[
  {
    "path": ".clang-format",
    "content": "BasedOnStyle: Google\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = false\ninsert_final_newline = true"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-bug-report.en-US.yml",
    "content": "name: '🐞 Bug Report'\ndescription: Report a bug to Lynx\ntitle: '[Bug]: '\ntype: Bug\nlabels: ['pending triage']\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thank you for taking the time to report this issue! Before submitting, please note:\n\n         - Confirm that your problem cannot be solved by official documentation.\n         - Make sure you've searched in the [Issues](https://github.com/lynx-family/lynx/issues) and haven't found the same issue.\n         - If it's not bug report, please post on the [Discussions](https://github.com/lynx-family/lynx/discussions).\n\n  - type: textarea\n    id: system-info\n    attributes:\n      label: System Info\n      description: Output of `npx envinfo --system --npmPackages '@lynx-js/*' --binaries`\n      placeholder: |\n        System:\n        Binaries:\n        npmPackages:\n    validations:\n      required: true\n\n  - type: textarea\n    id: details\n    attributes:\n      label: Details\n      description: Please describe the bug, it would be better to provide some screenshots.\n    validations:\n      required: true\n\n  - type: input\n    id: reproduce\n    attributes:\n      label: Reproduce link\n      description: 'Please provide a URL of the repository that reproduces the problem. We recommend the [Lynx repro template](https://github.com/lynx-family/lynx-repro) for creating a minimal reproducible example.'\n      placeholder: paste link here\n\n  - type: textarea\n    id: reproduce-steps\n    attributes:\n      label: Reproduce Steps\n      description: Please provide the simplest steps so that we can quickly reproduce the problem.\n      placeholder: |\n        For example:\n        1. Run `npm run dev`\n        2. Find some error messages\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-feature-request.en-US.yml",
    "content": "name: '✨ Feature Request'\ndescription: Submit a new feature request to Lynx\ntitle: '[Feature]: '\ntype: Feature\nlabels: ['pending triage']\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thank you for submitting a new feature request! Before submitting, please note:\n\n         - Confirm that this is a common feature and cannot be implemented using existing APIs.\n         - Make sure you've searched in the [Issues](https://github.com/lynx-family/lynx/issues) and haven't found the same request.\n         - You can discuss the feature in the [Discussions](https://github.com/lynx-family/lynx/discussions) first.\n\n  - type: textarea\n    id: description\n    attributes:\n      label: What problem does this feature solve?\n      description: Please describe the use case for this feature.\n    validations:\n      required: true\n\n  - type: textarea\n    id: api\n    attributes:\n      label: What does the proposed API of configuration look like?\n      description: Describe the new API, give some code examples.\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "---\ncontact_links:\n  - about: 'Please raise issues about framework or toolset on its own repo.'\n    name: Framework or Toolset\n    url: 'https://github.com/lynx-family/lynx-stack/issues'\n  - about: 'Please raise issues about the site on its own repo.'\n    name: Website\n    url: 'https://github.com/lynx-family/lynx-website/issues'\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\n  Thank you for submitting a pull request!\n\n  We appreciate the time and effort you have invested in making these changes. Please ensure that you provide enough information to allow others to review your pull request.\n\n  Upon submission, your pull request will be automatically assigned with reviewers.\n\n  If you want to learn more about contributing to this project, please visit: https://github.com/lynx-family/lynx/blob/develop/CONTRIBUTING.md.\n-->\n\n## Summary\n\n<!-- Can you explain the reasoning behind implementing this change? What problem or issue does this pull request resolve? -->\n\n<!-- It would be helpful if you could provide any relevant context, such as GitHub issues or related discussions. -->\n\n## Checklist\n\n<!--- Check and mark with an \"x\" -->\n\n- [ ] Tests updated (or not required).\n- [ ] Documentation updated (or not required).\n"
  },
  {
    "path": ".github/actions/android-explorer-build/action.yml",
    "content": "name: build android explorer\n\ndescription: build android explorer apk\n\ninputs:\n  abi-list:\n    description: The abi list of the sdk to publish\n    required: false\n    default: 'armeabi-v7a'\n  build-params:\n    description: Set project properties for gradle when building explorer\n    required: false\n    default: ''\n\nruns:\n  using: composite\n  steps:\n    - name: Python Setup\n      uses: actions/setup-python@v5\n      with:\n        python-version: '3.13'\n    - name: Install Common Dependencies\n      uses: ./lynx/.github/actions/common-deps\n    - name: Build Explorer App\n      shell: bash\n      run: |-\n        cd $GITHUB_WORKSPACE/lynx\n        source tools/envsetup.sh\n        pushd explorer/android\n        ./gradlew :LynxExplorer:assembleNoasanRelease -Penable_trace=perfetto -PabiList=${{ inputs.abi-list }} -no-daemon ${{ inputs.build-params }}\n        popd\n    - name: upload artifact\n      uses: lynx-infra/upload-artifact@332ec52e99f7cbf1fbbdc9bcc09280d49147d092\n      continue-on-error: true\n      with:\n        name: android-lynx-explorer\n        path: '${{ github.workspace }}/lynx/explorer/android/lynx_explorer/build/outputs/apk/noasan/release/LynxExplorer-noasan-release.apk'\n"
  },
  {
    "path": ".github/actions/android-sdk-release/action.yml",
    "content": "name: build android sdk\n\ndescription: build android sdk and publish aar to maven if build-type is 'release'\n\ninputs:\n  version:\n    description: The version of the sdk to publish\n    required: true\n  signingKeyId:\n    description: The key id of the sdk to publish\n    required: false\n    default: ''\n  signingPassword:\n    description: The password of the sdk to publish\n    required: false\n  signingSecretKey:\n    description: The secret key of the sdk to publish\n    required: false\n  abiList:\n    description: The abi list of the sdk to publish\n    required: false\n    default: 'armeabi-v7a'\n\nruns:\n  using: composite\n  steps:\n    - name: Build artifact\n      shell: bash\n      run: |-\n        cd $GITHUB_WORKSPACE/lynx\n        source tools/envsetup.sh\n        pushd platform/android\n        ./gradlew assembleAllModulesRelease -Penable_trace=none -PabiList=${{ inputs.abiList }} -PbuildLynxDebugSo -no-daemon\n        ./gradlew assembleAllModulesDevRelease -Penable_trace=perfetto -PabiList=${{ inputs.abiList }} -no-daemon\n        ./gradlew publishAllModules \\\n          -Pversion=${{ inputs.version || '0.0.1-alpha.1' }} \\\n          -Psigning.keyId=${{ inputs.signingKeyId }} \\\n          -Psigning.password=${{ inputs.signingPassword }} \\\n          -Psigning.secretKey=${{ inputs.signingSecretKey }}\n        ./gradlew publishAllModulesDevRelease \\\n          -Pversion=\"${{ inputs.version || '0.0.1-alpha.1' }}-dev\" \\\n          -Psigning.keyId=${{ inputs.signingKeyId }} \\\n          -Psigning.password=${{ inputs.signingPassword }} \\\n          -Psigning.secretKey=${{ inputs.signingSecretKey }}\n        popd\n"
  },
  {
    "path": ".github/actions/clay-macos-build/action.yml",
    "content": "name: build clay example\n\ndescription: build clay example\n\nruns:\n  using: composite\n  steps:\n    - name: Install Common Dependencies\n      uses: ./lynx/.github/actions/common-deps\n      with:\n        target: 'clay'\n    - name: Build Clay Example\n      shell: bash\n      run: |-\n        cd $GITHUB_WORKSPACE/lynx\n        source tools/envsetup.sh\n        buildtools/gn/gn gen out/Default --args='enable_clay_standalone = true disable_visibility_hidden = true use_ndk_static_cxx = false enable_linker_map = false enable_clay = true is_headless = true skia_enable_flutter_defines = true skia_use_dng_sdk = false skia_use_sfntly = false skia_enable_pdf = false skia_enable_svg = true enable_svg = true skia_enable_skottie = true skia_use_x11 = false skia_use_wuffs = true skia_use_expat = true skia_use_fontconfig = false clay_enable_skshaper = true skia_use_icu = true skia_gl_standard = \"\" allow_deprecated_api_calls = true stripped_symbols = true is_official_build = true is_debug = false use_clang_static_analyzer = false enable_lto = false' --export-compile-commands\n        buildtools/ninja/ninja -C out/Default clay/example/glfw\n"
  },
  {
    "path": ".github/actions/common-deps/action.yml",
    "content": "name: install common dependencies\n\ndescription: pull common source and binary dependencies via habitat.\n\ninputs:\n  concurrency:\n    description: concurrency of requests\n    default: '2'\n    required: false\n  cache-backend:\n    description: cache backend for habitat\n    default: 'lynx-infra'\n    required: false\n  target:\n    description: habitat target\n    default: ''\n    required: false\n  # actions/cache stores files with relative directory, which causes inconsistency between self-hosted runner and\n  # container runner. Therefore, add this temporary option to modify cache key prefix as a workaround. Delete this when\n  # we are able to adjust the container runner's directory structure to the same as self-hosted runner.\n  cache-key-prefix:\n    description: cache key prefix for cache action\n    default: ''\n    required: false\n\nruns:\n  using: composite\n  steps:\n    - name: Get habitat cache key\n      id: hab\n      shell: bash\n      run: |\n        if [[ \"${{ inputs.target }}\" == \"\" ]]; then\n          python3 lynx/tools/ci/habitat_cache_helper.py\n        else\n          python3 lynx/tools/ci/habitat_cache_helper.py --target ${{ inputs.target }}\n        fi\n\n    - name: Setup cache action\n      if: ${{ inputs.cache-backend == 'lynx-infra' }}\n      uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n      with:\n        path: ~/.habitat_cache\n        key: hab${{ inputs.cache-key-prefix }}-${{ runner.os }}-${{ steps.hab.outputs.HABITAT_TARGETS }}-${{ steps.hab.outputs.HABITAT_DEPS_FILE_DIGEST }}\n        restore-keys: |\n          hab${{ inputs.cache-key-prefix }}-${{ runner.os }}-${{ steps.hab.outputs.HABITAT_TARGETS }}-\n\n    - name: Setup cache action\n      if: ${{ inputs.cache-backend == 'github' }}\n      uses: actions/cache@v4\n      with:\n        path: ~/.habitat_cache\n        key: hab-${{ runner.os }}-${{ steps.hab.outputs.HABITAT_TARGETS }}-${{ steps.hab.outputs.HABITAT_DEPS_FILE_DIGEST }}\n        restore-keys: |\n          hab-${{ runner.os }}-${{ steps.hab.outputs.HABITAT_TARGETS }}-\n\n    - name: Get Python Cache Directory\n      id: pip\n      shell: bash\n      if: ${{ inputs.cache-backend == 'lynx-infra' }}\n      run: echo \"PIP_CACHE_DIR=$(python3 -m pip cache dir)\" >> $GITHUB_OUTPUT\n\n    - name: Python Requirements Cache\n      if: ${{ inputs.cache-backend == 'lynx-infra' }}\n      uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n      with:\n        path: ${{ steps.pip.outputs.PIP_CACHE_DIR }}\n        key: pip${{ inputs.cache-key-prefix }}-${{ runner.os }}-${{ hashFiles('lynx/tools/vpython_tools/requirements.txt', 'lynx/testing/integration_test/test_script/requirements.txt') }}\n        restore-keys: |\n          pip${{ inputs.cache-key-prefix }}-${{ runner.os }}-\n\n    - name: Cache pnpm Dependencies\n      if: ${{ inputs.cache-backend == 'lynx-infra' && inputs.cache-key-prefix == '' }}\n      uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n      with:\n        # set pnpm store path to a specific value since we can't get it via `pnpm store path` before pnpm is setup.\n        path: ~/.local/share/pnpm/store\n        key: pnpm-${{ runner.os }}-${{ hashFiles('lynx/pnpm-lock.yaml') }}\n        restore-keys: |\n          pnpm-${{ runner.os }}-\n\n    - name: Install Python dependencies\n      uses: nick-fields/retry@v3\n      with:\n        max_attempts: 3\n        retry_wait_seconds: 3\n        timeout_minutes: 20\n        command: |\n          set -e\n          if [ \"$RUNNER_OS\" == \"macOS\" ]; then\n            python3 -m pip install -r lynx/tools/vpython_tools/requirements.txt --break-system-packages --retries 10 --timeout 60\n            python3 -m pip install -r lynx/testing/integration_test/test_script/requirements.txt --break-system-packages --retries 10 --timeout 60\n          else\n            python3 -m pip install -r lynx/tools/vpython_tools/requirements.txt --retries 10 --timeout 60\n            python3 -m pip install -r lynx/testing/integration_test/test_script/requirements.txt --retries 10 --timeout 60\n          fi\n\n    - name: run habitat sync\n      shell: bash\n      working-directory: lynx\n      run: |\n        if [[ \"${{ inputs.target }}\" == \"\" ]]; then\n          tools/hab sync .\n        else\n          tools/hab sync . --target ${{ inputs.target }}\n        fi\n      env:\n        HABITAT_CONCURRENCY: ${{ inputs.concurrency }}\n        npm_config_store_dir: ~/.local/share/pnpm/store\n"
  },
  {
    "path": ".github/actions/free-android-disk/action.yml",
    "content": "name: Free Android Disk Space\n\ndescription: Free up disk space on Android runner.\n\nruns:\n  using: composite\n  steps:\n    - name: Free up disk space\n      shell: bash\n      working-directory: /home/runner\n      run: |\n        CLEAN_DIRS=(\n          \"/var/lib/docker\"\n          \"/usr/local/lib/node_modules\"\n        )\n        for dir in \"${CLEAN_DIRS[@]}\"; do\n          if [ -d \"$dir\" ]; then\n            echo \"Deleting $dir...\"\n            sudo rm -rf \"$dir\"\n          else\n            echo \"$dir does not exist, skipping...\"\n          fi\n        done\n        sudo apt-get clean > /dev/null 2>&1\n"
  },
  {
    "path": ".github/actions/generate-changelog/action.yml",
    "content": "name: generate changelog\n\ndescription: Using git-cliff to generate changelog according to the given reference range\n\ninputs:\n  ref-from:\n    description: The starting git reference (tag name or commit hash) (excluded) from which the changelog will be generated\n    required: true\n  ref-to:\n    description: The ending git reference (tag name or commit hash) (included) up to which the changelog will be generated\n    required: true\n  running-path:\n    description: Path to the running of git-cliff command\n    required: false\n    default: 'lynx'\n  config-path:\n    description: Path to the configuration file for git-cliff, relative to lynx/\n    required: false\n    default: 'tools/cliff.toml'\n  changelog-path:\n    description: Path to the generated changelog file, relative to lynx/\n    required: false\n    default: 'CHANGELOG.md'\n\nruns:\n  using: composite\n  steps:\n    - name: Download git-cliff\n      shell: bash\n      env:\n        GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }}\n      run: |-\n        ARCHIVE_EXT='tar.gz'\n        ARCHIVE_CMD='tar -xf'\n        GIT_CLIFF_BIN='git-cliff'\n        ARCH=$(uname -m | tr '[:upper:]' '[:lower:]')\n        case \"$ARCH\" in\n            arm64) ARCH=aarch64 ;;\n            arm)   ARCH=pc-windows-msvc ;;\n            x86)   ARCH=i686 ;;\n            *)     ARCH=x86_64 ;;\n        esac\n        OS=$(uname -s | tr '[:upper:]' '[:lower:]')\n        case \"$OS\" in\n            darwin) OS=apple-darwin ;;\n            *)      OS=unknown-linux-gnu ;;\n        esac\n        INSTALL_DIR=\"$RUNNER_TEMP/git-cliff\"\n        mkdir -p \"$INSTALL_DIR\"\n        cd \"$INSTALL_DIR\"\n        echo \"git-cliff-${ARCH}-${OS}.${ARCHIVE_EXT}\"\n        RELEASE_URL='https://api.github.com/repos/orhun/git-cliff/releases/latest'\n        RELEASE_INFO=\"$(curl --silent --show-error --fail \\\n          --header \"authorization: Bearer ${GITHUB_TOKEN}\" \\\n          --header 'Cache-Control: no-cache, must-revalidate' \\\n          \"${RELEASE_URL}\")\"\n        TAG_NAME=\"$(echo \"${RELEASE_INFO}\" | jq --raw-output \".tag_name\")\"\n        TARGET=\"git-cliff-${TAG_NAME:1}-${ARCH}-${OS}.${ARCHIVE_EXT}\"\n        LOCATION=\"$(echo \"${RELEASE_INFO}\" |\n          jq --raw-output \".assets[].browser_download_url\" |\n          grep \"${TARGET}$\")\"\n        echo \"Found release: ${LOCATION}\"\n        mkdir -p ./bin\n        if [[ ! -e \"$TARGET\" ]]; then\n          echo \"Downloading ${TARGET}...\"\n          curl --silent --show-error --fail --location --output \"$TARGET\" \"$LOCATION\"\n          echo \"Unpacking ${TARGET}...\"\n          ${ARCHIVE_CMD} \"$TARGET\"\n          mv git-cliff-${TAG_NAME:1}/${GIT_CLIFF_BIN} \"./bin/$GIT_CLIFF_BIN\"\n        else\n          echo \"Using cached git-cliff binary.\"\n        fi\n        chmod +x \"./bin/$GIT_CLIFF_BIN\"\n        echo \"git-cliff is ready to use!\"\n    - name: Generate Changelog\n      shell: bash\n      env:\n        GITHUB_TOKEN: ${{ env.GITHUB_TOKEN }}\n      run: |-\n        cd \"$GITHUB_WORKSPACE/${{ inputs.running-path }}\"\n        DIR_PATH=$(dirname \"${{ inputs.changelog-path }}\")\n        mkdir -p \"$DIR_PATH\"\n        \"$RUNNER_TEMP/git-cliff/bin/git-cliff\" ${{ inputs.ref-from }}..${{ inputs.ref-to }} -c ${{ inputs.config-path }} -o ${{ inputs.changelog-path }}\n        if [[ \"$(uname)\" == \"Darwin\" ]]; then\n          sed -i '' '/^\\t*$/d' \"${{ inputs.changelog-path }}\"\n        else\n          sed -i '/^\\t*$/d' \"${{ inputs.changelog-path }}\"\n        fi\n        echo \"changelog-path: $GITHUB_WORKSPACE/${{ inputs.running-path }}/${{ inputs.changelog-path }}\"\n"
  },
  {
    "path": ".github/actions/get-latest-tag/action.yml",
    "content": "name: Get Latest Ancestor Tag\n\ndescription: Get the latest ancestor tag of the current commit.\n\ninputs:\n  current-tag:\n    description: The current publishing tag\n    required: true\n\noutputs:\n  latest-tag:\n    description: 'The latest tag'\n    value: ${{ steps.get-tag.outputs.latest-tag }}\n\nruns:\n  using: composite\n  steps:\n    - name: get latest ancestor tag\n      id: get-ancestor-tag\n      working-directory: lynx\n      shell: bash\n      run: |\n        set -e\n        pwd\n        ANCESTOR_TAG=$(git describe --tags --exclude=${{ inputs.current-tag }} --abbrev=0 2>/dev/null)\n        echo \"ANCESTOR_TAG=$ANCESTOR_TAG\" >> $GITHUB_ENV\n\n    - name: get latest tag from api\n      id: get-latest-tag-from-api\n      shell: bash\n      run: |\n        REPO=\"${{ github.repository }}\"\n        LATEST_TAG=$(curl -s \"https://api.github.com/repos/$REPO/releases/latest\" | jq -r '.tag_name')\n        if [ \"$LATEST_TAG\" = \"null\" ] || [ \"$LATEST_TAG\" = \"${{ inputs.current-tag }}\" ]; then\n          LATEST_TAG=$(curl -s \"https://api.github.com/repos/$REPO/releases?per_page=20\" \\\n            | jq -r --arg CURRENT \"${{ inputs.current-tag }}\" \\\n              '.[] | select(\n                .draft == false and \n                .tag_name != $CURRENT\n              ) | .tag_name' \\\n            | head -n 1)\n        fi\n        echo \"LATEST_TAG=$LATEST_TAG\" >> $GITHUB_ENV\n\n    - id: get-tag\n      shell: bash\n      run: |\n        if [ -n \"${{ env.ANCESTOR_TAG }}\" ]; then\n          FINAL_TAG=\"${{ env.ANCESTOR_TAG }}\"\n        else\n          FINAL_TAG=\"${{ env.LATEST_TAG }}\"\n        fi\n\n        echo \"Latest tag found: $FINAL_TAG\"\n        echo \"latest-tag=$FINAL_TAG\" >> $GITHUB_OUTPUT\n"
  },
  {
    "path": ".github/actions/harmony-explorer-build/action.yml",
    "content": "name: build harmony explorer\n\ndescription: build harmony explorer hap\n\nruns:\n  using: composite\n  steps:\n    - name: Install Common Dependencies\n      uses: ./lynx/.github/actions/common-deps\n      with:\n        cache-key-prefix: '-container'\n    - name: Build Explorer App\n      shell: bash\n      working-directory: lynx\n      run: |-\n        source tools/envsetup.sh\n        pushd platform/harmony && ohpm install && popd\n        pushd explorer/harmony && ohpm install && popd\n        python3 explorer/harmony/script/build.py --dev --build_lynx_core --build_bundle --build_hap\n    - name: upload artifact\n      uses: lynx-infra/upload-artifact@332ec52e99f7cbf1fbbdc9bcc09280d49147d092\n      continue-on-error: true\n      with:\n        name: harmony-lynx-explorer\n        path: '${{ github.workspace }}/lynx/explorer/harmony/lynx_explorer/build/default/outputs/default/lynx_explorer-default-unsigned.hap'\n"
  },
  {
    "path": ".github/actions/ios-common-deps/action.yml",
    "content": "name: setup ruby cache\n\ndescription: setup ruby cache for the Lynx iOS build stage\n\ninputs:\n  path:\n    default: 'lynx/ruby'\n    required: false\n    type: string\n  cache-backend:\n    description: cache backend for habitat\n    default: 'lynx-infra'\n    required: false\n\nruns:\n  using: composite\n  steps:\n    - name: get ruby version\n      id: get_ruby_version\n      shell: bash\n      run: |-\n        ruby_version=$(ruby -v | awk '{print $2}')\n        ruby_version=$(echo $ruby_version | grep -oE '[0-9]+\\.[0-9]+\\.[0-9]+')\n        ruby_version=$(echo $ruby_version | awk -F. '{print $1\".\"$2\".0\"}')\n        echo \"VERSION=$ruby_version\" >> $GITHUB_OUTPUT;\n\n    - name: setup ruby cache action\n      if: ${{ inputs.cache-backend == 'lynx-infra' }}\n      uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n      with:\n        path: ${{ inputs.path }}/${{ steps.get_ruby_version.outputs.VERSION }}\n        key: ${{ runner.os }}-ruby-${{ steps.get_ruby_version.outputs.VERSION }}-${{ hashFiles('lynx/Gemfile.lock') }}\n        restore-keys: |\n          ${{ runner.os }}-ruby-${{ steps.get_ruby_version.outputs.VERSION }}\n    - name: setup cache action\n      if: ${{ inputs.cache-backend == 'github' }}\n      uses: actions/cache@v4\n      with:\n        path: ${{ inputs.path }}/${{ steps.get_ruby_version.outputs.VERSION }}\n        key: ${{ runner.os }}-ruby-${{ steps.get_ruby_version.outputs.VERSION }}-${{ hashFiles('lynx/Gemfile.lock') }}\n        restore-keys: |\n          ${{ runner.os }}-ruby-${{ steps.get_ruby_version.outputs.VERSION }}\n"
  },
  {
    "path": ".github/actions/ios-explorer-build/action.yml",
    "content": "name: build ios explorer\n\ndescription: build ios explorer app\n\ninputs:\n  build-arch:\n    description: The architecture of explorer we are building. It could be x86_64,arm64\n    required: true\n  build-params:\n    description: The build params of explorer we are building.\n    required: false\n    default: ''\n\nruns:\n  using: composite\n  steps:\n    - name: Install Common Dependencies\n      uses: ./lynx/.github/actions/common-deps\n    - name: Setup Ruby Cache\n      uses: ./lynx/.github/actions/ios-common-deps\n    - name: Install iOS Dependencies\n      uses: nick-fields/retry@v2\n      with:\n        timeout_minutes: 20\n        max_attempts: 3\n        command: |-\n          set -e\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd explorer/darwin/ios/lynx_explorer\n          git config --global url.\"https://github.com/\".insteadOf \"git@github.com:\"\n          bash bundle_install.sh ${{ inputs.build-params }}\n          popd\n    - name: Build Explorer App\n      shell: bash\n      run: |-\n        cd $GITHUB_WORKSPACE/lynx\n        source tools/envsetup.sh\n        pushd explorer/darwin/ios/lynx_explorer\n        xcodebuild -workspace LynxExplorer.xcworkspace -scheme LynxExplorer -configuration Debug -arch ${{ inputs.build-arch }} -archivePath build/LynxExplorer.xcarchive -derivedDataPath iOSCoreBuild/DerivedData -resultBundlePath results_bundle -showBuildTimingSummary -sdk iphonesimulator LD_GENERATE_MAP_FILE=YES LD_MAP_FILE_PATH=Build/linkmap.txt -jobs 24 build\n        popd\n        tar --strip-components 10 -czvf LynxExplorer-${{ inputs.build-arch }}.app.tar.gz explorer/darwin/ios/lynx_explorer/iOSCoreBuild/DerivedData/Build/Products/Debug-iphonesimulator/LynxExplorer.app\n"
  },
  {
    "path": ".github/actions/ios-sdk-publish/action.yml",
    "content": "name: publish ios sdk\n\ndescription: package ios source code, upload the zip to release and publish the podspec file to cocoapods\n\ninputs:\n  version:\n    required: false\n    description: The version of pod.\n    type: string\n  tag:\n    required: false\n    type: string\n    description: The tag for release.\n  component:\n    required: false\n    type: string\n    default: 'all'\n  cocoapods_trunk_token:\n    description: The secret key of the sdk to publish\n    type: string\n  github_token:\n    description: The github token\n    type: string\n\nruns:\n  using: composite\n  steps:\n    - name: Prepare cocoapods publish source\n      shell: bash\n      run: |-\n        cd $GITHUB_WORKSPACE/lynx\n        source tools/envsetup.sh\n        python3 tools/ios_tools/cocoapods_publish_helper.py --prepare-source --tag ${{ inputs.tag }} --version ${{ inputs.version }} --component ${{ inputs.component }}\n    - name: Push iOS SDK to release\n      uses: ncipollo/release-action@v1\n      with:\n        tag: ${{ inputs.tag }}\n        token: ${{ inputs.github_token }}\n        artifacts: '${{ github.workspace }}/lynx/*.zip'\n        replacesArtifacts: true\n        allowUpdates: true\n        body: ''\n    - name: Publish to CocoaPods Repo\n      env:\n        COCOAPODS_TRUNK_TOKEN: ${{ inputs.cocoapods_trunk_token }}\n      shell: bash\n      run: |-\n        cd $GITHUB_WORKSPACE/lynx\n        source tools/envsetup.sh\n        git config --global user.name \"lynx.authors\"\n        git config --global user.email \"lynx.authors@users.noreply.github.com\"\n        python3 tools/ios_tools/skip_pod_lint.py --source public\n        python3 tools/ios_tools/cocoapods_publish_helper.py --publish --component ${{ inputs.component }}\n"
  },
  {
    "path": ".github/actions/macos-explorer-build/action.yml",
    "content": "name: build macos explorer\n\ndescription: build macos explorer\n\ninputs:\n  build-arch:\n    description: The architecture of explorer we are building. It could be x64,arm64\n    required: true\n\nruns:\n  using: composite\n  steps:\n    - name: Install Common Dependencies\n      uses: ./lynx/.github/actions/common-deps\n      with:\n        target: 'clay'\n    - name: Build macOS Explorer\n      shell: bash\n      run: |-\n        cd $GITHUB_WORKSPACE/lynx\n        source tools/envsetup.sh\n        pnpm install\n        buildtools/gn/gn gen out/Default --args='target_cpu = \"${{ inputs.build-arch }}\" desktop_enable_embedder_layer = true enable_clay_standalone = true disable_visibility_hidden = true use_ndk_static_cxx = false enable_linker_map = false enable_clay = true is_headless = true skia_enable_flutter_defines = true skia_use_dng_sdk = false skia_use_sfntly = false skia_enable_pdf = false skia_enable_svg = true enable_svg = true skia_enable_skottie = true skia_use_x11 = false skia_use_wuffs = true skia_use_expat = true skia_use_fontconfig = false clay_enable_skshaper = true skia_use_icu = true skia_gl_standard = \"\" skia_use_metal = true shell_enable_metal = true allow_deprecated_api_calls = true stripped_symbols = true is_official_build = true use_clang_static_analyzer = false enable_lto = false enable_lepusng_worklet = true enable_napi_binding = true enable_inspector = true jsengine_type=\"quickjs\" use_flutter_cxx = false is_debug = false'\n        buildtools/ninja/ninja -C out/Default explorer platform/darwin/macos:package_sdk\n        tar --strip-components 2 -czvf LynxExplorer-macos-${{ inputs.build-arch }}.app.tar.gz out/Default/LynxExplorer.app\n"
  },
  {
    "path": ".github/actions/release-changelog/action.yml",
    "content": "name: release changelog\n\ndescription: Truncating changelog to max 124000 chars and pushing changelog to release note\n\ninputs:\n  version:\n    description: Version (tag) of the release\n    required: true\n  changelog-path:\n    description: Path to the changelog file, relative to lynx/\n    required: false\n    default: 'CHANGELOG.md'\n  token:\n    description: secrets.GITHUB_TOKEN\n    required: true\n\nruns:\n  using: composite\n  steps:\n    - name: Truncate Changelog If Necessary\n      shell: bash\n      run: |-\n        cd $GITHUB_WORKSPACE/lynx\n        MAX_CHARS=124000\n        CHANGELOG=\"${{ inputs.changelog-path }}\"\n        TRUNCATED=\"$CHANGELOG-truncated.md\"\n        if [[ ! -f \"$CHANGELOG\" ]]; then\n          echo \"Error: file '$CHANGELOG' not found.\">&2\n          exit 1\n        fi\n        content=$(cat \"$CHANGELOG\")\n        if [[ ${#content} -le $MAX_CHARS ]]; then\n          cp \"$CHANGELOG\" \"$TRUNCATED\"\n          exit 0\n        fi\n        truncated=$(head -c \"$MAX_CHARS\" <<< \"$content\")\n        full_body=\"$truncated\\n\\n---\\n📄 View the full changelog [here]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/blob/$GITHUB_SHA/$CHANGELOG)\"\n        echo -e \"$full_body\" > \"$TRUNCATED\"\n    - name: Push to Release\n      uses: ncipollo/release-action@v1\n      with:\n        tag: ${{ inputs.version }}\n        token: ${{ inputs.token }}\n        artifacts: '${{ github.workspace }}/lynx/${{ inputs.changelog-path }}'\n        bodyFile: '${{ github.workspace }}/lynx/${{ inputs.changelog-path }}-truncated.md'\n        allowUpdates: true\n"
  },
  {
    "path": ".github/actions/setup-android-env/action.yml",
    "content": "name: setup android enviroment\n\ndescription: Setup Android enviroment, which includes JDK, Android SDK and NDK.\n\nruns:\n  using: composite\n  steps:\n    - name: Setup Java SDK\n      uses: actions/setup-java@v3\n      with:\n        java-version: 11\n        distribution: 'zulu'\n        cache: 'gradle'\n    - name: Setup Android SDK\n      uses: android-actions/setup-android@v3\n      with:\n        cmdline-tools-version: 9862592\n        include-emulator: false\n        include-preview: false\n    - name: Setup Android NDK\n      shell: bash\n      run: |-\n        sdkmanager \"ndk;21.1.6352462\"\n        export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/21.1.6352462\n"
  },
  {
    "path": ".github/actions/windows-common-deps/action.yml",
    "content": "name: install windows common dependencies\n\ndescription: pull common source and binary dependencies via habitat for windows.\n\ninputs:\n  concurrency:\n    description: concurrency of requests\n    default: '2'\n    required: false\n  cache-backend:\n    description: cache backend for habitat\n    default: 'lynx-infra'\n    required: false\n  target:\n    description: habitat target\n    default: ''\n    required: false\n  # actions/cache stores files with relative directory, which causes inconsistency between self-hosted runner and\n  # container runner. Therefore, add this temporary option to modify cache key prefix as a workaround. Delete this when\n  # we are able to adjust the container runner's directory structure to the same as self-hosted runner.\n  cache-key-prefix:\n    description: cache key prefix for cache action\n    default: ''\n    required: false\n\nruns:\n  using: composite\n  steps:\n    - name: Get habitat cache key\n      id: hab\n      shell: pwsh\n      run: |\n        if ([string]::IsNullOrEmpty(\"${{ inputs.target }}\")) {\n          python lynx/tools/ci/habitat_cache_helper.py\n        } else {\n          python lynx/tools/ci/habitat_cache_helper.py --target ${{ inputs.target }}\n        }\n\n    - name: Get habitat cache directory\n      id: hab-cache-dir\n      shell: pwsh\n      run: |\n        $habitat_cache_dir = python -c \"import os; import tempfile; print(os.path.join(os.environ.get('HOME', tempfile.gettempdir()), '.habitat_cache'))\"\n        echo \"HABITAT_CACHE_DIR=$habitat_cache_dir\" >> ${env:GITHUB_OUTPUT}\n\n    - name: Get npm cache directory\n      id: npm-cache-dir\n      shell: pwsh\n      run: |\n        $npm_cache_dir = python -c \"import os; import pathlib; print(os.path.join(pathlib.Path.home(), '.local', 'share', 'pnpm', 'store'))\"\n        echo \"NPM_CACHE_DIR=$npm_cache_dir\" >> ${env:GITHUB_OUTPUT}\n\n    - name: Get Python Cache Directory\n      id: pip\n      shell: pwsh\n      if: ${{ inputs.cache-backend == 'lynx-infra' }}\n      run: |\n        $cache = python -m pip cache dir\n        $correctPath = (Get-Item $cache).FullName\n        echo \"PIP_CACHE_DIR=$correctPath\" >> ${env:GITHUB_OUTPUT}\n\n    - name: Setup cache action\n      if: ${{ inputs.cache-backend == 'lynx-infra' }}\n      uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n      with:\n        path: ${{ steps.hab-cache-dir.outputs.HABITAT_CACHE_DIR }}\n        key: hab${{ inputs.cache-key-prefix }}-${{ runner.os }}-${{ steps.hab.outputs.HABITAT_TARGETS }}-${{ steps.hab.outputs.HABITAT_DEPS_FILE_DIGEST }}\n        restore-keys: |\n          hab${{ inputs.cache-key-prefix }}-${{ runner.os }}-${{ steps.hab.outputs.HABITAT_TARGETS }}-\n\n    - name: Setup cache action\n      if: ${{ inputs.cache-backend == 'github' }}\n      uses: actions/cache@v4\n      with:\n        path: ${{ steps.hab-cache-dir.outputs.HABITAT_CACHE_DIR }}\n        key: hab-${{ runner.os }}-${{ steps.hab.outputs.HABITAT_TARGETS }}-${{ steps.hab.outputs.HABITAT_DEPS_FILE_DIGEST }}\n        restore-keys: |\n          hab-${{ runner.os }}-${{ steps.hab.outputs.HABITAT_TARGETS }}-\n\n    - name: Python Requirements Cache\n      if: ${{ inputs.cache-backend == 'lynx-infra' }}\n      uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n      with:\n        path: ${{ steps.pip.outputs.PIP_CACHE_DIR }}\n        key: pip${{ inputs.cache-key-prefix }}-${{ runner.os }}-${{ hashFiles('lynx/tools/vpython_tools/requirements.txt', 'lynx/testing/integration_test/test_script/requirements.txt') }}\n        restore-keys: |\n          pip${{ inputs.cache-key-prefix }}-${{ runner.os }}-\n\n    - name: Cache pnpm Dependencies\n      if: ${{ inputs.cache-backend == 'lynx-infra' && inputs.cache-key-prefix == '' }}\n      uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n      with:\n        # set pnpm store path to a specific value since we can't get it via `pnpm store path` before pnpm is setup.\n        path: ${{ steps.npm-cache-dir.outputs.NPM_CACHE_DIR }}\n        key: pnpm-${{ runner.os }}-${{ hashFiles('lynx/pnpm-lock.yaml') }}\n        restore-keys: |\n          pnpm-${{ runner.os }}-\n\n    - name: Install Python dependencies\n      shell: pwsh\n      run: |\n        python -m pip install -r lynx/tools/vpython_tools/requirements.txt\n        python -m pip install -r lynx/testing/integration_test/test_script/requirements.txt\n\n    - name: run habitat sync\n      shell: pwsh\n      working-directory: lynx\n      run: |\n        if ([string]::IsNullOrEmpty(\"${{ inputs.target }}\")) {\n            powershell.exe -Command \"& .\\tools\\hab.ps1 sync .\"\n        } else {\n            powershell.exe -Command \"& .\\tools\\hab.ps1 sync . --target ${{ inputs.target }}\"\n        }\n      env:\n        HABITAT_CONCURRENCY: ${{ inputs.concurrency }}\n        npm_config_store_dir: ${{ steps.npm-cache-dir.outputs.NPM_CACHE_DIR }}\n"
  },
  {
    "path": ".github/actions/windows-explorer-build/action.yml",
    "content": "name: build windows explorer\n\ndescription: build windows explorer apk.\n\ninputs:\n  build-arch:\n    description: The architecture of explorer we are building. It could be x64,x86\n    required: true\nruns:\n  using: composite\n  steps:\n    - name: Python Setup\n      uses: actions/setup-python@v5\n      with:\n        python-version: '3.13'\n    - name: Setup Node.js 18.20.0\n      uses: actions/setup-node@v6\n      with:\n        node-version: '18.20.0'\n    - name: Get npm customcache directory\n      id: npm-custom-cache-dir\n      shell: pwsh\n      run: |\n        $npm_custom_cache_dir = python -c \"import os; import pathlib; print(os.path.join(pathlib.Path.home(), '.pnpm', 'v3'))\"\n        echo \"NPM_CUSTOM_CACHE_DIR=$npm_custom_cache_dir\" >> ${env:GITHUB_OUTPUT}\n    - name: Cache pnpm Dependencies\n      uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n      with:\n        path: ${{ steps.npm-custom-cache-dir.outputs.NPM_CUSTOM_CACHE_DIR }}\n        key: pnpm-custom-${{ runner.os }}-${{ hashFiles('lynx/pnpm-lock.yaml') }}\n        restore-keys: |\n          pnpm-custom-${{ runner.os }}-\n    - name: Install Common Dependencies\n      uses: ./lynx/.github/actions/windows-common-deps\n      with:\n        target: 'clay'\n    - name: Build Explorer App\n      shell: pwsh\n      run: |-\n        cd lynx\n        $env:vs2022_install = \"${env:ProgramFiles(x86)}\\Microsoft Visual Studio\\2022\\BuildTools\"\n        $env:DEPOT_TOOLS_WIN_TOOLCHAIN = 0\n        $env:GYP_MSVS_OVERRIDE_PATH = \"${env:ProgramFiles(x86)}\\Microsoft Visual Studio\\2022\\BuildTools\"\n        $env:WINDOWSSDKDIR = \"${env:ProgramFiles(x86)}\\Windows Kits\\10\"\n        .\\tools\\envsetup.ps1\n        .\\buildtools\\node\\pnpm install\n        $PSNativeCommandArgumentPassing = 'Legacy'\n        .\\buildtools\\gn\\gn.exe gen out\\Default --args='target_cpu=\\\"${{ inputs.build-arch }}\\\" desktop_enable_embedder_layer=true enable_clay_standalone=true disable_visibility_hidden=true use_ndk_static_cxx=false enable_linker_map=false enable_clay=true is_headless=true skia_enable_flutter_defines=true skia_use_dng_sdk=false skia_use_sfntly=false skia_enable_pdf=false skia_enable_svg=true enable_svg=true skia_enable_skottie=true skia_use_x11=false skia_use_wuffs=true skia_use_expat=true skia_use_fontconfig=false clay_enable_skshaper=true skia_use_icu=true allow_deprecated_api_calls=true stripped_symbols=true is_official_build=true enable_lto=false is_clang=true enable_lepusng_worklet=true enable_napi_binding=true is_debug=false enable_inspector=true jsengine_type=\\\"quickjs\\\"' --ide=vs\n        .\\buildtools\\ninja\\ninja.exe -C out\\Default explorer\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\n\non:\n  pull_request:\n  push:\n    branches:\n      - CQ-*\n      - cq-*\n\ndefaults:\n  run:\n    working-directory: lynx\n\njobs:\n  static-check:\n    runs-on: lynx-ubuntu-22.04-medium\n    timeout-minutes: 30\n    steps:\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          fetch-depth: 2\n          ref: ${{ github.event.pull_request.head.sha }}\n          path: lynx\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Run file type check\n        run: |\n          source tools/envsetup.sh\n          python3 tools_shared/git_lynx.py check --checkers file-type\n      - name: Run cpplint check\n        run: |\n          source tools/envsetup.sh\n          python3 tools_shared/git_lynx.py check --checkers cpplint\n      - name: Run java-lint check\n        run: |\n          source tools/envsetup.sh\n          python3 tools_shared/git_lynx.py check --checkers java-lint\n      - name: Run commit-message check\n        run: |\n          source tools/envsetup.sh\n          python3 tools_shared/git_lynx.py check --checkers commit-message\n      - name: Run coding-style check\n        run: |\n          source tools/envsetup.sh\n          python3 tools_shared/git_lynx.py check --checkers coding-style\n      - name: Run android-check-style check\n        run: |\n          source tools/envsetup.sh\n          python3 tools_shared/git_lynx.py check --checkers android-check-style\n      - name: Run api check\n        run: |\n          source tools/envsetup.sh\n          python3 tools_shared/git_lynx.py check --checkers api-check --all\n      - name: Run gn relative path check\n        run: |\n          source tools/envsetup.sh\n          python3 tools_shared/git_lynx.py check --checkers gn-relative-path-check\n\n  darwin-native-unittests-check:\n    timeout-minutes: 60\n    runs-on: lynx-darwin-14-medium\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Run Unittests\n        run: |\n          set -e\n          source tools/envsetup.sh\n          tools/rtf/rtf native-ut run --names lynx\n\n  linux-native-unittests-check:\n    runs-on: lynx-ubuntu-22.04-large\n    timeout-minutes: 60\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Run Unittests\n        run: |\n          set -e\n          source tools/envsetup.sh\n          tools/rtf/rtf native-ut run --names lynx\n\n  linux-native-devtool-unittests-check:\n    runs-on: lynx-ubuntu-22.04-large\n    timeout-minutes: 60\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Run Unittests\n        run: |\n          set -e\n          source tools/envsetup.sh\n          tools/rtf/rtf native-ut run --names devtool\n\n  ios-unittests-check:\n    timeout-minutes: 60\n    runs-on: lynx-darwin-14-medium\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Setup Ruby Cache\n        uses: ./lynx/.github/actions/ios-common-deps\n        with:\n          cache-backend: 'lynx-infra'\n      - name: Install iOS Dependencies\n        uses: nick-fields/retry@v2\n        with:\n          timeout_minutes: 20\n          max_attempts: 3\n          command: |\n            set -e\n            cd $GITHUB_WORKSPACE/lynx\n            source tools/envsetup.sh\n            pushd explorer/darwin/ios/lynx_explorer\n            git config --global url.\"https://github.com/\".insteadOf \"git@github.com:\"\n            bash bundle_install.sh --skip-card-build\n            popd\n      - name: Run Unittests\n        run: |\n          set -e\n          source tools/envsetup.sh\n          pushd explorer/darwin/ios/lynx_explorer\n          xcodebuild -showsdks | grep -Eo -m 1 \"iphonesimulator([0-9]{1,}\\.)+[0-9]{1,}\" > sdk.txt\n          sdkVersion=$(awk '{ sub(/iphonesimulator/,\"\"); print $0 }' sdk.txt)\n          echo $sdkVersion > sdk.txt\n          xcodebuild build-for-testing ARCHS=arm64 -workspace LynxExplorer.xcworkspace -scheme LynxExplorerTests -enableCodeCoverage YES -configuration Debug -sdk iphonesimulator$(cat sdk.txt) COMPILER_INDEX_STORE_ENABLE=NO -derivedDataPath iOSCoreBuild/DerivedData -dest\"platform=iOS Simulator,OS=$(cat sdk.txt),name=iPhone 11\" SYMROOT=`pwd`/Build/Products -testPlan UTTest\n          chmod u+x xctestrunner\n          ./xctestrunner --xctestrun `pwd`/Build/Products/LynxExplorerTests_UTTest_iphonesimulator$(cat sdk.txt)-arm64.xctestrun --work_dir `pwd` --output_dir `pwd`/iOSCoreBuild/DerivedData simulator_test\n          popd\n\n  tasm-linux-build:\n    runs-on: lynx-ubuntu-22.04-large\n    timeout-minutes: 60\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Build Linux Tasm\n        run: |\n          source tools/envsetup.sh\n          pushd oliver/lynx-tasm\n          npm install\n          npm run build:release:linux\n          popd\n\n  tasm-darwin-build:\n    timeout-minutes: 60\n    runs-on: lynx-darwin-14-medium\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Build Darwin Tasm\n        run: |\n          source tools/envsetup.sh\n          python3 tools/oliver/lynx-tasm/gn_to_cmake_oliver.py\n          pushd oliver/lynx-tasm\n          npm install\n          ../../buildtools/cmake/bin/cmake .\n          ../../buildtools/cmake/bin/cmake --build .\n          ./lepus_cmd\n\n          npm run build:release:darwin\n          popd\n\n  tasm-wasm-build:\n    timeout-minutes: 60\n    runs-on: lynx-darwin-14-medium\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          target: tasm\n      - name: Build Wasm Tasm\n        run: |\n          source tools/envsetup.sh\n          pushd oliver/lynx-tasm\n          npm install\n          npm run build:wasm\n          popd\n\n  lynx-types-check:\n    runs-on: lynx-ubuntu-22.04-medium\n    timeout-minutes: 60\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Lynx Types Check\n        run: |\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd js_libraries/types\n          npm run test\n          popd\n\n  android-unittests-check:\n    timeout-minutes: 60\n    runs-on: lynx-ubuntu-22.04-physical-medium\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Setup gradle cache\n        uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n        with:\n          path: ~/.gradle/caches/modules-2\n          key: gradle-android-unittests-check\n      - name: Build Example App\n        run: |\n          source tools/envsetup.sh\n          tools/rtf/rtf android-ut run --name lynx\n\n  android-explorer-build:\n    runs-on: lynx-ubuntu-22.04-large\n    timeout-minutes: 60\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Setup gradle cache\n        uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n        with:\n          path: ~/.gradle/caches/modules-2\n          key: gradle-android-explorer-build\n      - name: Build Explorer App\n        uses: ./lynx/.github/actions/android-explorer-build\n        with:\n          abi-list: x86,arm64-v8a\n          build-params: -PIntegrationTest\n\n  windows-explorer-build:\n    # Disable temporarily due to flakiness\n    if: false\n    runs-on: lynx-windows-2022-large\n    strategy:\n      matrix:\n        arch: [x86, x64]\n    steps:\n      - name: Configure Git\n        shell: PowerShell\n        run: |\n          git config --global core.autocrlf false\n          git config --global core.eol lf\n          git config --global http.sslBackend \"schannel\"\n        working-directory: ./\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Build windows Explorer\n        uses: ./lynx/.github/actions/windows-explorer-build\n        with:\n          build-arch: ${{ matrix.arch }}\n\n  android-sdk-release:\n    timeout-minutes: 60\n    runs-on: lynx-ubuntu-22.04-large\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: 'lynx-infra'\n      - name: Setup gradle cache\n        uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n        with:\n          path: ~/.gradle/caches/modules-2\n          key: gradle-android-sdk-release\n      - name: Build Android SDK\n        uses: ./lynx/.github/actions/android-sdk-release\n        with:\n          version: 0.0.1-alpha.1\n      - name: Collect and zip artifacts\n        run: |\n          cd $GITHUB_WORKSPACE/lynx/platform/android\n          ./gradlew zipAllArtifacts\n      - name: upload zip artifact\n        uses: lynx-infra/upload-artifact@332ec52e99f7cbf1fbbdc9bcc09280d49147d092\n        continue-on-error: true\n        with:\n          name: android-sdk-release\n          path: '${{ github.workspace }}/lynx/platform/android/build/artifacts-collection.zip'\n\n  android-e2e-test:\n    timeout-minutes: 60\n    needs: [android-explorer-build]\n    env:\n      APPIUM_TEST_SERVER_PORT: 4723\n      APPIUM_TEST_SERVER_HOST: 127.0.0.1\n    runs-on: lynx-ubuntu-22.04-physical-medium\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Install Appium\n        run: |\n          pushd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pnpm install appium@2.11.2 -w\n          pnpm install appium-espresso-driver@4.0.0 -w\n          popd\n      - name: Setup Java SDK\n        uses: actions/setup-java@v3\n        with:\n          java-version: 17\n          distribution: 'zulu'\n      - name: Gradle Wrapper Cache\n        uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n        with:\n          path: ~/.gradle/wrapper/dists\n          key: gradle-8.7-${{ runner.os }}\n      - name: Start Android 28 emulator\n        run: |\n          set -x\n          target_avd=$(emulator -list-avds | head -n 1)\n          if [ -z \"$target_avd\" ]; then\n              echo \"No available emulator was found!\"\n              exit 1\n          fi\n          export QT_QPA_PLATFORM=offscreen\n          nohup $ANDROID_HOME/emulator/emulator -no-window -avd $target_avd -no-snapshot -no-audio -no-boot-anim &\n          max_wait_time=300\n          start_time=$(date +%s)\n          while true; do\n              current_time=$(date +%s)\n              elapsed_time=$((current_time - start_time))\n              if [ $elapsed_time -gt $max_wait_time ]; then\n                  echo \"Emulator startup timed out.\"\n                  exit 1\n              fi\n              $ANDROID_HOME/platform-tools/adb devices\n              if $ANDROID_HOME/platform-tools/adb devices | grep -q \"device$\"; then\n                  echo \"Emulator startup completed.\"\n                  break\n              fi\n              echo \"Waiting for emulator to startup...\"\n              sleep 10\n          done\n          $ANDROID_HOME/platform-tools/adb devices\n      - name: Download Explorer APK\n        uses: lynx-infra/download-artifact@79d9914484f933089c2840552cf439bac85debad\n        with:\n          name: android-lynx-explorer\n          path: lynx/explorer/android/lynx_explorer/build_temp\n      - name: Repack and Install Explorer App\n        run: |\n          pushd $GITHUB_WORKSPACE/lynx\n          LATEST_VERSION=$(ls -1 \"$ANDROID_HOME/build-tools\" | sort -V | tail -n 1)\n          target_dir=$(find ./node_modules/.pnpm -type d -name \"appium-adb@*\" | head -n 1)\n          ESPRESSO_SIGN_KEYS_DIR=\"\"\n          if [ -n \"$target_dir\" ]; then\n              ESPRESSO_SIGN_KEYS_DIR=\"$target_dir/node_modules/appium-adb/keys\"\n          else\n              echo \"node_modules/.pnpm/appium-adb is not founded!\"\n              exit 1\n          fi\n          APK_PATH=${{ github.workspace }}/lynx/explorer/android/lynx_explorer/build_temp\n          java -jar $ANDROID_HOME/build-tools/$LATEST_VERSION/lib/apksigner.jar sign --key $ESPRESSO_SIGN_KEYS_DIR/testkey.pk8 --cert $ESPRESSO_SIGN_KEYS_DIR/testkey.x509.pem  --out $APK_PATH/LynxExplorer-noasan-release-signed.apk $APK_PATH/LynxExplorer-noasan-release.apk\n          adb install $APK_PATH/LynxExplorer-noasan-release-signed.apk\n      - name: Start Appium server\n        run: |\n          pushd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd node_modules/appium/node_modules/.bin\n          ls\n          nohup ./appium server \\\n            --port=$APPIUM_TEST_SERVER_PORT \\\n            --address=$APPIUM_TEST_SERVER_HOST \\\n            --log-no-colors \\\n            --log-timestamp \\\n            2>&1 > \"$GITHUB_WORKSPACE/lynx/appium.log\" &\n          adb logcat > \"$GITHUB_WORKSPACE/lynx/device.log\" 2>&1 &\n          popd\n          popd\n      - name: Check if Appium Server is running\n        run: |\n          max_attempts=10\n          attempt=0\n          while [ $attempt -lt $max_attempts ]; do\n            if nc -z $APPIUM_TEST_SERVER_HOST $APPIUM_TEST_SERVER_PORT; then\n              echo \"Appium Server is running.\"\n              break\n            else\n              attempt=$((attempt + 1))\n              echo \"Attempt $attempt: Appium Server is not yet running. Retrying in 2 seconds...\"\n              sleep 2\n            fi\n          done\n          if [ $attempt -eq $max_attempts ]; then\n            echo \"Failed to start Appium Server.\"\n            exit 1\n          fi\n      - name: Run Android E2E Test\n        uses: nick-fields/retry@v3\n        with:\n          max_attempts: 2\n          retry_wait_seconds: 5\n          timeout_minutes: 20\n          command: |\n            set -e\n            source $GITHUB_WORKSPACE/lynx/tools/envsetup.sh\n            pushd $GITHUB_WORKSPACE/lynx/testing/integration_test/test_script\n            export server_port=$APPIUM_TEST_SERVER_PORT\n            export platform=android\n            python3 -m pip install -r requirements.txt\n            python3 manage.py runtest android_test.core\n            echo \"TASK_STATUS=success\" >> $GITHUB_OUTPUT\n        id: run_test\n      - name: Collect Lynx-E2E execution logs\n        if: always()\n        run: |\n          TASK_STATUS=\"${{ steps.run_test.outputs.TASK_STATUS }}\"\n          if [ -n \"$TASK_STATUS\" ] && [ \"$TASK_STATUS\" = \"success\" ]; then\n            echo \"Test execution succeeded.\"\n          else\n            echo \"Test execution failed, collecting logs...\"\n            pushd $GITHUB_WORKSPACE/lynx/testing/integration_test/test_script\n            ls | grep -E \"^lynx_e2e_devtools_.*\\.log$\" | xargs cat\n            popd\n          fi\n      - name: Collect Appium Server logs\n        if: always()\n        run: |\n          TASK_STATUS=\"${{ steps.run_test.outputs.TASK_STATUS }}\"\n          if [ -n \"$TASK_STATUS\" ] && [ \"$TASK_STATUS\" = \"success\" ]; then\n            echo \"Test execution succeeded.\"\n          else\n            echo \"Test execution failed, collecting logs...\"\n            pushd $GITHUB_WORKSPACE/lynx/\n            if [ -f \"appium.log\" ]; then\n              cat appium.log\n            else\n              echo \"Warning: appium.log file not found\"\n            fi\n            popd\n          fi\n      - name: Collect Generated Resource Files\n        if: always()\n        run: |\n          TASK_STATUS=\"${{ steps.run_test.outputs.TASK_STATUS }}\"\n          if [ -n \"$TASK_STATUS\" ] && [ \"$TASK_STATUS\" = \"success\" ]; then\n            echo \"Test execution succeeded.\"\n          else\n            pushd $GITHUB_WORKSPACE/lynx/testing/integration_test/test_script\n            if [ -d \"screenshots\" ]; then\n              zip -r screenshots.zip ./screenshots/\n              echo \"Successfully created screenshots.zip\"\n              echo \"ZIP_RESOURCES=success\" >> $GITHUB_OUTPUT\n            else\n              echo \"Warning: screenshot directory not found\"\n            fi\n          fi\n        id: collect_resource\n      - name: Upload Generated Resource Files\n        if: always() && steps.collect_resource.outputs.ZIP_RESOURCES == 'success'\n        uses: lynx-infra/upload-artifact@332ec52e99f7cbf1fbbdc9bcc09280d49147d092\n        continue-on-error: true\n        with:\n          name: android-e2e-resources\n          path: '${{ github.workspace }}/lynx/testing/integration_test/test_script/screenshots.zip'\n\n  android-integeration-test:\n    needs: [android-sdk-release]\n    timeout-minutes: 15\n    runs-on: lynx-ubuntu-22.04-large\n    steps:\n      - name: Download Integeration Demo Source Code\n        uses: actions/checkout@v4.2.2\n        with:\n          repository: lynx-family/integrating-lynx-demo-projects\n          ref: b106f7f21d5221acfed645f4afa9d829a1cc0cc2\n          path: lynx\n      - name: Cherry Pick Commit To Build With Local AAR\n        run: |\n          echo \"GITHUB_WORKSPACE: $GITHUB_WORKSPACE\"\n          git config user.name \"GitHub Actions\"\n          git config user.email \"actions@github.com\"\n          git fetch origin 12858f5a4f51454057f738f0b1d2bb001986dc91\n          git cherry-pick 12858f5a4f51454057f738f0b1d2bb001986dc91\n      - name: Download Lynx SDK Artifacts\n        uses: lynx-infra/download-artifact@79d9914484f933089c2840552cf439bac85debad\n        with:\n          name: android-sdk-release\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Setup Java SDK\n        uses: actions/setup-java@v3\n        with:\n          java-version: 17\n          distribution: 'zulu'\n      - name: Gradle Wrapper Cache\n        uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n        with:\n          path: ~/.gradle/wrapper/dists\n          key: gradle-wrapper-${{ runner.os }}\n      - name: Setup cache action\n        uses: lynx-infra/cache@5c6160a6a4c7fca80a2f3057bb9dfc9513fcb732\n        with:\n          path: ~/.gradle/caches/modules-2\n          key: gradle-integration-deps-${{ runner.os }}\n      - name: Build With Local AAR\n        id: build_apk\n        run: |\n          set -e\n          pushd android/JavaEmptyProject/\n          python3 shell/prepare_package_with_local_aar.py $GITHUB_WORKSPACE/lynx/artifacts-collection.zip 0.0.1-alpha.1\n          ./gradlew publishAllZips\n          ./gradlew :app:assembleDebug\n          popd\n\n  harmony-explorer-build:\n    runs-on: lynx-custom-container\n    container:\n      image: ghcr.io/lynx-family/ubuntu24.04-harmony@sha256:bf493c3710de3c44bfa84caf402c0727b9310c42497f6e52580ad441ea640ef1\n      credentials:\n        username: lynx-family\n        password: ${{ secrets.GITHUB_TOKEN }}\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Build Explorer App\n        uses: ./lynx/.github/actions/harmony-explorer-build\n\n  ios-explorer-build:\n    timeout-minutes: 60\n    runs-on: lynx-darwin-14-medium\n    strategy:\n      matrix:\n        arch: [arm64, x86_64]\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Build Explorer App\n        uses: ./lynx/.github/actions/ios-explorer-build\n        with:\n          build-arch: ${{ matrix.arch }}\n          build-params: '--integration-test'\n      - name: upload artifact\n        uses: lynx-infra/upload-artifact@332ec52e99f7cbf1fbbdc9bcc09280d49147d092\n        continue-on-error: true\n        with:\n          name: ios-explorer-build-${{ matrix.arch }}\n          path: '${{ github.workspace }}/lynx/LynxExplorer-${{ matrix.arch }}.app.tar.gz'\n\n  ios-integration-test:\n    timeout-minutes: 15\n    runs-on: lynx-darwin-14-medium\n    env:\n      LOCAL_POD_NAME: 'local_pod'\n      POD_VERSION: '0.0.1-alpha.1'\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          fetch-depth: 2\n          ref: ${{ github.event.pull_request.head.sha }}\n          path: lynx\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Setup Ruby Cache\n        uses: ./lynx/.github/actions/ios-common-deps\n        with:\n          cache-backend: 'lynx-infra'\n      - name: Prepare cocoapods publish source\n        run: |-\n          source tools/envsetup.sh\n          export LANG=en_US.UTF-8\n          python3 tools/ios_tools/cocoapods_publish_helper.py --prepare-source --tag $POD_VERSION --version $POD_VERSION --component all\n      - name: Publish to Local Pod Source\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          git config --global user.name \"lynx.authors\"\n          git config --global user.email \"lynx.authors@users.noreply.github.com\"\n          python3 tools/ios_tools/cocoapods_publish_helper.py --publish_local $LOCAL_POD_NAME --component all\n      - name: Download the Demo Project\n        uses: actions/checkout@v4.2.2\n        with:\n          path: demo\n          repository: lynx-family/integrating-lynx-demo-projects\n          ref: 0ab9340e1e560f07d3d4c4331f8b62b0702d067a\n      - name: Build the Demo Project\n        run: |-\n          cd $GITHUB_WORKSPACE\n          python3 lynx/tools/ios_tools/process_podfile.py --action replace_pod_version --component Lynx --version $POD_VERSION --podfile demo/ios/HelloLynxObjc/Podfile\n          python3 lynx/tools/ios_tools/process_podfile.py --action replace_pod_version --component LynxService --version $POD_VERSION --podfile demo/ios/HelloLynxObjc/Podfile\n          python3 lynx/tools/ios_tools/process_podfile.py --action replace_pod_version --component XElement --version $POD_VERSION --podfile demo/ios/HelloLynxObjc/Podfile\n          python3 lynx/tools/ios_tools/process_podfile.py --action insert_pod_source --podfile demo/ios/HelloLynxObjc/Podfile --pod_source \"file://$GITHUB_WORKSPACE/lynx/$LOCAL_POD_NAME\"\n          cd demo/ios/HelloLynxObjc\n          export LANG=en_US.UTF-8\n          BUNDLE_GEMFILE=$GITHUB_WORKSPACE/lynx/Gemfile bundle --version\n          BUNDLE_GEMFILE=$GITHUB_WORKSPACE/lynx/Gemfile bundle exec pod install\n          xcodebuild -workspace Hello-Lynx-OC.xcworkspace/ -scheme Hello-Lynx-OC CODE_SIGNING_ALLOWED=NO build\n\n  ios-e2e-test:\n    timeout-minutes: 60\n    needs: [ios-explorer-build]\n    env:\n      APPIUM_TEST_SERVER_PORT: 4723\n      APPIUM_TEST_SERVER_HOST: 127.0.0.1\n    runs-on: lynx-darwin-14-medium\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n      - name: Setup Ruby Cache\n        uses: ./lynx/.github/actions/ios-common-deps\n        with:\n          cache-backend: 'lynx-infra'\n      - name: Get iOS simulator SDK version\n        run: |\n          xcodebuild -showsdks | grep -Eo -m 1 \"iphonesimulator([0-9]{1,}\\.)+[0-9]{1,}\" > sdk.txt\n          sdkVersion=$(awk '{ sub(/iphonesimulator/,\"\"); print $0 }' sdk.txt)\n          echo \"SDK_VERSION=$sdkVersion\" >> $GITHUB_ENV\n      - name: Build WebDriverAgent\n        uses: nick-fields/retry@v3\n        with:\n          max_attempts: 2\n          retry_wait_seconds: 5\n          timeout_minutes: 5\n          command: |\n            pushd $GITHUB_WORKSPACE\n            git clone https://github.com/appium/WebDriverAgent.git\n            xcodebuild build-for-testing \\\n              -project ./WebDriverAgent/WebDriverAgent.xcodeproj \\\n              -scheme WebDriverAgentRunner \\\n              -configuration Release \\\n              -sdk iphonesimulator${{ env.SDK_VERSION }} \\\n              -derivedDataPath ./WebDriverAgent/DerivedData \\\n              -destination \"platform=iOS Simulator,OS=${{ env.SDK_VERSION }},name=iPhone 15\" \\\n              SYMROOT=$GITHUB_WORKSPACE/Build/Products\n            popd\n      - name: Start iOS Simulator\n        run: |\n          xcrun simctl boot \"iPhone 15\"\n      - name: Install WebDriverAgent to Simulator\n        run: |\n          set -e\n          SIMULATOR_UDID=$(xcrun simctl list devices \"iOS ${{ env.SDK_VERSION }}\" | grep \"iPhone 15 (\" | grep -oE '[0-9A-F-]{36}')\n          echo \"SIMULATOR_UDID=$SIMULATOR_UDID\" >> $GITHUB_ENV\n          xcrun simctl install $SIMULATOR_UDID \\\n            $GITHUB_WORKSPACE/Build/Products/Release-iphonesimulator/WebDriverAgentRunner-Runner.app\n      - name: Install Appium\n        run: |\n          pushd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pnpm install appium@2.11.2 -w\n          pnpm install appium-xcuitest-driver@7.24.18 -w\n          pnpm install appium-ios-simulator@6.2.6 -w\n          popd\n        env:\n          npm_config_store_dir: ~/.local/share/pnpm/store\n      - name: Download Explorer App\n        uses: lynx-infra/download-artifact@79d9914484f933089c2840552cf439bac85debad\n        with:\n          name: ios-explorer-build-arm64\n          path: lynx/explorer/darwin/ios/lynx_explorer/build_temp\n      - name: Install Explorer App\n        run: |\n          mkdir -p $GITHUB_WORKSPACE/lynx/explorer/darwin/ios/lynx_explorer/build_temp/LynxExplorer.app\n          tar -xzvf $GITHUB_WORKSPACE/lynx/explorer/darwin/ios/lynx_explorer/build_temp/LynxExplorer-arm64.app.tar.gz -C $GITHUB_WORKSPACE/lynx/explorer/darwin/ios/lynx_explorer/build_temp/LynxExplorer.app\n          xcrun simctl install $SIMULATOR_UDID $GITHUB_WORKSPACE/lynx/explorer/darwin/ios/lynx_explorer/build_temp/LynxExplorer.app\n      - name: Start Appium server\n        run: |\n          pushd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd node_modules/appium/node_modules/.bin\n          ls\n          ./appium server \\\n            --port=$APPIUM_TEST_SERVER_PORT \\\n            --address=$APPIUM_TEST_SERVER_HOST \\\n            --log-no-colors \\\n            --log-timestamp \\\n            2>&1 > \"$GITHUB_WORKSPACE/lynx/appium.log\" < /dev/null &\n          popd\n          popd\n      - name: Check if Appium Server is running\n        run: |\n          max_attempts=10\n          attempt=0\n          while [ $attempt -lt $max_attempts ]; do\n            if nc -z $APPIUM_TEST_SERVER_HOST $APPIUM_TEST_SERVER_PORT; then\n              echo \"Appium Server is running.\"\n              break\n            else\n              attempt=$((attempt + 1))\n              echo \"Attempt $attempt: Appium Server is not yet running. Retrying in 2 seconds...\"\n              sleep 2\n            fi\n          done\n          if [ $attempt -eq $max_attempts ]; then\n            echo \"Failed to start Appium Server.\"\n            exit 1\n          fi\n      - name: Run iOS E2E Test\n        uses: nick-fields/retry@v3\n        with:\n          max_attempts: 2\n          retry_wait_seconds: 5\n          timeout_minutes: 20\n          command: |\n            set -e\n            pushd $GITHUB_WORKSPACE/lynx\n            source tools/envsetup.sh\n            pushd $GITHUB_WORKSPACE/lynx/testing/integration_test/test_script\n            export server_port=$APPIUM_TEST_SERVER_PORT\n            export platform=ios\n            export APPIUM_isHeadless=True\n            pip3 install -r requirements.txt\n            python3 manage.py runtest ios_test.core\n            echo \"TASK_STATUS=success\" >> $GITHUB_OUTPUT\n            popd\n            popd\n        id: run_test\n      - name: Collect Lynx-E2E execution logs\n        if: always()\n        run: |\n          TASK_STATUS=\"${{ steps.run_test.outputs.TASK_STATUS }}\"\n          if [ -n \"$TASK_STATUS\" ] && [ \"$TASK_STATUS\" = \"success\" ]; then\n            echo \"Test execution succeeded.\"\n          else\n            echo \"Test execution failed, collecting logs...\"\n            pushd $GITHUB_WORKSPACE/lynx/testing/integration_test/test_script\n            ls | grep -E \"^lynx_e2e_devtools_.*\\.log$\" | xargs cat\n            popd\n          fi\n      - name: Collect Appium Server logs\n        if: always()\n        run: |\n          TASK_STATUS=\"${{ steps.run_test.outputs.TASK_STATUS }}\"\n          if [ -n \"$TASK_STATUS\" ] && [ \"$TASK_STATUS\" = \"success\" ]; then\n            echo \"Test execution succeeded.\"\n          else\n            echo \"Test execution failed, collecting logs...\"\n            pushd $GITHUB_WORKSPACE/lynx/\n            if [ -f \"appium.log\" ]; then\n              cat appium.log\n            else\n              echo \"Warning: appium.log file not found\"\n            fi\n            popd\n          fi\n      - name: Collect Generated Resource Files\n        if: always()\n        run: |\n          TASK_STATUS=\"${{ steps.run_test.outputs.TASK_STATUS }}\"\n          if [ -n \"$TASK_STATUS\" ] && [ \"$TASK_STATUS\" = \"success\" ]; then\n            echo \"Test execution succeeded.\"\n          else\n            pushd $GITHUB_WORKSPACE/lynx/testing/integration_test/test_script\n            if [ -d \"screenshots\" ]; then\n              zip -r screenshots.zip ./screenshots/\n              echo \"Successfully created screenshots.zip\"\n              echo \"ZIP_RESOURCES=success\" >> $GITHUB_OUTPUT\n            else\n              echo \"Warning: screenshot directory not found\"\n            fi\n          fi\n        id: collect_resource\n      - name: Upload Generated Resource Files\n        if: always() && steps.collect_resource.outputs.ZIP_RESOURCES == 'success'\n        uses: lynx-infra/upload-artifact@332ec52e99f7cbf1fbbdc9bcc09280d49147d092\n        continue-on-error: true\n        with:\n          name: ios-e2e-resources\n          path: '${{ github.workspace }}/lynx/testing/integration_test/test_script/screenshots.zip'\n\n  clay-macos-build:\n    timeout-minutes: 60\n    runs-on: lynx-darwin-14-medium\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Build Clay Example\n        uses: ./lynx/.github/actions/clay-macos-build\n      - name: upload artifact\n        uses: lynx-infra/upload-artifact@332ec52e99f7cbf1fbbdc9bcc09280d49147d092\n        with:\n          name: clay_glfw\n          path: '${{ github.workspace }}/lynx/out/Default/clay_glfw'\n\n  macos-explorer-build:\n    timeout-minutes: 60\n    runs-on: lynx-darwin-14-medium\n    strategy:\n      matrix:\n        arch: [arm64, x64]\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Build macOS Explorer\n        uses: ./lynx/.github/actions/macos-explorer-build\n        with:\n          build-arch: ${{ matrix.arch }}\n      - name: upload artifact\n        uses: lynx-infra/upload-artifact@332ec52e99f7cbf1fbbdc9bcc09280d49147d092\n        with:\n          name: macos-explorer-build-${{ matrix.arch }}\n          path: '${{ github.workspace }}/lynx/LynxExplorer-macos-${{ matrix.arch }}.app.tar.gz'\n"
  },
  {
    "path": ".github/workflows/cocoapods_token_keepalive.yml",
    "content": "name: CocoaPods Token Keepalive\n\non:\n  schedule:\n    - cron: '0 4 * * *'\n  workflow_dispatch:\njobs:\n  token-keepalive:\n    runs-on: lynx-darwin-14-medium\n    env:\n      COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Setup Ruby Cache\n        uses: ./lynx/.github/actions/ios-common-deps\n        with:\n          cache-backend: 'lynx-infra'\n      - name: Keep CocoaPods Token alive\n        run: |\n          cd $GITHUB_WORKSPACE/lynx\n          SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk bundle install --path .bundle\n          bundle exec pod trunk me --silent\n"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: 'CodeQL Advanced'\n\non:\n  schedule:\n    - cron: '28 19 * * *'\n\njobs:\n  analyze:\n    name: Analyze (${{ matrix.language }})\n    # Runner size impacts CodeQL analysis time. To learn more, please see:\n    #   - https://gh.io/recommended-hardware-resources-for-running-codeql\n    #   - https://gh.io/supported-runners-and-hardware-resources\n    #   - https://gh.io/using-larger-runners (GitHub.com only)\n    # Consider using larger runners or machines with greater resources for possible analysis time improvements.\n    runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}\n    permissions:\n      # required for all workflows\n      security-events: write\n\n      # required to fetch internal or private CodeQL packs\n      packages: read\n\n      # only required for workflows in private repositories\n      actions: read\n      contents: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - language: actions\n            build-mode: none\n          - language: c-cpp\n            build-mode: autobuild\n          - language: java-kotlin\n            build-mode: autobuild\n          - language: javascript-typescript\n            build-mode: none\n          - language: python\n            build-mode: none\n          - language: ruby\n            build-mode: none\n        # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'\n        # Use `c-cpp` to analyze code written in C, C++ or both\n        # Use 'java-kotlin' to analyze code written in Java, Kotlin or both\n        # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both\n        # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,\n        # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.\n        # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how\n        # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      # Add any setup steps before running the `github/codeql-action/init` action.\n      # This includes steps like installing compilers or runtimes (`actions/setup-node`\n      # or others). This is typically only required for manual builds.\n      # - name: Setup runtime (example)\n      #   uses: actions/setup-example@v1\n\n      # Initializes the CodeQL tools for scanning.\n      - name: Initialize CodeQL\n        uses: github/codeql-action/init@v4\n        with:\n          languages: ${{ matrix.language }}\n          build-mode: ${{ matrix.build-mode }}\n          # If you wish to specify custom queries, you can do so here or in a config file.\n          # By default, queries listed here will override any specified in a config file.\n          # Prefix the list here with \"+\" to use these queries and those in the config file.\n\n          # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs\n          # queries: security-extended,security-and-quality\n\n      # If the analyze step fails for one of the languages you are analyzing with\n      # \"We were unable to automatically build your code\", modify the matrix above\n      # to set the build mode to \"manual\" for that language. Then modify this step\n      # to build your code.\n      # ℹ️ Command-line programs to run using the OS shell.\n      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun\n      - name: Run manual build steps\n        if: matrix.build-mode == 'manual'\n        shell: bash\n        run: |\n          echo 'If you are using a \"manual\" build mode for one or more of the' \\\n            'languages you are analyzing, replace this with the commands to build' \\\n            'your code, for example:'\n          echo '  make bootstrap'\n          echo '  make release'\n          exit 1\n\n      - name: Perform CodeQL Analysis\n        uses: github/codeql-action/analyze@v4\n        with:\n          category: '/language:${{matrix.language}}'\n"
  },
  {
    "path": ".github/workflows/css-defines-publish.yml",
    "content": "name: css_defines_publish\n\non: workflow_dispatch\n\njobs:\n  css-defines-publish:\n    runs-on: ubuntu-latest\n    environment: npm\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install SSH Key\n        uses: kielabokkie/ssh-key-and-known-hosts-action@v1\n        with:\n          ssh-private-key: ${{ secrets.PRIVATE_SSH_KEY }}\n          ssh-host: github.com\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          registry-url: 'https://registry.npmjs.org'\n      - name: Setup Package Managers\n        run: |\n          npm install -g npm@latest\n          npm --version\n      - name: Build\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n      - name: Publish\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx/tools/css_generator\n          npm publish --access public --provenance\n"
  },
  {
    "path": ".github/workflows/issue-management.yml",
    "content": "name: issue_management\non:\n  issues:\n    types:\n      - opened\n      - assigned\n\npermissions:\n  issues: write\n\njobs:\n  add-label-triage:\n    timeout-minutes: 30\n    runs-on: lynx-ubuntu-22.04-mini\n    if: github.event.action == 'opened' # Only run for 'opened' event\n    steps:\n      - name: Add 'status:need triage' label to new issues\n        uses: actions-ecosystem/action-add-labels@bd52874380e3909a1ac983768df6976535ece7f8\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          labels: status:need triage\n\n  update-label-assigned:\n    timeout-minutes: 30\n    runs-on: lynx-ubuntu-22.04-mini\n    if: github.event.action == 'assigned' # Only run for 'assigned' event\n    steps:\n      # We won't check if the label exists. We are counting on the robustness of the actions.\n      - name: Remove 'status:need triage' label\n        uses: actions-ecosystem/action-remove-labels@v1\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          labels: status:need triage\n\n      - name: Add 'status:assigned' label\n        uses: actions-ecosystem/action-add-labels@bd52874380e3909a1ac983768df6976535ece7f8\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          labels: status:assigned\n"
  },
  {
    "path": ".github/workflows/lynx-core-publish.yml",
    "content": "name: lynx_core_publish\n\non: workflow_dispatch\n\njobs:\n  lynx_types_publish:\n    runs-on: ubuntu-latest\n    environment: npm\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: github\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          registry-url: 'https://registry.npmjs.org'\n      - name: Setup Package Managers\n        run: |\n          npm install -g npm@latest\n          npm --version\n      - name: Build\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd js_libraries/lynx-runtime-shared\n          npm run build\n          popd\n          pushd js_libraries/lynx-core\n          npm run build:web\n          popd\n      - name: Publish\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx/js_libraries/lynx-core\n          npm publish --access public --provenance\n"
  },
  {
    "path": ".github/workflows/lynx-types-publish.yml",
    "content": "name: lynx_types_publish\n\non:\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: 'The npm tag to publish with.'\n        required: false\n        default: 'latest'\n\njobs:\n  lynx_types_publish:\n    runs-on: ubuntu-latest\n    environment: npm\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: github\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          registry-url: 'https://registry.npmjs.org'\n      - name: Setup Package Managers\n        run: |\n          npm install -g npm@latest\n          npm --version\n      - name: Build\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n      - name: Update version for non-latest tags\n        if: github.event.inputs.tag != 'latest'\n        run: |\n          cd $GITHUB_WORKSPACE/lynx/js_libraries/types\n          npm version prerelease --preid=${{ github.event.inputs.tag }} --no-git-tag-version\n      - name: Publish\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx/js_libraries/types\n          npm publish --access public --provenance --tag ${{ github.event.inputs.tag }}\n"
  },
  {
    "path": ".github/workflows/publish-release.yml",
    "content": "name: publish-release\non:\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: 'release tag'\n        required: false\n        type: string\n      commitId:\n        description: 'specific commit ID to build'\n        required: false\n        type: string\n\n  release:\n    types: [published]\n\njobs:\n  get-version:\n    runs-on: lynx-ubuntu-22.04-medium\n    timeout-minutes: 60\n    outputs:\n      version: ${{ steps.get_version.outputs.VERSION }}\n    steps:\n      - name: Get Version\n        id: get_version\n        run: |-\n          if [ ${{ github.event_name }} == 'workflow_dispatch' ]; then\n            version=${{ github.event.inputs.tag }}\n          else\n            version=$(echo ${{ github.ref }} | awk -F \"/\" '{print $3}')\n          fi\n          if [[ $version =~ ^[0-9]+\\.[0-9]+\\.[0-9]+$ || \\\n                $version =~ ^[0-9]+\\.[0-9]+\\.[0-9]+-rc\\.[0-9]+$ || \\\n                $version =~ ^[0-9]+\\.[0-9]+\\.[0-9]+-alpha\\.[0-9]+$ ]]; then\n            echo \"Version is valid\"\n            echo \"VERSION=$version\" >> $GITHUB_OUTPUT;\n          else\n            echo \"Version is invalid\"\n            exit 1\n          fi\n\n  android-explorer-build:\n    timeout-minutes: 60\n    runs-on: lynx-ubuntu-22.04-large\n    needs: get-version\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          ref: ${{ github.event.inputs.commitId || github.ref }}\n          fetch-depth: 0\n      - name: Build Explorer App\n        uses: ./lynx/.github/actions/android-explorer-build\n        with:\n          abi-list: armeabi-v7a,x86,x86_64,arm64-v8a\n      - name: push to release\n        uses: ncipollo/release-action@v1\n        with:\n          tag: ${{ needs.get-version.outputs.version }}\n          token: ${{ secrets.GITHUB_TOKEN }}\n          artifacts: '${{ github.workspace }}/lynx/explorer/android/lynx_explorer/build/outputs/apk/noasan/release/LynxExplorer-noasan-release.apk'\n          allowUpdates: true\n          body: ''\n\n  ios-explorer-build:\n    timeout-minutes: 60\n    runs-on: lynx-darwin-14-medium\n    needs: get-version\n    strategy:\n      matrix:\n        arch: [arm64, x86_64]\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          ref: ${{ github.event.inputs.commitId || github.ref }}\n          fetch-depth: 0\n      - name: Build Explorer App\n        uses: ./lynx/.github/actions/ios-explorer-build\n        with:\n          build-arch: ${{ matrix.arch }}\n      - name: push explorer to release\n        uses: ncipollo/release-action@v1\n        with:\n          tag: ${{ needs.get-version.outputs.version }}\n          token: ${{ secrets.GITHUB_TOKEN }}\n          artifacts: '${{ github.workspace }}/lynx/LynxExplorer-${{ matrix.arch }}.app.tar.gz'\n          allowUpdates: true\n          body: ''\n\n  harmony-explorer-build:\n    runs-on: lynx-custom-container\n    container:\n      image: ghcr.io/lynx-family/ubuntu24.04-harmony@sha256:bf493c3710de3c44bfa84caf402c0727b9310c42497f6e52580ad441ea640ef1\n      credentials:\n        username: lynx-family\n        password: ${{ secrets.GITHUB_TOKEN }}\n    needs: get-version\n    defaults:\n      run:\n        working-directory: ${{ github.workspace }}\n        shell: bash\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          ref: ${{ github.event.inputs.commitId || github.ref }}\n          fetch-depth: 0\n      - name: Build Explorer App\n        uses: ./lynx/.github/actions/harmony-explorer-build\n      - name: push explorer to release\n        uses: ncipollo/release-action@v1\n        with:\n          tag: ${{ needs.get-version.outputs.version }}\n          token: ${{ secrets.GITHUB_TOKEN }}\n          artifacts: '${{ github.workspace }}/lynx/explorer/harmony/lynx_explorer/build/default/outputs/default/lynx_explorer-default-unsigned.hap'\n          allowUpdates: true\n          body: ''\n\n  android-sdk-release:\n    runs-on: ubuntu-22.04\n    needs: get-version\n    timeout-minutes: 240\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          ref: ${{ github.event.inputs.commitId || github.ref }}\n          fetch-depth: 0\n      - name: Free up disk space\n        uses: ./lynx/.github/actions/free-android-disk\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Setup Android environment\n        uses: ./lynx/.github/actions/setup-android-env\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: 'github'\n      - name: Android SDK Release\n        uses: ./lynx/.github/actions/android-sdk-release\n        with:\n          version: ${{ needs.get-version.outputs.version }}\n          signingKeyId: ${{ secrets.SIGNING_KEY_ID }}\n          signingPassword: ${{ secrets.SIGNING_PASSWORD }}\n          signingSecretKey: ${{ secrets.SIGNING_SECRET_KEY }}\n          abiList: 'armeabi-v7a,x86,x86_64,arm64-v8a'\n      - name: Package maven artifacts\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          pushd platform/android\n          ./gradlew zipArtifacts -Pversion=${{ needs.get-version.outputs.version }} getArtifactList\n          popd\n          pushd platform/android/build\n          artifact_list=$(<artifact-list)\n          echo \"::set-output name=artifact_list::$artifact_list\"\n          popd\n        id: build_artifact\n      - name: Publish artifact to maven\n        uses: lynx-infra/maven-publish-action@53b4da2f23f9cfc4e905b135eda2724fcf5a0f0e\n        with:\n          portal_api_token: ${{ secrets.PORTAL_API_TOKEN }}\n          artifact_path_list: ${{ steps.build_artifact.outputs.artifact_list }}\n\n  ios-sdk-publish:\n    timeout-minutes: 60\n    runs-on: macos-14\n    needs: get-version\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          ref: ${{ github.event.inputs.commitId || github.ref }}\n          fetch-depth: 0\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Set up Ruby + specify Bundler version\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: '2.6.10'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: github\n      - name: Setup Ruby Cache\n        uses: ./lynx/.github/actions/ios-common-deps\n        with:\n          cache-backend: 'github'\n      - name: Publish iOS SDK\n        uses: ./lynx/.github/actions/ios-sdk-publish\n        with:\n          version: ${{ needs.get-version.outputs.version }}\n          tag: ${{ needs.get-version.outputs.version }}\n          cocoapods_trunk_token: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          component: all\n      - name: Publish iOS dev SDK\n        uses: ./lynx/.github/actions/ios-sdk-publish\n        env:\n          LYNX_ENABLE_RECORDER: 1\n        with:\n          version: ${{ needs.get-version.outputs.version }}-dev\n          tag: ${{ needs.get-version.outputs.version }}\n          cocoapods_trunk_token: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          component: all\n\n  harmony-sdk-publish:\n    name: Publish Harmony SDK (${{ matrix.label }})\n    runs-on: lynx-custom-container\n    container:\n      image: ghcr.io/lynx-family/ubuntu24.04-harmony@sha256:25913874dad0557526b44f9c9ab30b9873080009db85bb569d718d2bb5f0e074\n      credentials:\n        username: lynx-family\n        password: ${{ secrets.GITHUB_TOKEN }}\n    defaults:\n      run:\n        shell: bash\n    needs: get-version\n    strategy:\n      matrix:\n        include:\n          - version_suffix: ''\n            modules: 'default'\n            build_param: ''\n            label: 'formal'\n          - version_suffix: '-dev'\n            modules: 'lynx lynx_devtool lynx_base'\n            build_param: '--dev'\n            label: 'dev'\n    steps:\n      - name: Install Rust toolchain\n        uses: actions-rs/toolchain@v1\n        with:\n          toolchain: stable\n          profile: minimal\n          override: true\n      - name: Install git-cliff\n        run: |\n          cargo install git-cliff --locked\n          git-cliff --version\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          fetch-depth: 0\n          fetch-tags: true\n          ref: ${{ github.event.inputs.commitId || github.ref }}\n      - name: Get latest tag\n        id: get-latest-tag\n        uses: ./lynx/.github/actions/get-latest-tag\n        with:\n          current-tag: ${{ needs.get-version.outputs.version }}\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-key-prefix: '-container'\n      - name: Generate Change Log\n        run: |\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          COMMIT_ID=$(git rev-list -n 1 ${{ steps.get-latest-tag.outputs.latest-tag }})\n          python3 explorer/harmony/script/generate_changelog.py --version ${{ needs.get-version.outputs.version }}${{ matrix.version_suffix }} --modules ${{ matrix.modules }} --base_commit $COMMIT_ID\n      - name: Build Harmony SDK\n        run: |\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd platform/harmony && ohpm install && popd\n          pushd explorer/harmony && ohpm install && popd\n          python3 explorer/harmony/script/build.py ${{ matrix.build_param }} --build_lynx_core --build_har --modules ${{ matrix.modules }} --override_version ${{ needs.get-version.outputs.version }}${{ matrix.version_suffix }}\n      - name: Publish Harmony SDK\n        run: |\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          export PUBLISH_KEY_PASSPHRASE=${{ secrets.HARMONY_PUBLISH_KEY_PASSPHRASE }}\n          cat << EOF > publish_key\n          ${{ secrets.HARMONY_PUBLISH_KEY }}\n          EOF\n          ohpm config set publish_registry https://ohpm.openharmony.cn/ohpm\n          export PUBLISH_ID=${{ secrets.HARMONY_PUBLISH_ID }}\n          export KEY_PATH=$GITHUB_WORKSPACE/lynx/publish_key\n          python3 explorer/harmony/script/publish.py --modules ${{ matrix.modules }} --version ${{ needs.get-version.outputs.version }}${{ matrix.version_suffix }}\n"
  },
  {
    "path": ".github/workflows/sdk-release-test.yml",
    "content": "name: sdk-release-test\n\non:\n  workflow_dispatch:\n    inputs:\n      commitId:\n        description: 'specific commit ID to build'\n        required: false\n        type: string\n  push:\n    branches:\n      - sdk_release_test/**\n\ndefaults:\n  run:\n    working-directory: lynx\n\njobs:\n  android-sdk-release-test:\n    timeout-minutes: 60\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          ref: ${{ github.event.inputs.commitId || github.ref }}\n          fetch-depth: 0\n      - name: Free up disk space\n        uses: ./lynx/.github/actions/free-android-disk\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Setup Android environment\n        uses: ./lynx/.github/actions/setup-android-env\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: 'github'\n      - name: Build Android SDK\n        uses: ./lynx/.github/actions/android-sdk-release\n        with:\n          version: 0.0.1-alpha.1\n      - name: Collect and zip artifacts\n        run: |\n          cd $GITHUB_WORKSPACE/lynx/platform/android\n          ./gradlew zipAllArtifacts\n\n  ios-sdk-release-test:\n    timeout-minutes: 15\n    runs-on: macos-14\n    env:\n      LOCAL_POD_NAME: 'local_pod'\n      POD_VERSION: '0.0.1-alpha.1'\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          ref: ${{ github.event.inputs.commitId || github.ref }}\n          fetch-depth: 0\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Set up Ruby + specify Bundler version\n        uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: '2.6.10'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: github\n      - name: Setup Ruby Cache\n        uses: ./lynx/.github/actions/ios-common-deps\n        with:\n          cache-backend: 'github'\n      - name: Prepare cocoapods publish source\n        run: |-\n          source tools/envsetup.sh\n          export LANG=en_US.UTF-8\n          python3 tools/ios_tools/cocoapods_publish_helper.py --prepare-source --tag $POD_VERSION --version $POD_VERSION --component all\n      - name: Publish to Local Pod Source\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          git config --global user.name \"lynx.authors\"\n          git config --global user.email \"lynx.authors@users.noreply.github.com\"\n          python3 tools/ios_tools/cocoapods_publish_helper.py --publish_local $LOCAL_POD_NAME --component all\n"
  },
  {
    "path": ".github/workflows/tasm-publish.yml",
    "content": "name: tasm_publish\n\non:\n  push:\n    branches:\n      - develop\n    paths:\n      - 'oliver/lynx-tasm/package.json'\n  workflow_dispatch:\n\njobs:\n  check-version-change:\n    runs-on: ubuntu-latest\n    outputs:\n      version_changed: ${{ steps.check.outputs.version_changed }}\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          fetch-depth: 2 # latest 2 commits\n\n      - name: Check version field change or skip on manual trigger\n        id: check\n        run: |\n          if [ \"${{ github.event_name }}\" = \"workflow_dispatch\" ]; then\n            echo \"Manual trigger detected, skip version check.\"\n            echo \"version_changed=true\" >> $GITHUB_OUTPUT\n          else\n            cd $GITHUB_WORKSPACE/lynx\n            version_current=$(jq -r '.version' oliver/lynx-tasm/package.json)\n            if git rev-parse HEAD~1 >/dev/null 2>&1; then\n              version_before=$(git show HEAD~1:oliver/lynx-tasm/package.json | jq -r '.version')\n            else\n              version_before=\"\"\n            fi\n            echo \"Current version: $version_current\"\n            echo \"Previous version: $version_before\"\n\n            if [ \"$version_current\" != \"$version_before\" ]; then\n              echo \"version_changed=true\" >> $GITHUB_OUTPUT\n            else\n              echo \"version_changed=false\" >> $GITHUB_OUTPUT\n            fi\n          fi\n\n  tasm-linux-build:\n    if: needs.check-version-change.outputs.version_changed == 'true'\n    runs-on: ubuntu-latest\n    needs: check-version-change\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 18\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: github\n          target: tasm\n      - name: Build Linux Tasm\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd oliver/lynx-tasm\n          npm install\n          npm run build:release:linux\n          popd\n      - name: Upload Linux Tasm Artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: tasm_linux\n          path: lynx/oliver/lynx-tasm/build/linux/Release/lepus.node\n\n  tasm-darwin-build:\n    if: needs.check-version-change.outputs.version_changed == 'true'\n    runs-on: macOS-latest\n    needs: check-version-change\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 18\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: github\n          target: tasm\n      - name: Build Darwin Tasm\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd oliver/lynx-tasm\n          npm install\n          npm run build:release:darwin\n          popd\n      - name: Upload Darwin Tasm Artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: tasm_darwin\n          path: lynx/oliver/lynx-tasm/build/darwin/Release/lepus.node\n\n  tasm-publish:\n    runs-on: macOS-latest\n    permissions:\n      contents: read\n      id-token: write\n    needs: [tasm-linux-build, tasm-darwin-build]\n    environment: npm\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          registry-url: 'https://registry.npmjs.org'\n      - name: Setup Package Managers\n        run: |\n          npm install -g npm@latest\n          npm --version\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: github\n          target: tasm\n      - name: Build Wasm Binary\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd oliver/lynx-tasm\n          npm install\n          npm run build:wasm\n          popd\n      - name: Download Linux Tasm Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: tasm_linux\n          path: lynx/oliver/lynx-tasm/build/linux/Release\n      - name: Download Darwin Tasm Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: tasm_darwin\n          path: lynx/oliver/lynx-tasm/build/darwin/Release\n      - name: Build\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n          pushd oliver/lynx-tasm\n          npm install\n          npm run build:wasm\n      - name: Publish\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx/oliver/lynx-tasm\n          npm publish --access public --provenance\n"
  },
  {
    "path": ".github/workflows/type-config-publish.yml",
    "content": "name: type_config_publish\n\non:\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: 'The npm tag to publish with.'\n        required: false\n        default: 'latest'\n\njobs:\n  type_config_publish:\n    runs-on: ubuntu-latest\n    environment: npm\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n      - name: Python Setup\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.13'\n      - name: Install Common Dependencies\n        uses: ./lynx/.github/actions/common-deps\n        with:\n          cache-backend: github\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          registry-url: 'https://registry.npmjs.org'\n      - name: Setup Package Managers\n        run: |\n          npm install -g npm@latest\n          npm --version\n      - name: Build\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx\n          source tools/envsetup.sh\n      - name: Update version for non-latest tags\n        if: github.event.inputs.tag != 'latest'\n        run: |\n          cd $GITHUB_WORKSPACE/lynx/js_libraries/type-config\n          npm version prerelease --preid=${{ github.event.inputs.tag }} --no-git-tag-version\n      - name: Test\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx/js_libraries/type-config\n          npm test\n      - name: Publish\n        run: |-\n          cd $GITHUB_WORKSPACE/lynx/js_libraries/type-config\n          npm publish --access public --provenance --tag ${{ github.event.inputs.tag }}\n"
  },
  {
    "path": ".github/workflows/type-element-api-publish.yml",
    "content": "name: type_element_api_publish\n\non:\n  push:\n    branches:\n      - develop\n    paths:\n      - 'js_libraries/type-element-api/*'\n  workflow_dispatch:\n\njobs:\n  check-version-change:\n    runs-on: ubuntu-latest\n    outputs:\n      version_changed: ${{ steps.check.outputs.version_changed }}\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          fetch-depth: 1 # latest 1 commit\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          registry-url: 'https://registry.npmjs.org'\n      - name: Setup Package Managers\n        run: |\n          npm install -g npm@latest\n          npm --version\n\n      - name: Check version field change\n        id: check\n        run: |\n          # achieve content of package.json \n          pushd $GITHUB_WORKSPACE/lynx/js_libraries/type-element-api\n          version_current=$(jq -r '.version' package.json)\n          version_before=$(npm view @lynx-js/type-element-api version)\n\n          echo \"Current version: $version_current\"\n          echo \"Previous version: $version_before\"\n\n          if [ \"$version_current\" != \"$version_before\" ]; then\n            echo \"version_changed=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"version_changed=false\" >> $GITHUB_OUTPUT\n          fi\n\n  type_element_api_publish:\n    if: needs.check-version-change.outputs.version_changed == 'true'\n    needs: check-version-change\n    runs-on: ubuntu-latest\n    environment: npm\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Download Source\n        uses: actions/checkout@v4.2.2\n        with:\n          path: lynx\n          fetch-depth: 1 # latest 1 commit\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          registry-url: 'https://registry.npmjs.org'\n      - name: Setup Package Managers\n        run: |\n          npm install -g npm@latest\n          npm --version\n      - name: Publish\n        run: |-\n          pushd $GITHUB_WORKSPACE/lynx/js_libraries/type-element-api\n          npm publish --access public\n"
  },
  {
    "path": ".gitignore",
    "content": "# Managed by Habitat\n/platform/android/gradle/wrapper/gradle-6.7.1-all.zip\n/build\n/buildtools\n/third_party/quickjs/src\n/third_party/libcxx\n/third_party/v8\n/third_party/libcxxabi\n/third_party/llvm\n/third_party/glfw\n/third_party/gyp\n/third_party/googletest\n/third_party/checkstyle\n/third_party/xhook\n/third_party/zlib\n/third_party/RTF/\n/third_party/NativeScript/include\n/third_party/NativeScript/lib\n/third_party/perfetto\n/third_party/httplib\n/third_party/boringssl\n/third_party/webview2\n/third_party/vulkan-deps\n*.profraw\n__pycache__\n/venv\n.venv\n.venv.lock\n*.log*\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n/*.podspec\n.bundle\n.pnpm-debug.log\nruby\n\n# Auto generated files\n/platform/android/lynx_android/src/main/java/com/lynx/tasm/featurecount/LynxFeatureCounter.java\n/platform/darwin/common/lynx/feature_count/LynxFeature.h\n/core/services/feature_count/feature.h\n/js_libraries/lynx-core/common/feature.ts\ncore/build/gen/\n/core/build/jni/gen\n/core/build/jni/BUILD.gn\n**/build/gen\n/core/template_bundle/template_codec/binary_decoder/lynx_config_auto_gen.h\n/core/template_bundle/template_codec/binary_decoder/lynx_config_constant_auto_gen.h\n/core/template_bundle/template_codec/binary_decoder/lynx_config_auto_gen.cc\n\n\n# Auto generated error code files\n/platform/darwin/common/lynx/LynxErrorBehavior.m\n/platform/darwin/common/lynx/LynxSubErrorCode.m\n/platform/darwin/common/lynx/Public/LynxErrorBehavior.h\n/platform/darwin/common/lynx/Public/LynxSubErrorCode.h\n\n\nout/\nplatform/darwin/ios/JSAssets/\nplatform/android/lynx_js_sdk/src/debug/assets/\nplatform/android/lynx_js_sdk/src/main/assets/\ntools/android_tools/ndk\ntools/android_tools/sdk\n\n\nplatform/android/.gradle/\nplatform/android/local.properties\nplatform/android/lynx_android/.cxx/\nplatform/android/lynx_processor/bin/\nplatform/android/lynx_android/CMakeLists_impl/\nplatform/android/lynx_android/libs/\nplatform/android/lynx_devtool/src/main/jniLibs/\nexplorer/android/lynx_explorer/build/\nexplorer/android/.gradle/\nexplorer/android/.idea/\nexplorer/android/build/\nexplorer/android/gradle/wrapper/gradle-6.7.1-all.zip\nexplorer/android/local.properties\nexplorer/android/lynx_explorer/src/main/assets/*\n!explorer/android/lynx_explorer/src/main/assets/testBench/\ncore/runtime/jsi/v8/CMakeLists_impl/\n\nexplorer/darwin/ios/lynx_explorer/Podfile.lock\nexplorer/darwin/ios/lynx_explorer/Pods\nexplorer/darwin/ios/lynx_explorer/LynxExplorer.xcworkspace/*\nexplorer/darwin/ios/lynx_explorer/LynxExplorer.xcodeproj/**\n!explorer/darwin/ios/lynx_explorer/LynxExplorer.xcodeproj/\n!explorer/darwin/ios/lynx_explorer/LynxExplorer.xcodeproj/project.pbxproj\n!explorer/darwin/ios/lynx_explorer/LynxExplorer.xcodeproj/xcshareddata/\n!explorer/darwin/ios/lynx_explorer/LynxExplorer.xcodeproj/xcshareddata/**\nexplorer/darwin/ios/lynx_explorer/LynxExplorer/Resource/*\n!explorer/darwin/ios/lynx_explorer/LynxExplorer/Resource/testBench\nexplorer/darwin/ios/lynx_explorer/iOSCoreBuild/\nexplorer/darwin/ios/lynx_explorer/results_bundle\nexplorer/darwin/ios/lynx_explorer/xctestrunner\nruby/\n\n# JSLibrary\nnode_modules\njs_libraries/**/dist/\njs_libraries/**/__dist__\njs_libraries/**/output/\njs_libraries/**/node_modules/\njs_libraries/**/lib\njs_libraries/lynx-core/src/common/feature.ts\n\n/core/renderer/css/auto_gen_css_decoder.h\n/core/renderer/css/auto_gen_css_decoder.cc\n/core/renderer/css/css_property_id.h\n/core/renderer/css/css_property_auto_gen_unittest.cc\n/core/renderer/css/parser/animation_property_handler.h\n/core/renderer/css/parser/bool_handler.h\n/core/renderer/css/parser/border_style_handler.h\n/core/renderer/css/parser/border_width_handler.h\n/core/renderer/css/parser/color_handler.h\n/core/renderer/css/parser/length_handler.h\n/core/renderer/css/parser/number_handler.h\n/core/renderer/css/parser/string_handler.h\n/core/renderer/css/parser/time_handler.h\n/core/renderer/css/parser/timing_function_handler.h\n/core/renderer/starlight/style/auto_gen_css_type.h\n/platform/darwin/common/lynx/public/base/LynxAutoGenCSSType.h\n/platform/android/lynx_processor/src/main/java/com/lynx/tasm/behavior/PropertyIDConstants.java\n/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/AutoGenStyleConstants.java\n\n# ignore all python script cache\n**/__pycache__/\n\n# ignore os specific hidden files\n**/.DS_Store\n\n# Editor specific files\n.vscode\n*.sublime-project\n*.sublime-workspace\n.idea\n\n# ignore Gradle files\n**/.gradle/\n\n# api metadata\n/tools/api/android\n/tools/api/ios\n/tools/api/docs/gen\n"
  },
  {
    "path": ".gn",
    "content": "# Copyright 2020 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\nscript_executable = rebase_path(\"//.venv/bin/python3\")\n\nbuildconfig = \"//build/config/BUILDCONFIG.gn\"\n\nsecondary_source = \"//build/secondary/\"\n\nroot = \":root\"\n\ndefault_args = {\n  lynx_dir = \"\"\n\n  # Disable ANGLE vulkan\n  angle_enable_vulkan = false\n  angle_build_vulkan_system_info = false\n\n  # Do not build ANGLE tests\n  angle_build_all = false\n}\n"
  },
  {
    "path": ".habitat",
    "content": "habitat_version = \"0.3.146-alpha.2\"\nsolutions = [\n    {\n        'name': '.',\n        'deps_file': 'dependencies/DEPS',\n        'url': '',\n        'target_deps_files': {\n            'clay': 'dependencies/DEPS.clay',\n            'dev': 'dependencies/DEPS.dev',\n            'extension': 'dependencies/DEPS.extension',\n            'tasm': 'dependencies/DEPS.tasm',\n            'tools_shared': 'dependencies/DEPS.tools_shared',\n            'lynxtron': 'dependencies/DEPS.lynxtron',\n        },\n    }\n]\n"
  },
  {
    "path": ".npmrc",
    "content": "# We are using flatten config of eslint and not using prettier\n# Disable the default public-hoist-pattern(*-eslint-*, *-prettier-*)\npublic-hoist-pattern=\nstrict-peer-dependencies=true\nengine-strict=true\nregistry=https://registry.npmjs.org\n"
  },
  {
    "path": ".prettierignore",
    "content": "__dist__/\ndist/\npackages/template-assembler-cli/lepus/**/*\npackages/lynx-core/__tests__/fixtures/**/*\npackages/lynx-core/lepus/**/*\n*.d.ts\noliver/**/*.js\n!oliver/**/*.config.js\n!oliver/compiler-ng/scripts/*.js\n!oliver/type-lynx/**/*.d.ts\nnode_modules/\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"bracketSpacing\": true,\n  \"printWidth\": 80,\n  \"singleQuote\": true,\n  \"tabWidth\": 2,\n  \"trailingComma\": \"es5\",\n  \"useTabs\": false\n}\n"
  },
  {
    "path": ".rtf/android-ut-lynx.template",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n\nbuilder({\n    \"default\": {\n        \"args\": [\n            \"--parallel\",\n            \"--configure-on-demand\",\n            \"-x lint\",\n            \"-PabiList=x86\",\n            \"-Penable_lynx_android_test=true\",\n            \"-Penable_coverage=true\",\n        ],\n        \"workspace\": \"platform/android\",\n        \"type\": \"gradle\",\n    }\n})\n\ncoverage({\n    \"output\": \"out/coverage\",\n    \"type\": \"jacoco\",\n    \"jacoco_cli\":\"../third_party/jacoco/lib/jacococli.jar\"\n})\n\ntargets({\n    \"LynxAndroid\": {\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"task\": \"LynxAndroid:assembleNoasanDebugAndroidTest\",\n        \"source_files\":\"platform/android/lynx_android/src/main/java\",\n        \"class_files\":\"platform/android/lynx_android/build/intermediates/javac/noasanDebug/classes/*\",\n        \"apk\":\"platform/android/lynx_android/build/outputs/apk/androidTest/noasan/debug/LynxAndroid-noasan-debug-androidTest.apk\",\n        \"package\":\"com.lynx.test\",\n        \"coverage\":False,\n    },\n    \"LynxDevtool\": {\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"task\": \"LynxDevtool:assembleNoasanDebugAndroidTest\",\n        \"source_files\":\"platform/android/lynx_devtool/src/main/java\",\n        \"class_files\":\"platform/android/lynx_devtool/build/intermediates/javac/noasanDebug/classes/*\",\n        \"apk\":\"platform/android/lynx_devtool/build/outputs/apk/androidTest/noasan/debug/LynxDevtool-noasan-debug-androidTest.apk\",\n        \"package\":\"com.example.lynxdevtool.test\",\n        \"coverage\":False,\n    }\n})\n"
  },
  {
    "path": ".rtf/config",
    "content": "plugins = [\"NativeUT\", \"AndroidUT\"]"
  },
  {
    "path": ".rtf/native-ut-devtool.template",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nbuilder({\n    \"default\" :{\n        \"args\": [\n            f\"use_sccache={options.get_str('use_sccache', 'false')}\",\n            \"enable_lepusng_worklet=true\",\n            \"enable_unittests=true\",\n            \"enable_napi_binding=true\",\n            \"enable_coverage=true\",\n            \"enable_inspector=true\",\n        ],\n        \"output\": \"out/Default\",\n        \"type\": \"gn\",\n    },\n    \"build_for_element_test\":{\n        \"args\": [\n            \"enable_lepusng_worklet=true\",\n            \"enable_unittests=true\",\n            \"enable_napi_binding=true\",\n            \"enable_coverage=true\",\n        ],\n        \"output\": \"out/Default_element_test\",\n        \"type\": \"gn\",\n    },\n    \"build_for_agent_test\":{\n        \"args\": [\n            \"enable_lepusng_worklet=true\",\n            \"enable_unittests=true\",\n            \"enable_napi_binding=true\",\n            \"enable_coverage=true\",\n            \"enable_testbench_recorder=true\",\n            \"enable_testbench_replay=true\",\n        ],\n        \"output\": \"out/Default_agent_test\",\n        \"type\": \"gn\",\n    }\n})\n\ncoverage({\n    \"output\": \"out/coverage\",\n    \"ignores\": [\n        \"third_party/*\",\n        \".*_unittest.cc\",\n        \".*_unittest.h\",\n        \"core/*\",\n        \"core/base/*\",\n        \"core/runtime/vm/lepus/*\",\n        \"core/parser/*\",\n        \"core/runtime/*\",\n        \"testing/*\"\n    ],\n    \"type\": \"llvm\",\n})\n\ntargets({\n    \"devtool_element_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"builder\":'build_for_element_test'\n    },\n    \"devtoolng_unittest_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"devtool_base_unittest_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"devtool_agent_unittest_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"builder\":'build_for_agent_test',\n        \"args\":[\"--gtest_filter=\\\"-*DISABLE*\\\"\"]\n    },\n    \"js_debug_unittest_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"shared_data_unittest_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"test_bench_utils_unittest_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"message_handler_unittest_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n})\n"
  },
  {
    "path": ".rtf/native-ut-lynx.template",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nbuilder({\n    \"default\" :{\n        \"args\": [\n            \"enable_lepusng_worklet=true\",\n            \"enable_unittests=true\",\n            \"enable_inspector = true\",\n            \"enable_napi_binding=true\",\n            \"enable_coverage=true\",\n            f\"is_asan={options.get_str('enable_asan', 'false')}\",\n        ],\n        \"output\": \"out/Default\",\n        \"type\": \"gn\",\n    },\n\n    \"builder_for_trace\" :{\n        \"args\": [\n            \"enable_lepusng_worklet=true\",\n            \"enable_unittests=true\",\n            \"enable_napi_binding=true\",\n            \"enable_coverage=true\",\n            \"enable_trace=\\\\\\\"perfetto\\\\\\\"\"\n        ],\n        \"output\": \"out/Default_test_enable_trace\",\n        \"type\": \"gn\",\n    },\n})\n\ncoverage({\n    \"output\": \"out/coverage\",\n    \"ignores\": [\n        \".*_unittest.cc\",\n        \".*_unittests.cc\",\n        \".*_unittests.mm\"\n        \"*_tests.cc\",\n        \"*_test.cc\",\n        \".*_test.h\",\n        \".*_unittest.h\",\n        \".*_test.cc\",\n        \"core/shell/testing/*\",\n        \"core/animation/testing/*\",\n        \"core/renderer/ui_wrapper/testing/*\",\n        \"core/renderer/dom/air/testing/*\",\n        \"core/runtime/vm/lepus/compiler/encode_main.cc\",\n        \"testing/*\",\n    ] + options.get_dir_list(\"third_party\", excludes=[\"base\"]),\n    \"type\": \"llvm\",\n})\n\ntargets({\n    \"base_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"args\":[\"--gtest_filter=\\\"-*TimeSensitiveTest*\\\"\"]\n    },\n    \"binary_decoder_unittest_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"trace_unittests_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"builder\":'builder_for_trace'\n    },\n    \"profile_unittests_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"builder\":'builder_for_trace'\n    },\n    \"v8_profile_unittests_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"builder\":'builder_for_trace'\n    },\n    \"quickjs_profile_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"builder\":'builder_for_trace'\n    },\n    \"lepusng_profile_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"builder\":'builder_for_trace'\n    },\n    \"dom_unittest_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"args\":[\"--gtest_filter=\\\"-*DISABLE*\\\"\"]\n    },\n    \"value_wrapper_unittest_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True\n    },\n    \"record_unittest_exec\":{\n       \"cwd\": \".\",\n       \"owners\":[\"ZhaoSongGOO\"],\n       \"coverage\": True,\n       \"enable_parallel\":True\n    },\n    \"renderer_utils_unittests_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"replay_unittest_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True\n    },\n    \"shell_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"args\":[\"--gtest_filter=\\\"-*DISABLE*\\\"\"]\n    },\n     \"css_parser_test_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"css_encoder_test_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"style_object_encoder_testset_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"Zhongyr\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"runtime_tests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"retry\":3,\n    },\n    \"worklet_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"shared_data_test_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"retry\":3,\n    },\n    \"element_selector_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"lazy_bundle_test_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"animation_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"basic_animation_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"enable\":False\n    },\n    \"lynx_basic_animator_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n        \"enable\":True\n    },\n    \"inspector_test_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"event_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n     \"events_test_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"signal_test_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n     \"starlight_unittest_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"runtime_common_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"js_runtime_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"lepus_runtime_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"task_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"page_config_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"mapbuffer_unittests_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"ZhaoSongGOO\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"list_container_testset_exec\":{\n        \"cwd\": \".\",\n        \"owners\":[\"dingwang.wxx\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"pipeline_test_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"yangguangzhao.solace\", \"nihao.royal\"],\n        \"coverage\": True,\n        \"enable_parallel\":True,\n    },\n    \"lynx_service_api_static_unittests_exec\": {\n        \"cwd\": \".\",\n        \"owners\":[\"dangyingkai\", \"nihao.royal\"],\n        \"coverage\": True,\n        \"enable_parallel\": True,\n    }\n})\n"
  },
  {
    "path": ".tools_shared",
    "content": "checker-config:\n  default-disable-checkers:\n    - 'deps'\n    - 'spell'\n  file-type-checker:\n    binary-files-allow-list:\n      - 'core/renderer/css/testing/*'\n      - 'devtool/lynx_devtool/resources/devtool-switch/src/assets/*'\n      - 'explorer/darwin/ios/Lynx_explorer/LynxExplorer/Assets.xcassets/AppIcon.appiconset/*'\n      - 'explorer/harmony/AppScope/resources/base/media/*'\n      - 'third_party/binding/idl-codegen/third_party/doc/_static/*'\n      - '^explorer/darwin/ios/lynx_explorer/*'\n      - '^explorer/homepage/assets/images/*'\n      - '^explorer/showcase/menu/assets/images/*'\n      - '^platform/android/gradle/wrapper/*'\n      - '^platform/android/lynx_android/src/main/res/*'\n      - '^platform/android/lynx_example/*'\n      - '^platform/android/lynx_test_bench/*'\n      - 'devtool/base_devtool/resources/images/*'\n      - 'devtool/lynx_devtool/resources/images/*'\n      - 'platform/android/lynx_devtool/src/main/res/drawable/*'\n      - 'explorer/android/gradle/*'\n      - 'explorer/android/lynx_explorer/*'\n      - 'explorer/android/lynx_explorer/src/main/res'\n      - 'testing/integration_test/test_script/resources/*'\n      - 'explorer/android/lynx_test_bench/scr/main/res'\n\n  coding-style-checker:\n    ignore-suffixes:\n      - '_jni.h'\n      - 'pnpm-lock.yaml'\n      - 'Podfile.yml'\n      - '.d.ts'\n      - '.r.ts'\n    ignore-dirs:\n      - '^core/build/gen/*'\n      - '^third_party/*'\n      - '^build/*'\n      - '^base/include/boost/*'\n      - '^third_party/(aes|double-conversion|modp_b64|rapidjson|binding|quickjs|napi)/*'\n      - '^explorer/darwin/ios/Lynx_explorer/LynxExplorer/Resource/'\n      - '^clay/testing/*'\n      - '^clay/third_party/txt/*'\n\n  cpplint-checker:\n    ignore-suffixes:\n      - '_jni.h'\n      - 'pnpm-lock.yaml'\n      - 'Podfile.yml'\n      - '.d.ts'\n      - '.r.ts'\n    ignore-dirs:\n      - '^core/build/gen/*'\n      - '^third_party/*'\n      - '^build/*'\n      - '^base/include/boost/*'\n      - '^third_party/(aes|double-conversion|modp_b64|rapidjson|binding|quickjs|napi)/*'\n      - '^explorer/darwin/ios/Lynx_explorer/LynxExplorer/Resource/'\n      - '^clay/testing/*'\n      - '^clay/third_party/txt/*'\n\n  api-checker:\n    api-check-bin: 'tools/api/main.py'\n    java-path:\n      - 'platform/android/lynx_android/src/main'\n    cpp-path:\n      - 'platform/darwin/ios/lynx'\n      - 'platform/darwin/common/lynx'\n    instruction-doc: https://github.com/lynx-family/lynx/blob/develop/CONTRIBUTING.md#static-code-analysis-tasks\n\n  gn-relative-path-checker:\n    skip-prefixes:\n      - \"//PODS_ROOT\"\n      - \"//PODS_CONFIGURATION_BUILD_DIR\"\n      - \"//TARGET_BUILD_DIR\"\n      - \"//PODS_TARGET_SRCROOT\"\n      - \"//build_overrides\"\n      - \"//build\"\n      - \"//$lynx_dir\"\n      - \"//${lynx_dir}\"\n      - \"//third_party\"\n      - \"//config.gni\"\n      - \"//tools/idl_parser\"\n      - \"//out/\"\n      - \"//tools_shared\"\n\n  header-path-checker:\n    processed-file-dirs:\n      - \"base\"\n      - \"clay\"\n      - \"core\"\n      - \"devtool\"\n      - \"explorer\"\n      - \"oliver\"\n      - \"platform\"\n    header-search-paths:\n      - \"base\"\n      - \"clay\"\n      - \"core\"\n      - \"devtool\"\n      - \"explorer\"\n      - \"oliver\"\n      - \"platform\"\n      - \"third_party\"\n    exclude-processed-file-dirs:\n      - \"core/include/starlight_standalone\"\n      - \"/public/\"\n      - \"clay/third_party/\"\n      - \"third_party/napi/include/\"\n      - \"explorer/\"\n    header-extend-prefixes:\n      - \"public\"\n    ignore-header-files:\n      - \"skity/\"\n      - \"napi.h\"\n      - \"v8.h\"\n      - \"v8-inspector.h\"\n      - \"v8-profiler.h\"\n      - \"quickjs/include/\"\n      - \"persistent-handle.h\"\n      - \"trace-gc.h\"\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# AI Context & Guidelines\n\nThis repository contains a dedicated directory structure for AI agents to better understand the project's specific domain context, architectural decisions, and coding conventions.\n\n## 🤖 For AI Agents\n\nWhen working in this repository, please consult the contents of the **[`agents/`](./agents)** directory. This folder serves as a knowledge base containing:\n\n- **Domain Knowledge**: Specific business logic and terminology used in this project.\n- **Architecture Overview**: High-level design patterns and structural decisions.\n- **Coding Standards**: Project-specific conventions that supplement general best practices.\n\n**Action**: Before proposing significant changes or architectural updates, read the relevant documents in `agents/` to align with the project's established patterns.\n"
  },
  {
    "path": "BUILD.gn",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"clay/common/config.gni\")\nimport(\"config.gni\")\n\ngroup(\"root\") {\n  testonly = true\n  deps = [\n    \":default\",\n    \":third_party_base_group\",\n    \":third_party_trace_group\",\n  ]\n}\n\ngroup(\"default\") {\n  testonly = true\n  deps = [ \"core:lynx_native\" ]\n  if (is_android) {\n    deps += [ \"platform/android:Android\" ]\n  } else if (is_harmony) {\n    deps += [ \"platform/harmony\" ]\n  } else if (is_ios) {\n    deps += [ \"platform/darwin/ios/lynx_service:LynxService\" ]\n    deps += [ \"platform/darwin/ios/lynx_service_api:LynxServiceAPI_podspec\" ]\n    deps += [ \"platform/darwin/ios/lynx_devtool:devtool_podspec\" ]\n    deps += [ \"platform/darwin/ios/lynx_xelement:XElement\" ]\n    deps += [ \"platform/darwin/ios:iOS\" ]\n    deps += [ \"devtool/base_devtool/darwin/ios:BaseDevtool_podspec\" ]\n  } else if ((is_win || is_mac) && desktop_enable_embedder_layer) {\n    deps += [ \"explorer:explorer\" ]\n  }\n\n  if (enable_unittests) {\n    deps += [ \"tools/gn_tools/test:gn_tools_test\" ]\n  }\n\n  if (enable_clay) {\n    deps += [ \"clay:standalone_lib\" ]\n  }\n}\n\ngroup(\"all\") {\n  # lynx core modules\n  deps = [ \"core:lynx_core_native\" ]\n\n  # testing\n  if (enable_unittests) {\n    testonly = true\n    deps += [ \"testing\" ]\n  }\n}\n\ngroup(\"oliver_group\") {\n  deps = [ \"oliver\" ]\n}\n\ngroup(\"third_party_base_group\") {\n  deps = []\n  testonly = true\n  if (enable_unittests) {\n    deps += [ \"base/src:base_unittests_exec\" ]\n  }\n}\n\ngroup(\"third_party_trace_group\") {\n  deps = []\n  testonly = true\n  if (enable_unittests) {\n    deps += [ \"base/trace/native:trace_tests\" ]\n  }\n}\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\nExamples of behavior that contributes to creating a positive environment include:\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n- The use of sexualized language or imagery and unwelcome sexual attention or advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others’ private information, such as a physical or electronic address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\nThis Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at opensource-conduct@tiktok.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership.\n\n## Attribution\nThis Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\nFor answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Lynx\n\nFirst off, thank you for considering contributing to Lynx!\nWe welcome you to join Lynx Authors and become a member.\nIt's people like you who make this project great.\n\n## How Can I Contribute?\n\n### Reporting Bugs\n\nIf you find a bug, please open an issue with the following details:\n\n- A clear and descriptive title for the issue.\n- A description of the steps to reproduce the issue.\n- Any additional information or screenshots that might help us understand the issue better.\n\n### Suggesting Enhancements\n\nWe’re always open to new ideas! If you have a suggestion, please:\n\n- Use the “Feature Request” issue template or create a new issue.\n- Describe the enhancement you’d like and explain why it would be useful.\n\n### Your First Code Contribution\n\nUnsure where to start? You can find beginner-friendly issues using the “good first issue” label.\nWorking on these issues helps you get familiar with the codebase before tackling more complex problems.\n\n### Pull Requests\n\nWhen you’re ready to make a code change, please create a Pull Request:\n\n1. Fork the repository and clone it to your local machine.\n2. Create a new branch: `git checkout -b name-of-your-branch`.\n3. Make your changes.\n4. Once you have finished the necessary tests and verifications locally,\n   commit the changes with a commit message in the following format (some parts are optional):\n   ```\n   [Label] Title of the commit message (one line)\n\n   Summary of change:\n   Longer description of change addressing as appropriate: why the change\n   is made, context if it is part of many changes, description of previous\n   behavior and newly introduced differences, etc.\n\n   Long lines should be wrapped to 72 columns for easier log message\n   viewing in terminals.\n\n   issue: #xxx\n   doc: https://xxxxxxxx\n   TEST: Test cases\n   ```\n   Some parts in the message template are required, while others are optional.\n   We use the labels `[Required]` and `[Optional]` to differentiate them in the detailed explanation below:\n    - **[Required]** The first line of the commit message should be the title, summarizing the changes (the title must\n      be on a separate line).\n    - **[Required]** The title must start with at least one label, and the first label must be one of the following:\n      `Feature`, `BugFix`, `Refactor`, `Optimize`, `Infra`, `Testing`, `Doc`. The format should be: `[Label]`, e.g.,\n      `[BugFix]`, `[Feature]`, `[BugFix][Tools]` (there must be at least one space between the label(s) and the title\n      content, and the title must not be empty).\n      >\n      > Which label should I use? Here are the explanations for them:\n      > - **`[Feature]`**: New features, new functions, or changes to existing features and functions. For example:\n      >   - `[Feature] Add new API for data binding` Add a new API for data binding\n      >   - `[Feature] Add service for light sensors` Add a service for light sensors\n      >   - `[Feature][Log] Support async event report` Support asynchronous event reporting\n      > - **`[BugFix]`**: Fixes for functional defects, performance anomalies, and problems in developer tools, etc.\n      For example:\n      >   - `[BugFix] Fix exception when playing audio` Fix the exception when playing audio\n      >   - `[BugFix] Fix leaks in xxx` Fix the memory leak problem in xxx\n      >   - `[BugFix][DevTool] Fix data error in DevTool` Fix the data error in the debug tool\n      > - **`[Refactor]`**: The overall refactoring of a module or function (mainly refers to large-scale code rewriting\n      or architecture optimization; small-scale refactoring can be classified as Optimize). For example:\n      >   - `[Refactor][Memory] Memory management in xxxx` Refactor the memory management of the xxx module\n      >   - `[Refactor][TestBench] XX module in TestBench` Refactor the xxx module in the TestBench\n      > - **`[Optimize]`**: Small-scale optimization of a certain feature or indicator,\n      such as performance optimization, memory optimization, etc. For example:\n      >   - `[Optimize][Performance] Jank when scrolling in xxxx` Optimization of the smoothness when scrolling in xxxx\n      > - **`[Infra]`**: Changes related to the compilation framework, CQ configuration, basic tools, etc. For example:\n      >   - `[Infra][Compile] Use -Oz compile params in xxx` Use -Oz compilation parameters\n      > - **`[Testing]`**: Modifications related to test cases and test frameworks. For example:\n      >   - `[Testing][Android] Fix xxx test case for Android` Fix a test case for Android\n      >   - `[Testing] Optimize test process` Optimize the process of a certain test framework\n      >\n      >\n      > Modifications only involving test cases, even if it is a `BugFix`, should be classified as `Testing`.\n      If both `Feature` code and related test cases are submitted in the same patch,\n      it should be classified as `Feature`.\n    - **[Required]** The section following the title should be the summary, providing a detailed description of the\n      changes (there must be a blank line between the title and the summary).\n    - **[Optional]** The commit can be linked to an issue, and the issue ID needs to appear in the format\n      `issue: #xxx`.\n    - **[Optional]** The commit can be linked to a document. If you labeled your changes as `Feature` or `Refactor`,\n      this is required. The format of a doc link should be like this: `doc: https://xxxxxxx`\n    - **[Optional]** The commit can be linked to tests (unit tests, UI tests). You can write the case names in the\n      format: `TEST: test_case_1, test_case_2`\n      <br>\n      We have set up a CI workflow to ensure that the commit message meets our formatting requirements.\n      So please make sure your message is well formatted before starting the Pull Request process.\n5. Push the changes to your remote branch and start a Pull Request.\n   > We encourage the submission of small patches and only accept PRs that contain a single commit. Therefore, please\n   split your PR into separate ones if it contains multiple commits, or squash them into a single commit if there are\n   not too many changes.\n   > The CI workflow will reject any PR that contains more than one commit.\n6. Make sure that your Pull Request adheres to the style guide and is properly documented.\n\n## Verifying and Reviewing Pull Requests\n\nA Pull Request needs to be verified by the CI workflows and reviewed by the Lynx authors before being merged.\n\nOnce you submit a Pull Request, you can invite the contributors of the repository to review your changes.\nIf you have no idea whom to invite to review your changes,\nthe GitHub branch protection rules and `git blame` are the right places to start.\n\nWhile any contributor can review your changes, at least one of the authors from\n[default reviewers](./DEFAULT_REVIEWERS) should be on the reviewer list.\nDefault reviewers can help trigger the CI workflow run to verify the changes\n(if this is your first PR for Lynx, you'll see a pending approval on the PR discussion area after you submit the PR)\nand then start the landing process.\n\n> The workflow run needs to be triggered by default reviewers so they can ensure the new changes\n  will not introduce any risks.\n\nTypically, a Pull Request will be reviewed **within one week**.\nThe landing process will be triggered manually if the changes pass all CI checks and are ready to be merged.\n\n### Static Code Analysis Tasks\nCI tasks include static code analysis, unit testing, building, etc. You can run static code analysis tasks:\n```\nsource tools/envsetup.sh\ntools/hab sync . -f\ngit lynx check\n```\n\nThe table below shows the specific tasks performed by `git lynx check`:\n\n| Task Name             | Description                                                                                           |\n| --------------------- | ----------------------------------------------------------------------------------------------------- |\n| `coding-style`        | Check the coding style of files                                                                       |\n| `commit-message`      | Check the format of commit message                                                                    |\n| `cpplint`             | Check your C++ code for style violations and potential errors                                         |\n| `java-lint`           | Check your Java code for style violations and potential errors                                        |\n| `android-check-style` | Check the import style of Android code                                                                |\n| `file-type`           | Check whether there are any binary files (We do not recommend storing binary files in the repository) |\n| `api-check`           | Check changes to public APIs and update the corresponding API files as needed                         |\n\nThese tasks can also be executed individually via commands, for example, the `api-check` task can be run with `git lynx check --checkers=api-check`.\n\nThe specific programming languages and tools supported by `coding-style` task:\n\n| Language               | Supported | Formatting Tool |\n| ---------------------- | --------- | --------------- |\n| C,C++,Objective-C,Java | ✅         | clang-format    |\n| TypeScript             | ✅         | prettier        |\n| GN                     | ✅         | gn              |\n\n## Landing Pull Requests\n\nTo make sure that new changes won't break the additional tests,\nall Pull Requests need to be landed by a self-hosted CI system.\nWhen a Pull Request is ready to be merged, default reviewers will comment the command `/land` on the PR.\nThen the CI system will start the landing process running full tests.\nPlease be patient, as that could take a while.\n\nIf a failure occurs during the landing process, we have two approaches to handle different situations:\n\n- If a change that breaks the tests is reasonable, the default reviewers will fix them in the self-hosted codebase\n  and restart the failed process. In this situation, the PR author needs to do nothing except wait for it to be merged.\n- If the changes are found to have bugs during testing, the person who started the landing process is responsible for\n  providing feedback on the issue and relevant information to the author of the PR. Once the bugs are fixed, the author\n  can restart from verifying and reviewing the PR and then wait for someone to reland it.\n\nWhen your changes pass all checks in the self-hosted CI system, the PR will be merged automatically.\n\n## Code Style\n\nOur project adheres to the coding style guidelines provided by Google.\nYou can find the detailed guidelines here: [Google's Style Guides](https://google.github.io/styleguide/).\n\nFollowing these style guides helps ensure that our code is consistent, clear, and of high quality.\nPlease make sure to familiarize yourself with these guidelines of C++, Java, Objective-C,\nand Python before contributing to the project.\n\nWe provide a convenient tool called `git lynx` that can automatically format your code when coding style checks fail. Note that it will only check changes that have been committed.\n```\ngit lynx format\n```\n\nSpecial thanks to Google for making these comprehensive style guides available for developers!\n\n## Testing\n\nMake sure your changes are covered by tests, if applicable.\nRun the existing tests to ensure everything works as expected:\n\n- [Unit Tests](testing/README_UT.md)\n\n## Code of Conduct\n\nPlease note that all participants in this project are expected to uphold our [Code of Conduct](CODE_OF_CONDUCT.md).\nBy participating, you agree to abide by its terms.\n\nWe're excited to see your contributions! Thank you!\n"
  },
  {
    "path": "CPPLINT.cfg",
    "content": "set noparent\nfilter=-,+lynx_custom/new_java_ref,+build/namespaces,+build/c++14,+build/class,+build/c++tr1,+build/deprecated,+build/endif_comment,+build/explicit_make_pair,+build/forward_decl,+build/header_guard,+build/include_what_you_use,+runtime/casting,+runtime/init,+runtime/invalid_increment,+runtime/member_string_references,+runtime/memset,+runtime/indentation_namespace,+runtime/operator,+runtime/vlog\nexclude_files=.*\\.java\nexclude_files=.*\\.mm\nexclude_files=.*\\.m\nexclude_files=.*unittest\\.cc\nexclude_files=.*mock_element\\.cc\n"
  },
  {
    "path": "DEFAULT_REVIEWERS",
    "content": "nhsprite\nAdrianLCA\nAwuiil\nBiggerDragonDragon\nChrisChan0668\ncjnhust\ncodeoxpower\ncolinaaa\ncoolkiid\nDango-2021\ndeanjingshui\nzsy-jason\nzhongyr\nZhaoSongGOO\nyuweizheng\nYellowFishWyfCPP\nYellow5A5\nwqyfavor\nviekai\nusczz\nupupming\ntalisk\nSuperYaoYU\nShouruiSong\nShouqun\nscanf3\nRuiwenTang\nRovicnowYoyi\nRobinWuu\nrAY-ooo\npopoaichuiniu\nPineconeSquirrel\npilipala195\npandazyp\nNeilcc\nMedicalProject\nlybvinci\nloongliu\nlinxs0211\nlidadating\nKingatnuaa0528\nicecreamx333\nhuzhanbo1996\nDwwWxx\nhfuttyh\nGhostFlying\nFrendyChen\nChrisChan0668\ntoretto-wt\nfzx2666\ncddcoding\npartholon\nKingAlen\nkaychn126\nHuxpro\naijm\nulivz\nzoolsher\nHuJean\nxuanyi-fu\nRandycn\nhxxft\nluhc228\njimmychanii\nlanjunq\nyulitaotao\ngivemefive9\nminalwws\nkeweibing\ngaoachao\nrel-q\nSherry-hue\nswfork\nAimee1608\nchenjiahan\nf0rdream\nkanan-su\nTamerlx\njtsky\nGuoXi\nshengweilin\nroland-reed\niaohangninja1989\nRickAi\nomnipo\nWATER1350\nkazec\ncherrycreek28\nwozaijinjiea\nbillllllllller\nXianghuDong\ncocochick\nSleen\nYradex\nminalwws\nAimee1608\nmitchilling\n\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\ngem \"cocoapods\", '1.11.3'\ngem \"ffi\", \"1.16.3\""
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "NOTICE",
    "content": "Lynx Project\nCopyright (c) 2018-2024 ByteDance Inc.\nCopyright (c) 2024 TikTok Inc.\nAll rights reserved.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n<p>\n  <a href=\"https://lynxjs.org\">\n    <img width=\"500\" alt=\"Lynx\" src=\"https://github.com/user-attachments/assets/23e35f90-1506-4b1d-8114-6bb2b8b643e7\" />\n  </a>\n</p>\n\n[![Apache licesed](https://img.shields.io/badge/License-Apache--2.0-cyan?logo=apache)](https://github.com/lynx-family/lynx/blob/develop/LICENSE)\n[![Latest release version](https://img.shields.io/github/v/release/lynx-family/lynx.svg)](https://github.com/lynx-family/lynx/releases)\n[![CI status](https://img.shields.io/github/actions/workflow/status/lynx-family/lynx/ci.yml)](https://github.com/lynx-family/lynx/actions)\n[![X (formerly Twitter) URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Fx.com%2Flynxjs_org&style=social&label=Lynx)](https://x.com/lynxjs_org)\n[![Discord](https://img.shields.io/discord/1345754526174543964?logo=discord&style=social&label=Lynx)](https://discord.gg/mXk7jqdDXk)\n\nEmpower the web community and invite more to build across platforms\n\n</div>\n\n\n## Content\n- [About Lynx](#About-Lynx)\n- [Documentation](#Documentation)\n- [How to Use Lynx](#How-to-Use-Lynx)\n- [Lynx Explorer](#Lynx-Explorer)\n- [How to Contribute](#How-to-Contribute)\n- [Discussions](#Discussions)\n- [Credits](#Credits)\n- [License](#License)\n\n## About Lynx\n\nLynx is a _family_ of open-source technologies empowering developers to use their existing web skills to create truly native UIs for both mobile and web from a single codebase, featuring performance at scale and velocity.\n- **💫 Write Once, Render Anywhere.** Enjoy native rendering on Android, iOS, and Web, or pixel-perfect consistency across mobile and desktop via our custom renderer.\n- **🌐 Web-Inspired Design.** Leverage your existing knowledge of CSS and React. We designed Lynx with the web knowledge and libraries in mind.\n- **⚡ Performance at Scale.** Achieve instant launch and silky UI responsiveness via our multithreaded engine, whether standalone or embedded.\n\nThis repository contains the **core engine** of Lynx. For other repositories in the Lynx family, visit our [org homepage](https://github.com/lynx-family).\n\n\n## Documentation\nYou shall find documentation for Lynx on [lynxjs.org](http://lynxjs.org).\n\n## How to Use Lynx\n### Requirements\nLynx apps may target iOS 10 and Android 5.0 (API 21) or newer.\n\nWe recommend using macOS as the development operating system. Windows and Linux are not yet verified or guaranteed, so you may encounter problems. If you need assistance, please file an issue, and we will be more than happy to help you address it.\n\n### Getting Started guide\n- Trying out Lynx with [hello world](https://lynxjs.org/guide/start/quick-start.html)\n- [Integrating Lynx with an Existing Application](https://lynxjs.org/guide/start/integrate-with-existing-apps.html)\n\n## Lynx Explorer\nLynx Explorer is the official app for testing and exploring Lynx. It provides native runtime environments (Android/iOS/Harmony/Windows/macOS) and bundles ReactLynx-based screens.\n\n- Build and run Lynx Explorer: see [lynx/explorer/README.md](explorer/README.md)\n\n## How to Contribute\n### [Code of Conduct][coc]\nWe are devoted to ensuring a positive, inclusive, and safe environment for all contributors. Please find our [Code of Conduct][coc] for detailed information.\n\n[coc]: CODE_OF_CONDUCT.md\n\n### [Contributing Guide][contributing]\nWe welcome you to join and become a member of Lynx Authors. It's people like you that make this project great.\n\nPlease refer to our [contributing guide][contributing] for details.\n\n[contributing]: CONTRIBUTING.md\n\n## Discussions\nBugs and feature requests are filed in [Github Issues](https://github.com/lynx-family/lynx/issues)\n\nLarge discussions and proposals are discussed in [Github Discussions](https://github.com/lynx-family/lynx/discussions)\n\nYou are always very welcome to join the [Discord Channel](https://discord.gg/mXk7jqdDXk) and meet others who are enthusiastic about making Lynx great.\n\n## Credits\nLynx makes use of several third-party libraries and draws inspiration from various projects. We would like to express our sincere gratitude to these sources.\n### Third Party Libraries\nLynx incorporates the following third-party libraries, which have significantly contributed to its functionality. We appreciate the efforts of the developers and the open-source community behind these projects:\n- [aes](https://github.com/SergeyBel/AES)\n- [benchmark](https://github.com/google/benchmark)\n- [binding](https://chromium.googlesource.com/chromium/blink/+/refs/heads/main/Source/bindings)\n- [boringssl](https://boringssl.googlesource.com/boringssl.git)\n- [boringssl_gen](https://github.com/dart-lang/boringssl_gen.git)\n- [buildroot](https://github.com/flutter/buildroot)\n- [checkstyle](https://github.com/checkstyle/checkstyle)\n- [cpp-httplib](https://github.com/yhirose/cpp-httplib)\n- [double-conversion](https://github.com/google/double-conversion)\n- [googletest](https://github.com/google/googletest)\n- [jsoncpp](https://github.com/open-source-parsers/jsoncpp)\n- [modp_b64](https://github.com/Piasy/modp_b64)\n- [node-addon-api](https://github.com/nodejs/node-addon-api)\n- [NativeScript](https://github.com/NativeScript/NativeScript)\n- [perfetto](https://android.googlesource.com/platform//external/perfetto/)\n- [rapidjson](https://github.com/Tencent/rapidjson)\n- [v8](https://chromium.googlesource.com/v8/v8.git)\n- [xctestrunner](https://github.com/google/xctestrunner)\n- [xhook](https://github.com/iqiyi/xHook.git)\n- [zlib](https://chromium.googlesource.com/chromium/src/third_party/zlib)\n\n### Referenced API Design and Implementations\nThe design of some APIs and some implementations in Lynx have been inspired by and referenced from the following outstanding projects. Their innovative designs and solutions have been invaluable in shaping Lynx:\n- [chromium](https://chromium.googlesource.com/chromium/)\n- [react-native](https://github.com/facebook/react-native)\n- [flutter engine](https://github.com/flutter/engine)\n\nWe respect the intellectual property rights of all these projects and adhere to relevant open-source licenses and usage guidelines.\n\n## [License][license]\nLynx is Apache licensed, as found in the [LICENSE][license] file.\n\n[license]: LICENSE\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Reporting a Vulnerability\n\nIf you happen to discover a potential security issue in this project, please do **not** create a public GitHub issue.\n\nWe kindly ask you to notify TikTok Security via email at [security@tiktok.com].\n\nValid vulnerability reports can be eligible for monetary rewards under our Bug Bounty program.\nFor eligible reports, we will provide additional instructions on how to claim the reward over email.\n\nWe greatly appreciate community contributions to the security of this project.\n"
  },
  {
    "path": "agents/code_review/commit_message_format.md",
    "content": "## Conventions\n\n- All commit messages MUST be written in **English**.\n- Preferred format:\n\n  Header:\n  `[Type][Scope] Short imperative summary`\n\n  Body (details):\n  `- What changed`\n  `- Why it was needed`\n  `- How it was verified / impacts`\n\n  Footer (optional):\n  `issue: #12345`\n\n### Type\n- `Feature`, `BugFix`, `Optimize`, `Refactor`, `Infra`, `Docs`, `Test`, etc., using capitalized English.\n\n### Scope\n- Module or subsystem name (e.g., `Clay`, `Layout`, `Headers`, `Painting`, `Harmony`, `Android`, `iOS`, etc.), optional but recommended.\n\n"
  },
  {
    "path": "base/CPPLINT.cfg",
    "content": "set noparent\nfilter=-,+lynx_custom/new_java_ref,+build/namespaces,+build/c++14,+build/class,+build/c++tr1,+build/deprecated,+build/endif_comment,+build/explicit_make_pair,+build/forward_decl,+build/header_guard,+build/include_what_you_use,+runtime/casting,+runtime/init,+runtime/invalid_increment,+untime/member_string_references,+runtime/memset,+runtime/indentation_namespace,+runtime/operator,+runtime/vlog\nexclude_files=.*\\.java\nexclude_files=.*\\.mm\nexclude_files=.*\\.m\nexclude_files=.*unittest.*|.*unittests.*\nexclude_files=.*mock_element\\.cc"
  },
  {
    "path": "base/README.md",
    "content": "# About //lynx/base\nThis is a basic library for the lynx project, which contains some basic components and utility functions. The aim is to make the development of the lynx project more convenient, reduce repetitive work, and facilitate code maintenance.\n# How to use?\nThe base library uses GN to organize the code. We can directly add the dependency of the base library to the project to use the basic functions provided by the base library.\n```GN\n# Add the base dependency.\nsource_set(\"lynx_project\") {\n    deps = [\n        \"//lynx/base/src:base\", // \"//lynx/base/src:base\" hasn't include trace and log functions\n    ]\n    deps += [\n        \"//lynx/base/src:base_log\", // log functions\n    ]\n}\n``` \n# What capabilities include?\n> Only some capabilities are listed. For more, please refer to the source code.\n- Log utils\n \n    The base library provides log utils. By using the [LOGV~LOGE](include/log/logging.h) log macro APIs, we can easily log messages. It supports log output, log filtering, log formatting, and customizable log output channels. Additionally, it provides a lightweight log stream class, which makes the log system more efficient.\n- thread utils\n \n    Thread utils include message loop, shared mutex, semaphore, and so on. The message loop is an event-driven thread programming model. It is used to process events and tasks for a particular thread. Its basic functionality is to accept posted tasks and run them on the associated thread.\n- String utils\n    \n    The base provides string utils, such as converting characters to numbers, converting characters to arrays, extracting substrings, and converting between unicode32, unicode16, and unicode8.\n- Trace utils\n    \n    The base provides an independent and shareable instrumentation tool named lynx-trace. Please refer to [trace/README.md](trace/README.md) for details.\n\n\n\n\n"
  },
  {
    "path": "base/include/algorithm.h",
    "content": "/*\n * Copyright 2006 The Android Open Source Project\n *\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n */\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_ALGORITHM_H_\n#define BASE_INCLUDE_ALGORITHM_H_\n\n#include <utility>\n\nnamespace lynx {\nnamespace base {\n\n/** Insertion Sort algorithm. Use this algorithm when the region to be sorted is\n * a small constant size (e.g., count <= 32)\n *\n * @param left points to the beginning of the region to be sorted\n * @param count number of items to be sorted\n * @param less_than a functor with bool operator()(T a, T b) which returns true\n * if a comes before b.\n *\n * */\ntemplate <typename T, typename C>\ninline void InsertionSort(T* left, size_t count, const C& less_than) {\n  if (count <= 1) {\n    return;\n  }\n  T* right = left + count - 1;\n  for (T* next = left + 1; next <= right; ++next) {\n    if (!less_than(*next, *(next - 1))) {\n      continue;\n    }\n    T insert = std::move(*next);\n    T* hole = next;\n    do {\n      *hole = std::move(*(hole - 1));\n      --hole;\n    } while (left < hole && less_than(insert, *(hole - 1)));\n    *hole = std::move(insert);\n  }\n}\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_ALGORITHM_H_\n"
  },
  {
    "path": "base/include/auto_create_optional.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_AUTO_CREATE_OPTIONAL_H_\n#define BASE_INCLUDE_AUTO_CREATE_OPTIONAL_H_\n\n#include <memory>\n\nnamespace lynx {\nnamespace base {\n\ntemplate <typename T>\nclass auto_create_optional {\n public:\n  auto_create_optional() = default;\n\n  auto_create_optional(const auto_create_optional& other) {\n    if (other.data_) {\n      data_ = std::make_unique<T>(*other.data_);\n    }\n  }\n\n  auto_create_optional& operator=(const auto_create_optional& other) {\n    if (this == &other) {\n      return *this;\n    }\n    if (other.data_) {\n      data_ = std::make_unique<T>(*other.data_);\n    } else {\n      data_ = nullptr;\n    }\n    return *this;\n  }\n\n  auto_create_optional(auto_create_optional&& other) = default;\n  auto_create_optional& operator=(auto_create_optional&& other) = default;\n\n  T& operator*() const { return *create_if_null(); }\n\n  T* operator->() const { return create_if_null(); }\n\n  T* get() const { return data_.get(); }\n\n  void reset() { data_.reset(); }\n\n  bool has_value() const noexcept { return data_ != nullptr; }\n\n  explicit operator bool() const noexcept { return has_value(); }\n\n private:\n  mutable std::unique_ptr<T> data_;\n\n  T* create_if_null() const {\n    if (!data_) {\n      data_ = std::make_unique<T>();\n    }\n    return data_.get();\n  }\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_AUTO_CREATE_OPTIONAL_H_\n"
  },
  {
    "path": "base/include/auto_reset.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_AUTO_RESET_H_\n#define BASE_INCLUDE_AUTO_RESET_H_\n\n#include <utility>\n\n// base::AutoReset<> is useful for setting a variable to a new value only within\n// a particular scope. An base::AutoReset<> object resets a variable to its\n// original value upon destruction, making it an alternative to writing\n// \"var = false;\" or \"var = old_val;\" at all of a block's exit points.\n//\n// This should be obvious, but note that an base::AutoReset<> instance should\n// have a shorter lifetime than its scoped_variable, to prevent invalid memory\n// writes when the base::AutoReset<> object is destroyed.\n\nnamespace lynx {\nnamespace base {\n\ntemplate <typename T>\nclass AutoReset {\n public:\n  template <typename U>\n  AutoReset(T* scoped_variable, U&& new_value)\n      : scoped_variable_(scoped_variable),\n        original_value_(\n            std::exchange(*scoped_variable_, std::forward<U>(new_value))) {}\n\n  AutoReset(AutoReset&& other)\n      : scoped_variable_(std::exchange(other.scoped_variable_, nullptr)),\n        original_value_(std::move(other.original_value_)) {}\n\n  AutoReset& operator=(AutoReset&& rhs) {\n    scoped_variable_ = std::exchange(rhs.scoped_variable_, nullptr);\n    original_value_ = std::move(rhs.original_value_);\n    return *this;\n  }\n\n  ~AutoReset() {\n    if (scoped_variable_) *scoped_variable_ = std::move(original_value_);\n  }\n\n private:\n  // `scoped_variable_` is not a raw_ptr<T> for performance reasons: Large\n  // number of non-PartitionAlloc pointees + AutoReset is typically short-lived\n  // (e.g. allocated on the stack).\n  T* scoped_variable_;\n\n  T original_value_;\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_AUTO_RESET_H_\n"
  },
  {
    "path": "base/include/base_defines.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_BASE_DEFINES_H_\n#define BASE_INCLUDE_BASE_DEFINES_H_\n\n#define unlikely(x) __builtin_expect(!!(x), 0)\n#define likely(x) __builtin_expect(!!(x), 1)\n\n#if _MSC_VER\n#define BASE_INLINE inline __forceinline\n#define BASE_NEVER_INLINE __declspec(noinline)\n#else\n#define BASE_INLINE inline __attribute__((always_inline))\n#define BASE_NEVER_INLINE __attribute__((noinline))\n#endif\n\n#ifdef __cplusplus\n#define BASE_EXTERN_C extern \"C\"\n#define BASE_EXTERN_C_BEGIN BASE_EXTERN_C {\n#define BASE_EXTERN_C_END }\n#else\n#define BASE_EXTERN_C\n#define BASE_EXTERN_C_BEGIN\n#define BASE_EXTERN_C_END\n#endif\n\n#endif  // BASE_INCLUDE_BASE_DEFINES_H_\n"
  },
  {
    "path": "base/include/base_export.h",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_BASE_EXPORT_H_\n#define BASE_INCLUDE_BASE_EXPORT_H_\n\n// Symbols in the lynx base are not exported by default due to\n// -fvisibility=hidden. We provide a macro to help you export some symbols:\n// 1.BASE_EXPORT. If certain symbols need to be accessed externally, they\n// should be marked with the BASE_EXPORT.\n//\n// If you need stricter symbol export rules from the Lynx base for purposes\n// such as better package management, you can provide BASE_NO_EXPORT to gn\n// script or customize the export rules using --version-script. Importantly, if\n// a symbol is not marked with BASE_EXPORT, it can not be exported, even if you\n// use a\n// --version-script to customize the export rules.\n#ifdef BASE_NO_EXPORT\n#define BASE_EXPORT\n#else  // BASE_NO_EXPORT\n\n#ifdef _WIN32\n#ifndef lynx_EXPORTS\n#define BASE_EXPORT __declspec(dllimport)\n#else  // lynx_EXPORTS\n#define BASE_EXPORT __declspec(dllexport)\n#endif  // lynx_EXPORTS\n#else   // _WIN32\n#define BASE_EXPORT __attribute__((visibility(\"default\")))\n#endif  // _WIN32\n#endif  // BASE_NO_EXPORT\n\n#define BASE_HIDE __attribute__((visibility(\"hidden\")))\n\n#endif  // BASE_INCLUDE_BASE_EXPORT_H_\n"
  },
  {
    "path": "base/include/base_public_headers.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../src/base.gni\")\n\n# base_log_public_headers begin\nbase_log_public_headers_list = [\n  \"debug/lynx_assert.h\",\n  \"debug/lynx_error.h\",\n  \"log/alog_wrapper.h\",\n  \"log/log_stream.h\",\n  \"log/logging.h\",\n]\nif (!is_win) {\n  base_log_public_headers_list += [ \"debug/backtrace.h\" ]\n}\nif (is_apple) {\n  base_log_public_headers_list += [ \"log/logging_darwin.h\" ]\n} else if (is_win) {\n  base_log_public_headers_list += [ \"log/logging_base.h\" ]\n} else if (is_linux) {\n  base_log_public_headers_list += [ \"log/logging_base.h\" ]\n}\nbase_log_public_headers = rebase_path(base_log_public_headers_list)\n\n# base_log_public_headers end\n\n# time_utils_public_headers begin\ntime_utils_public_headers_list = [ \"timer/time_utils.h\" ]\ntime_utils_public_headers = rebase_path(time_utils_public_headers_list)\n\n# time_utils_public_headers end\n\n# base_trace_public_headers begin\nbase_trace_public_headers_list = [ \"base_trace/trace_event_utils.h\" ]\nbase_trace_public_headers = rebase_path(base_trace_public_headers_list)\n\n# base_trace_public_headers end\n\n# base_core_public_headers begin\nbase_core_public_headers_list = [\n  \"algorithm.h\",\n  \"auto_create_optional.h\",\n  \"auto_reset.h\",\n  \"base_defines.h\",\n  \"base_export.h\",\n  \"boost/unordered.h\",\n  \"bundled_optional.h\",\n  \"cast_util.h\",\n  \"closure.h\",\n  \"compiler_specific.h\",\n  \"concurrent_queue.h\",\n  \"expected.h\",\n  \"expected_internal.h\",\n  \"file_utils.h\",\n  \"flex_optional.h\",\n  \"float_comparison.h\",\n  \"fml/concurrent_message_loop.h\",\n  \"fml/cpu_affinity.h\",\n  \"fml/delayed_task.h\",\n  \"fml/eintr_wrapper.h\",\n  \"fml/fml_trace_event_def.h\",\n  \"fml/macros.h\",\n  \"fml/make_copyable.h\",\n  \"fml/memory/ref_counted.h\",\n  \"fml/memory/ref_counted_internal.h\",\n  \"fml/memory/ref_ptr.h\",\n  \"fml/memory/ref_ptr_internal.h\",\n  \"fml/memory/task_runner_checker.h\",\n  \"fml/memory/weak_ptr.h\",\n  \"fml/memory/weak_ptr_internal.h\",\n  \"fml/message_loop.h\",\n  \"fml/message_loop_impl.h\",\n  \"fml/message_loop_task_queues.h\",\n  \"fml/platform/thread_config_setter.h\",\n  \"fml/synchronization/atomic_object.h\",\n  \"fml/synchronization/count_down_latch.h\",\n  \"fml/synchronization/semaphore.h\",\n  \"fml/synchronization/shared_mutex.h\",\n  \"fml/synchronization/sync_switch.h\",\n  \"fml/synchronization/waitable_event.h\",\n  \"fml/task_queue_id.h\",\n  \"fml/task_runner.h\",\n  \"fml/task_runner_delegate.h\",\n  \"fml/task_source.h\",\n  \"fml/task_source_grade.h\",\n  \"fml/thread.h\",\n  \"fml/time/time_delta.h\",\n  \"fml/time/time_point.h\",\n  \"fml/time/timer.h\",\n  \"fml/time/timestamp_provider.h\",\n  \"fml/unique_fd.h\",\n  \"fml/unique_object.h\",\n  \"fml/wakeable.h\",\n  \"geometry/point.h\",\n  \"geometry/rect.h\",\n  \"geometry/size.h\",\n  \"hybrid_map.h\",\n  \"linked_hash_map.h\",\n  \"lru_cache.h\",\n  \"lynx_actor.h\",\n  \"md5.h\",\n  \"memory/memory_pressure_level.h\",\n  \"no_destructor.h\",\n  \"path_utils.h\",\n  \"position.h\",\n  \"shared_vector.h\",\n  \"sorted_for_each.h\",\n  \"thread/base_semaphore.h\",\n  \"thread/pthread_rw_lock_guard.h\",\n  \"thread/timed_task.h\",\n  \"to_underlying.h\",\n  \"type_traits_addon.h\",\n  \"vector.h\",\n  \"vector2d.h\",\n  \"vector_helper.h\",\n  \"version_util.h\",\n  \"//build/build_config.h\",\n  \"fml/hash_combine.h\",\n  \"fml/raster_thread_merger.h\",\n  \"fml/shared_thread_merger.h\",\n]\nif (is_android || is_linux) {\n  base_core_public_headers_list += [ \"fml/platform/linux/timerfd.h\" ]\n}\nif (is_android) {\n  base_core_public_headers_list += [\n    \"fml/platform/android/cpu_affinity.h\",\n    \"fml/platform/android/message_loop_android.h\",\n    \"fml/synchronization/shared_mutex_std.h\",\n    \"platform/android/java_type.h\",\n    \"platform/android/jni_convert_helper.h\",\n    \"platform/android/jni_utils.h\",\n    \"platform/android/scoped_java_ref.h\",\n  ]\n} else if (is_harmony) {\n  base_core_public_headers_list += [\n    \"fml/platform/harmony/message_loop_harmony.h\",\n    \"fml/platform/linux/timerfd.h\",\n    \"fml/synchronization/shared_mutex_std.h\",\n    \"platform/harmony/harmony_vsync_manager.h\",\n    \"platform/harmony/napi_util.h\",\n  ]\n} else if (is_linux) {\n  base_core_public_headers_list += [ \"fml/synchronization/shared_mutex_std.h\" ]\n  if (!use_custom_message_loop) {\n    base_core_public_headers_list +=\n        [ \"fml/platform/linux/message_loop_linux.h\" ]\n  }\n} else if (is_apple) {\n  base_core_public_headers_list += [\n    \"fml/platform/darwin/cf_utils.h\",\n    \"fml/synchronization/shared_mutex_posix.h\",\n    \"platform/darwin/type_utils.h\",\n  ]\n  if (!use_custom_message_loop) {\n    base_core_public_headers_list +=\n        [ \"fml/platform/darwin/message_loop_darwin.h\" ]\n  }\n} else if (is_win) {\n  base_core_public_headers_list += [\n    \"fml/platform/win/message_loop_win.h\",\n    \"fml/platform/win/task_runner_win32.h\",\n    \"fml/platform/win/task_runner_win32_window.h\",\n    \"fml/synchronization/shared_mutex_std.h\",\n    \"string/string_conversion_win.h\",\n  ]\n}\nbase_core_public_headers = rebase_path(base_core_public_headers_list)\n\n# base_core_public_headers end\n\n# string_utils_public_headers begin\nstring_utils_public_headers_list = [\n  \"string/quickjs_dtoa.h\",\n  \"string/string_number_convert.h\",\n  \"string/string_utils.h\",\n]\nstring_utils_public_headers = rebase_path(string_utils_public_headers_list)\n\n# string_utils_public_headers end\n\n# base_values_public_headers begin\nbase_values_public_headers_list = [\n  \"value/array.h\",\n  \"value/base_value.h\",\n  \"value/byte_array.h\",\n  \"value/lynx_value_api.h\",\n  \"value/lynx_value_extended.h\",\n  \"value/path_parser.h\",\n  \"value/ref_counted_class.h\",\n  \"value/ref_type.h\",\n  \"value/table.h\",\n]\nbase_values_public_headers = rebase_path(base_values_public_headers_list)\n\n# base_values_public_headers end\n\n# values_public_headers begin\nvalues_public_headers_list = [\n  \"value/base_string.h\",\n  \"value/lynx_api_types.h\",\n  \"value/lynx_value_types.h\",\n]\nvalues_public_headers = rebase_path(values_public_headers_list)\n\n# values_public_headers end\n\n# datauri_utils_public_headers begin\ndatauri_utils_public_headers_list = [ \"datauri_utils.h\" ]\ndatauri_utils_public_headers = rebase_path(datauri_utils_public_headers_list)\n\n# datauri_utils_public_headers end\n\nbase_public_headers = base_log_public_headers + time_utils_public_headers +\n                      base_core_public_headers + string_utils_public_headers +\n                      base_values_public_headers + values_public_headers +\n                      datauri_utils_public_headers\n"
  },
  {
    "path": "base/include/base_trace/trace_event_utils.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_BASE_TRACE_TRACE_EVENT_UTILS_H_\n#define BASE_INCLUDE_BASE_TRACE_TRACE_EVENT_UTILS_H_\n\n#include <cstdint>\n#include <functional>\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace trace {\nenum class BaseTraceEventType : int32_t {\n  TYPE_UNSPECIFIED = 0,\n  TYPE_SLICE_BEGIN = 1,\n  TYPE_SLICE_END = 2,\n  TYPE_INSTANT = 3,\n  TYPE_COUNTER = 4,\n};\nusing trace_backend_ptr = void (*)(const char* category, const char* name,\n                                   BaseTraceEventType phase);\nBASE_EXPORT void SetTraceBackend(trace_backend_ptr backend);\nvoid TraceEventBegin(const char* category, const char* name);\nvoid TraceEventEnd(const char* category, const char* name);\n}  // namespace trace\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_BASE_TRACE_TRACE_EVENT_UTILS_H_\n"
  },
  {
    "path": "base/include/boost/unordered.h",
    "content": "// Copyright 2001, 2002 Peter Dimov and Multi Media Ltd\n// Copyright 2002, 2018-2022 Peter Dimov\n// Copyright 2002-2018 Peter Dimov\n// Copyright 2004 Pavel Vozenilek\n// Copyright 2005-2009 Daniel James\n// Copyright 2005-2014 Daniel James\n// Copyright 2006-2009 Emil Dotchevski and Reverge Studios, Inc\n// Copyright 2007, 2014 Peter Dimov\n// Copyright 2008-2009 Emil Dotchevski and Reverge Studios, Inc\n// Copyright 2008-2016 Daniel James\n// Copyright 2015 Ion Gaztanaga\n// Copyright 2017 Peter Dimov\n// Copyright 2017, 2018 Peter Dimov\n// Copyright 2017, 2022 Peter Dimov\n// Copyright 2018 Glen Joseph Fernandes\n// Copyright 2019, 2021 Peter Dimov\n// Copyright 2021 Peter Dimov\n// Copyright 2021, 2022 Peter Dimov\n// Copyright 2022 Christian Mazakas\n// Copyright 2022 Joaquin M Lopez Munoz\n// Copyright 2022 Peter Dimov\n// Copyright 2022, 2023 Peter Dimov\n// Copyright 2022-2023 Christian Mazakas\n// Copyright 2022-2023 Joaquin M Lopez Munoz\n// Copyright 2023 Christian Mazakas\n// Copyright Beman Dawes 2011\n//\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n// Amalgamated single header imported to Lynx with minor adaptations\n// from https://github.com/MikePopoloski/boost_unordered\n\n#ifndef BASE_INCLUDE_BOOST_UNORDERED_H_\n#define BASE_INCLUDE_BOOST_UNORDERED_H_\n\n#include <climits>\n#include <cmath>\n#include <complex>\n#include <cstddef>\n#include <cstdint>\n#include <cstdio>\n#include <cstring>\n#include <exception>\n#include <functional>\n#include <initializer_list>\n#include <iosfwd>\n#include <iterator>\n#include <limits>\n#include <memory>\n#include <optional>\n#include <stdexcept>\n#include <string>\n#include <system_error>\n#include <tuple>\n#include <type_traits>\n#include <typeindex>\n#include <utility>\n#include <variant>\n\n// Lynx Add Begin\n#include \"base/include/type_traits_addon.h\"\n\n// This template type is used to simplify the use of boost map for \n// lynx::base::HybridMap.\nnamespace boost {\ntemplate <template <typename...> class T, template <typename> class Hash,\n          template <typename> class Equal>\nstruct MapPolicy {\n  template <typename Key, typename Value>\n  using type = T<Key, Value, Hash<Key>, Equal<Key>>;\n\n  template <typename Key, typename Value>\n  using plain_bytes_type =\n      T<lynx::base::TypeOfPlainBytes<Key>, lynx::base::TypeOfPlainBytes<Value>,\n        Hash<lynx::base::TypeOfPlainBytes<Key>>,\n        Equal<lynx::base::TypeOfPlainBytes<Key>>>;\n};\n}  // namespace boost\n\nnamespace boost {\n  namespace unordered {\n    namespace detail {\n      namespace foa {\n        // Forward declare flat_map_types for custom `nosize_transfer_element` \n        // impl.\n        template <class Key, class T> struct flat_map_types;\n      \n        // Tag type to emplace key-value with key as plain bytes and value as \n        // uninitialized.\n        struct try_emplace_plain_bytes_key_t{};\n      \n        // Tag type to emplace key-value pair as plain bytes.\n        struct try_emplace_plain_bytes_t{};\n      }\n    }\n  }\n}\n\n// Lynx polyfill for undefined and C++17\nnamespace boost {\nusing uint16_t = unsigned short;\n}\n\n// Polyfill under C++20\n#if (defined(__cplusplus) && (__cplusplus >= 202002L))\n\n#include <bit>\n\n#if defined(_MSC_VER)\n#define CXX20_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]\n#else\n#define CXX20_NO_UNIQUE_ADDRESS [[no_unique_address]]\n#endif\n\n#else\n\n// Not C++ 20\n#define CXX20_NO_UNIQUE_ADDRESS\n\nnamespace std {\n\ntemplate <class T>\nstruct remove_cvref {\n  using type = std::remove_cv_t<std::remove_reference_t<T>>;\n};\n\ntemplate <class T>\nusing remove_cvref_t = typename remove_cvref<T>::type;\n\ntemplate <class T>\nconstexpr T *to_address(T *p) noexcept {\n  static_assert(!std::is_function_v<T>);\n  return p;\n}\n\ntemplate <class T>\nconstexpr auto to_address(const T &p) noexcept {\n  //    if constexpr (requires{ std::pointer_traits<T>::to_address(p); })\n  //        return std::pointer_traits<T>::to_address(p);\n  //    else\n  return std::to_address(p.operator->());\n}\n\ninline constexpr int builtin_clz(unsigned x) noexcept {\n  return __builtin_clz(x);\n}\n\ninline constexpr int builtin_clz(unsigned long x) noexcept {\n  return __builtin_clzl(x);\n}\n\ninline constexpr int builtin_clz(unsigned long long x) noexcept {\n  return __builtin_clzll(x);\n}\n\ninline constexpr int builtin_ctz(unsigned x) noexcept {\n  return __builtin_ctz(x);\n}\n\ninline constexpr int builtin_ctz(unsigned long x) noexcept {\n  return __builtin_ctzl(x);\n}\n\ninline constexpr int builtin_ctz(unsigned long long x) noexcept {\n  return __builtin_ctzll(x);\n}\n\ntemplate <class T>\nconstexpr T rotr(T t, unsigned int cnt) noexcept {\n  const unsigned int kDig = numeric_limits<T>::digits;\n  if ((cnt % kDig) == 0) return t;\n  return (t >> (cnt % kDig)) | (t << (kDig - (cnt % kDig)));\n}\n\ntemplate <class T>\nconstexpr int countr_zero(T t) noexcept {\n  if (t == 0) return numeric_limits<T>::digits;\n\n  if constexpr (sizeof(T) <= sizeof(unsigned int))\n    return builtin_ctz(static_cast<unsigned int>(t));\n  else if constexpr (sizeof(T) <= sizeof(unsigned long))\n    return builtin_ctz(static_cast<unsigned long>(t));\n  else if constexpr (sizeof(T) <= sizeof(unsigned long long))\n    return builtin_ctz(static_cast<unsigned long long>(t));\n  else {\n    static_assert(sizeof(T) == 0, \"countr_zero<T> where T is too large\");\n    return 0;\n  }\n}\n\ntemplate <class T>\nconstexpr int countl_zero(T t) noexcept {\n  if (t == 0) return numeric_limits<T>::digits;\n\n  if constexpr (sizeof(T) <= sizeof(unsigned int))\n    return builtin_clz(static_cast<unsigned int>(t)) -\n           (numeric_limits<unsigned int>::digits - numeric_limits<T>::digits);\n  else if constexpr (sizeof(T) <= sizeof(unsigned long))\n    return builtin_clz(static_cast<unsigned long>(t)) -\n           (numeric_limits<unsigned long>::digits - numeric_limits<T>::digits);\n  else if constexpr (sizeof(T) <= sizeof(unsigned long long))\n    return builtin_clz(static_cast<unsigned long long>(t)) -\n           (numeric_limits<unsigned long long>::digits -\n            numeric_limits<T>::digits);\n  else {\n    static_assert(sizeof(T) == 0, \"countl_zero<T> where T is too large\");\n    return 0;\n  }\n}\n\ntemplate <typename T>\nconstexpr T bit_log2(T t) noexcept {\n  return numeric_limits<T>::digits - 1 - countl_zero(t);\n}\n\ntemplate <typename T>\nconstexpr int bit_width(T t) noexcept {\n  return t == 0 ? 0 : static_cast<int>(bit_log2(t) + 1);\n}\n\n}\n#endif\n\n// clang-format off\n// Lynx Add End\n\n// This is a minimal header that contains only the small set\n// config entries needed to use boost::unordered, so that the\n// whole boost config lib doesn't need to be pulled in.\n#ifdef __clang__\n#  ifndef BOOST_CLANG\n#    define BOOST_CLANG 1\n#    define BOOST_CLANG_VERSION (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__ % 100)\n#  endif\n#elif defined(__GNUC__)\n#  ifndef BOOST_GCC\n#    define BOOST_GCC (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)\n#    define BOOST_GCC_VERSION BOOST_GCC\n#  endif\n#elif defined(_MSC_VER)\n#  ifndef BOOST_MSVC\n#    define BOOST_MSVC _MSC_VER\n#  endif\n#endif\n\n#ifndef BOOST_FORCEINLINE\n#  if defined(_MSC_VER)\n#    define BOOST_FORCEINLINE __forceinline\n#  elif defined(__GNUC__) && __GNUC__ > 3\n     // Clang also defines __GNUC__ (as 4)\n#    define BOOST_FORCEINLINE inline __attribute__ ((__always_inline__))\n#  else\n#    define BOOST_FORCEINLINE inline\n#  endif\n#endif\n\n#ifndef BOOST_NOINLINE\n#  if defined(_MSC_VER)\n#    define BOOST_NOINLINE __declspec(noinline)\n#  elif defined(__GNUC__) && __GNUC__ > 3\n     // Clang also defines __GNUC__ (as 4)\n#    if defined(__CUDACC__)\n       // nvcc doesn't always parse __noinline__,\n       // see: https://svn.boost.org/trac/boost/ticket/9392\n#      define BOOST_NOINLINE __attribute__ ((noinline))\n#    elif defined(__HIP__)\n       // See https://github.com/boostorg/config/issues/392\n#      define BOOST_NOINLINE __attribute__ ((noinline))\n#    else\n#      define BOOST_NOINLINE __attribute__ ((__noinline__))\n#    endif\n#  else\n#    define BOOST_NOINLINE\n#  endif\n#endif\n\n#if defined(BOOST_GCC) || defined(BOOST_CLANG)\n#  define BOOST_LIKELY(x) __builtin_expect(x, 1)\n#  define BOOST_UNLIKELY(x) __builtin_expect(x, 0)\n#  define BOOST_SYMBOL_VISIBLE __attribute__((__visibility__(\"default\")))\n#else\n#  define BOOST_SYMBOL_VISIBLE\n#endif\n\n#ifndef BOOST_LIKELY\n#  define BOOST_LIKELY(x) x\n#endif\n#ifndef BOOST_UNLIKELY\n#  define BOOST_UNLIKELY(x) x\n#endif\n\n#ifndef BOOST_NORETURN\n#  if defined(_MSC_VER)\n#    define BOOST_NORETURN __declspec(noreturn)\n#  elif defined(__GNUC__) || defined(__CODEGEARC__) && defined(__clang__)\n#    define BOOST_NORETURN __attribute__ ((__noreturn__))\n#  elif defined(__has_attribute) && defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x5130)\n#    if __has_attribute(noreturn)\n#      define BOOST_NORETURN [[noreturn]]\n#    endif\n#  elif defined(__has_cpp_attribute)\n#    if __has_cpp_attribute(noreturn)\n#      define BOOST_NORETURN [[noreturn]]\n#    endif\n#  endif\n#endif\n\n#ifndef BOOST_NORETURN\n#  define BOOST_NO_NORETURN\n#  define BOOST_NORETURN\n#endif\n\n#if BOOST_MSVC\n  #if !defined(_CPPUNWIND) && !defined(BOOST_NO_EXCEPTIONS)\n    #define BOOST_NO_EXCEPTIONS\n  #endif\n  #if !defined(_CPPRTTI) && !defined(BOOST_NO_RTTI)\n    #define BOOST_NO_RTTI\n  #endif\n#elif BOOST_GCC\n  #if !defined(__EXCEPTIONS) && !defined(BOOST_NO_EXCEPTIONS)\n    #define BOOST_NO_EXCEPTIONS\n  #endif\n  #if !defined(__GXX_RTTI) && !defined(BOOST_NO_RTTI)\n    #define BOOST_NO_RTTI\n  #endif\n#elif BOOST_CLANG\n  #if !__has_feature(cxx_exceptions) && !defined(BOOST_NO_EXCEPTIONS)\n    #define BOOST_NO_EXCEPTIONS\n  #endif\n  #if !__has_feature(cxx_rtti) && !defined(BOOST_NO_RTTI)\n    #define BOOST_NO_RTTI\n  #endif\n#endif\n\n// This is the only predef define needed for boost::unordered, so pull it\n// out here so we don't need to include all of predef.\n#if \\\n    defined(__ARM_ARCH) || defined(__TARGET_ARCH_ARM) || \\\n    defined(__TARGET_ARCH_THUMB) || defined(_M_ARM) || \\\n    defined(__arm__) || defined(__arm64) || defined(__thumb__) || \\\n    defined(_M_ARM64) || defined(__aarch64__) || defined(__AARCH64EL__) || \\\n    defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || \\\n    defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || \\\n    defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || \\\n    defined(__ARM_ARCH_6KZ__) || defined(__ARM_ARCH_6T2__) || \\\n    defined(__ARM_ARCH_5TE__) || defined(__ARM_ARCH_5TEJ__) || \\\n    defined(__ARM_ARCH_4T__) || defined(__ARM_ARCH_4__)\n#define BOOST_ARCH_ARM 1\n#else\n#define BOOST_ARCH_ARM 0\n#endif\n// Copyright 2005-2009 Daniel James.\n// Copyright 2021, 2022 Peter Dimov.\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_FUNCTIONAL_HASH_FWD_HPP\n#define BOOST_FUNCTIONAL_HASH_FWD_HPP\n\nnamespace boost\n{\n\nnamespace container_hash\n{\n\ntemplate<class T> struct is_range;\ntemplate<class T> struct is_contiguous_range;\ntemplate<class T> struct is_unordered_range;\ntemplate<class T> struct is_described_class;\ntemplate<class T> struct is_tuple_like;\n\n} // namespace container_hash\n\ntemplate<class T> struct hash;\n\ntemplate<class T> void hash_combine( std::size_t& seed, T const& v );\n\ntemplate<class It> void hash_range( std::size_t&, It, It );\ntemplate<class It> std::size_t hash_range( It, It );\n\ntemplate<class It> void hash_unordered_range( std::size_t&, It, It );\ntemplate<class It> std::size_t hash_unordered_range( It, It );\n\n} // namespace boost\n\n#endif // #ifndef BOOST_FUNCTIONAL_HASH_FWD_HPP\n/* Fast open-addressing concurrent hashset.\n *\n * Copyright 2023 Christian Mazakas.\n * Copyright 2023 Joaquin M Lopez Munoz.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifndef BOOST_UNORDERED_CONCURRENT_FLAT_SET_FWD_HPP\n#define BOOST_UNORDERED_CONCURRENT_FLAT_SET_FWD_HPP\n\nnamespace boost {\n  namespace unordered {\n\n    template <class Key, class Hash = boost::hash<Key>,\n      class Pred = std::equal_to<Key>,\n      class Allocator = std::allocator<Key> >\n    class concurrent_flat_set;\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      concurrent_flat_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      concurrent_flat_set<Key, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      concurrent_flat_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      concurrent_flat_set<Key, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class Hash, class Pred, class Alloc>\n    void swap(concurrent_flat_set<Key, Hash, Pred, Alloc>& x,\n      concurrent_flat_set<Key, Hash, Pred, Alloc>& y)\n      noexcept(noexcept(x.swap(y)));\n\n    template <class K, class H, class P, class A, class Predicate>\n    typename concurrent_flat_set<K, H, P, A>::size_type erase_if(\n      concurrent_flat_set<K, H, P, A>& c, Predicate pred);\n\n  } // namespace unordered\n\n  using boost::unordered::concurrent_flat_set;\n} // namespace boost\n\n#endif // BOOST_UNORDERED_CONCURRENT_FLAT_SET_FWD_HPP\n// Copyright (C) 2023 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_DETAIL_FOA_FLAT_SET_TYPES_HPP\n#define BOOST_UNORDERED_DETAIL_FOA_FLAT_SET_TYPES_HPP\n\nnamespace boost {\n  namespace unordered {\n    namespace detail {\n      namespace foa {\n        template <class Key> struct flat_set_types\n        {\n          using key_type = Key;\n          using init_type = Key;\n          using value_type = Key;\n\n          static Key const& extract(value_type const& key) { return key; }\n\n          using element_type = value_type;\n\n          static Key& value_from(element_type& x) { return x; }\n\n          static element_type&& move(element_type& x) { return std::move(x); }\n\n          template <class A, class... Args>\n          static void construct(A& al, value_type* p, Args&&... args)\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::construct(al, p, std::forward<Args>(args)...);\n          }\n\n          template <class A> static void destroy(A& al, value_type* p) noexcept\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::destroy(al, p);\n          }\n        };\n      } // namespace foa\n    }   // namespace detail\n  }     // namespace unordered\n} // namespace boost\n\n#endif // BOOST_UNORDERED_DETAIL_FOA_FLAT_SET_TYPES_HPP\n#ifndef BOOST_CURRENT_FUNCTION_HPP_INCLUDED\n#define BOOST_CURRENT_FUNCTION_HPP_INCLUDED\n\n// MS compatible compilers support #pragma once\n\n#if defined(_MSC_VER) && (_MSC_VER >= 1020)\n# pragma once\n#endif\n\n//\n//  boost/current_function.hpp - BOOST_CURRENT_FUNCTION\n//\n//  Copyright 2002-2018 Peter Dimov\n//\n//  Distributed under the Boost Software License, Version 1.0.\n//  See accompanying file LICENSE_1_0.txt or copy at\n//  http://www.boost.org/LICENSE_1_0.txt\n//\n//  http://www.boost.org/libs/assert\n//\n\nnamespace boost\n{\n\nnamespace detail\n{\n\ninline void current_function_helper()\n{\n\n#ifdef  BOOST_DISABLE_CURRENT_FUNCTION \n\n# define BOOST_CURRENT_FUNCTION \"(unknown)\"\n\n#elif defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__) || defined(__clang__)\n\n# define BOOST_CURRENT_FUNCTION __PRETTY_FUNCTION__\n\n#elif defined(__DMC__) && (__DMC__ >= 0x810)\n\n# define BOOST_CURRENT_FUNCTION __PRETTY_FUNCTION__\n\n#elif defined(__FUNCSIG__)\n\n# define BOOST_CURRENT_FUNCTION __FUNCSIG__\n\n#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500))\n\n# define BOOST_CURRENT_FUNCTION __FUNCTION__\n\n#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550)\n\n# define BOOST_CURRENT_FUNCTION __FUNC__\n\n#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)\n\n# define BOOST_CURRENT_FUNCTION __func__\n\n#elif defined(__cplusplus) && (__cplusplus >= 201103)\n\n# define BOOST_CURRENT_FUNCTION __func__\n\n#else\n\n# define BOOST_CURRENT_FUNCTION \"(unknown)\"\n\n#endif\n\n}\n\n} // namespace detail\n\n} // namespace boost\n\n#endif // #ifndef BOOST_CURRENT_FUNCTION_HPP_INCLUDED\n//\n//  boost/assert.hpp - BOOST_ASSERT(expr)\n//                     BOOST_ASSERT_MSG(expr, msg)\n//                     BOOST_VERIFY(expr)\n//                     BOOST_VERIFY_MSG(expr, msg)\n//                     BOOST_ASSERT_IS_VOID\n//\n//  Copyright (c) 2001, 2002 Peter Dimov and Multi Media Ltd.\n//  Copyright (c) 2007, 2014 Peter Dimov\n//  Copyright (c) Beman Dawes 2011\n//  Copyright (c) 2015 Ion Gaztanaga\n//\n//  Distributed under the Boost Software License, Version 1.0.\n//  See accompanying file LICENSE_1_0.txt or copy at\n//  http://www.boost.org/LICENSE_1_0.txt\n//\n//  Note: There are no include guards. This is intentional.\n//\n//  See http://www.boost.org/libs/assert/assert.html for documentation.\n//\n\n//\n// Stop inspect complaining about use of 'assert':\n//\n// boostinspect:naassert_macro\n//\n\n//\n// BOOST_ASSERT, BOOST_ASSERT_MSG, BOOST_ASSERT_IS_VOID\n//\n\n#undef BOOST_ASSERT\n#undef BOOST_ASSERT_MSG\n#undef BOOST_ASSERT_IS_VOID\n\n#if defined(BOOST_DISABLE_ASSERTS) || ( defined(BOOST_ENABLE_ASSERT_DEBUG_HANDLER) && defined(NDEBUG) )\n\n# define BOOST_ASSERT(expr) ((void)0)\n# define BOOST_ASSERT_MSG(expr, msg) ((void)0)\n# define BOOST_ASSERT_IS_VOID\n\n#elif defined(BOOST_ENABLE_ASSERT_HANDLER) || ( defined(BOOST_ENABLE_ASSERT_DEBUG_HANDLER) && !defined(NDEBUG) )\n\nnamespace boost\n{\n    void assertion_failed(char const * expr, char const * function, char const * file, long line); // user defined\n    void assertion_failed_msg(char const * expr, char const * msg, char const * function, char const * file, long line); // user defined\n} // namespace boost\n\n#define BOOST_ASSERT(expr) (BOOST_LIKELY(!!(expr))? ((void)0): ::boost::assertion_failed(#expr, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__))\n#define BOOST_ASSERT_MSG(expr, msg) (BOOST_LIKELY(!!(expr))? ((void)0): ::boost::assertion_failed_msg(#expr, msg, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__))\n\n#else\n\n# include <assert.h> // .h to support old libraries w/o <cassert> - effect is the same\n\n# define BOOST_ASSERT(expr) assert(expr)\n# define BOOST_ASSERT_MSG(expr, msg) assert((expr)&&(msg))\n#ifdef NDEBUG\n# define BOOST_ASSERT_IS_VOID\n#endif\n\n#endif\n\n//\n// BOOST_VERIFY, BOOST_VERIFY_MSG\n//\n\n#undef BOOST_VERIFY\n#undef BOOST_VERIFY_MSG\n\n#if defined(BOOST_DISABLE_ASSERTS) || ( !defined(BOOST_ENABLE_ASSERT_HANDLER) && defined(NDEBUG) )\n\n# define BOOST_VERIFY(expr) ((void)(expr))\n# define BOOST_VERIFY_MSG(expr, msg) ((void)(expr))\n\n#else\n\n# define BOOST_VERIFY(expr) BOOST_ASSERT(expr)\n# define BOOST_VERIFY_MSG(expr, msg) BOOST_ASSERT_MSG(expr,msg)\n\n#endif\n/*\nCopyright 2018 Glen Joseph Fernandes\n(glenjofe@gmail.com)\n\nDistributed under the Boost Software License, Version 1.0.\n(http://www.boost.org/LICENSE_1_0.txt)\n*/\n#ifndef BOOST_CORE_EMPTY_VALUE_HPP\n#define BOOST_CORE_EMPTY_VALUE_HPP\n\n#if defined(BOOST_GCC_VERSION) && (BOOST_GCC_VERSION >= 40700)\n#define BOOST_DETAIL_EMPTY_VALUE_BASE\n#elif defined(BOOST_INTEL) && defined(_MSC_VER) && (_MSC_VER >= 1800)\n#define BOOST_DETAIL_EMPTY_VALUE_BASE\n#elif defined(BOOST_MSVC) && (BOOST_MSVC >= 1800)\n#define BOOST_DETAIL_EMPTY_VALUE_BASE\n#elif defined(BOOST_CLANG) && !defined(__CUDACC__)\n#if __has_feature(is_empty) && __has_feature(is_final)\n#define BOOST_DETAIL_EMPTY_VALUE_BASE\n#endif\n#endif\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable:4510)\n#endif\n\nnamespace boost {\n\ntemplate<class T>\nstruct use_empty_value_base {\n    enum {\n#ifdef BOOST_DETAIL_EMPTY_VALUE_BASE\n        value = __is_empty(T) && !__is_final(T)\n#else\n        value = false\n#endif\n    };\n};\n\nstruct empty_init_t { };\n\nnamespace empty_ {\n\ntemplate<class T, unsigned N = 0,\n    bool E = boost::use_empty_value_base<T>::value>\nclass empty_value {\npublic:\n    typedef T type;\n\n    empty_value() = default;\n\n    constexpr empty_value(boost::empty_init_t)\n        : value_() { }\n\n    template<class U, class... Args>\n    constexpr empty_value(boost::empty_init_t, U&& value, Args&&... args)\n        : value_(std::forward<U>(value), std::forward<Args>(args)...) { }\n\n    constexpr const T& get() const noexcept {\n        return value_;\n    }\n\n    constexpr T& get() noexcept {\n        return value_;\n    }\n\nprivate:\n    T value_;\n};\n\ntemplate<class T, unsigned N>\nclass empty_value<T, N, true>\n    : T {\npublic:\n    typedef T type;\n\n    empty_value() = default;\n\n    constexpr empty_value(boost::empty_init_t)\n        : T() { }\n\n    template<class U, class... Args>\n    constexpr empty_value(boost::empty_init_t, U&& value, Args&&... args)\n        : T(std::forward<U>(value), std::forward<Args>(args)...) { }\n\n    constexpr const T& get() const noexcept {\n        return *this;\n    }\n\n    constexpr T& get() noexcept {\n        return *this;\n    }\n};\n\n}\n\nusing empty_::empty_value;\n\ninline constexpr empty_init_t empty_init = empty_init_t();\n\n}\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n#endif\n#ifndef BOOST_CORE_NO_EXCEPTIONS_SUPPORT_HPP\n#define BOOST_CORE_NO_EXCEPTIONS_SUPPORT_HPP\n\n#ifdef _MSC_VER\n#  pragma once\n#endif\n\n//----------------------------------------------------------------------\n// (C) Copyright 2004 Pavel Vozenilek.\n// Use, modification and distribution is subject to the Boost Software\n// License, Version 1.0. (See accompanying file LICENSE_1_0.txt\n// or copy at http://www.boost.org/LICENSE_1_0.txt)\n//\n//\n// This file contains helper macros used when exception support may be\n// disabled (as indicated by macro BOOST_NO_EXCEPTIONS).\n//\n// Before picking up these macros you may consider using RAII techniques\n// to deal with exceptions - their syntax can be always the same with \n// or without exception support enabled.\n//----------------------------------------------------------------------\n\n#if !(defined BOOST_NO_EXCEPTIONS)\n#    define BOOST_TRY { try\n#    define BOOST_CATCH(x) catch(x)\n#    define BOOST_RETHROW throw;\n#    define BOOST_CATCH_END }\n#else\n#    if !defined(BOOST_MSVC) || BOOST_MSVC >= 1900\n#        define BOOST_TRY { if (true)\n#        define BOOST_CATCH(x) else if (false)\n#    else\n         // warning C4127: conditional expression is constant\n#        define BOOST_TRY { \\\n             __pragma(warning(push)) \\\n             __pragma(warning(disable: 4127)) \\\n             if (true) \\\n             __pragma(warning(pop))\n#        define BOOST_CATCH(x) else \\\n             __pragma(warning(push)) \\\n             __pragma(warning(disable: 4127)) \\\n             if (false) \\\n             __pragma(warning(pop))\n#    endif\n#    define BOOST_RETHROW\n#    define BOOST_CATCH_END }\n#endif\n\n#endif\n// Copyright (C) 2023 Christian Mazakas\n//\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_DETAIL_OPT_STORAGE_HPP\n#define BOOST_UNORDERED_DETAIL_OPT_STORAGE_HPP\n\nnamespace boost {\n  namespace unordered {\n    namespace detail {\n      template <class T> union opt_storage\n      {\n        CXX20_NO_UNIQUE_ADDRESS T t_;\n\n        opt_storage() {}\n        ~opt_storage() {}\n\n        T* address() noexcept { return std::addressof(t_); }\n        T const* address() const noexcept { return std::addressof(t_); }\n      };\n    } // namespace detail\n  } // namespace unordered\n} // namespace boost\n\n#endif // BOOST_UNORDERED_DETAIL_OPT_STORAGE_HPP\n/* Copyright 2024 Braden Ganetsky.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifndef BOOST_UNORDERED_DETAIL_ALLOCATOR_CONSTRUCTED_HPP\n#define BOOST_UNORDERED_DETAIL_ALLOCATOR_CONSTRUCTED_HPP\n\nnamespace boost {\n  namespace unordered {\n    namespace detail {\n\n      struct allocator_policy\n      {\n        template <class Allocator, class T, class... Args>\n        static void construct(Allocator& a, T* p, Args&&... args)\n        {\n          std::allocator_traits<std::remove_cvref_t<decltype(a)>>::construct(a, p, std::forward<Args>(args)...);\n        }\n\n        template <class Allocator, class T>\n        static void destroy(Allocator& a, T* p)\n        {\n          std::allocator_traits<std::remove_cvref_t<decltype(a)>>::destroy(a, p);\n        }\n      };\n\n      /* constructs a stack-based object with the given policy and allocator */\n      template <class Allocator, class T, class Policy = allocator_policy>\n      class allocator_constructed\n      {\n        opt_storage<T> storage;\n        Allocator alloc;\n\n      public:\n        template <class... Args>\n        allocator_constructed(Allocator const& alloc_, Args&&... args)\n            : alloc(alloc_)\n        {\n          Policy::construct(\n            alloc, storage.address(), std::forward<Args>(args)...);\n        }\n\n        ~allocator_constructed() { Policy::destroy(alloc, storage.address()); }\n\n        T& value() { return *storage.address(); }\n      };\n\n    }\n  }\n}\n\n#endif\n// Copyright 2023 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_DETAIL_STATIC_ASSERT_HPP\n#define BOOST_UNORDERED_DETAIL_STATIC_ASSERT_HPP\n\n#pragma once\n\n#define BOOST_UNORDERED_STATIC_ASSERT(...)                                     \\\n  static_assert(__VA_ARGS__, #__VA_ARGS__)\n\n#endif // BOOST_UNORDERED_DETAIL_STATIC_ASSERT_HPP\n/* Copyright 2022 Joaquin M Lopez Munoz.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifndef BOOST_UNORDERED_DETAIL_NARROW_CAST_HPP\n#define BOOST_UNORDERED_DETAIL_NARROW_CAST_HPP\n\nnamespace boost{\nnamespace unordered{\nnamespace detail{\n\ntemplate<typename To,typename From>\nconstexpr To narrow_cast(From x) noexcept\n{\n  BOOST_UNORDERED_STATIC_ASSERT(std::is_integral<From>::value);\n  BOOST_UNORDERED_STATIC_ASSERT(std::is_integral<To>::value);\n  BOOST_UNORDERED_STATIC_ASSERT(sizeof(From)>=sizeof(To));\n\n  return static_cast<To>(\n    x\n\n#ifdef __MSVC_RUNTIME_CHECKS\n    /* Avoids VS's \"Run-Time Check Failure #1 - A cast to a smaller data type\n     * has caused a loss of data.\"\n     */\n    &static_cast<typename std::make_unsigned<To>::type>(~static_cast<To>(0))\n#endif\n  );\n}\n\n}\n}\n}\n\n#endif\n#ifndef BOOST_UNORDERED_DETAIL_MULX_HPP\n#define BOOST_UNORDERED_DETAIL_MULX_HPP\n\n// Copyright 2022 Peter Dimov.\n// Copyright 2022 Joaquin M Lopez Munoz.\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt)\n\n#if defined(_MSC_VER) && !defined(__clang__)\n# include <intrin.h>\n#endif\n\nnamespace boost {\nnamespace unordered {\nnamespace detail {\n\n// Bit mixer based on the mulx primitive\n\n#if defined(_MSC_VER) && defined(_M_X64) && !defined(__clang__)\n\n__forceinline std::uint64_t mulx64( std::uint64_t x, std::uint64_t y )\n{\n    std::uint64_t r2;\n    std::uint64_t r = _umul128( x, y, &r2 );\n    return r ^ r2;\n}\n\n#elif defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__)\n\n__forceinline std::uint64_t mulx64( std::uint64_t x, std::uint64_t y )\n{\n    std::uint64_t r = x * y;\n    std::uint64_t r2 = __umulh( x, y );\n    return r ^ r2;\n}\n\n#elif defined(__SIZEOF_INT128__)\n\ninline std::uint64_t mulx64( std::uint64_t x, std::uint64_t y )\n{\n    __uint128_t r = (__uint128_t)x * y;\n    return (std::uint64_t)r ^ (std::uint64_t)( r >> 64 );\n}\n\n#else\n\ninline std::uint64_t mulx64( std::uint64_t x, std::uint64_t y )\n{\n    std::uint64_t x1 = (std::uint32_t)x;\n    std::uint64_t x2 = x >> 32;\n\n    std::uint64_t y1 = (std::uint32_t)y;\n    std::uint64_t y2 = y >> 32;\n\n    std::uint64_t r3 = x2 * y2;\n\n    std::uint64_t r2a = x1 * y2;\n\n    r3 += r2a >> 32;\n\n    std::uint64_t r2b = x2 * y1;\n\n    r3 += r2b >> 32;\n\n    std::uint64_t r1 = x1 * y1;\n\n    std::uint64_t r2 = (r1 >> 32) + (std::uint32_t)r2a + (std::uint32_t)r2b;\n\n    r1 = (r2 << 32) + (std::uint32_t)r1;\n    r3 += r2 >> 32;\n\n    return r1 ^ r3;\n}\n\n#endif\n\ninline std::uint32_t mulx32( std::uint32_t x, std::uint32_t y )\n{\n    std::uint64_t r = (std::uint64_t)x * y;\n\n#ifdef __MSVC_RUNTIME_CHECKS\n\n    return (std::uint32_t)(r & UINT32_MAX) ^ (std::uint32_t)(r >> 32);\n\n#else\n\n    return (std::uint32_t)r ^ (std::uint32_t)(r >> 32);\n\n#endif\n}\n\n#ifdef SIZE_MAX\n#if ((((SIZE_MAX >> 16) >> 16) >> 16) >> 15) != 0\n#define BOOST_UNORDERED_64B_ARCHITECTURE\n#endif\n#elif defined(UINTPTR_MAX) /* used as proxy for std::size_t */\n#if ((((UINTPTR_MAX >> 16) >> 16) >> 16) >> 15) != 0\n#define BOOST_UNORDERED_64B_ARCHITECTURE\n#endif\n#endif\n\ninline std::size_t mulx( std::size_t x ) noexcept\n{\n#ifdef BOOST_UNORDERED_64B_ARCHITECTURE\n\n    // multiplier is phi\n    return (std::size_t)mulx64( (std::uint64_t)x, 0x9E3779B97F4A7C15ull );\n\n#else /* 32 bits assumed */\n\n    // multiplier from https://arxiv.org/abs/2001.05304\n    return mulx32( x, 0xE817FB2Du );\n\n#endif\n}\n\n#ifdef BOOST_UNORDERED_64B_ARCHITECTURE\n#undef BOOST_UNORDERED_64B_ARCHITECTURE\n#endif\n\n} // namespace detail\n} // namespace unordered\n} // namespace boost\n\n#endif // #ifndef BOOST_UNORDERED_DETAIL_MULX_HPP\n// Copyright (C) 2022-2023 Christian Mazakas\n// Copyright (C) 2024 Braden Ganetsky\n//\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_DETAIL_TYPE_TRAITS_HPP\n#define BOOST_UNORDERED_DETAIL_TYPE_TRAITS_HPP\n\n#pragma once\n\n// BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES\n\n#ifndef BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES\n#ifndef BOOST_NO_CXX17_DEDUCTION_GUIDES\n#define BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES 1\n#endif\n#endif\n\n#ifndef BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES\n#define BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES 0\n#endif\n\nnamespace boost {\n  namespace unordered {\n    namespace detail {\n\n      template <class T> struct type_identity\n      {\n        using type = T;\n      };\n\n      template <typename... Ts> struct make_void\n      {\n        typedef void type;\n      };\n\n      template <typename... Ts> using void_t = typename make_void<Ts...>::type;\n\n      template <class T, class = void> struct is_complete : std::false_type\n      {\n      };\n\n      template <class T>\n      struct is_complete<T, void_t<int[sizeof(T)]> > : std::true_type\n      {\n      };\n\n      template <class T>\n      using is_complete_and_move_constructible =\n        typename std::conditional<is_complete<T>::value,\n          std::is_move_constructible<T>, std::false_type>::type;\n\n      using std::is_trivially_default_constructible;\n\n      using std::is_trivially_copy_constructible;\n\n      using std::is_trivially_copy_assignable;\n\n      namespace type_traits_detail {\n        using std::swap;\n\n        template <class T, class = void> struct is_nothrow_swappable_helper\n        {\n          constexpr static bool const value = false;\n        };\n\n        template <class T>\n        struct is_nothrow_swappable_helper<T,\n          void_t<decltype(swap(std::declval<T&>(), std::declval<T&>()))> >\n        {\n          constexpr static bool const value =\n            noexcept(swap(std::declval<T&>(), std::declval<T&>()));\n        };\n\n      } // namespace type_traits_detail\n\n      template <class T>\n      struct is_nothrow_swappable\n          : public std::integral_constant<bool,\n              type_traits_detail::is_nothrow_swappable_helper<T>::value>\n      {\n      };\n\n      ////////////////////////////////////////////////////////////////////////////\n      // Type checkers used for the transparent member functions added by C++20\n      // and up\n\n      template <class, class = void>\n      struct is_transparent : public std::false_type\n      {\n      };\n\n      template <class T>\n      struct is_transparent<T,\n        boost::unordered::detail::void_t<typename T::is_transparent> >\n          : public std::true_type\n      {\n      };\n\n      template <class, class Hash, class KeyEqual> struct are_transparent\n      {\n        static bool const value =\n          is_transparent<Hash>::value && is_transparent<KeyEqual>::value;\n      };\n\n      template <class Key, class UnorderedMap> struct transparent_non_iterable\n      {\n        typedef typename UnorderedMap::hasher hash;\n        typedef typename UnorderedMap::key_equal key_equal;\n        typedef typename UnorderedMap::iterator iterator;\n        typedef typename UnorderedMap::const_iterator const_iterator;\n\n        static bool const value =\n          are_transparent<Key, hash, key_equal>::value &&\n          !std::is_convertible<Key, iterator>::value &&\n          !std::is_convertible<Key, const_iterator>::value;\n      };\n\n      template <class T>\n      using remove_cvref_t =\n        typename std::remove_cv<typename std::remove_reference<T>::type>::type;\n\n      template <class T, class U>\n      using is_similar = std::is_same<remove_cvref_t<T>, remove_cvref_t<U> >;\n\n      template <class, class...> struct is_similar_to_any : std::false_type\n      {\n      };\n      template <class T, class U, class... Us>\n      struct is_similar_to_any<T, U, Us...>\n          : std::conditional<is_similar<T, U>::value, is_similar<T, U>,\n              is_similar_to_any<T, Us...> >::type\n      {\n      };\n\n#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES\n      // https://eel.is/c++draft/container.requirements#container.alloc.reqmts-34\n      // https://eel.is/c++draft/container.requirements#unord.req.general-243\n\n      template <class InputIterator>\n      constexpr bool const is_input_iterator_v =\n        !std::is_integral<InputIterator>::value;\n\n      template <class A, class = void> struct is_allocator\n      {\n        constexpr static bool const value = false;\n      };\n\n      template <class A>\n      struct is_allocator<A,\n        boost::unordered::detail::void_t<typename A::value_type,\n          decltype(std::declval<A&>().allocate(std::size_t{}))> >\n      {\n        constexpr static bool const value = true;\n      };\n\n      template <class A>\n      constexpr bool const is_allocator_v = is_allocator<A>::value;\n\n      template <class H>\n      constexpr bool const is_hash_v =\n        !std::is_integral<H>::value && !is_allocator_v<H>;\n\n      template <class P> constexpr bool const is_pred_v = !is_allocator_v<P>;\n\n      template <typename T>\n      using iter_key_t =\n        typename std::iterator_traits<T>::value_type::first_type;\n      template <typename T>\n      using iter_val_t =\n        typename std::iterator_traits<T>::value_type::second_type;\n      template <typename T>\n      using iter_to_alloc_t =\n        typename std::pair<iter_key_t<T> const, iter_val_t<T> >;\n#endif\n    } // namespace detail\n  } // namespace unordered\n} // namespace boost\n\n#endif // BOOST_UNORDERED_DETAIL_TYPE_TRAITS_HPP\n/* Hash function characterization.\n *\n * Copyright 2022 Joaquin M Lopez Munoz.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifndef BOOST_UNORDERED_HASH_TRAITS_HPP\n#define BOOST_UNORDERED_HASH_TRAITS_HPP\n\nnamespace boost{\nnamespace unordered{\n\nnamespace detail{\n\ntemplate<typename Hash,typename=void>\nstruct hash_is_avalanching_impl: std::false_type{};\n\ntemplate<typename Hash>\nstruct hash_is_avalanching_impl<Hash,\n  boost::unordered::detail::void_t<typename Hash::is_avalanching> >:\n    std::true_type{};\n\n}\n\n/* Each trait can be partially specialized by users for concrete hash functions\n * when actual characterization differs from default.\n */\n\n/* hash_is_avalanching<Hash>::value is true when the type Hash::is_avalanching\n * is present, false otherwise.\n */\ntemplate<typename Hash>\nstruct hash_is_avalanching: detail::hash_is_avalanching_impl<Hash>::type{};\n\n}\n}\n\n#endif\n/* Copyright 2023 Joaquin M Lopez Munoz.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifdef BOOST_GCC\n#ifndef BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW\n /* GCC's -Wshadow triggers at scenarios like this: \n *\n *   struct foo{};\n *   template<typename Base>\n *   struct derived:Base\n *   {\n *     void f(){int foo;}\n *   };\n * \n *   derived<foo>x;\n *   x.f(); // declaration of \"foo\" in derived::f shadows base type \"foo\"\n *\n * This makes shadowing warnings unavoidable in general when a class template\n * derives from user-provided classes, as is the case with foa::table_core\n * deriving from empty_value.\n */\n\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wshadow\"\n#else\n#pragma GCC diagnostic pop\n#endif\n#endif\n/* Copyright 2023 Joaquin M Lopez Munoz.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#define BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW\n#undef BOOST_UNORDERED_DETAIL_RESTORE_WSHADOW\n/* Common base for Boost.Unordered open-addressing tables.\n *\n * Copyright 2022-2024 Joaquin M Lopez Munoz.\n * Copyright 2023 Christian Mazakas.\n * Copyright 2024 Braden Ganetsky.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifndef BOOST_UNORDERED_DETAIL_FOA_CORE_HPP\n#define BOOST_UNORDERED_DETAIL_FOA_CORE_HPP\n\n#include <new>\n\n#ifndef BOOST_UNORDERED_DISABLE_SSE2\n#if defined(BOOST_UNORDERED_ENABLE_SSE2)|| \\\n    defined(__SSE2__)|| \\\n    defined(_M_X64)||(defined(_M_IX86_FP)&&_M_IX86_FP>=2)\n#define BOOST_UNORDERED_SSE2\n#endif\n#endif\n\n#ifndef BOOST_UNORDERED_DISABLE_NEON\n#if defined(BOOST_UNORDERED_ENABLE_NEON)||\\\n    (defined(__ARM_NEON)&&!defined(__ARM_BIG_ENDIAN))\n#define BOOST_UNORDERED_LITTLE_ENDIAN_NEON\n#endif\n#endif\n\n#ifdef BOOST_UNORDERED_SSE2\n#include <emmintrin.h>\n#elif defined(BOOST_UNORDERED_LITTLE_ENDIAN_NEON)\n#include <arm_neon.h>\n#endif\n\n#ifdef __has_builtin\n#define BOOST_UNORDERED_HAS_BUILTIN(x) __has_builtin(x)\n#else\n#define BOOST_UNORDERED_HAS_BUILTIN(x) 0\n#endif\n\n#ifndef NDEBUG\n#define BOOST_UNORDERED_ASSUME(cond) BOOST_ASSERT(cond)\n#elif BOOST_UNORDERED_HAS_BUILTIN(__builtin_assume)\n#define BOOST_UNORDERED_ASSUME(cond) __builtin_assume(cond)\n#elif defined(__GNUC__) || BOOST_UNORDERED_HAS_BUILTIN(__builtin_unreachable)\n#define BOOST_UNORDERED_ASSUME(cond)    \\\n  do{                                   \\\n    if(!(cond))__builtin_unreachable(); \\\n  }while(0)\n#elif defined(_MSC_VER)\n#define BOOST_UNORDERED_ASSUME(cond) __assume(cond)\n#else\n#define BOOST_UNORDERED_ASSUME(cond)  \\\n  do{                                 \\\n    static_cast<void>(false&&(cond)); \\\n  }while(0)\n#endif\n\n/* We use BOOST_UNORDERED_PREFETCH[_ELEMENTS] macros rather than proper\n * functions because of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109985\n */\n\n#if defined(BOOST_GCC)||defined(BOOST_CLANG)\n#define BOOST_UNORDERED_PREFETCH(p) __builtin_prefetch((const char*)(p))\n#elif defined(BOOST_UNORDERED_SSE2)\n#define BOOST_UNORDERED_PREFETCH(p) _mm_prefetch((const char*)(p),_MM_HINT_T0)\n#else\n#define BOOST_UNORDERED_PREFETCH(p) ((void)(p))\n#endif\n\n/* We have experimentally confirmed that ARM architectures get a higher\n * speedup when around the first half of the element slots in a group are\n * prefetched, whereas for Intel just the first cache line is best.\n * Please report back if you find better tunings for some particular\n * architectures.\n */\n\n// Lynx Add Begin\n// Always use __builtin_prefetch\n #if 0 // BOOST_ARCH_ARM\n/* Cache line size can't be known at compile time, so we settle on\n * the very frequent value of 64B.\n */\n\n#define BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N)                          \\\n  do{                                                                   \\\n    auto           BOOST_UNORDERED_P=(p);                               \\\n    constexpr int  cache_line=64;                                       \\\n    const char    *p0=reinterpret_cast<const char*>(BOOST_UNORDERED_P), \\\n                  *p1=p0+sizeof(*BOOST_UNORDERED_P)*(N)/2;              \\\n    for(;p0<p1;p0+=cache_line)BOOST_UNORDERED_PREFETCH(p0);             \\\n  }while(0)\n#else\n#define BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N) BOOST_UNORDERED_PREFETCH(p)\n#endif\n// Lynx Add End\n\n#ifdef __has_feature\n#define BOOST_UNORDERED_HAS_FEATURE(x) __has_feature(x)\n#else\n#define BOOST_UNORDERED_HAS_FEATURE(x) 0\n#endif\n\n#if BOOST_UNORDERED_HAS_FEATURE(thread_sanitizer)|| \\\n    defined(__SANITIZE_THREAD__)\n#define BOOST_UNORDERED_THREAD_SANITIZER\n#endif\n\n#define BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred)                    \\\n  static_assert(boost::unordered::detail::is_nothrow_swappable<Hash>::value,   \\\n    \"Template parameter Hash is required to be nothrow Swappable.\");           \\\n  static_assert(boost::unordered::detail::is_nothrow_swappable<Pred>::value,   \\\n    \"Template parameter Pred is required to be nothrow Swappable\");\n\nnamespace boost{\nnamespace unordered{\nnamespace detail{\nnamespace foa{\n\nstatic constexpr std::size_t default_bucket_count=0;\n\n/* foa::table_core is the common base of foa::table and foa::concurrent_table,\n * which in their turn serve as the foundational core of\n * boost::unordered_(flat|node)_(map|set) and boost::concurrent_flat_(map|set),\n * respectively. Its main internal design aspects are:\n * \n *   - Element slots are logically split into groups of size N=15. The number\n *     of groups is always a power of two, so the number of allocated slots\n       is of the form (N*2^n)-1 (final slot reserved for a sentinel mark).\n *   - Positioning is done at the group level rather than the slot level, that\n *     is, for any given element its hash value is used to locate a group and\n *     insertion is performed on the first available element of that group;\n *     if the group is full (overflow), further groups are tried using\n *     quadratic probing.\n *   - Each group has an associated 16B metadata word holding reduced hash\n *     values and overflow information. Reduced hash values are used to\n *     accelerate lookup within the group by using 128-bit SIMD or 64-bit word\n *     operations.\n */\n\n/* group15 controls metadata information of a group of N=15 element slots.\n * The 16B metadata word is organized as follows (LSB depicted rightmost):\n *\n *   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n *   |ofw|h14|h13|h13|h11|h10|h09|h08|h07|h06|h05|h04|h03|h02|h01|h00|\n *   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n *\n * hi is 0 if the i-th element slot is avalaible, 1 to mark a sentinel and,\n * when the slot is occupied, a value in the range [2,255] obtained from the\n * element's original hash value.\n * ofw is the so-called overflow byte. If insertion of an element with hash\n * value h is tried on a full group, then the (h%8)-th bit of the overflow\n * byte is set to 1 and a further group is probed. Having an overflow byte\n * brings two advantages:\n * \n *   - There's no need to reserve a special value of hi to mark tombstone\n *     slots; each reduced hash value keeps then log2(254)=7.99 bits of the\n *     original hash (alternative approaches reserve one full bit to mark\n *     if the slot is available/deleted, so their reduced hash values are 7 bit\n *     strong only).\n *   - When doing an unsuccessful lookup (i.e. the element is not present in\n *     the table), probing stops at the first non-overflowed group. Having 8\n *     bits for signalling overflow makes it very likely that we stop at the\n *     current group (this happens when no element with the same (h%8) value\n *     has overflowed in the group), saving us an additional group check even\n *     under high-load/high-erase conditions. It is critical that hash\n *     reduction is invariant under modulo 8 (see maybe_caused_overflow).\n *\n * When looking for an element with hash value h, match(h) returns a bitmask\n * signalling which slots have the same reduced hash value. If available,\n * match uses SSE2 or (little endian) Neon 128-bit SIMD operations. On non-SIMD\n * scenarios, the logical layout described above is physically mapped to two\n * 64-bit words with *bit interleaving*, i.e. the least significant 16 bits of\n * the first 64-bit word contain the least significant bits of each byte in the\n * \"logical\" 128-bit word, and so forth. With this layout, match can be\n * implemented with 4 ANDs, 3 shifts, 2 XORs, 1 OR and 1 NOT.\n * \n * IntegralWrapper<Integral> is used to implement group15's underlying\n * metadata: it behaves as a plain integral for foa::table or introduces\n * atomic ops for foa::concurrent_table. If IntegralWrapper<...> is trivially\n * constructible, so is group15, in which case it can be initialized via memset\n * etc. Where needed, group15::initialize resets the metadata to the all\n * zeros (default state).\n */\n\n#ifdef BOOST_UNORDERED_SSE2\n\ntemplate<template<typename> class IntegralWrapper>\nstruct group15\n{\n  static constexpr std::size_t N=15;\n  static constexpr bool        regular_layout=true;\n\n  struct dummy_group_type\n  {\n    alignas(16) unsigned char storage[N+1]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0};\n  };\n\n  inline void initialize()\n  {\n    _mm_store_si128(\n      reinterpret_cast<__m128i*>(m),_mm_setzero_si128());\n  }\n\n  inline void set(std::size_t pos,std::size_t hash)\n  {\n    BOOST_ASSERT(pos<N);\n    at(pos)=reduced_hash(hash);\n  }\n\n  inline void set_sentinel()\n  {\n    at(N-1)=sentinel_;\n  }\n\n  inline bool is_sentinel(std::size_t pos)const\n  {\n    BOOST_ASSERT(pos<N);\n    return at(pos)==sentinel_;\n  }\n\n  static inline bool is_sentinel(unsigned char* pc)noexcept\n  {\n    return *pc==sentinel_;\n  }\n\n  inline void reset(std::size_t pos)\n  {\n    BOOST_ASSERT(pos<N);\n    at(pos)=available_;\n  }\n\n  static inline void reset(unsigned char* pc)\n  {\n    *reinterpret_cast<slot_type*>(pc)=available_;\n  }\n\n  inline int match(std::size_t hash)const\n  {\n    return _mm_movemask_epi8(\n      _mm_cmpeq_epi8(load_metadata(),_mm_set1_epi32(match_word(hash))))&0x7FFF;\n  }\n\n  inline bool is_not_overflowed(std::size_t hash)const\n  {\n    static constexpr unsigned char shift[]={1,2,4,8,16,32,64,128};\n\n    return !(overflow()&shift[hash%8]);\n  }\n\n  inline void mark_overflow(std::size_t hash)\n  {\n    overflow()|=static_cast<unsigned char>(1<<(hash%8));\n  }\n\n  static inline bool maybe_caused_overflow(unsigned char* pc)\n  {\n    std::size_t pos=reinterpret_cast<uintptr_t>(pc)%sizeof(group15);\n    group15    *pg=reinterpret_cast<group15*>(pc-pos);\n    return !pg->is_not_overflowed(*pc);\n  }\n\n  inline int match_available()const\n  {\n    return _mm_movemask_epi8(\n      _mm_cmpeq_epi8(load_metadata(),_mm_setzero_si128()))&0x7FFF;\n  }\n\n  inline bool is_occupied(std::size_t pos)const\n  {\n    BOOST_ASSERT(pos<N);\n    return at(pos)!=available_;\n  }\n\n  static inline bool is_occupied(unsigned char* pc)noexcept\n  {\n    return *reinterpret_cast<slot_type*>(pc)!=available_;\n  }\n\n  inline int match_occupied()const\n  {\n    return (~match_available())&0x7FFF;\n  }\n\nprivate:\n  using slot_type=IntegralWrapper<unsigned char>;\n  BOOST_UNORDERED_STATIC_ASSERT(sizeof(slot_type)==1);\n\n  static constexpr unsigned char available_=0,\n                                 sentinel_=1;\n\n  inline __m128i load_metadata()const\n  {\n#ifdef BOOST_UNORDERED_THREAD_SANITIZER\n    /* ThreadSanitizer complains on 1-byte atomic writes combined with\n     * 16-byte atomic reads.\n     */\n\n    return _mm_set_epi8(\n      (char)m[15],(char)m[14],(char)m[13],(char)m[12],\n      (char)m[11],(char)m[10],(char)m[ 9],(char)m[ 8],\n      (char)m[ 7],(char)m[ 6],(char)m[ 5],(char)m[ 4],\n      (char)m[ 3],(char)m[ 2],(char)m[ 1],(char)m[ 0]);\n#else\n    return _mm_load_si128(reinterpret_cast<const __m128i*>(m));\n#endif\n  }\n\n  inline static int match_word(std::size_t hash)\n  {\n    static constexpr std::uint32_t word[]=\n    {\n      0x08080808u,0x09090909u,0x02020202u,0x03030303u,0x04040404u,0x05050505u,\n      0x06060606u,0x07070707u,0x08080808u,0x09090909u,0x0A0A0A0Au,0x0B0B0B0Bu,\n      0x0C0C0C0Cu,0x0D0D0D0Du,0x0E0E0E0Eu,0x0F0F0F0Fu,0x10101010u,0x11111111u,\n      0x12121212u,0x13131313u,0x14141414u,0x15151515u,0x16161616u,0x17171717u,\n      0x18181818u,0x19191919u,0x1A1A1A1Au,0x1B1B1B1Bu,0x1C1C1C1Cu,0x1D1D1D1Du,\n      0x1E1E1E1Eu,0x1F1F1F1Fu,0x20202020u,0x21212121u,0x22222222u,0x23232323u,\n      0x24242424u,0x25252525u,0x26262626u,0x27272727u,0x28282828u,0x29292929u,\n      0x2A2A2A2Au,0x2B2B2B2Bu,0x2C2C2C2Cu,0x2D2D2D2Du,0x2E2E2E2Eu,0x2F2F2F2Fu,\n      0x30303030u,0x31313131u,0x32323232u,0x33333333u,0x34343434u,0x35353535u,\n      0x36363636u,0x37373737u,0x38383838u,0x39393939u,0x3A3A3A3Au,0x3B3B3B3Bu,\n      0x3C3C3C3Cu,0x3D3D3D3Du,0x3E3E3E3Eu,0x3F3F3F3Fu,0x40404040u,0x41414141u,\n      0x42424242u,0x43434343u,0x44444444u,0x45454545u,0x46464646u,0x47474747u,\n      0x48484848u,0x49494949u,0x4A4A4A4Au,0x4B4B4B4Bu,0x4C4C4C4Cu,0x4D4D4D4Du,\n      0x4E4E4E4Eu,0x4F4F4F4Fu,0x50505050u,0x51515151u,0x52525252u,0x53535353u,\n      0x54545454u,0x55555555u,0x56565656u,0x57575757u,0x58585858u,0x59595959u,\n      0x5A5A5A5Au,0x5B5B5B5Bu,0x5C5C5C5Cu,0x5D5D5D5Du,0x5E5E5E5Eu,0x5F5F5F5Fu,\n      0x60606060u,0x61616161u,0x62626262u,0x63636363u,0x64646464u,0x65656565u,\n      0x66666666u,0x67676767u,0x68686868u,0x69696969u,0x6A6A6A6Au,0x6B6B6B6Bu,\n      0x6C6C6C6Cu,0x6D6D6D6Du,0x6E6E6E6Eu,0x6F6F6F6Fu,0x70707070u,0x71717171u,\n      0x72727272u,0x73737373u,0x74747474u,0x75757575u,0x76767676u,0x77777777u,\n      0x78787878u,0x79797979u,0x7A7A7A7Au,0x7B7B7B7Bu,0x7C7C7C7Cu,0x7D7D7D7Du,\n      0x7E7E7E7Eu,0x7F7F7F7Fu,0x80808080u,0x81818181u,0x82828282u,0x83838383u,\n      0x84848484u,0x85858585u,0x86868686u,0x87878787u,0x88888888u,0x89898989u,\n      0x8A8A8A8Au,0x8B8B8B8Bu,0x8C8C8C8Cu,0x8D8D8D8Du,0x8E8E8E8Eu,0x8F8F8F8Fu,\n      0x90909090u,0x91919191u,0x92929292u,0x93939393u,0x94949494u,0x95959595u,\n      0x96969696u,0x97979797u,0x98989898u,0x99999999u,0x9A9A9A9Au,0x9B9B9B9Bu,\n      0x9C9C9C9Cu,0x9D9D9D9Du,0x9E9E9E9Eu,0x9F9F9F9Fu,0xA0A0A0A0u,0xA1A1A1A1u,\n      0xA2A2A2A2u,0xA3A3A3A3u,0xA4A4A4A4u,0xA5A5A5A5u,0xA6A6A6A6u,0xA7A7A7A7u,\n      0xA8A8A8A8u,0xA9A9A9A9u,0xAAAAAAAAu,0xABABABABu,0xACACACACu,0xADADADADu,\n      0xAEAEAEAEu,0xAFAFAFAFu,0xB0B0B0B0u,0xB1B1B1B1u,0xB2B2B2B2u,0xB3B3B3B3u,\n      0xB4B4B4B4u,0xB5B5B5B5u,0xB6B6B6B6u,0xB7B7B7B7u,0xB8B8B8B8u,0xB9B9B9B9u,\n      0xBABABABAu,0xBBBBBBBBu,0xBCBCBCBCu,0xBDBDBDBDu,0xBEBEBEBEu,0xBFBFBFBFu,\n      0xC0C0C0C0u,0xC1C1C1C1u,0xC2C2C2C2u,0xC3C3C3C3u,0xC4C4C4C4u,0xC5C5C5C5u,\n      0xC6C6C6C6u,0xC7C7C7C7u,0xC8C8C8C8u,0xC9C9C9C9u,0xCACACACAu,0xCBCBCBCBu,\n      0xCCCCCCCCu,0xCDCDCDCDu,0xCECECECEu,0xCFCFCFCFu,0xD0D0D0D0u,0xD1D1D1D1u,\n      0xD2D2D2D2u,0xD3D3D3D3u,0xD4D4D4D4u,0xD5D5D5D5u,0xD6D6D6D6u,0xD7D7D7D7u,\n      0xD8D8D8D8u,0xD9D9D9D9u,0xDADADADAu,0xDBDBDBDBu,0xDCDCDCDCu,0xDDDDDDDDu,\n      0xDEDEDEDEu,0xDFDFDFDFu,0xE0E0E0E0u,0xE1E1E1E1u,0xE2E2E2E2u,0xE3E3E3E3u,\n      0xE4E4E4E4u,0xE5E5E5E5u,0xE6E6E6E6u,0xE7E7E7E7u,0xE8E8E8E8u,0xE9E9E9E9u,\n      0xEAEAEAEAu,0xEBEBEBEBu,0xECECECECu,0xEDEDEDEDu,0xEEEEEEEEu,0xEFEFEFEFu,\n      0xF0F0F0F0u,0xF1F1F1F1u,0xF2F2F2F2u,0xF3F3F3F3u,0xF4F4F4F4u,0xF5F5F5F5u,\n      0xF6F6F6F6u,0xF7F7F7F7u,0xF8F8F8F8u,0xF9F9F9F9u,0xFAFAFAFAu,0xFBFBFBFBu,\n      0xFCFCFCFCu,0xFDFDFDFDu,0xFEFEFEFEu,0xFFFFFFFFu,\n    };\n\n    return (int)word[narrow_cast<unsigned char>(hash)];\n  }\n\n  inline static unsigned char reduced_hash(std::size_t hash)\n  {\n    return narrow_cast<unsigned char>(match_word(hash));\n  }\n\n  inline slot_type& at(std::size_t pos)\n  {\n    return m[pos];\n  }\n\n  inline const slot_type& at(std::size_t pos)const\n  {\n    return m[pos];\n  }\n\n  inline slot_type& overflow()\n  {\n    return at(N);\n  }\n\n  inline const slot_type& overflow()const\n  {\n    return at(N);\n  }\n\n  alignas(16) slot_type m[16];\n};\n\n#elif defined(BOOST_UNORDERED_LITTLE_ENDIAN_NEON)\n\ntemplate<template<typename> class IntegralWrapper>\nstruct group15\n{\n  static constexpr std::size_t N=15;\n  static constexpr bool        regular_layout=true;\n\n  struct dummy_group_type\n  {\n    alignas(16) unsigned char storage[N+1]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0};\n  };\n\n  inline void initialize()\n  {\n    vst1q_u8(reinterpret_cast<uint8_t*>(m),vdupq_n_u8(0));\n  }\n\n  inline void set(std::size_t pos,std::size_t hash)\n  {\n    BOOST_ASSERT(pos<N);\n    at(pos)=reduced_hash(hash);\n  }\n\n  inline void set_sentinel()\n  {\n    at(N-1)=sentinel_;\n  }\n\n  inline bool is_sentinel(std::size_t pos)const\n  {\n    BOOST_ASSERT(pos<N);\n    return pos==N-1&&at(N-1)==sentinel_;\n  }\n\n  static inline bool is_sentinel(unsigned char* pc)noexcept\n  {\n    return *reinterpret_cast<slot_type*>(pc)==sentinel_;\n  }\n\n  inline void reset(std::size_t pos)\n  {\n    BOOST_ASSERT(pos<N);\n    at(pos)=available_;\n  }\n\n  static inline void reset(unsigned char* pc)\n  {\n    *reinterpret_cast<slot_type*>(pc)=available_;\n  }\n\n  inline int match(std::size_t hash)const\n  {\n    return simde_mm_movemask_epi8(vceqq_u8(\n      load_metadata(),vdupq_n_u8(reduced_hash(hash))))&0x7FFF;\n  }\n\n  inline bool is_not_overflowed(std::size_t hash)const\n  {\n    static constexpr unsigned char shift[]={1,2,4,8,16,32,64,128};\n\n    return !(overflow()&shift[hash%8]);\n  }\n\n  inline void mark_overflow(std::size_t hash)\n  {\n    overflow()|=static_cast<unsigned char>(1<<(hash%8));\n  }\n\n  static inline bool maybe_caused_overflow(unsigned char* pc)\n  {\n    std::size_t pos=reinterpret_cast<uintptr_t>(pc)%sizeof(group15);\n    group15    *pg=reinterpret_cast<group15*>(pc-pos);\n    return !pg->is_not_overflowed(*pc);\n  };\n\n  inline int match_available()const\n  {\n    return simde_mm_movemask_epi8(vceqq_u8(\n      load_metadata(),vdupq_n_u8(0)))&0x7FFF;\n  }\n\n  inline bool is_occupied(std::size_t pos)const\n  {\n    BOOST_ASSERT(pos<N);\n    return at(pos)!=available_;\n  }\n\n  static inline bool is_occupied(unsigned char* pc)noexcept\n  {\n    return *reinterpret_cast<slot_type*>(pc)!=available_;\n  }\n\n  inline int match_occupied()const\n  {\n    return simde_mm_movemask_epi8(vcgtq_u8(\n      load_metadata(),vdupq_n_u8(0)))&0x7FFF;\n  }\n\nprivate:\n  using slot_type=IntegralWrapper<unsigned char>;\n  BOOST_UNORDERED_STATIC_ASSERT(sizeof(slot_type)==1);\n\n  static constexpr unsigned char available_=0,\n                                 sentinel_=1;\n\n  inline uint8x16_t load_metadata()const\n  {\n#ifdef BOOST_UNORDERED_THREAD_SANITIZER\n    /* ThreadSanitizer complains on 1-byte atomic writes combined with\n     * 16-byte atomic reads.\n     */\n\n    alignas(16) uint8_t data[16]={\n      m[ 0],m[ 1],m[ 2],m[ 3],m[ 4],m[ 5],m[ 6],m[ 7],\n      m[ 8],m[ 9],m[10],m[11],m[12],m[13],m[14],m[15]};\n    return vld1q_u8(data);\n#else\n    return vld1q_u8(reinterpret_cast<const uint8_t*>(m));\n#endif\n  }\n\n  inline static unsigned char reduced_hash(std::size_t hash)\n  {\n    static constexpr unsigned char table[]={\n      8,9,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\n      16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,\n      32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,\n      48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,\n      64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,\n      80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,\n      96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,\n      112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,\n      128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,\n      144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,\n      160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,\n      176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,\n      192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,\n      208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,\n      224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,\n      240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,\n    };\n    \n    return table[(unsigned char)hash];\n  }\n\n  /* Copied from \n   * https://github.com/simd-everywhere/simde/blob/master/simde/x86/\n   * sse2.h#L3763\n   */\n\n  static inline int simde_mm_movemask_epi8(uint8x16_t a)\n  {\n    static constexpr uint8_t md[16]={\n      1 << 0, 1 << 1, 1 << 2, 1 << 3,\n      1 << 4, 1 << 5, 1 << 6, 1 << 7,\n      1 << 0, 1 << 1, 1 << 2, 1 << 3,\n      1 << 4, 1 << 5, 1 << 6, 1 << 7,\n    };\n\n    uint8x16_t  masked=vandq_u8(vld1q_u8(md),a);\n    uint8x8x2_t tmp=vzip_u8(vget_low_u8(masked),vget_high_u8(masked));\n    uint16x8_t  x=vreinterpretq_u16_u8(vcombine_u8(tmp.val[0],tmp.val[1]));\n\n#ifdef __ARM_ARCH_ISA_A64\n    return vaddvq_u16(x);\n#else\n    uint64x2_t t64=vpaddlq_u32(vpaddlq_u16(x));\n    return int(vgetq_lane_u64(t64,0))+int(vgetq_lane_u64(t64,1));\n#endif\n  }\n\n  inline slot_type& at(std::size_t pos)\n  {\n    return m[pos];\n  }\n\n  inline const slot_type& at(std::size_t pos)const\n  {\n    return m[pos];\n  }\n\n  inline slot_type& overflow()\n  {\n    return at(N);\n  }\n\n  inline const slot_type& overflow()const\n  {\n    return at(N);\n  }\n\n  alignas(16) slot_type m[16];\n};\n\n#else /* non-SIMD */\n\ntemplate<template<typename> class IntegralWrapper>\nstruct group15\n{\n  static constexpr std::size_t N=15;\n  static constexpr bool        regular_layout=false;\n\n  struct dummy_group_type\n  {\n    alignas(16) std::uint64_t m[2]=\n      {0x0000000000004000ull,0x0000000000000000ull};\n  };\n\n  inline void initialize(){m[0]=0;m[1]=0;}\n\n  inline void set(std::size_t pos,std::size_t hash)\n  {\n    BOOST_ASSERT(pos<N);\n    set_impl(pos,reduced_hash(hash));\n  }\n\n  inline void set_sentinel()\n  {\n    set_impl(N-1,sentinel_);\n  }\n\n  inline bool is_sentinel(std::size_t pos)const\n  {\n    BOOST_ASSERT(pos<N);\n    return \n      pos==N-1&&\n      (m[0] & std::uint64_t(0x4000400040004000ull))==\n        std::uint64_t(0x4000ull)&&\n      (m[1] & std::uint64_t(0x4000400040004000ull))==0;\n  }\n\n  inline void reset(std::size_t pos)\n  {\n    BOOST_ASSERT(pos<N);\n    set_impl(pos,available_);\n  }\n\n  static inline void reset(unsigned char* pc)\n  {\n    std::size_t pos=reinterpret_cast<uintptr_t>(pc)%sizeof(group15);\n    pc-=pos;\n    reinterpret_cast<group15*>(pc)->reset(pos);\n  }\n\n  inline int match(std::size_t hash)const\n  {\n    return match_impl(reduced_hash(hash));\n  }\n\n  inline bool is_not_overflowed(std::size_t hash)const\n  {\n    return !(reinterpret_cast<const boost::uint16_t*>(m)[hash%8] & 0x8000u);\n  }\n\n  inline void mark_overflow(std::size_t hash)\n  {\n    reinterpret_cast<boost::uint16_t*>(m)[hash%8]|=0x8000u;\n  }\n\n  static inline bool maybe_caused_overflow(unsigned char* pc)\n  {\n    std::size_t     pos=reinterpret_cast<uintptr_t>(pc)%sizeof(group15);\n    group15        *pg=reinterpret_cast<group15*>(pc-pos);\n    std::uint64_t x=((pg->m[0])>>pos)&0x000100010001ull;\n    std::uint32_t y=narrow_cast<std::uint32_t>(x|(x>>15)|(x>>30));\n    return !pg->is_not_overflowed(y);\n  };\n\n  inline int match_available()const\n  {\n    std::uint64_t x=~(m[0]|m[1]);\n    std::uint32_t y=static_cast<std::uint32_t>(x&(x>>32));\n    y&=y>>16;\n    return y&0x7FFF;\n  }\n\n  inline bool is_occupied(std::size_t pos)const\n  {\n    BOOST_ASSERT(pos<N);\n    std::uint64_t x=m[0]|m[1];\n    return (x&(0x0001000100010001ull<<pos))!=0;\n  }\n\n  inline int match_occupied()const\n  {\n    std::uint64_t x=m[0]|m[1];\n    std::uint32_t y=narrow_cast<std::uint32_t>(x|(x>>32));\n    y|=y>>16;\n    return y&0x7FFF;\n  }\n\nprivate:\n  using word_type=IntegralWrapper<uint64_t>;\n  BOOST_UNORDERED_STATIC_ASSERT(sizeof(word_type)==8);\n\n  static constexpr unsigned char available_=0,\n                                 sentinel_=1;\n\n  inline static unsigned char reduced_hash(std::size_t hash)\n  {\n    static constexpr unsigned char table[]={\n      8,9,2,3,4,5,6,7,8,9,10,11,12,13,14,15,\n      16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,\n      32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,\n      48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,\n      64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,\n      80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,\n      96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,\n      112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,\n      128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,\n      144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,\n      160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,\n      176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,\n      192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,\n      208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,\n      224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,\n      240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,\n    };\n    \n    return table[narrow_cast<unsigned char>(hash)];\n  }\n\n  inline void set_impl(std::size_t pos,std::size_t n)\n  {\n    BOOST_ASSERT(n<256);\n    set_impl(m[0],pos,n&0xFu);\n    set_impl(m[1],pos,n>>4);\n  }\n\n  static inline void set_impl(word_type& x,std::size_t pos,std::size_t n)\n  {\n    static constexpr std::uint64_t mask[]=\n    {\n      0x0000000000000000ull,0x0000000000000001ull,0x0000000000010000ull,\n      0x0000000000010001ull,0x0000000100000000ull,0x0000000100000001ull,\n      0x0000000100010000ull,0x0000000100010001ull,0x0001000000000000ull,\n      0x0001000000000001ull,0x0001000000010000ull,0x0001000000010001ull,\n      0x0001000100000000ull,0x0001000100000001ull,0x0001000100010000ull,\n      0x0001000100010001ull,\n    };\n    static constexpr std::uint64_t imask[]=\n    {\n      0x0001000100010001ull,0x0001000100010000ull,0x0001000100000001ull,\n      0x0001000100000000ull,0x0001000000010001ull,0x0001000000010000ull,\n      0x0001000000000001ull,0x0001000000000000ull,0x0000000100010001ull,\n      0x0000000100010000ull,0x0000000100000001ull,0x0000000100000000ull,\n      0x0000000000010001ull,0x0000000000010000ull,0x0000000000000001ull,\n      0x0000000000000000ull,\n    };\n\n    BOOST_ASSERT(pos<16&&n<16);\n    x|=   mask[n]<<pos;\n    x&=~(imask[n]<<pos);\n  }\n\n  inline int match_impl(std::size_t n)const\n  {\n    static constexpr std::uint64_t mask[]=\n    {\n      0x0000000000000000ull,0x000000000000ffffull,0x00000000ffff0000ull,\n      0x00000000ffffffffull,0x0000ffff00000000ull,0x0000ffff0000ffffull,\n      0x0000ffffffff0000ull,0x0000ffffffffffffull,0xffff000000000000ull,\n      0xffff00000000ffffull,0xffff0000ffff0000ull,0xffff0000ffffffffull,\n      0xffffffff00000000ull,0xffffffff0000ffffull,0xffffffffffff0000ull,\n      0xffffffffffffffffull,\n    };\n\n    BOOST_ASSERT(n<256);\n    std::uint64_t x=m[0]^mask[n&0xFu];\n                    x=~((m[1]^mask[n>>4])|x);\n    std::uint32_t y=static_cast<std::uint32_t>(x&(x>>32));\n                    y&=y>>16;\n    return          y&0x7FFF;\n  }\n\n  alignas(16) word_type m[2];\n};\n\n#endif\n\n/* foa::table_core uses a size policy to obtain the permissible sizes of the\n * group array (and, by implication, the element array) and to do the\n * hash->group mapping.\n * \n *   - size_index(n) returns an unspecified \"index\" number used in other policy\n *     operations.\n *   - size(size_index_) returns the number of groups for the given index. It\n *     is guaranteed that size(size_index(n)) >= n.\n *   - min_size() is the minimum number of groups permissible, i.e.\n *     size(size_index(0)).\n *   - position(hash,size_index_) maps hash to a position in the range\n *     [0,size(size_index_)).\n * \n * The reason we're introducing the intermediate index value for calculating\n * sizes and positions is that it allows us to optimize the implementation of\n * position, which is in the hot path of lookup and insertion operations:\n * pow2_size_policy, the actual size policy used by foa::table, returns 2^n\n * (n>0) as permissible sizes and returns the n most significant bits\n * of the hash value as the position in the group array; using a size index\n * defined as i = (bits in std::size_t) - n, we have an unbeatable\n * implementation of position(hash) as hash>>i.\n * There's a twofold reason for choosing the high bits of hash for positioning:\n *   - Multiplication-based mixing tends to yield better entropy in the high\n *     part of its result.\n *   - group15 reduced-hash values take the *low* bits of hash, and we want\n *     these values and positioning to be as uncorrelated as possible.\n */\n\nstruct pow2_size_policy\n{\n  static inline std::size_t size_index(std::size_t n)\n  {\n    // TODO: min size is 2, see if we can bring it down to 1 without loss\n    // of performance\n\n    return sizeof(std::size_t)*CHAR_BIT-\n      (n<=2?1:((std::size_t)(std::bit_width(n-1))));\n  }\n\n  static inline std::size_t size(std::size_t size_index_)\n  {\n     return std::size_t(1)<<(sizeof(std::size_t)*CHAR_BIT-size_index_);  \n  }\n    \n  static constexpr std::size_t min_size(){return 2;}\n\n  static inline std::size_t position(std::size_t hash,std::size_t size_index_)\n  {\n    return hash>>size_index_;\n  }\n};\n\n/* size index of a group array for a given *element* capacity */\n\ntemplate<typename Group,typename SizePolicy>\nstatic inline std::size_t size_index_for(std::size_t n)\n{\n  /* n/N+1 == ceil((n+1)/N) (extra +1 for the sentinel) */\n  return SizePolicy::size_index(n/Group::N+1);\n}\n\n/* Quadratic prober over a power-of-two range using triangular numbers.\n * mask in next(mask) must be the range size minus one (and since size is 2^n,\n * mask has exactly its n first bits set to 1).\n */\n\nstruct pow2_quadratic_prober\n{\n  pow2_quadratic_prober(std::size_t pos_):pos{pos_}{}\n\n  inline std::size_t get()const{return pos;}\n\n  /* next returns false when the whole array has been traversed, which ends\n   * probing (in practice, full-table probing will only happen with very small\n   * arrays).\n   */\n\n  inline bool next(std::size_t mask)\n  {\n    step+=1;\n    pos=(pos+step)&mask;\n    return step<=mask;\n  }\n\nprivate:\n  std::size_t pos,step=0;\n};\n\n/* Mixing policies: no_mix is the identity function, and mulx_mix\n * uses the mulx function from <boost/unordered/detail/mulx.hpp>.\n *\n * foa::table_core mixes hash results with mulx_mix unless the hash is marked\n * as avalanching, i.e. of good quality\n * (see <boost/unordered/hash_traits.hpp>).\n */\n\nstruct no_mix\n{\n  template<typename Hash,typename T>\n  static inline std::size_t mix(const Hash& h,const T& x)\n  {\n    return h(x);\n  }\n};\n\nstruct mulx_mix\n{\n  template<typename Hash,typename T>\n  static inline std::size_t mix(const Hash& h,const T& x)\n  {\n    return mulx(h(x));\n  }\n};\n\n/* std::countr_zero has a potentially costly check for\n * the case x==0.\n */\n\ninline unsigned int unchecked_countr_zero(int x)\n{\n#ifdef BOOST_MSVC\n  unsigned long r;\n  _BitScanForward(&r,(unsigned long)x);\n  return (unsigned int)r;\n#else\n  BOOST_UNORDERED_ASSUME(x!=0);\n  return (unsigned int)std::countr_zero((unsigned int)x);\n#endif\n}\n\n/* table_arrays controls allocation, initialization and deallocation of\n * paired arrays of groups and element slots. Only one chunk of memory is\n * allocated to place both arrays: this is not done for efficiency reasons,\n * but in order to be able to properly align the group array without storing\n * additional offset information --the alignment required (16B) is usually\n * greater than alignof(std::max_align_t) and thus not guaranteed by\n * allocators.\n */\n\ntemplate<typename Group,std::size_t Size>\nGroup* dummy_groups()\n{\n  /* Dummy storage initialized as if in an empty container (actually, each\n   * of its groups is initialized like a separate empty container).\n   * We make table_arrays::groups point to this when capacity()==0, so that\n   * we are not allocating any dynamic memory and yet lookup can be implemented\n   * without checking for groups==nullptr. This space won't ever be used for\n   * insertion as the container's capacity is precisely zero.\n   */\n\n  static constexpr typename Group::dummy_group_type\n  storage[Size]={typename Group::dummy_group_type(),};\n\n  return reinterpret_cast<Group*>(\n    const_cast<typename Group::dummy_group_type*>(storage));\n}\n\ntemplate<\n  typename Ptr,typename Ptr2,\n  typename std::enable_if<!std::is_same<Ptr,Ptr2>::value>::type* = nullptr\n>\nPtr to_pointer(Ptr2 p)\n{\n  if(!p){return nullptr;}\n  return std::pointer_traits<Ptr>::pointer_to(*p);\n}\n\ntemplate<typename Ptr>\nPtr to_pointer(Ptr p)\n{\n  return p;\n}\n\ntemplate<typename Arrays,typename Allocator>\nstruct arrays_holder\n{\n  arrays_holder(const Arrays& arrays,const Allocator& al):\n    arrays_{arrays},al_{al}\n  {}\n  \n  /* not defined but VS in pre-C++17 mode needs to see it for RVO */\n  arrays_holder(arrays_holder const&);\n  arrays_holder& operator=(arrays_holder const&)=delete;\n\n  ~arrays_holder()\n  {\n    if(!released_){\n      arrays_.delete_(typename Arrays::allocator_type(al_),arrays_);\n    }\n  }\n\n  const Arrays& release()\n  {\n    released_=true;\n    return arrays_;\n  }\n\nprivate:\n  Arrays    arrays_;\n  Allocator al_;\n  bool      released_=false;\n};\n\ntemplate<typename Value,typename Group,typename SizePolicy,typename Allocator>\nstruct table_arrays\n{\n  using allocator_type=typename std::allocator_traits<Allocator>::template rebind_alloc<Value>;\n\n  using value_type=Value;\n  using group_type=Group;\n  static constexpr auto N=group_type::N;\n  using size_policy=SizePolicy;\n  using value_type_pointer=\n    typename std::allocator_traits<allocator_type>::pointer;\n  using group_type_pointer=\n    typename std::pointer_traits<value_type_pointer>::template\n      rebind<group_type>;\n  using group_type_pointer_traits=std::pointer_traits<group_type_pointer>;\n\n  table_arrays(\n    std::size_t gsi,std::size_t gsm,\n    group_type_pointer pg,value_type_pointer pe):\n    groups_size_index{gsi},groups_size_mask{gsm},groups_{pg},elements_{pe}{}\n\n  value_type* elements()const noexcept{return std::to_address(elements_);}\n  group_type* groups()const noexcept{return std::to_address(groups_);}\n\n  static void set_arrays(table_arrays& arrays,allocator_type al,std::size_t n)\n  {\n    return set_arrays(\n      arrays,al,n,std::is_same<group_type*,group_type_pointer>{});\n  }\n\n  static void set_arrays(\n    table_arrays& arrays,allocator_type al,std::size_t,\n    std::false_type /* always allocate */)\n  {\n    using storage_traits=std::allocator_traits<allocator_type>;\n    auto groups_size_index=arrays.groups_size_index;\n    auto groups_size=size_policy::size(groups_size_index);\n\n    auto sal=allocator_type(al);\n    arrays.elements_=storage_traits::allocate(sal,buffer_size(groups_size));\n    \n    /* Align arrays.groups to sizeof(group_type). table_iterator critically\n      * depends on such alignment for its increment operation.\n      */\n\n    auto p=reinterpret_cast<unsigned char*>(arrays.elements()+groups_size*N-1);\n    p+=(uintptr_t(sizeof(group_type))-\n        reinterpret_cast<uintptr_t>(p))%sizeof(group_type);\n    arrays.groups_=\n      group_type_pointer_traits::pointer_to(*reinterpret_cast<group_type*>(p));\n\n    initialize_groups(\n      arrays.groups(),groups_size,\n      is_trivially_default_constructible<group_type>{});\n    arrays.groups()[groups_size-1].set_sentinel();\n  }\n\n  static void set_arrays(\n    table_arrays& arrays,allocator_type al,std::size_t n,\n    std::true_type /* optimize for n==0*/)\n  {\n    if(!n){\n      arrays.groups_=dummy_groups<group_type,size_policy::min_size()>();\n    }\n    else{\n      set_arrays(arrays,al,n,std::false_type{});\n    }\n  }\n\n  static table_arrays new_(allocator_type al,std::size_t n)\n  {\n    auto         groups_size_index=size_index_for<group_type,size_policy>(n);\n    auto         groups_size=size_policy::size(groups_size_index);\n    table_arrays arrays{groups_size_index,groups_size-1,nullptr,nullptr};\n\n    set_arrays(arrays,al,n);\n    return arrays;\n  }\n\n  static void delete_(allocator_type al,table_arrays& arrays)noexcept\n  {\n    using storage_traits=std::allocator_traits<allocator_type>;\n\n    auto sal=allocator_type(al);\n    if(arrays.elements()){\n      storage_traits::deallocate(\n        sal,arrays.elements_,buffer_size(arrays.groups_size_mask+1));\n    }\n  }\n\n  /* combined space for elements and groups measured in sizeof(value_type)s */\n\n  static std::size_t buffer_size(std::size_t groups_size)\n  {\n    auto buffer_bytes=\n      /* space for elements (we subtract 1 because of the sentinel) */\n      sizeof(value_type)*(groups_size*N-1)+\n      /* space for groups + padding for group alignment */\n      sizeof(group_type)*(groups_size+1)-1;\n\n    /* ceil(buffer_bytes/sizeof(value_type)) */\n    return (buffer_bytes+sizeof(value_type)-1)/sizeof(value_type);\n  }\n\n  static void initialize_groups(\n    group_type* pg,std::size_t size,std::true_type /* memset */)\n  {\n    /* memset faster/not slower than manual, assumes all zeros is group_type's\n     * default layout.\n     * reinterpret_cast: GCC may complain about group_type not being trivially\n     * copy-assignable when we're relying on trivial copy constructibility.\n     */\n\n    std::memset(\n      reinterpret_cast<unsigned char*>(pg),0,sizeof(group_type)*size);\n  }\n\n  static void initialize_groups(\n    group_type* pg,std::size_t size,std::false_type /* manual */)\n  {\n    while(size--!=0)::new (pg++) group_type();\n  }\n\n  std::size_t        groups_size_index;\n  std::size_t        groups_size_mask;\n  group_type_pointer groups_;\n  value_type_pointer elements_;\n};\n\nstruct if_constexpr_void_else{void operator()()const{}};\n\ntemplate<bool B,typename F,typename G=if_constexpr_void_else>\nvoid if_constexpr(F f,G g={})\n{\n  std::get<B?0:1>(std::forward_as_tuple(f,g))();\n}\n\ntemplate<bool B,typename T,typename std::enable_if<B>::type* =nullptr>\nvoid copy_assign_if(T& x,const T& y){x=y;}\n\ntemplate<bool B,typename T,typename std::enable_if<!B>::type* =nullptr>\nvoid copy_assign_if(T&,const T&){}\n\ntemplate<bool B,typename T,typename std::enable_if<B>::type* =nullptr>\nvoid move_assign_if(T& x,T& y){x=std::move(y);}\n\ntemplate<bool B,typename T,typename std::enable_if<!B>::type* =nullptr>\nvoid move_assign_if(T&,T&){}\n\ntemplate<bool B,typename T,typename std::enable_if<B>::type* =nullptr>\nvoid swap_if(T& x,T& y){using std::swap; swap(x,y);}\n\ntemplate<bool B,typename T,typename std::enable_if<!B>::type* =nullptr>\nvoid swap_if(T&,T&){}\n\ntemplate<typename Allocator>\nstruct is_std_allocator:std::false_type{};\n\ntemplate<typename T>\nstruct is_std_allocator<std::allocator<T>>:std::true_type{};\n\n/* std::allocator::construct marked as deprecated */\n#ifdef _LIBCPP_SUPPRESS_DEPRECATED_PUSH\n_LIBCPP_SUPPRESS_DEPRECATED_PUSH\n#elif defined(_STL_DISABLE_DEPRECATED_WARNING)\n_STL_DISABLE_DEPRECATED_WARNING\n#elif defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable:4996)\n#endif\n\ntemplate<typename Allocator,typename Ptr,typename... Args>\nstruct alloc_has_construct\n{\nprivate:\n  template<typename Allocator2>\n  static decltype(\n    std::declval<Allocator2&>().construct(\n      std::declval<Ptr>(),std::declval<Args&&>()...),\n    std::true_type{}\n  ) check(int);\n\n  template<typename> static std::false_type check(...);\n\npublic:\n  static constexpr bool value=decltype(check<Allocator>(0))::value;\n};\n\n#ifdef _LIBCPP_SUPPRESS_DEPRECATED_POP\n_LIBCPP_SUPPRESS_DEPRECATED_POP\n#elif defined(_STL_RESTORE_DEPRECATED_WARNING)\n_STL_RESTORE_DEPRECATED_WARNING\n#elif defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n\n/* We expose the hard-coded max load factor so that tests can use it without\n * needing to pull it from an instantiated class template such as the table\n * class.\n */\nstatic constexpr float mlf=0.875f;\n\ntemplate<typename Group,typename Element>\nstruct table_locator\n{\n  table_locator()=default;\n  table_locator(Group* pg_,unsigned int n_,Element* p_):pg{pg_},n{n_},p{p_}{}\n\n  explicit operator bool()const noexcept{return p!=nullptr;}\n\n  Group        *pg=nullptr;\n  unsigned int  n=0;\n  Element      *p=nullptr;\n};\n\nstruct try_emplace_args_t{};\n\ntemplate<typename TypePolicy,typename Allocator,typename... Args>\nclass alloc_cted_insert_type\n{\n  using emplace_type=typename std::conditional<\n    std::is_constructible<typename TypePolicy::init_type,Args...>::value,\n    typename TypePolicy::init_type,\n    typename TypePolicy::value_type\n  >::type;\n\n  using insert_type=typename std::conditional<\n    std::is_constructible<typename TypePolicy::value_type,emplace_type>::value,\n    emplace_type,typename TypePolicy::element_type\n  >::type;\n\n  using alloc_cted = allocator_constructed<Allocator, insert_type, TypePolicy>;\n  alloc_cted val;\n\npublic:\n  alloc_cted_insert_type(const Allocator& al_,Args&&... args):val{al_,std::forward<Args>(args)...}\n  {\n  }\n\n  insert_type& value(){return val.value();}\n};\n\ntemplate<typename TypePolicy,typename Allocator,typename... Args>\nalloc_cted_insert_type<TypePolicy,Allocator,Args...>\nalloc_make_insert_type(const Allocator& al,Args&&... args)\n{\n  return {al,std::forward<Args>(args)...};\n}\n\ntemplate <typename TypePolicy, typename Allocator, typename KFwdRef,\n  typename = void>\nclass alloc_cted_or_fwded_key_type\n{\n  using key_type = typename TypePolicy::key_type;\n  allocator_constructed<Allocator, key_type, TypePolicy> val;\n\npublic:\n  alloc_cted_or_fwded_key_type(const Allocator& al_, KFwdRef k)\n      : val(al_, std::forward<KFwdRef>(k))\n  {\n  }\n\n  key_type&& move_or_fwd() { return std::move(val.value()); }\n};\n\ntemplate <typename TypePolicy, typename Allocator, typename KFwdRef>\nclass alloc_cted_or_fwded_key_type<TypePolicy, Allocator, KFwdRef,\n  typename std::enable_if<\n    is_similar<KFwdRef, typename TypePolicy::key_type>::value>::type>\n{\n  // This specialization acts as a forwarding-reference wrapper\n  BOOST_UNORDERED_STATIC_ASSERT(std::is_reference<KFwdRef>::value);\n  KFwdRef ref;\n\npublic:\n  alloc_cted_or_fwded_key_type(const Allocator&, KFwdRef k)\n      : ref(std::forward<KFwdRef>(k))\n  {\n  }\n\n  KFwdRef move_or_fwd() { return std::forward<KFwdRef>(ref); }\n};\n\ntemplate <typename Container>\nusing is_map =\n  std::integral_constant<bool, !std::is_same<typename Container::key_type,\n                                 typename Container::value_type>::value>;\n\ntemplate <typename Container, typename K>\nusing is_emplace_kv_able = std::integral_constant<bool,\n  is_map<Container>::value &&\n    (is_similar<K, typename Container::key_type>::value ||\n      is_complete_and_move_constructible<typename Container::key_type>::value)>;\n\n/* table_core. The TypePolicy template parameter is used to generate\n * instantiations suitable for either maps or sets, and introduces non-standard\n * init_type and element_type:\n *\n *   - TypePolicy::key_type and TypePolicy::value_type have the obvious\n *     meaning. TypePolicy::mapped_type is expected to be provided as well\n *     when key_type and value_type are not the same.\n *\n *   - TypePolicy::init_type is the type implicitly converted to when\n *     writing x.insert({...}). For maps, this is std::pair<Key,T> rather\n *     than std::pair<const Key,T> so that, for instance, x.insert({\"hello\",0})\n *     produces a cheaply moveable std::string&& (\"hello\") rather than\n *     a copyable const std::string&&. foa::table::insert is extended to accept\n *     both init_type and value_type references.\n *\n *   - TypePolicy::construct and TypePolicy::destroy are used for the\n *     construction and destruction of the internal types: value_type,\n *     init_type, element_type, and key_type.\n * \n *   - TypePolicy::move is used to provide move semantics for the internal\n *     types used by the container during rehashing and emplace. These types\n *     are init_type, value_type and emplace_type. During insertion, a\n *     stack-local type will be created based on the constructibility of the\n *     value_type and the supplied arguments. TypePolicy::move is used here\n *     for transfer of ownership. Similarly, TypePolicy::move is also used\n *     during rehashing when elements are moved to the new table.\n *\n *   - TypePolicy::extract returns a const reference to the key part of\n *     a value of type value_type, init_type, element_type or\n *     decltype(TypePolicy::move(...)).\n *\n *   - TypePolicy::element_type is the type that table_arrays uses when\n *     allocating buckets, which allows us to have flat and node container.\n *     For flat containers, element_type is value_type. For node\n *     containers, it is a strong typedef to value_type*.\n *\n *   - TypePolicy::value_from returns a mutable reference to value_type from\n *     a given element_type. This is used when elements of the table themselves\n *     need to be moved, such as during move construction/assignment when\n *     allocators are unequal and there is no propagation. For all other cases,\n *     the element_type itself is moved.\n */\n\n#ifdef BOOST_MSVC\n#pragma warning(push)\n#pragma warning(disable:4714)\n#endif\n\ntemplate<\n  typename TypePolicy,typename Group,template<typename...> class Arrays,\n  typename SizeControl,typename Hash,typename Pred,typename Allocator\n>\nclass \n\n#if defined(_MSC_VER)&&_MSC_FULL_VER>=190023918\n__declspec(empty_bases)\n#endif\n\ntable_core:empty_value<Hash,0>,empty_value<Pred,1>,empty_value<Allocator,2>\n{\npublic:\n  using type_policy=TypePolicy;\n  using group_type=Group;\n  static constexpr auto N=group_type::N;\n  using size_policy=pow2_size_policy;\n  using prober=pow2_quadratic_prober;\n  using mix_policy=typename std::conditional<\n    hash_is_avalanching<Hash>::value,\n    no_mix,\n    mulx_mix\n  >::type;\n  using alloc_traits=std::allocator_traits<Allocator>;\n  using element_type=typename type_policy::element_type;\n  using arrays_type=Arrays<element_type,group_type,size_policy,Allocator>;\n  using size_ctrl_type=SizeControl;\n  static constexpr auto uses_fancy_pointers=!std::is_same<\n    typename alloc_traits::pointer,\n    typename alloc_traits::value_type*\n  >::value;\n\n  using key_type=typename type_policy::key_type;\n  using init_type=typename type_policy::init_type;\n  using value_type=typename type_policy::value_type;\n  using hasher=Hash;\n  using key_equal=Pred;\n  using allocator_type=Allocator;\n  using pointer=value_type*;\n  using const_pointer=const value_type*;\n  using reference=value_type&;\n  using const_reference=const value_type&;\n  using size_type=std::size_t;\n  using difference_type=std::ptrdiff_t;\n  using locator=table_locator<group_type,element_type>;\n  using arrays_holder_type=arrays_holder<arrays_type,Allocator>;\n\n  table_core(\n    std::size_t n=default_bucket_count,const Hash& h_=Hash(),\n    const Pred& pred_=Pred(),const Allocator& al_=Allocator()):\n    hash_base{empty_init,h_},pred_base{empty_init,pred_},\n    allocator_base{empty_init,al_},arrays(new_arrays(n)),\n    size_ctrl{initial_max_load(),0}\n    {}\n\n  /* genericize on an ArraysFn so that we can do things like delay an\n   * allocation for the group_access data required by cfoa after the move\n   * constructors of Hash, Pred have been invoked\n   */\n  template<typename ArraysFn>\n  table_core(\n    Hash&& h_,Pred&& pred_,Allocator&& al_,\n    ArraysFn arrays_fn,const size_ctrl_type& size_ctrl_):\n    hash_base{empty_init,std::move(h_)},\n    pred_base{empty_init,std::move(pred_)},\n    allocator_base{empty_init,std::move(al_)},\n    arrays(arrays_fn()),size_ctrl(size_ctrl_)\n  {}\n\n  table_core(const table_core& x):\n    table_core{x,alloc_traits::select_on_container_copy_construction(x.al())}{}\n\n  template<typename ArraysFn>\n  table_core(table_core&& x,arrays_holder_type&& ah,ArraysFn arrays_fn):\n    table_core(\n      std::move(x.h()),std::move(x.pred()),std::move(x.al()),\n      arrays_fn,x.size_ctrl)\n  {\n    x.arrays=ah.release();\n    x.size_ctrl.ml=x.initial_max_load();\n    x.size_ctrl.size=0;\n  }\n\n  table_core(table_core&& x)\n    noexcept(\n      std::is_nothrow_move_constructible<Hash>::value&&\n      std::is_nothrow_move_constructible<Pred>::value&&\n      std::is_nothrow_move_constructible<Allocator>::value&&\n      !uses_fancy_pointers):\n    table_core{\n      std::move(x),x.make_empty_arrays(),[&x]{return x.arrays;}}\n  {}\n\n  table_core(const table_core& x,const Allocator& al_):\n    table_core{std::size_t(std::ceil(float(x.size())/mlf)),x.h(),x.pred(),al_}\n  {\n    copy_elements_from(x);\n  }\n\n  table_core(table_core&& x,const Allocator& al_):\n    table_core{std::move(x.h()),std::move(x.pred()),al_}\n  {\n    if(al()==x.al()){\n      using std::swap;\n      swap(arrays,x.arrays);\n      swap(size_ctrl,x.size_ctrl);\n    }\n    else{\n      reserve(x.size());\n      clear_on_exit c{x};\n      (void)c;\n\n      /* This works because subsequent x.clear() does not depend on the\n       * elements' values.\n       */\n      x.for_all_elements([this](element_type* p){\n        unchecked_insert(type_policy::move(type_policy::value_from(*p)));\n      });\n    }\n  }\n\n  ~table_core()noexcept\n  {\n    for_all_elements([this](element_type* p){\n      destroy_element(p);\n    });\n    delete_arrays(arrays);\n  }\n\n  std::size_t initial_max_load()const\n  {\n    static constexpr std::size_t small_capacity=2*N-1;\n\n    auto capacity_=capacity();\n    if(capacity_<=small_capacity){\n      return capacity_;\n    }\n    else{\n      return (std::size_t)(mlf*(float)(capacity_));\n    }\n  }\n\n  arrays_holder_type make_empty_arrays()const\n  {\n    return make_arrays(0);\n  }\n\n  table_core& operator=(const table_core& x)\n  {\n    BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred)\n\n    static constexpr auto pocca=\n      alloc_traits::propagate_on_container_copy_assignment::value;\n\n    if(this!=std::addressof(x)){\n      /* If copy construction here winds up throwing, the container is still\n       * left intact so we perform these operations first.\n       */\n      hasher    tmp_h=x.h();\n      key_equal tmp_p=x.pred();\n\n      clear();\n\n      /* Because we've asserted at compile-time that Hash and Pred are nothrow\n       * swappable, we can safely mutate our source container and maintain\n       * consistency between the Hash, Pred compatibility.\n       */\n      using std::swap;\n      swap(h(),tmp_h);\n      swap(pred(),tmp_p);\n\n      if_constexpr<pocca>([&,this]{\n        if(al()!=x.al()){\n          auto ah=x.make_arrays(std::size_t(std::ceil(float(x.size())/mlf)));\n          delete_arrays(arrays);\n          arrays=ah.release();\n          size_ctrl.ml=initial_max_load();\n        }\n        copy_assign_if<pocca>(al(),x.al());\n      });\n      /* noshrink: favor memory reuse over tightness */\n      noshrink_reserve(x.size());\n      copy_elements_from(x);\n    }\n    return *this;\n  }\n\n#ifdef BOOST_MSVC\n#pragma warning(push)\n#pragma warning(disable:4127)\n#endif\n\n  table_core& operator=(table_core&& x)\n    noexcept(\n      (alloc_traits::propagate_on_container_move_assignment::value||\n      alloc_traits::is_always_equal::value)&&!uses_fancy_pointers)\n  {\n    BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred)\n\n    static constexpr auto pocma=\n      alloc_traits::propagate_on_container_move_assignment::value;\n\n    if(this!=std::addressof(x)){\n      /* Given ambiguity in implementation strategies briefly discussed here:\n       * https://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2227\n       *\n       * we opt into requiring nothrow swappability and eschew the move\n       * operations associated with Hash, Pred.\n       *\n       * To this end, we ensure that the user never has to consider the\n       * moved-from state of their Hash, Pred objects\n       */\n\n      using std::swap;\n\n      clear();\n\n      if(pocma||al()==x.al()){\n        auto ah=x.make_empty_arrays();\n        swap(h(),x.h());\n        swap(pred(),x.pred());\n        delete_arrays(arrays);\n        move_assign_if<pocma>(al(),x.al());\n        arrays=x.arrays;\n        size_ctrl.ml=std::size_t(x.size_ctrl.ml);\n        size_ctrl.size=std::size_t(x.size_ctrl.size);\n        x.arrays=ah.release();\n        x.size_ctrl.ml=x.initial_max_load();\n        x.size_ctrl.size=0;\n      }\n      else{\n        swap(h(),x.h());\n        swap(pred(),x.pred());\n\n        /* noshrink: favor memory reuse over tightness */\n        noshrink_reserve(x.size());\n        clear_on_exit c{x};\n        (void)c;\n\n        /* This works because subsequent x.clear() does not depend on the\n         * elements' values.\n         */\n        x.for_all_elements([this](element_type* p){\n          unchecked_insert(type_policy::move(type_policy::value_from(*p)));\n        });\n      }\n    }\n    return *this;\n  }\n\n#ifdef BOOST_MSVC\n#pragma warning(pop)\n#endif\n\n  allocator_type get_allocator()const noexcept{return al();}\n\n  bool        empty()const noexcept{return size()==0;}\n  std::size_t size()const noexcept{return size_ctrl.size;}\n  std::size_t max_size()const noexcept{return SIZE_MAX;}\n\n  BOOST_FORCEINLINE\n  void erase(group_type* pg,unsigned int pos,element_type* p)noexcept\n  {\n    destroy_element(p);\n    recover_slot(pg,pos);\n  }\n\n  BOOST_FORCEINLINE\n  void erase(unsigned char* pc,element_type* p)noexcept\n  {\n    destroy_element(p);\n    recover_slot(pc);\n  }\n\n  template<typename Key>\n  BOOST_FORCEINLINE locator find(const Key& x)const\n  {\n    auto hash=hash_for(x);\n    return find(x,position_for(hash),hash);\n  }\n\n#ifdef BOOST_MSVC\n/* warning: forcing value to bool 'true' or 'false' in bool(pred()...) */\n#pragma warning(push)\n#pragma warning(disable:4800)\n#endif\n\n  template<typename Key>\n  BOOST_FORCEINLINE locator find(\n    const Key& x,std::size_t pos0,std::size_t hash)const\n  {    \n    prober pb(pos0);\n    do{\n      auto pos=pb.get();\n      auto pg=arrays.groups()+pos;\n      auto mask=pg->match(hash);\n      if(mask){\n        auto elements=arrays.elements();\n        BOOST_UNORDERED_ASSUME(elements!=nullptr);\n        auto p=elements+pos*N;\n        BOOST_UNORDERED_PREFETCH_ELEMENTS(p,N);\n        do{\n          auto n=unchecked_countr_zero(mask);\n          if(BOOST_LIKELY(bool(pred()(x,key_from(p[n]))))){\n            return {pg,n,p+n};\n          }\n          mask&=mask-1;\n        }while(mask);\n      }\n      if(BOOST_LIKELY(pg->is_not_overflowed(hash))){\n        return {};\n      }\n    }\n    while(BOOST_LIKELY(pb.next(arrays.groups_size_mask)));\n    return {};\n  }\n\n#ifdef BOOST_MSVC\n#pragma warning(pop)\n#endif\n\n  void swap(table_core& x)\n    noexcept(\n      alloc_traits::propagate_on_container_swap::value||\n      alloc_traits::is_always_equal::value)\n  {\n    BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred)\n\n    static constexpr auto pocs=\n      alloc_traits::propagate_on_container_swap::value;\n\n    using std::swap;\n    if_constexpr<pocs>([&,this]{\n      swap_if<pocs>(al(),x.al());\n    },\n    [&,this]{\n      BOOST_ASSERT(al()==x.al());\n      (void)this;\n    });\n\n    swap(h(),x.h());\n    swap(pred(),x.pred());\n    swap(arrays,x.arrays);\n    swap(size_ctrl,x.size_ctrl);\n  }\n\n  void clear()noexcept\n  {\n    auto p=arrays.elements();\n    if(p){\n      for(auto pg=arrays.groups(),last=pg+arrays.groups_size_mask+1;\n          pg!=last;++pg,p+=N){\n        auto mask=match_really_occupied(pg,last);\n        while(mask){\n          destroy_element(p+unchecked_countr_zero(mask));\n          mask&=mask-1;\n        }\n        /* we wipe the entire metadata to reset the overflow byte as well */\n        pg->initialize();\n      }\n      arrays.groups()[arrays.groups_size_mask].set_sentinel();\n      size_ctrl.ml=initial_max_load();\n      size_ctrl.size=0;\n    }\n  }\n\n  hasher hash_function()const{return h();}\n  key_equal key_eq()const{return pred();}\n\n  std::size_t capacity()const noexcept\n  {\n    return arrays.elements()?(arrays.groups_size_mask+1)*N-1:0;\n  }\n  \n  float load_factor()const noexcept\n  {\n    if(capacity()==0)return 0;\n    else             return float(size())/float(capacity());\n  }\n\n  float max_load_factor()const noexcept{return mlf;}\n\n  std::size_t max_load()const noexcept{return size_ctrl.ml;}\n\n  void rehash(std::size_t n)\n  {\n    auto m=size_t(std::ceil(float(size())/mlf));\n    if(m>n)n=m;\n    if(n)n=capacity_for(n);\n\n    if(n!=capacity())unchecked_rehash(n);\n  }\n\n  void reserve(std::size_t n)\n  {\n    rehash(std::size_t(std::ceil(float(n)/mlf)));\n  }\n\n  friend bool operator==(const table_core& x,const table_core& y)\n  {\n    return\n      x.size()==y.size()&&\n      x.for_all_elements_while([&](element_type* p){\n        auto loc=y.find(key_from(*p));\n        return loc&&\n          const_cast<const value_type&>(type_policy::value_from(*p))==\n          const_cast<const value_type&>(type_policy::value_from(*loc.p));\n      });\n  }\n\n  friend bool operator!=(const table_core& x,const table_core& y)\n  {\n    return !(x==y);\n  }\n\n  struct clear_on_exit\n  {\n    ~clear_on_exit(){x.clear();}\n    table_core& x;\n  };\n\n  Hash&            h(){return hash_base::get();}\n  const Hash&      h()const{return hash_base::get();}\n  Pred&            pred(){return pred_base::get();}\n  const Pred&      pred()const{return pred_base::get();}\n  Allocator&       al(){return allocator_base::get();}\n  const Allocator& al()const{return allocator_base::get();}\n\n  template<typename... Args>\n  void construct_element(element_type* p,Args&&... args)\n  {\n    type_policy::construct(al(),p,std::forward<Args>(args)...);\n  }\n\n  template<typename... Args>\n  void construct_element(element_type* p,try_emplace_args_t,Args&&... args)\n  {\n    construct_element_from_try_emplace_args(\n      p,\n      std::integral_constant<bool,std::is_same<key_type,value_type>::value>{},\n      std::forward<Args>(args)...);\n  }\n  \n// Lynx Add Begin\n  // Constructs key-value at address p, but is treated as constructing key with\n  // plain bytes from argument key and value is left as uninitialized plain\n  // bytes.\n  template<typename... Args>\n  void construct_element(element_type* p,try_emplace_plain_bytes_key_t,\n                         Args&&... args)\n  {\n    type_policy::construct_plain_bytes_key(p,std::forward<Args>(args)...);\n  }\n  \n  // Constructs key-value at address p, but is treated as constructing with\n  // plain bytes from address from_p.\n  template<typename... Args>\n  void construct_element(element_type* p,try_emplace_plain_bytes_t,\n                         Args&&... args) {\n    type_policy::construct_plain_bytes(p,std::forward<Args>(args)...);\n  }\n  \n  template<typename Key,typename... Args>\n  static inline const Key& key_from(\n    try_emplace_plain_bytes_key_t,const Key& x,const Args&...)\n  {\n    return x;\n  }\n// Lynx Add End\n\n  void destroy_element(element_type* p)noexcept\n  {\n    type_policy::destroy(al(),p);\n  }\n\n  struct destroy_element_on_exit\n  {\n    ~destroy_element_on_exit(){this_->destroy_element(p);}\n    table_core   *this_;\n    element_type *p;\n  };\n\n  template<typename T>\n  static inline auto key_from(const T& x)\n    ->decltype(type_policy::extract(x))\n  {\n    return type_policy::extract(x);\n  }\n\n  template<typename Key,typename... Args>\n  static inline const Key& key_from(\n    try_emplace_args_t,const Key& x,const Args&...)\n  {\n    return x;\n  }\n\n  template<typename Key>\n  inline std::size_t hash_for(const Key& x)const\n  {\n    return mix_policy::mix(h(),x);\n  }\n\n  inline std::size_t position_for(std::size_t hash)const\n  {\n    return position_for(hash,arrays);\n  }\n\n  static inline std::size_t position_for(\n    std::size_t hash,const arrays_type& arrays_)\n  {\n    return size_policy::position(hash,arrays_.groups_size_index);\n  }\n\n  static inline int match_really_occupied(group_type* pg,group_type* last)\n  {\n    /* excluding the sentinel */\n    return pg->match_occupied()&~(int(pg==last-1)<<(N-1));\n  }\n\n  template<typename... Args>\n  locator unchecked_emplace_at(\n    std::size_t pos0,std::size_t hash,Args&&... args)\n  {\n    auto res=nosize_unchecked_emplace_at(\n      arrays,pos0,hash,std::forward<Args>(args)...);\n    ++size_ctrl.size;\n    return res;\n  }\n\n  BOOST_NOINLINE void unchecked_rehash_for_growth()\n  {\n    auto new_arrays_=new_arrays_for_growth();\n    unchecked_rehash(new_arrays_);\n  }\n\n  template<typename... Args>\n  BOOST_NOINLINE locator\n  unchecked_emplace_with_rehash(std::size_t hash,Args&&... args)\n  {\n    auto    new_arrays_=new_arrays_for_growth();\n    locator it;\n    BOOST_TRY{\n      /* strong exception guarantee -> try insertion before rehash */\n      it=nosize_unchecked_emplace_at(\n        new_arrays_,position_for(hash,new_arrays_),\n        hash,std::forward<Args>(args)...);\n    }\n    BOOST_CATCH(...){\n      delete_arrays(new_arrays_);\n      BOOST_RETHROW\n    }\n    BOOST_CATCH_END\n\n    /* new_arrays_ lifetime taken care of by unchecked_rehash */\n    unchecked_rehash(new_arrays_);\n    ++size_ctrl.size;\n    return it;\n  }\n\n  void noshrink_reserve(std::size_t n)\n  {\n    /* used only on assignment after element clearance */\n    BOOST_ASSERT(empty());\n\n    if(n){\n      n=std::size_t(std::ceil(float(n)/mlf));\n      n=capacity_for(n);\n\n      if(n>capacity()){\n        auto new_arrays_=new_arrays(n);\n        delete_arrays(arrays);\n        arrays=new_arrays_;\n        size_ctrl.ml=initial_max_load();\n      }\n    }\n  }\n\n  template<typename F>\n  void for_all_elements(F f)const\n  {\n    for_all_elements(arrays,f);\n  }\n\n  template<typename F>\n  static auto for_all_elements(const arrays_type& arrays_,F f)\n    ->decltype(f(nullptr),void())\n  {\n    for_all_elements_while(arrays_,[&](element_type* p){f(p);return true;});\n  }\n\n  template<typename F>\n  static auto for_all_elements(const arrays_type& arrays_,F f)\n    ->decltype(f(nullptr,0,nullptr),void())\n  {\n    for_all_elements_while(\n      arrays_,[&](group_type* pg,unsigned int n,element_type* p)\n        {f(pg,n,p);return true;});\n  }\n\n  template<typename F>\n  bool for_all_elements_while(F f)const\n  {\n    return for_all_elements_while(arrays,f);\n  }\n\n  template<typename F>\n  static auto for_all_elements_while(const arrays_type& arrays_,F f)\n    ->decltype(f(nullptr),bool())\n  {\n    return for_all_elements_while(\n      arrays_,[&](group_type*,unsigned int,element_type* p){return f(p);});\n  }\n\n  template<typename F>\n  static auto for_all_elements_while(const arrays_type& arrays_,F f)\n    ->decltype(f(nullptr,0,nullptr),bool())\n  {\n    auto p=arrays_.elements();\n    if(p){\n      for(auto pg=arrays_.groups(),last=pg+arrays_.groups_size_mask+1;\n          pg!=last;++pg,p+=N){\n        auto mask=match_really_occupied(pg,last);\n        while(mask){\n          auto n=unchecked_countr_zero(mask);\n          if(!f(pg,n,p+n))return false;\n          mask&=mask-1;\n        }\n      }\n    }\n    return true;\n  }\n\n  arrays_type    arrays;\n  size_ctrl_type size_ctrl;\n\nprivate:\n  template<\n    typename,typename,template<typename...> class,\n    typename,typename,typename,typename\n  >\n  friend class table_core;\n\n  using hash_base=empty_value<Hash,0>;\n  using pred_base=empty_value<Pred,1>;\n  using allocator_base=empty_value<Allocator,2>;\n\n  /* used by allocator-extended move ctor */\n\n  table_core(Hash&& h_,Pred&& pred_,const Allocator& al_):\n    hash_base{empty_init,std::move(h_)},\n    pred_base{empty_init,std::move(pred_)},\n    allocator_base{empty_init,al_},arrays(new_arrays(0)),\n    size_ctrl{initial_max_load(),0}\n  {\n  }\n\n  arrays_type new_arrays(std::size_t n)const\n  {\n    return arrays_type::new_(typename arrays_type::allocator_type(al()),n);\n  }\n\n  arrays_type new_arrays_for_growth()const\n  {\n    /* Due to the anti-drift mechanism (see recover_slot), the new arrays may\n     * be of the same size as the old arrays; in the limit, erasing one\n     * element at full load and then inserting could bring us back to the same\n     * capacity after a costly rehash. To avoid this, we jump to the next\n     * capacity level when the number of erased elements is <= 10% of total\n     * elements at full load, which is implemented by requesting additional\n     * F*size elements, with F = P * 10% / (1 - P * 10%), where P is the\n     * probability of an element having caused overflow; P has been measured as\n     * ~0.162 under ideal conditions, yielding F ~ 0.0165 ~ 1/61.\n     */\n    return new_arrays(std::size_t(\n      std::ceil(static_cast<float>(size()+size()/61+1)/mlf)));\n  }\n\n  void delete_arrays(arrays_type& arrays_)noexcept\n  {\n    arrays_type::delete_(typename arrays_type::allocator_type(al()),arrays_);\n  }\n\n  arrays_holder_type make_arrays(std::size_t n)const\n  {\n    return {new_arrays(n),al()};\n  }\n\n  template<typename Key,typename... Args>\n  void construct_element_from_try_emplace_args(\n    element_type* p,std::false_type,Key&& x,Args&&... args)\n  {\n    type_policy::construct(\n      this->al(),p,\n      std::piecewise_construct,\n      std::forward_as_tuple(std::forward<Key>(x)),\n      std::forward_as_tuple(std::forward<Args>(args)...));\n  }\n\n  /* This overload allows boost::unordered_flat_set to internally use\n   * try_emplace to implement heterogeneous insert (P2363).\n   */\n\n  template<typename Key>\n  void construct_element_from_try_emplace_args(\n    element_type* p,std::true_type,Key&& x)\n  {\n    type_policy::construct(this->al(),p,std::forward<Key>(x));\n  }\n\n  void copy_elements_from(const table_core& x)\n  {\n    BOOST_ASSERT(empty());\n    BOOST_ASSERT(this!=std::addressof(x));\n    if(arrays.groups_size_mask==x.arrays.groups_size_mask){\n      fast_copy_elements_from(x);\n    }\n    else{\n      x.for_all_elements([this](const element_type* p){\n        unchecked_insert(*p);\n      });\n    }\n  }\n\n  void fast_copy_elements_from(const table_core& x)\n  {\n    if(arrays.elements()&&x.arrays.elements()){\n      copy_elements_array_from(x);\n      copy_groups_array_from(x);\n      size_ctrl.ml=std::size_t(x.size_ctrl.ml);\n      size_ctrl.size=std::size_t(x.size_ctrl.size);\n    }\n  }\n\n  void copy_elements_array_from(const table_core& x)\n  {\n    copy_elements_array_from(\n      x,\n      std::integral_constant<\n        bool,\n        is_trivially_copy_constructible<element_type>::value&&(\n          is_std_allocator<Allocator>::value||\n          !alloc_has_construct<Allocator,value_type*,const value_type&>::value)\n      >{}\n    );\n  }\n\n  void copy_elements_array_from(\n    const table_core& x,std::true_type /* -> memcpy */)\n  {\n    /* reinterpret_cast: GCC may complain about value_type not being trivially\n     * copy-assignable when we're relying on trivial copy constructibility.\n     */\n    std::memcpy(\n      reinterpret_cast<unsigned char*>(arrays.elements()),\n      reinterpret_cast<unsigned char*>(x.arrays.elements()),\n      x.capacity()*sizeof(value_type));\n  }\n\n  void copy_elements_array_from(\n    const table_core& x,std::false_type /* -> manual */)\n  {\n    std::size_t num_constructed=0;\n    BOOST_TRY{\n      x.for_all_elements([&,this](const element_type* p){\n        construct_element(arrays.elements()+(p-x.arrays.elements()),*p);\n        ++num_constructed;\n      });\n    }\n    BOOST_CATCH(...){\n      if(num_constructed){\n        x.for_all_elements_while([&,this](const element_type* p){\n          destroy_element(arrays.elements()+(p-x.arrays.elements()));\n          return --num_constructed!=0;\n        });\n      }\n      BOOST_RETHROW\n    }\n    BOOST_CATCH_END\n  }\n\n  void copy_groups_array_from(const table_core& x) {\n    copy_groups_array_from(x,is_trivially_copy_assignable<group_type>{});\n  }\n\n  void copy_groups_array_from(\n    const table_core& x, std::true_type /* -> memcpy */)\n  {\n    std::memcpy(\n      arrays.groups(),x.arrays.groups(),\n      (arrays.groups_size_mask+1)*sizeof(group_type));\n  }\n\n  void copy_groups_array_from(\n    const table_core& x, std::false_type /* -> manual */) \n  {\n    auto pg=arrays.groups();\n    auto xpg=x.arrays.groups();\n    for(std::size_t i=0;i<arrays.groups_size_mask+1;++i){\n      pg[i]=xpg[i];\n    }\n  }\n\n  void recover_slot(unsigned char* pc)\n  {\n    /* If this slot potentially caused overflow, we decrease the maximum load\n     * so that average probe length won't increase unboundedly in repeated\n     * insert/erase cycles (drift).\n     */\n    size_ctrl.ml-=group_type::maybe_caused_overflow(pc);\n    group_type::reset(pc);\n    --size_ctrl.size;\n  }\n\n  void recover_slot(group_type* pg,std::size_t pos)\n  {\n    recover_slot(reinterpret_cast<unsigned char*>(pg)+pos);\n  }\n\n  static std::size_t capacity_for(std::size_t n)\n  {\n    return size_policy::size(size_index_for<group_type,size_policy>(n))*N-1;\n  }\n\n  BOOST_NOINLINE void unchecked_rehash(std::size_t n)\n  {\n    auto new_arrays_=new_arrays(n);\n    unchecked_rehash(new_arrays_);\n  }\n\n  BOOST_NOINLINE void unchecked_rehash(arrays_type& new_arrays_)\n  {\n    std::size_t num_destroyed=0;\n    BOOST_TRY{\n      for_all_elements([&,this](element_type* p){\n        nosize_transfer_element(p,new_arrays_,num_destroyed);\n      });\n    }\n    BOOST_CATCH(...){\n      if(num_destroyed){\n        for_all_elements_while(\n          [&,this](group_type* pg,unsigned int n,element_type*){\n            recover_slot(pg,n);\n            return --num_destroyed!=0;\n          }\n        );\n      }\n      for_all_elements(new_arrays_,[this](element_type* p){\n        destroy_element(p);\n      });\n      delete_arrays(new_arrays_);\n      BOOST_RETHROW\n    }\n    BOOST_CATCH_END\n\n    /* either all moved and destroyed or all copied */\n    BOOST_ASSERT(num_destroyed==size()||num_destroyed==0);\n    if(num_destroyed!=size()){\n      for_all_elements([this](element_type* p){\n        destroy_element(p);\n      });\n    }\n    delete_arrays(arrays);\n    arrays=new_arrays_;\n    size_ctrl.ml=initial_max_load();\n  }\n\n  template<typename Value>\n  void unchecked_insert(Value&& x)\n  {\n    auto hash=hash_for(key_from(x));\n    unchecked_emplace_at(position_for(hash),hash,std::forward<Value>(x));\n  }\n\n  void nosize_transfer_element(\n    element_type* p,const arrays_type& arrays_,std::size_t& num_destroyed)\n  {\n    nosize_transfer_element(\n      p,hash_for(key_from(*p)),arrays_,num_destroyed,\n      std::integral_constant<\n        bool,\n        std::is_nothrow_move_constructible<init_type>::value||\n        !std::is_same<element_type,value_type>::value||\n        !std::is_copy_constructible<element_type>::value>{});\n  }\n\n  void nosize_transfer_element(\n    element_type* p,std::size_t hash,const arrays_type& arrays_,\n    std::size_t& num_destroyed,std::true_type /* ->move */)\n  {\n// Lynx Add Begin\n    if constexpr (std::is_same_v<typename type_policy::value_type,\n                                 typename type_policy::key_type>) {\n      // is set, no opt\n    } else {\n      constexpr bool is_relocatable = lynx::base::IsTriviallyRelocatable<\n          value_type, lynx::base::is_instance<value_type, std::pair>{}>::value;\n      if constexpr (is_relocatable) {\n        if constexpr (std::is_same_v<type_policy,\n                                     detail::foa::flat_map_types<\n                                         typename type_policy::key_type,\n                                         typename type_policy::mapped_type>>) {\n          // Key and T are both relocatable in flat map, fast path to copy data\n          // trivially and ignores destructors.\n          // No destroy, trivially moved.\n          ++num_destroyed;\n          nosize_unchecked_emplace_at(\n            arrays_,position_for(hash,arrays_),hash,try_emplace_plain_bytes_t{},p);\n          return;\n        }\n      }\n    }\n// Lynx Add End\n    /* Destroy p even if an an exception is thrown in the middle of move\n     * construction, which could leave the source half-moved.\n     */\n    ++num_destroyed;\n    destroy_element_on_exit d{this,p};\n    (void)d;\n    nosize_unchecked_emplace_at(\n      arrays_,position_for(hash,arrays_),hash,type_policy::move(*p));\n  }\n\n  void nosize_transfer_element(\n    element_type* p,std::size_t hash,const arrays_type& arrays_,\n    std::size_t& /*num_destroyed*/,std::false_type /* ->copy */)\n  {\n    nosize_unchecked_emplace_at(\n      arrays_,position_for(hash,arrays_),hash,\n      const_cast<const element_type&>(*p));\n  }\n\n  template<typename... Args>\n  locator nosize_unchecked_emplace_at(\n    const arrays_type& arrays_,std::size_t pos0,std::size_t hash,\n    Args&&... args)\n  {\n    for(prober pb(pos0);;pb.next(arrays_.groups_size_mask)){\n      auto pos=pb.get();\n      auto pg=arrays_.groups()+pos;\n      auto mask=pg->match_available();\n      if(BOOST_LIKELY(mask!=0)){\n        auto n=unchecked_countr_zero(mask);\n        auto p=arrays_.elements()+pos*N+n;\n        construct_element(p,std::forward<Args>(args)...);\n        pg->set(n,hash);\n        return {pg,n,p};\n      }\n      else pg->mark_overflow(hash);\n    }\n  }\n};\n\n#ifdef BOOST_MSVC\n#pragma warning(pop)\n#endif\n\n}\n}\n}\n}\n\n#undef BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED\n#undef BOOST_UNORDERED_HAS_FEATURE\n#undef BOOST_UNORDERED_HAS_BUILTIN\n#endif\n/* Fast open-addressing hash table.\n *\n * Copyright 2022-2023 Joaquin M Lopez Munoz.\n * Copyright 2023 Christian Mazakas.\n * Copyright 2024 Braden Ganetsky.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifndef BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP\n#define BOOST_UNORDERED_DETAIL_FOA_TABLE_HPP\n\nnamespace boost{\nnamespace unordered{\nnamespace detail{\nnamespace foa{\n\n/* use plain integrals for group metadata storage */\n\ntemplate<typename Integral>\nstruct plain_integral\n{\n  operator Integral()const{return n;}\n  void operator=(Integral m){n=m;}\n\n  void operator|=(Integral m){n|=m;}\n  void operator&=(Integral m){n&=m;}\n\n  Integral n;\n};\n\nstruct plain_size_control\n{\n  std::size_t ml;\n  std::size_t size;\n};\n\ntemplate<typename,typename,typename,typename>\nclass table;\n\n/* table_iterator keeps two pointers:\n * \n *   - A pointer p to the element slot.\n *   - A pointer pc to the n-th byte of the associated group metadata, where n\n *     is the position of the element in the group.\n *\n * A simpler solution would have been to keep a pointer p to the element, a\n * pointer pg to the group, and the position n, but that would increase\n * sizeof(table_iterator) by 4/8 bytes. In order to make this compact\n * representation feasible, it is required that group objects are aligned\n * to their size, so that we can recover pg and n as\n * \n *   - n = pc%sizeof(group)\n *   - pg = pc-n\n * \n * (for explanatory purposes pg and pc are treated above as if they were memory\n * addresses rather than pointers).\n * \n * p = nullptr is conventionally used to mark end() iterators.\n */\n\n/* internal conversion from const_iterator to iterator */\nstruct const_iterator_cast_tag{}; \n\ntemplate<typename TypePolicy,typename GroupPtr,bool Const>\nclass table_iterator\n{\n  using group_pointer_traits=std::pointer_traits<GroupPtr>;\n  using type_policy=TypePolicy;\n  using table_element_type=typename type_policy::element_type;\n  using group_type=typename group_pointer_traits::element_type;\n  using table_element_pointer=\n    typename group_pointer_traits::template rebind<table_element_type>;\n  using char_pointer=\n    typename group_pointer_traits::template rebind<unsigned char>;\n  static constexpr auto N=group_type::N;\n  static constexpr auto regular_layout=group_type::regular_layout;\n\npublic:\n  using difference_type=std::ptrdiff_t;\n  using value_type=typename type_policy::value_type;\n  using pointer=\n    typename std::conditional<Const,value_type const*,value_type*>::type;\n  using reference=\n    typename std::conditional<Const,value_type const&,value_type&>::type;\n  using iterator_category=std::forward_iterator_tag;\n  using element_type=\n    typename std::conditional<Const,value_type const,value_type>::type;\n\n  table_iterator():pc_{nullptr},p_{nullptr}{};\n  template<bool Const2,typename std::enable_if<!Const2>::type* =nullptr>\n  table_iterator(const table_iterator<TypePolicy,GroupPtr,Const2>& x):\n    pc_{x.pc_},p_{x.p_}{}\n  table_iterator(\n    const_iterator_cast_tag, const table_iterator<TypePolicy,GroupPtr,true>& x):\n    pc_{x.pc_},p_{x.p_}{}\n\n  inline reference operator*()const noexcept\n    {return type_policy::value_from(*p());}\n  inline pointer operator->()const noexcept\n    {return std::addressof(type_policy::value_from(*p()));}\n  inline table_iterator& operator++()noexcept{increment();return *this;}\n  inline table_iterator operator++(int)noexcept\n    {auto x=*this;increment();return x;}\n  friend inline bool operator==(\n    const table_iterator& x,const table_iterator& y)\n    {return x.p()==y.p();}\n  friend inline bool operator!=(\n    const table_iterator& x,const table_iterator& y)\n    {return !(x==y);}\n  \n// Lynx Add Begin\n  table_iterator(void* pc, const void* element): \n    pc_(static_cast<char_pointer>(pc)), \n    p_(static_cast<table_element_pointer>(const_cast<void*>(element))) {}\n  \n  char_pointer& inner_pc() noexcept{return pc_;}\n  table_element_pointer& inner_p() noexcept{return p_;}\n// Lynx Add End\n\nprivate:\n  template<typename,typename,bool> friend class table_iterator;\n  template<typename> friend class table_erase_return_type;\n  template<typename,typename,typename,typename> friend class table;\n\n  table_iterator(group_type* pg,std::size_t n,const table_element_type* ptet):\n    pc_{to_pointer<char_pointer>(\n      reinterpret_cast<unsigned char*>(const_cast<group_type*>(pg))+n)},\n    p_{to_pointer<table_element_pointer>(const_cast<table_element_type*>(ptet))}\n  {}\n\n  unsigned char* pc()const noexcept{return std::to_address(pc_);}\n  table_element_type* p()const noexcept{return std::to_address(p_);}\n\n  inline void increment()noexcept\n  {\n    BOOST_ASSERT(p()!=nullptr);\n    increment(std::integral_constant<bool,regular_layout>{});\n  }\n\n  inline void increment(std::true_type /* regular layout */)noexcept\n  {\n    using diff_type=\n      typename std::pointer_traits<char_pointer>::difference_type;\n\n    for(;;){\n      ++p_;\n      if(reinterpret_cast<uintptr_t>(pc())%sizeof(group_type)==N-1){\n        pc_+=static_cast<diff_type>(sizeof(group_type)-(N-1));\n        break;\n      }\n      ++pc_;\n      if(!group_type::is_occupied(pc()))continue;\n      if(BOOST_UNLIKELY(group_type::is_sentinel(pc())))p_=nullptr;\n      return;\n    }\n\n    for(;;){\n      int mask=reinterpret_cast<group_type*>(pc())->match_occupied();\n      if(mask!=0){\n        auto n=unchecked_countr_zero(mask);\n        if(BOOST_UNLIKELY(reinterpret_cast<group_type*>(pc())->is_sentinel(n))){\n          p_=nullptr;\n        }\n        else{\n          pc_+=static_cast<diff_type>(n);\n          p_+=static_cast<diff_type>(n);\n        }\n        return;\n      }\n      pc_+=static_cast<diff_type>(sizeof(group_type));\n      p_+=static_cast<diff_type>(N);\n    }\n  }\n\n  inline void increment(std::false_type /* interleaved */)noexcept\n  {\n    using diff_type=\n      typename std::pointer_traits<char_pointer>::difference_type;\n\n    std::size_t n0=reinterpret_cast<uintptr_t>(pc())%sizeof(group_type);\n    pc_-=static_cast<diff_type>(n0);\n\n    int mask=(\n      reinterpret_cast<group_type*>(pc())->match_occupied()>>(n0+1))<<(n0+1);\n    if(!mask){\n      do{\n        pc_+=sizeof(group_type);\n        p_+=N;\n      }\n      while((mask=reinterpret_cast<group_type*>(pc())->match_occupied())==0);\n    }\n\n    auto n=unchecked_countr_zero(mask);\n    if(BOOST_UNLIKELY(reinterpret_cast<group_type*>(pc())->is_sentinel(n))){\n      p_=nullptr;\n    }\n    else{\n      pc_+=static_cast<diff_type>(n);\n      p_-=static_cast<diff_type>(n0);\n      p_+=static_cast<diff_type>(n);\n    }\n  }\n\n  template<typename Archive>\n  friend void serialization_track(Archive& ar,const table_iterator& x)\n  {\n    if(x.p()){\n      track_address(ar,x.pc_);\n      track_address(ar,x.p_);\n    }\n  }\n\n  template<typename Archive>\n  void serialize(Archive& ar,unsigned int)\n  {\n    if(!p())pc_=nullptr;\n    serialize_tracked_address(ar,pc_);\n    serialize_tracked_address(ar,p_);\n  }\n\n  char_pointer          pc_=nullptr;\n  table_element_pointer  p_=nullptr;\n};\n\n/* Returned by table::erase([const_]iterator) to avoid iterator increment\n * if discarded.\n */\n\ntemplate<typename Iterator>\nclass table_erase_return_type; \n\ntemplate<typename TypePolicy,typename GroupPtr,bool Const>\nclass table_erase_return_type<table_iterator<TypePolicy,GroupPtr,Const>>\n{\n  using iterator=table_iterator<TypePolicy,GroupPtr,Const>;\n  using const_iterator=table_iterator<TypePolicy,GroupPtr,true>;\n\npublic:\n  /* can't delete it because VS in pre-C++17 mode needs to see it for RVO */\n  table_erase_return_type(const table_erase_return_type&);\n\n  operator iterator()const noexcept\n  {\n    auto it=pos;\n    it.increment();\n    return iterator(const_iterator_cast_tag{},it);\n  }\n\n  template<\n    bool dependent_value=false,\n    typename std::enable_if<!Const||dependent_value>::type* =nullptr\n  >\n  operator const_iterator()const noexcept{return this->operator iterator();}\n\nprivate:\n  template<typename,typename,typename,typename> friend class table;\n\n  table_erase_return_type(const_iterator pos_):pos{pos_}{}\n  table_erase_return_type& operator=(const table_erase_return_type&)=delete;\n\n  const_iterator pos;\n};\n\n/* foa::table interface departs in a number of ways from that of C++ unordered\n * associative containers because it's not for end-user consumption\n * (boost::unordered_(flat|node)_(map|set) wrappers complete it as\n * appropriate).\n *\n * The table supports two main modes of operation: flat and node-based. In the\n * flat case, buckets directly store elements. For node-based, buckets store\n * pointers to individually heap-allocated elements.\n *\n * For both flat and node-based:\n *\n *   - begin() is not O(1).\n *   - No bucket API.\n *   - Load factor is fixed and can't be set by the user.\n * \n * For flat only:\n *\n *   - value_type must be moveable.\n *   - Pointer stability is not kept under rehashing.\n *   - No extract API.\n *\n * try_emplace, erase and find support heterogeneous lookup by default,\n * that is, without checking for any ::is_transparent typedefs --the\n * checking is done by boost::unordered_(flat|node)_(map|set).\n */\n\ntemplate<typename,typename,typename,typename>\nclass concurrent_table;\n\ntemplate <typename TypePolicy,typename Hash,typename Pred,typename Allocator>\nusing table_core_impl=\n  table_core<TypePolicy,group15<plain_integral>,table_arrays,\n  plain_size_control,Hash,Pred,Allocator>;\n\n#ifdef BOOST_MSVC\n#pragma warning(push)\n#pragma warning(disable:4714)\n#endif\n\ntemplate<typename TypePolicy,typename Hash,typename Pred,typename Allocator>\nclass table:table_core_impl<TypePolicy,Hash,Pred,Allocator>\n{\n  using super=table_core_impl<TypePolicy,Hash,Pred,Allocator>;\n  using type_policy=typename super::type_policy;\n  using group_type=typename super::group_type;\n  using super::N;\n  using prober=typename super::prober;\n  using arrays_type=typename super::arrays_type;\n  using size_ctrl_type=typename super::size_ctrl_type;\n  using locator=typename super::locator;\n  using compatible_concurrent_table=\n    concurrent_table<TypePolicy,Hash,Pred,Allocator>;\n  using group_type_pointer=typename std::pointer_traits<\n    typename std::allocator_traits<Allocator>::pointer\n  >::template rebind<group_type>;\n  friend compatible_concurrent_table;\n\npublic:\n  using key_type=typename super::key_type;\n  using init_type=typename super::init_type;\n  using value_type=typename super::value_type;\n  using element_type=typename super::element_type;\n\nprivate:\n  static constexpr bool has_mutable_iterator=\n    !std::is_same<key_type,value_type>::value;\npublic:\n  using hasher=typename super::hasher;\n  using key_equal=typename super::key_equal;\n  using allocator_type=typename super::allocator_type;\n  using pointer=typename super::pointer;\n  using const_pointer=typename super::const_pointer;\n  using reference=typename super::reference;\n  using const_reference=typename super::const_reference;\n  using size_type=typename super::size_type;\n  using difference_type=typename super::difference_type;\n  using const_iterator=table_iterator<type_policy,group_type_pointer,true>;\n  using iterator=typename std::conditional<\n    has_mutable_iterator,\n    table_iterator<type_policy,group_type_pointer,false>,\n    const_iterator>::type;\n  using erase_return_type=table_erase_return_type<iterator>;\n\n  table(\n    std::size_t n=default_bucket_count,const Hash& h_=Hash(),\n    const Pred& pred_=Pred(),const Allocator& al_=Allocator()):\n    super{n,h_,pred_,al_}\n    {}\n\n  table(const table& x)=default;\n  table(table&& x)=default;\n  table(const table& x,const Allocator& al_):super{x,al_}{}\n  table(table&& x,const Allocator& al_):super{std::move(x),al_}{}\n  table(compatible_concurrent_table&& x):\n    table(std::move(x),x.exclusive_access()){}\n  ~table()=default;\n\n  table& operator=(const table& x)=default;\n  table& operator=(table&& x)=default;\n\n  using super::get_allocator;\n\n  iterator begin()noexcept\n  {\n    iterator it{this->arrays.groups(),0,this->arrays.elements()};\n    if(this->arrays.elements()&&\n       !(this->arrays.groups()[0].match_occupied()&0x1))++it;\n    return it;\n  }\n\n  const_iterator begin()const noexcept\n                   {return const_cast<table*>(this)->begin();}\n  iterator       end()noexcept{return {};}\n  const_iterator end()const noexcept{return const_cast<table*>(this)->end();}\n  const_iterator cbegin()const noexcept{return begin();}\n  const_iterator cend()const noexcept{return end();}\n\n  using super::empty;\n  using super::size;\n  using super::max_size;\n\n  template<typename... Args>\n  BOOST_FORCEINLINE std::pair<iterator,bool> emplace(Args&&... args)\n  {\n    alloc_cted_insert_type<type_policy,Allocator,Args...> x(\n      this->al(),std::forward<Args>(args)...);\n    return emplace_impl(type_policy::move(x.value()));\n  }\n\n  /* Optimization for value_type and init_type, to avoid constructing twice */\n  template <typename T>\n  BOOST_FORCEINLINE typename std::enable_if<\n    detail::is_similar_to_any<T, value_type, init_type>::value,\n    std::pair<iterator, bool> >::type\n  emplace(T&& x)\n  {\n    return emplace_impl(std::forward<T>(x));\n  }\n\n  /* Optimizations for maps for (k,v) to avoid eagerly constructing value */\n  template <typename K, typename V>\n  BOOST_FORCEINLINE\n    typename std::enable_if<is_emplace_kv_able<table, K>::value,\n      std::pair<iterator, bool> >::type\n    emplace(K&& k, V&& v)\n  {\n    alloc_cted_or_fwded_key_type<type_policy, Allocator, K&&> x(\n      this->al(), std::forward<K>(k));\n    return emplace_impl(\n      try_emplace_args_t{}, x.move_or_fwd(), std::forward<V>(v));\n  }\n\n  template<typename Key,typename... Args>\n  BOOST_FORCEINLINE std::pair<iterator,bool> try_emplace(\n    Key&& x,Args&&... args)\n  {\n    return emplace_impl(\n      try_emplace_args_t{},std::forward<Key>(x),std::forward<Args>(args)...);\n  }\n  \n// Lynx Add Begin\n  BOOST_FORCEINLINE std::pair<iterator, bool> try_emplace_plain_bytes_key(\n    key_type const& key)\n  {\n    return emplace_impl(\n      try_emplace_plain_bytes_key_t{},key);\n  }\n// Lynx Add End\n\n  BOOST_FORCEINLINE std::pair<iterator,bool>\n  insert(const init_type& x){return emplace_impl(x);}\n\n  BOOST_FORCEINLINE std::pair<iterator,bool>\n  insert(init_type&& x){return emplace_impl(std::move(x));}\n\n  /* template<typename=void> tilts call ambiguities in favor of init_type */\n\n  template<typename=void>\n  BOOST_FORCEINLINE std::pair<iterator,bool>\n  insert(const value_type& x){return emplace_impl(x);}\n\n  template<typename=void>\n  BOOST_FORCEINLINE std::pair<iterator,bool>\n  insert(value_type&& x){return emplace_impl(std::move(x));}\n\n  template<typename T=element_type>\n  BOOST_FORCEINLINE\n  typename std::enable_if<\n    !std::is_same<T,value_type>::value,\n    std::pair<iterator,bool>\n  >::type\n  insert(element_type&& x){return emplace_impl(std::move(x));}\n\n  template<\n    bool dependent_value=false,\n    typename std::enable_if<\n      has_mutable_iterator||dependent_value>::type* =nullptr\n  >\n  erase_return_type erase(iterator pos)noexcept\n  {return erase(const_iterator(pos));}\n\n  BOOST_FORCEINLINE\n  erase_return_type erase(const_iterator pos)noexcept\n  {\n    super::erase(pos.pc(),pos.p());\n    return {pos};\n  }\n\n  template<typename Key>\n  BOOST_FORCEINLINE\n  auto erase(Key&& x) -> typename std::enable_if<\n    !std::is_convertible<Key,iterator>::value&&\n    !std::is_convertible<Key,const_iterator>::value, std::size_t>::type\n  {\n    auto it=find(x);\n    if(it!=end()){\n      erase(it);\n      return 1;\n    }\n    else return 0;\n  }\n\n  void swap(table& x)\n    noexcept(noexcept(std::declval<super&>().swap(std::declval<super&>())))\n  {\n    super::swap(x);\n  }\n\n  using super::clear;\n\n  element_type extract(const_iterator pos)\n  {\n    BOOST_ASSERT(pos!=end());\n    erase_on_exit e{*this,pos};\n    (void)e;\n    return std::move(*pos.p());\n  }\n\n  // TODO: should we accept different allocator too?\n  template<typename Hash2,typename Pred2>\n  void merge(table<TypePolicy,Hash2,Pred2,Allocator>& x)\n  {\n    x.for_all_elements([&,this](group_type* pg,unsigned int n,element_type* p){\n      erase_on_exit e{x,{pg,n,p}};\n      if(!emplace_impl(type_policy::move(*p)).second)e.rollback();\n    });\n  }\n\n  template<typename Hash2,typename Pred2>\n  void merge(table<TypePolicy,Hash2,Pred2,Allocator>&& x){merge(x);}\n\n  using super::hash_function;\n  using super::key_eq;\n\n  template<typename Key>\n  BOOST_FORCEINLINE iterator find(const Key& x)\n  {\n    return make_iterator(super::find(x));\n  }\n\n  template<typename Key>\n  BOOST_FORCEINLINE const_iterator find(const Key& x)const\n  {\n    return const_cast<table*>(this)->find(x);\n  }\n\n  using super::capacity;\n  using super::load_factor;\n  using super::max_load_factor;\n  using super::max_load;\n  using super::rehash;\n  using super::reserve;\n\n  template<typename Predicate>\n  friend std::size_t erase_if(table& x,Predicate& pr)\n  {\n    using value_reference=typename std::conditional<\n      std::is_same<key_type,value_type>::value,\n      const_reference,\n      reference\n    >::type;\n\n    std::size_t s=x.size();\n    x.for_all_elements(\n      [&](group_type* pg,unsigned int n,element_type* p){\n        if(pr(const_cast<value_reference>(type_policy::value_from(*p)))){\n          x.super::erase(pg,n,p);\n        }\n      });\n    return std::size_t(s-x.size());\n  }\n\n  friend bool operator==(const table& x,const table& y)\n  {\n    return static_cast<const super&>(x)==static_cast<const super&>(y);\n  }\n\n  friend bool operator!=(const table& x,const table& y){return !(x==y);}\n\nprivate:\n  template<typename ArraysType>\n  table(compatible_concurrent_table&& x,arrays_holder<ArraysType,Allocator>&& ah):\n    super{\n      std::move(x.h()),std::move(x.pred()),std::move(x.al()),\n      [&x]{return arrays_type{\n        x.arrays.groups_size_index,x.arrays.groups_size_mask,\n        to_pointer<group_type_pointer>(\n          reinterpret_cast<group_type*>(x.arrays.groups())),\n        x.arrays.elements_};},\n      size_ctrl_type{x.size_ctrl.ml,x.size_ctrl.size}}\n  {\n    compatible_concurrent_table::arrays_type::delete_group_access(x.al(),x.arrays);\n    x.arrays=ah.release();\n    x.size_ctrl.ml=x.initial_max_load();\n    x.size_ctrl.size=0;\n  }\n\n  template<typename ExclusiveLockGuard>\n  table(compatible_concurrent_table&& x,ExclusiveLockGuard):\n    table(std::move(x),x.make_empty_arrays())\n  {}\n\n  struct erase_on_exit\n  {\n    erase_on_exit(table& x_,const_iterator it_):x(x_),it(it_){}\n    ~erase_on_exit(){if(!rollback_)x.erase(it);}\n\n    void rollback(){rollback_=true;}\n\n    table&         x;\n    const_iterator it;\n    bool           rollback_=false;\n  };\n\n  static inline iterator make_iterator(const locator& l)noexcept\n  {\n    return {l.pg,l.n,l.p};\n  }\n\n  template<typename... Args>\n  BOOST_FORCEINLINE std::pair<iterator,bool> emplace_impl(Args&&... args)\n  {\n    const auto &k=this->key_from(std::forward<Args>(args)...);\n    auto        hash=this->hash_for(k);\n    auto        pos0=this->position_for(hash);\n    auto        loc=super::find(k,pos0,hash);\n\n    if(loc){\n      return {make_iterator(loc),false};\n    }\n    if(BOOST_LIKELY(this->size_ctrl.size<this->size_ctrl.ml)){\n      return {\n        make_iterator(\n          this->unchecked_emplace_at(pos0,hash,std::forward<Args>(args)...)),\n        true\n      };  \n    }\n    else{\n      return {\n        make_iterator(\n          this->unchecked_emplace_with_rehash(\n            hash,std::forward<Args>(args)...)),\n        true\n      };  \n    }\n  }\n};\n\n#ifdef BOOST_MSVC\n#pragma warning(pop)\n#endif\n\n}\n}\n}\n}\n\n#endif\n// Copyright (C) 2022 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_FLAT_SET_FWD_HPP_INCLUDED\n#define BOOST_UNORDERED_FLAT_SET_FWD_HPP_INCLUDED\n\n#pragma once\n\nnamespace boost {\n  namespace unordered {\n    template <class Key, class Hash = boost::hash<Key>,\n      class KeyEqual = std::equal_to<Key>,\n      class Allocator = std::allocator<Key> >\n    class unordered_flat_set;\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    void swap(unordered_flat_set<Key, Hash, KeyEqual, Allocator>& lhs,\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator>& rhs)\n      noexcept(noexcept(lhs.swap(rhs)));\n  } // namespace unordered\n\n  using boost::unordered::unordered_flat_set;\n} // namespace boost\n\n#endif\n// Copyright 2017 Peter Dimov.\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_HASH_IS_RANGE_HPP_INCLUDED\n#define BOOST_HASH_IS_RANGE_HPP_INCLUDED\n\nnamespace boost\n{\n\nnamespace hash_detail\n{\n\ntemplate<class T> struct iterator_traits: std::iterator_traits<T> {};\ntemplate<> struct iterator_traits< void* > {};\ntemplate<> struct iterator_traits< void const* > {};\n\ntemplate<class T, class It>\n    std::integral_constant< bool, !std::is_same<typename std::remove_cv<T>::type, typename iterator_traits<It>::value_type>::value >\n        is_range_check( It first, It last );\n\ntemplate<class T> decltype( is_range_check<T>( std::declval<T const&>().begin(), std::declval<T const&>().end() ) ) is_range_( int );\ntemplate<class T> std::false_type is_range_( ... );\n\n} // namespace hash_detail\n\nnamespace container_hash\n{\n\ntemplate<class T> struct is_range: decltype( hash_detail::is_range_<T>( 0 ) )\n{\n};\n\n} // namespace container_hash\n\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_IS_RANGE_HPP_INCLUDED\n// Copyright 2017, 2018 Peter Dimov.\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_HASH_IS_CONTIGUOUS_RANGE_HPP_INCLUDED\n#define BOOST_HASH_IS_CONTIGUOUS_RANGE_HPP_INCLUDED\n\nnamespace boost\n{\nnamespace hash_detail\n{\n\ntemplate<class It, class T, class S>\n    std::integral_constant< bool, std::is_same<typename std::iterator_traits<It>::value_type, T>::value && std::is_integral<S>::value >\n        is_contiguous_range_check( It first, It last, T const*, T const*, S );\n\ntemplate<class T> decltype( is_contiguous_range_check( std::declval<T const&>().begin(), std::declval<T const&>().end(), std::declval<T const&>().data(), std::declval<T const&>().data() + std::declval<T const&>().size(), std::declval<T const&>().size() ) ) is_contiguous_range_( int );\ntemplate<class T> std::false_type is_contiguous_range_( ... );\n\ntemplate<class T> struct is_contiguous_range: decltype( hash_detail::is_contiguous_range_<T>( 0 ) )\n{\n};\n\n} // namespace hash_detail\n\nnamespace container_hash\n{\n\ntemplate<class T> struct is_contiguous_range: std::integral_constant< bool, is_range<T>::value && hash_detail::is_contiguous_range<T>::value >\n{\n};\n\n} // namespace container_hash\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_IS_CONTIGUOUS_RANGE_HPP_INCLUDED\n// Copyright 2017 Peter Dimov.\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_HASH_IS_UNORDERED_RANGE_HPP_INCLUDED\n#define BOOST_HASH_IS_UNORDERED_RANGE_HPP_INCLUDED\n\nnamespace boost\n{\nnamespace hash_detail\n{\n\ntemplate<class T, class E = std::true_type> struct has_hasher_: std::false_type\n{\n};\n\ntemplate<class T> struct has_hasher_< T, std::integral_constant< bool,\n        std::is_same<typename T::hasher, typename T::hasher>::value\n    > >: std::true_type\n{\n};\n\n} // namespace hash_detail\n\nnamespace container_hash\n{\n\ntemplate<class T> struct is_unordered_range: std::integral_constant< bool, is_range<T>::value && hash_detail::has_hasher_<T>::value >\n{\n};\n\n} // namespace container_hash\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_IS_UNORDERED_RANGE_HPP_INCLUDED\n// Copyright 2022 Peter Dimov.\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_HASH_IS_DESCRIBED_CLASS_HPP_INCLUDED\n#define BOOST_HASH_IS_DESCRIBED_CLASS_HPP_INCLUDED\n\nnamespace boost\n{\nnamespace container_hash\n{\n\n#ifdef BOOST_DESCRIBE_CXX11\n\ntemplate<class T> struct is_described_class: std::integral_constant<bool,\n    describe::has_describe_bases<T>::value &&\n    describe::has_describe_members<T>::value &&\n    !std::is_union<T>::value>\n{\n};\n\n#else\n\ntemplate<class T> struct is_described_class: std::false_type\n{\n};\n\n#endif\n\n} // namespace container_hash\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_IS_DESCRIBED_CLASS_HPP_INCLUDED\n// Copyright 2022 Peter Dimov\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_HASH_DETAIL_HASH_MIX_HPP\n#define BOOST_HASH_DETAIL_HASH_MIX_HPP\n\nnamespace boost\n{\nnamespace hash_detail\n{\n\ntemplate<std::size_t Bits> struct hash_mix_impl;\n\n// hash_mix for 64 bit size_t\n//\n// The general \"xmxmx\" form of state of the art 64 bit mixers originates\n// from Murmur3 by Austin Appleby, which uses the following function as\n// its \"final mix\":\n//\n//\tk ^= k >> 33;\n//\tk *= 0xff51afd7ed558ccd;\n//\tk ^= k >> 33;\n//\tk *= 0xc4ceb9fe1a85ec53;\n//\tk ^= k >> 33;\n//\n// (https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp)\n//\n// It has subsequently been improved multiple times by different authors\n// by changing the constants. The most well known improvement is the\n// so-called \"variant 13\" function by David Stafford:\n//\n//\tk ^= k >> 30;\n//\tk *= 0xbf58476d1ce4e5b9;\n//\tk ^= k >> 27;\n//\tk *= 0x94d049bb133111eb;\n//\tk ^= k >> 31;\n//\n// (https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html)\n//\n// This mixing function is used in the splitmix64 RNG:\n// http://xorshift.di.unimi.it/splitmix64.c\n//\n// We use Jon Maiga's implementation from\n// http://jonkagstrom.com/mx3/mx3_rev2.html\n//\n// \tx ^= x >> 32;\n//\tx *= 0xe9846af9b1a615d;\n//\tx ^= x >> 32;\n//\tx *= 0xe9846af9b1a615d;\n//\tx ^= x >> 28;\n//\n// An equally good alternative is Pelle Evensen's Moremur:\n//\n//\tx ^= x >> 27;\n//\tx *= 0x3C79AC492BA7B653;\n//\tx ^= x >> 33;\n//\tx *= 0x1C69B3F74AC4AE35;\n//\tx ^= x >> 27;\n//\n// (https://mostlymangling.blogspot.com/2019/12/stronger-better-morer-moremur-better.html)\n\ntemplate<> struct hash_mix_impl<64>\n{\n    inline static std::uint64_t fn( std::uint64_t x )\n    {\n        std::uint64_t const m = 0xe9846af9b1a615d;\n\n        x ^= x >> 32;\n        x *= m;\n        x ^= x >> 32;\n        x *= m;\n        x ^= x >> 28;\n\n        return x;\n    }\n};\n\n// hash_mix for 32 bit size_t\n//\n// We use the \"best xmxmx\" implementation from\n// https://github.com/skeeto/hash-prospector/issues/19\n\ntemplate<> struct hash_mix_impl<32>\n{\n    inline static std::uint32_t fn( std::uint32_t x )\n    {\n        std::uint32_t const m1 = 0x21f0aaad;\n        std::uint32_t const m2 = 0x735a2d97;\n\n        x ^= x >> 16;\n        x *= m1;\n        x ^= x >> 15;\n        x *= m2;\n        x ^= x >> 15;\n\n        return x;\n    }\n};\n\ninline std::size_t hash_mix( std::size_t v )\n{\n    return hash_mix_impl<sizeof(std::size_t) * CHAR_BIT>::fn( v );\n}\n\n} // namespace hash_detail\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_DETAIL_HASH_MIX_HPP\n// Copyright 2021-2023 Peter Dimov\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_HASH_DETAIL_HASH_INTEGRAL_HPP\n#define BOOST_HASH_DETAIL_HASH_INTEGRAL_HPP\n\nnamespace boost\n{\nnamespace hash_detail\n{\n\n// libstdc++ doesn't provide support for __int128 in the standard traits\n\ntemplate<class T> struct is_integral: public std::is_integral<T>\n{\n};\n\ntemplate<class T> struct is_unsigned: public std::is_unsigned<T>\n{\n};\n\ntemplate<class T> struct make_unsigned: public std::make_unsigned<T>\n{\n};\n\n#ifdef __SIZEOF_INT128__\n\ntemplate<> struct is_integral<__int128_t>: public std::true_type\n{\n};\n\ntemplate<> struct is_integral<__uint128_t>: public std::true_type\n{\n};\n\ntemplate<> struct is_unsigned<__int128_t>: public std::false_type\n{\n};\n\ntemplate<> struct is_unsigned<__uint128_t>: public std::true_type\n{\n};\n\ntemplate<> struct make_unsigned<__int128_t>\n{\n    typedef __uint128_t type;\n};\n\ntemplate<> struct make_unsigned<__uint128_t>\n{\n    typedef __uint128_t type;\n};\n\n#endif\n\ntemplate<class T,\n    bool bigger_than_size_t = (sizeof(T) > sizeof(std::size_t)),\n    bool is_unsigned = is_unsigned<T>::value,\n    std::size_t size_t_bits = sizeof(std::size_t) * CHAR_BIT,\n    std::size_t type_bits = sizeof(T) * CHAR_BIT>\nstruct hash_integral_impl;\n\ntemplate<class T, bool is_unsigned, std::size_t size_t_bits, std::size_t type_bits> struct hash_integral_impl<T, false, is_unsigned, size_t_bits, type_bits>\n{\n    static std::size_t fn( T v )\n    {\n        return static_cast<std::size_t>( v );\n    }\n};\n\ntemplate<class T, std::size_t size_t_bits, std::size_t type_bits> struct hash_integral_impl<T, true, false, size_t_bits, type_bits>\n{\n    static std::size_t fn( T v )\n    {\n        typedef typename make_unsigned<T>::type U;\n\n        if( v >= 0 )\n        {\n            return hash_integral_impl<U>::fn( static_cast<U>( v ) );\n        }\n        else\n        {\n            return ~hash_integral_impl<U>::fn( static_cast<U>( ~static_cast<U>( v ) ) );\n        }\n    }\n};\n\ntemplate<class T> struct hash_integral_impl<T, true, true, 32, 64>\n{\n    static std::size_t fn( T v )\n    {\n        std::size_t seed = 0;\n\n        seed = static_cast<std::size_t>( v >> 32 ) + hash_detail::hash_mix( seed );\n        seed = static_cast<std::size_t>( v  & 0xFFFFFFFF ) + hash_detail::hash_mix( seed );\n\n        return seed;\n    }\n};\n\ntemplate<class T> struct hash_integral_impl<T, true, true, 32, 128>\n{\n    static std::size_t fn( T v )\n    {\n        std::size_t seed = 0;\n\n        seed = static_cast<std::size_t>( v >> 96 ) + hash_detail::hash_mix( seed );\n        seed = static_cast<std::size_t>( v >> 64 ) + hash_detail::hash_mix( seed );\n        seed = static_cast<std::size_t>( v >> 32 ) + hash_detail::hash_mix( seed );\n        seed = static_cast<std::size_t>( v ) + hash_detail::hash_mix( seed );\n\n        return seed;\n    }\n};\n\ntemplate<class T> struct hash_integral_impl<T, true, true, 64, 128>\n{\n    static std::size_t fn( T v )\n    {\n        std::size_t seed = 0;\n\n        seed = static_cast<std::size_t>( v >> 64 ) + hash_detail::hash_mix( seed );\n        seed = static_cast<std::size_t>( v ) + hash_detail::hash_mix( seed );\n\n        return seed;\n    }\n};\n\n} // namespace hash_detail\n\ntemplate <typename T>\ntypename std::enable_if<hash_detail::is_integral<T>::value, std::size_t>::type\n    hash_value( T v )\n{\n    return hash_detail::hash_integral_impl<T>::fn( v );\n}\n\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_DETAIL_HASH_INTEGRAL_HPP\n#ifndef BOOST_HASH_IS_TUPLE_LIKE_HPP_INCLUDED\n#define BOOST_HASH_IS_TUPLE_LIKE_HPP_INCLUDED\n\n// Copyright 2017, 2022 Peter Dimov.\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\nnamespace boost\n{\nnamespace hash_detail\n{\n\ntemplate<class T, class E = std::true_type> struct is_tuple_like_: std::false_type\n{\n};\n\ntemplate<class T> struct is_tuple_like_<T, std::integral_constant<bool, std::tuple_size<T>::value == std::tuple_size<T>::value> >: std::true_type\n{\n};\n\n} // namespace hash_detail\n\nnamespace container_hash\n{\n\ntemplate<class T> struct is_tuple_like: hash_detail::is_tuple_like_< typename std::remove_cv<T>::type >\n{\n};\n\n} // namespace container_hash\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_IS_TUPLE_LIKE_HPP_INCLUDED\n// Copyright 2005-2009 Daniel James.\n// Copyright 2021 Peter Dimov.\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_HASH_DETAIL_HASH_TUPLE_LIKE_HPP\n#define BOOST_HASH_DETAIL_HASH_TUPLE_LIKE_HPP\n\nnamespace boost\n{\nnamespace hash_detail\n{\n\ntemplate <std::size_t I, typename T>\ninline\ntypename std::enable_if<(I == std::tuple_size<T>::value), void>::type\n    hash_combine_tuple_like( std::size_t&, T const& )\n{\n}\n\ntemplate <std::size_t I, typename T>\ninline\ntypename std::enable_if<(I < std::tuple_size<T>::value), void>::type\n    hash_combine_tuple_like( std::size_t& seed, T const& v )\n{\n    using std::get;\n    boost::hash_combine( seed, get<I>( v ) );\n\n    boost::hash_detail::hash_combine_tuple_like<I + 1>( seed, v );\n}\n\ntemplate <typename T>\ninline std::size_t hash_tuple_like( T const& v )\n{\n    std::size_t seed = 0;\n\n    boost::hash_detail::hash_combine_tuple_like<0>( seed, v );\n\n    return seed;\n}\n\n} // namespace hash_detail\n\ntemplate <class T>\ninline\ntypename std::enable_if<\n    container_hash::is_tuple_like<T>::value && !container_hash::is_range<T>::value,\nstd::size_t>::type\n    hash_value( T const& v )\n{\n    return boost::hash_detail::hash_tuple_like( v );\n}\n\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_DETAIL_HASH_TUPLE_LIKE_HPP\n// Copyright 2022, 2023 Peter Dimov\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_HASH_DETAIL_MULX_HPP\n#define BOOST_HASH_DETAIL_MULX_HPP\n\n#ifdef _MSC_VER\n# include <intrin.h>\n#endif\n\nnamespace boost\n{\nnamespace hash_detail\n{\n\n#if defined(_MSC_VER) && defined(_M_X64) && !defined(__clang__)\n\n__forceinline std::uint64_t mulx( std::uint64_t x, std::uint64_t y )\n{\n    std::uint64_t r2;\n    std::uint64_t r = _umul128( x, y, &r2 );\n    return r ^ r2;\n}\n\n#elif defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__)\n\n__forceinline std::uint64_t mulx( std::uint64_t x, std::uint64_t y )\n{\n    std::uint64_t r = x * y;\n    std::uint64_t r2 = __umulh( x, y );\n    return r ^ r2;\n}\n\n#elif defined(__SIZEOF_INT128__)\n\ninline std::uint64_t mulx( std::uint64_t x, std::uint64_t y )\n{\n    __uint128_t r = static_cast<__uint128_t>( x ) * y;\n    return static_cast<std::uint64_t>( r ) ^ static_cast<std::uint64_t>( r >> 64 );\n}\n\n#else\n\ninline std::uint64_t mulx( std::uint64_t x, std::uint64_t y )\n{\n    std::uint64_t x1 = static_cast<std::uint32_t>( x );\n    std::uint64_t x2 = x >> 32;\n\n    std::uint64_t y1 = static_cast<std::uint32_t>( y );\n    std::uint64_t y2 = y >> 32;\n\n    std::uint64_t r3 = x2 * y2;\n\n    std::uint64_t r2a = x1 * y2;\n\n    r3 += r2a >> 32;\n\n    std::uint64_t r2b = x2 * y1;\n\n    r3 += r2b >> 32;\n\n    std::uint64_t r1 = x1 * y1;\n\n    std::uint64_t r2 = (r1 >> 32) + static_cast<std::uint32_t>( r2a ) + static_cast<std::uint32_t>( r2b );\n\n    r1 = (r2 << 32) + static_cast<std::uint32_t>( r1 );\n    r3 += r2 >> 32;\n\n    return r1 ^ r3;\n}\n\n#endif\n\n} // namespace hash_detail\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_DETAIL_MULX_HPP\n// Copyright 2022 Peter Dimov\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n#ifndef BOOST_HASH_DETAIL_HASH_RANGE_HPP\n#define BOOST_HASH_DETAIL_HASH_RANGE_HPP\n\nnamespace boost\n{\nnamespace hash_detail\n{\n\ntemplate<class T> struct is_char_type: public std::false_type {};\n\n#if CHAR_BIT == 8\n\ntemplate<> struct is_char_type<char>: public std::true_type {};\ntemplate<> struct is_char_type<signed char>: public std::true_type {};\ntemplate<> struct is_char_type<unsigned char>: public std::true_type {};\n\n#if defined(__cpp_char8_t) && __cpp_char8_t >= 201811L\ntemplate<> struct is_char_type<char8_t>: public std::true_type {};\n#endif\n\n#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L\ntemplate<> struct is_char_type<std::byte>: public std::true_type {};\n#endif\n\n#endif\n\n// generic version\n\ntemplate<class It>\ninline typename std::enable_if<\n    !is_char_type<typename std::iterator_traits<It>::value_type>::value,\nstd::size_t >::type\n    hash_range( std::size_t seed, It first, It last )\n{\n    for( ; first != last; ++first )\n    {\n        hash_combine<typename std::iterator_traits<It>::value_type>( seed, *first );\n    }\n\n    return seed;\n}\n\n// specialized char[] version, 32 bit\n\ntemplate<class It> inline std::uint32_t read32le( It p )\n{\n    // clang 5+, gcc 5+ figure out this pattern and use a single mov on x86\n    // gcc on s390x and power BE even knows how to use load-reverse\n\n    std::uint32_t w =\n        static_cast<std::uint32_t>( static_cast<unsigned char>( p[0] ) ) |\n        static_cast<std::uint32_t>( static_cast<unsigned char>( p[1] ) ) <<  8 |\n        static_cast<std::uint32_t>( static_cast<unsigned char>( p[2] ) ) << 16 |\n        static_cast<std::uint32_t>( static_cast<unsigned char>( p[3] ) ) << 24;\n\n    return w;\n}\n\n#if defined(_MSC_VER) && !defined(__clang__)\n\ntemplate<class T> inline std::uint32_t read32le( T* p )\n{\n    std::uint32_t w;\n\n    std::memcpy( &w, p, 4 );\n    return w;\n}\n\n#endif\n\ninline std::uint64_t mul32( std::uint32_t x, std::uint32_t y )\n{\n    return static_cast<std::uint64_t>( x ) * y;\n}\n\ntemplate<class It>\ninline typename std::enable_if<\n    is_char_type<typename std::iterator_traits<It>::value_type>::value &&\n    std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value &&\n    std::numeric_limits<std::size_t>::digits <= 32,\nstd::size_t>::type\n    hash_range( std::size_t seed, It first, It last )\n{\n    It p = first;\n    std::size_t n = static_cast<std::size_t>( last - first );\n\n    std::uint32_t const q = 0x9e3779b9U;\n    std::uint32_t const k = 0xe35e67b1U; // q * q\n\n    std::uint64_t h = mul32( static_cast<std::uint32_t>( seed ) + q, k );\n    std::uint32_t w = static_cast<std::uint32_t>( h & 0xFFFFFFFF );\n\n    h ^= n;\n\n    while( n >= 4 )\n    {\n        std::uint32_t v1 = read32le( p );\n\n        w += q;\n        h ^= mul32( v1 + w, k );\n\n        p += 4;\n        n -= 4;\n    }\n\n    {\n        std::uint32_t v1 = 0;\n\n        if( n >= 1 )\n        {\n            std::size_t const x1 = ( n - 1 ) & 2; // 1: 0, 2: 0, 3: 2\n            std::size_t const x2 = n >> 1;        // 1: 0, 2: 1, 3: 1\n\n            v1 =\n                static_cast<std::uint32_t>( static_cast<unsigned char>( p[ static_cast<std::ptrdiff_t>( x1 ) ] ) ) << x1 * 8 |\n                static_cast<std::uint32_t>( static_cast<unsigned char>( p[ static_cast<std::ptrdiff_t>( x2 ) ] ) ) << x2 * 8 |\n                static_cast<std::uint32_t>( static_cast<unsigned char>( p[ 0 ] ) );\n        }\n\n        w += q;\n        h ^= mul32( v1 + w, k );\n    }\n\n    w += q;\n    h ^= mul32( static_cast<std::uint32_t>( h & 0xFFFFFFFF ) + w, static_cast<std::uint32_t>( h >> 32 ) + w + k );\n\n    return static_cast<std::uint32_t>( h & 0xFFFFFFFF ) ^ static_cast<std::uint32_t>( h >> 32 );\n}\n\ntemplate<class It>\ninline typename std::enable_if<\n    is_char_type<typename std::iterator_traits<It>::value_type>::value &&\n    !std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value &&\n    std::numeric_limits<std::size_t>::digits <= 32,\nstd::size_t>::type\n    hash_range( std::size_t seed, It first, It last )\n{\n    std::size_t n = 0;\n\n    std::uint32_t const q = 0x9e3779b9U;\n    std::uint32_t const k = 0xe35e67b1U; // q * q\n\n    std::uint64_t h = mul32( static_cast<std::uint32_t>( seed ) + q, k );\n    std::uint32_t w = static_cast<std::uint32_t>( h & 0xFFFFFFFF );\n\n    std::uint32_t v1 = 0;\n\n    for( ;; )\n    {\n        v1 = 0;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint32_t>( static_cast<unsigned char>( *first ) );\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint32_t>( static_cast<unsigned char>( *first ) ) << 8;\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint32_t>( static_cast<unsigned char>( *first ) ) << 16;\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint32_t>( static_cast<unsigned char>( *first ) ) << 24;\n        ++first;\n        ++n;\n\n        w += q;\n        h ^= mul32( v1 + w, k );\n    }\n\n    h ^= n;\n\n    w += q;\n    h ^= mul32( v1 + w, k );\n\n    w += q;\n    h ^= mul32( static_cast<std::uint32_t>( h & 0xFFFFFFFF ) + w, static_cast<std::uint32_t>( h >> 32 ) + w + k );\n\n    return static_cast<std::uint32_t>( h & 0xFFFFFFFF ) ^ static_cast<std::uint32_t>( h >> 32 );\n}\n\n// specialized char[] version, 64 bit\n\ntemplate<class It> inline std::uint64_t read64le( It p )\n{\n    std::uint64_t w =\n        static_cast<std::uint64_t>( static_cast<unsigned char>( p[0] ) ) |\n        static_cast<std::uint64_t>( static_cast<unsigned char>( p[1] ) ) <<  8 |\n        static_cast<std::uint64_t>( static_cast<unsigned char>( p[2] ) ) << 16 |\n        static_cast<std::uint64_t>( static_cast<unsigned char>( p[3] ) ) << 24 |\n        static_cast<std::uint64_t>( static_cast<unsigned char>( p[4] ) ) << 32 |\n        static_cast<std::uint64_t>( static_cast<unsigned char>( p[5] ) ) << 40 |\n        static_cast<std::uint64_t>( static_cast<unsigned char>( p[6] ) ) << 48 |\n        static_cast<std::uint64_t>( static_cast<unsigned char>( p[7] ) ) << 56;\n\n    return w;\n}\n\n#if defined(_MSC_VER) && !defined(__clang__)\n\ntemplate<class T> inline std::uint64_t read64le( T* p )\n{\n    std::uint64_t w;\n\n    std::memcpy( &w, p, 8 );\n    return w;\n}\n\n#endif\n\ntemplate<class It>\ninline typename std::enable_if<\n    is_char_type<typename std::iterator_traits<It>::value_type>::value &&\n    std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value &&\n    (std::numeric_limits<std::size_t>::digits > 32),\nstd::size_t>::type\n    hash_range( std::size_t seed, It first, It last )\n{\n    It p = first;\n    std::size_t n = static_cast<std::size_t>( last - first );\n\n    std::uint64_t const q = 0x9e3779b97f4a7c15;\n    std::uint64_t const k = 0xdf442d22ce4859b9; // q * q\n\n    std::uint64_t w = mulx( seed + q, k );\n    std::uint64_t h = w ^ n;\n\n    while( n >= 8 )\n    {\n        std::uint64_t v1 = read64le( p );\n\n        w += q;\n        h ^= mulx( v1 + w, k );\n\n        p += 8;\n        n -= 8;\n    }\n\n    {\n        std::uint64_t v1 = 0;\n\n        if( n >= 4 )\n        {\n            v1 = static_cast<std::uint64_t>( read32le( p + static_cast<std::ptrdiff_t>( n - 4 ) ) ) << ( n - 4 ) * 8 | read32le( p );\n        }\n        else if( n >= 1 )\n        {\n            std::size_t const x1 = ( n - 1 ) & 2; // 1: 0, 2: 0, 3: 2\n            std::size_t const x2 = n >> 1;        // 1: 0, 2: 1, 3: 1\n\n            v1 =\n                static_cast<std::uint64_t>( static_cast<unsigned char>( p[ static_cast<std::ptrdiff_t>( x1 ) ] ) ) << x1 * 8 |\n                static_cast<std::uint64_t>( static_cast<unsigned char>( p[ static_cast<std::ptrdiff_t>( x2 ) ] ) ) << x2 * 8 |\n                static_cast<std::uint64_t>( static_cast<unsigned char>( p[ 0 ] ) );\n        }\n\n        w += q;\n        h ^= mulx( v1 + w, k );\n    }\n\n    return mulx( h + w, k );\n}\n\ntemplate<class It>\ninline typename std::enable_if<\n    is_char_type<typename std::iterator_traits<It>::value_type>::value &&\n    !std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value &&\n    (std::numeric_limits<std::size_t>::digits > 32),\nstd::size_t>::type\n    hash_range( std::size_t seed, It first, It last )\n{\n    std::size_t n = 0;\n\n    std::uint64_t const q = 0x9e3779b97f4a7c15;\n    std::uint64_t const k = 0xdf442d22ce4859b9; // q * q\n\n    std::uint64_t w = mulx( seed + q, k );\n    std::uint64_t h = w;\n\n    std::uint64_t v1 = 0;\n\n    for( ;; )\n    {\n        v1 = 0;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint64_t>( static_cast<unsigned char>( *first ) );\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint64_t>( static_cast<unsigned char>( *first ) ) << 8;\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint64_t>( static_cast<unsigned char>( *first ) ) << 16;\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint64_t>( static_cast<unsigned char>( *first ) ) << 24;\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint64_t>( static_cast<unsigned char>( *first ) ) << 32;\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint64_t>( static_cast<unsigned char>( *first ) ) << 40;\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint64_t>( static_cast<unsigned char>( *first ) ) << 48;\n        ++first;\n        ++n;\n\n        if( first == last )\n        {\n            break;\n        }\n\n        v1 |= static_cast<std::uint64_t>( static_cast<unsigned char>( *first ) ) << 56;\n        ++first;\n        ++n;\n\n        w += q;\n        h ^= mulx( v1 + w, k );\n    }\n\n    h ^= n;\n\n    w += q;\n    h ^= mulx( v1 + w, k );\n\n    return mulx( h + w, k );\n}\n\n} // namespace hash_detail\n} // namespace boost\n\n#endif // #ifndef BOOST_HASH_DETAIL_HASH_RANGE_HPP\n// Copyright 2005-2014 Daniel James.\n// Copyright 2021, 2022 Peter Dimov.\n// Distributed under the Boost Software License, Version 1.0.\n// https://www.boost.org/LICENSE_1_0.txt\n\n// Based on Peter Dimov's proposal\n// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf\n// issue 6.18.\n\n#ifndef BOOST_FUNCTIONAL_HASH_HASH_HPP\n#define BOOST_FUNCTIONAL_HASH_HASH_HPP\n\n#ifdef BOOST_DESCRIBE_CXX14\n# include <boost/mp11/algorithm.hpp>\n#endif\n\n#ifndef BOOST_NO_CXX11_SMART_PTR\n# include <memory>\n#endif\n\n#ifndef BOOST_NO_CXX17_HDR_STRING_VIEW\n# include <string_view>\n#endif\n\nnamespace boost\n{\n\n    //\n    // boost::hash_value\n    //\n\n    // integral types\n    //   in detail/hash_integral.hpp\n\n    // enumeration types\n\n    template <typename T>\n    typename std::enable_if<std::is_enum<T>::value, std::size_t>::type\n        hash_value( T v )\n    {\n        // This should in principle return the equivalent of\n        //\n        // boost::hash_value( to_underlying(v) );\n        //\n        // However, the C++03 implementation of underlying_type,\n        //\n        // conditional<is_signed<T>, make_signed<T>, make_unsigned<T>>::type::type\n        //\n        // generates a legitimate -Wconversion warning in is_signed,\n        // because -1 is not a valid enum value when all the enumerators\n        // are nonnegative.\n        //\n        // So the legacy implementation will have to do for now.\n\n        return static_cast<std::size_t>( v );\n    }\n\n    // floating point types\n\n    namespace hash_detail\n    {\n        template<class T,\n            std::size_t Bits = sizeof(T) * CHAR_BIT,\n            int Digits = std::numeric_limits<T>::digits>\n        struct hash_float_impl;\n\n        // float\n        template<class T, int Digits> struct hash_float_impl<T, 32, Digits>\n        {\n            static std::size_t fn( T v )\n            {\n                std::uint32_t w;\n                std::memcpy( &w, &v, sizeof( v ) );\n\n                return w;\n            }\n        };\n\n        // double\n        template<class T, int Digits> struct hash_float_impl<T, 64, Digits>\n        {\n            static std::size_t fn( T v )\n            {\n                std::uint64_t w;\n                std::memcpy( &w, &v, sizeof( v ) );\n\n                return hash_value( w );\n            }\n        };\n\n        // 80 bit long double in 12 bytes\n        template<class T> struct hash_float_impl<T, 96, 64>\n        {\n            static std::size_t fn( T v )\n            {\n                std::uint64_t w[ 2 ] = {};\n                std::memcpy( &w, &v, 80 / CHAR_BIT );\n\n                std::size_t seed = 0;\n\n                seed = hash_value( w[0] ) + hash_detail::hash_mix( seed );\n                seed = hash_value( w[1] ) + hash_detail::hash_mix( seed );\n\n                return seed;\n            }\n        };\n\n        // 80 bit long double in 16 bytes\n        template<class T> struct hash_float_impl<T, 128, 64>\n        {\n            static std::size_t fn( T v )\n            {\n                std::uint64_t w[ 2 ] = {};\n                std::memcpy( &w, &v, 80 / CHAR_BIT );\n\n                std::size_t seed = 0;\n\n                seed = hash_value( w[0] ) + hash_detail::hash_mix( seed );\n                seed = hash_value( w[1] ) + hash_detail::hash_mix( seed );\n\n                return seed;\n            }\n        };\n\n        // 128 bit long double\n        template<class T, int Digits> struct hash_float_impl<T, 128, Digits>\n        {\n            static std::size_t fn( T v )\n            {\n                std::uint64_t w[ 2 ];\n                std::memcpy( &w, &v, sizeof( v ) );\n\n                std::size_t seed = 0;\n\n#if defined(__FLOAT_WORD_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__\n\n                seed = hash_value( w[1] ) + hash_detail::hash_mix( seed );\n                seed = hash_value( w[0] ) + hash_detail::hash_mix( seed );\n\n#else\n\n                seed = hash_value( w[0] ) + hash_detail::hash_mix( seed );\n                seed = hash_value( w[1] ) + hash_detail::hash_mix( seed );\n\n#endif\n                return seed;\n            }\n        };\n\n    } // namespace hash_detail\n\n    template <typename T>\n    typename std::enable_if<std::is_floating_point<T>::value, std::size_t>::type\n        hash_value( T v )\n    {\n        return boost::hash_detail::hash_float_impl<T>::fn( v + 0 );\n    }\n\n    // pointer types\n\n    // `x + (x >> 3)` adjustment by Alberto Barbati and Dave Harris.\n    template <class T> std::size_t hash_value( T* const& v )\n    {\n        std::uintptr_t x = reinterpret_cast<std::uintptr_t>( v );\n        return boost::hash_value( x + (x >> 3) );\n    }\n\n    // array types\n\n    template<class T, std::size_t N>\n    inline std::size_t hash_value( T const (&x)[ N ] )\n    {\n        return boost::hash_range( x, x + N );\n    }\n\n    template<class T, std::size_t N>\n    inline std::size_t hash_value( T (&x)[ N ] )\n    {\n        return boost::hash_range( x, x + N );\n    }\n\n    // complex\n\n    template <class T>\n    std::size_t hash_value( std::complex<T> const& v )\n    {\n        std::size_t re = boost::hash<T>()( v.real() );\n        std::size_t im = boost::hash<T>()( v.imag() );\n\n        return re + hash_detail::hash_mix( im );\n    }\n\n    // pair\n\n    template <class A, class B>\n    std::size_t hash_value( std::pair<A, B> const& v )\n    {\n        std::size_t seed = 0;\n\n        boost::hash_combine( seed, v.first );\n        boost::hash_combine( seed, v.second );\n\n        return seed;\n    }\n\n    // ranges (list, set, deque...)\n\n    template <typename T>\n    typename std::enable_if<container_hash::is_range<T>::value && !container_hash::is_contiguous_range<T>::value && !container_hash::is_unordered_range<T>::value, std::size_t>::type\n        hash_value( T const& v )\n    {\n        return boost::hash_range( v.begin(), v.end() );\n    }\n\n    // contiguous ranges (string, vector, array)\n\n    template <typename T>\n    typename std::enable_if<container_hash::is_contiguous_range<T>::value, std::size_t>::type\n        hash_value( T const& v )\n    {\n        return boost::hash_range( v.data(), v.data() + v.size() );\n    }\n\n    // unordered ranges (unordered_set, unordered_map)\n\n    template <typename T>\n    typename std::enable_if<container_hash::is_unordered_range<T>::value, std::size_t>::type\n        hash_value( T const& v )\n    {\n        return boost::hash_unordered_range( v.begin(), v.end() );\n    }\n\n#if (  ( defined(_MSVC_STL_VERSION) && _MSVC_STL_VERSION < 142 ) ||  ( !defined(_MSVC_STL_VERSION) && defined(_CPPLIB_VER) && _CPPLIB_VER >= 520 ) )\n\n    // resolve ambiguity with unconstrained stdext::hash_value in <xhash> :-/\n\n    template<template<class...> class L, class... T>\n    typename std::enable_if<container_hash::is_range<L<T...>>::value && !container_hash::is_contiguous_range<L<T...>>::value && !container_hash::is_unordered_range<L<T...>>::value, std::size_t>::type\n        hash_value( L<T...> const& v )\n    {\n        return boost::hash_range( v.begin(), v.end() );\n    }\n\n    // contiguous ranges (string, vector, array)\n\n    template<template<class...> class L, class... T>\n    typename std::enable_if<container_hash::is_contiguous_range<L<T...>>::value, std::size_t>::type\n        hash_value( L<T...> const& v )\n    {\n        return boost::hash_range( v.data(), v.data() + v.size() );\n    }\n\n    template<template<class, std::size_t> class L, class T, std::size_t N>\n    typename std::enable_if<container_hash::is_contiguous_range<L<T, N>>::value, std::size_t>::type\n        hash_value( L<T, N> const& v )\n    {\n        return boost::hash_range( v.data(), v.data() + v.size() );\n    }\n\n    // unordered ranges (unordered_set, unordered_map)\n\n    template<template<class...> class L, class... T>\n    typename std::enable_if<container_hash::is_unordered_range<L<T...>>::value, std::size_t>::type\n        hash_value( L<T...> const& v )\n    {\n        return boost::hash_unordered_range( v.begin(), v.end() );\n    }\n\n#endif\n\n    // described classes\n\n#ifdef BOOST_DESCRIBE_CXX14\n\n#if defined(_MSC_VER) && _MSC_VER == 1900\n# pragma warning(push)\n# pragma warning(disable: 4100) // unreferenced formal parameter\n#endif\n\n    template <typename T>\n    typename std::enable_if<container_hash::is_described_class<T>::value, std::size_t>::type\n        hash_value( T const& v )\n    {\n        static_assert( !std::is_union<T>::value, \"described unions are not supported\" );\n\n        std::size_t r = 0;\n\n        using Bd = describe::describe_bases<T, describe::mod_any_access>;\n\n        mp11::mp_for_each<Bd>([&](auto D){\n\n            using B = typename decltype(D)::type;\n            boost::hash_combine( r, (B const&)v );\n\n        });\n\n        using Md = describe::describe_members<T, describe::mod_any_access>;\n\n        mp11::mp_for_each<Md>([&](auto D){\n\n            boost::hash_combine( r, v.*D.pointer );\n\n        });\n\n        return r;\n    }\n\n#if defined(_MSC_VER) && _MSC_VER == 1900\n# pragma warning(pop)\n#endif\n\n#endif\n\n    // std::unique_ptr, std::shared_ptr\n\n#ifndef BOOST_NO_CXX11_SMART_PTR\n\n    template <typename T>\n    std::size_t hash_value( std::shared_ptr<T> const& x )\n    {\n        return boost::hash_value( x.get() );\n    }\n\n    template <typename T, typename Deleter>\n    std::size_t hash_value( std::unique_ptr<T, Deleter> const& x )\n    {\n        return boost::hash_value( x.get() );\n    }\n\n#endif\n\n    // std::type_index\n\n#ifndef BOOST_NO_CXX11_HDR_TYPEINDEX\n\n    inline std::size_t hash_value( std::type_index const& v )\n    {\n        return v.hash_code();\n    }\n\n#endif\n\n    // std::error_code, std::error_condition\n\n#ifndef BOOST_NO_CXX11_HDR_SYSTEM_ERROR\n\n    inline std::size_t hash_value( std::error_code const& v )\n    {\n        std::size_t seed = 0;\n\n        boost::hash_combine( seed, v.value() );\n        boost::hash_combine( seed, &v.category() );\n\n        return seed;\n    }\n\n    inline std::size_t hash_value( std::error_condition const& v )\n    {\n        std::size_t seed = 0;\n\n        boost::hash_combine( seed, v.value() );\n        boost::hash_combine( seed, &v.category() );\n\n        return seed;\n    }\n\n#endif\n\n    // std::nullptr_t\n\n#ifndef BOOST_NO_CXX11_NULLPTR\n\n    template <typename T>\n    typename std::enable_if<std::is_same<T, std::nullptr_t>::value, std::size_t>::type\n        hash_value( T const& /*v*/ )\n    {\n        return boost::hash_value( static_cast<void*>( nullptr ) );\n    }\n\n#endif\n\n    // std::optional\n\n#ifndef BOOST_NO_CXX17_HDR_OPTIONAL\n\n    template <typename T>\n    std::size_t hash_value( std::optional<T> const& v )\n    {\n        if( !v )\n        {\n            // Arbitrary value for empty optional.\n            return 0x12345678;\n        }\n        else\n        {\n            return boost::hash<T>()(*v);\n        }\n    }\n\n#endif\n\n    // std::variant\n\n#ifndef BOOST_NO_CXX17_HDR_VARIANT\n\n    inline std::size_t hash_value( std::monostate )\n    {\n        return 0x87654321;\n    }\n\n    template <typename... Types>\n    std::size_t hash_value( std::variant<Types...> const& v )\n    {\n        std::size_t seed = 0;\n\n        hash_combine( seed, v.index() );\n        std::visit( [&seed](auto&& x) { hash_combine(seed, x); }, v );\n\n        return seed;\n    }\n\n#endif\n\n    //\n    // boost::hash_combine\n    //\n\n    template <class T>\n    inline void hash_combine( std::size_t& seed, T const& v )\n    {\n        seed = boost::hash_detail::hash_mix( seed + 0x9e3779b9 + boost::hash<T>()( v ) );\n    }\n\n    //\n    // boost::hash_range\n    //\n\n    template <class It>\n    inline void hash_range( std::size_t& seed, It first, It last )\n    {\n        seed = hash_detail::hash_range( seed, first, last );\n    }\n\n    template <class It>\n    inline std::size_t hash_range( It first, It last )\n    {\n        std::size_t seed = 0;\n\n        hash_range( seed, first, last );\n\n        return seed;\n    }\n\n    //\n    // boost::hash_unordered_range\n    //\n\n    template <class It>\n    inline void hash_unordered_range( std::size_t& seed, It first, It last )\n    {\n        std::size_t r = 0;\n        std::size_t const s2( seed );\n\n        for( ; first != last; ++first )\n        {\n            std::size_t s3( s2 );\n\n            hash_combine<typename std::iterator_traits<It>::value_type>( s3, *first );\n\n            r += s3;\n        }\n\n        seed += r;\n    }\n\n    template <class It>\n    inline std::size_t hash_unordered_range( It first, It last )\n    {\n        std::size_t seed = 0;\n\n        hash_unordered_range( seed, first, last );\n\n        return seed;\n    }\n\n    //\n    // boost::hash\n    //\n\n    template <class T> struct hash\n    {\n        typedef T argument_type;\n        typedef std::size_t result_type;\n\n        std::size_t operator()( T const& val ) const\n        {\n            return hash_value( val );\n        }\n    };\n\n#if (  ( defined(_MSVC_STL_VERSION) && _MSVC_STL_VERSION < 142 ) ||  ( !defined(_MSVC_STL_VERSION) && defined(_CPPLIB_VER) && _CPPLIB_VER >= 520 ) )\n\n    // Dinkumware has stdext::hash_value for basic_string in <xhash> :-/\n\n    template<class E, class T, class A> struct hash< std::basic_string<E, T, A> >\n    {\n        typedef std::basic_string<E, T, A> argument_type;\n        typedef std::size_t result_type;\n\n        std::size_t operator()( std::basic_string<E, T, A> const& val ) const\n        {\n            return boost::hash_value( val );\n        }\n    };\n\n#endif\n\n    // boost::unordered::hash_is_avalanching\n\n    namespace unordered\n    {\n        template<class T> struct hash_is_avalanching;\n        template<class Ch> struct hash_is_avalanching< boost::hash< std::basic_string<Ch> > >: std::is_integral<Ch> {};\n\n#ifndef BOOST_NO_CXX17_HDR_STRING_VIEW\n\n        template<class Ch> struct hash_is_avalanching< boost::hash< std::basic_string_view<Ch> > >: std::is_integral<Ch> {};\n\n#endif\n    } // namespace unordered\n\n} // namespace boost\n\n#endif // #ifndef BOOST_FUNCTIONAL_HASH_HASH_HPP\n// Copyright (C) 2022-2023 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_UNORDERED_FLAT_SET_HPP_INCLUDED\n#define BOOST_UNORDERED_UNORDERED_FLAT_SET_HPP_INCLUDED\n\n#pragma once\n\nnamespace boost {\n  namespace unordered {\n\n#ifdef BOOST_MSVC\n#pragma warning(push)\n#pragma warning(disable : 4714)\n#endif\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    class unordered_flat_set\n    {\n      template <class Key2, class Hash2, class KeyEqual2, class Allocator2>\n      friend class concurrent_flat_set;\n\n      using set_types = detail::foa::flat_set_types<Key>;\n\n      using table_type = detail::foa::table<set_types, Hash, KeyEqual,\n        typename std::allocator_traits<Allocator>::template rebind_alloc<\n          typename set_types::value_type>>;\n\n      table_type table_;\n\n      template <class K, class H, class KE, class A>\n      bool friend operator==(unordered_flat_set<K, H, KE, A> const& lhs,\n        unordered_flat_set<K, H, KE, A> const& rhs);\n\n      template <class K, class H, class KE, class A, class Pred>\n      typename unordered_flat_set<K, H, KE, A>::size_type friend erase_if(\n        unordered_flat_set<K, H, KE, A>& set, Pred pred);\n\n    public:\n      using key_type = Key;\n      using value_type = typename set_types::value_type;\n      using init_type = typename set_types::init_type;\n      using size_type = std::size_t;\n      using difference_type = std::ptrdiff_t;\n      using hasher = Hash;\n      using key_equal = KeyEqual;\n      using allocator_type = Allocator;\n      using reference = value_type&;\n      using const_reference = value_type const&;\n      using pointer = typename std::allocator_traits<allocator_type>::pointer;\n      using const_pointer =\n        typename std::allocator_traits<allocator_type>::const_pointer;\n      using iterator = typename table_type::iterator;\n      using const_iterator = typename table_type::const_iterator;\n\n      unordered_flat_set() : unordered_flat_set(0) {}\n\n      explicit unordered_flat_set(size_type n, hasher const& h = hasher(),\n        key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : table_(n, h, pred, a)\n      {\n      }\n\n      unordered_flat_set(size_type n, allocator_type const& a)\n          : unordered_flat_set(n, hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_flat_set(size_type n, hasher const& h, allocator_type const& a)\n          : unordered_flat_set(n, h, key_equal(), a)\n      {\n      }\n\n      template <class InputIterator>\n      unordered_flat_set(\n        InputIterator f, InputIterator l, allocator_type const& a)\n          : unordered_flat_set(f, l, size_type(0), hasher(), key_equal(), a)\n      {\n      }\n\n      explicit unordered_flat_set(allocator_type const& a)\n          : unordered_flat_set(0, a)\n      {\n      }\n\n      template <class Iterator>\n      unordered_flat_set(Iterator first, Iterator last, size_type n = 0,\n        hasher const& h = hasher(), key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : unordered_flat_set(n, h, pred, a)\n      {\n        this->insert(first, last);\n      }\n\n      template <class InputIt>\n      unordered_flat_set(\n        InputIt first, InputIt last, size_type n, allocator_type const& a)\n          : unordered_flat_set(first, last, n, hasher(), key_equal(), a)\n      {\n      }\n\n      template <class Iterator>\n      unordered_flat_set(Iterator first, Iterator last, size_type n,\n        hasher const& h, allocator_type const& a)\n          : unordered_flat_set(first, last, n, h, key_equal(), a)\n      {\n      }\n\n      unordered_flat_set(unordered_flat_set const& other) : table_(other.table_)\n      {\n      }\n\n      unordered_flat_set(\n        unordered_flat_set const& other, allocator_type const& a)\n          : table_(other.table_, a)\n      {\n      }\n\n      unordered_flat_set(unordered_flat_set&& other)\n        noexcept(std::is_nothrow_move_constructible<table_type>::value)\n          : table_(std::move(other.table_))\n      {\n      }\n\n      unordered_flat_set(unordered_flat_set&& other, allocator_type const& al)\n          : table_(std::move(other.table_), al)\n      {\n      }\n\n      unordered_flat_set(std::initializer_list<value_type> ilist,\n        size_type n = 0, hasher const& h = hasher(),\n        key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : unordered_flat_set(ilist.begin(), ilist.end(), n, h, pred, a)\n      {\n      }\n\n      unordered_flat_set(\n        std::initializer_list<value_type> il, allocator_type const& a)\n          : unordered_flat_set(il, size_type(0), hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_flat_set(std::initializer_list<value_type> init, size_type n,\n        allocator_type const& a)\n          : unordered_flat_set(init, n, hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_flat_set(std::initializer_list<value_type> init, size_type n,\n        hasher const& h, allocator_type const& a)\n          : unordered_flat_set(init, n, h, key_equal(), a)\n      {\n      }\n\n      unordered_flat_set(\n        concurrent_flat_set<Key, Hash, KeyEqual, Allocator>&& other)\n          : table_(std::move(other.table_))\n      {\n      }\n\n      ~unordered_flat_set() = default;\n\n      unordered_flat_set& operator=(unordered_flat_set const& other)\n      {\n        table_ = other.table_;\n        return *this;\n      }\n\n      unordered_flat_set& operator=(unordered_flat_set&& other) noexcept(\n        noexcept(std::declval<table_type&>() = std::declval<table_type&&>()))\n      {\n        table_ = std::move(other.table_);\n        return *this;\n      }\n\n      allocator_type get_allocator() const noexcept\n      {\n        return table_.get_allocator();\n      }\n\n      /// Iterators\n      ///\n\n      iterator begin() noexcept { return table_.begin(); }\n      const_iterator begin() const noexcept { return table_.begin(); }\n      const_iterator cbegin() const noexcept { return table_.cbegin(); }\n\n      iterator end() noexcept { return table_.end(); }\n      const_iterator end() const noexcept { return table_.end(); }\n      const_iterator cend() const noexcept { return table_.cend(); }\n\n      /// Capacity\n      ///\n\n      [[nodiscard]] bool empty() const noexcept\n      {\n        return table_.empty();\n      }\n\n      size_type size() const noexcept { return table_.size(); }\n\n      size_type max_size() const noexcept { return table_.max_size(); }\n\n      /// Modifiers\n      ///\n\n      void clear() noexcept { table_.clear(); }\n\n      BOOST_FORCEINLINE std::pair<iterator, bool> insert(\n        value_type const& value)\n      {\n        return table_.insert(value);\n      }\n\n      BOOST_FORCEINLINE std::pair<iterator, bool> insert(value_type&& value)\n      {\n        return table_.insert(std::move(value));\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::transparent_non_iterable<K, unordered_flat_set>::value,\n        std::pair<iterator, bool> >::type\n      insert(K&& k)\n      {\n        return table_.try_emplace(std::forward<K>(k));\n      }\n\n      BOOST_FORCEINLINE iterator insert(const_iterator, value_type const& value)\n      {\n        return table_.insert(value).first;\n      }\n\n      BOOST_FORCEINLINE iterator insert(const_iterator, value_type&& value)\n      {\n        return table_.insert(std::move(value)).first;\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::transparent_non_iterable<K, unordered_flat_set>::value,\n        iterator>::type\n      insert(const_iterator, K&& k)\n      {\n        return table_.try_emplace(std::forward<K>(k)).first;\n      }\n\n      template <class InputIterator>\n      void insert(InputIterator first, InputIterator last)\n      {\n        for (auto pos = first; pos != last; ++pos) {\n          table_.emplace(*pos);\n        }\n      }\n\n      void insert(std::initializer_list<value_type> ilist)\n      {\n        this->insert(ilist.begin(), ilist.end());\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE std::pair<iterator, bool> emplace(Args&&... args)\n      {\n        return table_.emplace(std::forward<Args>(args)...);\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE iterator emplace_hint(const_iterator, Args&&... args)\n      {\n        return table_.emplace(std::forward<Args>(args)...).first;\n      }\n\n      BOOST_FORCEINLINE typename table_type::erase_return_type erase(\n        const_iterator pos)\n      {\n        return table_.erase(pos);\n      }\n\n      iterator erase(const_iterator first, const_iterator last)\n      {\n        while (first != last) {\n          this->erase(first++);\n        }\n        return iterator{detail::foa::const_iterator_cast_tag{}, last};\n      }\n\n      BOOST_FORCEINLINE size_type erase(key_type const& key)\n      {\n        return table_.erase(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::transparent_non_iterable<K, unordered_flat_set>::value,\n        size_type>::type\n      erase(K const& key)\n      {\n        return table_.erase(key);\n      }\n\n      void swap(unordered_flat_set& rhs) noexcept(\n        noexcept(std::declval<table_type&>().swap(std::declval<table_type&>())))\n      {\n        table_.swap(rhs.table_);\n      }\n\n      template <class H2, class P2>\n      void merge(unordered_flat_set<key_type, H2, P2, allocator_type>& source)\n      {\n        table_.merge(source.table_);\n      }\n\n      template <class H2, class P2>\n      void merge(unordered_flat_set<key_type, H2, P2, allocator_type>&& source)\n      {\n        table_.merge(std::move(source.table_));\n      }\n\n      /// Lookup\n      ///\n\n      BOOST_FORCEINLINE size_type count(key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        return pos != table_.end() ? 1 : 0;\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value, size_type>::type\n      count(K const& key) const\n      {\n        auto pos = table_.find(key);\n        return pos != table_.end() ? 1 : 0;\n      }\n\n      BOOST_FORCEINLINE iterator find(key_type const& key)\n      {\n        return table_.find(key);\n      }\n\n      BOOST_FORCEINLINE const_iterator find(key_type const& key) const\n      {\n        return table_.find(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        iterator>::type\n      find(K const& key)\n      {\n        return table_.find(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        const_iterator>::type\n      find(K const& key) const\n      {\n        return table_.find(key);\n      }\n\n      BOOST_FORCEINLINE bool contains(key_type const& key) const\n      {\n        return this->find(key) != this->end();\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        bool>::type\n      contains(K const& key) const\n      {\n        return this->find(key) != this->end();\n      }\n\n      std::pair<iterator, iterator> equal_range(key_type const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      std::pair<const_iterator, const_iterator> equal_range(\n        key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      template <class K>\n      typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<iterator, iterator> >::type\n      equal_range(K const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      template <class K>\n      typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<const_iterator, const_iterator> >::type\n      equal_range(K const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      /// Hash Policy\n      ///\n\n      size_type bucket_count() const noexcept { return table_.capacity(); }\n\n      float load_factor() const noexcept { return table_.load_factor(); }\n\n      float max_load_factor() const noexcept\n      {\n        return table_.max_load_factor();\n      }\n\n      void max_load_factor(float) {}\n\n      size_type max_load() const noexcept { return table_.max_load(); }\n\n      void rehash(size_type n) { table_.rehash(n); }\n\n      void reserve(size_type n) { table_.reserve(n); }\n\n      /// Observers\n      ///\n\n      hasher hash_function() const { return table_.hash_function(); }\n\n      key_equal key_eq() const { return table_.key_eq(); }\n    };\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& rhs)\n    {\n      return lhs.table_ == rhs.table_;\n    }\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator> const& rhs)\n    {\n      return !(lhs == rhs);\n    }\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    void swap(unordered_flat_set<Key, Hash, KeyEqual, Allocator>& lhs,\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator>& rhs)\n      noexcept(noexcept(lhs.swap(rhs)))\n    {\n      lhs.swap(rhs);\n    }\n\n    template <class Key, class Hash, class KeyEqual, class Allocator,\n      class Pred>\n    typename unordered_flat_set<Key, Hash, KeyEqual, Allocator>::size_type\n    erase_if(unordered_flat_set<Key, Hash, KeyEqual, Allocator>& set, Pred pred)\n    {\n      return erase_if(set.table_, pred);\n    }\n\n    template <class Archive, class Key, class Hash, class KeyEqual,\n      class Allocator>\n    void serialize(Archive& ar,\n      unordered_flat_set<Key, Hash, KeyEqual, Allocator>& set,\n      unsigned int version)\n    {\n    }\n\n#ifdef BOOST_MSVC\n#pragma warning(pop)\n#endif\n\n#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES\n    template <class InputIterator,\n      class Hash =\n        boost::hash<typename std::iterator_traits<InputIterator>::value_type>,\n      class Pred =\n        std::equal_to<typename std::iterator_traits<InputIterator>::value_type>,\n      class Allocator = std::allocator<\n        typename std::iterator_traits<InputIterator>::value_type>,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_pred_v<Pred> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_set(InputIterator, InputIterator,\n      std::size_t = boost::unordered::detail::foa::default_bucket_count,\n      Hash = Hash(), Pred = Pred(), Allocator = Allocator())\n      -> unordered_flat_set<\n        typename std::iterator_traits<InputIterator>::value_type, Hash, Pred,\n        Allocator>;\n\n    template <class T, class Hash = boost::hash<T>,\n      class Pred = std::equal_to<T>, class Allocator = std::allocator<T>,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_pred_v<Pred> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_set(std::initializer_list<T>,\n      std::size_t = boost::unordered::detail::foa::default_bucket_count,\n      Hash = Hash(), Pred = Pred(), Allocator = Allocator())\n      -> unordered_flat_set<T, Hash, Pred, Allocator>;\n\n    template <class InputIterator, class Allocator,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_set(InputIterator, InputIterator, std::size_t, Allocator)\n      -> unordered_flat_set<\n        typename std::iterator_traits<InputIterator>::value_type,\n        boost::hash<typename std::iterator_traits<InputIterator>::value_type>,\n        std::equal_to<typename std::iterator_traits<InputIterator>::value_type>,\n        Allocator>;\n\n    template <class InputIterator, class Hash, class Allocator,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_set(\n      InputIterator, InputIterator, std::size_t, Hash, Allocator)\n      -> unordered_flat_set<\n        typename std::iterator_traits<InputIterator>::value_type, Hash,\n        std::equal_to<typename std::iterator_traits<InputIterator>::value_type>,\n        Allocator>;\n\n    template <class T, class Allocator,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_set(std::initializer_list<T>, std::size_t, Allocator)\n      -> unordered_flat_set<T, boost::hash<T>, std::equal_to<T>, Allocator>;\n\n    template <class T, class Hash, class Allocator,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_set(std::initializer_list<T>, std::size_t, Hash, Allocator)\n      -> unordered_flat_set<T, Hash, std::equal_to<T>, Allocator>;\n\n    template <class InputIterator, class Allocator,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_set(InputIterator, InputIterator, Allocator)\n      -> unordered_flat_set<\n        typename std::iterator_traits<InputIterator>::value_type,\n        boost::hash<typename std::iterator_traits<InputIterator>::value_type>,\n        std::equal_to<typename std::iterator_traits<InputIterator>::value_type>,\n        Allocator>;\n\n    template <class T, class Allocator,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_set(std::initializer_list<T>, Allocator)\n      -> unordered_flat_set<T, boost::hash<T>, std::equal_to<T>, Allocator>;\n#endif\n\n  } // namespace unordered\n} // namespace boost\n\n#endif\n/* Fast open-addressing concurrent hashmap.\n *\n * Copyright 2023 Christian Mazakas.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifndef BOOST_UNORDERED_CONCURRENT_FLAT_MAP_FWD_HPP\n#define BOOST_UNORDERED_CONCURRENT_FLAT_MAP_FWD_HPP\n\nnamespace boost {\n  namespace unordered {\n\n    template <class Key, class T, class Hash = boost::hash<Key>,\n      class Pred = std::equal_to<Key>,\n      class Allocator = std::allocator<std::pair<Key const, T> > >\n    class concurrent_flat_map;\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class T, class Hash, class Pred, class Alloc>\n    void swap(concurrent_flat_map<Key, T, Hash, Pred, Alloc>& x,\n      concurrent_flat_map<Key, T, Hash, Pred, Alloc>& y)\n      noexcept(noexcept(x.swap(y)));\n\n    template <class K, class T, class H, class P, class A, class Predicate>\n    typename concurrent_flat_map<K, T, H, P, A>::size_type erase_if(\n      concurrent_flat_map<K, T, H, P, A>& c, Predicate pred);\n\n  } // namespace unordered\n\n  using boost::unordered::concurrent_flat_map;\n} // namespace boost\n\n#endif // BOOST_UNORDERED_CONCURRENT_FLAT_MAP_HPP\n// Copyright (C) 2023 Christian Mazakas\n// Copyright (C) 2024 Braden Ganetsky\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_DETAIL_FOA_FLAT_MAP_TYPES_HPP\n#define BOOST_UNORDERED_DETAIL_FOA_FLAT_MAP_TYPES_HPP\n\nnamespace boost {\n  namespace unordered {\n    namespace detail {\n      namespace foa {\n        template <class Key, class T> struct flat_map_types\n        {\n          using key_type = Key;\n          using mapped_type = T;\n          using raw_key_type = typename std::remove_const<Key>::type;\n          using raw_mapped_type = typename std::remove_const<T>::type;\n\n          using init_type = std::pair<raw_key_type, raw_mapped_type>;\n          using moved_type = std::pair<raw_key_type&&, raw_mapped_type&&>;\n          using value_type = std::pair<Key const, T>;\n\n          using element_type = value_type;\n\n          static value_type& value_from(element_type& x) { return x; }\n\n          template <class K, class V>\n          static raw_key_type const& extract(std::pair<K, V> const& kv)\n          {\n            return kv.first;\n          }\n\n          static moved_type move(init_type& x)\n          {\n            return {std::move(x.first), std::move(x.second)};\n          }\n\n          static moved_type move(element_type& x)\n          {\n            // TODO: we probably need to launder here\n            return {std::move(const_cast<raw_key_type&>(x.first)),\n              std::move(const_cast<raw_mapped_type&>(x.second))};\n          }\n\n          template <class A, class... Args>\n          static void construct(A& al, init_type* p, Args&&... args)\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::construct(al, p, std::forward<Args>(args)...);\n          }\n\n          template <class A, class... Args>\n          static void construct(A& al, value_type* p, Args&&... args)\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::construct(al, p, std::forward<Args>(args)...);\n          }\n\n          template <class A, class... Args>\n          static void construct(A& al, key_type* p, Args&&... args)\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::construct(al, p, std::forward<Args>(args)...);\n          }\n          \n// Lynx Add Begin\n          static void construct_plain_bytes_key(value_type* p, const key_type& key)\n          {\n            // Simply copy bytes of key to dest memory.\n            using PlainBytesKey = lynx::base::TypeOfPlainBytes<key_type>;\n            const_cast<PlainBytesKey&>(reinterpret_cast<const PlainBytesKey&>(p->first)) =\n              reinterpret_cast<const PlainBytesKey&>(key);\n          }\n          \n          static void construct_plain_bytes(value_type* p, const value_type* from_p)\n          {\n            // Simply copy bytes of key-value.\n            using PlainBytesValueType = lynx::base::TypeOfPlainBytes<value_type>;\n            *reinterpret_cast<PlainBytesValueType*>(p) = \n              *reinterpret_cast<const PlainBytesValueType*>(from_p);\n          }\n// Lynx Add End\n\n          template <class A> static void destroy(A& al, init_type* p) noexcept\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::destroy(al, p);\n          }\n\n          template <class A> static void destroy(A& al, value_type* p) noexcept\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::destroy(al, p);\n          }\n\n          template <class A> static void destroy(A& al, key_type* p) noexcept\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::destroy(al, p);\n          }\n        };\n      } // namespace foa\n    }   // namespace detail\n  }     // namespace unordered\n} // namespace boost\n\n#endif // BOOST_UNORDERED_DETAIL_FOA_FLAT_MAP_TYPES_HPP\n#ifndef BOOST_ASSERT_SOURCE_LOCATION_HPP_INCLUDED\n#define BOOST_ASSERT_SOURCE_LOCATION_HPP_INCLUDED\n\n// http://www.boost.org/libs/assert\n//\n// Copyright 2019, 2021 Peter Dimov\n// Distributed under the Boost Software License, Version 1.0.\n// http://www.boost.org/LICENSE_1_0.txt\n\n#if defined(__cpp_lib_source_location) && __cpp_lib_source_location >= 201907L\n# include <source_location>\n#endif\n\nnamespace boost\n{\n\nstruct source_location\n{\nprivate:\n\n    char const * file_;\n    char const * function_;\n    std::uint_least32_t line_;\n    std::uint_least32_t column_;\n\npublic:\n\n    constexpr source_location() noexcept: file_( \"\" ), function_( \"\" ), line_( 0 ), column_( 0 )\n    {\n    }\n\n    constexpr source_location( char const * file, std::uint_least32_t ln, char const * function, std::uint_least32_t col = 0 ) noexcept: file_( file ), function_( function ), line_( ln ), column_( col )\n    {\n    }\n\n#if defined(__cpp_lib_source_location) && __cpp_lib_source_location >= 201907L\n\n    constexpr source_location( std::source_location const& loc ) noexcept: file_( loc.file_name() ), function_( loc.function_name() ), line_( loc.line() ), column_( loc.column() )\n    {\n    }\n\n#endif\n\n    constexpr char const * file_name() const noexcept\n    {\n        return file_;\n    }\n\n    constexpr char const * function_name() const noexcept\n    {\n        return function_;\n    }\n\n    constexpr std::uint_least32_t line() const noexcept\n    {\n        return line_;\n    }\n\n    constexpr std::uint_least32_t column() const noexcept\n    {\n        return column_;\n    }\n\n#ifdef BOOST_MSVC\n# pragma warning( push )\n# pragma warning( disable: 4996 )\n#endif\n\n#if ( defined(_MSC_VER) && _MSC_VER < 1900 ) || ( defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR) )\n# define BOOST_ASSERT_SNPRINTF(buffer, format, arg) std::sprintf(buffer, format, arg)\n#else\n# define BOOST_ASSERT_SNPRINTF(buffer, format, arg) std::snprintf(buffer, sizeof(buffer)/sizeof(buffer[0]), format, arg)\n#endif\n\n    std::string to_string() const\n    {\n        unsigned long ln = line();\n\n        if( ln == 0 )\n        {\n            return \"(unknown source location)\";\n        }\n\n        std::string r = file_name();\n\n        char buffer[ 16 ];\n\n        BOOST_ASSERT_SNPRINTF( buffer, \":%lu\", ln );\n        r += buffer;\n\n        unsigned long co = column();\n\n        if( co )\n        {\n            BOOST_ASSERT_SNPRINTF( buffer, \":%lu\", co );\n            r += buffer;\n        }\n\n        char const* fn = function_name();\n\n        if( *fn != 0 )\n        {\n            r += \" in function '\";\n            r += fn;\n            r += '\\'';\n        }\n\n        return r;\n    }\n\n#undef BOOST_ASSERT_SNPRINTF\n\n#ifdef BOOST_MSVC\n# pragma warning( pop )\n#endif\n\n    inline friend bool operator==( source_location const& s1, source_location const& s2 ) noexcept\n    {\n        return std::strcmp( s1.file_, s2.file_ ) == 0 && std::strcmp( s1.function_, s2.function_ ) == 0 && s1.line_ == s2.line_ && s1.column_ == s2.column_;\n    }\n\n    inline friend bool operator!=( source_location const& s1, source_location const& s2 ) noexcept\n    {\n        return !( s1 == s2 );\n    }\n};\n\ntemplate<class E, class T> std::basic_ostream<E, T> & operator<<( std::basic_ostream<E, T> & os, source_location const & loc )\n{\n    os << loc.to_string();\n    return os;\n}\n\n} // namespace boost\n\n#ifdef BOOST_DISABLE_CURRENT_LOCATION\n\n# define BOOST_CURRENT_LOCATION ::boost::source_location()\n\n#elif defined(BOOST_MSVC) && BOOST_MSVC >= 1935\n\n# define BOOST_CURRENT_LOCATION ::boost::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCSIG(), __builtin_COLUMN())\n\n#elif defined(BOOST_MSVC) && BOOST_MSVC >= 1926\n\n// std::source_location::current() is available in -std:c++20, but fails with consteval errors before 19.31, and doesn't produce\n// the correct result under 19.31, so prefer the built-ins\n# define BOOST_CURRENT_LOCATION ::boost::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION(), __builtin_COLUMN())\n\n#elif defined(BOOST_MSVC)\n\n// __LINE__ is not a constant expression under /ZI (edit and continue) for 1925 and before\n\n# define BOOST_CURRENT_LOCATION_IMPL_1(x) BOOST_CURRENT_LOCATION_IMPL_2(x)\n# define BOOST_CURRENT_LOCATION_IMPL_2(x) (x##0 / 10)\n\n# define BOOST_CURRENT_LOCATION ::boost::source_location(__FILE__, BOOST_CURRENT_LOCATION_IMPL_1(__LINE__), \"\")\n\n#elif defined(__cpp_lib_source_location) && __cpp_lib_source_location >= 201907L && !defined(__NVCC__)\n\n// Under nvcc, __builtin_source_location is not constexpr\n// https://github.com/boostorg/assert/issues/32\n\n# define BOOST_CURRENT_LOCATION ::boost::source_location(::std::source_location::current())\n\n#elif defined(BOOST_CLANG) && BOOST_CLANG_VERSION >= 90000\n\n# define BOOST_CURRENT_LOCATION ::boost::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION(), __builtin_COLUMN())\n\n#elif defined(BOOST_GCC) && BOOST_GCC >= 70000\n\n// The built-ins are available in 4.8+, but are not constant expressions until 7\n# define BOOST_CURRENT_LOCATION ::boost::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION())\n\n#elif defined(BOOST_GCC) && BOOST_GCC >= 50000\n\n// __PRETTY_FUNCTION__ is allowed outside functions under GCC, but 4.x suffers from codegen bugs\n# define BOOST_CURRENT_LOCATION ::boost::source_location(__FILE__, __LINE__, __PRETTY_FUNCTION__)\n\n#else\n\n// __func__ macros aren't allowed outside functions, but BOOST_CURRENT_LOCATION is\n# define BOOST_CURRENT_LOCATION ::boost::source_location(__FILE__, __LINE__, \"\")\n\n#endif\n\n#endif // #ifndef BOOST_ASSERT_SOURCE_LOCATION_HPP_INCLUDED\n//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc.\n\n//Distributed under the Boost Software License, Version 1.0. (See accompanying\n//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_EXCEPTION_274DA366004E11DCB1DDFE2E56D89593\n#define BOOST_EXCEPTION_274DA366004E11DCB1DDFE2E56D89593\n\n#ifdef BOOST_EXCEPTION_MINI_BOOST\n#include  <memory>\nnamespace boost { namespace exception_detail { using std::shared_ptr; } }\n#else\nnamespace boost { template <class T> class shared_ptr; }\nnamespace boost { namespace exception_detail { using boost::shared_ptr; } }\n#endif\n\n#ifndef BOOST_EXCEPTION_ENABLE_WARNINGS\n#if defined(__GNUC__) && __GNUC__*100+__GNUC_MINOR__>301\n#pragma GCC system_header\n#endif\n#ifdef __clang__\n#pragma clang system_header\n#endif\n#ifdef _MSC_VER\n#pragma warning(push,1)\n#pragma warning(disable: 4265)\n#endif\n#endif\n\nnamespace\nboost\n    {\n    namespace\n    exception_detail\n        {\n        template <class T>\n        class\n        refcount_ptr\n            {\n            public:\n\n            refcount_ptr():\n                px_(0)\n                {\n                }\n\n            ~refcount_ptr()\n                {\n                release();\n                }\n\n            refcount_ptr( refcount_ptr const & x ):\n                px_(x.px_)\n                {\n                add_ref();\n                }\n\n            refcount_ptr &\n            operator=( refcount_ptr const & x )\n                {\n                adopt(x.px_);\n                return *this;\n                }\n\n            void\n            adopt( T * px )\n                {\n                release();\n                px_=px;\n                add_ref();\n                }\n\n            T *\n            get() const\n                {\n                return px_;\n                }\n\n            private:\n\n            T * px_;\n\n            void\n            add_ref()\n                {\n                if( px_ )\n                    px_->add_ref();\n                }\n\n            void\n            release()\n                {\n                if( px_ && px_->release() )\n                    px_=0;\n                }\n            };\n        }\n\n    ////////////////////////////////////////////////////////////////////////\n\n    template <class Tag,class T>\n    class error_info;\n\n    typedef error_info<struct throw_function_,char const *> throw_function;\n    typedef error_info<struct throw_file_,char const *> throw_file;\n    typedef error_info<struct throw_line_,int> throw_line;\n    typedef error_info<struct throw_column_,int> throw_column;\n\n    template <>\n    class\n    error_info<throw_function_,char const *>\n        {\n        public:\n        typedef char const * value_type;\n        value_type v_;\n        explicit\n        error_info( value_type v ):\n            v_(v)\n            {\n            }\n        };\n\n    template <>\n    class\n    error_info<throw_file_,char const *>\n        {\n        public:\n        typedef char const * value_type;\n        value_type v_;\n        explicit\n        error_info( value_type v ):\n            v_(v)\n            {\n            }\n        };\n\n    template <>\n    class\n    error_info<throw_line_,int>\n        {\n        public:\n        typedef int value_type;\n        value_type v_;\n        explicit\n        error_info( value_type v ):\n            v_(v)\n            {\n            }\n        };\n\n    template <>\n    class\n    error_info<throw_column_,int>\n        {\n        public:\n        typedef int value_type;\n        value_type v_;\n        explicit\n        error_info( value_type v ):\n            v_(v)\n            {\n            }\n        };\n\n    class\n    BOOST_SYMBOL_VISIBLE\n    exception;\n\n    namespace\n    exception_detail\n        {\n        class error_info_base;\n        struct type_info_;\n\n        struct\n        error_info_container\n            {\n            virtual char const * diagnostic_information( char const * ) const = 0;\n            virtual shared_ptr<error_info_base> get( type_info_ const & ) const = 0;\n            virtual void set( shared_ptr<error_info_base> const &, type_info_ const & ) = 0;\n            virtual void add_ref() const = 0;\n            virtual bool release() const = 0;\n            virtual refcount_ptr<exception_detail::error_info_container> clone() const = 0;\n\n            protected:\n\n            ~error_info_container() noexcept\n                {\n                }\n            };\n\n        template <class>\n        struct get_info;\n\n        template <>\n        struct get_info<throw_function>;\n\n        template <>\n        struct get_info<throw_file>;\n\n        template <>\n        struct get_info<throw_line>;\n\n        template <>\n        struct get_info<throw_column>;\n\n        template <class>\n        struct set_info_rv;\n\n        template <>\n        struct set_info_rv<throw_function>;\n\n        template <>\n        struct set_info_rv<throw_file>;\n\n        template <>\n        struct set_info_rv<throw_line>;\n\n        template <>\n        struct set_info_rv<throw_column>;\n\n        char const * get_diagnostic_information( exception const &, char const * );\n\n        void copy_boost_exception( exception *, exception const * );\n\n        template <class E,class Tag,class T>\n        E const & set_info( E const &, error_info<Tag,T> const & );\n\n        template <class E>\n        E const & set_info( E const &, throw_function const & );\n\n        template <class E>\n        E const & set_info( E const &, throw_file const & );\n\n        template <class E>\n        E const & set_info( E const &, throw_line const & );\n\n        template <class E>\n        E const & set_info( E const &, throw_column const & );\n\n        boost::source_location get_exception_throw_location( exception const & );\n        }\n\n    class\n    BOOST_SYMBOL_VISIBLE\n    exception\n        {\n        //<N3757>\n        public:\n        template <class Tag> void set( typename Tag::type const & );\n        template <class Tag> typename Tag::type const * get() const;\n        //</N3757>\n\n        protected:\n\n        exception():\n            throw_function_(0),\n            throw_file_(0),\n            throw_line_(-1),\n            throw_column_(-1)\n            {\n            }\n\n#ifdef __HP_aCC\n        //On HP aCC, this protected copy constructor prevents throwing boost::exception.\n        //On all other platforms, the same effect is achieved by the pure virtual destructor.\n        exception( exception const & x ) noexcept:\n            data_(x.data_),\n            throw_function_(x.throw_function_),\n            throw_file_(x.throw_file_),\n            throw_line_(x.throw_line_),\n            throw_column_(x.throw_column_)\n            {\n            }\n#endif\n\n        virtual ~exception() noexcept\n#ifndef __HP_aCC\n            = 0 //Workaround for HP aCC, =0 incorrectly leads to link errors.\n#endif\n            ;\n\n#if (defined(__MWERKS__) && __MWERKS__<=0x3207) || (defined(_MSC_VER) && _MSC_VER<=1310)\n        public:\n#else\n        private:\n\n        template <class E>\n        friend E const & exception_detail::set_info( E const &, throw_function const & );\n\n        template <class E>\n        friend E const & exception_detail::set_info( E const &, throw_file const & );\n\n        template <class E>\n        friend E const & exception_detail::set_info( E const &, throw_line const & );\n\n        template <class E>\n        friend E const & exception_detail::set_info( E const &, throw_column const & );\n\n        template <class E,class Tag,class T>\n        friend E const & exception_detail::set_info( E const &, error_info<Tag,T> const & );\n\n        friend char const * exception_detail::get_diagnostic_information( exception const &, char const * );\n\n        friend boost::source_location exception_detail::get_exception_throw_location( exception const & );\n\n        template <class>\n        friend struct exception_detail::get_info;\n        friend struct exception_detail::get_info<throw_function>;\n        friend struct exception_detail::get_info<throw_file>;\n        friend struct exception_detail::get_info<throw_line>;\n        friend struct exception_detail::get_info<throw_column>;\n        template <class>\n        friend struct exception_detail::set_info_rv;\n        friend struct exception_detail::set_info_rv<throw_function>;\n        friend struct exception_detail::set_info_rv<throw_file>;\n        friend struct exception_detail::set_info_rv<throw_line>;\n        friend struct exception_detail::set_info_rv<throw_column>;\n        friend void exception_detail::copy_boost_exception( exception *, exception const * );\n#endif\n        mutable exception_detail::refcount_ptr<exception_detail::error_info_container> data_;\n        mutable char const * throw_function_;\n        mutable char const * throw_file_;\n        mutable int throw_line_;\n        mutable int throw_column_;\n        };\n\n    inline\n    exception::\n    ~exception() noexcept\n        {\n        }\n\n    namespace\n    exception_detail\n        {\n        template <class E>\n        E const &\n        set_info( E const & x, throw_function const & y )\n            {\n            x.throw_function_=y.v_;\n            return x;\n            }\n\n        template <class E>\n        E const &\n        set_info( E const & x, throw_file const & y )\n            {\n            x.throw_file_=y.v_;\n            return x;\n            }\n\n        template <class E>\n        E const &\n        set_info( E const & x, throw_line const & y )\n            {\n            x.throw_line_=y.v_;\n            return x;\n            }\n\n        template <class E>\n        E const &\n        set_info( E const & x, throw_column const & y )\n            {\n            x.throw_column_=y.v_;\n            return x;\n            }\n\n        template <>\n        struct\n        set_info_rv<throw_column>\n            {\n            template <class E>\n            static\n            E const &\n            set( E const & x, throw_column && y )\n                {\n                x.throw_column_=y.v_;\n                return x;\n                }\n            };\n\n        inline boost::source_location get_exception_throw_location( exception const & x )\n            {\n            return boost::source_location(\n                x.throw_file_? x.throw_file_: \"\",\n                x.throw_line_ >= 0? x.throw_line_: 0,\n                x.throw_function_? x.throw_function_: \"\",\n                x.throw_column_ >= 0? x.throw_column_: 0\n                );\n            }\n        }\n\n    ////////////////////////////////////////////////////////////////////////\n\n    namespace\n    exception_detail\n        {\n        template <class T>\n        struct\n        BOOST_SYMBOL_VISIBLE\n        error_info_injector:\n            public T,\n            public exception\n            {\n            explicit\n            error_info_injector( T const & x ):\n                T(x)\n                {\n                }\n\n            ~error_info_injector() noexcept\n                {\n                }\n            };\n\n        struct large_size { char c[256]; };\n        large_size dispatch_boost_exception( exception const * );\n\n        struct small_size { };\n        small_size dispatch_boost_exception( void const * );\n\n        template <class,int>\n        struct enable_error_info_helper;\n\n        template <class T>\n        struct\n        enable_error_info_helper<T,sizeof(large_size)>\n            {\n            typedef T type;\n            };\n\n        template <class T>\n        struct\n        enable_error_info_helper<T,sizeof(small_size)>\n            {\n            typedef error_info_injector<T> type;\n            };\n\n        template <class T>\n        struct\n        enable_error_info_return_type\n            {\n            typedef typename enable_error_info_helper<T,sizeof(exception_detail::dispatch_boost_exception(static_cast<T *>(0)))>::type type;\n            };\n        }\n\n    template <class T>\n    inline\n    typename\n    exception_detail::enable_error_info_return_type<T>::type\n    enable_error_info( T const & x )\n        {\n        typedef typename exception_detail::enable_error_info_return_type<T>::type rt;\n        return rt(x);\n        }\n\n    ////////////////////////////////////////////////////////////////////////\n#ifdef BOOST_NO_EXCEPTIONS\n    BOOST_NORETURN void throw_exception(std::exception const & e); // user defined\n#endif\n\n    namespace\n    exception_detail\n        {\n        class\n        BOOST_SYMBOL_VISIBLE\n        clone_base\n            {\n            public:\n\n            virtual clone_base const * clone() const = 0;\n            virtual void rethrow() const = 0;\n\n            virtual\n            ~clone_base() noexcept\n                {\n                }\n            };\n\n        inline\n        void\n        copy_boost_exception( exception * a, exception const * b )\n            {\n            refcount_ptr<error_info_container> data;\n            if( error_info_container * d=b->data_.get() )\n                data = d->clone();\n            a->throw_file_ = b->throw_file_;\n            a->throw_line_ = b->throw_line_;\n            a->throw_function_ = b->throw_function_;\n            a->throw_column_ = b->throw_column_;\n            a->data_ = data;\n            }\n\n        inline\n        void\n        copy_boost_exception( void *, void const * )\n            {\n            }\n\n        template <class T>\n        class\n        BOOST_SYMBOL_VISIBLE\n        clone_impl:\n            public T,\n            public virtual clone_base\n            {\n            struct clone_tag { };\n            clone_impl( clone_impl const & x, clone_tag ):\n                T(x)\n                {\n                copy_boost_exception(this,&x);\n                }\n\n            public:\n\n            explicit\n            clone_impl( T const & x ):\n                T(x)\n                {\n                copy_boost_exception(this,&x);\n                }\n\n            ~clone_impl() noexcept\n                {\n                }\n\n            private:\n\n            clone_base const *\n            clone() const\n                {\n                return new clone_impl(*this,clone_tag());\n                }\n\n            void\n            rethrow() const\n                {\n#ifdef BOOST_NO_EXCEPTIONS\n                boost::throw_exception(*this);\n#else\n                throw*this;\n#endif\n                }\n            };\n        }\n\n    template <class T>\n    inline\n    exception_detail::clone_impl<T>\n    enable_current_exception( T const & x )\n        {\n        return exception_detail::clone_impl<T>(x);\n        }\n    }\n\n#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS)\n#pragma warning(pop)\n#endif\n\n#endif // #ifndef BOOST_EXCEPTION_274DA366004E11DCB1DDFE2E56D89593\n#ifndef BOOST_THROW_EXCEPTION_HPP_INCLUDED\n#define BOOST_THROW_EXCEPTION_HPP_INCLUDED\n\n// MS compatible compilers support #pragma once\n\n#if defined(_MSC_VER) && (_MSC_VER >= 1020)\n# pragma once\n#endif\n\n//  boost/throw_exception.hpp\n//\n//  Copyright (c) 2002, 2018-2022 Peter Dimov\n//  Copyright (c) 2008-2009 Emil Dotchevski and Reverge Studios, Inc.\n//\n//  Distributed under the Boost Software License, Version 1.0. (See\n//  accompanying file LICENSE_1_0.txt or copy at\n//  http://www.boost.org/LICENSE_1_0.txt)\n//\n//  http://www.boost.org/libs/throw_exception\n\nnamespace boost\n{\n\n#ifdef  BOOST_NO_EXCEPTIONS \n\nBOOST_NORETURN void throw_exception( std::exception const & e ); // user defined\nBOOST_NORETURN void throw_exception( std::exception const & e, boost::source_location const & loc ); // user defined\n\n#endif\n\n// boost::wrapexcept<E>\n\nnamespace detail\n{\n\ntypedef char (&wrapexcept_s1)[ 1 ];\ntypedef char (&wrapexcept_s2)[ 2 ];\n\ntemplate<class T> wrapexcept_s1 wrapexcept_is_convertible( T* );\ntemplate<class T> wrapexcept_s2 wrapexcept_is_convertible( void* );\n\ntemplate<class E, class B, std::size_t I = sizeof( wrapexcept_is_convertible<B>( static_cast< E* >( nullptr ) ) ) > struct wrapexcept_add_base;\n\ntemplate<class E, class B> struct wrapexcept_add_base<E, B, 1>\n{\n    struct type {};\n};\n\ntemplate<class E, class B> struct wrapexcept_add_base<E, B, 2>\n{\n    typedef B type;\n};\n\n} // namespace detail\n\ntemplate<class E> struct BOOST_SYMBOL_VISIBLE wrapexcept:\n    public detail::wrapexcept_add_base<E, boost::exception_detail::clone_base>::type,\n    public E,\n    public detail::wrapexcept_add_base<E, boost::exception>::type\n{\nprivate:\n\n    struct deleter\n    {\n        wrapexcept * p_;\n        ~deleter() { delete p_; }\n    };\n\nprivate:\n\n    void copy_from( void const* )\n    {\n    }\n\n    void copy_from( boost::exception const* p )\n    {\n        static_cast<boost::exception&>( *this ) = *p;\n    }\n\npublic:\n\n    explicit wrapexcept( E const & e ): E( e )\n    {\n        copy_from( &e );\n    }\n\n    explicit wrapexcept( E const & e, boost::source_location const & loc ): E( e )\n    {\n        copy_from( &e );\n\n        set_info( *this, throw_file( loc.file_name() ) );\n        set_info( *this, throw_line( static_cast<int>( loc.line() ) ) );\n        set_info( *this, throw_function( loc.function_name() ) );\n        set_info( *this, throw_column( static_cast<int>( loc.column() ) ) );\n    }\n\n    virtual boost::exception_detail::clone_base const * clone() const override\n    {\n        wrapexcept * p = new wrapexcept( *this );\n        deleter del = { p };\n\n        boost::exception_detail::copy_boost_exception( p, this );\n\n        del.p_ = nullptr;\n        return p;\n    }\n\n    virtual void rethrow() const override\n    {\n#ifdef  BOOST_NO_EXCEPTIONS \n\n        boost::throw_exception( *this );\n\n#else\n\n        throw *this;\n\n#endif\n    }\n};\n\n// All boost exceptions are required to derive from std::exception,\n// to ensure compatibility with BOOST_NO_EXCEPTIONS.\n\ninline void throw_exception_assert_compatibility( std::exception const & ) {}\n\n// boost::throw_exception\n\n#ifndef  BOOST_NO_EXCEPTIONS \n\n#ifdef  BOOST_EXCEPTION_DISABLE \n\ntemplate<class E> BOOST_NORETURN void throw_exception( E const & e )\n{\n    throw_exception_assert_compatibility( e );\n    throw e;\n}\n\ntemplate<class E> BOOST_NORETURN void throw_exception( E const & e, boost::source_location const & )\n{\n    throw_exception_assert_compatibility( e );\n    throw e;\n}\n\n#else // defined( BOOST_EXCEPTION_DISABLE )\n\ntemplate<class E> BOOST_NORETURN void throw_exception( E const & e )\n{\n    throw_exception_assert_compatibility( e );\n    throw wrapexcept<E>( e );\n}\n\ntemplate<class E> BOOST_NORETURN void throw_exception( E const & e, boost::source_location const & loc )\n{\n    throw_exception_assert_compatibility( e );\n    throw wrapexcept<E>( e, loc );\n}\n\n#endif // defined( BOOST_EXCEPTION_DISABLE )\n\n#endif // !defined( BOOST_NO_EXCEPTIONS )\n\n} // namespace boost\n\n// BOOST_THROW_EXCEPTION\n\n#define BOOST_THROW_EXCEPTION(x) ::boost::throw_exception(x, BOOST_CURRENT_LOCATION)\n\nnamespace boost\n{\n\n// throw_with_location\n\nnamespace detail\n{\n\nstruct BOOST_SYMBOL_VISIBLE throw_location\n{\n    boost::source_location location_;\n\n    explicit throw_location( boost::source_location const & loc ): location_( loc )\n    {\n    }\n};\n\ntemplate<class E> class BOOST_SYMBOL_VISIBLE with_throw_location: public E, public throw_location\n{\npublic:\n\n    with_throw_location( E const & e, boost::source_location const & loc ): E( e ), throw_location( loc )\n    {\n    }\n\n    with_throw_location( E && e, boost::source_location const & loc ): E( std::move( e ) ), throw_location( loc )\n    {\n    }\n\n};\n\n} // namespace detail\n\n#ifndef BOOST_NO_EXCEPTIONS\n\n#ifndef BOOST_NO_CXX11_HDR_TYPE_TRAITS\n\ntemplate<class E> BOOST_NORETURN void throw_with_location( E && e, boost::source_location const & loc = BOOST_CURRENT_LOCATION )\n{\n    throw_exception_assert_compatibility( e );\n    throw detail::with_throw_location<typename std::decay<E>::type>( std::forward<E>( e ), loc );\n}\n\n#else\n\ntemplate<class E> BOOST_NORETURN void throw_with_location( E const & e, boost::source_location const & loc = BOOST_CURRENT_LOCATION )\n{\n    throw_exception_assert_compatibility( e );\n    throw detail::with_throw_location<E>( e, loc );\n}\n\n#endif\n\n#else\n\ntemplate<class E> BOOST_NORETURN void throw_with_location( E const & e, boost::source_location const & loc = BOOST_CURRENT_LOCATION )\n{\n    boost::throw_exception( e, loc );\n}\n\n#endif\n\n// get_throw_location\n\ntemplate<class E> boost::source_location get_throw_location( E const & e )\n{\n#ifdef BOOST_NO_RTTI\n\n    (void)e;\n    return boost::source_location();\n\n#else\n\n    if( detail::throw_location const* pl = dynamic_cast< detail::throw_location const* >( &e ) )\n    {\n        return pl->location_;\n    }\n    else if( boost::exception const* px = dynamic_cast< boost::exception const* >( &e ) )\n    {\n        return exception_detail::get_exception_throw_location( *px );\n    }\n    else\n    {\n        return boost::source_location();\n    }\n\n#endif\n}\n\n} // namespace boost\n\n#endif // #ifndef BOOST_THROW_EXCEPTION_HPP_INCLUDED\n// Copyright (C) 2023 Braden Ganetsky\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_DETAIL_THROW_EXCEPTION_HPP\n#define BOOST_UNORDERED_DETAIL_THROW_EXCEPTION_HPP\n\n#pragma once\n\nnamespace boost {\n  namespace unordered {\n    namespace detail {\n\n      BOOST_NOINLINE BOOST_NORETURN inline void throw_out_of_range(\n        char const* message)\n      {\n        boost::throw_exception(std::out_of_range(message));\n      }\n\n    } // namespace detail\n  } // namespace unordered\n} // namespace boost\n\n#endif // BOOST_UNORDERED_DETAIL_THROW_EXCEPTION_HPP\n// Copyright (C) 2022 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_FLAT_MAP_FWD_HPP_INCLUDED\n#define BOOST_UNORDERED_FLAT_MAP_FWD_HPP_INCLUDED\n\n#pragma once\n\nnamespace boost {\n  namespace unordered {\n    template <class Key, class T, class Hash = boost::hash<Key>,\n      class KeyEqual = std::equal_to<Key>,\n      class Allocator = std::allocator<std::pair<const Key, T> > >\n    class unordered_flat_map;\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    void swap(unordered_flat_map<Key, T, Hash, KeyEqual, Allocator>& lhs,\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator>& rhs)\n      noexcept(noexcept(lhs.swap(rhs)));\n  } // namespace unordered\n\n  using boost::unordered::unordered_flat_map;\n} // namespace boost\n\n#endif\n// Copyright (C) 2022-2023 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_UNORDERED_FLAT_MAP_HPP_INCLUDED\n#define BOOST_UNORDERED_UNORDERED_FLAT_MAP_HPP_INCLUDED\n\n#pragma once\n\nnamespace boost {\n  namespace unordered {\n\n#ifdef BOOST_MSVC\n#pragma warning(push)\n#pragma warning(disable : 4714)\n#endif\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    class unordered_flat_map\n    {\n      template <class Key2, class T2, class Hash2, class Pred2,\n        class Allocator2>\n      friend class concurrent_flat_map;\n\n      using map_types = detail::foa::flat_map_types<Key, T>;\n\n      using table_type = detail::foa::table<map_types, Hash, KeyEqual,\n        typename std::allocator_traits<Allocator>::template rebind_alloc<\n          typename map_types::value_type>>;\n\n      table_type table_;\n\n      template <class K, class V, class H, class KE, class A>\n      bool friend operator==(unordered_flat_map<K, V, H, KE, A> const& lhs,\n        unordered_flat_map<K, V, H, KE, A> const& rhs);\n\n      template <class K, class V, class H, class KE, class A, class Pred>\n      typename unordered_flat_map<K, V, H, KE, A>::size_type friend erase_if(\n        unordered_flat_map<K, V, H, KE, A>& set, Pred pred);\n\n    public:\n      using key_type = Key;\n      using mapped_type = T;\n      using value_type = typename map_types::value_type;\n      using init_type = typename map_types::init_type;\n      using size_type = std::size_t;\n      using difference_type = std::ptrdiff_t;\n      using hasher = typename boost::unordered::detail::type_identity<Hash>::type;\n      using key_equal = typename boost::unordered::detail::type_identity<KeyEqual>::type;\n      using allocator_type = typename boost::unordered::detail::type_identity<Allocator>::type;\n      using reference = value_type&;\n      using const_reference = value_type const&;\n      using pointer = typename std::allocator_traits<allocator_type>::pointer;\n      using const_pointer =\n        typename std::allocator_traits<allocator_type>::const_pointer;\n      using iterator = typename table_type::iterator;\n      using const_iterator = typename table_type::const_iterator;\n\n      unordered_flat_map() : unordered_flat_map(0) {}\n\n      explicit unordered_flat_map(size_type n, hasher const& h = hasher(),\n        key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : table_(n, h, pred, a)\n      {\n      }\n\n      unordered_flat_map(size_type n, allocator_type const& a)\n          : unordered_flat_map(n, hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_flat_map(size_type n, hasher const& h, allocator_type const& a)\n          : unordered_flat_map(n, h, key_equal(), a)\n      {\n      }\n\n      template <class InputIterator>\n      unordered_flat_map(\n        InputIterator f, InputIterator l, allocator_type const& a)\n          : unordered_flat_map(f, l, size_type(0), hasher(), key_equal(), a)\n      {\n      }\n\n      explicit unordered_flat_map(allocator_type const& a)\n          : unordered_flat_map(0, a)\n      {\n      }\n\n      template <class Iterator>\n      unordered_flat_map(Iterator first, Iterator last, size_type n = 0,\n        hasher const& h = hasher(), key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : unordered_flat_map(n, h, pred, a)\n      {\n        this->insert(first, last);\n      }\n\n      template <class Iterator>\n      unordered_flat_map(\n        Iterator first, Iterator last, size_type n, allocator_type const& a)\n          : unordered_flat_map(first, last, n, hasher(), key_equal(), a)\n      {\n      }\n\n      template <class Iterator>\n      unordered_flat_map(Iterator first, Iterator last, size_type n,\n        hasher const& h, allocator_type const& a)\n          : unordered_flat_map(first, last, n, h, key_equal(), a)\n      {\n      }\n\n      unordered_flat_map(unordered_flat_map const& other) : table_(other.table_)\n      {\n      }\n\n      unordered_flat_map(\n        unordered_flat_map const& other, allocator_type const& a)\n          : table_(other.table_, a)\n      {\n      }\n\n      unordered_flat_map(unordered_flat_map&& other)\n        noexcept(std::is_nothrow_move_constructible<table_type>::value)\n          : table_(std::move(other.table_))\n      {\n      }\n\n      unordered_flat_map(unordered_flat_map&& other, allocator_type const& al)\n          : table_(std::move(other.table_), al)\n      {\n      }\n\n      unordered_flat_map(std::initializer_list<value_type> ilist,\n        size_type n = 0, hasher const& h = hasher(),\n        key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : unordered_flat_map(ilist.begin(), ilist.end(), n, h, pred, a)\n      {\n      }\n\n      unordered_flat_map(\n        std::initializer_list<value_type> il, allocator_type const& a)\n          : unordered_flat_map(il, size_type(0), hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_flat_map(std::initializer_list<value_type> init, size_type n,\n        allocator_type const& a)\n          : unordered_flat_map(init, n, hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_flat_map(std::initializer_list<value_type> init, size_type n,\n        hasher const& h, allocator_type const& a)\n          : unordered_flat_map(init, n, h, key_equal(), a)\n      {\n      }\n\n      unordered_flat_map(\n        concurrent_flat_map<Key, T, Hash, KeyEqual, Allocator>&& other)\n          : table_(std::move(other.table_))\n      {\n      }\n\n      ~unordered_flat_map() = default;\n\n      unordered_flat_map& operator=(unordered_flat_map const& other)\n      {\n        table_ = other.table_;\n        return *this;\n      }\n\n      unordered_flat_map& operator=(unordered_flat_map&& other) noexcept(\n        noexcept(std::declval<table_type&>() = std::declval<table_type&&>()))\n      {\n        table_ = std::move(other.table_);\n        return *this;\n      }\n\n      allocator_type get_allocator() const noexcept\n      {\n        return table_.get_allocator();\n      }\n\n      /// Iterators\n      ///\n\n      iterator begin() noexcept { return table_.begin(); }\n      const_iterator begin() const noexcept { return table_.begin(); }\n      const_iterator cbegin() const noexcept { return table_.cbegin(); }\n\n      iterator end() noexcept { return table_.end(); }\n      const_iterator end() const noexcept { return table_.end(); }\n      const_iterator cend() const noexcept { return table_.cend(); }\n\n      /// Capacity\n      ///\n\n      [[nodiscard]] bool empty() const noexcept\n      {\n        return table_.empty();\n      }\n\n      size_type size() const noexcept { return table_.size(); }\n\n      size_type max_size() const noexcept { return table_.max_size(); }\n\n      /// Modifiers\n      ///\n\n      void clear() noexcept { table_.clear(); }\n\n      template <class Ty>\n      BOOST_FORCEINLINE auto insert(Ty&& value)\n        -> decltype(table_.insert(std::forward<Ty>(value)))\n      {\n        return table_.insert(std::forward<Ty>(value));\n      }\n\n      BOOST_FORCEINLINE std::pair<iterator, bool> insert(init_type&& value)\n      {\n        return table_.insert(std::move(value));\n      }\n\n      template <class Ty>\n      BOOST_FORCEINLINE auto insert(const_iterator, Ty&& value)\n        -> decltype(table_.insert(std::forward<Ty>(value)).first)\n      {\n        return table_.insert(std::forward<Ty>(value)).first;\n      }\n\n      BOOST_FORCEINLINE iterator insert(const_iterator, init_type&& value)\n      {\n        return table_.insert(std::move(value)).first;\n      }\n\n      template <class InputIterator>\n      BOOST_FORCEINLINE void insert(InputIterator first, InputIterator last)\n      {\n        for (auto pos = first; pos != last; ++pos) {\n          table_.emplace(*pos);\n        }\n      }\n\n      void insert(std::initializer_list<value_type> ilist)\n      {\n        this->insert(ilist.begin(), ilist.end());\n      }\n\n      template <class M>\n      std::pair<iterator, bool> insert_or_assign(key_type const& key, M&& obj)\n      {\n        auto ibp = table_.try_emplace(key, std::forward<M>(obj));\n        if (ibp.second) {\n          return ibp;\n        }\n        ibp.first->second = std::forward<M>(obj);\n        return ibp;\n      }\n\n      template <class M>\n      std::pair<iterator, bool> insert_or_assign(key_type&& key, M&& obj)\n      {\n        auto ibp = table_.try_emplace(std::move(key), std::forward<M>(obj));\n        if (ibp.second) {\n          return ibp;\n        }\n        ibp.first->second = std::forward<M>(obj);\n        return ibp;\n      }\n\n      template <class K, class M>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<iterator, bool> >::type\n      insert_or_assign(K&& k, M&& obj)\n      {\n        auto ibp = table_.try_emplace(std::forward<K>(k), std::forward<M>(obj));\n        if (ibp.second) {\n          return ibp;\n        }\n        ibp.first->second = std::forward<M>(obj);\n        return ibp;\n      }\n\n      template <class M>\n      iterator insert_or_assign(const_iterator, key_type const& key, M&& obj)\n      {\n        return this->insert_or_assign(key, std::forward<M>(obj)).first;\n      }\n\n      template <class M>\n      iterator insert_or_assign(const_iterator, key_type&& key, M&& obj)\n      {\n        return this->insert_or_assign(std::move(key), std::forward<M>(obj))\n          .first;\n      }\n\n      template <class K, class M>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        iterator>::type\n      insert_or_assign(const_iterator, K&& k, M&& obj)\n      {\n        return this->insert_or_assign(std::forward<K>(k), std::forward<M>(obj))\n          .first;\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE std::pair<iterator, bool> emplace(Args&&... args)\n      {\n        return table_.emplace(std::forward<Args>(args)...);\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE iterator emplace_hint(const_iterator, Args&&... args)\n      {\n        return table_.emplace(std::forward<Args>(args)...).first;\n      }\n      \n// Lynx Add Begin\n      BOOST_FORCEINLINE std::pair<iterator, bool> try_emplace_plain_bytes_key(\n        key_type const& key)\n      {\n        return table_.try_emplace_plain_bytes_key(key);\n      }\n// Lynx Add End\n\n      template <class... Args>\n      BOOST_FORCEINLINE std::pair<iterator, bool> try_emplace(\n        key_type const& key, Args&&... args)\n      {\n        return table_.try_emplace(key, std::forward<Args>(args)...);\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE std::pair<iterator, bool> try_emplace(\n        key_type&& key, Args&&... args)\n      {\n        return table_.try_emplace(std::move(key), std::forward<Args>(args)...);\n      }\n\n      template <class K, class... Args>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::transparent_non_iterable<K,\n          unordered_flat_map>::value,\n        std::pair<iterator, bool> >::type\n      try_emplace(K&& key, Args&&... args)\n      {\n        return table_.try_emplace(\n          std::forward<K>(key), std::forward<Args>(args)...);\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE iterator try_emplace(\n        const_iterator, key_type const& key, Args&&... args)\n      {\n        return table_.try_emplace(key, std::forward<Args>(args)...).first;\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE iterator try_emplace(\n        const_iterator, key_type&& key, Args&&... args)\n      {\n        return table_.try_emplace(std::move(key), std::forward<Args>(args)...)\n          .first;\n      }\n\n      template <class K, class... Args>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::transparent_non_iterable<K,\n          unordered_flat_map>::value,\n        iterator>::type\n      try_emplace(const_iterator, K&& key, Args&&... args)\n      {\n        return table_\n          .try_emplace(std::forward<K>(key), std::forward<Args>(args)...)\n          .first;\n      }\n\n      BOOST_FORCEINLINE typename table_type::erase_return_type erase(\n        iterator pos)\n      {\n        return table_.erase(pos);\n      }\n\n      BOOST_FORCEINLINE typename table_type::erase_return_type erase(\n        const_iterator pos)\n      {\n        return table_.erase(pos);\n      }\n\n      iterator erase(const_iterator first, const_iterator last)\n      {\n        while (first != last) {\n          this->erase(first++);\n        }\n        return iterator{detail::foa::const_iterator_cast_tag{}, last};\n      }\n\n      BOOST_FORCEINLINE size_type erase(key_type const& key)\n      {\n        return table_.erase(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::transparent_non_iterable<K, unordered_flat_map>::value,\n        size_type>::type\n      erase(K const& key)\n      {\n        return table_.erase(key);\n      }\n\n      void swap(unordered_flat_map& rhs) noexcept(\n        noexcept(std::declval<table_type&>().swap(std::declval<table_type&>())))\n      {\n        table_.swap(rhs.table_);\n      }\n\n      template <class H2, class P2>\n      void merge(\n        unordered_flat_map<key_type, mapped_type, H2, P2, allocator_type>&\n          source)\n      {\n        table_.merge(source.table_);\n      }\n\n      template <class H2, class P2>\n      void merge(\n        unordered_flat_map<key_type, mapped_type, H2, P2, allocator_type>&&\n          source)\n      {\n        table_.merge(std::move(source.table_));\n      }\n\n      /// Lookup\n      ///\n\n      mapped_type& at(key_type const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos != table_.end()) {\n          return pos->second;\n        }\n        // TODO: someday refactor this to conditionally serialize the key and\n        // include it in the error message\n        //\n        boost::unordered::detail::throw_out_of_range(\n          \"key was not found in unordered_flat_map\");\n      }\n\n      mapped_type const& at(key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos != table_.end()) {\n          return pos->second;\n        }\n        boost::unordered::detail::throw_out_of_range(\n          \"key was not found in unordered_flat_map\");\n      }\n\n      template <class K>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        mapped_type&>::type\n      at(K&& key)\n      {\n        auto pos = table_.find(std::forward<K>(key));\n        if (pos != table_.end()) {\n          return pos->second;\n        }\n        boost::unordered::detail::throw_out_of_range(\n          \"key was not found in unordered_flat_map\");\n      }\n\n      template <class K>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        mapped_type const&>::type\n      at(K&& key) const\n      {\n        auto pos = table_.find(std::forward<K>(key));\n        if (pos != table_.end()) {\n          return pos->second;\n        }\n        boost::unordered::detail::throw_out_of_range(\n          \"key was not found in unordered_flat_map\");\n      }\n\n      BOOST_FORCEINLINE mapped_type& operator[](key_type const& key)\n      {\n        return table_.try_emplace(key).first->second;\n      }\n\n      BOOST_FORCEINLINE mapped_type& operator[](key_type&& key)\n      {\n        return table_.try_emplace(std::move(key)).first->second;\n      }\n\n      template <class K>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        mapped_type&>::type\n      operator[](K&& key)\n      {\n        return table_.try_emplace(std::forward<K>(key)).first->second;\n      }\n\n      BOOST_FORCEINLINE size_type count(key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        return pos != table_.end() ? 1 : 0;\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value, size_type>::type\n      count(K const& key) const\n      {\n        auto pos = table_.find(key);\n        return pos != table_.end() ? 1 : 0;\n      }\n\n      BOOST_FORCEINLINE iterator find(key_type const& key)\n      {\n        return table_.find(key);\n      }\n\n      BOOST_FORCEINLINE const_iterator find(key_type const& key) const\n      {\n        return table_.find(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        iterator>::type\n      find(K const& key)\n      {\n        return table_.find(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        const_iterator>::type\n      find(K const& key) const\n      {\n        return table_.find(key);\n      }\n\n      BOOST_FORCEINLINE bool contains(key_type const& key) const\n      {\n        return this->find(key) != this->end();\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        bool>::type\n      contains(K const& key) const\n      {\n        return this->find(key) != this->end();\n      }\n\n      std::pair<iterator, iterator> equal_range(key_type const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      std::pair<const_iterator, const_iterator> equal_range(\n        key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      template <class K>\n      typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<iterator, iterator> >::type\n      equal_range(K const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      template <class K>\n      typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<const_iterator, const_iterator> >::type\n      equal_range(K const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      /// Hash Policy\n      ///\n\n      size_type bucket_count() const noexcept { return table_.capacity(); }\n\n      float load_factor() const noexcept { return table_.load_factor(); }\n\n      float max_load_factor() const noexcept\n      {\n        return table_.max_load_factor();\n      }\n\n      void max_load_factor(float) {}\n\n      size_type max_load() const noexcept { return table_.max_load(); }\n\n      void rehash(size_type n) { table_.rehash(n); }\n\n      void reserve(size_type n) { table_.reserve(n); }\n\n      /// Observers\n      ///\n\n      hasher hash_function() const { return table_.hash_function(); }\n\n      key_equal key_eq() const { return table_.key_eq(); }\n    };\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)\n    {\n      return lhs.table_ == rhs.table_;\n    }\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)\n    {\n      return !(lhs == rhs);\n    }\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    void swap(unordered_flat_map<Key, T, Hash, KeyEqual, Allocator>& lhs,\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator>& rhs)\n      noexcept(noexcept(lhs.swap(rhs)))\n    {\n      lhs.swap(rhs);\n    }\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator,\n      class Pred>\n    typename unordered_flat_map<Key, T, Hash, KeyEqual, Allocator>::size_type\n    erase_if(\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator>& map, Pred pred)\n    {\n      return erase_if(map.table_, pred);\n    }\n\n    template <class Archive, class Key, class T, class Hash, class KeyEqual,\n      class Allocator>\n    void serialize(Archive& ar,\n      unordered_flat_map<Key, T, Hash, KeyEqual, Allocator>& map,\n      unsigned int version)\n    {\n    }\n\n#ifdef BOOST_MSVC\n#pragma warning(pop)\n#endif\n\n#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES\n\n    template <class InputIterator,\n      class Hash =\n        boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,\n      class Pred =\n        std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,\n      class Allocator = std::allocator<\n        boost::unordered::detail::iter_to_alloc_t<InputIterator> >,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_pred_v<Pred> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_map(InputIterator, InputIterator,\n      std::size_t = boost::unordered::detail::foa::default_bucket_count,\n      Hash = Hash(), Pred = Pred(), Allocator = Allocator())\n      -> unordered_flat_map<boost::unordered::detail::iter_key_t<InputIterator>,\n        boost::unordered::detail::iter_val_t<InputIterator>, Hash, Pred,\n        Allocator>;\n\n    template <class Key, class T,\n      class Hash = boost::hash<std::remove_const_t<Key> >,\n      class Pred = std::equal_to<std::remove_const_t<Key> >,\n      class Allocator = std::allocator<std::pair<const Key, T> >,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_pred_v<Pred> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_map(std::initializer_list<std::pair<Key, T> >,\n      std::size_t = boost::unordered::detail::foa::default_bucket_count,\n      Hash = Hash(), Pred = Pred(), Allocator = Allocator())\n      -> unordered_flat_map<std::remove_const_t<Key>, T, Hash, Pred,\n        Allocator>;\n\n    template <class InputIterator, class Allocator,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_map(InputIterator, InputIterator, std::size_t, Allocator)\n      -> unordered_flat_map<boost::unordered::detail::iter_key_t<InputIterator>,\n        boost::unordered::detail::iter_val_t<InputIterator>,\n        boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,\n        std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,\n        Allocator>;\n\n    template <class InputIterator, class Allocator,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_map(InputIterator, InputIterator, Allocator)\n      -> unordered_flat_map<boost::unordered::detail::iter_key_t<InputIterator>,\n        boost::unordered::detail::iter_val_t<InputIterator>,\n        boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,\n        std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,\n        Allocator>;\n\n    template <class InputIterator, class Hash, class Allocator,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_map(\n      InputIterator, InputIterator, std::size_t, Hash, Allocator)\n      -> unordered_flat_map<boost::unordered::detail::iter_key_t<InputIterator>,\n        boost::unordered::detail::iter_val_t<InputIterator>, Hash,\n        std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,\n        Allocator>;\n\n    template <class Key, class T, class Allocator,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_map(std::initializer_list<std::pair<Key, T> >, std::size_t,\n      Allocator) -> unordered_flat_map<std::remove_const_t<Key>, T,\n      boost::hash<std::remove_const_t<Key> >,\n      std::equal_to<std::remove_const_t<Key> >, Allocator>;\n\n    template <class Key, class T, class Allocator,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_map(std::initializer_list<std::pair<Key, T> >, Allocator)\n      -> unordered_flat_map<std::remove_const_t<Key>, T,\n        boost::hash<std::remove_const_t<Key> >,\n        std::equal_to<std::remove_const_t<Key> >, Allocator>;\n\n    template <class Key, class T, class Hash, class Allocator,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_flat_map(std::initializer_list<std::pair<Key, T> >, std::size_t,\n      Hash, Allocator) -> unordered_flat_map<std::remove_const_t<Key>, T,\n      Hash, std::equal_to<std::remove_const_t<Key> >, Allocator>;\n#endif\n\n  } // namespace unordered\n} // namespace boost\n\n#endif\n/* Copyright 2023 Christian Mazakas.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifndef BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP\n#define BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP\n\nnamespace boost{\nnamespace unordered{\nnamespace detail{\nnamespace foa{\n\ntemplate<class T,class VoidPtr>\nstruct element_type\n{\n  using value_type=T;\n  using pointer=typename std::pointer_traits<VoidPtr>::template rebind<T>;\n\n  pointer p;\n\n  /*\n   * we use a deleted copy constructor here so the type is no longer\n   * trivially copy-constructible which inhibits our memcpy\n   * optimizations when copying the tables\n   */\n  element_type()=default;\n  element_type(pointer p_):p(p_){}\n  element_type(element_type const&)=delete;\n  element_type(element_type&& rhs)noexcept\n  {\n    p = rhs.p;\n    rhs.p = nullptr;\n  }\n\n  element_type& operator=(element_type const&)=delete;\n  element_type& operator=(element_type&& rhs)noexcept\n  {\n    if (this!=&rhs){\n      p=rhs.p;\n      rhs.p=nullptr;\n    }\n    return *this;\n  }\n\n  void swap(element_type& rhs)noexcept\n  {\n    auto tmp=p;\n    p=rhs.p;\n    rhs.p=tmp;\n  }\n};\n\n}\n}\n}\n}\n\n#endif // BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP\n/* Copyright 2023 Christian Mazakas.\n * Distributed under the Boost Software License, Version 1.0.\n * (See accompanying file LICENSE_1_0.txt or copy at\n * http://www.boost.org/LICENSE_1_0.txt)\n *\n * See https://www.boost.org/libs/unordered for library home page.\n */\n\n#ifndef BOOST_UNORDERED_DETAIL_FOA_NODE_HANDLE_HPP\n#define BOOST_UNORDERED_DETAIL_FOA_NODE_HANDLE_HPP\n\nnamespace boost{\nnamespace unordered{\nnamespace detail{\nnamespace foa{\n\ntemplate <class Iterator,class NodeType>\nstruct insert_return_type\n{\n  Iterator position;\n  bool     inserted;\n  NodeType node;\n};\n\ntemplate <class TypePolicy,class Allocator>\nstruct node_handle_base\n{\n  protected:\n    using type_policy=TypePolicy;\n    using element_type=typename type_policy::element_type;\n\n  public:\n    using allocator_type = Allocator;\n\n  private:\n    using node_value_type=typename type_policy::value_type;\n    element_type p_;\n    CXX20_NO_UNIQUE_ADDRESS opt_storage<Allocator> a_;\n\n  protected:\n    node_value_type& data()noexcept\n    {\n      return *(p_.p);\n    }\n\n    node_value_type const& data()const noexcept\n    {\n      return *(p_.p);\n    }\n\n    element_type& element()noexcept\n    {\n      BOOST_ASSERT(!empty());\n      return p_;\n    }\n\n    element_type const& element()const noexcept\n    {\n      BOOST_ASSERT(!empty());\n      return p_;\n    }\n\n    Allocator& al()noexcept\n    {\n      BOOST_ASSERT(!empty());\n      return a_.t_;\n    }\n\n    Allocator const& al()const noexcept\n    {\n      BOOST_ASSERT(!empty());\n      return a_.t_;\n    }\n\n    void emplace(element_type&& x,Allocator a)\n    {\n      BOOST_ASSERT(empty());\n      auto* p=x.p;\n      p_.p=p;\n      new(&a_.t_)Allocator(a);\n      x.p=nullptr;\n    }\n\n    void reset()\n    {\n      a_.t_.~Allocator();\n      p_.p=nullptr;\n    }\n\n  public:\n    constexpr node_handle_base()noexcept:p_{nullptr}{}\n\n    node_handle_base(node_handle_base&& nh) noexcept\n    {\n      p_.p = nullptr;\n      if (!nh.empty()){\n        emplace(std::move(nh.p_),nh.al());\n        nh.reset();\n      }\n    }\n\n    node_handle_base& operator=(node_handle_base&& nh)noexcept\n    {\n      if(this!=&nh){\n        if(empty()){\n          if(nh.empty()){\n            /* nothing to do */\n          }else{\n            emplace(std::move(nh.p_),std::move(nh.al()));\n            nh.reset();\n          }\n        }else{\n          if(nh.empty()){\n            type_policy::destroy(al(),&p_);\n            reset();\n          }else{\n            bool const pocma=\n              std::allocator_traits<\n                Allocator>::propagate_on_container_move_assignment::value;\n\n            BOOST_ASSERT(pocma||al()==nh.al());\n\n            type_policy::destroy(al(),&p_);\n            if(pocma){\n              al()=std::move(nh.al());\n            }\n\n            p_=std::move(nh.p_);\n            nh.reset();\n          }\n        }\n      }else{\n        if(empty()){\n          /* nothing to do */\n        }else{\n          type_policy::destroy(al(),&p_);\n          reset();\n        }\n      }\n      return *this;\n    }\n\n    ~node_handle_base()\n    {\n      if(!empty()){\n        type_policy::destroy(al(),&p_);\n        reset();\n      }\n    }\n\n    allocator_type get_allocator()const noexcept{return al();}\n    explicit operator bool()const noexcept{ return !empty();}\n    [[nodiscard]] bool empty()const noexcept{return p_.p==nullptr;}\n\n    void swap(node_handle_base& nh) noexcept(\n      std::allocator_traits<Allocator>::is_always_equal::value||\n      std::allocator_traits<Allocator>::propagate_on_container_swap::value)\n    {\n      if(this!=&nh){\n        if(empty()){\n          if(nh.empty()) {\n            /* nothing to do here */\n          } else {\n            emplace(std::move(nh.p_), nh.al());\n            nh.reset();\n          }\n        }else{\n          if(nh.empty()){\n            nh.emplace(std::move(p_),al());\n            reset();\n          }else{\n            bool const pocs=\n              std::allocator_traits<\n                Allocator>::propagate_on_container_swap::value;\n\n            BOOST_ASSERT(pocs || al()==nh.al());\n\n            using std::swap;\n            p_.swap(nh.p_);\n            if(pocs)swap(al(),nh.al());\n          }\n        }\n      }\n    }\n\n    friend\n    void swap(node_handle_base& lhs,node_handle_base& rhs)\n      noexcept(noexcept(lhs.swap(rhs)))\n    {\n      return lhs.swap(rhs);\n    }\n};\n\n}\n}\n}\n}\n\n#endif // BOOST_UNORDERED_DETAIL_FOA_NODE_HANDLE_HPP\n// Copyright (C) 2023 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_DETAIL_FOA_NODE_SET_TYPES_HPP\n#define BOOST_UNORDERED_DETAIL_FOA_NODE_SET_TYPES_HPP\n\nnamespace boost {\n  namespace unordered {\n    namespace detail {\n      namespace foa {\n\n        template <class Key, class VoidPtr> struct node_set_types\n        {\n          using key_type = Key;\n          using init_type = Key;\n          using value_type = Key;\n\n          static Key const& extract(value_type const& key) { return key; }\n\n          using element_type = foa::element_type<value_type, VoidPtr>;\n\n          static value_type& value_from(element_type const& x) { return *x.p; }\n          static Key const& extract(element_type const& k) { return *k.p; }\n          static element_type&& move(element_type& x) { return std::move(x); }\n          static value_type&& move(value_type& x) { return std::move(x); }\n\n          template <class A>\n          static void construct(\n            A& al, element_type* p, element_type const& copy)\n          {\n            construct(al, p, *copy.p);\n          }\n\n          template <typename Allocator>\n          static void construct(\n            Allocator&, element_type* p, element_type&& x) noexcept\n          {\n            p->p = x.p;\n            x.p = nullptr;\n          }\n\n          template <class A, class... Args>\n          static void construct(A& al, value_type* p, Args&&... args)\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::construct(al, p, std::forward<Args>(args)...);\n          }\n\n          template <class A, class... Args>\n          static void construct(A& al, element_type* p, Args&&... args)\n          {\n            p->p = std::allocator_traits<std::remove_cvref_t<decltype(al)>>::allocate(al, 1);\n            BOOST_TRY\n            {\n              std::allocator_traits<std::remove_cvref_t<decltype(\n                al)>>::construct(\n                al, std::to_address(p->p), std::forward<Args>(args)...);\n            }\n            BOOST_CATCH(...)\n            {\n              std::allocator_traits<std::remove_cvref_t<decltype(al)>>::deallocate(al, p->p, 1);\n              BOOST_RETHROW\n            }\n            BOOST_CATCH_END\n          }\n\n          template <class A> static void destroy(A& al, value_type* p) noexcept\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::destroy(al, p);\n          }\n\n          template <class A>\n          static void destroy(A& al, element_type* p) noexcept\n          {\n            if (p->p) {\n              destroy(al, std::to_address(p->p));\n              std::allocator_traits<std::remove_cvref_t<decltype(al)>>::deallocate(al, p->p, 1);\n            }\n          }\n        };\n\n      } // namespace foa\n    }   // namespace detail\n  }     // namespace unordered\n} // namespace boost\n\n#endif // BOOST_UNORDERED_DETAIL_FOA_NODE_SET_TYPES_HPP\n// Copyright (C) 2023 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_NODE_SET_FWD_HPP_INCLUDED\n#define BOOST_UNORDERED_NODE_SET_FWD_HPP_INCLUDED\n\n#pragma once\n\nnamespace boost {\n  namespace unordered {\n    template <class Key, class Hash = boost::hash<Key>,\n      class KeyEqual = std::equal_to<Key>,\n      class Allocator = std::allocator<Key> >\n    class unordered_node_set;\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      unordered_node_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_node_set<Key, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      unordered_node_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_node_set<Key, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    void swap(unordered_node_set<Key, Hash, KeyEqual, Allocator>& lhs,\n      unordered_node_set<Key, Hash, KeyEqual, Allocator>& rhs)\n      noexcept(noexcept(lhs.swap(rhs)));\n  } // namespace unordered\n\n  using boost::unordered::unordered_node_set;\n} // namespace boost\n\n#endif\n// Copyright (C) 2022-2023 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_UNORDERED_NODE_SET_HPP_INCLUDED\n#define BOOST_UNORDERED_UNORDERED_NODE_SET_HPP_INCLUDED\n\n#pragma once\n\nnamespace boost {\n  namespace unordered {\n\n#ifdef BOOST_MSVC\n#pragma warning(push)\n#pragma warning(disable : 4714)\n#endif\n\n    namespace detail {\n      template <class TypePolicy, class Allocator>\n      struct node_set_handle\n          : public detail::foa::node_handle_base<TypePolicy, Allocator>\n      {\n      private:\n        using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;\n\n        using typename base_type::type_policy;\n\n        template <class Key, class Hash, class Pred, class Alloc>\n        friend class boost::unordered::unordered_node_set;\n\n      public:\n        using value_type = typename TypePolicy::value_type;\n\n        constexpr node_set_handle() noexcept = default;\n        node_set_handle(node_set_handle&& nh) noexcept = default;\n        node_set_handle& operator=(node_set_handle&&) noexcept = default;\n\n        value_type& value() const\n        {\n          BOOST_ASSERT(!this->empty());\n          return const_cast<value_type&>(this->data());\n        }\n      };\n    } // namespace detail\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    class unordered_node_set\n    {\n      using set_types = detail::foa::node_set_types<Key,\n        typename std::allocator_traits<Allocator>::void_pointer>;\n\n      using table_type = detail::foa::table<set_types, Hash, KeyEqual,\n        typename std::allocator_traits<Allocator>::template rebind_alloc<\n          typename set_types::value_type>>;\n\n      table_type table_;\n\n      template <class K, class H, class KE, class A>\n      bool friend operator==(unordered_node_set<K, H, KE, A> const& lhs,\n        unordered_node_set<K, H, KE, A> const& rhs);\n\n      template <class K, class H, class KE, class A, class Pred>\n      typename unordered_node_set<K, H, KE, A>::size_type friend erase_if(\n        unordered_node_set<K, H, KE, A>& set, Pred pred);\n\n    public:\n      using key_type = Key;\n      using value_type = typename set_types::value_type;\n      using init_type = typename set_types::init_type;\n      using size_type = std::size_t;\n      using difference_type = std::ptrdiff_t;\n      using hasher = Hash;\n      using key_equal = KeyEqual;\n      using allocator_type = Allocator;\n      using reference = value_type&;\n      using const_reference = value_type const&;\n      using pointer = typename std::allocator_traits<allocator_type>::pointer;\n      using const_pointer =\n        typename std::allocator_traits<allocator_type>::const_pointer;\n      using iterator = typename table_type::iterator;\n      using const_iterator = typename table_type::const_iterator;\n      using node_type = detail::node_set_handle<set_types,\n        typename std::allocator_traits<Allocator>::template rebind_alloc<\n          typename set_types::value_type>>;\n      using insert_return_type =\n        detail::foa::insert_return_type<iterator, node_type>;\n\n      unordered_node_set() : unordered_node_set(0) {}\n\n      explicit unordered_node_set(size_type n, hasher const& h = hasher(),\n        key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : table_(n, h, pred, a)\n      {\n      }\n\n      unordered_node_set(size_type n, allocator_type const& a)\n          : unordered_node_set(n, hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_node_set(size_type n, hasher const& h, allocator_type const& a)\n          : unordered_node_set(n, h, key_equal(), a)\n      {\n      }\n\n      template <class InputIterator>\n      unordered_node_set(\n        InputIterator f, InputIterator l, allocator_type const& a)\n          : unordered_node_set(f, l, size_type(0), hasher(), key_equal(), a)\n      {\n      }\n\n      explicit unordered_node_set(allocator_type const& a)\n          : unordered_node_set(0, a)\n      {\n      }\n\n      template <class Iterator>\n      unordered_node_set(Iterator first, Iterator last, size_type n = 0,\n        hasher const& h = hasher(), key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : unordered_node_set(n, h, pred, a)\n      {\n        this->insert(first, last);\n      }\n\n      template <class InputIt>\n      unordered_node_set(\n        InputIt first, InputIt last, size_type n, allocator_type const& a)\n          : unordered_node_set(first, last, n, hasher(), key_equal(), a)\n      {\n      }\n\n      template <class Iterator>\n      unordered_node_set(Iterator first, Iterator last, size_type n,\n        hasher const& h, allocator_type const& a)\n          : unordered_node_set(first, last, n, h, key_equal(), a)\n      {\n      }\n\n      unordered_node_set(unordered_node_set const& other) : table_(other.table_)\n      {\n      }\n\n      unordered_node_set(\n        unordered_node_set const& other, allocator_type const& a)\n          : table_(other.table_, a)\n      {\n      }\n\n      unordered_node_set(unordered_node_set&& other)\n        noexcept(std::is_nothrow_move_constructible<table_type>::value)\n          : table_(std::move(other.table_))\n      {\n      }\n\n      unordered_node_set(unordered_node_set&& other, allocator_type const& al)\n          : table_(std::move(other.table_), al)\n      {\n      }\n\n      unordered_node_set(std::initializer_list<value_type> ilist,\n        size_type n = 0, hasher const& h = hasher(),\n        key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : unordered_node_set(ilist.begin(), ilist.end(), n, h, pred, a)\n      {\n      }\n\n      unordered_node_set(\n        std::initializer_list<value_type> il, allocator_type const& a)\n          : unordered_node_set(il, size_type(0), hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_node_set(std::initializer_list<value_type> init, size_type n,\n        allocator_type const& a)\n          : unordered_node_set(init, n, hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_node_set(std::initializer_list<value_type> init, size_type n,\n        hasher const& h, allocator_type const& a)\n          : unordered_node_set(init, n, h, key_equal(), a)\n      {\n      }\n\n      ~unordered_node_set() = default;\n\n      unordered_node_set& operator=(unordered_node_set const& other)\n      {\n        table_ = other.table_;\n        return *this;\n      }\n\n      unordered_node_set& operator=(unordered_node_set&& other) noexcept(\n        noexcept(std::declval<table_type&>() = std::declval<table_type&&>()))\n      {\n        table_ = std::move(other.table_);\n        return *this;\n      }\n\n      allocator_type get_allocator() const noexcept\n      {\n        return table_.get_allocator();\n      }\n\n      /// Iterators\n      ///\n\n      iterator begin() noexcept { return table_.begin(); }\n      const_iterator begin() const noexcept { return table_.begin(); }\n      const_iterator cbegin() const noexcept { return table_.cbegin(); }\n\n      iterator end() noexcept { return table_.end(); }\n      const_iterator end() const noexcept { return table_.end(); }\n      const_iterator cend() const noexcept { return table_.cend(); }\n\n      /// Capacity\n      ///\n\n      [[nodiscard]] bool empty() const noexcept\n      {\n        return table_.empty();\n      }\n\n      size_type size() const noexcept { return table_.size(); }\n\n      size_type max_size() const noexcept { return table_.max_size(); }\n\n      /// Modifiers\n      ///\n\n      void clear() noexcept { table_.clear(); }\n\n      BOOST_FORCEINLINE std::pair<iterator, bool> insert(\n        value_type const& value)\n      {\n        return table_.insert(value);\n      }\n\n      BOOST_FORCEINLINE std::pair<iterator, bool> insert(value_type&& value)\n      {\n        return table_.insert(std::move(value));\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::transparent_non_iterable<K, unordered_node_set>::value,\n        std::pair<iterator, bool> >::type\n      insert(K&& k)\n      {\n        return table_.try_emplace(std::forward<K>(k));\n      }\n\n      BOOST_FORCEINLINE iterator insert(const_iterator, value_type const& value)\n      {\n        return table_.insert(value).first;\n      }\n\n      BOOST_FORCEINLINE iterator insert(const_iterator, value_type&& value)\n      {\n        return table_.insert(std::move(value)).first;\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::transparent_non_iterable<K, unordered_node_set>::value,\n        iterator>::type\n      insert(const_iterator, K&& k)\n      {\n        return table_.try_emplace(std::forward<K>(k)).first;\n      }\n\n      template <class InputIterator>\n      void insert(InputIterator first, InputIterator last)\n      {\n        for (auto pos = first; pos != last; ++pos) {\n          table_.emplace(*pos);\n        }\n      }\n\n      void insert(std::initializer_list<value_type> ilist)\n      {\n        this->insert(ilist.begin(), ilist.end());\n      }\n\n      insert_return_type insert(node_type&& nh)\n      {\n        if (nh.empty()) {\n          return {end(), false, node_type{}};\n        }\n\n        BOOST_ASSERT(get_allocator() == nh.get_allocator());\n\n        auto itp = table_.insert(std::move(nh.element()));\n        if (itp.second) {\n          nh.reset();\n          return {itp.first, true, node_type{}};\n        } else {\n          return {itp.first, false, std::move(nh)};\n        }\n      }\n\n      iterator insert(const_iterator, node_type&& nh)\n      {\n        if (nh.empty()) {\n          return end();\n        }\n\n        BOOST_ASSERT(get_allocator() == nh.get_allocator());\n\n        auto itp = table_.insert(std::move(nh.element()));\n        if (itp.second) {\n          nh.reset();\n          return itp.first;\n        } else {\n          return itp.first;\n        }\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE std::pair<iterator, bool> emplace(Args&&... args)\n      {\n        return table_.emplace(std::forward<Args>(args)...);\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE iterator emplace_hint(const_iterator, Args&&... args)\n      {\n        return table_.emplace(std::forward<Args>(args)...).first;\n      }\n\n      BOOST_FORCEINLINE typename table_type::erase_return_type erase(\n        const_iterator pos)\n      {\n        return table_.erase(pos);\n      }\n\n      iterator erase(const_iterator first, const_iterator last)\n      {\n        while (first != last) {\n          this->erase(first++);\n        }\n        return iterator{detail::foa::const_iterator_cast_tag{}, last};\n      }\n\n      BOOST_FORCEINLINE size_type erase(key_type const& key)\n      {\n        return table_.erase(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::transparent_non_iterable<K, unordered_node_set>::value,\n        size_type>::type\n      erase(K const& key)\n      {\n        return table_.erase(key);\n      }\n\n      void swap(unordered_node_set& rhs) noexcept(\n        noexcept(std::declval<table_type&>().swap(std::declval<table_type&>())))\n      {\n        table_.swap(rhs.table_);\n      }\n\n      node_type extract(const_iterator pos)\n      {\n        BOOST_ASSERT(pos != end());\n        node_type nh;\n        auto elem = table_.extract(pos);\n        nh.emplace(std::move(elem), get_allocator());\n        return nh;\n      }\n\n      node_type extract(key_type const& key)\n      {\n        auto pos = find(key);\n        return pos != end() ? extract(pos) : node_type();\n      }\n\n      template <class K>\n      typename std::enable_if<\n        boost::unordered::detail::transparent_non_iterable<K,\n          unordered_node_set>::value,\n        node_type>::type\n      extract(K const& key)\n      {\n        auto pos = find(key);\n        return pos != end() ? extract(pos) : node_type();\n      }\n\n      template <class H2, class P2>\n      void merge(unordered_node_set<key_type, H2, P2, allocator_type>& source)\n      {\n        BOOST_ASSERT(get_allocator() == source.get_allocator());\n        table_.merge(source.table_);\n      }\n\n      template <class H2, class P2>\n      void merge(unordered_node_set<key_type, H2, P2, allocator_type>&& source)\n      {\n        BOOST_ASSERT(get_allocator() == source.get_allocator());\n        table_.merge(std::move(source.table_));\n      }\n\n      /// Lookup\n      ///\n\n      BOOST_FORCEINLINE size_type count(key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        return pos != table_.end() ? 1 : 0;\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value, size_type>::type\n      count(K const& key) const\n      {\n        auto pos = table_.find(key);\n        return pos != table_.end() ? 1 : 0;\n      }\n\n      BOOST_FORCEINLINE iterator find(key_type const& key)\n      {\n        return table_.find(key);\n      }\n\n      BOOST_FORCEINLINE const_iterator find(key_type const& key) const\n      {\n        return table_.find(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        iterator>::type\n      find(K const& key)\n      {\n        return table_.find(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        const_iterator>::type\n      find(K const& key) const\n      {\n        return table_.find(key);\n      }\n\n      BOOST_FORCEINLINE bool contains(key_type const& key) const\n      {\n        return this->find(key) != this->end();\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        bool>::type\n      contains(K const& key) const\n      {\n        return this->find(key) != this->end();\n      }\n\n      std::pair<iterator, iterator> equal_range(key_type const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      std::pair<const_iterator, const_iterator> equal_range(\n        key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      template <class K>\n      typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<iterator, iterator> >::type\n      equal_range(K const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      template <class K>\n      typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<const_iterator, const_iterator> >::type\n      equal_range(K const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      /// Hash Policy\n      ///\n\n      size_type bucket_count() const noexcept { return table_.capacity(); }\n\n      float load_factor() const noexcept { return table_.load_factor(); }\n\n      float max_load_factor() const noexcept\n      {\n        return table_.max_load_factor();\n      }\n\n      void max_load_factor(float) {}\n\n      size_type max_load() const noexcept { return table_.max_load(); }\n\n      void rehash(size_type n) { table_.rehash(n); }\n\n      void reserve(size_type n) { table_.reserve(n); }\n\n      /// Observers\n      ///\n\n      hasher hash_function() const { return table_.hash_function(); }\n\n      key_equal key_eq() const { return table_.key_eq(); }\n    };\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      unordered_node_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_node_set<Key, Hash, KeyEqual, Allocator> const& rhs)\n    {\n      return lhs.table_ == rhs.table_;\n    }\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      unordered_node_set<Key, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_node_set<Key, Hash, KeyEqual, Allocator> const& rhs)\n    {\n      return !(lhs == rhs);\n    }\n\n    template <class Key, class Hash, class KeyEqual, class Allocator>\n    void swap(unordered_node_set<Key, Hash, KeyEqual, Allocator>& lhs,\n      unordered_node_set<Key, Hash, KeyEqual, Allocator>& rhs)\n      noexcept(noexcept(lhs.swap(rhs)))\n    {\n      lhs.swap(rhs);\n    }\n\n    template <class Key, class Hash, class KeyEqual, class Allocator,\n      class Pred>\n    typename unordered_node_set<Key, Hash, KeyEqual, Allocator>::size_type\n    erase_if(unordered_node_set<Key, Hash, KeyEqual, Allocator>& set, Pred pred)\n    {\n      return erase_if(set.table_, pred);\n    }\n\n    template <class Archive, class Key, class Hash, class KeyEqual,\n      class Allocator>\n    void serialize(Archive& ar,\n      unordered_node_set<Key, Hash, KeyEqual, Allocator>& set,\n      unsigned int version)\n    {\n    }\n\n#ifdef BOOST_MSVC\n#pragma warning(pop)\n#endif\n\n#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES\n    template <class InputIterator,\n      class Hash =\n        boost::hash<typename std::iterator_traits<InputIterator>::value_type>,\n      class Pred =\n        std::equal_to<typename std::iterator_traits<InputIterator>::value_type>,\n      class Allocator = std::allocator<\n        typename std::iterator_traits<InputIterator>::value_type>,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_pred_v<Pred> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_set(InputIterator, InputIterator,\n      std::size_t = boost::unordered::detail::foa::default_bucket_count,\n      Hash = Hash(), Pred = Pred(), Allocator = Allocator())\n      -> unordered_node_set<\n        typename std::iterator_traits<InputIterator>::value_type, Hash, Pred,\n        Allocator>;\n\n    template <class T, class Hash = boost::hash<T>,\n      class Pred = std::equal_to<T>, class Allocator = std::allocator<T>,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_pred_v<Pred> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_set(std::initializer_list<T>,\n      std::size_t = boost::unordered::detail::foa::default_bucket_count,\n      Hash = Hash(), Pred = Pred(), Allocator = Allocator())\n      -> unordered_node_set<T, Hash, Pred, Allocator>;\n\n    template <class InputIterator, class Allocator,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_set(InputIterator, InputIterator, std::size_t, Allocator)\n      -> unordered_node_set<\n        typename std::iterator_traits<InputIterator>::value_type,\n        boost::hash<typename std::iterator_traits<InputIterator>::value_type>,\n        std::equal_to<typename std::iterator_traits<InputIterator>::value_type>,\n        Allocator>;\n\n    template <class InputIterator, class Hash, class Allocator,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_set(\n      InputIterator, InputIterator, std::size_t, Hash, Allocator)\n      -> unordered_node_set<\n        typename std::iterator_traits<InputIterator>::value_type, Hash,\n        std::equal_to<typename std::iterator_traits<InputIterator>::value_type>,\n        Allocator>;\n\n    template <class T, class Allocator,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_set(std::initializer_list<T>, std::size_t, Allocator)\n      -> unordered_node_set<T, boost::hash<T>, std::equal_to<T>, Allocator>;\n\n    template <class T, class Hash, class Allocator,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_set(std::initializer_list<T>, std::size_t, Hash, Allocator)\n      -> unordered_node_set<T, Hash, std::equal_to<T>, Allocator>;\n\n    template <class InputIterator, class Allocator,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_set(InputIterator, InputIterator, Allocator)\n      -> unordered_node_set<\n        typename std::iterator_traits<InputIterator>::value_type,\n        boost::hash<typename std::iterator_traits<InputIterator>::value_type>,\n        std::equal_to<typename std::iterator_traits<InputIterator>::value_type>,\n        Allocator>;\n\n    template <class T, class Allocator,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_set(std::initializer_list<T>, Allocator)\n      -> unordered_node_set<T, boost::hash<T>, std::equal_to<T>, Allocator>;\n#endif\n\n  } // namespace unordered\n} // namespace boost\n\n#endif\n// Copyright (C) 2023 Christian Mazakas\n// Copyright (C) 2024 Braden Ganetsky\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_DETAIL_FOA_NODE_MAP_TYPES_HPP\n#define BOOST_UNORDERED_DETAIL_FOA_NODE_MAP_TYPES_HPP\n\nnamespace boost {\n  namespace unordered {\n    namespace detail {\n      namespace foa {\n        template <class Key, class T, class VoidPtr> struct node_map_types\n        {\n          using key_type = Key;\n          using mapped_type = T;\n          using raw_key_type = typename std::remove_const<Key>::type;\n          using raw_mapped_type = typename std::remove_const<T>::type;\n\n          using init_type = std::pair<raw_key_type, raw_mapped_type>;\n          using value_type = std::pair<Key const, T>;\n          using moved_type = std::pair<raw_key_type&&, raw_mapped_type&&>;\n\n          using element_type = foa::element_type<value_type, VoidPtr>;\n\n          static value_type& value_from(element_type const& x)\n          {\n            return *(x.p);\n          }\n\n          template <class K, class V>\n          static raw_key_type const& extract(std::pair<K, V> const& kv)\n          {\n            return kv.first;\n          }\n\n          static raw_key_type const& extract(element_type const& kv)\n          {\n            return kv.p->first;\n          }\n\n          static element_type&& move(element_type& x) { return std::move(x); }\n          static moved_type move(init_type& x)\n          {\n            return {std::move(x.first), std::move(x.second)};\n          }\n\n          static moved_type move(value_type& x)\n          {\n            return {std::move(const_cast<raw_key_type&>(x.first)),\n              std::move(const_cast<raw_mapped_type&>(x.second))};\n          }\n\n          template <class A>\n          static void construct(A&, element_type* p, element_type&& x) noexcept\n          {\n            p->p = x.p;\n            x.p = nullptr;\n          }\n\n          template <class A>\n          static void construct(\n            A& al, element_type* p, element_type const& copy)\n          {\n            construct(al, p, *copy.p);\n          }\n\n          template <class A, class... Args>\n          static void construct(A& al, init_type* p, Args&&... args)\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::construct(al, p, std::forward<Args>(args)...);\n          }\n\n          template <class A, class... Args>\n          static void construct(A& al, value_type* p, Args&&... args)\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::construct(al, p, std::forward<Args>(args)...);\n          }\n\n          template <class A, class... Args>\n          static void construct(A& al, key_type* p, Args&&... args)\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::construct(al, p, std::forward<Args>(args)...);\n          }\n\n          template <class A, class... Args>\n          static void construct(A& al, element_type* p, Args&&... args)\n          {\n            p->p = std::allocator_traits<std::remove_cvref_t<decltype(al)>>::allocate(al, 1);\n            BOOST_TRY\n            {\n              std::allocator_traits<std::remove_cvref_t<decltype(\n                al)>>::construct(\n                al, std::to_address(p->p), std::forward<Args>(args)...);\n            }\n            BOOST_CATCH(...)\n            {\n              std::allocator_traits<std::remove_cvref_t<decltype(al)>>::deallocate(al, p->p, 1);\n              BOOST_RETHROW\n            }\n            BOOST_CATCH_END\n          }\n\n          template <class A> static void destroy(A& al, value_type* p) noexcept\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::destroy(al, p);\n          }\n\n          template <class A> static void destroy(A& al, init_type* p) noexcept\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::destroy(al, p);\n          }\n\n          template <class A> static void destroy(A& al, key_type* p) noexcept\n          {\n            std::allocator_traits<std::remove_cvref_t<decltype(al)>>::destroy(al, p);\n          }\n\n          template <class A>\n          static void destroy(A& al, element_type* p) noexcept\n          {\n            if (p->p) {\n              destroy(al, std::to_address(p->p));\n              std::allocator_traits<std::remove_cvref_t<decltype(al)>>::deallocate(al, p->p, 1);\n            }\n          }\n        };\n\n      } // namespace foa\n    }   // namespace detail\n  }     // namespace unordered\n} // namespace boost\n\n#endif // BOOST_UNORDERED_DETAIL_FOA_NODE_MAP_TYPES_HPP\n// Copyright (C) 2022 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_NODE_MAP_FWD_HPP_INCLUDED\n#define BOOST_UNORDERED_NODE_MAP_FWD_HPP_INCLUDED\n\n#pragma once\n\nnamespace boost {\n  namespace unordered {\n    template <class Key, class T, class Hash = boost::hash<Key>,\n      class KeyEqual = std::equal_to<Key>,\n      class Allocator = std::allocator<std::pair<const Key, T> > >\n    class unordered_node_map;\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& rhs);\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    void swap(unordered_node_map<Key, T, Hash, KeyEqual, Allocator>& lhs,\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator>& rhs)\n      noexcept(noexcept(lhs.swap(rhs)));\n  } // namespace unordered\n\n  using boost::unordered::unordered_node_map;\n} // namespace boost\n\n#endif\n// Copyright (C) 2022-2023 Christian Mazakas\n// Distributed under the Boost Software License, Version 1.0. (See accompanying\n// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)\n\n#ifndef BOOST_UNORDERED_UNORDERED_NODE_MAP_HPP_INCLUDED\n#define BOOST_UNORDERED_UNORDERED_NODE_MAP_HPP_INCLUDED\n\n#pragma once\n\nnamespace boost {\n  namespace unordered {\n\n#ifdef BOOST_MSVC\n#pragma warning(push)\n#pragma warning(disable : 4714)\n#endif\n\n    namespace detail {\n      template <class TypePolicy, class Allocator>\n      struct node_map_handle\n          : public detail::foa::node_handle_base<TypePolicy, Allocator>\n      {\n      private:\n        using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;\n\n        using typename base_type::type_policy;\n\n        template <class Key, class T, class Hash, class Pred, class Alloc>\n        friend class boost::unordered::unordered_node_map;\n\n      public:\n        using key_type = typename TypePolicy::key_type;\n        using mapped_type = typename TypePolicy::mapped_type;\n\n        constexpr node_map_handle() noexcept = default;\n        node_map_handle(node_map_handle&& nh) noexcept = default;\n\n        node_map_handle& operator=(node_map_handle&&) noexcept = default;\n\n        key_type& key() const\n        {\n          BOOST_ASSERT(!this->empty());\n          return const_cast<key_type&>(this->data().first);\n        }\n\n        mapped_type& mapped() const\n        {\n          BOOST_ASSERT(!this->empty());\n          return const_cast<mapped_type&>(this->data().second);\n        }\n      };\n    } // namespace detail\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    class unordered_node_map\n    {\n      using map_types = detail::foa::node_map_types<Key, T,\n        typename std::allocator_traits<Allocator>::void_pointer>;\n\n      using table_type = detail::foa::table<map_types, Hash, KeyEqual,\n        typename std::allocator_traits<Allocator>::template rebind_alloc<\n          std::pair<Key const, T> >>;\n\n      table_type table_;\n\n      template <class K, class V, class H, class KE, class A>\n      bool friend operator==(unordered_node_map<K, V, H, KE, A> const& lhs,\n        unordered_node_map<K, V, H, KE, A> const& rhs);\n\n      template <class K, class V, class H, class KE, class A, class Pred>\n      typename unordered_node_map<K, V, H, KE, A>::size_type friend erase_if(\n        unordered_node_map<K, V, H, KE, A>& set, Pred pred);\n\n    public:\n      using key_type = Key;\n      using mapped_type = T;\n      using value_type = typename map_types::value_type;\n      using init_type = typename map_types::init_type;\n      using size_type = std::size_t;\n      using difference_type = std::ptrdiff_t;\n      using hasher = typename boost::unordered::detail::type_identity<Hash>::type;\n      using key_equal = typename boost::unordered::detail::type_identity<KeyEqual>::type;\n      using allocator_type = typename boost::unordered::detail::type_identity<Allocator>::type;\n      using reference = value_type&;\n      using const_reference = value_type const&;\n      using pointer = typename std::allocator_traits<allocator_type>::pointer;\n      using const_pointer =\n        typename std::allocator_traits<allocator_type>::const_pointer;\n      using iterator = typename table_type::iterator;\n      using const_iterator = typename table_type::const_iterator;\n      using node_type = detail::node_map_handle<map_types,\n        typename std::allocator_traits<Allocator>::template rebind_alloc<\n          typename map_types::value_type>>;\n      using insert_return_type =\n        detail::foa::insert_return_type<iterator, node_type>;\n\n      unordered_node_map() : unordered_node_map(0) {}\n\n      explicit unordered_node_map(size_type n, hasher const& h = hasher(),\n        key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : table_(n, h, pred, a)\n      {\n      }\n\n      unordered_node_map(size_type n, allocator_type const& a)\n          : unordered_node_map(n, hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_node_map(size_type n, hasher const& h, allocator_type const& a)\n          : unordered_node_map(n, h, key_equal(), a)\n      {\n      }\n\n      template <class InputIterator>\n      unordered_node_map(\n        InputIterator f, InputIterator l, allocator_type const& a)\n          : unordered_node_map(f, l, size_type(0), hasher(), key_equal(), a)\n      {\n      }\n\n      explicit unordered_node_map(allocator_type const& a)\n          : unordered_node_map(0, a)\n      {\n      }\n\n      template <class Iterator>\n      unordered_node_map(Iterator first, Iterator last, size_type n = 0,\n        hasher const& h = hasher(), key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : unordered_node_map(n, h, pred, a)\n      {\n        this->insert(first, last);\n      }\n\n      template <class Iterator>\n      unordered_node_map(\n        Iterator first, Iterator last, size_type n, allocator_type const& a)\n          : unordered_node_map(first, last, n, hasher(), key_equal(), a)\n      {\n      }\n\n      template <class Iterator>\n      unordered_node_map(Iterator first, Iterator last, size_type n,\n        hasher const& h, allocator_type const& a)\n          : unordered_node_map(first, last, n, h, key_equal(), a)\n      {\n      }\n\n      unordered_node_map(unordered_node_map const& other) : table_(other.table_)\n      {\n      }\n\n      unordered_node_map(\n        unordered_node_map const& other, allocator_type const& a)\n          : table_(other.table_, a)\n      {\n      }\n\n      unordered_node_map(unordered_node_map&& other)\n        noexcept(std::is_nothrow_move_constructible<table_type>::value)\n          : table_(std::move(other.table_))\n      {\n      }\n\n      unordered_node_map(unordered_node_map&& other, allocator_type const& al)\n          : table_(std::move(other.table_), al)\n      {\n      }\n\n      unordered_node_map(std::initializer_list<value_type> ilist,\n        size_type n = 0, hasher const& h = hasher(),\n        key_equal const& pred = key_equal(),\n        allocator_type const& a = allocator_type())\n          : unordered_node_map(ilist.begin(), ilist.end(), n, h, pred, a)\n      {\n      }\n\n      unordered_node_map(\n        std::initializer_list<value_type> il, allocator_type const& a)\n          : unordered_node_map(il, size_type(0), hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_node_map(std::initializer_list<value_type> init, size_type n,\n        allocator_type const& a)\n          : unordered_node_map(init, n, hasher(), key_equal(), a)\n      {\n      }\n\n      unordered_node_map(std::initializer_list<value_type> init, size_type n,\n        hasher const& h, allocator_type const& a)\n          : unordered_node_map(init, n, h, key_equal(), a)\n      {\n      }\n\n      ~unordered_node_map() = default;\n\n      unordered_node_map& operator=(unordered_node_map const& other)\n      {\n        table_ = other.table_;\n        return *this;\n      }\n\n      unordered_node_map& operator=(unordered_node_map&& other) noexcept(\n        noexcept(std::declval<table_type&>() = std::declval<table_type&&>()))\n      {\n        table_ = std::move(other.table_);\n        return *this;\n      }\n\n      allocator_type get_allocator() const noexcept\n      {\n        return table_.get_allocator();\n      }\n\n      /// Iterators\n      ///\n\n      iterator begin() noexcept { return table_.begin(); }\n      const_iterator begin() const noexcept { return table_.begin(); }\n      const_iterator cbegin() const noexcept { return table_.cbegin(); }\n\n      iterator end() noexcept { return table_.end(); }\n      const_iterator end() const noexcept { return table_.end(); }\n      const_iterator cend() const noexcept { return table_.cend(); }\n\n      /// Capacity\n      ///\n\n      [[nodiscard]] bool empty() const noexcept\n      {\n        return table_.empty();\n      }\n\n      size_type size() const noexcept { return table_.size(); }\n\n      size_type max_size() const noexcept { return table_.max_size(); }\n\n      /// Modifiers\n      ///\n\n      void clear() noexcept { table_.clear(); }\n\n      template <class Ty>\n      BOOST_FORCEINLINE auto insert(Ty&& value)\n        -> decltype(table_.insert(std::forward<Ty>(value)))\n      {\n        return table_.insert(std::forward<Ty>(value));\n      }\n\n      BOOST_FORCEINLINE std::pair<iterator, bool> insert(init_type&& value)\n      {\n        return table_.insert(std::move(value));\n      }\n\n      template <class Ty>\n      BOOST_FORCEINLINE auto insert(const_iterator, Ty&& value)\n        -> decltype(table_.insert(std::forward<Ty>(value)).first)\n      {\n        return table_.insert(std::forward<Ty>(value)).first;\n      }\n\n      BOOST_FORCEINLINE iterator insert(const_iterator, init_type&& value)\n      {\n        return table_.insert(std::move(value)).first;\n      }\n\n      template <class InputIterator>\n      BOOST_FORCEINLINE void insert(InputIterator first, InputIterator last)\n      {\n        for (auto pos = first; pos != last; ++pos) {\n          table_.emplace(*pos);\n        }\n      }\n\n      void insert(std::initializer_list<value_type> ilist)\n      {\n        this->insert(ilist.begin(), ilist.end());\n      }\n\n      insert_return_type insert(node_type&& nh)\n      {\n        if (nh.empty()) {\n          return {end(), false, node_type{}};\n        }\n\n        BOOST_ASSERT(get_allocator() == nh.get_allocator());\n\n        auto itp = table_.insert(std::move(nh.element()));\n        if (itp.second) {\n          nh.reset();\n          return {itp.first, true, node_type{}};\n        } else {\n          return {itp.first, false, std::move(nh)};\n        }\n      }\n\n      iterator insert(const_iterator, node_type&& nh)\n      {\n        if (nh.empty()) {\n          return end();\n        }\n\n        BOOST_ASSERT(get_allocator() == nh.get_allocator());\n\n        auto itp = table_.insert(std::move(nh.element()));\n        if (itp.second) {\n          nh.reset();\n          return itp.first;\n        } else {\n          return itp.first;\n        }\n      }\n\n      template <class M>\n      std::pair<iterator, bool> insert_or_assign(key_type const& key, M&& obj)\n      {\n        auto ibp = table_.try_emplace(key, std::forward<M>(obj));\n        if (ibp.second) {\n          return ibp;\n        }\n        ibp.first->second = std::forward<M>(obj);\n        return ibp;\n      }\n\n      template <class M>\n      std::pair<iterator, bool> insert_or_assign(key_type&& key, M&& obj)\n      {\n        auto ibp = table_.try_emplace(std::move(key), std::forward<M>(obj));\n        if (ibp.second) {\n          return ibp;\n        }\n        ibp.first->second = std::forward<M>(obj);\n        return ibp;\n      }\n\n      template <class K, class M>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<iterator, bool> >::type\n      insert_or_assign(K&& k, M&& obj)\n      {\n        auto ibp = table_.try_emplace(std::forward<K>(k), std::forward<M>(obj));\n        if (ibp.second) {\n          return ibp;\n        }\n        ibp.first->second = std::forward<M>(obj);\n        return ibp;\n      }\n\n      template <class M>\n      iterator insert_or_assign(const_iterator, key_type const& key, M&& obj)\n      {\n        return this->insert_or_assign(key, std::forward<M>(obj)).first;\n      }\n\n      template <class M>\n      iterator insert_or_assign(const_iterator, key_type&& key, M&& obj)\n      {\n        return this->insert_or_assign(std::move(key), std::forward<M>(obj))\n          .first;\n      }\n\n      template <class K, class M>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        iterator>::type\n      insert_or_assign(const_iterator, K&& k, M&& obj)\n      {\n        return this->insert_or_assign(std::forward<K>(k), std::forward<M>(obj))\n          .first;\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE std::pair<iterator, bool> emplace(Args&&... args)\n      {\n        return table_.emplace(std::forward<Args>(args)...);\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE iterator emplace_hint(const_iterator, Args&&... args)\n      {\n        return table_.emplace(std::forward<Args>(args)...).first;\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE std::pair<iterator, bool> try_emplace(\n        key_type const& key, Args&&... args)\n      {\n        return table_.try_emplace(key, std::forward<Args>(args)...);\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE std::pair<iterator, bool> try_emplace(\n        key_type&& key, Args&&... args)\n      {\n        return table_.try_emplace(std::move(key), std::forward<Args>(args)...);\n      }\n\n      template <class K, class... Args>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::transparent_non_iterable<K,\n          unordered_node_map>::value,\n        std::pair<iterator, bool> >::type\n      try_emplace(K&& key, Args&&... args)\n      {\n        return table_.try_emplace(\n          std::forward<K>(key), std::forward<Args>(args)...);\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE iterator try_emplace(\n        const_iterator, key_type const& key, Args&&... args)\n      {\n        return table_.try_emplace(key, std::forward<Args>(args)...).first;\n      }\n\n      template <class... Args>\n      BOOST_FORCEINLINE iterator try_emplace(\n        const_iterator, key_type&& key, Args&&... args)\n      {\n        return table_.try_emplace(std::move(key), std::forward<Args>(args)...)\n          .first;\n      }\n\n      template <class K, class... Args>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::transparent_non_iterable<K,\n          unordered_node_map>::value,\n        iterator>::type\n      try_emplace(const_iterator, K&& key, Args&&... args)\n      {\n        return table_\n          .try_emplace(std::forward<K>(key), std::forward<Args>(args)...)\n          .first;\n      }\n\n      BOOST_FORCEINLINE typename table_type::erase_return_type erase(\n        iterator pos)\n      {\n        return table_.erase(pos);\n      }\n\n      BOOST_FORCEINLINE typename table_type::erase_return_type erase(\n        const_iterator pos)\n      {\n        return table_.erase(pos);\n      }\n\n      iterator erase(const_iterator first, const_iterator last)\n      {\n        while (first != last) {\n          this->erase(first++);\n        }\n        return iterator{detail::foa::const_iterator_cast_tag{}, last};\n      }\n\n      BOOST_FORCEINLINE size_type erase(key_type const& key)\n      {\n        return table_.erase(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::transparent_non_iterable<K, unordered_node_map>::value,\n        size_type>::type\n      erase(K const& key)\n      {\n        return table_.erase(key);\n      }\n\n      void swap(unordered_node_map& rhs) noexcept(\n        noexcept(std::declval<table_type&>().swap(std::declval<table_type&>())))\n      {\n        table_.swap(rhs.table_);\n      }\n\n      node_type extract(const_iterator pos)\n      {\n        BOOST_ASSERT(pos != end());\n        node_type nh;\n        auto elem = table_.extract(pos);\n        nh.emplace(std::move(elem), get_allocator());\n        return nh;\n      }\n\n      node_type extract(key_type const& key)\n      {\n        auto pos = find(key);\n        return pos != end() ? extract(pos) : node_type();\n      }\n\n      template <class K>\n      typename std::enable_if<\n        boost::unordered::detail::transparent_non_iterable<K,\n          unordered_node_map>::value,\n        node_type>::type\n      extract(K const& key)\n      {\n        auto pos = find(key);\n        return pos != end() ? extract(pos) : node_type();\n      }\n\n      template <class H2, class P2>\n      void merge(\n        unordered_node_map<key_type, mapped_type, H2, P2, allocator_type>&\n          source)\n      {\n        BOOST_ASSERT(get_allocator() == source.get_allocator());\n        table_.merge(source.table_);\n      }\n\n      template <class H2, class P2>\n      void merge(\n        unordered_node_map<key_type, mapped_type, H2, P2, allocator_type>&&\n          source)\n      {\n        BOOST_ASSERT(get_allocator() == source.get_allocator());\n        table_.merge(std::move(source.table_));\n      }\n\n      /// Lookup\n      ///\n\n      mapped_type& at(key_type const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos != table_.end()) {\n          return pos->second;\n        }\n        // TODO: someday refactor this to conditionally serialize the key and\n        // include it in the error message\n        //\n        boost::unordered::detail::throw_out_of_range(\n          \"key was not found in unordered_node_map\");\n      }\n\n      mapped_type const& at(key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos != table_.end()) {\n          return pos->second;\n        }\n        boost::unordered::detail::throw_out_of_range(\n          \"key was not found in unordered_node_map\");\n      }\n\n      template <class K>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        mapped_type&>::type\n      at(K&& key)\n      {\n        auto pos = table_.find(std::forward<K>(key));\n        if (pos != table_.end()) {\n          return pos->second;\n        }\n        boost::unordered::detail::throw_out_of_range(\n          \"key was not found in unordered_node_map\");\n      }\n\n      template <class K>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        mapped_type const&>::type\n      at(K&& key) const\n      {\n        auto pos = table_.find(std::forward<K>(key));\n        if (pos != table_.end()) {\n          return pos->second;\n        }\n        boost::unordered::detail::throw_out_of_range(\n          \"key was not found in unordered_node_map\");\n      }\n\n      BOOST_FORCEINLINE mapped_type& operator[](key_type const& key)\n      {\n        return table_.try_emplace(key).first->second;\n      }\n\n      BOOST_FORCEINLINE mapped_type& operator[](key_type&& key)\n      {\n        return table_.try_emplace(std::move(key)).first->second;\n      }\n\n      template <class K>\n      typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        mapped_type&>::type\n      operator[](K&& key)\n      {\n        return table_.try_emplace(std::forward<K>(key)).first->second;\n      }\n\n      BOOST_FORCEINLINE size_type count(key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        return pos != table_.end() ? 1 : 0;\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value, size_type>::type\n      count(K const& key) const\n      {\n        auto pos = table_.find(key);\n        return pos != table_.end() ? 1 : 0;\n      }\n\n      BOOST_FORCEINLINE iterator find(key_type const& key)\n      {\n        return table_.find(key);\n      }\n\n      BOOST_FORCEINLINE const_iterator find(key_type const& key) const\n      {\n        return table_.find(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        iterator>::type\n      find(K const& key)\n      {\n        return table_.find(key);\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        const_iterator>::type\n      find(K const& key) const\n      {\n        return table_.find(key);\n      }\n\n      BOOST_FORCEINLINE bool contains(key_type const& key) const\n      {\n        return this->find(key) != this->end();\n      }\n\n      template <class K>\n      BOOST_FORCEINLINE typename std::enable_if<\n        boost::unordered::detail::are_transparent<K, hasher, key_equal>::value,\n        bool>::type\n      contains(K const& key) const\n      {\n        return this->find(key) != this->end();\n      }\n\n      std::pair<iterator, iterator> equal_range(key_type const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      std::pair<const_iterator, const_iterator> equal_range(\n        key_type const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      template <class K>\n      typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<iterator, iterator> >::type\n      equal_range(K const& key)\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      template <class K>\n      typename std::enable_if<\n        detail::are_transparent<K, hasher, key_equal>::value,\n        std::pair<const_iterator, const_iterator> >::type\n      equal_range(K const& key) const\n      {\n        auto pos = table_.find(key);\n        if (pos == table_.end()) {\n          return {pos, pos};\n        }\n\n        auto next = pos;\n        ++next;\n        return {pos, next};\n      }\n\n      /// Hash Policy\n      ///\n\n      size_type bucket_count() const noexcept { return table_.capacity(); }\n\n      float load_factor() const noexcept { return table_.load_factor(); }\n\n      float max_load_factor() const noexcept\n      {\n        return table_.max_load_factor();\n      }\n\n      void max_load_factor(float) {}\n\n      size_type max_load() const noexcept { return table_.max_load(); }\n\n      void rehash(size_type n) { table_.rehash(n); }\n\n      void reserve(size_type n) { table_.reserve(n); }\n\n      /// Observers\n      ///\n\n      hasher hash_function() const { return table_.hash_function(); }\n\n      key_equal key_eq() const { return table_.key_eq(); }\n    };\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator==(\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)\n    {\n      return lhs.table_ == rhs.table_;\n    }\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    bool operator!=(\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& lhs,\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator> const& rhs)\n    {\n      return !(lhs == rhs);\n    }\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator>\n    void swap(unordered_node_map<Key, T, Hash, KeyEqual, Allocator>& lhs,\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator>& rhs)\n      noexcept(noexcept(lhs.swap(rhs)))\n    {\n      lhs.swap(rhs);\n    }\n\n    template <class Key, class T, class Hash, class KeyEqual, class Allocator,\n      class Pred>\n    typename unordered_node_map<Key, T, Hash, KeyEqual, Allocator>::size_type\n    erase_if(\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator>& map, Pred pred)\n    {\n      return erase_if(map.table_, pred);\n    }\n\n    template <class Archive, class Key, class T, class Hash, class KeyEqual,\n      class Allocator>\n    void serialize(Archive& ar,\n      unordered_node_map<Key, T, Hash, KeyEqual, Allocator>& map,\n      unsigned int version)\n    {\n    }\n\n#ifdef BOOST_MSVC\n#pragma warning(pop)\n#endif\n\n#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES\n\n    template <class InputIterator,\n      class Hash =\n        boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,\n      class Pred =\n        std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,\n      class Allocator = std::allocator<\n        boost::unordered::detail::iter_to_alloc_t<InputIterator> >,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_pred_v<Pred> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_map(InputIterator, InputIterator,\n      std::size_t = boost::unordered::detail::foa::default_bucket_count,\n      Hash = Hash(), Pred = Pred(), Allocator = Allocator())\n      -> unordered_node_map<boost::unordered::detail::iter_key_t<InputIterator>,\n        boost::unordered::detail::iter_val_t<InputIterator>, Hash, Pred,\n        Allocator>;\n\n    template <class Key, class T,\n      class Hash = boost::hash<std::remove_const_t<Key> >,\n      class Pred = std::equal_to<std::remove_const_t<Key> >,\n      class Allocator = std::allocator<std::pair<const Key, T> >,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_pred_v<Pred> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_map(std::initializer_list<std::pair<Key, T> >,\n      std::size_t = boost::unordered::detail::foa::default_bucket_count,\n      Hash = Hash(), Pred = Pred(), Allocator = Allocator())\n      -> unordered_node_map<std::remove_const_t<Key>, T, Hash, Pred,\n        Allocator>;\n\n    template <class InputIterator, class Allocator,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_map(InputIterator, InputIterator, std::size_t, Allocator)\n      -> unordered_node_map<boost::unordered::detail::iter_key_t<InputIterator>,\n        boost::unordered::detail::iter_val_t<InputIterator>,\n        boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,\n        std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,\n        Allocator>;\n\n    template <class InputIterator, class Allocator,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_map(InputIterator, InputIterator, Allocator)\n      -> unordered_node_map<boost::unordered::detail::iter_key_t<InputIterator>,\n        boost::unordered::detail::iter_val_t<InputIterator>,\n        boost::hash<boost::unordered::detail::iter_key_t<InputIterator> >,\n        std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,\n        Allocator>;\n\n    template <class InputIterator, class Hash, class Allocator,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_input_iterator_v<InputIterator> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_map(\n      InputIterator, InputIterator, std::size_t, Hash, Allocator)\n      -> unordered_node_map<boost::unordered::detail::iter_key_t<InputIterator>,\n        boost::unordered::detail::iter_val_t<InputIterator>, Hash,\n        std::equal_to<boost::unordered::detail::iter_key_t<InputIterator> >,\n        Allocator>;\n\n    template <class Key, class T, class Allocator,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_map(std::initializer_list<std::pair<Key, T> >, std::size_t,\n      Allocator) -> unordered_node_map<std::remove_const_t<Key>, T,\n      boost::hash<std::remove_const_t<Key> >,\n      std::equal_to<std::remove_const_t<Key> >, Allocator>;\n\n    template <class Key, class T, class Allocator,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_map(std::initializer_list<std::pair<Key, T> >, Allocator)\n      -> unordered_node_map<std::remove_const_t<Key>, T,\n        boost::hash<std::remove_const_t<Key> >,\n        std::equal_to<std::remove_const_t<Key> >, Allocator>;\n\n    template <class Key, class T, class Hash, class Allocator,\n      class = std::enable_if_t<detail::is_hash_v<Hash> >,\n      class = std::enable_if_t<detail::is_allocator_v<Allocator> > >\n    unordered_node_map(std::initializer_list<std::pair<Key, T> >, std::size_t,\n      Hash, Allocator) -> unordered_node_map<std::remove_const_t<Key>, T,\n      Hash, std::equal_to<std::remove_const_t<Key> >, Allocator>;\n#endif\n\n  } // namespace unordered\n} // namespace boost\n\n#endif\n\n#endif  // BASE_INCLUDE_BOOST_UNORDERED_H_\n"
  },
  {
    "path": "base/include/bundled_optional.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_BUNDLED_OPTIONAL_H_\n#define BASE_INCLUDE_BUNDLED_OPTIONAL_H_\n\n#include <algorithm>\n#include <array>\n#include <cstdint>\n#include <numeric>\n#include <utility>\n\n#include \"base/include/type_traits_addon.h\"\n\nnamespace lynx {\nnamespace base {\n\nnamespace bo_priv {\ntemplate <typename T, typename... Pack>\nstruct get_type_index;\ntemplate <typename T, typename Head, typename... Rest>\nstruct get_type_index<T, Head, Rest...> {\n  static constexpr std::size_t value = 1 + get_type_index<T, Rest...>::value;\n};\ntemplate <typename T, typename... Rest>\nstruct get_type_index<T, T, Rest...> {\n  static constexpr std::size_t value = 0;\n};\ntemplate <typename T, typename... Pack>\nconstexpr std::size_t get_type_index_v = get_type_index<T, Pack...>::value;\n\ntemplate <std::size_t I, typename... Types>\nstruct get_type_at;\ntemplate <std::size_t I, typename Head, typename... Rest>\nstruct get_type_at<I, Head, Rest...> {\n  using Type = typename get_type_at<I - 1, Rest...>::Type;\n};\ntemplate <typename Head, typename... Rest>\nstruct get_type_at<0, Head, Rest...> {\n  using Type = Head;\n};\ntemplate <std::size_t I, typename... Types>\nusing get_type_at_t = typename get_type_at<I, Types...>::Type;\n\ntemplate <typename... Types>\nstruct sum_sizeof_aligned_helper;\ntemplate <typename T, typename... Rest>\nstruct sum_sizeof_aligned_helper<T, Rest...> {\n  using FieldType = typename T::Type;\n  static constexpr size_t alignment = sizeof(void*);\n  static constexpr size_t aligned_size =\n      (sizeof(FieldType) + alignment - 1) / alignment * alignment;\n  static constexpr size_t value =\n      aligned_size + sum_sizeof_aligned_helper<Rest...>::value;\n};\ntemplate <>\nstruct sum_sizeof_aligned_helper<> {\n  static constexpr size_t value = 0;\n};\ntemplate <typename... Types>\nconstexpr size_t sum_sizeof_aligned() {\n  return sum_sizeof_aligned_helper<Types...>::value;\n}\n\nconstexpr uintptr_t align_up(uintptr_t addr, size_t alignment) {\n  return (addr + alignment - 1) & ~(alignment - 1);\n}\n}  // namespace bo_priv\n\n// =================================================================================\n//  USAGE:\n//\n//  struct Parent {\n//    struct NameDef { using Type = std::string; };\n//    struct AttributesDef { using Type = std::vector<std::string>; };\n//\n//    char ch;\n//    BundledOptionals<NameDef, AttributesDef> optionals;\n//  };\n// =================================================================================\ntemplate <typename... Types>\nstruct BundledOptionals {\n public:\n  static constexpr auto kFieldsCount = sizeof...(Types);\n\n private:\n  static_assert(bo_priv::sum_sizeof_aligned<Types...>() <=\n                    (UINT8_MAX - 1) * sizeof(void*),\n                \"Total size of bundled types exceeds offset capacity.\");\n  static constexpr size_t kAlignment = sizeof(void*);\n  static constexpr size_t kPaddedOffsetsSize =\n      (kFieldsCount + kAlignment - 1) / kAlignment * kAlignment;\n\n public:\n  BundledOptionals() noexcept : bundled_data_(nullptr) {\n    std::fill(std::begin(offsets_), std::end(offsets_), UINT8_MAX);\n  }\n\n  ~BundledOptionals() { Clear(); }\n\n  BundledOptionals(const BundledOptionals& other)\n      : offsets_(other.offsets_), bundled_data_(nullptr) {\n    if (!other.bundled_data_) return;\n    size_t total_size = 0;\n    for (size_t i = 0; i < kFieldsCount; ++i) {\n      if (other.offsets_[i] != UINT8_MAX) {\n        CallOnFieldType(i, SizeCalculatorFunctor(total_size));\n      }\n    }\n    if (total_size > 0) {\n      bundled_data_ = static_cast<void*>(new char[total_size]);\n      for (size_t i = 0; i < kFieldsCount; ++i) {\n        if (offsets_[i] != UINT8_MAX) {\n          CallOnFieldType(\n              i, CopyConstructorFunctor(i, bundled_data_, other.bundled_data_,\n                                        offsets_));\n        }\n      }\n    }\n  }\n\n  BundledOptionals(BundledOptionals&& other) noexcept\n      : offsets_(other.offsets_), bundled_data_(other.bundled_data_) {\n    other.bundled_data_ = nullptr;\n    std::fill(std::begin(other.offsets_), std::end(other.offsets_), UINT8_MAX);\n  }\n\n  BundledOptionals& operator=(const BundledOptionals& other) {\n    if (this != &other) {\n      BundledOptionals temp(other);\n      swap(temp);\n    }\n    return *this;\n  }\n\n  BundledOptionals& operator=(BundledOptionals&& other) noexcept {\n    if (this != &other) {\n      Clear();\n      swap(other);\n    }\n    return *this;\n  }\n\n  template <typename T>\n  static constexpr auto GetIndex() {\n    return bo_priv::get_type_index_v<T, Types...>;\n  }\n\n  template <typename T>\n  bool HasValue() const {\n    return offsets_[GetIndex<T>()] != UINT8_MAX;\n  }\n\n  template <typename T>\n  typename T::Type* GetOrNull() {\n    constexpr auto type_index = GetIndex<T>();\n    if (HasValue<T>()) {\n      return reinterpret_cast<typename T::Type*>(\n          reinterpret_cast<uintptr_t>(bundled_data_) +\n          offsets_[type_index] * sizeof(void*));\n    }\n    return nullptr;\n  }\n\n  template <typename T>\n  const typename T::Type* GetOrNull() const {\n    constexpr auto type_index = GetIndex<T>();\n    if (HasValue<T>()) {\n      return reinterpret_cast<const typename T::Type*>(\n          reinterpret_cast<uintptr_t>(bundled_data_) +\n          offsets_[type_index] * sizeof(void*));\n    }\n    return nullptr;\n  }\n\n  template <typename T>\n  typename T::Type& Get() {\n    constexpr auto type_index = GetIndex<T>();\n    if (!HasValue<T>()) {\n      CreateField(type_index);\n    }\n    return *reinterpret_cast<typename T::Type*>(\n        reinterpret_cast<uintptr_t>(bundled_data_) +\n        offsets_[type_index] * sizeof(void*));\n  }\n\n  template <typename T>\n  void Release() {\n    ReleaseField(GetIndex<T>());\n  }\n\n  template <typename T>\n  typename T::Type ReleaseTransfer() {\n    typename T::Type result;\n    constexpr auto type_index = GetIndex<T>();\n    if (type_index >= kFieldsCount || offsets_[type_index] == UINT8_MAX) {\n      return result;\n    }\n    result = std::move(Get<T>());\n    ReleaseField(type_index);\n    return result;\n  }\n\n  void Clear() {\n    if (!bundled_data_) return;\n    for (std::size_t i = 0; i < kFieldsCount; ++i) {\n      if (offsets_[i] != UINT8_MAX) {\n        destructors_[i](\n            reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(bundled_data_) +\n                                    offsets_[i] * sizeof(void*)));\n      }\n    }\n    delete[] static_cast<char*>(bundled_data_);\n    bundled_data_ = nullptr;\n    std::fill(std::begin(offsets_), std::end(offsets_), UINT8_MAX);\n  }\n\n private:\n  struct SizeCalculatorFunctor {\n    size_t& r;\n    explicit SizeCalculatorFunctor(size_t& s) : r(s) {}\n    template <class F>\n    void operator()() const {\n      r += (sizeof(F) + sizeof(void*) - 1) / sizeof(void*) * sizeof(void*);\n    }\n  };\n\n  struct CopyConstructorFunctor {\n    size_t i;\n    void* d;\n    const void* s;\n    const std::array<uint8_t, kPaddedOffsetsSize>& o;\n    CopyConstructorFunctor(size_t i, void* d, const void* s,\n                           const std::array<uint8_t, kPaddedOffsetsSize>& o)\n        : i(i), d(d), s(s), o(o) {}\n    template <class F>\n    void operator()() const {\n      new (reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(d) +\n                                   o[i] * sizeof(void*)))\n          F(*reinterpret_cast<const F*>(reinterpret_cast<uintptr_t>(s) +\n                                        o[i] * sizeof(void*)));\n    }\n  };\n\n  struct CreateFieldLayoutFunctor {\n    size_t& t;\n    uint8_t& o;\n    CreateFieldLayoutFunctor(size_t& t, uint8_t& o) : t(t), o(o) {}\n    template <class F>\n    void operator()() const {\n      o = t / sizeof(void*);\n      t += (sizeof(F) + sizeof(void*) - 1) / sizeof(void*) * sizeof(void*);\n    }\n  };\n\n  struct MoverAndCreatorFunctor {\n    size_t ci, ni;\n    void *nd, *od;\n    const std::array<uint8_t, kPaddedOffsetsSize>& oo;\n    const std::array<uint8_t, kPaddedOffsetsSize>& no;\n    MoverAndCreatorFunctor(size_t ci, size_t ni, void* nd, void* od,\n                           const std::array<uint8_t, kPaddedOffsetsSize>& oo,\n                           const std::array<uint8_t, kPaddedOffsetsSize>& no)\n        : ci(ci), ni(ni), nd(nd), od(od), oo(oo), no(no) {}\n    template <class F>\n    void operator()() const {\n      uintptr_t na = reinterpret_cast<uintptr_t>(nd) + no[ci] * sizeof(void*);\n      if (ci == ni) {\n        new (reinterpret_cast<void*>(na)) F();\n      } else {\n        uintptr_t oa = reinterpret_cast<uintptr_t>(od) + oo[ci] * sizeof(void*);\n        static constexpr auto is_trivially_relocatable =\n            IsTriviallyRelocatable<F, is_instance<F, std::pair>{}>::value;\n        static constexpr auto is_trivially_destructible_after_move =\n            is_trivially_relocatable ||\n            IsTriviallyDestructibleAfterMove<\n                F, is_instance<F, std::pair>{}>::value;\n        if constexpr (is_trivially_relocatable) {\n          // Trivially copy as plain bytes and skip destructor.\n          using TrivialFType = TypeOfPlainBytes<F>;\n          *reinterpret_cast<TrivialFType*>(na) =\n              *reinterpret_cast<TrivialFType*>(oa);\n        } else {\n          new (reinterpret_cast<void*>(na))\n              F(std::move(*reinterpret_cast<F*>(oa)));\n          if constexpr (!is_trivially_destructible_after_move) {\n            reinterpret_cast<F*>(oa)->~F();\n          }\n        }\n      }\n    }\n  };\n\n  template <std::size_t I = 0, typename Func>\n  void CallOnFieldType(std::size_t index, Func&& func) const {\n    if constexpr (I < kFieldsCount) {\n      if (I == index) {\n        func.template\n        operator()<typename bo_priv::get_type_at_t<I, Types...>::Type>();\n      } else {\n        CallOnFieldType<I + 1>(index, std::forward<Func>(func));\n      }\n    }\n  }\n\n  using DestructorFn = void (*)(void*);\n  template <typename T>\n  static void destroy_field_impl(void* ptr) {\n    using ActualType = typename T::Type;\n    reinterpret_cast<ActualType*>(ptr)->~ActualType();\n  }\n  static constexpr std::array<DestructorFn, kFieldsCount> destructors_ = {\n      &destroy_field_impl<Types>...};\n\n  void swap(BundledOptionals& other) noexcept {\n    std::swap(bundled_data_, other.bundled_data_);\n    std::swap(offsets_, other.offsets_);\n  }\n\n  void CreateField(std::size_t type_index) {\n    std::array<uint8_t, kPaddedOffsetsSize> new_offsets_map;\n    new_offsets_map.fill(UINT8_MAX);\n    size_t new_total_size = 0;\n    for (size_t i = 0; i < kFieldsCount; ++i) {\n      if (offsets_[i] != UINT8_MAX || i == type_index) {\n        CallOnFieldType(\n            i, CreateFieldLayoutFunctor(new_total_size, new_offsets_map[i]));\n      }\n    }\n    if (new_total_size == 0) return;\n    void* new_data = static_cast<void*>(new char[new_total_size]);\n    void* old_data = bundled_data_;\n    for (size_t i = 0; i < kFieldsCount; ++i) {\n      if (new_offsets_map[i] != UINT8_MAX) {\n        CallOnFieldType(\n            i, MoverAndCreatorFunctor(i, type_index, new_data, old_data,\n                                      offsets_, new_offsets_map));\n      }\n    }\n    delete[] static_cast<char*>(old_data);\n    bundled_data_ = new_data;\n    offsets_ = new_offsets_map;\n  }\n\n  void ReleaseField(std::size_t type_index) {\n    if (type_index >= kFieldsCount || offsets_[type_index] == UINT8_MAX) return;\n    destructors_[type_index](\n        reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(bundled_data_) +\n                                offsets_[type_index] * sizeof(void*)));\n    offsets_[type_index] = UINT8_MAX;\n    bool all_released = true;\n    for (size_t i = 0; i < kFieldsCount; ++i) {\n      if (offsets_[i] != UINT8_MAX) {\n        all_released = false;\n        break;\n      }\n    }\n    if (all_released) {\n      delete[] static_cast<char*>(bundled_data_);\n      bundled_data_ = nullptr;\n    }\n  }\n\n  std::array<uint8_t, kPaddedOffsetsSize> offsets_;\n  void* bundled_data_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_BUNDLED_OPTIONAL_H_\n"
  },
  {
    "path": "base/include/bundled_optional_lldb.py",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport lldb\n\n\nclass BundledOptionalsSyntheticProvider:\n    \"\"\"\n    Synthetic children provider for the BundledOptionals class.\n    It reconstructs the view of optional members stored in a single memory block.\n    \"\"\"\n    def __init__(self, val_obj, internal_dict):\n        self.val_obj = val_obj\n        self.update()\n\n    def update(self):\n        \"\"\"\n        Fetches all necessary information from the debugged process.\n        This is called when the provider is first created or when the state changes.\n        \"\"\"\n        self.target = self.val_obj.GetTarget()\n        self.process = self.target.GetProcess()\n        self.ptr_size = self.target.GetAddressByteSize()\n        self.UINT8_MAX = 255\n\n        data_ptr_member = self.val_obj.GetChildMemberWithName('bundled_data_')\n        if data_ptr_member and data_ptr_member.IsValid():\n            self.data_block_addr = data_ptr_member.GetValueAsUnsigned(0)\n        else:\n            self.data_block_addr = 0 # Indicate failure\n\n        # --- Read the offsets_ array ---\n        self.offsets = []\n        self.num_fields = self.val_obj.GetType().GetNumberOfTemplateArguments()\n        offsets_member = self.val_obj.GetChildMemberWithName('offsets_')\n        if offsets_member.IsValid():\n            # offsets_ is an array inside a struct, get its first element\n            array_start = offsets_member.GetChildAtIndex(0)\n            for i in range(self.num_fields):\n                offset_val = array_start.GetChildAtIndex(i).GetValueAsUnsigned(0)\n                self.offsets.append(offset_val)\n        # --- Get template argument types ---\n        self.def_types = []\n        self.real_types = []\n        for i in range(self.num_fields):\n            def_type = self.val_obj.GetType().GetTemplateArgumentType(i)\n            self.def_types.append(def_type)\n            # Find the inner 'Type' from the definition struct\n            real_type_field = def_type.FindDirectNestedType('Type')\n            self.real_types.append(real_type_field.GetTypedefedType())\n\n    def num_children(self):\n        return self.num_fields + 2\n    \n    def get_child_at_index(self, index):\n        if index == 0:\n            return self.val_obj.GetChildMemberWithName('offsets_')\n        elif index == 1:\n            return self.val_obj.GetChildMemberWithName('bundled_data_')\n\n        index -= 2\n        if index >= self.num_fields:\n            return None\n        offset = self.offsets[index]\n        def_type = self.def_types[index]\n        child_name = f'[{index}] {def_type.GetName()}'\n        if offset == self.UINT8_MAX or self.data_block_addr == 0:\n            # Member does not exist or data block is invalid\n            return self.val_obj.CreateValueFromData(child_name,\n                                                   lldb.SBData.CreateDataFromCString(self.process.GetByteOrder(), self.ptr_size, \"[not set]\"),\n                                                   self.target.GetBasicType(lldb.eBasicTypeChar).GetArrayType(9))\n        else:\n            # Member exists, calculate its address and create a typed value\n            real_type = self.real_types[index]\n            child_addr = self.data_block_addr + (offset * self.ptr_size)\n            return self.val_obj.CreateValueFromAddress(child_name, child_addr, real_type)\n        \n    def has_children(self):\n        return True\n    \ndef __lldb_init_module(debugger, internal_dict):\n    class_name = 'lynx::base::BundledOptionals'\n    regex = f\"^{class_name}<.*>$\"\n    debugger.HandleCommand(f'type synthetic add --python-class {__name__}.BundledOptionalsSyntheticProvider -x \"{regex}\" -w liblynx')\n    debugger.HandleCommand('type category enable liblynx')"
  },
  {
    "path": "base/include/cast_util.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_CAST_UTIL_H_\n#define BASE_INCLUDE_CAST_UTIL_H_\n\n#include <cstring>\n\nnamespace lynx {\nnamespace base {\n\ntemplate <typename Dst, typename Src>\nDst BitCast(Src&& value) {\n  static_assert(sizeof(Src) == sizeof(Dst), \"BitCast sizes must match.\");\n  Dst result;\n  memcpy(&result, &value, sizeof(result));\n  return result;\n}\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_CAST_UTIL_H_\n"
  },
  {
    "path": "base/include/closure.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_CLOSURE_H_\n#define BASE_INCLUDE_CLOSURE_H_\n\n#include <cstddef>\n#include <functional>\n#include <type_traits>\n#include <utility>\n\n#include \"base/include/base_defines.h\"\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace base {\n\ntemplate <typename Ret, typename... Args>\nclass ClosureBase {\n public:\n  ClosureBase() = default;\n  virtual ~ClosureBase() = default;\n  virtual Ret operator()(Args...) = 0;\n\n  ClosureBase(const ClosureBase&) = delete;\n  ClosureBase& operator=(const ClosureBase&) = delete;\n  ClosureBase(ClosureBase&&) = default;\n  ClosureBase& operator=(ClosureBase&&) = default;\n};\n\ntemplate <typename F, typename Ret, typename... Args>\nclass ClosureImpl : public ClosureBase<Ret, Args...> {\n public:\n  explicit ClosureImpl(F&& func) : func_(std::move(func)) {}\n  ~ClosureImpl() override = default;\n\n  Ret operator()(Args... arguments) override {\n    return func_(std::forward<Args>(arguments)...);\n  }\n\n  ClosureImpl(const ClosureImpl&) = delete;\n  ClosureImpl& operator=(const ClosureImpl&) = delete;\n  ClosureImpl(ClosureImpl&&) = default;\n  ClosureImpl& operator=(ClosureImpl&&) = default;\n\n private:\n  F func_;\n};\n\ntemplate <typename Ret = void, typename... Args>\nclass MoveOnlyClosure {\n public:\n  MoveOnlyClosure() : impl_(nullptr) {}\n  MoveOnlyClosure(std::nullptr_t) : impl_(nullptr) {}\n\n  template <typename F, typename = std::enable_if_t<\n                            std::is_invocable_r_v<Ret, F, Args...> &&\n                            !std::is_same_v<std::decay_t<F>, MoveOnlyClosure>>>\n  MoveOnlyClosure(F&& func)\n      : impl_(new ClosureImpl<F, Ret, Args...>(std::move(func))) {}\n  ~MoveOnlyClosure() { delete impl_; }\n\n  // Here we delete the constructor with lvalue\n  // to avoid usage like:\n  //   auto task = []() {};\n  //   runtime_task_runner()->PostTask(task);\n  // which passing a lvalue reference to construct a MoveOnlyClosure.\n  //\n  // This actually does not compile since ClosureImpl requires a rvalue.\n  // But explicitly deletion will give more readable error message like:\n  //   conversion function from '(lambda at closure_unittest.cc:165:12)' to\n  //   'base::closure' (aka 'MoveOnlyClosure<>') invokes a deleted function\n  //   note: 'MoveOnlyClosure<(lambda at closure_unittest.cc:165:12)>' has been\n  //   explicitly marked deleted here MoveOnlyClosure(F&) = delete;\n  //                                  ^\n  // Without this, the error message will look like:\n  //   no matching constructor for initialization of\n  //   'ClosureImpl<CopyableLambda<(lambda at foo.cc:10:28)> &, void>'\n  template <typename F>\n  MoveOnlyClosure(F&) = delete;\n  MoveOnlyClosure(const MoveOnlyClosure&) = delete;\n  MoveOnlyClosure& operator=(const MoveOnlyClosure&) = delete;\n\n  BASE_INLINE MoveOnlyClosure(MoveOnlyClosure&& other) {\n    impl_ = other.impl_;\n    other.impl_ = nullptr;\n  }\n\n  BASE_INLINE MoveOnlyClosure& operator=(MoveOnlyClosure&& other) {\n    delete impl_;\n    impl_ = other.impl_;\n    other.impl_ = nullptr;\n    return *this;\n  }\n\n  MoveOnlyClosure& operator=(std::nullptr_t) {\n    delete impl_;\n    impl_ = nullptr;\n    return *this;\n  }\n\n  Ret operator()(Args... arguments) const {\n    return (*impl_)(std::forward<Args>(arguments)...);\n  }\n\n  explicit operator bool() const { return impl_ != nullptr; }\n\n  bool operator==(std::nullptr_t) const { return impl_ == nullptr; }\n\n  bool operator!=(std::nullptr_t) const { return impl_ != nullptr; }\n\n private:\n  ClosureBase<Ret, Args...>* impl_;\n};\n\nusing closure = base::MoveOnlyClosure<>;\n\n}  // namespace base\n}  // namespace lynx\n\nnamespace fml {\n\nusing closure = std::function<void()>;\n\n//------------------------------------------------------------------------------\n/// @brief      Wraps a closure that is invoked in the destructor unless\n///             released by the caller.\n///\n///             This is especially useful in dealing with APIs that return a\n///             resource by accepting ownership of a sub-resource and a closure\n///             that releases that resource. When such APIs are chained, each\n///             link in the chain must check that the next member in the chain\n///             has accepted the resource. If not, it must invoke the closure\n///             eagerly. Not doing this results in a resource leak in the\n///             erroneous case. Using this wrapper, the closure can be released\n///             once the next call in the chain has successfully accepted\n///             ownership of the resource. If not, the closure gets invoked\n///             automatically at the end of the scope. This covers the cases\n///             where there are early returns as well.\n///\nclass ScopedCleanupClosure {\n public:\n  ScopedCleanupClosure() = default;\n\n  explicit ScopedCleanupClosure(const fml::closure& closure)\n      : closure_(closure) {}\n\n  ~ScopedCleanupClosure() {\n    if (closure_) {\n      closure_();\n    }\n  }\n\n  fml::closure SetClosure(const fml::closure& closure) {\n    auto old_closure = closure_;\n    closure_ = closure;\n    return old_closure;\n  }\n\n  fml::closure Release() {\n    fml::closure closure = closure_;\n    closure_ = nullptr;\n    return closure;\n  }\n\n private:\n  fml::closure closure_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ScopedCleanupClosure);\n};\n\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_CLOSURE_H_\n"
  },
  {
    "path": "base/include/compiler_specific.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_COMPILER_SPECIFIC_H_\n#define BASE_INCLUDE_COMPILER_SPECIFIC_H_\n\n#if !defined(__GNUC__) && !defined(__clang__) && !defined(_MSC_VER)\n#error Unsupported compiler.\n#endif\n\n#if !defined(UNLIKELY)\n#if defined(COMPILER_GCC) || defined(__clang__)\n#define UNLIKELY(x) __builtin_expect(!!(x), 0)\n#else\n#define UNLIKELY(x) (x)\n#endif\n#endif\n\n#if !defined(LIKELY)\n#if defined(COMPILER_GCC) || defined(__clang__)\n#define LIKELY(x) __builtin_expect(!!(x), 1)\n#else\n#define LIKELY(x) (x)\n#endif\n#endif\n\n#undef WARN_UNUSED_RESULT\n#if defined(COMPILER_GCC) || defined(__clang__)\n#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))\n#else\n#define WARN_UNUSED_RESULT\n#endif\n\n// Compiler feature-detection.\n// clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension\n#if defined(__has_feature)\n#define HAS_FEATURE(FEATURE) __has_feature(FEATURE)\n#else\n#define HAS_FEATURE(FEATURE) 0\n#endif\n\n#define ALLOW_UNUSED_LOCAL(x) (void)x\n\n#if defined(__GNUC__) || defined(__clang__)\n#define ALLOW_UNUSED_TYPE __attribute__((unused))\n#else\n#define ALLOW_UNUSED_TYPE\n#endif\n\n#if defined(__clang__)\n#define ALWAYS_INLINE __attribute__((__always_inline__))\n#define NO_INLINE __attribute__((__noinline__))\n#else\n// GCC is too pedantic and often fails with the error:\n// \"always_inline function might not be inlinable\"\n#define ALWAYS_INLINE\n#define NO_INLINE\n#endif\n\n#endif  // BASE_INCLUDE_COMPILER_SPECIFIC_H_\n"
  },
  {
    "path": "base/include/concurrent_queue.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_CONCURRENT_QUEUE_H_\n#define BASE_INCLUDE_CONCURRENT_QUEUE_H_\n\n#include <atomic>\n#include <iterator>\n#include <utility>\n\nnamespace lynx {\nnamespace base {\n\n/*\n  Thread safe lock free queue. Not provide node access like\n  Front/Back/Top since they may break thread safe. Not provide Pop single\n  node since it needs more code and we dont need it now. Last one is appended\n  to the end of internal list. PopAll will return in pushed order.\n*/\ntemplate <typename T>\nclass ConcurrentQueue {\n public:\n  struct Node {\n    T data;\n    Node* next;\n    explicit Node(T data) : data(std::move(data)), next(nullptr) {}\n  };\n\n  struct Iterator {\n    using difference_type = ptrdiff_t;\n    using value_type = T;\n    using pointer = T*;\n    using reference = T&;\n    using iterator_category = std::forward_iterator_tag;\n\n    Node* ptr;\n    Iterator() : ptr(nullptr) {}\n    explicit Iterator(Node* ptr) : ptr(ptr) {}\n\n    T& operator*() const { return ptr->data; }\n\n    T* operator->() const { return &ptr->data; }\n\n    Iterator& operator++() {\n      ptr = ptr->next;\n      return *this;\n    }\n\n    Iterator operator++(int) {\n      Iterator t(*this);\n      ++(*this);\n      return t;\n    }\n\n    friend bool operator==(const Iterator& x, const Iterator& y) {\n      return x.ptr == y.ptr;\n    }\n\n    friend bool operator!=(const Iterator& x, const Iterator& y) {\n      return !(x == y);\n    }\n  };\n\n  struct IterableContainer {\n    IterableContainer() : head_(nullptr) {}\n    explicit IterableContainer(Node* head, bool reverse_order) {\n      if (reverse_order) {\n        head_ = head;\n        return;\n      }\n\n      // Reverse the single linked list.\n      Node* prev = nullptr;\n      Node* curr = head;\n      while (curr != nullptr) {\n        Node* next = curr->next;\n        curr->next = prev;\n        prev = curr;\n        curr = next;\n      }\n      head_ = prev;\n    }\n\n    IterableContainer(const IterableContainer&) = delete;\n    IterableContainer& operator=(const IterableContainer&) = delete;\n    IterableContainer(IterableContainer&& other) : head_(other.head_) {\n      other.head_ = nullptr;\n    }\n    IterableContainer& operator=(IterableContainer&& other) {\n      if (this != &other) {\n        FreeList();\n        head_ = other.head_;\n        other.head_ = nullptr;\n      }\n      return *this;\n    }\n\n    ~IterableContainer() { FreeList(); }\n\n    bool empty() const { return head_ == nullptr; }\n\n    Iterator begin() { return Iterator(head_); }\n\n    Iterator end() { return Iterator(); }\n\n    Iterator begin() const { return Iterator(head_); }\n\n    Iterator end() const { return Iterator(); }\n\n    T& front() { return head_->data; }\n\n    size_t size() const {\n      // Normally size() is only used by unittests.\n      // We calculate it everytime to keep IterableContainer fits\n      // into single register.\n      size_t result = 0;\n      Node* n = head_;\n      while (n != nullptr) {\n        ++result;\n        n = n->next;\n      }\n      return result;\n    }\n\n    void reset() { FreeList(); }\n\n   private:\n    Node* head_;\n\n    void FreeList() {\n      while (head_ != nullptr) {\n        Node* pre = head_;\n        head_ = head_->next;\n        delete pre;\n      }\n    }\n  };\n\n  void Push(T data) {\n    Node* const new_head = new Node(std::move(data));\n    new_head->next = head_.load();\n    while (!head_.compare_exchange_weak(new_head->next, new_head)) {\n    }\n  }\n\n  void Push(ConcurrentQueue<T>& other) {\n    Node* pop_head = other.head_.exchange(nullptr);\n    // afterwards pop_head is thread safe\n    if (pop_head == nullptr) {\n      return;\n    }\n\n    Node* pop_tail = pop_head;\n    while (pop_tail->next != nullptr) {\n      pop_tail = pop_tail->next;\n    }\n\n    pop_tail->next = head_.load();\n    while (!head_.compare_exchange_weak(pop_tail->next, pop_head)) {\n    }\n  }\n\n  IterableContainer PopAll() {\n    return IterableContainer(head_.exchange(nullptr), false);\n  }\n\n  IterableContainer ReversePopAll() {\n    return IterableContainer(head_.exchange(nullptr), true);\n  }\n\n  bool Empty() { return (head_.load() == nullptr); }\n\n  ConcurrentQueue() : head_(nullptr) {}\n\n  ~ConcurrentQueue() {\n    Node* pop_head = head_.exchange(nullptr);\n    // afterwards pop_head is thread safe\n    DestroyUnsafe(pop_head);\n  }\n\n  ConcurrentQueue(ConcurrentQueue&& other) {\n    head_ = other.head_.exchange(nullptr);\n  }\n\n  ConcurrentQueue& operator=(ConcurrentQueue&& other) {\n    if (this != &other) {\n      Node* other_head = other.head_.exchange(nullptr);\n      Node* pop_head = head_.load();\n      while (!head_.compare_exchange_weak(pop_head, other_head)) {\n      }\n      DestroyUnsafe(pop_head);\n    }\n    return *this;\n  }\n\n private:\n  std::atomic<Node*> head_;\n\n  static void DestroyUnsafe(Node* head) {\n    while (head != nullptr) {\n      Node* pre = head;\n      head = head->next;\n      delete pre;\n    }\n  }\n\n  ConcurrentQueue(const ConcurrentQueue&) = delete;\n  ConcurrentQueue& operator=(const ConcurrentQueue&) = delete;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_CONCURRENT_QUEUE_H_\n"
  },
  {
    "path": "base/include/datauri_utils.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_DATAURI_UTILS_H_\n#define BASE_INCLUDE_DATAURI_UTILS_H_\n\n#include <cstdint>\n#include <memory>\n#include <string_view>\n#include <utility>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/closure.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass DataURIUtil {\n public:\n  using BufferFactory = base::MoveOnlyClosure<char *, size_t>;\n\n  /**\n   * decode base64 str to binary data\n   * @param base64_str  base64 str without any prefix\n   * @return size & binary data, binary data's size maybe larger than the size\n   * returned, do not assume them is equal, return 0 if failed\n   */\n  static int32_t DecodeBase64(const std::string_view &base64_str,\n                              BufferFactory factory);\n  /**\n   * decode base64 encoded data uri(data:[<mediatype>];base64,<data>) str to\n   * binary data\n   * @param uri base64 encoded data uri string\n   * @return size & binary data, binary data's size maybe larger than the size\n   * returned, do not assume them is equal, return 0 if failed\n   */\n  BASE_EXPORT static int32_t DecodeDataURI(const std::string_view &uri,\n                                           BufferFactory factory);\n\n  /**\n   * Check if the given string is a data uri, do not handle any leading blank,\n   * trim first.\n   */\n  BASE_EXPORT static bool IsDataURI(const std::string_view &uri);\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_DATAURI_UTILS_H_\n"
  },
  {
    "path": "base/include/debug/backtrace.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_DEBUG_BACKTRACE_H_\n#define BASE_INCLUDE_DEBUG_BACKTRACE_H_\n\n#include <iomanip>\n#include <iostream>\n#include <memory>\n#include <sstream>\n#include <string>\n#if OS_IOS\n#include <execinfo.h>\n#endif\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace debug {\n\nclass BacktraceDelegate {\n public:\n  virtual ~BacktraceDelegate() {}\n  virtual std::string TraceLog(std::string& msg, int skipDepth) = 0;\n};\n\nvoid SetBacktraceDelegate(BacktraceDelegate* delegate);\n\n// IOS\nstd::string GetBacktraceInfo(std::string& error_message);\n\n}  // namespace debug\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_DEBUG_BACKTRACE_H_\n"
  },
  {
    "path": "base/include/debug/lynx_assert.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_DEBUG_LYNX_ASSERT_H_\n#define BASE_INCLUDE_DEBUG_LYNX_ASSERT_H_\n\n#include <string>\n#include <utility>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/debug/lynx_error.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/include/string/string_utils.h\"\n\n// TODO(yanghuiwen): As the new error reporting interface has added error\n// levels, to avoid confusion caused by the name of the old macros, it is\n// necessary to replace the following macro with macro LYNX_ERROR.\n#define LynxInfo(error_code, ...)                                  \\\n  auto exception = lynx::base::LynxError(error_code, __VA_ARGS__); \\\n  lynx::base::ErrorStorage::GetInstance().SetError(std::move(exception));\n\n#define LynxWarning(expression, error_code, ...)                            \\\n  if (!(expression)) {                                                      \\\n    auto exception = lynx::base::LynxError(error_code, __VA_ARGS__);        \\\n    lynx::base::ErrorStorage::GetInstance().SetError(std::move(exception)); \\\n  }\n\n// ATTENTION: invoke this, will log and abort\n#define LynxFatal(expression, error_code, ...)                           \\\n  if (!(expression)) {                                                   \\\n    LOGF(\"LynxFatal error: error_code:\"                                  \\\n         << error_code                                                   \\\n         << \" error_message:\" << lynx::base::FormatString(__VA_ARGS__)); \\\n  }\n\n#endif  // BASE_INCLUDE_DEBUG_LYNX_ASSERT_H_\n"
  },
  {
    "path": "base/include/debug/lynx_error.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_DEBUG_LYNX_ERROR_H_\n#define BASE_INCLUDE_DEBUG_LYNX_ERROR_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/base_export.h\"\n\n// Store the error into ErrorStorage, so that it can be retrieved and reported\n// from ErrorStorage after a processing is completed. e.g. report error when the\n// destructor of class Scope is called.\n#define LYNX_WARN(code, msg, suggestion)        \\\n  lynx::base::StoreError(code, msg, suggestion, \\\n                         lynx::base::LynxErrorLevel::Warn)\n#define LYNX_ERROR(code, msg, suggestion)       \\\n  lynx::base::StoreError(code, msg, suggestion, \\\n                         lynx::base::LynxErrorLevel::Error)\n\n// Store the error into ErrorStorage if expression is not satisfied.\n#define LYNX_WARN_CHECK(expression, code, msg, suggestion)       \\\n  lynx::base::StoreErrorIfNot(expression, code, msg, suggestion, \\\n                              lynx::base::LynxErrorLevel::Warn)\n#define LYNX_ERROR_CHECK(expression, code, msg, suggestion)      \\\n  lynx::base::StoreErrorIfNot(expression, code, msg, suggestion, \\\n                              lynx::base::LynxErrorLevel::Error)\n\nnamespace lynx {\nnamespace base {\n\n/** Some commonly used suggestions */\n// Suggestion for errors with a complex cause that require a detailed\n// explanation on the official site.\nstatic constexpr const char* kLynxErrorSuggestionRefOfficialSite =\n    \"Please refer to the solution in Doc 'LynxError FAQ' on the official \"\n    \"website.\";\n\nenum class LynxErrorLevel : int32_t { Fatal = 0, Error, Warn };\n\n// Store the error into ErrorStorage\nBASE_EXPORT bool StoreError(int32_t error_code, std::string error_msg,\n                            std::string fix_suggestion,\n                            LynxErrorLevel level = LynxErrorLevel::Error);\n// Store the error into ErrorStorage if expression is not satisfied.\nBASE_EXPORT bool StoreErrorIfNot(bool expression, int32_t error_code,\n                                 std::string error_msg,\n                                 std::string fix_suggestion,\n                                 LynxErrorLevel level = LynxErrorLevel::Error);\n\nstruct BASE_EXPORT LynxError {\n  LynxError(int error_code, const char* format, ...);\n  LynxError(int error_code, std::string error_msg, std::string fix_suggestion,\n            LynxErrorLevel level, bool is_logbox_only = false);\n\n  LynxError(int error_code, std::string error_message)\n      : LynxError(error_code, error_message, \"\", LynxErrorLevel::Error) {}\n\n  LynxError(LynxError&& other) = default;\n  LynxError& operator=(LynxError&& other) = default;\n\n  LynxError(const LynxError& other) = delete;\n  LynxError& operator=(const LynxError& other) = delete;\n\n  void AddCallStack(const std::string& stack);\n  void AddContextInfo(const std::string& key, const std::string& value);\n\n  static std::string GetLevelString(int32_t level);\n\n  // required fields\n  LynxErrorLevel error_level_;\n  int error_code_;\n  std::string error_message_;\n  // optional fields\n  std::string fix_suggestion_;\n  // custom fields\n  std::unordered_map<std::string, std::string> custom_info_;\n\n  // Indicates whether the error only needs to be displayed\n  // using LogBox and does not require reporting.\n  bool is_logbox_only_;\n  // Indicates whether the error should be abort\n  bool should_abort_ = false;\n};\n\nclass BASE_EXPORT ErrorStorage {\n public:\n  static ErrorStorage& GetInstance();\n\n  template <typename... T>\n  bool SetError(T&&... args) {\n    if (error_ == nullptr) {\n      error_ = std::make_unique<LynxError>(std::forward<T>(args)...);\n      return true;\n    }\n    return false;\n  }\n\n  void Reset() { error_ = nullptr; }\n\n  const std::unique_ptr<LynxError>& GetError() const { return error_; }\n\n  void AddCustomInfoToError(\n      const std::unordered_map<std::string, std::string>& custom_info);\n\n  void AddCustomInfoToError(const std::string& key, const std::string& value);\n\n private:\n  ErrorStorage() = default;\n\n  std::unique_ptr<LynxError> error_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_DEBUG_LYNX_ERROR_H_\n"
  },
  {
    "path": "base/include/expected.h",
    "content": "// Copyright 2022 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_EXPECTED_H_\n#define BASE_INCLUDE_EXPECTED_H_\n\n#include <algorithm>\n#include <type_traits>\n#include <utility>\n\n#include \"base/include/expected_internal.h\"  // IWYU pragma: export\n\n// Note: The lynx version and Chromium version differ from the standard in\n// the following ways:\n//\n// * Add `namespace lynx`.\n// * Replace `absl::*` with `std::*` since we support C++17.\n// * Replace `std::get` with `std::get_if` since `std::get` will throw\n// `std::bad_variant_access` which is not inlined and not supported until iOS 11\n// and macOS 10.14.\n// * Implement std::remove_cvref and std::remove_cvref_t which appears in\n// base/template_util.h of chromium.\n\n// Class template `expected<T, E>` is a vocabulary type which contains an\n// expected value of type `T`, or an error `E`. The class skews towards behaving\n// like a `T`, because its intended use is when the expected type is contained.\n// When something unexpected occurs, more typing is required. When all is good,\n// code mostly looks as if a `T` were being handled.\n//\n// Class template `expected<T, E>` contains either:\n// * A value of type `T`, the expected value type; or\n// * A value of type `E`, an error type used when an unexpected outcome\n// occurred.\n//\n// The interface can be queried as to whether the underlying value is the\n// expected value (of type `T`) or an unexpected value (of type `E`). The\n// interface and the rational are based on `std::optional`. We consider\n// `expected<T, E>` as a supplement to `optional<T>`, expressing why an expected\n// value isn’t contained in the object.\n//\n// Example Usage:\n//\n// Before:\n//   bool ParseInt32(base::StringPiece input,\n//                   int32_t* output,\n//                   ParseIntError* error);\n//   ...\n//\n//   int32_t output;\n//   ParseIntError error;\n//   if (ParseInt32(\"...\". &output, &error)) {\n//     // process `output`\n//   } else {\n//     // process `error`\n//   }\n//\n// After:\n//\n//   base::expected<int32_t, ParseIntError> ParseInt32(base::StringPiece input);\n//   ...\n//\n//   if (auto parsed = ParseInt32(\"...\"); parsed.has_value()) {\n//     // process `parsed.value()`\n//   } else {\n//     // process `parsed.error()`\n//   }\n//\n// References:\n// * https://wg21.link/P0323\n// * https://eel.is/c++draft/expected\n// * https://en.cppreference.com/w/cpp/utility/expected\n// * https://source.chromium.org/chromium/chromium/src/+/main:base/types/expected.h\nnamespace lynx {\nnamespace base {\n// Note: base::unexpected and base::expected are C++17 compatible backports of\n// C++23's std::unexpected and std::expected. They differ from the standard in\n// the following ways:\n//\n// * Not all member functions can be used in a constexpr context. This is due to\n//   limitations in both the C++17 language and the Abseil library used for the\n//   implementation.\n// * Since Chromium does not support exceptions, there is no bad_expected_access\n//   exception and the program will just terminate when the exception would have\n//   been thrown. Furthermore, all member functions are marked noexcept.\n// * C++23 allows an implicit conversion from U to expected<T, E> if U is\n//   implicitly convertible to T; the Chromium version only allows an implicit\n//   conversion if U is implicitly convertible to T *and* U is *not* implicitly\n//   convertible to E, to guard against bug-prone patterns such as:\n//     // creates an expected value containing true, not an unexpected value\n//     // containing 123L.\n//     expected<bool, long> e = 123L;\n// * Because of the above restriction, the Chromium version also introduces\n//   `base::ok` as a complement to `base::unexpected` to simplify returning\n//   success values when the implicit conversion above is disallowed.\n// * Calling operator* or operator-> on an unexpected value results in program\n//   termination, and not UB.\n// * There is no operator bool due to bug-prone usage when the value type is\n//   convertible to bool, see e.g. https://abseil.io/tips/141.\n// * Moving out of an expected object will put it into a moved-from state.\n//   Trying to use it before re-initializing it will result in program\n//   termination.\n// * The expected<void> specialization is done via a defaulted boolean template\n//   parameter, due to the lack of requires clauses in C++17.\n// * Since equality operators can not be defaulted in C++17, equality and\n//   inequality operators are specified explicitly.\n// * base::expected implements the monadic interface proposal\n//   (https://wg21.link/P2505), which is currently only on track for C++26.\n\n// Class template used as a type hint for constructing a `base::expected`\n// containing a value (i.e. success). Useful when implicit conversion\n// construction of `base::expected` is disallowed, e.g. due to ambiguity.\n// Example usage:\n//\n//   base::expected<std::string, std::string> RunOp() {\n//     std::string error;\n//     std::string result = RunOpImpl(..., &error);\n//     if (!error.empty()) {\n//       return base::unexpected(std::move(error));\n//     }\n//     // The C++23 std::expected proposal allows this to be simply written as\n//     //   return result;\n//     //\n//     // However, the Chromium version disallows this if E implicitly converts\n//     // to T, so without base::ok(), this would have to be written as:\n//     //   return base::expected<std::string, std::string>(std::move(result));\n//\n//     return base::ok(std::move(result));\n//   }\ntemplate <typename T>\nclass ok<T, /* is_void_v<T> = */ false> {\n public:\n  template <typename U = T, internal::EnableIfOkValueConstruction<T, U> = 0>\n  constexpr explicit ok(U&& val) noexcept : value_(std::forward<U>(val)) {}\n\n  template <typename... Args>\n  constexpr explicit ok(std::in_place_t, Args&&... args) noexcept\n      : value_(std::forward<Args>(args)...) {}\n\n  template <typename U, typename... Args>\n  constexpr explicit ok(std::in_place_t, std::initializer_list<U> il,\n                        Args&&... args) noexcept\n      : value_(il, std::forward<Args>(args)...) {}\n\n  constexpr T& value() & noexcept { return value_; }\n  constexpr const T& value() const& noexcept { return value_; }\n  constexpr T&& value() && noexcept { return std::move(value()); }\n  constexpr const T&& value() const&& noexcept { return std::move(value()); }\n\n  constexpr void swap(ok& other) noexcept {\n    using std::swap;\n    swap(value(), other.value());\n  }\n\n  friend constexpr void swap(ok& x, ok& y) noexcept { x.swap(y); }\n\n private:\n  T value_;\n};\n\ntemplate <typename T>\nclass ok<T, /* is_void_v<T> = */ true> {\n public:\n  constexpr explicit ok() noexcept = default;\n};\n\ntemplate <typename T, typename U>\nconstexpr bool operator==(const ok<T>& lhs, const ok<U>& rhs) noexcept {\n  return lhs.value() == rhs.value();\n}\n\ntemplate <typename T, typename U>\nconstexpr bool operator!=(const ok<T>& lhs, const ok<U>& rhs) noexcept {\n  return !(lhs == rhs);\n}\n\ntemplate <typename T>\nok(T) -> ok<T>;\n\nok() -> ok<void>;\n\n// [expected.un.object], class template unexpected\n// https://eel.is/c++draft/expected#un.object\ntemplate <typename E>\nclass unexpected {\n public:\n  // [expected.un.ctor] Constructors\n  template <typename Err = E,\n            internal::EnableIfUnexpectedValueConstruction<E, Err> = 0>\n  constexpr explicit unexpected(Err&& err) noexcept\n      : error_(std::forward<Err>(err)) {}\n\n  template <typename... Args>\n  constexpr explicit unexpected(std::in_place_t, Args&&... args) noexcept\n      : error_(std::forward<Args>(args)...) {}\n\n  template <typename U, typename... Args>\n  constexpr explicit unexpected(std::in_place_t, std::initializer_list<U> il,\n                                Args&&... args) noexcept\n      : error_(il, std::forward<Args>(args)...) {}\n\n  // [expected.un.obs] Observers\n  constexpr E& error() & noexcept { return error_; }\n  constexpr const E& error() const& noexcept { return error_; }\n  constexpr E&& error() && noexcept { return std::move(error()); }\n  constexpr const E&& error() const&& noexcept { return std::move(error()); }\n\n  // [expected.un.swap] Swap\n  constexpr void swap(unexpected& other) noexcept {\n    using std::swap;\n    swap(error(), other.error());\n  }\n\n  friend constexpr void swap(unexpected& x, unexpected& y) noexcept {\n    x.swap(y);\n  }\n\n private:\n  E error_;\n};\n\n// [expected.un.eq] Equality operator\ntemplate <typename E, typename G>\nconstexpr bool operator==(const unexpected<E>& lhs,\n                          const unexpected<G>& rhs) noexcept {\n  return lhs.error() == rhs.error();\n}\n\ntemplate <typename E, typename G>\nconstexpr bool operator!=(const unexpected<E>& lhs,\n                          const unexpected<G>& rhs) noexcept {\n  return !(lhs == rhs);\n}\n\ntemplate <typename E>\nunexpected(E) -> unexpected<E>;\n\n// [expected.expected], class template expected\n// https://eel.is/c++draft/expected#expected\ntemplate <typename T, typename E>\nclass [[nodiscard]] expected<T, E, /* is_void_v<T> = */ false> {\n  // Note: A partial specialization for void value types follows below.\n  static_assert(!std::is_void_v<T>, \"Error: T must not be void\");\n\n public:\n  using value_type = T;\n  using error_type = E;\n  using unexpected_type = unexpected<E>;\n\n  // Alias template to explicitly opt into the std::pointer_traits machinery.\n  // See e.g. https://en.cppreference.com/w/cpp/memory/pointer_traits#Notes\n  template <typename U>\n  using rebind = expected<U, E>;\n\n  template <typename U, typename G, bool IsVoid>\n  friend class expected;\n\n  // [expected.object.ctor], constructors\n  constexpr expected() noexcept = default;\n\n  // Converting copy and move constructors. These constructors are explicit if\n  // either the value or error type is not implicitly convertible from `rhs`'s\n  // corresponding type.\n  template <typename U, typename G,\n            internal::EnableIfExplicitConversion<T, E, const U&, const G&> = 0>\n  explicit constexpr expected(const expected<U, G>& rhs) noexcept\n      : impl_(rhs.impl_) {}\n\n  template <typename U, typename G,\n            internal::EnableIfImplicitConversion<T, E, const U&, const G&> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  /* implicit */ constexpr expected(const expected<U, G>& rhs) noexcept\n      : impl_(rhs.impl_) {}\n\n  template <typename U, typename G,\n            internal::EnableIfExplicitConversion<T, E, U, G> = 0>\n  explicit constexpr expected(expected<U, G>&& rhs) noexcept\n      : impl_(std::move(rhs.impl_)) {}\n\n  template <typename U, typename G,\n            internal::EnableIfImplicitConversion<T, E, U, G> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  /* implicit */ constexpr expected(expected<U, G>&& rhs) noexcept\n      : impl_(std::move(rhs.impl_)) {}\n\n  // Deviation from the Standard, which allows implicit conversions as long as U\n  // is implicitly convertible to T: Chromium additionally requires that U is\n  // not implicitly convertible to E.\n  template <typename U = T,\n            internal::EnableIfExplicitValueConstruction<T, E, U> = 0>\n  explicit constexpr expected(U&& v) noexcept\n      : impl_(kValTag, std::forward<U>(v)) {}\n\n  template <typename U = T,\n            internal::EnableIfImplicitValueConstruction<T, E, U> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  /* implicit */ constexpr expected(U&& v) noexcept\n      : impl_(kValTag, std::forward<U>(v)) {}\n\n  template <typename U, internal::EnableIfExplicitConstruction<T, const U&> = 0>\n  explicit constexpr expected(const ok<U>& o) noexcept\n      : impl_(kValTag, o.value()) {}\n\n  template <typename U, internal::EnableIfImplicitConstruction<T, const U&> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  /* implicit */ constexpr expected(const ok<U>& o) noexcept\n      : impl_(kValTag, o.value()) {}\n\n  template <typename U, internal::EnableIfExplicitConstruction<T, U> = 0>\n  explicit constexpr expected(ok<U>&& o) noexcept\n      : impl_(kValTag, std::move(o.value())) {}\n\n  template <typename U, internal::EnableIfImplicitConstruction<T, U> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  /* implicit */ constexpr expected(ok<U>&& o) noexcept\n      : impl_(kValTag, std::move(o.value())) {}\n\n  template <typename G, internal::EnableIfExplicitConstruction<E, const G&> = 0>\n  explicit constexpr expected(const unexpected<G>& e) noexcept\n      : impl_(kErrTag, e.error()) {}\n\n  template <typename G, internal::EnableIfImplicitConstruction<E, const G&> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  /* implicit */ constexpr expected(const unexpected<G>& e) noexcept\n      : impl_(kErrTag, e.error()) {}\n\n  template <typename G, internal::EnableIfExplicitConstruction<E, G> = 0>\n  explicit constexpr expected(unexpected<G>&& e) noexcept\n      : impl_(kErrTag, std::move(e.error())) {}\n\n  template <typename G, internal::EnableIfImplicitConstruction<E, G> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  /* implicit */ constexpr expected(unexpected<G>&& e) noexcept\n      : impl_(kErrTag, std::move(e.error())) {}\n\n  template <typename... Args>\n  constexpr explicit expected(std::in_place_t, Args&&... args) noexcept\n      : impl_(kValTag, std::forward<Args>(args)...) {}\n\n  template <typename U, typename... Args>\n  constexpr explicit expected(std::in_place_t, std::initializer_list<U> il,\n                              Args&&... args) noexcept\n      : impl_(kValTag, il, std::forward<Args>(args)...) {}\n\n  template <typename... Args>\n  constexpr explicit expected(unexpect_t, Args&&... args) noexcept\n      : impl_(kErrTag, std::forward<Args>(args)...) {}\n\n  template <typename U, typename... Args>\n  constexpr explicit expected(unexpect_t, std::initializer_list<U> il,\n                              Args&&... args) noexcept\n      : impl_(kErrTag, il, std::forward<Args>(args)...) {}\n\n  // [expected.object.assign], assignment\n  template <typename U = T, internal::EnableIfValueAssignment<T, E, U> = 0>\n  constexpr expected& operator=(U&& v) noexcept {\n    emplace(std::forward<U>(v));\n    return *this;\n  }\n\n  template <typename U>\n  constexpr expected& operator=(const ok<U>& o) noexcept {\n    emplace(o.value());\n    return *this;\n  }\n\n  template <typename U>\n  constexpr expected& operator=(ok<U>&& o) noexcept {\n    emplace(std::move(o.value()));\n    return *this;\n  }\n\n  template <typename G>\n  constexpr expected& operator=(const unexpected<G>& e) noexcept {\n    impl_.emplace_error(e.error());\n    return *this;\n  }\n\n  template <typename G>\n  constexpr expected& operator=(unexpected<G>&& e) noexcept {\n    impl_.emplace_error(std::move(e.error()));\n    return *this;\n  }\n\n  template <typename... Args>\n  constexpr T& emplace(Args&&... args) noexcept {\n    return impl_.emplace_value(std::forward<Args>(args)...);\n  }\n\n  template <typename U, typename... Args>\n  constexpr T& emplace(std::initializer_list<U> il, Args&&... args) noexcept {\n    return impl_.emplace_value(il, std::forward<Args>(args)...);\n  }\n\n  // [expected.object.swap], swap\n  constexpr void swap(expected& rhs) noexcept { impl_.swap(rhs.impl_); }\n  friend constexpr void swap(expected& x, expected& y) noexcept { x.swap(y); }\n\n  // [expected.object.obs], observers\n  constexpr T* operator->() noexcept { return std::addressof(value()); }\n  constexpr const T* operator->() const noexcept {\n    return std::addressof(value());\n  }\n\n  constexpr T& operator*() & noexcept { return value(); }\n  constexpr const T& operator*() const& noexcept { return value(); }\n  constexpr T&& operator*() && noexcept { return std::move(value()); }\n  constexpr const T&& operator*() const&& noexcept {\n    return std::move(value());\n  }\n\n  // Note: Deviation from the Standard: No operator bool due to bug-prone\n  // patterns when the value type is convertible to bool, see e.g.\n  // https://abseil.io/tips/141.\n  constexpr bool has_value() const noexcept { return impl_.has_value(); }\n\n  constexpr T& value() & noexcept { return impl_.value(); }\n  constexpr const T& value() const& noexcept { return impl_.value(); }\n  constexpr T&& value() && noexcept { return std::move(value()); }\n  constexpr const T&& value() const&& noexcept { return std::move(value()); }\n\n  constexpr E& error() & noexcept { return impl_.error(); }\n  constexpr const E& error() const& noexcept { return impl_.error(); }\n  constexpr E&& error() && noexcept { return std::move(error()); }\n  constexpr const E&& error() const&& noexcept { return std::move(error()); }\n\n  template <typename U>\n  constexpr T value_or(U&& v) const& noexcept {\n    static_assert(std::is_copy_constructible_v<T>,\n                  \"expected<T, E>::value_or: T must be copy constructible\");\n    static_assert(std::is_convertible_v<U&&, T>,\n                  \"expected<T, E>::value_or: U must be convertible to T\");\n    return has_value() ? value() : static_cast<T>(std::forward<U>(v));\n  }\n\n  template <typename U>\n  constexpr T value_or(U&& v) && noexcept {\n    static_assert(std::is_move_constructible_v<T>,\n                  \"expected<T, E>::value_or: T must be move constructible\");\n    static_assert(std::is_convertible_v<U&&, T>,\n                  \"expected<T, E>::value_or: U must be convertible to T\");\n    return has_value() ? std::move(value())\n                       : static_cast<T>(std::forward<U>(v));\n  }\n\n  template <typename G>\n  constexpr E error_or(G&& e) const& noexcept {\n    static_assert(std::is_copy_constructible_v<E>,\n                  \"expected<T, E>::error_or: E must be copy constructible\");\n    static_assert(std::is_convertible_v<G&&, E>,\n                  \"expected<T, E>::error_or: G must be convertible to E\");\n    return has_value() ? static_cast<E>(std::forward<G>(e)) : error();\n  }\n\n  template <typename G>\n  constexpr E error_or(G&& e) && noexcept {\n    static_assert(std::is_move_constructible_v<E>,\n                  \"expected<T, E>::error_or: E must be move constructible\");\n    static_assert(std::is_convertible_v<G&&, E>,\n                  \"expected<T, E>::error_or: G must be convertible to E\");\n    return has_value() ? static_cast<E>(std::forward<G>(e))\n                       : std::move(error());\n  }\n\n  // [expected.object.monadic], monadic operations\n  //\n  // This section implements the monadic interface consisting of `and_then`,\n  // `or_else`, `transform` and `transform_error`.\n\n  // `and_then`: This methods accepts a callable `f` that is invoked with\n  // `value()` in case `has_value()` is true.\n  //\n  // `f`'s return type is required to be a (cvref-qualified) specialization of\n  // base::expected with a matching error_type, i.e. it needs to be of the form\n  // base::expected<U, E> cv ref for some U.\n  //\n  // If `has_value()` is false, this is effectively a no-op, and the function\n  // returns base::expected<U, E>(unexpect, std::forward<E>(error()));\n  //\n  // `and_then` is overloaded for all possible forms of const and ref\n  // qualifiers.\n  template <typename F, typename LazyE = E,\n            internal::EnableIfCopyConstructible<LazyE> = 0>\n  constexpr auto and_then(F&& f) & noexcept {\n    return internal::AndThen(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfCopyConstructible<LazyE> = 0>\n  constexpr auto and_then(F&& f) const& noexcept {\n    return internal::AndThen(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfMoveConstructible<LazyE> = 0>\n  constexpr auto and_then(F&& f) && noexcept {\n    return internal::AndThen(std::move(*this), std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfMoveConstructible<LazyE> = 0>\n  constexpr auto and_then(F&& f) const&& noexcept {\n    return internal::AndThen(std::move(*this), std::forward<F>(f));\n  }\n\n  // `or_else`: This methods accepts a callable `f` that is invoked with\n  // `error()` in case `has_value()` is false.\n  //\n  // `f`'s return type is required to be a (cvref-qualified) specialization of\n  // base::expected with a matching value_type, i.e. it needs to be of the form\n  // base::expected<T, G> cv ref for some G.\n  //\n  // If `has_value()` is true, this is effectively a no-op, and the function\n  // returns base::expected<T, G>(std::forward<T>(value()));\n  //\n  // `or_else` is overloaded for all possible forms of const and ref\n  // qualifiers.\n  template <typename F, typename LazyT = T,\n            internal::EnableIfCopyConstructible<LazyT> = 0>\n  constexpr auto or_else(F&& f) & noexcept {\n    return internal::OrElse(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyT = T,\n            internal::EnableIfCopyConstructible<LazyT> = 0>\n  constexpr auto or_else(F&& f) const& noexcept {\n    return internal::OrElse(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyT = T,\n            internal::EnableIfMoveConstructible<LazyT> = 0>\n  constexpr auto or_else(F&& f) && noexcept {\n    return internal::OrElse(std::move(*this), std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyT = T,\n            internal::EnableIfMoveConstructible<LazyT> = 0>\n  constexpr auto or_else(F&& f) const&& noexcept {\n    return internal::OrElse(std::move(*this), std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfCopyConstructible<LazyE> = 0>\n  constexpr auto transform(F&& f) & noexcept {\n    return internal::Transform(*this, std::forward<F>(f));\n  }\n\n  // `transform`: This methods accepts a callable `f` that is invoked with\n  // `value()` in case `has_value()` is true.\n  //\n  // `f`'s return type U needs to be a valid value_type for expected, i.e. any\n  // type for which `remove_cv_t` is either void, or a complete non-array object\n  // type that is not `std::in_place_t`, `base::unexpect_t`, or a\n  // specialization of `base::ok` or `base::unexpected`.\n  //\n  // Returns an instance of base::expected<remove_cv_t<U>, E> that is\n  // constructed with f(value()) if there is a value, or unexpected(error())\n  // otherwise.\n  //\n  // `transform` is overloaded for all possible forms of const and ref\n  // qualifiers.\n  template <typename F, typename LazyE = E,\n            internal::EnableIfCopyConstructible<LazyE> = 0>\n  constexpr auto transform(F&& f) const& noexcept {\n    return internal::Transform(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfMoveConstructible<LazyE> = 0>\n  constexpr auto transform(F&& f) && noexcept {\n    return internal::Transform(std::move(*this), std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfMoveConstructible<LazyE> = 0>\n  constexpr auto transform(F&& f) const&& noexcept {\n    return internal::Transform(std::move(*this), std::forward<F>(f));\n  }\n\n  // `transform_error`: This methods accepts a callable `f` that is invoked with\n  // `error()` in case `has_value()` is false.\n  //\n  // `f`'s return type G needs to be a valid error_type for expected, i.e. any\n  // type for which `remove_cv_t` is a complete non-array object type that is\n  // not `std::in_place_t`, `base::unexpect_t`, or a specialization of\n  // `base::ok` or `base::unexpected`.\n  //\n  // Returns an instance of base::expected<T, remove_cv_t<G>> that is\n  // constructed with unexpected(f(error())) if there is no value, or value()\n  // otherwise.\n  //\n  // `transform_error` is overloaded for all possible forms of const and ref\n  // qualifiers.\n  template <typename F, typename LazyT = T,\n            internal::EnableIfCopyConstructible<LazyT> = 0>\n  constexpr auto transform_error(F&& f) & noexcept {\n    return internal::TransformError(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyT = T,\n            internal::EnableIfCopyConstructible<LazyT> = 0>\n  constexpr auto transform_error(F&& f) const& noexcept {\n    return internal::TransformError(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyT = T,\n            internal::EnableIfMoveConstructible<LazyT> = 0>\n  constexpr auto transform_error(F&& f) && noexcept {\n    return internal::TransformError(std::move(*this), std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyT = T,\n            internal::EnableIfMoveConstructible<LazyT> = 0>\n  constexpr auto transform_error(F&& f) const&& noexcept {\n    return internal::TransformError(std::move(*this), std::forward<F>(f));\n  }\n\n private:\n  using Impl = internal::ExpectedImpl<T, E>;\n  static constexpr auto kValTag = Impl::kValTag;\n  static constexpr auto kErrTag = Impl::kErrTag;\n\n  Impl impl_;\n};\n\n// [expected.void], partial specialization of expected for void types\ntemplate <typename T, typename E>\nclass [[nodiscard]] expected<T, E, /* is_void_v<T> = */ true> {\n  // Note: A partial specialization for non-void value types can be found above.\n  static_assert(std::is_void_v<T>, \"Error: T must be void\");\n\n public:\n  using value_type = T;\n  using error_type = E;\n  using unexpected_type = unexpected<E>;\n\n  // Alias template to explicitly opt into the std::pointer_traits machinery.\n  // See e.g. https://en.cppreference.com/w/cpp/memory/pointer_traits#Notes\n  template <typename U>\n  using rebind = expected<U, E>;\n\n  template <typename U, typename G, bool IsVoid>\n  friend class expected;\n\n  // [expected.void.ctor], constructors\n  constexpr expected() noexcept = default;\n\n  // Converting copy and move constructors. These constructors are explicit if\n  // the error type is not implicitly convertible from `rhs`'s error type.\n  template <typename U, typename G,\n            internal::EnableIfExplicitVoidConversion<E, U, const G&> = 0>\n  constexpr explicit expected(const expected<U, G>& rhs) noexcept\n      : impl_(rhs.impl_) {}\n\n  template <typename U, typename G,\n            internal::EnableIfImplicitVoidConversion<E, U, const G&> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  constexpr /* implicit */ expected(const expected<U, G>& rhs) noexcept\n      : impl_(rhs.impl_) {}\n\n  template <typename U, typename G,\n            internal::EnableIfExplicitVoidConversion<E, U, G> = 0>\n  constexpr explicit expected(expected<U, G>&& rhs) noexcept\n      : impl_(std::move(rhs.impl_)) {}\n\n  template <typename U, typename G,\n            internal::EnableIfImplicitVoidConversion<E, U, G> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  constexpr /* implicit */ expected(expected<U, G>&& rhs) noexcept\n      : impl_(std::move(rhs.impl_)) {}\n\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  constexpr /* implicit */ expected(base::ok<T>) noexcept {}\n\n  template <typename G, internal::EnableIfExplicitConstruction<E, const G&> = 0>\n  explicit constexpr expected(const unexpected<G>& e) noexcept\n      : impl_(kErrTag, e.error()) {}\n\n  template <typename G, internal::EnableIfImplicitConstruction<E, const G&> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  /* implicit */ constexpr expected(const unexpected<G>& e) noexcept\n      : impl_(kErrTag, e.error()) {}\n\n  template <typename G, internal::EnableIfExplicitConstruction<E, G> = 0>\n  explicit constexpr expected(unexpected<G>&& e) noexcept\n      : impl_(kErrTag, std::move(e.error())) {}\n\n  template <typename G, internal::EnableIfImplicitConstruction<E, G> = 0>\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  /* implicit */ constexpr expected(unexpected<G>&& e) noexcept\n      : impl_(kErrTag, std::move(e.error())) {}\n\n  constexpr explicit expected(std::in_place_t) noexcept {}\n\n  template <typename... Args>\n  constexpr explicit expected(unexpect_t, Args&&... args) noexcept\n      : impl_(kErrTag, std::forward<Args>(args)...) {}\n\n  template <typename U, typename... Args>\n  constexpr explicit expected(unexpect_t, std::initializer_list<U> il,\n                              Args&&... args) noexcept\n      : impl_(kErrTag, il, std::forward<Args>(args)...) {}\n\n  // [expected.void.assign], assignment\n  template <typename G>\n  constexpr expected& operator=(const unexpected<G>& e) noexcept {\n    impl_.emplace_error(e.error());\n    return *this;\n  }\n\n  template <typename G>\n  constexpr expected& operator=(unexpected<G>&& e) noexcept {\n    impl_.emplace_error(std::move(e.error()));\n    return *this;\n  }\n\n  constexpr void emplace() noexcept { impl_.emplace_value(); }\n\n  // [expected.void.swap], swap\n  constexpr void swap(expected& rhs) noexcept { impl_.swap(rhs.impl_); }\n  friend constexpr void swap(expected& x, expected& y) noexcept { x.swap(y); }\n\n  // [expected.void.obs], observers\n  // Note: Deviation from the Standard: No operator bool due to consistency with\n  // non-void expected types.\n  constexpr bool has_value() const noexcept { return impl_.has_value(); }\n\n  constexpr void operator*() const { LYNX_BASE_CHECK(has_value()); }\n  constexpr void value() const { LYNX_BASE_CHECK(has_value()); }\n\n  constexpr E& error() & { return impl_.error(); }\n  constexpr const E& error() const& { return impl_.error(); }\n  constexpr E&& error() && { return std::move(error()); }\n  constexpr const E&& error() const&& { return std::move(error()); }\n\n  template <typename G>\n  constexpr E error_or(G&& e) const& noexcept {\n    static_assert(std::is_copy_constructible_v<E>,\n                  \"expected<T, E>::error_or: E must be copy constructible\");\n    static_assert(std::is_convertible_v<G&&, E>,\n                  \"expected<T, E>::error_or: G must be convertible to E\");\n    return has_value() ? static_cast<E>(std::forward<G>(e)) : error();\n  }\n\n  template <typename G>\n  constexpr E error_or(G&& e) && noexcept {\n    static_assert(std::is_move_constructible_v<E>,\n                  \"expected<T, E>::error_or: E must be move constructible\");\n    static_assert(std::is_convertible_v<G&&, E>,\n                  \"expected<T, E>::error_or: G must be convertible to E\");\n    return has_value() ? static_cast<E>(std::forward<G>(e))\n                       : std::move(error());\n  }\n\n  // [expected.void.monadic], monadic operations\n  //\n  // This section implements the monadic interface consisting of `and_then`,\n  // `or_else`, `transform` and `transform_error`. In contrast to the non void\n  // specialization it is mandated that the callables for `and_then` and\n  // `transform`don't take any arguments.\n\n  // `and_then`: This methods accepts a callable `f` that is invoked with\n  // no arguments in case `has_value()` is true.\n  //\n  // `f`'s return type is required to be a (cvref-qualified) specialization of\n  // base::expected with a matching error_type, i.e. it needs to be of the form\n  // base::expected<U, E> cv ref for some U.\n  //\n  // If `has_value()` is false, this is effectively a no-op, and the function\n  // returns base::expected<U, E>(unexpect, std::forward<E>(error()));\n  //\n  // `and_then` is overloaded for all possible forms of const and ref\n  // qualifiers.\n  template <typename F, typename LazyE = E,\n            internal::EnableIfCopyConstructible<LazyE> = 0>\n  constexpr auto and_then(F&& f) & noexcept {\n    return internal::AndThen(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfCopyConstructible<LazyE> = 0>\n  constexpr auto and_then(F&& f) const& noexcept {\n    return internal::AndThen(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfMoveConstructible<LazyE> = 0>\n  constexpr auto and_then(F&& f) && noexcept {\n    return internal::AndThen(std::move(*this), std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfMoveConstructible<LazyE> = 0>\n  constexpr auto and_then(F&& f) const&& noexcept {\n    return internal::AndThen(std::move(*this), std::forward<F>(f));\n  }\n\n  // `or_else`: This methods accepts a callable `f` that is invoked with\n  // `error()` in case `has_value()` is false.\n  //\n  // `f`'s return type is required to be a (cvref-qualified) specialization of\n  // base::expected with a matching value_type, i.e. it needs to be cv void.\n  //\n  // If `has_value()` is true, this is effectively a no-op, and the function\n  // returns base::expected<cv void, G>().\n  //\n  // `or_else` is overloaded for all possible forms of const and ref\n  // qualifiers.\n  template <typename F>\n  constexpr auto or_else(F&& f) & noexcept {\n    return internal::OrElse(*this, std::forward<F>(f));\n  }\n\n  template <typename F>\n  constexpr auto or_else(F&& f) const& noexcept {\n    return internal::OrElse(*this, std::forward<F>(f));\n  }\n\n  template <typename F>\n  constexpr auto or_else(F&& f) && noexcept {\n    return internal::OrElse(std::move(*this), std::forward<F>(f));\n  }\n\n  template <typename F>\n  constexpr auto or_else(F&& f) const&& noexcept {\n    return internal::OrElse(std::move(*this), std::forward<F>(f));\n  }\n\n  // `transform`: This methods accepts a callable `f` that is invoked with no\n  // arguments in case `has_value()` is true.\n  //\n  // `f`'s return type U needs to be a valid value_type for expected, i.e. any\n  // type for which `remove_cv_t` is either void, or a complete non-array object\n  // type that is not `std::in_place_t`, `base::unexpect_t`, or a\n  // specialization of `base::ok` or `base::unexpected`.\n  //\n  // Returns an instance of base::expected<remove_cv_t<U>, E> that is\n  // constructed with f() if has_value() is true, or unexpected(error())\n  // otherwise.\n  //\n  // `transform` is overloaded for all possible forms of const and ref\n  // qualifiers.\n  template <typename F, typename LazyE = E,\n            internal::EnableIfCopyConstructible<LazyE> = 0>\n  constexpr auto transform(F&& f) & noexcept {\n    return internal::Transform(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfCopyConstructible<LazyE> = 0>\n  constexpr auto transform(F&& f) const& noexcept {\n    return internal::Transform(*this, std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfMoveConstructible<LazyE> = 0>\n  constexpr auto transform(F&& f) && noexcept {\n    return internal::Transform(std::move(*this), std::forward<F>(f));\n  }\n\n  template <typename F, typename LazyE = E,\n            internal::EnableIfMoveConstructible<LazyE> = 0>\n  constexpr auto transform(F&& f) const&& noexcept {\n    return internal::Transform(std::move(*this), std::forward<F>(f));\n  }\n\n  // `transform_error`: This methods accepts a callable `f` that is invoked with\n  // `error()` in case `has_value()` is false.\n  //\n  // `f`'s return type G needs to be a valid error_type for expected, i.e. any\n  // type for which `remove_cv_t` is a complete non-array object type that is\n  // not `std::in_place_t`, `base::unexpect_t`, or a specialization of\n  // `base::ok` or `base::unexpected`.\n  //\n  // Returns an instance of base::expected<cv void, remove_cv_t<G>> that is\n  // constructed with unexpected(f(error())) if there is no value, or default\n  // constructed otherwise.\n  //\n  // `transform_error` is overloaded for all possible forms of const and ref\n  // qualifiers.\n  template <typename F>\n  constexpr auto transform_error(F&& f) & noexcept {\n    return internal::TransformError(*this, std::forward<F>(f));\n  }\n\n  template <typename F>\n  constexpr auto transform_error(F&& f) const& noexcept {\n    return internal::TransformError(*this, std::forward<F>(f));\n  }\n\n  template <typename F>\n  constexpr auto transform_error(F&& f) && noexcept {\n    return internal::TransformError(std::move(*this), std::forward<F>(f));\n  }\n\n  template <typename F>\n  constexpr auto transform_error(F&& f) const&& noexcept {\n    return internal::TransformError(std::move(*this), std::forward<F>(f));\n  }\n\n private:\n  // Note: Since we can't store void types we use std::monostate instead.\n  using Impl = internal::ExpectedImpl<std::monostate, E>;\n  static constexpr auto kErrTag = Impl::kErrTag;\n\n  Impl impl_;\n};\n\n// [expected.object.eq], equality operators\n// [expected.void.eq], equality operators\ntemplate <typename T, typename E, typename U, typename G, bool IsVoid>\nconstexpr bool operator==(const expected<T, E, IsVoid>& x,\n                          const expected<U, G, IsVoid>& y) noexcept {\n  auto equal_values = [](const auto& x, const auto& y) {\n    // Values for expected void types always compare equal.\n    if constexpr (IsVoid) {\n      return true;\n    } else {\n      return x.value() == y.value();\n    }\n  };\n\n  return x.has_value() == y.has_value() &&\n         (x.has_value() ? equal_values(x, y) : x.error() == y.error());\n}\n\ntemplate <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>\nconstexpr bool operator==(const expected<T, E>& x, const U& v) noexcept {\n  return x.has_value() && x.value() == v;\n}\n\ntemplate <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>\nconstexpr bool operator==(const U& v, const expected<T, E>& x) noexcept {\n  return x == v;\n}\n\ntemplate <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>\nconstexpr bool operator==(const expected<T, E>& x, const ok<U>& o) noexcept {\n  return x.has_value() && x.value() == o.value();\n}\n\ntemplate <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>\nconstexpr bool operator==(const ok<U>& o, const expected<T, E>& x) noexcept {\n  return x == o;\n}\n\ntemplate <typename T, typename E, typename G>\nconstexpr bool operator==(const expected<T, E>& x,\n                          const unexpected<G>& e) noexcept {\n  return !x.has_value() && x.error() == e.error();\n}\n\ntemplate <typename T, typename E, typename G>\nconstexpr bool operator==(const unexpected<G>& e,\n                          const expected<T, E>& x) noexcept {\n  return x == e;\n}\n\ntemplate <typename T, typename E, typename U, typename G, bool IsVoid>\nconstexpr bool operator!=(const expected<T, E, IsVoid>& x,\n                          const expected<U, G, IsVoid>& y) noexcept {\n  return !(x == y);\n}\n\ntemplate <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>\nconstexpr bool operator!=(const expected<T, E>& x, const U& v) noexcept {\n  return !(x == v);\n}\n\ntemplate <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>\nconstexpr bool operator!=(const U& v, const expected<T, E>& x) noexcept {\n  return !(v == x);\n}\n\ntemplate <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>\nconstexpr bool operator!=(const expected<T, E>& x, const ok<U>& o) noexcept {\n  return !(x == o);\n}\n\ntemplate <typename T, typename E, typename U, internal::EnableIfNotVoid<T> = 0>\nconstexpr bool operator!=(const ok<U>& o, const expected<T, E>& x) noexcept {\n  return !(o == x);\n}\n\ntemplate <typename T, typename E, typename G>\nconstexpr bool operator!=(const expected<T, E>& x,\n                          const unexpected<G>& e) noexcept {\n  return !(x == e);\n}\n\ntemplate <typename T, typename E, typename G>\nconstexpr bool operator!=(const unexpected<G>& e,\n                          const expected<T, E>& x) noexcept {\n  return !(e == x);\n}\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_EXPECTED_H_\n"
  },
  {
    "path": "base/include/expected_internal.h",
    "content": "// Copyright 2022 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_EXPECTED_INTERNAL_H_\n#define BASE_INCLUDE_EXPECTED_INTERNAL_H_\n\n// IWYU pragma: private, include \"base/expected.h\"\n#include <functional>\n#include <type_traits>\n#include <utility>\n#include <variant>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/type_traits_addon.h\"\n\n// This header defines type traits and aliases used for the implementation of\n// base::expected.\nnamespace lynx {\nnamespace base {\n\ntemplate <typename T, bool = std::is_void_v<T>>\nclass ok;\n\ntemplate <typename E>\nclass unexpected;\n\nstruct unexpect_t {\n  explicit unexpect_t() = default;\n};\n\n// in-place construction of unexpected values\ninline constexpr unexpect_t unexpect{};\n\ntemplate <typename T, typename E, bool = std::is_void_v<T>>\nclass expected;\n\nnamespace internal {\n\ntemplate <typename T>\nstruct IsOk : std::false_type {};\n\ntemplate <typename T>\nstruct IsOk<ok<T>> : std::true_type {};\n\ntemplate <typename T>\nstruct IsUnexpected : std::false_type {};\n\ntemplate <typename E>\nstruct IsUnexpected<unexpected<E>> : std::true_type {};\n\ntemplate <typename T>\nstruct IsExpected : std::false_type {};\n\ntemplate <typename T, typename E>\nstruct IsExpected<expected<T, E>> : std::true_type {};\n\ntemplate <typename T, typename U>\nstruct IsConstructibleOrConvertible\n    : std::disjunction<std::is_constructible<T, U>, std::is_convertible<U, T>> {\n};\n\ntemplate <typename T, typename U>\nstruct IsAnyConstructibleOrConvertible\n    : std::disjunction<IsConstructibleOrConvertible<T, U&>,\n                       IsConstructibleOrConvertible<T, U&&>,\n                       IsConstructibleOrConvertible<T, const U&>,\n                       IsConstructibleOrConvertible<T, const U&&>> {};\n\n// Checks whether a given expected<U, G> can be converted into another\n// expected<T, E>. Used inside expected's conversion constructors. UF and GF are\n// the forwarded versions of U and G, e.g. UF is const U& for the converting\n// copy constructor and U for the converting move constructor. Similarly for GF.\n// ExUG is used for convenience, and not expected to be passed explicitly.\n// See https://eel.is/c++draft/expected#lib:expected,constructor___\ntemplate <typename T, typename E, typename UF, typename GF,\n          typename ExUG = expected<remove_cvref_t<UF>, remove_cvref_t<GF>>>\nstruct IsValidConversion\n    : std::conjunction<\n          std::is_constructible<T, UF>, std::is_constructible<E, GF>,\n          std::negation<IsAnyConstructibleOrConvertible<T, ExUG>>,\n          std::negation<IsAnyConstructibleOrConvertible<unexpected<E>, ExUG>>> {\n};\n\n// Checks whether a given expected<U, G> can be converted into another\n// expected<T, E> when T is a void type. Used inside expected<void>'s conversion\n// constructors. GF is the forwarded versions of G, e.g. GF is const G& for the\n// converting copy constructor and G for the converting move constructor. ExUG\n// is used for convenience, and not expected to be passed explicitly. See\n// https://eel.is/c++draft/expected#lib:expected%3cvoid%3e,constructor___\ntemplate <typename E, typename U, typename GF,\n          typename ExUG = expected<U, remove_cvref_t<GF>>>\nstruct IsValidVoidConversion\n    : std::conjunction<\n          std::is_void<U>, std::is_constructible<E, GF>,\n          std::negation<IsAnyConstructibleOrConvertible<unexpected<E>, ExUG>>> {\n};\n\n// Checks whether expected<T, E> can be constructed from a value of type U.\ntemplate <typename T, typename E, typename U>\nstruct IsValidValueConstruction\n    : std::conjunction<\n          std::is_constructible<T, U>,\n          std::negation<std::is_same<remove_cvref_t<U>, std::in_place_t>>,\n          std::negation<std::is_same<remove_cvref_t<U>, expected<T, E>>>,\n          std::negation<IsOk<remove_cvref_t<U>>>,\n          std::negation<IsUnexpected<remove_cvref_t<U>>>> {};\n\ntemplate <typename T, typename E, typename UF, typename GF>\nstruct AreValueAndErrorConvertible\n    : std::conjunction<std::is_convertible<UF, T>, std::is_convertible<GF, E>> {\n};\n\ntemplate <typename T>\nusing EnableIfDefaultConstruction =\n    std::enable_if_t<std::is_default_constructible_v<T>, int>;\n\ntemplate <typename T, typename E, typename UF, typename GF>\nusing EnableIfExplicitConversion = std::enable_if_t<\n    std::conjunction_v<\n        IsValidConversion<T, E, UF, GF>,\n        std::negation<AreValueAndErrorConvertible<T, E, UF, GF>>>,\n    int>;\n\ntemplate <typename T, typename E, typename UF, typename GF>\nusing EnableIfImplicitConversion = std::enable_if_t<\n    std::conjunction_v<IsValidConversion<T, E, UF, GF>,\n                       AreValueAndErrorConvertible<T, E, UF, GF>>,\n    int>;\n\ntemplate <typename E, typename U, typename GF>\nusing EnableIfExplicitVoidConversion = std::enable_if_t<\n    std::conjunction_v<IsValidVoidConversion<E, U, GF>,\n                       std::negation<std::is_convertible<GF, E>>>,\n    int>;\n\ntemplate <typename E, typename U, typename GF>\nusing EnableIfImplicitVoidConversion =\n    std::enable_if_t<std::conjunction_v<IsValidVoidConversion<E, U, GF>,\n                                        std::is_convertible<GF, E>>,\n                     int>;\n\ntemplate <typename T, typename U>\nusing EnableIfOkValueConstruction = std::enable_if_t<\n    std::conjunction_v<\n        std::negation<std::is_same<remove_cvref_t<U>, ok<T>>>,\n        std::negation<std::is_same<remove_cvref_t<U>, std::in_place_t>>,\n        std::is_constructible<T, U>>,\n    int>;\n\ntemplate <typename T, typename U>\nusing EnableIfUnexpectedValueConstruction = std::enable_if_t<\n    std::conjunction_v<\n        std::negation<std::is_same<remove_cvref_t<U>, unexpected<T>>>,\n        std::negation<std::is_same<remove_cvref_t<U>, std::in_place_t>>,\n        std::is_constructible<T, U>>,\n    int>;\n\ntemplate <typename T, typename E, typename U>\nusing EnableIfExplicitValueConstruction = std::enable_if_t<\n    std::conjunction_v<\n        IsValidValueConstruction<T, E, U>,\n        std::disjunction<std::negation<std::is_convertible<U, T>>,\n                         std::is_convertible<U, E>>>,\n    int>;\n\ntemplate <typename T, typename E, typename U>\nusing EnableIfImplicitValueConstruction = std::enable_if_t<\n    std::conjunction_v<\n        IsValidValueConstruction<T, E, U>,\n        std::conjunction<std::is_convertible<U, T>,\n                         std::negation<std::is_convertible<U, E>>>>,\n    int>;\n\ntemplate <typename T, typename U>\nusing EnableIfExplicitConstruction = std::enable_if_t<\n    std::conjunction_v<std::is_constructible<T, U>,\n                       std::negation<std::is_convertible<U, T>>>,\n    int>;\n\ntemplate <typename T, typename U>\nusing EnableIfImplicitConstruction = std::enable_if_t<\n    std::conjunction_v<std::is_constructible<T, U>, std::is_convertible<U, T>>,\n    int>;\n\ntemplate <typename T, typename E, typename U>\nusing EnableIfValueAssignment = std::enable_if_t<\n    std::conjunction_v<\n        std::negation<std::is_same<expected<T, E>, remove_cvref_t<U>>>,\n        std::negation<IsOk<remove_cvref_t<U>>>,\n        std::negation<IsUnexpected<remove_cvref_t<U>>>,\n        std::is_constructible<T, U>, std::is_assignable<T&, U>>,\n    int>;\n\ntemplate <typename T>\nusing EnableIfCopyConstructible =\n    std::enable_if_t<std::is_copy_constructible_v<T>, int>;\n\ntemplate <typename T>\nusing EnableIfMoveConstructible =\n    std::enable_if_t<std::is_move_constructible_v<T>, int>;\n\ntemplate <typename T>\nusing EnableIfNotVoid = std::enable_if_t<std::negation_v<std::is_void<T>>, int>;\n\ntemplate <typename T, typename E>\nclass ExpectedImpl {\n public:\n  static constexpr size_t kValIdx = 1;\n  static constexpr size_t kErrIdx = 2;\n  static constexpr std::in_place_index_t<1> kValTag{};\n  static constexpr std::in_place_index_t<2> kErrTag{};\n\n  template <typename U, typename G>\n  friend class ExpectedImpl;\n\n  template <typename LazyT = T, EnableIfDefaultConstruction<LazyT> = 0>\n  constexpr ExpectedImpl() noexcept : data_(kValTag) {}\n  constexpr ExpectedImpl(const ExpectedImpl& rhs) noexcept : data_(rhs.data_) {\n    LYNX_BASE_CHECK(!rhs.is_moved_from());\n  }\n  constexpr ExpectedImpl(ExpectedImpl&& rhs) noexcept\n      : data_(std::move(rhs.data_)) {\n    LYNX_BASE_CHECK(!rhs.is_moved_from());\n    rhs.set_is_moved_from();\n  }\n\n  template <typename U, typename G>\n  constexpr explicit ExpectedImpl(const ExpectedImpl<U, G>& rhs) noexcept {\n    if (rhs.has_value()) {\n      emplace_value(rhs.value());\n    } else {\n      emplace_error(rhs.error());\n    }\n  }\n\n  template <typename U, typename G>\n  constexpr explicit ExpectedImpl(ExpectedImpl<U, G>&& rhs) noexcept {\n    if (rhs.has_value()) {\n      emplace_value(std::move(rhs.value()));\n    } else {\n      emplace_error(std::move(rhs.error()));\n    }\n    rhs.set_is_moved_from();\n  }\n\n  template <typename... Args>\n  constexpr explicit ExpectedImpl(decltype(kValTag), Args&&... args) noexcept\n      : data_(kValTag, std::forward<Args>(args)...) {}\n\n  template <typename U, typename... Args>\n  constexpr explicit ExpectedImpl(decltype(kValTag),\n                                  std::initializer_list<U> il,\n                                  Args&&... args) noexcept\n      : data_(kValTag, il, std::forward<Args>(args)...) {}\n\n  template <typename... Args>\n  constexpr explicit ExpectedImpl(decltype(kErrTag), Args&&... args) noexcept\n      : data_(kErrTag, std::forward<Args>(args)...) {}\n\n  template <typename U, typename... Args>\n  constexpr explicit ExpectedImpl(decltype(kErrTag),\n                                  std::initializer_list<U> il,\n                                  Args&&... args) noexcept\n      : data_(kErrTag, il, std::forward<Args>(args)...) {}\n\n  constexpr ExpectedImpl& operator=(const ExpectedImpl& rhs) noexcept {\n    LYNX_BASE_CHECK(!rhs.is_moved_from());\n    data_ = rhs.data_;\n    return *this;\n  }\n\n  constexpr ExpectedImpl& operator=(ExpectedImpl&& rhs) noexcept {\n    LYNX_BASE_CHECK(!rhs.is_moved_from());\n    data_ = std::move(rhs.data_);\n    rhs.set_is_moved_from();\n    return *this;\n  }\n\n  template <typename... Args>\n  constexpr T& emplace_value(Args&&... args) noexcept {\n    return data_.template emplace<kValIdx>(std::forward<Args>(args)...);\n  }\n\n  template <typename U, typename... Args>\n  constexpr T& emplace_value(std::initializer_list<U> il,\n                             Args&&... args) noexcept {\n    return data_.template emplace<kValIdx>(il, std::forward<Args>(args)...);\n  }\n\n  template <typename... Args>\n  constexpr E& emplace_error(Args&&... args) noexcept {\n    return data_.template emplace<kErrIdx>(std::forward<Args>(args)...);\n  }\n\n  template <typename U, typename... Args>\n  constexpr E& emplace_error(std::initializer_list<U> il,\n                             Args&&... args) noexcept {\n    return data_.template emplace<kErrIdx>(il, std::forward<Args>(args)...);\n  }\n\n  void swap(ExpectedImpl& rhs) noexcept {\n    LYNX_BASE_CHECK(!is_moved_from());\n    LYNX_BASE_CHECK(!rhs.is_moved_from());\n    data_.swap(rhs.data_);\n  }\n\n  constexpr bool has_value() const noexcept {\n    LYNX_BASE_CHECK(!is_moved_from());\n    return data_.index() == kValIdx;\n  }\n\n  // Note: Changing from `std::get` to `std::get_if` to avoid getting a\n  // `std::bad_variant_access`, which does not compile on iOS 10.\n  // This is because the virtual `std::bad_variant_access::what()` method is not\n  // inlined and thus defined in the `libc++.dylib` (provided by the OS).\n  constexpr T& value() noexcept {\n    LYNX_BASE_CHECK(!is_moved_from());\n    T* ptr = std::get_if<kValIdx>(&data_);\n    LYNX_BASE_CHECK(ptr != nullptr);\n    return *ptr;\n  }\n  constexpr const T& value() const noexcept {\n    LYNX_BASE_CHECK(!is_moved_from());\n    const T* ptr = std::get_if<kValIdx>(&data_);\n    LYNX_BASE_CHECK(ptr != nullptr);\n    return *ptr;\n  }\n\n  constexpr E& error() noexcept {\n    LYNX_BASE_CHECK(!is_moved_from());\n    E* ptr = std::get_if<kErrIdx>(&data_);\n    LYNX_BASE_CHECK(ptr != nullptr);\n    return *ptr;\n  }\n  constexpr const E& error() const noexcept {\n    LYNX_BASE_CHECK(!is_moved_from());\n    const E* ptr = std::get_if<kErrIdx>(&data_);\n    LYNX_BASE_CHECK(ptr != nullptr);\n    return *ptr;\n  }\n\n private:\n  static constexpr size_t kNulIdx = 0;\n  static_assert(kNulIdx != kValIdx);\n  static_assert(kNulIdx != kErrIdx);\n\n  constexpr bool is_moved_from() const noexcept {\n    return data_.index() == kNulIdx;\n  }\n\n  constexpr void set_is_moved_from() noexcept {\n    data_.template emplace<kNulIdx>();\n  }\n\n  std::variant<std::monostate, T, E> data_;\n};\n\ntemplate <typename Exp, typename F>\nconstexpr auto AndThen(Exp&& exp, F&& f) noexcept {\n  using T = remove_cvref_t<decltype(exp.value())>;\n  using E = remove_cvref_t<decltype(exp.error())>;\n\n  auto invoke_f = [&]() -> decltype(auto) {\n    if constexpr (!std::is_void_v<T>) {\n      return std::invoke(std::forward<F>(f), std::forward<Exp>(exp).value());\n    } else {\n      return std::invoke(std::forward<F>(f));\n    }\n  };\n\n  using U = remove_cvref_t<decltype(invoke_f())>;\n  static_assert(internal::IsExpected<U>::value,\n                \"expected<T, E>::and_then: Result of f() must be a \"\n                \"specialization of expected\");\n  static_assert(\n      std::is_same_v<typename U::error_type, E>,\n      \"expected<T, E>::and_then: Result of f() must have E as error_type\");\n\n  return exp.has_value() ? invoke_f()\n                         : U(unexpect, std::forward<Exp>(exp).error());\n}\n\ntemplate <typename Exp, typename F>\nconstexpr auto OrElse(Exp&& exp, F&& f) noexcept {\n  using T = remove_cvref_t<decltype(exp.value())>;\n  using G = remove_cvref_t<\n      std::invoke_result_t<F, decltype(std::forward<Exp>(exp).error())>>;\n\n  static_assert(internal::IsExpected<G>::value,\n                \"expected<T, E>::or_else: Result of f() must be a \"\n                \"specialization of expected\");\n  static_assert(\n      std::is_same_v<typename G::value_type, T>,\n      \"expected<T, E>::or_else: Result of f() must have T as value_type\");\n\n  if (!exp.has_value()) {\n    return std::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());\n  }\n\n  if constexpr (!std::is_void_v<T>) {\n    return G(std::in_place, std::forward<Exp>(exp).value());\n  } else {\n    return G();\n  }\n}\n\ntemplate <typename Exp, typename F>\nconstexpr auto Transform(Exp&& exp, F&& f) noexcept {\n  using T = remove_cvref_t<decltype(exp.value())>;\n  using E = remove_cvref_t<decltype(exp.error())>;\n\n  auto invoke_f = [&]() -> decltype(auto) {\n    if constexpr (!std::is_void_v<T>) {\n      return std::invoke(std::forward<F>(f), std::forward<Exp>(exp).value());\n    } else {\n      return std::invoke(std::forward<F>(f));\n    }\n  };\n\n  using U = std::remove_cv_t<decltype(invoke_f())>;\n  if constexpr (!std::is_void_v<U>) {\n    static_assert(!std::is_array_v<U>,\n                  \"expected<T, E>::transform: Result of f() should \"\n                  \"not be an Array\");\n    static_assert(!std::is_same_v<U, std::in_place_t>,\n                  \"expected<T, E>::transform: Result of f() should \"\n                  \"not be std::in_place_t\");\n    static_assert(!std::is_same_v<U, unexpect_t>,\n                  \"expected<T, E>::transform: Result of f() should \"\n                  \"not be unexpect_t\");\n    static_assert(!internal::IsOk<U>::value,\n                  \"expected<T, E>::transform: Result of f() should \"\n                  \"not be a specialization of ok\");\n    static_assert(!internal::IsUnexpected<U>::value,\n                  \"expected<T, E>::transform: Result of f() should \"\n                  \"not be a specialization of unexpected\");\n    static_assert(std::is_object_v<U>,\n                  \"expected<T, E>::transform: Result of f() should be \"\n                  \"an object type\");\n  }\n\n  if (!exp.has_value()) {\n    return expected<U, E>(unexpect, std::forward<Exp>(exp).error());\n  }\n\n  if constexpr (!std::is_void_v<U>) {\n    return expected<U, E>(std::in_place, invoke_f());\n  } else {\n    invoke_f();\n    return expected<U, E>();\n  }\n}\n\ntemplate <typename Exp, typename F>\nconstexpr auto TransformError(Exp&& exp, F&& f) noexcept {\n  using T = remove_cvref_t<decltype(exp.value())>;\n  using G = std::remove_cv_t<\n      std::invoke_result_t<F, decltype(std::forward<Exp>(exp).error())>>;\n\n  static_assert(\n      !std::is_array_v<G>,\n      \"expected<T, E>::transform_error: Result of f() should not be an Array\");\n  static_assert(!std::is_same_v<G, std::in_place_t>,\n                \"expected<T, E>::transform_error: Result of f() should not be \"\n                \"std::in_place_t\");\n  static_assert(!std::is_same_v<G, unexpect_t>,\n                \"expected<T, E>::transform_error: Result of f() should not be \"\n                \"unexpect_t\");\n  static_assert(!internal::IsOk<G>::value,\n                \"expected<T, E>::transform_error: Result of f() should not be \"\n                \"a specialization of ok\");\n  static_assert(!internal::IsUnexpected<G>::value,\n                \"expected<T, E>::transform_error: Result of f() should not be \"\n                \"a specialization of unexpected\");\n  static_assert(std::is_object_v<G>,\n                \"expected<T, E>::transform_error: Result of f() should be an \"\n                \"object type\");\n\n  if (!exp.has_value()) {\n    return expected<T, G>(\n        unexpect,\n        std::invoke(std::forward<F>(f), std::forward<Exp>(exp).error()));\n  }\n\n  if constexpr (std::is_void_v<T>) {\n    return expected<T, G>();\n  } else {\n    return expected<T, G>(std::in_place, std::forward<Exp>(exp).value());\n  }\n}\n\n}  // namespace internal\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_EXPECTED_INTERNAL_H_\n"
  },
  {
    "path": "base/include/file_utils.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FILE_UTILS_H_\n#define BASE_INCLUDE_FILE_UTILS_H_\n\n#include <string>\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass BASE_EXPORT FileUtils {\n public:\n  [[nodiscard]] static bool ReadFileBinary(const std::string &path,\n                                           size_t max_size,\n                                           std::string &file_content);\n  [[nodiscard]] static bool WriteFileBinary(const std::string &path,\n                                            const unsigned char *file_content,\n                                            size_t size);\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FILE_UTILS_H_\n"
  },
  {
    "path": "base/include/flex_optional.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FLEX_OPTIONAL_H_\n#define BASE_INCLUDE_FLEX_OPTIONAL_H_\n\n#include <cstdlib>\n#include <memory>\n#include <optional>\n#include <type_traits>\n#include <utility>\n\nnamespace lynx {\nnamespace base {\n\nnamespace optional {\n\n// Heap allocated storage\ntemplate <class T>\nstruct flex_optional_mem_save {\n  using value_type = T;\n  ~flex_optional_mem_save() = default;\n  flex_optional_mem_save() noexcept = default;\n  flex_optional_mem_save(const flex_optional_mem_save& other) {\n    if (other.val_) {\n      val_ = std::unique_ptr<value_type>(new value_type(*other.val_));\n    }\n  }\n  flex_optional_mem_save(flex_optional_mem_save&& other) noexcept\n      : val_(std::move(other.val_)) {}\n  flex_optional_mem_save(std::nullopt_t) noexcept {}\n\n  template <class... Args>\n  explicit flex_optional_mem_save(std::in_place_t, Args&&... args)\n      : val_(new value_type(std::forward<Args>(args)...)) {}\n\n  template <class U, class... Args>\n  explicit flex_optional_mem_save(std::in_place_t,\n                                  std::initializer_list<U> list, Args&&... args)\n      : val_(new value_type(list, std::forward<Args>(args)...)) {}\n\n  template <class U = std::remove_cv_t<value_type>,\n            class = std::enable_if_t<std::is_constructible_v<value_type, U>>>\n  flex_optional_mem_save(U&& value)\n      : val_(new value_type(std::forward<U>(value))) {}\n\n  template <class U>\n  explicit flex_optional_mem_save(const flex_optional_mem_save<U>& other)\n      : val_(other ? new value_type(*other.val_) : nullptr) {}\n\n  template <class U>\n  explicit flex_optional_mem_save(flex_optional_mem_save<U>&& other)\n      : val_(other ? new value_type(*other.val_) : nullptr) {}\n\n  flex_optional_mem_save& operator=(std::nullopt_t) noexcept {\n    reset();\n    return *this;\n  }\n\n  // same value_type\n  flex_optional_mem_save& operator=(const flex_optional_mem_save& other) {\n    this->val_ = std::unique_ptr<value_type>(new value_type(*other.val_));\n    return *this;\n  }\n\n  flex_optional_mem_save& operator=(flex_optional_mem_save&& other) noexcept {\n    this->val_ = std::move(other.val_);\n    return *this;\n  }\n\n  template <typename>\n  friend struct flex_optional_mem_save;\n  // value_type can be constructed from U\n  template <class U,\n            class = std::enable_if_t<std::is_constructible_v<value_type, U>>>\n  flex_optional_mem_save& operator=(const flex_optional_mem_save<U>& other) {\n    this->val_ = std::unique_ptr<value_type>(new value_type(*other.val_));\n    return *this;\n  }\n\n  template <class U,\n            class = std::enable_if_t<std::is_constructible_v<value_type, U>>>\n  flex_optional_mem_save& operator=(flex_optional_mem_save<U>&& other) {\n    this->val_ =\n        std::unique_ptr<value_type>(new value_type(std::move(*other.val_)));\n    return *this;\n  }\n\n  template <class U = std::remove_cv_t<value_type>,\n            class = std::enable_if_t<std::is_constructible_v<value_type, U>>>\n  flex_optional_mem_save& operator=(U&& value) {\n    val_ = std::unique_ptr<value_type>(new value_type(std::forward<U>(value)));\n    return *this;\n  }\n\n  template <class... Args>\n  value_type& emplace(Args&&... args) {\n    val_ = std::unique_ptr<value_type>(\n        new value_type(std::forward<Args>(args)...));\n    return *val_;\n  }\n\n  template <class U, class... Args>\n  value_type& emplace(std::initializer_list<U> list, Args&&... args) {\n    val_ = std::unique_ptr<value_type>(\n        new value_type(list, std::forward<Args>(args)...));\n    return *val_;\n  }\n\n  void swap(flex_optional_mem_save<value_type>& other) noexcept {\n    std::swap(this->val_, other.val_);\n  }\n\n  const value_type* operator->() const noexcept { return val_.get(); }\n  value_type* operator->() noexcept { return val_.get(); }\n  const value_type& operator*() const& noexcept { return *val_; }\n  value_type& operator*() & noexcept { return *val_; }\n  const value_type&& operator*() const&& noexcept { return *std::move(val_); }\n  value_type&& operator*() && noexcept { return *std::move(val_); }\n\n  bool has_value() const noexcept { return val_ != nullptr; }\n\n  explicit operator bool() const noexcept { return has_value(); }\n\n  value_type& value() & {\n    if (!has_value()) {\n      abort();\n    }\n    return *val_;\n  }\n  const value_type& value() const& {\n    if (!has_value()) {\n      abort();\n    }\n    return *val_;\n  }\n\n  value_type&& value() && {\n    if (!has_value()) {\n      abort();\n    }\n    return std::move(*val_);\n  }\n  const value_type&& value() const&& {\n    if (!has_value()) {\n      abort();\n    }\n    return std::move(*val_);\n  }\n\n  template <class U = std::remove_cv_t<value_type>>\n  value_type value_or(U&& default_value) const& {\n    return has_value()\n               ? *val_\n               : static_cast<value_type>(std::forward<U>(default_value));\n  }\n\n  template <class U = std::remove_cv_t<value_type>>\n  value_type value_or(U&& default_value) && {\n    return has_value()\n               ? std::move(val_)\n               : static_cast<value_type>(std::forward<U>(default_value));\n  }\n\n  void reset() noexcept { val_ = nullptr; }\n\n private:\n  std::unique_ptr<value_type> val_;\n};\n\ntemplate <class T>\nflex_optional_mem_save(T) -> flex_optional_mem_save<T>;\n\n// Comparisons between optionals\ntemplate <class _Tp, class _Up>\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() ==\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator==(const flex_optional_mem_save<_Tp>& __x,\n           const flex_optional_mem_save<_Up>& __y) {\n  if (static_cast<bool>(__x) != static_cast<bool>(__y)) return false;\n  if (!static_cast<bool>(__x)) return true;\n  return *__x == *__y;\n}\n\ntemplate <class _Tp, class _Up>\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() !=\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator!=(const flex_optional_mem_save<_Tp>& __x,\n           const flex_optional_mem_save<_Up>& __y) {\n  if (static_cast<bool>(__x) != static_cast<bool>(__y)) return true;\n  if (!static_cast<bool>(__x)) return false;\n  return *__x != *__y;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() <\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator<(const flex_optional_mem_save<_Tp>& __x,\n          const flex_optional_mem_save<_Up>& __y) {\n  if (!static_cast<bool>(__y)) return false;\n  if (!static_cast<bool>(__x)) return true;\n  return *__x < *__y;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() >\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator>(const flex_optional_mem_save<_Tp>& __x,\n          const flex_optional_mem_save<_Up>& __y) {\n  if (!static_cast<bool>(__x)) return false;\n  if (!static_cast<bool>(__y)) return true;\n  return *__x > *__y;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() <=\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator<=(const flex_optional_mem_save<_Tp>& __x,\n           const flex_optional_mem_save<_Up>& __y) {\n  if (!static_cast<bool>(__x)) return true;\n  if (!static_cast<bool>(__y)) return false;\n  return *__x <= *__y;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() >=\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator>=(const flex_optional_mem_save<_Tp>& __x,\n           const flex_optional_mem_save<_Up>& __y) {\n  if (!static_cast<bool>(__y)) return true;\n  if (!static_cast<bool>(__x)) return false;\n  return *__x >= *__y;\n}\n\n// Comparisons with nullopt\ntemplate <class _Tp>\n\nbool operator==(const flex_optional_mem_save<_Tp>& __x,\n                std::nullopt_t) noexcept {\n  return !static_cast<bool>(__x);\n}\n\ntemplate <class _Tp>\n\nbool operator==(std::nullopt_t,\n                const flex_optional_mem_save<_Tp>& __x) noexcept {\n  return !static_cast<bool>(__x);\n}\n\ntemplate <class _Tp>\n\nbool operator!=(const flex_optional_mem_save<_Tp>& __x,\n                std::nullopt_t) noexcept {\n  return static_cast<bool>(__x);\n}\n\ntemplate <class _Tp>\n\nbool operator!=(std::nullopt_t,\n                const flex_optional_mem_save<_Tp>& __x) noexcept {\n  return static_cast<bool>(__x);\n}\n\ntemplate <class _Tp>\n\nbool operator<(const flex_optional_mem_save<_Tp>&, std::nullopt_t) noexcept {\n  return false;\n}\n\ntemplate <class _Tp>\n\nbool operator<(std::nullopt_t,\n               const flex_optional_mem_save<_Tp>& __x) noexcept {\n  return static_cast<bool>(__x);\n}\n\ntemplate <class _Tp>\n\nbool operator<=(const flex_optional_mem_save<_Tp>& __x,\n                std::nullopt_t) noexcept {\n  return !static_cast<bool>(__x);\n}\n\ntemplate <class _Tp>\n\nbool operator<=(std::nullopt_t, const flex_optional_mem_save<_Tp>&) noexcept {\n  return true;\n}\n\ntemplate <class _Tp>\n\nbool operator>(const flex_optional_mem_save<_Tp>& __x,\n               std::nullopt_t) noexcept {\n  return static_cast<bool>(__x);\n}\n\ntemplate <class _Tp>\n\nbool operator>(std::nullopt_t, const flex_optional_mem_save<_Tp>&) noexcept {\n  return false;\n}\n\ntemplate <class _Tp>\n\nbool operator>=(const flex_optional_mem_save<_Tp>&, std::nullopt_t) noexcept {\n  return true;\n}\n\ntemplate <class _Tp>\n\nbool operator>=(std::nullopt_t,\n                const flex_optional_mem_save<_Tp>& __x) noexcept {\n  return !static_cast<bool>(__x);\n}\n\n// Comparisons with T\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() ==\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator==(const flex_optional_mem_save<_Tp>& __x, const _Up& __v) {\n  return static_cast<bool>(__x) ? *__x == __v : false;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() ==\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator==(const _Tp& __v, const flex_optional_mem_save<_Up>& __x) {\n  return static_cast<bool>(__x) ? __v == *__x : false;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() !=\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator!=(const flex_optional_mem_save<_Tp>& __x, const _Up& __v) {\n  return static_cast<bool>(__x) ? *__x != __v : true;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() !=\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator!=(const _Tp& __v, const flex_optional_mem_save<_Up>& __x) {\n  return static_cast<bool>(__x) ? __v != *__x : true;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() <\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator<(const flex_optional_mem_save<_Tp>& __x, const _Up& __v) {\n  return static_cast<bool>(__x) ? *__x < __v : true;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() <\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator<(const _Tp& __v, const flex_optional_mem_save<_Up>& __x) {\n  return static_cast<bool>(__x) ? __v < *__x : false;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() <=\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator<=(const flex_optional_mem_save<_Tp>& __x, const _Up& __v) {\n  return static_cast<bool>(__x) ? *__x <= __v : true;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() <=\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator<=(const _Tp& __v, const flex_optional_mem_save<_Up>& __x) {\n  return static_cast<bool>(__x) ? __v <= *__x : false;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() >\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator>(const flex_optional_mem_save<_Tp>& __x, const _Up& __v) {\n  return static_cast<bool>(__x) ? *__x > __v : false;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() >\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator>(const _Tp& __v, const flex_optional_mem_save<_Up>& __x) {\n  return static_cast<bool>(__x) ? __v > *__x : true;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() >=\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator>=(const flex_optional_mem_save<_Tp>& __x, const _Up& __v) {\n  return static_cast<bool>(__x) ? *__x >= __v : false;\n}\n\ntemplate <class _Tp, class _Up>\n\nstd::enable_if_t<std::is_convertible_v<decltype(std::declval<const _Tp&>() >=\n                                                std::declval<const _Up&>()),\n                                       bool>,\n                 bool>\noperator>=(const _Tp& __v, const flex_optional_mem_save<_Up>& __x) {\n  return static_cast<bool>(__x) ? __v >= *__x : true;\n}\n\n// Check if T has declared flag `AlwaysUseFlexOptionalMemSave`.\ntemplate <typename T, typename = void>\nstruct has_always_use_flex_optional_mem_save : std::false_type {};\n\ntemplate <typename T>\nstruct has_always_use_flex_optional_mem_save<\n    T, std::void_t<typename T::AlwaysUseFlexOptionalMemSave>> : std::true_type {\n};\n\n// switching between std::optional and flex_optional_mem_save\ntemplate <class T, typename Enable = void>\nstruct flex_optional_type {\n  using Type = flex_optional_mem_save<T>;\n};\n\ntemplate <class T>\nstruct flex_optional_type<\n    T, std::enable_if_t<(sizeof(T) <= 32) &&\n                        !has_always_use_flex_optional_mem_save<T>::value>> {\n  using Type = std::optional<T>;\n};\n\n}  // namespace optional\n\ntemplate <class T>\nusing flex_optional = typename optional::flex_optional_type<T>::Type;\n\ntemplate <class T>\nconstexpr flex_optional<std::decay_t<T>> make_flex_optional(T&& v) {\n  return flex_optional<std::decay_t<T>>(std::forward<T>(v));\n}\n\ntemplate <class T, class... Args>\nconstexpr flex_optional<T> make_flex_optional(Args&&... args) {\n  return flex_optional<T>(std::in_place, std::forward<Args>(args)...);\n}\n\ntemplate <class T, class _Up, class... Args>\nconstexpr flex_optional<T> make_flex_optional(std::initializer_list<_Up> list,\n                                              Args&&... args) {\n  return flex_optional<T>(std::in_place, list, std::forward<Args>(args)...);\n}\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FLEX_OPTIONAL_H_\n"
  },
  {
    "path": "base/include/float_comparison.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FLOAT_COMPARISON_H_\n#define BASE_INCLUDE_FLOAT_COMPARISON_H_\n\n#include <math.h>\n\n#include <cmath>\n\nnamespace lynx {\nnamespace base {\n\n// the error range in floating-point number comparison, with a value of 0.01f.\nconstexpr float EPSILON = 0.01f;\n// a more precise error range in floating-point number comparison, with a value\n// of 0.001f.\nconstexpr float EPSILON_PRECISE = 1e-3;\n// the error range in double-precision floating-point number comparison, with a\n// value of 0.000001.\nconstexpr double EPSILON_DOUBLE = 1e-6;\n\ninline bool DoublesEqual(const double first, const double second) {\n  return fabs(first - second) < EPSILON_DOUBLE;\n}\n\ninline bool IsZero(const double d) { return DoublesEqual(d, 0.00000L); }\n\ninline bool FloatsEqual(const float first, const float second) {\n  return fabs(first - second) < EPSILON;\n}\n\ninline bool FloatsEqualPrecise(const float first, const float second) {\n  return fabs(first - second) < EPSILON_PRECISE;\n}\n\ninline bool FloatsNotEqual(const float first, const float second) {\n  return fabs(first - second) >= EPSILON;\n}\n\ninline bool FloatsLarger(const float first, const float second) {\n  return fabs(first - second) >= EPSILON && first > second;\n}\n\ninline bool FloatsLargerPrecise(const float first, const float second) {\n  return fabs(first - second) >= EPSILON_PRECISE && first > second;\n}\n\ninline bool FloatsLargerOrEqual(const float first, const float second) {\n  return first > second || fabs(first - second) < EPSILON;\n}\n\ninline bool IsZero(const float f) { return FloatsEqual(f, 0.0f); }\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FLOAT_COMPARISON_H_\n"
  },
  {
    "path": "base/include/fml/LICENSE",
    "content": "Copyright 2013 The Flutter Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "base/include/fml/concurrent_message_loop.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_CONCURRENT_MESSAGE_LOOP_H_\n#define BASE_INCLUDE_FML_CONCURRENT_MESSAGE_LOOP_H_\n\n#include <atomic>\n#include <condition_variable>\n#include <cstdint>\n#include <memory>\n#include <queue>\n#include <string>\n#include <vector>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/closure.h\"\n#include \"base/include/fml/thread.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass ConcurrentTaskRunner;\n\nclass BASE_EXPORT ConcurrentMessageLoop\n    : public std::enable_shared_from_this<ConcurrentMessageLoop> {\n public:\n  static std::shared_ptr<ConcurrentMessageLoop> Create(\n      size_t worker_count = std::thread::hardware_concurrency());\n  static std::shared_ptr<ConcurrentMessageLoop> Create(\n      const Thread::ThreadConfigSetter& setter,\n      size_t worker_count = std::thread::hardware_concurrency());\n\n  explicit ConcurrentMessageLoop(\n      const std::string& name_prefix,\n      Thread::ThreadPriority priority = Thread::ThreadPriority::NORMAL,\n      size_t worker_count = std::thread::hardware_concurrency());\n  explicit ConcurrentMessageLoop(\n      const std::string& name_prefix, const Thread::ThreadConfigSetter& setter,\n      Thread::ThreadPriority priority = Thread::ThreadPriority::NORMAL,\n      size_t worker_count = std::thread::hardware_concurrency());\n\n  ~ConcurrentMessageLoop();\n\n  void PostTask(base::closure task);\n\n  // Returns true if the calling thread is one of the worker threads owned by\n  // this concurrent message loop.\n  bool RunsTasksOnCurrentThreadWorker() const;\n\n  size_t GetWorkerCount() const;\n\n  std::shared_ptr<ConcurrentTaskRunner> GetTaskRunner();\n\n  void Terminate();\n\n private:\n  std::mutex notify_mutex_;\n  std::condition_variable notify_condition_;\n  std::vector<std::thread> workers_;\n  std::atomic<std::uint32_t> worker_count_ = 0;\n  std::mutex tasks_mutex_;\n  std::queue<base::closure> tasks_;\n  std::atomic<std::uint32_t> task_count_ = 0;\n  std::atomic_bool shutdown_ = false;\n\n  void WorkerMain(uint32_t index);\n};\n\nclass ConcurrentTaskRunner : public BasicTaskRunner {\n public:\n  explicit ConcurrentTaskRunner(std::weak_ptr<ConcurrentMessageLoop> weak_loop);\n\n  virtual ~ConcurrentTaskRunner();\n\n  void PostTask(lynx::base::closure task) override;\n\n private:\n  friend ConcurrentMessageLoop;\n\n  std::weak_ptr<ConcurrentMessageLoop> weak_loop_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ConcurrentTaskRunner);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::ConcurrentMessageLoop;\nusing lynx::fml::ConcurrentTaskRunner;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_CONCURRENT_MESSAGE_LOOP_H_\n"
  },
  {
    "path": "base/include/fml/cpu_affinity.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_CPU_AFFINITY_H_\n#define BASE_INCLUDE_FML_CPU_AFFINITY_H_\n\n#include <cstdint>\n#include <optional>\n#include <string>\n#include <vector>\n\nnamespace lynx {\nnamespace fml {\n\n/// The CPU Affinity provides a hint to the operating system on which cores a\n/// particular thread should be scheduled on. The operating system may or may\n/// not honor these requests.\nenum class CpuAffinity {\n  /// @brief Request CPU affinity for the performance cores.\n  ///\n  ///        Generally speaking, only the UI and Raster thread should\n  ///        use this option.\n  kPerformance,\n\n  /// @brief Request CPU affinity for the efficiency cores.\n  kEfficiency,\n\n  /// @brief Request affinity for all non-performance cores.\n  kNotPerformance,\n\n  /// @brief Request affinity for all non-efficiency cores.\n  kNotEfficiency,\n};\n\n/// @brief Request count of efficiency cores.\n///\n///        Efficiency cores are defined as those with the lowest reported\n///        cpu_max_freq. If the CPU speed could not be determined, or if all\n///        cores have the same reported speed then this returns std::nullopt.\n///        That is, the result will never be 0.\nstd::optional<size_t> EfficiencyCoreCount();\n\n/// @brief Request the given affinity for the current thread.\n///\n///        Returns true if successfull, or if it was a no-op. This function is\n///        only supported on Android devices.\n///\n///        Affinity requests are based on documented CPU speed. This speed data\n///        is parsed from cpuinfo_max_freq files, see also:\n///        https://www.kernel.org/doc/Documentation/cpu-freq/user-guide.txt\nbool RequestAffinity(CpuAffinity affinity);\n\nstruct CpuIndexAndSpeed {\n  // The index of the given CPU.\n  size_t index;\n  // CPU speed in kHZ\n  int64_t speed;\n};\n\n/// @brief A class that computes the correct CPU indices for a requested CPU\n///        affinity.\n///\n/// @note  This is visible for testing.\nclass CPUSpeedTracker {\n public:\n  explicit CPUSpeedTracker(std::vector<CpuIndexAndSpeed> data);\n\n  /// @brief The class is valid if it has more than one CPU index and a distinct\n  ///        set of efficiency or performance CPUs.\n  ///\n  ///        If all CPUs are the same speed this returns false, and all requests\n  ///        to set affinity are ignored.\n  bool IsValid() const;\n\n  /// @brief Return the set of CPU indices for the requested CPU affinity.\n  ///\n  ///        If the tracker is valid, this will always return a non-empty set.\n  const std::vector<size_t>& GetIndices(CpuAffinity affinity) const;\n\n private:\n  bool valid_ = false;\n  std::vector<CpuIndexAndSpeed> cpu_speeds_;\n  std::vector<size_t> efficiency_;\n  std::vector<size_t> performance_;\n  std::vector<size_t> not_performance_;\n  std::vector<size_t> not_efficiency_;\n};\n\n/// @note Visible for testing.\nstd::optional<int64_t> ReadIntFromFile(const std::string& path);\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_CPU_AFFINITY_H_\n"
  },
  {
    "path": "base/include/fml/delayed_task.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_DELAYED_TASK_H_\n#define BASE_INCLUDE_FML_DELAYED_TASK_H_\n\n#include <functional>\n#include <queue>\n#include <utility>\n\n#include \"base/include/base_defines.h\"\n#include \"base/include/closure.h\"\n#include \"base/include/fml/task_source_grade.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass DelayedTask {\n public:\n  DelayedTask(size_t order, base::closure task, fml::TimePoint target_time,\n              fml::TaskSourceGrade task_source_grade)\n      : order_(order),\n        task_source_grade_(task_source_grade),\n        target_time_(target_time),\n        task_(std::move(task)) {}\n\n  ~DelayedTask() = default;\n\n  DelayedTask(const DelayedTask&) = delete;\n  DelayedTask& operator=(const DelayedTask&) = delete;\n  BASE_INLINE DelayedTask(DelayedTask&&) = default;\n  BASE_INLINE DelayedTask& operator=(DelayedTask&&) = default;\n\n  // after invoke this func, task_ will become nullptr!\n  base::closure GetTask() const { return std::move(task_); }\n\n  fml::TimePoint GetTargetTime() const { return target_time_; }\n\n  fml::TaskSourceGrade GetTaskSourceGrade() const { return task_source_grade_; }\n\n  // Let std::priority_queue algorithms to always inline compare function.\n  BASE_INLINE bool operator>(const DelayedTask& other) const {\n    if (target_time_ == other.target_time_) {\n      return order_ > other.order_;\n    }\n    return target_time_ > other.target_time_;\n  }\n\n  // A flag telling base containers such as `base::Vector<DelayedTask>` to\n  // optimize for reallocate, insert and erase.\n  using TriviallyRelocatable = bool;\n\n private:\n  size_t order_;\n  fml::TaskSourceGrade task_source_grade_;\n  fml::TimePoint target_time_;\n  mutable base::closure task_;\n};\n\nclass DelayedTaskQueue\n    : public std::priority_queue<DelayedTask, base::Vector<DelayedTask>,\n                                 std::greater<DelayedTask>> {\n public:\n  using std::priority_queue<DelayedTask, base::Vector<DelayedTask>,\n                            std::greater<DelayedTask>>::priority_queue;\n  void reserve(size_t capacity) { this->c.reserve(capacity); }\n  void clear() { this->c.clear(); }\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_DELAYED_TASK_H_\n"
  },
  {
    "path": "base/include/fml/eintr_wrapper.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_EINTR_WRAPPER_H_\n#define BASE_INCLUDE_FML_EINTR_WRAPPER_H_\n\n#include <errno.h>\n\n#include \"build/build_config.h\"\n\n#if defined(OS_WIN)\n\n// Windows has no concept of EINTR.\n#define FML_HANDLE_EINTR(x) (x)\n#define FML_IGNORE_EINTR(x) (x)\n\n#else\n\n#define FML_HANDLE_EINTR(x)                                 \\\n  ({                                                        \\\n    decltype(x) eintr_wrapper_result;                       \\\n    do {                                                    \\\n      eintr_wrapper_result = (x);                           \\\n    } while (eintr_wrapper_result == -1 && errno == EINTR); \\\n    eintr_wrapper_result;                                   \\\n  })\n\n#define FML_IGNORE_EINTR(x)                               \\\n  ({                                                      \\\n    decltype(x) eintr_wrapper_result;                     \\\n    do {                                                  \\\n      eintr_wrapper_result = (x);                         \\\n      if (eintr_wrapper_result == -1 && errno == EINTR) { \\\n        eintr_wrapper_result = 0;                         \\\n      }                                                   \\\n    } while (0);                                          \\\n    eintr_wrapper_result;                                 \\\n  })\n\n#endif  // defined(OS_WIN)\n\n#endif  // BASE_INCLUDE_FML_EINTR_WRAPPER_H_\n"
  },
  {
    "path": "base/include/fml/fml_trace_event_def.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_FML_TRACE_EVENT_DEF_H_\n#define BASE_INCLUDE_FML_FML_TRACE_EVENT_DEF_H_\n\nstatic constexpr const char* const CONCURRENT_WORKER_AWOKE =\n    \"ConcurrentWorker AWoke\";\nstatic constexpr const char* const MESSAGE_LOOP_FLUSH_TASK =\n    \"MessageLoop::FlushTasks\";\nstatic constexpr const char* const MESSAGE_LOOP_FLUSH_VASYNC_ALIGNED_TASKS =\n    \"MessageLoop::FlushVSyncAlignedTasks\";\nstatic constexpr const char* const MESSAGE_LOOP_IMPL_BIND =\n    \"MessageLoopImpl::Bind\";\n\n#endif  // BASE_INCLUDE_FML_FML_TRACE_EVENT_DEF_H_\n"
  },
  {
    "path": "base/include/fml/hash_combine.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_FML_HASH_COMBINE_H_\n#define BASE_INCLUDE_FML_HASH_COMBINE_H_\n\n#include <functional>\n\nnamespace lynx {\nnamespace fml {\n\ntemplate <class Type>\n// NOLINTNEXTLINE\nconstexpr void HashCombineSeed(std::size_t& seed, Type arg) {\n  seed ^= std::hash<Type>{}(arg) + 0x9e3779b9 + (seed << 6) + (seed >> 2);\n}\n\ntemplate <class Type, class... Rest>\n// NOLINTNEXTLINE\nconstexpr void HashCombineSeed(std::size_t& seed, Type arg,\n                               Rest... other_args) {\n  HashCombineSeed(seed, arg);\n  HashCombineSeed(seed, other_args...);\n}\n\nconstexpr std::size_t HashCombine() { return 0xdabbad00; }\n\ntemplate <class... Type>\n[[nodiscard]] constexpr std::size_t HashCombine(Type... args) {\n  std::size_t seed = HashCombine();\n  HashCombineSeed(seed, args...);\n  return seed;\n}\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::HashCombine;\nusing lynx::fml::HashCombineSeed;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_HASH_COMBINE_H_\n"
  },
  {
    "path": "base/include/fml/macros.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_MACROS_H_\n#define BASE_INCLUDE_FML_MACROS_H_\n\n#include <stdlib.h>\n\n#define BASE_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete\n\n#define BASE_DISALLOW_ASSIGN(TypeName) \\\n  TypeName& operator=(const TypeName&) = delete\n\n#define BASE_DISALLOW_MOVE(TypeName) \\\n  TypeName(TypeName&&) = delete;     \\\n  TypeName& operator=(TypeName&&) = delete\n\n#define BASE_DISALLOW_COPY_AND_ASSIGN(TypeName) \\\n  TypeName(const TypeName&) = delete;           \\\n  TypeName& operator=(const TypeName&) = delete\n\n#define BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \\\n  TypeName(const TypeName&) = delete;                \\\n  TypeName(TypeName&&) = delete;                     \\\n  TypeName& operator=(const TypeName&) = delete;     \\\n  TypeName& operator=(TypeName&&) = delete\n\n#define BASE_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \\\n  TypeName() = delete;                                \\\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName)\n\n#ifndef FML_USED_ON_EMBEDDER\n\n#define FML_EMBEDDER_ONLY [[deprecated]]\n\n#else  // FML_USED_ON_EMBEDDER\n\n#define FML_EMBEDDER_ONLY\n\n#endif  // FML_USED_ON_EMBEDDER\n\n// TODO(zhengsenyao): Support stream operator << for LYNX_(D)BASE_CHECK\n#define LYNX_BASE_CHECK(condition) (condition) ? (void)0 : abort();\n\n#ifndef NDEBUG\n#define LYNX_BASE_DCHECK(condition) LYNX_BASE_CHECK(condition)\n#else\n#define LYNX_BASE_DCHECK(condition) (void)0\n#endif\n\n#endif  // BASE_INCLUDE_FML_MACROS_H_\n"
  },
  {
    "path": "base/include/fml/make_copyable.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_MAKE_COPYABLE_H_\n#define BASE_INCLUDE_FML_MAKE_COPYABLE_H_\n\n#include <utility>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace internal {\n\ntemplate <typename T>\nclass CopyableLambda {\n public:\n  // TODO(chenyouhui): Merge ref_counted. It will conflict with each other!\n  explicit CopyableLambda(T func)\n      : impl_(lynx::fml::MakeRefCounted<Impl>(std::move(func))) {}\n\n  template <typename... ArgType>\n  auto operator()(ArgType&&... args) const {\n    return impl_->func_(std::forward<ArgType>(args)...);\n  }\n\n private:\n  class Impl : public RefCountedThreadSafe<Impl> {\n   public:\n    explicit Impl(T func) : func_(std::move(func)) {}\n    T func_;\n  };\n\n  RefPtr<Impl> impl_;\n};\n\n}  // namespace internal\n\n// Provides a wrapper for a move-only lambda that is implictly convertable to an\n// std::function.\n//\n// std::function is copyable, but if a lambda captures an argument with a\n// move-only type, the lambda itself is not copyable. In order to use the lambda\n// in places that accept std::functions, we provide a copyable object that wraps\n// the lambda and is implicitly convertable to an std::function.\n//\n// EXAMPLE:\n//\n// std::unique_ptr<Foo> foo = ...\n// std::function<int()> func =\n//     fml::MakeCopyable([bar = std::move(foo)]() { return bar->count(); });\n//\n// Notice that the return type of MakeCopyable is rarely used directly. Instead,\n// callers typically erase the type by implicitly converting the return value\n// to an std::function.\ntemplate <typename T>\ninternal::CopyableLambda<T> MakeCopyable(T lambda) {\n  return internal::CopyableLambda<T>(std::move(lambda));\n}\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::MakeCopyable;\n}\n\n#endif  // BASE_INCLUDE_FML_MAKE_COPYABLE_H_\n"
  },
  {
    "path": "base/include/fml/memory/ref_counted.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// Provides a base class for reference-counted classes.\n\n#ifndef BASE_INCLUDE_FML_MEMORY_REF_COUNTED_H_\n#define BASE_INCLUDE_FML_MEMORY_REF_COUNTED_H_\n\n#include <type_traits>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted_internal.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// A base class for (thread-safe) reference-counted classes. Use like:\n//\n//   class Foo : public RefCountedThreadSafe<Foo> {\n//     ...\n//   };\n//\n// |~Foo()| *may* be made private (e.g., to avoid accidental deletion of objects\n// while there are still references to them), |Foo| should friend\n// |RefCountedThreadSafe<Foo>|; use |FML_FRIEND_REF_COUNTED_THREAD_SAFE()|\n// for this:\n//\n//   class Foo : public RefCountedThreadSafe<Foo> {\n//     ...\n//    private:\n//     FML_FRIEND_REF_COUNTED_THREAD_SAFE(Foo);\n//     ~Foo();\n//     ...\n//   };\n//\n// Similarly, |Foo(...)| may be made private. In this case, there should either\n// be a static factory method performing the requisite adoption:\n//\n//   class Foo : public RefCountedThreadSafe<Foo> {\n//     ...\n//    public:\n//     inline static RefPtr<Foo> Create() { return AdoptRef(new Foo()); }\n//     ...\n//    private:\n//     Foo();\n//     ...\n//   };\n//\n// Or, to allow |MakeRefCounted()| to be used, use\n// |FML_FRIEND_MAKE_REF_COUNTED()|:\n//\n//   class Foo : public RefCountedThreadSafe<Foo> {\n//     ...\n//    private:\n//     FML_FRIEND_MAKE_REF_COUNTED(Foo);\n//     Foo();\n//     Foo(const Bar& bar, bool maybe);\n//     ...\n//   };\n//\n// For now, we only have thread-safe reference counting, since that's all we\n// need. It's easy enough to add thread-unsafe versions if necessary.\ntemplate <typename T>\nclass RefCountedThreadSafe : public internal::RefCountedThreadSafeBase {\n public:\n  // Adds a reference to this object.\n  // Inherited from the internal superclass:\n  //   void AddRef() const;\n\n  // Releases a reference to this object. This will destroy this object once the\n  // last reference is released.\n  void Release() const {\n    if (internal::RefCountedThreadSafeBase::Release()) {\n      delete static_cast<const T*>(this);\n    }\n  }\n\n  // Returns true if there is exactly one reference to this object. Use of this\n  // is subtle, and should usually be avoided. To assert that there is only one\n  // reference (typically held by the calling thread, possibly in a local\n  // variable), use |AssertHasOneRef()| instead. However, a use is:\n  //\n  //   if (foo->HasOneRef()) {\n  //     // Do something \"fast\", since |foo| is the only reference and no other\n  //     // thread can get another reference.\n  //     ...\n  //   } else {\n  //     // Do something slower, but still valid even if there were only one\n  //     // reference.\n  //     ...\n  //   }\n  //\n  // Inherited from the internal superclass:\n  //   bool HasOneRef();\n\n  // Asserts that there is exactly one reference to this object; does nothing in\n  // Release builds (when |NDEBUG| is defined).\n  // Inherited from the internal superclass:\n  //   void AssertHasOneRef();\n\n protected:\n  // Constructor. Note that the object is constructed with a reference count of\n  // 1, and then must be adopted (see |AdoptRef()| in ref_ptr.h).\n  RefCountedThreadSafe() {}\n\n  // Destructor. Note that this object should only be destroyed via |Release()|\n  // (see above), or something that calls |Release()| (see, e.g., |RefPtr<>| in\n  // ref_ptr.h).\n  ~RefCountedThreadSafe() {}\n\n private:\n#ifndef NDEBUG\n  template <typename U>\n  friend RefPtr<U> AdoptRef(U*);\n  // Marks the initial reference (assumed on construction) as adopted. This is\n  // only required for Debug builds (when |NDEBUG| is not defined).\n  // TODO(vtl): Should this really be private? This makes manual ref-counting\n  // and also writing one's own ref pointer class impossible.\n  void Adopt() { internal::RefCountedThreadSafeBase::Adopt(); }\n#endif\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe);\n};\n\n// If you subclass |RefCountedThreadSafe| and want to keep your destructor\n// private, use this. (See the example above |RefCountedThreadSafe|.)\n#define FML_FRIEND_REF_COUNTED_THREAD_SAFE(T) \\\n  friend class lynx::fml::RefCountedThreadSafe<T>\n\n// If you want to keep your constructor(s) private and still want to use\n// |MakeRefCounted<T>()|, use this. (See the example above\n// |RefCountedThreadSafe|.)\n#define FML_FRIEND_MAKE_REF_COUNTED(T) \\\n  friend class lynx::fml::internal::MakeRefCountedHelper<T>\n\n// RefCountedThreadSafe<T> supports compile time polymorphism destroy by delete\n// static_cast<const T*>(this). But not support runtime polymorphism destroy\n// since RefCountedThreadSafe<T> is not abstruct and destructor is not virtual.\n// Use RefCountedThreadSafeStorage if need delete subclass by ref to superclass\n// with virtual ReleaseSelf.\nclass RefCountedThreadSafeStorage\n    : public fml::RefCountedThreadSafe<RefCountedThreadSafeStorage> {\n public:\n  void Release() const {\n    if (fml::internal::RefCountedThreadSafeBase::Release()) {\n      ReleaseSelf();\n    }\n  }\n\n protected:\n  virtual void ReleaseSelf() const = 0;\n  virtual ~RefCountedThreadSafeStorage() = default;\n};\n\n// static_cast from base to derived\ntemplate <typename D, typename B = RefCountedThreadSafeStorage,\n          typename = std::enable_if_t<std::is_base_of<B, D>::value, D>>\nRefPtr<D> static_ref_ptr_cast(const RefPtr<B>& rhs) {\n  return Ref<D>(static_cast<D*>(rhs.get()));\n}\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::RefCountedThreadSafe;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_MEMORY_REF_COUNTED_H_\n"
  },
  {
    "path": "base/include/fml/memory/ref_counted_internal.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// Internal implementation details for ref_counted.h.\n\n#ifndef BASE_INCLUDE_FML_MEMORY_REF_COUNTED_INTERNAL_H_\n#define BASE_INCLUDE_FML_MEMORY_REF_COUNTED_INTERNAL_H_\n\n#include <atomic>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace internal {\n\n// See ref_counted.h for comments on the public methods.\nclass RefCountedThreadSafeBase {\n public:\n  void AddRef() const {\n#ifndef NDEBUG\n    // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n    // DCHECK(!adoption_required_);\n    // DCHECK(!destruction_started_);\n#endif\n    ref_count_.fetch_add(1u, std::memory_order_relaxed);\n  }\n\n  bool HasOneRef() const {\n    return ref_count_.load(std::memory_order_acquire) == 1u;\n  }\n\n  void AssertHasOneRef() const { HasOneRef(); }\n\n  // Returns the current reference count (with no barriers). This is subtle, and\n  // should be used only for debugging.\n  int SubtleRefCountForDebug() const {\n    return ref_count_.load(std::memory_order_relaxed);\n  }\n\n protected:\n  RefCountedThreadSafeBase();\n  ~RefCountedThreadSafeBase();\n\n  // Returns true if the object should self-delete.\n  bool Release() const {\n#ifndef NDEBUG\n    // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n    // DCHECK(!adoption_required_);\n    // DCHECK(!destruction_started_);\n#endif\n    // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n    // DCHECK(ref_count_.load(std::memory_order_acquire) != 0u);\n    // TODO(vtl): We could add the following:\n    //     if (ref_count_.load(std::memory_order_relaxed) == 1u) {\n    // #ifndef NDEBUG\n    //       destruction_started_= true;\n    // #endif\n    //       return true;\n    //     }\n    // This would be correct. On ARM (an Nexus 4), in *single-threaded* tests,\n    // this seems to make the destruction case marginally faster (barely\n    // measurable), and while the non-destruction case remains about the same\n    // (possibly marginally slower, but my measurements aren't good enough to\n    // have any confidence in that). I should try multithreaded/multicore tests.\n    if (ref_count_.fetch_sub(1u, std::memory_order_release) == 1u) {\n      std::atomic_thread_fence(std::memory_order_acquire);\n#ifndef NDEBUG\n      destruction_started_ = true;\n#endif\n      return true;\n    }\n    return false;\n  }\n\n#ifndef NDEBUG\n  void Adopt() {\n    // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n    // DCHECK(adoption_required_);\n    adoption_required_ = false;\n  }\n#endif\n\n protected:\n  union {\n    struct {\n      mutable std::atomic_uint32_t ref_count_;\n\n      // Because subclass of RefCountedThreadSafeBase contains vtable, on 64-bit\n      // platforms, 4 bytes of free space are left here.\n      union {\n        [[maybe_unused]] uint8_t __padding_chars__[4];\n        [[maybe_unused]] uint16_t __padding_shorts__[2];\n        [[maybe_unused]] uint32_t __padding__;\n      };\n    };\n\n    // Use `__init__` to fast initialize `ref_count_` to 1 and `__padding__` to\n    // 0. Testing results with Clang on Arm64:\n    //\n    //    1. Initialize by `ref_count_(1), __padding__(0)`.\n    //      0x100003e0c <+32>: adrp   x8, 0\n    //      0x100003e10 <+36>: ldr    d0, [x8, #0xf00]\n    //      0x100003e14 <+40>: str    d0, [x0, #0x8]\n    //      0x100003e18 <+44>: adrp   x8, 1\n    //      0x100003e1c <+48>: add    x8, x8, #0x38   ; vtable\n    //      0x100003e20 <+52>: str    x8, [x0]\n    //    Loads combined value of `ref_count_` and `__padding__` from DATA\n    //    section as 64bit to register d0 and then stores to x0[0x8]\n    //\n    //    2. Initialize with single `__init__(1)`.\n    //      0x100003e20 <+32>: adrp   x8, 1\n    //      0x100003e24 <+36>: add    x8, x8, #0x38   ; vtable\n    //      0x100003e28 <+40>: mov    w9, #0x1        ; =1\n    //      0x100003e2c <+44>: stp    x8, x9, [x0]\n    //    No loading of combined value from memory and stores to x0 together\n    //    with vtable using single stp instruction.\n    uint64_t __init__;\n  };\n\n#ifndef NDEBUG\n  mutable bool adoption_required_;\n  mutable bool destruction_started_;\n#endif\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase);\n};\n\ninline RefCountedThreadSafeBase::RefCountedThreadSafeBase()\n    : __init__(1u)\n#ifndef NDEBUG\n      ,\n      adoption_required_(true),\n      destruction_started_(false)\n#endif\n{\n}\n\ninline RefCountedThreadSafeBase::~RefCountedThreadSafeBase() {\n#ifndef NDEBUG\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(!adoption_required_);\n  // Should only be destroyed as a result of |Release()|.\n  // DCHECK(destruction_started_);\n#endif\n}\n\n}  // namespace internal\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_MEMORY_REF_COUNTED_INTERNAL_H_\n"
  },
  {
    "path": "base/include/fml/memory/ref_ptr.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// Provides a smart pointer class for intrusively reference-counted objects.\n\n#ifndef BASE_INCLUDE_FML_MEMORY_REF_PTR_H_\n#define BASE_INCLUDE_FML_MEMORY_REF_PTR_H_\n\n#include <cstddef>\n#include <functional>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_ptr_internal.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// A smart pointer class for intrusively reference-counted objects (e.g., those\n// subclassing |RefCountedThreadSafe| -- see ref_counted.h).\n//\n// Such objects require *adoption* to obtain the first |RefPtr|, which is\n// accomplished using |AdoptRef| (see below). (This is due to such objects being\n// constructed with a reference count of 1. The adoption requirement is\n// enforced, at least in Debug builds, by assertions.)\n//\n// E.g., if |Foo| is an intrusively reference-counted class:\n//\n//   // The |AdoptRef| may be put in a static factory method (e.g., if |Foo|'s\n//   // constructor is private).\n//   RefPtr<Foo> my_foo_ptr(AdoptRef(new Foo()));\n//\n//   // Now OK, since \"my Foo\" has been adopted ...\n//   RefPtr<Foo> another_ptr_to_my_foo(my_foo_ptr.get());\n//\n//   // ... though this would preferable in this situation.\n//   RefPtr<Foo> yet_another_ptr_to_my_foo(my_foo_ptr);\n//\n// Unlike Chromium's |scoped_refptr|, |RefPtr| is only explicitly constructible\n// from a plain pointer (and not assignable). It is however implicitly\n// constructible from |nullptr|. So:\n//\n//   RefPtr<Foo> foo(plain_ptr_to_adopted_foo);    // OK.\n//   foo = plain_ptr_to_adopted_foo;               // Not OK (doesn't compile).\n//   foo = RefPtr<Foo>(plain_ptr_to_adopted_foo);  // OK.\n//   foo = nullptr;                                // OK.\n//\n// And if we have |void MyFunction(RefPtr<Foo> foo)|, calling it using\n// |MyFunction(nullptr)| is also valid.\n//\n// Implementation note: For copy/move constructors/operator=s, we often have\n// templated versions, so that the operation can be done on a |RefPtr<U>|, where\n// |U| is a subclass of |T|. However, we also have non-templated versions with\n// |U = T|, since the templated versions don't count as copy/move\n// constructors/operator=s for the purposes of causing the default copy\n// constructor/operator= to be deleted. E.g., if we didn't declare any\n// non-templated versions, we'd get the default copy constructor/operator= (we'd\n// only not get the default move constructor/operator= by virtue of having a\n// destructor)! (In fact, it'd suffice to only declare a non-templated move\n// constructor or move operator=, which would cause the copy\n// constructor/operator= to be deleted, but for clarity we include explicit\n// non-templated versions of everything.)\ntemplate <typename T>\nclass RefPtr final {\n public:\n  RefPtr() : ptr_(nullptr) {}\n  RefPtr(std::nullptr_t)  // NOLINT(google-explicit-constructor)\n      : ptr_(nullptr) {}\n\n  // Explicit constructor from a plain pointer (to an object that must have\n  // already been adopted). (Note that in |T::T()|, references to |this| cannot\n  // be taken, since the object being constructed will not have been adopted\n  // yet.)\n  template <typename U>\n  explicit RefPtr(U* p) : ptr_(p) {\n    if (ptr_) {\n      ptr_->AddRef();\n    }\n  }\n\n  // Copy constructor.\n  RefPtr(const RefPtr<T>& r)  // NOLINT(google-explicit-constructor)\n      : ptr_(r.ptr_) {\n    if (ptr_) {\n      ptr_->AddRef();\n    }\n  }\n\n  // Copy conversion constructor.\n  template <typename U, typename = typename std::enable_if<\n                            std::is_convertible<U*, T*>::value>::type>\n  RefPtr(const RefPtr<U>& r)  // NOLINT(google-explicit-constructor)\n      : ptr_(r.ptr_) {\n    if (ptr_) {\n      ptr_->AddRef();\n    }\n  }\n\n  // Move constructor. This is required in addition to the conversion\n  // constructor below in order for clang to warn about pessimizing moves.\n  RefPtr(RefPtr&& r) : ptr_(r.ptr_) { r.ptr_ = nullptr; }\n\n  // Move conversion constructor.\n  template <typename U, typename = typename std::enable_if<\n                            std::is_convertible<U*, T*>::value>::type>\n  RefPtr(RefPtr<U>&& r) : ptr_(r.ptr_) {  // NOLINT(google-explicit-constructor)\n    r.ptr_ = nullptr;\n  }\n\n  // Destructor.\n  ~RefPtr() {\n    if (ptr_) {\n      ptr_->Release();\n    }\n  }\n\n  T* get() const { return ptr_; }\n\n  T& operator*() const {\n    // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n    // DCHECK(ptr_);\n    return *ptr_;\n  }\n\n  T* operator->() const {\n    // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n    // DCHECK(ptr_);\n    return ptr_;\n  }\n\n  // Copy assignment.\n  RefPtr<T>& operator=(const RefPtr<T>& r) {\n    if (r.ptr_) {\n      r.ptr_->AddRef();\n    }\n    T* old_ptr = ptr_;\n    ptr_ = r.ptr_;\n    if (old_ptr) {\n      old_ptr->Release();\n    }\n    return *this;\n  }\n\n  template <typename U>\n  RefPtr<T>& operator=(const RefPtr<U>& r) {\n    if (reinterpret_cast<T*>(r.ptr_) == ptr_) {\n      return *this;\n    }\n    if (r.ptr_) {\n      r.ptr_->AddRef();\n    }\n    T* old_ptr = ptr_;\n    ptr_ = r.ptr_;\n    if (old_ptr) {\n      old_ptr->Release();\n    }\n    return *this;\n  }\n\n  // Move assignment.\n  // Note: Like |std::shared_ptr|, we support self-move and move assignment is\n  // equivalent to |RefPtr<T>(std::move(r)).swap(*this)|.\n  RefPtr<T>& operator=(RefPtr<T>&& r) {\n    RefPtr<T>(std::move(r)).swap(*this);\n    return *this;\n  }\n\n  template <typename U>\n  RefPtr<T>& operator=(RefPtr<U>&& r) {\n    RefPtr<T>(std::move(r)).swap(*this);\n    return *this;\n  }\n\n  void swap(RefPtr<T>& r) {\n    T* p = ptr_;\n    ptr_ = r.ptr_;\n    r.ptr_ = p;\n  }\n\n  // Returns a new |RefPtr<T>| with the same contents as this pointer. Useful\n  // when a function takes a |RefPtr<T>&&| argument and the caller wants to\n  // retain its reference (rather than moving it).\n  RefPtr<T> Clone() const { return *this; }\n\n  explicit operator bool() const { return !!ptr_; }\n\n  template <typename U>\n  bool operator==(const RefPtr<U>& rhs) const {\n    return ptr_ == rhs.ptr_;\n  }\n\n  template <typename U>\n  bool operator!=(const RefPtr<U>& rhs) const {\n    return !operator==(rhs);\n  }\n\n  template <typename U>\n  bool operator<(const RefPtr<U>& rhs) const {\n    return ptr_ < rhs.ptr_;\n  }\n\n  // Release the ownership of internal T without ref-count decrement.\n  // The returned raw T* should be manually ref-counting managed.\n  // This function is opposite to AdoptRef and both of them do not\n  // change the reference count variable.\n  T* AbandonRef() {\n    auto* result = ptr_;\n    ptr_ = nullptr;\n    return result;\n  }\n\n private:\n  template <typename U>\n  friend class RefPtr;\n\n  friend RefPtr<T> AdoptRef<T>(T*);\n\n  enum AdoptTag { ADOPT };\n  RefPtr(T* ptr, AdoptTag) : ptr_(ptr) {\n    // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n    // DCHECK(ptr_);\n  }\n\n  T* ptr_;\n};\n\n// Adopts a newly-created |T|. Typically used in a static factory method, like:\n//\n//   // static\n//   RefPtr<Foo> Foo::Create() {\n//     return AdoptRef(new Foo());\n//   }\ntemplate <typename T>\ninline RefPtr<T> AdoptRef(T* ptr) {\n#ifndef NDEBUG\n  ptr->Adopt();\n#endif\n  return RefPtr<T>(ptr, RefPtr<T>::ADOPT);\n}\n\n// Constructs a |RefPtr<T>| from a plain pointer (to an object that must\n// have already been adoped).  Avoids having to spell out the full type name.\n//\n//   Foo* foo = ...;\n//   auto foo_ref = Ref(foo);\n//\n// (|foo_ref| will be of type |RefPtr<Foo>|.)\ntemplate <typename T>\ninline RefPtr<T> Ref(T* ptr) {\n  return RefPtr<T>(ptr);\n}\n\n// Creates an intrusively reference counted |T|, producing a |RefPtr<T>| (and\n// performing the required adoption). Use like:\n//\n//   auto my_foo = MakeRefCounted<Foo>(ctor_arg1, ctor_arg2);\n//\n// (|my_foo| will be of type |RefPtr<Foo>|.)\ntemplate <typename T, typename... Args>\nRefPtr<T> MakeRefCounted(Args&&... args) {\n  return internal::MakeRefCountedHelper<T>::MakeRefCounted(\n      std::forward<Args>(args)...);\n}\n\n// Convienence function to compare a |RefPtr<T>| with std::nullptr\n//\n//   (e.g., |if (foo == nullptr)|).\n//   |foo|'s type is |RefPtr<Foo>|.\ntemplate <typename T>\nbool operator==(const RefPtr<T>& lhs, std::nullptr_t null) {\n  return !static_cast<bool>(lhs);\n}\n\ntemplate <typename T>\nbool operator==(std::nullptr_t null, const RefPtr<T>& rhs) {\n  return !static_cast<bool>(rhs);\n}\n\ntemplate <typename T>\nbool operator!=(const RefPtr<T>& lhs, std::nullptr_t null) {\n  return static_cast<bool>(lhs);\n}\n\ntemplate <typename T>\nbool operator!=(std::nullptr_t null, const RefPtr<T>& rhs) {\n  return static_cast<bool>(rhs);\n}\n\n// WeakRefPtr does not strongly reference the internal RefCounted object. It is\n// mainly used in synchronous call scenarios to avoid modifying the reference\n// count and improve efficiency. It can be implicitly converted to the\n// corresponding RefPtr strong reference pointer. Because WeakRefPtr itself\n// cannot be copied, when it needs to be passed to a closure asynchronously,\n// please call strongify() to convert it to a RefPtr strong reference pointer.\ntemplate <typename T>\nstruct WeakRefPtr {\n public:\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(WeakRefPtr);\n\n  WeakRefPtr(std::nullptr_t) : value_(nullptr) {}\n  explicit WeakRefPtr(T* value) : value_(value) {}\n\n  template <typename U, typename = typename std::enable_if<\n                            std::is_convertible<U*, T*>::value>::type>\n  WeakRefPtr(const WeakRefPtr<U>& r)  // NOLINT(google-explicit-constructor)\n      : value_(static_cast<T*>(r.value_)) {}\n\n  WeakRefPtr& operator=(std::nullptr_t) {\n    value_ = nullptr;\n    return *this;\n  }\n\n  T& operator*() const { return *value_; }\n\n  T* operator->() const { return value_; }\n\n  T* get() const { return value_; }\n\n  void reset(T* v) { value_ = v; }\n\n  RefPtr<T> strongify() const { return RefPtr<T>(value_); }\n\n  explicit operator bool() const { return !!value_; }\n\n  operator RefPtr<T>() const { return fml::RefPtr<T>(value_); }\n\n  template <typename U, typename = typename std::enable_if<\n                            std::is_convertible<T*, U*>::value>::type>\n  operator RefPtr<U>() const {\n    return fml::RefPtr<U>(static_cast<U*>(value_));\n  }\n\n  operator T&() const { return *value_; }\n\n  template <typename U>\n  bool operator==(const WeakRefPtr<U>& rhs) const {\n    return value_ == rhs.value_;\n  }\n\n  template <typename U>\n  bool operator!=(const WeakRefPtr<U>& rhs) const {\n    return value_ != rhs.value_;\n  }\n\n  bool operator==(std::nullptr_t) const { return value_ == nullptr; }\n  bool operator!=(std::nullptr_t) const { return value_ != nullptr; }\n\n private:\n  T* value_;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n// Inject custom std::hash<> function object for |RefPtr<T>|.\nnamespace std {\ntemplate <typename T>\nstruct hash<lynx::fml::RefPtr<T>> {\n  using argument_type = lynx::fml::RefPtr<T>;\n  using result_type = std::size_t;\n\n  result_type operator()(const argument_type& ptr) const {\n    return std::hash<T*>()(ptr.get());\n  }\n};\n}  // namespace std\n\nnamespace fml {\nusing lynx::fml::AdoptRef;\nusing lynx::fml::MakeRefCounted;\nusing lynx::fml::Ref;\nusing lynx::fml::RefPtr;\nusing lynx::fml::WeakRefPtr;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_MEMORY_REF_PTR_H_\n"
  },
  {
    "path": "base/include/fml/memory/ref_ptr_internal.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_MEMORY_REF_PTR_INTERNAL_H_\n#define BASE_INCLUDE_FML_MEMORY_REF_PTR_INTERNAL_H_\n\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace fml {\n\ntemplate <typename T>\nclass RefPtr;\n\ntemplate <typename T>\nRefPtr<T> AdoptRef(T* ptr);\n\nnamespace internal {\n\n// This is a wrapper class that can be friended for a particular |T|, if you\n// want to make |T|'s constructor private, but still use |MakeRefCounted()|\n// (below). (You can't friend partial specializations.) See |MakeRefCounted()|\n// and |FML_FRIEND_MAKE_REF_COUNTED()|.\ntemplate <typename T>\nclass MakeRefCountedHelper final {\n public:\n  template <typename... Args>\n  static RefPtr<T> MakeRefCounted(Args&&... args) {\n    return fml::AdoptRef<T>(new T(std::forward<Args>(args)...));\n  }\n};\n\n}  // namespace internal\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_MEMORY_REF_PTR_INTERNAL_H_\n"
  },
  {
    "path": "base/include/fml/memory/task_runner_checker.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_MEMORY_TASK_RUNNER_CHECKER_H_\n#define BASE_INCLUDE_FML_MEMORY_TASK_RUNNER_CHECKER_H_\n\n#include <set>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/task_runner.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass BASE_EXPORT TaskRunnerChecker final {\n public:\n  TaskRunnerChecker();\n\n  ~TaskRunnerChecker();\n\n  bool RunsOnCreationTaskRunner() const;\n\n  static bool RunsOnTheSameThread(TaskQueueId queue_a, TaskQueueId queue_b);\n\n private:\n  TaskQueueId initialized_queue_id_;\n  std::set<TaskQueueId> subsumed_queue_ids_;\n\n  TaskQueueId InitTaskQueueId();\n};\n\n#if !defined(NDEBUG)\n#define FML_DECLARE_TASK_RUNNER_CHECKER(c) fml::TaskRunnerChecker c\n// TODO(zhengsenyao): Add DCHECK when DCHECK available.\n#define FML_DCHECK_TASK_RUNNER_IS_CURRENT(c) (c).RunsOnCreationTaskRunner()\n#else\n#define FML_DECLARE_TASK_RUNNER_CHECKER(c)\n#define FML_DCHECK_TASK_RUNNER_IS_CURRENT(c) ((void)0)\n#endif\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::TaskRunnerChecker;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_MEMORY_TASK_RUNNER_CHECKER_H_\n"
  },
  {
    "path": "base/include/fml/memory/weak_ptr.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_FML_MEMORY_WEAK_PTR_H_\n#define BASE_INCLUDE_FML_MEMORY_WEAK_PTR_H_\n\n#include <utility>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/memory/task_runner_checker.h\"\n#include \"base/include/fml/memory/weak_ptr_internal.h\"\n#include \"base/include/fml/task_runner.h\"\n\n#ifdef __OBJC__\n#include \"base/include/platform/darwin/type_utils.h\"\n#endif\n\nnamespace lynx {\nnamespace fml {\n\nstruct DebugTaskRunnerChecker {\n  FML_DECLARE_TASK_RUNNER_CHECKER(checker);\n};\n\n// Forward declaration, so |WeakPtr<T>| can friend it.\ntemplate <typename T>\nclass EnableWeakFromThis;\n\ntemplate <typename T>\nclass WeakPtrFactory;\n\n// Class for \"weak pointers\" that can be invalidated. Valid weak pointers\n// can only originate from a |EnableWeakFromThis| (see below), though weak\n// pointers are copyable and movable.\n//\n// Weak pointers are not in general thread-safe. They may only be *used* on\n// threads which are belong to the same |TaskRunner|, namely the same thread as\n// the \"originating\" |EnableWeakFromThis| (which can invalidate the weak\n// pointers that it generates).\n//\n// However, weak pointers may be passed to other threads, reset on other\n// threads, or destroyed on other threads. They may also be reassigned on\n// other threads (in which case they should then only be used on the thread\n// corresponding to the new \"originating\" |EnableWeakFromThis|).\ntemplate <typename T>\nclass WeakPtr {\n public:\n#ifdef __OBJC__\n  static_assert(!base::is_objc_class<T*>::value,\n                \"WeakPtr<T> is not supported for Objective-C class\");\n#endif\n  WeakPtr() : ptr_(nullptr) {}\n\n  WeakPtr(const WeakPtr<T>& r) = default;\n\n  template <typename U>\n  // NOLINTNEXTLINE\n  WeakPtr(const WeakPtr<U>& r)\n      : ptr_(static_cast<T*>(r.ptr_)), flag_(r.flag_), checker_(r.checker_) {}\n\n  WeakPtr(WeakPtr<T>&& r) = default;\n\n  template <typename U>\n  // NOLINTNEXTLINE\n  WeakPtr(WeakPtr<U>&& r)\n      : ptr_(static_cast<T*>(r.ptr_)),\n        flag_(std::move(r.flag_)),\n        checker_(r.checker_) {}\n\n  ~WeakPtr() = default;\n\n  WeakPtr<T>& operator=(const WeakPtr<T>& r) = default;\n\n  WeakPtr<T>& operator=(WeakPtr<T>&& r) = default;\n\n  void reset() { flag_ = nullptr; }\n\n  // The following methods should only be called on the same thread as the\n  // \"originating\" |EnableWeakFromThis|.\n\n  explicit operator bool() const {\n    CheckThreadSafety();\n    return flag_ && flag_->is_valid();\n  }\n\n  T* get() const {\n    CheckThreadSafety();\n    return *this ? ptr_ : nullptr;\n  }\n\n  T& operator*() const {\n    CheckThreadSafety();\n    LYNX_BASE_DCHECK(*this);\n    return *get();\n  }\n\n  T* operator->() const {\n    CheckThreadSafety();\n    LYNX_BASE_DCHECK(*this);\n    return get();\n  }\n\n  bool operator==(const WeakPtr<T>& rhs) const {\n    CheckThreadSafety();\n    rhs.CheckThreadSafety();\n    return get() == rhs.get();\n  }\n\n  bool operator!=(const WeakPtr<T>& rhs) const { return !operator==(rhs); }\n\n protected:\n  void CheckThreadSafety() const {\n    FML_DCHECK_TASK_RUNNER_IS_CURRENT(checker_.checker);\n  }\n\n private:\n  template <typename U>\n  friend class WeakPtr;\n  friend class EnableWeakFromThis<T>;\n  friend class WeakPtrFactory<T>;\n\n  explicit WeakPtr(T* ptr, fml::RefPtr<fml::internal::WeakPtrFlag>&& flag,\n                   DebugTaskRunnerChecker checker)\n      : ptr_(ptr), flag_(std::move(flag)), checker_(checker) {}\n\n  T* ptr_;\n  fml::RefPtr<fml::internal::WeakPtrFlag> flag_;\n  DebugTaskRunnerChecker checker_;\n};\n\n// Class that works as base and produces (valid) |WeakPtr<T>|s.\n// This class is not thread-safe, and should only be created, destroyed and\n// used on threads that are belong to the same |TaskRunner|\n//\n// Example:\n//\n//  class Controller : public EnableWeakFromThis<Controller> {\n//   public:\n//    Controller() {}\n//    ...\n//\n//    void SpawnWorker() { Worker::StartNew(WeakFromThis()); }\n//    void WorkComplete(const Result& result) { ... }\n//  };\n//\n//  class Worker {\n//   public:\n//    static void StartNew(const WeakPtr<Controller>& controller) {\n//      Worker* worker = new Worker(controller);\n//      // Kick off asynchronous processing....\n//    }\n//\n//   private:\n//    Worker(const WeakPtr<Controller>& controller) : controller_(controller) {}\n//\n//    void DidCompleteAsynchronousProcessing(const Result& result) {\n//      if (controller_)\n//        controller_->WorkComplete(result);\n//    }\n//\n//    WeakPtr<Controller> controller_;\n//  };\ntemplate <typename T>\nclass EnableWeakFromThis {\n public:\n#ifdef __OBJC__\n  static_assert(!base::is_objc_class<T*>::value,\n                \"WeakPtr<T> is not supported for Objective-C class\");\n#endif\n\n  EnableWeakFromThis() = default;\n\n  ~EnableWeakFromThis() {\n    CheckThreadSafety();\n    if (flag_) {\n      flag_->Invalidate();\n    }\n  }\n\n  // Gets a new weak pointer, which will be valid until this object is\n  // destroyed.\n  WeakPtr<T> WeakFromThis() const {\n    if (!flag_) {\n      flag_ = fml::MakeRefCounted<fml::internal::WeakPtrFlag>();\n    }\n    return WeakPtr<T>(static_cast<T*>(const_cast<EnableWeakFromThis*>(this)),\n                      flag_.Clone(), checker_);\n  }\n\n private:\n  mutable fml::RefPtr<fml::internal::WeakPtrFlag> flag_;\n\n  void CheckThreadSafety() const {\n    FML_DCHECK_TASK_RUNNER_IS_CURRENT(checker_.checker);\n  }\n\n  DebugTaskRunnerChecker checker_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(EnableWeakFromThis);\n};\n\n/// Deprecated!!!\n/// Use EnableWeakFromThis instead.\n/// TODO(yuyang), replace usage of WeakPtrFactory in other modules.\ntemplate <typename T>\nclass WeakPtrFactory {\n public:\n#ifdef __OBJC__\n  static_assert(!base::is_objc_class<T*>::value,\n                \"WeakPtr<T> is not supported for Objective-C class\");\n#endif\n\n  explicit WeakPtrFactory(T* ptr)\n      : ptr_(ptr), flag_(fml::MakeRefCounted<fml::internal::WeakPtrFlag>()) {\n    LYNX_BASE_DCHECK(ptr_);\n  }\n\n  ~WeakPtrFactory() {\n    CheckThreadSafety();\n    flag_->Invalidate();\n  }\n\n  // Gets a new weak pointer, which will be valid until this object is\n  // destroyed.\n  WeakPtr<T> GetWeakPtr() const {\n    return WeakPtr<T>(ptr_, flag_.Clone(), checker_);\n  }\n\n private:\n  // Note: See weak_ptr_internal.h for an explanation of why we store the\n  // pointer here, instead of in the \"flag\".\n  T* const ptr_;\n  fml::RefPtr<fml::internal::WeakPtrFlag> flag_;\n\n  void CheckThreadSafety() const {\n    FML_DCHECK_TASK_RUNNER_IS_CURRENT(checker_.checker);\n  }\n\n  DebugTaskRunnerChecker checker_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(WeakPtrFactory);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::EnableWeakFromThis;\nusing lynx::fml::WeakPtr;\nusing lynx::fml::WeakPtrFactory;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_MEMORY_WEAK_PTR_H_\n"
  },
  {
    "path": "base/include/fml/memory/weak_ptr_internal.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_FML_MEMORY_WEAK_PTR_INTERNAL_H_\n#define BASE_INCLUDE_FML_MEMORY_WEAK_PTR_INTERNAL_H_\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace internal {\n\n// |WeakPtr<T>|s have a reference to a |WeakPtrFlag| to determine whether they\n// are valid (non-null) or not. We do not store a |T*| in this object since\n// there may also be |WeakPtr<U>|s to the same object, where |U| is a superclass\n// of |T|.\n//\n// This class in not thread-safe, though references may be released on any\n// thread (allowing weak pointers to be destroyed/reset/reassigned on any\n// thread).\nclass BASE_EXPORT WeakPtrFlag : public fml::RefCountedThreadSafe<WeakPtrFlag> {\n public:\n  WeakPtrFlag() = default;\n\n  ~WeakPtrFlag() {\n    // Should be invalidated before destruction.\n    LYNX_BASE_DCHECK(!is_valid());\n  }\n\n  bool is_valid() const { return __padding_chars__[0] == 0; }\n\n  void Invalidate() {\n    // Invalidation should happen exactly once.\n    LYNX_BASE_DCHECK(is_valid());\n    __padding_chars__[0] = 1;\n  }\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(WeakPtrFlag);\n};\n\n}  // namespace internal\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_MEMORY_WEAK_PTR_INTERNAL_H_\n"
  },
  {
    "path": "base/include/fml/message_loop.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_MESSAGE_LOOP_H_\n#define BASE_INCLUDE_FML_MESSAGE_LOOP_H_\n#include <iostream>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/task_queue_id.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/time/time_delta.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass TaskRunner;\nclass MessageLoopImpl;\n\n/// An event loop associated with a thread.\n///\n/// This class is the generic front-end to the MessageLoop, differences in\n/// implementation based on the running platform are in the subclasses of\n/// flutter::MessageLoopImpl (ex flutter::MessageLoopAndroid).\n///\n/// For scheduling events on the message loop see flutter::TaskRunner.\n///\n/// \\see fml::TaskRunner\n/// \\see fml::MessageLoopImpl\n/// \\see fml::MessageLoopTaskQueues\n/// \\see fml::Wakeable\nclass BASE_EXPORT MessageLoop {\n public:\n  static MessageLoop& GetCurrent();\n\n  void Run();\n\n  void Terminate();\n\n  void AddTaskObserver(intptr_t key, base::closure callback);\n\n  void RemoveTaskObserver(intptr_t key);\n\n  const fml::RefPtr<fml::TaskRunner>& GetTaskRunner() const;\n\n  // Exposed for the embedder shell which allows clients to poll for events\n  // instead of dedicating a thread to the message loop.\n  void RunExpiredTasksNow();\n\n  /// Set the max restriction duration to restrict maximum flush duration when\n  /// FlushTasks\n  void SetMessageLoopRestrictionDuration(fml::TimeDelta restriction_duration);\n\n  static MessageLoop& EnsureInitializedForCurrentThread(\n      void* platform_loop = nullptr);\n  const fml::RefPtr<MessageLoopImpl>& GetLoopImpl() const;\n\n  void Bind(const TaskQueueId& queue_id);\n\n  void UnBind(const TaskQueueId& queue_id);\n\n  /// Returns nonnull MessageLoop pointer if \\p\n  /// EnsureInitializedForCurrentThread has been called on this thread already,\n  /// or else returns nullptr.\n  static MessageLoop* IsInitializedForCurrentThread();\n\n  ~MessageLoop();\n\n  /// Gets the unique identifier for the TaskQueue associated with the current\n  /// thread.\n  /// \\see fml::MessageLoopTaskQueues\n  static TaskQueueId GetCurrentTaskQueueId();\n\n private:\n  friend class TaskRunner;\n  friend class MessageLoopImpl;\n\n  fml::RefPtr<MessageLoopImpl> loop_;\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n\n  explicit MessageLoop(void* platform_loop = nullptr);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(MessageLoop);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::MessageLoop;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_MESSAGE_LOOP_H_\n"
  },
  {
    "path": "base/include/fml/message_loop_impl.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_MESSAGE_LOOP_IMPL_H_\n#define BASE_INCLUDE_FML_MESSAGE_LOOP_IMPL_H_\n\n#include <atomic>\n#include <deque>\n#include <map>\n#include <mutex>\n#include <queue>\n#include <utility>\n#include <vector>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/delayed_task.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/fml/wakeable.h\"\n\nnamespace lynx {\nnamespace fml {\n// VSyncCallback from VSyncRequest. The first parameter represents\n// frame_start_time, and the second one represents frame_target_time, both in\n// nanoseconds\nusing VSyncCallback = base::MoveOnlyClosure<void, int64_t, int64_t>;\n\n// Use for requesting a VSync from platform layer, so that we could align the\n// execution of tasks with VSync to reduce jank.\nusing VSyncRequest = base::MoveOnlyClosure<void, VSyncCallback>;\n\n/// An abstract class that represents the differences in implementation of a \\p\n/// fml::MessageLoop depending on the platform.\n/// \\see fml::MessageLoop\n/// \\see fml::MessageLoopAndroid\n/// \\see fml::MessageLoopDarwin\nclass BASE_EXPORT MessageLoopImpl\n    : public Wakeable,\n      public fml::RefCountedThreadSafe<MessageLoopImpl> {\n public:\n  static fml::RefPtr<MessageLoopImpl> Create(void* platform_loop = nullptr);\n\n  virtual ~MessageLoopImpl();\n\n  virtual void Run() = 0;\n\n  virtual void Terminate() = 0;\n\n  void PostTask(base::closure task, fml::TimePoint target_time,\n                fml::TaskSourceGrade task_source_grade =\n                    fml::TaskSourceGrade::kUnspecified,\n                bool instant_task_hint = false);\n\n  void AddTaskObserver(intptr_t key, base::closure callback);\n\n  void RemoveTaskObserver(intptr_t key);\n\n  void DoRun();\n\n  void DoTerminate();\n\n  virtual TaskQueueId GetTaskQueueId() const;\n\n  virtual std::vector<TaskQueueId> GetTaskQueueIds() const;\n\n  void SetRestrictionDuration(fml::TimeDelta duration);\n\n  void Bind(const TaskQueueId& queue_id,\n            bool should_run_expired_tasks_immediately = false);\n\n  void UnBind(const TaskQueueId& queue_id);\n\n  virtual bool CanRunNow();\n\n  void SetVSyncRequest(VSyncRequest vsync_request);\n\n  void WakeUp(fml::TimePoint time_point, bool is_woken_by_vsync) override;\n\n  virtual void WakeUp(fml::TimePoint time_point) = 0;\n\n protected:\n  // Exposed for the embedder shell which allows clients to poll for events\n  // instead of dedicating a thread to the message loop.\n  friend class MessageLoop;\n\n  void RunExpiredTasksNow();\n\n  void RunSingleExpiredTaskNow();\n\n protected:\n  MessageLoopImpl();\n  MessageLoopTaskQueues* task_queue_;\n  TaskQueueId internal_queue_id_;\n  std::vector<TaskQueueId> queue_ids_;\n  std::vector<TaskQueueId> vsync_aligned_task_queue_ids_;\n\n private:\n  void WakeUpByVSync(fml::TimePoint time_point);\n\n  bool WaitForVSyncTimeOut();\n\n  bool HasPendingVSyncRequest();\n\n  void FlushVSyncAlignedTasks(FlushType type);\n  // Return true if reach the given restriction_duration, otherwise, return\n  // false.\n  bool FlushTasksWithRestrictionDuration(\n      FlushType type, const std::vector<TaskQueueId>& queue_ids,\n      int64_t restriction_duration);\n\n  std::atomic_bool terminated_;\n  // Default fml::TimeDelta::Max(), means no effect.\n  fml::TimeDelta restriction_duration_;\n\n  VSyncRequest vsync_request_;\n\n  // The max execution time, its value is determined by the screen refresh rate.\n  // Will be set inside the vsync callback.\n  int64_t max_execute_time_ms_ = 16;\n  static constexpr int64_t kNSecPerMSec = 1000000;\n\n  // This is an estimated value. It indicates the proportion of FlushTasks in\n  // the entire vsync cycle.\n  static constexpr float kTraversalProportion = 0.75;\n\n  // Used to record the time for requesting vsync, it will be reset to 0 when\n  // the vsync callback is executed.\n  int64_t request_vsync_time_millis_ = 0;\n\n  // The maximum timeout for waiting for the vsync callback.\n  static constexpr int64_t kWaitingVSyncTimeoutMillis = 5000;\n\n  virtual void FlushTasks(FlushType type);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(MessageLoopImpl);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::MessageLoopImpl;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_MESSAGE_LOOP_IMPL_H_\n"
  },
  {
    "path": "base/include/fml/message_loop_task_queues.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_MESSAGE_LOOP_TASK_QUEUES_H_\n#define BASE_INCLUDE_FML_MESSAGE_LOOP_TASK_QUEUES_H_\n\n#include <atomic>\n#include <map>\n#include <memory>\n#include <mutex>\n#include <optional>\n#include <set>\n#include <vector>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/closure.h\"\n#include \"base/include/fml/delayed_task.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/task_queue_id.h\"\n#include \"base/include/fml/task_source.h\"\n#include \"base/include/fml/wakeable.h\"\n#include \"base/include/no_destructor.h\"\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace fml {\n\n/// A collection of tasks and observers associated with one TaskQueue.\n///\n/// Often a TaskQueue has a one-to-one relationship with a fml::MessageLoop,\n/// this isn't the case when TaskQueues are merged via\n/// \\p fml::MessageLoopTaskQueues::Merge.\nclass TaskQueueEntry {\n public:\n  using TaskObservers = std::map<intptr_t, base::closure>;\n  Wakeable* wakeable;\n  TaskObservers task_observers;\n  std::unique_ptr<TaskSource> task_source;\n\n  /// Set of the TaskQueueIds which is owned by this TaskQueue. If the set is\n  /// empty, this TaskQueue does not own any other TaskQueues.\n  std::set<TaskQueueId> owner_of;\n\n  /// Identifies the TaskQueue that subsumes this TaskQueue. If it is _kUnmerged\n  /// it indicates that this TaskQueue is not owned by any other TaskQueue.\n  TaskQueueId subsumed_by;\n\n  TaskQueueId created_for;\n\n  bool is_aligned_with_vsync_ = false;\n\n  bool IsAlignedWithVSync() const { return is_aligned_with_vsync_; }\n\n  explicit TaskQueueEntry(TaskQueueId created_for,\n                          bool is_aligned_with_vsync = false);\n\n private:\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(TaskQueueEntry);\n};\n\nenum class FlushType {\n  kSingle,\n  kAll,\n};\n\n/// A singleton container for all tasks and observers associated with all\n/// fml::MessageLoops.\n///\n/// This also wakes up the loop at the required times.\n/// \\see fml::MessageLoop\n/// \\see fml::Wakeable\nclass BASE_EXPORT MessageLoopTaskQueues {\n public:\n  // Lifecycle.\n\n  static MessageLoopTaskQueues* GetInstance();\n\n  TaskQueueId CreateTaskQueue(bool is_vsync_aligned_task_queue = false);\n\n  void Dispose(TaskQueueId queue_id);\n\n  void DisposeTasks(TaskQueueId queue_id);\n\n  // Tasks methods.\n\n  void RegisterTask(TaskQueueId queue_id, base::closure task,\n                    fml::TimePoint target_time,\n                    fml::TaskSourceGrade task_source_grade =\n                        fml::TaskSourceGrade::kUnspecified,\n                    bool instant_task_hint = false);\n\n  bool HasPendingTasks(TaskQueueId queue_id) const;\n\n  base::closure GetNextTaskToRun(\n      const std::vector<TaskQueueId>& queue_ids, fml::TimePoint from_time,\n      std::vector<const base::closure*>* observers = nullptr);\n\n  size_t GetNumPendingTasks(TaskQueueId queue_id) const;\n\n  // Observers methods.\n\n  void AddTaskObserver(TaskQueueId queue_id, intptr_t key,\n                       base::closure callback);\n\n  void RemoveTaskObserver(TaskQueueId queue_id, intptr_t key);\n\n  std::vector<const base::closure*> GetObserversToNotify(\n      TaskQueueId queue_id) const;\n\n  // Misc.\n\n  void SetWakeable(TaskQueueId queue_id, fml::Wakeable* wakeable);\n\n  // Invariants for merge and un-merge\n  //  1. RegisterTask will always submit to the queue_id that is passed\n  //     to it. It is not aware of whether a queue is merged or not. Same with\n  //     task observers.\n  //  2. When we get the tasks to run now, we look at both the queue_ids\n  //     for the owner and the subsumed task queues.\n  //  3. One TaskQueue can subsume multiple other TaskQueues. A TaskQueue can be\n  //     in exactly one of the following three states:\n  //     a. Be an owner of multiple other TaskQueues.\n  //     b. Be subsumed by a TaskQueue (an owner can never be subsumed).\n  //     c. Be independent, i.e, neither owner nor be subsumed.\n  //\n  //  Methods currently aware of the merged state of the queues:\n  //  HasPendingTasks, GetNextTaskToRun, GetNumPendingTasks\n  bool Merge(TaskQueueId owner, TaskQueueId subsumed);\n\n  // Will return false if the owner has not been merged before, or owner was\n  // subsumed by others, or subsumed wasn't subsumed by others, or owner\n  // didn't own the given subsumed queue id.\n  bool Unmerge(TaskQueueId owner, TaskQueueId subsumed);\n\n  /// Returns \\p true if \\p owner owns the \\p subsumed task queue.\n  bool Owns(TaskQueueId owner, TaskQueueId subsumed) const;\n\n  // Returns the subsumed task queue if any or |TaskQueueId::kUnmerged|\n  // otherwise.\n  std::set<TaskQueueId> GetSubsumedTaskQueueId(TaskQueueId owner) const;\n\n  // use for trace, need for each all queues.\n  std::vector<TaskQueueId> GetAllQueueIds();\n\n  bool IsSubsumed(TaskQueueId queue_id) const;\n\n  void WakeUp(const std::vector<TaskQueueId>& queue_ids) const;\n\n  // TODO(heshan): Temporary workaround for now.\n  // After refactoring AutoConcurrency with Bind/Unbind methods, remove this\n  // method.\n  bool IsTaskQueueRunningOnGivenMessageLoop(Wakeable* loop,\n                                            TaskQueueId queue_id);\n\n  bool IsTaskQueueAlignedWithVSync(TaskQueueId queue_id);\n\n private:\n  class MergedQueuesRunner;\n\n  friend base::NoDestructor<MessageLoopTaskQueues>;\n\n  MessageLoopTaskQueues();\n\n  ~MessageLoopTaskQueues();\n\n  void WakeUpUnlocked(TaskQueueId queue_id, fml::TimePoint time) const;\n  void WakeUpUnlocked(TaskQueueEntry& queue, fml::TimePoint time) const;\n\n  bool HasPendingTasksUnlocked(TaskQueueId queue_id) const;\n\n  bool HasPendingTasksUnlocked(const std::vector<TaskQueueId>& queue_ids) const;\n\n  TaskSource::TopTask PeekNextTaskUnlocked(TaskQueueId owner) const;\n\n  TaskSource::TopTask PeekNextTaskUnlocked(\n      const std::vector<TaskQueueId>& owners) const;\n\n  fml::TimePoint GetNextWakeTimeUnlocked(TaskQueueId queue_id) const;\n\n  fml::TimePoint GetNextWakeTimeUnlocked(\n      const std::vector<TaskQueueId>& queue_ids) const;\n\n  mutable std::mutex queue_mutex_;\n  mutable base::LinearFlatMap<TaskQueueId, std::unique_ptr<TaskQueueEntry>>\n      queue_entries_;\n\n  size_t task_queue_id_counter_;\n\n  std::atomic_int order_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(MessageLoopTaskQueues);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\n\nusing lynx::fml::FlushType;\nusing lynx::fml::MessageLoopTaskQueues;\nusing lynx::fml::TaskQueueEntry;\n\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_MESSAGE_LOOP_TASK_QUEUES_H_\n"
  },
  {
    "path": "base/include/fml/platform/android/cpu_affinity.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_ANDROID_CPU_AFFINITY_H_\n#define BASE_INCLUDE_FML_PLATFORM_ANDROID_CPU_AFFINITY_H_\n\n#include \"base/include/fml/cpu_affinity.h\"\n\nnamespace lynx {\nnamespace fml {\n\n/// @brief Android specific implementation of EfficiencyCoreCount.\nstd::optional<size_t> AndroidEfficiencyCoreCount();\n\n/// @brief Android specific implementation of RequestAffinity.\nbool AndroidRequestAffinity(CpuAffinity affinity);\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_ANDROID_CPU_AFFINITY_H_\n"
  },
  {
    "path": "base/include/fml/platform/android/message_loop_android.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_ANDROID_MESSAGE_LOOP_ANDROID_H_\n#define BASE_INCLUDE_FML_PLATFORM_ANDROID_MESSAGE_LOOP_ANDROID_H_\n\n#include <android/looper.h>\n#include <jni.h>\n\n#include <atomic>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/unique_fd.h\"\n\nnamespace lynx {\nnamespace fml {\n\nstruct UniqueLooperTraits {\n  static ALooper* InvalidValue() { return nullptr; }\n  static bool IsValid(ALooper* value) { return value != nullptr; }\n  static void Free(ALooper* value) { ::ALooper_release(value); }\n};\n\n/// Android implementation of \\p MessageLoopImpl.\n///\n/// This implemenation wraps usage of Android's \\p looper.\n/// \\see https://developer.android.com/ndk/reference/group/looper\nclass BASE_EXPORT MessageLoopAndroid : public MessageLoopImpl {\n public:\n  static bool Register(JNIEnv* env);\n\n private:\n  fml::UniqueObject<ALooper*, UniqueLooperTraits> looper_;\n  fml::UniqueFD timer_fd_;\n  bool running_;\n\n  void Run() override;\n\n  void Terminate() override;\n\n  void OnEventFired();\n\n  FML_FRIEND_MAKE_REF_COUNTED(MessageLoopAndroid);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MessageLoopAndroid);\n  BASE_DISALLOW_COPY_AND_ASSIGN(MessageLoopAndroid);\n\n protected:\n  void WakeUp(fml::TimePoint time_point) override;\n\n  MessageLoopAndroid();\n\n  ~MessageLoopAndroid() override;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::MessageLoopAndroid;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_ANDROID_MESSAGE_LOOP_ANDROID_H_\n"
  },
  {
    "path": "base/include/fml/platform/darwin/cf_utils.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_DARWIN_CF_UTILS_H_\n#define BASE_INCLUDE_FML_PLATFORM_DARWIN_CF_UTILS_H_\n\n#include <CoreFoundation/CoreFoundation.h>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace fml {\n\ntemplate <class T>\nclass CFRef {\n public:\n  CFRef() : instance_(nullptr) {}\n\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  CFRef(T instance) : instance_(instance) {}\n\n  CFRef(const CFRef& other) : instance_(other.instance_) {\n    if (instance_) {\n      CFRetain(instance_);\n    }\n  }\n\n  CFRef(CFRef&& other) : instance_(other.instance_) {\n    other.instance_ = nullptr;\n  }\n\n  CFRef& operator=(CFRef&& other) {\n    Reset(other.Release());\n    return *this;\n  }\n\n  ~CFRef() {\n    if (instance_ != nullptr) {\n      CFRelease(instance_);\n    }\n    instance_ = nullptr;\n  }\n\n  void Reset(T instance = nullptr) {\n    if (instance_ != nullptr) {\n      CFRelease(instance_);\n    }\n\n    instance_ = instance;\n  }\n\n  [[nodiscard]] T Release() {\n    auto instance = instance_;\n    instance_ = nullptr;\n    return instance;\n  }\n\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  operator T() const { return instance_; }\n\n  explicit operator bool() const { return instance_ != nullptr; }\n\n private:\n  T instance_;\n\n  CFRef& operator=(const CFRef&) = delete;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_DARWIN_CF_UTILS_H_\n"
  },
  {
    "path": "base/include/fml/platform/darwin/message_loop_darwin.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_DARWIN_MESSAGE_LOOP_DARWIN_H_\n#define BASE_INCLUDE_FML_PLATFORM_DARWIN_MESSAGE_LOOP_DARWIN_H_\n\n#include <CoreFoundation/CoreFoundation.h>\n\n#include <atomic>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/platform/darwin/cf_utils.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass MessageLoopDarwin : public MessageLoopImpl {\n public:\n  // A custom CFRunLoop mode used when processing flutter messages,\n  // so that the CFRunLoop can be run without being interrupted by UIKit,\n  // while still being able to receive and be interrupted by framework messages.\n  static CFStringRef kMessageLoopCFRunLoopMode;\n\n protected:\n  void WakeUp(fml::TimePoint time_point) override;\n\n  MessageLoopDarwin();\n\n  ~MessageLoopDarwin() override;\n\n private:\n  std::atomic_bool running_;\n  CFRef<CFRunLoopTimerRef> delayed_wake_timer_;\n  CFRef<CFRunLoopRef> loop_;\n  CFRef<CFRunLoopSourceRef> work_source_;\n\n  // |fml::MessageLoopImpl|\n  void Run() override;\n\n  // |fml::MessageLoopImpl|\n  void Terminate() override;\n\n  static void OnSourceFire(MessageLoopDarwin* loop);\n\n  static void OnTimerFire(CFRunLoopTimerRef timer, MessageLoopDarwin* loop);\n\n  FML_FRIEND_MAKE_REF_COUNTED(MessageLoopDarwin);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MessageLoopDarwin);\n  BASE_DISALLOW_COPY_AND_ASSIGN(MessageLoopDarwin);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::MessageLoopDarwin;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_DARWIN_MESSAGE_LOOP_DARWIN_H_\n"
  },
  {
    "path": "base/include/fml/platform/harmony/message_loop_harmony.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_HARMONY_MESSAGE_LOOP_HARMONY_H_\n#define BASE_INCLUDE_FML_PLATFORM_HARMONY_MESSAGE_LOOP_HARMONY_H_\n\n#include <uv.h>\n\n#include <atomic>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/unique_fd.h\"\n\nnamespace lynx {\nnamespace fml {\n\n/// Harmony implementation of \\p MessageLoopImpl.\n///\nclass MessageLoopHarmony : public MessageLoopImpl {\n private:\n  uv_loop_t* looper_;\n  bool is_looper_owner_;\n  uv_poll_t poll_;\n  fml::UniqueFD timer_fd_;\n  bool running_;\n\n  void Run() override;\n\n  void Terminate() override;\n\n  void OnEventFired();\n\n  FML_FRIEND_MAKE_REF_COUNTED(MessageLoopHarmony);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MessageLoopHarmony);\n  BASE_DISALLOW_COPY_AND_ASSIGN(MessageLoopHarmony);\n\n protected:\n  void WakeUp(fml::TimePoint time_point) override;\n\n  explicit MessageLoopHarmony(void* platform_loop);\n\n  ~MessageLoopHarmony() override;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::MessageLoopHarmony;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_HARMONY_MESSAGE_LOOP_HARMONY_H_\n"
  },
  {
    "path": "base/include/fml/platform/linux/message_loop_linux.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_LINUX_MESSAGE_LOOP_LINUX_H_\n#define BASE_INCLUDE_FML_PLATFORM_LINUX_MESSAGE_LOOP_LINUX_H_\n\n#include <atomic>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/unique_fd.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass MessageLoopLinux : public MessageLoopImpl {\n private:\n  fml::UniqueFD epoll_fd_;\n  fml::UniqueFD timer_fd_;\n  bool running_;\n\n  MessageLoopLinux();\n\n  ~MessageLoopLinux() override;\n\n  // |fml::MessageLoopImpl|\n  void Run() override;\n\n  // |fml::MessageLoopImpl|\n  void Terminate() override;\n\n  // |fml::MessageLoopImpl|\n  void WakeUp(fml::TimePoint time_point) override;\n\n  void OnEventFired();\n\n  bool AddOrRemoveTimerSource(bool add);\n\n  FML_FRIEND_MAKE_REF_COUNTED(MessageLoopLinux);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MessageLoopLinux);\n  BASE_DISALLOW_COPY_AND_ASSIGN(MessageLoopLinux);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::MessageLoopLinux;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_LINUX_MESSAGE_LOOP_LINUX_H_\n"
  },
  {
    "path": "base/include/fml/platform/linux/timerfd.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_LINUX_TIMERFD_H_\n#define BASE_INCLUDE_FML_PLATFORM_LINUX_TIMERFD_H_\n\n#include \"base/include/fml/time/time_point.h\"\n\n// clang-format off\n#if __has_include(<sys/timerfd.h>) && \\\n    (!defined(__ANDROID_API__) || __ANDROID_API__ >= 19)\n    // sys/timerfd.h is always present in Android NDK due to unified headers,\n    // but timerfd functions are only available on API 19 or later.\n// clang-format on\n\n#include <sys/timerfd.h>\n\n#define FML_TIMERFD_AVAILABLE 1\n\n#else  // __has_include(<sys/timerfd.h>)\n\n#define FML_TIMERFD_AVAILABLE 0\n\n#include <sys/types.h>\n// Must come after sys/types\n#include <linux/time.h>\n\n#define TFD_TIMER_ABSTIME (1 << 0)\n#define TFD_TIMER_CANCEL_ON_SET (1 << 1)\n\n#define TFD_CLOEXEC O_CLOEXEC\n#define TFD_NONBLOCK O_NONBLOCK\n\nint timerfd_create(int clockid, int flags);\n\nint timerfd_settime(int ufc, int flags, const struct itimerspec* utmr,\n                    struct itimerspec* otmr);\n\n#endif  // __has_include(<sys/timerfd.h>)\n\nnamespace lynx {\nnamespace fml {\n\n/// Rearms the timer to expire at the given time point.\nbool TimerRearm(int fd, fml::TimePoint time_point);\n\n/// Drains the timer FD and returns true if it has expired. This may be false in\n/// case the timer read is non-blocking and this routine was called before the\n/// timer expiry.\nbool TimerDrain(int fd);\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::TimerDrain;\nusing lynx::fml::TimerRearm;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_LINUX_TIMERFD_H_\n"
  },
  {
    "path": "base/include/fml/platform/thread_config_setter.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_THREAD_CONFIG_SETTER_H_\n#define BASE_INCLUDE_FML_PLATFORM_THREAD_CONFIG_SETTER_H_\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/thread.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass BASE_EXPORT PlatformThreadPriority {\n public:\n  static void Setter(const lynx::fml::Thread::ThreadConfig& config);\n};\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_THREAD_CONFIG_SETTER_H_\n"
  },
  {
    "path": "base/include/fml/platform/win/message_loop_win.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_WIN_MESSAGE_LOOP_WIN_H_\n#define BASE_INCLUDE_FML_PLATFORM_WIN_MESSAGE_LOOP_WIN_H_\n\n#include <windows.h>\n\n#include <atomic>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/unique_object.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass MessageLoopWin : public MessageLoopImpl {\n private:\n  struct UniqueHandleTraits {\n    static HANDLE InvalidValue() { return NULL; }\n    static bool IsValid(HANDLE value) { return value != NULL; }\n    static void Free(HANDLE value) { CloseHandle(value); }\n  };\n\n  bool running_;\n  fml::UniqueObject<HANDLE, UniqueHandleTraits> timer_;\n  uint32_t timer_resolution_;\n\n  MessageLoopWin();\n\n  ~MessageLoopWin() override;\n\n  void Run() override;\n\n  void Terminate() override;\n\n  void WakeUp(fml::TimePoint time_point) override;\n\n  FML_FRIEND_MAKE_REF_COUNTED(MessageLoopWin);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MessageLoopWin);\n  BASE_DISALLOW_COPY_AND_ASSIGN(MessageLoopWin);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::MessageLoopWin;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_WIN_MESSAGE_LOOP_WIN_H_\n"
  },
  {
    "path": "base/include/fml/platform/win/task_runner_win32.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_WIN_TASK_RUNNER_WIN32_H_\n#define BASE_INCLUDE_FML_PLATFORM_WIN_TASK_RUNNER_WIN32_H_\n\n#include <windows.h>\n\n#include <deque>\n#include <memory>\n#include <queue>\n\n#include \"base/include/fml/platform/win/task_runner_win32_window.h\"\n#include \"base/include/fml/task_runner.h\"\n\nnamespace lynx {\nnamespace fml {\n\ntypedef uint64_t (*CurrentTimeProc)();\n\n// A custom task runner that integrates with user32 GetMessage semantics so that\n// host app can own its own message loop and flutter still gets to process\n// tasks on a timely basis.\nclass TaskRunnerWin32 : public TaskRunner,\n                        public TaskRunnerWin32Window::Delegate {\n public:\n  using TaskTimePoint = std::chrono::steady_clock::time_point;\n\n  // Creates a new task runner with the current thread, current time\n  // provider, and callback for tasks that are ready to be run.\n  static fml::RefPtr<fml::TaskRunner> Create();\n\n  virtual ~TaskRunnerWin32();\n\n  // |TaskRunner|\n  void PostTask(base::closure closure) override;\n\n  // |TaskRunner|\n  void PostTaskForTime(base::closure closure,\n                       fml::TimePoint target_time) override;\n\n  // |TaskRunner|\n  void PostDelayedTask(base::closure closure, fml::TimeDelta delay) override;\n\n  // |TaskRunner|\n  bool RunsTasksOnCurrentThread() override;\n\n  // |TaskRunnerWin32Window::Delegate|\n  std::chrono::nanoseconds ProcessTasks() override;\n\n private:\n  TaskRunnerWin32();\n\n  // Returns the current TaskTimePoint that can be used to determine whether a\n  // task is expired.\n  //\n  // Tests can override this to mock up the time.\n  TaskTimePoint GetCurrentTimeForTask() const {\n    return TaskTimePoint::clock::now();\n  }\n\n  struct Task {\n    uint64_t order;\n    TaskTimePoint fire_time;\n    mutable base::closure closure;\n\n    struct Comparer {\n      bool operator()(const Task& a, const Task& b) {\n        if (a.fire_time == b.fire_time) {\n          return a.order > b.order;\n        }\n        return a.fire_time > b.fire_time;\n      }\n    };\n  };\n\n  std::mutex task_queue_mutex_;\n  std::priority_queue<Task, std::deque<Task>, Task::Comparer> task_queue_;\n\n  DWORD main_thread_id_;\n  std::shared_ptr<TaskRunnerWin32Window> task_runner_window_;\n\n  FML_FRIEND_MAKE_REF_COUNTED(TaskRunnerWin32);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(TaskRunnerWin32);\n\n  TaskRunnerWin32(const TaskRunnerWin32&) = delete;\n  TaskRunnerWin32& operator=(const TaskRunnerWin32&) = delete;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_WIN_TASK_RUNNER_WIN32_H_\n"
  },
  {
    "path": "base/include/fml/platform/win/task_runner_win32_window.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_PLATFORM_WIN_TASK_RUNNER_WIN32_WINDOW_H_\n#define BASE_INCLUDE_FML_PLATFORM_WIN_TASK_RUNNER_WIN32_WINDOW_H_\n\n#include <windows.h>\n\n#include <chrono>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace lynx {\nnamespace fml {\n\n// Hidden HWND responsible for processing flutter tasks on main thread\nclass TaskRunnerWin32Window {\n public:\n  class Delegate {\n   public:\n    virtual std::chrono::nanoseconds ProcessTasks() = 0;\n  };\n\n  static std::shared_ptr<TaskRunnerWin32Window> GetSharedInstance();\n\n  // Triggers processing delegate tasks on main thread\n  void WakeUp();\n\n  void AddDelegate(Delegate* delegate);\n  void RemoveDelegate(Delegate* delegate);\n\n  ~TaskRunnerWin32Window();\n\n private:\n  TaskRunnerWin32Window();\n\n  void ProcessTasks();\n\n  void SetTimer(std::chrono::nanoseconds when);\n\n  WNDCLASS RegisterWindowClass();\n\n  LRESULT\n  HandleMessage(UINT const message, WPARAM const wparam,\n                LPARAM const lparam) noexcept;\n\n  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,\n                                  WPARAM const wparam,\n                                  LPARAM const lparam) noexcept;\n\n  HWND window_handle_;\n  std::wstring window_class_name_;\n  std::vector<Delegate*> delegates_;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_PLATFORM_WIN_TASK_RUNNER_WIN32_WINDOW_H_\n"
  },
  {
    "path": "base/include/fml/raster_thread_merger.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_RASTER_THREAD_MERGER_H_\n#define BASE_INCLUDE_FML_RASTER_THREAD_MERGER_H_\n\n#include <condition_variable>\n#include <mutex>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"base/include/fml/shared_thread_merger.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass MessageLoopImpl;\n\nenum class RasterThreadStatus {\n  kRemainsMerged,\n  kRemainsUnmerged,\n  kUnmergedNow\n};\n\n/// This class is a client and proxy between the rasterizer and\n/// |SharedThreadMerger|. The multiple |RasterThreadMerger| instances with same\n/// owner_queue_id and same subsumed_queue_id share the same\n/// |SharedThreadMerger| instance. Whether they share the same inner instance is\n/// determined by |RasterThreadMerger::CreateOrShareThreadMerger| method.\nclass RasterThreadMerger\n    : public fml::RefCountedThreadSafe<RasterThreadMerger> {\n public:\n  // Merges the raster thread into platform thread for the duration of\n  // the lease term. Lease is managed by the caller by either calling\n  // |ExtendLeaseTo| or |DecrementLease|.\n  // When the caller merges with a lease term of say 2. The threads\n  // are going to remain merged until 2 invocations of |DecreaseLease|,\n  // unless an |ExtendLeaseTo| gets called.\n  //\n  // If the task queues are the same, we consider them statically merged.\n  // When task queues are statically merged this method becomes no-op.\n  void MergeWithLease(size_t lease_term);\n\n  // Gets the shared merger from current merger object\n  const fml::RefPtr<SharedThreadMerger>& GetSharedRasterThreadMerger() const;\n\n  /// Creates a new merger from parent, share the inside shared_merger member\n  /// when the platform_queue_id and raster_queue_id are same, otherwise create\n  /// a new shared_merger instance\n  static fml::RefPtr<fml::RasterThreadMerger> CreateOrShareThreadMerger(\n      const fml::RefPtr<fml::RasterThreadMerger>& parent_merger,\n      TaskQueueId platform_id, TaskQueueId raster_id);\n\n  // Un-merges the threads now if current caller is the last merge caller,\n  // and it resets the lease term to 0, otherwise it will remove the caller\n  // record and return. The multiple caller records were recorded after\n  // |MergeWithLease| or |ExtendLeaseTo| method.\n  //\n  // Must be executed on the raster task runner.\n  //\n  // If the task queues are the same, we consider them statically merged.\n  // When task queues are statically merged, we never unmerge them and\n  // this method becomes no-op.\n  void UnMergeNowIfLastOne();\n\n  // If the task queues are the same, we consider them statically merged.\n  // When task queues are statically merged this method becomes no-op.\n  void ExtendLeaseTo(size_t lease_term);\n\n  // Returns |RasterThreadStatus::kUnmergedNow| if this call resulted in\n  // splitting the raster and platform threads. Reduces the lease term by 1.\n  //\n  // If the task queues are the same, we consider them statically merged.\n  // When task queues are statically merged this method becomes no-op.\n  RasterThreadStatus DecrementLease();\n\n  // The method is locked by current instance, and asks the shared instance of\n  // SharedThreadMerger and the merging state is determined by the\n  // lease_term_ counter.\n  bool IsMerged();\n\n  // Waits until the threads are merged.\n  //\n  // Must run on the platform task runner.\n  void WaitUntilMerged();\n\n  // Returns true if the current thread owns rasterizing.\n  // When the threads are merged, platform thread owns rasterizing.\n  // When un-merged, raster thread owns rasterizing.\n  bool IsOnRasterizingThread();\n\n  // Returns true if the current thread is the platform thread.\n  bool IsOnPlatformThread() const;\n\n  // Enables the thread merger.\n  void Enable();\n\n  // Disables the thread merger. Once disabled, any call to\n  // |MergeWithLease| or |UnMergeNowIfLastOne| results in a noop.\n  void Disable();\n\n  // Whether the thread merger is enabled. By default, the thread merger is\n  // enabled. If false, calls to |MergeWithLease| or |UnMergeNowIfLastOne|\n  // or |ExtendLeaseTo| or |DecrementLease| results in a noop.\n  bool IsEnabled();\n\n  // Registers a callback that can be used to clean up global state right after\n  // the thread configuration has changed.\n  //\n  // For example, it can be used to clear the GL context so it can be used in\n  // the next task from a different thread.\n  void SetMergeUnmergeCallback(base::closure callback);\n\n private:\n  fml::TaskQueueId platform_queue_id_;\n  fml::TaskQueueId gpu_queue_id_;\n\n  RasterThreadMerger(fml::TaskQueueId platform_queue_id,\n                     fml::TaskQueueId gpu_queue_id);\n  RasterThreadMerger(fml::RefPtr<fml::SharedThreadMerger> shared_merger,\n                     fml::TaskQueueId platform_queue_id,\n                     fml::TaskQueueId gpu_queue_id);\n\n  const fml::RefPtr<fml::SharedThreadMerger> shared_merger_;\n  std::condition_variable merged_condition_;\n  std::mutex mutex_;\n  base::closure merge_unmerge_callback_;\n\n  bool IsMergedUnSafe() const;\n\n  bool IsEnabledUnSafe() const;\n\n  // The platform_queue_id and gpu_queue_id are exactly the same.\n  // We consider the threads are always merged and cannot be unmerged.\n  bool TaskQueuesAreSame() const;\n\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(RasterThreadMerger);\n  FML_FRIEND_MAKE_REF_COUNTED(RasterThreadMerger);\n  BASE_DISALLOW_COPY_AND_ASSIGN(RasterThreadMerger);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::RasterThreadMerger;\nusing lynx::fml::RasterThreadStatus;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_RASTER_THREAD_MERGER_H_\n"
  },
  {
    "path": "base/include/fml/shared_thread_merger.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_SHARED_THREAD_MERGER_H_\n#define BASE_INCLUDE_FML_SHARED_THREAD_MERGER_H_\n\n#include <condition_variable>\n#include <map>\n#include <mutex>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/message_loop_task_queues.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass RasterThreadMerger;\n\ntypedef void* RasterThreadMergerId;\n\n/// Instance of this class is shared between multiple |RasterThreadMerger|\n/// instances, Most of the callings from |RasterThreadMerger| will be redirected\n/// to this class with one more caller parameter.\nclass SharedThreadMerger\n    : public fml::RefCountedThreadSafe<SharedThreadMerger> {\n public:\n  SharedThreadMerger(TaskQueueId owner, TaskQueueId subsumed);\n\n  // It's called by |RasterThreadMerger::MergeWithLease|.\n  // See the doc of |RasterThreadMerger::MergeWithLease|.\n  bool MergeWithLease(RasterThreadMergerId caller, size_t lease_term);\n\n  // It's called by |RasterThreadMerger::UnMergeNowIfLastOne|.\n  // See the doc of |RasterThreadMerger::UnMergeNowIfLastOne|.\n  bool UnMergeNowIfLastOne(RasterThreadMergerId caller);\n\n  // It's called by |RasterThreadMerger::ExtendLeaseTo|.\n  // See the doc of |RasterThreadMerger::ExtendLeaseTo|.\n  void ExtendLeaseTo(RasterThreadMergerId caller, size_t lease_term);\n\n  // It's called by |RasterThreadMerger::IsMergedUnSafe|.\n  // See the doc of |RasterThreadMerger::IsMergedUnSafe|.\n  bool IsMergedUnSafe() const;\n\n  // It's called by |RasterThreadMerger::IsEnabledUnSafe|.\n  // See the doc of |RasterThreadMerger::IsEnabledUnSafe|.\n  bool IsEnabledUnSafe() const;\n\n  // It's called by |RasterThreadMerger::Enable| or\n  // |RasterThreadMerger::Disable|.\n  void SetEnabledUnSafe(bool enabled);\n\n  // It's called by |RasterThreadMerger::DecrementLease|.\n  // See the doc of |RasterThreadMerger::DecrementLease|.\n  bool DecrementLease(RasterThreadMergerId caller);\n\n private:\n  fml::TaskQueueId owner_;\n  fml::TaskQueueId subsumed_;\n  fml::MessageLoopTaskQueues* task_queues_;\n  std::mutex mutex_;\n  bool enabled_;\n\n  /// The |MergeWithLease| or |ExtendLeaseTo| method will record the caller\n  /// into this lease_term_by_caller_ map, |UnMergeNowIfLastOne|\n  /// method will remove the caller from this lease_term_by_caller_.\n  std::map<RasterThreadMergerId, std::atomic_size_t> lease_term_by_caller_;\n\n  bool IsAllLeaseTermsZeroUnSafe() const;\n\n  bool UnMergeNowUnSafe();\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SharedThreadMerger);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::SharedThreadMerger;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_SHARED_THREAD_MERGER_H_\n"
  },
  {
    "path": "base/include/fml/synchronization/atomic_object.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_FML_SYNCHRONIZATION_ATOMIC_OBJECT_H_\n#define BASE_INCLUDE_FML_SYNCHRONIZATION_ATOMIC_OBJECT_H_\n\n#include <mutex>\n\nnamespace lynx {\nnamespace base {\n\n// A wrapper for an object instance that can be read or written atomically.\ntemplate <typename T>\nclass AtomicObject {\n public:\n  AtomicObject() = default;\n  explicit AtomicObject(T object) : object_(object) {}\n\n  T Load() const {\n    std::scoped_lock lock(mutex_);\n    return object_;\n  }\n\n  void Store(const T& object) {\n    std::scoped_lock lock(mutex_);\n    object_ = object;\n  }\n\n private:\n  mutable std::mutex mutex_;\n  T object_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_SYNCHRONIZATION_ATOMIC_OBJECT_H_\n"
  },
  {
    "path": "base/include/fml/synchronization/count_down_latch.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_SYNCHRONIZATION_COUNT_DOWN_LATCH_H_\n#define BASE_INCLUDE_FML_SYNCHRONIZATION_COUNT_DOWN_LATCH_H_\n\n#include <atomic>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass BASE_EXPORT CountDownLatch {\n public:\n  explicit CountDownLatch(size_t count);\n\n  ~CountDownLatch();\n\n  void Wait();\n\n  void CountDown();\n\n private:\n  std::atomic_size_t count_;\n  ManualResetWaitableEvent waitable_event_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(CountDownLatch);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::CountDownLatch;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_SYNCHRONIZATION_COUNT_DOWN_LATCH_H_\n"
  },
  {
    "path": "base/include/fml/synchronization/semaphore.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_FML_SYNCHRONIZATION_SEMAPHORE_H_\n#define BASE_INCLUDE_FML_SYNCHRONIZATION_SEMAPHORE_H_\n\n#include <memory>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass PlatformSemaphore;\n\n//------------------------------------------------------------------------------\n/// @brief      A traditional counting semaphore.  `Wait`s decrement the counter\n///             and `Signal` increments it.\n///\n///             This is a cross-platform replacement for std::counting_semaphore\n///             which is only available since C++20. Once Flutter migrates past\n///             that point, this class should become obsolete and must be\n///             replaced.\n///\nclass Semaphore {\n public:\n  //----------------------------------------------------------------------------\n  /// @brief      Initializes the counting semaphore to a specified start count.\n  ///\n  /// @warning    Callers must check if the handle could be successfully created\n  ///             by calling the `IsValid` method. `Wait`s on an invalid\n  ///             semaphore will always fail and signals will fail silently.\n  ///\n  /// @param[in]  count  The starting count of the counting semaphore.\n  ///\n  explicit Semaphore(uint32_t count);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Destroy the counting semaphore.\n  ///\n  ~Semaphore();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Check if the underlying semaphore handle could be created.\n  ///             Failure modes are platform specific and may occur due to issue\n  ///             like handle exhaustion. All `Wait`s on invalid semaphore\n  ///             handles will fail and `Signal` calls will be ignored.\n  ///\n  /// @return     True if valid, False otherwise.\n  ///\n  bool IsValid() const;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Decrements the count and waits indefinitely if the value is\n  ///             less than zero for a `Signal`.\n  ///\n  /// @return     If the `Wait` call was successful. See `IsValid` for failure.\n  ///\n  [[nodiscard]] bool Wait();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Decrement the counts if it is greater than zero. Returns false\n  ///             if the counter is already at zero.\n  ///\n  /// @warning    False is also returned if the semaphore handle is invalid.\n  ///             Which makes doing the validity check before this call doubly\n  ///             important.\n  ///\n  /// @return     If the count could be decrement.\n  ///\n  [[nodiscard]] bool TryWait();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Increment the count by one. Any pending `Wait`s will be\n  ///             resolved at this point.\n  ///\n  void Signal();\n\n private:\n  std::unique_ptr<PlatformSemaphore> _impl;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Semaphore);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::Semaphore;\n}\n\n#endif  // BASE_INCLUDE_FML_SYNCHRONIZATION_SEMAPHORE_H_\n"
  },
  {
    "path": "base/include/fml/synchronization/shared_mutex.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_FML_SYNCHRONIZATION_SHARED_MUTEX_H_\n#define BASE_INCLUDE_FML_SYNCHRONIZATION_SHARED_MUTEX_H_\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// Interface for a reader/writer lock.\nclass BASE_EXPORT SharedMutex {\n public:\n  static SharedMutex* Create();\n  virtual ~SharedMutex() = default;\n\n  virtual void Lock() = 0;\n  virtual void LockShared() = 0;\n  virtual void Unlock() = 0;\n  virtual void UnlockShared() = 0;\n};\n\n// RAII wrapper that does a shared acquire of a SharedMutex.\nclass SharedLock {\n public:\n  explicit SharedLock(SharedMutex& shared_mutex) : shared_mutex_(shared_mutex) {\n    shared_mutex_.LockShared();\n  }\n\n  ~SharedLock() { shared_mutex_.UnlockShared(); }\n\n private:\n  SharedMutex& shared_mutex_;\n};\n\n// RAII wrapper that does an exclusive acquire of a SharedMutex.\nclass UniqueLock {\n public:\n  explicit UniqueLock(SharedMutex& shared_mutex) : shared_mutex_(shared_mutex) {\n    shared_mutex_.Lock();\n  }\n\n  ~UniqueLock() { shared_mutex_.Unlock(); }\n\n private:\n  SharedMutex& shared_mutex_;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::SharedLock;\nusing lynx::fml::SharedMutex;\nusing lynx::fml::UniqueLock;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_SYNCHRONIZATION_SHARED_MUTEX_H_\n"
  },
  {
    "path": "base/include/fml/synchronization/shared_mutex_posix.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_FML_SYNCHRONIZATION_SHARED_MUTEX_POSIX_H_\n#define BASE_INCLUDE_FML_SYNCHRONIZATION_SHARED_MUTEX_POSIX_H_\n\n#include <shared_mutex>  // NOLINT\n\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass SharedMutexPosix : public SharedMutex {\n public:\n  virtual void Lock();\n  virtual void LockShared();\n  virtual void Unlock();\n  virtual void UnlockShared();\n\n private:\n  friend SharedMutex* SharedMutex::Create();\n  SharedMutexPosix();\n\n  pthread_rwlock_t rwlock_;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_SYNCHRONIZATION_SHARED_MUTEX_POSIX_H_\n"
  },
  {
    "path": "base/include/fml/synchronization/shared_mutex_std.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_FML_SYNCHRONIZATION_SHARED_MUTEX_STD_H_\n#define BASE_INCLUDE_FML_SYNCHRONIZATION_SHARED_MUTEX_STD_H_\n\n#include <shared_mutex>  // NOLINT\n\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass SharedMutexStd : public SharedMutex {\n public:\n  virtual void Lock();\n  virtual void LockShared();\n  virtual void Unlock();\n  virtual void UnlockShared();\n\n private:\n  friend SharedMutex* SharedMutex::Create();\n  SharedMutexStd() = default;\n\n  std::shared_timed_mutex mutex_;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_SYNCHRONIZATION_SHARED_MUTEX_STD_H_\n"
  },
  {
    "path": "base/include/fml/synchronization/sync_switch.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_FML_SYNCHRONIZATION_SYNC_SWITCH_H_\n#define BASE_INCLUDE_FML_SYNCHRONIZATION_SYNC_SWITCH_H_\n\n#include <forward_list>\n#include <functional>\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n\nnamespace lynx {\nnamespace fml {\n\n/// A threadsafe structure that allows you to switch between 2 different\n/// execution paths.\n///\n/// Execution and setting the switch is exclusive, i.e. only one will happen\n/// at a time.\nclass BASE_EXPORT SyncSwitch {\n public:\n  /// Represents the 2 code paths available when calling |SyncSwitch::Execute|.\n  struct Handlers {\n    /// Sets the handler that will be executed if the |SyncSwitch| is true.\n    Handlers& SetIfTrue(const std::function<void()>& handler);\n\n    /// Sets the handler that will be executed if the |SyncSwitch| is false.\n    Handlers& SetIfFalse(const std::function<void()>& handler);\n\n    std::function<void()> true_handler = [] {};\n    std::function<void()> false_handler = [] {};\n  };\n\n  /// Create a |SyncSwitch| with the specified value.\n  ///\n  /// @param[in]  value  Default value for the |SyncSwitch|.\n  explicit SyncSwitch(bool value = false);\n\n  /// Diverge execution between true and false values of the SyncSwitch.\n  ///\n  /// This can be called on any thread.  Note that attempting to call\n  /// |SetSwitch| inside of the handlers will result in a self deadlock.\n  ///\n  /// @param[in]  handlers  Called for the correct value of the |SyncSwitch|.\n  void Execute(const Handlers& handlers) const;\n\n  /// Set the value of the SyncSwitch.\n  ///\n  /// This can be called on any thread.\n  ///\n  /// @param[in]  value  New value for the |SyncSwitch|.\n  void SetSwitch(bool value);\n\n private:\n  mutable std::unique_ptr<fml::SharedMutex> mutex_;\n  bool value_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SyncSwitch);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::SyncSwitch;\n}\n\n#endif  // BASE_INCLUDE_FML_SYNCHRONIZATION_SYNC_SWITCH_H_\n"
  },
  {
    "path": "base/include/fml/synchronization/waitable_event.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// Provides classes with functionality analogous to (but much more limited than)\n// Chromium's |base::WaitableEvent|, which in turn provides functionality\n// analogous to Windows's Event. (Unlike these two, we have separate types for\n// the manual- and auto-reset versions.)\n\n#ifndef BASE_INCLUDE_FML_SYNCHRONIZATION_WAITABLE_EVENT_H_\n#define BASE_INCLUDE_FML_SYNCHRONIZATION_WAITABLE_EVENT_H_\n\n#include <condition_variable>\n#include <mutex>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_delta.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// AutoResetWaitableEvent ------------------------------------------------------\n\n// An event that can be signaled and waited on. This version automatically\n// returns to the unsignaled state after unblocking one waiter. (This is similar\n// to Windows's auto-reset Event, which is also imitated by Chromium's\n// auto-reset |base::WaitableEvent|. However, there are some limitations -- see\n// |Signal()|.) This class is thread-safe.\nclass BASE_EXPORT AutoResetWaitableEvent final {\n public:\n  AutoResetWaitableEvent() {}\n  ~AutoResetWaitableEvent() {}\n\n  // Put the event in the signaled state. Exactly one |Wait()| will be unblocked\n  // and the event will be returned to the unsignaled state.\n  //\n  // Notes (these are arguably bugs, but not worth working around):\n  // * That |Wait()| may be one that occurs on the calling thread, *after* the\n  //   call to |Signal()|.\n  // * A |Signal()|, followed by a |Reset()|, may cause *no* waiting thread to\n  //   be unblocked.\n  // * We rely on pthreads's queueing for picking which waiting thread to\n  //   unblock, rather than enforcing FIFO ordering.\n  void Signal();\n\n  // Put the event into the unsignaled state. Generally, this is not recommended\n  // on an auto-reset event (see notes above).\n  void Reset();\n\n  // Blocks the calling thread until the event is signaled. Upon unblocking, the\n  // event is returned to the unsignaled state, so that (unless |Reset()| is\n  // called) each |Signal()| unblocks exactly one |Wait()|.\n  void Wait();\n\n  // Like |Wait()|, but with a timeout. Also unblocks if |timeout| expires\n  // without being signaled in which case it returns true (otherwise, it returns\n  // false).\n  bool WaitWithTimeout(TimeDelta timeout);\n\n  // Returns whether this event is in a signaled state or not. For use in tests\n  // only (in general, this is racy). Note: Unlike\n  // |base::WaitableEvent::IsSignaled()|, this doesn't reset the signaled state.\n  bool IsSignaledForTest();\n\n private:\n  std::condition_variable cv_;\n  std::mutex mutex_;\n\n  // True if this event is in the signaled state.\n  bool signaled_ = false;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(AutoResetWaitableEvent);\n};\n\n// ManualResetWaitableEvent ----------------------------------------------------\n\n// An event that can be signaled and waited on. This version remains signaled\n// until explicitly reset. (This is similar to Windows's manual-reset Event,\n// which is also imitated by Chromium's manual-reset |base::WaitableEvent|.)\n// This class is thread-safe.\nclass ManualResetWaitableEvent final {\n public:\n  ManualResetWaitableEvent() {}\n  ~ManualResetWaitableEvent() {}\n\n  // Put the event into the unsignaled state.\n  void Reset();\n\n  // Put the event in the signaled state. If this is a manual-reset event, it\n  // wakes all waiting threads (blocked on |Wait()| or |WaitWithTimeout()|).\n  // Otherwise, it wakes a single waiting thread (and returns to the unsignaled\n  // state), if any; if there are none, it remains signaled.\n  void Signal();\n\n  // Blocks the calling thread until the event is signaled.\n  void Wait();\n\n  // Like |Wait()|, but with a timeout. Also unblocks if |timeout| expires\n  // without being signaled in which case it returns true (otherwise, it returns\n  // false).\n  bool WaitWithTimeout(TimeDelta timeout);\n\n  // Returns whether this event is in a signaled state or not. For use in tests\n  // only (in general, this is racy).\n  bool IsSignaledForTest();\n\n private:\n  std::condition_variable cv_;\n  std::mutex mutex_;\n\n  // True if this event is in the signaled state.\n  bool signaled_ = false;\n\n  // While |std::condition_variable::notify_all()| (|pthread_cond_broadcast()|)\n  // will wake all waiting threads, one has to deal with spurious wake-ups.\n  // Checking |signaled_| isn't sufficient, since another thread may have been\n  // awoken and (manually) reset |signaled_|. This is a counter that is\n  // incremented in |Signal()| before calling\n  // |std::condition_variable::notify_all()|. A waiting thread knows it was\n  // awoken if |signal_id_| is different from when it started waiting.\n  unsigned signal_id_ = 0u;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ManualResetWaitableEvent);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::AutoResetWaitableEvent;\nusing lynx::fml::ManualResetWaitableEvent;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_SYNCHRONIZATION_WAITABLE_EVENT_H_\n"
  },
  {
    "path": "base/include/fml/task_queue_id.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TASK_QUEUE_ID_H_\n#define BASE_INCLUDE_FML_TASK_QUEUE_ID_H_\n\n#include <climits>\n\nnamespace lynx {\nnamespace fml {\n\n/**\n * `MessageLoopTaskQueues` task dispatcher's internal task queue identifier.\n */\nclass TaskQueueId {\n public:\n  /// This constant indicates whether a task queue has been subsumed by a task\n  /// runner.\n  static constexpr size_t kUnmerged = ULONG_MAX;\n\n  /// Intializes a task queue with the given value as it's ID.\n  explicit constexpr TaskQueueId(size_t value) : value_(value) {}\n\n  constexpr operator size_t() const {  // NOLINT(google-explicit-constructor)\n    return value_;\n  }\n\n  std::size_t hash() const { return std::hash<size_t>()(value_); }\n\n private:\n  size_t value_ = kUnmerged;\n};\n\nconstexpr TaskQueueId _kUnmerged = TaskQueueId(TaskQueueId::kUnmerged);\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::TaskQueueId;\n}  // namespace fml\n\nnamespace std {\ntemplate <>\nstruct hash<lynx::fml::TaskQueueId> {\n  std::size_t operator()(const lynx::fml::TaskQueueId& k) const {\n    return k.hash();\n  }\n};\n}  // namespace std\n\n#endif  // BASE_INCLUDE_FML_TASK_QUEUE_ID_H_\n"
  },
  {
    "path": "base/include/fml/task_runner.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TASK_RUNNER_H_\n#define BASE_INCLUDE_FML_TASK_RUNNER_H_\n\n#include <memory>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/closure.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"base/include/fml/time/time_point.h\"\nnamespace lynx {\nnamespace fml {\n\nclass MessageLoopImpl;\nclass TaskRunnerDelegate;\n\n/// An interface over the ability to schedule tasks on a \\p TaskRunner.\nclass BasicTaskRunner {\n public:\n  /// Schedules \\p task to be executed on the TaskRunner's associated event\n  /// loop.\n  virtual void PostTask(base::closure task) = 0;\n};\n\n/// The object for scheduling tasks on a \\p fml::MessageLoop.\n///\n/// Typically there is one \\p TaskRunner associated with each thread.  When one\n/// wants to execute an operation on that thread they post a task to the\n/// TaskRunner.\n///\n/// \\see fml::MessageLoop\nclass BASE_EXPORT TaskRunner : public fml::RefCountedThreadSafe<TaskRunner>,\n                               public BasicTaskRunner {\n public:\n  virtual ~TaskRunner();\n\n  virtual void PostTask(base::closure task) override;\n\n  virtual void PostTaskForTime(base::closure task, fml::TimePoint target_time);\n\n  /// Schedules a task to be run on the MessageLoop after the time \\p delay has\n  /// passed.\n  /// \\note There is latency between when the task is schedule and actually\n  /// executed so that the actual execution time is: now + delay +\n  /// message_loop_latency, where message_loop_latency is undefined and could be\n  /// tens of milliseconds.\n  virtual void PostDelayedTask(base::closure task, fml::TimeDelta delay);\n\n  /// Returns \\p true when the current executing thread's TaskRunner matches\n  /// this instance.\n  virtual bool RunsTasksOnCurrentThread();\n\n  /// Returns the unique identifier associated with the TaskRunner.\n  /// \\see fml::MessageLoopTaskQueues\n  virtual TaskQueueId GetTaskQueueId();\n\n  void PostEmergencyTask(base::closure task);\n\n  void PostMicroTask(base::closure task);\n\n  // Schedules a task in the idle period.\n  // TODO(heshan):now this method just schedules a lowest priority task,\n  // and will implement as web standard in the future.\n  // https://w3c.github.io/requestidlecallback/#the-requestidlecallback-method\n  void PostIdleTask(base::closure task);\n\n  void PostSyncTask(base::closure task);\n\n  /// Executes the \\p task directly if the TaskRunner \\p runner is the\n  /// TaskRunner associated with the current executing thread.\n  static void RunNowOrPostTask(const fml::RefPtr<fml::TaskRunner>& runner,\n                               base::closure task);\n  static void RunNowOrPostTask(const std::shared_ptr<fml::TaskRunner>& runner,\n                               base::closure task);\n\n  // WARN: This can only be safely called on Android. On other platforms,\n  // sometimes `loop_` is null (e.g. in case of using `EmbedderTaskRunner`).\n  void AddTaskObserver(intptr_t key, base::closure callback);\n\n  void RemoveTaskObserver(intptr_t key);\n\n  void Bind(fml::RefPtr<MessageLoopImpl> target_loop,\n            bool should_run_expired_tasks_immediately = false);\n\n  void UnBind();\n\n  const fml::RefPtr<MessageLoopImpl>& GetLoop() const;\n\n  void SetDelegate(lynx::fml::TaskRunnerDelegate* delegate);\n\n protected:\n  explicit TaskRunner(fml::RefPtr<MessageLoopImpl> loop,\n                      bool is_aligned_with_vsync = false);\n  fml::RefPtr<MessageLoopImpl> loop_;\n\n private:\n  TaskQueueId queue_id_;\n  std::shared_ptr<bool> unbound_ = std::make_shared<bool>(false);\n  fml::TaskRunnerDelegate* delegate_ = nullptr;\n\n  void BindOnCreate(fml::RefPtr<MessageLoopImpl> loop);\n\n  FML_FRIEND_MAKE_REF_COUNTED(TaskRunner);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(TaskRunner);\n  BASE_DISALLOW_COPY_AND_ASSIGN(TaskRunner);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::BasicTaskRunner;\nusing lynx::fml::TaskRunner;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_TASK_RUNNER_H_\n"
  },
  {
    "path": "base/include/fml/task_runner_delegate.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TASK_RUNNER_DELEGATE_H_\n#define BASE_INCLUDE_FML_TASK_RUNNER_DELEGATE_H_\n\n#include \"base/include/base_export.h\"\n#include \"base/include/closure.h\"\n\nnamespace lynx {\nnamespace fml {\nclass BASE_EXPORT TaskRunnerDelegate {\n public:\n  TaskRunnerDelegate() = default;\n  ~TaskRunnerDelegate() = default;\n  virtual void PostTask(lynx::base::closure task) {}\n\n  virtual void PostTaskForTime(lynx::base::closure task, int64_t target_time) {}\n\n  virtual void PostDelayedTask(lynx::base::closure task, int64_t delay) {}\n\n  virtual bool RunsTasksOnCurrentThread() { return false; }\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_TASK_RUNNER_DELEGATE_H_\n"
  },
  {
    "path": "base/include/fml/task_source.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TASK_SOURCE_H_\n#define BASE_INCLUDE_FML_TASK_SOURCE_H_\n\n#include <list>\n#include <queue>\n\n#include \"base/include/fml/delayed_task.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/task_queue_id.h\"\n#include \"base/include/fml/task_source_grade.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass MessageLoopTaskQueues;\n\n/**\n * A Source of tasks for the `MessageLoopTaskQueues` task dispatcher. This is a\n * wrapper around a primary and secondary task heap with the difference between\n * them being that the secondary task heap can be paused and resumed by the task\n * dispatcher. `TaskSourceGrade` determines what task heap the task is assigned\n * to.\n *\n * Registering Tasks\n * -----------------\n * The task dispatcher associates a task source with each `TaskQueueID`. When\n * the user of the task dispatcher registers a task, the task is in-turn\n * registered with the `TaskSource` corresponding to the `TaskQueueID`.\n *\n * Processing Tasks\n * ----------------\n * Task dispatcher provides the event loop a way to acquire tasks to run via\n * `GetNextTaskToRun`. Task dispatcher asks the underlying `TaskSource` for the\n * next task.\n */\nclass TaskSource {\n public:\n  struct TopTask {\n    TaskQueueId task_queue_id;\n    const DelayedTask& task;\n  };\n\n  /// Construts a TaskSource with the given `task_queue_id`.\n  explicit TaskSource(TaskQueueId task_queue_id);\n\n  ~TaskSource();\n\n  /// Drops the pending tasks from both primary and secondary task heaps.\n  void ShutDown();\n\n  /// Adds a task to the corresponding task heap as dictated by the\n  /// `TaskSourceGrade` of the `DelayedTask`.\n  void RegisterTask(DelayedTask task);\n\n  /// Pops the task heap corresponding to the `TaskSourceGrade`.\n  void PopTask(TaskSourceGrade grade);\n\n  /// Returns the number of pending tasks. Excludes the tasks from the secondary\n  /// heap if it's paused.\n  size_t GetNumPendingTasks() const;\n\n  /// Returns true if `GetNumPendingTasks` is zero.\n  bool IsEmpty() const;\n\n  /// Returns the top task based on scheduled time, taking into account whether\n  /// the secondary heap has been paused or not.\n  TopTask Top() const;\n\n  /// Returns the top task or nullptr.\n  const DelayedTask* TopOrNull() const;\n\n private:\n  const fml::TaskQueueId task_queue_id_;\n  fml::DelayedTaskQueue primary_task_queue_;\n  fml::DelayedTaskQueue emergency_task_queue_;\n  fml::DelayedTaskQueue micro_task_queue_;\n  // we not care about the target time of idle tasks, just FIFO is enough.\n  std::queue<DelayedTask, std::list<DelayedTask>> idle_task_queue_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(TaskSource);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_TASK_SOURCE_H_\n"
  },
  {
    "path": "base/include/fml/task_source_grade.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TASK_SOURCE_GRADE_H_\n#define BASE_INCLUDE_FML_TASK_SOURCE_GRADE_H_\n\nnamespace lynx {\nnamespace fml {\n\n/**\n * Categories of work dispatched to `MessageLoopTaskQueues` dispatcher. By\n * specifying the `TaskSourceGrade`, you indicate the task's importance to the\n * dispatcher.\n */\nenum class TaskSourceGrade {\n  /// This `TaskSourceGrade` indicates that a task is critical to user\n  /// interaction.\n  kUserInteraction,\n  /// The absence of a specialized `TaskSourceGrade`.\n  kUnspecified,\n  /// This `TaskSourceGrade` indicates that a task is urgent to execute.\n  kEmergency,\n\n  /// This `TaskSourceGrade` indicates that a task is a microtask.\n  /// Note: this is only for the JS thread and is used to simulate microtasks.\n  /// In the js thread, this level is the highest priority.\n  kMicrotask,\n  /// This `TaskSourceGrade` indicates that a task just executes when idle.\n  kIdle,\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_TASK_SOURCE_GRADE_H_\n"
  },
  {
    "path": "base/include/fml/thread.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_THREAD_H_\n#define BASE_INCLUDE_FML_THREAD_H_\n\n#include <atomic>\n#include <functional>\n#include <memory>\n#include <string>\n#include <thread>\n#include <utility>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/task_runner.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass ThreadHandle;\n\nclass BASE_EXPORT Thread {\n public:\n  /// Valid values for priority of Thread.\n  enum class ThreadPriority : int {\n    /// Suitable for threads that shouldn't disrupt high priority work.\n    /// The default background priority is the same as low, you can adjust it\n    /// with the ThreadConfigSetter defined by yourself\n    BACKGROUND,\n    /// Suitable for threads that shouldn't disrupt high priority work.\n    LOW,\n    /// Default priority level.\n    NORMAL,\n    /// Suitable for threads which execute for runtime engine、layout\n    /// engine、template render.\n    HIGH,\n  };\n\n  /// The ThreadConfig is the thread info include thread name, thread priority.\n  struct ThreadConfig {\n    ThreadConfig(const std::string& name, ThreadPriority priority,\n                 std::shared_ptr<base::closure> additional_setup_closure)\n        : name(name),\n          priority(priority),\n          additional_setup_closure(std::move(additional_setup_closure)) {}\n\n    ThreadConfig(const std::string& name, ThreadPriority priority)\n        : ThreadConfig(name, priority, nullptr) {}\n\n    explicit ThreadConfig(const std::string& name)\n        : ThreadConfig(name, ThreadPriority::NORMAL, nullptr) {}\n\n    ThreadConfig() : ThreadConfig(\"\", ThreadPriority::NORMAL, nullptr) {}\n\n    std::string name;\n    ThreadPriority priority;\n    std::shared_ptr<base::closure>\n        additional_setup_closure;  // thread config should be copyable\n  };\n\n  using ThreadConfigSetter = std::function<void(const ThreadConfig&)>;\n\n  explicit Thread(const std::string& name = \"\");\n\n  explicit Thread(const ThreadConfig& config);\n\n  explicit Thread(const ThreadConfigSetter& setter,\n                  const ThreadConfig& config = ThreadConfig());\n\n  ~Thread();\n\n  const fml::RefPtr<fml::TaskRunner>& GetTaskRunner() const;\n\n  void Join();\n\n  const fml::RefPtr<fml::MessageLoopImpl>& GetLoop() const;\n\n  static void SetCurrentThreadName(const ThreadConfig& config);\n\n  static size_t GetDefaultStackSize();\n\n private:\n  std::unique_ptr<ThreadHandle> thread_;\n\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n\n  fml::RefPtr<fml::MessageLoopImpl> loop_;\n\n  std::atomic_bool joined_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Thread);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::Thread;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_THREAD_H_\n"
  },
  {
    "path": "base/include/fml/thread_host.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_THREAD_HOST_H_\n#define BASE_INCLUDE_FML_THREAD_HOST_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/thread.h\"\n\nnamespace lynx {\n\nusing ThreadConfig = fml::Thread::ThreadConfig;\nusing ThreadConfigSetter = fml::Thread::ThreadConfigSetter;\n\n/// The collection of all the threads used by the engine.\nstruct ThreadHost {\n  enum Type {\n    Platform = 1 << 0,\n    UI = 1 << 1,\n    RASTER = 1 << 2,\n    IO = 1 << 3,\n    Profiler = 1 << 4,\n  };\n\n  /// The collection of all the thread configures, and we create custom thread\n  /// configure in engine to info the thread.\n  struct ThreadHostConfig {\n    explicit ThreadHostConfig(\n        const ThreadConfigSetter& setter = fml::Thread::SetCurrentThreadName)\n        : type_mask(0), config_setter(setter) {}\n\n    ThreadHostConfig(\n        const std::string& name_prefix, uint64_t mask,\n        const ThreadConfigSetter& setter = fml::Thread::SetCurrentThreadName)\n        : type_mask(mask), name_prefix(name_prefix), config_setter(setter) {}\n\n    explicit ThreadHostConfig(\n        uint64_t mask,\n        const ThreadConfigSetter& setter = fml::Thread::SetCurrentThreadName)\n        : ThreadHostConfig(\"\", mask, setter) {}\n\n    /// Check if need to create thread.\n    bool isThreadNeeded(Type type) const { return type_mask & type; }\n\n    /// Use the prefix and thread type to generator a thread name.\n    static std::string MakeThreadName(Type type, const std::string& prefix);\n\n    /// Specified the UI Thread Config, meanwhile set the mask.\n    void SetUIConfig(const ThreadConfig&);\n\n    /// Specified the Platform Thread Config, meanwhile set the mask.\n    void SetPlatformConfig(const ThreadConfig&);\n\n    /// Specified the IO Thread Config, meanwhile set the mask.\n    void SetRasterConfig(const ThreadConfig&);\n\n    /// Specified the IO Thread Config, meanwhile set the mask.\n    void SetIOConfig(const ThreadConfig&);\n\n    /// Specified the ProfilerThread  Config, meanwhile set the mask.\n    void SetProfilerConfig(const ThreadConfig&);\n\n    uint64_t type_mask;\n\n    std::string name_prefix = \"\";\n\n    const ThreadConfigSetter config_setter;\n\n    std::optional<ThreadConfig> platform_config;\n    std::optional<ThreadConfig> ui_config;\n    std::optional<ThreadConfig> raster_config;\n    std::optional<ThreadConfig> io_config;\n    std::optional<ThreadConfig> profiler_config;\n  };\n\n  std::string name_prefix;\n  std::unique_ptr<fml::Thread> platform_thread;\n  std::unique_ptr<fml::Thread> ui_thread;\n  std::unique_ptr<fml::Thread> raster_thread;\n  std::unique_ptr<fml::Thread> io_thread;\n  std::unique_ptr<fml::Thread> profiler_thread;\n\n  ThreadHost();\n\n  ThreadHost(ThreadHost&&);\n\n  ThreadHost& operator=(ThreadHost&&) = default;\n\n  ThreadHost(const std::string name_prefix, uint64_t mask);\n\n  explicit ThreadHost(const ThreadHostConfig& host_config);\n\n  ~ThreadHost();\n\n private:\n  std::unique_ptr<fml::Thread> CreateThread(\n      Type type, std::optional<ThreadConfig> thread_config,\n      const ThreadHostConfig& host_config) const;\n\n public:\n  static lynx::ThreadHost CreateThreadHost(const std::string& name_prefix) {\n    fml::Thread::SetCurrentThreadName(\n        fml::Thread::ThreadConfig(name_prefix + \".platform\"));\n\n    return lynx::ThreadHost(name_prefix, lynx::ThreadHost::Type::Platform |\n                                             lynx::ThreadHost::Type::RASTER |\n                                             lynx::ThreadHost::Type::UI |\n                                             lynx::ThreadHost::Type::IO);\n  }\n\n  /// Inheriting ThreadConfigurer and use Android platform thread API to\n  /// configure the thread priorities\n  static void PlatformThreadConfigSetter(\n      const fml::Thread::ThreadConfig& config);\n};\n\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_FML_THREAD_HOST_H_\n"
  },
  {
    "path": "base/include/fml/time/chrono_timestamp_provider.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TIME_CHRONO_TIMESTAMP_PROVIDER_H_\n#define BASE_INCLUDE_FML_TIME_CHRONO_TIMESTAMP_PROVIDER_H_\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/fml/time/timestamp_provider.h\"\n\nnamespace lynx {\nnamespace fml {\n\n/// TimestampProvider implementation that is backed by std::chrono::steady_clock\n/// meant to be used only in tests for `fml`. Other components needing the\n/// current time ticks since epoch should instantiate their own time stamp\n/// provider backed by Dart clock.\nclass ChronoTimestampProvider : TimestampProvider {\n public:\n  static ChronoTimestampProvider& Instance() {\n    static ChronoTimestampProvider instance;\n    return instance;\n  }\n\n  ~ChronoTimestampProvider() override;\n\n  fml::TimePoint Now() override;\n\n private:\n  ChronoTimestampProvider();\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ChronoTimestampProvider);\n};\n\nfml::TimePoint ChronoTicksSinceEpoch();\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::ChronoTicksSinceEpoch;\nusing lynx::fml::ChronoTimestampProvider;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_TIME_CHRONO_TIMESTAMP_PROVIDER_H_\n"
  },
  {
    "path": "base/include/fml/time/time_delta.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TIME_TIME_DELTA_H_\n#define BASE_INCLUDE_FML_TIME_TIME_DELTA_H_\n\n#include <chrono>\n#include <cstdint>\n#include <ctime>\n#include <iosfwd>\n#include <limits>\n\nnamespace lynx {\nnamespace fml {\n\n// A TimeDelta represents the difference between two time points.\nclass TimeDelta {\n public:\n  constexpr TimeDelta() = default;\n\n  static constexpr TimeDelta Zero() { return TimeDelta(); }\n  static constexpr TimeDelta Min() {\n    return TimeDelta(std::numeric_limits<int64_t>::min());\n  }\n  static constexpr TimeDelta Max() {\n    return TimeDelta(std::numeric_limits<int64_t>::max());\n  }\n  static constexpr TimeDelta FromNanoseconds(int64_t nanos) {\n    return TimeDelta(nanos);\n  }\n  static constexpr TimeDelta FromMicroseconds(int64_t micros) {\n    return FromNanoseconds(micros * 1000);\n  }\n  static constexpr TimeDelta FromMilliseconds(int64_t millis) {\n    return FromMicroseconds(millis * 1000);\n  }\n  static constexpr TimeDelta FromSeconds(int64_t seconds) {\n    return FromMilliseconds(seconds * 1000);\n  }\n\n  static constexpr TimeDelta FromSecondsF(double seconds) {\n    return FromNanoseconds(seconds * (1000.0 * 1000.0 * 1000.0));\n  }\n\n  static constexpr TimeDelta FromMillisecondsF(double millis) {\n    return FromNanoseconds(millis * (1000.0 * 1000.0));\n  }\n\n  constexpr int64_t ToNanoseconds() const { return delta_; }\n  constexpr int64_t ToMicroseconds() const { return ToNanoseconds() / 1000; }\n  constexpr int64_t ToMilliseconds() const { return ToMicroseconds() / 1000; }\n  constexpr int64_t ToSeconds() const { return ToMilliseconds() / 1000; }\n\n  constexpr double ToNanosecondsF() const { return delta_; }\n  constexpr double ToMicrosecondsF() const { return delta_ / 1000.0; }\n  constexpr double ToMillisecondsF() const {\n    return delta_ / (1000.0 * 1000.0);\n  }\n  constexpr double ToSecondsF() const {\n    return delta_ / (1000.0 * 1000.0 * 1000.0);\n  }\n\n  constexpr TimeDelta operator-(TimeDelta other) const {\n    return TimeDelta::FromNanoseconds(delta_ - other.delta_);\n  }\n\n  constexpr TimeDelta operator+(TimeDelta other) const {\n    return TimeDelta::FromNanoseconds(delta_ + other.delta_);\n  }\n\n  constexpr TimeDelta operator/(int64_t divisor) const {\n    return TimeDelta::FromNanoseconds(delta_ / divisor);\n  }\n\n  constexpr int64_t operator/(TimeDelta other) const {\n    return delta_ / other.delta_;\n  }\n\n  constexpr TimeDelta operator*(int64_t multiplier) const {\n    return TimeDelta::FromNanoseconds(delta_ * multiplier);\n  }\n\n  constexpr TimeDelta operator*(double multiplier) const {\n    return TimeDelta::FromNanoseconds(delta_ * multiplier);\n  }\n\n  constexpr TimeDelta operator%(TimeDelta other) const {\n    return TimeDelta::FromNanoseconds(delta_ % other.delta_);\n  }\n\n  bool operator==(TimeDelta other) const { return delta_ == other.delta_; }\n  bool operator!=(TimeDelta other) const { return delta_ != other.delta_; }\n  bool operator<(TimeDelta other) const { return delta_ < other.delta_; }\n  bool operator<=(TimeDelta other) const { return delta_ <= other.delta_; }\n  bool operator>(TimeDelta other) const { return delta_ > other.delta_; }\n  bool operator>=(TimeDelta other) const { return delta_ >= other.delta_; }\n\n  static constexpr TimeDelta FromTimespec(struct timespec ts) {\n    return TimeDelta::FromSeconds(ts.tv_sec) +\n           TimeDelta::FromNanoseconds(ts.tv_nsec);\n  }\n  struct timespec ToTimespec() {\n    struct timespec ts;\n    constexpr int64_t kNanosecondsPerSecond = 1000000000ll;\n    ts.tv_sec = static_cast<time_t>(ToSeconds());\n    ts.tv_nsec = delta_ % kNanosecondsPerSecond;\n    return ts;\n  }\n\n private:\n  // Private, use one of the FromFoo() types\n  explicit constexpr TimeDelta(int64_t delta) : delta_(delta) {}\n\n  int64_t delta_ = 0;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\n\n// NOLINTNEXTLINE\nusing namespace std::chrono_literals;\n\nusing Milliseconds = std::chrono::duration<double, std::milli>;\n\n// Default to 60fps.\nconstexpr Milliseconds kDefaultFrameBudget = Milliseconds(1s) / 60;\n\ntemplate <typename T>\nMilliseconds RefreshRateToFrameBudget(T refresh_rate) {\n  return Milliseconds(1s) / refresh_rate;\n}\n\nusing lynx::fml::TimeDelta;\n\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_TIME_TIME_DELTA_H_\n"
  },
  {
    "path": "base/include/fml/time/time_point.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TIME_TIME_POINT_H_\n#define BASE_INCLUDE_FML_TIME_TIME_POINT_H_\n\n#include <cstdint>\n#include <iosfwd>\n#include <limits>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/time/time_delta.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// A TimePoint represents a point in time represented as an integer number of\n// nanoseconds elapsed since an arbitrary point in the past.\n//\n// WARNING: This class should not be serialized across reboots, or across\n// devices: the reference point is only stable for a given device between\n// reboots.\nclass BASE_EXPORT TimePoint {\n public:\n  // Default TimePoint with internal value 0 (epoch).\n  constexpr TimePoint() = default;\n\n  static TimePoint Now();\n\n  static TimePoint CurrentWallTime();\n\n  static constexpr TimePoint Min() {\n    return TimePoint(std::numeric_limits<int64_t>::min());\n  }\n\n  static constexpr TimePoint Max() {\n    return TimePoint(std::numeric_limits<int64_t>::max());\n  }\n\n  static constexpr TimePoint FromEpochDelta(TimeDelta ticks) {\n    return TimePoint(ticks.ToNanoseconds());\n  }\n\n  // Expects ticks in nanos.\n  static constexpr TimePoint FromTicks(int64_t ticks) {\n    return TimePoint(ticks);\n  }\n\n  TimeDelta ToEpochDelta() const { return TimeDelta::FromNanoseconds(ticks_); }\n\n  // Compute the difference between two time points.\n  TimeDelta operator-(TimePoint other) const {\n    return TimeDelta::FromNanoseconds(ticks_ - other.ticks_);\n  }\n\n  TimePoint operator+(TimeDelta duration) const {\n    return TimePoint(ticks_ + duration.ToNanoseconds());\n  }\n  TimePoint operator-(TimeDelta duration) const {\n    return TimePoint(ticks_ - duration.ToNanoseconds());\n  }\n\n  bool operator==(TimePoint other) const { return ticks_ == other.ticks_; }\n  bool operator!=(TimePoint other) const { return ticks_ != other.ticks_; }\n  bool operator<(TimePoint other) const { return ticks_ < other.ticks_; }\n  bool operator<=(TimePoint other) const { return ticks_ <= other.ticks_; }\n  bool operator>(TimePoint other) const { return ticks_ > other.ticks_; }\n  bool operator>=(TimePoint other) const { return ticks_ >= other.ticks_; }\n\n  static int64_t Dart_TimelineGetMicros() {\n    return fml::TimePoint::Now().ToEpochDelta().ToMicroseconds();\n  }\n\n private:\n  explicit constexpr TimePoint(int64_t ticks) : ticks_(ticks) {}\n\n  int64_t ticks_ = 0;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::TimePoint;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_TIME_TIME_POINT_H_\n"
  },
  {
    "path": "base/include/fml/time/timer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TIME_TIMER_H_\n#define BASE_INCLUDE_FML_TIME_TIMER_H_\n\n#include <functional>\n#include <utility>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/time/time_delta.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// Timer util based to fml::TaskRunner. NOT thread-safe.\n// As most functionality of oneshot timer can be substituted by PostDelayTask,\n// but repeating tasks or cancelable oneshot task are not so convenient to do.\n// So here provide a simple repeating / oneshot cancalable timer for caret\n// twinkling or swiper autoplay and so on.\n// NOTE: The actual time delay may giant than expected, and it won't be fixed.\nclass BASE_EXPORT Timer : public EnableWeakFromThis<Timer> {\n public:\n  // Task runner should be same with calling thread.\n  Timer(fml::RefPtr<fml::TaskRunner> task_runner, bool repeat)\n      : task_runner_(std::move(task_runner)), repeating_(repeat) {}\n\n  virtual ~Timer() { Stop(); }\n\n  using Task = std::function<void()>;\n\n  // Start a timer with delay.\n  // First task will be fired after |delay| rather than right now.\n  // It can be called multiple times. Once called, the previous scheduled task\n  // will be invalidated and delay time will be reset.\n  void Start(fml::TimeDelta delay, Task task);\n\n  // Whence stopped, all scheduled tasks are invalidated.\n  void Stop();\n\n  bool Stopped() const { return !running_; }\n\n protected:\n  void RunUserTask();\n  void ResetState();\n\n private:\n  void AbandonScheduledTasks();\n\n  void ScheduleNewTask();\n\n private:\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n  const bool repeating_ = true;\n  Task user_task_;\n  fml::TimeDelta delay_;\n\n  // Used for invalidate scheduled tasks.\n  // Not 100% safe if there're more than numeric_limits<uint64_t>::max() tasks\n  // scheduled within one period of delay.\n  uint64_t validator_ = 0;\n  bool running_ = false;\n};\n\nclass RepeatingTimer : public Timer {\n public:\n  explicit RepeatingTimer(fml::RefPtr<fml::TaskRunner> task_runner)\n      : Timer(task_runner, true) {}\n};\n\nclass BASE_EXPORT OneshotTimer : public Timer {\n public:\n  explicit OneshotTimer(fml::RefPtr<fml::TaskRunner> task_runner)\n      : Timer(task_runner, false) {}\n\n  // Fire now if task has not been fired ever. After fire, timer will be reset.\n  bool FireImmediately();\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::OneshotTimer;\nusing lynx::fml::RepeatingTimer;\nusing lynx::fml::Timer;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_TIME_TIMER_H_\n"
  },
  {
    "path": "base/include/fml/time/timestamp_provider.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_TIME_TIMESTAMP_PROVIDER_H_\n#define BASE_INCLUDE_FML_TIME_TIMESTAMP_PROVIDER_H_\n\n#include <cstdint>\n\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace lynx {\nnamespace fml {\n\n/// Pluggable provider of monotonic timestamps. Invocations of `Now` must return\n/// unique values. Any two consecutive invocations must be ordered.\nclass TimestampProvider {\n public:\n  virtual ~TimestampProvider(){};\n\n  // Returns the number of ticks elapsed by a monotonic clock since epoch.\n  virtual fml::TimePoint Now() = 0;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::TimestampProvider;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_TIME_TIMESTAMP_PROVIDER_H_\n"
  },
  {
    "path": "base/include/fml/unique_fd.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_UNIQUE_FD_H_\n#define BASE_INCLUDE_FML_UNIQUE_FD_H_\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/unique_object.h\"\n#include \"build/build_config.h\"\n\n#if OS_WIN\n#include <windows.h>\n\n#include <map>\n#include <mutex>\n#include <optional>\n#include <string>\n#else  // OS_WIN\n#include <dirent.h>\n#include <unistd.h>\n\n#include <map>\n#endif  // OS_WIN\n\nnamespace lynx {\nnamespace fml {\nnamespace internal {\n\n#if OS_WIN\n\nnamespace os_win {\n\nstruct DirCacheEntry {\n  std::wstring filename;\n  FILE_ID_128 id;\n};\n\n// The order of these is important.  Must come before UniqueFDTraits struct\n// else linker error.  Embedding in struct also causes linker error.\n\nstruct UniqueFDTraits {\n  static std::mutex file_map_mutex;\n  static std::map<HANDLE, DirCacheEntry> file_map;\n\n  static HANDLE InvalidValue() { return INVALID_HANDLE_VALUE; }\n  static bool IsValid(HANDLE value) { return value != InvalidValue(); }\n  static void Free_Handle(HANDLE fd);\n\n  static void Free(HANDLE fd) {\n    RemoveCacheEntry(fd);\n\n    UniqueFDTraits::Free_Handle(fd);\n  }\n\n  static void RemoveCacheEntry(HANDLE fd) {\n    const std::lock_guard<std::mutex> lock(file_map_mutex);\n\n    file_map.erase(fd);\n  }\n\n  static void StoreCacheEntry(HANDLE fd, DirCacheEntry state) {\n    const std::lock_guard<std::mutex> lock(file_map_mutex);\n    file_map[fd] = state;\n  }\n\n  static std::optional<DirCacheEntry> GetCacheEntry(HANDLE fd) {\n    const std::lock_guard<std::mutex> lock(file_map_mutex);\n    auto found = file_map.find(fd);\n    return found == file_map.end()\n               ? std::nullopt\n               : std::optional<DirCacheEntry>{found->second};\n  }\n};\n\n}  // namespace os_win\n\n#else  // OS_WIN\n\nnamespace os_unix {\n\nstruct BASE_EXPORT UniqueFDTraits {\n  static int InvalidValue() { return -1; }\n  static bool IsValid(int value) { return value >= 0; }\n  static void Free(int fd);\n};\n\nstruct BASE_EXPORT UniqueDirTraits {\n  static DIR* InvalidValue() { return nullptr; }\n  static bool IsValid(DIR* value) { return value != nullptr; }\n  static void Free(DIR* dir);\n};\n\n}  // namespace os_unix\n\n#endif  // OS_WIN\n\n}  // namespace internal\n\n#if OS_WIN\n\nusing UniqueFD = UniqueObject<HANDLE, internal::os_win::UniqueFDTraits>;\n\n#else  // OS_WIN\n\nusing UniqueFD = UniqueObject<int, internal::os_unix::UniqueFDTraits>;\nusing UniqueDir = UniqueObject<DIR*, internal::os_unix::UniqueDirTraits>;\n\n#endif  // OS_WIN\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::UniqueFD;\n#if !OS_WIN\nusing lynx::fml::UniqueDir;\n#endif\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_UNIQUE_FD_H_\n"
  },
  {
    "path": "base/include/fml/unique_object.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_UNIQUE_OBJECT_H_\n#define BASE_INCLUDE_FML_UNIQUE_OBJECT_H_\n\n#include <utility>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// struct UniqueFooTraits {\n//   // This function should be fast and inline.\n//   static int InvalidValue() { return 0; }\n//\n//   // This function should be fast and inline.\n//   static bool IsValid(const T& value) { return value != InvalidValue(); }\n//\n//   // This free function will not be called if f == InvalidValue()!\n//   static void Free(int f) { ::FreeFoo(f); }\n// };\n\ntemplate <typename T, typename Traits>\nclass UniqueObject {\n private:\n  // This must be first since it's used inline below.\n  //\n  // Use the empty base class optimization to allow us to have a Traits\n  // member, while avoiding any space overhead for it when Traits is an\n  // empty class.  See e.g. http://www.cantrip.org/emptyopt.html for a good\n  // discussion of this technique.\n  struct Data : public Traits {\n    explicit Data(const T& in) : value_(in) {}\n    Data(const T& in, const Traits& other) : Traits(other), value_(in) {}\n\n    T value_;\n  };\n\n public:\n  using element_type = T;\n  using traits_type = Traits;\n\n  UniqueObject() : data_(Traits::InvalidValue()) {}\n  explicit UniqueObject(const T& value) : data_(value) {}\n\n  UniqueObject(const T& value, const Traits& traits) : data_(value, traits) {}\n\n  UniqueObject(UniqueObject&& other)\n      : data_(other.release(), other.get_traits()) {}\n\n  ~UniqueObject() { FreeIfNecessary(); }\n\n  UniqueObject& operator=(UniqueObject&& other) {\n    reset(other.release());\n    return *this;\n  }\n\n  void reset(const T& value = Traits::InvalidValue()) {\n    LYNX_BASE_CHECK(data_.value_ == Traits::InvalidValue() ||\n                    data_.value_ != value);\n    FreeIfNecessary();\n    data_.value_ = value;\n  }\n\n  void swap(UniqueObject& other) {\n    // Standard swap idiom: 'using std::swap' ensures that std::swap is\n    // present in the overload set, but we call swap unqualified so that\n    // any more-specific overloads can be used, if available.\n    using std::swap;\n    swap(static_cast<Traits&>(data_), static_cast<Traits&>(other.data_));\n    swap(data_.value_, other.data_.value_);\n  }\n\n  // Release the object. The return value is the current object held by this\n  // object. After this operation, this object will hold an invalid value, and\n  // will not own the object any more.\n  [[nodiscard]] T release() {\n    T old_value = data_.value_;\n    data_.value_ = Traits::InvalidValue();\n    return old_value;\n  }\n\n  const T& get() const { return data_.value_; }\n\n  bool is_valid() const { return Traits::IsValid(data_.value_); }\n\n  bool operator==(const T& value) const { return data_.value_ == value; }\n\n  bool operator!=(const T& value) const { return data_.value_ != value; }\n\n  Traits& get_traits() { return data_; }\n  const Traits& get_traits() const { return data_; }\n\n private:\n  void FreeIfNecessary() {\n    if (data_.value_ != Traits::InvalidValue()) {\n      data_.Free(data_.value_);\n      data_.value_ = Traits::InvalidValue();\n    }\n  }\n\n  // Forbid comparison. If U != T, it totally doesn't make sense, and if U ==\n  // T, it still doesn't make sense because you should never have the same\n  // object owned by two different UniqueObject.\n  template <typename T2, typename Traits2>\n  bool operator==(const UniqueObject<T2, Traits2>& p2) const = delete;\n\n  template <typename T2, typename Traits2>\n  bool operator!=(const UniqueObject<T2, Traits2>& p2) const = delete;\n\n  Data data_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(UniqueObject);\n};\n\ntemplate <class T, class Traits>\nvoid swap(const UniqueObject<T, Traits>& a, const UniqueObject<T, Traits>& b) {\n  a.swap(b);\n}\n\ntemplate <class T, class Traits>\nbool operator==(const T& value, const UniqueObject<T, Traits>& object) {\n  return value == object.get();\n}\n\ntemplate <class T, class Traits>\nbool operator!=(const T& value, const UniqueObject<T, Traits>& object) {\n  return !(value == object.get());\n}\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::UniqueObject;\n}\n\n#endif  // BASE_INCLUDE_FML_UNIQUE_OBJECT_H_\n"
  },
  {
    "path": "base/include/fml/wakeable.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_FML_WAKEABLE_H_\n#define BASE_INCLUDE_FML_WAKEABLE_H_\n\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace lynx {\nnamespace fml {\n\n/// Interface over the ability to \\p WakeUp a \\p fml::MessageLoopImpl.\n/// \\see fml::MessageLoopTaskQueues\nclass Wakeable {\n public:\n  virtual ~Wakeable() {}\n\n  virtual void WakeUp(fml::TimePoint time_point, bool is_woken_by_vsync) = 0;\n};\n\n}  // namespace fml\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::fml::Wakeable;\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_FML_WAKEABLE_H_\n"
  },
  {
    "path": "base/include/geometry/point.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_GEOMETRY_POINT_H_\n#define BASE_INCLUDE_GEOMETRY_POINT_H_\n\nnamespace lynx {\nnamespace base {\nnamespace geometry {\n\ntemplate <typename T>\nclass Point {\n public:\n  Point() : x_(0), y_(0) {}\n  Point(T x, T y) : x_(x), y_(y) {}\n\n  T X() const { return x_; }\n  T Y() const { return y_; }\n\n  void SetX(T x) { x_ = x; }\n  void SetY(T y) { y_ = y; }\n\n  void MoveBy(const Point<T>& offset) { Move(offset.X(), offset.Y()); }\n  void Move(T dx, T dy) {\n    x_ += dx;\n    y_ += dy;\n  }\n\n private:\n  T x_;\n  T y_;\n};\n\ntemplate <typename T>\ninline Point<T> operator+(const Point<T>& a, const Point<T>& b) {\n  return Point<T>(a.X() + b.X(), a.Y() + b.Y());\n}\n\ntemplate <typename T>\ninline Point<T>& operator+=(Point<T>& a, const Point<T>& b) {\n  a.Move(b.X(), b.Y());\n  return a;\n}\n\ntemplate <typename T>\ninline Point<T> operator-(const Point<T>& a, const Point<T>& b) {\n  return Point<T>(a.X() - b.X(), a.Y() - b.Y());\n}\n\ntemplate <typename T>\ninline Point<T> operator-(const Point<T>& point) {\n  return Point<T>(-point.X(), -point.Y());\n}\n\ntemplate <typename T>\ninline bool operator==(const Point<T>& a, const Point<T>& b) {\n  return a.X() == b.X() && a.Y() == b.Y();\n}\n\ntemplate <typename T>\ninline bool operator!=(const Point<T>& a, const Point<T>& b) {\n  return a.X() != b.X() || a.Y() != b.Y();\n}\n\nusing IntPoint = Point<int>;\nusing FloatPoint = Point<float>;\n\n}  // namespace geometry\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_GEOMETRY_POINT_H_\n"
  },
  {
    "path": "base/include/geometry/rect.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_GEOMETRY_RECT_H_\n#define BASE_INCLUDE_GEOMETRY_RECT_H_\n\n#include <algorithm>\n#include <ostream>\n\n#include \"base/include/geometry/point.h\"\n#include \"base/include/geometry/size.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace geometry {\n\ntemplate <typename T>\nclass Rectangle {\n public:\n  Rectangle() = default;\n\n  Rectangle(const Point<T>& location, const Size<T>& size)\n      : location_(location), size_(size) {}\n\n  Point<T> GetLocation() const { return location_; }\n  Size<T> GetSize() const { return size_; }\n\n  void SetLocation(const Point<T>& location) { location_ = location; }\n  void SetSize(const base::geometry::Size<T>& size) { size_ = size; }\n  T X() const { return location_.X(); }\n  T Y() const { return location_.Y(); }\n  T MaxX() const { return X() + Width(); }\n  T MaxY() const { return Y() + Height(); }\n  T Width() const { return size_.Width(); }\n  T Height() const { return size_.Height(); }\n\n  void SetX(T x) { location_.SetX(x); }\n  void SetY(T y) { location_.SetY(y); }\n  void SetWidth(T width) { size_.SetWidth(width); }\n  void SetHeight(T height) { size_.SetHeight(height); }\n\n  bool IsEmpty() const { return size_.IsEmpty(); }\n\n  void Move(const Point<T>& offset) { location_ += offset; }\n  void Move(T dx, T dy) { location_.Move(dx, dy); }\n\n  void Expand(const Size<T>& size) { size_ += size; }\n  void Expand(T dw, T dh) { size_.Expand(dw, dh); }\n  void Contract(const Size<T>& size) { size_ -= size; }\n  void Contract(T dw, T dh) { size_.Expand(-dw, -dh); }\n\n  bool IsIntersectedWith(const Rectangle<T>& other) const {\n    return !IsEmpty() && !other.IsEmpty() && X() < other.MaxX() &&\n           other.X() < MaxX() && Y() < other.MaxY() && other.Y() < MaxY();\n  }\n\n  void Intersect(const Rectangle<T>& other) {\n    T left = std::max(X(), other.X());\n    T top = std::max(Y(), other.Y());\n    T right = std::min(MaxX(), other.MaxX());\n    T bottom = std::min(MaxY(), other.MaxY());\n\n    // Return a clean empty rectangle for non-intersecting cases.\n    if (left >= right || top >= bottom) {\n      left = 0;\n      top = 0;\n      right = 0;\n      bottom = 0;\n    }\n\n    location_.SetX(left);\n    location_.SetY(top);\n    size_.SetWidth(right - left);\n    size_.SetHeight(bottom - top);\n  }\n\n  bool Contains(T x, T y) {\n    return (x >= X() && x <= MaxX()) && (y >= Y() && y <= MaxY());\n  }\n\n  bool Equals(const Rectangle<T>& t) {\n    return location_ == t.GetLocation() && size_ == t.GetSize();\n  }\n\n private:\n  Point<T> location_;\n  Size<T> size_;\n};\n\nusing IntRect = Rectangle<int>;\nusing FloatRect = Rectangle<float>;\n\ntemplate <typename T>\nstd::ostream& operator<<(std::ostream& stream, const Rectangle<T>& obj) {\n  return stream << \"Rectangle(\" << obj.X() << \", \" << obj.Y() << \", \"\n                << obj.Width() << \", \" << obj.Height() << \")\";\n}\n\ntemplate <typename T>\ninline bool operator==(const Rectangle<T>& a, const Rectangle<T>& b) {\n  return a.GetLocation() == b.GetLocation() && a.GetSize() == b.GetSize();\n}\n\n}  // namespace geometry\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_GEOMETRY_RECT_H_\n"
  },
  {
    "path": "base/include/geometry/size.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_GEOMETRY_SIZE_H_\n#define BASE_INCLUDE_GEOMETRY_SIZE_H_\n\nnamespace lynx {\nnamespace base {\nnamespace geometry {\n\ntemplate <typename T>\nclass Size {\n public:\n  Size() : width_(0), height_(0) {}\n  Size(T width, T height) : width_(width), height_(height) {}\n\n  T Width() const { return width_; }\n  T Height() const { return height_; }\n\n  void SetWidth(T width) { width_ = width; }\n  void SetHeight(T height) { height_ = height; }\n\n  bool IsEmpty() const { return width_ == 0 && height_ == 0; }\n\n  void Expand(T width, T height) {\n    width_ += width;\n    height_ += height;\n  }\n\n  Size<T> ExpandedTo(const Size<T>& other) const {\n    return Size<T>(width_ > other.width_ ? width_ : other.width_,\n                   height_ > other.height_ ? height_ : other.height_);\n  }\n\n private:\n  T width_;\n  T height_;\n};\n\ntemplate <typename T>\ninline Size<T>& operator+=(Size<T>& a, const Size<T>& b) {\n  a.SetWidth(a.Width() + b.Width());\n  a.SetHeight(a.Height() + b.Height());\n  return a;\n}\n\ntemplate <typename T>\ninline Size<T>& operator-=(Size<T>& a, const Size<T>& b) {\n  a.SetWidth(a.Width() - b.Width());\n  a.SetHeight(a.Height() - b.Height());\n  return a;\n}\n\ntemplate <typename T>\ninline Size<T> operator+(const Size<T>& a, const Size<T>& b) {\n  return Size<T>(a.Width() + b.Width(), a.Height() + b.Height());\n}\n\ntemplate <typename T>\ninline Size<T> operator-(const Size<T>& a, const Size<T>& b) {\n  return Size<T>(a.Width() - b.Width(), a.Height() - b.Height());\n}\n\ntemplate <typename T>\ninline Size<T> operator-(const Size<T>& size) {\n  return Size<T>(-size.Width(), -size.Height());\n}\n\ntemplate <typename T>\ninline bool operator==(const Size<T>& a, const Size<T>& b) {\n  return a.Width() == b.Width() && a.Height() == b.Height();\n}\n\ntemplate <typename T>\ninline bool operator!=(const Size<T>& a, const Size<T>& b) {\n  return a.Width() != b.Width() || a.Height() != b.Height();\n}\n\nusing IntSize = Size<int>;\nusing FloatSize = Size<float>;\n\n}  // namespace geometry\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_GEOMETRY_SIZE_H_\n"
  },
  {
    "path": "base/include/hybrid_map.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_HYBRID_MAP_H_\n#define BASE_INCLUDE_HYBRID_MAP_H_\n\n#include <algorithm>\n#include <functional>\n#include <limits>\n#include <tuple>\n#include <type_traits>\n#include <utility>\n\n#ifdef _MSC_VER\n#define HYBRID_MAP_UNLIKELY(x) x\n#define HYBRID_MAP_LIKELY(x) x\n\n#define HYBRID_MAP_INLINE inline __forceinline\n#define HYBRID_MAP_NEVER_INLINE __declspec(noinline)\n#else\n#define HYBRID_MAP_UNLIKELY(x) __builtin_expect(!!(x), 0)\n#define HYBRID_MAP_LIKELY(x) __builtin_expect(!!(x), 1)\n\n#define HYBRID_MAP_INLINE inline __attribute__((always_inline))\n#define HYBRID_MAP_NEVER_INLINE __attribute__((noinline))\n#endif\n\nnamespace lynx {\nnamespace base {\n\n/// Checks if map type T has a method with name \"reserve\".\ntemplate <typename T, typename = void>\nstruct has_reserve_method : std::false_type {};\n\ntemplate <typename T>\nstruct has_reserve_method<T, std::void_t<decltype(std::declval<T&>().reserve(\n                                 std::declval<size_t>()))>> : std::true_type {};\n\ntemplate <typename T>\ninline constexpr bool has_reserve_method_v = has_reserve_method<T>::value;\n\n/// Checks if map type T has a method with name \"contains\".\ntemplate <typename T, typename = void>\nstruct has_contains_method : std::false_type {};\n\ntemplate <typename T>\nstruct has_contains_method<T, std::void_t<decltype(std::declval<T&>().contains(\n                                  std::declval<typename T::key_type>()))>>\n    : std::true_type {};\n\ntemplate <typename T>\ninline constexpr bool has_contains_method_v = has_contains_method<T>::value;\n\n/// Checks if map type T has a method with name \"for_each\".\ntemplate <typename T, typename Callback, typename = void>\nstruct has_for_each_method : std::false_type {};\n\ntemplate <typename T, typename Callback>\nstruct has_for_each_method<T, Callback,\n                           std::void_t<decltype(std::declval<T&>().for_each(\n                               std::declval<Callback>()))>> : std::true_type {};\n\ntemplate <typename T, typename Callback>\ninline constexpr bool has_for_each_method_v =\n    has_for_each_method<T, Callback>::value;\n\n/// Maps store `std::pair<const Key, T>` by default. This default implementation\n/// of KeyExtractor extracts the Key from the pair instance.\nstruct KeyExtractor {\n  template <typename V>\n  const auto& operator()(V&& v) {\n    return v.first;\n  }\n};\n\n/// Generic map policy which only accepts additional arguments of types.\n/// Usage: using ReverseMapPolicy = MapPolicy<std::map, std::greater<int>>;\ntemplate <template <typename...> class T, typename... Args>\nstruct MapPolicy {\n  template <typename Key, typename Value>\n  using type = T<Key, Value, Args...>;\n};\n\n/// The default logic of migrating data from small map to big map and destruct\n/// the small map afterwards. This transfer policy will not return pointer to\n/// the last transferred value.\nstruct DefaultTransferPolicy {\n  template <typename SmallMap, typename BigMap>\n  void operator()(SmallMap& small_map, BigMap& big_map) {\n    for (auto& p : small_map) {\n      big_map.emplace(\n          std::move(const_cast<typename SmallMap::key_type&>(p.first)),\n          std::move(p.second));\n    }\n  }\n};\n\n/// HybridMap discourages the use of iterators because using a unified iterator\n/// to encapsulate iterators for different map types incurs additional overhead,\n/// such as requiring additional fields to record the iterator type and checking\n/// the iterator type each time data is retrieved. By default, only the\n/// following iterator implementations are provided. You can implement more\n/// efficient iterators yourself.\ntemplate <typename Key, typename T, typename SmallMapPolicy,\n          typename BigMapPolicy>\nstruct DefaultIteratorPolicy {\n  using small_map_type = typename SmallMapPolicy::template type<Key, T>;\n  using big_map_type = typename BigMapPolicy::template type<Key, T>;\n\n  static constexpr auto isomorphic_value_type =\n      std::is_same_v<typename small_map_type::value_type,\n                     typename big_map_type::value_type>;\n\n  static constexpr auto using_same_iterator =\n      std::is_same_v<typename small_map_type::iterator,\n                     typename big_map_type::iterator>;\n\n  using value_type = typename small_map_type::value_type;\n\n  template <bool Const>\n  struct Iterator {\n    using small_map_iterator =\n        std::conditional_t<Const, typename small_map_type::const_iterator,\n                           typename small_map_type::iterator>;\n    using big_map_iterator =\n        std::conditional_t<Const, typename big_map_type::const_iterator,\n                           typename big_map_type::iterator>;\n\n    using pointer = std::conditional_t<Const, const value_type*, value_type*>;\n    using reference = std::conditional_t<Const, const value_type&, value_type&>;\n\n    Iterator(small_map_iterator it)\n        : using_small_map_(true), small_map_it_(it) {}\n    Iterator(big_map_iterator it) : using_small_map_(false), big_map_it_(it) {}\n\n    explicit operator small_map_iterator() const { return small_map_it_; }\n    explicit operator big_map_iterator() const { return big_map_it_; }\n\n    reference operator*() const {\n      return using_small_map_ ? *small_map_it_ : *big_map_it_;\n    }\n\n    pointer operator->() const {\n      return using_small_map_ ? &(*small_map_it_) : &(*big_map_it_);\n    }\n\n    Iterator& operator++() {\n      if (using_small_map_) {\n        small_map_it_++;\n      } else {\n        big_map_it_++;\n      }\n      return *this;\n    }\n\n    Iterator operator++(int) {\n      Iterator t(*this);\n      ++(*this);\n      return t;\n    }\n\n    friend bool operator==(const Iterator& x, const Iterator& y) {\n      if (x.using_small_map_) {\n        return y.using_small_map_ && x.small_map_it_ == y.small_map_it_;\n      } else {\n        return !y.using_small_map_ && x.big_map_it_ == y.big_map_it_;\n      }\n    }\n\n    friend bool operator!=(const Iterator& x, const Iterator& y) {\n      return !(x == y);\n    }\n\n   private:\n    bool using_small_map_;\n    union {\n      small_map_iterator small_map_it_;\n      big_map_iterator big_map_it_;\n    };\n  };\n\n  // DefaultIteratorPolicy only supports isomorphic map types.\n  using iterator = std::conditional_t<\n      isomorphic_value_type,\n      std::conditional_t<using_same_iterator, typename small_map_type::iterator,\n                         Iterator<false>>,\n      void>;\n  using const_iterator = std::conditional_t<\n      isomorphic_value_type,\n      std::conditional_t<using_same_iterator,\n                         typename small_map_type::const_iterator,\n                         Iterator<true>>,\n      void>;\n};\n\n/**\n * About the design ideas of HybridMap.\n * 1. Discourage iterator because iterators compatible with different maps will\n * inevitably incur performance overhead of frequently checking if it is of\n * small map or big map. During testing, it was found that this part of the\n * overhead cannot be ignored. Use `for_each` to iterate elements. You can still\n * wrap iterators outside using `using_small_map()`, `small_map()` and\n * `big_map()` methods or provide `IteratorPolicy` template argument such as\n * `DefaultIteratorPolicy`.\n * 2. `std::variant` is not used to manage different maps. Some additional\n * performance overhead cannot be avoided. For example, you must first use\n * `std::holds_alternative` to determine the type of the map, and then use\n * `std::get`, but the internal implementation of `std::get` must determine the\n * type again.\n *\n * `TransferPolicy`: a callable object providing custom migration logic of data\n * from small map to big map. If not provided DefaultTransferPolicy would be\n * used. If the `()` operator returns a pointer to mapped_type, it would be\n * treated as the last inserted value which triggers map data transfer.\n */\ntemplate <typename Key, typename T, size_t MaxSmallMapSize,\n          typename SmallMapPolicy, typename BigMapPolicy,\n          typename TransferPolicy = DefaultTransferPolicy,\n          typename IteratorPolicy =\n              DefaultIteratorPolicy<Key, T, SmallMapPolicy, BigMapPolicy>>\nclass HybridMap {\n public:\n  // Use the policies to generate the actual map types.\n  using small_map_type = typename SmallMapPolicy::template type<Key, T>;\n  using big_map_type = typename BigMapPolicy::template type<Key, T>;\n\n  static_assert(\n      std::is_same_v<typename small_map_type::key_type,\n                     typename big_map_type::key_type> &&\n          std::is_same_v<typename small_map_type::mapped_type,\n                         typename big_map_type::mapped_type>,\n      \"Requires map types have identical internal defined value types.\");\n\n  using size_type = size_t;\n  using key_type = typename small_map_type::key_type;        // Key\n  using mapped_type = typename small_map_type::mapped_type;  // T\n\n  // The purpose of defining `value_type` is to provide the constructor\n  // `HybridMap(std::initializer_list<value_type>)`.\n  // Doesn't mean that both SmallMap and BigMap must use the same value_type.\n  using value_type = std::pair<const Key, T>;\n\n  using iterator = typename IteratorPolicy::iterator;\n  using const_iterator = typename IteratorPolicy::const_iterator;\n\n  // If TransferPolicy returns pointer to last inserted value which caused\n  // map data transfer, we can use the returned pointer for optimization.\n  static constexpr auto TransferPolicyReturnsPointer =\n      std::is_same_v<std::remove_const_t<std::invoke_result_t<\n                         TransferPolicy, small_map_type&, big_map_type&>>,\n                     mapped_type*>;\n\n public:\n  HybridMap() : using_small_map_(true), small_map_() {}\n  ~HybridMap() {\n    using_small_map_ ? small_map_.~small_map_type() : big_map_.~big_map_type();\n  }\n\n  HybridMap(std::initializer_list<value_type> initial_list) {\n    if (initial_list.size() <= MaxSmallMapSize) {\n      using_small_map_ = true;\n      new (&small_map_) small_map_type(std::move(initial_list));\n    } else {\n      using_small_map_ = false;\n      new (&big_map_) big_map_type(std::move(initial_list));\n    }\n  }\n\n  HybridMap(const HybridMap& other) {\n    if (other.using_small_map_) {\n      using_small_map_ = true;\n      new (&small_map_) small_map_type(other.small_map_);\n    } else {\n      using_small_map_ = false;\n      new (&big_map_) big_map_type(other.big_map_);\n    }\n  }\n\n  HybridMap(HybridMap&& other) {\n    if (other.using_small_map_) {\n      using_small_map_ = true;\n      new (&small_map_) small_map_type(std::move(other.small_map_));\n    } else {\n      using_small_map_ = false;\n      new (&big_map_) big_map_type(std::move(other.big_map_));\n    }\n  }\n\n  HybridMap& operator=(const HybridMap& other) {\n    if (this == &other) {\n      return *this;\n    }\n    if (using_small_map_) {\n      if (other.using_small_map_) {\n        small_map_ = other.small_map_;\n      } else {\n        small_map_.~small_map_type();\n        new (&big_map_) big_map_type(other.big_map_);\n        using_small_map_ = false;\n      }\n    } else {\n      if (other.using_small_map_) {\n        big_map_.~big_map_type();\n        new (&small_map_) small_map_type(other.small_map_);\n        using_small_map_ = true;\n      } else {\n        big_map_ = other.big_map_;\n      }\n    }\n    return *this;\n  }\n\n  HybridMap& operator=(HybridMap&& other) {\n    if (this == &other) {\n      return *this;\n    }\n    if (using_small_map_) {\n      if (other.using_small_map_) {\n        small_map_ = std::move(other.small_map_);\n      } else {\n        small_map_.~small_map_type();\n        new (&big_map_) big_map_type(std::move(other.big_map_));\n        using_small_map_ = false;\n      }\n    } else {\n      if (other.using_small_map_) {\n        big_map_.~big_map_type();\n        new (&small_map_) small_map_type(std::move(other.small_map_));\n        using_small_map_ = true;\n      } else {\n        big_map_ = std::move(other.big_map_);\n      }\n    }\n    return *this;\n  }\n\n  bool using_small_map() const { return using_small_map_; }\n\n  const auto& small_map() const { return small_map_; }\n  auto& small_map() { return small_map_; }\n  const auto& big_map() const { return big_map_; }\n  auto& big_map() { return big_map_; }\n\n  size_t size() const {\n    return HYBRID_MAP_LIKELY(using_small_map_) ? small_map_.size()\n                                               : big_map_.size();\n  }\n\n  bool empty() const {\n    return HYBRID_MAP_LIKELY(using_small_map_) ? small_map_.empty()\n                                               : big_map_.empty();\n  }\n\n  void clear() {\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      small_map_.clear();\n    } else {\n      big_map_.~big_map_type();\n      new (&small_map_) small_map_type();\n      using_small_map_ = true;\n    }\n  }\n\n  void reserve(size_t count) {\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      if (count > MaxSmallMapSize) {\n        _transfer_reserve(count);\n      } else if constexpr (has_reserve_method_v<small_map_type>) {\n        small_map_.reserve(count);\n      }\n    } else if constexpr (has_reserve_method_v<big_map_type>) {\n      big_map_.reserve(count);\n    }\n  }\n\n  size_t erase(const Key& key) {\n    return HYBRID_MAP_LIKELY(using_small_map_) ? small_map_.erase(key)\n                                               : big_map_.erase(key);\n  }\n\n  /// Different with other maps, the result is the pointer of data value in this\n  /// map corresponding to key. DO NOT cache the pointer if either small map or\n  /// big map is not node based.\n  /// @return nullptr if key not found in map.\n  T* find(const Key& key) {\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      auto it = small_map_.find(key);\n      return it != small_map_.end() ? &it->second : nullptr;\n    } else {\n      auto it = big_map_.find(key);\n      return it != big_map_.end() ? &it->second : nullptr;\n    }\n  }\n\n  const T* find(const Key& key) const {\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      auto it = small_map_.find(key);\n      return it != small_map_.end() ? &it->second : nullptr;\n    } else {\n      auto it = big_map_.find(key);\n      return it != big_map_.end() ? &it->second : nullptr;\n    }\n  }\n\n  bool contains(const Key& key) const {\n    // This implementation is faster than `return find(key) != nullptr;` with\n    // less comparisons.\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      if constexpr (has_contains_method_v<small_map_type>) {\n        return small_map_.contains(key);\n      } else {\n        return small_map_.find(key) != small_map_.end();\n      }\n    } else {\n      if constexpr (has_contains_method_v<big_map_type>) {\n        return big_map_.contains(key);\n      } else {\n        return big_map_.find(key) != big_map_.end();\n      }\n    }\n  }\n\n  size_t count(const Key& key) const { return contains(key) ? 1 : 0; }\n\n  T& operator[](const Key& key) {\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      // [] will insert default constructed value and may trigger data transfer.\n      auto res = small_map_.try_emplace(key);\n      if (res.second) {\n        // Insertion took place, may need to transfer data to big map.\n        if (HYBRID_MAP_UNLIKELY(small_map_.size() > MaxSmallMapSize)) {\n          if constexpr (TransferPolicyReturnsPointer) {\n            // Fast path, result of _transfer() is the last inserted value in\n            // big map.\n            return *_transfer();\n          } else {\n            // Slow path, must find inserted value in big map again.\n            _transfer();\n            return big_map_.find(key)->second;\n          }\n        }\n      }\n      return res.first->second;\n    } else {\n      return big_map_[key];\n    }\n  }\n\n  T& operator[](Key&& key) {\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      // [] will insert default constructed value and may trigger data transfer.\n      auto res = small_map_.try_emplace(std::move(key));\n      if (res.second) {\n        // Insertion took place, may need to transfer data to big map.\n        if (HYBRID_MAP_UNLIKELY(small_map_.size() > MaxSmallMapSize)) {\n          if constexpr (TransferPolicyReturnsPointer) {\n            // Fast path, result of _transfer() is the last inserted value in\n            // big map.\n            return *_transfer();\n          } else {\n            // Slow path, must find inserted value in big map again.\n            auto key_copy = res.first->first;\n            _transfer();\n            return big_map_.find(key_copy)->second;\n          }\n        }\n      }\n      return res.first->second;\n    } else {\n      return big_map_[std::move(key)];\n    }\n  }\n\n  T& at(const Key& key) {\n    return HYBRID_MAP_LIKELY(using_small_map_) ? small_map_.at(key)\n                                               : big_map_.at(key);\n  }\n\n  /// Different with other maps, the result is the pointer of data value in this\n  /// map corresponding to key and a flag telling if insertion took place. DO\n  /// NOT cache the pointer if either small map or big map is not node based.\n  template <class V>\n  std::pair<T*, bool> insert_or_assign(const Key& key, V&& value) {\n    std::pair<T*, bool> result;\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      auto res = small_map_.insert_or_assign(key, std::forward<V>(value));\n      result = {&res.first->second, res.second};\n      if (result.second) {\n        // Insertion took place, may need to transfer data to big map.\n        if (HYBRID_MAP_UNLIKELY(small_map_.size() > MaxSmallMapSize)) {\n          if constexpr (TransferPolicyReturnsPointer) {\n            // Fast path, result of _transfer() is the last inserted value in\n            // big map.\n            result.first = _transfer();\n          } else {\n            // Slow path, must find inserted value in big map again.\n            _transfer();\n            result.first = &big_map_.find(key)->second;\n          }\n        }\n      }\n    } else {\n      auto res = big_map_.insert_or_assign(key, std::forward<V>(value));\n      result = {&res.first->second, res.second};\n    }\n    return result;\n  }\n\n  template <class V>\n  std::pair<T*, bool> insert_or_assign(Key&& key, V&& value) {\n    std::pair<T*, bool> result;\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      auto res =\n          small_map_.insert_or_assign(std::move(key), std::forward<V>(value));\n      result = {&res.first->second, res.second};\n      if (result.second) {\n        // Insertion took place, may need to transfer data to big map.\n        if (HYBRID_MAP_UNLIKELY(small_map_.size() > MaxSmallMapSize)) {\n          if constexpr (TransferPolicyReturnsPointer) {\n            // Fast path, result of _transfer() is the last inserted value in\n            // big map.\n            result.first = _transfer();\n          } else {\n            // Slow path, must find inserted value in big map again. Must copy\n            // key beforehand becuase argument `key` is moved and `res` is\n            // invalid after transferring.\n            auto key_copy = res.first->first;\n            _transfer();\n            result.first = &big_map_.find(key_copy)->second;\n          }\n        }\n      }\n    } else {\n      auto res =\n          big_map_.insert_or_assign(std::move(key), std::forward<V>(value));\n      result = {&res.first->second, res.second};\n    }\n    return result;\n  }\n\n  template <class V>\n  std::pair<T*, bool> insert(const Key& key, V&& value) {\n    return emplace(key, std::forward<V>(value));\n  }\n\n  template <class V>\n  std::pair<T*, bool> insert(Key&& key, V&& value) {\n    return emplace(std::move(key), std::forward<V>(value));\n  }\n\n  template <class... Args>\n  std::pair<T*, bool> emplace(const Key& key, Args&&... args) {\n    std::pair<T*, bool> result;\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      auto res = small_map_.try_emplace(key, std::forward<Args>(args)...);\n      result = {&res.first->second, res.second};\n      if (result.second) {\n        // Insertion took place, may need to transfer data to big map.\n        if (HYBRID_MAP_UNLIKELY(small_map_.size() > MaxSmallMapSize)) {\n          if constexpr (TransferPolicyReturnsPointer) {\n            // Fast path, result of _transfer() is the last inserted value in\n            // big map.\n            result.first = _transfer();\n          } else {\n            // Slow path, must find inserted value in big map again.\n            _transfer();\n            result.first = &big_map_.find(key)->second;\n          }\n        }\n      }\n    } else {\n      auto res = big_map_.try_emplace(key, std::forward<Args>(args)...);\n      result = {&res.first->second, res.second};\n    }\n    return result;\n  }\n\n  template <class... Args>\n  std::pair<T*, bool> emplace(Key&& key, Args&&... args) {\n    std::pair<T*, bool> result;\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      auto res =\n          small_map_.try_emplace(std::move(key), std::forward<Args>(args)...);\n      result = {&res.first->second, res.second};\n      if (result.second) {\n        // Insertion took place, may need to transfer data to big map.\n        if (HYBRID_MAP_UNLIKELY(small_map_.size() > MaxSmallMapSize)) {\n          if constexpr (TransferPolicyReturnsPointer) {\n            // Fast path, result of _transfer() is the last inserted value in\n            // big map.\n            result.first = _transfer();\n          } else {\n            // Slow path, must find inserted value in big map again. Must copy\n            // key beforehand becuase argument `key` is moved and `res` is\n            // invalid after transferring.\n            auto key_copy = res.first->first;\n            _transfer();\n            result.first = &big_map_.find(key_copy)->second;\n          }\n        }\n      }\n    } else {\n      auto res =\n          big_map_.try_emplace(std::move(key), std::forward<Args>(args)...);\n      result = {&res.first->second, res.second};\n    }\n    return result;\n  }\n\n  template <class U, class... Args>\n  std::pair<T*, bool> try_emplace(U&& key, Args&&... args) {\n    return emplace(std::forward<U>(key), std::forward<Args>(args)...);\n  }\n\n  template <typename... Args1, typename... Args2>\n  std::pair<T*, bool> emplace(std::piecewise_construct_t,\n                              std::tuple<Args1...> args1,\n                              std::tuple<Args2...> args2) {\n    std::pair<T*, bool> result;\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      auto res = small_map_.emplace(std::piecewise_construct, std::move(args1),\n                                    std::move(args2));\n      result = {&res.first->second, res.second};\n      if (result.second) {\n        // Insertion took place, may need to transfer data to big map.\n        if (HYBRID_MAP_UNLIKELY(small_map_.size() > MaxSmallMapSize)) {\n          if constexpr (TransferPolicyReturnsPointer) {\n            // Fast path, result of _transfer() is the last inserted value in\n            // big map.\n            result.first = _transfer();\n          } else {\n            // Slow path, must find inserted value in big map again. Must copy\n            // key beforehand becuase argument `key` is moved and `res` is\n            // invalid after transferring.\n            auto key_copy = res.first->first;\n            _transfer();\n            result.first = &big_map_.find(key_copy)->second;\n          }\n        }\n      }\n    } else {\n      auto res = big_map_.emplace(std::piecewise_construct, std::move(args1),\n                                  std::move(args2));\n      result = {&res.first->second, res.second};\n    }\n    return result;\n  }\n\n  /**\n   * @brief Iterates over each key-value pair in the map and applies a\n   * user-provided callback.\n   *\n   * This method supports two types of callbacks for maximum flexibility:\n   * 1.  **Callback with `void` return type**: The function will be executed for\n   * every element in the map. Signature: `void(const K& key, const T& value)`\n   *\n   * 2.  **Callback with `bool` return type**: This allows for early termination\n   * of the loop. If the callback returns `true`, the iteration stops\n   * immediately. If it returns `false`, the iteration continues to the next\n   * element. Signature: `bool(const K& key, const T& value)`\n   *\n   * @tparam Callback The type of the callback function or callable object\n   * (e.g., a lambda).\n   *\n   * @param callback The callable object to be invoked for each key-value pair.\n   * It will be perfectly forwarded.\n   *\n   * @note This method is enabled only if the provided `Callback` is invocable\n   * with arguments of type `const K&` and `const T&`.\n   * @note The method uses `if constexpr` to create a zero-overhead abstraction,\n   * ensuring that the check for a boolean return value happens at compile-time\n   * with no runtime cost.\n   *\n   * @code\n   * MyMap<std::string, int> map = {{\"a\", 1}, {\"b\", 2}};\n   *\n   * // Example 1: Print all elements (void return).\n   * map.for_each([](const std::string& key, int value) {\n   *   std::cout << key << \": \" << value << std::endl;\n   * });\n   *\n   * // Example 2: Find an element and stop (bool return).\n   * map.for_each([](const std::string& key, int value) -> bool {\n   *   if (key == \"b\") {\n   *     std::cout << \"Found 'b'!\" << std::endl;\n   *     return true; // Stop iterating\n   *   }\n   *   return false; // Continue iterating\n   * });\n   * @endcode\n   */\n  template <typename Callback, typename = std::enable_if_t<std::is_invocable_v<\n                                   Callback, const Key&, const T&>>>\n  void for_each(Callback&& callback) const {\n    using callback_ret_type =\n        std::invoke_result_t<Callback, const Key&, const T&>;\n    constexpr bool callback_returns_bool =\n        std::is_same_v<callback_ret_type, bool>;\n\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      if constexpr (has_for_each_method_v<small_map_type, Callback>) {\n        small_map_.for_each(std::forward<Callback>(callback));\n      } else {\n        if constexpr (callback_returns_bool) {\n          for (auto& p : small_map_) {\n            if (callback(p.first, p.second)) {\n              break;\n            }\n          }\n        } else {\n          for (auto& p : small_map_) {\n            callback(p.first, p.second);\n          }\n        }\n      }\n    } else {\n      if constexpr (has_for_each_method_v<big_map_type, Callback>) {\n        big_map_.for_each(std::forward<Callback>(callback));\n      } else {\n        if constexpr (callback_returns_bool) {\n          for (auto& p : big_map_) {\n            if (callback(p.first, p.second)) {\n              break;\n            }\n          }\n        } else {\n          for (auto& p : big_map_) {\n            callback(p.first, p.second);\n          }\n        }\n      }\n    }\n  }\n\n  template <typename Callback,\n            typename =\n                std::enable_if_t<std::is_invocable_v<Callback, const Key&, T&>>>\n  void for_each(Callback&& callback) {\n    using callback_ret_type = std::invoke_result_t<Callback, const Key&, T&>;\n    constexpr bool callback_returns_bool =\n        std::is_same_v<callback_ret_type, bool>;\n\n    if (HYBRID_MAP_LIKELY(using_small_map_)) {\n      if constexpr (has_for_each_method_v<small_map_type, Callback>) {\n        small_map_.for_each(std::forward<Callback>(callback));\n      } else {\n        if constexpr (callback_returns_bool) {\n          for (auto& p : small_map_) {\n            if (callback(p.first, p.second)) {\n              break;\n            }\n          }\n        } else {\n          for (auto& p : small_map_) {\n            callback(p.first, p.second);\n          }\n        }\n      }\n    } else {\n      if constexpr (has_for_each_method_v<big_map_type, Callback>) {\n        big_map_.for_each(std::forward<Callback>(callback));\n      } else {\n        if constexpr (callback_returns_bool) {\n          for (auto& p : big_map_) {\n            if (callback(p.first, p.second)) {\n              break;\n            }\n          }\n        } else {\n          for (auto& p : big_map_) {\n            callback(p.first, p.second);\n          }\n        }\n      }\n    }\n  }\n\n  const_iterator cbegin() const {\n    return using_small_map_ ? const_iterator(small_map_.cbegin())\n                            : const_iterator(big_map_.cbegin());\n  }\n\n  const_iterator cend() const {\n    return using_small_map_ ? const_iterator(small_map_.cend())\n                            : const_iterator(big_map_.cend());\n  }\n\n  iterator begin() {\n    return using_small_map_ ? iterator(small_map_.begin())\n                            : iterator(big_map_.begin());\n  }\n\n  iterator end() {\n    return using_small_map_ ? iterator(small_map_.end())\n                            : iterator(big_map_.end());\n  }\n\n  const_iterator begin() const {\n    return using_small_map_ ? const_iterator(small_map_.begin())\n                            : const_iterator(big_map_.begin());\n  }\n\n  const_iterator end() const {\n    return using_small_map_ ? const_iterator(small_map_.end())\n                            : const_iterator(big_map_.end());\n  }\n\n  const_iterator find_iterator(const Key& key) const {\n    return using_small_map_ ? const_iterator(small_map_.find(key))\n                            : const_iterator(big_map_.find(key));\n  }\n\n  iterator find_iterator(const Key& key) {\n    return using_small_map_ ? iterator(small_map_.find(key))\n                            : iterator(big_map_.find(key));\n  }\n\n  template <typename It,\n            typename = std::enable_if_t<!std::is_void_v<It> &&\n                                        (std::is_same_v<It, iterator> ||\n                                         std::is_same_v<It, const_iterator>)>>\n  iterator erase_iterator(It pos) {\n    if constexpr (std::is_same_v<It, iterator>) {\n      return using_small_map_\n                 ? iterator(\n                       small_map_.erase(typename small_map_type::iterator(pos)))\n                 : iterator(\n                       big_map_.erase(typename big_map_type::iterator(pos)));\n    } else {\n      return using_small_map_\n                 ? iterator(small_map_.erase(\n                       typename small_map_type::const_iterator(pos)))\n                 : iterator(big_map_.erase(\n                       typename big_map_type::const_iterator(pos)));\n    }\n  }\n\n private:\n  std::conditional_t<TransferPolicyReturnsPointer, mapped_type*, void>\n  _transfer() {\n    big_map_type temp_big_map;\n    if constexpr (has_reserve_method_v<big_map_type>) {\n      temp_big_map.reserve(small_map_.size() +\n                           2);  // size+2 for coming insertion\n    }\n    if constexpr (TransferPolicyReturnsPointer) {\n      auto last_pointer = TransferPolicy()(small_map_, temp_big_map);\n      small_map_.~small_map_type();\n      new (&big_map_) big_map_type(std::move(temp_big_map));\n      using_small_map_ = false;\n      return last_pointer;\n    } else {\n      TransferPolicy()(small_map_, temp_big_map);\n      small_map_.~small_map_type();\n      new (&big_map_) big_map_type(std::move(temp_big_map));\n      using_small_map_ = false;\n    }\n  }\n\n  void _transfer_reserve(size_t size) {\n    if (HYBRID_MAP_LIKELY(small_map_.empty())) {\n      small_map_.~small_map_type();\n      new (&big_map_) big_map_type();\n      if constexpr (has_reserve_method_v<big_map_type>) {\n        big_map_.reserve(size);\n      }\n    } else {\n      big_map_type temp_big_map;\n      if constexpr (has_reserve_method_v<big_map_type>) {\n        temp_big_map.reserve(size);\n      }\n\n      TransferPolicy()(small_map_, temp_big_map);\n      small_map_.~small_map_type();\n      new (&big_map_) big_map_type(std::move(temp_big_map));\n    }\n    using_small_map_ = false;\n  }\n\n  bool using_small_map_;\n  union {\n    small_map_type small_map_;\n    big_map_type big_map_;\n  };\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_HYBRID_MAP_H_\n"
  },
  {
    "path": "base/include/linked_hash_map.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_LINKED_HASH_MAP_H_\n#define BASE_INCLUDE_LINKED_HASH_MAP_H_\n\n#include <algorithm>\n#include <cstddef>\n#include <functional>\n#include <iostream>\n#include <limits>\n#include <tuple>\n#include <utility>\n\n#include \"base/include/boost/unordered.h\"\n\nnamespace lynx {\nnamespace base {\n\n/**\n LinkedHashMap works like Java LinkedHashMap.\n It is a hash table and linked list implementation with limited map interface,\n and with predictable iteration order. It maintains a doubly-linked list running\n through all of its entries. This linked list defines the iteration ordering,\n which is normally the order in which keys were inserted into the map\n (insertion-order).\n\n The inner hash table is not created until element count reaches\n LinearFindThreshold. Before that, finding algorithm is linear search.\n This contributes to performance in two ways:\n 1. Linear search for few elements is faster than hash table.\n 2. Saves the time of creating and maintaining HashTable nodes.\n\n LinkedHashMap is node based and guarantees pointer stability.\n\n Performance Tips:\n If the final number of elements can be estimated, calling the reserve() method\n in advance can allocate a whole block of memory(the pool) for the nodes. No\n additional memory allocation system calls are made when the capacity is not\n exceeded.\n\n Debugger Tips:\n Currently only LLDB script is provided. Reference //tools/lldb/lynx_lldb.py for\n global settings. You can also execute LLDB command\n    `command script import {full_path_of linked_hash_map_lldb.py}`\n when LLDB stops for each LLDB session.\n */\ntemplate <class Key, class T,\n          uint32_t InsertionBuildMapThreshold =\n              (std::is_integral_v<Key> || std::is_enum_v<Key>) ? 32 : 12,\n          uint32_t FindBuildMapThreshold =\n              (std::is_integral_v<Key> || std::is_enum_v<Key>) ? 12 : 6,\n          class Hash = std::hash<Key>, class Pred = std::equal_to<Key>>\nclass LinkedHashMap {\n public:\n  using size_type = size_t;\n  using key_type = Key;\n  using value_type = std::pair<Key, T>;\n  using reference = value_type&;\n  using const_reference = const value_type&;\n\n  struct Node;\n  struct NodeBase {\n    Node* prev;\n    Node* next;\n\n    NodeBase() : prev(AsNode()), next(AsNode()) {}\n\n    void Reset() { prev = next = AsNode(); }\n\n    Node* AsNode() { return static_cast<Node*>(this); }\n\n    const Node* AsNode() const { return static_cast<const Node*>(this); }\n  };\n\n  struct Node : public NodeBase {\n    value_type value;\n\n    template <class... Args>\n    Node(const Key& key, Args&&... args)\n        : NodeBase(),\n          value(std::piecewise_construct, std::forward_as_tuple(key),\n                std::forward_as_tuple(std::forward<Args>(args)...)) {}\n\n    template <class... Args>\n    Node(Key&& key, Args&&... args)\n        : NodeBase(),\n          value(std::piecewise_construct, std::forward_as_tuple(std::move(key)),\n                std::forward_as_tuple(std::forward<Args>(args)...)) {}\n  };\n\n  struct Iterator {\n    NodeBase* ptr;\n    Iterator() : ptr(nullptr) {}\n    explicit Iterator(NodeBase* ptr) : ptr(ptr) {}\n\n    Node* node_ptr() const { return static_cast<Node*>(ptr); }\n\n    value_type& operator*() const { return ptr->AsNode()->value; }\n\n    value_type* operator->() const { return &ptr->AsNode()->value; }\n\n    Iterator& operator++() {\n      ptr = ptr->next;\n      return *this;\n    }\n\n    Iterator operator++(int) {\n      Iterator t(*this);\n      ++(*this);\n      return t;\n    }\n\n    Iterator& operator--() {\n      ptr = ptr->prev;\n      return *this;\n    }\n\n    Iterator operator--(int) {\n      Iterator t(*this);\n      --(*this);\n      return t;\n    }\n\n    friend bool operator==(const Iterator& x, const Iterator& y) {\n      return x.ptr == y.ptr;\n    }\n\n    friend bool operator!=(const Iterator& x, const Iterator& y) {\n      return !(x == y);\n    }\n  };\n\n  struct ConstIterator {\n    NodeBase* ptr;\n    ConstIterator() : ptr(nullptr) {}\n    explicit ConstIterator(const NodeBase* ptr)\n        : ptr(const_cast<NodeBase*>(ptr)) {}\n    ConstIterator(const Iterator& other) : ptr(other.ptr) {}\n\n    Node* node_ptr() const { return static_cast<Node*>(ptr); }\n\n    const value_type& operator*() const { return ptr->AsNode()->value; }\n\n    const value_type* operator->() const { return &ptr->AsNode()->value; }\n\n    ConstIterator& operator++() {\n      ptr = ptr->next;\n      return *this;\n    }\n\n    ConstIterator operator++(int) {\n      ConstIterator t(*this);\n      ++(*this);\n      return t;\n    }\n\n    ConstIterator& operator--() {\n      ptr = ptr->prev;\n      return *this;\n    }\n\n    ConstIterator operator--(int) {\n      ConstIterator t(*this);\n      --(*this);\n      return t;\n    }\n\n    friend bool operator==(const ConstIterator& x, const ConstIterator& y) {\n      return x.ptr == y.ptr;\n    }\n\n    friend bool operator!=(const ConstIterator& x, const ConstIterator& y) {\n      return !(x == y);\n    }\n  };\n\n  using iterator = Iterator;\n  using const_iterator = ConstIterator;\n\n  using map_type = boost::unordered_flat_map<Key, iterator, Hash, Pred>;\n\n public:\n  /// The default initial pool size.\n  static constexpr uint16_t kInitialAllocationSize = 2;\n\n  /// @param initial_allocation_size Capacity for nodes memory for first\n  /// allocation.\n  explicit LinkedHashMap(\n      uint16_t initial_allocation_size = kInitialAllocationSize)\n      : pool_size_(initial_allocation_size) {}\n\n  ~LinkedHashMap() {\n#if 0\n    PrintElements(\"dtor\");\n#endif\n\n    if (is_imperfect_) {\n      list_clear();\n    } else {\n      // Fast path\n      for (size_t i = 0; i < count_; i++) {\n        pool_[i].~Node();\n      }\n    }\n\n    if (pool_ != nullptr) {\n      std::free(pool_);\n    }\n    if (map_ != nullptr) {\n      delete map_;\n    }\n  }\n\n#if 0\n  void PrintElements(const char* scene, const char* tag = \"[AA]\") {\n    [[maybe_unused]] bool has_not_on_pool = false;\n    std::cout << tag << \" \" << scene << \" \" << (void*)this << \" \" << count_\n              << \" pool_size: \" << pool_size_\n              << \" pool allocated: \" << (pool_ != nullptr ? \"1\" : \"0\")\n              << \" imperfect: \" << (is_imperfect_ ? \"1\" : \"0\")\n              << \" has_map: \" << (map_ != nullptr ? \"1\" : \"0\") << std::endl;\n    if (count_ == 1) {\n      auto p = begin().node_ptr();\n      if (!ptr_on_pool(p)) {\n        std::cout << tag << \"    not on pool: 0\" << std::endl;\n        has_not_on_pool = true;\n      }\n    } else if (count_ > 1) {\n      auto it = begin();\n      auto p = it.node_ptr();\n      if (ptr_on_pool(p)) {\n        std::cout << tag << \"    \"\n                  << \"on pool: 0\" << std::endl;\n      } else {\n        std::cout << tag << \"    \"\n                  << \"not on pool: 0\" << std::endl;\n        has_not_on_pool = true;\n      }\n      ++it;\n      for (; it != end(); ++it) {\n        auto pIt = it.node_ptr();\n        if (ptr_on_pool(pIt)) {\n          std::cout << tag << \"    \"\n                    << \"on pool: \" << (intptr_t)pIt - (intptr_t)p << std::endl;\n        } else {\n          std::cout << tag << \"    \"\n                    << \"not on pool: \" << (intptr_t)pIt - (intptr_t)p\n                    << std::endl;\n          has_not_on_pool = true;\n        }\n        p = pIt;\n      }\n    }\n//    breakLog(scene, count_, has_not_on_pool);\n  }\n#endif\n\n  LinkedHashMap(std::initializer_list<value_type> initial_list) {\n    reserve(initial_list.size());\n    for (auto& value : initial_list) {\n      insert_or_assign(std::move(value.first), std::move(value.second));\n    }\n  }\n\n  LinkedHashMap(const LinkedHashMap& other) {\n    if (!other.empty()) {\n      reserve(other.size());\n      // For copy construct, only copy values, leaves map_ as nullptr.\n      for (auto it = other.begin(), e = other.end(); it != e; ++it) {\n        construct_node_at_end(it->first, it->second);\n      }\n    }\n  }\n\n  LinkedHashMap& operator=(const LinkedHashMap& other) {\n    clear();\n    if (!other.empty()) {\n      reserve(other.size());\n      // Copy assignment, if map_ already exists, build map_.\n      if (map_ != nullptr) {\n        for (auto it = other.begin(), e = other.end(); it != e; ++it) {\n          map_->emplace(it->first,\n                        construct_node_at_end(it->first, it->second));\n        }\n      } else {\n        for (auto it = other.begin(), e = other.end(); it != e; ++it) {\n          construct_node_at_end(it->first, it->second);\n        }\n      }\n    }\n    return *this;\n  }\n\n  LinkedHashMap(LinkedHashMap&& other) : pool_size_(other.pool_size_) {\n    if (!other.empty()) {\n      // If other has map, steal it or leaves map_ as nullptr.\n      if (other.map_ != nullptr) {\n        map_ = other.map_;\n        other.map_ = nullptr;\n      }\n\n      if (other.pool_ != nullptr) {\n        pool_ = other.pool_;\n        pool_cursor_ = other.pool_cursor_;\n        other.pool_ = nullptr;\n        other.pool_size_ = kInitialAllocationSize;\n        // No need to reset other.pool_cursor_\n      }\n\n      count_ = other.count_;\n      other.count_ = 0;\n\n      end_.next = other.end_.next;\n      end_.next->prev =\n          end_as_link()->AsNode();  // Must use self's end_ as real end.\n      end_.prev = other.end_.prev;\n      end_.prev->next = end_as_link()->AsNode();\n      other.end_.Reset();\n\n      is_imperfect_ = other.is_imperfect_;\n      other.is_imperfect_ = 0;\n    }\n  }\n\n  LinkedHashMap& operator=(LinkedHashMap&& other) {\n    clear();\n    if (pool_ != nullptr) {\n      std::free(pool_);\n      pool_ = nullptr;\n    }\n    if (map_ != nullptr) {\n      delete map_;\n      map_ = nullptr;\n    }\n\n    // Reconstruct self.\n    return *(new (this) LinkedHashMap(std::move(other)));\n  }\n\n  /// @param free_pool When true, also release the memory of pool.\n  /// And when next time this map is inserted with any data, a new memory\n  /// pool will be allocated.\n  void clear(bool free_pool = false) noexcept {\n    if (empty()) {\n      return;\n    }\n    list_clear();\n    count_ = 0;\n    is_imperfect_ = 0;\n\n    if (free_pool) {\n      if (pool_ != nullptr) {\n        std::free(pool_);\n        pool_ = nullptr;\n      }\n      pool_size_ = kInitialAllocationSize;\n    } else {\n      // Reset pool cursor so that pool can be reused or reserved with larger\n      // size.\n      pool_cursor_ = 0;\n    }\n\n    if (map_ != nullptr) {\n      map_->clear();\n    }\n  }\n\n  iterator find(const Key& key) {\n    return inner_find(key, FindBuildMapThreshold);\n  }\n\n  const_iterator find(const Key& key) const noexcept {\n    return (const_cast<LinkedHashMap*>(this))\n        ->inner_find(key, FindBuildMapThreshold);\n  }\n\n  size_type erase(const Key& key) {\n    if (map_ != nullptr) {\n      if (auto map_it = map_->find(key); map_it != map_->end()) {\n        auto list_it = map_it->second;\n        map_->erase(map_it);\n        erase_node(list_it);\n        return 1;\n      }\n    } else {\n      // map_ is nullptr, call inner_find but do not build map_\n      // or we have to erase from map_ again which makes no sense.\n      auto it = inner_find(key, std::numeric_limits<uint32_t>::max());\n      if (it != end()) {\n        erase_node(it);\n        return 1;\n      }\n    }\n    return 0;\n  }\n\n  iterator erase(iterator pos) { return erase(const_iterator(pos)); }\n\n  iterator erase(const_iterator pos) {\n    if (map_ != nullptr) {\n      map_->erase(pos->first);\n    }\n    if (pos != end()) {\n      return erase_node(pos);\n    } else {\n      return end();\n    }\n  }\n\n  /// @brief Compared with for range loop, it is recommended to use for_each\n  /// method to traverse nodes. The for_each method will detect whether the data\n  /// nodes are continuous and completely located in the memory pool. If this\n  /// condition is met, it will traverse the nodes in an array manner. When the\n  /// for range loop uses begin() and end() iterators, the iterators use a\n  /// double linked list to access nodes, and the performance is slightly worse\n  /// than that of an array. However, the for_each method may bring a little\n  /// binary increment and does not allow the map to be modified while\n  /// iterating.\n  template <typename Callback, typename = std::enable_if_t<std::is_invocable_v<\n                                   Callback, const Key&, const T&>>>\n  void for_each(Callback&& callback) const {\n    if (is_imperfect_) {\n      // Fallback to default iterators which are linked nodes.\n      for (const auto& it : *this) {\n        callback(it.first, it.second);\n      }\n    } else {\n      // Traverse like array.\n      for (size_t i = 0; i < count_; i++) {\n        const Node& n = pool_[i];\n        callback(n.value.first, n.value.second);\n      }\n    }\n  }\n\n  template <typename Callback,\n            typename =\n                std::enable_if_t<std::is_invocable_v<Callback, const Key&, T&>>>\n  void for_each(Callback&& callback) {\n    if (is_imperfect_) {\n      // Fallback to default iterators which are linked nodes.\n      for (auto& it : *this) {\n        callback(it.first, it.second);\n      }\n    } else {\n      // Traverse like array.\n      for (size_t i = 0; i < count_; i++) {\n        Node& n = pool_[i];\n        callback(n.value.first, n.value.second);\n      }\n    }\n  }\n\n  /// @brief Merge other map into self. This method provides optimizations\n  /// when self is empty and use other's for_each method to accelerate the\n  /// iteration.\n  void merge(const LinkedHashMap& other) {\n    if (empty()) {\n      // If self is empty, assigning from source is more efficient\n      // because it does not check key existence when inserting nodes.\n      *this = other;\n    } else {\n      other.for_each(\n          [this](const Key& k, const T& v) { this->insert_or_assign(k, v); });\n    }\n  }\n\n  const_iterator begin() const noexcept { return const_iterator(end_.next); }\n\n  const_iterator end() const noexcept { return const_iterator(end_as_link()); }\n\n  iterator begin() noexcept { return iterator(end_.next); }\n\n  iterator end() noexcept { return iterator(end_as_link()); }\n\n  const_reference front() const noexcept { return end_.next->value; }\n\n  reference front() noexcept { return end_.next->value; }\n\n  const_reference back() const noexcept { return end_.prev->value; }\n\n  reference back() noexcept { return end_.prev->value; }\n\n  T& operator[](const Key& key) { return at(key); }\n\n  T& operator[](Key&& key) { return at(std::move(key)); }\n\n  T& at(const Key& key) { return insert_default_if_absent(key).first->second; }\n\n  T& at(Key&& key) {\n    return insert_default_if_absent(std::move(key)).first->second;\n  }\n\n  bool contains(const Key& key) const {\n    return (const_cast<LinkedHashMap*>(this))\n               ->inner_find(key, FindBuildMapThreshold) != end();\n  }\n\n  size_t count(const Key& key) const { return contains(key) ? 1 : 0; }\n\n  /// @brief This method is basically for internal usage. It searches the map\n  /// and if key is absent, a default constructed value will be inserted.\n  /// This is more efficient than insert_or_assign(key, T()) because it\n  /// does not construct a T() instance when key is found.\n  std::pair<iterator, bool> insert_default_if_absent(const Key& key) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(key), true};\n    } else {\n      return {it, false};\n    }\n  }\n\n  std::pair<iterator, bool> insert_default_if_absent(Key&& key) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(std::move(key)), true};\n    } else {\n      return {it, false};\n    }\n  }\n\n  /// @brief Since the total number of elements cannot be calculated from the\n  /// last and first iterators, it is recommended to call reserve() in advance\n  /// to allocate memory.\n  template <class InputIt>\n  void insert(InputIt first, InputIt last) {\n    for (; first != last; ++first) {\n      insert_or_assign(first->first, first->second);\n    }\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace_or_assign(const Key& key, Args&&... args) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(key, std::forward<Args>(args)...), true};\n    } else {\n      it->second.~T();\n      new (&(it->second)) T(std::forward<Args>(args)...);\n      return {it, false};\n    }\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace_or_assign(Key&& key, Args&&... args) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {\n          construct_node_at_end(std::move(key), std::forward<Args>(args)...),\n          true};\n    } else {\n      it->second.~T();\n      new (&(it->second)) T(std::forward<Args>(args)...);\n      return {it, false};\n    }\n  }\n\n  std::pair<iterator, bool> insert_or_assign(const Key& key, const T& obj) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(key, obj), true};\n    } else {\n      it->second = obj;\n      return {it, false};\n    }\n  }\n\n  std::pair<iterator, bool> insert_or_assign(const Key& key, T&& obj) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(key, std::move(obj)), true};\n    } else {\n      it->second = std::move(obj);\n      return {it, false};\n    }\n  }\n\n  // For Key of base::String type.\n  std::pair<iterator, bool> insert_or_assign(Key&& key, T&& obj) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(std::move(key), std::move(obj)), true};\n    } else {\n      it->second = std::move(obj);\n      return {it, false};\n    }\n  }\n\n  std::pair<iterator, bool> insert_or_assign(Key&& key, const T& obj) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(std::move(key), obj), true};\n    } else {\n      it->second = obj;\n      return {it, false};\n    }\n  }\n\n  std::pair<iterator, bool> insert_if_absent(const Key& key, const T& obj) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(key, obj), true};\n    } else {\n      return {it, false};\n    }\n  }\n\n  std::pair<iterator, bool> insert_if_absent(const Key& key, T&& obj) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(key, std::move(obj)), true};\n    } else {\n      return {it, false};\n    }\n  }\n\n  // For Key of base::String type.\n  std::pair<iterator, bool> insert_if_absent(Key&& key, T&& obj) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(std::move(key), std::move(obj)), true};\n    } else {\n      return {it, false};\n    }\n  }\n\n  std::pair<iterator, bool> insert_if_absent(Key&& key, const T& obj) {\n    auto it = inner_find(key, InsertionBuildMapThreshold);\n    if (it == end()) {\n      return {construct_node_at_end(std::move(key), obj), true};\n    } else {\n      return {it, false};\n    }\n  }\n\n  bool empty() const { return count_ == 0; }\n\n  size_type size() const noexcept { return count_; }\n\n  /// @brief Pre-allocate memory for next nodes to be inserted into the map.\n  /// This method only records the size of the memory pool required and does not\n  /// actually allocate memory. A whole block of memory is allocated only when\n  /// the first node data is inserted. Before the first node is inserted, you\n  /// can call this method multiple times, and it will record the maximum\n  /// required capacity. Once any node has been added to the map and the memory\n  /// pool is used, subsequent reserve() calls will not take effect.\n  void reserve(size_type count) {\n    if (pool_ == nullptr) {\n      if (count > pool_size_) {\n        pool_size_ = count > std::numeric_limits<uint16_t>::max()\n                         ? std::numeric_limits<uint16_t>::max()\n                         : static_cast<uint16_t>(count);\n      }\n    } else if (pool_cursor_ == 0 && count > pool_size_) {\n      // Pool allocated but not used and new reserving count is larger.\n      std::free(pool_);\n      pool_ = nullptr;\n      pool_size_ = count > std::numeric_limits<uint16_t>::max()\n                       ? std::numeric_limits<uint16_t>::max()\n                       : static_cast<uint16_t>(count);\n    } else {\n      // failed reserve\n    }\n    // Space size reserved and allocate pool until first node being allocated\n    // because user may do reserve but no element pushed.\n  }\n\n  /// @brief Unlike the reserve() method, it allows you to reduce the size of\n  /// the memory pool that needs to be created. LinkedHashMap uses\n  /// **kInitialAllocationSize** as the initial memory pool size by default.\n  /// If you only need a smaller capacity (for example, you know that the Map\n  /// will only have 1 to 2 elements in the end), you can use this method to\n  /// reduce the memory pool and save memory.\n  void set_pool_capacity(size_type count) {\n    if (count > pool_size_) {\n      reserve(count);\n    } else if (count < pool_size_) {\n      // Allows to set to smaller pool capacity.\n      if (pool_ == nullptr) {\n        pool_size_ = count > std::numeric_limits<uint16_t>::max()\n                         ? std::numeric_limits<uint16_t>::max()\n                         : static_cast<uint16_t>(count);\n      } else {\n        // Pool already allocated, no need to reset to smaller size.\n      }\n    }\n  }\n\n  /// This class is for testing only.\n  class Testing {\n   public:\n    /// @brief It calculates count of alive nodes which are on pool memory.\n    static size_t count_of_nodes_on_pool(const LinkedHashMap& map) {\n      size_t result = 0;\n      for (auto it = map.begin(); it != map.end(); ++it) {\n        if (map.ptr_on_pool(it.node_ptr())) {\n          result++;\n        }\n      }\n      return result;\n    }\n\n    static bool assume_status(const LinkedHashMap& map, bool has_map,\n                              bool is_perfect) {\n      return ((map.map_ != nullptr) == has_map) &&\n             (map.is_imperfect_ == !is_perfect);\n    }\n\n    static bool assume_end_in_initial_state(const LinkedHashMap& map) {\n      return map.end_.prev == map.end_as_link() &&\n             map.end_.next == map.end_as_link();\n    }\n\n    static bool check_consistency(const LinkedHashMap& map) {\n      if (map.empty()) {\n        bool end_in_initial_state = map.end_.prev == map.end_as_link() &&\n                                    map.end_.next == map.end_as_link();\n        if (!end_in_initial_state) {\n          return false;\n        }\n      }\n\n      if (map.map_ != nullptr) {\n        if (map.map_->size() != map.count_) {\n          return false;\n        }\n        for (auto it = map.begin(); it != map.end(); ++it) {\n          if (map.map_->find(it->first)->second != it) {\n            return false;\n          }\n        }\n      }\n\n      size_t count = 0;\n      for (auto it = map.begin(); it != map.end(); ++it) {\n        count++;\n      }\n      if (count != map.count_) {\n        return false;\n      }\n\n      if (!map.is_imperfect_) {\n        for (size_t i = 0; i < map.count_; i++) {\n          Node& n = map.pool_[i];\n          if (!map.ptr_on_pool(&n)) {\n            return false;\n          }\n\n          if (i == 0) {\n            if (n.prev != map.end_as_link()) {\n              return false;\n            }\n          } else {\n            if (n.prev != &map.pool_[i - 1]) {\n              return false;\n            }\n          }\n\n          if (i == map.count_ - 1) {\n            if (n.next != map.end_as_link()) {\n              return false;\n            }\n          } else {\n            if (n.next != &map.pool_[i + 1]) {\n              return false;\n            }\n          }\n        }\n      }\n\n      return true;\n    }\n  };\n\n private:\n  friend class Testing;\n\n  NodeBase end_;\n  Node* pool_{nullptr};\n  uint16_t pool_size_{kInitialAllocationSize};\n  uint16_t pool_cursor_;\n\n  union {\n    struct {\n      uint32_t count_ : 31;\n\n      // A pefect map only contains nodes continuous on pool memory.\n      // We can fast iterate nodes like array if self is perfect.\n      uint32_t is_imperfect_ : 1;\n    };\n\n    uint32_t __init__{0u};\n  };\n\n  // Map is created until element count reaches LinearFindThreshold.\n  map_type* map_{nullptr};\n\n  // If map was built, search by map or do linear search and create\n  // map if count of elements reaches build_map_threshold.\n  iterator inner_find(const Key& key, uint32_t build_map_threshold) {\n    iterator result = end();\n    if (map_ != nullptr) {\n      // map_ not null also means hash search may be better choice.\n      if (const auto map_it = map_->find(key); map_it != map_->end()) {\n        result = map_it->second;\n      }\n    } else if (count_ > build_map_threshold) {\n      // Do last time linear find and also build the map in the same loop.\n      map_ = new map_type();\n      map_->reserve(std::max<size_t>(pool_size_, count_));\n\n      if (is_imperfect_) {\n        // Not perfect nodes, loop as linked list.\n        auto i = begin();\n        const auto e = end();\n        for (; i != e; ++i) {\n          if (i->first == key) {\n            result = i;\n            break;\n          }\n          map_->emplace(i->first, i);\n        }\n\n        // Loop for remained index to build map, but no need to\n        // check equality with key.\n        for (; i != e; ++i) {\n          map_->emplace(i->first, i);\n        }\n      } else {\n        // List nodes are in perfect state, loop as an array for\n        // better performance.\n        size_t i = 0;\n        for (; i < count_; i++) {\n          Node& n = pool_[i];\n          if (n.value.first == key) {\n            result = iterator(&n);\n            break;\n          }\n          map_->emplace(std::piecewise_construct,\n                        std::forward_as_tuple(n.value.first),\n                        std::forward_as_tuple(&n));\n        }\n\n        // Loop for remained index to build map, but no need to\n        // check equality with key.\n        for (; i < count_; i++) {\n          Node& n = pool_[i];\n          map_->emplace(std::piecewise_construct,\n                        std::forward_as_tuple(n.value.first),\n                        std::forward_as_tuple(&n));\n        }\n      }\n    } else {\n      // Do linear find.\n      if (is_imperfect_) {\n        const auto e = end();\n        for (auto i = begin(); i != e; ++i) {\n          if (i->first == key) {\n            result = i;\n            break;\n          }\n        }\n      } else {\n        for (size_t i = 0; i < count_; i++) {\n          Node& n = pool_[i];\n          if (n.value.first == key) {\n            result = iterator(&n);\n            break;\n          }\n        }\n      }\n    }\n    return result;\n  }\n\n  inline bool alloc_pool() {\n    if (map_ != nullptr) {\n      map_->reserve(pool_size_);\n    }\n\n    pool_ = static_cast<Node*>(std::malloc(sizeof(Node) * pool_size_));\n    pool_cursor_ = 0;\n    if (pool_ != nullptr) {\n      return true;\n    } else {\n      // No more try to allocate pool.\n      pool_size_ = 0;\n      return false;\n    }\n  }\n\n  Node* alloc_node() {\n    if (pool_ == nullptr && pool_size_ > 0) {\n      if (alloc_pool()) {\n        return &pool_[pool_cursor_++];\n      }\n    }\n    if (pool_ != nullptr && pool_cursor_ < pool_size_) {\n      return &pool_[pool_cursor_++];\n    } else {\n      is_imperfect_ = 1;\n      return static_cast<Node*>(std::malloc(sizeof(Node)));\n    }\n  }\n\n  inline bool ptr_on_pool(Node* ptr) const {\n    return pool_ != nullptr && ptr >= pool_ && ptr < pool_ + pool_size_;\n  }\n\n  void free_node(Node* ptr) {\n    if (ptr_on_pool(ptr)) {\n      // node on pool, do nothing\n    } else {\n      std::free(ptr);\n    }\n  }\n\n  // To minimize binary size of construct_node_at_end.\n  __attribute__((noinline)) iterator finish_construct_node_at_end(Node* n) {\n    link_nodes_at_back(n, n);\n    count_++;\n    if (map_ != nullptr) {\n      return map_\n          ->emplace(std::piecewise_construct,\n                    std::forward_as_tuple(n->value.first),\n                    std::forward_as_tuple(n))\n          .first->second;\n    } else {\n      return iterator(n);\n    }\n  }\n\n  template <class... Args>\n  inline __attribute__((always_inline)) iterator construct_node_at_end(\n      const Key& key, Args&&... args) {\n    return finish_construct_node_at_end(\n        new (alloc_node()) Node(key, std::forward<Args>(args)...));\n  }\n\n  template <class... Args>\n  inline __attribute__((always_inline)) iterator construct_node_at_end(\n      Key&& key, Args&&... args) {\n    return finish_construct_node_at_end(\n        new (alloc_node()) Node(std::move(key), std::forward<Args>(args)...));\n  }\n\n  void link_nodes_at_back(NodeBase* f, NodeBase* l) {\n    l->next = end_as_link()->AsNode();\n    f->prev = end_.prev;\n    f->prev->next = f->AsNode();\n    end_.prev = l->AsNode();\n  }\n\n  void unlink_nodes(NodeBase* f, NodeBase* l) {\n    f->prev->next = l->next;\n    l->next->prev = f->prev;\n  }\n\n  const NodeBase* end_as_link() const { return &end_; }\n\n  NodeBase* end_as_link() { return &end_; }\n\n  iterator erase_node(const_iterator pos) {\n    auto n = pos.ptr;\n    auto r = n->next;\n    unlink_nodes(n, n);\n    auto nptr = n->AsNode();\n    nptr->~Node();\n    free_node(nptr);\n    count_--;\n    if (empty()) {\n      // All nodes removed, reset pool cursor so that pool can be reused or\n      // reserved with larger size.\n      pool_cursor_ = 0;\n      is_imperfect_ = 0;\n    } else {\n      is_imperfect_ = 1;\n    }\n    return iterator(r);\n  }\n\n  void list_clear() {\n    if (!is_imperfect_) {\n      // Fast path\n      for (size_t i = 0; i < count_; i++) {\n        pool_[i].~Node();\n      }\n      end_.Reset();\n    } else if (!empty()) {\n      auto f = end_.next;\n      auto l = end_as_link();\n      unlink_nodes(f, l->prev);\n      while (f != l) {\n        auto n = f->next;\n        auto nptr = f->AsNode();\n        nptr->~Node();\n        free_node(nptr);\n        f = n;\n      }\n    }\n  }\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_LINKED_HASH_MAP_H_\n"
  },
  {
    "path": "base/include/linked_hash_map_lldb.py",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport lldb\n\n# LLDB formatter for LinkedHashMap container.\n\n\nclass linked_hash_map_entry:\n\n    def __init__(self, entry):\n        self.entry = entry\n\n    def _next_impl(self):\n        return linked_hash_map_entry(self.entry.GetChildMemberWithName('next'))\n\n    def _prev_impl(self):\n        return linked_hash_map_entry(self.entry.GetChildMemberWithName('prev'))\n\n    def _value_impl(self):\n        return self.entry.GetValueAsUnsigned(0)\n\n    def _isnull_impl(self):\n        return self._value_impl() == 0\n\n    def _sbvalue_impl(self):\n        return self.entry\n\n    next = property(_next_impl, None)\n    value = property(_value_impl, None)\n    is_null = property(_isnull_impl, None)\n    sbvalue = property(_sbvalue_impl, None)\n\n\nclass linked_hash_map_iterator:\n\n    def increment_node(self, node):\n        if node.is_null:\n            return None\n        return node.next\n\n    def __init__(self, node):\n        self.node = linked_hash_map_entry(node)\n\n    def value(self):\n        return self.node.sbvalue  # and return the SBValue back on exit\n\n    def next(self):\n        node = self.increment_node(self.node)\n        if node is not None and node.sbvalue.IsValid() and not (node.is_null):\n            self.node = node\n            return self.value()\n        else:\n            return None\n\n    def advance(self, N):\n        if N < 0:\n            return None\n        if N == 0:\n            return self.value()\n        if N == 1:\n            return self.next()\n        while N > 0:\n            self.next()\n            N = N - 1\n        return self.value()\n\n\nclass linked_hash_map_SynthProvider:\n\n    def __init__(self, valobj, dict):\n        self.valobj = valobj\n        self.count = self.valobj.GetChildMemberWithName(\n            'count_').GetValueAsUnsigned()\n\n    def next_node(self, node):\n        return node.GetChildMemberWithName('next')\n\n    def value(self, node):\n        return node.GetValueAsUnsigned()\n\n    def num_children(self):\n        return self.count\n\n    def get_child_index(self, name):\n        try:\n            return int(name.lstrip('[').rstrip(']'))\n        except:\n            return -1\n\n    def get_child_at_index(self, index):\n        if index < 0:\n            return None\n        if index >= self.num_children():\n            return None\n        try:\n            current = linked_hash_map_iterator(self.head)\n            current = current.advance(index)\n            obj = current.GetChildMemberWithName('value')\n            return self.valobj.CreateValueFromData('[' + str(index) + ']',\n                                                   obj.GetData(),\n                                                   obj.GetType())\n        except:\n            return None\n\n    def update(self):\n        self.count = self.valobj.GetChildMemberWithName(\n            'count_').GetValueAsUnsigned()\n        end_node = self.valobj.GetChildMemberWithName('end_')\n        self.head = end_node.GetChildMemberWithName('next')\n\n    def has_children(self):\n        return True\n\n\ndef __lldb_init_module(debugger, dict):\n    debugger.HandleCommand(\n        'type synthetic add -x \"^lynx::base::LinkedHashMap<.*>$\" -l linked_hash_map_lldb.linked_hash_map_SynthProvider -w liblynx'\n    )\n    debugger.HandleCommand(\n        'type summary add -x \"^lynx::base::LinkedHashMap<.*>$\" -s \"size=${var.count_}, pool_size=${var.pool_size_}, pool_cursor=${var.pool_cursor_}, is_imperfect=${var.is_imperfect_}, map=${var.map_}\" -w liblynx'\n    )\n    debugger.HandleCommand('type category enable liblynx')\n"
  },
  {
    "path": "base/include/log/alog_wrapper.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_LOG_ALOG_WRAPPER_H_\n#define BASE_INCLUDE_LOG_ALOG_WRAPPER_H_\n\nnamespace lynx {\nnamespace base {\n\n#define ALOG_LEVEL_VERBOSE 0\n#define ALOG_LEVEL_DEBUG 1\n#define ALOG_LEVEL_INFO 2\n#define ALOG_LEVEL_WARN 3\n#define ALOG_LEVEL_ERROR 4\n\n// This parameter is only valid in darwin.\n#define ALOG_LEVEL_FATAL 5\n\nusing alog_write_func_ptr = void (*)(unsigned int level, const char* tag,\n                                     const char* msg);\nusing alog_init_mutex_cond_thread_ptr = void (*)(void);\n\nbool InitAlog(alog_write_func_ptr addr);\n\nvoid ALogWrite(unsigned int level, const char* tag, const char* msg);\n\nvoid ALogWriteV(const char* tag, const char* msg);\nvoid ALogWriteD(const char* tag, const char* msg);\nvoid ALogWriteI(const char* tag, const char* msg);\nvoid ALogWriteW(const char* tag, const char* msg);\nvoid ALogWriteE(const char* tag, const char* msg);\nvoid ALogWriteF(const char* tag, const char* msg);\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_LOG_ALOG_WRAPPER_H_\n"
  },
  {
    "path": "base/include/log/log_stream.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_LOG_LOG_STREAM_H_\n#define BASE_INCLUDE_LOG_LOG_STREAM_H_\n\n#include <atomic>\n#include <cstdint>\n#include <cstdlib>\n#include <cstring>  // for function memcpy\n#include <memory>\n#include <sstream>\n#include <string>\n#include <thread>\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\n\nconstexpr int32_t kMaximumBufferSize = 4096;\n\n// Cache logging data for the LogStream class, use the thread-local heap storage\n// to helps to avoid stack overflow in recursive scenarios while also improving\n// performance by reducing the frequency of heap memory allocation and\n// destruction Example:\n//    MixBuffer buffer;\n//    const std::string contexnt = \"hello world\"\n//    if (buffer.Available() > context.length()) {\n//      buffer.Append(content.c_str(), context.length());\n//    }\nclass MixBuffer {\n public:\n  MixBuffer()\n      : current_(Data()),\n        end_(Data() + static_cast<size_t>(kMaximumBufferSize)) {}\n\n  // uncopyable\n  MixBuffer(const MixBuffer&) = delete;\n  void operator=(const MixBuffer&) = delete;\n\n  // If there is enough space left, append additional logging datas.\n  void Append(const char* buffer, size_t length) {\n    if (static_cast<size_t>(Available()) > length) {\n      memcpy(current_, buffer, length);\n      AddLength(length);\n    }\n  }\n\n  // Initialize and access thread-local variables.\n  static char* Data() {\n    static thread_local std::unique_ptr<char[]> data =\n        std::make_unique<char[]>(kMaximumBufferSize);\n    return data.get();\n  }\n\n  int32_t Length() const { return static_cast<int32_t>(current_ - Data()); }\n  char* Current() const { return current_; }\n  int32_t Available() const { return static_cast<int32_t>(end_ - current_); }\n  // write at the beginning of the buffer\n  void Reset() { current_ = Data(); }\n  // empty the buffer\n  void Clear() {\n    memset(Data(), 0, static_cast<size_t>(kMaximumBufferSize));\n    Reset();\n  }\n\n private:\n  // upgate the length of buffer\n  void AddLength(size_t length) {\n    current_ += length;\n    *current_ = '\\0';\n  }\n\n  char* current_;\n  const char* const end_;\n};\n\n// brief: Replace std::iostream with class LogStream, overwrite operator<< for\n// base types, include: bool, char, int, int64, size_t, void*, float, double,\n// string, ostream, ostringstream In case of int, double, char*, address type,\n// It is faster and faster than snprintf and std::ostream Example:\n//    LogStream os;\n//    os << 2022 << \"-\" << 9 << \"-\" << 5 << \"  \" << \"Welcome to the world of\n//    lynx\"; std::cout << os.str() << std::endl;\n// !!!!!  Notice  !!!!!\n// 1、class LogStream don`t support format string, such as std::hex,\n//    std::setfill, std::setw. but you can do like this:\n//        std::ostringstream buf;\n//        LogStream os;\n//        buf << std::setfill('0') << std::setw(10) << 123456;\n//        os << buf;\n// 2、when buffer size is longer than 4096 bytes(kSmallBuffer), do nothing.such\n//    as ALog limits size to 4096 bytes\n// 3、when convert address to hex string,\n//    . if nullptr, the output is 0x00000000 in 32bits OS\n//      and 0x0000000000000000 in 64bits OS\n//    . have a fixed length output\n// 4、 convert const char * to string\n//    if nullptr, the output will be truncated\n\nclass BASE_EXPORT LogStream {\n public:\n  LogStream() = default;\n  ~LogStream() = default;\n\n  // uncopyable\n  LogStream(const LogStream&) = delete;\n  void operator=(const LogStream&) = delete;\n\n  LogStream& operator<<(bool);\n\n  /**\n   * replace snprintf with Milo based on branchlut scheme.[Written by Milo\n   * Yip][Designed by Wojciech Muła] about 25x speedup faster than snprintf\n   */\n  // integer\n  LogStream& operator<<(int8_t);\n  LogStream& operator<<(uint8_t);\n  LogStream& operator<<(int16_t);\n  LogStream& operator<<(uint16_t);\n  LogStream& operator<<(int32_t);\n  LogStream& operator<<(uint32_t);\n  LogStream& operator<<(int64_t);\n  LogStream& operator<<(uint64_t);\n\n// int64_t has different definition\n// long long in 32bits while long in 64bits Android and linux\n// long long in 64bits MacOS, iOS, Windows\n#if defined(__LP64__) && (defined(OS_ANDROID) || defined(__linux__))\n  LogStream& operator<<(long long value) {\n    return operator<<(static_cast<int64_t>(value));\n  }\n  LogStream& operator<<(unsigned long long value) {\n    return operator<<(static_cast<uint64_t>(value));\n  }\n#else\n  LogStream& operator<<(long value) {\n    return operator<<(static_cast<int64_t>(value));\n  }\n  LogStream& operator<<(unsigned long value) {\n    return operator<<(static_cast<uint64_t>(value));\n  }\n#endif\n\n  LogStream& operator<<(const void*);\n\n  /**\n   * replace snprintf with Milo based on Grisu2.[Written by Milo Yip][Designed\n   * by Florian Loitsch] about 9x speedup faster than snprintf 15 precision\n   * default\n   */\n  // TODO(lipin): overload float func with precision\n  LogStream& operator<<(float);\n  LogStream& operator<<(double);\n\n  LogStream& operator<<(const char&);\n\n  LogStream& operator<<(const char*);\n  LogStream& operator<<(const unsigned char* value) {\n    return operator<<(reinterpret_cast<const char*>(value));\n  }\n  LogStream& operator<<(unsigned char* const value) {\n    return operator<<(reinterpret_cast<const char*>(value));\n  }\n  LogStream& operator<<(char* const value) {\n    return operator<<(static_cast<const char*>(value));\n  }\n\n  LogStream& operator<<(const std::string&);\n  LogStream& operator<<(const std::string_view&);\n\n  // overload for wchar_t, std::wstring\n#if defined(OS_WIN)\n  LogStream& operator<<(wchar_t value);\n  LogStream& operator<<(const wchar_t* value);\n  LogStream& operator<<(const std::wstring& value);\n  LogStream& operator<<(const std::wstring_view& value);\n#endif\n\n  // operator for std::ostream\n  LogStream& operator<<(const std::ostringstream& output) {\n    return operator<<(output.str());\n  }\n\n  friend std::ostream& operator<<(std::ostream& output,\n                                  const LogStream& message) {\n    output << message.c_str();\n    return output;\n  }\n\n  LogStream& operator<<(const LogStream& output) {\n    Append(output.c_str(), output.Buffer().Length());\n    return *this;\n  }\n\n  // overload for std::shared_ptr\n  template <typename T>\n  LogStream& operator<<(const std::shared_ptr<T>& value) {\n    return operator<<(value.get());\n  }\n\n  // overload for std::unique_ptr\n  template <typename T>\n  LogStream& operator<<(const std::unique_ptr<T>& value) {\n    return operator<<(value.get());\n  }\n\n  // overload for std::weak_ptr\n  template <typename T>\n  LogStream& operator<<(const std::weak_ptr<T>& value) {\n    return operator<<(value.lock().get());\n  }\n\n  // overload for std::atomic\n  // type which is not trivially copyable is not supported\n  // support UDT which must be trivially copyable, such as:\n  // class SelfType {\n  //   double value_;\n  //   public:\n  //    explicit SelfType(double value) : value_(value) {}\n  //    inline friend LogStream& operator<<(LogStream& output, const SelfType&\n  //    input) {\n  //     output << input.value_;\n  //     return output;\n  //    }\n  // };\n\n  template <typename T>\n  LogStream& operator<<(const std::atomic<T>& value) {\n    *this << value.load();\n    return *this;\n  }\n\n  // overload for std::endl\n  // Function implementation:\n  // *\n  //   template <class _CharT, class _Traits>\n  //   inline _LIBCPP_INLINE_VISIBILITY\n  //   basic_ostream<_CharT, _Traits>&\n  //   endl(basic_ostream<_CharT, _Traits>& __os)\n  //   {\n  //       __os.put(__os.widen('\\n'));\n  //       __os.flush();\n  //       return __os;\n  //   }\n  // *\n  // !!!NOTICE: !!!\n  // Need to distinguish the same implementation between std::endl, std::ends\n  // and std::flush\n  using CharT_ = char;\n  using TraitsT_ = std::char_traits<CharT_>;\n  LogStream& operator<<(std::basic_ostream<CharT_, TraitsT_>& (*function_endl)(\n      std::basic_ostream<CharT_, TraitsT_>&)) {\n    if (function_endl == std::endl<CharT_, TraitsT_>) {\n#if defined(OS_WIN)\n      return operator<<(\"\\r\\n\");\n#else\n      return operator<<(\"\\n\");\n#endif\n    }\n// for debug, if not std::endl, than abort\n// for release, do nothing\n#ifndef NDEBUG\n    else {\n      // only support type std::endl;\n      // you need to overload opertator<< for UDT\n      abort();\n    }\n#endif\n    return *this;\n  }\n\n  // overload\n  // convert std::thread::id into a hexadecimal string in uppercase form\n  LogStream& operator<<(const std::thread::id& value) {\n    std::stringstream ss;\n    ss << std::uppercase << std::hex << value;\n    return operator<<(ss.str());\n  }\n\n  void Append(const char* buffer, size_t length) {\n    buffer_.Append(buffer, length);\n  }\n  const MixBuffer& Buffer() const { return buffer_; }\n  const char* c_str() const { return buffer_.Data(); }\n  std::string str() const { return std::string(buffer_.Data()); }\n  void Reset() { buffer_.Reset(); }\n  void Clear() { buffer_.Clear(); }\n\n private:\n  MixBuffer buffer_;\n};\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_LOG_LOG_STREAM_H_\n"
  },
  {
    "path": "base/include/log/logging.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_LOG_LOGGING_H_\n#define BASE_INCLUDE_LOG_LOGGING_H_\n\n#include <memory>\n#include <sstream>\n#include <string>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/log/alog_wrapper.h\"\n#include \"base/include/log/log_stream.h\"\n#include \"base/include/path_utils.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\n\nclass LogMessage;\n\nusing PlatformLogCallBack = void (*)(LogMessage* msg, const char* tag);\nusing InitAlogCallBack = alog_write_func_ptr (*)();\n\nBASE_EXPORT void InitLynxLogging(InitAlogCallBack initAlogCallback,\n                                 PlatformLogCallBack PlatformLogCallBack,\n                                 bool isPrintAllLogToAllChannels);\n\nBASE_EXPORT void SetMinLogLevel(int level);\n\nBASE_EXPORT int GetMinLogLevel();\n\nBASE_EXPORT void PrintLogToLynxLogging(int level, const char* tag,\n                                       const char* message);\n\nbool HasInitedLynxLogWriteFunction();\nvoid EnableLogOutputByPlatform();\nvoid DisableLogOutputByPlatform();\n\n#define LYNX_LOG_LEVEL_VERBOSE 0\n#define LYNX_LOG_LEVEL_DEBUG 1\n#define LYNX_LOG_LEVEL_INFO 2\n#define LYNX_LOG_LEVEL_WARNING 3\n#define LYNX_LOG_LEVEL_ERROR 4\n#define LYNX_LOG_LEVEL_FATAL 5\n#define LYNX_LOG_LEVEL_NUM 6\n\ntypedef int LogSeverity;\nconst LogSeverity LOG_VERBOSE = LYNX_LOG_LEVEL_VERBOSE;\nconst LogSeverity LOG_DEBUG = LYNX_LOG_LEVEL_DEBUG;\nconst LogSeverity LOG_INFO = LYNX_LOG_LEVEL_INFO;\nconst LogSeverity LOG_WARNING = LYNX_LOG_LEVEL_WARNING;\nconst LogSeverity LOG_ERROR = LYNX_LOG_LEVEL_ERROR;\n// The error in windi.h in the windows environment will pollute the LOG_0 here,\n// and a LOG_0 needs to be defined directly\n#if defined(_WIN32) && !defined(LOG_0)\n#define LOG_0 LOG_ERROR\n#endif\nconst LogSeverity LOG_FATAL = LYNX_LOG_LEVEL_FATAL;\nconst LogSeverity LOG_NUM_SEVERITIES = LYNX_LOG_LEVEL_NUM;\n\n// !!! Need to be aligned with platform layer. !!!\n// !!! in file <LogSource.java> !!!\ntypedef int LogSource;\nconst LogSource LOG_SOURCE_NATIVE = 0;\nconst LogSource LOG_SOURCE_JS = 1;\nconst LogSource LOG_SOURCE_JS_EXT =\n    2;  // used for console.alog & console.report\n\n// channel type from lynx internal and external developer\nusing LogChannel = int32_t;\nconstexpr LogChannel LOG_CHANNEL_LYNX_INTERNAL = 0;\nconstexpr LogChannel LOG_CHANNEL_LYNX_EXTERNAL = 1;\n\n// A lightweight class that consumes and discards all stream inputs.\nclass NullLogStream {\n public:\n  NullLogStream() {}\n  template <typename T>\n  NullLogStream& operator<<(T) {\n    return *this;\n  }\n};\n\n// This class is used to explicitly ignore values in the conditional\n// logging macros.  This avoids compiler warnings like \"value computed\n// is not used\" and \"statement has no effect\".\nclass LogMessageVoidify {\n public:\n  LogMessageVoidify() {}\n  // This has to be an operator with a precedence lower than << but\n  // higher than ?:\n  void operator&(LogStream&) {}\n  void operator&(const NullLogStream&) {}\n};\n\n#ifdef __FILE_NAME__\n#define __LOG_FILE_NAME__ __FILE_NAME__\n#else\n#define __LOG_FILE_NAME__ \\\n  lynx::base::PathUtils::GetLastPath(__FILE__, sizeof(__FILE__) - 1)\n#endif\n\n#define LOG_IS_ON(severity)                 \\\n  ((lynx::base::logging::LOG_##severity) >= \\\n   lynx::base::logging::GetMinLogLevel())\n\n#define LOG_STREAM(severity)                                           \\\n  lynx::base::logging::LogMessage(__LOG_FILE_NAME__, __LINE__,         \\\n                                  lynx::base::logging::LOG_##severity) \\\n      .stream()\n\n#define LOG_STREAM_INFO \\\n  lynx::base::logging::LogMessageI(__LOG_FILE_NAME__, __LINE__).stream()\n\n#define LOG_STREAM_WARNING \\\n  lynx::base::logging::LogMessageW(__LOG_FILE_NAME__, __LINE__).stream()\n\n#define LOG_STREAM_ERROR \\\n  lynx::base::logging::LogMessageE(__LOG_FILE_NAME__, __LINE__).stream()\n\n#define JS_LOG_STREAM(severity, source, runtime_id, channel_type)       \\\n  lynx::base::logging::LogMessage(                                      \\\n      __LOG_FILE_NAME__, __LINE__, lynx::base::logging::LOG_##severity, \\\n      lynx::base::logging::LOG_SOURCE_JS, runtime_id, channel_type)     \\\n      .stream()\n\n#define JS_ALOG_STREAM(severity, source, runtime_id, channel_type)      \\\n  lynx::base::logging::LogMessage(                                      \\\n      __LOG_FILE_NAME__, __LINE__, lynx::base::logging::LOG_##severity, \\\n      lynx::base::logging::LOG_SOURCE_JS_EXT, runtime_id, channel_type) \\\n      .stream()\n\n#define LAZY_STREAM(stream, condition) \\\n  !(condition) ? (void)0 : lynx::base::logging::LogMessageVoidify() & (stream)\n\n// Add a new FML-style LOG macro\n#define BASE_LOG(severity) \\\n  LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity))\n\n// Use this macro to suppress warning if the variable in log is not used.\n#define UNUSED_LOG_VARIABLE __attribute__((unused))\n\n#ifndef LYNX_MIN_LOG_LEVEL\n#ifdef NDEBUG\n#define LYNX_MIN_LOG_LEVEL LYNX_LOG_LEVEL_INFO\n#else\n#define LYNX_MIN_LOG_LEVEL LYNX_LOG_LEVEL_VERBOSE\n#endif\n#endif\n\n// TODO(zhixuan): Currently, the usage of log macros is like \"LOGI(\"abc\" <<\n// variable)\", which is mixed of stream pattern and format string pattern.\n// Change the logging fashion entirely to format string pattern in future.\n#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_VERBOSE\n#define LOGV(msg) \\\n  { LAZY_STREAM(LOG_STREAM(VERBOSE), LOG_IS_ON(VERBOSE)) << msg; }\n#else\n#define LOGV(msg)\n#endif\n\n#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_DEBUG\n#define LOGD(msg) \\\n  { LAZY_STREAM(LOG_STREAM(DEBUG), LOG_IS_ON(DEBUG)) << msg; }\n#else\n#define LOGD(msg)\n#endif\n\n#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_INFO\n#define LOGI(msg) \\\n  { LAZY_STREAM(LOG_STREAM_INFO, LOG_IS_ON(INFO)) << msg; }\n#else\n#define LOGI(msg)\n#endif\n\n#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_WARNING\n#define LOGW(msg) \\\n  { LAZY_STREAM(LOG_STREAM_WARNING, LOG_IS_ON(WARNING)) << msg; }\n#else\n#define LOGW(msg)\n#endif\n\n#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_ERROR\n#define LOGE(msg) \\\n  { LAZY_STREAM(LOG_STREAM_ERROR, LOG_IS_ON(ERROR)) << msg; }\n#else\n#define LOGE(msg)\n#endif\n\n#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_ERROR\n#define LOGR(msg) \\\n  { LAZY_STREAM(LOG_STREAM_ERROR, LOG_IS_ON(ERROR)) << msg; }\n#else\n#define LOGR(msg)\n#endif\n\n#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_FATAL\n#define LOGF(msg) \\\n  { LAZY_STREAM(LOG_STREAM(FATAL), LOG_IS_ON(FATAL)) << msg; }\n#else\n#define LOGF(msg)\n#endif\n\n#define JSLOG(severity, runtime_id, channel_type) \\\n  LAZY_STREAM(JS_LOG_STREAM(severity, JS, runtime_id, channel_type), true)\n\n#define JSALOG(severity, runtime_id, channel_type) \\\n  LAZY_STREAM(JS_ALOG_STREAM(severity, JS, runtime_id, channel_type), true)\n\n#define CHECK(condition)                       \\\n  LAZY_STREAM(LOG_STREAM(FATAL), !(condition)) \\\n      << \"Check failed: \" #condition \". \"\n\n// now support DCHECK(...) << ...\n#ifndef DCHECK\n// for debug, if check failed, log fatal and abort\n#ifndef NDEBUG\n#define DCHECK(condition) CHECK(condition)\n#else\n// In release builds, DCHECK is a no-op that still consumes the stream.\n#define DCHECK(condition)                                          \\\n  true || (condition) ? (void)0                                    \\\n                      : lynx::base::logging::LogMessageVoidify() & \\\n                            lynx::base::logging::NullLogStream()\n#endif\n#endif\n\n#define DCHECK_EQ(v1, v2) DCHECK((v1) == (v2))\n\n#if defined(__clang__) || defined(__GNUC__)\n#define LYNX_BUILTIN_UNREACHABLE() __builtin_unreachable()\n#elif defined(_MSC_VER)\n#define LYNX_BUILTIN_UNREACHABLE() __assume(false)\n#else\n#define LYNX_BUILTIN_UNREACHABLE() abort()\n#endif\n\n#undef NOTREACHED\n#define NOTREACHED()            \\\n  do {                          \\\n    LOGF(\"Abort here!!!\");      \\\n    LYNX_BUILTIN_UNREACHABLE(); \\\n  } while (0);\n\n// A macro to provide a stream for logging a function that is not implemented.\n// In debug builds, it will log an ERROR. In release builds, it's a no-op.\n#ifndef NDEBUG\n#define UNIMPLEMENTED() \\\n  BASE_LOG(ERROR) << \"Not implemented in \" << __FUNCTION__ << \"() \";\n#else\n#define UNIMPLEMENTED() (void)0;\n#endif\n\nclass BASE_EXPORT LogMessage {\n public:\n  LogMessage(const char* file, int line, LogSeverity severity, LogSource source,\n             int64_t rt_id, LogChannel channel_type);\n  LogMessage(const char* file, int line, LogSeverity severity);\n  ~LogMessage();\n  LogStream& stream() { return stream_; }\n  LogSeverity severity() { return severity_; }\n  LogSource source() { return source_; }\n  size_t messageStart() { return message_start_; }\n  int64_t runtimeId() { return runtime_id_; }\n  LogChannel ChannelType() { return channel_type_; }\n\n private:\n  void Init(const char* file, int line);\n\n  LogSeverity severity_;\n  LogStream stream_;\n  size_t message_start_;\n\n  const char* file_;\n  const int line_;\n  LogSource source_;\n  int64_t runtime_id_;\n  LogChannel channel_type_;\n  LogMessage(const LogMessage&) = delete;\n  LogMessage& operator=(const LogMessage&) = delete;\n};\n\n/**\n Specialized for different log levels to omit severity argument\n at callsite for binary size optimization.\n */\nclass BASE_EXPORT LogMessageI : public LogMessage {\n public:\n  LogMessageI(const char* file, int line) : LogMessage(file, line, LOG_INFO) {}\n};\n\nclass BASE_EXPORT LogMessageW : public LogMessage {\n public:\n  LogMessageW(const char* file, int line)\n      : LogMessage(file, line, LOG_WARNING) {}\n};\n\nclass BASE_EXPORT LogMessageE : public LogMessage {\n public:\n  LogMessageE(const char* file, int line) : LogMessage(file, line, LOG_ERROR) {}\n};\n\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_LOG_LOGGING_H_\n"
  },
  {
    "path": "base/include/log/logging_base.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_LOG_LOGGING_BASE_H_\n#define BASE_INCLUDE_LOG_LOGGING_BASE_H_\n\n#include <cstdint>\n#include <vector>\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\n\nvoid InitLynxBaseLog(bool is_print_log_to_all_channel);\n\nenum LynxLogSource {\n  LynxLogSourceNaitve = 1 << 0,\n  LynxLogSourceJS = 1 << 1,\n};\n\ntypedef void (*LynxLogFunction)(int level, const char* message);\n\nstruct LynxLogDelegate {\n  LynxLogFunction log_function;\n  int min_log_level;\n  bool should_format_message;\n  LynxLogSource accept_source;\n  int64_t accept_runtime_id;\n\n  LynxLogDelegate() {\n    log_function = nullptr;\n    min_log_level = -1;\n    should_format_message = true;\n    accept_source = LynxLogSource(LynxLogSourceNaitve | LynxLogSourceJS);\n    accept_runtime_id = -1;\n  }\n};\n\nBASE_EXPORT void SetDebugLoggingDelegate(LynxLogDelegate* delegate);\nBASE_EXPORT int AddLoggingDelegate(LynxLogDelegate* delegate);\nBASE_EXPORT LynxLogDelegate* GetLoggingDelegate(int delegate_id);\nBASE_EXPORT std::vector<LynxLogDelegate*> GetLoggingDelegates();\nBASE_EXPORT void RemoveLoggingDelegate(int delegate_id);\nBASE_EXPORT void SetMinimumLoggingLevel(int min_log_level);\nBASE_EXPORT void SetJSLogsFromExternalChannels(bool is_open);\nBASE_EXPORT int GetMinimumLoggingLevel();\n\nBASE_EXPORT int LynxSetLogFunction(LynxLogFunction log_function);\nBASE_EXPORT LynxLogFunction LynxGetLogFunction();\n\n}  // namespace lynx\n#endif  // BASE_INCLUDE_LOG_LOGGING_BASE_H_\n"
  },
  {
    "path": "base/include/log/logging_darwin.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_LOG_LOGGING_DARWIN_H_\n#define BASE_INCLUDE_LOG_LOGGING_DARWIN_H_\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\n\nconstexpr const char* kLynxLogLevels[] = {\"V\", \"D\", \"I\", \"W\", \"E\", \"F\"};\n\nvoid InitLynxLoggingNative(void* log_address,\n                           PlatformLogCallBack platform_log_channel,\n                           bool print_logs_to_all_channels);\n\nvoid SetLynxLogMinLevel(int min_level);\n\nvoid InternalLogNative(const char* file, int32_t line, int level,\n                       const char* message);\n\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_LOG_LOGGING_DARWIN_H_\n"
  },
  {
    "path": "base/include/lru_cache.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_LRU_CACHE_H_\n#define BASE_INCLUDE_LRU_CACHE_H_\n#include <stddef.h>\n\n#include <functional>\n#include <list>\n#include <unordered_map>\n#include <utility>\n\nnamespace lynx {\nnamespace base {\n\n#define DEFAULT_CAPACITY 500\n\ntemplate <typename Key, typename Value>\nclass LRUCache {\n public:\n  LRUCache() : capacity_(DEFAULT_CAPACITY) {}\n  explicit LRUCache(size_t capacity) : capacity_(capacity) {}\n\n  Value* Get(const Key& key) {\n    auto it = cache_map_.find(key);\n    if (it == cache_map_.end()) {\n      return nullptr;\n    }\n    cache_list_.splice(cache_list_.begin(), cache_list_, it->second);\n    return &(it->second->second);\n  }\n\n  void Put(const Key& key, Value value) {\n    auto it = cache_map_.find(key);\n    if (it != cache_map_.end()) {\n      it->second->second = std::move(value);\n      cache_list_.splice(cache_list_.begin(), cache_list_, it->second);\n      return;\n    }\n    if (cache_map_.size() == capacity_) {\n      auto last = cache_list_.back();\n      cache_map_.erase(last.first);\n      cache_list_.pop_back();\n    }\n    cache_list_.emplace_front(key, std::move(value));\n    cache_map_[key] = cache_list_.begin();\n  }\n\n  void Clear() {\n    cache_list_.clear();\n    cache_map_.clear();\n  }\n\n private:\n  size_t capacity_;\n  std::list<std::pair<Key, Value>> cache_list_;\n  std::unordered_map<Key, typename std::list<std::pair<Key, Value>>::iterator>\n      cache_map_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_LRU_CACHE_H_\n"
  },
  {
    "path": "base/include/lynx_actor.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_LYNX_ACTOR_H_\n#define BASE_INCLUDE_LYNX_ACTOR_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n\nnamespace lynx {\nnamespace shell {\n\n// if you don't need instance id, just use kUnknownInstanceId.\nconstexpr static int32_t kUnknownInstanceId = -1;\n\ntemplate <typename C, typename T, typename Enable = void>\nclass LynxActorMixin {\n protected:\n  void BeforeInvoked() {}\n\n  void AfterInvoked() {}\n};\n\n// actor for each thread\ntemplate <typename T>\nclass LynxActor : public LynxActorMixin<LynxActor<T>, T>,\n                  public std::enable_shared_from_this<LynxActor<T>> {\n public:\n  LynxActor(std::unique_ptr<T> impl, fml::RefPtr<fml::TaskRunner> runner,\n            int32_t instance_id = kUnknownInstanceId, bool enable = true)\n      : impl_(std::move(impl)),\n        runner_(std::move(runner)),\n        instance_id_(instance_id),\n        enable_(enable) {}\n  ~LynxActor() {}\n\n  template <typename F>\n  void Act(F&& func) {\n    if (!enable_) {\n      return;\n    }\n    if (runner_->RunsTasksOnCurrentThread()) {\n      Invoke(std::forward<F>(func));\n    } else {\n      runner_->PostTask([self = this->shared_from_this(),\n                         func = std::forward<F>(func)]() mutable {\n        self->Invoke(std::forward<F>(func));\n      });\n    }\n  }\n\n  // Warning: ONlY for LynxActor<LayoutContext>! Don't use it in other\n  // LynxActors.\n  template <typename F>\n  void ActLite(F&& func) {\n    if (!enable_) {\n      return;\n    }\n    if (runner_->RunsTasksOnCurrentThread()) {\n      InvokeLite(func);\n    } else {\n      runner_->PostTask(\n          [self = this->shared_from_this(),\n           func = std::forward<F>(func)]() mutable { self->InvokeLite(func); });\n    }\n  }\n\n  template <typename F>\n  void ActAsync(F&& func) {\n    if (!enable_) {\n      return;\n    }\n\n    runner_->PostTask([self = this->shared_from_this(),\n                       func = std::forward<F>(func)]() mutable {\n      self->Invoke(std::forward<F>(func));\n    });\n  }\n\n  template <typename F>\n  void ActIdle(F&& func) {\n    if (!enable_) {\n      return;\n    }\n\n    runner_->PostIdleTask([self = this->shared_from_this(),\n                           func = std::forward<F>(func)]() mutable {\n      self->Invoke(std::forward<F>(func));\n    });\n  }\n\n  template <typename F>\n  void ActEmergency(F&& func) {\n    if (!enable_) {\n      return;\n    }\n\n    runner_->PostEmergencyTask([self = this->shared_from_this(),\n                                func = std::forward<F>(func)]() mutable {\n      self->Invoke(std::forward<F>(func));\n    });\n  }\n\n  template <typename F,\n            typename = std::enable_if_t<!std::is_void<\n                std::invoke_result_t<F, std::unique_ptr<T>&>>::value>>\n  auto ActSync(F&& func) {\n    std::invoke_result_t<F, std::unique_ptr<T>&> result;\n    ActSync([&result, func = std::forward<F>(func)](auto& impl) mutable {\n      result = func(impl);\n    });\n    return result;\n  }\n\n  template <typename F,\n            typename = std::enable_if_t<std::is_void<\n                std::invoke_result_t<F, std::unique_ptr<T>&>>::value>>\n  void ActSync(F&& func) {\n    if (!enable_) {\n      return;\n    }\n    runner_->PostSyncTask([this, func = std::forward<F>(func)]() mutable {\n      Invoke(std::forward<F>(func));\n    });\n  }\n\n  // TODO(heshan):now use for LynxRuntime, will remove,\n  // now need for devtool...\n  T* Impl() { return impl_.get(); }\n  // TODO(lipin): now use for LayoutMediator,maybe remove later\n  bool CanRunNow() { return runner_->RunsTasksOnCurrentThread(); }\n\n  int32_t GetInstanceId() { return instance_id_; }\n\n  fml::RefPtr<fml::TaskRunner>& GetRunner() { return runner_; }\n\n  // Create a new LynxActor instance using current impl.\n  // Retaining existing IDs during reuse facilitates precise identification of\n  // corresponding Engine objects.\n  std::shared_ptr<LynxActor<T>> TransferToNewActor(\n      fml::RefPtr<fml::TaskRunner> new_runner, bool new_enable = true) {\n    if (!enable_ || !impl_) {\n      return nullptr;\n    }\n    std::shared_ptr<LynxActor<T>> new_actor;\n    if (runner_->RunsTasksOnCurrentThread()) {\n      if (impl_) {\n        new_actor = std::make_shared<LynxActor<T>>(\n            std::move(impl_), std::move(new_runner), instance_id_, new_enable);\n      }\n    } else {\n      runner_->PostSyncTask([this, &new_runner, new_enable, &new_actor]() {\n        if (impl_) {\n          new_actor = std::make_shared<LynxActor<T>>(std::move(impl_),\n                                                     std::move(new_runner),\n                                                     instance_id_, new_enable);\n        }\n      });\n    }\n    return new_actor;\n    // TODO(huangweiwu): If more aggressive technical solutions are required in\n    // the future, we will consider whether to support actively pausing the\n    // runner here.\n  }\n\n private:\n  template <typename F>\n  void Invoke(F&& func) {\n    LynxActorMixin<LynxActor<T>, T>::BeforeInvoked();\n\n    if (impl_ != nullptr) {\n      func(impl_);\n    }\n\n    LynxActorMixin<LynxActor<T>, T>::AfterInvoked();\n  }\n\n  template <typename F>\n  void InvokeLite(F&& func) {\n    if (impl_ != nullptr) {\n      func(impl_);\n    }\n  }\n\n  std::unique_ptr<T> impl_;\n\n  fml::RefPtr<fml::TaskRunner> runner_;\n\n  // Generated in the LynxShell, id of LynxShell.\n  // instance_id_ is a value greater than or equal to 0, the initial value is\n  // -1.\n  const int32_t instance_id_;\n\n  bool enable_ = true;\n};\n\n}  // namespace shell\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_LYNX_ACTOR_H_\n"
  },
  {
    "path": "base/include/md5.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// a small class for calculating MD5 hashes of strings or byte arrays\n// it is not meant to be fast or secure\n//\n// usage: 1) feed it blocks of uchars with update()\n//      2) finalize()\n//      3) get hexdigest() string\n//      or\n//      MD5(std::string).hexdigest()\n//\n// assumes that char is 8 bit and int is 32 bit\n\n#ifndef BASE_INCLUDE_MD5_H_\n#define BASE_INCLUDE_MD5_H_\n\n#include <algorithm>\n#include <string>\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass MD5 {\n public:\n  typedef unsigned int size_type;  // must be 32bit\n\n  MD5();\n  MD5(const std::string& text);\n  MD5(const char* text, size_t length);\n  void update(const unsigned char* buf, size_type length);\n  void update(const char* buf, size_type length);\n  MD5& finalize();\n  std::string hexdigest() const;\n  friend std::ostream& operator<<(std::ostream&, MD5 md5);\n\n private:\n  void init();\n  typedef unsigned char uint1;  //  8bit\n  typedef unsigned int uint4;   // 32bit\n  enum { blocksize = 64 };      // VC6 won't eat a const static int here\n\n  void transform(const uint1 block[blocksize]);\n  static void decode(uint4 output[], const uint1 input[], size_type len);\n  static void encode(uint1 output[], const uint4 input[], size_type len);\n\n  bool finalized;\n  uint1 buffer[blocksize];  // bytes that didn't fit in last 64 byte chunk\n  uint4 count[2];           // 64bit counter for number of bits (lo, hi)\n  uint4 state[4];           // digest so far\n  uint1 digest[16];         // the result\n\n  // low level logic operations\n  static inline uint4 F(uint4 x, uint4 y, uint4 z);\n  static inline uint4 G(uint4 x, uint4 y, uint4 z);\n  static inline uint4 H(uint4 x, uint4 y, uint4 z);\n  static inline uint4 I(uint4 x, uint4 y, uint4 z);\n  static inline uint4 rotate_left(uint4 x, int n);\n  static inline void FF(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,\n                        uint4 ac);\n  static inline void GG(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,\n                        uint4 ac);\n  static inline void HH(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,\n                        uint4 ac);\n  static inline void II(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,\n                        uint4 ac);\n};\n\nBASE_EXPORT std::string md5(const std::string& str);\nBASE_EXPORT std::string md5(const char* data, const size_t length);\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_MD5_H_\n"
  },
  {
    "path": "base/include/memory/memory_pressure_level.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_MEMORY_MEMORY_PRESSURE_LEVEL_H_\n#define BASE_INCLUDE_MEMORY_MEMORY_PRESSURE_LEVEL_H_\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\nnamespace base {\n\n// A Java counterpart is in\n// lynx/base/platform/android/src/main/java/com/lynx/base/memory/MemoryPressureLevel.java\nenum BASE_EXPORT MemoryPressureLevel {\n  // No problems, there is enough memory to use. This event is not sent via\n  // callback.\n  MEMORY_PRESSURE_LEVEL_NONE = 0,\n\n  // Modules are advised to free buffers that are cheap to re-allocate and not\n  // immediately needed.\n  MEMORY_PRESSURE_LEVEL_MODERATE = 1,\n\n  // At this level, modules are advised to free all possible memory.  The\n  // alternative is to be killed by the system, which means all memory will\n  // have to be re-created, plus the cost of a cold start.\n  MEMORY_PRESSURE_LEVEL_CRITICAL = 2,\n\n  // This must be the last value in the enum. The casing is different from the\n  // other values to make this enum work well with the\n  // UMA_HISTOGRAM_ENUMERATION macro.\n  kMaxValue = MEMORY_PRESSURE_LEVEL_CRITICAL,\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_MEMORY_MEMORY_PRESSURE_LEVEL_H_\n"
  },
  {
    "path": "base/include/no_destructor.h",
    "content": "// Copyright 2018 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef BASE_INCLUDE_NO_DESTRUCTOR_H_\n#define BASE_INCLUDE_NO_DESTRUCTOR_H_\n\n#include <new>\n#include <utility>\n\nnamespace lynx {\nnamespace base {\n\ntemplate <typename T>\nclass NoDestructor {\n public:\n  // Not constexpr; just write static constexpr T x = ...; if the value should\n  // be a constexpr.\n  template <typename... Args>\n  explicit NoDestructor(Args &&...args) {\n    new (storage_) T(std::forward<Args>(args)...);\n  }\n\n  // Allows copy and move construction of the contained type, to allow\n  // construction from an initializer list, e.g. for std::vector.\n  explicit NoDestructor(const T &x) { new (storage_) T(x); }\n\n  explicit NoDestructor(T &&x) { new (storage_) T(std::move(x)); }\n\n  NoDestructor(const NoDestructor &) = delete;\n\n  NoDestructor &operator=(const NoDestructor &) = delete;\n\n  ~NoDestructor() = default;\n\n  const T &operator*() const { return *get(); }\n\n  T &operator*() { return *get(); }\n\n  const T *operator->() const { return get(); }\n\n  T *operator->() { return get(); }\n\n  const T *get() const { return reinterpret_cast<const T *>(storage_); }\n\n  T *get() { return reinterpret_cast<T *>(storage_); }\n\n private:\n  alignas(T) char storage_[sizeof(T)];\n\n#if defined(LEAK_SANITIZER)\n  // TODO(https://crbug.com/812277): This is a hack to work around the fact\n  // that LSan doesn't seem to treat NoDestructor as a root for reachability\n  // analysis. This means that code like this:\n  //   static base::NoDestructor<std::vector<int>> v({1, 2, 3});\n  // is considered a leak. Using the standard leak sanitizer annotations to\n  // suppress leaks doesn't work: std::vector is implicitly constructed before\n  // calling the base::NoDestructor constructor.\n  //\n  // Unfortunately, I haven't been able to demonstrate this issue in simpler\n  // reproductions: until that's resolved, hold an explicit pointer to the\n  // placement-new'd object in leak sanitizer mode to help LSan realize that\n  // objects allocated by the contained type are still reachable.\n  T *storage_ptr_ = reinterpret_cast<T *>(storage_);\n#endif  // defined(LEAK_SANITIZER)\n};\n\n}  // namespace base\n}  // namespace lynx\n\nnamespace fml {\nusing lynx::base::NoDestructor;\n}\n\n#endif  // BASE_INCLUDE_NO_DESTRUCTOR_H_\n"
  },
  {
    "path": "base/include/path_utils.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_PATH_UTILS_H_\n#define BASE_INCLUDE_PATH_UTILS_H_\n\n#include <sstream>\n#include <string>\n#include <vector>\n\nnamespace lynx {\nnamespace base {\nclass PathUtils {\n public:\n  // Convert relative path of local resources to absolute path\n  static std::string RedirectUrlPath(const std::string &dirname,\n                                     const std::string &url) {\n    if (url.size() == 0) {\n      return url;\n    } else if (url.find(\"://\") != std::string::npos) {\n      return url;\n    } else if (url[0] == '/') {\n      return url;\n    } else {\n      std::stringstream input_url(dirname + url);\n      std::vector<std::string> segment_urls;\n      std::string current_segment;\n      std::string output_url;\n      while (getline(input_url, current_segment, '/')) {\n        if (current_segment != \".\" && current_segment != \"\") {\n          if (current_segment != \"..\") {\n            segment_urls.push_back(current_segment);\n          } else if (!segment_urls.empty()) {\n            segment_urls.pop_back();\n          }\n        }\n      }\n\n      if (segment_urls.empty()) {\n        return \"/\";\n      } else {\n        for (auto &segment_url : segment_urls) {\n          output_url.append(\"/\");\n          output_url.append(segment_url);\n        }\n        return output_url;\n      }\n    }\n  }\n  static std::string Url(const std::string &url) {\n    return \"url(\\\"\" + url + \"\\\")\";\n  }\n\n  static std::string JoinPaths(std::initializer_list<std::string> components) {\n    std::stringstream stream;\n    size_t i = 0;\n    const size_t size = components.size();\n    for (const auto &component : components) {\n      i++;\n      stream << component;\n      if (i != size) {\n#if defined(OS_WIN)\n        stream << \"\\\\\";\n#else\n        stream << \"/\";\n#endif\n      }\n    }\n    return stream.str();\n  }\n\n  /**brief: get last path form macro __FILE__ at compile time\n   * The feature running at compile time will be enable only when passed const\n   * expression as paraments\n   */\n  static constexpr const char *GetLastPath(const char *filename,\n                                           int32_t length) {\n    for (int32_t i = length; i > 0; --i) {\n      if (filename[i] == '/' || filename[i] == '\\\\') {\n        return filename + i + 1;\n      }\n    }\n\n    return filename;\n  }\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_PATH_UTILS_H_\n"
  },
  {
    "path": "base/include/platform/android/java_type.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_PLATFORM_ANDROID_JAVA_TYPE_H_\n#define BASE_INCLUDE_PLATFORM_ANDROID_JAVA_TYPE_H_\n\n#include <jni.h>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nstatic constexpr char kShortType = 'S';\nstatic constexpr char kIntType = 'I';\nstatic constexpr char kLongType = 'J';\nstatic constexpr char kFloatType = 'F';\nstatic constexpr char kDoubleType = 'D';\nstatic constexpr char kCharType = 'C';\nstatic constexpr char kBooleanType = 'Z';\nstatic constexpr char kByteType = 'B';\nstatic constexpr char kVoidType = 'V';\nstatic constexpr char kArrayFlag = '[';\n\nstatic constexpr char kStringType = 's';\nstatic constexpr char kObjectType = 'O';\n\nclass JType {\n public:\n  static ScopedLocalJavaRef<jobject> NewByte(JNIEnv* env, jbyte value);\n  static ScopedLocalJavaRef<jobject> NewChar(JNIEnv* env, jchar value);\n  static ScopedLocalJavaRef<jobject> NewBoolean(JNIEnv* env, jboolean value);\n  static ScopedLocalJavaRef<jobject> NewShort(JNIEnv* env, jshort value);\n  static ScopedLocalJavaRef<jobject> NewInt(JNIEnv* env, jint value);\n  static ScopedLocalJavaRef<jobject> NewLong(JNIEnv* env, jlong value);\n  static ScopedLocalJavaRef<jobject> NewFloat(JNIEnv* env, jfloat value);\n  static ScopedLocalJavaRef<jobject> NewDouble(JNIEnv* env, jdouble value);\n\n  static ScopedLocalJavaRef<jstring> NewString(JNIEnv* env, const char* str);\n\n  static jbyte ByteValue(JNIEnv* env, jobject value);\n  static jchar CharValue(JNIEnv* env, jobject value);\n  static jboolean BooleanValue(JNIEnv* env, jobject value);\n  static jshort ShortValue(JNIEnv* env, jobject value);\n  static jint IntValue(JNIEnv* env, jobject value);\n  static jlong LongValue(JNIEnv* env, jobject value);\n  static jfloat FloatValue(JNIEnv* env, jobject value);\n  static jdouble DoubleValue(JNIEnv* env, jobject value);\n\n  static ScopedLocalJavaRef<jobjectArray> NewObjectArray(JNIEnv* env,\n                                                         int length);\n  static ScopedLocalJavaRef<jdoubleArray> NewDoubleArray(JNIEnv* env,\n                                                         int length);\n  static ScopedLocalJavaRef<jobjectArray> NewStringArray(JNIEnv* env,\n                                                         int length);\n\n  static void Init(JNIEnv* env, char type);\n  static void ReleaseAll(JNIEnv* env);\n\n private:\n  static void EnsureInstance(JNIEnv* env, char type);\n\n  static jclass byte_clazz;\n  static jmethodID byte_ctor;\n  static jfieldID byte_valueField;\n\n  static jclass char_clazz;\n  static jmethodID char_ctor;\n  static jfieldID char_valueField;\n\n  static jclass boolean_clazz;\n  static jmethodID boolean_ctor;\n  static jfieldID boolean_valueField;\n\n  static jclass short_clazz;\n  static jmethodID short_ctor;\n  static jfieldID short_valueField;\n\n  static jclass int_clazz;\n  static jmethodID int_ctor;\n  static jfieldID int_valueField;\n\n  static jclass long_clazz;\n  static jmethodID long_ctor;\n  static jfieldID long_valueField;\n\n  static jclass float_clazz;\n  static jmethodID float_ctor;\n  static jfieldID float_valueField;\n\n  static jclass double_clazz;\n  static jmethodID double_ctor;\n  static jfieldID double_valueField;\n\n  static jclass string_clazz;\n  static jclass object_clazz;\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n#endif  // BASE_INCLUDE_PLATFORM_ANDROID_JAVA_TYPE_H_\n"
  },
  {
    "path": "base/include/platform/android/jni_convert_helper.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_PLATFORM_ANDROID_JNI_CONVERT_HELPER_H_\n#define BASE_INCLUDE_PLATFORM_ANDROID_JNI_CONVERT_HELPER_H_\n\n#include <jni.h>\n\n#include <cstdint>\n#include <functional>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <vector>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/platform/android/java_type.h\"\n#include \"base/include/platform/android/scoped_java_ref.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\nclass BASE_EXPORT JNIConvertHelper {\n public:\n  static lynx::base::android::ScopedLocalJavaRef<jstring> ConvertToJNIStringUTF(\n      JNIEnv* env, const std::string& value);\n\n  static lynx::base::android::ScopedLocalJavaRef<jstring> ConvertToJNIStringUTF(\n      JNIEnv* env, const char* value);\n\n  static lynx::base::android::ScopedLocalJavaRef<jstring> ConvertToJNIString(\n      JNIEnv* env, const jchar* unicode_chars, jsize len);\n\n  static ScopedLocalJavaRef<jstring> U16StringToJNIString(\n      JNIEnv* env, const std::u16string& str);\n\n  static std::string ConvertToString(JNIEnv* env, jstring value);\n\n  static std::string ConvertToString(JNIEnv* env, jbyteArray j_binary);\n\n  // TODO(qiuxian):Add unit test when framework is ready.\n  static lynx::base::android::ScopedLocalJavaRef<jbyteArray>\n  ConvertToJNIByteArray(JNIEnv* env, const std::string& str);\n\n  static std::vector<uint8_t> ConvertJavaBinary(JNIEnv* env,\n                                                jbyteArray j_binary);\n\n  static bool ConvertJavaBinary(JNIEnv* env, jbyteArray j_binary,\n                                std::function<void*(int32_t size)> allocator);\n\n  static bool ConvertJavaDirectByteBuffer(\n      JNIEnv* env, jobject j_buffer,\n      std::function<void*(int32_t size)> allocator);\n\n  inline static int ConvertToInt(JNIEnv* env, jobject j_obj) {\n    int value = JType::IntValue(env, j_obj);\n    return value;\n  }\n\n  inline static long ConvertToLong(JNIEnv* env, jobject j_obj) {\n    long value = static_cast<long>(JType::LongValue(env, j_obj));\n    return value;\n  }\n\n  inline static float ConvertToFloat(JNIEnv* env, jobject j_obj) {\n    float value = static_cast<float>(JType::FloatValue(env, j_obj));\n    return value;\n  }\n\n  inline static double ConvertToDouble(JNIEnv* env, jobject j_obj) {\n    double value = static_cast<double>(JType::DoubleValue(env, j_obj));\n    return value;\n  }\n\n  inline static bool ConvertToBoolean(JNIEnv* env, jobject j_obj) {\n    jboolean value = JType::BooleanValue(env, j_obj);\n    return static_cast<bool>(value == JNI_TRUE);\n  }\n\n  static ScopedLocalJavaRef<jobjectArray> ConvertStringVectorToJavaStringArray(\n      JNIEnv* env, const std::vector<std::string>& input);\n\n  static ScopedLocalJavaRef<jobjectArray> ConvertStringVectorToJavaStringArray(\n      JNIEnv* env, const std::vector<std::u16string>& input);\n\n  static ScopedLocalJavaRef<jbyteArray> ConvertToJNIByteArray(JNIEnv* env,\n                                                              const void* data,\n                                                              int32_t size);\n\n  static ScopedLocalJavaRef<jobject> ConvertToJavaDirectByteBuffer(\n      JNIEnv* env, const void* data, int32_t size);\n\n  static ScopedLocalJavaRef<jobjectArray> ConvertVectorToBufferArray(\n      JNIEnv* env, const std::vector<std::vector<uint8_t>>& vector);\n\n  static std::vector<std::string> ConvertJavaStringArrayToStringVector(\n      JNIEnv* env, jobjectArray array);\n\n  static std::unordered_set<std::string> ConvertJavaStringSetToSTLStringSet(\n      JNIEnv* env, jobject set);\n\n  static std::unique_ptr<std::unordered_map<std::string, std::string>>\n  ConvertJavaStringHashMapToSTLStringMap(JNIEnv* env, jobject javaMap);\n};\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_PLATFORM_ANDROID_JNI_CONVERT_HELPER_H_\n"
  },
  {
    "path": "base/include/platform/android/jni_utils.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_PLATFORM_ANDROID_JNI_UTILS_H_\n#define BASE_INCLUDE_PLATFORM_ANDROID_JNI_UTILS_H_\n\n#include <jni.h>\n\n#include <cstdint>\n#include <string>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/platform/android/scoped_java_ref.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nBASE_EXPORT void InitVM(JavaVM *vm);\n\nBASE_EXPORT JNIEnv *AttachCurrentThread();\n\nBASE_EXPORT void DetachFromVM();\n\nBASE_EXPORT ScopedLocalJavaRef<jclass> GetClass(JNIEnv *env,\n                                                const char *class_name);\n\nBASE_EXPORT ScopedGlobalJavaRef<jclass> GetGlobalClass(JNIEnv *env,\n                                                       const char *class_name);\n\nenum MethodType {\n  STATIC_METHOD,\n  INSTANCE_METHOD,\n};\n\nBASE_EXPORT jmethodID GetMethod(JNIEnv *env, jclass clazz, MethodType type,\n                                const char *method_name,\n                                const char *jni_signature);\n\nBASE_EXPORT jmethodID GetMethod(JNIEnv *env, jclass clazz, MethodType type,\n                                const char *method_name,\n                                const char *jni_signature, intptr_t *method_id);\n\nBASE_EXPORT bool HasException(JNIEnv *env);\nBASE_EXPORT bool ClearException(JNIEnv *env);\n// Return false if exception occured\nBASE_EXPORT bool CheckException(JNIEnv *env, std::string &exception_msg);\nBASE_EXPORT bool CheckAndPrintException(JNIEnv *env);\nstd::string GetJavaExceptionInfo(JNIEnv *env, jthrowable java_throwable);\n\nBASE_EXPORT int GetAPILevel();\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\nnamespace fml {\nnamespace jni {\nusing lynx::base::android::AttachCurrentThread;\nusing lynx::base::android::CheckException;\nusing lynx::base::android::ClearException;\nusing lynx::base::android::DetachFromVM;\nusing lynx::base::android::GetAPILevel;\nusing lynx::base::android::HasException;\n}  // namespace jni\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_PLATFORM_ANDROID_JNI_UTILS_H_\n"
  },
  {
    "path": "base/include/platform/android/scoped_java_ref.h",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_PLATFORM_ANDROID_SCOPED_JAVA_REF_H_\n#define BASE_INCLUDE_PLATFORM_ANDROID_SCOPED_JAVA_REF_H_\n\n#include <jni.h>\n\n#include <string>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\n// Forward declare the generic java reference template class.\ntemplate <typename T>\nclass JavaRef;\n\n// Template specialization of JavaRef, which acts as the base class for all\n// other JavaRef<> template types. This allows you to e.g. pass\n// ScopedLocalJavaRef<jstring> into a function taking const JavaRef<jobject>&\ntemplate <>\nclass BASE_EXPORT JavaRef<jobject> {\n public:\n  JavaRef();\n\n  JavaRef(JNIEnv* env, jobject obj);\n\n  ~JavaRef() {}\n#ifndef NDEBUG\n  static int GlobalRefCount();\n\n  void SetOwnerName(const std::string& str) { owner_name_ = str; }\n  const std::string OwnerName() const { return owner_name_; }\n\n private:\n  std::string owner_name_ = \"initial\";\n\n public:\n#endif\n  // NOLINTNEXTLINE\n  JNIEnv* ResetNewLocalRef(JNIEnv* env, jobject obj);\n\n  void ReleaseLocalRef(JNIEnv* env);\n\n  void ResetNewGlobalRef(JNIEnv* env, jobject obj);  // NOLINT\n\n  void ReleaseGlobalRef(JNIEnv* env);\n\n  // NOLINTNEXTLINE\n  void ResetNewWeakGlobalRef(JNIEnv* env, jobject obj);\n\n  void ReleaseWeakGlobalRef(JNIEnv* env);\n\n  jobject Get() const { return obj_; }\n\n  bool IsNull() const { return obj_ == nullptr; }\n\n  virtual bool IsLocal() const { return false; }\n  virtual bool IsGlobal() const { return false; }\n  virtual bool IsWeakGlobal() const { return false; }\n\n protected:\n  jobject obj_ = nullptr;\n};\n\n// Generic base class for ScopedLocalJavaRef and ScopedGlobalJavaRef. Useful\n// for allowing functions to accept a reference without having to mandate\n// whether it is a local or global type.\ntemplate <typename T>\nclass JavaRef : public JavaRef<jobject> {\n public:\n  T Get() const { return static_cast<T>(JavaRef<jobject>::Get()); }\n\n protected:\n  JavaRef() {}\n  ~JavaRef() {}\n\n  JavaRef(JNIEnv* env, T obj) : JavaRef<jobject>(env, obj) {}\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(JavaRef);\n};\n\n/* Note: If you put ScopedLocalJavaRef in std::vector, be careful to hold or\n * save ScopedLocalJavaRef's obj_(raw pointer) directly for a long time. If\n * vector's reallocation happens, it will use ScopedLocalJavaRef's copy\n * constructor to construct a new one and destruct the old one. The raw pointer\n * you hold will become dangling pointer.\n */\n// TODO: Add noexcept in move semantics function.\ntemplate <typename T>\nclass ScopedLocalJavaRef final : public JavaRef<T> {\n public:\n  ScopedLocalJavaRef() : env_(nullptr) {}\n\n  ScopedLocalJavaRef(JNIEnv* env, T obj) : JavaRef<T>(env, obj), env_(env) {}\n\n  ScopedLocalJavaRef(const ScopedLocalJavaRef<T>& other) : env_(other.env_) {\n    Reset(env_, other.Get());\n  }\n\n  template <typename U>\n  explicit ScopedLocalJavaRef(const U& other) : env_(NULL) {\n    this->Reset(other);\n  }\n\n  ScopedLocalJavaRef(ScopedLocalJavaRef<T>&& other) {\n    JavaRef<T>::obj_ = other.obj_;\n    other.obj_ = nullptr;\n  }\n\n  bool IsLocal() const override { return true; }\n\n  void operator=(const ScopedLocalJavaRef<T>& other) {\n    env_ = other.env_;\n    Reset(env_, other.Get());\n  }\n\n  ~ScopedLocalJavaRef() { this->ReleaseLocalRef(env_); }\n\n  // NOLINTNEXTLINE\n  void Reset(JNIEnv* env, jobject obj) { this->ResetNewLocalRef(env, obj); }\n  void Reset() { this->ReleaseLocalRef(env_); }\n\n  template <typename U>\n  void Reset(const ScopedLocalJavaRef<U>& other) {\n    // We can copy over env_ here as |other| instance must be from the same\n    // thread as |this| local ref. (See class comment for multi-threading\n    // limitations, and alternatives).\n    this->Reset(other.env_, other.Get());\n  }\n\n  template <typename U>\n  void Reset(const U& other) {\n    // If |env_| was not yet set (is still NULL) it will be attached to the\n    // current thread in ResetNewLocalRef().\n    this->Reset(env_, other.Get());\n  }\n\n private:\n  JNIEnv* env_ = nullptr;\n};\n\n/* Note: If you put ScopedGlobalJavaRef in std::vector, be careful to hold or\n * save ScopedGlobalJavaRef's obj_(raw pointer) directly for a long time. If\n * vector's reallocation happens, it will use ScopedGlobalJavaRef's copy\n * constructor to construct a new one and destruct the old one. The raw pointer\n * you hold will become dangling pointer.\n */\n// TODO: Add noexcept in move semantics function.\ntemplate <typename T>\nclass ScopedGlobalJavaRef final : public JavaRef<T> {\n public:\n  ScopedGlobalJavaRef() {}\n\n  ScopedGlobalJavaRef(JNIEnv* env, T obj) { Reset(env, obj); }\n\n  ScopedGlobalJavaRef(const ScopedGlobalJavaRef<T>& other) {\n    Reset(nullptr, other.Get());\n  }\n\n  ScopedGlobalJavaRef(const ScopedLocalJavaRef<T>& other) {\n    Reset(nullptr, other.Get());\n  }\n\n  template <typename U>\n  explicit ScopedGlobalJavaRef(const U& other) {\n    this->Reset(other);\n  }\n\n  ScopedGlobalJavaRef(ScopedGlobalJavaRef<T>&& other) {\n    JavaRef<T>::obj_ = other.obj_;\n    other.obj_ = nullptr;\n  }\n\n  ~ScopedGlobalJavaRef() { this->ReleaseGlobalRef(nullptr); }\n\n  bool IsGlobal() const override { return true; }\n  void operator=(const ScopedGlobalJavaRef<T>& other) {\n    Reset(nullptr, other.Get());\n  }\n\n  void Reset(JNIEnv* env, const ScopedLocalJavaRef<T>& other) {\n    this->ResetNewGlobalRef(env, other.Get());  // NOLINT\n  }\n\n  // NOLINTNEXTLINE\n  void Reset(JNIEnv* env, jobject obj) { this->ResetNewGlobalRef(env, obj); }\n\n  void Reset() { this->ReleaseGlobalRef(nullptr); }\n\n  template <typename U>\n  void Reset(const U& other) {\n    this->Reset(NULL, other.Get());\n  }\n};\n\n/* Note: If you put ScopedWeakGlobalJavaRef in std::vector, be careful to hold\n * or save ScopedWeakGlobalJavaRef's obj_(raw pointer) directly for a long time.\n * If vector's reallocation happens, it will use ScopedWeakGlobalJavaRef's copy\n * constructor to construct a new one and destruct the old one. The raw pointer\n * you hold will become dangling pointer.\n */\n// TODO: Add noexcept in move semantics function.\ntemplate <typename T>\nclass ScopedWeakGlobalJavaRef final : public JavaRef<T> {\n public:\n  ScopedWeakGlobalJavaRef() {}\n\n  ScopedWeakGlobalJavaRef(JNIEnv* env, T obj) { Reset(env, obj); }\n\n  ScopedWeakGlobalJavaRef(const ScopedGlobalJavaRef<T>& other) {\n    Reset(nullptr, other.Get());\n  }\n\n  ScopedWeakGlobalJavaRef(const ScopedLocalJavaRef<T>& other) {\n    Reset(nullptr, other.Get());\n  }\n\n  ScopedWeakGlobalJavaRef(const ScopedWeakGlobalJavaRef<T>& other) {\n    Reset(nullptr, other.Get());\n  }\n\n  ~ScopedWeakGlobalJavaRef() { this->ReleaseWeakGlobalRef(nullptr); }\n\n  bool IsWeakGlobal() const override { return true; }\n\n  void operator=(const ScopedWeakGlobalJavaRef<T>& other) {\n    Reset(nullptr, other.Get());\n  }\n\n  void Reset(JNIEnv* env, const ScopedLocalJavaRef<T>& other) {\n    this->ResetNewWeakGlobalRef(env, other.Get());  // NOLINT\n  }\n\n  void Reset(JNIEnv* env, jobject obj) {\n    this->ResetNewWeakGlobalRef(env, obj);  // NOLINT\n  }\n};\n\nclass BASE_EXPORT ScopedJavaLocalFrame {\n public:\n  explicit ScopedJavaLocalFrame(JNIEnv* env);\n  ScopedJavaLocalFrame(JNIEnv* env, int capacity);\n  ~ScopedJavaLocalFrame();\n\n private:\n  // This class is only good for use on the thread it was created on so\n  // it's safe to cache the non-threadsafe JNIEnv* inside this object.\n  JNIEnv* env_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(ScopedJavaLocalFrame);\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\nnamespace fml {\nnamespace jni {\nusing lynx::base::android::JavaRef;\nusing lynx::base::android::ScopedGlobalJavaRef;\nusing lynx::base::android::ScopedJavaLocalFrame;\nusing lynx::base::android::ScopedLocalJavaRef;\n}  // namespace jni\n}  // namespace fml\n\n#endif  // BASE_INCLUDE_PLATFORM_ANDROID_SCOPED_JAVA_REF_H_\n"
  },
  {
    "path": "base/include/platform/darwin/type_utils.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_PLATFORM_DARWIN_TYPE_UTILS_H_\n#define BASE_INCLUDE_PLATFORM_DARWIN_TYPE_UTILS_H_\n\n#include <objc/objc.h>\n\n#include <type_traits>\n\nnamespace lynx {\nnamespace base {\n\n// https://stackoverflow.com/a/23283812\ntemplate <typename T, typename V = bool>\nstruct is_objc_class : std::false_type {};\n\ntemplate <typename T>\nstruct is_objc_class<\n    T, typename std::enable_if<std::is_convertible<T, id>::value, bool>::type>\n    : std::true_type {};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_PLATFORM_DARWIN_TYPE_UTILS_H_\n"
  },
  {
    "path": "base/include/platform/harmony/harmony_vsync_manager.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_PLATFORM_HARMONY_HARMONY_VSYNC_MANAGER_H_\n#define BASE_INCLUDE_PLATFORM_HARMONY_HARMONY_VSYNC_MANAGER_H_\n\n#include <functional>\n#include <mutex>\n#include <vector>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/macros.h\"\n\nstruct OH_NativeVSync;\nnamespace lynx {\nnamespace base {\n\n// This class is used to request vsync signal from Harmony, and dispatch it to\n// all vsync monitors.\nclass HarmonyVsyncManager {\n public:\n  using VSyncCallback = std::function<void(long long)>;\n\n  BASE_EXPORT static HarmonyVsyncManager& GetInstance();\n\n  // request the next vsync signal, then callback when it arrives.\n  // OH_NativeVSync_Create takes very long time (≈1ms), may slow down\n  // initization, so we use a global OH_NativeVSync here, and dispatch to each\n  // vsync monitor.\n  BASE_EXPORT void RequestVSync(const VSyncCallback& callback);\n\n private:\n  HarmonyVsyncManager();\n  ~HarmonyVsyncManager();\n\n  // it's the callback for a vsync request, dispatch all callbacks in the\n  // callbacks vector, and make sure it's thread-safe.\n  static void OnVsyncFromHarmony(long long timestamp, void* data);\n\n  OH_NativeVSync* vsync_handle_ = nullptr;\n  bool requested_ = false;\n  std::mutex mutex_;\n  std::vector<VSyncCallback> callbacks_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(HarmonyVsyncManager);\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_PLATFORM_HARMONY_HARMONY_VSYNC_MANAGER_H_\n"
  },
  {
    "path": "base/include/platform/harmony/napi_util.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_PLATFORM_HARMONY_NAPI_UTIL_H_\n#define BASE_INCLUDE_PLATFORM_HARMONY_NAPI_UTIL_H_\n\n#include <node_api.h>\n#include <string.h>\n#include <uv.h>\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <variant>\n#include <vector>\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\nnamespace base {\n\n#define NAPI_THROW_IF_FAILED_STATUS(env, status, format, ...)             \\\n  if ((status) != napi_ok) {                                              \\\n    char msg[1024];                                                       \\\n    snprintf(msg, sizeof(msg), format, __VA_ARGS__);                      \\\n    napi_throw_error(env, base::NapiUtil::StatusToString(status).c_str(), \\\n                     msg);                                                \\\n    return status;                                                        \\\n  }\n\n#define NAPI_THROW_IF_FAILED_NULL(env, status, message)                   \\\n  if ((status) != napi_ok) {                                              \\\n    napi_throw_error(env, base::NapiUtil::StatusToString(status).c_str(), \\\n                     message);                                            \\\n    return nullptr;                                                       \\\n  }\n\n#define NAPI_CREATE_FUNCTION(env, exports, function_name, function)           \\\n  {                                                                           \\\n    napi_value native_result;                                                 \\\n    napi_create_function(env, function_name, strlen(function_name), function, \\\n                         nullptr, &native_result);                            \\\n    napi_set_named_property(env, exports, function_name, native_result);      \\\n  }\n\nclass NapiHandleScope {\n public:\n  explicit NapiHandleScope(napi_env env) : env_(env) {\n    napi_open_handle_scope(env_, &scope_);\n  }\n\n  ~NapiHandleScope() { napi_close_handle_scope(env_, scope_); }\n\n private:\n  napi_env env_;\n  napi_handle_scope scope_ = nullptr;\n};\n\nstruct NapiAsyncContext {\n  napi_env env = nullptr;\n  napi_async_work async_work = nullptr;\n  napi_ref ref_napi_obj = nullptr;\n  std::string method_name;\n  std::vector<napi_ref> args;\n};\n\nclass BASE_EXPORT NapiUtil {\n public:\n  static int32_t ConvertToInt32(napi_env env, napi_value obj);\n  static uint32_t ConvertToUInt32(napi_env env, napi_value obj);\n  static int64_t ConvertToInt64(napi_env env, napi_value obj);\n  static int64_t ConvertToBigInt64(napi_env env, napi_value obj);\n  static uint64_t ConvertToBigUInt64(napi_env env, napi_value obj);\n  static float ConvertToFloat(napi_env env, napi_value obj);\n  static double ConvertToDouble(napi_env env, napi_value obj);\n  static bool ConvertToBoolean(napi_env env, napi_value obj);\n  static std::string ConvertToShortString(napi_env env, napi_value arg);\n  static std::string ConvertToString(napi_env env, napi_value arg);\n\n  static bool ConvertToArrayString(napi_env env, napi_value arg,\n                                   std::vector<std::string>& array_string);\n  static bool ConvertToArrayBuffer(napi_env env, napi_value arg,\n                                   std::vector<uint8_t>& buffer);\n  static bool ConvertToArrayBuffer(napi_env env, napi_value arg,\n                                   std::unique_ptr<uint8_t[]>& buffer,\n                                   size_t& length);\n  static bool ConvertToArray(napi_env env, napi_value arg,\n                             std::vector<napi_value>& array);\n\n  static bool ConvertToArray(napi_env env, napi_value arg, napi_value array[],\n                             uint32_t size);\n  /**\n   * convert Record<string, string> to unordered_map<string, string>\n   */\n  static bool ConvertToMap(napi_env env, napi_value arg,\n                           std::unordered_map<std::string, std::string>& map);\n\n  static bool NapiIsType(napi_env env, napi_value value, napi_valuetype type);\n  static bool NapiIsAnyType(napi_env env, napi_value value, ...);\n  static bool IsArray(napi_env env, napi_value value);\n  static bool IsArrayBuffer(napi_env env, napi_value value);\n\n  static napi_value CreateArrayBuffer(napi_env env, void* input_data,\n                                      size_t data_size);\n\n  static napi_value CreateMap(\n      napi_env env, const std::unordered_map<int32_t, std::string> map);\n  static napi_value CreateMap(napi_env env,\n                              const std::unordered_map<int32_t, double> map);\n  static napi_value CreateMap(\n      napi_env env, const std::unordered_map<std::string, std::string> map);\n\n  static napi_value CreatePtrArray(napi_env env, uint64_t ptr);\n\n  static napi_value CreateUint32(napi_env env, uint32_t num);\n\n  static napi_value CreateInt32(napi_env env, int32_t num);\n\n  static napi_status SetPropToJSMap(napi_env env, napi_value js_map,\n                                    const std::string& key,\n                                    const std::string& value);\n\n  static napi_status SetPropToJSMap(napi_env env, napi_value js_map,\n                                    const std::string& key, int32_t value);\n\n  static napi_status SetPropToJSMap(napi_env env, napi_value js_map,\n                                    const std::string& key, double value);\n\n  static uint64_t ConvertToPtr(napi_env env, napi_value arr);\n\n  static napi_status InvokeJsMethod(napi_env env, napi_ref ref_napi_obj,\n                                    napi_ref ref_napi_method, size_t argc,\n                                    const napi_value* argv,\n                                    napi_value* result = nullptr);\n\n  static napi_status AsyncInvokeJsMethod(napi_env env, napi_ref ref_napi_obj,\n                                         const char* method_name, size_t argc,\n                                         const napi_value* argv);\n\n  static napi_status InvokeJsMethod(napi_env env, napi_ref ref_napi_obj,\n                                    const char* method_name, size_t argc,\n                                    const napi_value* argv,\n                                    napi_value* result = nullptr);\n\n  static napi_status InvokeJsMethod(napi_env env, napi_value napi_obj,\n                                    const char* method_name, size_t argc,\n                                    const napi_value* argv,\n                                    napi_value* result = nullptr);\n\n  static const std::string& StatusToString(napi_status status);\n\n  static napi_value GetReferenceNapiValue(napi_env env, napi_ref reference);\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_PLATFORM_HARMONY_NAPI_UTIL_H_\n"
  },
  {
    "path": "base/include/position.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_POSITION_H_\n#define BASE_INCLUDE_POSITION_H_\n\n#include \"base/include/vector2d.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass Position {\n public:\n  Position() : left_(0), top_(0), right_(0), bottom_(0) {}\n\n  Position(int l, int t, int r, int b)\n      : left_(l), top_(t), right_(r), bottom_(b) {}\n\n  Position(const Position& pos)\n      : left_(pos.left_),\n        top_(pos.top_),\n        right_(pos.right_),\n        bottom_(pos.bottom_) {}\n\n  ~Position() {}\n\n  void Update(Vector2D& vec) {\n    left_ += vec.x();\n    top_ += vec.y();\n    right_ += vec.x();\n    bottom_ += vec.y();\n  }\n\n  Vector2D Offset() { return Vector2D(left_, top_); }\n\n  bool Reset(const Position& position) {\n    if (left_ == position.left_ && top_ == position.top_ &&\n        right_ == position.right_ && bottom_ == position.bottom_) {\n      return false;\n    }\n\n    left_ = position.left_;\n    top_ = position.top_;\n    right_ = position.right_;\n    bottom_ = position.bottom_;\n    return true;\n  }\n\n  bool Reset(int left, int top, int right, int bottom) {\n    if (left_ == left && top_ == top && right_ == right && bottom_ == bottom) {\n      return false;\n    }\n\n    left_ = left;\n    top_ = top;\n    right_ = right;\n    bottom_ = bottom;\n    return true;\n  }\n\n  bool Equal(int left, int top, int right, int bottom) {\n    return (left_ == left && top_ == top && right_ == right &&\n            bottom_ == bottom);\n  }\n\n  inline int GetWidth() const {\n    int width = right_ - left_;\n    return width > 0 ? width : 0;\n  }\n\n  inline int GetHeight() const {\n    int height = bottom_ - top_;\n    return height > 0 ? height : 0;\n  }\n\n  inline bool IsEmpty() const {\n    return !(left_ || top_ || right_ || bottom_) || GetWidth() == 0 ||\n           GetHeight() == 0;\n  }\n\n public:\n  int left_;\n  int top_;\n  int right_;\n  int bottom_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_POSITION_H_\n"
  },
  {
    "path": "base/include/shared_vector.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_SHARED_VECTOR_H_\n#define BASE_INCLUDE_SHARED_VECTOR_H_\n\n#include <vector>\n\nnamespace lynx {\nnamespace base {\ntemplate <typename T>\nclass SharedVector {\n public:\n  SharedVector(const std::vector<T>& source)\n      : data_(source.data()), size_(source.size()) {}\n\n  SharedVector(const T* data, size_t size) : data_(data), size_(size) {}\n\n  const T* Data() const { return data_; }\n\n  const T& operator[](std::size_t idx) const { return data_[idx]; }\n\n  size_t Size() const { return size_; }\n\n private:\n  const T* data_;\n  const size_t size_;\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_SHARED_VECTOR_H_\n"
  },
  {
    "path": "base/include/sorted_for_each.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_SORTED_FOR_EACH_H_\n#define BASE_INCLUDE_SORTED_FOR_EACH_H_\n\n#include <algorithm>\n#include <functional>\n#include <utility>\n#include <vector>\n\n#include \"base/include/algorithm.h\"\n\nnamespace lynx::base {\n\n// `sorted_for_each` work just like `std::for_each`, but in a sorted order.\n//\n// It applies the given function object f to the result of dereferencing every\n// iterator in the range [first, last), in order.\n//\n// InputIt:\n// The iterators in the range [first, last) should keep being valid during the\n// whole traversing process, since they are stored and sorted.\n// The InputIt must meet the requirements of LegacyInputIterator.\n// see: https://en.cppreference.com/w/cpp/named_req/InputIterator\n//\n// Sort:\n// Use `InsertionSort` instead of `std::stable_sort` for binary size optimal.\n// The iterators are sorted by `std::stable_sort`, it sorts the elements with\n// the dereferenced iterators in the range [first, last) in non-descending\n// order. The order of equivalent elements is guaranteed to be preserved. If the\n// compare function is not passed, will use `std::less<>` as default.\n// see: https://en.cppreference.com/w/cpp/algorithm/stable_sort\n//\n// Function object f:\n// If the iterator type is mutable, f may modify the elements of the range\n// through the dereferenced iterator.\n// If f returns a result, the result is ignored.\n// The signature of the function should be equivalent to the following:\n//   `void fun(const InputIt::value_type &a);`\n// The signature does not need to have `const &`.\n// The type Type must be such that an object of type `InputIt` can be\n// dereferenced and then implicitly converted to `Type`.\ntemplate <class InputIt, class UnaryFunc, class Compare>\nconstexpr UnaryFunc sorted_for_each(InputIt first, InputIt last, UnaryFunc f,\n                                    Compare comp) {\n  // iterators from [first, last) is stored and sorted in a `std::vector`\n  // so they should keep being valid\n  std::vector<InputIt> iterators;\n  for (; first != last; first++) {\n    iterators.push_back(first);\n  }\n\n  InsertionSort(iterators.data(), iterators.size(),\n                [&comp](InputIt a, InputIt b) { return comp(*a, *b); });\n  //  std::stable_sort(iterators.begin(), iterators.end(),\n  //                   [&comp](InputIt a, InputIt b) { return comp(*a, *b); });\n\n  for (auto& it : iterators) {\n    // f is called with the dereferenced iterators\n    f(*it);\n  }\n  // return the f as `std::for_each` does\n  return std::move(f);\n}\n\n// Overrides of `sorted_for_each`, Compare function defaults to `std::less<>`\ntemplate <class InputIt, class UnaryFunc>\nconstexpr UnaryFunc sorted_for_each(InputIt first, InputIt last, UnaryFunc f) {\n  return sorted_for_each(first, last, std::forward<UnaryFunc>(f),\n                         std::less<>());\n}\n\ntemplate <class Container, class UnaryFunc, class Compare>\nconstexpr UnaryFunc SortedForEach(const Container& container, UnaryFunc f,\n                                  Compare comp) {\n  return sorted_for_each(container.begin(), container.end(),\n                         std::forward<UnaryFunc>(f), comp);\n}\n\ntemplate <class Container, class UnaryFunc>\nconstexpr UnaryFunc SortedForEach(const Container& container, UnaryFunc f) {\n  return sorted_for_each(container.begin(), container.end(),\n                         std::forward<UnaryFunc>(f), std::less<>());\n}\n\n}  // namespace lynx::base\n\n#endif  // BASE_INCLUDE_SORTED_FOR_EACH_H_\n"
  },
  {
    "path": "base/include/string/quickjs_dtoa.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_STRING_QUICKJS_DTOA_H_\n#define BASE_INCLUDE_STRING_QUICKJS_DTOA_H_\n\n#include \"base/include/base_defines.h\"\n#include \"base/include/base_export.h\"\n\nBASE_EXTERN_C_BEGIN\n\nBASE_EXPORT void js_dtoa(char* buf, double val);\n\nBASE_EXTERN_C_END\n\n#endif  // BASE_INCLUDE_STRING_QUICKJS_DTOA_H_\n"
  },
  {
    "path": "base/include/string/string_conversion_win.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_STRING_STRING_CONVERSION_WIN_H_\n#define BASE_INCLUDE_STRING_STRING_CONVERSION_WIN_H_\n\n#include <string>\n#include <string_view>\n\nnamespace lynx {\nnamespace base {\n\n// Converts a string from UTF-16 to UTF-8. Returns an empty string if the\n// input is not valid UTF-16.\nstd::string Utf8FromUtf16(const std::wstring& utf16_string);\nstd::string Utf8FromUtf16(const std::wstring_view& utf8_string);\nstd::string Utf8FromUtf16(const wchar_t* utf16_string, int32_t length);\n\n// Converts a string from UTF-8 to UTF-16. Returns an empty string if the\n// input is not valid UTF-8.\nstd::wstring Utf16FromUtf8(const std::string& utf8_string);\nstd::wstring Utf16FromUtf8(const std::string_view& utf8_string);\nstd::wstring Utf16FromUtf8(const char* utf8_string, int32_t length);\n\n// Converts a string from UTF-8 to ANSI or OEM .\n// This function was originally designed  because older V8 code uses Fopen and\n// only allows you to pass const char* argument. Like\n// v8::V8::InitializeExternalStartupData;\n// See :\n// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170\nstd::string Utf8ToANSIOrOEM(const std::string& utf8_string);\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_STRING_STRING_CONVERSION_WIN_H_\n"
  },
  {
    "path": "base/include/string/string_number_convert.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_STRING_STRING_NUMBER_CONVERT_H_\n#define BASE_INCLUDE_STRING_STRING_NUMBER_CONVERT_H_\n\n#include <string>\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\nnamespace base {\nBASE_EXPORT bool StringToInt(const std::string& input, int64_t& output,\n                             uint8_t base = 10);\nBASE_EXPORT bool StringToInt(const std::string& input, int* output,\n                             uint8_t base = 10);\n/*\n * Convert from string to double.\n * @param input, the string to be converted.\n * @param ouput, output the conversion result, will remain as it was if string\n * is not a valid number.\n * @param error_on_nan_or_inf, treat inf or nan as invalid value if set to true.\n * @return if string is a valid double number.\n */\nBASE_EXPORT bool StringToDouble(const std::string& input, double& output,\n                                bool error_on_nan_or_inf = false);\n\nBASE_EXPORT bool StringToFloat(const std::string& input, float& output,\n                               bool error_on_nan_or_inf = false);\n}  // namespace base\n}  // namespace lynx\n#endif  // BASE_INCLUDE_STRING_STRING_NUMBER_CONVERT_H_\n"
  },
  {
    "path": "base/include/string/string_utils.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_STRING_STRING_UTILS_H_\n#define BASE_INCLUDE_STRING_STRING_UTILS_H_\n\n#include <algorithm>\n#include <array>\n#include <cassert>\n#include <cmath>\n#include <cstring>\n#include <functional>\n#include <sstream>\n#include <string>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace base {\n\nusing UChar = char16_t;\nusing LChar = char;\nusing UChar32 = int32_t;\n\nenum TrimPositions {\n  TRIM_NONE = 0,\n  TRIM_LEADING = 1 << 0,\n  TRIM_TRAILING = 1 << 1,\n  TRIM_ALL = TRIM_LEADING | TRIM_TRAILING,\n};\n\ninline bool BeginsWith(std::string_view s, std::string_view begin) {\n  if (s.length() >= begin.length()) {\n    return (0 == s.compare(0, begin.length(), begin));\n  } else {\n    return false;\n  }\n}\n\n// Callback returns pointer, length and index of each splitted string.\n// If separator is not space and trim is true, the returned string will be\n// automatically trimmed.\n// Return pointer to last character processed.\n// Return false of callback to terminate processing.\nBASE_EXPORT const char* SplitString(\n    std::string_view target, char separator, bool trim,\n    const std::function<bool(const char*, size_t, int)>& callback);\n\nBASE_EXPORT bool SplitString(std::string_view target, char separator,\n                             std::vector<std::string>& result);\n\nBASE_EXPORT bool SplitStringBySpaceOutOfBrackets(\n    std::string_view target, std::vector<std::string>& result);\n\n// For CSS style handlers with maximum 4 components.\nBASE_EXPORT bool SplitStringBySpaceOutOfBrackets(\n    std::string_view target, base::InlineVector<std::string, 4>& result);\n\ntemplate <typename OutputStringType, typename T>\nstd::vector<OutputStringType> SplitString(T str, T delims,\n                                          bool want_all = false) {\n  std::vector<OutputStringType> output;\n\n  for (auto first = str.data(), second = str.data(), last = first + str.size();\n       second != last && first != last; first = second + 1) {\n    second =\n        std::find_first_of(first, last, std::cbegin(delims), std::cend(delims));\n\n    if (want_all || first != second) {\n      output.emplace_back(first, second);\n    }\n  }\n\n  return output;\n}\n\nBASE_EXPORT std::vector<std::string_view> SplitToStringViews(\n    std::string_view str, const std::string& delims = \" \");\n\n// Returns a string joined by the given delimiter.\nBASE_EXPORT std::string Join(const std::vector<std::string>& vec,\n                             const char* delimiter);\n\nstd::string JoinString(const std::vector<std::string>& pieces);\n\nBASE_EXPORT std::string CamelCaseToDashCase(std::string_view);\n\ninline bool EndsWith(std::string_view s, std::string_view ending) {\n  if (s.length() >= ending.length()) {\n    return (0 ==\n            s.compare(s.length() - ending.length(), ending.length(), ending));\n  } else {\n    return false;\n  }\n}\n\nBASE_EXPORT bool EndsWithIgnoreSourceCase(std::string_view s,\n                                          std::string_view ending);\n\nvoid TrimWhitespaceASCII(std::string_view input, int position,\n                         std::string* output);\nBASE_EXPORT std::string StringToLowerASCII(std::string_view input);\n\nnamespace internal {\ntemplate <class T>\nvoid AppendStringImpl(std::stringstream& ss, const T& head) {\n  ss << head;\n}\n\ntemplate <class T, class... Args>\nvoid AppendStringImpl(std::stringstream& ss, const T& head,\n                      const Args&... args) {\n  ss << head;\n  AppendStringImpl(ss, args...);\n}\n}  // namespace internal\n\ntemplate <class... Args>\nstd::string AppendString(const Args&... args) {\n  if constexpr (sizeof...(Args) > 0) {\n    std::stringstream ss;\n    internal::AppendStringImpl(ss, args...);\n    return ss.str();\n  }\n  return {};\n}\n\n// String utils\nBASE_EXPORT std::string TrimString(std::string_view str);\nBASE_EXPORT std::string_view TrimToStringView(std::string_view to_trim);\nBASE_EXPORT std::string TrimString(std::string input, std::string trim_chars,\n                                   TrimPositions positions);\nBASE_EXPORT std::string_view TrimString(std::string_view input,\n                                        std::string_view trim_chars,\n                                        TrimPositions positions);\n\nBASE_EXPORT base::InlineVector<std::string, 32> SplitStringByCharsOrderly(\n    std::string_view str, char cs[], size_t char_count);\n\n// Use like this `SplitStringByCharsOrderly<':', ';'>(input);`\ntemplate <char... chars>\nauto SplitStringByCharsOrderly(std::string_view str) {\n  char cs[] = {chars...};\n  return SplitStringByCharsOrderly(str, cs, sizeof(cs));\n}\n\nBASE_EXPORT void ReplaceAll(std::string& str, std::string_view from,\n                            std::string_view to);\n\ninline std::string_view TruncateToStringView(const std::string& input,\n                                             size_t max_length) {\n  return std::string_view(input.c_str(), std::min(input.length(), max_length));\n}\n\nstd::string SafeStringConvert(const char* str);\n\nstd::string PtrToStr(void* ptr);\n\n// (1,2, 3,4) ==> vector:{1,2,3,4}\nbool ConvertParenthesesStringToVector(std::string& origin,\n                                      std::vector<std::string>& ret,\n                                      char separator = ',');\n// delimiter=\",\": \"a,b,(1,2,3),d\" =>[a,b,(1,2,3),d]\nBASE_EXPORT std::vector<std::string> SplitStringIgnoreBracket(std::string str,\n                                                              char delimiter);\n\n// Remove all spaces (https://www.cplusplus.com/reference/cctype/isspace/) from\n// `str`.\nBASE_EXPORT std::string RemoveSpaces(std::string_view str);\n// this method will modify input str.\n// \"a b    c  d   \" => \"a b c d \"\nvoid ReplaceMultiSpaceWithOne(std::string& str);\n\n// The purpose of this function is to replace \\n, \\r, and \\t in \\\"\\\" with \\\\n,\n// \\\\r, and \\\\t, respectively, to avoid lepusNG generating code cache failure.\n// Now, this function is only used in the encoder.\nvoid ReplaceEscapeCharacterWithLiteralString(std::string& input);\n\ninline bool IsInUtf16BMP(char16_t c) { return (c & 0xF800) != 0xD800; }\n\ninline bool IsLeadingSurrogate(char16_t c) { return c >= 0xD800 && c < 0xDC00; }\n\ninline bool IsTrailingSurrogate(char16_t c) {\n  return c >= 0xDC00 && c <= 0xDFFF;\n}\n\ninline bool IsUtf8Start(char c) {\n  // Start with \"11\" or \"0X\", which all consists UTF8 encode.\n  return (c & 0xc0) != 0x80;\n}\n\nBASE_EXPORT std::u16string U8StringToU16(std::string_view u8_string);\nBASE_EXPORT std::string U16StringToU8(std::u16string_view u16_string);\nBASE_EXPORT std::u32string U8StringToU32(std::string_view u8_string);\nBASE_EXPORT std::string U32StringToU8(std::u32string_view u32_string);\nBASE_EXPORT std::u32string U16StringToU32(std::u16string_view u16_string);\nBASE_EXPORT std::u16string U32StringToU16(std::u32string_view u32_string);\n\nBASE_EXPORT std::string FormatStringWithVaList(const char* format,\n                                               va_list args);\nBASE_EXPORT std::string FormatString(const char* format, ...);\n\ninline bool StringEqual(const char* a, const char* b) {\n  assert(a != nullptr && b != nullptr);\n  auto la = strlen(a);\n  if (la != strlen(b)) return false;\n  return strncmp(a, b, la) == 0;\n}\n\nBASE_EXPORT bool EqualsIgnoreCase(std::string_view left,\n                                  std::string_view right);\n\n// Refer from\n// https://chromium.googlesource.com/v8/v8/+/master/src/inspector/v8-string-conversions.cc\n\nconstexpr bool IsASCII(UChar c) { return !(c & ~0x7F); }\n\nconstexpr bool IsASCIINumber(UChar c) { return c >= '0' && c <= '9'; }\n\nconstexpr bool IsASCIIHexNumber(UChar c) {\n  return ((c | 0x20) >= 'a' && (c | 0x20) <= 'f') || IsASCIINumber(c);\n}\n\nconstexpr int ToASCIIHexValue(UChar c) {\n  return c < 'A' ? c - '0' : (c - 'A' + 10) & 0xF;\n}\n\nconstexpr bool IsASCIIAlphaCaselessEqual(UChar css_character, char character) {\n  // This function compares a (preferably) constant ASCII\n  // lowercase letter to any input character.\n  LYNX_BASE_DCHECK(character >= 'a');\n  LYNX_BASE_DCHECK(character <= 'z');\n  return (css_character | 0x20) == character;\n}\n\nconstexpr bool IsASCIISpace(UChar c) {\n  return c <= ' ' && (c == ' ' || (c <= 0xD && c >= 0x9));\n}\n\ntemplate <typename CharType>\nconstexpr bool IsHTMLSpace(CharType character) {\n  return character <= ' ' &&\n         (character == ' ' || character == '\\n' || character == '\\t' ||\n          character == '\\r' || character == '\\f');\n}\n\nconstexpr bool IsSpaceOrNewline(UChar c) {\n  return IsASCII(c) && c <= ' ' && (c == ' ' || (c <= 0xD && c >= 0x9));\n}\n\ninline size_t InlineUTF8SequenceLengthNonASCII(char b0) {\n  if ((b0 & 0xC0) != 0xC0) return 0;\n  if ((b0 & 0xE0) == 0xC0) return 2;\n  if ((b0 & 0xF0) == 0xE0) return 3;\n  if ((b0 & 0xF8) == 0xF0) return 4;\n  return 0;\n}\n\ninline size_t InlineUTF8SequenceLength(char b0) {\n  return IsASCII(b0) ? 1 : InlineUTF8SequenceLengthNonASCII(b0);\n}\n\ninline size_t InlineUTF8SequenceLength(const char* utf8, size_t c_length,\n                                       size_t utf8_index) {\n  size_t cur_utf8_index = 0;\n  size_t cur_index = 0;\n  while (cur_utf8_index != utf8_index && cur_index < c_length) {\n    cur_index += InlineUTF8SequenceLength(utf8[cur_index]);\n    cur_utf8_index++;\n  }\n  return cur_index;\n}\n\ninline size_t UTF8IndexToCIndex(const char* utf8, size_t c_length,\n                                size_t utf8_index) {\n  size_t cur_utf8_index = 0;\n  size_t cur_index = 0;\n  while (cur_utf8_index != utf8_index && cur_index < c_length) {\n    cur_index += InlineUTF8SequenceLength(utf8[cur_index]);\n    cur_utf8_index++;\n  }\n  return cur_index;\n}\n\ninline size_t Utf8IndexToCIndexForUtf16(const char* utf8, size_t c_length,\n                                        size_t utf16_index) {\n  size_t cur_utf16_index = 0, cur_c_index = 0, cur_char_size = 0;\n\n  while (cur_utf16_index < utf16_index && cur_c_index < c_length) {\n    cur_char_size = InlineUTF8SequenceLength(utf8[cur_c_index]);\n    cur_c_index += cur_char_size;\n    // if cur_char_size == 4, the char's length is 2 in utf16.\n    cur_utf16_index += (cur_char_size == 4 ? 2 : 1);\n  }\n\n  if (cur_utf16_index > utf16_index) {\n    return cur_c_index - cur_char_size;\n  }\n  return cur_c_index;\n}\n\ninline size_t CIndexToUTF8Index(const char* utf8, size_t c_length,\n                                size_t c_index) {\n  size_t cur_c_index = 0;\n  size_t cur_utf8_index = 0;\n  while (cur_c_index < c_index && cur_c_index < c_length) {\n    cur_c_index += InlineUTF8SequenceLength(utf8[cur_c_index]);\n    cur_utf8_index++;\n  }\n  return cur_utf8_index;\n}\n\ninline size_t SizeOfUtf8(const char* utf8, size_t c_length) {\n  size_t size = 0;\n  size_t cur_index = 0;\n  while (cur_index < c_length) {\n    cur_index += InlineUTF8SequenceLength(utf8[cur_index]);\n    size++;\n  }\n  return size;\n}\n\ninline size_t SizeOfUtf16(const std::string& src_u8) {\n  std::u16string u16_conv = lynx::base::U8StringToU16(src_u8);\n  return u16_conv.size();\n}\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_STRING_STRING_UTILS_H_\n"
  },
  {
    "path": "base/include/thread/base_semaphore.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_THREAD_BASE_SEMAPHORE_H_\n#define BASE_INCLUDE_THREAD_BASE_SEMAPHORE_H_\n\n#include <semaphore.h>\n#include <time.h>\n\nnamespace lynx {\nnamespace base {\nstruct Semaphore {\n  inline Semaphore(int value) { sem_init(&_sem, 0, value); }\n\n  inline ~Semaphore() { sem_destroy(&_sem); }\n\n  inline void Wait() { sem_wait(&_sem); }\n\n  inline void Wait(long usec) {\n#if defined(OS_IOS) || defined(OS_OSX)\n    sem_wait(&_sem);\n#else\n    struct timespec timeout;\n    struct timeval tt;\n    gettimeofday(&tt, nullptr);\n\n    tt.tv_usec += usec;\n    timeout.tv_sec = tt.tv_sec + tt.tv_usec / 1000000;\n    timeout.tv_nsec = tt.tv_usec % 1000000 * 1000;\n\n    sem_timedwait(&_sem, &timeout);\n#endif\n  }\n\n  inline void Post() { sem_post(&_sem); }\n\n private:\n  sem_t _sem;\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_THREAD_BASE_SEMAPHORE_H_\n"
  },
  {
    "path": "base/include/thread/pthread_rw_lock_guard.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_THREAD_PTHREAD_RW_LOCK_GUARD_H_\n#define BASE_INCLUDE_THREAD_PTHREAD_RW_LOCK_GUARD_H_\n\n#include <pthread/pthread.h>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass PthreadRWLockGuard {\n public:\n  enum class Type : int {\n    write = 0,\n    read = 1,\n  };\n\n  PthreadRWLockGuard(pthread_rwlock_t& lock, Type type) : lock_(lock) {\n    if (type == Type::write) {\n      pthread_rwlock_wrlock(&lock_);\n    } else if (type == Type::read) {\n      pthread_rwlock_rdlock(&lock_);\n    }\n  }\n\n  ~PthreadRWLockGuard() { pthread_rwlock_unlock(&lock_); }\n\n private:\n  pthread_rwlock_t& lock_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PthreadRWLockGuard);\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_THREAD_PTHREAD_RW_LOCK_GUARD_H_\n"
  },
  {
    "path": "base/include/thread/timed_task.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_THREAD_TIMED_TASK_H_\n#define BASE_INCLUDE_THREAD_TIMED_TASK_H_\n\n#include <memory>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/closure.h\"\n#include \"base/include/fml/task_runner.h\"\n\nnamespace lynx {\nnamespace base {\n\n// not thread safe, need ensure lifecycle on one thread forever.\nclass BASE_EXPORT TimedTaskManager {\n public:\n  explicit TimedTaskManager(bool need_stop_all_tasks_when_exit = true,\n                            fml::RefPtr<fml::TaskRunner> runner = nullptr);\n  ~TimedTaskManager();\n\n  uint32_t SetTimeout(closure closure, int64_t delay);\n\n  uint32_t SetInterval(closure closure, int64_t delay);\n\n  void StopTask(uint32_t id);\n\n  void StopAllTasks();\n\n private:\n  struct Controller {\n    explicit Controller(closure other) : closure(std::move(other)) {}\n\n    Controller(const Controller&) = delete;\n    Controller& operator=(const Controller&) = delete;\n\n    closure closure;\n  };\n\n  // need forbid StopTask in execute timed task,\n  // or delete this will cause crash.\n  // just use a scope to control.\n  class Scope {\n   public:\n    Scope(TimedTaskManager* manager, uint32_t current,\n          bool is_interval = false);\n    ~Scope();\n\n   private:\n    TimedTaskManager* manager_;\n    bool is_interval_;\n  };\n\n  void SetInterval(std::unique_ptr<TimedTaskManager::Controller> controller,\n                   int64_t delay, uint32_t current);\n\n  uint32_t current_ = 0;\n\n  std::unordered_map<uint32_t, Controller*> controllers_;\n\n  // bind to thread which TimedTaskManager created.\n  fml::RefPtr<fml::TaskRunner> runner_;\n\n  uint32_t current_executing_task_{0};\n\n  bool has_pending_remove_task_{false};\n\n  // TODO(heshan.pro): Try to avoid crash when the thread is released and needs\n  // to be deleted TimedTaskManager after refactor.\n  bool need_stop_all_tasks_when_exit_{true};\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_THREAD_TIMED_TASK_H_\n"
  },
  {
    "path": "base/include/timer/time_utils.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_TIMER_TIME_UTILS_H_\n#define BASE_INCLUDE_TIMER_TIME_UTILS_H_\n\n#include <time.h>\n\n#include <cstdint>\n\n#include \"base/include/base_export.h\"\n\nnamespace lynx {\nnamespace base {\n// This method should not be used except in unsatisfied scenarios.\nBASE_EXPORT uint64_t CurrentSystemTimeMilliseconds();\n// This method should not be used except in unsatisfied scenarios.\nBASE_EXPORT uint64_t CurrentSystemTimeMicroseconds();\nBASE_EXPORT uint64_t CurrentTimeMicroseconds();\nBASE_EXPORT uint64_t CurrentTimeMilliseconds();\nBASE_EXPORT uint64_t CurrentThreadCPUTimeMicroseconds();\n#if !defined(OS_WIN)\nBASE_EXPORT timespec ToTimeSpecFromNow(uint64_t interval_time);\n#endif\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_TIMER_TIME_UTILS_H_\n"
  },
  {
    "path": "base/include/to_underlying.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_TO_UNDERLYING_H_\n#define BASE_INCLUDE_TO_UNDERLYING_H_\n\n#include <type_traits>\n\nnamespace lynx::base {\n\n// Implementation of C++23's std::to_underlying.\n//\n// Note: This has an additional `std::is_enum<EnumT>` requirement to be SFINAE\n// friendly prior to C++20.\n//\n// Reference: https://en.cppreference.com/w/cpp/utility/to_underlying\ntemplate <typename EnumT, typename = std::enable_if_t<std::is_enum<EnumT>{}>>\nconstexpr std::underlying_type_t<EnumT> to_underlying(EnumT e) noexcept {\n  return static_cast<std::underlying_type_t<EnumT>>(e);\n}\n\n}  // namespace lynx::base\n\n#endif  // BASE_INCLUDE_TO_UNDERLYING_H_\n"
  },
  {
    "path": "base/include/type_traits_addon.h",
    "content": "// Copyright 2022 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_TYPE_TRAITS_ADDON_H_\n#define BASE_INCLUDE_TYPE_TRAITS_ADDON_H_\n\n#include <type_traits>\n\nnamespace lynx {\nnamespace base {\n// Implementation of C++20's std::remove_cvref.\n//\n// References:\n// - https://en.cppreference.com/w/cpp/types/remove_cvref\n// - https://wg21.link/meta.trans.other#lib:remove_cvref\ntemplate <typename T>\nstruct remove_cvref {\n  using type = std::remove_cv_t<std::remove_reference_t<T>>;\n};\n\n// Implementation of C++20's std::remove_cvref_t.\n//\n// References:\n// - https://en.cppreference.com/w/cpp/types/remove_cvref\n// - https://wg21.link/meta.type.synop#lib:remove_cvref_t\ntemplate <typename T>\nusing remove_cvref_t = typename remove_cvref<T>::type;\n\n/**\n * Checks if a type is template instance of another type.\n * For example:\n *     is_instance<std::shared_ptr<std::string>, std::shared_ptr> = true\n */\ntemplate <class, template <class, class...> class>\nstruct is_instance : public std::false_type {};\n\ntemplate <class... Ts, template <class, class...> class U>\nstruct is_instance<U<Ts...>, U> : public std::true_type {};\n\n/**\n * By default, std::is_trivially_copyable_v<std::pair<int, int>> is false which\n * causes Vector<std::pair<int, int>> to be treated with non-trivial\n * element type. We check first and second elements of std::pair<> and treat the\n * whole type as trivial if both elements are trivial.\n *\n * From C++23, the standard was updated to mandate that if `T1` and `T2` are\n * both trivially copyable, then `std::pair<T1, T2>` must also be trivially\n * copyable.\n */\ntemplate <typename T, bool is_pair>\nstruct IsTrivial {};\n\ntemplate <typename T>\nstruct IsTrivial<T, false> {\n  static constexpr auto value =\n      std::is_trivially_destructible_v<T> && std::is_trivially_copyable_v<T>;\n};\n\ntemplate <typename T>\nstruct IsTrivial<T, true> {\n  static constexpr auto value =\n      std::is_trivially_destructible_v<T> &&\n      IsTrivial<typename T::first_type,\n                is_instance<typename T::first_type, std::pair>{}>::value &&\n      IsTrivial<typename T::second_type,\n                is_instance<typename T::second_type, std::pair>{}>::value;\n};\n\n// Check if T has declared flag `TriviallyDestructibleAfterMove`.\n// When this optimization hint is turned on, if one T instance is moved away,\n// its destructor will not be called.\ntemplate <typename T, typename = void>\nstruct has_trivially_destructible_after_move_flag : std::false_type {};\n\ntemplate <typename T>\nstruct has_trivially_destructible_after_move_flag<\n    T, std::void_t<typename T::TriviallyDestructibleAfterMove>>\n    : std::true_type {};\n\ntemplate <typename T, bool is_pair>\nstruct IsTriviallyDestructibleAfterMove {};\n\ntemplate <typename T>\nstruct IsTriviallyDestructibleAfterMove<T, false> {\n  static constexpr auto value =\n      std::is_trivially_destructible_v<T> ||\n      has_trivially_destructible_after_move_flag<T>::value;\n};\n\ntemplate <typename T>\nstruct IsTriviallyDestructibleAfterMove<T, true> {\n  static constexpr auto value =\n      (std::is_trivially_destructible_v<typename T::first_type> ||\n       IsTriviallyDestructibleAfterMove<\n           typename T::first_type,\n           is_instance<typename T::first_type, std::pair>{}>::value) &&\n      (std::is_trivially_destructible_v<typename T::second_type> ||\n       IsTriviallyDestructibleAfterMove<\n           typename T::second_type,\n           is_instance<typename T::second_type, std::pair>{}>::value);\n};\n\n// Check if T has declared flag `TriviallyRelocatable`.\n// When this optimization hint is turned on, the instances of type T can be\n// moved as a whole(if multiple instances on linear memory) to another piece of\n// memory using the memcpy or memmove method, and the T type instance in the\n// original memory does not need destruction.\ntemplate <typename T, typename = void>\nstruct has_trivially_relocatable_flag : std::false_type {};\n\ntemplate <typename T>\nstruct has_trivially_relocatable_flag<\n    T, std::void_t<typename T::TriviallyRelocatable>> : std::true_type {};\n\ntemplate <typename T, bool is_pair>\nstruct IsTriviallyRelocatable {};\n\ntemplate <typename T>\nstruct IsTriviallyRelocatable<T, false> {\n  static constexpr auto value =\n      IsTrivial<T, is_instance<T, std::pair>{}>::value ||\n      has_trivially_relocatable_flag<T>::value;\n};\n\ntemplate <typename T>\nstruct IsTriviallyRelocatable<T, true> {\n  static constexpr auto value =\n      (IsTrivial<typename T::first_type,\n                 is_instance<typename T::first_type, std::pair>{}>::value ||\n       IsTriviallyRelocatable<\n           typename T::first_type,\n           is_instance<typename T::first_type, std::pair>{}>::value) &&\n      (IsTrivial<typename T::second_type,\n                 is_instance<typename T::second_type, std::pair>{}>::value ||\n       IsTriviallyRelocatable<\n           typename T::second_type,\n           is_instance<typename T::second_type, std::pair>{}>::value);\n};\n\ntemplate <typename T>\nclass alignas(T) TypeOfPlainBytes {\n public:\n  bool operator==(const TypeOfPlainBytes& other) const {\n    return reinterpret_cast<const T&>(*this) ==\n           reinterpret_cast<const T&>(other);\n  }\n\n  // Must be trivially relocatable.\n  using TriviallyRelocatable = bool;\n\n private:\n  [[maybe_unused]] char buffer_[sizeof(T)];\n};\n\ntemplate <typename T>\nstruct IsTypeOfPlainBytes : std::false_type {};\n\ntemplate <typename T>\nstruct IsTypeOfPlainBytes<TypeOfPlainBytes<T>> : std::true_type {};\n\n}  // namespace base\n}  // namespace lynx\n\nnamespace std {\ntemplate <typename T>\nstruct hash<lynx::base::TypeOfPlainBytes<T>> {\n  std::size_t operator()(const lynx::base::TypeOfPlainBytes<T>& k) const {\n    return hash<T>()(reinterpret_cast<const T&>(k));\n  }\n};\n}  // namespace std\n\n#endif  // BASE_INCLUDE_TYPE_TRAITS_ADDON_H_\n"
  },
  {
    "path": "base/include/value/array.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_VALUE_ARRAY_H_\n#define BASE_INCLUDE_VALUE_ARRAY_H_\n#include <type_traits>\n#include <utility>\n\n#include \"base/include/base_defines.h\"\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/ref_counted_class.h\"\n#include \"base/include/value/ref_type.h\"\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace lepus {\nclass BASE_EXPORT CArray : public RefCountedBase {\n public:\n  static fml::RefPtr<CArray> Create() {\n    return fml::AdoptRef<CArray>(new CArray());\n  }\n\n  RefType GetRefType() const override { return RefType::kLepusArray; }\n\n  // Push a default constructed value at back.\n  Value* push_back_default() {\n    if (IsConstLog()) {\n      return nullptr;\n    }\n    return &vec_.emplace_back();\n  }\n\n  bool push_back(const Value& value) {\n    if (IsConstLog()) {\n      return false;\n    }\n    vec_.push_back(value);\n    return true;\n  }\n\n  bool push_back(Value&& value) {\n    if (IsConstLog()) {\n      return false;\n    }\n    vec_.push_back(std::move(value));\n    return true;\n  }\n\n  template <class ValueType, typename = typename std::enable_if<!std::is_same_v<\n                                 std::decay_t<ValueType>, Value>>::type>\n  bool push_back(ValueType&& v) {\n    if (IsConstLog()) {\n      return false;\n    }\n    vec_.emplace_back(std::forward<ValueType>(v));\n    return true;\n  }\n\n  template <class... Args>\n  bool emplace_back(Args&&... args) {\n    if (IsConstLog()) {\n      return false;\n    }\n    vec_.emplace_back(std::forward<Args>(args)...);\n    return true;\n  }\n\n  bool pop_back() {\n    if (IsConstLog()) {\n      return false;\n    }\n    if (vec_.size() > 0) vec_.pop_back();\n    return true;\n  }\n\n  bool Erase(uint32_t idx) {\n    if (IsConstLog()) {\n      return false;\n    }\n\n    if (idx >= 0 && idx < vec_.size()) {\n      vec_.erase(vec_.begin() + idx);\n    }\n    return true;\n  }\n\n  bool Erase(size_t start, size_t del_count) {\n    if (IsConstLog()) {\n      return false;\n    }\n\n    auto begin = (start < vec_.size()) ? (vec_.begin() + start) : vec_.end();\n    auto end =\n        (start + del_count <= vec_.size()) ? (begin + del_count) : vec_.end();\n\n    vec_.erase(begin, end);\n    return true;\n  }\n\n  bool Insert(uint32_t idx, const lepus::Value& value) {\n    if (IsConstLog()) {\n      return false;\n    }\n\n    if (idx >= 0) {\n      vec_.insert(vec_.begin() + idx, value);\n    }\n    return true;\n  }\n\n  Value get_shift() {\n    if (vec_.size() > 0) {\n      Value ret = std::move(vec_[0]);\n      vec_.erase(vec_.begin(), vec_.begin() + 1);\n      return ret;\n    } else {\n      return Value();\n    }\n  }\n\n  const Value& get(size_t index) const {\n    if (index >= vec_.size()) {\n      static Value empty;\n      empty = Value();\n      return empty;\n    }\n    return vec_[index];\n  }\n\n  void resize(long size) { vec_.resize(size); }\n\n  void reserve(long size) { vec_.reserve(size); }\n\n  bool set(size_t index, const Value& v) {\n    if (IsConstLog()) {\n      return false;\n    }\n    if (static_cast<size_t>(index) >= vec_.size()) {\n      resize(index + 1);\n    }\n    vec_[index] = v;\n    return true;\n  }\n\n  bool set(size_t index, Value&& v) {\n    if (IsConstLog()) {\n      return false;\n    }\n    if (static_cast<size_t>(index) >= vec_.size()) {\n      resize(index + 1);\n    }\n    vec_[index] = std::move(v);\n    return true;\n  }\n\n  template <class ValueType, typename = typename std::enable_if<!std::is_same_v<\n                                 std::decay_t<ValueType>, Value>>::type>\n  bool set(size_t index, ValueType&& v) {\n    if (IsConstLog()) {\n      return false;\n    }\n    if (index >= vec_.size()) {\n      resize(index + 1);\n    }\n    std::decay_t<ValueType>::Assign(vec_[index], std::forward<ValueType>(v));\n    return true;\n  }\n\n  void SetIsMatchResult() { __padding_chars__[1] = 1; }\n\n  bool GetIsMatchResult() const { return __padding_chars__[1]; }\n\n  size_t size() const { return vec_.size(); }\n\n  ~CArray() override = default;\n\n  friend bool operator==(const CArray& left, const CArray& right) {\n    return left.vec_ == right.vec_ &&\n           left.GetIsMatchResult() == right.GetIsMatchResult();\n  }\n\n  friend bool operator!=(const CArray& left, const CArray& right) {\n    return !(left == right);\n  }\n\n  Value GetMatchIndex() {\n    DCHECK(GetIsMatchResult());\n    DCHECK(size() >= 3);\n    return get(size() - 3);\n  }\n\n  Value GetMatchGroups() {\n    DCHECK(GetIsMatchResult());\n    DCHECK(size() >= 3);\n    return get(size() - 1);\n  }\n\n  Value GetMatchInput() {\n    DCHECK(GetIsMatchResult());\n    DCHECK(size() >= 3);\n    return get(size() - 2);\n  }\n\n  bool IsConst() const override { return __padding_chars__[0]; }\n\n  bool MarkConst() {\n    if (IsConst()) return true;\n    for (const auto& ele : vec_) {\n      if (!ele.MarkConst()) return false;\n    }\n    __padding_chars__[0] = 1;\n    return true;\n  }\n\n  class Unsafe {\n   public:\n    Unsafe() = delete;\n\n    static BASE_INLINE CArray* RawCreate() { return new CArray(); }\n  };\n\n protected:\n  CArray() {}\n\n  friend class Value;\n\n  void Reset() {\n    vec_.clear();\n    __padding__ = 0;\n  }\n\n private:\n  base::InlineVector<Value, 2> vec_;\n\n  friend class LEPUSValueHelper;\n\n  BASE_INLINE bool IsConstLog() const {\n    if (IsConst()) {\n#ifdef DEBUG\n      // TODO(yuyang), Currently LOGD still produce assembly in release mode.\n      LOGD(\"Lepus array is const\");\n#endif\n      return true;\n    }\n    return false;\n  }\n};\n\n}  // namespace lepus\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_VALUE_ARRAY_H_\n"
  },
  {
    "path": "base/include/value/base_string.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_VALUE_BASE_STRING_H_\n#define BASE_INCLUDE_VALUE_BASE_STRING_H_\n\n#include <cstring>\n#include <iomanip>\n#include <limits>\n#include <sstream>\n#include <string>\n#include <string_view>\n// TODO(yuyang), move StringTable, StringConvertHelper to other files.\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/base_defines.h\"\n#include \"base/include/base_export.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/type_traits_addon.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass RefCountedStringImpl;\nclass String;\n\nnamespace static_string {\ntemplate <char... chars>\nstruct Entry;\n\nclass StaticString;\nclass GenericCache;\n}  // namespace static_string\n\nstruct StaticStringPayload {\n  RefCountedStringImpl* impl;\n  const char string[];\n};\n\n/// General implementation of String managed by atomic reference counter.\n/// Constructors and factory methods of RefCountedStringImpl are hidden and\n/// are only visible to String.\nclass BASE_EXPORT RefCountedStringImpl\n    : public fml::RefCountedThreadSafeStorage {\n public:\n  ~RefCountedStringImpl() override = default;\n\n  BASE_INLINE std::size_t hash() const {\n    if (hash_ == 0) {\n      // For strings whose hash value is exactly 0, the calculation is always\n      // repeated.\n      hash_ = std::hash<std::string>()(str_);\n    }\n    return hash_;\n  }\n\n  const char* c_str() const { return str_.c_str(); }\n  const std::string& str() const { return str_; }\n\n  bool empty() const { return str_.empty(); }\n\n  std::size_t length() const { return length_; }\n  std::size_t length_utf8();\n  std::size_t length_utf16();\n\n  uint8_t* __get_padding_chars__() { return __padding_chars__; }\n  uint16_t* __get_padding_shorts__() { return __padding_shorts__; }\n\n  class Unsafe {\n    // ATTENTION: function under this class is UNSAFE to use.\n    // Do NOT use them unless you have consulted with the owners of String.\n   public:\n    Unsafe() = delete;\n    // For kEmptyString it is desired to be a static C++ global-initialization\n    // variable when image is loaded because our base::String default\n    // constructor heavily relies on it for best performance.\n    static RefCountedStringImpl kEmptyString;\n    static RefCountedStringImpl& kTrueString();\n    static RefCountedStringImpl& kFalseString();\n    static inline __attribute__((always_inline)) RefCountedStringImpl*\n    RawCreate(const char* str) {\n      return new RefCountedStringImpl(str);\n    }\n\n    static inline __attribute__((always_inline)) RefCountedStringImpl*\n    RawCreate(std::string str) {\n      return new RefCountedStringImpl(std::move(str));\n    }\n  };\n\n protected:\n  void ReleaseSelf() const override { delete this; }\n\n private:\n  std::string str_;\n  mutable std::size_t hash_;  // 0 as a sentinel value for not calculated\n  uint32_t length_;\n\n  union {\n    struct {\n      // utf16_length_ is lazily calculated and cached when length_utf16() is\n      // invoked.\n      uint32_t utf16_length_ : 31;\n      uint32_t utf16_len_calculated_ : 1;\n    };\n\n    uint32_t init_{0};  // initialize the anonymous struct above to all 0\n  };\n\n  RefCountedStringImpl(const char* str, std::size_t len);\n  explicit RefCountedStringImpl(const char* str);\n  explicit RefCountedStringImpl(std::string str);\n\n  RefCountedStringImpl(const RefCountedStringImpl&) = delete;\n  RefCountedStringImpl& operator=(const RefCountedStringImpl&) = delete;\n\n  friend class String;\n  friend class Unsafe;\n  friend class static_string::StaticString;\n  friend class static_string::GenericCache;\n\n  template <char... chars>\n  friend struct static_string::Entry;\n\n  static RefCountedStringImpl* RawCreate(const char* str, std::size_t len) {\n    return new RefCountedStringImpl(str, len);\n  }\n\n  static RefCountedStringImpl* RawCreateStatic(const char* str) {\n    // System strlen may do tricky optimizations for long strings and\n    // additional branch instructions are involved.\n    // Considering that most our static strings are short ones less than\n    // 20 characters, calculate length directly.\n    const char* s = str;\n    for (; *s; ++s) {\n    }\n    return new RefCountedStringImpl(str, s - str);\n  }\n};\n\n/// Basic thread-safe constant string type.\nclass String {\n public:\n  // The impl pointer is tagged of last bit if it is not strong referenced\n  // by String instance.\n  // Use tagged pointer instead of some enum field such as storage_type\n  // to denote the pointer type for better performance because adding a enum\n  // field would make String not fit into machine pointer size or register.\n  static constexpr uintptr_t kPointerLastBitSetMask = 1 << 0;\n  static constexpr uintptr_t kPointerLastBitClearMask =\n      std::numeric_limits<uintptr_t>::max() - 1;\n\n  static inline __attribute__((pure)) bool IsTaggedImpl(\n      RefCountedStringImpl* p) {\n    return (reinterpret_cast<uintptr_t>(p) & kPointerLastBitSetMask) > 0;\n  }\n\n  static inline RefCountedStringImpl* MakeTaggedImpl(RefCountedStringImpl* p) {\n    return reinterpret_cast<RefCountedStringImpl*>(\n        reinterpret_cast<uintptr_t>(p) | kPointerLastBitSetMask);\n  }\n\n  static inline RefCountedStringImpl* UntagImpl(RefCountedStringImpl* p) {\n    return reinterpret_cast<RefCountedStringImpl*>(\n        reinterpret_cast<uintptr_t>(p) & kPointerLastBitClearMask);\n  }\n\n public:\n  // Store kEmptyString as tagged and skip its AddRef()\n  String()\n      : ref_impl_(MakeTaggedImpl(&RefCountedStringImpl::Unsafe::kEmptyString)) {\n  }\n  String(const std::string& str)\n      : ref_impl_(RefCountedStringImpl::Unsafe::RawCreate(str)) {}\n  String(std::string&& str)\n      : ref_impl_(RefCountedStringImpl::Unsafe::RawCreate(std::move(str))) {}\n  String(const char* c_str)\n      : ref_impl_(RefCountedStringImpl::Unsafe::RawCreate(c_str)) {}\n  String(const char* c_str, std::size_t length)\n      : ref_impl_(RefCountedStringImpl::RawCreate(c_str, length)) {}\n\n  String(const String& other) {\n    // Copy from a raw ref pointer one, self should be upgraded to normal\n    // ref counted one.\n    ref_impl_ = UntagImpl(other.ref_impl_);\n    ref_impl_->AddRef();\n  }\n\n  String(String&& other) noexcept {\n    // Move from a raw ref pointer, self should be upgraded to normal ref\n    // counted one.\n    ref_impl_ = UntagImpl(other.ref_impl_);\n    if (ref_impl_ != other.ref_impl_) {\n      ref_impl_->AddRef();\n    }\n    other.SetEmptyString();\n  }\n\n  String& operator=(const String& other) {\n    String(other).swap(*this);\n    return *this;\n  }\n\n  String& operator=(String&& other) {\n    String(std::move(other)).swap(*this);\n    return *this;\n  }\n\n  // Make it inlined so that ~StaticString() sees.\n  inline __attribute__((always_inline)) ~String() {\n    if (!IsTaggedImpl(ref_impl_)) {\n      ref_impl_->Release();\n    }\n  }\n\n  std::size_t hash() const { return UntagImpl(ref_impl_)->hash(); }\n\n  std::string_view string_view() const {\n    return std::string_view(UntagImpl(ref_impl_)->str());\n  }\n\n  const std::string& str() const { return UntagImpl(ref_impl_)->str(); }\n\n  const char* c_str() const { return UntagImpl(ref_impl_)->c_str(); }\n\n  bool empty() const { return UntagImpl(ref_impl_)->empty(); }\n\n  size_t length() const { return UntagImpl(ref_impl_)->length(); }\n\n  size_t length_utf8() const { return UntagImpl(ref_impl_)->length_utf8(); }\n\n  size_t length_utf16() const { return UntagImpl(ref_impl_)->length_utf16(); }\n\n  bool IsEqual(const char* other) const { return str() == other; }\n  bool IsEqual(const std::string& other) const { return str() == other; }\n  bool IsEqual(const String& other) const { return str() == other.str(); }\n\n  template <size_t N>\n  bool IsEquals(char const (&p)[N]) const {\n    return string_view() == std::string_view(p, N - 1);\n  }\n\n  bool operator==(const String& other) const {\n    auto* this_impl = UntagImpl(ref_impl_);\n    auto* other_impl = UntagImpl(other.ref_impl_);\n    return this_impl->hash_ == other_impl->hash_ &&\n           this_impl->str() == other_impl->str();\n  }\n\n  // A callable comparer of two Strings if their hash values are known to be\n  // equal. Only compares string content.\n  struct equal_when_hash_equal {\n    bool operator()(const String& x, const String& y) const {\n      return x.str() == y.str();\n    }\n  };\n\n  bool operator==(const char* other) const { return str() == other; }\n  bool operator==(const std::string& other) const { return str() == other; }\n\n  bool operator!=(const String& other) const {\n    auto* this_impl = UntagImpl(ref_impl_);\n    auto* other_impl = UntagImpl(other.ref_impl_);\n    return this_impl->hash_ != other_impl->hash_ ||\n           this_impl->str() != other_impl->str();\n  }\n  bool operator!=(const char* other) const { return str() != other; }\n  bool operator!=(const std::string& other) const { return str() != other; }\n\n  bool operator<(const String& other) const { return str() < other.str(); }\n  bool operator<(const char* other) const { return str() < other; }\n  bool operator<(const std::string& other) const { return str() < other; }\n\n  bool operator<=(const String& other) const { return str() <= other.str(); }\n  bool operator<=(const char* other) const { return str() <= other; }\n  bool operator<=(const std::string& other) const { return str() <= other; }\n\n  bool operator>(const String& other) const { return str() > other.str(); }\n  bool operator>(const char* other) const { return str() > other; }\n  bool operator>(const std::string& other) const { return str() > other; }\n\n  bool operator>=(const String& other) const { return str() >= other.str(); }\n  bool operator>=(const char* other) const { return str() >= other; }\n  bool operator>=(const std::string& other) const { return str() >= other; }\n\n  std::size_t find(const String& other, std::size_t pos) const {\n    return str().find(other.str(), pos);\n  }\n  std::size_t find(const char* other, std::size_t pos) const {\n    return str().find(other, pos);\n  }\n  std::size_t find(const std::string& other, std::size_t pos) const {\n    return str().find(other, pos);\n  }\n\n  class Unsafe {\n    // ATTENTION: function under this class is UNSAFE to use.\n    // Do NOT use them unless you have consulted with the owners of String.\n   public:\n    Unsafe() = delete;\n\n    static inline __attribute__((always_inline)) String\n    ConstructWeakRefStringFromRawRef(RefCountedStringImpl* str) {\n      return String(str, String::kCreateAsRawRefPointerTag);\n    }\n\n    static inline __attribute__((always_inline)) String\n    ConstructStringFromRawRef(RefCountedStringImpl* str) {\n      return String(str);\n    }\n\n    static inline __attribute__((always_inline)) RefCountedStringImpl*\n    GetStringRawRef(const String& str) {\n      return str.ref_impl_;\n    }\n\n    static inline __attribute__((always_inline)) RefCountedStringImpl*\n    GetUntaggedStringRawRef(const String& str) {\n      return UntagImpl(str.ref_impl_);\n    }\n\n    static inline __attribute__((always_inline)) void SetStringToEmpty(\n        String& str) {\n      str.SetEmptyString();\n    }\n  };\n\n  // A flag telling base containers such as `base::Vector<base::String>` to\n  // optimize for reallocate, insert and erase.\n  using TriviallyRelocatable = bool;\n\n protected:\n  friend class Unsafe;\n\n  template <char... chars>\n  friend struct static_string::Entry;\n\n  friend class static_string::StaticString;\n\n  RefCountedStringImpl* ref_impl_;  // Guaranteed to be non-null.\n\n  /// Explicitly create a String instance which holds weak pointer of\n  /// ref-counted impl.\n  enum CreateAsRawRefPointerTag { kCreateAsRawRefPointerTag };\n  String(RefCountedStringImpl* str, CreateAsRawRefPointerTag)\n      : ref_impl_(MakeTaggedImpl(str)) {}\n\n  /// No-op constructor.\n  enum CreateAsUninitializedTag { kCreateAsUninitializedTag };\n  String(CreateAsUninitializedTag) {}\n\n  /// Explicitly create a String instance which retains the impl.\n  explicit String(RefCountedStringImpl* str) : ref_impl_(str) {\n    ref_impl_->AddRef();\n  }\n\n  inline void swap(String& other) noexcept {\n    std::swap(ref_impl_, other.ref_impl_);\n  }\n\n  inline void SetEmptyString() {\n    ref_impl_ = MakeTaggedImpl(&RefCountedStringImpl::Unsafe::kEmptyString);\n  }\n};\n\nnamespace static_string {\n\n/// Subclass of String which is constructed from static constant C string.\n/// StaticString always stores tagged impl pointer and its destructor could be\n/// omitted by compiler.\nclass StaticString : public String {\n public:\n  StaticString(RefCountedStringImpl* tagged_ref)\n      : String(String::kCreateAsUninitializedTag) {\n    // This constructor is used by GenericCache::operator StaticString() and\n    // tagged_ref must be a tagged pointer.\n    ref_impl_ = tagged_ref;\n  }\n\n  StaticString(StaticStringPayload* payload)\n      : String(String::kCreateAsUninitializedTag) {\n    if (payload->impl == nullptr) {\n      // Directly stores tagged value to payload so that it could be\n      // directly assigned to ref_impl_ later.\n      payload->impl = MakeTaggedImpl(\n          RefCountedStringImpl::RawCreateStatic(payload->string));\n    }\n    ref_impl_ = payload->impl;\n  }\n\n  inline __attribute__((always_inline)) ~StaticString() {\n    // Hint for compiler to skip String::~String() because StaticString\n    // never add reference count to its impl.\n    // The compiler no longer outputs any assembly instructions for\n    // destruction of StaticString.\n    __builtin_assume(String::IsTaggedImpl(ref_impl_));\n  }\n};\n\n// With C++20, we can do this more elegantly.\ntemplate <std::size_t N>\nusing CharArray = char const[N];\n\ntemplate <auto const& str, std::size_t iter = std::size(str),\n          char const... chars>\nstruct CharArrayPack {\n  using Type =\n      typename CharArrayPack<str, iter - 1, str[iter - 1], chars...>::Type;\n};\n\ntemplate <auto const& str, char const... chars>\nstruct CharArrayPack<str, 0, chars...> {\n  using Type = CharArrayPack<str, 0, chars...>;\n\n  template <template <char...> typename T>\n  using Apply = T<chars...>;\n};\n\n// CharZeroSequence produces integer sequence of all char type zeros.\ntemplate <std::size_t... Is>\nconstexpr auto CharZeroHelper(std::index_sequence<Is...> const&)\n    -> decltype(std::integer_sequence<char,\n                                      (static_cast<void>(Is), '\\0')...>{});\n\ntemplate <typename T>\nusing CharZeroSequence = decltype(CharZeroHelper(std::declval<T>()));\n\nusing NullPtrZeros = CharZeroSequence<std::make_index_sequence<sizeof(void*)>>;\n\n/// Specialized for unique sequence of chars. It produces a payload byte array\n/// which stores the source chars an a null-initialized pointer to the relative\n/// RefCountedStringImpl.\ntemplate <char... chars>\nstruct BASE_HIDE Entry {\n private:\n  // Char count including ending 0\n  static constexpr auto kCharCount = sizeof...(chars);\n\n  template <typename T>\n  struct Payload;\n\n  template <char... nullptr_zeros>\n  struct Payload<std::integer_sequence<char, nullptr_zeros...>> {\n    RefCountedStringImpl* impl;\n    const char string[kCharCount];\n\n    inline static StaticStringPayload* Data() {\n      // It is declared as writable data in final binary section.\n      alignas(void*) static char kBuffer[sizeof(Payload)] = {nullptr_zeros...,\n                                                             chars...};\n      return reinterpret_cast<StaticStringPayload*>(kBuffer);\n    }\n  };\n\n public:\n  // Only with \"always_inline\" could this be actually inlined and get binary\n  // size benefits.\n  static inline __attribute__((always_inline)) StaticString GetString() {\n    // At runtime, string impl is initialized once and stored to payload.\n    // There should be no need to worry about multi-threading issues.\n    // Even if different threads enter GetString() method at the same time\n    // for the same Entry, it is OK to create multiple RefCountedStringImpl\n    // instances and store any one to payload->impl.\n    return StaticString(Payload<NullPtrZeros>::Data());\n  }\n};\n\n/// This struct works as key of hash map for static strings to eliminate\n/// unnecessary type conversion or string data copy.\n/// For example:\n///   std::unordered_map<base::String, V> table1;\n///   std::unordered_map<std::string, V> table2;\n///   std::unordered_map<GenericCacheKey, V> table3;\n/// Searching in table1 by `std::string` or `const char*` will implicitly\n/// construct base::String.\n/// Searching in table2 by `const char*` will implicitly construct\n/// std::string.\n/// Searching in table3, you just need to convert 'base::String',\n/// 'std::string' or `const char*` to GenericCacheKey which is light-weight.\nstruct GenericCacheKey {\n  std::string_view content;\n  std::size_t hash;\n\n  // Standard guarantees that\n  // std::hash<std::string>()(s) == std::hash<std::string_view>()(s)\n  GenericCacheKey() = default;\n  GenericCacheKey(const base::String& s)\n      : content(s.string_view()), hash(s.hash()) {}\n  GenericCacheKey(const char* s)\n      : content(s), hash(std::hash<std::string_view>()(content)) {}\n  GenericCacheKey(const char* s, std::size_t len)\n      : content(s, len), hash(std::hash<std::string_view>()(content)) {}\n  GenericCacheKey(const std::string& s)\n      : content(s), hash(std::hash<std::string_view>()(content)) {}\n\n  bool operator==(const GenericCacheKey& other) const {\n    return content == other.content;\n  }\n};\n\n/// GenericCache is constructed from a C string pointer and caches its base\n/// string impl internally.\nclass GenericCache {\n public:\n  GenericCache(const char* s) : s_(s) {}\n  GenericCache(const GenericCache&) = delete;\n  GenericCache& operator=(const GenericCache&) = delete;\n  GenericCache(GenericCache&&) = delete;\n  GenericCache& operator=(GenericCache&&) = delete;\n\n  const char* c_str() const { return s_; }\n\n  const std::string& str() const { return ref_impl()->str(); }\n\n  operator StaticString() const {\n    // Implicitly convertable to base::StaticString.\n    // And forwarding tagged impl pointer directly to StaticString constructor.\n    return StaticString(tagged_ref_impl());\n  }\n\n  RefCountedStringImpl* ref_impl() const {\n    return String::UntagImpl(tagged_ref_impl());\n  }\n\n  // Create RefCountedStringImpl if null and stores tagged pointer directly.\n  RefCountedStringImpl* tagged_ref_impl() const {\n    if (ref_impl_ == nullptr) {\n      ref_impl_ =\n          String::MakeTaggedImpl(RefCountedStringImpl::RawCreateStatic(s_));\n    }\n    return ref_impl_;\n  }\n\n protected:\n  // Must be a static constant C string and the first member of class.\n  const char* s_;\n\n  // Lazily created\n  mutable RefCountedStringImpl* ref_impl_{nullptr};\n};\n\n/// @brief Use this to declare a base string variable `v` with C string\n/// `s`. `s` should be a string literal.\n/// The defined variable is of type base::static_string::StaticString.\n///\n/// Example:\n///   BASE_STATIC_STRING_DECL(kTag, \"tag\")\n#define BASE_STATIC_STRING_DECL(v, s)                                       \\\n  static constexpr lynx::base::static_string::CharArray<std::size(s)>       \\\n      v##_cstr_storage{s};                                                  \\\n  using v##StaticStringEntryType =                                          \\\n      typename lynx::base::static_string::CharArrayPack<v##_cstr_storage>:: \\\n          Type::template Apply<lynx::base::static_string::Entry>;           \\\n  auto v = v##StaticStringEntryType::GetString()\n\n/// @brief Convert a char array to base::String and require `s` to be\n/// defined as `char[]` not `char*`.\n///\n/// Example:\n///   static constexpr const char kPosition[] = \"position\";\n///   static constexpr const char* kPosition = \"position\"; // Won't compile\n///   auto kPos = BASE_STATIC_STRING(kPosition);\n///   auto kPos2 = BASE_STATIC_STRING(\"position\"); // Won't compile\n#define BASE_STATIC_STRING(s)                                        \\\n  lynx::base::static_string::CharArrayPack<s>::Type::template Apply< \\\n      lynx::base::static_string::Entry>::GetString()\n\n};  // namespace static_string\n\n// used for encode\nclass StringTable {\n public:\n  size_t NewString(const char* str) {\n    if (!str) {\n      str = \"\";\n    }\n    auto iter = string_map_.find(str);\n    if (iter != string_map_.end()) {\n      return iter->second;\n    }\n\n    std::string std_str(str);\n    string_list.push_back(std_str);\n    size_t index = string_list.size() - 1;\n    string_map_.insert(std::make_pair(std_str, index));\n    return index;\n  }\n\n public:\n  std::unordered_map<std::string, size_t> string_map_;\n  std::vector<base::String> string_list;\n};\n\nclass StringConvertHelper {\n public:\n  static constexpr int kMaxInt = 0x7FFFFFFF;\n  static constexpr int kMinInt = -kMaxInt - 1;\n  static constexpr long long kMaxInt64 = 0x7fffffffffffffff;\n  static constexpr long long kMinInt64 = -kMaxInt64 - 1;\n\n  static bool IsMinusZero(double value);\n\n  static inline double FastI2D(int x) { return static_cast<double>(x); }\n\n  static inline int FastD2I(double x) { return static_cast<int32_t>(x); }\n\n  static inline double FastI642D(int64_t x) { return static_cast<double>(x); }\n\n  static inline int64_t FastD2I64(double x) { return static_cast<int64_t>(x); }\n\n  static bool IsInt32Double(double value) {\n    return value >= kMinInt && value <= kMaxInt && !IsMinusZero(value) &&\n           value == FastI2D(FastD2I(value));\n  }\n\n  static bool IsInt64Double(double value) {\n    return FastD2I64(value) >= kMinInt64 && FastD2I64(value) <= kMaxInt64 &&\n           !IsMinusZero(value) && value == FastI642D(FastD2I64(value));\n  }\n\n  static const char* IntToCString(int n, char buffer[], size_t buffer_size) {\n    bool negative = true;\n    if (n >= 0) {\n      n = -n;\n      negative = false;\n    }\n    // Build the string backwards from the least significant digit.\n    size_t i = buffer_size;\n    buffer[--i] = '\\0';\n    do {\n      // We ensured n <= 0, so the subtraction does the right addition.\n      buffer[--i] = '0' - (n % 10);\n      n /= 10;\n    } while (n);\n    if (negative) buffer[--i] = '-';\n    return &buffer[0] + i;\n  }\n\n  static const char* NumberToString(double double_value, char buffer[],\n                                    size_t buffer_size) {\n    if (IsInt32Double(double_value)) {\n      return IntToCString(double_value, buffer, buffer_size);\n    }\n    return nullptr;\n  }\n\n  static std::string DoubleToString(double double_value) {\n    std::ostringstream double2str;\n    double2str << std::setprecision(std::numeric_limits<double>::digits10)\n               << double_value;\n    return double2str.str();\n  }\n};\n\n}  // namespace base\n}  // namespace lynx\n\nnamespace std {\ntemplate <>\nstruct hash<lynx::base::String> {\n  std::size_t operator()(const lynx::base::String& k) const { return k.hash(); }\n};\n\ntemplate <>\nstruct hash<lynx::base::static_string::GenericCacheKey> {\n  std::size_t operator()(\n      const lynx::base::static_string::GenericCacheKey& k) const {\n    return k.hash;\n  }\n};\n}  // namespace std\n\n#endif  // BASE_INCLUDE_VALUE_BASE_STRING_H_\n"
  },
  {
    "path": "base/include/value/base_string_lldb.py",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport lldb\n\ndef get_string_summary(valobj, internal_dict):\n    \"\"\"Get summary for lynx::base::String.\n    \n    Args:\n        valobj: The value object of lynx::base::String\n        internal_dict: Internal dictionary\n        \n    Returns:\n        A string containing the string content\n    \"\"\"\n    try:\n        ref_impl = valobj.GetChildMemberWithName('ref_impl_')\n        if not ref_impl:\n            return '<invalid>'\n        ptr_value = ref_impl.GetValueAsUnsigned()\n        is_tagged = (ptr_value & 1) == 1\n        expr = valobj.EvaluateExpression('((lynx::base::RefCountedStringImpl*)((uintptr_t)ref_impl_ & ~1))->str_')\n        str_value = expr.GetSummary() if expr.IsValid() else None\n        if str_value and str_value != '\"\"':\n            if str_value.startswith('\"') and str_value.endswith('\"'):\n                str_value = str_value[1:-1]\n            return f'\"{str_value}\"'\n        else:\n            return '\"\"'\n    except Exception as e:\n        return f'<error: {str(e)}>'\n\ndef __lldb_init_module(debugger, internal_dict):\n    \"\"\"Initialize the LLDB module.\n    \n    Args:\n        debugger: The LLDB debugger\n        internal_dict: Internal dictionary\n    \"\"\"\n    # Add type summary for lynx::base::String\n    debugger.HandleCommand(\n        'type summary add -F base_string_lldb.get_string_summary -x \"^lynx::base::String$\" -w liblynx'\n    )\n    debugger.HandleCommand('type category enable liblynx')"
  },
  {
    "path": "base/include/value/base_value.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_VALUE_BASE_VALUE_H_\n#define BASE_INCLUDE_VALUE_BASE_VALUE_H_\n\n#include <cstring>\n#include <functional>\n#include <string>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"base/include/base_defines.h\"\n#include \"base/include/base_export.h\"\n#include \"base/include/closure.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/include/type_traits_addon.h\"\n#include \"base/include/value/base_string.h\"\n#include \"base/include/value/lynx_value_extended.h\"\n#include \"base/include/value/ref_type.h\"\n#include \"base/include/vector.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n#include \"base/include/value/lynx_value_types.h\"\n#ifdef __cplusplus\n}\n#endif\n\nnamespace lynx {\nnamespace runtime {\nclass MTSContext;\nclass MTSRuntime;\n}  // namespace runtime\n\nnamespace lepus {\n\nclass Value;\nstruct RestrictedValue;\n\nusing LepusValueIterator =\n    base::MoveOnlyClosure<void, const lepus::Value&, const lepus::Value&>;\n\nusing ExtendedValueIteratorCallback =\n    base::MoveOnlyClosure<void, lynx_api_env, const lynx_value&,\n                          const lynx_value&>;\n\n#define NormalNumberType(V) \\\n  V(Double, double)         \\\n  V(Int32, int32_t)         \\\n  V(UInt32, uint32_t)       \\\n  V(UInt64, uint64_t)\n\n#define NumberType(V) NormalNumberType(V) V(Int64, int64_t)\n\n/*\nLepusNG will add more types:\n  1. JSValue\n    It include:  type_ > Value_TypeCount || type_ < 0\n    It make lepus::Value can hold quickjs JSValue type\n*/\nenum ValueType {\n  Value_Nil,\n  Value_Double,\n  Value_Bool,\n  Value_String,\n  Value_Table,\n  Value_Array,\n  Value_Closure,\n  Value_CFunction,\n  Value_CPointer,\n  Value_Int32,\n  Value_Int64,\n  Value_UInt32,\n  Value_UInt64,\n  Value_NaN,\n  Value_CDate,\n  Value_RegExp,\n  Value_JSObject,\n  Value_Undefined,\n  Value_ByteArray,\n  Value_RefCounted,\n  // Value_TypeCount is used for encoding jsvalue tag,\n  // Adding a new Value_type needs to be inserted before 'Value_TypeCount'\n  Value_PrimJsValue,\n  Value_FunctionTable,\n  Value_TypeCount,\n};\n\nusing MTSContext = runtime::MTSContext;\nclass VMContext;\nclass CArray;\nclass Dictionary;\nclass ByteArray;\nclass RefCounted;\nclass BuiltinFunctionTable;\n\nenum CFunctionType {\n  CFunctionType_Default,\n  CFunctionType_Builtin,\n};\n\ntypedef Value (*CFunction)(MTSContext*, lepus::Value*, int);\ntypedef RestrictedValue (*CFunctionBuiltin)(VMContext*);\n\nclass BASE_EXPORT Value {\n private:\n  lynx_value value_;\n  lynx_api_env env_;\n  lynx_value_ref value_ref_;\n\n public:\n  Value() { value_.type = lynx_value_null; };\n  ~Value() { FreeValue(); }\n\n  enum CreateAsUndefinedTag { kCreateAsUndefinedTag };\n  explicit Value(CreateAsUndefinedTag);\n\n  // This constructor is UNSAFE and used by lepus::Dictionary to optimize\n  // value insertion.\n  // It does not initialize any field of Value and the Value may be in some\n  // invalid status(type not set, even not set to lynx_value_null) due to\n  // random memory.\n  enum UnsafeCreateAsUninitialized { kUnsafeCreateAsUninitialized };\n  Value(UnsafeCreateAsUninitialized) {}\n\n  Value(const Value& value);\n  Value(Value&& value) noexcept;\n  Value& operator=(const Value& value);\n  Value& operator=(Value&& value) noexcept;\n\n  explicit Value(const base::String& data);\n  explicit Value(base::String&& data);\n  explicit Value(const char* val);\n  explicit Value(const std::string& str);\n  explicit Value(std::string&& str);\n\n  explicit Value(const fml::RefPtr<Dictionary>& data);\n  explicit Value(fml::RefPtr<Dictionary>&& data);\n  explicit Value(const fml::WeakRefPtr<Dictionary>& data);\n  explicit Value(const fml::RefPtr<CArray>& data);\n  explicit Value(fml::RefPtr<CArray>&& data);\n  explicit Value(const fml::WeakRefPtr<CArray>& data);\n  explicit Value(const fml::RefPtr<lepus::ByteArray>& data);\n  explicit Value(fml::RefPtr<lepus::ByteArray>&& data);\n  explicit Value(const fml::RefPtr<lepus::RefCounted>& data);\n  explicit Value(fml::RefPtr<lepus::RefCounted>&& data);\n\n#define NumberConstructor(name, type) explicit Value(type data);\n\n  NumberType(NumberConstructor)\n\n      explicit Value(uint8_t data);\n#undef NumberConstructor\n\n  enum CreateAsNanTag { kCreateAsNanTag };\n  explicit Value(CreateAsNanTag);\n\n  explicit Value(bool val);\n  explicit Value(void* data);\n  explicit Value(CFunction val);\n  explicit Value(CFunctionBuiltin val);\n  explicit Value(BuiltinFunctionTable* data);\n  explicit Value(const lynx_value& value) : value_(value) {}\n  Value(lynx_api_env env, int64_t val, int32_t tag);\n  Value(lynx_api_env env, const lynx_value& value);\n  Value(lynx_api_env env, lynx_value&& value);\n\n  void CopyWeakValue(const Value& value);\n  BASE_INLINE void DupValue() const {\n    if (IsReference() && likely(value_.val_ptr)) {\n      reinterpret_cast<fml::RefCountedThreadSafeStorage*>(value_.val_ptr)\n          ->AddRef();\n    }\n  }\n  void FreeValue();\n  void ResetValueRef();\n\n  inline bool IsCDate() const {\n    return value_.type == lynx_value_object &&\n           value_.tag == static_cast<int32_t>(RefType::kCDate);\n  }\n  inline bool IsRegExp() const {\n    return value_.type == lynx_value_object &&\n           value_.tag == static_cast<int32_t>(RefType::kRegExp);\n  }\n\n  inline bool IsClosure() const {\n    return value_.type == lynx_value_object &&\n           value_.tag == static_cast<int32_t>(RefType::kClosure);\n  }\n  inline bool IsCallable() const { return IsClosure() || IsJSFunction(); }\n\n  void SetNumber(double val) {\n    FreeValue();\n    value_.type = lynx_value_double;\n    value_.val_double = val;\n  }\n\n  void SetNumber(int32_t val) {\n    FreeValue();\n    value_.type = lynx_value_int32;\n    value_.val_int32 = val;\n  }\n\n  void SetNumber(uint32_t val) {\n    FreeValue();\n    value_.type = lynx_value_uint32;\n    value_.val_uint32 = val;\n  }\n\n  void SetNumber(int64_t val) {\n    FreeValue();\n    value_.type = lynx_value_int64;\n    value_.val_int64 = val;\n  }\n\n  void SetNumber(uint64_t val) {\n    FreeValue();\n    value_.type = lynx_value_uint64;\n    value_.val_uint64 = val;\n  }\n\n  inline ValueType Type() const { return LegacyTypeFromLynxValue(value_); }\n\n  static Value Clone(const Value& src, bool clone_as_jsvalue = false);\n\n  static Value ShallowCopy(const Value& src, bool clone_as_jsvalue = false);\n\n  BASE_INLINE bool IsReference() const {\n    return (value_.type >= lynx_value_string &&\n            value_.type <= lynx_value_object);\n  }\n  inline void* Ptr() const { return value_.val_ptr; }\n\n  inline bool IsBool() const {\n    return value_.type == lynx_value_bool || IsJSBool();\n  }\n\n  inline bool IsString() const {\n    return value_.type == lynx_value_string || IsJSString();\n  }\n\n  inline bool IsInt64() const {\n    return value_.type == lynx_value_int64 || IsJSInteger();\n  }\n\n  inline bool IsNumber() const {\n    return (value_.type >= lynx_value_double &&\n            value_.type <= lynx_value_uint64) ||\n           IsJSNumber();\n  }\n\n  inline bool IsDouble() const { return value_.type == lynx_value_double; }\n\n  inline bool IsArray() const { return value_.type == lynx_value_array; }\n\n  inline bool IsTable() const { return value_.type == lynx_value_map; }\n\n  inline bool IsObject() const {\n    if (IsTable()) return true;\n    if (IsJSValue()) return IsJSTable();\n    return false;\n  }\n\n  inline bool IsArrayOrJSArray() const {\n    if (IsArray()) return true;\n    if (IsJSValue()) return IsJSArray();\n    return false;\n  }\n\n  inline bool IsCPointer() const {\n    return value_.type == lynx_value_external || IsJSCPointer();\n  }\n\n  inline bool IsRefCounted() const {\n    return value_.type == lynx_value_object &&\n           value_.tag < static_cast<int32_t>(RefType::kJSIObject);\n  }\n\n  inline bool IsInt32() const { return value_.type == lynx_value_int32; }\n  inline bool IsUInt32() const { return value_.type == lynx_value_uint32; }\n  inline bool IsUInt64() const { return value_.type == lynx_value_uint64; }\n  inline bool IsNil() const {\n    return (value_.type == lynx_value_null) || IsJsNull();\n  }\n  inline bool IsUndefined() const {\n    return value_.type == lynx_value_undefined || IsJSUndefined();\n  }\n  inline bool IsCFunction() const { return value_.type == lynx_value_function; }\n  inline CFunctionType GetCFunctionType() const {\n    return static_cast<CFunctionType>(value_.tag);\n  }\n  inline bool IsBuiltinFunctionTable() const {\n    return value_.type == lynx_value_function_table;\n  }\n  inline bool IsJSObject() const {\n    return value_.type == lynx_value_object &&\n           value_.tag == static_cast<int32_t>(RefType::kJSIObject);\n  }\n  inline bool IsByteArray() const {\n    return value_.type == lynx_value_arraybuffer;\n  }\n  inline bool IsNaN() const { return value_.type == lynx_value_nan; }\n\n  inline bool Bool() const {\n    if (value_.type != lynx_value_bool) return !IsFalse();\n    return value_.val_bool;\n  }\n  inline bool NaN() const {\n    return value_.type == lynx_value_nan && value_.val_bool;\n  }\n\n  double Number() const;\n\n#define NumberValue(name, type) type name() const;\n  NumberType(NumberValue)\n#undef NumberValue\n\n      /// @brief Returns a string view of the internal lepus string\n      /// storage. The result is guaranteed to be null terminated.\n      std::string_view StringView() const {\n    return StdString();\n  }\n\n  /// @brief Equivalent to `String().c_str()` but with better performance and\n  /// binary size because there is no temporary base::String object generated\n  /// although it does not retain the underlying string impl. Use only when C\n  /// string is required.\n  const char* CString() const { return StdString().c_str(); }\n\n  /// @brief Equivalent to `String().str()` but with better performance and\n  /// binary size because there is no temporary base::String object generated\n  /// although it does not retain the underlying string impl. Use this function\n  /// for most scenarios.\n  const std::string& StdString() const;\n\n  /// @Note\n  /// If possible, use StringView(), StdString() or CString() instead of this\n  /// because this function produce a temporary base::String instance although\n  /// it does not retain the underlying ref-counted impl.\n  ///\n  /// @brief For values representing string, js string, boolean and js boolean\n  /// this function returns a base::String instance which does not strong\n  /// retains the underlying impl object. You can use the result instance for\n  /// its string content safely in synchronous codes and when result is used for\n  /// copy or move construction of other base::String instances, the underlying\n  /// impl is retained normally by new instances.\n  ///\n  /// There is only one unsafe case which is assigning result of String()\n  /// directly to a variable in asynchronous lambda's capture list.\n  /// {\n  ///   lepus::Value value;\n  ///   do_async([s = value.String()] {\n  ///     // s is not retaining the underlying string and is not safe to be used\n  ///     // in asynchronous codes.\n  ///   });\n  /// }\n  ///\n  /// Instead you should write code like this so that the variable seen by\n  /// lambda body retains the string when capture list is formed.\n  /// {\n  ///   lepus::Value value;\n  ///   auto value_str = value.String();  // Not retaining the string\n  ///   do_async([value_str] {            // Capture by copy.\n  ///     // This value_str is copy constructed from outer value_str.\n  ///   });\n  /// }\n  base::String String() const&;\n\n  /// For rvalue this object, returns base::String which retains the underlying\n  /// string impl to avoid dangling pointer. Consider a function which returns\n  /// a Value. Without this overload, `s` would be using a dangling string impl.\n  ///   static lepus::Value funcA(const char* s) {\n  ///      return lepus::Value(s);\n  ///   }\n  ///   auto s = funcA(\"asdf\").String();\n  ///\n  /// There is one case which matches this overload but not desired.\n  ///   auto s = table_value.GetProperty(\"a\").String();\n  /// s would retain the string impl because String() is called for a rvalue\n  /// returned by GetProperty(). In this case the base::String returned is not\n  /// optimal but acceptable for safety reason.\n  base::String String() &&;\n\n  fml::WeakRefPtr<Dictionary> Table() const&;\n  fml::WeakRefPtr<CArray> Array() const&;\n  fml::WeakRefPtr<lepus::ByteArray> ByteArray() const&;\n  fml::WeakRefPtr<lepus::RefCounted> RefCounted() const&;\n\n  fml::RefPtr<Dictionary> Table() &&;\n  fml::RefPtr<CArray> Array() &&;\n  fml::RefPtr<lepus::ByteArray> ByteArray() &&;\n  fml::RefPtr<lepus::RefCounted> RefCounted() &&;\n\n  CFunction Function() const;\n  CFunctionBuiltin FunctionBuiltin() const;\n  BuiltinFunctionTable* FunctionTable() const;\n  void* CPoint() const;\n\n  void SetBool(bool);\n\n  void SetString(const base::String&);\n  void SetString(base::String&&);\n\n  void SetTable(const fml::RefPtr<lepus::Dictionary>&);\n  void SetTable(fml::RefPtr<lepus::Dictionary>&&);\n  void SetArray(const fml::RefPtr<lepus::CArray>&);\n  void SetArray(fml::RefPtr<lepus::CArray>&&);\n\n  void SetByteArray(const fml::RefPtr<lepus::ByteArray>&);\n  void SetByteArray(fml::RefPtr<lepus::ByteArray>&&);\n\n  void SetRefCounted(const fml::RefPtr<lepus::RefCounted>&);\n  void SetRefCounted(fml::RefPtr<lepus::RefCounted>&&);\n\n  bool SetProperty(uint32_t idx, const Value& val);\n  bool SetProperty(uint32_t idx, Value&& val);\n  bool SetProperty(const base::String& key, const Value& val);\n  bool SetProperty(base::String&& key, const Value& val);\n  bool SetProperty(base::String&& key, Value&& val);\n\n  Value GetProperty(uint32_t idx) const;\n  Value GetProperty(const base::String& key) const;\n\n  int GetLength() const;\n  bool Contains(const base::String& key) const;\n  static void MergeValue(lepus::Value& target, const lepus::Value& update);\n  static bool UpdateValueByPath(lepus::Value& target,\n                                const lepus::Value& update,\n                                const base::Vector<std::string>& path);\n\n  bool MarkConst() const;\n\n  BASE_INLINE bool IsJSValue() const {\n    return value_.type == lynx_value_extended;\n  }\n\n  lynx_api_env env() const { return env_; }\n\n  /*\n   deep convert jsvalue to lepus::Value when deep_convert == true;\n   */\n  Value ToLepusValue(bool deep_convert = false) const;\n\n  inline bool IsJSCPointer() const {\n    return IsJSValue() && (value_.tag >> 16) == lynx_value_external;\n  }\n\n  inline void* LEPUSCPointer() const {\n    DCHECK(IsJSCPointer());\n    void* ret;\n    lynx_value_get_external_ext(env_, value_, &ret);\n    return ret;\n  }\n\n  bool IsJSArray() const;\n  bool IsJSTable() const;\n\n  inline bool IsJSBool() const {\n    return IsJSValue() && (value_.tag >> 16) == lynx_value_bool;\n  }\n  inline bool LEPUSBool() const {\n    if (!IsJSBool()) return false;\n    bool ret;\n    lynx_value_get_bool_ext(env_, value_, &ret);\n    return ret;\n  }\n  inline bool IsJSString() const {\n    return IsJSValue() && (value_.tag >> 16) == lynx_value_string;\n  }\n\n  inline bool IsJSUndefined() const {\n    return IsJSValue() && (value_.tag >> 16) == lynx_value_undefined;\n  }\n\n  inline bool IsJSNumber() const {\n    int32_t type = value_.tag >> 16;\n    return IsJSValue() &&\n           (type == lynx_value_int32 || type == lynx_value_int64 ||\n            type == lynx_value_double);\n  }\n\n  inline bool IsJsNull() const {\n    return IsJSValue() && (value_.tag >> 16) == lynx_value_null;\n  }\n\n  double LEPUSNumber() const;\n  bool IsJSInteger() const;\n  bool IsJSFunction() const;\n  int GetJSLength() const;\n  bool IsJSFalse() const;\n  int64_t JSInteger() const;\n  std::string ToString() const;\n\n  void SetCPoint(void*);\n  void SetCFunction(CFunction);\n  void SetNan(bool);\n\n  bool IsTrue() const { return !IsFalse(); }\n\n  bool IsFalse() const {\n    return value_.type == lynx_value_null || value_.type == lynx_value_nan ||\n           value_.type == lynx_value_undefined ||\n           (value_.type == lynx_value_bool && !Bool()) ||\n           (IsNumber() && Number() == 0) ||\n           (value_.type == lynx_value_string && StringView().empty()) ||\n           IsJSFalse();\n  }\n  inline bool IsEmpty() const {\n    return (value_.type == lynx_value_null) ||\n           (value_.type == lynx_value_undefined) || IsJSUndefined() ||\n           IsJsNull();\n  }\n\n  inline void SetNil() {\n    FreeValue();\n    value_.type = lynx_value_null;\n    value_.val_ptr = nullptr;\n  }\n\n  inline void SetUndefined() {\n    FreeValue();\n    value_.type = lynx_value_undefined;\n    value_.val_ptr = nullptr;\n  }\n\n  bool IsEqual(const Value& value) const;\n  BASE_EXPORT friend bool operator==(const Value& left, const Value& right);\n\n  BASE_EXPORT friend bool operator!=(const Value& left, const Value& right) {\n    return !(left == right);\n  }\n\n  friend Value operator+(const Value& left, const Value& right) {\n    Value value;\n    if (left.IsNumber() && right.IsNumber()) {\n      if (left.IsInt64() && right.IsInt64()) {\n        value.SetNumber(left.Int64() + right.Int64());\n      } else {\n        value.SetNumber(left.Number() + right.Number());\n      }\n    }\n    return value;\n  }\n\n  friend Value operator-(const Value& left, const Value& right) {\n    Value value;\n    if (left.IsNumber() && right.IsNumber()) {\n      if (left.IsInt64() && right.IsInt64()) {\n        value.SetNumber(left.Int64() - right.Int64());\n      } else {\n        value.SetNumber(left.Number() - right.Number());\n      }\n    }\n    return value;\n  }\n\n  friend Value operator*(const Value& left, const Value& right) {\n    Value value;\n    if (left.IsNumber() && right.IsNumber()) {\n      if (left.IsInt64() && right.IsInt64()) {\n        value.SetNumber(left.Int64() * right.Int64());\n      } else {\n        value.SetNumber(left.Number() * right.Number());\n      }\n    }\n    return value;\n  }\n\n  friend Value operator/(const Value& left, const Value& right) {\n    Value value;\n    if (left.IsNumber() && right.IsNumber()) {\n      if (left.IsInt64() && right.IsInt64()) {\n        value.SetNumber(left.Int64() / right.Int64());\n      } else {\n        value.SetNumber(left.Number() / right.Number());\n      }\n    }\n    return value;\n  }\n\n  friend Value operator%(const Value& left, const Value& right) {\n    Value value;\n    if (left.IsNumber() && right.IsNumber()) {\n      value.SetNumber((int64_t)(left.Number()) % ((int64_t)right.Number()));\n    }\n    return value;\n  }\n\n  Value& operator+=(const Value& value) {\n    if (IsNumber() && value.IsNumber()) {\n      if (IsInt64() && value.IsInt64()) {\n        SetNumber(Int64() + value.Int64());\n      } else {\n        SetNumber(Number() + value.Number());\n      }\n    }\n    return *this;\n  }\n\n  Value& operator-=(const Value& value) {\n    if (IsNumber() && value.IsNumber()) {\n      if (IsInt64() && value.IsInt64()) {\n        SetNumber(Int64() - value.Int64());\n      } else {\n        SetNumber(Number() - value.Number());\n      }\n    }\n    return *this;\n  }\n\n  Value& operator*=(const Value& value) {\n    if (IsNumber() && value.IsNumber()) {\n      if (IsInt64() && value.IsInt64()) {\n        SetNumber(Int64() * value.Int64());\n      } else {\n        SetNumber(Number() * value.Number());\n      }\n    }\n    return *this;\n  }\n\n  Value& operator/=(const Value& value) {\n    if (IsNumber() && value.IsNumber()) {\n      if (IsInt64() && value.IsInt64()) {\n        SetNumber(Int64() / value.Int64());\n      } else {\n        SetNumber(Number() / value.Number());\n      }\n    }\n    return *this;\n  }\n\n  Value& operator%=(const Value& value) {\n    if (IsNumber() && value.IsNumber()) {\n      SetNumber((int64_t)Number() % (int64_t)value.Number());\n    }\n    return *this;\n  }\n\n  void Print() const;\n  void PrintValue(std::ostream& output, bool ignore_other = false,\n                  bool pretty = false, bool sort_map_key = false) const;\n\n  // Preferred for unit-tests for stable output.\n  inline void PrintValueSorted(std::ostream& output) const {\n    PrintValue(output, false, false, true);\n  }\n\n  // `operator<<` will NOT print entries of a map in order of keys.\n  // So avoid using it in unit-tests for comparision. Use `PrintValueSorted`\n  // instead.\n  friend std::ostream& operator<<(std::ostream& output, const lepus::Value& v) {\n    v.PrintValue(output);\n    return output;\n  }\n\n  // override operator<< for class LogStream\n  friend base::logging::LogStream& operator<<(base::logging::LogStream& output,\n                                              const lepus::Value& v) {\n    std::ostringstream output_lepus_value;\n    v.PrintValue(output_lepus_value);\n    output << output_lepus_value;\n    return output;\n  }\n\n  void IteratorJSValue(const LepusValueIterator& callback) const;\n  const lynx_value& value() const& { return value_; }\n  lynx_value& value() && { return value_; }\n  static ValueType LegacyTypeFromLynxValue(const lynx_value& value);\n  static lynx_value_type ToLynxValueType(ValueType type);\n\n  static inline void IterateExtendedValue(\n      lynx_api_env env, const lynx_value& val,\n      ExtendedValueIteratorCallback* pfunc) {\n    if (!env || !val.val_ptr) {\n      LOGE(\"IterateExtendedValue but env or value is nil\");\n      return;\n    }\n    lynx_value_iterate_value_ext(env, val, LynxValueIteratorCallback,\n                                 reinterpret_cast<void*>(pfunc), nullptr);\n  }\n\n  /* The function is used for :\n    1. convert lynx_value to lepus::Value when flag == 0;\n    2. deep clone lynx_value to lepus::Value when flag == 1;\n    3. shallow copy lynx_value to lepus::Value when flag == 2;\n  flag default's value is 0\n  */\n  static Value ToLepusValue(lynx_api_env env, const lynx_value& val,\n                            int32_t flag = 0);\n  static bool IsLepusValueEqualToExtendedValue(lynx_api_env env,\n                                               const lepus::Value& src,\n                                               const lynx_value& dst);\n  static CArray* DummyArray();\n  static Dictionary* DummyTable();\n  static class ByteArray* DummyByteArray();\n\n  static void ForEachLepusValue(const Value& value, LepusValueIterator func);\n\n  // A flag telling `base::flex_optional<>` to save memory.\n  using AlwaysUseFlexOptionalMemSave = bool;\n\n  // A flag telling base containers such as `base::Vector<Value>` to optimize\n  // for reallocate, insert and erase.\n  using TriviallyRelocatable = bool;\n\n private:\n  Value GetPropertyFromTableOrArray(const std::string& key) const;\n  bool SetPropertyToTableOrArray(const std::string& key, const Value& update);\n  inline lynx_value DeepCopyExtendedValue() const {\n    lynx_value ret;\n    lynx_value_deep_copy_value_ext(env_, value_, &ret);\n    return ret;\n  }\n  bool IsJSUninitialized() const {\n    bool ret;\n    lynx_value_is_uninitialized_ext(env_, value_, &ret);\n    return ret;\n  }\n\n  static void ToLepusValueRecursively(Value& value, bool deep_convert);\n  static Value CloneRecursively(const Value& src, bool clone_as_jsvalue);\n\n  static inline void LynxValueIteratorCallback(lynx_api_env env, lynx_value key,\n                                               lynx_value value, void* pfunc,\n                                               void* raw_data) {\n    reinterpret_cast<ExtendedValueIteratorCallback*>(pfunc)->operator()(\n        env, key, value);\n  }\n\n  static Value ToLepusArray(lynx_api_env env, const lynx_value& val,\n                            int32_t flag = 0);\n  static Value ToLepusMap(lynx_api_env env, const lynx_value& val,\n                          int32_t flag = 0);\n  static bool IsLepusArrayEqualToExtendedArray(lynx_api_env env,\n                                               lepus::CArray* src,\n                                               const lynx_value& dst);\n  static bool IsLepusDictEqualToExtendedDict(lynx_api_env env,\n                                             lepus::Dictionary* src,\n                                             const lynx_value& dst);\n};\n}  // namespace lepus\n}  // namespace lynx\ntypedef lynx::lepus::Value lepus_value;\n#endif  // BASE_INCLUDE_VALUE_BASE_VALUE_H_\n"
  },
  {
    "path": "base/include/value/byte_array.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_VALUE_BYTE_ARRAY_H_\n#define BASE_INCLUDE_VALUE_BYTE_ARRAY_H_\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/base_export.h\"\n#include \"base/include/value/ref_counted_class.h\"\n#include \"base/include/value/ref_type.h\"\n\nnamespace lynx {\nnamespace lepus {\n\nclass BASE_EXPORT ByteArray : public lepus::RefCounted {\n public:\n  static fml::RefPtr<ByteArray> Create() {\n    return fml::AdoptRef<ByteArray>(new ByteArray());\n  }\n  static fml::RefPtr<ByteArray> Create(std::unique_ptr<uint8_t[]> ptr,\n                                       size_t length) {\n    return fml::AdoptRef<ByteArray>(new ByteArray(std::move(ptr), length));\n  }\n\n  std::unique_ptr<uint8_t[]> MovePtr();\n\n  size_t GetLength();\n\n  uint8_t* GetPtr();\n\n  ~ByteArray() override = default;\n\n  RefType GetRefType() const override { return RefType::kByteArray; };\n\n protected:\n  ByteArray() = default;\n  ByteArray(std::unique_ptr<uint8_t[]> ptr, size_t length);\n\n  friend class Value;\n\n  void Reset() {\n    ptr_ = nullptr;\n    length_ = 0;\n  }\n\n private:\n  std::unique_ptr<uint8_t[]> ptr_;\n  size_t length_{0};\n};\n\n}  // namespace lepus\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_VALUE_BYTE_ARRAY_H_\n"
  },
  {
    "path": "base/include/value/lynx_api_types.h",
    "content": "/**\n * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved.\n *\n * Use of this source code is governed by a MIT license that can be\n * found in the LICENSE file in the root of the source tree.\n */\n\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VALUE_LYNX_API_TYPES_H_\n#define BASE_INCLUDE_VALUE_LYNX_API_TYPES_H_\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifndef LYNX_VALUE_COMPILE_UNIT\n#define LYNX_VALUE_COMPILE_UNIT\n#endif\n\n#define LYNX_VALUE_OPAQUE_STRUCT__2(name, unit) lynx_##name##__##unit\n#define LYNX_VALUE_OPAQUE_STRUCT__1(name, unit) \\\n  LYNX_VALUE_OPAQUE_STRUCT__2(name, unit)\n#define LYNX_VALUE_OPAQUE_STRUCT(name) \\\n  LYNX_VALUE_OPAQUE_STRUCT__1(name, LYNX_VALUE_COMPILE_UNIT)\n\ntypedef struct lynx_value_ptr__* lynx_value_ptr;\ntypedef struct lynx_api_env__* lynx_api_env;\n\ntypedef struct LYNX_VALUE_OPAQUE_STRUCT(api_context) * lynx_api_context;\ntypedef enum {\n  lynx_api_ok,\n  lynx_api_failed,\n  lynx_api_invalid_arg,\n  lynx_api_not_support,\n  lynx_api_bool_expected,\n  lynx_api_double_expected,\n  lynx_api_int32_expected,\n  lynx_api_uint32_expected,\n  lynx_api_int64_expected,\n  lynx_api_uint64_expected,\n  lynx_api_string_expected,\n  lynx_api_array_expected,\n  lynx_api_map_expected,\n  lynx_api_external_expected,\n  lynx_api_arraybuffer_expected,\n  lynx_api_function_expected,\n  lynx_api_index_out_of_range,\n  lynx_api_handle_scope_mismatch,\n} lynx_api_status;\n\nstruct lynx_api_env__ {\n  lynx_api_context ctx;\n};\n\n#endif  // BASE_INCLUDE_VALUE_LYNX_API_TYPES_H_\n"
  },
  {
    "path": "base/include/value/lynx_value_api.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VALUE_LYNX_VALUE_API_H_\n#define BASE_INCLUDE_VALUE_LYNX_VALUE_API_H_\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"base/include/value/lynx_value_types.h\"\n\n#ifdef _WIN32\n#ifdef lynx_EXPORTS\n#define LYNX_VALUE_EXPORT __declspec(dllexport)\n#else\n#define LYNX_VALUE_EXPORT __declspec(dllimport)\n#endif  // lynx_EXPORTS\n#elif defined(__ANDROID__)\n#define LYNX_VALUE_EXPORT\n#else\n#define LYNX_VALUE_EXPORT __attribute__((visibility(\"default\")))\n#endif  // _WIN32\n\n// Get the type of lynx_value.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_typeof(lynx_api_env env,\n                                                    lynx_value value,\n                                                    lynx_value_type* result);\n// This API creates a lynx_value of type null.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_null(lynx_api_env env,\n                                                         lynx_value* result);\n// This API creates a lynx_value of type bool with given values.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_bool(lynx_api_env env,\n                                                         bool value,\n                                                         lynx_value* result);\n// This API creates a lynx_value of type bool with given values.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_double(lynx_api_env env,\n                                                           double value,\n                                                           lynx_value* result);\n// This API creates a lynx_value of type int32_t with given values.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_int32(lynx_api_env env,\n                                                          int32_t value,\n                                                          lynx_value* result);\n// This API creates a lynx_value of type uint32_t with given values.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_uint32(lynx_api_env env,\n                                                           uint32_t value,\n                                                           lynx_value* result);\n// This API creates a lynx_value of type int64_t with given values.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_int64(lynx_api_env env,\n                                                          int64_t value,\n                                                          lynx_value* result);\n// This API creates a lynx_value of type uint64_t with given values.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_uint64(lynx_api_env env,\n                                                           uint64_t value,\n                                                           lynx_value* result);\n// This API creates a string value from a UTF8-encoded C string. The native\n// string is copied.\n// This API allocates memory on the heap to store the string. After use,\n// lynx_value_remove_reference must be called to free this portion of\n// memory, otherwise it will result in a memory leak.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_string_utf8(\n    lynx_api_env env, const char* str, size_t length, lynx_value* result);\n// This API creates a map container.\n// This API allocates memory on the heap to store the container. After use,\n// lynx_value_remove_reference must be called to free this portion of\n// memory, otherwise it will result in a memory leak.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_map(lynx_api_env env,\n                                                        lynx_value* result);\n// This API creates a array container.\n// This API allocates memory on the heap to store the container. After use,\n// lynx_value_remove_reference must be called to free this portion of\n// memory, otherwise it will result in a memory leak.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_create_array(lynx_api_env env,\n                                                          lynx_value* result);\n\n// Read the numberic value from lynx_value.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_number(lynx_api_env env,\n                                                        lynx_value value,\n                                                        double* result);\n// Read the double value from lynx_value. lynx_api_double_expected is returned\n// if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_double(lynx_api_env env,\n                                                        lynx_value value,\n                                                        double* result);\n// Read the int32_t value from lynx_value. lynx_api_int32_expected is returned\n// if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_int32(lynx_api_env env,\n                                                       lynx_value value,\n                                                       int32_t* result);\n// Read the uint32_t value from lynx_value. lynx_api_uint32_expected is returned\n// if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_uint32(lynx_api_env env,\n                                                        lynx_value value,\n                                                        uint32_t* result);\n// Read the int64_t value from lynx_value. lynx_api_int64_expected is returned\n// if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_int64(lynx_api_env env,\n                                                       lynx_value value,\n                                                       int64_t* result);\n// Read the uint64_t value from lynx_value. lynx_api_uint64_expected is returned\n// if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_uint64(lynx_api_env env,\n                                                        lynx_value value,\n                                                        uint64_t* result);\n// Read the bool value from lynx_value. lynx_api_bool_expected is returned\n// if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_bool(lynx_api_env env,\n                                                      lynx_value value,\n                                                      bool* result);\n// Read the UTF8-encoded string value from lynx_value. lynx_api_string_expected\n// is returned if the types do not match. If the buf argument is passed NULL,\n// the length of the string in bytes and excluding the null terminator is\n// returned in result\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_string_utf8(lynx_api_env env,\n                                                             lynx_value value,\n                                                             char* buf,\n                                                             size_t bufsize,\n                                                             size_t* result);\n// Read the length of array from lynx_value. lynx_api_array_expected is returned\n// if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_array_length(lynx_api_env env,\n                                                              lynx_value value,\n                                                              uint32_t* result);\n// Insert lynx_value element into array at the index specified.\n// lynx_api_array_expected is returned if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_set_element(lynx_api_env env,\n                                                         lynx_value object,\n                                                         uint32_t index,\n                                                         lynx_value value);\n// Read the array element from lynx_value. lynx_api_array_expected is returned\n// if the types do not match.\n// The reference count of the lynx_value read out is automatically incremented\n// by 1. After use, lynx_value_remove_reference needs to be called to free the\n// memory Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_element(lynx_api_env env,\n                                                         lynx_value object,\n                                                         uint32_t index,\n                                                         lynx_value* result);\n// This API checks if the object passed in has the named property.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_has_property(lynx_api_env env,\n                                                          lynx_value object,\n                                                          const char* utf8name,\n                                                          bool* result);\n// Read the map keys from lynx_value. lynx_api_map_expected is returned\n// if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_get_property_names(\n    lynx_api_env env, lynx_value object, lynx_value* result);\n// Set lynx_value property into map at the key specified.\n// lynx_api_map_expected is returned if the types do not match.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status\nlynx_value_set_named_property(lynx_api_env env, lynx_value object,\n                              const char* utf8name, lynx_value value);\n// Read the map property from lynx_value. lynx_api_map_expected is returned\n// if the types do not match.\n// The reference count of the lynx_value read out is automatically incremented\n// by 1. After use, lynx_value_remove_reference needs to be called to free the\n// memory. Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status\nlynx_value_get_named_property(lynx_api_env env, lynx_value object,\n                              const char* utf8name, lynx_value* result);\n// This API is used for iterating through array or map type lynx_value.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_iterate_value(\n    lynx_api_env env, lynx_value object, lynx_value_iterator_callback callback,\n    void* pfunc, void* raw_data);\n// Increment the reference count by 1 for pointer data.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_add_reference(\n    lynx_api_env env, lynx_value value, lynx_value_ref* result);\n// Decrement the reference count by 1 for pointer data.\n// Returns lynx_api_ok if the API succeeded.\nLYNX_VALUE_EXPORT lynx_api_status lynx_value_remove_reference(\n    lynx_api_env env, lynx_value value, lynx_value_ref ref);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  // BASE_INCLUDE_VALUE_LYNX_VALUE_API_H_\n"
  },
  {
    "path": "base/include/value/lynx_value_extended.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VALUE_LYNX_VALUE_EXTENDED_H_\n#define BASE_INCLUDE_VALUE_LYNX_VALUE_EXTENDED_H_\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"base/include/value/lynx_value_types.h\"\n\n#define MAKE_LYNX_VALUE(val, tag)                                  \\\n  {                                                                \\\n    .val_int64 = static_cast<int64_t>(LEPUS_VALUE_GET_INT64(val)), \\\n    .type = lynx_value_extended, .tag = tag                        \\\n  }\nlynx_api_status lynx_value_get_bool_ext(lynx_api_env env, lynx_value value,\n                                        bool* result);\nlynx_api_status lynx_value_get_double_ext(lynx_api_env env, lynx_value value,\n                                          double* result);\nlynx_api_status lynx_value_get_int32_ext(lynx_api_env env, lynx_value value,\n                                         int32_t* result);\nlynx_api_status lynx_value_get_int64_ext(lynx_api_env env, lynx_value value,\n                                         int64_t* result);\nlynx_api_status lynx_value_is_integer_ext(lynx_api_env env, lynx_value value,\n                                          bool* result);\nlynx_api_status lynx_value_get_integer_ext(lynx_api_env env, lynx_value value,\n                                           int64_t* result);\nlynx_api_status lynx_value_get_number_ext(lynx_api_env env, lynx_value value,\n                                          double* result);\nlynx_api_status lynx_value_get_string_ref_ext(lynx_api_env env,\n                                              lynx_value value, void** result);\nlynx_api_status lynx_value_get_external_ext(lynx_api_env env, lynx_value value,\n                                            void** result);\nlynx_api_status lynx_value_get_length_ext(lynx_api_env env, lynx_value value,\n                                          uint32_t* result);\nlynx_api_status lynx_value_is_array_ext(lynx_api_env env, lynx_value value,\n                                        bool* result);\nlynx_api_status lynx_value_set_element_ext(lynx_api_env env, lynx_value object,\n                                           uint32_t index, lynx_value value);\nlynx_api_status lynx_value_get_element_ext(lynx_api_env env, lynx_value object,\n                                           uint32_t index, lynx_value* result);\nlynx_api_status lynx_value_is_map_ext(lynx_api_env env, lynx_value value,\n                                      bool* result);\nlynx_api_status lynx_value_set_named_property_ext(lynx_api_env env,\n                                                  lynx_value object,\n                                                  const char* utf8name,\n                                                  lynx_value value);\nlynx_api_status lynx_value_has_named_property_ext(lynx_api_env env,\n                                                  lynx_value object,\n                                                  const char* utf8name,\n                                                  bool* result);\nlynx_api_status lynx_value_get_named_property_ext(lynx_api_env env,\n                                                  lynx_value object,\n                                                  const char* utf8name,\n                                                  lynx_value* result);\nlynx_api_status lynx_value_is_function_ext(lynx_api_env env, lynx_value value,\n                                           bool* result);\nlynx_api_status lynx_value_to_string_utf8_ext(lynx_api_env env,\n                                              lynx_value value, void* result);\nlynx_api_status lynx_value_typeof_ext(lynx_api_env env, lynx_value value,\n                                      lynx_value_type* result);\nlynx_api_status lynx_value_iterate_value_ext(\n    lynx_api_env env, lynx_value object, lynx_value_iterator_callback callback,\n    void* pfunc, void* raw_data);\nlynx_api_status lynx_value_equals_ext(lynx_api_env env, lynx_value lhs,\n                                      lynx_value rhs, bool* result);\nlynx_api_status lynx_value_deep_copy_value_ext(lynx_api_env env, lynx_value src,\n                                               lynx_value* result);\nlynx_api_status lynx_value_print_ext(lynx_api_env env, lynx_value value,\n                                     void* stream,\n                                     lynx_value_print_ext_callback callback);\nlynx_api_status lynx_value_add_reference_ext(lynx_api_env env, lynx_value value,\n                                             lynx_value_ref* result);\nlynx_api_status lynx_value_add_reference_weak_ext(lynx_api_env env,\n                                                  lynx_value value,\n                                                  lynx_value_ref* result);\nlynx_api_status lynx_value_move_reference_ext(lynx_api_env env,\n                                              lynx_value src_val,\n                                              lynx_value_ref src_ref,\n                                              lynx_value_ref* result);\nlynx_api_status lynx_value_remove_reference_ext(lynx_api_env env,\n                                                lynx_value value,\n                                                lynx_value_ref ref);\nlynx_api_status lynx_value_has_ref_count_ext(lynx_api_env env, lynx_value val,\n                                             bool* result);\nlynx_api_status lynx_value_is_uninitialized_ext(lynx_api_env env,\n                                                lynx_value val, bool* result);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  // BASE_INCLUDE_VALUE_LYNX_VALUE_EXTENDED_H_\n"
  },
  {
    "path": "base/include/value/lynx_value_lldb.py",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport lldb\nfrom enum import IntEnum\n\n# LLDB formatter for lynx_value.\n\nclass LynxValueType(IntEnum):\n    NULL = 0\n    UNDEFINED = 1\n    BOOL = 2\n    DOUBLE = 3\n    INT32 = 4\n    UINT32 = 5\n    INT64 = 6\n    UINT64 = 7\n    NAN = 8\n    STRING = 9\n    ARRAY = 10\n    DICTIONARY = 11\n    ARRAY_BUFFER = 12\n    OBJECT = 13\n    FUNCTION = 14\n    EXTERNAL = 15\n    EXTENDED = 16\n\nclass LynxValueSyntheticProvider:\n  def __init__(self, val_obj, internal_dict):\n    self.val_obj = val_obj\n\n  def update(self):\n    try:\n      # get type field in lynx_value\n      self.type_field = self.val_obj.GetChildMemberWithName('type')\n      self.type_value = self.type_field.GetValueAsUnsigned()\n      \n      # get val_ptr field in lynx_value\n      self.val_ptr_field = self.val_obj.GetChildMemberWithName('val_ptr')\n\n      # get tag field in lynx_value\n      self.tag_field = self.val_obj.GetChildMemberWithName('tag')\n      \n      self.child_count = 4\n      self.load_addr = self.val_obj.GetLoadAddress()\n    except:\n      self.type_value = 0\n      self.child_count = 0\n\n  def num_children(self, max_children):\n    return self.child_count\n\n  def get_child_at_index(self,index):\n    if index < 0 or index >= self.child_count:\n      return None\n    \n    try:\n      if index == 0:\n        # If val_ptr is a RefCountedStringImpl raw pointer\n        if self.type_value == LynxValueType.STRING:\n          string_type = self.val_obj.GetTarget().FindFirstType(\n              'lynx::base::RefCountedStringImpl').GetPointerType()\n          return self.val_ptr_field.Cast(string_type)\n        # If val_ptr is a CArray raw pointer\n        elif self.type_value == LynxValueType.ARRAY:\n          array_type = self.val_obj.GetTarget().FindFirstType(\n              'lynx::lepus::CArray').GetPointerType()\n          return self.val_ptr_field.Cast(array_type)\n        # If val_ptr is a Dictionary raw pointer\n        elif self.type_value == LynxValueType.DICTIONARY:\n          dict_type = self.val_obj.GetTarget().FindFirstType(\n              'lynx::lepus::Dictionary').GetPointerType()\n          return self.val_ptr_field.Cast(dict_type)\n        # If val_ptr is a ByteArray raw pointer\n        elif self.type_value == LynxValueType.ARRAY_BUFFER:\n          array_buffer_type = self.val_obj.GetTarget().FindFirstType(\n              'lynx::lepus::ByteArray').GetPointerType()\n          return self.val_ptr_field.Cast(array_buffer_type)\n        elif self.type_value == LynxValueType.OBJECT:\n          # TODO(frendy): Use RefCounted type\n          object_type = self.val_obj.GetTarget().FindFirstType(\n              'lynx::fml::RefCountedThreadSafeStorage').GetPointerType()\n          return self.val_ptr_field.Cast(object_type)\n        else:\n          return self.get_union_value()\n\n      if index == 1:\n        return self.type_field\n      if index == 2:\n        return self.tag_field\n      if index == 3:\n        return self.val_obj.GetChildMemberWithName('val_int64')\n    except:\n      return None\n\n  def has_children(self):\n    return True\n\n  def get_union_value(self):\n    try:\n      if self.type_value in [LynxValueType.BOOL, LynxValueType.NAN]:\n        return self.val_obj.GetChildMemberWithName('val_bool')\n      elif self.type_value == LynxValueType.DOUBLE:\n        return self.val_obj.GetChildMemberWithName('val_double')\n      elif self.type_value in [LynxValueType.FUNCTION, LynxValueType.EXTERNAL]:\n        return self.val_ptr_field\n      else:\n        return self.val_obj.GetChildMemberWithName('val_int64')\n    except:\n      return None\n\ndef __lldb_init_module(debugger, internal_dict):\n    debugger.HandleCommand(\n      'type synthetic add -x \"^lynx_value$\" -l lynx_value_lldb.LynxValueSyntheticProvider -w liblynx'\n    )\n    debugger.HandleCommand('type category enable liblynx')"
  },
  {
    "path": "base/include/value/lynx_value_types.h",
    "content": "/**\n * Copyright (c) 2017 Node.js API collaborators. All Rights Reserved.\n *\n * Use of this source code is governed by a MIT license that can be\n * found in the LICENSE file in the root of the source tree.\n */\n\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VALUE_LYNX_VALUE_TYPES_H_\n#define BASE_INCLUDE_VALUE_LYNX_VALUE_TYPES_H_\n\n#include <stddef.h>\n#include <stdint.h>\n\n#include \"base/include/value/lynx_api_types.h\"\n\ntypedef struct LYNX_VALUE_OPAQUE_STRUCT(value_ref) * lynx_value_ref;\n\ntypedef enum : uint8_t {\n  lynx_value_null,\n  lynx_value_undefined,\n  lynx_value_bool,\n  lynx_value_double,\n  lynx_value_int32,\n  lynx_value_uint32,\n  lynx_value_int64,\n  lynx_value_uint64,\n  lynx_value_nan,\n  lynx_value_string,\n  lynx_value_array,\n  lynx_value_map,\n  lynx_value_arraybuffer,\n  lynx_value_object,\n  lynx_value_function,\n  lynx_value_function_table,\n  lynx_value_external,  // external raw pointer\n  lynx_value_extended,\n} lynx_value_type;\n\n#define LYNX_VALUE_BASE_STORAGE_DEFINITION \\\n  union {                                  \\\n    bool val_bool;                         \\\n    double val_double;                     \\\n    int32_t val_int32;                     \\\n    uint32_t val_uint32;                   \\\n    int64_t val_int64;                     \\\n    uint64_t val_uint64;                   \\\n    lynx_value_ptr val_ptr;                \\\n  };\n\nstruct lynx_value {\n  LYNX_VALUE_BASE_STORAGE_DEFINITION\n  lynx_value_type type;\n\n  uint8_t __unnamed0__;\n  uint8_t __unnamed1__;\n  uint8_t __unnamed2__;\n\n  int32_t tag;\n};\n\nstatic_assert(sizeof(lynx_value) == 16,\n              \"The lynx_value was precisely designed to be 16 bytes.\");\n\ntypedef void (*lynx_value_iterator_callback)(lynx_api_env env, lynx_value key,\n                                             lynx_value val, void* pfunc,\n                                             void* raw_data);\ntypedef void (*lynx_value_print_ext_callback)(void* stream, const char* str);\n\n#endif  // BASE_INCLUDE_VALUE_LYNX_VALUE_TYPES_H_\n"
  },
  {
    "path": "base/include/value/path_parser.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VALUE_PATH_PARSER_H_\n#define BASE_INCLUDE_VALUE_PATH_PARSER_H_\n\n#include <string>\n\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace lepus {\n\nusing PathVector = base::InlineVector<std::string, 8>;\n\n// handle value path\n// a.b => [a, b]\n// a[3] => [a, 3]\n// returns an empty vector if path is invalid\nPathVector ParseValuePath(const std::string &path);\n\n}  // namespace lepus\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_VALUE_PATH_PARSER_H_\n"
  },
  {
    "path": "base/include/value/ref_counted_class.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VALUE_REF_COUNTED_CLASS_H_\n#define BASE_INCLUDE_VALUE_REF_COUNTED_CLASS_H_\n\n#include <memory>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/ref_type.h\"\n\nnamespace lynx {\nnamespace lepus {\nclass RefCountedBase : public fml::RefCountedThreadSafeStorage {\n public:\n  void ReleaseSelf() const override { delete this; }\n  virtual void visitor(void* rt, void* mark_func, uint64_t trace_tool) {}\n  ~RefCountedBase() override = default;\n\n  virtual bool IsConst() const { return false; }\n\n  /*\n   * Return RefType of this RefCountedBase.\n   * ByteArray in base/include/value/byte_array.h,\n   * Value_JSOBject in core/runtime/lepus/js_object.h,\n   * Element in core/renderer/dom/element.h,\n   * AirElement in core/renderer/dom/air/air_element/air_element.h\n   */\n  virtual RefType GetRefType() const = 0;\n\n protected:\n  RefCountedBase() = default;\n};\n\nclass RefCounted : public RefCountedBase {\n public:\n  virtual fml::RefPtr<RefCounted> Clone() {\n    return fml::RefPtr<RefCounted>(nullptr);\n  }\n\n  virtual void Print(std::ostream& output) {}\n\n  virtual bool Equals(const fml::RefPtr<RefCounted>& other) {\n    return this == other.get();\n  }\n\n  std::unique_ptr<Value> js_object_cache;\n};\n\n}  // namespace lepus\n\nnamespace fml {\n// Specialized for lepus::RefCounted to solve compiling issues.\ntemplate <typename D>\nWeakRefPtr<D> static_ref_ptr_cast(const WeakRefPtr<lepus::RefCounted>& rhs) {\n  return WeakRefPtr<D>(static_cast<D*>(rhs.get()));\n}\n}  // namespace fml\n\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_VALUE_REF_COUNTED_CLASS_H_\n"
  },
  {
    "path": "base/include/value/ref_type.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VALUE_REF_TYPE_H_\n#define BASE_INCLUDE_VALUE_REF_TYPE_H_\n\n#include <cstdint>\n\nnamespace lynx {\nnamespace lepus {\n\n// TODO(songshourui.null): Currently, Element, AirElement, lepus::Array,\n// lepus::Table, ByteArray, and LEPUSObject all inherit from lepus::RefCounted.\n// In the future, new classes added to the Signal API will also be extended\n// based on lepus::RefCounted. Since both Element and Signal are exposed to the\n// frontend, Native needs to distinguish the specific types of\n// lepus::RefCounted, hence the above changes were made. In the long term, we\n// should avoid defining these enumerations within Lepus as much as possible. We\n// will consider optimization plans in the future.\nenum class RefType : int32_t {\n  kLepusTable,\n  kLepusArray,\n  kByteArray,\n  kElement,\n  kSignal,\n  kComputation,\n  kMemo,\n  kScope,\n  kOtherType,\n  kStyleObject,\n  kCSSFragment,\n  kJSIObject,\n  kEvent,\n  kClosure,\n  kCDate,\n  kRegExp,\n};\n\n}  // namespace lepus\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_VALUE_REF_TYPE_H_\n"
  },
  {
    "path": "base/include/value/table.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_INCLUDE_VALUE_TABLE_H_\n#define BASE_INCLUDE_VALUE_TABLE_H_\n\n#include <algorithm>\n#include <functional>\n#include <string>\n#include <tuple>\n#include <type_traits>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/base_defines.h\"\n#include \"base/include/base_export.h\"\n#include \"base/include/boost/unordered.h\"\n#include \"base/include/hybrid_map.h\"\n#include \"base/include/type_traits_addon.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/base_string.h\"\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/ref_counted_class.h\"\n#include \"base/include/value/ref_type.h\"\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace lepus {\n\nclass BASE_EXPORT Dictionary : public RefCountedBase {\n public:\n  // Stores up-to 4 key-value pairs on inline memory.\n  static constexpr size_t kInlineStorageSize = 4;\n\n  // Stores up-to 16 elements in small map.\n  static constexpr size_t kSmallMapMaximumSize = 16;\n\n  using MapValueType = std::pair<const base::String, Value>;\n\n  // Use LinearFlatMap as small map type of final hybrid map.\n  // 1. Use inline memory.\n  // 2. Custom key policy which enables hash quick find instead of plain linear\n  // find and use `base::String::EqualWhenHashEqual` as comparer when two\n  // `base::String` objects are known to have the same hash values.\n  using SmallMapPolicy =\n      base::InlineFlatMapPolicy<base::InlineLinearFlatMap, kInlineStorageSize>;\n  using SmallMapType =\n      typename SmallMapPolicy::template type<base::String, Value>;\n\n  static_assert(\n      std::is_same_v<typename base::String::equal_when_hash_equal,\n                     typename SmallMapType::key_policy::equal_when_hash_equal>,\n      \"Should use `base::String::equal_when_hash_equal` for best performance.\");\n\n  // Use boost::unordered_flat_map as big map type of final hybrid map.\n  using BigMapPolicy =\n      boost::MapPolicy<boost::unordered_flat_map, std::hash, std::equal_to>;\n  using BigMapType = typename BigMapPolicy::template type<base::String, Value>;\n\n  // Custom data transfer method from small map to big map. Utilize the\n  // relocatable type-trait of key value types.\n  struct PlainBytesTransferPolicy {\n    template <typename SmallMap, typename BigMap>\n    typename SmallMap::mapped_type* operator()(SmallMap& small_map,\n                                               BigMap& big_map) {\n      static_assert(\n          base::IsTriviallyRelocatable<typename BigMap::key_type, false>::value,\n          \"Requires `base::String` to be trivially relocatable.\");\n      static_assert(base::IsTriviallyRelocatable<typename BigMap::mapped_type,\n                                                 false>::value,\n                    \"Requires `lepus::Value` to be trivially relocatable.\");\n\n      typename SmallMap::mapped_type* last_value_ptr;\n      small_map.for_each([&](const auto& key, auto& value) {\n        // Treat big_map as a map containing plain bytes of key and value so\n        // that we can do trivial copy and then ignores the destruction of\n        // original objects.\n        using PlainBytesBigMap =\n            BigMapPolicy::plain_bytes_type<typename BigMap::key_type,\n                                           typename BigMap::mapped_type>;\n        using PlainBytesMappedType = typename PlainBytesBigMap::mapped_type;\n        last_value_ptr =\n            &big_map.try_emplace_plain_bytes_key(key).first->second;\n        auto& target_value =\n            reinterpret_cast<PlainBytesMappedType&>(*last_value_ptr);\n        target_value = reinterpret_cast<const PlainBytesMappedType&>(value);\n      });\n      // Force set small_map_ as empty to skip destruction of objects.\n      std::decay_t<decltype(small_map)>::Unsafe::SetSize(small_map, 0);\n      return last_value_ptr;\n    }\n  };\n\n  // HybridMap uses DefaultIteratorPolicy as default iterator implementation\n  // which is not efficient. Given the types of small and big maps, we can\n  // implement more efficient iterator types.\n  struct IteratorPolicy {\n    template <bool Const>\n    struct Iterator {\n     private:\n      // We use iterator type of boost map(the big map) as a unified iterator\n      // because it contains two pointer members and can be used to express\n      // small map's iterator which is a single pointer.\n      using UnderlyingIterator =\n          std::conditional_t<Const, typename BigMapType::const_iterator,\n                             typename BigMapType::iterator>;\n\n      UnderlyingIterator it_;\n\n     public:\n      using SmallMapIterator =\n          std::conditional_t<Const, typename SmallMapType::const_iterator,\n                             typename SmallMapType::iterator>;\n      using BigMapIterator = UnderlyingIterator;\n\n      using Pointer =\n          std::conditional_t<Const, const MapValueType*, MapValueType*>;\n      using Reference =\n          std::conditional_t<Const, const MapValueType&, MapValueType&>;\n\n     public:\n      Iterator(SmallMapIterator it) : it_(nullptr, it) {}\n      Iterator(BigMapIterator it) : it_(it) {}\n\n      explicit operator SmallMapIterator() const { return it_.inner_p(); }\n      explicit operator BigMapIterator() const { return it_; }\n\n      Reference operator*() const { return *it_; }\n\n      Pointer operator->() const { return &(*it_); }\n\n      Iterator& operator++() {\n        if (HYBRID_MAP_LIKELY(it_.inner_pc() == nullptr)) {\n          // for small map, just step element pointer\n          it_.inner_p()++;\n        } else {\n          // for big map, call original increment\n          it_++;\n        }\n        return *this;\n      }\n\n      Iterator operator++(int) {\n        Iterator t(*this);\n        ++(*this);\n        return t;\n      }\n\n      friend bool operator==(const Iterator& x, const Iterator& y) {\n        return x.it_ == y.it_;\n      }\n\n      friend bool operator!=(const Iterator& x, const Iterator& y) {\n        return !(x == y);\n      }\n    };\n\n    using iterator = Iterator<false>;\n    using const_iterator = Iterator<true>;\n  };\n\n  using Map =\n      base::HybridMap<base::String, Value, kSmallMapMaximumSize, SmallMapPolicy,\n                      BigMapPolicy, PlainBytesTransferPolicy, IteratorPolicy>;\n\n  static_assert(Map::TransferPolicyReturnsPointer,\n                \"Requires optimized transfer policy.\");\n\n  /// Use ValueWrapper as result of Dictionary's GetValue() method to\n  /// reduce the chance that user cache pointer address of inner Value\n  /// objects of the dictionary.\n  struct ValueWrapper {\n   public:\n    explicit ValueWrapper(const Value* value) : value_(value) {}\n\n    BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ValueWrapper);\n\n    const Value& value() const { return *value_; }\n    const Value& operator*() const { return *value_; }\n    const Value* operator->() const { return value_; }\n    const Value* get() const { return value_; }\n\n    operator const Value&() const { return *value_; }\n\n    explicit operator bool() const { return value_ != nullptr; }\n    bool has_value() const { return value_ != nullptr; }\n\n    // The following methods may not cover all those of lepus::Value.\n    // Add missing ones if you need, or you should use '->'.\n    ValueType Type() const { return value_->Type(); }\n    bool IsCDate() const { return value_->IsCDate(); }\n    bool IsRegExp() const { return value_->IsRegExp(); }\n    bool IsClosure() const { return value_->IsClosure(); }\n    bool IsCallable() const { return value_->IsCallable(); }\n    bool IsReference() const { return value_->IsReference(); }\n    bool IsBool() const { return value_->IsBool(); }\n    bool IsString() const { return value_->IsString(); }\n    bool IsInt64() const { return value_->IsInt64(); }\n    bool IsNumber() const { return value_->IsNumber(); }\n    bool IsDouble() const { return value_->IsDouble(); }\n    bool IsArray() const { return value_->IsArray(); }\n    bool IsTable() const { return value_->IsTable(); }\n    bool IsObject() const { return value_->IsObject(); }\n    bool IsArrayOrJSArray() const { return value_->IsArrayOrJSArray(); }\n    bool IsCPointer() const { return value_->IsCPointer(); }\n    bool IsRefCounted() const { return value_->IsRefCounted(); }\n    bool IsInt32() const { return value_->IsInt32(); }\n    bool IsUInt32() const { return value_->IsUInt32(); }\n    bool IsUInt64() const { return value_->IsUInt64(); }\n    bool IsNil() const { return value_->IsNil(); }\n    bool IsUndefined() const { return value_->IsUndefined(); }\n    bool IsCFunction() const { return value_->IsCFunction(); }\n    bool IsJSObject() const { return value_->IsJSObject(); }\n    bool IsByteArray() const { return value_->IsByteArray(); }\n    bool IsNaN() const { return value_->IsNaN(); }\n    bool IsJSValue() const { return value_->IsJSValue(); }\n    bool IsJSCPointer() const { return value_->IsJSCPointer(); }\n    bool IsJSArray() const { return value_->IsJSArray(); }\n    bool IsJSTable() const { return value_->IsJSTable(); }\n    bool IsJSBool() const { return value_->IsJSBool(); }\n    bool LEPUSBool() const { return value_->LEPUSBool(); }\n    bool IsJSString() const { return value_->IsJSString(); }\n    bool IsJSUndefined() const { return value_->IsJSUndefined(); }\n    bool IsJSNumber() const { return value_->IsJSNumber(); }\n    bool IsJsNull() const { return value_->IsJsNull(); }\n    double LEPUSNumber() const { return value_->LEPUSNumber(); }\n    bool IsJSInteger() const { return value_->IsJSInteger(); }\n    bool IsJSFunction() const { return value_->IsJSFunction(); }\n    int GetJSLength() const { return value_->GetJSLength(); }\n    bool IsJSFalse() const { return value_->IsJSFalse(); }\n    int64_t JSInteger() const { return value_->JSInteger(); }\n    std::string ToString() const { return value_->ToString(); }\n    bool IsTrue() const { return value_->IsTrue(); }\n    bool IsFalse() const { return value_->IsFalse(); }\n    bool IsEmpty() const { return value_->IsEmpty(); }\n    bool IsEqual(const Value& value) const { return value_->IsEqual(value); }\n    bool Bool() const { return value_->Bool(); }\n    double Double() const { return value_->Double(); }\n    int32_t Int32() const { return value_->Int32(); }\n    uint32_t UInt32() const { return value_->UInt32(); }\n    int64_t Int64() const { return value_->Int64(); }\n    uint64_t UInt64() const { return value_->UInt64(); }\n    double Number() const { return value_->Number(); }\n    base::String String() const { return value_->String(); }\n    std::string_view StringView() const { return value_->StringView(); }\n    const char* CString() const { return value_->CString(); }\n    const std::string& StdString() const { return value_->StdString(); }\n    fml::WeakRefPtr<CArray> Array() const { return value_->Array(); }\n    fml::WeakRefPtr<Dictionary> Table() const { return value_->Table(); }\n    CFunction Function() const { return value_->Function(); }\n    void* CPoint() const { return value_->CPoint(); }\n    void* LEPUSCPointer() const { return value_->LEPUSCPointer(); }\n    fml::WeakRefPtr<class RefCounted> RefCounted() const {\n      return value_->RefCounted();\n    }\n    Value GetProperty(uint32_t idx) const { return value_->GetProperty(idx); }\n    Value GetProperty(const base::String& key) const {\n      return value_->GetProperty(key);\n    }\n    int GetLength() const { return value_->GetLength(); }\n    bool Contains(const base::String& key) const {\n      return value_->Contains(key);\n    }\n\n   private:\n    const Value* value_;\n  };\n\n  class Unsafe {\n   public:\n    /// These methods are usually used to copy data between dictionaries. When\n    /// it is known that the data to be added will not have duplicate keys,\n    /// calling these methods can complete the construction of the entire\n    /// dictionary more quickly. Implementation notes:\n    /// 1. Using a lot of method overloading instead of templat<K, V> and\n    /// `std::forward` can reduce binary size expansion. The cost is to use the\n    /// move semantics of `Value` instead of true in-place construction from\n    /// arguments.\n    /// 2. When using big-map, call `SetValue` directly to avoid specializing\n    /// too many big-map's `emplace` and related methods. The cost is that\n    /// `SetValue` will finally check again whether it is a big-map or a\n    /// small-map.\n    static Value& SetValueUniqueKey(Dictionary& target,\n                                    const base::String& key) {\n      // Default construct value for key. This is to optimize table decoding.\n      auto& map = target.map_;\n      if (HYBRID_MAP_LIKELY(map.using_small_map())) {\n        return map.small_map().emplace_unique(key)->second;\n      } else {\n        return const_cast<Value&>(*target.SetValue(key));\n      }\n    }\n\n    static Value& SetValueUniqueKey(Dictionary& target, base::String&& key) {\n      // Default construct value for key. This is to optimize table decoding.\n      auto& map = target.map_;\n      if (HYBRID_MAP_LIKELY(map.using_small_map())) {\n        return map.small_map().emplace_unique(std::move(key))->second;\n      } else {\n        return const_cast<Value&>(*target.SetValue(std::move(key)));\n      }\n    }\n\n    static void SetValueUniqueKey(Dictionary& target, const base::String& key,\n                                  const Value& value) {\n      auto& map = target.map_;\n      if (HYBRID_MAP_LIKELY(map.using_small_map())) {\n        map.small_map().emplace_unique(key, value);\n      } else {\n        target.SetValue(key, value);\n      }\n    }\n\n    static void SetValueUniqueKey(Dictionary& target, const base::String& key,\n                                  Value&& value) {\n      auto& map = target.map_;\n      if (HYBRID_MAP_LIKELY(map.using_small_map())) {\n        map.small_map().emplace_unique(key, std::move(value));\n      } else {\n        target.SetValue(key, std::move(value));\n      }\n    }\n\n    static void SetValueUniqueKey(Dictionary& target, base::String&& key,\n                                  const Value& value) {\n      auto& map = target.map_;\n      if (HYBRID_MAP_LIKELY(map.using_small_map())) {\n        map.small_map().emplace_unique(std::move(key), value);\n      } else {\n        target.SetValue(std::move(key), value);\n      }\n    }\n\n    static void SetValueUniqueKey(Dictionary& target, base::String&& key,\n                                  Value&& value) {\n      auto& map = target.map_;\n      if (HYBRID_MAP_LIKELY(map.using_small_map())) {\n        map.small_map().emplace_unique(std::move(key), std::move(value));\n      } else {\n        target.SetValue(std::move(key), std::move(value));\n      }\n    }\n\n    static BASE_INLINE Dictionary* RawCreate() { return new Dictionary(); }\n  };\n\n  friend class Unsafe;\n\n public:\n  static fml::RefPtr<Dictionary> Create() {\n    return fml::AdoptRef<Dictionary>(new Dictionary());\n  }\n  static fml::RefPtr<Dictionary> Create(\n      std::initializer_list<MapValueType>&& data) {\n    return fml::AdoptRef<Dictionary>(new Dictionary(std::move(data)));\n  }\n  ~Dictionary() = default;\n\n  RefType GetRefType() const override { return RefType::kLepusTable; }\n\n  ///  @note Why this method is implemented like this.\n  ///\n  ///  The most primitive and intuitive code to implement this method is as\n  ///  follows.\n  ///\n  ///      map_[key] = Value(std::forward<Args>(args)...);\n  ///\n  ///  or\n  ///\n  ///      if (auto result = map_.try_emplace(key,\n  ///         std::forward<Args>(args)...); !result.second) {\n  ///         result.first->second = Value(std::forward<Args>(args)...);\n  ///      }\n  ///\n  ///  But unfortunately, the first way is not optimal in performance which may\n  ///  bring default construction and move assignment of Value.\n  ///\n  ///  The second way is better in performance but will cause severely binary\n  ///  expansion for template specialization of try_emplace() method.\n  ///\n  ///  @return ValueWrapper to Value if success or nullptr on failure.\n  template <class... Args>\n  ValueWrapper SetValue(const base::String& key, Args&&... args) {\n    if (HYBRID_MAP_UNLIKELY(IsConstLog())) {\n      return ValueWrapper(nullptr);\n    }\n\n    // Possible that input args is a single 'Value' instance happens to be\n    // exactly the same instance of exsiting one.\n    // Like `table->SetValue(key, *table->GetValue(key))`.\n    if constexpr (sizeof...(Args) == 1) {\n      using single_arg_t = std::remove_cv_t<std::remove_reference_t<\n          std::tuple_element_t<0, std::tuple<Args...>>>>;\n      if constexpr (std::is_same_v<single_arg_t, Value>) {\n        auto [target_ptr, inserted] =\n            map_.try_emplace(key, Value::kUnsafeCreateAsUninitialized);\n        if (HYBRID_MAP_UNLIKELY(!inserted)) {\n          if (HYBRID_MAP_UNLIKELY(target_ptr ==\n                                  &std::get<0>(std::tie(args...)))) {\n            return ValueWrapper(target_ptr);  // Do not override.\n          }\n          target_ptr->~Value();\n        }\n        // Placement new Value() on target_ptr with variadic args.\n        return ValueWrapper(new (target_ptr)\n                                Value(std::forward<Args>(args)...));\n      }\n    }\n\n    // Other cases of args, calling the public method without inlining can\n    // optimize binary size, but with a slight performance loss.\n    return ValueWrapper(new (SetValuePrepare(key))\n                            Value(std::forward<Args>(args)...));\n  }\n\n  /// Return a default nil value if key not found.\n  ValueWrapper GetValue(const base::String& key) const;\n\n  /// Return a default undefined value if key not found.\n  ValueWrapper GetValueOrUndefined(const base::String& key) const;\n\n  /// Return a nullable ValueWrapper and you should check before use.\n  ValueWrapper GetValueOrNull(const base::String& key) const;\n\n  /// Return false if the dictionary is const, or always true.\n  bool Erase(const base::String& key);\n\n  /// Return -1 if the dictionary is const, or returns number of elements\n  /// erased(0 or 1).\n  int32_t EraseKey(const base::String& key);\n\n  bool Contains(const base::String& key) const {\n    return map_.find(key) != nullptr;\n  }\n\n  auto find(const base::String& key) const { return map_.find_iterator(key); }\n\n  auto find(const base::String& key) { return map_.find_iterator(key); }\n\n  size_t size() const { return map_.size(); }\n  bool empty() const { return size() == 0; }\n\n  void reserve(size_t count) { map_.reserve(count); }\n\n  template <typename Callback>\n  void for_each(Callback&& callback) {\n    map_.for_each(std::forward<Callback>(callback));\n  }\n\n  template <typename Callback>\n  void for_each(Callback&& callback) const {\n    map_.for_each(std::forward<Callback>(callback));\n  }\n\n  /// @note Do not cache pointer to value using `&(it->second)`\n  /// to other variables. The underlying map does not guarantee pointer\n  /// stability.\n  auto cbegin() const { return map_.cbegin(); }\n  auto cend() const { return map_.cend(); }\n  auto begin() { return map_.begin(); }\n  auto end() { return map_.end(); }\n  auto begin() const { return map_.begin(); }\n  auto end() const { return map_.end(); }\n\n  friend bool operator==(const Dictionary& left, const Dictionary& right);\n\n  friend bool operator!=(const Dictionary& left, const Dictionary& right) {\n    return !(left == right);\n  }\n\n  bool IsConst() const override { return __padding_chars__[0]; }\n\n  bool MarkConst() {\n    if (IsConst()) return true;\n    // For best iteration performance\n    if (map_.using_small_map()) {\n      for (const auto& [key, value] : map_.small_map()) {\n        if (!value.MarkConst()) return false;\n      }\n    } else {\n      for (const auto& [key, value] : map_.big_map()) {\n        if (!value.MarkConst()) return false;\n      }\n    }\n    __padding_chars__[0] = 1;\n    return true;\n  }\n\n  bool using_small_map() const {\n    // For unittest\n    return map_.using_small_map();\n  }\n\n protected:\n  Dictionary() {}\n  Dictionary(std::initializer_list<MapValueType>&& data)\n      : map_(std::move(data)) {}\n\n  friend class Value;\n\n  void Reset() {\n    map_.clear();\n    __padding__ = 0;\n  }\n\n private:\n  Map map_;\n\n  BASE_INLINE bool IsConstLog() const {\n    if (IsConst()) {\n#ifdef DEBUG\n      // TODO(yuyang), Currently LOGD still produce assembly in release mode.\n      LOGD(\"Lepus table is const\");\n#endif\n      return true;\n    }\n    return false;\n  }\n\n  HYBRID_MAP_NEVER_INLINE Value* SetValuePrepare(const base::String& key) {\n    // If value does not exist at key, construct but do not initialize any field\n    // for best performance. Later placement new on the memory.\n    auto [target_ptr, inserted] =\n        map_.try_emplace(key, Value::kUnsafeCreateAsUninitialized);\n    if (HYBRID_MAP_UNLIKELY(!inserted)) {\n      // Insertion failed, destruct the existing Value.\n      target_ptr->~Value();\n    }\n    return target_ptr;\n  }\n};\n\nusing DictionaryPtr = fml::RefPtr<Dictionary>;\n\n}  // namespace lepus\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_VALUE_TABLE_H_\n"
  },
  {
    "path": "base/include/vector.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VECTOR_H_\n#define BASE_INCLUDE_VECTOR_H_\n\n#include <algorithm>\n#include <cassert>\n#include <cstdlib>\n#include <cstring>\n#include <functional>\n#include <limits>\n#include <stack>\n#include <tuple>\n#include <type_traits>\n#include <utility>\n\n#include \"base/include/type_traits_addon.h\"\n\n#ifndef BASE_VECTOR_DISABLE_SSE2\n#if defined(BASE_VECTOR_ENABLE_SSE2) || defined(__SSE2__) || \\\n    defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)\n#define BASE_VECTOR_SSE2\n#endif\n#endif\n\n#ifndef BASE_VECTOR_DISABLE_NEON\n#if defined(BASE_VECTOR_ENABLE_NEON) || defined(__ARM_NEON)\n#define BASE_VECTOR_NEON\n#endif\n#endif\n\n#ifdef BASE_VECTOR_SSE2\n#include <immintrin.h>\n#elif defined(BASE_VECTOR_NEON)\n#include <arm_neon.h>\n#if defined(__aarch64__)\n#define BASE_VECTOR_NEON_ARMV8\n#endif\n#endif\n\n#ifdef _MSC_VER\n#include <intrin.h>  // for _BitScanForward\n#endif\n\n#ifdef DEBUG\n#define BASE_VECTOR_DCHECK(...) assert(__VA_ARGS__)\n#else\n#define BASE_VECTOR_DCHECK(...)\n#endif\n\n#ifdef _MSC_VER\n#define BASE_VECTOR_UNLIKELY(x) x\n#define BASE_VECTOR_LIKELY(x) x\n#else\n#define BASE_VECTOR_UNLIKELY(x) __builtin_expect(!!(x), 0)\n#define BASE_VECTOR_LIKELY(x) __builtin_expect(!!(x), 1)\n#endif\n\n/**\n * Inline annotations to precisely control functions for benefits of binary\n * size.\n */\n#if _MSC_VER\n#define BASE_VECTOR_INLINE inline __forceinline\n#define BASE_VECTOR_NEVER_INLINE __declspec(noinline)\n#else\n#define BASE_VECTOR_INLINE inline __attribute__((always_inline))\n#define BASE_VECTOR_NEVER_INLINE __attribute__((noinline))\n#endif\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunused-template\"\n\nnamespace lynx {\nnamespace base {\n\nnamespace {  // NOLINT\n\ntemplate <bool EnableCounter>\nBASE_VECTOR_INLINE void* HeapAlloc(const size_t size) {\n  if constexpr (EnableCounter) {\n    // Call counter\n  }\n  return std::malloc(size);\n}\n\nBASE_VECTOR_INLINE void HeapFree(void* ptr) { std::free(ptr); }\n\ntemplate <typename T>\nconstexpr T AlignUp(T a, unsigned int align) {\n  const T mask = T(align - 1);\n  return T((a + mask) & ~mask);\n}\n\nBASE_VECTOR_INLINE int unchecked_count_right_zero(int x) {\n#ifdef _MSC_VER\n  unsigned long r;\n  _BitScanForward(&r, (unsigned long)x);\n  return (int)r;\n#else\n  return __builtin_ctz((unsigned int)x);\n#endif\n}\n\ntemplate <class T>\nvoid _nontrivial_destruct_reverse([[maybe_unused]] T* begin,\n                                  [[maybe_unused]] size_t count) {\n  if constexpr (!std::is_trivially_destructible_v<T>) {\n    // To be consistent with std::vector. Elements are destructed from back.\n    auto end = begin + count;\n    while (end != begin) {\n      --end;\n      end->~T();\n    }\n    //  for (; begin != end; ++begin) {\n    //    begin->~T();\n    //  }\n  }\n}\n\ntemplate <class T>\nvoid _nontrivial_destruct_one(T* begin) {\n  if constexpr (!std::is_trivially_destructible_v<T>) {\n    begin->~T();\n  }\n}\n\ntemplate <class T>\nBASE_VECTOR_NEVER_INLINE T* _nontrivial_move(T* dest, T* source,\n                                             ptrdiff_t count) {\n  auto end = source + count;\n  auto step = count >= 0 ? 1 : -1;\n  for (; source != end; source += step, dest += step) {\n    *dest = std::move(*source);\n  }\n  return dest;\n}\n\ntemplate <class T>\nBASE_VECTOR_INLINE T* _nontrivial_move_forward(T* dest, T* source,\n                                               size_t count) {\n  return _nontrivial_move<T>(dest, source, count);\n}\n\ntemplate <class T>\nBASE_VECTOR_INLINE T* _nontrivial_move_backward(T* dest, T* source,\n                                                size_t count) {\n  return _nontrivial_move<T>(dest, source, -count);\n}\n\ntemplate <class T>\nBASE_VECTOR_NEVER_INLINE void _nontrivial_construct_move(T* dest, T* source,\n                                                         size_t count) {\n  auto end = source + count;\n  for (; source != end; ++source, ++dest) {\n    ::new (static_cast<void*>(dest)) T(std::move(*source));\n  }\n}\n\ntemplate <class T>\nBASE_VECTOR_NEVER_INLINE void _nontrivial_construct_copy(T* dest, T* source,\n                                                         size_t count) {\n  auto end = source + count;\n  for (; source != end; ++source, ++dest) {\n    ::new (static_cast<void*>(dest)) T(*source);\n  }\n}\n}  // namespace\n\n/**\n * Prototype of Vector provides view of its three members.\n * This base class does not provide any method to change data.\n *\n * `ExtraBytesPerElement`: Leading extra bytes allocated along with array\n * buffer. This extra space is currently used by map/set to store hash values\n * and is transparent to Vector or InlineVector's insert, push_back or other\n * data modifier methods. But the data is copied along with array elements by\n * Vector's copy and move constructors and copy and move assign operators.\n *\n * `CountRealloc`: A flag for local performance debugging. When enabled,\n * array buffer reallocation will be counted. Note that the first allocation\n * will NOT be counted.\n */\ntemplate <class T, size_t ExtraBytesPerElement, bool CountRealloc>\nstruct VectorPrototype {\n  static constexpr auto is_trivial =\n      IsTrivial<T, is_instance<T, std::pair>{}>::value;\n  static constexpr auto is_trivially_destructible =\n      std::is_trivially_destructible_v<T>;\n  static constexpr auto is_trivially_relocatable =\n      IsTriviallyRelocatable<T, is_instance<T, std::pair>{}>::value;\n  static constexpr auto is_trivially_destructible_after_move =\n      is_trivially_relocatable ||\n      IsTriviallyDestructibleAfterMove<T, is_instance<T, std::pair>{}>::value;\n\n  using iterator = T*;\n  using const_iterator = const T*;\n\n  VectorPrototype() {}\n\n  [[maybe_unused]] size_t size() const { return count_; }\n\n  bool empty() const { return count_ == 0; }\n\n  size_t capacity() const { return std::abs(capacity_); }\n\n  T* data() { return reinterpret_cast<T*>(_begin_iter()); }\n\n  T* data() const { return reinterpret_cast<T*>(_begin_iter()); }\n\n  bool is_static_buffer() const { return capacity_ < 0; }\n\n  BASE_VECTOR_INLINE void* get_memory_allocate() const {\n    return _buffer_alloc_address(_begin_iter(), capacity());\n  }\n\n protected:\n  template <class, size_t, bool>\n  friend struct VectorPrototype;\n\n  template <size_t, bool>\n  friend struct VectorTemplateless;\n\n  template <class, class, class, size_t, bool>\n  friend struct KeyValueArray;\n\n  BASE_VECTOR_INLINE static void* _buffer_alloc_address(const void* buffer,\n                                                        size_t capacity) {\n    return static_cast<uint8_t*>(const_cast<void*>(buffer)) -\n           AlignUp(capacity * ExtraBytesPerElement, 8);\n  }\n\n  BASE_VECTOR_NEVER_INLINE static void* _reallocate_buffer(void* array,\n                                                           size_t element_size,\n                                                           size_t count) {\n    auto* proto_array = reinterpret_cast<\n        VectorPrototype<uint8_t, ExtraBytesPerElement, CountRealloc>*>(array);\n    auto old_capacity = proto_array->capacity();\n    // old*2 for consistent with std::vector of libc++.\n    // But we do not use max(count, old_cap * 2).\n    auto new_capacity =\n        count > 0\n            ? count\n            : (old_capacity == 0\n                   ? (element_size >= 48\n                          ? 1\n                          : (element_size >= 16 ? 2 : (32 / element_size)))\n                   : 2 * old_capacity);\n    if (BASE_VECTOR_UNLIKELY(new_capacity <= old_capacity)) {\n      return nullptr;\n    }\n    size_t extra_bytes_size = 0;\n    if constexpr (ExtraBytesPerElement > 0) {\n      extra_bytes_size = AlignUp(new_capacity * ExtraBytesPerElement, 8);\n    }\n    const auto buffer_size = extra_bytes_size + new_capacity * element_size;\n    uint8_t* buffer;\n    if constexpr (CountRealloc) {\n      if (old_capacity > 0) {\n        // This is a RE-allocation.\n        buffer = static_cast<uint8_t*>(HeapAlloc<true>(buffer_size));\n      } else {\n        // First time allocation, skip counter.\n        buffer = static_cast<uint8_t*>(HeapAlloc<false>(buffer_size));\n      }\n    } else {\n      buffer = static_cast<uint8_t*>(HeapAlloc<false>(buffer_size));\n    }\n    if (BASE_VECTOR_UNLIKELY(buffer == nullptr)) {\n      return nullptr;\n    }\n    buffer += extra_bytes_size;  // slide extra bytes\n    proto_array->_set_memory(buffer);\n    proto_array->_set_capacity(new_capacity, false);\n    return buffer;\n  }\n\n  BASE_VECTOR_INLINE void _copy_extra_bytes_from(const VectorPrototype& other) {\n    if constexpr (ExtraBytesPerElement > 0) {\n      if (other.size() > 0) {\n        std::memcpy(get_memory_allocate(), other.get_memory_allocate(),\n                    ExtraBytesPerElement * other.size());\n      }\n    }\n  }\n\n  BASE_VECTOR_NEVER_INLINE void _reallocate_nontrivial(size_t count = 0) {\n    static_assert(\n        !is_trivial,\n        \"Only for non-trivial element types you can use this method.\");\n    auto prev_begin = _begin_iter();\n    [[maybe_unused]] void* prev_alloc_buffer;\n    if constexpr (ExtraBytesPerElement > 0) {\n      prev_alloc_buffer = get_memory_allocate();\n    }\n    bool need_free = !is_static_buffer();\n    auto buffer =\n        VectorPrototype<uint8_t, ExtraBytesPerElement,\n                        CountRealloc>::_reallocate_buffer(this, sizeof(T),\n                                                          count);\n    if (buffer && prev_begin) {\n      /* Leading bytes should be copied separately. */\n      if constexpr (ExtraBytesPerElement > 0) {\n        if (size() > 0) {\n          std::memcpy(get_memory_allocate(), prev_alloc_buffer,\n                      ExtraBytesPerElement * size());\n        }\n      }\n\n      if constexpr (is_trivially_relocatable) {\n        /* Fastest, allowing memcpy to copy all and skip destructors. */\n        std::memcpy(buffer, prev_begin, sizeof(T) * size());\n      } else {\n        /* Move T objects one by one from old buffer to new buffer. */\n        _nontrivial_construct_move((iterator)buffer, prev_begin, size());\n\n        if constexpr (is_trivially_destructible_after_move) {\n          /* Faster, skip destructors. */\n        } else {\n          /* Slow path, destruct objects on old buffer one by one. */\n          _nontrivial_destruct_reverse(prev_begin, size());\n        }\n      }\n      if (need_free) {\n        if constexpr (ExtraBytesPerElement > 0) {\n          HeapFree(prev_alloc_buffer);\n        } else {\n          HeapFree(prev_begin);\n        }\n      }\n    }\n  }\n\n  void _free() {\n    if (memory_ != nullptr) {\n      if constexpr (!is_trivial) {\n        _nontrivial_destruct_reverse(_begin_iter(), size());\n      }\n      if (!is_static_buffer()) {\n        HeapFree(get_memory_allocate());\n      }\n    }\n  }\n\n  void _reset() {\n    memory_ = nullptr;\n    count_ = 0;\n    capacity_ = 0;\n  }\n\n  BASE_VECTOR_INLINE void* _memory() const { return memory_; }\n\n  BASE_VECTOR_INLINE void _set_memory(void* value) {\n    memory_ = reinterpret_cast<T*>(value);\n  }\n\n  BASE_VECTOR_INLINE iterator _begin_iter() const {\n    return reinterpret_cast<iterator>(_memory());\n  }\n\n  BASE_VECTOR_INLINE iterator _end_iter() const {\n    return _begin_iter() + count_;\n  }\n\n  BASE_VECTOR_INLINE iterator _finish_iter() const {\n    return _begin_iter() + capacity();\n  }\n\n  BASE_VECTOR_INLINE void _set_count(size_t value) {\n    count_ = static_cast<uint32_t>(value);\n  }\n\n  BASE_VECTOR_INLINE void _inc_count(size_t count = 1) {\n    count_ += static_cast<uint32_t>(count);\n  }\n\n  BASE_VECTOR_INLINE void _dec_count(size_t count = 1) {\n    count_ -= static_cast<uint32_t>(count);\n  }\n\n  BASE_VECTOR_INLINE void _set_capacity(size_t value, bool is_static) {\n    capacity_ =\n        is_static ? -static_cast<int32_t>(value) : static_cast<int32_t>(value);\n  }\n\n  BASE_VECTOR_INLINE void _transfer_from(VectorPrototype& other) {\n    // Memory buffer of other array might be an inplace buffer which is\n    // unsafe to be directly transferred ownership to self.\n    BASE_VECTOR_DCHECK(!other.is_static_buffer());\n\n    memory_ = other.memory_;\n    count_ = other.count_;\n    capacity_ = other.capacity_;\n  }\n\n  void _swap(VectorPrototype& other) {\n    // Memory buffer of other array might be an inplace buffer which is\n    // unsafe to be directly transferred ownership to self.\n    BASE_VECTOR_DCHECK(!is_static_buffer() && !other.is_static_buffer());\n\n    std::swap(memory_, other.memory_);\n    std::swap(count_, other.count_);\n    std::swap(capacity_, other.capacity_);\n  }\n\n private:\n  T* memory_{nullptr};\n\n  uint32_t count_{0};\n\n  // Negative capacity value means the memory_ buffer should not be freed.\n  int32_t capacity_{0};\n};\n\n/**\n APIs to manipulate Vector without template argument T.\n Note that templateless manipulators should only be used for trivial T types.\n*/\ntemplate <size_t ExtraBytesPerElement, bool CountRealloc>\nstruct VectorTemplateless {\n  BASE_VECTOR_NEVER_INLINE static void ReallocateTrivial(void* array,\n                                                         size_t element_size,\n                                                         size_t count) {\n    auto* proto_array = reinterpret_cast<\n        VectorPrototype<uint8_t, ExtraBytesPerElement, CountRealloc>*>(array);\n    const bool free_prev = !proto_array->is_static_buffer();\n    auto prev_begin = proto_array->_begin_iter();\n    [[maybe_unused]] void* prev_alloc_buffer;\n    if constexpr (ExtraBytesPerElement > 0) {\n      prev_alloc_buffer = proto_array->get_memory_allocate();\n    }\n    auto buffer =\n        VectorPrototype<uint8_t, ExtraBytesPerElement,\n                        CountRealloc>::_reallocate_buffer(array, element_size,\n                                                          count);\n    if (buffer && prev_begin) {\n      /* Leading bytes should be copied separately. */\n      if constexpr (ExtraBytesPerElement > 0) {\n        if (proto_array->size() > 0) {\n          std::memcpy(proto_array->get_memory_allocate(), prev_alloc_buffer,\n                      ExtraBytesPerElement * proto_array->size());\n        }\n      }\n\n      /* Copy array data. */\n      std::memcpy(buffer, prev_begin, proto_array->size() * element_size);\n      if (free_prev) {\n        if constexpr (ExtraBytesPerElement > 0) {\n          HeapFree(prev_alloc_buffer);\n        } else {\n          HeapFree(prev_begin);\n        }\n      }\n    }\n  }\n\n  /**\n   Use this never-inline function to replace ReallocateTrivial full arguments\n   version to avoid one default-to-0 argument at caller site. This function\n   passes count==0 to grow the capacity.\n   */\n  BASE_VECTOR_NEVER_INLINE static void ReallocateTrivial(void* array,\n                                                         size_t element_size) {\n    ReallocateTrivial(array, element_size, 0);\n  }\n\n  /**\n   All trivial arrays share the same Resize implementation for binary size\n   benefits. Note that filling operations are done by std::memcpy which may be\n   slower than std::vector.\n   */\n  BASE_VECTOR_NEVER_INLINE static bool Resize(void* array, size_t element_size,\n                                              size_t count,\n                                              const void* fill_value) {\n    auto* proto_array = reinterpret_cast<\n        VectorPrototype<uint8_t, ExtraBytesPerElement, CountRealloc>*>(array);\n    bool reallocated = false;\n    if (BASE_VECTOR_LIKELY(count > proto_array->size())) {\n      if (BASE_VECTOR_LIKELY(count > proto_array->capacity())) {\n        ReallocateTrivial(array, element_size, count);\n        reallocated = true;\n      }\n      if (fill_value) {\n        auto begin = proto_array->_begin_iter();\n        for (size_t i = proto_array->size(); i < count; i++) {\n          std::memcpy(begin + i * element_size, fill_value, element_size);\n        }\n      }\n    }\n    proto_array->_set_count(count);\n    return reallocated;\n  }\n\n  /**\n   This function prepares space for the data to be inserted, but does not\n   perform the final insertion.\n   */\n  BASE_VECTOR_NEVER_INLINE static void* PrepareInsert(void* array,\n                                                      size_t element_size,\n                                                      const void* dest) {\n    auto* proto_array = reinterpret_cast<\n        VectorPrototype<uint8_t, ExtraBytesPerElement, CountRealloc>*>(array);\n    auto diff = static_cast<const uint8_t*>(dest) - proto_array->_begin_iter();\n    if (BASE_VECTOR_UNLIKELY(proto_array->size() == proto_array->capacity())) {\n      ReallocateTrivial(array, element_size);\n    }\n    auto pos = proto_array->_begin_iter() + diff;\n    auto new_last =\n        proto_array->_begin_iter() + proto_array->size() * element_size;\n    if (BASE_VECTOR_UNLIKELY(pos > new_last)) {\n      BASE_VECTOR_DCHECK(false);\n    } else {\n      if (BASE_VECTOR_LIKELY(pos < new_last)) {\n        std::memmove(pos + element_size, pos, new_last - pos);\n      }\n      // The actual assignment is performed after PrepareInsert() so that memcpy\n      // could be optimized by compiler. std::memcpy(pos, source, element_size);\n      proto_array->_inc_count();\n    }\n    return pos;\n  }\n\n  /**\n   Erase element at index. The index is measured by element_size.\n   Returns false if index or index + count is out of range.\n   */\n  BASE_VECTOR_NEVER_INLINE static bool Erase(void* array, size_t element_size,\n                                             size_t index, size_t count = 1) {\n    auto* proto_array = reinterpret_cast<\n        VectorPrototype<uint8_t, ExtraBytesPerElement, CountRealloc>*>(array);\n    if (BASE_VECTOR_UNLIKELY(index >= proto_array->size())) {\n      return false;\n    }\n    if (BASE_VECTOR_UNLIKELY(index + count > proto_array->size())) {\n      return false;\n    }\n    auto dst = proto_array->_begin_iter() + element_size * index;\n    std::memmove(dst, dst + element_size * count,\n                 element_size * (proto_array->size() - index - count));\n    proto_array->_set_count(proto_array->size() - count);\n    return true;\n  }\n\n  /**\n   Push N[count] elements from [source] to Vector of address [array].\n   */\n  BASE_VECTOR_NEVER_INLINE static void PushBackBatch(void* array,\n                                                     size_t element_size,\n                                                     const void* source,\n                                                     size_t count) {\n    if (BASE_VECTOR_UNLIKELY(source == nullptr)) {\n      return;\n    }\n    auto* proto_array = reinterpret_cast<\n        VectorPrototype<uint8_t, ExtraBytesPerElement, CountRealloc>*>(array);\n    if (proto_array->size() + count > proto_array->capacity()) {\n      ReallocateTrivial(array, element_size, proto_array->size() + count);\n    }\n    auto end = proto_array->_begin_iter() + element_size * proto_array->size();\n    std::memcpy(end, source, count * element_size);\n    proto_array->_inc_count(count);\n  }\n\n  /**\n   Fill elements from index position of original array with contents of\n   [source].\n   */\n  BASE_VECTOR_NEVER_INLINE static void Fill(void* array, size_t element_size,\n                                            const void* source,\n                                            size_t byte_size, size_t position) {\n    const auto source_count = byte_size / element_size;\n    if (BASE_VECTOR_UNLIKELY(source_count == 0)) {\n      return;\n    }\n    auto* proto_array = reinterpret_cast<\n        VectorPrototype<uint8_t, ExtraBytesPerElement, CountRealloc>*>(array);\n    auto count = source_count + position;\n    ReallocateTrivial(array, element_size, count);\n    auto dest = proto_array->_begin_iter() + position * element_size;\n    if (source != nullptr) {\n      std::memcpy(dest, source, source_count * element_size);\n    } else {\n      std::memset(dest, 0, source_count * element_size);\n    }\n    proto_array->_set_count(count);\n  }\n};\n\n/**\n * Replacement of Vector for binary size benefits. This linear container\n * provides basic methods of Vector with the same method signatures. For\n * POD types, it also provides some non-stl-standard methods for convenience.\n */\ntemplate <class T, size_t ExtraBytesPerElement = 0, bool CountRealloc = false>\nstruct Vector\n    : protected VectorTemplateless<ExtraBytesPerElement, CountRealloc>,\n      public VectorPrototype<T, ExtraBytesPerElement, CountRealloc> {\n  using\n      typename VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::iterator;\n  using typename VectorPrototype<T, ExtraBytesPerElement,\n                                 CountRealloc>::const_iterator;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::is_trivial;\n  using VectorPrototype<T, ExtraBytesPerElement,\n                        CountRealloc>::is_trivially_destructible;\n  using VectorPrototype<T, ExtraBytesPerElement,\n                        CountRealloc>::is_trivially_destructible_after_move;\n  using VectorPrototype<T, ExtraBytesPerElement,\n                        CountRealloc>::is_trivially_relocatable;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::size;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::capacity;\n  using VectorPrototype<T, ExtraBytesPerElement,\n                        CountRealloc>::is_static_buffer;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_begin_iter;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_end_iter;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_finish_iter;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_free;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_transfer_from;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_swap;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_reset;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_set_count;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_inc_count;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_dec_count;\n  using VectorPrototype<T, ExtraBytesPerElement,\n                        CountRealloc>::_reallocate_nontrivial;\n  using VectorPrototype<T, ExtraBytesPerElement,\n                        CountRealloc>::_copy_extra_bytes_from;\n\n  using VectorTemplateless<ExtraBytesPerElement,\n                           CountRealloc>::ReallocateTrivial;\n  using VectorTemplateless<ExtraBytesPerElement, CountRealloc>::Resize;\n  using VectorTemplateless<ExtraBytesPerElement, CountRealloc>::PrepareInsert;\n  using VectorTemplateless<ExtraBytesPerElement, CountRealloc>::PushBackBatch;\n  using VectorTemplateless<ExtraBytesPerElement, CountRealloc>::Fill;\n\n  using size_type = size_t;\n  using difference_type = ptrdiff_t;\n  using value_type = T;\n  using reference = T&;\n  using const_reference = const T&;\n  using pointer = T*;\n  using const_pointer = const T*;\n  using reverse_iterator = std::reverse_iterator<iterator>;\n  using const_reverse_iterator = std::reverse_iterator<const_iterator>;\n  using unique_id_type = uintptr_t;\n\n  /**\n   * @brief We allows 'array = nullptr' to reset and clear memory of array.\n   */\n  Vector(std::nullptr_t) {}  // NOLINT\n  Vector() {}\n  explicit Vector(size_t count) { _construct_fill_default(count); }\n\n  Vector(size_t count, const T& value) { _construct_fill(count, value); }\n\n  /**\n   * @brief Create array by size and data, if parameter 'data' is valid, copy it\n   * to array.\n   * @param count element count of array\n   * @param data source data. If non-null, will copy from data.\n   */\n  template <class U = T, typename = typename std::enable_if<\n                             std::is_same_v<U, T> && is_trivial>::type>\n  Vector(size_t count, const void* data) {\n    fill(data, count * sizeof(T));\n  }\n\n  Vector(std::initializer_list<T> list) {\n    _from(list.begin(), list.size(), _nontrivial_construct_move);\n  }\n\n  /**\n   * @brief We only support construct array from array iterators because we can\n   * calculate distance from begin to end. std::vector supports being\n   * constructed from other iterators such as std::set's, but it is not\n   * efficient because we cannot precalculate capacity of the array.\n   */\n  Vector(const_iterator begin, const_iterator end) {\n    _from(begin, end - begin, _nontrivial_construct_copy);\n  }\n\n  Vector(iterator begin, iterator end) {\n    _from(begin, end - begin, _nontrivial_construct_copy);\n  }\n\n  Vector(const Vector& other) {\n    _from(other.data(), other.size(), _nontrivial_construct_copy);\n    _copy_extra_bytes_from(other);\n  }\n\n  Vector& operator=(const Vector& other) {\n    if (BASE_VECTOR_LIKELY(this != &other)) {\n      clear();\n      _from(other.data(), other.size(), _nontrivial_construct_copy);\n      _copy_extra_bytes_from(other);\n    }\n    return *this;\n  }\n\n  Vector& operator=(std::initializer_list<T> list) {\n    clear();\n    _from(list.begin(), list.size(), _nontrivial_construct_move);\n    return *this;\n  }\n\n  Vector(Vector&& other) noexcept {\n    if (other.is_static_buffer()) {\n      _from(other.data(), other.size(), _nontrivial_construct_move);\n      _copy_extra_bytes_from(other);\n      other.clear();\n    } else {\n      _transfer_from(other);\n      other._reset();\n    }\n  }\n\n  Vector& operator=(Vector&& other) {\n    if (BASE_VECTOR_LIKELY(this != &other)) {\n      if (other.is_static_buffer()) {\n        clear();\n        _from(other.data(), other.size(), _nontrivial_construct_move);\n        _copy_extra_bytes_from(other);\n        other.clear();\n      } else {\n        _free();\n        _transfer_from(other);\n        other._reset();\n      }\n    }\n    return *this;\n  }\n\n  ~Vector() { _free(); }\n\n  template <class U>\n  reference push_back(U&& v) {\n    return emplace_back(std::forward<U>(v));\n  }\n\n  template <class... Args>\n  reference emplace_back(Args&&... args) {\n    _grow_if_need();\n    auto end = _end_iter();\n    ::new (static_cast<void*>(end)) T(std::forward<Args>(args)...);\n    _inc_count();\n    return *end;\n  }\n\n  void pop_back() {\n    if (size() == 0) {\n      return;\n    }\n    if constexpr (!is_trivial) {\n      _nontrivial_destruct_one(_end_iter() - 1);\n    }\n    _dec_count();\n  }\n\n  const_reference back() const { return *(_end_iter() - 1); }\n\n  reference back() { return *(_end_iter() - 1); }\n\n  const_reference front() const { return *_begin_iter(); }\n\n  reference front() { return *_begin_iter(); }\n\n  const_reference operator[](size_t n) const {\n    BASE_VECTOR_DCHECK(n < size());\n    return *(_begin_iter() + n);\n  }\n\n  reference operator[](size_t n) {\n    BASE_VECTOR_DCHECK(n < size());\n    return *(_begin_iter() + n);\n  }\n\n  const_reference at(size_t n) const {\n    BASE_VECTOR_DCHECK(n < size());\n    return *(_begin_iter() + n);\n  }  // Always nothrow\n\n  reference at(size_t n) {\n    BASE_VECTOR_DCHECK(n < size());\n    return *(_begin_iter() + n);\n  }  // Always nothrow\n\n  /**\n   * @brief Returns pointer to the underlying array serving as element storage.\n   * @return If size() is ​0​, data() may or may not return a null pointer.\n   */\n  template <typename U = T>\n  U* data() {\n    return reinterpret_cast<U*>(_begin_iter());\n  }\n\n  /**\n   * @brief Returns pointer to the underlying array serving as element storage.\n   * @return If size() is ​0​, data() may or may not return a null pointer.\n   */\n  template <typename U = T>\n  const U* data() const {\n    return reinterpret_cast<const U*>(_begin_iter());\n  }\n\n  iterator begin() { return _begin_iter(); }\n\n  const_iterator begin() const { return _begin_iter(); }\n\n  const_iterator cbegin() const { return _begin_iter(); }\n\n  iterator end() { return _end_iter(); }\n\n  const_iterator end() const { return _end_iter(); }\n\n  const_iterator cend() const { return _end_iter(); }\n\n  reverse_iterator rbegin() { return reverse_iterator(_end_iter()); }\n\n  const_reverse_iterator rbegin() const {\n    return const_reverse_iterator(_end_iter());\n  }\n\n  const_reverse_iterator crbegin() const {\n    return const_reverse_iterator(_end_iter());\n  }\n\n  reverse_iterator rend() { return reverse_iterator(_begin_iter()); }\n\n  const_reverse_iterator rend() const {\n    return const_reverse_iterator(_begin_iter());\n  }\n\n  const_reverse_iterator crend() const {\n    return const_reverse_iterator(_begin_iter());\n  }\n\n  iterator erase(std::nullptr_t) = delete;  // Avoid misuse of erase(0)\n  iterator erase(std::nullptr_t,\n                 std::nullptr_t) = delete;  // Avoid misuse of erase(0)\n  iterator erase(iterator pos) { return erase(pos, pos + 1); }\n\n  iterator erase(const_iterator pos) { return erase((iterator)pos); }\n\n  iterator erase(iterator first, iterator last) {\n    BASE_VECTOR_DCHECK(first <= last);\n    if (BASE_VECTOR_LIKELY(first != last)) {\n      if constexpr (is_trivial || is_trivially_relocatable) {\n        std::memmove((void*)first, last, (_end_iter() - last) * sizeof(T));\n      } else {\n        // Slow path, move elements one by one and skip destructors if possible.\n        [[maybe_unused]] auto it =\n            _nontrivial_move_forward(first, last, _end_iter() - last);\n        if constexpr (!is_trivially_destructible_after_move) {\n          _nontrivial_destruct_reverse(it, last - first);\n        }\n      }\n      _dec_count(last - first);\n    }\n    return first;\n  }\n\n  template <class... Args>\n  iterator emplace(const_iterator pos, Args&&... args) {\n    if constexpr (is_trivial) {\n      // For trivial types, always call templateless implementation for binary\n      // size benefits.\n      auto it = (iterator)PrepareInsert(this, sizeof(T), pos);\n      ::new (static_cast<void*>(it)) T(std::forward<Args>(args)...);\n      return it;\n    } else {\n      auto pair = _nontrivial_prepare_insert(pos);\n      if constexpr (!is_trivially_destructible) {\n        if constexpr (!is_trivially_relocatable) {\n          // If T is_trivially_relocatable, we can skip destructor because in\n          // _nontrivial_prepare_insert the element at `pair.first` is moved\n          // away by `memmove`.\n          if (!pair.second) {\n            // Element at dest_pos was moved but needs destruction.\n            pair.first->~T();\n          }\n        }\n      }\n      // Construct at dest_pos.\n      ::new (static_cast<void*>(pair.first)) T(std::forward<Args>(args)...);\n      return pair.first;\n    }\n  }\n\n  template <class U>\n  iterator insert(std::nullptr_t,\n                  U&& value) = delete;  // Avoid misuse of insert(0)\n\n  BASE_VECTOR_INLINE iterator insert(const_iterator pos, const T& value) {\n    return emplace(pos, value);\n  }\n\n  BASE_VECTOR_INLINE iterator insert(const_iterator pos, T&& value) {\n    return emplace(pos, std::move(value));\n  }\n\n  /**\n   * @brief Reserve capacity.\n   * @return True if reallocation occurs.\n   */\n  bool reserve(size_t count) {\n    if (BASE_VECTOR_LIKELY(count > capacity())) {\n      _reallocate(count);\n      return true;\n    }\n    return false;\n  }\n\n  void clear() {\n    if constexpr (!is_trivial) {\n      _nontrivial_destruct_reverse(_begin_iter(), size());\n    }\n    // The capacity should not change.\n    _set_count(0);\n  }\n\n  void clear_and_shrink() {\n    _free();\n    _reset();\n  }\n\n  void shrink_to_fit() {\n    if (is_static_buffer()) {\n      return;\n    }\n    if (BASE_VECTOR_LIKELY(capacity() > size())) {\n      // This is not optimal for InlineVector if current size fits into inline\n      // buffer.\n      Vector tmp;\n      tmp._from(data(), size(), _nontrivial_construct_move);\n      tmp._copy_extra_bytes_from(*this);\n      _swap(tmp);\n    }\n  }\n\n  void swap(Vector& other) {\n    if (is_static_buffer() || other.is_static_buffer()) {\n      // If any one is using inline static buffer, we cannot simply swap the\n      // pointers.\n      Vector tmp(*this);\n      *this = other;\n      other = tmp;\n    } else {\n      _swap(other);\n    }\n  }\n\n  /**\n   * @brief Resize the array to desired length.\n   *  template<bool fill>:  If you want to set different values for new elements\n   *  after resize, you can pass fill as false to wipe out unnecessary\n   * assignment operations. This template argument is required for performance\n   * benefits.\n   *\n   * @param count Desired length.\n   * @return True if reallocation occurs.\n   */\n  template <bool fill, class U = T>\n  typename std::enable_if<std::is_same_v<U, T> && is_trivial && fill,\n                          bool>::type\n  resize(size_t count) {\n    if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T> ||\n                  std::is_pointer_v<T>) {\n      // For efficiency, merge to single memset instead of std::memcpy in\n      // Resize.\n      bool reallocated = false;\n      if (count > size()) {\n        if (count > capacity()) {\n          ReallocateTrivial(this, sizeof(T), count);\n          reallocated = true;\n        }\n        std::memset(begin() + size(), 0, (count - size()) * sizeof(T));\n      }\n      _set_count(count);\n      return reallocated;\n    } else {\n      if (count > size()) {\n        T value;\n        return Resize(this, sizeof(T), count, &value);\n      } else {\n        _set_count(count);\n        return false;\n      }\n    }\n  }\n\n  template <bool fill, class U = T>\n  typename std::enable_if<std::is_same_v<U, T> && is_trivial && fill,\n                          bool>::type\n  resize(size_t count, const T& value) {\n    if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T> ||\n                  std::is_pointer_v<T>) {\n      // For efficiency, merge to single memset if value is T() instead of\n      // std::memcpy in Resize.\n      bool reallocated = false;\n      if (count > size()) {\n        if (count > capacity()) {\n          ReallocateTrivial(this, sizeof(T), count);\n          reallocated = true;\n        }\n        if (value == T()) {\n          std::memset(begin() + size(), 0, (count - size()) * sizeof(T));\n        } else {\n          std::fill(begin() + size(), begin() + count, value);\n        }\n      }\n      _set_count(count);\n      return reallocated;\n    } else {\n      return Resize(this, sizeof(T), count, &value);\n    }\n  }\n\n  template <bool fill, class U = T>\n  typename std::enable_if<std::is_same_v<U, T> && is_trivial && !fill,\n                          bool>::type\n  resize(size_t count) {\n    return Resize(this, sizeof(T), count, nullptr);\n  }\n\n  /**\n   * \\brief It is recommended to call grow(N) for nontrivial type if you are\n   * surely to expand the array. Because resize() method specialize a branch to\n   * erase items.\n   */\n  template <class U = T>\n  typename std::enable_if<std::is_same_v<U, T> && !is_trivial &&\n                              std::is_copy_constructible_v<U>,\n                          bool>::type\n  resize(size_t count, const T& value) {\n    bool reallocated = false;\n    if (count > size()) {\n      if (count > capacity()) {\n        _reallocate(count);\n        reallocated = true;\n      }\n      _fill(_end_iter(), _begin_iter() + count, value);\n      _set_count(count);\n    } else {\n      erase(_begin_iter() + count, _end_iter());\n    }\n    return reallocated;\n  }\n\n  /**\n   * \\brief It is recommended to call grow(N) for nontrivial type if you are\n   * surely to expand the array. Because resize() method specialize a branch to\n   * erase items.\n   */\n  template <class U = T>\n  typename std::enable_if<std::is_same_v<U, T> && !is_trivial, bool>::type\n  resize(size_t count) {\n    bool reallocated = false;\n    if (count > size()) {\n      if (count > capacity()) {\n        _reallocate(count);\n        reallocated = true;\n      }\n      _fill(_end_iter(), _begin_iter() + count);\n      _set_count(count);\n    } else {\n      erase(_begin_iter() + count, _end_iter());\n    }\n    return reallocated;\n  }\n\n  /**\n   * \\brief Grow by increment length and returns the last object.\n   * The difference between grow and push_back/emplace_back is that for trivial\n   * type grow method does not do any copy or construction. For non-trivial\n   * type, grow only construct by default.\n   */\n  reference grow() {\n    _grow_if_need();\n    if constexpr (!is_trivial) {\n      auto end = _end_iter();\n      ::new (static_cast<void*>(end)) T();\n      _inc_count();\n      return *end;\n    } else {\n      _inc_count();\n      return back();\n    }\n  }\n\n  template <class U = T>\n  typename std::enable_if<std::is_same_v<U, T> && is_trivial>::type grow(\n      size_t count) {\n    BASE_VECTOR_DCHECK(count >= size());\n    resize<false>(count);\n  }\n\n  template <class U = T>\n  typename std::enable_if<std::is_same_v<U, T> && !is_trivial>::type grow(\n      size_t count) {\n    if (count > size()) {\n      if (count > capacity()) {\n        _reallocate(count);\n      }\n      _fill(_end_iter(), _begin_iter() + count);\n      _set_count(count);\n    } else if (count < size()) {\n      BASE_VECTOR_DCHECK(false);\n    }\n  }\n\n  /**\n   * @brief Use data to fill array buffer. The size of array will be reset to\n   *  byte_size / sizeof(T) + position.\n   *\n   * @param data Data source pointer. If null, buffer will be filled by 0.\n   * @param byte_size Data source byte length.\n   * @param position Optional, from which index of T to write.\n   */\n  template <class U = T>\n  typename std::enable_if<std::is_same_v<U, T> && is_trivial>::type fill(\n      const void* data, size_type byte_size, size_type position = 0) {\n    Fill(this, sizeof(T), data, byte_size, position);\n  }\n\n  /**\n   * @brief Append data buffer to end of this array.\n   * @param data Data source pointer. If null, buffer will be filled by 0.\n   * @param byte_size Data source byte length.\n   */\n  template <class U = T>\n  typename std::enable_if<std::is_same_v<U, T> && is_trivial>::type append(\n      const void* data, size_type byte_size) {\n    fill(data, byte_size, size());\n  }\n\n  /**\n   * @brief Append data buffer of another array to end of this array.\n   * @param other Another array which may differ in element type.\n   */\n  template <class W = T, class U>\n  typename std::enable_if<std::is_same_v<W, T> && is_trivial>::type append(\n      const Vector<U, ExtraBytesPerElement, CountRealloc>& other) {\n    if (!other.empty()) {\n      fill(other.data(), other.size() * sizeof(U), size());\n    }\n  }\n\n  /**\n   * @brief Sugar for_each method which provides only T or T& in callback.\n   */\n  template <typename Callback>\n  void for_each(Callback&& callback) {\n    const auto count = size();\n    for (uint32_t i = 0; i < count; i++) {\n      callback((*this)[i]);\n    }\n  }\n\n  class Unsafe {\n    // ATTENTION: function under this class is UNSAFE to use.\n    // Do NOT use them unless you have consulted with the owners of Vector.\n   public:\n    Unsafe() = delete;\n\n    // Force to set size of vector to target value. It is usually called for a\n    // vector that is about to be destroyed and sets the size to 0 to avoid\n    // calling the destructor method of the internal objects that have been\n    // relocated.\n    static void SetSize(Vector& vector, size_t value) {\n      vector._set_count(value);\n    }\n  };\n\n protected:\n  friend class Unsafe;\n\n  template <class U>\n  struct always_false : std::false_type {};\n\n  void _reallocate(size_t value) {\n    if constexpr (is_trivial || is_trivially_relocatable) {\n      ReallocateTrivial(this, sizeof(T), value);\n    } else {\n      _reallocate_nontrivial(value);\n    }\n  }\n\n  BASE_VECTOR_INLINE void _grow_if_need() {\n    if (BASE_VECTOR_UNLIKELY(size() == capacity())) {\n      // _grow_if_need is inlined at caller side. Although we can call\n      // _reallocate(0) but implement standalone for binary size optimization.\n      if constexpr (is_trivial || is_trivially_relocatable) {\n        ReallocateTrivial(this, sizeof(T));\n      } else {\n        _reallocate_nontrivial();\n      }\n    }\n  }\n\n  void _from(const void* src, size_t size,\n             [[maybe_unused]] void (*func)(iterator, iterator, size_t)) {\n    if (size > 0) {\n      if constexpr (is_trivial) {\n        PushBackBatch(this, sizeof(T), src, size);\n      } else {\n        reserve(size);\n        func(_begin_iter(), reinterpret_cast<iterator>(const_cast<void*>(src)),\n             size);\n        _set_count(size);\n      }\n    }\n  }\n\n  void _fill(iterator begin, iterator end, const T& v) {\n    for (; begin != end; begin++) {\n      ::new (static_cast<void*>(begin)) T(v);\n    }\n  }\n\n  void _fill(iterator begin, iterator end) {\n    for (; begin != end; begin++) {\n      ::new (static_cast<void*>(begin)) T();\n    }\n  }\n\n  void _construct_fill_default(size_t count) {\n    if (count > 0) {\n      if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T> ||\n                    std::is_pointer_v<T>) {\n        fill(nullptr, count * sizeof(T));  // Resize and set all bits to 0.\n      } else if constexpr (is_trivial) {\n        resize<true>(count);\n      } else {\n        // Do not use `resize` directly because for non-trivial types, the\n        // `resize` method instantiates `erase` method.\n        _reallocate(count);\n        _fill(_end_iter(), _begin_iter() + count);\n        _set_count(count);\n      }\n    }\n  }\n\n  void _construct_fill(size_t count, const T& value) {\n    if (count > 0) {\n      if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T> ||\n                    std::is_pointer_v<T>) {\n        if (value == T()) {\n          fill(nullptr, count * sizeof(T));  // Resize and set all bits to 0.\n        } else {\n          resize<false>(count);\n          std::fill(begin(), end(), value);\n        }\n      } else if constexpr (is_trivial) {\n        resize<true>(count, value);\n      } else {\n        // Do not use `resize` directly because for non-trivial types, the\n        // `resize` method instantiates `erase` method.\n        _reallocate(count);\n        _fill(_end_iter(), _begin_iter() + count, value);\n        _set_count(count);\n      }\n    }\n  }\n\n  // Returns pair<pos, true_if_construct_at_end>\n  BASE_VECTOR_INLINE std::pair<iterator, bool> _nontrivial_prepare_insert(\n      const_iterator pos) {\n    auto diff = pos - _begin_iter();\n    _grow_if_need();\n    auto dest_pos = _begin_iter() + diff;\n    auto new_last = _end_iter();\n    bool construct_at_end = false;\n    if (BASE_VECTOR_UNLIKELY(dest_pos > new_last)) {\n      BASE_VECTOR_DCHECK(false);\n    } else {\n      if (dest_pos < new_last) {\n        if constexpr (is_trivially_relocatable) {\n          // Fast path, use memmove to shift all elements to next slot.\n          std::memmove((void*)(dest_pos + 1), dest_pos,\n                       sizeof(T) * (new_last - dest_pos));\n        } else {\n          // Slow path, construct new T at end, move previous back item to it.\n          _nontrivial_construct_move(new_last, new_last - 1, 1);\n          // Move left objects.\n          _nontrivial_move_backward(new_last - 1, new_last - 2,\n                                    new_last - dest_pos - 1);\n        }\n      } else {\n        construct_at_end = true;\n      }\n      _inc_count();\n    }\n    return {dest_pos, construct_at_end};\n  }\n};\n\ntemplate <class T, size_t ExtraBytesPerElement, bool CountRealloc>\ninline bool operator==(\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __x,\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __y) {\n  const typename Vector<T, ExtraBytesPerElement, CountRealloc>::size_type __sz =\n      __x.size();\n  if (__sz != __y.size()) {\n    return false;\n  }\n  if (!std::equal(__x.begin(), __x.end(), __y.begin())) {\n    return false;\n  }\n  if constexpr (ExtraBytesPerElement > 0) {\n    // Also compares extra bytes.\n    if (__sz > 0) {\n      if (std::memcmp(__x.get_memory_allocate(), __y.get_memory_allocate(),\n                      __sz * ExtraBytesPerElement) != 0) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\ntemplate <class T, size_t ExtraBytesPerElement, bool CountRealloc>\ninline bool operator!=(\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __x,\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __y) {\n  return !(__x == __y);\n}\n\ntemplate <class T, size_t ExtraBytesPerElement, bool CountRealloc>\ninline bool operator<(\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __x,\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __y) {\n  return std::lexicographical_compare(__x.begin(), __x.end(), __y.begin(),\n                                      __y.end());\n}\n\ntemplate <class T, size_t ExtraBytesPerElement, bool CountRealloc>\ninline bool operator>(\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __x,\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __y) {\n  return __y < __x;\n}\n\ntemplate <class T, size_t ExtraBytesPerElement, bool CountRealloc>\ninline bool operator>=(\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __x,\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __y) {\n  return !(__x < __y);\n}\n\ntemplate <class T, size_t ExtraBytesPerElement, bool CountRealloc>\ninline bool operator<=(\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __x,\n    const Vector<T, ExtraBytesPerElement, CountRealloc>& __y) {\n  return !(__y < __x);\n}\n\nusing ByteArray = Vector<uint8_t, 0>;\n\n/**\n * @brief Sugar for construction ByteArray from static primitive array.\n * ByteArray a = ByteArrayFromBuffer((float[]) {0, 1, 1, 1, 1, 0, 0, 0});\n */\ntemplate <typename T, size_t Num>\nByteArray ByteArrayFromBuffer(const T (&data)[Num]) {\n  return ByteArray(sizeof(data), data);\n}\n\n/**\n * @brief A resizable array type initialized with capacity of N and the buffer\n * is inplace following up the array struct. When element count exceeds N, a new\n * external buffer will be allocated and the inplace_buffer_ will be wasted.\n */\ntemplate <class T, size_t N, size_t ExtraBytesPerElement = 0,\n          bool CountRealloc = false>\nstruct InlineVector : public Vector<T, ExtraBytesPerElement, CountRealloc> {\n  using\n      typename VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::iterator;\n  using typename VectorPrototype<T, ExtraBytesPerElement,\n                                 CountRealloc>::const_iterator;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::is_trivial;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::size;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::capacity;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_set_memory;\n  using VectorPrototype<T, ExtraBytesPerElement, CountRealloc>::_set_capacity;\n  using VectorPrototype<T, ExtraBytesPerElement,\n                        CountRealloc>::_copy_extra_bytes_from;\n  using Vector<T, ExtraBytesPerElement, CountRealloc>::_begin_iter;\n  using Vector<T, ExtraBytesPerElement, CountRealloc>::_end_iter;\n  using Vector<T, ExtraBytesPerElement, CountRealloc>::reserve;\n  using Vector<T, ExtraBytesPerElement, CountRealloc>::clear;\n  using Vector<T, ExtraBytesPerElement, CountRealloc>::clear_and_shrink;\n  using Vector<T, ExtraBytesPerElement, CountRealloc>::_from;\n  using Vector<T, ExtraBytesPerElement, CountRealloc>::_construct_fill;\n  using Vector<T, ExtraBytesPerElement, CountRealloc>::_construct_fill_default;\n\n  static constexpr size_t kInlinedSize = N;\n\n  InlineVector() : Vector<T, ExtraBytesPerElement, CountRealloc>() {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n  }\n\n  InlineVector(std::nullptr_t) : InlineVector() {}  // NOLINT\n\n  explicit InlineVector(size_t count) {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    _construct_fill_default(count);\n  }\n\n  InlineVector(size_t count, const T& value) {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    _construct_fill(count, value);\n  }\n\n  InlineVector(const_iterator begin, const_iterator end) {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    _from(begin, end - begin, _nontrivial_construct_copy);\n  }\n\n  InlineVector(iterator begin, iterator end) {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    _from(begin, end - begin, _nontrivial_construct_copy);\n  }\n\n  InlineVector(std::initializer_list<T> list)\n      : Vector<T, ExtraBytesPerElement, CountRealloc>() {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    *this = std::move(list);\n  }\n\n  InlineVector(\n      const Vector<T, ExtraBytesPerElement, CountRealloc>& other) {  // NOLINT\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    *this = other;\n  }\n\n  InlineVector(const InlineVector& other) {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    *this = other;\n  }\n\n  template <size_t N2>\n  InlineVector(\n      const InlineVector<T, N2, ExtraBytesPerElement, CountRealloc>& other) {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    *this = other;\n  }\n\n  InlineVector(\n      Vector<T, ExtraBytesPerElement, CountRealloc>&& other) {  // NOLINT\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    *this = std::move(other);\n  }\n\n  InlineVector(InlineVector&& other) {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    *this = std::move(other);\n  }\n\n  template <size_t N2>\n  InlineVector(\n      InlineVector<T, N2, ExtraBytesPerElement, CountRealloc>&& other) {\n    static_assert(N > 0,\n                  \"InlineVector must have initial capacity larger than 0.\");\n    _init_static();\n    *this = std::move(other);\n  }\n\n  InlineVector& operator=(std::initializer_list<T> list) {\n    reinterpret_cast<Vector<T, ExtraBytesPerElement, CountRealloc>*>(this)->\n    operator=(std::move(list));\n    return *this;\n  }\n\n  InlineVector& operator=(\n      const Vector<T, ExtraBytesPerElement, CountRealloc>& other) {\n    reinterpret_cast<Vector<T, ExtraBytesPerElement, CountRealloc>*>(this)->\n    operator=(other);\n    return *this;\n  }\n\n  InlineVector& operator=(const InlineVector& other) {\n    reinterpret_cast<Vector<T, ExtraBytesPerElement, CountRealloc>*>(this)->\n    operator=(other);\n    return *this;\n  }\n\n  template <size_t N2>\n  InlineVector& operator=(\n      const InlineVector<T, N2, ExtraBytesPerElement, CountRealloc>& other) {\n    reinterpret_cast<Vector<T, ExtraBytesPerElement, CountRealloc>*>(this)->\n    operator=(other);\n    return *this;\n  }\n\n  InlineVector& operator=(\n      Vector<T, ExtraBytesPerElement, CountRealloc>&& other) {\n    if (this != &other) {\n      if (other.size() > capacity()) {\n        if (!other.is_static_buffer()) {\n          // Just swap pointers.\n          reinterpret_cast<Vector<T, ExtraBytesPerElement, CountRealloc>*>(this)\n              ->\n              operator=(std::move(other));\n          return *this;\n        } else {\n          reserve(other.size());\n        }\n      }\n\n      // Trying best to use inplace buffer, move items of other to self.\n      clear();\n      _from(other.begin(), other.size(), _nontrivial_construct_move);\n      _copy_extra_bytes_from(other);\n      other.clear();\n    }\n    return *this;\n  }\n\n  InlineVector& operator=(InlineVector&& other) {\n    return operator=(std::move(\n        *reinterpret_cast<Vector<T, ExtraBytesPerElement, CountRealloc>*>(\n            &other)));\n  }\n\n  template <size_t N2>\n  InlineVector& operator=(\n      InlineVector<T, N2, ExtraBytesPerElement, CountRealloc>&& other) {\n    return operator=(std::move(\n        *reinterpret_cast<Vector<T, ExtraBytesPerElement, CountRealloc>*>(\n            &other)));\n  }\n\n  void clear_and_shrink() {\n    Vector<T, ExtraBytesPerElement, CountRealloc>::clear_and_shrink();\n    _init_static();\n  }\n\n private:\n  alignas(std::max(alignof(T), sizeof(void*))) uint8_t\n      inplace_buffer_[AlignUp(N * ExtraBytesPerElement, 8) + sizeof(T) * N];\n\n  void _init_static() {\n    _set_capacity(N, true);\n    _set_memory(&inplace_buffer_[AlignUp(N * ExtraBytesPerElement, 8)]);\n  }\n};\n\n/**\n * @brief Stack using Vector<T> or InlineVector<T, N> as underlying container.\n */\ntemplate <class T>\nusing Stack = std::stack<T, Vector<T>>;\n\ntemplate <class T, size_t N>\nusing InlineStack = std::stack<T, InlineVector<T, N>>;\n\ntemplate <bool Flag>\nstruct MapStatisticsBase;\n\nenum class MapStatisticsFindKind {\n  kFind,                 // find(), contains() or [] to find a value\n  kInsertFindCollision,  // key found before insert\n  kInsertFind,           // key not found before insert\n};\n\ntemplate <>\nstruct MapStatisticsBase<true> {\n  mutable uint32_t max_count{0};\n  mutable uint32_t insert_count{0};\n  mutable uint32_t erase_count{0};\n  mutable uint32_t total_find_count{0};\n  mutable uint32_t find_count{0};\n  mutable uint32_t insert_find_collision_count{0};\n\n  ~MapStatisticsBase();\n\n  void UpdateMaxCount(size_t v) const;\n\n  void IncreaseInsertCount() const { insert_count++; }\n\n  void IncreaseEraseCount() const { erase_count++; }\n\n  void RecordFind(MapStatisticsFindKind kind, size_t find_of_count) const;\n\n  static void IncreaseFindOfCount(MapStatisticsFindKind kind,\n                                  size_t find_of_count);\n};\n\ntemplate <>\nstruct MapStatisticsBase<false> {\n  void UpdateMaxCount([[maybe_unused]] size_t v) const {}\n  void IncreaseInsertCount() const {}\n  void IncreaseEraseCount() const {}\n  void RecordFind([[maybe_unused]] MapStatisticsFindKind kind,\n                  [[maybe_unused]] size_t find_of_count) const {}\n};\n\n#define BASE_VECTOR_HAS_MEMBER_OF_NAME(N)                                      \\\n  template <typename T, typename = void>                                       \\\n  struct has_##N##_member : std::false_type {};                                \\\n  template <typename T>                                                        \\\n  struct has_##N##_member<T, std::void_t<decltype(T::N)>> : std::true_type {}; \\\n  template <typename T>                                                        \\\n  inline constexpr bool has_##N##_member_v = has_##N##_member<T>::value\n\n#define BASE_VECTOR_HAS_TYPE_MEMBER_OF_NAME(N)                                \\\n  template <typename T, typename = void>                                      \\\n  struct has_##N##_member : std::false_type {};                               \\\n  template <typename T>                                                       \\\n  struct has_##N##_member<T, std::void_t<typename T::N>> : std::true_type {}; \\\n  template <typename T>                                                       \\\n  inline constexpr bool has_##N##_member_v = has_##N##_member<T>::value\n\nBASE_VECTOR_HAS_MEMBER_OF_NAME(use_hash);\nBASE_VECTOR_HAS_MEMBER_OF_NAME(consecutive_key);\nBASE_VECTOR_HAS_MEMBER_OF_NAME(assign_existing_for_merge);\nBASE_VECTOR_HAS_TYPE_MEMBER_OF_NAME(hash);\nBASE_VECTOR_HAS_TYPE_MEMBER_OF_NAME(equal);\nBASE_VECTOR_HAS_TYPE_MEMBER_OF_NAME(equal_when_hash_equal);\n\n#undef BASE_VECTOR_HAS_MEMBER_OF_NAME\n#undef BASE_VECTOR_HAS_TYPE_MEMBER_OF_NAME\n\n// Only stores 32bit hash value for SIMD/NEON instructions.\nusing KeyPolicyReducedHashValueType = uint32_t;\n\ntemplate <class K, class Hash = std::hash<K>>\nstruct ReducedHash {\n  using Type = KeyPolicyReducedHashValueType;\n  using OriginalHash = Hash;\n\n  static constexpr auto ByReinterpret =\n      (sizeof(K) <= sizeof(Type)) &&\n      (std::is_enum_v<K> || std::is_integral_v<K>);\n\n  using BestTypeForOperator =\n      std::conditional_t<(sizeof(K) <= sizeof(void*)) &&\n                             std::is_trivially_copyable_v<K>,\n                         K, const K&>;\n\n  Type operator()(BestTypeForOperator v) const {\n    if constexpr (sizeof(K) <= sizeof(Type)) {\n      // Reinterpret K with unsigned zero extend when K is integer type or enum\n      // type and fits into Type.\n      if constexpr (std::is_enum_v<K>) {\n        using EnumIntType = std::underlying_type_t<K>;\n        using UnsignedEnumIntType = std::make_unsigned_t<EnumIntType>;\n        UnsignedEnumIntType num_value = static_cast<UnsignedEnumIntType>(v);\n        return static_cast<Type>(num_value);\n      } else if constexpr (std::is_integral_v<K>) {\n        using UnsignedIntType = std::make_unsigned_t<K>;\n        UnsignedIntType num_value = static_cast<UnsignedIntType>(v);\n        return static_cast<Type>(num_value);\n      }\n    }\n    // Calculate original hash value and reduce.\n    return static_cast<Type>(OriginalHash()(v));\n  }\n\n  // If K can be directly reinterpreted as uint32_t, EqualWhenHashEqual always\n  // returns true.\n  struct AlwaysEqualWhenHashEqual {\n    bool operator()([[maybe_unused]] K x, [[maybe_unused]] K y) const {\n      return true;\n    }\n  };\n};\n\n/**\n * The KeyPolicy type is used to speed up key lookups. For example, you can\n * provide a hash method for the key type. When implementing Map/Set, internal\n * lookups can use hash values for quick judgment. Currently, this only works\n * for Map/Set implemented by linear search.\n *\n * `Hash` is optional. You could also provide a policy like `KeyPolicy<K, void,\n * CustomEqual>` to customize the equality check function such as case\n * insensitive comparision without using hash.\n *\n * `EqualWhenHashEqual` is optional. When provided, it would be used to check if\n * two keys are strict equal after their hash values are known to be equal.\n * Sometimes you can use this to do tiny optimizations. For example, if the\n * `operator==` method of the K type already has a hash check, then when the\n * hashes are known to be equal, the hash check can be avoided and other\n * attributes can be checked to save one integer equality check and branch.\n * For another example, if the hash value of the K is itself, EqualWhenHashEqual\n * can directly return true which will be optimized out.\n */\ntemplate <class K, class Hash = std::hash<K>, class Equal = std::equal_to<K>,\n          class EqualWhenHashEqual = Equal>\nstruct KeyPolicy {\n  static constexpr auto use_hash = !std::is_void_v<Hash>;\n\n  using hash = Hash;\n  using equal = Equal;\n  using equal_when_hash_equal = EqualWhenHashEqual;\n};\n\n/**\n * This policy enables KeyPolicy for LinearFlatMap by default, which means using\n * hash for search optimization. If the K type contains the\n * `equal_when_hash_equal` structure, it will be used as an optimized method to\n * determine whether two K objects are completely equal when their hashes are\n * already equal. If K can be directly reinterpreted as uint32_t, use\n * ReducedHash.\n */\ntemplate <class K>\nstruct ReducedHashKeyPolicy {\n public:\n  static constexpr auto use_hash = true;\n\n  using hash = ReducedHash<K, std::hash<K>>;\n  using equal = std::equal_to<K>;\n\n private:\n  template <typename AnyType>\n  struct type_identity {\n    using type = AnyType;\n  };\n\n  template <bool Explicit>\n  struct EqualWhenHashEqualHelper {\n    static auto select() {\n      if constexpr (Explicit) {\n        return type_identity<typename K::equal_when_hash_equal>{};\n      } else if constexpr (hash::ByReinterpret) {\n        return type_identity<typename hash::AlwaysEqualWhenHashEqual>{};\n      } else {\n        return type_identity<equal>{};\n      }\n    }\n  };\n\n public:\n  using equal_when_hash_equal =\n      typename decltype(EqualWhenHashEqualHelper<\n                        has_equal_when_hash_equal_member_v<K>>::select())::type;\n};\n\n/**\n * This is a key policy for map and to optimize key of integer types.\n * By default, the map stores `std::pair<K, V>`, and the data can be accessed\n * through `it->first` and `it->second` during iteration. When\n * `MapKeyPolicyConsecutiveIntegers` is used, keys and values are stored\n * independently and continuously. In short, key values are stored like\n * \"K-K-K-V-V-V...\" instead of \"K-V-K-V-K-V...\" and iterators only return\n * values. Requirements:\n * 1. K is trivial and fits into 32 bits. uint32_t, int16_t, enums are\n * supported.\n * 2. hash(key) == key.\n * 3. Only for maps.\n */\ntemplate <class K>\nstruct MapKeyPolicyConsecutiveIntegers {\n  static_assert(ReducedHash<K>::ByReinterpret,\n                \"Using MapKeyPolicyConsecutiveIntegers, the key type K must be \"\n                \"integer or enum which fit into 32 bits.\");\n\n  static constexpr auto use_hash = true;\n  static constexpr auto consecutive_key = true;\n\n  using hash = ReducedHash<K>;\n  using equal = std::equal_to<K>;\n  using equal_when_hash_equal = typename hash::AlwaysEqualWhenHashEqual;\n\n  template <class T, bool Const>\n  struct Reference {\n    using key_iterator = const KeyPolicyReducedHashValueType*;\n    using value_iterator = std::conditional_t<Const, const T*, T*>;\n    using value_reference = std::conditional_t<Const, const T&, T&>;\n\n    // Mimic the behavior of ordinary value type `std::pair<K, T>`.\n    const K& first;\n    value_reference second;\n\n    Reference(key_iterator key_it, value_iterator value_it)\n        : first(*reinterpret_cast<const K*>(key_it)), second(*value_it) {}\n  };\n\n  template <class T, bool Const>\n  struct RangeLoopIterator {\n    using key_iterator = const KeyPolicyReducedHashValueType*;\n    using value_iterator = std::conditional_t<Const, const T*, T*>;\n    using value_reference = std::conditional_t<Const, const T&, T&>;\n\n    struct PointerView {\n      key_iterator first;\n      value_iterator second;\n    };\n\n    // Mimic the behavior of ordinary value type `std::pair<K, T>`.\n    const K& first;\n    value_reference second;\n\n    RangeLoopIterator(key_iterator key_it, value_iterator value_it)\n        : first(*reinterpret_cast<const K*>(key_it)), second(*value_it) {}\n    RangeLoopIterator(const RangeLoopIterator& other)\n        : first(other.first), second(other.second) {}\n    RangeLoopIterator& operator=(const RangeLoopIterator& other) {\n      *reinterpret_cast<PointerView*>(this) =\n          *reinterpret_cast<const PointerView*>(&other);\n      return *this;\n    }\n\n    RangeLoopIterator& operator*() { return *this; }\n    const RangeLoopIterator& operator*() const { return *this; }\n\n    RangeLoopIterator* operator->() { return this; }\n    const RangeLoopIterator* operator->() const { return this; }\n\n    operator std::pair<const K, T>() const { return {first, second}; }\n\n    RangeLoopIterator& operator++() {\n      reinterpret_cast<PointerView*>(this)->first++;\n      reinterpret_cast<PointerView*>(this)->second++;\n      return *this;\n    }\n\n    RangeLoopIterator operator++(int) {\n      RangeLoopIterator t(*this);\n      ++(*this);\n      return t;\n    }\n\n    friend bool operator==(const RangeLoopIterator& x, const T* y) {\n      return &x.second == y;\n    }\n\n    friend bool operator==(const T* x, const RangeLoopIterator& y) {\n      return x == &y.second;\n    }\n\n    friend bool operator!=(const RangeLoopIterator& x, const T* y) {\n      return &x.second != y;\n    }\n\n    friend bool operator!=(const T* x, const RangeLoopIterator& y) {\n      return x != &y.second;\n    }\n\n    friend bool operator==(const RangeLoopIterator& x,\n                           const RangeLoopIterator& y) {\n      return &x.second == &y.second;\n    }\n\n    friend bool operator!=(const RangeLoopIterator& x,\n                           const RangeLoopIterator& y) {\n      return &x.second != &y.second;\n    }\n\n    value_iterator value_it() const { return &second; }\n  };\n};\n\n/**\n * @brief Base storage type for binary-search array and linear search array.\n * It provides definitions and interfaces which have nothing to do with\n * query method of key.\n */\ntemplate <class K, class T, class KeyPolicy, size_t N, bool Stat>\nstruct KeyValueArray : protected MapStatisticsBase<Stat> {\n private:\n  static constexpr bool get_is_key_hash() {\n    if constexpr (has_use_hash_member_v<KeyPolicy>) {\n      return KeyPolicy::use_hash;\n    } else {\n      return false;\n    }\n  }\n\n  static constexpr bool get_consecutive_key() {\n    if constexpr (has_consecutive_key_member_v<KeyPolicy>) {\n      return KeyPolicy::consecutive_key;\n    } else {\n      return false;\n    }\n  }\n\n  template <typename AnyType>\n  struct type_identity {\n    using type = AnyType;\n  };\n\n public:\n  static constexpr auto is_map = !std::is_void_v<T>;\n  static constexpr auto is_key_hash = get_is_key_hash();\n  static constexpr auto consecutive_key = get_consecutive_key();\n\n  static_assert(!consecutive_key || is_map, \"KeyPolicy only applies for map.\");\n\n private:\n  // V is insert_value_type or store_value_type.\n  struct KeyExtractor {\n    template <typename V>\n    const auto& operator()(V&& v) {\n      if constexpr (is_map) {\n        return v.first;\n      } else {\n        return v;\n      }\n    }\n  };\n\n  struct ValueExtractor {\n    template <typename V>\n    auto& operator()(V&& v) {\n      if constexpr (is_map) {\n        if constexpr (consecutive_key) {\n          return v;\n        } else {\n          return v.second;\n        }\n      } else {\n        return v;\n      }\n    }\n  };\n\n public:\n  using key_type = K;\n  using mapped_type = T;\n  using key_policy = KeyPolicy;\n  using key_extractor = KeyExtractor;\n  using value_extractor = ValueExtractor;\n\n  // Paramater type for traditional map insert() method and constructor from\n  // initializer list.\n  using insert_value_type =\n      typename std::conditional_t<is_map, std::pair<const K, T>, K>;\n\n  using non_const_insert_value_type =\n      typename std::conditional_t<is_map, std::pair<K, T>, K>;\n\n  // Value type in array from user's perspective.\n  using value_type = typename std::conditional_t<\n      is_map, std::conditional_t<consecutive_key, T, std::pair<const K, T>>, K>;\n\n  // The actual type for array template `T` which removes const qualifier of K.\n  using store_value_type = typename std::conditional_t<\n      is_map, std::conditional_t<consecutive_key, T, std::pair<K, T>>, K>;\n\n  using container_type = typename std::conditional_t<\n      (N > 0),\n      InlineVector<value_type, N,\n                   is_key_hash ? sizeof(KeyPolicyReducedHashValueType) : 0,\n                   Stat>,\n      Vector<value_type,\n             is_key_hash ? sizeof(KeyPolicyReducedHashValueType) : 0, Stat>>;\n\n  using store_container_type = typename std::conditional_t<\n      (N > 0),\n      InlineVector<store_value_type, N,\n                   is_key_hash ? sizeof(KeyPolicyReducedHashValueType) : 0,\n                   Stat>,\n      Vector<store_value_type,\n             is_key_hash ? sizeof(KeyPolicyReducedHashValueType) : 0, Stat>>;\n  using store_iterator =\n      std::conditional_t<is_map, typename store_container_type::iterator,\n                         typename store_container_type::const_iterator>;\n  using const_store_iterator = typename store_container_type::const_iterator;\n\n protected:\n  container_type& array() const {\n    return *reinterpret_cast<container_type*>(\n        const_cast<store_container_type*>(&array_));\n  }\n\n  template <class K2, class T2, class KeyPolicy2, size_t N2, bool Stat2>\n  friend struct KeyValueArray;\n\n  using MapStatisticsBase<Stat>::UpdateMaxCount;\n  using MapStatisticsBase<Stat>::IncreaseEraseCount;\n\n public:\n  using iterator = std::conditional_t<is_map, typename container_type::iterator,\n                                      typename container_type::const_iterator>;\n  using const_iterator = typename container_type::const_iterator;\n  using reverse_iterator =\n      std::conditional_t<is_map, typename container_type::reverse_iterator,\n                         typename container_type::const_reverse_iterator>;\n  using const_reverse_iterator =\n      typename container_type::const_reverse_iterator;\n\n private:\n  // For maps with Consecutive-Key policy enabled, since the key and value are\n  // separate, special iterators are used for the begin() and end() methods, and\n  // access to the key and value is provided through it.first() and it.second().\n  template <bool Const>\n  struct RangeLoopIteratorHelper {\n    static auto select() {\n      if constexpr (is_map && consecutive_key) {\n        return type_identity<\n            typename KeyPolicy::template RangeLoopIterator<T, Const>>{};\n      } else {\n        return type_identity<\n            std::conditional_t<Const, const_iterator, iterator>>{};\n      }\n    }\n  };\n\n  template <bool Const>\n  struct ReferenceHelper {\n    static auto select() {\n      if constexpr (is_map) {\n        if constexpr (consecutive_key) {\n          return type_identity<\n              typename KeyPolicy::template Reference<T, Const>>{};\n        } else {\n          return type_identity<\n              std::conditional_t<Const, const value_type&, value_type&>>{};\n        }\n      } else {\n        return type_identity<const value_type&>{};\n      }\n    }\n  };\n\n public:\n  using range_loop_iterator =\n      typename decltype(RangeLoopIteratorHelper<false>::select())::type;\n  using range_loop_const_iterator =\n      typename decltype(RangeLoopIteratorHelper<true>::select())::type;\n\n  using reference_type =\n      typename decltype(ReferenceHelper<false>::select())::type;\n  using const_reference_type =\n      typename decltype(ReferenceHelper<true>::select())::type;\n\n protected:\n  // To accommodate the Consecutive-Key feature, the begin() and end() method\n  // return a range_loop_iterator.\n  iterator value_begin() { return array().begin(); }\n  const_iterator value_begin() const { return array().begin(); }\n  iterator value_end() { return array().end(); }\n  const_iterator value_end() const { return array().end(); }\n\n public:\n  KeyValueArray() {}\n\n  // Only when not using hash, the internal array can be moved from source.\n  // Because we are using extra leading bytes of array buffer to store hash\n  // values.\n  template <size_t ExtraBytesPerElement, bool CountRealloc,\n            typename = std::enable_if_t<ExtraBytesPerElement == 0>>\n  KeyValueArray(Vector<store_value_type, ExtraBytesPerElement, CountRealloc>&&\n                    source_array)\n      : array_(std::move(source_array)) {\n    static_assert(!is_key_hash);\n    UpdateMaxCount(array_.size());\n  }\n\n  template <size_t N2>\n  KeyValueArray(const KeyValueArray<K, T, KeyPolicy, N2, Stat>& other)\n      : array_(other.array_) {\n    UpdateMaxCount(array_.size());\n  }\n\n  template <size_t N2>\n  KeyValueArray(KeyValueArray<K, T, KeyPolicy, N2, Stat>&& other)\n      : array_(std::move(other.array_)) {\n    UpdateMaxCount(array_.size());\n  }\n\n  template <size_t N2>\n  KeyValueArray& operator=(\n      const KeyValueArray<K, T, KeyPolicy, N2, Stat>& other) {\n    array_ = other.array_;\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  KeyValueArray& operator=(KeyValueArray<K, T, KeyPolicy, N2, Stat>&& other) {\n    array_ = std::move(other.array_);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  bool is_static_buffer() const { return array_.is_static_buffer(); }\n\n  size_t size() const { return array_.size(); }\n\n  size_t capacity() const { return array_.capacity(); }\n\n  bool empty() const { return array_.empty(); }\n\n  void clear() {\n    // We should also free memory so `array_.clear()` is not feasible.\n    array_.clear_and_shrink();\n  }\n  void clear_keep_buffer() { array_.clear(); }\n\n  bool reserve(size_t count) { return array_.reserve(count); }\n\n  reference_type front() {\n    if constexpr (is_map && consecutive_key) {\n      return reference_type(_key_hash_data(), array().begin());\n    } else {\n      return array().front();\n    }\n  }\n\n  const_reference_type front() const {\n    if constexpr (is_map && consecutive_key) {\n      return const_reference_type(_key_hash_data(), array().begin());\n    } else {\n      return array().front();\n    }\n  }\n\n  reference_type back() {\n    if constexpr (is_map && consecutive_key) {\n      return reference_type(&_key_hash_data()[array().size() - 1],\n                            &array().back());\n    } else {\n      return array().back();\n    }\n  }\n\n  const_reference_type back() const {\n    if constexpr (is_map && consecutive_key) {\n      return const_reference_type(&_key_hash_data()[array().size() - 1],\n                                  &array().back());\n    } else {\n      return array().back();\n    }\n  }\n\n  range_loop_iterator begin() {\n    if constexpr (is_map && consecutive_key) {\n      return range_loop_iterator(_key_hash_data(), array().begin());\n    } else {\n      return array().begin();\n    }\n  }\n\n  range_loop_const_iterator begin() const {\n    if constexpr (is_map && consecutive_key) {\n      return range_loop_const_iterator(_key_hash_data(), array().begin());\n    } else {\n      return array().begin();\n    }\n  }\n\n  const_iterator cbegin() const { return array().cbegin(); }\n\n  range_loop_iterator end() {\n    if constexpr (is_map && consecutive_key) {\n      // RangeLoopIterator only compares value iterator.\n      return range_loop_iterator(nullptr, array().end());\n    } else {\n      return array().end();\n    }\n  }\n\n  const_iterator cend() const { return array().cend(); }\n\n  range_loop_const_iterator end() const {\n    if constexpr (is_map && consecutive_key) {\n      // RangeLoopIterator only compares value iterator.\n      return range_loop_const_iterator(nullptr, array().end());\n    } else {\n      return array().end();\n    }\n  }\n\n  reverse_iterator rbegin() { return array().rbegin(); }\n\n  const_reverse_iterator rbegin() const { return array().rbegin(); }\n\n  const_reverse_iterator crbegin() const { return array().crbegin(); }\n\n  reverse_iterator rend() { return array().rend(); }\n\n  const_reverse_iterator rend() const { return array().rend(); }\n\n  const_reverse_iterator crend() const { return array().crend(); }\n\n  template <typename It,\n            typename = std::enable_if_t<std::is_same_v<It, iterator> ||\n                                        std::is_same_v<It, const_iterator>>>\n  iterator erase(It pos) {\n    IncreaseEraseCount();\n    if constexpr (is_key_hash) {\n      _erase_key_hash(pos - value_begin());\n    }\n    if constexpr (std::is_same_v<It, iterator>) {\n      return reinterpret_cast<iterator>(\n          array_.erase(reinterpret_cast<store_iterator>(pos)));\n    } else {\n      return reinterpret_cast<iterator>(\n          array_.erase(reinterpret_cast<const_store_iterator>(pos)));\n    }\n  }\n\n  // For usage of `map.erase(map.begin());` when map is using\n  // MapKeyPolicyConsecutiveIntegers.\n  template <typename It, typename = std::enable_if_t<\n                             (is_map && consecutive_key) &&\n                             (std::is_same_v<It, range_loop_iterator> ||\n                              std::is_same_v<It, range_loop_const_iterator>)>>\n  iterator erase(const It& pos) {\n    IncreaseEraseCount();\n    if constexpr (is_key_hash) {\n      _erase_key_hash(pos.value_it() - value_begin());\n    }\n    if constexpr (std::is_same_v<It, range_loop_iterator>) {\n      return reinterpret_cast<iterator>(\n          array_.erase(reinterpret_cast<store_iterator>(pos.value_it())));\n    } else {\n      return reinterpret_cast<iterator>(\n          array_.erase(reinterpret_cast<const_store_iterator>(pos.value_it())));\n    }\n  }\n\n  template <size_t N2>\n  bool operator==(const KeyValueArray<K, T, KeyPolicy, N2, Stat>& other) const {\n    return array_ == other.array_;\n  }\n\n  template <size_t N2>\n  bool operator!=(const KeyValueArray<K, T, KeyPolicy, N2, Stat>& other) const {\n    return array_ != other.array_;\n  }\n\n  class Unsafe {\n    // ATTENTION: function under this class is UNSAFE to use.\n    // Do NOT use them unless you have consulted with the owners of Vector.\n   public:\n    Unsafe() = delete;\n\n    // Force to set size of vector to target value. It is usually called for a\n    // vector that is about to be destroyed and sets the size to 0 to avoid\n    // calling the destructor method of the internal objects that have been\n    // relocated.\n    static void SetSize(KeyValueArray& map, size_t value) {\n      decltype(map.array_)::Unsafe::SetSize(map.array_, value);\n    }\n  };\n\n protected:\n  friend class Unsafe;\n\n  store_container_type array_;\n\n  template <size_t N2>\n  void _swap(KeyValueArray<K, T, KeyPolicy, N2, Stat>& other) {\n    array_.swap(other.array_);\n  }\n\n  BASE_VECTOR_INLINE KeyPolicyReducedHashValueType* _key_hash_data() const {\n    return static_cast<KeyPolicyReducedHashValueType*>(\n        array_.get_memory_allocate());\n  }\n\n  void _erase_key_hash(size_t index) {\n    auto* data = _key_hash_data();\n    std::memmove(&data[index], &data[index + 1],\n                 (size() - index - 1) * sizeof(KeyPolicyReducedHashValueType));\n  }\n};\n\n/**\n * @brief For ordered map/set base on array. Performance consideration:\n *  1. Insertion is relatively slow because of reallocation and moving of\n * elements. While map/set also need rebalancing of RB tree.\n * Performance test data shows that when the key is a basic type such as int,\n * the insertion performance of the container is still ahead of std::map when\n * the data volume reaches several thousands. When the key is a type with\n * relatively low movement overhead such as std::string, the insertion\n * performance is on par with std::map when the data volume reaches 30-50.\n * Besides, ordered map/set on array supports real reserve() to pre-allocate\n * container buffer.\n *  2. Finding is array-based binary-search which should outperform map/set\n * because array is linear and much simpler.\n *  3. Memory layout is much better than node-based map and unordered_map\n * because elements are stored linearly and continuously.\n *  4. Compiled binary size is the same as map/set and smaller than\n * unordered_map/unordered_set.\n *  5. Struct type size(16b) is smaller than std::map, std::set,\n * std::unordered_map and std::unordered_set.\n *\n * Best use scenarios:\n * 1. Small amount of data, from dozens to hundreds.\n * 2. Low moving overhead for Key and Value, such as primitive types,\n * std::string, std::shared_ptr.\n * 3. More queries than data modifications.\n *\n * Please use the following specialized types:\n *   OrderedFlatMap\n *   InlineOrderedFlatMap\n *   OrderedFlatSet\n *   InlineOrderedFlatSet\n *\n * When implementing custom Compare method. Use f(const K&, const K&) as\n * signature to avoid unnecessary copy.\n *\n * Binary search array is ordered and does not support KeyPolicy with hash\n * value to boost finding right now.\n */\ntemplate <class K, class T, size_t N, class Compare, bool Stat>\nstruct BinarySearchArray : public KeyValueArray<K, T, void, N, Stat> {\n protected:\n  template <class K2, class T2, size_t N2, class Compare2, bool Stat2>\n  friend struct BinarySearchArray;\n\n  using MapStatisticsBase<Stat>::UpdateMaxCount;\n  using MapStatisticsBase<Stat>::IncreaseInsertCount;\n  using MapStatisticsBase<Stat>::IncreaseEraseCount;\n  using MapStatisticsBase<Stat>::RecordFind;\n\n  using KeyValueArray<K, T, void, N, Stat>::is_map;\n  using KeyValueArray<K, T, void, N, Stat>::array_;\n  using KeyValueArray<K, T, void, N, Stat>::_swap;\n\n public:\n  using key_compare = Compare;\n  using typename KeyValueArray<K, T, void, N, Stat>::key_extractor;\n  using typename KeyValueArray<K, T, void, N, Stat>::value_type;\n  using typename KeyValueArray<K, T, void, N, Stat>::store_value_type;\n  using typename KeyValueArray<K, T, void, N, Stat>::iterator;\n  using typename KeyValueArray<K, T, void, N, Stat>::const_iterator;\n  using typename KeyValueArray<K, T, void, N, Stat>::store_iterator;\n  using KeyValueArray<K, T, void, N, Stat>::begin;\n  using KeyValueArray<K, T, void, N, Stat>::end;\n  using KeyValueArray<K, T, void, N, Stat>::erase;\n\n public:\n  BinarySearchArray() {}\n  BinarySearchArray(std::initializer_list<value_type> list) {\n    array_.reserve(list.size());\n    for (auto& v : list) {\n      insert(std::move(v));\n    }\n  }\n\n  template <size_t N2>\n  BinarySearchArray(const BinarySearchArray<K, T, N2, Compare, Stat>& other)\n      : KeyValueArray<K, T, void, N, Stat>(other) {}\n\n  template <size_t N2>\n  BinarySearchArray(BinarySearchArray<K, T, N2, Compare, Stat>&& other)\n      : KeyValueArray<K, T, void, N, Stat>(std::move(other)) {}\n\n  template <size_t N2>\n  BinarySearchArray& operator=(\n      const BinarySearchArray<K, T, N2, Compare, Stat>& other) {\n    array_ = other.array_;\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  BinarySearchArray& operator=(\n      BinarySearchArray<K, T, N2, Compare, Stat>&& other) {\n    array_ = std::move(other.array_);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  std::pair<iterator, bool> insert(const value_type& value) {\n    if constexpr (is_map) {\n      auto pos = lower_bound(value.first);\n      if (BASE_VECTOR_UNLIKELY(pos != end() && pos->first == value.first)) {\n        RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n        return {pos, false};  // duplicate\n      } else {\n        RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n        return {\n            reinterpret_cast<iterator>(array_.emplace(\n                reinterpret_cast<store_iterator>(pos), std::piecewise_construct,\n                std::forward_as_tuple(value.first),\n                std::forward_as_tuple(value.second))),\n            true};\n      }\n    } else {\n      auto pos = lower_bound(value);\n      if (BASE_VECTOR_UNLIKELY(pos != end() && *pos == value)) {\n        RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n        return {pos, false};  // duplicate\n      } else {\n        RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n        return {reinterpret_cast<iterator>(array_.emplace(\n                    reinterpret_cast<store_iterator>(pos), value)),\n                true};\n      }\n    }\n  }\n\n  std::pair<iterator, bool> insert(value_type&& value) {\n    if constexpr (is_map) {\n      auto pos = lower_bound(value.first);\n      if (BASE_VECTOR_UNLIKELY(pos != end() && pos->first == value.first)) {\n        RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n        return {pos, false};  // duplicate\n      } else {\n        RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n        return {\n            reinterpret_cast<iterator>(array_.emplace(\n                reinterpret_cast<store_iterator>(pos), std::piecewise_construct,\n                std::forward_as_tuple(std::move(value.first)),\n                std::forward_as_tuple(std::move(value.second)))),\n            true};\n      }\n    } else {\n      auto pos = lower_bound(value);\n      if (BASE_VECTOR_UNLIKELY(pos != end() && *pos == value)) {\n        RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n        return {pos, false};  // duplicate\n      } else {\n        RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n        return {reinterpret_cast<iterator>(array_.emplace(\n                    reinterpret_cast<store_iterator>(pos), std::move(value))),\n                true};\n      }\n    }\n  }\n\n  // Without this overload, `std::pair<K, T> v; map.insert(std::move(v));` would\n  // implicitly construct `std::pair<const K, T>` from `std::move(v)`.\n  template <class U, class = std::enable_if_t<\n                         is_map && std::is_same_v<U, store_value_type>>>\n  std::pair<iterator, bool> insert(U&& value) {\n    auto pos = lower_bound(value.first);\n    if (BASE_VECTOR_UNLIKELY(pos != end() && pos->first == value.first)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      return {pos, false};  // duplicate\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      return {reinterpret_cast<iterator>(array_.emplace(\n                  reinterpret_cast<store_iterator>(pos), std::move(value))),\n              true};\n    }\n  }\n\n  size_t erase(const K& key) {\n    IncreaseEraseCount();\n    RecordFind(MapStatisticsFindKind::kFind, array_.size());\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      array_.erase(reinterpret_cast<store_iterator>(pos));\n      return 1;\n    } else {\n      return 0;\n    }\n  }\n\n  iterator find(const K& key) {\n    RecordFind(MapStatisticsFindKind::kFind, array_.size());\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      return pos;\n    } else {\n      return end();\n    }\n  }\n\n  const_iterator find(const K& key) const {\n    RecordFind(MapStatisticsFindKind::kFind, array_.size());\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      return pos;\n    } else {\n      return end();\n    }\n  }\n\n  bool contains(const K& key) const { return find(key) != end(); }\n\n  size_t count(const K& key) const { return contains(key) ? 1 : 0; }\n\n  template <size_t N2>\n  void swap(BinarySearchArray<K, T, N2, Compare, Stat>& other) {\n    _swap(other);\n  }\n\n  template <size_t N2>\n  void merge(BinarySearchArray<K, T, N2, Compare, Stat>& other) {\n    auto& other_array = other.array_;\n    for (intptr_t i = other_array.size() - 1; i >= 0; i--) {\n      if (insert(std::move(other_array[i])).second) {\n        other_array.erase(other_array.begin() + i);\n      }\n    }\n  }\n\n  bool is_data_ordered() const { return true; }\n\n  template <size_t N2>\n  bool operator==(\n      const BinarySearchArray<K, T, N2, Compare, Stat>& other) const {\n    return array_ == other.array_;\n  }\n\n  template <size_t N2>\n  bool operator!=(\n      const BinarySearchArray<K, T, N2, Compare, Stat>& other) const {\n    return array_ != other.array_;\n  }\n\n protected:\n  BASE_VECTOR_NEVER_INLINE iterator lower_bound(const K& key) const {\n    return const_cast<iterator>(std::lower_bound(\n        begin(), end(), key, [](const value_type& item, const K& value) {\n          return Compare()(key_extractor()(item), value);\n        }));\n  }\n};\n\n/**\n * @brief Used to implement map/set, but only provides map/set style interface,\n * internal data is stored in array linearly, and search also uses linear\n * search.\n *\n * It is very suitable for scenarios that require a map/set interface, but the\n * amount of data is relatively small, and the number of container traversals is\n * far greater than the number of searches.\n *\n * You could provide non-void KeyPolicy to boost key finding with hash value and\n * customize key equality check function.\n */\ntemplate <class K, class T, class KeyPolicy, size_t N, bool Stat>\nstruct LinearSearchArray : public KeyValueArray<K, T, KeyPolicy, N, Stat> {\n private:\n  template <typename AnyType>\n  struct type_identity {\n    using type = AnyType;\n  };\n\n  template <bool Flag>\n  struct KeyPolicyEqualHelper {\n    static auto select() {\n      if constexpr (Flag) {\n        return type_identity<typename KeyPolicy::equal>{};\n      } else {\n        return type_identity<std::equal_to<K>>{};\n      }\n    }\n  };\n\n  template <bool Flag>\n  struct KeyPolicyEqualWhenHashEqualHelper {\n    static auto select() {\n      if constexpr (Flag) {\n        return type_identity<typename KeyPolicy::equal_when_hash_equal>{};\n      } else {\n        return type_identity<std::equal_to<K>>{};\n      }\n    }\n  };\n\n  static constexpr bool get_assign_existing_for_merge() {\n    if constexpr (has_assign_existing_for_merge_member_v<KeyPolicy>) {\n      return KeyPolicy::assign_existing_for_merge;\n    } else {\n      return false;\n    }\n  }\n\n protected:\n  template <class K2, class T2, class KeyPolicy2, size_t N2, bool Stat2>\n  friend struct LinearSearchArray;\n\n  using MapStatisticsBase<Stat>::UpdateMaxCount;\n  using MapStatisticsBase<Stat>::IncreaseInsertCount;\n  using MapStatisticsBase<Stat>::IncreaseEraseCount;\n  using MapStatisticsBase<Stat>::RecordFind;\n\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::is_map;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::array_;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::_swap;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::_key_hash_data;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::value_begin;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::value_end;\n\n public:\n  static constexpr auto assign_existing_for_merge =\n      get_assign_existing_for_merge();\n\n  using key_compare = void;\n  using key_equal =\n      typename decltype(KeyPolicyEqualHelper<\n                        has_equal_member_v<KeyPolicy>>::select())::type;\n  using key_equal_when_hash_equal =\n      typename decltype(KeyPolicyEqualWhenHashEqualHelper<\n                        has_equal_when_hash_equal_member_v<KeyPolicy>>::\n                            select())::type;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::key_extractor;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::value_extractor;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::is_key_hash;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::consecutive_key;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::insert_value_type;\n  using typename KeyValueArray<K, T, KeyPolicy, N,\n                               Stat>::non_const_insert_value_type;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::value_type;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::store_value_type;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::iterator;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::const_iterator;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::store_iterator;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::range_loop_iterator;\n  using typename KeyValueArray<K, T, KeyPolicy, N,\n                               Stat>::range_loop_const_iterator;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::begin;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::end;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::erase;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::empty;\n\n protected:\n  inline static KeyPolicyReducedHashValueType _reduced_key_hash(const K& key) {\n    if constexpr (is_key_hash) {\n      return static_cast<KeyPolicyReducedHashValueType>(\n          typename KeyPolicy::hash()(key));\n    } else {\n      return KeyPolicyReducedHashValueType{};\n    }\n  }\n\n public:\n  LinearSearchArray() {}\n\n  LinearSearchArray(std::initializer_list<insert_value_type> list) {\n    array_.reserve(list.size());\n    for (auto& v : list) {\n      insert(std::move(v));\n    }\n  }\n\n  // Only when not using hash, the internal array can be moved from source.\n  // Because we are using extra leading bytes of array buffer to store hash\n  // values.\n  template <size_t ExtraBytesPerElement, bool CountRealloc,\n            typename = std::enable_if_t<ExtraBytesPerElement == 0>>\n  LinearSearchArray(Vector<store_value_type, ExtraBytesPerElement,\n                           CountRealloc>&& source_array)\n      : KeyValueArray<K, T, KeyPolicy, N, Stat>(std::move(source_array)) {\n    static_assert(!is_key_hash);\n  }\n\n  template <size_t ExtraBytesPerElement, bool CountRealloc,\n            typename = std::enable_if_t<ExtraBytesPerElement == 0>>\n  LinearSearchArray& operator=(Vector<store_value_type, ExtraBytesPerElement,\n                                      CountRealloc>&& source_array) {\n    static_assert(!is_key_hash);\n    array_ = std::move(source_array);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  LinearSearchArray(const LinearSearchArray<K, T, KeyPolicy, N2, Stat>& other)\n      : KeyValueArray<K, T, KeyPolicy, N, Stat>(other) {}\n\n  template <size_t N2>\n  LinearSearchArray(LinearSearchArray<K, T, KeyPolicy, N2, Stat>&& other)\n      : KeyValueArray<K, T, KeyPolicy, N, Stat>(std::move(other)) {}\n\n  template <size_t N2>\n  LinearSearchArray& operator=(\n      const LinearSearchArray<K, T, KeyPolicy, N2, Stat>& other) {\n    array_ = other.array_;\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  LinearSearchArray& operator=(\n      LinearSearchArray<K, T, KeyPolicy, N2, Stat>&& other) {\n    array_ = std::move(other.array_);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  // No check of key existence.\n  iterator insert_unique(const insert_value_type& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key_extractor()(value));\n    }\n\n    iterator result;\n    if constexpr (is_map) {\n      if constexpr (consecutive_key) {\n        result = reinterpret_cast<iterator>(&array_.emplace_back(value.second));\n      } else {\n        result = reinterpret_cast<iterator>(&array_.emplace_back(\n            std::piecewise_construct, std::forward_as_tuple(value.first),\n            std::forward_as_tuple(value.second)));\n      }\n    } else {\n      result = reinterpret_cast<iterator>(&array_.emplace_back(value));\n    }\n    if constexpr (is_key_hash) {\n      // Store hash value after array insertion because array may expand.\n      _key_hash_data()[array_.size() - 1] = hash;\n    }\n    return result;\n  }\n\n  iterator insert_unique(insert_value_type&& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key_extractor()(value));\n    }\n\n    iterator result;\n    if constexpr (is_map) {\n      if constexpr (consecutive_key) {\n        result = reinterpret_cast<iterator>(\n            &array_.emplace_back(std::move(value.second)));\n      } else {\n        result = reinterpret_cast<iterator>(&array_.emplace_back(\n            std::piecewise_construct,\n            std::forward_as_tuple(std::move(value.first)),\n            std::forward_as_tuple(std::move(value.second))));\n      }\n    } else {\n      result =\n          reinterpret_cast<iterator>(&array_.emplace_back(std::move(value)));\n    }\n    if constexpr (is_key_hash) {\n      // Store hash value after array insertion because array may expand.\n      _key_hash_data()[array_.size() - 1] = hash;\n    }\n    return result;\n  }\n\n  template <bool enabled = is_map>\n  std::enable_if_t<enabled, iterator> insert_unique(\n      const non_const_insert_value_type& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key_extractor()(value));\n    }\n\n    iterator result;\n    if constexpr (consecutive_key) {\n      result = reinterpret_cast<iterator>(&array_.emplace_back(value.second));\n    } else {\n      result = reinterpret_cast<iterator>(&array_.emplace_back(\n          std::piecewise_construct, std::forward_as_tuple(value.first),\n          std::forward_as_tuple(value.second)));\n    }\n    if constexpr (is_key_hash) {\n      // Store hash value after array insertion because array may expand.\n      _key_hash_data()[array_.size() - 1] = hash;\n    }\n    return result;\n  }\n\n  template <bool enabled = is_map>\n  std::enable_if_t<enabled, iterator> insert_unique(\n      non_const_insert_value_type&& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key_extractor()(value));\n    }\n\n    iterator result;\n    if constexpr (consecutive_key) {\n      result = reinterpret_cast<iterator>(\n          &array_.emplace_back(std::move(value.second)));\n    } else {\n      result = reinterpret_cast<iterator>(\n          &array_.emplace_back(std::piecewise_construct,\n                               std::forward_as_tuple(std::move(value.first)),\n                               std::forward_as_tuple(std::move(value.second))));\n    }\n    if constexpr (is_key_hash) {\n      // Store hash value after array insertion because array may expand.\n      _key_hash_data()[array_.size() - 1] = hash;\n    }\n    return result;\n  }\n\n  std::pair<iterator, bool> insert(const insert_value_type& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key_extractor()(value));\n      pos = find_exact(key_extractor()(value),\n                       hash);  // find with hash value\n    } else {\n      pos = find_exact(key_extractor()(value));\n    }\n\n    std::pair<iterator, bool> result;\n    if (BASE_VECTOR_UNLIKELY(pos != nullptr)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};  // duplicate\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (is_map) {\n        if constexpr (consecutive_key) {\n          result = {\n              reinterpret_cast<iterator>(&array_.emplace_back(value.second)),\n              true};\n        } else {\n          result = {\n              reinterpret_cast<iterator>(&array_.emplace_back(\n                  std::piecewise_construct, std::forward_as_tuple(value.first),\n                  std::forward_as_tuple(value.second))),\n              true};\n        }\n      } else {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back(value)),\n                  true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  std::pair<iterator, bool> insert(insert_value_type&& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key_extractor()(value));\n      pos = find_exact(key_extractor()(value),\n                       hash);  // find with hash value\n    } else {\n      pos = find_exact(key_extractor()(value));\n    }\n\n    std::pair<iterator, bool> result;\n    if (BASE_VECTOR_UNLIKELY(pos != nullptr)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};  // duplicate\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (is_map) {\n        if constexpr (consecutive_key) {\n          result = {reinterpret_cast<iterator>(\n                        &array_.emplace_back(std::move(value.second))),\n                    true};\n        } else {\n          result = {reinterpret_cast<iterator>(&array_.emplace_back(\n                        std::piecewise_construct,\n                        std::forward_as_tuple(std::move(value.first)),\n                        std::forward_as_tuple(std::move(value.second)))),\n                    true};\n        }\n      } else {\n        result = {\n            reinterpret_cast<iterator>(&array_.emplace_back(std::move(value))),\n            true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  template <bool enabled = is_map>\n  std::enable_if_t<enabled, std::pair<iterator, bool>> insert(\n      const non_const_insert_value_type& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key_extractor()(value));\n      pos = find_exact(key_extractor()(value),\n                       hash);  // find with hash value\n    } else {\n      pos = find_exact(key_extractor()(value));\n    }\n\n    std::pair<iterator, bool> result;\n    if (BASE_VECTOR_UNLIKELY(pos != nullptr)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};  // duplicate\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {\n            reinterpret_cast<iterator>(&array_.emplace_back(value.second)),\n            true};\n      } else {\n        result = {\n            reinterpret_cast<iterator>(&array_.emplace_back(\n                std::piecewise_construct, std::forward_as_tuple(value.first),\n                std::forward_as_tuple(value.second))),\n            true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  template <bool enabled = is_map>\n  std::enable_if_t<enabled, std::pair<iterator, bool>> insert(\n      non_const_insert_value_type&& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key_extractor()(value));\n      pos = find_exact(key_extractor()(value),\n                       hash);  // find with hash value\n    } else {\n      pos = find_exact(key_extractor()(value));\n    }\n\n    std::pair<iterator, bool> result;\n    if (BASE_VECTOR_UNLIKELY(pos != nullptr)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};  // duplicate\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {reinterpret_cast<iterator>(\n                      &array_.emplace_back(std::move(value.second))),\n                  true};\n      } else {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back(\n                      std::piecewise_construct,\n                      std::forward_as_tuple(std::move(value.first)),\n                      std::forward_as_tuple(std::move(value.second)))),\n                  true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  size_t erase(const K& key) {\n    RecordFind(MapStatisticsFindKind::kFind, array_.size());\n    iterator pos;\n    if constexpr (is_key_hash) {\n      auto hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n    if (pos != nullptr) {\n      erase(pos);\n      return 1;\n    } else {\n      return 0;\n    }\n  }\n\n  range_loop_iterator find(const K& key) {\n    RecordFind(MapStatisticsFindKind::kFind, array_.size());\n    iterator pos;\n    if constexpr (is_key_hash) {\n      auto hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n    if (pos != nullptr) {\n      if constexpr (is_map && consecutive_key) {\n        return range_loop_iterator(_key_hash_data() + (pos - value_begin()),\n                                   pos);\n      } else {\n        return pos;\n      }\n    } else {\n      return end();\n    }\n  }\n\n  range_loop_const_iterator find(const K& key) const {\n    RecordFind(MapStatisticsFindKind::kFind, array_.size());\n    iterator pos;\n    if constexpr (is_key_hash) {\n      auto hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n    if (pos != nullptr) {\n      if constexpr (is_map && consecutive_key) {\n        return range_loop_const_iterator(\n            _key_hash_data() + (pos - value_begin()), pos);\n      } else {\n        return pos;\n      }\n    } else {\n      return end();\n    }\n  }\n\n  bool contains(const K& key) const {\n    RecordFind(MapStatisticsFindKind::kFind, array_.size());\n    if constexpr (is_key_hash) {\n      auto hash = _reduced_key_hash(key);\n      return find_exact(key, hash) != nullptr;  // find with hash value\n    } else {\n      return find_exact(key) != nullptr;\n    }\n  }\n\n  size_t count(const K& key) const { return contains(key) ? 1 : 0; }\n\n  template <size_t N2>\n  void swap(LinearSearchArray<K, T, KeyPolicy, N2, Stat>& other) {\n    _swap(other);\n  }\n\n  template <size_t N2>\n  void merge(const LinearSearchArray<K, T, KeyPolicy, N2, Stat>& other) {\n    merge(const_cast<LinearSearchArray<K, T, KeyPolicy, N2, Stat>&>(other));\n  }\n\n  template <size_t N2>\n  void merge(LinearSearchArray<K, T, KeyPolicy, N2, Stat>& other) {\n    auto& other_array = other.array_;\n    if (empty()) {\n      // Fast path when self is empty.\n      if constexpr (assign_existing_for_merge) {\n        array_ = other_array;\n      } else {\n        array_ = std::move(other_array);\n      }\n      return;\n    }\n\n    [[maybe_unused]] const KeyPolicyReducedHashValueType* other_key_hash_data;\n    if constexpr (is_key_hash) {\n      other_key_hash_data = other._key_hash_data();\n    }\n    // LinearSearchArray has an implicit property: the iteration order is\n    // consistent with the data insertion order. To ensure this property is not\n    // violated, the `other` container must also be traversed from front to back\n    // during the `merge` operation.\n    size_t other_size = other_array.size();\n    for (size_t i = 0; i < other_size;) {\n      auto& object = other_array[i];\n      [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n      iterator pos;\n      if constexpr (is_key_hash) {\n        hash = other_key_hash_data[i];\n        if constexpr (consecutive_key) {\n          pos = find_exact(static_cast<K>(hash), hash);\n        } else {\n          pos = find_exact(key_extractor()(object), hash);\n        }\n      } else {\n        pos = find_exact(key_extractor()(object));\n      }\n      if constexpr (assign_existing_for_merge) {\n        // If KeyPolicy specifies assign_existing_for_merge as true, for data\n        // that already exists in this, do assignment and do not modify other.\n        if (pos == nullptr) {\n          array_.emplace_back(object);\n          if constexpr (is_key_hash) {\n            // Store hash value after array insertion because array may expand.\n            _key_hash_data()[array_.size() - 1] = hash;\n          }\n        } else if constexpr (is_map) {\n          value_extractor()(*pos) = value_extractor()(object);\n        }\n      } else {\n        // Behavior like std::unordered_map, splice from other.\n        if (pos == nullptr) {\n          array_.emplace_back(std::move(object));\n          if constexpr (is_key_hash) {\n            // Store hash value after array insertion because array may expand.\n            _key_hash_data()[array_.size() - 1] = hash;\n          }\n          other.erase(other.value_begin() + i);\n          other_size--;\n          continue;\n        }\n      }\n      i++;\n    }\n  }\n\n  bool is_data_ordered() const { return false; }\n\n  template <size_t N2>\n  bool operator==(\n      const LinearSearchArray<K, T, KeyPolicy, N2, Stat>& other) const {\n    return array_ == other.array_;\n  }\n\n  template <size_t N2>\n  bool operator!=(\n      const LinearSearchArray<K, T, KeyPolicy, N2, Stat>& other) const {\n    return array_ != other.array_;\n  }\n\n protected:\n  BASE_VECTOR_INLINE iterator find_exact(const K& key) const {\n    for (auto it = array_.begin(), end = array_.end(); it != end; it++) {\n      if (key_equal()(key_extractor()(*it), key)) {\n        return reinterpret_cast<iterator>(const_cast<store_iterator>(it));\n      }\n    }\n    return nullptr;\n  }\n\n  template <bool enabled = is_key_hash>\n  std::enable_if_t<enabled, iterator> find_exact(\n      const K& key, KeyPolicyReducedHashValueType hash) const {\n    // Boosted find with hash value when KeyPolicy provided.\n    const auto* key_hash_data = _key_hash_data();\n    const intptr_t count = static_cast<intptr_t>(array_.size());\n    auto begin = array_.begin();\n    intptr_t i = 0;\n\n#define BASE_VECTOR_CHECK_KEY_WHEN_HASH_EQUAL                            \\\n  if constexpr (consecutive_key) {                                       \\\n    return reinterpret_cast<iterator>(const_cast<store_iterator>(it));   \\\n  } else {                                                               \\\n    if (key_equal_when_hash_equal()(key_extractor()(*it), key)) {        \\\n      return reinterpret_cast<iterator>(const_cast<store_iterator>(it)); \\\n    }                                                                    \\\n  }\n\n#ifdef BASE_VECTOR_NEON\n    const uint32x4_t target_vec = vdupq_n_u32(hash);\n    for (; i <= count - 4; i += 4) {\n      uint32x4_t data_vec = vld1q_u32(key_hash_data + i);\n      uint32x4_t mask = vceqq_u32(data_vec, target_vec);\n\n#ifdef BASE_VECTOR_NEON_ARMV8\n      // Using armv8, pre-checking once for existence is faster than checking\n      // twice.\n      if (vmaxvq_u32(mask) != 0)\n#endif\n      {\n        if (auto pair = vgetq_lane_u64(mask, 0); pair != 0) {\n          if ((uint32_t)pair != 0) {\n            auto it = begin + i + 0;\n            BASE_VECTOR_CHECK_KEY_WHEN_HASH_EQUAL\n          }\n          if ((pair >> 32) != 0) {\n            auto it = begin + i + 1;\n            BASE_VECTOR_CHECK_KEY_WHEN_HASH_EQUAL\n          }\n        }\n        if (auto pair = vgetq_lane_u64(mask, 1); pair != 0) {\n          if ((uint32_t)pair != 0) {\n            auto it = begin + i + 2;\n            BASE_VECTOR_CHECK_KEY_WHEN_HASH_EQUAL\n          }\n          if ((pair >> 32) != 0) {\n            auto it = begin + i + 3;\n            BASE_VECTOR_CHECK_KEY_WHEN_HASH_EQUAL\n          }\n        }\n      }\n    }\n#elif defined(BASE_VECTOR_SSE2)\n    const __m128i target_vec = _mm_set1_epi32(static_cast<int>(hash));\n    for (; i <= count - 4; i += 4) {\n      __m128i data_vec =\n          _mm_loadu_si128(reinterpret_cast<const __m128i*>(key_hash_data + i));\n      __m128i mask = _mm_cmpeq_epi32(data_vec, target_vec);\n      int movemask_result = _mm_movemask_ps(_mm_castsi128_ps(mask));\n      if (movemask_result != 0) {\n        int temp_mask = movemask_result;\n        while (temp_mask != 0) {\n          int j = unchecked_count_right_zero(temp_mask);\n          auto it = begin + i + j;\n          BASE_VECTOR_CHECK_KEY_WHEN_HASH_EQUAL\n          // Clear this bit and goto the next iteration.\n          temp_mask &= (temp_mask - 1);\n        }\n      }\n    }\n#else\n    // No SIMD supports, fall-through\n#endif\n\n    for (; i < count; ++i) {\n      if (key_hash_data[i] == hash) {\n        auto it = begin + i;\n        BASE_VECTOR_CHECK_KEY_WHEN_HASH_EQUAL\n      }\n    }\n    return nullptr;\n  }\n#undef BASE_VECTOR_CHECK_KEY_WHEN_HASH_EQUAL\n};\n\ntemplate <class K, class T, size_t N, class Compare, bool Stat>\nstruct BinarySearchMap : public BinarySearchArray<K, T, N, Compare, Stat> {\n protected:\n  template <class K2, class T2, size_t N2, class Compare2, bool Stat2>\n  friend struct BinarySearchMap;\n\n  using MapStatisticsBase<Stat>::UpdateMaxCount;\n  using MapStatisticsBase<Stat>::IncreaseInsertCount;\n  using MapStatisticsBase<Stat>::IncreaseEraseCount;\n  using MapStatisticsBase<Stat>::RecordFind;\n\n  using KeyValueArray<K, T, void, N, Stat>::array_;\n\n public:\n  using typename KeyValueArray<K, T, void, N, Stat>::key_extractor;\n  using typename BinarySearchArray<K, T, N, Compare, Stat>::value_type;\n  using typename BinarySearchArray<K, T, N, Compare, Stat>::iterator;\n  using typename BinarySearchArray<K, T, N, Compare, Stat>::store_iterator;\n  using BinarySearchArray<K, T, N, Compare, Stat>::insert;\n  using BinarySearchArray<K, T, N, Compare, Stat>::lower_bound;\n  using KeyValueArray<K, T, void, N, Stat>::end;\n\n  BinarySearchMap() {}\n  BinarySearchMap(std::initializer_list<value_type> list) {\n    array_.reserve(list.size());\n    for (auto& v : list) {\n      insert(std::move(v));\n    }\n  }\n\n  template <size_t N2>\n  BinarySearchMap(const BinarySearchMap<K, T, N2, Compare, Stat>& other)\n      : BinarySearchArray<K, T, N, Compare, Stat>(other) {}\n\n  template <size_t N2>\n  BinarySearchMap(BinarySearchMap<K, T, N2, Compare, Stat>&& other)\n      : BinarySearchArray<K, T, N, Compare, Stat>(std::move(other)) {}\n\n  template <size_t N2>\n  BinarySearchMap& operator=(\n      const BinarySearchMap<K, T, N2, Compare, Stat>& other) {\n    array_ = other.array_;\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  BinarySearchMap& operator=(BinarySearchMap<K, T, N2, Compare, Stat>&& other) {\n    array_ = std::move(other.array_);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <class U>\n  T& operator[](U&& key) {\n    return try_insert(std::forward<U>(key)).first->second;\n  }\n\n  template <class U>\n  T& at(U&& key) {\n    return try_insert(std::forward<U>(key)).first->second;\n  }\n\n  template <class U>\n  std::pair<iterator, bool> insert_default_if_absent(U&& key) {\n    return try_insert(std::forward<U>(key));\n  }\n\n  template <class V>\n  std::pair<iterator, bool> insert_or_assign(const K& key, V&& value) {\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      pos->second = std::forward<V>(value);\n      return {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      return {reinterpret_cast<iterator>(array_.emplace(\n                  reinterpret_cast<store_iterator>(pos),\n                  std::piecewise_construct, std::forward_as_tuple(key),\n                  std::forward_as_tuple(std::forward<V>(value)))),\n              true};\n    }\n  }\n\n  template <class V>\n  std::pair<iterator, bool> insert_or_assign(K&& key, V&& value) {\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      pos->second = std::forward<V>(value);\n      return {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      return {\n          reinterpret_cast<iterator>(array_.emplace(\n              reinterpret_cast<store_iterator>(pos), std::piecewise_construct,\n              std::forward_as_tuple(std::move(key)),\n              std::forward_as_tuple(std::forward<V>(value)))),\n          true};\n    }\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace(const K& key, Args&&... args) {\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      return {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      return {reinterpret_cast<iterator>(array_.emplace(\n                  reinterpret_cast<store_iterator>(pos),\n                  std::piecewise_construct, std::forward_as_tuple(key),\n                  std::forward_as_tuple(std::forward<Args>(args)...))),\n              true};\n    }\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace(K&& key, Args&&... args) {\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      return {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      return {\n          reinterpret_cast<iterator>(array_.emplace(\n              reinterpret_cast<store_iterator>(pos), std::piecewise_construct,\n              std::forward_as_tuple(std::move(key)),\n              std::forward_as_tuple(std::forward<Args>(args)...))),\n          true};\n    }\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace_or_assign(const K& key, Args&&... args) {\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      pos->second.~T();\n      new (&(pos->second)) T(std::forward<Args>(args)...);\n      return {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      return {reinterpret_cast<iterator>(array_.emplace(\n                  reinterpret_cast<store_iterator>(pos),\n                  std::piecewise_construct, std::forward_as_tuple(key),\n                  std::forward_as_tuple(std::forward<Args>(args)...))),\n              true};\n    }\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace_or_assign(K&& key, Args&&... args) {\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      pos->second.~T();\n      new (&(pos->second)) T(std::forward<Args>(args)...);\n      return {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      return {\n          reinterpret_cast<iterator>(array_.emplace(\n              reinterpret_cast<store_iterator>(pos), std::piecewise_construct,\n              std::forward_as_tuple(std::move(key)),\n              std::forward_as_tuple(std::forward<Args>(args)...))),\n          true};\n    }\n  }\n\n  template <class U, class... Args>\n  std::pair<iterator, bool> try_emplace(U&& key, Args&&... args) {\n    return emplace(std::forward<U>(key), std::forward<Args>(args)...);\n  }\n\n  template <typename... Args1, typename... Args2>\n  std::pair<iterator, bool> emplace(std::piecewise_construct_t,\n                                    std::tuple<Args1...> args1,\n                                    std::tuple<Args2...> args2) {\n    // Construct key in first place.\n    K key = std::make_from_tuple<K>(std::move(args1));\n\n    // Do emplace.\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      return {pos, false};  // key is discarded\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      return {\n          reinterpret_cast<iterator>(array_.emplace(\n              reinterpret_cast<store_iterator>(pos), std::piecewise_construct,\n              std::forward_as_tuple(std::move(key)), std::move(args2))),\n          true};\n    }\n  }\n\n  template <size_t N2>\n  bool operator==(const BinarySearchMap<K, T, N2, Compare, Stat>& other) const {\n    return array_ == other.array_;\n  }\n\n  template <size_t N2>\n  bool operator!=(const BinarySearchMap<K, T, N2, Compare, Stat>& other) const {\n    return array_ != other.array_;\n  }\n\n protected:\n  std::pair<iterator, bool> try_insert(const K& key) {\n    std::pair<iterator, bool> result;\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      result = {\n          reinterpret_cast<iterator>(array_.emplace(\n              reinterpret_cast<store_iterator>(pos), std::piecewise_construct,\n              std::forward_as_tuple(key), std::tuple<>())),\n          true};\n    }\n    return result;\n  }\n\n  std::pair<iterator, bool> try_insert(K&& key) {\n    std::pair<iterator, bool> result;\n    auto pos = lower_bound(key);\n    if (pos != end() && key_extractor()(*pos) == key) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      result = {\n          reinterpret_cast<iterator>(array_.emplace(\n              reinterpret_cast<store_iterator>(pos), std::piecewise_construct,\n              std::forward_as_tuple(std::move(key)), std::tuple<>())),\n          true};\n    }\n    return result;\n  }\n};\n\ntemplate <class K, size_t N, class Compare, bool Stat>\nstruct BinarySearchSet : public BinarySearchArray<K, void, N, Compare, Stat> {\n protected:\n  template <class K2, size_t N2, class Compare2, bool Stat2>\n  friend struct BinarySearchSet;\n\n  using MapStatisticsBase<Stat>::UpdateMaxCount;\n\n  using BinarySearchArray<K, void, N, Compare, Stat>::array_;\n\n public:\n  using typename BinarySearchArray<K, void, N, Compare, Stat>::value_type;\n  using typename BinarySearchArray<K, void, N, Compare, Stat>::iterator;\n  using BinarySearchArray<K, void, N, Compare, Stat>::insert;\n\n  BinarySearchSet() {}\n  BinarySearchSet(std::initializer_list<value_type> list) {\n    array_.reserve(list.size());\n    for (auto& v : list) {\n      insert(std::move(v));\n    }\n  }\n\n  template <size_t N2>\n  BinarySearchSet(const BinarySearchSet<K, N2, Compare, Stat>& other)\n      : BinarySearchArray<K, void, N, Compare, Stat>(other) {}\n\n  template <size_t N2>\n  BinarySearchSet(BinarySearchSet<K, N2, Compare, Stat>&& other)\n      : BinarySearchArray<K, void, N, Compare, Stat>(std::move(other)) {}\n\n  template <size_t N2>\n  BinarySearchSet& operator=(\n      const BinarySearchSet<K, N2, Compare, Stat>& other) {\n    array_ = other.array_;\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  BinarySearchSet& operator=(BinarySearchSet<K, N2, Compare, Stat>&& other) {\n    array_ = std::move(other.array_);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace(Args&&... args) {\n    return insert(K(std::forward<Args>(args)...));\n  }\n\n  template <size_t N2>\n  bool operator==(const BinarySearchSet<K, N2, Compare, Stat>& other) const {\n    return array_ == other.array_;\n  }\n\n  template <size_t N2>\n  bool operator!=(const BinarySearchSet<K, N2, Compare, Stat>& other) const {\n    return array_ != other.array_;\n  }\n};\n\ntemplate <class K, class T, class KeyPolicy, size_t N, bool Stat>\nstruct LinearSearchMap : public LinearSearchArray<K, T, KeyPolicy, N, Stat> {\n protected:\n  template <class K2, class T2, class KeyPolicy2, size_t N2, bool Stat2>\n  friend struct LinearSearchMap;\n\n  using MapStatisticsBase<Stat>::UpdateMaxCount;\n  using MapStatisticsBase<Stat>::IncreaseInsertCount;\n  using MapStatisticsBase<Stat>::IncreaseEraseCount;\n  using MapStatisticsBase<Stat>::RecordFind;\n\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::array_;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::_key_hash_data;\n  using typename KeyValueArray<K, T, KeyPolicy, N, Stat>::value_extractor;\n\n  using LinearSearchArray<K, T, KeyPolicy, N, Stat>::_reduced_key_hash;\n\n public:\n  using LinearSearchArray<K, T, KeyPolicy, N, Stat>::is_key_hash;\n  using LinearSearchArray<K, T, KeyPolicy, N, Stat>::consecutive_key;\n  using typename LinearSearchArray<K, T, KeyPolicy, N, Stat>::insert_value_type;\n  using typename LinearSearchArray<K, T, KeyPolicy, N, Stat>::value_type;\n  using typename LinearSearchArray<K, T, KeyPolicy, N, Stat>::store_value_type;\n  using typename LinearSearchArray<K, T, KeyPolicy, N, Stat>::iterator;\n  using typename LinearSearchArray<K, T, KeyPolicy, N, Stat>::store_iterator;\n  using LinearSearchArray<K, T, KeyPolicy, N, Stat>::insert;\n  using LinearSearchArray<K, T, KeyPolicy, N, Stat>::find_exact;\n  using KeyValueArray<K, T, KeyPolicy, N, Stat>::end;\n\n  LinearSearchMap() {}\n  LinearSearchMap(std::initializer_list<insert_value_type> list) {\n    array_.reserve(list.size());\n    for (auto& v : list) {\n      insert(std::move(v));\n    }\n  }\n\n  // Only when not using hash, the internal array can be moved from source.\n  // Because we are using extra leading bytes of array buffer to store hash\n  // values.\n  template <size_t ExtraBytesPerElement, bool CountRealloc,\n            typename = std::enable_if_t<ExtraBytesPerElement == 0>>\n  LinearSearchMap(Vector<store_value_type, ExtraBytesPerElement, CountRealloc>&&\n                      source_array)\n      : LinearSearchArray<K, T, KeyPolicy, N, Stat>(std::move(source_array)) {\n    static_assert(!is_key_hash);\n  }\n\n  template <size_t ExtraBytesPerElement, bool CountRealloc,\n            typename = std::enable_if_t<ExtraBytesPerElement == 0>>\n  LinearSearchMap& operator=(Vector<store_value_type, ExtraBytesPerElement,\n                                    CountRealloc>&& source_array) {\n    static_assert(!is_key_hash);\n    array_ = std::move(source_array);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  LinearSearchMap(const LinearSearchMap<K, T, KeyPolicy, N2, Stat>& other)\n      : LinearSearchArray<K, T, KeyPolicy, N, Stat>(other) {}\n\n  template <size_t N2>\n  LinearSearchMap(LinearSearchMap<K, T, KeyPolicy, N2, Stat>&& other)\n      : LinearSearchArray<K, T, KeyPolicy, N, Stat>(std::move(other)) {}\n\n  template <size_t N2>\n  LinearSearchMap& operator=(\n      const LinearSearchMap<K, T, KeyPolicy, N2, Stat>& other) {\n    array_ = other.array_;\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  LinearSearchMap& operator=(\n      LinearSearchMap<K, T, KeyPolicy, N2, Stat>&& other) {\n    array_ = std::move(other.array_);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <class U>\n  T& operator[](U&& key) {\n    return value_extractor()(*try_insert(std::forward<U>(key)).first);\n  }\n\n  template <class U>\n  T& at(U&& key) {\n    return value_extractor()(*try_insert(std::forward<U>(key)).first);\n  }\n\n  template <class U>\n  std::pair<iterator, bool> insert_default_if_absent(U&& key) {\n    return try_insert(std::forward<U>(key));\n  }\n\n  template <class U>\n  std::pair<iterator, bool> insert_if_absent(U&& key, const T& obj) {\n    auto result = try_insert(std::forward<U>(key));\n    if (result.second) {\n      // Insertion took place and that means key not exists before.\n      value_extractor()(*result.first) = obj;\n    }\n    return result;\n  }\n\n  template <class U>\n  std::pair<iterator, bool> insert_if_absent(U&& key, T&& obj) {\n    auto result = try_insert(std::forward<U>(key));\n    if (result.second) {\n      // Insertion took place and that means key not exists before.\n      value_extractor()(*result.first) = std::move(obj);\n    }\n    return result;\n  }\n\n  template <class V>\n  std::pair<iterator, bool> insert_or_assign(const K& key, V&& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n\n    std::pair<iterator, bool> result;\n    if (pos != nullptr) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      value_extractor()(*pos) = std::forward<V>(value);\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {reinterpret_cast<iterator>(\n                      &array_.emplace_back(std::forward<V>(value))),\n                  true};\n      } else {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back(\n                      std::piecewise_construct, std::forward_as_tuple(key),\n                      std::forward_as_tuple(std::forward<V>(value)))),\n                  true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  template <class V>\n  std::pair<iterator, bool> insert_or_assign(K&& key, V&& value) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n\n    std::pair<iterator, bool> result;\n    if (pos != nullptr) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      value_extractor()(*pos) = std::forward<V>(value);\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {reinterpret_cast<iterator>(\n                      &array_.emplace_back(std::forward<V>(value))),\n                  true};\n      } else {\n        result = {\n            reinterpret_cast<iterator>(&array_.emplace_back(\n                std::piecewise_construct, std::forward_as_tuple(std::move(key)),\n                std::forward_as_tuple(std::forward<V>(value)))),\n            true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  // No check of key existence.\n  template <class... Args>\n  iterator emplace_unique(const K& key, Args&&... args) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n    }\n\n    iterator result;\n    if constexpr (consecutive_key) {\n      result = reinterpret_cast<iterator>(\n          &array_.emplace_back(std::forward<Args>(args)...));\n    } else {\n      result = reinterpret_cast<iterator>(&array_.emplace_back(\n          std::piecewise_construct, std::forward_as_tuple(key),\n          std::forward_as_tuple(std::forward<Args>(args)...)));\n    }\n    if constexpr (is_key_hash) {\n      // Store hash value after array insertion because array may expand.\n      _key_hash_data()[array_.size() - 1] = hash;\n    }\n    return result;\n  }\n\n  template <class... Args>\n  iterator emplace_unique(K&& key, Args&&... args) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n    }\n\n    iterator result;\n    if constexpr (consecutive_key) {\n      result = reinterpret_cast<iterator>(\n          &array_.emplace_back(std::forward<Args>(args)...));\n    } else {\n      result = reinterpret_cast<iterator>(&array_.emplace_back(\n          std::piecewise_construct, std::forward_as_tuple(std::move(key)),\n          std::forward_as_tuple(std::forward<Args>(args)...)));\n    }\n    if constexpr (is_key_hash) {\n      // Store hash value after array insertion because array may expand.\n      _key_hash_data()[array_.size() - 1] = hash;\n    }\n    return result;\n  }\n\n  template <typename... Args1, typename... Args2>\n  iterator emplace_unique(std::piecewise_construct_t,\n                          std::tuple<Args1...> args1,\n                          std::tuple<Args2...> args2) {\n    // Construct key in first place and find.\n    K key = std::make_from_tuple<K>(std::move(args1));\n\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n    }\n\n    // Do emplace.\n    iterator result;\n    if constexpr (consecutive_key) {\n      std::apply(\n          [&](auto&&... unpacked_args) {\n            result = reinterpret_cast<iterator>(&array_.emplace_back(\n                std::forward<decltype(unpacked_args)>(unpacked_args)...));\n          },\n          std::move(args2));\n    } else {\n      result = reinterpret_cast<iterator>(&array_.emplace_back(\n          std::piecewise_construct, std::forward_as_tuple(std::move(key)),\n          std::move(args2)));\n    }\n    if constexpr (is_key_hash) {\n      // Store hash value after array insertion because array may expand.\n      _key_hash_data()[array_.size() - 1] = hash;\n    }\n    return result;\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace(const K& key, Args&&... args) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n\n    std::pair<iterator, bool> result;\n    if (BASE_VECTOR_UNLIKELY(pos != nullptr)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {reinterpret_cast<iterator>(\n                      &array_.emplace_back(std::forward<Args>(args)...)),\n                  true};\n      } else {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back(\n                      std::piecewise_construct, std::forward_as_tuple(key),\n                      std::forward_as_tuple(std::forward<Args>(args)...))),\n                  true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace(K&& key, Args&&... args) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n\n    std::pair<iterator, bool> result;\n    if (BASE_VECTOR_UNLIKELY(pos != nullptr)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {reinterpret_cast<iterator>(\n                      &array_.emplace_back(std::forward<Args>(args)...)),\n                  true};\n      } else {\n        result = {\n            reinterpret_cast<iterator>(&array_.emplace_back(\n                std::piecewise_construct, std::forward_as_tuple(std::move(key)),\n                std::forward_as_tuple(std::forward<Args>(args)...))),\n            true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace_or_assign(const K& key, Args&&... args) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n\n    std::pair<iterator, bool> result;\n    if (BASE_VECTOR_UNLIKELY(pos != nullptr)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      auto& v = value_extractor()(*pos);\n      v.~T();\n      new (&v) T(std::forward<Args>(args)...);\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {reinterpret_cast<iterator>(\n                      &array_.emplace_back(std::forward<Args>(args)...)),\n                  true};\n      } else {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back(\n                      std::piecewise_construct, std::forward_as_tuple(key),\n                      std::forward_as_tuple(std::forward<Args>(args)...))),\n                  true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace_or_assign(K&& key, Args&&... args) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n\n    std::pair<iterator, bool> result;\n    if (BASE_VECTOR_UNLIKELY(pos != nullptr)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      auto& v = value_extractor()(*pos);\n      v.~T();\n      new (&v) T(std::forward<Args>(args)...);\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {reinterpret_cast<iterator>(\n                      &array_.emplace_back(std::forward<Args>(args)...)),\n                  true};\n      } else {\n        result = {\n            reinterpret_cast<iterator>(&array_.emplace_back(\n                std::piecewise_construct, std::forward_as_tuple(std::move(key)),\n                std::forward_as_tuple(std::forward<Args>(args)...))),\n            true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  template <class U, class... Args>\n  std::pair<iterator, bool> try_emplace(U&& key, Args&&... args) {\n    return emplace(std::forward<U>(key), std::forward<Args>(args)...);\n  }\n\n  template <typename... Args1, typename... Args2>\n  std::pair<iterator, bool> emplace(std::piecewise_construct_t,\n                                    std::tuple<Args1...> args1,\n                                    std::tuple<Args2...> args2) {\n    // Construct key in first place and find.\n    K key = std::make_from_tuple<K>(std::move(args1));\n\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n\n    // Do emplace.\n    std::pair<iterator, bool> result;\n    if (BASE_VECTOR_UNLIKELY(pos != nullptr)) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};  // insertion failed and key is discarded\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        std::apply(\n            [&](auto&&... unpacked_args) {\n              result = {\n                  reinterpret_cast<iterator>(&array_.emplace_back(\n                      std::forward<decltype(unpacked_args)>(unpacked_args)...)),\n                  true};\n            },\n            std::move(args2));\n      } else {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back(\n                      std::piecewise_construct,\n                      std::forward_as_tuple(std::move(key)), std::move(args2))),\n                  true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  /**\n   * @brief Iterates over each key-value pair in the map and applies a\n   * user-provided callback.\n   *\n   * This method supports two types of callbacks for maximum flexibility:\n   * 1.  **Callback with `void` return type**: The function will be executed for\n   * every element in the map. Signature: `void(const K& key, const T& value)`\n   *\n   * 2.  **Callback with `bool` return type**: This allows for early termination\n   * of the loop. If the callback returns `true`, the iteration stops\n   * immediately. If it returns `false`, the iteration continues to the next\n   * element. Signature: `bool(const K& key, const T& value)`\n   *\n   * @tparam Callback The type of the callback function or callable object\n   * (e.g., a lambda).\n   *\n   * @param callback The callable object to be invoked for each key-value pair.\n   * It will be perfectly forwarded.\n   *\n   * @note This method is enabled only if the provided `Callback` is invocable\n   * with arguments of type `const K&` and `const T&`.\n   * @note The method uses `if constexpr` to create a zero-overhead abstraction,\n   * ensuring that the check for a boolean return value happens at compile-time\n   * with no runtime cost.\n   *\n   * @code\n   * MyMap<std::string, int> map = {{\"a\", 1}, {\"b\", 2}};\n   *\n   * // Example 1: Print all elements (void return).\n   * map.for_each([](const std::string& key, int value) {\n   *   std::cout << key << \": \" << value << std::endl;\n   * });\n   *\n   * // Example 2: Find an element and stop (bool return).\n   * map.for_each([](const std::string& key, int value) -> bool {\n   *   if (key == \"b\") {\n   *     std::cout << \"Found 'b'!\" << std::endl;\n   *     return true; // Stop iterating\n   *   }\n   *   return false; // Continue iterating\n   * });\n   * @endcode\n   */\n  template <typename Callback, typename = std::enable_if_t<std::is_invocable_v<\n                                   Callback, const K&, const T&>>>\n  void for_each(Callback&& callback) const {\n    using callback_ret_type =\n        std::invoke_result_t<Callback, const K&, const T&>;\n    constexpr bool callback_returns_bool =\n        std::is_same_v<callback_ret_type, bool>;\n    const auto sz = array_.size();\n    auto* begin = array_.begin();\n    if constexpr (consecutive_key) {\n      const auto* hash_data = _key_hash_data();\n      if constexpr (callback_returns_bool) {\n        for (size_t i = 0; i < sz; i++) {\n          if (callback(static_cast<K>(hash_data[i]), *(begin + i))) {\n            break;\n          }\n        }\n      } else {\n        for (size_t i = 0; i < sz; i++) {\n          callback(static_cast<K>(hash_data[i]), *(begin + i));\n        }\n      }\n    } else {\n      if constexpr (callback_returns_bool) {\n        for (size_t i = 0; i < sz; i++) {\n          auto it = begin + i;\n          if (callback(it->first, it->second)) {\n            break;\n          }\n        }\n      } else {\n        for (size_t i = 0; i < sz; i++) {\n          auto it = begin + i;\n          callback(it->first, it->second);\n        }\n      }\n    }\n  }\n\n  template <typename Callback, typename = std::enable_if_t<\n                                   std::is_invocable_v<Callback, const K&, T&>>>\n  void for_each(Callback&& callback) {\n    using callback_ret_type = std::invoke_result_t<Callback, const K&, T&>;\n    constexpr bool callback_returns_bool =\n        std::is_same_v<callback_ret_type, bool>;\n    const auto sz = array_.size();\n    auto* begin = array_.begin();\n    if constexpr (consecutive_key) {\n      const auto* hash_data = _key_hash_data();\n      if constexpr (callback_returns_bool) {\n        for (size_t i = 0; i < sz; i++) {\n          if (callback(static_cast<K>(hash_data[i]), *(begin + i))) {\n            break;\n          }\n        }\n      } else {\n        for (size_t i = 0; i < sz; i++) {\n          callback(static_cast<K>(hash_data[i]), *(begin + i));\n        }\n      }\n    } else {\n      if constexpr (callback_returns_bool) {\n        for (size_t i = 0; i < sz; i++) {\n          auto it = begin + i;\n          if (callback(it->first, it->second)) {\n            break;\n          }\n        }\n      } else {\n        for (size_t i = 0; i < sz; i++) {\n          auto it = begin + i;\n          callback(it->first, it->second);\n        }\n      }\n    }\n  }\n\n  template <size_t N2>\n  bool operator==(\n      const LinearSearchMap<K, T, KeyPolicy, N2, Stat>& other) const {\n    // Data in self is not ordered, we cannot compare array_ directly.\n    if (array_.size() != other.array_.size()) {\n      return false;\n    }\n    bool equal = true;\n    for_each([&](const K& k, const T& v) -> bool {\n      auto it = other.find(k);\n      if (it == other.value_end()) {\n        equal = false;\n        return true;  // stop\n      }\n      if (it->second != v) {\n        equal = false;\n        return true;  // stop\n      }\n      return false;  // continue\n    });\n    return equal;\n  }\n\n  template <size_t N2>\n  bool operator!=(\n      const LinearSearchMap<K, T, KeyPolicy, N2, Stat>& other) const {\n    return !(*this == other);\n  }\n\n protected:\n  std::pair<iterator, bool> try_insert(const K& key) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n\n    std::pair<iterator, bool> result;\n    if (pos != nullptr) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back()), true};\n      } else {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back(\n                      std::piecewise_construct, std::forward_as_tuple(key),\n                      std::tuple<>())),\n                  true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n\n  std::pair<iterator, bool> try_insert(K&& key) {\n    [[maybe_unused]] KeyPolicyReducedHashValueType hash;\n    iterator pos;\n    if constexpr (is_key_hash) {\n      hash = _reduced_key_hash(key);\n      pos = find_exact(key, hash);  // find with hash value\n    } else {\n      pos = find_exact(key);\n    }\n\n    std::pair<iterator, bool> result;\n    if (pos != nullptr) {\n      RecordFind(MapStatisticsFindKind::kInsertFindCollision, array_.size());\n      result = {pos, false};\n    } else {\n      RecordFind(MapStatisticsFindKind::kInsertFind, array_.size());\n      if constexpr (consecutive_key) {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back()), true};\n      } else {\n        result = {reinterpret_cast<iterator>(&array_.emplace_back(\n                      std::piecewise_construct,\n                      std::forward_as_tuple(std::move(key)), std::tuple<>())),\n                  true};\n      }\n      if constexpr (is_key_hash) {\n        // Store hash value after array insertion because array may expand.\n        _key_hash_data()[array_.size() - 1] = hash;\n      }\n    }\n    return result;\n  }\n};\n\ntemplate <class K, class KeyPolicy, size_t N, bool Stat>\nstruct LinearSearchSet : public LinearSearchArray<K, void, KeyPolicy, N, Stat> {\n protected:\n  template <class K2, class KeyPolicy2, size_t N2, bool Stat2>\n  friend struct LinearSearchSet;\n\n  using MapStatisticsBase<Stat>::UpdateMaxCount;\n\n  using LinearSearchArray<K, void, KeyPolicy, N, Stat>::array_;\n\n public:\n  using LinearSearchArray<K, void, KeyPolicy, N, Stat>::is_key_hash;\n  using typename LinearSearchArray<K, void, KeyPolicy, N,\n                                   Stat>::insert_value_type;\n  using typename LinearSearchArray<K, void, KeyPolicy, N, Stat>::value_type;\n  using\n      typename LinearSearchArray<K, void, KeyPolicy, N, Stat>::store_value_type;\n  using typename LinearSearchArray<K, void, KeyPolicy, N, Stat>::iterator;\n  using LinearSearchArray<K, void, KeyPolicy, N, Stat>::insert;\n\n  LinearSearchSet() {}\n  LinearSearchSet(std::initializer_list<insert_value_type> list) {\n    array_.reserve(list.size());\n    for (auto& v : list) {\n      insert(std::move(v));\n    }\n  }\n\n  // Only when not using hash, the internal array can be moved from source.\n  // Because we are using extra leading bytes of array buffer to store hash\n  // values.\n  template <size_t ExtraBytesPerElement, bool CountRealloc,\n            typename = std::enable_if_t<ExtraBytesPerElement == 0>>\n  LinearSearchSet(Vector<store_value_type, ExtraBytesPerElement, CountRealloc>&&\n                      source_array)\n      : LinearSearchArray<K, void, KeyPolicy, N, Stat>(\n            std::move(source_array)) {\n    static_assert(!is_key_hash);\n  }\n\n  template <size_t ExtraBytesPerElement, bool CountRealloc,\n            typename = std::enable_if_t<ExtraBytesPerElement == 0>>\n  LinearSearchSet& operator=(Vector<store_value_type, ExtraBytesPerElement,\n                                    CountRealloc>&& source_array) {\n    static_assert(!is_key_hash);\n    array_ = std::move(source_array);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  LinearSearchSet(const LinearSearchSet<K, KeyPolicy, N2, Stat>& other)\n      : LinearSearchArray<K, void, KeyPolicy, N, Stat>(other) {}\n\n  template <size_t N2>\n  LinearSearchSet(LinearSearchSet<K, KeyPolicy, N2, Stat>&& other)\n      : LinearSearchArray<K, void, KeyPolicy, N, Stat>(std::move(other)) {}\n\n  template <size_t N2>\n  LinearSearchSet& operator=(\n      const LinearSearchSet<K, KeyPolicy, N2, Stat>& other) {\n    array_ = other.array_;\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <size_t N2>\n  LinearSearchSet& operator=(LinearSearchSet<K, KeyPolicy, N2, Stat>&& other) {\n    array_ = std::move(other.array_);\n    UpdateMaxCount(array_.size());\n    return *this;\n  }\n\n  template <class... Args>\n  std::pair<iterator, bool> emplace(Args&&... args) {\n    return insert(K(std::forward<Args>(args)...));\n  }\n\n  template <size_t N2>\n  bool operator==(const LinearSearchSet<K, KeyPolicy, N2, Stat>& other) const {\n    // Data in self is not ordered, we cannot compare array_ directly.\n    if (array_.size() != other.array_.size()) {\n      return false;\n    }\n    for (const auto& k : *this) {\n      if (!other.contains(k)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  template <size_t N2>\n  bool operator!=(const LinearSearchSet<K, KeyPolicy, N2, Stat>& other) const {\n    return !(*this == other);\n  }\n};\n\ntemplate <class K, class T, class Compare = std::less<K>>\nusing OrderedFlatMap = BinarySearchMap<K, T, 0, Compare, false>;\n\ntemplate <class K, class T, size_t N, class Compare = std::less<K>>\nusing InlineOrderedFlatMap = BinarySearchMap<K, T, N, Compare, false>;\n\ntemplate <class K, class Compare = std::less<K>>\nusing OrderedFlatSet = BinarySearchSet<K, 0, Compare, false>;\n\ntemplate <class K, size_t N, class Compare = std::less<K>>\nusing InlineOrderedFlatSet = BinarySearchSet<K, N, Compare, false>;\n\ntemplate <class K, class T, class KeyPolicy = ReducedHashKeyPolicy<K>>\nusing LinearFlatMap = LinearSearchMap<K, T, KeyPolicy, 0, false>;\n\ntemplate <class K, class T, size_t N, class KeyPolicy = ReducedHashKeyPolicy<K>>\nusing InlineLinearFlatMap = LinearSearchMap<K, T, KeyPolicy, N, false>;\n\ntemplate <class K, class KeyPolicy = ReducedHashKeyPolicy<K>>\nusing LinearFlatSet = LinearSearchSet<K, KeyPolicy, 0, false>;\n\ntemplate <class K, size_t N, class KeyPolicy = ReducedHashKeyPolicy<K>>\nusing InlineLinearFlatSet = LinearSearchSet<K, KeyPolicy, N, false>;\n\n// As HybridMap's policy template argument for inlined flat map types.\n// The generic MapPolicy template wont't accept InlineOrderedFlatMap\n// which contains non-type template arguments.\ntemplate <template <typename, typename, size_t, typename...> class T, size_t N,\n          typename... Args>\nstruct InlineFlatMapPolicy {\n  template <typename Key, typename Value>\n  using type = T<Key, Value, N, Args...>;\n};\n\n/**\n Helper to declare inlined version of existing map type.\n    using MapType = OrderedFlatMap<int, int>;\n    Inlined<MapType, 5>::type inlined_map;\n */\ntemplate <class From, size_t N>\nstruct Inlined {\n  using type = std::conditional_t<\n      std::is_same_v<From, OrderedFlatMap<typename From::key_type,\n                                          typename From::mapped_type,\n                                          typename From::key_compare>>,\n      InlineOrderedFlatMap<typename From::key_type, typename From::mapped_type,\n                           N, typename From::key_compare>,\n      std::conditional_t<\n          std::is_same_v<From, OrderedFlatSet<typename From::key_type,\n                                              typename From::key_compare>>,\n          InlineOrderedFlatSet<typename From::key_type, N,\n                               typename From::key_compare>,\n\n          std::conditional_t<\n              std::is_same_v<From, LinearFlatMap<typename From::key_type,\n                                                 typename From::mapped_type,\n                                                 typename From::key_policy>>,\n              InlineLinearFlatMap<typename From::key_type,\n                                  typename From::mapped_type, N,\n                                  typename From::key_policy>,\n\n              std::conditional_t<\n                  std::is_same_v<From,\n                                 LinearFlatSet<typename From::key_type,\n                                               typename From::key_policy>>,\n                  InlineLinearFlatSet<typename From::key_type, N,\n                                      typename From::key_policy>,\n                  void>>>>;\n};\n\nstatic_assert(std::is_same_v<Inlined<OrderedFlatMap<int, int>, 5>::type,\n                             InlineOrderedFlatMap<int, int, 5>>);\nstatic_assert(std::is_same_v<Inlined<LinearFlatMap<int, int>, 5>::type,\n                             InlineLinearFlatMap<int, int, 5>>);\n\nstatic_assert(std::is_same_v<Inlined<OrderedFlatSet<int>, 5>::type,\n                             InlineOrderedFlatSet<int, 5>>);\nstatic_assert(std::is_same_v<Inlined<LinearFlatSet<int>, 5>::type,\n                             InlineLinearFlatSet<int, 5>>);\n\n}  // namespace base\n}  // namespace lynx\n\nnamespace std {\ntemplate <class T, size_t ExtraBytesPerElement, bool CountRealloc>\ninline void swap(lynx::base::Vector<T, ExtraBytesPerElement, CountRealloc>& x,\n                 lynx::base::Vector<T, ExtraBytesPerElement, CountRealloc>& y) {\n  x.swap(y);\n}\n\ntemplate <class K, class T, size_t N1, size_t N2, class Compare1,\n          class Compare2, bool Stat1, bool Stat2,\n          typename = typename std::enable_if<N1 != N2>::type>\ninline void swap(lynx::base::BinarySearchMap<K, T, N1, Compare1, Stat1>& x,\n                 lynx::base::BinarySearchMap<K, T, N2, Compare2, Stat2>& y) {\n  x.swap(y);\n}\n\ntemplate <class K, size_t N1, size_t N2, class Compare1, class Compare2,\n          bool Stat1, bool Stat2,\n          typename = typename std::enable_if<N1 != N2>::type>\ninline void swap(lynx::base::BinarySearchSet<K, N1, Compare1, Stat1>& x,\n                 lynx::base::BinarySearchSet<K, N2, Compare2, Stat2>& y) {\n  x.swap(y);\n}\n\ntemplate <class K, class T, class KeyPolicy, size_t N1, size_t N2, bool Stat1,\n          bool Stat2, typename = typename std::enable_if<N1 != N2>::type>\ninline void swap(lynx::base::LinearSearchMap<K, T, KeyPolicy, N1, Stat1>& x,\n                 lynx::base::LinearSearchMap<K, T, KeyPolicy, N2, Stat2>& y) {\n  x.swap(y);\n}\n\ntemplate <class K, class KeyPolicy, size_t N1, size_t N2, bool Stat1,\n          bool Stat2, typename = typename std::enable_if<N1 != N2>::type>\ninline void swap(lynx::base::LinearSearchSet<K, KeyPolicy, N1, Stat1>& x,\n                 lynx::base::LinearSearchSet<K, KeyPolicy, N2, Stat2>& y) {\n  x.swap(y);\n}\n}  // namespace std\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunused-template\"\n\n#endif  // BASE_INCLUDE_VECTOR_H_\n"
  },
  {
    "path": "base/include/vector2d.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VECTOR2D_H_\n#define BASE_INCLUDE_VECTOR2D_H_\n\nnamespace lynx {\nnamespace base {\nclass Vector2D {\n public:\n  Vector2D() : x_(0), y_(0) {}\n  Vector2D(int x, int y) : x_(x), y_(y) {}\n  ~Vector2D() {}\n\n  int x() { return x_; }\n  int y() { return y_; }\n\n  void Reset() {\n    x_ = 0;\n    y_ = 0;\n  }\n\n  void Offset(int x, int y) {\n    x_ += x;\n    y_ += y;\n  }\n\n private:\n  int x_;\n  int y_;\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_VECTOR2D_H_\n"
  },
  {
    "path": "base/include/vector_helper.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VECTOR_HELPER_H_\n#define BASE_INCLUDE_VECTOR_HELPER_H_\n\n#include <istream>\n#include <string>\n\n#include \"base/include/vector.h\"\n\n// This file is separated from 'Array.h' for usage in other repositories like\n// Lynx.\n\nnamespace lynx {\nnamespace base {\n\n/**\n * @brief Convert std::string to ByteArray without tailing '\\0'.\n *\n * @param s The input string.\n * @return ByteArray\n */\ninline ByteArray ByteArrayFromString(const std::string& s) {\n  return ByteArray(s.length(), s.c_str());\n}\n\n/**\n * @brief Convert std::basic_istream to ByteArray. This function reads from the\n * current position to the end of stream.\n *\n * @param input Input stream to read.\n * @return ByteArray\n */\ninline ByteArray ByteArrayFromStream(std::basic_istream<char>& input) {\n  const auto current = input.tellg();\n  input.seekg(0, std::ios::end);\n  const auto length = input.tellg();\n  if (current == -1 && length == -1) {\n    BASE_VECTOR_DCHECK(false);\n    return {};\n  }\n  ByteArray result(length - current);\n  input.seekg(current);\n  input.read(result.data<char>(), result.size());\n  return result;\n}\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_INCLUDE_VECTOR_HELPER_H_\n"
  },
  {
    "path": "base/include/vector_lldb.py",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport lldb\n\n# LLDB formatter for Vector container.\n\ndef align_up(addr, alignment):\n    \"\"\"Replicates the C++ align_up function.\"\"\"\n    return (addr + alignment - 1) & ~(alignment - 1)\n\nclass linear_map_SynthProvider:\n\n    def __init__(self, val_obj, dict):\n        self.map_obj = val_obj\n        self.update()\n\n    def num_children(self):\n        return self.count\n\n    def get_child_at_index(self, index):\n        if index < 0:\n            return None\n        if index >= self.num_children():\n            return None\n        try:\n            offset = index * self.data_size\n            child_name = '[' + str(index) + ']'\n            if self.consecutive_key:\n                extra_hash_value = self.container_obj.CreateValueFromAddress('', self.alloc_start + index * self.extra_bytes_per_element, self.key_type)\n                child_name += '[' + extra_hash_value.GetValue() + ']'\n            return self.start.CreateChildAtOffset(child_name,\n                                                    offset, self.data_type)\n\n        except:\n            return None\n\n    def num_capacity(self):\n        return self.capacity\n\n    def update(self):\n        self.map_type = self.map_obj.GetType().GetDereferencedType()\n        self.key_type = self.map_type.GetTemplateArgumentType(0)\n        self.key_policy_type = self.map_type.GetTemplateArgumentType(2)\n        self.consecutive_key = str(self.key_policy_type).find(\"lynx::base::MapKeyPolicyConsecutiveIntegers\") >= 0\n        self.container_obj = self.map_obj.GetChildMemberWithName(\"array_\")\n        self.container_target = self.container_obj.GetTarget()\n        self.container_type = self.container_obj.GetType()\n        if self.container_type.GetCanonicalType().GetName().find(\"InlineVector\") < 0:\n            # template Vector<class T, size_t ExtraBytesPerElement = 0, bool CountRealloc = false>\n            self.extra_bytes_per_element = self.container_type.GetTemplateArgumentValue(self.container_target, 1).GetValueAsUnsigned()\n        else:\n            # template InlineVector<class T, size_t N, size_t ExtraBytesPerElement = 0, bool CountRealloc = false>\n            self.extra_bytes_per_element = self.container_type.GetTemplateArgumentValue(self.container_target, 2).GetValueAsUnsigned()\n        self.count = self.container_obj.GetChildMemberWithName(\n            'count_').GetValueAsUnsigned()\n        self.capacity = self.container_obj.GetChildMemberWithName(\n            'capacity_').GetValueAsSigned()\n        self.start = self.container_obj.GetChildMemberWithName('memory_')\n        self.alloc_start = self.start.GetValueAsAddress() - align_up(self.extra_bytes_per_element * abs(self.capacity), self.container_target.GetAddressByteSize())\n        self.data_type = self.start.GetType().GetPointeeType()\n        self.data_size = self.data_type.GetByteSize()\n\n    def has_children(self):\n        return True\n\nclass vector_SynthProvider:\n\n    def __init__(self, val_obj, dict):\n        self.val_obj = val_obj\n        self.count = self.val_obj.GetChildMemberWithName(\n            'count_').GetValueAsUnsigned()\n        self.capacity = self.val_obj.GetChildMemberWithName(\n            'capacity_').GetValueAsSigned()\n\n    def num_children(self):\n        return self.count\n\n    def get_child_at_index(self, index):\n        if index < 0:\n            return None\n        if index >= self.num_children():\n            return None\n        try:\n            offset = index * self.data_size\n            return self.start.CreateChildAtOffset('[' + str(index) + ']',\n                                                  offset, self.data_type)\n        except:\n            return None\n\n    def num_capacity(self):\n        return self.capacity\n\n    def update(self):\n        self.count = self.val_obj.GetChildMemberWithName(\n            'count_').GetValueAsUnsigned()\n        self.capacity = self.val_obj.GetChildMemberWithName(\n            'capacity_').GetValueAsSigned()\n        self.start = self.val_obj.GetChildMemberWithName('memory_')\n        self.data_type = self.start.GetType().GetPointeeType()\n        self.data_size = self.data_type.GetByteSize()\n\n    def has_children(self):\n        return True\n\n\ndef __lldb_init_module(debugger, dict):\n    debugger.HandleCommand(\n        'type synthetic add -x \"^lynx::base::LinearSearchMap<.*>$\" -l vector_lldb.linear_map_SynthProvider -w liblynx'\n    )\n    debugger.HandleCommand(\n        'type synthetic add -x \"^lynx::base::Vector<.*>$\" -l vector_lldb.vector_SynthProvider -w liblynx'\n    )\n    debugger.HandleCommand(\n        'type summary add -x \"^lynx::base::Vector<.*>$\" -s \"size=${var.count_}, capacity=${var.capacity_}\" -w liblynx'\n    )\n    debugger.HandleCommand('type category enable liblynx')\n"
  },
  {
    "path": "base/include/version.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VERSION_H_\n#define BASE_INCLUDE_VERSION_H_\n\n#include <cstdio>\n#include <iostream>\n#include <string>\n\n#include \"base/include/log/log_stream.h\"\n\nnamespace lynx {\nnamespace base {\n\nstruct Version {\n  explicit Version(const std::string& version) {\n    std::sscanf(version.c_str(), \"%d.%d.%d.%d\", &major_, &minor_, &revision_,\n                &build_);\n    if (major_ < 0) major_ = 0;\n    if (minor_ < 0) minor_ = 0;\n    if (revision_ < 0) revision_ = 0;\n    if (build_ < 0) build_ = 0;\n  }\n\n  constexpr Version(int major, int minor, int revision = 0, int build = 0)\n      : major_(major), minor_(minor), revision_(revision), build_(build) {}\n\n  bool operator<(const Version& other) const {\n    // Compare major\n    if (major_ < other.major_)\n      return true;\n    else if (major_ > other.major_)\n      return false;\n\n    // Compare minor\n    if (minor_ < other.minor_)\n      return true;\n    else if (minor_ > other.minor_)\n      return false;\n\n    // Compare revision\n    if (revision_ < other.revision_)\n      return true;\n    else if (revision_ > other.revision_)\n      return false;\n\n    // Compare build\n    if (build_ < other.build_)\n      return true;\n    else if (build_ > other.build_)\n      return false;\n\n    return false;\n  }\n\n  bool operator==(const Version& other) const {\n    return major_ == other.major_ && minor_ == other.minor_ &&\n           revision_ == other.revision_ && build_ == other.build_;\n  }\n\n  bool operator!=(const Version& other) const { return !(*this == other); }\n\n  bool operator>(const Version& other) const {\n    if (*this == other) return false;\n    return !(*this < other);\n  }\n\n  bool operator<=(const Version& other) const {\n    if (*this == other) return true;\n    return *this < other;\n  }\n\n  bool operator>=(const Version& other) const {\n    if (*this == other) return true;\n    return *this > other;\n  }\n\n  std::string ToString() const {\n    std::string version;\n    version.append(std::to_string(major_))\n        .append(\".\")\n        .append(std::to_string(minor_));\n\n    if (revision_ == 0 && build_ == 0) {\n      return version;\n    }\n\n    return version.append(\".\")\n        .append(std::to_string(revision_))\n        .append(\".\")\n        .append(std::to_string(build_));\n  }\n\n  friend std::ostream& operator<<(std::ostream& stream,\n                                  const Version& version) {\n    return stream << version.ToString();\n  }\n\n  friend base::logging::LogStream& operator<<(base::logging::LogStream& stream,\n                                              const Version& version) {\n    std::ostringstream output_version;\n    output_version << version;\n    stream << output_version;\n    return stream;\n  }\n\n  int Major() const { return major_; }\n  int Minor() const { return minor_; }\n  int Revision() const { return revision_; }\n  int Build() const { return build_; }\n\n private:\n  int major_ = 0, minor_ = 0, revision_ = 0, build_ = 0;\n};\n\n}  // namespace base\n}  // namespace lynx\n#endif  // BASE_INCLUDE_VERSION_H_\n"
  },
  {
    "path": "base/include/version_util.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_INCLUDE_VERSION_UTIL_H_\n#define BASE_INCLUDE_VERSION_UTIL_H_\n\n#include <cstdio>\n#include <iostream>\n#include <string>\n\n#include \"base/include/log/log_stream.h\"\n\nnamespace lynx {\nnamespace base {\n\nstruct Version {\n  explicit Version(const std::string& version) {\n    std::sscanf(version.c_str(), \"%d.%d.%d.%d\", &major_, &minor_, &revision_,\n                &build_);\n    if (major_ < 0) major_ = 0;\n    if (minor_ < 0) minor_ = 0;\n    if (revision_ < 0) revision_ = 0;\n    if (build_ < 0) build_ = 0;\n  }\n\n  constexpr Version(int major, int minor, int revision = 0, int build = 0)\n      : major_(major), minor_(minor), revision_(revision), build_(build) {}\n\n  bool operator<(const Version& other) const {\n    // Compare major\n    if (major_ < other.major_)\n      return true;\n    else if (major_ > other.major_)\n      return false;\n\n    // Compare minor\n    if (minor_ < other.minor_)\n      return true;\n    else if (minor_ > other.minor_)\n      return false;\n\n    // Compare revision\n    if (revision_ < other.revision_)\n      return true;\n    else if (revision_ > other.revision_)\n      return false;\n\n    // Compare build\n    if (build_ < other.build_)\n      return true;\n    else if (build_ > other.build_)\n      return false;\n\n    return false;\n  }\n\n  bool operator==(const Version& other) const {\n    return major_ == other.major_ && minor_ == other.minor_ &&\n           revision_ == other.revision_ && build_ == other.build_;\n  }\n\n  bool operator!=(const Version& other) const { return !(*this == other); }\n\n  bool operator>(const Version& other) const {\n    if (*this == other) return false;\n    return !(*this < other);\n  }\n\n  bool operator<=(const Version& other) const {\n    if (*this == other) return true;\n    return *this < other;\n  }\n\n  bool operator>=(const Version& other) const {\n    if (*this == other) return true;\n    return *this > other;\n  }\n\n  std::string ToString() const {\n    std::string version;\n    version.append(std::to_string(major_))\n        .append(\".\")\n        .append(std::to_string(minor_));\n\n    if (revision_ == 0 && build_ == 0) {\n      return version;\n    }\n\n    return version.append(\".\")\n        .append(std::to_string(revision_))\n        .append(\".\")\n        .append(std::to_string(build_));\n  }\n\n  friend std::ostream& operator<<(std::ostream& stream,\n                                  const Version& version) {\n    return stream << version.ToString();\n  }\n\n  friend base::logging::LogStream& operator<<(base::logging::LogStream& stream,\n                                              const Version& version) {\n    std::ostringstream output_version;\n    output_version << version;\n    stream << output_version;\n    return stream;\n  }\n\n  int Major() const { return major_; }\n  int Minor() const { return minor_; }\n  int Revision() const { return revision_; }\n  int Build() const { return build_; }\n\n private:\n  int major_ = 0, minor_ = 0, revision_ = 0, build_ = 0;\n};\n\n}  // namespace base\n}  // namespace lynx\n#endif  // BASE_INCLUDE_VERSION_UTIL_H_\n"
  },
  {
    "path": "base/platform/android/.gitignore",
    "content": ".cxx\n/build\n/CMakeLists_impl"
  },
  {
    "path": "base/platform/android/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//build/toolchain/clang.gni\")\nimport(\"../../../config.gni\")\n\nassert(is_android)\n\nconfig(\"base_android_private_config\") {\n  ldflags = [\n    \"-Wl,--exclude-libs,ALL,--gc-sections\",\n    \"-fuse-ld=lld\",\n    \"-Xlinker\",\n    \"-Map=output.map\",\n  ]\n  if (!is_debug) {\n    ldflags += [\n      \"-O2\",\n      \"-Wl,--icf=all\",\n    ]\n    if (enable_lto) {\n      ldflags += [ \"-flto\" ]\n    }\n  }\n}\n\ncmake_target(\"LynxBase\") {\n  cmake_version = \"3.4.1\"\n  target_type = \"shared_library\"\n  output_name = \"lynxbase\"\n\n  deps = [\n    \":base_jni_files\",\n    \"../../src:base_group\",\n  ]\n\n  configs = [\n    \":base_android_private_config\",\n    \"../../../platform/android:16kb_page\",\n  ]\n}\n\ngroup(\"base_jni_files\") {\n  exec_script(\"//tools_shared/jni_generator/generate_and_register_jni_files.py\",\n              [\n                \"-root\",\n                rebase_path(\"../../../\"),\n                \"-path\",\n                rebase_path(\"./src/main/jni/jni_configs.yml\"),\n                \"--use-base-jni-header\",\n              ])\n  public_deps = [ \"src/main/jni:build\" ]\n}\n"
  },
  {
    "path": "base/platform/android/CMakeLists.txt",
    "content": "# Set the minimum version of CMAKE that is required\ncmake_minimum_required(VERSION 3.4.1)\n\n# According to the build variant, \n# import the CMakeLists-impl.cmake file to compile the native methods.\ninclude(${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists_impl/${FLAVOR_NAME}${BUILD_TYPE}${ANDROID_ABI}/CMakeLists.txt)\n"
  },
  {
    "path": "base/platform/android/build.gradle",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\napply plugin: 'com.android.library'\napply from: '../../../platform/android/publish.gradle'\nimport org.json.simple.JSONObject\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n    ndkVersion rootProject.ext.ndkVersion\n\n    defaultConfig {\n        minSdkVersion 16\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles \"consumer-rules.pro\"\n        multiDexEnabled true\n\n        packagingOptions {\n            exclude 'lib/*/libc++_shared.so'\n        }\n\n    }\n\n    flavorDimensions \"lynx\"\n    productFlavors {\n        debugMode {\n            dimension \"lynx\"\n            externalNativeBuild {\n                cmake {\n                    cppFlags '-Wl,-ldl',\n                            '-Wno-unused-command-line-argument'\n                    arguments '-DFLAVOR_NAME=debugMode'\n\n                }\n            }\n        }\n        asan {\n            dimension \"lynx\"\n            externalNativeBuild {\n                cmake {\n                    cppFlags '-fsanitize=address',\n                            '-fsanitize-address-use-after-scope',\n                            '-fPIE',\n                            '-Wl,-ldl',\n                            '-Wl,-llog',\n                            '-Wno-unused-command-line-argument'\n                    arguments '-DANDROID_ARM_MODE=arm', '-DFLAVOR_NAME=asan'\n                    abiFilters(*rootProject.ext.abiList)\n                }\n            }\n        }\n        noasan {\n            dimension \"lynx\"\n            externalNativeBuild {\n                cmake {\n                    arguments '-DFLAVOR_NAME=noasan'\n                }\n            }\n        }\n        dev {\n            dimension \"lynx\"\n            matchingFallbacks = [\"noasan\"]\n            externalNativeBuild {\n                cmake {\n                    cppFlags '-Wl,-ldl',\n                            '-Wno-unused-command-line-argument'\n                    arguments '-DANDROID_STL='+getCppLib(),\n                            '-DFLAVOR_NAME=dev'\n                }\n            }\n        }\n    }\n\n    buildTypes {\n        release {\n            externalNativeBuild {\n                cmake {\n                    arguments '-DANDROID_PLATFORM=android-14',\n                            '-DBUILD_LEPUS_COMPILE=false',\n                            '-DANDROID_TOOLCHAIN=clang',\n                            '-DANDROID_STL='+getCppLib(),\n                            '-DCMAKE_BUILD_TYPE=Release',\n                            '-DLOCAL_ARM_NEON=true',\n                            '-DBUILD_TYPE=release',\n                            '-LH'// Show compile parameters.\n                    cppFlags '-Wl,-ldl',\n                            '-Wno-unused-command-line-argument'\n                }\n            }\n            ndk {\n                abiFilters(*rootProject.ext.abiList)\n            }\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n        debug {\n            externalNativeBuild {\n                cmake {\n                    arguments '-DANDROID_PLATFORM=android-14',\n                            '-DBUILD_LEPUS_COMPILE=false',\n                            '-DANDROID_TOOLCHAIN=clang',\n                            '-DANDROID_STL='+getCppLib(),\n                            '-DLOCAL_ARM_NEON=true',\n                            '-DBUILD_TYPE=debug',\n                            '-LH'// Show compile parameters.\n\n                    // Symbol table with line numbers.\n                    cppFlags \"-g\",\n                            '-Wl,-ldl',\n                            '-Wno-unused-command-line-argument'\n                }\n            }\n            ndk {\n                abiFilters(*rootProject.ext.abiList)\n            }\n            \n            debuggable true\n        }\n    }\n\n   externalNativeBuild {\n       cmake {\n           path \"CMakeLists.txt\"\n           version CMAKE_VERSION\n       }\n\n   }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n\n// Configure the compilation parameters in gn to generate the corresponding CMakeLists.txt files.\ntask configGnCompileParas {\n    def buildVariantList = getFlavorNamesAndBuildTypes(project)\n    def flavorNames = buildVariantList[0]\n    def buildTypes = buildVariantList[1]\n    JSONObject gnBuildArgs_map = new JSONObject()\n    flavorNames.each{\n        JSONObject gnBuildArgsList = new JSONObject()\n        String flavorName = it\n        if (flavorName == \"asan\") {\n            gnBuildArgsList.put(\"is_asan\", \"true\")\n        } else {\n            gnBuildArgsList.put(\"is_asan\", \"false\")\n        }\n\n        buildTypes.each{\n            JSONObject gnBuildArgs_list = new JSONObject(gnBuildArgsList)\n            String buildType = it\n            if (buildType == \"release\") {\n                gnBuildArgs_list.put(\"is_debug\", \"false\")\n            } else if(buildType == \"debug\") {\n                gnBuildArgs_list.put(\"is_debug\", \"true\")\n            }\n            rootProject.ext.abiList.each { abi ->\n                gnBuildArgs_map.put(flavorName+buildType+abi, gnBuildArgs_list)\n            }\n        }\n    }\n    writeGnArgs(gnBuildArgs_map.toJSONString())\n}\n\ndependencies {\n    compileOnly 'androidx.annotation:annotation:1.0.0'\n    api project(':ServiceAPI')\n}"
  },
  {
    "path": "base/platform/android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\nARTIFACT_GROUP=org.lynxsdk.lynx\nARTIFACT_NAME=lynx-base\nDESCRIPTION=Lynx Base\nREPOSITORY_URL=https://github.com/lynx-family/lynx\nREPOSITORY_SSH_URL=github.com/lynx-family/lynx.git\nDEVELOPER_NAME=lynx\nDEVELOPER_EMAIL=lynx.authors@gmail.com"
  },
  {
    "path": "base/platform/android/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n# Classes retained using the @Keep annotation.\n-dontwarn android.support.annotation.Keep\n-keep @android.support.annotation.Keep class **\n-keep @android.support.annotation.Keep class ** {\n    @android.support.annotation.Keep <fields>;\n    @android.support.annotation.Keep <methods>;\n}\n-dontwarn androidx.annotation.Keep\n-keep @androidx.annotation.Keep class **\n-keep @androidx.annotation.Keep class ** {\n    @androidx.annotation.Keep <fields>;\n    @androidx.annotation.Keep <methods>;\n}\n\n-keepclasseswithmembers,includedescriptorclasses class * {\n    native <methods>;\n}\n\n-keepclasseswithmembers class * {\n    @com.lynx.base.CalledByNative <methods>;\n}\n\n"
  },
  {
    "path": "base/platform/android/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.lynx.base\">\n\n</manifest>"
  },
  {
    "path": "base/platform/android/src/main/java/com/lynx/base/CalledByNative.java",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.base;\n\nimport static java.lang.annotation.ElementType.METHOD;\nimport static java.lang.annotation.RetentionPolicy.CLASS;\n\nimport androidx.annotation.Keep;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\n@Documented\n@Retention(CLASS)\n@Target({METHOD})\n@Keep\npublic @interface CalledByNative {}\n"
  },
  {
    "path": "base/platform/android/src/main/java/com/lynx/base/IBaseNativeLibraryLoader.java",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.base;\n\npublic interface IBaseNativeLibraryLoader {\n  void loadLibrary(String libName) throws UnsatisfiedLinkError;\n}\n"
  },
  {
    "path": "base/platform/android/src/main/java/com/lynx/base/LynxBaseEnv.java",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.base;\n\nimport android.util.Log;\nimport com.lynx.base.IBaseNativeLibraryLoader;\nimport com.lynx.base.LynxBaseTrace;\nimport com.lynx.base.log.LynxLog;\n\npublic class LynxBaseEnv {\n  private static LynxBaseEnv sInstance;\n  private volatile boolean mIsNativeLibraryLoaded = false;\n\n  public static LynxBaseEnv inst() {\n    if (sInstance == null) {\n      synchronized (LynxBaseEnv.class) {\n        if (sInstance == null) {\n          sInstance = new LynxBaseEnv();\n        }\n      }\n    }\n    return sInstance;\n  }\n\n  private LynxBaseEnv() {}\n\n  public boolean isNativeLibraryLoaded() {\n    return mIsNativeLibraryLoaded;\n  }\n\n  public boolean init(\n      IBaseNativeLibraryLoader nativeLibraryLoader, boolean isPrintLogsToAllChannels) {\n    if (!mIsNativeLibraryLoaded) {\n      mIsNativeLibraryLoaded = loadNativeTraceLibrary(nativeLibraryLoader);\n    }\n    LynxLog.initLynxLog(isPrintLogsToAllChannels);\n    LynxBaseTrace.init();\n    return true;\n  }\n\n  public synchronized boolean loadNativeTraceLibrary(IBaseNativeLibraryLoader nativeLibraryLoader) {\n    if (mIsNativeLibraryLoaded) {\n      return mIsNativeLibraryLoaded;\n    }\n    try {\n      if (nativeLibraryLoader != null) {\n        nativeLibraryLoader.loadLibrary(\"lynxbase\");\n      } else {\n        System.loadLibrary(\"lynxbase\");\n      }\n      return true;\n    } catch (Exception e) {\n      Log.e(\"lynx base env init\", \"failed to load liblynxbase.so\");\n      return false;\n    }\n  }\n}\n"
  },
  {
    "path": "base/platform/android/src/main/java/com/lynx/base/LynxBaseTrace.java",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.base;\n\nimport com.lynx.base.LynxBaseEnv;\nimport com.lynx.base.log.LynxLog;\nimport com.lynx.tasm.service.ILynxTraceService;\nimport com.lynx.tasm.service.LynxServiceCenter;\n\npublic class LynxBaseTrace {\n  private static final String TAG = \"LynxBaseTrace\";\n  private static boolean sIsNativeLibLoad = false;\n\n  public static void init() {\n    try {\n      if (!sIsNativeLibLoad) {\n        sIsNativeLibLoad = LynxBaseEnv.inst().isNativeLibraryLoaded();\n      }\n      if (sIsNativeLibLoad) {\n        initNativeBaseTrace();\n      }\n    } catch (ArrayIndexOutOfBoundsException error) {\n      LynxLog.e(\"lynx\", \"init LynxBaseTrace exception [ \" + error.getMessage() + \" ]\");\n    }\n  }\n\n  private static boolean initNativeBaseTrace() {\n    long address = 0;\n    ILynxTraceService service = LynxServiceCenter.inst().getService(ILynxTraceService.class);\n    if (service == null) {\n      nativeInitBaseTrace(0);\n      LynxLog.i(TAG, \"LynxBaseTrace init successfully by itself.\");\n      return true;\n    }\n    address = service.getDefaultTraceFunction();\n    if (address != 0) {\n      nativeInitBaseTrace(address);\n      LynxLog.i(TAG,\n          \"LynxBaseTrace init successfully by custom LynxBaseTraceService. function native address is \"\n              + address);\n      return true;\n    }\n    LynxLog.i(TAG, \"failed to init LynxBaseTrace dependency\");\n    return false;\n  }\n\n  private static native void nativeInitBaseTrace(long addr);\n}\n"
  },
  {
    "path": "base/platform/android/src/main/java/com/lynx/base/log/AbsBaseLogDelegate.java",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.base.log;\n\nimport android.util.Log;\n\npublic abstract class AbsBaseLogDelegate {\n  public int mMinimumLoggingLevel = Log.INFO;\n  /**\n   * Sets a minimum log-level under which the logger will not log regardless of other checks.\n   *\n   * @param level the minimum level to set\n   */\n  public void setMinimumLoggingLevel(int level) {\n    mMinimumLoggingLevel = level;\n  }\n\n  /**\n   * Gets a minimum log-level under which the logger will not log regardless of other checks.\n   *\n   * @return the minimum level\n   */\n  public int getMinimumLoggingLevel() {\n    return mMinimumLoggingLevel;\n  }\n\n  /**\n   * Gets whether the specified level is loggable.\n   *\n   * @param level the level to check\n   * @return the level\n   */\n  public boolean isLoggable(int level) {\n    return mMinimumLoggingLevel <= level;\n  }\n\n  /**\n   * Send a {@link android.util.Log#VERBOSE} log message.\n   *\n   * @param tag Used to identify the source of a log message.  It usually identifies\n   *            the class or activity where the log call occurs.\n   * @param msg The message you would like logged.\n   */\n  public void v(String tag, String msg) {\n    println(Log.VERBOSE, tag, msg);\n  }\n\n  /**\n   * Send a {@link android.util.Log#DEBUG} log message.\n   *\n   * @param tag Used to identify the source of a log message.  It usually identifies\n   *            the class or activity where the log call occurs.\n   * @param msg The message you would like logged.\n   */\n  public void d(String tag, String msg) {\n    println(Log.DEBUG, tag, msg);\n  }\n\n  /**\n   * Send an {@link android.util.Log#INFO} log message.\n   *\n   * @param tag Used to identify the source of a log message.  It usually identifies\n   *            the class or activity where the log call occurs.\n   * @param msg The message you would like logged.\n   */\n  public void i(String tag, String msg) {\n    println(Log.INFO, tag, msg);\n  }\n\n  /**\n   * Send a {@link android.util.Log#WARN} log message.\n   *\n   * @param tag Used to identify the source of a log message.  It usually identifies\n   *            the class or activity where the log call occurs.\n   * @param msg The message you would like logged.\n   */\n  public void w(String tag, String msg) {\n    println(Log.WARN, tag, msg);\n  }\n\n  /**\n   * Send an {@link android.util.Log#ERROR} log message.\n   *\n   * @param tag Used to identify the source of a log message.  It usually identifies\n   *            the class or activity where the log call occurs.\n   * @param msg The message you would like logged.\n   */\n  public void e(String tag, String msg) {\n    println(Log.ERROR, tag, msg);\n  }\n\n  /**\n   * Logs a message.\n   *\n   * @param priority the priority of the message\n   * @param tag Used to identify the source of a log message.\n   * @param msg The message you would like logged.\n   */\n  public void log(int priority, String tag, String msg) {\n    println(priority, tag, msg);\n  }\n\n  private void println(int priority, String tag, String msg) {\n    if (tag != null && msg != null) {\n      Log.println(priority, tag, msg);\n    }\n  }\n}\n"
  },
  {
    "path": "base/platform/android/src/main/java/com/lynx/base/log/LynxLog.java",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.base.log;\n\nimport android.util.Log;\nimport androidx.annotation.Nullable;\nimport com.lynx.base.BuildConfig;\nimport com.lynx.base.CalledByNative;\nimport com.lynx.base.LynxBaseEnv;\nimport com.lynx.tasm.service.ILynxLogService;\nimport com.lynx.tasm.service.LynxServiceCenter;\nimport java.util.Timer;\nimport java.util.TimerTask;\n\npublic class LynxLog {\n  private static final String TAG = \"LynxLog\";\n\n  /*\n   * Align LLog levels to the LogSeverity in the <logging.h>\n   * */\n  public static final int VERBOSE = 0;\n  public static final int DEBUG = 1;\n  public static final int INFO = 2;\n  public static final int WARN = 3;\n  public static final int ERROR = 4;\n\n  private static AbsBaseLogDelegate sDebugLoggingDelegate;\n  private static int sALogMinLogLevel = BuildConfig.DEBUG ? DEBUG : INFO;\n\n  private static boolean sIsNativeLibLoad = false;\n  private static boolean sIsJSLogsFromExternalChannelsOpen = false;\n  private static ILynxLogService service = null;\n\n  // set interval as 500 ms when init LynxLog\n  private static final int sDetectALogDependencyInterval = 500;\n  private static int sTryCounts = 0;\n  private static final int sMaxTryCounts = (1 * 60 * 1000) / sDetectALogDependencyInterval;\n\n  public static void initLynxLog(boolean isPrintLogsToAllChannels) {\n    try {\n      if (!sIsNativeLibLoad) {\n        sIsNativeLibLoad = LynxBaseEnv.inst().isNativeLibraryLoaded();\n      }\n      if (sIsNativeLibLoad) {\n        initLynxLogging(isPrintLogsToAllChannels);\n      }\n    } catch (ArrayIndexOutOfBoundsException error) {\n      Log.e(\"lynx\", \"init LynxLogging exception [ \" + error.getMessage() + \" ]\");\n    }\n  }\n\n  private static void initLynxLogging(boolean isPrintLogsToAllChannels) {\n    nativeInitLynxLoggingNative(isPrintLogsToAllChannels);\n    asyncInitLynxNativeLog();\n    setLogOutputChannel();\n  }\n\n  public static void setDebugLoggingDelegate(AbsBaseLogDelegate delegate) {\n    sDebugLoggingDelegate = delegate;\n  }\n\n  public static void setMinimumLoggingLevel(int level) {\n    try {\n      if (!sIsNativeLibLoad) {\n        sIsNativeLibLoad = LynxBaseEnv.inst().isNativeLibraryLoaded();\n      }\n      if (sIsNativeLibLoad) {\n        final String[] logLevelName = {\"VERBOSE\", \"DEBUG\", \"INFO\", \"WARN\", \"ERROR\"};\n        if (sALogMinLogLevel < level) {\n          sALogMinLogLevel = level;\n          nativeSetNativeMinLogLevel(level);\n          Log.w(\"lynx\",\n              String.format(\"Reset minimum log level as %s\", logLevelName[sALogMinLogLevel]));\n        } else {\n          Log.w(\"lynx\",\n              String.format(\"Please set a log level higher than %s to filter lynx logs!\",\n                  logLevelName[sALogMinLogLevel]));\n        }\n      }\n    } catch (ArrayIndexOutOfBoundsException error) {\n      Log.e(\"lynx\", \"Please check index, \" + error.getMessage());\n    }\n  }\n\n  public static int getMinimumLoggingLevel() {\n    return sALogMinLogLevel;\n  }\n\n  /*\n   * turn off by default\n   * JS logs form external channels: recorded by business developers (mostly front-end)\n   */\n  public static void setJSLogsFromExternalChannels(boolean isOpen) {\n    sIsJSLogsFromExternalChannelsOpen = isOpen;\n  }\n\n  public static void v(String tag, String msg) {\n    internalLog(VERBOSE, tag, msg);\n  }\n\n  public static void d(String tag, String msg) {\n    internalLog(DEBUG, tag, msg);\n  }\n\n  public static void i(String tag, String msg) {\n    internalLog(INFO, tag, msg);\n  }\n\n  public static void w(String tag, String msg) {\n    internalLog(WARN, tag, msg);\n  }\n\n  public static void e(String tag, String msg) {\n    internalLog(ERROR, tag, msg);\n  }\n\n  private static void logByAndroidUtil(int level, String tag, String msg) {\n    switch (level) {\n      case VERBOSE:\n        Log.v(tag, msg);\n        break;\n      case DEBUG:\n        Log.d(tag, msg);\n        break;\n      case INFO:\n        Log.i(tag, msg);\n        break;\n      case WARN:\n        Log.w(tag, msg);\n        break;\n      case ERROR:\n        Log.e(tag, msg);\n        break;\n    }\n  }\n\n  /**\n   * upload logs to devtool for debug mode\n   */\n  private static void logByDebugLoggingDelegate(int level, String tag, String msg) {\n    if (sDebugLoggingDelegate == null) {\n      return;\n    }\n    switch (level) {\n      case VERBOSE:\n        sDebugLoggingDelegate.v(tag, msg);\n        break;\n      case DEBUG:\n        sDebugLoggingDelegate.d(tag, msg);\n        break;\n      case INFO:\n        sDebugLoggingDelegate.i(tag, msg);\n        break;\n      case WARN:\n        sDebugLoggingDelegate.w(tag, msg);\n        break;\n      case ERROR:\n        sDebugLoggingDelegate.e(tag, msg);\n        break;\n    }\n  }\n\n  public static void internalLog(int level, String tag, String msg) {\n    if (msg == null || tag == null) {\n      return;\n    }\n    logByDebugLoggingDelegate(level, tag, msg);\n    try {\n      if (!sIsNativeLibLoad) {\n        sIsNativeLibLoad = LynxBaseEnv.inst().isNativeLibraryLoaded();\n        if (!sIsNativeLibLoad) {\n          logByAndroidUtil(level, tag, msg);\n          return;\n        }\n      }\n      if (level >= sALogMinLogLevel) {\n        if (service != null && service.isLogOutputByPlatform()) {\n          service.logByPlatform(level, tag, msg);\n        } else {\n          nativeInternalLog(level, tag, msg);\n        }\n      }\n    } catch (UnsatisfiedLinkError e) {\n      // in the case of only ALog delegation, So here don`t use LLog\n      Log.e(\"lynx\", \"load liblynxbase.so exception [ \" + e.getMessage() + \" ]\");\n    }\n  }\n\n  public static void DCHECK(boolean condition) {\n    if (!BuildConfig.DEBUG) {\n      return;\n    }\n    if (!condition) {\n      throw new RuntimeException(\"LYNX DEBUG ABORT\");\n    }\n  }\n\n  public static void DTHROW() {\n    DTHROW(null);\n  }\n\n  public static void DTHROW(@Nullable RuntimeException e) {\n    if (!BuildConfig.DEBUG) {\n      return;\n    }\n    if (e != null) {\n      throw e;\n    } else {\n      throw new RuntimeException(\"LYNX DEBUG ABORT\");\n    }\n  }\n\n  private static void asyncInitLynxNativeLog() {\n    if (detectALogDependence()) {\n      return;\n    }\n    final Timer timer = new Timer();\n    TimerTask timerTask = new TimerTask() {\n      @Override\n      public void run() {\n        if (detectALogDependence()) {\n          cancel();\n          timer.cancel();\n        }\n      }\n    };\n    timer.schedule(timerTask, 0, sDetectALogDependencyInterval);\n  }\n\n  private static boolean detectALogDependence() {\n    long address = 0;\n    service = LynxServiceCenter.inst().getService(ILynxLogService.class);\n    if (service != null) {\n      address = service.getDefaultWriteFunction();\n    }\n    if (address != 0) {\n      nativeInitALogNative(address);\n      Log.i(TAG, \"LynxLog dependency load successfully. function native address is \" + address);\n      return true;\n    }\n    if (++sTryCounts >= sMaxTryCounts) {\n      Log.i(TAG, \"failed to load LynxLog dependency\");\n      return true;\n    }\n    return false;\n  }\n\n  private static void setLogOutputChannel() {\n    if (service != null && service.isLogOutputByPlatform()) {\n      nativeSetLogOutputByPlatform();\n    }\n  }\n\n  private static native void nativeSetNativeMinLogLevel(int level);\n  private static native void nativeInitALogNative(long addr);\n  private static native void nativeInternalLog(int level, String tag, String msg);\n  private static native void nativeInitLynxLoggingNative(boolean isPrintLogsToAllChannels);\n  private static native void nativeSetLogOutputByPlatform();\n\n  @CalledByNative\n  private static void log(int priority, String tag, String msg, int source, long runtimeId,\n      int channelType, int messageStart) {\n    try {\n      priority = priority > ERROR ? ERROR : priority;\n      // 0.consume all logs from the native layer.\n      if (service != null && service.isLogOutputByPlatform()) {\n        service.logByPlatform(priority, tag, msg);\n      }\n      // 1.upload all logs to devtool for debug mode\n      logByDebugLoggingDelegate(priority, tag, msg);\n    } catch (Throwable e) {\n      Log.e(\"lynx\", \"\" + e.getMessage());\n    }\n  }\n\n  /**\n   * Emoji will make App crash when use `NewStringUTF` API in Android 5.x\n   */\n  @CalledByNative\n  private static void logByte(int priority, String tag, byte[] msg, int source, long runtimeId,\n      int channelType, int messageStart) {\n    log(priority, tag, new String(msg), source, runtimeId, channelType, messageStart);\n  }\n}\n"
  },
  {
    "path": "base/platform/android/src/main/java/com/lynx/base/memory/MemoryPressureCallback.java",
    "content": "// Copyright 2018 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\npackage com.lynx.base.memory;\n\n/**\n * Memory pressure callback interface.\n */\n@FunctionalInterface\npublic interface MemoryPressureCallback {\n  void onPressure(@MemoryPressureLevel int pressure);\n}\n"
  },
  {
    "path": "base/platform/android/src/main/java/com/lynx/base/memory/MemoryPressureLevel.java",
    "content": "// Copyright 2023 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\npackage com.lynx.base.memory;\n\nimport androidx.annotation.IntDef;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n@IntDef({MemoryPressureLevel.NONE, MemoryPressureLevel.MODERATE, MemoryPressureLevel.CRITICAL,\n    MemoryPressureLevel.MAX_VALUE})\n@Retention(RetentionPolicy.SOURCE)\npublic @interface MemoryPressureLevel {\n  /**\n   * No problems, there is enough memory to use. This event is not sent via callback, but the enum\n   * is used in other places to find out the current state of the system.\n   */\n  int NONE = 0;\n  /**\n   * Modules are advised to free buffers that are cheap to re-allocate and not immediately needed.\n   */\n  int MODERATE = 1;\n  /**\n   * At this level, modules are advised to free all possible memory.  The alternative is to be\n   * killed by the system, which means all memory will have to be re-created, plus the cost of a\n   * cold start.\n   */\n  int CRITICAL = 2;\n  /**\n   * This must be the last value in the enum. The casing is different from the other values to make\n   * this enum work well with the UMA_HISTOGRAM_ENUMERATION macro.\n   */\n  int MAX_VALUE = CRITICAL;\n}\n"
  },
  {
    "path": "base/platform/android/src/main/jni/.gitignore",
    "content": "gen/\n"
  },
  {
    "path": "base/platform/android/src/main/jni/BUILD.gn",
    "content": "# This file is autogenerated.\n\n# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../../../src/base.gni\")\n\nlynx_base_source_set(\"build\") {\n  sources = [\n    \"gen/LynxBaseTrace_jni.h\",\n    \"gen/LynxBaseTrace_register_jni.h\",\n    \"gen/LynxLog_jni.h\",\n    \"gen/LynxLog_register_jni.h\",\n    \"gen/lynx_base_so_load.cc\",\n  ]\n}\n"
  },
  {
    "path": "base/platform/android/src/main/jni/jni_configs.yml",
    "content": "# jni register file configs.\njni_register_configs:\n  output_path: base/platform/android/src/main/jni/gen/lynx_base_so_load.cc\n  namespaces:\n    - lynxbase\n  custom_headers:\n    - base/include/platform/android/jni_utils.h\n  special_cases:\n    - header: base/include/fml/platform/android/message_loop_android.h\n      method: lynx::fml::MessageLoopAndroid::Register(env);\n# gn file configs.\ngn_configs:\n  output_path: base/platform/android/src/main/jni/BUILD.gn\n  template_name: lynx_base_source_set\n  custom_headers:\n    - base/src/base.gni\n# jni files configs.\njni_class_configs:\n  output_dir: base/platform/android/src/main/jni/gen\n  jni_classes:\n    - java: base/platform/android/src/main/java/com/lynx/base/log/LynxLog.java\n    - java: base/platform/android/src/main/java/com/lynx/base/LynxBaseTrace.java\n"
  },
  {
    "path": "base/platform/darwin/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//${lynx_dir}/build_overrides/darwin.gni\")\nimport(\"../../src/base.gni\")\n\nbase_pod_target_xcconfig = {\n  GCC_PREPROCESSOR_DEFINITIONS = [\n    \"OS_IOS=1\",\n    \"HOST_OSTYPE=HOST_IOS\",\n  ]\n  OTHER_CPLUSPLUSFLAGS = \"-fno-aligned-allocation -std=gnu++17\"\n}\n\npodspec_target(\"lynx_base_podspec\") {\n  global_variables =\n      [ \"\\$enable_frozen_mode = Integer(ENV[\\\"LYNX_ENABLE_FROZEN\\\"] || 0)\" ]\n  output_name = \"LynxBase.podspec\"\n  output_path = rebase_path(\"//\")\n  root_specification = {\n    name = \"LynxBase\"\n    version = \"$lynx_version\"\n    summary = \"The framework of LynxBase.\"\n    homepage = \"https://github.com/lynx-family/lynx\"\n    license = \"Apache 2.0\"\n    author = \"Lynx\"\n    source = {\n      git = \"https://github.com/lynx-family/lynx.git\"\n    }\n    requires_arc = true\n    default_subspec = \"Framework\"\n    compiler_flags = [\n      \"-Wall\",\n      \"-Wextra\",\n      \"-Wno-unused-parameter\",\n      \"-Wno-missing-field-initializers\",\n      \"-Wshorten-64-to-32\",\n      \"-fno-rtti\",\n    ]\n    pod_target_xcconfig = base_pod_target_xcconfig\n    pod_target_xcconfig.CLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\"\n    ios = {\n      deployment_target = \"10.0\"\n    }\n  }\n  deps = [ \":Framework\" ]\n}\n\nbase_darwin_public_headers = [\n  \"log/LynxLog.h\",\n  \"LynxBaseEnv.h\",\n  \"LynxBaseDefines.h\",\n  \"trace/LynxBaseTrace.h\",\n]\n\nsubspec_target(\"Framework\") {\n  # The following flags are added just to keep it consistent with the original podspec.\n  pod_target_xcconfig = base_pod_target_xcconfig\n  pod_target_xcconfig.GCC_PREPROCESSOR_DEFINITIONS += [\n    \"RAPIDJSON_HAS_STDSTRING=1\",\n    \"RAPIDJSON_NAMESPACE=lynx::rapidjson\",\n    \"RAPIDJSON_NAMESPACE_BEGIN=\\\\\\\"namespace lynx{namespace rapidjson{\\\\\\\"\",\n    \"RAPIDJSON_NAMESPACE_END=\\\\\\\"}};namespace rapidjson=::lynx::rapidjson;\\\\\\\"\",\n    \"ENABLE_INSPECTOR=1\",\n    \"LYNX_ENABLE_FROZEN_MODE=#{\\$enable_frozen_mode}\",\n    \"OS_POSIX=1\",\n  ]\n  pod_target_xcconfig.HEADER_SEARCH_PATHS = [\n    \"//PODS_ROOT/LynxBase/lynx\",\n    \"//PODS_ROOT/LynxBase\",\n    \"../../..\",\n    \"//\",\n  ]\n  pod_target_xcconfig.OTHER_CPLUSPLUSFLAGS += \" -std=gnu++17\"\n\n  if (!is_debug) {\n    pod_target_xcconfig.GCC_PREPROCESSOR_DEFINITIONS += [ \"NDEBUG=1\" ]\n  }\n  compiler_flags = [\n    \"-Wall\",\n    \"-Wextra\",\n    \"-Wno-unused-parameter\",\n    \"-Wno-missing-field-initializers\",\n    \"-Wno-documentation\",\n  ]\n  public_header_files = base_darwin_public_headers\n  deps = [\n    \":base\",\n    \"../../../third_party/rapidjson:rapidjson\",\n    \"../../src:base_core\",\n    \"../../src:base_log\",\n    \"../../src:base_log_headers\",\n    \"../../src:base_trace\",\n    \"../../src:datauri_utils\",\n    \"../../src:string_utils\",\n    \"../../src:time_utils\",\n    \"../../src:values\",\n    \"//third_party/modp_b64\",\n  ]\n  frameworks = []\n  libraries = [ \"stdc++\" ]\n  dependency = [ \"LynxServiceAPI/Core\" ]\n}\n\nlynx_base_source_set(\"base\") {\n  public = base_darwin_public_headers\n  sources = base_darwin_public_headers + [\n              \"LynxBaseEnv.mm\",\n              \"log/LynxLog.mm\",\n              \"trace/LynxBaseTrace.mm\",\n            ]\n}\n\ngroup(\"base_for_macos\") {\n  public_deps = [\n    \":base\",\n    \"../../src:base_group\",\n  ]\n}\n"
  },
  {
    "path": "base/platform/darwin/LynxBaseDefines.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_PLATFORM_DARWIN_LYNXBASEDEFINES_H_\n#define BASE_PLATFORM_DARWIN_LYNXBASEDEFINES_H_\n\n#ifndef LYNX_BASE_CONCAT\n#define LYNX_BASE_CONCAT2(A, B) A##B\n#define LYNX_BASE_CONCAT(A, B) LYNX_BASE_CONCAT2(A, B)\n#endif\n\n#if defined(__cplusplus)\n#define LYNX_BASE_EXTERN extern \"C\" __attribute__((visibility(\"default\")))\n#else\n#define LYNX_BASE_EXTERN extern __attribute__((visibility(\"default\")))\n#endif\n\n#endif  // BASE_PLATFORM_DARWIN_LYNXBASEDEFINES_H_\n"
  },
  {
    "path": "base/platform/darwin/LynxBaseEnv.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_PLATFORM_DARWIN_LYNXBASEENV_H_\n#define BASE_PLATFORM_DARWIN_LYNXBASEENV_H_\n\n#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n@interface LynxBaseEnv : NSObject\n\n+ (instancetype)sharedInstance;\n\n- (bool)initialize:(bool)print_logs_to_all_channels;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n#endif  // BASE_PLATFORM_DARWIN_LYNXBASEENV_H_\n"
  },
  {
    "path": "base/platform/darwin/LynxBaseEnv.mm",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <LynxBase/LynxBaseEnv.h>\n#import <LynxBase/LynxBaseTrace.h>\n#import <LynxBase/LynxLog.h>\n\n@implementation LynxBaseEnv\n\n+ (instancetype)sharedInstance {\n  static LynxBaseEnv *_instance = nil;\n  static dispatch_once_t onceToken;\n  dispatch_once(&onceToken, ^{\n    _instance = [[LynxBaseEnv alloc] init];\n  });\n\n  return _instance;\n}\n\n- (bool)initialize:(bool)print_logs_to_all_channels {\n  InitLynxLog(print_logs_to_all_channels);\n  InitLynxBaseTrace();\n  return true;\n}\n\n@end\n"
  },
  {
    "path": "base/platform/darwin/log/LynxLog.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_PLATFORM_DARWIN_LOG_LYNXLOG_H_\n#define BASE_PLATFORM_DARWIN_LOG_LYNXLOG_H_\n\n#import <Foundation/Foundation.h>\n#import <LynxBase/LynxBaseDefines.h>\n\n#define LLog(...) _LynxLog(LynxLogLevelInfo, __VA_ARGS__)\n#define LLogVerbose(...) _LynxLog(LynxLogLevelVerbose, __VA_ARGS__)\n#define LLogDebug(...) _LynxLog(LynxLogLevelDebug, __VA_ARGS__)\n#define LLogInfo(...) _LynxLog(LynxLogLevelInfo, __VA_ARGS__)\n#define LLogWarn(...) _LynxLog(LynxLogLevelWarning, __VA_ARGS__)\n#define LLogError(...) _LynxLog(LynxLogLevelError, __VA_ARGS__)\n#define LLogReport(...) _LynxLog(LynxLogLevelError, __VA_ARGS__)\n#define LLogFatal(...) _LynxLog(LynxLogLevelFatal, __VA_ARGS__)\n\n#define _LogV(...) _LynxLog(LynxLogLevelVerbose, __VA_ARGS__)\n#define _LogD(...) _LynxLog(LynxLogLevelDebug, __VA_ARGS__)\n#define _LogI(...) _LynxLog(LynxLogLevelInfo, __VA_ARGS__)\n#define _LogW(...) _LynxLog(LynxLogLevelWarning, __VA_ARGS__)\n#define _LogE(...) _LynxLog(LynxLogLevelError, __VA_ARGS__)\n#define _LogR(...) _LynxLog(LynxLogLevelError, __VA_ARGS__)\n#define _LogF(...) _LynxLog(LynxLogLevelFatal, __VA_ARGS__)\n\n// just provide for .m, same as LynxFatal/LynxWarning/LynxInfo in lynx_assert.h\n#define LErrInfo(errCode, ...) _LynxErrorInfo(errCode, __VA_ARGS__)\n#define LErrWarn(expression, errCode, ...) _LynxErrorWarning(expression, errCode, __VA_ARGS__)\n#define LErrFatal(expression, errCode, ...) _LynxErrorFatal(expression, errCode, __VA_ARGS__)\n\ntypedef NS_ENUM(NSInteger, LynxLogLevel) {\n  // LynxLogLevelReport is deprecated\n  LynxLogLevelReport = -1,\n\n  LynxLogLevelVerbose = 0,\n  LynxLogLevelDebug = 1,\n  LynxLogLevelInfo = 2,\n  LynxLogLevelWarning = 3,\n  LynxLogLevelError = 4,\n  LynxLogLevelFatal = 5\n};\n\ntypedef NS_OPTIONS(NSInteger, LynxLogSource) {\n  LynxLogSourceNaitve = 1 << 0,\n  LynxLogSourceJS = 1 << 1,\n};\n\ntypedef void (^LynxLogFunction)(LynxLogLevel level, NSString *message);\n\n@interface LynxLogDelegate : NSObject\n\n@property(nonatomic, copy) LynxLogFunction logFunction;\n@property(nonatomic, assign) LynxLogLevel minLogLevel;\n@property(nonatomic, assign) BOOL shouldFormatMessage;    // Default is YES.\n@property(nonatomic, assign) LynxLogSource acceptSource;  // Default is all\n@property(nonatomic, assign)\n    NSInteger acceptRuntimeId;  // -1 means receiving all runtime logs, default is -1\n\n- (instancetype)initWithLogFunction:(LynxLogFunction)logFunction\n                        minLogLevel:(LynxLogLevel)minLogLevel;\n\n@end\n\nLYNX_BASE_EXTERN void InitLynxLog(bool print_logs_to_all_channels);\n\nLYNX_BASE_EXTERN void SetDebugLoggingDelegate(LynxLogDelegate *delegate);\n\nLYNX_BASE_EXTERN NSInteger AddLoggingDelegate(LynxLogDelegate *delegate);\nLYNX_BASE_EXTERN LynxLogDelegate *GetLoggingDelegate(NSInteger delegateId);\nLYNX_BASE_EXTERN void RemoveLoggingDelegate(NSInteger delegateId);\nLYNX_BASE_EXTERN NSArray<LynxLogDelegate *> *GetLoggingDelegates(void);\nLYNX_BASE_EXTERN void SetMinimumLoggingLevel(LynxLogLevel minLogLevel);\nLYNX_BASE_EXTERN void SetJSLogsFromExternalChannels(bool isOpen);\nLYNX_BASE_EXTERN LynxLogLevel GetMinimumLoggingLevel(void);\n\nLYNX_BASE_EXTERN LynxLogFunction LynxDefaultLogFunction;\n\nLYNX_BASE_EXTERN NSInteger LynxSetLogFunction(LynxLogFunction logFunction);\nLYNX_BASE_EXTERN LynxLogFunction LynxGetLogFunction(void);\n\n#ifdef __FILE_NAME__\n#define __LOG_FILE_NAME__ __FILE_NAME__\n#else\n#define __LOG_FILE_NAME__ _GetLastPath(__FILE__, sizeof(__FILE__) - 1)\n#endif\n\n#define _LynxLog(log_level, ...) \\\n  _LynxLogInternal(__LOG_FILE_NAME__, __LINE__, log_level, __VA_ARGS__)\nLYNX_BASE_EXTERN void _LynxLogInternal(const char *, int32_t, LynxLogLevel, NSString *, ...)\n    NS_FORMAT_FUNCTION(4, 5);\nLYNX_BASE_EXTERN const char *_GetLastPath(const char *filename, int32_t length);\n\n#define _LynxErrorInfo(errCode, ...) _LynxErrorInfoInternal(errCode, __VA_ARGS__)\nLYNX_BASE_EXTERN void _LynxErrorInfoInternal(NSInteger, NSString *, ...);\n\n#define _LynxErrorWarning(expression, errCode, ...) \\\n  _LynxErrorWarningInternal(expression, errCode, __VA_ARGS__)\nLYNX_BASE_EXTERN void _LynxErrorWarningInternal(bool, NSInteger, NSString *, ...);\n\n#define _LynxErrorFatal(expression, errCode, ...) \\\n  _LynxErrorFatalInternal(expression, errCode, __VA_ARGS__)\nLYNX_BASE_EXTERN void _LynxErrorFatalInternal(bool, NSInteger, NSString *, ...);\n\n#pragma mark - Debug Log\n#define LYNX_DEBUG_LOG(TAG, FORMAT, ...)                                        \\\n  do {                                                                          \\\n    fprintf(stderr, #TAG \", %s, %s\\n\", [NSStringFromSelector(_cmd) UTF8String], \\\n            [[[NSString stringWithFormat:FORMAT, ##__VA_ARGS__]                 \\\n                stringByReplacingOccurrencesOfString:@\"\\n\"                      \\\n                                          withString:@\"\"] UTF8String]);         \\\n  } while (0);\n\n#endif  // BASE_PLATFORM_DARWIN_LOG_LYNXLOG_H_\n"
  },
  {
    "path": "base/platform/darwin/log/LynxLog.mm",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <LynxBase/LynxLog.h>\n#include <map>\n\n#import <LynxServiceAPI/LynxServiceLogProtocol.h>  // nogncheck\n#import <LynxServiceAPI/ServiceAPI.h>              // nogncheck\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/log/logging_darwin.h\"\n#include \"base/src/base_trace/base_trace_event_def.h\"\n#include \"base/src/base_trace/trace_event.h\"\n\n#define LOCKED(...)             \\\n  @synchronized(gDelegateDic) { \\\n    __VA_ARGS__;                \\\n  }\n\n@implementation LynxLogDelegate\n\n- (instancetype)initWithLogFunction:(LynxLogFunction)logFunction\n                        minLogLevel:(LynxLogLevel)minLogLevel {\n  if (self = [super init]) {\n    self.logFunction = logFunction;\n    self.minLogLevel = minLogLevel;\n    self.acceptSource = NSIntegerMax;\n    self.acceptRuntimeId = -1;\n    self.shouldFormatMessage = true;\n  }\n  return self;\n}\n\n@end\n\nstatic NSInteger gDefaultDelegateId = -1;\nstatic NSInteger gCurrentId = 0;\nstatic NSMutableDictionary *gDelegateDic = [[NSMutableDictionary alloc] init];\n#ifdef DEBUG\nstatic LynxLogLevel gLogMinLevel = LynxLogLevelDebug;\n#else\nstatic LynxLogLevel gLogMinLevel = LynxLogLevelInfo;\n#endif\nstatic bool gIsJSLogsFromExternalChannelsOpen = false;\nstatic LynxLogDelegate *gDebugLoggingDelegate;\n\nvoid SetDebugLoggingDelegate(LynxLogDelegate *delegate) { gDebugLoggingDelegate = delegate; }\n\nvoid PrintLogMessageForDebug(LynxLogLevel level, NSString *message) {\n  if (gDebugLoggingDelegate == nullptr || level < gDebugLoggingDelegate.minLogLevel) {\n    return;\n  }\n  LynxLogFunction logFunction = gDebugLoggingDelegate.logFunction;\n  if (logFunction == nil) {\n    return;\n  }\n  logFunction(level, message);\n}\n\n// turn off by default\n// JS logs form external channels: recorded by business developers (mostly front-end)\nvoid SetJSLogsFromExternalChannels(bool isOpen) { gIsJSLogsFromExternalChannelsOpen = isOpen; }\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\n\nbool IsExternalChannel(lynx::base::logging::LogChannel channelType) {\n  return gIsJSLogsFromExternalChannelsOpen &&\n         channelType == lynx::base::logging::LOG_CHANNEL_LYNX_EXTERNAL;\n}\n\nvoid PrintLogMessageByLogDelegate(LogMessage *msg, const char *tag) {\n  LynxLogLevel level = (LynxLogLevel)msg->severity();\n  NSString *message = gDebugLoggingDelegate.shouldFormatMessage\n                          ? [NSString stringWithUTF8String:msg->stream().str().c_str()]\n                          : [[NSString stringWithUTF8String:msg->stream().str().c_str()]\n                                substringFromIndex:msg->messageStart()];\n  // print native's log to devtool for debug\n  PrintLogMessageForDebug(level, message);\n\n  NSArray<LynxLogDelegate *> *delegates = GetLoggingDelegates();\n  for (LynxLogDelegate *delegate in delegates) {\n    LynxLogFunction logFunction = delegate.logFunction;\n    if (logFunction == nil || level < gLogMinLevel ||\n        (delegate.acceptRuntimeId >= 0 && delegate.acceptRuntimeId != msg->runtimeId())) {\n      continue;\n    }\n    message = delegate.shouldFormatMessage\n                  ? [NSString stringWithUTF8String:msg->stream().str().c_str()]\n                  : [[NSString stringWithUTF8String:msg->stream().str().c_str()]\n                        substringFromIndex:msg->messageStart()];\n    if (message == nil) {\n      return;\n    }\n    // only upload external JS logs and console.report to logging delegate\n    switch (msg->source()) {\n      case LOG_SOURCE_JS:\n        if (IsExternalChannel(msg->ChannelType()) && (delegate.acceptSource & LynxLogSourceJS)) {\n          logFunction(level, message);\n        }\n        break;\n      case LOG_SOURCE_JS_EXT:\n        logFunction(level, message);\n        break;\n#if OS_MAC\n      // output the native log of lynx when alog is not supported on the PC(Windows & Mac)\n      case LOG_SOURCE_NATIVE:\n        logFunction(level, message);\n        break;\n#endif\n      default:\n        break;\n    }\n  }\n}\n\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n\nvoid InitLynxLog(bool print_logs_to_all_channels) {\n  id service = LynxService(LynxServiceLogProtocol);\n  void *log_address = nullptr;\n  if (service) {\n    log_address = (void *)[service getWriteFunction];\n  }\n  lynx::base::logging::InitLynxLoggingNative(\n      log_address, lynx::base::logging::PrintLogMessageByLogDelegate, print_logs_to_all_channels);\n}\n\nNSInteger AddLoggingDelegate(LynxLogDelegate *delegate) {\n  NSInteger delegateId = ++gCurrentId;\n  LOCKED([gDelegateDic setObject:delegate forKey:@(delegateId)]);\n  return delegateId;\n}\n\nLynxLogDelegate *GetLoggingDelegate(NSInteger delegateId) {\n  LOCKED(return [gDelegateDic objectForKey:@(delegateId)]);\n}\n\nvoid RemoveLoggingDelegate(NSInteger delegateId) {\n  LOCKED([gDelegateDic removeObjectForKey:@(delegateId)]);\n}\n\nNSArray<LynxLogDelegate *> *GetLoggingDelegates(void) { LOCKED(return [gDelegateDic allValues]); }\n\nvoid SetMinimumLoggingLevel(LynxLogLevel minLogLevel) {\n  [[maybe_unused]] static constexpr const char *kLogLevelName[] = {\n      \"LynxLogLevelVerbose\", \"LynxLogLevelDebug\", \"LynxLogLevelInfo\",\n      \"LynxLogLevelWarning\", \"LynxLogLevelError\", \"LynxLogLevelFatal\"};\n  if (gLogMinLevel < minLogLevel) {\n    gLogMinLevel = minLogLevel;\n    lynx::base::logging::SetLynxLogMinLevel(static_cast<int>(minLogLevel));\n    NSLog(@\"W/lynx: Reset minimum log level as %s\", kLogLevelName[gLogMinLevel]);\n  } else {\n    NSLog(@\"W/lynx: Please set a log level higher than %s to filter lynx logs!\",\n          kLogLevelName[gLogMinLevel]);\n  }\n}\n\nLynxLogLevel GetMinimumLoggingLevel(void) { return gLogMinLevel; }\n\nNSInteger LynxSetLogFunction(LynxLogFunction logFunction) {\n  LynxLogDelegate *delegate = [[LynxLogDelegate alloc] initWithLogFunction:logFunction\n                                                               minLogLevel:LynxLogLevelInfo];\n  gDefaultDelegateId = AddLoggingDelegate(delegate);\n  return gDefaultDelegateId;\n}\n\nLynxLogFunction LynxGetLogFunction(void) {\n  LynxLogDelegate *delegate = GetLoggingDelegate(gDefaultDelegateId);\n  if (!delegate) {\n    return LynxDefaultLogFunction;\n  }\n  return delegate.logFunction;\n}\n\nLynxLogFunction LynxDefaultLogFunction = ^(LynxLogLevel level, NSString *message) {\n  NSLog(@\"%s/lynx: %@\", lynx::base::logging::kLynxLogLevels[level], message);\n};\n\nvoid _LynxLogInternal(const char *file, int32_t line, LynxLogLevel level, NSString *format, ...) {\n  BASE_TRACE_EVENT(LYNX_BASE_TRACE_CATEGORY, LYNX_BASE_LOG_INTERNAL);\n  @autoreleasepool {\n    va_list args;\n    va_start(args, format);\n    NSString *messageTail = [[NSString alloc] initWithFormat:format arguments:args];\n    va_end(args);\n    NSString *message = [[NSString alloc] initWithFormat:@\"[%s:%s(%d)] %@\",\n                                                         lynx::base::logging::kLynxLogLevels[level],\n                                                         file, line, messageTail];\n    // print log\n    if (level >= gLogMinLevel) {\n      lynx::base::logging::InternalLogNative(file, line, (int)level, [message UTF8String]);\n    }\n\n    // print log to devtool for debug\n    PrintLogMessageForDebug(level, message);\n  }\n}\n\nvoid _LynxErrorInfo(NSInteger errorCode, NSString *format, ...) {\n  va_list args;\n  va_start(args, format);\n  NSString *message = [[NSString alloc] initWithFormat:format arguments:args];\n  va_end(args);\n  LynxInfo(static_cast<int>(errorCode), message.UTF8String);\n}\n\nvoid _LynxErrorWarning(bool expression, NSInteger errorCode, NSString *format, ...) {\n  if (!expression) {\n    va_list args;\n    va_start(args, format);\n    NSString *message = [[NSString alloc] initWithFormat:format arguments:args];\n    va_end(args);\n    LynxWarning(expression, static_cast<int>(errorCode), message.UTF8String);\n  }\n}\n\nvoid _LynxErrorFatal(bool expression, NSInteger errorCode, NSString *format, ...) {\n  if (!expression) {\n    va_list args;\n    va_start(args, format);\n    NSString *message = [[NSString alloc] initWithFormat:format arguments:args];\n    va_end(args);\n    LynxFatal(expression, static_cast<int>(errorCode), message.UTF8String);\n  }\n}\n\nconst char *_GetLastPath(const char *filename, int32_t length) {\n  return lynx::base::PathUtils::GetLastPath(filename, length);\n}\n"
  },
  {
    "path": "base/platform/darwin/trace/LynxBaseTrace.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_PLATFORM_DARWIN_TRACE_LYNXBASETRACE_H_\n#define BASE_PLATFORM_DARWIN_TRACE_LYNXBASETRACE_H_\n\n#import <Foundation/Foundation.h>\n#import <LynxBase/LynxBaseDefines.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\nLYNX_BASE_EXTERN void InitLynxBaseTrace(void);\n\nNS_ASSUME_NONNULL_END\n\n#endif  // BASE_PLATFORM_DARWIN_TRACE_LYNXBASETRACE_H_\n"
  },
  {
    "path": "base/platform/darwin/trace/LynxBaseTrace.mm",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <LynxBase/LynxBaseTrace.h>\n#import <LynxServiceAPI/LynxServiceTraceProtocol.h>\n#import <LynxServiceAPI/ServiceAPI.h>\n\n#include \"base/include/base_trace/trace_event_utils.h\"\n\nvoid InitLynxBaseTrace(void) {\n  static id<LynxServiceTraceProtocol> trace_service = LynxService(LynxServiceTraceProtocol);\n  if (!trace_service) {\n    return;\n  }\n  lynx::base::trace::SetTraceBackend(\n      (lynx::base::trace::trace_backend_ptr)[trace_service getDefaultTraceFunction]);\n}\n"
  },
  {
    "path": "base/platform/harmony/.gitignore",
    "content": "/node_modules\n/oh_modules\n/.preview\n/build\n/.cxx\n/.test\n/libs/**/*.so\nBuildProfile.ets\noh-package-lock.json5\n"
  },
  {
    "path": "base/platform/harmony/.ohpmignore",
    "content": "**/*.cc\n**/*.h\n**/*.gn\n"
  },
  {
    "path": "base/platform/harmony/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nassert(is_harmony)\n\nshared_library(\"lynx_base\") {\n  sources = [ \"src/cpp/entry.cc\" ]\n  output_name = \"lynxbase\"\n\n  deps = [ \"../../src/:base_group\" ]\n  cflags = []\n  libs = [\n    \"ace_napi.z\",\n    \"uv\",\n    \"native_vsync\",\n    \"hilog_ndk.z\",\n  ]\n\n  configs += [ \"../../../platform/harmony/lynx_harmony:harmony_public_config\" ]\n}\n"
  },
  {
    "path": "base/platform/harmony/Index.ets",
    "content": "export * from \"./src/main/ets/Index\";"
  },
  {
    "path": "base/platform/harmony/LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n"
  },
  {
    "path": "base/platform/harmony/README.md",
    "content": "LynxBase\n=======\n\nLynxBase is a foundational library that provides a collection of common utilities and core functionalities for all components within the Lynx ecosystem.\n\n## Overview\n\nThis module provides essential utilities for development within the Lynx ecosystem, including a `message_loop`, `log` utilities, `thread` management tools, `string` manipulation functions, and `value` helpers. Its primary purpose is to streamline the development process by offering a solid foundation of shared capabilities.\n\n## Installation\n\n```bash\nohpm install @lynx/lynx_base\n```\n\n## Usage\n\nYou can add a dependency in your `oh-package.json5` file like this:\n\n```json5\n{\n  \"dependencies\": {\n    \"@lynx/lynx_base\": \"0.0.1-alpha.1\"\n  }\n}\n```\n"
  },
  {
    "path": "base/platform/harmony/build-profile.json5",
    "content": "{\n  \"apiType\": \"stageMode\",\n  \"buildOption\": {\n    \"arkOptions\": {\n      \"byteCodeHar\": false,\n      \"buildProfileFields\": {\n        \"COMMIT_HASH\": \"COMMIT_HASH_PLACE_HOLDER_TO_DISALLOW_PUBLISH_BY_ACCIDENT\",\n        \"VERSION\": \"VERSION_PLACE_HOLDER_TO_DISALLOW_PUBLISH_BY_ACCIDENT\",\n      },\n      \"runtimeOnly\": {\n        \"sources\": []\n      }\n    }\n  },\n  \"buildOptionSet\": [\n    {\n      \"name\": \"debug\",\n    },\n    {\n      \"name\": \"release\",\n      \"copyFrom\": \"debug\",\n      \"arkOptions\": {\n        \"obfuscation\": {\n          \"ruleOptions\": {\n            \"enable\": true,\n            \"files\": [\n              \"./obfuscation-rules.txt\"\n            ]\n          },\n          \"consumerFiles\": [\n            \"./consumer-rules.txt\"\n          ]\n        }\n      }\n    },\n  ],\n  \"targets\": [\n    {\n      \"name\": \"default\"\n    }\n  ]\n}\n"
  },
  {
    "path": "base/platform/harmony/consumer-rules.txt",
    "content": "# Obfuscation options:\n-disable-obfuscation\n"
  },
  {
    "path": "base/platform/harmony/hvigorfile.ts",
    "content": "import { harTasks } from '@ohos/hvigor-ohos-plugin';\n\nexport default {\n  system: harTasks /* Built-in plugin of Hvigor. It cannot be modified. */,\n  plugins: [] /* Custom plugin to extend the functionality of Hvigor. */,\n};\n"
  },
  {
    "path": "base/platform/harmony/obfuscation-rules.txt",
    "content": "# Define project specific obfuscation rules here.\n# You can include the obfuscation configuration files in the current module's build-profile.json5.\n#\n# Obfuscation options:\n# -disable-obfuscation: disable all obfuscations\n# -enable-property-obfuscation: obfuscate the property names\n# -enable-toplevel-obfuscation: obfuscate the names in the global scope\n# -compact: remove unnecessary blank spaces and all line feeds\n# -remove-log: remove all console.* statements\n# -print-namecache: print the name cache that contains the mapping from the old names to new names\n# -apply-namecache: reuse the given cache file\n\n# Keep options:\n# -keep-property-name: specifies property names that you want to keep\n# -keep-global-name: specifies names that you want to keep in the global scope"
  },
  {
    "path": "base/platform/harmony/oh-package.json5",
    "content": "{\n  \"name\": \"@lynx/lynx_base\",\n  \"license\": \"Apache-2.0\",\n  \"devDependencies\": {\n  },\n  \"main\": \"Index.ets\",\n  \"version\": \"@param:dependencies.lynx_version\",\n  \"dependencies\": {\n    \"liblynxbase.so\": \"file:./src/cpp/types/liblynxbase\",\n  },\n  \"repository\": \"https://github.com/lynx-family/lynx\",\n  \"author\": \"lynx\",\n  \"description\": \"Lynx Base\"\n}\n"
  },
  {
    "path": "base/platform/harmony/src/cpp/entry.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <napi/native_api.h>\n\n#include \"base/src/base_trace/trace_harmony.h\"\n#include \"base/src/log/logging_harmony.h\"\n\nEXTERN_C_START static napi_value InitLynxBase(napi_env env,\n                                              napi_value exports) {\n  lynx::base::logging::LynxLog::Init(env, exports);\n  lynx::base::trace::LynxBaseTrace::Init(env, exports);\n  return exports;\n}\n\nEXTERN_C_END\n\nstatic napi_module lynx_base_module = {\n    .nm_version = 1,\n    .nm_flags = 0,\n    .nm_filename = nullptr,\n    .nm_register_func = InitLynxBase,\n    .nm_modname = \"lynxbase\",\n    .nm_priv = ((void *)0),\n    .reserved = {0},\n};\n\nextern \"C\" __attribute__((constructor)) void RegisterEntryModule(void) {\n  napi_module_register(&lynx_base_module);\n}\n"
  },
  {
    "path": "base/platform/harmony/src/cpp/types/liblynxbase/index.d.ts",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\nexport const nativeInitLynxLogWriteFunction: (logWriteFunctionAddress: number) => void;\n\nexport const nativeInitLynxLog: (isPrintLogToAllChannel: boolean) => void;\n\nexport const nativeUseSysLog: (open: boolean) => void;\n\nexport const nativeInternalLog: (level: number, tag: string, message: string) => void;\n\nexport const nativeSetMinLogLevel: (level: number) => void;\n\nexport const nativeInitLynxBaseTrace: (address: number) => void;\n"
  },
  {
    "path": "base/platform/harmony/src/cpp/types/liblynxbase/oh-package.json5",
    "content": "{\n  \"name\": \"liblynxbase.so\",\n  \"types\": \"./index.d.ts\",\n  \"version\": \"0.0.1\",\n  \"description\": \"Lynx Base.\"\n}"
  },
  {
    "path": "base/platform/harmony/src/main/ets/Index.ets",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\nexport { LynxBaseEnv } from './LynxBaseEnv'\n\nexport { LynxLog, LynxBaseLogLevel } from './log/LynxLog'\n\nexport { LynxBaseTrace } from './LynxBaseTrace'\n\nexport { LynxBaseServiceCenter, LynxBaseServiceType } from './service/LynxBaseServiceCenter'\n\nexport { ILynxBaseLogService, LogOutputChannelType } from './service/ILynxBaseLogService'\n\nexport { ILynxBaseTraceService } from './service/ILynxBaseTraceService'\n"
  },
  {
    "path": "base/platform/harmony/src/main/ets/LynxBaseEnv.ets",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\nimport { LynxLog } from './log/LynxLog';\nimport { LynxBaseTrace } from './LynxBaseTrace';\n\nconst TAG = 'LynxBaseEnv';\n\nclass LynxBaseEnvironment {\n  private initialized = false;\n\n  public initialize(printLogsToAllChannels: boolean) {\n    if (this.initialized) {\n      return;\n    }\n    LynxLog.initLynxLog(printLogsToAllChannels);\n    LynxBaseTrace.initLynxBaseTrace();\n    this.initialized = true;\n  }\n}\n\nexport const LynxBaseEnv = new LynxBaseEnvironment();\n"
  },
  {
    "path": "base/platform/harmony/src/main/ets/LynxBaseTrace.ets",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\nimport lynxbase from 'liblynxbase.so';\nimport { LynxLog } from './log/LynxLog';\nimport { LynxBaseServiceCenter, LynxBaseServiceType } from './service/LynxBaseServiceCenter';\nimport { ILynxBaseTraceService } from './service/ILynxBaseTraceService';\n\nconst TAG = 'LynxBaseTrace';\n\nexport class LynxBaseTrace {\n  private static service?: ILynxBaseTraceService;\n\n  static initLynxBaseTrace(): boolean {\n    LynxBaseTrace.service = LynxBaseServiceCenter.getService<ILynxBaseTraceService>(LynxBaseServiceType.Trace);\n    if (LynxBaseTrace.service) {\n      let address: number | undefined = LynxBaseTrace.service.getDefaultTraceFunction();\n      if (address != 0) {\n        lynxbase.nativeInitLynxBaseTrace(address);\n        LynxLog.i(TAG,\n          'LynxBaseTrace init successfully by custom LynxBaseTraceService. function native address is ' + address);\n        return true;\n      }\n    }\n    LynxLog.i(TAG, 'failed to init LynxBaseTrace dependency');\n    return false;\n  }\n}\n"
  },
  {
    "path": "base/platform/harmony/src/main/ets/log/LynxLog.ets",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\nimport hilog from '@ohos.hilog';\nimport BuildProfile from '../../../../BuildProfile';\nimport lynxbase from 'liblynxbase.so';\nimport { LynxBaseServiceCenter, LynxBaseServiceType } from '../service/LynxBaseServiceCenter';\nimport { ILynxBaseLogService } from '../service/ILynxBaseLogService';\n\nconst DOMAIN = 0xff00;\n\nexport enum LynxBaseLogLevel {\n  VERBOSE = 0,\n  DEBUG = 1,\n  INFO = 2,\n  WARN = 3,\n  ERROR = 4,\n}\n\nexport class LynxLog {\n  private static format: string = '%{public}s';\n  private static mMinLogLevel: number = BuildProfile.DEBUG ? LynxBaseLogLevel.DEBUG : LynxBaseLogLevel.INFO;\n  private static mService: ILynxBaseLogService | undefined;\n  private static mUseSysLog: boolean = false;\n\n  static useSysLog(useSysLog: boolean): void {\n    LynxLog.mUseSysLog = useSysLog;\n    lynxbase.nativeUseSysLog(LynxLog.mUseSysLog);\n  }\n\n  static initLynxLog(isPrintLogToAllChannel: boolean): void {\n    LynxLog.mService = LynxBaseServiceCenter.getService<ILynxBaseLogService>(LynxBaseServiceType.Log);\n    if (LynxLog.mService) {\n      if (LynxLog.mService.isLogOutputByPlatform()) {\n        LynxLog.useSysLog(true);\n      }\n      lynxbase.nativeInitLynxLogWriteFunction(LynxLog.mService.getDefaultWriteFunction());\n    }\n    lynxbase.nativeInitLynxLog(isPrintLogToAllChannel);\n  }\n\n  static setMinimumLoggingLevel(level: LynxBaseLogLevel): void {\n    let logLevelName: Array<string> = [\"DEBUG\", \"INFO\", \"WARN\", \"ERROR\"];\n    if (LynxLog.mMinLogLevel < level) {\n      LynxLog.mMinLogLevel = level;\n      lynxbase.nativeSetMinLogLevel(level);\n      hilog.warn(DOMAIN, \"lynxbase\", LynxLog.format, `Reset minimum log level as ${logLevelName[LynxLog.mMinLogLevel]}`);\n    } else {\n      hilog.warn(DOMAIN, \"lynxbase\", LynxLog.format,\n        `Please set a log level higher than ${logLevelName[LynxLog.mMinLogLevel]}} to filter lynx logs!`);\n    }\n  }\n\n  static v(tag: string, message: string): void {\n    LynxLog.internalLynxLog(LynxBaseLogLevel.VERBOSE, tag, message);\n  }\n\n  static d(tag: string, message: string): void {\n    LynxLog.internalLynxLog(LynxBaseLogLevel.DEBUG, tag, message);\n  }\n\n  static i(tag: string, message: string): void {\n    LynxLog.internalLynxLog(LynxBaseLogLevel.INFO, tag, message);\n  }\n\n  static w(tag: string, message: string): void {\n    LynxLog.internalLynxLog(LynxBaseLogLevel.WARN, tag, message);\n  }\n\n  static e(tag: string, message: string): void {\n    LynxLog.internalLynxLog(LynxBaseLogLevel.ERROR, tag, message);\n  }\n\n  static r(tag: string, message: string): void {\n    LynxLog.internalLynxLog(LynxBaseLogLevel.ERROR, tag, message);\n  }\n\n  private static internalLynxLog(level: LynxBaseLogLevel, tag: string, message: string): void {\n    if (level >= LynxLog.mMinLogLevel) {\n      if (LynxLog.mUseSysLog && LynxLog.mService) {\n        LynxLog.mService.logByPlatform(level, tag, message);\n      }\n      lynxbase.nativeInternalLog(level, tag, message);\n    }\n  }\n}\n"
  },
  {
    "path": "base/platform/harmony/src/main/ets/service/IBaseServiceProvider.ets",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\nexport interface IBaseServiceProvider {}\n"
  },
  {
    "path": "base/platform/harmony/src/main/ets/service/ILynxBaseLogService.ets",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\nimport { IBaseServiceProvider } from './IBaseServiceProvider';\n\nexport enum LogOutputChannelType {\n  Native,\n  Platform,\n}\n\n/*\n * The selection of lynx log consumption channels is supported. Developers can choose to consume\n * lynx logs at either the platform layer or the C++ layer as required.\n * - For platform layer channel: Developers need to overload ILynxBaseLogService::logByPlatform;\n * - For C++ layer channel: Developers are required to introduce the corresponding C/C++ library,\n * overload the function ILynxBaseLogService::getDefaultWriteFunction, and return the address of the C++\n * layer log processing function via this function;\n * */\nexport interface ILynxBaseLogService extends IBaseServiceProvider {\n  /*\n   * Consume lynx logs on the platform layer and it is necessary to implement log capabilities\n   * independently.\n   * */\n  logByPlatform(level: number, tag: string, message: string): void;\n\n  /*\n   * Whether to consume lynx logs on the platform layer.\n   * If it is true, the logByPlatform function will be called; otherwise, the log function in the\n   * C++\n   * */\n  isLogOutputByPlatform(): boolean;\n\n  /*\n   * For C++ layer channel: Developers are required to introduce the corresponding C/C++ library,\n   * Return the address of the C/C++ log function.\n   * */\n  getDefaultWriteFunction(): number;\n}\n"
  },
  {
    "path": "base/platform/harmony/src/main/ets/service/ILynxBaseTraceService.ets",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\nimport { IBaseServiceProvider } from './IBaseServiceProvider';\n\n/*\n * Developers are required to introduce the corresponding C/C++ library, overload the function\n * ILynxBaseTraceService::getDefaultTraceFunction, and return the address of the C++ layer log\n * processing function via this function.\n * */\nexport interface ILynxBaseTraceService extends IBaseServiceProvider {\n  /*\n   * For C++ layer channel: Developers are required to introduce the corresponding C/C++ library,\n   * Return the address of the C/C++ trace function.\n   * */\n  getDefaultTraceFunction(): number;\n}\n"
  },
  {
    "path": "base/platform/harmony/src/main/ets/service/LynxBaseServiceCenter.ets",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\nimport { IBaseServiceProvider } from './IBaseServiceProvider';\n\nconst TAG = 'LynxBaseServiceCenter';\n\nexport enum LynxBaseServiceType {\n  Log,\n  Trace,\n}\n\nclass LynxBaseServiceCenterImpl {\n  private serviceMap: Map<LynxBaseServiceType, IBaseServiceProvider> = new Map();\n\n  constructor() {\n  }\n\n  getService<T>(serviceType: LynxBaseServiceType): T | undefined {\n    return this.serviceMap.get(serviceType) as T;\n  }\n\n  registerService(serviceType: LynxBaseServiceType, instance: IBaseServiceProvider) {\n    this.serviceMap.set(serviceType, instance);\n  }\n\n  unregisterService(serviceType: LynxBaseServiceType,) {\n    this.serviceMap.delete(serviceType);\n  }\n}\n\nexport const LynxBaseServiceCenter = new LynxBaseServiceCenterImpl()"
  },
  {
    "path": "base/platform/harmony/src/main/module.json5",
    "content": "{\n  \"module\": {\n    \"name\": \"lynx_base\",\n    \"type\": \"har\",\n    \"deviceTypes\": [\n      \"default\",\n      \"tablet\",\n      \"2in1\"\n    ]\n  }\n}\n"
  },
  {
    "path": "base/platform/windows/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nassert(is_win)\n\nstatic_library(\"lynx_base\") {\n  sources = [\n    \"lynx_base_env.cc\",\n    \"lynx_base_env.h\",\n  ]\n  public_deps = [ \"../../src:base_group\" ]\n}\n"
  },
  {
    "path": "base/platform/windows/lynx_base_env.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/platform/windows/lynx_base_env.h\"\n\n#include \"base/include/base_trace/trace_event_utils.h\"\n#include \"base/include/log/logging_base.h\"\n#include \"base/trace/native/trace_event.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace {\nvoid trace_backend(const char* category, const char* name,\n                   trace::BaseTraceEventType phase) {\n  switch (phase) {\n    case trace::BaseTraceEventType::TYPE_SLICE_BEGIN:\n      TRACE_EVENT_BEGIN(category, name);\n      break;\n    case trace::BaseTraceEventType::TYPE_SLICE_END:\n      TRACE_EVENT_END(category);\n      break;\n    case trace::BaseTraceEventType::TYPE_INSTANT:\n      TRACE_EVENT_INSTANT(category, name);\n      break;\n    default:\n      break;\n  }\n}\n\nvoid initBaseTrace() { trace::SetTraceBackend(trace_backend); }\n}  // namespace\n\nLynxBaseEnv* LynxBaseEnv::Instance() {\n  static LynxBaseEnv instance;\n  return &instance;\n}\n\nvoid LynxBaseEnv::Init(bool is_print_log_to_all_channel) {\n  lynx::InitLynxBaseLog(is_print_log_to_all_channel);\n  initBaseTrace();\n}\n\nvoid LynxBaseEnv::OnlyInitBaseTrace() { initBaseTrace(); }\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/platform/windows/lynx_base_env.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_PLATFORM_WINDOWS_LYNX_BASE_ENV_H_\n#define BASE_PLATFORM_WINDOWS_LYNX_BASE_ENV_H_\n\nnamespace lynx {\nnamespace base {\nclass LynxBaseEnv {\n public:\n  LynxBaseEnv() = default;\n  ~LynxBaseEnv() = default;\n  static LynxBaseEnv* Instance();\n\n  void Init(bool is_print_log_to_all_channel);\n  void OnlyInitBaseTrace();\n};\n}  // namespace base\n}  // namespace lynx\n#endif  // BASE_PLATFORM_WINDOWS_LYNX_BASE_ENV_H_\n"
  },
  {
    "path": "base/src/BUILD.gn",
    "content": "# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../config.gni\")\nimport(\"../../oliver/oliver.gni\")\nimport(\"../../testing/test.gni\")\nimport(\"../include/base_public_headers.gni\")\nimport(\"base.gni\")\n\nstatic_library(\"base_static\") {\n  sources = [\n    \"../platform/windows/lynx_base_env.cc\",\n    \"../platform/windows/lynx_base_env.h\",\n  ]\n  deps = [ \":base_group\" ]\n  configs += [ \":base_private_config\" ]\n}\n\ngroup(\"base_group\") {\n  public_deps = [\n    \":base_core\",\n    \":base_log\",\n    \":base_trace\",\n    \":datauri_utils\",\n    \":string_utils\",\n    \":time_utils\",\n    \":values\",\n  ]\n}\n\ngroup(\"base_for_oliver\") {\n  public_deps = [\n    \":base_log\",\n    \":base_values\",\n    \":string_utils\",\n    \":time_utils\",\n  ]\n  if (is_oliver_ssr || is_oliver_node_lynx) {\n    public_deps += [\n      \":base_core\",\n      \":base_trace\",\n    ]\n  }\n}\n\nconfig(\"base_public_config\") {\n  defines = []\n  cflags = []\n  if (!base_export_symbols) {\n    defines += [ \"BASE_NO_EXPORT\" ]\n  }\n  if (!is_debug) {\n    defines += [ \"NDEBUG\" ]\n  }\n\n  if (is_android) {\n    defines += [\n      \"GNU_SUPPORT=1\",\n      \"OS_ANDROID=1\",\n      \"OS_POSIX=1\",\n    ]\n  } else if (is_ios) {\n    defines += [\n      \"OS_IOS=1\",\n      \"OS_POSIX=1\",\n    ]\n  } else if (is_mac) {\n    defines += [\n      \"OS_OSX=1\",\n      \"OS_POSIX=1\",\n    ]\n  } else if (is_win) {\n    defines += [\n      \"OS_WIN=1\",\n      \"_CRT_SECURE_NO_WARNINGS=1\",\n      \"BASE_NO_EXPORT\",\n      \"lynx_EXPORTS\",\n    ]\n  }\n  configs = [ \":base_log_public_config\" ]\n}\n\nconfig(\"base_private_config\") {\n  cflags = [\n    \"-ffunction-sections\",\n    \"-fdata-sections\",\n    \"-fno-exceptions\",\n    \"-fvisibility=hidden\",\n    \"-fvisibility-inlines-hidden\",\n    \"-fno-short-enums\",\n    \"-fno-stack-protector\",\n    \"-fno-strict-aliasing\",\n    \"-Wextra\",\n    \"-Wno-unused-parameter\",\n  ]\n  cflags_c = []\n  cflags_cc = [\n    \"-fno-rtti\",\n    \"-std=c++17\",\n    \"-Wl,-ldl\",\n    \"-Wno-unused-command-line-argument\",\n  ]\n  configs = []\n  if (is_debug) {\n    cflags_cc += [ \"-g\" ]\n  }\n\n  defines = []\n}\n\nconfig(\"base_log_public_config\") {\n  defines = []\n  if (enable_lite && enable_lite_production) {\n    defines += [ \"LYNX_MIN_LOG_LEVEL=5\" ]\n  } else if (is_oliver_ssr) {\n    defines += [ \"LYNX_MIN_LOG_LEVEL=5\" ]\n  } else if (is_debug) {\n    # In debug mode, logs of all levels will be output.\n    defines += [ \"LYNX_MIN_LOG_LEVEL=0\" ]\n  } else {\n    # In release mode, logs with a priority higher than LOG_DEBUG will be output.\n    defines += [ \"LYNX_MIN_LOG_LEVEL=2\" ]\n  }\n  if (is_mac) {\n    configs = darwin_config\n  }\n}\n\nlynx_base_source_set(\"base_log_headers\") {\n  public_configs = [ \":base_log_public_config\" ]\n  sources = base_log_public_headers\n}\n\nlynx_base_source_set(\"base_log\") {\n  public_configs = [ \":base_log_public_config\" ]\n  visibility = [\n    \":*\",\n    \"../platform/darwin:*\",\n  ]\n  public = base_log_public_headers\n  public_deps = [ \":base_log_headers\" ]\n\n  sources = [\n    \"debug/lynx_error.cc\",\n    \"log/alog_wrapper.cc\",\n    \"log/log_stream.cc\",\n    \"log/logging.cc\",\n  ]\n  if (is_android) {\n    sources += [ \"log/logging_android.cc\" ]\n  } else if (is_apple) {\n    sources += [ \"log/logging_darwin.mm\" ]\n  } else if (is_harmony) {\n    sources += [\n      \"log/logging_harmony.cc\",\n      \"log/logging_harmony.h\",\n    ]\n  } else if (is_win) {\n    sources += [ \"log/logging_base.cc\" ]\n  } else if (is_linux) {\n    sources += [ \"log/logging_base.cc\" ]\n  }\n  if (!is_win) {\n    # Windows do not overwrite this file\n    sources += [ \"debug/backtrace.cc\" ]\n  }\n\n  deps = [ \"../../third_party/rapidjson:rapidjson\" ]\n}\n\nlynx_base_source_set(\"time_utils\") {\n  visibility = [\n    \":*\",\n    \"../platform/darwin:*\",\n  ]\n  public = time_utils_public_headers\n  sources = time_utils_public_headers + [ \"timer/time_utils.cc\" ]\n}\n\nlynx_base_source_set(\"base_trace\") {\n  visibility = [\n    \":*\",\n    \"../platform/darwin:*\",\n  ]\n  public = base_trace_public_headers\n  sources = base_trace_public_headers + [\n              \"base_trace/base_trace_event_def.h\",\n              \"base_trace/trace_event.h\",\n              \"base_trace/trace_event_utils.cc\",\n            ]\n  if (is_android) {\n    sources += [ \"base_trace/trace_android.cc\" ]\n  } else if (is_harmony) {\n    sources += [ \"base_trace/trace_harmony.cc\" ]\n  }\n}\n\nlynx_base_source_set(\"base_core\") {\n  visibility = [\n    \":*\",\n    \"../platform/darwin:*\",\n  ]\n  public = base_core_public_headers\n  public_deps = [\n    \":string_utils\",\n    \":time_utils\",\n    \":values\",\n  ]\n\n  sources = base_core_public_headers + [\n              \"./fml/raster_thread_merger.cc\",\n              \"./fml/shared_thread_merger.cc\",\n              \"file_utils.cc\",\n              \"fml/concurrent_message_loop.cc\",\n              \"fml/cpu_affinity.cc\",\n              \"fml/memory/task_runner_checker.cc\",\n              \"fml/message_loop.cc\",\n              \"fml/message_loop_impl.cc\",\n              \"fml/message_loop_task_queues.cc\",\n              \"fml/synchronization/count_down_latch.cc\",\n              \"fml/synchronization/semaphore.cc\",\n              \"fml/synchronization/sync_switch.cc\",\n              \"fml/synchronization/waitable_event.cc\",\n              \"fml/task_runner.cc\",\n              \"fml/task_source.cc\",\n              \"fml/thread.cc\",\n              \"fml/thread_name_setter.h\",\n              \"fml/time/time_point.cc\",\n              \"fml/time/timer.cc\",\n              \"fml/unique_fd.cc\",\n              \"md5.cc\",\n              \"thread/timed_task.cc\",\n              \"vector.cc\",\n            ]\n\n  if (is_android || is_linux) {\n    sources += [ \"fml/platform/linux/timerfd.cc\" ]\n  }\n\n  if (is_android) {\n    sources += [\n      \"fml/platform/android/cpu_affinity.cc\",\n      \"fml/platform/android/thread_config_setter_android.cc\",\n      \"fml/platform/posix/thread_name_setter_posix.cc\",\n      \"fml/synchronization/shared_mutex_std.cc\",\n      \"platform/android/java_type.cc\",\n      \"platform/android/jni_convert_helper.cc\",\n      \"platform/android/jni_utils.cc\",\n      \"platform/android/scoped_java_ref.cc\",\n    ]\n    if (enable_unittests) {\n      # Use NDK implementation of message loop instead of JNI as UT doesn't have a JVM.\n      # Also ref to https://github.com/flutter/engine/pull/29889/files and\n      # https://github.com/flutter/engine/pull/31750 for historical reasons.\n      sources += [ \"fml/platform/android/message_loop_android_ndk.cc\" ]\n    } else {\n      sources += [ \"fml/platform/android/message_loop_android.cc\" ]\n    }\n  } else if (is_harmony) {\n    sources += [\n      \"fml/platform/harmony/message_loop_harmony.cc\",\n      \"fml/platform/linux/timerfd.cc\",\n      \"fml/platform/posix/thread_name_setter_posix.cc\",\n      \"fml/synchronization/shared_mutex_std.cc\",\n      \"platform/harmony/harmony_vsync_manager.cc\",\n      \"platform/harmony/napi_util.cc\",\n    ]\n  } else if (is_linux) {\n    sources += [\n      \"fml/platform/posix/thread_name_setter_posix.cc\",\n      \"fml/synchronization/shared_mutex_std.cc\",\n    ]\n    if (!use_custom_message_loop) {\n      sources += [ \"fml/platform/linux/message_loop_linux.cc\" ]\n    }\n  } else if (is_apple) {\n    sources += [\n      \"fml/platform/darwin/thread_config_setter_darwin.mm\",\n      \"fml/platform/darwin/thread_name_setter_darwin.cc\",\n      \"fml/synchronization/shared_mutex_posix.cc\",\n    ]\n\n    if (!use_custom_message_loop) {\n      sources += [ \"fml/platform/darwin/message_loop_darwin.mm\" ]\n    }\n\n    flutter_cflags_objc = [\n      \"-Werror=overriding-method-mismatch\",\n      \"-Werror=undeclared-selector\",\n    ]\n    flutter_cflags_objcc = flutter_cflags_objc\n\n    cflags_objc = flutter_cflags_objc\n    cflags_objcc = flutter_cflags_objcc\n    frameworks = [ \"Foundation.framework\" ]\n  } else if (is_win) {\n    sources += [\n      \"fml/platform/win/message_loop_win.cc\",\n      \"fml/platform/win/task_runner_win32.cc\",\n      \"fml/platform/win/task_runner_win32_window.cc\",\n      \"fml/platform/win/thread_name_setter_win.cc\",\n      \"fml/synchronization/shared_mutex_std.cc\",\n      \"string/string_conversion_win.cc\",\n    ]\n  }\n\n  deps = [ \":datauri_utils\" ]\n\n  libs = []\n  if (is_android) {\n    libs += [ \"android\" ]\n  }\n  if (is_win) {\n    libs += [ \"user32.lib\" ]\n  }\n}\n\nif (enable_unittests) {\n  unittest_exec(\"base_unittests_exec\") {\n    testonly = true\n    sources = [\n      \"algorithm_unittest.cc\",\n      \"auto_create_optional_unittest.cc\",\n      \"auto_reset_unittest.cc\",\n      \"boost/unordered_unittest.cc\",\n      \"bundled_optional_unittest.cc\",\n      \"closure_unittest.cc\",\n      \"concurrent_queue_unittest.cc\",\n      \"datauri_utils_unittest.cc\",\n      \"debug/lynx_error_unittest.cc\",\n      \"expected_unittest.cc\",\n      \"file_utils_unittest.cc\",\n      \"flex_optional_unittest.cc\",\n      \"fml/cpu_affinity_unittests.cc\",\n      \"fml/hash_combine_unittests.cc\",\n      \"fml/memory/ref_counted_unittest.cc\",\n      \"fml/memory/task_runner_checker_unittest.cc\",\n      \"fml/memory/weak_ptr_unittest.cc\",\n      \"fml/message_loop_impl_unittests.cc\",\n      \"fml/message_loop_task_queues_merge_unmerge_unittests.cc\",\n      \"fml/message_loop_task_queues_unittests.cc\",\n      \"fml/message_loop_unittests.cc\",\n      \"fml/raster_thread_merger_unittests.cc\",\n      \"fml/synchronization/count_down_latch_unittests.cc\",\n      \"fml/synchronization/semaphore_unittest.cc\",\n      \"fml/synchronization/sync_switch_unittest.cc\",\n      \"fml/synchronization/waitable_event_unittest.cc\",\n      \"fml/task_runner_unittests.cc\",\n      \"fml/task_source_unittests.cc\",\n      \"fml/thread_unittests.cc\",\n      \"fml/time/chrono_timestamp_provider.cc\",\n      \"fml/time/time_delta_unittest.cc\",\n      \"fml/time/time_point_unittest.cc\",\n      \"fml/time/time_unittest.cc\",\n      \"geometry_unittest.cc\",\n      \"hybrid_map_unittest.cc\",\n      \"linked_hash_map_unittest.cc\",\n      \"log/log_stream_unittest.cc\",\n      \"lynx_actor_unittest.cc\",\n      \"path_utils_unittest.cc\",\n      \"sorted_for_each_unittest.cc\",\n      \"string/string_number_convert_unittest.cc\",\n      \"string/string_utils_unittest.cc\",\n      \"thread/timed_task_unittest.cc\",\n      \"timer/time_utils_unittest.cc\",\n      \"to_underlying_unittest.cc\",\n      \"type_traits_addon_unittest.cc\",\n      \"value/base_value_unittest.cc\",\n      \"value/lynx_value_extended_empty.cc\",\n      \"value/path_parser_unittest.cc\",\n      \"vector_unittest.cc\",\n      \"version_unittest.cc\",\n    ]\n    if (is_mac) {\n      sources += [ \"fml/platform/darwin/cf_utils_unittests.mm\" ]\n    }\n    deps = [\n      \":base_core\",\n      \":base_log\",\n      \":base_values\",\n      \"//third_party/googletest:gtest\",\n    ]\n  }\n}\n\nlynx_base_source_set(\"string_utils\") {\n  visibility = [\n    \":*\",\n    \"../platform/darwin:*\",\n  ]\n  public = string_utils_public_headers\n  sources = string_utils_public_headers + [\n              \"string/quickjs_dtoa.c\",\n              \"string/string_number_convert.cc\",\n              \"string/string_utils.cc\",\n            ]\n}\n\n# TODO(chenyouhui): Merge 'values' into 'base_values' target\nconfig(\"value_config\") {\n  cflags_cc = [\n    \"-Wno-missing-field-initializers\",\n    \"-Wno-error=missing-field-initializers\",\n  ]\n}\nlynx_base_source_set(\"base_values\") {\n  public = base_values_public_headers\n  sources = base_values_public_headers + [\n              \"value/base_value.cc\",\n              \"value/base_value_print.cc\",\n              \"value/byte_array.cc\",\n              \"value/lynx_value_api_impl.cc\",\n              \"value/path_parser.cc\",\n              \"value/table.cc\",\n            ]\n  public_deps = [ \":values\" ]\n  configs = [ \":value_config\" ]\n}\n\nlynx_base_source_set(\"base_values_extended\") {\n  sources = [ \"value/lynx_value_extended_empty.cc\" ]\n}\n\nlynx_base_source_set(\"values\") {\n  visibility = [\n    \":*\",\n    \"../platform/darwin:*\",\n  ]\n  public = values_public_headers\n\n  sources = values_public_headers + [ \"value/base_string.cc\" ]\n}\n\nlynx_base_source_set(\"datauri_utils\") {\n  visibility = [\n    \":*\",\n    \"../platform/darwin:*\",\n  ]\n  public = datauri_utils_public_headers\n  sources = datauri_utils_public_headers + [ \"datauri_utils.cc\" ]\n\n  public_deps = [ \"//third_party/modp_b64\" ]\n}\n"
  },
  {
    "path": "base/src/algorithm_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/algorithm.h\"\n\n#include <algorithm>\n#include <random>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nstatic bool CompareLess(int a, int b) { return a < b; }\nstatic bool CompareGreater(int a, int b) { return a > b; }\n\nTEST(Algorithm, Sort) {\n  std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  std::vector<int> expected = v;\n\n  std::random_device rd;\n  std::mt19937 g(rd());\n\n  std::shuffle(v.begin(), v.end(), g);\n\n  EXPECT_NE(expected, v);\n\n  InsertionSort(v.data(), v.size(), CompareLess);\n\n  EXPECT_EQ(expected, v);\n}\n\nTEST(Algorithm, SortCompareGreater) {\n  std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n  std::vector<int> expected = v;\n  std::reverse(expected.begin(), expected.end());\n\n  InsertionSort(v.data(), v.size(), CompareGreater);\n\n  EXPECT_EQ(expected, v);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/auto_create_optional_unittest.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstdio>\n#include <string>\n#include <vector>\n\n#define private public\n\n#include \"base/include/auto_create_optional.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace test {\n\nstruct DataStruct {\n  std::vector<std::string> vec1;\n  std::vector<std::string> vec2;\n};\n\nTEST(AutoCreateOptional, BoolShortCircuit) {\n  auto_create_optional<DataStruct> data;\n  if (data.has_value() && !data->vec1.empty()) {\n    printf(\"1\");\n  }\n  if (!data.has_value() || data->vec1.empty()) {\n    printf(\"2\");\n  }\n  EXPECT_FALSE(data.has_value());\n}\n\nTEST(AutoCreateOptional, CopyConstruct) {\n  auto_create_optional<DataStruct> data;\n  auto_create_optional<DataStruct> data_empty(data);\n  EXPECT_FALSE(data_empty.has_value());\n  EXPECT_FALSE(bool(data_empty));\n  EXPECT_EQ(data_empty.get(), nullptr);\n\n  data->vec1.push_back(\"123\");\n  data->vec2.push_back(\"abc\");\n\n  auto_create_optional<DataStruct> data2(data);\n  EXPECT_TRUE(data2.has_value());\n  EXPECT_TRUE(bool(data2));\n\n  EXPECT_EQ(&(data2.data_->vec1), &(data2.get()->vec1));\n  EXPECT_EQ(&(data2.data_->vec1), &((*data2).vec1));\n  EXPECT_TRUE(data2.data_->vec1.size() == 1);\n  EXPECT_TRUE(data2.data_->vec1[0] == \"123\");\n  EXPECT_TRUE(data2.data_->vec2.size() == 1);\n  EXPECT_TRUE(data2.data_->vec2[0] == \"abc\");\n\n  EXPECT_TRUE(data.has_value());\n  EXPECT_TRUE(bool(data));\n  EXPECT_EQ(&(data.data_->vec1), &(data.get()->vec1));\n  EXPECT_EQ(&(data.data_->vec1), &((*data).vec1));\n  EXPECT_TRUE(data.data_->vec1.size() == 1);\n  EXPECT_TRUE(data.data_->vec1[0] == \"123\");\n  EXPECT_TRUE(data.data_->vec2.size() == 1);\n  EXPECT_TRUE(data.data_->vec2[0] == \"abc\");\n}\n\nTEST(AutoCreateOptional, CopyAssign) {\n  auto_create_optional<DataStruct> data;\n  auto_create_optional<DataStruct> data_empty;\n  data_empty = data;\n  EXPECT_FALSE(data_empty.has_value());\n  EXPECT_FALSE(bool(data_empty));\n  EXPECT_EQ(data_empty.get(), nullptr);\n\n  data->vec1.push_back(\"123\");\n  data->vec2.push_back(\"abc\");\n\n  auto_create_optional<DataStruct> data2;\n  EXPECT_FALSE(data2.has_value());\n  EXPECT_FALSE(bool(data2));\n  EXPECT_EQ(data2.get(), nullptr);\n\n  data2 = data;\n  EXPECT_TRUE(data2.has_value());\n  EXPECT_TRUE(bool(data2));\n\n  EXPECT_EQ(&(data2.data_->vec1), &(data2.get()->vec1));\n  EXPECT_EQ(&(data2.data_->vec1), &((*data2).vec1));\n  EXPECT_TRUE(data2.data_->vec1.size() == 1);\n  EXPECT_TRUE(data2.data_->vec1[0] == \"123\");\n  EXPECT_TRUE(data2.data_->vec2.size() == 1);\n  EXPECT_TRUE(data2.data_->vec2[0] == \"abc\");\n\n  EXPECT_TRUE(data.has_value());\n  EXPECT_TRUE(bool(data));\n  EXPECT_EQ(&(data.data_->vec1), &(data.get()->vec1));\n  EXPECT_EQ(&(data.data_->vec1), &((*data).vec1));\n  EXPECT_TRUE(data.data_->vec1.size() == 1);\n  EXPECT_TRUE(data.data_->vec1[0] == \"123\");\n  EXPECT_TRUE(data.data_->vec2.size() == 1);\n  EXPECT_TRUE(data.data_->vec2[0] == \"abc\");\n\n  data2 = data_empty;\n  EXPECT_FALSE(data2.has_value());\n  EXPECT_FALSE(bool(data2));\n  EXPECT_EQ(data2.get(), nullptr);\n}\n\nTEST(AutoCreateOptional, MoveConstruct) {\n  auto_create_optional<DataStruct> data;\n  auto_create_optional<DataStruct> data_empty(std::move(data));\n  EXPECT_FALSE(data_empty.has_value());\n  EXPECT_FALSE(bool(data_empty));\n  EXPECT_EQ(data_empty.get(), nullptr);\n\n  data->vec1.push_back(\"123\");\n  data->vec2.push_back(\"abc\");\n\n  auto_create_optional<DataStruct> data2(std::move(data));\n  EXPECT_FALSE(data.has_value());\n  EXPECT_FALSE(bool(data));\n  EXPECT_EQ(data.get(), nullptr);\n  EXPECT_TRUE(data2.has_value());\n  EXPECT_TRUE(bool(data2));\n\n  EXPECT_EQ(&(data2.data_->vec1), &(data2.get()->vec1));\n  EXPECT_EQ(&(data2.data_->vec1), &((*data2).vec1));\n  EXPECT_TRUE(data2.data_->vec1.size() == 1);\n  EXPECT_TRUE(data2.data_->vec1[0] == \"123\");\n  EXPECT_TRUE(data2.data_->vec2.size() == 1);\n  EXPECT_TRUE(data2.data_->vec2[0] == \"abc\");\n}\n\nTEST(AutoCreateOptional, MoveAssign) {\n  auto_create_optional<DataStruct> data;\n  auto_create_optional<DataStruct> data_empty;\n  data_empty = std::move(data);\n  EXPECT_FALSE(data_empty.has_value());\n  EXPECT_FALSE(bool(data_empty));\n  EXPECT_EQ(data_empty.get(), nullptr);\n\n  data->vec1.push_back(\"123\");\n  data->vec2.push_back(\"abc\");\n\n  auto_create_optional<DataStruct> data2;\n  EXPECT_FALSE(data2.has_value());\n  EXPECT_FALSE(bool(data2));\n  EXPECT_EQ(data2.get(), nullptr);\n\n  data2 = std::move(data);\n  EXPECT_TRUE(data2.has_value());\n  EXPECT_TRUE(bool(data2));\n\n  EXPECT_EQ(&(data2.data_->vec1), &(data2.get()->vec1));\n  EXPECT_EQ(&(data2.data_->vec1), &((*data2).vec1));\n  EXPECT_TRUE(data2.data_->vec1.size() == 1);\n  EXPECT_TRUE(data2.data_->vec1[0] == \"123\");\n  EXPECT_TRUE(data2.data_->vec2.size() == 1);\n  EXPECT_TRUE(data2.data_->vec2[0] == \"abc\");\n\n  EXPECT_FALSE(data.has_value());\n  EXPECT_FALSE(bool(data));\n  EXPECT_EQ(data.get(), nullptr);\n\n  data2 = std::move(data_empty);\n  EXPECT_FALSE(data2.has_value());\n  EXPECT_FALSE(bool(data2));\n  EXPECT_EQ(data2.get(), nullptr);\n}\n\nTEST(AutoCreateOptional, CreateByArrow) {\n  auto_create_optional<DataStruct> data;\n  EXPECT_FALSE(data.has_value());\n  EXPECT_FALSE(bool(data));\n  EXPECT_EQ(data.get(), nullptr);\n\n  data->vec1.push_back(\"123\");\n  data->vec2.push_back(\"abc\");\n\n  EXPECT_TRUE(data.has_value());\n  EXPECT_TRUE(bool(data));\n\n  EXPECT_EQ(&(data.data_->vec1), &(data.get()->vec1));\n  EXPECT_EQ(&(data.data_->vec1), &((*data).vec1));\n  EXPECT_TRUE(data.data_->vec1.size() == 1);\n  EXPECT_TRUE(data.data_->vec1[0] == \"123\");\n  EXPECT_TRUE(data.data_->vec2.size() == 1);\n  EXPECT_TRUE(data.data_->vec2[0] == \"abc\");\n\n  data.reset();\n  EXPECT_FALSE(data.has_value());\n  EXPECT_FALSE(bool(data));\n  EXPECT_EQ(data.get(), nullptr);\n\n  data->vec1.push_back(\"123\");\n  data->vec2.push_back(\"abc\");\n\n  EXPECT_TRUE(data.has_value());\n  EXPECT_TRUE(bool(data));\n\n  EXPECT_EQ(&(data.data_->vec1), &(data.get()->vec1));\n  EXPECT_EQ(&(data.data_->vec1), &((*data).vec1));\n  EXPECT_TRUE(data.data_->vec1.size() == 1);\n  EXPECT_TRUE(data.data_->vec1[0] == \"123\");\n  EXPECT_TRUE(data.data_->vec2.size() == 1);\n  EXPECT_TRUE(data.data_->vec2[0] == \"abc\");\n}\n\nTEST(AutoCreateOptional, CreateByAsterisk) {\n  auto_create_optional<std::vector<std::string>> data;\n  EXPECT_FALSE(data.has_value());\n  EXPECT_FALSE(bool(data));\n  EXPECT_EQ(data.get(), nullptr);\n\n  (*data).push_back(\"123\");\n  (*data).push_back(\"abc\");\n\n  EXPECT_TRUE(data.has_value());\n  EXPECT_TRUE(bool(data));\n\n  EXPECT_EQ(data.data_.get(), &(*data));\n  EXPECT_TRUE(data.data_->size() == 2);\n  EXPECT_TRUE((*data.data_)[0] == \"123\");\n  EXPECT_TRUE((*data.data_)[1] == \"abc\");\n\n  data.reset();\n  EXPECT_FALSE(data.has_value());\n  EXPECT_FALSE(bool(data));\n  EXPECT_EQ(data.get(), nullptr);\n\n  (*data).push_back(\"123\");\n  (*data).push_back(\"abc\");\n\n  EXPECT_TRUE(data.has_value());\n  EXPECT_TRUE(bool(data));\n\n  EXPECT_EQ(data.data_.get(), &(*data));\n  EXPECT_TRUE(data.data_->size() == 2);\n  EXPECT_TRUE((*data.data_)[0] == \"123\");\n  EXPECT_TRUE((*data.data_)[1] == \"abc\");\n}\n\n}  // namespace test\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/auto_reset_unittest.cc",
    "content": "// Copyright 2019 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/auto_reset.h\"\n\n#include <utility>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nTEST(AutoReset, Move) {\n  int value = 10;\n  {\n    AutoReset<int> ar1{&value, 20};\n    EXPECT_EQ(20, value);\n    {\n      value = 15;\n      AutoReset<int> ar2 = std::move(ar1);\n      // Moving to a new re-setter does not change the value;\n      EXPECT_EQ(15, value);\n    }\n    // Moved-to `ar2` is out of scoped, and resets to the original value\n    // that was in moved-from `ar1`.\n    EXPECT_EQ(10, value);\n    value = 105;\n  }\n  // Moved-from `ar1` does not reset to anything.\n  EXPECT_EQ(105, value);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/base.gni",
    "content": "import(\"//${lynx_dir}/build_overrides/darwin.gni\")\nimport(\"//build/config/profiler.gni\")\n\ndeclare_args() {\n  # Whether to use a custom message loop.\n  # If false, the platform’s default implementation will be used.\n  use_custom_message_loop = false\n\n  # Controls the symbol visibility of the lynx base library.\n  # If true, symbols decorated with `BASE_EXPORT` are exported.\n  # Otherwise, no symbols from this library are exported.\n  base_export_symbols = true\n}\n\nbase_configs = rebase_path([ \":base_private_config\" ])\nbase_public_configs = rebase_path([ \":base_public_config\" ])\nuse_sanitizer = is_asan || is_lsan || is_tsan || is_msan || is_hwasan\n\ntemplate(\"lynx_base_source_set\") {\n  source_set(target_name) {\n    forward_variables_from(invoker,\n                           \"*\",\n                           [\n                             \"configs\",\n                             \"public_configs\",\n                           ])\n\n    if (!defined(deps)) {\n      deps = []\n    }\n    if (!defined(cflags)) {\n      cflags = []\n    }\n\n    if (!defined(configs)) {\n      configs = []\n    }\n    if (!defined(libs)) {\n      libs = []\n    }\n    if (defined(invoker.configs)) {\n      configs += invoker.configs\n    }\n\n    if (!defined(public_configs)) {\n      public_configs = []\n    }\n    if (defined(invoker.public_configs)) {\n      public_configs += invoker.public_configs\n    }\n    if (is_android) {\n      libs += [ \"log\" ]\n    }\n    configs += base_configs\n    public_configs += base_public_configs\n    if (is_apple) {\n      configs += darwin_config\n    }\n    if (is_android && !is_debug) {\n      if (!use_sanitizer && !enable_profiling) {\n        cflags += [ \"-fomit-frame-pointer\" ]\n      }\n      configs -= [ \"//build/config/compiler:optimize\" ]\n      cflags += [ \"-O3\" ]\n    }\n  }\n}\n"
  },
  {
    "path": "base/src/base_trace/base_trace_event_def.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_SRC_BASE_TRACE_BASE_TRACE_EVENT_DEF_H_\n#define BASE_SRC_BASE_TRACE_BASE_TRACE_EVENT_DEF_H_\n\n// event names\nstatic const char* const LYNX_BASE_LOG_INTERNAL = \"_LynxLogInternal\";\n\n// categories\nstatic const char* const LYNX_BASE_TRACE_CATEGORY = \"lynx_base\";\n\n#endif  // BASE_SRC_BASE_TRACE_BASE_TRACE_EVENT_DEF_H_\n"
  },
  {
    "path": "base/src/base_trace/trace_android.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <dlfcn.h>\n\n#include \"base/include/base_trace/trace_event_utils.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/platform/android/src/main/jni/gen/LynxBaseTrace_jni.h\"\n#include \"base/platform/android/src/main/jni/gen/LynxBaseTrace_register_jni.h\"\n\nnamespace lynxbase {\nnamespace jni {\nbool RegisterJNIForLynxBaseTrace(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynxbase\n\nnamespace lynx {\nnamespace base {\nnamespace trace {\nusing BaseTraceBeginSectionFunc =\n    void (*)(const char* category_group, const char* section_name,\n             int64_t trace_id, const char* arg1_name, const char* arg1_val,\n             const char* arg2_name, const char* arg2_val);\nusing BaseTraceEndSectionFunc = void (*)(const char* category_group,\n                                         const char* section_name,\n                                         int64_t trace_id);\n\nstatic BaseTraceBeginSectionFunc trace_begin_section_func = nullptr;\nstatic BaseTraceEndSectionFunc trace_end_section_func = nullptr;\n\nbool GetDefaultTraceBackend() {\n  void* lib = dlopen(\"liblynxtrace.so\", RTLD_LOCAL | RTLD_NOW);\n  if (!lib) {\n    LOGE(\"GetDefaultTraceBackend can't find liblynxtrace.so\");\n    return false;\n  }\n\n  trace_begin_section_func = reinterpret_cast<BaseTraceBeginSectionFunc>(\n      dlsym(lib, \"TraceEventBeginEx\"));\n  trace_end_section_func =\n      reinterpret_cast<BaseTraceEndSectionFunc>(dlsym(lib, \"TraceEventEndEx\"));\n  if (!trace_begin_section_func || !trace_end_section_func) {\n    LOGE(\"TraceEventBeginEx TraceEventEndEx not found\");\n    return false;\n  }\n  return true;\n}\n\nvoid TraceBackend(const char* category, const char* name,\n                  BaseTraceEventType phase) {\n  switch (phase) {\n    case BaseTraceEventType::TYPE_SLICE_BEGIN:\n      trace_begin_section_func(category, name, -1, nullptr, nullptr, nullptr,\n                               nullptr);\n      break;\n    case BaseTraceEventType::TYPE_SLICE_END:\n      trace_end_section_func(category, name, -1);\n      break;\n    default:\n      break;\n  }\n}\n\n}  // namespace trace\n}  // namespace base\n}  // namespace lynx\n\nvoid InitBaseTrace(JNIEnv* env, jclass jcaller, jlong addr) {\n  auto trace_backend_ptr =\n      reinterpret_cast<lynx::base::trace::trace_backend_ptr>(addr);\n  if (!trace_backend_ptr && lynx::base::trace::GetDefaultTraceBackend()) {\n    trace_backend_ptr = lynx::base::trace::TraceBackend;\n    LOGV(\"base trace init success by dlopen liblynxtrace.so.\");\n  }\n  lynx::base::trace::SetTraceBackend(trace_backend_ptr);\n}\n"
  },
  {
    "path": "base/src/base_trace/trace_event.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_SRC_BASE_TRACE_TRACE_EVENT_H_\n#define BASE_SRC_BASE_TRACE_TRACE_EVENT_H_\n\n#include \"base/include/base_trace/trace_event_utils.h\"\n\n#define BASE_INTERNAL_TRACE_EVENT_UID3(a, b) trace_event_uid_##a##b\n#define BASE_INTERNAL_TRACE_EVENT_UID2(a, b) \\\n  BASE_INTERNAL_TRACE_EVENT_UID3(a, b)\n#define BASE_INTERNAL_TRACE_EVENT_UID(name) \\\n  BASE_INTERNAL_TRACE_EVENT_UID2(name, __LINE__)\n\n#define BASE_TRACE_EVENT(category, name)                              \\\n  struct BASE_INTERNAL_TRACE_EVENT_UID(ScopedEvent) {                 \\\n    struct BaseEventFinalizer {                                       \\\n      BaseEventFinalizer(...) {}                                      \\\n      ~BaseEventFinalizer() { BASE_TRACE_EVENT_END(category, name); } \\\n    } base_finalizer;                                                 \\\n  } BASE_INTERNAL_TRACE_EVENT_UID(scoped_event) {                     \\\n    [&]() {                                                           \\\n      BASE_TRACE_EVENT_BEGIN(category, name);                         \\\n      return 0;                                                       \\\n    }()                                                               \\\n  }\n\n#define BASE_TRACE_EVENT_BEGIN(category, name) \\\n  lynx::base::trace::TraceEventBegin(category, name)\n#define BASE_TRACE_EVENT_END(category, name) \\\n  lynx::base::trace::TraceEventEnd(category, name)\n\n#endif  // BASE_SRC_BASE_TRACE_TRACE_EVENT_H_\n"
  },
  {
    "path": "base/src/base_trace/trace_event_utils.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/base_trace/trace_event_utils.h\"\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace trace {\nnamespace {\nstatic trace_backend_ptr trace_backend = nullptr;\n}\nvoid TraceEventBegin(const char* category, const char* name) {\n  if (trace_backend == nullptr) {\n    return;\n  }\n  trace_backend(category, name, BaseTraceEventType::TYPE_SLICE_BEGIN);\n}\nvoid TraceEventEnd(const char* category, const char* name) {\n  if (trace_backend == nullptr) {\n    return;\n  }\n  trace_backend(category, name, BaseTraceEventType::TYPE_SLICE_END);\n}\n\nvoid SetTraceBackend(trace_backend_ptr backend) {\n  if (backend != nullptr) {\n    trace_backend = backend;\n    LOGI(\"base trace init success, backend: \"\n         << reinterpret_cast<uint64_t>(backend));\n    return;\n  }\n  LOGE(\"base trace init failed.\");\n}\n}  // namespace trace\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/base_trace/trace_harmony.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/src/base_trace/trace_harmony.h\"\n\n#include <dlfcn.h>\n\n#include \"base/include/base_trace/trace_event_utils.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/include/platform/harmony/napi_util.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace trace {\n\nnapi_value LynxBaseTrace::Init(napi_env env, napi_value exports) {\n  NAPI_CREATE_FUNCTION(env, exports, \"nativeInitLynxBaseTrace\",\n                       NativeInitLynxBaseTrace);\n  return exports;\n}\n\nnapi_value LynxBaseTrace::NativeInitLynxBaseTrace(napi_env env,\n                                                  napi_callback_info info) {\n  size_t argc = 1;\n  napi_value args[1] = {nullptr};\n  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);\n  trace_backend_ptr trace_backend_address = reinterpret_cast<trace_backend_ptr>(\n      NapiUtil::ConvertToInt64(env, args[0]));\n  if (trace_backend_address == nullptr) {\n    LOGV(\"base trace init failed.\");\n    return nullptr;\n  }\n  SetTraceBackend(trace_backend_address);\n  return nullptr;\n}\n}  // namespace trace\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/base_trace/trace_harmony.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_SRC_BASE_TRACE_TRACE_HARMONY_H_\n#define BASE_SRC_BASE_TRACE_TRACE_HARMONY_H_\n\n#include <node_api.h>\n\nnamespace lynx {\nnamespace base {\nnamespace trace {\n\nclass LynxBaseTrace {\n public:\n  static napi_value Init(napi_env env, napi_value exports);\n  static napi_value NativeInitLynxBaseTrace(napi_env env,\n                                            napi_callback_info info);\n};\n\n}  // namespace trace\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_SRC_BASE_TRACE_TRACE_HARMONY_H_\n"
  },
  {
    "path": "base/src/boost/unordered_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/boost/unordered.h\"\n\n#include <algorithm>\n#include <string>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace test {\n\n#define TEST_INSERT(T)                                          \\\n  TEST(BoostUnordered, T##_insert) {                            \\\n    boost::T<std::string, std::string> map{{\"key0\", \"value0\"}}; \\\n    EXPECT_TRUE(map.insert({\"key1\", \"value1\"}).second);         \\\n    map[\"key2\"] = \"value2\";                                     \\\n    EXPECT_TRUE(map.emplace(\"key3\", \"value3\").second);          \\\n    EXPECT_TRUE(map.try_emplace(\"key4\", \"value4\").second);      \\\n                                                                \\\n    EXPECT_TRUE(map.size() == 5);                               \\\n    EXPECT_FALSE(map.insert({\"key1\", \"0\"}).second);             \\\n    EXPECT_FALSE(map.emplace(\"key2\", \"0\").second);              \\\n    EXPECT_FALSE(map.try_emplace(\"key3\", \"0\").second);          \\\n  }\n\nTEST_INSERT(unordered_flat_map)\nTEST_INSERT(unordered_node_map)\n\n#define TEST_ERASE(T)                       \\\n  TEST(BoostUnordered, T##_erase) {         \\\n    boost::T<int, std::string> map;         \\\n    for (int i = 0; i < 100; i++) {         \\\n      map[i] = \"value\" + std::to_string(i); \\\n    }                                       \\\n    for (int i = 0; i < 100; i += 2) {      \\\n      map.erase(i);                         \\\n    }                                       \\\n    int total = 0;                          \\\n    for (const auto& [key, _] : map) {      \\\n      total += key;                         \\\n    }                                       \\\n    EXPECT_TRUE(total == 2500);             \\\n  }\n\nTEST_ERASE(unordered_flat_map)\nTEST_ERASE(unordered_node_map)\n\n#define TEST_COPY(T)                                \\\n  TEST(BoostUnordered, T##_copy) {                  \\\n    boost::T<int, std::string> map0;                \\\n    for (int i = 0; i < 100; i++) {                 \\\n      map0[i] = std::to_string(i);                  \\\n    }                                               \\\n                                                    \\\n    auto map1 = map0;                               \\\n    EXPECT_TRUE(map1.size() == map0.size());        \\\n    int total = 0;                                  \\\n    for (const auto& [key, value] : map1) {         \\\n      total += key;                                 \\\n      EXPECT_TRUE(key == std::atoi(value.c_str())); \\\n    }                                               \\\n    EXPECT_TRUE(total == 4950);                     \\\n  }\n\nTEST_COPY(unordered_flat_map)\nTEST_COPY(unordered_node_map)\n\n#define TEST_MOVE(T)                                \\\n  TEST(BoostUnordered, T##_move) {                  \\\n    boost::T<int, std::string> map0;                \\\n    for (int i = 0; i < 100; i++) {                 \\\n      map0[i] = std::to_string(i);                  \\\n    }                                               \\\n                                                    \\\n    auto map1 = std::move(map0);                    \\\n    EXPECT_TRUE(map1.size() == 100);                \\\n    EXPECT_TRUE(map0.empty());                      \\\n    int total = 0;                                  \\\n    for (const auto& [key, value] : map1) {         \\\n      total += key;                                 \\\n      EXPECT_TRUE(key == std::atoi(value.c_str())); \\\n    }                                               \\\n    EXPECT_TRUE(total == 4950);                     \\\n  }\n\nTEST_MOVE(unordered_flat_map)\nTEST_MOVE(unordered_node_map)\n\n#define TEST_FIND(T)                           \\\n  TEST(BoostUnordered, T##_find) {             \\\n    boost::T<int, std::string> map;            \\\n    for (int i = 0; i < 100; i += 2) {         \\\n      map[i] = std::to_string(i);              \\\n    }                                          \\\n                                               \\\n    for (int i = 0; i < 100; i++) {            \\\n      if (i % 2 == 0) {                        \\\n        EXPECT_TRUE(map.find(i) != map.end()); \\\n      } else {                                 \\\n        EXPECT_TRUE(map.find(i) == map.end()); \\\n      }                                        \\\n    }                                          \\\n  }\n\nTEST_FIND(unordered_flat_map)\nTEST_FIND(unordered_node_map)\n\ntemplate <class SET>\nstatic void TestSet() {\n  auto to_s = [](SET& set) -> std::string {\n    std::string result;\n    for (auto& i : set) {\n      result += std::to_string(i);\n    }\n    std::sort(result.begin(), result.end());\n    return result;\n  };\n\n  SET set;\n  set.insert(1);\n  set.insert(5);\n  set.insert(3);\n  set.insert(7);\n  set.insert(6);\n  set.insert(2);\n  set.insert(4);\n  auto it = set.insert(8);\n  EXPECT_EQ(*it.first, 8);\n  EXPECT_TRUE(it.second);\n  EXPECT_FALSE(set.insert(3).second);\n  EXPECT_EQ(to_s(set), \"12345678\");\n\n  EXPECT_TRUE(set.size() == 8);\n\n  set.erase(5);\n  set.erase(1);\n  EXPECT_TRUE(set.size() == 6);\n  EXPECT_EQ(to_s(set), \"234678\");\n\n  EXPECT_TRUE(set.contains(6));\n  EXPECT_FALSE(set.contains(1));\n  EXPECT_FALSE(set.contains(5));\n\n  auto find3 = set.find(3);\n  auto find1 = set.find(1);\n  EXPECT_EQ(*find3, 3);\n  EXPECT_TRUE(find1 == set.end());\n\n  EXPECT_EQ(to_s(set), \"234678\");\n  set.erase(find3);\n  EXPECT_EQ(to_s(set), \"24678\");\n\n  set.clear();\n  EXPECT_TRUE(set.empty());\n\n  // Check functionality after clear.\n  set.insert(1);\n  EXPECT_TRUE(set.size() == 1);\n  EXPECT_TRUE(set.contains(1));\n  EXPECT_TRUE(set.find(1) != set.end());\n}\n\ntemplate <class MAP>\nstatic void TestMap1() {\n  auto to_s = [](MAP& map) -> std::string {\n    std::string result;\n    for (auto& i : map) {\n      result += i.second;\n    }\n    std::sort(result.begin(), result.end());\n    return result;\n  };\n\n  MAP map;\n  EXPECT_TRUE(map.empty());\n\n  map.insert({1, \"1\"});\n  map.insert({5, \"5\"});\n  map.insert({3, \"3\"});\n  map.insert({7, \"7\"});\n  map.insert({6, \"6\"});\n  map.insert({2, \"2\"});\n  map.insert({4, \"4\"});\n  auto it = map.insert({8, \"8\"});\n  EXPECT_EQ(it.first->first, 8);\n  EXPECT_EQ(it.first->second, \"8\");\n  EXPECT_TRUE(it.second);\n  EXPECT_FALSE(map.insert({3, \"3\"}).second);\n  EXPECT_EQ(to_s(map), \"12345678\");\n\n  EXPECT_TRUE(map.size() == 8);\n\n  typename MAP::value_type pair{0, \"0\"};\n  map.insert(pair);\n  EXPECT_EQ(to_s(map), \"012345678\");\n\n  map.erase(5);\n  map.erase(1);\n  map.erase(1024);\n  EXPECT_TRUE(map.size() == 7);\n  EXPECT_EQ(to_s(map), \"0234678\");\n\n  EXPECT_TRUE(map.contains(0));\n  EXPECT_TRUE(map.contains(6));\n  EXPECT_FALSE(map.contains(1));\n  EXPECT_FALSE(map.contains(5));\n\n  auto find3 = map.find(3);\n  auto find1 = map.find(1);\n  EXPECT_TRUE(find1 == map.end());\n  EXPECT_EQ(find3->first, 3);\n  EXPECT_EQ(find3->second, \"3\");\n  find3->second = \"333\";\n  EXPECT_EQ(to_s(map), \"023334678\");\n\n  map.erase(find3);\n  EXPECT_EQ(to_s(map), \"024678\");\n\n  EXPECT_EQ(map[1], \"\");\n  EXPECT_TRUE(map.size() == 7);\n  EXPECT_EQ(to_s(map), \"024678\");\n\n  map[1] = \"1\";\n  map[5] = \"5\";\n  map[8] = \"888\";\n  EXPECT_TRUE(map.size() == 8);\n  EXPECT_EQ(to_s(map), \"0124567888\");\n\n  map.clear();\n  EXPECT_TRUE(map.empty());\n\n  // Check functionality after clear.\n  map.insert({1, \"1\"});\n  EXPECT_TRUE(map.size() == 1);\n  EXPECT_TRUE(map.contains(1));\n  EXPECT_TRUE(map.find(1) != map.end());\n}\n\ntemplate <class MAP>\nstatic void TestMap2() {\n  auto to_s = [](MAP& map) -> std::string {\n    std::string result;\n    for (auto& i : map) {\n      result += std::to_string(i.second);\n    }\n    std::sort(result.begin(), result.end());\n    return result;\n  };\n\n  MAP map;\n  EXPECT_TRUE(map.empty());\n\n  map.insert({\"1\", 1});\n  map.insert({\"5\", 5});\n  map.insert({\"3\", 3});\n  map.insert({\"7\", 7});\n  map.insert({\"6\", 6});\n  map.insert({\"2\", 2});\n  map.insert({\"4\", 4});\n  auto it = map.insert({\"8\", 8});\n  EXPECT_EQ(it.first->first, \"8\");\n  EXPECT_EQ(it.first->second, 8);\n  EXPECT_TRUE(it.second);\n  EXPECT_FALSE(map.insert({\"3\", 3}).second);\n  EXPECT_EQ(to_s(map), \"12345678\");\n\n  EXPECT_TRUE(map.size() == 8);\n\n  typename MAP::value_type pair{\"0\", 0};\n  map.insert(pair);\n  EXPECT_EQ(to_s(map), \"012345678\");\n\n  map.erase(\"5\");\n  map.erase(\"1\");\n  map.erase(\"abc\");\n  EXPECT_TRUE(map.size() == 7);\n  EXPECT_EQ(to_s(map), \"0234678\");\n\n  EXPECT_TRUE(map.contains(\"0\"));\n  EXPECT_TRUE(map.contains(\"6\"));\n  EXPECT_FALSE(map.contains(\"1\"));\n  EXPECT_FALSE(map.contains(\"5\"));\n\n  auto find3 = map.find(\"3\");\n  auto find1 = map.find(\"1\");\n  EXPECT_TRUE(find1 == map.end());\n  EXPECT_EQ(find3->first, \"3\");\n  EXPECT_EQ(find3->second, 3);\n  find3->second = 333;\n  EXPECT_EQ(to_s(map), \"023334678\");\n\n  map.erase(find3);\n  EXPECT_EQ(to_s(map), \"024678\");\n\n  EXPECT_EQ(map[\"1\"], 0);\n  EXPECT_TRUE(map.size() == 7);\n  EXPECT_EQ(to_s(map), \"0024678\");\n\n  map[\"1\"] = 1;\n  map[\"5\"] = 5;\n  map[\"8\"] = 888;\n  EXPECT_TRUE(map.size() == 8);\n  EXPECT_EQ(to_s(map), \"0124567888\");\n\n  std::string key = \"a\";\n  map[std::move(key)] = 999;\n  EXPECT_EQ(to_s(map), \"0124567888999\");\n  EXPECT_TRUE(key.empty());\n\n  map.clear();\n  EXPECT_TRUE(map.empty());\n\n  // Check functionality after clear.\n  map.insert({\"1\", 1});\n  EXPECT_TRUE(map.size() == 1);\n  EXPECT_TRUE(map.contains(\"1\"));\n  EXPECT_TRUE(map.find(\"1\") != map.end());\n}\n\nTEST(BoostUnordered, Set) {\n  TestSet<boost::unordered_flat_set<int>>();\n  TestSet<boost::unordered_node_set<int>>();\n}\n\nTEST(BoostUnordered, Map) {\n  TestMap1<boost::unordered_flat_map<int, std::string>>();\n  TestMap1<boost::unordered_node_map<int, std::string>>();\n  TestMap2<boost::unordered_flat_map<std::string, int>>();\n  TestMap2<boost::unordered_node_map<std::string, int>>();\n}\n\n}  // namespace test\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/bundled_optional_unittest.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstdio>\n#include <string>\n#include <vector>\n\n#define private public\n\n#include \"base/include/bundled_optional.h\"\n#include \"base/include/value/base_string.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace test {\n\ntemplate <typename T>\nstruct CountedWrapper {\n  static int32_t g_instance_count;\n\n  CountedWrapper() { g_instance_count++; }\n  ~CountedWrapper() { g_instance_count--; }\n  CountedWrapper(const CountedWrapper& other) : value(other.value) {\n    g_instance_count++;\n  }\n  CountedWrapper(CountedWrapper&& other) : value(std::move(other.value)) {\n    g_instance_count++;\n  }\n  CountedWrapper& operator=(const CountedWrapper& other) = default;\n  CountedWrapper& operator=(CountedWrapper&& other) = default;\n\n  T value;\n};\n\ntemplate <typename T>\nint32_t CountedWrapper<T>::g_instance_count = 0;\n\nstruct NameField {\n  using Type = CountedWrapper<std::string>;\n};\n\nstruct SchoolsField {\n  using Type = CountedWrapper<std::vector<std::string>>;\n};\n\nstruct AgeField {\n  using Type = int;\n};\n\nstruct HobbyField {\n  using Type = String;\n};\n\nstruct Person {\n  char id;\n  BundledOptionals<NameField, SchoolsField, AgeField, HobbyField> optionals;\n\n  Person() : id(-1) {}\n  Person(const Person& other) = default;\n  Person(Person&& other) : id(other.id), optionals(std::move(other.optionals)) {\n    other.id = -1;\n  }\n  Person& operator=(const Person& other) = default;\n  Person& operator=(Person&& other) {\n    if (this != &other) {\n      id = other.id;\n      optionals = std::move(other.optionals);\n      other.id = -1;\n    }\n    return *this;\n  }\n};\n\ntemplate <typename T>\nstatic void AssertInstanceCount(int32_t value) {\n  EXPECT_EQ(T::Type::g_instance_count, value);\n}\n\nTEST(BundledOptional, Empty) {\n  Person p;\n  EXPECT_FALSE(p.optionals.HasValue<NameField>());\n  EXPECT_FALSE(p.optionals.HasValue<SchoolsField>());\n  EXPECT_FALSE(p.optionals.HasValue<AgeField>());\n  EXPECT_FALSE(p.optionals.HasValue<HobbyField>());\n  EXPECT_TRUE(p.optionals.GetOrNull<NameField>() == nullptr);\n  EXPECT_TRUE(p.optionals.GetOrNull<SchoolsField>() == nullptr);\n  EXPECT_TRUE(p.optionals.GetOrNull<AgeField>() == nullptr);\n  EXPECT_TRUE(p.optionals.GetOrNull<HobbyField>() == nullptr);\n\n  const Person p2;\n  EXPECT_TRUE(p2.optionals.GetOrNull<NameField>() == nullptr);\n  EXPECT_TRUE(p2.optionals.GetOrNull<SchoolsField>() == nullptr);\n  EXPECT_TRUE(p2.optionals.GetOrNull<AgeField>() == nullptr);\n  EXPECT_TRUE(p2.optionals.GetOrNull<HobbyField>() == nullptr);\n\n  const Person p3 = p;\n  EXPECT_TRUE(p3.optionals.GetOrNull<NameField>() == nullptr);\n  EXPECT_TRUE(p3.optionals.GetOrNull<SchoolsField>() == nullptr);\n  EXPECT_TRUE(p3.optionals.GetOrNull<AgeField>() == nullptr);\n  EXPECT_TRUE(p3.optionals.GetOrNull<HobbyField>() == nullptr);\n\n  Person p4(std::move(p));\n  EXPECT_TRUE(p4.optionals.GetOrNull<NameField>() == nullptr);\n  EXPECT_TRUE(p4.optionals.GetOrNull<SchoolsField>() == nullptr);\n  EXPECT_TRUE(p4.optionals.GetOrNull<AgeField>() == nullptr);\n  EXPECT_TRUE(p4.optionals.GetOrNull<HobbyField>() == nullptr);\n\n  Person p5;\n  p5 = p3;\n  EXPECT_FALSE(p5.optionals.HasValue<NameField>());\n  EXPECT_FALSE(p5.optionals.HasValue<SchoolsField>());\n  EXPECT_FALSE(p5.optionals.HasValue<AgeField>());\n  EXPECT_FALSE(p5.optionals.HasValue<HobbyField>());\n\n  Person p6;\n  p6 = std::move(p4);\n  EXPECT_FALSE(p6.optionals.HasValue<NameField>());\n  EXPECT_FALSE(p6.optionals.HasValue<SchoolsField>());\n  EXPECT_FALSE(p6.optionals.HasValue<AgeField>());\n  EXPECT_FALSE(p6.optionals.HasValue<HobbyField>());\n\n  AssertInstanceCount<NameField>(0);\n  AssertInstanceCount<SchoolsField>(0);\n}\n\nTEST(BundledOptional, Construct) {\n  {\n    Person p_empty;\n    Person p_empty2 = p_empty;\n    EXPECT_FALSE(p_empty2.optionals.HasValue<NameField>());\n    EXPECT_FALSE(p_empty2.optionals.HasValue<SchoolsField>());\n    EXPECT_FALSE(p_empty2.optionals.HasValue<AgeField>());\n    EXPECT_FALSE(p_empty2.optionals.HasValue<HobbyField>());\n\n    Person p_empty3(std::move(p_empty));\n    EXPECT_FALSE(p_empty3.optionals.HasValue<NameField>());\n    EXPECT_FALSE(p_empty3.optionals.HasValue<SchoolsField>());\n    EXPECT_FALSE(p_empty3.optionals.HasValue<AgeField>());\n    EXPECT_FALSE(p_empty3.optionals.HasValue<HobbyField>());\n  }\n\n  Person p0;\n  p0.optionals.Get<NameField>().value = \"name0\";\n  p0.optionals.Get<HobbyField>() = String(\"basketball\");\n  p0.optionals.Get<SchoolsField>().value.push_back(\"elementary school\");\n  p0.optionals.Get<SchoolsField>().value.push_back(\"middle school\");\n  EXPECT_TRUE(p0.optionals.GetOrNull<NameField>()->value == \"name0\");\n  EXPECT_TRUE(p0.optionals.HasValue<NameField>());\n  EXPECT_TRUE(p0.optionals.GetOrNull<HobbyField>()->str() == \"basketball\");\n  EXPECT_TRUE(p0.optionals.HasValue<HobbyField>());\n  EXPECT_TRUE(p0.optionals.HasValue<SchoolsField>());\n  EXPECT_FALSE(p0.optionals.HasValue<AgeField>());\n  AssertInstanceCount<NameField>(1);\n  AssertInstanceCount<SchoolsField>(1);\n\n  {\n    Person p1(p0);\n    EXPECT_TRUE(p1.optionals.HasValue<NameField>());\n    EXPECT_TRUE(p1.optionals.HasValue<HobbyField>());\n    EXPECT_TRUE(p1.optionals.HasValue<SchoolsField>());\n    EXPECT_FALSE(p1.optionals.HasValue<AgeField>());\n    EXPECT_TRUE(p1.optionals.Get<NameField>().value == \"name0\");\n    EXPECT_TRUE(p1.optionals.Get<HobbyField>().str() == \"basketball\");\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value.size() == 2);\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value[0] ==\n                \"elementary school\");\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value[1] == \"middle school\");\n    AssertInstanceCount<NameField>(2);\n    AssertInstanceCount<SchoolsField>(2);\n  }\n\n  {\n    Person p1 = p0;\n    EXPECT_TRUE(p1.optionals.HasValue<NameField>());\n    EXPECT_TRUE(p1.optionals.HasValue<HobbyField>());\n    EXPECT_TRUE(p1.optionals.HasValue<SchoolsField>());\n    EXPECT_FALSE(p1.optionals.HasValue<AgeField>());\n    EXPECT_TRUE(p1.optionals.Get<NameField>().value == \"name0\");\n    EXPECT_TRUE(p1.optionals.Get<HobbyField>().str() == \"basketball\");\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value.size() == 2);\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value[0] ==\n                \"elementary school\");\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value[1] == \"middle school\");\n    AssertInstanceCount<NameField>(2);\n    AssertInstanceCount<SchoolsField>(2);\n  }\n\n  EXPECT_TRUE(p0.optionals.Get<NameField>().value == \"name0\");\n  EXPECT_TRUE(p0.optionals.Get<HobbyField>().str() == \"basketball\");\n  EXPECT_TRUE(p0.optionals.Get<SchoolsField>().value.size() == 2);\n  EXPECT_TRUE(p0.optionals.Get<SchoolsField>().value[0] == \"elementary school\");\n  EXPECT_TRUE(p0.optionals.Get<SchoolsField>().value[1] == \"middle school\");\n  AssertInstanceCount<NameField>(1);\n  AssertInstanceCount<SchoolsField>(1);\n\n  {\n    Person p2(std::move(p0));\n    EXPECT_TRUE(p2.optionals.HasValue<NameField>());\n    EXPECT_TRUE(p2.optionals.HasValue<HobbyField>());\n    EXPECT_TRUE(p2.optionals.HasValue<SchoolsField>());\n    EXPECT_FALSE(p2.optionals.HasValue<AgeField>());\n    EXPECT_TRUE(p2.optionals.Get<NameField>().value == \"name0\");\n    EXPECT_TRUE(p2.optionals.Get<HobbyField>().str() == \"basketball\");\n    EXPECT_TRUE(p2.optionals.Get<SchoolsField>().value.size() == 2);\n    EXPECT_TRUE(p2.optionals.Get<SchoolsField>().value[0] ==\n                \"elementary school\");\n    EXPECT_TRUE(p2.optionals.Get<SchoolsField>().value[1] == \"middle school\");\n    AssertInstanceCount<NameField>(1);\n    AssertInstanceCount<SchoolsField>(1);\n  }\n\n  EXPECT_FALSE(p0.optionals.HasValue<NameField>());\n  EXPECT_FALSE(p0.optionals.HasValue<HobbyField>());\n  EXPECT_FALSE(p0.optionals.HasValue<SchoolsField>());\n  AssertInstanceCount<NameField>(0);\n  AssertInstanceCount<SchoolsField>(0);\n}\n\nTEST(BundledOptional, Assign) {\n  {\n    Person p_empty;\n    Person p_empty2;\n    p_empty2.optionals.Get<NameField>().value = \"name_empty2\";\n    p_empty2.optionals.Get<HobbyField>() = String(\"reading\");\n    p_empty2.optionals.Get<AgeField>() = 13;\n    AssertInstanceCount<NameField>(1);\n    p_empty2 = p_empty;\n    EXPECT_FALSE(p_empty2.optionals.HasValue<NameField>());\n    EXPECT_FALSE(p_empty2.optionals.HasValue<HobbyField>());\n    EXPECT_FALSE(p_empty2.optionals.HasValue<SchoolsField>());\n    EXPECT_FALSE(p_empty2.optionals.HasValue<AgeField>());\n    AssertInstanceCount<NameField>(0);\n\n    Person p_empty3;\n    p_empty3.optionals.Get<NameField>().value = \"name_empty3\";\n    p_empty3.optionals.Get<HobbyField>() = String(\"reading\");\n    p_empty3.optionals.Get<AgeField>() = 13;\n    AssertInstanceCount<NameField>(1);\n    p_empty3 = std::move(p_empty);\n    EXPECT_FALSE(p_empty3.optionals.HasValue<NameField>());\n    EXPECT_FALSE(p_empty2.optionals.HasValue<HobbyField>());\n    EXPECT_FALSE(p_empty3.optionals.HasValue<SchoolsField>());\n    EXPECT_FALSE(p_empty3.optionals.HasValue<AgeField>());\n    EXPECT_FALSE(p_empty.optionals.HasValue<NameField>());\n    EXPECT_FALSE(p_empty.optionals.HasValue<HobbyField>());\n    EXPECT_FALSE(p_empty.optionals.HasValue<SchoolsField>());\n    EXPECT_FALSE(p_empty.optionals.HasValue<AgeField>());\n    AssertInstanceCount<NameField>(0);\n  }\n\n  Person p0;\n  p0.optionals.Get<NameField>().value = \"name0\";\n  p0.optionals.Get<SchoolsField>().value.push_back(\"elementary school\");\n  p0.optionals.Get<SchoolsField>().value.push_back(\"middle school\");\n  EXPECT_TRUE(p0.optionals.HasValue<NameField>());\n  EXPECT_TRUE(p0.optionals.HasValue<SchoolsField>());\n  EXPECT_FALSE(p0.optionals.HasValue<AgeField>());\n  EXPECT_FALSE(p0.optionals.HasValue<HobbyField>());\n  AssertInstanceCount<NameField>(1);\n  AssertInstanceCount<SchoolsField>(1);\n\n  {\n    Person p1;\n    p1.optionals.Get<NameField>().value = \"name1\";\n    p1.optionals.Get<HobbyField>() = String(\"football\");\n    p1.optionals.Get<AgeField>() = 13;\n    EXPECT_TRUE(p1.optionals.HasValue<AgeField>());\n    EXPECT_TRUE(p1.optionals.HasValue<HobbyField>());\n    p1 = p0;\n    EXPECT_TRUE(p1.optionals.Get<NameField>().value == \"name0\");\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value.size() == 2);\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value[0] ==\n                \"elementary school\");\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value[1] == \"middle school\");\n    EXPECT_FALSE(p1.optionals.HasValue<AgeField>());\n    EXPECT_FALSE(p1.optionals.HasValue<HobbyField>());\n    p1.optionals.Get<NameField>().value = \"name1\";\n    EXPECT_TRUE(p0.optionals.Get<NameField>().value == \"name0\");\n    AssertInstanceCount<NameField>(2);\n    AssertInstanceCount<SchoolsField>(2);\n  }\n\n  EXPECT_TRUE(p0.optionals.Get<NameField>().value == \"name0\");\n  EXPECT_TRUE(p0.optionals.Get<SchoolsField>().value.size() == 2);\n  EXPECT_TRUE(p0.optionals.Get<SchoolsField>().value[0] == \"elementary school\");\n  EXPECT_TRUE(p0.optionals.Get<SchoolsField>().value[1] == \"middle school\");\n  EXPECT_FALSE(p0.optionals.HasValue<HobbyField>());\n  AssertInstanceCount<NameField>(1);\n  AssertInstanceCount<SchoolsField>(1);\n\n  {\n    Person p1;\n    p1.optionals.Get<NameField>().value = \"name1\";\n    p1.optionals.Get<HobbyField>() = String(\"football\");\n    p1.optionals.Get<AgeField>() = 13;\n    EXPECT_TRUE(p1.optionals.HasValue<AgeField>());\n    EXPECT_TRUE(p1.optionals.HasValue<HobbyField>());\n    p1 = std::move(p0);\n    EXPECT_TRUE(p1.optionals.Get<NameField>().value == \"name0\");\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value.size() == 2);\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value[0] ==\n                \"elementary school\");\n    EXPECT_TRUE(p1.optionals.Get<SchoolsField>().value[1] == \"middle school\");\n    EXPECT_FALSE(p1.optionals.HasValue<AgeField>());\n    EXPECT_FALSE(p1.optionals.HasValue<HobbyField>());\n    EXPECT_FALSE(p0.optionals.HasValue<NameField>());\n    EXPECT_FALSE(p0.optionals.HasValue<SchoolsField>());\n    AssertInstanceCount<NameField>(1);\n    AssertInstanceCount<SchoolsField>(1);\n  }\n\n  AssertInstanceCount<NameField>(0);\n  AssertInstanceCount<SchoolsField>(0);\n}\n\nTEST(BundledOptional, Release) {\n  Person p0;\n  p0.optionals.Get<NameField>().value = \"name0\";\n  p0.optionals.Get<SchoolsField>().value.push_back(\"elementary school\");\n  p0.optionals.Get<SchoolsField>().value.push_back(\"middle school\");\n  EXPECT_TRUE(p0.optionals.HasValue<NameField>());\n  EXPECT_TRUE(p0.optionals.HasValue<SchoolsField>());\n  EXPECT_FALSE(p0.optionals.HasValue<AgeField>());\n  AssertInstanceCount<NameField>(1);\n  AssertInstanceCount<SchoolsField>(1);\n\n  p0.optionals.Release<NameField>();\n  EXPECT_FALSE(p0.optionals.HasValue<NameField>());\n  AssertInstanceCount<NameField>(0);\n  EXPECT_TRUE(p0.optionals.HasValue<SchoolsField>());\n  EXPECT_FALSE(p0.optionals.bundled_data_ == nullptr);\n  p0.optionals.Release<SchoolsField>();\n  EXPECT_FALSE(p0.optionals.HasValue<SchoolsField>());\n  AssertInstanceCount<SchoolsField>(0);\n  EXPECT_TRUE(p0.optionals.bundled_data_ == nullptr);\n\n  p0.optionals.Get<NameField>().value = \"name0\";\n  p0.optionals.Get<SchoolsField>().value.push_back(\"elementary school\");\n  p0.optionals.Get<SchoolsField>().value.push_back(\"middle school\");\n  p0.optionals.Get<AgeField>() = 13;\n  EXPECT_TRUE(p0.optionals.HasValue<NameField>());\n  EXPECT_TRUE(p0.optionals.HasValue<SchoolsField>());\n  EXPECT_TRUE(p0.optionals.HasValue<AgeField>());\n  EXPECT_FALSE(p0.optionals.bundled_data_ == nullptr);\n  AssertInstanceCount<NameField>(1);\n  AssertInstanceCount<SchoolsField>(1);\n\n  auto name = p0.optionals.ReleaseTransfer<NameField>().value;\n  EXPECT_TRUE(name == \"name0\");\n  EXPECT_FALSE(p0.optionals.HasValue<NameField>());\n  EXPECT_TRUE(p0.optionals.HasValue<SchoolsField>());\n  EXPECT_TRUE(p0.optionals.HasValue<AgeField>());\n  AssertInstanceCount<NameField>(0);\n  AssertInstanceCount<SchoolsField>(1);\n\n  auto schools = p0.optionals.ReleaseTransfer<SchoolsField>().value;\n  EXPECT_TRUE(schools.size() == 2);\n  EXPECT_TRUE(schools[0] == \"elementary school\");\n  EXPECT_TRUE(schools[1] == \"middle school\");\n  EXPECT_FALSE(p0.optionals.HasValue<NameField>());\n  EXPECT_FALSE(p0.optionals.HasValue<SchoolsField>());\n  EXPECT_TRUE(p0.optionals.HasValue<AgeField>());\n  AssertInstanceCount<NameField>(0);\n  AssertInstanceCount<SchoolsField>(0);\n\n  p0.optionals.Clear();\n  EXPECT_FALSE(p0.optionals.HasValue<NameField>());\n  EXPECT_FALSE(p0.optionals.HasValue<SchoolsField>());\n  EXPECT_FALSE(p0.optionals.HasValue<AgeField>());\n  EXPECT_TRUE(p0.optionals.bundled_data_ == nullptr);\n}\n\nTEST(BundledOptional, InVector) {\n  std::vector<Person> people;\n  for (int i = 0; i < 100; i++) {\n    people.emplace_back();\n    Person& p = people[i];\n    p.optionals.Get<NameField>().value =\n        std::string(\"name\") + std::to_string(i);\n    p.optionals.Get<AgeField>() = i;\n  }\n  AssertInstanceCount<NameField>(100);\n  AssertInstanceCount<SchoolsField>(0);\n\n  for (int i = 0; i < 100; i++) {\n    Person& p = people[i];\n    EXPECT_TRUE(p.optionals.HasValue<NameField>());\n    EXPECT_TRUE(p.optionals.HasValue<AgeField>());\n    EXPECT_FALSE(p.optionals.HasValue<SchoolsField>());\n    EXPECT_TRUE(p.optionals.Get<NameField>().value ==\n                std::string(\"name\") + std::to_string(i));\n    EXPECT_TRUE(p.optionals.Get<AgeField>() == i);\n  }\n\n  for (int i = 0; i < 100; i++) {\n    Person& p = people[i];\n    p.optionals.Get<SchoolsField>().value.push_back(std::string(\"school\") +\n                                                    std::to_string(i));\n  }\n  AssertInstanceCount<NameField>(100);\n  AssertInstanceCount<SchoolsField>(100);\n\n  for (int i = 0; i < 100; i++) {\n    Person& p = people[i];\n    EXPECT_TRUE(p.optionals.HasValue<NameField>());\n    EXPECT_TRUE(p.optionals.HasValue<AgeField>());\n    EXPECT_TRUE(p.optionals.HasValue<SchoolsField>());\n    EXPECT_TRUE(p.optionals.Get<NameField>().value ==\n                std::string(\"name\") + std::to_string(i));\n    EXPECT_TRUE(p.optionals.Get<SchoolsField>().value[0] ==\n                std::string(\"school\") + std::to_string(i));\n    EXPECT_TRUE(p.optionals.Get<AgeField>() == i);\n  }\n\n  for (int i = 0; i < 100; i++) {\n    Person& p = people[i];\n    p.optionals.Release<NameField>();\n  }\n  AssertInstanceCount<NameField>(0);\n  AssertInstanceCount<SchoolsField>(100);\n  for (int i = 0; i < 100; i++) {\n    Person& p = people[i];\n    EXPECT_FALSE(p.optionals.HasValue<NameField>());\n    EXPECT_TRUE(p.optionals.HasValue<AgeField>());\n    EXPECT_TRUE(p.optionals.HasValue<SchoolsField>());\n    EXPECT_TRUE(p.optionals.Get<SchoolsField>().value[0] ==\n                std::string(\"school\") + std::to_string(i));\n    EXPECT_TRUE(p.optionals.Get<AgeField>() == i);\n  }\n\n  people.back().optionals.Clear();\n  AssertInstanceCount<NameField>(0);\n  AssertInstanceCount<SchoolsField>(99);\n\n  people.erase(people.begin());\n  AssertInstanceCount<NameField>(0);\n  AssertInstanceCount<SchoolsField>(98);\n\n  people.clear();\n  AssertInstanceCount<NameField>(0);\n  AssertInstanceCount<SchoolsField>(0);\n}\n\n}  // namespace test\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/closure_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/closure.h\"\n\n#include <functional>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nnamespace {\n\nclass ReleaseCallback {\n public:\n  ReleaseCallback(std::function<void()> func,\n                  std::function<void()> release_func)\n      : func_(std::move(func)), release_func_(std::move(release_func)) {}\n  ~ReleaseCallback() {\n    if (release_func_) {\n      release_func_();\n    }\n  }\n\n  ReleaseCallback(const ReleaseCallback&) = delete;\n  ReleaseCallback& operator=(const ReleaseCallback&) = delete;\n\n  ReleaseCallback(ReleaseCallback&& other) {\n    func_ = std::move(other.func_);\n    release_func_ = std::move(other.release_func_);\n    other.func_ = nullptr;\n    other.release_func_ = nullptr;\n  }\n\n  ReleaseCallback& operator=(ReleaseCallback&& other) {\n    func_ = std::move(other.func_);\n    release_func_ = std::move(other.release_func_);\n    other.func_ = nullptr;\n    other.release_func_ = nullptr;\n    return *this;\n  }\n\n  void operator()() { func_(); }\n\n private:\n  std::function<void()> func_;\n\n  std::function<void()> release_func_;\n};\n\n};  // namespace\n\nclass ClosureTest : public ::testing::Test {\n protected:\n  ClosureTest() = default;\n  ~ClosureTest() override = default;\n\n  void SetUp() override {}\n\n  void TearDown() override {}\n};\n\nTEST_F(ClosureTest, MoveOnlyClosureCheckNull1) {\n  MoveOnlyClosure closure;\n  ASSERT_FALSE(closure);\n  ASSERT_TRUE(closure == nullptr);\n  ASSERT_FALSE(closure != nullptr);\n}\n\nTEST_F(ClosureTest, MoveOnlyClosureCheckNull2) {\n  MoveOnlyClosure closure(nullptr);\n  ASSERT_FALSE(closure);\n  ASSERT_TRUE(closure == nullptr);\n  ASSERT_FALSE(closure != nullptr);\n}\n\nTEST_F(ClosureTest, MoveOnlyClosureCheckNotNull) {\n  MoveOnlyClosure closure([] {});\n  ASSERT_TRUE(closure);\n  ASSERT_FALSE(closure == nullptr);\n  ASSERT_TRUE(closure != nullptr);\n}\n\nTEST_F(ClosureTest, MoveConstructor) {\n  bool has_run = false;\n  bool has_release = false;\n  MoveOnlyClosure need_move_closure(\n      ReleaseCallback([&has_run]() mutable { has_run = true; },\n                      [&has_release]() mutable { has_release = true; }));\n\n  auto* closure = new MoveOnlyClosure(std::move(need_move_closure));\n  ASSERT_FALSE(has_run);\n  ASSERT_FALSE(has_release);\n  ASSERT_FALSE(need_move_closure);\n\n  (*closure)();\n  ASSERT_TRUE(has_run);\n  ASSERT_FALSE(has_release);\n\n  delete closure;\n  ASSERT_TRUE(has_release);\n}\n\nTEST_F(ClosureTest, AssignmentOperator) {\n  bool has_run = false;\n  bool has_release = false;\n  MoveOnlyClosure need_move_closure(\n      ReleaseCallback([&has_run]() mutable { has_run = true; },\n                      [&has_release]() mutable { has_release = true; }));\n\n  bool old_func_has_release = false;\n  MoveOnlyClosure closure(ReleaseCallback(\n      [] {},\n      [&old_func_has_release]() mutable { old_func_has_release = true; }));\n  closure = std::move(need_move_closure);\n  ASSERT_FALSE(need_move_closure);\n  ASSERT_FALSE(has_run);\n  ASSERT_FALSE(has_release);\n  ASSERT_TRUE(old_func_has_release);\n\n  closure();\n  ASSERT_TRUE(has_run);\n  ASSERT_FALSE(has_release);\n\n  closure = nullptr;\n  ASSERT_TRUE(has_release);\n}\n\nTEST_F(ClosureTest, TypedClosureBasicTest) {\n  // Should support return type\n  MoveOnlyClosure<int32_t> closure([]() { return 0xff; });\n  ASSERT_EQ(closure(), 0xff);\n\n  // Should support return type and multiple arguments\n  MoveOnlyClosure<int32_t, int32_t, int32_t> add(\n      [](int32_t a, int32_t b) { return a + b; });\n  ASSERT_EQ(add(1, 2), 3);\n\n  // Should be move-only\n  static_assert(std::is_move_constructible_v<decltype(add)> &&\n                std::is_move_assignable_v<decltype(add)> &&\n                not std::is_copy_assignable_v<decltype(add)> &&\n                not std::is_copy_constructible_v<decltype(add)>);\n}\n\nTEST_F(ClosureTest, TypedClosureForwardTest) {\n  class Foo {};\n  MoveOnlyClosure<void, Foo&> closure([](auto& value) {\n    static_assert(std::is_same_v<decltype(value), Foo&>);\n    return;\n  });\n  Foo foo{};\n  Foo* foo_p = &foo;\n  closure(foo);\n\n  MoveOnlyClosure<Foo, Foo&&> closure_rvalue([foo_p](auto&& value) {\n    static_assert(std::is_same_v<decltype(value), Foo&&>);\n    EXPECT_EQ(foo_p, &value);\n    return value;\n  });\n  foo = closure_rvalue(std::move(foo));\n  EXPECT_EQ(foo_p, &foo);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/concurrent_queue_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/concurrent_queue.h\"\n\n#include <algorithm>\n#include <cstddef>\n#include <cstdint>\n#include <thread>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace {\n\nstatic void ToStep(std::atomic<int32_t>& s, int32_t next) {\n  int32_t pre = next - 1;\n  while (!s.compare_exchange_weak(pre, next)) {\n    std::this_thread::sleep_for(std::chrono::milliseconds(1));\n  }\n}\n\nstatic void Wait(std::atomic<int32_t>& s, int32_t next) {\n  while (s.load() != next) {\n    std::this_thread::sleep_for(std::chrono::milliseconds(1));\n  }\n}\n\ntemplate <typename PopFunction>\nstatic void TestEnqueue(ConcurrentQueue<int32_t>& queue, PopFunction pop_fn) {\n  constexpr int32_t thread_num = 8;\n  constexpr int32_t push_num = 100;\n  std::thread threads[thread_num];\n  std::atomic<int32_t> start(0);\n\n  for (auto i = 0; i < thread_num; ++i) {\n    threads[i] = std::thread([&start, &queue, i] {\n      Wait(start, 1);\n      for (auto j = i * push_num; j < (i + 1) * push_num; ++j) {\n        queue.Push(j);\n        std::this_thread::yield();\n      }\n    });\n  }\n\n  std::this_thread::sleep_for(std::chrono::milliseconds(thread_num));\n  ToStep(start, 1);\n\n  for (auto i = 0; i < thread_num; ++i) {\n    threads[i].join();\n  }\n\n  auto list = (queue.*pop_fn)();\n\n  for (auto i = 0; i < thread_num * push_num; ++i) {\n    // Each push is popped\n    EXPECT_TRUE(std::find(list.begin(), list.end(), i) != list.end());\n  }\n\n  for (auto i = 0; i < thread_num; ++i) {\n    for (auto j = 0; j < push_num; ++j) {\n      auto j_it = std::find(list.begin(), list.end(), i * push_num + j);\n      for (auto j_after = i * push_num + j + 1; j_after < (i + 1) * push_num;\n           ++j_after) {\n        auto j_after_it = std::find(list.begin(), list.end(), j_after);\n        // All push in the same thread keep the order\n        EXPECT_GT(std::distance(list.begin(), j_after_it),\n                  std::distance(list.begin(), j_it));\n      }\n    }\n  }\n}\n\nTEST(ConcurrentQueueTest, ConcurrentlyEnqueueVector) {\n  ConcurrentQueue<int32_t> queue;\n  TestEnqueue(queue, &ConcurrentQueue<int32_t>::PopAll);\n}\n\ntemplate <typename PopFunction>\nstatic void TestEnqueueWithReverseDequeue(ConcurrentQueue<int32_t>& queue,\n                                          PopFunction pop_fn) {\n  constexpr int32_t thread_num = 8;\n  constexpr int32_t push_num = 100;\n  std::thread threads[thread_num];\n  std::atomic<int32_t> start(0);\n\n  for (auto i = 0; i < thread_num; ++i) {\n    threads[i] = std::thread([&start, &queue, i] {\n      Wait(start, 1);\n      for (auto j = i * push_num; j < (i + 1) * push_num; ++j) {\n        queue.Push(j);\n        std::this_thread::yield();\n      }\n    });\n  }\n\n  std::this_thread::sleep_for(std::chrono::milliseconds(thread_num));\n  ToStep(start, 1);\n\n  for (auto i = 0; i < thread_num; ++i) {\n    threads[i].join();\n  }\n\n  auto list = (queue.*pop_fn)();\n\n  for (auto i = 0; i < thread_num * push_num; ++i) {\n    // Each push is popped\n    EXPECT_TRUE(std::find(list.begin(), list.end(), i) != list.end());\n  }\n\n  for (auto i = 0; i < thread_num; ++i) {\n    for (auto j = 0; j < push_num; ++j) {\n      auto j_it = std::find(list.begin(), list.end(), i * push_num + j);\n      for (auto j_after = i * push_num + j + 1; j_after < (i + 1) * push_num;\n           ++j_after) {\n        auto j_after_it = std::find(list.begin(), list.end(), j_after);\n        // All push in the same thread is popped in reverse order\n        EXPECT_GT(std::distance(list.begin(), j_it),\n                  std::distance(list.begin(), j_after_it));\n      }\n    }\n  }\n}\n\nTEST(ConcurrentQueueTest, ConcurrentlyEnqueueVectorWithReverseDequeue) {\n  ConcurrentQueue<int32_t> queue;\n  TestEnqueueWithReverseDequeue(queue,\n                                &ConcurrentQueue<int32_t>::ReversePopAll);\n}\n\ntemplate <typename PopFunction>\nstatic void TestAppendOrder(ConcurrentQueue<int32_t>& super_queue,\n                            PopFunction pop_fn) {\n  super_queue.Push(0);\n  super_queue.Push(1);\n  ConcurrentQueue<int32_t> sub_queue;\n  sub_queue.Push(2);\n  sub_queue.Push(3);\n\n  super_queue.Push(sub_queue);\n  super_queue.Push(4);\n\n  auto result = (super_queue.*pop_fn)();\n  EXPECT_EQ(result.size(), size_t(5));\n  size_t i = 0;\n  for (auto it = result.begin(); it != result.end(); ++it, ++i) {\n    EXPECT_EQ(size_t(*it), i);\n  }\n\n  EXPECT_TRUE(sub_queue.PopAll().empty());\n  EXPECT_TRUE(super_queue.PopAll().empty());\n}\n\nTEST(ConcurrentQueueTest, AppendOrderVector) {\n  ConcurrentQueue<int32_t> super_queue;\n  TestAppendOrder(super_queue, &ConcurrentQueue<int32_t>::PopAll);\n}\n\ntemplate <typename PopFunction>\nstatic void TestAppendEmpty(ConcurrentQueue<int32_t>& super_queue,\n                            PopFunction pop_fn) {\n  super_queue.Push(0);\n  ConcurrentQueue<int32_t> sub_queue;\n  super_queue.Push(sub_queue);\n\n  auto result = (super_queue.*pop_fn)();\n  EXPECT_EQ(result.size(), size_t(1));\n  EXPECT_EQ(result.front(), 0);\n\n  EXPECT_TRUE(sub_queue.PopAll().empty());\n  EXPECT_TRUE(super_queue.PopAll().empty());\n\n  sub_queue.Push(0);\n  super_queue.Push(sub_queue);\n\n  result = (super_queue.*pop_fn)();\n  EXPECT_EQ(result.size(), size_t(1));\n  EXPECT_EQ(result.front(), 0);\n\n  EXPECT_TRUE(sub_queue.PopAll().empty());\n  EXPECT_TRUE(super_queue.PopAll().empty());\n}\n\nTEST(ConcurrentQueueTest, AppendEmptyVector) {\n  ConcurrentQueue<int32_t> super_queue;\n  TestAppendEmpty(super_queue, &ConcurrentQueue<int32_t>::PopAll);\n}\n\ntemplate <typename PopFunction>\nstatic void TestMove(ConcurrentQueue<int32_t>& src_queue, PopFunction pop_fn) {\n  src_queue.Push(0);\n  ConcurrentQueue<int32_t> dst_queue(std::move(src_queue));\n  auto result = (dst_queue.*pop_fn)();\n  EXPECT_EQ(result.size(), size_t(1));\n  EXPECT_EQ(result.front(), 0);\n  EXPECT_TRUE(src_queue.PopAll().empty());\n  EXPECT_TRUE(dst_queue.PopAll().empty());\n\n  src_queue.Push(0);\n  dst_queue = std::move(src_queue);\n  auto result2 = (dst_queue.*pop_fn)();\n  EXPECT_EQ(result2.size(), size_t(1));\n  EXPECT_EQ(result2.front(), 0);\n  EXPECT_TRUE(src_queue.PopAll().empty());\n  EXPECT_TRUE(dst_queue.PopAll().empty());\n}\n\nTEST(ConcurrentQueueTest, MoveVector) {\n  ConcurrentQueue<int32_t> src_queue;\n  TestMove(src_queue, &ConcurrentQueue<int32_t>::PopAll);\n}\n\n}  // namespace\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/datauri_utils.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/datauri_utils.h\"\n\n#include \"base/include/string/string_utils.h\"\n#include \"third_party/modp_b64/modp_b64.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace {\nconst constexpr char *kDataURIPrefix = \"data:\";\nconst constexpr char *kBase64Prefix = \";base64,\";\n}  // namespace\n\nbool DataURIUtil::IsDataURI(const std::string_view &uri) {\n  return BeginsWith(uri, kDataURIPrefix);\n}\n\nint32_t DataURIUtil::DecodeBase64(\n    const std::string_view &base64_str,\n    lynx::base::DataURIUtil::BufferFactory factory) {\n  size_t buffer_size = lynx_modp_b64_decode_len(base64_str.length());\n  auto buffer = factory(buffer_size);\n  size_t actual_buffer_size =\n      lynx_modp_b64_decode(buffer, base64_str.data(), base64_str.length());\n  if (actual_buffer_size == static_cast<size_t>(-1)) {\n    return 0;\n  }\n  return static_cast<int32_t>(actual_buffer_size);\n}\n\nint32_t DataURIUtil::DecodeDataURI(\n    const std::string_view &uri,\n    lynx::base::DataURIUtil::BufferFactory factory) {\n  size_t pos = uri.find(kBase64Prefix);\n  if (pos == std::string_view::npos) {\n    return 0;\n  }\n\n  return DecodeBase64(uri.substr(pos + sizeof(kBase64Prefix)),\n                      std::move(factory));\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/datauri_utils_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/datauri_utils.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nTEST(DataURIUtil, IsDataURI) {\n  EXPECT_TRUE(DataURIUtil::IsDataURI(\n      \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAABJRU5ErkJggg==\"));\n  EXPECT_FALSE(DataURIUtil::IsDataURI(\n      \"  \"\n      \"data:image/\"\n      \"png;base64,iVBORw0KGgoAAAANSUhEUgAAAABJRU5ErkJggg=\"\n      \"=\"));  // do not support leading space\n  EXPECT_FALSE(DataURIUtil::IsDataURI(\n      \"image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAABJRU5ErkJggg==\"));  // no data\n                                                                      // prefix\n}\n\nTEST(DataURIUtil, DecodeBase64) {\n  {\n    std::vector<char> data;\n\n    auto size = DataURIUtil::DecodeBase64(\"SGVsbG8=\", [&](size_t size) {\n      data.resize(size);\n      return data.data();\n    });\n    EXPECT_EQ(size, 5);\n    // [72, 101, 108, 108, 111]\n    EXPECT_EQ(data[0], 72);\n    EXPECT_EQ(data[1], 101);\n    EXPECT_EQ(data[2], 108);\n    EXPECT_EQ(data[3], 108);\n    EXPECT_EQ(data[4], 111);\n  }\n\n  {\n    // invalid\n    std::vector<char> data;\n\n    auto size = DataURIUtil::DecodeBase64(\"SGVsbG8\", [&](size_t size) {\n      data.resize(size);\n      return data.data();\n    });\n    EXPECT_EQ(size, 0);\n  }\n}\n\nTEST(DataURIUtil, DecodeDataURI) {\n  const char *valid_data_uri =\n      \"data:image/\"\n      \"png;base64,\"\n      \"iVBORw0KGgoAAAANSUhEUgAAAs0AAAC4BAMAAADqEczpAAAAJFBMVEUAAAD///////////\"\n      \"////////////////////////////////+0CY3pAAAAC3RSTlMAwD5/\"\n      \"6loQ0KAgkIqmJncAAA/iSURBVHja7N3Lc9tEHAfwjR27cXqJKa/\"\n      \"BF9e8ycVNeAzkkvIGXwxlyiMXA+2UmVwKhR7qSwcKM4wvLo/h0EtgKAzTS2wrdRr9c2hf/\"\n      \"spaaS1ZjmLJ+\"\n      \"z00kW1ZyUfqavenlUNOIk9ustSqJGrybMWNGjEJkS1b5EaXRMtpm2d4pktMQjvbR63IzlL\"\n      \"6ZWIS2tl+Paoz8g8xCe1sVad2turEZIIz8nZUZ+SwRUwmOA+\"\n      \"cbsO5BsUK6JA0g5ytcrnc4NDXickE5336Ndd2sLr+\"\n      \"L1gLcu6xHVFh50JiEsaZXHGw6tM4k9KLFHqPmIRxLtkAjeZMSvSI7hOTMM6kIr6L7kxWaJ\"\n      \"eDmIRy3premXQc6CYxmZVzrVZr+\"\n      \"TmfcpyvygWlVvLLMwQp1mrPjy0tRoEEuvfod2fL5SeIyOflMu+3nS8/\"\n      \"Qmi+rND24S8f59yogS6doytcJIUyXclZv1qoOA+MDvbnnKex+AJd6l0k2Q+cG/\"\n      \"SQXEbvzj2EGTBm0VV+SHUmHfGaUoePD1/\"\n      \"Oswecb59maw3FMf6UGD9y6LNiaQHqIyPnAuubrbp6d41x59Joua4677AF7BvrSelcsVneI\"\n      \"jRf2SI92vzkXSP+rIc5yyOty3p320TAjzt/\"\n      \"hEE2nN2DQ9bxkHmHOyOUstgeLV4XZ0+\"\n      \"5F7Ie6fwcrYzy3t0BYVkad87ZyE2XM17cknJYyVM8uYLFq2ynLM4BveVoOWerNvvd0QA4u\"\n      \"UPrFzgPXrKdXHuJFUIGivMqO2LzzOzMa5Ux5/\"\n      \"c3b4l1OvTpHy78bdvDFt00W3qF74WMhzeoaDNxIrxHuxDo11G7n+XZsOt1zjPnO+\"\n      \"IUV3wRzqysvcWalYKsoD5FHyzJ8+Ftum2S8bidqwJsT54Gt+\"\n      \"GcG7Wiz9IBur9zR7YppfbI2WqJdrtJloU6KX5b5Y9dH/\"\n      \"0MWW84uDOuXBUFLylQGjgv8UXR73jgdc5RqhJqq1dGzn0Gy3opO6OG+\"\n      \"DJvl4at0U5aI9kOnNEJOBBDPKsF511nN6APd+R1LlDnPEaFuZHztiyerNF/\"\n      \"BmNbfkB4KvTbbMflzN3WBd86RYGzg3ufLvIm3PJtN5boP3hf7rwnF7eLQh24N7HnBiTbcQ\"\n      \"D6NSdP3hIjEInVoccYnNkXmHb9nFknWuSOdG5Kyf3ceIWbroE9d0iyHYy7X+\"\n      \"SHW4Gfy4oN+gXAFRthemr/\"\n      \"uYu2hTU7A5flurMZfq5Ei44sQFkVzqUGb5kbrDfLUIKd625n0R2kmAeA93UmHmeEZDvUGd\"\n      \"8e0i9Ma5kpwrmhceaOQ/\"\n      \"pPHw1JROf0TWq6UJ3OeZfD3aFk4tiEs61x5oOaoxjOqexAb1lnqhGd8X9fnggrtLEO3W4U\"\n      \"WZvjbjdWFsCZ1hi6EZ0FsAC4WaKWHufva4inv7HKzqG71DbKefDDGpK+\"\n      \"iTasS2z92J3qeOZN8f08H6XA+R77gsBZMNp1+\"\n      \"gZD9OsU58J4vy71lxSpM6RDOsuGWZwI0aNVxymqc67hbK5LfMcpcC5huEjTph3HNIc6c+\"\n      \"m7rSjOHQF3yTFeH42E1XE3+\"\n      \"bjlcf7IZqoF1CmKDdWZyvZdW8ZS8RpJYaizyBDSE5y50oGcjYFmwl1Hki98fdz5tixeU1z\"\n      \"UkRTnrVFj9CXqSPzVP5H0hTpD+q+\"\n      \"QzqWOLLfn0AHwq4uSe1QczsUPRldEtmQLXGz7OV8aVUI7e6iLsrl9wyS7z89tzM4ZCVffe\"\n      \"K2NK6yYmIgdwev8aCSY87BWu3CujUnqp1gVW7xGdc7L/fGHfUDHn6LOX6Q/\"\n      \"8Hskuazb5189AWekJ8tnTg7wguHmhrhudab29N9MlTt7L/\"\n      \"DlGuxS1ObDtp8z31fDjdo52gbJ61Y1Ot80qVMiekfvfnqCztfRwaOjFKAPuCFUvc6Pjl6M\"\n      \"qM6Xxm7SWMFSUuU6OEM6eedeazRmRk/\"\n      \"3jiAjT7mRuLMyL6Aw2hlvqs5j++p1zCtI7HCGM8/7F0/E2ZK0JbrUwiiRO5fauEnC4/\"\n      \"wdkXlWvtdZH2dMLODnvXzC8zfgLHPjYvLO1n/\"\n      \"uWVyHBGMQ7kzyDUzacjv3RqdvzKZ5eUl1lkVu7NLbcscl1NuAM6SryTh/\"\n      \"UBYRtSdMKZC5LZ3J5TZr15riQOfr/fAhGcsnDUf+ZbI6msdInND5kaw/\"\n      \"XnxF3nCIeYz2tcSY4YycaSXg7AkGc2tY/N25u1g84dwUBNag6O9h/\"\n      \"sJ5N7iWnHd8hiQXOCPVk3IupL7Ekw7n5YxfrpsXZ1avz3DmxbmT8XmFc+JcyvrdgHPinE/\"\n      \"l1ef0OWd+dtCcOO9k/ebWOXFuZ32W7Hw45zI+Spkb583NTZLpzIdz9mOc/WOc0xnj7B/\"\n      \"jnM4YZ/8Y53TGOPvHOKczxtk/\"\n      \"xnnO8yv7JPD5dc5jxdAb3CcBqdjT3Safs3GHbvRfA9P9+\"\n      \"ovtvFr25vGmj3MTzlNKPFhs5yVbidVUnatBzoVN37ykbHrfOHtzpHXWr48p3Aj/\"\n      \"NCLjrKQ+W2ebbdo4K+nPzhlnUuOsVYrvXOBn0pN3/rPsH35/\"\n      \"ie9TPzQxo1emwX7Bsho8ex7LT+icui6l2M55/\"\n      \"g4n71yxo8eqYzNTpadzqruUYjnjJXMwHoRzhAy7x+u8SVOVzm6uC/\"\n      \"SZZhjnGsuz7LioufJ8bOfiZ9cScrbfPl7nhmjPVWfcMaRx1v9ib8R0fqFtD5Jy7s3O2XI1\"\n      \"49K5oneux3K2v4njTG91TM7Zbs7E2dOarow5b/\"\n      \"s452bhbFWncIZyos5Xj8O5yBXRS1Sdw6tUNK1eNGco005Yss4PZuiMwJkPWFa9zvmZOB9O\"\n      \"5fwrVU7auX+MzkKhD16PM4npbHejO/9+y0aScx4cv/\"\n      \"OBeMHQ5bwyG+\"\n      \"d6VOeSo5xB59NsE3iBcMYDMZ3Xojrn7IjO8S915BrYUsz6ht75yMd5eXGcyc7xO59iTyjO\"\n      \"cgcshvP6cTujXVacd7HtSanRvGDTbNRG2TLOcBanO8UZJ0htdOW6gnF2OefRTPg490moYI\"\n      \"TZIkjbOMNZ1J0VZ1y9DhWsh1SMM5xzbucBvLCZkFlmDb1x1jrXvc4oMIWLbHiMc4Az0Tqv\"\n      \"Rf1ZjbPWeU11bkT8aXfmxpldh92L4nyWrvHI8TpXfJ3xgnBB4S+s8+\"\n      \"rdVizn2ScB522y7nEuRXXuRHResnsbi+TM305xDlXmV2fXRXG2Hek5dmYzOdb0jVNU5/\"\n      \"ti+AfnQlTnBvonYZ0hPWtnbEAfTJqNG70zZi3z4R+c8xjehQjOpxGcec5vJO+\"\n      \"MWM2EnMWB7HXGdawQAc/\"\n      \"NSM5S+\"\n      \"tXEnZGjRJzxybEeZ1nHYyltasKQMN6J6owP003UGakn5Hxa49zDrx6Yvrtc14zoDOnmSTm\"\n      \"/l4yzvG6y5XFm/\"\n      \"IfhnVeBGMEZ2T8p50FCzksa50FIZ6yWPuch8aRQjpS76katMjJyztVoFGf28PPhnZfT6my\"\n      \"rFyyi5a3JG617Rz9wRiY7o1y3iM72zSSd1xfX+TBx58E8ORc2tTmH9jmGMxgTct6aO+\"\n      \"eQsyGO4ju/HcG5MwvnfgqdH8R3PojgXJneGes/\"\n      \"SJHzKgziOvdm4Yyc0v2l5DZbP0XOHZzEEnfeDnRG101XrruaHucVdMpiO9tRnde0znc09a\"\n      \"0SL4umxrlYweGsGQ8+\"\n      \"ZtN8XQ7Kw94fZtX3Rs5meGd03ZRAZy81zldw848mE68qFTtBPwxA3ctXPc6obyhXWnWz6+\"\n      \"ppcS620VOI40wuhXPGcnsWztW0OP+\"\n      \"Gwzme81Jo5wZftj3OywHj6P2A7aXKOdfA4ZyEMwqkcFbq/\"\n      \"MqVVs32UuL8lLy7cU6cLd2VVrXPZ6XEudDAX2VMyjnndq4r12H1XQpklx3+\"\n      \"KXHeweGcsHNVcc4HONc1MwEP0+FcwM3nM3dG/\"\n      \"yGSc1XtUuhmMabDuSMKoq2knKHXVZzVLawEbBMKB6lwXsGfQU7WGayqs+YtVdN+Kpw7+\"\n      \"DPICTqvqM7qfFFcabV0zvfT4HwFBaSEnPHCoepM4By6XLefAmc54j4kSTh/\"\n      \"U2Ppyukb4g2bXuc9b5fiSOe8lgJnHM5JOKMuuux2rmJNFDyCuxRILi3OGHEn6iwfV5ylUs\"\n      \"gyUoG/\"\n      \"3Xw40ynguoIxCsPuPHSSztvaLgWSnyfnij1N+\"\n      \"sfpLIv3GIQo92lOLtehb22c9c79MM62rlxnnCc47+\"\n      \"ic3RsvBpbrMIvROAc4y9vlFWd5oIco16EsapwnOO8L55bGOT+\"\n      \"xXNczzsHOHbcz1pT3rUQp1x0Z5yBniaE4YwXtqW4MYWCcA8fdEmNFdfZUAU7pfr17bIxln\"\n      \"APqSLjcvaQ4M9eecqrTlUXnxFkzHsQ0oWTGg3Dm7YfiLAtM2lMd0mBUc+\"\n      \"KMxPhwjmSdrSjlum3jHOQs66FQDfwc163J5TrjPMG5ilbCzxmnur5xns65MMG5qjnVneWn\"\n      \"D4xhjHOgsxwHnlKcsYmgUx1GjKu89THOE5zp0/7OTc2pDs4Yw8R3Lt3KovNKgDM+wd+/\"\n      \"CYYzxjDdmM4yvz+cPWfZLqvOBM5oyPf8nU9ja7Gc8fn8i+Z8U1OugzOrOQ1n4Yy/\"\n      \"N5Et59Nu5yOsGdDAVFVnjBVn4gzpSuacB/\"\n      \"ga6LykccYYZibOkM6O867GGR0MNMFJOCOftbPiLD+2bl1xxoVY7BDLxxljmFk64++\"\n      \"1Zci5r3HWlOvg3J65M5IN5y9qTp7xdf6FPaW5MgVnLmWc/Z0R1TnElSk4l/\"\n      \"hY0TjHdUYTrDpjrGic/\"\n      \"Z2Ln7zhMdM4tzXOBT6kMc5+zkV0msI4CwzVGWNF46w6YxDAs6V3RhOsOstVmsZZcb5G/\"\n      \"w3tjCZY51wN5TxcKGeaSM55lOsUZzFWbIVwtu62iHHWOK+\"\n      \"iCVadd7GK1tn6sUvIgjhfbkzhjKZBdcZYMdgZygvifPlfG1Gc7wc7L2OD6r7ZCeFsnYGyn\"\n      \"/NaqpyLNZFnfZ0t29Y576vOmnJdscGcMVYMcmbKVUI0zjeqJFXO7HFEgQp0bk9wXlfLdX/\"\n      \"Y0rkzyRnKvs43LhKyGM45O5Rz2R2bO2NMHuQMZY8zlLPi/\"\n      \"H97d4yqMBCEAXhQ3gNf92ob8QSK2NvYmCaCiGWOYG1lY694AfEIKih4ORPN8iuTHxzQmFX\"\n      \"/KgRCyJeww4ZNpjIKtPJuFrkhYMCccZUqcA5u/\"\n      \"nm5bKVJl6JGrIKgBYLduVdI57JSRvnHHjgjDXzkz52P6k01r29whrLJOT2skM7ODkFhus+\"\n      \"5Sp3dSTtWZzROMTnblftq/\"\n      \"XNuzomydh5z54A5d9Bw1+\"\n      \"iMRkAGZ3VzzCNOTs5Q1s5T7nxkmWJObnOumLVclyZPnCehSLZzSJ1L1HnulnY0bM52rbTr\"\n      \"mCfOvaEIcd4Jdf5hzFvMyZ/unCh74pyUEOq84c5l5rzCnPzJzs1YuTDOv/\"\n      \"WbwBklhDuHxBk7VbpqTq6dX55HOfPAmQ9uV2bc+e8/\"\n      \"MzNJsjhvf51jOyhT58OcON+Zr3Nst18LS+uSdpTZ3qUm/ic/\"\n      \"51IkH5y+Gu9WNud3eNiKHY+cT6io+EYELY6MAAAAAElFTkSuQmCC\";\n\n  {\n    std::vector<char> data;\n\n    auto size = DataURIUtil::DecodeDataURI(valid_data_uri, [&](size_t size) {\n      data.resize(size);\n      return data.data();\n    });\n    EXPECT_EQ(size, 4194);\n  }\n\n  {\n    // do not support leading blank\n    std::vector<char> data;\n\n    auto leading_blank_data_uri = \"  \" + std::string(valid_data_uri);\n    auto size =\n        DataURIUtil::DecodeBase64(leading_blank_data_uri, [&](size_t size) {\n          data.resize(size);\n          return data.data();\n        });\n    EXPECT_EQ(size, 0);\n  }\n\n  {\n    // invalid base64\n    std::vector<char> data;\n\n    auto size = DataURIUtil::DecodeBase64(\n        std::string(valid_data_uri).substr(0, sizeof(valid_data_uri) - 1),\n        [&](size_t size) {\n          data.resize(size);\n          return data.data();\n        });\n    EXPECT_EQ(size, 0);\n  }\n\n  {\n    // invalid data uri prefix\n    std::vector<char> data;\n\n    auto size = DataURIUtil::DecodeBase64(std::string(valid_data_uri).substr(5),\n                                          [&](size_t size) {\n                                            data.resize(size);\n                                            return data.data();\n                                          });\n    EXPECT_EQ(size, 0);\n  }\n\n  {\n    // invalid data uri prefix\n    std::vector<char> data;\n\n    auto size =\n        DataURIUtil::DecodeBase64(\"image/png;base64,SGVsbG8\", [&](size_t size) {\n          data.resize(size);\n          return data.data();\n        });\n    EXPECT_EQ(size, 0);\n  }\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/debug/backtrace.cc",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/debug/backtrace.h\"\n\n#include <cstdlib>\n#include <mutex>\n#include <utility>\n\nnamespace lynx {\nnamespace base {\nnamespace debug {\n\nstatic BacktraceDelegate* g_backtrace_delegate = nullptr;\nstatic std::mutex g_backtrace_delegate_mutex;\nvoid SetBacktraceDelegate(BacktraceDelegate* delegate) {\n  std::lock_guard<std::mutex> lock(g_backtrace_delegate_mutex);\n  if (g_backtrace_delegate) {\n    delete g_backtrace_delegate;\n  }\n  g_backtrace_delegate = delegate;\n}\n// IOS\nstd::string GetBacktraceInfo(std::string& error_message) {\n  if (g_backtrace_delegate) {\n    std::string traceInfo = g_backtrace_delegate->TraceLog(error_message, 2);\n    if (!traceInfo.empty()) {\n      return traceInfo;\n    }\n  }\n  // in debug\n#if OS_IOS\n  error_message.append(\"\\n\\n\");\n  constexpr int max = 30;\n  void* buffer[max];\n  int stack_num = backtrace(buffer, max);\n  char** stacktrace = backtrace_symbols(buffer, stack_num);\n  if (stacktrace == nullptr) {\n    return \"\";\n  }\n  // begin from 2 can throw backtrace for AddBackTrace and LynxException\n  for (int i = 2; i < stack_num; ++i) {\n    // make order begin with 0\n    int order = i - 2;\n    int offset = order >= 10 ? 3 : 2;\n    error_message.append(std::to_string(order))\n        .append(stacktrace[i] + offset)\n        .append(\"\\n\");\n  }\n  free(stacktrace);\n#endif\n  return error_message;\n}\n\n}  // namespace debug\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/debug/lynx_error.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/debug/lynx_error.h\"\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/include/string/string_utils.h\"\n\n#if !defined(OS_WIN)\n#include \"base/include/debug/backtrace.h\"\n#endif\n\n#include <cstdarg>\n\nnamespace lynx {\n\nnamespace base {\n\nstatic constexpr const char* kLynxErrorKeyPrefixContext = \"lynx_context_\";\n\n// some commonly used key\nstatic constexpr const char* kLynxErrorKeyErrorStack = \"error_stack\";\n\nnamespace {\n\nstd::string AddBackTrace(std::string& error_message) {\n#if OS_IOS\n  return lynx::base::debug::GetBacktraceInfo(error_message);\n#else\n  return error_message;\n#endif\n}\n\n}  // namespace\n\nbool StoreError(int32_t error_code, std::string error_msg,\n                std::string fix_suggestion, LynxErrorLevel level) {\n  LynxError error{error_code, std::move(error_msg), std::move(fix_suggestion),\n                  level};\n  return ErrorStorage::GetInstance().SetError(std::move(error));\n}\n\nbool StoreErrorIfNot(bool expression, int32_t error_code, std::string error_msg,\n                     std::string fix_suggestion, base::LynxErrorLevel level) {\n  if (!expression) {\n    LynxError error{error_code, std::move(error_msg), std::move(fix_suggestion),\n                    level};\n    return ErrorStorage::GetInstance().SetError(std::move(error));\n  }\n  return false;\n}\n\nLynxError::LynxError(int error_code, const char* format, ...)\n    : error_code_(error_code), is_logbox_only_(false) {\n  va_list args;\n  va_start(args, format);\n  error_message_ = FormatStringWithVaList(format, args);\n  va_end(args);\n  error_message_ = AddBackTrace(error_message_);\n  error_level_ = LynxErrorLevel::Error;\n  LOGI(\"LynxError occurs error_code:\" << error_code\n                                      << \" error_message:\" << error_message_);\n}\n\nLynxError::LynxError(int error_code, std::string error_msg,\n                     std::string fix_suggestion, LynxErrorLevel level,\n                     bool is_logbox_only)\n    : error_level_(level),\n      error_code_(error_code),\n      error_message_(std::move(error_msg)),\n      fix_suggestion_(std::move(fix_suggestion)),\n      is_logbox_only_(is_logbox_only) {\n  LOGI(\"LynxError occurs error_code:\" << error_code_\n                                      << \" error_message:\" << error_message_);\n}\n\nvoid LynxError::AddCallStack(const std::string& stack) {\n  custom_info_[kLynxErrorKeyErrorStack] = stack;\n}\n\nvoid LynxError::AddContextInfo(const std::string& key,\n                               const std::string& value) {\n  std::string context_key(kLynxErrorKeyPrefixContext);\n  context_key.append(key);\n  custom_info_[context_key] = value;\n}\n\nstd::string LynxError::GetLevelString(int32_t level) {\n  LynxErrorLevel l_enum = LynxErrorLevel::Error;\n  if (level >= static_cast<int32_t>(LynxErrorLevel::Fatal) &&\n      level <= static_cast<int32_t>(LynxErrorLevel::Warn)) {\n    l_enum = static_cast<LynxErrorLevel>(level);\n  }\n  switch (l_enum) {\n    case LynxErrorLevel::Fatal:\n      return \"fatal\";\n    case LynxErrorLevel::Warn:\n      return \"warn\";\n    case LynxErrorLevel::Error:\n    default:\n      return \"error\";\n  }\n}\n\nErrorStorage& ErrorStorage::GetInstance() {\n#if defined(OS_IOS) && defined(__i386__)\n  // constructor is private, so must pass here\n  static ThreadLocal<ErrorStorage> instance([] { return new ErrorStorage; });\n#else\n  static thread_local ErrorStorage instance;\n#endif\n  return instance;\n}\n\nvoid ErrorStorage::AddCustomInfoToError(\n    const std::unordered_map<std::string, std::string>& custom_info) {\n  if (!error_) {\n    return;\n  }\n  for (const auto& [key, value] : custom_info) {\n    error_->custom_info_[key] = value;\n  }\n}\n\nvoid ErrorStorage::AddCustomInfoToError(const std::string& key,\n                                        const std::string& value) {\n  if (error_) {\n    error_->custom_info_[key] = value;\n  }\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/debug/lynx_error_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/debug/lynx_error.h\"\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nstatic constexpr int kTestErrorCode = -100;\nstatic constexpr char kTestErrorMessage[] = \"Test error.\";\nstatic constexpr char kTestErrorSuggestion[] = \"Some fix suggestion\";\nstatic constexpr char kTestErrorContextValue1[] = \"context field test value1\";\nstatic constexpr char kTestErrorContextValue2[] = \"context field test value2\";\n\nTEST(ErrorStorageTest, GetSetAndRest) {\n  int err = -1;\n  int err2 = -100;\n  std::string err_msg = \"Test error.\";\n  std::string err_msg2 = \"Other error.\";\n\n  ErrorStorage::GetInstance().SetError(-1, \"Test error.\");\n  auto& error = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error, nullptr);\n  ASSERT_EQ(error->error_code_, err);\n  ASSERT_EQ(error->error_message_, err_msg);\n\n  // If not reset, ignore the following error\n  ErrorStorage::GetInstance().SetError(err2, err_msg2);\n  auto& error_set_again = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error_set_again, nullptr);\n  ASSERT_EQ(error_set_again->error_code_, err);\n  ASSERT_EQ(error_set_again->error_message_, err_msg);\n\n  // Reset error\n  ErrorStorage::GetInstance().Reset();\n  auto& reset_error = ErrorStorage::GetInstance().GetError();\n  ASSERT_EQ(reset_error, nullptr);\n\n  // After reset, we can set error now.\n  ErrorStorage::GetInstance().SetError(err2, err_msg2);\n  auto& error2 = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error2, nullptr);\n  ASSERT_EQ(error2->error_code_, err2);\n  ASSERT_EQ(error2->error_message_, err_msg2);\n}\n\nTEST(LynxErrorTest, MacroWithString) {\n  int error_code = 601;\n  constexpr char error_message[] = \"some error occurred!\";\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  // test report error by LynxInfo with string\n  LynxInfo(error_code, error_message);\n  auto& error1 = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error1, nullptr);\n  ASSERT_EQ(error1->error_code_, error_code);\n  ASSERT_EQ(error1->error_message_, error_message);\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  // test report error by LynxWarning with expression value is true\n  LynxWarning(true, error_code, error_message);\n  auto& error3 = ErrorStorage::GetInstance().GetError();\n  ASSERT_EQ(error3, nullptr);\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  // test report error by LynxWarning with expression value is false\n  LynxWarning(false, error_code, error_message);\n  auto& error4 = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error4, nullptr);\n  ASSERT_EQ(error4->error_code_, error_code);\n  ASSERT_EQ(error4->error_message_, error_message);\n}\n\nTEST(LynxErrorTest, MacroWithFormatString) {\n  int* ptr = new int(10);\n  int error_code = 601;\n  constexpr char fmt_error_message[] =\n      \"the error is %s, the code is %d, the pointer is %p\";\n  std::stringstream ss;\n  ss << \"the error is error, the code is 601, the pointer is \" << ptr;\n  std::string expect_fmt_error_msg = ss.str();\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  // test report error by LynxInfo with format string\n  LynxInfo(error_code, fmt_error_message, \"error\", error_code, ptr);\n  auto& error2 = ErrorStorage::GetInstance().GetError();\n  // ASSERT_NE(error2, nullptr);\n  ASSERT_EQ(error2->error_code_, error_code);\n  ASSERT_EQ(error2->error_message_, expect_fmt_error_msg);\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  // test report error by LynxInfo with format string\n  LynxWarning(false, error_code, fmt_error_message, \"error\", error_code, ptr);\n  auto& error5 = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error5, nullptr);\n  ASSERT_EQ(error5->error_code_, error_code);\n  ASSERT_EQ(error5->error_message_, expect_fmt_error_msg);\n}\n\nTEST(LynxErrorTest, StoreError) {\n  int error_code = 601;\n  constexpr char error_message[] = \"some error occurred!\";\n  constexpr char fix_suggestion[] = \"a fix suggestion\";\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  // test store error by LYNX_ERROR\n  LYNX_ERROR(error_code, error_message, fix_suggestion);\n  auto& error1 = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error1, nullptr);\n  ASSERT_EQ(error1->error_code_, error_code);\n  ASSERT_EQ(error1->error_message_, error_message);\n  ASSERT_EQ(error1->error_level_, LynxErrorLevel::Error);\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  LYNX_WARN(error_code, error_message, fix_suggestion);\n  auto& error2 = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error2, nullptr);\n  ASSERT_EQ(error2->error_level_, LynxErrorLevel::Warn);\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  // test store error by LYNX_ERROR_CHECK with expression value is true\n  LYNX_ERROR_CHECK(true, error_code, error_message, fix_suggestion);\n  auto& error3 = ErrorStorage::GetInstance().GetError();\n  ASSERT_EQ(error3, nullptr);\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  // test store error by LYNX_ERROR_CHECK with expression value is false\n  LYNX_ERROR_CHECK(false, error_code, error_message, fix_suggestion);\n  auto& error4 = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error4, nullptr);\n  ASSERT_EQ(error4->error_code_, error_code);\n  ASSERT_EQ(error4->error_message_, error_message);\n  ASSERT_EQ(error4->error_level_, LynxErrorLevel::Error);\n\n  // reset error\n  ErrorStorage::GetInstance().Reset();\n\n  LYNX_WARN_CHECK(false, error_code, error_message, fix_suggestion);\n  auto& error5 = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error5, nullptr);\n  ASSERT_EQ(error5->error_level_, LynxErrorLevel::Warn);\n}\n\nTEST(LynxErrorTest, AddCustomInfoToStoredError) {\n  int err_code = -100;\n  std::string err_msg = \"Test error.\";\n  std::string fix_suggestion = \"Some fix suggestion\";\n\n  // Test add custom info when ErrorStorage is empty\n  ErrorStorage::GetInstance().Reset();\n  std::unordered_map<std::string, std::string> custom_info(\n      {{\"key1\", \"value1\"}, {\"key2\", \"value2\"}});\n  ErrorStorage::GetInstance().AddCustomInfoToError(custom_info);\n  ErrorStorage::GetInstance().AddCustomInfoToError(\"key3\", \"value3\");\n  ASSERT_EQ(ErrorStorage::GetInstance().GetError(), nullptr);\n\n  // Test add custom info when ErrorStorage has error\n  LynxError lynxError(err_code, err_msg, fix_suggestion, LynxErrorLevel::Error);\n  ErrorStorage::GetInstance().SetError(std::move(lynxError));\n  ASSERT_NE(ErrorStorage::GetInstance().GetError(), nullptr);\n  ASSERT_TRUE(ErrorStorage::GetInstance().GetError()->custom_info_.empty());\n\n  ErrorStorage::GetInstance().AddCustomInfoToError(custom_info);\n  ErrorStorage::GetInstance().AddCustomInfoToError(\"key3\", \"value3\");\n  const auto& error = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(error->custom_info_.find(\"key1\"), error->custom_info_.end());\n  ASSERT_NE(error->custom_info_.find(\"key2\"), error->custom_info_.end());\n  ASSERT_NE(error->custom_info_.find(\"key3\"), error->custom_info_.end());\n  ASSERT_EQ(error->custom_info_.find(\"key1\")->second, \"value1\");\n  ASSERT_EQ(error->custom_info_.find(\"key2\")->second, \"value2\");\n  ASSERT_EQ(error->custom_info_.find(\"key3\")->second, \"value3\");\n}\n\nTEST(LynxErrorTest, AddContextInfo) {\n  LynxError error{kTestErrorCode, std::string(kTestErrorMessage),\n                  kTestErrorSuggestion, LynxErrorLevel::Error};\n  error.AddContextInfo(\"key1\", kTestErrorContextValue1);\n  error.AddContextInfo(\"key2\", kTestErrorContextValue2);\n  ASSERT_NE(error.custom_info_.find(\"lynx_context_key1\"),\n            error.custom_info_.end());\n  ASSERT_NE(error.custom_info_.find(\"lynx_context_key2\"),\n            error.custom_info_.end());\n  ASSERT_EQ(error.custom_info_.find(\"lynx_context_key1\")->second,\n            kTestErrorContextValue1);\n  ASSERT_EQ(error.custom_info_.find(\"lynx_context_key2\")->second,\n            kTestErrorContextValue2);\n}\n\nTEST(LynxErrorTest, GetLevelString) {\n  int32_t fatal_int = static_cast<int32_t>(LynxErrorLevel::Fatal);\n  int32_t error_int = static_cast<int32_t>(LynxErrorLevel::Error);\n  int32_t warn_int = static_cast<int32_t>(LynxErrorLevel::Warn);\n  int32_t unknown_int = 100;\n  ASSERT_EQ(LynxError::GetLevelString(fatal_int), \"fatal\");\n  ASSERT_EQ(LynxError::GetLevelString(error_int), \"error\");\n  ASSERT_EQ(LynxError::GetLevelString(warn_int), \"warn\");\n  ASSERT_EQ(LynxError::GetLevelString(unknown_int), \"error\");\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/expected_unittest.cc",
    "content": "// Copyright 2022 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/expected.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nnamespace {\n\n// Additional restrictions on implicit conversions. Not present in the C++23\n// proposal.\nstatic_assert(!std::is_convertible_v<int, expected<int, int>>);\nstatic_assert(!std::is_convertible_v<long, expected<bool, long>>);\n\ntemplate <typename T>\nstruct Strong {\n  constexpr explicit Strong(T value) : value(std::move(value)) {}\n  T value;\n};\n\ntemplate <typename T>\nstruct Weak {\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  constexpr Weak(T value) : value(std::move(value)) {}\n  T value;\n};\n\ntemplate <typename T>\nstruct StrongMoveOnly {\n  constexpr explicit StrongMoveOnly(T&& value) : value(std::move(value)) {}\n  constexpr StrongMoveOnly(StrongMoveOnly&& other)\n      : value(std::exchange(other.value, {})) {}\n\n  constexpr StrongMoveOnly& operator=(StrongMoveOnly&& other) {\n    value = std::exchange(other.value, {});\n    return *this;\n  }\n\n  T value;\n};\n\ntemplate <typename T>\nstruct WeakMoveOnly {\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  constexpr WeakMoveOnly(T&& value) : value(std::move(value)) {}\n  constexpr WeakMoveOnly(WeakMoveOnly&& other)\n      : value(std::exchange(other.value, {})) {}\n  T value;\n};\n\nenum class Error {\n  kFail,\n};\n\nenum class CvRef {\n  kNone,\n  kRef,\n  kConstRef,\n  kRRef,\n  kConstRRef,\n};\n\nstruct SaveCvRef {\n  constexpr SaveCvRef() = default;\n  constexpr SaveCvRef(SaveCvRef&) : cvref(CvRef::kRef) {}\n  constexpr SaveCvRef(const SaveCvRef&) : cvref(CvRef::kConstRef) {}\n  constexpr SaveCvRef(SaveCvRef&&) : cvref(CvRef::kRRef) {}\n  constexpr SaveCvRef(const SaveCvRef&&) : cvref(CvRef::kConstRRef) {}\n\n  constexpr explicit SaveCvRef(CvRef cvref) : cvref(cvref) {}\n\n  CvRef cvref = CvRef::kNone;\n};\n\nTEST(Ok, ValueConstructor) {\n  constexpr ok<int> o(42);\n  static_assert(o.value() == 42);\n}\n\nTEST(Ok, DefaultConstructor) {\n  constexpr ok<int> o(std::in_place);\n  static_assert(o.value() == 0);\n}\n\nTEST(Ok, InPlaceConstructor) {\n  constexpr ok<std::pair<int, double>> o(std::in_place, 42, 3.14);\n  static_assert(o.value() == std::pair(42, 3.14));\n}\n\nTEST(Ok, InPlaceListConstructor) {\n  ok<std::vector<int>> o(std::in_place, {1, 2, 3});\n  EXPECT_EQ(o.value(), std::vector({1, 2, 3}));\n}\n\nTEST(Ok, ValueIsQualified) {\n  using Ok = ok<int>;\n  static_assert(std::is_same_v<decltype(std::declval<Ok&>().value()), int&>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const Ok&>().value()), const int&>);\n  static_assert(std::is_same_v<decltype(std::declval<Ok>().value()), int&&>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const Ok>().value()), const int&&>);\n}\n\nTEST(Ok, MemberSwap) {\n  ok o1(42);\n  ok o2(123);\n  o1.swap(o2);\n\n  EXPECT_EQ(o1.value(), 123);\n  EXPECT_EQ(o2.value(), 42);\n}\n\nTEST(Ok, EqualityOperators) {\n  static_assert(ok(42) == ok(42.0));\n  static_assert(ok(42) != ok(43));\n}\n\nTEST(Ok, FreeSwap) {\n  ok o1(42);\n  ok o2(123);\n  swap(o1, o2);\n\n  EXPECT_EQ(o1.value(), 123);\n  EXPECT_EQ(o2.value(), 42);\n}\n\nTEST(Unexpected, ValueConstructor) {\n  constexpr unexpected<int> unex(42);\n  static_assert(unex.error() == 42);\n}\n\nTEST(Unexpected, DefaultConstructor) {\n  constexpr unexpected<int> unex(std::in_place);\n  static_assert(unex.error() == 0);\n}\n\nTEST(Unexpected, InPlaceConstructor) {\n  constexpr unexpected<std::pair<int, double>> unex(std::in_place, 42, 3.14);\n  static_assert(unex.error() == std::pair(42, 3.14));\n}\n\nTEST(Unexpected, InPlaceListConstructor) {\n  unexpected<std::vector<int>> unex(std::in_place, {1, 2, 3});\n  EXPECT_EQ(unex.error(), std::vector({1, 2, 3}));\n}\n\nTEST(Unexpected, ErrorIsQualified) {\n  using Unex = unexpected<int>;\n  static_assert(std::is_same_v<decltype(std::declval<Unex&>().error()), int&>);\n  static_assert(std::is_same_v<decltype(std::declval<const Unex&>().error()),\n                               const int&>);\n  static_assert(std::is_same_v<decltype(std::declval<Unex>().error()), int&&>);\n  static_assert(std::is_same_v<decltype(std::declval<const Unex>().error()),\n                               const int&&>);\n}\n\nTEST(Unexpected, MemberSwap) {\n  unexpected u1(42);\n  unexpected u2(123);\n  u1.swap(u2);\n\n  EXPECT_EQ(u1.error(), 123);\n  EXPECT_EQ(u2.error(), 42);\n}\n\nTEST(Unexpected, EqualityOperators) {\n  static_assert(unexpected(42) == unexpected(42.0));\n  static_assert(unexpected(42) != unexpected(43));\n}\n\nTEST(Unexpected, FreeSwap) {\n  unexpected u1(42);\n  unexpected u2(123);\n  swap(u1, u2);\n\n  EXPECT_EQ(u1.error(), 123);\n  EXPECT_EQ(u2.error(), 42);\n}\n\nTEST(Expected, Triviality) {\n  using TrivialExpected = expected<int, Error>;\n  static_assert(std::is_trivially_destructible_v<TrivialExpected>);\n\n  using NonTrivialExpected = expected<int, std::string>;\n  static_assert(!std::is_trivially_destructible_v<NonTrivialExpected>);\n}\n\nTEST(Expected, DefaultConstructor) {\n  constexpr expected<int, Error> ex;\n  static_assert(ex.has_value());\n  EXPECT_EQ(ex.value(), 0);\n\n  static_assert(std::is_default_constructible_v<expected<int, Error>>);\n  static_assert(!std::is_default_constructible_v<expected<Strong<int>, Error>>);\n}\n\nTEST(Expected, CopyConstructor) {\n  {\n    constexpr expected<int, Error> ex1 = 42;\n    constexpr expected<int, Error> ex2 = ex1;\n    static_assert(ex2.has_value());\n    static_assert(ex2.value() == 42);\n  }\n\n  {\n    constexpr expected<int, Error> ex1 = unexpected(Error::kFail);\n    constexpr expected<int, Error> ex2 = ex1;\n    static_assert(!ex2.has_value());\n    static_assert(ex2.error() == Error::kFail);\n  }\n}\n\nTEST(Expected, MoveConstructor) {\n  {\n    expected<StrongMoveOnly<int>, int> ex1 = StrongMoveOnly(42);\n    expected<StrongMoveOnly<int>, int> ex2 = std::move(ex1);\n    ASSERT_TRUE(ex2.has_value());\n    EXPECT_EQ(ex2.value().value, 42);\n  }\n\n  {\n    expected<int, StrongMoveOnly<int>> ex1 = unexpected(StrongMoveOnly(42));\n    expected<int, StrongMoveOnly<int>> ex2 = std::move(ex1);\n    ASSERT_FALSE(ex2.has_value());\n    EXPECT_EQ(ex2.error().value, 42);\n  }\n}\n\nTEST(Expected, ExplicitConvertingCopyConstructor) {\n  {\n    expected<int, Error> ex1 = 42;\n    expected<Strong<int>, Error> ex2(ex1);\n    static_assert(!std::is_convertible_v<decltype(ex1), decltype(ex2)>);\n    ASSERT_TRUE(ex2.has_value());\n    EXPECT_EQ(ex2.value().value, 42);\n  }\n\n  {\n    expected<int, Error> ex1 = unexpected(Error::kFail);\n    expected<int, Strong<Error>> ex2(ex1);\n    static_assert(!std::is_convertible_v<decltype(ex1), decltype(ex2)>);\n    ASSERT_FALSE(ex2.has_value());\n    EXPECT_EQ(ex2.error().value, Error::kFail);\n  }\n}\n\nTEST(Expected, ImplicitConvertingCopyConstructor) {\n  {\n    expected<int, Error> ex1 = 42;\n    expected<Weak<int>, Weak<Error>> ex2 = ex1;\n    ASSERT_TRUE(ex2.has_value());\n    EXPECT_EQ(ex2.value().value, 42);\n  }\n  {\n    expected<int, Error> ex1 = unexpected(Error::kFail);\n    expected<Weak<int>, Weak<Error>> ex2 = ex1;\n    ASSERT_FALSE(ex2.has_value());\n    EXPECT_EQ(ex2.error().value, Error::kFail);\n  }\n}\n\nTEST(Expected, ExplicitConvertingMoveConstructor) {\n  {\n    expected<int, Error> ex1 = 42;\n    expected<StrongMoveOnly<int>, Error> ex2(std::move(ex1));\n    static_assert(\n        !std::is_convertible_v<decltype(std::move(ex1)), decltype(ex2)>);\n    ASSERT_TRUE(ex2.has_value());\n    EXPECT_EQ(ex2.value().value, 42);\n  }\n\n  {\n    expected<int, Error> ex1 = unexpected(Error::kFail);\n    expected<int, StrongMoveOnly<Error>> ex2(std::move(ex1));\n    static_assert(\n        !std::is_convertible_v<decltype(std::move(ex1)), decltype(ex2)>);\n    ASSERT_FALSE(ex2.has_value());\n    EXPECT_EQ(ex2.error().value, Error::kFail);\n  }\n}\n\nTEST(Expected, ImplicitConvertingMoveConstructor) {\n  {\n    expected<int, Error> ex1 = 42;\n    expected<WeakMoveOnly<int>, Error> ex2 = std::move(ex1);\n    ASSERT_TRUE(ex2.has_value());\n    EXPECT_EQ(ex2.value().value, 42);\n  }\n\n  {\n    expected<int, Error> ex1 = unexpected(Error::kFail);\n    expected<int, WeakMoveOnly<Error>> ex2 = std::move(ex1);\n    ASSERT_FALSE(ex2.has_value());\n    EXPECT_EQ(ex2.error().value, Error::kFail);\n  }\n}\n\nTEST(Expected, ExplicitValueConstructor) {\n  {\n    constexpr expected<Strong<int>, int> ex(42);\n    static_assert(!std::is_convertible_v<int, decltype(ex)>);\n    static_assert(ex.has_value());\n    EXPECT_EQ(ex.value().value, 42);\n  }\n\n  {\n    constexpr expected<StrongMoveOnly<int>, int> ex(42);\n    static_assert(!std::is_constructible_v<decltype(ex), int&>);\n    static_assert(!std::is_convertible_v<int, decltype(ex)>);\n    static_assert(ex.has_value());\n    EXPECT_EQ(ex.value().value, 42);\n  }\n}\n\nTEST(Expected, ImplicitValueConstructor) {\n  {\n    constexpr expected<Weak<int>, Error> ex = 42;\n    static_assert(ex.has_value());\n    EXPECT_EQ(ex.value().value, 42);\n  }\n\n  {\n    constexpr expected<WeakMoveOnly<int>, Error> ex = 42;\n    static_assert(!std::is_convertible_v<int&, decltype(ex)>);\n    static_assert(ex.has_value());\n    EXPECT_EQ(ex.value().value, 42);\n  }\n}\n\nTEST(Expected, ExplicitOkConstructor) {\n  {\n    constexpr expected<Strong<int>, int> ex(ok(42));\n    static_assert(!std::is_convertible_v<ok<int>, decltype(ex)>);\n    static_assert(ex.has_value());\n    EXPECT_EQ(ex.value().value, 42);\n  }\n\n  {\n    constexpr expected<StrongMoveOnly<int>, int> ex(ok(42));\n    static_assert(!std::is_constructible_v<decltype(ex), ok<int>&>);\n    static_assert(!std::is_convertible_v<ok<int>, decltype(ex)>);\n    static_assert(ex.has_value());\n    EXPECT_EQ(ex.value().value, 42);\n  }\n}\n\nTEST(Expected, ImplicitOkConstructor) {\n  {\n    constexpr expected<Weak<int>, Error> ex = ok(42);\n    static_assert(ex.has_value());\n    EXPECT_EQ(ex.value().value, 42);\n  }\n\n  {\n    constexpr expected<WeakMoveOnly<int>, Error> ex = ok(42);\n    static_assert(!std::is_convertible_v<ok<int>&, decltype(ex)>);\n    static_assert(ex.has_value());\n    EXPECT_EQ(ex.value().value, 42);\n  }\n}\n\nTEST(Expected, ExplicitErrorConstructor) {\n  {\n    constexpr expected<int, Strong<int>> ex(unexpected(42));\n    static_assert(!std::is_convertible_v<unexpected<int>, decltype(ex)>);\n    static_assert(!ex.has_value());\n    EXPECT_EQ(ex.error().value, 42);\n  }\n\n  {\n    constexpr expected<int, StrongMoveOnly<int>> ex(unexpected(42));\n    static_assert(!std::is_constructible_v<decltype(ex), unexpected<int>&>);\n    static_assert(!std::is_convertible_v<unexpected<int>, decltype(ex)>);\n    static_assert(!ex.has_value());\n    EXPECT_EQ(ex.error().value, 42);\n  }\n}\n\nTEST(Expected, ImplicitErrorConstructor) {\n  {\n    constexpr expected<int, Weak<int>> ex = unexpected(42);\n    static_assert(!ex.has_value());\n    EXPECT_EQ(ex.error().value, 42);\n  }\n\n  {\n    constexpr expected<int, WeakMoveOnly<int>> ex = unexpected(42);\n    static_assert(!std::is_convertible_v<unexpected<int>&, decltype(ex)>);\n    static_assert(!ex.has_value());\n    EXPECT_EQ(ex.error().value, 42);\n  }\n}\n\nTEST(Expected, InPlaceConstructor) {\n  constexpr expected<Strong<int>, int> ex(std::in_place, 42);\n  static_assert(ex.has_value());\n  EXPECT_EQ(ex.value().value, 42);\n}\n\nTEST(Expected, InPlaceListConstructor) {\n  expected<std::vector<int>, int> ex(std::in_place, {1, 2, 3});\n  ASSERT_TRUE(ex.has_value());\n  EXPECT_EQ(ex.value(), std::vector({1, 2, 3}));\n}\n\nTEST(Expected, UnexpectConstructor) {\n  constexpr expected<int, Strong<int>> ex(unexpect, 42);\n  static_assert(!ex.has_value());\n  EXPECT_EQ(ex.error().value, 42);\n}\n\nTEST(Expected, UnexpectListConstructor) {\n  expected<int, std::vector<int>> ex(unexpect, {1, 2, 3});\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error(), std::vector({1, 2, 3}));\n}\n\nTEST(Expected, AssignValue) {\n  expected<int, int> ex = unexpected(0);\n  EXPECT_FALSE(ex.has_value());\n\n  ex = 42;\n  ASSERT_TRUE(ex.has_value());\n  EXPECT_EQ(ex.value(), 42);\n\n  ex = 123;\n  ASSERT_TRUE(ex.has_value());\n  EXPECT_EQ(ex.value(), 123);\n}\n\nTEST(Expected, CopyAssignOk) {\n  expected<int, int> ex = unexpected(0);\n  EXPECT_FALSE(ex.has_value());\n\n  ex = ok(42);\n  ASSERT_TRUE(ex.has_value());\n  EXPECT_EQ(ex.value(), 42);\n\n  ex = ok(123);\n  ASSERT_TRUE(ex.has_value());\n  EXPECT_EQ(ex.value(), 123);\n}\n\nTEST(Expected, MoveAssignOk) {\n  expected<StrongMoveOnly<int>, int> ex = unexpected(0);\n  EXPECT_FALSE(ex.has_value());\n\n  ex = ok(StrongMoveOnly(42));\n  ASSERT_TRUE(ex.has_value());\n  EXPECT_EQ(ex.value().value, 42);\n\n  ex = ok(StrongMoveOnly(123));\n  ASSERT_TRUE(ex.has_value());\n  EXPECT_EQ(ex.value().value, 123);\n}\n\nTEST(Expected, CopyAssignUnexpected) {\n  expected<int, int> ex;\n  EXPECT_TRUE(ex.has_value());\n\n  ex = unexpected(42);\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error(), 42);\n\n  ex = unexpected(123);\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error(), 123);\n}\n\nTEST(Expected, MoveAssignUnexpected) {\n  expected<int, StrongMoveOnly<int>> ex;\n  EXPECT_TRUE(ex.has_value());\n\n  ex = unexpected(StrongMoveOnly(42));\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error().value, 42);\n\n  ex = unexpected(StrongMoveOnly(123));\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error().value, 123);\n}\n\nTEST(Expected, Emplace) {\n  expected<StrongMoveOnly<int>, int> ex = unexpected(0);\n  EXPECT_FALSE(ex.has_value());\n\n  ex.emplace(42);\n  ASSERT_TRUE(ex.has_value());\n  EXPECT_EQ(ex.value().value, 42);\n}\n\nTEST(Expected, EmplaceList) {\n  expected<std::vector<int>, int> ex = unexpected(0);\n  EXPECT_FALSE(ex.has_value());\n\n  ex.emplace({1, 2, 3});\n  ASSERT_TRUE(ex.has_value());\n  EXPECT_EQ(ex.value(), std::vector({1, 2, 3}));\n}\n\nTEST(Expected, MemberSwap) {\n  expected<int, int> ex1(42);\n  expected<int, int> ex2 = unexpected(123);\n\n  ex1.swap(ex2);\n  ASSERT_FALSE(ex1.has_value());\n  EXPECT_EQ(ex1.error(), 123);\n\n  ASSERT_TRUE(ex2.has_value());\n  EXPECT_EQ(ex2.value(), 42);\n}\n\nTEST(Expected, FreeSwap) {\n  expected<int, int> ex1(42);\n  expected<int, int> ex2 = unexpected(123);\n\n  swap(ex1, ex2);\n  ASSERT_FALSE(ex1.has_value());\n  EXPECT_EQ(ex1.error(), 123);\n\n  ASSERT_TRUE(ex2.has_value());\n  EXPECT_EQ(ex2.value(), 42);\n}\n\nTEST(Expected, OperatorArrow) {\n  expected<Strong<int>, int> ex(0);\n  EXPECT_EQ(ex->value, 0);\n\n  ex->value = 1;\n  EXPECT_EQ(ex->value, 1);\n\n  constexpr expected<Strong<int>, int> c_ex(0);\n  EXPECT_EQ(c_ex->value, 0);\n  static_assert(std::is_same_v<decltype((c_ex->value)), const int&>);\n}\n\nTEST(Expected, OperatorStar) {\n  expected<int, int> ex;\n  EXPECT_EQ(*ex, 0);\n\n  *ex = 1;\n  EXPECT_EQ(*ex, 1);\n\n  using Ex = expected<int, int>;\n  static_assert(std::is_same_v<decltype(*std::declval<Ex&>()), int&>);\n  static_assert(\n      std::is_same_v<decltype(*std::declval<const Ex&>()), const int&>);\n  static_assert(std::is_same_v<decltype(*std::declval<Ex&&>()), int&&>);\n  static_assert(\n      std::is_same_v<decltype(*std::declval<const Ex&&>()), const int&&>);\n}\n\nTEST(Expected, HasValue) {\n  constexpr expected<int, int> ex;\n  static_assert(ex.has_value());\n\n  constexpr expected<int, int> unex = unexpected(0);\n  static_assert(!unex.has_value());\n}\n\nTEST(Expected, Value) {\n  expected<int, int> ex;\n  EXPECT_EQ(ex.value(), 0);\n\n  ex.value() = 1;\n  EXPECT_EQ(ex.value(), 1);\n\n  using Ex = expected<int, int>;\n  static_assert(std::is_same_v<decltype(std::declval<Ex&>().value()), int&>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const Ex&>().value()), const int&>);\n  static_assert(std::is_same_v<decltype(std::declval<Ex&&>().value()), int&&>);\n  static_assert(std::is_same_v<decltype(std::declval<const Ex&&>().value()),\n                               const int&&>);\n}\n\nTEST(Expected, Error) {\n  expected<int, int> ex = unexpected(0);\n  EXPECT_EQ(ex.error(), 0);\n\n  ex.error() = 1;\n  EXPECT_EQ(ex.error(), 1);\n\n  using Ex = expected<int, int>;\n  static_assert(std::is_same_v<decltype(std::declval<Ex&>().error()), int&>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const Ex&>().error()), const int&>);\n  static_assert(std::is_same_v<decltype(std::declval<Ex&&>().error()), int&&>);\n  static_assert(std::is_same_v<decltype(std::declval<const Ex&&>().error()),\n                               const int&&>);\n}\n\nTEST(Expected, ValueOr) {\n  {\n    expected<int, int> ex;\n    EXPECT_EQ(ex.value_or(123), 0);\n\n    expected<int, int> unex = unexpected(0);\n    EXPECT_EQ(unex.value_or(123), 123);\n  }\n\n  {\n    expected<WeakMoveOnly<int>, int> ex(0);\n    EXPECT_EQ(std::move(ex).value_or(123).value, 0);\n\n    expected<WeakMoveOnly<int>, int> unex = unexpected(0);\n    EXPECT_EQ(std::move(unex).value_or(123).value, 123);\n  }\n}\n\nTEST(Expected, ErrorOr) {\n  {\n    expected<int, int> ex;\n    EXPECT_EQ(ex.error_or(123), 123);\n\n    expected<int, int> unex = unexpected(0);\n    EXPECT_EQ(unex.error_or(123), 0);\n  }\n\n  {\n    expected<int, WeakMoveOnly<int>> ex(0);\n    EXPECT_EQ(std::move(ex).error_or(123).value, 123);\n\n    expected<int, WeakMoveOnly<int>> unex = unexpected(0);\n    EXPECT_EQ(std::move(unex).error_or(123).value, 0);\n  }\n}\n\nTEST(Expected, AndThen) {\n  using ExIn = expected<SaveCvRef, SaveCvRef>;\n  using ExOut = expected<CvRef, SaveCvRef>;\n\n  auto get_ex_cvref = [](auto&& x) -> ExOut {\n    return SaveCvRef(std::forward<decltype(x)>(x)).cvref;\n  };\n\n  ExIn ex;\n  EXPECT_EQ(ex.and_then(get_ex_cvref), CvRef::kRef);\n  EXPECT_EQ(std::as_const(ex).and_then(get_ex_cvref), CvRef::kConstRef);\n  EXPECT_EQ(std::move(ex).and_then(get_ex_cvref), CvRef::kRRef);\n  EXPECT_EQ(std::move(std::as_const(ex)).and_then(get_ex_cvref),\n            CvRef::kConstRRef);\n\n  ExIn unex(unexpect);\n  EXPECT_EQ(unex.and_then(get_ex_cvref).error().cvref, CvRef::kRef);\n  EXPECT_EQ(std::as_const(unex).and_then(get_ex_cvref).error().cvref,\n            CvRef::kConstRef);\n  EXPECT_EQ(std::move(unex).and_then(get_ex_cvref).error().cvref, CvRef::kRRef);\n  EXPECT_EQ(std::move(std::as_const(unex)).and_then(get_ex_cvref).error().cvref,\n            CvRef::kConstRRef);\n\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&>().and_then(get_ex_cvref)),\n                     ExOut>);\n  static_assert(\n      std::is_same_v<\n          decltype(std::declval<const ExIn&>().and_then(get_ex_cvref)), ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&&>().and_then(get_ex_cvref)),\n                     ExOut>);\n  static_assert(std::is_same_v<decltype(std::declval<const ExIn&&>().and_then(\n                                   get_ex_cvref)),\n                               ExOut>);\n}\n\nTEST(Expected, OrElse) {\n  using ExIn = expected<SaveCvRef, SaveCvRef>;\n  using ExOut = expected<SaveCvRef, CvRef>;\n\n  auto get_unex_cvref = [](auto&& x) -> ExOut {\n    return unexpected(SaveCvRef(std::forward<decltype(x)>(x)).cvref);\n  };\n\n  ExIn ex;\n  EXPECT_EQ(ex.or_else(get_unex_cvref).value().cvref, CvRef::kRef);\n  EXPECT_EQ(std::as_const(ex).or_else(get_unex_cvref).value().cvref,\n            CvRef::kConstRef);\n  EXPECT_EQ(std::move(ex).or_else(get_unex_cvref).value().cvref, CvRef::kRRef);\n  EXPECT_EQ(std::move(std::as_const(ex)).or_else(get_unex_cvref).value().cvref,\n            CvRef::kConstRRef);\n\n  ExIn unex(unexpect);\n  EXPECT_EQ(unex.or_else(get_unex_cvref).error(), CvRef::kRef);\n  EXPECT_EQ(std::as_const(unex).or_else(get_unex_cvref).error(),\n            CvRef::kConstRef);\n  EXPECT_EQ(std::move(unex).or_else(get_unex_cvref).error(), CvRef::kRRef);\n  EXPECT_EQ(std::move(std::as_const(unex)).or_else(get_unex_cvref).error(),\n            CvRef::kConstRRef);\n\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&>().or_else(get_unex_cvref)),\n                     ExOut>);\n  static_assert(std::is_same_v<decltype(std::declval<const ExIn&>().or_else(\n                                   get_unex_cvref)),\n                               ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&&>().or_else(get_unex_cvref)),\n                     ExOut>);\n  static_assert(std::is_same_v<decltype(std::declval<const ExIn&&>().or_else(\n                                   get_unex_cvref)),\n                               ExOut>);\n}\n\nTEST(Expected, Transform) {\n  using ExIn = expected<SaveCvRef, SaveCvRef>;\n  using ExOut = expected<CvRef, SaveCvRef>;\n\n  auto get_cvref = [](auto&& x) {\n    return SaveCvRef(std::forward<decltype(x)>(x)).cvref;\n  };\n\n  {\n    ExIn ex;\n    EXPECT_EQ(ex.transform(get_cvref), CvRef::kRef);\n    EXPECT_EQ(std::as_const(ex).transform(get_cvref), CvRef::kConstRef);\n    EXPECT_EQ(std::move(ex).transform(get_cvref), CvRef::kRRef);\n    EXPECT_EQ(std::move(std::as_const(ex)).transform(get_cvref),\n              CvRef::kConstRRef);\n\n    ExIn unex(unexpect);\n    EXPECT_EQ(unex.transform(get_cvref).error().cvref, CvRef::kRef);\n    EXPECT_EQ(std::as_const(unex).transform(get_cvref).error().cvref,\n              CvRef::kConstRef);\n    EXPECT_EQ(std::move(unex).transform(get_cvref).error().cvref, CvRef::kRRef);\n    EXPECT_EQ(std::move(std::as_const(unex)).transform(get_cvref).error().cvref,\n              CvRef::kConstRRef);\n\n    static_assert(\n        std::is_same_v<decltype(std::declval<ExIn&>().transform(get_cvref)),\n                       ExOut>);\n    static_assert(\n        std::is_same_v<\n            decltype(std::declval<const ExIn&>().transform(get_cvref)), ExOut>);\n    static_assert(\n        std::is_same_v<decltype(std::declval<ExIn&&>().transform(get_cvref)),\n                       ExOut>);\n    static_assert(std::is_same_v<\n                  decltype(std::declval<const ExIn&&>().transform(get_cvref)),\n                  ExOut>);\n  }\n\n  // Test void transform.\n  {\n    using ExOutVoid = expected<void, SaveCvRef>;\n    CvRef cvref = CvRef::kNone;\n    auto write_cvref = [&cvref](auto&& x) {\n      cvref = SaveCvRef(std::forward<decltype(x)>(x)).cvref;\n    };\n\n    ExIn ex;\n    EXPECT_TRUE(ex.transform(write_cvref).has_value());\n    EXPECT_EQ(cvref, CvRef::kRef);\n    EXPECT_TRUE(std::as_const(ex).transform(write_cvref).has_value());\n    EXPECT_EQ(cvref, CvRef::kConstRef);\n    EXPECT_TRUE(std::move(ex).transform(write_cvref).has_value());\n    EXPECT_EQ(cvref, CvRef::kRRef);\n    EXPECT_TRUE(\n        std::move(std::as_const(ex)).transform(write_cvref).has_value());\n    EXPECT_EQ(cvref, CvRef::kConstRRef);\n\n    cvref = CvRef::kNone;\n    ExIn unex(unexpect);\n    EXPECT_EQ(unex.transform(write_cvref).error().cvref, CvRef::kRef);\n    EXPECT_EQ(cvref, CvRef::kNone);\n    EXPECT_EQ(std::as_const(unex).transform(write_cvref).error().cvref,\n              CvRef::kConstRef);\n    EXPECT_EQ(cvref, CvRef::kNone);\n    EXPECT_EQ(std::move(unex).transform(write_cvref).error().cvref,\n              CvRef::kRRef);\n    EXPECT_EQ(cvref, CvRef::kNone);\n    EXPECT_EQ(\n        std::move(std::as_const(unex)).transform(write_cvref).error().cvref,\n        CvRef::kConstRRef);\n    EXPECT_EQ(cvref, CvRef::kNone);\n\n    static_assert(\n        std::is_same_v<decltype(std::declval<ExIn&>().transform(write_cvref)),\n                       ExOutVoid>);\n    static_assert(std::is_same_v<decltype(std::declval<const ExIn&>().transform(\n                                     write_cvref)),\n                                 ExOutVoid>);\n    static_assert(\n        std::is_same_v<decltype(std::declval<ExIn&&>().transform(write_cvref)),\n                       ExOutVoid>);\n    static_assert(std::is_same_v<\n                  decltype(std::declval<const ExIn&&>().transform(write_cvref)),\n                  ExOutVoid>);\n  }\n}\n\nTEST(Expected, TransformError) {\n  using ExIn = expected<SaveCvRef, SaveCvRef>;\n  using ExOut = expected<SaveCvRef, CvRef>;\n\n  auto get_cvref = [](auto&& x) {\n    return SaveCvRef(std::forward<decltype(x)>(x)).cvref;\n  };\n\n  ExIn ex;\n  EXPECT_EQ(ex.transform_error(get_cvref).value().cvref, CvRef::kRef);\n  EXPECT_EQ(std::as_const(ex).transform_error(get_cvref).value().cvref,\n            CvRef::kConstRef);\n  EXPECT_EQ(std::move(ex).transform_error(get_cvref).value().cvref,\n            CvRef::kRRef);\n  EXPECT_EQ(\n      std::move(std::as_const(ex)).transform_error(get_cvref).value().cvref,\n      CvRef::kConstRRef);\n\n  ExIn unex(unexpect);\n  EXPECT_EQ(unex.transform_error(get_cvref).error(), CvRef::kRef);\n  EXPECT_EQ(std::as_const(unex).transform_error(get_cvref).error(),\n            CvRef::kConstRef);\n  EXPECT_EQ(std::move(unex).transform_error(get_cvref).error(), CvRef::kRRef);\n  EXPECT_EQ(std::move(std::as_const(unex)).transform_error(get_cvref).error(),\n            CvRef::kConstRRef);\n\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&>().transform_error(get_cvref)),\n                     ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const ExIn&>().transform_error(\n                         get_cvref)),\n                     ExOut>);\n  static_assert(\n      std::is_same_v<\n          decltype(std::declval<ExIn&&>().transform_error(get_cvref)), ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const ExIn&&>().transform_error(\n                         get_cvref)),\n                     ExOut>);\n}\n\nTEST(Expected, EqualityOperators) {\n  using ExInt = expected<int, int>;\n  using ExLong = expected<long, long>;\n\n  EXPECT_EQ(ExInt(42), ExLong(42));\n  EXPECT_EQ(ExLong(42), ExInt(42));\n  EXPECT_EQ(ExInt(42), 42);\n  EXPECT_EQ(42, ExInt(42));\n  EXPECT_EQ(ExInt(42), ok(42));\n  EXPECT_EQ(ok(42), ExInt(42));\n  EXPECT_EQ(ExInt(unexpect, 42), unexpected(42));\n  EXPECT_EQ(unexpected(42), ExInt(unexpect, 42));\n\n  EXPECT_NE(ExInt(42), ExLong(123));\n  EXPECT_NE(ExLong(123), ExInt(42));\n  EXPECT_NE(ExInt(42), 123);\n  EXPECT_NE(123, ExInt(42));\n  EXPECT_NE(ExInt(42), ok(123));\n  EXPECT_NE(ok(123), ExInt(42));\n  EXPECT_NE(ExInt(unexpect, 123), unexpected(42));\n  EXPECT_NE(unexpected(42), ExInt(unexpect, 123));\n  EXPECT_NE(ExInt(123), unexpected(123));\n  EXPECT_NE(unexpected(123), ExInt(123));\n}\n\nTEST(ExpectedTest, DeathTestsTimeSensitiveTest) {\n  using ExpectedInt = expected<int, int>;\n  using ExpectedDouble = expected<double, double>;\n\n  ExpectedInt moved_from;\n  ExpectedInt ex = std::move(moved_from);\n\n  // Accessing moved from objects crashes.\n  // NOLINTBEGIN(bugprone-use-after-move)\n  EXPECT_DEATH_IF_SUPPORTED((void)ExpectedInt{moved_from}, \"\");\n  EXPECT_DEATH_IF_SUPPORTED((void)ExpectedInt{std::move(moved_from)}, \"\");\n  EXPECT_DEATH_IF_SUPPORTED((void)ExpectedDouble{moved_from}, \"\");\n  EXPECT_DEATH_IF_SUPPORTED((void)ExpectedDouble{std::move(moved_from)}, \"\");\n  EXPECT_DEATH_IF_SUPPORTED(ex = moved_from, \"\");\n  EXPECT_DEATH_IF_SUPPORTED(ex = std::move(moved_from), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(ex.swap(moved_from), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.swap(ex), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.operator->(), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(*moved_from, \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.has_value(), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.value(), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.error(), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.value_or(0), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(std::ignore = (ex == moved_from), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(std::ignore = (moved_from == ex), \"\");\n  // NOLINTEND(bugprone-use-after-move)\n\n  // Accessing inactive union-members crashes.\n  EXPECT_DEATH_IF_SUPPORTED(ExpectedInt{}.error(), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(ExpectedInt{unexpect}.value(), \"\");\n}\n\nTEST(ExpectedVoid, Triviality) {\n  using TrivialExpected = expected<void, int>;\n  static_assert(std::is_trivially_destructible_v<TrivialExpected>);\n\n  using NonTrivialExpected = expected<void, std::string>;\n  static_assert(!std::is_trivially_destructible_v<NonTrivialExpected>);\n}\n\nTEST(ExpectedVoid, DefaultConstructor) {\n  constexpr expected<void, int> ex;\n  static_assert(ex.has_value());\n  static_assert(std::is_default_constructible_v<expected<void, int>>);\n}\n\nTEST(ExpectedVoid, InPlaceConstructor) {\n  constexpr expected<void, int> ex(std::in_place);\n  static_assert(ex.has_value());\n}\n\nTEST(ExpectedVoid, CopyConstructor) {\n  constexpr expected<void, int> ex1 = unexpected(42);\n  constexpr expected<void, int> ex2 = ex1;\n  static_assert(!ex2.has_value());\n  EXPECT_EQ(ex2.error(), 42);\n}\n\nTEST(ExpectedVoid, MoveConstructor) {\n  expected<void, StrongMoveOnly<int>> ex1 = unexpected(StrongMoveOnly(42));\n  expected<void, StrongMoveOnly<int>> ex2 = std::move(ex1);\n  ASSERT_FALSE(ex2.has_value());\n  EXPECT_EQ(ex2.error().value, 42);\n}\n\nTEST(ExpectedVoid, ExplicitConvertingCopyConstructor) {\n  constexpr expected<void, int> ex1 = unexpected(42);\n  expected<const void, Strong<int>> ex2(ex1);\n  static_assert(!std::is_convertible_v<decltype(ex1), decltype(ex2)>);\n  ASSERT_FALSE(ex2.has_value());\n  EXPECT_EQ(ex2.error().value, 42);\n}\n\nTEST(ExpectedVoid, ImplicitConvertingCopyConstructor) {\n  constexpr expected<void, int> ex1 = unexpected(42);\n  expected<const void, Weak<int>> ex2 = ex1;\n  ASSERT_FALSE(ex2.has_value());\n  EXPECT_EQ(ex2.error().value, 42);\n}\n\nTEST(ExpectedVoid, ExplicitConvertingMoveConstructor) {\n  expected<void, int> ex1 = unexpected(42);\n  expected<const void, StrongMoveOnly<int>> ex2(std::move(ex1));\n  static_assert(\n      !std::is_convertible_v<decltype(std::move(ex1)), decltype(ex2)>);\n  ASSERT_FALSE(ex2.has_value());\n  EXPECT_EQ(ex2.error().value, 42);\n}\n\nTEST(ExpectedVoid, ImplicitConvertingMoveConstructor) {\n  expected<void, int> ex1 = unexpected(42);\n  expected<const void, WeakMoveOnly<int>> ex2 = std::move(ex1);\n  ASSERT_FALSE(ex2.has_value());\n  EXPECT_EQ(ex2.error().value, 42);\n}\n\nTEST(ExpectedVoid, OkConstructor) {\n  constexpr expected<void, int> ex = ok();\n  static_assert(ex.has_value());\n}\n\nTEST(ExpectedVoid, ExplicitErrorConstructor) {\n  {\n    constexpr expected<void, Strong<int>> ex(unexpected(42));\n    static_assert(!std::is_convertible_v<unexpected<int>, decltype(ex)>);\n    static_assert(!ex.has_value());\n    EXPECT_EQ(ex.error().value, 42);\n  }\n\n  {\n    constexpr expected<void, StrongMoveOnly<int>> ex(unexpected(42));\n    static_assert(!std::is_constructible_v<decltype(ex), unexpected<int>&>);\n    static_assert(!std::is_convertible_v<unexpected<int>, decltype(ex)>);\n    static_assert(!ex.has_value());\n    EXPECT_EQ(ex.error().value, 42);\n  }\n}\n\nTEST(ExpectedVoid, ImplicitErrorConstructor) {\n  {\n    constexpr expected<void, Weak<int>> ex = unexpected(42);\n    static_assert(!ex.has_value());\n    EXPECT_EQ(ex.error().value, 42);\n  }\n\n  {\n    constexpr expected<void, WeakMoveOnly<int>> ex = unexpected(42);\n    static_assert(!std::is_convertible_v<unexpected<int>&, decltype(ex)>);\n    static_assert(!ex.has_value());\n    EXPECT_EQ(ex.error().value, 42);\n  }\n}\n\nTEST(ExpectedVoid, UnexpectConstructor) {\n  constexpr expected<void, Strong<int>> ex(unexpect, 42);\n  static_assert(!ex.has_value());\n  EXPECT_EQ(ex.error().value, 42);\n}\n\nTEST(ExpectedVoid, UnexpectListConstructor) {\n  expected<void, std::vector<int>> ex(unexpect, {1, 2, 3});\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error(), std::vector({1, 2, 3}));\n}\n\nTEST(ExpectedVoid, CopyAssignUnexpected) {\n  expected<void, int> ex;\n  EXPECT_TRUE(ex.has_value());\n\n  ex = unexpected(42);\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error(), 42);\n\n  ex = unexpected(123);\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error(), 123);\n}\n\nTEST(ExpectedVoid, MoveAssignUnexpected) {\n  expected<void, StrongMoveOnly<int>> ex;\n  EXPECT_TRUE(ex.has_value());\n\n  ex = unexpected(StrongMoveOnly(42));\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error().value, 42);\n\n  ex = unexpected(StrongMoveOnly(123));\n  ASSERT_FALSE(ex.has_value());\n  EXPECT_EQ(ex.error().value, 123);\n}\n\nTEST(ExpectedVoid, Emplace) {\n  expected<void, int> ex = unexpected(0);\n  EXPECT_FALSE(ex.has_value());\n\n  ex.emplace();\n  ASSERT_TRUE(ex.has_value());\n}\n\nTEST(ExpectedVoid, MemberSwap) {\n  expected<void, int> ex1;\n  expected<void, int> ex2 = unexpected(123);\n\n  ex1.swap(ex2);\n  ASSERT_FALSE(ex1.has_value());\n  EXPECT_EQ(ex1.error(), 123);\n\n  ASSERT_TRUE(ex2.has_value());\n}\n\nTEST(ExpectedVoid, FreeSwap) {\n  expected<void, int> ex1;\n  expected<void, int> ex2 = unexpected(123);\n\n  swap(ex1, ex2);\n  ASSERT_FALSE(ex1.has_value());\n  EXPECT_EQ(ex1.error(), 123);\n\n  ASSERT_TRUE(ex2.has_value());\n}\n\nTEST(ExpectedVoid, OperatorStar) {\n  expected<void, int> ex;\n  *ex;\n  static_assert(std::is_void_v<decltype(*ex)>);\n}\n\nTEST(ExpectedVoid, HasValue) {\n  constexpr expected<void, int> ex;\n  static_assert(ex.has_value());\n\n  constexpr expected<void, int> unex = unexpected(0);\n  static_assert(!unex.has_value());\n}\n\nTEST(ExpectedVoid, Value) {\n  expected<void, int> ex;\n  ex.value();\n  static_assert(std::is_void_v<decltype(ex.value())>);\n}\n\nTEST(ExpectedVoid, Error) {\n  expected<void, int> ex = unexpected(0);\n  EXPECT_EQ(ex.error(), 0);\n\n  ex.error() = 1;\n  EXPECT_EQ(ex.error(), 1);\n\n  using Ex = expected<void, int>;\n  static_assert(std::is_same_v<decltype(std::declval<Ex&>().error()), int&>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const Ex&>().error()), const int&>);\n  static_assert(std::is_same_v<decltype(std::declval<Ex&&>().error()), int&&>);\n  static_assert(std::is_same_v<decltype(std::declval<const Ex&&>().error()),\n                               const int&&>);\n}\n\nTEST(ExpectedVoid, ErrorOr) {\n  {\n    expected<void, int> ex;\n    EXPECT_EQ(ex.error_or(123), 123);\n\n    expected<void, int> unex = unexpected(0);\n    EXPECT_EQ(unex.error_or(123), 0);\n  }\n\n  {\n    expected<void, WeakMoveOnly<int>> ex;\n    EXPECT_EQ(std::move(ex).error_or(123).value, 123);\n\n    expected<void, WeakMoveOnly<int>> unex = unexpected(0);\n    EXPECT_EQ(std::move(unex).error_or(123).value, 0);\n  }\n}\n\nTEST(ExpectedVoid, AndThen) {\n  using ExIn = expected<void, SaveCvRef>;\n  using ExOut = expected<bool, SaveCvRef>;\n\n  auto get_true = []() -> ExOut { return ok(true); };\n\n  ExIn ex;\n  EXPECT_TRUE(ex.and_then(get_true).value());\n  EXPECT_TRUE(std::as_const(ex).and_then(get_true).value());\n  EXPECT_TRUE(std::move(ex).and_then(get_true).value());\n  EXPECT_TRUE(std::move(std::as_const(ex)).and_then(get_true).value());\n\n  ExIn unex = unexpected(SaveCvRef());\n  EXPECT_EQ(unex.and_then(get_true).error().cvref, CvRef::kRef);\n  EXPECT_EQ(std::as_const(unex).and_then(get_true).error().cvref,\n            CvRef::kConstRef);\n  EXPECT_EQ(std::move(unex).and_then(get_true).error().cvref, CvRef::kRRef);\n  EXPECT_EQ(std::move(std::as_const(unex)).and_then(get_true).error().cvref,\n            CvRef::kConstRRef);\n\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&>().and_then(get_true)),\n                     ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const ExIn&>().and_then(get_true)),\n                     ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&&>().and_then(get_true)),\n                     ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const ExIn&&>().and_then(get_true)),\n                     ExOut>);\n}\n\nTEST(ExpectedVoid, OrElse) {\n  using ExIn = expected<void, SaveCvRef>;\n  using ExOut = expected<void, CvRef>;\n\n  auto get_unex_cvref = [](auto&& x) -> ExOut {\n    return unexpected(SaveCvRef(std::forward<decltype(x)>(x)).cvref);\n  };\n\n  ExIn ex;\n  EXPECT_TRUE(ex.or_else(get_unex_cvref).has_value());\n  EXPECT_TRUE(std::as_const(ex).or_else(get_unex_cvref).has_value());\n  EXPECT_TRUE(std::move(ex).or_else(get_unex_cvref).has_value());\n  EXPECT_TRUE(std::move(std::as_const(ex)).or_else(get_unex_cvref).has_value());\n\n  ExIn unex(unexpect);\n  EXPECT_EQ(unex.or_else(get_unex_cvref).error(), CvRef::kRef);\n  EXPECT_EQ(std::as_const(unex).or_else(get_unex_cvref).error(),\n            CvRef::kConstRef);\n  EXPECT_EQ(std::move(unex).or_else(get_unex_cvref).error(), CvRef::kRRef);\n  EXPECT_EQ(std::move(std::as_const(unex)).or_else(get_unex_cvref).error(),\n            CvRef::kConstRRef);\n\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&>().or_else(get_unex_cvref)),\n                     ExOut>);\n  static_assert(std::is_same_v<decltype(std::declval<const ExIn&>().or_else(\n                                   get_unex_cvref)),\n                               ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&&>().or_else(get_unex_cvref)),\n                     ExOut>);\n  static_assert(std::is_same_v<decltype(std::declval<const ExIn&&>().or_else(\n                                   get_unex_cvref)),\n                               ExOut>);\n}\n\nTEST(ExpectedVoid, Transform) {\n  using ExIn = expected<void, SaveCvRef>;\n  using ExOut = expected<bool, SaveCvRef>;\n  auto get_true = [] { return true; };\n\n  {\n    ExIn ex;\n    EXPECT_TRUE(ex.transform(get_true).value());\n    EXPECT_TRUE(std::as_const(ex).transform(get_true).value());\n    EXPECT_TRUE(std::move(ex).transform(get_true).value());\n    EXPECT_TRUE(std::move(std::as_const(ex)).transform(get_true).value());\n\n    ExIn unex(unexpect);\n    EXPECT_EQ(unex.transform(get_true).error().cvref, CvRef::kRef);\n    EXPECT_EQ(std::as_const(unex).transform(get_true).error().cvref,\n              CvRef::kConstRef);\n    EXPECT_EQ(std::move(unex).transform(get_true).error().cvref, CvRef::kRRef);\n    EXPECT_EQ(std::move(std::as_const(unex)).transform(get_true).error().cvref,\n              CvRef::kConstRRef);\n\n    static_assert(\n        std::is_same_v<decltype(std::declval<ExIn&>().transform(get_true)),\n                       ExOut>);\n    static_assert(\n        std::is_same_v<\n            decltype(std::declval<const ExIn&>().transform(get_true)), ExOut>);\n    static_assert(\n        std::is_same_v<decltype(std::declval<ExIn&&>().transform(get_true)),\n                       ExOut>);\n    static_assert(\n        std::is_same_v<\n            decltype(std::declval<const ExIn&&>().transform(get_true)), ExOut>);\n  }\n\n  // Test void transform.\n  {\n    auto do_nothing = [] {};\n\n    ExIn ex;\n    EXPECT_TRUE(ex.transform(do_nothing).has_value());\n    EXPECT_TRUE(std::as_const(ex).transform(do_nothing).has_value());\n    EXPECT_TRUE(std::move(ex).transform(do_nothing).has_value());\n    EXPECT_TRUE(std::move(std::as_const(ex)).transform(do_nothing).has_value());\n\n    ExIn unex(unexpect);\n    EXPECT_EQ(unex.transform(do_nothing).error().cvref, CvRef::kRef);\n    EXPECT_EQ(std::as_const(unex).transform(do_nothing).error().cvref,\n              CvRef::kConstRef);\n    EXPECT_EQ(std::move(unex).transform(do_nothing).error().cvref,\n              CvRef::kRRef);\n    EXPECT_EQ(\n        std::move(std::as_const(unex)).transform(do_nothing).error().cvref,\n        CvRef::kConstRRef);\n\n    static_assert(\n        std::is_same_v<decltype(std::declval<ExIn&>().transform(do_nothing)),\n                       ExIn>);\n    static_assert(\n        std::is_same_v<\n            decltype(std::declval<const ExIn&>().transform(do_nothing)), ExIn>);\n    static_assert(\n        std::is_same_v<decltype(std::declval<ExIn&&>().transform(do_nothing)),\n                       ExIn>);\n    static_assert(std::is_same_v<\n                  decltype(std::declval<const ExIn&&>().transform(do_nothing)),\n                  ExIn>);\n  }\n}\n\nTEST(ExpectedVoid, TransformError) {\n  using ExIn = expected<void, SaveCvRef>;\n  using ExOut = expected<void, CvRef>;\n\n  auto get_cvref = [](auto&& x) {\n    return SaveCvRef(std::forward<decltype(x)>(x)).cvref;\n  };\n\n  ExIn ex;\n  EXPECT_TRUE(ex.transform_error(get_cvref).has_value());\n  EXPECT_TRUE(std::as_const(ex).transform_error(get_cvref).has_value());\n  EXPECT_TRUE(std::move(ex).transform_error(get_cvref).has_value());\n  EXPECT_TRUE(\n      std::move(std::as_const(ex)).transform_error(get_cvref).has_value());\n\n  ExIn unex(unexpect);\n  EXPECT_EQ(unex.transform_error(get_cvref).error(), CvRef::kRef);\n  EXPECT_EQ(std::as_const(unex).transform_error(get_cvref).error(),\n            CvRef::kConstRef);\n  EXPECT_EQ(std::move(unex).transform_error(get_cvref).error(), CvRef::kRRef);\n  EXPECT_EQ(std::move(std::as_const(unex)).transform_error(get_cvref).error(),\n            CvRef::kConstRRef);\n\n  static_assert(\n      std::is_same_v<decltype(std::declval<ExIn&>().transform_error(get_cvref)),\n                     ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const ExIn&>().transform_error(\n                         get_cvref)),\n                     ExOut>);\n  static_assert(\n      std::is_same_v<\n          decltype(std::declval<ExIn&&>().transform_error(get_cvref)), ExOut>);\n  static_assert(\n      std::is_same_v<decltype(std::declval<const ExIn&&>().transform_error(\n                         get_cvref)),\n                     ExOut>);\n}\n\nTEST(ExpectedVoid, EqualityOperators) {\n  using Ex = expected<void, int>;\n  using ConstEx = expected<const void, const int>;\n\n  EXPECT_EQ(Ex(), ConstEx());\n  EXPECT_EQ(ConstEx(), Ex());\n  EXPECT_EQ(Ex(unexpect, 42), unexpected(42));\n  EXPECT_EQ(unexpected(42), Ex(unexpect, 42));\n\n  EXPECT_NE(Ex(unexpect, 123), unexpected(42));\n  EXPECT_NE(unexpected(42), Ex(unexpect, 123));\n  EXPECT_NE(Ex(), unexpected(0));\n  EXPECT_NE(unexpected(0), Ex());\n}\n\nTEST(ExpectedVoidTest, DeathTests) {\n  using ExpectedInt = expected<void, int>;\n  using ExpectedDouble = expected<void, double>;\n\n  ExpectedInt moved_from;\n  ExpectedInt ex = std::move(moved_from);\n\n  // Accessing moved from objects crashes.\n  // NOLINTBEGIN(bugprone-use-after-move)\n  EXPECT_DEATH_IF_SUPPORTED((void)ExpectedInt{moved_from}, \"\");\n  EXPECT_DEATH_IF_SUPPORTED((void)ExpectedInt{std::move(moved_from)}, \"\");\n  EXPECT_DEATH_IF_SUPPORTED((void)ExpectedDouble{moved_from}, \"\");\n  EXPECT_DEATH_IF_SUPPORTED((void)ExpectedDouble{std::move(moved_from)}, \"\");\n  EXPECT_DEATH_IF_SUPPORTED(ex = moved_from, \"\");\n  EXPECT_DEATH_IF_SUPPORTED(ex = std::move(moved_from), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(ex.swap(moved_from), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.swap(ex), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(*moved_from, \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.has_value(), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.value(), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(moved_from.error(), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(std::ignore = (ex == moved_from), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(std::ignore = (moved_from == ex), \"\");\n  // NOLINTEND(bugprone-use-after-move)\n\n  // Accessing inactive union-members crashes.\n  EXPECT_DEATH_IF_SUPPORTED(ExpectedInt{}.error(), \"\");\n  EXPECT_DEATH_IF_SUPPORTED(ExpectedInt{unexpect}.value(), \"\");\n}\n\n}  // namespace\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/file_utils.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/file_utils.h\"\n\n#include <cstdio>\n#include <memory>\n\nnamespace lynx {\nnamespace base {\n\nusing FileUniquePtr = std::unique_ptr<FILE, decltype(&fclose)>;\n\nbool FileUtils::ReadFileBinary(const std::string &path, size_t max_size,\n                               std::string &file_content) {\n  auto file = FileUniquePtr(fopen(path.c_str(), \"rb\"), &fclose);\n  if (!file) {\n    return false;\n  }\n\n  // obtain file size\n  fseek(file.get(), 0, SEEK_END);\n  long size = ftell(file.get());\n  rewind(file.get());\n  if (size < 0 || static_cast<size_t>(size) > max_size) {\n    return false;\n  }\n\n  // read file\n  file_content.resize(size);\n  fread(file_content.data(), 1, static_cast<size_t>(size), file.get());\n\n  // check result\n  bool ret = !ferror(file.get());\n  return ret;\n}\n\nbool FileUtils::WriteFileBinary(const std::string &path,\n                                const unsigned char *file_content,\n                                size_t size) {\n  auto file = FileUniquePtr(fopen(path.c_str(), \"wb\"), &fclose);\n  if (!file) {\n    return false;\n  }\n  size_t bytes_wrote =\n      fwrite(file_content, sizeof(unsigned char), size, file.get());\n  return bytes_wrote == size;\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/file_utils_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/file_utils.h\"\n\n#include <cstdlib>\n\n#include \"base/include/path_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#if OS_WIN\nstatic const char kChars[] =\n    \"0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\nstatic char *mkdtemp(char *tmpl) {\n  int len = (int)strlen(tmpl);\n  if (len < 6 || strcmp(&tmpl[len - 6], \"XXXXXX\")) {\n    return NULL;\n  }\n  char *XXXXXX = &tmpl[len - 6];\n  unsigned long num = rand();\n  for (int i = 0; i < 1000; i++) {\n    unsigned long v = num;\n    for (int j = 0; j < 6; j++) {\n      XXXXXX[j] = kChars[v % 63];\n      v /= 63;\n    }\n    int fd = _mkdir(tmpl);\n    if (fd == 0) {\n      return tmpl;\n    }\n    if (errno != EEXIST) {\n      break;\n    }\n    num += 137;\n  }\n  return NULL;\n}\n#endif\n\nnamespace lynx {\nnamespace base {\nnamespace testing {\n\nclass FileUtilsTest : public ::testing::Test {\n public:\n  static void SetUpTestSuite() {\n    // make temp testing dir\n    char *dir = mkdtemp(path.data());\n    ASSERT_NE(dir, nullptr);\n  }\n\n  static void TearDownTestSuite() {\n    // cleanup testing dir\n#if OS_WIN\n    int ret = _rmdir(path.data());\n#else\n    int ret = remove(path.data());\n#endif\n    ASSERT_EQ(ret, 0);\n  }\n\n  static std::string path;\n};\n\nstd::string FileUtilsTest::path = \"FileUtilsTestXXXXXX\";\n\nTEST_F(FileUtilsTest, ReadWriteFile) {\n  std::string filename = \"ReadWriteFile\";\n  std::string file_path = PathUtils::JoinPaths({path, filename});\n  std::string write_data = \"FirstFirstFirstWriteData\";\n  bool ret = FileUtils::WriteFileBinary(\n      file_path, (unsigned char *)write_data.data(), write_data.size());\n  ASSERT_TRUE(ret);\n  std::string read_data;\n  ret = FileUtils::ReadFileBinary(file_path, 10'000'000, read_data);\n  ASSERT_TRUE(ret);\n  ASSERT_EQ(read_data, write_data);\n}\n\nTEST_F(FileUtilsTest, ReadWriteEmptyFile) {\n  std::string filename = \"ReadWriteEmptyFile\";\n  std::string file_path = PathUtils::JoinPaths({path, filename});\n  std::string write_data = \"\";\n  bool ret = FileUtils::WriteFileBinary(\n      file_path, (unsigned char *)write_data.data(), write_data.size());\n  ASSERT_TRUE(ret);\n  std::string read_data;\n  ret = FileUtils::ReadFileBinary(file_path, 10'000'000, read_data);\n  ASSERT_TRUE(ret);\n  ASSERT_EQ(read_data, write_data);\n  remove(file_path.data());\n}\n\nTEST_F(FileUtilsTest, WriteFileToExisted) {\n  std::string filename = \"ReadWriteFile\";\n  std::string file_path = PathUtils::JoinPaths({path, filename});\n  std::string write_data = \"AnotherWriteData\";\n  bool ret = FileUtils::WriteFileBinary(\n      file_path, (unsigned char *)write_data.data(), write_data.size());\n  ASSERT_TRUE(ret);\n  std::string read_data;\n  ret = FileUtils::ReadFileBinary(file_path, 10'000'000, read_data);\n  ASSERT_TRUE(ret);\n  ASSERT_EQ(read_data, write_data);\n  remove(file_path.data());\n}\n\nTEST_F(FileUtilsTest, WriteToNonExistedDir) {\n  std::string filename = \"ReadWriteFile\";\n  std::string file_path = PathUtils::JoinPaths({path, \"not_existed\", filename});\n  std::string write_data = \"WriteData\";\n  bool ret = FileUtils::WriteFileBinary(\n      file_path, (unsigned char *)write_data.data(), write_data.size());\n  ASSERT_FALSE(ret);\n}\n\nTEST_F(FileUtilsTest, ReadNonExistedFile) {\n  std::string filename = \"ReadWriteFile\";\n  std::string file_path = PathUtils::JoinPaths({path, \"not_existed\", filename});\n  std::string read_data;\n  bool ret = FileUtils::ReadFileBinary(file_path, 10'000'000, read_data);\n  ASSERT_FALSE(ret);\n}\n\nTEST_F(FileUtilsTest, ReadFileTooLarge) {\n  std::string filename = \"ReadWriteFile\";\n  std::string file_path = PathUtils::JoinPaths({path, filename});\n  std::string write_data = \"FirstFirstFirstWriteData\";\n  bool ret = FileUtils::WriteFileBinary(\n      file_path, (unsigned char *)write_data.data(), write_data.size());\n  ASSERT_TRUE(ret);\n  std::string read_data;\n  ret = FileUtils::ReadFileBinary(file_path, 10, read_data);\n  ASSERT_FALSE(ret);\n  remove(file_path.data());\n}\n\n}  // namespace testing\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/flex_optional_unittest.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/flex_optional.h\"\n\n#include <initializer_list>\n#include <optional>\n#include <utility>\n#include <vector>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace test {\n\nstruct s32 {\n  uint64_t a;\n  uint64_t b;\n  uint64_t c;\n  uint64_t d;\n};\n\nstruct s32_mem_save {\n  uint64_t a;\n  uint64_t b;\n  uint64_t c;\n  uint64_t d;\n\n  // A flag telling `base::flex_optional<>` to save memory.\n  using AlwaysUseFlexOptionalMemSave = bool;\n};\n\nstruct s40Convertible {\n  uint64_t a;\n  uint64_t b;\n  uint64_t c;\n  uint64_t d;\n  uint64_t e;\n};\n\nstruct s40 {\n  uint64_t a;\n  uint64_t b;\n  uint64_t c;\n  uint64_t d;\n  uint64_t e;\n  s40(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e)\n      : a(a), b(b), c(c), d(d), e(e) {}\n\n  s40(const s40Convertible& s) : a(s.a) {}\n  template <class U>\n  s40(std::initializer_list<U> list) {\n    a = list.begin()[0];\n    b = list.begin()[1];\n    c = list.begin()[2];\n    d = list.begin()[3];\n    e = list.begin()[4];\n  }\n};\n\nstruct s40MoveOnly {\n  uint64_t a;\n  uint64_t b;\n  uint64_t c;\n  uint64_t d;\n  uint64_t e;\n  s40MoveOnly(const s40MoveOnly& other) = delete;\n  s40MoveOnly& operator=(const s40MoveOnly& other) = delete;\n};\n\nTEST(FlexOptional, ChooseFromType) {\n  auto small = base::flex_optional<s32>();\n  ASSERT_EQ(sizeof(small), sizeof(std::optional<s32>));\n  auto small_mem_save = base::flex_optional<s32_mem_save>();\n  ASSERT_EQ(sizeof(small_mem_save), sizeof(void*));\n  auto big = base::flex_optional<s40>();\n  ASSERT_EQ(sizeof(big), sizeof(void*));\n\n  ASSERT_TRUE(!big);\n  ASSERT_FALSE(big.has_value());\n  big = s40{\n      1, 2, 3, 4, 5,\n  };\n\n  ASSERT_FALSE(!big);\n  ASSERT_TRUE(big.has_value());\n  ASSERT_EQ(big->a, 1u);\n  ASSERT_EQ(big.value().a, 1u);\n}\n\nTEST(FlexOptional, CopyConstructorFromEmpty) {\n  base::flex_optional<s40> original;\n  base::flex_optional<s40> copy(original);\n  EXPECT_FALSE(bool(copy));\n}\n\nTEST(FlexOptional, CopyConstructorFromValue) {\n  const base::flex_optional<s40> original(std::in_place, 1, 2, 3, 4, 5);\n  const base::flex_optional<s40> copy(original);\n  EXPECT_TRUE(bool(copy));\n  EXPECT_EQ(copy->a, 1u);\n}\nTEST(FlexOptional, MoveValueConstructor) {\n  base::flex_optional<s40> s40MoveOnly(std::in_place, 1, 2, 3, 4, 5);\n  base::flex_optional<s40> moved(std::move(s40MoveOnly));\n  EXPECT_TRUE(bool(moved));\n  EXPECT_EQ(moved->a, 1u);\n}\n\nTEST(FlexOptional, NulloptConstructor) {\n  base::flex_optional<s40> obj(std::nullopt);\n  EXPECT_FALSE(bool(obj));\n}\n\nTEST(FlexOptional, InPlaceConstructor) {\n  const base::flex_optional<s40> obj(std::in_place, 1, 2, 3, 4, 5);\n  EXPECT_TRUE(bool(obj));\n  EXPECT_EQ(obj->a, 1u);\n}\n\nTEST(FlexOptional, InPlaceConstructorWithInitializerList) {\n  const base::flex_optional<s40> obj(std::in_place, {1, 2, 3, 4, 5});\n  EXPECT_TRUE(bool(obj));\n  EXPECT_EQ(obj->a, 1u);\n\n  const base::flex_optional<std::vector<int>> vec(std::in_place,\n                                                  {1, 2, 3, 4, 5});\n  EXPECT_TRUE(bool(vec));\n  EXPECT_EQ(vec->operator[](0), 1);\n}\n\nTEST(FlexOptional, CopyConstructorWithValue) {\n  const base::flex_optional<s40> original(std::in_place, 1, 2, 3, 4, 5);\n  base::flex_optional<s40> copy(original);\n\n  EXPECT_TRUE(original.has_value());\n  EXPECT_TRUE(copy.has_value());\n  EXPECT_EQ(original.value().a, copy.value().a);\n  EXPECT_NE(&original.value(), &copy.value());\n}\n\nTEST(FlexOptional, CopyConstructorWithoutValue) {\n  base::flex_optional<s40> original;\n  base::flex_optional<s40> copy(original);\n\n  EXPECT_FALSE(original.has_value());\n  EXPECT_FALSE(copy.has_value());\n}\n\nTEST(FlexOptional, MoveConstructorWithValue) {\n  // const ?\n  base::flex_optional<s40> original(std::in_place, 1, 2, 3, 4, 5);\n  s40 original_value = original.value();\n  base::flex_optional<s40> moved(std::move(original));\n\n  EXPECT_FALSE(original.has_value());\n  EXPECT_TRUE(moved.has_value());\n  EXPECT_EQ(moved.value().a, original_value.a);\n}\n\nTEST(FlexOptional, MoveConstructorWithoutValue) {\n  base::flex_optional<s40> original;\n  base::flex_optional<s40> moved(std::move(original));\n\n  EXPECT_FALSE(original.has_value());\n  EXPECT_FALSE(moved.has_value());\n}\n\nTEST(FlexOptional, OperatorAssign) {\n  base::flex_optional<s40> obj = s40{1};\n  base::flex_optional<s40> from = s40{2};\n\n  // const copy\n  obj = from;\n  EXPECT_EQ(obj->a, 2u);\n\n  // move copy\n  obj = base::flex_optional<s40>({1});\n  EXPECT_EQ(obj->a, 1u);\n\n  // move assign\n  obj = std::move(from);\n  EXPECT_EQ(obj->a, 2u);\n\n  // nullopt\n  obj = std::nullopt;\n  EXPECT_FALSE(bool(obj));\n}\n\nTEST(FlexOptional, OperatorAssignConstructable) {\n  base::flex_optional<s40> obj = s40{1};\n  base::flex_optional<s40Convertible> from = s40Convertible{2};\n\n  // const copy\n  obj = from;\n  EXPECT_EQ(obj->a, 2u);\n\n  // move copy\n  obj = base::flex_optional<s40Convertible>({1});\n  EXPECT_EQ(obj->a, 1u);\n\n  // move assign\n  obj = std::move(from);\n  EXPECT_EQ(obj->a, 2u);\n\n  // nullopt\n  obj = std::nullopt;\n  EXPECT_FALSE(bool(obj));\n}\n\nTEST(FlexOptional, OperatorAssignByValue) {\n  base::flex_optional<s40> obj = s40{1};\n  s40 from = s40{2};\n\n  // const copy\n  obj = from;\n  EXPECT_EQ(obj->a, 2u);\n\n  // move copy\n  obj = s40{1};\n  EXPECT_EQ(obj->a, 1u);\n\n  // move assign\n  obj = std::move(from);\n  EXPECT_EQ(obj->a, 2u);\n}\n\nTEST(FlexOptional, Emplace) {\n  base::flex_optional<s40> obj;\n  obj.emplace(1, 2, 3, 4, 5);\n  EXPECT_TRUE(bool(obj));\n  EXPECT_EQ(obj->a, 1u);\n\n  base::flex_optional<std::vector<int>> vec;\n  vec.emplace(5, 0);\n  EXPECT_TRUE(bool(vec));\n  EXPECT_EQ(vec->operator[](0), 0);\n}\n\nTEST(FlexOptional, EmplaceInitializerList) {\n  base::flex_optional<s40> obj;\n  obj.emplace({1, 2, 3, 4, 5});\n  EXPECT_TRUE(bool(obj));\n  EXPECT_EQ(obj->a, 1u);\n\n  base::flex_optional<std::vector<int>> vec;\n  vec.emplace({1, 2, 3, 4, 5});\n  EXPECT_TRUE(bool(vec));\n  EXPECT_EQ(vec->operator[](0), 1);\n}\n\nTEST(FlexOptional, Swap) {\n  base::flex_optional<s40> obj1 = s40{1, 2, 3, 4, 5};\n  base::flex_optional<s40> obj2 = s40{1, 2, 3, 4, 5};\n\n  void* ptr1 = &*obj1;\n  void* ptr2 = &*obj2;\n  obj1.swap(obj2);\n  EXPECT_EQ(&*obj1, ptr2);\n  EXPECT_EQ(&*obj2, ptr1);\n}\n\nTEST(FlexOptional, HasValue) {\n  base::flex_optional<s40> obj;\n  EXPECT_FALSE(bool(obj));\n  EXPECT_FALSE(obj.has_value());\n  obj.emplace({1, 2, 3, 4, 5});\n  EXPECT_TRUE(bool(obj));\n  EXPECT_TRUE(obj.has_value());\n}\n\nTEST(FlexOptional, OperatorStar) {\n  base::flex_optional<s40> obj;\n  obj.emplace({1, 2, 3, 4, 5});\n  EXPECT_EQ(obj->a, 1u);\n  EXPECT_EQ((*obj).a, 1u);\n\n  const base::flex_optional<s40> c_obj = s40{1, 2, 3, 4, 5};\n  EXPECT_EQ(c_obj->a, 1u);\n  EXPECT_EQ((*c_obj).a, 1u);\n}\n\nTEST(FlexOptional, Value) {\n  base::flex_optional<s40> obj = s40{1};\n  EXPECT_EQ(obj.value().a, 1u);\n\n  const base::flex_optional<s40> c_obj = s40{1};\n  EXPECT_EQ(c_obj.value().a, 1u);\n\n  EXPECT_EQ(std::move(obj).value().a, 1u);\n  EXPECT_EQ(std::move(c_obj).value().a, 1u);\n}\n\nTEST(FlexOptional, ValueOr) {\n  base::flex_optional<s40> obj;\n  EXPECT_EQ(obj.value_or(s40{1}).a, 1u);\n  base::flex_optional<s40> obj2 = s40{2};\n  EXPECT_EQ(obj2.value_or(s40{1}).a, 2u);\n}\n\nTEST(FlexOptional, Reset) {\n  base::flex_optional<s40> obj = s40{1};\n  obj.reset();\n  EXPECT_FALSE(bool(obj));\n}\n\nTEST(FlexOptional, TypeInfer) {\n  std::optional obj = s40{1};\n  EXPECT_EQ(obj->a, 1u);\n  EXPECT_EQ(obj.value().a, 1u);\n}\n// TODO: operator test\n\n}  // namespace test\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/LICENSE",
    "content": "Copyright 2013 The Flutter Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "base/src/fml/concurrent_message_loop.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/concurrent_message_loop.h\"\n\n#include <thread>\n\n#include \"base/include/fml/fml_trace_event_def.h\"\n#include \"base/include/fml/platform/thread_config_setter.h\"\n#include \"base/src/base_trace/base_trace_event_def.h\"\n#include \"base/src/base_trace/trace_event.h\"\n#include \"build/build_config.h\"\n\n#if defined(OS_IOS)\nextern \"C\" void* objc_autoreleasePoolPush(void);\nextern \"C\" void objc_autoreleasePoolPop(void*);\n#endif\n\nnamespace lynx {\nnamespace fml {\n\nnamespace {\n// A thread-local pointer to the message loop instance this worker thread\n// belongs to. This allows for checking if the current thread is a worker\n// of a *specific* ConcurrentMessageLoop instance.\nthread_local ConcurrentMessageLoop* g_current_message_loop_worker = nullptr;\n}  // namespace\n\nstatic constexpr uint32_t kWorkerSleepMultipleMicroseconds = 340;\nstatic constexpr uint32_t kWorkerMaxIdleMicroseconds = 34000;\n\nstd::shared_ptr<ConcurrentMessageLoop> ConcurrentMessageLoop::Create(\n    size_t worker_count) {\n  return std::shared_ptr<ConcurrentMessageLoop>{new ConcurrentMessageLoop(\n      \"io.worker.\", Thread::ThreadPriority::NORMAL, worker_count)};\n}\n\nstd::shared_ptr<ConcurrentMessageLoop> ConcurrentMessageLoop::Create(\n    const Thread::ThreadConfigSetter& setter, size_t worker_count) {\n  return std::shared_ptr<ConcurrentMessageLoop>{new ConcurrentMessageLoop(\n      \"io.worker.\", setter, Thread::ThreadPriority::NORMAL, worker_count)};\n}\n\nConcurrentMessageLoop::ConcurrentMessageLoop(const std::string& name_prefix,\n                                             Thread::ThreadPriority priority,\n                                             size_t worker_count)\n    : ConcurrentMessageLoop(name_prefix,\n#if defined(OS_IOS) || defined(OS_ANDROID)\n                            PlatformThreadPriority::Setter,\n#else\n                            Thread::SetCurrentThreadName,\n#endif\n                            priority, worker_count) {\n}\n\nConcurrentMessageLoop::ConcurrentMessageLoop(\n    const std::string& name_prefix, const Thread::ThreadConfigSetter& setter,\n    Thread::ThreadPriority priority, size_t worker_count) {\n  uint32_t max_worker_count =\n      std::max<uint32_t>(static_cast<uint32_t>(worker_count), 1u);\n  worker_count_.store(max_worker_count);\n  workers_.reserve(max_worker_count);\n  for (uint32_t i = 0; i < max_worker_count; ++i) {\n    base::closure setup_thread = [name_prefix, i, priority, setter, this]() {\n      const auto config = fml::Thread::ThreadConfig(\n          std::string{name_prefix + std::to_string(i + 1)}, priority);\n      setter(config);\n      WorkerMain(i);\n    };\n    workers_.emplace_back(std::move(setup_thread));\n  }\n}\n\nConcurrentMessageLoop::~ConcurrentMessageLoop() {\n  Terminate();\n  for (auto& worker : workers_) {\n    worker.join();\n  }\n}\n\nbool ConcurrentMessageLoop::RunsTasksOnCurrentThreadWorker() const {\n  return g_current_message_loop_worker == this;\n}\n\nsize_t ConcurrentMessageLoop::GetWorkerCount() const { return workers_.size(); }\n\nvoid ConcurrentMessageLoop::PostTask(base::closure task) {\n  if (!task) {\n    return;\n  }\n\n  // Don't just drop tasks on the floor in case of shutdown.\n  if (shutdown_) {\n    // TODO(zhengsenyao): Uncomment LOG code when LOG available\n    //    DLOGW(\n    //        \"Tried to post a task to shutdown concurrent message \"\n    //        \"loop. The task will be executed on the callers thread.\");\n    task();\n    return;\n  }\n\n  std::unique_lock lock(tasks_mutex_);\n  tasks_.push(std::move(task));\n  lock.unlock();\n\n  task_count_.fetch_add(1);\n\n  if (worker_count_.load() <= 0) {\n    notify_condition_.notify_all();\n  }\n\n  return;\n}\n\nvoid ConcurrentMessageLoop::WorkerMain(uint32_t index) {\n  g_current_message_loop_worker = this;\n\n  const uint32_t sleep_microseconds =\n      kWorkerSleepMultipleMicroseconds * (index + 1);\n  const uint32_t max_sleep_count =\n      kWorkerMaxIdleMicroseconds / sleep_microseconds;\n  uint32_t sleep_count_down = 0;\n  while (true) {\n    uint32_t task_count = task_count_.load();\n    while (task_count > 0 &&\n           !task_count_.compare_exchange_weak(task_count, task_count - 1)) {\n    }\n\n    if (task_count > 0) {\n      base::closure task;\n\n      std::unique_lock lock(tasks_mutex_);\n      if (tasks_.size() != 0) {\n        task = std::move(tasks_.front());\n        tasks_.pop();\n      }\n      lock.unlock();\n\n      if (task) {\n#if defined(OS_IOS)\n        void* pool = objc_autoreleasePoolPush();\n#endif\n        task();\n        task = nullptr;\n#if defined(OS_IOS)\n        objc_autoreleasePoolPop(pool);\n#endif\n      }\n\n      std::uint32_t worker_count = worker_count_.load();\n      if (worker_count < workers_.size() && worker_count < (task_count - 1)) {\n        notify_condition_.notify_all();\n      }\n      continue;\n    }\n\n    if (shutdown_) {\n      break;\n    }\n\n    if (sleep_count_down == 0) {\n      --worker_count_;\n      std::unique_lock lock(notify_mutex_);\n      notify_condition_.wait(\n          lock, [&]() { return task_count_.load() > 0 || shutdown_; });\n      lock.unlock();\n      ++worker_count_;\n      sleep_count_down = max_sleep_count;\n      BASE_TRACE_EVENT(LYNX_BASE_TRACE_CATEGORY, CONCURRENT_WORKER_AWOKE);\n    } else {\n      --sleep_count_down;\n      std::this_thread::sleep_for(\n          std::chrono::microseconds(sleep_microseconds));\n    }\n  }\n\n  g_current_message_loop_worker = nullptr;\n}\n\nstd::shared_ptr<ConcurrentTaskRunner> ConcurrentMessageLoop::GetTaskRunner() {\n  return std::make_shared<ConcurrentTaskRunner>(weak_from_this());\n}\n\nvoid ConcurrentMessageLoop::Terminate() {\n  shutdown_ = true;\n  notify_condition_.notify_all();\n}\n\nConcurrentTaskRunner::ConcurrentTaskRunner(\n    std::weak_ptr<ConcurrentMessageLoop> weak_loop)\n    : weak_loop_(std::move(weak_loop)) {}\n\nConcurrentTaskRunner::~ConcurrentTaskRunner() = default;\n\nvoid ConcurrentTaskRunner::PostTask(lynx::base::closure task) {\n  if (!task) {\n    return;\n  }\n\n  if (auto loop = weak_loop_.lock()) {\n    loop->PostTask(std::move(task));\n    return;\n  }\n\n  task();\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/cpu_affinity.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/cpu_affinity.h\"\n\n#include <cstdint>\n#include <fstream>\n#include <optional>\n#include <string>\n\n#include \"build/build_config.h\"\n\n#ifdef OS_ANDROID\n#include \"base/include/fml/platform/android/cpu_affinity.h\"\n#endif  // OS_ANDROID\n\nnamespace lynx {\nnamespace fml {\n\nstd::optional<size_t> EfficiencyCoreCount() {\n#ifdef OS_ANDROID\n  return AndroidEfficiencyCoreCount();\n#else\n  return std::nullopt;\n#endif\n}\n\nbool RequestAffinity(CpuAffinity affinity) {\n#ifdef OS_ANDROID\n  return AndroidRequestAffinity(affinity);\n#else\n  return true;\n#endif\n}\n\nCPUSpeedTracker::CPUSpeedTracker(std::vector<CpuIndexAndSpeed> data)\n    : cpu_speeds_(std::move(data)) {\n  std::optional<int64_t> max_speed = std::nullopt;\n  std::optional<int64_t> min_speed = std::nullopt;\n  for (const auto& data : cpu_speeds_) {\n    if (!max_speed.has_value() || data.speed > *max_speed) {\n      max_speed = data.speed;\n    }\n    if (!min_speed.has_value() || data.speed < *min_speed) {\n      min_speed = data.speed;\n    }\n  }\n  if (!max_speed.has_value() || !min_speed.has_value() ||\n      *min_speed == *max_speed) {\n    return;\n  }\n  const int64_t max_speed_value = *max_speed;\n  const int64_t min_speed_value = *min_speed;\n\n  for (const auto& data : cpu_speeds_) {\n    if (data.speed == max_speed_value) {\n      performance_.push_back(data.index);\n    } else {\n      not_performance_.push_back(data.index);\n    }\n    if (data.speed == min_speed_value) {\n      efficiency_.push_back(data.index);\n    } else {\n      not_efficiency_.push_back(data.index);\n    }\n  }\n\n  valid_ = true;\n}\n\nbool CPUSpeedTracker::IsValid() const { return valid_; }\n\nconst std::vector<size_t>& CPUSpeedTracker::GetIndices(\n    CpuAffinity affinity) const {\n  switch (affinity) {\n    case CpuAffinity::kPerformance:\n      return performance_;\n    case CpuAffinity::kEfficiency:\n      return efficiency_;\n    case CpuAffinity::kNotPerformance:\n      return not_performance_;\n    case CpuAffinity::kNotEfficiency:\n      return not_efficiency_;\n  }\n}\n\n// Get the size of the cpuinfo file by reading it until the end. This is\n// required because files under /proc do not always return a valid size\n// when using fseek(0, SEEK_END) + ftell(). Nor can they be mmap()-ed.\nstd::optional<int64_t> ReadIntFromFile(const std::string& path) {\n  std::ifstream file;\n  file.open(path.c_str());\n\n  // Dont use stoi because if this data isnt a parseable number then it\n  // will abort, as we compile with exceptions disabled.\n  int64_t speed = 0;\n  file >> speed;\n  if (speed > 0) {\n    return speed;\n  }\n  return std::nullopt;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/cpu_affinity_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/cpu_affinity.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace testing {\n\nTEST(CpuAffinity, NonAndroidPlatformDefaults) {\n  ASSERT_FALSE(fml::EfficiencyCoreCount().has_value());\n  ASSERT_TRUE(fml::RequestAffinity(fml::CpuAffinity::kEfficiency));\n}\n\nTEST(CpuAffinity, NormalSlowMedFastCores) {\n  auto speeds = {CpuIndexAndSpeed{.index = 0, .speed = 1},\n                 CpuIndexAndSpeed{.index = 1, .speed = 2},\n                 CpuIndexAndSpeed{.index = 2, .speed = 3}};\n  auto tracker = CPUSpeedTracker(speeds);\n\n  ASSERT_TRUE(tracker.IsValid());\n  ASSERT_EQ(tracker.GetIndices(CpuAffinity::kEfficiency)[0], 0u);\n  ASSERT_EQ(tracker.GetIndices(CpuAffinity::kPerformance)[0], 2u);\n  ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotPerformance).size(), 2u);\n  ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotPerformance)[0], 0u);\n  ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotPerformance)[1], 1u);\n  ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotEfficiency).size(), 2u);\n  ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotEfficiency)[0], 1u);\n  ASSERT_EQ(tracker.GetIndices(CpuAffinity::kNotEfficiency)[1], 2u);\n}\n\nTEST(CpuAffinity, NoCpuData) {\n  auto tracker = CPUSpeedTracker({});\n\n  ASSERT_FALSE(tracker.IsValid());\n}\n\nTEST(CpuAffinity, AllSameSpeed) {\n  auto speeds = {CpuIndexAndSpeed{.index = 0, .speed = 1},\n                 CpuIndexAndSpeed{.index = 1, .speed = 1},\n                 CpuIndexAndSpeed{.index = 2, .speed = 1}};\n  auto tracker = CPUSpeedTracker(speeds);\n\n  ASSERT_FALSE(tracker.IsValid());\n}\n\nTEST(CpuAffinity, SingleCore) {\n  auto speeds = {CpuIndexAndSpeed{.index = 0, .speed = 1}};\n  auto tracker = CPUSpeedTracker(speeds);\n\n  ASSERT_FALSE(tracker.IsValid());\n}\n\nTEST(CpuAffinity, MissingFileParsing) {\n  auto result = ReadIntFromFile(\"/does_not_exist\");\n  ASSERT_FALSE(result.has_value());\n}\n\n}  // namespace testing\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/hash_combine_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"base/include/fml/hash_combine.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace fml {\nnamespace testing {\n\nTEST(HashCombineTest, CanHash) {\n  ASSERT_EQ(HashCombine(), HashCombine());\n  ASSERT_EQ(HashCombine(\"Hello\"), HashCombine(\"Hello\"));\n  ASSERT_NE(HashCombine(\"Hello\"), HashCombine(\"World\"));\n  ASSERT_EQ(HashCombine(\"Hello\", \"World\"), HashCombine(\"Hello\", \"World\"));\n  ASSERT_NE(HashCombine(\"World\", \"Hello\"), HashCombine(\"Hello\", \"World\"));\n  ASSERT_EQ(HashCombine(12u), HashCombine(12u));\n  ASSERT_NE(HashCombine(12u), HashCombine(12.0f));\n  ASSERT_EQ(HashCombine('a'), HashCombine('a'));\n}\n\n}  // namespace testing\n}  // namespace fml\n"
  },
  {
    "path": "base/src/fml/memory/ref_counted_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// This file tests both ref_counted.h and ref_ptr.h (which the former includes).\n// TODO(vtl): Possibly we could separate these tests out better, since a lot of\n// it is actually testing |RefPtr|.\n\n#include \"base/include/fml/memory/ref_counted.h\"\n\n#include \"base/include/fml/macros.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#if defined(__clang__)\n#define ALLOW_PESSIMIZING_MOVE(code_line)                                   \\\n  _Pragma(\"clang diagnostic push\")                                          \\\n      _Pragma(\"clang diagnostic ignored \\\"-Wpessimizing-move\\\"\") code_line; \\\n  _Pragma(\"clang diagnostic pop\")\n#else\n#define ALLOW_PESSIMIZING_MOVE(code_line) code_line;\n#endif\n\n#if defined(__clang__)\n#define ALLOW_SELF_MOVE(code_line)                                   \\\n  _Pragma(\"clang diagnostic push\")                                   \\\n      _Pragma(\"clang diagnostic ignored \\\"-Wself-move\\\"\") code_line; \\\n  _Pragma(\"clang diagnostic pop\")\n#else\n#define ALLOW_SELF_MOVE(code_line) code_line;\n#endif\n\n#if defined(__clang__)\n#define ALLOW_SELF_ASSIGN_OVERLOADED(code_line)                        \\\n  _Pragma(\"clang diagnostic push\")                                     \\\n      _Pragma(\"clang diagnostic ignored \\\"-Wself-assign-overloaded\\\"\") \\\n          code_line;                                                   \\\n  _Pragma(\"clang diagnostic pop\")\n#else\n#define ALLOW_SELF_ASSIGN_OVERLOADED(code_line) code_line;\n#endif\n\nnamespace lynx {\nnamespace fml {\nnamespace {\n\nclass MyClass : public RefCountedThreadSafe<MyClass> {\n protected:\n  MyClass(MyClass** created, bool* was_destroyed)\n      : was_destroyed_(was_destroyed) {\n    if (created) {\n      *created = this;\n    }\n  }\n  virtual ~MyClass() {\n    if (was_destroyed_) {\n      *was_destroyed_ = true;\n    }\n  }\n\n private:\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MyClass);\n  FML_FRIEND_MAKE_REF_COUNTED(MyClass);\n\n  bool* was_destroyed_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(MyClass);\n};\n\nclass MySubclass final : public MyClass {\n private:\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MySubclass);\n  FML_FRIEND_MAKE_REF_COUNTED(MySubclass);\n\n  MySubclass(MySubclass** created, bool* was_destroyed)\n      : MyClass(nullptr, was_destroyed) {\n    if (created) {\n      *created = this;\n    }\n  }\n  ~MySubclass() override {}\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(MySubclass);\n};\n\nTEST(RefCountedTest, Constructors) {\n  bool was_destroyed;\n\n  {\n    // Default.\n    RefPtr<MyClass> r;\n    EXPECT_TRUE(r.get() == nullptr);\n    EXPECT_FALSE(r);\n  }\n\n  {\n    // Nullptr.\n    RefPtr<MyClass> r(nullptr);\n    EXPECT_TRUE(r.get() == nullptr);\n    EXPECT_FALSE(r);\n  }\n\n  {\n    MyClass* created = nullptr;\n    was_destroyed = false;\n    // Adopt, then RVO.\n    RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));\n    EXPECT_TRUE(created);\n    EXPECT_EQ(created, r.get());\n    EXPECT_TRUE(r);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MyClass* created = nullptr;\n    was_destroyed = false;\n    // Adopt, then move.\n    ALLOW_PESSIMIZING_MOVE(RefPtr<MyClass> r(\n        std::move(MakeRefCounted<MyClass>(&created, &was_destroyed))))\n    EXPECT_TRUE(created);\n    EXPECT_EQ(created, r.get());\n    EXPECT_TRUE(r);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MyClass* created = nullptr;\n    was_destroyed = false;\n    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));\n    // Copy.\n    RefPtr<MyClass> r2(r1);\n    EXPECT_TRUE(created);\n    EXPECT_EQ(created, r1.get());\n    EXPECT_EQ(created, r2.get());\n    EXPECT_TRUE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MyClass* created = nullptr;\n    was_destroyed = false;\n    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));\n    // From raw pointer.\n    RefPtr<MyClass> r2(created);\n    EXPECT_TRUE(created);\n    EXPECT_EQ(created, r1.get());\n    EXPECT_EQ(created, r2.get());\n    EXPECT_TRUE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MySubclass* created = nullptr;\n    was_destroyed = false;\n    // Adopt, then \"move\".\n    RefPtr<MyClass> r(MakeRefCounted<MySubclass>(&created, &was_destroyed));\n    EXPECT_TRUE(created);\n    EXPECT_EQ(static_cast<MyClass*>(created), r.get());\n    EXPECT_TRUE(r);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MySubclass* created = nullptr;\n    was_destroyed = false;\n    // Adopt, then \"move\".\n    ALLOW_PESSIMIZING_MOVE(RefPtr<MyClass> r(\n        std::move(MakeRefCounted<MySubclass>(&created, &was_destroyed))))\n    EXPECT_TRUE(created);\n    EXPECT_EQ(static_cast<MyClass*>(created), r.get());\n    EXPECT_TRUE(r);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MySubclass* created = nullptr;\n    was_destroyed = false;\n    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));\n    // \"Copy\".\n    RefPtr<MyClass> r2(r1);\n    EXPECT_TRUE(created);\n    EXPECT_EQ(static_cast<MyClass*>(created), r1.get());\n    EXPECT_EQ(static_cast<MyClass*>(created), r2.get());\n    EXPECT_TRUE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MySubclass* created = nullptr;\n    was_destroyed = false;\n    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));\n    // From raw pointer.\n    RefPtr<MyClass> r2(created);\n    EXPECT_TRUE(created);\n    EXPECT_EQ(static_cast<MyClass*>(created), r1.get());\n    EXPECT_EQ(static_cast<MyClass*>(created), r2.get());\n    EXPECT_TRUE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n}\n\nTEST(RefCountedTest, NullAssignmentToNull) {\n  RefPtr<MyClass> r1;\n  // No-op null assignment using |nullptr|.\n  r1 = nullptr;\n  EXPECT_TRUE(r1.get() == nullptr);\n  EXPECT_FALSE(r1);\n\n  RefPtr<MyClass> r2;\n  // No-op null assignment using copy constructor.\n  r1 = r2;\n  EXPECT_TRUE(r1.get() == nullptr);\n  EXPECT_TRUE(r2.get() == nullptr);\n  EXPECT_FALSE(r1);\n  EXPECT_FALSE(r2);\n\n  // No-op null assignment using move constructor.\n  r1 = std::move(r2);\n  EXPECT_TRUE(r1.get() == nullptr);\n  // The clang linter flags the method called on the moved-from reference, but\n  // this is testing the move implementation, so it is marked NOLINT.\n  EXPECT_TRUE(r2.get() == nullptr);  // NOLINT(clang-analyzer-cplusplus.Move)\n  EXPECT_FALSE(r1);\n  EXPECT_FALSE(r2);\n\n  RefPtr<MySubclass> r3;\n  // No-op null assignment using \"copy\" constructor.\n  r1 = r3;\n  EXPECT_TRUE(r1.get() == nullptr);\n  EXPECT_TRUE(r3.get() == nullptr);\n  EXPECT_FALSE(r1);\n  EXPECT_FALSE(r3);\n\n  // No-op null assignment using \"move\" constructor.\n  r1 = std::move(r3);\n  EXPECT_TRUE(r1.get() == nullptr);\n  EXPECT_TRUE(r3.get() == nullptr);\n  EXPECT_FALSE(r1);\n  EXPECT_FALSE(r3);\n}\n\nTEST(RefCountedTest, NonNullAssignmentToNull) {\n  bool was_destroyed;\n\n  {\n    MyClass* created = nullptr;\n    was_destroyed = false;\n    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));\n    RefPtr<MyClass> r2;\n    // Copy assignment (to null ref pointer).\n    r2 = r1;\n    EXPECT_EQ(created, r1.get());\n    EXPECT_EQ(created, r2.get());\n    EXPECT_TRUE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MyClass* created = nullptr;\n    was_destroyed = false;\n    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created, &was_destroyed));\n    RefPtr<MyClass> r2;\n    // Move assignment (to null ref pointer).\n    r2 = std::move(r1);\n    // The clang linter flags the method called on the moved-from reference, but\n    // this is testing the move implementation, so it is marked NOLINT.\n    EXPECT_TRUE(r1.get() == nullptr);  // NOLINT(clang-analyzer-cplusplus.Move)\n    EXPECT_EQ(created, r2.get());\n    EXPECT_FALSE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MySubclass* created = nullptr;\n    was_destroyed = false;\n    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));\n    RefPtr<MyClass> r2;\n    // \"Copy\" assignment (to null ref pointer).\n    r2 = r1;\n    EXPECT_EQ(created, r1.get());\n    EXPECT_EQ(static_cast<MyClass*>(created), r2.get());\n    EXPECT_TRUE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MySubclass* created = nullptr;\n    was_destroyed = false;\n    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));\n    RefPtr<MyClass> r2;\n    // \"Move\" assignment (to null ref pointer).\n    r2 = std::move(r1);\n    EXPECT_TRUE(r1.get() == nullptr);\n    EXPECT_EQ(static_cast<MyClass*>(created), r2.get());\n    EXPECT_FALSE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n}\n\nTEST(RefCountedTest, NullAssignmentToNonNull) {\n  bool was_destroyed = false;\n  RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed));\n  // Null assignment (to non-null ref pointer) using |nullptr|.\n  r1 = nullptr;\n  EXPECT_TRUE(r1.get() == nullptr);\n  EXPECT_FALSE(r1);\n  EXPECT_TRUE(was_destroyed);\n\n  was_destroyed = false;\n  r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);\n  RefPtr<MyClass> r2;\n  // Null assignment (to non-null ref pointer) using copy constructor.\n  r1 = r2;\n  EXPECT_TRUE(r1.get() == nullptr);\n  EXPECT_TRUE(r2.get() == nullptr);\n  EXPECT_FALSE(r1);\n  EXPECT_FALSE(r2);\n  EXPECT_TRUE(was_destroyed);\n\n  was_destroyed = false;\n  r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);\n  // Null assignment using move constructor.\n  r1 = std::move(r2);\n  EXPECT_TRUE(r1.get() == nullptr);\n  // The clang linter flags the method called on the moved-from reference, but\n  // this is testing the move implementation, so it is marked NOLINT.\n  EXPECT_TRUE(r2.get() == nullptr);  // NOLINT(clang-analyzer-cplusplus.Move)\n  EXPECT_FALSE(r1);\n  EXPECT_FALSE(r2);\n  EXPECT_TRUE(was_destroyed);\n\n  was_destroyed = false;\n  r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);\n  RefPtr<MySubclass> r3;\n  // Null assignment (to non-null ref pointer) using \"copy\" constructor.\n  r1 = r3;\n  EXPECT_TRUE(r1.get() == nullptr);\n  EXPECT_TRUE(r3.get() == nullptr);\n  EXPECT_FALSE(r1);\n  EXPECT_FALSE(r3);\n  EXPECT_TRUE(was_destroyed);\n\n  was_destroyed = false;\n  r1 = MakeRefCounted<MyClass>(nullptr, &was_destroyed);\n  // Null assignment (to non-null ref pointer) using \"move\" constructor.\n  r1 = std::move(r3);\n  EXPECT_TRUE(r1.get() == nullptr);\n  EXPECT_TRUE(r3.get() == nullptr);\n  EXPECT_FALSE(r1);\n  EXPECT_FALSE(r3);\n  EXPECT_TRUE(was_destroyed);\n}\n\nTEST(RefCountedTest, NonNullAssignmentToNonNull) {\n  bool was_destroyed1;\n  bool was_destroyed2;\n\n  {\n    was_destroyed1 = false;\n    was_destroyed2 = false;\n    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed1));\n    RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));\n    // Copy assignment (to non-null ref pointer).\n    r2 = r1;\n    EXPECT_EQ(r1.get(), r2.get());\n    EXPECT_TRUE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed1);\n    EXPECT_TRUE(was_destroyed2);\n  }\n  EXPECT_TRUE(was_destroyed1);\n\n  {\n    was_destroyed1 = false;\n    was_destroyed2 = false;\n    RefPtr<MyClass> r1(MakeRefCounted<MyClass>(nullptr, &was_destroyed1));\n    RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));\n    // Move assignment (to non-null ref pointer).\n    r2 = std::move(r1);\n    // The clang linter flags the method called on the moved-from reference, but\n    // this is testing the move implementation, so it is marked NOLINT.\n    EXPECT_TRUE(r1.get() == nullptr);  // NOLINT(clang-analyzer-cplusplus.Move)\n    EXPECT_FALSE(r2.get() == nullptr);\n    EXPECT_FALSE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed1);\n    EXPECT_TRUE(was_destroyed2);\n  }\n  EXPECT_TRUE(was_destroyed1);\n\n  {\n    was_destroyed1 = false;\n    was_destroyed2 = false;\n    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(nullptr, &was_destroyed1));\n    RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));\n    // \"Copy\" assignment (to non-null ref pointer).\n    r2 = r1;\n    EXPECT_EQ(r1.get(), r2.get());\n    EXPECT_TRUE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed1);\n    EXPECT_TRUE(was_destroyed2);\n  }\n  EXPECT_TRUE(was_destroyed1);\n\n  {\n    was_destroyed1 = false;\n    was_destroyed2 = false;\n    RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(nullptr, &was_destroyed1));\n    RefPtr<MyClass> r2(MakeRefCounted<MyClass>(nullptr, &was_destroyed2));\n    // Move assignment (to non-null ref pointer).\n    r2 = std::move(r1);\n    EXPECT_TRUE(r1.get() == nullptr);\n    EXPECT_FALSE(r2.get() == nullptr);\n    EXPECT_FALSE(r1);\n    EXPECT_TRUE(r2);\n    EXPECT_FALSE(was_destroyed1);\n    EXPECT_TRUE(was_destroyed2);\n  }\n  EXPECT_TRUE(was_destroyed1);\n}\n\nTEST(RefCountedTest, SelfAssignment) {\n  bool was_destroyed;\n\n  {\n    MyClass* created = nullptr;\n    was_destroyed = false;\n    // This line is marked NOLINT because the clang linter does not reason about\n    // the value of the reference count. In particular, the self-assignment\n    // below is handled in the copy constructor by a refcount increment then\n    // decrement. The linter sees only that the decrement might destroy the\n    // object.\n    RefPtr<MyClass> r(MakeRefCounted<MyClass>(  // NOLINT\n        &created, &was_destroyed));\n    // Copy.\n    ALLOW_SELF_ASSIGN_OVERLOADED(r = r);\n    EXPECT_EQ(created, r.get());\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n\n  {\n    MyClass* created = nullptr;\n    was_destroyed = false;\n    RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));\n    // Move.\n    ALLOW_SELF_MOVE(r = std::move(r))\n    EXPECT_EQ(created, r.get());\n    EXPECT_FALSE(was_destroyed);\n  }\n  EXPECT_TRUE(was_destroyed);\n}\n\nTEST(RefCountedTest, Swap) {\n  MyClass* created1 = nullptr;\n  static bool was_destroyed1;\n  was_destroyed1 = false;\n  RefPtr<MyClass> r1(MakeRefCounted<MyClass>(&created1, &was_destroyed1));\n  EXPECT_TRUE(created1);\n  EXPECT_EQ(created1, r1.get());\n\n  MyClass* created2 = nullptr;\n  static bool was_destroyed2;\n  was_destroyed2 = false;\n  RefPtr<MyClass> r2(MakeRefCounted<MyClass>(&created2, &was_destroyed2));\n  EXPECT_TRUE(created2);\n  EXPECT_EQ(created2, r2.get());\n  EXPECT_NE(created1, created2);\n\n  r1.swap(r2);\n  EXPECT_EQ(created2, r1.get());\n  EXPECT_EQ(created1, r2.get());\n}\n\nTEST(RefCountedTest, GetAndDereferenceOperators) {\n  // Note: We check here that .get(), operator*, and operator-> are const, but\n  // return non-const pointers/refs.\n\n  MyClass* created = nullptr;\n  const RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, nullptr));\n  MyClass* ptr = r.get();  // Assign to non-const pointer.\n  EXPECT_EQ(created, ptr);\n  ptr = r.operator->();  // Assign to non-const pointer.\n  EXPECT_EQ(created, ptr);\n  MyClass& ref = *r;  // \"Assign\" to non-const reference.\n  EXPECT_EQ(created, &ref);\n}\n\n// You can manually call |AddRef()| and |Release()| if you want.\nTEST(RefCountedTest, AddRefRelease) {\n  MyClass* created = nullptr;\n  bool was_destroyed = false;\n  {\n    RefPtr<MyClass> r(MakeRefCounted<MyClass>(&created, &was_destroyed));\n    EXPECT_EQ(created, r.get());\n    created->AddRef();\n  }\n  EXPECT_FALSE(was_destroyed);\n  created->Release();\n  EXPECT_TRUE(was_destroyed);\n}\n\nTEST(RefCountedTest, Mix) {\n  MySubclass* created = nullptr;\n  bool was_destroyed = false;\n  RefPtr<MySubclass> r1(MakeRefCounted<MySubclass>(&created, &was_destroyed));\n  ASSERT_FALSE(was_destroyed);\n  EXPECT_TRUE(created->HasOneRef());\n  created->AssertHasOneRef();\n\n  RefPtr<MySubclass> r2 = r1;\n  ASSERT_FALSE(was_destroyed);\n  EXPECT_FALSE(created->HasOneRef());\n\n  r1 = nullptr;\n  ASSERT_FALSE(was_destroyed);\n  created->AssertHasOneRef();\n\n  {\n    RefPtr<MyClass> r3 = r2;\n    EXPECT_FALSE(created->HasOneRef());\n    {\n      RefPtr<MyClass> r4(r3);\n      r2 = nullptr;\n      ASSERT_FALSE(was_destroyed);\n      EXPECT_FALSE(created->HasOneRef());\n    }\n    ASSERT_FALSE(was_destroyed);\n    EXPECT_TRUE(created->HasOneRef());\n    created->AssertHasOneRef();\n\n    r1 = RefPtr<MySubclass>(static_cast<MySubclass*>(r3.get()));\n    ASSERT_FALSE(was_destroyed);\n    EXPECT_FALSE(created->HasOneRef());\n  }\n  ASSERT_FALSE(was_destroyed);\n  EXPECT_TRUE(created->HasOneRef());\n  created->AssertHasOneRef();\n\n  EXPECT_EQ(created, r1.get());\n\n  r1 = nullptr;\n  EXPECT_TRUE(was_destroyed);\n}\n\nclass MyPublicClass : public RefCountedThreadSafe<MyPublicClass> {\n public:\n  // Overloaded constructors work with |MakeRefCounted()|.\n  MyPublicClass() : has_num_(false), num_(0) {}\n  explicit MyPublicClass(int num) : has_num_(true), num_(num) {}\n\n  ~MyPublicClass() {}\n\n  bool has_num() const { return has_num_; }\n  int num() const { return num_; }\n\n private:\n  bool has_num_;\n  int num_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(MyPublicClass);\n};\n\n// You can also just keep constructors and destructors public. Make sure that\n// works (mostly that it compiles).\nTEST(RefCountedTest, PublicCtorAndDtor) {\n  RefPtr<MyPublicClass> r1 = MakeRefCounted<MyPublicClass>();\n  ASSERT_TRUE(r1);\n  EXPECT_FALSE(r1->has_num());\n\n  RefPtr<MyPublicClass> r2 = MakeRefCounted<MyPublicClass>(123);\n  ASSERT_TRUE(r2);\n  EXPECT_TRUE(r2->has_num());\n  EXPECT_EQ(123, r2->num());\n  EXPECT_NE(r1.get(), r2.get());\n\n  r1 = r2;\n  EXPECT_TRUE(r1->has_num());\n  EXPECT_EQ(123, r1->num());\n  EXPECT_EQ(r1.get(), r2.get());\n\n  r2 = nullptr;\n  EXPECT_FALSE(r2);\n  EXPECT_TRUE(r1->has_num());\n  EXPECT_EQ(123, r1->num());\n\n  r1 = nullptr;\n  EXPECT_FALSE(r1);\n}\n\nclass MyRefCountedClass : public RefCountedThreadSafeStorage {\n public:\n  uint32_t GetRefCount() const { return ref_count_.load(); }\n\n  uint32_t GetPadding() const { return __padding__; }\n\n protected:\n  void ReleaseSelf() const override { delete this; }\n};\n\nTEST(RefCountedTest, MemberInitialValues) {\n  RefPtr<MyRefCountedClass> inst = MakeRefCounted<MyRefCountedClass>();\n  EXPECT_EQ(inst->GetRefCount(), 1u);\n  EXPECT_EQ(inst->GetPadding(), 0u);\n}\n\n// The danger with having a public constructor or destructor is that certain\n// things will compile. You should get some protection by assertions in Debug\n// builds.\n#ifndef NDEBUG\n// FIXME(heshan):our log is different from flutter, in fact dcheck successfully\n// but not received EXPECT_DEATH_IF_SUPPORTED\nTEST(RefCountedTest, DISABLED_DebugChecks) {\n  {\n    MyPublicClass* p = new MyPublicClass();\n    EXPECT_DEATH_IF_SUPPORTED(  // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)\n        delete p, \"!adoption_required_\");\n  }\n\n  {\n    MyPublicClass* p = new MyPublicClass();\n    EXPECT_DEATH_IF_SUPPORTED(  // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)\n        RefPtr<MyPublicClass> r(p), \"!adoption_required_\");\n  }\n\n  {\n    RefPtr<MyPublicClass> r(MakeRefCounted<MyPublicClass>());\n    EXPECT_DEATH_IF_SUPPORTED(delete r.get(), \"destruction_started_\");\n  }\n}\n#endif\n\n// TODO(vtl): Add (threaded) stress tests.\n\n}  // namespace\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/memory/task_runner_checker.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/memory/task_runner_checker.h\"\n\nnamespace lynx {\nnamespace fml {\n\nTaskRunnerChecker::TaskRunnerChecker()\n    : initialized_queue_id_(InitTaskQueueId()),\n      subsumed_queue_ids_(\n          MessageLoopTaskQueues::GetInstance()->GetSubsumedTaskQueueId(\n              initialized_queue_id_)){};\n\nTaskRunnerChecker::~TaskRunnerChecker() = default;\n\nbool TaskRunnerChecker::RunsOnCreationTaskRunner() const {\n  MessageLoop* current_loop = fml::MessageLoop::IsInitializedForCurrentThread();\n  if (current_loop == nullptr) {\n    return false;\n  }\n  const auto current_queue_id = current_loop->GetTaskRunner()->GetTaskQueueId();\n  if (RunsOnTheSameThread(current_queue_id, initialized_queue_id_)) {\n    return true;\n  }\n  for (auto& subsumed : subsumed_queue_ids_) {\n    if (RunsOnTheSameThread(current_queue_id, subsumed)) {\n      return true;\n    }\n  }\n  return false;\n};\n\nbool TaskRunnerChecker::RunsOnTheSameThread(TaskQueueId queue_a,\n                                            TaskQueueId queue_b) {\n  if (queue_a == queue_b) {\n    return true;\n  }\n\n  auto queues = MessageLoopTaskQueues::GetInstance();\n  if (queues->Owns(queue_a, queue_b)) {\n    return true;\n  }\n  if (queues->Owns(queue_b, queue_a)) {\n    return true;\n  }\n  return false;\n};\n\nTaskQueueId TaskRunnerChecker::InitTaskQueueId() {\n  return MessageLoop::EnsureInitializedForCurrentThread()\n      .GetTaskRunner()\n      ->GetTaskQueueId();\n};\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/memory/task_runner_checker_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"base/include/fml/memory/task_runner_checker.h\"\n\n#include <thread>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/raster_thread_merger.h\"\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace testing {\n\nTEST(TaskRunnerCheckerTests, RunsOnCurrentTaskRunner) {\n  TaskRunnerChecker checker;\n  EXPECT_EQ(checker.RunsOnCreationTaskRunner(), true);\n}\n\nTEST(TaskRunnerCheckerTests, FailsTheCheckIfOnDifferentTaskRunner) {\n  TaskRunnerChecker checker;\n  EXPECT_EQ(checker.RunsOnCreationTaskRunner(), true);\n  fml::MessageLoop* loop = nullptr;\n  fml::AutoResetWaitableEvent latch;\n  std::thread anotherThread([&]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop = &fml::MessageLoop::GetCurrent();\n    loop->GetTaskRunner()->PostTask([&]() {\n      EXPECT_EQ(checker.RunsOnCreationTaskRunner(), false);\n      latch.Signal();\n    });\n    loop->Run();\n  });\n  latch.Wait();\n  loop->Terminate();\n  anotherThread.join();\n  EXPECT_EQ(checker.RunsOnCreationTaskRunner(), true);\n}\n\nTEST(TaskRunnerCheckerTests, SameTaskRunnerRunsOnTheSameThread) {\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  fml::MessageLoop& loop1 = fml::MessageLoop::GetCurrent();\n  fml::MessageLoop& loop2 = fml::MessageLoop::GetCurrent();\n  TaskQueueId a = loop1.GetTaskRunner()->GetTaskQueueId();\n  TaskQueueId b = loop2.GetTaskRunner()->GetTaskQueueId();\n  EXPECT_EQ(TaskRunnerChecker::RunsOnTheSameThread(a, b), true);\n}\n\nTEST(TaskRunnerCheckerTests, RunsOnDifferentThreadsReturnsFalse) {\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  fml::MessageLoop& loop1 = fml::MessageLoop::GetCurrent();\n  TaskQueueId a = loop1.GetTaskRunner()->GetTaskQueueId();\n  fml::AutoResetWaitableEvent latch;\n  std::thread anotherThread([&]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    fml::MessageLoop& loop2 = fml::MessageLoop::GetCurrent();\n    TaskQueueId b = loop2.GetTaskRunner()->GetTaskQueueId();\n    EXPECT_EQ(TaskRunnerChecker::RunsOnTheSameThread(a, b), false);\n    latch.Signal();\n  });\n  latch.Wait();\n  anotherThread.join();\n}\n\nTEST(TaskRunnerCheckerTests, MergedTaskRunnersRunsOnTheSameThread) {\n  fml::MessageLoop* loop1 = nullptr;\n  fml::AutoResetWaitableEvent latch1;\n  fml::AutoResetWaitableEvent term1;\n  std::thread thread1([&loop1, &latch1, &term1]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop1 = &fml::MessageLoop::GetCurrent();\n    latch1.Signal();\n    term1.Wait();\n  });\n\n  fml::MessageLoop* loop2 = nullptr;\n  fml::AutoResetWaitableEvent latch2;\n  fml::AutoResetWaitableEvent term2;\n  std::thread thread2([&loop2, &latch2, &term2]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop2 = &fml::MessageLoop::GetCurrent();\n    latch2.Signal();\n    term2.Wait();\n  });\n\n  latch1.Wait();\n  latch2.Wait();\n  fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId();\n  fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId();\n  const auto raster_thread_merger_ =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n  const size_t kNumFramesMerged = 5;\n\n  raster_thread_merger_->MergeWithLease(kNumFramesMerged);\n\n  // merged, running on the same thread\n  EXPECT_EQ(TaskRunnerChecker::RunsOnTheSameThread(qid1, qid2), true);\n\n  for (size_t i = 0; i < kNumFramesMerged; i++) {\n    ASSERT_TRUE(raster_thread_merger_->IsMerged());\n    raster_thread_merger_->DecrementLease();\n  }\n\n  ASSERT_FALSE(raster_thread_merger_->IsMerged());\n\n  // un-merged, not running on the same thread\n  EXPECT_EQ(TaskRunnerChecker::RunsOnTheSameThread(qid1, qid2), false);\n\n  term1.Signal();\n  term2.Signal();\n  thread1.join();\n  thread2.join();\n}\n\nTEST(TaskRunnerCheckerTests,\n     PassesRunsOnCreationTaskRunnerIfOnDifferentTaskRunner) {\n  fml::MessageLoop* loop1 = nullptr;\n  fml::AutoResetWaitableEvent latch1;\n  std::thread thread1([&]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop1 = &fml::MessageLoop::GetCurrent();\n    latch1.Signal();\n    loop1->Run();\n  });\n\n  fml::MessageLoop* loop2 = nullptr;\n  fml::AutoResetWaitableEvent latch2;\n  std::thread thread2([&]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop2 = &fml::MessageLoop::GetCurrent();\n    latch2.Signal();\n    loop2->Run();\n  });\n\n  latch1.Wait();\n  latch2.Wait();\n\n  fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId();\n  fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId();\n  fml::MessageLoopTaskQueues::GetInstance()->Merge(qid1, qid2);\n\n  std::unique_ptr<TaskRunnerChecker> checker;\n\n  fml::AutoResetWaitableEvent latch3;\n  loop2->GetTaskRunner()->PostTask([&]() {\n    checker = std::make_unique<TaskRunnerChecker>();\n    EXPECT_EQ(checker->RunsOnCreationTaskRunner(), true);\n    latch3.Signal();\n  });\n  latch3.Wait();\n\n  fml::MessageLoopTaskQueues::GetInstance()->Unmerge(qid1, qid2);\n\n  fml::AutoResetWaitableEvent latch4;\n  loop2->GetTaskRunner()->PostTask([&]() {\n    EXPECT_EQ(checker->RunsOnCreationTaskRunner(), true);\n    latch4.Signal();\n  });\n  latch4.Wait();\n\n  loop1->Terminate();\n  loop2->Terminate();\n  thread1.join();\n  thread2.join();\n}\n\n}  // namespace testing\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/memory/weak_ptr_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n\n#include <thread>\n#include <utility>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/raster_thread_merger.h\"\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"base/include/fml/task_queue_id.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace fml {\nnamespace {\n\nstruct Integer : public EnableWeakFromThis<Integer> {\n  Integer(int data) : data(data) {}\n  bool operator==(const Integer& other) const { return data == other.data; }\n\n  int data;\n};\n\nTEST(WeakPtrTest, Basic) {\n  Integer data = 0;\n  auto ptr = data.WeakFromThis();\n  EXPECT_EQ(&data, ptr.get());\n}\n\nTEST(WeakPtrTest, CopyConstruction) {\n  Integer data = 0;\n  auto ptr = data.WeakFromThis();\n  auto ptr2(ptr);\n  EXPECT_EQ(&data, ptr.get());\n  EXPECT_EQ(&data, ptr2.get());\n}\n\nTEST(WeakPtrTest, MoveConstruction) {\n  Integer data = 0;\n  auto ptr = data.WeakFromThis();\n  auto ptr2(std::move(ptr));\n  // The clang linter flags the method called on the moved-from reference, but\n  // this is testing the move implementation, so it is marked NOLINT.\n  EXPECT_EQ(nullptr, ptr.get());  // NOLINT\n  EXPECT_EQ(&data, ptr2.get());\n}\n\nTEST(WeakPtrTest, CopyAssignment) {\n  Integer data = 0;\n  auto ptr = data.WeakFromThis();\n  WeakPtr<Integer> ptr2;\n  EXPECT_EQ(nullptr, ptr2.get());\n  ptr2 = ptr;\n  EXPECT_EQ(&data, ptr.get());\n  EXPECT_EQ(&data, ptr2.get());\n}\n\nTEST(WeakPtrTest, MoveAssignment) {\n  Integer data = 0;\n  auto ptr = data.WeakFromThis();\n  WeakPtr<Integer> ptr2;\n  EXPECT_EQ(nullptr, ptr2.get());\n  ptr2 = std::move(ptr);\n  // The clang linter flags the method called on the moved-from reference, but\n  // this is testing the move implementation, so it is marked NOLINT.\n  EXPECT_EQ(nullptr, ptr.get());  // NOLINT\n  EXPECT_EQ(&data, ptr2.get());\n}\n\nTEST(WeakPtrTest, Testable) {\n  Integer data = 0;\n  WeakPtr<Integer> ptr;\n  EXPECT_EQ(nullptr, ptr.get());\n  EXPECT_FALSE(ptr);\n  ptr = data.WeakFromThis();\n  EXPECT_EQ(&data, ptr.get());\n  EXPECT_TRUE(ptr);\n}\n\nTEST(WeakPtrTest, OutOfScope) {\n  WeakPtr<Integer> ptr;\n  EXPECT_EQ(nullptr, ptr.get());\n  {\n    Integer data = 0;\n    ptr = data.WeakFromThis();\n  }\n  EXPECT_EQ(nullptr, ptr.get());\n}\n\nTEST(WeakPtrTest, Multiple) {\n  WeakPtr<Integer> a;\n  WeakPtr<Integer> b;\n  {\n    Integer data = 0;\n    a = data.WeakFromThis();\n    b = data.WeakFromThis();\n    EXPECT_EQ(&data, a.get());\n    EXPECT_EQ(&data, b.get());\n  }\n  EXPECT_EQ(nullptr, a.get());\n  EXPECT_EQ(nullptr, b.get());\n}\n\nTEST(WeakPtrTest, MultipleStaged) {\n  WeakPtr<Integer> a;\n  {\n    Integer data = 0;\n    a = data.WeakFromThis();\n    { auto b = data.WeakFromThis(); }\n    EXPECT_NE(a.get(), nullptr);\n  }\n  EXPECT_EQ(nullptr, a.get());\n}\n\nstruct Base : public EnableWeakFromThis<Base> {\n  double member = 0.;\n};\nstruct Derived : public Base {};\n\nTEST(WeakPtrTest, Dereference) {\n  Base data;\n  data.member = 123456.;\n  WeakPtr<Base> ptr = data.WeakFromThis();\n  EXPECT_EQ(&data, ptr.get());\n  EXPECT_EQ(data.member, (*ptr).member);\n  EXPECT_EQ(data.member, ptr->member);\n}\n\nTEST(WeakPtrTest, UpcastCopyConstruction) {\n  Derived data;\n  WeakPtr<Derived> ptr = data.WeakFromThis();\n  WeakPtr<Base> ptr2(ptr);\n  EXPECT_EQ(&data, ptr.get());\n  EXPECT_EQ(&data, ptr2.get());\n}\n\nTEST(WeakPtrTest, UpcastMoveConstruction) {\n  Derived data;\n  WeakPtr<Derived> ptr = data.WeakFromThis();\n  WeakPtr<Base> ptr2(std::move(ptr));\n  // The clang linter flags the method called on the moved-from reference, but\n  // this is testing the move implementation, so it is marked NOLINT.\n  EXPECT_EQ(nullptr, ptr.get());  // NOLINT\n  EXPECT_EQ(&data, ptr2.get());\n}\n\nTEST(WeakPtrTest, UpcastCopyAssignment) {\n  Derived data;\n  WeakPtr<Derived> ptr = data.WeakFromThis();\n  WeakPtr<Base> ptr2;\n  EXPECT_EQ(nullptr, ptr2.get());\n  ptr2 = ptr;\n  EXPECT_EQ(&data, ptr.get());\n  EXPECT_EQ(&data, ptr2.get());\n}\n\nTEST(WeakPtrTest, UpcastMoveAssignment) {\n  Derived data;\n  WeakPtr<Derived> ptr = data.WeakFromThis();\n  WeakPtr<Base> ptr2;\n  EXPECT_EQ(nullptr, ptr2.get());\n  ptr2 = std::move(ptr);\n  // The clang linter flags the method called on the moved-from reference, but\n  // this is testing the move implementation, so it is marked NOLINT.\n  EXPECT_EQ(nullptr, ptr.get());  // NOLINT\n  EXPECT_EQ(&data, ptr2.get());\n}\n\nTEST(WeakPtrTest, ShouldNotCrashIfRunningOnTheSameTaskRunner) {\n  fml::MessageLoop* loop1 = nullptr;\n  fml::AutoResetWaitableEvent latch1;\n  fml::AutoResetWaitableEvent term1;\n  std::thread thread1([&loop1, &latch1, &term1]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop1 = &fml::MessageLoop::GetCurrent();\n    latch1.Signal();\n    term1.Wait();\n  });\n\n  fml::MessageLoop* loop2 = nullptr;\n  fml::AutoResetWaitableEvent latch2;\n  fml::AutoResetWaitableEvent term2;\n  fml::AutoResetWaitableEvent loop2_task_finish_latch;\n  fml::AutoResetWaitableEvent loop2_task_start_latch;\n  std::thread thread2([&loop2, &latch2, &term2, &loop2_task_finish_latch,\n                       &loop2_task_start_latch]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    Integer data = 0;\n    loop2 = &fml::MessageLoop::GetCurrent();\n\n    loop2->GetTaskRunner()->PostTask([&]() {\n      latch2.Signal();\n      loop2_task_start_latch.Wait();\n      auto ptr = data.WeakFromThis();\n      EXPECT_EQ(*ptr, data);\n      loop2_task_finish_latch.Signal();\n    });\n    loop2->Run();\n    term2.Wait();\n  });\n\n  latch1.Wait();\n  latch2.Wait();\n  fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId();\n  fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId();\n  const auto raster_thread_merger =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n  const size_t kNumFramesMerged = 5;\n\n  raster_thread_merger->MergeWithLease(kNumFramesMerged);\n\n  loop2_task_start_latch.Signal();\n  loop2_task_finish_latch.Wait();\n\n  for (size_t i = 0; i < kNumFramesMerged; i++) {\n    ASSERT_TRUE(raster_thread_merger->IsMerged());\n    raster_thread_merger->DecrementLease();\n  }\n\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n  loop2->Terminate();\n\n  term1.Signal();\n  term2.Signal();\n  thread1.join();\n  thread2.join();\n}\n\n}  // namespace\n}  // namespace fml\n"
  },
  {
    "path": "base/src/fml/message_loop.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/message_loop.h\"\n\n#include <utility>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/no_destructor.h\"\n\nnamespace lynx {\nnamespace fml {\n\nnamespace {\nstd::unique_ptr<MessageLoop>& GetThreadLocalLooper() {\n  static thread_local base::NoDestructor<std::unique_ptr<MessageLoop>>\n      tls_message_loop_instance;\n  return *tls_message_loop_instance;\n}\n}  // namespace\n\nMessageLoop& MessageLoop::GetCurrent() {\n  auto* loop = GetThreadLocalLooper().get();\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(loop != nullptr);\n  //     << \"MessageLoop::EnsureInitializedForCurrentThread was not called on \"\n  //        \"this thread prior to message loop use.\";\n  return *loop;\n}\n\nMessageLoop& MessageLoop::EnsureInitializedForCurrentThread(\n    void* platform_loop) {\n  auto& looper_storage = GetThreadLocalLooper();\n  if (looper_storage == nullptr) {\n    looper_storage.reset(new MessageLoop(platform_loop));\n  }\n  return *looper_storage;\n}\n\nMessageLoop* MessageLoop::IsInitializedForCurrentThread() {\n  return GetThreadLocalLooper().get();\n}\n\nMessageLoop::MessageLoop(void* platform_loop)\n    : loop_(MessageLoopImpl::Create(platform_loop)),\n      task_runner_(fml::MakeRefCounted<fml::TaskRunner>(loop_)) {\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(loop_);\n  LYNX_BASE_CHECK(task_runner_);\n\n  // Cannot get the current MessageLoop in the constructor of this TaskRunner\n  // because the MessageLoop constructor has not finished yet.\n  // It should be explicitly bound to the current MessageLoop here.\n  loop_->Bind(task_runner_->GetTaskQueueId());\n}\n\nMessageLoop::~MessageLoop() = default;\n\nvoid MessageLoop::Run() { loop_->DoRun(); }\n\nvoid MessageLoop::Terminate() { loop_->DoTerminate(); }\n\nconst fml::RefPtr<fml::TaskRunner>& MessageLoop::GetTaskRunner() const {\n  return task_runner_;\n}\n\nconst fml::RefPtr<MessageLoopImpl>& MessageLoop::GetLoopImpl() const {\n  return loop_;\n}\n\nvoid MessageLoop::AddTaskObserver(intptr_t key, base::closure callback) {\n  loop_->AddTaskObserver(key, std::move(callback));\n}\n\nvoid MessageLoop::RemoveTaskObserver(intptr_t key) {\n  loop_->RemoveTaskObserver(key);\n}\n\nvoid MessageLoop::RunExpiredTasksNow() { loop_->RunExpiredTasksNow(); }\n\nvoid MessageLoop::SetMessageLoopRestrictionDuration(\n    fml::TimeDelta restriction_duration) {\n  loop_->SetRestrictionDuration(restriction_duration);\n}\n\nTaskQueueId MessageLoop::GetCurrentTaskQueueId() {\n  auto* loop = GetThreadLocalLooper().get();\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(loop != nullptr);\n  //     << \"MessageLoop::EnsureInitializedForCurrentThread was not called on \"\n  //        \"this thread prior to message loop use.\";\n  return loop->GetTaskRunner()->GetTaskQueueId();\n}\n\nvoid MessageLoop::Bind(const TaskQueueId& queue_id) {\n  // This TaskRunner should bind to the current MessageLoop in the constructor.\n  if (queue_id == task_runner_->GetTaskQueueId()) {\n    return;\n  }\n\n  loop_->Bind(queue_id);\n}\n\nvoid MessageLoop::UnBind(const TaskQueueId& queue_id) {\n  // Unbinding this TaskRunner from the current MessageLoop is illegal.\n  LYNX_BASE_CHECK(queue_id != task_runner_->GetTaskQueueId());\n\n  loop_->UnBind(queue_id);\n}\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/message_loop_impl.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"base/include/fml/message_loop_impl.h\"\n\n#include <algorithm>\n#include <vector>\n\n#include \"base/include/fml/fml_trace_event_def.h\"\n#include \"base/include/timer/time_utils.h\"\n#include \"base/src/base_trace/base_trace_event_def.h\"\n#include \"base/src/base_trace/trace_event.h\"\n#include \"build/build_config.h\"\n\nnamespace lynx {\nnamespace fml {\n\nMessageLoopImpl::MessageLoopImpl()\n    : task_queue_(MessageLoopTaskQueues::GetInstance()),\n      internal_queue_id_(task_queue_->CreateTaskQueue()),\n      terminated_(false),\n      restriction_duration_(fml::TimeDelta::Max()) {\n  Bind(internal_queue_id_);\n}\n\nMessageLoopImpl::~MessageLoopImpl() {\n  task_queue_->Dispose(internal_queue_id_);\n}\n\nvoid MessageLoopImpl::PostTask(base::closure task, fml::TimePoint target_time,\n                               fml::TaskSourceGrade task_source_grade,\n                               bool instant_task_hint) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(task != nullptr);\n  if (terminated_) {\n    // If the message loop has already been terminated, PostTask should destruct\n    // |task| synchronously within this function.\n    return;\n  }\n  task_queue_->RegisterTask(internal_queue_id_, std::move(task), target_time,\n                            task_source_grade, instant_task_hint);\n}\n\nvoid MessageLoopImpl::AddTaskObserver(intptr_t key, base::closure callback) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(callback != nullptr);\n  // DCHECK(MessageLoop::GetCurrent().GetLoopImpl().get() == this)\n  //     << \"Message loop task observer must be added on the same thread as the\n  //     \"\n  //        \"loop.\";\n  if (callback != nullptr) {\n    task_queue_->AddTaskObserver(internal_queue_id_, key, std::move(callback));\n  } else {\n    // TODO(zhengsenyao): Uncomment LOG code when LOG available\n    // LOGE(\"Tried to add a null TaskObserver.\");\n  }\n}\n\nvoid MessageLoopImpl::RemoveTaskObserver(intptr_t key) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(MessageLoop::GetCurrent().GetLoopImpl().get() == this)\n  //     << \"Message loop task observer must be removed from the same thread as\n  //     \"\n  //        \"the loop.\";\n  task_queue_->RemoveTaskObserver(internal_queue_id_, key);\n}\n\nvoid MessageLoopImpl::DoRun() {\n  if (terminated_) {\n    // Message loops may be run only once.\n    return;\n  }\n\n  // Allow the implementation to do its thing.\n  Run();\n\n  // The loop may have been implicitly terminated. This can happen if the\n  // implementation supports termination via platform specific APIs or just\n  // error conditions. Set the terminated flag manually.\n  terminated_ = true;\n\n  // The message loop is shutting down. Check if there are expired tasks. This\n  // is the last chance for expired tasks to be serviced. Make sure the\n  // terminated flag is already set so we don't accrue additional tasks now.\n  RunExpiredTasksNow();\n\n  // When the message loop is in the process of shutting down, pending tasks\n  // should be destructed on the message loop's thread. We have just returned\n  // from the implementations |Run| method which we know is on the correct\n  // thread. Drop all pending tasks on the floor.\n  task_queue_->DisposeTasks(internal_queue_id_);\n}\n\nvoid MessageLoopImpl::DoTerminate() {\n  terminated_ = true;\n  Terminate();\n}\n\nvoid MessageLoopImpl::FlushTasks(FlushType type) {\n  BASE_TRACE_EVENT(LYNX_BASE_TRACE_CATEGORY, MESSAGE_LOOP_FLUSH_TASK);\n  if (FlushTasksWithRestrictionDuration(\n          type, queue_ids_, restriction_duration_.ToMilliseconds())) {\n    // Call WakeUp here to flush the remaining tasks when reaching maximum\n    // restriction duration.\n    task_queue_->WakeUp(queue_ids_);\n  }\n}\n\nvoid MessageLoopImpl::FlushVSyncAlignedTasks(FlushType type) {\n  BASE_TRACE_EVENT(LYNX_BASE_TRACE_CATEGORY,\n                   MESSAGE_LOOP_FLUSH_VASYNC_ALIGNED_TASKS);\n  FlushTasksWithRestrictionDuration(type, vsync_aligned_task_queue_ids_,\n                                    max_execute_time_ms_);\n}\n\nbool MessageLoopImpl::FlushTasksWithRestrictionDuration(\n    FlushType type, const std::vector<TaskQueueId>& queue_ids,\n    int64_t restriction_duration) {\n  if (queue_ids.empty()) {\n    return false;\n  }\n  std::vector<const base::closure*> observers;\n  const auto now = fml::TimePoint::Now();\n  if (restriction_duration == fml::TimeDelta::Max().ToMilliseconds()) {\n    // No restriction, skip checks in loop.\n    do {\n      auto invocation =\n          task_queue_->GetNextTaskToRun(queue_ids, now, &observers);\n      if (!invocation) {\n        break;\n      }\n      invocation();\n      if (!observers.empty()) {\n        for (const auto& observer : observers) {\n          (*observer)();\n        }\n        observers.clear();\n      }\n      if (type == FlushType::kSingle) {\n        break;\n      }\n    } while (true);\n    return false;\n  } else {\n    bool reach_max_restriction = false;\n    do {\n      auto invocation =\n          task_queue_->GetNextTaskToRun(queue_ids, now, &observers);\n      if (!invocation) {\n        break;\n      }\n      invocation();\n      if (!observers.empty()) {\n        for (const auto& observer : observers) {\n          (*observer)();\n        }\n        observers.clear();\n      }\n      if (type == FlushType::kSingle) {\n        break;\n      }\n      // Reach maximum restriction duration, break\n      if (restriction_duration <=\n          (fml::TimePoint::Now() - now).ToMilliseconds()) {\n        reach_max_restriction = true;\n        break;\n      }\n    } while (true);\n    return reach_max_restriction;\n  }\n}\n\nvoid MessageLoopImpl::WakeUp(fml::TimePoint time_point,\n                             bool is_woken_by_vsync) {\n  if (is_woken_by_vsync && vsync_request_) {\n    WakeUpByVSync(time_point);\n    return;\n  }\n  WakeUp(time_point);\n}\n\nvoid MessageLoopImpl::WakeUpByVSync(fml::TimePoint time_point) {\n  if (fml::TimePoint::Now() < time_point || WaitForVSyncTimeOut()) {\n    // Scenario 1: The execution time of the task has not yet arrived. Use the\n    // epoll to wake up the looper at the specified time.\n    // Scenario 2: When app goes into the background, the platform layer may no\n    // longer provides VSync callbacks to the application. In this case, we need\n    // to use epoll to wake up the looper to flush tasks.\n    WakeUp(time_point);\n  } else if (!HasPendingVSyncRequest()) {\n    // No pending VSync request, a new VSync request should be sent.\n    request_vsync_time_millis_ = base::CurrentSystemTimeMilliseconds();\n    vsync_request_([this](int64_t frame_start_time_ns,\n                          int64_t frame_target_time_ns) {\n      request_vsync_time_millis_ = 0;\n      max_execute_time_ms_ =\n          static_cast<int64_t>((frame_target_time_ns - frame_start_time_ns) *\n                               kTraversalProportion / kNSecPerMSec);\n      FlushVSyncAlignedTasks(FlushType::kAll);\n    });\n  }\n}\n\nbool MessageLoopImpl::WaitForVSyncTimeOut() {\n  auto now = base::CurrentSystemTimeMilliseconds();\n  // There is a pending VSync request, and the waiting time has already exceeded\n  // the threshold.\n  return HasPendingVSyncRequest() &&\n         (now - request_vsync_time_millis_ >= kWaitingVSyncTimeoutMillis);\n}\n\nbool MessageLoopImpl::HasPendingVSyncRequest() {\n  return request_vsync_time_millis_ > 0;\n}\n\nvoid MessageLoopImpl::RunExpiredTasksNow() { FlushTasks(FlushType::kAll); }\n\nvoid MessageLoopImpl::RunSingleExpiredTaskNow() {\n  FlushTasks(FlushType::kSingle);\n}\n\nTaskQueueId MessageLoopImpl::GetTaskQueueId() const {\n  return internal_queue_id_;\n}\n\nstd::vector<TaskQueueId> MessageLoopImpl::GetTaskQueueIds() const {\n  return queue_ids_;\n}\n\nvoid MessageLoopImpl::Bind(const TaskQueueId& queue_id,\n                           bool should_run_expired_tasks_immediately) {\n  BASE_TRACE_EVENT(LYNX_BASE_TRACE_CATEGORY, MESSAGE_LOOP_IMPL_BIND);\n\n  auto& ids_vec =\n      (vsync_request_ && task_queue_->IsTaskQueueAlignedWithVSync(queue_id))\n          ? vsync_aligned_task_queue_ids_\n          : queue_ids_;\n\n  // Avoid duplicates\n  bool found = false;\n  for (const auto& exist : ids_vec) {\n    if (exist == queue_id) {\n      found = true;\n      break;\n    }\n  }\n  if (!found) {\n    ids_vec.emplace_back(queue_id);\n  }\n\n  task_queue_->SetWakeable(queue_id, this);\n\n  if (should_run_expired_tasks_immediately) {\n    FlushTasksWithRestrictionDuration(FlushType::kAll, {queue_id},\n                                      fml::TimeDelta::Max().ToMilliseconds());\n  }\n}\n\nvoid MessageLoopImpl::UnBind(const TaskQueueId& queue_id) {\n  auto& ids =\n      (vsync_request_ && task_queue_->IsTaskQueueAlignedWithVSync(queue_id))\n          ? vsync_aligned_task_queue_ids_\n          : queue_ids_;\n  for (auto it = ids.begin(); it != ids.end(); ++it) {\n    if (*it == queue_id) {\n      ids.erase(it);\n      task_queue_->SetWakeable(queue_id, nullptr);\n      break;\n    }\n  }\n}\n\nvoid MessageLoopImpl::SetRestrictionDuration(fml::TimeDelta duration) {\n  restriction_duration_ = duration;\n}\n\nvoid MessageLoopImpl::SetVSyncRequest(VSyncRequest vsync_request) {\n  vsync_request_ = std::move(vsync_request);\n}\n\nbool MessageLoopImpl::CanRunNow() {\n  return MessageLoop::GetCurrent().GetLoopImpl().get() == this;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/message_loop_impl_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include <thread>\n\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#define TIMESENSITIVE(x) TimeSensitiveTest_##x\n\nnamespace lynx {\nclass MockVSyncMonitor {\n public:\n  MockVSyncMonitor() = default;\n  ~MockVSyncMonitor() = default;\n\n  void RequestVSync(fml::VSyncCallback callback) {\n    callback_ = std::move(callback);\n  }\n\n  void TriggerVSync() {\n    if (callback_) {\n      callback_(current_, current_ + kFrameDuration);\n    }\n  }\n\n private:\n  int64_t current_ = kFrameDuration;\n  constexpr static int64_t kFrameDuration = 16;  // ms\n  fml::VSyncCallback callback_;\n};\n\nTEST(MessageLoopImpl, TIMESENSITIVE(WakeUpTimersAreSingletons)) {\n  auto loop_impl = fml::MessageLoopImpl::Create();\n\n  const auto t1 = fml::TimeDelta::FromMilliseconds(10);\n  const auto t2 = fml::TimeDelta::FromMilliseconds(30);\n\n  const auto begin = fml::TimePoint::Now();\n\n  // Register a task scheduled in the future. This schedules a WakeUp call on\n  // the MessageLoopImpl with that fml::TimePoint.\n  loop_impl->PostTask(\n      [&]() {\n        auto delta = fml::TimePoint::Now() - begin;\n        auto ms = delta.ToMillisecondsF();\n        ASSERT_GE(ms, 20);\n        ASSERT_LE(ms, 40);\n\n        loop_impl->Terminate();\n      },\n      begin + t1);\n\n  // Call WakeUp manually to change the WakeUp time further in the future. If\n  // the timer is correctly set up to be rearmed instead of a task being\n  // scheduled for each WakeUp, the above task will be executed at t2 instead\n  // of t1 now.\n  loop_impl->WakeUp(begin + t2);\n\n  loop_impl->Run();\n}\n\nTEST(MessageLoopImpl, WakeUpByVSync) {\n  fml::AutoResetWaitableEvent latch;\n  fml::RefPtr<fml::MessageLoopImpl> loop_impl;\n  base::closure setup_thread = [&]() {\n    auto& loop = fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop_impl = loop.GetLoopImpl();\n    latch.Signal();\n    loop.Run();\n  };\n  auto thread = std::make_unique<std::thread>(std::move(setup_thread));\n  latch.Wait();\n  latch.Reset();\n\n  auto task_queues = fml::MessageLoopTaskQueues::GetInstance();\n  auto vsync_queue_id = task_queues->CreateTaskQueue(true);\n  auto vsync_monitor = MockVSyncMonitor();\n  loop_impl->SetVSyncRequest([&](fml::VSyncCallback vsync_callback) {\n    vsync_monitor.RequestVSync(\n        [vsync_callback = std::move(vsync_callback)](\n            int64_t frame_start_time_ns, int64_t frame_target_time_ns) {\n          vsync_callback(frame_start_time_ns, frame_target_time_ns);\n        });\n  });\n\n  // Bind vsync queue to the loop.\n  loop_impl->PostTask(\n      [&]() {\n        loop_impl->Bind(vsync_queue_id);\n        latch.Signal();\n      },\n      fml::TimePoint::Now());\n  latch.Wait();\n  latch.Reset();\n\n  // Register vsync task\n  bool vsync_task_executed = false;\n  auto vsync_aligned_task = [&]() { vsync_task_executed = true; };\n  task_queues->RegisterTask(vsync_queue_id, std::move(vsync_aligned_task),\n                            fml::TimePoint::Now());\n\n  loop_impl->PostTask(\n      [&]() {\n        vsync_monitor.TriggerVSync();\n        latch.Signal();\n      },\n      fml::TimePoint::Now());\n  latch.Wait();\n  ASSERT_TRUE(vsync_task_executed);\n\n  // Terminate thread safely\n  loop_impl->PostTask([&]() { loop_impl->Terminate(); }, fml::TimePoint::Now());\n  thread->join();\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/message_loop_task_queues.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"base/include/fml/message_loop_task_queues.h\"\n\n#include <algorithm>\n#include <iostream>\n#include <memory>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/fml/task_source.h\"\n\nnamespace lynx {\nnamespace fml {\n\nTaskQueueEntry::TaskQueueEntry(TaskQueueId created_for_arg,\n                               bool is_aligned_with_vsync)\n    : subsumed_by(_kUnmerged),\n      created_for(created_for_arg),\n      is_aligned_with_vsync_(is_aligned_with_vsync) {\n  wakeable = NULL;\n  task_observers = TaskObservers();\n  task_source = std::make_unique<TaskSource>(created_for);\n}\n\nMessageLoopTaskQueues* MessageLoopTaskQueues::GetInstance() {\n  static base::NoDestructor<MessageLoopTaskQueues> instance;\n  return instance.get();\n}\n\nTaskQueueId MessageLoopTaskQueues::CreateTaskQueue(\n    bool is_vsync_aligned_task_queue) {\n  std::lock_guard guard(queue_mutex_);\n  TaskQueueId loop_id = TaskQueueId(task_queue_id_counter_);\n  ++task_queue_id_counter_;\n  queue_entries_[loop_id] =\n      std::make_unique<TaskQueueEntry>(loop_id, is_vsync_aligned_task_queue);\n  return loop_id;\n}\n\nMessageLoopTaskQueues::MessageLoopTaskQueues()\n    : task_queue_id_counter_(0), order_(0) {}\n\nMessageLoopTaskQueues::~MessageLoopTaskQueues() = default;\n\nvoid MessageLoopTaskQueues::Dispose(TaskQueueId queue_id) {\n  std::lock_guard guard(queue_mutex_);\n  const auto& queue_entry = queue_entries_.at(queue_id);\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(queue_entry->subsumed_by == _kUnmerged);\n  auto& subsumed_set = queue_entry->owner_of;\n  for (auto& subsumed : subsumed_set) {\n    queue_entries_.erase(subsumed);\n  }\n  // Erase owner queue_id at last to avoid &subsumed_set from being invalid\n  queue_entries_.erase(queue_id);\n}\n\nvoid MessageLoopTaskQueues::DisposeTasks(TaskQueueId queue_id) {\n  std::lock_guard guard(queue_mutex_);\n  const auto& queue_entry = queue_entries_.at(queue_id);\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(queue_entry->subsumed_by == _kUnmerged);\n  auto& subsumed_set = queue_entry->owner_of;\n  queue_entry->task_source->ShutDown();\n  for (auto& subsumed : subsumed_set) {\n    queue_entries_.at(subsumed)->task_source->ShutDown();\n  }\n}\n\nvoid MessageLoopTaskQueues::RegisterTask(TaskQueueId queue_id,\n                                         base::closure task,\n                                         fml::TimePoint target_time,\n                                         fml::TaskSourceGrade task_source_grade,\n                                         bool instant_task_hint) {\n  std::lock_guard guard(queue_mutex_);\n  size_t order = order_++;\n  const auto& queue_entry = queue_entries_.at(queue_id);\n  queue_entry->task_source->RegisterTask(\n      {order, std::move(task), target_time, task_source_grade});\n  TaskQueueId loop_to_wake = queue_id;\n  if (queue_entry->subsumed_by != _kUnmerged) {\n    loop_to_wake = queue_entry->subsumed_by;\n  }\n  if (loop_to_wake == queue_id) {\n    // A task was just added to queue_id, no need to check\n    // HasPendingTasksUnlocked(loop_to_wake)\n    if (instant_task_hint) {\n      // WakeUp immediately using 0 time point.\n      WakeUpUnlocked(*queue_entry, fml::TimePoint());\n    } else {\n      WakeUpUnlocked(*queue_entry, GetNextWakeTimeUnlocked(loop_to_wake));\n    }\n  } else if (HasPendingTasksUnlocked(loop_to_wake)) {\n    WakeUpUnlocked(loop_to_wake, GetNextWakeTimeUnlocked(loop_to_wake));\n  }\n}\n\nbool MessageLoopTaskQueues::IsTaskQueueRunningOnGivenMessageLoop(\n    Wakeable* loop, TaskQueueId queue_id) {\n  std::lock_guard guard(queue_mutex_);\n\n  auto& entry = queue_entries_.at(queue_id);\n\n  if (entry->subsumed_by == _kUnmerged) {\n    // If the TaskQueue has not been merged with another queue,\n    // simply check whether the wakeable is the same as the given loop.\n    return entry->wakeable == loop;\n  }\n\n  // If the TaskQueue has been merged with another queue, we should check the\n  // owner's wakeable instead.\n  return queue_entries_.at(entry->subsumed_by)->wakeable == loop;\n}\n\nbool MessageLoopTaskQueues::IsTaskQueueAlignedWithVSync(TaskQueueId queue_id) {\n  std::lock_guard guard(queue_mutex_);\n  return queue_entries_.at(queue_id)->IsAlignedWithVSync();\n}\n\nbool MessageLoopTaskQueues::HasPendingTasks(TaskQueueId queue_id) const {\n  std::lock_guard guard(queue_mutex_);\n  return HasPendingTasksUnlocked(queue_id);\n}\n\nbase::closure MessageLoopTaskQueues::GetNextTaskToRun(\n    const std::vector<TaskQueueId>& queue_ids, fml::TimePoint from_time,\n    std::vector<const base::closure*>* observers) {\n  base::closure result;\n\n  struct TopTaskInfo {\n    const TaskQueueEntry& queue_entry;\n    const DelayedTask& task;\n  };\n\n  std::optional<TopTaskInfo> top_task;\n  auto update_top_task_from_queue_entry =\n      [&top_task](const TaskQueueEntry& queue_entry) {\n        if (queue_entry.task_source) {\n          const auto* task = queue_entry.task_source->TopOrNull();\n          if (task) {\n            if (!top_task.has_value() || top_task->task > *task) {\n              top_task.emplace(TopTaskInfo{queue_entry, *task});\n            }\n          }\n        }\n      };\n\n  // Combining `HasPendingTasksUnlocked`, `PeekNextTaskUnlocked` and\n  // `GetNextWakeTimeUnlocked` into single one to reduce redundant judgments and\n  // repetitive logic.\n  std::lock_guard guard(queue_mutex_);\n\n  // Select the top-most task from all queues.\n  for (const auto& queue_id : queue_ids) {\n    const auto& entry = queue_entries_.at(queue_id);\n    if (entry->subsumed_by != _kUnmerged) {\n      continue;  // Owned by other\n    }\n    update_top_task_from_queue_entry(*entry);\n    if (!entry->owner_of.empty()) {\n      for (const auto& subsumed : entry->owner_of) {\n        const auto& subsumed_entry = queue_entries_.at(subsumed);\n        update_top_task_from_queue_entry(*subsumed_entry);\n      }\n    }\n  }\n\n  // No task found.\n  if (!top_task.has_value()) {\n    return result;\n  }\n\n  // The top-most task has not yet been triggered, wake up in future.\n  if (auto target_time = top_task->task.GetTargetTime();\n      target_time > from_time) {\n    WakeUpUnlocked(queue_ids.front(), target_time);\n    return result;\n  }\n\n  // This task has been confirmed to be executed and should be removed from the\n  // queue.\n  const auto& queue_entry = top_task->queue_entry;\n  result = top_task->task.GetTask();\n  queue_entry.task_source->PopTask(top_task->task.GetTaskSourceGrade());\n\n  // Get attached observers.\n  if (observers) {\n    if (!queue_entry.task_observers.empty()) {\n      for (const auto& observer : queue_entry.task_observers) {\n        observers->push_back(&(observer.second));\n      }\n    }\n\n    auto& subsumed_set = queue_entry.owner_of;\n    if (!subsumed_set.empty()) {\n      for (auto& subsumed : subsumed_set) {\n        for (const auto& observer :\n             queue_entries_.at(subsumed)->task_observers) {\n          observers->push_back(&(observer.second));\n        }\n      }\n    }\n  }\n\n  return result;\n}\n\nvoid MessageLoopTaskQueues::WakeUpUnlocked(TaskQueueId queue_id,\n                                           fml::TimePoint time) const {\n  auto& q = queue_entries_.at(queue_id);\n  if (q->wakeable) {\n    q->wakeable->WakeUp(time, q->IsAlignedWithVSync());\n  }\n}\n\nvoid MessageLoopTaskQueues::WakeUpUnlocked(TaskQueueEntry& queue,\n                                           fml::TimePoint time) const {\n  if (queue.wakeable) {\n    queue.wakeable->WakeUp(time, queue.IsAlignedWithVSync());\n  }\n}\n\nsize_t MessageLoopTaskQueues::GetNumPendingTasks(TaskQueueId queue_id) const {\n  std::lock_guard guard(queue_mutex_);\n  const auto& queue_entry = queue_entries_.at(queue_id);\n  if (queue_entry->subsumed_by != _kUnmerged) {\n    return 0;\n  }\n\n  size_t total_tasks = 0;\n  total_tasks += queue_entry->task_source->GetNumPendingTasks();\n\n  auto& subsumed_set = queue_entry->owner_of;\n  for (auto& subsumed : subsumed_set) {\n    const auto& subsumed_entry = queue_entries_.at(subsumed);\n    total_tasks += subsumed_entry->task_source->GetNumPendingTasks();\n  }\n  return total_tasks;\n}\n\nvoid MessageLoopTaskQueues::AddTaskObserver(TaskQueueId queue_id, intptr_t key,\n                                            base::closure callback) {\n  std::lock_guard guard(queue_mutex_);\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(callback != nullptr) << \"Observer callback must be non-null.\";\n  queue_entries_.at(queue_id)->task_observers[key] = std::move(callback);\n}\n\nvoid MessageLoopTaskQueues::RemoveTaskObserver(TaskQueueId queue_id,\n                                               intptr_t key) {\n  std::lock_guard guard(queue_mutex_);\n  queue_entries_.at(queue_id)->task_observers.erase(key);\n}\n\nstd::vector<const base::closure*> MessageLoopTaskQueues::GetObserversToNotify(\n    TaskQueueId queue_id) const {\n  std::lock_guard guard(queue_mutex_);\n  std::vector<const base::closure*> observers;\n\n  if (queue_entries_.at(queue_id)->subsumed_by != _kUnmerged) {\n    return observers;\n  }\n\n  for (const auto& observer : queue_entries_.at(queue_id)->task_observers) {\n    observers.push_back(&(observer.second));\n  }\n\n  auto& subsumed_set = queue_entries_.at(queue_id)->owner_of;\n  for (auto& subsumed : subsumed_set) {\n    for (const auto& observer : queue_entries_.at(subsumed)->task_observers) {\n      observers.push_back(&(observer.second));\n    }\n  }\n\n  return observers;\n}\n\nvoid MessageLoopTaskQueues::SetWakeable(TaskQueueId queue_id,\n                                        fml::Wakeable* wakeable) {\n  std::lock_guard guard(queue_mutex_);\n  queue_entries_.at(queue_id)->wakeable = wakeable;\n}\n\nbool MessageLoopTaskQueues::Merge(TaskQueueId owner, TaskQueueId subsumed) {\n  if (owner == subsumed) {\n    return true;\n  }\n  std::lock_guard guard(queue_mutex_);\n  auto& owner_entry = queue_entries_.at(owner);\n  auto& subsumed_entry = queue_entries_.at(subsumed);\n  auto& subsumed_set = owner_entry->owner_of;\n  if (subsumed_set.find(subsumed) != subsumed_set.end()) {\n    return true;\n  }\n\n  // Won't check owner_entry->owner_of, because it may contains items when\n  // merged with other different queues.\n\n  // Ensure owner_entry->subsumed_by being _kUnmerged\n  if (owner_entry->subsumed_by != _kUnmerged) {\n    // TODO(zhengsenyao): Uncomment LOGW code when LOGW available.\n    // LOGW(\n    //     \"Thread merging failed: owner_entry was already \"\n    //     \"subsumed by others, owner=\"\n    //     << owner << \", subsumed=\" << subsumed\n    //     << \", owner->subsumed_by=\" << owner_entry->subsumed_by);\n    return false;\n  }\n  // Ensure subsumed_entry->owner_of being empty\n  if (!subsumed_entry->owner_of.empty()) {\n    // TODO(zhengsenyao): Uncomment LOGW code when LOGW available.\n    // LOGW(\"Thread merging failed: subsumed_entry already owns others, owner=\"\n    //      << owner << \", subsumed=\" << subsumed\n    //      << \", subsumed->owner_of.size()=\" <<\n    //      subsumed_entry->owner_of.size());\n    return false;\n  }\n  // Ensure subsumed_entry->subsumed_by being _kUnmerged\n  if (subsumed_entry->subsumed_by != _kUnmerged) {\n    // TODO(zhengsenyao): Uncomment LOGW code when LOGW available.\n    // LOGW(\n    //     \"Thread merging failed: subsumed_entry was already \"\n    //     \"subsumed by others, owner=\"\n    //     << owner << \", subsumed=\" << subsumed\n    //     << \", subsumed->subsumed_by=\" << subsumed_entry->subsumed_by);\n    return false;\n  }\n  // All checking is OK, set merged state.\n  owner_entry->owner_of.insert(subsumed);\n  subsumed_entry->subsumed_by = owner;\n\n  if (HasPendingTasksUnlocked(owner)) {\n    WakeUpUnlocked(owner, GetNextWakeTimeUnlocked(owner));\n  }\n\n  return true;\n}\n\nbool MessageLoopTaskQueues::Unmerge(TaskQueueId owner, TaskQueueId subsumed) {\n  std::lock_guard guard(queue_mutex_);\n  const auto& owner_entry = queue_entries_.at(owner);\n  if (owner_entry->owner_of.empty()) {\n    // TODO(zhengsenyao): Uncomment LOGW code when LOGW available.\n    // LOGW(\"Thread unmerging failed: owner_entry doesn't own anyone, owner=\"\n    //      << owner << \", subsumed=\" << subsumed);\n    return false;\n  }\n  if (owner_entry->subsumed_by != _kUnmerged) {\n    // TODO(zhengsenyao): Uncomment LOGW code when LOGW available.\n    // LOGW(\"Thread unmerging failed: owner_entry was subsumed by others,\n    // owner=\"\n    //      << owner << \", subsumed=\" << subsumed\n    //      << \", owner_entry->subsumed_by=\" << owner_entry->subsumed_by);\n    return false;\n  }\n  if (queue_entries_.at(subsumed)->subsumed_by == _kUnmerged) {\n    // TODO(zhengsenyao): Uncomment LOGW code when LOGW available.\n    // LOGW(\n    //     \"Thread unmerging failed: subsumed_entry wasn't \"\n    //     \"subsumed by others, owner=\"\n    //     << owner << \", subsumed=\" << subsumed);\n    return false;\n  }\n  if (owner_entry->owner_of.find(subsumed) == owner_entry->owner_of.end()) {\n    // TODO(zhengsenyao): Uncomment LOGW code when LOGW available.\n    // LOGW(\n    //     \"Thread unmerging failed: owner_entry didn't own the \"\n    //     \"given subsumed queue id, owner=\"\n    //     << owner << \", subsumed=\" << subsumed);\n    return false;\n  }\n\n  queue_entries_.at(subsumed)->subsumed_by = _kUnmerged;\n  owner_entry->owner_of.erase(subsumed);\n\n  if (HasPendingTasksUnlocked(owner)) {\n    WakeUpUnlocked(owner, GetNextWakeTimeUnlocked(owner));\n  }\n\n  if (HasPendingTasksUnlocked(subsumed)) {\n    WakeUpUnlocked(subsumed, GetNextWakeTimeUnlocked(subsumed));\n  }\n\n  return true;\n}\n\nbool MessageLoopTaskQueues::Owns(TaskQueueId owner,\n                                 TaskQueueId subsumed) const {\n  std::lock_guard guard(queue_mutex_);\n  if (owner == _kUnmerged || subsumed == _kUnmerged) {\n    return false;\n  }\n  auto& subsumed_set = queue_entries_.at(owner)->owner_of;\n  return subsumed_set.find(subsumed) != subsumed_set.end();\n}\n\nstd::set<TaskQueueId> MessageLoopTaskQueues::GetSubsumedTaskQueueId(\n    TaskQueueId owner) const {\n  std::lock_guard guard(queue_mutex_);\n  return queue_entries_.at(owner)->owner_of;\n}\n\n// Subsumed queues will never have pending tasks.\n// Owning queues will consider both their and their subsumed tasks.\nbool MessageLoopTaskQueues::HasPendingTasksUnlocked(\n    TaskQueueId queue_id) const {\n  const auto& entry = queue_entries_.at(queue_id);\n  bool is_subsumed = entry->subsumed_by != _kUnmerged;\n  if (is_subsumed) {\n    return false;\n  }\n\n  if (!entry->task_source->IsEmpty()) {\n    return true;\n  }\n\n  auto& subsumed_set = entry->owner_of;\n  return std::any_of(\n      subsumed_set.begin(), subsumed_set.end(), [&](const auto& subsumed) {\n        return !queue_entries_.at(subsumed)->task_source->IsEmpty();\n      });\n}\n\nbool MessageLoopTaskQueues::HasPendingTasksUnlocked(\n    const std::vector<TaskQueueId>& queue_ids) const {\n  return std::any_of(\n      queue_ids.begin(), queue_ids.end(),\n      [this](auto& queue_id) { return HasPendingTasksUnlocked(queue_id); });\n}\n\nfml::TimePoint MessageLoopTaskQueues::GetNextWakeTimeUnlocked(\n    const std::vector<TaskQueueId>& queue_ids) const {\n  std::optional<fml::TimePoint> time_point;\n  for (auto& queue_id : queue_ids) {\n    if (!HasPendingTasksUnlocked(queue_id)) {\n      continue;\n    }\n    fml::TimePoint other_time_point = GetNextWakeTimeUnlocked(queue_id);\n    if (!time_point.has_value() || time_point > other_time_point) {\n      time_point.emplace(other_time_point);\n    }\n  }\n  return *time_point;\n}\n\nfml::TimePoint MessageLoopTaskQueues::GetNextWakeTimeUnlocked(\n    TaskQueueId queue_id) const {\n  return PeekNextTaskUnlocked(queue_id).task.GetTargetTime();\n}\n\nTaskSource::TopTask MessageLoopTaskQueues::PeekNextTaskUnlocked(\n    const std::vector<TaskQueueId>& queue_ids) const {\n  std::optional<TaskSource::TopTask> top_task;\n  const auto top_task_updater =\n      [&top_task](const TaskSource::TopTask other_task) {\n        if (!top_task.has_value() || top_task->task > other_task.task) {\n          top_task.emplace(other_task);\n        }\n      };\n  for (auto& queue_id : queue_ids) {\n    if (HasPendingTasksUnlocked(queue_id)) {\n      auto task = PeekNextTaskUnlocked(queue_id);\n      top_task_updater(task);\n    }\n  }\n\n  return *top_task;\n}\n\nTaskSource::TopTask MessageLoopTaskQueues::PeekNextTaskUnlocked(\n    TaskQueueId owner) const {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(HasPendingTasksUnlocked(owner));\n  const auto& entry = queue_entries_.at(owner);\n  if (entry->owner_of.empty()) {\n    // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK\n    // available.\n    LYNX_BASE_CHECK(!entry->task_source->IsEmpty());\n    return entry->task_source->Top();\n  }\n\n  // Use optional for the memory of TopTask object.\n  std::optional<TaskSource::TopTask> top_task;\n\n  std::function<void(const TaskSource*)> top_task_updater =\n      [&top_task](const TaskSource* source) {\n        if (source && !source->IsEmpty()) {\n          TaskSource::TopTask other_task = source->Top();\n          if (!top_task.has_value() || top_task->task > other_task.task) {\n            top_task.emplace(other_task);\n          }\n        }\n      };\n\n  TaskSource* owner_tasks = entry->task_source.get();\n  top_task_updater(owner_tasks);\n\n  for (TaskQueueId subsumed : entry->owner_of) {\n    TaskSource* subsumed_tasks = queue_entries_.at(subsumed)->task_source.get();\n    top_task_updater(subsumed_tasks);\n  }\n  // At least one task at the top because PeekNextTaskUnlocked() is called after\n  // HasPendingTasksUnlocked()\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(top_task.has_value());\n  return *top_task;\n}\n\nstd::vector<TaskQueueId> MessageLoopTaskQueues::GetAllQueueIds() {\n  std::vector<TaskQueueId> ids;\n\n  std::lock_guard guard(queue_mutex_);\n  for (const auto& [id, entry] : queue_entries_) {\n    ids.emplace_back(id);\n  }\n\n  return ids;\n}\n\nbool MessageLoopTaskQueues::IsSubsumed(TaskQueueId queue_id) const {\n  std::lock_guard guard(queue_mutex_);\n  return queue_entries_.at(queue_id)->subsumed_by != _kUnmerged;\n}\n\nvoid MessageLoopTaskQueues::WakeUp(\n    const std::vector<TaskQueueId>& queue_ids) const {\n  std::lock_guard guard(queue_mutex_);\n  if (!HasPendingTasksUnlocked(queue_ids)) {\n    WakeUpUnlocked(queue_ids.front(), fml::TimePoint::Max());\n  } else {\n    WakeUpUnlocked(queue_ids.front(), GetNextWakeTimeUnlocked(queue_ids));\n  }\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/message_loop_task_queues_merge_unmerge_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include <thread>\n\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/time/chrono_timestamp_provider.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace testing {\n\nclass TestWakeable : public fml::Wakeable {\n public:\n  using WakeUpCall = std::function<void(const fml::TimePoint)>;\n\n  explicit TestWakeable(WakeUpCall call) : wake_up_call_(call) {}\n\n  void WakeUp(fml::TimePoint time_point, bool is_woken_by_vsync) override {\n    wake_up_call_(time_point);\n  }\n\n private:\n  WakeUpCall wake_up_call_;\n};\n\nstatic int CountRemainingTasks(MessageLoopTaskQueues* task_queue,\n                               const TaskQueueId& queue_id,\n                               bool run_invocation = false) {\n  const auto now = ChronoTicksSinceEpoch();\n  int count = 0;\n  std::vector<TaskQueueId> queue_ids = {queue_id};\n  do {\n    auto invocation = task_queue->GetNextTaskToRun(queue_ids, now);\n    if (!invocation) {\n      break;\n    }\n    count++;\n    if (run_invocation) {\n      invocation();\n    }\n  } while (true);\n  return count;\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge,\n     AfterMergePrimaryTasksServicedOnPrimary) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n\n  task_queue->RegisterTask(\n      queue_id_1, []() {}, ChronoTicksSinceEpoch());\n  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_1));\n\n  task_queue->Merge(queue_id_1, queue_id_2);\n  task_queue->RegisterTask(\n      queue_id_1, []() {}, ChronoTicksSinceEpoch());\n\n  ASSERT_EQ(2u, task_queue->GetNumPendingTasks(queue_id_1));\n  ASSERT_EQ(0u, task_queue->GetNumPendingTasks(queue_id_2));\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge,\n     AfterMergeSecondaryTasksAlsoServicedOnPrimary) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n\n  task_queue->RegisterTask(\n      queue_id_2, []() {}, ChronoTicksSinceEpoch());\n  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_2));\n\n  task_queue->Merge(queue_id_1, queue_id_2);\n  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_1));\n  ASSERT_EQ(0u, task_queue->GetNumPendingTasks(queue_id_2));\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge, MergeUnmergeTasksPreserved) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n\n  task_queue->RegisterTask(\n      queue_id_1, []() {}, ChronoTicksSinceEpoch());\n  task_queue->RegisterTask(\n      queue_id_2, []() {}, ChronoTicksSinceEpoch());\n\n  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_1));\n  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_2));\n\n  task_queue->Merge(queue_id_1, queue_id_2);\n\n  ASSERT_EQ(2u, task_queue->GetNumPendingTasks(queue_id_1));\n  ASSERT_EQ(0u, task_queue->GetNumPendingTasks(queue_id_2));\n\n  task_queue->Unmerge(queue_id_1, queue_id_2);\n\n  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_1));\n  ASSERT_EQ(1u, task_queue->GetNumPendingTasks(queue_id_2));\n}\n\n/// Multiple standalone engines scene\nTEST(MessageLoopTaskQueueMergeUnmerge,\n     OneCanOwnMultipleQueuesAndUnmergeIndependently) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n  auto queue_id_3 = task_queue->CreateTaskQueue();\n\n  // merge\n  ASSERT_TRUE(task_queue->Merge(queue_id_1, queue_id_2));\n  ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_2));\n  ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_3));\n\n  ASSERT_TRUE(task_queue->Merge(queue_id_1, queue_id_3));\n  ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_2));\n  ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_3));\n\n  // unmerge\n  ASSERT_TRUE(task_queue->Unmerge(queue_id_1, queue_id_2));\n  ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_2));\n  ASSERT_TRUE(task_queue->Owns(queue_id_1, queue_id_3));\n\n  ASSERT_TRUE(task_queue->Unmerge(queue_id_1, queue_id_3));\n  ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_2));\n  ASSERT_FALSE(task_queue->Owns(queue_id_1, queue_id_3));\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge,\n     CannotMergeSameQueueToTwoDifferentOwners) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue = task_queue->CreateTaskQueue();\n  auto owner_1 = task_queue->CreateTaskQueue();\n  auto owner_2 = task_queue->CreateTaskQueue();\n\n  ASSERT_TRUE(task_queue->Merge(owner_1, queue));\n  ASSERT_FALSE(task_queue->Merge(owner_2, queue));\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge, MergeFailIfAlreadySubsumed) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n  auto queue_id_3 = task_queue->CreateTaskQueue();\n\n  ASSERT_TRUE(task_queue->Merge(queue_id_1, queue_id_2));\n  ASSERT_FALSE(task_queue->Merge(queue_id_2, queue_id_3));\n  ASSERT_FALSE(task_queue->Merge(queue_id_2, queue_id_1));\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge,\n     MergeFailIfAlreadyOwnsButTryToBeSubsumed) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n  auto queue_id_3 = task_queue->CreateTaskQueue();\n\n  task_queue->Merge(queue_id_1, queue_id_2);\n  // A recursively linked merging will fail\n  ASSERT_FALSE(task_queue->Merge(queue_id_3, queue_id_1));\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge, UnmergeFailsOnSubsumedOrNeverMerged) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n  auto queue_id_3 = task_queue->CreateTaskQueue();\n\n  task_queue->Merge(queue_id_1, queue_id_2);\n  ASSERT_FALSE(task_queue->Unmerge(queue_id_2, queue_id_3));\n  ASSERT_FALSE(task_queue->Unmerge(queue_id_1, queue_id_3));\n  ASSERT_FALSE(task_queue->Unmerge(queue_id_3, queue_id_1));\n  ASSERT_FALSE(task_queue->Unmerge(queue_id_2, queue_id_1));\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge, MergeInvokesBothWakeables) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n\n  fml::CountDownLatch latch(2);\n\n  task_queue->SetWakeable(\n      queue_id_1,\n      new TestWakeable([&](fml::TimePoint wake_time) { latch.CountDown(); }));\n  task_queue->SetWakeable(\n      queue_id_2,\n      new TestWakeable([&](fml::TimePoint wake_time) { latch.CountDown(); }));\n\n  task_queue->RegisterTask(\n      queue_id_1, []() {}, ChronoTicksSinceEpoch());\n\n  task_queue->Merge(queue_id_1, queue_id_2);\n\n  CountRemainingTasks(task_queue, queue_id_1);\n\n  latch.Wait();\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge,\n     MergeUnmergeInvokesBothWakeablesSeparately) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n\n  fml::AutoResetWaitableEvent latch_1, latch_2;\n\n  task_queue->SetWakeable(\n      queue_id_1,\n      new TestWakeable([&](fml::TimePoint wake_time) { latch_1.Signal(); }));\n  task_queue->SetWakeable(\n      queue_id_2,\n      new TestWakeable([&](fml::TimePoint wake_time) { latch_2.Signal(); }));\n\n  task_queue->RegisterTask(\n      queue_id_1, []() {}, ChronoTicksSinceEpoch());\n  task_queue->RegisterTask(\n      queue_id_2, []() {}, ChronoTicksSinceEpoch());\n\n  task_queue->Merge(queue_id_1, queue_id_2);\n  task_queue->Unmerge(queue_id_1, queue_id_2);\n\n  CountRemainingTasks(task_queue, queue_id_1);\n\n  latch_1.Wait();\n\n  CountRemainingTasks(task_queue, queue_id_2);\n\n  latch_2.Wait();\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge, DISABLED_GetTasksToRunNowBlocksMerge) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n\n  fml::AutoResetWaitableEvent wake_up_start, wake_up_end, merge_start,\n      merge_end;\n\n  task_queue->SetWakeable(queue_id_1,\n                          new TestWakeable([&](fml::TimePoint wake_time) {\n                            wake_up_start.Signal();\n                            wake_up_end.Wait();\n                          }));\n\n  std::thread register_task_thread([&]() {\n    // Register task on another thread and wake up the queues.\n    task_queue->RegisterTask(\n        queue_id_1, []() {}, ChronoTicksSinceEpoch());\n  });\n\n  std::thread tasks_to_run_now_thread([&]() {\n    // Fetch task previously added with task_queue's queue_mutex_.\n    CountRemainingTasks(task_queue, queue_id_1);\n  });\n\n  wake_up_start.Wait();\n  bool merge_done = false;\n\n  std::thread merge_thread([&]() {\n    merge_start.Signal();\n    /*\n     * Merge requires task_queue's queue_mutex_.\n     * Before wake_up_end.Signal() called, queue_mutex_\n     * is held by task_queue_->WakeUp.\n     */\n    task_queue->Merge(queue_id_1, queue_id_2);\n    merge_done = true;\n    merge_end.Signal();\n  });\n\n  merge_start.Wait();\n  ASSERT_FALSE(merge_done);\n  wake_up_end.Signal();\n\n  merge_end.Wait();\n  ASSERT_TRUE(merge_done);\n\n  register_task_thread.join();\n  tasks_to_run_now_thread.join();\n  merge_thread.join();\n}\n\nTEST(MessageLoopTaskQueueMergeUnmerge,\n     FollowingTasksSwitchQueueIfFirstTaskMergesThreads) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n\n  auto queue_id_1 = task_queue->CreateTaskQueue();\n  auto queue_id_2 = task_queue->CreateTaskQueue();\n\n  fml::CountDownLatch latch(2);\n\n  task_queue->SetWakeable(\n      queue_id_1,\n      new TestWakeable([&](fml::TimePoint wake_time) { latch.CountDown(); }));\n  task_queue->SetWakeable(\n      queue_id_2,\n      new TestWakeable([&](fml::TimePoint wake_time) { latch.CountDown(); }));\n\n  task_queue->RegisterTask(\n      queue_id_2, [&]() { task_queue->Merge(queue_id_1, queue_id_2); },\n      ChronoTicksSinceEpoch());\n\n  task_queue->RegisterTask(\n      queue_id_2, []() {}, ChronoTicksSinceEpoch());\n\n  ASSERT_EQ(CountRemainingTasks(task_queue, queue_id_2, true), 1);\n  ASSERT_EQ(CountRemainingTasks(task_queue, queue_id_1, true), 1);\n\n  latch.Wait();\n}\n\n}  // namespace testing\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/message_loop_task_queues_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <vector>\n#define FML_USED_ON_EMBEDDER\n\n#include <algorithm>\n#include <cstdlib>\n#include <thread>\n\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/time/chrono_timestamp_provider.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace testing {\n\nclass TestWakeable : public fml::Wakeable {\n public:\n  using WakeUpCall = std::function<void(const fml::TimePoint)>;\n\n  explicit TestWakeable(WakeUpCall call) : wake_up_call_(call) {}\n\n  void WakeUp(fml::TimePoint time_point, bool is_woken_by_vsync) override {\n    wake_up_call_(time_point);\n  }\n\n private:\n  WakeUpCall wake_up_call_;\n};\n\nTEST(MessageLoopTaskQueue, StartsWithNoPendingTasks) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue_id = task_queue->CreateTaskQueue();\n  ASSERT_FALSE(task_queue->HasPendingTasks(queue_id));\n}\n\nTEST(MessageLoopTaskQueue, RegisterOneTask) {\n  const auto time = fml::TimePoint::Max();\n\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue_id = task_queue->CreateTaskQueue();\n  task_queue->SetWakeable(queue_id,\n                          new TestWakeable([&time](fml::TimePoint wake_time) {\n                            ASSERT_TRUE(wake_time == time);\n                          }));\n\n  task_queue->RegisterTask(\n      queue_id, [] {}, time);\n  ASSERT_TRUE(task_queue->HasPendingTasks(queue_id));\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(queue_id) == 1);\n}\n\nTEST(MessageLoopTaskQueue, RegisterTwoTasksAndCount) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue_id = task_queue->CreateTaskQueue();\n  task_queue->RegisterTask(\n      queue_id, [] {}, ChronoTicksSinceEpoch());\n  task_queue->RegisterTask(\n      queue_id, [] {}, fml::TimePoint::Max());\n  ASSERT_TRUE(task_queue->HasPendingTasks(queue_id));\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(queue_id) == 2);\n}\n\nTEST(MessageLoopTaskQueue, RegisterTasksOnMergedQueuesAndCount) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto platform_queue = task_queue->CreateTaskQueue();\n  auto raster_queue = task_queue->CreateTaskQueue();\n  // A task in platform_queue\n  task_queue->RegisterTask(\n      platform_queue, []() {}, fml::TimePoint::Now());\n  // A task in raster_queue\n  task_queue->RegisterTask(\n      raster_queue, []() {}, fml::TimePoint::Now());\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 1);\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 1);\n\n  ASSERT_FALSE(task_queue->Owns(platform_queue, raster_queue));\n  task_queue->Merge(platform_queue, raster_queue);\n  ASSERT_TRUE(task_queue->Owns(platform_queue, raster_queue));\n  ASSERT_TRUE(task_queue->IsSubsumed(raster_queue));\n\n  ASSERT_TRUE(task_queue->HasPendingTasks(platform_queue));\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 2);\n  // The task count of subsumed queue is 0\n  ASSERT_FALSE(task_queue->HasPendingTasks(raster_queue));\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 0);\n\n  task_queue->Unmerge(platform_queue, raster_queue);\n  ASSERT_FALSE(task_queue->Owns(platform_queue, raster_queue));\n  ASSERT_FALSE(task_queue->IsSubsumed(raster_queue));\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 1);\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 1);\n}\n\nTEST(MessageLoopTaskQueue, PreserveTaskOrdering) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue_id = task_queue->CreateTaskQueue();\n  int test_val = 0;\n\n  // order: 0\n  task_queue->RegisterTask(\n      queue_id, [&test_val]() { test_val = 1; }, ChronoTicksSinceEpoch());\n\n  // order: 1\n  task_queue->RegisterTask(\n      queue_id, [&test_val]() { test_val = 2; }, ChronoTicksSinceEpoch());\n\n  const auto now = ChronoTicksSinceEpoch();\n  int expected_value = 1;\n  std::vector<TaskQueueId> queue_ids = {queue_id};\n  while (true) {\n    auto invocation = task_queue->GetNextTaskToRun(queue_ids, now);\n    if (!invocation) {\n      break;\n    }\n    invocation();\n    ASSERT_TRUE(test_val == expected_value);\n    expected_value++;\n  }\n}\n\nTEST(MessageLoopTaskQueue, RegisterTasksOnMergedQueuesPreserveTaskOrdering) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto platform_queue = task_queue->CreateTaskQueue();\n  auto raster1_queue = task_queue->CreateTaskQueue();\n  auto raster2_queue = task_queue->CreateTaskQueue();\n  int test_val = 0;\n\n  // order 0 in raster1_queue\n  task_queue->RegisterTask(\n      raster1_queue, [&test_val]() { test_val = 0; }, fml::TimePoint::Now());\n\n  // order 1 in platform_queue\n  task_queue->RegisterTask(\n      platform_queue, [&test_val]() { test_val = 1; }, fml::TimePoint::Now());\n\n  // order 2 in raster2_queue\n  task_queue->RegisterTask(\n      raster2_queue, [&test_val]() { test_val = 2; }, fml::TimePoint::Now());\n\n  task_queue->Merge(platform_queue, raster1_queue);\n  ASSERT_TRUE(task_queue->Owns(platform_queue, raster1_queue));\n  ASSERT_TRUE(task_queue->IsSubsumed(raster1_queue));\n  task_queue->Merge(platform_queue, raster2_queue);\n  ASSERT_TRUE(task_queue->Owns(platform_queue, raster2_queue));\n  ASSERT_TRUE(task_queue->IsSubsumed(raster2_queue));\n  const auto now = fml::TimePoint::Now();\n  int expected_value = 0;\n  // Right order:\n  // \"test_val = 0\" in raster1_queue\n  // \"test_val = 1\" in platform_queue\n  // \"test_val = 2\" in raster2_queue\n  std::vector<TaskQueueId> queue_ids = {platform_queue};\n  while (true) {\n    auto invocation = task_queue->GetNextTaskToRun(queue_ids, now);\n    if (!invocation) {\n      break;\n    }\n    invocation();\n    ASSERT_TRUE(test_val == expected_value);\n    expected_value++;\n  }\n}\n\nTEST(MessageLoopTaskQueue, UnmergeRespectTheOriginalTaskOrderingInQueues) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto platform_queue = task_queue->CreateTaskQueue();\n  auto raster_queue = task_queue->CreateTaskQueue();\n  int test_val = 0;\n\n  // order 0 in platform_queue\n  task_queue->RegisterTask(\n      platform_queue, [&test_val]() { test_val = 0; }, fml::TimePoint::Now());\n  // order 1 in platform_queue\n  task_queue->RegisterTask(\n      platform_queue, [&test_val]() { test_val = 1; }, fml::TimePoint::Now());\n  // order 2 in raster_queue\n  task_queue->RegisterTask(\n      raster_queue, [&test_val]() { test_val = 2; }, fml::TimePoint::Now());\n  // order 3 in raster_queue\n  task_queue->RegisterTask(\n      raster_queue, [&test_val]() { test_val = 3; }, fml::TimePoint::Now());\n  // order 4 in platform_queue\n  task_queue->RegisterTask(\n      platform_queue, [&test_val]() { test_val = 4; }, fml::TimePoint::Now());\n  // order 5 in raster_queue\n  task_queue->RegisterTask(\n      raster_queue, [&test_val]() { test_val = 5; }, fml::TimePoint::Now());\n\n  ASSERT_TRUE(task_queue->Merge(platform_queue, raster_queue));\n  ASSERT_TRUE(task_queue->Owns(platform_queue, raster_queue));\n  ASSERT_TRUE(task_queue->IsSubsumed(raster_queue));\n  const auto now = fml::TimePoint::Now();\n  // The right order after merged and consumed 3 tasks:\n  // \"test_val = 0\" in platform_queue\n  // \"test_val = 1\" in platform_queue\n  // \"test_val = 2\" in raster_queue (running on platform)\n  std::vector<TaskQueueId> queue_ids = {platform_queue};\n  for (int i = 0; i < 3; i++) {\n    auto invocation = task_queue->GetNextTaskToRun(queue_ids, now);\n    ASSERT_FALSE(!invocation);\n    invocation();\n    ASSERT_TRUE(test_val == i);\n  }\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 3);\n  ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 0);\n\n  ASSERT_TRUE(task_queue->Unmerge(platform_queue, raster_queue));\n  ASSERT_FALSE(task_queue->Owns(platform_queue, raster_queue));\n  ASSERT_FALSE(task_queue->IsSubsumed(raster_queue));\n\n  // The right order after unmerged and left 3 tasks:\n  // \"test_val = 3\" in raster_queue\n  // \"test_val = 4\" in platform_queue\n  // \"test_val = 5\" in raster_queue\n\n  // platform_queue has 1 task left: \"test_val = 4\"\n  {\n    ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 1);\n    std::vector<TaskQueueId> queue_ids = {platform_queue};\n    auto invocation = task_queue->GetNextTaskToRun(queue_ids, now);\n    ASSERT_FALSE(!invocation);\n    invocation();\n    ASSERT_TRUE(test_val == 4);\n    ASSERT_TRUE(task_queue->GetNumPendingTasks(platform_queue) == 0);\n  }\n\n  // raster_queue has 2 tasks left: \"test_val = 3\" and \"test_val = 5\"\n  {\n    ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 2);\n    std::vector<TaskQueueId> queue_ids = {raster_queue};\n    auto invocation = task_queue->GetNextTaskToRun(queue_ids, now);\n    ASSERT_FALSE(!invocation);\n    invocation();\n    ASSERT_TRUE(test_val == 3);\n  }\n  {\n    ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 1);\n    std::vector<TaskQueueId> queue_ids = {raster_queue};\n    auto invocation = task_queue->GetNextTaskToRun(queue_ids, now);\n    ASSERT_FALSE(!invocation);\n    invocation();\n    ASSERT_TRUE(test_val == 5);\n    ASSERT_TRUE(task_queue->GetNumPendingTasks(raster_queue) == 0);\n  }\n}\n\nvoid TestNotifyObservers(fml::TaskQueueId queue_id) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto observers = task_queue->GetObserversToNotify(queue_id);\n  for (const auto& observer : observers) {\n    (*observer)();\n  }\n}\n\nTEST(MessageLoopTaskQueue, AddRemoveNotifyObservers) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue_id = task_queue->CreateTaskQueue();\n\n  int test_val = 0;\n  intptr_t key = 123;\n\n  task_queue->AddTaskObserver(queue_id, key, [&test_val]() { test_val = 1; });\n  TestNotifyObservers(queue_id);\n  ASSERT_TRUE(test_val == 1);\n\n  test_val = 0;\n  task_queue->RemoveTaskObserver(queue_id, key);\n  TestNotifyObservers(queue_id);\n  ASSERT_TRUE(test_val == 0);\n}\n\nTEST(MessageLoopTaskQueue, WakeUpIndependentOfTime) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue_id = task_queue->CreateTaskQueue();\n\n  int num_wakes = 0;\n  task_queue->SetWakeable(\n      queue_id, new TestWakeable(\n                    [&num_wakes](fml::TimePoint wake_time) { ++num_wakes; }));\n\n  task_queue->RegisterTask(\n      queue_id, []() {}, ChronoTicksSinceEpoch());\n  task_queue->RegisterTask(\n      queue_id, []() {}, fml::TimePoint::Max());\n\n  ASSERT_TRUE(num_wakes == 2);\n}\n\nTEST(MessageLoopTaskQueue, WokenUpWithNewerTime) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue_id = task_queue->CreateTaskQueue();\n  fml::CountDownLatch latch(2);\n\n  fml::TimePoint expected = fml::TimePoint::Max();\n\n  task_queue->SetWakeable(\n      queue_id, new TestWakeable([&latch, &expected](fml::TimePoint wake_time) {\n        ASSERT_TRUE(wake_time == expected);\n        latch.CountDown();\n      }));\n\n  task_queue->RegisterTask(\n      queue_id, []() {}, fml::TimePoint::Max());\n\n  const auto now = ChronoTicksSinceEpoch();\n  expected = now;\n  task_queue->RegisterTask(\n      queue_id, []() {}, now);\n\n  latch.Wait();\n}\n\nTEST(MessageLoopTaskQueue, NotifyObserversWhileCreatingQueues) {\n  auto task_queues = fml::MessageLoopTaskQueues::GetInstance();\n  fml::TaskQueueId queue_id = task_queues->CreateTaskQueue();\n  fml::AutoResetWaitableEvent first_observer_executing, before_second_observer;\n\n  task_queues->AddTaskObserver(queue_id, queue_id + 1, [&]() {\n    first_observer_executing.Signal();\n    before_second_observer.Wait();\n  });\n\n  for (int i = 0; i < 100; i++) {\n    task_queues->AddTaskObserver(queue_id, queue_id + i + 2, [] {});\n  }\n\n  std::thread notify_observers([&]() { TestNotifyObservers(queue_id); });\n\n  first_observer_executing.Wait();\n\n  for (int i = 0; i < 100; i++) {\n    task_queues->CreateTaskQueue();\n  }\n\n  before_second_observer.Signal();\n  notify_observers.join();\n}\n\nTEST(MessageLoopTaskQueue, QueueDoNotOwnItself) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto queue_id = task_queue->CreateTaskQueue();\n  ASSERT_FALSE(task_queue->Owns(queue_id, queue_id));\n}\n\nTEST(MessageLoopTaskQueue, QueueDoNotOwnUnmergedTaskQueueId) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  ASSERT_FALSE(task_queue->Owns(task_queue->CreateTaskQueue(), _kUnmerged));\n  ASSERT_FALSE(task_queue->Owns(_kUnmerged, task_queue->CreateTaskQueue()));\n  ASSERT_FALSE(task_queue->Owns(_kUnmerged, _kUnmerged));\n}\n\nTEST(MessageLoopTaskQueue, QueueOwnsMergedTaskQueueId) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto platform_queue = task_queue->CreateTaskQueue();\n  auto raster_queue = task_queue->CreateTaskQueue();\n  ASSERT_FALSE(task_queue->Owns(platform_queue, raster_queue));\n  ASSERT_FALSE(task_queue->Owns(raster_queue, platform_queue));\n  task_queue->Merge(platform_queue, raster_queue);\n  ASSERT_TRUE(task_queue->Owns(platform_queue, raster_queue));\n  ASSERT_FALSE(task_queue->Owns(raster_queue, platform_queue));\n  ASSERT_TRUE(task_queue->IsSubsumed(raster_queue));\n}\n\n//------------------------------------------------------------------------------\n/// Verifies that tasks can be added to task queues concurrently.\n///\nTEST(MessageLoopTaskQueue, ConcurrentQueueAndTaskCreatingCounts) {\n  auto task_queues = fml::MessageLoopTaskQueues::GetInstance();\n\n  // kThreadCount threads post kThreadTaskCount tasks each to kTaskQueuesCount\n  // task queues. Each thread picks a task queue randomly for each task.\n  constexpr size_t kThreadCount = 4;\n  constexpr size_t kTaskQueuesCount = 2;\n  constexpr size_t kThreadTaskCount = 500;\n\n  std::vector<TaskQueueId> task_queue_ids;\n  for (size_t i = 0; i < kTaskQueuesCount; ++i) {\n    task_queue_ids.emplace_back(task_queues->CreateTaskQueue());\n  }\n\n  ASSERT_EQ(task_queue_ids.size(), kTaskQueuesCount);\n\n  fml::CountDownLatch tasks_posted_latch(kThreadCount);\n\n  auto thread_main = [&]() {\n    for (size_t i = 0; i < kThreadTaskCount; i++) {\n      const auto current_task_queue_id =\n          task_queue_ids[std::rand() % kTaskQueuesCount];\n      const auto empty_task = []() {};\n      // The timepoint doesn't matter as the queue is never going to be drained.\n      const auto task_timepoint = ChronoTicksSinceEpoch();\n\n      task_queues->RegisterTask(current_task_queue_id, std::move(empty_task),\n                                task_timepoint);\n    }\n\n    tasks_posted_latch.CountDown();\n  };\n\n  std::vector<std::thread> threads;\n\n  for (size_t i = 0; i < kThreadCount; i++) {\n    threads.emplace_back(std::thread{thread_main});\n  }\n\n  ASSERT_EQ(threads.size(), kThreadCount);\n\n  for (size_t i = 0; i < kThreadCount; i++) {\n    threads[i].join();\n  }\n\n  // All tasks have been posted by now. Check that they are all pending.\n\n  size_t pending_tasks = 0u;\n  std::for_each(task_queue_ids.begin(), task_queue_ids.end(),\n                [&](const auto& queue) {\n                  pending_tasks += task_queues->GetNumPendingTasks(queue);\n                });\n\n  ASSERT_EQ(pending_tasks, kThreadCount * kThreadTaskCount);\n}\n\nTEST(MessageLoopTaskQueue, RegisterTaskWakesUpOwnerQueue) {\n  auto task_queue = fml::MessageLoopTaskQueues::GetInstance();\n  auto platform_queue = task_queue->CreateTaskQueue();\n  auto raster_queue = task_queue->CreateTaskQueue();\n\n  std::vector<fml::TimePoint> wakes;\n\n  task_queue->SetWakeable(platform_queue,\n                          new TestWakeable([&wakes](fml::TimePoint wake_time) {\n                            wakes.push_back(wake_time);\n                          }));\n\n  task_queue->SetWakeable(raster_queue,\n                          new TestWakeable([](fml::TimePoint wake_time) {\n                            // The raster queue is owned by the platform queue.\n                            ASSERT_FALSE(true);\n                          }));\n\n  auto time1 = ChronoTicksSinceEpoch() + fml::TimeDelta::FromMilliseconds(1);\n  auto time2 = ChronoTicksSinceEpoch() + fml::TimeDelta::FromMilliseconds(2);\n\n  ASSERT_EQ(0UL, wakes.size());\n\n  task_queue->RegisterTask(\n      platform_queue, []() {}, time1);\n\n  ASSERT_EQ(1UL, wakes.size());\n  ASSERT_EQ(time1, wakes[0]);\n\n  task_queue->Merge(platform_queue, raster_queue);\n  ASSERT_TRUE(task_queue->IsSubsumed(raster_queue));\n\n  task_queue->RegisterTask(\n      raster_queue, []() {}, time2);\n\n  ASSERT_EQ(3UL, wakes.size());\n  ASSERT_EQ(time1, wakes[1]);\n  ASSERT_EQ(time1, wakes[2]);\n}\n\n}  // namespace testing\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/message_loop_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include <iostream>\n#include <thread>\n\n#include \"base/include/fml/concurrent_message_loop.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/time/chrono_timestamp_provider.h\"\n#include \"build/build_config.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#define TIMESENSITIVE(x) TimeSensitiveTest_##x\n#if OS_WIN\n#define PLATFORM_SPECIFIC_CAPTURE(...) [ __VA_ARGS__, count ]\n#else\n#define PLATFORM_SPECIFIC_CAPTURE(...) [__VA_ARGS__]\n#endif\n\nnamespace lynx {\n\nTEST(MessageLoop, GetCurrent) {\n  std::thread thread([]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    ASSERT_TRUE(fml::MessageLoop::GetCurrent().GetTaskRunner());\n  });\n  thread.join();\n}\n\nTEST(MessageLoop, DifferentThreadsHaveDifferentLoops) {\n  fml::MessageLoop* loop1 = nullptr;\n  fml::AutoResetWaitableEvent latch1;\n  fml::AutoResetWaitableEvent term1;\n  std::thread thread1([&loop1, &latch1, &term1]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop1 = &fml::MessageLoop::GetCurrent();\n    latch1.Signal();\n    term1.Wait();\n  });\n\n  fml::MessageLoop* loop2 = nullptr;\n  fml::AutoResetWaitableEvent latch2;\n  fml::AutoResetWaitableEvent term2;\n  std::thread thread2([&loop2, &latch2, &term2]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop2 = &fml::MessageLoop::GetCurrent();\n    latch2.Signal();\n    term2.Wait();\n  });\n  latch1.Wait();\n  latch2.Wait();\n  ASSERT_FALSE(loop1 == loop2);\n  term1.Signal();\n  term2.Signal();\n  thread1.join();\n  thread2.join();\n}\n\nTEST(MessageLoop, CanRunAndTerminate) {\n  bool started = false;\n  bool terminated = false;\n  std::thread thread([&started, &terminated]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    ASSERT_TRUE(loop.GetTaskRunner());\n    loop.GetTaskRunner()->PostTask([&terminated]() {\n      fml::MessageLoop::GetCurrent().Terminate();\n      terminated = true;\n    });\n    loop.Run();\n    started = true;\n  });\n  thread.join();\n  ASSERT_TRUE(started);\n  ASSERT_TRUE(terminated);\n}\n\nTEST(MessageLoop, NonDelayedTasksAreRunInOrder) {\n  const size_t count = 100;\n  bool started = false;\n  bool terminated = false;\n  std::thread thread([&started, &terminated, count]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    size_t current = 0;\n    for (size_t i = 0; i < count; i++) {\n      loop.GetTaskRunner()->PostTask(\n          PLATFORM_SPECIFIC_CAPTURE(&terminated, i, &current)() {\n            ASSERT_EQ(current, i);\n            current++;\n            if (count == i + 1) {\n              fml::MessageLoop::GetCurrent().Terminate();\n              terminated = true;\n            }\n          });\n    }\n    loop.Run();\n    ASSERT_EQ(current, count);\n    started = true;\n  });\n  thread.join();\n  ASSERT_TRUE(started);\n  ASSERT_TRUE(terminated);\n}\n\nTEST(MessageLoop, DelayedTasksAtSameTimeAreRunInOrder) {\n  const size_t count = 100;\n  bool started = false;\n  bool terminated = false;\n  std::thread thread([&started, &terminated, count]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    size_t current = 0;\n    const auto now_plus_some =\n        fml::ChronoTicksSinceEpoch() + fml::TimeDelta::FromMilliseconds(2);\n    for (size_t i = 0; i < count; i++) {\n      loop.GetTaskRunner()->PostTaskForTime(\n          PLATFORM_SPECIFIC_CAPTURE(&terminated, i, &current)() {\n            ASSERT_EQ(current, i);\n            current++;\n            if (count == i + 1) {\n              fml::MessageLoop::GetCurrent().Terminate();\n              terminated = true;\n            }\n          },\n          now_plus_some);\n    }\n    loop.Run();\n    ASSERT_EQ(current, count);\n    started = true;\n  });\n  thread.join();\n  ASSERT_TRUE(started);\n  ASSERT_TRUE(terminated);\n}\n\nTEST(MessageLoop, CheckRunsTaskOnCurrentThread) {\n  fml::RefPtr<fml::TaskRunner> runner;\n  fml::AutoResetWaitableEvent latch;\n  std::thread thread([&runner, &latch]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    runner = loop.GetTaskRunner();\n    latch.Signal();\n    ASSERT_TRUE(loop.GetTaskRunner()->RunsTasksOnCurrentThread());\n  });\n  latch.Wait();\n  ASSERT_TRUE(runner);\n  ASSERT_FALSE(runner->RunsTasksOnCurrentThread());\n  thread.join();\n}\n\nTEST(MessageLoop, TIMESENSITIVE(SingleDelayedTaskByDelta)) {\n  bool checked = false;\n  std::thread thread([&checked]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    auto begin = fml::ChronoTicksSinceEpoch();\n    loop.GetTaskRunner()->PostDelayedTask(\n        [begin, &checked]() {\n          auto delta = fml::ChronoTicksSinceEpoch() - begin;\n          auto ms = delta.ToMillisecondsF();\n          ASSERT_GE(ms, 3);\n          ASSERT_LE(ms, 7);\n          checked = true;\n          fml::MessageLoop::GetCurrent().Terminate();\n        },\n        fml::TimeDelta::FromMilliseconds(5));\n    loop.Run();\n  });\n  thread.join();\n  ASSERT_TRUE(checked);\n}\n\nTEST(MessageLoop, TIMESENSITIVE(SingleDelayedTaskForTime)) {\n  bool checked = false;\n  std::thread thread([&checked]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    auto begin = fml::ChronoTicksSinceEpoch();\n    loop.GetTaskRunner()->PostTaskForTime(\n        [begin, &checked]() {\n          auto delta = fml::ChronoTicksSinceEpoch() - begin;\n          auto ms = delta.ToMillisecondsF();\n          ASSERT_GE(ms, 3);\n          ASSERT_LE(ms, 7);\n          checked = true;\n          fml::MessageLoop::GetCurrent().Terminate();\n        },\n        fml::ChronoTicksSinceEpoch() + fml::TimeDelta::FromMilliseconds(5));\n    loop.Run();\n  });\n  thread.join();\n  ASSERT_TRUE(checked);\n}\n\nTEST(MessageLoop, TIMESENSITIVE(MultipleDelayedTasksWithIncreasingDeltas)) {\n  const auto count = 10;\n  int checked = false;\n  std::thread thread(PLATFORM_SPECIFIC_CAPTURE(&checked)() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    for (int target_ms = 0 + 2; target_ms < count + 2; target_ms++) {\n      auto begin = fml::ChronoTicksSinceEpoch();\n      loop.GetTaskRunner()->PostDelayedTask(\n          PLATFORM_SPECIFIC_CAPTURE(begin, target_ms, &checked)() {\n            auto delta = fml::ChronoTicksSinceEpoch() - begin;\n            auto ms = delta.ToMillisecondsF();\n            ASSERT_GE(ms, target_ms - 2);\n            ASSERT_LE(ms, target_ms + 2);\n            checked++;\n            if (checked == count) {\n              fml::MessageLoop::GetCurrent().Terminate();\n            }\n          },\n          fml::TimeDelta::FromMilliseconds(target_ms));\n    }\n    loop.Run();\n  });\n  thread.join();\n  ASSERT_EQ(checked, count);\n}\n\nTEST(MessageLoop, TIMESENSITIVE(MultipleDelayedTasksWithDecreasingDeltas)) {\n  const auto count = 10;\n  int checked = false;\n  std::thread thread(PLATFORM_SPECIFIC_CAPTURE(&checked)() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    for (int target_ms = count + 2; target_ms > 0 + 2; target_ms--) {\n      auto begin = fml::ChronoTicksSinceEpoch();\n      loop.GetTaskRunner()->PostDelayedTask(\n          PLATFORM_SPECIFIC_CAPTURE(begin, target_ms, &checked)() {\n            auto delta = fml::ChronoTicksSinceEpoch() - begin;\n            auto ms = delta.ToMillisecondsF();\n            ASSERT_GE(ms, target_ms - 2);\n            ASSERT_LE(ms, target_ms + 2);\n            checked++;\n            if (checked == count) {\n              fml::MessageLoop::GetCurrent().Terminate();\n            }\n          },\n          fml::TimeDelta::FromMilliseconds(target_ms));\n    }\n    loop.Run();\n  });\n  thread.join();\n  ASSERT_EQ(checked, count);\n}\n\nTEST(MessageLoop, TaskObserverFire) {\n  bool started = false;\n  bool terminated = false;\n  std::thread thread([&started, &terminated]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    const size_t count = 25;\n    auto& loop = fml::MessageLoop::GetCurrent();\n    size_t task_count = 0;\n    size_t obs_count = 0;\n    auto obs = PLATFORM_SPECIFIC_CAPTURE(&obs_count)() { obs_count++; };\n    for (size_t i = 0; i < count; i++) {\n      loop.GetTaskRunner()->PostTask(\n          PLATFORM_SPECIFIC_CAPTURE(&terminated, i, &task_count)() {\n            ASSERT_EQ(task_count, i);\n            task_count++;\n            if (count == i + 1) {\n              fml::MessageLoop::GetCurrent().Terminate();\n              terminated = true;\n            }\n          });\n    }\n    loop.GetTaskRunner()->AddTaskObserver(0, std::move(obs));\n    loop.Run();\n    ASSERT_EQ(task_count, count);\n    ASSERT_EQ(obs_count, count);\n    started = true;\n  });\n  thread.join();\n  ASSERT_TRUE(started);\n  ASSERT_TRUE(terminated);\n}\n\nTEST(MessageLoop, ConcurrentMessageLoopHasNonZeroWorkers) {\n  auto loop =\n      fml::ConcurrentMessageLoop(\"\", fml::Thread::ThreadPriority::NORMAL,\n                                 0u /* explicitly specify zero workers */);\n  ASSERT_GT(loop.GetWorkerCount(), 0u);\n}\n\nTEST(MessageLoop,\n     DISABLED_CanCreateAndShutdownConcurrentMessageLoopsOverAndOver) {\n  for (size_t i = 0; i < 10; ++i) {\n    auto loop = fml::ConcurrentMessageLoop(\n        \"\", fml::Thread::ThreadPriority::NORMAL, i + 1);\n    ASSERT_EQ(loop.GetWorkerCount(), i + 1);\n  }\n}\n\nTEST(MessageLoop, CanCreateConcurrentMessageLoop) {\n  auto loop = fml::ConcurrentMessageLoop(\"\");\n  const size_t kCount = 10;\n  fml::CountDownLatch latch(kCount);\n  std::mutex thread_ids_mutex;\n  std::set<std::thread::id> thread_ids;\n  for (size_t i = 0; i < kCount; ++i) {\n    loop.PostTask([&]() {\n      std::this_thread::sleep_for(std::chrono::seconds(1));\n      std::cout << \"Ran on thread: \" << std::this_thread::get_id() << std::endl;\n      std::scoped_lock lock(thread_ids_mutex);\n      thread_ids.insert(std::this_thread::get_id());\n      latch.CountDown();\n    });\n  }\n  latch.Wait();\n  ASSERT_GE(thread_ids.size(), 1u);\n}\n\n#if !defined(OS_WIN)\nstatic void MockThreadConfigSetter(const fml::Thread::ThreadConfig& config) {\n  // set thread name\n  fml::Thread::SetCurrentThreadName(config);\n\n  pthread_t tid = pthread_self();\n  struct sched_param param;\n  int policy = SCHED_OTHER;\n  switch (config.priority) {\n    case fml::Thread::ThreadPriority::HIGH:\n      param.sched_priority = 10;\n      break;\n    default:\n      param.sched_priority = 1;\n  }\n    // In linx system, sched_priority can only be set to 0 with SCHED_OTHER\n    // policy.\n#if defined(OS_LINUX)\n  param.sched_priority = 0;\n#endif\n  pthread_setschedparam(tid, policy, &param);\n}\n\nTEST(MessageLoop, CreateConcurrentMessageLoopWithThreadConfigSetter) {\n  auto loop = fml::ConcurrentMessageLoop(\"test\", MockThreadConfigSetter,\n                                         fml::Thread::ThreadPriority::HIGH, 1);\n  const size_t kCount = 1;\n  fml::CountDownLatch latch(kCount);\n  for (size_t i = 0; i < kCount; ++i) {\n    loop.PostTask([&]() {\n      std::cout << \"Thread created\" << std::endl;\n      struct sched_param param;\n      char thread_name[16];\n      int policy = SCHED_OTHER;\n      pthread_t current_thread = pthread_self();\n      std::cout << \"Start get thread name\" << std::endl;\n      pthread_getname_np(current_thread, thread_name, sizeof(thread_name));\n      std::cout << \"Start get thread parameters\" << std::endl;\n      pthread_getschedparam(current_thread, &policy, &param);\n      std::cout << \"Compare thread info\" << std::endl;\n      const std::string name = \"test1\";\n      ASSERT_EQ(thread_name, name);\n      ASSERT_EQ(policy, SCHED_OTHER);\n      // In linx system, sched_priority can only be set to 0 with SCHED_OTHER\n      // policy.\n#if !defined(OS_LINUX)\n      ASSERT_EQ(param.sched_priority, 10);\n#else\n      ASSERT_EQ(param.sched_priority, 0);\n#endif\n      latch.CountDown();\n    });\n  }\n  latch.Wait();\n}\n#endif\n\nTEST(MessageLoop, PostEmergencyTask) {\n  constexpr uint32_t count = 10;\n  std::string str;\n  std::thread thread([&str]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    for (uint32_t i = 0; i < count; ++i) {\n      loop.GetTaskRunner()->PostTask([&str, i]() {\n        str.push_back('0' + i);\n        if (i + 1 == count) {\n          fml::MessageLoop::GetCurrent().Terminate();\n        }\n      });\n    }\n    loop.GetTaskRunner()->PostEmergencyTask([&str]() { str.push_back('a'); });\n    loop.Run();\n  });\n  thread.join();\n  ASSERT_EQ(str, \"a0123456789\");\n}\n\nTEST(MessageLoop, PostIdleTaskNotInIdlePeriod) {\n  constexpr uint32_t count = 10;\n  std::string str;\n  std::thread thread([&str]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    loop.GetTaskRunner()->PostIdleTask([&str]() { str.push_back('a'); });\n    for (uint32_t i = 0; i < count; ++i) {\n      loop.GetTaskRunner()->PostTask([&str, i]() {\n        str.push_back('0' + i);\n        if (i + 1 == count) {\n          fml::MessageLoop::GetCurrent().Terminate();\n        }\n      });\n    }\n    loop.GetTaskRunner()->PostIdleTask([&str]() { str.push_back('b'); });\n    loop.Run();\n  });\n  thread.join();\n  ASSERT_EQ(str, \"0123456789ab\");\n}\n\nTEST(MessageLoop, TIMESENSITIVE(PostIdleTaskInIdlePeriod)) {\n  constexpr uint32_t count = 10;\n  std::string str;\n  std::thread thread([&str]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    for (uint32_t i = 0; i < count; ++i) {\n      loop.GetTaskRunner()->PostDelayedTask(\n          [&str, i]() {\n            str.push_back('0' + i);\n            if (i + 1 == count) {\n              fml::MessageLoop::GetCurrent().Terminate();\n            }\n          },\n          fml::TimeDelta::FromMilliseconds(100));\n    }\n    loop.GetTaskRunner()->PostIdleTask([&str]() { str.push_back('a'); });\n    loop.Run();\n  });\n  thread.join();\n  ASSERT_EQ(str, \"a0123456789\");\n}\n\nTEST(MessageLoop, PostSyncTask) {\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  fml::MessageLoop* async = nullptr;\n  fml::AutoResetWaitableEvent latch;\n\n  std::thread thread([&async, &latch]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    async = &loop;\n    latch.Signal();\n\n    // on current loop, need run immediately\n    bool is_run_immediately = false;\n    loop.GetTaskRunner()->PostSyncTask(\n        [&is_run_immediately]() { is_run_immediately = true; });\n    ASSERT_TRUE(is_run_immediately);\n\n    loop.Run();\n  });\n\n  latch.Wait();\n  bool ret = false;\n  async->GetTaskRunner()->PostSyncTask([&ret, async]() {\n    ret = async == &(fml::MessageLoop::GetCurrent());\n    fml::MessageLoop::GetCurrent().Terminate();\n  });\n  ASSERT_TRUE(ret);\n  thread.join();\n}\n\nTEST(MessageLoop, PostMicroTask) {\n  constexpr uint32_t count = 10;\n  std::string str;\n  std::thread thread([&str]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    auto& loop = fml::MessageLoop::GetCurrent();\n    for (uint32_t i = 0; i < count; ++i) {\n      loop.GetTaskRunner()->PostTask([&str, i]() {\n        str.push_back('0' + i);\n        if (i + 1 == count) {\n          fml::MessageLoop::GetCurrent().Terminate();\n        }\n      });\n    }\n    loop.GetTaskRunner()->PostMicroTask([&str]() { str.push_back('a'); });\n    loop.GetTaskRunner()->PostEmergencyTask([&str]() { str.push_back('b'); });\n    loop.GetTaskRunner()->PostMicroTask([&str]() { str.push_back('c'); });\n    loop.Run();\n  });\n  thread.join();\n  ASSERT_EQ(str, \"acb0123456789\");\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/android/cpu_affinity.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/android/cpu_affinity.h\"\n\n#include <pthread.h>\n#include <sys/resource.h>\n#include <sys/time.h>\n#include <unistd.h>\n\n#include <mutex>\n#include <optional>\n#include <thread>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace fml {\n\n/// The CPUSpeedTracker is initialized once the first time a thread affinity is\n/// requested.\nstd::once_flag gCPUTrackerFlag;\nstatic CPUSpeedTracker* gCPUTracker;\n\n// For each CPU index provided, attempts to open the file\n// /sys/devices/system/cpu/cpu$NUM/cpufreq/cpuinfo_max_freq and parse a number\n// containing the CPU frequency.\nvoid InitCPUInfo(size_t cpu_count) {\n  std::vector<CpuIndexAndSpeed> cpu_speeds;\n\n  for (auto i = 0u; i < cpu_count; i++) {\n    auto path = \"/sys/devices/system/cpu/cpu\" + std::to_string(i) +\n                \"/cpufreq/cpuinfo_max_freq\";\n    auto speed = ReadIntFromFile(path);\n    if (speed.has_value()) {\n      cpu_speeds.push_back({.index = i, .speed = speed.value()});\n    }\n  }\n  gCPUTracker = new CPUSpeedTracker(cpu_speeds);\n}\n\nbool SetUpCPUTracker() {\n  // Populate CPU Info if uninitialized.\n  auto online_cpu_count = sysconf(_SC_NPROCESSORS_ONLN);\n  auto cpu_count = sysconf(_SC_NPROCESSORS_CONF);\n  if (online_cpu_count <= 0 || online_cpu_count != cpu_count) {\n    return false;\n  }\n  std::call_once(gCPUTrackerFlag, [cpu_count]() { InitCPUInfo(cpu_count); });\n  if (gCPUTracker == nullptr || !gCPUTracker->IsValid()) {\n    return false;\n  }\n  return true;\n}\n\nstd::optional<size_t> AndroidEfficiencyCoreCount() {\n  if (!SetUpCPUTracker()) {\n    return true;\n  }\n  auto result = gCPUTracker->GetIndices(CpuAffinity::kEfficiency).size();\n  LYNX_BASE_CHECK(result > 0);\n  return result;\n}\n\nbool AndroidRequestAffinity(CpuAffinity affinity) {\n  if (!SetUpCPUTracker()) {\n    return true;\n  }\n\n  cpu_set_t set;\n  CPU_ZERO(&set);\n  for (const auto index : gCPUTracker->GetIndices(affinity)) {\n    CPU_SET(index, &set);\n  }\n  return sched_setaffinity(gettid(), sizeof(set), &set) == 0;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/android/message_loop_android.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/android/message_loop_android.h\"\n\n#include <fcntl.h>\n#include <unistd.h>\n\n#include \"base/include/fml/platform/linux/timerfd.h\"\n#include \"base/include/platform/android/jni_utils.h\"\n#include \"base/include/platform/android/scoped_java_ref.h\"\n\nnamespace lynx {\nnamespace fml {\n\nfml::RefPtr<MessageLoopImpl> MessageLoopImpl::Create(void* platform_loop) {\n  return fml::MakeRefCounted<MessageLoopAndroid>();\n}\n\nstatic constexpr int kClockType = CLOCK_MONOTONIC;\n\nstatic lynx::base::android::ScopedGlobalJavaRef<jclass>* g_looper_class =\n    nullptr;\nstatic jmethodID g_looper_prepare_method_ = nullptr;\nstatic jmethodID g_looper_loop_method_ = nullptr;\nstatic jmethodID g_looper_my_looper_method_ = nullptr;\nstatic jmethodID g_looper_quit_method_ = nullptr;\n\nstatic void LooperPrepare() {\n  JNIEnv* env = lynx::base::android::AttachCurrentThread();\n  env->CallStaticVoidMethod(g_looper_class->Get(), g_looper_prepare_method_);\n}\n\nstatic void LooperLoop() {\n  JNIEnv* env = lynx::base::android::AttachCurrentThread();\n  env->CallStaticVoidMethod(g_looper_class->Get(), g_looper_loop_method_);\n}\n\nstatic void LooperQuit() {\n  JNIEnv* env = lynx::base::android::AttachCurrentThread();\n  auto my_looper = env->CallStaticObjectMethod(g_looper_class->Get(),\n                                               g_looper_my_looper_method_);\n  if (my_looper != nullptr) {\n    env->CallVoidMethod(my_looper, g_looper_quit_method_);\n  }\n}\n\nstatic ALooper* AcquireLooperForThread() {\n  ALooper* looper = ALooper_forThread();\n  if (looper == nullptr) {\n    // No looper has been configured for the current thread. Create one and\n    // return the same.\n    looper = ALooper_prepare(0);\n  }\n\n  // The thread already has a looper. Acquire a reference to the same and return\n  // it.\n  ALooper_acquire(looper);\n  return looper;\n}\n\nMessageLoopAndroid::MessageLoopAndroid()\n    : looper_(AcquireLooperForThread()),\n      timer_fd_(::timerfd_create(kClockType, TFD_NONBLOCK | TFD_CLOEXEC)),\n      running_(false) {\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(looper_.is_valid());\n  LYNX_BASE_CHECK(timer_fd_.is_valid());\n  static const int kWakeEvents = ALOOPER_EVENT_INPUT;\n\n  ALooper_callbackFunc read_event_fd = [](int, int events, void* data) -> int {\n    if (events & kWakeEvents) {\n      reinterpret_cast<MessageLoopAndroid*>(data)->OnEventFired();\n    }\n    return 1;  // continue receiving callbacks\n  };\n\n  [[maybe_unused]] int add_result =\n      ::ALooper_addFd(looper_.get(),          // looper\n                      timer_fd_.get(),        // fd\n                      ALOOPER_POLL_CALLBACK,  // ident\n                      kWakeEvents,            // events\n                      read_event_fd,          // callback\n                      this                    // baton\n      );\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(add_result == 1);\n}\n\nMessageLoopAndroid::~MessageLoopAndroid() {\n  [[maybe_unused]] int remove_result =\n      ::ALooper_removeFd(looper_.get(), timer_fd_.get());\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(remove_result == 1);\n}\n\nvoid MessageLoopAndroid::Run() {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(looper_.get() == ALooper_forThread());\n\n  running_ = true;\n\n  LooperPrepare();\n  LooperLoop();\n}\n\nvoid MessageLoopAndroid::Terminate() {\n  running_ = false;\n  LooperQuit();\n}\n\nvoid MessageLoopAndroid::WakeUp(fml::TimePoint time_point) {\n  [[maybe_unused]] bool result = TimerRearm(timer_fd_.get(), time_point);\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(result);\n}\n\nvoid MessageLoopAndroid::OnEventFired() {\n  if (TimerDrain(timer_fd_.get())) {\n    RunExpiredTasksNow();\n  }\n}\n\nbool MessageLoopAndroid::Register(JNIEnv* env) {\n  jclass clazz = env->FindClass(\"android/os/Looper\");\n\n  if (clazz == nullptr) {\n    return false;\n  }\n\n  g_looper_class = new base::android::ScopedGlobalJavaRef<jclass>(env, clazz);\n\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(!(g_looper_class->IsNull()));\n\n  g_looper_prepare_method_ =\n      env->GetStaticMethodID(g_looper_class->Get(), \"prepare\", \"()V\");\n  LYNX_BASE_CHECK(g_looper_prepare_method_ != nullptr);\n\n  g_looper_loop_method_ =\n      env->GetStaticMethodID(g_looper_class->Get(), \"loop\", \"()V\");\n  LYNX_BASE_CHECK(g_looper_loop_method_ != nullptr);\n\n  g_looper_my_looper_method_ = env->GetStaticMethodID(\n      g_looper_class->Get(), \"myLooper\", \"()Landroid/os/Looper;\");\n  LYNX_BASE_CHECK(g_looper_my_looper_method_ != nullptr);\n\n  g_looper_quit_method_ =\n      env->GetMethodID(g_looper_class->Get(), \"quit\", \"()V\");\n\n  return true;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/android/message_loop_android_ndk.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// This is copied from the original implementation, by ndk, for unittests.\n\n#include <fcntl.h>\n#include <unistd.h>\n\n#include \"base/include/fml/platform/android/message_loop_android.h\"\n#include \"base/include/fml/platform/linux/timerfd.h\"\n#include \"base/include/platform/android/jni_utils.h\"\n#include \"base/include/platform/android/scoped_java_ref.h\"\n\nnamespace lynx {\nnamespace fml {\n\nfml::RefPtr<MessageLoopImpl> MessageLoopImpl::Create(void* platform_loop) {\n  return fml::MakeRefCounted<MessageLoopAndroid>();\n}\n\nstatic constexpr int kClockType = CLOCK_MONOTONIC;\n\nstatic ALooper* AcquireLooperForThread() {\n  ALooper* looper = ALooper_forThread();\n  if (looper == nullptr) {\n    // No looper has been configured for the current thread. Create one and\n    // return the same.\n    looper = ALooper_prepare(0);\n  }\n\n  // The thread already has a looper. Acquire a reference to the same and return\n  // it.\n  ALooper_acquire(looper);\n  return looper;\n}\n\nMessageLoopAndroid::MessageLoopAndroid()\n    : looper_(AcquireLooperForThread()),\n      timer_fd_(::timerfd_create(kClockType, TFD_NONBLOCK | TFD_CLOEXEC)),\n      running_(false) {\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(looper_.is_valid());\n  LYNX_BASE_CHECK(timer_fd_.is_valid());\n  static const int kWakeEvents = ALOOPER_EVENT_INPUT;\n\n  ALooper_callbackFunc read_event_fd = [](int, int events, void* data) -> int {\n    if (events & kWakeEvents) {\n      reinterpret_cast<MessageLoopAndroid*>(data)->OnEventFired();\n    }\n    return 1;  // continue receiving callbacks\n  };\n\n  [[maybe_unused]] int add_result =\n      ::ALooper_addFd(looper_.get(),          // looper\n                      timer_fd_.get(),        // fd\n                      ALOOPER_POLL_CALLBACK,  // ident\n                      kWakeEvents,            // events\n                      read_event_fd,          // callback\n                      this                    // baton\n      );\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(add_result == 1);\n}\n\nMessageLoopAndroid::~MessageLoopAndroid() {\n  [[maybe_unused]] int remove_result =\n      ::ALooper_removeFd(looper_.get(), timer_fd_.get());\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(remove_result == 1);\n}\n\nvoid MessageLoopAndroid::Run() {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(looper_.get() == ALooper_forThread());\n\n  running_ = true;\n\n  while (running_) {\n    int result = ::ALooper_pollOnce(-1,       // infinite timeout\n                                    nullptr,  // out fd,\n                                    nullptr,  // out events,\n                                    nullptr   // out data\n    );\n    if (result == ALOOPER_POLL_TIMEOUT || result == ALOOPER_POLL_ERROR) {\n      // This handles the case where the loop is terminated using ALooper APIs.\n      running_ = false;\n    }\n  }\n}\n\nvoid MessageLoopAndroid::Terminate() {\n  running_ = false;\n  ALooper_wake(looper_.get());\n}\n\nvoid MessageLoopAndroid::WakeUp(fml::TimePoint time_point) {\n  [[maybe_unused]] bool result = TimerRearm(timer_fd_.get(), time_point);\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(result);\n}\n\nvoid MessageLoopAndroid::OnEventFired() {\n  if (TimerDrain(timer_fd_.get())) {\n    RunExpiredTasksNow();\n  }\n}\n\n// Fake impl to make compiler happy.\nbool MessageLoopAndroid::Register(JNIEnv* env) { return true; }\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/android/thread_config_setter_android.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <sys/resource.h>\n\n#include \"base/include/fml/platform/thread_config_setter.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// Inheriting ThreadConfigurer and use Android platform thread API to\n// configure the thread priorities\nvoid PlatformThreadPriority::Setter(\n    const lynx::fml::Thread::ThreadConfig& config) {\n  // set thread name\n  lynx::fml::Thread::SetCurrentThreadName(config);\n\n  // set thread priority\n  switch (config.priority) {\n    case lynx::fml::Thread::ThreadPriority::BACKGROUND: {\n      // android.os.Process.THREAD_PRIORITY_BACKGROUND == 10\n      if (::setpriority(PRIO_PROCESS, 0, 10) != 0) {\n        // TODO(zhengsenyao): Uncomment the following log code\n        // LOGE(\"Failed to lower the priority of the task runner\");\n      }\n      break;\n    }\n    case lynx::fml::Thread::ThreadPriority::LOW: {\n      // android.os.Process.THREAD_PRIORITY_BACKGROUND == 10\n      if (::setpriority(PRIO_PROCESS, 0, 10) != 0) {\n        // TODO(zhengsenyao): Uncomment the following log code\n        // LOGE(\"Failed to lower the priority of the task runner\");\n      }\n      break;\n    }\n    case lynx::fml::Thread::ThreadPriority::NORMAL: {\n      // android.os.Process.ANDROID_PRIORITY_NORMAL == 0\n      // android.os.Process.ANDROID_PRIORITY_MORE_FAVORABLE == -1\n      if (::setpriority(PRIO_PROCESS, 0, -1) != 0) {\n        // TODO(zhengsenyao): Uncomment the following log code\n        // LOGE(\"Unable to assign normal priority to task runner\");\n      }\n      break;\n    }\n    case lynx::fml::Thread::ThreadPriority::HIGH: {\n      // android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY == -8\n      // Android describes -8 as \"most important display threads, for\n      // compositing the screen and retrieving input events\".\n      if (::setpriority(PRIO_PROCESS, 0, -8) != 0) {\n        // Defensive fallback. Depending on the OEM, it may not be possible\n        // to set priority to -8.\n        if (::setpriority(PRIO_PROCESS, 0, -2) != 0) {\n          // TODO(zhengsenyao): Uncomment the following log code\n          // LOGE(\"Unable to assign higher priority to task runner\");\n        }\n      }\n      break;\n    }\n  }\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/darwin/cf_utils_unittests.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/darwin/cf_utils.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace testing {\n\nTEST(CFTest, CanCreateRefs) {\n  CFRef<CFMutableStringRef> string(CFStringCreateMutable(kCFAllocatorDefault, 100u));\n  // Cast\n  ASSERT_TRUE(static_cast<bool>(string));\n  ASSERT_TRUE(string);\n\n  const auto ref_count = CFGetRetainCount(string);\n\n  // Copy & Reset\n  {\n    CFRef<CFMutableStringRef> string2 = string;\n    ASSERT_TRUE(string2);\n    ASSERT_EQ(ref_count + 1u, CFGetRetainCount(string));\n    ASSERT_EQ(CFGetRetainCount(string2), CFGetRetainCount(string));\n\n    string2.Reset();\n    ASSERT_FALSE(string2);\n    ASSERT_EQ(ref_count, CFGetRetainCount(string));\n  }\n\n  // Release\n  {\n    auto string3 = string;\n    ASSERT_TRUE(string3);\n    ASSERT_EQ(ref_count + 1u, CFGetRetainCount(string));\n    auto raw_string3 = string3.Release();\n    ASSERT_FALSE(string3);\n    ASSERT_EQ(ref_count + 1u, CFGetRetainCount(string));\n    CFRelease(raw_string3);\n    ASSERT_EQ(ref_count, CFGetRetainCount(string));\n  }\n\n  // Move\n  {\n    auto string_source = string;\n    ASSERT_TRUE(string_source);\n    auto string_move = std::move(string_source);\n    ASSERT_FALSE(string_source);\n    ASSERT_EQ(ref_count + 1u, CFGetRetainCount(string));\n    string_move.Reset();\n    ASSERT_EQ(ref_count, CFGetRetainCount(string));\n  }\n\n  // Move assign.\n  {\n    auto string_move_assign = std::move(string);\n    ASSERT_FALSE(string);\n    ASSERT_EQ(ref_count, CFGetRetainCount(string_move_assign));\n  }\n}\n\n}  // namespace testing\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/darwin/message_loop_darwin.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/darwin/message_loop_darwin.h\"\n\n#include <CoreFoundation/CFRunLoop.h>\n#import <Foundation/Foundation.h>\n\nnamespace lynx {\nnamespace fml {\n\nfml::RefPtr<MessageLoopImpl> MessageLoopImpl::Create(void* platform_loop) {\n  return fml::MakeRefCounted<MessageLoopDarwin>();\n}\n\nstatic constexpr CFTimeInterval kDistantFuture = 1.0e10;\n\nCFStringRef MessageLoopDarwin::kMessageLoopCFRunLoopMode = CFSTR(\"fmlMessageLoop\");\n\nMessageLoopDarwin::MessageLoopDarwin()\n    : running_(false), loop_((CFRunLoopRef)CFRetain(CFRunLoopGetCurrent())) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(loop_ != nullptr);\n\n  // Setup the delayed wake source.\n  CFRunLoopTimerContext timer_context = {\n      .info = this,\n  };\n  delayed_wake_timer_.Reset(\n      CFRunLoopTimerCreate(kCFAllocatorDefault, kDistantFuture /* fire date */,\n                           HUGE_VAL /* interval */, 0 /* flags */, 0 /* order */,\n                           reinterpret_cast<CFRunLoopTimerCallBack>(&MessageLoopDarwin::OnTimerFire)\n                           /* callout */,\n                           &timer_context /* context */));\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(delayed_wake_timer_ != nullptr);\n  CFRunLoopAddTimer(loop_, delayed_wake_timer_, kCFRunLoopCommonModes);\n  // This mode will be used by FlutterKeyboardManager.\n  CFRunLoopAddTimer(loop_, delayed_wake_timer_, kMessageLoopCFRunLoopMode);\n\n  // Setup the non-delayed wake source.\n  CFRunLoopSourceContext source_context = CFRunLoopSourceContext();\n  source_context.info = this;\n  source_context.perform = reinterpret_cast<void (*)(void*)>(&MessageLoopDarwin::OnSourceFire);\n  work_source_.Reset(CFRunLoopSourceCreate(NULL, 1, /* priority*/\n                                           &source_context));\n  CFRunLoopAddSource(loop_, work_source_, kCFRunLoopCommonModes);\n  CFRunLoopAddSource(loop_, work_source_, kMessageLoopCFRunLoopMode);\n}\n\nMessageLoopDarwin::~MessageLoopDarwin() {\n  CFRunLoopTimerInvalidate(delayed_wake_timer_);\n  CFRunLoopRemoveTimer(loop_, delayed_wake_timer_, kCFRunLoopCommonModes);\n  CFRunLoopRemoveTimer(loop_, delayed_wake_timer_, kMessageLoopCFRunLoopMode);\n  CFRunLoopRemoveSource(loop_, work_source_, kCFRunLoopCommonModes);\n  CFRunLoopRemoveSource(loop_, work_source_, kMessageLoopCFRunLoopMode);\n}\n\nvoid MessageLoopDarwin::Run() {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(loop_ == CFRunLoopGetCurrent());\n\n  running_ = true;\n\n  while (running_) {\n    @autoreleasepool {\n      int result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, kDistantFuture, YES);\n      if (result == kCFRunLoopRunStopped || result == kCFRunLoopRunFinished) {\n        // This handles the case where the loop is terminated using\n        // CoreFoundation APIs.\n        @autoreleasepool {\n          RunExpiredTasksNow();\n        }\n        running_ = false;\n      }\n    }\n  }\n}\n\nvoid MessageLoopDarwin::Terminate() {\n  running_ = false;\n  CFRunLoopStop(loop_);\n}\n\nvoid MessageLoopDarwin::WakeUp(fml::TimePoint time_point) {\n  // Rearm the timer. The time bases used by CoreFoundation and FXL are\n  // different and must be accounted for.\n  if (time_point == fml::TimePoint()) {\n    // TimePoint(0) is a hint for wakeup immediately.\n    // This skips a `TimePoint::Now()` call.\n    CFRunLoopSourceSignal(work_source_);\n    CFRunLoopWakeUp(loop_);\n    return;\n  }\n\n  auto now = TimePoint::Now();\n  if (time_point <= now) {\n    CFRunLoopSourceSignal(work_source_);\n    CFRunLoopWakeUp(loop_);\n    return;\n  }\n\n  // Wakeup at specific time.\n  CFRunLoopTimerSetNextFireDate(delayed_wake_timer_,\n                                CFAbsoluteTimeGetCurrent() + (time_point - now).ToSecondsF());\n}\n\nvoid MessageLoopDarwin::OnSourceFire(MessageLoopDarwin* loop) {\n  @autoreleasepool {\n    // RunExpiredTasksNow rearms the timer as appropriate via a call to WakeUp.\n    loop->RunExpiredTasksNow();\n  }\n}\n\nvoid MessageLoopDarwin::OnTimerFire(CFRunLoopTimerRef timer, MessageLoopDarwin* loop) {\n  OnSourceFire(loop);\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/darwin/thread_config_setter_darwin.mm",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Foundation/Foundation.h>\n#include <pthread.h>\n#include \"base/include/fml/platform/thread_config_setter.h\"\n\nnamespace lynx {\nnamespace fml {\n\nnamespace {\n\nvoid SetThreadPriority(int sched_priority, qos_class_t qos_class, double thread_priority) {\n  pthread_set_qos_class_self_np(qos_class, 0);\n  [[NSThread currentThread] setThreadPriority:thread_priority];\n  sched_param param;\n  int policy;\n  pthread_t thread = pthread_self();\n  if (pthread_getschedparam(thread, &policy, &param) == 0) {\n    param.sched_priority = sched_priority;\n    pthread_setschedparam(thread, policy, &param);\n  }\n}\n\n}  // namespace\n\n/// Inheriting ThreadConfigurer and use iOS platform thread API to configure the thread priorities\n/// Using iOS platform thread API to configure thread priority\n// thread_name |  sched_priority | threadPriority\n// ui | 31 |  0.5\n// js |  31->50 |  0.5->0.806452\n// Layout | 31->50 |  0.5->0.806452\n// TASM | 31->50 |  0.5->0.806452\nvoid PlatformThreadPriority::Setter(const lynx::fml::Thread::ThreadConfig& config) {\n  @autoreleasepool {\n    // set thread name\n    lynx::fml::Thread::SetCurrentThreadName(config);\n\n    // set thread priority\n    // The sched_priority values are based on the Darwin kernel's scheduling priorities.\n    // The range for default policy (SCHED_OTHER) is typically 0-63.\n    // A higher number means a higher priority.\n    // 4: A low priority, suitable for background tasks that are not time-sensitive.\n    // 31: The default priority for user-interactive threads.\n    // 46: A high priority, just below the typical main thread priority (47),\n    //     suitable for important, user-initiated work.\n    switch (config.priority) {\n      case lynx::fml::Thread::ThreadPriority::BACKGROUND:\n      case lynx::fml::Thread::ThreadPriority::LOW:\n        SetThreadPriority(4, QOS_CLASS_BACKGROUND, 0.0);\n        break;\n      case lynx::fml::Thread::ThreadPriority::NORMAL:\n        SetThreadPriority(31, QOS_CLASS_DEFAULT, 0.5);\n        break;\n      case lynx::fml::Thread::ThreadPriority::HIGH:\n        SetThreadPriority(46, QOS_CLASS_USER_INITIATED, 1.0);\n        break;\n    }\n  }\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/darwin/thread_name_setter_darwin.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <pthread.h>\n\n#include \"base/src/fml/thread_name_setter.h\"\n\nnamespace lynx {\nnamespace fml {\nvoid SetThreadName(const std::string& name) {\n  if (name == \"\") {\n    return;\n  }\n  pthread_setname_np(name.c_str());\n}\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/harmony/message_loop_harmony.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/harmony/message_loop_harmony.h\"\n\n#include <fcntl.h>\n#include <uv.h>\n\n#include \"base/include/fml/platform/linux/timerfd.h\"\n\n// temporarily workaround to compile without logging\n#undef LOGE\n#undef DCHECK\n#define LOGE(...)\n#define LOGI(...)\n#define DCHECK(...)\n\nnamespace lynx {\nnamespace fml {\n\nfml::RefPtr<MessageLoopImpl> MessageLoopImpl::Create(void* platform_loop) {\n  return fml::MakeRefCounted<MessageLoopHarmony>(platform_loop);\n}\n\nstatic constexpr int kClockType = CLOCK_MONOTONIC;\n\nMessageLoopHarmony::MessageLoopHarmony(void* platform_loop)\n    : timer_fd_(timerfd_create(kClockType, TFD_NONBLOCK | TFD_CLOEXEC)),\n      running_(false) {\n  // If the platform_loop is not null, use it to initialize the looper_\n  if (platform_loop != nullptr) {\n    looper_ = reinterpret_cast<uv_loop_t*>(platform_loop);\n    is_looper_owner_ = false;\n  } else {\n    looper_ = uv_loop_new();\n    is_looper_owner_ = true;\n  }\n  // Harmony Developer Beta1 Canary3 break\n  uv_async_send(&looper_->wq_async);\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(timer_fd_.is_valid());\n\n  auto read_event_fd = [](uv_poll_t* handle, int status, int events) {\n    if (events & UV_READABLE) {\n      reinterpret_cast<MessageLoopHarmony*>(handle->data)->OnEventFired();\n    }\n  };\n\n  [[maybe_unused]] int init_result =\n      uv_poll_init(looper_, &poll_, timer_fd_.get());\n  poll_.data = this;\n  LYNX_BASE_CHECK(init_result == 0);\n  [[maybe_unused]] int start_result =\n      uv_poll_start(&poll_, UV_READABLE, read_event_fd);\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(start_result == 0);\n}\n\nMessageLoopHarmony::~MessageLoopHarmony() {}\n\nvoid MessageLoopHarmony::Run() {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  running_ = true;\n  uv_run(looper_, UV_RUN_DEFAULT);\n\n  if (is_looper_owner_) {\n    uv_loop_delete(looper_);\n    LOGI(\"MessageLoopHarmony::Run, this:\" << this << \", delete uv_loop \"\n                                          << looper_);\n  }\n  looper_ = nullptr;\n}\n\nvoid MessageLoopHarmony::Terminate() {\n  running_ = false;\n  [[maybe_unused]] int stop = uv_poll_stop(&poll_);\n  uv_close(reinterpret_cast<uv_handle_t*>(&poll_), NULL);\n  LOGI(\"MessageLoopHarmony::Terminate, this:\"\n       << this << \", uv_poll_stop result:\" << stop);\n}\n\nvoid MessageLoopHarmony::WakeUp(fml::TimePoint time_point) {\n  [[maybe_unused]] bool result = TimerRearm(timer_fd_.get(), time_point);\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(result);\n}\n\nvoid MessageLoopHarmony::OnEventFired() {\n  if (TimerDrain(timer_fd_.get())) {\n    RunExpiredTasksNow();\n  }\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/linux/message_loop_linux.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/linux/message_loop_linux.h\"\n\n#include <sys/epoll.h>\n#include <unistd.h>\n\n#include \"base/include/fml/eintr_wrapper.h\"\n#include \"base/include/fml/platform/linux/timerfd.h\"\n\nnamespace lynx {\nnamespace fml {\n\nfml::RefPtr<MessageLoopImpl> MessageLoopImpl::Create(void* platform_loop) {\n  return fml::MakeRefCounted<MessageLoopLinux>();\n}\n\nstatic constexpr int kClockType = CLOCK_MONOTONIC;\n\nMessageLoopLinux::MessageLoopLinux()\n    : epoll_fd_(FML_HANDLE_EINTR(::epoll_create(1 /* unused */))),\n      timer_fd_(::timerfd_create(kClockType, TFD_NONBLOCK | TFD_CLOEXEC)),\n      running_(false) {\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(epoll_fd_.is_valid());\n  LYNX_BASE_CHECK(timer_fd_.is_valid());\n  [[maybe_unused]] bool added_source = AddOrRemoveTimerSource(true);\n  LYNX_BASE_CHECK(added_source);\n}\n\nMessageLoopLinux::~MessageLoopLinux() {\n  [[maybe_unused]] bool removed_source = AddOrRemoveTimerSource(false);\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(removed_source);\n}\n\nbool MessageLoopLinux::AddOrRemoveTimerSource(bool add) {\n  struct epoll_event event = {};\n\n  event.events = EPOLLIN;\n  // The data is just for informational purposes so we know when we were worken\n  // by the FD.\n  event.data.fd = timer_fd_.get();\n\n  int ctl_result =\n      ::epoll_ctl(epoll_fd_.get(), add ? EPOLL_CTL_ADD : EPOLL_CTL_DEL,\n                  timer_fd_.get(), &event);\n  return ctl_result == 0;\n}\n\n// |fml::MessageLoopImpl|\nvoid MessageLoopLinux::Run() {\n  running_ = true;\n\n  while (running_) {\n    struct epoll_event event = {};\n\n    int epoll_result = FML_HANDLE_EINTR(\n        ::epoll_wait(epoll_fd_.get(), &event, 1, -1 /* timeout */));\n\n    // Errors are fatal.\n    if (event.events & (EPOLLERR | EPOLLHUP)) {\n      running_ = false;\n      continue;\n    }\n\n    // Timeouts are fatal since we specified an infinite timeout already.\n    // Likewise, > 1 is not possible since we waited for one result.\n    if (epoll_result != 1) {\n      running_ = false;\n      continue;\n    }\n\n    if (event.data.fd == timer_fd_.get()) {\n      OnEventFired();\n    }\n  }\n}\n\n// |fml::MessageLoopImpl|\nvoid MessageLoopLinux::Terminate() {\n  running_ = false;\n  WakeUp(fml::TimePoint::Now());\n}\n\n// |fml::MessageLoopImpl|\nvoid MessageLoopLinux::WakeUp(fml::TimePoint time_point) {\n  bool result = TimerRearm(timer_fd_.get(), time_point);\n  (void)result;\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(result);\n}\n\nvoid MessageLoopLinux::OnEventFired() {\n  if (TimerDrain(timer_fd_.get())) {\n    RunExpiredTasksNow();\n  }\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/linux/timerfd.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/linux/timerfd.h\"\n\n#include <sys/types.h>\n#include <unistd.h>\n\n#include <cstring>\n\n#include \"base/include/fml/eintr_wrapper.h\"\n\n#if FML_TIMERFD_AVAILABLE == 0\n\n#include <asm/unistd.h>\n#include <sys/syscall.h>\n\nint timerfd_create(int clockid, int flags) {\n  return syscall(__NR_timerfd_create, clockid, flags);\n}\n\nint timerfd_settime(int ufc, int flags, const struct itimerspec* utmr,\n                    struct itimerspec* otmr) {\n  return syscall(__NR_timerfd_settime, ufc, flags, utmr, otmr);\n}\n\n#endif  // FML_TIMERFD_AVAILABLE == 0\n\nnamespace lynx {\nnamespace fml {\n\n#ifndef NSEC_PER_SEC\n#define NSEC_PER_SEC 1000000000\n#endif\n\nbool TimerRearm(int fd, fml::TimePoint time_point) {\n  if (time_point == fml::TimePoint()) {\n    // TimePoint(0) is a hint for wakeup immediately.\n    struct itimerspec new_value;\n    new_value.it_interval.tv_sec = 0;\n    new_value.it_interval.tv_nsec = 0;\n    new_value.it_value.tv_sec = 0;\n    new_value.it_value.tv_nsec = 1;\n    return ::timerfd_settime(fd, 0, &new_value, nullptr) == 0;\n  }\n\n  // Wakeup at absolute time.\n  uint64_t nano_secs = time_point.ToEpochDelta().ToNanoseconds();\n\n  // \"0\" will disarm the timer, desired behavior is to immediately\n  // trigger the timer.\n  if (nano_secs < 1) {\n    nano_secs = 1;\n  }\n\n  struct itimerspec spec;\n  spec.it_value.tv_sec = static_cast<time_t>(nano_secs / NSEC_PER_SEC);\n  spec.it_value.tv_nsec = nano_secs % NSEC_PER_SEC;\n  spec.it_interval = spec.it_value;  // single expiry.\n\n  int result = ::timerfd_settime(fd, TFD_TIMER_ABSTIME, &spec, nullptr);\n  if (result != 0) {\n    // TODO(zhengsenyao): Uncomment DLOGE code when DLOGE available\n    // DLOGE(\"timerfd_settime err:\" << strerror(errno));\n  }\n  return result == 0;\n}\n\nbool TimerDrain(int fd) {\n  // 8 bytes must be read from a signaled timer file descriptor when signaled.\n  uint64_t fire_count = 0;\n  ssize_t size = FML_HANDLE_EINTR(::read(fd, &fire_count, sizeof(uint64_t)));\n  if (size != sizeof(uint64_t)) {\n    return false;\n  }\n  return fire_count > 0;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/posix/thread_name_setter_posix.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include <pthread.h>\n\n#include \"base/src/fml/thread_name_setter.h\"\n\nnamespace lynx {\nnamespace fml {\nvoid SetThreadName(const std::string& name) {\n  if (name == \"\") {\n    return;\n  }\n  /**\n   * The thread name is a meaningful C language string, whose length is\n   * restricted to 16 characters, including the terminating null byte ('\\0').\n   * Fails with error 34 (ERANGE) on Android if longer than 16 bytes.\n   */\n  std::string compliant_name(name.substr(0, 15));\n  pthread_setname_np(pthread_self(), compliant_name.c_str());\n}\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/win/message_loop_win.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/win/message_loop_win.h\"\n\n#include <VersionHelpers.h>\n#include <timeapi.h>\n\n#pragma comment(lib, \"Winmm.lib\")\n\nconstexpr uint32_t kHighResolutionTimer = 1;  // 1 ms\nconstexpr uint32_t kLowResolutionTimer = 15;  // 15 ms\n\nnamespace lynx {\nnamespace fml {\n\nfml::RefPtr<MessageLoopImpl> MessageLoopImpl::Create(void* platform_loop) {\n  return fml::MakeRefCounted<MessageLoopWin>();\n}\n\nMessageLoopWin::MessageLoopWin()\n    : timer_(CreateWaitableTimer(NULL, FALSE, NULL)) {\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(timer_.is_valid());\n  // Flutter uses timers to schedule frames. By default, Windows timers do\n  // not have the precision to reliably schedule frame rates greater than\n  // 60hz. We can increase the precision, but on versions of Windows before\n  // 10, this would globally increase timer precision leading to increased\n  // resource usage. This would be particularly problematic on a laptop or\n  // mobile device.\n  if (IsWindows10OrGreater()) {\n    timer_resolution_ = kHighResolutionTimer;\n  } else {\n    timer_resolution_ = kLowResolutionTimer;\n  }\n  timeBeginPeriod(timer_resolution_);\n}\n\nMessageLoopWin::~MessageLoopWin() = default;\n\nvoid MessageLoopWin::Run() {\n  running_ = true;\n\n  while (running_) {\n    // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK\n    // available.\n    LYNX_BASE_CHECK(WaitForSingleObject(timer_.get(), INFINITE) == 0);\n    RunExpiredTasksNow();\n  }\n}\n\nvoid MessageLoopWin::Terminate() {\n  running_ = false;\n  WakeUp(fml::TimePoint::Now());\n  timeEndPeriod(timer_resolution_);\n}\n\nvoid MessageLoopWin::WakeUp(fml::TimePoint time_point) {\n  LARGE_INTEGER due_time = {0};\n  if (time_point == fml::TimePoint()) {\n    // TimePoint(0) is a hint for wakeup immediately.\n    // This skips a `TimePoint::Now()` call.\n    // due_time.QuadPart already set to 0 which means to\n    // wake up the timer immediately.\n  } else {\n    fml::TimePoint now = fml::TimePoint::Now();\n    if (time_point > now) {\n      due_time.QuadPart = (time_point - now).ToNanoseconds() / -100;\n    }\n  }\n\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(\n      SetWaitableTimer(timer_.get(), &due_time, 0, NULL, NULL, FALSE));\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/win/task_runner_win32.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/win/task_runner_win32.h\"\n\n#include <algorithm>\n\n#include \"base/include/fml/message_loop_impl.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// static\nfml::RefPtr<fml::TaskRunner> TaskRunnerWin32::Create() {\n  return fml::MakeRefCounted<fml::TaskRunnerWin32>();\n}\n\nTaskRunnerWin32::TaskRunnerWin32() : TaskRunner(nullptr) {\n  main_thread_id_ = GetCurrentThreadId();\n  task_runner_window_ = TaskRunnerWin32Window::GetSharedInstance();\n  task_runner_window_->AddDelegate(this);\n}\n\nTaskRunnerWin32::~TaskRunnerWin32() {\n  task_runner_window_->RemoveDelegate(this);\n}\n\nvoid TaskRunnerWin32::PostTask(base::closure closure) {\n  PostTaskForTime(std::move(closure), fml::TimePoint::Now());\n}\n\nvoid TaskRunnerWin32::PostTaskForTime(base::closure closure,\n                                      fml::TimePoint target_time) {\n  Task task;\n  TaskTimePoint fire_time = TaskTimePoint(\n      std::chrono::nanoseconds(target_time.ToEpochDelta().ToNanoseconds()));\n  task.fire_time = fire_time;\n  task.closure = std::move(closure);\n\n  static std::atomic_uint64_t sGlobalTaskOrder(0);\n\n  task.order = ++sGlobalTaskOrder;\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    task_queue_.push(std::move(task));\n\n    // Make sure the queue mutex is unlocked before waking up the loop. In case\n    // the wake causes this thread to be descheduled for the primary thread to\n    // process tasks, the acquisition of the lock on that thread while holding\n    // the lock here momentarily till the end of the scope is a pessimization.\n  }\n\n  task_runner_window_->WakeUp();\n}\n\nvoid TaskRunnerWin32::PostDelayedTask(base::closure closure,\n                                      fml::TimeDelta delay) {\n  PostTaskForTime(std::move(closure), fml::TimePoint::Now() + delay);\n}\n\nbool TaskRunnerWin32::RunsTasksOnCurrentThread() {\n  return GetCurrentThreadId() == main_thread_id_;\n}\n\nstd::chrono::nanoseconds TaskRunnerWin32::ProcessTasks() {\n  const TaskTimePoint now = GetCurrentTimeForTask();\n\n  std::vector<base::closure> expired_tasks;\n\n  // Process expired tasks.\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    while (!task_queue_.empty()) {\n      const auto& top = task_queue_.top();\n      // If this task (and all tasks after this) has not yet expired, there is\n      // nothing more to do. Quit iterating.\n      if (top.fire_time > now) {\n        break;\n      }\n\n      // Make a record of the expired task. Do NOT service the task here\n      // because we are still holding onto the task queue mutex. We don't want\n      // other threads to block on posting tasks onto this thread till we are\n      // done processing expired tasks.\n      expired_tasks.push_back(std::move(task_queue_.top().closure));\n\n      // Remove the tasks from the delayed tasks queue.\n      task_queue_.pop();\n    }\n  }\n\n  // Fire expired tasks.\n  {\n    // Flushing tasks here without holing onto the task queue mutex.\n    for (const auto& task : expired_tasks) {\n      task();\n    }\n  }\n\n  // Calculate duration to sleep for on next iteration.\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    const auto next_wake = task_queue_.empty() ? TaskTimePoint::max()\n                                               : task_queue_.top().fire_time;\n\n    return std::min(next_wake - now, std::chrono::nanoseconds::max());\n  }\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/win/task_runner_win32_window.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/platform/win/task_runner_win32_window.h\"\n\n#include <algorithm>\n#include <iostream>\n\nnamespace lynx {\nnamespace fml {\n\nTaskRunnerWin32Window::TaskRunnerWin32Window() {\n  WNDCLASS window_class = RegisterWindowClass();\n  window_handle_ =\n      CreateWindowEx(0, window_class.lpszClassName, L\"\", 0, 0, 0, 0, 0,\n                     HWND_MESSAGE, nullptr, window_class.hInstance, nullptr);\n\n  if (window_handle_) {\n    SetWindowLongPtr(window_handle_, GWLP_USERDATA,\n                     reinterpret_cast<LONG_PTR>(this));\n  } else {\n    auto error = GetLastError();\n    LPWSTR message = nullptr;\n    size_t size = FormatMessageW(\n        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |\n            FORMAT_MESSAGE_IGNORE_INSERTS,\n        NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n        reinterpret_cast<LPWSTR>(&message), 0, NULL);\n    OutputDebugString(message);\n    LocalFree(message);\n  }\n}\n\nTaskRunnerWin32Window::~TaskRunnerWin32Window() {\n  if (window_handle_) {\n    DestroyWindow(window_handle_);\n    window_handle_ = nullptr;\n  }\n  UnregisterClass(window_class_name_.c_str(), nullptr);\n}\n\nstd::shared_ptr<TaskRunnerWin32Window>\nTaskRunnerWin32Window::GetSharedInstance() {\n  static std::weak_ptr<TaskRunnerWin32Window> instance;\n  auto res = instance.lock();\n  if (!res) {\n    // can't use make_shared with private contructor\n    res.reset(new TaskRunnerWin32Window());\n    instance = res;\n  }\n  return res;\n}\n\nvoid TaskRunnerWin32Window::WakeUp() {\n  if (!PostMessage(window_handle_, WM_NULL, 0, 0)) {\n    std::cerr << \"Failed to post message to main thread.\" << std::endl;\n  }\n}\n\nvoid TaskRunnerWin32Window::AddDelegate(Delegate* delegate) {\n  delegates_.push_back(delegate);\n  SetTimer(std::chrono::nanoseconds::zero());\n}\n\nvoid TaskRunnerWin32Window::RemoveDelegate(Delegate* delegate) {\n  auto i = std::find(delegates_.begin(), delegates_.end(), delegate);\n  if (i != delegates_.end()) {\n    delegates_.erase(i);\n  }\n}\n\nvoid TaskRunnerWin32Window::ProcessTasks() {\n  auto next = std::chrono::nanoseconds::max();\n  auto delegates_copy(delegates_);\n  for (auto delegate : delegates_copy) {\n    // if not removed in the meanwhile\n    if (std::find(delegates_.begin(), delegates_.end(), delegate) !=\n        delegates_.end()) {\n      next = std::min(next, delegate->ProcessTasks());\n    }\n  }\n  SetTimer(next);\n}\n\nvoid TaskRunnerWin32Window::SetTimer(std::chrono::nanoseconds when) {\n  if (when == std::chrono::nanoseconds::max()) {\n    KillTimer(window_handle_, 0);\n  } else {\n    auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(when);\n    ::SetTimer(window_handle_, 0, millis.count() + 1, nullptr);\n  }\n}\n\nWNDCLASS TaskRunnerWin32Window::RegisterWindowClass() {\n  window_class_name_ = L\"LynxTaskRunnerWindow\";\n\n  WNDCLASS window_class{};\n  window_class.hCursor = nullptr;\n  window_class.lpszClassName = window_class_name_.c_str();\n  window_class.style = 0;\n  window_class.cbClsExtra = 0;\n  window_class.cbWndExtra = 0;\n  window_class.hInstance = GetModuleHandle(nullptr);\n  window_class.hIcon = nullptr;\n  window_class.hbrBackground = 0;\n  window_class.lpszMenuName = nullptr;\n  window_class.lpfnWndProc = WndProc;\n  RegisterClass(&window_class);\n  return window_class;\n}\n\nLRESULT\nTaskRunnerWin32Window::HandleMessage(UINT const message, WPARAM const wparam,\n                                     LPARAM const lparam) noexcept {\n  switch (message) {\n    case WM_TIMER:\n    case WM_NULL:\n      ProcessTasks();\n      return 0;\n  }\n  return DefWindowProcW(window_handle_, message, wparam, lparam);\n}\n\nLRESULT TaskRunnerWin32Window::WndProc(HWND const window, UINT const message,\n                                       WPARAM const wparam,\n                                       LPARAM const lparam) noexcept {\n  if (auto* that = reinterpret_cast<TaskRunnerWin32Window*>(\n          GetWindowLongPtr(window, GWLP_USERDATA))) {\n    return that->HandleMessage(message, wparam, lparam);\n  } else {\n    return DefWindowProc(window, message, wparam, lparam);\n  }\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/platform/win/thread_name_setter_win.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <windows.h>\n\n#include \"base/src/fml/thread_name_setter.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// The information on how to set the thread name comes from\n// a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx\nconst DWORD kVCThreadNameException = 0x406D1388;\ntypedef struct tagTHREADNAME_INFO {\n  DWORD dwType;      // Must be 0x1000.\n  LPCSTR szName;     // Pointer to name (in user addr space).\n  DWORD dwThreadID;  // Thread ID (-1=caller thread).\n  DWORD dwFlags;     // Reserved for future use, must be zero.\n} THREADNAME_INFO;\n// The SetThreadDescription API was brought in version 1607 of Windows 10.\ntypedef HRESULT(WINAPI* SetThreadDescription)(HANDLE hThread,\n                                              PCWSTR lpThreadDescription);\n\nvoid SetThreadName(const std::string& name) {\n  if (name == \"\") {\n    return;\n  }\n\n  static auto set_thread_description_func =\n      reinterpret_cast<SetThreadDescription>(::GetProcAddress(\n          ::GetModuleHandle(L\"Kernel32.dll\"), \"SetThreadDescription\"));\n  if (set_thread_description_func) {\n    wchar_t wstr[128];\n    mbstowcs(wstr, name.c_str(), 128);\n    set_thread_description_func(::GetCurrentThread(), wstr);\n  }\n  if (::IsDebuggerPresent()) {\n    THREADNAME_INFO info;\n    info.dwType = 0x1000;\n    info.szName = name.c_str();\n    info.dwThreadID = GetCurrentThreadId();\n    info.dwFlags = 0;\n    __try {\n      RaiseException(kVCThreadNameException, 0, sizeof(info) / sizeof(DWORD),\n                     reinterpret_cast<DWORD_PTR*>(&info));\n    } __except (EXCEPTION_CONTINUE_EXECUTION) {  // NOLINT\n    }\n  }\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/raster_thread_merger.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"base/include/fml/raster_thread_merger.h\"\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n\nnamespace lynx {\nnamespace fml {\n\nRasterThreadMerger::RasterThreadMerger(fml::TaskQueueId platform_queue_id,\n                                       fml::TaskQueueId gpu_queue_id)\n    : RasterThreadMerger(\n          MakeRefCounted<SharedThreadMerger>(platform_queue_id, gpu_queue_id),\n          platform_queue_id, gpu_queue_id) {}\n\nRasterThreadMerger::RasterThreadMerger(\n    fml::RefPtr<fml::SharedThreadMerger> shared_merger,\n    fml::TaskQueueId platform_queue_id, fml::TaskQueueId gpu_queue_id)\n    : platform_queue_id_(platform_queue_id),\n      gpu_queue_id_(gpu_queue_id),\n      shared_merger_(std::move(shared_merger)) {}\n\nvoid RasterThreadMerger::SetMergeUnmergeCallback(base::closure callback) {\n  merge_unmerge_callback_ = std::move(callback);\n}\n\nconst fml::RefPtr<fml::SharedThreadMerger>&\nRasterThreadMerger::GetSharedRasterThreadMerger() const {\n  return shared_merger_;\n}\n\nfml::RefPtr<fml::RasterThreadMerger>\nRasterThreadMerger::CreateOrShareThreadMerger(\n    const fml::RefPtr<fml::RasterThreadMerger>& parent_merger,\n    TaskQueueId platform_id, TaskQueueId raster_id) {\n  if (parent_merger && parent_merger->platform_queue_id_ == platform_id &&\n      parent_merger->gpu_queue_id_ == raster_id) {\n    auto shared_merger = parent_merger->GetSharedRasterThreadMerger();\n    return fml::MakeRefCounted<RasterThreadMerger>(shared_merger, platform_id,\n                                                   raster_id);\n  } else {\n    return fml::MakeRefCounted<RasterThreadMerger>(platform_id, raster_id);\n  }\n}\n\nvoid RasterThreadMerger::MergeWithLease(size_t lease_term) {\n  std::scoped_lock lock(mutex_);\n  if (TaskQueuesAreSame()) {\n    return;\n  }\n  if (!IsEnabledUnSafe()) {\n    return;\n  }\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(lease_term > 0) << \"lease_term should be positive.\";\n\n  if (IsMergedUnSafe()) {\n    merged_condition_.notify_one();\n    return;\n  }\n\n  bool success = shared_merger_->MergeWithLease(this, lease_term);\n  if (success && merge_unmerge_callback_ != nullptr) {\n    merge_unmerge_callback_();\n  }\n\n  merged_condition_.notify_one();\n}\n\nvoid RasterThreadMerger::UnMergeNowIfLastOne() {\n  std::scoped_lock lock(mutex_);\n\n  if (TaskQueuesAreSame()) {\n    return;\n  }\n  if (!IsEnabledUnSafe()) {\n    return;\n  }\n  bool success = shared_merger_->UnMergeNowIfLastOne(this);\n  if (success && merge_unmerge_callback_ != nullptr) {\n    merge_unmerge_callback_();\n  }\n}\n\nbool RasterThreadMerger::IsOnPlatformThread() const {\n  return MessageLoop::GetCurrentTaskQueueId() == platform_queue_id_;\n}\n\nbool RasterThreadMerger::IsOnRasterizingThread() {\n  std::scoped_lock lock(mutex_);\n\n  if (IsMergedUnSafe()) {\n    return IsOnPlatformThread();\n  } else {\n    return !IsOnPlatformThread();\n  }\n}\n\nvoid RasterThreadMerger::ExtendLeaseTo(size_t lease_term) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(lease_term > 0) << \"lease_term should be positive.\";\n  if (TaskQueuesAreSame()) {\n    return;\n  }\n  std::scoped_lock lock(mutex_);\n  if (!IsEnabledUnSafe()) {\n    return;\n  }\n  shared_merger_->ExtendLeaseTo(this, lease_term);\n}\n\nbool RasterThreadMerger::IsMerged() {\n  std::scoped_lock lock(mutex_);\n  return IsMergedUnSafe();\n}\n\nvoid RasterThreadMerger::Enable() {\n  std::scoped_lock lock(mutex_);\n  shared_merger_->SetEnabledUnSafe(true);\n}\n\nvoid RasterThreadMerger::Disable() {\n  std::scoped_lock lock(mutex_);\n  shared_merger_->SetEnabledUnSafe(false);\n}\n\nbool RasterThreadMerger::IsEnabled() {\n  std::scoped_lock lock(mutex_);\n  return IsEnabledUnSafe();\n}\n\nbool RasterThreadMerger::IsEnabledUnSafe() const {\n  return shared_merger_->IsEnabledUnSafe();\n}\n\nbool RasterThreadMerger::IsMergedUnSafe() const {\n  return TaskQueuesAreSame() || shared_merger_->IsMergedUnSafe();\n}\n\nbool RasterThreadMerger::TaskQueuesAreSame() const {\n  return platform_queue_id_ == gpu_queue_id_;\n}\n\nvoid RasterThreadMerger::WaitUntilMerged() {\n  if (TaskQueuesAreSame()) {\n    return;\n  }\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(IsOnPlatformThread());\n  std::unique_lock<std::mutex> lock(mutex_);\n  merged_condition_.wait(lock, [&] { return IsMergedUnSafe(); });\n}\n\nRasterThreadStatus RasterThreadMerger::DecrementLease() {\n  if (TaskQueuesAreSame()) {\n    return RasterThreadStatus::kRemainsMerged;\n  }\n  std::scoped_lock lock(mutex_);\n  if (!IsMergedUnSafe()) {\n    return RasterThreadStatus::kRemainsUnmerged;\n  }\n  if (!IsEnabledUnSafe()) {\n    return RasterThreadStatus::kRemainsMerged;\n  }\n  bool unmerged_after_decrement = shared_merger_->DecrementLease(this);\n  if (unmerged_after_decrement) {\n    if (merge_unmerge_callback_ != nullptr) {\n      merge_unmerge_callback_();\n    }\n    return RasterThreadStatus::kUnmergedNow;\n  }\n\n  return RasterThreadStatus::kRemainsMerged;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/raster_thread_merger_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#define FML_USED_ON_EMBEDDER\n\n#include <thread>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/raster_thread_merger.h\"\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/thread.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace testing {\n\n/// A mock task queue NOT calling MessageLoop->Run() in thread\nstruct TaskQueueWrapper {\n  fml::MessageLoop* loop = nullptr;\n\n  /// The waiter for message loop initialized ok\n  fml::AutoResetWaitableEvent latch;\n\n  /// The waiter for thread finished\n  fml::AutoResetWaitableEvent term;\n\n  /// This field must below latch and term member, because\n  /// cpp standard reference:\n  /// non-static data members are initialized in the order they were declared in\n  /// the class definition\n  std::thread thread;\n\n  TaskQueueWrapper()\n      : thread([this]() {\n          fml::MessageLoop::EnsureInitializedForCurrentThread();\n          loop = &fml::MessageLoop::GetCurrent();\n          latch.Signal();\n          term.Wait();\n        }) {\n    latch.Wait();\n  }\n\n  ~TaskQueueWrapper() {\n    term.Signal();\n    thread.join();\n  }\n\n  fml::TaskQueueId GetTaskQueueId() const {\n    return loop->GetTaskRunner()->GetTaskQueueId();\n  }\n};\n\nTEST(RasterThreadMerger, RemainMergedTillLeaseExpires) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n  const auto raster_thread_merger =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n  const size_t kNumFramesMerged = 5;\n\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->MergeWithLease(kNumFramesMerged);\n\n  for (size_t i = 0; i < kNumFramesMerged; i++) {\n    ASSERT_TRUE(raster_thread_merger->IsMerged());\n    raster_thread_merger->DecrementLease();\n  }\n\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n}\n\nTEST(RasterThreadMerger, IsNotOnRasterizingThread) {\n  fml::MessageLoop* loop1 = nullptr;\n  fml::AutoResetWaitableEvent latch1;\n  std::thread thread1([&loop1, &latch1]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop1 = &fml::MessageLoop::GetCurrent();\n    loop1->GetTaskRunner()->PostTask([&]() { latch1.Signal(); });\n    loop1->Run();\n  });\n\n  fml::MessageLoop* loop2 = nullptr;\n  fml::AutoResetWaitableEvent latch2;\n  std::thread thread2([&loop2, &latch2]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop2 = &fml::MessageLoop::GetCurrent();\n    loop2->GetTaskRunner()->PostTask([&]() { latch2.Signal(); });\n    loop2->Run();\n  });\n\n  latch1.Wait();\n  latch2.Wait();\n\n  fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId();\n  fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId();\n  const auto raster_thread_merger =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n\n  fml::CountDownLatch pre_merge(2), post_merge(2), post_unmerge(2);\n\n  loop1->GetTaskRunner()->PostTask([&]() {\n    ASSERT_FALSE(raster_thread_merger->IsOnRasterizingThread());\n    ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread());\n    ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1);\n    pre_merge.CountDown();\n  });\n\n  loop2->GetTaskRunner()->PostTask([&]() {\n    ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread());\n    ASSERT_FALSE(raster_thread_merger->IsOnPlatformThread());\n    ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid2);\n    pre_merge.CountDown();\n  });\n\n  pre_merge.Wait();\n\n  raster_thread_merger->MergeWithLease(1);\n\n  loop1->GetTaskRunner()->PostTask([&]() {\n    ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread());\n    ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread());\n    ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1);\n    post_merge.CountDown();\n  });\n\n  loop2->GetTaskRunner()->PostTask([&]() {\n    // this will be false since this is going to be run\n    // on loop1 really.\n    ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread());\n    ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread());\n    ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1);\n    post_merge.CountDown();\n  });\n\n  post_merge.Wait();\n\n  raster_thread_merger->DecrementLease();\n\n  loop1->GetTaskRunner()->PostTask([&]() {\n    ASSERT_FALSE(raster_thread_merger->IsOnRasterizingThread());\n    ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread());\n    ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1);\n    post_unmerge.CountDown();\n  });\n\n  loop2->GetTaskRunner()->PostTask([&]() {\n    ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread());\n    ASSERT_FALSE(raster_thread_merger->IsOnPlatformThread());\n    ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid2);\n    post_unmerge.CountDown();\n  });\n\n  post_unmerge.Wait();\n\n  loop1->GetTaskRunner()->PostTask([&]() { loop1->Terminate(); });\n\n  loop2->GetTaskRunner()->PostTask([&]() { loop2->Terminate(); });\n\n  thread1.join();\n  thread2.join();\n}\n\nTEST(RasterThreadMerger, LeaseExtension) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n  const auto raster_thread_merger =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n  const size_t kNumFramesMerged = 5;\n\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->MergeWithLease(kNumFramesMerged);\n\n  // let there be one more turn till the leases expire.\n  for (size_t i = 0; i < kNumFramesMerged - 1; i++) {\n    ASSERT_TRUE(raster_thread_merger->IsMerged());\n    raster_thread_merger->DecrementLease();\n  }\n\n  // extend the lease once.\n  raster_thread_merger->ExtendLeaseTo(kNumFramesMerged);\n\n  // we will NOT last for 1 extra turn, we just set it.\n  for (size_t i = 0; i < kNumFramesMerged; i++) {\n    ASSERT_TRUE(raster_thread_merger->IsMerged());\n    raster_thread_merger->DecrementLease();\n  }\n\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n}\n\nTEST(RasterThreadMerger, WaitUntilMerged) {\n  fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger;\n\n  fml::AutoResetWaitableEvent create_thread_merger_latch;\n  fml::MessageLoop* loop_platform = nullptr;\n  fml::AutoResetWaitableEvent latch_platform;\n  fml::AutoResetWaitableEvent term_platform;\n  fml::AutoResetWaitableEvent latch_merged;\n  std::thread thread_platform([&]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop_platform = &fml::MessageLoop::GetCurrent();\n    latch_platform.Signal();\n    create_thread_merger_latch.Wait();\n    raster_thread_merger->WaitUntilMerged();\n    latch_merged.Signal();\n    term_platform.Wait();\n  });\n\n  const size_t kNumFramesMerged = 5;\n  fml::MessageLoop* loop_raster = nullptr;\n  fml::AutoResetWaitableEvent term_raster;\n  std::thread thread_raster([&]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop_raster = &fml::MessageLoop::GetCurrent();\n    latch_platform.Wait();\n    fml::TaskQueueId qid_platform =\n        loop_platform->GetTaskRunner()->GetTaskQueueId();\n    fml::TaskQueueId qid_raster =\n        loop_raster->GetTaskRunner()->GetTaskQueueId();\n    raster_thread_merger =\n        fml::MakeRefCounted<fml::RasterThreadMerger>(qid_platform, qid_raster);\n    ASSERT_FALSE(raster_thread_merger->IsMerged());\n    create_thread_merger_latch.Signal();\n    raster_thread_merger->MergeWithLease(kNumFramesMerged);\n    term_raster.Wait();\n  });\n\n  latch_merged.Wait();\n  ASSERT_TRUE(raster_thread_merger->IsMerged());\n\n  for (size_t i = 0; i < kNumFramesMerged; i++) {\n    ASSERT_TRUE(raster_thread_merger->IsMerged());\n    raster_thread_merger->DecrementLease();\n  }\n\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n\n  term_platform.Signal();\n  term_raster.Signal();\n  thread_platform.join();\n  thread_raster.join();\n}\n\nTEST(RasterThreadMerger, HandleTaskQueuesAreTheSame) {\n  TaskQueueWrapper queue;\n  fml::TaskQueueId qid1 = queue.GetTaskQueueId();\n  fml::TaskQueueId qid2 = qid1;\n  const auto raster_thread_merger =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n  // Statically merged.\n  ASSERT_TRUE(raster_thread_merger->IsMerged());\n\n  // Test decrement lease and unmerge are both no-ops.\n  // The task queues should be always merged.\n  const size_t kNumFramesMerged = 5;\n  raster_thread_merger->MergeWithLease(kNumFramesMerged);\n\n  for (size_t i = 0; i < kNumFramesMerged; i++) {\n    ASSERT_TRUE(raster_thread_merger->IsMerged());\n    raster_thread_merger->DecrementLease();\n  }\n\n  ASSERT_TRUE(raster_thread_merger->IsMerged());\n\n  // Wait until merged should also return immediately.\n  raster_thread_merger->WaitUntilMerged();\n  ASSERT_TRUE(raster_thread_merger->IsMerged());\n}\n\nTEST(RasterThreadMerger, Enable) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n  const auto raster_thread_merger =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n\n  raster_thread_merger->Disable();\n  raster_thread_merger->MergeWithLease(1);\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->Enable();\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->MergeWithLease(1);\n  ASSERT_TRUE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->DecrementLease();\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n}\n\nTEST(RasterThreadMerger, Disable) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n  const auto raster_thread_merger =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n\n  raster_thread_merger->Disable();\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->MergeWithLease(1);\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->Enable();\n  raster_thread_merger->MergeWithLease(1);\n  ASSERT_TRUE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->Disable();\n  raster_thread_merger->UnMergeNowIfLastOne();\n  ASSERT_TRUE(raster_thread_merger->IsMerged());\n\n  {\n    auto decrement_result = raster_thread_merger->DecrementLease();\n    ASSERT_EQ(fml::RasterThreadStatus::kRemainsMerged, decrement_result);\n  }\n\n  ASSERT_TRUE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->Enable();\n  raster_thread_merger->UnMergeNowIfLastOne();\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n\n  raster_thread_merger->MergeWithLease(1);\n\n  ASSERT_TRUE(raster_thread_merger->IsMerged());\n\n  {\n    auto decrement_result = raster_thread_merger->DecrementLease();\n    ASSERT_EQ(fml::RasterThreadStatus::kUnmergedNow, decrement_result);\n  }\n\n  ASSERT_FALSE(raster_thread_merger->IsMerged());\n}\n\nTEST(RasterThreadMerger, IsEnabled) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n  const auto raster_thread_merger =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n  ASSERT_TRUE(raster_thread_merger->IsEnabled());\n\n  raster_thread_merger->Disable();\n  ASSERT_FALSE(raster_thread_merger->IsEnabled());\n\n  raster_thread_merger->Enable();\n  ASSERT_TRUE(raster_thread_merger->IsEnabled());\n}\n\nTEST(RasterThreadMerger, TwoMergersWithSameThreadPairShareEnabledState) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n  const auto merger1 =\n      RasterThreadMerger::CreateOrShareThreadMerger(nullptr, qid1, qid2);\n  const auto merger2 =\n      RasterThreadMerger::CreateOrShareThreadMerger(merger1, qid1, qid2);\n  ASSERT_TRUE(merger1->IsEnabled());\n  ASSERT_TRUE(merger2->IsEnabled());\n\n  merger1->Disable();\n  ASSERT_FALSE(merger1->IsEnabled());\n  ASSERT_FALSE(merger2->IsEnabled());\n\n  merger2->Enable();\n  ASSERT_TRUE(merger1->IsEnabled());\n  ASSERT_TRUE(merger2->IsEnabled());\n}\n\nTEST(RasterThreadMerger, RunExpiredTasksWhileFirstTaskMergesThreads) {\n  fml::MessageLoop* loop_platform = nullptr;\n  fml::AutoResetWaitableEvent latch1;\n  std::thread thread_platform([&loop_platform, &latch1]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop_platform = &fml::MessageLoop::GetCurrent();\n    loop_platform->GetTaskRunner()->PostTask([&]() { latch1.Signal(); });\n    loop_platform->Run();\n  });\n\n  fml::MessageLoop* loop_raster = nullptr;\n  fml::AutoResetWaitableEvent latch2;\n  std::thread thread_raster([&loop_raster, &loop_platform, &latch1, &latch2]() {\n    latch1.Wait();\n\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop_raster = &fml::MessageLoop::GetCurrent();\n    fml::TaskQueueId qid_platform =\n        loop_platform->GetTaskRunner()->GetTaskQueueId();\n    fml::TaskQueueId qid_raster =\n        loop_raster->GetTaskRunner()->GetTaskQueueId();\n    fml::CountDownLatch post_merge(2);\n    const auto raster_thread_merger =\n        fml::MakeRefCounted<fml::RasterThreadMerger>(qid_platform, qid_raster);\n    loop_raster->GetTaskRunner()->PostTask([&]() {\n      ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread());\n      ASSERT_FALSE(raster_thread_merger->IsOnPlatformThread());\n      ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid_raster);\n      raster_thread_merger->MergeWithLease(1);\n      post_merge.CountDown();\n    });\n\n    loop_raster->GetTaskRunner()->PostTask([&]() {\n      ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread());\n      ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread());\n      ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid_platform);\n      raster_thread_merger->DecrementLease();\n      post_merge.CountDown();\n    });\n\n    loop_raster->RunExpiredTasksNow();\n    post_merge.Wait();\n    latch2.Signal();\n  });\n\n  latch2.Wait();\n  loop_platform->GetTaskRunner()->PostTask(\n      [&]() { loop_platform->Terminate(); });\n\n  thread_platform.join();\n  thread_raster.join();\n}\n\nTEST(RasterThreadMerger, RunExpiredTasksWhileFirstTaskUnMergesThreads) {\n  fml::Thread platform_thread(\"test_platform_thread\");\n\n  fml::AutoResetWaitableEvent raster_latch;\n  std::thread thread_raster([&]() {\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    fml::MessageLoop* loop_raster = &fml::MessageLoop::GetCurrent();\n\n    fml::TaskQueueId qid_platform =\n        platform_thread.GetTaskRunner()->GetTaskQueueId();\n    fml::TaskQueueId qid_raster =\n        loop_raster->GetTaskRunner()->GetTaskQueueId();\n\n    fml::AutoResetWaitableEvent merge_latch;\n    const auto raster_thread_merger =\n        fml::MakeRefCounted<fml::RasterThreadMerger>(qid_platform, qid_raster);\n    loop_raster->GetTaskRunner()->PostTask([&]() {\n      raster_thread_merger->MergeWithLease(1);\n      merge_latch.Signal();\n    });\n\n    loop_raster->RunExpiredTasksNow();\n    merge_latch.Wait();\n\n    // threads should be merged at this point.\n    fml::AutoResetWaitableEvent unmerge_latch;\n    loop_raster->GetTaskRunner()->PostTask([&]() {\n      ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread());\n      ASSERT_TRUE(raster_thread_merger->IsOnPlatformThread());\n      ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid_platform);\n      raster_thread_merger->DecrementLease();\n      unmerge_latch.Signal();\n    });\n\n    fml::AutoResetWaitableEvent post_unmerge_latch;\n    loop_raster->GetTaskRunner()->PostTask([&]() {\n      ASSERT_TRUE(raster_thread_merger->IsOnRasterizingThread());\n      ASSERT_FALSE(raster_thread_merger->IsOnPlatformThread());\n      ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid_raster);\n      post_unmerge_latch.Signal();\n    });\n\n    unmerge_latch.Wait();\n    loop_raster->RunExpiredTasksNow();\n\n    post_unmerge_latch.Wait();\n    raster_latch.Signal();\n  });\n\n  raster_latch.Wait();\n  thread_raster.join();\n}\n\nTEST(RasterThreadMerger, SetMergeUnmergeCallback) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n\n  const auto raster_thread_merger =\n      fml::MakeRefCounted<fml::RasterThreadMerger>(qid1, qid2);\n\n  int callbacks = 0;\n  raster_thread_merger->SetMergeUnmergeCallback(\n      [&callbacks]() { callbacks++; });\n\n  ASSERT_EQ(0, callbacks);\n\n  raster_thread_merger->MergeWithLease(1);\n  ASSERT_EQ(1, callbacks);\n\n  raster_thread_merger->DecrementLease();\n  ASSERT_EQ(2, callbacks);\n}\n\nTEST(RasterThreadMerger, MultipleMergersCanMergeSameThreadPair) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n  // Two mergers will share one same inner merger\n  const auto raster_thread_merger1 =\n      fml::RasterThreadMerger::CreateOrShareThreadMerger(nullptr, qid1, qid2);\n  const auto raster_thread_merger2 =\n      fml::RasterThreadMerger::CreateOrShareThreadMerger(raster_thread_merger1,\n                                                         qid1, qid2);\n  const size_t kNumFramesMerged = 5;\n  ASSERT_FALSE(raster_thread_merger1->IsMerged());\n  ASSERT_FALSE(raster_thread_merger2->IsMerged());\n\n  // Merge using the first merger\n  raster_thread_merger1->MergeWithLease(kNumFramesMerged);\n\n  ASSERT_TRUE(raster_thread_merger1->IsMerged());\n  ASSERT_TRUE(raster_thread_merger2->IsMerged());\n\n  // let there be one more turn till the leases expire.\n  for (size_t i = 0; i < kNumFramesMerged - 1; i++) {\n    // Check merge state using the two merger\n    ASSERT_TRUE(raster_thread_merger1->IsMerged());\n    ASSERT_TRUE(raster_thread_merger2->IsMerged());\n    raster_thread_merger1->DecrementLease();\n  }\n\n  ASSERT_TRUE(raster_thread_merger1->IsMerged());\n  ASSERT_TRUE(raster_thread_merger2->IsMerged());\n\n  // extend the lease once with the first merger\n  raster_thread_merger1->ExtendLeaseTo(kNumFramesMerged);\n\n  // we will NOT last for 1 extra turn, we just set it.\n  for (size_t i = 0; i < kNumFramesMerged; i++) {\n    // Check merge state using the two merger\n    ASSERT_TRUE(raster_thread_merger1->IsMerged());\n    ASSERT_TRUE(raster_thread_merger2->IsMerged());\n    raster_thread_merger1->DecrementLease();\n  }\n\n  ASSERT_FALSE(raster_thread_merger1->IsMerged());\n  ASSERT_FALSE(raster_thread_merger2->IsMerged());\n}\n\nTEST(RasterThreadMerger, TheLastCallerOfMultipleMergersCanUnmergeNow) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n  // Two mergers will share one same inner merger\n  const auto raster_thread_merger1 =\n      fml::RasterThreadMerger::CreateOrShareThreadMerger(nullptr, qid1, qid2);\n  const auto raster_thread_merger2 =\n      fml::RasterThreadMerger::CreateOrShareThreadMerger(raster_thread_merger1,\n                                                         qid1, qid2);\n  const size_t kNumFramesMerged = 5;\n  ASSERT_FALSE(raster_thread_merger1->IsMerged());\n  ASSERT_FALSE(raster_thread_merger2->IsMerged());\n\n  // Merge using the mergers\n  raster_thread_merger1->MergeWithLease(kNumFramesMerged);\n  ASSERT_TRUE(raster_thread_merger1->IsMerged());\n  ASSERT_TRUE(raster_thread_merger2->IsMerged());\n  // Extend the second merger's lease\n  raster_thread_merger2->ExtendLeaseTo(kNumFramesMerged);\n  ASSERT_TRUE(raster_thread_merger1->IsMerged());\n  ASSERT_TRUE(raster_thread_merger2->IsMerged());\n\n  // Two callers state becomes one caller left.\n  raster_thread_merger1->UnMergeNowIfLastOne();\n  // Check if still merged\n  ASSERT_TRUE(raster_thread_merger1->IsMerged());\n  ASSERT_TRUE(raster_thread_merger2->IsMerged());\n\n  // One caller state becomes no callers left.\n  raster_thread_merger2->UnMergeNowIfLastOne();\n  // Check if unmerged\n  ASSERT_FALSE(raster_thread_merger1->IsMerged());\n  ASSERT_FALSE(raster_thread_merger2->IsMerged());\n}\n\n/// This case tests multiple standalone engines using independent merger to\n/// merge two different raster threads into the same platform thread.\nTEST(RasterThreadMerger,\n     TwoIndependentMergersCanMergeTwoDifferentThreadsIntoSamePlatformThread) {\n  TaskQueueWrapper queue1;\n  TaskQueueWrapper queue2;\n  TaskQueueWrapper queue3;\n  fml::TaskQueueId qid1 = queue1.GetTaskQueueId();\n  fml::TaskQueueId qid2 = queue2.GetTaskQueueId();\n  fml::TaskQueueId qid3 = queue3.GetTaskQueueId();\n\n  // Two mergers will NOT share same inner merger\n  const auto merger_from_2_to_1 =\n      fml::RasterThreadMerger::CreateOrShareThreadMerger(nullptr, qid1, qid2);\n  const auto merger_from_3_to_1 =\n      fml::RasterThreadMerger::CreateOrShareThreadMerger(merger_from_2_to_1,\n                                                         qid1, qid3);\n  const size_t kNumFramesMerged = 5;\n  ASSERT_FALSE(merger_from_2_to_1->IsMerged());\n  ASSERT_FALSE(merger_from_3_to_1->IsMerged());\n\n  // Merge thread2 into thread1\n  merger_from_2_to_1->MergeWithLease(kNumFramesMerged);\n  // Merge thread3 into thread1\n  merger_from_3_to_1->MergeWithLease(kNumFramesMerged);\n\n  ASSERT_TRUE(merger_from_2_to_1->IsMerged());\n  ASSERT_TRUE(merger_from_3_to_1->IsMerged());\n\n  for (size_t i = 0; i < kNumFramesMerged; i++) {\n    ASSERT_TRUE(merger_from_2_to_1->IsMerged());\n    merger_from_2_to_1->DecrementLease();\n  }\n\n  ASSERT_FALSE(merger_from_2_to_1->IsMerged());\n  ASSERT_TRUE(merger_from_3_to_1->IsMerged());\n\n  for (size_t i = 0; i < kNumFramesMerged; i++) {\n    ASSERT_TRUE(merger_from_3_to_1->IsMerged());\n    merger_from_3_to_1->DecrementLease();\n  }\n\n  ASSERT_FALSE(merger_from_2_to_1->IsMerged());\n  ASSERT_FALSE(merger_from_3_to_1->IsMerged());\n\n  merger_from_2_to_1->MergeWithLease(kNumFramesMerged);\n  ASSERT_TRUE(merger_from_2_to_1->IsMerged());\n  ASSERT_FALSE(merger_from_3_to_1->IsMerged());\n  merger_from_3_to_1->MergeWithLease(kNumFramesMerged);\n  ASSERT_TRUE(merger_from_2_to_1->IsMerged());\n  ASSERT_TRUE(merger_from_3_to_1->IsMerged());\n\n  // Can unmerge independently\n  merger_from_2_to_1->UnMergeNowIfLastOne();\n  ASSERT_FALSE(merger_from_2_to_1->IsMerged());\n  ASSERT_TRUE(merger_from_3_to_1->IsMerged());\n\n  // Can unmerge independently\n  merger_from_3_to_1->UnMergeNowIfLastOne();\n  ASSERT_FALSE(merger_from_2_to_1->IsMerged());\n  ASSERT_FALSE(merger_from_3_to_1->IsMerged());\n}\n\n}  // namespace testing\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/shared_thread_merger.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"base/include/fml/shared_thread_merger.h\"\n\n#include <algorithm>\n#include <set>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace fml {\n\nSharedThreadMerger::SharedThreadMerger(fml::TaskQueueId owner,\n                                       fml::TaskQueueId subsumed)\n    : owner_(owner),\n      subsumed_(subsumed),\n      task_queues_(fml::MessageLoopTaskQueues::GetInstance()),\n      enabled_(true) {}\n\nbool SharedThreadMerger::MergeWithLease(RasterThreadMergerId caller,\n                                        size_t lease_term) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(lease_term > 0) << \"lease_term should be positive.\";\n  std::scoped_lock lock(mutex_);\n  if (IsMergedUnSafe()) {\n    return true;\n  }\n  bool success = task_queues_->Merge(owner_, subsumed_);\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(success);\n  // << \"Unable to merge the raster and platform threads.\";\n  // Save the lease term\n  lease_term_by_caller_[caller] = lease_term;\n  return success;\n}\n\nbool SharedThreadMerger::UnMergeNowUnSafe() {\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(IsAllLeaseTermsZeroUnSafe());\n  // << \"all lease term records must be zero before calling \"\n  //  \"UnMergeNowUnSafe()\";\n  bool success = task_queues_->Unmerge(owner_, subsumed_);\n  LYNX_BASE_CHECK(success);\n  // << \"Unable to un-merge the raster and platform threads.\";\n  return success;\n}\n\nbool SharedThreadMerger::UnMergeNowIfLastOne(RasterThreadMergerId caller) {\n  std::scoped_lock lock(mutex_);\n  lease_term_by_caller_.erase(caller);\n  if (!lease_term_by_caller_.empty()) {\n    return true;\n  }\n  return UnMergeNowUnSafe();\n}\n\nbool SharedThreadMerger::DecrementLease(RasterThreadMergerId caller) {\n  std::scoped_lock lock(mutex_);\n  auto entry = lease_term_by_caller_.find(caller);\n  bool exist = entry != lease_term_by_caller_.end();\n  if (exist) {\n    std::atomic_size_t& lease_term_ref = entry->second;\n    // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK\n    // available.\n    LYNX_BASE_CHECK(lease_term_ref > 0);\n    // << \"lease_term should always be positive when merged, lease_term=\"\n    // << lease_term_ref;\n    lease_term_ref--;\n  } else {\n    // TODO(zhengsenyao): Uncomment LOGW code when LOGW available.\n    // LOGW(\n    //     \"The caller does not exist when calling \"\n    //     \"DecrementLease(), ignored. This may happens after \"\n    //     \"caller is erased in UnMergeNowIfLastOne(). caller=\"\n    //     << caller);\n  }\n  if (IsAllLeaseTermsZeroUnSafe()) {\n    // Unmerge now because lease_term_ decreased to zero.\n    UnMergeNowUnSafe();\n    return true;\n  }\n  return false;\n}\n\nvoid SharedThreadMerger::ExtendLeaseTo(RasterThreadMergerId caller,\n                                       size_t lease_term) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(lease_term > 0) << \"lease_term should be positive.\";\n  std::scoped_lock lock(mutex_);\n  // DCHECK(IsMergedUnSafe()) << \"should be merged state when calling this\n  // method\";\n  lease_term_by_caller_[caller] = lease_term;\n}\n\nbool SharedThreadMerger::IsMergedUnSafe() const {\n  return !IsAllLeaseTermsZeroUnSafe();\n}\n\nbool SharedThreadMerger::IsEnabledUnSafe() const { return enabled_; }\n\nvoid SharedThreadMerger::SetEnabledUnSafe(bool enabled) { enabled_ = enabled; }\n\nbool SharedThreadMerger::IsAllLeaseTermsZeroUnSafe() const {\n  return std::all_of(lease_term_by_caller_.begin(), lease_term_by_caller_.end(),\n                     [&](const auto& item) { return item.second == 0; });\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/synchronization/count_down_latch.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n\nnamespace lynx {\nnamespace fml {\n\nCountDownLatch::CountDownLatch(size_t count) : count_(count) {\n  if (count_ == 0) {\n    waitable_event_.Signal();\n  }\n}\n\nCountDownLatch::~CountDownLatch() = default;\n\nvoid CountDownLatch::Wait() { waitable_event_.Wait(); }\n\nvoid CountDownLatch::CountDown() {\n  if (--count_ == 0) {\n    waitable_event_.Signal();\n  }\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/synchronization/count_down_latch_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <chrono>\n#include <thread>\n\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"base/include/fml/thread.h\"\n#include \"build/build_config.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\n\nTEST(CountDownLatchTest, CanWaitOnZero) {\n  CountDownLatch latch(0);\n  latch.Wait();\n}\n\nTEST(CountDownLatchTest, CanWait) {\n  fml::Thread thread(\"test_thread\");\n  const size_t count = 100;\n  size_t current_count = 0;\n  CountDownLatch latch(count);\n  auto decrement_latch_on_thread = [runner = thread.GetTaskRunner(), &latch,\n                                    &current_count]() {\n    runner->PostTask([&latch, &current_count]() {\n      std::this_thread::sleep_for(std::chrono::microseconds(100));\n      current_count++;\n      latch.CountDown();\n    });\n  };\n  for (size_t i = 0; i < count; ++i) {\n    decrement_latch_on_thread();\n  }\n  latch.Wait();\n  ASSERT_EQ(current_count, count);\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/synchronization/semaphore.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"base/include/fml/synchronization/semaphore.h\"\n\n#include \"base/include/fml/macros.h\"\n#include \"build/build_config.h\"\n\n#if OS_MACOSX\n#include <dispatch/dispatch.h>\n\nnamespace lynx {\nnamespace fml {\n\nclass PlatformSemaphore {\n public:\n  explicit PlatformSemaphore(uint32_t count)\n      : sem_(dispatch_semaphore_create(count)), initial_(count) {}\n\n  ~PlatformSemaphore() {\n    for (uint32_t i = 0; i < initial_; ++i) {\n      Signal();\n    }\n    if (sem_ != nullptr) {\n      dispatch_release(reinterpret_cast<dispatch_object_t>(sem_));\n      sem_ = nullptr;\n    }\n  }\n\n  bool IsValid() const { return sem_ != nullptr; }\n\n  bool Wait() {\n    if (sem_ == nullptr) {\n      return false;\n    }\n    return dispatch_semaphore_wait(sem_, DISPATCH_TIME_FOREVER) == 0;\n  }\n\n  bool TryWait() {\n    if (sem_ == nullptr) {\n      return false;\n    }\n\n    return dispatch_semaphore_wait(sem_, DISPATCH_TIME_NOW) == 0;\n  }\n\n  void Signal() {\n    if (sem_ != nullptr) {\n      dispatch_semaphore_signal(sem_);\n    }\n  }\n\n private:\n  dispatch_semaphore_t sem_;\n  const uint32_t initial_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PlatformSemaphore);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#elif OS_WIN\n#include <windows.h>\n\nnamespace lynx {\nnamespace fml {\n\nclass PlatformSemaphore {\n public:\n  explicit PlatformSemaphore(uint32_t count)\n      : _sem(CreateSemaphore(NULL, count, LONG_MAX, NULL)) {}\n\n  ~PlatformSemaphore() {\n    if (_sem != nullptr) {\n      CloseHandle(_sem);\n      _sem = nullptr;\n    }\n  }\n\n  bool IsValid() const { return _sem != nullptr; }\n\n  bool Wait() {\n    if (_sem == nullptr) {\n      return false;\n    }\n\n    return WaitForSingleObject(_sem, INFINITE) == WAIT_OBJECT_0;\n  }\n\n  bool TryWait() {\n    if (_sem == nullptr) {\n      return false;\n    }\n\n    return WaitForSingleObject(_sem, 0) == WAIT_OBJECT_0;\n  }\n\n  void Signal() {\n    if (_sem != nullptr) {\n      ReleaseSemaphore(_sem, 1, NULL);\n    }\n  }\n\n private:\n  HANDLE _sem;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PlatformSemaphore);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#else\n#include <semaphore.h>\n\n#include \"base/include/fml/eintr_wrapper.h\"\n\nnamespace lynx {\nnamespace fml {\n\nclass PlatformSemaphore {\n public:\n  explicit PlatformSemaphore(uint32_t count)\n      : valid_(::sem_init(&sem_, 0 /* not shared */, count) == 0) {}\n\n  ~PlatformSemaphore() {\n    if (valid_) {\n      int result = ::sem_destroy(&sem_);\n      (void)result;\n      // Can only be EINVAL which should not be possible since we checked for\n      // validity.\n      LYNX_BASE_DCHECK(result == 0);\n    }\n  }\n\n  bool IsValid() const { return valid_; }\n\n  bool Wait() {\n    if (!valid_) {\n      return false;\n    }\n\n    return FML_HANDLE_EINTR(::sem_wait(&sem_)) == 0;\n  }\n\n  bool TryWait() {\n    if (!valid_) {\n      return false;\n    }\n\n    return FML_HANDLE_EINTR(::sem_trywait(&sem_)) == 0;\n  }\n\n  void Signal() {\n    if (!valid_) {\n      return;\n    }\n\n    ::sem_post(&sem_);\n\n    return;\n  }\n\n private:\n  bool valid_;\n  sem_t sem_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PlatformSemaphore);\n};\n\n}  // namespace fml\n}  // namespace lynx\n\n#endif\n\nnamespace lynx {\nnamespace fml {\n\nSemaphore::Semaphore(uint32_t count) : _impl(new PlatformSemaphore(count)) {}\n\nSemaphore::~Semaphore() = default;\n\nbool Semaphore::IsValid() const { return _impl->IsValid(); }\n\nbool Semaphore::Wait() { return _impl->Wait(); }\n\nbool Semaphore::TryWait() { return _impl->TryWait(); }\n\nvoid Semaphore::Signal() { return _impl->Signal(); }\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/synchronization/semaphore_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"base/include/fml/synchronization/semaphore.h\"\n\n#include <thread>\n\n#include \"base/include/fml/thread.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nTEST(SemaphoreTest, SimpleValidity) {\n  fml::Semaphore sem(100);\n  ASSERT_TRUE(sem.IsValid());\n}\n\nTEST(SemaphoreTest, WaitOnZero) {\n  fml::Semaphore sem(0);\n  ASSERT_FALSE(sem.TryWait());\n}\n\nTEST(SemaphoreTest, WaitOnZeroSignalThenWait) {\n  fml::Semaphore sem(0);\n  ASSERT_FALSE(sem.TryWait());\n  std::thread thread([&sem]() { sem.Signal(); });\n  thread.join();\n  ASSERT_TRUE(sem.TryWait());\n  ASSERT_FALSE(sem.TryWait());\n}\n\nTEST(SemaphoreTest, IndefiniteWait) {\n  auto start = fml::TimePoint::Now();\n  constexpr double wait_in_seconds = 0.25;\n  fml::Semaphore sem(0);\n  ASSERT_TRUE(sem.IsValid());\n  fml::Thread signaller(\"signaller_thread\");\n  signaller.GetTaskRunner()->PostTaskForTime(\n      [&sem]() { sem.Signal(); },\n      start + fml::TimeDelta::FromSecondsF(wait_in_seconds));\n  ASSERT_TRUE(sem.Wait());\n  auto delta = fml::TimePoint::Now() - start;\n  ASSERT_GE(delta.ToSecondsF(), wait_in_seconds);\n  signaller.Join();\n}\n"
  },
  {
    "path": "base/src/fml/synchronization/shared_mutex_posix.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"base/include/fml/synchronization/shared_mutex_posix.h\"\n\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace fml {\n\nSharedMutex* SharedMutex::Create() { return new SharedMutexPosix(); }\n\nSharedMutexPosix::SharedMutexPosix() {\n  LYNX_BASE_CHECK(pthread_rwlock_init(&rwlock_, nullptr) == 0);\n}\n\nvoid SharedMutexPosix::Lock() { pthread_rwlock_wrlock(&rwlock_); }\n\nvoid SharedMutexPosix::LockShared() { pthread_rwlock_rdlock(&rwlock_); }\n\nvoid SharedMutexPosix::Unlock() { pthread_rwlock_unlock(&rwlock_); }\n\nvoid SharedMutexPosix::UnlockShared() { pthread_rwlock_unlock(&rwlock_); }\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/synchronization/shared_mutex_std.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"base/include/fml/synchronization/shared_mutex_std.h\"\n\nnamespace lynx {\nnamespace fml {\n\nSharedMutex* SharedMutex::Create() { return new SharedMutexStd(); }\n\nvoid SharedMutexStd::Lock() { mutex_.lock(); }\n\nvoid SharedMutexStd::LockShared() { mutex_.lock_shared(); }\n\nvoid SharedMutexStd::Unlock() { mutex_.unlock(); }\n\nvoid SharedMutexStd::UnlockShared() { mutex_.unlock_shared(); }\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/synchronization/sync_switch.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"base/include/fml/synchronization/sync_switch.h\"\n\nnamespace lynx {\nnamespace fml {\n\nSyncSwitch::Handlers& SyncSwitch::Handlers::SetIfTrue(\n    const std::function<void()>& handler) {\n  true_handler = handler;\n  return *this;\n}\n\nSyncSwitch::Handlers& SyncSwitch::Handlers::SetIfFalse(\n    const std::function<void()>& handler) {\n  false_handler = handler;\n  return *this;\n}\n\nSyncSwitch::SyncSwitch(bool value)\n    : mutex_(std::unique_ptr<fml::SharedMutex>(fml::SharedMutex::Create())),\n      value_(value) {}\n\nvoid SyncSwitch::Execute(const SyncSwitch::Handlers& handlers) const {\n  fml::SharedLock lock(*mutex_);\n  if (value_) {\n    handlers.true_handler();\n  } else {\n    handlers.false_handler();\n  }\n}\n\nvoid SyncSwitch::SetSwitch(bool value) {\n  fml::UniqueLock lock(*mutex_);\n  value_ = value;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/synchronization/sync_switch_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"base/include/fml/synchronization/sync_switch.h\"\n\n#include <thread>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nusing lynx::fml::SyncSwitch;\n\nTEST(SyncSwitchTest, Basic) {\n  SyncSwitch sync_switch;\n  bool switch_value = false;\n  sync_switch.Execute(SyncSwitch::Handlers()\n                          .SetIfTrue([&] { switch_value = true; })\n                          .SetIfFalse([&] { switch_value = false; }));\n  EXPECT_FALSE(switch_value);\n  sync_switch.SetSwitch(true);\n  sync_switch.Execute(SyncSwitch::Handlers()\n                          .SetIfTrue([&] { switch_value = true; })\n                          .SetIfFalse([&] { switch_value = false; }));\n  EXPECT_TRUE(switch_value);\n}\n\nTEST(SyncSwitchTest, NoopIfUndefined) {\n  SyncSwitch sync_switch;\n  bool switch_value = false;\n  sync_switch.Execute(SyncSwitch::Handlers());\n  EXPECT_FALSE(switch_value);\n}\n\nTEST(SyncSwitchTest, SharedLock) {\n  SyncSwitch sync_switch;\n  sync_switch.SetSwitch(true);\n  bool switch_value1 = false;\n  bool switch_value2 = false;\n\n  std::thread thread1([&] {\n    sync_switch.Execute(\n        SyncSwitch::Handlers()\n            .SetIfTrue([&] {\n              switch_value1 = true;\n\n              std::thread thread2([&]() {\n                sync_switch.Execute(\n                    SyncSwitch::Handlers()\n                        .SetIfTrue([&] { switch_value2 = true; })\n                        .SetIfFalse([&] { switch_value2 = false; }));\n              });\n              thread2.join();\n            })\n            .SetIfFalse([&] { switch_value1 = false; }));\n  });\n  thread1.join();\n  EXPECT_TRUE(switch_value1);\n  EXPECT_TRUE(switch_value2);\n}\n"
  },
  {
    "path": "base/src/fml/synchronization/waitable_event.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/synchronization/waitable_event.h\"\n\n#include <cerrno>\n#include <ctime>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace lynx {\nnamespace fml {\n\n// Waits with a timeout on |condition()|. Returns true on timeout, or false if\n// |condition()| ever returns true. |condition()| should have no side effects\n// (and will always be called with |*mutex| held).\ntemplate <typename ConditionFn>\nbool WaitWithTimeoutImpl(std::unique_lock<std::mutex>* locker,\n                         std::condition_variable* cv, ConditionFn condition,\n                         TimeDelta timeout) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(locker->owns_lock());\n\n  if (condition()) {\n    return false;\n  }\n\n  // We may get spurious wakeups.\n  TimeDelta wait_remaining = timeout;\n  TimePoint start = TimePoint::Now();\n  while (true) {\n    if (std::cv_status::timeout ==\n        cv->wait_for(*locker, std::chrono::nanoseconds(\n                                  wait_remaining.ToNanoseconds()))) {\n      return true;  // Definitely timed out.\n    }\n\n    // We may have been awoken.\n    if (condition()) {\n      return false;\n    }\n\n    // Or the wakeup may have been spurious.\n    TimePoint now = TimePoint::Now();\n    // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n    // DCHECK(now >= start);\n    TimeDelta elapsed = now - start;\n    // It's possible that we may have timed out anyway.\n    if (elapsed >= timeout) {\n      return true;\n    }\n\n    // Otherwise, recalculate the amount that we have left to wait.\n    wait_remaining = timeout - elapsed;\n  }\n}\n\n// AutoResetWaitableEvent ------------------------------------------------------\n\nvoid AutoResetWaitableEvent::Signal() {\n  std::scoped_lock locker(mutex_);\n  signaled_ = true;\n  cv_.notify_one();\n}\n\nvoid AutoResetWaitableEvent::Reset() {\n  std::scoped_lock locker(mutex_);\n  signaled_ = false;\n}\n\nvoid AutoResetWaitableEvent::Wait() {\n  std::unique_lock<std::mutex> locker(mutex_);\n  while (!signaled_) {\n    cv_.wait(locker);\n  }\n  signaled_ = false;\n}\n\nbool AutoResetWaitableEvent::WaitWithTimeout(TimeDelta timeout) {\n  std::unique_lock<std::mutex> locker(mutex_);\n\n  if (signaled_) {\n    signaled_ = false;\n    return false;\n  }\n\n  // We may get spurious wakeups.\n  TimeDelta wait_remaining = timeout;\n  TimePoint start = TimePoint::Now();\n  while (true) {\n    if (std::cv_status::timeout ==\n        cv_.wait_for(\n            locker, std::chrono::nanoseconds(wait_remaining.ToNanoseconds()))) {\n      return true;  // Definitely timed out.\n    }\n\n    // We may have been awoken.\n    if (signaled_) {\n      break;\n    }\n\n    // Or the wakeup may have been spurious.\n    TimePoint now = TimePoint::Now();\n    // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n    // DCHECK(now >= start);\n    TimeDelta elapsed = now - start;\n    // It's possible that we may have timed out anyway.\n    if (elapsed >= timeout) {\n      return true;\n    }\n\n    // Otherwise, recalculate the amount that we have left to wait.\n    wait_remaining = timeout - elapsed;\n  }\n\n  signaled_ = false;\n  return false;\n}\n\nbool AutoResetWaitableEvent::IsSignaledForTest() {\n  std::scoped_lock locker(mutex_);\n  return signaled_;\n}\n\n// ManualResetWaitableEvent ----------------------------------------------------\n\nvoid ManualResetWaitableEvent::Signal() {\n  std::scoped_lock locker(mutex_);\n  signaled_ = true;\n  signal_id_++;\n  cv_.notify_all();\n}\n\nvoid ManualResetWaitableEvent::Reset() {\n  std::scoped_lock locker(mutex_);\n  signaled_ = false;\n}\n\nvoid ManualResetWaitableEvent::Wait() {\n  std::unique_lock<std::mutex> locker(mutex_);\n\n  if (signaled_) {\n    return;\n  }\n\n  auto last_signal_id = signal_id_;\n  do {\n    cv_.wait(locker);\n  } while (signal_id_ == last_signal_id);\n}\n\nbool ManualResetWaitableEvent::WaitWithTimeout(TimeDelta timeout) {\n  std::unique_lock<std::mutex> locker(mutex_);\n\n  auto last_signal_id = signal_id_;\n  // Disable thread-safety analysis for the lambda: We could annotate it with\n  // |FML_EXCLUSIVE_LOCKS_REQUIRED(mutex_)|, but then the analyzer currently\n  // isn't able to figure out that |WaitWithTimeoutImpl()| calls it while\n  // holding |mutex_|.\n  bool rv = WaitWithTimeoutImpl(\n      &locker, &cv_,\n      [this, last_signal_id]() {\n        // Also check |signaled_| in case we're already signaled.\n        return signaled_ || signal_id_ != last_signal_id;\n      },\n      timeout);\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(rv || signaled_ || signal_id_ != last_signal_id);\n  return rv;\n}\n\nbool ManualResetWaitableEvent::IsSignaledForTest() {\n  std::scoped_lock locker(mutex_);\n  return signaled_;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/synchronization/waitable_event_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/synchronization/waitable_event.h\"\n\n#include <atomic>\n#include <cstddef>\n#include <cstdint>\n#include <cstdlib>\n#include <thread>\n#include <type_traits>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n// rand() is only used for tests in this file.\n// NOLINTBEGIN(clang-analyzer-security.insecureAPI.rand)\n\nnamespace lynx {\nnamespace fml {\nnamespace {\n\nconstexpr TimeDelta kEpsilonTimeout = TimeDelta::FromMilliseconds(20);\nconstexpr TimeDelta kTinyTimeout = TimeDelta::FromMilliseconds(100);\nconstexpr TimeDelta kActionTimeout = TimeDelta::FromMilliseconds(10000);\n\n// Sleeps for a \"very small\" amount of time.\n\nvoid SleepFor(TimeDelta duration) {\n  std::this_thread::sleep_for(\n      std::chrono::nanoseconds(duration.ToNanoseconds()));\n}\n\nvoid EpsilonRandomSleep() {\n  TimeDelta duration =\n      TimeDelta::FromMilliseconds(static_cast<unsigned>(rand()) % 20u);\n  SleepFor(duration);\n}\n\n// AutoResetWaitableEvent ------------------------------------------------------\n\nTEST(AutoResetWaitableEventTest, Basic) {\n  AutoResetWaitableEvent ev;\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  ev.Signal();\n  EXPECT_TRUE(ev.IsSignaledForTest());\n  ev.Wait();\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  ev.Reset();\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  ev.Signal();\n  EXPECT_TRUE(ev.IsSignaledForTest());\n  ev.Reset();\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  EXPECT_TRUE(ev.WaitWithTimeout(TimeDelta::Zero()));\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  EXPECT_TRUE(ev.WaitWithTimeout(TimeDelta::FromMilliseconds(1)));\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  ev.Signal();\n  EXPECT_TRUE(ev.IsSignaledForTest());\n  EXPECT_FALSE(ev.WaitWithTimeout(TimeDelta::Zero()));\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  EXPECT_TRUE(ev.WaitWithTimeout(TimeDelta::FromMilliseconds(1)));\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  ev.Signal();\n  EXPECT_FALSE(ev.WaitWithTimeout(TimeDelta::FromMilliseconds(1)));\n  EXPECT_FALSE(ev.IsSignaledForTest());\n}\n\nTEST(AutoResetWaitableEventTest, MultipleWaiters) {\n  AutoResetWaitableEvent ev;\n\n  for (size_t i = 0u; i < 5u; i++) {\n    std::atomic_uint wake_count(0u);\n    std::vector<std::thread> threads;\n    for (size_t j = 0u; j < 4u; j++) {\n      threads.push_back(std::thread([&ev, &wake_count]() {\n        if (rand() % 2 == 0) {\n          ev.Wait();\n        } else {\n          EXPECT_FALSE(ev.WaitWithTimeout(kActionTimeout));\n        }\n        wake_count.fetch_add(1u);\n        // Note: We can't say anything about the signaled state of |ev| here,\n        // since the main thread may have already signaled it again.\n      }));\n    }\n\n    // Unfortunately, we can't really wait for the threads to be waiting, so we\n    // just sleep for a bit, and count on them having started and advanced to\n    // waiting.\n    SleepFor(kTinyTimeout + kTinyTimeout);\n\n    for (size_t j = 0u; j < threads.size(); j++) {\n      unsigned old_wake_count = wake_count.load();\n      EXPECT_EQ(j, old_wake_count);\n\n      // Each |Signal()| should wake exactly one thread.\n      ev.Signal();\n\n      // Poll for |wake_count| to change.\n      while (wake_count.load() == old_wake_count) {\n        SleepFor(kEpsilonTimeout);\n      }\n\n      EXPECT_FALSE(ev.IsSignaledForTest());\n\n      // And once it's changed, wait a little longer, to see if any other\n      // threads are awoken (they shouldn't be).\n      SleepFor(kEpsilonTimeout);\n\n      EXPECT_EQ(old_wake_count + 1u, wake_count.load());\n\n      EXPECT_FALSE(ev.IsSignaledForTest());\n    }\n\n    // Having done that, if we signal |ev| now, it should stay signaled.\n    ev.Signal();\n    SleepFor(kEpsilonTimeout);\n    EXPECT_TRUE(ev.IsSignaledForTest());\n\n    for (auto& thread : threads) {\n      thread.join();\n    }\n\n    ev.Reset();\n  }\n}\n\n// ManualResetWaitableEvent ----------------------------------------------------\n\nTEST(ManualResetWaitableEventTest, Basic) {\n  ManualResetWaitableEvent ev;\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  ev.Signal();\n  EXPECT_TRUE(ev.IsSignaledForTest());\n  ev.Wait();\n  EXPECT_TRUE(ev.IsSignaledForTest());\n  ev.Reset();\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  EXPECT_TRUE(ev.WaitWithTimeout(TimeDelta::Zero()));\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  EXPECT_TRUE(ev.WaitWithTimeout(TimeDelta::FromMilliseconds(1)));\n  EXPECT_FALSE(ev.IsSignaledForTest());\n  ev.Signal();\n  EXPECT_TRUE(ev.IsSignaledForTest());\n  EXPECT_FALSE(ev.WaitWithTimeout(TimeDelta::Zero()));\n  EXPECT_TRUE(ev.IsSignaledForTest());\n  EXPECT_FALSE(ev.WaitWithTimeout(TimeDelta::FromMilliseconds(1)));\n  EXPECT_TRUE(ev.IsSignaledForTest());\n}\n\nTEST(ManualResetWaitableEventTest, SignalMultiple) {\n  ManualResetWaitableEvent ev;\n\n  for (size_t i = 0u; i < 10u; i++) {\n    for (size_t num_waiters = 1u; num_waiters < 5u; num_waiters++) {\n      std::vector<std::thread> threads;\n      for (size_t j = 0u; j < num_waiters; j++) {\n        threads.push_back(std::thread([&ev]() {\n          EpsilonRandomSleep();\n\n          if (rand() % 2 == 0) {\n            ev.Wait();\n          } else {\n            EXPECT_FALSE(ev.WaitWithTimeout(kActionTimeout));\n          }\n        }));\n      }\n\n      EpsilonRandomSleep();\n\n      ev.Signal();\n\n      // The threads will only terminate once they've successfully waited (or\n      // timed out).\n      for (auto& thread : threads) {\n        thread.join();\n      }\n\n      ev.Reset();\n    }\n  }\n}\n\n}  // namespace\n}  // namespace fml\n}  // namespace lynx\n\n// NOLINTEND(clang-analyzer-security.insecureAPI.rand)\n"
  },
  {
    "path": "base/src/fml/task_runner.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"base/include/fml/task_runner.h\"\n\n#include <utility>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/task_runner_delegate.h\"\n\nnamespace lynx {\nnamespace fml {\n\nTaskRunner::TaskRunner(fml::RefPtr<MessageLoopImpl> loop,\n                       bool is_aligned_with_vsync)\n    : queue_id_(MessageLoopTaskQueues::GetInstance()->CreateTaskQueue(\n          is_aligned_with_vsync)) {\n  BindOnCreate(loop);\n}\n\nvoid TaskRunner::BindOnCreate(fml::RefPtr<MessageLoopImpl> loop) {\n  if (!loop) {\n    return;\n  }\n  MessageLoop* current_loop = fml::MessageLoop::IsInitializedForCurrentThread();\n  loop_ = std::move(loop);\n  if (current_loop != nullptr && current_loop->GetLoopImpl() == loop_) {\n    loop_->Bind(queue_id_);\n  } else {\n    loop_->PostTask(\n        [unbound = unbound_, loop = loop_, queue_id = queue_id_]() {\n          if (*unbound) {\n            return;\n          }\n          loop->Bind(queue_id);\n        },\n        fml::TimePoint::Now(), TaskSourceGrade::kEmergency);\n  }\n}\n\nTaskRunner::~TaskRunner() {\n  if (loop_) {\n    loop_->PostTask(\n        [loop = loop_, queue_id = queue_id_]() {\n          loop->UnBind(queue_id);\n          MessageLoopTaskQueues::GetInstance()->DisposeTasks(queue_id);\n          MessageLoopTaskQueues::GetInstance()->Dispose(queue_id);\n        },\n        fml::TimePoint::Now(), fml::TaskSourceGrade::kEmergency);\n  } else {\n    MessageLoopTaskQueues::GetInstance()->DisposeTasks(queue_id_);\n    MessageLoopTaskQueues::GetInstance()->Dispose(queue_id_);\n  }\n};\n\nvoid TaskRunner::PostTask(base::closure task) {\n  if (delegate_) {\n    delegate_->PostTask(std::move(task));\n    return;\n  }\n  MessageLoopTaskQueues::GetInstance()->RegisterTask(\n      queue_id_, std::move(task), fml::TimePoint::Now(),\n      fml::TaskSourceGrade::kUnspecified, true);\n}\n\nvoid TaskRunner::PostEmergencyTask(base::closure task) {\n  if (delegate_) {\n    delegate_->PostTask(std::move(task));\n    return;\n  }\n  MessageLoopTaskQueues::GetInstance()->RegisterTask(\n      queue_id_, std::move(task), fml::TimePoint::Now(),\n      fml::TaskSourceGrade::kEmergency, true);\n}\n\nvoid TaskRunner::PostMicroTask(base::closure task) {\n  MessageLoopTaskQueues::GetInstance()->RegisterTask(\n      queue_id_, std::move(task), fml::TimePoint::Now(),\n      fml::TaskSourceGrade::kMicrotask, true);\n}\n\nvoid TaskRunner::PostIdleTask(base::closure task) {\n  if (delegate_) {\n    delegate_->PostTask(std::move(task));\n    return;\n  }\n  MessageLoopTaskQueues::GetInstance()->RegisterTask(\n      queue_id_, std::move(task), fml::TimePoint::Now(),\n      fml::TaskSourceGrade::kIdle);\n}\n\nvoid TaskRunner::PostSyncTask(base::closure task) {\n  if (RunsTasksOnCurrentThread()) {\n    task();\n  } else {\n    fml::AutoResetWaitableEvent arwe;\n    PostTask([task = std::move(task), &arwe]() {\n      task();\n      arwe.Signal();\n    });\n    arwe.Wait();\n  }\n}\n\nvoid TaskRunner::PostTaskForTime(base::closure task,\n                                 fml::TimePoint target_time) {\n  if (delegate_) {\n    delegate_->PostTaskForTime(std::move(task),\n                               target_time.ToEpochDelta().ToMicroseconds());\n    return;\n  }\n  MessageLoopTaskQueues::GetInstance()->RegisterTask(\n      queue_id_, std::move(task), target_time,\n      fml::TaskSourceGrade::kUnspecified);\n}\n\nvoid TaskRunner::PostDelayedTask(base::closure task, fml::TimeDelta delay) {\n  if (delegate_) {\n    delegate_->PostDelayedTask(std::move(task), delay.ToMilliseconds());\n    return;\n  }\n  MessageLoopTaskQueues::GetInstance()->RegisterTask(\n      queue_id_, std::move(task), fml::TimePoint::Now() + delay,\n      fml::TaskSourceGrade::kUnspecified);\n}\n\nTaskQueueId TaskRunner::GetTaskQueueId() {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(loop_);\n  return queue_id_;\n}\n\n// TODO(heshan):this method acquires the lock of MessageLoopTaskQueues\n// three times, needs to be optimized.\nbool TaskRunner::RunsTasksOnCurrentThread() {\n  if (delegate_) {\n    return delegate_->RunsTasksOnCurrentThread();\n  }\n  MessageLoop* current_loop = fml::MessageLoop::IsInitializedForCurrentThread();\n  if (current_loop == nullptr) {\n    return false;\n  }\n\n  return MessageLoopTaskQueues::GetInstance()\n      ->IsTaskQueueRunningOnGivenMessageLoop(current_loop->GetLoopImpl().get(),\n                                             queue_id_);\n}\n\nvoid TaskRunner::RunNowOrPostTask(const fml::RefPtr<fml::TaskRunner>& runner,\n                                  base::closure task) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(runner);\n  if (runner->RunsTasksOnCurrentThread()) {\n    task();\n  } else {\n    runner->PostTask(std::move(task));\n  }\n}\n\nvoid TaskRunner::RunNowOrPostTask(\n    const std::shared_ptr<fml::TaskRunner>& runner, base::closure task) {\n  // TODO(zhengsenyao): Uncomment DCHECK code when DCHECK available.\n  // DCHECK(runner);\n  if (runner->RunsTasksOnCurrentThread()) {\n    task();\n  } else {\n    runner->PostTask(std::move(task));\n  }\n}\n\nvoid TaskRunner::Bind(fml::RefPtr<MessageLoopImpl> target_loop,\n                      bool should_run_expired_tasks_immediately) {\n  if (target_loop && target_loop != loop_) {\n    LYNX_BASE_CHECK(target_loop->CanRunNow());\n    UnBind();\n    target_loop->Bind(queue_id_, should_run_expired_tasks_immediately);\n    loop_ = std::move(target_loop);\n\n    if (!should_run_expired_tasks_immediately) {\n      // If expired tasks should not be run immediately,\n      // attempt to wake up the loop when there are tasks in the queue.\n      MessageLoopTaskQueues::GetInstance()->WakeUp(loop_->GetTaskQueueIds());\n    }\n  }\n}\n\nvoid TaskRunner::UnBind() {\n  if (!loop_) {\n    return;\n  }\n\n  if (!RunsTasksOnCurrentThread()) {\n    fml::AutoResetWaitableEvent arwe;\n    PostEmergencyTask([this, &arwe]() {\n      loop_->UnBind(queue_id_);\n      *unbound_ = true;\n      arwe.Signal();\n    });\n    arwe.Wait();\n  } else {\n    loop_->UnBind(queue_id_);\n    *unbound_ = true;\n  }\n  loop_ = nullptr;\n}\n\nvoid TaskRunner::AddTaskObserver(intptr_t key, base::closure callback) {\n  if (callback != nullptr) {\n    MessageLoopTaskQueues::GetInstance()->AddTaskObserver(queue_id_, key,\n                                                          std::move(callback));\n  }\n}\n\nvoid TaskRunner::RemoveTaskObserver(intptr_t key) {\n  MessageLoopTaskQueues::GetInstance()->RemoveTaskObserver(queue_id_, key);\n}\n\nconst fml::RefPtr<MessageLoopImpl>& TaskRunner::GetLoop() const {\n  return loop_;\n}\n\nvoid TaskRunner::SetDelegate(lynx::fml::TaskRunnerDelegate* delegate) {\n  delegate_ = delegate;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/task_runner_unittests.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/thread.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace testing {\n\nclass TaskRunnerTest : public ::testing::Test {\n protected:\n  TaskRunnerTest() = default;\n  ~TaskRunnerTest() override = default;\n\n  void SetUp() override {}\n\n  void TearDown() override {}\n};\n\nTEST_F(TaskRunnerTest, Bind) {\n  fml::Thread original_thread(\"original_thread\");\n  fml::Thread target_thread(\"target_thread\");\n\n  auto original_loop = original_thread.GetLoop();\n  auto target_loop = target_thread.GetLoop();\n\n  auto task_runner = fml::MakeRefCounted<fml::TaskRunner>(original_loop);\n  int32_t result = 0;\n  constexpr int32_t expected = 10;\n\n  for (int32_t i = 0; i < expected; ++i) {\n    task_runner->PostTask([&result, original_loop, target_loop]() {\n      ++result;\n      ASSERT_EQ(MessageLoop::GetCurrent().GetLoopImpl().get(),\n                original_loop.get());\n      ASSERT_NE(MessageLoop::GetCurrent().GetLoopImpl().get(),\n                target_loop.get());\n    });\n  }\n\n  fml::AutoResetWaitableEvent arwe;\n  original_thread.GetTaskRunner()->PostTask([&arwe]() { arwe.Signal(); });\n\n  arwe.Wait();\n  ASSERT_EQ(result, expected);\n\n  arwe.Reset();\n  target_thread.GetTaskRunner()->PostTask([&arwe, task_runner, target_loop]() {\n    task_runner->Bind(target_loop);\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  result = 0;\n\n  for (int32_t i = 0; i < expected; ++i) {\n    task_runner->PostTask([&result, original_loop, target_loop]() {\n      ++result;\n      ASSERT_EQ(MessageLoop::GetCurrent().GetLoopImpl().get(),\n                target_loop.get());\n      ASSERT_NE(MessageLoop::GetCurrent().GetLoopImpl().get(),\n                original_loop.get());\n    });\n  }\n\n  arwe.Reset();\n  target_thread.GetTaskRunner()->PostTask([&arwe]() { arwe.Signal(); });\n\n  arwe.Wait();\n  ASSERT_EQ(result, expected);\n}\n\nTEST_F(TaskRunnerTest, BindWithShouldRunExpiredTasksImmediately) {\n  fml::Thread original_thread(\"original_thread\");\n  fml::Thread target_thread(\"target_thread\");\n\n  auto original_loop = original_thread.GetLoop();\n  auto target_loop = target_thread.GetLoop();\n\n  auto task_runner = fml::MakeRefCounted<fml::TaskRunner>(original_loop);\n  int32_t result = 0;\n  static constexpr int32_t expected = 10;\n\n  fml::AutoResetWaitableEvent arwe;\n  original_thread.GetTaskRunner()->PostTask([&arwe, &result, &target_thread,\n                                             task_runner, original_loop,\n                                             target_loop]() {\n    for (int32_t i = 0; i < expected; ++i) {\n      task_runner->PostTask([&result, original_loop, target_loop]() {\n        ++result;\n        ASSERT_EQ(MessageLoop::GetCurrent().GetLoopImpl().get(),\n                  target_loop.get());\n        ASSERT_NE(MessageLoop::GetCurrent().GetLoopImpl().get(),\n                  original_loop.get());\n      });\n    }\n\n    task_runner->UnBind();\n\n    target_thread.GetTaskRunner()->PostTask(\n        [&arwe, &result, task_runner, target_loop]() {\n          task_runner->Bind(target_loop);\n          ASSERT_EQ(result, 0);\n          arwe.Signal();\n        });\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  task_runner->PostTask([&arwe]() { arwe.Signal(); });\n  arwe.Wait();\n\n  ASSERT_EQ(result, expected);\n\n  original_thread.GetTaskRunner()->PostTask(\n      [task_runner, original_loop]() { task_runner->Bind(original_loop); });\n\n  result = 0;\n  original_thread.GetTaskRunner()->PostTask([&arwe, &result, &target_thread,\n                                             task_runner, original_loop,\n                                             target_loop]() {\n    for (int32_t i = 0; i < expected; ++i) {\n      task_runner->PostTask([&result, original_loop, target_loop]() {\n        ++result;\n        ASSERT_EQ(MessageLoop::GetCurrent().GetLoopImpl().get(),\n                  target_loop.get());\n        ASSERT_NE(MessageLoop::GetCurrent().GetLoopImpl().get(),\n                  original_loop.get());\n      });\n    }\n\n    task_runner->UnBind();\n\n    target_thread.GetTaskRunner()->PostTask(\n        [&arwe, &result, task_runner, target_loop]() {\n          task_runner->Bind(target_loop, true);\n          ASSERT_EQ(result, expected);\n          arwe.Signal();\n        });\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  task_runner->PostTask([&arwe]() { arwe.Signal(); });\n  arwe.Wait();\n\n  ASSERT_EQ(result, expected);\n}\n\n}  // namespace testing\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/task_source.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"base/include/fml/task_source.h\"\n\nnamespace lynx {\nnamespace fml {\n\nnamespace {\n\n// we use 50ms for a idle period\n// https://w3c.github.io/requestidlecallback/#why50\nconstexpr int64_t kIdlePeriod = 50;  // milliseconds\n\n}  // namespace\n\nTaskSource::TaskSource(TaskQueueId task_queue_id)\n    : task_queue_id_(task_queue_id) {\n  primary_task_queue_.reserve(64);\n  emergency_task_queue_.reserve(32);\n  micro_task_queue_.reserve(32);\n}\n\nTaskSource::~TaskSource() { ShutDown(); }\n\nvoid TaskSource::ShutDown() {\n  primary_task_queue_ = {};\n  emergency_task_queue_ = {};\n  idle_task_queue_ = {};\n  micro_task_queue_ = {};\n}\n\nvoid TaskSource::RegisterTask(DelayedTask task) {\n  switch (task.GetTaskSourceGrade()) {\n    case TaskSourceGrade::kUserInteraction:\n      primary_task_queue_.push(std::move(task));\n      break;\n    case TaskSourceGrade::kUnspecified:\n      primary_task_queue_.push(std::move(task));\n      break;\n    case TaskSourceGrade::kEmergency:\n      emergency_task_queue_.push(std::move(task));\n      break;\n    case TaskSourceGrade::kIdle:\n      idle_task_queue_.push(std::move(task));\n      break;\n    case TaskSourceGrade::kMicrotask:\n      micro_task_queue_.push(std::move(task));\n      break;\n  }\n}\n\nvoid TaskSource::PopTask(TaskSourceGrade grade) {\n  switch (grade) {\n    case TaskSourceGrade::kUserInteraction:\n      primary_task_queue_.pop();\n      break;\n    case TaskSourceGrade::kUnspecified:\n      primary_task_queue_.pop();\n      break;\n    case TaskSourceGrade::kEmergency:\n      emergency_task_queue_.pop();\n      break;\n    case TaskSourceGrade::kIdle:\n      idle_task_queue_.pop();\n      break;\n    case TaskSourceGrade::kMicrotask:\n      micro_task_queue_.pop();\n      break;\n  }\n}\n\nsize_t TaskSource::GetNumPendingTasks() const {\n  return primary_task_queue_.size() + emergency_task_queue_.size() +\n         idle_task_queue_.size() + micro_task_queue_.size();\n}\n\nbool TaskSource::IsEmpty() const { return GetNumPendingTasks() == 0; }\n\nTaskSource::TopTask TaskSource::Top() const {\n  if (!micro_task_queue_.empty()) {\n    const auto& microtask_top = micro_task_queue_.top();\n    return {\n        .task_queue_id = task_queue_id_,\n        .task = microtask_top,\n    };\n  }\n\n  if (!emergency_task_queue_.empty()) {\n    const auto& emergency_top = emergency_task_queue_.top();\n    return {\n        .task_queue_id = task_queue_id_,\n        .task = emergency_top,\n    };\n  }\n\n  if (!primary_task_queue_.empty()) {\n    const auto& primary_top = primary_task_queue_.top();\n    // if there are primary tasks in a idle period,\n    // the idle tasks will be suspended.\n    if (idle_task_queue_.empty() ||\n        (primary_top.GetTargetTime() - TimePoint::Now()).ToMilliseconds() <=\n            kIdlePeriod) {\n      return {\n          .task_queue_id = task_queue_id_,\n          .task = primary_top,\n      };\n    }\n  }\n\n  // TODO(zhengsenyao): Replace LYNX_BASE_CHECK with CHECK when CHECK available.\n  LYNX_BASE_CHECK(!idle_task_queue_.empty());\n  const auto& idle_top = idle_task_queue_.front();\n  return {\n      .task_queue_id = task_queue_id_,\n      .task = idle_top,\n  };\n}\n\nconst DelayedTask* TaskSource::TopOrNull() const {\n  if (!micro_task_queue_.empty()) {\n    return &micro_task_queue_.top();\n  }\n\n  if (!emergency_task_queue_.empty()) {\n    return &emergency_task_queue_.top();\n  }\n\n  if (!primary_task_queue_.empty()) {\n    const auto& primary_top = primary_task_queue_.top();\n    // if there are primary tasks in a idle period,\n    // the idle tasks will be suspended.\n    if (idle_task_queue_.empty() ||\n        (primary_top.GetTargetTime() - TimePoint::Now()).ToMilliseconds() <=\n            kIdlePeriod) {\n      return &primary_top;\n    }\n  }\n\n  if (!idle_task_queue_.empty()) {\n    return &idle_task_queue_.front();\n  }\n\n  return nullptr;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/task_source_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <atomic>\n#include <thread>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/task_source.h\"\n#include \"base/include/fml/time/chrono_timestamp_provider.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace testing {\n\nTEST(TaskSourceTests, SimpleInitialization) {\n  TaskSource task_source = TaskSource(TaskQueueId(1));\n  task_source.RegisterTask(\n      {1, [] {}, ChronoTicksSinceEpoch(), TaskSourceGrade::kUnspecified});\n  ASSERT_EQ(task_source.GetNumPendingTasks(), 1u);\n}\n\nTEST(TaskSourceTests, MultipleTaskGrades) {\n  TaskSource task_source = TaskSource(TaskQueueId(1));\n  task_source.RegisterTask(\n      {1, [] {}, ChronoTicksSinceEpoch(), TaskSourceGrade::kUnspecified});\n  task_source.RegisterTask(\n      {2, [] {}, ChronoTicksSinceEpoch(), TaskSourceGrade::kUserInteraction});\n  task_source.RegisterTask(\n      {3, [] {}, ChronoTicksSinceEpoch(), TaskSourceGrade::kEmergency});\n  task_source.RegisterTask(\n      {3, [] {}, ChronoTicksSinceEpoch(), TaskSourceGrade::kIdle});\n  task_source.RegisterTask(\n      {4, [] {}, ChronoTicksSinceEpoch(), TaskSourceGrade::kMicrotask});\n  ASSERT_EQ(task_source.GetNumPendingTasks(), 5u);\n}\n\nTEST(TaskSourceTests, SimpleOrdering) {\n  TaskSource task_source = TaskSource(TaskQueueId(1));\n  auto time_stamp = ChronoTicksSinceEpoch();\n  int value = 0;\n  task_source.RegisterTask(\n      {1, [&] { value = 1; }, time_stamp, TaskSourceGrade::kUnspecified});\n  task_source.RegisterTask({2, [&] { value = 7; },\n                            time_stamp + fml::TimeDelta::FromMilliseconds(1),\n                            TaskSourceGrade::kUnspecified});\n  task_source.Top().task.GetTask()();\n  task_source.PopTask(TaskSourceGrade::kUnspecified);\n  ASSERT_EQ(value, 1);\n  task_source.Top().task.GetTask()();\n  task_source.PopTask(TaskSourceGrade::kUnspecified);\n  ASSERT_EQ(value, 7);\n}\n\nTEST(TaskSourceTests, SimpleOrderingMultiTaskHeaps) {\n  TaskSource task_source = TaskSource(TaskQueueId(1));\n  auto time_stamp = ChronoTicksSinceEpoch();\n  int value = 0;\n  task_source.RegisterTask({0, [&] { value = 17; },\n                            time_stamp + fml::TimeDelta::FromMilliseconds(1),\n                            TaskSourceGrade::kIdle});\n  task_source.RegisterTask({1, [&] { value = 1; },\n                            time_stamp + fml::TimeDelta::FromMilliseconds(1),\n                            TaskSourceGrade::kUserInteraction});\n  task_source.RegisterTask({2, [&] { value = 7; },\n                            time_stamp + fml::TimeDelta::FromMilliseconds(1),\n                            TaskSourceGrade::kEmergency});\n  task_source.RegisterTask({3, [&] { value = 20; },\n                            time_stamp + fml::TimeDelta::FromMilliseconds(1),\n                            TaskSourceGrade::kMicrotask});\n  auto zero_task = task_source.Top();\n  zero_task.task.GetTask()();\n  task_source.PopTask(zero_task.task.GetTaskSourceGrade());\n  ASSERT_EQ(value, 20);\n\n  auto top_task = task_source.Top();\n  top_task.task.GetTask()();\n  task_source.PopTask(top_task.task.GetTaskSourceGrade());\n  ASSERT_EQ(value, 7);\n\n  auto second_task = task_source.Top();\n  second_task.task.GetTask()();\n  task_source.PopTask(second_task.task.GetTaskSourceGrade());\n  ASSERT_EQ(value, 1);\n\n  auto third_task = task_source.Top();\n  third_task.task.GetTask()();\n  task_source.PopTask(third_task.task.GetTaskSourceGrade());\n  ASSERT_EQ(value, 17);\n}\n\n}  // namespace testing\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/thread.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"base/include/fml/thread.h\"\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/src/fml/thread_name_setter.h\"\n#include \"build/build_config.h\"\n\n#if defined(OS_WIN)\n#include <process.h>\n#include <windows.h>\n#endif\n\n#if defined(OS_IOS) || defined(OS_ANDROID)\n#include <sched.h>\n\n#include \"base/include/fml/platform/thread_config_setter.h\"\n#endif\n\n#if defined(OS_ANDROID)\n#include \"base/include/platform/android/jni_utils.h\"\n#endif\n\nnamespace lynx {\nnamespace fml {\n\nclass ThreadHandle {\n public:\n  ThreadHandle(base::closure&& function);\n  ~ThreadHandle();\n\n  void Join();\n\n private:\n#if defined(OS_WIN)\n  HANDLE thread_;\n#else\n  pthread_t thread_;\n#endif\n};\n\n#if defined(OS_WIN)\nThreadHandle::ThreadHandle(base::closure&& function) {\n  thread_ = (HANDLE*)_beginthreadex(\n      nullptr, Thread::GetDefaultStackSize(),\n      [](void* arg) -> unsigned {\n        std::unique_ptr<base::closure> function(\n            reinterpret_cast<base::closure*>(arg));\n        (*function)();\n        return 0;\n      },\n      new base::closure(std::move(function)), 0, nullptr);\n  LYNX_BASE_CHECK(thread_ != nullptr);\n}\n\nvoid ThreadHandle::Join() { WaitForSingleObjectEx(thread_, INFINITE, FALSE); }\n\nThreadHandle::~ThreadHandle() { CloseHandle(thread_); }\n#else\n\nThreadHandle::ThreadHandle(base::closure&& function) {\n  pthread_attr_t attr;\n  pthread_attr_init(&attr);\n\n  // Set stack size.\n  int result = pthread_attr_setstacksize(&attr, Thread::GetDefaultStackSize());\n  LYNX_BASE_CHECK(result == 0);\n\n#if defined(OS_IOS)\n  // Set scheduling policy and priority for the new thread.\n  // This can fail if the user does not have permissions to do so. We will\n  // not check the result of these calls and let the thread be created with\n  // default attributes.\n  if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) == 0) {\n    int policy;\n    struct sched_param current_param;\n    if (pthread_getschedparam(pthread_self(), &policy, &current_param) == 0) {\n      if (pthread_attr_setschedpolicy(&attr, policy) == 0) {\n        struct sched_param high_prio_param;\n        high_prio_param.sched_priority = sched_get_priority_max(policy);\n        pthread_attr_setschedparam(&attr, &high_prio_param);\n      }\n    }\n  }\n#endif\n\n  result = pthread_create(\n      &thread_, &attr,\n      [](void* arg) -> void* {\n        std::unique_ptr<base::closure> function(\n            reinterpret_cast<base::closure*>(arg));\n        (*function)();\n        return nullptr;\n      },\n      new base::closure(std::move(function)));\n  LYNX_BASE_CHECK(result == 0);\n  result = pthread_attr_destroy(&attr);\n  LYNX_BASE_CHECK(result == 0);\n}\n\nvoid ThreadHandle::Join() { pthread_join(thread_, nullptr); }\n\nThreadHandle::~ThreadHandle() = default;\n\n#endif\n\nvoid Thread::SetCurrentThreadName(const Thread::ThreadConfig& config) {\n  SetThreadName(config.name);\n}\n\nThread::Thread(const std::string& name) : Thread(ThreadConfig(name)) {}\n\n#if defined(OS_IOS) || defined(OS_ANDROID)\nThread::Thread(const ThreadConfig& config)\n    : Thread(PlatformThreadPriority::Setter, config) {}\n#else\nThread::Thread(const ThreadConfig& config)\n    : Thread(Thread::SetCurrentThreadName, config) {}\n#endif\n\nThread::Thread(const ThreadConfigSetter& setter, const ThreadConfig& config)\n    : joined_(false) {\n  fml::AutoResetWaitableEvent latch;\n  fml::RefPtr<fml::TaskRunner> runner;\n  fml::RefPtr<fml::MessageLoopImpl> loop_impl;\n  base::closure setup_thread = [&latch, &runner, &loop_impl, setter, config]() {\n    auto additional_setup_closure = config.additional_setup_closure;\n    if (additional_setup_closure) {\n      (*additional_setup_closure)();\n    }\n\n    auto& loop = fml::MessageLoop::EnsureInitializedForCurrentThread();\n    loop_impl = loop.GetLoopImpl();\n    runner = loop.GetTaskRunner();\n    latch.Signal();\n    setter(config);\n    loop.Run();\n    // hack, because we cannot detach vm within MessageLoop Terminate,\n    // Terminate is called in Android Looper, the java code.\n    // If we invoke attempting DetachCurrentThread within Terminate,\n    // we will get another exception \"attempting to detach while still running\n    // code\". so we must detach here, after the loop stop running.\n#if defined(OS_ANDROID)\n    lynx::base::android::DetachFromVM();\n#endif\n  };\n  thread_ = std::make_unique<ThreadHandle>(std::move(setup_thread));\n  latch.Wait();\n  task_runner_ = runner;\n  loop_ = loop_impl;\n}\n\nThread::~Thread() { Join(); }\n\nconst fml::RefPtr<fml::TaskRunner>& Thread::GetTaskRunner() const {\n  return task_runner_;\n}\n\nconst fml::RefPtr<fml::MessageLoopImpl>& Thread::GetLoop() const {\n  return loop_;\n}\n\nvoid Thread::Join() {\n  if (joined_) {\n    return;\n  }\n  joined_ = true;\n  task_runner_->PostTask([]() { MessageLoop::GetCurrent().Terminate(); });\n  thread_->Join();\n}\n\nsize_t Thread::GetDefaultStackSize() { return 1024 * 1024 * 1; }\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/thread_host.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/thread_host.h\"\n\n#include <algorithm>\n#include <memory>\n#include <optional>\n#include <string>\n#include <utility>\n\nnamespace lynx {\n\nstd::string ThreadHost::ThreadHostConfig::MakeThreadName(\n    Type type, const std::string& prefix) {\n  switch (type) {\n    case Type::Platform:\n      return prefix + \".platform\";\n    case Type::UI:\n      return prefix + \".ui\";\n    case Type::IO:\n      return prefix + \".io\";\n    case Type::RASTER:\n      return prefix + \".raster\";\n    case Type::Profiler:\n      return prefix + \".profiler\";\n  }\n}\n\nvoid ThreadHost::ThreadHostConfig::SetIOConfig(const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::IO;\n  io_config = config;\n}\n\nvoid ThreadHost::ThreadHostConfig::SetUIConfig(const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::UI;\n  ui_config = config;\n}\n\nvoid ThreadHost::ThreadHostConfig::SetPlatformConfig(\n    const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::Platform;\n  platform_config = config;\n}\n\nvoid ThreadHost::ThreadHostConfig::SetRasterConfig(const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::RASTER;\n  raster_config = config;\n}\n\nvoid ThreadHost::ThreadHostConfig::SetProfilerConfig(\n    const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::Profiler;\n  profiler_config = config;\n}\n\nstd::unique_ptr<fml::Thread> ThreadHost::CreateThread(\n    Type type, std::optional<ThreadConfig> thread_config,\n    const ThreadHostConfig& host_config) const {\n  /// if not specified ThreadConfig, create a ThreadConfig.\n  if (!thread_config.has_value()) {\n    thread_config = ThreadConfig(\n        ThreadHostConfig::MakeThreadName(type, host_config.name_prefix));\n  }\n  return std::make_unique<fml::Thread>(host_config.config_setter,\n                                       *thread_config);\n}\n\nThreadHost::ThreadHost() = default;\n\nThreadHost::ThreadHost(ThreadHost&&) = default;\n\nThreadHost::ThreadHost(const std::string name_prefix, uint64_t mask)\n    : ThreadHost(ThreadHostConfig(name_prefix, mask)) {}\n\nThreadHost::ThreadHost(const ThreadHostConfig& host_config)\n    : name_prefix(host_config.name_prefix) {\n  if (host_config.isThreadNeeded(ThreadHost::Type::Platform)) {\n    platform_thread =\n        CreateThread(Type::Platform, host_config.platform_config, host_config);\n  }\n\n  if (host_config.isThreadNeeded(ThreadHost::Type::UI)) {\n    ui_thread = CreateThread(Type::UI, host_config.ui_config, host_config);\n  }\n\n  if (host_config.isThreadNeeded(ThreadHost::Type::RASTER)) {\n    raster_thread =\n        CreateThread(Type::RASTER, host_config.raster_config, host_config);\n  }\n\n  if (host_config.isThreadNeeded(ThreadHost::Type::IO)) {\n    io_thread = CreateThread(Type::IO, host_config.io_config, host_config);\n  }\n\n  if (host_config.isThreadNeeded(ThreadHost::Type::Profiler)) {\n    profiler_thread =\n        CreateThread(Type::Profiler, host_config.profiler_config, host_config);\n  }\n}\n\nThreadHost::~ThreadHost() = default;\n\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/thread_name_setter.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_SRC_FML_THREAD_NAME_SETTER_H_\n#define BASE_SRC_FML_THREAD_NAME_SETTER_H_\n\n#include <string>\n\nnamespace lynx {\nnamespace fml {\nvoid SetThreadName(const std::string& name);\n}\n}  // namespace lynx\n\n#endif  // BASE_SRC_FML_THREAD_NAME_SETTER_H_\n"
  },
  {
    "path": "base/src/fml/thread_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/thread.h\"\n#include \"build/build_config.h\"\n\n#if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_ANDROID)\n#define FLUTTER_PTHREAD_SUPPORTED 1\n#else\n#define FLUTTER_PTHREAD_SUPPORTED 0\n#endif\n\n#if FLUTTER_PTHREAD_SUPPORTED\n#include <pthread.h>\n#else\n#endif\n\n#include <memory>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\n\nTEST(Thread, CanStartAndEnd) {\n  fml::Thread thread;\n  ASSERT_TRUE(thread.GetTaskRunner());\n}\n\nTEST(Thread, CanStartAndEndWithExplicitJoin) {\n  fml::Thread thread;\n  ASSERT_TRUE(thread.GetTaskRunner());\n  thread.Join();\n}\n\nTEST(Thread, HasARunningMessageLoop) {\n  fml::Thread thread;\n  bool done = false;\n  thread.GetTaskRunner()->PostTask([&done]() { done = true; });\n  thread.Join();\n  ASSERT_TRUE(done);\n}\n\n#if FLUTTER_PTHREAD_SUPPORTED\nTEST(Thread, ThreadNameCreatedWithConfig) {\n  const std::string name = \"Thread1\";\n  fml::Thread thread(name);\n\n  bool done = false;\n  thread.GetTaskRunner()->PostTask([&done, &name]() {\n    done = true;\n    // The thread name's length is set to 16 on Android & Linux\n    char thread_name[16];\n    pthread_t current_thread = pthread_self();\n    pthread_getname_np(current_thread, thread_name, sizeof(thread_name));\n    ASSERT_EQ(thread_name, name);\n  });\n  thread.Join();\n  ASSERT_TRUE(done);\n}\n\nstatic void MockThreadConfigSetter(const fml::Thread::ThreadConfig& config) {\n  // set thread name\n  fml::Thread::SetCurrentThreadName(config);\n\n  pthread_t tid = pthread_self();\n  struct sched_param param;\n  int policy = SCHED_OTHER;\n  switch (config.priority) {\n    case fml::Thread::ThreadPriority::HIGH:\n      param.sched_priority = 10;\n      break;\n    default:\n      param.sched_priority = 1;\n  }\n    // In linx system, sched_priority can only be set to 0 with SCHED_OTHER\n    // policy.\n#if defined(OS_LINUX)\n  param.sched_priority = 0;\n#endif\n  pthread_setschedparam(tid, policy, &param);\n}\n\nTEST(Thread, ThreadPriorityCreatedWithConfig) {\n  const std::string thread1_name = \"Thread1\";\n  const std::string thread2_name = \"Thread2\";\n\n  fml::Thread thread(MockThreadConfigSetter,\n                     fml::Thread::ThreadConfig(\n                         thread1_name, fml::Thread::ThreadPriority::NORMAL));\n  bool done = false;\n\n  struct sched_param param;\n  int policy;\n  thread.GetTaskRunner()->PostTask([&]() {\n    done = true;\n    char thread_name[16];\n    pthread_t current_thread = pthread_self();\n    pthread_getname_np(current_thread, thread_name, sizeof(thread_name));\n    pthread_getschedparam(current_thread, &policy, &param);\n    ASSERT_EQ(thread_name, thread1_name);\n    ASSERT_EQ(policy, SCHED_OTHER);\n    // In linx system, sched_priority can only be set to 0 with SCHED_OTHER\n    // policy.\n#if !defined(OS_LINUX)\n    ASSERT_EQ(param.sched_priority, 1);\n#else\n    ASSERT_EQ(param.sched_priority, 0);\n#endif\n  });\n\n  struct sched_param param2;\n  fml::Thread thread2(MockThreadConfigSetter,\n                      fml::Thread::ThreadConfig(\n                          thread2_name, fml::Thread::ThreadPriority::HIGH));\n  thread2.GetTaskRunner()->PostTask([&]() {\n    done = true;\n    char thread_name[16];\n    pthread_t current_thread = pthread_self();\n    pthread_getname_np(current_thread, thread_name, sizeof(thread_name));\n    pthread_getschedparam(current_thread, &policy, &param2);\n    ASSERT_EQ(thread_name, thread2_name);\n    ASSERT_EQ(policy, SCHED_OTHER);\n    // In linx system, sched_priority can only be set to 0 with SCHED_OTHER\n    // policy.\n#if !defined(OS_LINUX)\n    ASSERT_EQ(param2.sched_priority, 10);\n#else\n    ASSERT_EQ(param2.sched_priority, 0);\n#endif\n  });\n  thread.Join();\n  ASSERT_TRUE(done);\n}\n#endif\n\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/time/chrono_timestamp_provider.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/time/chrono_timestamp_provider.h\"\n\n#include <chrono>\n\n#include \"base/include/fml/time/time_delta.h\"\n\nnamespace lynx {\nnamespace fml {\n\nChronoTimestampProvider::ChronoTimestampProvider() = default;\n\nChronoTimestampProvider::~ChronoTimestampProvider() = default;\n\nfml::TimePoint ChronoTimestampProvider::Now() {\n  const auto chrono_time_point = std::chrono::steady_clock::now();\n  const auto ticks_since_epoch = chrono_time_point.time_since_epoch().count();\n  return fml::TimePoint::FromTicks(ticks_since_epoch);\n}\n\nfml::TimePoint ChronoTicksSinceEpoch() {\n  return ChronoTimestampProvider::Instance().Now();\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/time/time_delta_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/time/time_delta.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace {\n\nTEST(TimeDelta, Control) {\n  EXPECT_LT(TimeDelta::Min(), TimeDelta::Zero());\n  EXPECT_GT(TimeDelta::Max(), TimeDelta::Zero());\n\n  EXPECT_GT(TimeDelta::Zero(), TimeDelta::FromMilliseconds(-100));\n  EXPECT_LT(TimeDelta::Zero(), TimeDelta::FromMilliseconds(100));\n\n  EXPECT_EQ(TimeDelta::FromMilliseconds(1000), TimeDelta::FromSeconds(1));\n}\n\n}  // namespace\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/time/time_point.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/time/time_point.h\"\n\n#include \"build/build_config.h\"\n\n#if defined(OS_FUCHSIA)\n#include <zircon/syscalls.h>\n#else\n#include <chrono>\n#endif\n\nnamespace lynx {\nnamespace fml {\n\n#if defined(OS_FUCHSIA)\n\n// static\nTimePoint TimePoint::Now() { return TimePoint(zx_clock_get_monotonic()); }\n\nTimePoint TimePoint::CurrentWallTime() { return Now(); }\n\n#else\n\ntemplate <typename Clock, typename Duration>\nstatic int64_t NanosSinceEpoch(\n    std::chrono::time_point<Clock, Duration> time_point) {\n  const auto elapsed = time_point.time_since_epoch();\n  return std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();\n}\n\nTimePoint TimePoint::Now() {\n  const int64_t nanos = NanosSinceEpoch(std::chrono::steady_clock::now());\n  return TimePoint(nanos);\n}\n\nTimePoint TimePoint::CurrentWallTime() {\n  const int64_t nanos = NanosSinceEpoch(std::chrono::system_clock::now());\n  return TimePoint(nanos);\n}\n\n#endif\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/time/time_point_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <thread>\n\n#include \"base/include/fml/time/chrono_timestamp_provider.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace {\n\nTEST(TimePoint, Control) {\n  EXPECT_LT(TimePoint::Min(), ChronoTicksSinceEpoch());\n  EXPECT_GT(TimePoint::Max(), ChronoTicksSinceEpoch());\n}\n\n}  // namespace\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/time/time_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <thread>\n\n#include \"base/include/fml/time/chrono_timestamp_provider.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace {\n\nTEST(Time, Now) {\n  auto start = ChronoTicksSinceEpoch();\n  for (int i = 0; i < 3; ++i) {\n    auto now = ChronoTicksSinceEpoch();\n    EXPECT_GE(now, start);\n    std::this_thread::yield();\n  }\n}\n\nTEST(Time, IntConversions) {\n  // Integer conversions should all truncate, not round.\n  TimeDelta delta = TimeDelta::FromNanoseconds(102304506708ll);\n  EXPECT_EQ(102304506708ll, delta.ToNanoseconds());\n  EXPECT_EQ(102304506ll, delta.ToMicroseconds());\n  EXPECT_EQ(102304ll, delta.ToMilliseconds());\n  EXPECT_EQ(102ll, delta.ToSeconds());\n}\n\nTEST(Time, FloatConversions) {\n  // Float conversions should remain close to the original value.\n  TimeDelta delta = TimeDelta::FromNanoseconds(102304506708ll);\n  EXPECT_FLOAT_EQ(102304506708.0, delta.ToNanosecondsF());\n  EXPECT_FLOAT_EQ(102304506.708, delta.ToMicrosecondsF());\n  EXPECT_FLOAT_EQ(102304.506708, delta.ToMillisecondsF());\n  EXPECT_FLOAT_EQ(102.304506708, delta.ToSecondsF());\n}\n\nTEST(Time, TimespecConversions) {\n  struct timespec ts;\n  ts.tv_sec = 5;\n  ts.tv_nsec = 7;\n  TimeDelta from_timespec = TimeDelta::FromTimespec(ts);\n  EXPECT_EQ(5, from_timespec.ToSeconds());\n  EXPECT_EQ(5 * 1000000000ll + 7, from_timespec.ToNanoseconds());\n  struct timespec to_timespec = from_timespec.ToTimespec();\n  EXPECT_EQ(ts.tv_sec, to_timespec.tv_sec);\n  EXPECT_EQ(ts.tv_nsec, to_timespec.tv_nsec);\n}\n\n}  // namespace\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/time/timer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/time/timer.h\"\n\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace fml {\n\nvoid Timer::Start(fml::TimeDelta delay, Task task) {\n  if (!task || !task_runner_) {\n    LYNX_BASE_DCHECK(false);\n    return;\n  }\n\n  delay_ = delay;\n  LYNX_BASE_DCHECK(delay_ != fml::TimeDelta::Zero());\n  user_task_ = std::move(task);\n  running_ = true;\n  AbandonScheduledTasks();\n  ScheduleNewTask();\n}\n\nvoid Timer::AbandonScheduledTasks() {\n  // Overflow will be reset to 0.\n  ++validator_;\n}\n\nvoid Timer::ScheduleNewTask() {\n  task_runner_->PostDelayedTask(\n      [validator = validator_, self = WeakFromThis()]() {\n        if (!self || !self->running_ || validator != self->validator_) {\n          return;\n        }\n\n        if (self->repeating_) {\n          self->ScheduleNewTask();\n        } else {\n          self->ResetState();\n        }\n\n        // Must run after schedule. Otherwise timer maybe destructed.\n        self->RunUserTask();\n      },\n      delay_);\n}\n\n// Whence stopped, all scheduled tasks are invalidated.\nvoid Timer::Stop() {\n  ResetState();\n  user_task_ = nullptr;\n}\n\nvoid Timer::ResetState() {\n  running_ = false;\n  AbandonScheduledTasks();\n}\n\nvoid Timer::RunUserTask() {\n  if (user_task_) {\n    user_task_();\n  }\n}\n\nbool OneshotTimer::FireImmediately() {\n  if (!Stopped()) {\n    ResetState();\n    RunUserTask();\n    return true;\n  }\n  return false;\n}\n\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/fml/unique_fd.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/unique_fd.h\"\n\n#include \"base/include/fml/eintr_wrapper.h\"\n\nnamespace lynx {\nnamespace fml {\nnamespace internal {\n\n#if OS_WIN\n\nnamespace os_win {\n\nstd::mutex UniqueFDTraits::file_map_mutex;\nstd::map<HANDLE, DirCacheEntry> UniqueFDTraits::file_map;\n\nvoid UniqueFDTraits::Free_Handle(HANDLE fd) { CloseHandle(fd); }\n\n}  // namespace os_win\n\n#else  // OS_WIN\n\nnamespace os_unix {\n\nvoid UniqueFDTraits::Free(int fd) { close(fd); }\n\nvoid UniqueDirTraits::Free(DIR* dir) { closedir(dir); }\n\n}  // namespace os_unix\n\n#endif  // OS_WIN\n\n}  // namespace internal\n}  // namespace fml\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/geometry_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/geometry/point.h\"\n#include \"base/include/geometry/rect.h\"\n#include \"base/include/geometry/size.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace geometry {\nnamespace {\n\nTEST(GeometryTest, PointInitAndAccess) {\n  FloatPoint float_point_0;\n  EXPECT_EQ(0.f, float_point_0.X());\n  EXPECT_EQ(0.f, float_point_0.Y());\n\n  FloatPoint float_point_1(3.4, 2.8);\n  EXPECT_EQ(3.4f, float_point_1.X());\n  EXPECT_EQ(2.8f, float_point_1.Y());\n\n  float_point_0.SetX(3.4);\n  float_point_0.SetY(2.8);\n  EXPECT_EQ(3.4f, float_point_0.X());\n  EXPECT_EQ(2.8f, float_point_0.Y());\n}\n\nTEST(GeometryTest, PointMove) {\n  FloatPoint float_point_0(3.4, 2.8);\n  FloatPoint float_point_1(-1.2, 0.4);\n\n  float_point_0.Move(0.8, -1.3);\n  EXPECT_FLOAT_EQ(4.2, float_point_0.X());\n  EXPECT_FLOAT_EQ(1.5, float_point_0.Y());\n\n  float_point_0.MoveBy(float_point_1);\n  EXPECT_FLOAT_EQ(3.0, float_point_0.X());\n  EXPECT_FLOAT_EQ(1.9, float_point_0.Y());\n}\n\nTEST(GeometryTest, PointOperation) {\n  FloatPoint float_point_0(3.4f, 2.8f);\n  FloatPoint float_point_1(3.4f, 2.8f);\n  FloatPoint float_point_2(3.4f, 2.f);\n  FloatPoint float_point_3(1.f, 2.8f);\n  FloatPoint float_point_4(1.f, -2.f);\n\n  EXPECT_TRUE(float_point_0 == float_point_1);\n  EXPECT_FALSE(float_point_0 == float_point_2);\n  EXPECT_FALSE(float_point_0 == float_point_3);\n  EXPECT_FALSE(float_point_0 == float_point_4);\n\n  EXPECT_FALSE(float_point_0 != float_point_1);\n  EXPECT_TRUE(float_point_0 != float_point_2);\n  EXPECT_TRUE(float_point_0 != float_point_3);\n  EXPECT_TRUE(float_point_0 != float_point_4);\n\n  FloatPoint float_point_5 = float_point_0 + float_point_4;\n  EXPECT_FLOAT_EQ(4.4f, float_point_5.X());\n  EXPECT_FLOAT_EQ(0.8f, float_point_5.Y());\n  float_point_0 += float_point_4;\n  EXPECT_EQ(float_point_5, float_point_0);\n  EXPECT_EQ(float_point_1, float_point_5 - float_point_4);\n}\n\nTEST(GeometryTest, SizeInitAndAccess) {\n  FloatSize float_size_0;\n  EXPECT_EQ(0.f, float_size_0.Width());\n  EXPECT_EQ(0.f, float_size_0.Height());\n  EXPECT_TRUE(float_size_0.IsEmpty());\n\n  FloatSize float_size_1(3.4, 2.8);\n  EXPECT_EQ(3.4f, float_size_1.Width());\n  EXPECT_EQ(2.8f, float_size_1.Height());\n  EXPECT_FALSE(float_size_1.IsEmpty());\n\n  float_size_0.SetWidth(3.4);\n  float_size_0.SetHeight(2.8);\n  EXPECT_EQ(3.4f, float_size_0.Width());\n  EXPECT_EQ(2.8f, float_size_0.Height());\n  EXPECT_FALSE(float_size_0.IsEmpty());\n\n  float_size_1.SetWidth(0.f);\n  float_size_1.SetHeight(0.f);\n  EXPECT_TRUE(float_size_1.IsEmpty());\n}\n\nTEST(GeometryTest, SizeOperation) {\n  FloatSize float_size_0(3.4f, 2.8f);\n  FloatSize float_size_1(3.4f, 2.8f);\n  FloatSize float_size_2(3.4f, 2.f);\n  FloatSize float_size_3(1.f, 2.8f);\n  FloatSize float_size_4(1.f, -2.f);\n\n  EXPECT_TRUE(float_size_0 == float_size_1);\n  EXPECT_FALSE(float_size_0 == float_size_2);\n  EXPECT_FALSE(float_size_0 == float_size_3);\n  EXPECT_FALSE(float_size_0 == float_size_4);\n\n  EXPECT_FALSE(float_size_0 != float_size_1);\n  EXPECT_TRUE(float_size_0 != float_size_2);\n  EXPECT_TRUE(float_size_0 != float_size_3);\n  EXPECT_TRUE(float_size_0 != float_size_4);\n\n  FloatSize float_size_5 = float_size_0 + float_size_4;\n  EXPECT_FLOAT_EQ(4.4f, float_size_5.Width());\n  EXPECT_FLOAT_EQ(0.8f, float_size_5.Height());\n  float_size_0 += float_size_4;\n  EXPECT_EQ(float_size_5, float_size_0);\n  EXPECT_EQ(float_size_1, float_size_5 - float_size_4);\n}\n\nTEST(GeometryTest, SizeExpand) {\n  FloatSize float_size_0(3.4, 2.8);\n  FloatSize float_size_1(3.3, 0.4);\n  FloatSize float_size_2(4.6, 0.4);\n  FloatSize float_size_3(3.3, 3.8);\n  FloatSize float_size_4(4.3, 5.8);\n\n  EXPECT_EQ(float_size_0, float_size_0.ExpandedTo(float_size_1));\n  EXPECT_EQ(FloatSize(4.6, 2.8), float_size_0.ExpandedTo(float_size_2));\n  EXPECT_EQ(FloatSize(3.4, 3.8), float_size_0.ExpandedTo(float_size_3));\n  EXPECT_EQ(FloatSize(4.3, 5.8), float_size_0.ExpandedTo(float_size_4));\n\n  float_size_2.Expand(float_size_3.Width(), float_size_3.Height());\n  EXPECT_FLOAT_EQ(7.9f, float_size_2.Width());\n  EXPECT_FLOAT_EQ(4.2f, float_size_2.Height());\n}\n\nTEST(GeometryTest, RectInitAndAccess) {\n  FloatRect float_rect_0;\n  FloatRect float_rect_1(FloatPoint(3.3, 0.4), FloatSize(4.3, 2.8));\n  EXPECT_TRUE(float_rect_0.IsEmpty());\n  EXPECT_FALSE(float_rect_1.IsEmpty());\n  EXPECT_EQ(FloatPoint(0, 0), float_rect_0.GetLocation());\n  EXPECT_EQ(FloatSize(0, 0), float_rect_0.GetSize());\n  EXPECT_EQ(FloatPoint(3.3, 0.4), float_rect_1.GetLocation());\n  EXPECT_EQ(FloatSize(4.3, 2.8), float_rect_1.GetSize());\n  EXPECT_FLOAT_EQ(7.6, float_rect_1.MaxX());\n  EXPECT_FLOAT_EQ(3.2, float_rect_1.MaxY());\n\n  float_rect_0.SetSize(FloatSize(4.3, 2.8));\n  float_rect_0.SetLocation(FloatPoint(3.3, 0.4));\n  EXPECT_EQ(FloatPoint(3.3, 0.4), float_rect_0.GetLocation());\n  EXPECT_EQ(FloatSize(4.3, 2.8), float_rect_0.GetSize());\n}\n\nTEST(GeometryTest, RectContains) {\n  FloatRect float_rect_0(FloatPoint(3.3, 0.4), FloatSize(4.3, 2.8));\n  EXPECT_TRUE(float_rect_0.Contains(4.5, 1.2));\n  EXPECT_FALSE(float_rect_0.Contains(1.2, 1.2));\n  EXPECT_FALSE(float_rect_0.Contains(10, 1.2));\n  EXPECT_FALSE(float_rect_0.Contains(4.5, 4.2));\n  EXPECT_FALSE(float_rect_0.Contains(10, 4.2));\n  EXPECT_FALSE(float_rect_0.Contains(10, 0));\n  EXPECT_FALSE(float_rect_0.Contains(4.5, 0));\n}\n\nTEST(GeometryTest, RectIntersectedSize) {\n  FloatRect float_rect_0(FloatPoint(3.3, 0.4), FloatSize(4.3, 2.8));\n  FloatRect float_rect_1(FloatPoint(3.4, 0.8), FloatSize(4.3, 2.8));\n  FloatRect float_rect_2(FloatPoint(1.2, 1.2), FloatSize(4.3, 2.8));\n  FloatRect float_rect_3(FloatPoint(1.2, 100.2), FloatSize(4.3, 2.8));\n  FloatRect float_rect_4(FloatPoint(-100.f, 1.2), FloatSize(4.3, 2.8));\n  EXPECT_TRUE(float_rect_0.IsIntersectedWith(float_rect_1));\n  EXPECT_TRUE(float_rect_0.IsIntersectedWith(float_rect_2));\n  EXPECT_FALSE(float_rect_0.IsIntersectedWith(float_rect_3));\n  EXPECT_FALSE(float_rect_0.IsIntersectedWith(float_rect_4));\n}\n\nTEST(GeometryTest, RectIntersect) {\n  IntRect int_rect_0(IntPoint(3, 0), IntSize(4, 5));\n  IntRect int_rect_1(IntPoint(3, 0), IntSize(4, 5));\n  IntRect int_rect_2(IntPoint(1, 4), IntSize(4, 5));\n  IntRect int_rect_3(IntPoint(1, 100), IntSize(4, 5));\n  IntRect int_rect_4(IntPoint(-100, 1), IntSize(4, 5));\n  IntRect int_rect_5(IntPoint(4, 3), IntSize(1, 1));\n  IntRect int_rect_6(IntPoint(1, -2), IntSize(4, 5));\n\n  IntRect int_rect = int_rect_0;\n  int_rect.Intersect(int_rect_1);\n\n  EXPECT_EQ(3, int_rect.X());\n  EXPECT_EQ(0, int_rect.Y());\n  EXPECT_EQ(7, int_rect.MaxX());\n  EXPECT_EQ(5, int_rect.MaxY());\n\n  int_rect = int_rect_0;\n  int_rect.Intersect(int_rect_2);\n\n  EXPECT_EQ(3, int_rect.X());\n  EXPECT_EQ(4, int_rect.Y());\n  EXPECT_EQ(5, int_rect.MaxX());\n  EXPECT_EQ(5, int_rect.MaxY());\n\n  int_rect = int_rect_0;\n  int_rect.Intersect(int_rect_3);\n\n  EXPECT_TRUE(int_rect.IsEmpty());\n\n  int_rect = int_rect_0;\n  int_rect.Intersect(int_rect_4);\n\n  EXPECT_TRUE(int_rect.IsEmpty());\n\n  int_rect = int_rect_0;\n  int_rect.Intersect(int_rect_5);\n\n  EXPECT_EQ(4, int_rect.X());\n  EXPECT_EQ(3, int_rect.Y());\n  EXPECT_EQ(5, int_rect.MaxX());\n  EXPECT_EQ(4, int_rect.MaxY());\n\n  int_rect = int_rect_0;\n  int_rect.Intersect(int_rect_6);\n\n  EXPECT_EQ(3, int_rect.X());\n  EXPECT_EQ(0, int_rect.Y());\n  EXPECT_EQ(5, int_rect.MaxX());\n  EXPECT_EQ(3, int_rect.MaxY());\n}\n\n}  // namespace\n}  // namespace geometry\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/hybrid_map_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/hybrid_map.h\"\n\n#include <algorithm>\n#include <map>\n#include <string>\n#include <unordered_map>\n\n#include \"base/include/boost/unordered.h\"\n#include \"base/include/vector.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace test {\n\nstatic std::string ConcatOrderedMap(\n    const std::map<std::string, std::string> map) {\n  std::string result;\n  for (const auto& p : map) {\n    result += p.first;\n    result += p.second;\n  }\n  return result;\n}\n\nusing MapPolicyStdMap = MapPolicy<std::map>;\nusing MapPolicyStdUnorderedMap = MapPolicy<std::unordered_map>;\nusing MapPolicyLinearFlatMap = MapPolicy<LinearFlatMap>;\nusing MapPolicyOrderedFlatMap = MapPolicy<OrderedFlatMap>;\nusing MapPolicyBoostFlatMap = MapPolicy<boost::unordered_flat_map>;\n\ntemplate <size_t N, typename... Args>\nusing MapPolicyInlineLinearFlatMap =\n    InlineFlatMapPolicy<InlineLinearFlatMap, N, Args...>;\n\ntemplate <size_t N>\nusing MapPolicyInlineOrderedFlatMap =\n    InlineFlatMapPolicy<InlineOrderedFlatMap, N>;\n\ntemplate <class MAP>\nstatic void Test_MapInsertOrAssign() {\n  MAP map{{\"3\", \"c\"}, {\"2\", \"b\"}, {\"1\", \"a\"}};\n  EXPECT_TRUE(map.size() == 3);\n  EXPECT_EQ(map[\"1\"], \"a\");\n  EXPECT_EQ(map[\"2\"], \"b\");\n  EXPECT_EQ(map[\"3\"], \"c\");\n  EXPECT_EQ(map[\"4\"], \"\");\n\n  auto r = map.insert_or_assign(\"4\", \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[\"4\"], \"d\");\n\n  std::string s5 = \"5\";\n  std::string se = \"e\";\n  auto r2 = map.insert_or_assign(s5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[\"5\"], \"e\");\n  EXPECT_EQ(s5, \"5\");\n  EXPECT_TRUE(se.empty());\n\n  std::string s6 = \"6\";\n  std::string sf = \"f\";\n  auto r3 = map.insert_or_assign(std::move(s6), std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[\"6\"], \"f\");\n  EXPECT_TRUE(s6.empty());\n  EXPECT_TRUE(sf.empty());\n\n  std::string s7 = \"7\";\n  std::string sg = \"g\";\n  auto r4 = map.insert_or_assign(std::move(s7), sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[\"7\"], \"g\");\n  EXPECT_TRUE(s7.empty());\n  EXPECT_EQ(sg, \"g\");\n\n  EXPECT_EQ(map.size(), 7u);\n}\n\ntemplate <class MAP>\nstatic void Test_MapInsertOrAssign2() {\n  MAP m;\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(*it, \"apple\");\n    EXPECT_EQ(m.size(), 1u);\n  }\n\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"banana\");\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(*it, \"banana\");\n    EXPECT_EQ(m.size(), 1u);\n  }\n\n  m.insert_or_assign(\"empty\", \"\");\n  EXPECT_EQ(m[\"empty\"], \"\");\n}\n\ntemplate <class MAP>\nstatic void Test_MapEmplace() {\n  MAP map;\n  auto r = map.emplace(std::piecewise_construct,\n                       std::tuple<const char*, size_t>(\"123\", 2),\n                       std::tuple<const char*, size_t>(\"abc\", 2));\n  EXPECT_TRUE(r.second);\n  EXPECT_EQ(*r.first, \"ab\");\n  auto r2 = map.emplace(std::piecewise_construct,\n                        std::tuple<const char*, size_t>(\"112\", 2),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(*r2.first, \"xy\");\n\n  EXPECT_EQ(map.size(), 2u);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n\n  auto r3 = map.emplace(std::piecewise_construct, std::forward_as_tuple(\"12\"),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(*r3.first, \"ab\");\n\n  EXPECT_EQ(map.size(), 2u);\n\n  auto r4 = map.try_emplace(\"11\", \"ab\");\n  EXPECT_FALSE(r4.second);\n  EXPECT_EQ(*r4.first, \"xy\");\n\n  std::string s11 = \"11\";\n  std::string sXYZ = \"xyz\";\n  auto r5 = map.try_emplace(std::move(s11), std::move(sXYZ));\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(*r5.first, \"xy\");\n  EXPECT_EQ(s11, \"11\");\n  EXPECT_EQ(sXYZ, \"xyz\");\n\n  std::string s13 = \"13\";\n  auto r6 = map.try_emplace(std::move(s13), std::move(sXYZ));\n  EXPECT_TRUE(r6.second);\n  EXPECT_EQ(*r6.first, \"xyz\");\n  EXPECT_TRUE(s13.empty());\n  EXPECT_TRUE(sXYZ.empty());\n\n  EXPECT_EQ(map.size(), 3u);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n\n  std::string s14 = \"14\";\n  std::string sUVW = \"uvw\";\n  auto r7 = map.try_emplace(s14, sUVW);\n  EXPECT_TRUE(r7.second);\n  EXPECT_EQ(*r7.first, \"uvw\");\n  EXPECT_EQ(s14, \"14\");\n  EXPECT_EQ(sUVW, \"uvw\");\n\n  EXPECT_EQ(map.size(), 4u);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n  EXPECT_EQ(map[\"14\"], \"uvw\");\n}\n\ntemplate <class MAP>\nstatic void Test_MapElementAccess() {\n  MAP m{{\"apple\", \"red\"}, {\"banana\", \"yellow\"}};\n\n  EXPECT_EQ(m[\"apple\"], \"red\");\n\n  m[\"apple\"] = \"green\";\n  EXPECT_EQ(m[\"apple\"], \"green\");\n  EXPECT_EQ(m.at(\"apple\"), \"green\");\n\n  EXPECT_EQ(m[\"grape\"], \"\");\n  EXPECT_EQ(m.at(\"grape\"), \"\");\n  EXPECT_EQ(m.size(), 3u);\n}\n\ntemplate <class MAP>\nstatic void Test_MapInsertUpdate() {\n  MAP m;\n\n  auto ret1 = m.insert(\"fruit\", \"apple\");\n  EXPECT_TRUE(ret1.second);\n  auto ret2 = m.insert(\"fruit\", \"banana\");\n  EXPECT_FALSE(ret2.second);\n  EXPECT_EQ(*ret2.first, \"apple\");\n\n  auto emp_ret = m.emplace(\"color\", \"blue\");\n  EXPECT_TRUE(emp_ret.second);\n  EXPECT_EQ(*emp_ret.first, \"blue\");\n\n  m[\"color\"] = \"red\";\n  EXPECT_EQ(m[\"color\"], \"red\");\n}\n\ntemplate <class MAP>\nstatic void Test_MapEraseOperations() {\n  MAP m{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_EQ(m.size(), 3u);\n  EXPECT_EQ(m[\"A\"], \"1\");\n  EXPECT_EQ(m[\"B\"], \"2\");\n  EXPECT_EQ(m[\"C\"], \"3\");\n\n  size_t cnt = m.erase(\"B\");\n  EXPECT_EQ(cnt, 1u);\n  EXPECT_EQ(m.size(), 2u);\n  EXPECT_FALSE(m.contains(\"B\"));\n\n  EXPECT_EQ(m.erase(\"X\"), 0u);\n}\n\ntemplate <class MAP>\nstatic void Test_MapEdgeCases() {\n  MAP m;\n\n  m[\"\"] = \"empty_key\";\n  m.emplace(\"empty_value\", \"\");\n  EXPECT_EQ(m[\"\"], \"empty_key\");\n  EXPECT_EQ(m[\"empty_value\"], \"\");\n\n  std::string big_key(1000, 'K');\n  std::string big_value(10000, 'V');\n  m[big_key] = big_value;\n  EXPECT_EQ(m[big_key].size(), 10000u);\n}\n\ntemplate <class MAP>\nstatic void Test_MapEmplacePiecewise() {\n  MAP m;\n\n  auto emp_it =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(5, 'X'));\n  ASSERT_TRUE(emp_it.second);\n  EXPECT_EQ(*emp_it.first, \"XXXXX\");\n\n  m.emplace(std::piecewise_construct, std::forward_as_tuple(3, 'K'),\n            std::forward_as_tuple(3, 'k'));\n  EXPECT_EQ(m[\"KKK\"], \"kkk\");\n\n  auto emp_fail =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(\"new_value\"));\n  EXPECT_FALSE(emp_fail.second);\n  EXPECT_EQ(m[\"piece_key\"], \"XXXXX\");\n}\n\ntemplate <class MAP>\nstatic void Test_MapForeach() {\n  std::map<std::string, std::string> visited;\n  MAP map;\n  map[\"B\"] = \"2\";\n  map[\"A\"] = \"1\";\n  map[\"C\"] = \"3\";\n  const auto& const_map = map;\n  const_map.for_each([&](const std::string& key, const std::string& value) {\n    visited[key] = value;\n  });\n  EXPECT_TRUE(ConcatOrderedMap(visited) == \"A1B2C3\");\n\n  visited.clear();\n  auto map2 = map;\n  map2.for_each([&](const std::string& key, std::string& value) {\n    visited[key] = value;\n    if (key == \"B\") {\n      value = \"22\";\n    }\n  });\n  EXPECT_TRUE(ConcatOrderedMap(visited) == \"A1B2C3\");\n  EXPECT_TRUE(map2[\"B\"] == \"22\");\n}\n\ntemplate <class MAP>\nstatic void Test_MapIterator() {\n  std::map<std::string, std::string> visited;\n  MAP map;\n  map[\"B\"] = \"2\";\n  map[\"A\"] = \"1\";\n  map[\"C\"] = \"3\";\n  EXPECT_TRUE(map.find_iterator(\"B\")->first == \"B\");\n  EXPECT_TRUE(map.find_iterator(\"B\")->second == \"2\");\n  EXPECT_EQ(map.find_iterator(\"D\"), map.end());\n\n  const auto& const_map = map;\n  for (const auto& p : const_map) {\n    visited[p.first] = p.second;\n  }\n  EXPECT_TRUE(ConcatOrderedMap(visited) == \"A1B2C3\");\n\n  visited.clear();\n  auto map2 = map;\n  for (auto it = map2.begin(); it != map2.end(); it++) {\n    visited[it->first] = it->second;\n    if (it->first == \"B\") {\n      it->second = \"22\";\n    }\n  }\n  EXPECT_TRUE(ConcatOrderedMap(visited) == \"A1B2C3\");\n  EXPECT_TRUE(map2[\"B\"] == \"22\");\n\n  visited.clear();\n  for (auto it = map.cbegin(); it != map.cend(); it++) {\n    visited[it->first] = it->second;\n  }\n  EXPECT_TRUE(ConcatOrderedMap(visited) == \"A1B2C3\");\n}\n\ntemplate <class MAP>\nstatic void Test_MapEraseIterator() {\n  MAP map;\n  for (int i = 1; i <= 10; ++i) {\n    map[\"key_\" + std::to_string(i)] = std::to_string(i);\n  }\n  for (auto it = map.begin(); it != map.end();) {\n    if (std::stoi(it->second) % 2 == 0) {\n      it = map.erase_iterator(it);\n    } else {\n      ++it;\n    }\n  }\n\n  EXPECT_TRUE(map.size() == 5);\n  for (const auto& pair : map) {\n    EXPECT_TRUE(std::stoi(pair.second) % 2 != 0);\n  }\n\n  const auto& const_map = map;\n  auto it = const_map.find_iterator(\"key_5\");\n  EXPECT_TRUE(it != const_map.end());\n  EXPECT_TRUE(it->second == \"5\");\n  map.erase_iterator(it);\n  EXPECT_TRUE(map.size() == 4);\n  it = const_map.find_iterator(\"key_5\");\n  EXPECT_TRUE(it == const_map.end());\n\n  int i = 0;\n  while (!map.empty()) {\n    map.erase_iterator(map.begin());\n    i++;\n  }\n  EXPECT_EQ(i, 4);\n}\n\ntemplate <class MAP>\nstatic void assert_map_content(MAP& map, const base::Vector<std::string> keys,\n                               const base::Vector<std::string> values) {\n  EXPECT_EQ(keys.size(), values.size());\n  EXPECT_EQ(map.size(), keys.size());\n  if (keys.size() > 0) {\n    EXPECT_TRUE(!map.empty());\n  } else {\n    EXPECT_TRUE(map.empty());\n  }\n  for (size_t i = 0; i < keys.size(); i++) {\n    auto it = map.find(keys[i]);\n    EXPECT_TRUE(it != nullptr);\n    EXPECT_TRUE(*it == values[i]);\n    EXPECT_TRUE(map.at(keys[i]) == values[i]);\n    EXPECT_TRUE(map.count(keys[i]) == 1);\n    EXPECT_TRUE(map.contains(keys[i]));\n  }\n};\n\ntemplate <class MAP>\nstatic void Test_MapMisc_4_As_SmallMap_MaxSize() {\n  {\n    MAP map_small{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}, {\"D\", \"4\"}};\n    EXPECT_TRUE(map_small.using_small_map());\n    MAP map_small2{{\"A\", \"1\"}, {\"B\", \"2\"}};\n    EXPECT_TRUE(map_small2.using_small_map());\n    MAP map_big{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}, {\"D\", \"4\"}, {\"E\", \"5\"}};\n    EXPECT_FALSE(map_big.using_small_map());\n    MAP map_big2{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"},\n                 {\"D\", \"4\"}, {\"E\", \"5\"}, {\"F\", \"6\"}};\n    EXPECT_FALSE(map_big2.using_small_map());\n\n    MAP map(map_small);\n    EXPECT_TRUE(map.using_small_map());\n    assert_map_content(map, {\"A\", \"B\", \"C\", \"D\"}, {\"1\", \"2\", \"3\", \"4\"});\n    map = map_small2;\n    EXPECT_TRUE(map.using_small_map());\n    assert_map_content(map, {\"A\", \"B\"}, {\"1\", \"2\"});\n    map = map_big;\n    EXPECT_FALSE(map.using_small_map());\n    assert_map_content(map, {\"A\", \"B\", \"C\", \"D\", \"E\"},\n                       {\"1\", \"2\", \"3\", \"4\", \"5\"});\n    map = map_big2;\n    EXPECT_FALSE(map.using_small_map());\n    assert_map_content(map, {\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"},\n                       {\"1\", \"2\", \"3\", \"4\", \"5\", \"6\"});\n    map = map_small;\n    EXPECT_TRUE(map.using_small_map());\n    assert_map_content(map, {\"A\", \"B\", \"C\", \"D\"}, {\"1\", \"2\", \"3\", \"4\"});\n\n    MAP map2(map_big);\n    EXPECT_FALSE(map2.using_small_map());\n    assert_map_content(map2, {\"A\", \"B\", \"C\", \"D\", \"E\"},\n                       {\"1\", \"2\", \"3\", \"4\", \"5\"});\n\n    MAP map3(std::move(map));\n    EXPECT_TRUE(map.empty());\n    EXPECT_TRUE(map3.using_small_map());\n    assert_map_content(map3, {\"A\", \"B\", \"C\", \"D\"}, {\"1\", \"2\", \"3\", \"4\"});\n\n    MAP map_big_copy = map_big;\n    EXPECT_FALSE(map_big_copy.using_small_map());\n\n    MAP map4(std::move(map_big_copy));\n    EXPECT_TRUE(map_big_copy.empty());\n    EXPECT_FALSE(map4.using_small_map());\n    assert_map_content(map4, {\"A\", \"B\", \"C\", \"D\", \"E\"},\n                       {\"1\", \"2\", \"3\", \"4\", \"5\"});\n\n    MAP map_small_copy = map_small;\n    map4 = std::move(map_small_copy);\n    EXPECT_TRUE(map_small_copy.empty());\n    EXPECT_TRUE(map4.using_small_map());\n    assert_map_content(map4, {\"A\", \"B\", \"C\", \"D\"}, {\"1\", \"2\", \"3\", \"4\"});\n\n    map4 = std::move(map_small2);\n    EXPECT_TRUE(map_small2.empty());\n    EXPECT_TRUE(map4.using_small_map());\n    assert_map_content(map4, {\"A\", \"B\"}, {\"1\", \"2\"});\n\n    map4 = std::move(map_big2);\n    EXPECT_TRUE(map_big2.empty());\n    EXPECT_FALSE(map4.using_small_map());\n    assert_map_content(map4, {\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"},\n                       {\"1\", \"2\", \"3\", \"4\", \"5\", \"6\"});\n\n    map4 = std::move(map_big);\n    EXPECT_TRUE(map_big.empty());\n    EXPECT_FALSE(map4.using_small_map());\n    assert_map_content(map4, {\"A\", \"B\", \"C\", \"D\", \"E\"},\n                       {\"1\", \"2\", \"3\", \"4\", \"5\"});\n  }\n\n  {\n    MAP map;\n    map[\"a\"] = \"1\";\n    map[\"b\"] = \"2\";\n    map[\"c\"] = \"3\";\n    map[\"d\"] = \"4\";\n    EXPECT_TRUE(map.using_small_map());\n    map[\"d\"] = \"5\";\n    EXPECT_TRUE(map.using_small_map());\n    assert_map_content(map, {\"a\", \"b\", \"c\", \"d\"}, {\"1\", \"2\", \"3\", \"5\"});\n    map.reserve(10);\n    EXPECT_FALSE(map.using_small_map());\n    assert_map_content(map, {\"a\", \"b\", \"c\", \"d\"}, {\"1\", \"2\", \"3\", \"5\"});\n  }\n\n  {\n    MAP map;\n    map[\"a\"] = \"1\";\n    map[\"b\"] = \"2\";\n    map[\"c\"] = \"3\";\n    map[\"d\"] = \"4\";\n    EXPECT_TRUE(map.using_small_map());\n    assert_map_content(map, {\"a\", \"b\", \"c\", \"d\"}, {\"1\", \"2\", \"3\", \"4\"});\n    std::string c_str = \"c\";\n    map[c_str] = \"33\";\n    map[\"d\"] = \"44\";\n    EXPECT_TRUE(map.using_small_map());\n    assert_map_content(map, {\"a\", \"b\", \"c\", \"d\"}, {\"1\", \"2\", \"33\", \"44\"});\n    map[\"e\"] = \"5\";\n    EXPECT_FALSE(map.using_small_map());\n    assert_map_content(map, {\"a\", \"b\", \"c\", \"d\", \"e\"},\n                       {\"1\", \"2\", \"33\", \"44\", \"5\"});\n    std::string f_str = \"f\";\n    map[f_str] = \"6\";\n    EXPECT_FALSE(map.using_small_map());\n    assert_map_content(map, {\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"},\n                       {\"1\", \"2\", \"33\", \"44\", \"5\", \"6\"});\n  }\n\n  MAP map;\n  EXPECT_TRUE(map.using_small_map());\n  EXPECT_TRUE(map.empty());\n  EXPECT_TRUE(map.size() == 0);\n\n  auto ret = map.insert(\"apple\", \"red\");\n  ASSERT_TRUE(ret.second);\n  EXPECT_EQ(map[\"apple\"], \"red\");\n  EXPECT_EQ(*ret.first, \"red\");\n  assert_map_content(map, {\"apple\"}, {\"red\"});\n\n  ret = map.emplace(std::piecewise_construct, std::forward_as_tuple(3, 'K'),\n                    std::forward_as_tuple(3, 'k'));\n  ASSERT_TRUE(ret.second);\n  EXPECT_EQ(map[\"KKK\"], \"kkk\");\n  EXPECT_EQ(*ret.first, \"kkk\");\n  assert_map_content(map, {\"apple\", \"KKK\"}, {\"red\", \"kkk\"});\n\n  ret = map.try_emplace(\"KKK\", \"ab\");\n  EXPECT_FALSE(ret.second);\n  EXPECT_EQ(*ret.first, \"kkk\");\n  EXPECT_TRUE(map.using_small_map());\n  assert_map_content(map, {\"apple\", \"KKK\"}, {\"red\", \"kkk\"});\n\n  map[\"banana\"] = \"black\";\n  EXPECT_EQ(map[\"banana\"], \"black\");\n  assert_map_content(map, {\"apple\", \"KKK\", \"banana\"}, {\"red\", \"kkk\", \"black\"});\n  std::string banana = \"banana\";\n  ret = map.insert_or_assign(banana, \"pink\");\n  EXPECT_FALSE(ret.second);\n  EXPECT_EQ(map[\"banana\"], \"pink\");\n  assert_map_content(map, {\"apple\", \"KKK\", \"banana\"}, {\"red\", \"kkk\", \"pink\"});\n  ret = map.insert_or_assign(std::move(banana), \"yellow\");\n  EXPECT_FALSE(ret.second);\n  EXPECT_EQ(map[\"banana\"], \"yellow\");\n  assert_map_content(map, {\"apple\", \"KKK\", \"banana\"}, {\"red\", \"kkk\", \"yellow\"});\n  EXPECT_TRUE(map.using_small_map());\n\n  std::string sJJJ = \"JJJ\";\n  ret = map.insert_or_assign(std::move(sJJJ), \"jjj\");\n  EXPECT_TRUE(ret.second);\n  EXPECT_TRUE(sJJJ.empty());\n  EXPECT_EQ(map[\"JJJ\"], \"jjj\");\n  EXPECT_TRUE(map.using_small_map());\n  assert_map_content(map, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                     {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n\n  EXPECT_EQ(map.at(\"apple\"), \"red\");\n  EXPECT_TRUE(map.count(\"banana\") == 1);\n  EXPECT_TRUE(map.count(\"orange\") == 0);\n  EXPECT_TRUE(map.find(\"banana\") != nullptr);\n  EXPECT_TRUE(map.find(\"orange\") == nullptr);\n  EXPECT_TRUE(map.contains(\"banana\"));\n  EXPECT_FALSE(map.contains(\"orange\"));\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    map_copy[\"AAA\"] = \"aaa\";\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n    map_copy[\"AAA\"] = \"aaaa\";\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaaa\"});\n    auto ret = map_copy.insert_or_assign(\"AAA\", \"aaaaa\");\n    EXPECT_FALSE(ret.second);\n    EXPECT_EQ(*ret.first, \"aaaaa\");\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaaaa\"});\n\n    ret = map_copy.emplace(std::piecewise_construct,\n                           std::forward_as_tuple(3, 'K'),\n                           std::forward_as_tuple(5, 'k'));\n    ASSERT_FALSE(ret.second);\n    EXPECT_EQ(*ret.first, \"kkk\");\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaaaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    auto ret = map_copy.insert(\"AAA\", \"aaa\");\n    EXPECT_TRUE(ret.second);\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    EXPECT_EQ(*ret.first, \"aaa\");\n    EXPECT_EQ(ret.first, map_copy.find(\"AAA\"));\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    auto ret = map_copy.insert(\"apple\", \"green\");\n    EXPECT_FALSE(ret.second);\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    const std::string key = \"apple\";\n    std::string value = \"green\";\n    auto ret = map_copy.insert(key, value);\n    EXPECT_FALSE(ret.second);\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    const std::string key = \"apple\";\n    std::string value = \"green\";\n    auto ret = map_copy.insert(key, std::move(value));\n    EXPECT_FALSE(ret.second);\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    std::string key = \"apple\";\n    std::string value = \"green\";\n    auto ret = map_copy.insert(std::move(key), value);\n    EXPECT_FALSE(ret.second);\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    std::string key = \"apple\";\n    std::string value = \"green\";\n    auto ret = map_copy.insert(std::move(key), std::move(value));\n    EXPECT_FALSE(ret.second);\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    std::pair<std::string, std::string> data = {\"AAA\", \"aaa\"};\n    auto ret = map_copy.insert(data.first, data.second);\n    EXPECT_TRUE(ret.second);\n    EXPECT_FALSE(data.first.empty());\n    EXPECT_FALSE(data.second.empty());\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    EXPECT_EQ(*ret.first, \"aaa\");\n    EXPECT_EQ(ret.first, map_copy.find(\"AAA\"));\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    std::pair<std::string, std::string> data = {\"AAA\", \"aaa\"};\n    auto ret = map_copy.insert(std::move(data.first), std::move(data.second));\n    EXPECT_TRUE(ret.second);\n    EXPECT_TRUE(data.first.empty());\n    EXPECT_TRUE(data.second.empty());\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    EXPECT_EQ(*ret.first, \"aaa\");\n    EXPECT_EQ(ret.first, map_copy.find(\"AAA\"));\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    const std::pair<const std::string, std::string> data = {\"AAA\", \"aaa\"};\n    auto ret = map_copy.insert(data.first, data.second);\n    EXPECT_TRUE(ret.second);\n    EXPECT_FALSE(data.first.empty());\n    EXPECT_FALSE(data.second.empty());\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    EXPECT_EQ(*ret.first, \"aaa\");\n    EXPECT_EQ(ret.first, map_copy.find(\"AAA\"));\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    std::pair<const std::string, std::string> data = {\"AAA\", \"aaa\"};\n    auto ret = map_copy.insert(std::move(data.first), std::move(data.second));\n    EXPECT_TRUE(ret.second);\n    EXPECT_FALSE(data.first.empty());\n    EXPECT_TRUE(data.second.empty());\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    EXPECT_EQ(*ret.first, \"aaa\");\n    EXPECT_EQ(ret.first, map_copy.find(\"AAA\"));\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n    const std::string key = \"apple\";\n    auto ret = map_copy.insert_or_assign(key, \"green\");\n    EXPECT_FALSE(ret.second);                 // assignment took place\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    EXPECT_EQ(*ret.first, \"green\");\n    EXPECT_EQ(ret.first, map_copy.find(\"apple\"));\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"green\", \"kkk\", \"yellow\", \"jjj\"});\n    auto ret2 = map_copy.insert_or_assign(\"AAA\", \"aaa\");\n    EXPECT_TRUE(ret2.second);  // insertion took place\n    EXPECT_EQ(*ret2.first, \"aaa\");\n    EXPECT_EQ(ret2.first, map_copy.find(\"AAA\"));\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"green\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n    auto ret = map_copy.insert_or_assign(\"apple\", \"green\");\n    EXPECT_FALSE(ret.second);                 // assignment took place\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    EXPECT_EQ(*ret.first, \"green\");\n    EXPECT_EQ(ret.first, map_copy.find(\"apple\"));\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"green\", \"kkk\", \"yellow\", \"jjj\"});\n    auto ret2 = map_copy.insert_or_assign(\"AAA\", \"aaa\");\n    EXPECT_TRUE(ret2.second);\n    EXPECT_EQ(*ret2.first, \"aaa\");\n    EXPECT_EQ(ret2.first, map_copy.find(\"AAA\"));\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"green\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    const std::string key = \"apple\";\n    auto ret = map_copy.emplace(key, \"green\", 4);\n    EXPECT_FALSE(ret.second);\n    EXPECT_EQ(*ret.first, \"red\");\n    EXPECT_EQ(ret.first, map_copy.find(key));\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n    const std::string key2 = \"AAA\";\n    auto ret2 = map_copy.emplace(key2, \"aaaaaa\", 3);\n    EXPECT_TRUE(ret2.second);\n    EXPECT_EQ(*ret2.first, \"aaa\");\n    EXPECT_EQ(ret2.first, map_copy.find(\"AAA\"));\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    const std::string key = \"apple\";\n    auto ret = map_copy.emplace(key, \"green\", 4);\n    EXPECT_FALSE(ret.second);\n    EXPECT_EQ(*ret.first, \"red\");\n    EXPECT_EQ(ret.first, map_copy.find(key));\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n    std::string key2 = \"AAA\";\n    auto ret2 = map_copy.emplace(std::move(key2), \"aaaaaa\", 3);\n    EXPECT_TRUE(key2.empty());\n    EXPECT_TRUE(ret2.second);\n    EXPECT_EQ(*ret2.first, \"aaa\");\n    EXPECT_EQ(ret2.first, map_copy.find(\"AAA\"));\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n\n  {\n    auto map_copy = map;\n    EXPECT_TRUE(map_copy.using_small_map());\n    auto ret = map_copy.emplace(std::piecewise_construct,\n                                std::forward_as_tuple(3, 'K'),\n                                std::forward_as_tuple(3, 'i'));\n    EXPECT_FALSE(ret.second);\n    EXPECT_EQ(*ret.first, \"kkk\");\n    EXPECT_EQ(ret.first, map_copy.find(\"KKK\"));\n    EXPECT_TRUE(map_copy.using_small_map());  // not transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\"});\n    auto ret2 = map_copy.emplace(std::piecewise_construct,\n                                 std::forward_as_tuple(3, 'A'),\n                                 std::forward_as_tuple(3, 'a'));\n    EXPECT_TRUE(ret2.second);\n    EXPECT_EQ(*ret2.first, \"aaa\");\n    EXPECT_EQ(ret2.first, map_copy.find(\"AAA\"));\n    EXPECT_FALSE(map_copy.using_small_map());  // transferred\n    assert_map_content(map_copy, {\"apple\", \"KKK\", \"banana\", \"JJJ\", \"AAA\"},\n                       {\"red\", \"kkk\", \"yellow\", \"jjj\", \"aaa\"});\n  }\n}\n\n#define RUN_TESTS_FOR_MAP()        \\\n  Test_MapInsertOrAssign<MAP>();   \\\n  Test_MapInsertOrAssign2<MAP>();  \\\n  Test_MapEmplace<MAP>();          \\\n  Test_MapElementAccess<MAP>();    \\\n  Test_MapInsertUpdate<MAP>();     \\\n  Test_MapEraseOperations<MAP>();  \\\n  Test_MapEdgeCases<MAP>();        \\\n  Test_MapEmplacePiecewise<MAP>(); \\\n  Test_MapForeach<MAP>();          \\\n  Test_MapIterator<MAP>();         \\\n  Test_MapEraseIterator<MAP>()\n\n#define RUN_TESTS_OF_MAX_SMALL_SIZE(N, INLINE_N)                               \\\n  {                                                                            \\\n    using MAP = HybridMap<std::string, std::string, N, MapPolicyStdMap,        \\\n                          MapPolicyStdUnorderedMap>;                           \\\n    RUN_TESTS_FOR_MAP();                                                       \\\n    if (N == 4) {                                                              \\\n      Test_MapMisc_4_As_SmallMap_MaxSize<MAP>();                               \\\n    }                                                                          \\\n  }                                                                            \\\n  {                                                                            \\\n    using MAP = HybridMap<std::string, std::string, N,                         \\\n                          MapPolicyStdUnorderedMap, MapPolicyStdMap>;          \\\n    RUN_TESTS_FOR_MAP();                                                       \\\n    if (N == 4) {                                                              \\\n      Test_MapMisc_4_As_SmallMap_MaxSize<MAP>();                               \\\n    }                                                                          \\\n  }                                                                            \\\n  {                                                                            \\\n    using MAP = HybridMap<std::string, std::string, N, MapPolicyLinearFlatMap, \\\n                          MapPolicyStdUnorderedMap>;                           \\\n    RUN_TESTS_FOR_MAP();                                                       \\\n    if (N == 4) {                                                              \\\n      Test_MapMisc_4_As_SmallMap_MaxSize<MAP>();                               \\\n    }                                                                          \\\n  }                                                                            \\\n  {                                                                            \\\n    using MAP = HybridMap<std::string, std::string, N, MapPolicyLinearFlatMap, \\\n                          MapPolicyOrderedFlatMap>;                            \\\n    RUN_TESTS_FOR_MAP();                                                       \\\n    if (N == 4) {                                                              \\\n      Test_MapMisc_4_As_SmallMap_MaxSize<MAP>();                               \\\n    }                                                                          \\\n  }                                                                            \\\n  {                                                                            \\\n    using MAP = HybridMap<std::string, std::string, N, MapPolicyLinearFlatMap, \\\n                          MapPolicyBoostFlatMap>;                              \\\n    RUN_TESTS_FOR_MAP();                                                       \\\n    if (N == 4) {                                                              \\\n      Test_MapMisc_4_As_SmallMap_MaxSize<MAP>();                               \\\n    }                                                                          \\\n  }                                                                            \\\n  {                                                                            \\\n    using MAP = HybridMap<std::string, std::string, N,                         \\\n                          MapPolicyInlineLinearFlatMap<INLINE_N>,              \\\n                          MapPolicyBoostFlatMap>;                              \\\n    RUN_TESTS_FOR_MAP();                                                       \\\n    if (N == 4) {                                                              \\\n      Test_MapMisc_4_As_SmallMap_MaxSize<MAP>();                               \\\n    }                                                                          \\\n  }                                                                            \\\n  {                                                                            \\\n    using MAP = HybridMap<std::string, std::string, N,                         \\\n                          MapPolicyInlineOrderedFlatMap<INLINE_N>,             \\\n                          MapPolicyBoostFlatMap>;                              \\\n    RUN_TESTS_FOR_MAP();                                                       \\\n    if (N == 4) {                                                              \\\n      Test_MapMisc_4_As_SmallMap_MaxSize<MAP>();                               \\\n    }                                                                          \\\n  }\n\nTEST(HybridMap, All) {\n  RUN_TESTS_OF_MAX_SMALL_SIZE(2, 1);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(2, 2);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(3, 2);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(4, 2);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(4, 4);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(4, 6);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(5, 4);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(6, 4);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(7, 4);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(8, 8);\n  RUN_TESTS_OF_MAX_SMALL_SIZE(16, 8);\n}\n\nTEST(HybridMap, ReserveWithInline) {\n  using MAP = HybridMap<std::string, std::string, 4,\n                        MapPolicyInlineLinearFlatMap<2>, MapPolicyBoostFlatMap>;\n  {\n    MAP map;\n    map[\"a\"] = \"1\";\n    map[\"b\"] = \"2\";\n    EXPECT_TRUE(map.using_small_map());\n    EXPECT_TRUE(map.small_map().is_static_buffer());\n    map[\"c\"] = \"3\";\n    EXPECT_TRUE(map.using_small_map());\n    EXPECT_FALSE(map.small_map().is_static_buffer());\n    map[\"d\"] = \"4\";\n    EXPECT_TRUE(map.using_small_map());\n    EXPECT_FALSE(map.small_map().is_static_buffer());\n    map[\"e\"] = \"5\";\n    EXPECT_FALSE(map.using_small_map());\n  }\n\n  {\n    MAP map;\n    EXPECT_TRUE(map.using_small_map());\n    EXPECT_TRUE(map.small_map().is_static_buffer());\n    map.reserve(2);\n    EXPECT_TRUE(map.using_small_map());\n    EXPECT_TRUE(map.small_map().is_static_buffer());\n    map.reserve(3);\n    EXPECT_TRUE(map.using_small_map());\n    EXPECT_FALSE(map.small_map().is_static_buffer());\n    map.reserve(4);\n    EXPECT_TRUE(map.using_small_map());\n    EXPECT_FALSE(map.small_map().is_static_buffer());\n    map.reserve(5);\n    EXPECT_FALSE(map.using_small_map());\n  }\n}\n\n}  // namespace test\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/linked_hash_map_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/linked_hash_map.h\"\n\n#include <set>\n#include <string>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace test {\n\nTEST(LinkedHashMap, insert) {\n  LinkedHashMap<std::string, std::string> map;\n  map.insert_or_assign(std::string(\"key4\"), std::string(\"value4\"));\n  map.insert_or_assign(std::string(\"key3\"), std::string(\"value3\"));\n  map.insert_or_assign(std::string(\"key2\"), std::string(\"value2222\"));\n  EXPECT_EQ(map.find(\"key2\")->second, \"value2222\");\n  const std::string value2 = \"value2\";\n  EXPECT_FALSE(map.insert_or_assign(std::string(\"key2\"), value2).second);\n  EXPECT_EQ(map.find(\"key2\")->second, value2);\n  map[std::string(\"key1\")] = std::string(\"value1111\");\n  EXPECT_EQ(map.find(\"key1\")->second, \"value1111\");\n  map[std::string(\"key1\")] = std::string(\"value1\");\n  EXPECT_EQ(map.find(\"key1\")->second, \"value1\");\n  int idx = 4;\n  for (auto it = map.begin(); it != map.end(); it++) {\n    EXPECT_EQ(it->first, std::string(\"key\") + std::to_string(idx));\n    EXPECT_EQ(it->second, std::string(\"value\") + std::to_string(idx));\n    idx--;\n  }\n  EXPECT_TRUE(map.size() == 4);\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\nTEST(LinkedHashMap, insert_if_absent) {\n  LinkedHashMap<std::string, std::string> map;\n  const std::string value2222 = \"value2222\";\n  map.insert_if_absent(std::string(\"key4\"), std::string(\"value4\"));\n  map.insert_if_absent(std::string(\"key3\"), std::string(\"value3\"));\n  map.insert_if_absent(std::string(\"key2\"), value2222);\n  EXPECT_TRUE(map.size() == 3);\n  const std::string key2 = \"key2\";\n  std::string value2 = \"value2\";\n  EXPECT_EQ(map.find(key2)->second, value2222);\n  EXPECT_FALSE(map.insert_if_absent(key2, value2).second);\n  EXPECT_EQ(map.find(key2)->second, value2222);\n  EXPECT_FALSE(map.insert_if_absent(key2, std::move(value2)).second);\n  EXPECT_EQ(map.find(key2)->second, value2222);\n  EXPECT_EQ(value2, \"value2\");  // value2 not moved\n  EXPECT_TRUE(map.insert_if_absent(\"key5\", std::move(value2)).second);\n  EXPECT_TRUE(value2.empty());  // value2 is moved\n  EXPECT_TRUE(map.size() == 4);\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\nTEST(LinkedHashMap, operator_bracket) {\n  LinkedHashMap<std::string, std::string> map;\n  map[\"abc\"] = \"123\";\n  std::string abc = \"abc\";\n  std::string xyz = \"xyz\";\n  EXPECT_EQ(map[abc], \"123\");\n  map[std::move(abc)] = \"321\";\n  map[std::move(xyz)] = \"456\";\n  EXPECT_FALSE(abc.empty());\n  EXPECT_TRUE(xyz.empty());\n  EXPECT_EQ(map[\"abc\"], \"321\");\n  EXPECT_EQ(map[\"xyz\"], \"456\");\n}\n\nTEST(LinkedHashMap, emplace) {\n  LinkedHashMap<std::string, std::string> map;\n\n  std::string key1 = \"key1\";\n  map.emplace_or_assign(key1, \"value1_abc_123\", 6);\n\n  std::string key2 = \"key2\";\n  map[key2] = \"value2\";\n  map.emplace_or_assign(key2, 5, 'v');\n\n  EXPECT_EQ(map[key1], \"value1\");\n  EXPECT_EQ(map[key2], \"vvvvv\");\n\n  map.emplace_or_assign(std::move(key1), std::move(key2));\n\n  EXPECT_TRUE(key2.empty());\n  EXPECT_EQ(map[\"key1\"], \"key2\");\n}\n\nTEST(LinkedHashMap, range_insert) {\n  // insert x values in vector, range insert x-15 values from vector to map,\n  // check values\n  const int nb_values = 1000;\n  std::vector<std::pair<int, int>> values;\n  for (int i = 0; i < nb_values; i++) {\n    values.push_back(std::make_pair(i, i + 1));\n  }\n\n  LinkedHashMap<int, int> map;\n  map[-1] = 1;\n  map[-2] = 2;\n  map.insert(values.begin() + 10, values.end() - 5);\n\n  EXPECT_EQ(map.size(), static_cast<size_t>(987));\n  EXPECT_EQ(map.at(-1), 1);\n  for (int i = 10, j = 2; i < nb_values - 5; i++, j++) {\n    EXPECT_EQ(map.at(i), i + 1);\n  }\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  map.erase(-2);\n  map.erase(99);\n  map.erase(199);\n  EXPECT_EQ(map.size(), static_cast<size_t>(984));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\nTEST(LinkedHashMap, duplicated_in_initializer) {\n  LinkedHashMap<std::string, int> map = {\n      {\"Key2\", 2}, {\"Key4\", 4},   {\"Key6\", 6},   {\"Key8\", 8},\n      {\"Key9\", 9}, {\"Key10\", 10}, {\"Key11\", 11}, {\"Key2\", 12}};\n  EXPECT_TRUE(map.size() == 7);\n  EXPECT_EQ(map[\"Key2\"], 12);\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\nTEST(LinkedHashMap, position_order) {\n  LinkedHashMap<std::string, int> map = {\n      {\"Key2\", 2}, {\"Key4\", 4},   {\"Key6\", 6},   {\"Key8\", 8},\n      {\"Key9\", 9}, {\"Key10\", 10}, {\"Key11\", 11}, {\"Key12\", 12}};\n\n  map.insert_or_assign(std::string(\"Key1\"), 1);\n  map.insert_or_assign(std::string(\"Key3\"), 3);\n  map.insert_or_assign(std::string(\"Key5\"), 5);\n  EXPECT_EQ(\n      decltype(map)::Testing::count_of_nodes_on_pool(map),\n      std::min<size_t>(\n          map.size(),\n          std::max<size_t>((size_t)8, decltype(map)::kInitialAllocationSize)));\n\n  const std::vector<std::pair<std::string, int>> vector_values = {\n      {\"Key2\", 2}, {\"Key4\", 4},   {\"Key6\", 6},   {\"Key8\", 8},\n      {\"Key9\", 9}, {\"Key10\", 10}, {\"Key11\", 11}, {\"Key12\", 12},\n      {\"Key1\", 1}, {\"Key3\", 3},   {\"Key5\", 5}};\n  EXPECT_EQ(map.size(), vector_values.size());\n  std::vector<std::pair<std::string, int>> temp_array;\n  for (auto value : map) {\n    temp_array.emplace_back(value);\n  }\n  EXPECT_EQ(temp_array.size(), vector_values.size());\n  for (size_t i = 0; i < temp_array.size(); i++) {\n    EXPECT_EQ(temp_array[i].first, vector_values[i].first);\n    EXPECT_EQ(temp_array[i].second, vector_values[i].second);\n  }\n  EXPECT_EQ(map.front(), vector_values.front());\n  EXPECT_EQ(map.back(), vector_values.back());\n}\n\nTEST(LinkedHashMap, copy_move_status) {\n  LinkedHashMap<std::string, std::string, 12, 6> map;\n  // Reserve enough\n  map.reserve(10);\n  for (int i = 0; i < 10; i++) {\n    map.insert_or_assign(std::string(\"key\") + std::to_string(i),\n                         std::string(\"value\") + std::to_string(i));\n  }\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, true));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  // Insert another node, allocate node not on pool, not perfect.\n  map.insert_or_assign(\"key10\", \"value10\");\n  EXPECT_TRUE(map.size() == 11);\n  EXPECT_EQ(decltype(map)::Testing::count_of_nodes_on_pool(map),\n            map.size() - 1);\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, false));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  // Find trigger map to be built.\n  EXPECT_EQ(map.find(\"key9\")->second,\n            \"value9\");  // Linear find, not perfect path\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, true, false));\n  EXPECT_EQ(map.find(\"key8\")->second, \"value8\");  // Hash find\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  // Copied map should always be perfect and no map.\n  auto map_copy = map;\n  EXPECT_TRUE(map_copy.size() == 11);\n  EXPECT_TRUE(\n      decltype(map_copy)::Testing::assume_status(map_copy, false, true));\n  EXPECT_TRUE(decltype(map_copy)::Testing::check_consistency(map_copy));\n\n  // Find trigger map to be built.\n  EXPECT_EQ(map_copy.find(\"key4\")->second,\n            \"value4\");  // Linear find, perfect path\n  EXPECT_TRUE(decltype(map_copy)::Testing::assume_status(map_copy, true, true));\n  EXPECT_EQ(map_copy.find(\"key6\")->second, \"value6\");  // Hash find\n  EXPECT_TRUE(decltype(map_copy)::Testing::check_consistency(map_copy));\n\n  // Make map_copy not perfect.\n  EXPECT_TRUE(map_copy.erase(\"key8\") == 1);\n  EXPECT_TRUE(map_copy.size() == 10);\n  EXPECT_TRUE(\n      decltype(map_copy)::Testing::assume_status(map_copy, true, false));\n  EXPECT_TRUE(decltype(map_copy)::Testing::check_consistency(map_copy));\n\n  // Copy to another map.\n  decltype(map_copy) map_copy_copy;\n  for (int i = 0; i < 20; i++) {\n    map_copy_copy.insert_or_assign(std::string(\"key\") + std::to_string(i),\n                                   std::string(\"value\") + std::to_string(i));\n  }\n  EXPECT_TRUE(decltype(map_copy_copy)::Testing::assume_status(map_copy_copy,\n                                                              true, false));\n  map_copy_copy = map_copy;\n  EXPECT_TRUE(decltype(map_copy_copy)::Testing::assume_status(map_copy_copy,\n                                                              true, true));\n  EXPECT_EQ(map_copy_copy.size(), map_copy.size());\n  EXPECT_TRUE(\n      decltype(map_copy_copy)::Testing::check_consistency(map_copy_copy));\n  map_copy_copy.clear();\n  EXPECT_TRUE(\n      decltype(map_copy_copy)::Testing::check_consistency(map_copy_copy));\n\n  // Move, keep status\n  auto map_move = std::move(map_copy);\n  EXPECT_TRUE(map_copy.empty());\n  EXPECT_TRUE(\n      decltype(map_copy)::Testing::assume_status(map_copy, false, true));\n  EXPECT_TRUE(decltype(map_copy)::Testing::check_consistency(map_copy));\n  EXPECT_TRUE(map_move.size() == 10);\n  EXPECT_TRUE(\n      decltype(map_move)::Testing::assume_status(map_move, true, false));\n  EXPECT_TRUE(decltype(map_move)::Testing::check_consistency(map_move));\n\n  decltype(map_copy) tiny_map;\n  tiny_map[\"key0\"] = \"value0\";\n  EXPECT_TRUE(\n      decltype(tiny_map)::Testing::assume_status(tiny_map, false, true));\n  decltype(map_copy) map_move_move;\n  for (int i = 0; i < 20; i++) {\n    map_move_move.insert_or_assign(std::string(\"key\") + std::to_string(i),\n                                   std::string(\"value\") + std::to_string(i));\n  }\n  EXPECT_TRUE(decltype(map_move_move)::Testing::assume_status(map_move_move,\n                                                              true, false));\n  map_move_move = std::move(tiny_map);\n  EXPECT_TRUE(tiny_map.empty());\n  EXPECT_TRUE(map_move_move.size() == 1);\n  EXPECT_TRUE(decltype(map_move_move)::Testing::assume_status(map_move_move,\n                                                              false, true));\n}\n\nTEST(LinkedHashMap, for_each_status) {\n  std::set<std::string> key_set;\n  LinkedHashMap<std::string, std::string, 12, 6> map;\n  map.reserve(20);\n  for (int i = 0; i < 10; i++) {\n    key_set.insert(std::string(\"key\") + std::to_string(i));\n    map.insert_or_assign(std::string(\"key\") + std::to_string(i),\n                         std::string(\"value\") + std::to_string(i));\n  }\n  EXPECT_EQ(decltype(map)::Testing::count_of_nodes_on_pool(map), map.size());\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, true));\n\n  auto key_set2 = key_set;\n\n  // Perfect map iterates base on array.\n  map.for_each([&](const std::string& key, const std::string& value) {\n    EXPECT_TRUE(key_set.count(key) == 1);\n    key_set.erase(key);\n  });\n  EXPECT_TRUE(key_set.empty());\n\n  // Make map not perfect, iterate base on linked nodes.\n  map.erase(\"key5\");\n  EXPECT_TRUE(map.size() == 9);\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, false));\n  map.for_each([&](const std::string& key, const std::string& value) {\n    EXPECT_TRUE(key_set2.count(key) == 1);\n    key_set2.erase(key);\n  });\n  EXPECT_TRUE(key_set2.size() == 1);\n  EXPECT_TRUE(key_set2.count(\"key5\") == 1);\n}\n\nTEST(LinkedHashMap, find) {\n  LinkedHashMap<std::string, std::string, 6, 10> map;\n  map.reserve(5);\n  map.insert_or_assign(std::string(\"key4\"), std::string(\"value4\"));\n  map.insert_or_assign(std::string(\"key3\"), std::string(\"value3\"));\n  map.insert_or_assign(std::string(\"key2\"), std::string(\"value2\"));\n  map.insert_or_assign(std::string(\"key1\"), std::string(\"value1\"));\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, true));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  // FindBuildMapThreshold == 10, will not build map, all linear find, perfect\n  // path\n  EXPECT_EQ(map.find(\"key1\")->second, \"value1\");\n  EXPECT_EQ(map.find(\"key2\")->second, \"value2\");\n  EXPECT_EQ(map.find(\"key5\"), map.end());\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, true));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  // Insert to build map, linear find\n  map.insert_or_assign(std::string(\"key5\"), std::string(\"value5\"));\n  map.insert_or_assign(std::string(\"key6\"), std::string(\"value6\"));\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(\n      map, false, false));  // Not not on pool, not perfect\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n  map.insert_or_assign(std::string(\"key3\"), std::string(\"value33333\"));\n  map.insert_or_assign(std::string(\"key7\"), std::string(\"value7\"));\n  EXPECT_TRUE(map.size() == 7);\n  EXPECT_EQ(map.find(\"key5\")->second, \"value5\");\n  EXPECT_EQ(map.find(\"key7\")->second, \"value7\");\n  EXPECT_EQ(map.find(\"key0\"), map.end());\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, false));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  map.insert_or_assign(std::string(\"key8\"), std::string(\"value8\"));\n  EXPECT_TRUE(map.size() == 8);\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, true, false));\n  EXPECT_EQ(map.find(\"key1\")->second, \"value1\");  // hash find\n  EXPECT_EQ(map.find(\"key2\")->second, \"value2\");  // hash find\n  EXPECT_EQ(map.find(\"key0\"), map.end());         // hash find\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\nTEST(LinkedHashMap, find_inner_map_created) {\n  LinkedHashMap<std::string, std::string> map;\n  for (int i = 0; i < 100; i++) {\n    map[std::string(\"key\") + std::to_string(i)] =\n        std::string(\"value\") + std::to_string(i);\n  }\n  for (int i = 0; i < 100; i++) {\n    EXPECT_EQ(map.find(std::string(\"key\") + std::to_string(i))->second,\n              std::string(\"value\") + std::to_string(i));\n  }\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\nTEST(LinkedHashMap, contains) {\n  LinkedHashMap<std::string, std::string> map;\n  map.insert_or_assign(std::string(\"key4\"), std::string(\"value4\"));\n  map.insert_or_assign(std::string(\"key3\"), std::string(\"value3\"));\n  map.insert_or_assign(std::string(\"key2\"), std::string(\"value2\"));\n  map.insert_or_assign(std::string(\"key1\"), std::string(\"value1\"));\n  EXPECT_TRUE(map.contains(\"key2\"));\n  EXPECT_FALSE(map.contains(\"key5\"));\n}\n\nTEST(LinkedHashMap, erase) {\n  LinkedHashMap<std::string, std::string, 20, 2> map;\n  map.reserve(3);\n  map.insert_or_assign(std::string(\"key3\"), std::string(\"value3\"));\n  map.insert_or_assign(std::string(\"key2\"), std::string(\"value2\"));\n  map.insert_or_assign(std::string(\"key1\"), std::string(\"value1\"));\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, true));\n  EXPECT_EQ(map.erase(map.begin())->second, \"value2\");\n  EXPECT_EQ(map.erase(map.begin())->second, \"value1\");\n  EXPECT_TRUE(map.size() == 1);\n  for (auto it = map.begin(); it != map.end(); it++) {\n    EXPECT_EQ(it->first, \"key1\");\n    EXPECT_EQ(it->second, \"value1\");\n  }\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n  EXPECT_TRUE(map.erase(\"key2\") == 0);\n  EXPECT_TRUE(map.erase(\"key1\") == 1);\n  EXPECT_TRUE(map.empty());\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, true));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  map[\"key5\"] = \"value5\";\n  EXPECT_EQ(map.erase(map.begin()), map.end());\n  EXPECT_TRUE(map.empty());\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, true));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  // erase to empty, pool of map could be reused.\n  for (size_t i = 0; i < 15; i++) {\n    map[std::string(\"key\") + std::to_string(i)] =\n        std::string(\"value\") + std::to_string(i);\n  }\n  EXPECT_TRUE(decltype(map)::Testing::count_of_nodes_on_pool(map) == 3);\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, false, false));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  // find trigger build map\n  EXPECT_EQ(map.erase(map.find(\"key0\"))->first, \"key1\");\n  EXPECT_FALSE(map.contains(\"key0\"));\n  EXPECT_EQ(map.find(\"key0\"), map.end());\n  EXPECT_TRUE(decltype(map)::Testing::assume_status(map, true, false));\n  EXPECT_EQ(map.erase(map.find(\"key10\"))->first, \"key11\");\n  EXPECT_TRUE(map.erase(\"key11\") == 1);\n  EXPECT_TRUE(map.size() == 12);\n}\n\nTEST(LinkedHashMap, merge) {\n  LinkedHashMap<int, int> map0;\n  LinkedHashMap<int, int> map1;\n\n  for (size_t i = 0; i < 5; i++) {\n    map1[i] = i * 10;\n  }\n  map0.merge(map1);\n  EXPECT_TRUE(map0.size() == 5);\n\n  LinkedHashMap<int, int> map2;\n  map2[2] = 20;\n  map2[10] = 100;\n  map2.merge(map1);\n  EXPECT_TRUE(map2.size() == 6);\n}\n\nTEST(LinkedHashMap, clear) {\n  LinkedHashMap<std::string, std::string> map;\n  map.reserve(decltype(map)::kInitialAllocationSize + 1);\n  map.insert_or_assign(std::string(\"key3\"), std::string(\"value3\"));\n  map.insert_or_assign(std::string(\"key2\"), std::string(\"value2\"));\n  map.insert_or_assign(std::string(\"key1\"), std::string(\"value1\"));\n  map.clear();\n  EXPECT_EQ(map.size(), static_cast<size_t>(0));\n  EXPECT_TRUE(map.empty());\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  // after map.clear(), its pool is cleared and could be reused.\n  for (size_t i = 0; i < decltype(map)::kInitialAllocationSize + 10; i++) {\n    map[std::string(\"key\") + std::to_string(i)] =\n        std::string(\"value\") + std::to_string(i);\n  }\n  EXPECT_EQ(decltype(map)::Testing::count_of_nodes_on_pool(map),\n            static_cast<size_t>(decltype(map)::kInitialAllocationSize + 1));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\nTEST(LinkedHashMap, clear_pool) {\n  LinkedHashMap<std::string, std::string> map;\n  map.reserve(decltype(map)::kInitialAllocationSize + 1);\n  map.insert_or_assign(std::string(\"key3\"), std::string(\"value3\"));\n  map.insert_or_assign(std::string(\"key2\"), std::string(\"value2\"));\n  map.insert_or_assign(std::string(\"key1\"), std::string(\"value1\"));\n  map.clear(true);\n  EXPECT_EQ(map.size(), static_cast<size_t>(0));\n  EXPECT_TRUE(map.empty());\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n\n  // after map.clear(true), its pool size is reset to kInitialAllocationSize\n  for (size_t i = 0; i < decltype(map)::kInitialAllocationSize + 10; i++) {\n    map[std::string(\"key\") + std::to_string(i)] =\n        std::string(\"value\") + std::to_string(i);\n  }\n  EXPECT_EQ(decltype(map)::Testing::count_of_nodes_on_pool(map),\n            static_cast<size_t>(decltype(map)::kInitialAllocationSize));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\nTEST(LinkedHashMap, reserve) {\n  LinkedHashMap<std::string, std::string> map;\n  map.reserve(2);\n  map.reserve(decltype(map)::kInitialAllocationSize + 4);\n  map.reserve(1);\n  for (size_t i = 0; i < decltype(map)::kInitialAllocationSize + 10; i++) {\n    map[std::string(\"key\") + std::to_string(i)] =\n        std::string(\"value\") + std::to_string(i);\n  }\n  EXPECT_EQ(decltype(map)::Testing::count_of_nodes_on_pool(map),\n            static_cast<size_t>(decltype(map)::kInitialAllocationSize + 4));\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\nTEST(LinkedHashMap, set_pool_capacity) {\n  LinkedHashMap<std::string, std::string> map;\n  map.reserve(2);\n  map.set_pool_capacity(decltype(map)::kInitialAllocationSize + 4);\n  map.set_pool_capacity(1);\n  for (size_t i = 0; i < decltype(map)::kInitialAllocationSize + 10; i++) {\n    map[std::string(\"key\") + std::to_string(i)] =\n        std::string(\"value\") + std::to_string(i);\n  }\n  EXPECT_EQ(decltype(map)::Testing::count_of_nodes_on_pool(map), 1u);\n  EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));\n}\n\n#define TEST_COPY_MOVE_POOL_SIZE(n)                                          \\\n  TEST(LinkedHashMap, copy_move_pool_size_##n) {                             \\\n    LinkedHashMap<std::string, std::string> map(n);                          \\\n    map.insert_or_assign(std::string(\"key5\"), std::string(\"value5\"));        \\\n    map.insert_or_assign(std::string(\"key4\"), std::string(\"value4\"));        \\\n    map.insert_or_assign(std::string(\"key3\"), std::string(\"value3\"));        \\\n    map.insert_or_assign(std::string(\"key2\"), std::string(\"value2\"));        \\\n    map.insert_or_assign(std::string(\"key1\"), std::string(\"value1\"));        \\\n    EXPECT_EQ(decltype(map)::Testing::count_of_nodes_on_pool(map),           \\\n              std::min<size_t>(map.size(), n));                              \\\n    EXPECT_TRUE(decltype(map)::Testing::check_consistency(map));             \\\n                                                                             \\\n    auto Check = [](const LinkedHashMap<std::string, std::string>& target) { \\\n      int idx = 5;                                                           \\\n      for (auto it = target.begin(); it != target.end(); it++) {             \\\n        EXPECT_EQ(it->first, std::string(\"key\") + std::to_string(idx));      \\\n        EXPECT_EQ(it->second, std::string(\"value\") + std::to_string(idx));   \\\n        idx--;                                                               \\\n      }                                                                      \\\n    };                                                                       \\\n                                                                             \\\n    /* copy, all nodes should be on pool */                                  \\\n    LinkedHashMap<std::string, std::string> map2;                            \\\n    map2 = map;                                                              \\\n    Check(map2);                                                             \\\n    EXPECT_EQ(decltype(map2)::Testing::count_of_nodes_on_pool(map2),         \\\n              map2.size());                                                  \\\n    EXPECT_TRUE(decltype(map2)::Testing::check_consistency(map2));           \\\n                                                                             \\\n    /* move, pool is also moved */                                           \\\n    LinkedHashMap<std::string, std::string> map3;                            \\\n    map3 = std::move(map2);                                                  \\\n    Check(map3);                                                             \\\n    EXPECT_EQ(decltype(map3)::Testing::count_of_nodes_on_pool(map3),         \\\n              map3.size());                                                  \\\n    EXPECT_TRUE(decltype(map3)::Testing::check_consistency(map3));           \\\n    EXPECT_TRUE(map2.empty());                                               \\\n                                                                             \\\n    LinkedHashMap<std::string, std::string> map4(map);                       \\\n    Check(map4);                                                             \\\n    EXPECT_EQ(decltype(map4)::Testing::count_of_nodes_on_pool(map4),         \\\n              map4.size());                                                  \\\n    EXPECT_TRUE(decltype(map4)::Testing::check_consistency(map4));           \\\n                                                                             \\\n    LinkedHashMap<std::string, std::string> map5(std::move(map4));           \\\n    Check(map5);                                                             \\\n    EXPECT_EQ(decltype(map5)::Testing::count_of_nodes_on_pool(map5),         \\\n              map5.size());                                                  \\\n    EXPECT_TRUE(decltype(map5)::Testing::check_consistency(map5));           \\\n    EXPECT_TRUE(map4.empty());                                               \\\n                                                                             \\\n    /* after moved, map4's pool_size is reset to kInitialAllocationSize. */  \\\n    map4.insert_or_assign(std::string(\"key5\"), std::string(\"value5\"));       \\\n    map4.insert_or_assign(std::string(\"key4\"), std::string(\"value4\"));       \\\n    map4.insert_or_assign(std::string(\"key3\"), std::string(\"value3\"));       \\\n    map4.insert_or_assign(std::string(\"key2\"), std::string(\"value2\"));       \\\n    map4.insert_or_assign(std::string(\"key1\"), std::string(\"value1\"));       \\\n    Check(map4);                                                             \\\n    EXPECT_EQ(decltype(map4)::Testing::count_of_nodes_on_pool(map4),         \\\n              std::min<size_t>(decltype(map4)::kInitialAllocationSize,       \\\n                               map4.size()));                                \\\n    EXPECT_TRUE(decltype(map4)::Testing::check_consistency(map4));           \\\n  }  // namespace test\n\nTEST_COPY_MOVE_POOL_SIZE(0u)\nTEST_COPY_MOVE_POOL_SIZE(2u)\nTEST_COPY_MOVE_POOL_SIZE(4u)\nTEST_COPY_MOVE_POOL_SIZE(6u)\n\n}  // namespace test\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/log/alog_wrapper.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/log/alog_wrapper.h\"\n\nnamespace lynx {\nnamespace base {\n\nnamespace {\n\nstatic alog_write_func_ptr s_alog_write_func_ptr = nullptr;\n\n}  // namespace\n\nbool InitAlog(alog_write_func_ptr addr) {\n  if (s_alog_write_func_ptr != nullptr) {\n    return true;\n  }\n  if (addr == nullptr) {\n    return false;\n  }\n  s_alog_write_func_ptr = addr;\n  return true;\n}\n\nvoid ALogWrite(unsigned int level, const char* tag, const char* msg) {\n  if (s_alog_write_func_ptr != nullptr) {\n    s_alog_write_func_ptr(level, tag, msg);\n  }\n}\n\nvoid ALogWriteV(const char* tag, const char* msg) {\n  ALogWrite(ALOG_LEVEL_VERBOSE, tag, msg);\n}\n\nvoid ALogWriteD(const char* tag, const char* msg) {\n  ALogWrite(ALOG_LEVEL_DEBUG, tag, msg);\n}\n\nvoid ALogWriteI(const char* tag, const char* msg) {\n  ALogWrite(ALOG_LEVEL_INFO, tag, msg);\n}\n\nvoid ALogWriteW(const char* tag, const char* msg) {\n  ALogWrite(ALOG_LEVEL_WARN, tag, msg);\n}\n\nvoid ALogWriteE(const char* tag, const char* msg) {\n  ALogWrite(ALOG_LEVEL_ERROR, tag, msg);\n}\n\nvoid ALogWriteF(const char* tag, const char* msg) {\n  ALogWrite(ALOG_LEVEL_FATAL, tag, msg);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/log/log_stream.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/log/log_stream.h\"\n\n#include <algorithm>\n#include <cstdio>\n#include <limits>\n\n#include \"third_party/rapidjson/internal/dtoa.h\"\n#include \"third_party/rapidjson/internal/itoa.h\"\n\n#if defined(OS_WIN)\n#include \"base/include/string/string_conversion_win.h\"\n#endif\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\n\nnamespace detail {\n\nconstexpr char kDigitHex[] = \"0123456789ABCDEF\";\nconstexpr int32_t kMaxNumericSize = 48;\n#if defined(__LP64__) || defined(_WIN64)\nconstexpr int32_t kAddressSignificantBit = 16;\n#else\nconstexpr int32_t kAddressSignificantBit = 8;\n#endif\n\n#define TRANSFER_FUNCTION(type, value, buffer)      \\\n  char* transfer_start = buffer;                    \\\n  char* transfer_end = type(value, transfer_start); \\\n  *transfer_end = '\\0';                             \\\n  return static_cast<uint32_t>(transfer_end - transfer_start);\n\ninline int32_t Integer32ToString(int32_t value, char* buffer) {\n  TRANSFER_FUNCTION(rapidjson::internal::i32toa, value, buffer);\n}\n\ninline int32_t UnsignedInteger32ToString(uint32_t value, char* buffer) {\n  TRANSFER_FUNCTION(rapidjson::internal::u32toa, value, buffer);\n}\n\ninline int32_t Integer64ToString(int64_t value, char* buffer) {\n  TRANSFER_FUNCTION(rapidjson::internal::i64toa, value, buffer);\n}\n\ninline int32_t UnsignedInteger64ToString(uint64_t value, char* buffer) {\n  TRANSFER_FUNCTION(rapidjson::internal::u64toa, value, buffer);\n}\n\ninline int32_t DoubleToString(double value, char* buffer) {\n  TRANSFER_FUNCTION(rapidjson::internal::dtoa, value, buffer);\n}\n\nvoid StaticCheck() {\n  static_assert(kMaxNumericSize - 10 > std::numeric_limits<double>::digits10,\n                \"kMaxNumericSize is large enough\");\n  static_assert(\n      kMaxNumericSize - 10 > std::numeric_limits<long double>::digits10,\n      \"kMaxNumericSize is large enough\");\n  static_assert(kMaxNumericSize - 10 > std::numeric_limits<long>::digits10,\n                \"kMaxNumericSize is large enough\");\n  static_assert(kMaxNumericSize - 10 > std::numeric_limits<int64_t>::digits10,\n                \"kMaxNumericSize is large enough\");\n}\n}  // namespace detail\n\n// looping convert address type to string as hex\n// if null, the output is 0x00000000 in 32bits OS\n// and 0x0000000000000000 in 64bits OS\n// have a out of bundary check at function operator<<(const void* address)\n// have a fixed length output\nvoid ConvertAddressToHexString(char buffer[], uintptr_t value) {\n  uintptr_t base = value;\n  char* target = buffer;\n  int32_t index = detail::kAddressSignificantBit - 1;\n\n  do {\n    int32_t least_significant_bit = static_cast<int32_t>(base % 16);\n    base /= 16;\n    target[index] = detail::kDigitHex[least_significant_bit];\n    --index;\n  } while (base != 0 && index >= 0);\n\n  // fill zero at higher bits\n  while (index >= 0) {\n    target[index--] = '0';\n  }\n}\n\nLogStream& LogStream::operator<<(bool value) {\n  return value ? operator<<(\"true\") : operator<<(\"false\");\n}\n\n// integer\nLogStream& LogStream::operator<<(int8_t value) {\n  return operator<<(static_cast<int32_t>(value));\n}\n\nLogStream& LogStream::operator<<(uint8_t value) {\n  return operator<<(static_cast<uint32_t>(value));\n}\n\nLogStream& LogStream::operator<<(int16_t value) {\n  return operator<<(static_cast<int32_t>(value));\n}\n\nLogStream& LogStream::operator<<(uint16_t value) {\n  return operator<<(static_cast<uint32_t>(value));\n}\n\nLogStream& LogStream::operator<<(int32_t value) {\n  if (buffer_.Available() > detail::kMaxNumericSize) {\n    char numeric_container[detail::kMaxNumericSize];\n    int32_t length = detail::Integer32ToString(value, numeric_container);\n    Append(numeric_container, length);\n  }\n  return *this;\n}\n\nLogStream& LogStream::operator<<(uint32_t value) {\n  if (buffer_.Available() > detail::kMaxNumericSize) {\n    char numeric_container[detail::kMaxNumericSize];\n    int32_t length =\n        detail::UnsignedInteger32ToString(value, numeric_container);\n    Append(numeric_container, length);\n  }\n  return *this;\n}\n\nLogStream& LogStream::operator<<(int64_t value) {\n  if (buffer_.Available() > detail::kMaxNumericSize) {\n    char numeric_container[detail::kMaxNumericSize];\n    int32_t length = detail::Integer64ToString(value, numeric_container);\n    Append(numeric_container, length);\n  }\n  return *this;\n}\n\nLogStream& LogStream::operator<<(uint64_t value) {\n  if (buffer_.Available() > detail::kMaxNumericSize) {\n    char numeric_container[detail::kMaxNumericSize];\n    int32_t length =\n        detail::UnsignedInteger64ToString(value, numeric_container);\n    Append(numeric_container, length);\n  }\n  return *this;\n}\n\n// convert address to hex string,\n// if null, the output is 0x00000000 in 32bits OS\n// and 0x0000000000000000 in 64bits OS\n// have a fixed length output\nLogStream& LogStream::operator<<(const void* address) {\n  if (buffer_.Available() > detail::kMaxNumericSize) {\n    constexpr int32_t hex_prefix_size = 2;\n    constexpr int32_t hex_address_size =\n        detail::kAddressSignificantBit + hex_prefix_size;\n    char numeric_container[hex_address_size];\n    uintptr_t value = reinterpret_cast<uintptr_t>(address);\n    numeric_container[0] = '0';\n    numeric_container[1] = 'x';\n    ConvertAddressToHexString(numeric_container + hex_prefix_size, value);\n    Append(numeric_container, hex_address_size);\n  }\n  return *this;\n}\n\n// 6 precision default\nLogStream& LogStream::operator<<(float value) {\n  if (buffer_.Available() > detail::kMaxNumericSize) {\n    char numeric_container[detail::kMaxNumericSize];\n    int32_t length =\n        snprintf(numeric_container, detail::kMaxNumericSize, \"%.6g\", value);\n    Append(numeric_container, length);\n  }\n  return *this;\n}\n\nLogStream& LogStream::operator<<(double value) {\n  if (buffer_.Available() > detail::kMaxNumericSize) {\n    char numeric_container[detail::kMaxNumericSize];\n    int32_t length = detail::DoubleToString(value, numeric_container);\n    Append(numeric_container, length);\n  }\n  return *this;\n}\n\nLogStream& LogStream::operator<<(const char& value) {\n  Append(&value, 1);\n  return *this;\n}\n\n// convert const char * to string\n// if null, the output will be truncated\nLogStream& LogStream::operator<<(const char* value) {\n  if (value) {\n    Append(value, strlen(value));\n  } else {\n    constexpr char terminator = '\\0';\n    Append(&terminator, 1);\n  }\n  return *this;\n}\n\nLogStream& LogStream::operator<<(const std::string& value) {\n  Append(value.c_str(), value.length());\n  return *this;\n}\n\nLogStream& LogStream::operator<<(const std::string_view& value) {\n  Append(value.data(), value.length());\n  return *this;\n}\n\n// overload for wchar_t, std::wstring\n#if defined(OS_WIN)\nLogStream& LogStream::operator<<(wchar_t value) {\n  return operator<<(Utf8FromUtf16(&value, 1));\n}\n\nLogStream& LogStream::operator<<(const wchar_t* value) {\n  return operator<<(Utf8FromUtf16(value, wcslen(value)));\n}\n\nLogStream& LogStream::operator<<(const std::wstring& value) {\n  return operator<<(Utf8FromUtf16(value));\n}\n\nLogStream& LogStream::operator<<(const std::wstring_view& value) {\n  return operator<<(Utf8FromUtf16(value));\n}\n#endif\n\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/log/log_stream_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/log/log_stream.h\"\n\n#include <algorithm>\n#include <cstring>\n#include <ctime>\n#include <sstream>\n#include <string>\n#include <vector>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\ntemplate <typename INT>\nstruct NumberContrastString {\n  INT number;\n  std::string expected;\n  std::string unsigned_expected;\n};\n\ntemplate <typename INT>\nstd::string ConvertToString(const INT& number) {\n  LogStream output;\n  output << number;\n  return output.str();\n}\n\ntemplate <typename T>\nvoid SmartPointerToHexString() {\n  srand(time(nullptr));\n  int32_t array_length = rand() % 100 + 10;\n  std::vector<T> tests;\n  for (int32_t i = 0; i < array_length; ++i) {\n    tests.emplace_back(T(new int32_t(i)));\n  }\n\n  for (const auto& test : tests) {\n    std::ostringstream output;\n    output << test;\n    std::string output_str = output.str();\n    std::transform(output_str.begin() + 2, output_str.end(),\n                   output_str.begin() + 2, ::toupper);\n\n    std::string sub_output = output_str.substr(2);\n    std::string sub_logstream_output = ConvertToString(test).substr(2);\n    size_t position1 = sub_output.find_first_not_of(\"0\");\n    size_t position2 = sub_logstream_output.find_first_not_of(\"0\");\n\n    EXPECT_EQ(sub_logstream_output.substr(position2),\n              sub_output.substr(position1));\n  }\n}\n\nclass SelfType {\n  double value_;\n\n public:\n  explicit SelfType(double value) : value_(value) {}\n  inline friend LogStream& operator<<(LogStream& output,\n                                      const SelfType& input) {\n    output << input.value_;\n    return output;\n  }\n};\n\nTEST(LogStreamTest, BoolToString) {\n  static const struct {\n    bool input;\n    std::string output;\n  } cases[] = {{true, \"true\"}, {false, \"false\"}};\n\n  for (const auto& test : cases) {\n    EXPECT_EQ(ConvertToString<bool>(test.input), test.output);\n  }\n}\n\nTEST(LogStreamTest, NumberToString) {\n  static const NumberContrastString<int8_t> int8_tests[] = {\n      {0, \"0\", \"0\"},\n      {-1, \"-1\", \"255\"},\n      {std::numeric_limits<int8_t>::max(), \"127\", \"127\"},\n      {std::numeric_limits<int8_t>::min(), \"-128\", \"128\"},\n  };\n\n  static const NumberContrastString<int16_t> int16_tests[] = {\n      {0, \"0\", \"0\"},\n      {-1, \"-1\", \"65535\"},\n      {std::numeric_limits<int16_t>::max(), \"32767\", \"32767\"},\n      {std::numeric_limits<int16_t>::min(), \"-32768\", \"32768\"},\n  };\n\n  static const NumberContrastString<int> int_tests[] = {\n      {0, \"0\", \"0\"},\n      {-1, \"-1\", \"4294967295\"},\n      {std::numeric_limits<int>::max(), \"2147483647\", \"2147483647\"},\n      {std::numeric_limits<int>::min(), \"-2147483648\", \"2147483648\"},\n  };\n  static const NumberContrastString<int64_t> int64_tests[] = {\n      {0, \"0\", \"0\"},\n      {-1, \"-1\", \"18446744073709551615\"},\n      {\n          std::numeric_limits<int64_t>::max(),\n          \"9223372036854775807\",\n          \"9223372036854775807\",\n      },\n      {std::numeric_limits<int64_t>::min(), \"-9223372036854775808\",\n       \"9223372036854775808\"},\n  };\n\n  for (const auto& test : int8_tests) {\n    EXPECT_EQ(ConvertToString(test.number), test.expected);\n    EXPECT_EQ(ConvertToString(static_cast<uint8_t>(test.number)),\n              test.unsigned_expected);\n  }\n\n  for (const auto& test : int16_tests) {\n    EXPECT_EQ(ConvertToString(test.number), test.expected);\n    EXPECT_EQ(ConvertToString(static_cast<uint16_t>(test.number)),\n              test.unsigned_expected);\n  }\n\n  for (const auto& test : int_tests) {\n    EXPECT_EQ(ConvertToString(test.number), test.expected);\n    EXPECT_EQ(ConvertToString(static_cast<unsigned>(test.number)),\n              test.unsigned_expected);\n  }\n\n  for (const auto& test : int64_tests) {\n    EXPECT_EQ(ConvertToString(test.number), test.expected);\n    EXPECT_EQ(ConvertToString(static_cast<uint64_t>(test.number)),\n              test.unsigned_expected);\n  }\n}\n\nTEST(LogStreamTest, Uint64ToString) {\n  static const struct {\n    uint64_t input;\n    std::string output;\n  } cases[] = {\n      {0, \"0\"},\n      {42, \"42\"},\n      {INT_MAX, \"2147483647\"},\n      {std::numeric_limits<uint64_t>::max(), \"18446744073709551615\"},\n  };\n\n  for (const auto& test : cases) {\n    EXPECT_EQ(ConvertToString(test.input), test.output);\n  }\n}\n\nTEST(LogStreamTest, Size_TToString) {\n  static const struct {\n    size_t input;\n    std::string output;\n  } cases[] = {\n      {0, \"0\"},\n      {9, \"9\"},\n      {42, \"42\"},\n      {INT_MAX, \"2147483647\"},\n      {2147483648U, \"2147483648\"},\n  };\n\n  for (const auto& test : cases) {\n    EXPECT_EQ(ConvertToString(test.input), test.output);\n  }\n}\n\nTEST(LogStreamTest, floatToString) {\n  static const struct {\n    float input;\n    std::string expected;\n  } cases[] = {\n      {0.0, \"0\"},\n      {0.5, \"0.5\"},\n      {1.25, \"1.25\"},\n      {3.1415926, \"3.14159\"},\n      {2.123456789, \"2.12346\"},\n      {2.12345678912345, \"2.12346\"},\n      {1.123e-14, \"1.123e-14\"},\n      {1e-17, \"1e-17\"},\n      {1.33545e+09, \"1.33545e+09\"},\n  };\n\n  for (const auto& test : cases) {\n    EXPECT_EQ(ConvertToString(test.input), test.expected);\n  }\n}\n\nTEST(LogStreamTest, DoubleToString) {\n  static const struct {\n    double input;\n    std::string expected;\n  } cases[] = {\n      {0.0, \"0.0\"},     {0.5, \"0.5\"},\n      {1.25, \"1.25\"},   {1.123e-14, \"1.123e-14\"},\n      {1e-17, \"1e-17\"}, {1.33545e+009, \"1335450000.0\"},\n  };\n\n  for (const auto& test : cases) {\n    EXPECT_EQ(ConvertToString(test.input), test.expected);\n  }\n\n  // The following two values were seen in crashes in the wild.\n  const char input_bytes[8] = {0, 0, 0, 0, '\\xee', '\\x6d', '\\x73', '\\x42'};\n  double input = 0;\n  memcpy(&input, input_bytes, sizeof(input_bytes) / sizeof(input_bytes[0]));\n  EXPECT_EQ(\"1335179083776.0\", ConvertToString(input));\n  const char input_bytes2[8] = {0,      0,      0,      '\\xa0',\n                                '\\xda', '\\x6c', '\\x73', '\\x42'};\n  input = 0;\n  memcpy(&input, input_bytes2, sizeof(input_bytes2) / sizeof(input_bytes2[0]));\n  EXPECT_EQ(\"1334890332160.0\", ConvertToString(input));\n}\n\nTEST(LogStreamTest, AddressToHexString) {\n  static const char* cases[] = {\"0\", \"42\", \"-42\", \"7fffffff\", \"0XDeadBeef\"};\n\n  for (const auto& test : cases) {\n    std::ostringstream output;\n    output << &test;\n    std::string output_str = output.str();\n    std::transform(output_str.begin() + 2, output_str.end(),\n                   output_str.begin() + 2, ::toupper);\n\n    std::string sub_output = output_str.substr(2);\n    std::string sub_logstream_output = ConvertToString(&test).substr(2);\n    size_t position1 = sub_output.find_first_not_of(\"0\");\n    size_t position2 = sub_logstream_output.find_first_not_of(\"0\");\n\n    EXPECT_EQ(sub_logstream_output.substr(position2),\n              sub_output.substr(position1));\n  }\n}\n\nTEST(LogStreamTest, SharedPtrToHexString) {\n  SmartPointerToHexString<std::shared_ptr<int32_t>>();\n}\n\nTEST(LogStreamTest, UniquePtrToHexString) {\n  SmartPointerToHexString<std::unique_ptr<int32_t>>();\n}\n\nTEST(LogStreamTest, AtomicToHexString) {\n  {\n    // int\n    int32_t value = 1024;\n    std::atomic<int32_t> input(value);\n    LogStream output;\n    output << input;\n    EXPECT_EQ(output.str(), \"1024\");\n  }\n\n  {\n    // double\n    double value = 3.124;\n    std::atomic<double> input(value);\n    LogStream output;\n    output << input;\n    EXPECT_EQ(output.str(), \"3.124\");\n  }\n\n  {\n    // char\n    char value = 'a';\n    std::atomic<char> input(value);\n    LogStream output;\n    output << input;\n    EXPECT_EQ(output.str(), \"a\");\n  }\n\n  // UDT which is trivially copyable\n  {\n    double value = 3.1415926;\n    SelfType temp(value);\n    std::atomic<SelfType> input(temp);\n    LogStream output;\n    output << input;\n    EXPECT_EQ(output.str(), \"3.1415926\");\n  }\n}\n\n#if defined(OS_WIN)\nTEST(LogStreamTest, WstringToString) {\n  static const struct {\n    std::wstring input;\n    std::string output;\n  } cases[] = {\n      {L\"0\", \"0\"},\n      {L\"42\", \"42\"},\n      {L\"2147483647\", \"2147483647\"},\n      {L\"Hello World\", \"Hello World\"},\n  };\n\n  for (const auto& test : cases) {\n    EXPECT_EQ(ConvertToString(test.input), test.output);\n  }\n}\n\nTEST(LogStreamTest, WCharToString) {\n  static const struct {\n    wchar_t input;\n    std::string output;\n  } cases[] = {{L'1', \"1\"},   {L'a', \"a\"},   {L'b', \"b\"},\n               {L'c', \"c\"},   {L'\\r', \"\\r\"}, {L'\\t', \"\\t\"},\n               {L'\\n', \"\\n\"}, {L'汉', \"汉\"}, {0x6C49, \"汉\"}};\n\n  for (const auto& test : cases) {\n    EXPECT_EQ(ConvertToString(test.input), test.output);\n  }\n}\n#endif\n\nTEST(LogStreamTest, NullCharToString) {\n  const std::string target_string =\n      \"When input is nullptr, truncate output stream\";\n  const std::string need_truncated = \"need to be truncated\";\n  char* null_char_ptr = nullptr;\n  LogStream output;\n  output << target_string << null_char_ptr << need_truncated;\n  EXPECT_EQ(output.str(), target_string);\n}\n\nTEST(LogStreamTest, StringViewToString) {\n  const std::string target_string = \"convert string_view to string\";\n  const std::string_view target_view(target_string);\n  LogStream output;\n  output << target_view;\n  EXPECT_EQ(output.str(), target_string);\n}\n\nTEST(LogStreamTest, LogStreamBase) {\n  const char* target_string = \"Welcome to the world of lynx\";\n  LogStream output;\n  output << target_string;\n  EXPECT_TRUE(output.Buffer().Length() != 0);\n\n  output.Reset();\n  EXPECT_TRUE(output.Buffer().Length() == 0);\n\n  output << \"Today is \" << 2022 << \"-\" << 11 << \"-\" << 2;\n  EXPECT_TRUE(output.Buffer().Length() != 0);\n  output.Clear();\n  EXPECT_TRUE(output.Buffer().Length() == 0);\n\n  // test overload std::ostingstream\n  std::ostringstream std_os;\n  std_os << target_string;\n  output << std_os;\n  EXPECT_EQ(output.str(), std_os.str());\n  output.Clear();\n}\n\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/log/logging.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/log/logging.h\"\n\n#include <algorithm>\n#include <ctime>\n#include <iomanip>\n#include <ostream>\n#include <string>\n#include <utility>\n\n#if !defined(_WIN32)\n#include <unistd.h>\n#endif\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\nnamespace {\n\nconst char* const kLogSeverityNames[LOG_NUM_SEVERITIES] = {\n    \"VERBOSE\", \"DEBUG\", \"INFO\", \"WARNING\", \"ERROR\", \"FATAL\"};\n\n#ifdef NDEBUG\nint32_t g_min_log_level = LOG_INFO;\n#else\nint32_t g_min_log_level = LOG_DEBUG;\n#endif\n\nstatic bool isLogOutputByPlatform = false;\nstatic bool kHasInitedLynxLog = false;\nstatic bool kHasInitedLynxLogWriteFunction = false;\n// TODO(tangyongjie): Use NDEBUG macro determine kIsPrintAllLogToAllChannels\n// value instead of external input.\nstatic bool kIsPrintAllLogToAllChannels = false;\n\nstatic PlatformLogCallBack kPlatformLogFunc = nullptr;\nstatic InitAlogCallBack kInitAlogCallBack = nullptr;\n\n// init alog write function\nbool InitAlogNative() {\n  if (!kHasInitedLynxLogWriteFunction) {\n    alog_write_func_ptr alog_write_func =\n        kInitAlogCallBack ? kInitAlogCallBack() : nullptr;\n    if (lynx::base::InitAlog(alog_write_func)) {\n      kHasInitedLynxLogWriteFunction = true;\n    }\n  }\n  return kHasInitedLynxLogWriteFunction;\n}\n\nvoid PrintLogMessageByAlog(int level, const char* tag, const char* message) {\n  if (InitAlogNative()) {\n    if (level < ALOG_LEVEL_VERBOSE || level > ALOG_LEVEL_FATAL) {\n      return;\n    }\n#if defined(OS_ANDROID)\n    // for logging FATAL level on android platform\n    if (level == ALOG_LEVEL_FATAL) {\n      ALogWrite(ALOG_LEVEL_ERROR, tag, message);\n      return;\n    }\n#endif\n    // use ALog to print native logMessage\n    ALogWrite(level, tag, message);\n  }\n}\n\nvoid PrintLogMessageByPlatformLog(LogMessage* msg, const char* tag) {\n  if (kPlatformLogFunc) {\n    kPlatformLogFunc(msg, tag);\n  }\n}\n\nbool IsLogOutputByPlatform() { return isLogOutputByPlatform; }\n\nvoid Log(LogMessage* msg) {\n  constexpr const char* kTag = \"lynx\";\n  constexpr const char* kConsoleTag = \"lynx-console\";\n  const char* tag = (msg->source() == logging::LOG_SOURCE_JS_EXT ||\n                     msg->source() == logging::LOG_SOURCE_JS)\n                        ? kConsoleTag\n                        : kTag;\n  // 0. all logs are logged to the delegate and ALog for debug.\n  if (kIsPrintAllLogToAllChannels) {\n    PrintLogMessageByAlog(msg->severity(), tag, msg->stream().str().c_str());\n    PrintLogMessageByPlatformLog(msg, tag);\n    return;\n  }\n  // 1. all logs are consumed at the platform layer.\n  if (IsLogOutputByPlatform()) {\n    PrintLogMessageByPlatformLog(msg, tag);\n    return;\n  }\n  // 2. only native logs output to ALog for release.\n  if (msg->source() == logging::LOG_SOURCE_NATIVE) {\n    PrintLogMessageByAlog(msg->severity(), kTag, msg->stream().str().c_str());\n    return;\n  }\n  // 3. console.alog output to Alog and console.report output to delegate for\n  // release.\n  if (msg->source() == logging::LOG_SOURCE_JS_EXT) {\n    if (msg->severity() == logging::LOG_INFO) {\n      // console.alog output to Alog\n      PrintLogMessageByAlog(logging::LOG_ERROR, kConsoleTag,\n                            msg->stream().str().c_str());\n    } else {\n      // console.report output to delegate\n      PrintLogMessageByPlatformLog(msg, kConsoleTag);\n    }\n  }\n}\n\nconst char* LogSeverityName(int32_t severity) {\n  if (severity >= 0 && severity < LOG_NUM_SEVERITIES)\n    return kLogSeverityNames[severity];\n  return \"UNKNOWN\";\n}\n\n}  // namespace\n\n[[maybe_unused]] bool HasInitedLynxLogWriteFunction() {\n  return kHasInitedLynxLogWriteFunction;\n}\n\nvoid InitLynxLogging(InitAlogCallBack initAlogCallback,\n                     PlatformLogCallBack PlatformLogCallBack,\n                     bool isPrintAllLogToAllChannels) {\n  if (initAlogCallback || PlatformLogCallBack) {\n    kInitAlogCallBack = initAlogCallback;\n    kPlatformLogFunc = PlatformLogCallBack;\n    kIsPrintAllLogToAllChannels = isPrintAllLogToAllChannels;\n    kHasInitedLynxLog = true;\n  }\n}\n\nvoid SetMinLogLevel(int level) {\n  if (g_min_log_level >= level) {\n    return;\n  }\n  g_min_log_level = std::min(LOG_FATAL, level);\n}\n\nint GetMinLogLevel() { return g_min_log_level; }\n\nvoid PrintLogToLynxLogging(int level, const char* tag, const char* message) {\n  PrintLogMessageByAlog(level, tag, message);\n}\n\n[[maybe_unused]] void EnableLogOutputByPlatform() {\n  isLogOutputByPlatform = true;\n}\n\n[[maybe_unused]] void DisableLogOutputByPlatform() {\n  isLogOutputByPlatform = false;\n}\n\nLogMessage::LogMessage(const char* file, int line, LogSeverity severity,\n                       LogSource source, int64_t rt_id, LogChannel channel_type)\n    : severity_(severity),\n      file_(file),\n      line_(line),\n      source_(source),\n      runtime_id_(rt_id),\n      channel_type_(channel_type) {\n  // FIXME(shouqun): Suppress unused warning.\n  (void)line_;\n  (void)file_;\n  Init(file, line);\n}\n\nLogMessage::LogMessage(const char* file, int line, LogSeverity severity)\n    : severity_(severity),\n      file_(file),\n      line_(line),\n      source_(LOG_SOURCE_NATIVE),\n      runtime_id_(-1),\n      channel_type_(LOG_CHANNEL_LYNX_INTERNAL) {\n  Init(file, line);\n}\n\nLogMessage::~LogMessage() {\n  stream_ << std::endl;\n\n  if (kHasInitedLynxLog) {\n    Log(this);\n  } else {\n    std::string str_newline(stream_.str());\n    printf(\"lynx/%s [%s:%d]: %s\\n\", LogSeverityName(severity_), file_, line_,\n           str_newline.c_str());\n  }\n\n  if (severity_ == LOG_FATAL) {\n    abort();\n  }\n}\n\n// writes the common header info to the stream\nvoid LogMessage::Init(const char* file, int line) {\n  std::string filename(file);\n\n  stream_ << '[';\n  thread_local std::thread::id tid = std::this_thread::get_id();\n  stream_ << tid << ':';\n\n  // function localtime_r will call getenv which system function, but getenv is\n  // not thread-safe although, it does not make crash directly, but when setenv\n  // will be called in other thread it possibly will crash in lynx log. Then,\n  // the time tag is useless, and other user, like ALog, has owner time tag so\n  // disable it here.\n  //  time_t t = time(NULL);\n  // #if defined(OS_WIN)\n  //  struct tm local_time = {0};\n  //\n  //  localtime_s(&local_time, &t);\n  // #else\n  //  struct tm local_time = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr};\n  //  localtime_r(&t, &local_time);\n  // #endif\n  //\n  //  struct tm* tm_time = &local_time;\n  //  stream_ << std::setfill('0') << std::setw(2) << 1 + tm_time->tm_mon\n  //          << std::setw(2) << tm_time->tm_mday << '/' << std::setw(2)\n  //          << tm_time->tm_hour << std::setw(2) << tm_time->tm_min <<\n  //          std::setw(2)\n  //          << tm_time->tm_sec << ':';\n\n  stream_ << LogSeverityName(severity_);\n  stream_ << \":\" << filename << \"(\" << line << \")] \";\n\n  message_start_ = stream_.str().length();\n}\n\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/log/logging_android.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <android/api-level.h>\n#include <android/log.h>\n#include <jni.h>\n\n#include <memory>\n#include <string>\n\n#include \"base/include/log/alog_wrapper.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/include/platform/android/jni_convert_helper.h\"\n#include \"base/platform/android/src/main/jni/gen/LynxLog_jni.h\"\n#include \"base/platform/android/src/main/jni/gen/LynxLog_register_jni.h\"\n\nnamespace lynxbase {\nnamespace jni {\nbool RegisterJNIForLynxLog(JNIEnv* env) { return RegisterNativesImpl(env); }\n}  // namespace jni\n}  // namespace lynxbase\n\nstatic lynx::base::alog_write_func_ptr s_alog_write = nullptr;\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\nnamespace {\nbase::alog_write_func_ptr InitAlog() { return s_alog_write; }\n\nvoid PrintLogMessageByLogDelegate(LogMessage* msg, const char* tag) {\n  JNIEnv* env = android::AttachCurrentThread();\n  auto c_msg = msg->stream().str();\n  static android::ScopedGlobalJavaRef<jstring> lynx_tag(\n      android::JNIConvertHelper::ConvertToJNIStringUTF(env, tag));\n\n  size_t msg_len = c_msg.size();\n  if (msg_len == 0) {\n    return;\n  }\n\n  static const int api_level = android_get_device_api_level();\n  // Emoji will make App crash when use `NewStringUTF` API in Android 5.x\n  // So we send byte[] to Java and construct a new String in Java.\n  if (api_level < __ANDROID_API_M__) {  // Build.VERSION_CODES.M\n    base::android::ScopedLocalJavaRef<jbyteArray> jni_byte_msg =\n        android::JNIConvertHelper::ConvertToJNIByteArray(env, c_msg);\n    Java_LynxLog_logByte(env, msg->severity(), lynx_tag.Get(),\n                         jni_byte_msg.Get(), msg->source(), msg->runtimeId(),\n                         msg->ChannelType(), msg->messageStart());\n  } else {\n    base::android::ScopedLocalJavaRef<jstring> jni_msg =\n        android::JNIConvertHelper::ConvertToJNIStringUTF(env, c_msg);\n    Java_LynxLog_log(env, msg->severity(), lynx_tag.Get(), jni_msg.Get(),\n                     msg->source(), msg->runtimeId(), msg->ChannelType(),\n                     msg->messageStart());\n  }\n}\n}  // namespace\n\nvoid InitLynxLog(bool is_all_channels) {\n  InitLynxLogging(InitAlog, PrintLogMessageByLogDelegate, is_all_channels);\n}\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n\nvoid InitLynxLoggingNative(JNIEnv* env, jclass jcaller,\n                           jboolean is_all_channels) {\n  lynx::base::logging::InitLynxLog(is_all_channels ? true : false);\n}\n\nvoid SetNativeMinLogLevel(JNIEnv* env, jclass jcaller, jint level) {\n  lynx::base::logging::SetMinLogLevel(level);\n}\n\nvoid InitALogNative(JNIEnv* env, jclass jcaller, jlong addr) {\n  s_alog_write = reinterpret_cast<lynx::base::alog_write_func_ptr>(addr);\n}\n\nvoid InternalLog(JNIEnv* env, jclass jcaller, jint level, jstring tag,\n                 jstring msg) {\n  const char* c_tag = env->GetStringUTFChars(tag, JNI_FALSE);\n  const char* c_msg = env->GetStringUTFChars(msg, JNI_FALSE);\n  lynx::base::logging::PrintLogToLynxLogging(static_cast<int>(level), c_tag,\n                                             c_msg);\n  env->ReleaseStringUTFChars(tag, c_tag);\n  env->ReleaseStringUTFChars(msg, c_msg);\n}\n\nvoid SetLogOutputByPlatform(JNIEnv* env, jclass jcaller) {\n  lynx::base::logging::EnableLogOutputByPlatform();\n}\n"
  },
  {
    "path": "base/src/log/logging_base.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/log/logging_base.h\"\n\n#include <iostream>\n#include <map>\n#include <string>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\n\nvoid PrintLogMessageForDebug(int level, const char* message);\nbool IsExternalChannel(lynx::base::logging::LogChannel channel_type);\n\nnamespace base {\nnamespace logging {\n\nvoid PrintLogMessageByDelegate(LogMessage* msg, const char* tag) {\n  int level = msg->severity();\n  std::string message = msg->stream().str();\n  // print native's log to hybrid devtool for debug\n  PrintLogMessageForDebug(level, message.c_str());\n\n  std::vector<LynxLogDelegate*> delegates = GetLoggingDelegates();\n  for (LynxLogDelegate* delegate : delegates) {\n    LynxLogFunction log_function = delegate->log_function;\n    if (log_function == nullptr || level < delegate->min_log_level ||\n        (delegate->accept_runtime_id >= 0 &&\n         delegate->accept_runtime_id != msg->runtimeId())) {\n      continue;\n    }\n\n    // only upload external JS logs and console.report to logging delegate\n    switch (msg->source()) {\n      case LOG_SOURCE_JS:\n        if (IsExternalChannel(msg->ChannelType()) &&\n            (delegate->accept_source & LynxLogSourceJS)) {\n          log_function(level, message.c_str());\n        }\n        break;\n      case LOG_SOURCE_JS_EXT:\n        log_function(level, message.c_str());\n        break;\n      case LOG_SOURCE_NATIVE:\n        // output the native log of lynx when alog is not supported on the\n        // PC（Windows & Mac)\n        log_function(level, message.c_str());\n        break;\n      default:\n        break;\n    }\n  }\n}\n}  // namespace logging\n}  // namespace base\n\nvoid InitLynxBaseLog(bool print_logs_to_all_channels) {\n  base::logging::InitLynxLogging(nullptr,\n                                 base::logging::PrintLogMessageByDelegate,\n                                 print_logs_to_all_channels);\n}\n\nstatic LynxLogDelegate* lynx_log_debug_delegate_ = nullptr;\nstatic std::map<int, LynxLogDelegate*> lynx_log_delegates_;\n\nstatic int lynx_default_delegate_id_ = -1;\nstatic int lynx_current_id_ = 0;\n#ifdef DEBUG\nstatic int lynx_alog_min_level_ = lynx::base::logging::LOG_DEBUG;\n#else\nstatic int lynx_alog_min_level_ = lynx::base::logging::LOG_INFO;\n#endif\nstatic bool is_jS_logs_from_external_channels_open_ = false;\n\nbool IsExternalChannel(lynx::base::logging::LogChannel channel_type) {\n  return is_jS_logs_from_external_channels_open_ &&\n         channel_type == lynx::base::logging::LOG_CHANNEL_LYNX_EXTERNAL;\n}\n\nvoid PrintLogMessageForDebug(int level, const char* message) {\n  if (lynx_log_debug_delegate_ == nullptr ||\n      level < lynx_log_debug_delegate_->min_log_level) {\n    return;\n  }\n  LynxLogFunction log_function = lynx_log_debug_delegate_->log_function;\n  if (log_function) {\n    log_function(level, message);\n  }\n}\n\nvoid SetDebugLoggingDelegate(LynxLogDelegate* delegate) {\n  lynx_log_debug_delegate_ = delegate;\n}\n\nint AddLoggingDelegate(LynxLogDelegate* delegate) {\n  int delegate_id = ++lynx_current_id_;\n  lynx_log_delegates_[delegate_id] = delegate;\n  return delegate_id;\n}\n\nLynxLogDelegate* GetLoggingDelegate(int delegate_id) {\n  if (lynx_log_delegates_.find(delegate_id) != lynx_log_delegates_.end()) {\n    return lynx_log_delegates_[delegate_id];\n  }\n  return nullptr;\n}\n\nstd::vector<LynxLogDelegate*> GetLoggingDelegates() {\n  std::vector<LynxLogDelegate*> result;\n  for (auto& delegate : lynx_log_delegates_) {\n    result.push_back(delegate.second);\n  }\n  return result;\n}\n\nvoid RemoveLoggingDelegate(int delegate_id) {\n  if (lynx_log_delegates_.find(delegate_id) != lynx_log_delegates_.end()) {\n    delete lynx_log_delegates_[delegate_id];\n    lynx_log_delegates_.erase(delegate_id);\n  }\n}\n\nvoid SetMinimumLoggingLevel(int min_log_level) {\n  if (lynx_alog_min_level_ < min_log_level) {\n    lynx_alog_min_level_ = min_log_level;\n    lynx::base::logging::SetMinLogLevel(min_log_level);\n  }\n}\n\nvoid SetJSLogsFromExternalChannels(bool is_open) {\n  is_jS_logs_from_external_channels_open_ = is_open;\n}\n\nint GetMinimumLoggingLevel() { return lynx_alog_min_level_; }\n\n// TODO: remove this exporting when lynxtron migrated to C API\n#ifdef _WIN32\n__declspec(dllexport)\n#endif\n    int LynxSetLogFunction(LynxLogFunction log_function) {\n  LynxLogDelegate* delegate = new LynxLogDelegate();\n  delegate->log_function = log_function;\n  lynx_default_delegate_id_ = AddLoggingDelegate(delegate);\n  return lynx_default_delegate_id_;\n}\n\nvoid DefaultLogFunction(int level, const char* message) {\n  std::cerr << message;\n}\n\nLynxLogFunction LynxGetLogFunction() {\n  LynxLogDelegate* delegate = GetLoggingDelegate(lynx_default_delegate_id_);\n  if (!delegate) {\n    return DefaultLogFunction;\n  }\n  return delegate->log_function;\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/log/logging_darwin.mm",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/log/logging_darwin.h\"\n#include \"base/include/log/alog_wrapper.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\nnamespace {\n\nstatic alog_write_func_ptr alog_write_func_addr = nullptr;\n\nalog_write_func_ptr GetAlogWriteFuncAddr() { return alog_write_func_addr; }\n\n}  // namespace\nvoid SetLynxLogMinLevel(int min_level) { SetMinLogLevel(min_level); }\n\nvoid InternalLogNative(const char *file, int32_t line, int level, const char *message) {\n  constexpr const char *kTag = \"lynx\";\n  lynx::base::logging::PrintLogToLynxLogging(level, kTag, message);\n}\n\nvoid InitLynxLoggingNative(void *log_address, PlatformLogCallBack platform_log_channel,\n                           bool print_logs_to_all_channels) {\n  alog_write_func_addr = reinterpret_cast<alog_write_func_ptr>(log_address);\n  lynx::base::logging::InitLynxLogging(GetAlogWriteFuncAddr, platform_log_channel,\n                                       print_logs_to_all_channels);\n}\n\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/log/logging_harmony.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/src/log/logging_harmony.h\"\n\n#include <hilog/log.h>\n\n#include <memory>\n#include <string>\n\n#include \"base/include/log/alog_wrapper.h\"\n#include \"base/include/platform/harmony/napi_util.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\nnamespace {\n\nstatic alog_write_func_ptr s_alog_write = nullptr;\n\nalog_write_func_ptr GetLynxLogWriteFunction() { return s_alog_write; }\n\nconst unsigned int LOG_PRINT_DOMAIN = 0xFF00;\nvoid PrintLogMessageByLogDelegate(LogMessage *msg, const char *tag) {\n  LogLevel priority = LogLevel::LOG_DEBUG;\n  switch (msg->severity()) {\n    case logging::LOG_VERBOSE:\n    case logging::LOG_DEBUG:\n      priority = LogLevel::LOG_DEBUG;\n      break;\n    case logging::LOG_INFO:\n      priority = LogLevel::LOG_INFO;\n      break;\n    case logging::LOG_WARNING:\n      priority = LogLevel::LOG_WARN;\n      break;\n    case logging::LOG_ERROR:\n      priority = LogLevel::LOG_ERROR;\n      break;\n    case logging::LOG_FATAL:\n      priority = LogLevel::LOG_FATAL;\n      break;\n  }\n  OH_LOG_Print(LOG_APP, priority, LOG_PRINT_DOMAIN, tag, \"%{public}s\",\n               msg->stream().c_str());\n}\n\n}  // namespace\n\nnapi_value LynxLog::Init(napi_env env, napi_value exports) {\n  NAPI_CREATE_FUNCTION(env, exports, \"nativeInitLynxLogWriteFunction\",\n                       NativeInitLynxLogWriteFunction);\n  NAPI_CREATE_FUNCTION(env, exports, \"nativeInitLynxLog\", NativeInitLynxLog);\n  NAPI_CREATE_FUNCTION(env, exports, \"nativeUseSysLog\", NativeUseSysLog);\n  NAPI_CREATE_FUNCTION(env, exports, \"nativeInternalLog\", nativeInternalLog);\n  NAPI_CREATE_FUNCTION(env, exports, \"nativeSetMinLogLevel\",\n                       nativeSetMinLogLevel);\n  return exports;\n}\n\nnapi_value LynxLog::NativeInitLynxLogWriteFunction(napi_env env,\n                                                   napi_callback_info info) {\n  size_t argc = 1;\n  napi_value args[1] = {nullptr};\n  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);\n  int64_t log_write_function_address = NapiUtil::ConvertToInt64(env, args[0]);\n  if (!HasInitedLynxLogWriteFunction() && log_write_function_address != 0) {\n    s_alog_write =\n        reinterpret_cast<alog_write_func_ptr>(log_write_function_address);\n  }\n  return nullptr;\n}\n\nnapi_value LynxLog::NativeInitLynxLog(napi_env env, napi_callback_info info) {\n  size_t argc = 1;\n  napi_value args[1] = {nullptr};\n  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);\n  bool print_logs_to_all_channels = NapiUtil::ConvertToBoolean(env, args[0]);\n  InitLynxLogging(GetLynxLogWriteFunction, PrintLogMessageByLogDelegate,\n                  print_logs_to_all_channels);\n  return nullptr;\n}\n\nnapi_value LynxLog::NativeUseSysLog(napi_env env, napi_callback_info info) {\n  size_t argc = 1;\n  napi_value args[1] = {nullptr};\n  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);\n  bool s_use_sys_log = NapiUtil::ConvertToBoolean(env, args[0]);\n  if (s_use_sys_log) {\n    EnableLogOutputByPlatform();\n  } else {\n    DisableLogOutputByPlatform();\n  }\n  return nullptr;\n}\n\nnapi_value LynxLog::nativeInternalLog(napi_env env, napi_callback_info info) {\n  size_t argc = 3;\n  napi_value args[3] = {nullptr};\n  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);\n  int32_t level = NapiUtil::ConvertToInt32(env, args[0]);\n  std::string tag = NapiUtil::ConvertToString(env, args[1]);\n  std::string message = NapiUtil::ConvertToString(env, args[2]);\n  PrintLogToLynxLogging(static_cast<int>(level), tag.c_str(), message.c_str());\n  return nullptr;\n}\n\nnapi_value LynxLog::nativeSetMinLogLevel(napi_env env,\n                                         napi_callback_info info) {\n  size_t argc = 1;\n  napi_value args[1] = {nullptr};\n  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);\n  int32_t level = NapiUtil::ConvertToInt32(env, args[0]);\n  SetMinLogLevel(static_cast<int>(level));\n  return nullptr;\n}\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/log/logging_harmony.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_SRC_LOG_LOGGING_HARMONY_H_\n#define BASE_SRC_LOG_LOGGING_HARMONY_H_\n\n#include <node_api.h>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace logging {\n\nclass LynxLog {\n public:\n  static napi_value Init(napi_env env, napi_value exports);\n  static napi_value NativeInitLynxLog(napi_env env, napi_callback_info info);\n  static napi_value NativeInitLynxLogWriteFunction(napi_env env,\n                                                   napi_callback_info info);\n  static napi_value NativeUseSysLog(napi_env env, napi_callback_info info);\n  static napi_value nativeInternalLog(napi_env env, napi_callback_info info);\n  static napi_value nativeSetMinLogLevel(napi_env env, napi_callback_info info);\n};\n\n}  // namespace logging\n}  // namespace base\n}  // namespace lynx\n\n#endif  // BASE_SRC_LOG_LOGGING_HARMONY_H_\n"
  },
  {
    "path": "base/src/lynx_actor_unittest.cc",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/lynx_actor.h\"\n\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/thread.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace shell {\n\nfml::RefPtr<fml::TaskRunner> GetHookTaskRunner() {\n  static base::NoDestructor<fml::Thread> thread(\"Test_Runner\");\n  return thread->GetTaskRunner();\n}\n\nclass LynxActorTest : public ::testing::Test {\n protected:\n  LynxActorTest() = default;\n  ~LynxActorTest() override = default;\n\n  void SetUp() override {\n    actor_ = std::make_shared<LynxActor<std::string>>(\n        std::make_unique<std::string>(), task_runner_);\n  }\n\n  void TearDown() override {\n    actor_->ActSync([](auto& str) { str = nullptr; });\n  }\n\n  fml::RefPtr<fml::TaskRunner> task_runner_ = GetHookTaskRunner();\n  std::shared_ptr<LynxActor<std::string>> actor_;\n  fml::AutoResetWaitableEvent arwe_;\n};\n\nTEST_F(LynxActorTest, ActAsync) {\n  actor_->Act([](auto& str) { *str = \"MAGA\"; });\n  ASSERT_TRUE(actor_->ActSync([](auto& str) { return *str == \"MAGA\"; }));\n}\n\nTEST_F(LynxActorTest, ActAsyncAfterDestroy) {\n  actor_->ActSync([](auto& str) { str.reset(); });\n\n  bool result = true;\n  actor_->Act([&result](auto& str) mutable { result = false; });\n  task_runner_->PostTask([this]() mutable { arwe_.Signal(); });\n  arwe_.Wait();\n  ASSERT_TRUE(result);\n}\n\nTEST_F(LynxActorTest, ActSync) {\n  actor_->ActSync([](auto& str) { *str = \"MAGA\"; });\n  ASSERT_TRUE(actor_->ActSync([](auto& str) { return *str == \"MAGA\"; }));\n}\n\nTEST_F(LynxActorTest, ActSyncAfterDestroy) {\n  actor_->ActSync([](auto& str) { str.reset(); });\n\n  bool result = true;\n  actor_->ActSync([&result](auto& str) mutable { result = false; });\n  ASSERT_TRUE(result);\n}\n\nTEST_F(LynxActorTest, ActSyncWithRet) {\n  actor_->ActSync([](auto& str) { *str = \"MAGA\"; });\n  ASSERT_EQ(actor_->ActSync([](auto& str) { return *str; }), \"MAGA\");\n}\n\nTEST_F(LynxActorTest, ActSyncWithRetAfterDestroy) {\n  actor_->ActSync([](auto& str) { str.reset(); });\n\n  ASSERT_TRUE(actor_->ActSync([](auto& str) { return *str; }).empty());\n}\n\n}  // namespace shell\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/md5.cc",
    "content": "/* MD5\n converted to C++ class by Frank Thilo (thilo@unix-ag.org)\n for bzflag (http://www.bzflag.org)\n\n   based on:\n\n   md5.h and md5.c\n   reference implemantion of RFC 1321\n\n   Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All\nrights reserved.\n\nLicense to copy and use this software is granted provided that it\nis identified as the \"RSA Data Security, Inc. MD5 Message-Digest\nAlgorithm\" in all material mentioning or referencing this software\nor this function.\n\nLicense is also granted to make and use derivative works provided\nthat such works are identified as \"derived from the RSA Data\nSecurity, Inc. MD5 Message-Digest Algorithm\" in all material\nmentioning or referencing the derived work.\n\nRSA Data Security, Inc. makes no representations concerning either\nthe merchantability of this software or the suitability of this\nsoftware for any particular purpose. It is provided \"as is\"\nwithout express or implied warranty of any kind.\n\nThese notices must be retained in any copies of any part of this\ndocumentation and/or software.\n\n*/\n\n// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/md5.h\"\n\n#include <cstring>\n#include <memory>\n/* system implementation headers */\n#include <cstdio>\n#include <ostream>\n\n// \"Derived from the RSA Data Security, Inc. MD5 Message Digest Algorithm\"\n\nnamespace lynx {\nnamespace base {\n\n// Constants for MD5Transform routine.\n#define S11 7\n#define S12 12\n#define S13 17\n#define S14 22\n#define S21 5\n#define S22 9\n#define S23 14\n#define S24 20\n#define S31 4\n#define S32 11\n#define S33 16\n#define S34 23\n#define S41 6\n#define S42 10\n#define S43 15\n#define S44 21\n\n///////////////////////////////////////////////\n\n// F, G, H and I are basic MD5 functions.\ninline MD5::uint4 MD5::F(uint4 x, uint4 y, uint4 z) {\n  return (x & y) | (~x & z);\n}\n\ninline MD5::uint4 MD5::G(uint4 x, uint4 y, uint4 z) {\n  return (x & z) | (y & ~z);\n}\n\ninline MD5::uint4 MD5::H(uint4 x, uint4 y, uint4 z) { return x ^ y ^ z; }\n\ninline MD5::uint4 MD5::I(uint4 x, uint4 y, uint4 z) { return y ^ (x | ~z); }\n\n// rotate_left rotates x left n bits.\ninline MD5::uint4 MD5::rotate_left(uint4 x, int n) {\n  return (x << n) | (x >> (32 - n));\n}\n\n// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.\n// Rotation is separate from addition to prevent recomputation.\ninline void MD5::FF(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,\n                    uint4 ac) {\n  a = rotate_left(a + F(b, c, d) + x + ac, s) + b;\n}\n\ninline void MD5::GG(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,\n                    uint4 ac) {\n  a = rotate_left(a + G(b, c, d) + x + ac, s) + b;\n}\n\ninline void MD5::HH(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,\n                    uint4 ac) {\n  a = rotate_left(a + H(b, c, d) + x + ac, s) + b;\n}\n\ninline void MD5::II(uint4& a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,\n                    uint4 ac) {\n  a = rotate_left(a + I(b, c, d) + x + ac, s) + b;\n}\n\n//////////////////////////////////////////////\n\n// default ctor, just initailize\nMD5::MD5() { init(); }\n\n//////////////////////////////////////////////\n\n// nifty shortcut ctor, compute MD5 for string and finalize it right away\nMD5::MD5(const std::string& text) {\n  init();\n  update(text.c_str(), static_cast<size_type>(text.length()));\n  finalize();\n}\n\nMD5::MD5(const char* text, size_t length) {\n  init();\n  update(text, static_cast<size_type>(length));\n  finalize();\n}\n\n//////////////////////////////\n\nvoid MD5::init() {\n  finalized = false;\n\n  count[0] = 0;\n  count[1] = 0;\n\n  // load magic initialization constants.\n  state[0] = 0x67452301;\n  state[1] = 0xefcdab89;\n  state[2] = 0x98badcfe;\n  state[3] = 0x10325476;\n}\n\n//////////////////////////////\n\n// decodes input (unsigned char) into output (uint4). Assumes len is a multiple\n// of 4.\nvoid MD5::decode(uint4 output[], const uint1 input[], size_type len) {\n  for (unsigned int i = 0, j = 0; j < len; i++, j += 4)\n    output[i] = (static_cast<uint4>(input[j])) |\n                ((static_cast<uint4>(input[j + 1])) << 8) |\n                ((static_cast<uint4>(input[j + 2])) << 16) |\n                ((static_cast<uint4>(input[j + 3])) << 24);\n}\n\n//////////////////////////////\n\n// encodes input (uint4) into output (unsigned char). Assumes len is\n// a multiple of 4.\nvoid MD5::encode(uint1 output[], const uint4 input[], size_type len) {\n  for (size_type i = 0, j = 0; j < len; i++, j += 4) {\n    output[j] = input[i] & 0xff;\n    output[j + 1] = (input[i] >> 8) & 0xff;\n    output[j + 2] = (input[i] >> 16) & 0xff;\n    output[j + 3] = (input[i] >> 24) & 0xff;\n  }\n}\n\n//////////////////////////////\n\n// apply MD5 algo on a block\nvoid MD5::transform(const uint1 block[blocksize]) {\n  uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];\n  decode(x, block, blocksize);\n\n  /* Round 1 */\n  FF(a, b, c, d, x[0], S11, 0xd76aa478);  /* 1 */\n  FF(d, a, b, c, x[1], S12, 0xe8c7b756);  /* 2 */\n  FF(c, d, a, b, x[2], S13, 0x242070db);  /* 3 */\n  FF(b, c, d, a, x[3], S14, 0xc1bdceee);  /* 4 */\n  FF(a, b, c, d, x[4], S11, 0xf57c0faf);  /* 5 */\n  FF(d, a, b, c, x[5], S12, 0x4787c62a);  /* 6 */\n  FF(c, d, a, b, x[6], S13, 0xa8304613);  /* 7 */\n  FF(b, c, d, a, x[7], S14, 0xfd469501);  /* 8 */\n  FF(a, b, c, d, x[8], S11, 0x698098d8);  /* 9 */\n  FF(d, a, b, c, x[9], S12, 0x8b44f7af);  /* 10 */\n  FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */\n  FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */\n  FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */\n  FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */\n  FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */\n  FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */\n\n  /* Round 2 */\n  GG(a, b, c, d, x[1], S21, 0xf61e2562);  /* 17 */\n  GG(d, a, b, c, x[6], S22, 0xc040b340);  /* 18 */\n  GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */\n  GG(b, c, d, a, x[0], S24, 0xe9b6c7aa);  /* 20 */\n  GG(a, b, c, d, x[5], S21, 0xd62f105d);  /* 21 */\n  GG(d, a, b, c, x[10], S22, 0x2441453);  /* 22 */\n  GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */\n  GG(b, c, d, a, x[4], S24, 0xe7d3fbc8);  /* 24 */\n  GG(a, b, c, d, x[9], S21, 0x21e1cde6);  /* 25 */\n  GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */\n  GG(c, d, a, b, x[3], S23, 0xf4d50d87);  /* 27 */\n  GG(b, c, d, a, x[8], S24, 0x455a14ed);  /* 28 */\n  GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */\n  GG(d, a, b, c, x[2], S22, 0xfcefa3f8);  /* 30 */\n  GG(c, d, a, b, x[7], S23, 0x676f02d9);  /* 31 */\n  GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */\n\n  /* Round 3 */\n  HH(a, b, c, d, x[5], S31, 0xfffa3942);  /* 33 */\n  HH(d, a, b, c, x[8], S32, 0x8771f681);  /* 34 */\n  HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */\n  HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */\n  HH(a, b, c, d, x[1], S31, 0xa4beea44);  /* 37 */\n  HH(d, a, b, c, x[4], S32, 0x4bdecfa9);  /* 38 */\n  HH(c, d, a, b, x[7], S33, 0xf6bb4b60);  /* 39 */\n  HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */\n  HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */\n  HH(d, a, b, c, x[0], S32, 0xeaa127fa);  /* 42 */\n  HH(c, d, a, b, x[3], S33, 0xd4ef3085);  /* 43 */\n  HH(b, c, d, a, x[6], S34, 0x4881d05);   /* 44 */\n  HH(a, b, c, d, x[9], S31, 0xd9d4d039);  /* 45 */\n  HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */\n  HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */\n  HH(b, c, d, a, x[2], S34, 0xc4ac5665);  /* 48 */\n\n  /* Round 4 */\n  II(a, b, c, d, x[0], S41, 0xf4292244);  /* 49 */\n  II(d, a, b, c, x[7], S42, 0x432aff97);  /* 50 */\n  II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */\n  II(b, c, d, a, x[5], S44, 0xfc93a039);  /* 52 */\n  II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */\n  II(d, a, b, c, x[3], S42, 0x8f0ccc92);  /* 54 */\n  II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */\n  II(b, c, d, a, x[1], S44, 0x85845dd1);  /* 56 */\n  II(a, b, c, d, x[8], S41, 0x6fa87e4f);  /* 57 */\n  II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */\n  II(c, d, a, b, x[6], S43, 0xa3014314);  /* 59 */\n  II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */\n  II(a, b, c, d, x[4], S41, 0xf7537e82);  /* 61 */\n  II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */\n  II(c, d, a, b, x[2], S43, 0x2ad7d2bb);  /* 63 */\n  II(b, c, d, a, x[9], S44, 0xeb86d391);  /* 64 */\n\n  state[0] += a;\n  state[1] += b;\n  state[2] += c;\n  state[3] += d;\n\n  // Zeroize sensitive information.\n  memset(x, 0, sizeof x);\n}\n\n//////////////////////////////\n\n// MD5 block update operation. Continues an MD5 message-digest\n// operation, processing another message block\nvoid MD5::update(const unsigned char input[], size_type length) {\n  // compute number of bytes mod 64\n  size_type index = count[0] / 8 % blocksize;\n\n  // Update number of bits\n  if ((count[0] += (length << 3)) < (length << 3)) count[1]++;\n  count[1] += (length >> 29);\n\n  // number of bytes we need to fill in buffer\n  size_type firstpart = 64 - index;\n\n  size_type i;\n\n  // transform as many times as possible.\n  if (length >= firstpart) {\n    // fill buffer first, transform\n    memcpy(&buffer[index], input, firstpart);\n    transform(buffer);\n\n    // transform chunks of blocksize (64 bytes)\n    for (i = firstpart; i + blocksize <= length; i += blocksize)\n      transform(&input[i]);\n\n    index = 0;\n  } else\n    i = 0;\n\n  // buffer remaining input\n  memcpy(&buffer[index], &input[i], length - i);\n}\n\n//////////////////////////////\n\n// for convenience provide a verson with signed char\nvoid MD5::update(const char input[], size_type length) {\n  update(reinterpret_cast<const unsigned char*>(input), length);\n}\n\n//////////////////////////////\n\n// MD5 finalization. Ends an MD5 message-digest operation, writing the\n// the message digest and zeroizing the context.\nMD5& MD5::finalize() {\n  static unsigned char padding[64] = {\n      0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n      0,    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n      0,    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};\n\n  if (!finalized) {\n    // Save number of bits\n    unsigned char bits[8];\n    encode(bits, count, 8);\n\n    // pad out to 56 mod 64.\n    size_type index = count[0] / 8 % 64;\n    size_type padLen = (index < 56) ? (56 - index) : (120 - index);\n    update(padding, padLen);\n\n    // Append length (before padding)\n    update(bits, 8);\n\n    // Store state in digest\n    encode(digest, state, 16);\n\n    // Zeroize sensitive information.\n    memset(buffer, 0, sizeof buffer);\n    memset(count, 0, sizeof count);\n\n    finalized = true;\n  }\n\n  return *this;\n}\n\n//////////////////////////////\n\n// return hex representation of digest as string\nstd::string MD5::hexdigest() const {\n  if (!finalized) return \"\";\n\n  char buf[33];\n  for (int i = 0; i < 16; i++) snprintf(buf + i * 2, 33, \"%02x\", digest[i]);\n  buf[32] = 0;\n\n  return std::string(buf);\n}\n\n//////////////////////////////\n\nstd::ostream& operator<<(std::ostream& out, MD5 md5) {\n  return out << md5.hexdigest();\n}\n\n//////////////////////////////\n\nstd::string md5(const std::string& str) {\n  MD5 md5 = MD5(str);\n\n  return md5.hexdigest();\n}\n\nstd::string md5(const char* str, const size_t length) {\n  MD5 md5 = MD5(str, length);\n\n  return md5.hexdigest();\n}\n\n}  //  namespace base\n}  //  namespace  lynx\n"
  },
  {
    "path": "base/src/path_utils_unittest.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/path_utils.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nTEST(PathUtils, AbsolutePathTest) {\n  std::string dirname = \"/pages/index/\";\n  std::string url = \"/assert/logo.png\";\n  EXPECT_EQ(\"/assert/logo.png\", PathUtils::RedirectUrlPath(dirname, url));\n}\n\nTEST(PathUtils, RelativePathTest) {\n  std::string dirname = \"/pages/index/\";\n  std::string url = \"../../assert/logo.png\";\n  EXPECT_EQ(\"/assert/logo.png\", PathUtils::RedirectUrlPath(dirname, url));\n}\n\nTEST(PathUtils, OtherPathTest) {\n  std::string dirname = \"/pages/index/\";\n  std::string url = \"ttfile://page/logo.png\";\n  EXPECT_EQ(\"ttfile://page/logo.png\", PathUtils::RedirectUrlPath(dirname, url));\n}\n\nTEST(PathUtils, GetLastPath) {\n  static const struct {\n    std::string file_name;\n    std::string expected;\n  } cases[] = {{__FILE__, \"path_utils_unittest.cc\"},\n               {\"path_utils.cc\", \"path_utils.cc\"},\n               {\"\", \"\"}};\n\n  for (const auto& test : cases) {\n    EXPECT_EQ(std::string(PathUtils::GetLastPath(test.file_name.c_str(),\n                                                 test.file_name.length())),\n              test.expected);\n  }\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/platform/android/java_type.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/platform/android/java_type.h\"\n\n#include \"base/include/platform/android/jni_convert_helper.h\"\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nScopedLocalJavaRef<jstring> JType::NewString(JNIEnv* env, const char* str) {\n  return JNIConvertHelper::ConvertToJNIStringUTF(env, str);\n}\n\nScopedLocalJavaRef<jobjectArray> JType::NewObjectArray(JNIEnv* env,\n                                                       int length) {\n  EnsureInstance(env, kObjectType);\n  return ScopedLocalJavaRef<jobjectArray>(\n      env, env->NewObjectArray(length, object_clazz, nullptr));\n}\n\nScopedLocalJavaRef<jdoubleArray> JType::NewDoubleArray(JNIEnv* env,\n                                                       int length) {\n  return ScopedLocalJavaRef<jdoubleArray>(env, env->NewDoubleArray(length));\n}\n\nScopedLocalJavaRef<jobjectArray> JType::NewStringArray(JNIEnv* env,\n                                                       int length) {\n  EnsureInstance(env, kStringType);\n  return ScopedLocalJavaRef<jobjectArray>(\n      env, env->NewObjectArray(length, string_clazz, nullptr));\n}\n\nScopedLocalJavaRef<jobject> JType::NewByte(JNIEnv* env, jbyte value) {\n  EnsureInstance(env, kByteType);\n  jobject result = env->NewObject(byte_clazz, byte_ctor, value);\n  return ScopedLocalJavaRef<jobject>(env, result);\n}\n\nScopedLocalJavaRef<jobject> JType::NewChar(JNIEnv* env, jchar value) {\n  EnsureInstance(env, kCharType);\n  jobject result = env->NewObject(char_clazz, char_ctor, value);\n  return ScopedLocalJavaRef<jobject>(env, result);\n}\n\nScopedLocalJavaRef<jobject> JType::NewBoolean(JNIEnv* env, jboolean value) {\n  EnsureInstance(env, kBooleanType);\n  jobject result = env->NewObject(boolean_clazz, boolean_ctor, value);\n  return ScopedLocalJavaRef<jobject>(env, result);\n}\n\nScopedLocalJavaRef<jobject> JType::NewShort(JNIEnv* env, jshort value) {\n  EnsureInstance(env, kShortType);\n  jobject result = env->NewObject(short_clazz, short_ctor, value);\n  return ScopedLocalJavaRef<jobject>(env, result);\n}\n\nScopedLocalJavaRef<jobject> JType::NewInt(JNIEnv* env, jint value) {\n  EnsureInstance(env, kIntType);\n  jobject result = env->NewObject(int_clazz, int_ctor, value);\n  return ScopedLocalJavaRef<jobject>(env, result);\n}\n\nScopedLocalJavaRef<jobject> JType::NewLong(JNIEnv* env, jlong value) {\n  EnsureInstance(env, kLongType);\n  jobject result = env->NewObject(long_clazz, long_ctor, value);\n  return ScopedLocalJavaRef<jobject>(env, result);\n}\n\nScopedLocalJavaRef<jobject> JType::NewFloat(JNIEnv* env, jfloat value) {\n  EnsureInstance(env, kFloatType);\n  jobject result = env->NewObject(float_clazz, float_ctor, value);\n  return ScopedLocalJavaRef<jobject>(env, result);\n}\n\nScopedLocalJavaRef<jobject> JType::NewDouble(JNIEnv* env, jdouble value) {\n  EnsureInstance(env, kDoubleType);\n  jobject result = env->NewObject(double_clazz, double_ctor, value);\n  return ScopedLocalJavaRef<jobject>(env, result);\n}\n\njbyte JType::ByteValue(JNIEnv* env, jobject value) {\n  EnsureInstance(env, kByteType);\n  return env->GetByteField(value, byte_valueField);\n}\n\njchar JType::CharValue(JNIEnv* env, jobject value) {\n  EnsureInstance(env, kCharType);\n  return env->GetCharField(value, char_valueField);\n}\n\njboolean JType::BooleanValue(JNIEnv* env, jobject value) {\n  EnsureInstance(env, kBooleanType);\n  return env->GetBooleanField(value, boolean_valueField);\n}\n\njshort JType::ShortValue(JNIEnv* env, jobject value) {\n  EnsureInstance(env, kShortType);\n  return env->GetShortField(value, short_valueField);\n}\n\njint JType::IntValue(JNIEnv* env, jobject value) {\n  EnsureInstance(env, kIntType);\n  return env->GetIntField(value, int_valueField);\n}\n\njlong JType::LongValue(JNIEnv* env, jobject value) {\n  EnsureInstance(env, kLongType);\n  return env->GetLongField(value, long_valueField);\n}\n\njfloat JType::FloatValue(JNIEnv* env, jobject value) {\n  EnsureInstance(env, kFloatType);\n  return env->GetFloatField(value, float_valueField);\n}\n\njdouble JType::DoubleValue(JNIEnv* env, jobject value) {\n  EnsureInstance(env, kDoubleType);\n  return env->GetDoubleField(value, double_valueField);\n}\n\nvoid JType::EnsureInstance(JNIEnv* env, char type) { Init(env, type); }\n\nvoid JType::Init(JNIEnv* env, char type) {\n  // TODO(Provide a fallback mechanism to prevent possible field name changes)\n  switch (type) {\n    case kByteType:\n      if (byte_clazz == nullptr) {\n        byte_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/Byte\"));\n        byte_ctor = env->GetMethodID(byte_clazz, \"<init>\", \"(B)V\");\n        byte_valueField = env->GetFieldID(byte_clazz, \"value\", \"B\");\n      }\n      break;\n    case kCharType:\n      if (char_clazz == nullptr) {\n        char_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/Character\"));\n        char_ctor = env->GetMethodID(char_clazz, \"<init>\", \"(C)V\");\n        char_valueField = env->GetFieldID(char_clazz, \"value\", \"C\");\n      }\n      break;\n    case kBooleanType:\n      if (boolean_clazz == nullptr) {\n        boolean_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/Boolean\"));\n        boolean_ctor = env->GetMethodID(boolean_clazz, \"<init>\", \"(Z)V\");\n        boolean_valueField = env->GetFieldID(boolean_clazz, \"value\", \"Z\");\n      }\n      break;\n    case kShortType:\n      if (short_clazz == nullptr) {\n        short_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/Short\"));\n        short_ctor = env->GetMethodID(short_clazz, \"<init>\", \"(S)V\");\n        short_valueField = env->GetFieldID(short_clazz, \"value\", \"S\");\n      }\n      break;\n    case kIntType:\n      if (int_clazz == nullptr) {\n        int_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/Integer\"));\n        int_ctor = env->GetMethodID(int_clazz, \"<init>\", \"(I)V\");\n        int_valueField = env->GetFieldID(int_clazz, \"value\", \"I\");\n      }\n      break;\n    case kLongType:\n      if (long_clazz == nullptr) {\n        long_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/Long\"));\n        long_ctor = env->GetMethodID(long_clazz, \"<init>\", \"(J)V\");\n        long_valueField = env->GetFieldID(long_clazz, \"value\", \"J\");\n      }\n      break;\n    case kFloatType:\n      if (float_clazz == nullptr) {\n        float_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/Float\"));\n        float_ctor = env->GetMethodID(float_clazz, \"<init>\", \"(F)V\");\n        float_valueField = env->GetFieldID(float_clazz, \"value\", \"F\");\n      }\n      break;\n    case kDoubleType:\n      if (double_clazz == nullptr) {\n        double_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/Double\"));\n        double_ctor = env->GetMethodID(double_clazz, \"<init>\", \"(D)V\");\n        double_valueField = env->GetFieldID(double_clazz, \"value\", \"D\");\n      }\n      break;\n    case kStringType:\n      if (string_clazz == nullptr) {\n        string_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/String\"));\n      }\n      break;\n    case kObjectType:\n      if (object_clazz == nullptr) {\n        object_clazz =\n            // NOLINTNEXTLINE\n            (jclass)env->NewGlobalRef(env->FindClass(\"java/lang/Object\"));\n      }\n      break;\n    default:\n      break;\n  }\n}\n\njclass JType::byte_clazz;\njmethodID JType::byte_ctor;\njfieldID JType::byte_valueField;\n\njclass JType::char_clazz;\njmethodID JType::char_ctor;\njfieldID JType::char_valueField;\n\njclass JType::boolean_clazz;\njmethodID JType::boolean_ctor;\njfieldID JType::boolean_valueField;\n\njclass JType::short_clazz;\njmethodID JType::short_ctor;\njfieldID JType::short_valueField;\n\njclass JType::int_clazz;\njmethodID JType::int_ctor;\njfieldID JType::int_valueField;\n\njclass JType::long_clazz;\njmethodID JType::long_ctor;\njfieldID JType::long_valueField;\n\njclass JType::float_clazz;\njmethodID JType::float_ctor;\njfieldID JType::float_valueField;\n\njclass JType::double_clazz;\njmethodID JType::double_ctor;\njfieldID JType::double_valueField;\n\njclass JType::string_clazz;\njclass JType::object_clazz;\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/platform/android/jni_convert_helper.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/platform/android/jni_convert_helper.h\"\n\n#include \"base/include/fml/macros.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\n#define ASSERT_NO_EXCEPTION() \\\n  LYNX_BASE_CHECK(env->ExceptionCheck() == JNI_FALSE);\n\nlynx::base::android::ScopedLocalJavaRef<jstring>\nJNIConvertHelper::ConvertToJNIStringUTF(JNIEnv* env, const std::string& value) {\n  jstring str = env->NewStringUTF(value.c_str());  // NOLINT\n  return lynx::base::android::ScopedLocalJavaRef<jstring>(env, str);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jstring>\nJNIConvertHelper::ConvertToJNIStringUTF(JNIEnv* env, const char* value) {\n  jstring str = env->NewStringUTF(value);  // NOLINT\n  return lynx::base::android::ScopedLocalJavaRef<jstring>(env, str);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jstring>\nJNIConvertHelper::ConvertToJNIString(JNIEnv* env, const jchar* unicode_chars,\n                                     jsize len) {\n  jstring str = env->NewString(unicode_chars, len);  // NOLINT\n  return lynx::base::android::ScopedLocalJavaRef<jstring>(env, str);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jbyteArray>\nJNIConvertHelper::ConvertToJNIByteArray(JNIEnv* env, const std::string& str) {\n  jbyteArray array = env->NewByteArray(str.length());  // NOLINT\n  env->SetByteArrayRegion(array, 0, str.length(),\n                          reinterpret_cast<const jbyte*>(str.c_str()));\n  return lynx::base::android::ScopedLocalJavaRef<jbyteArray>(env, array);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jbyteArray>\nJNIConvertHelper::ConvertToJNIByteArray(JNIEnv* env, const void* data,\n                                        int32_t size) {\n  jbyteArray array = env->NewByteArray(size);  // NOLINT\n  env->SetByteArrayRegion(array, 0, size, reinterpret_cast<const jbyte*>(data));\n  return lynx::base::android::ScopedLocalJavaRef<jbyteArray>(env, array);\n}\n\nstd::vector<uint8_t> JNIConvertHelper::ConvertJavaBinary(JNIEnv* env,\n                                                         jbyteArray j_binary) {\n  std::vector<uint8_t> binary;\n  if (j_binary != nullptr) {\n    auto* temp = env->GetByteArrayElements(j_binary, JNI_FALSE);\n    size_t len = env->GetArrayLength(j_binary);\n    if (len > 0) {\n      auto begin = reinterpret_cast<const uint8_t*>(temp);\n      binary.assign(begin, begin + len);\n    }\n    env->ReleaseByteArrayElements(j_binary, temp, JNI_FALSE);\n  }\n  return binary;\n}\n\nbool JNIConvertHelper::ConvertJavaBinary(\n    JNIEnv* env, jbyteArray j_binary,\n    std::function<void*(int32_t size)> allocator) {\n  if (j_binary != nullptr) {\n    auto* temp = env->GetByteArrayElements(j_binary, nullptr);\n    size_t len = env->GetArrayLength(j_binary);\n    if (len > 0) {\n      auto data = allocator(len);\n      if (data && temp) {\n        std::memcpy(data, temp, len);\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nbool JNIConvertHelper::ConvertJavaDirectByteBuffer(\n    JNIEnv* env, jobject j_buffer,\n    std::function<void*(int32_t size)> allocator) {\n  if (j_buffer != nullptr) {\n    auto* temp = env->GetDirectBufferAddress(j_buffer);\n    size_t len = env->GetDirectBufferCapacity(j_buffer);\n    if (len > 0 && temp != nullptr) {\n      auto data = allocator(len);\n      if (data) {\n        std::memcpy(data, temp, len);\n      }\n      return true;\n    }\n  }\n\n  return false;\n}\n\nlynx::base::android::ScopedLocalJavaRef<jobject>\nJNIConvertHelper::ConvertToJavaDirectByteBuffer(JNIEnv* env, const void* data,\n                                                int32_t size) {\n  auto buffer = env->NewDirectByteBuffer(const_cast<void*>(data), size);\n  return lynx::base::android::ScopedLocalJavaRef<jobject>(env, buffer);\n}\n\nstd::string JNIConvertHelper::ConvertToString(JNIEnv* env, jstring j_str) {\n  std::string res;\n  if (j_str != nullptr) {\n    const char* str = env->GetStringUTFChars(j_str, JNI_FALSE);\n    if (str) {\n      res = std::string(str);\n    }\n    env->ReleaseStringUTFChars(j_str, str);\n  }\n  return res;\n}\n\nstd::string JNIConvertHelper::ConvertToString(JNIEnv* env,\n                                              jbyteArray j_binary) {\n  std::string str;\n  if (j_binary != nullptr) {\n    auto* temp = env->GetByteArrayElements(j_binary, JNI_FALSE);\n    size_t len = env->GetArrayLength(j_binary);\n    str.assign(reinterpret_cast<char*>(temp), len);\n    env->ReleaseByteArrayElements(j_binary, temp, JNI_FALSE);\n  }\n  return str;\n}\n\nScopedLocalJavaRef<jobjectArray>\nJNIConvertHelper::ConvertStringVectorToJavaStringArray(\n    JNIEnv* env, const std::vector<std::string>& input) {\n  auto size = input.size();\n  jobjectArray result = env->NewObjectArray(\n      static_cast<jsize>(size), env->FindClass(\"java/lang/String\"), nullptr);\n\n  for (size_t i = 0; i < size; ++i) {\n    auto j_str =\n        base::android::JNIConvertHelper::ConvertToJNIStringUTF(env, input[i]);\n    env->SetObjectArrayElement(result, i, j_str.Get());\n  }\n  return ScopedLocalJavaRef<jobjectArray>(env, result);\n}\n\nScopedLocalJavaRef<jstring> JNIConvertHelper::U16StringToJNIString(\n    JNIEnv* env, const std::u16string& str) {\n  auto result = ScopedLocalJavaRef<jstring>(\n      env, env->NewString(reinterpret_cast<const jchar*>(str.data()),  // NOLINT\n                          str.length()));\n  return result;\n}\n\nScopedLocalJavaRef<jobjectArray>\nJNIConvertHelper::ConvertStringVectorToJavaStringArray(\n    JNIEnv* env, const std::vector<std::u16string>& input) {\n  auto size = input.size();\n  jobjectArray result = env->NewObjectArray(\n      static_cast<jsize>(size), env->FindClass(\"java/lang/String\"), nullptr);\n\n  for (size_t i = 0; i < size; ++i) {\n    auto j_str =\n        base::android::JNIConvertHelper::U16StringToJNIString(env, input[i]);\n    env->SetObjectArrayElement(result, i, j_str.Get());\n  }\n  return ScopedLocalJavaRef<jobjectArray>(env, result);\n}\n\nScopedLocalJavaRef<jobjectArray> ConvertVectorToBufferArray(\n    JNIEnv* env, const std::vector<std::vector<uint8_t>>& vector) {\n  ScopedLocalJavaRef<jclass> byte_buffer_clazz(\n      env, env->FindClass(\"java/nio/ByteBuffer\"));\n  LYNX_BASE_DCHECK(!byte_buffer_clazz.IsNull());\n  jobjectArray java_array =\n      env->NewObjectArray(vector.size(), byte_buffer_clazz.Get(), NULL);\n  ASSERT_NO_EXCEPTION();\n  for (size_t i = 0; i < vector.size(); ++i) {\n    uint8_t* data = const_cast<uint8_t*>(vector[i].data());\n    ScopedLocalJavaRef<jobject> item(\n        env, env->NewDirectByteBuffer(reinterpret_cast<void*>(data),\n                                      vector[i].size()));\n    env->SetObjectArrayElement(java_array, i, item.Get());\n  }\n  return ScopedLocalJavaRef<jobjectArray>(env, java_array);\n}\n\nstd::vector<std::string> JNIConvertHelper::ConvertJavaStringArrayToStringVector(\n    JNIEnv* env, jobjectArray array) {\n  std::vector<std::string> result;\n  if (array == nullptr) {\n    return result;\n  }\n  jsize len = env->GetArrayLength(array);\n  for (size_t i = 0; i < static_cast<size_t>(len); ++i) {\n    auto java_obj =\n        ScopedLocalJavaRef<jobject>(env, env->GetObjectArrayElement(array, i));\n    result.emplace_back(\n        ConvertToString(env, static_cast<jstring>(java_obj.Get())));\n  }\n  return result;\n}\n\nstd::unordered_set<std::string>\nJNIConvertHelper::ConvertJavaStringSetToSTLStringSet(JNIEnv* env, jobject set) {\n  std::unordered_set<std::string> ret_set;\n\n  auto set_class =\n      ScopedLocalJavaRef<jclass>(env, env->FindClass(\"java/util/Set\"));\n\n  jmethodID set_to_array =\n      env->GetMethodID(set_class.Get(), \"toArray\", \"()[Ljava/lang/Object;\");\n\n  auto str_array = ScopedLocalJavaRef<jobjectArray>(\n      env, static_cast<jobjectArray>(env->CallObjectMethod(set, set_to_array)));\n\n  if (str_array.Get() == nullptr) {\n    return ret_set;\n  }\n\n  size_t len = env->GetArrayLength(str_array.Get());\n  for (size_t i = 0; i < len; ++i) {\n    auto java_obj = ScopedLocalJavaRef<jobject>(\n        env, env->GetObjectArrayElement(str_array.Get(), i));\n    ret_set.emplace(ConvertToString(env, static_cast<jstring>(java_obj.Get())));\n  }\n  return ret_set;\n}\n\nstd::unique_ptr<std::unordered_map<std::string, std::string>>\nJNIConvertHelper::ConvertJavaStringHashMapToSTLStringMap(JNIEnv* env,\n                                                         jobject javaMap) {\n  if (!javaMap) {\n    return nullptr;\n  }\n\n  // get class\n  jclass mapClass = env->FindClass(\"java/util/HashMap\");\n  jclass setClass = env->FindClass(\"java/util/Set\");\n  jclass iteratorClass = env->FindClass(\"java/util/Iterator\");\n  jclass entryClass = env->FindClass(\"java/util/Map$Entry\");\n  jclass stringClass = env->FindClass(\"java/lang/String\");\n  if (!mapClass || !setClass || !iteratorClass || !entryClass || !stringClass) {\n    return nullptr;\n  }\n\n  // get method id\n  jmethodID entrySetMethod =\n      env->GetMethodID(mapClass, \"entrySet\", \"()Ljava/util/Set;\");\n  jmethodID iteratorMethod =\n      env->GetMethodID(setClass, \"iterator\", \"()Ljava/util/Iterator;\");\n  jmethodID hasNextMethod = env->GetMethodID(iteratorClass, \"hasNext\", \"()Z\");\n  jmethodID nextMethod =\n      env->GetMethodID(iteratorClass, \"next\", \"()Ljava/lang/Object;\");\n  jmethodID getKeyMethod =\n      env->GetMethodID(entryClass, \"getKey\", \"()Ljava/lang/Object;\");\n  jmethodID getValueMethod =\n      env->GetMethodID(entryClass, \"getValue\", \"()Ljava/lang/Object;\");\n  if (!entrySetMethod || !iteratorMethod || !hasNextMethod || !nextMethod ||\n      !getKeyMethod || !getValueMethod) {\n    return nullptr;\n  }\n\n  // get entrySet\n  jobject entrySet = env->CallObjectMethod(javaMap, entrySetMethod);\n  if (env->ExceptionCheck() || !entrySet) {\n    env->ExceptionClear();\n    return nullptr;\n  }\n\n  // get iterator\n  jobject iterator = env->CallObjectMethod(entrySet, iteratorMethod);\n  if (env->ExceptionCheck() || !iterator) {\n    env->ExceptionClear();\n    env->DeleteLocalRef(entrySet);\n    return nullptr;\n  }\n\n  auto cppMapPtr =\n      std::make_unique<std::unordered_map<std::string, std::string>>();\n  // convert\n  while (env->CallBooleanMethod(iterator, hasNextMethod)) {\n    if (env->ExceptionCheck()) {\n      env->ExceptionClear();\n      break;\n    }\n\n    jobject entry = env->CallObjectMethod(iterator, nextMethod);\n    if (env->ExceptionCheck() || !entry) {\n      env->ExceptionClear();\n      continue;\n    }\n\n    // get string key\n    jobject keyObj = env->CallObjectMethod(entry, getKeyMethod);\n    if (env->ExceptionCheck() || !keyObj ||\n        !env->IsInstanceOf(keyObj, stringClass)) {\n      if (env->ExceptionCheck()) {\n        env->ExceptionClear();\n      }\n      env->DeleteLocalRef(entry);\n      if (keyObj) env->DeleteLocalRef(keyObj);\n      continue;\n    }\n\n    // get string value\n    jobject valueObj = env->CallObjectMethod(entry, getValueMethod);\n    if (env->ExceptionCheck() || !valueObj ||\n        !env->IsInstanceOf(valueObj, stringClass)) {\n      if (env->ExceptionCheck()) {\n        env->ExceptionClear();\n      }\n      env->DeleteLocalRef(entry);\n      env->DeleteLocalRef(keyObj);\n      if (valueObj) env->DeleteLocalRef(valueObj);\n      continue;\n    }\n\n    jstring javaKey = static_cast<jstring>(keyObj);\n    jstring javaValue = static_cast<jstring>(valueObj);\n\n    std::string key =\n        lynx::base::android::JNIConvertHelper::ConvertToString(env, javaKey);\n    std::string value =\n        lynx::base::android::JNIConvertHelper::ConvertToString(env, javaValue);\n\n    cppMapPtr->emplace(std::move(key), std::move(value));\n\n    // clear local ref\n    env->DeleteLocalRef(javaKey);\n    env->DeleteLocalRef(javaValue);\n    env->DeleteLocalRef(entry);\n  }\n\n  // clear local ref\n  env->DeleteLocalRef(iterator);\n  env->DeleteLocalRef(entrySet);\n  env->DeleteLocalRef(mapClass);\n  env->DeleteLocalRef(setClass);\n  env->DeleteLocalRef(iteratorClass);\n  env->DeleteLocalRef(entryClass);\n  env->DeleteLocalRef(stringClass);\n\n  return cppMapPtr;\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/platform/android/jni_utils.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/platform/android/jni_utils.h\"\n\n#include <android/log.h>\n#include <sys/prctl.h>\n#include <sys/system_properties.h>\n\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/platform/android/jni_convert_helper.h\"\n\nnamespace {\nJavaVM *g_jvm = nullptr;\n}\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nvoid InitVM(JavaVM *vm) { g_jvm = vm; }\n\nstruct JNIDetach {\n  ~JNIDetach() { DetachFromVM(); }\n};\n\n// Thread-local object that will detach from JNI during thread shutdown;\nstatic thread_local std::unique_ptr<JNIDetach> tls_jni_detach;\n\nJNIEnv *AttachCurrentThread() {\n  LYNX_BASE_DCHECK(g_jvm != nullptr);\n\n  JNIEnv *env = nullptr;\n  jint ret = g_jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_2);\n  if (ret == JNI_OK && env) {\n    return env;\n  }\n\n  if (ret == JNI_EDETACHED || !env) {\n    JavaVMAttachArgs args;\n    args.version = JNI_VERSION_1_2;\n    args.group = nullptr;\n\n    // 16 is the maximum size for thread names on Android.\n    char thread_name[16];\n    int err = prctl(PR_GET_NAME, thread_name);\n    if (err < 0) {\n      args.name = nullptr;\n    } else {\n      args.name = thread_name;\n    }\n\n    ret = g_jvm->AttachCurrentThread(&env, &args);\n  }\n\n  if (tls_jni_detach.get() == nullptr) {\n    tls_jni_detach.reset(new JNIDetach());\n  }\n\n  return env;\n}\n\nvoid DetachFromVM() {\n  if (g_jvm) {\n    g_jvm->DetachCurrentThread();\n  }\n}\n\nScopedLocalJavaRef<jclass> GetClass(JNIEnv *env, const char *class_name) {\n  jclass clazz = env->FindClass(class_name);\n  if (ClearException(env) || !clazz) {\n    std::string msg = \"Failed to find class \" + std::string(class_name);\n    __android_log_write(ANDROID_LOG_FATAL, \"lynx\", msg.c_str());\n  }\n  return ScopedLocalJavaRef<jclass>(env, clazz);\n}\n\nScopedGlobalJavaRef<jclass> GetGlobalClass(JNIEnv *env,\n                                           const char *class_name) {\n  jclass clazz = env->FindClass(class_name);\n  if (ClearException(env) || !clazz) {\n    std::string msg = \"Failed to find class \" + std::string(class_name ?: \"\");\n    __android_log_write(ANDROID_LOG_FATAL, \"lynx\", msg.c_str());\n  }\n  return ScopedGlobalJavaRef<jclass>(env, clazz);\n}\n\njmethodID GetMethod(JNIEnv *env, jclass clazz, MethodType type,\n                    const char *method_name, const char *jni_signature) {\n  jmethodID id = nullptr;\n  if (clazz) {\n    if (type == STATIC_METHOD) {\n      id = env->GetStaticMethodID(clazz, method_name, jni_signature);\n    } else if (type == INSTANCE_METHOD) {\n      id = env->GetMethodID(clazz, method_name, jni_signature);\n    }\n    if (ClearException(env) || !id) {\n      std::string msg = \"Failed to find \" +\n                        std::string((type == STATIC_METHOD) ? \"static\" : \"\") +\n                        std::string(method_name ?: \"\") +\n                        std::string(jni_signature ?: \"\");\n      __android_log_write(ANDROID_LOG_FATAL, \"lynx\", msg.c_str());\n    }\n  }\n  return id;\n}\n\njmethodID GetMethod(JNIEnv *env, jclass clazz, MethodType type,\n                    const char *method_name, const char *jni_signature,\n                    intptr_t *method_id) {\n  if (*method_id) {\n    return reinterpret_cast<jmethodID>(*method_id);\n  }\n  *method_id = reinterpret_cast<intptr_t>(\n      GetMethod(env, clazz, type, method_name, jni_signature));\n  return reinterpret_cast<jmethodID>(*method_id);\n}\n\nbool HasException(JNIEnv *env) { return env->ExceptionCheck() != JNI_FALSE; }\n\nbool ClearException(JNIEnv *env) {\n  if (!HasException(env)) return false;\n  env->ExceptionDescribe();\n  env->ExceptionClear();\n  return true;\n}\n\nbool CheckException(JNIEnv *env, std::string &exception_msg) {\n  if (!HasException(env)) return true;\n\n  // Exception has been found, might as well tell BreakPad about it.\n  lynx::base::android::ScopedLocalJavaRef<jthrowable> throwable(\n      env, env->ExceptionOccurred());\n  if (throwable.Get()) {\n    // Clear the pending exception, since a local reference is now held.\n    env->ExceptionDescribe();\n    env->ExceptionClear();\n    exception_msg = GetJavaExceptionInfo(env, throwable.Get());\n  }\n\n  return false;\n}\n\nbool CheckAndPrintException(JNIEnv *env) {\n  std::string error_msg;\n  bool has_no_exception = CheckException(env, error_msg);\n  if (!has_no_exception) {\n    std::string exception_msg = \"JNI exception found: \" + error_msg;\n    __android_log_write(ANDROID_LOG_FATAL, \"lynx\", exception_msg.c_str());\n    return false;\n  }\n  return true;\n}\n\nstd::string GetJavaExceptionInfo(JNIEnv *env, jthrowable java_throwable) {\n  ScopedLocalJavaRef<jclass> throwable_clazz(\n      env, env->FindClass(\"java/lang/Throwable\"));\n  jmethodID throwable_printstacktrace = env->GetMethodID(\n      throwable_clazz.Get(), \"printStackTrace\", \"(Ljava/io/PrintStream;)V\");\n\n  // Create an instance of ByteArrayOutputStream.\n  ScopedLocalJavaRef<jclass> bytearray_output_stream_clazz(\n      env, env->FindClass(\"java/io/ByteArrayOutputStream\"));\n  jmethodID bytearray_output_stream_constructor =\n      env->GetMethodID(bytearray_output_stream_clazz.Get(), \"<init>\", \"()V\");\n  jmethodID bytearray_output_stream_tostring = env->GetMethodID(\n      bytearray_output_stream_clazz.Get(), \"toString\", \"()Ljava/lang/String;\");\n  ScopedLocalJavaRef<jobject> bytearray_output_stream(\n      env, env->NewObject(bytearray_output_stream_clazz.Get(),\n                          bytearray_output_stream_constructor));\n\n  // Create an instance of PrintStream.\n  ScopedLocalJavaRef<jclass> printstream_clazz(\n      env, env->FindClass(\"java/io/PrintStream\"));\n  jmethodID printstream_constructor = env->GetMethodID(\n      printstream_clazz.Get(), \"<init>\", \"(Ljava/io/OutputStream;)V\");\n  ScopedLocalJavaRef<jobject> printstream(\n      env, env->NewObject(printstream_clazz.Get(), printstream_constructor,\n                          bytearray_output_stream.Get()));\n\n  // Call Throwable.printStackTrace(PrintStream)\n  env->CallVoidMethod(java_throwable, throwable_printstacktrace,\n                      printstream.Get());\n\n  // Call ByteArrayOutputStream.toString()\n  ScopedLocalJavaRef<jstring> exception_string(\n      env,\n      static_cast<jstring>(env->CallObjectMethod(\n          bytearray_output_stream.Get(), bytearray_output_stream_tostring)));\n  if (ClearException(env)) {\n    return \"Java OOM'd in exception handling, check logcat\";\n  }\n\n  return JNIConvertHelper::ConvertToString(env, exception_string.Get());\n}\n\nint GetAPILevel() {\n  char sdk_version_string[PROP_VALUE_MAX];\n  if (__system_property_get(\"ro.build.version.sdk\", sdk_version_string)) {\n    return atoi(sdk_version_string);\n  } else {\n    return -1;\n  }\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/platform/android/scoped_java_ref.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n\n#include <vector>\n\n#include \"base/include/platform/android/jni_utils.h\"\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nstatic constexpr int32_t kDefaultLocalFrameCapacity = 16;\n\nJavaRef<jobject>::JavaRef() = default;\n\nJavaRef<jobject>::JavaRef(JNIEnv* env, jobject obj) : obj_(obj) {}\n\n// NOLINTNEXTLINE\nJNIEnv* JavaRef<jobject>::ResetNewLocalRef(JNIEnv* env, jobject obj) {\n  if (!env) {\n    env = AttachCurrentThread();\n  }\n  if (obj) {\n    obj = env->NewLocalRef(obj);  // NOLINT\n  }\n\n  if (obj_) env->DeleteLocalRef(obj_);\n  obj_ = obj;\n\n  return env;\n}\n\nvoid JavaRef<jobject>::ReleaseLocalRef(JNIEnv* env) {\n  if (!obj_) {\n    return;\n  }\n  if (!env) {\n    env = AttachCurrentThread();\n  }\n  env->DeleteLocalRef(obj_);\n  obj_ = nullptr;\n}\n\nvoid JavaRef<jobject>::ResetNewGlobalRef(JNIEnv* env, jobject obj) {  // NOLINT\n  if (!env) {\n    env = AttachCurrentThread();\n  }\n\n  if (obj) obj = env->NewGlobalRef(obj);  // NOLINT\n  if (obj_) env->DeleteGlobalRef(obj_);   // NOLINT\n  obj_ = obj;\n}\n\nvoid JavaRef<jobject>::ReleaseGlobalRef(JNIEnv* env) {\n  if (obj_ == nullptr) {\n    return;\n  }\n  if (!env) {\n    env = AttachCurrentThread();\n  }\n\n  if (!env) {\n    // Oppo Android 5.1, JNIEnv is null when destruct global JavaRef in thread\n    return;\n  }\n\n  env->DeleteGlobalRef(obj_);  // NOLINT\n  obj_ = nullptr;\n}\n\n// NOLINTNEXTLINE\nvoid JavaRef<jobject>::ResetNewWeakGlobalRef(JNIEnv* env, jobject obj) {\n  if (!env) {\n    env = AttachCurrentThread();\n  }\n  if (obj) obj = env->NewWeakGlobalRef(obj);  // NOLINT\n  if (obj_) env->DeleteWeakGlobalRef(obj_);   // NOLINT\n  obj_ = obj;\n}\n\nvoid JavaRef<jobject>::ReleaseWeakGlobalRef(JNIEnv* env) {\n  if (obj_ == nullptr) {\n    return;\n  }\n  if (!env) {\n    env = AttachCurrentThread();\n  }\n  env->DeleteWeakGlobalRef(obj_);  // NOLINT\n  obj_ = nullptr;\n}\n\nScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env) : env_(env) {\n  env_->PushLocalFrame(kDefaultLocalFrameCapacity);\n}\n\nScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env, int capacity)\n    : env_(env) {\n  env_->PushLocalFrame(capacity);\n}\n\nScopedJavaLocalFrame::~ScopedJavaLocalFrame() { env_->PopLocalFrame(nullptr); }\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/platform/harmony/harmony_vsync_manager.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/platform/harmony/harmony_vsync_manager.h\"\n\n#include <native_vsync/native_vsync.h>\n\n#include \"base/include/log/logging.h\"\n#include \"base/src/base_trace/base_trace_event_def.h\"\n#include \"base/src/base_trace/trace_event.h\"\n\n// temporarily workaround to compile without logging\n#undef LOGE\n#undef DCHECK\n#define LOGE(...)\n#define DCHECK(...)\n\nnamespace lynx {\nnamespace base {\n\nnamespace {\n\nconst char* kLynxSyncName = \"lynx_vsync_connect\";\n\n}  // namespace\n\nHarmonyVsyncManager& HarmonyVsyncManager::GetInstance() {\n  static HarmonyVsyncManager manager;\n  return manager;\n}\n\nHarmonyVsyncManager::HarmonyVsyncManager() {\n  vsync_handle_ = OH_NativeVSync_Create(kLynxSyncName, strlen(kLynxSyncName));\n  DCHECK(vsync_handle_);\n}\n\nHarmonyVsyncManager::~HarmonyVsyncManager() {\n  OH_NativeVSync_Destroy(vsync_handle_);\n}\n\nvoid HarmonyVsyncManager::RequestVSync(const VSyncCallback& callback) {\n  bool requested = false;\n  {\n    std::lock_guard<std::mutex> l(mutex_);\n    callbacks_.push_back(callback);\n    requested = requested_;\n    requested_ = true;\n  }\n  if (!requested) {\n    OH_NativeVSync* handle = vsync_handle_;\n    BASE_TRACE_EVENT(LYNX_BASE_TRACE_CATEGORY,\n                     \"HarmonyVsyncManager::RequestVSync\");\n    int32_t ret = 0;\n    if (0 != (ret = OH_NativeVSync_RequestFrame(handle, &OnVsyncFromHarmony,\n                                                this))) {\n      // request vsync failed.\n      LOGE(\"RequestVSync...failed:\" << ret);\n      std::lock_guard<std::mutex> l(mutex_);\n      requested_ = false;\n    }\n  }\n}\n\nvoid HarmonyVsyncManager::OnVsyncFromHarmony(long long timestamp, void* data) {\n  HarmonyVsyncManager* self = reinterpret_cast<HarmonyVsyncManager*>(data);\n  std::vector<VSyncCallback> callbacks;\n  // make callbacks to be thread-safed.\n  {\n    std::lock_guard<std::mutex> l(self->mutex_);\n    self->requested_ = false;\n    callbacks = self->callbacks_;\n    self->callbacks_.clear();\n  }\n  for (auto& callback : callbacks) {\n    callback(timestamp);\n  }\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/platform/harmony/napi_util.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/platform/harmony/napi_util.h\"\n\n#include <node_api.h>\n\n#include <algorithm>\n#include <cstdarg>\n#include <cstring>\n#include <string>\n#include <unordered_map>\n#include <variant>\n\n#include \"base/include/no_destructor.h\"\n\n// temporarily workaround to compile without logging\n#undef LOGE\n#undef DCHECK\n#define LOGE(...)\n#define DCHECK(...)\nnamespace lynx {\nnamespace base {\n\nnamespace {\n\n[[maybe_unused]] static std::string NapiGetLastError(napi_env env,\n                                                     napi_status status) {\n  std::string ret(\"Napi Error:\");\n  ret += status;\n\n  const napi_extended_error_info* error_info;\n  napi_get_last_error_info(env, &error_info);\n  ret += error_info->error_message;\n  return ret;\n}\n\n}  // namespace\n\nbool NapiUtil::IsArrayBuffer(napi_env env, napi_value value) {\n  napi_status status;\n  bool result = false;\n\n  // Check if the value is an ArrayBuffer\n  status = napi_is_arraybuffer(env, value, &result);\n  if (status != napi_ok) {\n    LOGE(\"napi_is_arraybuffer: failed:\" << status);\n    return false;\n  }\n\n  return result;\n}\n\nint32_t NapiUtil::ConvertToInt32(napi_env env, napi_value obj) {\n  DCHECK(NapiIsType(env, obj, napi_number));\n  int32_t ret;\n  napi_status status = napi_get_value_int32(env, obj, &ret);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get int32:\" << status);\n  }\n  return ret;\n}\n\nuint32_t NapiUtil::ConvertToUInt32(napi_env env, napi_value obj) {\n  DCHECK(NapiIsType(env, obj, napi_number));\n  uint32_t ret;\n  napi_status status = napi_get_value_uint32(env, obj, &ret);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get uint32:\" << status);\n  }\n  return ret;\n}\n\nint64_t NapiUtil::ConvertToInt64(napi_env env, napi_value obj) {\n  DCHECK(NapiIsType(env, obj, napi_number));\n  int64_t ret;\n  napi_status status = napi_get_value_int64(env, obj, &ret);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get int64:\" << status);\n  }\n  return ret;\n}\n\nint64_t NapiUtil::ConvertToBigInt64(napi_env env, napi_value obj) {\n  int64_t ret;\n  bool lossless;\n  napi_status status = napi_get_value_bigint_int64(env, obj, &ret, &lossless);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get bigint:\" << status);\n  }\n  return ret;\n}\n\nuint64_t NapiUtil::ConvertToBigUInt64(napi_env env, napi_value obj) {\n  uint64_t ret;\n  bool lossless;\n  napi_status status = napi_get_value_bigint_uint64(env, obj, &ret, &lossless);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get bigint:\" << status);\n  }\n  return ret;\n}\n\nfloat NapiUtil::ConvertToFloat(napi_env env, napi_value obj) {\n  DCHECK(NapiIsType(env, obj, napi_number));\n  double ret;\n  napi_status status = napi_get_value_double(env, obj, &ret);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get float:\" << status);\n  }\n  return ret;\n}\n\ndouble NapiUtil::ConvertToDouble(napi_env env, napi_value obj) {\n  DCHECK(NapiIsType(env, obj, napi_number));\n  double ret;\n  napi_status status = napi_get_value_double(env, obj, &ret);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get double:\" << status);\n  }\n  return ret;\n}\n\nbool NapiUtil::ConvertToBoolean(napi_env env, napi_value obj) {\n  DCHECK(NapiIsType(env, obj, napi_boolean));\n  bool ret;\n  napi_status status = napi_get_value_bool(env, obj, &ret);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get bool:\" << status);\n  }\n  return ret;\n}\n\nstd::string NapiUtil::ConvertToShortString(napi_env env, napi_value arg) {\n  DCHECK(NapiIsType(env, arg, napi_string));\n  constexpr int BUFFER_SIZE = 128;\n  char buf[BUFFER_SIZE] = {0};\n  size_t size;\n  napi_status status =\n      napi_get_value_string_utf8(env, arg, buf, BUFFER_SIZE, &size);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get string:\" << status);\n    return \"\";\n  }\n  return std::string(buf);\n}\n\nstd::string NapiUtil::ConvertToString(napi_env env, napi_value arg) {\n  DCHECK(NapiIsType(env, arg, napi_string));\n  size_t str_size;\n  napi_status status =\n      napi_get_value_string_utf8(env, arg, nullptr, 0, &str_size);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get size:\" << status);\n    return \"\";\n  }\n  auto buf = std::make_unique<char[]>(str_size + 1);\n  status =\n      napi_get_value_string_utf8(env, arg, buf.get(), str_size + 1, &str_size);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get string:\" << status);\n    return \"\";\n  }\n  return std::string(buf.get(), str_size);\n}\n\nbool NapiUtil::ConvertToArrayString(napi_env env, napi_value arg,\n                                    std::vector<std::string>& array_string) {\n  DCHECK(IsArray(env, arg));\n  uint32_t array_length;\n  napi_status status = napi_get_array_length(env, arg, &array_length);\n  if (status != napi_ok) {\n    LOGE(\"Fail to get array length:\" << status);\n    return false;\n  }\n  for (uint32_t i = 0; i < array_length; i++) {\n    napi_value element;\n    napi_get_element(env, arg, i, &element);\n    std::string str = ConvertToString(env, element);\n    array_string.push_back(str);\n  }\n  return true;\n}\n\nbool NapiUtil::ConvertToArrayBuffer(napi_env env, napi_value arg,\n                                    std::vector<uint8_t>& buffer) {\n  DCHECK(IsArrayBuffer(env, arg));\n  size_t length;\n  void* data;\n  napi_status status = napi_get_arraybuffer_info(env, arg, &data, &length);\n  if (status != napi_ok || data == nullptr) {\n    LOGE(\"Fail to get array buffer \" << NapiGetLastError(env, status));\n    return false;\n  }\n  buffer.resize(length);\n  memcpy(buffer.data(), data, length);\n  return true;\n}\n\nbool NapiUtil::ConvertToArrayBuffer(napi_env env, napi_value arg,\n                                    std::unique_ptr<uint8_t[]>& buffer,\n                                    size_t& length) {\n  DCHECK(IsArrayBuffer(env, arg));\n  void* data;\n  napi_status status = napi_get_arraybuffer_info(env, arg, &data, &length);\n  if (status != napi_ok || data == nullptr) {\n    LOGE(\"Fail to get array buffer \" << NapiGetLastError(env, status));\n    return false;\n  }\n\n  buffer = std::make_unique<uint8_t[]>(length);\n  if (buffer == nullptr) {\n    LOGE(\"Fail to alloc memory\");\n    return false;\n  }\n\n  memcpy(buffer.get(), data, length);\n  return true;\n}\n\nbool NapiUtil::ConvertToArray(napi_env env, napi_value arg,\n                              std::vector<napi_value>& array) {\n  DCHECK(IsArray(env, arg));\n  uint32_t len;\n  napi_status status = napi_get_array_length(env, arg, &len);\n  if (status != napi_ok) {\n    return false;\n  }\n  for (uint32_t i = 0; i < len; i++) {\n    napi_value item;\n    napi_get_element(env, arg, i, &item);\n    array.push_back(item);\n  }\n  return true;\n}\n\nbool NapiUtil::ConvertToArray(napi_env env, napi_value arg, napi_value array[],\n                              uint32_t size) {\n  DCHECK(IsArray(env, arg));\n  uint32_t len;\n  napi_status status = napi_get_array_length(env, arg, &len);\n  if (status != napi_ok || len != size) {\n    return false;\n  }\n  for (uint32_t i = 0; i < len; i++) {\n    napi_value item;\n    napi_get_element(env, arg, i, &item);\n    array[i] = item;\n  }\n  return true;\n}\n\nbool NapiUtil::ConvertToMap(napi_env env, napi_value arg,\n                            std::unordered_map<std::string, std::string>& map) {\n  DCHECK(NapiIsType(env, arg, napi_object));\n  napi_value property_names;\n  napi_status status = napi_get_property_names(env, arg, &property_names);\n  if (status != napi_ok) {\n    return false;\n  }\n\n  uint32_t length;\n  status = napi_get_array_length(env, property_names, &length);\n  if (status != napi_ok) {\n    return false;\n  }\n\n  for (uint32_t i = 0; i < length; i++) {\n    // get key\n    napi_value property_name;\n    status = napi_get_element(env, property_names, i, &property_name);\n    if (status != napi_ok) {\n      continue;\n    }\n    std::string key = NapiUtil::ConvertToString(env, property_name);\n    if (key.empty()) {\n      continue;\n    }\n\n    // get value\n    napi_value property;\n    status = napi_get_named_property(env, arg, key.c_str(), &property);\n    if (status != napi_ok) {\n      continue;\n    }\n    std::string value = NapiUtil::ConvertToString(env, property);\n\n    map.emplace(std::move(key), std::move(value));\n  }\n  return true;\n}\n\nbool NapiUtil::NapiIsType(napi_env env, napi_value value, napi_valuetype type) {\n  napi_status status;\n  napi_valuetype arg_type;\n  status = napi_typeof(env, value, &arg_type);\n  return status == napi_ok && type == arg_type;\n}\n\nbool NapiUtil::NapiIsAnyType(napi_env env, napi_value value, ...) {\n  napi_status status;\n  napi_valuetype arg_type;\n  status = napi_typeof(env, value, &arg_type);\n\n  if (status != napi_ok) {\n    return false;\n  }\n\n  bool matched = false;\n  {\n    va_list types;\n    va_start(types, value);\n    napi_valuetype cur;\n    while (!matched) {\n      cur = va_arg(types, napi_valuetype);\n      matched = (cur == arg_type);\n    }\n    va_end(types);\n  }\n  return matched;\n}\n\nbool NapiUtil::IsArray(napi_env env, napi_value value) {\n  bool ret;\n  napi_is_array(env, value, &ret);\n  return ret;\n}\n\nnapi_value NapiUtil::CreateArrayBuffer(napi_env env, void* input_data,\n                                       size_t data_size) {\n  void* data = nullptr;\n  napi_value array_buffer = nullptr;\n  if (input_data == nullptr) {\n    return nullptr;\n  }\n  napi_status status =\n      napi_create_arraybuffer(env, data_size, &data, &array_buffer);\n  NAPI_THROW_IF_FAILED_NULL(env, status, \"napi_create_arraybuffer failed\");\n\n  memcpy(data, input_data, data_size);\n  return array_buffer;\n}\n\nnapi_value NapiUtil::CreateMap(\n    napi_env env, const std::unordered_map<std::string, std::string> map) {\n  napi_value result;\n  napi_create_object(env, &result);\n  for (const auto& pair : map) {\n    napi_value key, value;\n    napi_create_string_utf8(env, pair.first.c_str(), pair.first.length(), &key);\n    napi_create_string_utf8(env, pair.second.c_str(), pair.second.length(),\n                            &value);\n    napi_set_property(env, result, key, value);\n  }\n  return result;\n}\n\nnapi_value NapiUtil::CreateMap(napi_env env,\n                               const std::unordered_map<int32_t, double> map) {\n  napi_value result;\n  napi_create_object(env, &result);\n  for (const auto& pair : map) {\n    napi_value key, value;\n    napi_create_int32(env, pair.first, &key);\n    napi_create_double(env, pair.second, &value);\n    napi_set_property(env, result, key, value);\n  }\n  return result;\n}\n\nnapi_value NapiUtil::CreateMap(\n    napi_env env, const std::unordered_map<int32_t, std::string> map) {\n  napi_value result;\n  napi_create_object(env, &result);\n  for (const auto& pair : map) {\n    napi_value key, value;\n    napi_create_int32(env, pair.first, &key);\n    napi_create_string_utf8(env, pair.second.c_str(), pair.second.length(),\n                            &value);\n    napi_set_property(env, result, key, value);\n  }\n  return result;\n}\n\nnapi_value NapiUtil::CreatePtrArray(napi_env env, uint64_t ptr) {\n  napi_value result;\n  napi_create_array_with_length(env, 2, &result);\n  uint32_t ptr_high = static_cast<uint32_t>(ptr >> 32);\n  uint32_t ptr_low = static_cast<uint32_t>(ptr & 0xffffffff);\n  napi_set_element(env, result, 0, CreateUint32(env, ptr_high));\n  napi_set_element(env, result, 1, CreateUint32(env, ptr_low));\n  return result;\n}\n\nnapi_value NapiUtil::CreateUint32(napi_env env, uint32_t num) {\n  napi_value result;\n  napi_create_uint32(env, num, &result);\n  return result;\n}\n\nnapi_value NapiUtil::CreateInt32(napi_env env, int32_t num) {\n  napi_value result;\n  napi_create_int32(env, num, &result);\n  return result;\n}\n\nnapi_status NapiUtil::SetPropToJSMap(napi_env env, napi_value js_map,\n                                     const std::string& key,\n                                     const std::string& value) {\n  napi_value js_value;\n  napi_create_string_utf8(env, value.c_str(), value.length(), &js_value);\n  return napi_set_named_property(env, js_map, key.c_str(), js_value);\n}\n\nnapi_status NapiUtil::SetPropToJSMap(napi_env env, napi_value js_map,\n                                     const std::string& key, int32_t value) {\n  return napi_set_named_property(env, js_map, key.c_str(),\n                                 CreateInt32(env, value));\n}\n\nnapi_status NapiUtil::SetPropToJSMap(napi_env env, napi_value js_map,\n                                     const std::string& key, double value) {\n  napi_value js_value;\n  napi_create_double(env, value, &js_value);\n  return napi_set_named_property(env, js_map, key.c_str(), js_value);\n}\n\nuint64_t NapiUtil::ConvertToPtr(napi_env env, napi_value arr) {\n  napi_value item;\n  napi_get_element(env, arr, 0, &item);\n  uint32_t ptr_high = ConvertToUInt32(env, item);\n  napi_get_element(env, arr, 1, &item);\n  uint32_t ptr_low = ConvertToUInt32(env, item);\n  return (static_cast<uint64_t>(ptr_high) << 32) + ptr_low;\n}\n\nnapi_status NapiUtil::InvokeJsMethod(napi_env env, napi_ref ref_napi_obj,\n                                     napi_ref ref_napi_method, size_t argc,\n                                     const napi_value* argv,\n                                     napi_value* result) {\n  napi_value napi_obj;\n  napi_get_reference_value(env, ref_napi_obj, &napi_obj);\n  napi_value napi_method;\n  napi_get_reference_value(env, ref_napi_method, &napi_method);\n  if (!napi_obj || !napi_method) {\n    return napi_invalid_arg;\n  }\n  // call js func\n  return napi_call_function(env, napi_obj, napi_method, argc, argv, result);\n}\n\nnapi_status NapiUtil::AsyncInvokeJsMethod(napi_env env, napi_ref ref_napi_obj,\n                                          const char* method_name, size_t argc,\n                                          const napi_value* argv) {\n  napi_value work_name;\n  napi_create_string_utf8(env, \"NapiUtil::AsyncInvokeJsMethod\",\n                          NAPI_AUTO_LENGTH, &work_name);\n\n  auto* context = new NapiAsyncContext();\n  context->env = env;\n  napi_value value;\n  napi_status status = napi_get_reference_value(env, ref_napi_obj, &value);\n  if (status != napi_ok || value == nullptr) {\n    LOGE(\"napi_get_reference_value fail: \" << method_name);\n    return status;\n  }\n  // Create new weak ref for async work\n  napi_create_reference(env, value, 0, &context->ref_napi_obj);\n  context->method_name = method_name;\n  context->args.resize(argc);\n  for (size_t i = 0; i < argc; ++i) {\n    napi_ref ref;\n    napi_create_reference(env, argv[i], 1, &ref);\n    context->args[i] = ref;\n  }\n  napi_create_async_work(\n      env, nullptr, work_name, [](napi_env env, void* data) {},\n      [](napi_env env, napi_status status, void* data) {\n        NapiHandleScope scope(env);\n        auto* context = reinterpret_cast<NapiAsyncContext*>(data);\n        std::vector<napi_value> argv(context->args.size());\n        std::transform(context->args.begin(), context->args.end(), argv.begin(),\n                       [context](napi_ref ref) {\n                         napi_value value;\n                         napi_get_reference_value(context->env, ref, &value);\n                         napi_delete_reference(context->env, ref);\n                         return value;\n                       });\n        InvokeJsMethod(env, context->ref_napi_obj, context->method_name.c_str(),\n                       context->args.size(), argv.data(), nullptr);\n        napi_delete_reference(env, context->ref_napi_obj);\n        napi_delete_async_work(env, context->async_work);\n        delete context;\n      },\n      reinterpret_cast<void*>(context), &context->async_work);\n  return napi_queue_async_work(env, context->async_work);\n}\n\nnapi_status NapiUtil::InvokeJsMethod(napi_env env, napi_ref ref_napi_obj,\n                                     const char* method_name, size_t argc,\n                                     const napi_value* argv,\n                                     napi_value* result) {\n  napi_value napi_obj = nullptr;\n  napi_status status = napi_get_reference_value(env, ref_napi_obj, &napi_obj);\n  if (status != napi_ok || napi_obj == nullptr) {\n    LOGE(\"napi_get_reference_value fail: \" << method_name);\n    return status;\n  }\n  return InvokeJsMethod(env, napi_obj, method_name, argc, argv, result);\n}\n\nnapi_status NapiUtil::InvokeJsMethod(napi_env env, napi_value napi_obj,\n                                     const char* method_name, size_t argc,\n                                     const napi_value* argv,\n                                     napi_value* result) {\n  static const char* const kGetPropertyFailed =\n      \"napi_get_named_property fail: %s\";\n  static const char* const kCallFunctionFailed = \"napi_call_function fail: %s\";\n  napi_value fn;\n  napi_status status = napi_get_named_property(env, napi_obj, method_name, &fn);\n  NAPI_THROW_IF_FAILED_STATUS(env, status, kGetPropertyFailed, method_name);\n  status = napi_call_function(env, napi_obj, fn, argc, argv, result);\n  NAPI_THROW_IF_FAILED_STATUS(env, status, kCallFunctionFailed, method_name);\n  return napi_ok;\n}\n\nconst std::string& NapiUtil::StatusToString(napi_status status) {\n  const static base::NoDestructor<std::unordered_map<napi_status, std::string>>\n      status_map{{\n          {napi_ok, \"napi_ok\"},\n          {napi_invalid_arg, \"napi_invalid_arg\"},\n          {napi_object_expected, \"napi_object_expected\"},\n          {napi_string_expected, \"napi_string_expected\"},\n          {napi_name_expected, \"napi_name_expected\"},\n          {napi_function_expected, \"napi_function_expected\"},\n          {napi_number_expected, \"napi_number_expected\"},\n          {napi_boolean_expected, \"napi_boolean_expected\"},\n          {napi_array_expected, \"napi_array_expected\"},\n          {napi_generic_failure, \"napi_generic_failure\"},\n          {napi_pending_exception, \"napi_pending_exception\"},\n          {napi_cancelled, \"napi_cancelled\"},\n          {napi_escape_called_twice, \"napi_escape_called_twice\"},\n          {napi_handle_scope_mismatch, \"napi_handle_scope_mismatch\"},\n          {napi_callback_scope_mismatch, \"napi_callback_scope_mismatch\"},\n          {napi_queue_full, \"napi_queue_full\"},\n          {napi_closing, \"napi_closing\"},\n          {napi_bigint_expected, \"napi_bigint_expected\"},\n          {napi_date_expected, \"napi_date_expected\"},\n          {napi_arraybuffer_expected, \"napi_arraybuffer_expected\"},\n          {napi_detachable_arraybuffer_expected,\n           \"napi_detachable_arraybuffer_expected\"},\n          {napi_would_deadlock, \"napi_would_deadlock\"},\n      }};\n  const static base::NoDestructor<std::string> kEmpty;\n  auto it = status_map->find(status);\n  return it != status_map->end() ? it->second : *kEmpty;\n}\n\nnapi_value NapiUtil::GetReferenceNapiValue(const napi_env env,\n                                           const napi_ref reference) {\n  napi_value ret = nullptr;\n  if (const napi_status status = napi_get_reference_value(env, reference, &ret);\n      status != napi_ok) {\n    return nullptr;\n  }\n  return ret;\n}\n\n}  // end namespace base\n}  // end namespace lynx\n"
  },
  {
    "path": "base/src/sorted_for_each_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/sorted_for_each.h\"\n\n#include <unordered_map>\n#include <vector>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx::base::testing {\nnamespace {\n\ntemplate <class Key, class Value>\nclass FooMap : public std::unordered_map<Key, Value> {\n public:\n  FooMap() : std::unordered_map<Key, Value>() {}\n  FooMap(std::initializer_list<std::pair<const Key, Value>> value)\n      : std::unordered_map<Key, Value>(std::move(value)) {}\n\n private:\n  // making FooMap move only\n  std::unique_ptr<int> ptr_ = std::make_unique<int>(1);\n};\n}  // namespace\n\nTEST(SortedForEachTest, SortedTest) {\n  std::vector<int> before{6, 3, 1, 7, 1}, actual{}, expected{};\n\n  auto f = [&actual](const auto& i) { actual.push_back(i); };\n  base::sorted_for_each(before.begin(), before.end(), f);\n\n  expected = {1, 1, 3, 6, 7};\n  ASSERT_EQ(expected, actual);\n}\n\nTEST(SortedForEachTest, MoveOnlyTest) {\n  // should be able to deal with move only containers\n  FooMap<int, std::unique_ptr<int>> map{};\n  map.insert({2, std::make_unique<int>(2)});\n  map.insert({3, std::make_unique<int>(3)});\n  map.insert({1, std::make_unique<int>(1)});\n\n  std::vector<int> actual{}, expected{};\n  base::sorted_for_each(\n      map.begin(), map.end(),\n      [&actual](const auto& it) { actual.push_back(it.first); },\n      // custom compare\n      [](const auto& a, const auto& b) { return a.first < b.first; });\n\n  expected = {1, 2, 3};\n\n  ASSERT_EQ(expected, actual);\n\n  actual = {}, expected = {3, 2, 1};\n  base::sorted_for_each(\n      map.cbegin(), map.cend(),\n      [&actual](const auto& it) { actual.push_back(it.first); },\n      // custom compare\n      [](const auto& a, const auto& b) { return a.first >= b.first; });\n\n  ASSERT_EQ(expected, actual);\n}\n\nTEST(SortedForEachTest, ConstIterTest) {\n  FooMap<int, int> map{{1, 1}, {3, 3}, {2, 2}, {8, 8}, {6, 6}};\n\n  std::vector<int> actual{}, expected{};\n  base::sorted_for_each(map.cbegin(), map.cend(), [&actual](const auto& it) {\n    actual.push_back(it.first);\n  });\n\n  expected = {1, 2, 3, 6, 8};\n\n  ASSERT_EQ(actual, expected);\n}\n\n}  // namespace lynx::base::testing\n"
  },
  {
    "path": "base/src/string/quickjs_dtoa.c",
    "content": "/*\n * QuickJS Javascript Engine\n *\n * Copyright (c) 2017-2021 Fabrice Bellard\n * Copyright (c) 2017-2021 Charlie Gordon\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n#include \"base/include/string/quickjs_dtoa.h\"\n\n#include <assert.h>\n#include <fenv.h>\n#include <inttypes.h>\n#include <math.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#if defined(__APPLE__)\n#include <malloc/malloc.h>\n#elif defined(__linux__)\n#include <malloc.h>\n#elif defined(__FreeBSD__)\n#include <malloc_np.h>\n#endif\n\n#define BOOL int\n#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1)\n\n/* 2 <= base <= 36 */\nstatic char* i64toa(char* buf_end, int64_t n, unsigned int base) {\n  char* q = buf_end;\n  int digit, is_neg;\n\n  is_neg = 0;\n  if (n < 0) {\n    is_neg = 1;\n    n = -n;\n  }\n  *--q = '\\0';\n  do {\n    digit = (uint64_t)n % base;\n    n = (uint64_t)n / base;\n    if (digit < 10)\n      digit += '0';\n    else\n      digit += 'a' - 10;\n    *--q = digit;\n  } while (n != 0);\n  if (is_neg) *--q = '-';\n  return q;\n}\n\n/* buf1 contains the printf result */\nstatic void js_ecvt1(double d, int n_digits, int* decpt, int* sign, char* buf,\n                     int rounding_mode, char* buf1, int buf1_size) {\n  if (rounding_mode != FE_TONEAREST) fesetround(rounding_mode);\n  snprintf(buf1, buf1_size, \"%+.*e\", n_digits - 1, d);\n  if (rounding_mode != FE_TONEAREST) fesetround(FE_TONEAREST);\n  *sign = (buf1[0] == '-');\n  /* mantissa */\n  buf[0] = buf1[1];\n  if (n_digits > 1) memcpy(buf + 1, buf1 + 3, n_digits - 1);\n  buf[n_digits] = '\\0';\n  /* exponent */\n  *decpt = atoi(buf1 + n_digits + 2 + (n_digits > 1)) + 1;\n}\n\n/* maximum buffer size for js_dtoa */\n#define JS_DTOA_BUF_SIZE 128\n\n/* needed because ecvt usually limits the number of digits to\n   17. Return the number of digits. */\nstatic int js_ecvt(double d, int n_digits, int* decpt, int* sign, char* buf,\n                   BOOL is_fixed) {\n  int rounding_mode;\n  char buf_tmp[JS_DTOA_BUF_SIZE];\n\n  if (!is_fixed) {\n    unsigned int n_digits_min, n_digits_max;\n    /* find the minimum amount of digits (XXX: inefficient but simple) */\n    n_digits_min = 1;\n    n_digits_max = 17;\n    while (n_digits_min < n_digits_max) {\n      n_digits = (n_digits_min + n_digits_max) / 2;\n      js_ecvt1(d, n_digits, decpt, sign, buf, FE_TONEAREST, buf_tmp,\n               sizeof(buf_tmp));\n      if (strtod(buf_tmp, NULL) == d) {\n        /* no need to keep the trailing zeros */\n        while (n_digits >= 2 && buf[n_digits - 1] == '0') n_digits--;\n        n_digits_max = n_digits;\n      } else {\n        n_digits_min = n_digits + 1;\n      }\n    }\n    n_digits = n_digits_max;\n    rounding_mode = FE_TONEAREST;\n  } else {\n    rounding_mode = FE_TONEAREST;\n  }\n  js_ecvt1(d, n_digits, decpt, sign, buf, rounding_mode, buf_tmp,\n           sizeof(buf_tmp));\n  return n_digits;\n}\n\n/* radix != 10 is only supported with flags = JS_DTOA_VAR_FORMAT */\n/* use as many digits as necessary */\n#define JS_DTOA_VAR_FORMAT (0 << 0)\n\nstatic void js_dtoa1(char* buf, double d, int radix, int n_digits, int flags) {\n  char* q;\n\n  if (!isfinite(d)) {\n    if (isnan(d)) {\n      strcpy(buf, \"NaN\");\n    } else {\n      q = buf;\n      if (d < 0) *q++ = '-';\n      strcpy(q, \"Infinity\");\n    }\n  } else if (flags == JS_DTOA_VAR_FORMAT) {\n    int64_t i64;\n    char buf1[70], *ptr;\n    i64 = (int64_t)d;\n    if (d != i64 || i64 > MAX_SAFE_INTEGER || i64 < -MAX_SAFE_INTEGER)\n      goto generic_conv;\n    /* fast path for integers */\n    ptr = i64toa(buf1 + sizeof(buf1), i64, radix);\n    strcpy(buf, ptr);\n  } else {\n    if (d == 0.0) d = 0.0; /* convert -0 to 0 */\n    {\n      char buf1[JS_DTOA_BUF_SIZE];\n      int sign, decpt, k, n, i, p, n_max;\n      BOOL is_fixed;\n    generic_conv:\n      is_fixed = 0;\n      if (is_fixed) {\n        n_max = n_digits;\n      } else {\n        n_max = 21;\n      }\n      /* the number has k digits (k >= 1) */\n      k = js_ecvt(d, n_digits, &decpt, &sign, buf1, is_fixed);\n      n = decpt; /* d=10^(n-k)*(buf1) i.e. d= < x.yyyy 10^(n-1) */\n      q = buf;\n      if (sign) *q++ = '-';\n      if (n >= 1 && n <= n_max) {\n        if (k <= n) {\n          memcpy(q, buf1, k);\n          q += k;\n          for (i = 0; i < (n - k); i++) *q++ = '0';\n          *q = '\\0';\n        } else {\n          /* k > n */\n          memcpy(q, buf1, n);\n          q += n;\n          *q++ = '.';\n          for (i = 0; i < (k - n); i++) *q++ = buf1[n + i];\n          *q = '\\0';\n        }\n      } else if (n >= -5 && n <= 0) {\n        *q++ = '0';\n        *q++ = '.';\n        for (i = 0; i < -n; i++) *q++ = '0';\n        memcpy(q, buf1, k);\n        q += k;\n        *q = '\\0';\n      } else {\n        /* exponential notation */\n        *q++ = buf1[0];\n        if (k > 1) {\n          *q++ = '.';\n          for (i = 1; i < k; i++) *q++ = buf1[i];\n        }\n        *q++ = 'e';\n        p = n - 1;\n        if (p >= 0) *q++ = '+';\n        snprintf(q, sizeof(q), \"%d\", p);\n      }\n    }\n  }\n}\n\nvoid js_dtoa(char* buf, double val) {\n  js_dtoa1(buf, val, 10, 0, JS_DTOA_VAR_FORMAT);\n}\n"
  },
  {
    "path": "base/src/string/string_conversion_win.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/string/string_conversion_win.h\"\n\n#include <Windows.h>\n\nnamespace lynx {\nnamespace base {\n\nstd::string Utf8FromUtf16(const std::wstring& utf16_string) {\n  return Utf8FromUtf16(utf16_string.data(), utf16_string.length());\n}\n\nstd::string Utf8FromUtf16(const std::wstring_view& utf16_string) {\n  return Utf8FromUtf16(utf16_string.data(), utf16_string.length());\n}\n\nstd::string Utf8FromUtf16(const wchar_t* utf16_string, int32_t length) {\n  if (!utf16_string || length <= 0) {\n    return {};\n  }\n  int target_length =\n      ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, length,\n                            nullptr, 0, nullptr, nullptr);\n  if (target_length == 0) {\n    return {};\n  }\n  std::string utf8_string;\n  utf8_string.resize(target_length);\n  int converted_length = ::WideCharToMultiByte(\n      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, length,\n      const_cast<LPSTR>(utf8_string.c_str()), target_length, nullptr, nullptr);\n  if (converted_length == 0) {\n    return {};\n  }\n  return utf8_string;\n}\n\nstd::wstring Utf16FromUtf8(const std::string& utf8_string) {\n  return Utf16FromUtf8(utf8_string.data(),\n                       static_cast<int>(utf8_string.length()));\n}\n\nstd::wstring Utf16FromUtf8(const std::string_view& utf8_string) {\n  return Utf16FromUtf8(utf8_string.data(),\n                       static_cast<int>(utf8_string.length()));\n}\n\nstd::wstring Utf16FromUtf8(const char* utf8_string, int32_t length) {\n  if (!utf8_string || length <= 0) {\n    return {};\n  }\n\n  int target_length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,\n                                            utf8_string, length, nullptr, 0);\n  if (target_length == 0) {\n    return {};\n  }\n  std::wstring utf16_string;\n  utf16_string.resize(target_length);\n  int converted_length = ::MultiByteToWideChar(\n      CP_UTF8, MB_ERR_INVALID_CHARS, utf8_string, length,\n      const_cast<LPWSTR>(utf16_string.c_str()), target_length);\n  if (converted_length == 0) {\n    return {};\n  }\n  return utf16_string;\n}\n\nstd::string Utf8ToANSIOrOEM(const std::string& utf8_string) {\n  std::wstring utf16_str = Utf16FromUtf8(utf8_string);\n  if (utf16_str.empty()) {\n    return {};\n  }\n\n  UINT type = CP_ACP;\n  if (!::AreFileApisANSI()) {\n    type = CP_OEMCP;\n  }\n\n  int target_length = ::WideCharToMultiByte(\n      type, WC_NO_BEST_FIT_CHARS, utf16_str.c_str(),\n      static_cast<int>(utf16_str.length()), nullptr, 0, nullptr, nullptr);\n  if (target_length == 0) {\n    return {};\n  }\n\n  std::string ansi_string;\n  ansi_string.resize(target_length);\n  int converted_length = ::WideCharToMultiByte(\n      type, WC_NO_BEST_FIT_CHARS, utf16_str.data(),\n      static_cast<int>(utf16_str.length()),\n      const_cast<LPSTR>(ansi_string.c_str()), target_length, nullptr, nullptr);\n  if (converted_length == 0) {\n    return {};\n  }\n\n  return ansi_string;\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/string/string_number_convert.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/string/string_number_convert.h\"\n\n#include <ctype.h>\n#include <errno.h>\n#include <stdlib.h>\n\n#include <cmath>\n\nnamespace lynx {\nnamespace base {\n\nbool StringToDouble(const std::string& input, double& output,\n                    bool error_on_nan_or_inf) {\n  int old_error = errno;\n  errno = 0;\n  char* endptr = nullptr;\n  double d = strtod(input.c_str(), &endptr);\n  bool valid = (errno == 0 && !input.empty() &&\n                input.c_str() + input.length() == endptr && !isspace(input[0]));\n\n  if (errno == 0) errno = old_error;\n  if (valid) output = d;\n  if (error_on_nan_or_inf && (std::isnan(d) || std::isinf(d))) {\n    valid = false;\n  }\n  return valid;\n}\n\nbool StringToFloat(const std::string& input, float& output,\n                   bool error_on_nan_or_inf) {\n  int old_error = errno;\n  errno = 0;\n  char* endptr = nullptr;\n  float f = strtof(input.c_str(), &endptr);\n  bool valid = (errno == 0 && !input.empty() &&\n                input.c_str() + input.length() == endptr && !isspace(input[0]));\n  if (errno == 0) errno = old_error;\n  if (valid) output = f;\n  if (error_on_nan_or_inf && (std::isnan(f) || std::isinf(f))) {\n    valid = false;\n  }\n  return valid;\n}\n\nbool StringToInt(const std::string& input, int64_t& output, uint8_t base) {\n  int old_error = errno;\n  errno = 0;\n  char* endptr = nullptr;\n  int64_t i = strtoll(input.c_str(), &endptr, base);\n  bool valid = (errno == 0 && !input.empty() &&\n                input.c_str() + input.length() == endptr && !isspace(input[0]));\n  if (errno == 0) errno = old_error;\n  if (valid) output = i;\n  return valid;\n}\n\nbool StringToInt(const std::string& input, int* output, uint8_t base) {\n  int old_error = errno;\n  errno = 0;\n  char* endptr = nullptr;\n  int64_t i = strtoll(input.c_str(), &endptr, base);\n  bool valid = (errno == 0 && !input.empty() &&\n                input.c_str() + input.length() == endptr && !isspace(input[0]));\n  if (errno == 0) errno = old_error;\n  if (valid) *output = static_cast<int>(i);\n  return valid;\n}\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/string/string_number_convert_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/string/string_number_convert.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nTEST(StringNumberConvertTest, StringToDoubleTest) {\n  double num1 = 0;\n  EXPECT_TRUE(StringToDouble(\"123.456\", num1));\n  EXPECT_EQ(123.456, num1);\n\n  EXPECT_FALSE(StringToDouble(\"12c.456\", num1));\n  EXPECT_EQ(123.456, num1);\n}\n\nTEST(StringNumberConvertTest, StringToIntTest) {\n  int64_t num1 = 0;\n  EXPECT_FALSE(StringToInt(\"123.456\", num1, 10));\n\n  EXPECT_TRUE(StringToInt(\"123\", num1, 16));\n  EXPECT_EQ(291, num1);\n  EXPECT_TRUE(StringToInt(\"123\", num1, 10));\n  EXPECT_EQ(123, num1);\n  EXPECT_TRUE(StringToInt(\"123\", num1, 8));\n  EXPECT_EQ(83, num1);\n  EXPECT_FALSE(StringToInt(\"123\", num1, 2));\n  EXPECT_TRUE(StringToInt(\"10\", num1, 2));\n  EXPECT_EQ(2, num1);\n\n  EXPECT_FALSE(StringToInt(\"123abc\", num1, 10));\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/string/string_utils.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/string/string_utils.h\"\n\n#include <algorithm>\n#include <array>\n#include <cinttypes>\n#include <cstdarg>\n#include <cstdlib>\n#include <cstring>\n#include <sstream>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n\n// TODO(zhengsenyao): Uncomment LOG code when LOG available\n// #include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace base {\nconst char kWhitespaceASCII[] = {0x09,  // CHARACTER TABULATION\n                                 0x0A,  // LINE FEED (LF)\n                                 0x0B,  // LINE TABULATION\n                                 0x0C,  // FORM FEED (FF)\n                                 0x0D,  // CARRIAGE RETURN (CR)\n                                 0x20,  // SPACE\n                                 0};\n\nconst char* SplitString(\n    std::string_view target, char separator, bool trim,\n    const std::function<bool(const char*, size_t, int)>& callback) {\n  if (target.empty()) {\n    return target.data();\n  }\n\n  auto is_trimmed = [](char c) -> bool { return (c == ' ' || c == '\\t'); };\n\n  int index = 0;\n  const char* s = target.data();\n  const char* end = &target.back() + 1;\n  if (trim) {\n    while (s != end && is_trimmed(*s)) {\n      ++s;\n    }\n  }\n  const char* e = s;\n  while (e != end) {\n    e = s;\n    while (e != end && *e != separator) {\n      ++e;\n    }\n    if (e - s > 0) {\n      long sc = 0;\n      if (trim) {\n        const char* b = e - 1;\n        while (b >= s && is_trimmed(*b)) {\n          --b;\n          sc++;\n        }\n      }\n      if (e - s - sc > 0) {\n        if (!callback(s, e - s - sc, index++)) {\n          return e;\n        }\n      }\n    }\n\n    if (e == end) {\n      break;\n    }\n\n    s = e + 1;\n    if (trim) {\n      while (s != end && is_trimmed(*s)) {\n        ++s;\n      }\n    }\n  }\n  return e;\n}\n\nbool SplitString(std::string_view target, char separator,\n                 std::vector<std::string>& result) {\n  size_t i = 0, len = target.length(), start = i;\n  while (i < len) {\n    bool is_last = i == len - 1;\n    char current_char = target[i];\n    if (current_char != separator) {\n      if (is_last) {\n        result.push_back(std::string(target.substr(start)));\n      }\n    } else {\n      if (i == start) {\n        start++;\n      } else {\n        result.push_back(std::string(target.substr(start, i - start)));\n        start = i + 1;\n      }\n    }\n    i++;\n  }\n  return !result.empty();\n}\n\nnamespace {\ntemplate <class VECTOR_LIKE>\nbool SplitStringBySpaceOutOfBrackets(std::string_view target,\n                                     VECTOR_LIKE& result) {\n  size_t i = 0, len = target.length(), start = i, bracket = 0;\n  while (i < len) {\n    bool is_last = i == len - 1;\n    char current_char = target[i];\n    if (current_char == '(') {\n      ++bracket;\n    } else if (current_char == ')') {\n      --bracket;\n    }\n    if (bracket > 0 || !isspace(current_char)) {\n      if (is_last) {\n        result.push_back(std::string(target.substr(start)));\n      }\n    } else {\n      if (i == start) {\n        start++;\n      } else {\n        result.push_back(std::string(target.substr(start, i - start)));\n        start = i + 1;\n      }\n    }\n    i++;\n  }\n  return !result.empty();\n}\n}  // namespace\n\nbool SplitStringBySpaceOutOfBrackets(std::string_view target,\n                                     std::vector<std::string>& result) {\n  return SplitStringBySpaceOutOfBrackets<decltype(result)>(target, result);\n}\n\nbool SplitStringBySpaceOutOfBrackets(\n    std::string_view target, base::InlineVector<std::string, 4>& result) {\n  return SplitStringBySpaceOutOfBrackets<decltype(result)>(target, result);\n}\n\nstd::vector<std::string_view> SplitToStringViews(std::string_view str,\n                                                 const std::string& delims) {\n  std::vector<std::string_view> output;\n\n  for (auto first = str.data(), second = str.data(), last = first + str.size();\n       second != last && first != last; first = second + 1) {\n    second =\n        std::find_first_of(first, last, std::cbegin(delims), std::cend(delims));\n\n    if (first != second) {\n      output.emplace_back(first, second - first);\n    }\n  }\n\n  return output;\n}\n\nstd::string Join(const std::vector<std::string>& vec, const char* delim) {\n  std::stringstream res;\n  for (size_t i = 0; i < vec.size(); ++i) {\n    res << vec[i];\n    if (i < vec.size() - 1) {\n      res << delim;\n    }\n  }\n  return res.str();\n}\n\nstd::string JoinString(const std::vector<std::string>& pieces) {\n  std::string joined;\n  for (const auto& piece : pieces) {\n    joined += piece + \" \";\n  }\n  return joined;\n}\n\nbool EndsWithIgnoreSourceCase(std::string_view s, std::string_view ending) {\n  return EndsWith(StringToLowerASCII(s), ending);\n}\n\n// flexDirection => flex-direction\n// backgroundColor => background-color\n// width => width\n// line-height => line-height\nstd::string CamelCaseToDashCase(std::string_view camel_case_property) {\n  std::string dash_case_property;\n  // \"A\" -> \"-a\" 2\n  // The upper bound of length of responding dash_case_property\n  // is 2 times the original one\n  dash_case_property.reserve(camel_case_property.length() * 2);\n\n  for (const auto& c : camel_case_property) {\n    if (c >= 'A' && c <= 'Z') {\n      dash_case_property.push_back('-');\n      dash_case_property.push_back(std::tolower(c));\n    } else {\n      dash_case_property.push_back(c);\n    }\n  }\n\n  return dash_case_property;\n}\n\nvoid TrimWhitespaceASCII(std::string_view input, int position,\n                         std::string* output) {\n  size_t first_good_char = std::string::npos;\n  for (char c : kWhitespaceASCII) {\n    size_t pos = input.find_first_not_of(c, position);\n    if (pos == std::string::npos) continue;\n    if (first_good_char == std::string::npos) first_good_char = pos;\n    first_good_char = first_good_char < pos ? pos : first_good_char;\n  }\n\n  size_t last_good_char = input.size();\n  for (char c : kWhitespaceASCII) {\n    size_t pos = input.find_last_not_of(c, position);\n    if (pos == std::string::npos) continue;\n    last_good_char = last_good_char > pos ? last_good_char : pos;\n  }\n\n  if (input.empty() || first_good_char == std::string::npos ||\n      last_good_char == std::string::npos) {\n    output->clear();\n    return;\n  }\n\n  *output = input.substr(first_good_char, last_good_char - first_good_char + 1);\n  return;\n}\n\nstd::string StringToLowerASCII(std::string_view input) {\n  std::string output;\n  output.reserve(input.size());\n  for (char i : input) {\n    if (i >= 'A' && i <= 'Z') {\n      output.push_back(i - ('A' - 'a'));\n    } else {\n      output.push_back(i);\n    }\n  }\n  return output;\n}\n\n//\n// Uses for trim blank around string off.\n//    \" aa \"     =>   \"aa\"\n//    \" a  a \"   =>   \"a  a\"\n//\nstd::string TrimString(std::string_view str) {\n  if (str.empty()) return \"\";\n  size_t length = str.size();\n  uint32_t front_space_count = 0;\n  uint32_t back_space_count = 0;\n  uint32_t total_space_count = 0;\n  while (front_space_count < length && str[front_space_count] == ' ') {\n    front_space_count++;\n  }\n  while (front_space_count + back_space_count < length &&\n         str[length - back_space_count - 1] == ' ') {\n    back_space_count++;\n  }\n  total_space_count = front_space_count + back_space_count;\n  return std::string(str.substr(front_space_count, length - total_space_count));\n}\n\nstd::string_view TrimToStringView(std::string_view to_trim) {\n  auto left = to_trim.begin();\n  for (;; ++left) {\n    if (left == to_trim.end()) {\n      return std::string_view();\n    }\n    if (!std::isspace(*left)) {\n      break;\n    }\n  }\n  auto right = to_trim.end() - 1;\n  for (; right > left && isspace(*right); --right) {\n  }\n  return std::string_view(to_trim.data() + std::distance(to_trim.begin(), left),\n                          std::distance(left, right) + 1);\n}\n\ntemplate <typename T, typename CharT = typename T::value_type>\nT TrimStringT(T input, T trim_chars, TrimPositions positions) {\n  size_t begin =\n      (positions & TRIM_LEADING) ? input.find_first_not_of(trim_chars) : 0;\n  size_t end = (positions & TRIM_TRAILING)\n                   ? input.find_last_not_of(trim_chars) + 1\n                   : input.size();\n  return input.substr(std::min(begin, input.size()), end - begin);\n}\n\nstd::string TrimString(std::string input, std::string trim_chars,\n                       TrimPositions positions) {\n  return TrimStringT(input, trim_chars, positions);\n}\nstd::string_view TrimString(std::string_view input, std::string_view trim_chars,\n                            TrimPositions positions) {\n  return TrimStringT(input, trim_chars, positions);\n}\n\n//\n// Splits string by pattern in the char vector and following the order in\n// vector. Won't spilt wrap by '', () or \"\" as string.\n//\n// \"color: white; font-size: 100\" => {\"color\", \" white\", \" font-size\", \" 100\"}\n// \"color:white; :font-size:100\" => {\"color\", \" white\"}\n// \"color:white;:;width:100\" => {\"color\", \"white\", \"\", \"\", \"width\",\"100\"}\n// \"width: 200px; height: 200px;background-image: url('https://xxxx.jpg');\"\n// \"width: 200px; height: 200px;background-image: url(https://xxxx.jpg);\"\nbase::InlineVector<std::string, 32> SplitStringByCharsOrderly(\n    std::string_view str, char cs[], size_t char_count) {\n  const char* byte_array = str.data();\n  const size_t size = str.size();\n  base::InlineVector<std::string, 32> result;\n  if (!size || char_count == 0) {\n    result.push_back(std::string(str));\n    return result;\n  }\n  char characters[256];\n  memset(&characters[0], 0, 256);\n  for (size_t i = 0; i < char_count; i++) {\n    char c = cs[i];\n    switch (c) {\n      case '{':\n      case '}':\n      case '(':\n      case ')':\n      case '\\\"':\n      case '\\'': {\n        return result;  // Guarantee NRVO. Do not return '{}'.\n      }\n      default:\n        break;\n    }\n    characters[static_cast<int>(c)] = 1;\n  }\n\n  std::string value;\n  int word_start = -1;\n  uint32_t word_count = 0;\n  bool word_produced = false;\n  int order = 0;\n  base::InlineVector<std::string, 8> grouper;\n  bool is_variable = false;\n  bool is_string = false;\n  int end_char = -1;\n  for (int i = 0; static_cast<size_t>(i) < size; ++i) {\n    char c = byte_array[i];\n    if (!is_variable && !is_string && cs[order % char_count] == c) {\n      word_produced = true;\n      order++;\n    } else if (!is_variable && !is_string && characters[static_cast<int>(c)]) {\n      // restart\n      order = 0;\n      word_start = -1;\n      word_count = 0;\n      grouper.clear();\n    } else {\n      if (c == '{') {\n        is_variable = true;\n      } else if (is_variable && c == '}') {\n        is_variable = false;\n      }\n      if ((c == '\\'' || c == '\\\"' || c == '(') && !is_string) {\n        is_string = true;\n        if (c == '(') {\n          end_char = ')';\n        } else {\n          end_char = c;\n        }\n      } else if (is_string && c == end_char) {\n        is_string = false;\n        end_char = -1;\n      }\n      word_start = word_start == -1 ? i : word_start;\n      word_count++;\n    }\n    if (word_produced || (static_cast<size_t>(i) == size - 1 && word_count)) {\n      // consumed\n      if (word_count && word_start >= 0 &&\n          static_cast<size_t>(word_start) + static_cast<size_t>(word_count) <=\n              size) {\n        grouper.emplace_back(str, static_cast<size_t>(word_start),\n                             static_cast<size_t>(word_count));\n      } else {\n        grouper.emplace_back();\n      }\n      word_start = -1;\n      word_count = 0;\n\n      if (grouper.size() == char_count) {\n        // output\n        for (auto& s : grouper) {\n          result.emplace_back(std::move(s));\n        }\n      }\n\n      if (order % char_count == 0) {\n        grouper.clear();\n      }\n\n      word_produced = false;\n    }\n  }\n  return result;\n}\n\nvoid ReplaceAll(std::string& subject, std::string_view search,\n                std::string_view replace) {\n  size_t pos = 0;\n  while ((pos = subject.find(search, pos)) != std::string::npos) {\n    subject.replace(pos, search.length(), replace);\n    pos += replace.length();\n  }\n}\n\nstd::string SafeStringConvert(const char* str) {\n  return str == nullptr ? std::string() : str;\n}\n\nstd::string PtrToStr(void* ptr) {\n  // TODO(heshan):use std::to_chars instead when support c++17\n  char temp[20]{0};\n  // for hexadecimal, begin with 0x\n#ifndef __EMSCRIPTEN__\n  std::snprintf(temp, sizeof(temp), \"0x%\" PRIxPTR,\n                reinterpret_cast<std::uintptr_t>(ptr));\n#else\n  // emcc not qualify cpp standard, use uint32 for PRIxPTR in 64bit...\n  // so use zx instead...\n  std::snprintf(temp, sizeof(temp), \"0x%zx\",\n                reinterpret_cast<std::uintptr_t>(ptr));\n#endif\n  return temp;\n}\n\n// (1,2, 3,4) ==> vector:{1,2,3,4}\nbool ConvertParenthesesStringToVector(std::string& origin,\n                                      std::vector<std::string>& ret,\n                                      char separator) {\n  origin.erase(remove_if(origin.begin(), origin.end(), isspace), origin.end());\n  auto start = origin.find(\"(\");\n  auto end = origin.find(\")\");\n  if (start >= end) {\n    return false;\n  }\n  origin = origin.substr(start + 1, end - start - 1);\n  return base::SplitString(origin, separator, ret);\n}\n\nstd::vector<std::string> SplitStringIgnoreBracket(std::string str,\n                                                  char delimiter) {\n  int start = 0;\n  std::vector<std::string> result;\n  bool has_bracket = false;\n  for (int i = 0; static_cast<size_t>(i) < str.size(); i++) {\n    if (str[i] == delimiter) {\n      if (has_bracket) {\n        continue;\n      } else {\n        if (i > start) {\n          result.push_back(TrimString(str.substr(start, i - start)));\n        }\n        start = i + 1;\n      }\n    } else if (str[i] == '(') {\n      has_bracket = true;\n    } else if (str[i] == ')') {\n      has_bracket = false;\n    }\n  }\n  if (static_cast<size_t>(start) < str.size()) {\n    result.push_back(TrimString(str.substr(start, str.size() - start)));\n  }\n  return result;\n}\n\nbool BothAreSpaces(char lhs, char rhs) {\n  return (lhs == rhs) && (isspace(lhs));\n}\n\nstd::string RemoveSpaces(std::string_view str) {\n  std::stringstream ss;\n  for (size_t i = 0; i < str.length(); ++i) {\n    if (!std::isspace(str[i])) {\n      ss << str[i];\n    }\n  }\n  return ss.str();\n}\n\n// \"a b    c  d   \" => \"a b c d \"\nvoid ReplaceMultiSpaceWithOne(std::string& str) {\n  std::string::iterator new_end =\n      std::unique(str.begin(), str.end(), &BothAreSpaces);\n  str.erase(new_end, str.end());\n}\n\n// if \\n, \\r, \\t in \\\"\\\", exec the following replace actions\n// '\\n' => \"\\n\"\n// '\\r' => \"\\r\"\n// '\\t' => \"\\t\"\n// \"\\\"a\\\"\" => \"\\\"a\\\"\"\n//  \"\\\"a\\nb\\\"\" => \"\\\"a\\\\nb\\\"\"\n// \"( x? \\\"a\\\" : \\\"b\\\")\" => \"( x? \\\"a\\\" : \\\"b\\\")\"\n// \"( x ? \\n \\\"a\\\" : \\n\\\"b\\\")\" => \"( x ? \\n \\\"a\\\" : \\n\\\"b\\\")\"\n// \"( x ? \\n\\\"a \\nc\\\": \\n\\\"b\\\"\" => \"( x ? \\n\\\"a \\\\nc\\\": \\n\\\"b\\\"\"\n// \"( x ? \\n a : \\n b)\" => \"( x ? \\n a : \\n b)\"\nvoid ReplaceEscapeCharacterWithLiteralString(std::string& input) {\n  int newline_count = 0;\n  int double_quotes_count = 0;\n  bool pre_is_escape = false;\n  for (auto c : input) {\n    if (c == '\\\\') {\n      pre_is_escape = true;\n      continue;\n    }\n    if (!pre_is_escape && c == '\\\"') {\n      ++double_quotes_count;\n    }\n    if (c == '\\r' || c == '\\n' || c == '\\t') {\n      if (double_quotes_count % 2 == 1) {\n        ++newline_count;\n      }\n    }\n    pre_is_escape = false;\n  }\n  size_t len = input.size();\n  input.resize(len + newline_count);\n  size_t left = len - 1;\n  size_t right = input.size() - 1;\n  auto insert_back_slash = [](std::string& str, size_t& index) {\n    --index;\n    str[index] = '\\\\';\n  };\n\n  double_quotes_count = 0;\n  while (left < right) {\n    if (input[left] == '\\\"') {\n      if (left == 0 || input[left - 1] != '\\\\') {\n        ++double_quotes_count;\n      }\n    }\n    if (double_quotes_count % 2 == 0) {\n      input[right] = input[left];\n      --right;\n      --left;\n      continue;\n    }\n    if (input[left] == '\\n') {\n      input[right] = 'n';\n      insert_back_slash(input, right);\n    } else if (input[left] == '\\r') {\n      input[right] = 'r';\n      insert_back_slash(input, right);\n    } else if (input[left] == '\\t') {\n      input[right] = 't';\n      insert_back_slash(input, right);\n    } else {\n      input[right] = input[left];\n    }\n    --right;\n    --left;\n  }\n}\n\nstd::u16string U8StringToU16(std::string_view u8_string) {\n  std::u32string u32str = U8StringToU32(u8_string);\n  return U32StringToU16(u32str);\n}\n\nstd::string U16StringToU8(std::u16string_view u16_string) {\n  std::u32string u32_string = U16StringToU32(u16_string);\n  return U32StringToU8(u32_string);\n}\n\nstd::u32string U8StringToU32(std::string_view u8_string) {\n  size_t length = u8_string.length();\n\n  std::u32string u32str;\n  uint32_t i = 0;\n\n  while (i < length) {\n    uint32_t utf32 = static_cast<unsigned char>(u8_string[i]);\n    int additional_bytes = 0;\n\n    if ((utf32 & 0x80u) == 0) {\n      additional_bytes = 0;\n    } else if ((utf32 & 0xE0u) == 0xC0) {\n      utf32 &= 0x1Fu;\n      additional_bytes = 1;\n    } else if ((utf32 & 0xF0u) == 0xE0) {\n      utf32 &= 0x0Fu;\n      additional_bytes = 2;\n    } else if ((utf32 & 0xF8u) == 0xF0) {\n      utf32 &= 0x07u;\n      additional_bytes = 3;\n    } else {\n      // TODO(zhengsenyao): Uncomment LOG code when LOG available\n      // LOGE(\"Invalid UTF-8 encoding\");\n      return U\"\";\n    }\n\n    for (int j = 0; j < additional_bytes; ++j) {\n      if (++i >= length) {\n        // TODO(zhengsenyao): Uncomment LOG code when LOG available\n        // LOGE(\"Invalid UTF-8 encoding\");\n        return U\"\";\n      }\n\n      auto byte = static_cast<unsigned char>(u8_string[i]);\n      if ((byte & 0xC0u) != 0x80) {\n        // TODO(zhengsenyao): Uncomment LOG code when LOG available\n        // LOGE(\"Invalid UTF-8 encoding\");\n        return U\"\";\n      }\n\n      utf32 = (utf32 << 6u) | (byte & 0x3Fu);\n    }\n\n    u32str.push_back(utf32);\n    ++i;\n  }\n\n  return u32str;\n}\n\nstd::string U32StringToU8(std::u32string_view u32_string) {\n  size_t length = u32_string.length();\n\n  std::string utf8;\n\n  for (uint32_t i = 0; i < length; ++i) {\n    auto utf32 = u32_string[i];\n\n    if (utf32 <= 0x7F) {\n      utf8.push_back(utf32);\n    } else if (utf32 <= 0x7FF) {\n      utf8.push_back(0xC0 | ((utf32 >> 6u) & 0x1Fu));\n      utf8.push_back(0x80 | (utf32 & 0x3Fu));\n    } else if (utf32 <= 0xFFFF) {\n      utf8.push_back(0xE0 | ((utf32 >> 12u) & 0x0Fu));\n      utf8.push_back(0x80 | ((utf32 >> 6u) & 0x3Fu));\n      utf8.push_back(0x80 | (utf32 & 0x3Fu));\n    } else if (utf32 <= 0x10FFFF) {\n      utf8.push_back(0xF0 | ((utf32 >> 18u) & 0x07u));\n      utf8.push_back(0x80 | ((utf32 >> 12u) & 0x3Fu));\n      utf8.push_back(0x80 | ((utf32 >> 6u) & 0x3Fu));\n      utf8.push_back(0x80 | (utf32 & 0x3Fu));\n    } else {\n      // TODO(zhengsenyao): Uncomment LOG code when LOG available\n      // LOGE(\"U32StringToU8 convert error\");\n      return \"\";\n    }\n  }\n\n  return utf8;\n}\n\nstd::u32string U16StringToU32(std::u16string_view u16_string) {\n  size_t length = u16_string.length();\n\n  std::u32string u32str;\n\n  for (uint32_t i = 0; i < length;) {\n    uint32_t utf32 = u16_string[i];\n\n    if (utf32 >= 0xD800 && utf32 <= 0xDBFF) {\n      if (i + 1 < length) {\n        uint32_t low_surrogate = u16_string[i + 1];\n        if (low_surrogate >= 0xDC00 && low_surrogate <= 0xDFFF) {\n          utf32 = ((utf32 - 0xD800) << 10) + (low_surrogate - 0xDC00) + 0x10000;\n          i += 2;\n        } else {\n          // TODO(zhengsenyao): Uncomment LOG code when LOG available\n          // LOGE(\"Invalid UTF-16 encoding\");\n          i++;\n          continue;\n        }\n      } else {\n        // TODO(zhengsenyao): Uncomment LOG code when LOG available\n        // LOGE(\"Invalid UTF-16 encoding\");\n        i++;\n        continue;\n      }\n    } else {\n      i++;\n    }\n\n    u32str.push_back(utf32);\n  }\n\n  return u32str;\n}\n\nstd::u16string U32StringToU16(std::u32string_view u32_string) {\n  size_t length = u32_string.length();\n\n  std::u16string u16str;\n\n  for (uint32_t i = 0; i < length; ++i) {\n    uint32_t utf32 = u32_string[i];\n\n    if (utf32 <= 0xFFFF) {\n      u16str.push_back(static_cast<char16_t>(utf32));\n    } else if (utf32 >= 0x10000 && utf32 <= 0x10FFFF) {\n      utf32 -= 0x10000;\n      u16str.push_back(static_cast<char16_t>(0xD800 | ((utf32 >> 10) & 0x3FF)));\n      u16str.push_back(static_cast<char16_t>(0xDC00 | (utf32 & 0x3FF)));\n    } else {\n      // TODO(zhengsenyao): Uncomment LOG code when LOG available\n      // LOGE(\"U32StringToU16 Invalid UTF-32 encoding\");\n      return u\"\";\n    }\n  }\n\n  return u16str;\n}\n\nbool IsValidUtf8Bytes(const unsigned char* p, int count) {\n  for (int i = 0; i < count; ++i) {\n    uint8_t c = p[++i];\n    if ((c & 0xC0) != 0x80) {\n      return false;\n    }\n  }\n  return true;\n}\n\nstd::string FormatStringWithVaList(const char* format, va_list args) {\n  int length, size = 100;\n  char* mes = nullptr;\n  if ((mes = static_cast<char*>(malloc(size * sizeof(char)))) == nullptr) {\n    return \"\";\n  }\n  while (true) {\n    va_list copy_args;\n    va_copy(copy_args, args);\n    length = vsnprintf(mes, size, format, copy_args);\n    va_end(copy_args);\n    if (length > -1 && length < size) break;\n    size *= 2;\n    char* clone = static_cast<char*>(realloc(mes, size * sizeof(char)));\n    if (clone == nullptr) {\n      break;\n    } else {\n      mes = clone;\n      clone = nullptr;\n    }\n  }\n  std::string message = mes;\n  free(mes);\n  mes = nullptr;\n  return message;\n}\n\nstd::string FormatString(const char* format, ...) {\n  std::string error_msg;\n  va_list args;\n  va_start(args, format);\n  error_msg = FormatStringWithVaList(format, args);\n  va_end(args);\n  return error_msg;\n}\n\nbool EqualsIgnoreCase(std::string_view left, std::string_view right) {\n  auto left_lower = StringToLowerASCII(left);\n  auto right_lower = StringToLowerASCII(right);\n\n  return left_lower == right_lower;\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/string/string_utils_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/string/string_utils.h\"\n\n#include <string>\n#include <type_traits>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nTEST(StringUtils, ConvertParenthesesStringToVector) {\n  int idx = 1;\n  std::string s1(\"(1,2,3,4)\");\n  std::vector<std::string> ret;\n\n  ConvertParenthesesStringToVector(s1, ret, ',');\n  EXPECT_EQ(ret.size(), static_cast<size_t>(4));\n  for (auto& it : ret) {\n    EXPECT_EQ(it, std::to_string(idx++));\n  }\n  ret.clear();\n\n  idx = 1;\n  s1 = \"( 1,    2,    3,  4 )\";\n  ConvertParenthesesStringToVector(s1, ret, ',');\n  EXPECT_EQ(ret.size(), static_cast<size_t>(4));\n  for (auto& it : ret) {\n    EXPECT_EQ(it, std::to_string(idx++));\n  }\n  ret.clear();\n\n  idx = 1;\n  s1 = \"(@1@@2@@@3@@@@4)\";\n  ConvertParenthesesStringToVector(s1, ret, '@');\n  EXPECT_EQ(ret.size(), static_cast<size_t>(4));\n  for (auto& it : ret) {\n    EXPECT_EQ(it, std::to_string(idx++));\n  }\n  ret.clear();\n\n  idx = 1;\n  s1 = \"(@1@@2@@@3@@@@4)\";\n  ConvertParenthesesStringToVector(s1, ret, ',');\n  EXPECT_EQ(ret.size(), static_cast<size_t>(1));\n  ret.clear();\n\n  idx = 1;\n  s1 = \"@1@@2@@@3@@@@4\";\n  ConvertParenthesesStringToVector(s1, ret, '@');\n  EXPECT_EQ(ret.size(), static_cast<size_t>(0));\n  ret.clear();\n}\n\nTEST(StringNumberConvertTest, SplitStringIgnoreBracket) {\n  int idx = 1;\n  std::string s1(\"1,2,3,4\");\n  std::vector<std::string> ret;\n\n  ret = SplitStringIgnoreBracket(s1, ',');\n  EXPECT_EQ(ret.size(), static_cast<size_t>(4));\n  for (auto& it : ret) {\n    EXPECT_EQ(it, std::to_string(idx++));\n  }\n  ret.clear();\n\n  s1 = \"1,(2,3),4\";\n  ret = SplitStringIgnoreBracket(s1, ',');\n  EXPECT_EQ(ret.size(), static_cast<size_t>(3));\n  EXPECT_EQ(ret[0], \"1\");\n  EXPECT_EQ(ret[1], \"(2,3)\");\n  EXPECT_EQ(ret[2], \"4\");\n  ret.clear();\n\n  s1 = \"(1,2,3,4)\";\n  ret = SplitStringIgnoreBracket(s1, ',');\n  EXPECT_EQ(ret.size(), static_cast<size_t>(1));\n  EXPECT_EQ(ret[0], \"(1,2,3,4)\");\n  ret.clear();\n}\n\nTEST(StringNumberConvertTest, ReplaceMultiSpaceWithOne) {\n  std::string s1(\"1,   2,  3, 4\");\n  ReplaceMultiSpaceWithOne(s1);\n  EXPECT_EQ(\"1, 2, 3, 4\", s1);\n\n  s1 = \"1,2,3,4\";\n  ReplaceMultiSpaceWithOne(s1);\n  EXPECT_EQ(\"1,2,3,4\", s1);\n}\n\nTEST(StringNumberConvertTest, ReplaceEscapeCharacterWithLiteralString) {\n  std::vector<std::pair<std::string, std::string>> input = {\n      {\"\", \"\"},\n      {\"\\\\n\", \"\\\\n\"},\n      {\"\\\"a \\nb\\\"\", \"\\\"a \\\\nb\\\"\"},\n      {\"( xxx ? \\\"a\\\" : \\\"b\\\")\", \"( xxx ? \\\"a\\\" : \\\"b\\\")\"},\n      {\"( xxx ? \\n                \\\"a\\\" : \\n                \\\"b\\\")\",\n       \"( xxx ? \\n                \\\"a\\\" : \\n                \\\"b\\\")\"},\n      {\"( xxx ? \\n    a : \\n    b )\", \"( xxx ? \\n    a : \\n    b )\"},\n      {\"\\\"a\\\"\", \"\\\"a\\\"\"},\n      {\"\\\"a \\nb\\\"\", \"\\\"a \\\\nb\\\"\"},\n      {\"\\\"\\\\\\\"a\\\\\\\"\\\"\", \"\\\"\\\\\\\"a\\\\\\\"\\\"\"},\n      {\"\\\"\\\\\\\"a\\\\\\\" \\\\n \\\\\\\"b\\\\\\\"\\\"\", \"\\\"\\\\\\\"a\\\\\\\" \\\\n \\\\\\\"b\\\\\\\"\\\"\"},\n      {\"\\\"\\\\\\\"a \\nb\\\\\\\"\\\"\", \"\\\"\\\\\\\"a \\\\nb\\\\\\\"\\\"\"},\n      {\"\\\"\\\\\\\"a \\nb\\\\\\\"\\\"\", \"\\\"\\\\\\\"a \\\\nb\\\\\\\"\\\"\"},\n      {\"( xxx ? \\\"a\\\" : \\\"b\\\")+\\\"\\\\\\\"\\\"\", \"( xxx ? \\\"a\\\" : \\\"b\\\")+\\\"\\\\\\\"\\\"\"},\n      {\"( xxx ? \\n                \\\"a\\\" : \\n                \\\"b\\\")+\\\"\\\\\\\">\\\"\",\n       \"( xxx ? \\n                \\\"a\\\" : \\n                \\\"b\\\")+\\\"\\\\\\\">\\\"\"},\n      {\"( xxx ? \\n    a : \\n    b )\", \"( xxx ? \\n    a : \\n    b )\"},\n  };\n  for (auto& pair : input) {\n    ReplaceEscapeCharacterWithLiteralString(pair.first);\n    ASSERT_EQ(pair.first, pair.second);\n  }\n}\n\nTEST(CamelCaseToDashCaseTest, CamelCaseToDashCase) {\n  std::vector<std::pair<std::string, std::string>> inputs = {\n      {\"\", \"\"},\n      {\"123\", \"123\"},\n      {\"aaaa\", \"aaaa\"},\n      {\"fontSize\", \"font-size\"},\n      {\"backgroundColor\", \"background-color\"},\n      {\"listCrossAxisGap\", \"list-cross-axis-gap\"},\n  };\n\n  for (auto& pair : inputs) {\n    EXPECT_EQ(CamelCaseToDashCase(pair.first), pair.second);\n  }\n}\n\nTEST(SplitStringByCharsOrderlyTest, SplitStringByCharsOrderly) {\n  std::string input;\n  base::Vector<std::string> expected;\n\n  input = \"color: white; font-size: 100\";\n  auto result = SplitStringByCharsOrderly<':', ';'>(input);\n  expected = {\"color\", \" white\", \" font-size\", \" 100\"};\n  ASSERT_EQ(result, expected);\n\n  input = \"color:white; font-size:100\";\n  result = SplitStringByCharsOrderly<':', ';'>(input);\n  expected = {\"color\", \"white\", \" font-size\", \"100\"};\n  ASSERT_EQ(result, expected);\n\n  input = \"color:white;:;width:100\";\n  result = SplitStringByCharsOrderly<':', ';'>(input);\n  expected = {\"color\", \"white\", \"\", \"\", \"width\", \"100\"};\n  ASSERT_EQ(result, expected);\n\n  input = \"font-family:'white';width:100\";\n  result = SplitStringByCharsOrderly<':', ';'>(input);\n  expected = {\"font-family\", \"'white'\", \"width\", \"100\"};\n  ASSERT_EQ(result, expected);\n\n  input = \"background-image: url('https://xxxx.jpg');\";\n  result = SplitStringByCharsOrderly<':', ';'>(input);\n  expected = {\"background-image\", \" url('https://xxxx.jpg')\"};\n  ASSERT_EQ(result, expected);\n\n  input = \"background-image: url(https://xxxx.jpg);\";\n  result = SplitStringByCharsOrderly<':', ';'>(input);\n  expected = {\"background-image\", \" url(https://xxxx.jpg)\"};\n  ASSERT_EQ(result, expected);\n\n  input = \"background-image: url(\\\"https://xxxx.jpg\\\");\";\n  result = SplitStringByCharsOrderly<':', ';'>(input);\n  expected = {\"background-image\", \" url(\\\"https://xxxx.jpg\\\")\"};\n  ASSERT_EQ(result, expected);\n\n  input = \"background-image: {x:xx}\";\n  result = SplitStringByCharsOrderly<':', ';'>(input);\n  expected = {\"background-image\", \" {x:xx}\"};\n  ASSERT_EQ(result, expected);\n}\n\nTEST(SplitStringByCharsOrderlyTest,\n     SplitStringByCharsOrderlyWithNullCharacter) {\n  std::string input(\"background-\\0image: {x:\\0xx}\", 26);\n  ;\n  base::Vector<std::string> expected;\n\n  auto result = SplitStringByCharsOrderly<':', ';'>(input);\n  expected = {std::string(\"background-\\0image\", 17),\n              std::string(\" {x:\\0xx}\", 8)};\n  ASSERT_EQ(result, expected);\n}\n\nTEST(U8StringToU16Test, U8StringToU16) {\n  std::vector<std::string> input;\n  std::vector<std::u16string> expected;\n\n  // err1:The string is out of the range of UTF-8!\n  std::vector<uint8_t> test1 = {std::uint8_t(0b10000000)};\n  std::string err1(test1.begin(), test1.end());\n  // err2:The string is out of the range of UTF-8!\n  std::vector<uint8_t> test2 = {std::uint8_t(0b11000000),\n                                std::uint8_t(0b11101110)};\n  std::string err2(test2.begin(), test2.end());\n  // err3:An error occurred in some UTF-8 strings!\n  std::vector<uint8_t> test3 = {std::uint8_t(0b11100000),\n                                std::uint8_t(0b11000010),\n                                std::uint8_t(0b10101110)};\n  std::string err3(test3.begin(), test3.end());\n  // err4:The string is out of the range of UTF-8!\n  std::vector<uint8_t> test4 = {\n      std::uint8_t(0b11111000), std::uint8_t(0b10101110),\n      std::uint8_t(0b10101110), std::uint8_t(0b10101110)};\n  std::string err4(test4.begin(), test4.end());\n  // err5:An error occurred in some UTF-8 strings\n  std::vector<uint8_t> test5 = {std::uint8_t(0b11011000),\n                                std::uint8_t(0b00101110)};\n  std::string err5(test5.begin(), test5.end());\n  // err6:The UTF-8 string is missing bytes!\n  std::vector<uint8_t> test6 = {std::uint8_t(0b11101000),\n                                std::uint8_t(0b10101110)};\n  std::string err6(test6.begin(), test6.end());\n  // err7:he UTF-8 string is missing bytes!\n  std::vector<uint8_t> test7 = {\n      std::uint8_t(0b11010000), std::uint8_t(0b10101110),\n      std::uint8_t(0b11101000), std::uint8_t(0b10101110)};\n  std::string err7(test7.begin(), test7.end());\n\n  input = {u8\".?\\\"`~-_=+} 、,.<｜｜》〉？'/]>{.[$¥%^\",\n           u8\"hello,WORLD!\",\n           u8\"世界,你好！\",\n           u8\"\",\n           u8\"\\uC10F\",\n           u8\"      \",\n           u8\"\\u07FF\",\n           u8\"\\uFFFF\\u079E\",\n           u8\"\\U0010EEEE\",\n           u8\"\\u0000\",\n           u8\"\\U00004E23\\U0001F601\\U00001EB5\",\n           u8\"\\U00001EB7\",\n           u8\"\\U00000152\",\n           u8\"\\U000020A7\",\n           u8\"\\U0001F606\",\n           err1,\n           err2,\n           err3,\n           err4,\n           err5,\n           err6,\n           err7};\n  expected = {u\".?\\\"`~-_=+} 、,.<｜｜》〉？'/]>{.[$¥%^\",\n              u\"hello,WORLD!\",\n              u\"世界,你好！\",\n              u\"\",\n              u\"\\U0000C10F\",\n              u\"      \",\n              u\"\\U000007FF\",\n              u\"\\U0000FFFF\\U0000079E\",\n              u\"\\U0010EEEE\",\n              u\"\",\n              u\"\\U00004E23\\U0001F601\\U00001EB5\",\n              u\"\\U00001EB7\",\n              u\"\\U00000152\",\n              u\"\\U000020A7\",\n              u\"\\U0001F606\",\n              u\"\",\n              u\"\",\n              u\"\",\n              u\"\",\n              u\"\",\n              u\"\",\n              u\"\"};\n  for (size_t i = 0; i < input.size(); ++i) {\n    std::u16string result = U8StringToU16(input[i]);\n    ASSERT_EQ(result, expected[i]);\n  }\n}\n\nTEST(U16StringToU8Test, U16StringToU8) {\n  std::vector<std::u16string> input;\n  std::vector<std::string> expected;\n\n  // err1:The string is out of the range of UTF-16!\n  std::vector<uint16_t> test1 = {std::uint16_t(0x0020), std::uint16_t(0xFFFF)};\n  std::u16string err1(test1.begin(), test1.end());\n  // err2:The string is out of the range of UTF-16!\n  std::vector<uint16_t> test2 = {std::uint16_t(0x0010), std::uint16_t(0xFFFF),\n                                 std::uint16_t(0x0020), std::uint16_t(0xFFFF)};\n  std::u16string err2(test2.begin(), test2.end());\n\n  input = {u\".?\\\"`~-_=+} 、,.<｜｜》〉？'/]>{.[$¥%^\",\n           u\"hello,WORLD!\",\n           u\"世界,你好！\",\n           u\"\",\n           u\"\\uC10F\",\n           u\"      \",\n           u\"\\U000007FF\",\n           u\"\\U0000FFFF\\U0000079E\",\n           u\"\\u006E\",\n           u\"\\u06EE\",\n           u\"\\u08FF\\u06FF\",\n           u\"\\u08FF\\U0010EEEE\\U0001FFFF\",\n           u\"\\u0000\",\n           u\"\\uFFFF\",\n           u\"\\U00100000\",\n           u\"\\U0010FFFF\",\n           u\"\\U00004E23\\U0001F601\\U00001EB5\",\n           u\"\\U00001EB7\",\n           u\"\\U00000152\",\n           u\"\\U000020A7\",\n           u\"\\U0001F606\"};\n  expected = {u8\".?\\\"`~-_=+} 、,.<｜｜》〉？'/]>{.[$¥%^\",\n              u8\"hello,WORLD!\",\n              u8\"世界,你好！\",\n              u8\"\",\n              u8\"\\uC10F\",\n              u8\"      \",\n              u8\"\\u07FF\",\n              u8\"\\uFFFF\\u079E\",\n              u8\"\\u006E\",\n              u8\"\\u06EE\",\n              u8\"\\u08FF\\u06FF\",\n              u8\"\\u08FF\\U0010EEEE\\U0001FFFF\",\n              u8\"\\u0000\",\n              u8\"\\uFFFF\",\n              u8\"\\U00100000\",\n              u8\"\\U0010FFFF\",\n              u8\"\\U00004E23\\U0001F601\\U00001EB5\",\n              u8\"\\U00001EB7\",\n              u8\"\\U00000152\",\n              u8\"\\U000020A7\",\n              u8\"\\U0001F606\"};\n\n  for (size_t i = 0; i < input.size(); ++i) {\n    std::string result = U16StringToU8(input[i]);\n    ASSERT_EQ(result, expected[i]);\n  }\n}\n\nTEST(U8StringToU16Test, Utf16ToUtf16Empty) {\n  EXPECT_EQ(U8StringToU16(\"\"), u\"\");\n}\n\nTEST(U8StringToU16Test, Utf8ToUtf16Ascii) {\n  EXPECT_EQ(U8StringToU16(\"abc123\"), u\"abc123\");\n}\n\nTEST(U8StringToU16Test, Utf8ToUtf16Unicode) {\n  EXPECT_EQ(U8StringToU16(\"\\xe2\\x98\\x83\"), u\"\\x2603\");\n}\n\nTEST(U16StringToU8Test, Utf16ToUtf8Empty) { EXPECT_EQ(U16StringToU8(u\"\"), \"\"); }\n\nTEST(U16StringToU8Test, Utf16ToUtf8Ascii) {\n  EXPECT_EQ(U16StringToU8(u\"abc123\"), \"abc123\");\n}\n\nTEST(U16StringToU8Test, Utf16ToUtf8Unicode) {\n  EXPECT_EQ(U16StringToU8(u\"\\x2603\"), \"\\xe2\\x98\\x83\");\n}\n\nTEST(FormatStringTest, FormatString) {\n  int* ptr = new int(10);\n  constexpr char str_format[] =\n      \"the string is %s, the num is %d, the char is %c, the pointer is %p\";\n  std::stringstream ss;\n  ss << \"the string is world, the num is 0, the char is c, the pointer is \"\n     << ptr;\n  std::string expect_str = ss.str();\n\n  // test format with base placeholders\n  auto str_after_format1 = FormatString(str_format, \"world\", 0, 'c', ptr);\n  ASSERT_EQ(str_after_format1, expect_str);\n\n  // test format string longer than 100\n  std::string long_str(100, 'a');\n  std::string long_str_format = long_str + str_format;\n  auto str_after_format2 =\n      FormatString(long_str_format.c_str(), \"world\", 0, 'c', ptr);\n  ASSERT_EQ(str_after_format2, long_str + expect_str);\n\n  // test empty string\n  auto str_after_format3 = FormatString(\"\");\n  ASSERT_EQ(str_after_format3, \"\");\n\n  delete ptr;\n}\n\nTEST(AppendStringTest, EmptyTest) {\n  auto empty = AppendString();\n  ASSERT_EQ(empty, \"\");\n\n  empty = AppendString(\"\");\n  ASSERT_EQ(empty, \"\");\n\n  empty = AppendString(\"\", \"\", \"\");\n  ASSERT_EQ(empty, \"\");\n}\n\nTEST(AppendStringTest, StdStringTest) {\n  std::string hello{\"hello\"};\n  std::string world{\" world\"};\n  std::string suffix{\"!\"};\n\n  auto result = AppendString(hello, world, suffix);\n  static_assert(std::is_same_v<decltype(result), std::string>);\n  ASSERT_EQ(result, \"hello world!\");\n}\n\nTEST(AppendStringTest, StdStringWithCStringTest) {\n  std::string hello{\"hello\"};\n\n  auto result = AppendString(hello, \" world\", \"!\");\n  static_assert(std::is_same_v<decltype(result), std::string>);\n  ASSERT_EQ(result, \"hello world!\");\n}\n\nTEST(AppendStringTest, StdStringWithNonStringTest) {\n  std::string hello{\"hello world\"};\n\n  auto result = AppendString(hello, \" nullptr: \", nullptr, \" boolean: \", false,\n                             \" int: \", 0xff);\n  static_assert(std::is_same_v<decltype(result), std::string>);\n  ASSERT_EQ(result, \"hello world nullptr: nullptr boolean: 0 int: 255\");\n}\n\nnamespace {\nstruct Foo {\n  Foo() = default;\n  explicit Foo(std::int32_t x) : x_(x){};\n  friend std::stringstream& operator<<(std::stringstream& output,\n                                       const Foo& foo) {\n    output << foo.x_;\n    return output;\n  }\n\n private:\n  std::int32_t x_;\n};\n}  // namespace\n\nTEST(AppendStringTest, CustomOperatorTest) {\n  auto result = AppendString(Foo{}, Foo{1}, Foo{2});\n\n  ASSERT_EQ(result, \"012\");\n}\n\nTEST(StringUtils, SplitString) {\n  {\n    std::vector<std::string> parts;\n    SplitString(\" abc,e  , 11, 3 \", ',', true,\n                [&](const char* s, size_t len, int index) {\n                  auto full = std::to_string(index) + ':' + std::string(s, len);\n                  parts.push_back(std::move(full));\n                  return true;\n                });\n    EXPECT_TRUE(parts.size() == 4);\n    EXPECT_EQ(parts[0], \"0:abc\");\n    EXPECT_EQ(parts[1], \"1:e\");\n    EXPECT_EQ(parts[2], \"2:11\");\n    EXPECT_EQ(parts[3], \"3:3\");\n  }\n  {\n    std::vector<std::string> parts;\n    SplitString(\" abc,e  , 11, 3 \", ',', false,\n                [&](const char* s, size_t len, int index) {\n                  auto full = std::to_string(index) + ':' + std::string(s, len);\n                  parts.push_back(std::move(full));\n                  return true;\n                });\n    EXPECT_TRUE(parts.size() == 4);\n    EXPECT_EQ(parts[0], \"0: abc\");\n    EXPECT_EQ(parts[1], \"1:e  \");\n    EXPECT_EQ(parts[2], \"2: 11\");\n    EXPECT_EQ(parts[3], \"3: 3 \");\n  }\n  {\n    std::vector<std::string> parts;\n    SplitString(\" abc, ,e  , 11,\", ',', true,\n                [&](const char* s, size_t len, int index) {\n                  auto full = std::to_string(index) + ':' + std::string(s, len);\n                  parts.push_back(std::move(full));\n                  return true;\n                });\n    EXPECT_TRUE(parts.size() == 3);\n    EXPECT_EQ(parts[0], \"0:abc\");\n    EXPECT_EQ(parts[1], \"1:e\");\n    EXPECT_EQ(parts[2], \"2:11\");\n  }\n  {\n    std::vector<std::string> parts;\n    SplitString(\" ,  \", ',', false, [&](const char* s, size_t len, int index) {\n      auto full = std::to_string(index) + ':' + std::string(s, len);\n      parts.push_back(std::move(full));\n      return true;\n    });\n    EXPECT_TRUE(parts.size() == 2);\n    EXPECT_EQ(parts[0], \"0: \");\n    EXPECT_EQ(parts[1], \"1:  \");\n  }\n  {\n    std::vector<std::string> parts;\n    SplitString(\" ,  \", ',', true, [&](const char* s, size_t len, int index) {\n      auto full = std::to_string(index) + ':' + std::string(s, len);\n      parts.push_back(std::move(full));\n      return true;\n    });\n    EXPECT_TRUE(parts.empty());\n  }\n  {\n    std::vector<std::string> parts;\n    SplitString(\"    \", ',', true, [&](const char* s, size_t len, int index) {\n      auto full = std::to_string(index) + ':' + std::string(s, len);\n      parts.push_back(std::move(full));\n      return true;\n    });\n    EXPECT_TRUE(parts.empty());\n  }\n  {\n    std::vector<std::string> parts;\n    SplitString(\"    \", ',', false, [&](const char* s, size_t len, int index) {\n      auto full = std::to_string(index) + ':' + std::string(s, len);\n      parts.push_back(std::move(full));\n      return true;\n    });\n    EXPECT_TRUE(parts.size() == 1);\n    EXPECT_EQ(parts[0], \"0:    \");\n  }\n}\n\nTEST(StringUtils, all) {\n  {\n    std::vector<std::string> result;\n    EXPECT_FALSE(SplitString(\"\", ' ', result));\n    EXPECT_TRUE(SplitString(\"a bc def ghij\", ' ', result));\n    EXPECT_EQ((int)result.size(), 4);\n    EXPECT_EQ(result[0], \"a\");\n    EXPECT_EQ(result[1], \"bc\");\n    EXPECT_EQ(result[2], \"def\");\n    EXPECT_EQ(result[3], \"ghij\");\n  }\n\n  EXPECT_TRUE(EndsWith(\"abcdeft\", \"deft\"));\n  EXPECT_FALSE(EndsWith(\"\", \"a\"));\n  EXPECT_FALSE(EndsWith(\"abc\", \"d\"));\n\n  EXPECT_TRUE(EndsWithIgnoreSourceCase(\"abCdE\", \"cde\"));\n  EXPECT_EQ(StringToLowerASCII(\" !@#$%^123aBcDeF\"), \" !@#$%^123abcdef\");\n\n  EXPECT_EQ(TrimString(\" aa \"), \"aa\");\n  EXPECT_EQ(TrimString(\" a a \"), \"a a\");\n\n  EXPECT_TRUE(EqualsIgnoreCase(\"12aBcDeF45\", \"12AbCdEf45\"));\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/thread/timed_task.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/thread/timed_task.h\"\n\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/fml/message_loop.h\"\n\nnamespace lynx {\nnamespace base {\n\nTimedTaskManager::TimedTaskManager(bool need_stop_all_tasks_when_exit,\n                                   fml::RefPtr<fml::TaskRunner> runner)\n    : runner_(runner ? runner : fml::MessageLoop::GetCurrent().GetTaskRunner()),\n      need_stop_all_tasks_when_exit_(need_stop_all_tasks_when_exit) {}\n\nTimedTaskManager::~TimedTaskManager() {\n  if (need_stop_all_tasks_when_exit_) {\n    StopAllTasks();\n  }\n}\n\nuint32_t TimedTaskManager::SetTimeout(closure closure, int64_t delay) {\n  // DCHECK(runner_->RunsTasksOnCurrentThread());\n  auto controller = std::make_unique<Controller>(std::move(closure));\n  controllers_.emplace(++current_, controller.get());\n\n  runner_->PostDelayedTask(\n      fml::MakeCopyable([this, current = current_,\n                         controller = std::move(controller)]() mutable {\n        if (controller->closure) {\n          Scope scope(this, current);\n          controller->closure();\n          controllers_.erase(current);\n        }\n      }),\n      fml::TimeDelta::FromMilliseconds(delay));\n\n  return current_;\n}\n\nuint32_t TimedTaskManager::SetInterval(closure closure, int64_t delay) {\n  // DCHECK(runner_->RunsTasksOnCurrentThread());\n  auto controller = std::make_unique<Controller>(std::move(closure));\n  controllers_.emplace(++current_, controller.get());\n  SetInterval(std::move(controller), delay, current_);\n  return current_;\n}\n\nvoid TimedTaskManager::SetInterval(\n    std::unique_ptr<TimedTaskManager::Controller> controller, int64_t delay,\n    uint32_t current) {\n  runner_->PostDelayedTask(\n      fml::MakeCopyable(\n          [this, controller = std::move(controller), delay, current]() mutable {\n            if (controller->closure) {\n              Scope scope(this, current, true);\n              auto& closure = controller->closure;\n              SetInterval(std::move(controller), delay, current);\n              closure();\n            }\n          }),\n      fml::TimeDelta::FromMilliseconds(delay));\n}\n\nvoid TimedTaskManager::StopTask(uint32_t id) {\n  // DCHECK(runner_->RunsTasksOnCurrentThread());\n  // the return value of setTimeout and setInterval begins with 1,\n  // which means \"id == 0\" here is invalid\n  if (id == 0) {\n    return;\n  }\n\n  // need pending remove task when it is executing\n  if (current_executing_task_ == id) {\n    has_pending_remove_task_ = true;\n    return;\n  }\n\n  auto iter = controllers_.find(id);\n  if (iter == controllers_.end()) {\n    return;\n  }\n\n  auto* controller = iter->second;\n  controller->closure = nullptr;\n\n  controllers_.erase(iter);\n}\n\nvoid TimedTaskManager::StopAllTasks() {\n  // DCHECK(runner_->RunsTasksOnCurrentThread());\n  for (auto& [id, controller] : controllers_) {\n    controller->closure = nullptr;\n  }\n\n  controllers_.clear();\n}\n\nTimedTaskManager::Scope::Scope(TimedTaskManager* manager, uint32_t current,\n                               bool is_interval)\n    : manager_(manager), is_interval_(is_interval) {\n  manager_->current_executing_task_ = current;\n}\n\nTimedTaskManager::Scope::~Scope() {\n  uint32_t current = manager_->current_executing_task_;\n  manager_->current_executing_task_ = 0;\n\n  // for interval task, need delay stop here.\n  // for non-interval, do nothing, because it has been removed.\n  if (is_interval_ && manager_->has_pending_remove_task_) {\n    manager_->StopTask(current);\n  }\n\n  manager_->has_pending_remove_task_ = false;\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/thread/timed_task_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/thread/timed_task.h\"\n\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/thread.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#if OS_WIN\n#include <Windows.h>\n#include <synchapi.h>\n\nstatic void usleep(long num) {\n  SleepEx(num < 500 ? 1 : (num + 500) / 1000, true);\n}\n#endif\n\nnamespace lynx {\nnamespace base {\n\nclass TimedTaskTest : public ::testing::Test {\n protected:\n  TimedTaskTest() = default;\n  ~TimedTaskTest() override = default;\n\n  void SetUp() override {\n    thread_.GetTaskRunner()->PostTask(\n        [this]() { manager_ = std::make_unique<TimedTaskManager>(); });\n  }\n\n  void TearDown() override {\n    arwe_.Reset();\n    thread_.GetTaskRunner()->PostTask([this]() {\n      manager_ = nullptr;\n      arwe_.Signal();\n    });\n\n    arwe_.Wait();\n  }\n\n  void WaitResult(int64_t delay) {\n    thread_.GetTaskRunner()->PostTask([this, delay]() {\n      manager_->SetTimeout([this]() { arwe_.Signal(); }, delay);\n    });\n    arwe_.Wait();\n  }\n\n  // delay 10ms\n  static constexpr int64_t DELAY = 10;\n\n  static constexpr int32_t LOOP = 10;\n\n  fml::Thread thread_;\n  std::unique_ptr<TimedTaskManager> manager_;\n  fml::AutoResetWaitableEvent arwe_;\n  int32_t result_ = 0;\n};\n\nTEST_F(TimedTaskTest, SetTimeout) {\n  int32_t expect = result_;\n\n  for (int32_t i = 1; i <= LOOP; ++i) {\n    thread_.GetTaskRunner()->PostTask(\n        [this]() { manager_->SetTimeout([this]() { ++result_; }, DELAY); });\n    ++expect;\n  }\n\n  WaitResult(DELAY);\n  ASSERT_EQ(result_, expect);\n}\n\nTEST_F(TimedTaskTest, SetInterval) {\n  int32_t expect = 0;\n\n  thread_.GetTaskRunner()->PostTask([this, &expect]() mutable {\n    manager_->SetInterval(\n        [this, &expect]() mutable {\n          ++result_;\n          if (++expect == LOOP) {\n            arwe_.Signal();\n          }\n        },\n        DELAY);\n  });\n\n  arwe_.Wait();\n  ASSERT_EQ(result_, expect);\n}\n\nTEST_F(TimedTaskTest, StopSetTimeout) {\n  int32_t expect = result_;\n\n  for (int32_t i = 0; i < LOOP; ++i) {\n    thread_.GetTaskRunner()->PostTask([this]() {\n      uint32_t id = manager_->SetTimeout([this]() { ++result_; }, DELAY);\n      manager_->StopTask(id);\n    });\n  }\n\n  WaitResult(DELAY);\n  ASSERT_EQ(result_, expect);\n}\n\nTEST_F(TimedTaskTest, DISABLED_StopSetInterval) {\n  int32_t expect = 0;\n  int32_t id = 0;\n\n  thread_.GetTaskRunner()->PostTask([this, &id]() mutable {\n    id = manager_->SetInterval([this]() { ++result_; }, DELAY);\n  });\n\n  usleep(DELAY * LOOP * 100);\n\n  thread_.GetTaskRunner()->PostTask([this, id, &expect]() {\n    expect = result_;\n    manager_->StopTask(id);\n    arwe_.Signal();\n  });\n  arwe_.Wait();\n\n  usleep(DELAY * LOOP * 1000);\n  ASSERT_EQ(result_, expect);\n}\n\nTEST_F(TimedTaskTest, StopAllTasks) {\n  int32_t expect = result_;\n\n  int32_t delay_more = DELAY * 10;\n  for (int32_t i = 0; i < LOOP; ++i) {\n    thread_.GetTaskRunner()->PostTask([this, delay_more]() {\n      manager_->SetTimeout([this]() { ++result_; }, delay_more);\n    });\n  }\n\n  thread_.GetTaskRunner()->PostTask([this]() { manager_->StopAllTasks(); });\n\n  WaitResult(delay_more);\n  ASSERT_EQ(result_, expect);\n}\n\nTEST_F(TimedTaskTest, StopOtherTaskInSetTimeout) {\n  for (int32_t i = 0; i < LOOP; ++i) {\n    thread_.GetTaskRunner()->PostTask([this]() {\n      uint32_t id = manager_->SetTimeout([this]() { ++result_; }, DELAY);\n      manager_->SetTimeout([this, id]() { manager_->StopTask(id); }, DELAY / 2);\n    });\n  }\n\n  WaitResult(DELAY);\n  ASSERT_EQ(result_, 0);\n}\n\nTEST_F(TimedTaskTest, StopSelfTaskInSetTimeout) {\n  int32_t expect = result_;\n  for (int32_t i = 0; i < LOOP; ++i) {\n    thread_.GetTaskRunner()->PostTask([this, id = i + 1]() {\n      manager_->SetTimeout(\n          [this, id]() {\n            manager_->StopTask(id);\n            ++result_;\n          },\n          DELAY);\n    });\n    ++expect;\n  }\n\n  WaitResult(DELAY);\n  ASSERT_EQ(result_, expect);\n}\n\nTEST_F(TimedTaskTest, StopOtherTaskInSetInterval) {\n  int32_t delay_more = DELAY * 10;\n  int32_t expect = 0;\n  for (int32_t i = 0; i < LOOP; ++i) {\n    thread_.GetTaskRunner()->PostTask([this, delay_more]() {\n      manager_->SetTimeout([this]() { ++result_; }, delay_more);\n    });\n  }\n\n  thread_.GetTaskRunner()->PostTask([this, delay_more]() mutable {\n    manager_->SetInterval(\n        [this]() {\n          for (int32_t i = 1; i <= LOOP; ++i) {\n            manager_->StopTask(i);\n          }\n        },\n        delay_more / 100);\n  });\n  // Wait twice as long to ensure the validity of the unittest.\n  WaitResult(delay_more * 2);\n  ASSERT_EQ(result_, expect);\n}\n\nTEST_F(TimedTaskTest, StopSelfTaskInSetInterval) {\n  thread_.GetTaskRunner()->PostTask([this]() mutable {\n    manager_->SetInterval(\n        [this]() {\n          manager_->StopTask(1);\n          ++result_;\n        },\n        DELAY);\n  });\n\n  usleep(DELAY * LOOP * 1000);\n  ASSERT_EQ(result_, 1);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/timer/time_utils.cc",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/timer/time_utils.h\"\n\n#include <time.h>\n\n#include <chrono>\n#include <cstdint>\n\n#if !defined(OS_WIN)\n#include <sys/time.h>\n#endif\n\nnamespace lynx {\nnamespace base {\n\nuint64_t CurrentSystemTimeMilliseconds() {\n  return std::chrono::duration_cast<std::chrono::milliseconds>(\n             std::chrono::system_clock::now().time_since_epoch())\n      .count();\n}\n\nuint64_t CurrentSystemTimeMicroseconds() {\n  return std::chrono::duration_cast<std::chrono::microseconds>(\n             std::chrono::system_clock::now().time_since_epoch())\n      .count();\n}\n\nuint64_t CurrentTimeMilliseconds() {\n  // Use steady clock to guarantee the clock is monotonic.\n  const auto time = std::chrono::steady_clock::now().time_since_epoch();\n  return std::chrono::duration_cast<std::chrono::milliseconds>(time).count();\n}\n\nuint64_t CurrentTimeMicroseconds() {\n  // Use steady clock to guarantee the clock is monotonic.\n  const auto time = std::chrono::steady_clock::now().time_since_epoch();\n  return std::chrono::duration_cast<std::chrono::microseconds>(time).count();\n}\n\nuint64_t CurrentThreadCPUTimeMicroseconds() {\n  struct timespec ts;\n#if OS_IOS\n  if (__builtin_available(iOS 10.0, *)) {\n    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);\n  } else {\n    // TODO(liushouqun):solved this on ios\n    return -1;\n  }\n#elif OS_ANDROID\n  clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);\n#endif\n  uint64_t cpu_thread_time = ts.tv_sec;\n  cpu_thread_time *= 1000ll * 1000ll;\n  cpu_thread_time += ts.tv_nsec / 1000ll;\n  return cpu_thread_time;\n}\n\n#if !defined(OS_WIN)\ntimespec ToTimeSpecFromNow(uint64_t interval_time) {\n  // FIXME: Currently `ToTimeSpecFromNow` is only used by the Condition class,\n  // which provides abstime for `pthread_cond_timedwait`, however this is buggy\n  // because the time is not monotonic.\n  // Consider using monotonic time in Condition class by the following setting:\n  //  ```\n  //    pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC);\n  //  ```\n  timespec out_time;\n  struct timeval now;\n  uint64_t abs_msec;\n  gettimeofday(&now, nullptr);\n  abs_msec = now.tv_sec * 1000ll + now.tv_usec / 1000ll;\n  abs_msec += interval_time;\n\n  out_time.tv_sec = static_cast<time_t>(abs_msec / 1000ll);\n  out_time.tv_nsec = static_cast<long>(abs_msec % 1000ll * 1000000ll);\n  return out_time;\n}\n#endif\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/timer/time_utils_unittest.cc",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/timer/time_utils.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nTEST(TimeUtils, GetCurrentTimeSimple) {\n  EXPECT_GT(CurrentTimeMicroseconds(), static_cast<uint64_t>(0));\n  EXPECT_GT(CurrentTimeMilliseconds(), static_cast<uint64_t>(0));\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/to_underlying_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/to_underlying.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\n\nTEST(ToUnderLyingTest, EnumClassTest) {\n  enum class E1 : char { e };\n  static_assert(std::is_same_v<char, decltype(base::to_underlying(E1::e))>);\n\n  enum class ColorMask : std::uint32_t {\n    red = 0xFF,\n    green = (red << 8),\n    blue = (green << 8),\n    alpha = (blue << 8)\n  };\n\n  ASSERT_EQ(base::to_underlying(ColorMask::red),\n            static_cast<const uint32_t>(0xFF));\n  ASSERT_EQ(base::to_underlying(ColorMask::green),\n            static_cast<const uint32_t>(0xFF00));\n  ASSERT_EQ(base::to_underlying(ColorMask::blue),\n            static_cast<const uint32_t>(0xFF0000));\n  ASSERT_EQ(base::to_underlying(ColorMask::alpha),\n            static_cast<const uint32_t>(0xFF000000));\n\n  [[maybe_unused]] std::underlying_type_t<ColorMask> y =\n      base::to_underlying(ColorMask::alpha);  // OK\n}\n\nTEST(ToUnderLyingTest, EnumStructTest) {\n  enum struct E2 : long { e };\n  static_assert(std::is_same_v<long, decltype(base::to_underlying(E2::e))>);\n\n  ASSERT_EQ(base::to_underlying(E2::e), 0);\n}\n\nTEST(ToUnderLyingTest, EnumTest) {\n  enum E3 : unsigned { e };\n  static_assert(std::is_same_v<unsigned, decltype(base::to_underlying(e))>);\n\n  ASSERT_EQ(base::to_underlying(e), static_cast<const uint32_t>(0));\n}\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/type_traits_addon_unittest.cc",
    "content": "// Copyright 2022 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/type_traits_addon.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace {\n\nstruct SimpleStruct {};\n\nTEST(TemplateUtil, RemoveCvRefT) {\n  static_assert(std::is_same<int, remove_cvref_t<const int>>::value, \"\");\n  static_assert(std::is_same<int, remove_cvref_t<const volatile int>>::value,\n                \"\");\n  static_assert(std::is_same<int, remove_cvref_t<int&>>::value, \"\");\n  static_assert(std::is_same<int, remove_cvref_t<const int&>>::value, \"\");\n  static_assert(std::is_same<int, remove_cvref_t<const volatile int&>>::value,\n                \"\");\n  static_assert(std::is_same<int, remove_cvref_t<int&&>>::value, \"\");\n  static_assert(\n      std::is_same<SimpleStruct, remove_cvref_t<const SimpleStruct&>>::value,\n      \"\");\n  static_assert(std::is_same<int*, remove_cvref_t<int*>>::value, \"\");\n\n  // Test references and pointers to arrays.\n  static_assert(std::is_same<int[3], remove_cvref_t<int[3]>>::value, \"\");\n  static_assert(std::is_same<int[3], remove_cvref_t<int(&)[3]>>::value, \"\");\n  static_assert(std::is_same<int(*)[3], remove_cvref_t<int(*)[3]>>::value, \"\");\n\n  // Test references and pointers to functions.\n  static_assert(std::is_same<void(int), remove_cvref_t<void(int)>>::value, \"\");\n  static_assert(std::is_same<void(int), remove_cvref_t<void (&)(int)>>::value,\n                \"\");\n  static_assert(\n      std::is_same<void (*)(int), remove_cvref_t<void (*)(int)>>::value, \"\");\n}\n}  // namespace\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/value/base_string.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/value/base_string.h\"\n\n#include <cstring>\n#include <iostream>\n\n#include \"base/include/cast_util.h\"\n#include \"base/include/string/string_utils.h\"\n\nnamespace lynx {\nnamespace base {\n\nRefCountedStringImpl RefCountedStringImpl::Unsafe::kEmptyString(\"\", 0);\n\nRefCountedStringImpl& RefCountedStringImpl::Unsafe::kTrueString() {\n  static RefCountedStringImpl kTrueString(\n      \"true\", std::char_traits<char>::length(\"true\"));\n  return kTrueString;\n}\n\nRefCountedStringImpl& RefCountedStringImpl::Unsafe::kFalseString() {\n  static RefCountedStringImpl kFalseString(\n      \"false\", std::char_traits<char>::length(\"false\"));\n  return kFalseString;\n}\n\nRefCountedStringImpl::RefCountedStringImpl(const char* str)\n    : RefCountedStringImpl(str, str == nullptr ? 0 : std::strlen(str)) {}\n\nRefCountedStringImpl::RefCountedStringImpl(const char* str, std::size_t len) {\n  length_ = static_cast<uint32_t>(len);\n  str_.resize(len);\n  if (str == nullptr || len == 0) {\n    hash_ = std::hash<std::string>()(str_);\n    return;\n  }\n  std::memcpy(&str_[0], str, len);\n\n  // The likelihood of a long string being used as a hash key is extremely low,\n  // so its hash value is not calculated.\n  if (len <= 128) {\n    hash_ = std::hash<std::string>()(str_);\n  } else {\n    hash_ = 0;  // a sentinel value\n  }\n}\n\nRefCountedStringImpl::RefCountedStringImpl(std::string str) {\n  str_ = std::move(str);\n  length_ = static_cast<uint32_t>(str_.size());\n\n  // The likelihood of a long string being used as a hash key is extremely low,\n  // so its hash value is not calculated.\n  if (length_ <= 128) {\n    hash_ = std::hash<std::string>()(str_);\n  } else {\n    hash_ = 0;  // a sentinel value\n  }\n}\n\nsize_t RefCountedStringImpl::length_utf8() {\n  return base::SizeOfUtf8(str_.c_str(), str_.length());\n}\n\nsize_t RefCountedStringImpl::length_utf16() {\n  if (!utf16_len_calculated_) {\n    utf16_length_ = static_cast<uint32_t>(base::SizeOfUtf16(str_));\n    utf16_len_calculated_ = 1;\n  }\n  return utf16_length_;\n}\n\nbool StringConvertHelper::IsMinusZero(double value) {\n  return base::BitCast<int64_t>(value) == base::BitCast<int64_t>(-0.0);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/value/base_value.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/value/base_value.h\"\n\n#include <math.h>\n\n#include <cmath>\n#include <memory>\n#include <utility>\n\n#include \"base/include/base_defines.h\"\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/byte_array.h\"\n#include \"base/include/value/lynx_value_extended.h\"\n#include \"base/include/value/path_parser.h\"\n#include \"base/include/value/ref_counted_class.h\"\n#include \"base/include/value/table.h\"\n#include \"base/trace/native/trace_defines.h\"\n#include \"base/trace/native/trace_event.h\"\n\ninline constexpr const char* const VALUE_TO_LEPUS_VALUE = \"Value::ToLepusValue\";\ninline constexpr const char* const VALUE_SHADOW_COPY = \"Value::ShallowCopy\";\n\nnamespace lynx {\nnamespace lepus {\n\nValue::Value(CreateAsUndefinedTag) { value_.type = lynx_value_undefined; }\n\nValue::Value(const Value& value) {\n  value.DupValue();\n  if (unlikely(value.IsJSValue())) {\n    env_ = value.env_;\n    value_ref_ = nullptr;\n    lynx_value_add_reference_ext(value.env_, value.value_, &value_ref_);\n  }\n  value_ = value.value_;\n}\n\nvoid Value::CopyWeakValue(const Value& value) {\n  // avoid self-assignment\n  if (this == &value) {\n    return;\n  }\n  value.DupValue();\n  FreeValue();\n  if (value.IsJSValue()) {\n    env_ = value.env_;\n    if (value_.type != lynx_value_extended) {\n      value_ref_ = nullptr;\n    }\n    lynx_value_add_reference_weak_ext(env_, value.value_, &value_ref_);\n  }\n  value_ = value.value_;\n}\n\nValue::Value(Value&& value) noexcept {\n  if (unlikely(value.IsJSValue())) {\n    env_ = value.env_;\n    value_ref_ = nullptr;\n    lynx_value_move_reference_ext(env_, value.value_, value.value_ref_,\n                                  &value_ref_);\n  }\n  value_ = value.value_;\n  value.value_.type = lynx_value_null;\n}\n\nValue& Value::operator=(const Value& value) {\n  if (likely(this != &value)) {\n    value.DupValue();\n\n    // Manually inline FreeValue()\n    //    FreeValue();\n    if (unlikely(IsJSValue())) {\n      lynx_value_remove_reference_ext(env_, value_, value_ref_);\n      value_ref_ = nullptr;\n    } else if (IsReference() && likely(value_.val_ptr)) {\n      reinterpret_cast<fml::RefCountedThreadSafeStorage*>(value_.val_ptr)\n          ->Release();\n    }\n\n    if (unlikely(value.IsJSValue())) {\n      env_ = value.env_;\n      value_ref_ = nullptr;\n      lynx_value_add_reference_ext(value.env_, value.value_, &value_ref_);\n    }\n    value_ = value.value_;\n  }\n  return *this;\n}\n\nValue& Value::operator=(Value&& value) noexcept {\n  if (likely(this != &value)) {\n    this->~Value();\n    new (this) Value(std::move(value));\n  }\n  return *this;\n}\n\nValue::Value(const base::String& data) {\n  auto* str = base::String::Unsafe::GetUntaggedStringRawRef(data);\n  value_.type = lynx_value_string;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(str);\n  str->AddRef();\n}\n\nValue::Value(base::String&& data) {\n  auto* str = base::String::Unsafe::GetUntaggedStringRawRef(data);\n  value_.type = lynx_value_string;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(str);\n  if (str != base::String::Unsafe::GetStringRawRef(data)) {\n    str->AddRef();\n  }\n  base::String::Unsafe::SetStringToEmpty(data);\n}\n\nValue::Value(const fml::RefPtr<lepus::ByteArray>& data) {\n  value_.type = lynx_value_arraybuffer;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.get());\n  data.get()->AddRef();\n}\n\nValue::Value(fml::RefPtr<lepus::ByteArray>&& data) {\n  value_.type = lynx_value_arraybuffer;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.AbandonRef());\n}\n\nValue::Value(const fml::RefPtr<lepus::RefCounted>& data) {\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.get());\n  value_.type = lynx_value_object;\n  value_.tag = static_cast<int32_t>(data->GetRefType());\n  data.get()->AddRef();\n}\n\nValue::Value(fml::RefPtr<lepus::RefCounted>&& data) {\n  value_.tag = static_cast<int32_t>(data->GetRefType());\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.AbandonRef());\n  value_.type = lynx_value_object;\n}\n\nValue::Value(bool val) {\n  value_.type = lynx_value_bool;\n  value_.val_bool = val;\n}\n\nValue::Value(const char* val) {\n  auto* str = base::RefCountedStringImpl::Unsafe::RawCreate(val);\n  value_.type = lynx_value_string;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(str);\n}\n\nValue::Value(const std::string& str) {\n  auto* ptr = base::RefCountedStringImpl::Unsafe::RawCreate(str);\n  value_.type = lynx_value_string;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(ptr);\n}\n\nValue::Value(std::string&& str) {\n  auto* ptr = base::RefCountedStringImpl::Unsafe::RawCreate(std::move(str));\n  value_.type = lynx_value_string;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(ptr);\n}\n\nValue::Value(void* data) {\n  value_.type = lynx_value_external;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data);\n}\n\nValue::Value(CFunction val) {\n  value_.type = lynx_value_function;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(val);\n  value_.tag = static_cast<int32_t>(CFunctionType_Default);\n}\n\nValue::Value(CFunctionBuiltin val) {\n  value_.type = lynx_value_function;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(val);\n  value_.tag = static_cast<int32_t>(CFunctionType_Builtin);\n}\n\nValue::Value(BuiltinFunctionTable* data) {\n  value_.type = lynx_value_function_table;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data);\n}\n\nValue::Value(double val) {\n  value_.type = lynx_value_double;\n  value_.val_double = val;\n}\nValue::Value(int32_t val) {\n  value_.type = lynx_value_int32;\n  value_.val_int32 = val;\n}\nValue::Value(uint32_t val) {\n  value_.type = lynx_value_uint32;\n  value_.val_uint32 = val;\n}\nValue::Value(int64_t val) {\n  value_.type = lynx_value_int64;\n  value_.val_int64 = val;\n}\nValue::Value(uint64_t val) {\n  value_.type = lynx_value_uint64;\n  value_.val_uint64 = val;\n}\nValue::Value(uint8_t data) {\n  value_.type = lynx_value_uint32;\n  value_.val_uint32 = data;\n}\n\nValue::Value(CreateAsNanTag) {\n  value_.type = lynx_value_nan;\n  value_.val_bool = true;\n}\n\nValue::Value(const fml::RefPtr<Dictionary>& data) {\n  value_.type = lynx_value_map;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.get());\n  data.get()->AddRef();\n}\n\nValue::Value(fml::RefPtr<Dictionary>&& data) {\n  value_.type = lynx_value_map;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.AbandonRef());\n}\n\nValue::Value(const fml::WeakRefPtr<Dictionary>& data) {\n  value_.type = lynx_value_map;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.get());\n  data.get()->AddRef();\n}\n\nValue::Value(const fml::RefPtr<CArray>& data) {\n  value_.type = lynx_value_array;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.get());\n  data.get()->AddRef();\n}\n\nValue::Value(fml::RefPtr<CArray>&& data) {\n  value_.type = lynx_value_array;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.AbandonRef());\n}\n\nValue::Value(const fml::WeakRefPtr<CArray>& data) {\n  value_.type = lynx_value_array;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(data.get());\n  data.get()->AddRef();\n}\n\nValue::Value(lynx_api_env env, int64_t val, int32_t tag) : env_(env) {\n  value_.val_int64 = val;\n  value_.type = lynx_value_extended;\n  value_.tag = tag;\n  value_ref_ = nullptr;\n  lynx_value_add_reference_ext(env_, value_, &value_ref_);\n}\n\nValue::Value(lynx_api_env env, const lynx_value& value)\n    : value_(value), env_(env) {\n  if (value.type == lynx_value_extended && env) {\n    value_ref_ = nullptr;\n    lynx_value_add_reference_ext(env_, value_, &value_ref_);\n  } else if (!env) {\n    DupValue();\n  }\n}\n\nValue::Value(lynx_api_env env, lynx_value&& value)\n    : value_(std::move(value)), env_(env) {\n  if (value.type == lynx_value_extended && env) {\n    value_ref_ = nullptr;\n    lynx_value_move_reference_ext(env_, value_, nullptr, &value_ref_);\n  }\n}\n\n// nested use of recursive implementation to prevent excessive trace\n// instrumentation\nValue Value::ToLepusValue(bool deep_convert) const {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, VALUE_TO_LEPUS_VALUE);\n  ToLepusValueRecursively(const_cast<lepus::Value&>(*this), deep_convert);\n  return *this;\n}\n\n// recursively convert all internal values to lepus values\nvoid Value::ToLepusValueRecursively(Value& value, bool deep_convert) {\n  if (!value.IsJSValue()) {\n    if (value.IsTable()) {\n      const auto tbl =\n          reinterpret_cast<lepus::Dictionary*>(value.value_.val_ptr);\n      if (tbl != nullptr) {\n        tbl->for_each([&](const auto& key, Value& value) {\n          ToLepusValueRecursively(value, deep_convert);\n        });\n      }\n    } else if (value.IsArray()) {\n      const auto arr = reinterpret_cast<lepus::CArray*>(value.value_.val_ptr);\n      if (arr != nullptr) {\n        for (std::size_t i = 0; i < arr->size(); ++i) {\n          ToLepusValueRecursively(const_cast<lepus::Value&>(arr->get(i)),\n                                  deep_convert);\n        }\n      }\n    }\n    return;\n  }\n  int32_t flag = deep_convert ? 1 : 0;\n  value = ToLepusValue(value.env_, value.value_, flag);\n}\n\ndouble Value::Number() const {\n  switch (value_.type) {\n    case lynx_value_double:\n      return value_.val_double;\n    case lynx_value_int32:\n      return value_.val_int32;\n    case lynx_value_uint32:\n      return value_.val_uint32;\n    case lynx_value_int64:\n      return value_.val_int64;\n    case lynx_value_uint64:\n      return value_.val_uint64;\n    default:\n      if (IsJSNumber()) return LEPUSNumber();\n  }\n  return 0;\n}\n\nconst std::string& Value::StdString() const {\n  if (value_.type == lynx_value_string) {\n    return reinterpret_cast<base::RefCountedStringImpl*>(value_.val_ptr)->str();\n  } else if (value_.type == lynx_value_bool) {\n    return value_.val_bool\n               ? base::RefCountedStringImpl::Unsafe::kTrueString().str()\n               : base::RefCountedStringImpl::Unsafe::kFalseString().str();\n  } else if (IsJSString()) {\n    void* str_ref;\n    lynx_value_get_string_ref_ext(env_, value_, &str_ref);\n    return reinterpret_cast<base::RefCountedStringImpl*>(str_ref)->str();\n  } else if (IsJSBool()) {\n    return LEPUSBool()\n               ? base::RefCountedStringImpl::Unsafe::kTrueString().str()\n               : base::RefCountedStringImpl::Unsafe::kFalseString().str();\n  }\n  return base::RefCountedStringImpl::Unsafe::kEmptyString.str();\n}\n\nbase::String Value::String() const& {\n  if (value_.type == lynx_value_string) {\n    return base::String::Unsafe::ConstructWeakRefStringFromRawRef(\n        reinterpret_cast<base::RefCountedStringImpl*>(value_.val_ptr));\n  } else if (value_.type == lynx_value_bool) {\n    return value_.val_bool\n               ? base::String::Unsafe::ConstructWeakRefStringFromRawRef(\n                     &base::RefCountedStringImpl::Unsafe::kTrueString())\n               : base::String::Unsafe::ConstructWeakRefStringFromRawRef(\n                     &base::RefCountedStringImpl::Unsafe::kFalseString());\n  } else if (IsJSString()) {\n    void* str_ref;\n    lynx_value_get_string_ref_ext(env_, value_, &str_ref);\n    return base::String::Unsafe::ConstructWeakRefStringFromRawRef(\n        reinterpret_cast<base::RefCountedStringImpl*>(str_ref));\n  } else if (IsJSBool()) {\n    return LEPUSBool()\n               ? base::String::Unsafe::ConstructWeakRefStringFromRawRef(\n                     &base::RefCountedStringImpl::Unsafe::kTrueString())\n               : base::String::Unsafe::ConstructWeakRefStringFromRawRef(\n                     &base::RefCountedStringImpl::Unsafe::kFalseString());\n  }\n  return base::String();\n}\n\nbase::String Value::String() && {\n  if (value_.type == lynx_value_string) {\n    return base::String::Unsafe::ConstructStringFromRawRef(\n        reinterpret_cast<base::RefCountedStringImpl*>(value_.val_ptr));\n  } else if (value_.type == lynx_value_bool) {\n    return value_.val_bool\n               ? base::String::Unsafe::ConstructStringFromRawRef(\n                     &base::RefCountedStringImpl::Unsafe::kTrueString())\n               : base::String::Unsafe::ConstructStringFromRawRef(\n                     &base::RefCountedStringImpl::Unsafe::kFalseString());\n  } else if (IsJSString()) {\n    void* str_ref;\n    lynx_value_get_string_ref_ext(env_, value_, &str_ref);\n    return base::String::Unsafe::ConstructStringFromRawRef(\n        reinterpret_cast<base::RefCountedStringImpl*>(str_ref));\n  } else if (IsJSBool()) {\n    return LEPUSBool()\n               ? base::String::Unsafe::ConstructStringFromRawRef(\n                     &base::RefCountedStringImpl::Unsafe::kTrueString())\n               : base::String::Unsafe::ConstructStringFromRawRef(\n                     &base::RefCountedStringImpl::Unsafe::kFalseString());\n  }\n  return base::String();\n}\n\nfml::WeakRefPtr<lepus::ByteArray> Value::ByteArray() const& {\n  return fml::WeakRefPtr<lepus::ByteArray>(\n      value_.val_ptr != nullptr && value_.type == lynx_value_arraybuffer\n          ? reinterpret_cast<lepus::ByteArray*>(value_.val_ptr)\n          : DummyByteArray());\n}\n\nfml::RefPtr<lepus::ByteArray> Value::ByteArray() && {\n  if (value_.val_ptr != nullptr && value_.type == lynx_value_arraybuffer) {\n    return fml::RefPtr<lepus::ByteArray>(\n        reinterpret_cast<lepus::ByteArray*>(value_.val_ptr));\n  }\n  return lepus::ByteArray::Create();\n}\n\nfml::WeakRefPtr<Dictionary> Value::Table() const& {\n  return fml::WeakRefPtr<Dictionary>(\n      value_.val_ptr != nullptr && value_.type == lynx_value_map\n          ? reinterpret_cast<Dictionary*>(value_.val_ptr)\n          : DummyTable());\n}\n\nfml::RefPtr<Dictionary> Value::Table() && {\n  if (value_.val_ptr != nullptr && value_.type == lynx_value_map) {\n    return fml::RefPtr<Dictionary>(\n        reinterpret_cast<Dictionary*>(value_.val_ptr));\n  }\n  return Dictionary::Create();\n}\n\nfml::WeakRefPtr<CArray> Value::Array() const& {\n  return fml::WeakRefPtr<CArray>(value_.val_ptr != nullptr &&\n                                         value_.type == lynx_value_array\n                                     ? reinterpret_cast<CArray*>(value_.val_ptr)\n                                     : DummyArray());\n}\n\nfml::RefPtr<CArray> Value::Array() && {\n  if (value_.val_ptr != nullptr && value_.type == lynx_value_array) {\n    return fml::RefPtr<CArray>(reinterpret_cast<CArray*>(value_.val_ptr));\n  }\n  return CArray::Create();\n}\n\nfml::WeakRefPtr<lepus::RefCounted> Value::RefCounted() const& {\n  return fml::WeakRefPtr<lepus::RefCounted>(\n      value_.type == lynx_value_object\n          ? reinterpret_cast<lepus::RefCounted*>(value_.val_ptr)\n          : nullptr);\n}\n\nfml::RefPtr<lepus::RefCounted> Value::RefCounted() && {\n  if (value_.type == lynx_value_object) {\n    return fml::RefPtr<lepus::RefCounted>(\n        reinterpret_cast<lepus::RefCounted*>(value_.val_ptr));\n  }\n  return nullptr;\n}\n\nCFunction Value::Function() const {\n  if (likely(value_.type == lynx_value_function)) {\n    DCHECK(value_.tag == static_cast<int32_t>(CFunctionType_Default));\n    return reinterpret_cast<CFunction>(Ptr());\n  }\n  return nullptr;\n}\n\nCFunctionBuiltin Value::FunctionBuiltin() const {\n  if (likely(value_.type == lynx_value_function)) {\n    DCHECK(value_.tag == static_cast<int32_t>(CFunctionType_Builtin));\n    return reinterpret_cast<CFunctionBuiltin>(Ptr());\n  }\n  return nullptr;\n}\n\nBuiltinFunctionTable* Value::FunctionTable() const {\n  if (likely(value_.type == lynx_value_function_table)) {\n    return reinterpret_cast<BuiltinFunctionTable*>(Ptr());\n  }\n  return nullptr;\n}\n\nvoid* Value::CPoint() const {\n  if (value_.type == lynx_value_external) {\n    return Ptr();\n  }\n  if (IsJSCPointer()) {\n    return LEPUSCPointer();\n  }\n  return nullptr;\n}\n\nvoid Value::SetNan(bool value) {\n  FreeValue();\n  value_.type = lynx_value_nan;\n  value_.val_bool = value;\n}\n\nvoid Value::SetCPoint(void* point) {\n  FreeValue();\n  value_.type = lynx_value_external;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(point);\n}\n\nvoid Value::SetCFunction(CFunction func) {\n  FreeValue();\n  value_.type = lynx_value_function;\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(func);\n  value_.tag = static_cast<int32_t>(CFunctionType_Default);\n}\n\nvoid Value::SetBool(bool value) {\n  FreeValue();\n  value_.type = lynx_value_bool;\n  value_.val_bool = value;\n}\n\nvoid Value::SetString(const base::String& str) {\n  FreeValue();\n  auto* ptr = base::String::Unsafe::GetUntaggedStringRawRef(str);\n  ptr->AddRef();\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(ptr);\n  value_.type = lynx_value_string;\n}\n\nvoid Value::SetString(base::String&& str) {\n  FreeValue();\n  auto* ptr = base::String::Unsafe::GetUntaggedStringRawRef(str);\n  if (ptr != base::String::Unsafe::GetStringRawRef(str)) {\n    ptr->AddRef();\n  }\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(ptr);\n  value_.type = lynx_value_string;\n  base::String::Unsafe::SetStringToEmpty(str);\n}\n\nvoid Value::SetTable(const fml::RefPtr<lepus::Dictionary>& dictionary) {\n  FreeValue();\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(dictionary.get());\n  value_.type = lynx_value_map;\n  dictionary->AddRef();\n}\n\nvoid Value::SetTable(fml::RefPtr<lepus::Dictionary>&& dictionary) {\n  FreeValue();\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(dictionary.AbandonRef());\n  value_.type = lynx_value_map;\n}\n\nvoid Value::SetArray(const fml::RefPtr<lepus::CArray>& ary) {\n  FreeValue();\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(ary.get());\n  value_.type = lynx_value_array;\n  ary->AddRef();\n}\n\nvoid Value::SetArray(fml::RefPtr<lepus::CArray>&& ary) {\n  FreeValue();\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(ary.AbandonRef());\n  value_.type = lynx_value_array;\n}\n\nvoid Value::SetByteArray(const fml::RefPtr<lepus::ByteArray>& src) {\n  FreeValue();\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(src.get());\n  value_.type = lynx_value_arraybuffer;\n  src->AddRef();\n}\n\nvoid Value::SetByteArray(fml::RefPtr<lepus::ByteArray>&& src) {\n  FreeValue();\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(src.AbandonRef());\n  value_.type = lynx_value_arraybuffer;\n}\n\nvoid Value::SetRefCounted(const fml::RefPtr<lepus::RefCounted>& src) {\n  FreeValue();\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(src.get());\n  value_.type = lynx_value_object;\n  value_.tag = static_cast<int32_t>(src->GetRefType());\n  src->AddRef();\n}\n\nvoid Value::SetRefCounted(fml::RefPtr<lepus::RefCounted>&& src) {\n  FreeValue();\n  value_.tag = static_cast<int32_t>(src->GetRefType());\n  value_.val_ptr = reinterpret_cast<lynx_value_ptr>(src.AbandonRef());\n  value_.type = lynx_value_object;\n}\n\nint Value::GetLength() const {\n  if (value_.val_ptr == nullptr) {\n    return 0;\n  }\n  if (IsJSValue()) {\n    uint32_t len;\n    lynx_value_get_length_ext(env_, value_, &len);\n    return len;\n  }\n\n  switch (value_.type) {\n    case lynx_value_array:\n      return static_cast<int>(\n          reinterpret_cast<lepus::CArray*>(value_.val_ptr)->size());\n    case lynx_value_map:\n      return static_cast<int>(\n          reinterpret_cast<lepus::Dictionary*>(value_.val_ptr)->size());\n    case lynx_value_string:\n      return static_cast<int>(\n          reinterpret_cast<base::RefCountedStringImpl*>(value_.val_ptr)\n              ->length_utf8());\n    default:\n      break;\n  }\n\n  return 0;\n}\n\nbool Value::IsEqual(const Value& value) const { return (*this == value); }\n\nbool Value::SetProperty(uint32_t idx, const Value& val) {\n  if (IsJSArray()) {\n    return lynx_value_set_element_ext(env_, value_, idx, val.value_) ==\n           lynx_api_ok;\n  }\n\n  if (IsArray() && value_.val_ptr != nullptr) {\n    return reinterpret_cast<lepus::CArray*>(value_.val_ptr)->set(idx, val);\n  }\n  return false;\n}\n\nbool Value::SetProperty(uint32_t idx, Value&& val) {\n  if (IsJSArray()) {\n    return lynx_value_set_element_ext(env_, value_, idx, val.value_) ==\n           lynx_api_ok;\n  }\n\n  if (IsArray() && value_.val_ptr != nullptr) {\n    return reinterpret_cast<lepus::CArray*>(value_.val_ptr)\n        ->set(idx, std::move(val));\n  }\n  return false;\n}\n\nbool Value::SetProperty(const base::String& key, const Value& val) {\n  if (IsJSTable()) {\n    return lynx_value_set_named_property_ext(env_, value_, key.c_str(),\n                                             val.value_) == lynx_api_ok;\n  }\n\n  if (IsTable() && value_.val_ptr != nullptr) {\n    reinterpret_cast<lepus::Dictionary*>(value_.val_ptr)->SetValue(key, val);\n  }\n  return false;\n}\n\nbool Value::SetProperty(base::String&& key, const Value& val) {\n  if (IsJSTable()) {\n    return lynx_value_set_named_property_ext(env_, value_, key.c_str(),\n                                             val.value_) == lynx_api_ok;\n  }\n\n  if (IsTable() && value_.val_ptr != nullptr) {\n    return reinterpret_cast<lepus::Dictionary*>(value_.val_ptr)\n        ->SetValue(std::move(key), val)\n        .has_value();\n  }\n  return false;\n}\n\nbool Value::SetProperty(base::String&& key, Value&& val) {\n  if (IsJSTable()) {\n    return lynx_value_set_named_property_ext(env_, value_, key.c_str(),\n                                             val.value_) == lynx_api_ok;\n  }\n\n  if (IsTable() && value_.val_ptr != nullptr) {\n    return reinterpret_cast<lepus::Dictionary*>(value_.val_ptr)\n        ->SetValue(std::move(key), std::move(val))\n        .has_value();\n  }\n  return false;\n}\n\nValue Value::GetProperty(uint32_t idx) const {\n  if (IsJSArray()) {\n    lynx_value result;\n    lynx_value_get_element_ext(env_, value_, idx, &result);\n    return Value(env_, std::move(result));\n  }\n\n  if (IsArray()) {\n    if (value_.val_ptr != nullptr) {\n      return reinterpret_cast<lepus::CArray*>(value_.val_ptr)->get(idx);\n    }\n  } else if (value_.type == lynx_value_string) {\n    auto* ptr = reinterpret_cast<base::RefCountedStringImpl*>(value_.val_ptr);\n    if (ptr->length() > idx) {\n      char ss[2] = {ptr->str()[idx], 0};\n      return Value(base::String(ss, 1));\n    }\n  } else if (IsJSString()) {\n    const auto& s = StdString();\n    if (s.length() > idx) {\n      char ss[2] = {s.c_str()[idx], 0};\n      return lepus::Value(base::String(ss, 1));\n    }\n  }\n\n  return Value();\n}\n\nValue Value::GetProperty(const base::String& key) const {\n  if (IsJSTable()) {\n    lynx_value result;\n    lynx_value_get_named_property_ext(env_, value_, key.c_str(), &result);\n    return Value(env_, std::move(result));\n  }\n  if (IsTable() && value_.val_ptr != nullptr) {\n    return reinterpret_cast<lepus::Dictionary*>(value_.val_ptr)->GetValue(key);\n  }\n  return Value();\n}\n\nbool Value::Contains(const base::String& key) const {\n  if (IsJSTable()) {\n    bool ret;\n    lynx_value_has_named_property_ext(env_, value_, key.c_str(), &ret);\n    return ret;\n  }\n  if (IsTable() && value_.val_ptr != nullptr) {\n    return reinterpret_cast<lepus::Dictionary*>(value_.val_ptr)->Contains(key);\n  }\n  return false;\n}\n\nvoid Value::MergeValue(lepus::Value& target, const lepus::Value& update) {\n  if (update.IsJSTable()) {\n    ForEachLepusValue(update, [&target](const Value& key, const Value& val) {\n      // the update key may be a path\n      auto path = lepus::ParseValuePath(key.StdString());\n      if (!path.empty()) {\n        UpdateValueByPath(target, val.ToLepusValue(), path);\n      }\n    });\n    return;\n  }\n  // check target's first level variable.\n  // 1. if update key is not path, simply add new k-v pair for the first\n  // level\n  // 2. if update key is value path, clone the first level k-v pair and\n  // update\n  //     the exact value.\n  auto update_table =\n      update.IsTable()\n          ? reinterpret_cast<lepus::Dictionary*>(update.value_.val_ptr)\n          : nullptr;\n  if (update_table == nullptr) {\n    return;\n  }\n  auto target_table =\n      target.IsTable()\n          ? reinterpret_cast<lepus::Dictionary*>(target.value_.val_ptr)\n          : nullptr;\n  for (auto& it : *update_table) {\n    auto result = lepus::ParseValuePath(it.first.str());\n    if (result.size() == 1) {\n      target.SetProperty(it.first, it.second);\n    } else if (result.size() > 1) {\n      if (target_table != nullptr) {\n        auto front_value = result.begin();\n        lepus_value old_value = target_table->GetValue(*front_value);\n        if ((old_value.IsTable() && old_value.Table()->IsConst()) ||\n            (old_value.IsArray() && old_value.Array()->IsConst())) {\n          old_value = lepus_value::Clone(old_value);\n        }\n        result.erase(front_value);\n        UpdateValueByPath(old_value, it.second, result);\n        target_table->SetValue(*front_value, old_value);\n      }\n    }\n  }\n}\n\nbool Value::UpdateValueByPath(lepus::Value& target, const lepus::Value& update,\n                              const base::Vector<std::string>& path) {\n  // Feature: if path is empty, update target directly\n  // Many uses rely on this feature, please do not touch it.\n  if (path.empty()) {\n    target = update;\n    return true;\n  }\n\n  /**\n   * example:\n   * path: [\"a\", \"b\", \"c\", \"d\"]\n   *         |    |    |    |\n   *        get  get  get  set\n   */\n  auto current = target;\n  std::for_each(path.begin(), path.end() - 1, [&current](const auto& key) {\n    auto next = current.GetPropertyFromTableOrArray(key);\n    std::swap(current, next);\n  });\n  return current.SetPropertyToTableOrArray(path.back(), update);\n}\n\nValue Value::GetPropertyFromTableOrArray(const std::string& key) const {\n  if (IsTable() || IsJSTable()) {\n    return GetProperty(key);\n  }\n\n  if (IsArray() || IsJSArray()) {\n    int index;\n    if (lynx::base::StringToInt(key, &index, 10)) {\n      return GetProperty(index);\n    }\n  }\n\n  return Value();\n}\n\nbool Value::SetPropertyToTableOrArray(const std::string& key,\n                                      const Value& update) {\n  if (IsTable() || IsJSTable()) {\n    return SetProperty(key, update);\n  }\n\n  if (IsArray() || IsJSArray()) {\n    int index;\n    if (lynx::base::StringToInt(key, &index, 10)) {\n      return SetProperty(index, update);\n    }\n  }\n\n  return false;\n}\n\n// don't support Closure, CFunction, Cpoint\n// nested use of recursive implementation to prevent excessive trace\n// instrumentation\nValue Value::Clone(const Value& src, bool clone_as_jsvalue) {\n  return CloneRecursively(src, clone_as_jsvalue);\n}\n\nValue Value::CloneRecursively(const Value& src, bool clone_as_jsvalue) {\n  if (src.IsJSValue()) {\n    if (clone_as_jsvalue) {\n      return Value(src.env_, src.DeepCopyExtendedValue());\n    } else {\n      return ToLepusValue(src.env_, src.value_, 1);\n    }\n  }\n  switch (src.value_.type) {\n    case lynx_value_null:\n      return Value();\n    case lynx_value_undefined: {\n      Value v;\n      v.SetUndefined();\n      return v;\n    }\n    case lynx_value_double: {\n      double data = src.Number();\n      return Value(data);\n    }\n    case lynx_value_int32:\n      return Value(src.Int32());\n    case lynx_value_int64:\n      return Value(src.Int64());\n    case lynx_value_uint32:\n      return Value(src.UInt32());\n    case lynx_value_uint64:\n      return Value(src.UInt64());\n    case lynx_value_bool:\n      return Value(src.Bool());\n    case lynx_value_nan:\n      return Value(Value::kCreateAsNanTag);\n    case lynx_value_string: {\n      return Value(src.String());\n    }\n    case lynx_value_map: {\n      auto lepus_map = lepus::Dictionary::Create();\n      const auto src_tbl =\n          reinterpret_cast<lepus::Dictionary*>(src.value_.val_ptr);\n      if (src_tbl != nullptr) {\n        lepus_map->reserve(src_tbl->size());\n        src_tbl->for_each([&](const auto& key, const auto& value) {\n          Dictionary::Unsafe::SetValueUniqueKey(*lepus_map, key,\n                                                Value::Clone(value));\n        });\n      }\n      return Value(std::move(lepus_map));\n    }\n    case lynx_value_array: {\n      auto ary = CArray::Create();\n      const auto src_ary = reinterpret_cast<lepus::CArray*>(src.value_.val_ptr);\n      if (src_ary != nullptr) {\n        ary->reserve(src_ary->size());\n        for (size_t i = 0; i < src_ary->size(); ++i) {\n          ary->emplace_back(Value::Clone(src_ary->get(i)));\n        }\n      }\n      return Value(std::move(ary));\n    }\n    case lynx_value_object: {\n      RefType ref_type = static_cast<RefType>(src.value_.tag);\n      switch (ref_type) {\n        case RefType::kJSIObject:\n          return Value(src.RefCounted()->Clone());\n#if !ENABLE_JUST_LEPUSNG\n        case RefType::kCDate: {\n          auto date = src.RefCounted()->Clone();\n          return Value(std::move(date));\n        }\n#endif\n        default:\n          break;\n      }\n    } break;\n    case lynx_value_function:\n    case lynx_value_external:\n      break;\n    default:\n      LOGE(\"!! Value::Clone unknown type: \"\n           << static_cast<uint32_t>(src.value_.type));\n      break;\n  }\n  return Value();\n}\n\n// copy the first level, and mark last as const.\nValue Value::ShallowCopy(const Value& src, bool clone_as_jsvalue) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, VALUE_SHADOW_COPY);\n  if (src.IsJSValue()) {\n    if (clone_as_jsvalue) {\n      return Value(src.env_, src.DeepCopyExtendedValue());\n    } else {\n      return ToLepusValue(src.env_, src.value_, 2);\n    }\n  }\n  switch (src.value_.type) {\n    case lynx_value_map: {\n      auto lepus_map = lepus::Dictionary::Create();\n      const auto src_tbl =\n          reinterpret_cast<lepus::Dictionary*>(src.value_.val_ptr);\n      if (src_tbl != nullptr) {\n        lepus_map->reserve(src_tbl->size());\n        src_tbl->for_each([&](const auto& key, auto& value) {\n          if (value.MarkConst()) {\n            Dictionary::Unsafe::SetValueUniqueKey(*lepus_map, key, value);\n          } else {\n            Dictionary::Unsafe::SetValueUniqueKey(*lepus_map, key,\n                                                  Value::Clone(value));\n          }\n        });\n      }\n      return Value(std::move(lepus_map));\n    }\n    case lynx_value_array: {\n      auto ary = CArray::Create();\n      const auto src_ary = reinterpret_cast<lepus::CArray*>(src.value_.val_ptr);\n      if (src_ary != nullptr) {\n        ary->reserve(src_ary->size());\n        for (size_t i = 0; i < src_ary->size(); ++i) {\n          if (src_ary->get(i).MarkConst()) {\n            ary->push_back(src_ary->get(i));\n          } else {\n            ary->emplace_back(Value::Clone(src_ary->get(i)));\n          }\n        }\n      }\n      return Value(std::move(ary));\n    }\n    default:\n      break;\n  }\n  return Value::Clone(src);\n}\n\nbool operator==(const Value& left, const Value& right) {\n  if (&left == &right) {\n    return true;\n  }\n  // process JSValue type\n  if (left.IsJSValue() && right.IsJSValue()) {\n    bool ret;\n    lynx_value_equals_ext(left.env_, left.value_, right.value_, &ret);\n    return ret;\n  } else if (right.IsJSValue()) {\n    return Value::IsLepusValueEqualToExtendedValue(right.env_, left,\n                                                   right.value_);\n  } else if (left.IsJSValue()) {\n    return Value::IsLepusValueEqualToExtendedValue(left.env_, right,\n                                                   left.value_);\n  }\n  if (left.IsNumber() && right.IsNumber()) {\n    return fabs(left.Number() - right.Number()) < 0.000001;\n  }\n  if (left.value_.type != right.value_.type) return false;\n  switch (left.value_.type) {\n    case lynx_value_null:\n      return true;\n    case lynx_value_undefined:\n      return true;\n    case lynx_value_double:\n      return fabs(left.Number() - right.Number()) < 0.000001;\n    case lynx_value_bool:\n      return left.Bool() == right.Bool();\n    case lynx_value_nan:\n      return false;\n    case lynx_value_string:\n      return left.StdString() == right.StdString();\n    case lynx_value_function:\n      return left.Ptr() == right.Ptr();\n    case lynx_value_external:\n      return left.Ptr() == right.Ptr();\n    case lynx_value_map:\n      return *(left.Table().get()) == *(right.Table().get());\n    case lynx_value_array:\n      return *(left.Array().get()) == *(right.Array().get());\n    case lynx_value_arraybuffer:\n      // TODO(frendy): add impl\n      break;\n    case lynx_value_object: {\n      if (!left.RefCounted() && !right.RefCounted()) {\n        return true;\n      }\n      return left.RefCounted() && left.RefCounted()->Equals(right.RefCounted());\n    }\n    case lynx_value_int32:\n    case lynx_value_int64:\n    case lynx_value_uint32:\n    case lynx_value_uint64:\n    case lynx_value_extended:\n      // handled, ignore\n      break;\n    default:\n      break;\n  }\n  return false;\n}\n\nbool Value::MarkConst() const {\n  switch (value_.type) {\n    case lynx_value_null ... lynx_value_string:\n    case lynx_value_arraybuffer:\n    case lynx_value_function:\n    case lynx_value_function_table:\n    case lynx_value_external:\n      // ByteArray and Element objects don't cross thread, and don't need to\n      // markConst.\n      return true;\n    case lynx_value_object: {\n      RefType ref_type = static_cast<RefType>(value_.tag);\n      if (ref_type < RefType::kJSIObject) {\n        reinterpret_cast<lepus::RefCounted*>(value_.val_ptr)\n            ->js_object_cache.reset();\n      }\n      return true;\n    }\n    case lynx_value_map:\n      return reinterpret_cast<lepus::Dictionary*>(value_.val_ptr)->MarkConst();\n    case lynx_value_array:\n      return reinterpret_cast<lepus::CArray*>(value_.val_ptr)->MarkConst();\n    case lynx_value_extended:\n      // JSValue\n      bool ret;\n      lynx_value_has_ref_count_ext(env_, value_, &ret);\n      if (ret) {\n        return false;\n      }\n      // Primitive type value can be lightly converted to lepus::Value.\n      ToLepusValue();\n      return true;\n  }\n}\n\nvoid Value::FreeValue() {\n  if (unlikely(IsJSValue())) {\n    lynx_value_remove_reference_ext(env_, value_, value_ref_);\n    value_ref_ = nullptr;\n  } else if (IsReference() && likely(value_.val_ptr)) {\n    reinterpret_cast<fml::RefCountedThreadSafeStorage*>(value_.val_ptr)\n        ->Release();\n  }\n}\n\nvoid Value::ResetValueRef() {\n  if (IsJSValue()) {\n    lynx_value_remove_reference_ext(env_, value_, value_ref_);\n    value_ref_ = nullptr;\n  }\n}\n\ndouble Value::Double() const {\n  if (value_.type != lynx_value_double) {\n    return 0;\n  }\n  return value_.val_double;\n}\n\nint32_t Value::Int32() const {\n  if (value_.type != lynx_value_int32) {\n    return 0;\n  }\n  return value_.val_int32;\n}\n\nuint32_t Value::UInt32() const {\n  if (value_.type != lynx_value_uint32) {\n    return 0;\n  }\n  return value_.val_uint32;\n}\n\nuint64_t Value::UInt64() const {\n  if (value_.type != lynx_value_uint64) {\n    return 0;\n  }\n  return value_.val_uint64;\n}\n\nint64_t Value::Int64() const {\n  if (value_.type == lynx_value_int64) return value_.val_int64;\n  if (IsJSInteger()) {\n    return JSInteger();\n  }\n  return 0;\n}\n\nbool Value::IsJSArray() const {\n  if (unlikely(!IsJSValue())) return false;\n  bool ret;\n  lynx_value_is_array_ext(env_, value_, &ret);\n  return ret;\n}\n\nbool Value::IsJSTable() const {\n  if (unlikely(!IsJSValue())) return false;\n  bool ret;\n  lynx_value_is_map_ext(env_, value_, &ret);\n  return ret;\n}\n\nbool Value::IsJSInteger() const {\n  if (!IsJSValue()) return false;\n  bool ret;\n  lynx_value_is_integer_ext(env_, value_, &ret);\n  return ret;\n}\n\nbool Value::IsJSFunction() const {\n  if (!IsJSValue()) return false;\n  bool ret;\n  lynx_value_is_function_ext(env_, value_, &ret);\n  return ret;\n}\n\nint Value::GetJSLength() const {\n  if (!IsJSValue()) return 0;\n  uint32_t len;\n  lynx_value_get_length_ext(env_, value_, &len);\n  return static_cast<int>(len);\n}\n\nbool Value::IsJSFalse() const {\n  if (!IsJSValue()) return false;\n\n  return IsJSUndefined() || IsJsNull() || IsJSUninitialized() ||\n         (IsJSBool() && !LEPUSBool()) || (IsJSInteger() && JSInteger() == 0) ||\n         (IsJSString() && GetJSLength() == 0);\n}\n\nint64_t Value::JSInteger() const {\n  if (!IsJSValue()) return false;\n  int64_t ret;\n  lynx_value_get_integer_ext(env_, value_, &ret);\n  return ret;\n}\n\nstd::string Value::ToString() const {\n  if (!IsJSValue()) {\n    // judge whether it is a lepus string type\n    if (IsString()) {\n      return StdString();\n    }\n    // it is not string then return \"\"\n    return \"\";\n  }\n  std::string str;\n  lynx_value_to_string_utf8_ext(env_, value_, &str);\n  return str;\n}\n\nvoid Value::IteratorJSValue(const LepusValueIterator& callback) const {\n  if (IsJSValue() && (value_.tag >> 16) == lynx_value_object) {\n    ExtendedValueIteratorCallback callback_wrap =\n        [&callback](lynx_api_env env, const lynx_value& key,\n                    const lynx_value& value) {\n          lepus::Value keyWrap(env, key);\n          lepus::Value valueWrap(env, value);\n          callback(keyWrap, valueWrap);\n        };\n    IterateExtendedValue(env_, value_, &callback_wrap);\n  }\n}\n\ndouble Value::LEPUSNumber() const {\n  DCHECK(IsJSNumber());\n  if (unlikely(!IsJSValue())) return 0;\n  double ret;\n  lynx_value_get_number_ext(env_, value_, &ret);\n  return ret;\n}\n\nlynx_value_type Value::ToLynxValueType(ValueType type) {\n  switch (type) {\n    case Value_Nil:\n      return lynx_value_null;\n    case Value_Double:\n      return lynx_value_double;\n    case Value_Bool:\n      return lynx_value_bool;\n    case Value_String:\n      return lynx_value_string;\n    case Value_Table:\n      return lynx_value_map;\n    case Value_Array:\n      return lynx_value_array;\n    case Value_CFunction:\n      return lynx_value_function;\n    case Value_CPointer:\n      return lynx_value_external;\n    case Value_Int32:\n      return lynx_value_int32;\n    case Value_Int64:\n      return lynx_value_int64;\n    case Value_UInt32:\n      return lynx_value_uint32;\n    case Value_UInt64:\n      return lynx_value_uint64;\n    case Value_NaN:\n      return lynx_value_nan;\n    case Value_RefCounted:\n    case Value_Closure:\n    case Value_CDate:\n    case Value_RegExp:\n    case Value_JSObject:\n      return lynx_value_object;\n    case Value_Undefined:\n      return lynx_value_undefined;\n    case Value_ByteArray:\n      return lynx_value_arraybuffer;\n    default:\n      return lynx_value_extended;\n  }\n}\n\nValueType Value::LegacyTypeFromLynxValue(const lynx_value& value) {\n  switch (value.type) {\n    case lynx_value_null:\n      return Value_Nil;\n    case lynx_value_undefined:\n      return Value_Undefined;\n    case lynx_value_bool:\n      return Value_Bool;\n    case lynx_value_double:\n      return Value_Double;\n    case lynx_value_int32:\n      return Value_Int32;\n    case lynx_value_uint32:\n      return Value_UInt32;\n    case lynx_value_int64:\n      return Value_Int64;\n    case lynx_value_uint64:\n      return Value_UInt64;\n    case lynx_value_nan:\n      return Value_NaN;\n    case lynx_value_string:\n      return Value_String;\n    case lynx_value_array:\n      return Value_Array;\n    case lynx_value_map:\n      return Value_Table;\n    case lynx_value_arraybuffer:\n      return Value_ByteArray;\n    case lynx_value_function:\n      return Value_CFunction;\n    case lynx_value_function_table:\n      return Value_FunctionTable;\n    case lynx_value_object: {\n      RefType type = static_cast<RefType>(value.tag);\n      switch (type) {\n        case RefType::kJSIObject:\n          return Value_JSObject;\n        case RefType::kClosure:\n          return Value_Closure;\n        case RefType::kCDate:\n          return Value_CDate;\n        case RefType::kRegExp:\n          return Value_RegExp;\n        default:\n          return Value_RefCounted;\n          break;\n      }\n    } break;\n    case lynx_value_external:\n      return Value_CPointer;\n    case lynx_value_extended:\n      return Value_TypeCount;\n    default:\n      break;\n  }\n  return Value_Nil;\n}\n\nValue Value::ToLepusValue(lynx_api_env env, const lynx_value& val,\n                          int32_t flag) {\n  if (!env) {\n    goto ret_empty;\n  }\n  if (val.type != lynx_value_extended) {\n    if (likely(flag == 0)) {\n      return Value(env, val).ToLepusValue();\n    } else if (flag == 1) {\n      return Value::Clone(Value(env, val));\n    } else {\n      Value ret(env, val);\n      if (!ret.MarkConst()) {\n        ret = Value::Clone(ret);\n      }\n      return ret;\n    }\n  }\n  lynx_value_type type;\n  lynx_value_typeof_ext(env, val, &type);\n  switch (type) {\n    case lynx_value_null:\n      return lepus::Value();\n    case lynx_value_undefined: {\n      return lepus::Value(Value::kCreateAsUndefinedTag);\n    }\n    case lynx_value_bool: {\n      bool ret;\n      lynx_value_get_bool_ext(env, val, &ret);\n      return lepus::Value(ret);\n    }\n    case lynx_value_double: {\n      double ret;\n      lynx_value_get_double_ext(env, val, &ret);\n      return lepus::Value(ret);\n    }\n    case lynx_value_int32: {\n      int32_t ret;\n      lynx_value_get_int32_ext(env, val, &ret);\n      return lepus::Value(ret);\n    }\n    case lynx_value_int64: {\n      int64_t ret;\n      lynx_value_get_int64_ext(env, val, &ret);\n      return lepus::Value(ret);\n    }\n    case lynx_value_string: {\n      void* str;\n      lynx_value_get_string_ref_ext(env, val, &str);\n      auto* base_str = reinterpret_cast<base::RefCountedStringImpl*>(str);\n      return lepus::Value(\n          base::String::Unsafe::ConstructWeakRefStringFromRawRef(base_str));\n    }\n    case lynx_value_array: {\n      return ToLepusArray(env, val, flag);\n    }\n    case lynx_value_map: {\n      return ToLepusMap(env, val, flag);\n    }\n    case lynx_value_function: {\n      if (flag == 0) {\n        return lepus::Value(env, val);\n      }\n      break;\n    }\n    case lynx_value_external: {\n      void* ret;\n      lynx_value_get_external_ext(env, val, &ret);\n      return lepus::Value(ret);\n    }\n    default:\n      LOGE(\"not support type:\" << static_cast<uint32_t>(type));\n      break;\n  }\n\nret_empty:\n  static Value empty_value;\n  return empty_value;\n}\n\nValue Value::ToLepusArray(lynx_api_env env, const lynx_value& val,\n                          int32_t flag) {\n  auto arr = CArray::Create();\n  ExtendedValueIteratorCallback callback =\n      [&arr, flag](lynx_api_env env, const lynx_value& key,\n                   const lynx_value& value) {\n        arr->emplace_back(ToLepusValue(env, value, flag));\n      };\n  IterateExtendedValue(env, val, &callback);\n  return Value(std::move(arr));\n}\n\nValue Value::ToLepusMap(lynx_api_env env, const lynx_value& val, int32_t flag) {\n  auto map = Dictionary::Create();\n  ExtendedValueIteratorCallback callback =\n      [&map, flag](lynx_api_env env, const lynx_value& key,\n                   const lynx_value& value) {\n        std::string str;\n        lynx_value_to_string_utf8_ext(env, key, &str);\n        Dictionary::Unsafe::SetValueUniqueKey(*map, std::move(str),\n                                              ToLepusValue(env, value, flag));\n      };\n  IterateExtendedValue(env, val, &callback);\n  return Value(std::move(map));\n}\n\nbool Value::IsLepusValueEqualToExtendedValue(lynx_api_env env,\n                                             const lepus::Value& src,\n                                             const lynx_value& dst) {\n  lynx_value_type type;\n  lynx_value_typeof_ext(env, dst, &type);\n  if (type == lynx_value_array) {\n    if (!src.IsArray()) return false;\n    return IsLepusArrayEqualToExtendedArray(env, src.Array().get(), dst);\n  } else if (type == lynx_value_map) {\n    if (!src.IsTable()) return false;\n    return IsLepusDictEqualToExtendedDict(env, src.Table().get(), dst);\n  } else if (type == lynx_value_function) {\n    return false;\n  }\n\n  return src == ToLepusValue(env, dst);\n}\n\nbool Value::IsLepusArrayEqualToExtendedArray(lynx_api_env env,\n                                             lepus::CArray* src,\n                                             const lynx_value& dst) {\n  uint32_t len;\n  lynx_value_get_length_ext(env, dst, &len);\n  if (src->size() != static_cast<size_t>(len)) {\n    return false;\n  }\n  for (uint32_t i = 0; i < src->size(); i++) {\n    lynx_value val;\n    lynx_api_status status = lynx_value_get_element_ext(env, dst, i, &val);\n    if (status != lynx_api_ok) return false;\n    lepus::Value dst_element(env, std::move(val));\n    if (src->get(i) != dst_element) return false;\n  }\n  return true;\n}\n\nbool Value::IsLepusDictEqualToExtendedDict(lynx_api_env env,\n                                           lepus::Dictionary* src,\n                                           const lynx_value& dst) {\n  uint32_t len;\n  lynx_value_get_length_ext(env, dst, &len);\n  if (src->size() != static_cast<size_t>(len)) {\n    return false;\n  }\n  bool result = true;\n  src->for_each([&](const auto& key, Value& value) {\n    lynx_value val;\n    lynx_api_status status =\n        lynx_value_get_named_property_ext(env, dst, key.c_str(), &val);\n    if (status != lynx_api_ok) {\n      result = false;\n      return true;  // stop\n    }\n    lepus::Value dst_property(env, std::move(val));\n    if (value != dst_property) {\n      result = false;\n      return true;  // stop\n    }\n    return false;  // continue\n  });\n  return result;\n}\n\n// TODO(frendy): Remove lynx::tasm:ForEachLepusValue\nvoid Value::ForEachLepusValue(const lepus::Value& value,\n                              LepusValueIterator func) {\n  if (value.IsJSValue()) {\n    value.IteratorJSValue(std::move(func));\n    return;\n  }\n\n  switch (value.value_.type) {\n    case lynx_value_map: {\n      auto value_scope_ref_ptr = value.Table();\n      auto& table = *value_scope_ref_ptr;\n      table.for_each([&](const auto& key, const auto& value) {\n        func(lepus::Value(key), value);\n      });\n    } break;\n    case lynx_value_array: {\n      auto value_scope_ref_ptr = value.Array();\n      auto& array = *value_scope_ref_ptr;\n      for (auto i = decltype(array.size()){}; i < array.size(); ++i) {\n        func(lepus::Value{static_cast<int64_t>(i)}, array.get(i));\n      }\n    } break;\n    default: {\n      func(lepus::Value{}, value);\n    } break;\n  }\n}\n\nBASE_NEVER_INLINE CArray* Value::DummyArray() {\n  static thread_local CArray dummy;\n  dummy.Reset();\n  return &dummy;\n}\n\nBASE_NEVER_INLINE Dictionary* Value::DummyTable() {\n  static thread_local Dictionary dummy;\n  dummy.Reset();\n  return &dummy;\n}\n\nBASE_NEVER_INLINE ByteArray* Value::DummyByteArray() {\n  static thread_local lepus::ByteArray dummy(nullptr, 0);\n  dummy.Reset();\n  return &dummy;\n}\n\n}  // namespace lepus\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/value/base_value_print.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <sstream>\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"base/include/sorted_for_each.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/lynx_value_extended.h\"\n#include \"base/include/value/table.h\"\n\n/// TODO(yuyang). The two Print methods of Value are moved to this separate file\n/// to temporarily solve the random code degradation problem caused by the \"-Oz\"\n/// compiler option. The degraded code is in the `Value::Copy` method, and the\n/// compiler will insert unexpected outlined function.\n\nnamespace lynx {\nnamespace lepus {\n\nvoid Value::Print() const {\n  std::ostringstream s;\n  PrintValue(s);\n  LOGE(s.str() << std::endl);\n}\n\nvoid Value::PrintValue(std::ostream& output, bool ignore_other, bool pretty,\n                       bool sort_map_key) const {\n  if (IsJSValue()) {\n    lynx_value_print_ext(env_, value_, &output, nullptr);\n    return;\n  }\n  int64_t i64;\n  uint64_t u64;\n  switch (value_.type) {\n    case lynx_value_null:\n      if (!ignore_other) {\n        output << \"null\";\n      }\n      break;\n    case lynx_value_undefined:\n      if (!ignore_other) {\n        output << \"undefined\";\n      }\n      break;\n    case lynx_value_double:\n      output << base::StringConvertHelper::DoubleToString(Number());\n      break;\n    case lynx_value_int32:\n      i64 = Int32();\n      goto write_i64;\n    case lynx_value_int64:\n      i64 = Int64();\n    write_i64:\n      output << i64;\n      break;\n    case lynx_value_uint32:\n      u64 = UInt32();\n      goto write_u64;\n    case lynx_value_uint64:\n      u64 = UInt64();\n    write_u64:\n      output << u64;\n      break;\n    case lynx_value_bool:\n      output << (Bool() ? \"true\" : \"false\");\n      break;\n    case lynx_value_string:\n      if (pretty) {\n        output << \"\\\"\" << CString() << \"\\\"\";\n      } else {\n        output << CString();\n      }\n      break;\n    case lynx_value_map: {\n      auto table = Table();\n      output << \"{\";\n\n      bool first = true;\n      auto print_each = [&](const auto& it) {\n        if (!first) {\n          output << \",\";\n        }\n        if (pretty) {\n          output << \"\\\"\" << it.first.str() << \"\\\"\";\n        } else {\n          output << it.first.str();\n        }\n        output << \":\";\n        it.second.PrintValue(output, ignore_other, pretty, sort_map_key);\n        first = false;\n      };\n\n      if (sort_map_key) {\n        base::sorted_for_each(\n            table->begin(), table->end(),\n            [&](const auto& it) { print_each(it); },\n            [](const auto& left, const auto& right) {\n              return left.first.str() < right.first.str();\n            });\n      } else {\n        for (const auto& it : *table) {\n          print_each(it);\n        }\n      }\n      output << \"}\";\n      break;\n    }\n    case lynx_value_array: {\n      auto array = Array();\n      output << \"[\";\n      for (size_t i = 0; i < array->size(); i++) {\n        array->get(i).PrintValue(output, ignore_other, pretty, sort_map_key);\n        if (i != (array->size() - 1)) {\n          output << \",\";\n        }\n      }\n      output << \"]\";\n      break;\n    }\n    case lynx_value_function:\n    case lynx_value_external:\n      if (!ignore_other) {\n        output << \"closure/cfunction/cpointer/refcounted\" << std::endl;\n      }\n      break;\n    case lynx_value_object: {\n      RefType ref_type = static_cast<RefType>(value_.tag);\n      switch (ref_type) {\n        case RefType::kJSIObject:\n          if (!ignore_other) {\n            RefCounted()->Print(output);\n          }\n          break;\n#if !ENABLE_JUST_LEPUSNG\n        case RefType::kClosure:\n          if (!ignore_other) {\n            output << \"closure/cfunction/cpointer/refcounted\" << std::endl;\n          }\n          break;\n        case RefType::kCDate:\n          if (!ignore_other) {\n            RefCounted()->Print(output);\n          }\n          break;\n        case RefType::kRegExp: {\n          RefCounted()->Print(output);\n        } break;\n#endif\n        default:\n          // RefCounted\n          if (!ignore_other) {\n            output << \"closure/cfunction/cpointer/refcounted\" << std::endl;\n          }\n          break;\n      }\n    } break;\n    case lynx_value_nan:\n      if (!ignore_other) {\n        output << \"NaN\";\n      }\n      break;\n    case lynx_value_arraybuffer:\n      if (!ignore_other) {\n        output << \"ByteArray\";\n      }\n      break;\n    default:\n      if (!ignore_other) {\n        output << \"unknow type\";  // typo\n      }\n      break;\n  }\n}\n\n}  // namespace lepus\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/value/base_value_unittest.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/value/base_value.h\"\n\n#include <functional>\n#include <unordered_set>\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/byte_array.h\"\n#include \"base/include/value/lynx_value_api.h\"\n#include \"base/include/value/table.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass BaseValueTest : public ::testing::Test {\n protected:\n  BaseValueTest() = default;\n  ~BaseValueTest() override = default;\n\n  void SetUp() override {}\n  void TearDown() override {}\n};\n\nTEST_F(BaseValueTest, BaseValueNull) {\n  lepus::Value v1;\n  ASSERT_TRUE(v1.IsNil());\n  ASSERT_TRUE(!v1.IsReference());\n  ASSERT_TRUE(v1.IsFalse());\n  ASSERT_TRUE(v1.Type() == lepus::Value_Nil);\n  lepus::Value v2;\n  v2.SetNil();\n  ASSERT_TRUE(v2.IsNil());\n  ASSERT_TRUE(v2.IsEmpty());\n\n  lepus::Value v3;\n  v3.SetUndefined();\n  ASSERT_TRUE(v3.IsUndefined());\n  ASSERT_TRUE(v3.IsEmpty());\n  ASSERT_TRUE(v3.IsFalse());\n  ASSERT_TRUE(v3.Type() == lepus::Value_Undefined);\n}\n\nTEST_F(BaseValueTest, BaseValueNumber) {\n  {\n    lepus::Value v1(lepus::Value::kCreateAsNanTag);\n    ASSERT_TRUE(v1.IsNaN());\n    ASSERT_TRUE(v1.NaN());\n    ASSERT_TRUE(v1.Type() == lepus::Value_NaN);\n    lepus::Value v2;\n    v2.SetNan(true);\n    ASSERT_TRUE(v2.IsNaN());\n    ASSERT_TRUE(v2.NaN());\n    ASSERT_TRUE(v2.IsFalse());\n  }\n  {\n    lepus::Value v3((int32_t)10);\n    ASSERT_TRUE(v3.IsInt32());\n    ASSERT_TRUE(v3.IsNumber());\n    ASSERT_TRUE(v3.Int32() == 10);\n    ASSERT_TRUE(v3.Type() == lepus::Value_Int32);\n    v3.SetNumber((uint32_t)100);\n    ASSERT_TRUE(v3.IsUInt32());\n    ASSERT_TRUE(v3.UInt32() == 100);\n    ASSERT_TRUE(v3.Type() == lepus::Value_UInt32);\n    lepus::Value v4((uint32_t)50);\n    ASSERT_TRUE(v4.IsUInt32());\n    ASSERT_TRUE(v4.IsNumber());\n    ASSERT_TRUE(v4.UInt32() == 50);\n    v4.SetNumber((int32_t)101);\n    ASSERT_TRUE(v4.IsInt32());\n    ASSERT_TRUE(v4.Int32() == 101);\n    int64_t num1 = (int64_t)INT32_MAX + 1;\n    lepus::Value v5(num1);\n    ASSERT_TRUE(v5.IsInt64());\n    ASSERT_TRUE(v5.IsNumber());\n    ASSERT_TRUE(v5.Int64() == num1);\n    ASSERT_TRUE(v5.Type() == lepus::Value_Int64);\n    v5.SetNumber((int32_t)101);\n    ASSERT_TRUE(v5.IsInt32());\n    ASSERT_TRUE(v5.Int32() == 101);\n    uint64_t num2 = (uint64_t)INT32_MAX + 10;\n    lepus::Value v6(num2);\n    ASSERT_TRUE(v6.IsUInt64());\n    ASSERT_TRUE(v6.IsNumber());\n    ASSERT_TRUE(v6.UInt64() == num2);\n    ASSERT_TRUE(v6.Type() == lepus::Value_UInt64);\n    v6.SetNumber((int32_t)101);\n    ASSERT_TRUE(v6.IsInt32());\n    ASSERT_TRUE(v6.Int32() == 101);\n    lepus::Value v7(3.14f);\n    ASSERT_TRUE(v7.IsDouble());\n    ASSERT_TRUE(v7.IsNumber());\n    ASSERT_TRUE(v7.Double() == 3.14f);\n    ASSERT_TRUE(v7.Type() == lepus::Value_Double);\n    v7.SetNumber((int32_t)101);\n    ASSERT_TRUE(v7.IsInt32());\n    ASSERT_TRUE(v7.Int32() == 101);\n    lepus::Value v8((uint8_t)3);\n    ASSERT_TRUE(v8.IsUInt32());\n    ASSERT_TRUE(v8.UInt32() == 3);\n  }\n  {\n    lepus::Value v14((int32_t)10);\n    lepus::Value v15((int32_t)5);\n    ASSERT_TRUE((v14 / v15).Number() == 2);\n    ASSERT_TRUE((v14 * v15).Number() == 50);\n    ASSERT_TRUE((v14 + v15).Number() == 15);\n    ASSERT_TRUE((v14 - v15).Number() == 5);\n    ASSERT_TRUE((v14 % v15).Number() == 0);\n  }\n}\n\nTEST_F(BaseValueTest, BaseValueString) {\n  {\n    base::String s1(\"test\");\n    lepus::Value v1(s1);\n    ASSERT_TRUE(v1.IsString());\n    ASSERT_TRUE(v1.StdString() == \"test\");\n    ASSERT_TRUE(v1.GetLength() == 4);\n    ASSERT_TRUE(v1.IsReference());\n    ASSERT_TRUE(v1.Type() == lepus::Value_String);\n    lepus::Value b1(true);\n    ASSERT_TRUE(b1.IsBool());\n    ASSERT_TRUE(b1.Type() == lepus::Value_Bool);\n    ASSERT_TRUE(b1.StdString() == \"true\");\n    base::String s2(\"test2\");\n    lepus::Value v2(std::move(s2));\n    ASSERT_TRUE(v2.IsString());\n    ASSERT_TRUE(v2.StdString() == \"test2\");\n  }\n  {\n    lepus::Value v3(\"abcd\");\n    ASSERT_TRUE(v3.IsString());\n    ASSERT_TRUE(v3.StdString() == \"abcd\");\n    lepus::Value v4(v3);\n    ASSERT_TRUE(v4.IsString());\n    ASSERT_TRUE(v4.StdString() == \"abcd\");\n    auto str = v4.String();\n    ASSERT_TRUE(str.str() == \"abcd\");\n    auto str1 = lepus::Value(v4).String();\n    ASSERT_TRUE(str1.str() == \"abcd\");\n  }\n  {\n    lepus::Value v5;\n    base::String s3(\"test\");\n    v5.SetString(s3);\n    ASSERT_TRUE(v5.IsString());\n    ASSERT_TRUE(v5.StdString() == \"test\");\n    ASSERT_TRUE(v5.ToString() == \"test\");\n    lepus::Value v6;\n    v6.SetString(base::String(\"aaa\"));\n    ASSERT_TRUE(v6.IsString());\n    ASSERT_TRUE(v6.StdString() == \"aaa\");\n    auto r2 = v5.GetProperty(2);\n    ASSERT_TRUE(r2.StdString() == \"s\");\n    lepus::Value v7(\"\");\n    ASSERT_TRUE(v7.IsFalse());\n  }\n}\n\nTEST_F(BaseValueTest, BaseValueArray) {\n  auto arr1 = lepus::CArray::Create();\n  arr1->push_back(lepus::Value((int32_t)101));\n  arr1->push_back(lepus::Value((int64_t)2001));\n  arr1->push_back(lepus::Value(5.645f));\n  arr1->push_back(lepus::Value(false));\n  arr1->push_back(lepus::Value(\"testing\"));\n  lepus::Value v1(arr1);\n  ASSERT_TRUE(v1.Type() == lepus::Value_Array);\n  ASSERT_TRUE(v1.IsArray());\n  auto r0 = v1.GetProperty(0);\n  ASSERT_TRUE(r0.Int32() == 101);\n  auto r1 = v1.GetProperty(1);\n  ASSERT_TRUE(r1.Int64() == 2001);\n  auto r2 = v1.GetProperty(2);\n  ASSERT_TRUE(r2.Double() == 5.645f);\n  auto r3 = v1.GetProperty(3);\n  ASSERT_TRUE(r3.Bool() == false);\n  auto r4 = v1.GetProperty(4);\n  ASSERT_TRUE(r4.StdString() == \"testing\");\n  v1.SetProperty(5, lepus::Value(true));\n  auto r5 = v1.GetProperty(5);\n  ASSERT_TRUE(r5.Bool() == true);\n  lepus::Value id6(\"666\");\n  v1.SetProperty(6, id6);\n  auto r6 = v1.GetProperty(6);\n  ASSERT_TRUE(r6.StdString() == \"666\");\n  ASSERT_TRUE(v1.GetLength() == 7);\n  ASSERT_TRUE(v1.Array()->size() == 7);\n}\n\nTEST_F(BaseValueTest, BaseValueMap) {\n  auto dict = lepus::Dictionary::Create();\n  dict->SetValue(\"key1\", lepus::Value((int32_t)101));\n  dict->SetValue(\"key2\", lepus::Value((int64_t)2001));\n  dict->SetValue(\"key3\", lepus::Value(5.645f));\n  dict->SetValue(\"key4\", lepus::Value(false));\n  dict->SetValue(\"key5\", lepus::Value(\"testing\"));\n  lepus::Value v1(dict);\n  ASSERT_TRUE(v1.Type() == lepus::Value_Table);\n  ASSERT_TRUE(v1.IsTable());\n  auto r0 = v1.GetProperty(\"key1\");\n  ASSERT_TRUE(r0.Int32() == 101);\n  auto r1 = v1.GetProperty(\"key2\");\n  ASSERT_TRUE(r1.Int64() == 2001);\n  auto r2 = v1.GetProperty(\"key3\");\n  ASSERT_TRUE(r2.Double() == 5.645f);\n  auto r3 = v1.GetProperty(\"key4\");\n  ASSERT_TRUE(r3.Bool() == false);\n  auto r4 = v1.GetProperty(\"key5\");\n  ASSERT_TRUE(r4.StdString() == \"testing\");\n  v1.SetProperty(\"key6\", lepus::Value(true));\n  auto r5 = v1.GetProperty(\"key6\");\n  ASSERT_TRUE(r5.Bool() == true);\n  lepus::Value id6(\"666\");\n  v1.SetProperty(\"key7\", id6);\n  auto r6 = v1.GetProperty(\"key7\");\n  ASSERT_TRUE(r6.StdString() == \"666\");\n  lepus::Value id7(\"abc\");\n  base::String key8(\"key8\");\n  v1.SetProperty(key8, id7);\n  auto r7 = v1.GetProperty(\"key8\");\n  ASSERT_TRUE(r7.StdString() == \"abc\");\n  ASSERT_TRUE(v1.GetLength() == 8);\n  ASSERT_TRUE(v1.Table()->size() == 8);\n  ASSERT_TRUE(v1.Contains(\"key1\"));\n\n  for (auto& it : *(dict.get())) {\n    auto key = it.first.str();\n    if (key == \"key8\") {\n      ASSERT_TRUE(it.second.StdString() == \"abc\");\n    }\n  }\n}\n\nTEST_F(BaseValueTest, BaseValueArrayBuffer) {\n  auto buffer1 = lepus::ByteArray::Create();\n  lepus::Value v1(buffer1);\n  ASSERT_TRUE(v1.IsByteArray());\n  auto buffer2 = lepus::ByteArray::Create();\n  lepus::Value v2(std::move(buffer2));\n  ASSERT_TRUE(v2.IsByteArray());\n  ASSERT_TRUE(v2.ByteArray());\n  ASSERT_TRUE(v2.Type() == lepus::Value_ByteArray);\n}\n\nTEST_F(BaseValueTest, BaseValuePointer) {\n  int* a = new int;\n  *a = 10;\n  lepus::Value v1(a);\n  ASSERT_TRUE(v1.IsCPointer());\n  ASSERT_TRUE(v1.Type() == lepus::Value_CPointer);\n  int* b = reinterpret_cast<int*>(v1.CPoint());\n  ASSERT_TRUE(*a == *b);\n  lepus::Value v2;\n  int* c = new int;\n  v2.SetCPoint(c);\n  int* d = reinterpret_cast<int*>(v2.CPoint());\n  ASSERT_TRUE(v2.IsCPointer());\n  ASSERT_TRUE(!v2.IsReference());\n  ASSERT_TRUE(*c == *d);\n}\n\nTEST_F(BaseValueTest, BaseValueCloneValue) {\n  auto arr_ref = lepus::CArray::Create();\n  auto dict_ref = lepus::Dictionary::Create();\n  auto dict = lepus::Dictionary::Create();\n  dict->SetValue(\"key1\", lepus::Value(\"test_val\"));\n  dict->SetValue(\"key2\", lepus::Value());\n  lepus::Value undefined;\n  undefined.SetUndefined();\n  dict->SetValue(\"key3\", undefined);\n  dict->SetValue(\"key4\", lepus::Value((int32_t)10));\n  dict->SetValue(\"key5\", lepus::Value((uint32_t)11));\n  dict->SetValue(\"key6\", lepus::Value((int64_t)12));\n  dict->SetValue(\"key7\", lepus::Value((uint64_t)13));\n  dict->SetValue(\"key8\", lepus::Value(3.45f));\n  dict->SetValue(\"key9\", lepus::Value(\"string\"));\n  auto arr1 = lepus::CArray::Create();\n  arr1->push_back(lepus::Value(false));\n  arr1->push_back(lepus::Value(\"str\"));\n  dict->SetValue(\"key10\", lepus::Value(arr1));\n  auto dict1 = lepus::Dictionary::Create();\n  dict1->SetValue(\"key1\", lepus::Value(\"string\"));\n  dict->SetValue(\"key11\", lepus::Value(dict1));\n  lepus::Value v1(dict);\n  auto ret1 = lepus::Value::Clone(v1, true);\n  ASSERT_TRUE(ret1.IsTable());\n  auto r1 = ret1.GetProperty(\"key1\");\n  ASSERT_TRUE(r1.StdString() == \"test_val\");\n  ASSERT_TRUE(!r1.IsJSString());\n  auto r2 = ret1.GetProperty(\"key2\");\n  ASSERT_TRUE(r2.IsNil());\n  auto r3 = ret1.GetProperty(\"key3\");\n  ASSERT_TRUE(r3.IsUndefined());\n  auto r4 = ret1.GetProperty(\"key4\");\n  ASSERT_TRUE(r4.Int32() == 10);\n  auto r5 = ret1.GetProperty(\"key5\");\n  ASSERT_TRUE(r5.UInt32() == 11);\n  auto r6 = ret1.GetProperty(\"key6\");\n  ASSERT_TRUE(r6.Int64() == 12);\n  auto r7 = ret1.GetProperty(\"key7\");\n  ASSERT_TRUE(r7.UInt64() == 13);\n  auto r8 = ret1.GetProperty(\"key8\");\n  ASSERT_TRUE(r8.Double() == 3.45f);\n  auto r9 = ret1.GetProperty(\"key9\");\n  ASSERT_TRUE(r9.StdString() == \"string\");\n  auto r10 = ret1.GetProperty(\"key10\");\n  ASSERT_TRUE(r10.Array()->size() == 2);\n  ASSERT_TRUE(r10.Array()->get(1).StdString() == \"str\");\n  auto r11 = ret1.GetProperty(\"key11\");\n  ASSERT_TRUE(r11.Table()->size() == 1);\n  ASSERT_TRUE(ret1.IsEqual(v1));\n  auto ret2 = lepus::Value::Clone(v1, false);\n  ASSERT_TRUE(ret2.GetLength() == 11);\n  ASSERT_TRUE(ret2.IsEqual(v1));\n  ret2.SetProperty(\"key12\", lepus::Value(\"value12\"));\n  ASSERT_TRUE(ret2.GetLength() == 12);\n  ASSERT_TRUE(v1.GetLength() == 11);\n  ASSERT_TRUE(!ret2.GetProperty(\"key1\").IsJSString());\n  ASSERT_TRUE(!ret2.IsEqual(v1));\n  auto ret3 = lepus::Value::ShallowCopy(v1, true);\n  ASSERT_TRUE(ret3.IsEqual(v1));\n  auto ret4 = lepus::Value::ShallowCopy(v1, false);\n  ASSERT_TRUE(ret4.IsEqual(v1));\n}\n\nTEST_F(BaseValueTest, Dictionary) {\n  auto dict = lepus::Dictionary::Create();\n  ASSERT_TRUE(dict->using_small_map());\n  ASSERT_TRUE(dict->empty());\n  ASSERT_TRUE(dict->size() == 0);\n\n  {\n    auto dict = lepus::Dictionary::Create();\n    for (int i = 0; i < (int)lepus::Dictionary::kSmallMapMaximumSize; i++) {\n      dict->SetValue(String(std::to_string(i)), std::to_string(i));\n    }\n    ASSERT_TRUE(dict->using_small_map());\n    // Dictionary iterates and set value from self. No change and no transfer.\n    for (auto it = dict->begin(); it != dict->end(); it++) {\n      dict->SetValue(it->first, it->second);\n    }\n    ASSERT_TRUE(dict->using_small_map());\n    for (int i = 0; i < (int)lepus::Dictionary::kSmallMapMaximumSize; i++) {\n      ASSERT_EQ(dict->GetValue(String(std::to_string(i))).StdString(),\n                std::to_string(i));\n    }\n    auto* value_ptr =\n        dict->SetValue(\n                String(std::to_string(lepus::Dictionary::kSmallMapMaximumSize)),\n                \"asdf\")\n            .get();\n    ASSERT_EQ(value_ptr,\n              dict->GetValue(String(std::to_string(\n                                 lepus::Dictionary::kSmallMapMaximumSize)))\n                  .get());\n    ASSERT_FALSE(dict->using_small_map());\n    for (int i = 0; i < (int)lepus::Dictionary::kSmallMapMaximumSize; i++) {\n      ASSERT_EQ(dict->GetValue(String(std::to_string(i))).StdString(),\n                std::to_string(i));\n    }\n  }\n\n  std::unordered_set<std::string> keys;\n  for (int i = 0; i < (int)lepus::Dictionary::kSmallMapMaximumSize; i++) {\n    dict->SetValue(String(std::to_string(i)), std::to_string(i));\n    keys.insert(std::to_string(i));\n  }\n  ASSERT_TRUE(dict->size() == lepus::Dictionary::kSmallMapMaximumSize);\n  ASSERT_TRUE(dict->using_small_map());\n\n  for (int i = 0; i < (int)lepus::Dictionary::kSmallMapMaximumSize; i++) {\n    auto key = String(std::to_string(i));\n    ASSERT_TRUE(dict->Contains(key));\n    auto it = dict->find(key);\n    ASSERT_TRUE(it != dict->end());\n    ASSERT_TRUE(it->first.str() == std::to_string(i));\n    ASSERT_TRUE(it->second.String().str() == std::to_string(i));\n  }\n  for (int i = 1000; i < 1020; i++) {\n    auto key = String(std::to_string(i));\n    ASSERT_FALSE(dict->Contains(key));\n    auto it = dict->find(key);\n    ASSERT_TRUE(it == dict->end());\n  }\n\n  {\n    int count = 0;\n    auto keys_checker = keys;\n    ASSERT_TRUE(keys_checker.size() == lepus::Dictionary::kSmallMapMaximumSize);\n    for (const auto& p : *dict) {\n      count++;\n      ASSERT_TRUE(p.first.str() == p.second.String().str());\n      keys_checker.erase(p.first.str());\n    }\n    ASSERT_TRUE(count == lepus::Dictionary::kSmallMapMaximumSize);\n    ASSERT_TRUE(keys_checker.empty());\n  }\n\n  constexpr size_t ExtraCount = 50;\n\n  for (int i = (int)lepus::Dictionary::kSmallMapMaximumSize;\n       i < (int)(lepus::Dictionary::kSmallMapMaximumSize + ExtraCount); i++) {\n    dict->SetValue(String(std::to_string(i)), std::to_string(i));\n    keys.insert(std::to_string(i));\n  }\n  ASSERT_TRUE(dict->size() ==\n              lepus::Dictionary::kSmallMapMaximumSize + ExtraCount);\n  ASSERT_FALSE(dict->using_small_map());\n\n  for (int i = 0;\n       i < (int)(lepus::Dictionary::kSmallMapMaximumSize + ExtraCount); i++) {\n    auto key = String(std::to_string(i));\n    ASSERT_TRUE(dict->Contains(key));\n    auto it = dict->find(key);\n    ASSERT_TRUE(it != dict->end());\n    ASSERT_TRUE(it->first.str() == std::to_string(i));\n    ASSERT_TRUE(it->second.String().str() == std::to_string(i));\n  }\n  for (int i = 1000; i < 1020; i++) {\n    auto key = String(std::to_string(i));\n    ASSERT_FALSE(dict->Contains(key));\n    auto it = dict->find(key);\n    ASSERT_TRUE(it == dict->end());\n  }\n\n  {\n    int count = 0;\n    auto keys_checker = keys;\n    ASSERT_TRUE(keys_checker.size() ==\n                lepus::Dictionary::kSmallMapMaximumSize + ExtraCount);\n    for (const auto& p : *dict) {\n      count++;\n      ASSERT_TRUE(p.first.str() == p.second.String().str());\n      keys_checker.erase(p.first.str());\n    }\n    ASSERT_TRUE(count == lepus::Dictionary::kSmallMapMaximumSize + ExtraCount);\n    ASSERT_TRUE(keys_checker.empty());\n  }\n\n  auto dict2 = lepus::Dictionary::Create();\n  for (int i = 0;\n       i < (int)(lepus::Dictionary::kSmallMapMaximumSize + ExtraCount); i++) {\n    if (i % 2 == 0) {\n      ASSERT_TRUE(dict->EraseKey(String(std::to_string(i))) == 1);\n    } else {\n      dict2->SetValue(String(std::to_string(i)), std::to_string(i));\n    }\n  }\n  ASSERT_TRUE(*dict == *dict2);\n\n  auto dict3 = lepus::Dictionary::Create();\n  auto dict4 = lepus::Dictionary::Create();\n  auto dict5 = lepus::Dictionary::Create();\n  for (int i = 0; i < (int)lepus::Dictionary::kSmallMapMaximumSize; i++) {\n    dict3->SetValue(String(std::to_string(i)), std::to_string(i));\n  }\n  for (int i = (int)(lepus::Dictionary::kSmallMapMaximumSize - 1); i >= 0;\n       i--) {\n    dict4->SetValue(String(std::to_string(i)), std::to_string(i));\n    dict5->SetValue(String(std::to_string(i)), std::to_string(i));\n  }\n  ASSERT_TRUE(dict3->using_small_map());\n  ASSERT_TRUE(dict4->using_small_map());\n  ASSERT_TRUE(dict5->using_small_map());\n  dict5->SetValue(\n      String(std::to_string(lepus::Dictionary::kSmallMapMaximumSize)),\n      std::to_string(lepus::Dictionary::kSmallMapMaximumSize));\n  ASSERT_FALSE(dict5->using_small_map());  // dict5 transferred\n  ASSERT_TRUE(*dict3 == *dict4);\n  ASSERT_FALSE(*dict3 == *dict5);\n  dict5->EraseKey(\n      String(std::to_string(lepus::Dictionary::kSmallMapMaximumSize)));\n  ASSERT_TRUE(*dict3 == *dict5);\n  dict4->SetValue(String(std::to_string(1)), \"1\");\n  ASSERT_TRUE(dict4->using_small_map());\n  ASSERT_TRUE(*dict3 == *dict4);\n  dict4->SetValue(String(std::to_string(1)), \"1111111\");\n  ASSERT_TRUE(dict4->using_small_map());\n  ASSERT_FALSE(*dict3 == *dict4);\n\n  // Overwrite values\n  auto dict6 = lepus::Dictionary::Create();\n  auto dict7 = lepus::Dictionary::Create();\n  for (int i = 0; i < (int)(lepus::Dictionary::kSmallMapMaximumSize - 1); i++) {\n    // Use `lepus::Dictionary::kSmallMapMaximumSize - 1` so that later emplace\n    // on existing keys will not trigger transfer.\n    dict6->SetValue(String(std::to_string(i)), std::to_string(i));\n    dict7->SetValue(String(std::to_string(i)), (i % 2 == 0)\n                                                   ? std::to_string(i) + \"_even\"\n                                                   : std::to_string(i));\n  }\n  ASSERT_TRUE(dict6->using_small_map());\n  for (int i = 0; i < (int)(lepus::Dictionary::kSmallMapMaximumSize - 1); i++) {\n    if (i % 2 == 0) {\n      dict6->SetValue(String(std::to_string(i)), std::to_string(i) + \"_even\");\n    }\n  }\n  ASSERT_TRUE(dict6->using_small_map());\n  ASSERT_TRUE(*dict6 == *dict7);\n\n  // SetValue using Value instance of self, no change made to dictionary.\n  for (int i = 0; i < (int)(lepus::Dictionary::kSmallMapMaximumSize - 1); i++) {\n    auto key = String(std::to_string(i));\n    const auto& value = *(dict6->GetValue(key));\n    dict6->SetValue(key, value);\n  }\n  ASSERT_TRUE(dict6->using_small_map());\n  ASSERT_TRUE(*dict6 == *dict7);\n\n  auto dict8 = lepus::Dictionary::Create(\n      {{\"a\", lepus::Value(\"1\")}, {\"b\", lepus::Value(\"2\")}});\n  ASSERT_TRUE(dict8->using_small_map());\n  ASSERT_TRUE(dict8->size() == 2);\n  ASSERT_TRUE(dict8->Contains(\"a\"));\n  ASSERT_TRUE(dict8->Contains(\"b\"));\n  ASSERT_FALSE(dict8->Contains(\"c\"));\n}\n\nstatic inline void TestLynxValueIteratorCallback(lynx_api_env env,\n                                                 lynx_value key,\n                                                 lynx_value value, void* pfunc,\n                                                 void* raw_data) {\n  reinterpret_cast<lepus::ExtendedValueIteratorCallback*>(pfunc)->operator()(\n      env, key, value);\n}\n\nstatic inline void TestIterateLynxValue(\n    const lynx_value& val, lepus::ExtendedValueIteratorCallback* pfunc) {\n  lynx_value_iterate_value(nullptr, val, TestLynxValueIteratorCallback,\n                           reinterpret_cast<void*>(pfunc), nullptr);\n}\n\nTEST_F(BaseValueTest, LynxValueAPI) {\n  lynx_api_env env = nullptr;\n  lynx_value string_value;\n  lynx_value_create_string_utf8(env, \"hello lynx_value\", 16, &string_value);\n  lynx_value_type t1;\n  lynx_value_typeof(env, string_value, &t1);\n  ASSERT_TRUE(t1 == lynx_value_string);\n  size_t length = 0;\n  lynx_value_get_string_utf8(env, string_value, nullptr, 0, &length);\n  ASSERT_TRUE(length == 16);\n  std::string str;\n  str.resize(length);\n  lynx_value_get_string_utf8(env, string_value, &str[0], length + 1, &length);\n  ASSERT_TRUE(str == \"hello lynx_value\");\n  auto* string_ptr =\n      reinterpret_cast<lynx::base::RefCountedStringImpl*>(string_value.val_ptr);\n  ASSERT_TRUE(string_ptr->HasOneRef());\n  lynx_value_remove_reference(env, string_value, nullptr);\n\n  lynx_value map_value;\n  lynx_value_create_map(env, &map_value);\n  lynx_value_type t2;\n  lynx_value_typeof(env, map_value, &t2);\n  ASSERT_TRUE(t2 == lynx_value_map);\n  lynx_value v1;\n  lynx_value_create_int32(env, 10, &v1);\n  lynx_value v2;\n  lynx_value_create_double(env, 3.14f, &v2);\n  lynx_value v3;\n  lynx_value_create_string_utf8(env, \"string\", 6, &v3);\n  auto* v3_ptr =\n      reinterpret_cast<lynx::base::RefCountedStringImpl*>(v3.val_ptr);\n  ASSERT_TRUE(v3_ptr->HasOneRef());\n  lynx_value_set_named_property(env, map_value, \"v1\", v1);\n  lynx_value_set_named_property(env, map_value, \"v2\", v2);\n  lynx_value_set_named_property(env, map_value, \"v3\", v3);\n  lynx_value_set_named_property(env, map_value, \"v3\", v3);\n  lynx_api_status status = lynx_value_set_named_property(env, v3, \"v1\", v2);\n  ASSERT_TRUE(status != lynx_api_ok);\n  ASSERT_FALSE(v3_ptr->HasOneRef());\n  lynx_value v1_ret;\n  lynx_value_get_named_property(env, map_value, \"v1\", &v1_ret);\n  ASSERT_TRUE(v1_ret.type == lynx_value_int32);\n  ASSERT_TRUE(v1_ret.val_int32 == 10);\n  int32_t int32_ret;\n  lynx_value_get_int32(env, v1_ret, &int32_ret);\n  ASSERT_TRUE(int32_ret == 10);\n  lynx_value v2_ret;\n  lynx_value_get_named_property(env, map_value, \"v2\", &v2_ret);\n  ASSERT_TRUE(v2_ret.type == lynx_value_double);\n  ASSERT_TRUE(v2_ret.val_double == 3.14f);\n  double d_ret;\n  lynx_value_get_double(env, v2_ret, &d_ret);\n  ASSERT_TRUE(d_ret == 3.14f);\n  lynx_value v3_ret;\n  lynx_value_get_named_property(env, map_value, \"v3\", &v3_ret);\n  lynx_value_remove_reference(env, v3, nullptr);\n  lynx_value_remove_reference(env, v3_ret, nullptr);\n  int a = 0, b = 0;\n  lepus::ExtendedValueIteratorCallback callback_wrap =\n      [&a, &b](lynx_api_env env, const lynx_value& key,\n               const lynx_value& value) {\n        auto* k_ptr =\n            reinterpret_cast<lynx::base::RefCountedStringImpl*>(key.val_ptr);\n        auto str = k_ptr->str();\n        if (str == \"v1\" || str == \"v2\" || str == \"v3\") {\n          a++;\n        }\n        if (value.type == lynx_value_int32 || value.type == lynx_value_double ||\n            value.type == lynx_value_string) {\n          b++;\n        }\n        lynx_value_remove_reference(env, value, nullptr);\n      };\n  TestIterateLynxValue(map_value, &callback_wrap);\n  EXPECT_EQ(a, 3);\n  EXPECT_EQ(b, 3);\n  ASSERT_TRUE(v3_ptr->HasOneRef());\n  bool has_property;\n  lynx_value_has_property(env, map_value, \"v1\", &has_property);\n  ASSERT_TRUE(has_property);\n  lynx_value_remove_reference(env, map_value, nullptr);\n\n  lynx_value array_value;\n  lynx_value_create_array(env, &array_value);\n  lynx_value_type t3;\n  lynx_value_typeof(env, array_value, &t3);\n  ASSERT_TRUE(t3 == lynx_value_array);\n  lynx_value v4;\n  lynx_value_create_bool(env, true, &v4);\n  lynx_value v5;\n  lynx_value_create_int64(env, 100, &v5);\n  lynx_value v6;\n  lynx_value_create_string_utf8(env, \"string\", 6, &v6);\n  auto* v6_ptr =\n      reinterpret_cast<lynx::base::RefCountedStringImpl*>(v6.val_ptr);\n  ASSERT_TRUE(v6_ptr->HasOneRef());\n  lynx_value_set_element(env, array_value, 0, v4);\n  lynx_value_set_element(env, array_value, 1, v5);\n  lynx_value_set_element(env, array_value, 2, v6);\n  lynx_value_set_element(env, array_value, 2, v6);\n  status = lynx_value_set_element(env, v6, 2, v5);\n  ASSERT_TRUE(status != lynx_api_ok);\n  ASSERT_FALSE(v6_ptr->HasOneRef());\n  lynx_value v4_ret;\n  lynx_value_get_element(env, array_value, 0, &v4_ret);\n  ASSERT_TRUE(v4_ret.type == lynx_value_bool);\n  ASSERT_TRUE(v4_ret.val_bool);\n  bool b_ret;\n  lynx_value_get_bool(env, v4_ret, &b_ret);\n  ASSERT_TRUE(b_ret);\n  lynx_value v5_ret;\n  lynx_value_get_element(env, array_value, 1, &v5_ret);\n  ASSERT_TRUE(v5_ret.type == lynx_value_int64);\n  ASSERT_TRUE(v5_ret.val_int64 = 100);\n  int64_t int64_ret;\n  lynx_value_get_int64(env, v5_ret, &int64_ret);\n  ASSERT_TRUE(int64_ret == 100);\n  lynx_value v6_ret;\n  lynx_value_get_element(env, array_value, 2, &v6_ret);\n  lynx_value_remove_reference(env, v6, nullptr);\n  lynx_value_remove_reference(env, v6_ret, nullptr);\n  ASSERT_TRUE(v6_ptr->HasOneRef());\n  lynx_value_remove_reference(env, array_value, nullptr);\n  lynx_value v7;\n  lynx_value_create_uint32(env, 1001, &v7);\n  ASSERT_TRUE(v7.type == lynx_value_uint32);\n  ASSERT_TRUE(v7.val_uint32 == 1001);\n  lynx_value v8;\n  lynx_value_create_uint64(env, 10001, &v8);\n  ASSERT_TRUE(v8.type == lynx_value_uint64);\n  ASSERT_TRUE(v8.val_uint64 == 10001);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/value/byte_array.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/value/byte_array.h\"\n\nnamespace lynx {\nnamespace lepus {\n\nByteArray::ByteArray(std::unique_ptr<uint8_t[]> ptr, size_t length)\n    : ptr_(std::move(ptr)), length_(length) {}\n\nstd::unique_ptr<uint8_t[]> ByteArray::MovePtr() {\n  length_ = 0;\n  return std::move(ptr_);\n}\n\nuint8_t* ByteArray::GetPtr() { return ptr_.get(); }\n\nsize_t ByteArray::GetLength() { return length_; }\n\n}  // namespace lepus\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/value/lynx_value_api_impl.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <algorithm>\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/base_string.h\"\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/lynx_value_api.h\"\n#include \"base/include/value/table.h\"\n\nlynx_api_status lynx_value_typeof(lynx_api_env env, lynx_value value,\n                                  lynx_value_type* result) {\n  *result = value.type;\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_null(lynx_api_env env, lynx_value* result) {\n  (*result).type = lynx_value_null;\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_bool(lynx_api_env env, bool value,\n                                       lynx_value* result) {\n  *result = {.val_bool = value, .type = lynx_value_bool};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_double(lynx_api_env env, double value,\n                                         lynx_value* result) {\n  *result = {.val_double = value, .type = lynx_value_double};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_int32(lynx_api_env env, int32_t value,\n                                        lynx_value* result) {\n  *result = {.val_int32 = value, .type = lynx_value_int32};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_uint32(lynx_api_env env, uint32_t value,\n                                         lynx_value* result) {\n  *result = {.val_uint32 = value, .type = lynx_value_uint32};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_int64(lynx_api_env env, int64_t value,\n                                        lynx_value* result) {\n  *result = {.val_int64 = value, .type = lynx_value_int64};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_uint64(lynx_api_env env, uint64_t value,\n                                         lynx_value* result) {\n  *result = {.val_uint64 = value, .type = lynx_value_uint64};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_string_utf8(lynx_api_env env, const char* str,\n                                              size_t length,\n                                              lynx_value* result) {\n  *result = {.val_ptr = reinterpret_cast<lynx_value_ptr>(\n                 lynx::base::RefCountedStringImpl::Unsafe::RawCreate(str)),\n             .type = lynx_value_string};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_map(lynx_api_env env, lynx_value* result) {\n  *result = {.val_ptr = reinterpret_cast<lynx_value_ptr>(\n                 lynx::lepus::Dictionary::Unsafe::RawCreate()),\n             .type = lynx_value_map};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_create_array(lynx_api_env env, lynx_value* result) {\n  *result = {.val_ptr = reinterpret_cast<lynx_value_ptr>(\n                 lynx::lepus::CArray::Unsafe::RawCreate()),\n             .type = lynx_value_array};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_number(lynx_api_env env, lynx_value value,\n                                      double* result) {\n  switch (value.type) {\n    case lynx_value_double:\n      *result = value.val_double;\n      break;\n    case lynx_value_int32:\n      *result = value.val_int32;\n      break;\n    case lynx_value_uint32:\n      *result = value.val_uint32;\n      break;\n    case lynx_value_int64:\n      *result = value.val_int64;\n      break;\n    case lynx_value_uint64:\n      *result = value.val_uint64;\n      break;\n    case lynx_value_bool:\n      *result = value.val_bool;\n      break;\n    case lynx_value_string: {\n      auto* base_string =\n          reinterpret_cast<lynx::base::RefCountedStringImpl*>(value.val_ptr);\n      if (base_string) {\n        auto* str = base_string->c_str();\n        *result = strtod(str, nullptr);\n      }\n    } break;\n    default:\n      *result = 0;\n      break;\n  }\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_double(lynx_api_env env, lynx_value value,\n                                      double* result) {\n  if (unlikely(value.type != lynx_value_double)) {\n    return lynx_api_double_expected;\n  }\n  *result = value.val_double;\n\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_int32(lynx_api_env env, lynx_value value,\n                                     int32_t* result) {\n  if (unlikely(value.type != lynx_value_int32)) {\n    return lynx_api_int32_expected;\n  }\n  *result = value.val_int32;\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_uint32(lynx_api_env env, lynx_value value,\n                                      uint32_t* result) {\n  if (unlikely(value.type != lynx_value_uint32)) {\n    return lynx_api_uint32_expected;\n  }\n  *result = value.val_uint32;\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_int64(lynx_api_env env, lynx_value value,\n                                     int64_t* result) {\n  if (unlikely(value.type != lynx_value_int64)) {\n    return lynx_api_int64_expected;\n  }\n  *result = value.val_int64;\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_uint64(lynx_api_env env, lynx_value value,\n                                      uint64_t* result) {\n  if (unlikely(value.type != lynx_value_uint64)) {\n    return lynx_api_uint64_expected;\n  }\n  *result = value.val_uint64;\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_bool(lynx_api_env env, lynx_value value,\n                                    bool* result) {\n  if (unlikely(value.type != lynx_value_bool)) {\n    return lynx_api_bool_expected;\n  }\n  *result = value.val_bool;\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_string_utf8(lynx_api_env env, lynx_value value,\n                                           char* buf, size_t bufsize,\n                                           size_t* result) {\n  if (unlikely(value.type != lynx_value_string || value.val_ptr == nullptr)) {\n    *result = 0;\n    return lynx_api_string_expected;\n  }\n  auto* base_string =\n      reinterpret_cast<lynx::base::RefCountedStringImpl*>(value.val_ptr);\n  if (buf == nullptr) {\n    *result = base_string->length();\n  } else {\n    auto* str = base_string->c_str();\n    size_t size{std::min(base_string->length(), bufsize - 1)};\n    std::copy(str, str + size, buf);\n    buf[size] = '\\0';\n    if (result != nullptr) {\n      *result = size;\n    }\n  }\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_array_length(lynx_api_env env, lynx_value value,\n                                            uint32_t* result) {\n  if (unlikely(value.type != lynx_value_array || value.val_ptr == nullptr)) {\n    *result = 0;\n    return lynx_api_array_expected;\n  }\n  *result = static_cast<uint32_t>(\n      reinterpret_cast<lynx::lepus::CArray*>(value.val_ptr)->size());\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_set_element(lynx_api_env env, lynx_value object,\n                                       uint32_t index, lynx_value value) {\n  if (unlikely(object.type != lynx_value_array || object.val_ptr == nullptr)) {\n    return lynx_api_array_expected;\n  }\n  auto* array = reinterpret_cast<lynx::lepus::CArray*>(object.val_ptr);\n  array->set(index, lynx::lepus::Value(env, value));\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_element(lynx_api_env env, lynx_value object,\n                                       uint32_t index, lynx_value* result) {\n  if (unlikely(object.type != lynx_value_array || object.val_ptr == nullptr)) {\n    (*result).type = lynx_value_null;\n    return lynx_api_array_expected;\n  }\n  auto* array = reinterpret_cast<lynx::lepus::CArray*>(object.val_ptr);\n  array->get(index).DupValue();\n  *result = array->get(index).value();\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_has_property(lynx_api_env env, lynx_value object,\n                                        const char* utf8name, bool* result) {\n  if (unlikely(object.type != lynx_value_map || object.val_ptr == nullptr)) {\n    return lynx_api_map_expected;\n  }\n  auto* map = reinterpret_cast<lynx::lepus::Dictionary*>(object.val_ptr);\n  auto key = lynx::base::String(utf8name);\n  *result = map->Contains(key);\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_property_names(lynx_api_env env,\n                                              lynx_value object,\n                                              lynx_value* result) {\n  if (unlikely(object.type != lynx_value_map || object.val_ptr == nullptr)) {\n    return lynx_api_map_expected;\n  }\n  auto* array = lynx::lepus::CArray::Unsafe::RawCreate();\n  auto* map = reinterpret_cast<lynx::lepus::Dictionary*>(object.val_ptr);\n  map->for_each([&](const auto& key, lynx::lepus::Value& value) {\n    array->emplace_back(key);\n  });\n  *result = {.val_ptr = reinterpret_cast<lynx_value_ptr>(array),\n             .type = lynx_value_array};\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_set_named_property(lynx_api_env env,\n                                              lynx_value object,\n                                              const char* utf8name,\n                                              lynx_value value) {\n  if (unlikely(object.type != lynx_value_map || object.val_ptr == nullptr)) {\n    return lynx_api_map_expected;\n  }\n  auto* map = reinterpret_cast<lynx::lepus::Dictionary*>(object.val_ptr);\n  map->SetValue(utf8name, lynx::lepus::Value(env, value));\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_get_named_property(lynx_api_env env,\n                                              lynx_value object,\n                                              const char* utf8name,\n                                              lynx_value* result) {\n  if (unlikely(object.type != lynx_value_map || object.val_ptr == nullptr)) {\n    (*result).type = lynx_value_null;\n    return lynx_api_invalid_arg;\n  }\n  auto* map = reinterpret_cast<lynx::lepus::Dictionary*>(object.val_ptr);\n  auto key = lynx::base::String(utf8name);\n  map->GetValue(key).value().DupValue();\n  *result = map->GetValue(key).value().value();\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_iterate_value(lynx_api_env env, lynx_value object,\n                                         lynx_value_iterator_callback callback,\n                                         void* pfunc, void* raw_data) {\n  if (object.val_ptr == nullptr) {\n    return lynx_api_invalid_arg;\n  }\n  if (object.type == lynx_value_map) {\n    auto* map = reinterpret_cast<lynx::lepus::Dictionary*>(object.val_ptr);\n    map->for_each([&](const auto& key, lynx::lepus::Value& value) {\n      auto* ptr = lynx::base::String::Unsafe::GetStringRawRef(key);\n      lynx_value k;\n      k.val_ptr = reinterpret_cast<lynx_value_ptr>(ptr);\n      k.type = lynx_value_string;\n      value.DupValue();\n      callback(env, k, value.value(), pfunc, raw_data);\n    });\n  } else if (object.type == lynx_value_array) {\n    auto* array = reinterpret_cast<lynx::lepus::CArray*>(object.val_ptr);\n    for (std::size_t i = 0; i < array->size(); ++i) {\n      lynx_value k{.val_uint32 = static_cast<uint32_t>(i),\n                   .type = lynx_value_uint32};\n      array->get(i).DupValue();\n      callback(env, k, array->get(i).value(), pfunc, raw_data);\n    }\n  }\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_add_reference(lynx_api_env env, lynx_value value,\n                                         lynx_value_ref* result) {\n  if (value.type >= lynx_value_string && value.type <= lynx_value_object &&\n      value.val_ptr != nullptr) {\n    reinterpret_cast<lynx::fml::RefCountedThreadSafeStorage*>(value.val_ptr)\n        ->AddRef();\n  }\n  return lynx_api_ok;\n}\n\nlynx_api_status lynx_value_remove_reference(lynx_api_env env, lynx_value value,\n                                            lynx_value_ref ref) {\n  if (value.type >= lynx_value_string && value.type <= lynx_value_object &&\n      value.val_ptr != nullptr) {\n    reinterpret_cast<lynx::fml::RefCountedThreadSafeStorage*>(value.val_ptr)\n        ->Release();\n  }\n  return lynx_api_ok;\n}\n"
  },
  {
    "path": "base/src/value/lynx_value_extended_empty.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/value/lynx_value_extended.h\"\n\nlynx_api_status lynx_value_get_bool_ext(lynx_api_env env, lynx_value value,\n                                        bool* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_double_ext(lynx_api_env env, lynx_value value,\n                                          double* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_int32_ext(lynx_api_env env, lynx_value value,\n                                         int32_t* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_int64_ext(lynx_api_env env, lynx_value value,\n                                         int64_t* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_is_integer_ext(lynx_api_env env, lynx_value value,\n                                          bool* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_integer_ext(lynx_api_env env, lynx_value value,\n                                           int64_t* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_number_ext(lynx_api_env env, lynx_value value,\n                                          double* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_string_ref_ext(lynx_api_env env,\n                                              lynx_value value, void** result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_external_ext(lynx_api_env env, lynx_value value,\n                                            void** result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_length_ext(lynx_api_env env, lynx_value value,\n                                          uint32_t* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_is_array_ext(lynx_api_env env, lynx_value value,\n                                        bool* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_set_element_ext(lynx_api_env env, lynx_value object,\n                                           uint32_t index, lynx_value value) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_element_ext(lynx_api_env env, lynx_value object,\n                                           uint32_t index, lynx_value* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_is_map_ext(lynx_api_env env, lynx_value value,\n                                      bool* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_set_named_property_ext(lynx_api_env env,\n                                                  lynx_value object,\n                                                  const char* utf8name,\n                                                  lynx_value value) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_has_named_property_ext(lynx_api_env env,\n                                                  lynx_value object,\n                                                  const char* utf8name,\n                                                  bool* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_get_named_property_ext(lynx_api_env env,\n                                                  lynx_value object,\n                                                  const char* utf8name,\n                                                  lynx_value* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_is_function_ext(lynx_api_env env, lynx_value value,\n                                           bool* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_to_string_utf8_ext(lynx_api_env env,\n                                              lynx_value value, void* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_typeof_ext(lynx_api_env env, lynx_value value,\n                                      lynx_value_type* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_iterate_value_ext(\n    lynx_api_env env, lynx_value object, lynx_value_iterator_callback callback,\n    void* pfunc, void* raw_data) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_equals_ext(lynx_api_env env, lynx_value lhs,\n                                      lynx_value rhs, bool* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_deep_copy_value_ext(lynx_api_env env, lynx_value src,\n                                               lynx_value* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_print_ext(lynx_api_env env, lynx_value value,\n                                     void* stream,\n                                     lynx_value_print_ext_callback callback) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_add_reference_ext(lynx_api_env env, lynx_value value,\n                                             lynx_value_ref* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_add_reference_weak_ext(lynx_api_env env,\n                                                  lynx_value value,\n                                                  lynx_value_ref* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_move_reference_ext(lynx_api_env env,\n                                              lynx_value src_val,\n                                              lynx_value_ref src_ref,\n                                              lynx_value_ref* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_remove_reference_ext(lynx_api_env env,\n                                                lynx_value value,\n                                                lynx_value_ref ref) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_has_ref_count_ext(lynx_api_env env, lynx_value val,\n                                             bool* result) {\n  return lynx_api_not_support;\n}\n\nlynx_api_status lynx_value_is_uninitialized_ext(lynx_api_env env,\n                                                lynx_value val, bool* result) {\n  return lynx_api_not_support;\n}\n"
  },
  {
    "path": "base/src/value/path_parser.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/value/path_parser.h\"\n\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace lepus {\n\nPathVector ParseValuePath(const std::string &path) {\n  PathVector path_array;\n  std::stringstream result;\n  bool array_start_ = false;\n  bool num_in_array_ = false;\n  int digits = 0;\n  std::size_t length = path.length();\n  for (std::size_t index = 0; index < length; ++index) {\n    char c = path[index];\n    if (c == '.') {\n      std::string ss = result.str();\n      result.clear();\n      result.str(\"\");\n      if (ss.length() > 0) {\n        path_array.emplace_back(std::move(ss));\n      }\n    } else if (c == '[') {\n      if (array_start_) {\n        LOGE(\"Data Path Error, Path can not have nested []. Path: \" << path);\n        path_array.clear();\n        return path_array;  // Guarantee NRVO\n      }\n      std::string ss = result.str();\n      result.clear();\n      result.str(\"\");\n      if (ss.length() > 0) {\n        path_array.emplace_back(std::move(ss));\n      }\n      if (path_array.empty()) {\n        LOGE(\"Data Path Error, Path can not start with []. Path: \" << path);\n        path_array.clear();\n        return path_array;  // Guarantee NRVO\n      }\n      array_start_ = true;\n      num_in_array_ = false;\n    } else if (c == ']') {\n      if (!num_in_array_) {\n        LOGE(\"Data Path Error, Must has number in []. Path: \" << path);\n        path_array.clear();\n        return path_array;  // Guarantee NRVO\n      }\n      array_start_ = false;\n      path_array.emplace_back(std::to_string(digits));\n      digits = 0;\n\n      // there may have escape number in brackets like:\n      // a[\\1]\n      // should clear result here\n      result.clear();\n      result.str(\"\");\n    } else if (c == '\\\\') {\n      if (index == length - 1) {\n        result << c;\n        break;\n      }\n\n      char next = path[index + 1];\n      if (next == '[' || next == ']' || next == '.') {\n        // escape special char\n        result << next;\n        index++;\n      } else {\n        result << '\\\\';\n      }\n    } else if (array_start_) {\n      if (c < '0' || c > '9') {\n        LOGE(\"Data Path Error, Only number 0-9 could be inside []. Path: \"\n             << path);\n        path_array.clear();\n        return path_array;  // Guarantee NRVO\n      }\n      num_in_array_ = true;\n      digits = 10 * digits + (c - '0');\n    } else {\n      result << c;\n    }\n  }\n  if (array_start_) {\n    LOGE(\"Data Path Error, [] should appear in pairs. Path: \" << path);\n    path_array.clear();\n    return path_array;  // Guarantee NRVO\n  }\n  std::string ss = result.str();\n  if (ss.length() > 0) {\n    path_array.emplace_back(std::move(ss));\n  }\n  return path_array;\n}\n\n}  // namespace lepus\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/value/path_parser_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/value/path_parser.h\"\n\n#include <string>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\n\nTEST(LepusPathParserTest, BasicUsageTest) {\n  base::Vector<std::string> res{\"a\", \"b\", \"c\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b.c\"), res);\n\n  res = {\"a\", \"1\", \"1\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a[1]1\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"a.[1].1\"), res);\n\n  res = {\"a\", \"c1\", \"0\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.c1[0]\"), res);\n\n  res = {\"aa\", \"b\", \"1\", \"c\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"aa.b[1]c\"), res);\n\n  res = {\"a\", \"b\", \"1\", \"ccc\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b.[1].ccc\"), res);\n\n  res = {\"a\", \"b\", \"1\", \"c\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b[1]c\"), res);\n\n  res = {\"a\", \"b\", \"1\", \"2\", \"123\", \"4\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b[1].[2]123[4]\"), res);\n\n  res = {\"a\", \"b\", \"1\", \"2\", \"3\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b[1][2].[3]\"), res);\n\n  res = {\"a\", \"bbb\", \"1\", \"c\", \"2\", \"d\", \"3\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.bbb[1]c[2].d[3]\"), res);\n}\n\nTEST(LepusPathParserTest, StartsWithBracketsTest) {\n  base::Vector<std::string> res{};\n  ASSERT_EQ(lepus::ParseValuePath(\"[\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"[1\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"[1]\"), res);\n}\n\nTEST(LepusPathParserTest, BracketsNotMatchTest) {\n  base::Vector<std::string> res{};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b.c[\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b.c[1\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"a[1\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"a.[1\"), res);\n}\n\nTEST(LepusPathParserTest, NonNumberInBracketsTest) {\n  base::Vector<std::string> res{};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.[b]\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"a[b]\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"a.[b].[c]\"), res);\n}\n\nTEST(LepusPathParserTest, MultiBracketsTest) {\n  base::Vector<std::string> res{};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.[[[[1]]]]\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"a.[[[1]]\"), res);\n  ASSERT_EQ(lepus::ParseValuePath(\"a[[1]]]]\"), res);\n}\n\nTEST(LepusPathParserTest, EscapeCharTest) {\n  base::Vector<std::string> res{};\n\n  res = {\"\\\\a\", \"b.c\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"\\\\a.b\\\\.c\"), res);\n\n  res = {\"a\", \"1\", \"\\\\\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a[\\\\1]\\\\\"), res);\n\n  res = {\"a\", \"[1\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.\\\\[1\"), res);\n\n  res = {\"a\", \"c\\\\10]\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.c\\\\10\\\\]\"), res);\n\n  res = {\"a\", \"b\", \"1\", \"c\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b[\\\\\\\\1]c\"), res);\n\n  res = {\"a\", \"b.[1]\", \"c\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b\\\\.\\\\[1\\\\].c\"), res);\n\n  res = {\"aa\\\\a\", \"b\", \"1\", \"c\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"aa\\\\a.b[1]c\"), res);\n\n  res = {\"a\", \"\\\\b\", \"1\", \"2\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.\\\\b[1].[2]\"), res);\n\n  res = {\"a\", \"b\", \"1\", \"2\", \".\", \"3\"};\n  ASSERT_EQ(lepus::ParseValuePath(\"a.b[1][\\\\2]\\\\.[3]\"), res);\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/value/table.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/value/table.h\"\n\n#include \"base/include/log/logging.h\"\n#include \"base/include/value/base_value.h\"\n\nnamespace lynx {\nnamespace lepus {\n\nbool Dictionary::Erase(const base::String& key) {\n  if (IsConstLog()) {\n    return false;\n  }\n  map_.erase(key);\n  return true;\n}\n\nint32_t Dictionary::EraseKey(const base::String& key) {\n  if (IsConstLog()) {\n    return -1;\n  }\n  return static_cast<int32_t>(map_.erase(key));\n}\n\nDictionary::ValueWrapper Dictionary::GetValue(const base::String& key) const {\n  const auto* ptr = map_.find(key);\n  if (ptr != nullptr) {\n    return ValueWrapper(ptr);\n  } else {\n    static Value kNil;\n    return ValueWrapper(&kNil);\n  }\n}\n\nDictionary::ValueWrapper Dictionary::GetValueOrUndefined(\n    const base::String& key) const {\n  const auto* ptr = map_.find(key);\n  if (ptr != nullptr) {\n    return ValueWrapper(ptr);\n  } else {\n    static Value kUndefined(Value::kCreateAsUndefinedTag);\n    return ValueWrapper(&kUndefined);\n  }\n}\n\nDictionary::ValueWrapper Dictionary::GetValueOrNull(\n    const base::String& key) const {\n  return ValueWrapper(map_.find(key));\n}\n\nbool operator==(const Dictionary& left, const Dictionary& right) {\n  if (left.size() != right.size()) {\n    return false;\n  }\n  if (left.using_small_map()) {\n    for (const auto& [key, value] : left.map_.small_map()) {\n      auto it = right.find(key);\n      if (it == right.end()) {\n        return false;\n      }\n      if (value != it->second) {\n        return false;\n      }\n    }\n  } else {\n    for (const auto& [key, value] : left.map_.big_map()) {\n      auto it = right.find(key);\n      if (it == right.end()) {\n        return false;\n      }\n      if (value != it->second) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\n}  // namespace lepus\n}  // namespace lynx\n"
  },
  {
    "path": "base/src/vector.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/vector.h\"\n\n#if 0\n\n#include <cstdint>\n#include <map>\n#include <mutex>\n#include <vector>\n\n#include \"base/include/log/logging.h\"\n\nnamespace {\nstruct MapStatisticsEntry {\n  uint32_t max_count;\n  uint32_t insert_count;\n  uint32_t erase_count;\n  uint32_t total_find_count;\n  uint32_t find_count;\n  uint32_t insert_find_collision_count;\n};\n\nstatic std::mutex MapSt_Lock;\nstatic std::map<size_t, size_t> MapSt_TotalFindOfCount;\nstatic std::map<size_t, size_t> MapSt_FindOfCount;\nstatic std::vector<MapStatisticsEntry> MapSt_Entries;\n}  // namespace\n\nextern \"C\" {\nvoid ClearBaseMapStatistics() {\n  MapSt_FindOfCount.clear();\n  MapSt_Entries.clear();\n}\n\nvoid PrintBaseMapStatistics() {\n  std::map<uint32_t, uint32_t> max_count_info;\n  std::map<uint32_t, uint32_t> insert_count_info;\n  std::map<uint32_t, uint32_t> erase_count_info;\n  std::map<uint32_t, uint32_t> total_find_count_info;\n  std::map<uint32_t, uint32_t> find_count_info;\n  std::map<uint32_t, uint32_t> insert_find_collision_count_info;\n\n  size_t has_erase_count = 0;\n  size_t map_count = 0;\n  for (const auto& i : MapSt_Entries) {\n    map_count++;\n    if (i.erase_count > 0) {\n      has_erase_count++;\n    }\n    max_count_info[i.max_count]++;\n    insert_count_info[i.insert_count]++;\n    erase_count_info[i.erase_count]++;\n    total_find_count_info[i.total_find_count]++;\n    find_count_info[i.find_count]++;\n    insert_find_collision_count_info[i.insert_find_collision_count]++;\n  }\n  LOGI(\"[AA] map total count \" << map_count);\n  LOGI(\"[AA] erase occurred map count \"\n       << has_erase_count << \", accounting for \"\n       << (double)has_erase_count / map_count * 100.f << \"%\");\n\n  double sum_percentage = 0.f;\n  for (const auto& it : max_count_info) {\n    double p = (double)it.second / map_count * 100.f;\n    sum_percentage += p;\n    LOGI(\"[AA] max count of \" << it.first << \" maps are \" << it.second\n                              << \", accounting for \" << p << \"%\"\n                              << \", sum accounting for \" << sum_percentage\n                              << \"%\");\n  }\n\n  sum_percentage = 0.f;\n  for (const auto& it : insert_count_info) {\n    double p = (double)it.second / map_count * 100.f;\n    sum_percentage += p;\n    LOGI(\"[AA] insert count of \" << it.first << \" maps are \" << it.second\n                                 << \", accounting for \" << p << \"%\"\n                                 << \", sum accounting for \" << sum_percentage\n                                 << \"%\");\n  }\n\n  sum_percentage = 0.f;\n  for (const auto& it : erase_count_info) {\n    double p = (double)it.second / map_count * 100.f;\n    sum_percentage += p;\n    LOGI(\"[AA] erase count of \" << it.first << \" maps are \" << it.second\n                                << \", accounting for \" << p << \"%\"\n                                << \", sum accounting for \" << sum_percentage\n                                << \"%\");\n  }\n\n  sum_percentage = 0.f;\n  for (const auto& it : total_find_count_info) {\n    double p = (double)it.second / map_count * 100.f;\n    sum_percentage += p;\n    LOGI(\"[AA] total find count of \" << it.first << \" maps are \" << it.second\n                                     << \", accounting for \" << p << \"%\"\n                                     << \", sum accounting for \"\n                                     << sum_percentage << \"%\");\n  }\n\n  sum_percentage = 0.f;\n  for (const auto& it : find_count_info) {\n    double p = (double)it.second / map_count * 100.f;\n    sum_percentage += p;\n    LOGI(\"[AA] find count of \"\n         << it.first << \" by `find()`, `[]` or `contains()`\"\n         << \" maps are \" << it.second << \", accounting for \" << p << \"%\"\n         << \", sum accounting for \" << sum_percentage << \"%\");\n  }\n\n  sum_percentage = 0.f;\n  for (const auto& it : insert_find_collision_count_info) {\n    double p = (double)it.second / map_count * 100.f;\n    sum_percentage += p;\n    LOGI(\"[AA] find when inserting and collision count of \"\n         << it.first << \" maps are \" << it.second << \", accounting for \" << p\n         << \"%\"\n         << \", sum accounting for \" << sum_percentage << \"%\");\n  }\n\n  size_t total_find_count = 0;\n  for (const auto& it : MapSt_TotalFindOfCount) {\n    total_find_count += it.second;\n  }\n\n  sum_percentage = 0.f;\n  for (const auto& it : MapSt_TotalFindOfCount) {\n    double p = (double)it.second / total_find_count * 100.f;\n    sum_percentage += p;\n    LOGI(\"[AA] find(include insertion) from map of \"\n         << it.first << \" elements happen \" << it.second\n         << \" times, accounting for \" << p << \"%\"\n         << \", sum accounting for \" << sum_percentage << \"%\");\n  }\n\n  size_t find_count = 0;\n  for (const auto& it : MapSt_FindOfCount) {\n    find_count += it.second;\n  }\n\n  sum_percentage = 0.f;\n  for (const auto& it : MapSt_FindOfCount) {\n    double p = (double)it.second / find_count * 100.f;\n    sum_percentage += p;\n    LOGI(\"[AA] find by `find()`, `[]` or `contains()` from map of \"\n         << it.first << \" elements happen \" << it.second\n         << \" times, accounting for \" << p << \"%\"\n         << \", sum accounting for \" << sum_percentage << \"%\");\n  }\n}\n}\n\nnamespace lynx {\nnamespace base {\n\nMapStatisticsBase<true>::~MapStatisticsBase() {\n  std::lock_guard<std::mutex> lock(MapSt_Lock);\n  MapSt_Entries.push_back(\n      {.max_count = max_count,\n       .insert_count = insert_count,\n       .erase_count = erase_count,\n       .total_find_count = total_find_count,\n       .find_count = find_count,\n       .insert_find_collision_count = insert_find_collision_count});\n}\n\nvoid MapStatisticsBase<true>::UpdateMaxCount(size_t v) const {\n  if (v > max_count) {\n    max_count = static_cast<uint32_t>(v);\n  }\n}\n\nvoid MapStatisticsBase<true>::RecordFind(MapStatisticsFindKind kind,\n                                         size_t find_of_count) const {\n  total_find_count++;\n  switch (kind) {\n    case MapStatisticsFindKind::kFind:\n      find_count++;\n      break;\n    case MapStatisticsFindKind::kInsertFindCollision:\n      insert_find_collision_count++;\n      IncreaseInsertCount();\n      break;\n    case MapStatisticsFindKind::kInsertFind:\n      UpdateMaxCount(find_of_count + 1);\n      IncreaseInsertCount();\n      break;\n    default:\n      break;\n  }\n  IncreaseFindOfCount(kind, find_of_count);\n}\n\nvoid MapStatisticsBase<true>::IncreaseFindOfCount(MapStatisticsFindKind kind,\n                                                  size_t find_of_count) {\n  std::lock_guard<std::mutex> lock(MapSt_Lock);\n  MapSt_TotalFindOfCount[find_of_count]++;\n  if (kind == MapStatisticsFindKind::kFind) {\n    MapSt_FindOfCount[find_of_count]++;\n  }\n}\n\n}  // namespace base\n}  // namespace lynx\n\n#endif\n"
  },
  {
    "path": "base/src/vector_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/vector.h\"\n\n#include <algorithm>\n#include <cstring>\n#include <functional>\n#include <numeric>\n#include <random>\n#include <stack>\n#include <string>\n#include <vector>\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wsign-compare\"\n\n#include \"base/include/value/base_string.h\"\n#include \"base/include/vector_helper.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n/**\n * @brief Insertion sort algorithm.\n *\n * @details This is a STABLE in-place O(n^2) algorithm. It is efficient\n * for ranges smaller than 10 elements.\n *\n * All iterators of Vector are random-access iterators which are also\n * valid bidirectional iterators.\n * @param first a bidirectional iterator.\n * @param last a bidirectional iterator.\n * @param compare a comparison functor.\n */\ntemplate <\n    class BidirectionalIterator, class Compare,\n    class T = typename std::iterator_traits<BidirectionalIterator>::value_type>\ninline void InsertionSort(BidirectionalIterator first,\n                          BidirectionalIterator last, Compare compare) {\n  if (first == last) {\n    return;\n  }\n\n  auto it = first;\n  for (++it; it != last; ++it) {\n    auto key = std::move(*it);\n    auto insertPos = it;\n    for (auto movePos = it; movePos != first && compare(key, *(--movePos));\n         --insertPos) {\n      *insertPos = std::move(*movePos);\n    }\n    *insertPos = std::move(key);\n  }\n}\n\n// Polyfills from SarNative\n\nstruct Matrix3 {\n  static const Matrix3 zero;\n  static const Matrix3 identity;\n\n  union {\n    float elements[9]{1, 0, 0, 0, 1, 0, 0, 0, 1};\n    struct {\n      float col0[3];\n      float col1[3];\n      float col2[3];\n    };\n\n    struct {\n      float m0;\n      float m1;\n      float m2;\n      float m3;\n      float m4;\n      float m5;\n      float m6;\n      float m7;\n      float m8;\n    };\n  };\n\n  Matrix3() noexcept = default;\n\n  Matrix3(float e00, float e01, float e02, float e10, float e11, float e12,\n          float e20, float e21, float e22) {\n    Set(e00, e01, e02, e10, e11, e12, e20, e21, e22);\n  }\n\n  explicit Matrix3(const float arr[9]) {\n    std::memcpy(elements, arr, 9 * sizeof(float));\n  }\n\n  bool operator==(const Matrix3& value) const {\n    return std::memcmp(elements, value.elements, 9 * sizeof(float)) == 0;\n  }\n\n  bool operator!=(const Matrix3& value) const { return !(*this == value); }\n\n  float& operator[](const size_t index) { return elements[index]; }\n\n  float operator[](const size_t index) const { return elements[index]; }\n\n  Matrix3& Set(const float e00, const float e01, const float e02,\n               const float e10, const float e11, const float e12,\n               const float e20, const float e21, const float e22) {\n    elements[0] = e00;\n    elements[3] = e01;\n    elements[6] = e02;\n    elements[1] = e10;\n    elements[4] = e11;\n    elements[7] = e12;\n    elements[2] = e20;\n    elements[5] = e21;\n    elements[8] = e22;\n    return *this;\n  }\n};\n\nconst Matrix3 Matrix3::zero = {0, 0, 0, 0, 0, 0, 0, 0, 0};\nconst Matrix3 Matrix3::identity = {1, 0, 0, 0, 1, 0, 0, 0, 1};\n\ntemplate <class T>\nvoid _checkVector([[maybe_unused]] const Vector<T>& array,\n                  [[maybe_unused]] int line) {\n  // Currently nothing to do.\n}\n\n#define CheckVector(array) _checkVector(array, __LINE__)\n\nusing VectorTemplateless_0 = VectorTemplateless<0, false>;\n\nTEST(Vector, InlineTypeNoFullValueInitialization) {\n  // This test make sure that inlined types will not be fully\n  // value initialized with zero bytes.\n  {\n    using Type = InlineVector<std::string, 100>;\n    unsigned char buffer[sizeof(Type)];\n    std::memset(buffer, 0xAA, sizeof(buffer));\n    new (buffer) Type();\n    EXPECT_EQ(buffer[sizeof(buffer) - 1], 0xAA);  // byte not set to zero\n  }\n\n  {\n    using Type = InlineOrderedFlatMap<std::string, std::string, 100>;\n    unsigned char buffer[sizeof(Type)];\n    std::memset(buffer, 0xAA, sizeof(buffer));\n    new (buffer) Type();\n    EXPECT_EQ(buffer[sizeof(buffer) - 1], 0xAA);  // byte not set to zero\n  }\n\n  {\n    using Type = InlineOrderedFlatSet<std::string, 100>;\n    unsigned char buffer[sizeof(Type)];\n    std::memset(buffer, 0xAA, sizeof(buffer));\n    new (buffer) Type();\n    EXPECT_EQ(buffer[sizeof(buffer) - 1], 0xAA);  // byte not set to zero\n  }\n\n  {\n    using Type = InlineLinearFlatMap<std::string, std::string, 100>;\n    unsigned char buffer[sizeof(Type)];\n    std::memset(buffer, 0xAA, sizeof(buffer));\n    new (buffer) Type();\n    EXPECT_EQ(buffer[sizeof(buffer) - 1], 0xAA);  // byte not set to zero\n  }\n\n  {\n    using Type = InlineLinearFlatSet<std::string, 100>;\n    unsigned char buffer[sizeof(Type)];\n    std::memset(buffer, 0xAA, sizeof(buffer));\n    new (buffer) Type();\n    EXPECT_EQ(buffer[sizeof(buffer) - 1], 0xAA);  // byte not set to zero\n  }\n}\n\nTEST(Vector, ByteArray) {\n  struct Range {\n    uint32_t start;\n    uint32_t end;\n  };\n  Range range{10000, 20000};\n\n  // Additional tests for ByteArray\n  std::vector<uint8_t> vec;\n  vec.push_back(0);\n  vec.push_back(1);\n  vec.push_back(0);\n\n  std::vector<uint8_t> vec_final;\n  {\n    std::vector<uint8_t> start((uint8_t*)(&range.start),\n                               (uint8_t*)(&range.start) + sizeof(uint32_t));\n    std::vector<uint8_t> end((uint8_t*)(&range.end),\n                             (uint8_t*)(&range.end) + sizeof(uint32_t));\n\n    vec.insert(vec.end(), start.begin(), start.end());\n    vec.insert(vec.end(), end.begin(), end.end());\n\n    std::string s(vec.begin(), vec.end());\n    auto s2 = std::make_unique<std::string>(s);\n    auto u_char_arr = reinterpret_cast<unsigned const char*>(s2->c_str());\n    vec_final = std::vector<uint8_t>(u_char_arr, u_char_arr + s.size());\n  }\n\n  // ByteArray version\n  ByteArray array;\n  array.push_back(0);\n  array.push_back(1);\n  array.push_back(0);\n\n  ByteArray array_final;\n  {\n    ByteArray start((uint8_t*)(&range.start),\n                    (uint8_t*)(&range.start) + sizeof(uint32_t));\n    ByteArray end((uint8_t*)(&range.end),\n                  (uint8_t*)(&range.end) + sizeof(uint32_t));\n\n    array.append(start);\n    array.append(end);\n\n    std::string s(array.begin(), array.end());\n    auto s2 = std::make_unique<std::string>(s);\n    auto u_char_arr = reinterpret_cast<unsigned const char*>(s2->c_str());\n    array_final = ByteArray(u_char_arr, u_char_arr + s.size());\n  }\n\n  // Check\n  EXPECT_EQ(vec_final.size(), 11);\n  EXPECT_EQ(vec_final.size(), array_final.size());\n  for (size_t i = 0; i < vec_final.size(); i++) {\n    EXPECT_EQ(vec_final[i], array_final[i]);\n  }\n\n  std::vector<uint8_t> vec_copy(array_final.begin(), array_final.end());\n  EXPECT_EQ(vec_copy.size(), array_final.size());\n  for (size_t i = 0; i < vec_copy.size(); i++) {\n    EXPECT_EQ(vec_copy[i], array_final[i]);\n  }\n}\n\ntemplate <int N>\nstruct TinyTrivialStruct {\n  char c[N];\n};\n\nTEST(Vector, TrivialTinyInt){\n#define TRIVIAL_TINY_INT(T)       \\\n  {                               \\\n    Vector<T> array;              \\\n    T v = 100;                    \\\n    for (T i = 1; i < 100; i++) { \\\n      array.push_back(i);         \\\n    }                             \\\n    array.push_back(v);           \\\n    int sum = 0;                  \\\n    for (auto i : array) {        \\\n      sum += (int)i;              \\\n    }                             \\\n    EXPECT_EQ(sum, 5050);         \\\n  }\n\n    TRIVIAL_TINY_INT(uint8_t) TRIVIAL_TINY_INT(uint16_t)\n        TRIVIAL_TINY_INT(uint32_t) TRIVIAL_TINY_INT(uint64_t)}\n\nTEST(Vector, TrivialTinyStruct){\n#define TRIVIAL_TINY_STRUCT(N)                 \\\n  {                                            \\\n    Vector<TinyTrivialStruct<N>> array;        \\\n    TinyTrivialStruct<N> s;                    \\\n    for (int i = 0; i < N; i++) {              \\\n      s.c[i] = -i;                             \\\n    }                                          \\\n    array.push_back(s);                        \\\n    array.push_back(std::move(s));             \\\n    array.emplace_back(s);                     \\\n    for (int i = 0; i < N; i++) {              \\\n      for (int j = 0; j < array.size(); j++) { \\\n        EXPECT_EQ(array[j].c[i], -i);          \\\n      }                                        \\\n    }                                          \\\n  }\n\n    TRIVIAL_TINY_STRUCT(1) TRIVIAL_TINY_STRUCT(2) TRIVIAL_TINY_STRUCT(3)\n        TRIVIAL_TINY_STRUCT(4) TRIVIAL_TINY_STRUCT(5) TRIVIAL_TINY_STRUCT(6)\n            TRIVIAL_TINY_STRUCT(7) TRIVIAL_TINY_STRUCT(8)}\n\nTEST(Vector, FromStream) {\n  std::string data = \"Hello World!\";\n  std::stringstream stream(data);\n\n  std::vector<uint8_t> vector((std::istreambuf_iterator<char>(stream)),\n                              std::istreambuf_iterator<char>());\n\n  ByteArray empty = ByteArrayFromStream(stream);\n  EXPECT_TRUE(empty.empty());\n\n  {\n    stream.seekg(0);\n    ByteArray full = ByteArrayFromStream(stream);\n\n    EXPECT_EQ(vector.size(), full.size());\n    for (size_t i = 0; i < vector.size(); i++) {\n      EXPECT_EQ(vector[i], full[i]);\n    }\n  }\n\n  {\n    stream.seekg(1);\n    ByteArray partial = ByteArrayFromStream(stream);\n\n    EXPECT_EQ(vector.size() - 1, partial.size());\n    for (size_t i = 0; i < partial.size(); i++) {\n      EXPECT_EQ(vector[i + 1], partial[i]);\n    }\n  }\n}\n\nTEST(Vector, FromString) {\n  std::string data = \"Hello World!\";\n\n  std::vector<char> vector(data.begin(), data.end());\n  ByteArray array = ByteArrayFromString(data);\n\n  EXPECT_EQ(vector.size(), array.size());\n  for (size_t i = 0; i < vector.size(); i++) {\n    EXPECT_EQ(vector[i], array[i]);\n  }\n}\n\nTEST(Vector, Pointer) {\n  // Vector<T is pointer> shares the same push_back method.\n  int a = 100;\n  int b = 200;\n  std::string sa = \"300\";\n  std::string sb = \"400\";\n  std::string sc = \"500\";\n\n  Vector<const int*> ints;\n  ints.push_back(&a);\n  CheckVector(ints);\n  ints.push_back(&b);\n  CheckVector(ints);\n\n  Vector<std::string*> strings;\n  strings.push_back(&sa);\n  CheckVector(strings);\n  strings.push_back(&sb);\n  CheckVector(strings);\n  EXPECT_EQ(strings.emplace_back(&sc), &sc);\n  CheckVector(strings);\n\n  EXPECT_EQ(*ints[0], 100);\n  EXPECT_EQ(*ints[1], 200);\n  EXPECT_EQ(*strings[0], \"300\");\n  EXPECT_EQ(*strings[1], \"400\");\n  EXPECT_EQ(*strings[2], \"500\");\n\n  Vector<char> chars;\n  chars.push_back(0);\n  CheckVector(chars);  // Make sure push_back(const void* e) not called\n  chars.push_back(1);\n  CheckVector(chars);\n  EXPECT_EQ(chars[1], 1);\n\n  Vector<const char*> c_strings;\n  c_strings.push_back(\"abcd\");\n  c_strings.push_back(\"1234\");\n  EXPECT_EQ(std::strcmp(c_strings[0], \"abcd\"), 0);\n  EXPECT_EQ(std::strcmp(c_strings[1], \"1234\"), 0);\n}\n\nTEST(Vector, ConstructFill) {\n  class NontrivialInt {\n   public:\n    NontrivialInt(int i = -1) {\n      value_ = std::make_shared<std::string>(std::to_string(i));\n    }\n\n    operator int() const { return std::stoi(*value_); }\n    bool operator==(const NontrivialInt& other) {\n      return (int)(*this) == (int)other;\n    }\n    NontrivialInt& operator+=(int value) {\n      value_ = std::make_shared<std::string>(\n          std::to_string(std::stoi(*value_) + value));\n      return *this;\n    }\n\n   private:\n    std::shared_ptr<std::string> value_;\n  };\n\n  auto to_s = [](const Vector<NontrivialInt>& array) -> std::string {\n    std::string result;\n    for (auto& i : array) {\n      result += std::to_string((int)i);\n    }\n    return result;\n  };\n\n  {\n    NontrivialInt i5(5);\n    InlineVector<NontrivialInt, 3> vec(4, i5);\n    EXPECT_EQ(to_s(vec), \"5555\");\n    EXPECT_FALSE(vec.is_static_buffer());\n\n    InlineVector<NontrivialInt, 3> vec2(3, i5);\n    EXPECT_EQ(to_s(vec2), \"555\");\n    EXPECT_TRUE(vec2.is_static_buffer());\n\n    InlineVector<NontrivialInt, 3> vec3(3);\n    EXPECT_EQ(to_s(vec3), \"-1-1-1\");\n    EXPECT_TRUE(vec3.is_static_buffer());\n\n    InlineVector<NontrivialInt, 3> vec4(4);\n    EXPECT_EQ(to_s(vec4), \"-1-1-1-1\");\n    EXPECT_FALSE(vec4.is_static_buffer());\n  }\n\n  {\n    InlineVector<bool, 3> vec(3);\n    EXPECT_EQ(vec.size(), 3);\n    EXPECT_TRUE(vec.is_static_buffer());\n    for (auto b : vec) {\n      EXPECT_FALSE(b);\n    }\n  }\n\n  {\n    InlineVector<bool, 3> vec(5);\n    EXPECT_EQ(vec.size(), 5);\n    EXPECT_FALSE(vec.is_static_buffer());\n    for (auto b : vec) {\n      EXPECT_FALSE(b);\n    }\n  }\n\n  {\n    InlineVector<bool, 3> vec(5, true);\n    EXPECT_EQ(vec.size(), 5);\n    EXPECT_FALSE(vec.is_static_buffer());\n    for (auto b : vec) {\n      EXPECT_TRUE(b);\n    }\n  }\n\n  {\n    InlineVector<float, 3> vec(4, 3.14f);\n    EXPECT_EQ(vec.size(), 4);\n    for (auto f : vec) {\n      EXPECT_EQ(f, 3.14f);\n    }\n    EXPECT_FALSE(vec.is_static_buffer());\n\n    InlineVector<float, 3> vec2(3, 3.14f);\n    EXPECT_EQ(vec2.size(), 3);\n    for (auto f : vec2) {\n      EXPECT_EQ(f, 3.14f);\n    }\n    EXPECT_TRUE(vec2.is_static_buffer());\n\n    InlineVector<float, 3> vec3(3);\n    EXPECT_EQ(vec3.size(), 3);\n    for (auto f : vec3) {\n      EXPECT_EQ(f, 0.0f);\n    }\n    EXPECT_TRUE(vec3.is_static_buffer());\n\n    InlineVector<float, 3> vec4(4);\n    EXPECT_EQ(vec4.size(), 4);\n    for (auto f : vec4) {\n      EXPECT_EQ(f, 0.0f);\n    }\n    EXPECT_FALSE(vec4.is_static_buffer());\n  }\n\n  {\n    InlineVector<const void*, 3> vec(4, (const void*)(&Matrix3::zero));\n    for (auto p : vec) {\n      EXPECT_EQ(p, (const void*)(&Matrix3::zero));\n    }\n    EXPECT_EQ(vec.size(), 4);\n    EXPECT_FALSE(vec.is_static_buffer());\n\n    InlineVector<const void*, 3> vec2(3);\n    for (auto p : vec2) {\n      EXPECT_EQ(p, nullptr);\n    }\n    EXPECT_EQ(vec2.size(), 3);\n    EXPECT_TRUE(vec2.is_static_buffer());\n  }\n\n  {\n    InlineVector<NontrivialInt, 3> vec;\n    for (int i = 0; i < 10; i++) {\n      vec.emplace_back(i);\n    }\n\n    InlineVector<NontrivialInt, 3> vec2(vec.begin() + 2, vec.begin() + 5);\n    EXPECT_EQ(to_s(vec2), \"234\");\n    EXPECT_TRUE(vec2.is_static_buffer());\n  }\n\n  {\n    InlineVector<int, 3> vec;\n    for (int i = 0; i < 10; i++) {\n      vec.emplace_back(i);\n    }\n\n    InlineVector<int, 3> vec2(vec.begin() + 2, vec.begin() + 5);\n    EXPECT_EQ(vec2.size(), 3);\n    EXPECT_EQ(vec2[0], 2);\n    EXPECT_EQ(vec2[1], 3);\n    EXPECT_EQ(vec2[2], 4);\n    EXPECT_TRUE(vec2.is_static_buffer());\n  }\n}\n\nTEST(Vector, InlineSwap) {\n  auto to_s = [](const Vector<int>& array) -> std::string {\n    std::string result;\n    for (int i : array) {\n      result += std::to_string(i);\n    }\n    return result;\n  };\n\n  {\n    InlineVector<int, 5> array{0, 1, 2, 3, 4};\n    Vector<int> array2{5, 6, 7, 8, 9};\n    array2.swap(array);\n    EXPECT_EQ(to_s(array), \"56789\");\n    EXPECT_EQ(to_s(array2), \"01234\");\n  }\n\n  {\n    InlineVector<int, 5> array{0, 1, 2, 3, 4};\n    Vector<int> array2{5, 6, 7, 8, 9};\n    array.swap(array2);\n    EXPECT_EQ(to_s(array), \"56789\");\n    EXPECT_EQ(to_s(array2), \"01234\");\n  }\n\n  {\n    InlineVector<int, 5> array{0, 1, 2, 3, 4};\n    Vector<int> array2{5, 6, 7, 8, 9};\n    std::swap(array, array2);\n    EXPECT_EQ(to_s(array), \"56789\");\n    EXPECT_EQ(to_s(array2), \"01234\");\n  }\n\n  // Inline buffer overflow\n  {\n    InlineVector<int, 5> array{0, 1, 2, 3, 4, 5};\n    Vector<int> array2{5, 6, 7, 8, 9};\n    array2.swap(array);\n    EXPECT_EQ(to_s(array), \"56789\");\n    EXPECT_EQ(to_s(array2), \"012345\");\n  }\n\n  {\n    InlineVector<int, 5> array{0, 1, 2, 3, 4, 5};\n    Vector<int> array2{5, 6, 7, 8, 9};\n    array.swap(array2);\n    EXPECT_EQ(to_s(array), \"56789\");\n    EXPECT_EQ(to_s(array2), \"012345\");\n  }\n\n  {\n    InlineVector<int, 5> array{0, 1, 2, 3, 4, 5};\n    Vector<int> array2{5, 6, 7, 8, 9};\n    std::swap(array, array2);\n    EXPECT_EQ(to_s(array), \"56789\");\n    EXPECT_EQ(to_s(array2), \"012345\");\n  }\n}\n\nTEST(Vector, Inline) {\n  auto to_s = [](const Vector<int>& array) -> std::string {\n    std::string result;\n    for (int i : array) {\n      result += std::to_string(i);\n    }\n    return result;\n  };\n\n  InlineVector<int, 100> array;\n  const auto data0 = array.data();\n  EXPECT_TRUE((uint8_t*)data0 - (uint8_t*)&array == sizeof(Vector<int>));\n  for (size_t i = 1; i <= 80; i++) {\n    array.push_back(i);\n  }\n  EXPECT_EQ(data0, array.data());\n  CheckVector(array);\n\n  array.clear();\n  CheckVector(array);\n  EXPECT_EQ(data0, array.data());\n  for (size_t i = 1; i <= 80; i++) {\n    array.push_back(i);\n  }\n  EXPECT_EQ(data0, array.data());\n  CheckVector(array);\n\n  EXPECT_EQ(array.size(), 80);\n  EXPECT_FALSE(array.reserve(90));\n  CheckVector(array);\n  EXPECT_EQ(data0, array.data());  // Reserve but no reallocation.\n  for (size_t i = 81; i <= 90; i++) {\n    array.push_back(i);\n  }\n  EXPECT_EQ(array.size(), 90);\n  CheckVector(array);\n  EXPECT_EQ(data0, array.data());  // Still no reallocation.\n\n  EXPECT_FALSE(array.resize<false>(100));\n  CheckVector(array);              // Resize but still no reallocation.\n  EXPECT_EQ(data0, array.data());  // Resize but no reallocation.\n  for (size_t i = 90; i < 100; i++) {\n    array[i] = i + 1;\n  }\n  EXPECT_EQ(array.size(), 100);\n  CheckVector(array);\n\n  array.push_back(101);\n  CheckVector(array);  // Reallocation\n  EXPECT_TRUE(data0 != array.data());\n  int sum = 0;\n  for (auto i : array) {\n    sum += i;\n  }\n  EXPECT_EQ(sum, 5050 + 101);\n\n  array.clear_and_shrink();\n  EXPECT_TRUE(array.empty());\n  EXPECT_EQ(data0, array.data());\n  for (size_t i = 1; i <= 5; i++) {\n    array.push_back(i);\n  }\n  EXPECT_EQ(array.size(), 5);\n  EXPECT_EQ(to_s(array), \"12345\");\n\n  // Test constructors and assignments\n  Vector<int> sourceArray{0, 10, 20, 30, 40};\n  CheckVector(sourceArray);\n\n  {\n    InlineVector<int, 10> array;\n    CheckVector(array);\n    InlineVector<int, 5> samllArray{100, 101, 102, 103, 104};\n    EXPECT_TRUE((uint8_t*)samllArray.data() - (uint8_t*)&samllArray ==\n                sizeof(Vector<int>));\n\n    array = sourceArray;\n    CheckVector(array);\n    EXPECT_TRUE((uint8_t*)array.data() - (uint8_t*)&array ==\n                sizeof(Vector<int>));\n    EXPECT_EQ(array.capacity(), 10);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"010203040\");\n\n    array = {5, 4, 3, 2, 1};\n    CheckVector(array);\n    EXPECT_TRUE((uint8_t*)array.data() - (uint8_t*)&array ==\n                sizeof(Vector<int>));\n    EXPECT_EQ(array.capacity(), 10);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"54321\");\n\n    // move sourceArray to array, sourceArray.size() <= array.capacity(), no\n    // reallocation\n    array = std::move(sourceArray);\n    CheckVector(array);\n    EXPECT_TRUE((uint8_t*)array.data() - (uint8_t*)&array ==\n                sizeof(Vector<int>));\n    EXPECT_EQ(array.capacity(), 10);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"010203040\");\n    EXPECT_TRUE(sourceArray.empty());\n\n    // copy assign samllArray to array, no reallocation\n    array = samllArray;\n    CheckVector(array);\n    EXPECT_TRUE((uint8_t*)array.data() - (uint8_t*)&array ==\n                sizeof(Vector<int>));\n    EXPECT_EQ(array.capacity(), 10);\n    EXPECT_EQ(array.size(), samllArray.size());\n    EXPECT_EQ(to_s(array), \"100101102103104\");\n\n    array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};\n    CheckVector(array);  // Will reallocate\n    EXPECT_FALSE((uint8_t*)array.data() - (uint8_t*)&array ==\n                 sizeof(Vector<int>));\n    EXPECT_EQ(array.size(), 11);\n    EXPECT_EQ(to_s(array), \"1234567891011\");\n\n    InlineVector<int, 5> array2{1, 2, 3, 4, 5};\n    CheckVector(array2);\n    EXPECT_TRUE((uint8_t*)array2.data() - (uint8_t*)&array2 ==\n                sizeof(Vector<int>));\n    EXPECT_EQ(array2.capacity(), 5);\n    EXPECT_EQ(array2.size(), 5);\n    array2.push_back(6);\n    CheckVector(array2);\n    EXPECT_FALSE((uint8_t*)array2.data() - (uint8_t*)&array2 ==\n                 sizeof(Vector<int>));\n    EXPECT_EQ(to_s(array2), \"123456\");\n\n    InlineVector<int, 10> array3(samllArray);\n    CheckVector(array);\n    EXPECT_TRUE((uint8_t*)array3.data() - (uint8_t*)&array3 ==\n                sizeof(Vector<int>));\n    EXPECT_EQ(array3.capacity(), 10);\n    EXPECT_EQ(array3.size(), samllArray.size());\n    EXPECT_EQ(to_s(array3), \"100101102103104\");\n  }\n\n  {\n    InlineVector<int, 10> array0;\n    CheckVector(array0);\n    for (size_t i = 0; i < array0.capacity(); i++) {\n      array0.push_back(i);\n    }\n    CheckVector(array0);\n    EXPECT_TRUE((uint8_t*)array0.data() - (uint8_t*)&array0 ==\n                sizeof(Vector<int>));\n\n    InlineVector<int, 10> array1;\n    array1 = array0;\n    CheckVector(array1);\n    EXPECT_TRUE((uint8_t*)array1.data() - (uint8_t*)&array1 ==\n                sizeof(Vector<int>));\n    EXPECT_EQ(to_s(array1), \"0123456789\");\n\n    InlineVector<int, 10> array2;\n    array2 = std::move(array0);\n    CheckVector(array2);\n    EXPECT_TRUE((uint8_t*)array2.data() - (uint8_t*)&array2 ==\n                sizeof(Vector<int>));\n    EXPECT_EQ(to_s(array2), \"0123456789\");\n    EXPECT_TRUE(array0.empty());\n  }\n\n  {\n    InlineVector<int, 5> array;\n    array.resize<false>(5);\n    EXPECT_TRUE(array.is_static_buffer());\n\n    array.resize<true>(6);\n    EXPECT_FALSE(array.is_static_buffer());\n  }\n}\n\nTEST(Vector, InlineSafety) {\n  static int64_t gAliveCount = 0;\n  class NontrivialInt {\n   public:\n    NontrivialInt(int i = -1) {\n      gAliveCount++;\n      value_ = std::make_shared<std::string>(std::to_string(i));\n    }\n    NontrivialInt(NontrivialInt&& other) : value_(std::move(other.value_)) {\n      gAliveCount++;\n    }\n    NontrivialInt(const NontrivialInt& other) : value_(other.value_) {\n      gAliveCount++;\n    }\n    NontrivialInt& operator=(const NontrivialInt& other) = default;\n    NontrivialInt& operator=(NontrivialInt&& other) = default;\n    ~NontrivialInt() { gAliveCount--; }\n\n    operator int() const { return std::stoi(*value_); }\n\n   private:\n    std::shared_ptr<std::string> value_;\n  };\n\n  auto to_s = [](const Vector<NontrivialInt>& array) -> std::string {\n    std::string result;\n    for (auto& i : array) {\n      result += std::to_string((int)i);\n    }\n    return result;\n  };\n\n  {\n    InlineVector<NontrivialInt, 5> array;\n    array.reserve(1);\n    array.reserve(5);\n    EXPECT_TRUE(array.is_static_buffer());\n    array.reserve(6);\n    EXPECT_FALSE(array.is_static_buffer());\n  }\n\n  {\n    InlineVector<NontrivialInt, 5> array;\n    EXPECT_TRUE(array.is_static_buffer());\n  }\n\n  {\n    InlineVector<int, 5> array{100, 101, 102, 103, 104};\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_TRUE(array.is_static_buffer());\n\n    InlineVector<int, 5> array2{100, 101, 102, 103, 104, 105};\n    EXPECT_EQ(array2.size(), 6);\n    EXPECT_FALSE(array2.is_static_buffer());\n\n    array2 = {1, 2, 3, 4, 5};\n    EXPECT_EQ(array2.size(), 5);\n    EXPECT_FALSE(\n        array2.is_static_buffer());  // Not using static buffer even though size\n                                     // fits to self's static buffer.\n  }\n\n  {\n    // Copy contructors\n    Vector<NontrivialInt> source;\n    for (size_t i = 0; i < 5; i++) {\n      source.emplace_back(i);\n    }\n    EXPECT_EQ(to_s(source), \"01234\");\n    EXPECT_EQ(gAliveCount, 5);\n\n    InlineVector<NontrivialInt, 5> array(source);\n    EXPECT_TRUE(array.is_static_buffer());\n    EXPECT_EQ(to_s(array), \"01234\");\n    EXPECT_EQ(gAliveCount, 10);\n\n    source.emplace_back(5);\n    EXPECT_EQ(to_s(source), \"012345\");\n    EXPECT_EQ(gAliveCount, 11);\n\n    InlineVector<NontrivialInt, 5> array2(source);\n    EXPECT_FALSE(array2.is_static_buffer());\n    EXPECT_EQ(to_s(array2), \"012345\");\n    EXPECT_EQ(gAliveCount, 17);\n\n    decltype(array2) array3(array2);\n    EXPECT_FALSE(array3.is_static_buffer());\n    EXPECT_EQ(to_s(array3), \"012345\");\n    EXPECT_EQ(gAliveCount, 23);\n\n    InlineVector<NontrivialInt, 6> array4(array3);\n    EXPECT_TRUE(array4.is_static_buffer());\n    EXPECT_EQ(to_s(array4), \"012345\");\n    EXPECT_EQ(gAliveCount, 29);\n\n    array4.erase(array4.begin());\n    EXPECT_EQ(to_s(array4), \"12345\");\n    EXPECT_EQ(gAliveCount, 28);\n  }\n  EXPECT_EQ(gAliveCount, 0);\n\n  {\n    // Copy assignment operator\n    Vector<NontrivialInt> source;\n    for (size_t i = 0; i < 5; i++) {\n      source.emplace_back(i);\n    }\n    EXPECT_EQ(to_s(source), \"01234\");\n\n    InlineVector<NontrivialInt, 5> array;\n    array = source;\n    EXPECT_TRUE(array.is_static_buffer());\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    source.emplace_back(5);\n    EXPECT_EQ(to_s(source), \"012345\");\n\n    InlineVector<NontrivialInt, 5> array2;\n    array2 = source;\n    EXPECT_FALSE(array2.is_static_buffer());\n    EXPECT_EQ(to_s(array2), \"012345\");\n\n    decltype(array2) array3;\n    array3 = array2;\n    EXPECT_FALSE(array3.is_static_buffer());\n    EXPECT_EQ(to_s(array3), \"012345\");\n\n    InlineVector<NontrivialInt, 6> array4;\n    array4 = array3;\n    EXPECT_TRUE(array4.is_static_buffer());\n    EXPECT_EQ(to_s(array4), \"012345\");\n  }\n\n  {\n    // Move contructors\n    Vector<NontrivialInt> source;\n    for (size_t i = 0; i < 5; i++) {\n      source.emplace_back(i);\n    }\n    EXPECT_EQ(to_s(source), \"01234\");\n    EXPECT_EQ(gAliveCount, 5);\n\n    InlineVector<NontrivialInt, 5> array(std::move(source));\n    EXPECT_TRUE(array.is_static_buffer());\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    EXPECT_TRUE(source.empty());\n    EXPECT_EQ(gAliveCount, 5);\n\n    for (size_t i = 0; i < 6; i++) {\n      source.emplace_back(i);\n    }\n    EXPECT_EQ(to_s(source), \"012345\");\n    EXPECT_EQ(gAliveCount, 11);\n\n    const auto data0 = source.data();\n    InlineVector<NontrivialInt, 5> array2(std::move(source));\n    EXPECT_FALSE(array2.is_static_buffer());\n    EXPECT_EQ(to_s(array2), \"012345\");\n    EXPECT_EQ(data0, array2.data());  // buffer moved\n    EXPECT_TRUE(source.empty());\n    EXPECT_EQ(gAliveCount, 11);\n\n    decltype(array2) array3(std::move(array2));\n    EXPECT_FALSE(array3.is_static_buffer());\n    EXPECT_EQ(to_s(array3), \"012345\");\n    EXPECT_EQ(data0, array3.data());  // buffer moved\n    EXPECT_TRUE(array2.empty());\n    EXPECT_EQ(gAliveCount, 11);\n\n    InlineVector<NontrivialInt, 6> array4(std::move(array3));\n    EXPECT_TRUE(array4.is_static_buffer());\n    EXPECT_EQ(to_s(array4), \"012345\");\n    EXPECT_TRUE(array3.empty());\n    EXPECT_EQ(data0, array3.data());  // buffer not moved from array3\n    EXPECT_EQ(gAliveCount, 11);\n  }\n  EXPECT_EQ(gAliveCount, 0);\n\n  {\n    // Move assignment operator\n    Vector<NontrivialInt> source;\n    for (size_t i = 0; i < 5; i++) {\n      source.emplace_back(i);\n    }\n    EXPECT_EQ(to_s(source), \"01234\");\n    EXPECT_EQ(gAliveCount, 5);\n\n    InlineVector<NontrivialInt, 5> array;\n    array = std::move(source);\n    EXPECT_TRUE(array.is_static_buffer());\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    EXPECT_TRUE(source.empty());\n    EXPECT_EQ(gAliveCount, 5);\n\n    for (size_t i = 0; i < 6; i++) {\n      source.emplace_back(i);\n    }\n    EXPECT_EQ(to_s(source), \"012345\");\n    EXPECT_EQ(gAliveCount, 11);\n\n    const auto data0 = source.data();\n    InlineVector<NontrivialInt, 5> array2;\n    array2 = std::move(source);\n    EXPECT_FALSE(array2.is_static_buffer());\n    EXPECT_EQ(to_s(array2), \"012345\");\n    EXPECT_EQ(data0, array2.data());  // buffer moved\n    EXPECT_TRUE(source.empty());\n    EXPECT_EQ(gAliveCount, 11);\n\n    decltype(array2) array3;\n    array3 = std::move(array2);\n    EXPECT_FALSE(array3.is_static_buffer());\n    EXPECT_EQ(to_s(array3), \"012345\");\n    EXPECT_EQ(data0, array3.data());  // buffer moved\n    EXPECT_TRUE(array2.empty());\n    EXPECT_EQ(gAliveCount, 11);\n\n    InlineVector<NontrivialInt, 6> array4;\n    array4 = std::move(array3);\n    EXPECT_TRUE(array4.is_static_buffer());\n    EXPECT_EQ(to_s(array4), \"012345\");\n    EXPECT_TRUE(array3.empty());\n    EXPECT_EQ(data0, array3.data());  // buffer not moved from array3\n    EXPECT_EQ(gAliveCount, 11);\n  }\n  EXPECT_EQ(gAliveCount, 0);\n}\n\nTEST(Vector, InlineNontrivial) {\n  auto to_s = [](const Vector<std::string>& array) -> std::string {\n    std::string result;\n    for (auto& i : array) {\n      result += i;\n    }\n    return result;\n  };\n\n  InlineVector<std::string, 100> array;\n  const auto data0 = array.data();\n  EXPECT_TRUE((uint8_t*)data0 - (uint8_t*)&array ==\n              sizeof(Vector<std::string>));\n  for (size_t i = 1; i <= 80; i++) {\n    array.push_back(std::to_string(i));\n  }\n  EXPECT_EQ(data0, array.data());\n  CheckVector(array);\n\n  array.clear();\n  CheckVector(array);\n  EXPECT_EQ(data0, array.data());\n  for (size_t i = 1; i <= 80; i++) {\n    array.push_back(std::to_string(i));\n  }\n  EXPECT_EQ(data0, array.data());\n  CheckVector(array);\n\n  EXPECT_EQ(array.size(), 80);\n  EXPECT_FALSE(array.reserve(90));\n  CheckVector(array);\n  EXPECT_EQ(data0, array.data());  // Reserve but no reallocation.\n  for (size_t i = 81; i <= 90; i++) {\n    array.push_back(std::to_string(i));\n  }\n  EXPECT_EQ(array.size(), 90);\n  CheckVector(array);\n  EXPECT_EQ(data0, array.data());  // Still no reallocation.\n\n  EXPECT_FALSE(array.resize(100));\n  CheckVector(array);              // Resize but still no reallocation.\n  EXPECT_EQ(data0, array.data());  // Resize but no reallocation.\n  for (size_t i = 90; i < 100; i++) {\n    array[i] = std::to_string(i + 1);\n  }\n  EXPECT_EQ(array.size(), 100);\n  CheckVector(array);\n\n  array.push_back(std::to_string(101));\n  CheckVector(array);  // Reallocation\n  EXPECT_TRUE(data0 != array.data());\n  int sum = 0;\n  for (auto i : array) {\n    sum += std::stoi(i);\n  }\n  EXPECT_EQ(sum, 5050 + 101);\n\n  array.clear_and_shrink();\n  EXPECT_TRUE(array.empty());\n  EXPECT_EQ(data0, array.data());\n  for (size_t i = 1; i <= 5; i++) {\n    array.push_back(std::to_string(i));\n  }\n  EXPECT_EQ(array.size(), 5);\n  EXPECT_EQ(to_s(array), \"12345\");\n\n  // Test constructors and assignments\n  Vector<std::string> sourceArray{\"0\", \"10\", \"20\", \"30\", \"40\"};\n  CheckVector(sourceArray);\n\n  {\n    InlineVector<std::string, 10> array;\n    CheckVector(array);\n\n    array = sourceArray;\n    CheckVector(array);\n    EXPECT_TRUE((uint8_t*)array.data() - (uint8_t*)&array ==\n                sizeof(Vector<std::string>));\n    EXPECT_EQ(array.capacity(), 10);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"010203040\");\n\n    array = {\"5\", \"4\", \"3\", \"2\", \"1\"};\n    CheckVector(array);\n    EXPECT_TRUE((uint8_t*)array.data() - (uint8_t*)&array ==\n                sizeof(Vector<std::string>));\n    EXPECT_EQ(array.capacity(), 10);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"54321\");\n\n    // move sourceArray to array, sourceArray.size() <= array.capacity(), no\n    // reallocation\n    array = std::move(sourceArray);\n    CheckVector(array);\n    EXPECT_TRUE((uint8_t*)array.data() - (uint8_t*)&array ==\n                sizeof(Vector<std::string>));\n    EXPECT_EQ(array.capacity(), 10);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"010203040\");\n    EXPECT_TRUE(sourceArray.empty());\n\n    array = {\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"10\", \"11\"};\n    CheckVector(array);  // Will reallocate\n    EXPECT_FALSE((uint8_t*)array.data() - (uint8_t*)&array ==\n                 sizeof(Vector<std::string>));\n    EXPECT_EQ(array.size(), 11);\n    EXPECT_EQ(to_s(array), \"1234567891011\");\n\n    InlineVector<std::string, 5> array2{\"1\", \"2\", \"3\", \"4\", \"5\"};\n    CheckVector(array2);\n    EXPECT_TRUE((uint8_t*)array2.data() - (uint8_t*)&array2 ==\n                sizeof(Vector<int>));\n    EXPECT_EQ(array2.capacity(), 5);\n    EXPECT_EQ(array2.size(), 5);\n    array2.push_back(\"6\");\n    CheckVector(array2);\n    EXPECT_FALSE((uint8_t*)array2.data() - (uint8_t*)&array2 ==\n                 sizeof(Vector<int>));\n    EXPECT_EQ(to_s(array2), \"123456\");\n  }\n\n  {\n    InlineVector<std::string, 10> array0;\n    CheckVector(array0);\n    for (size_t i = 0; i < array0.capacity(); i++) {\n      array0.push_back(std::to_string(i));\n    }\n    CheckVector(array0);\n    EXPECT_TRUE((uint8_t*)array0.data() - (uint8_t*)&array0 ==\n                sizeof(Vector<std::string>));\n\n    InlineVector<std::string, 10> array1;\n    array1 = array0;\n    CheckVector(array1);\n    EXPECT_TRUE((uint8_t*)array1.data() - (uint8_t*)&array1 ==\n                sizeof(Vector<std::string>));\n    EXPECT_EQ(to_s(array1), \"0123456789\");\n\n    InlineVector<std::string, 10> array2;\n    array2 = std::move(array0);\n    CheckVector(array2);\n    EXPECT_TRUE((uint8_t*)array2.data() - (uint8_t*)&array2 ==\n                sizeof(Vector<std::string>));\n    EXPECT_EQ(to_s(array2), \"0123456789\");\n    EXPECT_TRUE(array0.empty());\n  }\n}\n\nTEST(Vector, Trivial) {\n  auto to_s = [](const Vector<int>& array) -> std::string {\n    std::string result;\n    for (int i : array) {\n      result += std::to_string(i);\n    }\n    return result;\n  };\n\n  static_assert(Vector<int>::is_trivial);\n  static_assert(Vector<int>::is_trivially_destructible);\n  static_assert(Vector<int>::is_trivially_destructible_after_move);\n  static_assert(Vector<int>::is_trivially_relocatable);\n  static_assert(Vector<std::pair<int, int>>::is_trivial);\n  static_assert(Vector<std::pair<int, int>>::is_trivially_destructible);\n  static_assert(\n      Vector<std::pair<int, int>>::is_trivially_destructible_after_move);\n  static_assert(Vector<std::pair<int, int>>::is_trivially_relocatable);\n\n  {\n    Vector<int> array;\n    EXPECT_EQ(array.size(), 0);\n    EXPECT_TRUE(array.empty());\n    CheckVector(array);\n  }\n\n  {\n    Vector<int> array(5);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_FALSE(array.empty());\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], 0);\n      EXPECT_EQ(array.at(i), 0);\n    }\n    EXPECT_EQ(array.front(), 0);\n    EXPECT_EQ(array.back(), 0);\n    CheckVector(array);\n  }\n\n  {\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<int> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    EXPECT_EQ(array.size(), 5);\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], buffer[i]);\n    }\n    CheckVector(array);\n\n    EXPECT_EQ(array.front(), 10);\n    EXPECT_EQ(array.back(), 14);\n    EXPECT_TRUE(std::memcmp(buffer, array.data(), sizeof(buffer)) == 0);\n  }\n\n  {\n    Matrix3 buffer[3] = {Matrix3::zero, Matrix3::identity,\n                         Matrix3(1, 2, 3, 4, 5, 6, 7, 8, 9)};\n    Vector<Matrix3> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    EXPECT_EQ(array.size(), 3);\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], buffer[i]);\n    }\n    EXPECT_EQ(array[2].elements[6], 3.f);\n    CheckVector(array);\n\n    // Copy\n    Vector<Matrix3> array2(array);\n    EXPECT_EQ(array2.size(), 3);\n    for (size_t i = 0; i < array2.size(); i++) {\n      EXPECT_EQ(array2[i], buffer[i]);\n    }\n    EXPECT_EQ(array2[2].elements[6], 3.f);\n    CheckVector(array2);\n\n    // Copy assign\n    Vector<Matrix3> array3(5);\n    EXPECT_EQ(array3.size(), 5);\n    for (size_t i = 0; i < array3.size(); i++) {\n      EXPECT_EQ(array3[i], Matrix3::identity);\n    }\n    array3 = array2;\n    CheckVector(array3);\n    EXPECT_EQ(array3.size(), 3);\n    for (size_t i = 0; i < array3.size(); i++) {\n      EXPECT_EQ(array3[i], buffer[i]);\n    }\n    EXPECT_EQ(array3[2].elements[6], 3.f);\n    CheckVector(array3);\n  }\n\n  {\n    Vector<Matrix3> array(5);\n    EXPECT_EQ(array.size(), 5);\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], Matrix3::identity);\n    }\n    CheckVector(array);\n  }\n\n  {\n    Vector<Matrix3> array({});\n    EXPECT_TRUE(array.empty());\n  }\n\n  {\n    // Construct from initializer list or iterators\n    Vector<Matrix3> array(\n        {Matrix3::zero, Matrix3::identity, Matrix3::zero, Matrix3::identity});\n    EXPECT_EQ(array.size(), 4);\n    EXPECT_EQ(array[0], Matrix3::zero);\n    EXPECT_EQ(array[1], Matrix3::identity);\n    EXPECT_EQ(array[2], Matrix3::zero);\n    EXPECT_EQ(array[3], Matrix3::identity);\n    CheckVector(array);\n\n    Vector<Matrix3> array2(5);\n    EXPECT_EQ(array2.size(), 5);\n    for (size_t i = 0; i < array2.size(); i++) {\n      EXPECT_EQ(array2[i], Matrix3::identity);\n    }\n    CheckVector(array2);\n    array2 = {Matrix3::identity, Matrix3::zero, Matrix3::identity,\n              Matrix3::zero};\n    CheckVector(array2);\n    EXPECT_EQ(array2.size(), 4);\n    EXPECT_EQ(array2[0], Matrix3::identity);\n    EXPECT_EQ(array2[1], Matrix3::zero);\n    EXPECT_EQ(array2[2], Matrix3::identity);\n    EXPECT_EQ(array2[3], Matrix3::zero);\n\n    {\n      int buffer[5] = {10, 11, 12, 13, 14};\n      Vector<int> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n      Vector<int> array2(array.begin(), array.end());\n      CheckVector(array2);\n      EXPECT_EQ(to_s(array2), \"1011121314\");\n      Vector<int> array3(array.begin() + 1, array.end());\n      CheckVector(array3);\n      EXPECT_EQ(to_s(array3), \"11121314\");\n      Vector<int> array4(array.begin() + 1, array.end() - 1);\n      CheckVector(array4);\n      EXPECT_EQ(to_s(array4), \"111213\");\n\n      Vector<int> array5(array.begin() + 2, array.end() - 2);\n      CheckVector(array5);\n      EXPECT_EQ(to_s(array5), \"12\");\n      Vector<int> array6(array.begin() + 3, array.end() - 2);\n      CheckVector(array6);\n      EXPECT_TRUE(array6.empty());\n    }\n\n    {\n      ByteArray array;\n      EXPECT_EQ(array.push_back(1), 1);\n      EXPECT_EQ(array.push_back(2), 2);\n      EXPECT_EQ(array.push_back(3), 3);\n      EXPECT_EQ(array.push_back(4), 4);\n\n      ByteArray array2(array.begin(), array.end());\n      CheckVector(array2);\n      EXPECT_EQ(array2.size(), 4);\n      EXPECT_EQ(array2[0], 1);\n      EXPECT_EQ(array2[1], 2);\n      EXPECT_EQ(array2[2], 3);\n      EXPECT_EQ(array2[3], 4);\n\n      ByteArray array3(&array[0], (&array[array.size() - 1]) + 1);\n      CheckVector(array3);\n      EXPECT_EQ(array3.size(), 4);\n      EXPECT_EQ(array3[0], 1);\n      EXPECT_EQ(array3[1], 2);\n      EXPECT_EQ(array3[2], 3);\n      EXPECT_EQ(array3[3], 4);\n    }\n  }\n\n  {\n    // Move\n    int buffer[5] = {10, 11, 12, 13, 14};\n    int buffer2[10] = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109};\n    Vector<int> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n\n    Vector<int> array2(std::move(array));\n    CheckVector(array2);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(array2.size(), 5);\n    for (size_t i = 0; i < array2.size(); i++) {\n      EXPECT_EQ(array2[i], buffer[i]);\n    }\n\n    Vector<int> array3(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    CheckVector(array3);\n    EXPECT_EQ(array3.size(), 10);\n    array = std::move(array3);\n    CheckVector(array);\n    EXPECT_TRUE(array3.empty());\n    EXPECT_EQ(array.size(), 10);\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], buffer2[i]);\n    }\n  }\n\n  {\n    // Basic push and pop\n    Vector<int> array;\n    for (int i = 1; i <= 100; i++) {\n      array.push_back(i);\n      CheckVector(array);\n    }\n    int sum = 0;\n    for (size_t i = 0; i < array.size(); i++) {\n      sum += array[i];\n    }\n    EXPECT_EQ(sum, 5050);\n\n    int buffer[5] = {10, 11, 12, 13, 14};\n    for (size_t i = 0; i < 5; i++) {\n      array.push_back(buffer[i]);\n    }\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 105);\n    sum = 0;\n    for (size_t i = 0; i < array.size(); i++) {\n      sum += array[i];\n    }\n    EXPECT_EQ(sum, 5050 + 10 + 11 + 12 + 13 + 14);\n\n    for (int i = 0; i < 5; i++) {\n      array.pop_back();\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 100);\n    sum = 0;\n    for (int i : array) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    array.grow() = 9999;\n    EXPECT_EQ(array.size(), 101);\n    EXPECT_EQ(array.back(), 9999);\n\n    array.grow(200);\n    EXPECT_EQ(array.size(), 200);\n  }\n\n  {\n    // Iterators\n    std::string output;\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<int> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (auto it = array.begin(); it != array.end(); it++) {\n      output += std::to_string(*it);\n    }\n    for (auto it = array.cbegin(); it != array.cend(); it++) {\n      output += std::to_string(*it);\n    }\n    for (int i : array) {\n      output += std::to_string(i);\n    }\n    EXPECT_EQ(output, \"101112131410111213141011121314\");\n\n    output = \"\";\n    for (auto it = array.rbegin(); it != array.rend(); it++) {\n      output += std::to_string(*it);\n    }\n    for (auto it = array.crbegin(); it != array.crend(); it++) {\n      output += std::to_string(*it);\n    }\n    EXPECT_EQ(output, \"14131211101413121110\");\n\n    // Writable iterator\n    output = \"\";\n    for (auto& i : array) {\n      i += 1;\n    }\n    for (auto it = array.begin(); it != array.end(); it++) {\n      *it += 1;\n    }\n    for (int i : array) {\n      output += std::to_string(i);\n    }\n    EXPECT_EQ(output, \"1213141516\");\n  }\n\n  {\n    // Erase and insert\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<int> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (int i = 0; i < 5; i++) {\n      auto it = array.erase(array.begin());\n      CheckVector(array);\n      EXPECT_EQ(it, array.begin());\n      if (i == 2) {\n        EXPECT_EQ(to_s(array), \"1314\");\n      }\n    }\n\n    EXPECT_TRUE(array.empty());\n    for (int i = 4; i >= 0; i--) {\n      array.insert(array.begin(), i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    auto it = array.erase(array.begin() + 1, array.begin() + 3);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"034\");\n    EXPECT_EQ(*it, 3);\n\n    it = array.erase(array.end() - 1);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"03\");\n    EXPECT_EQ(it, array.end());\n\n    it = array.erase(array.begin(), array.end());\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(it, array.end());\n\n    array.insert(array.begin(), 50);\n    CheckVector(array);\n    array.insert(array.end(), 51);\n    CheckVector(array);\n    array.insert(array.end(), 52);\n    CheckVector(array);\n    array.insert(array.begin() + 1, 49);\n    CheckVector(array);\n    array.insert(array.begin(), 48);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"4850495152\");\n\n    Vector<int> array2;\n    for (int i = 1; i <= 100; i++) {\n      array2.insert(array2.begin() + i - 1, i);\n    }\n    CheckVector(array2);\n    int sum = 0;\n    for (int i : array2) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    Vector<int> array3;\n    for (int i = 1; i <= 100; i++) {\n      array3.insert(array3.begin(), i);\n    }\n    CheckVector(array3);\n    sum = 0;\n    for (int i : array3) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n  }\n\n  {\n    // Erase and emplace\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<int> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (int i = 0; i < 5; i++) {\n      auto it = array.erase(array.begin());\n      CheckVector(array);\n      EXPECT_EQ(it, array.begin());\n      if (i == 2) {\n        EXPECT_EQ(to_s(array), \"1314\");\n      }\n    }\n\n    EXPECT_TRUE(array.empty());\n    for (int i = 4; i >= 0; i--) {\n      array.emplace(array.begin(), i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    auto it = array.erase(array.begin() + 1, array.begin() + 3);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"034\");\n    EXPECT_EQ(*it, 3);\n\n    it = array.erase(array.end() - 1);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"03\");\n    EXPECT_EQ(it, array.end());\n\n    it = array.erase(array.begin(), array.end());\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(it, array.end());\n\n    array.emplace(array.begin(), 50);\n    CheckVector(array);\n    array.emplace(array.end(), 51);\n    CheckVector(array);\n    array.emplace(array.end(), 52);\n    CheckVector(array);\n    array.emplace(array.begin() + 1, 49);\n    CheckVector(array);\n    array.emplace(array.begin(), 48);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"4850495152\");\n\n    Vector<int> array2;\n    for (int i = 1; i <= 100; i++) {\n      array2.emplace(array2.begin() + i - 1, i);\n    }\n    CheckVector(array2);\n    int sum = 0;\n    for (int i : array2) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    Vector<int> array3;\n    for (int i = 1; i <= 100; i++) {\n      array3.emplace(array3.begin(), i);\n    }\n    CheckVector(array3);\n    sum = 0;\n    for (int i : array3) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n  }\n\n  {\n    // Reserve\n    Vector<int> array;\n    EXPECT_TRUE(array.reserve(100));\n    CheckVector(array);\n    auto data_p = array.data();\n    for (int i = 1; i <= 100; i++) {\n      array.emplace_back(i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(data_p, array.data());\n  }\n\n  {\n    // Resize\n    Vector<float> farray;\n    farray.resize<true>(10);\n    EXPECT_EQ(farray.size(), 10);\n    for (auto f : farray) {\n      EXPECT_EQ(f, 0.0f);\n    }\n    farray.resize<true>(1);\n    EXPECT_EQ(farray.size(), 1);\n    EXPECT_EQ(farray[0], 0.0f);\n    farray.resize<true>(5, 3.14f);\n    EXPECT_EQ(farray.size(), 5);\n    EXPECT_EQ(farray[0], 0.0f);\n    for (size_t i = 1; i < farray.size(); i++) {\n      EXPECT_EQ(farray[i], 3.14f);\n    }\n\n    Vector<Matrix3> marray;\n    marray.resize<true>(10);\n    EXPECT_EQ(marray.size(), 10);\n    for (auto f : marray) {\n      EXPECT_EQ(f, Matrix3::identity);\n    }\n    marray.resize<true>(1);\n    EXPECT_EQ(marray.size(), 1);\n    EXPECT_EQ(marray[0], Matrix3::identity);\n    marray.resize<true>(5, Matrix3::zero);\n    EXPECT_EQ(marray.size(), 5);\n    EXPECT_EQ(marray[0], Matrix3::identity);\n    for (size_t i = 1; i < marray.size(); i++) {\n      EXPECT_EQ(marray[i], Matrix3::zero);\n    }\n\n    Vector<int> array;\n    EXPECT_FALSE(array.resize<false>(0));\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(array.capacity(), 0);\n    EXPECT_FALSE(array.resize<true>(0, 5));\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(array.capacity(), 0);\n\n    EXPECT_TRUE(array.resize<true>(50, 5));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 50);\n    for (int i : array) {\n      EXPECT_EQ(i, 5);\n    }\n\n    EXPECT_TRUE(array.resize<true>(100, 6));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 100);\n    for (size_t i = 0; i < 50; i++) {\n      EXPECT_EQ(array[i], 5);\n    }\n    for (size_t i = 50; i < 100; i++) {\n      EXPECT_EQ(array[i], 6);\n    }\n\n    EXPECT_FALSE(array.resize<false>(10));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 10);\n    for (size_t i = 0; i < 10; i++) {\n      EXPECT_EQ(array[i], 5);\n    }\n\n    array.clear_and_shrink();\n    EXPECT_TRUE(array.empty());\n    array.resize<true>(5, 5);\n    EXPECT_EQ(to_s(array), \"55555\");\n  }\n\n  {\n    // Algorithm\n    int buffer[5] = {12, 11, 15, 14, 10};\n    Vector<int> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    std::sort(array.begin(), array.end(), [](int& a, int& b) { return a < b; });\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"1011121415\");\n\n    Vector<int> array2(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(array2.begin(), array2.end(),\n                  [](int& a, int& b) { return a < b; });\n    CheckVector(array2);\n    EXPECT_EQ(to_s(array2), \"1011121415\");\n\n    Vector<int> array3(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(array3.begin() + 1, array3.end(),\n                  [](int& a, int& b) { return a < b; });\n    CheckVector(array3);\n    EXPECT_EQ(to_s(array3), \"1210111415\");\n\n    Vector<int> array4(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(array4.begin() + 1, array4.end() - 1,\n                  [](int& a, int& b) { return a < b; });\n    CheckVector(array4);\n    EXPECT_EQ(to_s(array4), \"1211141510\");\n  }\n\n  {\n    // Fill and append\n    int buffer[5] = {10, 11, 12, 13, 14};\n    int buffer2[5] = {20, 21, 22, 23, 24};\n    Vector<int> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    array.fill(buffer2, sizeof(buffer2));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"2021222324\");\n\n    // fill buffer again but from index 3\n    array.fill(buffer, sizeof(buffer), 3);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 8);\n    EXPECT_EQ(to_s(array), \"2021221011121314\");\n\n    array.fill(nullptr, sizeof(int) * 2, 1);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"2000\");\n\n    array.append(buffer2, sizeof(int) * 3);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"2000202122\");\n\n    Vector<int> array2(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    array.append(array2);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"20002021221011121314\");\n\n    Vector<int> array3;\n    array3.append(nullptr, 0);\n    EXPECT_TRUE(array3.empty());\n    array3.append(buffer, 0);\n    EXPECT_TRUE(array3.empty());\n    array3.append(buffer2, sizeof(int) * 3);\n    CheckVector(array3);\n    EXPECT_EQ(to_s(array3), \"202122\");\n    array3.append(nullptr, 0);\n    CheckVector(array3);\n    EXPECT_EQ(to_s(array3), \"202122\");\n    array3.append(buffer, 0);\n    CheckVector(array3);\n    EXPECT_EQ(to_s(array3), \"202122\");\n  }\n\n  {\n    // swap\n    int buffer[5] = {10, 11, 12, 13, 14};\n    int buffer2[5] = {20, 21, 22, 23, 24};\n    Vector<int> array1(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    Vector<int> array2(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    array1.swap(array2);\n    EXPECT_EQ(to_s(array1), \"2021222324\");\n    CheckVector(array1);\n    EXPECT_EQ(to_s(array2), \"1011121314\");\n    CheckVector(array2);\n  }\n\n  {\n    // Templateless methods.\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<int> array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n\n    VectorTemplateless_0::PushBackBatch(&array, sizeof(int), buffer, 5);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"10111213141011121314\");\n  }\n}\n\nTEST(Vector, Nontrivial) {\n  class NontrivialInt {\n   public:\n    NontrivialInt(int i = -1) {\n      value_ = std::make_shared<std::string>(std::to_string(i));\n    }\n\n    operator int() const { return std::stoi(*value_); }\n    bool operator==(const NontrivialInt& other) {\n      return (int)(*this) == (int)other;\n    }\n    NontrivialInt& operator+=(int value) {\n      value_ = std::make_shared<std::string>(\n          std::to_string(std::stoi(*value_) + value));\n      return *this;\n    }\n\n   private:\n    std::shared_ptr<std::string> value_;\n  };\n\n  auto to_nt_int_array = [](size_t count,\n                            int buffer[]) -> Vector<NontrivialInt> {\n    Vector<NontrivialInt> result;\n    for (size_t i = 0; i < count; i++) {\n      result.emplace_back(buffer[i]);\n    }\n    return result;\n  };\n\n  static_assert(!Vector<NontrivialInt>::is_trivial);\n  static_assert(!Vector<NontrivialInt>::is_trivially_destructible);\n  static_assert(!Vector<NontrivialInt>::is_trivially_destructible_after_move);\n  static_assert(!Vector<NontrivialInt>::is_trivially_relocatable);\n  static_assert(!Vector<std::pair<int, NontrivialInt>>::is_trivial);\n  static_assert(\n      !Vector<std::pair<int, NontrivialInt>>::is_trivially_destructible);\n  static_assert(\n      !Vector<\n          std::pair<int, NontrivialInt>>::is_trivially_destructible_after_move);\n  static_assert(\n      !Vector<std::pair<int, NontrivialInt>>::is_trivially_relocatable);\n\n  auto to_s = [](const Vector<NontrivialInt>& array) -> std::string {\n    std::string result;\n    for (auto& i : array) {\n      result += std::to_string((int)i);\n    }\n    return result;\n  };\n\n  NontrivialInt ni10000(10000);\n  NontrivialInt ni10001(10001);\n  NontrivialInt ni10002(10002);\n  NontrivialInt ni10003(10003);\n\n  {\n    Vector<NontrivialInt> array;\n    EXPECT_EQ(array.size(), 0);\n    EXPECT_TRUE(array.empty());\n    CheckVector(array);\n  }\n\n  {\n    Vector<NontrivialInt> array(5);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_FALSE(array.empty());\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], -1);\n      EXPECT_EQ(array.at(i), -1);\n    }\n    EXPECT_EQ(array.front(), -1);\n    EXPECT_EQ(array.back(), -1);\n    CheckVector(array);\n  }\n\n  {\n    Vector<Matrix3> array({});\n    EXPECT_TRUE(array.empty());\n  }\n\n  {\n    // Construct from initializer list or iterators\n    Vector<NontrivialInt> array({ni10000, ni10001, ni10002, ni10003});\n    EXPECT_EQ(array.size(), 4);\n    EXPECT_EQ(array[0], ni10000);\n    EXPECT_EQ(array[1], ni10001);\n    EXPECT_EQ(array[2], ni10002);\n    EXPECT_EQ(array[3], ni10003);\n    CheckVector(array);\n\n    Vector<NontrivialInt> array2(5);\n    EXPECT_EQ(array2.size(), 5);\n    CheckVector(array2);\n    for (size_t i = 0; i < array2.size(); i++) {\n      EXPECT_EQ(array2[i], -1);\n    }\n    array2 = {ni10000, ni10001, ni10002, ni10003};\n    CheckVector(array2);\n    EXPECT_EQ(array2.size(), 4);\n    EXPECT_EQ(array2[0], ni10000);\n    EXPECT_EQ(array2[1], ni10001);\n    EXPECT_EQ(array2[2], ni10002);\n    EXPECT_EQ(array2[3], ni10003);\n\n    Vector<NontrivialInt> array3(array2);\n    CheckVector(array3);\n    EXPECT_EQ(array3.size(), 4);\n    EXPECT_EQ(array3[0], ni10000);\n    EXPECT_EQ(array3[1], ni10001);\n    EXPECT_EQ(array3[2], ni10002);\n    EXPECT_EQ(array3[3], ni10003);\n\n    {\n      int buffer[5] = {10, 11, 12, 13, 14};\n      Vector<NontrivialInt> array =\n          to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n      Vector<NontrivialInt> array2(array.begin(), array.end());\n      CheckVector(array2);\n      EXPECT_EQ(to_s(array2), \"1011121314\");\n      Vector<NontrivialInt> array3(array.begin() + 1, array.end());\n      CheckVector(array3);\n      EXPECT_EQ(to_s(array3), \"11121314\");\n      Vector<NontrivialInt> array4(array.begin() + 1, array.end() - 1);\n      CheckVector(array4);\n      EXPECT_EQ(to_s(array4), \"111213\");\n\n      Vector<NontrivialInt> array5(array.begin() + 2, array.end() - 2);\n      CheckVector(array5);\n      EXPECT_EQ(to_s(array5), \"12\");\n      Vector<NontrivialInt> array6(array.begin() + 3, array.end() - 2);\n      CheckVector(array6);\n      EXPECT_TRUE(array6.empty());\n    }\n  }\n\n  {\n    // Move\n    int buffer[5] = {10, 11, 12, 13, 14};\n    int buffer2[10] = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n\n    Vector<NontrivialInt> array2(std::move(array));\n    CheckVector(array2);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(array2.size(), 5);\n    for (size_t i = 0; i < array2.size(); i++) {\n      EXPECT_EQ(array2[i], buffer[i]);\n    }\n\n    Vector<NontrivialInt> array3 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    CheckVector(array3);\n    EXPECT_EQ(array3.size(), 10);\n    array = std::move(array3);\n    CheckVector(array);\n    EXPECT_TRUE(array3.empty());\n    EXPECT_EQ(array.size(), 10);\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], buffer2[i]);\n    }\n  }\n\n  {\n    // Basic push and pop\n    Vector<NontrivialInt> array;\n    for (int i = 1; i <= 100; i++) {\n      EXPECT_EQ(array.push_back(i), i);\n      CheckVector(array);\n    }\n    int sum = 0;\n    for (size_t i = 0; i < array.size(); i++) {\n      sum += array[i];\n    }\n    EXPECT_EQ(sum, 5050);\n\n    int buffer[5] = {10, 11, 12, 13, 14};\n    for (int i = 0; i < 5; i++) {\n      array.push_back(buffer[i]);\n    }\n    EXPECT_EQ(array.size(), 105);\n    sum = 0;\n    for (size_t i = 0; i < array.size(); i++) {\n      sum += array[i];\n    }\n    EXPECT_EQ(sum, 5050 + 10 + 11 + 12 + 13 + 14);\n\n    for (int i = 0; i < 5; i++) {\n      array.pop_back();\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 100);\n    sum = 0;\n    for (int i : array) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    EXPECT_EQ(array.emplace_back(999), 999);\n\n    array.grow() = 9999;\n    EXPECT_EQ(array.size(), 102);\n    EXPECT_EQ(array.back(), 9999);\n\n    array.grow(200);\n    EXPECT_EQ(array.size(), 200);\n    EXPECT_EQ(array.back(), -1);\n  }\n\n  {\n    // Iterators\n    std::string output;\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (auto it = array.begin(); it != array.end(); it++) {\n      output += std::to_string(*it);\n    }\n    for (auto it = array.cbegin(); it != array.cend(); it++) {\n      output += std::to_string(*it);\n    }\n    for (int i : array) {\n      output += std::to_string(i);\n    }\n    EXPECT_EQ(output, \"101112131410111213141011121314\");\n\n    output = \"\";\n    for (auto it = array.rbegin(); it != array.rend(); it++) {\n      output += std::to_string(*it);\n    }\n    for (auto it = array.crbegin(); it != array.crend(); it++) {\n      output += std::to_string(*it);\n    }\n    EXPECT_EQ(output, \"14131211101413121110\");\n\n    // Writable iterator\n    output = \"\";\n    for (auto& i : array) {\n      i += 1;\n    }\n    for (auto it = array.begin(); it != array.end(); it++) {\n      *it += 1;\n    }\n    for (int i : array) {\n      output += std::to_string(i);\n    }\n    EXPECT_EQ(output, \"1213141516\");\n  }\n\n  {\n    // Erase and insert\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (int i = 0; i < 5; i++) {\n      auto it = array.erase(array.begin());\n      CheckVector(array);\n      EXPECT_EQ(it, array.begin());\n      if (i == 2) {\n        EXPECT_EQ(to_s(array), \"1314\");\n      }\n    }\n\n    EXPECT_TRUE(array.empty());\n    for (int i = 4; i >= 0; i--) {\n      array.insert(array.begin(), i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    auto it = array.erase(array.begin() + 1, array.begin() + 3);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"034\");\n    EXPECT_EQ(*it, 3);\n\n    it = array.erase(array.end() - 1);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"03\");\n    EXPECT_EQ(it, array.end());\n\n    it = array.erase(array.begin(), array.end());\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(it, array.end());\n\n    array.insert(array.begin(), 50);\n    CheckVector(array);\n    array.insert(array.end(), 51);\n    CheckVector(array);\n    array.insert(array.end(), 52);\n    CheckVector(array);\n    array.insert(array.begin() + 1, 49);\n    CheckVector(array);\n    array.insert(array.begin(), 48);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"4850495152\");\n\n    Vector<NontrivialInt> array2;\n    for (int i = 1; i <= 100; i++) {\n      array2.insert(array2.begin() + i - 1, i);\n    }\n    CheckVector(array2);\n    int sum = 0;\n    for (int i : array2) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    Vector<NontrivialInt> array3;\n    for (int i = 1; i <= 100; i++) {\n      array3.insert(array3.begin(), i);\n    }\n    CheckVector(array3);\n    sum = 0;\n    for (int i : array3) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n  }\n\n  {\n    // Erase and emplace\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (int i = 0; i < 5; i++) {\n      auto it = array.erase(array.begin());\n      CheckVector(array);\n      EXPECT_EQ(it, array.begin());\n      if (i == 2) {\n        EXPECT_EQ(to_s(array), \"1314\");\n      }\n    }\n\n    EXPECT_TRUE(array.empty());\n    for (int i = 4; i >= 0; i--) {\n      array.emplace(array.begin(), i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    auto it = array.erase(array.begin() + 1, array.begin() + 3);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"034\");\n    EXPECT_EQ(*it, 3);\n\n    it = array.erase(array.end() - 1);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"03\");\n    EXPECT_EQ(it, array.end());\n\n    it = array.erase(array.begin(), array.end());\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(it, array.end());\n\n    array.emplace(array.begin(), 50);\n    CheckVector(array);\n    array.emplace(array.end(), 51);\n    CheckVector(array);\n    array.emplace(array.end(), 52);\n    CheckVector(array);\n    array.emplace(array.begin() + 1, 49);\n    CheckVector(array);\n    array.emplace(array.begin(), 48);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"4850495152\");\n\n    Vector<NontrivialInt> array2;\n    for (int i = 1; i <= 100; i++) {\n      array2.emplace(array2.begin() + i - 1, i);\n    }\n    CheckVector(array2);\n    int sum = 0;\n    for (int i : array2) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    Vector<NontrivialInt> array3;\n    for (int i = 1; i <= 100; i++) {\n      array3.emplace(array3.begin(), i);\n    }\n    CheckVector(array3);\n    sum = 0;\n    for (int i : array3) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n  }\n\n  {\n    // Reserve\n    Vector<NontrivialInt> array;\n    EXPECT_TRUE(array.reserve(100));\n    CheckVector(array);\n    auto data_p = array.data();\n    for (int i = 1; i <= 100; i++) {\n      array.emplace_back(i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(data_p, array.data());\n  }\n\n  {\n    // Resize\n    Vector<NontrivialInt> array;\n    EXPECT_TRUE(array.resize(50, 5));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 50);\n    for (int i : array) {\n      EXPECT_EQ(i, 5);\n    }\n\n    EXPECT_TRUE(array.resize(100, 6));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 100);\n    for (size_t i = 0; i < 50; i++) {\n      EXPECT_EQ(array[i], 5);\n    }\n    for (size_t i = 50; i < 100; i++) {\n      EXPECT_EQ(array[i], 6);\n    }\n    EXPECT_FALSE(array.resize(10));\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"5555555555\");\n    EXPECT_FALSE(array.resize(0));\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n\n    array.push_back(1);\n    EXPECT_EQ(to_s(array), \"1\");\n    array.clear_and_shrink();\n    EXPECT_TRUE(array.empty());\n    array.resize(5, 5);\n    EXPECT_EQ(to_s(array), \"55555\");\n  }\n\n  {\n    // Algorithm\n    int buffer[5] = {12, 11, 15, 14, 10};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    std::sort(\n        array.begin(), array.end(),\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"1011121415\");\n\n    Vector<NontrivialInt> array2 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(\n        array2.begin(), array2.end(),\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array2);\n    EXPECT_EQ(to_s(array2), \"1011121415\");\n\n    Vector<NontrivialInt> array3 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(\n        array3.begin() + 1, array3.end(),\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array3);\n    EXPECT_EQ(to_s(array3), \"1210111415\");\n\n    Vector<NontrivialInt> array4 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(\n        array4.begin() + 1, array4.end() - 1,\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array4);\n    EXPECT_EQ(to_s(array4), \"1211141510\");\n  }\n\n  {\n    // swap\n    int buffer[5] = {12, 11, 15, 14, 10};\n    int buffer2[5] = {22, 21, 25, 24, 20};\n    Vector<NontrivialInt> array1 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    Vector<NontrivialInt> array2 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    array1.swap(array2);\n    EXPECT_EQ(to_s(array1), \"2221252420\");\n    CheckVector(array1);\n    EXPECT_EQ(to_s(array2), \"1211151410\");\n    CheckVector(array2);\n  }\n}\n\nTEST(Vector, NontrivialHintOfTriviallyDestructibleAfterMove) {\n  class NontrivialInt {\n   public:\n    using TriviallyDestructibleAfterMove = bool;\n\n    NontrivialInt(int i = -1) { value_ = new std::string(std::to_string(i)); }\n\n    ~NontrivialInt() {\n      if (value_) {\n        delete value_;\n      }\n    }\n\n    NontrivialInt(const NontrivialInt& other) {\n      value_ = new std::string(std::to_string((int)other));\n    }\n\n    NontrivialInt(NontrivialInt&& other) : value_(other.value_) {\n      other.value_ = nullptr;\n    }\n\n    NontrivialInt& operator=(const NontrivialInt& other) {\n      if (this == &other) {\n        return *this;\n      }\n      if (value_) {\n        delete value_;\n      }\n      value_ = new std::string(std::to_string((int)other));\n      return *this;\n    }\n\n    NontrivialInt& operator=(NontrivialInt&& other) {\n      if (this == &other) {\n        return *this;\n      }\n      if (value_) {\n        delete value_;\n      }\n      value_ = other.value_;\n      other.value_ = nullptr;\n      return *this;\n    }\n\n    operator int() const { return value_ ? std::stoi(*value_) : -1; }\n    bool operator==(const NontrivialInt& other) {\n      return (int)(*this) == (int)other;\n    }\n    NontrivialInt& operator+=(int value) {\n      int old_value_ = (int)(*this);\n      if (value_) {\n        delete value_;\n      }\n      value_ = new std::string(std::to_string(old_value_ + value));\n      return *this;\n    }\n\n   private:\n    std::string* value_;\n  };\n\n  static_assert(!Vector<NontrivialInt>::is_trivial);\n  static_assert(!Vector<NontrivialInt>::is_trivially_destructible);\n  static_assert(Vector<NontrivialInt>::is_trivially_destructible_after_move);\n  static_assert(\n      Vector<\n          std::pair<int, NontrivialInt>>::is_trivially_destructible_after_move);\n  static_assert(\n      Vector<\n          std::pair<NontrivialInt, int>>::is_trivially_destructible_after_move);\n  static_assert(!Vector<NontrivialInt>::is_trivially_relocatable);\n\n  auto to_nt_int_array = [](size_t count,\n                            int buffer[]) -> Vector<NontrivialInt> {\n    Vector<NontrivialInt> result;\n    for (size_t i = 0; i < count; i++) {\n      result.emplace_back(buffer[i]);\n    }\n    return result;\n  };\n\n  auto to_s = [](const Vector<NontrivialInt>& array) -> std::string {\n    std::string result;\n    for (auto& i : array) {\n      result += std::to_string((int)i);\n    }\n    return result;\n  };\n\n  NontrivialInt ni10000(10000);\n  NontrivialInt ni10001(10001);\n  NontrivialInt ni10002(10002);\n  NontrivialInt ni10003(10003);\n\n  {\n    Vector<NontrivialInt> array;\n    EXPECT_EQ(array.size(), 0);\n    EXPECT_TRUE(array.empty());\n    CheckVector(array);\n  }\n\n  {\n    Vector<NontrivialInt> array(5);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_FALSE(array.empty());\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], -1);\n      EXPECT_EQ(array.at(i), -1);\n    }\n    EXPECT_EQ(array.front(), -1);\n    EXPECT_EQ(array.back(), -1);\n    CheckVector(array);\n  }\n\n  {\n    Vector<Matrix3> array({});\n    EXPECT_TRUE(array.empty());\n  }\n\n  {\n    // Construct from initializer list or iterators\n    Vector<NontrivialInt> array({ni10000, ni10001, ni10002, ni10003});\n    EXPECT_EQ(array.size(), 4);\n    EXPECT_EQ(array[0], ni10000);\n    EXPECT_EQ(array[1], ni10001);\n    EXPECT_EQ(array[2], ni10002);\n    EXPECT_EQ(array[3], ni10003);\n    CheckVector(array);\n\n    Vector<NontrivialInt> array2(5);\n    EXPECT_EQ(array2.size(), 5);\n    CheckVector(array2);\n    for (size_t i = 0; i < array2.size(); i++) {\n      EXPECT_EQ(array2[i], -1);\n    }\n    array2 = {ni10000, ni10001, ni10002, ni10003};\n    CheckVector(array2);\n    EXPECT_EQ(array2.size(), 4);\n    EXPECT_EQ(array2[0], ni10000);\n    EXPECT_EQ(array2[1], ni10001);\n    EXPECT_EQ(array2[2], ni10002);\n    EXPECT_EQ(array2[3], ni10003);\n\n    Vector<NontrivialInt> array3(array2);\n    CheckVector(array3);\n    EXPECT_EQ(array3.size(), 4);\n    EXPECT_EQ(array3[0], ni10000);\n    EXPECT_EQ(array3[1], ni10001);\n    EXPECT_EQ(array3[2], ni10002);\n    EXPECT_EQ(array3[3], ni10003);\n\n    {\n      int buffer[5] = {10, 11, 12, 13, 14};\n      Vector<NontrivialInt> array =\n          to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n      Vector<NontrivialInt> array2(array.begin(), array.end());\n      CheckVector(array2);\n      EXPECT_EQ(to_s(array2), \"1011121314\");\n      Vector<NontrivialInt> array3(array.begin() + 1, array.end());\n      CheckVector(array3);\n      EXPECT_EQ(to_s(array3), \"11121314\");\n      Vector<NontrivialInt> array4(array.begin() + 1, array.end() - 1);\n      CheckVector(array4);\n      EXPECT_EQ(to_s(array4), \"111213\");\n\n      Vector<NontrivialInt> array5(array.begin() + 2, array.end() - 2);\n      CheckVector(array5);\n      EXPECT_EQ(to_s(array5), \"12\");\n      Vector<NontrivialInt> array6(array.begin() + 3, array.end() - 2);\n      CheckVector(array6);\n      EXPECT_TRUE(array6.empty());\n    }\n  }\n\n  {\n    // Move\n    int buffer[5] = {10, 11, 12, 13, 14};\n    int buffer2[10] = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n\n    Vector<NontrivialInt> array2(std::move(array));\n    CheckVector(array2);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(array2.size(), 5);\n    for (size_t i = 0; i < array2.size(); i++) {\n      EXPECT_EQ(array2[i], buffer[i]);\n    }\n\n    Vector<NontrivialInt> array3 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    CheckVector(array3);\n    EXPECT_EQ(array3.size(), 10);\n    array = std::move(array3);\n    CheckVector(array);\n    EXPECT_TRUE(array3.empty());\n    EXPECT_EQ(array.size(), 10);\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], buffer2[i]);\n    }\n  }\n\n  {\n    // Basic push and pop\n    Vector<NontrivialInt> array;\n    for (int i = 1; i <= 100; i++) {\n      EXPECT_EQ(array.push_back(i), i);\n      CheckVector(array);\n    }\n    int sum = 0;\n    for (size_t i = 0; i < array.size(); i++) {\n      sum += array[i];\n    }\n    EXPECT_EQ(sum, 5050);\n\n    int buffer[5] = {10, 11, 12, 13, 14};\n    for (int i = 0; i < 5; i++) {\n      array.push_back(buffer[i]);\n    }\n    EXPECT_EQ(array.size(), 105);\n    sum = 0;\n    for (size_t i = 0; i < array.size(); i++) {\n      sum += array[i];\n    }\n    EXPECT_EQ(sum, 5050 + 10 + 11 + 12 + 13 + 14);\n\n    for (int i = 0; i < 5; i++) {\n      array.pop_back();\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 100);\n    sum = 0;\n    for (int i : array) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    EXPECT_EQ(array.emplace_back(999), 999);\n\n    array.grow() = 9999;\n    EXPECT_EQ(array.size(), 102);\n    EXPECT_EQ(array.back(), 9999);\n\n    array.grow(200);\n    EXPECT_EQ(array.size(), 200);\n    EXPECT_EQ(array.back(), -1);\n  }\n\n  {\n    // Iterators\n    std::string output;\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (auto it = array.begin(); it != array.end(); it++) {\n      output += std::to_string(*it);\n    }\n    for (auto it = array.cbegin(); it != array.cend(); it++) {\n      output += std::to_string(*it);\n    }\n    for (int i : array) {\n      output += std::to_string(i);\n    }\n    EXPECT_EQ(output, \"101112131410111213141011121314\");\n\n    output = \"\";\n    for (auto it = array.rbegin(); it != array.rend(); it++) {\n      output += std::to_string(*it);\n    }\n    for (auto it = array.crbegin(); it != array.crend(); it++) {\n      output += std::to_string(*it);\n    }\n    EXPECT_EQ(output, \"14131211101413121110\");\n\n    // Writable iterator\n    output = \"\";\n    for (auto& i : array) {\n      i += 1;\n    }\n    for (auto it = array.begin(); it != array.end(); it++) {\n      *it += 1;\n    }\n    for (int i : array) {\n      output += std::to_string(i);\n    }\n    EXPECT_EQ(output, \"1213141516\");\n  }\n\n  {\n    // Erase and insert\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (int i = 0; i < 5; i++) {\n      auto it = array.erase(array.begin());\n      CheckVector(array);\n      EXPECT_EQ(it, array.begin());\n      if (i == 2) {\n        EXPECT_EQ(to_s(array), \"1314\");\n      }\n    }\n\n    EXPECT_TRUE(array.empty());\n    for (int i = 4; i >= 0; i--) {\n      array.insert(array.begin(), i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    auto it = array.erase(array.begin() + 1, array.begin() + 3);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"034\");\n    EXPECT_EQ(*it, 3);\n\n    it = array.erase(array.end() - 1);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"03\");\n    EXPECT_EQ(it, array.end());\n\n    it = array.erase(array.begin(), array.end());\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(it, array.end());\n\n    array.insert(array.begin(), 50);\n    CheckVector(array);\n    array.insert(array.end(), 51);\n    CheckVector(array);\n    array.insert(array.end(), 52);\n    CheckVector(array);\n    array.insert(array.begin() + 1, 49);\n    CheckVector(array);\n    array.insert(array.begin(), 48);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"4850495152\");\n\n    Vector<NontrivialInt> array2;\n    for (int i = 1; i <= 100; i++) {\n      array2.insert(array2.begin() + i - 1, i);\n    }\n    CheckVector(array2);\n    int sum = 0;\n    for (int i : array2) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    Vector<NontrivialInt> array3;\n    for (int i = 1; i <= 100; i++) {\n      array3.insert(array3.begin(), i);\n    }\n    CheckVector(array3);\n    sum = 0;\n    for (int i : array3) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n  }\n\n  {\n    // Erase and emplace\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (int i = 0; i < 5; i++) {\n      auto it = array.erase(array.begin());\n      CheckVector(array);\n      EXPECT_EQ(it, array.begin());\n      if (i == 2) {\n        EXPECT_EQ(to_s(array), \"1314\");\n      }\n    }\n\n    EXPECT_TRUE(array.empty());\n    for (int i = 4; i >= 0; i--) {\n      array.emplace(array.begin(), i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    auto it = array.erase(array.begin() + 1, array.begin() + 3);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"034\");\n    EXPECT_EQ(*it, 3);\n\n    it = array.erase(array.end() - 1);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"03\");\n    EXPECT_EQ(it, array.end());\n\n    it = array.erase(array.begin(), array.end());\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(it, array.end());\n\n    array.emplace(array.begin(), 50);\n    CheckVector(array);\n    array.emplace(array.end(), 51);\n    CheckVector(array);\n    array.emplace(array.end(), 52);\n    CheckVector(array);\n    array.emplace(array.begin() + 1, 49);\n    CheckVector(array);\n    array.emplace(array.begin(), 48);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"4850495152\");\n\n    Vector<NontrivialInt> array2;\n    for (int i = 1; i <= 100; i++) {\n      array2.emplace(array2.begin() + i - 1, i);\n    }\n    CheckVector(array2);\n    int sum = 0;\n    for (int i : array2) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    Vector<NontrivialInt> array3;\n    for (int i = 1; i <= 100; i++) {\n      array3.emplace(array3.begin(), i);\n    }\n    CheckVector(array3);\n    sum = 0;\n    for (int i : array3) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n  }\n\n  {\n    // Reserve\n    Vector<NontrivialInt> array;\n    EXPECT_TRUE(array.reserve(100));\n    CheckVector(array);\n    auto data_p = array.data();\n    for (int i = 1; i <= 100; i++) {\n      array.emplace_back(i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(data_p, array.data());\n  }\n\n  {\n    // Resize\n    Vector<NontrivialInt> array;\n    EXPECT_TRUE(array.resize(50, 5));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 50);\n    for (int i : array) {\n      EXPECT_EQ(i, 5);\n    }\n\n    EXPECT_TRUE(array.resize(100, 6));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 100);\n    for (size_t i = 0; i < 50; i++) {\n      EXPECT_EQ(array[i], 5);\n    }\n    for (size_t i = 50; i < 100; i++) {\n      EXPECT_EQ(array[i], 6);\n    }\n    EXPECT_FALSE(array.resize(10));\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"5555555555\");\n    EXPECT_FALSE(array.resize(0));\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n\n    array.push_back(1);\n    EXPECT_EQ(to_s(array), \"1\");\n    array.clear_and_shrink();\n    EXPECT_TRUE(array.empty());\n    array.resize(5, 5);\n    EXPECT_EQ(to_s(array), \"55555\");\n  }\n\n  {\n    // Algorithm\n    int buffer[5] = {12, 11, 15, 14, 10};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    std::sort(\n        array.begin(), array.end(),\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"1011121415\");\n\n    Vector<NontrivialInt> array2 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(\n        array2.begin(), array2.end(),\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array2);\n    EXPECT_EQ(to_s(array2), \"1011121415\");\n\n    Vector<NontrivialInt> array3 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(\n        array3.begin() + 1, array3.end(),\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array3);\n    EXPECT_EQ(to_s(array3), \"1210111415\");\n\n    Vector<NontrivialInt> array4 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(\n        array4.begin() + 1, array4.end() - 1,\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array4);\n    EXPECT_EQ(to_s(array4), \"1211141510\");\n  }\n\n  {\n    // swap\n    int buffer[5] = {12, 11, 15, 14, 10};\n    int buffer2[5] = {22, 21, 25, 24, 20};\n    Vector<NontrivialInt> array1 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    Vector<NontrivialInt> array2 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    array1.swap(array2);\n    EXPECT_EQ(to_s(array1), \"2221252420\");\n    CheckVector(array1);\n    EXPECT_EQ(to_s(array2), \"1211151410\");\n    CheckVector(array2);\n  }\n}\n\nTEST(Vector, NontrivialHintOfTriviallyRelocatable) {\n  class NontrivialInt {\n   public:\n    using TriviallyRelocatable = bool;\n\n    NontrivialInt(int i = -1) { value_ = new std::string(std::to_string(i)); }\n\n    ~NontrivialInt() {\n      if (value_) {\n        delete value_;\n      }\n    }\n\n    NontrivialInt(const NontrivialInt& other) {\n      value_ = new std::string(std::to_string((int)other));\n    }\n\n    NontrivialInt(NontrivialInt&& other) : value_(other.value_) {\n      other.value_ = nullptr;\n    }\n\n    NontrivialInt& operator=(const NontrivialInt& other) {\n      if (this == &other) {\n        return *this;\n      }\n      if (value_) {\n        delete value_;\n      }\n      value_ = new std::string(std::to_string((int)other));\n      return *this;\n    }\n\n    NontrivialInt& operator=(NontrivialInt&& other) {\n      if (this == &other) {\n        return *this;\n      }\n      if (value_) {\n        delete value_;\n      }\n      value_ = other.value_;\n      other.value_ = nullptr;\n      return *this;\n    }\n\n    operator int() const { return value_ ? std::stoi(*value_) : -1; }\n    bool operator==(const NontrivialInt& other) {\n      return (int)(*this) == (int)other;\n    }\n    NontrivialInt& operator+=(int value) {\n      int old_value_ = (int)(*this);\n      if (value_) {\n        delete value_;\n      }\n      value_ = new std::string(std::to_string(old_value_ + value));\n      return *this;\n    }\n\n   private:\n    std::string* value_;\n  };\n\n  static_assert(!Vector<NontrivialInt>::is_trivial);\n  static_assert(!Vector<NontrivialInt>::is_trivially_destructible);\n  static_assert(Vector<NontrivialInt>::is_trivially_destructible_after_move);\n  static_assert(\n      Vector<\n          std::pair<int, NontrivialInt>>::is_trivially_destructible_after_move);\n  static_assert(\n      Vector<\n          std::pair<NontrivialInt, int>>::is_trivially_destructible_after_move);\n  static_assert(Vector<NontrivialInt>::is_trivially_relocatable);\n  static_assert(\n      Vector<std::pair<int, NontrivialInt>>::is_trivially_relocatable);\n  static_assert(\n      Vector<std::pair<NontrivialInt, int>>::is_trivially_relocatable);\n\n  auto to_nt_int_array = [](size_t count,\n                            int buffer[]) -> Vector<NontrivialInt> {\n    Vector<NontrivialInt> result;\n    for (size_t i = 0; i < count; i++) {\n      result.emplace_back(buffer[i]);\n    }\n    return result;\n  };\n\n  auto to_s = [](const Vector<NontrivialInt>& array) -> std::string {\n    std::string result;\n    for (auto& i : array) {\n      result += std::to_string((int)i);\n    }\n    return result;\n  };\n\n  NontrivialInt ni10000(10000);\n  NontrivialInt ni10001(10001);\n  NontrivialInt ni10002(10002);\n  NontrivialInt ni10003(10003);\n\n  {\n    Vector<NontrivialInt> array;\n    EXPECT_EQ(array.size(), 0);\n    EXPECT_TRUE(array.empty());\n    CheckVector(array);\n  }\n\n  {\n    Vector<NontrivialInt> array(5);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_FALSE(array.empty());\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], -1);\n      EXPECT_EQ(array.at(i), -1);\n    }\n    EXPECT_EQ(array.front(), -1);\n    EXPECT_EQ(array.back(), -1);\n    CheckVector(array);\n  }\n\n  {\n    Vector<Matrix3> array({});\n    EXPECT_TRUE(array.empty());\n  }\n\n  {\n    // Construct from initializer list or iterators\n    Vector<NontrivialInt> array({ni10000, ni10001, ni10002, ni10003});\n    EXPECT_EQ(array.size(), 4);\n    EXPECT_EQ(array[0], ni10000);\n    EXPECT_EQ(array[1], ni10001);\n    EXPECT_EQ(array[2], ni10002);\n    EXPECT_EQ(array[3], ni10003);\n    CheckVector(array);\n\n    Vector<NontrivialInt> array2(5);\n    EXPECT_EQ(array2.size(), 5);\n    CheckVector(array2);\n    for (size_t i = 0; i < array2.size(); i++) {\n      EXPECT_EQ(array2[i], -1);\n    }\n    array2 = {ni10000, ni10001, ni10002, ni10003};\n    CheckVector(array2);\n    EXPECT_EQ(array2.size(), 4);\n    EXPECT_EQ(array2[0], ni10000);\n    EXPECT_EQ(array2[1], ni10001);\n    EXPECT_EQ(array2[2], ni10002);\n    EXPECT_EQ(array2[3], ni10003);\n\n    Vector<NontrivialInt> array3(array2);\n    CheckVector(array3);\n    EXPECT_EQ(array3.size(), 4);\n    EXPECT_EQ(array3[0], ni10000);\n    EXPECT_EQ(array3[1], ni10001);\n    EXPECT_EQ(array3[2], ni10002);\n    EXPECT_EQ(array3[3], ni10003);\n\n    {\n      int buffer[5] = {10, 11, 12, 13, 14};\n      Vector<NontrivialInt> array =\n          to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n      Vector<NontrivialInt> array2(array.begin(), array.end());\n      CheckVector(array2);\n      EXPECT_EQ(to_s(array2), \"1011121314\");\n      Vector<NontrivialInt> array3(array.begin() + 1, array.end());\n      CheckVector(array3);\n      EXPECT_EQ(to_s(array3), \"11121314\");\n      Vector<NontrivialInt> array4(array.begin() + 1, array.end() - 1);\n      CheckVector(array4);\n      EXPECT_EQ(to_s(array4), \"111213\");\n\n      Vector<NontrivialInt> array5(array.begin() + 2, array.end() - 2);\n      CheckVector(array5);\n      EXPECT_EQ(to_s(array5), \"12\");\n      Vector<NontrivialInt> array6(array.begin() + 3, array.end() - 2);\n      CheckVector(array6);\n      EXPECT_TRUE(array6.empty());\n    }\n  }\n\n  {\n    // Move\n    int buffer[5] = {10, 11, 12, 13, 14};\n    int buffer2[10] = {100, 101, 102, 103, 104, 105, 106, 107, 108, 109};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n\n    Vector<NontrivialInt> array2(std::move(array));\n    CheckVector(array2);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(array2.size(), 5);\n    for (size_t i = 0; i < array2.size(); i++) {\n      EXPECT_EQ(array2[i], buffer[i]);\n    }\n\n    Vector<NontrivialInt> array3 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    CheckVector(array3);\n    EXPECT_EQ(array3.size(), 10);\n    array = std::move(array3);\n    CheckVector(array);\n    EXPECT_TRUE(array3.empty());\n    EXPECT_EQ(array.size(), 10);\n    for (size_t i = 0; i < array.size(); i++) {\n      EXPECT_EQ(array[i], buffer2[i]);\n    }\n  }\n\n  {\n    // Basic push and pop\n    Vector<NontrivialInt> array;\n    for (int i = 1; i <= 100; i++) {\n      EXPECT_EQ(array.push_back(i), i);\n      CheckVector(array);\n    }\n    int sum = 0;\n    for (size_t i = 0; i < array.size(); i++) {\n      sum += array[i];\n    }\n    EXPECT_EQ(sum, 5050);\n\n    int buffer[5] = {10, 11, 12, 13, 14};\n    for (int i = 0; i < 5; i++) {\n      array.push_back(buffer[i]);\n    }\n    EXPECT_EQ(array.size(), 105);\n    sum = 0;\n    for (size_t i = 0; i < array.size(); i++) {\n      sum += array[i];\n    }\n    EXPECT_EQ(sum, 5050 + 10 + 11 + 12 + 13 + 14);\n\n    for (int i = 0; i < 5; i++) {\n      array.pop_back();\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 100);\n    sum = 0;\n    for (int i : array) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    EXPECT_EQ(array.emplace_back(999), 999);\n\n    array.grow() = 9999;\n    EXPECT_EQ(array.size(), 102);\n    EXPECT_EQ(array.back(), 9999);\n\n    array.grow(200);\n    EXPECT_EQ(array.size(), 200);\n    EXPECT_EQ(array.back(), -1);\n  }\n\n  {\n    // Iterators\n    std::string output;\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (auto it = array.begin(); it != array.end(); it++) {\n      output += std::to_string(*it);\n    }\n    for (auto it = array.cbegin(); it != array.cend(); it++) {\n      output += std::to_string(*it);\n    }\n    for (int i : array) {\n      output += std::to_string(i);\n    }\n    EXPECT_EQ(output, \"101112131410111213141011121314\");\n\n    output = \"\";\n    for (auto it = array.rbegin(); it != array.rend(); it++) {\n      output += std::to_string(*it);\n    }\n    for (auto it = array.crbegin(); it != array.crend(); it++) {\n      output += std::to_string(*it);\n    }\n    EXPECT_EQ(output, \"14131211101413121110\");\n\n    // Writable iterator\n    output = \"\";\n    for (auto& i : array) {\n      i += 1;\n    }\n    for (auto it = array.begin(); it != array.end(); it++) {\n      *it += 1;\n    }\n    for (int i : array) {\n      output += std::to_string(i);\n    }\n    EXPECT_EQ(output, \"1213141516\");\n  }\n\n  {\n    // Erase and insert\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (int i = 0; i < 5; i++) {\n      auto it = array.erase(array.begin());\n      CheckVector(array);\n      EXPECT_EQ(it, array.begin());\n      if (i == 2) {\n        EXPECT_EQ(to_s(array), \"1314\");\n      }\n    }\n\n    EXPECT_TRUE(array.empty());\n    for (int i = 4; i >= 0; i--) {\n      array.insert(array.begin(), i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    auto it = array.erase(array.begin() + 1, array.begin() + 3);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"034\");\n    EXPECT_EQ(*it, 3);\n\n    it = array.erase(array.end() - 1);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"03\");\n    EXPECT_EQ(it, array.end());\n\n    it = array.erase(array.begin(), array.end());\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(it, array.end());\n\n    array.insert(array.begin(), 50);\n    CheckVector(array);\n    array.insert(array.end(), 51);\n    CheckVector(array);\n    array.insert(array.end(), 52);\n    CheckVector(array);\n    array.insert(array.begin() + 1, 49);\n    CheckVector(array);\n    array.insert(array.begin(), 48);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"4850495152\");\n\n    Vector<NontrivialInt> array2;\n    for (int i = 1; i <= 100; i++) {\n      array2.insert(array2.begin() + i - 1, i);\n    }\n    CheckVector(array2);\n    int sum = 0;\n    for (int i : array2) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    Vector<NontrivialInt> array3;\n    for (int i = 1; i <= 100; i++) {\n      array3.insert(array3.begin(), i);\n    }\n    CheckVector(array3);\n    sum = 0;\n    for (int i : array3) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n  }\n\n  {\n    // Erase and emplace\n    int buffer[5] = {10, 11, 12, 13, 14};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    for (int i = 0; i < 5; i++) {\n      auto it = array.erase(array.begin());\n      CheckVector(array);\n      EXPECT_EQ(it, array.begin());\n      if (i == 2) {\n        EXPECT_EQ(to_s(array), \"1314\");\n      }\n    }\n\n    EXPECT_TRUE(array.empty());\n    for (int i = 4; i >= 0; i--) {\n      array.emplace(array.begin(), i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"01234\");\n\n    auto it = array.erase(array.begin() + 1, array.begin() + 3);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"034\");\n    EXPECT_EQ(*it, 3);\n\n    it = array.erase(array.end() - 1);\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"03\");\n    EXPECT_EQ(it, array.end());\n\n    it = array.erase(array.begin(), array.end());\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n    EXPECT_EQ(it, array.end());\n\n    array.emplace(array.begin(), 50);\n    CheckVector(array);\n    array.emplace(array.end(), 51);\n    CheckVector(array);\n    array.emplace(array.end(), 52);\n    CheckVector(array);\n    array.emplace(array.begin() + 1, 49);\n    CheckVector(array);\n    array.emplace(array.begin(), 48);\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 5);\n    EXPECT_EQ(to_s(array), \"4850495152\");\n\n    Vector<NontrivialInt> array2;\n    for (int i = 1; i <= 100; i++) {\n      array2.emplace(array2.begin() + i - 1, i);\n    }\n    CheckVector(array2);\n    int sum = 0;\n    for (int i : array2) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n\n    Vector<NontrivialInt> array3;\n    for (int i = 1; i <= 100; i++) {\n      array3.emplace(array3.begin(), i);\n    }\n    CheckVector(array3);\n    sum = 0;\n    for (int i : array3) {\n      sum += i;\n    }\n    EXPECT_EQ(sum, 5050);\n  }\n\n  {\n    // Reserve\n    Vector<NontrivialInt> array;\n    EXPECT_TRUE(array.reserve(100));\n    CheckVector(array);\n    auto data_p = array.data();\n    for (int i = 1; i <= 100; i++) {\n      array.emplace_back(i);\n      CheckVector(array);\n    }\n    EXPECT_EQ(data_p, array.data());\n  }\n\n  {\n    // Resize\n    Vector<NontrivialInt> array;\n    EXPECT_TRUE(array.resize(50, 5));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 50);\n    for (int i : array) {\n      EXPECT_EQ(i, 5);\n    }\n\n    EXPECT_TRUE(array.resize(100, 6));\n    CheckVector(array);\n    EXPECT_EQ(array.size(), 100);\n    for (size_t i = 0; i < 50; i++) {\n      EXPECT_EQ(array[i], 5);\n    }\n    for (size_t i = 50; i < 100; i++) {\n      EXPECT_EQ(array[i], 6);\n    }\n    EXPECT_FALSE(array.resize(10));\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"5555555555\");\n    EXPECT_FALSE(array.resize(0));\n    CheckVector(array);\n    EXPECT_TRUE(array.empty());\n\n    array.push_back(1);\n    EXPECT_EQ(to_s(array), \"1\");\n    array.clear_and_shrink();\n    EXPECT_TRUE(array.empty());\n    array.resize(5, 5);\n    EXPECT_EQ(to_s(array), \"55555\");\n  }\n\n  {\n    // Algorithm\n    int buffer[5] = {12, 11, 15, 14, 10};\n    Vector<NontrivialInt> array =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    std::sort(\n        array.begin(), array.end(),\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array);\n    EXPECT_EQ(to_s(array), \"1011121415\");\n\n    Vector<NontrivialInt> array2 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(\n        array2.begin(), array2.end(),\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array2);\n    EXPECT_EQ(to_s(array2), \"1011121415\");\n\n    Vector<NontrivialInt> array3 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(\n        array3.begin() + 1, array3.end(),\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array3);\n    EXPECT_EQ(to_s(array3), \"1210111415\");\n\n    Vector<NontrivialInt> array4 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    InsertionSort(\n        array4.begin() + 1, array4.end() - 1,\n        [](NontrivialInt& a, NontrivialInt& b) { return (int)a < (int)b; });\n    CheckVector(array4);\n    EXPECT_EQ(to_s(array4), \"1211141510\");\n  }\n\n  {\n    // swap\n    int buffer[5] = {12, 11, 15, 14, 10};\n    int buffer2[5] = {22, 21, 25, 24, 20};\n    Vector<NontrivialInt> array1 =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    Vector<NontrivialInt> array2 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    array1.swap(array2);\n    EXPECT_EQ(to_s(array1), \"2221252420\");\n    CheckVector(array1);\n    EXPECT_EQ(to_s(array2), \"1211151410\");\n    CheckVector(array2);\n  }\n}\n\nTEST(Vector, Nontrivial2) {\n  static int LiveInstance = 0;\n  class NontrivialInt {\n   public:\n    NontrivialInt(int i = -1) {\n      ++LiveInstance;\n      value_ = std::to_string(i);\n    }\n    NontrivialInt(const NontrivialInt& other) : value_(other.value_) {\n      ++LiveInstance;\n    }\n    NontrivialInt(NontrivialInt&& other) : value_(std::move(other.value_)) {\n      ++LiveInstance;\n    }\n    NontrivialInt& operator=(const NontrivialInt& other) {\n      if (this != &other) {\n        value_ = other.value_;\n      }\n      return *this;\n    }\n    NontrivialInt& operator=(NontrivialInt&& other) {\n      if (this != &other) {\n        value_ = std::move(other.value_);\n      }\n      return *this;\n    }\n    ~NontrivialInt() { --LiveInstance; }\n\n    operator int() const { return std::stoi(value_); }\n\n   private:\n    std::string value_;\n  };\n\n  {\n    Vector<NontrivialInt> array;\n    for (int i = 0; i < 100; i++) {\n      array.push_back(NontrivialInt(i));\n    }\n    EXPECT_EQ(LiveInstance, 100);\n    array.resize(200, NontrivialInt(9999));\n    EXPECT_EQ(LiveInstance, 200);\n    for (int i = 100; i < 200; i++) {\n      EXPECT_EQ(array[i], 9999);\n    }\n    array.erase(array.begin() + 5, array.begin() + 10);\n    EXPECT_EQ(LiveInstance, 195);\n    array.pop_back();\n    EXPECT_EQ(LiveInstance, 194);\n    EXPECT_FALSE(array.resize(100));\n    EXPECT_EQ(LiveInstance, 100);\n    array.clear();\n    EXPECT_EQ(LiveInstance, 0);\n  }\n\n  {\n    int i = 1;\n    std::string output;\n    Vector<Vector<NontrivialInt>> arrays;\n    for (int level = 0; level < 100; level++) {\n      arrays.push_back(Vector<NontrivialInt>());\n      for (int num = 0; num < level + 1; num++) {\n        output += std::to_string(i);\n        arrays[level].push_back(NontrivialInt(i++));\n      }\n    }\n    EXPECT_EQ(LiveInstance, (1 + 100) * 100 / 2);\n    std::string output2;\n    for (int level = 0; level < 100; level++) {\n      for (int num = 0; num < level + 1; num++) {\n        output2 += std::to_string((arrays[level][num]).operator int());\n      }\n    }\n    EXPECT_EQ(output, output2);\n  }\n  EXPECT_EQ(LiveInstance, 0);\n\n  // Put in Inline array and test deallocation.\n  {\n    InlineVector<NontrivialInt, 10> array{1, 2, 3, 4, 5};\n    EXPECT_EQ(LiveInstance, 5);\n  }\n  EXPECT_EQ(LiveInstance, 0);\n}\n\nTEST(Vector, PairElement) {\n  static_assert(Vector<int>::is_trivial, \"\");\n  static_assert(Vector<std::pair<float, float>>::is_trivial, \"\");\n  static_assert(Vector<std::pair<std::pair<long, long>, int>>::is_trivial, \"\");\n  static_assert(\n      Vector<\n          std::pair<std::pair<long, long>, std::pair<char, char>>>::is_trivial,\n      \"\");\n  static_assert(!Vector<std::string>::is_trivial, \"\");\n  static_assert(!Vector<std::pair<std::string, int>>::is_trivial, \"\");\n  static_assert(!Vector<std::pair<std::pair<long, long>,\n                                  std::pair<std::string, char>>>::is_trivial,\n                \"\");\n\n  {\n    Vector<std::pair<int, int>> array;\n    static_assert(decltype(array)::is_trivial, \"\");\n\n    array.resize<true>(100, {50, 50});\n    for (size_t i = 0; i < 100; i++) {\n      EXPECT_EQ(array[i].first, 50);\n      EXPECT_EQ(array[i].second, 50);\n    }\n\n    for (size_t i = 0; i < 100; i++) {\n      array[i].first = i;\n      array[i].second = i;\n    }\n\n    array.erase(array.begin(), array.begin() + 50);\n    EXPECT_EQ(array.size(), 50);\n    for (size_t i = 0; i < 50; i++) {\n      EXPECT_EQ(array[i].first, i + 50);\n      EXPECT_EQ(array[i].second, i + 50);\n    }\n  }\n\n  {\n    Vector<std::pair<std::pair<int, int>, std::pair<int, int>>> array;\n    static_assert(decltype(array)::is_trivial, \"\");\n\n    std::pair<std::pair<int, int>, std::pair<int, int>> buffer[5] = {\n        {{0, 0}, {0, 0}},\n        {{1, 1}, {1, 1}},\n        {{2, 2}, {2, 2}},\n        {{3, 3}, {3, 3}},\n        {{4, 4}, {4, 4}}};\n    VectorTemplateless_0::PushBackBatch(\n        &array, sizeof(std::pair<std::pair<int, int>, std::pair<int, int>>),\n        buffer, 5);\n    EXPECT_EQ(array.size(), 5);\n    for (int i = 0; i < 5; i++) {\n      EXPECT_EQ(array[i].first.first, i);\n      EXPECT_EQ(array[i].first.second, i);\n      EXPECT_EQ(array[i].second.first, i);\n      EXPECT_EQ(array[i].second.second, i);\n    }\n  }\n}\n\nTEST(Vector, DestructOrder) {\n  // To be consistent with std::vector. Elements are destructed from back.\n  static std::string sDestructionOrder;\n\n  class NontrivialInt {\n   public:\n    NontrivialInt(int i = -1) {\n      value_ = std::make_shared<std::string>(std::to_string(i));\n    }\n    ~NontrivialInt() {\n      if (value_) {\n        sDestructionOrder += *value_;\n      }\n    }\n    operator int() const { return std::stoi(*value_); }\n\n   private:\n    std::shared_ptr<std::string> value_;\n  };\n\n  {\n    Vector<NontrivialInt> v;\n    for (int i = 0; i < 5; i++) {\n      v.emplace_back(i);\n    }\n    sDestructionOrder = \"\";\n  }\n  EXPECT_TRUE(sDestructionOrder == \"43210\");\n\n  {\n    Vector<NontrivialInt> v;\n    for (int i = 0; i < 5; i++) {\n      v.emplace_back(i);\n    }\n    sDestructionOrder = \"\";\n    v.clear();\n  }\n  EXPECT_TRUE(sDestructionOrder == \"43210\");\n\n  {\n    Vector<NontrivialInt> v;\n    for (int i = 0; i < 5; i++) {\n      v.emplace_back(i);\n    }\n    sDestructionOrder = \"\";\n    v.erase(v.begin() + 1, v.begin() + 3);\n    EXPECT_TRUE(sDestructionOrder == \"43\");\n  }\n}\n\nTEST(Vector, Slice) {\n  Vector<uint32_t> array;\n  for (int i = 0; i < 100; i++) {\n    array.push_back(i);\n  }\n  EXPECT_EQ(array.size(), 100);\n\n  EXPECT_TRUE(VectorTemplateless_0::Erase(&array, 4, 0, 0));\n  EXPECT_EQ(array.size(), 100);\n\n  EXPECT_TRUE(VectorTemplateless_0::Erase(&array, 4, 99, 0));\n  EXPECT_EQ(array.size(), 100);\n  for (int i = 0; i < 100; i++) {\n    // Data not changed.\n    EXPECT_EQ(array[i], i);\n  }\n\n  // DeleteCount == 0 is allowed but index 100 is out of range, so return false.\n  EXPECT_FALSE(VectorTemplateless_0::Erase(&array, 4, 100, 0));\n  EXPECT_EQ(array.size(), 100);\n\n  EXPECT_TRUE(VectorTemplateless_0::Erase(&array, 4, 0, 50));\n\n  EXPECT_EQ(array.size(), 50);\n  EXPECT_EQ(array[0], 50);\n\n  EXPECT_TRUE(VectorTemplateless_0::Erase(&array, 4, 10, 10));\n\n  EXPECT_EQ(array.size(), 40);\n  EXPECT_EQ(array[0], 50);\n  EXPECT_EQ(array[10], 70);\n\n  EXPECT_FALSE(VectorTemplateless_0::Erase(&array, 4, 10, 100));\n  EXPECT_EQ(array.size(), 40);\n\n  EXPECT_TRUE(VectorTemplateless_0::Erase(&array, 4, 0, 40));\n  EXPECT_EQ(array.size(), 0);\n}\n\nTEST(Vector, Compare) {\n  class NontrivialInt {\n   public:\n    NontrivialInt(int i = -1) {\n      value_ = std::make_shared<std::string>(std::to_string(i));\n    }\n\n    operator int() const { return std::stoi(*value_); }\n    bool operator==(const NontrivialInt& other) {\n      return (int)(*this) == (int)other;\n    }\n    bool operator<(const NontrivialInt& other) {\n      return (int)(*this) < (int)other;\n    }\n    NontrivialInt& operator+=(int value) {\n      value_ = std::make_shared<std::string>(\n          std::to_string(std::stoi(*value_) + value));\n      return *this;\n    }\n\n   private:\n    std::shared_ptr<std::string> value_;\n  };\n\n  auto to_nt_int_array = [](size_t count,\n                            int buffer[]) -> Vector<NontrivialInt> {\n    Vector<NontrivialInt> result;\n    for (size_t i = 0; i < count; i++) {\n      result.emplace_back(buffer[i]);\n    }\n    return result;\n  };\n\n  {\n    int buffer1[] = {1, 2, 3, 4, 5};\n    int buffer2[] = {5, 4, 3, 2, 1};\n    Vector<NontrivialInt> vec1 =\n        to_nt_int_array(sizeof(buffer1) / sizeof(buffer1[0]), buffer1);\n    Vector<NontrivialInt> vec2 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    EXPECT_FALSE(vec1 == vec2);\n    EXPECT_TRUE(vec1 != vec2);\n    std::reverse(vec1.begin(), vec1.end());\n    EXPECT_TRUE(vec1 == vec2);\n    EXPECT_FALSE(vec1 != vec2);\n  }\n\n  {\n    int buffer1[] = {1, 2, 3, 4, 5};\n    int buffer2[] = {1, 2, 2, 4, 5};\n    Vector<NontrivialInt> vec1 =\n        to_nt_int_array(sizeof(buffer1) / sizeof(buffer1[0]), buffer1);\n    Vector<NontrivialInt> vec2 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    EXPECT_TRUE(vec1 > vec2);\n  }\n\n  {\n    int buffer1[] = {1, 2, 3, 4, 5};\n    int buffer2[] = {1, 2, 3, 4};\n    Vector<NontrivialInt> vec1 =\n        to_nt_int_array(sizeof(buffer1) / sizeof(buffer1[0]), buffer1);\n    Vector<NontrivialInt> vec2 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    EXPECT_TRUE(vec1 > vec2);\n  }\n\n  {\n    int buffer1[] = {1};\n    Vector<NontrivialInt> vec1 =\n        to_nt_int_array(sizeof(buffer1) / sizeof(buffer1[0]), buffer1);\n    Vector<NontrivialInt> vec2;\n    EXPECT_TRUE(vec1 > vec2);\n  }\n}\n\nTEST(Vector, StackContainer) {\n  std::stack<int, InlineVector<int, 5>> stack;\n  stack.push(1);\n  stack.push(2);\n  EXPECT_EQ(stack.size(), 2);\n  EXPECT_EQ(stack.top(), 2);\n  stack.pop();\n  EXPECT_EQ(stack.size(), 1);\n  EXPECT_EQ(stack.top(), 1);\n  stack.push(3);\n  stack.push(4);\n  EXPECT_EQ(stack.size(), 3);\n  EXPECT_EQ(stack.top(), 4);\n  std::string content;\n  while (!stack.empty()) {\n    content += std::to_string(stack.top());\n    stack.pop();\n  }\n  EXPECT_TRUE(stack.empty());\n  EXPECT_EQ(content, \"431\");\n}\n\nTEST(Vector, Algorithms) {\n  auto to_s = [](const Vector<int>& array) -> std::string {\n    std::string result;\n    for (int i : array) {\n      result += std::to_string(i);\n    }\n    return result;\n  };\n\n  {\n    Vector<int> vec;\n    vec.resize<false>(10);\n    std::iota(vec.begin(), vec.end(), 0);\n    EXPECT_EQ(to_s(vec), \"0123456789\");\n  }\n\n  {\n    Vector<int> vec = {1, 2, 3, 4, 5};\n    std::string cat;\n    std::for_each(vec.begin(), vec.end(),\n                  [&](int i) { cat += std::to_string(i); });\n    EXPECT_TRUE(cat == \"12345\");\n\n    std::for_each(vec.rbegin(), vec.rend(),\n                  [&](int i) { cat += std::to_string(i); });\n    EXPECT_TRUE(cat == \"1234554321\");\n  }\n\n  {\n    Vector<int> vec = {1, 2, 3, 4, 5, 4, 3, 2, 1};\n    std::sort(vec.begin(), vec.end());\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 1));\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 2));\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 3));\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 4));\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 5));\n    EXPECT_FALSE(std::binary_search(vec.begin(), vec.end(), 6));\n  }\n\n  {\n    Vector<int> vec = {5, 7, 4, 2, 8, 6, 1, 9, 0, 3};\n    std::sort(vec.begin(), vec.end());\n    EXPECT_EQ(to_s(vec), \"0123456789\");\n    std::sort(vec.begin(), vec.end(), std::greater<int>());\n    EXPECT_EQ(to_s(vec), \"9876543210\");\n  }\n\n  {\n    Vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};\n    std::reverse(vec.begin(), vec.end());\n    EXPECT_EQ(to_s(vec), \"987654321\");\n  }\n\n  {\n    Vector<int> vec1 = {1, 2, 3, 4, 5};\n    Vector<int> vec2 = {100, 200};\n    std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2));\n    EXPECT_EQ(to_s(vec2), \"10020012345\");\n  }\n\n  {\n    Vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8};\n    vec.erase(std::remove_if(vec.begin(), vec.end(),\n                             [](auto i) { return i % 2 == 0; }),\n              vec.end());\n    EXPECT_EQ(to_s(vec), \"1357\");\n  }\n\n  {\n    Vector<int> vec = {1, 2, 3, 3, 9, 10, 3, 4, 5, 8};\n    auto it = std::remove(vec.begin(), vec.end(), 15);\n    EXPECT_EQ(to_s(vec), \"12339103458\");\n    vec.erase(it, vec.end());  // Nothing erased\n    EXPECT_EQ(to_s(vec), \"12339103458\");\n  }\n\n  {\n    Vector<int> vec = {1, 1, 1, 1, 1};\n    auto it = std::remove(vec.begin(), vec.end(), 1);\n    vec.erase(it, vec.end());\n    EXPECT_TRUE(vec.empty());\n  }\n\n  {\n    Vector<int> vec = {1, 2, 3, 3, 9, 10, 3, 4, 5, 8};\n    auto it = std::remove(vec.begin(), vec.begin() + 5, 3);\n    vec.erase(it, vec.begin() + 5);\n    EXPECT_EQ(to_s(vec), \"129103458\");\n  }\n\n  {\n    Vector<int> vec = {1, 2, 3, 3, 9, 10, 3, 4, 5, 8};\n    auto it = std::remove(vec.begin(), vec.end(), 3);\n    EXPECT_EQ(to_s(vec), \"12910458458\");\n    auto d = std::distance(vec.begin(), it);\n    EXPECT_EQ(d, 7);\n    vec.erase(it, vec.end());\n    EXPECT_EQ(to_s(vec), \"12910458\");\n  }\n}\n\nTEST(Vector, AlgorithmsNontrivial) {\n  class NontrivialInt {\n   public:\n    NontrivialInt(int i = -1) {\n      value_ = std::make_shared<std::string>(std::to_string(i));\n    }\n\n    operator int() const { return value_ ? std::stoi(*value_) : -1; }\n    bool operator==(const NontrivialInt& other) {\n      return (int)(*this) == (int)other;\n    }\n    bool operator==(int other) { return (int)(*this) == other; }\n    NontrivialInt& operator+=(int value) {\n      value_ = std::make_shared<std::string>(\n          std::to_string(std::stoi(*value_) + value));\n      return *this;\n    }\n\n   private:\n    std::shared_ptr<std::string> value_;\n  };\n\n  auto to_nt_int_array = [](size_t count,\n                            int buffer[]) -> Vector<NontrivialInt> {\n    Vector<NontrivialInt> result;\n    for (size_t i = 0; i < count; i++) {\n      result.emplace_back(buffer[i]);\n    }\n    return result;\n  };\n\n  auto to_s = [](const Vector<NontrivialInt>& array) -> std::string {\n    std::string result;\n    for (auto& i : array) {\n      result += std::to_string((int)i);\n    }\n    return result;\n  };\n\n  {\n    int buffer[] = {1, 2, 3, 4, 5};\n    Vector<NontrivialInt> vec =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    std::string cat;\n    std::for_each(vec.begin(), vec.end(), [&](const NontrivialInt& i) {\n      cat += std::to_string((int)i);\n    });\n    EXPECT_TRUE(cat == \"12345\");\n\n    std::for_each(vec.rbegin(), vec.rend(), [&](const NontrivialInt& i) {\n      cat += std::to_string((int)i);\n    });\n    EXPECT_TRUE(cat == \"1234554321\");\n  }\n\n  {\n    int buffer[] = {1, 2, 3, 4, 5, 4, 3, 2, 1};\n    Vector<NontrivialInt> vec =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    std::sort(vec.begin(), vec.end());\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 1));\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 2));\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 3));\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 4));\n    EXPECT_TRUE(std::binary_search(vec.begin(), vec.end(), 5));\n    EXPECT_FALSE(std::binary_search(vec.begin(), vec.end(), 6));\n  }\n\n  {\n    int buffer[] = {5, 7, 4, 2, 8, 6, 1, 9, 0, 3};\n    Vector<NontrivialInt> vec =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    std::sort(vec.begin(), vec.end());\n    EXPECT_EQ(to_s(vec), \"0123456789\");\n    std::sort(vec.begin(), vec.end(), std::greater<int>());\n    EXPECT_EQ(to_s(vec), \"9876543210\");\n  }\n\n  {\n    int buffer[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};\n    Vector<NontrivialInt> vec =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    std::reverse(vec.begin(), vec.end());\n    EXPECT_EQ(to_s(vec), \"987654321\");\n  }\n\n  {\n    int buffer1[] = {1, 2, 3, 4, 5};\n    int buffer2[] = {100, 200};\n    Vector<NontrivialInt> vec1 =\n        to_nt_int_array(sizeof(buffer1) / sizeof(buffer1[0]), buffer1);\n    Vector<NontrivialInt> vec2 =\n        to_nt_int_array(sizeof(buffer2) / sizeof(buffer2[0]), buffer2);\n    std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2));\n    EXPECT_EQ(to_s(vec2), \"10020012345\");\n  }\n\n  {\n    int buffer[] = {1, 2, 3, 4, 5, 6, 7, 8};\n    Vector<NontrivialInt> vec =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    vec.erase(\n        std::remove_if(vec.begin(), vec.end(),\n                       [](const NontrivialInt& i) { return (int)i % 2 == 0; }),\n        vec.end());\n    EXPECT_EQ(to_s(vec), \"1357\");\n  }\n\n  {\n    int buffer[] = {1, 2, 3, 3, 9, 10, 3, 4, 5, 8};\n    Vector<NontrivialInt> vec =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    auto it = std::remove(vec.begin(), vec.end(), 15);\n    EXPECT_EQ(to_s(vec), \"12339103458\");\n    vec.erase(it, vec.end());  // Nothing erased\n    EXPECT_EQ(to_s(vec), \"12339103458\");\n  }\n\n  {\n    int buffer[] = {1, 1, 1, 1, 1};\n    Vector<NontrivialInt> vec =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    auto it = std::remove(vec.begin(), vec.end(), 1);\n    vec.erase(it, vec.end());\n    EXPECT_TRUE(vec.empty());\n  }\n\n  {\n    int buffer[] = {1, 2, 3, 3, 9, 10, 3, 4, 5, 8};\n    Vector<NontrivialInt> vec =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    auto it = std::remove(vec.begin(), vec.begin() + 5, 3);\n    vec.erase(it, vec.begin() + 5);\n    EXPECT_EQ(to_s(vec), \"129103458\");\n  }\n\n  {\n    int buffer[] = {1, 2, 3, 3, 9, 10, 3, 4, 5, 8};\n    Vector<NontrivialInt> vec =\n        to_nt_int_array(sizeof(buffer) / sizeof(buffer[0]), buffer);\n    auto it = std::remove(vec.begin(), vec.end(), 3);\n    EXPECT_EQ(to_s(vec), \"12910458-1-1-1\");  // moved to tail and is invalid.\n    auto d = std::distance(vec.begin(), it);\n    EXPECT_EQ(d, 7);\n    vec.erase(it, vec.end());\n    EXPECT_EQ(to_s(vec), \"12910458\");\n  }\n}\n\nTEST(Vector, ArrayEmplace) {\n  Vector<std::string> vec;\n  vec.emplace_back(\"abc\", 2);\n  vec.emplace_back(\"123\", 2);\n  EXPECT_EQ(*vec.emplace(vec.begin(), \"xyz\", 2), \"xy\");\n  EXPECT_EQ(*vec.emplace(vec.begin() + 1, \"opq\", 2), \"op\");\n  EXPECT_EQ(*vec.emplace(vec.end(), \"lmn\", 2), \"lm\");\n  EXPECT_EQ(vec.size(), 5);\n  EXPECT_TRUE(vec[0] == \"xy\");\n  EXPECT_TRUE(vec[1] == \"op\");\n  EXPECT_TRUE(vec[2] == \"ab\");\n  EXPECT_TRUE(vec[3] == \"12\");\n  EXPECT_TRUE(vec[4] == \"lm\");\n\n  Vector<int> vec2;\n  vec2.emplace_back(9);\n  vec2.emplace_back(8);\n  EXPECT_EQ(*vec2.emplace(vec2.begin(), 7), 7);\n  EXPECT_EQ(*vec2.emplace(vec2.begin() + 1, 6), 6);\n  EXPECT_EQ(*vec2.emplace(vec2.end(), 5), 5);\n  EXPECT_EQ(vec2.size(), 5);\n  EXPECT_TRUE(vec2[0] == 7);\n  EXPECT_TRUE(vec2[1] == 6);\n  EXPECT_TRUE(vec2[2] == 9);\n  EXPECT_TRUE(vec2[3] == 8);\n  EXPECT_TRUE(vec2[4] == 5);\n}\n\nTEST(VectorIntTest, BasicOperations) {\n  Vector<int> v1;\n  ASSERT_TRUE(v1.empty());\n\n  Vector<int> v2{1, 2, 3};\n  ASSERT_EQ(v2.size(), 3);\n  EXPECT_EQ(v2.front(), 1);\n  EXPECT_EQ(v2.back(), 3);\n\n  Vector<int> copy(v2);\n  ASSERT_EQ(copy.size(), 3);\n\n  Vector<int> moved(std::move(copy));\n  ASSERT_EQ(moved.size(), 3);\n  EXPECT_TRUE(copy.empty());\n}\n\nTEST(VectorStringTest, BasicOperations) {\n  Vector<std::string> sv{\"Hello\", \"World\"};\n  ASSERT_EQ(sv.size(), 2);\n  EXPECT_EQ(sv[0].length(), 5);\n\n  std::string s = \"Test\";\n  sv.push_back(std::move(s));\n  EXPECT_TRUE(s.empty());\n  EXPECT_EQ(sv.back(), \"Test\");\n}\n\nTEST(VectorIntTest, ElementAccess) {\n  Vector<int> v{10, 20, 30};\n\n  v[1] = 99;\n  EXPECT_EQ(v.at(1), 99);\n\n  int* ptr = v.data();\n  EXPECT_EQ(ptr[0], 10);\n}\n\nTEST(VectorStringTest, ElementAccess) {\n  Vector<std::string> sv{\"A\", \"B\", \"C\"};\n\n  sv.back() += \"_suffix\";\n  EXPECT_EQ(sv[2], \"C_suffix\");\n}\n\nTEST(VectorIntTest, CapacityManagement) {\n  Vector<int> v;\n  v.shrink_to_fit();\n  EXPECT_EQ(v.capacity(), 0);\n\n  v.reserve(100);\n  ASSERT_GE(v.capacity(), 100);\n\n  v.resize<true>(5, 42);\n  ASSERT_EQ(v.size(), 5);\n  EXPECT_EQ(v[3], 42);\n\n  v.shrink_to_fit();\n  EXPECT_EQ(v.capacity(), 5);\n  EXPECT_EQ(v, (Vector<int>{42, 42, 42, 42, 42}));\n\n  InlineVector<int, 5> v2{1, 2, 3};\n  EXPECT_TRUE(v2.is_static_buffer());\n  EXPECT_EQ(v2.size(), 3);\n  EXPECT_EQ(v2.capacity(), 5);\n  v2.shrink_to_fit();\n  EXPECT_EQ(v2.capacity(), 5);\n  EXPECT_EQ(v2, (Vector<int>{1, 2, 3}));\n\n  v2.push_back(4);\n  v2.push_back(5);\n  v2.push_back(6);\n  EXPECT_FALSE(v2.is_static_buffer());\n  EXPECT_EQ(v2.size(), 6);\n  EXPECT_EQ(v2, (Vector<int>{1, 2, 3, 4, 5, 6}));\n  v2.pop_back();\n  EXPECT_EQ(v2.size(), 5);\n  v2.shrink_to_fit();\n  EXPECT_EQ(v2.capacity(), 5);\n  EXPECT_FALSE(\n      v2.is_static_buffer());  // Unable to use static buffer event if after\n                               // shrink_to_fit(), the buffer is fit.\n  EXPECT_EQ(v2, (Vector<int>{1, 2, 3, 4, 5}));\n\n  InlineVector<std::string, 5> v3;\n  v3.shrink_to_fit();\n  EXPECT_EQ(v.capacity(), 5);\n  v3.emplace_back(\"1\");\n  v3.emplace_back(\"2\");\n  v3.emplace_back(\"3\");\n  v3.shrink_to_fit();\n  EXPECT_EQ(v.capacity(), 5);\n  EXPECT_TRUE(v3.is_static_buffer());\n  v3.emplace_back(\"4\");\n  v3.emplace_back(\"5\");\n  v3.emplace_back(\"6\");\n  EXPECT_FALSE(v3.is_static_buffer());\n  v3.pop_back();\n  v3.shrink_to_fit();\n  EXPECT_EQ(v3.capacity(), 5);\n  EXPECT_EQ(v3.size(), 5);\n  EXPECT_FALSE(v3.is_static_buffer());\n  EXPECT_EQ(v3[0], \"1\");\n  EXPECT_EQ(v3[1], \"2\");\n  EXPECT_EQ(v3[2], \"3\");\n  EXPECT_EQ(v3[3], \"4\");\n  EXPECT_EQ(v3[4], \"5\");\n}\n\nTEST(VectorStringTest, CapacityManagement) {\n  Vector<std::string> sv(3, \"Init\");\n  sv.reserve(100);\n\n  sv.emplace_back(\"NewString\");\n  EXPECT_GT(sv.capacity(), 3);\n  EXPECT_EQ(sv.back().length(), 9);\n}\n\nTEST(VectorIntTest, InsertOperations) {\n  Vector<int> v{1, 3};\n\n  auto it = v.insert(v.begin() + 1, 2);\n  ASSERT_EQ(v.size(), 3);\n  EXPECT_EQ(*it, 2);\n  EXPECT_EQ(v, (Vector<int>{1, 2, 3}));\n\n  v.insert(v.end(), 4);\n  EXPECT_EQ(v.back(), 4);\n}\n\nTEST(VectorStringTest, InsertOperations) {\n  Vector<std::string> sv{\"Start\", \"End\"};\n\n  sv.insert(sv.begin(), \"Header\");\n  EXPECT_EQ(sv.front(), \"Header\");\n\n  sv.emplace(sv.begin() + 1, 3, 'X');  // 构造 \"XXX\"\n  EXPECT_EQ(sv[1], \"XXX\");\n}\n\nTEST(VectorIntTest, EdgeCases) {\n  Vector<int> v;\n\n  v.insert(v.end(), 42);\n  ASSERT_EQ(v.size(), 1);\n\n  v.reserve(2);\n  v = {1, 2};  // capacity=2\n  v.insert(v.begin(), 0);\n  EXPECT_GT(v.capacity(), 2);\n  EXPECT_EQ(v, (Vector<int>{0, 1, 2}));\n}\n\ntemplate <class SET>\nstatic void TestSet() {\n  auto to_s = [](SET& set) -> std::string {\n    std::string result;\n    for (auto& i : set) {\n      result += std::to_string(i);\n    }\n    return result;\n  };\n\n  SET set;\n  set.insert(1);\n  set.insert(5);\n  set.insert(3);\n  set.insert(7);\n  set.insert(6);\n  set.insert(2);\n  set.insert(4);\n  auto it = set.insert(8);\n  EXPECT_EQ(*it.first, 8);\n  EXPECT_TRUE(it.second);\n  EXPECT_FALSE(set.insert(3).second);\n  if (set.is_data_ordered()) {\n    EXPECT_EQ(to_s(set), \"12345678\");\n  } else {\n    EXPECT_EQ(to_s(set), \"15376248\");\n  }\n\n  EXPECT_EQ(set.size(), 8);\n\n  EXPECT_EQ(set.erase(5), 1);\n  set.erase(1);\n  EXPECT_EQ(set.size(), 6);\n  EXPECT_EQ(to_s(set), set.is_data_ordered() ? \"234678\" : \"376248\");\n\n  EXPECT_TRUE(set.contains(6));\n  EXPECT_FALSE(set.contains(1));\n  EXPECT_FALSE(set.contains(5));\n\n  auto find3 = set.find(3);\n  auto find1 = set.find(1);\n  EXPECT_EQ(*find3, 3);\n  EXPECT_TRUE(find1 == set.end());\n\n  EXPECT_EQ(to_s(set), set.is_data_ordered() ? \"234678\" : \"376248\");\n  EXPECT_EQ(*set.erase(find3), set.is_data_ordered() ? 4 : 7);\n  EXPECT_EQ(to_s(set), set.is_data_ordered() ? \"24678\" : \"76248\");\n\n  set.clear();\n  EXPECT_TRUE(set.empty());\n\n  // Check functionality after clear.\n  set.insert(1);\n  EXPECT_EQ(set.size(), 1);\n  EXPECT_TRUE(set.contains(1));\n  EXPECT_TRUE(set.find(1) != set.end());\n}\n\nTEST(Vector, OrderedFlatSet) {\n  TestSet<OrderedFlatSet<int>>();\n  TestSet<LinearFlatSet<int>>();\n}\n\nTEST(Vector, InlineOrderedFlatSet) {\n  TestSet<InlineOrderedFlatSet<int, 20>>();\n  TestSet<InlineLinearFlatSet<int, 20>>();\n}\n\ntemplate <class MAP>\nstatic void TestMap1() {\n  auto to_s = [](MAP& map) -> std::string {\n    std::string result;\n    for (auto& i : map) {\n      result += i.second;\n    }\n    return result;\n  };\n\n  MAP map;\n  EXPECT_TRUE(map.empty());\n\n  map.insert({1, \"1\"});\n  map.insert({5, \"5\"});\n  map.insert({3, \"3\"});\n  map.insert({7, \"7\"});\n  map.insert({6, \"6\"});\n  map.insert({2, \"2\"});\n  map.insert({4, \"4\"});\n  auto it = map.insert({8, \"8\"});\n  EXPECT_EQ(it.first->first, 8);\n  EXPECT_EQ(it.first->second, \"8\");\n  EXPECT_TRUE(it.second);\n  EXPECT_FALSE(map.insert({3, \"3\"}).second);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"12345678\" : \"15376248\");\n\n  EXPECT_EQ(map.size(), 8);\n\n  typename MAP::value_type pair{0, \"0\"};\n  map.insert(pair);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"012345678\" : \"153762480\");\n\n  EXPECT_EQ(map.erase(5), 1);\n  map.erase(1);\n  map.erase(1024);\n  EXPECT_EQ(map.size(), 7);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"0234678\" : \"3762480\");\n\n  EXPECT_TRUE(map.contains(0));\n  EXPECT_TRUE(map.contains(6));\n  EXPECT_FALSE(map.contains(1));\n  EXPECT_FALSE(map.contains(5));\n\n  auto find3 = map.find(3);\n  auto find1 = map.find(1);\n  EXPECT_TRUE(find1 == map.end());\n  EXPECT_EQ(find3->first, 3);\n  EXPECT_EQ(find3->second, \"3\");\n  find3->second = \"333\";\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"023334678\" : \"333762480\");\n\n  EXPECT_EQ(map.erase(find3)->second, map.is_data_ordered() ? \"4\" : \"7\");\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"024678\" : \"762480\");\n\n  EXPECT_EQ(map[1], \"\");\n  EXPECT_EQ(map.size(), 7);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"024678\" : \"762480\");\n\n  map[1] = \"1\";\n  map[5] = \"5\";\n  map[8] = \"888\";\n  EXPECT_EQ(map.size(), 8);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"0124567888\" : \"7624888015\");\n\n  {\n    auto result = map.insert_or_assign(5, \"555\");\n    auto result2 = map.insert_or_assign(9, \"9\");\n    EXPECT_EQ(result.first->first, 5);\n    EXPECT_EQ(result.first->second, \"555\");\n    EXPECT_FALSE(result.second);\n\n    EXPECT_EQ(result2.first->first, 9);\n    EXPECT_EQ(result2.first->second, \"9\");\n    EXPECT_TRUE(result2.second);\n  }\n  EXPECT_EQ(map.size(), 9);\n  EXPECT_EQ(to_s(map),\n            map.is_data_ordered() ? \"0124555678889\" : \"7624888015559\");\n\n  {\n    auto er = map.emplace(1, \"1\");\n    EXPECT_EQ(er.first->first, 1);\n    EXPECT_EQ(er.first->second, \"1\");\n    EXPECT_FALSE(er.second);\n    EXPECT_EQ(map.size(), 9);\n    EXPECT_EQ(to_s(map),\n              map.is_data_ordered() ? \"0124555678889\" : \"7624888015559\");\n  }\n\n  {\n    auto er = map.emplace(10, \"abcdef\", 3);\n    EXPECT_EQ(er.first->first, 10);\n    EXPECT_EQ(er.first->second, \"abc\");\n    EXPECT_TRUE(er.second);\n    EXPECT_EQ(map.size(), 10);\n    EXPECT_EQ(to_s(map),\n              map.is_data_ordered() ? \"0124555678889abc\" : \"7624888015559abc\");\n  }\n\n  map.clear();\n  EXPECT_TRUE(map.empty());\n\n  // Check functionality after clear.\n  map.insert({1, \"1\"});\n  EXPECT_EQ(map.size(), 1);\n  EXPECT_TRUE(map.contains(1));\n  EXPECT_TRUE(map.find(1) != map.end());\n}\n\ntemplate <class MAP>\nstatic void TestMap2() {\n  auto to_s = [](MAP& map) -> std::string {\n    std::string result;\n    for (auto& i : map) {\n      result += std::to_string(i.second);\n    }\n    return result;\n  };\n\n  MAP map;\n  EXPECT_TRUE(map.empty());\n\n  map.insert({\"1\", 1});\n  map.insert({\"5\", 5});\n  map.insert({\"3\", 3});\n  map.insert({\"7\", 7});\n  map.insert({\"6\", 6});\n  map.insert({\"2\", 2});\n  map.insert({\"4\", 4});\n  auto it = map.insert({\"8\", 8});\n  EXPECT_EQ(it.first->first, \"8\");\n  EXPECT_EQ(it.first->second, 8);\n  EXPECT_TRUE(it.second);\n  EXPECT_FALSE(map.insert({\"3\", 3}).second);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"12345678\" : \"15376248\");\n\n  EXPECT_EQ(map.size(), 8);\n\n  typename MAP::value_type pair{\"0\", 0};\n  map.insert(pair);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"012345678\" : \"153762480\");\n\n  EXPECT_EQ(map.erase(\"5\"), 1);\n  map.erase(\"1\");\n  map.erase(\"abc\");\n  EXPECT_EQ(map.size(), 7);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"0234678\" : \"3762480\");\n\n  EXPECT_TRUE(map.contains(\"0\"));\n  EXPECT_TRUE(map.contains(\"6\"));\n  EXPECT_FALSE(map.contains(\"1\"));\n  EXPECT_FALSE(map.contains(\"5\"));\n\n  auto find3 = map.find(\"3\");\n  auto find1 = map.find(\"1\");\n  EXPECT_TRUE(find1 == map.end());\n  EXPECT_EQ(find3->first, \"3\");\n  EXPECT_EQ(find3->second, 3);\n  find3->second = 333;\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"023334678\" : \"333762480\");\n\n  EXPECT_EQ(map.erase(find3)->second, map.is_data_ordered() ? 4 : 7);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"024678\" : \"762480\");\n\n  EXPECT_EQ(map[\"1\"], 0);  // key inserted\n  EXPECT_EQ(map.size(), 7);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"0024678\" : \"7624800\");\n\n  map[\"1\"] = 1;\n  map[\"5\"] = 5;\n  map[\"8\"] = 888;\n  EXPECT_EQ(map.size(), 8);\n  EXPECT_EQ(to_s(map), map.is_data_ordered() ? \"0124567888\" : \"7624888015\");\n\n  std::string key = \"a\";\n  map[std::move(key)] = 999;\n  EXPECT_EQ(to_s(map),\n            map.is_data_ordered() ? \"0124567888999\" : \"7624888015999\");\n  EXPECT_TRUE(key.empty());\n\n  map.clear();\n  EXPECT_TRUE(map.empty());\n\n  // Check functionality after clear.\n  map.insert({\"1\", 1});\n  EXPECT_EQ(map.size(), 1);\n  EXPECT_TRUE(map.contains(\"1\"));\n  EXPECT_TRUE(map.find(\"1\") != map.end());\n}\n\nTEST(Vector, OrderedFlatMap) {\n  TestMap1<OrderedFlatMap<int, std::string>>();\n  TestMap1<LinearFlatMap<int, std::string>>();\n  TestMap2<OrderedFlatMap<std::string, int>>();\n  TestMap2<LinearFlatMap<std::string, int>>();\n}\n\nTEST(Vector, InlineOrderedFlatMap) {\n  TestMap1<InlineOrderedFlatMap<int, std::string, 20>>();\n  TestMap1<InlineLinearFlatMap<int, std::string, 20>>();\n  TestMap2<InlineOrderedFlatMap<std::string, int, 20>>();\n  TestMap2<InlineLinearFlatMap<std::string, int, 20>>();\n}\n\nTEST(Vector, MapInsertOrAssign) {\n  InlineOrderedFlatMap<std::string, std::string, 5> map{\n      {\"3\", \"c\"}, {\"2\", \"b\"}, {\"1\", \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map.capacity(), 5);\n  EXPECT_EQ(map[\"1\"], \"a\");\n  EXPECT_EQ(map[\"2\"], \"b\");\n  EXPECT_EQ(map[\"3\"], \"c\");\n  EXPECT_EQ(map[\"4\"], \"\");\n\n  auto r = map.insert_or_assign(\"4\", \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[\"4\"], \"d\");\n\n  std::string s5 = \"5\";\n  std::string se = \"e\";\n  auto r2 = map.insert_or_assign(s5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[\"5\"], \"e\");\n  EXPECT_EQ(s5, \"5\");\n  EXPECT_TRUE(se.empty());\n\n  std::string s6 = \"6\";\n  std::string sf = \"f\";\n  auto r3 = map.insert_or_assign(std::move(s6), std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[\"6\"], \"f\");\n  EXPECT_TRUE(s6.empty());\n  EXPECT_TRUE(sf.empty());\n\n  std::string s7 = \"7\";\n  std::string sg = \"g\";\n  auto r4 = map.insert_or_assign(std::move(s7), sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[\"7\"], \"g\");\n  EXPECT_TRUE(s7.empty());\n  EXPECT_EQ(sg, \"g\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, MapEmplaceOrAssign) {\n  InlineOrderedFlatMap<std::string, std::string, 5> map{\n      {\"3\", \"c\"}, {\"2\", \"b\"}, {\"1\", \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[\"1\"], \"a\");\n  EXPECT_EQ(map[\"2\"], \"b\");\n  EXPECT_EQ(map[\"3\"], \"c\");\n  EXPECT_EQ(map[\"4\"], \"\");\n\n  auto r = map.emplace_or_assign(\"4\", \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[\"4\"], \"d\");\n\n  std::string s5 = \"5\";\n  std::string se = \"e\";\n  auto r2 = map.emplace_or_assign(s5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[\"5\"], \"e\");\n  EXPECT_EQ(s5, \"5\");\n  EXPECT_TRUE(se.empty());\n\n  std::string s6 = \"6\";\n  std::string sf = \"f\";\n  auto r3 = map.emplace_or_assign(std::move(s6), std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[\"6\"], \"f\");\n  EXPECT_TRUE(s6.empty());\n  EXPECT_TRUE(sf.empty());\n\n  std::string s7 = \"7\";\n  std::string sg = \"g\";\n  auto r4 = map.emplace_or_assign(std::move(s7), sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[\"7\"], \"g\");\n  EXPECT_TRUE(s7.empty());\n  EXPECT_EQ(sg, \"g\");\n\n  auto r5 = map.emplace_or_assign(\"7\", 5, 'g');\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(map[\"7\"], \"ggggg\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, SetInsert) {\n  OrderedFlatSet<std::string> set{\"1\", \"2\", \"3\"};\n  auto r = set.insert(\"3\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(set.size(), 3);\n\n  auto r2 = set.insert(\"4\");\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(set.size(), 4);\n\n  std::string s5 = \"5\";\n  auto r3 = set.insert(std::move(s5));\n  EXPECT_TRUE(r3.second);\n  EXPECT_TRUE(s5.empty());\n\n  EXPECT_EQ(set.size(), 5);\n  EXPECT_TRUE(set.contains(\"1\"));\n  EXPECT_TRUE(set.contains(\"2\"));\n  EXPECT_TRUE(set.contains(\"3\"));\n  EXPECT_TRUE(set.contains(\"4\"));\n  EXPECT_TRUE(set.contains(\"5\"));\n  EXPECT_FALSE(set.contains(\"6\"));\n}\n\nTEST(Vector, MapEmplace) {\n  OrderedFlatMap<std::string, std::string> map;\n  auto r = map.emplace(std::piecewise_construct,\n                       std::tuple<const char*, size_t>(\"123\", 2),\n                       std::tuple<const char*, size_t>(\"abc\", 2));\n  EXPECT_TRUE(r.second);\n  EXPECT_EQ(r.first->first, \"12\");\n  EXPECT_EQ(r.first->second, \"ab\");\n  auto r2 = map.emplace(std::piecewise_construct,\n                        std::tuple<const char*, size_t>(\"112\", 2),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(r2.first->first, \"11\");\n  EXPECT_EQ(r2.first->second, \"xy\");\n\n  EXPECT_EQ(map.size(), 2);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n\n  auto r3 = map.emplace(std::piecewise_construct, std::forward_as_tuple(\"12\"),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, \"12\");\n  EXPECT_EQ(r3.first->second, \"ab\");\n\n  EXPECT_EQ(map.size(), 2);\n\n  auto r4 = map.try_emplace(\"11\", \"ab\");\n  EXPECT_FALSE(r4.second);\n  EXPECT_EQ(r4.first->first, \"11\");\n  EXPECT_EQ(r4.first->second, \"xy\");\n\n  std::string s11 = \"11\";\n  std::string sXYZ = \"xyz\";\n  auto r5 = map.try_emplace(std::move(s11), std::move(sXYZ));\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(r5.first->first, \"11\");\n  EXPECT_EQ(r5.first->second, \"xy\");\n  EXPECT_EQ(s11, \"11\");\n  EXPECT_EQ(sXYZ, \"xyz\");\n\n  std::string s13 = \"13\";\n  auto r6 = map.try_emplace(std::move(s13), std::move(sXYZ));\n  EXPECT_TRUE(r6.second);\n  EXPECT_EQ(r6.first->first, \"13\");\n  EXPECT_EQ(r6.first->second, \"xyz\");\n  EXPECT_TRUE(s13.empty());\n  EXPECT_TRUE(sXYZ.empty());\n\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n\n  std::string s14 = \"14\";\n  std::string sUVW = \"uvw\";\n  auto r7 = map.try_emplace(s14, sUVW);\n  EXPECT_TRUE(r7.second);\n  EXPECT_EQ(r7.first->first, \"14\");\n  EXPECT_EQ(r7.first->second, \"uvw\");\n  EXPECT_EQ(s14, \"14\");\n  EXPECT_EQ(sUVW, \"uvw\");\n\n  EXPECT_EQ(map.size(), 4);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n  EXPECT_EQ(map[\"14\"], \"uvw\");\n}\n\nTEST(MapStringTest, BasicOperations) {\n  OrderedFlatMap<std::string, std::string> m;\n\n  EXPECT_TRUE(m.empty());\n  EXPECT_EQ(m.size(), 0);\n\n  auto ret = m.insert({\"apple\", \"red\"});\n  ASSERT_TRUE(ret.second);\n  EXPECT_EQ(ret.first->first, \"apple\");\n  EXPECT_EQ(ret.first->second, \"red\");\n  EXPECT_EQ(m.size(), 1);\n}\n\nTEST(MapStringTest, ElementAccess) {\n  OrderedFlatMap<std::string, std::string> m{{\"apple\", \"red\"},\n                                             {\"banana\", \"yellow\"}};\n\n  EXPECT_EQ(m[\"apple\"], \"red\");\n\n  m[\"apple\"] = \"green\";\n  EXPECT_EQ(m[\"apple\"], \"green\");\n  EXPECT_EQ(m.at(\"apple\"), \"green\");\n\n  EXPECT_EQ(m[\"grape\"], \"\");\n  EXPECT_EQ(m.at(\"grape\"), \"\");\n  EXPECT_EQ(m.size(), 3);\n\n  EXPECT_EQ(m.at(\"orange\"), \"\");\n  m.at(\"orange\") = \"orange\";\n  EXPECT_EQ(m[\"orange\"], \"orange\");\n  EXPECT_EQ(m.size(), 4);\n\n  std::string melon = \"melon\";\n  m.at(std::move(melon)) = \"green\";\n  EXPECT_EQ(melon, \"\");\n  EXPECT_EQ(m.find(\"melon\")->second, \"green\");\n  EXPECT_EQ(m.size(), 5);\n\n  std::string pear = \"pear\";\n  auto r = m.insert_default_if_absent(std::move(pear));\n  EXPECT_TRUE(r.second);\n  r.first->second = \"yellow\";\n  EXPECT_EQ(pear, \"\");\n  EXPECT_EQ(m[\"pear\"], \"yellow\");\n  EXPECT_EQ(m.size(), 6);\n\n  auto r2 = m.insert_default_if_absent(\"apple\");\n  EXPECT_FALSE(r2.second);\n  EXPECT_EQ(m[\"apple\"], \"green\");\n  r2.first->second = \"red\";\n  EXPECT_EQ(m[\"apple\"], \"red\");\n  EXPECT_EQ(m.size(), 6);\n}\n\nTEST(MapStringTest, InsertUpdate) {\n  OrderedFlatMap<std::string, std::string> m;\n\n  auto ret1 = m.insert({\"fruit\", \"apple\"});\n  EXPECT_TRUE(ret1.second);\n  auto ret2 = m.insert({\"fruit\", \"banana\"});\n  EXPECT_FALSE(ret2.second);\n  EXPECT_EQ(ret2.first->second, \"apple\");\n\n  auto emp_ret = m.emplace(\"color\", \"blue\");\n  EXPECT_TRUE(emp_ret.second);\n  EXPECT_EQ(emp_ret.first->first, \"color\");\n\n  m[\"color\"] = \"red\";\n  EXPECT_EQ(m[\"color\"], \"red\");\n}\n\nTEST(MapStringTest, EraseOperations) {\n  OrderedFlatMap<std::string, std::string> m{\n      {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_EQ(m.size(), 3);\n  EXPECT_EQ(m[\"A\"], \"1\");\n  EXPECT_EQ(m[\"B\"], \"2\");\n  EXPECT_EQ(m[\"C\"], \"3\");\n\n  size_t cnt = m.erase(\"B\");\n  EXPECT_EQ(cnt, 1);\n  EXPECT_EQ(m.size(), 2);\n  EXPECT_FALSE(m.contains(\"B\"));\n\n  auto it = m.find(\"A\");\n  m.erase(it);\n  EXPECT_EQ(m.size(), 1);\n\n  EXPECT_EQ(m.erase(\"X\"), 0);\n}\n\nTEST(SetStringTest, Iterators) {\n  InlineOrderedFlatSet<std::string, 10> s{\"a\", \"z\", \"c\", \"b\",\n                                          \"m\", \"g\", \"q\", \"h\"};\n  std::string order;\n  for (auto it = s.begin(); it != s.end(); it++) {\n    order += *it;\n  }\n  EXPECT_EQ(order, \"abcghmqz\");\n\n  order = \"\";\n  for (auto it = s.rbegin(); it != s.rend(); it++) {\n    order += *it;\n  }\n  EXPECT_EQ(order, \"zqmhgcba\");\n}\n\nTEST(SetStringTest, FrontBack) {\n  {\n    InlineOrderedFlatSet<std::string, 10> s{\"a\", \"z\", \"c\", \"b\",\n                                            \"m\", \"g\", \"q\", \"h\"};\n    EXPECT_EQ(s.front(), \"a\");\n    EXPECT_EQ(s.back(), \"z\");\n    s.erase(\"a\");\n    s.erase(\"z\");\n    s.erase(\"g\");\n    EXPECT_EQ(s.front(), \"b\");\n    EXPECT_EQ(s.back(), \"q\");\n  }\n  {\n    const InlineOrderedFlatSet<std::string, 10> s{\"a\", \"z\", \"c\", \"b\",\n                                                  \"m\", \"g\", \"q\", \"h\"};\n    EXPECT_EQ(s.front(), \"a\");\n    EXPECT_EQ(s.back(), \"z\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(s)>>&>(s))\n        .erase(\"a\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(s)>>&>(s))\n        .erase(\"z\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(s)>>&>(s))\n        .erase(\"g\");\n    EXPECT_EQ(s.front(), \"b\");\n    EXPECT_EQ(s.back(), \"q\");\n  }\n}\n\nTEST(SetStringTest, Basic) {\n  InlineOrderedFlatSet<std::string, 10> s{\"a\", \"z\", \"c\", \"b\",\n                                          \"m\", \"g\", \"q\", \"h\"};\n  EXPECT_TRUE(s.is_static_buffer());\n  EXPECT_TRUE(s.contains(\"a\"));\n  EXPECT_FALSE(s.contains(\"y\"));\n  EXPECT_TRUE(s.find(\"c\") != s.end());\n  EXPECT_TRUE(s.find(\"y\") == s.end());\n  EXPECT_TRUE(s.count(\"q\") == 1);\n  EXPECT_TRUE(s.count(\"y\") == 0);\n  EXPECT_EQ(s.erase(\"y\"), 0);\n  EXPECT_EQ(s.size(), 8);\n  EXPECT_EQ(s.erase(\"z\"), 1);\n  EXPECT_EQ(s.size(), 7);\n  EXPECT_FALSE(s.contains(\"z\"));\n  auto it = s.erase(s.find(\"g\"));\n  EXPECT_EQ(s.size(), 6);\n  EXPECT_EQ(*it, \"h\");\n}\n\nTEST(MapStringTest, Iterators) {\n  OrderedFlatMap<std::string, std::string> m{\n      {\"Z\", \"26\"}, {\"A\", \"1\"}, {\"M\", \"13\"}};\n\n  auto it = m.begin();\n  EXPECT_EQ(it->first, \"A\");\n  ++it;\n  EXPECT_EQ(it->first, \"M\");\n  ++it;\n  EXPECT_EQ(it->first, \"Z\");\n  ++it;\n  EXPECT_EQ(it, m.end());\n\n  auto rit = m.rbegin();\n  EXPECT_EQ(rit->first, \"Z\");\n  ++rit;\n  EXPECT_EQ(rit->first, \"M\");\n  ++rit;\n  EXPECT_EQ(rit->first, \"A\");\n  ++rit;\n  EXPECT_EQ(rit, m.rend());\n}\n\nTEST(MapStringTest, FrontBack) {\n  {\n    OrderedFlatMap<std::string, std::string> m{\n        {\"Z\", \"26\"}, {\"A\", \"1\"}, {\"M\", \"13\"}};\n    EXPECT_EQ(m.front().first, \"A\");\n    EXPECT_EQ(m.front().second, \"1\");\n    EXPECT_EQ(m.back().first, \"Z\");\n    EXPECT_EQ(m.back().second, \"26\");\n    m.erase(\"A\");\n    m.erase(\"Z\");\n    EXPECT_EQ(m.front().first, \"M\");\n    EXPECT_EQ(m.front().second, \"13\");\n    EXPECT_EQ(m.back().first, \"M\");\n    EXPECT_EQ(m.back().second, \"13\");\n\n    m.front().second = \"This is M\";\n    EXPECT_EQ(m[\"M\"], \"This is M\");\n    m.back().second = \"This is M, too\";\n    EXPECT_EQ(m[\"M\"], \"This is M, too\");\n  }\n\n  {\n    const OrderedFlatMap<std::string, std::string> m{\n        {\"Z\", \"26\"}, {\"A\", \"1\"}, {\"M\", \"13\"}};\n    EXPECT_EQ(m.front().first, \"A\");\n    EXPECT_EQ(m.front().second, \"1\");\n    EXPECT_EQ(m.back().first, \"Z\");\n    EXPECT_EQ(m.back().second, \"26\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(m)>>&>(m))\n        .erase(\"A\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(m)>>&>(m))\n        .erase(\"Z\");\n    EXPECT_EQ(m.front().first, \"M\");\n    EXPECT_EQ(m.front().second, \"13\");\n    EXPECT_EQ(m.back().first, \"M\");\n    EXPECT_EQ(m.back().second, \"13\");\n  }\n}\n\nTEST(MapStringTest, EdgeCases) {\n  OrderedFlatMap<std::string, std::string> m;\n\n  m[\"\"] = \"empty_key\";\n  m.emplace(\"empty_value\", \"\");\n  EXPECT_EQ(m[\"\"], \"empty_key\");\n  EXPECT_EQ(m[\"empty_value\"], \"\");\n\n  std::string big_key(1000, 'K');\n  std::string big_value(10000, 'V');\n  m[big_key] = big_value;\n  EXPECT_EQ(m[big_key].size(), 10000);\n}\n\nTEST(MapStringTest, InsertOrAssign) {\n  OrderedFlatMap<std::string, std::string> m;\n\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"banana\");\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"banana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.insert_or_assign(\"empty\", \"\");\n  EXPECT_EQ(m[\"empty\"], \"\");\n\n  auto [it, _] = m.insert_or_assign(\"new_key\", \"value\");\n  EXPECT_EQ(it->first, \"new_key\");\n}\n\nTEST(MapStringTest, EmplaceOrAssign) {\n  OrderedFlatMap<std::string, std::string> m;\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(\"fruit\", \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(\"fruit\", \"banana\", 4);\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"bana\");\n    EXPECT_EQ(m[\"fruit\"], \"bana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.emplace_or_assign(\"empty\", \"\");\n  EXPECT_EQ(m[\"empty\"], \"\");\n\n  auto [it, _] = m.emplace_or_assign(\"new_key\", \"value\");\n  EXPECT_EQ(it->first, \"new_key\");\n}\n\nTEST(MapStringTest, EmplacePiecewise) {\n  OrderedFlatMap<std::string, std::string> m;\n\n  auto emp_it =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(5, 'X'));\n  ASSERT_TRUE(emp_it.second);\n  EXPECT_EQ(emp_it.first->second, \"XXXXX\");\n\n  m.emplace(std::piecewise_construct, std::forward_as_tuple(3, 'K'),\n            std::forward_as_tuple(3, 'k'));\n  EXPECT_EQ(m[\"KKK\"], \"kkk\");\n\n  auto emp_fail =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(\"new_value\"));\n  EXPECT_FALSE(emp_fail.second);\n  EXPECT_EQ(m[\"piece_key\"], \"XXXXX\");\n}\n\nTEST(Vector, LinearMapInsertOrAssign) {\n  InlineLinearFlatMap<std::string, std::string, 5> map{\n      {\"3\", \"c\"}, {\"2\", \"b\"}, {\"1\", \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map.capacity(), 5);\n  EXPECT_EQ(map[\"1\"], \"a\");\n  EXPECT_EQ(map[\"2\"], \"b\");\n  EXPECT_EQ(map[\"3\"], \"c\");\n  EXPECT_EQ(map[\"4\"], \"\");\n\n  auto r = map.insert_or_assign(\"4\", \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[\"4\"], \"d\");\n\n  std::string s5 = \"5\";\n  std::string se = \"e\";\n  auto r2 = map.insert_or_assign(s5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[\"5\"], \"e\");\n  EXPECT_EQ(s5, \"5\");\n  EXPECT_TRUE(se.empty());\n\n  std::string s6 = \"6\";\n  std::string sf = \"f\";\n  auto r3 = map.insert_or_assign(std::move(s6), std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[\"6\"], \"f\");\n  EXPECT_TRUE(s6.empty());\n  EXPECT_TRUE(sf.empty());\n\n  std::string s7 = \"7\";\n  std::string sg = \"g\";\n  auto r4 = map.insert_or_assign(std::move(s7), sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[\"7\"], \"g\");\n  EXPECT_TRUE(s7.empty());\n  EXPECT_EQ(sg, \"g\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearMapEmplaceOrAssign) {\n  InlineLinearFlatMap<std::string, std::string, 5> map{\n      {\"3\", \"c\"}, {\"2\", \"b\"}, {\"1\", \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[\"1\"], \"a\");\n  EXPECT_EQ(map[\"2\"], \"b\");\n  EXPECT_EQ(map[\"3\"], \"c\");\n  EXPECT_EQ(map[\"4\"], \"\");\n\n  auto r = map.emplace_or_assign(\"4\", \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[\"4\"], \"d\");\n\n  std::string s5 = \"5\";\n  std::string se = \"e\";\n  auto r2 = map.emplace_or_assign(s5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[\"5\"], \"e\");\n  EXPECT_EQ(s5, \"5\");\n  EXPECT_TRUE(se.empty());\n\n  std::string s6 = \"6\";\n  std::string sf = \"f\";\n  auto r3 = map.emplace_or_assign(std::move(s6), std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[\"6\"], \"f\");\n  EXPECT_TRUE(s6.empty());\n  EXPECT_TRUE(sf.empty());\n\n  std::string s7 = \"7\";\n  std::string sg = \"g\";\n  auto r4 = map.emplace_or_assign(std::move(s7), sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[\"7\"], \"g\");\n  EXPECT_TRUE(s7.empty());\n  EXPECT_EQ(sg, \"g\");\n\n  auto r5 = map.emplace_or_assign(\"7\", 5, 'g');\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(map[\"7\"], \"ggggg\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearSetInsert) {\n  LinearFlatSet<std::string> set{\"1\", \"2\", \"3\"};\n  auto r = set.insert(\"3\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(set.size(), 3);\n\n  auto r2 = set.insert(\"4\");\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(set.size(), 4);\n\n  std::string s5 = \"5\";\n  auto r3 = set.insert(std::move(s5));\n  EXPECT_TRUE(r3.second);\n  EXPECT_TRUE(s5.empty());\n\n  EXPECT_EQ(set.size(), 5);\n  EXPECT_TRUE(set.contains(\"1\"));\n  EXPECT_TRUE(set.contains(\"2\"));\n  EXPECT_TRUE(set.contains(\"3\"));\n  EXPECT_TRUE(set.contains(\"4\"));\n  EXPECT_TRUE(set.contains(\"5\"));\n  EXPECT_FALSE(set.contains(\"6\"));\n}\n\nTEST(Vector, LinearMapEmplace) {\n  LinearFlatMap<std::string, std::string> map;\n  auto r = map.emplace(std::piecewise_construct,\n                       std::tuple<const char*, size_t>(\"123\", 2),\n                       std::tuple<const char*, size_t>(\"abc\", 2));\n  EXPECT_TRUE(r.second);\n  EXPECT_EQ(r.first->first, \"12\");\n  EXPECT_EQ(r.first->second, \"ab\");\n  auto r2 = map.emplace(std::piecewise_construct,\n                        std::tuple<const char*, size_t>(\"112\", 2),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(r2.first->first, \"11\");\n  EXPECT_EQ(r2.first->second, \"xy\");\n\n  EXPECT_EQ(map.size(), 2);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n\n  auto r3 = map.emplace(std::piecewise_construct, std::forward_as_tuple(\"12\"),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, \"12\");\n  EXPECT_EQ(r3.first->second, \"ab\");\n\n  EXPECT_EQ(map.size(), 2);\n\n  auto r4 = map.try_emplace(\"11\", \"ab\");\n  EXPECT_FALSE(r4.second);\n  EXPECT_EQ(r4.first->first, \"11\");\n  EXPECT_EQ(r4.first->second, \"xy\");\n\n  std::string s11 = \"11\";\n  std::string sXYZ = \"xyz\";\n  auto r5 = map.try_emplace(std::move(s11), std::move(sXYZ));\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(r5.first->first, \"11\");\n  EXPECT_EQ(r5.first->second, \"xy\");\n  EXPECT_EQ(s11, \"11\");\n  EXPECT_EQ(sXYZ, \"xyz\");\n\n  std::string s13 = \"13\";\n  auto r6 = map.try_emplace(std::move(s13), std::move(sXYZ));\n  EXPECT_TRUE(r6.second);\n  EXPECT_EQ(r6.first->first, \"13\");\n  EXPECT_EQ(r6.first->second, \"xyz\");\n  EXPECT_TRUE(s13.empty());\n  EXPECT_TRUE(sXYZ.empty());\n\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n\n  std::string s14 = \"14\";\n  std::string sUVW = \"uvw\";\n  auto r7 = map.try_emplace(s14, sUVW);\n  EXPECT_TRUE(r7.second);\n  EXPECT_EQ(r7.first->first, \"14\");\n  EXPECT_EQ(r7.first->second, \"uvw\");\n  EXPECT_EQ(s14, \"14\");\n  EXPECT_EQ(sUVW, \"uvw\");\n\n  EXPECT_EQ(map.size(), 4);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n  EXPECT_EQ(map[\"14\"], \"uvw\");\n}\n\nTEST(MapStringTest, LinearBasicOperations) {\n  LinearFlatMap<std::string, std::string> m;\n\n  EXPECT_TRUE(m.empty());\n  EXPECT_EQ(m.size(), 0);\n\n  auto ret = m.insert({\"apple\", \"red\"});\n  ASSERT_TRUE(ret.second);\n  EXPECT_EQ(ret.first->first, \"apple\");\n  EXPECT_EQ(ret.first->second, \"red\");\n  EXPECT_EQ(m.size(), 1);\n}\n\nTEST(MapStringTest, LinearElementAccess) {\n  LinearFlatMap<std::string, std::string> m{{\"apple\", \"red\"},\n                                            {\"banana\", \"yellow\"}};\n\n  EXPECT_EQ(m[\"apple\"], \"red\");\n\n  m[\"apple\"] = \"green\";\n  EXPECT_EQ(m[\"apple\"], \"green\");\n  EXPECT_EQ(m.at(\"apple\"), \"green\");\n\n  EXPECT_EQ(m[\"grape\"], \"\");\n  EXPECT_EQ(m.at(\"grape\"), \"\");\n  EXPECT_EQ(m.size(), 3);\n\n  EXPECT_EQ(m.at(\"orange\"), \"\");\n  m.at(\"orange\") = \"orange\";\n  EXPECT_EQ(m[\"orange\"], \"orange\");\n  EXPECT_EQ(m.size(), 4);\n\n  std::string melon = \"melon\";\n  m.at(std::move(melon)) = \"green\";\n  EXPECT_EQ(melon, \"\");\n  EXPECT_EQ(m.find(\"melon\")->second, \"green\");\n  EXPECT_EQ(m.size(), 5);\n\n  std::string pear = \"pear\";\n  auto r = m.insert_default_if_absent(std::move(pear));\n  EXPECT_TRUE(r.second);\n  r.first->second = \"yellow\";\n  EXPECT_EQ(pear, \"\");\n  EXPECT_EQ(m[\"pear\"], \"yellow\");\n  EXPECT_EQ(m.size(), 6);\n\n  auto r2 = m.insert_default_if_absent(\"apple\");\n  EXPECT_FALSE(r2.second);\n  EXPECT_EQ(m[\"apple\"], \"green\");\n  r2.first->second = \"red\";\n  EXPECT_EQ(m[\"apple\"], \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string black = \"black\";\n  auto r3 = m.insert_if_absent(\"apple\", black);\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, \"apple\");\n  EXPECT_EQ(r3.first->second, \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string peach = \"peach\";\n  std::string pink = \"pink\";\n  auto r4 = m.insert_if_absent(std::move(peach), std::move(pink));\n  EXPECT_TRUE(r4.second);\n  EXPECT_TRUE(peach.empty());\n  EXPECT_TRUE(pink.empty());\n  EXPECT_EQ(r4.first->first, \"peach\");\n  EXPECT_EQ(r4.first->second, \"pink\");\n  EXPECT_EQ(m.size(), 7);\n\n  auto r5 = m.insert_if_absent(\"tomato\", black);\n  EXPECT_TRUE(r5.second);\n  EXPECT_EQ(r5.first->first, \"tomato\");\n  EXPECT_EQ(r5.first->second, \"black\");\n  EXPECT_EQ(black, \"black\");\n  EXPECT_EQ(m.size(), 8);\n}\n\nTEST(MapStringTest, LinearInsertUpdate) {\n  LinearFlatMap<std::string, std::string> m;\n\n  auto ret1 = m.insert({\"fruit\", \"apple\"});\n  EXPECT_TRUE(ret1.second);\n  auto ret2 = m.insert({\"fruit\", \"banana\"});\n  EXPECT_FALSE(ret2.second);\n  EXPECT_EQ(ret2.first->second, \"apple\");\n\n  auto emp_ret = m.emplace(\"color\", \"blue\");\n  EXPECT_TRUE(emp_ret.second);\n  EXPECT_EQ(emp_ret.first->first, \"color\");\n\n  m[\"color\"] = \"red\";\n  EXPECT_EQ(m[\"color\"], \"red\");\n}\n\nTEST(MapStringTest, LinearEraseOperations) {\n  LinearFlatMap<std::string, std::string> m{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_EQ(m.size(), 3);\n  EXPECT_EQ(m[\"A\"], \"1\");\n  EXPECT_EQ(m[\"B\"], \"2\");\n  EXPECT_EQ(m[\"C\"], \"3\");\n\n  size_t cnt = m.erase(\"B\");\n  EXPECT_EQ(cnt, 1);\n  EXPECT_EQ(m.size(), 2);\n  EXPECT_FALSE(m.contains(\"B\"));\n\n  auto it = m.find(\"A\");\n  m.erase(it);\n  EXPECT_EQ(m.size(), 1);\n\n  EXPECT_EQ(m.erase(\"X\"), 0);\n}\n\nTEST(SetStringTest, LinearIterators) {\n  InlineLinearFlatSet<std::string, 10> s{\"a\", \"z\", \"c\", \"b\",\n                                         \"m\", \"g\", \"q\", \"h\"};\n  std::string order;\n  for (auto it = s.begin(); it != s.end(); it++) {\n    order += *it;\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"abcghmqz\" : \"azcbmgqh\");\n\n  order = \"\";\n  for (auto it = s.rbegin(); it != s.rend(); it++) {\n    order += *it;\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"zqmhgcba\" : \"hqgmbcza\");\n}\n\nTEST(SetStringTest, LinearFrontBack) {\n  {\n    InlineLinearFlatSet<std::string, 10> s{\"a\", \"z\", \"c\", \"b\",\n                                           \"m\", \"g\", \"q\", \"h\"};\n    EXPECT_EQ(s.front(), \"a\");\n    EXPECT_EQ(s.back(), \"h\");\n    s.erase(\"a\");\n    s.erase(\"h\");\n    s.erase(\"g\");\n    EXPECT_EQ(s.front(), \"z\");\n    EXPECT_EQ(s.back(), \"q\");\n  }\n  {\n    InlineLinearFlatSet<std::string, 10> s{\"a\", \"z\", \"c\", \"b\",\n                                           \"m\", \"g\", \"q\", \"h\"};\n    EXPECT_EQ(s.front(), \"a\");\n    EXPECT_EQ(s.back(), \"h\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(s)>>&>(s))\n        .erase(\"a\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(s)>>&>(s))\n        .erase(\"h\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(s)>>&>(s))\n        .erase(\"g\");\n    EXPECT_EQ(s.front(), \"z\");\n    EXPECT_EQ(s.back(), \"q\");\n  }\n}\n\nTEST(SetStringTest, LinearBasic) {\n  InlineLinearFlatSet<std::string, 10> s{\"a\", \"z\", \"c\", \"b\",\n                                         \"m\", \"g\", \"q\", \"h\"};\n  EXPECT_TRUE(s.is_static_buffer());\n  EXPECT_TRUE(s.contains(\"a\"));\n  EXPECT_FALSE(s.contains(\"y\"));\n  EXPECT_TRUE(s.find(\"c\") != s.end());\n  EXPECT_TRUE(s.find(\"y\") == s.end());\n  EXPECT_TRUE(s.count(\"q\") == 1);\n  EXPECT_TRUE(s.count(\"y\") == 0);\n  EXPECT_EQ(s.erase(\"y\"), 0);\n  EXPECT_EQ(s.size(), 8);\n  EXPECT_EQ(s.erase(\"z\"), 1);\n  EXPECT_EQ(s.size(), 7);\n  EXPECT_FALSE(s.contains(\"z\"));\n  auto it = s.erase(s.find(\"g\"));\n  EXPECT_EQ(s.size(), 6);\n  EXPECT_EQ(*it, s.is_data_ordered() ? \"h\" : \"q\");\n}\n\nTEST(MapStringTest, LinearIterators) {\n  LinearFlatMap<std::string, std::string> m{\n      {\"Z\", \"26\"}, {\"A\", \"1\"}, {\"M\", \"13\"}};\n\n  auto it = m.begin();\n  EXPECT_EQ(it->first, m.is_data_ordered() ? \"A\" : \"Z\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? \"M\" : \"A\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? \"Z\" : \"M\");\n  ++it;\n  EXPECT_EQ(it, m.end());\n\n  auto rit = m.rbegin();\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? \"Z\" : \"M\");\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? \"M\" : \"A\");\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? \"A\" : \"Z\");\n  ++rit;\n  EXPECT_EQ(rit, m.rend());\n}\n\nTEST(MapStringTest, LinearFrontBack) {\n  {\n    LinearFlatMap<std::string, std::string> m{\n        {\"Z\", \"26\"}, {\"A\", \"1\"}, {\"M\", \"13\"}};\n    EXPECT_EQ(m.front().first, \"Z\");\n    EXPECT_EQ(m.front().second, \"26\");\n    EXPECT_EQ(m.back().first, \"M\");\n    EXPECT_EQ(m.back().second, \"13\");\n    m.erase(\"Z\");\n    m.erase(\"M\");\n    EXPECT_EQ(m.front().first, \"A\");\n    EXPECT_EQ(m.front().second, \"1\");\n    EXPECT_EQ(m.back().first, \"A\");\n    EXPECT_EQ(m.back().second, \"1\");\n\n    m.front().second = \"This is A\";\n    EXPECT_EQ(m[\"A\"], \"This is A\");\n    m.back().second = \"This is A, too\";\n    EXPECT_EQ(m[\"A\"], \"This is A, too\");\n  }\n\n  {\n    const LinearFlatMap<std::string, std::string> m{\n        {\"Z\", \"26\"}, {\"A\", \"1\"}, {\"M\", \"13\"}};\n    EXPECT_EQ(m.front().first, \"Z\");\n    EXPECT_EQ(m.front().second, \"26\");\n    EXPECT_EQ(m.back().first, \"M\");\n    EXPECT_EQ(m.back().second, \"13\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(m)>>&>(m))\n        .erase(\"Z\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(m)>>&>(m))\n        .erase(\"M\");\n    EXPECT_EQ(m.front().first, \"A\");\n    EXPECT_EQ(m.front().second, \"1\");\n    EXPECT_EQ(m.back().first, \"A\");\n    EXPECT_EQ(m.back().second, \"1\");\n  }\n}\n\nTEST(MapStringTest, LinearEdgeCases) {\n  LinearFlatMap<std::string, std::string> m;\n\n  m[\"\"] = \"empty_key\";\n  m.emplace(\"empty_value\", \"\");\n  EXPECT_EQ(m[\"\"], \"empty_key\");\n  EXPECT_EQ(m[\"empty_value\"], \"\");\n\n  std::string big_key(1000, 'K');\n  std::string big_value(10000, 'V');\n  m[big_key] = big_value;\n  EXPECT_EQ(m[big_key].size(), 10000);\n}\n\nTEST(MapStringTest, LinearInsertOrAssign) {\n  LinearFlatMap<std::string, std::string> m;\n\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"banana\");\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"banana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.insert_or_assign(\"empty\", \"\");\n  EXPECT_EQ(m[\"empty\"], \"\");\n\n  auto [it, _] = m.insert_or_assign(\"new_key\", \"value\");\n  EXPECT_EQ(it->first, \"new_key\");\n}\n\nTEST(MapStringTest, LinearEmplaceOrAssign) {\n  LinearFlatMap<std::string, std::string> m;\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(\"fruit\", \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(\"fruit\", \"banana\", 4);\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"bana\");\n    EXPECT_EQ(m[\"fruit\"], \"bana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.insert_or_assign(\"empty\", \"\");\n  EXPECT_EQ(m[\"empty\"], \"\");\n\n  auto [it, _] = m.emplace_or_assign(\"new_key\", \"value\");\n  EXPECT_EQ(it->first, \"new_key\");\n}\n\nTEST(MapStringTest, LinearEmplacePiecewise) {\n  LinearFlatMap<std::string, std::string> m;\n\n  auto emp_it =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(5, 'X'));\n  ASSERT_TRUE(emp_it.second);\n  EXPECT_EQ(emp_it.first->second, \"XXXXX\");\n\n  m.emplace(std::piecewise_construct, std::forward_as_tuple(3, 'K'),\n            std::forward_as_tuple(3, 'k'));\n  EXPECT_EQ(m[\"KKK\"], \"kkk\");\n\n  auto emp_fail =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(\"new_value\"));\n  EXPECT_FALSE(emp_fail.second);\n  EXPECT_EQ(m[\"piece_key\"], \"XXXXX\");\n}\n\nnamespace {\ntemplate <class MapType>\nbool AssertMapContent_ABC_123(MapType& m) {\n  return (m.size() == 3) && (m[\"A\"] == \"1\") && (m[\"B\"] == \"2\") &&\n         (m[\"C\"] == \"3\");\n}\n\ntemplate <class MapType>\nbool AssertMapContent_abc_123(MapType& m) {\n  return (m.size() == 3) && (m[\"a\"] == \"1\") && (m[\"b\"] == \"2\") &&\n         (m[\"c\"] == \"3\");\n}\n}  // namespace\n\nTEST(MapStringTest, MixedInlineSize) {\n  OrderedFlatMap<std::string, std::string> m_src{\n      {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_TRUE(AssertMapContent_ABC_123(m_src));\n  InlineOrderedFlatMap<std::string, std::string, 3> m_src2{\n      {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_TRUE(AssertMapContent_ABC_123(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  OrderedFlatMap<std::string, std::string> m1(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  EXPECT_TRUE(m1 == m_src);\n  OrderedFlatMap<std::string, std::string> m2(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineOrderedFlatMap<std::string, std::string, 2> m3(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineOrderedFlatMap<std::string, std::string, 2> m4(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineOrderedFlatMap<std::string, std::string, 5> m5(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineOrderedFlatMap<std::string, std::string, 5> m6(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  OrderedFlatMap<std::string, std::string> m7{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m7));\n\n  InlineOrderedFlatMap<std::string, std::string, 3> m8{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineOrderedFlatMap<std::string, std::string, 2> m9{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineOrderedFlatMap<std::string, std::string, 5> m10{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  OrderedFlatMap<std::string, std::string> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineOrderedFlatMap<std::string, std::string, 3> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineOrderedFlatMap<std::string, std::string, 2> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineOrderedFlatMap<std::string, std::string, 5> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  OrderedFlatMap<std::string, std::string> m15{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineOrderedFlatMap<std::string, std::string, 3> m16{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineOrderedFlatMap<std::string, std::string, 2> m17{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(MapStringTest, LinearMixedInlineSize) {\n  LinearFlatMap<std::string, std::string> m_src{\n      {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_TRUE(AssertMapContent_ABC_123(m_src));\n  InlineLinearFlatMap<std::string, std::string, 3> m_src2{\n      {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_TRUE(AssertMapContent_ABC_123(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatMap<std::string, std::string> m1(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatMap<std::string, std::string> m2(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatMap<std::string, std::string, 2> m3(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatMap<std::string, std::string, 2> m4(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatMap<std::string, std::string, 5> m5(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatMap<std::string, std::string, 5> m6(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatMap<std::string, std::string> m7{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m7));\n\n  InlineLinearFlatMap<std::string, std::string, 3> m8{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatMap<std::string, std::string, 2> m9{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatMap<std::string, std::string, 5> m10{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatMap<std::string, std::string> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatMap<std::string, std::string, 3> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatMap<std::string, std::string, 2> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatMap<std::string, std::string, 5> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatMap<std::string, std::string> m15{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatMap<std::string, std::string, 3> m16{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatMap<std::string, std::string, 2> m17{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nnamespace {\ntemplate <class SetType>\nbool AssertSetContent_ABC(const SetType& m) {\n  return m.size() == 3 && m.contains(\"A\") && m.contains(\"B\") && m.contains(\"C\");\n}\n\ntemplate <class SetType>\nbool AssertSetContent_abc(const SetType& m) {\n  return m.size() == 3 && m.contains(\"a\") && m.contains(\"b\") && m.contains(\"c\");\n}\n}  // namespace\n\nTEST(SetStringTest, Emplace) {\n  OrderedFlatSet<std::string> s;\n  s.emplace(\"ABC\", 2);\n  s.emplace(\"D\");\n  s.insert(\"AB\");\n  EXPECT_EQ(s.size(), 2);\n  EXPECT_TRUE(s.contains(\"AB\"));\n  EXPECT_TRUE(s.contains(\"D\"));\n}\n\nTEST(SetStringTest, LinearEmplace) {\n  LinearFlatSet<std::string> s;\n  s.emplace(\"ABC\", 2);\n  s.emplace(\"D\");\n  s.insert(\"AB\");\n  EXPECT_EQ(s.size(), 2);\n  EXPECT_TRUE(s.contains(\"AB\"));\n  EXPECT_TRUE(s.contains(\"D\"));\n}\n\nTEST(SetStringTest, MixedInlineSize) {\n  OrderedFlatSet<std::string> m_src{\"A\", \"B\", \"C\"};\n  EXPECT_TRUE(AssertSetContent_ABC(m_src));\n  InlineOrderedFlatSet<std::string, 3> m_src2{\"A\", \"B\", \"C\"};\n  EXPECT_TRUE(AssertSetContent_ABC(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  OrderedFlatSet<std::string> m1(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m1));\n  EXPECT_TRUE(m1 == m_src);\n  OrderedFlatSet<std::string> m2(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineOrderedFlatSet<std::string, 2> m3(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineOrderedFlatSet<std::string, 2> m4(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineOrderedFlatSet<std::string, 5> m5(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineOrderedFlatSet<std::string, 5> m6(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  OrderedFlatSet<std::string> m7{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m7));\n\n  InlineOrderedFlatSet<std::string, 3> m8{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineOrderedFlatSet<std::string, 2> m9{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineOrderedFlatSet<std::string, 5> m10{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  OrderedFlatSet<std::string> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineOrderedFlatSet<std::string, 3> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineOrderedFlatSet<std::string, 2> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineOrderedFlatSet<std::string, 5> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  OrderedFlatSet<std::string> m15{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineOrderedFlatSet<std::string, 3> m16{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineOrderedFlatSet<std::string, 2> m17{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(SetStringTest, LinearMixedInlineSize) {\n  LinearFlatSet<std::string> m_src{\"A\", \"B\", \"C\"};\n  EXPECT_TRUE(AssertSetContent_ABC(m_src));\n  InlineLinearFlatSet<std::string, 3> m_src2{\"A\", \"B\", \"C\"};\n  EXPECT_TRUE(AssertSetContent_ABC(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatSet<std::string> m1(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatSet<std::string> m2(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatSet<std::string, 2> m3(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatSet<std::string, 2> m4(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatSet<std::string, 5> m5(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatSet<std::string, 5> m6(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatSet<std::string> m7{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m7));\n\n  InlineLinearFlatSet<std::string, 3> m8{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatSet<std::string, 2> m9{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatSet<std::string, 5> m10{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatSet<std::string> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatSet<std::string, 3> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatSet<std::string, 2> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatSet<std::string, 5> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatSet<std::string> m15{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatSet<std::string, 3> m16{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatSet<std::string, 2> m17{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(LinearMap, FromSourceArray) {\n  {\n    Vector<std::pair<std::string, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    LinearFlatMap<std::string, std::string, void> map(std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n    EXPECT_TRUE(source_array.empty());\n  }\n\n  {\n    InlineVector<std::pair<std::string, std::string>, 5> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    LinearFlatMap<std::string, std::string, void> map(std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<std::string, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<std::string, std::string, 2, void> map(\n        std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<std::string, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<std::string, std::string, 5, void> map(\n        std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    InlineVector<std::pair<std::string, std::string>, 5> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<std::string, std::string, 5, void> map(\n        std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<std::string, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    LinearFlatMap<std::string, std::string, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n    EXPECT_TRUE(source_array.empty());\n  }\n\n  {\n    InlineVector<std::pair<std::string, std::string>, 5> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    LinearFlatMap<std::string, std::string, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<std::string, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<std::string, std::string, 2, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<std::string, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<std::string, std::string, 5, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    InlineVector<std::pair<std::string, std::string>, 5> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<std::string, std::string, 5, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n}\n\nTEST(LinearSet, FromSourceArray) {\n  {\n    Vector<std::string> source_array{\"z\", \"a\", \"e\"};\n    LinearFlatSet<std::string, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n    EXPECT_TRUE(source_array.empty());\n  }\n\n  {\n    InlineVector<std::string, 5> source_array{\"z\", \"a\", \"e\"};\n    LinearFlatSet<std::string, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<std::string> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<std::string, 2, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<std::string> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<std::string, 5, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    InlineVector<std::string, 5> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<std::string, 5, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<std::string> source_array{\"z\", \"a\", \"e\"};\n    LinearFlatSet<std::string, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n    EXPECT_TRUE(source_array.empty());\n  }\n\n  {\n    InlineVector<std::string, 5> source_array{\"z\", \"a\", \"e\"};\n    LinearFlatSet<std::string, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<std::string> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<std::string, 2, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<std::string> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<std::string, 5, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    InlineVector<std::string, 5> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<std::string, 5, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n}\n\nTEST(OrderedMap, Swap) {\n  {\n    OrderedFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    OrderedFlatMap<std::string, std::string> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    OrderedFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineOrderedFlatMap<std::string, std::string, 2> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    OrderedFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineOrderedFlatMap<std::string, std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    InlineOrderedFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineOrderedFlatMap<std::string, std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    OrderedFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    OrderedFlatMap<std::string, std::string> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    OrderedFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineOrderedFlatMap<std::string, std::string, 2> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    OrderedFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineOrderedFlatMap<std::string, std::string, 5> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    InlineOrderedFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineOrderedFlatMap<std::string, std::string, 5> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n}\n\nTEST(LinearMap, Swap) {\n  {\n    LinearFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 2> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 2> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 5> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 5> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n}\n\nTEST(OrderedSet, Swap) {\n  {\n    OrderedFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    OrderedFlatSet<std::string> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    OrderedFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    InlineOrderedFlatSet<std::string, 2> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    OrderedFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    InlineOrderedFlatSet<std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    InlineOrderedFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    InlineOrderedFlatSet<std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    OrderedFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    OrderedFlatSet<std::string> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    OrderedFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    InlineOrderedFlatSet<std::string, 2> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    OrderedFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    InlineOrderedFlatSet<std::string, 5> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    InlineOrderedFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    InlineOrderedFlatSet<std::string, 5> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n}\n\nTEST(LinearSet, Swap) {\n  {\n    LinearFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<std::string> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 2> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    InlineLinearFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<std::string> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 2> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 5> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    InlineLinearFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 5> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n}\n\nnamespace {\ntemplate <class MapType>\nbool AssertMapContent_ABCbD_1232040(MapType& m) {\n  return (m.size() == 5) && (m[\"A\"] == \"1\") && (m[\"B\"] == \"2\") &&\n         (m[\"C\"] == \"3\") && (m[\"b\"] == \"20\") && (m[\"D\"] == \"40\");\n}\n\ntemplate <class MapType>\nbool AssertMapContent_ABCbD_102302040(MapType& m) {\n  return (m.size() == 5) && (m[\"A\"] == \"10\") && (m[\"B\"] == \"2\") &&\n         (m[\"C\"] == \"30\") && (m[\"b\"] == \"20\") && (m[\"D\"] == \"40\");\n}\n\ntemplate <class MapType>\nbool AssertMapContent_AbCD_10203040(MapType& m) {\n  return (m.size() == 4) && (m[\"A\"] == \"10\") && (m[\"b\"] == \"20\") &&\n         (m[\"C\"] == \"30\") && (m[\"D\"] == \"40\");\n}\n\ntemplate <class MapType>\nbool AssertMapContent_AC_1030(MapType& m) {\n  return (m.size() == 2) && (m[\"A\"] == \"10\") && (m[\"C\"] == \"30\");\n}\n}  // namespace\n\nTEST(OrderedMap, Merge) {\n  {\n    OrderedFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    OrderedFlatMap<std::string, std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    OrderedFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    OrderedFlatMap<std::string, std::string> m2{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    OrderedFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    OrderedFlatMap<std::string, std::string> m2{\n        {\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_AC_1030(m2));\n  }\n\n  {\n    InlineOrderedFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    OrderedFlatMap<std::string, std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineOrderedFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineOrderedFlatMap<std::string, std::string, 3> m2{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    InlineOrderedFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineOrderedFlatMap<std::string, std::string, 4> m2{\n        {\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_AC_1030(m2));\n  }\n}\n\nTEST(LinearMap, Merge) {\n  {\n    LinearFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string> m2{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    LinearFlatMap<std::string, std::string> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string> m2{\n        {\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_AC_1030(m2));\n  }\n\n  {\n    InlineLinearFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 3> m2{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    InlineLinearFlatMap<std::string, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 4> m2{\n        {\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_AC_1030(m2));\n  }\n}\n\ntemplate <class K>\nstruct MergeAssignKeyPolicy : public ReducedHashKeyPolicy<K> {\n  static constexpr auto assign_existing_for_merge = true;\n};\n\nTEST(LinearMap, MergeAssign) {\n  {\n    LinearFlatMap<std::string, std::string, MergeAssignKeyPolicy<std::string>>\n        m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string, MergeAssignKeyPolicy<std::string>>\n        m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<std::string, std::string, MergeAssignKeyPolicy<std::string>>\n        m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string, MergeAssignKeyPolicy<std::string>>\n        m2{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    LinearFlatMap<std::string, std::string, MergeAssignKeyPolicy<std::string>>\n        m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string, MergeAssignKeyPolicy<std::string>>\n        m2{{\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_102302040(m1));\n    EXPECT_TRUE(AssertMapContent_AbCD_10203040(m2));\n  }\n\n  {\n    InlineLinearFlatMap<std::string, std::string, 3,\n                        MergeAssignKeyPolicy<std::string>>\n        m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<std::string, std::string, MergeAssignKeyPolicy<std::string>>\n        m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<std::string, std::string, 3,\n                        MergeAssignKeyPolicy<std::string>>\n        m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 3,\n                        MergeAssignKeyPolicy<std::string>>\n        m2{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    InlineLinearFlatMap<std::string, std::string, 3,\n                        MergeAssignKeyPolicy<std::string>>\n        m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<std::string, std::string, 4,\n                        MergeAssignKeyPolicy<std::string>>\n        m2{{\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_102302040(m1));\n    EXPECT_TRUE(AssertMapContent_AbCD_10203040(m2));\n  }\n}\n\nnamespace {\ntemplate <class SetType>\nbool AssertSetContent_ABCbD(SetType& m) {\n  return m.size() == 5 && m.contains(\"A\") && m.contains(\"B\") &&\n         m.contains(\"C\") && m.contains(\"b\") && m.contains(\"D\");\n}\n\ntemplate <class SetType>\nbool AssertSetContent_AC(SetType& m) {\n  return m.size() == 2 && m.contains(\"A\") && m.contains(\"C\");\n}\n\ntemplate <class SetType>\nbool AssertSetContent_AbCD(SetType& m) {\n  return m.size() == 4 && m.contains(\"A\") && m.contains(\"b\") &&\n         m.contains(\"C\") && m.contains(\"D\");\n}\n}  // namespace\n\nTEST(OrderedSet, Merge) {\n  {\n    OrderedFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    OrderedFlatSet<std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    OrderedFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    OrderedFlatSet<std::string> m2{\"A\", \"B\", \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    OrderedFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    OrderedFlatSet<std::string> m2{\"A\", \"b\", \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AC(m2));\n  }\n\n  {\n    InlineOrderedFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    OrderedFlatSet<std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineOrderedFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    InlineOrderedFlatSet<std::string, 3> m2{\"A\", \"B\", \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    InlineOrderedFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    InlineOrderedFlatSet<std::string, 4> m2{\"A\", \"b\", \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AC(m2));\n  }\n}\n\nTEST(LinearSet, Merge) {\n  {\n    LinearFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<std::string> m2{\"A\", \"B\", \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    LinearFlatSet<std::string> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<std::string> m2{\"A\", \"b\", \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AC(m2));\n  }\n\n  {\n    InlineLinearFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 3> m2{\"A\", \"B\", \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    InlineLinearFlatSet<std::string, 3> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 4> m2{\"A\", \"b\", \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AC(m2));\n  }\n}\n\nTEST(LinearSet, MergeAssign) {\n  {\n    LinearFlatSet<std::string, MergeAssignKeyPolicy<std::string>> m1{\"A\", \"B\",\n                                                                     \"C\"};\n    LinearFlatSet<std::string, MergeAssignKeyPolicy<std::string>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<std::string, MergeAssignKeyPolicy<std::string>> m1{\"A\", \"B\",\n                                                                     \"C\"};\n    LinearFlatSet<std::string, MergeAssignKeyPolicy<std::string>> m2{\"A\", \"B\",\n                                                                     \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    LinearFlatSet<std::string, MergeAssignKeyPolicy<std::string>> m1{\"A\", \"B\",\n                                                                     \"C\"};\n    LinearFlatSet<std::string, MergeAssignKeyPolicy<std::string>> m2{\"A\", \"b\",\n                                                                     \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AbCD(m2));\n  }\n\n  {\n    InlineLinearFlatSet<std::string, 3, MergeAssignKeyPolicy<std::string>> m1{\n        \"A\", \"B\", \"C\"};\n    LinearFlatSet<std::string, MergeAssignKeyPolicy<std::string>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    InlineLinearFlatSet<std::string, 3, MergeAssignKeyPolicy<std::string>> m1{\n        \"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 3, MergeAssignKeyPolicy<std::string>> m2{\n        \"A\", \"B\", \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    InlineLinearFlatSet<std::string, 3, MergeAssignKeyPolicy<std::string>> m1{\n        \"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<std::string, 4, MergeAssignKeyPolicy<std::string>> m2{\n        \"A\", \"b\", \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AbCD(m2));\n  }\n}\n\nTEST(Vector, LinearMapInsertOrAssignBaseStringKey) {\n  InlineLinearFlatMap<String, std::string, 5> map{\n      {\"3\", \"c\"}, {\"2\", \"b\"}, {\"1\", \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map.capacity(), 5);\n  EXPECT_EQ(map[\"1\"], \"a\");\n  EXPECT_EQ(map[\"2\"], \"b\");\n  EXPECT_EQ(map[\"3\"], \"c\");\n  EXPECT_EQ(map[\"4\"], \"\");\n\n  auto r = map.insert_or_assign(\"4\", \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[\"4\"], \"d\");\n\n  String s5(\"5\");\n  std::string se = \"e\";\n  auto r2 = map.insert_or_assign(s5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[\"5\"], \"e\");\n  EXPECT_EQ(s5, \"5\");\n  EXPECT_TRUE(se.empty());\n\n  String s6(\"6\");\n  std::string sf = \"f\";\n  auto r3 = map.insert_or_assign(std::move(s6), std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[\"6\"], \"f\");\n  EXPECT_TRUE(s6.empty());\n  EXPECT_TRUE(sf.empty());\n\n  String s7(\"7\");\n  std::string sg = \"g\";\n  auto r4 = map.insert_or_assign(std::move(s7), sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[\"7\"], \"g\");\n  EXPECT_TRUE(s7.empty());\n  EXPECT_EQ(sg, \"g\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearMapEmplaceOrAssignBaseStringKey) {\n  InlineLinearFlatMap<String, std::string, 5> map{\n      {\"3\", \"c\"}, {\"2\", \"b\"}, {\"1\", \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[\"1\"], \"a\");\n  EXPECT_EQ(map[\"2\"], \"b\");\n  EXPECT_EQ(map[\"3\"], \"c\");\n  EXPECT_EQ(map[\"4\"], \"\");\n\n  auto r = map.emplace_or_assign(\"4\", \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[\"4\"], \"d\");\n\n  String s5(\"5\");\n  std::string se = \"e\";\n  auto r2 = map.emplace_or_assign(s5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[\"5\"], \"e\");\n  EXPECT_EQ(s5, \"5\");\n  EXPECT_TRUE(se.empty());\n\n  String s6(\"6\");\n  std::string sf = \"f\";\n  auto r3 = map.emplace_or_assign(std::move(s6), std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[\"6\"], \"f\");\n  EXPECT_TRUE(s6.empty());\n  EXPECT_TRUE(sf.empty());\n\n  String s7(\"7\");\n  std::string sg = \"g\";\n  auto r4 = map.emplace_or_assign(std::move(s7), sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[\"7\"], \"g\");\n  EXPECT_TRUE(s7.empty());\n  EXPECT_EQ(sg, \"g\");\n\n  auto r5 = map.emplace_or_assign(\"7\", 5, 'g');\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(map[\"7\"], \"ggggg\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearSetInsertBaseStringKey) {\n  LinearFlatSet<String> set{\"1\", \"2\", \"3\"};\n  auto r = set.insert(\"3\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(set.size(), 3);\n\n  auto r2 = set.insert(\"4\");\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(set.size(), 4);\n\n  std::string s5 = \"5\";\n  auto r3 = set.insert(std::move(s5));\n  EXPECT_TRUE(r3.second);\n  EXPECT_TRUE(s5.empty());\n\n  EXPECT_EQ(set.size(), 5);\n  EXPECT_TRUE(set.contains(\"1\"));\n  EXPECT_TRUE(set.contains(\"2\"));\n  EXPECT_TRUE(set.contains(\"3\"));\n  EXPECT_TRUE(set.contains(\"4\"));\n  EXPECT_TRUE(set.contains(\"5\"));\n  EXPECT_FALSE(set.contains(\"6\"));\n}\n\nTEST(Vector, LinearSetInsertUniqueBaseStringKey) {\n  LinearFlatSet<String> set;\n  set.insert_unique(\"1\");\n  set.insert_unique(\"3\");\n  const String two(\"2\");\n  set.insert_unique(two);\n  set.insert_unique(\"6\");\n  auto it = set.insert_unique(\"0\");\n  EXPECT_EQ(set.size(), 5);\n  EXPECT_TRUE(set.contains(\"1\"));\n  EXPECT_TRUE(set.contains(\"3\"));\n  EXPECT_TRUE(set.contains(\"2\"));\n  EXPECT_TRUE(set.contains(\"6\"));\n  EXPECT_TRUE(set.contains(\"0\"));\n  EXPECT_TRUE(*it == \"0\");\n}\n\nTEST(Vector, LinearMapEmplaceBaseStringKey) {\n  LinearFlatMap<String, std::string> map;\n  auto r = map.emplace(std::piecewise_construct,\n                       std::tuple<const char*, size_t>(\"123\", 2),\n                       std::tuple<const char*, size_t>(\"abc\", 2));\n  EXPECT_TRUE(r.second);\n  EXPECT_EQ(r.first->first, \"12\");\n  EXPECT_EQ(r.first->second, \"ab\");\n  auto r2 = map.emplace(std::piecewise_construct,\n                        std::tuple<const char*, size_t>(\"112\", 2),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(r2.first->first, \"11\");\n  EXPECT_EQ(r2.first->second, \"xy\");\n\n  EXPECT_EQ(map.size(), 2);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n\n  auto r3 = map.emplace(std::piecewise_construct, std::forward_as_tuple(\"12\"),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, \"12\");\n  EXPECT_EQ(r3.first->second, \"ab\");\n\n  EXPECT_EQ(map.size(), 2);\n\n  auto r4 = map.try_emplace(\"11\", \"ab\");\n  EXPECT_FALSE(r4.second);\n  EXPECT_EQ(r4.first->first, \"11\");\n  EXPECT_EQ(r4.first->second, \"xy\");\n\n  String s11(\"11\");\n  std::string sXYZ = \"xyz\";\n  auto r5 = map.try_emplace(std::move(s11), std::move(sXYZ));\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(r5.first->first, \"11\");\n  EXPECT_EQ(r5.first->second, \"xy\");\n  EXPECT_EQ(s11, \"11\");\n  EXPECT_EQ(sXYZ, \"xyz\");\n\n  std::string s13 = \"13\";\n  auto r6 = map.try_emplace(std::move(s13), std::move(sXYZ));\n  EXPECT_TRUE(r6.second);\n  EXPECT_EQ(r6.first->first, \"13\");\n  EXPECT_EQ(r6.first->second, \"xyz\");\n  EXPECT_TRUE(s13.empty());\n  EXPECT_TRUE(sXYZ.empty());\n\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n\n  std::string s14 = \"14\";\n  std::string sUVW = \"uvw\";\n  auto r7 = map.try_emplace(s14, sUVW);\n  EXPECT_TRUE(r7.second);\n  EXPECT_EQ(r7.first->first, \"14\");\n  EXPECT_EQ(r7.first->second, \"uvw\");\n  EXPECT_EQ(s14, \"14\");\n  EXPECT_EQ(sUVW, \"uvw\");\n\n  EXPECT_EQ(map.size(), 4);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n  EXPECT_EQ(map[\"14\"], \"uvw\");\n}\n\nTEST(MapStringTest, LinearBasicOperationsBaseStringKey) {\n  LinearFlatMap<String, std::string> m;\n\n  EXPECT_TRUE(m.empty());\n  EXPECT_EQ(m.size(), 0);\n\n  auto ret = m.insert({\"apple\", \"red\"});\n  ASSERT_TRUE(ret.second);\n  EXPECT_EQ(ret.first->first, \"apple\");\n  EXPECT_EQ(ret.first->second, \"red\");\n  EXPECT_EQ(m.size(), 1);\n}\n\nTEST(MapStringTest, LinearElementAccessBaseStringKey) {\n  LinearFlatMap<String, std::string> m{{\"apple\", \"red\"}, {\"banana\", \"yellow\"}};\n\n  EXPECT_EQ(m[\"apple\"], \"red\");\n\n  m[\"apple\"] = \"green\";\n  EXPECT_EQ(m[\"apple\"], \"green\");\n  EXPECT_EQ(m.at(\"apple\"), \"green\");\n\n  EXPECT_EQ(m[\"grape\"], \"\");\n  EXPECT_EQ(m.at(\"grape\"), \"\");\n  EXPECT_EQ(m.size(), 3);\n\n  EXPECT_EQ(m.at(\"orange\"), \"\");\n  m.at(\"orange\") = \"orange\";\n  EXPECT_EQ(m[\"orange\"], \"orange\");\n  EXPECT_EQ(m.size(), 4);\n\n  std::string melon = \"melon\";\n  m.at(std::move(melon)) = \"green\";\n  EXPECT_EQ(melon, \"\");\n  EXPECT_EQ(m.find(\"melon\")->second, \"green\");\n  EXPECT_EQ(m.size(), 5);\n\n  std::string pear = \"pear\";\n  auto r = m.insert_default_if_absent(std::move(pear));\n  EXPECT_TRUE(r.second);\n  r.first->second = \"yellow\";\n  EXPECT_EQ(pear, \"\");\n  EXPECT_EQ(m[\"pear\"], \"yellow\");\n  EXPECT_EQ(m.size(), 6);\n\n  auto r2 = m.insert_default_if_absent(\"apple\");\n  EXPECT_FALSE(r2.second);\n  EXPECT_EQ(m[\"apple\"], \"green\");\n  r2.first->second = \"red\";\n  EXPECT_EQ(m[\"apple\"], \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string black = \"black\";\n  auto r3 = m.insert_if_absent(\"apple\", black);\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, \"apple\");\n  EXPECT_EQ(r3.first->second, \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string peach = \"peach\";\n  std::string pink = \"pink\";\n  auto r4 = m.insert_if_absent(std::move(peach), std::move(pink));\n  EXPECT_TRUE(r4.second);\n  EXPECT_TRUE(peach.empty());\n  EXPECT_TRUE(pink.empty());\n  EXPECT_EQ(r4.first->first, \"peach\");\n  EXPECT_EQ(r4.first->second, \"pink\");\n  EXPECT_EQ(m.size(), 7);\n\n  auto r5 = m.insert_if_absent(\"tomato\", black);\n  EXPECT_TRUE(r5.second);\n  EXPECT_EQ(r5.first->first, \"tomato\");\n  EXPECT_EQ(r5.first->second, \"black\");\n  EXPECT_EQ(black, \"black\");\n  EXPECT_EQ(m.size(), 8);\n}\n\nTEST(MapStringTest, LinearInsertUpdateBaseStringKey) {\n  LinearFlatMap<String, std::string> m;\n\n  auto ret1 = m.insert({\"fruit\", \"apple\"});\n  EXPECT_TRUE(ret1.second);\n  auto ret2 = m.insert({\"fruit\", \"banana\"});\n  EXPECT_FALSE(ret2.second);\n  EXPECT_EQ(ret2.first->second, \"apple\");\n\n  auto emp_ret = m.emplace(\"color\", \"blue\");\n  EXPECT_TRUE(emp_ret.second);\n  EXPECT_EQ(emp_ret.first->first, \"color\");\n\n  m[\"color\"] = \"red\";\n  EXPECT_EQ(m[\"color\"], \"red\");\n\n  std::pair<const String, std::string> value_type{String(\"vehicle\"), \"car\"};\n  auto ret3 = m.insert(value_type);\n  EXPECT_TRUE(ret3.second);\n  EXPECT_TRUE(!value_type.first.empty());\n  auto ret4 = m.insert(std::move(value_type));\n  EXPECT_FALSE(ret4.second);\n  EXPECT_TRUE(!value_type.second.empty());\n\n  m.erase(\"vehicle\");\n  auto ret5 = m.insert(std::move(value_type));\n  EXPECT_TRUE(ret5.second);\n  EXPECT_TRUE(!value_type.first.empty());\n  EXPECT_TRUE(value_type.second.empty());\n\n  std::pair<const String, std::string> value_data0{\"job\", \"doctor\"};\n  std::pair<const String, std::string> value_data1{\"road\", \"highway\"};\n  std::pair<String, std::string> value_data2{\"building\", \"hospital\"};\n  std::pair<String, std::string> value_data3{\"animal\", \"tiger\"};\n  auto it = m.insert_unique(value_data0);\n  EXPECT_TRUE(it->first == \"job\" && it->second == \"doctor\");\n  EXPECT_FALSE(value_data0.first.empty());\n  EXPECT_FALSE(value_data0.second.empty());\n  auto it2 = m.insert_unique(std::move(value_data1));\n  EXPECT_TRUE(it2->first == \"road\" && it2->second == \"highway\");\n  EXPECT_FALSE(value_data1.first.empty());\n  EXPECT_TRUE(value_data1.second.empty());\n  auto it3 = m.insert_unique(value_data2);\n  EXPECT_TRUE(it3->first == \"building\" && it3->second == \"hospital\");\n  EXPECT_FALSE(value_data2.first.empty());\n  EXPECT_FALSE(value_data2.second.empty());\n  auto it4 = m.insert_unique(std::move(value_data3));\n  EXPECT_TRUE(it4->first == \"animal\" && it4->second == \"tiger\");\n  EXPECT_TRUE(value_data3.first.empty());\n  EXPECT_TRUE(value_data3.second.empty());\n  EXPECT_TRUE(m.contains(\"job\"));\n  EXPECT_TRUE(m.contains(\"road\"));\n  EXPECT_TRUE(m.contains(\"building\"));\n  EXPECT_TRUE(m.contains(\"animal\"));\n  auto it5 = m.emplace_unique(\"number\", \"111\", 2);\n  EXPECT_TRUE(it5->first == \"number\" && it5->second == \"11\");\n  EXPECT_TRUE(m[\"number\"] == \"11\");\n  String key_body = \"body\";\n  auto it6 = m.emplace_unique(key_body, \"hand\");\n  EXPECT_TRUE(it6->first == key_body && it6->second == \"hand\");\n  EXPECT_TRUE(!key_body.empty());\n  EXPECT_TRUE(m[\"body\"] == \"hand\");\n  auto it7 = m.emplace_unique(std::piecewise_construct,\n                              std::forward_as_tuple(\"letter\"),\n                              std::forward_as_tuple(5, 'X'));\n  EXPECT_TRUE(it7->first == \"letter\" && it7->second == \"XXXXX\");\n  EXPECT_TRUE(m[\"letter\"] == \"XXXXX\");\n}\n\nTEST(MapStringTest, LinearEraseOperationsBaseStringKey) {\n  LinearFlatMap<String, std::string> m{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_EQ(m.size(), 3);\n  EXPECT_EQ(m[\"A\"], \"1\");\n  EXPECT_EQ(m[\"B\"], \"2\");\n  EXPECT_EQ(m[\"C\"], \"3\");\n\n  size_t cnt = m.erase(\"B\");\n  EXPECT_EQ(cnt, 1);\n  EXPECT_EQ(m.size(), 2);\n  EXPECT_FALSE(m.contains(\"B\"));\n\n  auto it = m.find(\"A\");\n  m.erase(it);\n  EXPECT_EQ(m.size(), 1);\n\n  EXPECT_EQ(m.erase(\"X\"), 0);\n}\n\nTEST(SetStringTest, LinearIteratorsBaseStringKey) {\n  InlineLinearFlatSet<String, 10> s{\"a\", \"z\", \"c\", \"b\", \"m\", \"g\", \"q\", \"h\"};\n  std::string order;\n  for (auto it = s.begin(); it != s.end(); it++) {\n    order += (*it).str();\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"abcghmqz\" : \"azcbmgqh\");\n\n  order = \"\";\n  for (auto it = s.rbegin(); it != s.rend(); it++) {\n    order += (*it).str();\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"zqmhgcba\" : \"hqgmbcza\");\n}\n\nTEST(SetStringTest, LinearBasicBaseStringKey) {\n  InlineLinearFlatSet<String, 10> s{\"a\", \"z\", \"c\", \"b\", \"m\", \"g\", \"q\", \"h\"};\n  EXPECT_TRUE(s.is_static_buffer());\n  EXPECT_TRUE(s.contains(\"a\"));\n  EXPECT_FALSE(s.contains(\"y\"));\n  EXPECT_TRUE(s.find(\"c\") != s.end());\n  EXPECT_TRUE(s.find(\"y\") == s.end());\n  EXPECT_TRUE(s.count(\"q\") == 1);\n  EXPECT_TRUE(s.count(\"y\") == 0);\n  EXPECT_EQ(s.erase(\"y\"), 0);\n  EXPECT_EQ(s.size(), 8);\n  EXPECT_EQ(s.erase(\"z\"), 1);\n  EXPECT_EQ(s.size(), 7);\n  EXPECT_FALSE(s.contains(\"z\"));\n  auto it = s.erase(s.find(\"g\"));\n  EXPECT_EQ(s.size(), 6);\n  EXPECT_EQ(*it, s.is_data_ordered() ? \"h\" : \"q\");\n}\n\nTEST(MapStringTest, LinearIteratorsBaseStringKey) {\n  LinearFlatMap<String, std::string> m{{\"Z\", \"26\"}, {\"A\", \"1\"}, {\"M\", \"13\"}};\n\n  auto it = m.begin();\n  EXPECT_EQ(it->first, m.is_data_ordered() ? \"A\" : \"Z\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? \"M\" : \"A\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? \"Z\" : \"M\");\n  ++it;\n  EXPECT_EQ(it, m.end());\n\n  auto rit = m.rbegin();\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? \"Z\" : \"M\");\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? \"M\" : \"A\");\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? \"A\" : \"Z\");\n  ++rit;\n  EXPECT_EQ(rit, m.rend());\n}\n\nTEST(MapStringTest, LinearEdgeCasesBaseStringKey) {\n  LinearFlatMap<String, std::string> m;\n\n  m[\"\"] = \"empty_key\";\n  m.emplace(\"empty_value\", \"\");\n  EXPECT_EQ(m[\"\"], \"empty_key\");\n  EXPECT_EQ(m[\"empty_value\"], \"\");\n\n  std::string big_key(1000, 'K');\n  std::string big_value(10000, 'V');\n  m[big_key] = big_value;\n  EXPECT_EQ(m[big_key].size(), 10000);\n}\n\nTEST(MapStringTest, LinearInsertOrAssignBaseStringKey) {\n  LinearFlatMap<String, std::string> m;\n\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"banana\");\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"banana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.insert_or_assign(\"empty\", \"\");\n  EXPECT_EQ(m[\"empty\"], \"\");\n\n  auto [it, _] = m.insert_or_assign(\"new_key\", \"value\");\n  EXPECT_EQ(it->first, \"new_key\");\n}\n\nTEST(MapStringTest, LinearEmplaceOrAssignBaseStringKey) {\n  LinearFlatMap<String, std::string> m;\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(\"fruit\", \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(\"fruit\", \"banana\", 4);\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"bana\");\n    EXPECT_EQ(m[\"fruit\"], \"bana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.emplace_or_assign(\"empty\", \"\");\n  EXPECT_EQ(m[\"empty\"], \"\");\n\n  auto [it, _] = m.emplace_or_assign(\"new_key\", \"value\");\n  EXPECT_EQ(it->first, \"new_key\");\n}\n\nTEST(MapStringTest, LinearEmplacePiecewiseBaseStringKey) {\n  LinearFlatMap<String, std::string> m;\n\n  auto emp_it =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(5, 'X'));\n  ASSERT_TRUE(emp_it.second);\n  EXPECT_EQ(emp_it.first->second, \"XXXXX\");\n\n  m.emplace(std::piecewise_construct, std::forward_as_tuple(\"KKKKK\", 3),\n            std::forward_as_tuple(3, 'k'));\n  EXPECT_EQ(m[\"KKK\"], \"kkk\");\n\n  auto emp_fail =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(\"new_value\"));\n  EXPECT_FALSE(emp_fail.second);\n  EXPECT_EQ(m[\"piece_key\"], \"XXXXX\");\n}\n\nTEST(MapStringTest, LinearMixedInlineSizeBaseStringKey) {\n  LinearFlatMap<String, std::string> m_src{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_TRUE(AssertMapContent_ABC_123(m_src));\n  InlineLinearFlatMap<String, std::string, 3> m_src2{\n      {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_TRUE(AssertMapContent_ABC_123(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatMap<String, std::string> m1(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatMap<String, std::string> m2(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatMap<String, std::string, 2> m3(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatMap<String, std::string, 2> m4(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatMap<String, std::string, 5> m5(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatMap<String, std::string, 5> m6(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatMap<String, std::string> m7{{\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m7));\n\n  InlineLinearFlatMap<String, std::string, 3> m8{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatMap<String, std::string, 2> m9{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatMap<String, std::string, 5> m10{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatMap<String, std::string> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatMap<String, std::string, 3> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatMap<String, std::string, 2> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatMap<String, std::string, 5> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatMap<String, std::string> m15{{\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatMap<String, std::string, 3> m16{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatMap<String, std::string, 2> m17{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(SetStringTest, LinearEmplaceBaseStringKey) {\n  LinearFlatSet<String> s;\n  s.emplace(\"ABC\", 2);\n  s.emplace(\"D\");\n  s.insert(\"AB\");\n  EXPECT_EQ(s.size(), 2);\n  EXPECT_TRUE(s.contains(\"AB\"));\n  EXPECT_TRUE(s.contains(\"D\"));\n}\n\nTEST(SetStringTest, LinearMixedInlineSizeBaseStringKey) {\n  LinearFlatSet<String> m_src{\"A\", \"B\", \"C\"};\n  EXPECT_TRUE(AssertSetContent_ABC(m_src));\n  InlineLinearFlatSet<String, 3> m_src2{\"A\", \"B\", \"C\"};\n  EXPECT_TRUE(AssertSetContent_ABC(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatSet<String> m1(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatSet<String> m2(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatSet<String, 2> m3(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatSet<String, 2> m4(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatSet<String, 5> m5(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatSet<String, 5> m6(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatSet<String> m7{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m7));\n\n  InlineLinearFlatSet<String, 3> m8{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatSet<String, 2> m9{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatSet<String, 5> m10{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatSet<String> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatSet<String, 3> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatSet<String, 2> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatSet<String, 5> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatSet<String> m15{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatSet<String, 3> m16{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatSet<String, 2> m17{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(LinearMap, FromSourceArrayBaseStringKey) {\n  {\n    Vector<std::pair<String, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    LinearFlatMap<String, std::string, void> map(std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n    EXPECT_TRUE(source_array.empty());\n  }\n\n  {\n    InlineVector<std::pair<String, std::string>, 5> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    LinearFlatMap<String, std::string, void> map(std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<String, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<String, std::string, 2, void> map(\n        std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<String, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<String, std::string, 5, void> map(\n        std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    InlineVector<std::pair<String, std::string>, 5> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<String, std::string, 5, void> map(\n        std::move(source_array));\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<String, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    LinearFlatMap<String, std::string, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n    EXPECT_TRUE(source_array.empty());\n  }\n\n  {\n    InlineVector<std::pair<String, std::string>, 5> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    LinearFlatMap<String, std::string, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<String, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<String, std::string, 2, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    Vector<std::pair<String, std::string>> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<String, std::string, 5, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n\n  {\n    InlineVector<std::pair<String, std::string>, 5> source_array{\n        {\"z\", \"Z\"}, {\"a\", \"A\"}, {\"e\", \"E\"}};\n    InlineLinearFlatMap<String, std::string, 5, void> map;\n    map = std::move(source_array);\n    EXPECT_EQ(map.size(), 3);\n    EXPECT_EQ(map[\"z\"], \"Z\");\n    EXPECT_EQ(map[\"a\"], \"A\");\n    EXPECT_EQ(map[\"e\"], \"E\");\n  }\n}\n\nTEST(LinearSet, FromSourceArrayBaseStringKey) {\n  {\n    Vector<String> source_array{\"z\", \"a\", \"e\"};\n    LinearFlatSet<String, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n    EXPECT_TRUE(source_array.empty());\n  }\n\n  {\n    InlineVector<String, 5> source_array{\"z\", \"a\", \"e\"};\n    LinearFlatSet<String, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<String> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<String, 2, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<String> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<String, 5, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    InlineVector<String, 5> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<String, 5, void> set(std::move(source_array));\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<String> source_array{\"z\", \"a\", \"e\"};\n    LinearFlatSet<String, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n    EXPECT_TRUE(source_array.empty());\n  }\n\n  {\n    InlineVector<String, 5> source_array{\"z\", \"a\", \"e\"};\n    LinearFlatSet<String, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<String> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<String, 2, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    Vector<String> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<String, 5, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n\n  {\n    InlineVector<String, 5> source_array{\"z\", \"a\", \"e\"};\n    InlineLinearFlatSet<String, 5, void> set;\n    set = std::move(source_array);\n    EXPECT_EQ(set.size(), 3);\n    EXPECT_TRUE(set.contains(\"z\"));\n    EXPECT_TRUE(set.contains(\"a\"));\n    EXPECT_TRUE(set.contains(\"e\"));\n  }\n}\n\nTEST(LinearMap, SwapBaseStringKey) {\n  {\n    LinearFlatMap<String, std::string> m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string> m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 2> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string> m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string> m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string> m2{{\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string> m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 2> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string> m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 5> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 5> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n}\n\nTEST(LinearSet, SwapBaseStringKey) {\n  {\n    LinearFlatSet<String> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 2> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    InlineLinearFlatSet<String, 3> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 2> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 5> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    InlineLinearFlatSet<String, 3> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 5> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n}\n\nTEST(LinearMap, MergeBaseStringKey) {\n  {\n    LinearFlatMap<String, std::string> m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatMap<String, std::string> m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string> m2{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    LinearFlatMap<String, std::string> m1{{\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string> m2{\n        {\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_AC_1030(m2));\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 3> m2{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 4> m2{\n        {\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_AC_1030(m2));\n  }\n}\n\nTEST(LinearSet, MergeBaseStringKey) {\n  {\n    LinearFlatSet<String> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatSet<String> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String> m2{\"A\", \"B\", \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    LinearFlatSet<String> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String> m2{\"A\", \"b\", \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AC(m2));\n  }\n\n  {\n    InlineLinearFlatSet<String, 3> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatSet<String, 3> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 3> m2{\"A\", \"B\", \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    InlineLinearFlatSet<String, 3> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 4> m2{\"A\", \"b\", \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AC(m2));\n  }\n}\n\nTEST(Vector, LinearMapInsertOrAssignBaseStringKeyWithPolicy) {\n  InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> map{\n      {\"3\", \"c\"}, {\"2\", \"b\"}, {\"1\", \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map.capacity(), 5);\n  EXPECT_EQ(map[\"1\"], \"a\");\n  EXPECT_EQ(map[\"2\"], \"b\");\n  EXPECT_EQ(map[\"3\"], \"c\");\n  EXPECT_EQ(map[\"4\"], \"\");\n\n  auto r = map.insert_or_assign(\"4\", \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[\"4\"], \"d\");\n\n  String s5(\"5\");\n  std::string se = \"e\";\n  auto r2 = map.insert_or_assign(s5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[\"5\"], \"e\");\n  EXPECT_EQ(s5, \"5\");\n  EXPECT_TRUE(se.empty());\n\n  String s6(\"6\");\n  std::string sf = \"f\";\n  auto r3 = map.insert_or_assign(std::move(s6), std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[\"6\"], \"f\");\n  EXPECT_TRUE(s6.empty());\n  EXPECT_TRUE(sf.empty());\n\n  String s7(\"7\");\n  std::string sg = \"g\";\n  auto r4 = map.insert_or_assign(std::move(s7), sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[\"7\"], \"g\");\n  EXPECT_TRUE(s7.empty());\n  EXPECT_EQ(sg, \"g\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearMapEmplaceOrAssignBaseStringKeyWithPolicy) {\n  InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> map{\n      {\"3\", \"c\"}, {\"2\", \"b\"}, {\"1\", \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[\"1\"], \"a\");\n  EXPECT_EQ(map[\"2\"], \"b\");\n  EXPECT_EQ(map[\"3\"], \"c\");\n  EXPECT_EQ(map[\"4\"], \"\");\n\n  auto r = map.emplace_or_assign(\"4\", \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[\"4\"], \"d\");\n\n  String s5(\"5\");\n  std::string se = \"e\";\n  auto r2 = map.emplace_or_assign(s5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[\"5\"], \"e\");\n  EXPECT_EQ(s5, \"5\");\n  EXPECT_TRUE(se.empty());\n\n  String s6(\"6\");\n  std::string sf = \"f\";\n  auto r3 = map.emplace_or_assign(std::move(s6), std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[\"6\"], \"f\");\n  EXPECT_TRUE(s6.empty());\n  EXPECT_TRUE(sf.empty());\n\n  String s7(\"7\");\n  std::string sg = \"g\";\n  auto r4 = map.emplace_or_assign(std::move(s7), sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[\"7\"], \"g\");\n  EXPECT_TRUE(s7.empty());\n  EXPECT_EQ(sg, \"g\");\n\n  auto r5 = map.emplace_or_assign(\"7\", 5, 'g');\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(map[\"7\"], \"ggggg\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearSetInsertBaseStringKeyWithPolicy) {\n  LinearFlatSet<String, KeyPolicy<String>> set{\"1\", \"2\", \"3\"};\n  auto r = set.insert(\"3\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(set.size(), 3);\n\n  auto r2 = set.insert(\"4\");\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(set.size(), 4);\n\n  std::string s5 = \"5\";\n  auto r3 = set.insert(std::move(s5));\n  EXPECT_TRUE(r3.second);\n  EXPECT_TRUE(s5.empty());\n\n  EXPECT_EQ(set.size(), 5);\n  EXPECT_TRUE(set.contains(\"1\"));\n  EXPECT_TRUE(set.contains(\"2\"));\n  EXPECT_TRUE(set.contains(\"3\"));\n  EXPECT_TRUE(set.contains(\"4\"));\n  EXPECT_TRUE(set.contains(\"5\"));\n  EXPECT_FALSE(set.contains(\"6\"));\n}\n\nTEST(Vector, LinearSetInsertUniqueBaseStringKeyWithPolicy) {\n  LinearFlatSet<String, KeyPolicy<String>> set;\n  set.insert_unique(\"1\");\n  set.insert_unique(\"3\");\n  const String two(\"2\");\n  set.insert_unique(two);\n  set.insert_unique(\"6\");\n  auto it = set.insert_unique(\"0\");\n  EXPECT_EQ(set.size(), 5);\n  EXPECT_TRUE(set.contains(\"1\"));\n  EXPECT_TRUE(set.contains(\"3\"));\n  EXPECT_TRUE(set.contains(\"2\"));\n  EXPECT_TRUE(set.contains(\"6\"));\n  EXPECT_TRUE(set.contains(\"0\"));\n  EXPECT_TRUE(*it == \"0\");\n}\n\nTEST(Vector, LinearMapEmplaceBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> map;\n  auto r = map.emplace(std::piecewise_construct,\n                       std::tuple<const char*, size_t>(\"123\", 2),\n                       std::tuple<const char*, size_t>(\"abc\", 2));\n  EXPECT_TRUE(r.second);\n  EXPECT_EQ(r.first->first, \"12\");\n  EXPECT_EQ(r.first->second, \"ab\");\n  auto r2 = map.emplace(std::piecewise_construct,\n                        std::tuple<const char*, size_t>(\"112\", 2),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(r2.first->first, \"11\");\n  EXPECT_EQ(r2.first->second, \"xy\");\n\n  EXPECT_EQ(map.size(), 2);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n\n  auto r3 = map.emplace(std::piecewise_construct, std::forward_as_tuple(\"12\"),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, \"12\");\n  EXPECT_EQ(r3.first->second, \"ab\");\n\n  EXPECT_EQ(map.size(), 2);\n\n  auto r4 = map.try_emplace(\"11\", \"ab\");\n  EXPECT_FALSE(r4.second);\n  EXPECT_EQ(r4.first->first, \"11\");\n  EXPECT_EQ(r4.first->second, \"xy\");\n\n  String s11(\"11\");\n  std::string sXYZ = \"xyz\";\n  auto r5 = map.try_emplace(std::move(s11), std::move(sXYZ));\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(r5.first->first, \"11\");\n  EXPECT_EQ(r5.first->second, \"xy\");\n  EXPECT_EQ(s11, \"11\");\n  EXPECT_EQ(sXYZ, \"xyz\");\n\n  std::string s13 = \"13\";\n  auto r6 = map.try_emplace(std::move(s13), std::move(sXYZ));\n  EXPECT_TRUE(r6.second);\n  EXPECT_EQ(r6.first->first, \"13\");\n  EXPECT_EQ(r6.first->second, \"xyz\");\n  EXPECT_TRUE(s13.empty());\n  EXPECT_TRUE(sXYZ.empty());\n\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n\n  std::string s14 = \"14\";\n  std::string sUVW = \"uvw\";\n  auto r7 = map.try_emplace(s14, sUVW);\n  EXPECT_TRUE(r7.second);\n  EXPECT_EQ(r7.first->first, \"14\");\n  EXPECT_EQ(r7.first->second, \"uvw\");\n  EXPECT_EQ(s14, \"14\");\n  EXPECT_EQ(sUVW, \"uvw\");\n\n  EXPECT_EQ(map.size(), 4);\n  EXPECT_EQ(map[\"12\"], \"ab\");\n  EXPECT_EQ(map[\"11\"], \"xy\");\n  EXPECT_EQ(map[\"13\"], \"xyz\");\n  EXPECT_EQ(map[\"14\"], \"uvw\");\n}\n\nTEST(MapStringTest, LinearBasicOperationsBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m;\n\n  EXPECT_TRUE(m.empty());\n  EXPECT_EQ(m.size(), 0);\n\n  auto ret = m.insert({\"apple\", \"red\"});\n  ASSERT_TRUE(ret.second);\n  EXPECT_EQ(ret.first->first, \"apple\");\n  EXPECT_EQ(ret.first->second, \"red\");\n  EXPECT_EQ(m.size(), 1);\n}\n\nTEST(MapStringTest, LinearElementAccessBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m{{\"apple\", \"red\"},\n                                                          {\"banana\", \"yellow\"}};\n\n  EXPECT_EQ(m[\"apple\"], \"red\");\n\n  m[\"apple\"] = \"green\";\n  EXPECT_EQ(m[\"apple\"], \"green\");\n  EXPECT_EQ(m.at(\"apple\"), \"green\");\n\n  EXPECT_EQ(m[\"grape\"], \"\");\n  EXPECT_EQ(m.at(\"grape\"), \"\");\n  EXPECT_EQ(m.size(), 3);\n\n  EXPECT_EQ(m.at(\"orange\"), \"\");\n  m.at(\"orange\") = \"orange\";\n  EXPECT_EQ(m[\"orange\"], \"orange\");\n  EXPECT_EQ(m.size(), 4);\n\n  std::string melon = \"melon\";\n  m.at(std::move(melon)) = \"green\";\n  EXPECT_EQ(melon, \"\");\n  EXPECT_EQ(m.find(\"melon\")->second, \"green\");\n  EXPECT_EQ(m.size(), 5);\n\n  std::string pear = \"pear\";\n  auto r = m.insert_default_if_absent(std::move(pear));\n  EXPECT_TRUE(r.second);\n  r.first->second = \"yellow\";\n  EXPECT_EQ(pear, \"\");\n  EXPECT_EQ(m[\"pear\"], \"yellow\");\n  EXPECT_EQ(m.size(), 6);\n\n  auto r2 = m.insert_default_if_absent(\"apple\");\n  EXPECT_FALSE(r2.second);\n  EXPECT_EQ(m[\"apple\"], \"green\");\n  r2.first->second = \"red\";\n  EXPECT_EQ(m[\"apple\"], \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string black = \"black\";\n  auto r3 = m.insert_if_absent(\"apple\", black);\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, \"apple\");\n  EXPECT_EQ(r3.first->second, \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string peach = \"peach\";\n  std::string pink = \"pink\";\n  auto r4 = m.insert_if_absent(std::move(peach), std::move(pink));\n  EXPECT_TRUE(r4.second);\n  EXPECT_TRUE(peach.empty());\n  EXPECT_TRUE(pink.empty());\n  EXPECT_EQ(r4.first->first, \"peach\");\n  EXPECT_EQ(r4.first->second, \"pink\");\n  EXPECT_EQ(m.size(), 7);\n\n  auto r5 = m.insert_if_absent(\"tomato\", black);\n  EXPECT_TRUE(r5.second);\n  EXPECT_EQ(r5.first->first, \"tomato\");\n  EXPECT_EQ(r5.first->second, \"black\");\n  EXPECT_EQ(black, \"black\");\n  EXPECT_EQ(m.size(), 8);\n}\n\nTEST(MapStringTest, LinearInsertUpdateBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m;\n\n  auto ret1 = m.insert({\"fruit\", \"apple\"});\n  EXPECT_TRUE(ret1.second);\n  auto ret2 = m.insert({\"fruit\", \"banana\"});\n  EXPECT_FALSE(ret2.second);\n  EXPECT_EQ(ret2.first->second, \"apple\");\n\n  auto emp_ret = m.emplace(\"color\", \"blue\");\n  EXPECT_TRUE(emp_ret.second);\n  EXPECT_EQ(emp_ret.first->first, \"color\");\n\n  m[\"color\"] = \"red\";\n  EXPECT_EQ(m[\"color\"], \"red\");\n\n  std::pair<const String, std::string> value_type{String(\"vehicle\"), \"car\"};\n  auto ret3 = m.insert(value_type);\n  EXPECT_TRUE(ret3.second);\n  EXPECT_TRUE(!value_type.first.empty());\n  auto ret4 = m.insert(std::move(value_type));\n  EXPECT_FALSE(ret4.second);\n  EXPECT_TRUE(!value_type.second.empty());\n\n  m.erase(\"vehicle\");\n  auto ret5 = m.insert(std::move(value_type));\n  EXPECT_TRUE(ret5.second);\n  EXPECT_TRUE(!value_type.first.empty());\n  EXPECT_TRUE(value_type.second.empty());\n\n  std::pair<const String, std::string> value_data0{\"job\", \"doctor\"};\n  std::pair<const String, std::string> value_data1{\"road\", \"highway\"};\n  std::pair<String, std::string> value_data2{\"building\", \"hospital\"};\n  std::pair<String, std::string> value_data3{\"animal\", \"tiger\"};\n  auto it = m.insert_unique(value_data0);\n  EXPECT_TRUE(it->first == \"job\" && it->second == \"doctor\");\n  EXPECT_FALSE(value_data0.first.empty());\n  EXPECT_FALSE(value_data0.second.empty());\n  auto it2 = m.insert_unique(std::move(value_data1));\n  EXPECT_TRUE(it2->first == \"road\" && it2->second == \"highway\");\n  EXPECT_FALSE(value_data1.first.empty());\n  EXPECT_TRUE(value_data1.second.empty());\n  auto it3 = m.insert_unique(value_data2);\n  EXPECT_TRUE(it3->first == \"building\" && it3->second == \"hospital\");\n  EXPECT_FALSE(value_data2.first.empty());\n  EXPECT_FALSE(value_data2.second.empty());\n  auto it4 = m.insert_unique(std::move(value_data3));\n  EXPECT_TRUE(it4->first == \"animal\" && it4->second == \"tiger\");\n  EXPECT_TRUE(value_data3.first.empty());\n  EXPECT_TRUE(value_data3.second.empty());\n  EXPECT_TRUE(m.contains(\"job\"));\n  EXPECT_TRUE(m.contains(\"road\"));\n  EXPECT_TRUE(m.contains(\"building\"));\n  EXPECT_TRUE(m.contains(\"animal\"));\n  auto it5 = m.emplace_unique(\"number\", \"111\", 2);\n  EXPECT_TRUE(it5->first == \"number\" && it5->second == \"11\");\n  EXPECT_TRUE(m[\"number\"] == \"11\");\n  String key_body = \"body\";\n  auto it6 = m.emplace_unique(key_body, \"hand\");\n  EXPECT_TRUE(it6->first == key_body && it6->second == \"hand\");\n  EXPECT_TRUE(!key_body.empty());\n  EXPECT_TRUE(m[\"body\"] == \"hand\");\n  auto it7 = m.emplace_unique(std::piecewise_construct,\n                              std::forward_as_tuple(\"letter\"),\n                              std::forward_as_tuple(5, 'X'));\n  EXPECT_TRUE(it7->first == \"letter\" && it7->second == \"XXXXX\");\n  EXPECT_TRUE(m[\"letter\"] == \"XXXXX\");\n}\n\nTEST(MapStringTest, LinearEraseOperationsBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m{\n      {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_EQ(m.size(), 3);\n  EXPECT_EQ(m[\"A\"], \"1\");\n  EXPECT_EQ(m[\"B\"], \"2\");\n  EXPECT_EQ(m[\"C\"], \"3\");\n\n  size_t cnt = m.erase(\"B\");\n  EXPECT_EQ(cnt, 1);\n  EXPECT_EQ(m.size(), 2);\n  EXPECT_FALSE(m.contains(\"B\"));\n\n  auto it = m.find(\"A\");\n  m.erase(it);\n  EXPECT_EQ(m.size(), 1);\n\n  EXPECT_EQ(m.erase(\"X\"), 0);\n}\n\nTEST(SetStringTest, LinearIteratorsBaseStringKeyWithPolicy) {\n  InlineLinearFlatSet<String, 10, KeyPolicy<String>> s{\"a\", \"z\", \"c\", \"b\",\n                                                       \"m\", \"g\", \"q\", \"h\"};\n  std::string order;\n  for (auto it = s.begin(); it != s.end(); it++) {\n    order += (*it).str();\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"abcghmqz\" : \"azcbmgqh\");\n\n  order = \"\";\n  for (auto it = s.rbegin(); it != s.rend(); it++) {\n    order += (*it).str();\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"zqmhgcba\" : \"hqgmbcza\");\n}\n\nTEST(SetStringTest, LinearBasicBaseStringKeyWithPolicy) {\n  InlineLinearFlatSet<String, 10, KeyPolicy<String>> s{\"a\", \"z\", \"c\", \"b\",\n                                                       \"m\", \"g\", \"q\", \"h\"};\n  EXPECT_TRUE(s.is_static_buffer());\n  EXPECT_TRUE(s.contains(\"a\"));\n  EXPECT_FALSE(s.contains(\"y\"));\n  EXPECT_TRUE(s.find(\"c\") != s.end());\n  EXPECT_TRUE(s.find(\"y\") == s.end());\n  EXPECT_TRUE(s.count(\"q\") == 1);\n  EXPECT_TRUE(s.count(\"y\") == 0);\n  EXPECT_EQ(s.erase(\"y\"), 0);\n  EXPECT_EQ(s.size(), 8);\n  EXPECT_EQ(s.erase(\"z\"), 1);\n  EXPECT_EQ(s.size(), 7);\n  EXPECT_FALSE(s.contains(\"z\"));\n  auto it = s.erase(s.find(\"g\"));\n  EXPECT_EQ(s.size(), 6);\n  EXPECT_EQ(*it, s.is_data_ordered() ? \"h\" : \"q\");\n}\n\nTEST(MapStringTest, LinearIteratorsBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m{\n      {\"Z\", \"26\"}, {\"A\", \"1\"}, {\"M\", \"13\"}};\n\n  auto it = m.begin();\n  EXPECT_EQ(it->first, m.is_data_ordered() ? \"A\" : \"Z\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? \"M\" : \"A\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? \"Z\" : \"M\");\n  ++it;\n  EXPECT_EQ(it, m.end());\n\n  auto rit = m.rbegin();\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? \"Z\" : \"M\");\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? \"M\" : \"A\");\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? \"A\" : \"Z\");\n  ++rit;\n  EXPECT_EQ(rit, m.rend());\n}\n\nTEST(MapStringTest, LinearEdgeCasesBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m;\n\n  m[\"\"] = \"empty_key\";\n  m.emplace(\"empty_value\", \"\");\n  EXPECT_EQ(m[\"\"], \"empty_key\");\n  EXPECT_EQ(m[\"empty_value\"], \"\");\n\n  std::string big_key(1000, 'K');\n  std::string big_value(10000, 'V');\n  m[big_key] = big_value;\n  EXPECT_EQ(m[big_key].size(), 10000);\n}\n\nTEST(MapStringTest, LinearInsertOrAssignBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m;\n\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.insert_or_assign(\"fruit\", \"banana\");\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"banana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.insert_or_assign(\"empty\", \"\");\n  EXPECT_EQ(m[\"empty\"], \"\");\n\n  auto [it, _] = m.insert_or_assign(\"new_key\", \"value\");\n  EXPECT_EQ(it->first, \"new_key\");\n}\n\nTEST(MapStringTest, LinearEmplaceOrAssignBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m;\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(\"fruit\", \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(\"fruit\", \"banana\", 4);\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"bana\");\n    EXPECT_EQ(m[\"fruit\"], \"bana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.emplace_or_assign(\"empty\", \"\");\n  EXPECT_EQ(m[\"empty\"], \"\");\n\n  auto [it, _] = m.emplace_or_assign(\"new_key\", \"value\");\n  EXPECT_EQ(it->first, \"new_key\");\n}\n\nTEST(MapStringTest, LinearEmplacePiecewiseBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m;\n\n  auto emp_it =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(5, 'X'));\n  ASSERT_TRUE(emp_it.second);\n  EXPECT_EQ(emp_it.first->second, \"XXXXX\");\n\n  m.emplace(std::piecewise_construct, std::forward_as_tuple(\"KKKKK\", 3),\n            std::forward_as_tuple(3, 'k'));\n  EXPECT_EQ(m[\"KKK\"], \"kkk\");\n\n  auto emp_fail =\n      m.emplace(std::piecewise_construct, std::forward_as_tuple(\"piece_key\"),\n                std::forward_as_tuple(\"new_value\"));\n  EXPECT_FALSE(emp_fail.second);\n  EXPECT_EQ(m[\"piece_key\"], \"XXXXX\");\n}\n\nTEST(MapStringTest, LinearMixedInlineSizeBaseStringKeyWithPolicy) {\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m_src{\n      {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_TRUE(AssertMapContent_ABC_123(m_src));\n  InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m_src2{\n      {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n  EXPECT_TRUE(AssertMapContent_ABC_123(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m1(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m2(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatMap<String, std::string, 2, KeyPolicy<String>> m3(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatMap<String, std::string, 2, KeyPolicy<String>> m4(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> m5(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> m6(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m7{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m7));\n\n  InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m8{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatMap<String, std::string, 2, KeyPolicy<String>> m9{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> m10{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m12(\n      std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatMap<String, std::string, 2, KeyPolicy<String>> m13(\n      std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> m14(\n      std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatMap<String, std::string, KeyPolicy<String>> m15{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m16{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatMap<String, std::string, 2, KeyPolicy<String>> m17{\n      {\"A\", \"11\"}, {\"B\", \"22\"}, {\"C\", \"33\"}};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertMapContent_ABC_123(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(SetStringTest, LinearEmplaceBaseStringKeyWithPolicy) {\n  LinearFlatSet<String, KeyPolicy<String>> s;\n  s.emplace(\"ABC\", 2);\n  s.emplace(\"D\");\n  s.insert(\"AB\");\n  EXPECT_EQ(s.size(), 2);\n  EXPECT_TRUE(s.contains(\"AB\"));\n  EXPECT_TRUE(s.contains(\"D\"));\n}\n\nTEST(SetStringTest, LinearMixedInlineSizeBaseStringKeyWithPolicy) {\n  LinearFlatSet<String, KeyPolicy<String>> m_src{\"A\", \"B\", \"C\"};\n  EXPECT_TRUE(AssertSetContent_ABC(m_src));\n  InlineLinearFlatSet<String, 3, KeyPolicy<String>> m_src2{\"A\", \"B\", \"C\"};\n  EXPECT_TRUE(AssertSetContent_ABC(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatSet<String, KeyPolicy<String>> m1(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatSet<String, KeyPolicy<String>> m2(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatSet<String, 2, KeyPolicy<String>> m3(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatSet<String, 2, KeyPolicy<String>> m4(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatSet<String, 5, KeyPolicy<String>> m5(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatSet<String, 5, KeyPolicy<String>> m6(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatSet<String, KeyPolicy<String>> m7{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m7));\n\n  InlineLinearFlatSet<String, 3, KeyPolicy<String>> m8{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatSet<String, 2, KeyPolicy<String>> m9{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatSet<String, 5, KeyPolicy<String>> m10{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatSet<String, KeyPolicy<String>> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatSet<String, 3, KeyPolicy<String>> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatSet<String, 2, KeyPolicy<String>> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatSet<String, 5, KeyPolicy<String>> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatSet<String, KeyPolicy<String>> m15{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatSet<String, 3, KeyPolicy<String>> m16{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertSetContent_ABC(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatSet<String, 2, KeyPolicy<String>> m17{\"a\", \"b\", \"c\"};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertSetContent_ABC(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(LinearMap, SwapBaseStringKeyWithPolicy) {\n  {\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 2, KeyPolicy<String>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 2, KeyPolicy<String>> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 5, KeyPolicy<String>> m2{\n        {\"a\", \"1\"}, {\"b\", \"2\"}, {\"c\", \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_abc_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_abc_123(m2));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n  }\n}\n\nTEST(LinearSet, SwapBaseStringKeyWithPolicy) {\n  {\n    LinearFlatSet<String, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String, KeyPolicy<String>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 2, KeyPolicy<String>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 5, KeyPolicy<String>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    InlineLinearFlatSet<String, 3, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 5, KeyPolicy<String>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String, KeyPolicy<String>> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 2, KeyPolicy<String>> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    LinearFlatSet<String, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 5, KeyPolicy<String>> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n\n  {\n    InlineLinearFlatSet<String, 3, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 5, KeyPolicy<String>> m2{\"a\", \"b\", \"c\"};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_abc(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_abc(m2));\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n  }\n}\n\nTEST(LinearMap, MergeBaseStringKeyWithPolicy) {\n  {\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m2{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m2{\n        {\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_AC_1030(m2));\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    LinearFlatMap<String, std::string, KeyPolicy<String>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m2{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABC_123(m1));\n    EXPECT_TRUE(AssertMapContent_ABC_123(m2));\n  }\n\n  {\n    InlineLinearFlatMap<String, std::string, 3, KeyPolicy<String>> m1{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    InlineLinearFlatMap<String, std::string, 4, KeyPolicy<String>> m2{\n        {\"A\", \"10\"}, {\"b\", \"20\"}, {\"C\", \"30\"}, {\"D\", \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_ABCbD_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_AC_1030(m2));\n  }\n}\n\nTEST(LinearSet, MergeBaseStringKeyWithPolicy) {\n  {\n    LinearFlatSet<String, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String, KeyPolicy<String>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatSet<String, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String, KeyPolicy<String>> m2{\"A\", \"B\", \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    LinearFlatSet<String, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String, KeyPolicy<String>> m2{\"A\", \"b\", \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AC(m2));\n  }\n\n  {\n    InlineLinearFlatSet<String, 3, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    LinearFlatSet<String, KeyPolicy<String>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatSet<String, 3, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 3, KeyPolicy<String>> m2{\"A\", \"B\", \"C\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABC(m1));\n    EXPECT_TRUE(AssertSetContent_ABC(m2));\n  }\n\n  {\n    InlineLinearFlatSet<String, 3, KeyPolicy<String>> m1{\"A\", \"B\", \"C\"};\n    InlineLinearFlatSet<String, 4, KeyPolicy<String>> m2{\"A\", \"b\", \"C\", \"D\"};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_ABCbD(m1));\n    EXPECT_TRUE(AssertSetContent_AC(m2));\n  }\n}\n\nnamespace {\ntemplate <class SetType>\nbool AssertSetContent_123(const SetType& m) {\n  return m.size() == 3 && m.contains(1) && m.contains(2) && m.contains(3);\n}\n\ntemplate <class SetType>\nbool AssertSetContent_n1n2n3(const SetType& m) {\n  return m.size() == 3 && m.contains(-1) && m.contains(-2) && m.contains(-3);\n}\n\ntemplate <class SetType>\nbool AssertSetContent_123n24(SetType& m) {\n  return m.size() == 5 && m.contains(1) && m.contains(2) && m.contains(3) &&\n         m.contains(-2) && m.contains(4);\n}\n\ntemplate <class SetType>\nbool AssertMapContent_13(SetType& m) {\n  return m.size() == 2 && m.contains(1) && m.contains(3);\n}\n\ntemplate <class SetType>\nbool AssertMapContent_1n234(SetType& m) {\n  return m.size() == 4 && m.contains(1) && m.contains(-2) && m.contains(3) &&\n         m.contains(4);\n}\n\ntemplate <class MapType>\nbool AssertMapContent_123_123(MapType& m) {\n  return (m.size() == 3) && (m[1] == \"1\") && (m[2] == \"2\") && (m[3] == \"3\");\n}\n\ntemplate <class MapType>\nbool AssertMapContent_n1n2n3_123(MapType& m) {\n  return (m.size() == 3) && (m[-1] == \"1\") && (m[-2] == \"2\") && (m[-3] == \"3\");\n}\n\ntemplate <class MapType>\nbool AssertMapContent_123n24_1232040(MapType& m) {\n  return (m.size() == 5) && (m[1] == \"1\") && (m[2] == \"2\") && (m[3] == \"3\") &&\n         (m[-2] == \"20\") && (m[4] == \"40\");\n}\n\ntemplate <class MapType>\nbool AssertMapContent_123n24_102302040(MapType& m) {\n  return (m.size() == 5) && (m[1] == \"10\") && (m[2] == \"2\") && (m[3] == \"30\") &&\n         (m[-2] == \"20\") && (m[4] == \"40\");\n}\n\ntemplate <class MapType>\nbool AssertMapContent_13_1030(MapType& m) {\n  return (m.size() == 2) && (m[1] == \"10\") && (m[3] == \"30\");\n}\n\ntemplate <class MapType>\nbool AssertMapContent_1n234_10203040(MapType& m) {\n  return (m.size() == 4) && (m[1] == \"10\") && (m[-2] == \"20\") &&\n         (m[3] == \"30\") && (m[4] == \"40\");\n}\n}  // namespace\n\nTEST(Vector, LinearMapInsertOrAssignIntKey) {\n  InlineLinearFlatMap<int16_t, std::string, 5> map{\n      {3, \"c\"}, {2, \"b\"}, {1, \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map.capacity(), 5);\n  EXPECT_EQ(map[1], \"a\");\n  EXPECT_EQ(map[2], \"b\");\n  EXPECT_EQ(map[3], \"c\");\n  EXPECT_EQ(map[4], \"\");\n\n  auto r = map.insert_or_assign(4, \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[4], \"d\");\n\n  std::string se = \"e\";\n  auto r2 = map.insert_or_assign(5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[5], \"e\");\n  EXPECT_TRUE(se.empty());\n\n  std::string sf = \"f\";\n  auto r3 = map.insert_or_assign(6, std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[6], \"f\");\n  EXPECT_TRUE(sf.empty());\n\n  std::string sg = \"g\";\n  auto r4 = map.insert_or_assign(7, sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[7], \"g\");\n  EXPECT_EQ(sg, \"g\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearMapEmplaceOrAssignIntKey) {\n  InlineLinearFlatMap<int16_t, std::string, 5> map{\n      {3, \"c\"}, {2, \"b\"}, {1, \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[1], \"a\");\n  EXPECT_EQ(map[2], \"b\");\n  EXPECT_EQ(map[3], \"c\");\n  EXPECT_EQ(map[4], \"\");\n\n  auto r = map.emplace_or_assign(4, \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[4], \"d\");\n\n  std::string se = \"e\";\n  auto r2 = map.emplace_or_assign(5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[5], \"e\");\n  EXPECT_TRUE(se.empty());\n\n  std::string sf = \"f\";\n  auto r3 = map.emplace_or_assign(6, std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[6], \"f\");\n  EXPECT_TRUE(sf.empty());\n\n  std::string sg = \"g\";\n  auto r4 = map.emplace_or_assign(7, sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[7], \"g\");\n  EXPECT_EQ(sg, \"g\");\n\n  auto r5 = map.emplace_or_assign(7, 5, 'g');\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(map[7], \"ggggg\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearSetInsertIntKey) {\n  LinearFlatSet<int16_t> set{1, 2, 3};\n  auto r = set.insert(3);\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(set.size(), 3);\n\n  auto r2 = set.insert(4);\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(set.size(), 4);\n\n  auto r3 = set.insert(5);\n  EXPECT_TRUE(r3.second);\n\n  EXPECT_EQ(set.size(), 5);\n  EXPECT_TRUE(set.contains(1));\n  EXPECT_TRUE(set.contains(2));\n  EXPECT_TRUE(set.contains(3));\n  EXPECT_TRUE(set.contains(4));\n  EXPECT_TRUE(set.contains(5));\n  EXPECT_FALSE(set.contains(6));\n}\n\nTEST(Vector, LinearMapEmplaceIntKey) {\n  LinearFlatMap<int16_t, std::string> map;\n  auto r = map.emplace(std::piecewise_construct, std::tuple<int16_t>(12),\n                       std::tuple<const char*, size_t>(\"abc\", 2));\n  EXPECT_TRUE(r.second);\n  EXPECT_EQ(r.first->first, 12);\n  EXPECT_EQ(r.first->second, \"ab\");\n  auto r2 = map.emplace(std::piecewise_construct, std::tuple<int16_t>(11),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(r2.first->first, 11);\n  EXPECT_EQ(r2.first->second, \"xy\");\n\n  EXPECT_EQ(map.size(), 2);\n  EXPECT_EQ(map[12], \"ab\");\n  EXPECT_EQ(map[11], \"xy\");\n\n  auto r3 = map.emplace(std::piecewise_construct, std::forward_as_tuple(12),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, 12);\n  EXPECT_EQ(r3.first->second, \"ab\");\n\n  EXPECT_EQ(map.size(), 2);\n\n  auto r4 = map.try_emplace(11, \"ab\");\n  EXPECT_FALSE(r4.second);\n  EXPECT_EQ(r4.first->first, 11);\n  EXPECT_EQ(r4.first->second, \"xy\");\n\n  std::string sXYZ = \"xyz\";\n  auto r5 = map.try_emplace(11, std::move(sXYZ));\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(r5.first->first, 11);\n  EXPECT_EQ(r5.first->second, \"xy\");\n  EXPECT_EQ(sXYZ, \"xyz\");\n\n  auto r6 = map.try_emplace(13, std::move(sXYZ));\n  EXPECT_TRUE(r6.second);\n  EXPECT_EQ(r6.first->first, 13);\n  EXPECT_EQ(r6.first->second, \"xyz\");\n  EXPECT_TRUE(sXYZ.empty());\n\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[12], \"ab\");\n  EXPECT_EQ(map[11], \"xy\");\n  EXPECT_EQ(map[13], \"xyz\");\n\n  std::string sUVW = \"uvw\";\n  auto r7 = map.try_emplace(14, sUVW);\n  EXPECT_TRUE(r7.second);\n  EXPECT_EQ(r7.first->first, 14);\n  EXPECT_EQ(r7.first->second, \"uvw\");\n  EXPECT_EQ(sUVW, \"uvw\");\n\n  EXPECT_EQ(map.size(), 4);\n  EXPECT_EQ(map[12], \"ab\");\n  EXPECT_EQ(map[11], \"xy\");\n  EXPECT_EQ(map[13], \"xyz\");\n  EXPECT_EQ(map[14], \"uvw\");\n}\n\nTEST(MapIntTest, LinearBasicOperationsIntKey) {\n  LinearFlatMap<int8_t, std::string> m;\n\n  EXPECT_TRUE(m.empty());\n  EXPECT_EQ(m.size(), 0);\n\n  auto ret = m.insert({99, \"red\"});\n  ASSERT_TRUE(ret.second);\n  EXPECT_EQ(ret.first->first, 99);\n  EXPECT_EQ(ret.first->second, \"red\");\n  EXPECT_EQ(m.size(), 1);\n}\n\nTEST(MapIntTest, LinearElementAccessIntKey) {\n  LinearFlatMap<int32_t, std::string> m{{99, \"red\"}, {100, \"yellow\"}};\n\n  EXPECT_EQ(m[99], \"red\");\n\n  m[99] = \"green\";\n  EXPECT_EQ(m[99], \"green\");\n  EXPECT_EQ(m.at(99), \"green\");\n\n  EXPECT_EQ(m[101], \"\");\n  EXPECT_EQ(m.at(101), \"\");\n  EXPECT_EQ(m.size(), 3);\n\n  EXPECT_EQ(m.at(102), \"\");\n  m.at(102) = \"orange\";\n  EXPECT_EQ(m[102], \"orange\");\n  EXPECT_EQ(m.size(), 4);\n\n  int32_t i103 = 103;\n  m.at(std::move(i103)) = \"green\";\n  EXPECT_EQ(m.find(103)->first, 103);\n  EXPECT_EQ(m.find(103)->second, \"green\");\n  EXPECT_EQ(m.size(), 5);\n\n  int32_t i105 = 105;\n  auto r = m.insert_default_if_absent(std::move(i105));\n  EXPECT_TRUE(r.second);\n  r.first->second = \"yellow\";\n  EXPECT_EQ(m[105], \"yellow\");\n  EXPECT_EQ(m.size(), 6);\n\n  auto r2 = m.insert_default_if_absent(99);\n  EXPECT_FALSE(r2.second);\n  EXPECT_EQ(m[99], \"green\");\n  r2.first->second = \"red\";\n  EXPECT_EQ(m[99], \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string black = \"black\";\n  auto r3 = m.insert_if_absent(99, black);\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, 99);\n  EXPECT_EQ(r3.first->second, \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string pink = \"pink\";\n  auto r4 = m.insert_if_absent(200, std::move(pink));\n  EXPECT_TRUE(r4.second);\n  EXPECT_TRUE(pink.empty());\n  EXPECT_EQ(r4.first->first, 200);\n  EXPECT_EQ(r4.first->second, \"pink\");\n  EXPECT_EQ(m.size(), 7);\n\n  auto r5 = m.insert_if_absent(300, black);\n  EXPECT_TRUE(r5.second);\n  EXPECT_EQ(r5.first->first, 300);\n  EXPECT_EQ(r5.first->second, \"black\");\n  EXPECT_EQ(black, \"black\");\n  EXPECT_EQ(m.size(), 8);\n}\n\nTEST(MapIntTest, LinearInsertUpdateIntKey) {\n  LinearFlatMap<int16_t, std::string> m;\n\n  auto ret1 = m.insert({99, \"apple\"});\n  EXPECT_TRUE(ret1.second);\n  auto ret2 = m.insert({99, \"banana\"});\n  EXPECT_FALSE(ret2.second);\n  EXPECT_EQ(ret2.first->second, \"apple\");\n\n  auto emp_ret = m.emplace(100, \"blue\");\n  EXPECT_TRUE(emp_ret.second);\n  EXPECT_EQ(emp_ret.first->first, 100);\n\n  m[100] = \"red\";\n  EXPECT_EQ(m[100], \"red\");\n}\n\nTEST(MapIntTest, LinearEraseOperationsIntKey) {\n  LinearFlatMap<int16_t, std::string> m{{30, \"1\"}, {31, \"2\"}, {32, \"3\"}};\n  EXPECT_EQ(m.size(), 3);\n  EXPECT_EQ(m[30], \"1\");\n  EXPECT_EQ(m[31], \"2\");\n  EXPECT_EQ(m[32], \"3\");\n\n  size_t cnt = m.erase(31);\n  EXPECT_EQ(cnt, 1);\n  EXPECT_EQ(m.size(), 2);\n  EXPECT_FALSE(m.contains(31));\n\n  auto it = m.find(30);\n  m.erase(it);\n  EXPECT_EQ(m.size(), 1);\n  EXPECT_FALSE(m.contains(30));\n\n  EXPECT_EQ(m.erase(100), 0);\n}\n\nTEST(SetIntTest, LinearIteratorsIntKey) {\n  InlineLinearFlatSet<int32_t, 10> s{5, 4, 9, 0, 1, 8, 2, 7};\n  std::string order;\n  for (auto it = s.begin(); it != s.end(); it++) {\n    order += std::to_string((*it));\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"01245789\" : \"54901827\");\n\n  order = \"\";\n  for (auto it = s.rbegin(); it != s.rend(); it++) {\n    order += std::to_string((*it));\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"98754210\" : \"72810945\");\n}\n\nTEST(SetIntTest, LinearBasicIntKey) {\n  {\n    InlineLinearFlatSet<int8_t, 10> s{5, 4, 9, 0, 1, 8, 2, 7};\n    EXPECT_TRUE(s.is_static_buffer());\n    EXPECT_TRUE(s.contains(5));\n    EXPECT_FALSE(s.contains(3));\n    EXPECT_TRUE(s.find(9) != s.end());\n    EXPECT_TRUE(s.find(3) == s.end());\n    EXPECT_TRUE(s.count(2) == 1);\n    EXPECT_TRUE(s.count(3) == 0);\n    EXPECT_EQ(s.erase(3), 0);\n    EXPECT_EQ(s.size(), 8);\n    EXPECT_EQ(s.erase(4), 1);\n    EXPECT_EQ(s.size(), 7);\n    EXPECT_FALSE(s.contains(4));\n    auto it = s.erase(s.find(8));\n    EXPECT_EQ(s.size(), 6);\n    EXPECT_EQ(*it, s.is_data_ordered() ? 9 : 2);\n  }\n  {\n    InlineLinearFlatSet<int16_t, 10> s{5, 4, 9, 0, 1, 8, 2, 7};\n    EXPECT_TRUE(s.is_static_buffer());\n    EXPECT_TRUE(s.contains(5));\n    EXPECT_FALSE(s.contains(3));\n    EXPECT_TRUE(s.find(9) != s.end());\n    EXPECT_TRUE(s.find(3) == s.end());\n    EXPECT_TRUE(s.count(2) == 1);\n    EXPECT_TRUE(s.count(3) == 0);\n    EXPECT_EQ(s.erase(3), 0);\n    EXPECT_EQ(s.size(), 8);\n    EXPECT_EQ(s.erase(4), 1);\n    EXPECT_EQ(s.size(), 7);\n    EXPECT_FALSE(s.contains(4));\n    auto it = s.erase(s.find(8));\n    EXPECT_EQ(s.size(), 6);\n    EXPECT_EQ(*it, s.is_data_ordered() ? 9 : 2);\n  }\n  {\n    InlineLinearFlatSet<int32_t, 10> s{5, 4, 9, 0, 1, 8, 2, 7};\n    EXPECT_TRUE(s.is_static_buffer());\n    EXPECT_TRUE(s.contains(5));\n    EXPECT_FALSE(s.contains(3));\n    EXPECT_TRUE(s.find(9) != s.end());\n    EXPECT_TRUE(s.find(3) == s.end());\n    EXPECT_TRUE(s.count(2) == 1);\n    EXPECT_TRUE(s.count(3) == 0);\n    EXPECT_EQ(s.erase(3), 0);\n    EXPECT_EQ(s.size(), 8);\n    EXPECT_EQ(s.erase(4), 1);\n    EXPECT_EQ(s.size(), 7);\n    EXPECT_FALSE(s.contains(4));\n    auto it = s.erase(s.find(8));\n    EXPECT_EQ(s.size(), 6);\n    EXPECT_EQ(*it, s.is_data_ordered() ? 9 : 2);\n  }\n}\n\nTEST(MapIntTest, LinearIteratorsIntKey) {\n  LinearFlatMap<int32_t, std::string> m{{26, \"26\"}, {1, \"1\"}, {13, \"13\"}};\n\n  auto it = m.begin();\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 1 : 26);\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 13 : 1);\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 26 : 13);\n  ++it;\n  EXPECT_EQ(it, m.end());\n\n  auto rit = m.rbegin();\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? 26 : 13);\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? 13 : 1);\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? 1 : 26);\n  ++rit;\n  EXPECT_EQ(rit, m.rend());\n}\n\nTEST(MapIntTest, LinearInsertOrAssignIntKey) {\n  LinearFlatMap<int16_t, std::string> m;\n\n  {\n    auto [it, inserted] = m.insert_or_assign(10, \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.insert_or_assign(10, \"banana\");\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"banana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.insert_or_assign(11, \"11\");\n  EXPECT_EQ(m[11], \"11\");\n\n  auto [it, _] = m.insert_or_assign(12, \"orange\");\n  EXPECT_EQ(it->first, 12);\n}\n\nTEST(MapIntTest, LinearEmplaceOrAssignIntKey) {\n  LinearFlatMap<int16_t, std::string> m;\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(10, \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(10, \"banana\", 4);\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"bana\");\n    EXPECT_EQ(m[10], \"bana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.emplace_or_assign(11, \"11\");\n  EXPECT_EQ(m[11], \"11\");\n\n  auto [it, _] = m.emplace_or_assign(12, \"orange\");\n  EXPECT_EQ(it->first, 12);\n}\n\nTEST(MapIntTest, LinearEmplacePiecewiseIntKey) {\n  LinearFlatMap<uint32_t, std::string> m;\n\n  auto emp_it = m.emplace(std::piecewise_construct, std::forward_as_tuple(99u),\n                          std::forward_as_tuple(5, 'X'));\n  ASSERT_TRUE(emp_it.second);\n  EXPECT_EQ(emp_it.first->second, \"XXXXX\");\n\n  m.emplace(std::piecewise_construct, std::forward_as_tuple(199u),\n            std::forward_as_tuple(3, 'k'));\n  EXPECT_EQ(m[199], \"kkk\");\n\n  auto emp_fail = m.emplace(std::piecewise_construct, std::forward_as_tuple(99),\n                            std::forward_as_tuple(\"new_value\"));\n  EXPECT_FALSE(emp_fail.second);\n  EXPECT_EQ(m[99], \"XXXXX\");\n}\n\nTEST(MapIntTest, LinearMixedInlineSizeIntKey) {\n  LinearFlatMap<int8_t, std::string> m_src{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n  EXPECT_TRUE(AssertMapContent_123_123(m_src));\n  InlineLinearFlatMap<int8_t, std::string, 3> m_src2{\n      {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n  EXPECT_TRUE(AssertMapContent_123_123(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatMap<int8_t, std::string> m1(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatMap<int8_t, std::string> m2(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatMap<int8_t, std::string, 2> m3(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatMap<int8_t, std::string, 2> m4(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatMap<int8_t, std::string, 5> m5(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatMap<int8_t, std::string, 5> m6(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatMap<int8_t, std::string> m7{{1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m7));\n\n  InlineLinearFlatMap<int8_t, std::string, 3> m8{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatMap<int8_t, std::string, 2> m9{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatMap<int8_t, std::string, 5> m10{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatMap<int8_t, std::string> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 3> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 2> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 5> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatMap<int8_t, std::string> m15{{1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 3> m16{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 2> m17{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(SetIntTest, LinearEmplaceIntKey) {\n  LinearFlatSet<int32_t> s;\n  s.emplace(9);\n  s.emplace(8);\n  s.insert(9);\n  EXPECT_EQ(s.size(), 2);\n  EXPECT_TRUE(s.contains(9));\n  EXPECT_TRUE(s.contains(8));\n}\n\nTEST(SetIntTest, LinearMixedInlineSizeIntKey) {\n  LinearFlatSet<int16_t> m_src{1, 2, 3};\n  EXPECT_TRUE(AssertSetContent_123(m_src));\n  InlineLinearFlatSet<int16_t, 3> m_src2{1, 2, 3};\n  EXPECT_TRUE(AssertSetContent_123(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatSet<int16_t> m1(m_src);\n  EXPECT_TRUE(AssertSetContent_123(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatSet<int16_t> m2(m_src2);\n  EXPECT_TRUE(AssertSetContent_123(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatSet<int16_t, 2> m3(m_src);\n  EXPECT_TRUE(AssertSetContent_123(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatSet<int16_t, 2> m4(m_src2);\n  EXPECT_TRUE(AssertSetContent_123(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatSet<int16_t, 5> m5(m_src);\n  EXPECT_TRUE(AssertSetContent_123(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatSet<int16_t, 5> m6(m_src2);\n  EXPECT_TRUE(AssertSetContent_123(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatSet<int16_t> m7{21, 22, 23};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m7));\n\n  InlineLinearFlatSet<int16_t, 3> m8{21, 22, 23};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatSet<int16_t, 2> m9{21, 22, 23};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatSet<int16_t, 5> m10{21, 22, 23};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatSet<int16_t> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatSet<int16_t, 3> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatSet<int16_t, 2> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatSet<int16_t, 5> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatSet<int16_t> m15{21, 22, 23};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatSet<int16_t, 3> m16{21, 22, 23};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertSetContent_123(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatSet<int16_t, 2> m17{21, 22, 23};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertSetContent_123(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(LinearMap, SwapIntKey) {\n  {\n    LinearFlatMap<int32_t, std::string> m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<int32_t, std::string> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string> m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 2> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string> m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<int32_t, std::string, 3> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string> m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<int32_t, std::string> m2{{-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string> m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 2> m2{\n        {-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string> m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5> m2{\n        {-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<int32_t, std::string, 3> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5> m2{\n        {-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n}\n\nTEST(LinearSet, SwapIntKey) {\n  {\n    LinearFlatSet<int> m1{1, 2, 3};\n    LinearFlatSet<int> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 2> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    InlineLinearFlatSet<int, 3> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 5> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int> m1{1, 2, 3};\n    LinearFlatSet<int> m2{-1, -2, -3};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 2> m2{-1, -2, -3};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 5> m2{-1, -2, -3};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    InlineLinearFlatSet<int, 3> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 5> m2{-1, -2, -3};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n}\n\nTEST(LinearMap, MergeIntKey) {\n  {\n    LinearFlatMap<uint8_t, std::string> m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string> m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string> m2{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string> m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string> m2{\n        {1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_13_1030(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 3> m2{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 4> m2{\n        {1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_13_1030(m2));\n  }\n}\n\nTEST(LinearMap, MergeAssignIntKey) {\n  {\n    LinearFlatMap<uint8_t, std::string, MergeAssignKeyPolicy<uint8_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string, MergeAssignKeyPolicy<uint8_t>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string, MergeAssignKeyPolicy<uint8_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string, MergeAssignKeyPolicy<uint8_t>> m2{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string, MergeAssignKeyPolicy<uint8_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string, MergeAssignKeyPolicy<uint8_t>> m2{\n        {1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_102302040(m1));\n    EXPECT_TRUE(AssertMapContent_1n234_10203040(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3, MergeAssignKeyPolicy<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string, MergeAssignKeyPolicy<uint8_t>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3, MergeAssignKeyPolicy<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 3, MergeAssignKeyPolicy<uint8_t>>\n        m2{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3, MergeAssignKeyPolicy<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 4, MergeAssignKeyPolicy<uint8_t>>\n        m2{{1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_102302040(m1));\n    EXPECT_TRUE(AssertMapContent_1n234_10203040(m2));\n  }\n}\n\nTEST(LinearSet, MergeIntKey) {\n  {\n    LinearFlatSet<int8_t> m1{1, 2, 3};\n    LinearFlatSet<int8_t> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatSet<int8_t> m1{1, 2, 3};\n    LinearFlatSet<int8_t> m2{1, 2, 3};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n  }\n\n  {\n    LinearFlatSet<int8_t> m1{1, 2, 3};\n    LinearFlatSet<int8_t> m2{1, -2, 3, 4};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123n24(m1));\n    EXPECT_TRUE(AssertMapContent_13(m2));\n  }\n\n  {\n    InlineLinearFlatSet<int8_t, 3> m1{1, 2, 3};\n    LinearFlatSet<int8_t> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatSet<int8_t, 3> m1{1, 2, 3};\n    InlineLinearFlatSet<int8_t, 3> m2{1, 2, 3};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n  }\n\n  {\n    InlineLinearFlatSet<int8_t, 3> m1{1, 2, 3};\n    InlineLinearFlatSet<int8_t, 4> m2{1, -2, 3, 4};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123n24(m1));\n    EXPECT_TRUE(AssertMapContent_13(m2));\n  }\n}\n\nTEST(LinearSet, MergeAssignIntKey) {\n  {\n    LinearFlatSet<int8_t, MergeAssignKeyPolicy<int8_t>> m1{1, 2, 3};\n    LinearFlatSet<int8_t, MergeAssignKeyPolicy<int8_t>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_123(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int8_t, MergeAssignKeyPolicy<int8_t>> m1{1, 2, 3};\n    LinearFlatSet<int8_t, MergeAssignKeyPolicy<int8_t>> m2{1, 2, 3};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n  }\n\n  {\n    LinearFlatSet<int8_t, MergeAssignKeyPolicy<int8_t>> m1{1, 2, 3};\n    LinearFlatSet<int8_t, MergeAssignKeyPolicy<int8_t>> m2{1, -2, 3, 4};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123n24(m1));\n    EXPECT_TRUE(AssertMapContent_1n234(m2));\n  }\n\n  {\n    InlineLinearFlatSet<int8_t, 3, MergeAssignKeyPolicy<int8_t>> m1{1, 2, 3};\n    LinearFlatSet<int8_t, MergeAssignKeyPolicy<int8_t>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_123(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    InlineLinearFlatSet<int8_t, 3, MergeAssignKeyPolicy<int8_t>> m1{1, 2, 3};\n    InlineLinearFlatSet<int8_t, 3, MergeAssignKeyPolicy<int8_t>> m2{1, 2, 3};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n  }\n\n  {\n    InlineLinearFlatSet<int8_t, 3, MergeAssignKeyPolicy<int8_t>> m1{1, 2, 3};\n    InlineLinearFlatSet<int8_t, 4, MergeAssignKeyPolicy<int8_t>> m2{1, -2, 3,\n                                                                    4};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123n24(m1));\n    EXPECT_TRUE(AssertMapContent_1n234(m2));\n  }\n}\n\nTEST(Vector, LinearMapInsertOrAssignIntKeyWithPolicy) {\n  InlineLinearFlatMap<int16_t, std::string, 5, KeyPolicy<int16_t>> map{\n      {3, \"c\"}, {2, \"b\"}, {1, \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map.capacity(), 5);\n  EXPECT_EQ(map[1], \"a\");\n  EXPECT_EQ(map[2], \"b\");\n  EXPECT_EQ(map[3], \"c\");\n  EXPECT_EQ(map[4], \"\");\n\n  auto r = map.insert_or_assign(4, \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[4], \"d\");\n\n  std::string se = \"e\";\n  auto r2 = map.insert_or_assign(5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[5], \"e\");\n  EXPECT_TRUE(se.empty());\n\n  std::string sf = \"f\";\n  auto r3 = map.insert_or_assign(6, std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[6], \"f\");\n  EXPECT_TRUE(sf.empty());\n\n  std::string sg = \"g\";\n  auto r4 = map.insert_or_assign(7, sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[7], \"g\");\n  EXPECT_EQ(sg, \"g\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearMapEmplaceOrAssignIntKeyWithPolicy) {\n  InlineLinearFlatMap<int16_t, std::string, 5, KeyPolicy<int16_t>> map{\n      {3, \"c\"}, {2, \"b\"}, {1, \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[1], \"a\");\n  EXPECT_EQ(map[2], \"b\");\n  EXPECT_EQ(map[3], \"c\");\n  EXPECT_EQ(map[4], \"\");\n\n  auto r = map.emplace_or_assign(4, \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[4], \"d\");\n\n  std::string se = \"e\";\n  auto r2 = map.emplace_or_assign(5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[5], \"e\");\n  EXPECT_TRUE(se.empty());\n\n  std::string sf = \"f\";\n  auto r3 = map.emplace_or_assign(6, std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[6], \"f\");\n  EXPECT_TRUE(sf.empty());\n\n  std::string sg = \"g\";\n  auto r4 = map.emplace_or_assign(7, sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[7], \"g\");\n  EXPECT_EQ(sg, \"g\");\n\n  auto r5 = map.emplace_or_assign(7, 5, 'g');\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(map[7], \"ggggg\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearSetInsertIntKeyWithPolicy) {\n  LinearFlatSet<int16_t, KeyPolicy<int16_t>> set{1, 2, 3};\n  auto r = set.insert(3);\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(set.size(), 3);\n\n  auto r2 = set.insert(4);\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(set.size(), 4);\n\n  auto r3 = set.insert(5);\n  EXPECT_TRUE(r3.second);\n\n  EXPECT_EQ(set.size(), 5);\n  EXPECT_TRUE(set.contains(1));\n  EXPECT_TRUE(set.contains(2));\n  EXPECT_TRUE(set.contains(3));\n  EXPECT_TRUE(set.contains(4));\n  EXPECT_TRUE(set.contains(5));\n  EXPECT_FALSE(set.contains(6));\n}\n\nTEST(Vector, LinearMapEmplaceIntKeyWithPolicy) {\n  LinearFlatMap<int16_t, std::string, KeyPolicy<int16_t>> map;\n  auto r = map.emplace(std::piecewise_construct, std::tuple<int16_t>(12),\n                       std::tuple<const char*, size_t>(\"abc\", 2));\n  EXPECT_TRUE(r.second);\n  EXPECT_EQ(r.first->first, 12);\n  EXPECT_EQ(r.first->second, \"ab\");\n  auto r2 = map.emplace(std::piecewise_construct, std::tuple<int16_t>(11),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(r2.first->first, 11);\n  EXPECT_EQ(r2.first->second, \"xy\");\n\n  EXPECT_EQ(map.size(), 2);\n  EXPECT_EQ(map[12], \"ab\");\n  EXPECT_EQ(map[11], \"xy\");\n\n  auto r3 = map.emplace(std::piecewise_construct, std::forward_as_tuple(12),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, 12);\n  EXPECT_EQ(r3.first->second, \"ab\");\n\n  EXPECT_EQ(map.size(), 2);\n\n  auto r4 = map.try_emplace(11, \"ab\");\n  EXPECT_FALSE(r4.second);\n  EXPECT_EQ(r4.first->first, 11);\n  EXPECT_EQ(r4.first->second, \"xy\");\n\n  std::string sXYZ = \"xyz\";\n  auto r5 = map.try_emplace(11, std::move(sXYZ));\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(r5.first->first, 11);\n  EXPECT_EQ(r5.first->second, \"xy\");\n  EXPECT_EQ(sXYZ, \"xyz\");\n\n  auto r6 = map.try_emplace(13, std::move(sXYZ));\n  EXPECT_TRUE(r6.second);\n  EXPECT_EQ(r6.first->first, 13);\n  EXPECT_EQ(r6.first->second, \"xyz\");\n  EXPECT_TRUE(sXYZ.empty());\n\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[12], \"ab\");\n  EXPECT_EQ(map[11], \"xy\");\n  EXPECT_EQ(map[13], \"xyz\");\n\n  std::string sUVW = \"uvw\";\n  auto r7 = map.try_emplace(14, sUVW);\n  EXPECT_TRUE(r7.second);\n  EXPECT_EQ(r7.first->first, 14);\n  EXPECT_EQ(r7.first->second, \"uvw\");\n  EXPECT_EQ(sUVW, \"uvw\");\n\n  EXPECT_EQ(map.size(), 4);\n  EXPECT_EQ(map[12], \"ab\");\n  EXPECT_EQ(map[11], \"xy\");\n  EXPECT_EQ(map[13], \"xyz\");\n  EXPECT_EQ(map[14], \"uvw\");\n}\n\nTEST(MapIntTest, LinearBasicOperationsIntKeyWithPolicy) {\n  LinearFlatMap<int8_t, std::string, KeyPolicy<int8_t>> m;\n\n  EXPECT_TRUE(m.empty());\n  EXPECT_EQ(m.size(), 0);\n\n  auto ret = m.insert({99, \"red\"});\n  ASSERT_TRUE(ret.second);\n  EXPECT_EQ(ret.first->first, 99);\n  EXPECT_EQ(ret.first->second, \"red\");\n  EXPECT_EQ(m.size(), 1);\n}\n\nTEST(MapIntTest, LinearElementAccessIntKeyWithPolicy) {\n  LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m{{99, \"red\"},\n                                                            {100, \"yellow\"}};\n\n  EXPECT_EQ(m[99], \"red\");\n\n  m[99] = \"green\";\n  EXPECT_EQ(m[99], \"green\");\n  EXPECT_EQ(m.at(99), \"green\");\n\n  EXPECT_EQ(m[101], \"\");\n  EXPECT_EQ(m.at(101), \"\");\n  EXPECT_EQ(m.size(), 3);\n\n  EXPECT_EQ(m.at(102), \"\");\n  m.at(102) = \"orange\";\n  EXPECT_EQ(m[102], \"orange\");\n  EXPECT_EQ(m.size(), 4);\n\n  int32_t i103 = 103;\n  m.at(std::move(i103)) = \"green\";\n  EXPECT_EQ(m.find(103)->first, 103);\n  EXPECT_EQ(m.find(103)->second, \"green\");\n  EXPECT_EQ(m.size(), 5);\n\n  int32_t i105 = 105;\n  auto r = m.insert_default_if_absent(std::move(i105));\n  EXPECT_TRUE(r.second);\n  r.first->second = \"yellow\";\n  EXPECT_EQ(m[105], \"yellow\");\n  EXPECT_EQ(m.size(), 6);\n\n  auto r2 = m.insert_default_if_absent(99);\n  EXPECT_FALSE(r2.second);\n  EXPECT_EQ(m[99], \"green\");\n  r2.first->second = \"red\";\n  EXPECT_EQ(m[99], \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string black = \"black\";\n  auto r3 = m.insert_if_absent(99, black);\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(r3.first->first, 99);\n  EXPECT_EQ(r3.first->second, \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string pink = \"pink\";\n  auto r4 = m.insert_if_absent(200, std::move(pink));\n  EXPECT_TRUE(r4.second);\n  EXPECT_TRUE(pink.empty());\n  EXPECT_EQ(r4.first->first, 200);\n  EXPECT_EQ(r4.first->second, \"pink\");\n  EXPECT_EQ(m.size(), 7);\n\n  auto r5 = m.insert_if_absent(300, black);\n  EXPECT_TRUE(r5.second);\n  EXPECT_EQ(r5.first->first, 300);\n  EXPECT_EQ(r5.first->second, \"black\");\n  EXPECT_EQ(black, \"black\");\n  EXPECT_EQ(m.size(), 8);\n}\n\nTEST(MapIntTest, LinearInsertUpdateIntKeyWithPolicy) {\n  LinearFlatMap<int16_t, std::string, KeyPolicy<int16_t>> m;\n\n  auto ret1 = m.insert({99, \"apple\"});\n  EXPECT_TRUE(ret1.second);\n  auto ret2 = m.insert({99, \"banana\"});\n  EXPECT_FALSE(ret2.second);\n  EXPECT_EQ(ret2.first->second, \"apple\");\n\n  auto emp_ret = m.emplace(100, \"blue\");\n  EXPECT_TRUE(emp_ret.second);\n  EXPECT_EQ(emp_ret.first->first, 100);\n\n  m[100] = \"red\";\n  EXPECT_EQ(m[100], \"red\");\n}\n\nTEST(MapIntTest, LinearEraseOperationsIntKeyWithPolicy) {\n  LinearFlatMap<int16_t, std::string, KeyPolicy<int16_t>> m{\n      {30, \"1\"}, {31, \"2\"}, {32, \"3\"}};\n  EXPECT_EQ(m.size(), 3);\n  EXPECT_EQ(m[30], \"1\");\n  EXPECT_EQ(m[31], \"2\");\n  EXPECT_EQ(m[32], \"3\");\n\n  size_t cnt = m.erase(31);\n  EXPECT_EQ(cnt, 1);\n  EXPECT_EQ(m.size(), 2);\n  EXPECT_FALSE(m.contains(31));\n\n  auto it = m.find(30);\n  m.erase(it);\n  EXPECT_EQ(m.size(), 1);\n  EXPECT_FALSE(m.contains(30));\n\n  EXPECT_EQ(m.erase(100), 0);\n}\n\nTEST(SetIntTest, LinearIteratorsIntKeyWithPolicy) {\n  InlineLinearFlatSet<int32_t, 10, KeyPolicy<int32_t>> s{5, 4, 9, 0,\n                                                         1, 8, 2, 7};\n  std::string order;\n  for (auto it = s.begin(); it != s.end(); it++) {\n    order += std::to_string((*it));\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"01245789\" : \"54901827\");\n\n  order = \"\";\n  for (auto it = s.rbegin(); it != s.rend(); it++) {\n    order += std::to_string((*it));\n  }\n  EXPECT_EQ(order, s.is_data_ordered() ? \"98754210\" : \"72810945\");\n}\n\nTEST(SetIntTest, LinearBasicIntKeyWithPolicy) {\n  {\n    InlineLinearFlatSet<int8_t, 10, KeyPolicy<int8_t>> s{5, 4, 9, 0,\n                                                         1, 8, 2, 7};\n    EXPECT_TRUE(s.is_static_buffer());\n    EXPECT_TRUE(s.contains(5));\n    EXPECT_FALSE(s.contains(3));\n    EXPECT_TRUE(s.find(9) != s.end());\n    EXPECT_TRUE(s.find(3) == s.end());\n    EXPECT_TRUE(s.count(2) == 1);\n    EXPECT_TRUE(s.count(3) == 0);\n    EXPECT_EQ(s.erase(3), 0);\n    EXPECT_EQ(s.size(), 8);\n    EXPECT_EQ(s.erase(4), 1);\n    EXPECT_EQ(s.size(), 7);\n    EXPECT_FALSE(s.contains(4));\n    auto it = s.erase(s.find(8));\n    EXPECT_EQ(s.size(), 6);\n    EXPECT_EQ(*it, s.is_data_ordered() ? 9 : 2);\n  }\n  {\n    InlineLinearFlatSet<int16_t, 10, KeyPolicy<int16_t>> s{5, 4, 9, 0,\n                                                           1, 8, 2, 7};\n    EXPECT_TRUE(s.is_static_buffer());\n    EXPECT_TRUE(s.contains(5));\n    EXPECT_FALSE(s.contains(3));\n    EXPECT_TRUE(s.find(9) != s.end());\n    EXPECT_TRUE(s.find(3) == s.end());\n    EXPECT_TRUE(s.count(2) == 1);\n    EXPECT_TRUE(s.count(3) == 0);\n    EXPECT_EQ(s.erase(3), 0);\n    EXPECT_EQ(s.size(), 8);\n    EXPECT_EQ(s.erase(4), 1);\n    EXPECT_EQ(s.size(), 7);\n    EXPECT_FALSE(s.contains(4));\n    auto it = s.erase(s.find(8));\n    EXPECT_EQ(s.size(), 6);\n    EXPECT_EQ(*it, s.is_data_ordered() ? 9 : 2);\n  }\n  {\n    InlineLinearFlatSet<int32_t, 10, KeyPolicy<int32_t>> s{5, 4, 9, 0,\n                                                           1, 8, 2, 7};\n    EXPECT_TRUE(s.is_static_buffer());\n    EXPECT_TRUE(s.contains(5));\n    EXPECT_FALSE(s.contains(3));\n    EXPECT_TRUE(s.find(9) != s.end());\n    EXPECT_TRUE(s.find(3) == s.end());\n    EXPECT_TRUE(s.count(2) == 1);\n    EXPECT_TRUE(s.count(3) == 0);\n    EXPECT_EQ(s.erase(3), 0);\n    EXPECT_EQ(s.size(), 8);\n    EXPECT_EQ(s.erase(4), 1);\n    EXPECT_EQ(s.size(), 7);\n    EXPECT_FALSE(s.contains(4));\n    auto it = s.erase(s.find(8));\n    EXPECT_EQ(s.size(), 6);\n    EXPECT_EQ(*it, s.is_data_ordered() ? 9 : 2);\n  }\n}\n\nTEST(MapIntTest, LinearIteratorsIntKeyWithPolicy) {\n  LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m{\n      {26, \"26\"}, {1, \"1\"}, {13, \"13\"}};\n\n  auto it = m.begin();\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 1 : 26);\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 13 : 1);\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 26 : 13);\n  ++it;\n  EXPECT_EQ(it, m.end());\n\n  auto rit = m.rbegin();\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? 26 : 13);\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? 13 : 1);\n  ++rit;\n  EXPECT_EQ(rit->first, m.is_data_ordered() ? 1 : 26);\n  ++rit;\n  EXPECT_EQ(rit, m.rend());\n}\n\nTEST(MapIntTest, LinearInsertOrAssignIntKeyWithPolicy) {\n  LinearFlatMap<int16_t, std::string, KeyPolicy<int16_t>> m;\n\n  {\n    auto [it, inserted] = m.insert_or_assign(10, \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.insert_or_assign(10, \"banana\");\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"banana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.insert_or_assign(11, \"11\");\n  EXPECT_EQ(m[11], \"11\");\n\n  auto [it, _] = m.insert_or_assign(12, \"orange\");\n  EXPECT_EQ(it->first, 12);\n}\n\nTEST(MapIntTest, LinearEmplaceOrAssignIntKeyWithPolicy) {\n  LinearFlatMap<int16_t, std::string, KeyPolicy<int16_t>> m;\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(10, \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(it->second, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(10, \"banana\", 4);\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(it->second, \"bana\");\n    EXPECT_EQ(m[10], \"bana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.emplace_or_assign(11, \"11\");\n  EXPECT_EQ(m[11], \"11\");\n\n  auto [it, _] = m.emplace_or_assign(12, \"orange\");\n  EXPECT_EQ(it->first, 12);\n}\n\nTEST(MapIntTest, LinearEmplacePiecewiseIntKeyWithPolicy) {\n  LinearFlatMap<uint32_t, std::string, KeyPolicy<uint32_t>> m;\n\n  auto emp_it = m.emplace(std::piecewise_construct, std::forward_as_tuple(99u),\n                          std::forward_as_tuple(5, 'X'));\n  ASSERT_TRUE(emp_it.second);\n  EXPECT_EQ(emp_it.first->second, \"XXXXX\");\n\n  m.emplace(std::piecewise_construct, std::forward_as_tuple(199u),\n            std::forward_as_tuple(3, 'k'));\n  EXPECT_EQ(m[199], \"kkk\");\n\n  auto emp_fail = m.emplace(std::piecewise_construct, std::forward_as_tuple(99),\n                            std::forward_as_tuple(\"new_value\"));\n  EXPECT_FALSE(emp_fail.second);\n  EXPECT_EQ(m[99], \"XXXXX\");\n}\n\nTEST(MapIntTest, LinearMixedInlineSizeIntKeyWithPolicy) {\n  LinearFlatMap<int8_t, std::string, KeyPolicy<int8_t>> m_src{\n      {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n  EXPECT_TRUE(AssertMapContent_123_123(m_src));\n  InlineLinearFlatMap<int8_t, std::string, 3, KeyPolicy<int8_t>> m_src2{\n      {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n  EXPECT_TRUE(AssertMapContent_123_123(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatMap<int8_t, std::string, KeyPolicy<int8_t>> m1(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatMap<int8_t, std::string, KeyPolicy<int8_t>> m2(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatMap<int8_t, std::string, 2, KeyPolicy<int8_t>> m3(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatMap<int8_t, std::string, 2, KeyPolicy<int8_t>> m4(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatMap<int8_t, std::string, 5, KeyPolicy<int8_t>> m5(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatMap<int8_t, std::string, 5, KeyPolicy<int8_t>> m6(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatMap<int8_t, std::string, KeyPolicy<int8_t>> m7{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m7));\n\n  InlineLinearFlatMap<int8_t, std::string, 3, KeyPolicy<int8_t>> m8{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatMap<int8_t, std::string, 2, KeyPolicy<int8_t>> m9{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatMap<int8_t, std::string, 5, KeyPolicy<int8_t>> m10{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatMap<int8_t, std::string, KeyPolicy<int8_t>> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 3, KeyPolicy<int8_t>> m12(\n      std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 2, KeyPolicy<int8_t>> m13(\n      std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 5, KeyPolicy<int8_t>> m14(\n      std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatMap<int8_t, std::string, KeyPolicy<int8_t>> m15{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 3, KeyPolicy<int8_t>> m16{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 2, KeyPolicy<int8_t>> m17{\n      {1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(SetIntTest, LinearEmplaceIntKeyWithPolicy) {\n  LinearFlatSet<int32_t, KeyPolicy<int32_t>> s;\n  s.emplace(9);\n  s.emplace(8);\n  s.insert(9);\n  EXPECT_EQ(s.size(), 2);\n  EXPECT_TRUE(s.contains(9));\n  EXPECT_TRUE(s.contains(8));\n}\n\nTEST(SetIntTest, LinearMixedInlineSizeIntKeyWithPolicy) {\n  LinearFlatSet<int16_t, KeyPolicy<int16_t>> m_src{1, 2, 3};\n  EXPECT_TRUE(AssertSetContent_123(m_src));\n  InlineLinearFlatSet<int16_t, 3, KeyPolicy<int16_t>> m_src2{1, 2, 3};\n  EXPECT_TRUE(AssertSetContent_123(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatSet<int16_t, KeyPolicy<int16_t>> m1(m_src);\n  EXPECT_TRUE(AssertSetContent_123(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatSet<int16_t, KeyPolicy<int16_t>> m2(m_src2);\n  EXPECT_TRUE(AssertSetContent_123(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatSet<int16_t, 2, KeyPolicy<int16_t>> m3(m_src);\n  EXPECT_TRUE(AssertSetContent_123(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatSet<int16_t, 2, KeyPolicy<int16_t>> m4(m_src2);\n  EXPECT_TRUE(AssertSetContent_123(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatSet<int16_t, 5, KeyPolicy<int16_t>> m5(m_src);\n  EXPECT_TRUE(AssertSetContent_123(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatSet<int16_t, 5, KeyPolicy<int16_t>> m6(m_src2);\n  EXPECT_TRUE(AssertSetContent_123(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatSet<int16_t, KeyPolicy<int16_t>> m7{21, 22, 23};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m7));\n\n  InlineLinearFlatSet<int16_t, 3, KeyPolicy<int16_t>> m8{21, 22, 23};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatSet<int16_t, 2, KeyPolicy<int16_t>> m9{21, 22, 23};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatSet<int16_t, 5, KeyPolicy<int16_t>> m10{21, 22, 23};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatSet<int16_t, KeyPolicy<int16_t>> m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatSet<int16_t, 3, KeyPolicy<int16_t>> m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatSet<int16_t, 2, KeyPolicy<int16_t>> m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatSet<int16_t, 5, KeyPolicy<int16_t>> m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatSet<int16_t, KeyPolicy<int16_t>> m15{21, 22, 23};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertSetContent_123(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatSet<int16_t, 3, KeyPolicy<int16_t>> m16{21, 22, 23};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertSetContent_123(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatSet<int16_t, 2, KeyPolicy<int16_t>> m17{21, 22, 23};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertSetContent_123(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(LinearMap, SwapIntKeyWithPolicy) {\n  {\n    LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 2, KeyPolicy<int32_t>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5, KeyPolicy<int32_t>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<int32_t, std::string, 3, KeyPolicy<int32_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5, KeyPolicy<int32_t>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m2{\n        {-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 2, KeyPolicy<int32_t>> m2{\n        {-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string, KeyPolicy<int32_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5, KeyPolicy<int32_t>> m2{\n        {-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<int32_t, std::string, 3, KeyPolicy<int32_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5, KeyPolicy<int32_t>> m2{\n        {-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n}\n\nTEST(LinearSet, SwapIntKeyWithPolicy) {\n  {\n    LinearFlatSet<int, KeyPolicy<int>> m1{1, 2, 3};\n    LinearFlatSet<int, KeyPolicy<int>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int, KeyPolicy<int>> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 2, KeyPolicy<int>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int, KeyPolicy<int>> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 5, KeyPolicy<int>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    InlineLinearFlatSet<int, 3, KeyPolicy<int>> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 5, KeyPolicy<int>> m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int, KeyPolicy<int>> m1{1, 2, 3};\n    LinearFlatSet<int, KeyPolicy<int>> m2{-1, -2, -3};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int, KeyPolicy<int>> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 2, KeyPolicy<int>> m2{-1, -2, -3};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    LinearFlatSet<int, KeyPolicy<int>> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 5, KeyPolicy<int>> m2{-1, -2, -3};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n\n  {\n    InlineLinearFlatSet<int, 3, KeyPolicy<int>> m1{1, 2, 3};\n    InlineLinearFlatSet<int, 5, KeyPolicy<int>> m2{-1, -2, -3};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertSetContent_n1n2n3(m2));\n    EXPECT_TRUE(AssertSetContent_123(m1));\n  }\n}\n\nTEST(LinearMap, MergeIntKeyWithPolicy) {\n  {\n    LinearFlatMap<uint8_t, std::string, KeyPolicy<uint8_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string, KeyPolicy<uint8_t>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string, KeyPolicy<uint8_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string, KeyPolicy<uint8_t>> m2{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string, KeyPolicy<uint8_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string, KeyPolicy<uint8_t>> m2{\n        {1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_13_1030(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3, KeyPolicy<uint8_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string, KeyPolicy<uint8_t>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3, KeyPolicy<uint8_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 3, KeyPolicy<uint8_t>> m2{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3, KeyPolicy<uint8_t>> m1{\n        {1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 4, KeyPolicy<uint8_t>> m2{\n        {1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_13_1030(m2));\n  }\n}\n\nTEST(LinearSet, MergeIntKeyWithPolicy) {\n  {\n    LinearFlatSet<int8_t, KeyPolicy<int8_t>> m1{1, 2, 3};\n    LinearFlatSet<int8_t, KeyPolicy<int8_t>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatSet<int8_t, KeyPolicy<int8_t>> m1{1, 2, 3};\n    LinearFlatSet<int8_t, KeyPolicy<int8_t>> m2{1, 2, 3};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n  }\n\n  {\n    LinearFlatSet<int8_t, KeyPolicy<int8_t>> m1{1, 2, 3};\n    LinearFlatSet<int8_t, KeyPolicy<int8_t>> m2{1, -2, 3, 4};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123n24(m1));\n    EXPECT_TRUE(AssertMapContent_13(m2));\n  }\n\n  {\n    InlineLinearFlatSet<int8_t, 3, KeyPolicy<int8_t>> m1{1, 2, 3};\n    LinearFlatSet<int8_t, KeyPolicy<int8_t>> m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertSetContent_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatSet<int8_t, 3, KeyPolicy<int8_t>> m1{1, 2, 3};\n    InlineLinearFlatSet<int8_t, 3, KeyPolicy<int8_t>> m2{1, 2, 3};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123(m1));\n    EXPECT_TRUE(AssertSetContent_123(m2));\n  }\n\n  {\n    InlineLinearFlatSet<int8_t, 3, KeyPolicy<int8_t>> m1{1, 2, 3};\n    InlineLinearFlatSet<int8_t, 4, KeyPolicy<int8_t>> m2{1, -2, 3, 4};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertSetContent_123n24(m1));\n    EXPECT_TRUE(AssertMapContent_13(m2));\n  }\n}\n\nTEST(Vector, LinearMapInsertOrAssignIntKeyWithConsecutivePolicy) {\n  InlineLinearFlatMap<int16_t, std::string, 5,\n                      MapKeyPolicyConsecutiveIntegers<int16_t>>\n      map{{3, \"c\"}, {2, \"b\"}, {1, \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map.capacity(), 5);\n  EXPECT_EQ(map[1], \"a\");\n  EXPECT_EQ(map[2], \"b\");\n  EXPECT_EQ(map[3], \"c\");\n  EXPECT_EQ(map[4], \"\");\n\n  auto r = map.insert_or_assign(4, \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[4], \"d\");\n\n  std::string se = \"e\";\n  auto r2 = map.insert_or_assign(5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[5], \"e\");\n  EXPECT_TRUE(se.empty());\n\n  std::string sf = \"f\";\n  auto r3 = map.insert_or_assign(6, std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[6], \"f\");\n  EXPECT_TRUE(sf.empty());\n\n  std::string sg = \"g\";\n  auto r4 = map.insert_or_assign(7, sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[7], \"g\");\n  EXPECT_EQ(sg, \"g\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearMapEmplaceOrAssignIntKeyWithConsecutivePolicy) {\n  InlineLinearFlatMap<int16_t, std::string, 5,\n                      MapKeyPolicyConsecutiveIntegers<int16_t>>\n      map{{3, \"c\"}, {2, \"b\"}, {1, \"a\"}};\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[1], \"a\");\n  EXPECT_EQ(map[2], \"b\");\n  EXPECT_EQ(map[3], \"c\");\n  EXPECT_EQ(map[4], \"\");\n\n  auto r = map.emplace_or_assign(4, \"d\");\n  EXPECT_FALSE(r.second);\n  EXPECT_EQ(map[4], \"d\");\n\n  std::string se = \"e\";\n  auto r2 = map.emplace_or_assign(5, std::move(se));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(map[5], \"e\");\n  EXPECT_TRUE(se.empty());\n\n  std::string sf = \"f\";\n  auto r3 = map.emplace_or_assign(6, std::move(sf));\n  EXPECT_TRUE(r3.second);\n  EXPECT_EQ(map[6], \"f\");\n  EXPECT_TRUE(sf.empty());\n\n  std::string sg = \"g\";\n  auto r4 = map.emplace_or_assign(7, sg);\n  EXPECT_TRUE(r4.second);\n  EXPECT_EQ(map[7], \"g\");\n  EXPECT_EQ(sg, \"g\");\n\n  auto r5 = map.emplace_or_assign(7, 5, 'g');\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(map[7], \"ggggg\");\n\n  EXPECT_EQ(map.size(), 7);\n}\n\nTEST(Vector, LinearMapEmplaceIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<int16_t, std::string, MapKeyPolicyConsecutiveIntegers<int16_t>>\n      map;\n  auto r = map.emplace(std::piecewise_construct, std::tuple<int16_t>(12),\n                       std::tuple<const char*, size_t>(\"abc\", 2));\n  EXPECT_TRUE(r.second);\n  EXPECT_EQ(*r.first, \"ab\");\n  auto r2 = map.emplace(std::piecewise_construct, std::tuple<int16_t>(11),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_TRUE(r2.second);\n  EXPECT_EQ(*r2.first, \"xy\");\n\n  EXPECT_EQ(map.size(), 2);\n  EXPECT_EQ(map[12], \"ab\");\n  EXPECT_EQ(map[11], \"xy\");\n\n  auto r3 = map.emplace(std::piecewise_construct, std::forward_as_tuple(12),\n                        std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(*r3.first, \"ab\");\n\n  EXPECT_EQ(map.size(), 2);\n\n  auto r4 = map.try_emplace(11, \"ab\");\n  EXPECT_FALSE(r4.second);\n  EXPECT_EQ(*r4.first, \"xy\");\n\n  std::string sXYZ = \"xyz\";\n  auto r5 = map.try_emplace(11, std::move(sXYZ));\n  EXPECT_FALSE(r5.second);\n  EXPECT_EQ(*r5.first, \"xy\");\n  EXPECT_EQ(sXYZ, \"xyz\");\n\n  auto r6 = map.try_emplace(13, std::move(sXYZ));\n  EXPECT_TRUE(r6.second);\n  EXPECT_EQ(*r6.first, \"xyz\");\n  EXPECT_TRUE(sXYZ.empty());\n\n  EXPECT_EQ(map.size(), 3);\n  EXPECT_EQ(map[12], \"ab\");\n  EXPECT_EQ(map[11], \"xy\");\n  EXPECT_EQ(map[13], \"xyz\");\n\n  std::string sUVW = \"uvw\";\n  auto r7 = map.try_emplace(14, sUVW);\n  EXPECT_TRUE(r7.second);\n  EXPECT_EQ(*r7.first, \"uvw\");\n  EXPECT_EQ(sUVW, \"uvw\");\n\n  EXPECT_EQ(map.size(), 4);\n  EXPECT_EQ(map[12], \"ab\");\n  EXPECT_EQ(map[11], \"xy\");\n  EXPECT_EQ(map[13], \"xyz\");\n  EXPECT_EQ(map[14], \"uvw\");\n}\n\nTEST(MapIntTest, LinearBasicOperationsIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>> m;\n\n  EXPECT_TRUE(m.empty());\n  EXPECT_EQ(m.size(), 0);\n\n  auto ret = m.insert({99, \"red\"});\n  ASSERT_TRUE(ret.second);\n  EXPECT_EQ(*ret.first, \"red\");\n  EXPECT_EQ(m.size(), 1);\n}\n\nTEST(MapIntTest, LinearElementAccessIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<int32_t, std::string, MapKeyPolicyConsecutiveIntegers<int32_t>>\n      m{{99, \"red\"}, {100, \"yellow\"}};\n\n  EXPECT_EQ(m[99], \"red\");\n\n  m[99] = \"green\";\n  EXPECT_EQ(m[99], \"green\");\n  EXPECT_EQ(m.at(99), \"green\");\n\n  EXPECT_EQ(m[101], \"\");\n  EXPECT_EQ(m.at(101), \"\");\n  EXPECT_EQ(m.size(), 3);\n\n  EXPECT_EQ(m.at(102), \"\");\n  m.at(102) = \"orange\";\n  EXPECT_EQ(m[102], \"orange\");\n  EXPECT_EQ(m.size(), 4);\n\n  int32_t i103 = 103;\n  m.at(std::move(i103)) = \"green\";\n  EXPECT_EQ(m.find(103)->first, 103);\n  EXPECT_EQ(m.find(103)->second, \"green\");\n  EXPECT_EQ(m.size(), 5);\n\n  int32_t i105 = 105;\n  auto r = m.insert_default_if_absent(std::move(i105));\n  EXPECT_TRUE(r.second);\n  *r.first = \"yellow\";\n  EXPECT_EQ(m[105], \"yellow\");\n  EXPECT_EQ(m.size(), 6);\n\n  auto r2 = m.insert_default_if_absent(99);\n  EXPECT_FALSE(r2.second);\n  EXPECT_EQ(m[99], \"green\");\n  *r2.first = \"red\";\n  EXPECT_EQ(m[99], \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string black = \"black\";\n  auto r3 = m.insert_if_absent(99, black);\n  EXPECT_FALSE(r3.second);\n  EXPECT_EQ(*r3.first, \"red\");\n  EXPECT_EQ(m.size(), 6);\n\n  std::string pink = \"pink\";\n  auto r4 = m.insert_if_absent(200, std::move(pink));\n  EXPECT_TRUE(r4.second);\n  EXPECT_TRUE(pink.empty());\n  EXPECT_EQ(*r4.first, \"pink\");\n  EXPECT_EQ(m.size(), 7);\n\n  auto r5 = m.insert_if_absent(300, black);\n  EXPECT_TRUE(r5.second);\n  EXPECT_EQ(*r5.first, \"black\");\n  EXPECT_EQ(black, \"black\");\n  EXPECT_EQ(m.size(), 8);\n}\n\nTEST(MapIntTest, LinearInsertUpdateIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<int16_t, std::string, MapKeyPolicyConsecutiveIntegers<int16_t>>\n      m;\n\n  auto ret1 = m.insert({99, \"apple\"});\n  EXPECT_TRUE(ret1.second);\n  auto ret2 = m.insert({99, \"banana\"});\n  EXPECT_FALSE(ret2.second);\n  EXPECT_EQ(*ret2.first, \"apple\");\n\n  auto emp_ret = m.emplace(100, \"blue\");\n  EXPECT_TRUE(emp_ret.second);\n  EXPECT_EQ(*emp_ret.first, \"blue\");\n\n  m[100] = \"red\";\n  EXPECT_EQ(m[100], \"red\");\n\n  auto it = m.insert_unique({101, \"car\"});\n  EXPECT_TRUE(*it == \"car\");\n  std::pair<const int16_t, std::string> value_type{102, \"train\"};\n  auto it2 = m.insert_unique(value_type);\n  EXPECT_TRUE(*it2 == \"train\");\n  EXPECT_TRUE(!value_type.second.empty());\n  EXPECT_TRUE(m[101] == \"car\");\n  EXPECT_TRUE(m[102] == \"train\");\n\n  std::pair<const int16_t, std::string> value_type2{200, \"tiger\"};\n  auto it3 = m.insert_unique(std::move(value_type2));\n  EXPECT_TRUE(*it3 == \"tiger\");\n  EXPECT_TRUE(value_type2.second.empty());\n  EXPECT_TRUE(m[200] == \"tiger\");\n\n  std::pair<int16_t, std::string> value_type3{201, \"student\"};\n  auto it4 = m.insert_unique(value_type3);\n  EXPECT_TRUE(*it4 == \"student\");\n  EXPECT_FALSE(value_type3.second.empty());\n  EXPECT_TRUE(m[201] == \"student\");\n\n  std::pair<int16_t, std::string> value_type4{202, \"doctor\"};\n  auto it5 = m.insert_unique(std::move(value_type4));\n  EXPECT_TRUE(*it5 == \"doctor\");\n  EXPECT_TRUE(value_type4.second.empty());\n  EXPECT_TRUE(m[202] == \"doctor\");\n\n  auto it6 = m.emplace_unique(300, \"fanfan\", 3);\n  EXPECT_TRUE(*it6 == \"fan\");\n  EXPECT_TRUE(m[300] == \"fan\");\n\n  const int16_t key301 = 301;\n  auto it7 = m.emplace_unique(key301, 3, 'x');\n  EXPECT_TRUE(*it7 == \"xxx\");\n  EXPECT_TRUE(m[301] == \"xxx\");\n\n  auto it8 =\n      m.emplace_unique(std::piecewise_construct, std::forward_as_tuple(302),\n                       std::tuple<const char*, size_t>(\"xyz\", 2));\n  EXPECT_TRUE(*it8 == \"xy\");\n  EXPECT_TRUE(m[302] == \"xy\");\n}\n\nTEST(MapIntTest, LinearEraseOperationsIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<int16_t, std::string, MapKeyPolicyConsecutiveIntegers<int16_t>>\n      m{{30, \"1\"}, {31, \"2\"}, {32, \"3\"}};\n  EXPECT_EQ(m.size(), 3);\n  EXPECT_EQ(m[30], \"1\");\n  EXPECT_EQ(m[31], \"2\");\n  EXPECT_EQ(m[32], \"3\");\n\n  size_t cnt = m.erase(31);\n  EXPECT_EQ(cnt, 1);\n  EXPECT_EQ(m.size(), 2);\n  EXPECT_FALSE(m.contains(31));\n\n  auto it = m.find(30);\n  m.erase(it);\n  EXPECT_EQ(m.size(), 1);\n  EXPECT_FALSE(m.contains(30));\n\n  EXPECT_EQ(m.erase(100), 0);\n}\n\nTEST(MapIntTest, LinearIteratorsIntKeyWithConsecutivePolicy_i8) {\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>> m{\n      {26, \"26\"}, {1, \"1\"}, {13, \"13\"}};\n\n  auto it = m.begin();\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 1 : 26);\n  EXPECT_EQ(it->second, m.is_data_ordered() ? \"1\" : \"26\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 13 : 1);\n  EXPECT_EQ(it->second, m.is_data_ordered() ? \"13\" : \"1\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 26 : 13);\n  EXPECT_EQ(it->second, m.is_data_ordered() ? \"26\" : \"13\");\n  ++it;\n  EXPECT_EQ(it, m.end());\n\n  auto rit = m.rbegin();\n  EXPECT_EQ(*rit, m.is_data_ordered() ? \"26\" : \"13\");\n  ++rit;\n  EXPECT_EQ(*rit, m.is_data_ordered() ? \"13\" : \"1\");\n  ++rit;\n  EXPECT_EQ(*rit, m.is_data_ordered() ? \"1\" : \"26\");\n  ++rit;\n  EXPECT_EQ(rit, m.rend());\n}\n\nTEST(MapIntTest, LinearIteratorsIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<int32_t, std::string, MapKeyPolicyConsecutiveIntegers<int32_t>>\n      m{{26, \"26\"}, {1, \"1\"}, {13, \"13\"}};\n\n  auto it = m.begin();\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 1 : 26);\n  EXPECT_EQ(it->second, m.is_data_ordered() ? \"1\" : \"26\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 13 : 1);\n  EXPECT_EQ(it->second, m.is_data_ordered() ? \"13\" : \"1\");\n  ++it;\n  EXPECT_EQ(it->first, m.is_data_ordered() ? 26 : 13);\n  EXPECT_EQ(it->second, m.is_data_ordered() ? \"26\" : \"13\");\n  ++it;\n  EXPECT_EQ(it, m.end());\n\n  auto rit = m.rbegin();\n  EXPECT_EQ(*rit, m.is_data_ordered() ? \"26\" : \"13\");\n  ++rit;\n  EXPECT_EQ(*rit, m.is_data_ordered() ? \"13\" : \"1\");\n  ++rit;\n  EXPECT_EQ(*rit, m.is_data_ordered() ? \"1\" : \"26\");\n  ++rit;\n  EXPECT_EQ(rit, m.rend());\n}\n\nTEST(MapIntTest, LinearInsertOrAssignIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<int16_t, std::string, MapKeyPolicyConsecutiveIntegers<int16_t>>\n      m;\n\n  {\n    auto [it, inserted] = m.insert_or_assign(10, \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(*it, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.insert_or_assign(10, \"banana\");\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(*it, \"banana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.insert_or_assign(11, \"11\");\n  EXPECT_EQ(m[11], \"11\");\n\n  auto [it, _] = m.insert_or_assign(12, \"orange\");\n  EXPECT_EQ(*it, \"orange\");\n}\n\nTEST(MapIntTest, LinearEmplaceOrAssignIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<int16_t, std::string, MapKeyPolicyConsecutiveIntegers<int16_t>>\n      m;\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(10, \"apple\");\n    EXPECT_TRUE(inserted);\n    EXPECT_EQ(*it, \"apple\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  {\n    auto [it, inserted] = m.emplace_or_assign(10, \"banana\", 4);\n    EXPECT_FALSE(inserted);\n    EXPECT_EQ(*it, \"bana\");\n    EXPECT_EQ(m[10], \"bana\");\n    EXPECT_EQ(m.size(), 1);\n  }\n\n  m.emplace_or_assign(11, \"11\");\n  EXPECT_EQ(m[11], \"11\");\n\n  auto [it, _] = m.emplace_or_assign(12, \"orange\");\n  EXPECT_EQ(*it, \"orange\");\n}\n\nTEST(MapIntTest, LinearEmplacePiecewiseIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<uint32_t, std::string,\n                MapKeyPolicyConsecutiveIntegers<uint32_t>>\n      m;\n\n  auto emp_it = m.emplace(std::piecewise_construct, std::forward_as_tuple(99u),\n                          std::forward_as_tuple(5, 'X'));\n  ASSERT_TRUE(emp_it.second);\n  EXPECT_EQ(*emp_it.first, \"XXXXX\");\n\n  m.emplace(std::piecewise_construct, std::forward_as_tuple(199u),\n            std::forward_as_tuple(3, 'k'));\n  EXPECT_EQ(m[199], \"kkk\");\n\n  auto emp_fail = m.emplace(std::piecewise_construct, std::forward_as_tuple(99),\n                            std::forward_as_tuple(\"new_value\"));\n  EXPECT_FALSE(emp_fail.second);\n  EXPECT_EQ(m[99], \"XXXXX\");\n}\n\nTEST(MapIntTest, LinearMixedInlineSizeIntKeyWithConsecutivePolicy) {\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m_src{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n  EXPECT_TRUE(AssertMapContent_123_123(m_src));\n  InlineLinearFlatMap<int8_t, std::string, 3,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m_src2{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n  EXPECT_TRUE(AssertMapContent_123_123(m_src2));\n  EXPECT_TRUE(m_src2.is_static_buffer());\n  EXPECT_TRUE(m_src == m_src2);\n\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m1(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m1));\n  EXPECT_TRUE(m1 == m_src);\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m2(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m2));\n  EXPECT_TRUE(m2 == m_src2);\n  InlineLinearFlatMap<int8_t, std::string, 2,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m3(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m3));\n  EXPECT_FALSE(m3.is_static_buffer());\n  EXPECT_TRUE(m3 == m_src);\n  InlineLinearFlatMap<int8_t, std::string, 2,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m4(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m4));\n  EXPECT_FALSE(m4.is_static_buffer());\n  EXPECT_TRUE(m4 == m_src2);\n  InlineLinearFlatMap<int8_t, std::string, 5,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m5(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m5));\n  EXPECT_TRUE(m5.is_static_buffer());\n  EXPECT_TRUE(m5 == m_src);\n  InlineLinearFlatMap<int8_t, std::string, 5,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m6(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m6));\n  EXPECT_TRUE(m6.is_static_buffer());\n  EXPECT_TRUE(m6 == m_src2);\n\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m7{{1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m7 == m_src);\n  EXPECT_TRUE(m7 != m_src);\n  m7 = m_src;\n  EXPECT_TRUE(m7 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m7));\n\n  InlineLinearFlatMap<int8_t, std::string, 3,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m8{{1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m8 == m_src);\n  EXPECT_TRUE(m8 != m_src);\n  m8 = m_src;\n  EXPECT_TRUE(m8 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m8));\n  EXPECT_TRUE(m8.is_static_buffer());\n\n  InlineLinearFlatMap<int8_t, std::string, 2,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m9{{1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m9 == m_src);\n  EXPECT_TRUE(m9 != m_src);\n  m9 = m_src;\n  EXPECT_TRUE(m9 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m9));\n  EXPECT_FALSE(m9.is_static_buffer());\n\n  InlineLinearFlatMap<int8_t, std::string, 5,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m10{{1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m10 == m_src);\n  EXPECT_TRUE(m10 != m_src);\n  m10 = m_src;\n  EXPECT_TRUE(m10 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m10));\n  EXPECT_TRUE(m10.is_static_buffer());\n\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m11(std::move(m7));\n  EXPECT_TRUE(m11 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m11));\n  EXPECT_TRUE(m7.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 3,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m12(std::move(m8));\n  EXPECT_TRUE(m12 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m12));\n  EXPECT_TRUE(m12.is_static_buffer());\n  EXPECT_TRUE(m8.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 2,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m13(std::move(m9));\n  EXPECT_TRUE(m13 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m13));\n  EXPECT_FALSE(m13.is_static_buffer());\n  EXPECT_TRUE(m9.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 5,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m14(std::move(m10));\n  EXPECT_TRUE(m14 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m14));\n  EXPECT_TRUE(m14.is_static_buffer());\n  EXPECT_TRUE(m10.empty());\n\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m15{{1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m15 == m_src);\n  EXPECT_TRUE(m15 != m_src);\n  m15 = std::move(m11);\n  EXPECT_TRUE(m15 == m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m15));\n  EXPECT_TRUE(m11.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 3,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m16{{1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m16 == m_src);\n  EXPECT_TRUE(m16 != m_src);\n  m16 = std::move(m_src);\n  EXPECT_TRUE(AssertMapContent_123_123(m16));\n  EXPECT_TRUE(m_src.empty());\n\n  InlineLinearFlatMap<int8_t, std::string, 2,\n                      MapKeyPolicyConsecutiveIntegers<int8_t>>\n      m17{{1, \"11\"}, {2, \"22\"}, {3, \"33\"}};\n  EXPECT_FALSE(m17 == m_src);\n  EXPECT_TRUE(m17 != m_src);\n  m17 = std::move(m_src2);\n  EXPECT_TRUE(AssertMapContent_123_123(m17));\n  EXPECT_TRUE(m_src2.empty());\n}\n\nTEST(LinearMap, SwapIntKeyWithConsecutivePolicy) {\n  {\n    LinearFlatMap<int32_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<int32_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 2,\n                        MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5,\n                        MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<int32_t, std::string, 3,\n                        MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5,\n                        MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m2;\n    m1.swap(m2);\n    EXPECT_TRUE(m1.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(m2.empty());\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<int32_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m2{{-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 2,\n                        MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m2{{-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<int32_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5,\n                        MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m2{{-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<int32_t, std::string, 3,\n                        MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<int32_t, std::string, 5,\n                        MapKeyPolicyConsecutiveIntegers<int32_t>>\n        m2{{-1, \"1\"}, {-2, \"2\"}, {-3, \"3\"}};\n    m1.swap(m2);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n\n    m2.swap(m1);\n    EXPECT_TRUE(AssertMapContent_n1n2n3_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n}\n\nTEST(LinearMap, MergeIntKeyWithConsecutivePolicy) {\n  {\n    LinearFlatMap<uint8_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2{{1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_13_1030(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3,\n                        MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string,\n                  MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(m1.empty());\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3,\n                        MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 3,\n                        MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3,\n                        MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 4,\n                        MapKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2{{1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_1232040(m1));\n    EXPECT_TRUE(AssertMapContent_13_1030(m2));\n  }\n}\n\ntemplate <class K>\nstruct MergeAssignKeyPolicyConsecutiveIntegers\n    : public MapKeyPolicyConsecutiveIntegers<K> {\n  static constexpr auto assign_existing_for_merge = true;\n};\n\nTEST(LinearMap, MergeAssignIntKeyWithConsecutivePolicy) {\n  {\n    LinearFlatMap<uint8_t, std::string,\n                  MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string,\n                  MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string,\n                  MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string,\n                  MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    LinearFlatMap<uint8_t, std::string,\n                  MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string,\n                  MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2{{1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_102302040(m1));\n    EXPECT_TRUE(AssertMapContent_1n234_10203040(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3,\n                        MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    LinearFlatMap<uint8_t, std::string,\n                  MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2;\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(m2.empty());\n    m2.merge(m1);\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3,\n                        MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 3,\n                        MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123_123(m1));\n    EXPECT_TRUE(AssertMapContent_123_123(m2));\n  }\n\n  {\n    InlineLinearFlatMap<uint8_t, std::string, 3,\n                        MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m1{{1, \"1\"}, {2, \"2\"}, {3, \"3\"}};\n    InlineLinearFlatMap<uint8_t, std::string, 4,\n                        MergeAssignKeyPolicyConsecutiveIntegers<uint8_t>>\n        m2{{1, \"10\"}, {-2, \"20\"}, {3, \"30\"}, {4, \"40\"}};\n    m1.merge(m2);\n    EXPECT_TRUE(AssertMapContent_123n24_102302040(m1));\n    EXPECT_TRUE(AssertMapContent_1n234_10203040(m2));\n  }\n}\n\ntemplate <class MAP>\nstatic void IntKeyMapComprehensiveTest() {\n  const int num_elements = 1001;\n  MAP original_map;\n  std::vector<std::pair<const int, std::string>> data_vector;\n  data_vector.reserve(num_elements);\n  for (int i = 0; i < num_elements; ++i) {\n    std::string value = \"Value_\" + std::to_string(i);\n    original_map[i] = value;\n    data_vector.emplace_back(i, value);\n  }\n  // Make sure data is correct\n  ASSERT_EQ(original_map.size(), num_elements);\n  ASSERT_EQ(data_vector.size(), num_elements);\n  // =========================================================================\n  // 2. Test Constructors\n  // =========================================================================\n  // 2.1 Test default constructors\n  MAP default_constructed_map;\n  ASSERT_TRUE(default_constructed_map.empty());\n  // 2.3 Copy Constructor\n  MAP copy_constructed_map(original_map);\n  ASSERT_EQ(copy_constructed_map.size(), num_elements);\n  ASSERT_EQ(copy_constructed_map, original_map);\n  copy_constructed_map.erase(0);\n  ASSERT_NE(copy_constructed_map, original_map);\n  ASSERT_EQ(original_map.size(), num_elements);\n  // 2.4 Move Constructor\n  MAP map_to_move = original_map;\n  MAP move_constructed_map(std::move(map_to_move));\n  ASSERT_EQ(move_constructed_map.size(), num_elements);\n  ASSERT_EQ(move_constructed_map, original_map);\n  ASSERT_TRUE(map_to_move.empty());\n  // Assignment Operators\n  MAP copy_assigned_map;\n  copy_assigned_map[9999] = \"some_value\";\n  copy_assigned_map = original_map;\n  ASSERT_EQ(copy_assigned_map.size(), num_elements);\n  ASSERT_EQ(copy_assigned_map, original_map);\n  copy_assigned_map.erase(1);\n  ASSERT_NE(copy_assigned_map, original_map);\n  ASSERT_EQ(original_map.size(), num_elements);\n  // 3.2 test move\n  MAP map_to_move_assign = original_map;\n  MAP move_assigned_map;\n  move_assigned_map[8888] = \"another_value\";\n  move_assigned_map = std::move(map_to_move_assign);\n  ASSERT_EQ(move_assigned_map.size(), num_elements);\n  ASSERT_EQ(move_assigned_map, original_map);\n  ASSERT_TRUE(map_to_move_assign.empty());\n  // 4. Equality/Inequality\n  MAP map_for_comparison = original_map;\n  ASSERT_TRUE(map_for_comparison == original_map);\n  map_for_comparison[0] = \"Modified_Value\";\n  ASSERT_TRUE(map_for_comparison != original_map);\n  ASSERT_NE(map_for_comparison, original_map);\n\n  MAP loop_test_map = original_map;\n  ASSERT_EQ(loop_test_map.size(), num_elements);\n  for (int i = 0; i < num_elements; ++i) {\n    // 5.1 test find\n    auto it = loop_test_map.find(i);\n    ASSERT_NE(it, loop_test_map.end()) << \"Failed to find key \" << i;\n    ASSERT_EQ(it->first, i);\n    ASSERT_EQ(it->second, \"Value_\" + std::to_string(i));\n    // 5.2 test erase\n    size_t size_before_erase = loop_test_map.size();\n    loop_test_map.erase(it);\n    ASSERT_EQ(loop_test_map.size(), size_before_erase - 1);\n    // make sure erased\n    ASSERT_EQ(loop_test_map.find(i), loop_test_map.end())\n        << \"Key \" << i << \" should have been erased.\";\n    // 5.3 test insert\n    std::string value_to_insert = \"Value_\" + std::to_string(i);\n    auto insert_result = loop_test_map.insert({i, value_to_insert});\n    // test inserted\n    ASSERT_TRUE(insert_result.second) << \"Failed to re-insert key \" << i;\n    // test size\n    ASSERT_EQ(loop_test_map.size(), size_before_erase);\n    // test content of inserted value\n    if constexpr (MAP::consecutive_key) {\n      ASSERT_EQ(*insert_result.first, value_to_insert);\n    } else {\n      ASSERT_EQ(insert_result.first->second, value_to_insert);\n    }\n  }\n  // Map unchanged\n  ASSERT_EQ(loop_test_map, original_map);\n}\n\ntemplate <class MAP>\nstatic void IntKeyMapRandomInsertEraseTest() {\n  const int num_elements = 1000;\n  std::vector<int> keys(num_elements);\n  std::iota(keys.begin(), keys.end(), 0);\n  ASSERT_EQ(keys.size(), num_elements);\n  ASSERT_EQ(keys[0], 0);\n  ASSERT_EQ(keys[num_elements - 1], 999);\n  std::random_device rd;\n  std::mt19937 g(rd());\n  std::shuffle(keys.begin(), keys.end(), g);\n  MAP map_under_test;\n  for (int key : keys) {\n    map_under_test[key] = \"Value_\" + std::to_string(key);\n  }\n  ASSERT_FALSE(map_under_test.empty());\n  ASSERT_EQ(map_under_test.size(), num_elements);\n  ASSERT_EQ(map_under_test.at(0), \"Value_0\");\n  ASSERT_EQ(map_under_test.at(500), \"Value_500\");\n  ASSERT_EQ(map_under_test.at(999), \"Value_999\");\n  std::shuffle(keys.begin(), keys.end(), g);\n  size_t expected_size = num_elements;\n  for (int key_to_erase : keys) {\n    ASSERT_EQ(map_under_test.size(), expected_size);\n    size_t erased_count = map_under_test.erase(key_to_erase);\n    ASSERT_EQ(erased_count, 1) << \"Failed to erase key: \" << key_to_erase;\n    expected_size--;\n  }\n  ASSERT_TRUE(map_under_test.empty())\n      << \"Map should be empty after erasing all elements.\";\n  ASSERT_EQ(map_under_test.size(), 0)\n      << \"Map size should be 0 after erasing all elements.\";\n}\n\nTEST(IntKeyMap, ComprehensiveTestLinearFlat) {\n  IntKeyMapComprehensiveTest<LinearFlatMap<int, std::string>>();\n  IntKeyMapComprehensiveTest<LinearFlatMap<\n      int16_t, std::string, MapKeyPolicyConsecutiveIntegers<int16_t>>>();\n  IntKeyMapComprehensiveTest<LinearFlatMap<\n      int32_t, std::string, MapKeyPolicyConsecutiveIntegers<int16_t>>>();\n  IntKeyMapRandomInsertEraseTest<LinearFlatMap<int, std::string>>();\n  IntKeyMapRandomInsertEraseTest<LinearFlatMap<\n      int16_t, std::string, MapKeyPolicyConsecutiveIntegers<int16_t>>>();\n  IntKeyMapRandomInsertEraseTest<LinearFlatMap<\n      int32_t, std::string, MapKeyPolicyConsecutiveIntegers<int16_t>>>();\n}\n\nTEST(IntKeyMap, ComprehensiveTestOrderedFlat) {\n  IntKeyMapComprehensiveTest<OrderedFlatMap<int, std::string>>();\n  IntKeyMapRandomInsertEraseTest<OrderedFlatMap<int, std::string>>();\n}\n\nTEST(IntKeyMap, ComprehensiveTestLinearFlatWithConsecutivePolicy) {\n  IntKeyMapRandomInsertEraseTest<\n      LinearFlatMap<int, std::string, MapKeyPolicyConsecutiveIntegers<int>>>();\n}\n\nTEST(IntKeyMap, ConsecutivePolicyRangedLoop) {\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      map{{3, \"33\"}, {2, \"22\"}, {1, \"11\"}};\n  int index = 3;\n  for (auto& it : map) {\n    EXPECT_TRUE(it.first == index);\n    EXPECT_TRUE(it.second == std::to_string(index) + std::to_string(index));\n    it.second =\n        std::to_string(index) + std::to_string(index) + std::to_string(index);\n    index--;\n  }\n  EXPECT_TRUE(index == 0);\n  EXPECT_TRUE(map[3] == \"333\");\n  EXPECT_TRUE(map[2] == \"222\");\n  EXPECT_TRUE(map[1] == \"111\");\n}\n\nTEST(IntKeyMap, ConsecutivePolicyRangedLoop2) {\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      map{{3, \"33\"}, {2, \"22\"}, {1, \"11\"}};\n  int index = 3;\n  for (auto& [key, value] : map) {\n    EXPECT_TRUE(key == index);\n    EXPECT_TRUE(value == std::to_string(index) + std::to_string(index));\n    value =\n        std::to_string(index) + std::to_string(index) + std::to_string(index);\n    index--;\n  }\n  EXPECT_TRUE(index == 0);\n  EXPECT_TRUE(map[3] == \"333\");\n  EXPECT_TRUE(map[2] == \"222\");\n  EXPECT_TRUE(map[1] == \"111\");\n}\n\nTEST(IntKeyMap, ConsecutivePolicyIteratorImplicitToPair) {\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      map{{3, \"33\"}, {2, \"22\"}, {1, \"11\"}};\n  std::map<int8_t, std::string> ordered_map(map.begin(), map.end());\n\n  int index = 1;\n  for (auto& [key, value] : ordered_map) {\n    EXPECT_TRUE(key == index);\n    EXPECT_TRUE(value == std::to_string(index) + std::to_string(index));\n    value =\n        std::to_string(index) + std::to_string(index) + std::to_string(index);\n    index++;\n  }\n}\n\nTEST(IntKeyMap, ConsecutivePolicyFrontBack) {\n  {\n    LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n        m{{3, \"33\"}, {2, \"22\"}, {1, \"11\"}};\n    EXPECT_EQ(m.front().first, 3);\n    EXPECT_EQ(m.front().second, \"33\");\n    EXPECT_EQ(m.back().first, 1);\n    EXPECT_EQ(m.back().second, \"11\");\n    m.erase(3);\n    m.erase(1);\n    EXPECT_EQ(m.front().first, 2);\n    EXPECT_EQ(m.front().second, \"22\");\n    EXPECT_EQ(m.back().first, 2);\n    EXPECT_EQ(m.back().second, \"22\");\n\n    m.front().second = \"22222\";\n    EXPECT_EQ(m[2], \"22222\");\n    m.back().second = \"222\";\n    EXPECT_EQ(m[2], \"222\");\n  }\n\n  {\n    const LinearFlatMap<int8_t, std::string,\n                        MapKeyPolicyConsecutiveIntegers<int8_t>>\n        m{{3, \"33\"}, {2, \"22\"}, {1, \"11\"}};\n    EXPECT_EQ(m.front().first, 3);\n    EXPECT_EQ(m.front().second, \"33\");\n    EXPECT_EQ(m.back().first, 1);\n    EXPECT_EQ(m.back().second, \"11\");\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(m)>>&>(m))\n        .erase(3);\n    (const_cast<std::remove_const_t<std::remove_reference_t<decltype(m)>>&>(m))\n        .erase(1);\n    EXPECT_EQ(m.front().first, 2);\n    EXPECT_EQ(m.front().second, \"22\");\n    EXPECT_EQ(m.back().first, 2);\n    EXPECT_EQ(m.back().second, \"22\");\n  }\n}\n\nTEST(IntKeyMap, ConsecutivePolicyFindEqEnd) {\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      empty_map;\n  EXPECT_TRUE(empty_map.find(2) == empty_map.end());\n  EXPECT_TRUE(empty_map.begin() == empty_map.end());\n\n  LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n      map{{3, \"33\"}, {2, \"22\"}, {1, \"11\"}};\n  auto it = map.find(3);\n  auto it2 = map.find(5);\n  auto it3 = map.find(2);\n  EXPECT_TRUE(it == map.begin());\n  EXPECT_TRUE(map.begin() == it);\n  EXPECT_TRUE(it3 != map.begin());\n  EXPECT_TRUE(map.begin() != it3);\n  EXPECT_TRUE(it != map.end());\n  EXPECT_TRUE(map.end() != it);\n  EXPECT_TRUE(it3 != map.end());\n  EXPECT_TRUE(map.end() != it3);\n  EXPECT_TRUE(it2 == map.end());\n  EXPECT_TRUE(map.end() == it2);\n  map.clear();\n  EXPECT_TRUE(map.begin() == map.end());\n}\n\nTEST(IntKeyMap, ConsecutivePolicyErase) {\n  {\n    LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n        map{{3, \"33\"}, {2, \"22\"}, {1, \"11\"}};\n    EXPECT_TRUE(map.size() == 3);\n    auto it = map.find(3);\n    auto it2 = map.erase(it);\n    EXPECT_TRUE(*it2 == \"22\");\n    EXPECT_TRUE(map.size() == 2);\n    it2 = map.erase(it2);\n    EXPECT_TRUE(*it2 == \"11\");\n    EXPECT_TRUE(map.size() == 1);\n    it2 = map.erase(it2);\n    EXPECT_TRUE(map.empty());\n    EXPECT_TRUE(it2 == map.end());\n    EXPECT_TRUE(map.begin() == map.end());\n  }\n\n  {\n    LinearFlatMap<int8_t, std::string, MapKeyPolicyConsecutiveIntegers<int8_t>>\n        map{{3, \"33\"}, {2, \"22\"}, {1, \"11\"}};\n    auto it = map.begin();\n    EXPECT_TRUE(it == map.find(3));\n    EXPECT_TRUE(map.size() == 3);\n    auto it2 = map.erase(it);\n    EXPECT_TRUE(*it2 == \"22\");\n    EXPECT_TRUE(map.size() == 2);\n    it = map.begin();\n    it2 = map.erase(it);\n    EXPECT_TRUE(*it2 == \"11\");\n    EXPECT_TRUE(map.size() == 1);\n    it = map.begin();\n    it2 = map.erase(it);\n    EXPECT_TRUE(map.empty());\n    EXPECT_TRUE(it2 == map.end());\n    EXPECT_TRUE(map.begin() == map.end());\n  }\n}\n\ntemplate <class MAP>\nstatic MAP GenerateStringStringMapRandomInsert() {\n  const int num_elements = 1000;\n  std::vector<int> keys(num_elements);\n  std::iota(keys.begin(), keys.end(), 0);\n  std::random_device rd;\n  std::mt19937 g(rd());\n  std::shuffle(keys.begin(), keys.end(), g);\n  MAP map_under_test;\n  for (int key : keys) {\n    map_under_test[std::string(\"Key_\") + std::to_string(key)] =\n        \"Value_\" + std::to_string(key);\n  }\n  return map_under_test;\n}\n\ntemplate <class MAP>\nstatic MAP GenerateIntStringMapRandomInsert() {\n  const int num_elements = 1000;\n  std::vector<int> keys(num_elements);\n  std::iota(keys.begin(), keys.end(), 0);\n  std::random_device rd;\n  std::mt19937 g(rd());\n  std::shuffle(keys.begin(), keys.end(), g);\n  MAP map_under_test;\n  for (int key : keys) {\n    map_under_test[key] = \"Value_\" + std::to_string(key);\n  }\n  return map_under_test;\n}\n\nTEST(Map, EqualityCheck) {\n  {\n    auto map1 = GenerateStringStringMapRandomInsert<\n        OrderedFlatMap<std::string, std::string>>();\n    auto map2 = GenerateStringStringMapRandomInsert<\n        InlineOrderedFlatMap<std::string, std::string, 500>>();\n    EXPECT_TRUE(map1 == map2);\n    map2[\"Key_500\"] = \"0\";\n    EXPECT_TRUE(map1 != map2);\n    map2[\"Key_500\"] = \"Value_500\";\n    EXPECT_TRUE(map1 == map2);\n    map1.erase(\"Key_100\");\n    EXPECT_TRUE(map1 != map2);\n    map1[\"Key_100\"] = \"Value_100\";\n    EXPECT_TRUE(map1 == map2);\n  }\n\n  {\n    auto map1 =\n        GenerateIntStringMapRandomInsert<OrderedFlatMap<int, std::string>>();\n    auto map2 = GenerateIntStringMapRandomInsert<\n        InlineOrderedFlatMap<int, std::string, 500>>();\n    EXPECT_TRUE(map1 == map2);\n    map2[500] = \"0\";\n    EXPECT_TRUE(map1 != map2);\n    map2[500] = \"Value_500\";\n    EXPECT_TRUE(map1 == map2);\n    map1.erase(100);\n    EXPECT_TRUE(map1 != map2);\n    map1[100] = \"Value_100\";\n    EXPECT_TRUE(map1 == map2);\n  }\n\n  {\n    auto map1 = GenerateStringStringMapRandomInsert<\n        LinearFlatMap<std::string, std::string>>();\n    auto map2 = GenerateStringStringMapRandomInsert<\n        InlineLinearFlatMap<std::string, std::string, 500>>();\n    EXPECT_TRUE(map1 == map2);\n    map2[\"Key_500\"] = \"0\";\n    EXPECT_TRUE(map1 != map2);\n    map2[\"Key_500\"] = \"Value_500\";\n    EXPECT_TRUE(map1 == map2);\n    map1.erase(\"Key_100\");\n    EXPECT_TRUE(map1 != map2);\n    map1[\"Key_100\"] = \"Value_100\";\n    EXPECT_TRUE(map1 == map2);\n  }\n\n  {\n    auto map1 =\n        GenerateIntStringMapRandomInsert<LinearFlatMap<int, std::string>>();\n    auto map2 = GenerateIntStringMapRandomInsert<\n        InlineLinearFlatMap<int, std::string, 500>>();\n    EXPECT_TRUE(map1 == map2);\n    map2[500] = \"0\";\n    EXPECT_TRUE(map1 != map2);\n    map2[500] = \"Value_500\";\n    EXPECT_TRUE(map1 == map2);\n    map1.erase(100);\n    EXPECT_TRUE(map1 != map2);\n    map1[100] = \"Value_100\";\n    EXPECT_TRUE(map1 == map2);\n  }\n\n  {\n    auto map1 = GenerateIntStringMapRandomInsert<LinearFlatMap<\n        int, std::string, MapKeyPolicyConsecutiveIntegers<int>>>();\n    auto map2 = GenerateIntStringMapRandomInsert<InlineLinearFlatMap<\n        int, std::string, 500, MapKeyPolicyConsecutiveIntegers<int>>>();\n    EXPECT_TRUE(map1 == map2);\n    map2[500] = \"0\";\n    EXPECT_TRUE(map1 != map2);\n    map2[500] = \"Value_500\";\n    EXPECT_TRUE(map1 == map2);\n    map1.erase(100);\n    EXPECT_TRUE(map1 != map2);\n    map1[100] = \"Value_100\";\n    EXPECT_TRUE(map1 == map2);\n  }\n}\n\nTEST(LinearFlatMap, for_each) {\n  {\n    LinearFlatMap<std::string, std::string> map{\n        {\"A\", \"1\"}, {\"B\", \"2\"}, {\"C\", \"3\"}};\n    std::string out;\n    map.for_each([&](const std::string& key, std::string& value) {\n      out += key;\n      out += value;\n      if (key == \"B\") {\n        value = \"22\";\n      }\n    });\n    EXPECT_TRUE(map[\"B\"] == \"22\");\n    EXPECT_TRUE(out == \"A1B2C3\");\n\n    const auto map2 = map;\n    map2.for_each([&](const std::string& key, const std::string& value) {\n      out += key;\n      out += value;\n    });\n    EXPECT_TRUE(out == \"A1B2C3A1B22C3\");\n  }\n  {\n    LinearFlatMap<int, std::string> map{{1, \"A\"}, {2, \"B\"}, {3, \"C\"}};\n    std::string out;\n    map.for_each([&](int key, std::string& value) {\n      out += std::to_string(key);\n      out += value;\n      if (key == 2) {\n        value = \"BB\";\n      }\n    });\n    EXPECT_TRUE(map[2] == \"BB\");\n    EXPECT_TRUE(out == \"1A2B3C\");\n\n    const auto map2 = map;\n    map2.for_each([&](int key, const std::string& value) {\n      out += std::to_string(key);\n      out += value;\n    });\n    EXPECT_TRUE(out == \"1A2B3C1A2BB3C\");\n  }\n  {\n    LinearFlatMap<int, std::string, MapKeyPolicyConsecutiveIntegers<int>> map{\n        {1, \"A\"}, {2, \"B\"}, {3, \"C\"}};\n    std::string out;\n    map.for_each([&](int key, std::string& value) {\n      out += std::to_string(key);\n      out += value;\n      if (key == 2) {\n        value = \"BB\";\n      }\n    });\n    EXPECT_TRUE(map[2] == \"BB\");\n    EXPECT_TRUE(out == \"1A2B3C\");\n\n    const auto map2 = map;\n    map2.for_each([&](int key, const std::string& value) {\n      out += std::to_string(key);\n      out += value;\n    });\n    EXPECT_TRUE(out == \"1A2B3C1A2BB3C\");\n  }\n}\n\n}  // namespace base\n}  // namespace lynx\n\n#pragma clang diagnostic pop\n"
  },
  {
    "path": "base/src/version_unittest.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/version_util.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace testing {\n\nconst constexpr Version V_2_12(2, 12);\nconst constexpr Version V_2_6(2, 6);\n\nTEST(VersionTest, checkVersion) {\n  ASSERT_TRUE(Version(\"\") < Version(\"1\"));\n  ASSERT_TRUE(Version(\"sdasdadadafd\") < Version(\"1\"));\n  ASSERT_TRUE(Version(2, 12) == V_2_12);\n  ASSERT_TRUE(Version(2, 11, 1, 2) == Version(2, 11, 1, 2));\n  ASSERT_TRUE(Version(2, 11) != V_2_12);\n  ASSERT_TRUE(Version(2, 8) > V_2_6);\n  ASSERT_TRUE(Version(2, 6) >= V_2_6);\n  ASSERT_TRUE(Version(2, 6) == V_2_6);\n  ASSERT_TRUE(Version(2, 6) <= V_2_6);\n  ASSERT_TRUE(Version(2, 5) < V_2_6);\n}\n\nTEST(VersionTest, ToString) {\n  ASSERT_TRUE(Version(2, 11).ToString() == \"2.11\");\n  ASSERT_TRUE(Version(2, 8).ToString() == \"2.8\");\n  ASSERT_TRUE(Version(2, 6).ToString() == \"2.6\");\n  ASSERT_TRUE(Version(2, 5).ToString() == \"2.5\");\n}\n\n}  // namespace testing\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/README.md",
    "content": "# What is lynx-trace?\n\nlynx-trace is an independent and shareable instrumentation tool that implements Android and Darwin (iOS and macOS) APIs based on perfetto.\n\nFor performance reasons, perfetto uses a large number of static variables to store information, which results in multiple dynamic libraries being unable to share a single copy of the perfetto code. lynx-trace encapsulates the global static variables and APIs of perfetto within the module, exposing only the platform layer interfaces, including [Android APIs](./android/src/main/java/com/lynx/tasm/base/TraceEvent.java), [Darwin APIs](./darwin/LynxTraceEvent.h), and [C++ shortcut trace event macros](./native/trace_event.h) for developers to utilize.\n\nWe don't need to worry about the initialization, configuration, or other setup tasks of perfetto; we can directly call the trace controller interfaces ([Android APIs](./android/src/main/java/com/lynx/tasm/base/TraceController.java), [Darwin APIs](./darwin/LynxTraceController.h), and [C++ APIs](./native/trace_controller_impl.h)) when we need to start or stop tracing.\n\nAdditionally, lynx-trace supports switching the backend to the system trace tool provided by Android for recording instrumentation information. By setting enable_trace=\"systrace\" in GN during the compilation process, the resulting lynxtrace.so will use the Android system trace as the backend to record performance instrumentation data."
  },
  {
    "path": "base/trace/android/.gitignore",
    "content": ".cxx\n/build\n/CMakeLists_impl"
  },
  {
    "path": "base/trace/android/BUILD.gn",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//build/toolchain/clang.gni\")\nimport(\"../../../config.gni\")\n\nassert(is_android)\n\nconfig(\"trace_android_private_config\") {\n  ldflags = [\n    \"-Wl,--exclude-libs,ALL,--gc-sections\",\n    \"-fuse-ld=lld\",\n    \"-Xlinker\",\n    \"-Map=output.map\",\n  ]\n\n  # Specify the symbols that need to be exported in order to hide symbols like `__emutls_get_address` that are not intended to be exported.\n  # lld will export `__emutls_get_address` with enable lto, it's a bug of lld.\n  ldflags +=\n      [ \"-Wl,--version-script,\" + rebase_path(\"lynx_trace_export_symbol.map\") ]\n}\n\ncmake_target(\"LynxTrace\") {\n  cmake_version = \"3.4.1\"\n  target_type = \"shared_library\"\n  output_name = \"lynxtrace\"\n\n  deps = [\n    \":trace_jni_files\",\n    \"../../platform/android:LynxBase\",\n    \"../native:trace\",\n  ]\n\n  libs = [ \"log\" ]\n\n  configs = [\n    \":trace_android_private_config\",\n    \"../../../platform/android:16kb_page\",\n  ]\n}\n\ngroup(\"trace_jni_files\") {\n  exec_script(\"//tools_shared/jni_generator/generate_and_register_jni_files.py\",\n              [\n                \"-root\",\n                rebase_path(\"../../../\"),\n                \"-path\",\n                rebase_path(\"./src/main/jni/jni_configs.yml\"),\n                \"--use-base-jni-header\",\n              ])\n  public_deps = [ \"src/main/jni:build\" ]\n}\n"
  },
  {
    "path": "base/trace/android/CMakeLists.txt",
    "content": "# Set the minimum version of CMAKE that is required\ncmake_minimum_required(VERSION 3.4.1)\n\n# According to the build variant, \n# import the CMakeLists-impl.cmake file to compile the native methods.\ninclude(${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists_impl/${FLAVOR_NAME}${BUILD_TYPE}${ANDROID_ABI}/CMakeLists.txt)\n"
  },
  {
    "path": "base/trace/android/build.gradle",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\napply plugin: 'com.android.library'\napply from: '../../../platform/android/publish.gradle'\nimport org.json.simple.JSONObject\n\next.ABI_FILTERS = ['armeabi-v7a', 'armeabi', 'arm64-v8a', 'x86']\n\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n    ndkVersion rootProject.ext.ndkVersion\n\n    defaultConfig {\n        minSdkVersion 16\n        targetSdkVersion 22\n        versionCode 1\n        versionName \"1.0\"\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles \"consumer-rules.pro\"\n        multiDexEnabled true\n\n        buildConfigField(\"String\",\"enable_trace\",\"\\\"$enable_trace\\\"\")\n\n        buildConfigField(\"String\",\"VERSION\",\"\\\"${VERSION}\\\"\")\n\n        packagingOptions {\n            exclude 'lib/*/libc++_shared.so'\n            exclude 'lib/*/liblynxbase.so'\n        }\n\n    }\n\n    flavorDimensions \"lynx\"\n    productFlavors {\n        debugMode {\n            dimension \"lynx\"\n            externalNativeBuild {\n                cmake {\n                    cppFlags '-Wl,-ldl',\n                            '-Wno-unused-command-line-argument'\n                    arguments '-DFLAVOR_NAME=debugMode'\n\n                }\n            }\n        }\n        asan {\n            dimension \"lynx\"\n            externalNativeBuild {\n                cmake {\n                    cppFlags '-fsanitize=address',\n                            '-fsanitize-address-use-after-scope',\n                            '-fPIE',\n                            '-Wl,-ldl',\n                            '-Wl,-llog',\n                            '-Wno-unused-command-line-argument'\n                    arguments '-DANDROID_ARM_MODE=arm', '-DFLAVOR_NAME=asan'\n                    abiFilters(*rootProject.ext.abiList)\n                }\n            }\n        }\n        noasan {\n            dimension \"lynx\"\n            externalNativeBuild {\n                cmake {\n                    arguments '-DFLAVOR_NAME=noasan'\n                }\n            }\n        }\n        dev {\n            dimension \"lynx\"\n            matchingFallbacks = [\"noasan\"]\n            externalNativeBuild {\n                cmake {\n                    arguments '-DFLAVOR_NAME=dev'\n                }\n            }\n        }\n    }\n\n    buildTypes {\n        release {\n            externalNativeBuild {\n                cmake {\n                    arguments '-DANDROID_PLATFORM=android-14',\n                            '-DBUILD_LEPUS_COMPILE=false',\n                            '-DANDROID_TOOLCHAIN=clang',\n                            '-DANDROID_STL='+getCppLib(),\n                            '-DCMAKE_BUILD_TYPE=Release',\n                            '-DLOCAL_ARM_NEON=true',\n                            '-DBUILD_TYPE=release',\n                            '-LH'// Show compile parameters.\n                    cppFlags '-Wl,-ldl',\n                            '-Wno-unused-command-line-argument'\n                }\n            }\n            ndk {\n                abiFilters(*rootProject.ext.abiList)\n            }\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n        debug {\n            externalNativeBuild {\n                cmake {\n                    arguments '-DANDROID_PLATFORM=android-14',\n                            '-DBUILD_LEPUS_COMPILE=false',\n                            '-DANDROID_TOOLCHAIN=clang',\n                            '-DANDROID_STL='+getCppLib(),\n                            '-DLOCAL_ARM_NEON=true',\n                            '-DBUILD_TYPE=debug',\n                            '-LH'// Show compile parameters.\n\n                    // Symbol table with line numbers.\n                    cppFlags \"-g\",\n                            '-Wl,-ldl',\n                            '-Wno-unused-command-line-argument'\n                }\n            }\n            ndk {\n                abiFilters(*rootProject.ext.abiList)\n            }\n\n            if (enable_lite_production == \"true\") {\n                buildConfigField \"String\", \"enable_trace\", \"\\\"none\\\"\"\n            } else if (enable_trace == \"none\") {\n                buildConfigField \"String\", \"enable_trace\", \"\\\"perfetto\\\"\"\n            } else {\n                buildConfigField \"String\", \"enable_trace\", \"\\\"${enable_trace}\\\"\"\n            }\n            \n            debuggable true\n        }\n    }\n\n   externalNativeBuild {\n       cmake {\n           path \"CMakeLists.txt\"\n           version CMAKE_VERSION\n       }\n\n   }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_8\n        targetCompatibility JavaVersion.VERSION_1_8\n    }\n}\n\n// Configure the compilation parameters in gn to generate the corresponding CMakeLists.txt files.\ntask configGnCompileParas {\n    def buildVariantList = getFlavorNamesAndBuildTypes(project)\n    def flavorNames = buildVariantList[0]\n    def buildTypes = buildVariantList[1]\n    JSONObject gnBuildArgs_map = new JSONObject()\n    flavorNames.each{\n        JSONObject gnBuildArgsList = new JSONObject()\n        String flavorName = it\n        if (flavorName == \"asan\") {\n            gnBuildArgsList.put(\"is_asan\", \"true\")\n        } else {\n            gnBuildArgsList.put(\"is_asan\", \"false\")\n        }\n\n        buildTypes.each{\n            JSONObject gnBuildArgs_list = new JSONObject(gnBuildArgsList)\n            String buildType = it\n            if (buildType == \"release\") {\n                gnBuildArgs_list.put(\"is_debug\", \"false\")\n                if (enable_lite_production == \"true\") {\n                    gnBuildArgs_list.put(\"enable_trace\", \"none\")\n                } else {\n                    gnBuildArgs_list.put(\"enable_trace\", \"\\\"${enable_trace}\\\"\")\n                }\n            } else if(buildType == \"debug\") {\n                gnBuildArgs_list.put(\"is_debug\", \"true\")\n                if (enable_lite_production == \"true\") {\n                    gnBuildArgs_list.put(\"enable_trace\", \"none\")\n                } else if (enable_trace == \"none\") {\n                    gnBuildArgs_list.put(\"enable_trace\", \"perfetto\")\n                } else {\n                    gnBuildArgs_list.put(\"enable_trace\", \"\\\"${enable_trace}\\\"\")\n                }\n            }\n            rootProject.ext.abiList.each { abi ->\n                gnBuildArgs_map.put(flavorName+buildType+abi, gnBuildArgs_list)\n            }\n        }\n    }\n    writeGnArgs(gnBuildArgs_map.toJSONString())\n}\n\ndependencies {\n    compileOnly 'androidx.annotation:annotation:1.0.0'\n    implementation project(':LynxBase')\n}"
  },
  {
    "path": "base/trace/android/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# Whether to enable trace\n# There are three modes: \"perfetto\", \"systrace\", \"none\"\n# 1. \"perfetto\" : enable perfetto trace, default value\n# 2. \"systrace\" : enable system trace, only for Android\n# 3. \"none\" : disable trace\nenable_trace=none\n\nARTIFACT_GROUP=org.lynxsdk.lynx\nARTIFACT_NAME=lynx-trace\nDESCRIPTION=Lynx Trace\nREPOSITORY_URL=https://github.com/lynx-family/lynx\nREPOSITORY_SSH_URL=github.com/lynx-family/lynx.git\nDEVELOPER_NAME=lynx\nDEVELOPER_EMAIL=lynx.authors@gmail.com"
  },
  {
    "path": "base/trace/android/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n# Classes retained using the @Keep annotation.\n-dontwarn android.support.annotation.Keep\n-keep @android.support.annotation.Keep class **\n-keep @android.support.annotation.Keep class ** {\n    @android.support.annotation.Keep <fields>;\n    @android.support.annotation.Keep <methods>;\n}\n-dontwarn androidx.annotation.Keep\n-keep @androidx.annotation.Keep class **\n-keep @androidx.annotation.Keep class ** {\n    @androidx.annotation.Keep <fields>;\n    @androidx.annotation.Keep <methods>;\n}\n\n-keepclasseswithmembers,includedescriptorclasses class * {\n    native <methods>;\n}\n-keepclasseswithmembers class * {\n    @com.lynx.trace.CalledByNative <methods>;\n}\n\n"
  },
  {
    "path": "base/trace/android/src/android_test/java/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.lynx.trace.test\">\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n</manifest>"
  },
  {
    "path": "base/trace/android/src/android_test/java/com/lynx/trace/TraceControllerTest.java",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.trace;\n\nimport static org.mockito.Mockito.*;\n\nimport android.content.Context;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.platform.app.InstrumentationRegistry;\nimport java.util.HashMap;\nimport java.util.Map;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\n@RunWith(AndroidJUnit4.class)\npublic class TraceControllerTest {\n  @Before\n  public void setUp() {\n    Context context =\n        InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();\n    TraceController.getInstance().init(context);\n  }\n\n  @Test\n  public void startTracing() {\n    TraceController controller = TraceController.getInstance();\n    TraceController spyController = spy(controller);\n    Map<String, String> config = new HashMap<>();\n    config.put(\"trace_file\", \"trace.proto\");\n    config.put(\"buffer_size\", \"1024\");\n    config.put(\"enable_systrace\", \"true\");\n    TraceController.CompleteCallback callback = new TraceController.CompleteCallback() {\n      @Override\n      public void onComplete(String traceFile) {}\n    };\n    spyController.startTracing(callback, config);\n\n    verify(spyController).startTracing(1024, null, null, \"trace.proto\", true);\n  }\n}\n"
  },
  {
    "path": "base/trace/android/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.lynx.trace\">\n\n</manifest>"
  },
  {
    "path": "base/trace/android/src/main/java/com/lynx/tasm/base/LynxTraceEnv.java",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.tasm.base;\n\nimport android.util.Log;\nimport com.lynx.tasm.base.TraceEvent;\n\npublic class LynxTraceEnv {\n  private static LynxTraceEnv sInstance;\n  private volatile boolean mIsNativeLibraryLoaded = false;\n\n  public static LynxTraceEnv inst() {\n    if (sInstance == null) {\n      synchronized (LynxTraceEnv.class) {\n        if (sInstance == null) {\n          sInstance = new LynxTraceEnv();\n        }\n      }\n    }\n    return sInstance;\n  }\n\n  private LynxTraceEnv() {}\n\n  public boolean isNativeLibraryLoaded() {\n    return mIsNativeLibraryLoaded;\n  }\n\n  public void markNativeLibraryLoaded(boolean status) {\n    mIsNativeLibraryLoaded = status;\n    TraceEvent.markTraceEnvInited(status);\n  }\n\n  public boolean init() {\n    if (mIsNativeLibraryLoaded) {\n      return mIsNativeLibraryLoaded;\n    }\n    mIsNativeLibraryLoaded = loadNativeTraceLibrary();\n    TraceEvent.markTraceEnvInited(mIsNativeLibraryLoaded);\n    return mIsNativeLibraryLoaded;\n  }\n\n  public boolean loadNativeTraceLibrary() {\n    if (mIsNativeLibraryLoaded) {\n      return mIsNativeLibraryLoaded;\n    }\n    try {\n      System.loadLibrary(\"lynxtrace\");\n      return true;\n    } catch (Exception e) {\n      Log.e(\"trace env init\", \"failed to load liblynxtrace.so\");\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "base/trace/android/src/main/java/com/lynx/tasm/base/TraceController.java",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.tasm.base;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.os.Build;\nimport android.os.Environment;\nimport android.os.Trace;\nimport android.text.TextUtils;\nimport android.util.JsonReader;\nimport android.util.Log;\nimport android.widget.Toast;\nimport com.lynx.tasm.base.LynxTraceEnv;\nimport com.lynx.tasm.base.TraceEvent;\nimport com.lynx.trace.BuildConfig;\nimport com.lynx.trace.CalledByNative;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.lang.reflect.Field;\nimport java.text.SimpleDateFormat;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport java.util.Timer;\nimport java.util.TimerTask;\n\n/**\n * Helper class for tracing Lynx App, use 'am broadcast' to start/stop tracing:\n *    adb shell am broadcast -a com.lynx.uiapp.LYNX_TRACE_START\n *    adb shell am broadcast -a com.lynx.uiapp.LYNX_TRACE_STOP\n */\n@SuppressWarnings(\"JniMissingFunction\")\npublic class TraceController {\n  private static final String ACTION_START = \"LYNX_TRACE_START\";\n  private static final String ACTION_STOP = \"LYNX_TRACE_STOP\";\n  private static final String CATEGORIES_EXTRA = \"categories\";\n  private static final String FILE_EXTRA = \"file\";\n  private static final String BUFFER_SIZE_EXTRA = \"buffer\";\n  private static final String NATIVE_ONLY_EXTRA = \"nativeOnly\";\n  private static final String ENABLE_COMPRESS = \"enableCompress\";\n  private static final String TAG = \"Lynx startup trace\";\n  private static final int DEFAULT_BUFFER_SIZE = 40960; // kb\n\n  private static final long ATRACE_TAG_ALL = ~((-1L) << 27);\n\n  private Context mContext;\n  private List<CompleteCallback> mCompleteCallbacks = new ArrayList<>();\n  private TraceBroadcastReceiver mBroadcastReceiver;\n  private static boolean mTracingStarted = false;\n  private long mNativeTraceController = 0;\n  private int tracingSession = -1;\n  // Control Whether Record Java Trace or Not\n  private static boolean sNativeTracingOnly = false;\n  private static boolean isTraceEnvInit = false;\n  private String traceFilePath;\n\n  private TraceController() {\n    try {\n      if (mNativeTraceController == 0 && isTraceEnvInited()) {\n        mNativeTraceController = nativeCreateTraceController();\n      }\n    } catch (java.lang.UnsatisfiedLinkError e) {\n      Log.w(TAG, \"failed to create NativeTraceController\", e);\n    } catch (Exception e) {\n      Log.w(TAG, \"failed to create NativeTraceController\", e);\n    }\n    if (mNativeTraceController == 0) {\n      Log.w(TAG, \"failed to create NativeTraceController\");\n      return;\n    }\n  }\n\n  private static class TraceControllerLoader {\n    private static final TraceController INSTANCE = new TraceController();\n  }\n\n  public interface CompleteCallback {\n    void onComplete(String traceFile);\n  }\n\n  public static boolean isNativeTracingOnly() {\n    return sNativeTracingOnly;\n  }\n\n  public static TraceController getInstance() {\n    return TraceControllerLoader.INSTANCE;\n  }\n\n  public long getNativeTraceController() {\n    return mNativeTraceController;\n  }\n\n  public String startTrace() {\n    File file = getFile();\n    String fileName = file.getPath();\n    startTracing(DEFAULT_BUFFER_SIZE, null, null, fileName, false, false);\n    String logMessage = \"Trace started at: \" + fileName;\n    Toast.makeText(mContext, logMessage, Toast.LENGTH_SHORT).show();\n    Log.i(TAG, logMessage);\n    return file.getAbsolutePath();\n  }\n\n  public void stopTrace() {\n    stopTracing();\n    Toast.makeText(mContext, \"Trace stopped\", Toast.LENGTH_SHORT).show();\n    Log.i(TAG, \"Trace stopped\");\n  }\n\n  public void startStartupTracingIfNeeded() {\n    if (mNativeTraceController != 0) {\n      nativeStartStartupTracingIfNeeded(mNativeTraceController);\n    }\n  }\n\n  public void startTracing(CompleteCallback callback, String config) {\n    mCompleteCallbacks.add(callback);\n    String traceFile = generateTracingFileName();\n    startTracing(DEFAULT_BUFFER_SIZE, null, null, traceFile, false, false);\n  }\n\n  public void startTracing(CompleteCallback callback, Map<String, String> config) {\n    mCompleteCallbacks.add(callback);\n    String traceFile = generateTracingFileName();\n    Boolean enableSystrace = false;\n    Boolean enableCompress = false;\n    int bufferSize = DEFAULT_BUFFER_SIZE;\n    if (config.containsKey(\"trace_file\")) {\n      traceFile = config.get(\"trace_file\");\n    }\n    if (config.containsKey(\"buffer_size\")) {\n      bufferSize = Integer.parseInt(config.get(\"buffer_size\"));\n    }\n    if (config.containsKey(\"enable_systrace\")) {\n      enableSystrace = Boolean.parseBoolean(config.get(\"enable_systrace\"));\n    }\n    if (config.containsKey(\"enable_compress\")) {\n      enableCompress = Boolean.parseBoolean(config.get(\"enable_compress\"));\n    }\n    startTracing(bufferSize, null, null, traceFile, enableSystrace, enableCompress);\n  }\n\n  public void stopTracing() {\n    if (mNativeTraceController == 0 || !mTracingStarted) {\n      return;\n    }\n    mTracingStarted = false;\n    nativeStopTracing(mNativeTraceController, tracingSession);\n    if (!traceFilePath.isEmpty()) {\n      onTracingComplete(traceFilePath);\n      traceFilePath = \"\";\n    }\n  }\n\n  public static boolean isTracingStarted() {\n    return mTracingStarted;\n  }\n\n  @Deprecated\n  public void recordClockSyncMarker(String syncId) {}\n\n  public void onTracingComplete(String traceFile) {\n    // callback only work once\n    for (CompleteCallback callback : mCompleteCallbacks) {\n      callback.onComplete(traceFile);\n    }\n    mCompleteCallbacks.clear();\n  }\n\n  private String generateTracingFileName() {\n    File file = getFile();\n    return file.getPath();\n  }\n\n  @CalledByNative\n  private String generateTracingFileDir() {\n    return mContext.getExternalFilesDir(null).getPath();\n  }\n\n  private File getFile() {\n    int pid = android.os.Process.myPid();\n    SimpleDateFormat formatter = new SimpleDateFormat(\"yyyy-MM-dd-HHmmss\", Locale.US);\n    formatter.setTimeZone(TimeZone.getTimeZone(\"UTC\"));\n    File dir = mContext.getExternalFilesDir(null);\n    return new File(dir, \"lynx-profile-trace-\" + pid + \"-\" + formatter.format(new Date()));\n  }\n\n  @CalledByNative\n  private void setIsTracingStarted(boolean is_tracing_started) {\n    mTracingStarted = is_tracing_started;\n  }\n\n  @CalledByNative\n  private void refreshATraceTags() {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n      try {\n        Field field = Trace.class.getDeclaredField(\"sEnabledTags\");\n        field.setAccessible(true);\n        field.setLong(null, ATRACE_TAG_ALL);\n      } catch (Throwable e) {\n        e.printStackTrace();\n      }\n    }\n  }\n\n  private static boolean isTraceEnvInited() {\n    if (isTraceEnvInit) {\n      return isTraceEnvInit;\n    }\n    isTraceEnvInit = LynxTraceEnv.inst().init();\n    return isTraceEnvInit;\n  }\n\n  protected void startTracing(int bufferSize, String[] includeCategories,\n      String[] excludeCategories, String traceFile, boolean enableSystrace,\n      boolean enableCompress) {\n    if (mTracingStarted) {\n      Toast.makeText(mContext, \"Trace already started, please stop it first\", Toast.LENGTH_SHORT)\n          .show();\n      return;\n    }\n    if (mNativeTraceController == 0) {\n      Log.w(TAG, \"tracing not enabled\");\n      return;\n    }\n    mTracingStarted = true;\n    traceFilePath = traceFile.isEmpty() ? getFile().getPath() : traceFile;\n    tracingSession = nativeStartTracing(mNativeTraceController, bufferSize, includeCategories,\n        excludeCategories, traceFilePath, enableSystrace, enableCompress);\n    Map<String, String> args = new HashMap<>();\n    args.put(\"Version\", BuildConfig.VERSION);\n    TraceEvent.instant(TraceEvent.CATEGORY_VITALS, \"Version\", args);\n  }\n\n  private static class TraceIntentFilter extends IntentFilter {\n    public TraceIntentFilter(Context context) {\n      addAction(context.getPackageName() + \".\" + ACTION_START);\n      addAction(context.getPackageName() + \".\" + ACTION_STOP);\n    }\n  }\n\n  public void init(Context context) {\n    mContext = context;\n    if (TraceEvent.enableTrace()) {\n      mBroadcastReceiver = new TraceBroadcastReceiver();\n      IntentFilter filter = new TraceIntentFilter(mContext);\n      // Android 14 (API level 34) or higher must specify a flag to indicate\n      // whether or not the receiver should be exported to all other apps on the device\n      // using context-registered\n      // <p>\n      // https://developer.android.com/about/versions/14/behavior-changes-14#runtime-receivers-exported\n      // Todo(suguannan.906): replace 34 with Build.VERSION_CODES.UPSIDE_DOWN_CAKE\n      //  after upgrading compileSdkVerion to 34 or higher\n      if (Build.VERSION.SDK_INT >= 34 && context.getApplicationInfo().targetSdkVersion >= 34) {\n        // 0x2 means Context.RECEIVER_EXPORTED\n        // <p>\n        // https://developer.android.com/reference/android/content/Context.html?hl=en#RECEIVER_EXPORTED\n        // Todo(suguannan.906): replace 0x2 to Context.RECEIVER_EXPORTED\n        //  after upgrading compileSdkVerion to 34 or higher\n        mContext.registerReceiver(mBroadcastReceiver, filter, 0x2);\n      } else {\n        mContext.registerReceiver(mBroadcastReceiver, filter);\n      }\n    }\n  }\n\n  public void onTerminate() {\n    if (TraceEvent.enableTrace()) {\n      mContext.unregisterReceiver(mBroadcastReceiver);\n    }\n    mContext = null;\n  }\n\n  class TraceBroadcastReceiver extends BroadcastReceiver {\n    @Override\n    public void onReceive(Context context, Intent intent) {\n      if (intent.getAction().endsWith(ACTION_START)) {\n        String categories = intent.getStringExtra(CATEGORIES_EXTRA);\n        String filename = intent.getStringExtra(FILE_EXTRA);\n        int bufferSize = intent.getIntExtra(BUFFER_SIZE_EXTRA, DEFAULT_BUFFER_SIZE);\n\n        boolean isNativeOnly = intent.getBooleanExtra(NATIVE_ONLY_EXTRA, false);\n        sNativeTracingOnly = isNativeOnly;\n        boolean enableCompress = intent.getBooleanExtra(ENABLE_COMPRESS, false);\n\n        if (filename == null) {\n          filename = generateTracingFileName();\n        }\n\n        startTracing(bufferSize, categories != null ? categories.split(\",\") : null, null, filename,\n            false, enableCompress);\n        String logMessage = \"Trace started at: \" + filename;\n        Toast.makeText(context, logMessage, Toast.LENGTH_SHORT).show();\n        Log.i(TAG, logMessage);\n      } else if (intent.getAction().endsWith(ACTION_STOP)) {\n        sNativeTracingOnly = false;\n        stopTracing();\n        Toast.makeText(context, \"Trace stopped\", Toast.LENGTH_SHORT).show();\n        Log.i(TAG, \"Trace stopped\");\n      }\n    }\n  }\n\n  private native long nativeCreateTraceController();\n  private native int nativeStartTracing(long ptr, int bufferSize, String[] includeCategories,\n      String[] excludeCategories, String traceFile, boolean enableSystrace, boolean enableCompress);\n  private native void nativeStopTracing(long ptr, int sessionId);\n  private native void nativeStartStartupTracingIfNeeded(long ptr);\n}\n"
  },
  {
    "path": "base/trace/android/src/main/java/com/lynx/tasm/base/TraceEvent.java",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.tasm.base;\n\nimport android.os.Trace;\nimport com.lynx.trace.BuildConfig;\nimport java.util.Map;\nimport java.util.Random;\n\npublic class TraceEvent {\n  public static final long CATEGORY_DEFAULT = 0;\n  public static final long CATEGORY_VITALS = 1L;\n  public static final long CATEGORY_SCREENSHOTS = 2L;\n  public static final long CATEGORY_FPS = 3L;\n  public static final String DEFAULT_INSTANT_COLOR = \"#FF0000\";\n\n  public static final String[] defualt_categories = {\"lynx\", \"vitals\", \"screenshot\", \"fps\"};\n\n  private static boolean sTraceEnvInited = false;\n  private static boolean sDebugModeEnabled = false;\n\n  private static boolean sPerfettoTraceEnabled = false;\n  private static boolean sSystemTraceEnabled = false;\n\n  private static String getRandomColor() {\n    if (TraceController.isNativeTracingOnly()) {\n      return DEFAULT_INSTANT_COLOR;\n    }\n    if (!enableTrace() || !isTracingStarted()) {\n      return DEFAULT_INSTANT_COLOR;\n    }\n    try {\n      StringBuilder result = new StringBuilder();\n      result.append('#');\n      for (int i = 0; i < 6; i++) {\n        result.append(Integer.toHexString(new Random().nextInt(16)));\n      }\n      return result.toString().toUpperCase();\n    } catch (Exception e) {\n      return DEFAULT_INSTANT_COLOR;\n    }\n  }\n\n  public static void markTraceEnvInited(boolean isInit) {\n    sTraceEnvInited = isInit;\n  }\n\n  public static void markTraceDebugMode(boolean isDebugModeEnabled) {\n    sDebugModeEnabled = isDebugModeEnabled;\n  }\n\n  public static void beginSection(String sectionName) {\n    beginSection(CATEGORY_DEFAULT, sectionName);\n  }\n\n  public static void endSection(String sectionName) {\n    endSection(CATEGORY_DEFAULT, sectionName);\n  }\n\n  @Deprecated\n  public static void beginSection(long category, String sectionName) {\n    beginSection(defualt_categories[(int) category], sectionName);\n  }\n\n  public static void beginSection(String category, String sectionName) {\n    if (TraceController.isNativeTracingOnly()) {\n      return;\n    }\n    if (enableTrace()) {\n      if (enablePerfettoTrace() && isTracingStarted()) {\n        nativeBeginSection(category, sectionName);\n      } else {\n        Trace.beginSection(sectionName);\n      }\n    }\n  }\n\n  @Deprecated\n  public static void endSection(long category, String sectionName) {\n    endSection(defualt_categories[(int) category], sectionName);\n  }\n\n  public static void endSection(String category, String sectionName) {\n    if (TraceController.isNativeTracingOnly()) {\n      return;\n    }\n    if (enableTrace()) {\n      if (enablePerfettoTrace() && isTracingStarted()) {\n        nativeEndSection(category, sectionName);\n      } else {\n        Trace.endSection();\n      }\n    }\n  }\n\n  public static void beginSection(String sectionName, Map<String, String> props) {\n    beginSection(CATEGORY_DEFAULT, sectionName, props);\n  }\n\n  @Deprecated\n  public static void beginSection(long category, String sectionName, Map<String, String> props) {\n    beginSection(defualt_categories[(int) category], sectionName, props);\n  }\n\n  public static void beginSection(String category, String sectionName, Map<String, String> props) {\n    if (TraceController.isNativeTracingOnly()) {\n      return;\n    }\n    if (enableTrace()) {\n      if (enablePerfettoTrace() && isTracingStarted()) {\n        nativeBeginSectionWithProps(category, sectionName, props);\n      } else {\n        Trace.beginSection(sectionName);\n      }\n    }\n  }\n\n  @Deprecated\n  public static void endSection(long category, String sectionName, Map<String, String> props) {\n    endSection(defualt_categories[(int) category], sectionName, props);\n  }\n\n  public static void endSection(String category, String sectionName, Map<String, String> props) {\n    if (TraceController.isNativeTracingOnly()) {\n      return;\n    }\n    if (enableTrace()) {\n      if (enablePerfettoTrace() && isTracingStarted()) {\n        nativeEndSectionWithProps(category, sectionName, props);\n      } else {\n        Trace.endSection();\n      }\n    }\n  }\n\n  @Deprecated\n  public static void instant(long category, String eventName) {\n    instant(defualt_categories[(int) category], eventName, System.nanoTime() / 1000);\n  }\n\n  public static void instant(String category, String eventName) {\n    instant(category, eventName, System.nanoTime() / 1000);\n  }\n\n  @Deprecated\n  public static void instant(long category, String eventName, long timestamp) {\n    instant(defualt_categories[(int) category], eventName, timestamp, getRandomColor());\n  }\n\n  public static void instant(String category, String eventName, long timestamp) {\n    instant(category, eventName, timestamp, getRandomColor());\n  }\n\n  @Deprecated\n  public static void instant(long category, String eventName, String color) {\n    instant(defualt_categories[(int) category], eventName, System.nanoTime() / 1000, color);\n  }\n\n  public static void instant(String category, String eventName, String color) {\n    instant(category, eventName, System.nanoTime() / 1000, color);\n  }\n\n  @Deprecated\n  public static void instant(long category, String eventName, Map<String, String> props) {\n    instant(defualt_categories[(int) category], eventName, props);\n  }\n\n  public static void instant(String category, String eventName, Map<String, String> props) {\n    if (enableTrace()) {\n      if (enablePerfettoTrace() && isTracingStarted()) {\n        nativeInstantWithProps(category, eventName, System.nanoTime() / 1000, props);\n      } else {\n        Trace.beginSection(eventName);\n        Trace.endSection();\n      }\n    }\n  }\n\n  @Deprecated\n  public static void counter(long category, String name, long counterValue) {\n    counter(defualt_categories[(int) category], name, counterValue);\n  }\n\n  public static void counter(String category, String name, long counterValue) {\n    if (TraceController.isNativeTracingOnly()) {\n      return;\n    }\n    if (enableTrace() && isTracingStarted()) {\n      nativeCounter(category, name, counterValue);\n    }\n  }\n\n  @Deprecated\n  public static boolean registerTraceBackend(long ptr) {\n    return false;\n  }\n\n  @Deprecated\n  public static boolean categoryEnabled(long category) {\n    return categoryEnabled(defualt_categories[(int) category]);\n  }\n\n  public static boolean categoryEnabled(String category) {\n    return enableTrace() && nativeCategoryEnabled(category);\n  }\n\n  public static boolean enableTrace() {\n    return BuildConfig.enable_trace == \"perfetto\" || BuildConfig.enable_trace == \"systrace\"\n        || sDebugModeEnabled;\n  }\n\n  public static boolean enableSystemTrace() {\n    if (!sSystemTraceEnabled && sTraceEnvInited) {\n      sSystemTraceEnabled = nativeSystemTraceEnabled();\n    }\n    return sSystemTraceEnabled;\n  }\n\n  public static boolean enablePerfettoTrace() {\n    if (!sPerfettoTraceEnabled && sTraceEnvInited) {\n      sPerfettoTraceEnabled = nativePerfettoTraceEnabled();\n    }\n    return sPerfettoTraceEnabled;\n  }\n\n  public static boolean isTracingStarted() {\n    return TraceController.isTracingStarted();\n  }\n\n  private static void instant(String category, String eventName, long timestamp, String color) {\n    if (enableTrace()) {\n      if (enablePerfettoTrace() && isTracingStarted()) {\n        nativeInstant(category, eventName, timestamp, color);\n      } else {\n        Trace.beginSection(eventName);\n        Trace.endSection();\n      }\n    }\n  }\n\n  public static void instant(\n      String category, String eventName, long timestamp, Map<String, String> props) {\n    if (enableTrace()) {\n      if (enablePerfettoTrace() && isTracingStarted()) {\n        timestamp = timestamp > 0 ? timestamp : System.nanoTime() / 1000;\n        nativeInstantWithProps(category, eventName, timestamp, props);\n      } else {\n        Trace.beginSection(eventName);\n        Trace.endSection();\n      }\n    }\n  }\n\n  private static native void nativeBeginSection(String category, String sectionName);\n  private static native void nativeBeginSectionWithProps(\n      String category, String sectionName, Map<String, String> props);\n  private static native void nativeEndSection(String category, String sectionName);\n  private static native void nativeEndSectionWithProps(\n      String category, String sectionName, Map<String, String> props);\n  private static native void nativeInstant(\n      String category, String sectionName, long timestamp, String color);\n  private static native void nativeInstantWithProps(\n      String category, String sectionName, long timestamp, Map<String, String> props);\n  private static native void nativeCounter(String category, String name, long counterValue);\n  private static native boolean nativeCategoryEnabled(String category);\n\n  private static native boolean nativeSystemTraceEnabled();\n  private static native boolean nativePerfettoTraceEnabled();\n}\n"
  },
  {
    "path": "base/trace/android/src/main/java/com/lynx/trace/CalledByNative.java",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\npackage com.lynx.trace;\n\nimport static java.lang.annotation.ElementType.METHOD;\nimport static java.lang.annotation.RetentionPolicy.CLASS;\n\nimport androidx.annotation.Keep;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\n@Documented\n@Retention(CLASS)\n@Target({METHOD})\n@Keep\npublic @interface CalledByNative {}\n"
  },
  {
    "path": "base/trace/android/src/main/jni/.gitignore",
    "content": "gen/\n"
  },
  {
    "path": "base/trace/android/src/main/jni/BUILD.gn",
    "content": "# This file is autogenerated.\n\n# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nsource_set(\"build\") {\n  sources = [\n    \"gen/TraceController_jni.h\",\n    \"gen/TraceController_register_jni.h\",\n    \"gen/TraceEvent_jni.h\",\n    \"gen/TraceEvent_register_jni.h\",\n    \"gen/lynx_trace_so_load.cc\",\n  ]\n  configs += [\n    \"../../../../native:trace_private_config\",\n    \"../../../../native:trace_public_config\",\n  ]\n}\n"
  },
  {
    "path": "base/trace/android/src/main/jni/jni_configs.yml",
    "content": "# jni register file configs.\njni_register_configs:\n  output_path: base/trace/android/src/main/jni/gen/lynx_trace_so_load.cc\n  namespaces:\n    - lynxtrace\n  custom_headers:\n    - base/include/platform/android/jni_utils.h\n# gn file configs.\ngn_configs:\n  output_path: base/trace/android/src/main/jni/BUILD.gn\n  template_name: source_set\n  custom_configs:\n    - base/trace/native:trace_private_config\n    - base/trace/native:trace_public_config\n# jni files configs.\njni_class_configs:\n  output_dir: base/trace/android/src/main/jni/gen\n  jni_classes:\n    - java: base/trace/android/src/main/java/com/lynx/tasm/base/TraceEvent.java\n    - java: base/trace/android/src/main/java/com/lynx/tasm/base/TraceController.java\n"
  },
  {
    "path": "base/trace/darwin/.gitignore",
    "content": "Headers/Private"
  },
  {
    "path": "base/trace/darwin/BUILD.gn",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../config.gni\")\n\ntrace_public_headers = [\n  \"LynxTraceController.h\",\n  \"LynxTraceEventWrapper.h\",\n  \"LynxTraceEvent.h\",\n]\n\ntrace_shared_sources = [\n  \"LynxTraceEvent.mm\",\n  \"LynxTraceEventWrapper.mm\",\n]\n\nif (enable_trace == \"perfetto\") {\n  trace_shared_sources += [ \"LynxTraceController.mm\" ]\n} else {\n  trace_shared_sources += [ \"LynxTraceController_mock.mm\" ]\n}\n\nconfig(\"trace_public_config\") {\n  include_dirs = [ \"./\" ]\n\n  cflags_objc = [\n    \"-fobjc-arc\",\n    \"-include\",\n    rebase_path(\"trace-prefix.h\", root_build_dir),\n  ]\n\n  cflags_objcc = cflags_objc + [ \"-std=gnu++17\" ]\n\n  defines = [ \"OS_POSIX=1\" ]\n  if (is_ios) {\n    defines += [ \"OS_IOS=1\" ]\n  } else if (is_mac) {\n    defines += [ \"OS_OSX=1\" ]\n  }\n}\n\nsubspec_target(\"LynxTrace_subspec\") {\n  sources = trace_shared_sources + trace_public_headers\n  public_header_files = trace_public_headers\n}\n\nsource_set(\"LynxTrace\") {\n  sources = trace_shared_sources + trace_public_headers\n  public = trace_public_headers\n\n  public_configs = [ \"../native:trace_public_config\" ]\n  public_deps = [ \"../native:trace\" ]\n\n  if (enable_trace == \"perfetto\" && is_mac) {\n    configs -= [ \"//build/config/compiler:cxx_version_default\" ]\n  }\n\n  public_configs += [ \":trace_public_config\" ]\n}\n\nsource_set(\"lynx_trace_public_headers\") {\n  sources = trace_public_headers\n}\n"
  },
  {
    "path": "base/trace/darwin/CPPLINT.cfg",
    "content": "set noparent\nfilter=-,+lynx_custom/new_java_ref,+build/namespaces,+build/c++14,+build/class,+build/c++tr1,+build/deprecated,+build/endif_comment,+build/explicit_make_pair\nexclude_files=.*\\.java\nexclude_files=.*\\.mm\nexclude_files=.*\\.m\nexclude_files=.*unittest\\.cc\nexclude_files=.*mock_element\\.cc\n"
  },
  {
    "path": "base/trace/darwin/LynxTraceController.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_DARWIN_LYNXTRACECONTROLLER_H_\n#define BASE_TRACE_DARWIN_LYNXTRACECONTROLLER_H_\n\ntypedef void (^completeBlockType)(NSString* traceFile);\n\n@interface LynxTraceController : NSObject\n+ (instancetype)sharedInstance;\n- (intptr_t)getTraceController;\n- (void)startTracing:(completeBlockType)completeBlock config:(NSDictionary*)config;\n- (void)startTracing:(completeBlockType)completeBlock jsonConfig:(NSString*)config;\n- (void)stopTracing;\n- (void)startTrace;\n- (void)stopTrace;\n- (void)onTracingComplete:(NSString*)traceFile;\n- (void)startStartupTracingIfNeeded;\n@end\n\n#endif  // BASE_TRACE_DARWIN_LYNXTRACECONTROLLER_H_\n"
  },
  {
    "path": "base/trace/darwin/LynxTraceController.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"LynxTraceController.h\"\n\n#import \"base/trace/native/platform/darwin/trace_controller_darwin.h\"\n\nstatic LynxTraceController *lynxTraceController = nil;\n\n@implementation LynxTraceController {\n  lynx::trace::TraceController *_traceController;\n  int _sessionId;\n  NSMutableArray<completeBlockType> *_completeBlocks;\n}\n\n+ (instancetype)sharedInstance {\n  static dispatch_once_t onceToken;\n  dispatch_once(&onceToken, ^{\n    lynxTraceController = [[self alloc] init];\n  });\n  return lynxTraceController;\n}\n\n- (instancetype)init {\n  _traceController = lynx::trace::GetTraceControllerInstance();\n  _completeBlocks = [[NSMutableArray alloc] init];\n  _sessionId = -1;\n  return self;\n}\n\n- (intptr_t)getTraceController {\n  return reinterpret_cast<intptr_t>(_traceController);\n}\n\n- (void)startTrace {\n  [self startTracing:nullptr config:@{}];\n}\n\n- (void)stopTrace {\n  [self stopTracing];\n}\n\n- (void)startTracing:(completeBlockType)completeBlock config:(NSDictionary *)config {\n  static int const kDefaultBufferSize = 40960;\n  if (completeBlock != nullptr) {\n    [_completeBlocks addObject:completeBlock];\n  }\n  if (_traceController != nullptr) {\n    int bufferSize = kDefaultBufferSize;\n    if ([config valueForKey:@\"buffer_size\"] != nil) {\n      bufferSize = [config[@\"buffer_size\"] intValue];\n    }\n    auto trace_config = std::make_shared<lynx::trace::TraceConfig>();\n    trace_config->buffer_size = bufferSize;\n    if ([config valueForKey:@\"trace_file\"] != nil) {\n      trace_config->file_path = [config[@\"trace_file\"] UTF8String];\n    }\n\n    if ([config valueForKey:@\"enable_compress\"] != nil) {\n      trace_config->enable_compress = [config[@\"enable_compress\"] boolValue];\n    }\n    trace_config->included_categories = {\"*\"};\n    trace_config->excluded_categories = {\"*\"};\n    _sessionId = _traceController->StartTracing(trace_config);\n    _traceController->AddCompleteCallback(_sessionId, [self, trace_config]() {\n      [self onTracingComplete:[NSString stringWithUTF8String:trace_config->file_path.c_str()]];\n    });\n  }\n}\n\n- (void)startTracing:(completeBlockType)completeBlock jsonConfig:(NSString *)config {\n  NSData *configData = [config dataUsingEncoding:NSUTF8StringEncoding];\n  NSError *error;\n  NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:configData\n                                                       options:kNilOptions\n                                                         error:&error];\n  if (!error) {\n    [self startTracing:completeBlock config:dict];\n  } else {\n    NSLog(@\"Error parsing config JSON: %@\", error);\n  }\n}\n\n- (void)stopTracing {\n  if (_traceController != nullptr) {\n    _traceController->StopTracing(_sessionId);\n  }\n}\n\n- (void)onTracingComplete:(NSString *)traceFile {\n  for (completeBlockType block in _completeBlocks) {\n    block(traceFile);\n  }\n}\n\n- (void)startStartupTracingIfNeeded {\n  if (_traceController != nullptr) {\n    _traceController->StartStartupTracingIfNeeded();\n  }\n}\n\n@end\n"
  },
  {
    "path": "base/trace/darwin/LynxTraceController_mock.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"LynxTraceController.h\"\n\nstatic LynxTraceController* lynxTraceController = nil;\n\n@implementation LynxTraceController {\n}\n\n+ (instancetype)sharedInstance {\n  static dispatch_once_t onceToken;\n  dispatch_once(&onceToken, ^{\n    lynxTraceController = [[self alloc] init];\n  });\n  return lynxTraceController;\n}\n\n- (intptr_t)getTraceController {\n  return 0;\n}\n\n- (void)startTrace {\n}\n\n- (void)stopTrace {\n}\n\n- (void)startTracing:(completeBlockType)completeBlock config:(NSDictionary*)config {\n}\n\n- (void)startTracing:(completeBlockType)completeBlock jsonConfig:(NSString*)config {\n}\n\n- (void)stopTracing {\n}\n\n- (void)startStartupTracingIfNeeded {\n}\n\n- (void)onTracingComplete:(NSString*)traceFile {\n}\n@end\n"
  },
  {
    "path": "base/trace/darwin/LynxTraceEvent.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_DARWIN_LYNXTRACEEVENT_H_\n#define BASE_TRACE_DARWIN_LYNXTRACEEVENT_H_\n\n#import \"LynxTraceEventWrapper.h\"\n\n#if ENABLE_TRACE_PERFETTO\n#define LYNX_TRACE_SECTION_WITH_INFO(category, name, info) \\\n  [LynxTraceEvent beginSection:category withName:name debugInfo:info];\n\n#define LYNX_TRACE_SECTION(category, name) [LynxTraceEvent beginSection:category withName:name];\n\n#define LYNX_TRACE_END_SECTION(category) [LynxTraceEvent endSection:category];\n\n#define LYNX_TRACE_END_SECTION_WITH_NAME(category, name) \\\n  [LynxTraceEvent endSection:category withName:name]\n\n#define LYNX_TRACE_INSTANT(category, name) [LynxTraceEvent instant:(category) withName:(name)];\n\n#define LYNX_TRACE_INSTANT_WITH_DEBUG_INFO(category, name, info) \\\n  [LynxTraceEvent instant:(category) withName:(name)debugInfo:(info)];\n#else\n\n#define LYNX_TRACE_SECTION_WITH_INFO(category, name, debugInfo)\n#define LYNX_TRACE_SECTION(category, name)\n#define LYNX_TRACE_END_SECTION(category)\n#define LYNX_TRACE_INSTANT(category, name)\n#define LYNX_TRACE_INSTANT_WITH_DEBUG_INFO(category, name, info)\n\n#endif\n\n#define DEPRECATED_API __attribute__((deprecated))\n\n@interface LynxTraceEvent : NSObject\n\n+ (NSString *)getRandomColor;\n\n+ (void)beginSection:(NSString *)category\n            withName:(NSString *)name\n           debugInfo:(NSDictionary *)keyValues;\n\n+ (void)beginSection:(NSString *)category withName:(NSString *)name;\n\n+ (void)endSection:(NSString *)category;\n\n+ (void)endSection:(NSString *)category\n          withName:(NSString *)name\n         debugInfo:(NSDictionary *)keyValues;\n\n+ (void)endSection:(NSString *)category withName:(NSString *)name;\n\n+ (void)instant:(NSString *)category withName:(NSString *)name;\n\n+ (void)instant:(NSString *)category withName:(NSString *)name withColor:(NSString *)color;\n\n+ (void)instant:(NSString *)category withName:(NSString *)name debugInfo:(NSDictionary *)keyValues;\n\n+ (void)instant:(NSString *)category withName:(NSString *)name withTimestamp:(int64_t)timestamp;\n\n+ (void)instant:(NSString *)category\n         withName:(NSString *)name\n    withTimestamp:(int64_t)timestamp\n        withColor:(NSString *)color;\n\n+ (void)counter:(NSString *)category withName:(NSString *)name withCounterValue:(uint64_t)value;\n\n+ (BOOL)categoryEnabled:(NSString *)category;\n\n+ (void)instant:(NSString *)category\n         withName:(NSString *)name\n    withTimestamp:(int64_t)timestamp\n        debugInfo:(NSDictionary *)keyValues DEPRECATED_API;\n\n+ (BOOL)registerTraceBackend:(intptr_t)ptr DEPRECATED_API;\n\n@end\n\n#endif  // DARWIN_COMMON_LYNX_BASE_LYNXTRACEEVENT_H_\n"
  },
  {
    "path": "base/trace/darwin/LynxTraceEvent.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"LynxTraceEvent.h\"\n\n#include \"base/trace/native/trace_event.h\"\n\n@implementation LynxTraceEvent\n\n+ (NSString *)getRandomColor {\n  NSMutableString *result = [NSMutableString stringWithCapacity:7];\n  [result appendString:@\"#\"];\n  for (int i = 0; i < 6; i++) {\n    [result appendFormat:@\"%X\", arc4random() % 16];\n  }\n  return result;\n}\n\n+ (void)beginSection:(NSString *)category\n            withName:(NSString *)name\n           debugInfo:(NSDictionary *)keyValues {\n  TRACE_EVENT_BEGIN([category UTF8String], nullptr, [&](lynx::perfetto::EventContext ctx) {\n    auto event = ctx.event();\n    event->set_name([name UTF8String]);\n    [keyValues\n        enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {\n          auto *debug = event->add_debug_annotations();\n          debug->set_name([[NSString stringWithFormat:@\"%@\", key] UTF8String]);\n          debug->set_string_value([[NSString stringWithFormat:@\"%@\", obj] UTF8String]);\n        }];\n  });\n}\n\n+ (void)beginSection:(NSString *)category withName:(NSString *)name {\n  TRACE_EVENT_BEGIN([category UTF8String], nullptr, [&](lynx::perfetto::EventContext ctx) {\n    ctx.event()->set_name([name UTF8String]);\n  });\n}\n\n+ (void)endSection:(NSString *)category {\n  TRACE_EVENT_END([category UTF8String]);\n}\n\n+ (void)endSection:(NSString *)category\n          withName:(NSString *)name\n         debugInfo:(NSDictionary *)keyValues {\n  TRACE_EVENT_END([category UTF8String], [&](lynx::perfetto::EventContext ctx) {\n    auto event = ctx.event();\n    event->set_name([name UTF8String]);\n    [keyValues\n        enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {\n          auto *debug = event->add_debug_annotations();\n          debug->set_name([[NSString stringWithFormat:@\"%@\", key] UTF8String]);\n          debug->set_string_value([[NSString stringWithFormat:@\"%@\", obj] UTF8String]);\n        }];\n  });\n}\n\n+ (void)endSection:(NSString *)category withName:(NSString *)name {\n  TRACE_EVENT_END([category UTF8String], [&](lynx::perfetto::EventContext ctx) {\n    ctx.event()->set_name([name UTF8String]);\n  });\n}\n\n+ (void)instant:(NSString *)category withName:(NSString *)name {\n  [self instant:category withName:name withColor:[self getRandomColor]];\n}\n\n+ (void)instant:(NSString *)category withName:(NSString *)name withColor:(NSString *)color {\n  TRACE_EVENT_INSTANT([category UTF8String], nullptr, [&](lynx::perfetto::EventContext ctx) {\n    ctx.event()->set_name([name UTF8String]);\n    auto *debug = ctx.event()->add_debug_annotations();\n    debug->set_name(\"color\");\n    debug->set_string_value([color UTF8String]);\n  });\n}\n\n+ (void)instant:(NSString *)category withName:(NSString *)name debugInfo:(NSDictionary *)keyValues {\n  TRACE_EVENT_INSTANT([category UTF8String], nullptr, [&](lynx::perfetto::EventContext ctx) {\n    auto event = ctx.event();\n    event->set_name([name UTF8String]);\n    [keyValues\n        enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {\n          auto *debug = event->add_debug_annotations();\n          debug->set_name([[NSString stringWithFormat:@\"%@\", key] UTF8String]);\n          debug->set_string_value([[NSString stringWithFormat:@\"%@\", obj] UTF8String]);\n        }];\n  });\n}\n\n+ (void)counter:(NSString *)category withName:(NSString *)name withCounterValue:(uint64_t)value {\n  TRACE_COUNTER([category UTF8String], lynx::perfetto::CounterTrack([name UTF8String]), value);\n}\n\n+ (BOOL)categoryEnabled:(NSString *)category {\n  return YES;\n}\n\n+ (void)instant:(NSString *)category withName:(NSString *)name withTimestamp:(int64_t)timestamp {\n  [self instant:category withName:name withColor:[self getRandomColor]];\n}\n\n+ (void)instant:(NSString *)category\n         withName:(NSString *)name\n    withTimestamp:(int64_t)timestamp\n        withColor:(NSString *)color {\n  [self instant:category withName:name withColor:color];\n}\n\n+ (void)instant:(NSString *)category\n         withName:(NSString *)name\n    withTimestamp:(int64_t)timestamp\n        debugInfo:(NSDictionary *)keyValues {\n  [self instant:category withName:name debugInfo:keyValues];\n}\n\n+ (BOOL)registerTraceBackend:(intptr_t)ptr {\n  return NO;\n}\n\n@end\n"
  },
  {
    "path": "base/trace/darwin/LynxTraceEventWrapper.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_TRACE_DARWIN_LYNXTRACEEVENTWRAPPER_H_\n#define BASE_TRACE_DARWIN_LYNXTRACEEVENTWRAPPER_H_\n\nNS_ASSUME_NONNULL_BEGIN\n\nextern NSString *LYNX_TRACE_CATEGORY_WRAPPER;\n\nNS_ASSUME_NONNULL_END\n\n#endif  // BASE_TRACE_DARWIN_LYNXTRACEEVENTWRAPPER_H_\n"
  },
  {
    "path": "base/trace/darwin/LynxTraceEventWrapper.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"LynxTraceEventWrapper.h\"\n#include \"base/trace/native/internal_trace_category.h\"\n\nNSString *LYNX_TRACE_CATEGORY_WRAPPER;\n\n@interface LynxTraceEventWrapper : NSObject\n\n@end\n\n@implementation LynxTraceEventWrapper\n\n+ (void)load {\n  [LynxTraceEventWrapper initEventName];\n}\n\n+ (void)initEventName {\n  LYNX_TRACE_CATEGORY_WRAPPER = [NSString stringWithUTF8String:INTERNAL_TRACE_CATEGORY];\n}\n\n@end\n"
  },
  {
    "path": "base/trace/darwin/trace-prefix.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#import <TargetConditionals.h>\n#ifdef __OBJC__\n#if defined(TARGET_OS_OSX) && TARGET_OS_OSX\n#import <Cocoa/Cocoa.h>\n#elif defined(TARGET_OS_IOS) && TARGET_OS_IOS\n#import <UIKit/UIKit.h>\n#elif defined(TARGET_OS_TV) && TARGET_OS_TV\n#import <UIKit/UIKit.h>\n#else\n#error \"unknown os\"\n#endif  // TARGET_OS_\n#else   // __OBJC__\n#ifndef BASE_TRACE_DARWIN_TRACE - PREFIX_H_\n#if defined(__cplusplus)\n#define BASE_TRACE_DARWIN_TRACE -PREFIX_H_ extern \"C\"\n#else\n#define FOUNDATION_EXPORT extern\n#endif  // BASE_TRACE_DARWIN_TRACE-PREFIX_H_\n#endif  // FOUNDATION_EXPORT\n#endif  // __OBJC__\n"
  },
  {
    "path": "base/trace/native/BUILD.gn",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../config.gni\")\nimport(\"../../../testing/test.gni\")\nimport(\"hook_systrace/hook_systrace.gni\")\nimport(\"platform/platform.gni\")\n\nconfig(\"trace_public_config\") {\n  include_dirs = [\n    \"//third_party/perfetto\",\n    \"$root_gen_dir/lynx\",\n  ]\n  defines = []\n  if (enable_trace == \"perfetto\") {\n    defines += [ \"ENABLE_TRACE_PERFETTO=1\" ]\n  } else if (enable_trace == \"systrace\") {\n    defines += [ \"ENABLE_TRACE_SYSTRACE=1\" ]\n  }\n  cflags = [ \"-Wno-unused-parameter\" ]\n\n  if (enable_trace != \"perfetto\") {\n    cflags += [ \"-Wno-unused-private-field\" ]\n  }\n\n  if (is_clang) {\n    cflags += [\n      \"-Wno-unused-lambda-capture\",\n      \"-Wno-unused-const-variable\",\n    ]\n  }\n}\n\nconfig(\"trace_private_config\") {\n  cflags = [\n    \"-ffunction-sections\",\n    \"-fdata-sections\",\n    \"-fno-exceptions\",\n    \"-fvisibility=hidden\",\n    \"-fvisibility-inlines-hidden\",\n    \"-fno-short-enums\",\n    \"-fno-stack-protector\",\n    \"-fno-strict-aliasing\",\n    \"-Wextra\",\n  ]\n  cflags_c = []\n  cflags_cc = [\n    \"-fno-rtti\",\n    \"-std=c++17\",\n    \"-Wl,-ldl\",\n    \"-Wno-unused-command-line-argument\",\n  ]\n  configs = []\n  if (is_debug) {\n    cflags_cc += [ \"-g\" ]\n  }\n\n  defines = []\n}\n\n# trace_public_headers & trace_shared_sources\ntrace_public_headers = [\n  \"internal_trace_category.h\",\n  \"trace_controller.h\",\n  \"trace_event.h\",\n  \"track_event_wrapper.h\",\n  \"trace_event_utils_perfetto.h\",\n  \"trace_export.h\",\n  \"trace_defines.h\",\n]\n\ntrace_shared_sources = [\n  \"internal_trace_category.h\",\n  \"trace_controller.h\",\n  \"trace_defines.h\",\n  \"trace_event.h\",\n  \"trace_event_utils_perfetto.h\",\n  \"trace_export.h\",\n  \"track_event_wrapper.h\",\n\n  # TODO(luchengxuan) remove & use trace_event directly in 218\n  \"trace_event_export_symbol.cc\",\n  \"trace_event_export_symbol.h\",\n]\n\nif (enable_trace != \"perfetto\") {\n  trace_shared_sources += [\n    \"trace_controller.cc\",\n    \"trace_event_utils_perfetto_mock.cc\",\n    \"track_event_wrapper_mock.cc\",\n  ]\n}\n\nif (enable_trace == \"perfetto\") {\n  trace_public_headers += [ \"instance_counter_trace.h\" ]\n  trace_shared_sources += [\n    \"instance_counter_trace.cc\",\n    \"instance_counter_trace.h\",\n    \"trace_controller_impl.cc\",\n    \"trace_controller_impl.h\",\n    \"track_event_wrapper.cc\",\n  ]\n} else if (enable_trace == \"systrace\") {\n  trace_public_headers += [\n    \"trace_event_utils_systrace.h\",\n    \"instance_counter_trace.h\",\n  ]\n  trace_shared_sources += [\n    \"instance_counter_trace.cc\",\n    \"instance_counter_trace.h\",\n    \"trace_event_utils_systrace.h\",\n  ]\n\n  if (is_android) {\n    trace_shared_sources += [ \"trace_event_utils_systrace_android.cc\" ]\n  } else if (is_apple || is_win || is_linux) {\n    trace_shared_sources += [ \"trace_event_utils_systrace_default.cc\" ]\n  }\n}\n\ntrace_public_headers += platform_public_headers + hook_systrace_public_headers\ntrace_shared_sources += platform_shared_sources + hook_systrace_shared_sources\n\n# trace_public_headers & trace_shared_sources end\n\nsource_set(\"trace\") {\n  sources = trace_shared_sources\n\n  public_deps = []\n\n  if ((is_android || is_harmony) && enable_trace == \"perfetto\") {\n    public_deps += [ \"//third_party/xhook:xhook\" ]\n  }\n\n  if (is_android) {\n    public_deps += [ \"../android:trace_jni_files\" ]\n  }\n\n  if (enable_trace == \"perfetto\") {\n    public_deps += [\n      \"../../../third_party/rapidjson:rapidjson\",\n      \"//third_party/perfetto/sdk:perfetto\",\n    ]\n  }\n\n  public_configs = [ \":trace_public_config\" ]\n  configs += [\n    \":trace_private_config\",\n    \"../../src:base_public_config\",\n  ]\n}\n\nsource_set(\"trace_public_headers\") {\n  sources = trace_public_headers\n  public_configs = [ \":trace_public_config\" ]\n}\n\nunittest_set(\"trace_testset\") {\n  sources = [\n    \"trace_controller_unittest.cc\",\n    \"trace_event_unittest.cc\",\n  ]\n  public_deps = [ \":trace\" ]\n}\n\nunittest_exec(\"trace_unittests_exec\") {\n  sources = []\n  deps = [\n    \":trace\",\n    \":trace_testset\",\n  ]\n}\n\ngroup(\"trace_tests\") {\n  testonly = true\n  deps = [ \":trace_unittests_exec\" ]\n  public_deps = [ \":trace_testset\" ]\n}\n"
  },
  {
    "path": "base/trace/native/hook_systrace/cpu_info_trace.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/hook_systrace/cpu_info_trace.h\"\n\n#include <string>\n\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n\n#if OS_ANDROID\n#include <unistd.h>\n\n#include <fstream>\n#endif\n\n#if OS_IOS\n#include <mach/mach_time.h>\n#include <mach/machine.h>\n#include <sys/sysctl.h>\n#include <sys/types.h>\n#endif\n\nnamespace lynx {\nnamespace trace {\n\nnamespace {\n#if OS_IOS\n#if defined(__arm64__)\nuint64_t GetSystemBootTimeSecond() {\n  auto init_time_factor = []() -> uint64_t {\n    mach_timebase_info_data_t timebase_info;\n    mach_timebase_info(&timebase_info);\n    return timebase_info.numer / timebase_info.denom;\n  };\n  static uint64_t monotonic_timebase_factor = init_time_factor();\n\n  return std::chrono::nanoseconds(mach_absolute_time() *\n                                  monotonic_timebase_factor)\n      .count();\n}\n#endif\n\nfloat GetCpuCurFreq() {\n#if defined(__arm64__)\n  // 10000 loop times is a balance between time-cost and accuracy\n  int count = 10000;\n  uint64_t start_time = GetSystemBootTimeSecond();\n  /**\n   * approximate calculating iOS cpu frequency.\n   * To reduce instruction-level concurrency, we use 24 general-purpose\n   * registers and every add instrument's input is the output of the previous\n   * instruction. next assembly codes executes for approximately 240000 clock\n   * cycles.\n   */\n  asm volatile(\n      \"0:\"\n      \"add     x2,  x2,  x1  \\n\"\n      \"add     x3,  x3,  x2  \\n\"\n      \"add     x4,  x4,  x3  \\n\"\n      \"add     x5,  x5,  x4  \\n\"\n      \"add     x6,  x6,  x5  \\n\"\n      \"add     x7,  x7,  x6  \\n\"\n      \"add     x9,  x9,  x7  \\n\"\n      \"add     x10, x10, x9  \\n\"\n      \"add     x11, x11, x10 \\n\"\n      \"add     x12, x12, x11 \\n\"\n      \"add     x13, x13, x12 \\n\"\n      \"add     x14, x14, x13 \\n\"\n      \"add     x15, x15, x14 \\n\"\n      \"add     x19, x19, x15 \\n\"\n      \"add     x20, x20, x19 \\n\"\n      \"add     x21, x21, x20 \\n\"\n      \"add     x22, x22, x21 \\n\"\n      \"add     x23, x23, x22 \\n\"\n      \"add     x24, x24, x23 \\n\"\n      \"add     x25, x25, x24 \\n\"\n      \"add     x26, x26, x25 \\n\"\n      \"add     x27, x27, x26 \\n\"\n      \"add     x28, x28, x27 \\n\"\n      \"add     x1, x1, x28 \\n\"\n      \"subs    %x0, %x0, #1  \\n\"\n      \"bne     0b            \\n\"\n      : \"=r\"(count)\n      : \"0\"(count)\n      : \"cc\", \"memory\", \"x1\", \"x2\", \"x3\", \"x4\", \"x5\", \"x6\", \"x7\", \"x9\", \"x10\",\n        \"x11\", \"x12\", \"x13\", \"x14\", \"x15\", \"x19\", \"x20\", \"x21\", \"x22\", \"x23\",\n        \"x24\", \"x25\", \"x26\", \"x27\", \"x28\");\n  uint64_t cost_time = GetSystemBootTimeSecond() - start_time;\n  // clock cycle counts divide by cost time\n  float freq = 240000.0 / cost_time;\n  return freq;\n#endif\n  return 0.0;\n}\n\nvoid ReadCpuFreqs(std::vector<CpuInfoTrace::CpuFreq>& cpu_freqs) {\n  const float cur_freq = GetCpuCurFreq();\n  cpu_freqs.emplace_back(CpuInfoTrace::CpuFreq{0, cur_freq});\n}\n#elif OS_ANDROID\nvoid ReadCpuFreqs(std::vector<CpuInfoTrace::CpuFreq>& cpu_freqs) {\n  static const std::string cpu_dir_path = \"/sys/devices/system/cpu\";\n  static const std::string cpu_file_path = \"/cpufreq/scaling_cur_freq\";\n  auto num_cpus = static_cast<size_t>(sysconf(_SC_NPROCESSORS_CONF));\n  for (size_t i = 0; i < num_cpus; i++) {\n    std::string cpu_file =\n        cpu_dir_path + \"/cpu\" + std::to_string(i) + cpu_file_path;\n    std::ifstream fin(cpu_file);\n    if (!fin.is_open()) {\n      cpu_freqs.emplace_back(CpuInfoTrace::CpuFreq{i, 0.0});\n      continue;\n    }\n    std::string str;\n    getline(fin, str);\n    float freq = std::stoul(str) / 1000 / 1000.0;  // Ghz\n    cpu_freqs.emplace_back(CpuInfoTrace::CpuFreq{i, freq});\n    fin.close();\n  }\n}\n#else\nvoid ReadCpuFreqs(std::vector<CpuInfoTrace::CpuFreq>&) {}\n#endif\n}  // namespace\n\nCpuInfoTrace::CpuInfoTrace() : thread_(\"cpu_freq_thread\") {}\n\nvoid CpuInfoTrace::DispatchBegin() {\n  // 16ms is a balance between time-cost and accuracy.\n  const static uint32_t sDelayTimeForCpuFreqTrace = 16;  // Ms\n  auto record_cpu_freq_task = [&] {\n    const std::vector<CpuInfoTrace::CpuFreq> cpu_freqs = this->ReadCpuCurFreq();\n    for (auto cpu_freq : cpu_freqs) {\n      const std::string track_name =\n          std::string(\"cpu\") + std::to_string(cpu_freq.first);\n      TRACE_COUNTER(INTERNAL_TRACE_CATEGORY_VITALS, track_name.c_str(),\n                    cpu_freq.second);\n    }\n  };\n\n  thread_.GetTaskRunner()->PostTask([this, record_cpu_freq_task] {\n    timer_ = std::make_unique<lynx::base::TimedTaskManager>();\n    timer_->SetInterval(std::move(record_cpu_freq_task),\n                        sDelayTimeForCpuFreqTrace);\n  });\n}\n\nvoid CpuInfoTrace::DispatchEnd() {\n  if (timer_ != nullptr) {\n    thread_.GetTaskRunner()->PostTask([&] { timer_.reset(nullptr); });\n  }\n}\n\nconst std::vector<CpuInfoTrace::CpuFreq> CpuInfoTrace::ReadCpuCurFreq() {\n  std::vector<CpuInfoTrace::CpuFreq> cpu_freqs;\n  ReadCpuFreqs(cpu_freqs);\n  return cpu_freqs;\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/hook_systrace/cpu_info_trace.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_HOOK_SYSTRACE_CPU_INFO_TRACE_H_\n#define BASE_TRACE_NATIVE_HOOK_SYSTRACE_CPU_INFO_TRACE_H_\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/thread.h\"\n#include \"base/include/thread/timed_task.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass CpuInfoTrace {\n public:\n  using CpuFreq = std::pair</*cpu_index*/ uint32_t, /* cpu_freq(GHz) */ float>;\n\n  CpuInfoTrace();\n  ~CpuInfoTrace() = default;\n  void DispatchBegin();\n  void DispatchEnd();\n\n private:\n  const std::vector<CpuFreq> ReadCpuCurFreq();\n  fml::Thread thread_;\n  std::unique_ptr<lynx::base::TimedTaskManager> timer_;\n};\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_HOOK_SYSTRACE_CPU_INFO_TRACE_H_\n"
  },
  {
    "path": "base/trace/native/hook_systrace/hook_system_trace.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_HOOK_SYSTRACE_HOOK_SYSTEM_TRACE_H_\n#define BASE_TRACE_NATIVE_HOOK_SYSTRACE_HOOK_SYSTEM_TRACE_H_\n\n#include <memory>\n\n#include \"base/trace/native/hook_systrace/cpu_info_trace.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass HookSystemTrace {\n public:\n  HookSystemTrace() = default;\n  ~HookSystemTrace() = default;\n\n  void Install();\n\n  void Uninstall();\n\n private:\n  static void InstallSystemTraceHooks();\n  static void UninstallSystemTraceHooks();\n  CpuInfoTrace cpu_info_trace_;\n};\n}  // namespace trace\n}  // namespace lynx\n#endif  // BASE_TRACE_NATIVE_HOOK_SYSTRACE_HOOK_SYSTEM_TRACE_H_\n"
  },
  {
    "path": "base/trace/native/hook_systrace/hook_system_trace_android.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/hook_systrace/hook_system_trace.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"third_party/xhook/libxhook/jni/xhook.h\"\n\n// Keep these in sync with system/core/libcutils/include/cutils/trace.h in\n// android source code.\n#define ATRACE_TAG_NEVER 0          // This tag is never enabled.\n#define ATRACE_TAG_ALWAYS (1 << 0)  // This tag is always enabled.\n#define ATRACE_TAG_GRAPHICS (1 << 1)\n#define ATRACE_TAG_INPUT (1 << 2)\n#define ATRACE_TAG_VIEW (1 << 3)\n#define ATRACE_TAG_WEBVIEW (1 << 4)\n#define ATRACE_TAG_WINDOW_MANAGER (1 << 5)\n#define ATRACE_TAG_ACTIVITY_MANAGER (1 << 6)\n#define ATRACE_TAG_SYNC_MANAGER (1 << 7)\n#define ATRACE_TAG_AUDIO (1 << 8)\n#define ATRACE_TAG_VIDEO (1 << 9)\n#define ATRACE_TAG_CAMERA (1 << 10)\n#define ATRACE_TAG_HAL (1 << 11)\n#define ATRACE_TAG_APP (1 << 12)\n#define ATRACE_TAG_RESOURCES (1 << 13)\n#define ATRACE_TAG_DALVIK (1 << 14)\n#define ATRACE_TAG_RS (1 << 15)\n#define ATRACE_TAG_BIONIC (1 << 16)\n#define ATRACE_TAG_POWER (1 << 17)\n#define ATRACE_TAG_PACKAGE_MANAGER (1 << 18)\n#define ATRACE_TAG_SYSTEM_SERVER (1 << 19)\n#define ATRACE_TAG_DATABASE (1 << 20)\n#define ATRACE_TAG_NETWORK (1 << 21)\n#define ATRACE_TAG_ADB (1 << 22)\n#define ATRACE_TAG_VIBRATOR (1 << 23)\n#define ATRACE_TAG_AIDL (1 << 24)\n#define ATRACE_TAG_NNAPI (1 << 25)\n#define ATRACE_TAG_RRO (1 << 26)\n#define ATRACE_TAG_LAST ATRACE_TAG_RRO\n#define ATRACE_TAG_ALL ~((uint64_t)(-1) << 27)\n\nnamespace lynx {\nnamespace trace {\n\nusing ATraceFunc = struct {\n  const char *name;\n  void *local_func;\n  void *real_func;\n};\n\nstatic void ATraceBeginBody(const char *name) {\n  TraceEventBegin(INTERNAL_TRACE_CATEGORY_ATRACE, nullptr,\n                  [name](lynx::perfetto::EventContext ctx) {\n                    ctx.event()->set_name(name);\n                  });\n}\n\nstatic void ATraceEndBody() { TraceEventEnd(INTERNAL_TRACE_CATEGORY_ATRACE); }\n\nstatic uint64_t ATraceGetEnabledTags() { return ATRACE_TAG_ALL; }\n\nstatic void ATraceUpdateTags() {}\n\nstatic uint64_t ATraceGetProperty() { return ATRACE_TAG_ALL; }\n\nstatic uint64_t s_atrace_enabled_tags = ATRACE_TAG_ALL;\n\nstatic ATraceFunc atrace_funcs[] = {\n    {\"atrace_begin_body\", reinterpret_cast<void *>(ATraceBeginBody), nullptr},\n    {\"atrace_end_body\", reinterpret_cast<void *>(ATraceEndBody), nullptr},\n    {\"atrace_update_tags\", reinterpret_cast<void *>(&ATraceUpdateTags),\n     nullptr},\n    {\"atrace_get_property\", reinterpret_cast<void *>(&ATraceGetProperty),\n     nullptr},\n    {\"atrace_enabled_tags\", reinterpret_cast<void *>(&s_atrace_enabled_tags),\n     nullptr},\n    {\"atrace_get_enabled_tags\", reinterpret_cast<void *>(ATraceGetEnabledTags),\n     nullptr}};\n\nvoid HookSystemTrace::InstallSystemTraceHooks() {\n  // may be modified by other thread, reset to default value\n  s_atrace_enabled_tags = ATRACE_TAG_ALL;\n  xhook_clear();\n  for (auto &func : atrace_funcs) {\n    int ret =\n        xhook_register(\".*\\\\.so$\", func.name, func.local_func, &func.real_func);\n    if (ret != 0) {\n      LOGE(\"failed to hook symbol:\" << func.name << \" ret \" << ret);\n    }\n  }\n  xhook_refresh(1);\n}\n\nvoid HookSystemTrace::UninstallSystemTraceHooks() {\n  xhook_clear();\n  for (auto func : atrace_funcs) {\n    if (func.real_func == nullptr) {\n      continue;\n    }\n    int ret = xhook_register(\".*\\\\.so$\", func.name, func.real_func, nullptr);\n    if (ret != 0) {\n      LOGE(\"failed to uninstall symbol:\" << func.name << \" ret \" << ret);\n    }\n  }\n  xhook_refresh(1);\n}\n\nvoid HookSystemTrace::Install() {\n  InstallSystemTraceHooks();\n  cpu_info_trace_.DispatchBegin();\n}\n\nvoid HookSystemTrace::Uninstall() {\n  UninstallSystemTraceHooks();\n  cpu_info_trace_.DispatchEnd();\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/hook_systrace/hook_system_trace_default.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/hook_systrace/hook_system_trace.h\"\n\nnamespace lynx {\nnamespace trace {\n\nvoid HookSystemTrace::InstallSystemTraceHooks() {}\n\nvoid HookSystemTrace::UninstallSystemTraceHooks() {}\n\nvoid HookSystemTrace::Install() { cpu_info_trace_.DispatchBegin(); }\n\nvoid HookSystemTrace::Uninstall() { cpu_info_trace_.DispatchEnd(); }\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/hook_systrace/hook_system_trace_harmony.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string>\n\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/hook_systrace/hook_system_trace.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"third_party/xhook/libxhook/jni/xhook.h\"\n\nnamespace lynx {\nnamespace trace {\n\nusing ATraceFunc = struct {\n  const char *name;\n  void *local_func;\n  void *real_func;\n};\n\nstatic void HiTraceBeginBody(uint64_t label, const std::string &name,\n                             __attribute__((unused)) float limit = -1) {\n  TRACE_EVENT_BEGIN(INTERNAL_TRACE_CATEGORY_ATRACE, nullptr,\n                    [&name, &label](lynx::perfetto::EventContext ctx) {\n                      ctx.event()->set_name(name);\n                      ctx.event()->add_debug_annotations(\"label\",\n                                                         std::to_string(label));\n                    });\n}\nstatic void HiTraceEndBody(uint64_t label) {\n  TRACE_EVENT_END(INTERNAL_TRACE_CATEGORY_ATRACE,\n                  [&label](lynx::perfetto::EventContext ctx) {\n                    ctx.event()->add_debug_annotations(\"label\",\n                                                       std::to_string(label));\n                  });\n}\n\nstatic void HiTraceBeginBodyAsync(uint64_t label, const std::string &name,\n                                  int32_t taskId,\n                                  __attribute__((unused)) float limit = -1) {\n  TRACE_EVENT(INTERNAL_TRACE_CATEGORY_ATRACE, nullptr,\n              [&label, &name, &taskId](lynx::perfetto::EventContext ctx) {\n                ctx.event()->set_name(name);\n                ctx.event()->add_debug_annotations(\"label\",\n                                                   std::to_string(label));\n                auto legacy_event = ctx.event()->set_legacy_event();\n                legacy_event->set_phase('S');\n                legacy_event->set_bind_id(taskId);\n                legacy_event->set_unscoped_id(taskId);\n                legacy_event->set_flow_direction(\n                    lynx::perfetto::FlowDirection::FLOW_IN);\n              });\n}\nstatic void HiTraceEndBodyAsync(uint64_t label, const std::string &name,\n                                int32_t taskId) {\n  TRACE_EVENT(INTERNAL_TRACE_CATEGORY_ATRACE, nullptr,\n              [&label, &name, &taskId](lynx::perfetto::EventContext ctx) {\n                ctx.event()->set_name(name);\n                ctx.event()->add_debug_annotations(\"label\",\n                                                   std::to_string(label));\n                auto legacy_event = ctx.event()->set_legacy_event();\n                legacy_event->set_phase('F');\n                legacy_event->set_bind_id(taskId);\n                legacy_event->set_unscoped_id(taskId);\n                legacy_event->set_flow_direction(\n                    lynx::perfetto::FlowDirection::FLOW_OUT);\n              });\n}\nstatic bool HiTraceIsTagEnabled(__attribute__((unused)) uint64_t tag) {\n  return true;\n}\n\n__attribute__((unused)) static ATraceFunc atrace_funcs[] = {\n    {\"StartTrace\", reinterpret_cast<void *>(HiTraceBeginBody), nullptr},\n    {\"FinishTrace\", reinterpret_cast<void *>(HiTraceEndBody), nullptr},\n    {\"IsTagEnabled\", reinterpret_cast<void *>(HiTraceIsTagEnabled), nullptr},\n    {\"StartAsyncTrace\", reinterpret_cast<void *>(HiTraceBeginBodyAsync),\n     nullptr},\n    {\"FinishAsyncTrace\", reinterpret_cast<void *>(HiTraceEndBodyAsync),\n     nullptr},\n};\n\nvoid HookSystemTrace::InstallSystemTraceHooks() {\n  xhook_clear();\n  for (auto &func : atrace_funcs) {\n    int ret =\n        xhook_register(\".*\\\\.so$\", func.name, func.local_func, &func.real_func);\n    if (ret != 0) {\n      LOGE(\"failed to hook symbol:\" << func.name << \" ret \" << ret);\n    }\n  }\n  xhook_refresh(0);\n}\n\nvoid HookSystemTrace::UninstallSystemTraceHooks() {\n  xhook_clear();\n  for (auto func : atrace_funcs) {\n    if (func.real_func == nullptr) {\n      continue;\n    }\n    int ret = xhook_register(\".*\\\\.so$\", func.name, func.real_func, nullptr);\n    if (ret != 0) {\n      LOGE(\"failed to uninstall symbol:\" << func.name << \" ret \" << ret);\n    }\n  }\n  xhook_refresh(0);\n}\n\nvoid HookSystemTrace::Install() {\n  InstallSystemTraceHooks();\n  cpu_info_trace_.DispatchBegin();\n}\n\nvoid HookSystemTrace::Uninstall() {\n  UninstallSystemTraceHooks();\n  cpu_info_trace_.DispatchEnd();\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/hook_systrace/hook_systrace.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../../config.gni\")\n\nhook_systrace_public_headers = []\nhook_systrace_shared_sources = []\n\nif (enable_trace == \"perfetto\") {\n  hook_systrace_public_headers += [ \"hook_systrace/hook_system_trace.h\" ]\n  hook_systrace_shared_sources += [\n    \"hook_systrace/cpu_info_trace.cc\",\n    \"hook_systrace/cpu_info_trace.h\",\n    \"hook_systrace/hook_system_trace.h\",\n  ]\n\n  if (is_android) {\n    hook_systrace_shared_sources +=\n        [ \"hook_systrace/hook_system_trace_android.cc\" ]\n  } else if (is_harmony) {\n    hook_systrace_shared_sources +=\n        [ \"hook_systrace/hook_system_trace_harmony.cc\" ]\n  } else if (is_apple || is_win || is_linux) {\n    hook_systrace_shared_sources +=\n        [ \"hook_systrace/hook_system_trace_default.cc\" ]\n  }\n}\n"
  },
  {
    "path": "base/trace/native/instance_counter_trace.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/instance_counter_trace.h\"\n\nnamespace lynx {\nnamespace trace {\n\nInstanceCounterTrace::Impl* InstanceCounterTrace::impl_;\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/instance_counter_trace.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_INSTANCE_COUNTER_TRACE_H_\n#define BASE_TRACE_NATIVE_INSTANCE_COUNTER_TRACE_H_\n\n#include <stdint.h>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/trace/native/trace_export.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass InstanceCounterTrace {\n public:\n  class TRACE_EXPORT Impl {\n   public:\n    Impl() = default;\n\n    virtual ~Impl() = default;\n\n    virtual void JsHeapMemoryUsedTraceImpl(const uint64_t jsHeapMemory){};\n  };\n\n  InstanceCounterTrace() = delete;\n\n  ~InstanceCounterTrace() = delete;\n\n  TRACE_EXPORT static void SetImpl(Impl* impl) { impl_ = impl; }\n\n  static void JsHeapMemoryUsedTrace(const uint64_t jsHeapMemory) {\n    if (LIKELY(!impl_)) {\n      return;\n    }\n    impl_->JsHeapMemoryUsedTraceImpl(jsHeapMemory);\n  }\n\n private:\n  TRACE_EXPORT static Impl* impl_;\n};\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_INSTANCE_COUNTER_TRACE_H_\n"
  },
  {
    "path": "base/trace/native/internal_trace_category.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef BASE_TRACE_NATIVE_INTERNAL_TRACE_CATEGORY_H_\n#define BASE_TRACE_NATIVE_INTERNAL_TRACE_CATEGORY_H_\n\nconstexpr char INTERNAL_TRACE_CATEGORY[] = \"lynx\";\nconstexpr char INTERNAL_TRACE_CATEGORY_VITALS[] = \"vitals\";\nconstexpr char INTERNAL_TRACE_CATEGORY_SCREENSHOTS[] = \"screenshot\";\nconstexpr char INTERNAL_TRACE_CATEGORY_FPS[] = \"timeline.frame\";\nconstexpr char INTERNAL_TRACE_CATEGORY_ATRACE[] = \"system\";\n\n#endif  // BASE_TRACE_NATIVE_INTERNAL_TRACE_CATEGORY_H_\n"
  },
  {
    "path": "base/trace/native/platform/android/trace_controller_android.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/platform/android/trace_controller_android.h\"\n\n#include <unistd.h>\n\n#include <chrono>\n#include <cstdint>\n#include <memory>\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"base/trace/android/src/main/jni/gen/TraceController_jni.h\"\n#include \"base/trace/android/src/main/jni/gen/TraceController_register_jni.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/base/android/jni_helper.h\"\n\nnamespace lynxtrace {\nnamespace jni {\nbool RegisterJNIForTraceController(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynxtrace\n\nstatic constexpr int kInvalidTraceSessionId = -1;\n\n// static\njlong CreateTraceController(JNIEnv* env, jobject jcaller) {\n  static bool should_init_delegate = true;\n  if (should_init_delegate) {\n    auto delegate =\n        std::make_unique<lynx::trace::TraceControllerDelegateAndroid>(env,\n                                                                      jcaller);\n    lynx::trace::TraceController::Instance()->SetDelegate(std::move(delegate));\n    should_init_delegate = false;\n  }\n  return reinterpret_cast<jlong>(lynx::trace::TraceController::Instance());\n}\n\n// static\njint StartTracing(JNIEnv* env, jobject jcaller, jlong ptr, jint buffer_size,\n                  jobjectArray include_categories,\n                  jobjectArray exclude_categories, jstring trace_file,\n                  jboolean enableSystrace, jboolean enableCompress) {\n  auto* controller = reinterpret_cast<lynx::trace::TraceController*>(ptr);\n  if (controller) {\n    auto trace_config = std::make_shared<lynx::trace::TraceConfig>();\n    trace_config->file_path =\n        lynx::base::android::JNIConvertHelper::ConvertToString(env, trace_file);\n    trace_config->included_categories = lynx::base::android::JNIConvertHelper::\n        ConvertJavaStringArrayToStringVector(env, include_categories);\n    trace_config->excluded_categories = lynx::base::android::JNIConvertHelper::\n        ConvertJavaStringArrayToStringVector(env, exclude_categories);\n    trace_config->buffer_size = buffer_size;\n    trace_config->enable_systrace = enableSystrace == JNI_TRUE;\n    trace_config->enable_compress = enableCompress == JNI_TRUE;\n    int session_id = controller->StartTracing(trace_config);\n\n    return session_id;\n  }\n  return kInvalidTraceSessionId;\n}\n\n// static\nvoid StopTracing(JNIEnv* env, jobject jcaller, jlong ptr, jint session_id) {\n  if (ptr != 0) {\n    auto* controller = reinterpret_cast<lynx::trace::TraceController*>(ptr);\n    controller->StopTracing(session_id);\n  }\n}\n\n// static\nvoid StartStartupTracingIfNeeded(JNIEnv* env, jobject jcaller, jlong ptr) {\n  if (ptr != 0) {\n    auto* controller = reinterpret_cast<lynx::trace::TraceController*>(ptr);\n    controller->StartStartupTracingIfNeeded();\n  }\n}\n\nnamespace lynx {\nnamespace trace {\n\nstd::string TraceControllerDelegateAndroid::GenerateTracingFileDir() {\n  JNIEnv* env = lynx::base::android::AttachCurrentThread();\n  auto ref =\n      Java_TraceController_generateTracingFileDir(env, weak_owner_.Get());\n  return lynx::base::android::JNIConvertHelper::ConvertToString(env, ref.Get());\n}\n\nvoid TraceControllerDelegateAndroid::RefreshATraceTags() {\n  JNIEnv* env = lynx::base::android::AttachCurrentThread();\n  Java_TraceController_refreshATraceTags(env, weak_owner_.Get());\n}\n\nvoid TraceControllerDelegateAndroid::SetIsTracingStarted(\n    bool is_tracing_started) {\n  JNIEnv* env = lynx::base::android::AttachCurrentThread();\n  Java_TraceController_setIsTracingStarted(env, weak_owner_.Get(),\n                                           is_tracing_started);\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/platform/android/trace_controller_android.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_PLATFORM_ANDROID_TRACE_CONTROLLER_ANDROID_H_\n#define BASE_TRACE_NATIVE_PLATFORM_ANDROID_TRACE_CONTROLLER_ANDROID_H_\n\n#include <string>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"base/trace/native/trace_controller.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass TraceControllerDelegateAndroid : public TraceController::Delegate {\n public:\n  TraceControllerDelegateAndroid(JNIEnv *env, jobject owner)\n      : weak_owner_(env, owner){};\n\n  ~TraceControllerDelegateAndroid() = default;\n\n  std::string GenerateTracingFileDir() override;\n\n  void SetIsTracingStarted(bool is_tracing_started) override;\n\n  void RefreshATraceTags() override;\n\n private:\n  TraceControllerDelegateAndroid(const TraceControllerDelegateAndroid &) =\n      delete;\n\n  TraceControllerDelegateAndroid &operator=(\n      const TraceControllerDelegateAndroid &) = delete;\n\n  lynx::base::android::ScopedWeakGlobalJavaRef<jobject> weak_owner_;\n};\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_PLATFORM_ANDROID_TRACE_CONTROLLER_ANDROID_H_\n"
  },
  {
    "path": "base/trace/native/platform/android/trace_controller_android_mock.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"base/trace/android/src/main/jni/gen/TraceController_jni.h\"\n#include \"base/trace/android/src/main/jni/gen/TraceController_register_jni.h\"\n#include \"base/trace/native/platform/android/trace_controller_android.h\"\n\nnamespace lynxtrace {\nnamespace jni {\nbool RegisterJNIForTraceController(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynxtrace\n\nstatic constexpr int kInvalidTraceSessionId = -1;\n\n// static\njlong CreateTraceController(JNIEnv* env, jobject jcaller) {\n  return reinterpret_cast<jlong>(lynx::trace::TraceController::Instance());\n}\n\n// static\njint StartTracing(JNIEnv* env, jobject jcaller, jlong ptr, jint buffer_size,\n                  jobjectArray include_categories,\n                  jobjectArray exclude_categories, jstring trace_file,\n                  jboolean enableSystrace, jboolean enableCompress) {\n  return kInvalidTraceSessionId;\n}\n\n// static\nvoid StopTracing(JNIEnv* env, jobject jcaller, jlong ptr, jint session_id) {}\n\n// static\nvoid StartStartupTracingIfNeeded(JNIEnv* env, jobject jcaller, jlong ptr) {}\n\nnamespace lynx {\nnamespace trace {\n\nstd::string TraceControllerDelegateAndroid::GenerateTracingFileDir() {\n  return \"\";\n}\n\nvoid TraceControllerDelegateAndroid::RefreshATraceTags() {}\n\nvoid TraceControllerDelegateAndroid::SetIsTracingStarted(\n    bool is_tracing_started) {}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/platform/android/trace_event_android_mock.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/android/src/main/jni/gen/TraceEvent_jni.h\"\n#include \"base/trace/android/src/main/jni/gen/TraceEvent_register_jni.h\"\n\nvoid BeginSection(JNIEnv* env, jclass jcaller, jstring category,\n                  jstring sectionName) {}\n// static\nvoid BeginSectionWithProps(JNIEnv* env, jclass jcaller, jstring category,\n                           jstring sectionName, jobject props) {}\n\n// static\nvoid EndSection(JNIEnv* env, jclass jcaller, jstring category,\n                jstring sectionName) {}\n\n// static\nvoid EndSectionWithProps(JNIEnv* env, jclass jcaller, jstring category,\n                         jstring sectionName, jobject props) {}\n\n// static\nstatic jboolean CategoryEnabled(JNIEnv* env, jclass jcaller, jstring category) {\n  return false;\n}\n\n// static\nvoid Instant(JNIEnv* env, jclass jcaller, jstring category, jstring sectionName,\n             jlong timestamp, jstring color) {}\n\n// static\nvoid InstantWithProps(JNIEnv* env, jclass jcaller, jstring category,\n                      jstring sectionName, jlong timestamp, jobject props) {}\n\nvoid Counter(JNIEnv* env, jclass jcaller, jstring category, jstring eventName,\n             jlong counterValue) {}\n\n// static\njboolean SystemTraceEnabled(JNIEnv* env, jclass jcaller) { return false; }\n// static\njboolean PerfettoTraceEnabled(JNIEnv* env, jclass jcaller) { return false; }\n\nnamespace lynxtrace {\nnamespace jni {\nbool RegisterJNIForTraceEvent(JNIEnv* env) { return RegisterNativesImpl(env); }\n}  // namespace jni\n}  // namespace lynxtrace\n"
  },
  {
    "path": "base/trace/native/platform/android/trace_event_android_perfetto.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <dlfcn.h>\n\n#include <string>\n\n#include \"base/trace/android/src/main/jni/gen/TraceEvent_jni.h\"\n#include \"base/trace/android/src/main/jni/gen/TraceEvent_register_jni.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"base/trace/native/track_event_wrapper.h\"\n#include \"core/base/android/jni_helper.h\"\n\nstatic void UpdateTraceDebugInfo(const jobject& props,\n                                 const lynx::perfetto::EventContext& ctx) {\n  JNIEnv* env = lynx::base::android::AttachCurrentThread();\n\n  // Gets the entrySet method in the java Map.\n  jmethodID entrySetMethod = env->GetMethodID(env->GetObjectClass(props),\n                                              \"entrySet\", \"()Ljava/util/Set;\");\n\n  // Invoke the entrySet method.\n  jobject set = env->CallObjectMethod(props, entrySetMethod);\n\n  // Gets the iterator method in the java Set.\n  jmethodID iteratorMethod = env->GetMethodID(\n      env->GetObjectClass(set), \"iterator\", \"()Ljava/util/Iterator;\");\n\n  // Invoke the iterator method.\n  jobject iterator = env->CallObjectMethod(set, iteratorMethod);\n\n  // Gets the Class of the java Iterator.\n  jclass iteratorClass = env->GetObjectClass(iterator);\n\n  // Gets hasNext and next methods from java Iterator.\n  jmethodID hasNextMethod = env->GetMethodID(iteratorClass, \"hasNext\", \"()Z\");\n  jmethodID nextMethod =\n      env->GetMethodID(iteratorClass, \"next\", \"()Ljava/lang/Object;\");\n\n  while (env->CallBooleanMethod(iterator, hasNextMethod)) {\n    jobject entry = env->CallObjectMethod(iterator, nextMethod);\n    jclass entryClass = env->GetObjectClass(entry);\n\n    jmethodID getKeyMethod =\n        env->GetMethodID(entryClass, \"getKey\", \"()Ljava/lang/Object;\");\n    jmethodID getValueMethod =\n        env->GetMethodID(entryClass, \"getValue\", \"()Ljava/lang/Object;\");\n\n    jstring key = (jstring)env->CallObjectMethod(entry, getKeyMethod);\n    jstring value = (jstring)env->CallObjectMethod(entry, getValueMethod);\n    const std::string& keyCStr =\n        lynx::base::android::JNIConvertHelper::ConvertToString(env, key);\n    const std::string& valueCStr =\n        lynx::base::android::JNIConvertHelper::ConvertToString(env, value);\n\n    auto* debug = ctx.event()->add_debug_annotations();\n    debug->set_name(keyCStr);\n    debug->set_string_value(valueCStr);\n\n    env->DeleteLocalRef(entry);\n    env->DeleteLocalRef(entryClass);\n  }\n}\n\nvoid BeginSection(JNIEnv* env, jclass jcaller, jstring category,\n                  jstring sectionName) {\n  const std::string& category_name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, category);\n  const std::string& name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, sectionName);\n  TRACE_EVENT_BEGIN(category_name.c_str(), nullptr,\n                    [&name](lynx::perfetto::EventContext ctx) {\n                      ctx.event()->set_name(name);\n                    });\n}\n// static\nvoid BeginSectionWithProps(JNIEnv* env, jclass jcaller, jstring category,\n                           jstring sectionName, jobject props) {\n  const std::string& category_name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, category);\n  const std::string& name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, sectionName);\n  TRACE_EVENT_BEGIN(category_name.c_str(), nullptr,\n                    [&name, &props](lynx::perfetto::EventContext ctx) {\n                      ctx.event()->set_name(name);\n                      UpdateTraceDebugInfo(props, ctx);\n                    });\n}\n\n// static\nvoid EndSection(JNIEnv* env, jclass jcaller, jstring category,\n                jstring sectionName) {\n  const std::string& category_name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, category);\n  const std::string& name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, sectionName);\n  TRACE_EVENT_END(category_name.c_str(),\n                  [&name](lynx::perfetto::EventContext ctx) {\n                    ctx.event()->set_name(name);\n                  });\n}\n\n// static\nvoid EndSectionWithProps(JNIEnv* env, jclass jcaller, jstring category,\n                         jstring sectionName, jobject props) {\n  const std::string& category_name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, category);\n  const std::string& name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, sectionName);\n  TRACE_EVENT_END(category_name.c_str(),\n                  [&name, &props](lynx::perfetto::EventContext ctx) {\n                    ctx.event()->set_name(name);\n                    UpdateTraceDebugInfo(props, ctx);\n                  });\n}\n\n// static\njboolean CategoryEnabled(JNIEnv* env, jclass jcaller, jstring category) {\n  const std::string& category_name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, category);\n  return TRACE_EVENT_CATEGORY_ENABLED(category_name.c_str());\n}\n\n// static\nvoid Instant(JNIEnv* env, jclass jcaller, jstring category, jstring sectionName,\n             jlong timestamp, jstring color) {\n  const std::string& category_name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, category);\n  const std::string& name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, sectionName);\n  const std::string& color_string =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, color);\n  TRACE_EVENT_INSTANT(\n      category_name.c_str(), nullptr,\n      [&name, &color_string, &timestamp](lynx::perfetto::EventContext ctx) {\n        ctx.event()->set_name(name);\n        ctx.event()->set_timestamp_absolute_us(\n            static_cast<uint64_t>(timestamp));\n        auto* debug = ctx.event()->add_debug_annotations();\n        debug->set_name(\"color\");\n        debug->set_string_value(color_string);\n      });\n}\n\n// static\nvoid InstantWithProps(JNIEnv* env, jclass jcaller, jstring category,\n                      jstring sectionName, jlong timestamp, jobject props) {\n  const std::string& category_name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, category);\n  const std::string& name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, sectionName);\n  TRACE_EVENT_INSTANT(\n      category_name.c_str(), nullptr,\n      [&name, &timestamp, &props](lynx::perfetto::EventContext ctx) {\n        ctx.event()->set_name(name);\n        ctx.event()->set_timestamp_absolute_us(\n            static_cast<uint64_t>(timestamp));\n        UpdateTraceDebugInfo(props, ctx);\n      });\n}\n\n// static\nvoid Counter(JNIEnv* env, jclass jcaller, jstring category, jstring name,\n             jlong counterValue) {\n  const std::string& category_name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, category);\n  const std::string& track_name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, name);\n  TRACE_COUNTER(category_name.c_str(), track_name.c_str(),\n                static_cast<uint64_t>(counterValue));\n}\n\n// static\njboolean SystemTraceEnabled(JNIEnv* env, jclass jcaller) { return false; }\n// static\njboolean PerfettoTraceEnabled(JNIEnv* env, jclass jcaller) { return true; }\n\nnamespace lynxtrace {\nnamespace jni {\nbool RegisterJNIForTraceEvent(JNIEnv* env) { return RegisterNativesImpl(env); }\n}  // namespace jni\n}  // namespace lynxtrace\n"
  },
  {
    "path": "base/trace/native/platform/android/trace_event_android_systrace.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <dlfcn.h>\n\n#include <string>\n\n#include \"base/trace/android/src/main/jni/gen/TraceEvent_jni.h\"\n#include \"base/trace/android/src/main/jni/gen/TraceEvent_register_jni.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/base/android/jni_helper.h\"\n\nvoid BeginSection(JNIEnv* env, jclass jcaller, jstring category,\n                  jstring sectionName) {\n  const std::string& name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, sectionName);\n  TRACE_EVENT_BEGIN(nullptr, name.c_str());\n}\n// static\nvoid BeginSectionWithProps(JNIEnv* env, jclass jcaller, jstring category,\n                           jstring sectionName, jobject props) {\n  const std::string& name =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, sectionName);\n  TRACE_EVENT_BEGIN(nullptr, name.c_str());\n}\n\n// static\nvoid EndSection(JNIEnv* env, jclass jcaller, jstring category,\n                jstring sectionName) {\n  TRACE_EVENT_END(nullptr);\n}\n\n// static\nvoid EndSectionWithProps(JNIEnv* env, jclass jcaller, jstring category,\n                         jstring sectionName, jobject props) {\n  TRACE_EVENT_END(nullptr);\n}\n\n// static\njboolean CategoryEnabled(JNIEnv* env, jclass jcaller, jstring category) {\n  return TRACE_EVENT_CATEGORY_ENABLED(nullptr);\n}\n\n// static\nvoid Instant(JNIEnv* env, jclass jcaller, jstring category, jstring sectionName,\n             jlong timestamp, jstring color) {}\n\n// static\nvoid InstantWithProps(JNIEnv* env, jclass jcaller, jstring category,\n                      jstring sectionName, jlong timestamp, jobject props) {}\n\n// static\nvoid Counter(JNIEnv* env, jclass jcaller, jstring category, jstring name,\n             jlong counterValue) {}\n\n// static\njboolean SystemTraceEnabled(JNIEnv* env, jclass jcaller) { return true; }\n// static\njboolean PerfettoTraceEnabled(JNIEnv* env, jclass jcaller) { return false; }\n\nnamespace lynx {\nnamespace trace {\n\nstatic void RuntimeLoadAtrace() {\n  LOGI(\"RuntimeLoadAtrace\");\n  void* lib = dlopen(\"libandroid.so\", RTLD_NOW || RTLD_LOCAL);\n  if (lib != nullptr) {\n    auto atrace_beginsection =\n        reinterpret_cast<lynx::trace::ATrace_beginSection_ptr>(\n            dlsym(lib, \"ATrace_beginSection\"));\n    auto atrace_endsection =\n        reinterpret_cast<lynx::trace::ATrace_endSection_ptr>(\n            dlsym(lib, \"ATrace_endSection\"));\n    auto atrace_beginasyncsection =\n        reinterpret_cast<lynx::trace::ATrace_beginAsyncSection_ptr>(\n            dlsym(lib, \"ATrace_beginAsyncSection\"));\n    auto atrace_endasyncsection =\n        reinterpret_cast<lynx::trace::ATrace_endAsyncSection_ptr>(\n            dlsym(lib, \"ATrace_endAsyncSection\"));\n\n    if (atrace_beginsection == nullptr || atrace_endsection == nullptr) {\n      LOGE(\"can not get ATrace_beginSection or ATrace_endSection\");\n      return;\n    }\n    lynx::trace::InitSystraceBeginSection(atrace_beginsection);\n    lynx::trace::InitSystraceEndSection(atrace_endsection);\n\n    if (atrace_beginasyncsection == nullptr ||\n        atrace_endasyncsection == nullptr) {\n      LOGE(\"can not get ATrace_beginAsyncSection or ATrace_endAsyncSection\");\n    }\n\n    lynx::trace::InitSystraceBeginAsynSection(atrace_beginasyncsection);\n    lynx::trace::InitSystraceEndAsynSection(atrace_endasyncsection);\n  } else {\n    LOGE(\"ATrace test_lib called failed, \");\n  }\n}\n\n}  // namespace trace\n}  // namespace lynx\n\nnamespace lynxtrace {\nnamespace jni {\nbool RegisterJNIForTraceEvent(JNIEnv* env) {\n  lynx::trace::RuntimeLoadAtrace();\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynxtrace\n"
  },
  {
    "path": "base/trace/native/platform/darwin/trace_controller_darwin.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_PLATFORM_DARWIN_TRACE_CONTROLLER_DARWIN_H_\n#define BASE_TRACE_NATIVE_PLATFORM_DARWIN_TRACE_CONTROLLER_DARWIN_H_\n\n#include <string>\n\n#include \"base/trace/native/trace_controller.h\"\n#include \"base/trace/native/trace_export.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass TRACE_EXPORT TraceControllerDelegateDarwin\n    : public TraceController::Delegate {\n public:\n  TraceControllerDelegateDarwin() = default;\n  virtual ~TraceControllerDelegateDarwin() = default;\n\n  std::string GenerateTracingFileDir() override;\n\n private:\n  TraceControllerDelegateDarwin(const TraceControllerDelegateDarwin&) = delete;\n  TraceControllerDelegateDarwin& operator=(\n      const TraceControllerDelegateDarwin&) = delete;\n};\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_PLATFORM_DARWIN_TRACE_CONTROLLER_DARWIN_H_\n"
  },
  {
    "path": "base/trace/native/platform/darwin/trace_controller_darwin.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/platform/darwin/trace_controller_darwin.h\"\n#import <Foundation/Foundation.h>\n#import <string>\n\nnamespace lynx {\nnamespace trace {\n\nTraceController* GetTraceControllerInstance() {\n  static bool should_init_delegate = true;\n  if (should_init_delegate) {\n    auto delegate = std::make_unique<TraceControllerDelegateDarwin>();\n    TraceController::Instance()->SetDelegate(std::move(delegate));\n    should_init_delegate = false;\n  }\n  return TraceController::Instance();\n}\n\nstd::string TraceControllerDelegateDarwin::GenerateTracingFileDir() {\n  return std::string([[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,\n                                                           YES) lastObject] UTF8String]);\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/platform/harmony/trace_controller_delegate_harmony.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/platform/harmony/trace_controller_delegate_harmony.h\"\n\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace trace {\n\nTraceController* GetTraceControllerInstance() {\n  static bool should_init_delegate = true;\n  if (should_init_delegate) {\n    auto delegate = std::make_unique<TraceControllerDelegateHarmony>();\n    TraceController::Instance()->SetDelegate(std::move(delegate));\n    should_init_delegate = false;\n  }\n  return TraceController::Instance();\n}\n\nstd::string TraceControllerDelegateHarmony::trace_dir_path_ = \"\";\n\nstd::string TraceControllerDelegateHarmony::GenerateTracingFileDir() {\n  DCHECK(trace_dir_path_.size());\n  return trace_dir_path_;\n}\n\n// static\nvoid TraceControllerDelegateHarmony::SetTraceDirPath(const std::string& dir) {\n  DCHECK(dir.size());\n  trace_dir_path_ = dir;\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/platform/harmony/trace_controller_delegate_harmony.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_PLATFORM_HARMONY_TRACE_CONTROLLER_DELEGATE_HARMONY_H_\n#define BASE_TRACE_NATIVE_PLATFORM_HARMONY_TRACE_CONTROLLER_DELEGATE_HARMONY_H_\n\n#include <memory>\n#include <string>\n\n#include \"base/trace/native/trace_controller.h\"\n#include \"base/trace/native/trace_export.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass TRACE_EXPORT TraceControllerDelegateHarmony\n    : public TraceController::Delegate {\n public:\n  TraceControllerDelegateHarmony() = default;\n  ~TraceControllerDelegateHarmony() = default;\n\n  std::string GenerateTracingFileDir() override;\n  static void SetTraceDirPath(const std::string& dir);\n\n private:\n  static std::string trace_dir_path_;\n};\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_PLATFORM_HARMONY_TRACE_CONTROLLER_DELEGATE_HARMONY_H_\n"
  },
  {
    "path": "base/trace/native/platform/harmony/trace_controller_harmony.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/platform/harmony/trace_controller_harmony.h\"\n\n#include <cassert>\n#include <memory>\n#include <string>\n#include <unordered_map>\n\n#include \"base/include/platform/harmony/napi_util.h\"\n#include \"base/trace/native/platform/harmony/trace_controller_delegate_harmony.h\"\n\nnamespace lynx {\nnamespace trace {\n\nnapi_value TraceControllerHarmony::Init(napi_env env, napi_value exports) {\n#define DECLARE_NAPI_STATIC_FUNCTION(name, func) \\\n  {(name), nullptr, (func), nullptr, nullptr, nullptr, napi_static, nullptr}\n\n  napi_property_descriptor properties[] = {\n      DECLARE_NAPI_STATIC_FUNCTION(\"startTracing\", StartTracing),\n      DECLARE_NAPI_STATIC_FUNCTION(\"stopTracing\", StopTracing),\n  };\n#undef DECLARE_NAPI_STATIC_FUNCTION\n\n  constexpr size_t size = std::size(properties);\n\n  napi_value cons;\n  napi_status status =\n      napi_define_class(env, \"TraceControllerHarmony\", NAPI_AUTO_LENGTH,\n                        Constructor, nullptr, size, properties, &cons);\n  assert(status == napi_ok);\n\n  status =\n      napi_set_named_property(env, exports, \"TraceControllerHarmony\", cons);\n  return exports;\n}\n\nnapi_value TraceControllerHarmony::Constructor(napi_env env,\n                                               napi_callback_info info) {\n  size_t argc = 0;\n  napi_value args[1];\n  napi_value js_this;\n  napi_get_cb_info(env, info, &argc, args, &js_this, nullptr);\n  return js_this;\n}\n\nnapi_value TraceControllerHarmony::StartTracing(napi_env env,\n                                                napi_callback_info info) {\n#if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n  size_t argc = 1;\n  napi_value args[1] = {nullptr};\n  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);\n  std::unordered_map<std::string, std::string> trace_config{};\n  base::NapiUtil::ConvertToMap(env, args[0], trace_config);\n  auto config = std::make_shared<lynx::trace::TraceConfig>();\n\n  config->buffer_size = 81920;  // Default buffer: 80M\n  if (trace_config.find(\"bufferSize\") != trace_config.end()) {\n    config->buffer_size = std::stoi(trace_config[\"bufferSize\"]);\n  }\n\n  if (trace_config.find(\"filePath\") != trace_config.end()) {\n    config->file_path = trace_config[\"filePath\"];\n  }\n\n  if (trace_config.find(\"enableCompress\") != trace_config.end()) {\n    config->enable_compress = trace_config[\"enableCompress\"] == \"true\";\n  }\n  config->included_categories = {\"*\"};\n  config->excluded_categories = {\"*\"};\n  auto session_id =\n      lynx::trace::GetTraceControllerInstance()->StartTracing(config);\n\n  napi_value sessionId = nullptr;\n  napi_create_int32(env, session_id, &sessionId);\n  return sessionId;\n#else\n  return nullptr;\n#endif\n}\n\nnapi_value TraceControllerHarmony::StopTracing(napi_env env,\n                                               napi_callback_info info) {\n#if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n  size_t argc = 1;\n  napi_value args[1] = {nullptr};\n  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);\n  int session_id = base::NapiUtil::ConvertToInt64(env, args[0]);\n  auto success =\n      lynx::trace::GetTraceControllerInstance()->StopTracing(session_id);\n\n  napi_value ret_success = nullptr;\n  napi_get_boolean(env, success, &ret_success);\n  return ret_success;\n#else\n  return nullptr;\n#endif\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/platform/harmony/trace_controller_harmony.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_PLATFORM_HARMONY_TRACE_CONTROLLER_HARMONY_H_\n#define BASE_TRACE_NATIVE_PLATFORM_HARMONY_TRACE_CONTROLLER_HARMONY_H_\n\n#include <node_api.h>\n\nnamespace lynx {\nnamespace trace {\n\nclass TraceControllerHarmony {\n public:\n  static napi_value Init(napi_env env, napi_value exports);\n\n private:\n  static napi_value Constructor(napi_env env, napi_callback_info info);\n  static napi_value StartTracing(napi_env env, napi_callback_info info);\n  static napi_value StopTracing(napi_env env, napi_callback_info info);\n};\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_PLATFORM_HARMONY_TRACE_CONTROLLER_HARMONY_H_\n"
  },
  {
    "path": "base/trace/native/platform/harmony/trace_event_utils_systrace_harmony.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string>\n\n#include \"base/trace/native/trace_event_utils_systrace.h\"\n#include \"hitrace/trace.h\"\n\nnamespace lynx {\nnamespace trace {\n\nvoid InitSystraceBeginSection(ATrace_beginSection_ptr atrace_beginsection) {}\nvoid InitSystraceEndSection(ATrace_endSection_ptr atrace_endsection) {}\nvoid InitSystraceBeginAsynSection(\n    ATrace_beginAsyncSection_ptr atrace_beginasyncsection) {}\nvoid InitSystraceEndAsynSection(\n    ATrace_endAsyncSection_ptr atrace_endasyncsection) {}\n\nvoid TraceEventBegin(const char* name) { OH_HiTrace_StartTrace(name); }\nvoid TraceEventBegin(const char* name,\n                     __attribute__((unused)) uint64_t cookie) {\n  OH_HiTrace_StartTrace(name);\n}\nvoid TraceEventBegin(const std::string& name) {\n  OH_HiTrace_StartTrace(name.c_str());\n}\nvoid TraceEventBegin(const std::string& name,\n                     __attribute__((unused)) uint64_t cookie) {\n  OH_HiTrace_StartTrace(name.c_str());\n}\nvoid TraceEventEnd() { OH_HiTrace_FinishTrace(); }\nvoid TraceEventEnd(__attribute__((unused)) const char* name,\n                   __attribute__((unused)) uint64_t cookie) {\n  OH_HiTrace_FinishTrace();\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/platform/platform.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../../config.gni\")\n\nplatform_public_headers = []\nplatform_shared_sources = []\n\nif (enable_trace == \"perfetto\") {\n  if (is_android) {\n    platform_public_headers += [ \"platform/android/trace_controller_android.h\" ]\n    platform_shared_sources += [\n      \"platform/android/trace_controller_android.cc\",\n      \"platform/android/trace_controller_android.h\",\n      \"platform/android/trace_event_android_perfetto.cc\",\n    ]\n  } else if (is_ios || is_mac) {\n    platform_public_headers += [ \"platform/darwin/trace_controller_darwin.h\" ]\n    platform_shared_sources += [\n      \"platform/darwin/trace_controller_darwin.h\",\n      \"platform/darwin/trace_controller_darwin.mm\",\n    ]\n  } else if (is_win) {\n    platform_public_headers += [ \"platform/windows/trace_controller_win.h\" ]\n    platform_shared_sources += [\n      \"platform/windows/trace_controller_win.cc\",\n      \"platform/windows/trace_controller_win.h\",\n    ]\n  }\n} else {\n  if (is_android) {\n    platform_public_headers += [ \"platform/android/trace_controller_android.h\" ]\n    platform_shared_sources += [\n      \"platform/android/trace_controller_android.h\",\n      \"platform/android/trace_controller_android_mock.cc\",\n    ]\n    if (enable_trace == \"systrace\") {\n      platform_shared_sources +=\n          [ \"platform/android/trace_event_android_systrace.cc\" ]\n    } else {\n      platform_shared_sources +=\n          [ \"platform/android/trace_event_android_mock.cc\" ]\n    }\n  } else if (is_win) {\n    platform_public_headers += [ \"platform/windows/trace_controller_win.h\" ]\n    platform_shared_sources += [\n      \"platform/windows/trace_controller_win.h\",\n      \"platform/windows/trace_controller_win_mock.cc\",\n    ]\n  }\n}\n\nif (is_harmony) {\n  if (enable_trace == \"systrace\") {\n    platform_shared_sources +=\n        [ \"platform/harmony/trace_event_utils_systrace_harmony.cc\" ]\n  }\n  platform_shared_sources += [\n    \"platform/harmony/trace_controller_delegate_harmony.cc\",\n    \"platform/harmony/trace_controller_delegate_harmony.h\",\n    \"platform/harmony/trace_controller_harmony.cc\",\n    \"platform/harmony/trace_controller_harmony.h\",\n  ]\n}\n"
  },
  {
    "path": "base/trace/native/platform/windows/trace_controller_win.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/platform/windows/trace_controller_win.h\"\n\n#include <Shlobj.h>\n#include <Shlwapi.h>\n#include <Windows.h>\n#include <time.h>\n\n#include <memory>\n#include <utility>\n\nnamespace lynx {\nnamespace trace {\n\nTraceController* GetTraceControllerInstance() {\n  static bool should_init_delegate = true;\n  if (should_init_delegate) {\n    auto delegate = std::make_unique<TraceControllerDelegateWin>();\n    TraceController::Instance()->SetDelegate(std::move(delegate));\n    should_init_delegate = false;\n  }\n  return TraceController::Instance();\n}\n\n// static\nstd::string TraceControllerDelegateWin::GenerateTracingFileDir() {\n  // put trace file on the desktop\n  char path[MAX_PATH];\n  ::SHGetFolderPathA(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, path);\n  return path;\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/platform/windows/trace_controller_win.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_PLATFORM_WINDOWS_TRACE_CONTROLLER_WIN_H_\n#define BASE_TRACE_NATIVE_PLATFORM_WINDOWS_TRACE_CONTROLLER_WIN_H_\n\n#include <string>\n\n#include \"base/trace/native/trace_controller.h\"\n#include \"base/trace/native/trace_export.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass TRACE_EXPORT TraceControllerDelegateWin\n    : public TraceController::Delegate {\n public:\n  TraceControllerDelegateWin() = default;\n\n  std::string GenerateTracingFileDir() override;\n};\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_PLATFORM_WINDOWS_TRACE_CONTROLLER_WIN_H_\n"
  },
  {
    "path": "base/trace/native/platform/windows/trace_controller_win_mock.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/platform/windows/trace_controller_win.h\"\n\nnamespace lynx {\nnamespace trace {\n\nTraceController* GetTraceControllerInstance() {\n  return TraceController::Instance();\n}\n\n// static\nstd::string TraceControllerDelegateWin::GenerateTracingFileDir() {\n  // put trace file on the desktop\n  return \"\";\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/trace_controller.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n/**\n * This empty implementation is used when the Trace functionality is turned off\n */\n\n#include \"base/trace/native/trace_controller.h\"\n\nnamespace lynx {\nnamespace trace {\n\nTraceController* TraceController::Instance() {\n  static TraceController instance_;\n  return &instance_;\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/trace_controller.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_TRACE_CONTROLLER_H_\n#define BASE_TRACE_NATIVE_TRACE_CONTROLLER_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/trace/native/trace_export.h\"\n\nnamespace lynx {\nnamespace trace {\n\nenum class RuntimeProfilerType { v8 = 0, quickjs };\n\nstruct TRACE_EXPORT TraceConfig {\n  const uint32_t kDefaultBufferSize = 40960;  // kb\n  enum RecordMode {\n    RECORD_AS_MUCH_AS_POSSIBLE,\n    RECORD_UNTIL_FULL,\n    RECORD_CONTINUOUSLY,\n    ECHO_TO_CONSOLE\n  } record_mode;\n  bool enable_systrace;\n  uint32_t buffer_size;\n  bool is_startup_tracing;\n  std::vector<std::string> included_categories;\n  std::vector<std::string> excluded_categories;\n  std::string file_path;\n  int32_t js_profile_interval;\n  RuntimeProfilerType js_profile_type;\n  bool enable_compress;\n  TraceConfig()\n      : record_mode(RECORD_AS_MUCH_AS_POSSIBLE),\n        enable_systrace(false),\n        buffer_size(kDefaultBufferSize),\n        is_startup_tracing(false),\n        js_profile_interval(-1),\n        js_profile_type(RuntimeProfilerType::quickjs),\n        enable_compress(false) {}\n};\n\n// DispatchBegin()/DispatchEnd() of TracePlugin that injected into\n// TraceController is called when start/stop lynx trace.\nclass TRACE_EXPORT TracePlugin\n    : public std::enable_shared_from_this<TracePlugin> {\n public:\n  TracePlugin() = default;\n  virtual ~TracePlugin() = default;\n  virtual void DispatchBegin() = 0;\n  virtual void DispatchEnd() = 0;\n  virtual void DispatchSetup(const std::shared_ptr<TraceConfig>& config) {}\n  virtual std::string Name() = 0;\n};\n\nclass TRACE_EXPORT TraceController {\n public:\n  class Delegate {\n   public:\n    virtual ~Delegate() = default;\n    virtual std::string GenerateTracingFileDir() = 0;\n#ifdef OS_ANDROID\n    virtual void RefreshATraceTags() = 0;\n    virtual void SetIsTracingStarted(bool is_tracing_started) = 0;\n#endif  // BASE_TRACE_NATIVE_TRACE_CONTROLLER_H_\n  };\n\n  TraceController() = default;\n  virtual ~TraceController() = default;\n\n  TraceController(const TraceController&) = delete;\n  TraceController& operator=(const TraceController&) = delete;\n  TraceController(TraceController&&) = delete;\n  TraceController& operator=(TraceController&&) = delete;\n\n  static TraceController* Instance();\n\n  void SetDelegate(std::unique_ptr<Delegate> delegate) {\n    delegate_ = std::move(delegate);\n  }\n\n  virtual int StartTracing(const std::shared_ptr<TraceConfig>& config) {\n    return -1;\n  };\n  virtual bool StopTracing(int session_id) { return false; }\n\n  // trace plugin\n  virtual void AddTracePlugin(TracePlugin* plugin) {}\n  virtual bool DeleteTracePlugin(const std::string& plugin_name) {\n    return false;\n  }\n\n  // register callback\n  virtual void AddCompleteCallback(int session_id,\n                                   const std::function<void()> callback) {}\n  virtual void RemoveCompleteCallbacks(int session_id) {}\n\n  // startup functions\n  virtual void StartStartupTracingIfNeeded() {}\n  virtual void SetStartupTracingConfig(std::string config) {}\n  virtual std::string GetStartupTracingConfig() { return \"\"; }\n  virtual std::string GetStartupTracingFilePath() { return \"\"; }\n  virtual bool IsTracingStarted() { return false; }\n\n protected:\n  std::unique_ptr<Delegate> delegate_ = nullptr;\n};\n\n[[maybe_unused]] TRACE_EXPORT TraceController* GetTraceControllerInstance();\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_TRACE_CONTROLLER_H_\n"
  },
  {
    "path": "base/trace/native/trace_controller_impl.cc",
    "content": "// Copyright (C) 2017 The Android Open Source Project\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/trace_controller_impl.h\"\n\n#include <fcntl.h>\n\n#include <cstring>\n#include <ctime>\n#include <fstream>\n#include <functional>\n#include <sstream>\n#include <string>\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"base/include/thread/timed_task.h\"\n#include \"base/trace/native/trace_event_utils_perfetto.h\"\n#include \"base/trace/native/track_event_wrapper.h\"\n#include \"third_party/rapidjson/document.h\"\n\nPERFETTO_DEFINE_CATEGORIES();\nPERFETTO_TRACK_EVENT_STATIC_STORAGE();\n\nusing TrackEventMessage = ::perfetto::protos::pbzero::TrackEvent;\nusing TrackEvent = ::perfetto::TrackEvent;\nusing TrackEventInternal = ::perfetto::internal::TrackEventInternal;\nusing TrackEvent_Type = ::perfetto::protos::pbzero::TrackEvent_Type;\n\nnamespace lynx {\n\nnamespace trace {\n\n// Implementations of the definition of the\n// \"base/trace/native/trace_event_utils_perfetto.h\"\nconstexpr ::perfetto::CounterTrack ConvertToPerfCounterTrack(\n    const lynx::perfetto::CounterTrack& counter_track) {\n  if (counter_track.is_global_) {\n    if (counter_track.unit_name_ != nullptr) {\n      return ::perfetto::CounterTrack::Global(\n          ::perfetto::DynamicString(counter_track.name_),\n          counter_track.unit_name_);\n    } else {\n      return ::perfetto::CounterTrack::Global(\n          ::perfetto::DynamicString(counter_track.name_),\n          static_cast<::perfetto::CounterTrack::Unit>(counter_track.unit_));\n    }\n  }\n  auto track =\n      ::perfetto::CounterTrack(::perfetto::DynamicString(counter_track.name_))\n          .set_category(counter_track.category_)\n          .set_unit_name(counter_track.unit_name_)\n          .set_unit_multiplier(counter_track.unit_multiplier_)\n          .set_is_incremental(counter_track.is_incremental_)\n          .set_unit(\n              static_cast<::perfetto::CounterTrack::Unit>(counter_track.unit_));\n  return track;\n}\n\nuint64_t GetFlowId() {\n  static std::atomic<uint64_t> sTraceEventFlowId = 0;\n  return sTraceEventFlowId++;\n}\n\nuint64_t GetTraceTimeNs() { return TrackEventInternal::GetTimeNs(); }\n\nvoid TraceEventImplementation(const char* category_name,\n                              const std::string& name, TraceEventType phase,\n                              const lynx::perfetto::Track* track_id,\n                              const uint64_t& timestamp,\n                              const FuncType& callback) {\n  TraceEventImplementation(category_name, nullptr, phase, track_id, timestamp,\n                           [&name, callback = std::move(callback)](\n                               lynx::perfetto::EventContext ctx) {\n                             ctx.event()->set_name(name);\n                             if (callback) {\n                               callback(ctx);\n                             }\n                           });\n}\n\nvoid TraceEventImplementation(const char* category_name, const char* name,\n                              TraceEventType phase,\n                              const lynx::perfetto::Track* track_id,\n                              const uint64_t& timestamp,\n                              const FuncType& callback) {\n  TrackEvent::Trace([&](TrackEvent::TraceContext ctx) {\n    if (!TrackEvent::IsDynamicCategoryEnabled(\n            &ctx, ::perfetto::DynamicCategory{category_name})) {\n      return;\n    }\n\n    ::perfetto::TraceTimestamp trace_timestamp = ::perfetto::\n        TraceTimestampTraits<uint64_t>::ConvertTimestampToTraceTimeNs(\n            timestamp ?: TrackEventInternal::GetTimeNs());\n    // TODO(yongjie): enable DCHECK later.\n    // DCHECK(trace_timestamp.clock_id == TrackEventInternal::GetClockId());\n\n    ::perfetto::internal::TrackEventTlsState& tls_state =\n        *ctx.GetCustomTlsState();\n    // Make sure incremental state is valid.\n    ::perfetto::TraceWriterBase* trace_writer = ctx.getTraceWriter();\n    ::perfetto::internal::TrackEventIncrementalState* incr_state =\n        ctx.GetIncrementalState();\n\n    TrackEventInternal::ResetIncrementalStateIfRequired(\n        trace_writer, incr_state, tls_state, trace_timestamp);\n    // Write the track descriptor before any event on the track.\n    TrackEventInternal::WriteTrackDescriptorIfNeeded(\n        track_id == nullptr ? TrackEventInternal::kDefaultTrack\n                            : ::perfetto::Track(track_id->id()),\n        trace_writer, incr_state, tls_state, trace_timestamp);\n\n    // Write the event itself.\n    {\n      auto event_ctx = TrackEventInternal::WriteEvent(\n          trace_writer, incr_state, tls_state, nullptr,\n          static_cast<TrackEvent_Type>(phase), trace_timestamp, false);\n      TrackEventInternal::WriteEventName(::perfetto::StaticString(name),\n                                         event_ctx, tls_state);\n      if (category_name != nullptr) {\n        event_ctx.event()->add_categories(category_name, strlen(category_name));\n      }\n      if (track_id != nullptr) {\n        event_ctx.event()->set_track_uuid(\n            ::perfetto::Track(track_id->id()).uuid);\n      }\n      if (callback) {\n        lynx::perfetto::TrackEvent event(&event_ctx);\n        lynx::perfetto::EventContext out_ctx(&event);\n        callback(std::move(out_ctx));\n      }\n    }  // event_ctx\n  });\n}\n\nvoid TraceEventImplementation(const char* category_name,\n                              const lynx::perfetto::CounterTrack& counter_track,\n                              TraceEventType phase, const uint64_t& timestamp,\n                              const uint64_t& counter,\n                              const FuncType& callback) {\n  auto track = ConvertToPerfCounterTrack(counter_track);\n\n  TrackEvent::Trace([&](TrackEvent::TraceContext ctx) {\n    if (!TrackEvent::IsDynamicCategoryEnabled(\n            &ctx, ::perfetto::DynamicCategory{category_name})) {\n      return;\n    }\n    ::perfetto::TraceTimestamp trace_timestamp = ::perfetto::\n        TraceTimestampTraits<uint64_t>::ConvertTimestampToTraceTimeNs(\n            timestamp ?: TrackEventInternal::GetTimeNs());\n    // DCHECK(trace_timestamp.clock_id == TrackEventInternal::GetClockId());\n\n    // Make sure incremental state is valid.\n    ::perfetto::internal::TrackEventTlsState& tls_state =\n        *ctx.GetCustomTlsState();\n    ::perfetto::TraceWriterBase* trace_writer = ctx.getTraceWriter();\n    ;\n    ::perfetto::internal::TrackEventIncrementalState* incr_state =\n        ctx.GetIncrementalState();\n\n    TrackEventInternal::ResetIncrementalStateIfRequired(\n        trace_writer, incr_state, tls_state, trace_timestamp);\n\n    // Write the track descriptor before any event on the track.\n    TrackEventInternal::WriteTrackDescriptorIfNeeded(\n        track, trace_writer, incr_state, tls_state, trace_timestamp);\n\n    // Write the event itself.\n    {\n      auto event_ctx = TrackEventInternal::WriteEvent(\n          trace_writer, incr_state, tls_state, nullptr,\n          static_cast<TrackEvent_Type>(phase), trace_timestamp, false);\n\n      event_ctx.event()->set_track_uuid(track.uuid);\n      event_ctx.event()->set_double_counter_value(counter);\n      if (callback) {\n        lynx::perfetto::TrackEvent event(&event_ctx);\n        lynx::perfetto::EventContext out_ctx(&event);\n        callback(std::move(out_ctx));\n      }\n    }  // event_ctx\n  });\n}\n\nbool TraceEventCategoryEnabled(const char* category) {\n  return TrackEvent::IsDynamicCategoryEnabled(\n      ::perfetto::DynamicCategory(category));\n}\n\nvoid TraceRuntimeProfile(const std::string& runtime_profile,\n                         const uint64_t track_id, const int32_t profile_id) {\n  static uint64_t size = 100 * 1024;  // 100kb\n  TrackEvent::Trace([&](TrackEvent::TraceContext ctx) {\n    uint64_t count = static_cast<uint64_t>(runtime_profile.size()) / size + 1;\n    ctx.Flush();\n    for (uint64_t j = 0; j < count; j++) {\n      auto packet = ctx.NewTracePacket();\n      auto profile_packet = packet->set_js_profile_packet();\n      profile_packet->set_track_id(track_id);\n      profile_packet->set_profile_id(profile_id);\n      bool is_done = false;\n      uint64_t length = size;\n      if (j == count - 1) {\n        is_done = true;\n        length = runtime_profile.size() - size * j;\n      }\n      profile_packet->set_runtime_profile(runtime_profile.data() + size * j,\n                                          length);\n      profile_packet->set_is_done(is_done);\n      packet->Finalize();\n      ctx.Flush();\n    }\n  });\n}\n\n// Implementations of the definition of the\n// \"base/trace/native/trace_controller_impl.h\"\n\nTraceController* TraceController::Instance() {\n  static TraceControllerImpl instance_;\n  return &instance_;\n}\n\nTraceControllerImpl::TraceControllerImpl() : TraceController() {\n  ::perfetto::TracingInitArgs args;\n  // #if OS_ANDROID\n  //   // only android support system backend\n  //   args.backends |= ::perfetto::kSystemBackend;\n  // #endif\n  args.backends |= ::perfetto::kInProcessBackend;\n  args.shmem_size_hint_kb = 1024;\n  ::perfetto::Tracing::Initialize(args);\n  TrackEvent::Register();\n}\n\nint TraceControllerImpl::StartTracing(\n    const std::shared_ptr<TraceConfig>& config) {\n  auto& session = CreateNewSession(config);\n  session.config = config;\n\n  // handle categories set\n  ::perfetto::protos::gen::TrackEventConfig track_event_cfg;\n  auto* enabled_categories = track_event_cfg.mutable_enabled_categories();\n  auto* disabled_categories = track_event_cfg.mutable_disabled_categories();\n  track_event_cfg.set_disable_incremental_timestamps(true);\n  enabled_categories->insert(enabled_categories->begin(),\n                             config->included_categories.begin(),\n                             config->included_categories.end());\n  disabled_categories->insert(disabled_categories->begin(),\n                              config->excluded_categories.begin(),\n                              config->excluded_categories.end());\n  if (std::find(enabled_categories->begin(), enabled_categories->end(),\n                INTERNAL_TRACE_CATEGORY_SCREENSHOTS) !=\n      enabled_categories->end()) {\n    track_event_cfg.add_enabled_tags(\"Screenshot\");\n  }\n\n  // perfetto trace config\n  ::perfetto::TraceConfig cfg;\n  auto* ds_cfg = cfg.add_data_sources()->mutable_config();\n  ds_cfg->set_name(\"track_event\");\n  ds_cfg->set_track_event_config_raw(track_event_cfg.SerializeAsString());\n  // DCHECK(config->buffer_size > 0);\n  cfg.set_flush_period_ms(1000);\n  cfg.add_buffers()->set_size_kb(config->buffer_size);\n\n  if (config->enable_compress) {\n    cfg.set_compression_type(::perfetto::TraceConfig::COMPRESSION_TYPE_DEFLATE);\n  }\n  // file path\n  if (config->file_path.empty() && delegate_) {\n    if (trace_file_dir_.empty()) {\n      trace_file_dir_ = delegate_->GenerateTracingFileDir();\n    }\n    config->file_path = GenerateTraceFilePath(trace_file_dir_);\n  }\n\n  // setup and start session\n  if (config->record_mode == TraceConfig::RECORD_CONTINUOUSLY) {\n    // write trace events from buffer to file every 3 seconds.\n    // DCHECK(!config->file_path.empty());\n    cfg.set_file_write_period_ms(3 * 1000);\n    int fd = open(config->file_path.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0600);\n    session.opened_fds.push_back(fd);\n    session.session_impl->Setup(cfg, fd);\n  } else {\n    session.session_impl->Setup(cfg);\n  }\n\n  for (auto& trace_plugin_pair : trace_plugins_) {\n    if (trace_plugin_pair.second) {\n      trace_plugin_pair.second->DispatchSetup(config);\n    }\n  }\n\n#ifdef OS_ANDROID\n  if (delegate_) {\n    delegate_->RefreshATraceTags();\n    delegate_->SetIsTracingStarted(true);\n  }\n#endif\n  session.session_impl->StartBlocking();\n\n  // plugin\n  for (auto& trace_plugin_pair : trace_plugins_) {\n    if (trace_plugin_pair.second) {\n      trace_plugin_pair.second->DispatchBegin();\n    }\n  }\n  if (config->enable_systrace) {\n    if (!hook_systrace_) {\n      hook_systrace_ = std::make_unique<HookSystemTrace>();\n    }\n    hook_systrace_->Install();\n  }\n\n  // status\n  session.started = true;\n  is_tracing_started_ = true;\n  LOGI(\"Tracing started, session id: \" << session.id << \" buffer size: \"\n                                       << config->buffer_size);\n\n  return session.id;\n}\n\nbool TraceControllerImpl::StopTracing(int session_id) {\n  // find session\n  auto session_pair = tracing_sessions_.find(session_id);\n  if (session_pair == tracing_sessions_.end()) {\n    LOGE(\"Tracing session not found: \" << session_id);\n    return false;\n  }\n\n  // clean plugin\n  for (auto& trace_plugin_pair : trace_plugins_) {\n    if (trace_plugin_pair.second) {\n      trace_plugin_pair.second->DispatchEnd();\n    }\n  }\n  trace_plugins_.clear();\n\n  auto& session = session_pair->second;\n  session->session_impl->StopBlocking();\n  session->started = false;\n  is_tracing_started_ = false;\n#ifdef OS_ANDROID\n  if (delegate_) {\n    delegate_->SetIsTracingStarted(false);\n  }\n#endif\n  if (session->config->is_startup_tracing) {\n    startup_tracing_file_ = session->config->file_path;\n  }\n  LOGI(\"Tracing stopped, file path:\" << session->config->file_path);\n  // DCHECK(session->config != nullptr);\n\n  if (session->config->record_mode == TraceConfig::RECORD_CONTINUOUSLY) {\n    for (int& fd : session->opened_fds) {\n#ifndef _WIN32\n      fsync(fd);\n#endif\n      close(fd);\n    }\n    session->opened_fds.clear();\n  } else {\n    std::vector<char> trace_data(session->session_impl->ReadTraceBlocking());\n    std::ofstream output(session->config->file_path,\n                         std::ios::out | std::ios::binary);\n    output.write(&trace_data[0], trace_data.size());\n    output.flush();\n  }\n\n  if (session->config->enable_systrace && hook_systrace_) {\n    hook_systrace_->Uninstall();\n  }\n\n  for (const auto& callback : session->complete_callbacks) {\n    callback();\n  }\n  // register an empty backend to avoid using a lock\n  tracing_sessions_.erase(session_id);\n  LOGI(\"Tracing stopped, session id: \" << session_id);\n\n  return true;\n}\n\nvoid TraceControllerImpl::AddTracePlugin(TracePlugin* plugin) {\n  if (plugin) {\n    auto plugin_name = plugin->Name();\n    std::lock_guard<std::mutex> lock(mutex_);\n    auto iter = trace_plugins_.find(plugin_name);\n    if (iter != trace_plugins_.end()) {\n      LOGI(\"The trace plugin is already set up.\");\n      return;\n    }\n    trace_plugins_.emplace(plugin_name, plugin);\n  }\n}\n\nbool TraceControllerImpl::DeleteTracePlugin(const std::string& plugin_name) {\n  auto iterator = trace_plugins_.find(plugin_name);\n  if (iterator != trace_plugins_.end()) {\n    trace_plugins_.erase(iterator);\n    return true;\n  }\n  LOGI(\"There is no trace plugin that you want to remove.\");\n  return false;\n}\n\nvoid TraceControllerImpl::AddCompleteCallback(\n    int session_id, const std::function<void()> callback) {\n  auto session_pair = tracing_sessions_.find(session_id);\n  if (session_pair == tracing_sessions_.end()) {\n    LOGE(\"Tracing session not found: \" << session_id);\n    return;\n  }\n  auto& session = session_pair->second;\n  session->complete_callbacks.push_back(callback);\n}\n\nvoid TraceControllerImpl::RemoveCompleteCallbacks(int session_id) {\n  auto session_pair = tracing_sessions_.find(session_id);\n  if (session_pair == tracing_sessions_.end()) {\n    LOGE(\"Tracing session not found: \" << session_id);\n    return;\n  }\n  auto& session = session_pair->second;\n  session->complete_callbacks.clear();\n}\n\nvoid TraceControllerImpl::StartStartupTracingIfNeeded() {\n  static constexpr const char* const kStartupDuraion = \"startup_duration\";\n  static constexpr const char* const kEnableSystrace = \"enable_systrace\";\n  static constexpr const char* const kResultFile = \"result_file\";\n  const auto startup_config = this->GetStartupTracingConfig();\n  if (startup_config.empty()) {\n    return;\n  }\n  rapidjson::Document doc;\n  if (doc.Parse(startup_config.c_str()).HasParseError()) {\n    return;\n  }\n\n  if (!doc.HasMember(kStartupDuraion) || !doc[kStartupDuraion].IsNumber()) {\n    return;\n  }\n  // unit: seconds\n  const int duration = doc[kStartupDuraion].GetInt();\n  if (duration <= 0) {\n    return;\n  }\n\n  bool enable_systrace = false;\n  if (doc.HasMember(kEnableSystrace) && doc[kEnableSystrace].IsBool()) {\n    enable_systrace = doc[kEnableSystrace].GetBool();\n  }\n\n  std::string result_file;\n  if (doc.HasMember(kResultFile) && doc[kResultFile].IsString()) {\n    result_file = doc[kResultFile].GetString();\n  }\n\n  static base::NoDestructor<fml::Thread> startup_trace_thread(\n      \"Lynx_Startup_Trace\");\n  auto trace_config = std::make_shared<lynx::trace::TraceConfig>();\n  if (!result_file.empty()) {\n    trace_config->file_path = result_file;\n  }\n  trace_config->included_categories = {\"*\"};\n  trace_config->excluded_categories = {\"*\"};\n  trace_config->enable_systrace = enable_systrace;\n  trace_config->is_startup_tracing = true;\n  const int session_id = this->StartTracing(trace_config);\n  LOGD(\"Lynx Startup Trace started\");\n  auto stop_startup_tracing = [this, session_id]() {\n    this->StopTracing(session_id);\n    LOGD(\"Lynx Startup Trace stopped\");\n    const auto config_path = this->trace_file_dir_ + kStartupTracingFile;\n    if (!remove(config_path.c_str())) {\n      LOGD(\"Lynx Startup Trace config file removed\");\n    } else {\n      LOGD(\"Lynx Startup Trace config file remove fail\");\n    }\n  };\n  startup_trace_thread->GetTaskRunner()->PostDelayedTask(\n      std::move(stop_startup_tracing), fml::TimeDelta::FromSeconds(duration));\n}\n\nvoid TraceControllerImpl::SetStartupTracingConfig(std::string config) {\n  if (trace_file_dir_.empty()) {\n    trace_file_dir_ = delegate_->GenerateTracingFileDir();\n    if (trace_file_dir_.empty()) {\n      return;\n    }\n  }\n  const std::string trace_config_path = trace_file_dir_ + kStartupTracingFile;\n  std::ofstream output(trace_config_path, std::ios::out | std::ios::binary);\n  if (output.is_open()) {\n    output.write(config.data(), config.size());\n    output.flush();\n    output.close();\n  } else {\n    LOGE(\"Write trace_config.json failed!\");\n  }\n}\n\nstd::string TraceControllerImpl::GetStartupTracingConfig() {\n  if (trace_file_dir_.empty()) {\n    trace_file_dir_ = delegate_->GenerateTracingFileDir();\n    if (trace_file_dir_.empty()) {\n      return \"\";\n    }\n  }\n  const std::string trace_config_path = trace_file_dir_ + kStartupTracingFile;\n  std::ifstream input(trace_config_path, std::ios::in | std::ios::binary);\n\n  if (input.is_open()) {\n    std::string config((std::istreambuf_iterator<char>(input)),\n                       std::istreambuf_iterator<char>());\n    input.close();\n    return config;\n  } else {\n    LOGE(\"Read trace_config.json failed!\");\n    return \"\";\n  }\n}\n\nstd::string TraceControllerImpl::GetStartupTracingFilePath() {\n  return startup_tracing_file_;\n}\n\nbool TraceControllerImpl::IsTracingStarted() { return is_tracing_started_; }\n\n// private\nTraceControllerImpl::TracingSession& TraceControllerImpl::CreateNewSession(\n    const std::shared_ptr<TraceConfig> config) {\n  static int next_session_id = 0;\n  next_session_id++;\n  auto new_session = new TracingSession;\n  new_session->session_impl = ::perfetto::Tracing::NewTrace();\n  new_session->id = next_session_id;\n  new_session->config = nullptr;\n  new_session->started = false;\n  tracing_sessions_[next_session_id] =\n      std::unique_ptr<TracingSession>(new_session);\n  return *new_session;\n}\n\nstd::string TraceControllerImpl::GenerateTraceFilePath(\n    const std::string& file_dir) {\n  std::string file_path = file_dir;\n  if (file_path.back() != '/') {\n    file_path.append(\"/\");\n  }\n\n  thread_local std::thread::id thread_id = std::this_thread::get_id();\n  static std::hash<std::thread::id> hasher;\n  auto pthd_id = static_cast<unsigned int>(hasher(thread_id));\n\n  time_t now = time(NULL);\n  struct tm* tm = localtime(&now);\n  std::ostringstream file_name;\n  file_name << \"lynx-profile-trace-\" << pthd_id << \"-\" << tm->tm_year + 1900\n            << \"-\" << tm->tm_mon + 1 << \"-\" << tm->tm_mday << \"-\" << tm->tm_hour\n            << tm->tm_min << tm->tm_sec;\n\n  file_path.append(file_name.str());\n  return file_path;\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/trace_controller_impl.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_TRACE_CONTROLLER_IMPL_H_\n#define BASE_TRACE_NATIVE_TRACE_CONTROLLER_IMPL_H_\n\n#include <map>\n#include <memory>\n#include <mutex>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/thread.h\"\n#include \"base/trace/native/hook_systrace/hook_system_trace.h\"\n#include \"base/trace/native/trace_controller.h\"\n#include \"third_party/perfetto/sdk/perfetto.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass TraceControllerImpl : public TraceController {\n public:\n  struct TracingSession {\n    TracingSession() : id(-1), started(false), all_read(false){};\n    ~TracingSession() {\n      for (int fd : opened_fds) {\n        if (fd > 0) {\n          close(fd);\n        }\n      }\n    }\n    std::shared_ptr<TraceConfig> config;\n    int id;\n    std::unique_ptr<::perfetto::TracingSession> session_impl;\n    std::vector<int> opened_fds;\n    std::vector<std::function<void()>> complete_callbacks;\n    bool started;\n    std::vector<std::function<void(const std::vector<char>&)>> event_callbacks;\n    std::vector<char> raw_traces;\n    std::vector<char> unsent_traces;\n    bool all_read;\n    std::mutex read_mutex;\n    std::condition_variable read_cv;\n    std::chrono::high_resolution_clock::time_point read_trace_begin;\n    std::chrono::high_resolution_clock::time_point read_trace_end;\n  };\n\n  TraceControllerImpl();\n  ~TraceControllerImpl() override = default;\n\n  int StartTracing(const std::shared_ptr<TraceConfig>& config) override;\n  bool StopTracing(int session_id) override;\n\n  // trace plugin\n  void AddTracePlugin(TracePlugin* plugin) override;\n  bool DeleteTracePlugin(const std::string& plugin_name) override;\n\n  // register callback\n  void AddCompleteCallback(int session_id,\n                           const std::function<void()> callback) override;\n  void RemoveCompleteCallbacks(int session_id) override;\n\n  // startup functions\n  void StartStartupTracingIfNeeded() override;\n  void SetStartupTracingConfig(std::string config) override;\n  std::string GetStartupTracingConfig() override;\n  std::string GetStartupTracingFilePath() override;\n  bool IsTracingStarted() override;\n\n private:\n  TracingSession& CreateNewSession(const std::shared_ptr<TraceConfig> config);\n\n  std::string GenerateTraceFilePath(const std::string& file_dir);\n\n  std::map<int, std::unique_ptr<TracingSession>> tracing_sessions_;\n  std::mutex mutex_;\n  std::map<std::string, TracePlugin*> trace_plugins_;\n  std::unique_ptr<HookSystemTrace> hook_systrace_;\n\n  const std::string kStartupTracingFile = \"/trace-config.json\";\n  std::string trace_file_dir_;\n  std::string startup_tracing_file_;\n  bool is_tracing_started_ = false;\n};\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_TRACE_CONTROLLER_IMPL_H_\n"
  },
  {
    "path": "base/trace/native/trace_controller_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/trace_controller.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass TraceControllerTest : public ::testing::Test {\n protected:\n  TraceControllerTest() = default;\n  ~TraceControllerTest() override = default;\n\n  void SetUp() override {}\n\n  void TearDown() override {}\n};\n\nTEST_F(TraceControllerTest, TraceControllerTotalTest) {\n  auto controller = TraceController::Instance();\n  static int const kDefaultBufferSize = 40960;\n  int bufferSize = kDefaultBufferSize;\n  std::string file_path = \"\";\n  auto trace_config = std::make_shared<lynx::trace::TraceConfig>();\n  trace_config->buffer_size = bufferSize;\n  trace_config->file_path = file_path;\n  trace_config->included_categories = {\"*\"};\n  trace_config->excluded_categories = {\"*\"};\n  auto session_id = controller->StartTracing(trace_config);\n  ASSERT_TRUE(session_id != -1);\n\n  controller->AddCompleteCallback(session_id,\n                                  []() { printf(\"AddCompleteCallback\\n\"); });\n\n  auto result = controller->StopTracing(session_id);\n  ASSERT_TRUE(result);\n  controller->RemoveCompleteCallbacks(session_id);\n  printf(\"TraceControllerTest finish!\\n\");\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/trace_defines.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_TRACE_DEFINES_H_\n#define BASE_TRACE_NATIVE_TRACE_DEFINES_H_\n\nstatic constexpr const char* const LYNX_TRACE_CATEGORY = \"lynx\";\n\n#endif  // BASE_TRACE_NATIVE_TRACE_DEFINES_H_\n"
  },
  {
    "path": "base/trace/native/trace_event.h",
    "content": "// Copyright (C) 2017 The Android Open Source Project\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_TRACE_EVENT_H_\n#define BASE_TRACE_NATIVE_TRACE_EVENT_H_\n\n#include <utility>\n\n// ================\n// Quickstart guide\n// ================\n//\n//   To add track events to your application, you can directly record\n//   events with the TRACE_EVENT macros:\n//\n//       #include \"trace_event.h\"\n//\n//       int main() {\n//\n//         // A basic track event with just a name.\n//         TRACE_EVENT(\"category\", \"MyEvent\");\n//\n//         // A track event with (up to two) debug annotations.\n//         TRACE_EVENT(\"category\", \"MyEvent\", \"parameter\", 42);\n//\n//         // A track event with a strongly typed parameter.\n//         TRACE_EVENT(\"category\", \"MyEvent\", [](perfetto::EventContext ctx) {\n//           ctx.event()->set_foo(42);\n//           ctx.event()->set_bar(.5f);\n//         });\n//\n//         // link two (or more) events (slices or instants), to mark them as\n//         // related.\n//         uint64_t flow_id = TRACE_FLOW_ID();\n//         TRACE_EVENT(\"category\", \"MyEvent\",\n//                             [&](lynx::perfetto::EventContext ctx) {\n//                               ctx.event()->add_flow_ids(flow_id);\n//                             });\n//         TRACE_EVENT(\"category\", \"OtherEvent\",\n//                             [&](lynx::perfetto::EventContext ctx) {\n//                               ctx.event()->add_flow_ids(flow_id);\n//                             });\n//         TRACE_EVENT_INSTANT(\"category\", \"OtherEventInstant\",\n//                             [&](lynx::perfetto::EventContext ctx) {\n//                               ctx.event()->add_flow_ids(flow_id);\n//                             });\n//\n//         // A basic track event instant with just a name.\n//         TRACE_EVENT_INSTANT(\"category\", \"MyEvent\");\n//\n//         // A track event instant with (up to two) debug annotations.\n//         TRACE_EVENT_INSTANT(\"category\", \"MyEvent\", \"parameter\", 42);\n//\n//         // A track event instant with a strongly typed parameter.\n//         TRACE_EVENT_INSTANT(\"category\", \"MyEvent\", [](perfetto::EventContext\n//         ctx) {\n//           ctx.event()->set_foo(42);\n//           ctx.event()->set_bar(.5f);\n//         });\n//\n//         // A basic trace counter.\n//         TRACE_COUNTER(\"category\",\n//         lynx::perfetto::CounterTrack(\"counter_tracker\"), 4);\n//\n//       }\n//\n//  Note that track events must be nested consistently, i.e., the following is\n//  not allowed:\n//\n//    TRACE_EVENT_BEGIN(\"a\", \"bar\", ...);\n//    TRACE_EVENT_BEGIN(\"b\", \"foo\", ...);\n//    TRACE_EVENT_END(\"a\");  // \"foo\" must be closed before \"bar\".\n//    TRACE_EVENT_END(\"b\");\n//\n\n#if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n// The DecayStringType method is used to avoid unnecessary instantiations of\n// templates on string constants of different sizes, like char[10] etc.\ntemplate <typename T>\n[[maybe_unused]] static T&& DecayStringType(T&& t) {\n  return std::forward<T>(t);\n}\n\n[[maybe_unused]] static inline const char* DecayStringType(const char* t) {\n  return t;\n}\n#endif\n\n#if ENABLE_TRACE_PERFETTO  // ENABLE_TRACE_PERFETTO\n\n#include \"base/trace/native/trace_event_utils_perfetto.h\"\n\nusing TraceEvent = lynx::perfetto::TrackEvent;\n\n// tools\n#define INTERNAL_TRACE_EVENT_UID3(a, b) trace_event_uid_##a##b\n#define INTERNAL_TRACE_EVENT_UID2(a, b) INTERNAL_TRACE_EVENT_UID3(a, b)\n#define INTERNAL_TRACE_EVENT_UID(name) INTERNAL_TRACE_EVENT_UID2(name, __LINE__)\n\n#define TRACE_EVENT(category, name, ...)                                      \\\n  struct INTERNAL_TRACE_EVENT_UID(ScopedEvent) {                              \\\n    struct EventFinalizer {                                                   \\\n      /* The parameter is an implementation detail. It allows the          */ \\\n      /* anonymous struct to use aggregate initialization to invoke the    */ \\\n      /* lambda (which emits the BEGIN event and returns an integer)       */ \\\n      /* with the proper reference capture for any                         */ \\\n      /* TrackEventArgumentFunction in |__VA_ARGS__|. This is required so  */ \\\n      /* that the scoped event is exactly ONE line and can't escape the    */ \\\n      /* scope if used in a single line if statement.                      */ \\\n      EventFinalizer(...) {}                                                  \\\n      ~EventFinalizer() { TRACE_EVENT_END(category); }                        \\\n    } finalizer;                                                              \\\n  } INTERNAL_TRACE_EVENT_UID(scoped_event) {                                  \\\n    [&]() {                                                                   \\\n      TRACE_EVENT_BEGIN(category, name, ##__VA_ARGS__);                       \\\n      return 0;                                                               \\\n    }()                                                                       \\\n  }\n\n#define TRACE_EVENT_BEGIN(category, name, ...) \\\n  lynx::trace::TraceEventBegin(category, DecayStringType(name), ##__VA_ARGS__)\n#define TRACE_EVENT_END(category, ...) \\\n  lynx::trace::TraceEventEnd(category, ##__VA_ARGS__)\n#define TRACE_EVENT_INSTANT(category, name, ...) \\\n  lynx::trace::TraceEventInstant(category, DecayStringType(name), ##__VA_ARGS__)\n#define TRACE_EVENT_CATEGORY_ENABLED(category) \\\n  lynx::trace::TraceEventCategoryEnabled(category)\n#define TRACE_COUNTER(category, track, ...)                                \\\n  lynx::trace::TraceCounter(category, lynx::perfetto::CounterTrack(track), \\\n                            ##__VA_ARGS__)\n#define TRACE_FLOW_ID() lynx::trace::GetFlowId()\n#define TRACE_TIME_NS() lynx::trace::GetTraceTimeNs()\n#elif ENABLE_TRACE_SYSTRACE  // ENABLE_TRACE_SYSTRACE\n\n#include \"base/trace/native/trace_event_utils_systrace.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass ScopedTracer {\n public:\n  template <typename EventNameType>\n  inline ScopedTracer(const EventNameType& name) {\n    lynx::trace::TraceEventBegin(name);\n  }\n\n  inline ~ScopedTracer() { lynx::trace::TraceEventEnd(); }\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#define INTERNAL_TRACE_EVENT_UID3(a, b) trace_event_uid_##a##b\n#define INTERNAL_TRACE_EVENT_UID2(a, b) INTERNAL_TRACE_EVENT_UID3(a, b)\n#define INTERNAL_TRACE_EVENT_UID(name) INTERNAL_TRACE_EVENT_UID2(name, __LINE__)\n\n#define TRACE_EVENT(category, name, ...)                     \\\n  lynx::base::ScopedTracer INTERNAL_TRACE_EVENT_UID(tracer)( \\\n      DecayStringType(name));\n\n#define TRACE_EVENT_BEGIN(category, name, ...) \\\n  lynx::trace::TraceEventBegin(DecayStringType(name))\n\n#define TRACE_EVENT_END(category, ...) lynx::trace::TraceEventEnd()\n\n#define TRACE_EVENT_INSTANT(category, name, ...)\n#define TRACE_EVENT_CATEGORY_ENABLED(category) true\n#define TRACE_COUNTER(category, track, ...)\n#define TRACE_FLOW_ID() 0\n#define TRACE_TIME_NS() 0\n#else  // DISABLE_TRACE\n\n#define TRACE_EVENT_BEGIN(category, name, ...)\n#define TRACE_EVENT_END(category, ...)\n\n#define TRACE_EVENT(category, name, ...)\n#define TRACE_EVENT_INSTANT(category, name, ...)\n#define TRACE_EVENT_CATEGORY_ENABLED(category)\n#define TRACE_COUNTER(category, track, ...)\n#define TRACE_FLOW_ID() 0\n#define TRACE_TIME_NS() 0\n#endif\n\n#if defined(__GNUC__) || defined(__clang__)\n#define CURRENT_FUNCTION __PRETTY_FUNCTION__\n#elif defined(_MSC_VER)\n#define CURRENT_FUNCTION __FUNCSIG__\n#else\n#define CURRENT_FUNCTION \"(unknown)\"\n#endif\n\n#define TRACE_EVENT_FUNC_NAME(category, ...) \\\n  TRACE_EVENT(category, CURRENT_FUNCTION, ##__VA_ARGS__)\n\n#endif  // BASE_TRACE_NATIVE_TRACE_EVENT_H_\n"
  },
  {
    "path": "base/trace/native/trace_event_export_symbol.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/trace_event_export_symbol.h\"\n\n#include <cstdint>\n\n#include \"base/trace/native/trace_event.h\"\n\nextern \"C\" {\n\nstatic const int64_t kTraceEventSync = -1;\nstatic const int64_t kTraceEventInstant = -2;  // not support now.\nvoid TraceEventBeginEx(const char *category, const char *event_name,\n                       int64_t trace_id, const char *arg1_name,\n                       const char *arg1_val, const char *arg2_name,\n                       const char *arg2_val) {\n  if (trace_id == kTraceEventSync) {\n    TRACE_EVENT_BEGIN(category, nullptr, [=](lynx::perfetto::EventContext ctx) {\n      auto *event = ctx.event();\n      event->set_name(event_name);\n      if (arg1_name && arg1_val) {\n        event->add_debug_annotations(arg1_name, arg1_val);\n      }\n      if (arg2_name && arg2_val) {\n        event->add_debug_annotations(arg2_name, arg2_val);\n      }\n    });\n\n  } else if (trace_id == kTraceEventInstant) {\n    TRACE_EVENT_INSTANT(category, nullptr,\n                        [=](lynx::perfetto::EventContext ctx) {\n                          auto *event = ctx.event();\n                          event->set_name(event_name);\n                          if (arg1_name && arg1_val) {\n                            event->add_debug_annotations(arg1_name, arg1_val);\n                          }\n                          if (arg2_name && arg2_val) {\n                            event->add_debug_annotations(arg2_name, arg2_val);\n                          }\n                        });\n  }\n}\n\nvoid TraceEventEndEx(const char *category, const char *event_name,\n                     int64_t trace_id) {\n  if (trace_id < 0) {  // sync\n    TRACE_EVENT_END(category, [=](lynx::perfetto::EventContext ctx) {\n      ctx.event()->set_name(event_name);\n    });\n  }\n}\n\nvoid TraceCounterEx(const char *category, const char *name, uint64_t counter,\n                    bool incremental) {\n  TRACE_COUNTER(\n      category,\n      lynx::perfetto::CounterTrack(name).set_category(category).set_incremental(\n          incremental),\n      counter);\n}\n}\n"
  },
  {
    "path": "base/trace/native/trace_event_export_symbol.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_TRACE_EVENT_EXPORT_SYMBOL_H_\n#define BASE_TRACE_NATIVE_TRACE_EVENT_EXPORT_SYMBOL_H_\n\n#include <cstdint>\n\n#include \"base/trace/native/trace_export.h\"\n\nextern \"C\" {\n\nTRACE_EXPORT void TraceEventBeginEx(const char *category,\n                                    const char *event_name, int64_t trace_id,\n                                    const char *arg1_name, const char *arg1_val,\n                                    const char *arg2_name,\n                                    const char *arg2_val);\n\nTRACE_EXPORT void TraceEventEndEx(const char *category, const char *event_name,\n                                  int64_t trace_id);\n\nTRACE_EXPORT void TraceCounterEx(const char *category, const char *name,\n                                 uint64_t counter, bool incremental);\n}\n#endif  // BASE_TRACE_NATIVE_TRACE_EVENT_EXPORT_SYMBOL_H_\n"
  },
  {
    "path": "base/trace/native/trace_event_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/trace_event.h\"\n\n#include <unistd.h>\n\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_controller.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace trace {\n\nclass TraceEventTest : public ::testing::Test {\n protected:\n  TraceEventTest() = default;\n  ~TraceEventTest() override = default;\n\n  void SetUp() override {}\n\n  void TearDown() override {}\n};\n\nTEST_F(TraceEventTest, TraceEventMacrosTest) {\n  auto controller = TraceController::Instance();\n  auto trace_config = std::make_shared<TraceConfig>();\n  std::string file_path = \"\";\n  trace_config->file_path = file_path;\n  auto session_id = controller->StartTracing(trace_config);\n  ASSERT_TRUE(session_id != -1);\n\n  // normal trace event\n  TRACE_EVENT(\"TraceTest\", \"TraceEventTest0\");\n\n  // trace event with a lambda\n  TRACE_EVENT(\"TraceTest\", nullptr, [](lynx::perfetto::EventContext ctx) {\n    auto* debug = ctx.event()->add_debug_annotations();\n    debug->set_name(\"TraceTest\");\n    debug->set_string_value(\"YES\");\n  });\n\n  // trace event with arbitrary number of debug annotations.\n  TRACE_EVENT(\"TraceTest\", \"TraceEventTest1\", \"testKey1\", \"value1\");\n  TRACE_EVENT(\"TraceTest\", \"TraceEventTest2\", \"testKey1\", \"value1\", \"testKey2\",\n              \"value2\");\n  TRACE_EVENT(\"TraceTest\", \"TraceEventTest3\", \"testKey1\", 1234);\n\n  // trace event with arbitrary number of debug annotations and a lambda.\n  TRACE_EVENT(\"TraceTest\", \"TraceEventTest4\", \"testKey1\", \"value1\",\n              [](lynx::perfetto::EventContext ctx) {\n                auto* debug = ctx.event()->add_debug_annotations();\n                debug->set_name(\"testKey2\");\n                debug->set_int_value(1234);\n              });\n\n  // Associate two trace events.\n  uint64_t flow_id = TRACE_FLOW_ID();\n  TRACE_EVENT(\"TraceTest\", \"TraceEventTest5-0\",\n              [&](lynx::perfetto::EventContext ctx) {\n                ctx.event()->add_flow_ids(flow_id);\n                ctx.event()->add_debug_annotations(\"startTimestamp\", \"1234\");\n              });\n  TRACE_EVENT(\"TraceTest\", \"TraceEventTest5-1\",\n              [&](lynx::perfetto::EventContext ctx) {\n                ctx.event()->add_flow_ids(flow_id);\n                ctx.event()->add_debug_annotations(\"startTimestamp\", \"2345\");\n              });\n  TRACE_EVENT_INSTANT(\"TraceTest\", \"TraceEventInstantTest0\",\n                      [&](lynx::perfetto::EventContext ctx) {\n                        ctx.event()->add_flow_ids(flow_id);\n                      });\n\n  // normal trace instant.\n  TRACE_EVENT_INSTANT(\"TraceTest\", \"TraceEventInstantTest0\");\n\n  // trace instant with a lambda.\n  TRACE_EVENT_INSTANT(\"TraceTest\", \"TraceEventInstantTest1\",\n                      [](lynx::perfetto::EventContext ctx) {\n                        auto* debug = ctx.event()->add_debug_annotations();\n                        debug->set_name(\"testKey2\");\n                        debug->set_int_value(1234);\n                      });\n\n  // trace instant with arbitrary number of debug annotations.\n  TRACE_EVENT_INSTANT(\"TraceTest\", \"TraceEventInstantTest2\", \"testKey1\",\n                      \"value1\");\n\n  // trace instant with arbitrary number of debug annotations and a lambda.\n  TRACE_EVENT_INSTANT(\"TraceTest\", \"TraceEventInstantTest3\", \"testKey1\",\n                      \"value1\", [](lynx::perfetto::EventContext ctx) {\n                        auto* debug = ctx.event()->add_debug_annotations();\n                        debug->set_name(\"testKey2\");\n                        debug->set_int_value(1234);\n                      });\n  // trace counter\n  TRACE_COUNTER(\"TraceTest\", lynx::perfetto::CounterTrack(\"counter_tracker\"),\n                4);\n\n  auto result = controller->StopTracing(session_id);\n  ASSERT_TRUE(result);\n  printf(\"TraceEventTest finish!\\n\");\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/trace_event_utils_perfetto.h",
    "content": "// Copyright (C) 2017 The Android Open Source Project\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_TRACE_EVENT_UTILS_PERFETTO_H_\n#define BASE_TRACE_NATIVE_TRACE_EVENT_UTILS_PERFETTO_H_\n\n#include <cstring>\n#include <functional>\n#include <memory>\n#include <string>\n#include <type_traits>\n#include <utility>\n#include <vector>\n\n#include \"base/trace/native/internal_trace_category.h\"\n#include \"base/trace/native/trace_export.h\"\n#include \"base/trace/native/track_event_wrapper.h\"\n\nnamespace lynx {\nnamespace trace {\n\nenum class TraceEventType : int32_t {\n  TYPE_UNSPECIFIED = 0,\n  TYPE_SLICE_BEGIN = 1,\n  TYPE_SLICE_END = 2,\n  TYPE_INSTANT = 3,\n  TYPE_COUNTER = 4,\n};\n\nusing argsVecPair =\n    std::pair<std::vector<const char*>, std::vector<std::string>>;\nusing FuncType = std::function<void(lynx::perfetto::EventContext)>;\n\nTRACE_EXPORT uint64_t GetFlowId();\nTRACE_EXPORT uint64_t GetTraceTimeNs();\nTRACE_EXPORT void TraceEventImplementation(\n    const char* category_name, const char* name, TraceEventType phase,\n    const lynx::perfetto::Track* track_id, const uint64_t& timestamp,\n    const FuncType& callback);\nTRACE_EXPORT void TraceEventImplementation(\n    const char* category_name, const std::string& name, TraceEventType phase,\n    const lynx::perfetto::Track* track_id, const uint64_t& timestamp,\n    const FuncType& callback);\nTRACE_EXPORT void TraceEventImplementation(\n    const char* category_name,\n    const lynx::perfetto::CounterTrack& counter_track, TraceEventType phase,\n    const uint64_t& timestamp, const uint64_t& counter,\n    const FuncType& callback);\nTRACE_EXPORT bool TraceEventCategoryEnabled(const char* category);\nTRACE_EXPORT void TraceRuntimeProfile(const std::string& runtime_profile,\n                                      const uint64_t track_id,\n                                      const int32_t profile_id);\n\n// remove_cvref is an implementation of std::remove_cvref from\n// C++20.\n//\n// Specification:\n// https://en.cppreference.com/w/cpp/types/remove_cvref\ntemplate <class T>\nstruct remove_cvref {\n  using type =\n      typename std::remove_cv<typename std::remove_reference<T>::type>::type;\n};\ntemplate <class T>\nusing remove_cvref_t = typename remove_cvref<T>::type;\n\ntemplate <typename T, bool = std::is_enum<T>::value>\nstruct safe_underlying_type {\n  using type = typename std::underlying_type<T>::type;\n};\n\ntemplate <typename T>\nstruct safe_underlying_type<T, false> {\n  using type = T;\n};\n\n// TraceFormatTraits implementations for primitive types.\ntemplate <typename T, class = void>\nstruct TraceFormatTraits;\n\n// Specialisation for signed integer types (note: it excludes enums, which have\n// their own explicit specialisation).\ntemplate <typename T>\nstruct TraceFormatTraits<\n    T, typename std::enable_if<std::is_integral<T>::value &&\n                               !std::is_same<T, bool>::value &&\n                               std::is_signed<T>::value>::type> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    T value) {\n    debug->set_int_value(value);\n  }\n};\n\n// Specialisation for unsigned integer types (note: it excludes enums, which\n// have their own explicit specialisation).\ntemplate <typename T>\nstruct TraceFormatTraits<\n    T, typename std::enable_if<std::is_integral<T>::value &&\n                               !std::is_same<T, bool>::value &&\n                               std::is_unsigned<T>::value>::type> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    T value) {\n    debug->set_uint_value(value);\n  }\n};\n\n// Specialisation for bools.\ntemplate <>\nstruct TraceFormatTraits<bool> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    bool value) {\n    debug->set_bool_value(value);\n  }\n};\n\n// Specialisation for floating point values.\ntemplate <typename T>\nstruct TraceFormatTraits<\n    T, typename std::enable_if<std::is_floating_point<T>::value>::type> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    T value) {\n    debug->set_double_value(static_cast<double>(value));\n  }\n};\n\n// Specialisation for signed enums.\ntemplate <typename T>\nstruct TraceFormatTraits<\n    T,\n    typename std::enable_if<\n        std::is_enum<T>::value &&\n        std::is_signed<typename safe_underlying_type<T>::type>::value>::type> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    T value) {\n    debug->set_int_value(static_cast<int64_t>(value));\n  }\n};\n\n// Specialisation for unsigned enums.\ntemplate <typename T>\nstruct TraceFormatTraits<\n    T, typename std::enable_if<std::is_enum<T>::value &&\n                               std::is_unsigned<typename safe_underlying_type<\n                                   T>::type>::value>::type> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    T value) {\n    debug->set_uint_value(static_cast<uint64_t>(value));\n  }\n};\n\n// Specialisations for C-style strings.\ntemplate <>\nstruct TraceFormatTraits<const char*> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    const char* value) {\n    if (value == nullptr) {\n      debug->set_string_value(\"nullptr\");\n    } else {\n      debug->set_string_value(value, strlen(value));\n    }\n  }\n};\n\ntemplate <size_t N>\nstruct TraceFormatTraits<char[N]> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    const char value[N]) {\n    debug->set_string_value(value, N);\n  }\n};\n\n// Specialisation for C++ strings.\ntemplate <>\nstruct TraceFormatTraits<std::string> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    const std::string& value) {\n    debug->set_string_value(value);\n  }\n};\n\n// Specialisation for (const) void*, which writes the pointer value.\ntemplate <>\nstruct TraceFormatTraits<void*> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    void* value) {\n    debug->set_pointer_value(reinterpret_cast<uint64_t>(value));\n  }\n};\n\ntemplate <>\nstruct TraceFormatTraits<const void*> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    const void* value) {\n    debug->set_pointer_value(reinterpret_cast<uint64_t>(value));\n  }\n};\n\n// Specialisation for std::unique_ptr<>, which writes either nullptr or the\n// object it points to.\ntemplate <typename T>\nstruct TraceFormatTraits<std::unique_ptr<T>> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    const std::unique_ptr<T>& value) {\n    debug->set_pointer_value(reinterpret_cast<uint64_t>(value.get()));\n  }\n};\n\n// Specialisation for raw pointer, which writes either nullptr or the object it\n// points to.\ntemplate <typename T>\nstruct TraceFormatTraits<T*> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    T* value) {\n    if (!value) {\n      debug->set_pointer_value(0);\n      return;\n    }\n    debug->set_pointer_value(reinterpret_cast<uint64_t>(value));\n  }\n};\n\n// Specialisation for nullptr.\ntemplate <>\nstruct TraceFormatTraits<std::nullptr_t> {\n  inline static void WriteIntoTrace(lynx::perfetto::LynxDebugAnnotation* debug,\n                                    std::nullptr_t) {\n    debug->set_pointer_value(0);\n  }\n};\n\ntemplate <typename ValueType>\ninline void WriteTraceEventArgs(lynx::perfetto::EventContext ctx,\n                                const char* arg_name, ValueType&& arg_value) {\n  auto* debug = ctx.event()->add_debug_annotations();\n  debug->set_name(arg_name);\n  TraceFormatTraits<trace::remove_cvref_t<ValueType>>::WriteIntoTrace(\n      std::move(debug), std::forward<ValueType>(arg_value));\n}\ninline void WriteTraceEventArgs(lynx::perfetto::EventContext ctx,\n                                FuncType callback) {\n  callback(std::move(ctx));\n}\ninline void WriteTraceEventArgs(lynx::perfetto::EventContext ctx) {}\ntemplate <typename ValueType, typename... Arguments>\ninline void WriteTraceEventArgs(lynx::perfetto::EventContext ctx,\n                                const char* arg_name, ValueType&& arg_value,\n                                Arguments... args) {\n  auto* debug = ctx.event()->add_debug_annotations();\n  debug->set_name(arg_name);\n  TraceFormatTraits<trace::remove_cvref_t<ValueType>>::WriteIntoTrace(\n      std::move(debug), std::forward<ValueType>(arg_value));\n  WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n}\n\ntemplate <typename EventNameType>\ninline void TraceEventBegin(const char* category, const EventNameType& name) {\n  TraceEventImplementation(category, name, TraceEventType::TYPE_SLICE_BEGIN,\n                           nullptr, 0, nullptr);\n}\n\ntemplate <typename EventNameType>\ninline void TraceEventBegin(const char* category, const EventNameType& name,\n                            FuncType callback) {\n  TraceEventImplementation(category, name, TraceEventType::TYPE_SLICE_BEGIN,\n                           nullptr, 0, [&](lynx::perfetto::EventContext ctx) {\n                             WriteTraceEventArgs(std::move(ctx), callback);\n                           });\n}\n\ntemplate <typename EventNameType, typename... Arguments>\ninline void TraceEventBegin(const char* category, const EventNameType& name,\n                            const char* key, Arguments&&... args) {\n  TraceEventImplementation(category, name, TraceEventType::TYPE_SLICE_BEGIN,\n                           nullptr, 0, [&](lynx::perfetto::EventContext ctx) {\n                             WriteTraceEventArgs(\n                                 std::move(ctx), key,\n                                 std::forward<Arguments>(args)...);\n                           });\n}\ntemplate <typename EventNameType, typename... Arguments>\ninline void TraceEventBegin(const char* category, const EventNameType& name,\n                            const lynx::perfetto::Track& track_id,\n                            uint64_t timestamp, Arguments&&... args) {\n  TraceEventImplementation(\n      category, name, TraceEventType::TYPE_SLICE_BEGIN, &track_id, timestamp,\n      [&](lynx::perfetto::EventContext ctx) {\n        WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n      });\n}\ntemplate <typename EventNameType, typename... Arguments>\ninline void TraceEventBegin(const char* category, const EventNameType& name,\n                            uint64_t timestamp, Arguments&&... args) {\n  TraceEventImplementation(\n      category, name, TraceEventType::TYPE_SLICE_BEGIN, nullptr, timestamp,\n      [&](lynx::perfetto::EventContext ctx) {\n        WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n      });\n}\ntemplate <typename EventNameType, typename... Arguments>\ninline void TraceEventBegin(const char* category, const EventNameType& name,\n                            const lynx::perfetto::Track& track_id,\n                            Arguments&&... args) {\n  TraceEventImplementation(\n      category, name, TraceEventType::TYPE_SLICE_BEGIN, &track_id, 0,\n      [&](lynx::perfetto::EventContext ctx) {\n        WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n      });\n}\n\ninline void TraceEventEnd(const char* category) {\n  TraceEventImplementation(category, nullptr, TraceEventType::TYPE_SLICE_END,\n                           nullptr, 0, nullptr);\n}\ninline void TraceEventEnd(const char* category, FuncType callback) {\n  TraceEventImplementation(category, nullptr, TraceEventType::TYPE_SLICE_END,\n                           nullptr, 0, [&](lynx::perfetto::EventContext ctx) {\n                             WriteTraceEventArgs(std::move(ctx), callback);\n                           });\n}\ntemplate <typename... Arguments>\ninline void TraceEventEnd(const char* category, const char* key,\n                          Arguments&&... args) {\n  TraceEventImplementation(category, nullptr, TraceEventType::TYPE_SLICE_END,\n                           nullptr, 0, [&](lynx::perfetto::EventContext ctx) {\n                             WriteTraceEventArgs(\n                                 std::move(ctx), key,\n                                 std::forward<Arguments>(args)...);\n                           });\n}\ntemplate <typename... Arguments>\ninline void TraceEventEnd(const char* category,\n                          const lynx::perfetto::Track& track_id,\n                          uint64_t timestamp, Arguments&&... args) {\n  TraceEventImplementation(\n      category, nullptr, TraceEventType::TYPE_SLICE_END, &track_id, timestamp,\n      [&](lynx::perfetto::EventContext ctx) {\n        WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n      });\n}\ntemplate <typename... Arguments>\ninline void TraceEventEnd(const char* category, uint64_t timestamp,\n                          Arguments&&... args) {\n  TraceEventImplementation(\n      category, nullptr, TraceEventType::TYPE_SLICE_END, nullptr, timestamp,\n      [&](lynx::perfetto::EventContext ctx) {\n        WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n      });\n}\ntemplate <typename... Arguments>\ninline void TraceEventEnd(const char* category,\n                          const lynx::perfetto::Track& track_id,\n                          Arguments&&... args) {\n  TraceEventImplementation(\n      category, nullptr, TraceEventType::TYPE_SLICE_END, &track_id, 0,\n      [&](lynx::perfetto::EventContext ctx) {\n        WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n      });\n}\n\ntemplate <typename EventNameType>\ninline void TraceEventInstant(const char* category, const EventNameType& name) {\n  TraceEventImplementation(category, name, TraceEventType::TYPE_INSTANT,\n                           nullptr, 0, nullptr);\n}\ntemplate <typename EventNameType>\ninline void TraceEventInstant(const char* category, const EventNameType& name,\n                              FuncType callback) {\n  TraceEventImplementation(category, name, TraceEventType::TYPE_INSTANT,\n                           nullptr, 0, [&](lynx::perfetto::EventContext ctx) {\n                             WriteTraceEventArgs(std::move(ctx), callback);\n                           });\n}\ntemplate <typename EventNameType, typename... Arguments>\ninline void TraceEventInstant(const char* category, const EventNameType& name,\n                              const char* key, Arguments&&... args) {\n  TraceEventImplementation(category, name, TraceEventType::TYPE_INSTANT,\n                           nullptr, 0, [&](lynx::perfetto::EventContext ctx) {\n                             WriteTraceEventArgs(\n                                 std::move(ctx), key,\n                                 std::forward<Arguments>(args)...);\n                           });\n}\ntemplate <typename EventNameType, typename... Arguments>\ninline void TraceEventInstant(const char* category, const EventNameType& name,\n                              const lynx::perfetto::Track& track_id,\n                              uint64_t timestamp, Arguments&&... args) {\n  TraceEventImplementation(\n      category, name, TraceEventType::TYPE_INSTANT, &track_id, timestamp,\n      [&](lynx::perfetto::EventContext ctx) {\n        WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n      });\n}\ntemplate <typename EventNameType, typename... Arguments>\ninline void TraceEventInstant(const char* category, const EventNameType& name,\n                              uint64_t timestamp, Arguments&&... args) {\n  TraceEventImplementation(\n      category, name, TraceEventType::TYPE_INSTANT, nullptr, timestamp,\n      [&](lynx::perfetto::EventContext ctx) {\n        WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n      });\n}\ntemplate <typename EventNameType, typename... Arguments>\ninline void TraceEventInstant(const char* category, const EventNameType& name,\n                              const lynx::perfetto::Track& track_id,\n                              Arguments&&... args) {\n  TraceEventImplementation(\n      category, name, TraceEventType::TYPE_INSTANT, &track_id, 0,\n      [&](lynx::perfetto::EventContext ctx) {\n        WriteTraceEventArgs(std::move(ctx), std::forward<Arguments>(args)...);\n      });\n}\n\ninline void TraceCounter(const char* category,\n                         const lynx::perfetto::CounterTrack& track,\n                         uint64_t timestamp, uint64_t counter) {\n  TraceEventImplementation(category, track, TraceEventType::TYPE_COUNTER,\n                           timestamp, counter, nullptr);\n}\ninline void TraceCounter(const char* category,\n                         const lynx::perfetto::CounterTrack& track,\n                         uint64_t counter) {\n  TraceEventImplementation(category, track, TraceEventType::TYPE_COUNTER, 0,\n                           counter, nullptr);\n}\n\ninline void TraceCounter(const char* category,\n                         const lynx::perfetto::CounterTrack& track,\n                         uint64_t timestamp, uint64_t counter,\n                         FuncType callback) {\n  TraceEventImplementation(category, track, TraceEventType::TYPE_COUNTER,\n                           timestamp, counter,\n                           [&](lynx::perfetto::EventContext ctx) {\n                             WriteTraceEventArgs(std::move(ctx), callback);\n                           });\n}\n\ninline void TraceCounter(const char* category,\n                         const lynx::perfetto::CounterTrack& track,\n                         uint64_t counter, FuncType callback) {\n  TraceEventImplementation(category, track, TraceEventType::TYPE_COUNTER, 0,\n                           counter, [&](lynx::perfetto::EventContext ctx) {\n                             WriteTraceEventArgs(std::move(ctx), callback);\n                           });\n}\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_TRACE_EVENT_UTILS_PERFETTO_H_\n"
  },
  {
    "path": "base/trace/native/trace_event_utils_perfetto_mock.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/trace_event_utils_perfetto.h\"\n\nnamespace lynx {\nnamespace trace {\nuint64_t GetFlowId() { return 0; }\nuint64_t GetTraceTimeNs() { return 0; }\nvoid TraceEventImplementation(const char* category_name, const char* name,\n                              TraceEventType phase,\n                              const lynx::perfetto::Track* track_id,\n                              const uint64_t& timestamp,\n                              const FuncType& callback) {}\nvoid TraceEventImplementation(const char* category_name,\n                              const std::string& name, TraceEventType phase,\n                              const lynx::perfetto::Track* track_id,\n                              const uint64_t& timestamp,\n                              const FuncType& callback) {}\nvoid TraceEventImplementation(const char* category_name,\n                              const lynx::perfetto::CounterTrack& counter_track,\n                              TraceEventType phase, const uint64_t& timestamp,\n                              const uint64_t& counter,\n                              const FuncType& callback) {}\nbool TraceEventCategoryEnabled(const char* category) { return false; }\nvoid TraceRuntimeProfile(const std::string& runtime_profile,\n                         const uint64_t track_id, const int32_t profile_id) {}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/trace_event_utils_systrace.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_TRACE_EVENT_UTILS_SYSTRACE_H_\n#define BASE_TRACE_NATIVE_TRACE_EVENT_UTILS_SYSTRACE_H_\n\n#include <dlfcn.h>\n\n#include <string>\n\n#include \"base/trace/native/internal_trace_category.h\"\n#include \"base/trace/native/trace_export.h\"\n#include \"base/trace/native/track_event_wrapper.h\"\n\nnamespace lynx {\nnamespace trace {\n\nusing ATrace_beginSection_ptr = void *(*)(const char *section_name);\nusing ATrace_endSection_ptr = void *(*)(void);\nusing ATrace_beginAsyncSection_ptr = void *(*)(const char *section_name,\n                                               int32_t cookie);\nusing ATrace_endAsyncSection_ptr = void *(*)(const char *section_name,\n                                             int32_t cookie);\n\nvoid InitSystraceBeginSection(ATrace_beginSection_ptr atrace_beginsection);\nvoid InitSystraceEndSection(ATrace_endSection_ptr atrace_endsection);\nvoid InitSystraceBeginAsynSection(\n    ATrace_beginAsyncSection_ptr atrace_beginasyncsection);\nvoid InitSystraceEndAsynSection(\n    ATrace_endAsyncSection_ptr atrace_endasyncsection);\n\nTRACE_EXPORT void TraceEventBegin(const char *name);\nTRACE_EXPORT void TraceEventBegin(const std::string &name);\nTRACE_EXPORT void TraceEventBegin(const char *name, uint64_t cookie);\nTRACE_EXPORT void TraceEventBegin(const std::string &name, uint64_t cookie);\nTRACE_EXPORT void TraceEventEnd();\nTRACE_EXPORT void TraceEventEnd(const char *name, uint64_t cookie);\n\ntemplate <typename EventNameType, typename TrackType, typename... Arguments,\n          typename TrackTypeCheck = typename std::enable_if_t<\n              std::is_same_v<TrackType, lynx::perfetto::Track>>>\nTRACE_EXPORT void TraceEventBegin(const EventNameType &name, TrackType track_id,\n                                  Arguments &&...args) {\n  const char *event_name = name == nullptr ? \"\" : name;\n  TraceEventBegin(event_name, track_id);\n}\ntemplate <typename EventNameType, typename... Arguments>\nTRACE_EXPORT void TraceEventBegin(const EventNameType &name,\n                                  Arguments &&...args) {\n  const char *event_name = name == nullptr ? \"\" : name;\n  TraceEventBegin(event_name);\n}\n\ntemplate <typename TrackType, typename... Arguments,\n          typename TrackTypeCheck = typename std::enable_if_t<\n              std::is_same_v<TrackType, lynx::perfetto::Track>>>\nTRACE_EXPORT void TraceEventEnd(TrackType track_id, Arguments &&...args) {\n  TraceEventEnd(\"end\", track_id);\n}\ntemplate <typename... Arguments>\nTRACE_EXPORT void TraceEventEnd(Arguments &&...args) {\n  TraceEventEnd();\n}\n\n}  // namespace trace\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_TRACE_EVENT_UTILS_SYSTRACE_H_\n"
  },
  {
    "path": "base/trace/native/trace_event_utils_systrace_android.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string>\n\n#include \"base/trace/native/trace_event_utils_systrace.h\"\n\nnamespace lynx {\nnamespace trace {\nnamespace {\n\nstatic ATrace_beginSection_ptr ATrace_beginSection = nullptr;\nstatic ATrace_endSection_ptr ATrace_endSection = nullptr;\nstatic ATrace_beginAsyncSection_ptr ATrace_beginAsyncSection = nullptr;\nstatic ATrace_endAsyncSection_ptr ATrace_endAsyncSection = nullptr;\n\n}  // namespace\n\nvoid InitSystraceBeginSection(ATrace_beginSection_ptr atrace_beginsection) {\n  ATrace_beginSection = atrace_beginsection;\n}\nvoid InitSystraceEndSection(ATrace_endSection_ptr atrace_endsection) {\n  ATrace_endSection = atrace_endsection;\n}\nvoid InitSystraceBeginAsynSection(\n    ATrace_beginAsyncSection_ptr atrace_beginasyncsection) {\n  ATrace_beginAsyncSection = atrace_beginasyncsection;\n}\nvoid InitSystraceEndAsynSection(\n    ATrace_endAsyncSection_ptr atrace_endasyncsection) {\n  ATrace_endAsyncSection = atrace_endasyncsection;\n}\n\nvoid TraceEventBegin(const char* name) {\n  if (ATrace_beginSection) {\n    ATrace_beginSection(name);\n  }\n}\nvoid TraceEventBegin(const char* name, uint64_t cookie) {\n  if (ATrace_beginAsyncSection) {\n    ATrace_beginAsyncSection(name, int32_t(cookie));\n  }\n}\nvoid TraceEventBegin(const std::string& name) { TraceEventBegin(name.c_str()); }\nvoid TraceEventBegin(const std::string& name, uint64_t cookie) {\n  TraceEventBegin(name.c_str(), cookie);\n}\nvoid TraceEventEnd() {\n  if (ATrace_endSection) {\n    ATrace_endSection();\n  }\n}\nvoid TraceEventEnd(const char* name, uint64_t cookie) {\n  if (ATrace_endAsyncSection) {\n    ATrace_endAsyncSection(name, int32_t(cookie));\n  }\n}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/trace_event_utils_systrace_default.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string>\n\n#include \"base/trace/native/trace_event_utils_systrace.h\"\n\nnamespace lynx {\nnamespace trace {\n\nvoid InitSystraceBeginSection(ATrace_beginSection_ptr atrace_beginsection) {}\nvoid InitSystraceEndSection(ATrace_endSection_ptr atrace_endsection) {}\nvoid InitSystraceBeginAsynSection(\n    ATrace_beginAsyncSection_ptr atrace_beginasyncsection) {}\nvoid InitSystraceEndAsynSection(\n    ATrace_endAsyncSection_ptr atrace_endasyncsection) {}\n\nvoid TraceEventBegin(const char* name) {}\nvoid TraceEventBegin(const char* name, uint64_t cookie) {}\nvoid TraceEventBegin(const std::string& name) {}\nvoid TraceEventBegin(const std::string& name, uint64_t cookie) {}\nvoid TraceEventEnd() {}\nvoid TraceEventEnd(const char* name, uint64_t cookie) {}\n\n}  // namespace trace\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/trace_export.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_TRACE_EXPORT_H_\n#define BASE_TRACE_NATIVE_TRACE_EXPORT_H_\n\n#ifdef _WIN32\n#define TRACE_EXPORT __declspec(dllexport)\n#else\n#define TRACE_EXPORT __attribute__((visibility(\"default\")))\n#endif\n\n#endif  // BASE_TRACE_NATIVE_TRACE_EXPORT_H_\n"
  },
  {
    "path": "base/trace/native/track_event_wrapper.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/track_event_wrapper.h\"\n\n#include \"third_party/perfetto/sdk/perfetto.h\"\n\nnamespace lynx {\nnamespace perfetto {\n\nuint64_t ThreadTrack::Current() {\n  return ::perfetto::ThreadTrack::Current().uuid;\n}\n\nvoid LynxDebugAnnotation::set_name(const std::string& value) {\n  return debug_annotation_->set_name(value);\n}\nvoid LynxDebugAnnotation::set_bool_value(bool value) {\n  return debug_annotation_->set_bool_value(value);\n}\nvoid LynxDebugAnnotation::set_uint_value(uint64_t value) {\n  return debug_annotation_->set_uint_value(value);\n}\nvoid LynxDebugAnnotation::set_int_value(int64_t value) {\n  return debug_annotation_->set_int_value(value);\n}\nvoid LynxDebugAnnotation::set_double_value(double value) {\n  return debug_annotation_->set_double_value(value);\n}\nvoid LynxDebugAnnotation::set_string_value(const char* data, size_t size) {\n  return debug_annotation_->set_string_value(data, size);\n}\nvoid LynxDebugAnnotation::set_string_value(const std::string& value) {\n  return debug_annotation_->set_string_value(value);\n}\nvoid LynxDebugAnnotation::set_pointer_value(uint64_t value) {\n  return debug_annotation_->set_pointer_value(value);\n}\n\nvoid LynxDebugAnnotation::set_legacy_json_value(const std::string& value) {\n  return debug_annotation_->set_legacy_json_value(value);\n}\n\nvoid TrackEvent_LegacyEvent::set_phase(int32_t value) {\n  legacy_event_->set_phase(value);\n}\nvoid TrackEvent_LegacyEvent::set_unscoped_id(uint64_t value) {\n  legacy_event_->set_unscoped_id(value);\n}\n\nvoid TrackEvent_LegacyEvent::set_bind_id(uint64_t value) {\n  legacy_event_->set_bind_id(value);\n}\n\nvoid TrackEvent_LegacyEvent::set_flow_direction(FlowDirection value) {\n  legacy_event_->set_flow_direction(\n      static_cast<\n          ::perfetto::protos::pbzero::TrackEvent_LegacyEvent_FlowDirection>(\n          value));\n}\n\nvoid TrackEvent_LegacyEvent::set_global_id(uint64_t value) {\n  legacy_event_->set_global_id(value);\n}\n\nvoid TrackEvent_LegacyEvent::set_use_async_tts(bool value) {\n  legacy_event_->set_use_async_tts(value);\n}\n\nvoid TrackEvent_LegacyEvent::set_bind_to_enclosing(bool value) {\n  legacy_event_->set_bind_to_enclosing(value);\n}\n\nvoid TrackEvent::set_name(const std::string& value) {\n  ctx_->event()->set_name(value);\n}\nvoid TrackEvent::set_track_uuid(uint64_t value) {\n  ctx_->event()->set_track_uuid(value);\n}\nvoid TrackEvent::add_flow_ids(uint64_t value) {\n  ctx_->event()->add_flow_ids(value);\n}\nvoid TrackEvent::add_terminating_flow_ids(uint64_t value) {\n  ctx_->event()->add_terminating_flow_ids(value);\n}\nLynxDebugAnnotation* TrackEvent::add_debug_annotations() {\n  auto perfetto_debug_annotation = ctx_->event()->add_debug_annotations();\n  lynx_debug_annotation_ =\n      std::make_unique<LynxDebugAnnotation>(perfetto_debug_annotation);\n  return lynx_debug_annotation_.get();\n}\nvoid TrackEvent::add_debug_annotations(const std::string& name,\n                                       const std::string& value) {\n  auto* debug = ctx_->event()->add_debug_annotations();\n  debug->set_name(name);\n  debug->set_string_value(value);\n}\nvoid TrackEvent::add_debug_annotations(std::string&& name,\n                                       std::string&& value) {\n  auto* debug = ctx_->event()->add_debug_annotations();\n  debug->set_name(name);\n  debug->set_string_value(value);\n}\nvoid TrackEvent::set_timestamp_absolute_us(int64_t value) {\n  ctx_->event()->set_timestamp_absolute_us(value);\n}\n\nTrackEvent_LegacyEvent* TrackEvent::set_legacy_event() {\n  auto legacy_event = ctx_->event()->set_legacy_event();\n  legacy_event_ = std::make_unique<TrackEvent_LegacyEvent>(legacy_event);\n  return legacy_event_.get();\n}\n\n}  // namespace perfetto\n}  // namespace lynx\n"
  },
  {
    "path": "base/trace/native/track_event_wrapper.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef BASE_TRACE_NATIVE_TRACK_EVENT_WRAPPER_H_\n#define BASE_TRACE_NATIVE_TRACK_EVENT_WRAPPER_H_\n\n#include <memory>\n#include <string>\n\n#include \"base/trace/native/trace_export.h\"\n\nnamespace perfetto {\nnamespace protos {\nnamespace pbzero {\nclass DebugAnnotation;\nclass TrackEvent_LegacyEvent;\n}  // namespace pbzero\n}  // namespace protos\nclass EventContext;\nclass CounterTrack;\n}  // namespace perfetto\n\nnamespace lynx {\nnamespace perfetto {\nclass CounterTrack;\n}  // namespace perfetto\nnamespace trace {\nconstexpr ::perfetto::CounterTrack ConvertToPerfCounterTrack(\n    const lynx::perfetto::CounterTrack& counter_tarck);\n}  // namespace trace\n}  // namespace lynx\n\n/**\n * Why use the lynx::perfetto namespace here?\n * Currently, lynx uses a large number of classes such as\n * lynx::perfetto::EventContext internally, and we want to keep the way that we\n * used it before, so leave the namespace of the associated class unchanged\n * here.\n */\nnamespace lynx {\n\nnamespace perfetto {\n\n// At present, Track is not exposed to the public and is not recommended for\n// use.\nclass Track {\n public:\n  Track() = delete;\n  Track(uint64_t id) : id_(id){};\n  ~Track() = default;\n  uint64_t id() const { return id_; }\n\n private:\n  uint64_t id_;\n};\n\nclass TRACE_EXPORT ThreadTrack {\n public:\n  static uint64_t Current();\n};\n\nenum Unit : int32_t {\n  UNIT_UNSPECIFIED = 0,\n  UNIT_TIME_NS = 1,\n  UNIT_COUNT = 2,\n  UNIT_SIZE_BYTES = 3,\n};\n\nclass TRACE_EXPORT CounterTrack {\n public:\n  // |name| must be a string with static lifetime.\n  constexpr explicit CounterTrack(const char* name)\n      : name_(name), category_(nullptr) {}\n\n  // |unit_name| is a free-form description of the unit used by this counter. It\n  // must have static lifetime.\n  constexpr CounterTrack(const char* name, const char* unit_name)\n      : name_(name), category_(nullptr), unit_name_(unit_name) {}\n\n  constexpr CounterTrack(const char* name, Unit unit)\n      : name_(name), category_(nullptr), unit_(unit) {}\n\n  constexpr CounterTrack(const char* name, const char* unit_name,\n                         bool is_global)\n      : name_(name),\n        category_(nullptr),\n        unit_name_(unit_name),\n        is_global_(is_global) {}\n\n  constexpr CounterTrack(const char* name, Unit unit, bool is_global)\n      : name_(name), category_(nullptr), unit_(unit), is_global_(is_global) {}\n\n  static constexpr CounterTrack Global(const char* name,\n                                       const char* unit_name) {\n    return CounterTrack(name, unit_name, true);\n  }\n\n  static constexpr CounterTrack Global(const char* name, Unit unit) {\n    return CounterTrack(name, unit, true);\n  }\n\n  static constexpr CounterTrack Global(const char* name) {\n    return Global(name, nullptr);\n  }\n\n  constexpr CounterTrack set_unit(Unit unit) const {\n    return CounterTrack(name_, category_, unit, unit_name_, unit_multiplier_,\n                        is_incremental_);\n  }\n\n  constexpr CounterTrack set_unit_name(const char* unit_name) const {\n    return CounterTrack(name_, category_, unit_, unit_name, unit_multiplier_,\n                        is_incremental_);\n  }\n\n  constexpr CounterTrack set_unit_multiplier(int64_t unit_multiplier) const {\n    return CounterTrack(name_, category_, unit_, unit_name_, unit_multiplier,\n                        is_incremental_);\n  }\n\n  constexpr CounterTrack set_category(const char* category) const {\n    return CounterTrack(name_, category, unit_, unit_name_, unit_multiplier_,\n                        is_incremental_);\n  }\n\n  constexpr CounterTrack set_incremental(bool is_incremental) const {\n    return CounterTrack(name_, category_, unit_, unit_name_, unit_multiplier_,\n                        is_incremental);\n  }\n\n private:\n  friend constexpr ::perfetto::CounterTrack\n  lynx::trace::ConvertToPerfCounterTrack(const CounterTrack& counter_tarck);\n  constexpr CounterTrack(const char* name, const char* category, Unit unit,\n                         const char* unit_name, int64_t unit_multiplier,\n                         bool is_incremental)\n      : name_(name),\n        category_(category),\n        unit_(unit),\n        unit_name_(unit_name),\n        unit_multiplier_(unit_multiplier),\n        is_incremental_(is_incremental) {}\n\n  const char* const name_;\n  const char* const category_;\n  Unit unit_ = UNIT_UNSPECIFIED;\n  const char* const unit_name_ = nullptr;\n  int64_t unit_multiplier_ = 1;\n  bool is_incremental_ = false;\n  bool is_global_ = false;\n};\n\nclass TRACE_EXPORT LynxDebugAnnotation {\n public:\n  LynxDebugAnnotation(\n      ::perfetto::protos::pbzero::DebugAnnotation* debug_annotation)\n      : debug_annotation_(debug_annotation) {}\n  ~LynxDebugAnnotation() = default;\n\n  void set_name(const std::string& value);\n  void set_bool_value(bool value);\n  void set_uint_value(uint64_t value);\n  void set_int_value(int64_t value);\n\n  void set_double_value(double value);\n  void set_string_value(const char* data, size_t size);\n  void set_string_value(const std::string& value);\n  void set_pointer_value(uint64_t value);\n\n  void set_legacy_json_value(const std::string& value);\n\n  LynxDebugAnnotation() = default;\n\n private:\n  ::perfetto::protos::pbzero::DebugAnnotation* debug_annotation_ = nullptr;\n};\n\nenum FlowDirection {\n  FLOW_UNSPECIFIED = 0,\n  FLOW_IN = 1,\n  FLOW_OUT = 2,\n  FLOW_INOUT = 3,\n};\n\nclass TRACE_EXPORT TrackEvent_LegacyEvent {\n public:\n  TrackEvent_LegacyEvent(\n      ::perfetto::protos::pbzero::TrackEvent_LegacyEvent* legacy_event)\n      : legacy_event_(legacy_event) {}\n  ~TrackEvent_LegacyEvent() = default;\n\n  void set_phase(int32_t value);\n  void set_unscoped_id(uint64_t value);\n  void set_bind_id(uint64_t value);\n  void set_flow_direction(FlowDirection value);\n  void set_global_id(uint64_t value);\n  void set_use_async_tts(bool value);\n  void set_bind_to_enclosing(bool value);\n\n private:\n  TrackEvent_LegacyEvent() = delete;\n  ::perfetto::protos::pbzero::TrackEvent_LegacyEvent* legacy_event_ = nullptr;\n};\n\nclass TRACE_EXPORT TrackEvent {\n public:\n  enum TrackEvent_Type : int32_t {\n    TrackEvent_Type_TYPE_UNSPECIFIED = 0,\n    TrackEvent_Type_TYPE_SLICE_BEGIN = 1,\n    TrackEvent_Type_TYPE_SLICE_END = 2,\n    TrackEvent_Type_TYPE_INSTANT = 3,\n    TrackEvent_Type_TYPE_COUNTER = 4,\n  };\n  TrackEvent() = delete;\n  TrackEvent(::perfetto::EventContext* ctx) : ctx_(ctx){};\n  ~TrackEvent() = default;\n\n  void set_name(const std::string& value);\n  void set_track_uuid(uint64_t value);\n  void add_flow_ids(uint64_t value);\n  void add_terminating_flow_ids(uint64_t value);\n  LynxDebugAnnotation* add_debug_annotations();\n  void add_debug_annotations(const std::string& name, const std::string& value);\n  void add_debug_annotations(std::string&& name, std::string&& value);\n  void set_timestamp_absolute_us(int64_t value);\n  TrackEvent_LegacyEvent* set_legacy_event();\n\n private:\n  ::perfetto::EventContext* ctx_ = nullptr;\n  std::unique_ptr<LynxDebugAnnotation> lynx_debug_annotation_ = nullptr;\n  std::unique_ptr<TrackEvent_LegacyEvent> legacy_event_ = nullptr;\n};\n\nclass EventContext {\n public:\n  EventContext(TrackEvent* event) : event_(event) {}\n  TrackEvent* event() const { return event_; }\n\n private:\n  TrackEvent* event_;\n};\n}  // namespace perfetto\n}  // namespace lynx\n\n#endif  // BASE_TRACE_NATIVE_TRACK_EVENT_WRAPPER_H_\n"
  },
  {
    "path": "base/trace/native/track_event_wrapper_mock.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/trace/native/track_event_wrapper.h\"\n\nnamespace perfetto {\nnamespace protos {\nnamespace pbzero {\nclass DebugAnnotation {};\n}  // namespace pbzero\n}  // namespace protos\nclass EventContext {};\nclass CounterTrack {};\n}  // namespace perfetto\n\nnamespace lynx {\nnamespace perfetto {}  // namespace perfetto\nnamespace trace {\nconstexpr ::perfetto::CounterTrack ConvertToPerfCounterTrack(\n    const lynx::perfetto::CounterTrack& counter_tarck) {\n  return ::perfetto::CounterTrack();\n}\n}  // namespace trace\n}  // namespace lynx\n\nnamespace lynx {\nnamespace perfetto {\n\nuint64_t ThreadTrack::Current() { return 0; }\n\nvoid LynxDebugAnnotation::set_name(const std::string& value) {}\nvoid LynxDebugAnnotation::set_bool_value(bool value) {}\nvoid LynxDebugAnnotation::set_uint_value(uint64_t value) {}\nvoid LynxDebugAnnotation::set_int_value(int64_t value) {}\nvoid LynxDebugAnnotation::set_double_value(double value) {}\nvoid LynxDebugAnnotation::set_string_value(const char* data, size_t size) {}\nvoid LynxDebugAnnotation::set_string_value(const std::string& value) {}\n\nvoid LynxDebugAnnotation::set_legacy_json_value(const std::string& value) {}\n\nvoid TrackEvent_LegacyEvent::set_phase(int32_t value) {}\nvoid TrackEvent_LegacyEvent::set_unscoped_id(uint64_t value) {}\nvoid TrackEvent_LegacyEvent::set_bind_id(uint64_t value) {}\nvoid TrackEvent_LegacyEvent::set_flow_direction(FlowDirection value) {}\n\nvoid TrackEvent::set_name(const std::string& value) {}\nvoid TrackEvent::set_track_uuid(uint64_t value) {}\nvoid TrackEvent::add_flow_ids(uint64_t value) {}\nvoid TrackEvent::add_terminating_flow_ids(uint64_t value) {}\nLynxDebugAnnotation* TrackEvent::add_debug_annotations() { return nullptr; }\nvoid TrackEvent::add_debug_annotations(const std::string& name,\n                                       const std::string& value) {}\nvoid TrackEvent::add_debug_annotations(std::string&& name,\n                                       std::string&& value) {}\nvoid TrackEvent::set_timestamp_absolute_us(int64_t value) {}\n\nTrackEvent_LegacyEvent* TrackEvent::set_legacy_event() { return nullptr; }\n\n}  // namespace perfetto\n}  // namespace lynx\n"
  },
  {
    "path": "build_overrides/android.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\ncustom_ldflags = []\ncustom_cflags = []\ncustom_defines = []\n"
  },
  {
    "path": "build_overrides/angle.gni",
    "content": "# Copyright 2019 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Ensure use_xcode_clang is visibile to ANGLE.\nimport(\"//build/toolchain/toolchain.gni\")\n\n# The ANGLE build requires this file to point to the location of third-party\n# dependencies.\n\nangle_root = \"//third_party/angle\"\n\nangle_vma_version = 30000001\n\n# Flutter's buildroot looks enough like Chromium to satisfy Angle, and enough\n# to cause GN variable collisions if we don't set this.\nif (!is_fuchsia) {\n  angle_has_build = true\n}\n\n# Flutter's buildroot looks enough like Chromium to satisfy Angle, and enough\n# to cause GN variable collisions if we don't set this.\nangle_has_build = true\n\nangle_abseil_cpp_dir = \"//third_party/abseil-cpp\"\nangle_glslang_dir = \"//third_party/vulkan-deps/glslang/src\"\nangle_googletest_dir = \"//third_party/googletest/googletest/src\"\nangle_libpng_dir = \"//third_party/libpng\"\nangle_spirv_headers_dir = \"//third_party/vulkan-deps/spirv-headers/src\"\nangle_spirv_tools_dir = \"//third_party/vulkan-deps/spirv-tools/src\"\nangle_spirv_cross_dir = \"//third_party/vulkan-deps/spirv-cross/src\"\nangle_vulkan_memory_allocator_dir = \"//third_party/vulkan_memory_allocator\"\n\n# Note, this is just a placeholder, angle actullay doesn't use it\nangle_jsoncpp_dir = \"//build/secondary/third_party/jsoncpp\"\n\n# This is a general Chromium flag, but in the Flutter build only ANGLE needs it\n# so it is defined here.\nis_cfi = false\n\nwayland_dir = \"$angle_root/third_party/wayland\"\n"
  },
  {
    "path": "build_overrides/bindings_files.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\ninterceptor_shared_sources =\n    rebase_path(\n        [\n          \"../core/runtime/js/bindings/interceptor/interceptor_factory.cc\",\n          \"../core/runtime/js/bindings/interceptor/interceptor_factory.h\",\n        ])\nnetwork_interceptor_shared_sources = []\n\njsbridge_extend_shared_sources = rebase_path(\n        [ \"../core/runtime/js/bindings/modules/ios/lynx_module_interceptor.mm\" ])\n\ntrace_extend_sources = rebase_path([\n                                     \"../core/base/lynx_trace_categories.h\",\n                                     \"../core/base/trace/trace_event_def.h\",\n                                   ])\n"
  },
  {
    "path": "build_overrides/build.gni",
    "content": "# Copyright 2019 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# The engine build uses some Chromium-sourced versions of third-party\n# dependencies (e.g, ANGLE, abseil) to use their GN build files, but we don't\n# want the Chromium-specific parts of the build.\nbuild_with_chromium = false\n\n# Controls whether to build with the source code of the weak-node-api library\nuse_weak_node_api_source = false\n"
  },
  {
    "path": "build_overrides/codec_files.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nmagic_source =\n    rebase_path([ \"../core/template_bundle/template_codec/magic_number.cc\" ])\n"
  },
  {
    "path": "build_overrides/darwin.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nuse_flatten_deps = true\n\n# when publish lynx, all lynx components should be the same version\nlynx_version = \"0.0.1\"\nlynx_dependency = [ [\n      \"Lynx\",\n      \"$lynx_version\",\n    ] ]\nlynxbase_dependency = [ [\n      \"LynxBase\",\n      \"$lynx_version\",\n    ] ]\nlynxdevtool_dependency = [ [\n      \"LynxDevtool\",\n      \"$lynx_version\",\n    ] ]\nbasedevtool_dependency = [ [\n      \"BaseDevtool\",\n      \"$lynx_version\",\n    ] ]\n\nlynx_group_extend_source =\n    rebase_path([ \"../platform/darwin/common/lynx/LynxGroup.mm\" ])\n\ndarwin_config = rebase_path([\n                              \"../platform/darwin:darwin_flag_config\",\n                              \"../platform/darwin:darwin_include_config\",\n                            ])\n\ndevtool_darwin_config =\n    rebase_path([ \"../platform/darwin/common/lynx_devtool:public_config\" ])\n\nlynx_public_extend_sources =\n    rebase_path([ \"../platform/darwin/common/lynx/public/LynxGroup.h\" ])\n\ntasm_extend_deps = []\n\nFramework_deps = [\n  \"Lynx/LazyLoad\",\n  \"Lynx/Native\",\n  \"Lynx/Trace\",\n  \"LynxBase/Framework\",\n]\n\nPRIMJS_includes = [\n  \"//PODS_ROOT/PrimJS/src/interpreter\",\n  \"//PODS_ROOT/PrimJS/src\",\n  \"//PODS_ROOT/PrimJS/src/gc\",\n  \"//PODS_ROOT/PrimJS/src/interpreter/quickjs/include\",\n  \"//PODS_ROOT/PrimJS/src/napi\",\n  \"//PODS_ROOT/PrimJS/src/napi/env\",\n  \"//PODS_ROOT/PrimJS/src/napi/quickjs\",\n  \"//PODS_ROOT/PrimJS/src/napi/jsc\",\n]\n\nNative_includes = rebase_path([\n                                \"//\",\n                                \"//PODS_ROOT/LynxBase/lynx\",\n                                \"//PODS_ROOT/LynxBase\",\n                                \"//TARGET_BUILD_DIR/Lynx/gen/Lynx\",\n                                \"../core\",\n                                \"..\",\n                                \"../platform\",\n                              ]) + PRIMJS_includes\n\nDevtool_Native_includes =\n    rebase_path([\n                  \"//\",\n                  \"//PODS_ROOT/BaseDevtool/\",\n                  \"//PODS_ROOT/BaseDevtool/lynx/\",\n                  \"//PODS_ROOT/Lynx/lynx\",\n                  \"//PODS_TARGET_SRCROOT/core\",\n                  \"//PODS_TARGET_SRCROOT/devtool/lynx_devtool\",\n                  \"..\",\n                  \"//TARGET_BUILD_DIR/Devtool/gen/Lynx\",\n                  \"../devtool/lynx_devtool\",\n                  \"//PODS_ROOT/LynxBase/lynx\",\n                  \"//PODS_ROOT/LynxBase\",\n                ]) + PRIMJS_includes\n\nPrimJS_Qjs_Dep = [\n  \"PrimJS/quickjs\",\n  \"3.8.0-alpha.4\",\n]\nLepus_deps = [ PrimJS_Qjs_Dep ]\n\ndevtool_extend_subspec = []\n\nJSDebug_deps = rebase_path(\n        [\n          \"../devtool/fundamentals/js_inspect:js_inspect_interface\",\n          \"../devtool/js_inspect:inspector_const\",\n          \"../devtool/js_inspect/lepus:lepus_debug\",\n          \"../devtool/js_inspect/quickjs:quickjs_debug\",\n          \"../devtool/lynx_devtool/js_debug:devtool_common_js_debug\",\n          \"../devtool/lynx_devtool/js_debug:devtool_js_debug\",\n          \"../devtool/lynx_devtool/js_debug:devtool_quickjs_debug\",\n          \"../devtool/lynx_devtool/js_debug:devtool_lepus_debug\",\n          \"../devtool/lynx_devtool/js_debug:devtool_lepus_debug_manager\",\n          \"../devtool/lynx_devtool/js_debug:devtool_qjs_bridge\",\n        ])\n\njs_debug_dependency = [\n  \"PrimJS/quickjs_debugger\",\n  \"PrimJS/quickjs_heapprofiler\",\n  \"PrimJS/quickjs_profiler\",\n]\n\nbuild_devtool_resources_command = [\n  \"python3 ./devtool/lynx_devtool/resources/copy_resources.py\",\n  \"python3 tools/js_tools/build.py --platform ios --release_output platform/darwin/ios/JSAssets/release/lynx_core.js --dev_output platform/darwin/ios/lynx_devtool/assets/lynx_core_dev.js\",\n]\n\ndevtool_prepare_command = build_devtool_resources_command\n\nLepusNG_deps = [ PrimJS_Qjs_Dep ]\n\nNapiBinding_Common_deps = [ \"PrimJS/napi/env\" ]\n\nQuickJS_includes = PRIMJS_includes\nQuickJS_deps = [ \"PrimJS/napi/quickjs\" ]\n\nJSC_deps = [ \"PrimJS/napi/jsc\" ]\n\nWorklet_deps = [ PrimJS_Qjs_Dep ]\n"
  },
  {
    "path": "build_overrides/fml.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nnative_library_posix =\n    rebase_path(\"../clay/fml/platform/posix/native_library_posix.cc\")\n"
  },
  {
    "path": "build_overrides/glslang.gni",
    "content": "# Copyright 2019 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nglslang_spirv_tools_dir = \"//third_party/vulkan-deps/spirv-tools/src\"\nspirv_tools_dir = \"//third_party/vulkan-deps/spirv-tools/src\"\n"
  },
  {
    "path": "build_overrides/oliver.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nnode_headers_dst = rebase_path(\"../oliver/lynx-tasm\")\n"
  },
  {
    "path": "build_overrides/renderer_utils_files.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nlynx_renderer_utils_extend_sources =\n    rebase_path([\n                  \"../core/renderer/utils/base/tasm_utils.cc\",\n                  \"../core/renderer/utils/base/tasm_utils.h\",\n                ])\n\nlynx_tasm_extend_shared_sources = []\n"
  },
  {
    "path": "build_overrides/resource_file.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nlynx_resources =\n    rebase_path([ \"../platform/darwin/ios/JSAssets/release/lynx_core.js\" ])\n"
  },
  {
    "path": "build_overrides/services_files.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nfeature_count_extend_shared_sources =\n    rebase_path([ \"../core/services/feature_count/feature_counter.cc\" ])\n"
  },
  {
    "path": "build_overrides/spirv_tools.gni",
    "content": "# Copyright 2019 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# We are building inside Flutter.\nspirv_tools_standalone = false\n\n# Paths to SPIRV-Tools dependencies in Flutter.\nspirv_tools_googletest_dir = \"//third_party/googletest/googletest/src\"\nspirv_tools_spirv_headers_dir = \"//third_party/vulkan-deps/spirv-headers/src\"\n"
  },
  {
    "path": "build_overrides/swiftshader.gni",
    "content": "# Copyright 2019 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# We are building SwiftShader in Flutter.\nswiftshader_standalone = false\n\n# Path to SwiftShader.\nswiftshader_dir = \"//third_party/swiftshader\"\n"
  },
  {
    "path": "build_overrides/vulkan_headers.gni",
    "content": "# Copyright 2020 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This file is needed by the vulkan-headers build, but doesn't need to actually\n# set anything.\n"
  },
  {
    "path": "build_overrides/vulkan_loader.gni",
    "content": "# Copyright 2020 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nvulkan_headers_dir = \"//third_party/vulkan-deps/vulkan-headers/src\"\n\n# Vulkan loader build options\nvulkan_loader_shared = true\n"
  },
  {
    "path": "build_overrides/vulkan_tools.gni",
    "content": "# Copyright 2020 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nvulkan_headers_dir = \"//third_party/vulkan-deps/vulkan-headers/src\"\n\n# Subdirectories for generated files\nvulkan_data_subdir = \"\"\nvulkan_gen_subdir = \"\"\n"
  },
  {
    "path": "build_overrides/wayland.gni",
    "content": "# Copyright 2019 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# ANGLE expects this to be here.\n\n# Flutter has no wayland third-party dir\nwayland_gn_dir = \"\"\n\nuse_system_libwayland = true\n"
  },
  {
    "path": "build_overrides/windows.gni",
    "content": "# Copyright 2026 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nnapi_win_libs_config = []\n"
  },
  {
    "path": "clay/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//build/toolchain/clang.gni\")\nimport(\"common/config.gni\")\nimport(\"testing/testing.gni\")\n\nif (is_win) {\n  import(\"//build/config/win/win.gni\")\n}\n\nconfig(\"config\") {\n  defines = feature_defines_list\n\n  include_dirs = [\n    \"..\",\n    \"$root_gen_dir\",\n  ]\n\n  if (is_win) {\n    defines += [ \"TTTEXT_OS_WIN\" ]\n  }\n\n  if (enable_skity) {\n    include_dirs += [\n      \"../base/trace\",\n\n      # skity\n      \"//third_party/skity\",\n      \"//third_party/skity/third_party/glm\",\n      \"//third_party/skity/include\",\n      \"//third_party/skity/skity/module/codec/include\",\n\n      # textlayout\n      \"//third_party/textlayout/textra/public\",\n\n      \"//third_party/icu/source/common\",\n    ]\n\n    if (is_win || is_mac) {\n      include_dirs += [ \"//third_party/skity/skity/include\" ]\n      defines += [ \"TTTEXT_OS_WIN\" ]\n    }\n\n    defines += [\n      \"ENABLE_SKITY\",\n      \"DISABLE_SKITY_EXPERIMENTAL_WARNINGS\",\n    ]\n  } else {\n    include_dirs += [\n      # textlayout\n      \"//third_party/textlayout/textra/public\",\n      \"//third_party/skia/include\",\n\n      # use the independent skity geometry.\n      \"third_party/glm/\",\n      \"third_party/skity_geometry/include/\",\n      \"third_party/txt/src\",\n    ]\n  }\n\n  cflags = []\n  if (is_mac) {\n    cflags += [ \"-g\" ]\n    cflags += [ \"-fno-standalone-debug\" ]\n  } else if (is_win) {\n    cflags += [\n      \"/Zi\",\n      \"-Wno-vla-extension\",\n      \"-Wno-vla-cxx-extension\",\n      \"-Wno-microsoft-template-shadow\",\n      \"-Wno-unknown-warning-option\",\n      \"-Wno-gnu-zero-variadic-macro-arguments\",\n    ]\n    ldflags = [ \"/DEBUG\" ]\n  }\n\n  if (is_apple) {\n    cflags_objc = [\n      \"-Werror=overriding-method-mismatch\",\n      \"-Werror=undeclared-selector\",\n      \"-fobjc-arc\",\n    ]\n    cflags_objcc = cflags_objc\n  }\n\n  if (is_clang) {\n    cflags += [\n      \"-Wno-unused-lambda-capture\",\n      \"-Wno-unused-const-variable\",\n    ]\n  }\n}\n\nconfig(\"export_dynamic_symbols\") {\n  # --dynamic-list is the GNU linker syntax supported by ELF linkers.\n  # -exported_symbols_list is the macOS linker syntax. The different flags\n  # accept files formatted differently, so we have exported_symbols.sym for GNU\n  # linker syntax, and exported_symbols_mac.sym for the macOS linker syntax.\n  if (is_linux || is_fuchsia) {\n    inputs = [ \"../common/exported_symbols.sym\" ]\n    ldflags = [ \"-Wl,--dynamic-list=\" + rebase_path(inputs[0], root_build_dir) ]\n  }\n}\n\nif (enable_clay_standalone) {\n  clay_lib_type = \"shared_library\"\n} else {\n  clay_lib_type = \"source_set\"\n}\n\ntarget(clay_lib_type, \"standalone_lib\") {\n  testonly = true\n  output_name = \"clay\"\n\n  include_dirs = [\n    \"../\",\n    \"../lynx\",\n  ]\n\n  sources = []\n\n  deps = [\n    \":unittests\",\n    \"../base/src:base_static\",\n    \"example/glfw\",\n    \"shell/common\",\n  ]\n\n  configs += [ \":config\" ]\n\n  cflags = []\n\n  lib_dirs = []\n  libs = []\n\n  if (enable_clay_standalone && is_win && use_flutter_cxx) {\n    if (windows_runtime_library_mode == \"md\") {\n      libs += [ \"msvcprt.lib\" ]\n    } else if (windows_runtime_library_mode == \"mt\") {\n      libs += [ \"libcpmt.lib\" ]\n    }\n  }\n}\n\ngroup(\"unittests\") {\n  testonly = true\n  public_deps = []\n\n  # Compile all unittests targets if enabled.\n  if (enable_unittests) {\n    public_deps += [\n      \"flow:flow_unittests\",\n      \"gfx:gfx_unittests\",\n      \"memory:memory_unittests\",\n      \"testing:testing_unittests\",\n      \"third_party/txt:txt_unittests\",\n      \"ui:clay_unittests\",\n    ]\n\n    if (use_net_loader) {\n      public_deps += [ \"net:net_unittests\" ]\n    }\n\n    if (is_mac || is_win) {\n      public_deps += [\n        \"shell/platform/common:common_cpp_core_unittests\",\n        \"shell/platform/common:common_cpp_unittests\",\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "clay/LICENSE",
    "content": "Copyright 2013 The Flutter Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-------------------------------------------------------------------------------\n\n// Copyright 2015 The Chromium Authors\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//    * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//    * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//    * Neither the name of Google LLC nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-------------------------------------------------------------------------------\n\n/*\n * Copyright 2018 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"
  },
  {
    "path": "clay/LICENSE.flutter",
    "content": "Copyright 2014 The Flutter Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "clay/README.md",
    "content": "# Clay\n\n`Clay` is a high-performance, feature-rich, and multi-platform consistent rendering runtime based\non the [Flutter engine](https://github.com/flutter/engine). It includes several feature enhancements\nand performance optimizations, including:\n- Support for rendering with the Skity 2D library (https://github.com/lynx-family/skity)\n- Performance optimizations such as frame scheduling, animations, memory and images\n- Expanded platform support on tvOS and HarmonyOS, also support for embedding in Qt and Electron\n- Feature alignment with the Lynx framework, including native modules, CSS rendering, and providing consistent elements, among others\n\nAnd additional features inspired by [Chromium](https://chromium.googlesource.com/chromium/), including:\n- Schedule frame based on a state machine\n- Run animations and defer image decoding on the GPU thread\n- Support for sharing graphics resources between different graphics APIs, including across multiple processes\n\nClay is committed to providing developers in different cross-platform scenarios with rendering\ncapabilities featuring \"high performance, multi-platform consistency, and easy to integrate\",\nhelping multi-platform developers accelerate the innovation and iteration of cross-platform\nsolutions and enabling users to have a consistent and excellent experience on any device.\n\n## Try Clay\n\n### Environment Setup\n\nThe project use [habitat](https://github.com/lynx-family/habitat) to manage dependencies.\nYou can install habitat by following the instructions on the habitat repository.\n\nAlso use [ninja](https://ninja-build.org/) to build project.\n\n### Build and Run\nTo learn about running example, see [example](example/glfw/README.md)\n\n\n## Credits\n\nClay use the following third-party libraries.\nWe appreciate the efforts of the developers and the open-source community behind these projects.\n\n- [libcxx](https://github.com/llvm/llvm-project/libcxx)\n- [libcxxabi](https://github.com/llvm/llvm-project/libcxxabi)\n- [abseil-cpp](https://github.com/abseil/abseil-cpp)\n- [ANGLE](https://github.com/google/angle)\n- [astc-encoder](https://github.com/ARM-software/astc-encoder)\n- [wayland](https://wayland.freedesktop.org/)\n- [expat](http://sourceforge.net/projects/expat)\n- [icu](https://github.com/unicode-org/icu)\n- [harfbuzz](https://github.com/harfbuzz/harfbuzz)\n- [libjpeg-turbo](https://fuchsia.googlesource.com/third_party/libjpeg-turbo.git)\n- [libpng](https://flutter.googlesource.com/third_party/libpng.git)\n- [libwebp](https://chromium.googlesource.com/webm/libwebp.git)\n- [skia](https://github.com/google/skia)\n- [skity](https://github.com/lynx-family/skity)\n- [swiftshader](https://swiftshader.googlesource.com/SwiftShader.git)\n- [wuffs](https://skia.googlesource.com/external/github.com/google/wuffs.git)\n- [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator)\n- [vulkan-deps](https://chromium.googlesource.com/vulkan-deps.git)\n- [Vulkan-Headers](https://github.com/KhronosGroup/Vulkan-Headers)\n- [Vulkan-Loader](https://github.com/KhronosGroup/Vulkan-Loader)\n- [Vulkan-Tools](https://github.com/KhronosGroup/Vulkan-Tools)\n- [SPIRV-Tools](https://github.com/KhronosGroup/SPIRV-Tools)\n- [SPIRV-Headers](https://github.com/KhronosGroup/SPIRV-Headers)\n- [SPIRV-Cross](https://github.com/KhronosGroup/SPIRV-Cross)\n- [glslang](https://github.com/KhronosGroup/glslang)\n- [zlib](https://chromium.googlesource.com/chromium/src/third_party/zlib)\n- [boringssl](https://boringssl.googlesource.com/boringssl.git)\n- [GLM](https://github.com/g-truc/glm)\n- [FreeType](https://www.freetype.org/)\n- [nanopng](https://gitlab.com/TSnake41/nanopng)\n- [GLFW](https://www.glfw.org/)\n- [gtest-parallel](https://github.com/google/gtest-parallel)\n- [googletest](https://github.com/google/googletest)\n- [google/benchmark](https://github.com/google/benchmark)\n\n## License\nClay is Apache licensed, as found in the [LICENSE](../LICENSE) file.\n"
  },
  {
    "path": "clay/assets/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../common/config.gni\")\n\nsource_set(\"assets\") {\n  if (is_harmony) {\n    sources = [\n      \"asset_manager.cc\",\n      \"asset_manager.h\",\n      \"asset_resolver.h\",\n      \"directory_asset_bundle.cc\",\n      \"directory_asset_bundle.h\",\n    ]\n  }\n\n  deps = [\n    \"../common\",\n    \"../fml:fml\",\n  ]\n\n  configs += [ \"../:config\" ]\n}\n"
  },
  {
    "path": "clay/assets/asset_manager.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/assets/asset_manager.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/assets/directory_asset_bundle.h\"\n\nnamespace clay {\n\nAssetManager::AssetManager() = default;\n\nAssetManager::~AssetManager() = default;\n\nbool AssetManager::PushFront(std::unique_ptr<AssetResolver> resolver) {\n  if (resolver == nullptr || !resolver->IsValid()) {\n    return false;\n  }\n\n  resolvers_.push_front(std::move(resolver));\n  return true;\n}\n\nbool AssetManager::PushBack(std::unique_ptr<AssetResolver> resolver) {\n  if (resolver == nullptr || !resolver->IsValid()) {\n    return false;\n  }\n\n  resolvers_.push_back(std::move(resolver));\n  return true;\n}\n\nvoid AssetManager::UpdateResolverByType(\n    std::unique_ptr<AssetResolver> updated_asset_resolver,\n    AssetResolver::AssetResolverType type) {\n  if (updated_asset_resolver == nullptr) {\n    return;\n  }\n  bool updated = false;\n  std::deque<std::unique_ptr<AssetResolver>> new_resolvers;\n  for (auto& old_resolver : resolvers_) {\n    if (!updated && old_resolver->GetType() == type) {\n      // Push the replacement updated resolver in place of the old_resolver.\n      new_resolvers.push_back(std::move(updated_asset_resolver));\n      updated = true;\n    } else {\n      new_resolvers.push_back(std::move(old_resolver));\n    }\n  }\n  // Append resolver to the end if not used as a replacement.\n  if (!updated) {\n    new_resolvers.push_back(std::move(updated_asset_resolver));\n  }\n  resolvers_.swap(new_resolvers);\n}\n\nstd::deque<std::unique_ptr<AssetResolver>> AssetManager::TakeResolvers() {\n  return std::move(resolvers_);\n}\n\n// |AssetResolver|\nstd::unique_ptr<fml::Mapping> AssetManager::GetAsMapping(\n    const std::string& asset_name) const {\n  if (asset_name.empty()) {\n    return nullptr;\n  }\n  TRACE_EVENT(\"flutter\", \"AssetManager::GetAsMapping\", \"name\",\n              asset_name.c_str());\n  for (const auto& resolver : resolvers_) {\n    auto mapping = resolver->GetAsMapping(asset_name);\n    if (mapping != nullptr) {\n      return mapping;\n    }\n  }\n  FML_DLOG(WARNING) << \"Could not find asset: \" << asset_name;\n  return nullptr;\n}\n\n// |AssetResolver|\nstd::vector<std::unique_ptr<fml::Mapping>> AssetManager::GetAsMappings(\n    const std::string& asset_pattern,\n    const std::optional<std::string>& subdir) const {\n  std::vector<std::unique_ptr<fml::Mapping>> mappings;\n  if (asset_pattern.empty()) {\n    return mappings;\n  }\n  TRACE_EVENT(\"flutter\", \"AssetManager::GetAsMappings\", \"pattern\",\n              asset_pattern.c_str());\n  for (const auto& resolver : resolvers_) {\n    auto resolver_mappings = resolver->GetAsMappings(asset_pattern, subdir);\n    mappings.insert(mappings.end(),\n                    std::make_move_iterator(resolver_mappings.begin()),\n                    std::make_move_iterator(resolver_mappings.end()));\n  }\n  return mappings;\n}\n\n// |AssetResolver|\nbool AssetManager::IsValid() const { return !resolvers_.empty(); }\n\n// |AssetResolver|\nbool AssetManager::IsValidAfterAssetManagerChange() const { return false; }\n\n// |AssetResolver|\nAssetResolver::AssetResolverType AssetManager::GetType() const {\n  return AssetResolverType::kAssetManager;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/assets/asset_manager.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_ASSETS_ASSET_MANAGER_H_\n#define CLAY_ASSETS_ASSET_MANAGER_H_\n\n#include <deque>\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/assets/asset_resolver.h\"\n\nnamespace clay {\n\nclass AssetManager final : public AssetResolver {\n public:\n  AssetManager();\n\n  ~AssetManager() override;\n\n  //--------------------------------------------------------------------------\n  /// @brief      Adds an asset resolver to the front of the resolver queue.\n  ///             Assets would be loaded from this resolver before any following\n  ///             resolvers.\n  ///\n  /// @return     Returns whether this resolver is valid and has been added to\n  ///             the resolver queue.\n  bool PushFront(std::unique_ptr<AssetResolver> resolver);\n\n  //--------------------------------------------------------------------------\n  /// @brief      Adds an asset resolver to the end of the resolver queue.\n  ///             Assets would be loaded from this resolver after any previous\n  ///             resolvers.\n  ///\n  /// @return     Returns whether this resolver is valid and has been added to\n  ///             the resolver queue.\n  bool PushBack(std::unique_ptr<AssetResolver> resolver);\n\n  //--------------------------------------------------------------------------\n  /// @brief      Replaces an asset resolver of the specified `type` with\n  ///             `updated_asset_resolver`. The matching AssetResolver is\n  ///             removed and replaced with `updated_asset_resolvers`.\n  ///\n  ///             AssetResolvers should be updated when the existing resolver\n  ///             becomes obsolete and a newer one becomes available that\n  ///             provides updated access to the same type of assets as the\n  ///             existing one. This update process is meant to be performed\n  ///             at runtime.\n  ///\n  ///             If a null resolver is provided, nothing will be done. If no\n  ///             matching resolver is found, the provided resolver will be\n  ///             added to the end of the AssetManager resolvers queue. The\n  ///             replacement only occurs with the first matching resolver.\n  ///             Any additional matching resolvers are untouched.\n  ///\n  /// @param[in]  updated_asset_resolver  The asset resolver to replace the\n  ///             resolver of matching type with.\n  ///\n  /// @param[in]  type  The type of AssetResolver to update. Only resolvers of\n  ///                   the specified type will be replaced by the updated\n  ///                   resolver.\n  ///\n  void UpdateResolverByType(\n      std::unique_ptr<AssetResolver> updated_asset_resolver,\n      AssetResolver::AssetResolverType type);\n\n  std::deque<std::unique_ptr<AssetResolver>> TakeResolvers();\n\n  // |AssetResolver|\n  bool IsValid() const override;\n\n  // |AssetResolver|\n  bool IsValidAfterAssetManagerChange() const override;\n\n  // |AssetResolver|\n  AssetResolver::AssetResolverType GetType() const override;\n\n  // |AssetResolver|\n  std::unique_ptr<fml::Mapping> GetAsMapping(\n      const std::string& asset_name) const override;\n\n  // |AssetResolver|\n  std::vector<std::unique_ptr<fml::Mapping>> GetAsMappings(\n      const std::string& asset_pattern,\n      const std::optional<std::string>& subdir) const override;\n\n private:\n  std::deque<std::unique_ptr<AssetResolver>> resolvers_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(AssetManager);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_ASSETS_ASSET_MANAGER_H_\n"
  },
  {
    "path": "clay/assets/asset_resolver.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_ASSETS_ASSET_RESOLVER_H_\n#define CLAY_ASSETS_ASSET_RESOLVER_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/mapping.h\"\n\nnamespace clay {\n\nclass AssetResolver {\n public:\n  AssetResolver() = default;\n\n  virtual ~AssetResolver() = default;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Identifies the type of AssetResolver an instance is.\n  ///\n  enum AssetResolverType {\n    kAssetManager,\n    kApkAssetProvider,\n    kDirectoryAssetBundle\n  };\n\n  virtual bool IsValid() const = 0;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Certain asset resolvers are still valid after the asset\n  ///             manager is replaced before a hot reload, or after a new run\n  ///             configuration is created during a hot restart. By preserving\n  ///             these resolvers and re-inserting them into the new resolver or\n  ///             run configuration, the tooling can avoid needing to sync all\n  ///             application assets through the Dart devFS upon connecting to\n  ///             the VM Service. Besides improving the startup performance of\n  ///             running a Flutter application, it also reduces the occurrence\n  ///             of tool failures due to repeated network flakes caused by\n  ///             damaged cables or hereto unknown bugs in the Dart HTTP server\n  ///             implementation.\n  ///\n  /// @return     Returns whether this resolver is valid after the asset manager\n  ///             or run configuration is updated.\n  ///\n  virtual bool IsValidAfterAssetManagerChange() const = 0;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Gets the type of AssetResolver this is. Types are defined in\n  ///             AssetResolverType.\n  ///\n  /// @return     Returns the AssetResolverType that this resolver is.\n  ///\n  virtual AssetResolverType GetType() const = 0;\n\n  [[nodiscard]] virtual std::unique_ptr<fml::Mapping> GetAsMapping(\n      const std::string& asset_name) const = 0;\n\n  //--------------------------------------------------------------------------\n  /// @brief      Same as GetAsMapping() but returns mappings for all files\n  ///             who's name matches a given pattern. Returns empty vector\n  ///             if no matching assets are found.\n  ///\n  /// @param[in]  asset_pattern  The pattern to match file names against.\n  ///\n  /// @param[in]  subdir  Optional subdirectory in which to search for files.\n  ///             If supplied this function does a flat search within the\n  ///             subdirectory instead of a recursive search through the entire\n  ///             assets directory.\n  ///\n  /// @return     Returns a vector of mappings of files which match the search\n  ///             parameters.\n  ///\n  [[nodiscard]] virtual std::vector<std::unique_ptr<fml::Mapping>>\n  GetAsMappings(const std::string& asset_pattern,\n                const std::optional<std::string>& subdir) const {\n    return {};\n  }\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(AssetResolver);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_ASSETS_ASSET_RESOLVER_H_\n"
  },
  {
    "path": "clay/assets/directory_asset_bundle.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/assets/directory_asset_bundle.h\"\n\n#include <memory>\n#include <regex>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/eintr_wrapper.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/fml/mapping.h\"\n\nnamespace clay {\n\nDirectoryAssetBundle::DirectoryAssetBundle(\n    fml::UniqueFD descriptor, bool is_valid_after_asset_manager_change)\n    : descriptor_(std::move(descriptor)) {\n  if (!fml::IsDirectory(descriptor_)) {\n    return;\n  }\n  is_valid_after_asset_manager_change_ = is_valid_after_asset_manager_change;\n  is_valid_ = true;\n}\n\nDirectoryAssetBundle::~DirectoryAssetBundle() = default;\n\n// |AssetResolver|\nbool DirectoryAssetBundle::IsValid() const { return is_valid_; }\n\n// |AssetResolver|\nbool DirectoryAssetBundle::IsValidAfterAssetManagerChange() const {\n  return is_valid_after_asset_manager_change_;\n}\n\n// |AssetResolver|\nAssetResolver::AssetResolverType DirectoryAssetBundle::GetType() const {\n  return AssetResolver::AssetResolverType::kDirectoryAssetBundle;\n}\n\n// |AssetResolver|\nstd::unique_ptr<fml::Mapping> DirectoryAssetBundle::GetAsMapping(\n    const std::string& asset_name) const {\n  if (!is_valid_) {\n    FML_DLOG(WARNING) << \"Asset bundle was not valid.\";\n    return nullptr;\n  }\n\n  auto mapping = std::make_unique<fml::FileMapping>(fml::OpenFile(\n      descriptor_, asset_name.c_str(), false, fml::FilePermission::kRead));\n\n  if (!mapping->IsValid()) {\n    return nullptr;\n  }\n\n  return mapping;\n}\n\nstd::vector<std::unique_ptr<fml::Mapping>> DirectoryAssetBundle::GetAsMappings(\n    const std::string& asset_pattern,\n    const std::optional<std::string>& subdir) const {\n  std::vector<std::unique_ptr<fml::Mapping>> mappings;\n  if (!is_valid_) {\n    FML_DLOG(WARNING) << \"Asset bundle was not valid.\";\n    return mappings;\n  }\n\n  std::regex asset_regex(asset_pattern);\n  fml::FileVisitor visitor = [&](const fml::UniqueFD& directory,\n                                 const std::string& filename) {\n    TRACE_EVENT(\"flutter\", \"DirectoryAssetBundle::GetAsMappings FileVisitor\");\n\n    if (std::regex_match(filename, asset_regex)) {\n      TRACE_EVENT(\"flutter\", \"Matched File\");\n\n      fml::UniqueFD fd = fml::OpenFile(directory, filename.c_str(), false,\n                                       fml::FilePermission::kRead);\n\n      if (fml::IsDirectory(fd)) {\n        return true;\n      }\n\n      auto mapping = std::make_unique<fml::FileMapping>(fd);\n\n      if (mapping && mapping->IsValid()) {\n        mappings.push_back(std::move(mapping));\n      } else {\n        FML_LOG(ERROR) << \"Mapping \" << filename << \" failed\";\n      }\n    }\n    return true;\n  };\n  if (!subdir) {\n    fml::VisitFilesRecursively(descriptor_, visitor);\n  } else {\n    fml::UniqueFD subdir_fd =\n        fml::OpenFileReadOnly(descriptor_, subdir.value().c_str());\n    if (!fml::IsDirectory(subdir_fd)) {\n      FML_LOG(ERROR) << \"Subdirectory path \" << subdir.value()\n                     << \" is not a directory\";\n      return mappings;\n    }\n    fml::VisitFiles(subdir_fd, visitor);\n  }\n\n  return mappings;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/assets/directory_asset_bundle.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_ASSETS_DIRECTORY_ASSET_BUNDLE_H_\n#define CLAY_ASSETS_DIRECTORY_ASSET_BUNDLE_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/unique_fd.h\"\n#include \"clay/assets/asset_resolver.h\"\n\nnamespace clay {\n\nclass DirectoryAssetBundle : public AssetResolver {\n public:\n  DirectoryAssetBundle(fml::UniqueFD descriptor,\n                       bool is_valid_after_asset_manager_change);\n\n  ~DirectoryAssetBundle() override;\n\n private:\n  const fml::UniqueFD descriptor_;\n  bool is_valid_ = false;\n  bool is_valid_after_asset_manager_change_ = false;\n\n  // |AssetResolver|\n  bool IsValid() const override;\n\n  // |AssetResolver|\n  bool IsValidAfterAssetManagerChange() const override;\n\n  // |AssetResolver|\n  AssetResolver::AssetResolverType GetType() const override;\n\n  // |AssetResolver|\n  std::unique_ptr<fml::Mapping> GetAsMapping(\n      const std::string& asset_name) const override;\n\n  // |AssetResolver|\n  std::vector<std::unique_ptr<fml::Mapping>> GetAsMappings(\n      const std::string& asset_pattern,\n      const std::optional<std::string>& subdir) const override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(DirectoryAssetBundle);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_ASSETS_DIRECTORY_ASSET_BUNDLE_H_\n"
  },
  {
    "path": "clay/build/concurrent_jobs.gni",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n_script = \"get_concurrent_jobs.py\"\n_args = [\n  \"--reserve-memory=1GB\",\n  \"--memory-per-job\",\n  \"dart=1GB\",\n]\n\nconcurrent_jobs = exec_script(_script, _args, \"json\", [ _script ])\n"
  },
  {
    "path": "clay/build/copy_info_plist.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n\"\"\"\nCopies the Info.plist and adds extra fields to it like the git hash of the\nengine.\n\nPrecondition: $CWD/../../flutter is the path to the flutter engine repo.\n\nusage: copy_info_plist.py --source <src_path> --destination <dest_path>\n                          --minversion=<deployment_target>\n\"\"\"\n\nimport argparse\nimport os\nimport subprocess\n\nimport git_revision\n\n\ndef get_clang_version():\n  clang_executable = str(\n      os.path.join(\n          '..', '..', 'buildtools', 'llvm', 'bin', 'clang++'\n      )\n  )\n  version = subprocess.check_output([clang_executable, '--version'])\n  return version.splitlines()[0]\n\n\ndef main():\n\n  parser = argparse.ArgumentParser(\n      description='Copies the Info.plist and adds extra fields to it like the '\n      'git hash of the engine'\n  )\n\n  parser.add_argument(\n      '--source',\n      help='Path to Info.plist source template',\n      type=str,\n      required=True\n  )\n  parser.add_argument(\n      '--destination',\n      help='Path to destination Info.plist',\n      type=str,\n      required=True\n  )\n  parser.add_argument(\n      '--minversion', help='Minimum device OS version like \"9.0\"', type=str\n  )\n  parser.add_argument('--bundlename',\n                      help='Bundle name, should equal to file name',\n                      type=str,\n                      required=True)\n  parser.add_argument('--bundleidentifier',\n                      help='Bundle identifier',\n                      type=str,\n                      required=False)\n\n  args = parser.parse_args()\n\n  text = open(args.source).read()\n  engine_path = os.path.join(os.getcwd(), '..', '..')\n  revision = git_revision.GetRepositoryVersion(engine_path)\n  clang_version = get_clang_version()\n  text = text.format(\n      revision=revision,\n      clang_version=clang_version,\n      min_version=args.minversion,\n      bundle_name=args.bundlename,\n      bundle_identifier=(args.bundleidentifier if args.bundleidentifier is not None else args.bundlename),\n  )\n\n  with open(args.destination, 'w') as outfile:\n    outfile.write(text)\n\n\nif __name__ == '__main__':\n  main()\n"
  },
  {
    "path": "clay/build/generate_coverage.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport sys\nimport subprocess\nimport os\nimport argparse\nimport errno\nimport shutil\n\n\ndef get_llvm_bin_directory():\n  buildtool_dir = os.path.join(\n      os.path.dirname(os.path.realpath(__file__)), '../../../buildtools'\n  )\n  llvm_bin_dir = os.path.abspath(\n      os.path.join(buildtool_dir, 'llvm/bin')\n  )\n  if not os.path.exists(llvm_bin_dir):\n    raise Exception('LLVM directory %s can not be located.' % llvm_bin_dir)\n  return llvm_bin_dir\n\n\ndef make_dirs(new_dir):\n  \"\"\"A wrapper around os.makedirs() that emulates \"mkdir -p\".\"\"\"\n  try:\n    os.makedirs(new_dir)\n  except OSError as err:\n    if err.errno != errno.EEXIST:\n      raise\n\n\ndef remove_if_exists(path):\n  if os.path.isdir(path) and not os.path.islink(path):\n    shutil.rmtree(path)\n  elif os.path.exists(path):\n    os.remove(path)\n\n\ndef collect_profiles(args):\n  raw_profiles = []\n  binaries = []\n\n  # Run all unit tests and collect raw profiles.\n  for test in args.tests:\n    absolute_test_path = os.path.abspath(test)\n    absolute_test_dir = os.path.dirname(absolute_test_path)\n    test_name = os.path.basename(absolute_test_path)\n\n    if not os.path.exists(absolute_test_path):\n      print('Path %s does not exist.' % absolute_test_path)\n      return -1\n\n    unstripped_test_path = os.path.join(\n        absolute_test_dir, 'exe.unstripped', test_name\n    )\n\n    if os.path.exists(unstripped_test_path):\n      binaries.append(unstripped_test_path)\n    else:\n      binaries.append(absolute_test_path)\n\n    raw_profile = absolute_test_path + '.rawprofile'\n\n    if not args.merge_only:\n      remove_if_exists(raw_profile)\n\n      print(\n          'Running test %s to gather profile.' %\n          os.path.basename(absolute_test_path)\n      )\n\n      test_command = [absolute_test_path]\n\n      test_args = ' '.join(args.test_args).split() if args.test_args is not None else None\n\n      print(test_args)\n\n      if test_args is not None:\n        test_command += test_args\n\n      subprocess.check_call(test_command, env={'LLVM_PROFILE_FILE': raw_profile})\n    else:\n      print(\"Merge only, skip generate profiles.\")\n\n    if not os.path.exists(raw_profile):\n      print('Could not find raw profile data for unit test run %s.' % test)\n      print('Did you build with the --coverage flag?')\n      return -1\n\n    raw_profiles.append(raw_profile)\n\n  return (binaries, raw_profiles)\n\n\ndef merge_profiles(llvm_bin_dir, raw_profiles, output):\n  # Merge all raw profiles into a single profile.\n  profdata_binary = os.path.join(llvm_bin_dir, 'llvm-profdata')\n\n  print('Merging %d raw profile(s) into single profile.' % len(raw_profiles))\n  merged_profile_path = os.path.join(output, 'all.profile')\n  remove_if_exists(merged_profile_path)\n  merge_command = [profdata_binary, 'merge', '-sparse'\n                  ] + raw_profiles + ['-o', merged_profile_path]\n  subprocess.check_call(merge_command)\n  print('Done.')\n  return merged_profile_path\n\n\ndef main():\n  parser = argparse.ArgumentParser()\n\n  parser.add_argument(\n      '-t',\n      '--tests',\n      nargs='+',\n      dest='tests',\n      required=True,\n      help='The unit tests to run and gather coverage data on.'\n  )\n  parser.add_argument(\n      '-o',\n      '--output',\n      dest='output',\n      required=True,\n      help='The output directory for coverage results.'\n  )\n  parser.add_argument(\n      '-f',\n      '--format',\n      type=str,\n      choices=['all', 'html', 'summary', 'lcov', \"json\"],\n      required=True,\n      help='The type of coverage information to be displayed.'\n  )\n  parser.add_argument(\n      '-a',\n      '--args',\n      nargs='+',\n      dest='test_args',\n      required=False,\n      help='The arguments to pass to the unit test executable being run.'\n  )\n  parser.add_argument(\n      '-m',\n      '--merge-only',\n      dest='merge_only',\n      action='store_true',\n      default=False,\n      help='Only merge profraw to specific format.'\n  )\n\n  args = parser.parse_args()\n\n  output = os.path.abspath(args.output)\n\n  make_dirs(output)\n\n  generate_all_reports = args.format == 'all'\n\n  binaries, raw_profiles = collect_profiles(args)\n\n  if len(raw_profiles) == 0:\n    print('No raw profiles could be generated.')\n    return -1\n\n  binaries_flag = []\n  for binary in binaries:\n    binaries_flag.append('-object')\n    binaries_flag.append(binary)\n\n  llvm_bin_dir = get_llvm_bin_directory()\n\n  merged_profile_path = merge_profiles(llvm_bin_dir, raw_profiles, output)\n\n  if not os.path.exists(merged_profile_path):\n    print('Could not generate or find merged profile %s.' % merged_profile_path)\n    return -1\n\n  llvm_cov_binary = os.path.join(llvm_bin_dir, 'llvm-cov')\n  instr_profile_flag = '-instr-profile=%s' % merged_profile_path\n  ignore_flags = '-ignore-filename-regex=third_party|unittest|fixture'\n\n  # Generate the HTML report if specified.\n  if generate_all_reports or args.format == 'html':\n    print('Generating HTML report.')\n    subprocess.check_call([llvm_cov_binary, 'show'] + binaries_flag + [\n        instr_profile_flag,\n        '-format=html',\n        '-output-dir=%s' % output,\n        '-tab-size=2',\n        ignore_flags,\n    ])\n    print('Done.')\n\n  # Generate a report summary if specified.\n  if generate_all_reports or args.format == 'summary':\n    print('Generating a summary report.')\n    cmds = [llvm_cov_binary, 'report'] + binaries_flag + [\n        instr_profile_flag,\n        ignore_flags,\n    ]\n    summary_file = os.path.join(output, 'summary.json')\n    subprocess.check_call(cmds)\n    print('Done.')\n\n  # Generate a lcov summary if specified.\n  if generate_all_reports or args.format == 'lcov':\n    print('Generating LCOV report.')\n    lcov_file = os.path.join(output, 'coverage.lcov')\n    remove_if_exists(lcov_file)\n    with open(lcov_file, 'w') as lcov_redirect:\n      subprocess.check_call([llvm_cov_binary, 'export'] + binaries_flag + [\n          instr_profile_flag,\n          ignore_flags,\n          '-format=lcov',\n      ],\n                            stdout=lcov_redirect)\n    print('Done.')\n\n  # Generate a lcov summary if specified.\n  if generate_all_reports or args.format == 'json':\n    print('Generating JSON report.')\n    summary_json_file = os.path.join(output, 'summary.json')\n    remove_if_exists(summary_json_file)\n    cmds = [llvm_cov_binary, 'export'] + binaries_flag + [\n          instr_profile_flag,\n          ignore_flags,\n          '--compilation-dir=\"/\"',\n          '-format=text',\n      ]\n    print(' '.join(cmds))\n    with open(summary_json_file, 'w') as json_redirect:\n      subprocess.check_call(cmds,\n                            stdout=json_redirect)\n    print('Done.')\n\n  return 0\n\n\nif __name__ == '__main__':\n  sys.exit(main())\n"
  },
  {
    "path": "clay/build/get_concurrent_jobs.py",
    "content": "#!/usr/bin/env python3\n# Copyright 2019 The Fuchsia Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# This script computes the number of concurrent jobs that can run in the\n# build as a function of the machine. It accepts a set of key value pairs\n# given by repeated --memory-per-job arguments. For example:\n#\n# $ get_concurrent_jobs.py --memory-per-job dart=1GB\n#\n# The result is a json map printed to stdout that gives the number of\n# concurrent jobs allowed of each kind. For example:\n#\n# {\"dart\": 8}\n#\n# Some memory can be held out of the calculation with the --reserve-memory flag.\n\nimport argparse\nimport ctypes\nimport json\nimport multiprocessing\nimport os\nimport re\nimport subprocess\nimport sys\n\nUNITS = {'B': 1, 'KB': 2**10, 'MB': 2**20, 'GB': 2**30, 'TB': 2**40}\n\n\n# pylint: disable=line-too-long\n# See https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex\n# and https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex\n# pylint: enable=line-too-long\nclass MEMORYSTATUSEX(ctypes.Structure):\n  _fields_ = [\n      ('dwLength', ctypes.c_ulong),\n      ('dwMemoryLoad', ctypes.c_ulong),\n      ('ullTotalPhys', ctypes.c_ulonglong),\n      ('ullAvailPhys', ctypes.c_ulonglong),\n      ('ullTotalPageFile', ctypes.c_ulonglong),\n      ('ullAvailPageFile', ctypes.c_ulonglong),\n      ('ullTotalVirtual', ctypes.c_ulonglong),\n      ('ullAvailVirtual', ctypes.c_ulonglong),\n      ('sullAvailExtendedVirtual', ctypes.c_ulonglong),\n  ]\n\n\ndef get_total_memory():\n  if sys.platform in ('win32', 'cygwin'):\n    stat = MEMORYSTATUSEX(dwLength=ctypes.sizeof(MEMORYSTATUSEX))\n    success = ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat))\n    return stat.ullTotalPhys if success else 0\n  if sys.platform.startswith('linux'):\n    if os.path.exists('/proc/meminfo'):\n      with open('/proc/meminfo') as meminfo:\n        memtotal_re = re.compile(r'^MemTotal:\\s*(\\d*)\\s*kB')\n        for line in meminfo:\n          match = memtotal_re.match(line)\n          if match:\n            return float(match.group(1)) * 2**10\n  if sys.platform == 'darwin':\n    try:\n      return int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']))\n    except:  # pylint: disable=bare-except\n      return 0\n  return 0\n\n\ndef parse_size(string):\n  i = next(i for (i, c) in enumerate(string) if not c.isdigit())\n  number = string[:i].strip()\n  unit = string[i:].strip()\n  return int(float(number) * UNITS[unit])\n\n\nclass ParseSizeAction(argparse.Action):\n\n  def __call__(self, parser, args, values, option_string=None):\n    sizes = getattr(args, self.dest, [])\n    for value in values:\n      (k, val) = value.split('=', 1)\n      sizes.append((k, parse_size(val)))\n    setattr(args, self.dest, sizes)\n\n\ndef main():\n  parser = argparse.ArgumentParser()\n  parser.add_argument(\n      '--memory-per-job',\n      action=ParseSizeAction,\n      default=[],\n      nargs='*',\n      help='Key value pairings (dart=1GB) giving an estimate of the amount of '\n      'memory needed for the class of job.'\n  )\n  parser.add_argument(\n      '--reserve-memory',\n      type=parse_size,\n      default=0,\n      help='The amount of memory to be held out of the amount for jobs to use.'\n  )\n  args = parser.parse_args()\n\n  total_memory = get_total_memory()\n\n  # Ensure the total memory used in the calculation below is at least 0\n  mem_total_bytes = max(0, total_memory - args.reserve_memory)\n\n  # Ensure the number of cpus used in the calculation below is at least 1\n  try:\n    cpu_cap = multiprocessing.cpu_count()\n  except:  # pylint: disable=bare-except\n    cpu_cap = 1\n\n  concurrent_jobs = {}\n  for job, memory_per_job in args.memory_per_job:\n    # Calculate the number of jobs that will fit in memory. Ensure the\n    # value is at least 1.\n    num_concurrent_jobs = int(max(1, mem_total_bytes / memory_per_job))\n    # Cap the number of jobs by the number of cpus available.\n    concurrent_jobs[job] = min(num_concurrent_jobs, cpu_cap)\n\n  print(json.dumps(concurrent_jobs))\n\n  return 0\n\n\nif __name__ == '__main__':\n  sys.exit(main())\n"
  },
  {
    "path": "clay/build/git_revision.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n\"\"\"Get the Git HEAD revision of a specified Git repository.\"\"\"\nfrom __future__ import absolute_import\nfrom __future__ import division\nfrom __future__ import print_function\n\nimport sys\nimport subprocess\nimport os\nimport argparse\n\ndef GetRepositoryVersion(repository):\n  \"Returns the Git HEAD for the supplied repository path as a string.\"\n  if not os.path.exists(repository):\n    raise IOError(\"path doesn't exist\")\n\n  version = subprocess.check_output(' '.join([\n    'git',\n    '-C',\n    repository,\n    'rev-parse',\n    'HEAD',\n  ]), shell=True)\n\n  return version.strip()\n\ndef main():\n  parser = argparse.ArgumentParser()\n\n  parser.add_argument('--repository',\n                      action='store',\n                      help='Path to the Git repository.',\n                      required=True)\n\n  args = parser.parse_args()\n  repository = os.path.abspath(args.repository)\n  version = GetRepositoryVersion(repository)\n  print(version.strip().decode())\n\n  return 0\n\nif __name__ == '__main__':\n  sys.exit(main())\n"
  },
  {
    "path": "clay/build/zip.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nimport argparse\nimport json\nimport os\nimport stat\nimport sys\nimport zipfile\n\n\ndef _zip_dir(path, zip_file, prefix):\n  path = path.rstrip('/\\\\')\n  for root, directories, files in os.walk(path):\n    for directory in directories:\n      if os.path.islink(os.path.join(root, directory)):\n        add_symlink(\n            zip_file,\n            os.path.join(root, directory),\n            os.path.join(root.replace(path, prefix), directory),\n        )\n    for file in files:\n      if os.path.islink(os.path.join(root, file)):\n        add_symlink(\n            zip_file, os.path.join(root, file),\n            os.path.join(root.replace(path, prefix), file)\n        )\n        continue\n      zip_file.write(\n          os.path.join(root, file),\n          os.path.join(root.replace(path, prefix), file)\n      )\n\n\ndef add_symlink(zip_file, source, target):\n  \"\"\"Adds a symlink to a zip file.\n\n  Args:\n    zip_file: The ZipFile obj where the symlink will be added.\n    source: The full path to the symlink.\n    target: The target path for the symlink within the zip file.\n  \"\"\"\n  zip_info = zipfile.ZipInfo(target)\n  zip_info.create_system = 3  # Unix like system\n  unix_st_mode = (\n      stat.S_IFLNK | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP\n      | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH\n  )\n  zip_info.external_attr = unix_st_mode << 16\n  zip_file.writestr(zip_info, os.readlink(source))\n\n\ndef main(args):\n  zip_file = zipfile.ZipFile(args.output, 'w', zipfile.ZIP_DEFLATED)\n  if args.source_file:\n    with open(args.source_file) as source_file:\n      file_dict_list = json.load(source_file)\n      for file_dict in file_dict_list:\n        if os.path.islink(file_dict['source']):\n          add_symlink(zip_file, file_dict['source'], file_dict['destination'])\n          continue\n        if os.path.isdir(file_dict['source']):\n          _zip_dir(file_dict['source'], zip_file, file_dict['destination'])\n        else:\n          zip_file.write(file_dict['source'], file_dict['destination'])\n  else:\n    for path, archive_name in args.input_pairs:\n      if os.path.islink(path):\n        add_symlink(zip_file, path, archive_name)\n        continue\n      if os.path.isdir(path):\n        _zip_dir(path, zip_file, archive_name)\n      else:\n        zip_file.write(path, archive_name)\n  zip_file.close()\n\n\nif __name__ == '__main__':\n  parser = argparse.ArgumentParser(description='This script creates zip files.')\n  parser.add_argument(\n      '-o',\n      dest='output',\n      action='store',\n      help='The name of the output zip file.'\n  )\n  parser.add_argument(\n      '-i',\n      dest='input_pairs',\n      nargs=2,\n      action='append',\n      help='The input file and its destination location in the zip archive.'\n  )\n  parser.add_argument(\n      '-f',\n      dest='source_file',\n      action='store',\n      help='The path to the file list to zip.'\n  )\n  sys.exit(main(parser.parse_args()))\n"
  },
  {
    "path": "clay/build/zip_bundle.gni",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\nimport(\"../common/config.gni\")\n\nif (clay_engine_mode == \"jit_release\") {\n  android_zip_archive_dir = \"android-$target_cpu-jit-release\"\n} else {\n  android_zip_archive_dir = \"android-$target_cpu\"\n  if (clay_engine_mode != \"debug\") {\n    android_zip_archive_dir += \"-$clay_engine_mode\"\n  }\n}\n\n# Creates a zip file in the $root_build_dir/zip_archives folder.\n#\n# The output variable specifies the name of the zip file to create.\n# The files variable is an array of scopes that specify a source file or\n# directory and a destination path in the archive to create.\n#\n# For example, to create a zip file named archive.zip with all files in the\n# root directory of the archive:\n#\n# zip_bundle(\"sample\") {\n#   output = \"archive.zip\"\n#   files = [\n#     {\n#       source = \"$root_build_dir/some/path/to/lib.so\"\n#       destination = \"lib.so\"\n#     },\n#     {\n#       source = \"$root_build_dir/some/other/path/with/files\"\n#       destination = \"other_files\"\n#     },\n#   ]\n# }\ntemplate(\"zip_bundle\") {\n  assert(defined(invoker.output), \"output must be defined\")\n  assert(defined(invoker.files), \"files must be defined as a list of scopes\")\n  action(target_name) {\n    script = \"zip.py\"\n    outputs = [ \"$root_build_dir/zip_archives/${invoker.output}\" ]\n    sources = []\n    forward_variables_from(invoker, [ \"visibility\" ])\n    deps = invoker.deps\n\n    args = [\n      \"-o\",\n      rebase_path(outputs[0]),\n    ]\n    foreach(input, invoker.files) {\n      args += [\n        \"-i\",\n        rebase_path(input.source),\n        input.destination,\n      ]\n      sources += [ input.source ]\n    }\n  }\n}\n\n# Creates a zip file in the $root_build_dir/zip_archives folder.\n#\n# The output variable specifies the name of the zip file to create.\n# The files variable is an array of scopes that specify a source file or\n# directory and a destination path in the archive to create.\n#\n# For example, to create a zip file named archive.zip with all files in the\n# root directory of the archive:\n#\n# zip_bundle(\"sample\") {\n#   output = \"archive.zip\"\n#   files = [\n#     {\n#       source = rebase_path(\"$root_build_dir/some/path/to/lib.so\")\n#       destination = \"lib.so\"\n#     },\n#     {\n#       source = rebase_path(\"$root_build_dir/some/other/path/with/files\")\n#       destination = \"other_files\"\n#     },\n#   ]\n# }\ntemplate(\"zip_bundle_from_file\") {\n  assert(defined(invoker.output), \"output must be defined\")\n  assert(defined(invoker.files), \"files must be defined as a list of scopes\")\n\n  action(target_name) {\n    forward_variables_from(invoker,\n                           [\n                             \"visibility\",\n                             \"deps\",\n                           ])\n    script = \"zip.py\"\n    outputs = [ \"$root_build_dir/zip_archives/${invoker.output}\" ]\n    sources = []\n    location_source_path = \"$target_gen_dir/zip_bundle.txt\"\n    write_file(location_source_path, invoker.files, \"json\")\n    args = [\n      \"-o\",\n      rebase_path(outputs[0]),\n      \"-f\",\n      rebase_path(location_source_path),\n    ]\n  }\n}\n"
  },
  {
    "path": "clay/common/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"config.gni\")\n\nsource_set(\"common\") {\n  sources = [\n    \"constants.h\",\n    \"element_id.cc\",\n    \"element_id.h\",\n    \"recyclable.h\",\n    \"settings.cc\",\n    \"settings.h\",\n    \"sha1.cc\",\n    \"sha1.h\",\n    \"sys_info.h\",\n    \"task_runners.cc\",\n    \"task_runners.h\",\n    \"thread_host.cc\",\n    \"thread_host.h\",\n    \"thread_host_holder.cc\",\n    \"thread_host_holder.h\",\n  ]\n\n  if (!is_android) {\n    sources += [ \"sys_info.cc\" ]\n  }\n\n  deps = [ \"../fml:fml\" ]\n\n  include_dirs = [ \"//third_party/boringssl/src/include\" ]\n\n  if (is_win || is_linux || is_mac) {\n    deps += [ \"//third_party/boringssl\" ]\n  } else if (is_android) {\n    deps += [ \"//third_party/boringssl:boringssl_asm\" ]\n    sources += [\n      \"//third_party/boringssl/src/crypto/cpu_aarch64_linux.cc\",\n      \"//third_party/boringssl/src/crypto/cpu_arm_linux.cc\",\n      \"//third_party/boringssl/src/crypto/cpu_intel.cc\",\n      \"//third_party/boringssl/src/crypto/crypto.cc\",\n      \"//third_party/boringssl/src/crypto/sha/sha1.cc\",\n      \"sha1_include.cc\",\n    ]\n  }\n\n  public_configs = [ \"../:config\" ]\n}\n"
  },
  {
    "path": "clay/common/config.gni",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../config.gni\")\n\ndeclare_args() {\n  # Whether to build with Clay rendering enabled\n  enable_clay = false\n\n  # Whether enable clay lite\n  enable_clay_lite = false\n\n  # The runtime mode (\"debug\", \"profile\", \"release\", or \"jit_release\")\n  clay_engine_mode = \"release\"\n\n  # Include net lib compile\n  use_net_loader = true\n\n  # Currently is used for benchmark tests on host platform.\n  force_enable_trim_skia_for_test = false\n\n  # Whether enable svg.\n  enable_svg = true\n\n  # Whether enable antialias\n  enable_antialias = true\n\n  # Whether use default typeface\n  use_default_typeface = false\n\n  clay_force_d3d9 = false\n\n  enable_clay_standalone = false\n\n  enable_software_rendering = false\n\n  enable_screenshot_service = enable_inspector\n}\n\ndeclare_args() {\n  # Whether to use skity backend\n  enable_skity = is_android || is_ios || enable_clay_lite\n\n  # Whether freetype lite enables capability of font variant. Clay will enable this\n  # by default. If canvas also wants to enable this config, just change this value to `true`.\n  freetype2_lite_enable_font_variant = enable_clay\n\n  # Whether to link the Minikin text module into the engine\n  clay_enable_minikin = false\n\n  # Whether to link the Skia text shaper module into the engine\n  clay_enable_skshaper = true\n\n  # enable tttext\n  clay_enable_tttext = false\n\n  use_platform_decode = false\n\n  enable_trim = enable_clay\n\n  # Whether use own binary icudtl.\n  use_binary_icudtl = false\n\n  use_accessbility = (is_android || is_ios) && !enable_clay_lite\n}\n\nif (enable_skity) {\n  clay_enable_tttext = true\n  use_platform_decode = true\n} else {\n  use_binary_icudtl =\n      (is_android || is_harmony || is_linux) && !enable_clay_standalone\n}\n\nif (clay_enable_tttext) {\n  clay_enable_skshaper = false\n}\n\n# feature_defines_list ---------------------------------------------------------\n\nfeature_defines_list = [\n  \"CLAY_ENGINE_MODE_DEBUG=1\",\n  \"CLAY_ENGINE_MODE_PROFILE=2\",\n  \"CLAY_ENGINE_MODE_RELEASE=3\",\n  \"CLAY_ENGINE_MODE_JIT_RELEASE=4\",\n\n  # clay matrix translation use fraction to promote animation fluency.\n  \"SUPPORT_FRACTIONAL_TRANSLATION\",\n  \"ENABLE_RASTER_CACHE_SCALE\",\n]\n\nfeature_defines_list += [\n  \"CLAY_ENGINE_MODE=3\",\n  \"FLUTTER_RELEASE=1\",\n]\n\nif (enable_trace == \"perfetto\") {\n  feature_defines_list += [ \"ENABLE_TRACE_PERFETTO\" ]\n} else if (enable_trace == \"systrace\") {\n  feature_defines_list += [ \"ENABLE_TRACE_SYSTRACE\" ]\n}\n\n# A combo of host os name and cpu is used in several locations to\n# generate the names of the archived artifacts.\nplatform_name = host_os\nif (platform_name == \"mac\") {\n  platform_name = \"darwin\"\n} else if (platform_name == \"win\") {\n  platform_name = \"windows\"\n}\n\nfull_platform_name = \"$platform_name-$host_cpu\"\n\ntarget_platform_name = target_os\nif (target_platform_name == \"mac\") {\n  platform_target_name = \"darwin\"\n} else if (target_platform_name == \"win\") {\n  target_platform_name = \"windows\"\n}\n\nfull_target_platform_name = \"$target_platform_name-$target_cpu\"\n\nif (use_platform_decode) {\n  feature_defines_list += [ \"ENABLE_PLATFORM_DECODE\" ]\n}\n\nif (use_net_loader) {\n  feature_defines_list += [ \"ENABLE_NET_LOADER\" ]\n}\n\nif (use_accessbility) {\n  feature_defines_list += [ \"ENABLE_ACCESSIBILITY\" ]\n}\n\nif (is_headless) {\n  feature_defines_list += [ \"ENABLE_HEADLESS\" ]\n}\n\nif (is_win || is_mac || is_headless || (is_android && !enable_clay_lite)) {\n  feature_defines_list += [ \"ENABLE_MOUSE_TRACKING\" ]\n}\n\nif (enable_svg) {\n  feature_defines_list += [ \"ENABLE_SVG\" ]\n}\n\nif (clay_enable_tttext) {\n  feature_defines_list += [\n    \"CLAY_ENABLE_TTTEXT\",\n    \"MINSIZE\",\n  ]\n}\n\nif (enable_antialias) {\n  feature_defines_list += [ \"ENABLE_ANTIALIAS\" ]\n}\n\nif (!is_android) {\n  feature_defines_list -= [ \"ENABLE_RASTER_CACHE_SCALE\" ]\n}\n\nif (enable_software_rendering) {\n  feature_defines_list += [ \"ENABLE_SOFTWARE_RENDERING\" ]\n}\n\nif (enable_clay_lite) {\n  feature_defines_list += [ \"ENABLE_CLAY_LITE=1\" ]\n}\n\nif (enable_skity) {\n  feature_defines_list += [ \"ENABLE_SKITY\" ]\n}\n\nif (enable_screenshot_service) {\n  feature_defines_list += [ \"ENABLE_SCREENSHOT_SERVICE=1\" ]\n}\n\nif (use_binary_icudtl) {\n  feature_defines_list += [ \"ENABLE_BINARY_ICUDTL=1\" ]\n}\n"
  },
  {
    "path": "clay/common/constants.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_CONSTANTS_H_\n#define CLAY_COMMON_CONSTANTS_H_\n\nnamespace clay {\nconstexpr double kMegaByteSizeInBytes = (1 << 20);\n}  // namespace clay\n\n#endif  // CLAY_COMMON_CONSTANTS_H_\n"
  },
  {
    "path": "clay/common/element_id.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/element_id.h\"\n\n#include <sstream>\n\nnamespace clay {\n\nuint64_t ElementId::NextUniqueID() {\n  static std::atomic<uint64_t> next_id(1);\n  uint64_t id;\n  do {\n    id = next_id.fetch_add(1);\n  } while (id == 0);  // 0 is reserved for an invalid id.\n  return id;\n}\n\nElementId::ElementId(int view_id, uint64_t unique_id)\n    : view_id_(view_id), unique_id_(unique_id) {}\n\nstd::string ElementId::ToString() const {\n  std::stringstream ss;\n  ss << \"ElementId: view_id=\" << view_id_ << \" unique_id=\" << unique_id_;\n  return ss.str();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/element_id.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_ELEMENT_ID_H_\n#define CLAY_COMMON_ELEMENT_ID_H_\n\n#include <string>\n\nnamespace clay {\n\nclass ElementId {\n public:\n  explicit ElementId(int view_id, uint64_t unique_id = NextUniqueID());\n\n  static uint64_t NextUniqueID();\n\n  bool operator==(const ElementId& o) const {\n    return view_id_ == o.view_id_ && unique_id_ == o.unique_id_;\n  }\n  bool operator!=(const ElementId& o) const { return !(*this == o); }\n\n  void UpdateViewId(int id) { view_id_ = id; }\n\n  int view_id() const { return view_id_; }\n  uint64_t unique_id() const { return unique_id_; }\n\n  std::string ToString() const;\n\n private:\n  int view_id_;\n  uint64_t unique_id_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_ELEMENT_ID_H_\n"
  },
  {
    "path": "clay/common/exported_symbols.sym",
    "content": "# These symbols are looked up from within the executable at runtime and must\n# be exported in the dynamic symbol table.\n{\n};\n"
  },
  {
    "path": "clay/common/exported_symbols_mac.sym",
    "content": "# These symbols are looked up from within the executable at runtime and must\n# be exported in the dynamic symbol table.\n"
  },
  {
    "path": "clay/common/graphics/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\nimport(\"../../testing/testing.gni\")\n\nsource_set(\"graphics\") {\n  sources = [\n    \"drawable_image.cc\",\n    \"drawable_image.h\",\n    \"gl_context_switch.cc\",\n    \"gl_context_switch.h\",\n    \"graphic_meminfo.h\",\n    \"msaa_sample_count.h\",\n    \"persistent_cache.cc\",\n    \"persistent_cache.h\",\n    \"screenshot.h\",\n    \"shared_drawable_image.cc\",\n    \"shared_drawable_image.h\",\n    \"shared_image_external_texture.cc\",\n    \"shared_image_external_texture.h\",\n  ]\n\n  if (is_linux) {\n    sources += [\n      \"shared_image_external_bitmap.cc\",\n      \"shared_image_external_bitmap.h\",\n    ]\n  }\n\n  # Heed caution when adding targets to the dependencies. This is a minimal\n  # target containing graphics sources that embedders can depend on. Any\n  # additions here could result in added app sizes across embeddings.\n  deps = [\n    \"../../../third_party/rapidjson\",\n    \"../../assets\",\n    \"../../fml:fml\",\n    \"../../version\",\n  ]\n\n  if (!enable_skity) {\n    deps += [\n      \"../../third_party/skity_geometry:skity_geometry\",\n      \"//third_party/skia\",\n    ]\n  }\n\n  public_configs = [ \"../../:config\" ]\n}\n\nsource_set(\"gl_scoped_binder\") {\n  sources = [\n    \"gl/gl_api.h\",\n    \"gl/scoped_framebuffer_binder.cc\",\n    \"gl/scoped_framebuffer_binder.h\",\n    \"gl/scoped_texture_binder.cc\",\n    \"gl/scoped_texture_binder.h\",\n  ]\n\n  public_configs = [ \"../../:config\" ]\n\n  if (is_android || is_win) {\n    sources += [\n      \"gl/gl_pipeline_helper.cc\",\n      \"gl/gl_pipeline_helper.h\",\n      \"gl/gl_shader_program.cc\",\n      \"gl/gl_shader_program.h\",\n    ]\n  }\n\n  sources += [\n    \"gl/gl_api_common.cc\",\n    \"gl/gl_api_common.h\",\n  ]\n  if (is_ios || is_tvos) {\n    frameworks = [ \"OpenGLES.framework\" ]\n  }\n\n  if (is_win) {\n    sources += [\n      \"gl/gl_api_windows.cc\",\n      \"gl/gl_api_windows.h\",\n    ]\n  }\n\n  public_deps = [ \"../../fml:fml\" ]\n\n  if (is_win) {\n    include_dirs = [ \"//third_party/angle/include\" ]\n    deps = [\n      \"//third_party/angle:libEGL_static\",\n      \"//third_party/angle:libGLESv2_static\",\n    ]\n  }\n}\n"
  },
  {
    "path": "clay/common/graphics/drawable_image.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/drawable_image.h\"\n\n#include <atomic>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nnamespace {\n\nuint64_t NextUniqueID() {\n  static std::atomic<uint64_t> next_id(1);\n  uint64_t id;\n  do {\n    id = next_id.fetch_add(1);\n  } while (id == 0);  // 0 is reserved for an invalid id.\n  return id;\n}\n\n}  // namespace\n\nContextListener::ContextListener() = default;\n\nContextListener::~ContextListener() = default;\n\nDrawableImage::DrawableImage() : id_(NextUniqueID()) {}\n\nDrawableImage::~DrawableImage() = default;\n\nDrawableImageRegistry::DrawableImageRegistry() = default;\n\nvoid DrawableImageRegistry::RegisterDrawableImage(\n    const std::shared_ptr<DrawableImage>& image) {\n  if (!image) {\n    return;\n  }\n  mapping_[image->Id()] = image;\n}\n\nvoid DrawableImageRegistry::RegisterContextListener(\n    uintptr_t id, std::weak_ptr<ContextListener> image) {\n  images_[id] = std::move(image);\n}\n\nvoid DrawableImageRegistry::UnregisterDrawableImage(int64_t id) {\n  auto found = mapping_.find(id);\n  if (found == mapping_.end()) {\n    return;\n  }\n  found->second->OnDrawableImageUnregistered();\n  mapping_.erase(found);\n}\n\nvoid DrawableImageRegistry::UnregisterContextListener(uintptr_t id) {\n  images_.erase(id);\n}\n\nvoid DrawableImageRegistry::OnGrContextCreated() {\n  for (auto& it : mapping_) {\n    it.second->OnGrContextCreated();\n  }\n\n  for (const auto& [id, weak_image] : images_) {\n    if (auto image = weak_image.lock()) {\n      image->OnGrContextCreated();\n    } else {\n      images_.erase(id);\n    }\n  }\n}\n\nvoid DrawableImageRegistry::OnGrContextDestroyed() {\n  for (auto& it : mapping_) {\n    it.second->OnGrContextDestroyed();\n  }\n\n  for (const auto& [id, weak_image] : images_) {\n    if (auto image = weak_image.lock()) {\n      image->OnGrContextDestroyed();\n    } else {\n      images_.erase(id);\n    }\n  }\n}\n\nstd::shared_ptr<DrawableImage> DrawableImageRegistry::GetDrawableImage(\n    int64_t id) {\n  auto it = mapping_.find(id);\n  return it != mapping_.end() ? it->second : nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/drawable_image.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_DRAWABLE_IMAGE_H_\n#define CLAY_COMMON_GRAPHICS_DRAWABLE_IMAGE_H_\n\n#include <map>\n#include <memory>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"clay/gfx/paint.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/rect.hpp\"\n\nclass GrDirectContext;\n\nnamespace clay {\n\nclass ContextListener {\n public:\n  ContextListener();\n  ~ContextListener();\n\n  // Called from raster thread.\n  virtual void OnGrContextCreated() = 0;\n\n  // Called from raster thread.\n  virtual void OnGrContextDestroyed() = 0;\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(ContextListener);\n};\n\nclass DrawableImage : public ContextListener {\n public:\n  enum class FitMode : uint8_t { kScaleToFill, kClipToBounds };\n\n  enum class ImageType : uint8_t {\n    kSharedImageTexture,\n    kSharedImageBitmap,\n    kMock,\n  };\n\n  struct PaintContext {\n    clay::GrCanvas* canvas = nullptr;\n    clay::GrContext* gr_context = nullptr;\n    const clay::GrPaint* sk_paint = nullptr;\n    const clay::Paint* clay_paint = nullptr;\n  };\n\n  DrawableImage();           // Called from UI or raster thread.\n  virtual ~DrawableImage();  // Called from raster thread.\n\n  virtual ImageType GetType() const = 0;\n\n  // Called on platform thread.\n  virtual void SetFrameAvailableCallback(const fml::closure& callback) = 0;\n\n  // Called from raster thread.\n  virtual void Paint(PaintContext& context, const skity::Rect& bounds,\n                     bool freeze, const clay::GrSamplingOptions& sampling,\n                     FitMode fit_mode) = 0;\n\n  // Called on raster thread.\n  virtual void MarkNewFrameAvailable() = 0;\n\n  // Called on raster thread.\n  virtual void OnDrawableImageUnregistered() = 0;\n\n  int64_t Id() { return id_; }\n\n private:\n  int64_t id_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(DrawableImage);\n};\n\nclass DrawableImageRegistry {\n public:\n  DrawableImageRegistry();\n\n  // Called from raster thread.\n  void RegisterDrawableImage(const std::shared_ptr<DrawableImage>& image);\n\n  // Called from raster thread.\n  void RegisterContextListener(uintptr_t id,\n                               std::weak_ptr<ContextListener> image);\n\n  // Called from raster thread.\n  void UnregisterDrawableImage(int64_t id);\n\n  // Called from the raster thread.\n  void UnregisterContextListener(uintptr_t id);\n\n  // Called from raster thread.\n  std::shared_ptr<DrawableImage> GetDrawableImage(int64_t id);\n\n  // Called from raster thread.\n  void OnGrContextCreated();\n\n  // Called from raster thread.\n  void OnGrContextDestroyed();\n\n private:\n  std::map<int64_t, std::shared_ptr<DrawableImage>> mapping_;\n  std::map<uintptr_t, std::weak_ptr<ContextListener>> images_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(DrawableImageRegistry);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_DRAWABLE_IMAGE_H_\n"
  },
  {
    "path": "clay/common/graphics/gl/gl_api.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_GL_GL_API_H_\n#define CLAY_COMMON_GRAPHICS_GL_GL_API_H_\n\n#include <cstdint>\n\nnamespace clay {\n\nclass GlApi {\n public:\n  virtual void GlGetIntegerv(uint32_t pname, int32_t* data) = 0;\n  virtual void GlBindFramebuffer(uint32_t target, uint32_t framebuffer) = 0;\n  virtual void GlBindTexture(uint32_t target, uint32_t texture) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_GL_GL_API_H_\n"
  },
  {
    "path": "clay/common/graphics/gl/gl_api_common.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/gl/gl_api_common.h\"\n\nnamespace clay {\n\nGlApiCommon* GlApiCommon::GetInstance() {\n  static GlApiCommon instance;\n  return &instance;\n}\n\nvoid GlApiCommon::GlGetIntegerv(uint32_t pname, int32_t* data) {\n  glGetIntegerv(pname, data);\n}\nvoid GlApiCommon::GlBindFramebuffer(uint32_t target, uint32_t framebuffer) {\n  glBindFramebuffer(target, framebuffer);\n}\nvoid GlApiCommon::GlBindTexture(uint32_t target, uint32_t texture) {\n  glBindTexture(target, texture);\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/gl/gl_api_common.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_GL_GL_API_COMMON_H_\n#define CLAY_COMMON_GRAPHICS_GL_GL_API_COMMON_H_\n\n#include <cstdint>\n\n#include \"build/build_config.h\"\n#include \"clay/common/graphics/gl/gl_api.h\"\n\n#if defined(OS_ANDROID)\n\n#include <GLES2/gl2.h>\n\n#elif defined(OS_MAC)\n\n#import <OpenGL/OpenGL.h>\n#import <OpenGL/gl3.h>\n\n#elif defined(OS_IOS) || defined(OS_TVOS)\n\n#import <OpenGLES/ES2/gl.h>\n\n#elif defined(OS_LINUX)\n\n#include <epoxy/egl.h>\n#include <epoxy/gl.h>\n\n#elif defined(OS_WIN)\n\n#include <GLES2/gl2.h>\n\n#else\n\n#include <GLES3/gl32.h>\n\n#endif\n\nnamespace clay {\n\nclass GlApiCommon : public GlApi {\n public:\n  static GlApiCommon* GetInstance();\n\n  void GlGetIntegerv(uint32_t pname, int32_t* data) override;\n  void GlBindFramebuffer(uint32_t target, uint32_t framebuffer) override;\n  void GlBindTexture(uint32_t target, uint32_t texture) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_GL_GL_API_COMMON_H_\n"
  },
  {
    "path": "clay/common/graphics/gl/gl_api_windows.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/gl/gl_api_windows.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nvoid GlApiWindows::GlGetIntegerv(uint32_t pname, int32_t* data) {\n  if (!gl_get_integerv_proc) {\n    FML_LOG(ERROR) << \"GlApiWindows has no glGetIntegerv proc\";\n    return;\n  }\n  gl_get_integerv_proc(pname, data);\n}\nvoid GlApiWindows::GlBindFramebuffer(uint32_t target, uint32_t framebuffer) {\n  if (!gl_bind_framebuffer_proc) {\n    FML_LOG(ERROR) << \"GlApiWindows has no glBindFramebuffer proc\";\n    return;\n  }\n  gl_bind_framebuffer_proc(target, framebuffer);\n}\nvoid GlApiWindows::GlBindTexture(uint32_t target, uint32_t texture) {\n  if (!gl_bind_texture_proc) {\n    FML_LOG(ERROR) << \"GlApiWindows has no glBindTexture proc\";\n    return;\n  }\n  gl_bind_texture_proc(target, texture);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/gl/gl_api_windows.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_GL_GL_API_WINDOWS_H_\n#define CLAY_COMMON_GRAPHICS_GL_GL_API_WINDOWS_H_\n\n#include <Windows.h>\n\n#include <cstdint>\n\n#include \"clay/common/graphics/gl/gl_api.h\"\n\ntypedef void(WINAPI* PFNGLGETINTEGERVPROC)(uint32_t pname, int32_t* data);\ntypedef void(WINAPI* PFNGLBINDFRAMEBUFFERPROC)(uint32_t target,\n                                               uint32_t framebuffer);\ntypedef void(WINAPI* PFNGLBINDTEXTUREPROC)(uint32_t target, uint32_t texture);\n\nnamespace clay {\n\nclass GlApiWindows : public GlApi {\n public:\n  void GlGetIntegerv(uint32_t pname, int32_t* data) override;\n  void GlBindFramebuffer(uint32_t target, uint32_t framebuffer) override;\n  void GlBindTexture(uint32_t target, uint32_t texture) override;\n\n  PFNGLGETINTEGERVPROC gl_get_integerv_proc = nullptr;\n  PFNGLBINDFRAMEBUFFERPROC gl_bind_framebuffer_proc = nullptr;\n  PFNGLBINDTEXTUREPROC gl_bind_texture_proc = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_GL_GL_API_WINDOWS_H_\n"
  },
  {
    "path": "clay/common/graphics/gl/gl_pipeline_helper.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/gl/gl_pipeline_helper.h\"\n\n#include \"base/include/closure.h\"\n#include \"clay/common/graphics/gl/gl_shader_program.h\"\n\nnamespace clay {\n\nGlPipelineHelper* GlPipelineHelper::GetInstance() {\n  static thread_local GlPipelineHelper* instance = new GlPipelineHelper();\n  return instance;\n}\n\nbool GlPipelineHelper::Initialize() {\n  if (initialized_) {\n    return true;\n  }\n\n  static const char* vertex_shader_source = R\"(\n      # version 100\n      precision mediump float;\n      attribute vec4 position;\n      attribute vec2 texCoord;\n      varying vec2 vTexCoord;\n      void main() {\n          gl_Position = position;\n          vTexCoord = texCoord;\n      }\n    )\";\n\n  static const char* fragment_shader_source = R\"(\n      # version 100\n      #extension GL_OES_EGL_image_external : require\n      precision mediump float;\n      varying vec2 vTexCoord;\n      uniform samplerExternalOES sTexture;\n      void main() {\n          gl_FragColor = texture2D(sTexture, vTexCoord);\n      }\n    )\";\n\n  program_ = CreateProgram(vertex_shader_source, fragment_shader_source);\n  if (program_ == 0) {\n    FML_LOG(ERROR) << \"Create program error\";\n    return false;\n  }\n\n  GLint current_program;\n  glGetIntegerv(GL_CURRENT_PROGRAM, &current_program);\n  fml::ScopedCleanupClosure restore_program(\n      [current_program]() { glUseProgram(current_program); });\n  glUseProgram(program_);\n  glUniform1i(glGetUniformLocation(program_, \"sTexture\"), 0);\n\n  GLint current_vao;\n  glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &current_vao);\n  fml::ScopedCleanupClosure restore_vao(\n      [current_vao]() { glBindVertexArray(current_vao); });\n  glGenVertexArrays(1, &vao_);\n  glBindVertexArray(vao_);\n\n  glGenBuffers(1, &vbo_);\n  glBindBuffer(GL_ARRAY_BUFFER, vbo_);\n\n  GLfloat vertices[] = {\n      -1.0f, -1.0f, 0.0f, 0.0f,  //\n      1.0f,  -1.0f, 1.0f, 0.0f,  //\n      -1.0f, 1.0f,  0.0f, 1.0f,  //\n      1.0f,  1.0f,  1.0f, 1.0f,\n  };\n\n  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n\n  auto location_pos = glGetAttribLocation(program_, \"position\");\n  auto location_tex = glGetAttribLocation(program_, \"texCoord\");\n\n  glEnableVertexAttribArray(location_pos);\n  glVertexAttribPointer(location_pos, 2, GL_FLOAT, GL_FALSE,\n                        4 * sizeof(GLfloat), (const void*)0);\n\n  glEnableVertexAttribArray(location_tex);\n  glVertexAttribPointer(location_tex, 2, GL_FLOAT, GL_FALSE,\n                        4 * sizeof(GLfloat),\n                        (const void*)(2 * sizeof(GLfloat)));\n\n  initialized_ = true;\n  return true;\n}\n\nvoid GlPipelineHelper::Destroy() {\n  if (!initialized_) {\n    return;\n  }\n\n  glDeleteBuffers(1, &vbo_);\n  glDeleteVertexArrays(1, &vao_);\n  glDeleteProgram(program_);\n  initialized_ = false;\n}\n\nbool GlPipelineHelper::Bind() {\n  if (!initialized_) {\n    return false;\n  }\n\n  glGetIntegerv(GL_CURRENT_PROGRAM, &saved_program_);\n  glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &saved_vao_);\n\n  // Bind our program and VAO\n  glUseProgram(program_);\n  glBindVertexArray(vao_);\n  return true;\n}\n\nvoid GlPipelineHelper::UnBind() {\n  if (!initialized_) {\n    return;\n  }\n  // Restore previous program and VAO state\n  glUseProgram(saved_program_);\n  glBindVertexArray(saved_vao_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/gl/gl_pipeline_helper.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_GL_GL_PIPELINE_HELPER_H_\n#define CLAY_COMMON_GRAPHICS_GL_GL_PIPELINE_HELPER_H_\n\n#include <GLES3/gl3.h>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nclass GlPipelineHelper {\n public:\n  static GlPipelineHelper* GetInstance();\n  bool Initialize();\n  void Destroy();\n\n  bool Bind();\n  void UnBind();\n\n private:\n  bool initialized_ = false;\n  GLuint vao_;\n  GLuint vbo_;\n  GLuint program_;\n\n  // Saved state for restoration\n  GLint saved_program_ = 0;\n  GLint saved_vao_ = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_GL_GL_PIPELINE_HELPER_H_\n"
  },
  {
    "path": "clay/common/graphics/gl/gl_shader_program.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/gl/gl_shader_program.h\"\n\nnamespace clay {\n\nGLuint CompileShader(GLenum type, const char* shader_src) {\n  if (type <= 0 || !shader_src) {\n    return 0;\n  }\n  GLuint shader = glCreateShader(type);\n  if (shader == 0) {\n    FML_LOG(ERROR) << \"Error occurred when creating shader\";\n    return 0;\n  }\n  glShaderSource(shader, 1, &shader_src, nullptr);\n  glCompileShader(shader);\n\n  GLint compiled;\n  glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);\n  if (!compiled) {\n    FML_LOG(ERROR) << \"Error occurred when compiling shader, type: \" << type;\n    glDeleteShader(shader);\n    return 0;\n  }\n  return shader;\n}\n\nGLuint CreateProgram(const char* vertex_source, const char* fragment_source) {\n  GLuint vertex_shader = CompileShader(GL_VERTEX_SHADER, vertex_source);\n  GLuint fragment_shader = CompileShader(GL_FRAGMENT_SHADER, fragment_source);\n  if (vertex_shader == 0 || fragment_shader == 0) {\n    FML_LOG(ERROR) << \"Cannot create program due to shader error\";\n    return 0;\n  }\n\n  GLuint program = glCreateProgram();\n  if (program == 0) {\n    FML_LOG(ERROR) << \"Error occurred when creating program\";\n    return 0;\n  }\n  glAttachShader(program, vertex_shader);\n  glAttachShader(program, fragment_shader);\n  glLinkProgram(program);\n  glDeleteShader(vertex_shader);\n  glDeleteShader(fragment_shader);\n\n  GLint linked;\n  glGetProgramiv(program, GL_LINK_STATUS, &linked);\n  if (!linked) {\n    FML_LOG(ERROR) << \"Linking program error\";\n    glDeleteProgram(program);\n    return 0;\n  }\n  return program;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/gl/gl_shader_program.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_GL_GL_SHADER_PROGRAM_H_\n#define CLAY_COMMON_GRAPHICS_GL_GL_SHADER_PROGRAM_H_\n\n#include <GLES2/gl2.h>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nGLuint CompileShader(GLenum type, const char* shader_src);\nGLuint CreateProgram(const char* vertex_source, const char* fragment_source);\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_GL_GL_SHADER_PROGRAM_H_\n"
  },
  {
    "path": "clay/common/graphics/gl/scoped_framebuffer_binder.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/gl/scoped_framebuffer_binder.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\ntypedef unsigned int GLenum;\n#define GL_FRAMEBUFFER 0x8D40\n#define GL_FRAMEBUFFER_BINDING 0x8CA6\n\nScopedFramebufferBinder::ScopedFramebufferBinder(unsigned int target,\n                                                 unsigned int framebuffer,\n                                                 GlApi* gl_api)\n    : target_(target), old_framebuffer_(-1), gl_api_(gl_api) {\n  if (!gl_api_) {\n    FML_LOG(ERROR) << \"Not get valid GLProc for ScopedFramebufferBinder\";\n    return;\n  }\n  GLenum target_getter = 0;\n  switch (target) {\n    case GL_FRAMEBUFFER:\n      target_getter = GL_FRAMEBUFFER_BINDING;\n      break;\n    default:\n      FML_LOG(ERROR) << \"Target not supported. \" << target;\n  }\n  gl_api_->GlGetIntegerv(target_getter, &old_framebuffer_);\n  gl_api_->GlBindFramebuffer(target_, framebuffer);\n}\n\nScopedFramebufferBinder::~ScopedFramebufferBinder() {\n  gl_api_->GlBindFramebuffer(target_, old_framebuffer_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/gl/scoped_framebuffer_binder.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_GL_SCOPED_FRAMEBUFFER_BINDER_H_\n#define CLAY_COMMON_GRAPHICS_GL_SCOPED_FRAMEBUFFER_BINDER_H_\n\n#include <cstdint>\n\n#include \"base/include/fml/macros.h\"\n#include \"build/build_config.h\"\n#include \"clay/common/graphics/gl/gl_api.h\"\n\n#if OS_WIN\n#include \"clay/common/graphics/gl/gl_api_windows.h\"\n#else\n#include \"clay/common/graphics/gl/gl_api_common.h\"\n#endif\n\nnamespace clay {\n\nclass ScopedFramebufferBinder {\n public:\n  ScopedFramebufferBinder(unsigned int target, unsigned int framebuffer,\n                          GlApi* gl_api\n#ifndef OS_WIN\n                          = GlApiCommon::GetInstance()\n#endif\n  );  // NOLINT\n\n  ~ScopedFramebufferBinder();\n\n private:\n  // Failing that we use GL calls to save and restore state.\n  int target_;\n  int old_framebuffer_;\n  GlApi* gl_api_ = nullptr;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ScopedFramebufferBinder);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_GL_SCOPED_FRAMEBUFFER_BINDER_H_\n"
  },
  {
    "path": "clay/common/graphics/gl/scoped_texture_binder.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\ntypedef unsigned int GLenum;\n#define GL_TEXTURE_2D 0x0DE1\n#define GL_TEXTURE_BINDING_2D 0x8069\n#define GL_TEXTURE_RECTANGLE 0x84F5\n#define GL_TEXTURE_BINDING_RECTANGLE 0x84F6\n#define GL_TEXTURE_EXTERNAL_OES 0x8D65\n#define GL_TEXTURE_BINDING_EXTERNAL_OES 0x8D67\n#define GL_TEXTURE_CUBE_MAP 0x8513\n#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514\n\nScopedTextureBinder::ScopedTextureBinder(unsigned int target, unsigned int id,\n                                         GlApi* gl_api)\n    : target_(target), old_id_(-1), gl_api_(gl_api) {\n  if (!gl_api_) {\n    FML_LOG(ERROR) << \"Not get valid GLProc for ScopedTextureBinder\";\n    return;\n  }\n  GLenum target_getter = 0;\n  switch (target) {\n    case GL_TEXTURE_2D:\n      target_getter = GL_TEXTURE_BINDING_2D;\n      break;\n    case GL_TEXTURE_RECTANGLE:\n      target_getter = GL_TEXTURE_BINDING_RECTANGLE;\n      break;\n    case GL_TEXTURE_CUBE_MAP:\n      target_getter = GL_TEXTURE_BINDING_CUBE_MAP;\n      break;\n    case GL_TEXTURE_EXTERNAL_OES:\n      target_getter = GL_TEXTURE_BINDING_EXTERNAL_OES;\n      break;\n    default:\n      FML_LOG(ERROR) << \"Target not supported. \" << target;\n  }\n  gl_api_->GlGetIntegerv(target_getter, &old_id_);\n  gl_api_->GlBindTexture(target_, id);\n}\n\nScopedTextureBinder::~ScopedTextureBinder() {\n  gl_api_->GlBindTexture(target_, old_id_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/gl/scoped_texture_binder.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_GL_SCOPED_TEXTURE_BINDER_H_\n#define CLAY_COMMON_GRAPHICS_GL_SCOPED_TEXTURE_BINDER_H_\n\n#include <cstdint>\n\n#include \"base/include/fml/macros.h\"\n#include \"build/build_config.h\"\n#include \"clay/common/graphics/gl/gl_api.h\"\n#include \"clay/common/graphics/gl/gl_api_common.h\"\n\nnamespace clay {\n\nclass ScopedTextureBinder {\n public:\n  ScopedTextureBinder(unsigned int target, unsigned int id,\n                      GlApi* gl_api = GlApiCommon::GetInstance());  // NOLINT\n\n  ~ScopedTextureBinder();\n\n private:\n  // Failing that we use GL calls to save and restore state.\n  int target_;\n  int old_id_;\n  GlApi* gl_api_ = nullptr;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ScopedTextureBinder);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_GL_SCOPED_TEXTURE_BINDER_H_\n"
  },
  {
    "path": "clay/common/graphics/gl_context_switch.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/gl_context_switch.h\"\n\n#include <utility>\n\nnamespace clay {\n\nSwitchableGLContext::SwitchableGLContext() = default;\n\nSwitchableGLContext::~SwitchableGLContext() = default;\n\nGLContextResult::GLContextResult() = default;\n\nGLContextResult::~GLContextResult() = default;\n\nGLContextResult::GLContextResult(bool static_result) : result_(static_result) {}\n\nbool GLContextResult::GetResult() { return result_; }\n\nGLContextDefaultResult::GLContextDefaultResult(bool static_result)\n    : GLContextResult(static_result) {}\n\nGLContextDefaultResult::~GLContextDefaultResult() = default;\n\nGLContextSwitch::GLContextSwitch(std::unique_ptr<SwitchableGLContext> context)\n    : context_(std::move(context)) {\n  FML_DCHECK(context_ != nullptr);\n  result_ = context_->SetCurrent();\n}\n\nGLContextSwitch::~GLContextSwitch() { context_->RemoveCurrent(); }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/gl_context_switch.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_GL_CONTEXT_SWITCH_H_\n#define CLAY_COMMON_GRAPHICS_GL_CONTEXT_SWITCH_H_\n\n#include <functional>\n#include <memory>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\n// This interface represents a gl context that can be switched by\n// |GLContextSwitch|.\n//\n// The implementation should wrap a \"Context\" object inside this class. For\n// example, in iOS while using a GL rendering surface, the implementation should\n// wrap an |EAGLContext|.\nclass SwitchableGLContext {\n public:\n  SwitchableGLContext();\n\n  virtual ~SwitchableGLContext();\n\n  // Implement this to set the context wrapped by this |SwitchableGLContext|\n  // object to the current context.\n  virtual bool SetCurrent() = 0;\n\n  // Implement this to remove the context wrapped by this |SwitchableGLContext|\n  // object from current context;\n  virtual bool RemoveCurrent() = 0;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SwitchableGLContext);\n};\n\n// Represents the result of setting a gl context.\n//\n// This class exists because context results are used in places applies to all\n// the platforms. On certain platforms(for example lower end iOS devices that\n// uses gl), a |GLContextSwitch| is required to protect flutter's gl context\n// from being polluted by other programs(embedded platform views). A\n// |GLContextSwitch| is a subclass of |GLContextResult|, which can be returned\n// on platforms that requires context switching. A |GLContextDefaultResult| is\n// also a subclass of |GLContextResult|, which can be returned on platforms\n// that doesn't require context switching.\nclass GLContextResult {\n public:\n  GLContextResult();\n  virtual ~GLContextResult();\n\n  //----------------------------------------------------------------------------\n  // Returns true if the gl context is set successfully.\n  bool GetResult();\n\n protected:\n  explicit GLContextResult(bool static_result);\n  bool result_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(GLContextResult);\n};\n\n//------------------------------------------------------------------------------\n/// The default implementation of |GLContextResult|.\n///\n/// Use this class on platforms that doesn't require gl context switching.\n/// * See also |GLContextSwitch| if the platform requires gl context switching.\nclass GLContextDefaultResult : public GLContextResult {\n public:\n  //----------------------------------------------------------------------------\n  /// Constructs a |GLContextDefaultResult| with a static result.\n  ///\n  /// Used this on platforms that doesn't require gl context switching. (For\n  /// example, metal on iOS)\n  ///\n  /// @param  static_result a static value that will be returned from\n  /// |GetResult|\n  explicit GLContextDefaultResult(bool static_result);\n\n  ~GLContextDefaultResult() override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(GLContextDefaultResult);\n};\n\n//------------------------------------------------------------------------------\n/// Switches the gl context to the a context that is passed in the\n/// constructor.\n///\n/// In destruction, it should restore the current context to what was\n/// before the construction of this switch.\nclass GLContextSwitch final : public GLContextResult {\n public:\n  //----------------------------------------------------------------------------\n  /// Constructs a |GLContextSwitch|.\n  ///\n  /// @param  context The context that is going to be set as the current\n  /// context. The |GLContextSwitch| should not outlive the owner of the gl\n  /// context wrapped inside the `context`.\n  explicit GLContextSwitch(std::unique_ptr<SwitchableGLContext> context);\n\n  ~GLContextSwitch() override;\n\n private:\n  std::unique_ptr<SwitchableGLContext> context_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(GLContextSwitch);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_GL_CONTEXT_SWITCH_H_\n"
  },
  {
    "path": "clay/common/graphics/graphic_meminfo.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_GRAPHIC_MEMINFO_H_\n#define CLAY_COMMON_GRAPHICS_GRAPHIC_MEMINFO_H_\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\nstruct RasterCacheInfo {\n  RasterCacheInfo(int64_t single_cache_size, int64_t single_cache_height,\n                  int64_t single_cache_width,\n                  std::string single_cache_color_type, int64_t layer_address,\n                  int64_t cache_address, clay::GrImagePtr image)\n      : single_cache_size(single_cache_size),\n        single_cache_height(single_cache_height),\n        single_cache_width(single_cache_width),\n        single_cache_color_type(single_cache_color_type),\n        layer_address(layer_address),\n        cache_address(cache_address),\n        image(image) {}\n  int64_t single_cache_size;\n  int64_t single_cache_height;\n  int64_t single_cache_width;\n  std::string single_cache_color_type;\n  int64_t layer_address;\n  int64_t cache_address;\n  clay::GrImagePtr image;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_GRAPHIC_MEMINFO_H_\n"
  },
  {
    "path": "clay/common/graphics/msaa_sample_count.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_MSAA_SAMPLE_COUNT_H_\n#define CLAY_COMMON_GRAPHICS_MSAA_SAMPLE_COUNT_H_\n\n// Supported MSAA sample count values.\nenum class MsaaSampleCount {\n  kNone = 1,\n  kTwo = 2,\n  kFour = 4,\n  kEight = 8,\n  kSixteen = 16,\n};\n\n#endif  // CLAY_COMMON_GRAPHICS_MSAA_SAMPLE_COUNT_H_\n"
  },
  {
    "path": "clay/common/graphics/persistent_cache.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/persistent_cache.h\"\n\n#include <cstdlib>\n#include <cstring>\n#include <future>\n#include <memory>\n#include <string>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/common/sha1.h\"\n#include \"clay/fml/base32.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/fml/paths.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/version/version.h\"\n#include \"third_party/rapidjson/document.h\"\n\nnamespace clay {\n\nstd::string PersistentCache::cache_base_path_;  // NOLINT(runtime/string)\n\nstd::mutex PersistentCache::instance_mutex_;\nstd::unique_ptr<PersistentCache> PersistentCache::gPersistentCache;\n\nbool PersistentCache::gIsReadOnly = false;\n\nstd::atomic<bool> PersistentCache::cache_sksl_ = false;\nstd::atomic<bool> PersistentCache::strategy_set_ = false;\n\nvoid PersistentCache::SetCacheSkSL(bool value) {\n  if (strategy_set_ && value != cache_sksl_) {\n    FML_DLOG(ERROR) << \"Cache SkSL can only be set before the \"\n                       \"GrContextOptions::fShaderCacheStrategy is set.\";\n    return;\n  }\n  cache_sksl_ = value;\n}\n\nPersistentCache* PersistentCache::GetCacheForProcess() {\n  std::scoped_lock lock(instance_mutex_);\n  if (gPersistentCache == nullptr) {\n    gPersistentCache.reset(new PersistentCache(gIsReadOnly));\n  }\n  return gPersistentCache.get();\n}\n\nvoid PersistentCache::ResetCacheForProcess() {\n  std::scoped_lock lock(instance_mutex_);\n  gPersistentCache.reset(new PersistentCache(gIsReadOnly));\n  strategy_set_ = false;\n}\n\nvoid PersistentCache::SetCacheDirectoryPath(std::string path) {\n  cache_base_path_ = std::move(path);\n}\n\nbool PersistentCache::Purge() {\n  // Make sure that this is called after the worker task runner setup so all the\n  // file system modifications would happen on that single thread to avoid\n  // racing.\n  FML_DCHECK(GetWorkerTaskRunner());\n\n  std::promise<bool> removed;\n  GetWorkerTaskRunner()->PostTask(\n      [&removed, cache_directory = cache_directory_]() {\n        if (cache_directory->is_valid()) {\n          // Only remove files but not directories.\n          FML_LOG(INFO) << \"Purge persistent cache.\";\n          fml::FileVisitor delete_file = [](const fml::UniqueFD& directory,\n                                            const std::string& filename) {\n            // Do not delete directories. Return true to continue with other\n            // files.\n            if (fml::IsDirectory(directory, filename.c_str())) {\n              return true;\n            }\n            return fml::UnlinkFile(directory, filename.c_str());\n          };\n          removed.set_value(\n              fml::VisitFilesRecursively(*cache_directory, delete_file));\n        } else {\n          removed.set_value(false);\n        }\n      });\n  return removed.get_future().get();\n}\n\nnamespace {\n\nconstexpr char kEngineComponent[] = \"clay_engine\";\n\nstatic void FreeOldCacheDirectory(const fml::UniqueFD& cache_base_dir) {\n  fml::UniqueFD engine_dir =\n      fml::OpenDirectoryReadOnly(cache_base_dir, kEngineComponent);\n  if (!engine_dir.is_valid()) {\n    return;\n  }\n  fml::VisitFiles(engine_dir, [](const fml::UniqueFD& directory,\n                                 const std::string& filename) {\n    if (filename != GetEngineVersion()) {\n      auto dir = fml::OpenDirectory(directory, filename.c_str(), false,\n                                    fml::FilePermission::kReadWrite);\n      if (dir.is_valid()) {\n        fml::RemoveDirectoryRecursively(directory, filename.c_str());\n      }\n    }\n    return true;\n  });\n}\n\nstatic std::shared_ptr<fml::UniqueFD> MakeCacheDirectory(\n    const std::string& global_cache_base_path, bool read_only,\n    bool cache_sksl) {\n  fml::UniqueFD cache_base_dir;\n  if (global_cache_base_path.length()) {\n    cache_base_dir = fml::OpenDirectory(global_cache_base_path.c_str(), false,\n                                        fml::FilePermission::kRead);\n  } else {\n    cache_base_dir = fml::paths::GetCachesDirectory();\n  }\n\n  if (cache_base_dir.is_valid()) {\n    FreeOldCacheDirectory(cache_base_dir);\n    std::vector<std::string> components = {kEngineComponent, GetEngineVersion(),\n                                           \"skia\", GetSkiaVersion()};\n    if (cache_sksl) {\n      components.push_back(PersistentCache::kSkSLSubdirName);\n    }\n    return std::make_shared<fml::UniqueFD>(\n        CreateDirectory(cache_base_dir, components,\n                        read_only ? fml::FilePermission::kRead\n                                  : fml::FilePermission::kReadWrite));\n  } else {\n    return std::make_shared<fml::UniqueFD>();\n  }\n}\n}  // namespace\n\n#ifndef ENABLE_SKITY\nstd::string PersistentCache::SkKeyToFilePath(const SkData& key) {\n  return clay::SHA1HashBytes(key.data(), key.size());\n}\n\nsk_sp<SkData> ParseBase32(const std::string& input) {\n  std::pair<bool, std::string> decode_result = fml::Base32Decode(input);\n  if (!decode_result.first) {\n    FML_LOG(ERROR) << \"Base32 can't decode: \" << input;\n    return nullptr;\n  }\n  const std::string& data_string = decode_result.second;\n  return SkData::MakeWithCopy(data_string.data(), data_string.length());\n}\n\nsize_t PersistentCache::PrecompileKnownSkSLs(GrDirectContext* context) const {\n  // clang-tidy has trouble reasoning about some of the complicated array and\n  // pointer-arithmetic code in rapidjson.\n  // NOLINTNEXTLINE(clang-analyzer-cplusplus.PlacementNew)\n  auto known_sksls = LoadSkSLs();\n  // A trace must be present even if no pre-compilations have been completed.\n  TRACE_EVENT(\"flutter\", \"PersistentCache::PrecompileKnownSkSLs\", \"count\",\n              known_sksls.size());\n\n  if (context == nullptr) {\n    return 0;\n  }\n\n  size_t precompiled_count = 0;\n  for (const auto& sksl : known_sksls) {\n    TRACE_EVENT(\"flutter\", \"PrecompilingSkSL\");\n    if (context->precompileShader(*sksl.key, *sksl.value)) {\n      precompiled_count++;\n    }\n  }\n\n  TRACE_COUNTER(\"flutter\", \"PersistentCache::PrecompiledSkSLs.Successful\",\n                precompiled_count);\n  return precompiled_count;\n}\n\nstd::vector<PersistentCache::SkSLCache> PersistentCache::LoadSkSLs() const {\n  TRACE_EVENT(\"flutter\", \"PersistentCache::LoadSkSLs\");\n  std::vector<PersistentCache::SkSLCache> result;\n  fml::FileVisitor visitor = [&result](const fml::UniqueFD& directory,\n                                       const std::string& filename) {\n    SkSLCache cache = LoadFile(directory, filename, true);\n    if (cache.key != nullptr && cache.value != nullptr) {\n      result.push_back(cache);\n    } else {\n      FML_LOG(ERROR) << \"Failed to load: \" << filename;\n    }\n    return true;\n  };\n\n  // Only visit sksl_cache_directory_ if this persistent cache is valid.\n  // However, we'd like to continue visit the asset dir even if this persistent\n  // cache is invalid.\n  if (IsValid()) {\n    // In case `rewinddir` doesn't work reliably, load SkSLs from a freshly\n    // opened directory (https://github.com/flutter/flutter/issues/65258).\n    fml::UniqueFD fresh_dir =\n        fml::OpenDirectoryReadOnly(*cache_directory_, kSkSLSubdirName);\n    if (fresh_dir.is_valid()) {\n      fml::VisitFiles(fresh_dir, visitor);\n    }\n  }\n  return result;\n}\n\nPersistentCache::SkSLCache PersistentCache::LoadFile(\n    const fml::UniqueFD& dir, const std::string& file_name, bool need_key) {\n  SkSLCache result;\n  auto file = fml::OpenFileReadOnly(dir, file_name.c_str());\n  if (!file.is_valid()) {\n    return result;\n  }\n  auto mapping = std::make_unique<fml::FileMapping>(file);\n  if (mapping->GetSize() < sizeof(CacheObjectHeader)) {\n    return result;\n  }\n  const CacheObjectHeader* header =\n      reinterpret_cast<const CacheObjectHeader*>(mapping->GetMapping());\n  if (header->signature != CacheObjectHeader::kSignature ||\n      header->version != CacheObjectHeader::kVersion1) {\n    FML_LOG(INFO) << \"Persistent cache header is corrupt: \" << file_name;\n    return result;\n  }\n  if (mapping->GetSize() < sizeof(CacheObjectHeader) + header->key_size) {\n    FML_LOG(INFO) << \"Persistent cache size is corrupt: \" << file_name;\n    return result;\n  }\n  if (need_key) {\n    result.key = SkData::MakeWithCopy(\n        mapping->GetMapping() + sizeof(CacheObjectHeader), header->key_size);\n  }\n  size_t value_offset = sizeof(CacheObjectHeader) + header->key_size;\n  result.value = SkData::MakeWithCopy(mapping->GetMapping() + value_offset,\n                                      mapping->GetSize() - value_offset);\n  return result;\n}\n\n// |GrContextOptions::PersistentCache|\nsk_sp<SkData> PersistentCache::load(const SkData& key) {\n  TRACE_EVENT(\"flutter\", \"PersistentCacheLoad\");\n  if (!IsValid()) {\n    return nullptr;\n  }\n  auto file_name = SkKeyToFilePath(key);\n  if (file_name.empty()) {\n    return nullptr;\n  }\n  auto result =\n      PersistentCache::LoadFile(*sksl_cache_directory_, file_name, false).value;\n  if (result != nullptr) {\n    TRACE_EVENT(\"flutter\", \"PersistentCacheLoadHit\");\n  }\n  return result;\n}\n\nstd::unique_ptr<fml::MallocMapping> PersistentCache::BuildCacheObject(\n    const SkData& key, const SkData& data) {\n  size_t total_size = sizeof(CacheObjectHeader) + key.size() + data.size();\n  uint8_t* mapping_buf = reinterpret_cast<uint8_t*>(malloc(total_size));\n  if (!mapping_buf) {\n    return nullptr;\n  }\n  auto mapping = std::make_unique<fml::MallocMapping>(mapping_buf, total_size);\n\n  CacheObjectHeader header(key.size());\n  memcpy(mapping_buf, &header, sizeof(CacheObjectHeader));\n  mapping_buf += sizeof(CacheObjectHeader);\n  memcpy(mapping_buf, key.data(), key.size());\n  mapping_buf += key.size();\n  memcpy(mapping_buf, data.data(), data.size());\n\n  return mapping;\n}\n\nstatic void PersistentCacheStore(\n    const fml::RefPtr<fml::TaskRunner>& worker,\n    const std::shared_ptr<fml::UniqueFD>& cache_directory, std::string key,\n    std::unique_ptr<fml::Mapping> value) {\n  // The static leak checker gets confused by the use of fml::MakeCopyable.\n  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)\n  auto task = fml::MakeCopyable([cache_directory,             //\n                                 file_name = std::move(key),  //\n                                 mapping = std::move(value)   //\n  ]() mutable {\n    TRACE_EVENT(\"flutter\", \"PersistentCacheStore\");\n    if (!fml::WriteAtomically(*cache_directory,   //\n                              file_name.c_str(),  //\n                              *mapping)           //\n    ) {\n      FML_LOG(WARNING) << \"Could not write cache contents to persistent store.\";\n    }\n  });\n\n  if (!worker) {\n    FML_DLOG(WARNING)\n        << \"The persistent cache has no available workers. Performing the task \"\n           \"on the current thread. This slow operation is going to occur on a \"\n           \"frame workload.\";\n    task();\n  } else {\n    worker->PostTask(std::move(task));\n  }\n}\n\n// |GrContextOptions::PersistentCache|\nvoid PersistentCache::store(const SkData& key, const SkData& data) {\n  stored_new_shaders_ = true;\n\n  if (is_read_only_) {\n    return;\n  }\n\n  if (!IsValid()) {\n    return;\n  }\n\n  auto file_name = SkKeyToFilePath(key);\n\n  if (file_name.empty()) {\n    return;\n  }\n\n  std::unique_ptr<fml::MallocMapping> mapping = BuildCacheObject(key, data);\n  if (!mapping) {\n    return;\n  }\n  PersistentCacheStore(GetWorkerTaskRunner(), sksl_cache_directory_,\n                       std::move(file_name), std::move(mapping));\n}\n\nvoid PersistentCache::DumpSkp(const SkData& data) {\n  if (is_read_only_ || !IsValid()) {\n    FML_DLOG(ERROR)\n        << \"Could not dump SKP from read-only or invalid persistent \"\n           \"cache.\";\n    return;\n  }\n\n  std::stringstream name_stream;\n  auto ticks = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();\n  name_stream << \"shader_dump_\" << std::to_string(ticks) << \".skp\";\n  std::string file_name = name_stream.str();\n  FML_DLOG(INFO) << \"Dumping \" << file_name;\n  auto mapping = std::make_unique<fml::DataMapping>(\n      std::vector<uint8_t>{data.bytes(), data.bytes() + data.size()});\n  PersistentCacheStore(GetWorkerTaskRunner(), cache_directory_,\n                       std::move(file_name), std::move(mapping));\n}\n#endif  // ENABLE_SKITY\n\nPersistentCache::PersistentCache(bool read_only)\n    : is_read_only_(read_only),\n      cache_directory_(MakeCacheDirectory(cache_base_path_, read_only, false)),\n      sksl_cache_directory_(\n          MakeCacheDirectory(cache_base_path_, read_only, true)),\n      resource_cache_(new ResourceCache) {\n  if (!IsValid()) {\n    FML_DLOG(WARNING) << \"Could not acquire the persistent cache directory. \"\n                         \"Caching of GPU resources on disk is disabled.\";\n  }\n}\n\nPersistentCache::~PersistentCache() = default;\n\nbool PersistentCache::IsValid() const {\n  return cache_directory_ && cache_directory_->is_valid();\n}\n\nvoid PersistentCache::AddWorkerTaskRunner(\n    const fml::RefPtr<fml::TaskRunner>& task_runner) {\n  std::scoped_lock lock(worker_task_runners_mutex_);\n  worker_task_runners_.insert(task_runner);\n}\n\nvoid PersistentCache::RemoveWorkerTaskRunner(\n    const fml::RefPtr<fml::TaskRunner>& task_runner) {\n  std::scoped_lock lock(worker_task_runners_mutex_);\n  auto found = worker_task_runners_.find(task_runner);\n  if (found != worker_task_runners_.end()) {\n    worker_task_runners_.erase(found);\n  }\n}\n\nfml::RefPtr<fml::TaskRunner> PersistentCache::GetWorkerTaskRunner() const {\n  fml::RefPtr<fml::TaskRunner> worker;\n\n  std::scoped_lock lock(worker_task_runners_mutex_);\n  if (!worker_task_runners_.empty()) {\n    worker = *worker_task_runners_.begin();\n  }\n\n  return worker;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/persistent_cache.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_PERSISTENT_CACHE_H_\n#define CLAY_COMMON_GRAPHICS_PERSISTENT_CACHE_H_\n\n#include <memory>\n#include <mutex>\n#include <set>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/unique_fd.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#ifndef ENABLE_SKITY\n#include \"third_party/skia/include/core/SkData.h\"\n#endif  // ENABLE_SKITY\n\nclass GrDirectContext;\n\nnamespace clay {\n\nnamespace testing {\nclass ShellTest;\n}\n\n/// A cache of SkData that gets stored to disk.\n///\n/// This is mainly used for Shaders but is also written to by Dart.  It is\n/// thread-safe for reading and writing from multiple threads.\nclass PersistentCache {\n public:\n#ifndef ENABLE_SKITY\n  class ResourceCache : public GrContextOptions::PersistentCache {\n   public:\n    sk_sp<SkData> load(const SkData& key) override {\n      return GetCacheForProcess()->load(key);\n    }\n    void store(const SkData& key, const SkData& data) override {\n      GetCacheForProcess()->store(key, data);\n    }\n  };\n#else\n  class ResourceCache {};\n#endif  // ENABLE_SKITY\n  // Mutable static switch that can be set before GetCacheForProcess. If true,\n  // we'll only read existing caches but not generate new ones. Some clients\n  // (e.g., embedded devices) prefer generating persistent cache files for the\n  // specific device beforehand, and ship them as readonly files in OTA\n  // packages.\n  static bool gIsReadOnly;\n\n  static PersistentCache* GetCacheForProcess();\n  static void ResetCacheForProcess();\n\n  // This must be called before |GetCacheForProcess|. Otherwise, it won't\n  // affect the cache directory returned by |GetCacheForProcess|.\n  static void SetCacheDirectoryPath(std::string path);\n\n#ifndef ENABLE_SKITY\n  // Convert a binary SkData key into a Base32 encoded string.\n  //\n  // This is used to specify persistent cache filenames and service protocol\n  // json keys.\n  static std::string SkKeyToFilePath(const SkData& key);\n\n  // Allocate a MallocMapping containing the given key and value in the file\n  // format used by the cache.\n  static std::unique_ptr<fml::MallocMapping> BuildCacheObject(\n      const SkData& key, const SkData& data);\n#endif  // ENABLE_SKITY\n\n  // Header written into the files used to store cached Skia objects.\n  struct CacheObjectHeader {\n    // A prefix used to identify the cache object file format.\n    static const uint32_t kSignature = 0xA869593F;\n    static const uint32_t kVersion1 = 1;\n\n    explicit CacheObjectHeader(uint32_t p_key_size) : key_size(p_key_size) {}\n\n    uint32_t signature = kSignature;\n    uint32_t version = kVersion1;\n    uint32_t key_size;\n  };\n\n  ~PersistentCache();\n\n  void AddWorkerTaskRunner(const fml::RefPtr<fml::TaskRunner>& task_runner);\n\n  void RemoveWorkerTaskRunner(const fml::RefPtr<fml::TaskRunner>& task_runner);\n\n  // Whether Skia tries to store any shader into this persistent cache after\n  // |ResetStoredNewShaders| is called. This flag is usually reset before each\n  // frame so we can know if Skia tries to compile new shaders in that frame.\n  bool StoredNewShaders() const { return stored_new_shaders_; }\n  void ResetStoredNewShaders() { stored_new_shaders_ = false; }\n  bool IsDumpingSkp() const { return is_dumping_skp_; }\n  void SetIsDumpingSkp(bool value) { is_dumping_skp_ = value; }\n\n  // Remove all files inside the persistent cache directory.\n  // Return whether the purge is successful.\n  bool Purge();\n#ifndef ENABLE_SKITY\n  void DumpSkp(const SkData& data);\n  // |GrContextOptions::PersistentCache|\n  sk_sp<SkData> load(const SkData& key);\n\n  struct SkSLCache {\n    sk_sp<SkData> key;\n    sk_sp<SkData> value;\n  };\n\n  /// Load all the SkSL shader caches in the right directory.\n  std::vector<SkSLCache> LoadSkSLs() const;\n#endif  // ENABLE_SKITY\n\n#ifndef ENABLE_SKITY\n  //----------------------------------------------------------------------------\n  /// @brief      Precompile SkSLs packaged with the application and gathered\n  ///             during previous runs in the given context.\n  ///\n  /// @warning    The context must be the rendering context. This context may be\n  ///             destroyed during application suspension and subsequently\n  ///             recreated. The SkSLs must be precompiled again in the new\n  ///             context.\n  ///\n  /// @param      context  The rendering context to precompile shaders in.\n  ///\n  /// @return     The number of SkSLs precompiled.\n  ///\n  size_t PrecompileKnownSkSLs(GrDirectContext* context) const;\n#endif  // ENABLE_SKITY\n\n  static bool cache_sksl() { return cache_sksl_; }\n\n  static void SetCacheSkSL(bool value);\n\n  static void MarkStrategySet() { strategy_set_ = true; }\n\n  static constexpr char kSkSLSubdirName[] = \"sksl\";\n  static constexpr char kAssetFileName[] = \"io.flutter.shaders.json\";\n\n  const std::shared_ptr<fml::UniqueFD> cache_directory() const {\n    return cache_directory_;\n  }\n\n  ResourceCache* GetResourceCache() { return resource_cache_.get(); }\n\n private:\n  static std::string cache_base_path_;\n\n  static std::mutex instance_mutex_;\n  static std::unique_ptr<PersistentCache> gPersistentCache;\n\n  // Mutable static switch that can be set before GetCacheForProcess is called\n  // and GrContextOptions.fShaderCacheStrategy is set. If true, it means that\n  // we'll set `GrContextOptions::fShaderCacheStrategy` to `kSkSL`, and all the\n  // persistent cache should be stored and loaded from the \"sksl\" directory.\n  static std::atomic<bool> cache_sksl_;\n\n  // Guard flag to make sure that cache_sksl_ is not modified after\n  // strategy_set_ becomes true.\n  static std::atomic<bool> strategy_set_;\n\n  const bool is_read_only_;\n  const std::shared_ptr<fml::UniqueFD> cache_directory_;\n  const std::shared_ptr<fml::UniqueFD> sksl_cache_directory_;\n  mutable std::mutex worker_task_runners_mutex_;\n  std::multiset<fml::RefPtr<fml::TaskRunner>> worker_task_runners_;\n  std::unique_ptr<ResourceCache> resource_cache_;\n\n  bool stored_new_shaders_ = false;\n  bool is_dumping_skp_ = false;\n\n#ifndef ENABLE_SKITY\n  static SkSLCache LoadFile(const fml::UniqueFD& dir,\n                            const std::string& file_name, bool need_key);\n\n  // |GrContextOptions::PersistentCache|\n  void store(const SkData& key, const SkData& data);\n#endif  // ENABLE_SKITY\n\n  bool IsValid() const;\n\n  explicit PersistentCache(bool read_only = false);\n\n  fml::RefPtr<fml::TaskRunner> GetWorkerTaskRunner() const;\n\n  friend class testing::ShellTest;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PersistentCache);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_PERSISTENT_CACHE_H_\n"
  },
  {
    "path": "clay/common/graphics/screenshot.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_SCREENSHOT_H_\n#define CLAY_COMMON_GRAPHICS_SCREENSHOT_H_\n\n#include <functional>\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nusing ExternalScreenshotCallback = std::function<GrImagePtr()>;\n\nstruct ScreenMetadata {\n  float device_width_ = 0.f;\n  float device_height_ = 0.f;\n  float timestamp_ = 0.f;\n  float page_scale_factor_ = 1.f;\n};\n\nusing ScreenshotCallback =\n    std::function<void(GrDataPtr, const ScreenMetadata&)>;\n\nenum class ScreenshotType { JPEG = 0, PNG, WEBP, BITMAP };\nstruct ScreenshotRequest {\n  size_t page_width_ = 0;\n  size_t page_height_ = 0;\n  size_t max_width_ = 0;\n  size_t max_height_ = 0;\n  int quality_ = 100;\n  ScreenshotType type_ = ScreenshotType::BITMAP;\n  float screen_scale_factor_ = 1.f;\n  // If `is_sync` is false, then the `callback_` is necessary.\n  bool is_sync_ = true;\n  lynx::fml::RefPtr<lynx::fml::TaskRunner> task_runner_;\n  std::optional<ScreenshotCallback> callback_;\n  std::string format_;\n  int every_nth_frame_;\n  uint32_t background_color_ = 0xffffffff;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_SCREENSHOT_H_\n"
  },
  {
    "path": "clay/common/graphics/shared_drawable_image.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/shared_drawable_image.h\"\n\n#include <algorithm>\n\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_sink_accessor.h\"\n\nnamespace clay {\n\nSharedDrawableImage::SharedDrawableImage(\n    fml::RefPtr<SharedImageSink> image_sink)\n    : image_sink_(image_sink) {}\n\nSharedDrawableImage::~SharedDrawableImage() = default;\n\n// |clay::DrawableImage|\n// Called on platform thread.\nvoid SharedDrawableImage::SetFrameAvailableCallback(\n    const fml::closure& callback) {\n  image_sink_->SetFrameAvailableCallback(callback);\n}\n\n// |clay::raster|\n// Called on raster thread.\nvoid SharedDrawableImage::MarkNewFrameAvailable() { frame_produce_cnt_++; }\n\n// |clay::raster|\n// Called on raster thread.\nvoid SharedDrawableImage::OnDrawableImageUnregistered() {\n  image_sink_->SetFrameAvailableCallback(nullptr);\n}\n\n// |clay::ContextListener|\n// Called from raster thread.\nvoid SharedDrawableImage::OnGrContextCreated() {\n  state_ = AttachmentState::uninitialized;\n}\n\n// |clay::ContextListener|\n// Called from raster thread.\nvoid SharedDrawableImage::OnGrContextDestroyed() {\n  state_ = AttachmentState::detached;\n  accessor_ = nullptr;\n}\n\n// Called from raster thread.\nbool SharedDrawableImage::EnsureAttached() {\n  if (state_ == AttachmentState::detached) {\n    return false;\n  }\n\n  if (state_ == AttachmentState::uninitialized) {\n    accessor_ = std::make_unique<SharedImageSinkAccessor>(\n        image_sink_,\n        [this](fml::RefPtr<SharedImageBacking> backing)\n            -> fml::RefPtr<SharedImageRepresentation> {\n#ifndef ENABLE_SKITY\n          return backing->CreateSkiaRepresentation(GetGrContext());\n#else\n          return backing->CreateSkityRepresentation(GetGrContext());\n#endif  // ENABLE_SKITY\n        });\n    state_ = AttachmentState::attached;\n  }\n  return true;\n}\n\n// Called from raster thread.\nvoid SharedDrawableImage::AdvanceFrameConsumption(bool freeze) {\n  if (image_sink_->Capacity() == 1) {\n    [[maybe_unused]] bool update_success = Update();\n  } else if (!freeze) {\n    // TODO(youfeng) request a raster frame instead drain all images\n    while (frame_consume_cnt_ < frame_produce_cnt_) {\n      if (!Update()) {\n        break;\n      }\n      frame_consume_cnt_++;\n    }\n  }\n}\n\n// Called from raster thread.\nbool SharedDrawableImage::Update() {\n  fml::RefPtr<SharedImageRepresentation> repr = accessor_->UpdateFront();\n  if (!repr) {\n    return false;\n  }\n\n#ifndef ENABLE_SKITY\n  FML_DCHECK(repr->GetType() == ImageRepresentationType::kSkia);\n  sk_image_ = static_cast<SkiaImageRepresentation*>(repr.get())->GetSkImage();\n  if (!sk_image_) {\n    return false;\n  }\n#else\n  FML_DCHECK(repr->GetType() == ImageRepresentationType::kSkity);\n  skity_image_ =\n      static_cast<SkityImageRepresentation*>(repr.get())->GetSkityImage();\n  if (!skity_image_) {\n    return false;\n  }\n#endif  // ENABLE_SKITY\n  transform_ = repr->GetBacking()->GetTransformation();\n\n  return true;\n}\n\n#ifndef ENABLE_SKITY\n// Called from raster thread.\nvoid SharedDrawableImage::DrawSkiaImage(sk_sp<SkImage> sk_image,\n                                        PaintContext& context,\n                                        const skity::Rect& bounds,\n                                        const GrSamplingOptions& sampling,\n                                        FitMode fit_mode) {\n  SkAutoCanvasRestore autoRestore(context.canvas, true);\n  context.canvas->translate(bounds.X(), bounds.Y());\n  SkRect dst_rect;\n  switch (fit_mode) {\n    case clay::DrawableImage::FitMode::kClipToBounds:\n      dst_rect = SkRect::MakeWH(\n          std::min(static_cast<int>(bounds.Width()), sk_image->width()),\n          std::min(static_cast<int>(bounds.Height()), sk_image->height()));\n      break;\n    case clay::DrawableImage::FitMode::kScaleToFill:\n      dst_rect = SkRect::MakeWH(bounds.Width(), bounds.Height());\n      break;\n  }\n\n  if (!transform_.IsIdentity()) {\n    SkMatrix scaled_transform;\n    switch (fit_mode) {\n      case clay::DrawableImage::FitMode::kScaleToFill:\n        scaled_transform =\n            SkMatrix::Scale(dst_rect.width(), dst_rect.height()) *\n            ConvertSkityMatrixToSkMatrix(transform_) *\n            SkMatrix::Scale(1.0 / sk_image->width(), 1.0 / sk_image->height());\n        break;\n      case clay::DrawableImage::FitMode::kClipToBounds:\n        scaled_transform =\n            SkMatrix::Scale(sk_image->width(), sk_image->height()) *\n            ConvertSkityMatrixToSkMatrix(transform_) *\n            SkMatrix::Scale(1.0 / sk_image->width(), 1.0 / sk_image->height());\n        break;\n    }\n    sk_sp<SkShader> shader = sk_image->makeShader(\n        SkTileMode::kRepeat, SkTileMode::kRepeat, sampling, scaled_transform);\n\n    SkPaint paint_with_shader;\n    if (context.sk_paint) {\n      paint_with_shader = *context.sk_paint;\n    }\n    paint_with_shader.setShader(shader);\n    context.canvas->drawRect(dst_rect, paint_with_shader);\n  } else {\n    SkRect image_src_rect;\n    switch (fit_mode) {\n      case clay::DrawableImage::FitMode::kClipToBounds:\n        image_src_rect = dst_rect;\n        break;\n      case clay::DrawableImage::FitMode::kScaleToFill:\n        image_src_rect = SkRect::MakeWH(sk_image->width(), sk_image->height());\n        break;\n    }\n    context.canvas->drawImageRect(\n        sk_image_, image_src_rect, dst_rect, sampling, context.sk_paint,\n        SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint);\n  }\n}\n#else\n// Called from raster thread.\nvoid SharedDrawableImage::DrawSkityImage(\n    std::shared_ptr<skity::Image> skity_image, PaintContext& context,\n    const skity::Rect& bounds, const GrSamplingOptions& sampling,\n    FitMode fit_mode) {\n  context.canvas->Save();\n  context.canvas->Translate(bounds.X(), bounds.Y());\n  skity::Rect dst_rect;\n  switch (fit_mode) {\n    case clay::DrawableImage::FitMode::kClipToBounds:\n      dst_rect = skity::Rect::MakeWH(\n          std::min(static_cast<size_t>(bounds.Width()), skity_image->Width()),\n          std::min(static_cast<size_t>(bounds.Height()),\n                   skity_image->Height()));\n      break;\n    case clay::DrawableImage::FitMode::kScaleToFill:\n      dst_rect = skity::Rect::MakeWH(bounds.Width(), bounds.Height());\n      break;\n  }\n\n  // We have specified the BottomLeft as the origin of the surface when create\n  // SkImage. But Skity doesn't provide this parameter. So flip the y-axis\n  // again.\n  skity::Matrix transform = skity::Matrix::Translate(0.f, 1.f) *\n                            skity::Matrix::Scale(1.f, -1.f) * transform_;\n\n  skity::Matrix scaled_transform;\n  switch (fit_mode) {\n    case clay::DrawableImage::FitMode::kScaleToFill:\n      scaled_transform =\n          skity::Matrix::Scale(dst_rect.Width(), dst_rect.Height()) *\n          transform *\n          skity::Matrix::Scale(1.0 / skity_image->Width(),\n                               1.0 / skity_image->Height());\n      break;\n    case clay::DrawableImage::FitMode::kClipToBounds:\n      scaled_transform =\n          skity::Matrix::Scale(skity_image->Width(), skity_image->Height()) *\n          transform *\n          skity::Matrix::Scale(1.0 / skity_image->Width(),\n                               1.0 / skity_image->Height());\n      break;\n  }\n  std::shared_ptr<skity::Shader> shader =\n      skity::Shader::MakeShader(skity_image, sampling, skity::TileMode::kRepeat,\n                                skity::TileMode::kRepeat, scaled_transform);\n\n  skity::Paint paint_with_shader;\n  if (context.sk_paint) {\n    paint_with_shader = *context.sk_paint;\n  }\n  paint_with_shader.SetShader(shader);\n  context.canvas->DrawRect(dst_rect, paint_with_shader);\n  context.canvas->Restore();\n}\n#endif  // ENABLE_SKITY\n\n// Called from raster thread.\nvoid SharedDrawableImage::FlushAndReleaseFrontForSingleBuffer() {\n  if (image_sink_->Capacity() == 1) {\n#ifndef ENABLE_SKITY\n    // Flush sk_image to make sure the shared image can be transferred to the\n    // producer.\n    // This is not required in multi-buffer mode.\n    // Since the previous sk_image_ is not paint in this frame and already\n    // released.\n    if (GetGrContext()) {\n      GetGrContext()->flush(sk_image_);\n    }\n    accessor_->ReleaseFront();\n    sk_image_ = nullptr;\n#else\n    // Flush sk_image to make sure the shared image can be transferred to the\n    // producer.\n    // This is not required in multi-buffer mode.\n    // Since the previous skity_image_ is not paint in this frame and already\n    // released.\n    // TODO(yudingqian): Skity doesn't support this API.\n    // if (GetGrContext()) {\n    //   GetGrContext()->flush(skity_image_);\n    // }\n    accessor_->ReleaseFront();\n    skity_image_ = nullptr;\n#endif\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/shared_drawable_image.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_SHARED_DRAWABLE_IMAGE_H_\n#define CLAY_COMMON_GRAPHICS_SHARED_DRAWABLE_IMAGE_H_\n\n#include <memory>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/matrix.hpp\"\n#if defined(ENABLE_SKITY)\n#include \"clay/gfx/skity/skity_image.h\"\n#endif\n\nnamespace clay {\n\nclass SharedImageSink;\nclass SharedImageSinkAccessor;\n\nclass SharedDrawableImage : public clay::DrawableImage {\n public:\n  explicit SharedDrawableImage(fml::RefPtr<SharedImageSink> image_sink);\n\n  ~SharedDrawableImage() override;  // Called from raster thread.\n\n  // |clay::DrawableImage|\n  // Called on platform thread.\n  void SetFrameAvailableCallback(const fml::closure& callback) override;\n\n  // |clay::DrawableImage|\n  // Called on raster thread.\n  void MarkNewFrameAvailable() override;\n\n  // |clay::DrawableImage|\n  // Called on raster thread.\n  void OnDrawableImageUnregistered() override;\n\n  // |clay::ContextListener|\n  // Called from raster thread.\n  void OnGrContextCreated() override;\n\n  // |clay::ContextListener|\n  // Called from raster thread.\n  void OnGrContextDestroyed() override;\n\n protected:\n  enum class AttachmentState { uninitialized, attached, detached };\n\n  virtual GrContext* GetGrContext() { return nullptr; }\n\n  // Called from raster thread.\n  bool EnsureAttached();\n\n  // Called from raster thread.\n  void AdvanceFrameConsumption(bool freeze);\n\n#ifndef ENABLE_SKITY\n  // Called from raster thread.\n  void DrawSkiaImage(sk_sp<SkImage> sk_image, PaintContext& context,\n                     const skity::Rect& bounds,\n                     const GrSamplingOptions& sampling, FitMode fit_mode);\n#else\n  // Called from raster thread.\n  void DrawSkityImage(std::shared_ptr<skity::Image> skity_image,\n                      PaintContext& context, const skity::Rect& bounds,\n                      const GrSamplingOptions& sampling, FitMode fit_mode);\n#endif  // ENABLE_SKITY\n\n  // Called from raster thread.\n  void FlushAndReleaseFrontForSingleBuffer();\n\n#ifndef ENABLE_SKITY\n  sk_sp<SkImage> sk_image_;\n#else\n  std::shared_ptr<SkityImage> skity_image_;\n#endif  // ENABLE_SKITY\n  skity::Matrix transform_;\n\n private:\n  [[nodiscard]] bool Update();\n\n  fml::RefPtr<SharedImageSink> image_sink_;\n  std::unique_ptr<SharedImageSinkAccessor> accessor_;\n  uint32_t frame_consume_cnt_ = 0;\n  uint32_t frame_produce_cnt_ = 0;\n\n  AttachmentState state_ = AttachmentState::uninitialized;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_SHARED_DRAWABLE_IMAGE_H_\n"
  },
  {
    "path": "clay/common/graphics/shared_image_external_bitmap.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/shared_image_external_bitmap.h\"\n\n#include <algorithm>\n#include <memory>\n\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_sink_accessor.h\"\n#if defined(ENABLE_SKITY)\n#include \"clay/gfx/skity/skity_image.h\"\n#endif\nnamespace clay {\n\nSharedImageExternalBitmap::SharedImageExternalBitmap(\n    fml::RefPtr<SharedImageSink> image_sink)\n    : clay::SharedDrawableImage(image_sink) {}\n\nSharedImageExternalBitmap::~SharedImageExternalBitmap() = default;\n\n// |clay::DrawableImage|\nDrawableImage::ImageType SharedImageExternalBitmap::GetType() const {\n  return ImageType::kSharedImageBitmap;\n}\n\n// |clay::DrawableImage|\n// Called from raster thread.\nvoid SharedImageExternalBitmap::Paint(PaintContext& context,\n                                      const skity::Rect& bounds, bool freeze,\n                                      const GrSamplingOptions& sampling,\n                                      FitMode fit_mode) {\n  if (!EnsureAttached()) {\n    return;\n  }\n  AdvanceFrameConsumption(freeze);\n\n#ifndef ENABLE_SKITY\n  if (!sk_image_) {\n    return;\n  }\n\n  DrawSkiaImage(sk_image_, context, bounds, sampling, fit_mode);\n#else\n  if (!skity_image_) {\n    return;\n  }\n\n  std::shared_ptr<skity::Image> skity_image = skity_image_->gr_image();\n  FML_DCHECK(skity_image);\n  DrawSkityImage(skity_image, context, bounds, sampling, fit_mode);\n#endif  // ENABLE_SKITY\n  FlushAndReleaseFrontForSingleBuffer();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/shared_image_external_bitmap.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_SHARED_IMAGE_EXTERNAL_BITMAP_H_\n#define CLAY_COMMON_GRAPHICS_SHARED_IMAGE_EXTERNAL_BITMAP_H_\n\n#include \"clay/common/graphics/shared_drawable_image.h\"\n\nnamespace clay {\n\nclass SharedImageSink;\nclass SharedImageSinkAccessor;\n\nclass SharedImageExternalBitmap final : public clay::SharedDrawableImage {\n public:\n  explicit SharedImageExternalBitmap(fml::RefPtr<SharedImageSink> image_sink);\n\n  ~SharedImageExternalBitmap() override;  // Called from raster thread.\n\n  // |clay::DrawableImage|\n  DrawableImage::ImageType GetType() const override;\n\n  // |clay::DrawableImage|\n  // Called from raster thread.\n  void Paint(PaintContext& context, const skity::Rect& bounds, bool freeze,\n             const GrSamplingOptions& sampling, FitMode fit_mode) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_SHARED_IMAGE_EXTERNAL_BITMAP_H_\n"
  },
  {
    "path": "clay/common/graphics/shared_image_external_texture.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/shared_image_external_texture.h\"\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_sink_accessor.h\"\n#if defined(ENABLE_SKITY)\n#include \"clay/gfx/skity/skity_image.h\"\n#endif\n\nnamespace clay {\n\nSharedImageExternalTexture::SharedImageExternalTexture(\n    fml::RefPtr<SharedImageSink> image_sink)\n    : clay::SharedDrawableImage(image_sink) {}\n\nSharedImageExternalTexture::~SharedImageExternalTexture() = default;\n\n// |clay::DrawableImage|\nDrawableImage::ImageType SharedImageExternalTexture::GetType() const {\n  return ImageType::kSharedImageTexture;\n}\n\n// |clay::DrawableImage|\n// Called from raster thread.\nvoid SharedImageExternalTexture::Paint(PaintContext& context,\n                                       const skity::Rect& bounds, bool freeze,\n                                       const GrSamplingOptions& sampling,\n                                       FitMode fit_mode) {\n  gr_context_ = context.gr_context;\n\n  if (!gr_context_) {\n    FML_LOG(ERROR) << \"No gpu context\";\n    return;\n  }\n\n  if (!EnsureAttached()) {\n    return;\n  }\n  AdvanceFrameConsumption(freeze);\n\n#ifndef ENABLE_SKITY\n  if (!sk_image_) {\n    return;\n  }\n\n  if (custom_painter_) {\n    // FIXME(youfeng) alpha video paint maybe broken now.\n    custom_painter_->Paint(context, sk_image_.get(), transform_, bounds,\n                           sampling);\n  } else {\n    DrawSkiaImage(sk_image_, context, bounds, sampling, fit_mode);\n  }\n#else\n  if (!skity_image_) {\n    return;\n  }\n\n  std::shared_ptr<skity::Image> skity_image = skity_image_->gr_image();\n  FML_DCHECK(skity_image);\n  if (custom_painter_) {\n    // FIXME(youfeng) alpha video paint maybe broken now.\n    custom_painter_->Paint(context, skity_image.get(), transform_, bounds,\n                           sampling);\n  } else {\n    DrawSkityImage(skity_image, context, bounds, sampling, fit_mode);\n  }\n#endif  // ENABLE_SKITY\n  FlushAndReleaseFrontForSingleBuffer();\n}\n\nvoid SharedImageExternalTexture::SetCustomPainter(\n    fml::RefPtr<ExternalTexturePainter> custom_painter) {\n  custom_painter_ = std::move(custom_painter);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/graphics/shared_image_external_texture.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_GRAPHICS_SHARED_IMAGE_EXTERNAL_TEXTURE_H_\n#define CLAY_COMMON_GRAPHICS_SHARED_IMAGE_EXTERNAL_TEXTURE_H_\n\n#include \"clay/common/graphics/shared_drawable_image.h\"\n#include \"clay/gfx/gpu_ref_object.h\"\n\nnamespace clay {\n\nclass SharedImageSink;\nclass SharedImageSinkAccessor;\nclass SkityImage;\n\nclass ExternalTexturePainter : public GPURefObject {\n public:\n  // This method is called in raster thread.\n  virtual void Paint(clay::DrawableImage::PaintContext& context,\n                     const GrImage* sk_image, const skity::Matrix& uv_transform,\n                     const skity::Rect& bounds,\n                     const GrSamplingOptions& sampling) = 0;\n};\n\nclass SharedImageExternalTexture final : public clay::SharedDrawableImage {\n public:\n  explicit SharedImageExternalTexture(fml::RefPtr<SharedImageSink> image_sink);\n\n  ~SharedImageExternalTexture() override;  // Called from raster thread.\n\n  // |clay::DrawableImage|\n  DrawableImage::ImageType GetType() const override;\n\n  // |clay::DrawableImage|\n  // Called from raster thread.\n  void Paint(PaintContext& context, const skity::Rect& bounds, bool freeze,\n             const GrSamplingOptions& sampling, FitMode fit_mode) override;\n\n  void SetCustomPainter(fml::RefPtr<ExternalTexturePainter> custom_painter);\n\n protected:\n  GrContext* GetGrContext() override { return gr_context_; }\n\n private:\n  GrContext* gr_context_ = nullptr;\n\n  fml::RefPtr<ExternalTexturePainter> custom_painter_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_GRAPHICS_SHARED_IMAGE_EXTERNAL_TEXTURE_H_\n"
  },
  {
    "path": "clay/common/recyclable.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_RECYCLABLE_H_\n#define CLAY_COMMON_RECYCLABLE_H_\n\nnamespace clay {\nclass Recyclable {\n public:\n  virtual ~Recyclable() = default;\n  virtual void CleanForRecycle() {}\n  virtual void PrepareForRecycle() {}\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_RECYCLABLE_H_\n"
  },
  {
    "path": "clay/common/service/BUILD.gn",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\nimport(\"../../testing/testing.gni\")\n\nsource_set(\"service\") {\n  sources = [\n    \"service.cc\",\n    \"service.h\",\n    \"service_manager.cc\",\n    \"service_manager.h\",\n  ]\n\n  # Heed caution when adding targets to the dependencies. This is a minimal\n  # target containing graphics sources that embedders can depend on. Any\n  # additions here could result in added app sizes across embeddings.\n  public_deps = [ \"../../fml:fml\" ]\n}\n"
  },
  {
    "path": "clay/common/service/service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/service/service.h\"\n\n#include \"clay/common/service/service_manager.h\"\n\nnamespace clay {\n\nBaseService::BaseService(ServiceId service_id, Owner service_owner)\n    : service_id_(service_id), service_owner_(service_owner) {}\n\nBaseService::~BaseService() = default;\n\nvoid BaseService::AddPendingCall(\n    lynx::base::MoveOnlyClosure<void, BaseService&> call) {\n  pending_calls_.emplace_back(std::move(call));\n}\n\nvoid BaseService::AttachServiceManager(ServiceManager& service_manager) {\n  task_runners_ = service_manager.GetTaskRunners();\n  valid_ = true;\n\n  weak_factory_.emplace(this);\n\n  std::apply(\n      [&](auto... arg) {\n        (\n            [&] {\n              constexpr Owner owner = decltype(arg)::value;\n              if (owner == service_owner_) {\n                static_cast<BaseServiceWithLifeCycle<owner>*>(this)->OnInit(\n                    service_manager,\n                    *std::get<+owner>(service_manager.GetContexts()));\n              }\n            }(),\n            ...);\n      },\n      Owners());\n\n  for (auto& call : pending_calls_) {\n    call(*this);\n  }\n\n  pending_calls_.clear();\n}\n\nvoid BaseService::DetachServiceManager() {\n  std::apply(\n      [&](auto... arg) {\n        (\n            [&] {\n              constexpr Owner owner = decltype(arg)::value;\n              if (owner == service_owner_) {\n                static_cast<BaseServiceWithLifeCycle<owner>*>(this)\n                    ->OnDestroy();\n              }\n            }(),\n            ...);\n      },\n      Owners());\n\n  task_runners_ = nullptr;\n  weak_factory_.reset();\n  valid_ = false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/service/service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_SERVICE_SERVICE_H_\n#define CLAY_COMMON_SERVICE_SERVICE_H_\n\n#include <future>\n#include <memory>\n#include <optional>\n#include <tuple>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nclass Engine;\nclass PlatformView;\nclass Rasterizer;\nclass Shell;\n}  // namespace clay\n\nnamespace clay {\n\nenum class Owner : size_t {\n  kPlatform = 0,\n  kUI,\n  kRaster,\n  kIO,\n};\n\nenum class ServiceFlags : uint32_t {\n  kNone = 0,\n  kManualRegister = 1 << 0,\n  // multi-thread service can get reference even not in the same thread\n  kMultiThread = 1 << 2\n};\n\ntemplate <typename T>\nconstexpr auto operator+(T e) noexcept\n    -> std::enable_if_t<std::is_enum_v<T>, std::underlying_type_t<T>> {\n  return static_cast<std::underlying_type_t<T>>(e);\n}\n\ninline constexpr ServiceFlags operator|(ServiceFlags lhs, ServiceFlags rhs) {\n  return static_cast<ServiceFlags>(+lhs | +rhs);\n}\n\ninline constexpr ServiceFlags operator&(ServiceFlags lhs, ServiceFlags rhs) {\n  return static_cast<ServiceFlags>(+lhs & +rhs);\n}\n\ntemplate <Owner owner>\nstruct OwnerEnum {\n  static constexpr Owner value = owner;\n};\n\nusing Owners = std::tuple<OwnerEnum<Owner::kPlatform>, OwnerEnum<Owner::kUI>,\n                          OwnerEnum<Owner::kRaster>, OwnerEnum<Owner::kIO>>;\n\nstruct PlatformServiceContext {\n  clay::PlatformView* platform_view = nullptr;\n  clay::Shell* shell = nullptr;\n};\n\nstruct UIServiceContext {\n  clay::Engine* engine = nullptr;\n};\n\nstruct RasterServiceContext {\n  clay::Rasterizer* rasterizer = nullptr;\n  uint32_t page_unique_id = 0;\n};\n\nstruct IOServiceContext {\n  void* cookie = nullptr;\n};\n\nusing ServiceContexts = std::tuple<PlatformServiceContext, UIServiceContext,\n                                   RasterServiceContext, IOServiceContext>;\n\ntemplate <Owner owner>\nusing ServiceContext = std::tuple_element_t<+owner, ServiceContexts>;\n\nnamespace details {\n\ntemplate <template <typename> typename M, typename T>\nstruct tuple_transform;\n\ntemplate <template <typename> typename M, typename... Ts>\nstruct tuple_transform<M, std::tuple<Ts...>> {\n  using type = std::tuple<typename M<Ts>::type...>;\n};\n}  // namespace details\n\n// We use static analyzer to find it the puppet support sync call\ntemplate <Owner puppet_owner, Owner actor_owner>\nstruct puppet_can_sync_call_actor;\n\n// Currently, only UI->Platform sync call is supported.\n// MUST ensure puppet_owner->actor_owner doesn't create a cylic!\ntemplate <>\nstruct puppet_can_sync_call_actor<Owner::kUI, Owner::kPlatform> {\n  static constexpr bool value = true;\n};\n\ntemplate <>\nstruct puppet_can_sync_call_actor<Owner::kPlatform, Owner::kRaster> {\n  static constexpr bool value = true;\n};\n\ntemplate <>\nstruct puppet_can_sync_call_actor<Owner::kPlatform, Owner::kIO> {\n  static constexpr bool value = true;\n};\n\ntemplate <Owner puppet_owner, Owner actor_owner>\nstruct puppet_can_sync_call_actor {\n  static constexpr bool value = false;\n};\n\ntemplate <Owner owner>\nstruct owner_thread_override;\n\n// In Clay's current implenmentation,\n// UI thread is running on the same thread with the platform thread.\n// To make `Act` more performant, we add this override.\ntemplate <>\nstruct owner_thread_override<Owner::kUI> {\n  static constexpr Owner value = Owner::kPlatform;\n};\n\ntemplate <Owner owner>\nstruct owner_thread_override {\n  static constexpr Owner value = owner;\n};\n\ntemplate <Owner a, Owner b>\nstruct is_owner_same_thread {\n  static constexpr bool value =\n      owner_thread_override<a>::value == owner_thread_override<b>::value;\n};\n\nclass ServiceTaskRunners {\n public:\n  template <typename OwnerEnum>\n  struct map_owner_to_task_runner {\n    using type = fml::RefPtr<fml::TaskRunner>;\n  };\n\n  using TaskRunnersTuple =\n      typename details::tuple_transform<map_owner_to_task_runner, Owners>::type;\n\n  explicit ServiceTaskRunners(TaskRunnersTuple task_runners)\n      : task_runners_(std::move(task_runners)) {}\n\n  template <Owner owner>\n  const fml::RefPtr<fml::TaskRunner>& SelectTaskRunner() const {\n    return std::get<+owner>(task_runners_);\n  }\n\n private:\n  TaskRunnersTuple task_runners_;\n};\n\nclass ServiceManager;\n\ntemplate <Owner puppet_owner, typename>\nclass Puppet;\n\ntemplate <typename S, Owner owner, ServiceFlags flags = ServiceFlags::kNone>\nclass Service;\n\n// https://stackoverflow.com/a/23348670\nusing ServiceId = uint64_t;\n\nnamespace details {\n\ntemplate <typename S, typename Enabled = void>\nstruct service_has_custom_service_id {\n  static constexpr bool value = false;\n};\n\ntemplate <typename S>\nstruct service_has_custom_service_id<\n    S, std::enable_if_t<\n           std::is_convertible_v<decltype(S::kServiceId), ServiceId>>> {\n  static constexpr bool value = true;\n};\n\n}  // namespace details\n\ntemplate <typename S>\nServiceId\nServiceIdNoRTTI()  // this function is instantiated for every different type\n{\n  if constexpr (details::service_has_custom_service_id<S>::value) {\n    return S::kServiceId;\n  } else {\n    // WARNING: works only inside one module: same type coming from different\n    // module will have different value!\n    static S* TypeUniqueMarker =\n        nullptr;  // thus this static variable will be created for each\n                  // TypeIdNoRTTI<T> separately\n    return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(\n        &TypeUniqueMarker));  // it's address is unique identifier of\n                              // TypeIdNoRTTI<T> type\n  }\n}\n\nclass BaseService {\n public:\n  ServiceId GetServiceId() const { return service_id_; }\n  Owner GetServiceOwner() const { return service_owner_; }\n  bool IsValid() const { return valid_; }\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(BaseService);\n\n protected:\n  BaseService(ServiceId service_id, Owner service_owner);\n  virtual ~BaseService();\n\n private:\n  const ServiceId service_id_;\n  const Owner service_owner_;\n  bool valid_ = false;\n  std::vector<lynx::base::MoveOnlyClosure<void, BaseService&>> pending_calls_;\n  std::shared_ptr<ServiceTaskRunners> task_runners_;\n  std::optional<fml::WeakPtrFactory<BaseService>> weak_factory_;\n\n  // called in target thread\n  void AddPendingCall(lynx::base::MoveOnlyClosure<void, BaseService&> call);\n\n  void AttachServiceManager(ServiceManager& service_manager);\n  void DetachServiceManager();\n\n  friend class ServiceManager;\n  template <typename, Owner, ServiceFlags>\n  friend class Service;\n};\n\n// A marker class to mark the object's owner thread\ntemplate <Owner owner>\nclass ActorObject {\n public:\n  static constexpr Owner kOwner = owner;\n};\n\nnamespace details {\n\ntemplate <typename S>\nstatic constexpr bool IsService = std::is_base_of_v<BaseService, S>;\n\ntemplate <typename T, typename Enabled = void>\nstruct is_multi_thread_service;\n\ntemplate <typename T>\nstruct is_multi_thread_service<T, std::enable_if_t<IsService<T>>> {\n  static constexpr bool value = T::kIsMultiThread;\n};\n\ntemplate <typename T, typename Enabled>\nstruct is_multi_thread_service : std::false_type {};\n\ntemplate <typename S>\nstatic constexpr bool IsMultiThreadService = is_multi_thread_service<S>::value;\n\ntemplate <typename T, typename Enabled = void>\nstruct is_actor_object : std::false_type {};\n\ntemplate <typename T>\nstruct is_actor_object<\n    T, std::enable_if_t<std::is_convertible_v<decltype(T::kOwner), Owner>>>\n    : std::true_type {};\n\ntemplate <typename T>\nstruct remove_ptr;\n\ntemplate <typename T>\nstruct remove_ptr<std::unique_ptr<T>> {\n  using type = T;\n  static constexpr bool value = true;\n};\n\ntemplate <typename T>\nstruct remove_ptr<std::shared_ptr<T>> {\n  using type = T;\n  static constexpr bool value = true;\n};\n\ntemplate <typename T>\nstruct remove_ptr<fml::WeakPtr<T>> {\n  using type = T;\n  static constexpr bool value = true;\n};\n\ntemplate <typename T>\nstruct remove_ptr {\n  static constexpr bool value = false;\n};\n\n}  // namespace details\n\ntemplate <typename ImplPtr>\nclass Actor : public std::enable_shared_from_this<Actor<ImplPtr>> {\n public:\n  using Impl = typename details::remove_ptr<ImplPtr>::type;\n  static_assert(details::is_actor_object<Impl>::value, \"Must be ActorObject\");\n  static constexpr Owner kOwner = Impl::kOwner;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(Actor);\n\n  ~Actor() {\n    if (impl_) {\n      // move impl to destruct on target thread\n      fml::TaskRunner::RunNowOrPostTask(\n          this->task_runners_->template SelectTaskRunner<kOwner>(),\n          [impl = std::move(this->impl_)] { (void)impl; });\n    }\n  }\n\n private:\n  template <bool check_thread>\n  static std::shared_ptr<Actor> Create(\n      std::shared_ptr<ServiceTaskRunners> task_runners, ImplPtr impl) {\n    if constexpr (check_thread) {\n      FML_DCHECK(task_runners->template SelectTaskRunner<kOwner>()\n                     ->RunsTasksOnCurrentThread());\n    }\n    return std::shared_ptr<Actor>(\n        new Actor(std::move(task_runners), std::move(impl)));\n  }\n\n  Actor(std::shared_ptr<ServiceTaskRunners> task_runners, ImplPtr impl)\n      : task_runners_(task_runners),\n        impl_(std::make_unique<ImplPtr>(std::move(impl))) {}\n\n  template <Owner get_owner, typename I = Impl,\n            typename = std::enable_if_t<details::IsMultiThreadService<I> ||\n                                        get_owner == kOwner>>\n  Impl& GetImpl() const {\n    if constexpr (details::IsService<Impl> &&\n                  !details::IsMultiThreadService<I>) {\n      FML_DCHECK((**impl_).IsValid())\n          << \"Service is not valid, do not use -> or *, use Act() instead\";\n    }\n    return **impl_;\n  }\n\n  // Post task automatically to the target thread\n  template <Owner from_owner, typename F,\n            typename = std::enable_if_t<std::is_invocable_v<F, Impl&>>>\n  void Act(F&& func) const {\n    auto c = [](const Actor& actor, F&& func) {\n      FML_DCHECK(actor.impl_);\n      if (auto& impl = *actor.impl_) {\n        if constexpr (details::IsService<Impl>) {\n          impl->TryCall(std::forward<F>(func));\n        } else {\n          func(*impl);\n        }\n      } else {\n        FML_DLOG(ERROR) << \"Actor::Act: Actor is not initialized\";\n      }\n    };\n    if constexpr (is_owner_same_thread<from_owner, kOwner>::value) {\n      c(*this, std::forward<F>(func));\n    } else {\n      this->task_runners_->template SelectTaskRunner<kOwner>()->PostTask(\n          [self = this->shared_from_this(), c = std::move(c),\n           func = std::move(func)]() mutable { c(*self, std::move(func)); });\n    }\n  }\n\n  template <Owner from_owner, typename F, typename C,\n            typename = std::enable_if_t<\n                std::is_invocable_v<C, std::invoke_result_t<F, Impl&>>>>\n  void Act(F&& func, C&& callback) const {\n    constexpr bool is_same_thread =\n        is_owner_same_thread<from_owner, kOwner>::value;\n    this->template Act<from_owner>(\n        [task_runner =\n             is_same_thread\n                 ? nullptr\n                 : this->task_runners_->template SelectTaskRunner<from_owner>(),\n         func = std::move(func),\n         callback = std::move(callback)](Impl& impl) mutable {\n          if constexpr (is_same_thread) {\n            (void)task_runner;\n            callback(func(impl));\n          } else {\n            task_runner->PostTask([result = func(impl),\n                                   callback = std::move(callback)]() mutable {\n              callback(std::move(result));\n            });\n          }\n        });\n  }\n\n  const std::shared_ptr<ServiceTaskRunners> task_runners_;\n  // WeakPtr must be constructed and destructed on the same thread.\n  // So we add unique_ptr<ImplPtr> to totally move ImplPtr to target thread\n  std::unique_ptr<ImplPtr> impl_ = nullptr;\n\n  template <Owner, typename>\n  friend class Puppet;\n\n  template <typename S, Owner, ServiceFlags>\n  friend class Service;\n\n  friend class ServiceManager;\n};\n\ntemplate <Owner puppet_owner, typename T>\nclass Puppet {\n public:\n  static_assert(details::IsService<T> || details::remove_ptr<T>::value,\n                \"T must be Service or ptr<ActorObject>\");\n  using ActorType =\n      Actor<std::conditional_t<details::IsService<T>, std::shared_ptr<T>, T>>;\n  using Impl = typename ActorType::Impl;\n\n  static constexpr Owner kPuppetOwner = puppet_owner;\n  static constexpr Owner kActorOwner = Impl::kOwner;\n\n  Puppet() = default;\n  // NOLINTNEXTLINE(google-explicit-constructor)\n  Puppet(std::shared_ptr<ActorType> actor) : actor_(std::move(actor)) {\n    CheckActor();\n  }\n\n  Puppet(const Puppet& other) { *this = other; }\n\n  Puppet(Puppet&& other) { *this = std::move(other); }\n\n  // Post task to the target thread\n  // If puppet_owner == kActorOwner, use `->` instead.\n  template <typename F,\n            typename = std::enable_if_t<std::is_invocable_v<F, Impl&>>>\n  void Act(F&& func) const {\n    this->actor_->template Act<puppet_owner>(std::forward<F>(func));\n  }\n\n  // Post task to the target thread,\n  // the callback is invoked in caller thread.\n  // If puppet_owner == kActorOwner, use `->` instead.\n  template <typename F, typename C,\n            typename = std::enable_if_t<\n                std::is_invocable_v<C, std::invoke_result_t<F, Impl&>>>>\n  void Act(F&& func, C&& callback) const {\n    this->actor_->template Act<puppet_owner>(std::forward<F>(func),\n                                             std::forward<C>(callback));\n  }\n\n  template <typename F,\n            typename = std::enable_if_t<\n                puppet_can_sync_call_actor<puppet_owner, kActorOwner>::value &&\n                std::is_invocable_v<F, Impl&>>,\n            typename Result = std::invoke_result_t<F, Impl&>>\n  std::future<std::optional<Result>> ActWithPromise(F&& f) const {\n    struct no_except_promise {\n      explicit no_except_promise(std::promise<std::optional<Result>> p)\n          : p(std::move(p)) {}\n      ~no_except_promise() {\n        if (!has_set_value) {\n          p.set_value(std::optional<Result>{});\n        }\n      }\n      void SetValue(Result&& value) {\n        FML_DCHECK(!has_set_value);\n        p.set_value(std::optional<Result>{std::forward<Result>(value)});\n        has_set_value = true;\n      }\n      bool has_set_value = false;\n      std::promise<std::optional<Result>> p;\n    };\n    std::promise<std::optional<Result>> promise;\n    auto future = promise.get_future();\n    this->Act([p = std::make_unique<no_except_promise>(std::move(promise)),\n               f = std::move(f)](Impl& impl) { p->SetValue(f(impl)); });\n\n    return future;\n  }\n\n  // Directly call the method,\n  template <typename I = Impl,\n            typename = std::enable_if_t<details::IsMultiThreadService<I> ||\n                                        kPuppetOwner == kActorOwner>>\n  Impl* operator->() const {\n    return &this->actor_->template GetImpl<puppet_owner>();\n  }\n\n  template <typename I = Impl,\n            typename = std::enable_if_t<details::IsMultiThreadService<I> ||\n                                        kPuppetOwner == kActorOwner>>\n  Impl& operator*() const {\n    return this->actor_->template GetImpl<puppet_owner>();\n  }\n\n  explicit operator bool() const { return actor_ != nullptr; }\n\n  Puppet& operator=(const Puppet& other) {\n    actor_ = other.actor_;\n    CheckActor();\n    return *this;\n  }\n\n  Puppet& operator=(Puppet&& other) {\n    actor_ = std::move(other.actor_);\n    CheckActor();\n    return *this;\n  }\n\n  void operator=(std::nullptr_t) { actor_ = nullptr; }\n\n  void operator=(const std::shared_ptr<ActorType>& actor) {\n    actor_ = actor;\n    CheckActor();\n  }\n\n  void operator=(std::shared_ptr<ActorType>&& actor) {\n    actor_ = std::move(actor);\n    CheckActor();\n  }\n\n  template <typename C,\n            typename = std::enable_if_t<std::is_invocable_v<C, Impl&>>,\n            typename Result = std::invoke_result_t<C, Impl&>,\n            typename A = Actor<Result>>\n  std::shared_ptr<A> CreateObjectInActorThread(C&& ctor) {\n    std::shared_ptr<A> actor =\n        A::template Create<false>(this->actor_->task_runners_, nullptr);\n    this->Act([actor, ctor = std::move(ctor)](auto& impl) mutable {\n      actor->impl_ = std::make_unique<Result>(ctor(impl));\n    });\n\n    return actor;\n  }\n\n  template <typename ActorImplPtr, typename F, typename A = Actor<ActorImplPtr>,\n            typename = std::enable_if_t<\n                std::is_invocable_v<F, Impl&, std::shared_ptr<A>>>>\n  void PostObjectToActorThread(ActorImplPtr actor_impl, F&& fn) {\n    static_assert(!std::is_pointer_v<ActorImplPtr>,\n                  \"do not use raw pointers, use std::unique_ptr, \"\n                  \"std::shared_ptr or fml::WeakPtr\");\n    std::shared_ptr<A> actor = A::template Create<true>(\n        this->actor_->task_runners_, std::move(actor_impl));\n    this->Act([actor = std::move(actor), fn = std::move(fn)](\n                  auto& impl) mutable { fn(impl, std::move(actor)); });\n  }\n\n private:\n  void CheckActor() {\n    FML_DCHECK(!this->actor_ || this->actor_->task_runners_\n                                    ->template SelectTaskRunner<puppet_owner>()\n                                    ->RunsTasksOnCurrentThread());\n  }\n\n  std::shared_ptr<ActorType> actor_;\n\n  template <typename S, Owner, ServiceFlags>\n  friend class Service;\n};\n\ntemplate <Owner owner>\nclass BaseServiceWithLifeCycle : public BaseService, public ActorObject<owner> {\n public:\n  using ContextType = ServiceContext<owner>;\n\n protected:\n  explicit BaseServiceWithLifeCycle(ServiceId id) : BaseService(id, owner) {}\n\n private:\n  friend class BaseService;\n\n  virtual void OnInit(ServiceManager& service_manager, const ContextType&) {}\n  virtual void OnDestroy() {}\n};\n\ntemplate <typename S, Owner owner, ServiceFlags flags>\nclass Service : public BaseServiceWithLifeCycle<owner> {\n public:\n  using Impl = S;\n  static constexpr bool kAutoInit =\n      (flags & ServiceFlags::kManualRegister) == ServiceFlags::kNone;\n  static constexpr bool kIsMultiThread =\n      (flags & ServiceFlags::kMultiThread) != ServiceFlags::kNone;\n\n  Service() : BaseServiceWithLifeCycle<owner>(ServiceIdNoRTTI<S>()) {}\n\n protected:\n  fml::WeakPtr<S> GetWeakPtr() const {\n    FML_DCHECK(this->weak_factory_.has_value()) << \"Service is invalid now!\";\n    return this->weak_factory_->GetWeakPtr();\n  }\n\n private:\n  template <typename F, typename = std::enable_if_t<std::is_invocable_v<F, S&>>>\n  void TryCall(F&& f) {\n    if (this->valid_) {\n      f(static_cast<S&>(*this));\n    } else {\n      this->AddPendingCall([f = std::move(f)](BaseService& service) mutable {\n        f(static_cast<S&>(service));\n      });\n    }\n  }\n\n  template <typename>\n  friend class Actor;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_SERVICE_SERVICE_H_\n"
  },
  {
    "path": "clay/common/service/service_manager.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/service/service_manager.h\"\n\n#include \"clay/common/service/service.h\"\n\nnamespace clay {\n\nstd::shared_ptr<ServiceManager> ServiceManager::Create(\n    ServiceTaskRunners::TaskRunnersTuple task_runners) {\n  return std::shared_ptr<ServiceManager>(new ServiceManager(\n      std::make_shared<ServiceTaskRunners>(std::move(task_runners))));\n}\n\nServiceManager::ServiceManager(std::shared_ptr<ServiceTaskRunners> task_runners)\n    : task_runners_(std::move(task_runners)) {}\n\nServiceManager::~ServiceManager() {\n  std::apply(\n      [](auto&... ctx) {\n        ([&] { FML_DCHECK(!ctx.has_value()) << \"Not detached!\"; }(), ...);\n      },\n      contexts_);\n}\n\nstd::shared_ptr<BaseService> ServiceManager::GetService(\n    ServiceId id, ServiceCreateFn create_fn) {\n  {\n    std::shared_lock<std::shared_mutex> lock(mutex_);\n    auto it = services_by_id_.find(id);\n    if (it != services_by_id_.end()) {\n      return it->second;\n    }\n  }\n\n  std::shared_ptr<BaseService> service;\n\n  {\n    std::unique_lock<std::shared_mutex> lock(mutex_);\n    auto it = services_by_id_.find(id);\n    if (it != services_by_id_.end()) {\n      return it->second;\n    }\n    if (!create_fn) {\n      return nullptr;\n    }\n\n    service = create_fn();\n    if (!service) {\n      return nullptr;\n    }\n    service->task_runners_ = task_runners_;\n\n    auto [_, inserted] = services_by_id_.try_emplace(id, service);\n    FML_DCHECK(inserted);\n  }\n\n  TryInitService(service);\n\n  return service;\n}\n\nvoid ServiceManager::RegisterService(ServiceId id,\n                                     std::shared_ptr<BaseService> service) {\n  FML_DCHECK(service);\n\n  {\n    std::unique_lock<std::shared_mutex> lock(mutex_);\n    service->task_runners_ = task_runners_;\n\n    auto [_, inserted] = services_by_id_.try_emplace(id, service);\n    FML_DCHECK(inserted);\n  }\n\n  TryInitService(service);\n}\n\ntemplate <Owner owner>\nvoid ServiceManager::Attach(ServiceContext<owner> context) {\n  FML_DCHECK(this->task_runners_->SelectTaskRunner<owner>()\n                 ->RunsTasksOnCurrentThread());\n  auto& ctx = std::get<+owner>(contexts_);\n  FML_DCHECK(!ctx.has_value());\n  ctx.emplace(std::move(context));\n\n  for (auto& service : std::get<+owner>(services_by_owner_)) {\n    DoServiceInit(*service);\n  }\n}\n\ntemplate <Owner owner>\nvoid ServiceManager::Detach() {\n  FML_DCHECK(this->task_runners_->SelectTaskRunner<owner>()\n                 ->RunsTasksOnCurrentThread());\n  std::vector<std::shared_ptr<BaseServiceWithLifeCycle<owner>>>& services =\n      std::get<+owner>(services_by_owner_);\n  for (auto& service : services) {\n    service->DetachServiceManager();\n  }\n  std::get<+owner>(contexts_).reset();\n  services.clear();\n}\n\n#if OS_LINUX || OS_MAC || OS_WIN\ntemplate void ServiceManager::Attach<Owner::kPlatform>(\n    ServiceContext<Owner::kPlatform> context);\ntemplate void ServiceManager::Attach<Owner::kUI>(\n    ServiceContext<Owner::kUI> context);\ntemplate void ServiceManager::Attach<Owner::kRaster>(\n    ServiceContext<Owner::kRaster> context);\ntemplate void ServiceManager::Attach<Owner::kIO>(\n    ServiceContext<Owner::kIO> context);\n\ntemplate void ServiceManager::Detach<Owner::kPlatform>();\ntemplate void ServiceManager::Detach<Owner::kUI>();\ntemplate void ServiceManager::Detach<Owner::kRaster>();\ntemplate void ServiceManager::Detach<Owner::kIO>();\n#endif\n\nvoid ServiceManager::TryInitService(std::shared_ptr<BaseService> service) {\n  std::apply(\n      [&](auto... arg) {\n        (\n            [&] {\n              constexpr Owner owner = decltype(arg)::value;\n              if (service->GetServiceOwner() == owner) {\n                fml::TaskRunner::RunNowOrPostTask(\n                    task_runners_->SelectTaskRunner<owner>(),\n                    [service, self = shared_from_this()] {\n                      // The order is important!\n                      // If ServiceManager is not attached to A thread,\n                      // Dependent services will be added before this service,\n                      // so that dep's init happens before this.\n                      self->DoServiceInit<owner>(\n                          static_cast<BaseServiceWithLifeCycle<owner>&>(\n                              *service));\n                      std::get<+owner>(self->services_by_owner_)\n                          .emplace_back(\n                              std::static_pointer_cast<\n                                  BaseServiceWithLifeCycle<owner>>(service));\n                    });\n              }\n            }(),\n            ...);\n      },\n      Owners());\n}\n\ntemplate <Owner owner>\nvoid ServiceManager::DoServiceInit(BaseServiceWithLifeCycle<owner>& service) {\n  FML_DCHECK(service.GetServiceOwner() == owner);\n  if (std::optional<ServiceContext<owner>>& ctx = std::get<+owner>(contexts_);\n      ctx.has_value()) {\n    FML_DCHECK(!service.valid_);\n    service.AttachServiceManager(*this);\n  }\n}\n\n[[maybe_unused]] void\nServiceManager::CreateTemplateSpecificationToPreventSymbolErrors() {\n  std::apply(\n      [&](auto... arg) {\n        (\n            [&] {\n              constexpr Owner owner = decltype(arg)::value;\n              Attach<owner>(ServiceContext<owner>{});\n              Detach<owner>();\n            }(),\n            ...);\n      },\n      Owners());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/service/service_manager.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_SERVICE_SERVICE_MANAGER_H_\n#define CLAY_COMMON_SERVICE_SERVICE_MANAGER_H_\n\n#include <memory>\n#include <shared_mutex>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/common/service/service.h\"\n#include \"clay/common/task_runners.h\"\n\nnamespace clay {\n\nclass ServiceManager : public std::enable_shared_from_this<ServiceManager> {\n public:\n  using ServiceCreateFn = std::shared_ptr<BaseService> (*)();\n\n  static std::shared_ptr<ServiceManager> Create(\n      // TaskRunners is a tuple<fml::RefPtr<fml::TaskRunner>...>\n      ServiceTaskRunners::TaskRunnersTuple task_runners);\n\n  ~ServiceManager();\n\n  template <typename T, typename = std::enable_if_t<details::IsService<T>>,\n            typename A = Actor<std::shared_ptr<T>>>\n  std::shared_ptr<A> GetService() {\n    ServiceCreateFn create_fn = nullptr;\n    if constexpr (T::kAutoInit) {\n      create_fn = []() -> std::shared_ptr<BaseService> { return T::Create(); };\n    }\n    std::shared_ptr<T> service = std::static_pointer_cast<T>(\n        GetService(ServiceIdNoRTTI<T>(), create_fn));\n    return service ? A::template Create<false>(this->task_runners_, service)\n                   : nullptr;\n  }\n\n  template <typename T,\n            typename = std::enable_if_t<details::IsMultiThreadService<T>>>\n  std::shared_ptr<T> GetMultiThreadService() {\n    ServiceCreateFn create_fn = nullptr;\n    if constexpr (T::kAutoInit) {\n      create_fn = []() -> std::shared_ptr<BaseService> { return T::Create(); };\n    }\n    std::shared_ptr<T> service = std::static_pointer_cast<T>(\n        GetService(ServiceIdNoRTTI<T>(), create_fn));\n    return service;\n  }\n\n  template <typename T, typename I,\n            typename = std::enable_if_t<std::is_base_of_v<BaseService, T> &&\n                                        std::is_base_of_v<T, I>>>\n  void RegisterService(std::shared_ptr<I> impl) {\n    RegisterService(ServiceIdNoRTTI<T>(), std::move(impl));\n  }\n\n  template <Owner owner>\n  void Attach(ServiceContext<owner> ctx);\n\n  template <Owner owner>\n  void Detach();\n\n  const std::shared_ptr<ServiceTaskRunners>& GetTaskRunners() const {\n    return task_runners_;\n  }\n\n  const auto& GetContexts() const { return contexts_; }\n\n private:\n  explicit ServiceManager(std::shared_ptr<ServiceTaskRunners> task_runners);\n\n  std::shared_ptr<BaseService> GetService(ServiceId id,\n                                          ServiceCreateFn create_fn);\n\n  void RegisterService(ServiceId id, std::shared_ptr<BaseService> service);\n\n  std::unordered_map<ServiceId, std::shared_ptr<BaseService>> services_by_id_;\n  std::shared_mutex mutex_;\n\n  std::shared_ptr<ServiceTaskRunners> task_runners_;\n\n  template <typename OwnerEnum>\n  struct map_owner_to_optional_context {\n    using type = std::optional<ServiceContext<OwnerEnum::value>>;\n  };\n  details::tuple_transform<map_owner_to_optional_context, Owners>::type\n      contexts_;\n\n  template <typename OwnerEnum>\n  struct map_owner_to_services_with_life_cycle {\n    using type = std::vector<\n        std::shared_ptr<BaseServiceWithLifeCycle<OwnerEnum::value>>>;\n  };\n  details::tuple_transform<map_owner_to_services_with_life_cycle, Owners>::type\n      services_by_owner_;\n\n  void TryInitService(std::shared_ptr<BaseService> service);\n\n  template <Owner owner>\n  void DoServiceInit(BaseServiceWithLifeCycle<owner>& service);\n\n  [[maybe_unused]] void CreateTemplateSpecificationToPreventSymbolErrors();\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_SERVICE_SERVICE_MANAGER_H_\n"
  },
  {
    "path": "clay/common/service/service_unittests.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/testing/thread_test.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\n// We enable all sync calls from other threads to platform thread in tests.\ntemplate <>\nstruct puppet_can_sync_call_actor<Owner::kRaster, Owner::kPlatform> {\n  static constexpr bool value = true;\n};\n\ntemplate <>\nstruct puppet_can_sync_call_actor<Owner::kIO, Owner::kPlatform> {\n  static constexpr bool value = true;\n};\n\nnamespace testing {\n\n// NOLINTNEXTLINE(build/namespaces)\nusing namespace ::testing;\n\nclass BaseServiceTest : public clay::testing::ThreadTest {\n public:\n  explicit BaseServiceTest(Owner owner)\n      : owner_(owner),\n        task_runners_(std::make_tuple(\n            GetCurrentTaskRunner(), GetCurrentTaskRunner(),\n            CreateNewThread(\"mock_raster\"), CreateNewThread(\"mock_io\"))) {\n    static_assert(is_owner_same_thread<Owner::kPlatform, Owner::kUI>::value,\n                  \"Test is under assumption that platform thread is the same \"\n                  \"as UI thread\");\n  }\n\n  void SetUp() override {\n    service_manager_ = ServiceManager::Create(task_runners_);\n    Attach(owner_);\n  }\n\n  void TearDown() override {\n    std::apply(\n        [&](auto... arg) {\n          (\n              [&] {\n                constexpr Owner o = decltype(arg)::value;\n                fml::TaskRunner::RunNowOrPostTask(\n                    std::get<+o>(task_runners_),\n                    [service_manager = service_manager_] {\n                      service_manager->Detach<o>();\n                    });\n              }(),\n              ...);\n        },\n        Owners());\n  }\n\n  void Attach(Owner owner) {\n    std::apply(\n        [&](auto... arg) {\n          (\n              [&] {\n                constexpr Owner o = decltype(arg)::value;\n                if (owner == o) {\n                  fml::TaskRunner::RunNowOrPostTask(\n                      std::get<+o>(task_runners_),\n                      [service_manager = service_manager_] {\n                        service_manager->Attach<o>(ServiceContext<o>{});\n                      });\n                }\n              }(),\n              ...);\n        },\n        Owners());\n  }\n\n  Owner owner_;\n  ServiceTaskRunners::TaskRunnersTuple task_runners_;\n  std::shared_ptr<ServiceManager> service_manager_;\n\n  template <typename T>\n  Puppet<Owner::kPlatform, T> GetService() {\n    return service_manager_->GetService<T>();\n  }\n};\n\ntemplate <Owner owner>\nclass TestService : public Service<TestService<owner>, owner> {\n public:\n  using ContextType = typename Service<TestService<owner>, owner>::ContextType;\n  static std::shared_ptr<TestService> Create() {\n    return std::make_shared<TestService>();\n  }\n\n  TestService() {\n    EXPECT_CALL(*this, OnInit);\n    EXPECT_CALL(*this, OnDestroy);\n  }\n\n  MOCK_METHOD(void, OnInit, (ServiceManager&, const ContextType&), (override));\n\n  MOCK_METHOD(void, OnDestroy, (), (override));\n\n  MOCK_METHOD(void, Hello, (int), (const));\n};\n\ntemplate <typename T>\nclass ServiceTest;\n\ntemplate <Owner owner>\nclass ServiceTest<OwnerEnum<owner>> : public BaseServiceTest {\n public:\n  ServiceTest() : BaseServiceTest(owner) {}\n  static constexpr Owner kOwner = owner;\n\n  using ServiceType = TestService<owner>;\n\n  fml::RefPtr<fml::TaskRunner> task_runner_ = std::get<+owner>(task_runners_);\n};\n\nusing Owners =\n    ::testing::Types<OwnerEnum<Owner::kPlatform>, OwnerEnum<Owner::kUI>,\n                     OwnerEnum<Owner::kRaster>, OwnerEnum<Owner::kIO>>;\n\nTYPED_TEST_SUITE(ServiceTest, Owners);\n\nTYPED_TEST(ServiceTest, ManageServices) {\n  class TestService : public Service<TestService, TestFixture::kOwner> {\n   public:\n    MOCK_METHOD(void, Hello, (int), (const));\n\n    static std::shared_ptr<TestService> Create() {\n      return std::make_shared<TestService>();\n    }\n  };\n  auto service = this->template GetService<TestService>();\n  EXPECT_TRUE(service);\n  service.Act([](auto& impl) { EXPECT_CALL(impl, Hello(233)); });\n  service.Act([](auto& impl) { impl.Hello(233); });\n  this->task_runner_->PostSyncTask([] {});\n}\n\nTYPED_TEST(ServiceTest, ManualRegisterServices) {\n  class TestService : public Service<TestService, TestFixture::kOwner,\n                                     ServiceFlags::kManualRegister> {\n   public:\n    explicit TestService(int a) : a_(a) {}\n    int a_;\n  };\n  Puppet<Owner::kPlatform, TestService> service =\n      this->template GetService<TestService>();\n  EXPECT_FALSE(service);\n  this->service_manager_->template RegisterService<TestService>(\n      std::make_shared<TestService>(233));\n  service = this->template GetService<TestService>();\n  EXPECT_TRUE(service);\n  service.Act([](auto& impl) { EXPECT_EQ(impl.a_, 233); });\n  service.Act([](auto& impl) { impl.a_++; });\n  service.Act([](auto& impl) { EXPECT_EQ(impl.a_, 234); });\n  this->task_runner_->PostSyncTask([] {});\n}\n\nTYPED_TEST(ServiceTest, CanGetOtherService) {\n  class DepService : public Service<DepService, Owner::kIO> {\n   public:\n    static std::shared_ptr<DepService> Create() {\n      return std::make_shared<DepService>();\n    }\n  };\n  class TestService : public Service<TestService, TestFixture::kOwner> {\n   public:\n    ServiceManager* manager;\n    Puppet<TestFixture::kOwner, DepService> dep_service;\n\n    MOCK_METHOD(void, Die, ());\n    MOCK_METHOD(void, Hello, ());\n\n    TestService() {\n      EXPECT_CALL(*this, Die);\n      EXPECT_CALL(*this, Hello);\n    }\n\n    ~TestService() { Die(); }\n\n    void OnInit(ServiceManager& manager,\n                const ServiceContext<TestFixture::kOwner>& context) override {\n      this->manager = &manager;\n      dep_service = manager.template GetService<DepService>();\n      dep_service.Act([](auto&) { return 1; },\n                      [weak_self = this->GetWeakPtr()](auto) {\n                        if (weak_self) {\n                          weak_self->Hello();\n                        }\n                      });\n    }\n\n    static std::shared_ptr<TestService> Create() {\n      return std::make_shared<TestService>();\n    }\n  };\n\n  auto service = this->template GetService<TestService>();\n\n  service.Act([](auto& impl) { EXPECT_TRUE(impl.dep_service); });\n\n  if constexpr (TestFixture::kOwner != Owner::kIO) {\n    this->Attach(Owner::kIO);\n  }\n  if constexpr (is_owner_same_thread<TestFixture::kOwner,\n                                     Owner::kPlatform>::value) {\n    fml::MessageLoop::GetCurrent().RunExpiredTasksNow();\n  } else {\n    this->task_runner_->PostSyncTask([] {});\n  }\n  std::get<+Owner::kIO>(this->task_runners_)->PostSyncTask([] {});\n  if constexpr (is_owner_same_thread<TestFixture::kOwner,\n                                     Owner::kPlatform>::value) {\n    fml::MessageLoop::GetCurrent().RunExpiredTasksNow();\n  } else {\n    this->task_runner_->PostSyncTask([] {});\n  }\n  std::get<+Owner::kIO>(this->task_runners_)->PostSyncTask([] {});\n}\n\nTYPED_TEST(ServiceTest, CallsOnInit) {\n  auto service = this->template GetService<typename TestFixture::ServiceType>();\n\n  this->task_runner_->PostSyncTask([] {});\n}\n\nTYPED_TEST(ServiceTest, CallsOnInitEvenNoAttachedBefore) {\n  auto service = this->template GetService<typename TestFixture::ServiceType>();\n\n  class TestPlatformService\n      : public Service<TestPlatformService, Owner::kPlatform> {\n   public:\n    using ContextType [[maybe_unused]] =\n        typename Service<TestPlatformService, Owner::kPlatform>::ContextType;\n\n    MOCK_METHOD(void, OnInit, (ServiceManager&, const ContextType&),\n                (override));\n\n    TestPlatformService() { EXPECT_CALL(*this, OnInit); }\n\n    static std::shared_ptr<TestPlatformService> Create() {\n      return std::make_shared<TestPlatformService>();\n    }\n  };\n\n  this->task_runner_->PostSyncTask([this] {\n    Puppet<TestFixture::kOwner, TestPlatformService> puppet =\n        this->service_manager_->template GetService<TestPlatformService>();\n  });\n  if constexpr (TestFixture::kOwner != Owner::kPlatform) {\n    this->Attach(Owner::kPlatform);\n    fml::MessageLoop::GetCurrent().RunExpiredTasksNow();\n  }\n}\n\nTYPED_TEST(ServiceTest, CreateNewActor) {\n  class MockObj : public ActorObject<TestFixture::kOwner> {\n   public:\n    MOCK_METHOD(int, Hello, (int a, int b));\n  };\n\n  class TestService : public Service<TestService, TestFixture::kOwner> {\n   public:\n    static std::shared_ptr<TestService> Create() {\n      return std::make_shared<TestService>();\n    }\n  };\n\n  MockFunction<void(int)> result_callback;\n\n  this->task_runner_->PostSyncTask([] {});\n\n  auto mock_obj = std::make_shared<MockObj>();\n  EXPECT_CALL(*mock_obj, Hello(233, 123)).WillOnce(Return(123456789));\n  EXPECT_CALL(result_callback, Call(123456789))\n      .WillOnce([current_runner =\n                     fml::MessageLoop::GetCurrent().GetTaskRunner()](auto) {\n        EXPECT_TRUE(current_runner->RunsTasksOnCurrentThread());\n      });\n  Puppet<Owner::kPlatform, TestService> service =\n      this->template GetService<TestService>();\n  Puppet<Owner::kPlatform, std::shared_ptr<MockObj>> mock_obj_puppet =\n      service.CreateObjectInActorThread(\n          [mock_obj](auto& impl) -> std::shared_ptr<MockObj> {\n            return mock_obj;\n          });\n\n  mock_obj_puppet.Act([](auto& impl) { return impl.Hello(233, 123); },\n                      result_callback.AsStdFunction());\n  fml::MessageLoop::GetCurrent().RunExpiredTasksNow();\n\n  this->task_runner_->PostSyncTask([] {});\n  fml::MessageLoop::GetCurrent().RunExpiredTasksNow();\n\n  this->task_runner_->PostSyncTask([] {});\n}\n\nTYPED_TEST(ServiceTest, ActorCallActor) {\n  class Listener : public ActorObject<Owner::kPlatform> {\n   public:\n    virtual int CallHello(int a, int b) = 0;\n  };\n\n  using ListenerActor = fml::WeakPtr<Listener>;\n\n  class OtherObj : public ActorObject<TestFixture::kOwner> {\n   public:\n    Puppet<TestFixture::kOwner, ListenerActor> platform_puppet;\n\n    void Hello() {\n      platform_puppet.Act(\n          [](auto& impl) { EXPECT_EQ(impl.CallHello(233, 123), 123456789); });\n    }\n  };\n\n  class PlatformObj : public Listener {\n   public:\n    MOCK_METHOD(int, Hello, (int a, int b));\n\n    int CallHello(int a, int b) override { return this->Hello(a, b); }\n\n    Puppet<Owner::kPlatform, std::unique_ptr<OtherObj>> other_puppet;\n    fml::WeakPtrFactory<Listener> weak_factory;\n    PlatformObj() : weak_factory(this) {}\n  };\n\n  PlatformObj obj;\n  EXPECT_CALL(obj, Hello(233, 123)).WillOnce(Return(123456789));\n\n  class TestService : public Service<TestService, TestFixture::kOwner> {\n   public:\n    static std::shared_ptr<TestService> Create() {\n      return std::make_shared<TestService>();\n    }\n  };\n\n  Puppet<Owner::kPlatform, TestService> service =\n      this->template GetService<TestService>();\n\n  Puppet<Owner::kPlatform, std::unique_ptr<OtherObj>> actor_puppet =\n      service.CreateObjectInActorThread(\n          [](auto&) { return std::make_unique<OtherObj>(); });\n\n  actor_puppet.PostObjectToActorThread(\n      obj.weak_factory.GetWeakPtr(),\n      [](OtherObj& obj, auto actor) { obj.platform_puppet = actor; });\n\n  actor_puppet.Act([](auto& impl) { impl.Hello(); });\n\n  this->task_runner_->PostSyncTask([] {});\n  fml::MessageLoop::GetCurrent().RunExpiredTasksNow();\n\n  this->task_runner_->PostSyncTask([] {});\n}\n\nTYPED_TEST(ServiceTest, EnableSyncCall) {\n  if constexpr (TestFixture::kOwner != Owner::kPlatform) {\n    this->Attach(Owner::kPlatform);\n  }\n  if constexpr (puppet_can_sync_call_actor<TestFixture::kOwner,\n                                           Owner::kPlatform>::value) {\n    class TestPlatformService\n        : public Service<TestPlatformService, Owner::kPlatform> {\n     public:\n      MOCK_METHOD(void, Hello, (int), ());\n\n      static std::shared_ptr<TestPlatformService> Create() {\n        return std::make_shared<TestPlatformService>();\n      }\n    };\n\n    Puppet<Owner::kPlatform, TestPlatformService> service =\n        this->template GetService<TestPlatformService>();\n    EXPECT_CALL(*service, Hello(233));\n\n    std::future<std::optional<bool>> f;\n    this->task_runner_->PostSyncTask([&] {\n      Puppet<TestFixture::kOwner, TestPlatformService> service =\n          this->service_manager_->template GetService<TestPlatformService>();\n      std::unique_ptr<int> u = std::make_unique<int>(233);\n      f = service.ActWithPromise([u = std::move(u)](auto& impl) {\n        impl.Hello(*u);\n        return true;\n      });\n    });\n    fml::MessageLoop::GetCurrent().RunExpiredTasksNow();\n    this->task_runner_->PostSyncTask([&] { EXPECT_EQ(f.get(), true); });\n  }\n}\n\nTYPED_TEST(ServiceTest, PendingCalls) {\n  class TestPlatformService\n      : public Service<TestPlatformService, Owner::kPlatform> {\n   public:\n    using ContextType [[maybe_unused]] =\n        typename Service<TestPlatformService, Owner::kPlatform>::ContextType;\n\n    MOCK_METHOD(void, OnInit, (ServiceManager&, const ContextType&),\n                (override));\n\n    TestPlatformService() { EXPECT_CALL(*this, OnInit); }\n\n    static std::shared_ptr<TestPlatformService> Create() {\n      return std::make_shared<TestPlatformService>();\n    }\n  };\n\n  MockFunction<void(TestPlatformService&)> callback;\n  MockFunction<void(int)> num_callback;\n  EXPECT_CALL(callback, Call);\n  EXPECT_CALL(num_callback, Call(233))\n      .WillOnce([current_runner = std::get<+TestFixture::kOwner>(\n                     this->task_runners_)](auto) {\n        EXPECT_TRUE(current_runner->RunsTasksOnCurrentThread());\n      });\n  this->task_runner_->PostSyncTask([&] {\n    Puppet<TestFixture::kOwner, TestPlatformService> puppet =\n        this->service_manager_->template GetService<TestPlatformService>();\n    puppet.Act(callback.AsStdFunction());\n    std::unique_ptr<int> u = std::make_unique<int>(233);\n    puppet.Act([u = std::move(u)](auto& impl) { return 233; },\n               num_callback.AsStdFunction());\n  });\n  fml::MessageLoop::GetCurrent().RunExpiredTasksNow();\n  Puppet<Owner::kPlatform, TestPlatformService> puppet =\n      this->template GetService<TestPlatformService>();\n  if constexpr (TestFixture::kOwner != Owner::kPlatform) {\n    this->Attach(Owner::kPlatform);\n  }\n  this->task_runner_->PostSyncTask([&] {});\n  EXPECT_TRUE(puppet->IsValid());\n}\n\nclass TestServiceWithServiceId\n    : public Service<TestServiceWithServiceId, Owner::kPlatform,\n                     ServiceFlags::kManualRegister> {\n public:\n  static constexpr ServiceId kServiceId = 123456789;\n};\n\nTYPED_TEST(ServiceTest, RegisterServiceWithServiceId) {\n  this->service_manager_->template RegisterService<TestServiceWithServiceId>(\n      std::make_shared<TestServiceWithServiceId>());\n  auto p = this->template GetService<TestServiceWithServiceId>();\n  if constexpr (TestFixture::kOwner != Owner::kPlatform) {\n    this->Attach(Owner::kPlatform);\n  }\n  EXPECT_EQ(p->GetServiceId(), TestServiceWithServiceId::kServiceId);\n}\n\n// Compile only, just make sure code editing works.\nnamespace test_service_typing {\ntemplate <Owner owner>\nclass TypingsTestService : public Service<TypingsTestService<owner>, owner,\n                                          ServiceFlags::kManualRegister |\n                                              ServiceFlags::kMultiThread> {\n public:\n  int ConstMethod() const { return a; }\n  void NonConstMethod() { a++; }\n\n  int a = 3;\n};\n\nstruct ActorObj : public ActorObject<Owner::kPlatform> {\n  int a = 3;\n  int ConstMethod() const { return a; }\n  void NonConstMethod() { a++; }\n};\n\nstruct Test {\n  void TestServiceTyping() {\n    void([] {\n      std::shared_ptr<ServiceManager> service_manager =\n          ServiceManager::Create({nullptr, nullptr, nullptr, nullptr});\n\n      {\n        using S = TypingsTestService<Owner::kPlatform>;\n        Puppet<Owner::kPlatform, S> p = service_manager->GetService<S>();\n        p->NonConstMethod();\n        p->ConstMethod();\n        p->a++;\n        p.Act([](auto&) {});\n        p.Act([](auto&) { return 233; }, [](auto) {});\n        // p->shared_from_this();\n      }\n\n      {\n        using S = TypingsTestService<Owner::kRaster>;\n        Puppet<Owner::kPlatform, S> p = service_manager->GetService<S>();\n        p->NonConstMethod();\n        p->ConstMethod();\n        p->a++;\n        p.Act([](auto&) {});\n        p.Act([](auto&) { return 233; }, [](auto) {});\n        // p.ActWithPromise([](auto&) { return true; }).get();\n      }\n\n      {\n        using S = TypingsTestService<Owner::kPlatform>;\n        Puppet<Owner::kUI, S> p = service_manager->GetService<S>();\n        p->NonConstMethod();\n        p->ConstMethod();\n        p->a++;\n        p.Act([](auto&) {});\n        p.Act([](auto&) { return 233; }, [](auto) {});\n        p.ActWithPromise([](auto&) { return true; }).get();\n      }\n    });\n  }\n\n  void TestActorTyping() {\n    void([] {\n      std::shared_ptr<ServiceManager> service_manager =\n          ServiceManager::Create({nullptr, nullptr, nullptr, nullptr});\n      {\n        using S = TypingsTestService<Owner::kPlatform>;\n        Puppet<Owner::kPlatform, S> p = service_manager->GetService<S>();\n\n        using A = std::unique_ptr<ActorObj>;\n        Puppet<Owner::kPlatform, A> a = p.CreateObjectInActorThread(\n            [](auto&) { return std::make_unique<ActorObj>(); });\n        a->NonConstMethod();\n        a->ConstMethod();\n        a->a++;\n        a.Act([](auto&) {});\n        a.Act([](auto&) { return 233; }, [](auto) {});\n        // a.ActWithPromise([](auto&) { return 233; });\n      }\n\n      {\n        using S = TypingsTestService<Owner::kIO>;\n        Puppet<Owner::kPlatform, S> p = service_manager->GetService<S>();\n\n        using A = std::unique_ptr<ActorObj>;\n        Puppet<Owner::kPlatform, A> a = p.CreateObjectInActorThread(\n            [](auto&) { return std::make_unique<ActorObj>(); });\n        // a->NonConstMethod();\n        // a->ConstMethod();\n        // a->a++;\n        a.Act([](auto&) {});\n        a.Act([](auto&) { return 233; }, [](auto) {});\n        // a.ActWithPromise([](auto&) {});\n      }\n\n      {\n        using S = TypingsTestService<Owner::kPlatform>;\n        Puppet<Owner::kUI, S> p = service_manager->GetService<S>();\n\n        using A = std::unique_ptr<ActorObj>;\n        Puppet<Owner::kUI, A> a = p.CreateObjectInActorThread(\n            [](auto&) { return std::make_unique<ActorObj>(); });\n        // a->NonConstMethod();\n        // a->ConstMethod();\n        // a->a++;\n        a.Act([](auto&) {});\n        a.Act([](auto&) { return 233; }, [](auto) {});\n        a.ActWithPromise([](auto&) { return 233; }).get();\n      }\n    });\n  }\n\n  void TestMultiThreadService() {\n    void([] {\n      std::shared_ptr<ServiceManager> service_manager =\n          ServiceManager::Create({nullptr, nullptr, nullptr, nullptr});\n      {\n        using S = TypingsTestService<Owner::kPlatform>;\n        auto s = service_manager->GetMultiThreadService<S>();\n        s->NonConstMethod();\n        s->ConstMethod();\n        s->a++;\n      }\n    });\n  }\n};\n\n}  // namespace test_service_typing\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/settings.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/settings.h\"\n\n#include <sstream>\n\nnamespace clay {\n\nbool Settings::enable_stencil_buffer;\nbool Settings::use_sdft_for_text;\n\nSettings::Settings() = default;\n\nSettings::Settings(const Settings& other) = default;\n\nSettings::~Settings() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/settings.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_SETTINGS_H_\n#define CLAY_COMMON_SETTINGS_H_\n\n#include <fcntl.h>\n\n#include <chrono>\n#include <cstdint>\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/fml/unique_fd.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/mapping.h\"\n\nnamespace clay {\n\n// TODO(26783): Deprecate all the \"path\" struct members in favor of the\n// callback that generates the mapping from these paths.\nusing MappingCallback = std::function<std::unique_ptr<fml::Mapping>(void)>;\nusing Mappings = std::vector<std::unique_ptr<const fml::Mapping>>;\nusing MappingsCallback = std::function<Mappings(void)>;\n\nstruct Settings {\n  Settings();\n\n  Settings(const Settings& other);\n\n  ~Settings();\n\n  std::string temp_directory_path;\n  // Isolate settings\n  bool trace_skia = false;\n  std::vector<std::string> trace_allowlist;\n  std::optional<std::vector<std::string>> trace_skia_allowlist;\n  bool dump_skp_on_shader_compilation = false;\n  bool cache_sksl = false;\n  bool purge_persistent_cache = false;\n\n  // Font settings\n  bool use_test_fonts = false;\n\n  bool use_asset_fonts = true;\n\n  // Indicates whether the embedding started a prefetch of the default font\n  // manager before creating the engine.\n  bool prefetched_default_font_manager = false;\n\n  // Selects the SkParagraph implementation of the text layout engine.\n  bool enable_skparagraph = false;\n\n  // Data set by platform-specific embedders for use in font initialization.\n  uint32_t font_initialization_data = 0;\n\n  // Engine settings\n  bool enable_software_rendering = false;\n  bool skia_deterministic_rendering_on_cpu = false;\n#ifndef NDEBUG\n  bool verbose_logging = true;\n#else\n  bool verbose_logging = false;\n#endif\n\n  // The icu_initialization_required setting does not have a corresponding\n  // switch because it is intended to be decided during build time, not runtime.\n  // Some companies apply source modification here because their build system\n  // brings its own ICU data files.\n  bool icu_initialization_required = true;\n  std::string icu_data_path;\n  MappingCallback icu_mapper;\n\n  // Assets settings\n  fml::UniqueFD::element_type assets_dir =\n      fml::UniqueFD::traits_type::InvalidValue();\n  std::string assets_path;\n\n  // This data will be available to the isolate immediately on launch via the\n  // PlatformDispatcher.getPersistentIsolateData callback. This is meant for\n  // information that the isolate cannot request asynchronously (platform\n  // messages can be used for that purpose). This data is held for the lifetime\n  // of the shell and is available on isolate restarts in the shell instance.\n  // Due to this, the buffer must be as small as possible.\n  std::shared_ptr<const fml::Mapping> persistent_isolate_data;\n\n  // Max bytes threshold of resource cache, or 0 for unlimited.\n  size_t resource_cache_max_bytes_threshold = 0;\n\n  /// The minimum number of samples to require in multipsampled anti-aliasing.\n  ///\n  /// Setting this value to 0 or 1 disables MSAA.\n  /// If it is not 0 or 1, it must be one of 2, 4, 8, or 16. However, if the\n  /// GPU does not support the requested sampling value, MSAA will be disabled.\n  uint8_t msaa_samples = 0;\n\n  static bool ShouldEnableStencilBuffer() { return enable_stencil_buffer; }\n  static void SetStencilBuffer(bool value) { enable_stencil_buffer = value; }\n\n  static bool ShouldUseSDFTForText() { return use_sdft_for_text; }\n  static void SetUseSDFTForText(bool value) { use_sdft_for_text = value; }\n\n  // Whether to use default focus ring.\n  bool enable_default_focus_ring = false;\n\n  // Whether to show performance overlay.\n  bool enable_performance_overlay = false;\n\n  // Whether create clay ui task runner.\n  bool enable_clay_ui_thread = false;\n\n  // clay memory cache related parameters\n  int image_texture_cache_max_limit = -1;\n  int low_end_image_texture_cache_max_limit = -1;\n\n  // Whether to use synchronous compositor.\n  bool enable_sync_compositor = false;\n  int gpu_resource_cache_multiplier = 1;\n\n  // Should be in range of [1,3]\n  int buffer_size_for_sync_compositor = 2;\n\n private:\n  static bool enable_stencil_buffer;\n  static bool use_sdft_for_text;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_SETTINGS_H_\n"
  },
  {
    "path": "clay/common/sha1.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/sha1.h\"\n\n#include <string>\n\n#include \"clay/fml/hex_codec.h\"\n\n#ifdef OS_IOS\n#include <openssl/sha.h>\n#else\n#include <cstdlib>\n\n#include \"third_party/boringssl/src/include/openssl/sha.h\"\n#endif\n#ifndef _WIN32\n#define __WEAK __attribute__((__weak__))\nextern \"C\" {\n// cSpell:disable\n__WEAK void CRYPTO_init_sysrand(void) {}\n__WEAK uint64_t CRYPTO_get_fork_generation(void) { return 0; }\n__WEAK void *OPENSSL_malloc(size_t size) { return malloc(size); }\n__WEAK void *OPENSSL_realloc(void *ptr, size_t size) {\n  return realloc(ptr, size);\n}\n__WEAK void OPENSSL_free(void *ptr) { free(ptr); }\n__WEAK void OPENSSL_cleanse(void *ptr, size_t len) {}\n__WEAK void CRYPTO_once(int32_t *once, void (*init)(void)) {\n  if (!*once) {\n    *once = 1;\n    init();\n  }\n}\n}\n#endif\n\nnamespace clay {\n\nstd::string SHA1HashBytes(const void *data, size_t size) {\n  if (data == nullptr || size == 0) {\n    return \"\";\n  }\n\n  uint8_t sha_digest[SHA_DIGEST_LENGTH];\n  SHA1(static_cast<const uint8_t *>(data), size, sha_digest);\n\n  std::string_view view(reinterpret_cast<const char *>(sha_digest),\n                        SHA_DIGEST_LENGTH);\n  return fml::HexEncode(view);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/sha1.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_SHA1_H_\n#define CLAY_COMMON_SHA1_H_\n\n#include <string>\n\nnamespace clay {\nstd::string SHA1HashBytes(const void* data, size_t size);\n\nstatic inline std::string SHA1HashString(const std::string& str) {\n  return SHA1HashBytes(str.c_str(), str.size());\n}\n}  // namespace clay\n\n#endif  // CLAY_COMMON_SHA1_H_\n"
  },
  {
    "path": "clay/common/sha1_include.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// This file is used to selectively introduce BoringSSL symbols on the Android\n// side.\n\n#include \"third_party/boringssl/src/crypto/fipsmodule/sha/sha1.cc.inc\"\n"
  },
  {
    "path": "clay/common/sys_info.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"clay/common/sys_info.h\"\n\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nstd::optional<bool> SysInfo::g_custom_is_low_end_device;\n\nint64_t SysInfo::AmountOfPhysicalMemory() {\n  FML_LOG(WARNING) << \"AmountOfPhysicalMemory not implemented.\";\n  return 0;\n}\nbool SysInfo::IsLowEndDevice() {\n  bool custom_is_low_end = false;\n  if (g_custom_is_low_end_device.has_value()) {\n    custom_is_low_end = g_custom_is_low_end_device.value();\n  }\n  return custom_is_low_end;\n}\nvoid SysInfo::SetCustomIsLowEndDevice(bool value) {\n  g_custom_is_low_end_device = value;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/sys_info.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_SYS_INFO_H_\n#define CLAY_COMMON_SYS_INFO_H_\n\n#include <cstdint>\n#include <optional>\n\nnamespace clay {\n\nclass SysInfo {\n public:\n  static int64_t AmountOfPhysicalMemory();\n  static bool IsLowEndDevice();\n  static void SetCustomIsLowEndDevice(bool value);\n\n private:\n  // Custom set by online settings or front end page.\n  static std::optional<bool> g_custom_is_low_end_device;\n};\n}  // namespace clay\n#endif  // CLAY_COMMON_SYS_INFO_H_\n"
  },
  {
    "path": "clay/common/task_runners.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/task_runners.h\"\n\n#include <utility>\n\nnamespace clay {\n\nTaskRunners::TaskRunners(std::string label,\n                         fml::RefPtr<fml::TaskRunner> platform,\n                         fml::RefPtr<fml::TaskRunner> raster,\n                         fml::RefPtr<fml::TaskRunner> ui,\n                         fml::RefPtr<fml::TaskRunner> io)\n    : label_(std::move(label)),\n      platform_(std::move(platform)),\n      raster_(std::move(raster)),\n      ui_(std::move(ui)),\n      io_(std::move(io)) {}\n\nTaskRunners::TaskRunners(const TaskRunners& other) = default;\n\nTaskRunners::~TaskRunners() = default;\n\nconst std::string& TaskRunners::GetLabel() const { return label_; }\n\nfml::RefPtr<fml::TaskRunner> TaskRunners::GetPlatformTaskRunner() const {\n  return platform_;\n}\n\nfml::RefPtr<fml::TaskRunner> TaskRunners::GetUITaskRunner() const {\n  return ui_;\n}\n\nfml::RefPtr<fml::TaskRunner> TaskRunners::GetIOTaskRunner() const {\n  return io_;\n}\n\nfml::RefPtr<fml::TaskRunner> TaskRunners::GetRasterTaskRunner() const {\n  return raster_;\n}\n\nbool TaskRunners::IsValid() const { return platform_ && raster_ && ui_ && io_; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/task_runners.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_TASK_RUNNERS_H_\n#define CLAY_COMMON_TASK_RUNNERS_H_\n\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/task_runner.h\"\n\nnamespace clay {\n\nclass TaskRunners {\n public:\n  TaskRunners(std::string label, fml::RefPtr<fml::TaskRunner> platform,\n              fml::RefPtr<fml::TaskRunner> raster,\n              fml::RefPtr<fml::TaskRunner> ui, fml::RefPtr<fml::TaskRunner> io);\n\n  TaskRunners(const TaskRunners& other);\n\n  ~TaskRunners();\n\n  const std::string& GetLabel() const;\n\n  fml::RefPtr<fml::TaskRunner> GetPlatformTaskRunner() const;\n\n  fml::RefPtr<fml::TaskRunner> GetUITaskRunner() const;\n\n  fml::RefPtr<fml::TaskRunner> GetIOTaskRunner() const;\n\n  fml::RefPtr<fml::TaskRunner> GetRasterTaskRunner() const;\n\n  bool IsValid() const;\n\n private:\n  const std::string label_;\n  fml::RefPtr<fml::TaskRunner> platform_;\n  fml::RefPtr<fml::TaskRunner> raster_;\n  fml::RefPtr<fml::TaskRunner> ui_;\n  fml::RefPtr<fml::TaskRunner> io_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_TASK_RUNNERS_H_\n"
  },
  {
    "path": "clay/common/thread_host.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/thread_host.h\"\n\n#if OS_ANDROID\n#include <android/looper.h>\n#endif\n\n#include <algorithm>\n#include <memory>\n#include <optional>\n#include <string>\n#include <utility>\n\nnamespace clay {\n\n#if OS_ANDROID\nstatic std::atomic_bool g_has_android_looper = false;\n#endif\n\nstatic std::shared_ptr<fml::Thread> g_clay_io_thread = nullptr;\n\nstd::string ThreadHost::ThreadHostConfig::MakeThreadName(\n    Type type, const std::string& prefix) {\n  switch (type) {\n    case Type::Platform:\n      return prefix + \".platform\";\n    case Type::UI:\n      return prefix + \".ui\";\n    case Type::IO:\n      return prefix + \".io\";\n    case Type::RASTER:\n      return prefix + \".raster\";\n    case Type::Profiler:\n      return prefix + \".profiler\";\n  }\n}\n\nvoid ThreadHost::ThreadHostConfig::SetIOConfig(const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::IO;\n  io_config = config;\n}\n\nvoid ThreadHost::ThreadHostConfig::SetUIConfig(const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::UI;\n  ui_config = config;\n}\n\nvoid ThreadHost::ThreadHostConfig::SetPlatformConfig(\n    const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::Platform;\n  platform_config = config;\n}\n\nvoid ThreadHost::ThreadHostConfig::SetRasterConfig(const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::RASTER;\n  raster_config = config;\n}\n\nvoid ThreadHost::ThreadHostConfig::SetProfilerConfig(\n    const ThreadConfig& config) {\n  type_mask |= ThreadHost::Type::Profiler;\n  profiler_config = config;\n}\n\nstd::unique_ptr<fml::Thread> ThreadHost::CreateThread(\n    Type type, std::optional<ThreadConfig> thread_config,\n    const ThreadHostConfig& host_config) const {\n  /// if not specified ThreadConfig, create a ThreadConfig.\n  if (!thread_config.has_value()) {\n    thread_config = ThreadConfig(\n        ThreadHostConfig::MakeThreadName(type, host_config.name_prefix));\n  }\n  return std::make_unique<fml::Thread>(host_config.config_setter,\n                                       *thread_config);\n}\n\nThreadHost::ThreadHost() = default;\n\nThreadHost::ThreadHost(ThreadHost&&) = default;\n\nThreadHost::ThreadHost(const std::string& name_prefix, uint64_t mask)\n    : ThreadHost(ThreadHostConfig(name_prefix, mask)) {}\n\nThreadHost::ThreadHost(const ThreadHostConfig& host_config)\n    : name_prefix(host_config.name_prefix) {\n  if (host_config.isThreadNeeded(ThreadHost::Type::Platform)) {\n    platform_thread =\n        CreateThread(Type::Platform, host_config.platform_config, host_config);\n  }\n\n  if (host_config.isThreadNeeded(ThreadHost::Type::UI)) {\n    ui_thread = CreateThread(Type::UI, host_config.ui_config, host_config);\n  }\n\n  if (host_config.isThreadNeeded(ThreadHost::Type::RASTER)) {\n    raster_thread =\n        CreateThread(Type::RASTER, host_config.raster_config, host_config);\n  }\n\n  if (host_config.isThreadNeeded(ThreadHost::Type::IO)) {\n    if (!g_clay_io_thread) {\n      g_clay_io_thread =\n          CreateThread(Type::IO, host_config.io_config, host_config);\n    }\n    io_thread = g_clay_io_thread;\n  }\n\n  if (host_config.isThreadNeeded(ThreadHost::Type::Profiler)) {\n    profiler_thread =\n        CreateThread(Type::Profiler, host_config.profiler_config, host_config);\n  }\n}\n\nThreadHost::~ThreadHost() = default;\n\n// static\nbool ThreadHost::HasALooperInRenderThread() {\n#if OS_ANDROID\n  return g_has_android_looper.load();\n#else\n  return false;\n#endif\n}\n// static\nvoid ThreadHost::CheckALooperInRenderThread() {\n#if OS_ANDROID\n  g_has_android_looper = ALooper_forThread() != nullptr;\n#endif\n}\n\nvoid ThreadHost::UpdateConfig(const ThreadHostConfig& config) {\n  if (ui_thread == nullptr && config.isThreadNeeded(Type::UI)) {\n    ui_thread = CreateThread(Type::UI, config.ui_config, config);\n  }\n  if (platform_thread == nullptr && config.isThreadNeeded(Type::Platform)) {\n    platform_thread =\n        CreateThread(Type::Platform, config.platform_config, config);\n  }\n  if (io_thread == nullptr && config.isThreadNeeded(Type::IO)) {\n    if (!g_clay_io_thread) {\n      g_clay_io_thread = CreateThread(Type::IO, config.io_config, config);\n    }\n    io_thread = g_clay_io_thread;\n  }\n  if (raster_thread == nullptr && config.isThreadNeeded(Type::RASTER)) {\n    raster_thread = CreateThread(Type::RASTER, config.raster_config, config);\n  }\n  if (profiler_thread == nullptr && config.isThreadNeeded(Type::Profiler)) {\n    profiler_thread =\n        CreateThread(Type::Profiler, config.profiler_config, config);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/thread_host.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_THREAD_HOST_H_\n#define CLAY_COMMON_THREAD_HOST_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/thread.h\"\n\nnamespace clay {\n\nusing ThreadConfig = fml::Thread::ThreadConfig;\nusing ThreadConfigSetter = fml::Thread::ThreadConfigSetter;\n\n/// The collection of all the threads used by the engine.\nstruct ThreadHost {\n  enum Type {\n    Platform = 1 << 0,\n    UI = 1 << 1,\n    RASTER = 1 << 2,\n    IO = 1 << 3,\n    Profiler = 1 << 4,\n  };\n\n  /// The collection of all the thread configures, and we create custom thread\n  /// configure in engine to info the thread.\n  struct ThreadHostConfig {\n    explicit ThreadHostConfig(\n        const ThreadConfigSetter& setter = fml::Thread::SetCurrentThreadName)\n        : type_mask(0), config_setter(setter) {}\n\n    ThreadHostConfig(\n        const std::string& name_prefix, uint64_t mask,\n        const ThreadConfigSetter& setter = fml::Thread::SetCurrentThreadName)\n        : type_mask(mask), name_prefix(name_prefix), config_setter(setter) {}\n\n    explicit ThreadHostConfig(\n        uint64_t mask,\n        const ThreadConfigSetter& setter = fml::Thread::SetCurrentThreadName)\n        : ThreadHostConfig(\"\", mask, setter) {}\n\n    /// Check if need to create thread.\n    bool isThreadNeeded(Type type) const { return type_mask & type; }\n\n    /// Use the prefix and thread type to generator a thread name.\n    static std::string MakeThreadName(Type type, const std::string& prefix);\n\n    /// Specified the UI Thread Config, meanwhile set the mask.\n    void SetUIConfig(const ThreadConfig&);\n\n    /// Specified the Platform Thread Config, meanwhile set the mask.\n    void SetPlatformConfig(const ThreadConfig&);\n\n    /// Specified the IO Thread Config, meanwhile set the mask.\n    void SetRasterConfig(const ThreadConfig&);\n\n    /// Specified the IO Thread Config, meanwhile set the mask.\n    void SetIOConfig(const ThreadConfig&);\n\n    /// Specified the ProfilerThread  Config, meanwhile set the mask.\n    void SetProfilerConfig(const ThreadConfig&);\n\n    uint64_t type_mask;\n\n    std::string name_prefix = \"\";\n\n    const ThreadConfigSetter config_setter;\n\n    std::optional<ThreadConfig> platform_config;\n    std::optional<ThreadConfig> ui_config;\n    std::optional<ThreadConfig> raster_config;\n    std::optional<ThreadConfig> io_config;\n    std::optional<ThreadConfig> profiler_config;\n  };\n\n  std::string name_prefix;\n  std::unique_ptr<fml::Thread> platform_thread;\n  std::unique_ptr<fml::Thread> ui_thread;\n  std::unique_ptr<fml::Thread> raster_thread;\n  std::shared_ptr<fml::Thread> io_thread;\n  std::unique_ptr<fml::Thread> profiler_thread;\n\n  ThreadHost();\n\n  ThreadHost(ThreadHost&&);\n\n  ThreadHost& operator=(ThreadHost&&) = default;\n\n  ThreadHost(const std::string& name_prefix, uint64_t mask);\n\n  explicit ThreadHost(const ThreadHostConfig& host_config);\n\n  ~ThreadHost();\n  // Android only\n  // Must be called first time in Render Thread.\n  static bool HasALooperInRenderThread();\n  static void CheckALooperInRenderThread();\n  void UpdateConfig(const ThreadHostConfig& config);\n\n private:\n  std::unique_ptr<fml::Thread> CreateThread(\n      Type type, std::optional<ThreadConfig> thread_config,\n      const ThreadHostConfig& host_config) const;\n\n  std::shared_ptr<fml::Thread> GetThreadSingleton(\n      Type type, std::optional<ThreadConfig> thread_config,\n      const ThreadHostConfig& host_config);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_THREAD_HOST_H_\n"
  },
  {
    "path": "clay/common/thread_host_holder.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/thread_host_holder.h\"\n\n#include <memory>\n\nnamespace clay {\n\nThreadHostHolder& ThreadHostHolder::Instance() {\n  static ThreadHostHolder holder;\n  return holder;\n}\n\nThreadHostHolder::ThreadHostHolder() = default;\n\nvoid ThreadHostHolder::SetConfig(\n    const clay::ThreadHost::ThreadHostConfig& config) {\n  if (thread_host_ == nullptr) {\n    thread_host_ = std::make_shared<ThreadHost>(config);\n    return;\n  }\n  thread_host_->UpdateConfig(config);\n}\n\nvoid ThreadHostHolder::SetMask(uint64_t mask, const std::string& thread_label,\n                               const ThreadConfigSetter& setter) {\n  std::string name_prefix = \"rk\" + thread_label;\n  clay::ThreadHost::ThreadHostConfig host_config(name_prefix, mask, setter);\n\n  if (mask & ThreadHost::Type::Platform) {\n    host_config.io_config = fml::Thread::ThreadConfig(\n        clay::ThreadHost::ThreadHostConfig::MakeThreadName(\n            clay::ThreadHost::Type::Platform, name_prefix),\n        fml::Thread::ThreadPriority::LOW);\n  }\n\n  if (mask & ThreadHost::Type::UI) {\n    host_config.ui_config = fml::Thread::ThreadConfig(\n        clay::ThreadHost::ThreadHostConfig::MakeThreadName(\n            clay::ThreadHost::Type::UI, name_prefix),\n        fml::Thread::ThreadPriority::NORMAL);\n  }\n\n  if (mask & ThreadHost::Type::RASTER) {\n    host_config.raster_config = fml::Thread::ThreadConfig(\n        clay::ThreadHost::ThreadHostConfig::MakeThreadName(\n            clay::ThreadHost::Type::RASTER, name_prefix),\n        fml::Thread::ThreadPriority::HIGH);\n  }\n\n  if (mask & ThreadHost::Type::IO) {\n    host_config.io_config = fml::Thread::ThreadConfig(\n        clay::ThreadHost::ThreadHostConfig::MakeThreadName(\n            clay::ThreadHost::Type::IO, name_prefix),\n        fml::Thread::ThreadPriority::LOW);\n  }\n\n  if (mask & ThreadHost::Type::Profiler) {\n    host_config.io_config = fml::Thread::ThreadConfig(\n        clay::ThreadHost::ThreadHostConfig::MakeThreadName(\n            clay::ThreadHost::Type::Profiler, name_prefix),\n        fml::Thread::ThreadPriority::LOW);\n  }\n  SetConfig(host_config);\n}\n\nconst std::shared_ptr<ThreadHost> ThreadHostHolder::GetThreadHost() const {\n  return thread_host_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/common/thread_host_holder.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_COMMON_THREAD_HOST_HOLDER_H_\n#define CLAY_COMMON_THREAD_HOST_HOLDER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/common/thread_host.h\"\n\nnamespace clay {\n\n// Singleton to contain ThreadHost.\nclass ThreadHostHolder {\n public:\n  static ThreadHostHolder& Instance();\n\n  ThreadHostHolder();\n\n  void SetMask(\n      uint64_t mask, const std::string& thread_label = \"\",\n      const ThreadConfigSetter& setter = fml::Thread::SetCurrentThreadName);\n\n  void SetConfig(const clay::ThreadHost::ThreadHostConfig& config);\n\n  const std::shared_ptr<ThreadHost> GetThreadHost() const;\n\n private:\n  std::shared_ptr<ThreadHost> thread_host_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_COMMON_THREAD_HOST_HOLDER_H_\n"
  },
  {
    "path": "clay/example/glfw/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\nimport(\"../../shell/config.gni\")\n\nif (is_win) {\n  import(\"//build/config/win/win.gni\")\n}\n\ngroup(\"glfw\") {\n  deps = [ \":clay_glfw\" ]\n}\n\nexecutable(\"clay_glfw\") {\n  sources = [\n    \"clay_glfw.cc\",\n    \"clay_logo.h\",\n    \"custom_task_runner.cc\",\n    \"custom_task_runner.h\",\n    \"event_loop.cc\",\n    \"event_loop.h\",\n    \"platform_view_impl.h\",\n    \"shell_impl.cc\",\n    \"shell_impl.h\",\n    \"surface_gl_impl.h\",\n    \"thread_host_holder.cc\",\n    \"thread_host_holder.h\",\n  ]\n\n  if (is_win || is_mac) {\n    sources += [\n      \"../../ui/platform/common/native_view_service_common.cc\",\n      \"../../ui/platform/common/native_view_service_common.h\",\n    ]\n  }\n\n  deps = [\n    \"../../../base/src:base_static\",\n    \"../../common/graphics:gl_scoped_binder\",\n    \"../../fml:fml\",\n    \"../../fml:icu_util\",\n    \"../../shell/common\",\n    \"../../shell/gpu:gpu_surface_gl\",\n    \"../../ui\",\n    \"//build/secondary/third_party/glfw\",\n    \"//third_party/skia\",\n  ]\n\n  ldflags = []\n  if (is_linux) {\n    ldflags += [\n      \"-lrt\",\n      \"-lepoxy\",\n    ]\n  }\n\n  defines = [ \"GLFW_INCLUDE_NONE\" ]\n  libs = []\n  if (is_win && use_flutter_cxx) {\n    if (windows_runtime_library_mode == \"md\") {\n      libs += [ \"msvcprt.lib\" ]\n    } else if (windows_runtime_library_mode == \"mt\") {\n      libs += [ \"libcpmt.lib\" ]\n    }\n  }\n\n  configs += [ \"../../:config\" ]\n}\n"
  },
  {
    "path": "clay/example/glfw/README.md",
    "content": "# Clay engine GLFW Example\n## Description\nThis is an example of how to use Clay Engine on MacOSX or Windows environment.\n\nIn this example we are demonstrating rendering UI inside of the GUI library\n[GLFW](https://www.glfw.org/).\n\n## Running Instructions\nThis example was tested on MacOSX, Windows and Ubuntu 22.04.\n\nThe example depends on GLFW:\n* [GLFW](https://www.glfw.org/) - This can be installed with [Homebrew](https://brew.sh/) - `brew install glfw`\n\nIn order to **build** and **run** the example you should be able to go into root directory and run:\n* MacOSX or Linux\n```shell\nsource tools/envsetup.sh\ntools/hab sync . -f --target clay\nbuildtools/gn/gn gen out/Default --args='enable_clay_standalone = true disable_visibility_hidden = true use_ndk_static_cxx = false enable_linker_map = false enable_clay = true is_headless = true skia_enable_flutter_defines = true skia_use_dng_sdk = false skia_use_sfntly = false skia_enable_pdf = false skia_enable_svg = true enable_svg = true skia_enable_skottie = true skia_use_x11 = false skia_use_wuffs = true skia_use_expat = true skia_use_fontconfig = false clay_enable_skshaper = true skia_use_icu = true skia_gl_standard = \"\" allow_deprecated_api_calls = true stripped_symbols = true is_official_build = true is_debug = false use_clang_static_analyzer = false enable_lto = false' --export-compile-commands\nbuildtools/ninja/ninja -C out/Default clay/example/glfw\n./out/Default/clay_glfw\n```\n\n* Windows\n```shell\ntools\\envsetup.ps1\ntools\\hab.ps1 sync . -f --target clay\nbuildtools\\gn\\gn.exe gen out\\Default --args=\"enable_clay_standalone = true disable_visibility_hidden = true use_ndk_static_cxx = false  enable_linker_map = false enable_clay = true is_headless = true skia_enable_flutter_defines = true  skia_use_dng_sdk = false skia_use_sfntly = false skia_enable_pdf = false skia_enable_svg = true enable_svg = true skia_enable_skottie = true skia_use_x11 = false skia_use_wuffs = true skia_use_expat = true skia_use_fontconfig = false clay_enable_skshaper = true skia_use_icu = true allow_deprecated_api_calls = true stripped_symbols = true is_official_build = true is_debug = false use_clang_static_analyzer = false enable_lto = false enable_libcpp_abi_namespace_cr = true use_flutter_cxx = true enable_desktop_embeddings = true is_clang = true\"\nbuildtools\\ninja\\ninja.exe -C out\\Default\\ clay\\example\\glfw\n.\\out\\Default\\clay_glfw.exe\n```\n"
  },
  {
    "path": "clay/example/glfw/clay_glfw.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <GLFW/glfw3.h>  // NOLINT\n\n#include <chrono>\n#include <deque>\n#include <functional>\n#include <iostream>\n#include <mutex>\n#include <string>\n#include <string_view>\n\n// A hack to forbid GL includes from gl_api_common.h\n// We don't want to include platform GL api.\n// We use API from GLFW instead\n#define CLAY_COMMON_GRAPHICS_GL_GL_API_COMMON_H_\n#include \"clay/common/graphics/gl/gl_api.h\"\n\nnamespace clay {\n\nclass GlApiCommon : public GlApi {\n public:\n  static GlApiCommon* GetInstance() { return nullptr; }\n\n  void GlGetIntegerv(uint32_t pname, int32_t* data) override {}\n  void GlBindFramebuffer(uint32_t target, uint32_t framebuffer) override {}\n  void GlBindTexture(uint32_t target, uint32_t texture) override {}\n};\n\n}  // namespace clay\n\n#include \"build/build_config.h\"\n#include \"clay/common/graphics/gl/scoped_framebuffer_binder.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/example/glfw/event_loop.h\"\n#include \"clay/example/glfw/shell_impl.h\"\n#include \"clay/example/glfw/surface_gl_impl.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/gpu/gpu_surface_gl_skia.h\"\n#include \"third_party/skia/include/gpu/ganesh/SkImageGanesh.h\"\n#include \"third_party/skia/include/gpu/gl/GrGLInterface.h\"\n#include \"third_party/skia/src/gpu/gl/GrGLDefines.h\"  // nogncheck\n\nclass GLDelegateGLFWWindow : public clay::GPUSurfaceGLDelegate {\n public:\n  explicit GLDelegateGLFWWindow(GLFWwindow* window) : window_(window) {}\n\n  std::unique_ptr<clay::GLContextResult> GLContextMakeCurrent() override {\n    glfwMakeContextCurrent(window_);\n    return std::make_unique<clay::GLContextDefaultResult>(true);\n  }\n\n  bool GLContextClearCurrent() override {\n    glfwMakeContextCurrent(nullptr);\n    return true;\n  }\n\n  bool GLContextPresent(const clay::GLPresentInfo& present_info) override {\n    glfwSwapBuffers(window_);\n    return true;\n  }\n\n  clay::GLFBOInfo GLContextFBO(clay::GLFrameInfo frame_info) const override {\n    return clay::GLFBOInfo{.fbo_id = 0, .existing_damage = {}};\n  }\n\n  GLProcResolver GetGLProcResolver() const override {\n    return [](const char* name) -> void* {\n      if (strncmp(name, \"egl\", 3) == 0) {\n        return nullptr;\n      } else {\n        return reinterpret_cast<void*>(glfwGetProcAddress(name));\n      }\n    };\n  }\n\n private:\n  GLFWwindow* window_;\n};\n\n// This value is calculated after the window is created.\nstatic double g_pixelRatio = 1.0;\nstatic const size_t kInitialWindowWidth = 1000;\nstatic const size_t kInitialWindowHeight = 800;\n\nvoid GLFWcursorPositionCallbackAtPhase(GLFWwindow* window,\n                                       ClayPointerPhase phase, double x,\n                                       double y) {\n  ClayPointerEvent event = {};\n  event.struct_size = sizeof(event);\n  event.phase = phase;\n  event.x = x * g_pixelRatio;\n  event.y = y * g_pixelRatio;\n  event.timestamp =\n      std::chrono::duration_cast<std::chrono::microseconds>(\n          std::chrono::high_resolution_clock::now().time_since_epoch())\n          .count();\n  reinterpret_cast<clay::example::ShellImpl*>(glfwGetWindowUserPointer(window))\n      ->SendPointerEvents(&event, 1);\n}\n\nvoid GLFWcursorPositionCallback(GLFWwindow* window, double x, double y) {\n  GLFWcursorPositionCallbackAtPhase(window, kClayPointerPhaseMove, x, y);\n}\n\nvoid GLFWmouseButtonCallback(GLFWwindow* window, int key, int action,\n                             int mods) {\n  if (key == GLFW_MOUSE_BUTTON_1 && action == GLFW_PRESS) {\n    double x, y;\n    glfwGetCursorPos(window, &x, &y);\n    GLFWcursorPositionCallbackAtPhase(window, kClayPointerPhaseDown, x, y);\n    glfwSetCursorPosCallback(window, GLFWcursorPositionCallback);\n  }\n\n  if (key == GLFW_MOUSE_BUTTON_1 && action == GLFW_RELEASE) {\n    double x, y;\n    glfwGetCursorPos(window, &x, &y);\n    GLFWcursorPositionCallbackAtPhase(window, kClayPointerPhaseUp, x, y);\n    glfwSetCursorPosCallback(window, nullptr);\n  }\n}\n\nstatic void GLFWKeyCallback(GLFWwindow* window, int key, int scancode,\n                            int action, int mods) {\n  if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {\n    glfwSetWindowShouldClose(window, GLFW_TRUE);\n  }\n}\n\nvoid GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) {\n  reinterpret_cast<clay::example::ShellImpl*>(glfwGetWindowUserPointer(window))\n      ->SendViewportMetrics(width * g_pixelRatio, height * g_pixelRatio,\n                            g_pixelRatio);\n}\n\nclass GLRenderer final : public clay::example::SurfaceDelegate,\n                         public clay::GlApi {\n public:\n  GLRenderer(GLFWwindow* window, clay::GPUSurfaceGLSkia& window_surface)\n      : window_(window), window_surface_(window_surface) {\n    // invisible window\n    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);\n    offscreen_context_ = glfwCreateWindow(640, 480, \"\", nullptr, window);\n    glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE);\n  }\n\n  ~GLRenderer() {\n    front_image_.reset();\n    for (auto& texture : textures_) {\n      window_surface_.GetContext()->deleteBackendTexture(texture);\n    }\n    glfwDestroyWindow(offscreen_context_);\n  }\n\n  // Main thread\n  void DrawToWindow() {\n    int width, height;\n    glfwGetFramebufferSize(window_, &width, &height);\n\n    std::unique_ptr<clay::SurfaceFrame> frame =\n        window_surface_.AcquireFrame({width, height});\n    SkCanvas* canvas = frame->GetCanvas();\n    canvas->clear(SkColor4f{.9f, .3f, .3f, 1.0f});\n\n    {\n      std::lock_guard<std::mutex> lock(mutex_);\n      if (!textures_.empty()) {\n        front_image_ = SkImages::AdoptTextureFrom(\n            window_surface_.GetContext(), textures_.front(),\n            kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType);\n        textures_.pop_front();\n      }\n    }\n\n    if (!front_image_) {\n      FML_LOG(ERROR) << \"Clay: No front_image_!\";\n      return;\n    }\n\n    canvas->drawImageRect(front_image_, SkRect::MakeIWH(width, height),\n                          SkSamplingOptions());\n\n    frame->Submit();\n\n    static GrGLFunction<GrGLFinishFn> glFinishFn =\n        GetFunction<GrGLFinishFn>(\"glFinish\");\n    glFinishFn();\n  }\n\n  void GlGetIntegerv(uint32_t pname, int32_t* data) override {\n    static GrGLFunction<GrGLGetIntegervFn> glGetIntegerv =\n        GetFunction<GrGLGetIntegervFn>(\"glGetIntegerv\");\n    glGetIntegerv(pname, data);\n  }\n\n  void GlBindFramebuffer(uint32_t target, uint32_t framebuffer) override {\n    static GrGLFunction<GrGLBindFramebufferFn> glBindFramebuffer =\n        GetFunction<GrGLBindFramebufferFn>(\"glBindFramebuffer\");\n    glBindFramebuffer(target, framebuffer);  // NOLINT(clay_custom/gl_bind)\n  }\n\n  void GlBindTexture(uint32_t target, uint32_t texture) override {\n    static GrGLFunction<GrGLBindTextureFn> glBindTexture =\n        GetFunction<GrGLBindTextureFn>(\"glBindTexture\");\n    glBindTexture(target, texture);  // NOLINT(clay_custom/gl_bind)\n  }\n\n private:\n  template <typename Fn>\n  std::add_pointer_t<Fn> GetFunction(const char* name) {\n    return reinterpret_cast<std::add_pointer_t<Fn>>(\n        reinterpret_cast<void*>(glfwGetProcAddress(name)));\n  }\n\n  // raster thread\n  // SurfaceDelegate\n  bool MakeCurrent() override {\n    glfwMakeContextCurrent(offscreen_context_);\n    return true;\n  }\n\n  bool ClearCurrent() override {\n    ClearFBO();\n    glfwMakeContextCurrent(nullptr);\n    return true;\n  }\n\n  bool Present() override {\n    glfwSwapBuffers(offscreen_context_);\n    static GrGLFunction<GrGLFinishFn> glFinishFn =\n        GetFunction<GrGLFinishFn>(\"glFinish\");\n    glFinishFn();\n    {\n      std::lock_guard<std::mutex> lock(mutex_);\n      textures_.push_back(current_write_texture_);\n      current_write_texture_ = {};\n    }\n    return true;\n  }\n\n  void* GetGLProcResolver(const char* what) const override {\n    if (strncmp(what, \"egl\", 3) == 0) {\n      return nullptr;\n    } else {\n      return reinterpret_cast<void*>(glfwGetProcAddress(what));\n    }\n  }\n\n  uint32_t FBO(const ClayFrameInfo* frame_info) override {\n    static GrGLFunction<GrGLGenFramebuffersFn> glGenFramebuffersFn =\n        GetFunction<GrGLGenFramebuffersFn>(\"glGenFramebuffers\");\n    static GrGLFunction<GrGLGenTexturesFn> glGenTexturesFn =\n        GetFunction<GrGLGenTexturesFn>(\"glGenTextures\");\n    static GrGLFunction<GrGLDeleteTexturesFn> glDeleteTexturesFn =\n        GetFunction<GrGLDeleteTexturesFn>(\"glDeleteTextures\");\n    static GrGLFunction<GrGLTexParameteriFn> glTexParameteriFn =\n        GetFunction<GrGLTexParameteriFn>(\"glTexParameteri\");\n    static GrGLFunction<GrGLTexImage2DFn> glTexImage2DFn =\n        GetFunction<GrGLTexImage2DFn>(\"glTexImage2D\");\n\n    static GrGLFunction<GrGLFramebufferTexture2DFn> glFramebufferTexture2DFn =\n        GetFunction<GrGLFramebufferTexture2DFn>(\"glFramebufferTexture2D\");\n    static GrGLFunction<GrGLCheckFramebufferStatusFn>\n        glCheckFramebufferStatusFn = GetFunction<GrGLCheckFramebufferStatusFn>(\n            \"glCheckFramebufferStatus\");\n\n    if (fbo_ == 0) {\n      glGenFramebuffersFn(1, &fbo_);\n    }\n\n    uint32_t texture;\n    glGenTexturesFn(1, &texture);\n    clay::ScopedTextureBinder scoped_texture_binder(GR_GL_TEXTURE_2D, texture,\n                                                    this);\n    glTexParameteriFn(GR_GL_TEXTURE_2D, GR_GL_TEXTURE_MIN_FILTER, GR_GL_LINEAR);\n    glTexParameteriFn(GR_GL_TEXTURE_2D, GR_GL_TEXTURE_MAG_FILTER, GR_GL_LINEAR);\n    glTexParameteriFn(GR_GL_TEXTURE_2D, GR_GL_TEXTURE_WRAP_S,\n                      GR_GL_CLAMP_TO_EDGE);\n    glTexParameteriFn(GR_GL_TEXTURE_2D, GR_GL_TEXTURE_WRAP_T,\n                      GR_GL_CLAMP_TO_EDGE);\n    glTexImage2DFn(GR_GL_TEXTURE_2D, 0, GR_GL_RGBA8, frame_info->width,\n                   frame_info->height, 0, GR_GL_RGBA, GR_GL_UNSIGNED_BYTE,\n                   nullptr);\n\n    clay::ScopedFramebufferBinder scoped_fbo_binder(GR_GL_FRAMEBUFFER, fbo_,\n                                                    this);\n    glFramebufferTexture2DFn(GR_GL_FRAMEBUFFER, GR_GL_COLOR_ATTACHMENT0,\n                             GR_GL_TEXTURE_2D, texture, 0);\n\n    if (glCheckFramebufferStatusFn(GR_GL_FRAMEBUFFER) !=\n        GR_GL_FRAMEBUFFER_COMPLETE) {\n      FML_LOG(ERROR) << \"Clay: GL_FRAMEBUFFER NOT COMPLETE\";\n      glDeleteTexturesFn(1, &texture);\n      ClearFBO();\n      return 0;\n    }\n\n    GrGLTextureInfo gl_texture_info{\n        .fTarget = GR_GL_TEXTURE_2D,\n        .fID = texture,\n        .fFormat = GR_GL_RGBA8,\n    };\n    current_write_texture_ =\n        GrBackendTexture(frame_info->width, frame_info->height,\n                         GrMipmapped::kNo, gl_texture_info);\n\n    return fbo_;\n  }\n\n  void ClearFBO() {\n    if (fbo_ != 0) {\n      static GrGLFunction<GrGLDeleteFramebuffersFn> glDeleteFrameBuffersFn =\n          GetFunction<GrGLDeleteFramebuffersFn>(\"glDeleteFramebuffers\");\n      glDeleteFrameBuffersFn(1, &fbo_);\n      fbo_ = 0;\n    }\n  }\n\n  GLFWwindow* window_;\n  GLFWwindow* offscreen_context_;\n  clay::GPUSurfaceGLSkia& window_surface_;\n  sk_sp<SkImage> front_image_;\n  std::deque<GrBackendTexture> textures_;\n  GrBackendTexture current_write_texture_;\n  std::mutex mutex_;\n  uint32_t fbo_ = 0;\n};\n\nvoid GLFW_ErrorCallback(int error, const char* description) {\n  std::cerr << \"GLFW Error: (\" << error << \") \" << description << std::endl;\n}\n\nint main(int argc, const char* argv[]) {\n  std::string icudtl_path = \"./icudtl.dat\";\n\n  glfwSetErrorCallback(GLFW_ErrorCallback);\n\n  /* Initialize the library */\n  if (!glfwInit()) {\n    return -1;\n  }\n\n  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GR_GL_TRUE);\n\n  /* Create a windowed mode window and its OpenGL context */\n  glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE);\n  GLFWwindow* window =\n      glfwCreateWindow(kInitialWindowWidth, kInitialWindowHeight, \"Clay Engine\",\n                       nullptr, nullptr);\n  if (!window) {\n    glfwTerminate();\n    return -1;\n  }\n\n  /* Make the window's context current */\n  glfwMakeContextCurrent(window);\n\n  glfwSwapInterval(1);\n\n  int framebuffer_width, framebuffer_height;\n  glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height);\n  g_pixelRatio = framebuffer_width / kInitialWindowWidth;\n\n  {\n    GLDelegateGLFWWindow delegate(window);\n    clay::GPUSurfaceGLSkia window_surface(&delegate, true);\n    std::unique_ptr<GLRenderer> renderer =\n        std::make_unique<GLRenderer>(window, window_surface);\n    if (!renderer) {\n      return -1;\n    }\n\n    clay::example::ShellImpl* engine = nullptr;\n\n    clay::example::EventLoop event_loop(std::this_thread::get_id(),\n                                        [&](const ClayTask* task) {\n                                          if (engine) {\n                                            engine->RunTask(task);\n                                          }\n                                        });\n\n    ClayTaskRunnerDescription platform_task_runner_desc{};\n    platform_task_runner_desc.struct_size = sizeof(ClayTaskRunnerDescription);\n    platform_task_runner_desc.user_data = &event_loop;\n    platform_task_runner_desc.runs_task_on_current_thread_callback =\n        [](void* user_data) -> bool {\n      return reinterpret_cast<clay::example::EventLoop*>(user_data)\n          ->RunsTasksOnCurrentThread();\n    };\n    platform_task_runner_desc.post_task_callback =\n        [](ClayTask task, uint64_t target_time, void* user_data) {\n          reinterpret_cast<clay::example::EventLoop*>(user_data)->PostTask(\n              task, target_time);\n        };\n    platform_task_runner_desc.identifier =\n        reinterpret_cast<intptr_t>(&event_loop);\n\n    engine = new clay::example::ShellImpl(icudtl_path.c_str(),  // icu data\n                                          renderer.get(),  // surface delegate\n                                          &platform_task_runner_desc);\n    engine->DrawUI();\n\n    glfwSetWindowUserPointer(window, engine);\n    GLFWwindowSizeCallback(window, kInitialWindowWidth, kInitialWindowHeight);\n\n    glfwSetKeyCallback(window, GLFWKeyCallback);\n    glfwSetWindowSizeCallback(window, GLFWwindowSizeCallback);\n    glfwSetMouseButtonCallback(window, GLFWmouseButtonCallback);\n\n    while (!glfwWindowShouldClose(window)) {\n      event_loop.WaitForEvents(std::chrono::milliseconds(32));\n      renderer->DrawToWindow();\n    }\n\n    delete engine;\n  }\n\n  glfwDestroyWindow(window);\n  glfwTerminate();\n\n  return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "clay/example/glfw/clay_logo.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_EXAMPLE_GLFW_CLAY_LOGO_H_\n#define CLAY_EXAMPLE_GLFW_CLAY_LOGO_H_\n\nnamespace clay {\nnamespace example {\n\nconstexpr char kClayLogo[] =\n    \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA/\"\n    \"4AAAQACAMAAABvfXIYAAADAFBMVEUAAADExMj1oGb1gq3sc2rnVHs6keTpmc0zdOcweOfxh8zx\"\n    \"hsxF3t1D3d3HxNdR3N3+/kynztrj/27mVHcRERH////dQobym8TgRoPiSn5ySNjWPY/\"\n    \"aP4vdus91TdZ2U9Y3Y+\"\n    \"b1ollMTeE7XeVZRd7RO5R2W9VFUuNAWOTtdWNSSeDxn8v2p2HNOplL1t1gQ93yk1czaOb0icn0\"\n    \"hcPvemBuRNkvbudqQ9rzmFfXvtHvf13zfMr0nFfltMg83t7IOp70g8r0fcTxiVrzdsNlQtw5a+\"\n    \"X0kMQpeuhIcuQsdOfyccf1pVv0er05eub4n3vwhFt1Y9U31Nw02d1K3d7prsz3pW4/\"\n    \"1N3RwdPyjlj4mobup8tC290y3d30eLYyfedbZuDjtc72pl06ztz3lorylsrzdsp0atRIv9rLxN\"\n    \"TExtX0dLw9c+\"\n    \"X3pmr1e7BUaOH4nIDEOaP4onc1cub2gKphYd5fm9b4o3L2kZm8OqvsrcT2hKNjlNblTnr2grBe\"\n    \"09xd291tW9xcodfwpcRycdXAOafzccL1fbfsbWpFxNrtb2VpiNVBzNxYp9hKa+\"\n    \"P3lpH3j5F8Utb3i5hnXt0qgOhufdTrZ25T3N1CbeRm2d1wd9Vmjtb1n1e2ytf3jKK3Oq6YRcfs\"\n    \"aGiTR8r3p2dLu9lsgtXrYm2dQ8RVrNh21d2KS8+0O7FRbuP2h6rpXXEyd+\"\n    \"f3p2RV1d2FTdLzj8tNt9miyteOSc1zWNpAx9uuydaBUNRDeeawPLXpXnVSr9joWHS8x9RPs9mQ\"\n    \"ydj3iZ5/\"\n    \"yNmnP7ytPbfqYnKN0NurzNlm0NyhQcFwztuZyth9z9u9ydds1NyW0NqkQL991dyrPrqF09yHzd\"\n    \"p3zNuez9pv2d2Hx9h5VtmlztqxzNgxKVtIRkhbJj2Njo8uQ1lKI09mZ2ksWWCno6X//Tx6fH/\"\n    \"tJ56+KMzWIbOeP94vPog8Wc17/7n0m152T2HxtHy6dZVEkJjy/2C0sbWa/6eL/7F2r786S6zS/\"\n    \"32o/53C/4qTYni1/5RRtL/WsG3TAAAAFHRSTlMA+mBgX49DP4vOwYtq1bGg+m/\"\n    \"8z5rymakAAR14SURBVHja7JtNjuIwEIWRkEAiQdxkFr2NZLXELkuydLY5R6/\"\n    \"mCpx36ieeZ2KK9GiGdJiuryplc4BXr+\"\n    \"yEzdPY1XV9Oh2JPgi8PZ3qerfbOI4D9vvtdltV1eFwOB/\"\n    \"O7SC0tD9U1Xa737wUu5pU3yuBg8GGoD7gXcD55rDoSfLn4Q5tSqGlPvASXYCU34+EFL/\"\n    \"pJQE1gdqbgPPdIN2r6k3hY2256toeqjW3gPoU+gmBE+ZPWRCPJ+\"\n    \"8BzveAdd8Ms8D2OdEC2rW2gF197EGYROH+Bd4DnP8aEX7zW/\"\n    \"oNlglw+yT+ZP70COdqu1kTu9Pl1vLTAvnfNgHvAc73QZU/\"\n    \"NIX6TfG3k8NA6gUazGE1HQC+D+\"\n    \"0HCk3oPrWBx8SjtwDnv0GUrwwSUiB97ADmfYieFoVXZQ2ngB20X7q/\"\n    \"JnRvi78bM3JEvg7YOM5LQ6bf/GYYE76vu8bw/0L7sH56El/\"\n    \"8NqC+9CUBmQLaxwI6rJ1E7IK3AOeF2W/V9CF+WD/\"\n    \"sv5m7+ZuMAJr8rGEEuCP+gDXceD8Wi44zPTF2HN4CnJdjL/\"\n    \"M+GFDz2X92+E+LVMpxmXL4mgZw6u8TsOQTgKQt/\"\n    \"ckAEGOkSg2g8xbgvA5wfWgfus8uAIaU1t0f2oBUMX0KWaYsfQ1oix8Xf6X0tfTW6M+RFpG/\"\n    \"hHA8+XWgs3Zw1i8R7ZeTv90A4P6S8H8tX9oA7DM/3P/\"\n    \"W++fnfgYdIKr25VF8CHDWCyb+4swvOTn8C2mjFcD5ZYMAX3UEgPhtwqh9xE0BAO6P6X8Uf+\"\n    \"DqQ4CzXvbVe/MYzP72nX/u/BP3L4ISLHoJuDs+ln5fuD+G/vmzfxld50OAs162h3cR/\"\n    \"zuHMfPjR377Zx3+y/kfr/zxq6TaPJ9T/xCIHuLHSmkN/p0GNkn0QTLn6B3AWQX7irT/\"\n    \"nimfFzAUZbj76r8xZn/sYPqP+LndFCw394ebH4jwJ/ZPGRneJPcHfgxw1gJrX8UvycUGJ//c/\"\n    \"bWA4s9+eTDFy/9FTwDHfoagKYEdbL9PmRGnc3/\"\n    \"UtYuZ+QfvAM6KGH0fQWn2gIESP6buj6K0KbM95n7N5QcAWL9N+cof4pfNlKhFTwCqfYW0H+\"\n    \"RhaCnxTwKcxYH2mSbvAFps4eOb/3GF+Tf2t3+wfvzrT+uyNwDzp36InioCpg9ApCA6bQJUFXF/\"\n    \"KVpLrpLdD+8AzrKo7/MD8av7U9E0XwEm6Su0GupvkVpg//Q85icOAE+/8AcBzl/4fzCu/\"\n    \"yNagI4AIvzYqf9D+xZXjg8/BThPB/\"\n    \"f8iSYlqz09hvS5omgoxgWAVn3wATAxfHYAWH7wDyh5aLkv/iihpaNg/\"\n    \"XNGSoz+UuwWcPV7AGcB9qJ9MHH/2Td/uoX6dW10V37yl7u/\"\n    \"UFz72fpfdPAHIX84CBSl7ACsfKwRH/7wxhT/B6fq3zuA82T21Y3u9eGaQnnnNBgosWHRw/\"\n    \"wN95eA2uH+Sx4Ajp/UvnHxd/fbn0iJNeanfnF/\"\n    \"CtrMcyWoEP49gPMsqsL4xfOlaKTFHv51QTDGy3+IX0raJLA12S6s/\"\n    \"pCVYvC3iMn8ZYmgo+wog9kGPpL/j+L3DuA8CQz9IL/5l+QgqJYM5RBg/uW3xUMlhYLz/\"\n    \"3IHgN2l/\"\n    \"zT42Le897tQZEQkhQz9uqrkGVopS67FAJD1AD8EOP+U6s2Sfub92E5aAFwfus8DRYDus/O/\"\n    \"YfyL6L/uwfwrv8nln2he1a9cSutHqOpTA0jaD/dv/NX7OVT7PgI4zFOM/\"\n    \"43SMH9b+zZQv9BoPRdDgMYdhgX0b6t/3vypcibrT8oHETsNAl/8atXo5kYAFr93AId4hvG/\"\n    \"UTD2BADto5Y3fqiDlBz9DemfU9HMyL//neOwrPpD+eHvKHsM/tjELDqunOz8OPtrmt/8XJP93/\"\n    \"j/B6W/CXD+3vjf3lj6D1pAg6SwD/+Q/pgDliGjaTmFs04AZ8jdVLzNYSH1h/\"\n    \"QgJbhA8RfOqMovDgCUavsy+mtoylNy1dDtB8Q/rj4COH9BlbSfCsEFNGmVwOW/\"\n    \"1QEGbCbS5+\"\n    \"35F3vXjtvIEUQNbGBgZWNDH8IAAzsUQHDQGTNyASXj0DyHIl1hoXPoIk51HL96VTWvl83mR7AN\"\n    \"WOpX3yGcvn7VNSMvwpqa/AXh7tRX/m/4/9ONyi/qN9/\"\n    \"7t5hrM93X5m+u+U+wivmyb4hHNLX8m6Egf/lhYOB24Qf5gY1bQ3698z/eAPbk/\"\n    \"6COoZyERy30YP9W94Bq3r/6AHiK9Pm/YP8+uK9JgBZzPxoUmDAz6DPda67+PGr978s/Z3/\"\n    \"CZ4BHJDoMGCPAwO1TP8kPDxP5G2jxJ/VfouW+WrcQ/LIof4Ft2Wzhzn1R/nAV9ZXB/3+Z/\"\n    \"eI8GJ7Cv4eD9jTviH1D/uxM/VGc/zJ4q/3Uepj4r80f+q+Pj/\"\n    \"BHHQBjBBi4HhJ+pt7sL+JnwMxF/vZlvzr2JQ6AlH6dAboBFCtlS1iFI67Ufur/v6/9JH8j/\"\n    \"mJ+B7OZvPnoD3aS/Bk0TyhUfzjZj2odAhh/FDRwy9T/\"\n    \"sjk+Abryr9lf+n8OB3Wl3gFEdrKzahJw3Yd5U1hRDh3ms6i/e9vXPucZz9AzpJ/\"\n    \"HAPLy7g+d5n46MO9T8WUBbf0k/awtJP/\"\n    \"mTKn+YbwDuI07wMC1Uz+xYaRv3PvM1wVAJ8DZDSBVny26iZkDgCXd/\"\n    \"7cIozksuZ+pnJ36YZ7hsLfxf9fn/tHDOn6JIwAO8wLsMwmzEi11f3b43R/e5X7u+0l81tz9B/\"\n    \"eZufwjxh1g4CI+gfwvMCCZb8ak3KL98K83+8MFPwOK9B+pwDUFBPuXjR9XAUUnAKOd+FWQ36r/\"\n    \"P5PaLdsjw9kG+an0qCj2Q3Bfyz/PFP5kPbOs/t5PzEdmCJr8Vc2ZYvCHEd/\"\n    \"CgXEHGLhw5X+xqR9zf6K++ncu/\"\n    \"+pgrvwd6RcK4sCc1E+5v0fQ4IjInPPr730K2uKVWXgKR1Jv5dPt7BfdxXeUJRMqTE7+\"\n    \"XPqnA8wzKwuf3Ge4RfzbPlab3V9H/OPin/d+NhJ/1BrjDjBwjvwvKf1a+mv4v3TzZ6UBSNf+f/\"\n    \"85/Oc2sNCB4k7bMtjFu4BG+UV3QFnsv/nvf7/ofh+FSP5L/\"\n    \"i1R6xHWwNbp5Lz+1odpthHATcrvyD/2h9N6zD9S/e/\"\n    \"zIzyZPw6AgSv3fS9ArvyElH5CRQjipyNgxOk7ABkvjxGgWMshwPUfnRncjZQH0CBJ/\"\n    \"dmbR3K6y3UQ3Mj/nxbGi/OyevYn86Mwk/Y59Uv95z1d4r+wn9A7P6bU/agNNPrTACbd/\"\n    \"aNYExhLgIHOvu+Fyo9ESP2zuXDxl/LXpcF0QNTrv1wAwGl4SO4v6r+FZSXXLdWk99IO/\"\n    \"fkLM+ym13/BbFp7BYCnrevDYVn8IdOUUDTvmzXyH1k3/+7gryZMeGQi5zvcfzYfHwMOiPwEB/\"\n    \"9W+5kuTf8Kv/TLCbEeKHyclrm/yA6A5ZKm+Z+qT/MTIFzMl/\"\n    \"Jn6NH97oaLfxJfrK8nfvW0vO2z7OEw9DSJPyK3fzOT5L966e/1wjf/32oDWAjUI/U/\"\n    \"fQCMLeBATf7fLMR7JkaYvCE+K1NWMV/UrzHpCqARgEbKa/aX4YlcL2VZ//\"\n    \"tJADBpym+YT8cU8ONtF3+R3ryZ/GtjMuJbAGv4Wj4jsaPk80nkl9vmH5ynu7n3NgCyBlJ/\"\n    \"+qkD4HkcAB8dC/nhrfTLK+arab/9R5L4x8NEvocn+RH+UOCc+i2o/azmrOwKu22wnuu/\"\n    \"NE8WTwix3YMP2V5//f8peE9XlfmPQHbUfPQeQrX735H3Kfy16YMfgxV3kX9uqd+M/\"\n    \"l9FfR8A8FNH+Zlgz2ML+\"\n    \"JEB8ida8kehoW7oTP3NPxzGHNxnDUwMz4bDVJAjpjwBgMJIB5hAbzFens8L2ZWesoGzfL5y9Ce\"\n    \"raUonzKD/xkf+gCYAqr85QsLf3Pqj+8qIcu6lP92s1X+tAOBCcwiMA+AjoyF/7wR4EfEv/\"\n    \"91Pve5nrwFAzYFsBwoMPvGNH8yd0l90/\"\n    \"ecvMffDDUVevtd5VDEeBiBH8+mq0T80vTYl6X5k8L5VfRoT5B4FTvYj5nbyr9AM/\"\n    \"0gtava3g78FrTP5P2d6fh4HwMdEkF/sj0bQzZ/N+fW/\"\n    \"1D8D7owX+f2J+YDkDwX54G7HQBi9MNX6jwjSS/\"\n    \"Qj4GI+sDypIP94xegvzi+ZqH9sod9IfDM5eY8G4W3khLQ/\"\n    \"anf1J+r31V8HAPy0+qeNA+AjIsgv+v+K6NwBqP5hAPP5138a/JsFoBru/\"\n    \"OmRuAQg35npSMXrFo4WkUW3f3hoPUGuJ6T/9M/XjP6ACJ/eIbugZT/6tJ2xX5s/\"\n    \"kj6KhJ+5Xvm7n937AVL/lvsyPLaDf2g/\"\n    \"YkwAHw4N+QlvhU1mOo2ph9R+\"\n    \"GXyCeVY6IBW09MLkJUiPIOWZaaQ8MhydBfyJnVkoOzwVn1ny75Xx6fLo/2ass0r8Te0RZiJ/h/\"\n    \"rCVxm9pb5kP/zEAEDpR+pN//UB8Nc4AD4InPwCpL8z/\"\n    \"W+YYD4AMM4OAPQJGYw39hv1JfvWavt3gE+891tFY+\"\n    \"5jv8gP5OOWRqBizw8nNPczGYL3GXLYxdH/FKj/F7lfq3/EzkV/zTRbeqi3foz26o/\"\n    \"Ukn+VSaeA9L8VfzgtXOQ/PgHGAfCBIPJL+nkAxGM7+sPYeNDMW+ZHRzP+666/\"\n    \"WBaKv34rcQygLgOAPbrBSXlmMzh7TvsFCVkb/mjqIKLeXR79/8iAi/\"\n    \"9M9C752cHT4NB6JV37kXog98l7Twjx3jrwHU9S/1MHgMh/\"\n    \"rPyINADZ8YoDYHwH8M5xQvlhgc4bQGe/cR/tWUDuEVHuWfwY0OpPRukn5e1p8udS/\"\n    \"PqPDnnCGbDcAHTbL/TouONLdmePpArXs7Z/ffEX4dUz+iDlM693TNbPnoL9DKh/\"\n    \"86GvkLofJdk/37L5r6RfDrQDgMjPGB8CvWs4+f+EoxjtGVn03W/DfvKf5O/\"\n    \"9f7+nCBbQHrFc+lv2p3kBkNAZ7XXxv0diFz9tvWj/BzzBnNilYrzayKzZfb4o/\"\n    \"rAG3YNgTdcRQJ+XRwq/vvPdeffQufIrRf2O+Sv68d/5o/YOABrR6D+8wevr6/hbgHeLOyd/\"\n    \"woR/gwRngPv0dvpn58zvXf41908sk5Z/Zig0utb9+Wsx8Y8JAAwnJq/\"\n    \"3eRpskXLhTzdCI6NHbZb8GZZp0Z7f/n3prvavetUHzKn+6BBrPMA1/CMk/mg7i7/\"\n    \"vXvuvYHDHigHOrxCh/pHF/N7NXweAjHg1GwfA+8Xdb5X0g/m1+kv8YZ3xH65invg9NF9Af5/\"\n    \"iz5NA1G/Ndf8edIehBnLvDyhJ9Ld6Dfjkp0AwPKeBjCP1Z/qhi31z479l40/GI4HvD+Q/\"\n    \"0oPVGb7excWf2v/QYb4iBn+a5J+8z9F/RdpfHP8Zoj6dJsQJ4AfA2AG+O9z9QuIj6bLPho/\"\n    \"8Jdh/+uufl2PisyP3hSnsnlna77w/Wv9FndgBdgLwvu/gc0EK8iO2KHoBAJrH8I+CjHYhehfa/\"\n    \"vW/97sR6ywU/R0Km5R8a6xyENDQv8Yh0DsAmOgwkn5GwGrxX11c+4n8J6QfyXLN/Rj/\"\n    \"EeMlwPvCp1/AfJHfktR/Uw3/SGjolH0a22A/kM1p/b9HY36vWPjuDRxgkw8HGBqqP6f9Ke/\"\n    \"9CbYC9Z7F+B8ubOn946Av/rcj3/Ax1t74Tywx8++QyHmdAHiYZz3p3/\"\n    \"VkuK3oEv+VpW9MNA4Ap978wbuv/Tt3/1c3YBwA7wk/fv4T8Llf7G/Gf1d/\"\n    \"YIMOLSLYv5EBmWHNCYAGks+Q9HMLyI5Juk/yl+jKRMW3X/wAoOVBAGjc5/ifDTxMF/\"\n    \"ysPdx1xP9N1E/ym0P58UCr/9p353t/ngGkPGqr/uI+s5v5qhJ/eNz6Vzn9d775SfVHbfkvE/\"\n    \"sRZD/Tzz8MvAeA/E59WEV7pZwE/\"\n    \"O4v5rMK9c2fQdbDRX2kUH+yH018AiSj5MOtcY+rPxIMhA+uS/\"\n    \"yneArCqz6V3Pu5JUrnBNjql39S/Hd7Ep/UF/vjrb/N+6Q78g50h6NFimjJvzL/uqLqm7NK/\"\n    \"P3aT+rn8h+tUH/\"\n    \"yc2Hv13Cf5hg7wPeBO1P+\"\n    \"HP3hznpYJgc6EN4tD4CG9mYBdAhpvwYAzf7G7o2zXQmZRV4sI4ASQwARRwFd2NIRrv1Muf9/\"\n    \"uhp3/5D4O/PhXPnvH/bOfh8KrJljE0jLUZ/ib31zBKzqEcAGfzsJKsuGzAfr6Q1a/\"\n    \"ov8vdXfa8V/prED/P/jb/bOX0fOIgjilixhyQdCRDzESQ7Y0JLFCemiTSDYgM0QEQ8BEa/\"\n    \"AMzhc+RWICJAlCyR4G6r6t+2aueETfwL7Ttrqnur+\"\n    \"lrimeub7fFx9au0nPPwTTq888DaQID0CjPYPG2JWxG/\"\n    \"LZwQQV0j8zue9cu6ns+ffVKrh4O9wd4aa7uvWbwbmX0FppN3EfzL/\"\n    \"LyGXEfZ7kfNFPXDyP5MSx/8S5xcpjmZUfzDN4kf2avLHPrzy0q/Sfl/f/\"\n    \"hHOrdd+LMpy+Hes+geXK4CHjycSP95/XfbvlSM/JRi17xUsf/EP3hXddf/dc9s/\"\n    \"B4HonnA28HyCDaDyC+77Az34pxG+66800UX3/w6PNz/4i8rzGHaaSC38vozfJKMX2/\"\n    \"XZDo4i3N+WD9gD3vY0gQwfPueZlI6fYK79vJQs4bTO/85g2/zJjsLlCuAhgxu/OvLX2D/f/\"\n    \"Hc2DX/uk0yN84vZApRx/lwA3PAT3q8UPa9E/OjdTa77KnorqH72/owC8XwQ8SN/\"\n    \"kWFOWbH56d9Hd+VNz/PyRMcaJ3/1Krzut/Zl/gf0z6WfG+zfETwjTWZ5fhEL35/NH/\"\n    \"GLWvyRfjFhwNv3fqv7X64AHjyu3hr/NZTxf9Q/tL72N4N+9aeAeeXvDuWDm3pQIYkSuSI1y0O/\"\n    \"gs5Dv6MI7WctmwDS94r/FyF85T/iyWr+kXa0rVSEqZRj1Rf8aMs/9LN3gIrjV0R/\"\n    \"38cJoLYEiV+dmBV48GcLIBNp2QEM7wJ3cBr+ta9j4+w/K98lMeLnywngwUGHfsBJ3/7vGL/\"\n    \"3849V8xkwkff/UropyPUfBfcneJj1L4js+AGSZ3HXlw3ivAsUETkJLOKnkvF+Vfg/2j/\"\n    \"mH8XT0kHzUMAz2wDNwaJXge390FFc8ZXHfgQf2cOx/6ZSefaABPbP2z68n3WK8BUnUWPdALbv/\"\n    \"uL8wc+PLnhIeIL4rxE7gf755J+f+AY4yOlfCC3X/5n7+/DvwgCgrBvABO/8g7i/\"\n    \"pU9XWwCv+tXlxi81yKt/SIswXOih/3D5V+JuCtPmcZ4C8H5m/LrzO9jzrX3+0A/\"\n    \"f9wq4vgnO9R/aj/PrQVXExO+oSh/3t/0TjZz/2/SpE1blI37IMc/+L/94eTkBPCjUoT/aP7s/\"\n    \"+ifQ/gTGfqJpAvrXIkw7Z7s/1Gf/+H9EnzQsc/V99jeLOoRulawFdd4neEL2MGsbV8vsn5N/\"\n    \"p9cUKa48EH6y6kUqtQ/UbF9nfOvdxBs/dE8IB877HZY+IYWLrXx1zv7cNyN/\"\n    \"jv4nEsGrEEGwdfbvcILsAC8vJ4CHAg79PvBb+XH/vvx3vIKF1f2j/Fz8pXEo2/0jer75Q/Tt/\"\n    \"1G+Y8wC0ucKsNkLsZspTmgBwldWbff/rhqgfoV/vzP7R9VDYTkT+aXM/\"\n    \"8gIIPIhoOjAHeDhhYo0rvTor6MAmn/BAgdouQTE5Y+1cur/yYtmvPiH4v29DTTN0o/2V/\"\n    \"Wv+n+pdFwGgIeAx9fWvtRfwg/F/EnBHO1H//Ay/iubutlpCZg/xSsXf14mKj1zf9KqV53cP1K/\"\n    \"2dY9ZLVn/hdL8JCWE4I3L/8+GN3c3Sz6zsT8cIBdD94IzncAEvzxwJFf4fZIdabm4O9gABDF/\"\n    \"t2M4WdET0H4JyreryK1s2agf6EI7Y/HfzLiR//CZQC47/Dcf+34bA3e/\"\n    \"UOEUBTzH4XvXKBxX7RTVSh37qP8G3zf7EaI6CkjMHuTAn4e8Qf+bXsHcLIDEE5GgIAeprsz/\"\n    \"X88Knqd7dkSnAm7PuwGxSv06BOA/6SfF9O/zN8/0bMBhLyedYoUSNwv/\"\n    \"caAlKi+ghbpn8r1qZz9ET+E9If4d6N/iZ8N4HIFeL9xJeUz8wsqJDFJX7m6/\"\n    \"+j76oKgdO8NQMWdognNG5wA/\"\n    \"IjeKap5+ZcG6WczIIxx+t8Qv2VfrJ4hQNXSb70rIBfgX2im2b+kDXWjTCmaA4KBvN/k/\"\n    \"cDWrwBn81dSzkQ+mzaAro7We4J7QFG/9SPRPnxi4ldVulsR8984AIBc/b1xES4ngHsM3/\"\n    \"cjfi+iki6mzzJv3/0ZlBi/k6E/NwE7E5vArqeA4fDv3MX+Czn5R/\"\n    \"mYfh4MeD0EoHojqldh5UYQlS/aT/NkmP2t3c5Wffp0Ub5rRv8XxY2j0lPBwSo3ZP6e/\"\n    \"Ef39+rSovfC/IvQv9JBCyR5kTPur3QIFj6yj/cv3/\"\n    \"xMO8D8xR+M+GGHUvj5k0cX3Ec8eXo9ir8b4u7wH/FTcP9Qx4xdrf7wR0SD6jvVM/\"\n    \"+LQT4CfE5ZDwD90OFsosATvlMo0/\"\n    \"XY71q7Ac6vBSnpwNU4+xvIHK2TET80mP6KQ1fS3YoyfiKl/\"\n    \"X+KRg8A7mDrHu17neL+J7Qv0gUAB3/E/31lNoBR+usWsLj/y8abywBwP3Fl8Vv+6L4lTxKZ/\"\n    \"kMzNr0f3aP8Xb3wk/\"\n    \"BVid4CEui9F7mCOSBDv0CFAno44gdSs6hCktevvP6L2BVNWjA5zf4NVA4vQP0byAnA7/1a/\"\n    \"StG5cfxOztQPony6Ur4FFWc/1n5fjaAmvuVs/t/P/1rX3iU/sbxH+/\"\n    \"XAm8uA8C9w5NPr+vGD8mrkMubPycBeTUw/s2bf7Bz8sbfdFf4u7oBLMd/\"\n    \"q3d9B0TTIwBrHv5hgswcsBfvVcUKUx/5K2hK/Ja+0yo3f9FqT/IIMv3/e4Gv/\"\n    \"zHjwEGqV+jmXx14EdEPBekz96v00f8g9kL8MED3ufqL+zP6e1WwD2gHiP2jfErcXzlgU/\"\n    \"1c+wNdAbx5qbgMAPcMT69Rf9x/VD4p5XuR7f+L9mFkv/\"\n    \"7BDy9OAMwAnAD6c1+pX1l049Ki99oeAWL/IMLH8UlByqfE/dO9fe1fLNpQvoIiyvT/\"\n    \"YYTvBdIu4GIABH3dBx+qOo9fRvpguPvD/pF9AvH3v/XLINDf+uH9FYhfRFr1Nn+HW8P8fZu/\"\n    \"ypb+1x0A/RNWf9n/ZQC4V3h83ep3GcWf+uoazYs7nJRGPvpp91+cH7LwVcWCqDyfdCil/\"\n    \"+m1n37IwyJ+MHk/vwSYv7MaTF+psM0r6ssftG37L15CoHg9zdF/Bgf8tNsngeDg+KpXscV/\"\n    \"5OavNc9ydBIZ/ysKlBz9eeOnSon4Mf1Wv2u/+YcA5h/1j7M/\"\n    \"7DDgdQCw9ok3lwHgnkBXfri+2NHun+Kw0iX9bffPFoDwb/\"\n    \"Pp73L+3+\"\n    \"1q9BflA0CMX0yU4htqn1PQPk9RPqqn37vb64Hjv8mxzwag6B2ABTuRvotI7D3Bleygdz4645uN\"\n    \"0X8ZBtbtQR1il+a93B4rDoqjH/gnfugeTrTvu1YOYcL8M/hH/GLnSWT2yuyvFFQp3xOc/FX//\"\n    \"q0fuc7+MX/\"\n    \"C+r8MAPcDV9egzd+KZ5GQtT9884v+cwNwi+7dqVr7VdwEu+\"\n    \"HwX7mr1VeAps9ppHPR7P4ilQWte/TeQXGaLPhxAzBR5hf/NfG33t0jeuUa06u/\"\n    \"DyL1Ffl5xYtyfagEr1QhyIZ+L+l3tvFL/K4qarxcBD/n7k8R+wcyfZYlL1YhuPrXys0/\"\n    \"kPAxf8PVOU/+m1d/8X7U/\"\n    \"+YyANwDPLmO+rWU0wbwSuQi+\"\n    \"QNXxE9I7sANqtcGQKgp3Eb7dFoKpgBBiidFXjcidgB348TPwwrEb+d3lv1L7iqD9mlj/\"\n    \"9+JRUNw4yfU3C+4gRxUsn+7Go/+G6Yfv6fJ8yE7gHpe9Lnh3O8zv9Per5WXfkamgEjfJXM/\"\n    \"TGXyN3PqV9CcPAFE/IRg0UN562fl5+iv5u/df/niv4IS91doALj8HwHfL7D+bAC5/\"\n    \"EP7ErpKVK9MJQTRbUaA0r3IwldunP/N5740zybgQPW1nMieuGEtym/\"\n    \"F96BfhZTGAZuAYdlrleapnche+ncSgjjP8wDwNEf//\"\n    \"4Sv8Hz4cH46cuwnivQM0Ho6WGnDV2QLmJRPg/hNP9USC9SWfKL1r8zZ/zRtAArnD7P4X3udZ//\"\n    \"XLX7IYesX4v6mywHgPQLrD3B/7F9ZSg9QvQuB6L+9HUYAez9n/wz/\"\n    \"4V0F2ieUgjjad5byS+9qHNg/DRzE80Uk3q8Q59KPDcANa4/oTVTP+2LfABR6EsDs3XeQbvPW/\"\n    \"38A8TstfTHKp9SFPzO/e8aBCqDG2QcA9oFZ+5n5Ya8oH/HXw6x/xn4tZ5/9lZA2gB79g9fjyz/\"\n    \"tAXfdPy/+z+bvNH59dMF7wtNrEOD+r7IRaM1A/\"\n    \"TAxKF+pouT4r1BVQfwporfGX8w9AOmZ312fAOL/lOJIP12f/vdeeqZTldpJKmHFU/\"\n    \"fqHJK3WRAjetFi/24THP7/n/rj/iquLXznkQax6yHCp6XSce/nOMLTFgBRuPAf3R+vH52/\"\n    \"Dv4Gvo/zk1z8tfZFgbwf9984+UOj/\"\n    \"XtdBoD3hceL9j87q97JzG+4IPuQwtRxq3Ay+DvVtPubqJn6p67tPzcAiF7c9m/\"\n    \"KDnAHiJxGy8Zvny/am8QsIS/+lGPU7F8v/xC9f4rQm2hcCP7g7wf/W/\"\n    \"oYPqyipQD10Dimo89DTv9Hi390//XDnyifqNu/SN+g/uBlivSHq7/eAdQsW0Bm/\"\n    \"3H6BzkAIP7LDeB7AW/7VnxLpEzI6D8pXzxN/ujfBdGjfGc3JhVapQK/dzbt/\"\n    \"LXfcuqHq2Tsh9J7O+jZ3zwOAOJkPvpTmoAfeAU4bwEp5tz9ffh/bd+R2qf+XvCR7jib/\"\n    \"tv2mbiP/agfPk6v/WfhGydF6X+FDX9w/7b/1r6K2DcA0b2zr/7UiRC/lwNa3B/\"\n    \"8erkBfGdYrX+9/SNXtPtH+2aV+H816L9PAGYS2WP7BHBF88ief/bvdoxIP4j2e9Ln1F/\"\n    \"FyneqEupFhps9irfmVSN81N/2zw4QUkDk07+5+fuG9a+Qyz4ysvepv9vR8OE0imPP/\"\n    \"UK3olp9AiCzCUC2+iDHgGwD6F5MsgPg/8Jq/s5MAHnrX5/\"\n    \"8+uQf9zddDgDvHtvWT+D9rEX7iL+TN4CNW2aAcn5CgOP+QwDMX0/iWL/\"\n    \"WErn+21P3TRn+9239PKH93Pi5JfzIEUDplaDY/\"\n    \"yP4cQcopuHmD72zuqFsA+dH9T33U5vAMv5DpFCKh3gRIGItUz8H/1PVk2IR/CD7NFz9xfxt/\"\n    \"GpYLX3E/1q5nP0dVv9w80cGfz664F0B6982/237R/oudHXxn5D+ZfxsABXqYv+W/m1u/\"\n    \"hPWe5Von6t/lzFCxh6GelnzSkKNgOLBcPVX/r/H8jtIR2uddvB9QGt6wjd/\"\n    \"28huAEf6ET+PGfphBoDlqh/JM/m7rWrhE8q8/Iv0swGwTCev3gbi/q18qJ0/O4DWiPXs/\"\n    \"9rRzh/95/Sf8b/x++UA8C6A9X+9qX9CgCjBaP63Iq98+nubG4C4fxUk/\"\n    \"8qFRXEoWVY7xd6P+zcllAZNdoA9TYRPv6+F489U+E6LCUALrZNa8I8Qso/\"\n    \"pZyLw3V97vYJiJpwhs5KSkZ9QJ9Dm/I/Og5z9o31ETyWhFeiekz/i1wJqWZY9hPH36R/t2/\"\n    \"mVlJa9l9nOj/IDtN+I9kXBb5cDwLvBY7S/Yf0JaPPwj/hfFTnOJO1z9a+oo3/\"\n    \"cf2fx32YCQPTqukH5nS16/Zh2uADck4oIH2rz15MInbu09M0QGwCyz/s/\"\n    \"dA8Fsf78oGX5f4CygdoQYh+eDJqDSDlsAECVpy0c4//9uk8d7/2VvQ+438KptwBlyz7iF2Xwj/\"\n    \"IRv7rS/Q+I3/QLG4BZ4hcYALxm+0f8zmj/\"\n    \"cgD4i51rV5GsiqKKwYADiiAY+\"\n    \"Qsd2JGBgQgddSJICUqDiODjHyzBYBio3MgPmLARYUyNtPExBr5AQQQ/\"\n    \"QMZYdD1qzzpV22N3jTVGtfbz1ojZ2mufc+/M/wqf+l+ZDoBx/WdyzF/8k/\"\n    \"QuwHoA0Ir58Crkv3F84hf/BLK83v2zsfZXseXsTztFkN/M5n32gLJT/\"\n    \"wGrPvxVYcdCya8NgOYhgN+K/Sb7ePJ3uCgrXSf9TfKYE8LIU/4D0x4VVH8dYQPGEuSkD4dF/\"\n    \"uWjwYNXm/Sb/9Z+ZqZ6368w6asuFfClXMzPAAig/pgBtfeL/\"\n    \"bn6h0f87dwBIv60wwHgQePaK2D+fPPPa3+nfvVXgPYr5a1/\"\n    \"EpQfcYLsAQAj9T8C5+3HmgBIW9Jv7pvyOffLZQD5b9oX9R0sMbrDbqEX+V3fqQWAxC/\"\n    \"qawwU+XP6n6AGwHW998ueH+7HxglgWPnhsqz/\"\n    \"If8lOMriT8MTSsJzoOOcYekX67kAhPhMm3d+JL8CVjMAkOwvI/5a/FnIfGl/yF8Gz91fW/\"\n    \"81Ar49HAAeJK6T+Qh6Jz8DRmQANPn34g8U+Z2K/\"\n    \"Ag4TVMAziQDRH7kfP8rI1yD50e3qfOtf1aAU4V5b49l7fd1gNcATwGOgDr5awxY/\"\n    \"el8yP5vuY9v4dGif3S9uvZz/bCQewXQsw0P4DFSzv2d9Ak7rXyowHwAmPyWfThKGwAIkR/\"\n    \"MRyHplevoL1S948DajwDvlQiULP45+DOxblDf/\"\n    \"D98AvTAcI3KH+pPBwCcRlTpZ3+pP6zU39l7P5yWxrs/\"\n    \"Chlv7ivTVXLyR2T7j1dXJwDynFl18+Yvzj8W1V0YRg0AmAsGADI9F36XC79whtf+\"\n    \"ZnWfAVusrxTm05HWEfWvOdBxlBqP2S8Bye9E4nP/HwZAab88sl/kR5L826D99FJ/Mr9u/\"\n    \"VXq4M/Urv7a7R/5/91DBwj7l/5XRH5Hp76532YAosGLv82XgJT/Mm/+qBZ+3/\"\n    \"rxQeRHMIvyPv8zUsJ8N35g2JhOmfzqn5k0T8oAYKiR+tcEqDs/\"\n    \"F60BIn8CsPyrd56PAbz27+xvBwDAqXzhsPib6C+C8TQ2yExT5rsoFfPbGjBd/l0+GC//\"\n    \"DJPedYmoCVDf/JD4yKK+XbgA88t+tNX6v9b/Uf7r7C8P9U3/wwHA2Lv0i/xR/\"\n    \"45wn0af44Qht+YnefGnIyFGE/lPcvMnr9IHQJKriweARgCz1n/\"\n    \"Ushz5kRTOdiFvAHTnF4tPqN5g+nfuu8Bjaasp6ZeJ+iiA2ksP/xWwq6r/\"\n    \"uZId5gLPCHAp5Weqpd+oEVD8R1yU9MMi/ohbJn/kf/7Zj0cA2H/g/wPBI5R+W3wTW9xP25gPJ/\"\n    \"TWz5kO8gPDEADfAaTivquYzyLr5A/E+5A/\"\n    \"Rt13RhLjXelmvJKb7P7qzHslIsy3+PsWsH6I9M+nwTWoPzASvZJq9v3RFky+\"\n    \"BwDTuQgs7Oz6CIjur4rqdici5K8SdPm3yUF5ukO8L/\"\n    \"EnivksFH5bJeICXuTP+o+iFWBy+O+LfxaAwxvAPUv/oyB/V/9Ofrgse7/TUyF/\"\n    \"rv9g7IAiPnMkXx3cidW6jwYZCUDK/t9Yb/eijxD36+WfxR/\"\n    \"JV4Gj8jPHkVhq6WcT4hdEe48Bp6sh9DfRlQflV1YZbaFiqrMX19Eow5DlblYoyAwXd0cr8J6zg\"\n    \"GGz4wcB/ZVO/vBSfldD7BfnA60C4j8yhV8W7Rfy4s/c1+f+cqN/9PetEk04XADsmf2QfpO/2O/\"\n    \"aB4A9X/1UNGQIyNBQ/hn5+Nfarwyz/iPROABskwHQ7wFOmSX7MrRmvY/\"\n    \"+eHKFx5KMDACES2D9N/\"\n    \"WV9cMUTf1N+Qq6k2tssa4Fve8z1+1OboyVld+ZhGfT137+iQeA0lT6mW2W/iA3/\"\n    \"ki58Rf1zXxlm5MmwB2bYeab/XfA+ju3Agt/V3+Gzfw/\"\n    \"HAD2e+fHiE0xbv3OJf7Bia0aXf6b7CiM7ACChV/\"\n    \"ZN4AObQJ0NIQfQnob3U+ivI78JHtGANNA+cAP4zYQhPLpaHRvAWivqv6vBy+\"\n    \"2Jn24X6pvWzdxlRL6oR5Z9dmvhR+uLtxXnuNcKZbGYyBYQvqRATQ0AEXMh8tEfiLsH0cAkON/\"\n    \"HwGMfvd3uAB4AHd+CDsNDuu8L5flrb/\"\n    \"J33HiDCfMebipD0T4nb32w9BI+F3dzqXftAfxMwUYMrNfoWSL+GcCuLoN+ZNiUf+r4ZGif1N+\"\n    \"WB8KVRcoZj46Ok0paz89W7/Iz4BpABTrx/t/YCWzd3jxd83Kr2TxX1r51aBa/\"\n    \"7P41wTgtd8Fuqg/3Wd+yz/UH4YB0P6pn+z+I0x94/AFwP7u/Er97UX8qsF49I/\"\n    \"6Kz2lKJTug+h8QG+Q/duQ9Kso++Wfa2g/4z5h7jPCeXu0H4HEAgeQ+wrQYfLXos+aVr4T/\"\n    \"ceFXr41C/KnTgtUY9FtNRz7RXZB5PdzcT+6b8arGbHaEv+QPwbfxHvWfhp6kz/\"\n    \"qb6vNH9TXAACQ/NU/ndyP9gf9H/zI5o84XADs887P5N/a/+HzL39y+U/Km/wAa0Q/ym//\"\n    \"yMZGQBPtj9O8/Oey/wQGn8Pkp3skFPPz/g/OdnzVl8Y5eCHS3+Vf6h+cJbPYO/0j783rx/\"\n    \"zEdlG0p6urLJKL+06F1R9//PGXOtCdAdcwCL997reH/B1b7N+ESS/3O3/\"\n    \"iXNl9JoC0v976X9T+T84jyfjuD0b5b9x3MsZ7/wP/98b+56z94X776q9Y3zZ/\"\n    \"JXPeKZDk29LVFLCX7ietN38lf+9zOe1Fc4IlWo9UL//xK7NXfuPs7O7dP//8/\"\n    \"ffff7ns6E+qJ9ks/\"\n    \"7YzRMbAXP2b4hvmvH8PfOg31e3F+nVdIWfpZ+en354Efv31Nw8Bsb+\"\n    \"0fqGM3rOg0uT2b8ka0VcOljkB2Hz0N+1L94v7Tnfy3o/ZyASYqn8hAwAuHC4A93PnZ2QDsDkF/\"\n    \"d1/tB+Gakestb8KDUCXNUAu2a/bf1/5OXvzrwXgJCvABL7wdzV8+WcH6+vF/9ndP0H5Jwdk/Z/\"\n    \"hncomviuoTuojM9oigHAJ/RtebD/AkMV/JZ//w3+13vnLc/\"\n    \"Snmf5GzQCf8lGYEcSKVRGszpmDZa7+lRiBpd/2Xmk/GsLcF/kvGDKpPxDx94s/\"\n    \"nvuL+f3wL283f8FnhwvAPSz+Jf1d/dsCIO6H/CoR/\"\n    \"iwCJ8X8CD9zpN+ltF9xTLf5PR+rPwOA9QkgzgcWepp/9h7g9wDgPWnf0Zg/u/\"\n    \"u3j7LvFMK7S88KD/375V5yfllsTAAXZf+QQ394X6no32YAZT/aH/\"\n    \"VHoZ8jB8tEyb9iRA79SlZ+Qm3U34s/4k4d/hGRfoav/mCbCPMDqz9TBsCB///hZf9Lg/\"\n    \"QrmGL06dkfqVSf1dYv/YXifsXanXLmd6HqF+fRI83FP1d+aulO9/6tnzMSfwprf3KnvgvDntX/\"\n    \"jDmEV3JJnqv/fBOI3g8TwOam+J7CfFTq32fAzRzz4YyV04ib5yL/isEW1Wt/w3JD/ZXytW/2/\"\n    \"wt21H7/fd+R/D86o9Rrf2n/KP/94s/MH7nPOLwA2MPib5j7jfxN/uvaP5t/kL5e/\"\n    \"AMqCCGsV8tybEdr9Qes+8xMLIo5cv3vb3+o+WH+BKS9I+gT4MwO0wAgt11CdUbE38Xe6D+\"\n    \"fAKZ6OmUd9JHhaFHz2c+Ktn32n44AX/tl9Zfqu+cD46artL/Uv04A8wEAKJv8FbX+S/\"\n    \"z16s9QNf/VUf2R8g/9VcrtX/vmPwMA/tmnDx1w34v//PAPsBsR5XdD7iOQynz69/\"\n    \"qfL3+SYGZ9JsBx9TkAsKKUzXFDSX6KhCx/Htt+I8FM/ecI83P/5yDTUYHTNceT1eRX0f/\"\n    \"qsOaH+nAW22qhCUC2w2VpGv1nW8BKqUl/uC/5t/azdCzL4O8p6ExR//Gj37r0D34s8oP43v2b/\"\n    \"Munn/1kABz4vzv7sfe/BAvz4YjYnPol+cwsCCS2lUz+e83A/\"\n    \"9r7U59REvGV0suAauGKy6bB3T8l+lem/3Enfb//\"\n    \"129Kvvs7JbNRTXWbU9V+9t95CJj8DBufV3DOgS07QoT+8xFQRwAlFzCd2ThfLZGYz/\"\n    \"ErMmvnfqQf0YDNH2HmI0B+RL/1B+09AAhmk7+t/xb+Cfml/9ceOmAHXCf5p/\"\n    \"Jf6q9wCkz+NB4AMpP/pDq0cCVkRrS/45hJ3Le5KF2CG3bJ/\"\n    \"hlUfwd07t+Ai+gKe7Xgu+NUldRGzU1flD8PO9J/Jdo7o8BH9V+xxTOqsYg1+s9GwB9/5bI/\"\n    \"OPcKUFC7hLcXf8vKpf4OS3+UH+RXsvjnk//s/iY/+q7+bfNv5DdMfuJwAbjT4l/iv6H/\"\n    \"Tf4dXf6fYrXoW/wdzHZv/jgCqNrVsQmy/cNL+J1BenkS4BzeKwhT/y6ovxvI9+g/\"\n    \"++CGWV85xwAOArqtiX81Dtiu6r9SEvlN+fWjdB/FQbhU2+g/RZ0DVuA8T/y+/T9ny4D2k/\"\n    \"wuiC7/o+W9X2D2S/z9yS894l8j4JZbaj885Fdskr9NAJPf6cD/\"\n    \"3T71eekS9Z8MgDeqwDwC3K5NqOP/\"\n    \"xvkfOeLfNoB29EdpK78e5jjDxr87RskP72FtA2APvVdXAwDP2fWTHLarqr+\"\n    \"v9krvEcg1BEx6zgInPDYsdqJ/\"\n    \"lgBvAedM7kB25eVNlqVskP+lY9R+J+O9qD8PAEsqP4PUhwVivaeAmN/Uv5/96+avy/9B/\"\n    \"3fAIy+J+W0EtMO/QiUA6RVMauv0nxlQzgkwvv8P9V1C/BJ/JXtd9sc7sgK0K/\"\n    \"5dlv8bwwxwqscMARZY6T4jFtqPrFevMqX/7f7TypHDflpmJkYQ7EZ/\"\n    \"LwE1Aaz8PvkLHgH2GgLLqfojtbO/v/lZ079u/ek5+mvtV2YT8k+++nNq6n/Q/\"\n    \"13wKNhf1tQf5mJjmrz3D/lhHgAbzHfQym1ugsCsHzyWSaDGpdb+rPy7gwx/\"\n    \"XkQf9v+MAYTID77b6I4zkvu0BoCjln+gykz9b8Ncw/eqTnJKvSqzVB/\"\n    \"9eOx3coT+O04Aqj9TvQRYIij9q/Pw/xwJxKe7KG1T37rvZOWnefNHNPlnkv4H5v7W3d/k1l/\"\n    \"kh/9f+n+NeOIeHmt4OGh4rCH/p2vCQ3u/weyL/5r9dJW2+/\"\n    \"e9P9Iv3QfvYdr+FcP6T2fyEGDO6R9w6TDZ08nc9sX/XaU9cJ/0F/PDeTSVc/\"\n    \"KHyd2rkuuAaQ6Tu6HVD1P6304Zm7B+qGV2w8R3dc7yvyt+9UUA5F++\"\n    \"fud3E2xnGs3kd8Tgm4D0W/3J/gsmMh8zAM1I/roDtPZ/cYs+in9/62/+t6t/uPD4vki+Qe8PP/\"\n    \"zwE8Q9fE10vP312xt4izbi5bde3sazLz8LdDxmXL9eg2F/i3+0v5M/8p/13z6OAKt/Z36Kb/\"\n    \"1OUAbRV1bPMhsDDvpoUX02jD1wH8ixHzmRWXB2rylTCx/5bgfclFUP+t9u7Jf+u/\"\n    \"hxpRrKj+ak8PZvxsPGFPrvPAHeJOlz4e+3fsjcBdiVNe43SPl99F+rP72rv3d/r/\"\n    \"6M4Cvzf0J+Rahv8hvf3A//r4XpH3/8+Ro/f/\"\n    \"7ziA32cwDAR+aL+4hQX+YU9iv1GdCnwNPPPk2wBOuJ8Mj9j4PrI/v77V9RH560SX3rP13sR/\"\n    \"0n8rvJ5R8/BDpBFuuRp/of4qcBnIP9cN/qf3wjgg8vq80fEf3XM0qd/\"\n    \"ZXGxgXIEmD6G28W+7v8l/Qn2YrvuegP+YXqjqz+9z8Bjghf/sPAeb77N/tj9GVt/2xlI/kl/\"\n    \"zARf4lkh/pjAMBGZAJY+sfj/1dgPxIcQPL637d/WfH/06vy3Wz/aROmfh8AH9IH9e/\"\n    \"8t8Fn6v+ysyzEt8EZIb+dNuK1p197TRVgp2nwCKfBTsd+Jdt8BET2u/i/\"\n    \"QSfzxX5vAWUhPk2F5Bfn0bl28vfzf3Z+9/2efx8w14/bvR+pz/6MUYaWgUROs6KgN8e39H+so/\"\n    \"q/aa2Hy+A2+4qxbYKLH7Pww8l7RDv73/c9gDcAXQC4hPuuIn1nfpH/QgtARgCT/rY/\"\n    \"vTPfmz9CA+B7aD8cdL8Fzof9JH7/5DfURxbA/znjv/zyhxEj8Tv1kQRRn27uw7aoT7eF+rFR/\"\n    \"eGOoAZAU39Y5D8jwAbfxHP3FoN/O/YLr9jb7R85r5TV3y3wfthPp9XuD5DyMXK/\"\n    \"ZkAOAZL+RIe4nkYPg+4/ldX/7O4euJ+z/\"\n    \"7HFPvoPZ5cBwIqOxsQMWruG6ohGfG0AOPvfFvGN0H5T/jv3DTWh/kLWcIQg/fcwAXDkR/Duj/\"\n    \"mcD6P+O1v+EfBtXIT7CILMrzD+pu7qeaydomi0CiKR+\"\n    \"BESjUoxrV8gkUgkIhIUUynkLSgUMolKp9BpFQoJnYgC8RnxESES4gcIjUZYH3db597lcX2NubP\"\n    \"2PnufZ0S59tr7nDPzvucSgJw7f5A/E0DO/lMBSv4P1Z/8D+dN+Y+JL2ghPu0rrhQA2rCfOFD/\"\n    \"tfeX9egPN/k3Zn847UD+4bKV+47wagCqABTObr75FhWCGvv3xb/6/77+G9rvk9/\"\n    \"irwIA7nMZorw7fy6pP5bMNWA6AG22isDkBBWDZ+8g99P0/3fqH+W3+SP7HPgN/\"\n    \"xlG7kP+9QcpApn91+Zf/IeP6Tt3ADIgGy6nYGRfHvr/ywrwK9/\"\n    \"8gt3PPc3IGeAVZdQAUx9Q7IO/6fsj/+S/reQf1EdgovzDhvcwnf1T/hnpWwf/\"\n    \"tIAF4M3bbrv1ljfF+SDkD/9h4T7tQP7Df6p/9P+Q+4/H4ETmfvs6/tfkH+6X+tMDd/\"\n    \"62wn1cYzucpQ5g7DcOm/+S/0P1fwYW/\"\n    \"U8NeIRjP9zEV7D0g+2get7+EeZ9CkDTfjwPf2wOjm76/0uE+NmspjJgxgNm/\"\n    \"7POCjIgzIc5ZgP6r71/Gn8vmRt/Rqao/WzWr/T+qQH/avbvg8BX/ALgaUR1AdB/VgA6g1Nf9+/\"\n    \"rP2Hxt/67BBxUABi57/Hf+k/ui/jwHfnT/H+wf+8fSwEo6seG/VwAQyoA3NRXXNkPOxD/\"\n    \"rgD7g3+r//2xgOrf5O/uP62/pZ9WFUABbluAhsDUhynauv/\"\n    \"PxqbRP+QH451n9GcC76P+CjY6239Yn/ttH/zbYc1+NP3/MXTtl8YfXI8xOGb2D+8BNwDeRP0j/\"\n    \"loz+4P49MFrUX+5LOLe4i9k8K8moNT/Xw4Br/j636ov/deeCVAg/\"\n    \"bsC6MYPGx37MVj7uQhHCr+pLwPzZYRnfxUAqj/JjwXLn/oIDk7+aZNK/OFhv8R/W/\"\n    \"3hUv8++Rvu9/gPRwj7bXX1t3T/af032A8/QDO/\"\n    \"ewCRnyEQ+dP+K8ML5ryyWK+NyZ8SoGADGKYAmPZjcN/0uf1XAhyb/On/\"\n    \"+9d+KPzf3f6fwxSHIR7QX/CG/KfoR/IR0w70qd+4P01/w5QP8fcM/Lb4w/kJD+sRg/989m/\"\n    \"89CtlH9wH43PkN7vw3ptc+Hsz9kLE3zZI5w9fW/9Zln+T3/L/AXwhfmwfzf2xQOw/7P6DqH/\"\n    \"0f7hf8v+45R8h+EPxZ4T3vZ89WGb/\"\n    \"Fv8xeNBdAG27BNjkYT7dizYVgAFOznMR0X7gUP2xtNGa0V9xJgBtSvVz/\"\n    \"3cHjH6vvSb+S2j+RX264KzOH9yXkfXMSikDRpr/hugPMgvI9pQBW4QemBGABeAI6bmcmv7/\"\n    \"fgh4xT1A3Kq/HP83Qvzd2d80/vXmz/ov6cdy669F6kv61flzoeX/gG0//YNSf5gQ3UeAh/8m/\"\n    \"9r8R/376C9H/6L+mFDsL/UX9Vv+6+BP1B8r7tOq/Q/xHQIT3rhTQT944KF15h/13z77u/\"\n    \"PwDGBm/wz+1n0FMVwpNvJPl+ab/M6Atlt9v8kPIHBZ/jPxXw79rfKGae/\"\n    \"s9KAW9d8VAEAQ95mK+Px2iPqL82MMYT4/XAGo9Wa8P5gYHY6gZ///5hyQXT8WotcFvcU/\"\n    \"3LdL8l0D1PqnAYBb+936R/25Qn4YiO8C4NYfLvK3/gcZAISQv5p/W7hv63v/VIB+9hPxtxk5/\"\n    \"j928p8B4C/O/kf7/weG/Pog++FEi7+8uI9gc3pmPQB8RqzHiq0T/+TpAPTYB3Es3f+fHvs/\"\n    \"BfG/F+f8WOL/gyX8/7n6r5LvjDimAkDyT7f/rJJDUz/R2fQfpAtY8Rycmu8yQMNe5Pc3/Dgug/\"\n    \"4+BbDak/UWfWUkZe6K/LbAxA9Efi9xnz7kZ/bp35Af7Df15UEf+3lJ/QOx3zYI91MBwnxZ3/\"\n    \"xb+Uv96+ZfiPa3+vetf03/2+oPV2j9j/yT+4iUf9uQP7O/\"\n    \"TV4FQMTHUgOQqV8B5ji9P2IKAA1Z5Kd74k8L4M8gvLf4O/rKX/\"\n    \"lSuv7QP5x38Jr4IDYz+KsCOPvwn5mp0c3/qvTOcMMDv3++03wad/w8Lv0Xd10W/\"\n    \"XMKQNEHLnLwt413uQQktv6+9Yv8v0cHLPw5+Iv620B6B1/7I3yqs/9wv579pv/HOmT/\"\n    \"gfpPCYj606P9ffS/dv9BCsA+93PvZ/LPqgrgEtDNfz/6kWcCaPKb+pH/MRE/\"\n    \"Ab599pf5f2RfMeN/zETP/V8GfuwQI/uwUN/7PaDpl+22d3uLs/5LhWXf5Hd0nprguV/\"\n    \"gjtIf7jsfR9Q/xFd6jkHC76DPNP7e2hyQNgvAXReXQv/MAADCBRYi1qTwvnr/Tfn32G9X2+/\"\n    \"wOZzhA7qP/uG7FoC8n2u/z0r9RX17n/1xhf8pAME++eUhv9ZCftsgyr9x91fdfz/\"\n    \"6JVr8HY0wX1aoCiDWw6T9Chn7t8XfMWf+DDDGOvJXoGcGkOLvDHsum8MkoblPZ+\"\n    \"CHuv7LRlTfwTk/3eXh+7MTg1SAngNa/QGT3z4VIRR/\"\n    \"WCXBn4qC858WgItLUv8cA+LxnwoAEiLWpvQP71+QR/tt1n7P/nTJvgOpb/Uf9vvxD1zn/hL/\"\n    \"fvPrkBpQB39Wf3jIL/kP+emH5/5cq/qvFaAw6h/qm/sIEX9azf4pAz38F6L9Siv16V7mvk/\"\n    \"+rP2DtP6hfqm//RmLP53hDu2j/0P+cB9LHtbLRP1wP5nLrT+4Puxn5sxP8l8+Muwr2QRln/\"\n    \"srxKb/LxxT/xz55zNbHQEwIGohrkFwCusvf/YPeA8QvQ+K+7GIP9eQfq78ueAhv9/8Y5n/\"\n    \"LgA2tP5Sf1g9+6/Ov8SfIUgB2Ff/PPuBV/tf4l/qT++b/xb/5v7+g7/wX37AfCTxf/viH/\"\n    \"KfY3+uMH8SbOvk/5k0/owIJv5c+WOF/evLPy67bN7/\"\n    \"LUYv9Rfh732KLQD8KU79Uf5Lhomedt9J9qAXDQUgyh/y/y36h/wZAryzi/\"\n    \"2GaY48RucGyX4Bh1n3sZH8Xyb9MwPMu//q/d9VoKf7H+LTo/\"\n    \"7vTtvvGuCxH67GH271H+7bwXuUgHD/s6XzrwpQF3/wjZO/iH8d/C/ibzOOPPsLTP66+XP4Nw9/\"\n    \"RvkV+sEPFj/c+4f8k+AOqQIhf2oAaW/iOzjfgVXab+onztAP/sccssJ9mj6eUs7If/lYj/\"\n    \"tz9W+I9LJd8PL+b9Pf7/5MeKG2Zr+T+A6Y7jZA0XQf/YeB+Zeu/ikAdfbXnX+0/\"\n    \"4WN4X+oT6cprA0AuZ8aYP579scaDP+r9d8494eb/QrRf1M/8m+rV//hfs3+kX/gmPw3+e197n/\"\n    \"k6K+F3w5Y/u/DWuR/PMxv+YeN7jOR9zZyHlG0tyWl/c+xv8m/dv9rAzAx/IcjXDL5W/\"\n    \"3vxpqYtj/Nv8wnfua//e/Rn9y3jeaX+BfW5l+uzUX3/hfuAS6Z/rkIXI/\"\n    \"9Hq0KoJhzv9b+SD9tHfyH/FH/MXBeAW7xF/nz7CfU57KF/7ZA3K93P3+u/uvJP3wf1fw/\"\n    \"ppXZX/jL9/5ctAlBvfqr8V/B5mB0BwDv0f8ZVgAHi79t5J90B5AKpr1Ynw5gU/xD/ViU/\"\n    \"38DaY+Vi//ZjPZno+xOoHCc/uS/vYU/ZcA6Tzi7CKQMOF2M/ifCLu3kv08Bt0Z/mFnPLb2P/\"\n    \"t8l5QFEaT/CdP+fI/jc3wOA9D8VAMjNvypA3/oN9+vYf+zPzv1hde7fv/Nn8iOG+XLailb/\"\n    \"+48/+wv5t+/9zH2vrff+sgKZHwPq+s9HfiI+PC//cuyH+IzJjs/l1n9/\"\n    \"CMgEMBleBSD899hv8v+faM2fxj+Bo7+5b+o7execZ1s/t/\"\n    \"rDaWG+Nq352dAcQn5aaO+hX4j6Xz5QACz9rfsKRh7822l0kt5BxGewif2Z/\"\n    \"sl5LTcANDiI75Wj/zz6tcuq+y/xhwX15r+nf2PjzT8DLOQP90v+hX77090/\"\n    \"rUqA0wb5vZnJ33NANL+qQJEf0OSvHgC0j93BcDD2zz7Uz4Of0n9AeVX+XeLY//+SP82/\"\n    \"vJH5Hy4T0vfzRyG5EzzE14r6w8z/RlO/3gTSmZG0cvbHCnD5R39dAFr9bcbIf7Sf3DfzffZnM/\"\n    \"/z4n9Gf2N0n+FTLhcAV4BS/9L/8H9/9Lcv3B87fvKX3/npN/918HdE/lv6++xvo/\"\n    \"mHN6L+NkCx1Z/BziWE+DTmjP5OoD4D06i/3IjqE938O9O1MffX1z6/3P6/\"\n    \"I0TfqgEmv4ONyBVgyN41wJ+kP1hvg29y31E+ou/E5U0+PAAomvv/L/3TAdSv+iE0TH4st/\"\n    \"9z8wdk9lfvP9ofrHVA5P9A5G/xH/0fy+Rvj/hrbf+1n1L/6H+6f3od/9Wz3xb/lv/tX/pp8m/\"\n    \"e/IvwchpdoWb/PP2tDiCn/gw+/UOe1p8bsp1bZq3c/AdkdxeAkH+0f7ErIL9n/\"\n    \"2O8z9aGPZdZXmeA50N+Z6+ov+21v4gQHiaP6VPUH/u/Zv/\"\n    \"uAB59N+rPMF5nf++F+rD3sATJv7g/4j+t/4pP4TApP4ILwGfV/csPYeof/2M/KQB59O/Bv3v/\"\n    \"ov7W3/qtv/TL1fKP0Orf7O93f1UCYvAm/+i/WY8U7fci7+mHNuof5ccqRP2xaE7jO/\"\n    \"bbx9j2XwWOSP/IfBuD2G0fA+YHKQSg/6PN/kbj4jm66R3zD/Ffcwrw/6t/CsC767I13qNb/\"\n    \"fO3Pkx92XufM07nT7Pmw01+BRr8M5i9n/zTHYIvavyvv/WXe//wv579hP8t/o6DjP+l/\"\n    \"un+j6q/USVgQ/zh2owVRvkJ0d62YMhPu1PrGa47Y2G+02wy9zM7OcTS88snaOa/\"\n    \"Ghwb+u3a287hCLkBxB5L6L5fQeoP3v+9CgDSM4Lt3PMz5q9Rfkv/VdA/HcBK/kX7S//\"\n    \"FeUQakMkfPrM/1R8h1I/8Lzd/w/0/mv3hWj34Z/iHFff/dPiH9+zff+s/5O+/9E/vX/lTIEr9/\"\n    \"7b8h/jZbcj/4j3+59p/ffqjxt83/3dyH/VvRPflNobiP8l/\"\n    \"VThy8hfq089TBED5mf6t9DRk78fsmv1X9r/WaIT6ZjxWmeqCtuL/FdBfBcDcX5V/\"\n    \"Uvgv8dey8Jv9XuG/6B/1R/jENr0/HJM/zfL/9mcgPCeA/q2f1v5+9NtHfxF/2fCffvTRX5G/\"\n    \"1b/bf5eAagBCfqwe/mPbjb/RN//L2Z/MuIHFQT81wCWAMPuj/kAd/Afie0pAif9Qn+mKlD/\"\n    \"0f4q+gXO4aD/T/4i/P3Lu/6zZHtYnu/m/2Ff/iz2Wc9UnPF+uAEV/VQdkriugf94BmPsxFYBu/\"\n    \"mfuR2JM589A3nMxfED5hxk5+dexHyILANT/s88+a/HfuPsn6REy++91/xn+w/7tP/Vp8c/\"\n    \"Nn0L1/lH/cF+2fe0f9jf5++i/xd8h1tQ398N/8v4+rICab+LPyR/3vvD3x+/\"\n    \"TP39S1J9N9f76Hs335t4Hr4r8ufjbYP95sqK1n0Ef5nuO/sJ2hfwEK7N/\"\n    \"9N8cl9Fn721Yj0Ta+6P1328DUQCupPnPU+D16g9o8s/lvzt/Y3bgfs7+CJL/k8+l/znz1/psd/\"\n    \"Ivs/xT++FBsf+LsRXd/af37/a/L/5L/scGUX+GVfwRavTffPa/9fIH7O/pP7yvs/+V+/\"\n    \"sW4s+Cpw4Yv5//G6F+NQChfvX+vOaL+uPzh9uvFpsHf+dcTpr4GfFJs9hjpyz8vtlS/9ea/\"\n    \"0sJKPXPfyHrkd3+M2F5kwHg959eAf1TAN6NCcit/lzvrmf+o/568rcDWC/1N/\"\n    \"lz7s9k7sNpbPuh//D6U7/0IMwP+QnHiH+/+gn3Q32FFX3w95hDP/kt7mu1/\"\n    \"pv641utf5FfbpszgJJ/u4zab/HHShGw+mPHAOTlD320XxWguF/q793ofwb/qyc/6O/\"\n    \"uXy0A8sYAoAJg+Z8P6ToyGU+E74vyS/tH/Vd/OFoPdxrLfjY+65dNShDzbVdG/\"\n    \"xQAIUWAEO8ZKf7ivcOM/QhaJL4mf4g/yT/I8J+TfxkDdJ+mEhC0+juukz9DiT9CUO1/\"\n    \"1H8s2BJ/BZP/2MsfLKB/6bf1P0YP+tqvyc8FV2Dc5z1sPGf/q/jLSfyIfxM/W7kzbVr/\"\n    \"U1B+ApSH9CN6OZ4jINrWXkDaLx+uE97sHf5p6Vt2qP6sAOn3i/\"\n    \"myJclh4bsCPPac1f8q8VPUv+CDP5j0n5YCgEX2M7r9J/Ut/1q591P3L+5n+EfzXw0AzL6q/\"\n    \"6wc/QV18ic7OPmDl/wfTP9AN/998qcQ5No/GO3f4H/9ud969iMP0vuPm/tYSikCo//\"\n    \"hPjLc5Pfkb+lHJ4B47NifMOXlO/b/ePspQIQfVzCycw0Q52UMI/nq/ofnRJp+b5yp/lZ9kH66/\"\n    \"+G0Ngvf6fmPYvlstVvFH3Ayroz+KQBG1QCpv+0ji/9k8/9zGPWfd/4IvPvD7J/\"\n    \"mn9yf0X9mf0GTf7jPzn/z1h9eL3/MfXmLP6zEX9gQf4f6d/66/w/\"\n    \"x4Qq0Fav6a7X4KxA9ANDksMLB+J++P7KvtMg+EyC2+8LPLUAOARpW/\"\n    \"XT7UX9f9J8ELPt308N8edQfS+6JXwERO9gkBG9mC+cS9OpP/\"\n    \"CeNd2Ug2s5EpBwoAA6Ihn9UmCOBq6c/JoC680//zwAHcukv+Nz/PfDens4/\"\n    \"T3+mALgEfBb44n9V//5jf/3kJ/JfB/+Cmf96df/V+G+e/Lf297u/7b/4F/L3vb9SxL/\"\n    \"Jb+vDP4v+bG7cJ+m/oQJgTwGAI0T7x6L3fe3fBSAQ/eHaPXki5Pfsj7kfkeYk+Nu05/\"\n    \"LGdD8f5rsMeMFtgreTcPHnAd909grM+PDf3mzPj8x3esb/E1B/FYA/Iv9Es58xh/6WfySO/ua/\"\n    \"r/0YAhDf4k/yw7HEfuSQv6/+8u7nfYbBq2A/wlevDv+r+Wesf+K31P8NLnN/+5/56pP//n1/\"\n    \"e7/637j5ox2Z/e1GTwA3lIf8dQCgCGgCsPvJj0YAK74y7AjmgY/1/0SG/qi/\"\n    \"nUB2zDfd3HfChnkITt7HgeoD5KC/OZ0gC+036Z7/\"\n    \"UcnfVQD4cddJ0J8TQBqAdP6xvPY3RH6pP5dbfzT+8HryJ9eLH8GJkz8to3+Lvw14v6/\"\n    \"9Mvn3q7+Qf+Parx7+/JW/9Avvk/8N6UeosV9W2l+9f1/79Z/8wuCvs39JP1c6fxcCU38C3/2L/\"\n    \"7MIx8i819zypwpY/3+5/YQQytOcRfJ92jsoWe5lxOwi9f7IbtT/0R3TMfYrhM0R/\"\n    \"vA9G6+kIX9wF9cpzP7Gt7+2/GuJ8zkCGOjFP10VYI7+CUVD0s9F1g//Lf9D/c2b//\"\n    \"fpYP7HIb+1n/bnV3+vpwDQUgJSA96A5a99Ff/pQfPf6m/rEgAwBmn+N/7cr3xMocX/\"\n    \"hqNDTv7MfLNei0nuaz+XALDdxJ+VtN0ATEn48bvbTwns/WHLBMBks/\"\n    \"I7DfO5MgI484dDdIRJ3mnx1d8AJOe6cArPI/NGdmF+tF4fDqkCJ0J/TgDF/Sj/IAd/YD0caVp/\"\n    \"LIRSf5Afm7AfyQ//5MabOfh/s+Q/EPvZ+8PD/Tz6e138D9bG3x4M+c3+fvYLr3v/\"\n    \"gwLQ3I9t3Ptt/SN/IX4j2n/DJQCYm7+UAJsT+G6bCmDxt8G5tif/u70Y9ZPTGfp3GM6L/\"\n    \"HaZ3cy3KchTBnIOAJ+JINFhd/GXWz/iQs3Ao4jYzTFfU99fDtt49K6TUn/ipxL/Ofpr9f/\"\n    \"cwb2/u3/Y3PpF/eW7zp9hEPYL9fLnC4g/hH8so//W7/u7AvTFX//BD6l/\"\n    \"Xv3AsXL0p9TqX9xXpAc1/Jv8R87+zX0n77sC3EDM3O/Jn8Hcj+Xib1X/OedHMPkZmvsl/\"\n    \"sApDf01+4v2SVb+cwavYN7+zd551H+NUX+Q3JgagG/yXj5jQRG/eN/d/\"\n    \"11TAi7uevSE6J8J4D3HsYbv/\"\n    \"N36wzz6KyDCg093PYDpz+ihH+71JmjPJOlHSPcf+Tf5Jf6QfhiczEfY4//r4v7rq/\"\n    \"5zGX7zz74/BSDkF4b6PfwX6uB/Ef5t9W/ue0X9GxH/Of/Ti3+P/igC4f6EqL/\"\n    \"6fjgWA625v90KnMhNf//Kj1r/jQqgFUvrz4AVqs8uaVX/A/FHprMEUP2L8Nk0WvtP5t5/D9/\"\n    \"Xqz8YwEQPrP6j/VH/FRr83f2L+9H+scGbmfzfNPnl0n840ff+Q/5of/\"\n    \"8Tn6v8v8EV9YdjFfnt9e/8bPyhf/jxf+inLv761X+sy8CNIb+v/aL+TDn8W+Sfyfo/b/5+7/\"\n    \"0dZzWm/T+ty74Vw/00+23n8GE+wBR47nfcqAEz+4f4yWr/LxBlF/8UT58g/W+//dc++c/\"\n    \"NX3ivtl/6Dwd88ucS0O3/QePPFVj+zX7UASHXfiS/\"\n    \"1quyr+DUfoZ36DADym8LVukf+U8BeMPk77/zX+qP1eK/Rf5+9LOp/\"\n    \"l0AtuFDf5cAuJhP6Rf3bQDD/tn/qH8YP3EbJ9v3R/0RbIekj/6H//kwx4P+TvNvwq/\"\n    \"kN9wCDP8v/gUePZWLv4MGIHO/TaGg1t/yL/bTAARD0u9I9nuZ/GMpAAgQ/nT+oL7F/+MD9V+b/\"\n    \"4X7xgb5M/2/kWv/PPs5cvZf6n9Ptf+Duvkzevbv1h8+Vn0/\"\n    \"td+8VwUY+QfRuaX+58jfqu9A1fdizKW/N3+OU+z7hdB+rwxgKZw/9VQU37R3EJhb7xt+8x/SR/\"\n    \"2ZRXu4wFLAZP+LvH9U+v/o6dE/R4Cr+s9a2/7p/K3+XK3+bv5N/BSAcP9NRTCf0i/\"\n    \"xf3Paf5q5j6CDP0QN/3IG0P/V9S/9016vP/Vbf+uTNeAN3vy9Ufd+sL8y/af3L+2vi7+6+m/\"\n    \"ln819Z/lzH4FJv9vApwjAMPz70i/YKACj/vZtTN//ze2nCjC+ja6wtvn4QDDlHcN9xW1Y/\"\n    \"YODQnBBu1Dc7wD84+P8P83ZP3eAPv4L+Vv6ZS4CvvejEUjp/N37c633/jGAyi/qi/wz+PvKH/\"\n    \"yHCSkArw73EYLX4br1h0X+gzfgc+vv+Dh85f9jCrJ+9Mtw/OHPWGEV/\"\n    \"y4CtDMEGmsAluGhX9FX/\"\n    \"khz7G9P6w+4708FoNgz0aP+NfsXTrTvF54Smv8gOyMgzqMPmCMA7VIBjiCzfzQ/\"\n    \"W2Eaf0WSeczu5eTQePR06a8GwMxHqgpA7svJ/GkAzH6GUn+aC0DEn8HyD7P60wAXAAcxfxaoT/\"\n    \"JT+2GI1H0Fc9/6L+or9M3feu7/uEZ/o1v/av635L/f/G4QP+K/\"\n    \"ce0HmPOMQUrAjXnxj7izZ+BKMIAxKb/zYwvtHa9h3y+Y4mB6NrbZiP2mPLLjNvOPq/8FQ6DX//\"\n    \"zha6H8Kv3js+ZnSU+r9Uc+Vfr7BGAMUOhDfyUhw3+0Xz78j/rn1e+bv1eAjxnVAWT619Eflsn/\"\n    \"KgzCT+qP+g/3F/kf/r+oArCv/qQ/4pQAsB8zQP8b/0p19Efro/\"\n    \"+6+ov+dxGIFc4YQHy6gt2qP8nqvy/+S+vvjePeL/ytp/4J11P6Sf+wXQj1NfnDASs/EnzIj/\"\n    \"zXoTf/EvqUAP2EC5F5tWzjXvO9loLJJ3Xvf9gAmPtdAT5PARh8UMP/FAAaoPDp2vrTXQBE/\"\n    \"rn1R+bQrxD1J/fn6N/0Z+evAWDIH/kn9Xv2F/dF/JF/hMeR9mHtp/\"\n    \"eT3yoA9erniP7DV6Df56KdnZ2Z8IhaQQqALvxvsATE0vl7F/jsf5//\"\n    \"dgeicVLv+4PAM37UXh9xBDIdbvZb/WXc/B31N6+5gguHicPuGgHyw1Den96c8Oy/\"\n    \"NADwav3JfZvDIOR3fIvkTwnQyvCf5t9m6bfN+O8eIOoP2x38uwLQ1nv/1zH81y/\"\n    \"8jvjD9OznDbjnfyt/n/3tkd9hhv+M/nX2R+uj/5Z/\"\n    \"htJ+RnAeizG0l2NR+OEuAiv1ncx+LyfBxLfXyR9S44SP/AZh+loG8gPIvtgO8jPNCWAWwt9r/\"\n    \"h2zsQH5Dw7MXRD2kncOT5/kyX/eAGyRX8uHfo4ivhIzmE8HEMV9h3r062d/\"\n    \"lv+8+Dftrf3mftT/KwTxP7/vB3fjL+l/HeJPQ/tP7of9rgAsASQ/r/1hb7wh8vfZX1Dq36P/\"\n    \"9j/y13/nv/XfluZf6i9bqoALALVfHjPhUwAW6R/cEZsAu459/6h/\"\n    \"FQAGf51zycN0bbP+8tFftN+bUN3ujxWkd5YtxE8y9U/z4i/4HsynE935V/9v9mvl1g8FYFr/\"\n    \"kF9rpJ8hBYC2dv/wtfW39Msk/2n+beT/jP9TASL/vPiX9Atu/FkHjHC/r/4i/\"\n    \"ikAB+oPhPyNqP8wH86spAEAtjAf+2X0l3kACPt19kfWZwYIJPqzQn76dTzyO1T/KL8DCa/\"\n    \"slKa/iP/nHcATD0b9D7idjX1W46LahZSDCSc9+wvf/uoCAAt85m83pPwKjNB9us3qLw/\"\n    \"77ZoATH5wHujOvwuA2n4f+2Pl2Y8cJvIz8OB/VX8F3vztmn+e/GPBCtH/nPrBFSL+/\"\n    \"eyP5tTEH1sw3PexP4f/h8x8FwERH8E+J/9YCHtm8b9h+bcHIn/MjnVNpd/qf/\"\n    \"fIvdY69Xsr7SeG7XKH0ByerGU8+OATRf9id1L2x/6Pw9ng1NUf+Cm9f1/7LTh89/\"\n    \"eW9X8qADzsd3hT8j/2++RPn9/23/uFPxsw8p9XP4v8k/vqAFwAXg//Lf8Ivz/\"\n    \"6Bfu1gnrzkyLQo/8f/yufm/I/v+1/hkTe27FgngHAf/\"\n    \"E+h4CWfruNfGdgZgTX1QIwqQdoeP63EdxcV+kX/RvpBs7j54w0uxajzGyfNB9ysr/\"\n    \"p33TOHlao/6Td73PBtVB/4Pt6749Fi/5n8Nduxn7mHPwH5r4LQEoAfFr/\"\n    \"kX7gi51l9J8Hf68O9XPvj8ibP+v/QeMP8ME/uY8Al5n7jjn4K/WX1ehfF/+x4r3Jbx+E/woS/\"\n    \"32DL8yX8i9Hf8ggPJbVH1YQ05XXu//rOvUbQ3j4mL8Tp+mnD/tpQ3uHZK7UgAch/\"\n    \"5v0P14P0u2v1K9OgH7KJ/85ARz6dwEo/Sf5FWAgP5L53xWAFvEX+X349z6C6C/Mi99F/\"\n    \"S3+UwJ6+Af9eeoP4WdGmgqgm3+rPyuAlR+pO/8e/fvqH17I7/\"\n    \"orbv0rv2gATHy79qP+Fn5TH3Yg/eE+HIDwS/2nDcDaLAITmK+r9ANheZD3/\"\n    \"uK5EO2nme7IWCa8PQWARn/w+TT//6AEtPrTE/JTbU6f/jUAkPtS/33uv8W5f5V/c/\"\n    \"8tLMbgbSu/K4B/199zv829v/j/xdircBog/lv75Rr6FbAMn/2v6j+2e+7PyJXWP+w/cu7Xv/\"\n    \"JbF3+t/zaFYOn/gfsy+4v8Tj7vi6kAsA7YKPlwQKkhxjvHrrH0A93wz7lfIq2ngJkAzPTxqL/\"\n    \"ZDzwf+v+LGtCCn88J14H+HAA+fzdjf+796+WPlB/L3OeCg/te+/O/1V9OE6T8MYo/ljuAoX8e/\"\n    \"Ur9v8rpH8oAWG/2S/9pg98nf3iu/iD/9ee+bGF/Pfof9ov8Qff+rf6c/bFSAmAKUn+ZyG/\"\n    \"153Z99YPFtJr4z6Th/5D8Nfxb/q+t9G/P/kbJ/9IEkOpQ/\"\n    \"hA+S04D9WE9+78E+0d1IJt8pQG4HvTXDcAg3A/5Lf3QfjgXjUEFoF79vGnuz7k/o2V/\"\n    \"XNoviP2j/MN+Ld/7g/8M4L3M3Afc9a/cl4H9sFF+rVJ/WbA2/gwm//af+Se2j/\"\n    \"7h6fsZlBAy+4f8WDG7uS+Q8lrq+ZHmDGBFab/t2kr/nPwX66P8CrAEQOwv9U/mAsh+QLM/KQ/\"\n    \"OI8IIffybnsA5H9dF/YHwf1V/IxUARvWX+9Tfqo+oEPX/Uqd+n+XcT9ovs/Cvh3/W//\"\n    \"0CMPf+rACvZ/gXvraR/Vxz7QcnPmSY3Qz/Ef+c/W0e/Yf9WiZ//wv/If9kaT8S1lAfbvH3zZ/\"\n    \"Uf2y0P7O/4xz7i/3UfMZQv8k/qk+7vlP/7/R/GSvoCuCFIR8BpN+JP/exhHEYyc/Z3/\"\n    \"QfytsmxOar0OjbAq7rQ3/fAOTWr8UfhgjGIyH6xZ/7fxg8IO01+4v8jiS+ld9Gt/hj8Bfvc/\"\n    \"LHoLZfBSDc3+d/qz9cmMl/5H8pAHX21wf/q/537+/Q8n+Ww3/zHU5DFund/\"\n    \"cdu2Bfuw0l5Ze+ecQVAsPinANT0b+4rKlxT6a/mvzHsf2L039+mOJM2v1u+IP7u/\"\n    \"a3+Ev99iwPZ2PKD47h26g/+e/rP1N/qD9p/7lv/sbcOjv6+XIsA1F/CT/Zb+6P/wRe/X/wFM/\"\n    \"zDJP5yGuEJQEd/Gf5H/TX1O1H3Hf/07K9mf5P/j37jLxWgMOJPy8M/25nU/\"\n    \"1D4GW744d9qGf3N/bFc/SEGufYzpgpcU+kn/Y/jfKiPGqB5f1xMb5vrfnA/\"\n    \"6r9Td7hCi382gGN+drQUPH3N6H/77T+v0/+q/2/\"\n    \"JPp8zP2xkn8GZxH1spwCQ9QxIUn9Tvw7+Mv0Do/7h/5BfK+r/9Rz+Awv16Wn8kRWa+hP65r/\"\n    \"Vv5/9pPWH16/8nTExWvsdYiC/3aZg8jvkus+7yD+ABiDUb/knzob/DNdU+kX/\"\n    \"43DjD1qr+ccONcDjP5wW9ycID/pb/qP+5L1lHijiKys4OQupAFUgAH9eO/rzAODzMYZD8Zf0D/\"\n    \"nhBomPEGQCEPURRv3htJX9DK8ihfo5/OPDv9dfNfftufkDXrQNMvvDjcfHNv/Un9H/zM/2X/\"\n    \"re/o0/BZ39M7gI0BT41372e4AbywCA/fr6x/yP/APeNEb3zybMAHBi/3jPf0T/J8x9Zo//5D4/\"\n    \"xXE4Fs1pLQZPQP9X9TflHb0cvIk52fOTVABvQ/tr2vzPC4A69wv/pwK8FQPxrfsj/\"\n    \"vQ3abj7O3jxp8Ch32b6a1n3g6/m5E/C/+pQH27m+9n/evFP97Vf9N/44xf/\"\n    \"NyHUtX+6f3rI3+qvEJwppPlnQEwB0Ar1JfwKY2B/hv64z/\"\n    \"6Zd8rfNQCkp6945jr8Xv9v7F27znVTFM1+\"\n    \"B4nKE3gCxd96AolEoqZRKYREo9boqRQaHYVG3Bri0iESxDVal0g0EuNyprHOGba7cLZ/\"\n    \"zLnmXNudZMwx51rr++3htyf/lICT+pP0WQCzPSXAH/c+ggLwCC/\"\n    \"+QvUL3sMHEfk6IPQfmt+wLDrz1dFfLwDeLPFn468g8Yfuy0R9s5+B3M+9n3LYz+X+v2Z/kl/\"\n    \"qrwHg4sf9pP5z9BftN/8X9ffhf0gfzOU/UL/M/7aof7/58/zf5Fco3BD/acjif5p/uUgPt/\"\n    \"Jz+h/5T/tv7RdMfgeR3iY4hfxGGoDHruHn+v8k/\"\n    \"cV+8v60TH4FGd3Zop88LQC2pv9QWLvxCScjQvNqCybKMxrMujb63/LpDzX6R/\"\n    \"s59uvuHzbqn84f8YMhPs3NP22a/+r8YYq5+DemAsB07U8/2WB59ffCS4xz8P9Ocb+0f7r/+pm/\"\n    \"dP597H/W//fRv9ziDwe4GyPrEYf/sZP4r8f+SDJlyz9sGN/aH/VP93+VZ35A0//\"\n    \"ZuBp9B5t34L4DI4zujT5kynbwn5EXfwPssu/fsIs5O6iTgPi10R8HgOb/\"\n    \"a+fiz+QID94m9ekMxge5+7Py5yd+AWxMf0fTH1d/sLNzv7AfzM+D/6g/n/\"\n    \"331V+hLv4Vevav4T/WP/FfBeCGl5/7a/IP9zP9L+2/dZ/\"\n    \"MR7bZ71G2+guPyTP8a63af+Oaf75n597/zpX8o/jZO5Lw7gDC+Ii/\"\n    \"ozfEtAC2NP+FIvgEolG3BIw2flwd/cV/TAAl/giMw3v3/Xaf/X+gyd/i75jnvvp5H7f/\"\n    \"b3Xz7+GfITWA9B/+v8C1YH7mXy//MvxX36/Q6m/bO/qvB79GqX/1/rAQ3/P/\"\n    \"kB9wAcCy+9WPOD8hoz8Dhd9rDGB0MKL5iFf70qfo/+yz3QBI/\"\n    \"0N7kp68Dx4V00N9JzmZ7zTV4UT/5/aJz5V9Uv2B2YzN/hrVnweAVP/3sDL4Z7NKP2HtJ/\"\n    \"kJRZL/Az78e+UVeNT/LSw/+FF4P69+59ovnb+S2U+HrewHrP199xfsH/1z6sdiuHsKQL/\"\n    \"86dFftnfwl+M/WnQ/4h/1l/jDXAVy7KdtxP8xrrFS/\"\n    \"4fT+AePXe2Z3wnd+mfodwkAMgKs8FngegAwH/Oprelv6iMi/\"\n    \"17stwA5JWDy5krpf8sXpP4br7n/T+df/BftCdAeW6o/Q9Qflp/2saEJeGsFtT/qn3s/\"\n    \"Otjv1h9OW/Hkh+Y/Ali/R/6c+xGgfb37mWM/edR//9ofLnQBqLP/cB/\"\n    \"I2d8y+dsSgBQArlSACH8g5qcGMF3vmV/Rn/znomXq93Y6gBAfzKevE/+EfBua/UN9b6Ya/\"\n    \"HGE84UrbP7F/zdeQ/9v9eeyw1r8yXsFNQBwBKu/mP9uTv0l/5L+VzL4uwQAuvlPARD5xX+mYv/\"\n    \"o/1QA1wD4irC/f7nP7v6D1IB69Cf62wJzX3FsnfwBR5Gf2p8ltkf+NfxP9y/ap/\"\n    \"N3ygQQ4g9uXPWZ3wX9n0rfb+GHaRfiMylrGavG27OdMOoPt9EVHP2b/kxPkEkAfrX0B/\"\n    \"9B9DfhC/ltK/ffjXMCmMn/Ayy4TeS3/MvV+DuY+Q4kfuBrv+n8Xxj2cxmc/NX6m/y7s7/532/\"\n    \"+0/1L/GXF/KDb/4aU3+9+ZvZnRpBl8M/oD8Yz5Nb/ovF3srnv197IqV9O/K/6zO8Ecjzkdwrx/\"\n    \"QW2Y6UEcOn1L7dD8poCEqj+o/iINrnrQKpAocmfXZh/3erPC0Bd/\"\n    \"EX+ZXBf+yvRxX4rvywFAAZY/Yf8gPkPC3zq7xTxt/7L69IP1OdSAZhrv7EFl4P/\"\n    \"huXJf+39NwWwvq/+af3k3x7csE8A1xVldmGd/mfwz6Ef3BsjV/9EPfp/OO2+FsPDV3zZv0DcT/\"\n    \"efCiC7mPqVHIycBkb1M/nPTupvzq8VAB7L95/GfVdLf/GffA/3tRn42Y/\"\n    \"WjP+pAGj9sSL+CGa+Xv0hDfE19oP/tEDM1wJegPprSf0DnvtH/Uv8qwIM9RXS+m+a+R1a/\"\n    \"RX64F/e438e/c/t//rmRx3AkN8bMz/kj/Tn3M9jv2wyYdbfONCJ/6L+0/w/\"\n    \"O7RHsvR72abzPyVb5J8e6jNrOVD9iaG4QpNfyPcf4/41qz8A/r9p9o8DLgSZ++U6+5trv7X3j/\"\n    \"hn+oedQfyXdQHwnd/zrf1u/q3+ufdnaPafY1MJAO/p4D1h/qsDCPrcP/K/c/ZHt4nzOfeP/\"\n    \"CPIeel/lyKW0g5y9pfRP5KP6HSMxp8Q+90AmPgxct8MD+mTsqtjgKi/\"\n    \"g+lvvjsb6ySQOgA4e7+Poxz9Df/71K/xwWu+9Ces/Ug1+QOUfvBftl77F/\"\n    \"E1+ufob8iPFYj39Jea+jn1j/oT2xz8ifl98L//Iz8t/xetf3r/mH+RP68M/\"\n    \"+B8fuYHGOojegWmfILUP699pgBcy/+963eCku/uf5R/ZJ9xwaP27LMi/\"\n    \"dnl496ofyFVYLLD2Xjw/6A/+G/y59BPG3oA5iMO99X6IyHyAtA+eGtP/S3+8Evx17k/\"\n    \"Asgvr+5fwz9M5G/xH+5jGRvIvzz659RfBaD+H3/96N+r5R9Yfs5/ctTfAOXd/\"\n    \"Ev3uWDGbDL1x2FUflgG/uOc+K/qb/\"\n    \"Kf6346fz33DeHXfcOkT3yE5Df9zXCFQHvxfFKYn97gf0H/0X/ZJKxQH+Zk7U8NAF4J86f5F/\"\n    \"ex6OF+iB+8Lu4jI/ad/1je/KAE7Fz8w3LsB8dOzF/VfzuRH7s7ov41+J9Jf3M/llt/QDEVIPP/\"\n    \"MvjfYAx6/I/8i/Mr9w9y4h/1BzT5x/DB9SDYjyhgw0XOpxDky+hJAD/x90Rm/\"\n    \"5X240GKg7OJX6eCit4djP6j/+E/XeHtdP+0gPwnrPywiH/sQv1z8F8lANpPg/fZ/4j/kyS/\"\n    \"DL774n/zkvxvqQD16Mfon/jLtZ9ik3999bc++h/my+fcX5G9fwb/6v1DfrhtFf/B9T/1uaC/\"\n    \"yc4NTUm8Z4C5BIzqP8tQaO578wT4P+r/eyHaByI/13xcTAjwo9Cf+m/2D++9Ajz5QfDsH5j/\"\n    \"8IK577iSv5nPmPf+Jf8Kyw/71uzv0R/I9K9bPzhS3vxiSf+3EH9f/W0t/zX96/qPUWE9/\"\n    \"TP3FaP98H31N/Xl602f/Sgn/gOzP9wPQHUpPqKBjJrAxSQ4BV0IHkH8Q/\"\n    \"Tv8cDkH67H6uXQ1dNf/IeP2UN+q7/Cu7Kwn+M/rNRfIXD3v3v5By/lJz5UBbD674u/zKo/sR/\"\n    \"90k7YyP7N/N9a/en94D/qv4i/ee8QiPypADbLv0Mjrf8ov3yO/K/+jX/TP+w/\"\n    \"2yEM8ZG1Q0VQAYAr7ZYAT/20JzL7F4rwjf6jdl4N4PMA6m/\"\n    \"+Z+qHn8PUz+ivjVnv+O6QX7GoX8Q3Xpif92Pbb8wmj37myf80//KS/5z7c/A3+ze7ZB8B5pu/\"\n    \"mfyx9m/9HaoCRP3h1nzsivsiPpzGkR+5xT/HfcV/eh78HGrsFyL7SjFP/s9yR9MnM77TC/\"\n    \"w6wH7Zif4vYyEY2TXH+zNf53ZZD66e/uS/G3940kp+bpCEmfzhmv1L/W3D/\"\n    \"YldA9z+w8B7WZDRH9Hin6O/Fv8Na6AKoMO/qL9bf6TAdWAqQCDeFyL+Fzan/\"\n    \"1UD4NF+ukJjKgBDyH/jYA/9LtU/z/wG5D6MOU0/PhBgCnvI1I8F6sOl/\"\n    \"mK71xjDfgnIpvhPR+BydD6C+oP/3fgHHzCsx/6W/53hv8/9evg3zHn/Yj9Bz/4vYZH8/\"\n    \"eC3Lv23/MTfBpOL+2v3b95v4v1W6l/D/42E1ejV+BMQe/NfhrxfACz/Uf80/\"\n    \"lqMBxv7hbNpPybVz0QQ2sMBJ33uaz8mf8v/A6b/kH8SfPZ/bCAI+YXUgAOov/kf8iusBWAsYz/\"\n    \"d5MdmR/772R88uu9Mgyu1+tsE6b9agB781QAYZn+EXwE+LYCpX7/Mf03+jP3ir7p/pCoAo/\"\n    \"wK4fyNor6Rc/957oN40LFfmIGfDoTvcIQ0/zoIdAOQrsA859pv/\"\n    \"q3+JHmMAc6VnuDP4GlHr2PQH/xP67+geP8uXOLPNNQP+/fk/5fEn+6Q2b86AHI/3X83//\"\n    \"SNFSDyz+7f3tf+fe931gLsH/yH+mn7A+3D/\"\n    \"Wr9nfbnf5GemxuZ+Q859ufoT0AuB9lpUX5lwt+PIA7X6U7eUfpRABhD/ws/\"\n    \"6wPyXWh0l3AY+pv/Xq39KQPVAGi1/PerP9uKF+bVX3Pf1M+tP0Lf+5n9trz6mYM/wt0/\"\n    \"QsQf2m/qqwko7ufeP8Kfbbgv+mPDrxZ/\"\n    \"m9OEHajlj436H+MX9gjq0a8sM8A414z8zIoJJrnZ7q3TPPcj881+0t+Q4M8GpjBu6k9WaLTyu/\"\n    \"M/EP0/Bf+BnQpw3gC84gu/Fv969jfcL/V/wdH2etgfhP2iPgMWvV/8bY5YVP2c/Kfx384P/\"\n    \"bZ68Bvxd2jlVxrT/+mnQdKnBnjez6r2X+vEeTcBwg2Fh245KJ4V/+nYyIwH8cmkNf3++HxA/\"\n    \"k8blQAluMIjevIX9W+4AjDa7M6sBkZ+4y6Opv63fDpc7+bfTrT2w3bFP81/yT+5H/\"\n    \"lnFPYrALlfMP8X9XfnD5sX/5R+n/1hpziNP8JWv9KXvX7et9p/z/\"\n    \"zODVPfbpPvSD+cNnVgjvyPeOg39Df5z8U/5/8IcQQbOf8IyF/qL5/wyCNR/\"\n    \"2Z+f5LzY0DaAXhy4+mj0f+WL1bd73t/reCVnXN/s7/Hf3gjZ36l/qb9iD/VH15H/xuDeU/\"\n    \"cn6M/H/2b+/3/+Nhy8FeH//Xqx2sl/yRhcqjv2Mx3Diz947r1B/Rxvf8Pn9+n/hb/IDVgdN/\"\n    \"ZG2RoPhwblwA4Fg2YyHc/Ofkv+jek+Mg5GhxLOm2Prv7gf7X/\"\n    \"H3iV+oP3jDR4nfzBzsVfzs3CfLt7/131f2lRf3Ef2374uzGhBMB05y+3/J/\"\n    \"U30Vgo83w7xogbMvs3+3/HfXgl2bi76h/\"\n    \"Hf5NPoe0Hm7CuxQc9a3PArb+8pBey3hwMs0ZTvKL4Nyt6p8vJqxq/gt1IED3KtZ746+D0/\"\n    \"+Wz0v/TX2HzP5Ehn+uffXvvr8LgNUfXtQf+bf0F/nXhz+E9T8v/gDP/X7xZ6Tzj/\"\n    \"S3+jvmxt8x+u+4T/6c+Tk3HjP/6Qw2+cF+xKfV/6kc/a0I4Z29BeNPrb99YbxtnKFm/\"\n    \"12E8w75rfkU+7OhOSEej/63fFfkd6QFaQD87re7f/nFpf9+DbAFgWR/79Zvw6pnP5Z+2vT/\"\n    \"kn05NqQ9lzv/y2u/+pmfiP966R/Vz67u/erFX9/\"\n    \"8ZdTfsFQCPAUc9tBvVX+zvxHm09Xwa6N8pv3JY6P9aAOK/\"\n    \"juI6ufbyEdob88CPr3lOPhh9+zPOdxXoCO0+PelX5/9j8XPmA9XjsG1ov6uATz4H/Wfe/+M/\"\n    \"uI/faPPyZ+Hf6N/qc8a/5v83gX95LfU3zFA42/ya2HL6f94P+C3d/\"\n    \"F3Z7HeMO3lGvRp3pny6gfCf1o25j9nAtL/RTgs/sfwXLKj2b+eDx5K/Yf/4X74f4nq/\"\n    \"F0AYvXkj6vlX8f+GQBa/uEmvny9/t+s/tP+2+cX+iNcBO5GNPkFz/53+Nzfm37145izvztC/\"\n    \"tL/PfGPhft15i/yGzf4267/f+Lzm3Dv/+weSPzp+/GRB4Dk/wXb1wLAL+o/\"\n    \"9zr5N+W9mOJBo3XfeX6rHJjfdyj6X+r/B8X/qL8tKPkP/+W7Xb+X0PqfJ39YxBb+D/el/T74Q/\"\n    \"SP+1r+o/7CNrM/C4B9v/U3+bEv/teLnyoA9ui+Q7f+xO3brbezCvj6/\"\n    \"8iHfqP+kv6GRV+ZSaJPncdGGWFReydEG+DgT9FfZLdppy3A6M9CoQ4IFVQCjqf+uf4P+\"\n    \"2V98yer3l+pnv1Y95VXpP/f0f7zsz+Amarvrn/ylID8Ut/n6j8m7bd78t/kt0X/h/\"\n    \"wMUf86+z95crjfo/8EpsHP2n/7DP7fHOuX8w0KYP+c/u0UAccHTwVgoopAqA5zYiTym03/\"\n    \"sB8OS7AtheB3IrcD3hxP/Xn9t3vv19yvAWDn4H9n9leyE8k5/V/\"\n    \"5f46NTuaP+svY+sM3GeDnPkbkH77tiL8tsPTfYMqxv3138O+zPyWHSD9Un9hgt9/4n2j/\"\n    \"6d4fTtujfhxMViT7gTn8D/mz5rfJrP5wh9lE97Fo8D+F6QWOdPRHfL7T/F+Iv3z31R/d1M/\"\n    \"s7zTkdxo7hScRDFPe6u+ZHw54c14GZvYf+Bf7MvVL/tUBiPyXg/92m5JNwdy3RfpDfcRGD/\"\n    \"8AkvKtXHIrv0x1gEf+x77wW9R/7BepH+Y/\"\n    \"K+qL28pR+PEzm6QF9Sde1nKCyWl2J5pCfgu9uwPT/sXnHCX/R1P/XP+Z+Iv4N/\"\n    \"uDevMfpADs3vrREcB/2Ln6Y9HE+u3J7QWrfoZ/kN+qr0iQ+Wn/Pfznx/1B+Mi/yA/\"\n    \"aC6b8pmkf6UT+GvyT6EGIvw7+TILUPQEOiPi3374h8ZX/Z7f8H4DOf78AZEu3uRAgD/\"\n    \"hlI0z4fPpDzb9ZT0T+z3uCuOF94M+uAmA/cbjm//z4r8Tf5M+zn/55f/l67z+29yN/\"\n    \"Q34wXwY3ltHf0s/ckPiv2h9s+cF/uBD5Dzz9e4dacAMFwUUAedi/\"\n    \"1wA0xHgHLBGf5o2jx37rPx3hwD/j85vqH9V3IsHHNPiP+sORzmf9Cfmkj/\"\n    \"oP9fMhd1i7gdnWIYF25rs/uIe9zHC85l8//TeT/5jQz36RiO7/9y7+HYKl9Rf1bevT//\"\n    \"xqH8T3wifGV8HnQGP5A7785EtBf/4cAAR98ZcOABjiOzBlBYFob+U35aP+iDTgmxtQfhi4j4//\"\n    \"DfvP1J+Ef2qc5HewT0F4RF/ReZUE26BPAob+gemcIjCBMaPA/KF0p9QH8d3R+ZDqP8d/H8hH/\"\n    \"Hv2pzGE+SE/VjX/ffw31F/w/Pfffwlmm9IfG//USKy/Nv4mrBSffPWJqkP/Uh9Wfu3CfrhXEV/\"\n    \"LcRF/m12Ex2+//TT8H/wn/\"\n    \"C5x3vvDB9xn9NdWkXRmcrikvQ1uzG6lfxeCIGVgvgXl2cPoCLbnsDwAvHhE+vP4z/Jf6l/\"\n    \"k33nzWz/yU+M/8cMPP5Dq5vl/6NwL/zCsCa4ISyWIwQsN0907LpsP+7zxdjv8U79L9T+5WU/\"\n    \"zTT8ygo1UXy/8iDkBSJcfzKs/mmb/HdQg0O0Bk/nuP4jLmwwDsiM2/xr/h/\"\n    \"smv2MXgf6JPxcAhiDMP9H9v0X231UPvnY1+H6hfYl/I+rvNJO+jPD4f+PwT/0u1H+Z/\"\n    \"RW0EeVVCpjJfcfKQ/rKoj681L/RhwF9UpBdCH92RXBQ9Tf/P9hVf5Fe0d7df/\"\n    \"DKD9L3ayP8b9SCH3/88stf5zxn/zA/uu/gze1M/zP2z+xPN/mZJszUrwgopBB4GaF+IP5H/\"\n    \"f8sRu17UrDDEGWHVH+O/yE/w57299Ufyf/BD5+A8l8ch/JBV4Jvv909/zPyvH+D3Dvyy+nG/\"\n    \"4v9UP+R/1F9OLMozx4Aayyb9eKvGn98WfxX9X/mRfjYHyJ/tuE8N/kttsOqP8f/D3bF3xYf/\"\n    \"PADSX90zu8Vgh+//ZKsF5DF+zEpv9Remxz4Q/v/D0/9fkH9yX+a3WGYj5yxP0veWMU/6i/\"\n    \"2I3D94ULwcoLzeji4pIOqv9p/z/40emOY/8EP3331xRdH/Q/x58pAXvzMpL/ZcNc3G3UF/\"\n    \"zf2e/a3+of3OfXzh5U/BwBF/EKpP6hOdzDvFbF3/nNjQeKBm//54R/xnqHVH/\"\n    \"7DDzdpv1cGvv762y/VBtyQ1OtpHwwbXvgDOvP732n/\"\n    \"ov4Z+EF4OPdz6M9l8jMkF1r9ZW7+V3MRSCEY+/M4tPpz/Hfvj4y0Arz/\"\n    \"7ibtf28v4C6Aoi+7XRf+mgH+h9pP9RceDfeTSPtT0y/Hb2FpeIQR6P6/1Z/hCap/\"\n    \"hF7Gj+y05GP0P3FCcNDZX4//u/VHn//5TeL/UUwR0E/58Y2/sP3/\"\n    \"5n6CBJ9jf1qmftMeXB+eP+UMA/wRNJ4Q9aP+o/aEc8Jwfq8NeKY2XQAOrf4c/z+wGTcV/\"\n    \"y92AqkBPPL/f2o/6T9H/rAEiz99RB9Oe0oG9zZo+X/Q3PfsH/JnzYaBmV97NwT5TuIfmj/\"\n    \"g2OrP9j/M/+iWm/gbcKoB/8P7/gF5Du2naasAYgNUd1UBLu5H8tUImPvJrf6PkP900T/\"\n    \"KH6QKyAeidpCmYNLkVISjz/5q/9XtH/lf8d/AZ19/+/3DD//\"\n    \"f7vsHOfLjzlty3uFZuS8AkCT9xX2GJv/Fq79mfrawSVUHnLnJnWHIz5B0aPW/5Zab7f7fjrQB/\"\n    \"0/2S/1Xy1Y9AFmPRIJrhzScl53wKqzByR/rwSeK/lUH8qHg1EivMGODM3dHV/+buIm/\"\n    \"HWG93DLPBcOeQQ0AKkDMwcSXZWfPzV/U/\"\n    \"3EYHcErqCNBmrNDkL3pnz+cfpP+N3ETf4T+wrOrTziRXnK/OqANTax3EJT9W0B7tf9W/\"\n    \"9DdFQDeZaDRxM93x5v0v4mb+EP0r+Y/\"\n    \"w79Mrf9qJj48tGeCT5Lya0X9yXTaJOQXT3mX7k3z3no53VT/m7iJP0h/y79DXLO/\"\n    \"3A99aDnvcwzn45cnAbj388n/41Z9mKN3Di4L+zWg0XXgZvN/EzfxJ2b/\"\n    \"tPzjKQDxMF9w418FwN9q/BnY+ANq/lEARHJEW3Y5D/gLuKn+N3ETfxDW/\"\n    \"lX9vZnBXxu5jdtXEYlRe3/YtB3lPwXN/kN+JLhDzMzHzv4ncfPk/yZu4g/\"\n    \"TPyXAmS7OO9knkfSQfUZRvaBSAB/yW/0D8d1RTmNw/kuNwKHv/W/iJ/\"\n    \"bNJedpIAjCp0aRYMUeEWWLxJp7+GTUI62KGQQ2Mbv6uqe6x1nX9Ng/lKt5ml7Fkr1m/\"\n    \"djesn1H0NuSQX6fNwHvnWbT5f8lAFTL/cQIUZeNpJf/Ui6f/mE++/vm72r30/\"\n    \"tjfvSv4aJKsSY8/UMOAMv+HQArVwLCGnGp/Ut5n73xX3f2/sgGpenZbEO8z3Rx89ifDrZ/\"\n    \"DJ8+YuJ8lzRcwj2y9i/lqum/et9q2RTfN5LprjYZ+fjYkKxYm+2/\"\n    \"kvEvmUcJZxzPWA8Cau1fygWX/xDvQ5BzAKD6CqAS6ys9/Cc2Ltr/HPs/Emoz6crIg07/Ut63/\"\n    \"8rGtCC50tj8xi3drm7+8A/F7q/\"\n    \"2z18BsudyyQcBxP4sgKpVrf1LedP+Gfth2yQoXrF9Rj+L1wN8dCDRn53+\"\n    \"PyRJkoHP1r2bXv5LuXr6b06H0p2nfuwuphvXY6nnZj/9Px/\"\n    \"wfsTpIU9sfy2Ff+j0L+U6+68HAAXpZXk1fvCrPxdSYvt/RiABVIn42zGwZ+7+UKGa/0XQ6V/\"\n    \"KZdPfslEVMj/TTVgOAVh+Yj79fR7fSw5eBXYXfTEFEbjp9C/lyulvy08VLiabQK/\"\n    \"D9BBafxT2fzG+i4Od86/E8qnM6Wr/Uv7T5X/bUv8AnE9BMp6p6W/zQxUSsD8QDhLrp3Gp/\"\n    \"Ut52/43eX+cn7/2e42u+K2fdZuP/yywf3y/NFiEzcSJQ6DTv5QL7H+TMKdRu3/\"\n    \"Zv0lXZHlgx1PX6e/4nDqBDfOV7E4cBJ3+pZwhVneT4tGv3I3/W86AxftqZXmJU1/\"\n    \"+5XbjxqZPgNmP+dMcuQHU/\"\n    \"qWc5NXzAR7XkbBJ7HtL3H7bbz35rUjIhqWc6Y+UYFmdiRTVNFazUvuX8k/\"\n    \"E+tQJQN1u9j2ULX7ZGCjikXwtM/yzPP1Npn9OgpFxf0pytm5D+FH7l3IKX/xt/\"\n    \"cCdvvhZZf49NxhdLObnmpd+it/9x/NTlRKvPPcO6UhSrPq107+Ui6b/fvh7bc/\"\n    \"5jwaCfcIHgFauAYrNa9B7wDr94/as9SIA3UcakOe1fyn/Qgyv9ELoyj+DX85/\"\n    \"JftxvZKBVOTbP3axf5C9Y/g8220dMbrTET7X/qWcJ3/ic0ls1E35kOMT5MHOV30HW9YcAybT/\"\n    \"w/E9KmKKdSET4Fsva/9SzlFRv6vyRu/P/Ohh4o4P1PfMqCd4A/+5S/2/\"\n    \"5I2Y981rbrE+j+IOv1LOUdG/hIbZJP7H9jsiMEpVrNJUvVj7P/hT4dASuzOdKsSeQ1lL/+lnJ/\"\n    \"+Toe7DeVpfYRxb2uzcXDvnLDk15n+HxBi6pH7wGJ9k08C8T9r7V/Kv0x/VXhewZy/\"\n    \"9SNy3af6DGCZGCF73ahjf6Ti2BGQfxiUDRSyPFXU/qWctj9Z7/06CzTxKYzxPuq+k8uT+62w/\"\n    \"RVKipsTfHlVLku+DfbTXyknGcevsfEA2Pv9WRxoXoe+yDZ4+tv7Fofy+EXA1amVJm8EtX8pJ/\"\n    \"Bd3+Fmqq7+43s6P6E9EhWb/dBXO7Kf/\"\n    \"kgL8rV4Tfk7eRVwpYjav5QzjPGnWjj4pXQ3cl7+bXl2s/\"\n    \"NFAGtQuyfTn+bfW9+\"\n    \"Z4h8OYetPi6z9SznFjc53sEtCNgTq44bUOUDPO2eHgrWwrfY3n586MY3SAqJ/pNO/\"\n    \"lLeI793lKNiQdL2D496ex1IBbAFlZZ3+4/5gzyeUEsVRvvTdv5R/\"\n    \"YGa9gimxbr79M3wD4A7JvX/I9N/+Yn+ZXaIAKmq8dbx6f/\"\n    \"oDdPqXch4ZXbBDqmxqaXkw1mcZ5yMB99Rj0x9ATbZIrNmqi/\"\n    \"GRBLX2LwX83+mvNWxp4XGbftJrw3J7bPovhY1zVyemPDe1fymXf/oL2jPH+uGh2Mb8SXDI/\"\n    \"iu5BkhmIRPAaqn9S7mKmfhISXzvb/7G3Yay87z1n+0fy5tMfpMzwYFMW/\"\n    \"uXcvn03z94SBDuaHyFRc1b9g+fU6hei+2BH1hq/1LeYKyOKo3vt7g/xP2q4sb1hv1XMviD/\"\n    \"a+M93MZqP1LOc3e+yuPWYjF/wwAvSuSIP39hP1j+7g+vYsjRcmo/Us5Q1y/kFs/\"\n    \"VtB2jgCIPB7DZxF3sv+nyWN40rvVss4j7XIE9PJfynlux3gg3Nj6zjuL1h1h7ll+\"\n    \"CIH9436IAnmQjP31hSCHQKd/KZfb/8FQo6TvHXedAEgEULXxVfJQ9s/4hwhv/\"\n    \"+WbgNK4mZeB2r+Ua+w/nldBKN3J9jwAoBCaHJsY3y0j018RYYDZHuezNV8DZ9X+pVx/\"\n    \"+YfPb0g1WLrwj98dzOxGgJ/A/vJ7fO8lHTn3YcDLG2vtX8rF9r972fpOwrGPNrd/\"\n    \"xMvQzyPVsX/Cadc7AUviAPK8qkvtX8pV9r/H/\"\n    \"O6e7wC68nOHouX7P1i+AGT6E3s+lo+slsfOPxx9Hei7fylXT/8IlwMN/Q/\"\n    \"cQJBg+egHYcy7vxIBYnxH2mxzEvydTv9SLrD/11T7HmnxHrN+vK/6WAMPdztPf7s/\"\n    \"aWL8Mfq0SAd3tX8pVxPTcykhjLwAuNr/XN4/\"\n    \"NPqnTkScsT+NDKS5B5j8yDInwPKJ0Lvav5RrkNvH/MEO/\"\n    \"2rXJ7yHYPniD2+zQFEsNr1DYvvb3hIul/WdwJlAghgfpfYv5RJie3YJ+9+dbB/\"\n    \"sfbseKcNHIdN4ZfoTV7M3fMRLW6RDMjqb2r+Ud/Dcnww2/\"\n    \"p2NFPLboNjlFuX+FpDpH9vvJVV9RAu5ngBPqf1LeW/6azkdtr4Lmox/13G+jM/izhsUhTPT/2/\"\n    \"E/KqTU5QOd7u29i/l3en/uqF49Gf8v079qG2evwKYR8of7R/Dg/U9wEv9xPh9qvrav5R/\"\n    \"nP7JdIr7cxl53UuBFGyimfrM2P8AH15rPK+GikJRLFeC2r+\"\n    \"UczxtnsRyuHjkK19cH7t7Kx6LskEetf+HFIvJZvkHAdlTav9S/nX6J7CL+e1+LG1t968U7xZi/\"\n    \"LC3/7cD85+tE5EGSUlk+PfyX8pJ5PSEbv8QLqVvBDA84uak96koSstq/L39vyEkwO0fWKa/\"\n    \"l0pOhZ3/qbV/KWd4cT5FHXNKhj2CG/QooyQnQAix/ycvhduD54DVNk9vycNe/\"\n    \"ks5yX7yWxJ3be8TeIDprwq+Wg5A+\"\n    \"9vqEudsuE5gr1ucGv399PeTXbM3siIGgnAVMREBCWBjvwAoDCwywOAcHLBJkAzoH6YaIeq9FRx\"\n    \"efzPqGe2B2Rrt3pVyjIe+IwUWx4LQ8Oo84LGFumEl+FcX7P/VZk/kAEhziZg/\"\n    \"7ZTav5QT++e+n5oTgFsaHEmjJ7K7Nv1hcCTEDViN74V44Pt0rnkr6Lt/\"\n    \"KYfE92tOedKPVeV5Tn5+DWBn0F+b/kmHE3iTl4KD8b98E+z0L+V8+lPWUyDV7/\"\n    \"4qGfp7B7lrf9s85CBAXT8FpLtCToJe/ks5nv4OpNcUj3yIM1VpAVZ3D6Y/\"\n    \"gE4wJZn+Flbl1ZuA6fQv5RAaPOanuFhkd1eFPO+CiDJvT1jKP9t/m/85AJyu+QaQ9grvO/\"\n    \"1LOcR236e/5SkNVvIpyd1q+xvC1Rn7hw9ZJu53s3wUdNyll/9S/\"\n    \"nb6k5wDwZsnSTIxMz9+zyZs9t9PginWvAc4gDuU2r+UZ2K58u+uV+\"\n    \"GK72dLh6veuBDxPHZSF0TsH8KHdMg8yVcBF6QXsvYv5Vmnf2Z/GLtTmBE5f7npSxQxfy7/\"\n    \"l8g3AIVSAnU4ta/9S/knxvypO09WREQBqLdP8rqTTJkTwfb/qHXA+oUgvmekc1v7l3LMPec/\"\n    \"RRCqNy6dAFA+W775S1wcmf4wPhLCvGh8pMUuz+M4Xq1X7V/\"\n    \"Kqf0f8zTFedMBgKT1sYHG9BPOffqzIFkUai5Crye15g7AhNT+pTyT/QPMTmGCJ1SIfD5/\"\n    \"C7CYnZL0pz+5XSmmIKT3XT+Ohzod6lTcdvqXcsRD48/SrJfpsdAiGb7/K8f3uQpMo8t/\"\n    \"4iPVEu+rPjwHQFRPZH4VaO1fygFXJr8Lgr2F7of6HGAzAfPH+eptf3vepgdsJc6Y/\"\n    \"y7xfgQBdVP7l/Is9rfhydj+yZ3lp+3pdv4LGz9h0Kj35R9ACB1/NwDk/hGwXwRq/1Kew/\"\n    \"63qQgoUqF+TgDAgtxDkoD9B9k7vSOF6f7+KbD/oWCnfynPYP+x+XoAyPKoT/\"\n    \"MEYf9jJWP6tHP594rzXVGmcZz/asDm77t/KUfs3l8UNeoKgTIz/H/JyDb9jU0/\"\n    \"fdpcAxQ0fR5iPfwm2Mt/KWfs3o/dJWkoUObqfTfJ/dEL29/\"\n    \"E+elDToHpsVLFTu1fyjn73M9mnJ5KbP3xuHTPCedm/+0mMF53k0PA4SS5DNT+pTyT/d8sl/\"\n    \"0EieedYJpMf2nSzAHAy/9jvo7C3MEnwkQSsKn9S/lr7P2oLZ+JT6u7jKpx2udRN1oOP7ho/9+/\"\n    \"DIrlNcBlOoG29i/l78jwhyrt73wHEK7+yTr9rS5KiskZkMt/ePwxkKE6G4szFwL3tX8pp8Dxi/\"\n    \"nl+hn6IsN+ebBZX7iL8TP9vzkuzH736ZQjCuV2D/hY+5fyN9N/\"\n    \"Xv5Rx97APcmD7Haetoqc6f9NCVHcIzNfJXV6bJjrLwc7/\"\n    \"Us5ROaXMGb2O9UtjRSpsr7vp7V6scj+ND8jxk+3s18BtCRu0zk6/\"\n    \"Us5hr73svUVueq7BGxPeKLI/jP0qW6dloNTwJZ3ifepnf6lnJHXfrg+Q98NlN2ibh67Pnj62/\"\n    \"YxvNNL5RK2fdr84XCnfyl/Pf3t6SlvGOzBv14AbH/hIwDpkDic18j4Nx7+tX8ph/\"\n    \"iv+DPw4fxlQf8Zv/sjBvZ5GUjn5HqM5z5zqP1LOcNv/DD6Grc3N4i8zx03/\"\n    \"0Cmv70fleQCAFSy210fzSOkpPYv5QDOew/9JWbyI8M/2j98nEKLu7hzMGf7YP5HmbV/\"\n    \"KSdo8u+znwJQqMqsv7N/LJ+SlvL7bwdEnu98TaHW/qUc258uXwNPVVEgQw4AJMJJUE+m/\"\n    \"34W0OYs2XjPGB7dBGr/Us6Y4b8F/Z1uc3uqkust5MT+H/cnsnzipyyfCc1OL/+lnPKb+5Oe/\"\n    \"QoAmcYxXc4A85ZPL9h/vwJI1c9u/wXhHTr9SznFE96R4qGPHJ9HoEsylCpmHrm3/T/\"\n    \"T1dC7ZNJrh6R48EsS2tf+pTzH9Ld9x8kKVT9apz6KySOVTH8n+0z/zwjKXfuntbrZ7/\"\n    \"9zStT+pfzT9Be+6++xPaXTsfbQY5GTxP8d9pftsZSKnRh/fR1QMCXeAwtX7V/\"\n    \"K33Az8u76DmBTRxhKLCXXHpn/Fiinv43vlbzyKhDgcxcnfa+itvYv5RR/\"\n    \"4JeToYTFNZ5XjeudkbieCoGSt2yxGfsvQmXcM7/rhJgi87tQ2dT+pRyhWR9xcWXJ6B/\"\n    \"ZMq5HsFPrp0At7A/\"\n    \"MN6RjEnEJOX0K91ZWa+\"\n    \"1fygm3TPzwKTrzPu12AXirQn2L4MauR6ve9p9hjyTf1EMmJZvfd9bfCeSPhTv9S/\"\n    \"nb6e86vYnjKWldgm3PxkULkf8z0z/DH8oOrcXNrPt8nKWQUDv9SzljnfjZKW+b1/\"\n    \"NAMtMfawKP9gNg7L+TM2FOATbISwfAlJn/tX8pfzP9ATUbcUuVjmTuS+N/\"\n    \"PWWmw493+693gZjfMu0DYn13tX8pJywD/zJj7pg+/l/fG17ffPm/M/dX+eYk2SEv/IKg9i/\"\n    \"liDg/3QFzALjmluDl24Gn/32+TVFOpLlj/rz+9/JfyhGx/KHttxNA8edbAux/xfsS5Wp7b+5/\"\n    \"Fezlv5Rjzn2PjO+tWK4rLynL9P9CecC35T1gAkgtIfTyX8ohh5f+t+nsd+\"\n    \"YNodzfDH5W2v8LQvrI/K5a6fJOMHzu9C/l3zga/FYkhSUXAGzC7bV0HnH6y/\"\n    \"UQKYP1wvdAhFIyFwI1amv/Uv6n/eN4a9LVwumP5t2NkfeAV/\"\n    \"oppr9dbxGoWIo7fFua8TsahTe1fyn/yf5xehqlw5OfyjL/J3Pfp4Knv/EZoJInG3E8c7w/\"\n    \"QqDcudT+pTyr/cff7tJQ0jrAlFlv3AFO/\"\n    \"y9fPiMFOlckelarM4T1BGCTmJOAUvuXcvZXf9+57n7ki/\"\n    \"sRFKp3CaWFi3hn+9vcWH98ByB58hgb3gXi1elfyhG74+VrF4dyDRvbfWTx/k3prS7/\"\n    \"AzrL+JyNNqkPrJ9u/R5Y+5dyQrw/GuuzOtdwYY3b1TjteKFDYKb/BIe/G5f1twFKyiM8/\"\n    \"tP33f8HO2dw3DYQBMEkHIMDcQAoffl2Fb8qfJQAo3AoDsZBMAPPznhrdL4qECfyOb17s3ug7d9\"\n    \"gD6DkEBYpbzvb7K7jZAcoVrt92Fe3X5AEV4bpX0vJYNrufQpoeYD/E4FM/\"\n    \"xAWuYC7bwAWJMOu7tZTvzc+7wt+679r7Re/\"\n    \"+sMy3Pg04B5RWrL4k4J59RfCCsOpv6S0o7ZYvqa+C1Wolfzk7N9/\"\n    \"Qi4oENpf2OmgRPuKqgqgKvXlw28IY/8Q1pD3O0qITV6NKpjN785cNP35Uz8a/7L/jE//\"\n    \"vfHwr6aQqjsi0z+ERXqeM2xypaQZP5d45wrDV1Ri8wOnANp/\"\n    \"tr47JEU7RgkYp38VSOwfwisYjO92fKM/Of8I/eDvpUr/5bL/CfxUMDieS+kLsX8IT/\"\n    \"PP4HffAUbflwjuz8C5/wOl7gPYy/7n/K+itMj1TK1M/xBeYn898E+P+FyiL3t7gA7+qCjwv9/\"\n    \"8n3E+rU51GX5FQMsXmbF/CE9Mfx0AuN6ZXpCZo+sy/\"\n    \"fs7nwH86u+GsKg5dQjw24DZ8u60jf1DWJv+bXv6/lng/ArN/\"\n    \"irQnv63Tq8TpwEkl78FdCnUZvqH8BX787kfbNDnkfX74P/\"\n    \"jZ+2H6Q9lqebBTcAPAKoKQPUeVJfpH8Ki/\"\n    \"fXcf+cCrCfZ5ityfwfvBpz+ZXnavpDpIcyW2fzzXtq+z/\"\n    \"QP4RnKsz76q0eeYJM6K5Dwu0O7sn8/\"\n    \"8gOUSvRqtJhixt8HUrjpHSPTP4RlOO498239jctdyeB0J4unf4UO/qXInv6/KNSi/Y8o3B6/\"\n    \"EKSo2P+1zfQPYY02OLG32/LqBqnizXRT8Ms/\"\n    \"tT+wtnr2ByUqfQ5QWx2QMI5PAtLxdwehsX8IS9TY54JsrPP0734e/402ui73Y7O9o9Gnmv7t/\"\n    \"O4RnRKfBCQzdry3fhjI4T+ENfvL93jqR0Hd0NSCa+18pYVsCv+ZKuOOe9Syv1w/\"\n    \"80ui0I7epx7/hPB0FPjzLYRwGo/4S7tbcDfdANxtdr838yUJ7W/DT6vU3u+UnP5Z4Uz/\"\n    \"EFbt39N+U4faMYzwDl5TrwS1EX3JxfY/pk//qGokKooDOP1j/xCWkJtr9HvK2/\"\n    \"aoWKUKbaCVkrEZzwXUWof2/zXU4T3A/NXAMTn8h7AAfa5gVnR2AH9W2P3El6Ue/5BqDu0/W//\"\n    \"ztjaOo5tApn8I69NfOVi+WyCdzvYt4591ZTNM/xXk+dn6CCgy0z+E5/\"\n    \"EbfZvdrlYZbwpevNKwdR3+iOx/vZUgmIfed4PFUNcbpEqmfwjPTP8xSkpnP3frRexxiaqv9PS/\"\n    \"tverUI8fA+x9KkWNjwHqMv1D+Lr9OyWmrrgx7qe/4Q1/+Kd0p/\"\n    \"3lfakSS3F4CrCO4e8G1WX6h7DMP9/fbfDq7XhztJ+vl+0/LnJ/\"\n    \"2d++dxL13k3I+t0pKX4I6Mz0D2GFnvu6AVA90V0eGn/+w/D9vusHgGH/\"\n    \"8niFanu+e+LOmDa907cBZQ7/\"\n    \"Iazav6wPLe+XTkwuP8F7GR9ZsnP69zEf4pi30Mf4XQDw4wC2sX8Ia/avX/\"\n    \"PZChasV7BfPnAP0B2A9qfDAcSjH73ClyjQA+e7kfTVTP8QlpDpa/K/EI5+SBVPf2DX+4G/\"\n    \"u8n7j98JDlnTP6/+QlhgK/q5n91rkPM9/\"\n    \"WV+ok7pvsMCPTgFuClh5vAfwhI69Uu5XnIU2PXWz9Ofh/9aHYSd3M+u/e6mFsOYX+PKm/\"\n    \"8Q1tDslxizcykX2QFnv+w/IlOrQnx5jOH7wUfk8B/CEj3twY66c2Pfm52pzhXK8IUPpt8AbLa/\"\n    \"XG63j5cUoNuTXwvk8B/CF5ln9ruNXcIl1XJxsyOQwydmnv4yvNc0+sv7lNsgrZn+IbzE/\"\n    \"nfOf1Sbu5aNTBlnvYqb8XjgKrX9Z4bh773MD9XyHQConcj0D2GRcVTf1e/\"\n    \"o3m1xiaq3QE2JkmvEh/8z9OhX1w0T4QJQY/8QnsKmHx7hDS+PRlfZtRHsFf6QLNj/\"\n    \"5o4i9xd+FhhOAYrYP4Sv4bM+83PXGyfg5VLfGEo6/mvEXvY/y805nwiqdTS1if1DWIbW5YN/\"\n    \"LRnaVcGUcHFXtVeVChfFh6qm/xtiDb8ZtPPVoqvVUVxj/\"\n    \"xAWoe09p5k+DzgqXcSm3PpiGb4sj8LQPyD7w/rFGwO5gBw/vRpwUKCxfwhLlH3b4GrZ2/\"\n    \"uFe9C9FlXG94f0vlT3A05/BoDWWuU2/ZAwFcnI4T+EVcrnbeHWyeztde+Y3bOU6Ws5JFU0/\"\n    \"dv7HSXnfN9i11tZC9TYP4QVdrMpJtf7+F+o2vBVpvhUPjT9afc3pGe/ZP1Z4EbhUogc/\"\n    \"kNYxIf4kmKc+0pfca8FcyM9+6fYsGD/cr687o4K3D7CZwD2Kh2xfwhr2PCQ0doSG994I+fv4j/\"\n    \"n1xUkgtOfJgdQJAOwcLkccnMz3wBi/xAW8CO9VSK8mZG/\"\n    \"R++zUfhDTX9A1zPRsFanOH0PuA0tliTP/\"\n    \"iEsQtPfB68jT2PfV3VXhaBw+\"\n    \"sv5DhXbfu7VHH8VSNWqyPQPYf3wb3V7zAdX97R81621AqDV9McScj+LV6V9X7LwIMCV6R/\"\n    \"CEntTR4BTJ357vlV4/Ev4GRI9p79uABXuGd2yKFoQC98E5Nk/hCXO+t2uV2ojUfSxn0vjv/\"\n    \"3PV3/G7ldx6/eCLEX1lGPv59k/hGVq7Nda8L1Nr2KjI3r4VzdOf3H1skpU1CIZ5x8GMv1DWGU/\"\n    \"OfOl9rm2rL6o6b/Xom5q2v7X77fvw/SXaOMC6VKWt3Qf+4fwEs5ZX02tDqJu/ISTH7D2DaCnv/\"\n    \"xvri7TCwA7/3NBw5y5xf4hrHH2eb/drZ1q+d+flbD3Aqywf81+mH+Y/2Ley/1q/\"\n    \"IPCrYCbTP8QXmL/\"\n    \"O3WmPd+2V8j9RamX1GBHKftr9CvRzwxfAairRgu9vN9R1MXYP4Svc8b3Ff8aJoOiAKxi+\"\n    \"ESL9v9O89v9M1d3CBZH4V8WrkudsX8Ir7T/h4uWxK5vYzsbt6an/\"\n    \"+8r7wBX3wGOzwHaMSTatBB1kNg/hEXsciiyWl8jw2BHUuYbQO+NNpvsX8Z/w3rk/uvQI9Up/\"\n    \"C4AqQBVM/1DWKbNbqerqJZ/JQhLMZrd16QjffjHuiHe4P8zBwBVpWpHPwgwMv3/snPGSE/\"\n    \"DQBQmnCFuoUvFYWi4QwoaGi7ABThAKi7AfVJkMsNkqCnoeXqP5SEEThxig5L9drW70v8zzGjyv\"\n    \"LIdSJLrkOQZA+iXMNX9PiKNqHLWpJU/\"\n    \"lQ8XUv+GfhaLn6iqvx6kMuWfJJOw0oUrqdnOIFx46vzHe//COxoI6SOhuIxo/MVl8tB/\"\n    \"yj9JpuCn/Mav+VkYrXlyGT78U/jKmFv6nFyKBN/cEdBT/kkyAUnaL/UAM13Bx/vJWP4bt39i/\"\n    \"dNidin+1rCyllL+STIF9XO6ZO6FmDe8uUb+7vN6CCDZe5VD4bz426MALA//STKN0HnlGq/\"\n    \"elPD2el79cvh3s4eDmFYrGBf3fw14dv8kufbw/\"\n    \"0rDnb8MLjG+QhjT+PgP3f1hSs2kuIoomSY+Esx7/\"\n    \"ySZBJRNmX9hIc234EcIMKMpXLVLmpJWLX+4rV1wIE6jws/unyRXQsF/oUa/\"\n    \"+CBg1b+RihmYXaryzx1UeEHv/\"\n    \"Sl3JdDq39p3ZvL6OHnvnySToNh9t18btWvVcwg3d4a646ss+\"\n    \"N6fqqdHYFK0uWA56algdv8kmYZUD70yCZS6FrTn+chej1UOLjW/9krdH87hpt9eARgs/\"\n    \"ZiwjpHyT5JbdX8d8ZkkfeVXcfS31hktcxnxtHR9TzlDQven7j0Q7Eoyn/\"\n    \"aVwlSrSPknyS3wDT4KmUoNSVtSprtGJXETLWDQaay4Hod/o6mF7+BUH/wtfk1T/\"\n    \"knyt0Tz1jv+kD11z5I1LXQOQuXElwVfBjT1H1T3d8/38HJ9BIgZPEyuHJOUf5JcTzzf/\"\n    \"8J+Le0rUdeREK1t4cpZopfHtJK/xW58/9/eB8SIVeueNcuUf5JcR/\"\n    \"UGX0ldP0QvA4oqZCbmLuEc7v7Com+Shc+sKiZeQPz1LeEdy//\"\n    \"w6Xj6utsNhd129fX46bBOvDXbn7bmc27NVPzyPtq+dc9gGt1b8l5QZuk55T+Cxd9+\"\n    \"JcChEr1qz2B3J/\"\n    \"9Px+1gzPb08NeAw3GVW3MD4ozP2J73LWXhlu4FF3V0afm3sjdWviq5jAGRCF8J7vLw/\"\n    \"3k1jLE9Pu7H/MzW7B54a67o/jKf+aOlu4dX+P5eBXHRYvm/34zy0hVMzhGFYnXnL9fS/cjfH/\"\n    \"ARdg952I2tySvALSgqt5Ug18SyjqnFP4KvC5a/tI+wv+gSYKlb9qrsYqNwP4f/\"\n    \"w3G4lNODfconbM3q0zo5C4Xq7+ha3M6MV9Me/s+fAmBMxscAGQoNfx/oTg7/\"\n    \"h9Uwhd0Dfcpza26On9BT/o3o/cOrkfz3G7iUz8Qwgrv9L3MPmcR/J/\"\n    \"f++oTnpzy3ZhlC8AgactfTeP0n+VPtGC8R4RN5+ftXhHLZXcj/\"\n    \"NFzD6hFuAY7DNeweYWv+AkldHukaXsNhQlOXkD81j8EI32vi1XHtu7TwmapvA3Uu/8/\"\n    \"DH3n0ZwC5NfNgAbdP+c4jycsxvOTS3Z9nf5hxbaZcC+D1taBn+R+2wwgPfcw9rHJr5sFH/\"\n    \"WlI8M0CTBNauLq/eU+Ligk+ovUaSz7m8q67/\"\n    \"3Eg2eVaPufWzEXzmH4KryN4An9dKg+4XvzR9m3zR1aa1Pt9J8DQefev+1ve5v5ma/\"\n    \"IAMANXCT9ae7vq7KDuX9jvEZ7rvr9q+55N46UCI6xb+\"\n    \"X8absFxfYcccmtm5NUteC2DY2hBgQuWv7QPJ1E8t+5RwaddAJy6lf9xuA1f7+\"\n    \"8AcKut2d7f1tyCs7K+gNdh4TCiGcbTJ9D9nlqHIbLmDQHmdFYIZH+p8O+i++N0K/\"\n    \"IGQJhTbs2snHmV19A+3lNtc2ABL91/XxTO0Or7PZdV+HcQMc7Sffc/\"\n    \"7IYhP+TG8GXI7cgHAC3TxG/hs3KnZxCaw+zo/lAybI9QIuGS0ETBh4BpF4FOH/\"\n    \"0V9eeH3Bg+9Lsln9fJqPzdzDEYGv3L4Bi11sMUVKgqh//98+/s99A/\"\n    \"jKcBidu8j0OCTD+O6Ri9dv/qyVY+5aovjLk1c1PrGkRmkMSJ1iz6iPXEc7q7f0h/A/\"\n    \"Gz1CEAqb3R1xHAyctjvOxR/lB/6j+35p9hWfuVXRmqEWOdUzd7a92Cr9ed4vD/PEbR/\"\n    \"L4U71HZ4AYLyhqaMt9R9+dHPD/kuTX/imjpsuqcT3xJoJ3jlaMX1P3xlJ96f6/\"\n    \"uX2YbRPiGlaw98aturgj3IP/DQPJDnlvzjwjtM1dOcx9XodK00/\"\n    \"YHkj81DqVL+tK8Q6Q4HljrLhmixOhe/ofB5EOuP933p/\"\n    \"5nxMd75EruoflLW74xlj+aPnWPsIf5MLDR1cBXAJlPAawYXApUPcu/frCdz/9za5bHt/\"\n    \"6heIpepdFsOr73l/gVqXsM1pS+PI4BzDaiGsl1LPcpfzS4Gen7/X9uzUKE8tu397dEL/\"\n    \"7KfT5CXAZQstYkgpZ8BqhbftR2rXYo/+3QkN//EauhIvU/Gz9e7vm8r+LW8i/ql/\"\n    \"Z5GdirZvuH8SyAGWuF6hpQFaF8TaPqTP7HYV52626Zd2se5H9Huozm5f01/f/\"\n    \"Defmr9SMicETCqnSvElj7ilqo9V7zXLEn+R+Hufm67pTPw9ys1omIfs8wlQ8yVBoKwqW7/\"\n    \"4auh35lisFU0AUgrgG+AjTtv7JIJfbU/Q/\"\n    \"D3HT7jDu3ZkGuvdW33F1Uy233lwP2e2WJXXBFp4DQPYP8t/oXpeyr++Ox3+x0eo+7xNbk4//\"\n    \"A0mee3v4/YMQsXKC0/Eu3tyOoUr2B+5KAFDTPAGLJXb/LF3/b4fek/\"\n    \"tdfB2Nya+ZhktgteheISAgoWXMI1Ir81h9MriFcesVNHjBHqvSvpACQepH/\"\n    \"cViCLu9xl9qa7ToBU6XvBaofWPFc4LQYg5bj3l8mj1BSzCVzs4mpU6V8mVJH3f8wLEOH3/\"\n    \"5bamvy9n+C/FvRY0TFwAWZEqbK1b0/cBEzY60b9/3IQBOnvl787YaF6O+Mm1uzLNPP/\"\n    \"URJhMxZ+BcwwiD/qulj0IimxrJ39BqHLxNWfz+H/9MwyiOfcY/DUnT9xYjZ5f/\"\n    \"ByZNK4iroMlYf/UNOZOz+z+AU/7OQu68FY1cAFXSVCq118uT/0zDOA59xR4/\"\n    \"+j701M3Gu3UvYIpq+dO+ZBiM1H7BijCf/uAIoFHvPoSXBomXTzDeuOegYPcj/\"\n    \"7Pn2cc+4uTVL83oECj9u6618GlVdZQzz+qMNP1H3h6P/\"\n    \"w2E6BZTBiOlFbFzIHBB7kP9xWJKunv4vuzV5/Afu9MQzST50zeWqpcOYNQ29ozZawYD8Q/\"\n    \"gcbP7q+1jkZQC5xPNsFDxTgnfR/Q/DsnT09H/prcnjP+Vv0XviBl8hxZuPMVfy+usXMKj/\"\n    \"Axy4+2OgsOwxeE1gOYXmJUAf8t8Oi7PuhNOwMHn8X1P2NfFVnlE+tiuRMHwe+Pha3R+\"\n    \"aj94f3Z8XBBhqzjFk59g4Ini1A/l/Gpammx63/NZ0/M+ibsVvbvMvwr/\"\n    \"mU8BHdn1EDNaw6P5QO+/\"\n    \"yIe4ieF8EcA1gZIUItHwh0fz7uPffDRfxiD0ut+YfQOkXA0xlfh5qvOr5L6T9UsNigu6PqnT/\"\n    \"guTPO37d7tOYEdz9fwyXLdJ8X93/OMzM6vjpcFiTw6fjagCdPP2bu/\"\n    \"m3m9PN1syIn+5dClRt4TOrlPRpXNZcVwHIH7oGlHKkCCFyRXr9o1ga5/+X/2GYldOh/\"\n    \"RtPu6GLHneYu/kff7s5j/5P/\"\n    \"yapX1pXUgyhv4gfISDSYfphkT80zzYPQv6RS+CisdpdOTdsOpH/aZiP1ec/\"\n    \"tdVVDz2uOhctuDmn9UPzjb1rR3IihoLlO6BkAyjySTkFpyAj4EhEXGBjLuOiiqIIiTan3b2PLp\"\n    \"UYe9fIK83wWtL7aTZRuec9acZeMP/pMLEpKZzuRX5oDkpOQiP7s86HBNHVCRkifxQHBvM/\"\n    \"Oy3Z283+N0v+K79f5Sw3ffq/YfLPH/c6g2eR3yneYnmMiujUAE1APrI/qE/yswaAUA/\"\n    \"e0zzBSrPNXgB6s/S/WfK/WMJO//m/YV30+T+v7zvR/z66qE6KR9Co8j4dFf/O/oAO/\"\n    \"OzDoSTRnecx7EiH2iL9b5T8N/Nk70lLk4vzorjMeluR3s1+V/\"\n    \"vRQhAwaIH+wXt0WUF+JnopEV4ONC3XAdA19TkpaxP0v0mG2/hPehNemlycF8YZxovPNoPvtEI/\"\n    \"cl9dyh1CYdA/\"\n    \"Mr0Ma90Caojx1Y2hqgzCkL2R7H+TDLePje2Ndv75XO8izhT6YZvWMgTNoMtb6CwQi2IWkCz+\"\n    \"3TFcBVScVyMi6TtcPwJUkJa8yel/\"\n    \"iwy3k9r2eyFycV4cDe+9oXfSr6G8HpacqgCIG8CCxgDpLwT7Ceu6AIDjgKjtO4ECkLTY4jYwM/\"\n    \"2Z4fID/nIv/G3oy07j0CZ+1ffBZfGXsOZAo7Ug85+UDEAVwGL+k/7O/IRMl/5O/\"\n    \"7Ip2DwTtb8lB/oGsv8NMtxefq9aS5OLMwDNu7wUrvWlzfwYiySazPAWcD8uPU0w5OzvMz/\"\n    \"BYQlx3Sp4Ty3bTwrZLebO/shw+QE/\"\n    \"szS5OCNQs1+pH726BUQkvJgVz0Fy8f7UDM5yqPh38peybGHuS7J7CyDXUyoC5j76U4bLyn/\"\n    \"1TDTZPwJ11R/1vmDOGyI1Oi91mHxHX+S5/o/if+XQ3x4p3UJsRzfbISFkaSgwM/\"\n    \"0PpZRkv2HwTDQXZwzIeo4ALdf4HAbdyO2UwrIsGAixQcNDlJ6z/7PwpvbYGJTlg0CFpqb/MT/\"\n    \"g55YmF2cQnMFpGI6QzaY6PV9DU0KTugD904IItLN/\"\n    \"A98SLqV+ob0BBPEVnpb+vTPc5n7Cex39t0X5vP/J0F6/zv2GWO+dPHp9iWbJd3KfJqN2MBX0v/\"\n    \"t61V3AaKkPwTb30V/n0609vc6GpcnFGQW/\"\n    \"sqcGWAiq5u0v6Ip50u0kbMH2W3++AVyBNyE81P2McN7s3zvDbeIr/E+u/XNxhiG28/\"\n    \"ULPZHBzXtqVfW0oBEi053uAYbkkf0Kg/69YPK3rwdMS/\"\n    \"+H0hV72ttiW5SLMwx+ukfWszEQ5AfLg+SivhyE2aR89EfDEf1x0P/rHQTG1TDjBZtTZ//\"\n    \"OGW5Xv1BfSi7OOAT3FxcAkeeV0ulVTJeOyBfZHIJn1Z39wfyu8LOAuY/\"\n    \"+vmd1e2ZpcnHGoSJ9wG/sUFKZ+Wy07QTx0cPAEOLB3x06mix5FRC4viCYuvg/\"\n    \"ZHW7BmyLcnHG4V7woz15VbMgZC/SZLlGA4ai+L8zySmwEaBeuxk8+9Ywb/Y/\"\n    \"lqxu11BKLs5A+CgfAw1qafkvTQVAifZWLf0dIf2Z+cV6SWoOM5wtbBvqGKuY++ivb4G7q/\"\n    \"dZsTS5OAMhKldEl8HRbOrF9nDlhNniPjSzP575QZyoDxlFgIZ6EL6yz3F+I9n/kPltBb3P/\"\n    \"fOFn2eCm/\"\n    \"4FApK9rvNhuQfdZVPrkkuI7E+\"\n    \"yk90aLANoqKsJCjvqTkG1EfqXftjb0VYpuTgj4dSv4zo1ekbtyF/\"\n    \"L+u2lor8qfyt1NTlmfbSK6J6n2M7ev2eBu7P8diyd8Z//\"\n    \"bP+zIcZLslMwApjohgMXcN9mf8rI+uy1UjMctMn4lrJ/twJ3f/\"\n    \"kNS5OLMxIu+sl8b++vREv9yP5VPY/eEtu1fUTYm81AzGt6evp/Lr2wt+TfeWnyP/ZeX/\"\n    \"yL+k73/XBP+ld53PSNzqAn6MriLMMhNCTs3E1K/44F7t5OtvstTSb/\"\n    \"6yDGB+2lIfoC9L8zwZ3qbbkmoIZiN9rjP8Xmz/7fSi/s7dj/dCqSizMU902x35/\"\n    \"83vub8FLsagQN2pyiE1GFrJ39Y8xJ/0Mm/\"\n    \"zU8lKyMxqLJ9lEGEB3pH3yveK9uDVByMEZHpgznfFu0Z83+Wd6uIpP/aDjxi/Wdie/\"\n    \"i3yR3NhetjZhSNK636V1+ewuYde/faX+7xwdbWJpcnLEQ5dlEffr96W/qq8eh3kpzyc/\"\n    \"LHA3WRwhafda9/7dM/mv4mQd/o7EYtyL/\"\n    \"e9GfzcqDcMx+CNcFq18JmPfo75Dl7RoesvYfDTLesP2B499oHyLo3+\"\n    \"Z4h87Mvv57WUAfgDHxyX/phf19mbX0RP5Pr2twJtd/\"\n    \"uGLG3Lcp+nsb35wC2G0nKd3rpwUhJn7wd8zydg3HrP2HY43fNmS3V0hJ+DLYj857OlA++\"\n    \"jObraPVe4Bqypf6gpBxHjgr/X9mebuCvk/9d/\"\n    \"TL5y+JtuqXG5TmoBTseI5CZJft4ezfbPtlBzxry1d4wppNJt05n/s/\"\n    \"5NH2Gr6V3BiNRrAdVP1Vp3MOw67JbYm/twsHmiFaeu5fYSX7K2azvhk4bO0/n/\"\n    \"PB38csb9dwyNp/OEj+X8t9y/DQYnQdM+N9kbpcOg6C/\"\n    \"sFpd8JaVk14+W36D4stZk72fPQvPfFqVyglF2c0gsxStNjC8iaAkya9nKC+HLGeHkBLlzwW/\"\n    \"0a1zTfH62mT2/OOe1INmC/7H0s37OzLfj2XJr/sdz39hV/Nvr46yjspE5sTYaP7hlELzGmA/\"\n    \"kaT/W2a8hLtvaI21NSnzP7dTv7292Sr48lfvu9/LRand2XqkM73IrczuhQNR8OGMuvVSH/\"\n    \"TuLEawtt3sjci4ElZGPPRv9vx1v52t1iaXJzRqE7tm+\"\n    \"09DbMenqBALaIiqO4ammTx38KsvjRb536btiFmLP4PpQ92uLt9KCUXZzQiz8dm3xYJbDBSxyqS\"\n    \"04b2ZXKd/a/Aa402bMtlwozFf+mCPW798UwkF2c4mkxPBR2dw7DfRBttz/S/\"\n    \"Hs71FvLDmbD4L52wwwfbpeTiDAczNId3AGeI7knD2b868bfiyX8XtKcANmbM/\"\n    \"sfSBzs83MrfQJsBPtx3xU6H7lNzf/0MILxq798JprrdebN/r9PtHR5uYWlycYaDdPXRvtN/U/\"\n    \"nTsnMGeM+fKkxn/97p3/6cJ//dTrf3d7j1s5RcnOGos/biCt/\"\n    \"+ZfgZX1CfBkzJfvQ359sKYMLs3+10e39fael2Z8yTv39AneAlzObnAmT/\"\n    \"gEbS01V7Mv3fsXOcv65K+sKM9D+\"\n    \"ULtjja21Ymlyc4WCajpd7JeRxMHyW775xECR7IMjv7N8w22EJKo7nYdLs/\"\n    \"7n0wC7Ptj+WkoszHB9ajlfkf//EtI8mvqsEkIUBaO8vgktA1am+dcPxxauYOPuXPtjhK7/\"\n    \"dliYP/v+N/s1RvXx6lme5H3X+e5iArAWWs3/F4Ibk9mk3N4YW/\"\n    \"oOJ6X8sXbDLs+1ScnHG40Tvt0tDcp/bg8mXsj66bwKMkffyoWPv7zxuo/JrwrezUhAbyf5J/\"\n    \"1Uck/4z4MPyq073AGK0HDsPU53qz22BrrO/0fIbWn091Zv7jqtPS/9ez7Z3+GgL9M/\"\n    \"FGQ9wk9mf+GWyv33Sjp+0V19os5HxCsLExPmTf5HemqKaXCO/zaT/xvA96T8Dgvcay1tpl/\"\n    \"aUNTzrkj9Ml/8RZDiO/lbI3ez/\"\n    \"3do9g8PoFIpOSP9uz7Z39zOfp7d+cnHGo2b029Z+70RvktOwL0N5X773BIvo/66ithl/\"\n    \"Gb5s9ehw1uzfif751s868q2ff4LSfs382lE2h6xLfplsgonvaT4DeMz+\"\n    \"78BSkR8KkAVRpXJAWn78TcwH5GlEZD76/8hPeNJ/aojtkh5O+GwGI5xDl09opw8t0yUBgqS/\"\n    \"uWx2uyEk/WgLDPlGoXiV82nInjH7H/IT/\"\n    \"pu9s8l9GgaiuLgDPkD2bBFC6pJdT8GOO8HZEBJCLFmxZ3iP4SfLpC3FJcb42Z6vpEhYeZmxk/\"\n    \"a/\"\n    \"g253xvXS3x8B7ls8kyH7XDVqfLsmvTyrkD5iKSdPDfqbzFKYNHUZofDdaT5c3wbGffC33vndxd\"\n    \"fSC/P9AYS/CNPV5BeNZUU/J8HFXymyutO/+W67or2f97ERGPSH7+Z1y/\"\n    \"6kuyOClDx1K07WvyR7UPq/WVf4Hp4s+o8A9vZEYRgPFIhuw4fyIz8PNUnfnkxv/\"\n    \"ZH04XVKIP5LJu11Di2jFAsKD0n/d+sK/zUW/QeBqE49D8MrOA6vFZNNmH1CB2jfi//\"\n    \"kPOQ39y1pGUkQsY2Q5JQRt/4W/fenZtF/\"\n    \"BIir5P4aRAiwKLAGSXydZzNdsj9ExrKQA6EJ52foFqloo9F/XeGL/mOD/XsLCF1jP46UIAqc/\"\n    \"VvAeilae6zlPx9Z9P/3sOg/BFqCYwKiNkj+oD2dk5396eT7xofvnGLwAYHbBG3R/1/\"\n    \"Bov8QCHJu5y2EejR5EWuxe6fAYPvwevYniVviqBl1+c9H61Pd56b/ZA/+yqL/\"\n    \"CDiL8m7nGFvw1+SP8PUEb4ED8KE/jK5dBMTnTDzO8BBsq81M/8le+yl9sOj/Zzgr+Wd7/\"\n    \"V2GylBbBbA7AADHrmR/iN2GLt0EiEB7i9mz/6L//zM5fw9B9OD5lxjZKAXY14PXd5D/xq0/\"\n    \"zGvggy/SVZs6+0/2pbbSDRN+H+rvIWiu9O98v22+F5zDVTQgcSeg/2Uit4Frt4sXIVgP/Adr/\"\n    \"0X//2dy/h6C3uL/F5f9tPMX3QK60b8jSPzcFqZf+0/\"\n    \"2ixalrMkZAOdN+LkFEP3sJuLLfTT9IfHNUP73h6xDzF38T3aFv1v0HwGb+B9C9f4Xk/\"\n    \"+1hwQ4Ivu/iBbStnuV/8Hsa//Jfs3yXVmTMwDOG+nfOu4B5n9U/wqGSMjH/pvF/4t9z5i9+J/\"\n    \"rp+wX/YfAJpj46hJuWflbGvYlkA+g/wu3NGVZe6SkFhiw+O92jc/\"\n    \"3Zz7elXVvHACbYXAD2LDgP8fvKf5f3JvpiaYihDfi2r/\"\n    \"bNT7f37F6WcqanONRkT9AUpdWw0VHR2zX6U9Ch7p3LwPyn7H4T+g/11u/\"\n    \"T0o3rNf+7ked+ysvRgjqfav03TJgCx2qpj8Fe1vWXwInXyoIBqV/t2t8vmfbX0s/rPd+7kZd/\"\n    \"KvJqGImvMJwXBYDbCjobxI32V9iP8fjETAIesxP/\"\n    \"7kebn0qZU3O8diuIGiMpNQn6cu1wWFEtfaH+ukRg9TK99wrbORHAIekR6R/\"\n    \"v2t8ut3tmJo1Ocdj277EBj/I3A/Me0VR5w1TLmSXhQn9mw39lBA/hvkv4Eu3jwAk5YxZ/\"\n    \"Pe7xqfb3f5Qypqc47FdR27/B5LoijucMcebyt8h09+ENuCsjZr4CqUj7YMK+kD+CyPv/\"\n    \"Pe7xqfb3f5cypqc47EJX7aLINHLCqMK13W/ZfpkfxK8NIkcQr+wBD6foM9TUx+c/\"\n    \"h2v8dm+1fqxlDU5x2Oj+r9aA2CmHZrdfx1hJZD25p1/8jUkz7RPRKhsUr21AphDP/\"\n    \"hbf8b20tSsyTke262A7Rg2UzfExxb9RWFpCbU60qKN80FrtgjGo//70hFzbW/\"\n    \"F1KzJOR7bbWDFbwUyak3Vj6G1Pzkb1mbEmsBNyHogP/8f0H+m135jatbkHI/\"\n    \"fSP3kfJABNgiT9xYU/zC9SeIi8XU094+K+SO+879+0uovTc10L0U+Hi39315m/\"\n    \"w1gGzBgQ5azP8AxjW+nfhuiepid/lOtb9+VNTnH4zL1b78NsPi3TCdOz7V/\"\n    \"Q3m8m8D5MB9TajT6r6+17uJJKWtyDkfwvgfOHmcL5347AvRvuHwf6lVEquGy/\"\n    \"9fSEXM93P5Uypqcw/F2i9aD/FiZ9Nn+g/7dwb7hiMX/h9ITUxW4nadmtq9EPRxk/7f98j9f/\"\n    \"at+LQD69wc1wHjZ/3PpiLkK3I+lrMk5HG8F7gD3FAPiuobZnyavCTyc/\"\n    \"s81hlv797zGJytw35eyJudwiPvR1QwZdm7HOSUW3wMg+z8U49G/8zU+VfVfumJV/3dhI/\"\n    \"1Lwfs7ye9OnOz/Sj1glfbM9O97jc9V4JayJudw/Ez+AeV8e9u9qL4OmFZk/\"\n    \"2Q5WlJC3Ur+RPTv+uRvrpdb+j75W9X/XQjWf1E3shC4+8GfYTN9F/\"\n    \"+vzGtrdzxc2cQDhHDTHJ3+\"\n    \"nR9vzVT9fyhlTc5RgP5gs6IAiGHccEs4Y4j8HtAfdkvjAaoBRqoAro1/\"\n    \"Ift33vqfKcN1n5r13v9d9D8F32tsGhXzNQAuOO/XA2R/VOoGHMViOPSvZP/eW/\"\n    \"8TZbju26LrBz/voT+cBxXBXQ+A370NkP3hK24DjrESqJcMREOMTf/e1/hMv2pVemNt/t1J/\"\n    \"y8if42N7cCa51XsOqB/Q34FoLmEAcdxJIlKOT4u/Xtf4zN97e9dWZNzNMR9APl/\"\n    \"SOiOEPAvlQAU/0nkULA+qW8Xxx3iJzgfX3pY+vfe354ow30qZU3OwXi7D9M/\"\n    \"FwOSgqK27O3vBpL9vzM9IFpTy8t4RQTH2p9JxyYIGzko/Xvvb0+U4T6XsianG/\"\n    \"rTH+LXLwOEJ1fyxuJfxDaRY0jYtjS/\"\n    \"5RLSJzKaPalODTF08d9972+eDPe+lLIm51hc5n4O813ddgDLxy/\"\n    \"TP1lMC8DqFJb2OGKWc7fQoCoI2BmQ/t0X/xO93Fq6Y6X/u+h/2q39rQQob9M+yrI2zkl/\"\n    \"uFw1hZPUtZkuUQt/jiND7/w/YINrnsfbT0p3zPRk5G8A5u9W/5XnTE/eVyDtjADW/\"\n    \"lXOr0gv4NQhK5YE9ekBdgTGzP79N7imefavfZE1OUfCyf+0x31LN7v4GwWBgU2FQPYXyPjkc/\"\n    \"k4SFw71txGvCJwG5b+/Rf/07z6p6lZk3Mk9ph/\"\n    \"asuCzYOnAZCeAfazP4t7QIA43E+SN8zn0Khr/7XBtQ/\"\n    \"9X9bkHAix3AIDiO2UAbibfNCuBPayPz0j9qSqVX57Kh/gMSJtyLX/Axb/0+z+afG/\"\n    \"JudA1IwPJ1qaYFO3IGBV098DkP3hcGOCJiymM8j+\"\n    \"0QhZD5n9vcItq8LdnZo1OcfhlHgbQ6S3cAc19y3Y/\"\n    \"rMAF+gPq9MgYAdQBDDqB4LuLgMGzf6PWOFOssHNwqis8v8QwH0n/\"\n    \"twGlGkF9bE2DUtTv3002NIf4gJoD+H3wQ2Az6rbHJH+a4V7dWrW5BwC6P/\"\n    \"WKhqcP23UADW2U4zqLqDOq8F2avrfjheN2whrusNj0v8RK9xJvtz6pAhrco6CWW+Y/\"\n    \"F4EOPVvJw15Jnv4bBSY5HkHYE2gQEP/bjDX28eDg9L/QSvcT0//\"\n    \"fXwuxpqcg3Cq4DuAdEixPlwRPZUlqJcD9ZogzN70f9FomwMX/+8LWDtcNfL/\"\n    \"sibnGNTctxLjob66YIPHA5uajOzy6/I/\"\n    \"HOjfH9wDBs3++ehvXeINqP7X5ByCEwCsBrgRyJJDmpct5OL/JI0f1iOK//ZpwcDF/\"\n    \"zf2zia3bRiIwugd4gMU6KqbeFcttOwheoQeKmczAgRFll51X+\"\n    \"Y9Tz6wrCw5NWXJ4iM5f1LRgtDjDCklfa/+2yN+Zmra5NwC/\"\n    \"RmI3pKYduzC9HeV+P82VPpTHlSh/7fCXir936v/9oifmZo2OTdAfw4kfr4CNv8/h/\"\n    \"NZzmdddtgVgLRHst/o/6huVeCRwS1S07Bo+rPDvfEj/rK8F2JZ9d/\"\n    \"4PzfOkT9UatHhv2wtAD4NVOtPpE/NQq7pD/\"\n    \"8hvWPBeYY7NwmECujgf7n0X0iJ+7zAH4bPp6bxf2b04wjOW9ugGsjWBPPfLfXk2Uz0D/\"\n    \"JD++EKIAJQHx29/KPLpX/NEvdpckb/tchPBXcZbjY5y6uLZsEE8ltS+MP93hFBWb//\"\n    \"LM0KYMnJPyh4bqUO8cOF98Sz9P+43M9+HuqWuBNJfXha5sdwVaYG/\"\n    \"k+dnCVOzQzowcgSwObfww6uzF5rgNlvS+X/\"\n    \"CP2L7QBLAJ5U7gXkLZn+z7ua+HWY8i9Y6H8Q+LorMP/\"\n    \"kbPZ3hE2lvulvTb3vZkXS78n+0rH3f0N+wBe5XchM6O4wf1KX8E5q0Ud/\"\n    \"lLh18PQ8mt2W+2NCuwIzFwAvT0udmvroLwCsz9J/\"\n    \"L4crsQJQB6TsH9W9eCxlX66CDsnWEKUVVJdvmw0AQteWS/\"\n    \"+ixJ3zGT98WvLPwReV0fyTs93fENZfhIzvprt1T/a39ipA9g8W5zCtbTiAzcLgmEeov/\"\n    \"cEyy7+OfyrhqfXgSr39dOyv4OvOTUsAEOT83PJU1MfXd91U7mv3sN+xWT4UjJlm/\"\n    \"LSavro19TNG1xWDy+uJkjjO8aV7C3CkrP/w24GfCof8pdfcXGZB38JI5VRzck5/c3LnZrq6E3/\"\n    \"7pIjQHM+GcF3uQ5J6g0A5wC9sv/jQPbHQAumPEa+gIQI43HBL/\"\n    \"4Snnez4On368vh7WE+HF5en39yYcG/Af91NxN+/n49vE/Or/\"\n    \"fJ2fLPB3dv7Je4ZAUI0huR9ikIFHNYS8GJ/\"\n    \"qT6gtx59e9O9ucuDztWWgEWfvKfsLstlny6tbsQG5qa2ugS+\"\n    \"tRcASQbyC4QST6JLEqEVcAfAZH9TeTpYFFQc4/hi6wNiz764/\"\n    \"f93waLPfj7QGW0pampjc78T91FQA58iB7KAtaX4A7R/4OgXAg3TM78wlww/\"\n    \"S894dpShtvdEpv+Nrg7oZco+E5kYAmA5VkER/kf+n8QecrPzwvxlkz/i0+4NpThbjQ1mz/\"\n    \"4O9Ef9BpQ33IUUe0nbS+/8l/0h/Iw3xpf9rKz/6Xpf0sZ7pZTs+G3fkH/\"\n    \"PrqH7OA+EhR5Xh0LyL1G9oftOBz+2Vx29r80x20pw7WpuRGyxN9rUAEIb1HpkXcBmLkP/\"\n    \"a+L75mx/Ox/UY7bWIZrU3MjdKBH9I6I9FIl+Uu2k/qJVaQ/\"\n    \"h4CuApa+978sx20sw7WpuQ3eqd9LCrbI+yPZH7oX5Jco6V+\"\n    \"nDFh68X9BjttchmtTcxt0oOfkz75G9mEAwIblhSlRk/4sAGvI/jfLcSv4afbpU9OS/\"\n    \"xVhdoMeJUEVIKUm2A3AdwycRP8vjwlfUpeScTWsJftfkOM2dOx/6dS05H9FdKBcAdj+k/\"\n    \"4dGH0n6C//NZz9TfqgvSw7hADRuOUu6H9BjtvMO3/jgq8it1YXVcW/2E/JL0P8F/\"\n    \"tt2RAwYX2Oz10akf0j+avh6AKNy+Utw/\"\n    \"i+BvpPznGb+eDv4qnZXF1UFd2+K2DKW7v38qC+hOQ4nP3h9BfS+xivTf649w6y/9TP27eX/\"\n    \"NvU3ATdueq/l1JDCFT+GMNQ9g8Ok+nhfpH4idMTZK2Z/tM+b9/\"\n    \"m4dbTbgI2WBfVRLdPLfUS5P6wejRHguMfBvPNv5kLsdVsA/vE822CAxFaH/\"\n    \"1fd+PY6OHWy24E252aCiD77wcrgLIK6HtiZr2jhiPhAvb+XzRMYDdMmjvCgwXDTow10X/\"\n    \"S6d9GD7cmTM0266J6EPnVwfA7wPjRYM4E7EJ5WUSgv2D2Z+k9B9sDpA03IQwWgRXRf/\"\n    \"yIa7P17fjUtHO/K6MT9qgcPcJSg2vBdGk8QPY/tbyzCJDbbUTPWnELpcBa6D/LEddKf4/\"\n    \"VhJ1RO/e7Kvax+WcAWK6OaaZbE+kt5ChwlHsU/\"\n    \"TP+S0mgJOW7W32RkZE9DAv+xEroP3bEteX69lz5v/GpqYO92C9h9g+jL/\"\n    \"x8X6BengPEiz8D4su2Z8ki4Dj3SceNNIuVFf9jNe6m69s2NTOj2+8T51kCbJ0Dx/\"\n    \"9Wlia6jVB0sn/g0T00NrVBaNzBPYH1Sug/W/m/4N/uO4DZyv9W+kf2V/\"\n    \"53H+d97tH4TWHSVAP50R8ZXyYdqnMDd4dL1U93W1P2f3j4uZsDqzr1N2Yr/1vpbyTOK//\"\n    \"vSf3qk9I/hrSbHTYCzv7QXcAgw7uHFa100jD1OSFYV/afqcZd1an/\"\n    \"3FPz+tCQYPIH9S0YZ9gfNtSH9dQB7j75N0LDeihPjDukNLhoTyJWghUd/\"\n    \"SW87OpjpbvbOfjfPviB/l1053+GxTDMeURmJMGLQWX/YQSPCbjJ5GpZJ4Qv8q8o+5/Z/\"\n    \"rfdbZuaGbH35p8XAPB+7yE5WgmY94zsJ4PO0d8kLw8GCCvG1YH2ZUV7/4eh7f/GN/7CwPa/\"\n    \"bfxrYG+wBGTngOfWgH7gXYBHhF0AnM3+cBxbw4wnhvSF6LwGXA/9D0+7HO0Rf8c/\"\n    \"p2bjZyJ1ENm/k3KjS9mISI4eCfcl3MMfKf5LD4tFoPwagJjcNdH/\"\n    \"n3vcdrY1NDXtTKQOIu2rS0B1j9IoAPUxKQmmZH+IjoEGBfGzU8E10T8//muP+CD/29RUA8U/\"\n    \"5b8EVFdnDFIfx8KS7P9h5EeCBMqVYF309xmX0R7x+ZbG3w8NOf0j88eQm20FrFEFID/W8STJ/\"\n    \"v+/BJDsbVMCrGvvX5P/K/zab66pWdnPQFVHZH6EmzySf7YcjL4EIPEbBf2/fmgJGFkS1pb93x/\"\n    \"y9ojPNTUrfh9SB5T+ltIGRT8KQ9YojtD/q1sy/rcMQGX7/9Vl//eHvLF/\"\n    \"aGoa++tiD1gDogJQJLmyzPvpxA9t+sN58V/\"\n    \"9q+yLAfcJrDP7nx7yxv6hqWnsr4x9gexNQBZ0E+TJOYejpbL/12gsAIj/\"\n    \"xzqzvx7yxv6BqWnsr479GfAekLNAd4A3BLJ/GpLWkN+xDxQDVP9rPPmv8ZDf1cH2c1sYawH6/\"\n    \"ziT/\"\n    \"zkRCGX2W7l1mOCYGc7++QqgHsKQ8eFVYKX0v+pDvvo3fjled9dD+\"\n    \"zGfgezPAlDCtI6tv2V0AQsblCf/UB+iOxrkJxBiCtZL/\"\n    \"4eXxv7hT6Pb1FSFyG9RogvZYXod6GgdVr44HDP6lwwn27MGhOSyjLum/\"\n    \"9Ue8jv8mP1aU7Pqj6ArQtR3L8AegCIg/PI1YPhCOOz9E6u/Br9lyDHwUFEbYNwt/\"\n    \"R8Ov9vJ1hB+tampiB9Q3xrkx/\"\n    \"62bKuVnwKgjklIKyD6n6icEFbJZ0cgvBWX75X+6QCg7W3rTc0dfAVZCz9OuV/\"\n    \"N5plTAHdCvBdQc8JHeCXYq/g34WkKSGMCRUNFi/gd0v/yKnc71W2bmgqA/\"\n    \"lEAYACSPwi2R5wqwCbEdzyyf7STwEuQ5gggjBDlha93Rv//\"\n    \"zXKf7rm6bVNTCz8SRH2JNM4fBFjgqPNJQFYGpPhR+qjsb6JD/qJx/bQS2OZ+oXhPeC/\"\n    \"0n5bltpnf2tRUwg/D5KcSKBGkB6T+oL80SvqYNEd/yC9EaFyx4vNAKTz7ISRXT/\"\n    \"+U5SY+5Rt8p/UybWq2ciByRfqL9AnK/PQxdBiR/\"\n    \"tGc+uky2T8v9s131gVafkfxrVD4Yd9D9k84nDnn3nhxe3huU3N9JNaz/\"\n    \"fciEH0cnUdW+\"\n    \"WPtj5ZF9mcBIEg4QrSc8pnPMnAX9H9bANoT3qZmLlD8xzpA15i4CLAMSL5bR8VF/\"\n    \"wwU+LDeIi8NULq12ACELXkP9P/\"\n    \"DztnjWg0DURgaBD1s4DUgqpSIMhugZQksirVdISFEA6KioMP3HEafRiEKDhcKON/\"\n    \"Y85NH9cTx2E7g+rf8bf6G51fzd2Dzv3Qb4xTV+Wn/\"\n    \"7v7WuQIZ7X+zLjhxtMlB+3T4n+n+4v2bJyJn/\"\n    \"g3v7+dX80e6f98ECMdjePevTH7Ahz+\"\n    \"Sv9VdCSm631wPdOfRvxB09S91f3E5vgV89592t8u7w8Xxw3/6q5kH7S9on/sAeTFzDKivgezd/\"\n    \"QF46w9cBfZ7gjJ+UtSP/p3uLy4f9xvd/f9V++by/mt+Nbfs/tsbAGtfXvar0q/oxHeBC/\"\n    \"LfpZ8K+tGAJ3R/FaX9f+TFn+h/z995DYA3Hz7m7/ePX83b/GpuJX+J3FFJaV6eL4OPeWGrhM3/\"\n    \"IdsDgiftvy8Gtn/pxd/PuVwu78Ulf7vzq7m9/\"\n    \"GGh+1vv9H45e4Bq95QV6P4z9O5f9IvASlX+U2f/EP4GTfq9+3MAkHPY1z580ZTyFU/IH8X3Tq/\"\n    \"aoZtc5B/C+e7PGqDE6aJJ+z+EBcD5C8v/7twOANgSMMoi/\"\n    \"xBOyx8WBnFRwcdAM2sA3f+uxgRsACi69OVskX8Ic/I/RtIXE7sAFoAvrfsrO7MKcA9I6O8II/\"\n    \"8Q5uS/btXO2V9GqWqpoNpxK3xw9x9ql+iVkKuco7d/l+n+IZzq/uswA4t9Ky13zxZwjS9N/\"\n    \"ojdKdwx5ab2Ada903T/EH6/\"\n    \"+0N1f5q968pYDpyBqy8arftzAODBiQWAGwGHdP8QTshfdrQKkCD2YU5I7YDuD6i9BdzkCjAs3T\"\n    \"+Ec5v/ffEvFT05/yN+EjsCuPvvifqOdeDUFsDk6i+Eaar1KwIs5TW9ADjjAKBHckoV4MtV/\"\n    \"ndXbduUDq/YdwDyOE3CDm7+6f4hzIL6Z0DsXAdScjxwJvmjfev/\"\n    \"ClFTJc7zUPsm8g9hmnVdDzYA9H6HpVJnVjzG7l/\"\n    \"pl9e1+R+iH9Par1hUStG7v+xoIYj8Qzjz4k9ueGf7LD0fg9M/Ae+p7o/\"\n    \"oB6wCNqDnl1MwZJDuH8JZrqJnAyCncbACcARQlC/9GxYAy780T/evrKyCvOGlgM1p5B/\"\n    \"CTVgHyP46ye0AOACw468cWAR89de6/zAnXf/URcsrRv4h3LL71/6fDcAR/\"\n    \"d8FsvUHParuL7krktkqKOVhw089xoz8Q7hN95fw3fxlzkgOvwRywe6/\"\n    \"YfmX4AsOADU4HGBU7SMBpZF/CL+Hpe81oMxuMzcsY1BcqwEnABU23/wD/Z8FQLbRPJlA/\"\n    \"FXWw8g/hFPdH+1rKOUBc3cNWCi4EeQtAN1/y/PmPVgUVGE15Er76f4h/\"\n    \"NbmX5NbwO1R4OhKYPEo9WsoR/47yqfh6wmFBobakTyHguuM/\"\n    \"EOYlj8rQKVb5TMA5ZMstqqVcPbfWwKArt8mp4PSvxOn6f4hnAHxe2pUMZwGXwQqsQOEjl/\"\n    \"sfA5A/rtU66fs6q/RdgItS/cPYVr+UFq3UbnvlztioeuvlSP/Y7biV7W9FTSu0v1DmAflN0r9/\"\n    \"IzG3+4Jj14Heg2YkL/V3h8xMDb+nleL/\"\n    \"EOYYX3Vmr8d2nfqon8SaE8Jlvx4vKyVIv8JUD6Zp4wl4Fql+4dwpvu/\"\n    \"2jR+Eo7+isrKnBsnwClACwDyPwVrANrHRM7+IUyzon/g8q9Gz3ktqMBUz2/\"\n    \"mh0P+T+8YmhPweaAShlxZun8IcxyIv3Rvs/TlPPpLgYqsAa8XBXV/qd5BzkvBHNsPhCL/\"\n    \"EH5r8y8DoOezCshvrv+UUejEr+Z/\"\n    \"Tej+noQT2t+0fixXfyFM8aqkb78Vv2ENICnR+6kqOXu3fwd1f4lfXZ/\"\n    \"UVm6i+7MQVNAVYOQfwrz8R7h6TUD5pNX3uROUs1G047/kv9P9KbQGTO3/Ub7TyD+EGaR+a/\"\n    \"9VZfIG0H6lPvs34Ssa7/xt3vxb3UqIzl05Ue74a18GuR6W7h/\"\n    \"CdPcfY9v+AfGTchWI+DfmNWBYnf1loiLSJ/Ij0i1d+Rrp/iFMUepfFS3+SjqldfxwCpvt/\"\n    \"8KT6v5N7GPsd3/7iZMAyfNPj0MIM93f+/6yNgzaJ3IScByjgoeV/5ruj9T79p7njpT8wV/\"\n    \"9ICDyD2FG/sLbf68EiF/pHqV8EkV2AM4XeZ/96fca6NuBcv7roOfp/iGckL9ZPS16ur/\"\n    \"rvQ8Ce4L6+VTYVt3/\"\n    \"OjFu+jbrAXOMcsek+4cwufnXwHEMQPevDvcAnk4Ilr+7f9mABaDpvrJyHqB8n3T/EOZg768gb/\"\n    \"NWoK8EaF2elMiJgCn5G8vewYb+lbUDQlsDKNP9Q7id/KvxV5BxCjj6GICA8p364ZD/\"\n    \"I+Su4ahMoSm/nQg863E2/yHcDLRPolCXgOX2pE9F3e8FLf9q/\"\n    \"yP27o+TCdaCcgU1wLfHIYQJ+QO7f0D/+x8E2W2O/uDuT6vXUMbgWCDo/\"\n    \"g5lLAORfwg33Pz3yNkf8R/zuhWa6v4PnxoWAJyifsBjbNCbP0vChs+PQwgnuz/\"\n    \"tf6VQqgXAdiB64KeWPy2/Ejti2aCLv3d/BcXIP4SbyB9YA0QJ3+O4/\"\n    \"ZO6ovu3TT9lE7wSZRinAUUHFgB4HEI4J//9WwD07zAH8t9ixduzCaiEYKk7KcfHwpF/CGfk/\"\n    \"3K//9vL+AzYidxp+dPxybkJINveAxDlNJVG/iHMgvoB8Uv4JGVwXv6dO2J/\"\n    \"P+Biew6wq+nHkX8Is91fBoh/uwxU48f86Fj+D57ug/ApXDooqcgCoNkXgPyD/xDmz/\"\n    \"4sAY0Vv9rXBYAMKKblT+dH+nvdv68ArAKu8799hTDb/\"\n    \"VH+SCpH9ppOSSx4XgoIhVn5d5B+e8RaQLDm21uAyD+E2bP/mLafs3ZvoXfhkx3J/\"\n    \"5nDMQie3AV2hSUgH/2HMMdV/G0F8ACw3CtX4SfKnCsF5H9P0mdMIN2TaGCc/\"\n    \"EcV+Yfwnb0zRnIiBqIoAbXlzTkAxQaMc0cO9wK+Cffg1sj/0/WqSzVoxqT/\"\n    \"dau7NZB+taQZzAnuT/FryJ0muPrTqOgFgCBa5T/5/PrlC7rfPJwO0/cBGEf/fPQfwjnuQ/\"\n    \"uWvSLqX54EpGwVyogd8Ts8LH+xEVfiR+0qeFTeLF/9hvBS9/dQrKuAHenT/\"\n    \"a37qm0lfc0qqhjyH4LdhhG8Ezh1G1AJl+WzvxBewsq34p3YDOzzSSjrbwJqBWDz/\"\n    \"15iL+U7e5w/AXT9u4j8QziFm371fpaCgeIO/CyQop1A93d+yl/Ct1VyHM6TNT/Idqe8+A/\"\n    \"hLNL7cJtwyUXgnvjnSj63f3X/yya9j4j+0b6SyrXum+yb5bO/EM5gwWugfLq/8/\"\n    \"KTYBvwHwcpWf5D+XJav70yp4KjdwA80sibvxBOy792AOz+HfkUYIfPaUptV6juL/\"\n    \"Xbqu76H7AyrJcADv16kBf/IZzj3nnYLX23fscVnxS28jr7vyF8BSay/jqAfPwmwJYX/yEcZlK/\"\n    \"AvXDvvwekGDxu1BV8n9Smsd6We3/zAIgqzov/\"\n    \"kM4K394eLhsqle6H90DlGsM+X+x9LcKZZRyloDVfeAPB8MZ4FsI4eXNv5ODpO+A+\"\n    \"Be3AYqCSvIv7Uv0Bv1TsBFgpnHslWCu/kN4Sf40fRg1+q84fHEQkNk/kX81/\"\n    \"9L4tARI6l33BKXG9AVA5B/Cce63uwFkT2nVK/TuTznz2br/r5K+kuA8gPrlDhRH/5XAIFf/\"\n    \"IZzo/rc99bMN4OEDY0cwyR6Q/2UDur2jnXJaAaj+Sa7+QzjZ/TUaD4JSVU64xM9kdweA/\"\n    \"AG4EvyJEeyGyc5PAuTqP4Sz3f82rPMgKvAasPo+iaEpeIb8d3TvaOcc0FcDin+/\"\n    \"DcjVfwhHGcqXAzxabDuCSf9MzacfcvbXi/89fpK9IsymIKpS7OTqP4QTqO8r/OMUIGc2hr0f/\"\n    \"0lyWMsf5Vv7jtigz/hIOPIP4VVut7/63zsFWO2z+Gn/qkvzFPZhkv9g2fsprf/\"\n    \"hFabPgarq5Oo/\"\n    \"hBPcrHm6P4D1DawLNjumB7CQP9p3RPzKqlyXKcyXgbn7C+EUt7v7PyuAfBfOAi4I/\"\n    \"aeC2sJg+b8PBX/IV8xfBcxGauTuL4TD3Dj5U2kA+37Hkj4TXgi0UOq3/\"\n    \"C+SvsKC1vArzDbovx2cw38IZ7u/VV8LAFcAMw+K+c1g3wGMoUpe8mcBUF5A85c7oX3av4JS5B/\"\n    \"CcUr8PgPURDgB0PQVWQ+UFFyUS/5v24eE7+Ty8H0AyleaFwGnQb77C+Fs98f2zwDwIDug/\"\n    \"npoU/H4K//SP4XywT1AOw5g/aeCcvcXwmFus/77e0AA1N5mPT1YBNz9vwypY/\"\n    \"JTJwGNdgjAnHL3F8L59/4jVLboWQTuVQDQ/pnbK6tG/sOxdgxQ2JV9n6N/p7/\"\n    \"uBzn8h3CQm2g7AK8I9qpghjP/tCp4lPzfS+gkO0MRAOEr4d3y4U8Ix6H547YKoh4uYP/\"\n    \"Ph4FPLP/Lh9iG9SMAi4DjPu3L4Ar9DJDDfwhnu3/Xv6ZN9oSjPCoh/\"\n    \"83ad+QkwAJAPLIEONh5FZDDfwjHu79ond/id22voFFxvQEYRvd/\"\n    \"+0D70j+tv4w9wEL5XAUSbHnzH8LJ7u/kqtp/\"\n    \"XQA6aiwWgAcl6OZffJTsnQhlBPuZb4NYBHL4D+GE/JE8BfeAflguYwlYUGd/\"\n    \"Qd9XQvyelHXf1z3aL8ub/xBOyx9K+xQ21yhes9UrQYP83yVyYdn3m0BnlXwUcAbrP4f/\"\n    \"EF6QPzsA1U6COacAhzXI/\"\n    \"8LNv1GlyErQLgWdll8Dyai2HP5DeL37k9zzqxTcBpKX8vfdX9d9vwhUsNidUb7qJT795/\"\n    \"AfwmvyByTfNgRcBaJ7VgHXgPwN8m9sNXCHsqcvvwuk+285/\"\n    \"IdwXv70f1d2JwX+SEYSymflv1GU21C9klh9Fuic3X8Ih+R/\"\n    \"vQFMRwCeNnu6h2zA01n+77P2ic72CtPXAcsFwHcA2f2HcET+LABd++Q7sucogClwBAB2B5a/7/\"\n    \"4ASvTEjeC53WP9VWD+zX8IL3X/OxWh/TnPkHgVPJi6P3d/AEi/\"\n    \"TzZOAxUGjvsngG8hhBXP5i+b6dqnz2viKQ9UcBUwd3/\"\n    \"kv4bPgFxUefBHQvLqL4RDlPRHHL7LfZpxN2BD+x5cDKr7c/\"\n    \"f3fYyKSzZcVqnGHtn9h7CG5u+xB3f/zOQ8lebrUbsCRP7vlv53pR29U3vMNwBV7ZJXfyEclD/\"\n    \"utIA3gB4cAdC9rU4KyP8i1dsV9tjmB8OUjnwTnN1/CGuu15vNLpT2uLeJnNt/l2VMLH8f/\"\n    \"r+X+jUUF3AL4MAxQDG7/xD+Q/72K9K/8gTQfu/+LnAb9UDy5/\"\n    \"Av5xCw0D5Vt5K+yuz+QzjN7XqVD6sgVDiuTwIeaH5KyP/\"\n    \"dui9b3wQifQoFS55PgrP7D+GF7j+GnAsAlaocgGZfkWz1k/i7yP/\"\n    \"yXaovX+8BgPeAzYQWgOz+Qzgp/1K+E8qX7V0IonkE7wLnQZM/vb+076i8pu0CeBFQnt1/\"\n    \"CIcp4Uv6tkptKC5hSah0vz5L5P82pC4kf9nhFYAXgLu3ANn9h3Cu+7v/\"\n    \"OyF+lywAVHCnbB8HXodprlryL6R8mfO8BFAvrgJ7++cgkN1/CIew+n9L/wou+iLgQmHN9Yb2W/\"\n    \"c37+r7Fj+679VqJ7DZVGg+XQbku/8QTnR/BfmAI0DzQ68CFK93WT2S/M0F4Q/\"\n    \"XhHtAJQ8X8l1YAjj6V5V/9RvCWv5A73dVpQv7AileHd9JqPtz+C84/\"\n    \"ZezAKg4dgUo52fCsvsP4ShIv7t0j/S7wwxtH5D/dPgXHzaCC5n86JtAJ7f/XP6FsJb/\"\n    \"72sD8bMAKCF+6iZ6hO+iy5/DP7SFoEkfZyx+KHC6AszuP4SV/IF3gPO3ADhXgYbJNPUq0OV/\"\n    \"oeerolb2aDcBC/Er0PsHKvLqP4QD8v89/Pa09hbQw45J2wSeGLhyAdjl/\"\n    \"yaZz+3fpUXvQLk6Aji3W8Bnnd1/\"\n    \"+MPe2eM6EcVQuEPQswCkNEwZCVHQT8F22ESUBaC0adgnzjmYT1dXNxOg5Hz22J4H7bHvz/\"\n    \"AIz9iulv62KTT0AKphzd/\"\n    \"vYtwftO6d9ZT8gYFf8EIHGPYBDscfA3x04hcD5PAvhFemf5lA+\"\n    \"4YGgPIdkLsq1vpkwfRfbP7hRBdok5/stIGZj46T5eo/hGf45I/Zj/\"\n    \"ZpAO2FJD26s8IXVQx+Pez9uflv5iXA+D2AQlvBagBoAZRa/\"\n    \"OfwL4QXp3+5wxXpGz4GYuyrVJTZXfLSw9+Lf1iM/gbNd0HNJ0EHXwP6JYd/\"\n    \"ITxB2mfnv4KrQAfLfFA/+Ysr3pD/cvWP8J0Murer6rdJ/\"\n    \"BRtufoP4an8f0180ghrf4JbgTI6b3P6wh+y+Gf1f/7wHKY/\"\n    \"PQAzXcD8fwXn8C+ENVemv5PfET9VWYf5SlBCx8augPy5+lu1AIY/pwFU5dhyF/DRIXd/\"\n    \"ITyl1S8vHClGPvE4VtD7M+u9P+zSfgUlletrAMXZCoJA/DSAjP8Qjqd/\"\n    \"PcMR4PIg0KDugsTiwJVslv87pr+iw6oLyFw4vPR7wj52G3gfQljJ/\"\n    \"7qVj01gMf5b5c7zOYDi0BFY/A+81dzHC8UFs/SpVtrvlLu/EJZY/diiBay6gB/Ubu/\"\n    \"wu7b8wet+u2s6AbDnd1RwXdANbKv/\"\n    \"LzR3fyEsGNVfJl+3gE9E55b51BDkLif5v7Pe7W12ANqAA+eA6H59CpDxH8KTxb/\"\n    \"D5sARgKo1nygcEPz4B0z/8epvlL5wXn8M4Aofpj8t4OPt1GT8h7AC8Ze34RvFGsTvSBOwz9P/\"\n    \"jSWP9F1xHQDAASAB7f/OUj58PGX8h7CQv9RP3NqHMwClY9k7ueKFxT/\"\n    \"s56L072T9V5AdXQWwE+jCdUWoBUDGfwhH8jdbmWWPWfykI8YjAUUzy/\"\n    \"+tVS+jVLCt4BwQf9h4HyDpQz79CQHgCjSAnv+bk538BzD959X/\"\n    \"w0llSu2yefaTEb9ep5vA7P5DOADhC7YAjP+DbcDxcgD5w27Zt/TtTlL/S98Fj/O/sx6F28eM/\"\n    \"xDWXIEzAJtE39a1iteFj/zn1b/5oGC3jeNfAYALAarhc0Bxy/gP4SX5w3wX0OuAVzmQ/\"\n    \"xurHsYW0L4WP8ljn9TTf+8OkPEfwqvy3/CN4FgJ/\"\n    \"wv5wz4pn4IewGkgAPpn+68lwN6fAWT3H8KfT/+xBWwKyoqq/\"\n    \"1b+rP7BmnfEFIwzIH7D+GcPgPpz9x/C6/LnFMA1QYOf8DfyZ/U/t4CxGxwfArD5ZwnA/\"\n    \"Z+e0y3jPwSA633ZALZOBJlwonpd/qz+AZj9lHKHmeWvBtHsz/gPYc31GRtp7gCUFz1/\"\n    \"Kv+3T4Y/x4DzScBC+iQuA1kIZPyHAMj/Ll/B9KcomPxd0geUAPkfr/7ByrfsKx19DMDSH+P/\"\n    \"CLhl/IewmP6LBrANBQ1g0LxeL5sjrOUP+1L4TH1VKsY7AMfjrQBfAuXX/\"\n    \"oRgAOXfuwILXplXh8Lal+wVnOYVwGUh/7cvDP/WPi3A5dODwDJX4/zPb/0LYZK/\"\n    \"TCjPoHwWAZsjm380fzma/qz+P5/9rFsA0kf2rABczLDzl/Q1//\"\n    \"PtTwiL6X+XOQPaJ7MLoAP0zKcVXJbyh3dnqf9ZAygT4wrg6QcBJ4qTc38FkPEfwsBj3y/\"\n    \"Jl9MK1ozXgOz9Ub2egctC/m/Pn8uOeoCFb8fcAg63AOO/A8j4D2GQf4m/\"\n    \"1T9UMxtlKbqDO4Cl3y7r3sD0n7D0y0SlgxYwtoHeAyjvyy5g8e+l/\"\n    \"1z+hTBNf5l90QCAS0ApXAWLAD1OFa+r6c/q/7Pnfj2Os+w7KKF+rNgX5/\"\n    \"47vxJI0z+XfyEYpj+7frugACn/8nv6O7sqWPcrmysn/4ur/\"\n    \"8+y4vAc0HInVbSJXS66OO1lHATmX/6FMFDqn/XPTwRceuVPC3BycWEbUEHO9F9f/XcTwCftTy/\"\n    \"D50AyALcAd4GH5fIvBIH8ufqrEt27uJQJ5643R58ByB5Zody4CTD9Z95+Rv7rBoDu/\"\n    \"TD9XYpK+7AP8OTnAOD2eMv4DwHurX+WAV0ATQC87G/h68H6/ZdfV/J/\"\n    \"87mR+ju4ELD69UAuKyF8Bn/brU8BcvoXAvK/evePMfht5Wi+3Emxp/9gclmhyPSfeIfwp+l/\"\n    \"3ADmXw+0ywknu3tBpSz/QzBMf8W5AVBUZBOgx/v+qrZndrUh/\"\n    \"3n8n+UVVLVxGvi0CaB+K78ysAso8fdFQC7/Q2hQvh3921Qg+ouyVa/x/5Ih/4ldwi/\"\n    \"HOAgsFNfSV+IawL7LSvd9AKCg3/2Ry/8Q4N6g/\"\n    \"0cyelO6PIKgERRbxV4BKC5sLf+3Pfv7cSy8Izge/ioH8ZvObgGe/\"\n    \"NUAPnzI5X8Ixoq392OTu6wklXv6OzwS87+zfDH9Dw//\"\n    \"yh0eKCupWDPqf3cQlWgBvRDI8j+E5g49/e2e/pr67Sz/lTY3ARnKd3JwU2Dvvzr8Y/\"\n    \"jTAlC+y+UJoGIb3wHuTsq6AfQNQO0BcvofgkH4zkz/\"\n    \"B5z8ydG+Xbr3g+rl+pGrR4H8V+Mf6SsSZK9cBKB/\"\n    \"bgGaHv2yLP9DMOPwH7YAfQrgSmqXEfq99e/CXlWfCiB/gG8s/knWvryDIwA3gPIqLP4+/\"\n    \"atAPN2y/A/B3IHlv831A+/17wjfuudVj0P9hL9UbEz/\"\n    \"1eEfsAaQy0hHa4C9Hq4BOP6X6rEPt5z+hyD5A4wfAlaWmqFe6AI0AxfKKtkOSP5H45/ZP6/\"\n    \"+cT0w/EtAPz39HfUU9IAs/0MwK/G7KLvcL8Ud6ZO6sru06MudHZD/8fjnHBD90wL8LBcA+/\"\n    \"mDvee/2Yc+cKtTwCz/Q0D+6B7pk64P/VfJfHflmrlPA+Bh+h8e/o2ccWteBW+LNYDbwLmn/\"\n    \"7AFqEef/+Xb/xCWi3+kf3HqJYB+Qg8Y5r4KTv5dMv2f3f3BvAdoY/YrqYJdT7cBDf/\"\n    \"94Z78tsLVqXK+/\"\n    \"Q9hIX49hdSu6V88JH7XIkClTM7cN0x+pv+fjX8uARVc8zGgbabW+90BSvyyXvfL+wLw9iGn/\"\n    \"yEg/xkr3y3A079SvXj8V2TqK0y6V8n0f338A0eAZAeKZnfS4Fdw3dO/3DuA8iqy/Q/\"\n    \"hvuQqvzhfFC5yc9AA5OZY/m+W2h9exvm/PgjcWQqU0H0EyA5A6r/ln/6HUPL/\"\n    \"8Wz+t3cnuHj4u7LgAcHDhvyf8O1g/NvYBAgVs/D9PJTeHwMw/zkArPP//N7/8L9zL34stW/\"\n    \"1yxRkMN0EwqZwuPfn7u+rHJbHAeMFQBsM3UBNgBPAYQWw5/Y//OeU+A86wGU8BqAJdAK0D0z/\"\n    \"w/G/agBM/rGa/5MAlgDSvnPpXKUbwCPeHvmW2//wvyPtK8hHmP5WumuXl5dA/\"\n    \"sfjv8zxOcx7xU77Zy7/nL3w16NquAKstX9u/8P/zv1HGcqfpc9LPSwBLP/\"\n    \"7P8ofvjH9ldeyH0pOAZTM7rATPuyKg53Kov/wXyPpqwHIVH4fNV/exTz3+cm/yf9tC1826/\"\n    \"881H44+2cJIPF3cNb2X7P/vO/Kbdn+h/+bu/\"\n    \"lBUkkLGI4AaQfi3uGv5Q+o+9kCwMHulzY5DaCeFn+P//3xVNXTv55s/3+yd/\"\n    \"bGkVwxEPZUUhZKQQ4zmGIxCSVA7yWhCKboyhlzNwKVzM1GOainm9BXU3NzpJZHg7to4AGYld1o\"\n    \"vJ+jGneNkH5x2DWAmQEgUCWTq2IA+Cj9kf8nRoDvdgBaAOwX8dMAFBB/\"\n    \"h7iIL6D8Dv36r3HHgP0ExN89wGnm+o8tABPA1fQHT0+7Yz++DxoBA4CQ7AXxnaL/\"\n    \"HgS2LWANvf1v3C2WBdmXZ61xthlKfyT/MdMEaADEj9D/56cHW9KrOxxvAbSwdQLI/\"\n    \"D9pbS8B82+BlafJiwbQx3+N+4XZ70ADSAhmWeJcZwAq3Aa4BHScr6c/\"\n    \"8m+D9ymOtgD7RwBy9gCrJWGgWkD/47/G/WIJ+TFuAmYbGwHzXiHib99yf5ZfRX/\"\n    \"knw5AF3ijBci3+4BJYaoeUCsmwP1Xm/pv/zTuFYsQ3ifFZ47/7LEEsX6WyzbjfzHf/6VKL+j/\"\n    \"TvnH3nwJxB6gzv+UpnQAxW+Jf1Jg9W/+N+4VVn+FWBI9IIzHvf2vx3/OXmkAs40WIEf93y3/\"\n    \"cqcyQ1l+gKj/xC5AhTiuykERg/VVrd7Xf437RKjPBJBc5HdFCyAnifU1A8B8+0b/\"\n    \"of8bGGY+jh03gGByGxiv8i+3/LsLjOI/9KcFKK25j/\"\n    \"8bdwmrvwP81+L13zzP6zesF+Fn876yfi2ux6gY/t8p/8KO/TC/\"\n    \"vgCjP1VeAJj8ror7MVel+g7N/8a9YhFK/VP8uYa5TB5LHyjVd0qdHmDAfb5R//\"\n    \"fJvxDyO2ztu5hW5o/fhqIUP+ov/svW6DM/VU67K4C+/\"\n    \"mvcJYr8zqnSAWZB9HZanKsbKK68VuX4h76QeoxO8H76/\"\n    \"xzyJ8bLSPINxvYYIHsAWRpARoASfjHfrhDiF1r/G3eIpfBn3DZ7669PxVJ+1n/\"\n    \"vALNyDbB6UX7fAaD/++WfASAmJKfcQ8I/1RQgj/APFVZ/2+qV6gFQ879xt4D5LirMDrPJj/\"\n    \"K7SGneZ/j3f1iTo9lPsEH/98j/cQc4vgscyH+2ADJfCKQFeBXtUyhw8N/\"\n    \"8b9wlFvAnnj5gqxFAKZZukNpZKO2nYCfA8P8e/PIEHhJxLgEObgKGTORfyynUV/\"\n    \"AzYMs+A4BX1N+eoq//G/cFyP+acHYBGgPM+MR1yQDDPla/G/+D/j8dMB/\"\n    \"TV8ifhPSPevln4iuozMotQFDEZ/5PUtH8b9wZIL7lPjk++4c1iu+VmAAYAFTVACBPUQ0gNwDQ/\"\n    \"3/IP9g+BVLFBADQ/9+0xH5fA0y2GvqT2QQg/wovrf+NO8NO/fmcF6u/\"\n    \"h38F2eIV4ss34u9iswuIGdD/PfIPwvpvbwF2LWDYa/DPWWCdACrD/BVs/ov6iq3/\"\n    \"jfvCskdt+eUOa4ylAXAcmKN/moBiPKF+Rv2vkv9QHqcBKCWO7QCgZemXZxMwxPU9qgFUC5D/\"\n    \"2vxv3BG+yf6EjP5zeJ/hX4Vi9QCH3cE/SZYK+r8He/IbUN8pEPOZAYZ8DXX6F/\"\n    \"LXGwCwVf+krN7/N+4K3+Q+CNnVALy4A1xcODhBfmYBcuh/\"\n    \"xeUfeKi4GQXYBDD5D1mon0PAdQ6YIv8J4T4doLb+6+r5v3FPWACoB8Bz+F+\"\n    \"rzMyvhPIbyD4tAPq/D+enR3h/0AYeEgN/M/kzCPj4T/agVWzn3g+g/\"\n    \"n3+17gjLMfgBFAhewCtcD5JIQ71A2rU/2PyDx7iDqF9XGswBwxeAShOssHDP30qxnddoPf/\"\n    \"jbvBcozZK9HIvr/aQGgP4D1Fao7+3n/693jAfRJNgGdAYbzc1foC0GlabUTzXa/\"\n    \"OKABeNP73+7/G3WBZTrJj7V+4A0iSZ/mnHSD+NerP5d9RA+AxMNXqCsO5tL/Uf3J2E9CC9GP/\"\n    \"1/9+rRbQf/63cR9YDFoAmMladkjv6Pw2oP/75d/\"\n    \"kf5QdgmPABGP4o3b+TqF+DgHC9geb6sExgKlv8vf7/8b9AOo7ARDhpw/\"\n    \"o0+Ft8v9xBf2Dc9Tf8fgAkBDb7vzrDeAaJttQEIYWDWAP6X/zv3EPEOvlNpeHM4AsH1vuLz9W/\"\n    \"Tn9eyz1V3j7JpATgFhJ/8Qr4OFvQ4OAiD/Qfub/6H/zv3EXKPYXqAAnADF5mbH8YPoz/\"\n    \"qcD1AIA9U8x5DkMGLY8/uf1vyt2AK5pAKi/fOq//9m4cYjw0X0lGS1gx3y8tH/5DPXn9O/\"\n    \"xbfIj/i6G6oi/HgNy95dC1Hd8CPdHdgGp9njpB4CN24dZ77B60hEutRlgBqAL/Dj6c/n/\"\n    \"KNcy950BQP1dDK7/GflD/lL/MUr9h5agXGAX8NL8b9w6xPxYJaG+oL2Wlf8yv6q/\"\n    \"nfSD1Z+3f1F+GeUReAqUJqCA+le1miDSj+K+43b2l4n9Cv3//2rcNE4rljKS4FQlbUCY/\"\n    \"WXaX+TcB6r+YfT/OaQv+T/c+5Me5GNdqsx2XEu678t/Qd+Z+fWbsvy3b87/\"\n    \"fQDYuG2I+3QAxv9tA4D3Xo4Jqi8KF4h/SdCyyU3/K/CLqB/\"\n    \"m7+z4EmDVfWWOAtgErBGs4u8OgPyDFw8Ba2j+N24YxX0Hp8WeENonKpTNPABYU+aAMB7yBy5F/\"\n    \"2twfkwD2LjgvAPPgIeMEWAoylwpKBmTvHb/+vi2+usEoA8AGreL0D5xiTP/\"\n    \"84O57zgryQOzfEHqt+ofR/2vGP9tcptdZnxjBDgrjeoAFn5VVn873Hc0RP5wXwHeO/\"\n    \"QFQOPmcQoWglvBdhTIEi7x2THiH+KrhPAVlFD/a8d/k98G/486gH041t2fTEhKAffr8u9Y/\"\n    \"fsAoHHTiO4TGQXwLMu/\"\n    \"wqzluMiL+84b1yJeSf+fivvKmFAFADX9e9kE09+\"\n    \"x9J8WoCDQA5D9XxVzANAvgBq3iI34r1aiH4tfvOQxeT5MdOfVdn69+jP+l/\"\n    \"5X4CmwVwDOXP+vy+SXm/dKirg3/\"\n    \"vIwv3rATvzVA2S9AWjcIEJ9YWHRDGzmvgPcv6zLaWGjH8NI0P+q039aAHMAIQAjvD9b+\"\n    \"dcyE78c7VeeRqL8txHmr+m5tN8I+ad+AdC4SbD3Z+vPZ7i/xkvKWMivIJP6F9UXxZ0x/F85/\"\n    \"tMAsAQvRzAqjmz/a/\"\n    \"jn4s9lcmTfxB9IP+rvWPrfBwCNm8MJwPolji1apf7RfWCOOybMCXI+of8Vp/\"\n    \"877WcCiBfOxGC82tn6b9ZXYhDI9R8XAOi/QlI6QG8AGrcGqJ/Ikl+K/msR7l8SeAKg5V/dA/\"\n    \"TpoMQVAMP/teN/8IS/GvwH1v2z3/zEMgaI/9wD2OS/WftNfILwvDv/j/\"\n    \"cGoHFbOO3B+B+P8exn9UoMABVnqmTu/a8e/59gf1KsiK8agGH5f6gsIP/\"\n    \"KCdOwx+ID4kN9lX0D0Lg1bFi/OwZIll1OF5Q/KPH3ogUU92OuoP914z+gAYT/\"\n    \"hx1gEOsFoLA2g9J+x6kOABgCBMVnmRHq1/jfG4DGTeEEAA1ASZw+Leg9I0AA9ZM2nszw//\"\n    \"Hxf9sAeBOokiOAhJHdf8J4SJIj/sLkEcBV9YHn3eTvIvVLDwCN28EB+9F+4yK/\"\n    \"rElsP8F9UqhfYPRPwdHfVfj98bgBIP9eYHhRyK3/NQDIk6bB+78qpP1ayH/\"\n    \"dALz0ANC4JZy+iyVWPUAm9tvDeUfTHdbL+CU19P/\"\n    \"w+E8XKP4rGvs7AA4AsIcaABgCavZP+SyvBqBg3m+trwAbt4LT6W/\"\n    \"7Dos1H+KH+wmFJWsPtgKJ0P/68R9wCuiwvQgAZxl7AOfIv2KkP6iCA0DU/\"\n    \"yXL0dYDQOOGcAr+Pt4BJJyK/Kpf1/oFFnhfBQn6f5z/\"\n    \"T5We4L7dSEL+h+owPzYcg9oEwPyR9Iz4e2Xnj/\"\n    \"UJQOM2IOKv9iYuZNE+DvO3mIkC6v+h2789tvJfiQZwRv/PQ7FawZA7FaZ6/\"\n    \"RurYPJ7bdVfqQeAxk3Awq8V22OB/JZ/uU2+KCodY/ZC/X/o9h/yE9wE0H7K0N6tAOY/ENn7Z/\"\n    \"O/IX9RP0ku9BVA4xbwN7w/3ANcvBQLpn3cSDoG9P/\"\n    \"4+H98BLgdAs61VhvJIr7KUcYrwGAqN+mHQvzFK/xH/vsIsPHlIcLbY/44woXT/\"\n    \"6Ra4PPoz+0fQP0rxwQagDzFwIbWASZZUshfDUAB3qvqR0CNG4DVPxbyuwSc/bMH2N4COHw+/\"\n    \"dn+Q30y3DdMeSb/mHxV/+h/haCk/3nAfh8AsAWoO4CQ3+zvI8DGF4fI7g4Q2suPYcIzB/\"\n    \"gz0f659Gf7D6A+/HdpOz+mBXAGOPThNuAWEDzYwSQvQ/3N/\"\n    \"mi+XMhHN4DG10bIj1X4XhNgAvjrctISFD+X/mz/j3cAqcL9jP/Ifyx1Kf9ZEUT4n18bAHt/\"\n    \"eyVRHvQjgMYXR9GfsMY3wP1fGoDJn6j8OfRn+//dLnBeIyFTP2eAhojPEYAcTI7P9VHSL9pL/\"\n    \"23uADUHdANofG2s7BcC2oBtj79K/5PFfJoAPcCggP6fcPuP6qdMBzD7Q/\"\n    \"qKqH8agG14Mf+b9Ln2qxuAuELEn7E/K/inG0DjKyKkZwJIZSQfNgFXxf7wv+S/\"\n    \"vAz6f8b2f9sHvOxPNQv4+D/7/gK3APIdeALM7C97wV4gf58BNr4sIv7hv/1A/\"\n    \"f8y5105yEV5OVLvVbZX/0/d/tMAzrUDWD9Me8Uyo3L0X1AEz9F/0V3L+r+1EN+e4l/2zd/\"\n    \"15ygK4xa/ZiUiipTF32CSDP6Fjz9ASTIYDAarEoONLIpBGSiRCYNNZikzf4DN6/08To/\"\n    \"T9UHyu/s8555zv/bXOffe98f+2QCm/lUZfudUzX5i/fQHfYIdqYhv059UFv4/\"\n    \"kf+ra8h3qRuA5z++S2EZe1bJHwFZpfEDwMmTuglgpF//\"\n    \"EHa1gHkCmPp39aShTyHKIt22DHv9Y+m5l7CnqmAldwPj/1ue/\"\n    \"+Ad9AmmPoZ8ce8bAKJiS+f+sQOcXMIpF4Aa/WoAqKG/X/nSbABT/\"\n    \"5jau1+2vvvjx95TS0U+xYwvW/9TtYACn9Ay/j/v+W/\"\n    \"9EeBubdwFWOadTf0IsAvyl8BNDH6WLwCi3ybAvj8BmPzZAKb+\"\n    \"QQ3TH6tYtXlchaj5r2ANMv1UQgv8f5q2dPZHAbrTMW1096+h3+EX+CxqE+\"\n    \"i7AagDwLyN2DP9Yf6keWdX2n9JDWB+BZj6h/T59I98789rn/7gzzCvzRr2SdrkRiD8f8vzH/\"\n    \"LcN//sPP/9KVD81y//qgOwxUv0C4CGPxu2ot8J+GOktH/h/tJ+8X/\"\n    \"p0qXZAKb+DYX4dvmvjXqA4KfkzX+p5C/B3wMj4/+zn/\"\n    \"9Y61QHfyWfAQx9bRDUpwWQxh8AEkec4V9WA7iG8+0/\"\n    \"AnxlNYB5B5j6B9TBzx9aIr6Yd8XsvO0aW4DTT57+aBX4tRnYT/YFQFd/\"\n    \"VG1Aycrpn21mf8HvO4CxJ9hcWzbiP9M/\"\n    \"SgOYjwBTf7+ejIJ6J878qLBP5BawjnwWdiV+Mv6bBubHDqCFKWoCkJ8f/iqd8fxfQqXL/\"\n    \"Jcl3f21EfwnO/r1AgD79qX5CDD1t2uEX+yDPoEgXE1At31cYssirRfcWz8Xf7RlYD5/\"\n    \"rgg3AEngr0i22HdWMv9t+vcDgNMy99MDGP5if/wCCPVKhPRhHgGm/\"\n    \"mY9icYWgNGSxLsSyGetBR+r/JLpD/\"\n    \"917HcMWinB+8rfAsmLiTwB5PSf6e9smXuSNkIfW8vwx8MLgOnHTpfmEWDqr9aTb+gx5MM/\"\n    \"i8DfL6j/BdM/z38hn8xSilZEFbCP3QTQMeyTP9k17CsR2MUy/\"\n    \"0If7Cnjxb9MoPkKMPUX66vgY+8MPuJIoC3J05+0jvpfiT/8G3gXbZ1FfOmuIx3Av/1lQ5JU8//\"\n    \"/FP30f2RJDX6f/j3+x7c/FsES//MSMPUXay38Vb2Dd68cA1jfpee/\"\n    \"BP9NW0N8S6mrrGP1X4EK+Ia+Si4A+PjnDcA/AMBwLwO/L/9sA38b/xQ0O8DU3y3QPj/\"\n    \"C39HXwi3aavod0x8x+OWjzmHf6Fe+q1iRbYkC+0phX3W8/\"\n    \"rNEviIV0L363d+ZyCVgdoCpv1XfcfevKht27Qnt/\"\n    \"hD+mwDd5CPnbPL+R2j0r0i4NwDDnyz6j2v8nzT5MsrjHyFB/bJITZdw0D+odO3U7ABTf6M0/\"\n    \"bWiKOh7411x7/hT+MO/GoDcK1EtIHFXJkx/HgALfaxKA8j0l4R+e/+DfsL3f63Q37/\"\n    \"9uV47dTBngPmLwKm/RdC8Fv6Ovqh3JQd+8p/AH20p1lUll/ERMC3AJvIAGPjJZ7SqB2T256e/\"\n    \"LkX/OP1Z9hLRKY4BrEXv5yFg6q9Q2GdDrD/4e+tMqReAZOK34g//n09/\"\n    \"b5woBb3tse8GsKrjfynnf4JVb3+k9gKIJbFPYhHuApGmv50rAKLSA2w28xAw9edV3GOL2g/\"\n    \"+KXDs5L/D/h+a/vAf4okqzivKMPzjVW8ABb8u/\"\n    \"n34V+gC4MHvZO75DDjA3yxp9JNU1QZOnZotYOoPq0989l1BH5Mz/\"\n    \"x2uWPGb8UdbryLxn6RNPgHYinQARGI1uQHU+f/4+ANgoqY/mRD/\"\n    \"TlF++dcF9AQL+G3ERWD2gKk/JEOf+T/eALKyfVwhI9Jvnv7h/yjRnbM/\"\n    \"y5v2EyAC8KmsCOidnN0ATtooR38tT//qAB394t9ZS9q/\"\n    \"wO9HALFPsj7MHjD1J2TstVLGd79alePcCFi/Gf/w39F39tazn1V2A7i7JL38rVjRcdg/\"\n    \"LhNamv3t9p/P/9fKXeHfJYL3a6d8/Mfh3+eAd7MJTP1WFfVt+i/\"\n    \"pcXpAkrOod871X5n43fhv2ir4FbK5J7Dnf7LYX5Ez/sV/2Ad+T34K8KO7MC/4UT4BePpXB1AK/\"\n    \"LWwFNVvADL5s6mTwOwCU2v126Z/9jn949DvPc7bvze/Cf/M/6Ifteu/U03/XP5XhOA/thou/\"\n    \"4S6gOknTirZpZz/qXX2V86v/vLyP97/fQRYTJWiEwR6TxuYfWDq1yqkk76uHP9R5r/XH7n7Z/\"\n    \"7X9O9WWmk5sIv5X87/1Aa/T/93CfOPfe53svM/f2gAhBwN079qrgAsGx0kGv2LIzoBeld6/\"\n    \"bv0llhvx9+lN4m1JqaQtW3j79DmDb9U8I/aI0BF/w1QXf+d/PWPSkRnlByWG0Dxn/HvlJf/\"\n    \"tR8Au4D+GgsjcrQP9B22dXadrnRdaHqASnsHXY4eXn5o3bOs20qodN+\"\n    \"ybg66Ed1CpRfRy096ikqvrGeo9AhZdx7dia6j0jlUunju4qjTF0+\"\n    \"j0uE1OnT4UNOBxVG0a512R9H2r2rH9h1rtHMn6/t1/of0JNbE37jhP9Cm3P6VbEO/\"\n    \"5vkPG36l1gC49S9Bwhr/Of9XF8j7/zVWrv+d/\"\n    \"hF+Tv317kd46i+b1gNO7DuxrBNaZ1lLHtiXi3zCDv5LiH1MNPgJXPwvLt2jC5Ru4+\"\n    \"CvBiD25Y5+2LeL/sWGHwv+xcH/qfFXAxD8Sxj+8A/\"\n    \"8S1xvLaA3AdzpJ4r+JUb0MREdcAMgGvnxqN0EDviEkvxl+MsD/\"\n    \"Fr217EnamufZ32D+YQrdvwX+MN/pr+j+PfYV8G5A/gI4CcAXODXCYBt/\"\n    \"g+w4D9OD2jTX9gbfaUuoU8oRf5LHcAmrIMugK90goX2EbBvd/pZwl8a2N8j78XiX45Afw/\"\n    \"g7yEe7gF+rXvAj0O/HPrDf2MfE60BjPSjl8V/4MfPnj4z/WkAWLpjVwPo6JMa/\"\n    \"GFfSw0g8OPGPjHybztFxf4BYiC/CiE19imdfS+zr0wIetL2xn6VkXwXBwmxU1o/7KmJVv8T/\"\n    \"M3/8ATgNlB3f4o35p/FxR/+8VIJtwANfcBfCoHz9Q/\"\n    \"cXdMA2vQP+lX6l3+jzxGASmCB3wX55LN1/Af8fbC/b5z/vQHYUdqApv+eB6G/OgCCe2wZ/\"\n    \"5z++/QnTL8Wtgw/YfjLYwcQ/X3+vyJoAZn/KPR/Br9Wm/6y4B9awGk701/0E41/mQ5Q8Dtwh1/\"\n    \"JHmX6A79LWaKkAYh91Z2knRj+vbN3yMSgzHwCm/4KlwH+4R+cCfwf4Q//Rj8JOZGJ/\"\n    \"vh312sJ7EtA03G8wF/j/3i/+7vm7k9BpCjkDw9/\"\n    \"zoQ93v73Af6pj+\"\n    \"SdsY1FVQxEu0BAABIiJaYENqGFbQCJiEIQBGSQEbASEgFIEBBBAYiMapg3g3VkZu9fSP+Off3+\"\n    \"FjAe29fv7Xvh/qcyPWQvkJ/u/22ddygAxH+rv5zeX27tt/LLKwFM8Q/\"\n    \"7A7gfIP4kAKgP9y8zwv7g92fln+afBCBr7rf8w3+5gfqDYb8cmP+l/nDfEd5jYBf/\"\n    \"CqcGID6U11GQmfs5skJ4n5M41f/KAxso/vp92f3QH/3nApCjsMVf5OeY/PYQf0p//\"\n    \"ZruX3AC+FjmHMDVX85+9/dBJzD7fQDkl3P9V8P/1P4X+23y9xb/3//sCwUSQOzDpf/vxnbrH/\"\n    \"mX+htT+79j8i/6H8v/GPyXDfmRfjJA+B/yyyG/y3+J/2+/VwL4Lg5O8h/\"\n    \"+x0J+Y8gffzYDyB3Q/wR5qX+or7gxPcAbawQQ/scVYH7cR2zPA+2nByAPbEB+QqJR5C/\"\n    \"1T8iPu+n9w//hvsku+BHXLw8BTf2YPIy/EoDxaPLPSQoY7tcCMPgqLivxH/I39+G/\"\n    \"T1UAMj3Qf/NfZwH1p/tXsP6n8Fek9Zevyb/V/zo3xf8l9kP+rf/M/uF/V/8X91fpv5v/\"\n    \"Jf9yuK+wsGd/sZ0A4P8H2Aa9v44A92NN/UQ1/2/EdvUvb/Ef7Z/\"\n    \"yXw9MPsrv0FgDgNjOAXl2vQ/3f7233t/8f/wSTPcv9+/4dABYin/9MPnXDNDK7wQg6ts/\"\n    \"SQWgHFAJQCHt/4NcP33OGeDTeIYACQbi/57Hf5+lBlD339X/WMhf8793fPun6n/m/7Cf8R/\"\n    \"iLwtE/D3+6+bf4dD8b/KP+NP79/RPtpp/kV8erPL/uyH/jP/o/pF/A/Kv8r8Q/\"\n    \"Yf8pf6kAKzF3/YGEwA7Vvx3DrBNEQD5rfz5YS/u4xPyCPG5CiADlPj7eWfq7/2fYIjPHzlr/\"\n    \"ceNv+lP968T6Z8WIKX/JIC59VMWACP93P9DfoU1/O8UAPGr+rfR+PfwX07/L97bgbV/\"\n    \"1P+dpf+fx1IAvCvmt/qf9b8nf3v4d+r+5+ovxujvz27+5RF/h+faf+SfBID6D/+Z/\"\n    \"cUAtb8OKPUXmvd9+xf+TwYgART3E5j7h/mU/0IGAUX+SgDovzy//0v1j/bfU+/P/\"\n    \"k8VAPIJw35FeeC6P+YMkOu/Wf+7olKAE4CIH/VH/q38fPpLdFfY6v8gu9H8+3gFYLhPBRD9z/\"\n    \"Tfnf/mf4zyPzkA8bfyl/yH/9Z++c86iP90/2AlgBr/nYZ/x6t/A/Xn9m/t/vT0b+p/yI/\"\n    \"8G8+pP9V/yK8DjtP/2En/5Rtb/g8VAKAFyI/kgp0CzPz0AbYGt/50AOtptOzzuLfef+n/\"\n    \"yH9i2P/\"\n    \"IArCDYjBFQPJAyn7uADP9TxHg6t++\"\n    \"5T8nGQD1l4v6dfln8vPDFvTsb9RfD8Sfi385GPUnAXgA4A7gneJ/zAj1FQ/d/w9NfvOf4d9S/\"\n    \"776l6/h367+ffVnq9n/TfUv+ZeBQ/UP83v5B/bbYP5L3b+53/w/j/4p/038rv8X6fmN/\"\n    \"PcUUGan/2/A/Pvr/Yf/gGtAUT9G6y/aO9ACYJMB0vx/HO67CIj+9/4Py78PVf9L/eUl/\"\n    \"9hzGcAm9msIMHd/wgz/W/+7+Q/5l/rLQ35af+k+6n9786/L/0Et/uwMQPVP94/2i/z/\"\n    \"bv7l4n6rP/SP+su2/Mtr+EcCqPp/5H+h5Z/5HwXAzStAZoCHNPAmhvrvNmCO7P9MAGPH0h/\"\n    \"xv6+LP+r/QpLAY9j/JQuApAH4r9qfBSDLv0w+SUDMTwJIDQD72f9RAniw9tvmpX/5cymgl//\"\n    \"n5l/0lweH3r+Kf9r/t7P3a/Z76bfVn+FfRv/e+yvu780fxN8G95H/Vn/I3/LvvR9xX75qfy7/\"\n    \"SAA9+5e1/Lf6U/43+2MDpP/y0n+o3wnA6k8G6MK/p38O6f/1ew//\"\n    \"eQcgf23W1w6gQ078xQHAPap/9D+UH9ABPCrkJYCQPjE5gAwQ1nP7pxju+/\"\n    \"HJ5T331wMbuPNXHkD+6+4PK/l/j82/6H+sEkBA9w/eyfRvqf9f8N/kd+s/HUBX/331D//76g/\"\n    \"+yw97f8a++tNh+6ea/+PdP+xn+kf3nwD/T+ov9+kC4ND86+wFIFKAkfs/+er/\"\n    \"4X7CnJiJj2ULeFr/mzuADuQCqO8fxwxwb2s/pf/Qnj5A5OdLIOF+qn/ZygBOAjZv/g4y/\"\n    \"UvY8i+b3l/qL3+w+Kf0P3/5ry//WfxXVDCG+4DaXx7qj+36/4L2/q5YL/5w+8/\"\n    \"eHwmgav9670fO6K9nfy3/qD/l/2VhvxEcrv5g/liv/t+4++vh3/DfYTX/9lX7w//\"\n    \"D7Z8DgPz25+//HZb6T/OfoHO4BAjpE2H+2LH/pwG4n1d+Ar7/W3D7H/\"\n    \"W3eQmQwV82APRjkf9Cin8dUd/I+380/7sM2Nd/MRE/5zQBSAJI6AHAe1F/\"\n    \"9L+rf7DZH+mXC7r567s/ecQ/d/+L/N0A/Gg7rf4y+zsO//+o7v9Pnwz/\"\n    \"VAX0e38y+S35l8N+GXd/sWi/wPQf9T/v/1T7T/kvO3YAjuz+OdiK/Ymof3oAs1/\"\n    \"RN4DY7TUgW88AbqaAO+394X8lgMh/qD/0Z/nnCobaf1IA9DecA6L+dfufi7+4bPjvyX9f/\"\n    \"k3hz/CviJ+gI1D5KwDUH/2H++H/u2n/F/m7/m/1D/cVQI3+lvzLKf+L/D37R/29+39Uf/se/k/\"\n    \"nf37vtyZ/5/LfmCfyn9jv/uAQH1s4v/8X3jP4c+jRX4ybgCL+PIr9KwNU53/P6r/4v8r/yL/\"\n    \"4nijQ/bMGkATA5E8G91X/6yztL/13KpjxH63/+QIA7vf0n83f7v3jgOG/8e7ov2D+b/\"\n    \"WP+Ef6FT7/Gf2Xl/rT+zMAbPWn+mf3Rz7sfz4ByC3/lQAi/uvqvxuAPfuTn1IA8g9a/\"\n    \"mkBFl4a/5EHoH5irEDrL38L9q8FgHjN/+oS0M/8whyex71O/uF/Yeb/\"\n    \"Co+KCkN8rv7XGhAZwMwPEH//KO5H/hVG/J0BNALUaepT/tt78Q/1j/\"\n    \"XWvwOlvwyY+7P557DVX54T4iP/59W/6v179h+vm79e/A08+Uvpr1NX/6c3/4RS/9782d1/\"\n    \"bf45HC7/YlAf/p92AC5P6P6/5T8hpl8zBTCG/zZSwKY+v2oJYI3+/Czu39/W33oBqGDqp/\"\n    \"V3BSBH/b34zw6Q2S+/TJ75X+Rflpt/RQM8OAPo4QwQEygA9APyJ3Txz/\"\n    \"If7D+mAMiP+lcGcBj2/1SLP3v4/4Oc2Z/5L3/u6r+G/2CLv2xN/35b/Pfdf731r1Nf/\"\n    \"fj6oP81/GcAcOv+jxSgKIf8Apu/Nf9PAdDv/+ZRCaC2f3sH2Jah/\"\n    \"yDlv9AvADZG+PcIUJhn4U4v/o4LwKQAkX9aAKp+Xv8xRPvH3fpTAYj70/u3+nvsb4f/\"\n    \"vv9b3K8MICR27Y/8f9rMjzX32fq3+GvyF/7L9uifT/70a3+993Pe/FnyH5Mfun+zf6Z/\"\n    \"MeEw+y/1l5n75xf/YX+r//PTP7kP6p/Q1b+Jz6n6v7mP3bz/fwP1p/6XkQHkDTr/\"\n    \"hNghByD9963+LAA0HuWzBkz3z/DP0QlARgKw7kf7HUz+BMR/1P/B4TLj2v0J+x1W/U/\"\n    \"Y5I8f1P+8+2Nb2h/9p/rfq/+z+Gvxl5MAIv6dAQaU/3X5Vx/9kpv8OkZ/9Evsv7w++vXy7L/\"\n    \"lP+TvF38P6m+rV/9q/FctQJFfcV0AzvR/uH9KACwA+yjsKeBY/Aa4/4fzfmy8Avqj/0V+++M/\"\n    \"EfVfVwB+fozJA3MfM37JMfFzRv1F/6/C/+usBPC01b/ln96/uA9Cfrr/cB+w9qMzNmD8h/\"\n    \"h3+Z/6v1Z/evYnC6L8fffPF/+K/1H/vfp3Wv0P+ur/9t0/o/+l/iX/qwC49epPDvzHjtP/\"\n    \"QuQfW8yP8r/hiPo3kP955gf6n/C66O8BYJGfNYDkABm3f3n4p4NvAGRCVn8t/xn/T/\"\n    \"1f7f+oP+KfZ83+n3RG9yci/qg/BUCrv+3U/P8Uk+tE/Qde/wv5Xf3Pi//wv8v/82t/3xyb/\"\n    \"yDqH7T8W/27+t/qL6f+P13968RY/JPt3v8o/03+w/6/mV/kj8soALj7rwFg1/\"\n    \"+xt+ZMH+DjKK8s0OAzoPY8FV6d+pv/hVn8CfW/NPFX94/\"\n    \"6yz9emOmfnCuAxfw8vqL7F6YBiM2DCkAP1L+r/7z0C/Ud++of+e/Z/5Z/Ub/U//\"\n    \"DJv9r7vdX8yyD/bv51kP+++ZN762fpf/b+JwEU+Y1u/uWt/vv6/4qw/5QAWP1b1B8fA5v/p/W/\"\n    \"BuzPAsAeAZj5qL9CfjT29i/CL+cO4FXRfw8Aug74aMm/wB2gmK8oo+r/nhHgUn7//mWobxf5p/\"\n    \"YPjot/Txf55SF/DQBiJf6QPy7rF3/43H/c8t83fwE3/y9/9YPbP1vN/r71Wc3/cfGfDGD+0/\"\n    \"v7wP/U/73805u/e/M/dqv592nyxy+TL1T9Dxj+O8xHwEgB5w+AzADAJ8U/\"\n    \"K4DynP0t8MZeBP73OwCvTP1PA4BH2n8xPheA0wDMUcxb/0kACfPmz8z+FGUg7M/lv9t+B8FxX/\"\n    \"492UR41wBPz07+h/qM/mVyUMV/sX9u/Wrx768M/jL71/PmB3/lfPKr3vth8w/17+Efi7+hvgP/\"\n    \"7mPLP+SH+8flfwP+7wQgR/1vvfibxwD2Hz79183/8J8MsD8AmHCY/3P9Z/\"\n    \"F3COv3BmCseb9LAD7+EXuFvX/xvyeADqH+zP/+qfodhev2P8gzb/0HMF/qDx5kz/X+8jX+p/5/\"\n    \"enrqy/+W/7A/Dlr8e+2fFJAMAP/l/ySBuv4jAWz1V6D+B/BfqKs/hn9gX/3L6j9+TO9fl/\"\n    \"8hvz3yT/vf6m8EPf07bP4ltPyTAWLyvvqL54T8/fHfzgE0Aan/\"\n    \"B1MIsAPoIzuuAdupAJgA6Lwq+p8GgGG/zE8yALs/H9v0bLD2F0sGkJn3OmF/kAQQ8g//HZ9C/\"\n    \"sst/v3e/zAfNPP7k/8yhn/s/Uf9W/6Dn21wf4/+V/n/43rvF+738K/Vn8V/y3/9v5++/B/1r/\"\n    \"6f3f+dAFB/MkCP/vvyv9TfluADzi//wX96/81/wRHq42Z9eK+43wAI+W23vga8u3/6/1eq/\"\n    \"nwCGO4n6qD+igIbwHGZAOuzAShLBnCQm/mBnvT/Ef6YeM/\"\n    \"0n9FfTK7yv+7+bQ5Dfex098flHwmAT/5N+w9m+O8I/2XH5l+nmn+jvvl1+6M//c2/\"\n    \"SQCIf7f+fPUH/kN+n8P0j/l/L//Wu/+1AHgQ/+P1Hx8BQf9rBlg5QCfGPwEKFvvlhOZ/\"\n    \"ToxLgPz56uh/HAAkA1w+S4BR/kQKgNCeGPIz+R/xp/JH/YFJbzceov1Rf0//4D7Ie/\"\n    \"8A6vfiT4/+Kf9H/RUi/HYB9Zfv2l8+4h9D/bv6p/Q/fvH39vf+Gf0BEgCrv63+9S8/UP+W/\"\n    \"+79+/aP1l+h3vxzlGNGtQBGFQD0AF37o/7yuQDYbwCOy15OAIHVH/H3z9dF/+F/lwC0ACP/\"\n    \"OSF/gh4fK3YGmMekgV8u4/Jfyu+1H5ue6L+M1t9u9j/JWv9384/8g/riv9/8Qf0j/XIW///\"\n    \"66y+u/mTz4t/i/9r7B6h/yX/x36j6vxIAKaDk//jVz63/C4v9Pki/o+2l734mYsD8h/rr5d/\"\n    \"xsc4BB/XvEcCcfgsolOdXY8m/wmtW/xoAQH1s5v+mPhngGv3bF/\"\n    \"e9+jvaL0f9xXuNAGJgEoCM6Z95P6X/U8s/3N/iX6V/b/7vl/5T/Zv98Wi/oogPznv/e/gvG/\"\n    \"Dav5hf7K+3fhn+1+wvtb/5Pyjy6zD7692/ob6nf3C/pv8vyH+v/ssPo7/T5X9cUIT7WHyB1X8/\"\n    \"Q3xGgKP6/sX+r+JR/x0i/yH/a1X/8wYAKeAjR8Tf5Lc7XHHJv1t/\"\n    \"5P8X5N8nLQDqP8SX6ySK+dP6W/wdzt/9+Oxw+99r/9T/3P1N7z/VfzDaTwvQCYDivz/\"\n    \"6qXPztX/UX3b7H36g/qDa/0kA8L/1f33z99D/9+7P4bs/sTig/o8vlPoD7v+K/\"\n    \"I5xW6RfvhDyJ0B9xwYrwLEJSQOvif5/\"\n    \"s3f+OrtOQRTvhBsQQqLTabVajcQVcAESUSgUCgmtRKJQikaCSqNwCTrRuRrLLDs/Y33rfY//\"\n    \"xXlmZs/7ndOvWTOzZ88TBQD3fqMk/4hvACcMgPwIAG9hVP+jktMCkKf9Z+yT+0t/\"\n    \"GwEQ+reUVz9R/Y/WAMDCD0Z/fnYJMDd/46j9+dpXn/rv3/sA/In/nPzJwd9kfyb/evOfyd8c/\"\n    \"A/6T/Y3+uPlL36j/7njysM/2v9zjPxoAUiD/lfqz/U//\"\n    \"G9nJQDUS8D8FIDdY8b+dQSY6p8SADH40QN9wO+Zv3FWyF8iZ+QP7sn+3QKg8lftL3UDQBa1/\"\n    \"+3qvz773QkAxf/PVP+CfhYAJv+V/pfJ31j7IXuw+8fkP/QfO3+z/wf759qfnP1p3/\"\n    \"yJ4d9gf52bw//1wz9j0f23h/4jBoD8jAAoAWDAz1fApNB/mwBg/i8eA8/P4wV/\"\n    \"CgDof36P2pQBMPJvs44w/rdnABQAuPpz+X/o33W/VEL+P6g/CcCw/2A/h/+i9z96t/\"\n    \"jPsV9ygMH/nvwB+zojefPXn/2Afjn5O+zP5f/a+Av626u/JH8e/gf2kQG/zqNu/\"\n    \"Y8IINPZ2KcCSGH2zz/x/KdeAIL/tQgYedZnhQDYP2XD/vz1uNX+FACF/\"\n    \"kf0k+LVfxJHgYV79Ptk/9FVAAz1r8t/o1/29mn+D/sD/uz9D/iz+pete/9xO/\"\n    \"1fr35+Nv879wf/WfwfAfx95a/BXyZ/Rwf8ZetnlP+F/Fn7ke/+KP7BPvyf93/gX9L2fvQP/\"\n    \"6Ib+3KjUfnPr5UGYHn+Z/o3/mUrABzlFvBGBZC7QB+72t8FQEB/dwFsFs/9j44d7n8jRwDF/\"\n    \"aPQv+z7z743/a8CYM0AfDN6AoCSACF+TrK/nKXf++3b/8z9mfwX8GWOAG7+wf45+jfcf+tj/\"\n    \"8n+lj32k+wvSfrfn/uTRe8/+b9t/YH/8+lfYH/y/mz/gf6s/30eLP5laISAYP8y/\"\n    \"UMWMEIAYA8Ydpv/bdbHMvmnADDm/bvRL00x62fznwRgWn/Q/7r9k6P1pzM64Ndx6W8z+Uf/\"\n    \"b7P/3eqfzF8+C4AXxn7+bop/gV+GEAOC/HPlX1b/g/5g//bJr+z+OQDExt+R3fofF/Rf1n6+Y/\"\n    \"3jJ7+T/v2z137byRL8dP/tWvs/pK0AJQqg5wIQ5Fug/3gF0PeBHX1c2Z8EIMZ/5A77b+Qf/\"\n    \"1D6P35yf8/9yHxE/ZP3H/a3o/d/LgC4+Tf6ZW+PgXzAf5f+g/3X1g8JzT+p5Ff2h/7p/\"\n    \"JP+l6s/ybgvovovkz9x9y/j8g/6N//n1P9RAkDQfxb/SO79yfIfDfK3PkT/sH+CP6v/\"\n    \"OfawfwB/vwUmAAD86P/PQWoQoAUg/zjCf78BeAOTOgBkBBi3oB/8PxXA4H/c4N/oJwI4AIB+g/\"\n    \"9XPQ7y3/U/DcB3x/bDvxz8293/6P79LHXtP8n/lpz8+2p8zP23/L80//naN0v/JKv1X9t/5P/\"\n    \"I/cUf+4O/+dmvEgFi/q8W/zI0Fv9B/3kDcJ/96QHaEIQCAO2fAjnqn8cx+a8jAFboP7P/Qv/\"\n    \"jpvW/2J/h/xMEkIkAYzP67/zfnT/9SOTAfvD/AH9cYf/A/v7ch1N/jLUfvvqXUfuT+svR+wvw/\"\n    \"9mrfwZ/kUP/ufLzcL8M6MuAvn65/Kf7dySxH7m/TKd+9FuS+NcB+vbZ/\"\n    \"ZMhZAFlAyjkjw77S5EN+L4ChL4fP+Mfx85/FgDmfB+z/yZ/Lv6IAD4EAFp/\"\n    \"KgFO8T8O3MP9Vonf/Sz69/Sv0Z9j/+T+/llyd+Ov+R9x79/g/3mTP3ICAOQP+9P9C/43+Kn/K/\"\n    \"4ti/732o+c+2+T/xv8Ovm536wAkv5z8Qf1/2ip/6Ul/x8l/\"\n    \"99lwL2PgKML+ysM7HKgNwAk6ysgj2fynwmAwS8b7Cf7k/uPRfo/yD8JwHr+Jyn8L+wL/\"\n    \"H74N2r6Hx2HCOw+RIB19ZdTf43+4f8Xjrr63w//Hq79d/F/0B9bf6Sl+Q/6mfuPVz+s/cvk3y/\"\n    \"/ys1fBIBRIkDHfo7+WnLtr09+9qO9/\"\n    \"i+zP2UFSA8Ati374q8vAMhvAsse39YfCcACv3VcJABSCeAfh3w/NoN/MtS41wnwr/\"\n    \"Hfb87gjz3YJ//f4PcpxX988Cvn/nRO7f+rGfzjY/QX8uf2Lz/4IUOE/1vNf+g/3/3n7R/f+/\"\n    \"naZxf/MuSk/+A/1n7cDwG7AdC//JctgFsXgFkDPI093SoAH+mx1f6LIsBOB0vhAvDxhn/\"\n    \"tAIB9xNC3jlvP/kf34g9Hgu/N/99/xvS/wa8zyJdx82f6R9fOHyJAvPur7J/JP/Qv/pcJ/gf/\"\n    \"C/w6Qf9l8m9n/7nxX0ru35/9wf7jSP9hf+p/sM/arwF+e/hLCMjqPwJAoN+w54/o/OkX+q/\"\n    \"sb9/7fzUDSPonCNitCEAjIPuA2GMPfxKAQb1/0V3++zfonwxA+lsAMP7N/4N7BQC5kd9FgG/\"\n    \"0Y/yPjhsx8vu73/7qf1f/CAGAyl82ohCg2l+68v8jufQL8i9f+8vv/SA/jFH8J/tH8y8iQCf//\"\n    \"fC3NP//0AHs7C8J/pfm4q8xuRj9g/3HJfj3BGCb//ffsggCBn/wf6P+9+Ml4GMN/5UArNw/\"\n    \"hKt/2B9R3T8K9f+qY6f6R6B/6TdCv6//3Pgz/UvOL/s+kcj9y81/5v4yg1/8fxr/Iv8BP73/\"\n    \"rP55898G/6Wb/GVc/c+JlV+ywP+RXPjP1T/gl2b3HzH0e/O/s781y3/rFhKAB3f/\"\n    \"WtkA0Onf1jeAJvL5ORMAdP/iy6Ab+hf8dwKwOgA5/cMlQN78EwSMfPsp/n2IAYhm/uYI+u7/w/\"\n    \"6yuvHX4OeTH3df/SNGP/w/+D8RoPf+Y+Mv9H/r1T+yl360D/7k7E82//ODP0iM/3xUH//m7G/\"\n    \"iP8mfD/+NGfl99x9v/5j9Wcs/YvxvTgoBIIMA0M8OACVAewT82MOfBMCob0O/\"\n    \"ZP462fsX9M3+M/1n6refADCKQP0e/QX4fvZzWv+n+rcF/Qv8c5L+CQDxuS+u/\"\n    \"p3660zmLxvp9E/vr7A/1T+9/2j97bH/nPxP+gf9sP9I9v7nB3mkT/7E2m+y/5j9uf/hj1Gkz/\"\n    \"4QBUYJAOOhfptxv/Z/xfw/ijuyI4DsYn9eARj2OltDYP/xUfy/JW/iP/a92V/K459c/\"\n    \"snK3xGu/vvXfs7J0V+wPw75LtjfzK+j7J/Rn771q3/se2/89g9C9i/mp/gH/\"\n    \"LJ19Rf4jwhQlv7RAGTur371s6/+4PIfyav/rP6N/Ez/JTQAWwQouf8KA4A/6wD4H+xnHLjY/\"\n    \"8EKYAUAxv9AvvVcAWzkzzH42fo7xb9U2PcJ/p8G4NT/p/dv59F/+P/NcZLd+1/Jf5B/rPuG/\"\n    \"Qf8o8b/J7LF/mPgH/BT/efXvmSL/evkP/Sf7A/6BX4E8C/2D/yz+Qf6L8/+qf5lCX6rLPFP+s/\"\n    \"kn80uAkBf/iG16Rz2jxvAGP4dl+CXxQwAP1kBXOxPArAaACC/vf3Lm3/\"\n    \"76fcZ97JXZXIyqY3Sn/L/1wJggD8nrv56ASCVteLfLt/90Pr3i1/pg4/\"\n    \"+6t1fzv1F6z+Xfj648w9Z/E8AgP97AWDo2+KTf5v9y+R/2f0V138+5fJ/bf6tH//\"\n    \"tHcBIADIC0PvPAPAsLq4By0XABf8jT5r7B/doAB/NACB1E0Bi3ncAmHu/\"\n    \"OZEBDPr97tcRAFnsb93kD/pz33//0r/sgF+wdwCQmPzHD/Y3+5evfZn+++RfH/yXxdgvQv1/n/\"\n    \"6lyEPg3/Svs/i/jP7cYf9xgfxx1vr2/0Horx1gLv/7+C/ozynAOTKATwUQGcDF/\"\n    \"lkB7M2fctkBpAeYMo/+TPw6o0L/5P8OAggpwKBf4B/5mMwf/BMDlsTSLz721Vd+B/3rrGv/\"\n    \"WPhbN/6u0l+2mv9/TP9lZeVXDv6N9ACQIWAt/YpP/v1qtQCQIfXtX67+kG7pX/3AEQEK/\"\n    \"YP9kgNI6P8H+PMKYCwmAa7kv1QAoF+GxNPfwL1MKj+oJ/2XcvtHABjiPz8Dexnsz7t/\"\n    \"HaZ+cuUvEQDwA/3e+ntxSn+/+5dQ/FP+k/5v7h9H+l+X/iT6IwAE/RMBEv0yS9n5R/bfB/\"\n    \"+Y+7m994vLv5z9scbwv33/8mfkAED//KU/sv7fT3/u0X9m/\"\n    \"jrr50r+79wBLM3Bf9nRfPrzaxgw+PkZ9Mvk9Gfc/Mvm+o8QMK3/o5D/2CL/\"\n    \"YP9y+0cA2O9+1t3flr30aw3+WTb2dYL+lwD+wH88/LlB/8n+BACa/3v0L5v/\"\n    \"sfcn5SUU5I9F+78hP6v/O9M/509ZzwAC/DkHFAmAz8X+SK0AwP2cVv8H/Vv98k9/\"\n    \"uPHvmz856bgUXwC4858FwOr8bfbXOdqf/ObKH1L/F+fqz9TPzT9X/+1jn0ib/\"\n    \"Pn8XvPPAvy98xdp6LfswZ/BP9iPrV8D/sC+HNl/0L9dJv9gP4X2/\"\n    \"63HvwwAhAzw7YL+QyUlAlhjAkDmc8H/\"\n    \"dgDYBUCPAbISBUz9sL+Yf7Af9D86r37mT4JArP3icz+3v/\"\n    \"ghLQVA1P6jZ+6f3t9IfuwzB39Z+8HdX537D/rv2f+94r/f/b83TgL9Q/7J/lH+x0d/HQL6l/\"\n    \"8i+R+3Zv9iAdAkAfH6b5z/dYv97ZH5u0UBjNz/Yv+7FQBLvxCAP66++vHkv87gf9zM/Zz6v7T/\"\n    \"BvuU/uOsXPzLlkD/5c0PqX9u/H/xvPqfADBz/9L+6r+zvyXYX341/5Fk/57/l6X/SPT+rUfuz/\"\n    \"3OGW23f7X3r5Pc35/+Hm0ZAKV//wgo43/cBC5J8rcCfP3rgv+9AICGrIe/gB/\"\n    \"ynwBwZn5m9O9M/TgS5NW/ldv/6P/Pzp9k/1vr/ln6l+zPs1+JK4Do/S/+R6LzH+i/f/vXm3+J/\"\n    \"p/q5R+yx/5hf/L/IH9ZPPubH0Z/CACt/9eXf43l/J/MroF//gr+zz1gRIBe/McFwOjF/\"\n    \"kgPAOT99kAf/Cf4daRm/XNenWMN5h83auAji/31Q/cf8eCfNOifR3+1/P+ZjZ9c/ulQ+0fyn/\"\n    \"Sfj377ox+gL5fFf9L//eo/s3/bbv1HAAD9EQTuX/1l7x/w9+4f4M/\"\n    \"6nxVgLAArMYAnwEfiEdCozciP2n/+74L/DXly0H+X/TP/d/dv9C0CgEzHKkPOu/9J/\"\n    \"+V2AcDrn9X521v/+toP2B8h+Zc59Tf5J/9n6z/X/cvi5j/Kf5b+ZfqfyX9e/QP/8sE/\"\n    \"5KT+45A++0PuL5uzu3/jHqoA7rF/6f/xADAiwCj0n9n/DgHJ/rkEgLe/dnsa+IJ/lydL63/\"\n    \"Tvz2lv72v/QgA0tP4Hxf0P9AfW+Bn7/ekAFz+0fyX9Nb/QN8G++/u34uM/\"\n    \"sSzvyOPvvNHdqf1x86vnPx5cPanb/1Y/J/5/41nv8z+CPx2B/vZ/\"\n    \"cvMH0UiAPTpn7L+9xb7g3vaf3IxA9SjADbuSv6RngF8b4kg0N79Gfywv2SKfymDPxEBKP3p/\"\n    \"gv8lP5vSk3/+e63rPwul3/71Y8Ol/8D/mj+DfYfcfBPZ8mu/Pe6fwT+Xzt/kPzih06p/p39R/\"\n    \"Fv8CPJ/vndH979jZaX//Ign85/jgDu0r8vAPexIrfy/6c77HHR+n/mgj9SXwJ8//KnA+JPbxX/\"\n    \"FP52xr6O/NT+skj9T+9vzODP9P+hu79Y+iVrvf8++kf5j/Dw3+Df1b+xL2/Jlb93vvfB6F+m/\"\n    \"7n1r8A/AwCf+yL7t/V3P+fqH0noSy1G/CsL+oX+V/e/CA2A2P5p53PjEyDQPwMAt+l/\"\n    \"bwKSXsn/IwYAIf/lT1/+Psg/7/4Nfpp/v3qGf6RvlQ7gpv/s/on7rREAwH+9+V/f+oP/QX8M/\"\n    \"oP/9uynkT/sn/n/H8Gf+I/Bn1vZv2xJWfkH/oP9c/FXjv/A/zH3ny9/c/\"\n    \"w3cK9jv6GfEwCj0QOIQeB+/0fzb6lN52L/R88ATgLAu58EP9gX7Gn/8wPunQLA/\"\n    \"1LAP47kX7b3fcbVX+z82c/+ZMgDrX8CANR/8+Z/bfzrX/vY937s/IiF3w8X/3er/771gwBw6/\"\n    \"IPif5ffPZ3NALAaGL/cP/56QMA40J4/Fd6AHwCmAjQ6d9eukKAI8AF/0fOAAb9yf5zELL/\"\n    \"CQFEAHP/0ZAVABD4f4402/9W2D9m/uPqf6f/ZP5r5e/u/Ukr/a8AgEQEWPf+gL9k/0hu/\"\n    \"crZH1ls/VjNP/30pd9zovs/2D+/efsH/4cQBFoPkOd/hfytsroDPF4Bp8D+8RbI6f8F/\"\n    \"0cKAE8pBUj8g32438jXOeW/we/ZH+vYBn6C/2Df0JeLV/8s/dn0T/rfBv/AvnQctT/SN34F/\"\n    \"ZdH/7Hw+y773x/8y52fWf4z+IsI8Hvpd7z7jeYflX/56t9a/RdPf/rrn1H/3Hr/a+D3K4DN/\"\n    \"jKkPwSm/3/V/o8sTzyhAAD4V+2fMUAmtZEACP+19EdlrPzbwz9T/JMAFPo/\"\n    \"4I+df+3yTwL9S8bF136+3aN/sfGLu39kAkBnf+SvXP0n/i1s/UQq++fsbyb/\"\n    \"VAD5zd9+93e0TABZ7XQ29HGwfykBFvT1094AMw/EQ+CL/f/UJOBa+9G6/2Cfzp8c5F+e/kH/\"\n    \"pfVXsW/Njb/5tZ/+7Meyp/51QL/sLyz8Lewfzf92959Lf5F49nuH/f3JX8CfD/\"\n    \"8H+xkBqP3tkv17ALDLvf97/q8M/8sD/kL+47L6zwiw9WL/\"\n    \"vxcAHnr2z9SPVUbrjysA7v65+qP7N27d/e0IINXPEsAv9DP4X7/1HRFAthIA2D8Gfy07+Yf/\"\n    \"AT8BYNO/bKTu/JG14j8nf/flXzT/ytf+CQAjWQDE499Y/EUEGMvHP2jyP4FgqD/Kf/405/\"\n    \"vY4u1/VACl90ceQPJ/sf+flCep/Wn9ZwAw8N0AYPTH0LdKcvYX6OfOP/gf+o/mf5v8i+w/\"\n    \"uH8kd34h+81/0j/Fv6zM/YP+2PqV3/qW9eyfyd/+ue8tH40D/HQAevXfv/kTO/\"\n    \"9r+j+ubP4s9P80xX/ZAEbyzwUgXhbg17HJGf1X5//vpQB5858pwIjBf2Rgbw/yAb8d4D/\"\n    \"UP97gN/3n8E8f/Avyr+wvLW/+pDH4f8BPBAj234P/gH+6f735t8HP5I8U/A/7I2B/3OgIX/\"\n    \"sfHw//cu2/XPnqf8z+wP5g3x70N/43/Y8eWdRPLMAtWQtAxlehCSB3sf/fCAB99m9n/\"\n    \"vzh8h99iP2tW4gBnvrVT6b+xr5czP21nR/wP1O/A37m/vrGv3j1l8V/X/id3b+k/9z4nXd/\"\n    \"fO8/W/8H/Jn9V/rvL//N/Iv+M/uPt/9ogp/K37+Je05+AKzvAGb8p68AGn/V/n//IpDBn/\"\n    \"mNV7/WFONftsjfB13gF+Kp/of7ZYCfb37k6A/Zf+/8/2qQv9xDN3+ysvKP3n8u/\"\n    \"dCR1o3f1P7Z+/spmv8DftlK/kF/2fyxmn932X9OkH/\"\n    \"96KfO0f7Zf1vK3eEfqTx63I1vgNy9A7T95q7k/++kAJn8g/28+xPiQT6r/yL9HyH7h/yp/\"\n    \"pdE6W/0L/ZHXof+YX+9+2Pl5yhzf0di7KcV/+A/wB/s/2V87m/YXyfwX9lfyrO//fQn6X/\"\n    \"LJv91+bf5314yPuv/LADGegIA+a/L/2gAAn1bCuwP95crQALAcVfy//dSAJh/\"\n    \"pf92Y8Y+cmJAyC32tz86lrV/2flhn70/hOLf0JeLdb/f2sH+61u/OwC4+Qf7U/6PQv757Jfq/\"\n    \"6f27mfhXyorl/82Saz9g//r1m+dP8H+7fHPHgHub3/q9M/OAMa1+38JCUBnf36v2v/vpgB87h/\"\n    \"qHz8q9JfEP57/cfOHIO79vb3u/fPZTxv8t4J9K9v+B/zifwvYz8GfQv+k/8H+sv3o/\"\n    \"+7Kr5vpf3b/bpX/I3v4d7P/o6/9t0L/wf460QAkASjsb/IfC+zby0bh/\"\n    \"w39EXJ+nb4DmD7Axf7/ZASA+u3RTABo/7Xmn3VjfzxaWv/Z+Qf/qwAQ8l8/n/sQ+l+E/\"\n    \"WU59ptf+5Ft+gf90D/k31d+yvrk3578kZH+I79DPwFAoB+fSz938R/\"\n    \"Vf979v1TWfkYESFnoL+P/JQAQAsB/K/+t49YcQG39213s/48EgCefyplfwH9+9uAvlX+nf5L/\"\n    \"AT+4b+wfgz+B/9dla+7/ZAAWbv6S/43+PftXJ/8QRv8BP/BfxX/Q/0K/AwDNP8b+52T5D/\"\n    \"HH5V9d+b3pX5b0nw9/2PwRwN8TAAn92wHg6ez/jTzf63/YvwkB4GL/fykCyNBo/fv47k9299W/\"\n    \"wX9Gf6j/W+3fs/9s/gv6NpL/s/Yr6D9X/uXGvy/ic19B/rv/R/c/4C/wb/on/bfQAIT9c/a/\"\n    \"sv+Qfxn+kS7pk3+M/0r+xPvfpP9xLQLs0n9cZP+m/dwBUicBL/j/cxHAiT/kPxL1P+/\"\n    \"+Gv1LEvwOAL4AKOwfpT/onyf/VP/O/nVUADj3H+Hqj+p/7fuk9cfkf7B/fOs/8Z8rfwX/\"\n    \"hX9Zfu6L5J+7v1Hon/Qf7Ef+L0v2z+x/XLB/DQB0ADL7v7n9D5XIp2z2x4N8EgAmgMcXuVp/\"\n    \"yD8cAeR0bnUAawSA+ucgb9rx4j+iwGL/GPv/QIebv+9syKZ/gX8sHv1B/qT/Qf+ydvX/ZXn3B/\"\n    \"/v5D/v/mTZ/JOWhz/58G8X/+OsOwBU/s+XP3MY/g/oe/Xfsb77d0yu9v8JAeMRwB/Tfw35F/\"\n    \"sj/3gVYPCD+teC/EE+ASAX/mbrX240oJ/0D/jz4k8m4j+9/xc39qX1U78ysB/\"\n    \"oL69+VvVfPvb7o3Sn/x7738k//J/v/rP2H03wZ/Ef1//U/gAfRWLr95wt+eXPHgCg/\"\n    \"74CDPbP+R851n/WF0CWi/2Rf74P0Ir/++yf9H/kwH8cEvSf6f+ouX+kD/7x6g/2z+o/\"\n    \"I8C6+7ca/I4Acfk34J+z2N/o15H8+NNe+gX6I/0vez/azk8ZG//b4q9xef03LvCPym68/\"\n    \"tHJsX8ZmsjHpHc2gO7KXycF7F/sj/zTt4FGPgbyoX+M4d+k/zPt560fB/tvN/oH/\"\n    \"7+eD3Plz+s0/7j6t+TdH4M/+bmPNvgjC/YH/HH1F3O/U/tn9g/7B/3H0r8c/Mvan+7fw8P/C/\"\n    \"sEgL74P6EvBful94cb6u8DwGi+/6X2H3ukAHDB/98QkgDwPwdZm7+Qdu3/qlC/p/5i9mdf/\"\n    \"Ov0j/1M6v/r6exvjdrfetAvW71/wG9F9tS/dWSzP9W/zOl/4J/qH/Bn+s+b/\"\n    \"9El8fJnNMFvze5fbv30jy0GAJ7Dldt/Fv9zCxDQj/3f5ct/jxwArou/f0/\"\n    \"oBUqy79dHf8qr37cVAEao/R/41v+HOu/KnQCw0/9D/ST/tP4H+gSAePRbdv7ty7/d/Lv5rf9k/\"\n    \"770A/CT/lfy993fYv/s/I9F9Z+L/0IY/dF59Lf/df0Haz8S+L0GMOdzE0gMiPn/\"\n    \"EwUu9v+vhSQgQ8Crdiz+Kfi3CPuvvin1d75usT+jPx/+WvrH4N8KAXP1Z5mpPzmS/\"\n    \"7E++Jfsf+/qD+xbIwD86LOaf7n0QzrkPzbYfzAArPQ/pn9Af1b/4F9aFn8k+LMBkFf/9pLy/\"\n    \"He8zD8b+3YytD8BjhHAK/n/H4UQgFD+j4as3p9Qj5j9IX/Qn1/\"\n    \"7UAxw8a8D9m986wfh5k+2AgCT/3B/PPvNwZ91918H/26v/IT869i/\"\n    \"wZ+9f7Bv8M+psz9r7Tfgv3n5P+ryf9xu/M2fef2fGwD7zT82yvzPRn4+AfbPBf//QwgBigGk/\"\n    \"v4jb/+yCfC21fx/4M+6b9DPzq8PB//B/jI6f/KEANZ+RfuP6j9Wfo5bk/+g/87knzTwT/\"\n    \"qfd39k/3n5R/8/03/IP7/2HeV/7P2Xz0/+RvqP1N2fO/\"\n    \"kf6yGA9X9LEv12OwL80t754zYRBlEcUVhCoohcpeBAnMLcIBUHiLStKzrTIeg4gX0gRECiQiBB\"\n    \"FGX2G0c/TV6ed/PH0Rb7vj/2Bd68mdn5Zt4Q/rcVmNV/KsANQP1F/s9iN/GH+blC8OM6885/\"\n    \"hv922A/6D/lJ/YcFAGbUvzT9iK25f1L/3vuvb/7VAdBpn1gAV/gD+/H8feovDuovA//\"\n    \"do188ANf2NzbAASjpPzf8i5Uw/j/kz21zgFiA2DP9p4BFtQGxFMr+XGEAUv8Z9eUe/oQHID1/\"\n    \"Uv9tyy8q/2rsf7ju33r/n8rXv3Hef2zx/lF/8/VPo/+S/ZM3v5L7z1PIr6W/\"\n    \"PPxT8uvoT+8BIP2+BDi3pv91BBDXab/w+zEBhAAz/SeC3gbU/F9Fev2x96vJ/g32if+BUb/\"\n    \"m0Z9+98cCgFL8ty0oj/7rq1+R/3G5P1C8f3p+CPchv3If6jv9h/wYgFwDz/\"\n    \"4Rf0IAnfsRl839x436u94/Qn6bAkT8G/cJAWoGAAMw039C6PMBd2t/\"\n    \"vyv72w+5v0H1D2i77wIsAD2/7iK/JP8Rf439te7Pd/yUwr9M/t98+afjV1F//\"\n    \"fY3kPuPJew3fT8A6q+v/qC+UX9X+htXHjf3O4/t/eOGAGr2r2QAGv/\"\n    \"n2H+iWGAFvrbLdPzlyz+DPpT8puxfRv2o+NuG/9uDdb+m7N+Qv7b7VPUX+Yf7vPqR1J+d9atP/\"\n    \"gIq/jb4V/XPmT/m5e9Q369CfoXTfgwAFUB2+vepjgCJPcf+E0ezAq/Qf9Pwm7ofafjDZ38/\"\n    \"7sOP+tbCX+R/C2A/wb+ov+32X/Uf+de6v37h/mvHr0OlP9Lu9zZo+KsjvwDc9y3/fN9PAaW/\"\n    \"ru8vcQDRv2Bw+F+J/WsL8Jn+k8ci7EBvCNbr9ee1Yrda7Va79vMYfMwTyI8N/\"\n    \"Wuit82v+FIaiW/6q9u823Txt0u8jxOFBHnOu/\"\n    \"MP3a2ool1hTOKMwTYQV+BnCy3i9wi4uIhzPPx4anzPE9dRMdN/uniZWC5P9vj2fDh5BH4/\"\n    \"EMt74d94XI7F1SD+j8OfQN6j8fee+PUUeP3iWXANRFpV2xIxX6cAAAAASUVORK5CYII=\";\n\n}  // namespace example\n}  // namespace clay\n\n#endif  // CLAY_EXAMPLE_GLFW_CLAY_LOGO_H_\n"
  },
  {
    "path": "clay/example/glfw/custom_task_runner.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/example/glfw/custom_task_runner.h\"\n\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nnamespace example {\n\nCustomTaskRunner::CustomTaskRunner(DispatchTable table,\n                                   size_t embedder_identifier)\n    : TaskRunner(nullptr /* loop implementation*/),\n      embedder_identifier_(embedder_identifier),\n      dispatch_table_(std::move(table)),\n      placeholder_id_(\n          fml::MessageLoopTaskQueues::GetInstance()->CreateTaskQueue()) {\n  FML_DCHECK(dispatch_table_.post_task_callback);\n  FML_DCHECK(dispatch_table_.runs_task_on_current_thread_callback);\n}\n\nCustomTaskRunner::~CustomTaskRunner() = default;\n\nsize_t CustomTaskRunner::GetEmbedderIdentifier() const {\n  return embedder_identifier_;\n}\n\nvoid CustomTaskRunner::PostTask(lynx::base::closure task) {\n  PostTaskForTime(std::move(task), fml::TimePoint::Now());\n}\n\nvoid CustomTaskRunner::PostTaskForTime(lynx::base::closure task,\n                                       fml::TimePoint target_time) {\n  if (!task) {\n    return;\n  }\n\n  uint64_t baton = 0;\n\n  {\n    // Release the lock before the jump via the dispatch table.\n    std::scoped_lock lock(tasks_mutex_);\n    if (thread_host_destroyed_) {\n      FML_LOG(ERROR) << \"Clay attempted to post a task to a destroyed \"\n                        \"thread host. Maybe leaked.\";\n      return;\n    }\n    baton = ++last_baton_;\n    pending_tasks_[baton] = std::make_pair(target_time, std::move(task));\n    dispatch_table_.post_task_callback(this, baton, target_time);\n  }\n}\n\nvoid CustomTaskRunner::PostDelayedTask(lynx::base::closure task,\n                                       fml::TimeDelta delay) {\n  PostTaskForTime(std::move(task), fml::TimePoint::Now() + delay);\n}\n\nbool CustomTaskRunner::RunsTasksOnCurrentThread() {\n  std::scoped_lock lock(tasks_mutex_);\n  if (thread_host_destroyed_) {\n    FML_LOG(ERROR) << \"Clay attempted to call RunsTasksOnCurrentThread\"\n                      \"to a destroyed thread host.\";\n    return false;\n  }\n  return dispatch_table_.runs_task_on_current_thread_callback();\n}\n\nvoid CustomTaskRunner::OnThreadHostDestroyed() {\n  std::unordered_map<uint64_t, std::pair<fml::TimePoint, lynx::base::closure>>\n      tasks;\n\n  {\n    std::scoped_lock lock(tasks_mutex_);\n    thread_host_destroyed_ = true;\n    tasks = std::move(pending_tasks_);\n  }\n\n  fml::TimePoint now = fml::TimePoint::Now();\n\n  for (auto& it : tasks) {\n    auto& [time, cb] = it.second;\n    if (time <= now) {\n      cb();\n    }\n  }\n}\n\nbool CustomTaskRunner::PostTask(uint64_t baton) {\n  lynx::base::closure task;\n\n  {\n    std::scoped_lock lock(tasks_mutex_);\n    if (thread_host_destroyed_) {\n      FML_LOG(ERROR) << \"Clay attempted to post a task to a destroyed \"\n                        \"thread host. Maybe leaked.\";\n      return false;\n    }\n    const auto& found = pending_tasks_.find(baton);\n    if (found == pending_tasks_.end()) {\n      FML_LOG(ERROR) << \"Clay attempted to post an unknown task.\";\n      return false;\n    }\n    task = std::move(found->second.second);\n    pending_tasks_.erase(found);\n\n    // Let go of the tasks mutex befor executing the task.\n  }\n\n  FML_DCHECK(task);\n  task();\n  return true;\n}\n\n// |fml::TaskRunner|\nfml::TaskQueueId CustomTaskRunner::GetTaskQueueId() { return placeholder_id_; }\n\n}  // namespace example\n}  // namespace clay\n"
  },
  {
    "path": "clay/example/glfw/custom_task_runner.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_EXAMPLE_GLFW_CUSTOM_TASK_RUNNER_H_\n#define CLAY_EXAMPLE_GLFW_CUSTOM_TASK_RUNNER_H_\n\n#include <mutex>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/task_runner.h\"\n\nnamespace clay {\nnamespace example {\n\nclass CustomTaskRunner final : public fml::TaskRunner {\n public:\n  //----------------------------------------------------------------------------\n  /// @brief      A\n  ///\n  struct DispatchTable {\n    //--------------------------------------------------------------------------\n    /// Delegates responsibility of deferred task execution to the embedder.\n    /// Once the embedder gets the task, it must call\n    /// `CustomTaskRunner::PostTask` with the supplied `task_baton` on the\n    /// correct thread after the tasks `target_time` point expires.\n    ///\n    std::function<void(CustomTaskRunner* task_runner, uint64_t task_baton,\n                       fml::TimePoint target_time)>\n        post_task_callback;\n    //--------------------------------------------------------------------------\n    /// Asks the embedder if tasks posted to it on this task task runner via the\n    /// `post_task_callback` will be executed (after task expiry) on the calling\n    /// thread.\n    ///\n    std::function<bool(void)> runs_task_on_current_thread_callback;\n  };\n\n  //----------------------------------------------------------------------------\n  /// @brief      Create a task runner with a dispatch table for delegation of\n  ///             task runner responsibility to the embedder. When embedders\n  ///             specify task runner dispatch tables that service tasks on the\n  ///             same thread, they also must ensure that their\n  ///             `embedder_idetifier`s match. This allows the engine to\n  ///             determine task runner equality without actually posting tasks\n  ///             to the task runner.\n  ///\n  /// @param[in]  table                The task runner dispatch table.\n  /// @param[in]  embedder_identifier  The embedder identifier\n  ///\n  CustomTaskRunner(DispatchTable table, size_t embedder_identifier);\n\n  // |fml::TaskRunner|\n  ~CustomTaskRunner() override;\n\n  //----------------------------------------------------------------------------\n  /// @brief      The unique identifier provided by the embedder for the task\n  ///             runner. Embedders whose dispatch tables service tasks on the\n  ///             same underlying OS thread must ensure that their identifiers\n  ///             match. This allows the engine to determine task runner\n  ///             equality without posting tasks on the thread.\n  ///\n  /// @return     The embedder identifier.\n  ///\n  size_t GetEmbedderIdentifier() const;\n\n  bool PostTask(uint64_t baton);\n\n  void OnThreadHostDestroyed();\n\n  // |fml::TaskRunner|\n  void PostTask(lynx::base::closure task) override;\n\n private:\n  const size_t embedder_identifier_;\n  DispatchTable dispatch_table_;\n  std::recursive_mutex tasks_mutex_;\n  uint64_t last_baton_ = 0;\n  std::unordered_map<uint64_t, std::pair<fml::TimePoint, lynx::base::closure>>\n      pending_tasks_;\n  fml::TaskQueueId placeholder_id_;\n  bool thread_host_destroyed_ = false;\n\n  // |fml::TaskRunner|\n  void PostTaskForTime(lynx::base::closure task,\n                       fml::TimePoint target_time) override;\n\n  // |fml::TaskRunner|\n  void PostDelayedTask(lynx::base::closure task, fml::TimeDelta delay) override;\n\n  // |fml::TaskRunner|\n  bool RunsTasksOnCurrentThread() override;\n\n  // |fml::TaskRunner|\n  fml::TaskQueueId GetTaskQueueId() override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(CustomTaskRunner);\n};\n\n}  // namespace example\n}  // namespace clay\n\n#endif  // CLAY_EXAMPLE_GLFW_CUSTOM_TASK_RUNNER_H_\n"
  },
  {
    "path": "clay/example/glfw/event_loop.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/example/glfw/event_loop.h\"\n\n#include <GLFW/glfw3.h>\n\n#include <algorithm>\n#include <atomic>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace clay {\nnamespace example {\n\nEventLoop::EventLoop(std::thread::id main_thread_id,\n                     const TaskExpiredCallback& on_task_expired)\n    : main_thread_id_(main_thread_id), on_task_expired_(on_task_expired) {}\n\nEventLoop::~EventLoop() = default;\n\nbool EventLoop::RunsTasksOnCurrentThread() const {\n  return std::this_thread::get_id() == main_thread_id_;\n}\n\nvoid EventLoop::WaitForEvents(std::chrono::nanoseconds max_wait) {\n  const auto now = TaskTimePoint::clock::now();\n  std::vector<ClayTask> expired_tasks;\n\n  // Process expired tasks.\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    while (!task_queue_.empty()) {\n      const auto& top = task_queue_.top();\n      // If this task (and all tasks after this) has not yet expired, there is\n      // nothing more to do. Quit iterating.\n      if (top.fire_time > now) {\n        break;\n      }\n\n      // Make a record of the expired task. Do NOT service the task here\n      // because we are still holding onto the task queue mutex. We don't want\n      // other threads to block on posting tasks onto this thread till we are\n      // done processing expired tasks.\n      expired_tasks.push_back(task_queue_.top().task);\n\n      // Remove the tasks from the delayed tasks queue.\n      task_queue_.pop();\n    }\n  }\n\n  // Fire expired tasks.\n  {\n    // Flushing tasks here without holing onto the task queue mutex.\n    for (const auto& task : expired_tasks) {\n      on_task_expired_(&task);\n    }\n  }\n\n  // Sleep till the next task needs to be processed. If a new task comes\n  // along, the wait will be resolved early because PostTask calls Wake().\n  {\n    TaskTimePoint next_wake;\n    {\n      std::lock_guard<std::mutex> lock(task_queue_mutex_);\n      TaskTimePoint max_wake_timepoint =\n          max_wait == std::chrono::nanoseconds::max() ? TaskTimePoint::max()\n                                                      : now + max_wait;\n      TaskTimePoint next_event_timepoint = task_queue_.empty()\n                                               ? TaskTimePoint::max()\n                                               : task_queue_.top().fire_time;\n      next_wake = std::min(max_wake_timepoint, next_event_timepoint);\n    }\n    WaitUntil(next_wake);\n  }\n}\n\nEventLoop::TaskTimePoint EventLoop::TimePointFromEngineTime(\n    uint64_t target_time_nanos) {\n  const auto now = TaskTimePoint::clock::now();\n  const int64_t duration =\n      target_time_nanos - fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();\n  return now + std::chrono::nanoseconds(duration);\n}\n\nvoid EventLoop::PostTask(ClayTask clay_task, uint64_t target_time_nanos) {\n  static std::atomic_uint64_t sGlobalTaskOrder(0);\n\n  Task task;\n  task.order = ++sGlobalTaskOrder;\n  task.fire_time = TimePointFromEngineTime(target_time_nanos);\n  task.task = clay_task;\n\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    task_queue_.push(task);\n\n    // Make sure the queue mutex is unlocked before waking up the loop. In case\n    // the wake causes this thread to be descheduled for the primary thread to\n    // process tasks, the acquisition of the lock on that thread while holding\n    // the lock here momentarily till the end of the scope is a pessimization.\n  }\n  Wake();\n}\n\nvoid EventLoop::WaitUntil(const TaskTimePoint& time) {\n  const auto now = TaskTimePoint::clock::now();\n\n  // Make sure the seconds are not integral.\n  using Seconds = std::chrono::duration<double, std::ratio<1>>;\n  const auto duration_to_wait = std::chrono::duration_cast<Seconds>(time - now);\n\n  if (duration_to_wait.count() > 0.0) {\n    ::glfwWaitEventsTimeout(duration_to_wait.count());\n  } else {\n    // Avoid engine task priority inversion by making sure GLFW events are\n    // always processed even when there is no need to wait for pending engine\n    // tasks.\n    ::glfwPollEvents();\n  }\n}\n\nvoid EventLoop::Wake() { ::glfwPostEmptyEvent(); }\n\n}  // namespace example\n}  // namespace clay\n"
  },
  {
    "path": "clay/example/glfw/event_loop.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_EXAMPLE_GLFW_EVENT_LOOP_H_\n#define CLAY_EXAMPLE_GLFW_EVENT_LOOP_H_\n\n#include <chrono>\n#include <deque>\n#include <functional>\n#include <mutex>\n#include <queue>\n#include <thread>\n\n#include \"clay/public/clay.h\"\n\nnamespace clay {\nnamespace example {\n\n// An event loop implementation that supports Engine tasks scheduling in the\n// GLFW event loop.\nclass EventLoop {\n public:\n  using TaskExpiredCallback = std::function<void(const ClayTask*)>;\n\n  // Creates an event loop running on the given thread, calling\n  // |on_task_expired| to run tasks.\n  EventLoop(std::thread::id main_thread_id,\n            const TaskExpiredCallback& on_task_expired);\n\n  ~EventLoop();\n\n  // Disallow copy.\n  EventLoop(const EventLoop&) = delete;\n  EventLoop& operator=(const EventLoop&) = delete;\n\n  // Returns if the current thread is the thread used by this event loop.\n  bool RunsTasksOnCurrentThread() const;\n\n  // Waits for the next event, processes it, and returns.\n  //\n  // Expired engine events, if any, are processed as well. The optional\n  // timeout should only be used when events not managed by this loop need to be\n  // processed in a polling manner.\n  void WaitForEvents(\n      std::chrono::nanoseconds max_wait = std::chrono::nanoseconds::max());\n\n  // Posts a engine task to the event loop for delayed execution.\n  void PostTask(ClayTask task, uint64_t target_time_nanos);\n\n protected:\n  using TaskTimePoint = std::chrono::steady_clock::time_point;\n\n  // Returns the timepoint corresponding to a engine task time.\n  static TaskTimePoint TimePointFromEngineTime(uint64_t target_time_nanos);\n\n  // Returns the mutex used to control the task queue. Subclasses may safely\n  // lock this mutex in the abstract methods below.\n  std::mutex& GetTaskQueueMutex() { return task_queue_mutex_; }\n\n  // Waits until the given time, or a Wake() call.\n  void WaitUntil(const TaskTimePoint& time);\n\n  // Wakes the main thread from a WaitUntil call.\n  void Wake();\n\n  struct Task {\n    uint64_t order;\n    TaskTimePoint fire_time;\n    ClayTask task;\n\n    struct Comparer {\n      bool operator()(const Task& a, const Task& b) {\n        if (a.fire_time == b.fire_time) {\n          return a.order > b.order;\n        }\n        return a.fire_time > b.fire_time;\n      }\n    };\n  };\n  std::thread::id main_thread_id_;\n  TaskExpiredCallback on_task_expired_;\n  std::mutex task_queue_mutex_;\n  std::priority_queue<Task, std::deque<Task>, Task::Comparer> task_queue_;\n};\n\n}  // namespace example\n}  // namespace clay\n\n#endif  // CLAY_EXAMPLE_GLFW_EVENT_LOOP_H_\n"
  },
  {
    "path": "clay/example/glfw/platform_view_impl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_EXAMPLE_GLFW_PLATFORM_VIEW_IMPL_H_\n#define CLAY_EXAMPLE_GLFW_PLATFORM_VIEW_IMPL_H_\n\n#include <memory>\n\n#include \"build/build_config.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/common/thread_host.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/common/output_surface.h\"\n#include \"clay/shell/common/platform_view.h\"\n#ifdef SHELL_ENABLE_GL\n#include \"clay/shell/platform/glfw/surface_gl_impl.h\"\n#include \"third_party/skia/include/gpu/gl/GrGLInterface.h\"\n#include \"third_party/skia/src/gpu/gl/GrGLDefines.h\"  // nogncheck\n#endif\n\nnamespace clay {\nnamespace example {\n\nclass PlatformViewImpl final : public clay::PlatformView {\n public:\n  PlatformViewImpl(std::shared_ptr<clay::ServiceManager> service_manager,\n                   clay::PlatformView::Delegate& delegate,\n                   const clay::TaskRunners& task_runners,\n                   SurfaceDelegate* surface_delegate)\n      : clay::PlatformView(service_manager, delegate, task_runners),\n        surface_(fml::MakeRefCounted<SurfaceGLImpl>(surface_delegate)) {}\n\n  ~PlatformViewImpl() override {}\n\n private:\n  // |PlatformView|\n  fml::RefPtr<clay::OutputSurface> GetOutputSurface() const override {\n    return surface_;\n  }\n\n  // |PlatformView|\n  void NotifyDestroyed() override {\n    clay::PlatformView::NotifyDestroyed();\n    fml::AutoResetWaitableEvent latch;\n    fml::TaskRunner::RunNowOrPostTask(\n        task_runners_.GetRasterTaskRunner(),\n        [&latch, surface = surface_.get()]() {\n          auto gr_context =\n              static_cast<clay::OutputSurface*>(surface)->GetMainGrContext();\n          if (gr_context) {\n            bool has_released = false;\n#ifdef SHELL_ENABLE_GL\n            if (gr_context->backend() == GrBackendApi::kOpenGL) {\n              auto surface_gl = static_cast<SurfaceGLImpl*>(surface);\n              auto status = static_cast<clay::GPUSurfaceGLDelegate*>(surface_gl)\n                                ->GLContextMakeCurrent();\n              if (status->GetResult()) {\n                gr_context->releaseResourcesAndAbandonContext();\n                static_cast<clay::GPUSurfaceGLDelegate*>(surface_gl)\n                    ->GLContextClearCurrent();\n                has_released = true;\n              }\n            }\n#endif\n            if (!has_released) {\n              gr_context->releaseResourcesAndAbandonContext();\n            }\n          }\n          latch.Signal();\n        });\n    latch.Wait();\n  }\n\n  fml::RefPtr<SurfaceGLImpl> surface_;\n};\n\n}  // namespace example\n}  // namespace clay\n\n#endif  // CLAY_EXAMPLE_GLFW_PLATFORM_VIEW_IMPL_H_\n"
  },
  {
    "path": "clay/example/glfw/shell_impl.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/example/glfw/shell_impl.h\"\n\n#include <string>\n#include <utility>\n\n#include \"clay/common/settings.h\"\n#include \"clay/example/glfw/clay_logo.h\"\n#include \"clay/example/glfw/platform_view_impl.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/shell/common/pointer_data_dispatcher.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/component/image_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/text/internal_text_view.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/window/pointer_data_helper.h\"\n\nnamespace clay {\nnamespace example {\n\nnamespace {\n\nstatic std::unique_ptr<clay::Rasterizer> CreateRasterizer(\n    std::shared_ptr<clay::ServiceManager> service_manager) {\n  return std::make_unique<clay::Rasterizer>(service_manager);\n};\n\nclay::ImageView* CreateLogo(int left, int top, int width, int height,\n                            clay::PageView* page_view) {\n  clay::ImageView* image_view = new clay::ImageView(-1, page_view);\n  image_view->SetX(left);\n  image_view->SetY(top);\n  image_view->SetWidth(width);\n  image_view->SetHeight(height);\n  image_view->SetSource(kClayLogo);\n  return image_view;\n}\n\nclay::InternalTextView* CreateText(const std::string& text, int left, int top,\n                                   int width, int height,\n                                   clay::PageView* page_view) {\n  auto* text_view = new clay::InternalTextView(-1, page_view);\n  text_view->SetX(left);\n  text_view->SetY(top);\n  text_view->SetText(text);\n  text_view->SetFontSize(page_view->FromLogical(40.0f));\n  MeasureResult result;\n  text_view->Measure(\n      {page_view->FromLogical(300), clay::TextMeasureMode::kAtMost,\n       page_view->FromLogical(40), clay::TextMeasureMode::kAtMost},\n      result);\n  text_view->SetPaddings(9.5f, 20.0f, 9.5f, 20.0f);\n  text_view->SetContentWidth(result.width);\n  text_view->SetContentHeight(result.height);\n\n  return text_view;\n}\n\nvoid DrawTextAndImage(clay::PageView* page_view) {\n  clay::Color color = clay::Color::kWhite();\n  page_view->SetBackgroundColor(color);\n  page_view->AddChild(CreateLogo(400, 120, 202, 202, page_view));\n  page_view->AddChild(CreateText(\"Hello Clay\", 400, 300, 400, 100, page_view));\n  page_view->RequestPaintBase();\n}\n\n}  // namespace\n\nShellImpl::ShellImpl(const char* icu_data_path, SurfaceDelegate* delegate,\n                     ClayTaskRunnerDescription* platform_task_runner_desc)\n    : thread_host_(\n          ThreadHostHolder::CreateThreadHostHolder(platform_task_runner_desc)),\n      task_runners_(thread_host_->GetTaskRunners()) {\n  service_manager_ = clay::ServiceManager::Create(\n      {task_runners_.GetPlatformTaskRunner(), task_runners_.GetUITaskRunner(),\n       task_runners_.GetRasterTaskRunner(), task_runners_.GetIOTaskRunner()});\n  clay::Shell::CreateCallback<clay::PlatformView> on_create_platform_view =\n      [=](std::shared_ptr<clay::ServiceManager> service_manager,\n          clay::Shell& shell) {\n        return std::make_unique<PlatformViewImpl>(\n            service_manager,\n            shell,                   // delegate\n            shell.GetTaskRunners(),  // task runners\n            delegate);\n      };\n  clay::Shell::CreateCallbackFnPtr<clay::Rasterizer> on_create_rasterizer =\n      &CreateRasterizer;\n  clay::Settings settings;\n  settings.icu_data_path = icu_data_path;\n  shell_ = Shell::Create(service_manager_, task_runners_, settings,\n                         on_create_platform_view, on_create_rasterizer);\n  auto platform_view = shell_->GetPlatformView();\n  if (platform_view) {\n    platform_view->NotifyCreated();\n  }\n\n  auto view_context = shell_->GetEngine()->GetViewContext();\n  if (view_context) {\n    view_context->OnFirstMeaningfulLayout();\n  }\n}\n\nShellImpl::~ShellImpl() {\n  auto platform_view = shell_->GetPlatformView();\n  if (platform_view) {\n    platform_view->NotifyDestroyed();\n  }\n  shell_.reset();\n}\n\nvoid ShellImpl::DrawUI() {\n  auto view_context = shell_->GetEngine()->GetViewContext();\n  if (view_context) {\n    DrawTextAndImage(view_context->GetPageView());\n  }\n}\n\nbool ShellImpl::RunTask(const ClayTask* task) {\n  if (task == nullptr) {\n    return false;\n  }\n  return thread_host_->PostTask(task->task);\n}\n\nvoid ShellImpl::SendViewportMetrics(int32_t width, int32_t height,\n                                    double pixel_ratio) {\n  if (shell_) {\n    clay::ViewportMetrics metrics;\n    metrics.physical_width = width;\n    metrics.physical_height = height;\n    metrics.device_pixel_ratio = pixel_ratio;\n    // Default logical pixel is 96\n    metrics.device_density_dpi = metrics.device_pixel_ratio * 96.f;\n    metrics.physical_view_inset_top = 0.0;\n    metrics.physical_view_inset_right = 0.0;\n    metrics.physical_view_inset_bottom = 0.0;\n    metrics.physical_view_inset_left = 0.0;\n    shell_->GetPlatformView()->SetViewportMetrics(metrics);\n  }\n}\n\nvoid ShellImpl::SendPointerEvents(const ClayPointerEvent* events,\n                                  size_t events_count) {\n  if (events == nullptr || events_count == 0 || !shell_) {\n    return;\n  }\n  auto packet = std::make_unique<clay::PointerDataPacket>(events_count);\n  const ClayPointerEvent* current = events;\n  for (size_t i = 0; i < events_count; ++i) {\n    clay::PointerData pointer_data;\n    pointer_data.Clear();\n    pointer_data.embedder_id = 0;\n    pointer_data.time_stamp = current->timestamp;\n    pointer_data.change =\n        PointerDataHelper::ToPointerDataChange(current->phase);\n    pointer_data.physical_x = current->x;\n    pointer_data.physical_y = current->y;\n    pointer_data.physical_delta_x = 0.0;\n    pointer_data.physical_delta_y = 0.0;\n    pointer_data.device = current->device;\n    pointer_data.pointer_identifier = 0;\n    pointer_data.signal_kind =\n        PointerDataHelper::ToPointerDataSignalKind(current->signal_kind);\n    pointer_data.scroll_delta_x = current->scroll_delta_x;\n    pointer_data.scroll_delta_y = current->scroll_delta_y;\n    ClayPointerDeviceKind device_kind = current->device_kind;\n    if (device_kind == 0) {\n      pointer_data.kind = clay::PointerData::DeviceKind::kMouse;\n      pointer_data.buttons =\n          PointerDataHelper::PointerDataButtonsForLegacyEvent(\n              pointer_data.change);\n\n    } else {\n      pointer_data.kind = PointerDataHelper::ToPointerDataKind(device_kind);\n      if (pointer_data.kind == clay::PointerData::DeviceKind::kTouch) {\n        if (pointer_data.change == clay::PointerData::Change::kDown ||\n            pointer_data.change == clay::PointerData::Change::kMove) {\n          pointer_data.buttons = clay::kPointerButtonTouchContact;\n        }\n      } else {\n        pointer_data.buttons = current->buttons;\n      }\n    }\n    pointer_data.pan_x = 0.0;\n    pointer_data.pan_y = 0.0;\n    pointer_data.pan_delta_x = 0.0;\n    pointer_data.pan_delta_y = 0.0;\n    pointer_data.scale = 0.0;\n    pointer_data.rotation = 0.0;\n    pointer_data.is_precise_scroll = true;\n    packet->SetPointerData(i, pointer_data);\n    current = reinterpret_cast<const ClayPointerEvent*>(\n        reinterpret_cast<const uint8_t*>(current) + current->struct_size);\n  }\n  shell_->GetPlatformView()->DispatchPointerDataPacket(std::move(packet));\n}\n\n}  // namespace example\n}  // namespace clay\n"
  },
  {
    "path": "clay/example/glfw/shell_impl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_EXAMPLE_GLFW_SHELL_IMPL_H_\n#define CLAY_EXAMPLE_GLFW_SHELL_IMPL_H_\n\n#include <memory>\n\n#include \"build/build_config.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/common/thread_host.h\"\n#include \"clay/example/glfw/surface_gl_impl.h\"\n#include \"clay/example/glfw/thread_host_holder.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/common/engine.h\"\n#include \"clay/shell/common/platform_view.h\"\n#include \"clay/shell/common/rasterizer.h\"\n#include \"clay/shell/common/shell.h\"\n#include \"clay/ui/window/viewport_metrics.h\"\n\nnamespace clay {\nnamespace example {\n\nclass ShellImpl {\n public:\n  ShellImpl(const char* icu_data_path, SurfaceDelegate* delegate,\n            ClayTaskRunnerDescription* platform_task_runner_desc);\n  ~ShellImpl();\n\n  clay::Shell& GetShell() { return *shell_.get(); }\n  const clay::TaskRunners& GetTaskRunners() const { return task_runners_; }\n  const std::shared_ptr<clay::ServiceManager>& GetServiceManager() const {\n    return service_manager_;\n  }\n\n  void NotifyCreated() {\n    if (shell_) {\n      shell_->GetPlatformView()->NotifyCreated();\n    }\n  }\n  void NotifyDestroyed() {\n    if (shell_) {\n      shell_->GetPlatformView()->NotifyDestroyed();\n    }\n  }\n  void SetViewportMetrics(const clay::ViewportMetrics& metrics) {\n    if (shell_) {\n      shell_->GetPlatformView()->SetViewportMetrics(metrics);\n    }\n  }\n\n  void ScheduleFrame() {\n    if (shell_) {\n      shell_->GetPlatformView()->ScheduleFrame();\n    }\n  }\n\n  bool RunTask(const ClayTask* task);\n\n  void SendViewportMetrics(int32_t width, int32_t height, double pixel_ratio);\n  void SendPointerEvents(const ClayPointerEvent* events, size_t events_count);\n  void DrawUI();\n\n private:\n  const std::unique_ptr<ThreadHostHolder> thread_host_;\n  clay::TaskRunners task_runners_;\n  std::shared_ptr<clay::ServiceManager> service_manager_;\n  std::unique_ptr<clay::Shell> shell_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ShellImpl);\n};\n\n}  // namespace example\n}  // namespace clay\n\n#endif  // CLAY_EXAMPLE_GLFW_SHELL_IMPL_H_\n"
  },
  {
    "path": "clay/example/glfw/surface_gl_impl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_EXAMPLE_GLFW_SURFACE_GL_IMPL_H_\n#define CLAY_EXAMPLE_GLFW_SURFACE_GL_IMPL_H_\n\n#include <memory>\n\n#include \"build/build_config.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/common/output_surface.h\"\n#include \"clay/shell/gpu/gpu_surface_gl_skia.h\"\n#include \"third_party/skia/src/gpu/gl/GrGLDefines.h\"  // nogncheck\n\nnamespace clay {\nnamespace example {\n\nclass SurfaceDelegate {\n public:\n  virtual bool MakeCurrent() = 0;\n  virtual bool ClearCurrent() = 0;\n  virtual bool Present() = 0;\n  virtual uint32_t FBO(const ClayFrameInfo* frame_info) = 0;\n  virtual void* GetGLProcResolver(const char* what) const = 0;\n};\n\nclass SurfaceGLImpl : public clay::OutputSurface,\n                      public clay::GPUSurfaceGLDelegate {\n public:\n  explicit SurfaceGLImpl(SurfaceDelegate* delegate) : delegate_(delegate) {}\n  ~SurfaceGLImpl() override {}\n\n private:\n  // |OutputSurface|\n  bool IsValid() const override { return true; }\n\n  // |OutputSurface|\n  std::unique_ptr<clay::Surface> CreateGPUSurface(\n      clay::GrContext* context) override {\n    const bool render_to_surface = true;\n    return std::make_unique<clay::GPUSurfaceGLSkia>(\n        context ? sk_ref_sp(context) : GetMainGrContext(),\n        this,              // GPU surface GL delegate\n        render_to_surface  // render to surface\n    );\n  }\n\n  // |OutputSurface|\n  clay::GrContextPtr GetMainGrContext() override {\n    if (!main_context_) {\n      main_context_ = clay::GPUSurfaceGLSkia::MakeGLContext(this);\n    }\n    return main_context_;\n  }\n\n  // |GPUSurfaceGLDelegate|\n  std::unique_ptr<GLContextResult> GLContextMakeCurrent() override {\n    return std::make_unique<GLContextDefaultResult>(delegate_->MakeCurrent());\n  }\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextClearCurrent() override { return delegate_->ClearCurrent(); }\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextPresent(const clay::GLPresentInfo& present_info) override {\n    delegate_->Present();\n    return true;\n  }\n\n  // |GPUSurfaceGLDelegate|\n  GLFBOInfo GLContextFBO(GLFrameInfo gl_frame_info) const override {\n    ClayFrameInfo frame_info = {};\n    frame_info.struct_size = sizeof(ClayFrameInfo);\n    frame_info.width = gl_frame_info.width;\n    frame_info.height = gl_frame_info.height;\n    return GLFBOInfo{.fbo_id = delegate_->FBO(&frame_info),\n                     .existing_damage = {}};\n  }\n\n  // |GPUSurfaceGLDelegate|\n  GLProcResolver GetGLProcResolver() const override {\n    return [&](const char* name) -> void* {\n      return reinterpret_cast<void*>(delegate_->GetGLProcResolver(name));\n    };\n  }\n\n  bool GLContextFBOResetAfterPresent() const override { return true; }\n\n  SurfaceDelegate* delegate_;\n  clay::GrContextPtr main_context_;\n};\n\n}  // namespace example\n}  // namespace clay\n\n#endif  // CLAY_EXAMPLE_GLFW_SURFACE_GL_IMPL_H_\n"
  },
  {
    "path": "clay/example/glfw/thread_host_holder.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/example/glfw/thread_host_holder.h\"\n\n#include <utility>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/thread.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nnamespace example {\n\nnamespace {\nconstexpr const char* kThreadName = \"clay.demo\";\n\nfml::Thread::ThreadConfig MakeThreadConfig(\n    clay::ThreadHost::Type type, fml::Thread::ThreadPriority priority) {\n  return fml::Thread::ThreadConfig(\n      clay::ThreadHost::ThreadHostConfig::MakeThreadName(type, kThreadName),\n      priority);\n}\n\nfml::RefPtr<fml::TaskRunner> GetCurrentThreadTaskRunner() {\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  return fml::MessageLoop::GetCurrent().GetTaskRunner();\n}\n\nstatic std::pair<bool, fml::RefPtr<CustomTaskRunner>> CreateCustomTaskRunner(\n    const ClayTaskRunnerDescription* description) {\n  if (description == nullptr) {\n    return {true, {}};\n  }\n\n  if (description->runs_task_on_current_thread_callback == nullptr) {\n    FML_LOG(ERROR) << \"ClayTaskRunnerDescription.runs_task_on_current_thread_\"\n                      \"callback was nullptr.\";\n    return {false, {}};\n  }\n\n  if (description->post_task_callback == nullptr) {\n    FML_LOG(ERROR)\n        << \"ClayTaskRunnerDescription.post_task_callback was nullptr.\";\n    return {false, {}};\n  }\n\n  auto user_data = description->user_data;\n\n  // ABI safety checks have been completed.\n  auto post_task_callback_c = description->post_task_callback;\n  auto runs_task_on_current_thread_callback_c =\n      description->runs_task_on_current_thread_callback;\n\n  CustomTaskRunner::DispatchTable task_runner_dispatch_table = {\n      // .post_task_callback\n      [post_task_callback_c, user_data](CustomTaskRunner* task_runner,\n                                        uint64_t task_baton,\n                                        fml::TimePoint target_time) -> void {\n        ClayTask task = {\n            // runner\n            reinterpret_cast<ClayTaskRunner>(task_runner),\n            // task\n            task_baton,\n        };\n        post_task_callback_c(task, target_time.ToEpochDelta().ToNanoseconds(),\n                             user_data);\n      },\n      // runs_task_on_current_thread_callback\n      [runs_task_on_current_thread_callback_c, user_data]() -> bool {\n        return runs_task_on_current_thread_callback_c(user_data);\n      }};\n\n  return {true, fml::MakeRefCounted<CustomTaskRunner>(\n                    task_runner_dispatch_table, description->identifier)};\n}\n\n}  // namespace\n\n// static\nstd::unique_ptr<ThreadHostHolder> ThreadHostHolder::CreateThreadHostHolder(\n    ClayTaskRunnerDescription* platform_task_runner_desc) {\n  auto thread_host_config = ThreadHost::ThreadHostConfig();\n  thread_host_config.SetRasterConfig(MakeThreadConfig(\n      clay::ThreadHost::RASTER, fml::Thread::ThreadPriority::HIGH));\n  thread_host_config.SetIOConfig(MakeThreadConfig(\n      clay::ThreadHost::IO, fml::Thread::ThreadPriority::BACKGROUND));\n  ThreadHost thread_host(thread_host_config);\n  auto platform_task_runner_pair =\n      CreateCustomTaskRunner(platform_task_runner_desc);\n  if (!platform_task_runner_pair.first) {\n    return nullptr;\n  }\n  auto platform_task_runner = platform_task_runner_pair.second\n                                  ? static_cast<fml::RefPtr<fml::TaskRunner>>(\n                                        platform_task_runner_pair.second)\n                                  : GetCurrentThreadTaskRunner();\n  auto ui_task_runner = platform_task_runner;\n\n  clay::TaskRunners task_runners(\n      kThreadName,\n      platform_task_runner,                        // platform\n      thread_host.raster_thread->GetTaskRunner(),  // raster\n      ui_task_runner, thread_host.io_thread->GetTaskRunner());\n  if (!task_runners.IsValid()) {\n    return nullptr;\n  }\n  return std::make_unique<ThreadHostHolder>(std::move(thread_host),\n                                            std::move(task_runners),\n                                            platform_task_runner_pair.second);\n}\n\nThreadHostHolder::ThreadHostHolder(\n    clay::ThreadHost thread_host, const clay::TaskRunners& runners,\n    fml::RefPtr<CustomTaskRunner> platform_task_runner)\n    : thread_host_(std::move(thread_host)),\n      runners_(runners),\n      platform_task_runner_(platform_task_runner) {}\n\nThreadHostHolder::~ThreadHostHolder() {\n  thread_host_.ui_thread.reset();\n  thread_host_.raster_thread.reset();\n  if (platform_task_runner_) {\n    platform_task_runner_->PostSyncTask(\n        [platform_task_runner = platform_task_runner_.get()]() {\n          platform_task_runner->OnThreadHostDestroyed();\n        });\n  }\n}\n\nbool ThreadHostHolder::PostTask(uint64_t task) const {\n  return platform_task_runner_ && platform_task_runner_->PostTask(task);\n}\n\n}  // namespace example\n}  // namespace clay\n"
  },
  {
    "path": "clay/example/glfw/thread_host_holder.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_EXAMPLE_GLFW_THREAD_HOST_HOLDER_H_\n#define CLAY_EXAMPLE_GLFW_THREAD_HOST_HOLDER_H_\n\n#include <memory>\n\n#include \"clay/common/task_runners.h\"\n#include \"clay/common/thread_host.h\"\n#include \"clay/example/glfw/custom_task_runner.h\"\n#include \"clay/public/clay.h\"\n\nnamespace clay {\nnamespace example {\n\nclass ThreadHostHolder {\n public:\n  static std::unique_ptr<ThreadHostHolder> CreateThreadHostHolder(\n      ClayTaskRunnerDescription* platform_task_runner_desc);\n  ThreadHostHolder(clay::ThreadHost thread_host,\n                   const clay::TaskRunners& runners,\n                   fml::RefPtr<CustomTaskRunner> platform_task_runner);\n  ~ThreadHostHolder();\n\n  // Disallow copy.\n  ThreadHostHolder(const ThreadHostHolder&) = delete;\n  ThreadHostHolder& operator=(const ThreadHostHolder&) = delete;\n\n  const clay::TaskRunners& GetTaskRunners() const { return runners_; }\n\n  bool PostTask(uint64_t task) const;\n\n private:\n  clay::ThreadHost thread_host_;\n  clay::TaskRunners runners_;\n  fml::RefPtr<CustomTaskRunner> platform_task_runner_;\n};\n\n}  // namespace example\n}  // namespace clay\n\n#endif  // CLAY_EXAMPLE_GLFW_THREAD_HOST_HOLDER_H_\n"
  },
  {
    "path": "clay/flow/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../common/config.gni\")\nimport(\"../testing/testing.gni\")\n\nsource_set(\"flow\") {\n  sources = [\n    \"animation/animation_host.cc\",\n    \"animation/animation_host.h\",\n    \"animation/animation_mutator.cc\",\n    \"animation/animation_mutator.h\",\n    \"animation/scroll_offset_animation.cc\",\n    \"animation/scroll_offset_animation.h\",\n    \"compositor/compositor_state.cc\",\n    \"compositor/compositor_state.h\",\n    \"compositor/overlay_views.h\",\n    \"compositor_context.cc\",\n    \"compositor_context.h\",\n    \"diff_context.cc\",\n    \"diff_context.h\",\n    \"embedded_views.cc\",\n    \"embedded_views.h\",\n    \"flow_rendering_backend.h\",\n    \"frame_timings.cc\",\n    \"frame_timings.h\",\n    \"layer_snapshot_store.cc\",\n    \"layer_snapshot_store.h\",\n    \"layers/backdrop_filter_layer.cc\",\n    \"layers/backdrop_filter_layer.h\",\n    \"layers/cacheable_layer.cc\",\n    \"layers/cacheable_layer.h\",\n    \"layers/clip_path_layer.cc\",\n    \"layers/clip_path_layer.h\",\n    \"layers/clip_rect_layer.cc\",\n    \"layers/clip_rect_layer.h\",\n    \"layers/clip_rrect_layer.cc\",\n    \"layers/clip_rrect_layer.h\",\n    \"layers/clip_shape_layer.h\",\n    \"layers/color_filter_layer.cc\",\n    \"layers/color_filter_layer.h\",\n    \"layers/container_layer.cc\",\n    \"layers/container_layer.h\",\n    \"layers/drawable_image_layer.cc\",\n    \"layers/drawable_image_layer.h\",\n    \"layers/external_view_layer.cc\",\n    \"layers/external_view_layer.h\",\n    \"layers/image_filter_layer.cc\",\n    \"layers/image_filter_layer.h\",\n    \"layers/layer.cc\",\n    \"layers/layer.h\",\n    \"layers/layer_raster_cache_item.cc\",\n    \"layers/layer_raster_cache_item.h\",\n    \"layers/layer_state_stack.cc\",\n    \"layers/layer_state_stack.h\",\n    \"layers/layer_tree.cc\",\n    \"layers/layer_tree.h\",\n    \"layers/offscreen_surface.cc\",\n    \"layers/offscreen_surface.h\",\n    \"layers/opacity_layer.cc\",\n    \"layers/opacity_layer.h\",\n    \"layers/performance_overlay_layer.cc\",\n    \"layers/performance_overlay_layer.h\",\n    \"layers/picture_complexity.cc\",\n    \"layers/picture_complexity.h\",\n    \"layers/picture_complexity_gl.cc\",\n    \"layers/picture_complexity_gl.h\",\n    \"layers/picture_complexity_metal.cc\",\n    \"layers/picture_complexity_metal.h\",\n    \"layers/picture_layer.cc\",\n    \"layers/picture_layer.h\",\n    \"layers/picture_raster_cache_item.cc\",\n    \"layers/picture_raster_cache_item.h\",\n    \"layers/platform_view_layer.cc\",\n    \"layers/platform_view_layer.h\",\n    \"layers/punch_hole_layer.cc\",\n    \"layers/punch_hole_layer.h\",\n    \"layers/shader_mask_layer.cc\",\n    \"layers/shader_mask_layer.h\",\n    \"layers/transform_layer.cc\",\n    \"layers/transform_layer.h\",\n    \"matrix_clip_tracker.cc\",\n    \"matrix_clip_tracker.h\",\n    \"paint_region.cc\",\n    \"paint_region.h\",\n    \"paint_utils.cc\",\n    \"paint_utils.h\",\n    \"raster_cache.cc\",\n    \"raster_cache.h\",\n    \"raster_cache_item.h\",\n    \"raster_cache_key.cc\",\n    \"raster_cache_key.h\",\n    \"raster_cache_util.cc\",\n    \"raster_cache_util.h\",\n    \"rtree.cc\",\n    \"rtree.h\",\n    \"services/animation_event_service.h\",\n    \"stopwatch.cc\",\n    \"stopwatch.h\",\n    \"view_slicer.cc\",\n    \"view_slicer.h\",\n\n    # \"resource_holder.h\",\n    \"surface.cc\",\n    \"surface.h\",\n    \"surface_frame.cc\",\n    \"surface_frame.h\",\n  ]\n\n  public_configs = [ \"../:config\" ]\n\n  deps = [\n    \"../common\",\n    \"../common/graphics\",\n    \"../common/service\",\n    \"../fml:fml\",\n    \"../gfx\",\n    \"../gfx/shared_image\",\n  ]\n\n  if (enable_skity) {\n    sources -= [\n      \"layers/offscreen_surface.cc\",\n      \"layers/offscreen_surface.h\",\n      \"rtree.cc\",\n      \"rtree.h\",\n    ]\n    sources += [\n      \"layers/picture_complexity_calculator_skity.cc\",\n      \"layers/picture_complexity_calculator_skity.h\",\n      \"layers/picture_complexity_helper_skity.h\",\n    ]\n    deps += [ \"../gfx/skity\" ]\n  } else {\n    sources += [ \"layers/picture_complexity_helper_skia.h\" ]\n    deps += [\n      \"../third_party/skity_geometry:skity_geometry\",\n      \"//third_party/skia\",\n    ]\n  }\n}\n\nif (enable_unittests) {\n  test_fixtures(\"flow_fixtures\") {\n    fixtures = []\n  }\n\n  source_set(\"flow_testing\") {\n    testonly = true\n\n    sources = [\n      \"testing/diff_context_test.cc\",\n      \"testing/diff_context_test.h\",\n      \"testing/gl_context_switch_test.cc\",\n      \"testing/gl_context_switch_test.h\",\n      \"testing/gpu_object_layer_test.cc\",\n      \"testing/gpu_object_layer_test.h\",\n      \"testing/layer_test.h\",\n      \"testing/mock_drawable_image.cc\",\n      \"testing/mock_drawable_image.h\",\n      \"testing/mock_layer.cc\",\n      \"testing/mock_layer.h\",\n      \"testing/mock_raster_cache.cc\",\n      \"testing/mock_raster_cache.h\",\n      \"testing/mock_shared_image_backing.cc\",\n      \"testing/mock_shared_image_backing.h\",\n      \"testing/picture_complexity_unittests.cc\",\n      \"testing/picture_test_utils.cc\",\n      \"testing/picture_test_utils.h\",\n    ]\n\n    public_deps = [\n      \"../testing:skia\",\n      \"//third_party/googletest:gtest\",\n    ]\n\n    deps = [\n      \":flow\",\n      \"../common/graphics\",\n      \"../gfx\",\n      \"../gfx/shared_image\",\n    ]\n  }\n\n  executable(\"flow_unittests\") {\n    testonly = true\n\n    sources = [\n      \"animation/scroll_offset_animation_unittests.cc\",\n      \"compositor/compositor_state_unittests.cc\",\n      \"diff_context_unittests.cc\",\n      \"embedded_view_params_unittests.cc\",\n      \"flow_run_all_unittests.cc\",\n      \"flow_test_utils.cc\",\n      \"flow_test_utils.h\",\n      \"frame_timings_recorder_unittests.cc\",\n      \"gl_context_switch_unittests.cc\",\n      \"layers/backdrop_filter_layer_unittests.cc\",\n      \"layers/checkerboard_layertree_unittests.cc\",\n      \"layers/clip_path_layer_unittests.cc\",\n      \"layers/clip_rect_layer_unittests.cc\",\n      \"layers/clip_rrect_layer_unittests.cc\",\n      \"layers/color_filter_layer_unittests.cc\",\n      \"layers/container_layer_unittests.cc\",\n      \"matrix_clip_tracker_unittests.cc\",\n      \"stopwatch_unittests.cc\",\n      \"view_slicer_unittests.cc\",\n\n      # \"layers/display_list_layer_unittests.cc\",\n      \"drawable_image_unittests.cc\",\n      \"layers/drawable_image_layer_unittests.cc\",\n      \"layers/image_filter_layer_unittests.cc\",\n      \"layers/layer_state_stack_unittests.cc\",\n      \"layers/layer_tree_unittests.cc\",\n      \"layers/offscreen_surface_unittests.cc\",\n      \"layers/opacity_layer_unittests.cc\",\n      \"layers/performance_overlay_layer_unittests.cc\",\n\n      #\"layers/physical_shape_layer_unittests.cc\",\n      \"layers/platform_view_layer_unittests.cc\",\n      \"layers/punch_hole_layer_unittests.cc\",\n      \"layers/shader_mask_layer_unittests.cc\",\n      \"layers/transform_layer_unittests.cc\",\n      \"mutators_stack_unittests.cc\",\n      \"raster_cache_unittests.cc\",\n      \"rtree_unittests.cc\",\n      \"surface_frame_unittests.cc\",\n      \"testing/mock_drawable_image_unittests.cc\",\n      \"testing/mock_layer_unittests.cc\",\n      \"testing/mock_shared_image_unittests.cc\",\n    ]\n\n    deps = [\n      \":flow\",\n      \":flow_fixtures\",\n      \":flow_testing\",\n      \"../../base/src:base_static\",\n      \"../common/graphics\",\n      \"../fml:fml\",\n      \"../testing:skia\",\n      \"../testing:testing_lib\",\n      \"../third_party/skity_geometry:skity_geometry\",\n      \"//third_party/googletest:gtest\",\n      \"//third_party/skia\",\n    ]\n\n    configs += [ \"../:config\" ]\n\n    if (!defined(defines)) {\n      defines = []\n    }\n    if (is_win) {\n      # Required for M_PI and others.\n      defines += [ \"_USE_MATH_DEFINES\" ]\n    }\n\n    if (enable_skity) {\n      sources -= [ \"layers/shader_mask_layer_unittests.cc\" ]\n    }\n  }\n}\n"
  },
  {
    "path": "clay/flow/animation/animation_host.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/animation/animation_host.h\"\n\n#include <utility>\n\nnamespace clay {\n\nAnimationHost::AnimationHost() {}\n\nAnimationHost::~AnimationHost() { animation_mutators_.clear(); }\n\nbool AnimationHost::HasAnimations() const {\n  return !retained_layer_ids_.empty() || !animation_mutators_.empty();\n}\n\nbool AnimationHost::HasAnimationRunning(uint64_t layer_id) const {\n  if (auto search = animation_mutators_.find(layer_id);\n      search != animation_mutators_.end()) {\n    return search->second->HasAnimationRunning();\n  }\n  return false;\n}\n\nbool AnimationHost::HasAnimation(uint64_t layer_id) const {\n  auto search = animation_mutators_.find(layer_id);\n  return search != animation_mutators_.end();\n}\n\nbool AnimationHost::DoAnimationFrame(int64_t frame_time) {\n  bool stopped = true;\n  for (auto& mutator : animation_mutators_) {\n    if (mutator.second) {\n      stopped &= mutator.second->DoAnimationFrame(frame_time);\n    }\n  }\n  return stopped;\n}\n\nvoid AnimationHost::AddAnimationMutator(\n    Layer* animation_layer, const std::shared_ptr<AnimationMutator> mutator) {\n  animation_layer->SetAnimationHost(shared_from_this());\n  uint64_t layer_id = animation_layer->unique_id();\n  animation_mutators_.emplace(std::make_pair(layer_id, std::move(mutator)));\n}\n\nconst std::shared_ptr<AnimationMutator>& AnimationHost::GetAnimationMutator(\n    uint64_t layer_id) const {\n  auto& mutator = animation_mutators_.at(layer_id);\n  FML_DCHECK(layer_id == mutator->layer_id());\n  return mutator;\n}\n\nvoid AnimationHost::SetServiceManager(\n    std::shared_ptr<clay::ServiceManager> service_manager) {\n  for (auto& [layer_id, mutator] : animation_mutators_) {\n    mutator->SetServiceManager(service_manager);\n  }\n}\n\nvoid AnimationHost::ResetServiceManager() {\n  for (auto& [layer_id, mutator] : animation_mutators_) {\n    mutator->ResetServiceManager();\n  }\n}\n\nvoid AnimationHost::AddRetainedLayerId(uint64_t layer_id) {\n  retained_layer_ids_.insert(layer_id);\n}\n\nvoid AnimationHost::MergeAnimations(const AnimationHost* prev_animation_host) {\n  if (!prev_animation_host) {\n    return;\n  }\n\n  // Merge animations for the new layer.\n  auto prev_mutators = prev_animation_host->animation_mutators_;\n  for (auto& [layer_id, mutator] : animation_mutators_) {\n    if (mutator->GetType() == AnimationMutatorType::kPicture) {\n      for (const auto& [prev_layer_id, prev_mutator] : prev_mutators) {\n        if (prev_mutator->GetType() == AnimationMutatorType::kPicture &&\n            mutator->HasSameElementId(prev_mutator)) {\n          mutator->SyncProperties(prev_mutator);\n        }\n      }\n    } else {\n      if (auto search = prev_mutators.find(layer_id);\n          search != prev_mutators.end()) {\n        mutator->SyncProperties(search->second);\n      }\n    }\n  }\n\n  // Merge animations for the retained layer.\n  std::set<uint64_t> layer_ids;\n  layer_ids.swap(retained_layer_ids_);\n  for (auto id : layer_ids) {\n    if (auto search = prev_mutators.find(id); search != prev_mutators.end()) {\n      if (search->second && search->second->HasAnimationRunning()) {\n        animation_mutators_.emplace(std::make_pair(id, search->second));\n      }\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/animation/animation_host.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_ANIMATION_ANIMATION_HOST_H_\n#define CLAY_FLOW_ANIMATION_ANIMATION_HOST_H_\n\n#include <memory>\n#include <set>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/common/element_id.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/flow/animation/animation_mutator.h\"\n#include \"clay/flow/layers/layer.h\"\n\nnamespace clay {\n\nclass AnimationMutator;\n\nclass AnimationHost : public std::enable_shared_from_this<AnimationHost> {\n public:\n  AnimationHost();\n  ~AnimationHost();\n\n  bool HasAnimations() const;\n  bool HasAnimation(uint64_t layer_id) const;\n  bool HasAnimationRunning(uint64_t layer_id) const;\n\n  bool DoAnimationFrame(int64_t frame_time);\n\n  void AddAnimationMutator(Layer* animation_layer,\n                           const std::shared_ptr<AnimationMutator> mutator);\n  const std::shared_ptr<AnimationMutator>& GetAnimationMutator(\n      uint64_t layer_id) const;\n\n  void SetServiceManager(std::shared_ptr<clay::ServiceManager> service_manager);\n  void ResetServiceManager();\n\n  void AddRetainedLayerId(uint64_t layer_id);\n  void MergeAnimations(const AnimationHost* prev_animation_host);\n\n private:\n  // Support reusing layer cross LayerTrees.\n  std::set<uint64_t> retained_layer_ids_;\n  // Stores all animation mutators for the current LayerTree.\n  AnimationMutators animation_mutators_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_ANIMATION_ANIMATION_HOST_H_\n"
  },
  {
    "path": "clay/flow/animation/animation_mutator.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/animation/animation_mutator.h\"\n\n#include <utility>\n\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/layers/picture_layer.h\"\n#include \"clay/flow/layers/transform_layer.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\n// static\nstd::shared_ptr<AnimationMutator> AnimationMutator::Create(\n    const clay::ElementId& element_id, const AnimationMutatorType& type,\n    Layer* layer) {\n  switch (type) {\n    case AnimationMutatorType::kTransform: {\n      auto mutator = std::make_shared<TransformMutator>(\n          element_id, layer->unique_id(),\n          static_cast<TransformLayer*>(layer)->GetTransform());\n      return std::move(mutator);\n    }\n    case AnimationMutatorType::kOpacity: {\n      float alpha = static_cast<OpacityLayer*>(layer)->opacity();\n      auto mutator = std::make_shared<OpacityMutator>(\n          element_id, layer->unique_id(), alpha);\n      return std::move(mutator);\n    }\n    case AnimationMutatorType::kPicture: {\n      auto mutator = std::make_shared<PictureMutator>(\n          element_id, layer->unique_id(),\n#ifndef ENABLE_SKITY\n          static_cast<PictureLayer*>(layer)->picture_skia());\n#else\n          static_cast<PictureLayer*>(layer)->picture_skity());\n#endif  // ENABLE_SKITY\n      return std::move(mutator);\n    }\n    case AnimationMutatorType::kScrollOffset: {\n      auto mutator = std::make_shared<ScrollOffsetMutator>(\n          element_id, layer->unique_id(),\n          static_cast<TransformLayer*>(layer)->GetMatrix());\n      return std::move(mutator);\n    }\n    default:\n      FML_UNREACHABLE();\n  }\n}\n\nAnimationMutator::AnimationMutator(const clay::ElementId& element_id,\n                                   uint64_t layer_id)\n    : element_id_(element_id), layer_id_(layer_id) {}\n\nAnimationMutator::~AnimationMutator() {\n  // Must reset keyframes manager and transition manager before\n  // destroying the puppet. Since the destructor of keyframes manager\n  // may call back to `this` and its puppet.\n  keyframes_managers_.clear();\n  transition_managers_.clear();\n  animation_event_service_ = nullptr;\n}\n\nbool AnimationMutator::HasSameElementId(\n    const std::shared_ptr<AnimationMutator> mutator) const {\n  return mutator && mutator->element_id() == element_id_;\n}\n\nvoid AnimationMutator::AddTransitionManager(\n    std::unique_ptr<clay::TransitionManager> manager) {\n  transition_managers_.push_back(std::move(manager));\n}\n\nvoid AnimationMutator::AddKeyframesManager(\n    std::unique_ptr<clay::KeyframesManager> manager) {\n  keyframes_managers_.push_back(std::move(manager));\n}\n\nvoid AnimationMutator::SetScrollOffsetAnimation(\n    std::shared_ptr<ScrollOffsetAnimation> animation) {\n  scroll_offset_animation_ = std::move(animation);\n}\n\nvoid AnimationMutator::SyncProperties(\n    const std::shared_ptr<AnimationMutator> mutator) {\n  if (!mutator) {\n    return;\n  }\n  for (auto& mgr : keyframes_managers_) {\n    for (auto& old_mgr : mutator->keyframes_managers_) {\n      mgr->SyncProperties(old_mgr.get());\n    }\n  }\n\n  for (auto& mgr : transition_managers_) {\n    for (auto& old_mgr : mutator->transition_managers_) {\n      mgr->SyncProperties(old_mgr.get());\n    }\n  }\n}\n\nbool AnimationMutator::HasAnimationRunning() const {\n  for (auto& mgr : transition_managers_) {\n    if (mgr->HasAnimationRunning()) {\n      return true;\n    }\n  }\n  for (auto& mgr : keyframes_managers_) {\n    if (mgr->HasAnimationRunning()) {\n      return true;\n    }\n  }\n  if (scroll_offset_animation_) {\n    return scroll_offset_animation_->IsRunning();\n  }\n  return false;\n}\n\nbool AnimationMutator::DoAnimationFrame(int64_t frame_time) {\n  bool stopped = true;\n  for (auto& mgr : transition_managers_) {\n    for (auto& animator : mgr->GetRunningAnimators()) {\n      stopped &= animator->DoAnimationFrame(frame_time);\n    }\n  }\n  for (auto& mgr : keyframes_managers_) {\n    for (auto& animation : mgr->animations()) {\n      if (animation.animator->IsStarted()) {\n        stopped &= animation.animator->DoAnimationFrame(frame_time);\n      }\n    }\n  }\n  if (scroll_offset_animation_) {\n    stopped &= scroll_offset_animation_->DoAnimationFrame(frame_time, this);\n  }\n  return stopped;\n}\n\nvoid AnimationMutator::SetServiceManager(\n    std::shared_ptr<clay::ServiceManager> service_manager) {\n  if (!animation_event_service_) {\n    animation_event_service_ =\n        service_manager->GetService<AnimationEventService>();\n  }\n}\n\nvoid AnimationMutator::ResetServiceManager() {\n  // A trick way to fix the issue that the animation event service is\n  // nullptr after the animation mutator is recreated by a new Frame .\n  // We should resue the animation mutator instead\n  if (!asScrollOffset()) {\n    animation_event_service_ = nullptr;\n  }\n}\n\nvoid AnimationMutator::OnAnimationEvent(\n    const clay::AnimationParams& animation_params) {\n  if (animation_event_service_) {\n    animation_event_service_->OnAnimationEvent(element_id_, animation_params);\n  }\n}\n\nvoid AnimationMutator::OnTransitionEvent(\n    const clay::AnimationParams& animation_params,\n    ClayAnimationPropertyType property_type) {\n  if (animation_event_service_) {\n    animation_event_service_->OnTransitionEvent(element_id_, animation_params,\n                                                property_type);\n  }\n}\n\nvoid TransformMutator::GetProperty(ClayAnimationPropertyType type,\n                                   clay::TransformOperations& value) {\n  if (type == ClayAnimationPropertyType::kTransform) {\n    value = transform_;\n  }\n}\n\nvoid TransformMutator::SetProperty(ClayAnimationPropertyType type,\n                                   const clay::TransformOperations& value,\n                                   bool skip_update_for_raster_animation) {\n  if (type == ClayAnimationPropertyType::kTransform) {\n    transform_ = value;\n  }\n}\n\nvoid OpacityMutator::GetProperty(ClayAnimationPropertyType type, float& value) {\n  if (type == ClayAnimationPropertyType::kOpacity) {\n    value = alpha_;\n  }\n}\n\nvoid OpacityMutator::SetProperty(ClayAnimationPropertyType type, float value,\n                                 bool skip_update_for_raster_animation) {\n  if (type == ClayAnimationPropertyType::kOpacity) {\n    alpha_ = value;\n  }\n}\n\nvoid PictureMutator::GetProperty(ClayAnimationPropertyType type,\n                                 clay::Color& value) {\n  if (!picture_) {\n    return;\n  }\n  value = picture_->ObtainWorkletValue(type);\n}\n\nvoid PictureMutator::SetProperty(ClayAnimationPropertyType type,\n                                 const clay::Color& value,\n                                 bool skip_update_for_raster_animation) {\n  if (!picture_) {\n    return;\n  }\n  picture_->DispatchToWorklet(type, value);\n}\n\nvoid ScrollOffsetMutator::Initialize(const skity::Vec2& offset,\n                                     const skity::Vec2& scroll_offset,\n                                     const skity::Rect& visible_offset_range,\n                                     const skity::Rect& max_offset_range) {\n  offset_ = offset;\n  scroll_offset_ = scroll_offset;\n  visible_offset_range_ = visible_offset_range;\n  max_offset_range_ = max_offset_range;\n}\n\nconst skity::Vec2& ScrollOffsetMutator::GetScrollOffset() const {\n  return scroll_offset_;\n}\n\nconst skity::Rect& ScrollOffsetMutator::GetVisibleOffsetRange() const {\n  return visible_offset_range_;\n}\n\nconst skity::Rect& ScrollOffsetMutator::GetMaxOffsetRange() const {\n  return max_offset_range_;\n}\n\nvoid ScrollOffsetMutator::UpdateScrollOffset(const skity::Vec2& scroll_offset) {\n  scroll_offset_ = scroll_offset;\n  transform_ = skity::Matrix::Translate(offset_.x + scroll_offset_.x,\n                                        offset_.y + scroll_offset_.y);\n}\n\nvoid ScrollOffsetMutator::OnScrolled(int32_t session_id, float scroll_offset,\n                                     bool ignore_ui_repaint) {\n  // Synchronize scrolled event to UI thread by AnimationEventService.\n  if (animation_event_service_) {\n    animation_event_service_->OnScrolled(element_id_, session_id, scroll_offset,\n                                         ignore_ui_repaint);\n  }\n}\n\nvoid ScrollOffsetMutator::OnScrollEnd(int32_t session_id, float scroll_offset,\n                                      float velocity) {\n  // Synchronize scroll end event to UI thread by AnimationEventService.\n  if (animation_event_service_) {\n    animation_event_service_->OnScrollEnd(element_id_, session_id,\n                                          scroll_offset, velocity);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/animation/animation_mutator.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_ANIMATION_ANIMATION_MUTATOR_H_\n#define CLAY_FLOW_ANIMATION_ANIMATION_MUTATOR_H_\n\n#include <memory>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/common/element_id.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/flow/animation/scroll_offset_animation.h\"\n#include \"clay/flow/services/animation_event_service.h\"\n#include \"clay/gfx/animation/animator_target.h\"\n#include \"clay/gfx/animation/keyframes_manager.h\"\n#include \"clay/gfx/animation/transition_manager.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n#ifndef ENABLE_SKITY\n#include \"clay/gfx/skia/picture_skia.h\"\n#endif\n#include \"skity/geometry/rect.hpp\"\n#include \"skity/geometry/vector.hpp\"\n\n#ifdef ENABLE_SKITY\nnamespace clay {\nclass PictureSkity;\n}  // namespace clay\n#endif\n\nnamespace clay {\n\nclass PictureMutator;\nclass Layer;\nclass OpacityMutator;\nclass ScrollOffsetMutator;\nclass TransformMutator;\n\nenum class AnimationMutatorType {\n  kTransform,\n  kOpacity,\n  kPicture,\n  kScrollOffset\n};\n\n// Stores animation information like Keyframes or Transition.\nclass AnimationMutator : public clay::AnimatorTarget,\n                         public clay::AnimationEventHandler {\n public:\n  AnimationMutator(const clay::ElementId& element_id, uint64_t layer_id);\n  ~AnimationMutator();\n\n  static std::shared_ptr<AnimationMutator> Create(\n      const clay::ElementId& element_id, const AnimationMutatorType& type,\n      Layer* layer);\n\n  virtual AnimationMutatorType GetType() const = 0;\n  virtual PictureMutator* asPicture() { return nullptr; }\n  virtual OpacityMutator* asOpacity() { return nullptr; }\n  virtual TransformMutator* asTransform() { return nullptr; }\n  virtual ScrollOffsetMutator* asScrollOffset() { return nullptr; }\n\n  uint64_t layer_id() const { return layer_id_; }\n\n  clay::FloatSize PercentageResolutionSize() override { return {}; }\n\n  const clay::ElementId& element_id() const { return element_id_; }\n\n  void SetServiceManager(std::shared_ptr<clay::ServiceManager> service_manager);\n  void ResetServiceManager();\n  void AddTransitionManager(std::unique_ptr<clay::TransitionManager> manager);\n  void AddKeyframesManager(std::unique_ptr<clay::KeyframesManager> manager);\n  void SetScrollOffsetAnimation(\n      std::shared_ptr<ScrollOffsetAnimation> animation);\n\n  void SyncProperties(const std::shared_ptr<AnimationMutator> mutator);\n\n  bool HasSameElementId(const std::shared_ptr<AnimationMutator> mutator) const;\n  bool HasAnimationRunning() const;\n  bool DoAnimationFrame(int64_t frame_time);\n\n protected:\n  void OnAnimationEvent(const clay::AnimationParams& animation_params) override;\n  void OnTransitionEvent(const clay::AnimationParams& animation_params,\n                         ClayAnimationPropertyType property_type) override;\n\n  clay::ElementId element_id_;\n  uint64_t layer_id_ = 0;\n  std::vector<std::unique_ptr<clay::KeyframesManager>> keyframes_managers_;\n  std::vector<std::unique_ptr<clay::TransitionManager>> transition_managers_;\n  std::shared_ptr<ScrollOffsetAnimation> scroll_offset_animation_;\n  clay::Puppet<clay::Owner::kUI, AnimationEventService>\n      animation_event_service_;\n};\n\nclass TransformMutator : public AnimationMutator {\n public:\n  TransformMutator(const clay::ElementId& element_id, uint64_t layer_id,\n                   const clay::TransformOperations& transform)\n      : AnimationMutator(element_id, layer_id), transform_(transform) {}\n\n  AnimationMutatorType GetType() const override {\n    return AnimationMutatorType::kTransform;\n  }\n  TransformMutator* asTransform() override { return this; }\n\n  const clay::TransformOperations& transform() const { return transform_; }\n\n private:\n  void GetProperty(ClayAnimationPropertyType type,\n                   clay::TransformOperations& value) override;\n  void SetProperty(ClayAnimationPropertyType type,\n                   const clay::TransformOperations& value,\n                   bool skip_update_for_raster_animation) override;\n\n  clay::TransformOperations transform_;\n};\n\nclass OpacityMutator : public AnimationMutator {\n public:\n  OpacityMutator(const clay::ElementId& element_id, uint64_t layer_id,\n                 float alpha)\n      : AnimationMutator(element_id, layer_id), alpha_(alpha) {}\n\n  AnimationMutatorType GetType() const override {\n    return AnimationMutatorType::kOpacity;\n  }\n  OpacityMutator* asOpacity() override { return this; }\n  float alpha() const { return alpha_ * 255; }\n\n private:\n  void GetProperty(ClayAnimationPropertyType type, float& value) override;\n  void SetProperty(ClayAnimationPropertyType type, float value,\n                   bool skip_update_for_raster_animation) override;\n\n  float alpha_;\n};\n\nclass PictureMutator : public AnimationMutator {\n public:\n  PictureMutator(const clay::ElementId& element_id, uint64_t layer_id,\n#ifndef ENABLE_SKITY\n                 clay::PictureSkia* picture\n#else\n                 clay::PictureSkity* picture\n#endif  // ENABLE_SKITY\n                 )\n      : AnimationMutator(element_id, layer_id), picture_(picture) {\n  }\n\n  AnimationMutatorType GetType() const override {\n    return AnimationMutatorType::kPicture;\n  }\n  PictureMutator* asPicture() override { return this; }\n\n private:\n  void GetProperty(ClayAnimationPropertyType type, clay::Color& value) override;\n  void SetProperty(ClayAnimationPropertyType type, const clay::Color& value,\n                   bool skip_update_for_raster_animation) override;\n\n#ifndef ENABLE_SKITY\n  clay::PictureSkia* picture_;\n#else\n  clay::PictureSkity* picture_;\n#endif  // ENABLE_SKITY\n};\n\nclass ScrollOffsetMutator : public AnimationMutator {\n public:\n  ScrollOffsetMutator(const clay::ElementId& element_id, uint64_t layer_id,\n                      const skity::Matrix& transform)\n      : AnimationMutator(element_id, layer_id), transform_(transform) {}\n  AnimationMutatorType GetType() const override {\n    return AnimationMutatorType::kScrollOffset;\n  }\n  ScrollOffsetMutator* asScrollOffset() override { return this; }\n\n  void Initialize(const skity::Vec2& offset, const skity::Vec2& scroll_offset,\n                  const skity::Rect& visible_offset_range,\n                  const skity::Rect& max_offset_range);\n\n  const skity::Vec2& GetScrollOffset() const;\n  const skity::Rect& GetVisibleOffsetRange() const;\n  const skity::Rect& GetMaxOffsetRange() const;\n  const skity::Matrix& transform() const { return transform_; }\n\n  void UpdateScrollOffset(const skity::Vec2& scroll_offset);\n\n  // The scroll animation events sent by ScrollOffsetAnimation, and will be sent\n  // to UI layer by `animation_event_service_`.\n  void OnScrolled(int32_t session_id, float scroll_offset,\n                  bool ignore_ui_repaint);\n  void OnScrollEnd(int32_t session_id, float scroll_offset, float velocity);\n\n private:\n  skity::Matrix transform_;\n  // Static offset from layout includes border, padding and margin. It's\n  // immutable.\n  skity::Vec2 offset_;\n  // Scroll offset, it's mutable and may be changed by raster animation.\n  skity::Vec2 scroll_offset_;\n  skity::Rect visible_offset_range_;\n  skity::Rect max_offset_range_;\n};\n\n// A AnimationMutators owns a stack of all Keyframes and Transition attached to\n// a single target Layer.\nusing AnimationMutators =\n    std::unordered_map<uint64_t, std::shared_ptr<AnimationMutator>>;\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_ANIMATION_ANIMATION_MUTATOR_H_\n"
  },
  {
    "path": "clay/flow/animation/scroll_offset_animation.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/animation/scroll_offset_animation.h\"\n\n#include <cmath>\n\n#include \"clay/flow/animation/animation_mutator.h\"\n\nnamespace clay {\n\nScrollOffsetAnimation::~ScrollOffsetAnimation() {\n  if (animator_->IsRunning()) {\n    animator_->Cancel();\n  }\n}\n\nvoid ScrollOffsetAnimation::StartIfNeeded() {\n  if (has_started_) {\n    return;\n  }\n  has_started_ = true;\n  animator_->SetAnimationHandler(&animation_handler_);\n  animator_->FlingInitialize();\n  animator_->Start();\n}\n\nbool ScrollOffsetAnimation::IsRunning() const { return animator_->IsRunning(); }\n\nbool ScrollOffsetAnimation::DoAnimationFrame(\n    int64_t frame_time, AnimationMutator* animation_mutator) {\n  ScrollOffsetMutator* mutator = animation_mutator->asScrollOffset();\n  if (!mutator) {\n    return true;\n  }\n  StartIfNeeded();\n  if (!animator_->IsRunning()) {\n    if (has_started_) {\n      // Should apply the current scroll offset to `mutator` even when the\n      // animation has reached the end, or the `mutator` may be got the wrong\n      // value from UI layer.\n      mutator->UpdateScrollOffset(current_scroll_offset_);\n    }\n    return true;\n  }\n  bool ignore_ui_repaint = true;\n  animator_->DoAnimationFrame(frame_time);\n  float value = animator_->GetValue();\n  auto& max_offset_range = mutator->GetMaxOffsetRange();\n  auto& visible_offset_range = mutator->GetVisibleOffsetRange();\n  auto scroll_offset = mutator->GetScrollOffset();\n  // If the value has reached the limit, it means that the animation should be\n  // finished.\n  if (direction_ == ScrollDirection::kVertical) {\n    if (value <= max_offset_range.Top() || value >= max_offset_range.Bottom()) {\n      animator_->Cancel();\n    }\n    value =\n        std::clamp(value, max_offset_range.Top(), max_offset_range.Bottom());\n  } else {\n    if (value <= max_offset_range.Left() || value >= max_offset_range.Right()) {\n      animator_->Cancel();\n    }\n    value =\n        std::clamp(value, max_offset_range.Left(), max_offset_range.Right());\n  }\n  float update_value = value;\n  // Clamped the value by visible offset range.\n  if (direction_ == ScrollDirection::kVertical) {\n    update_value = std::clamp(update_value, visible_offset_range.Top(),\n                              visible_offset_range.Bottom());\n  } else {\n    update_value = std::clamp(update_value, visible_offset_range.Left(),\n                              visible_offset_range.Right());\n  }\n  if (update_value != value) {\n    ignore_ui_repaint = false;\n  }\n  update_value = std::round(-update_value);\n  if (direction_ == ScrollDirection::kVertical) {\n    current_scroll_offset_ = {scroll_offset.x, update_value};\n  } else {\n    current_scroll_offset_ = {update_value, scroll_offset.y};\n  }\n  mutator->UpdateScrollOffset(current_scroll_offset_);\n  // Send the scroll animation events to the UI thread, and it will synchronize\n  // the animation value to the ScrollView.\n  mutator->OnScrolled(session_id_, value, ignore_ui_repaint);\n  bool stopped = !animator_->IsRunning();\n  if (stopped) {\n    // If the animation is stopped, send the scroll end event to the UI thread.\n    mutator->OnScrollEnd(session_id_, value, animator_->GetCurrentVelocity());\n  }\n  return stopped;\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/animation/scroll_offset_animation.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_ANIMATION_SCROLL_OFFSET_ANIMATION_H_\n#define CLAY_FLOW_ANIMATION_SCROLL_OFFSET_ANIMATION_H_\n\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/animation/fling_animator.h\"\n#include \"clay/gfx/scroll_direction.h\"\n#include \"skity/geometry/vector.hpp\"\n\nnamespace clay {\nclass AnimationMutator;\n\n// The scroll offset animation is used to run raster scroll animation. It's\n// managed by `ScrollOffsetMutator`.\nclass ScrollOffsetAnimation {\n public:\n  ScrollOffsetAnimation(int32_t session_id, ScrollDirection direction,\n                        std::unique_ptr<clay::FlingAnimator> animator)\n      : session_id_(session_id),\n        direction_(direction),\n        animator_(std::move(animator)) {}\n  ~ScrollOffsetAnimation();\n\n  clay::FlingAnimator* GetAnimator() { return animator_.get(); }\n\n  void StartIfNeeded();\n\n  bool IsRunning() const;\n  bool DoAnimationFrame(int64_t frame_time, AnimationMutator* mutator);\n\n private:\n  int32_t session_id_;\n  ScrollDirection direction_;\n  std::unique_ptr<clay::FlingAnimator> animator_;\n  clay::AnimationHandler animation_handler_;\n  bool has_started_ = false;\n  skity::Vec2 current_scroll_offset_;\n};\n\n}  // namespace clay\n#endif  // CLAY_FLOW_ANIMATION_SCROLL_OFFSET_ANIMATION_H_\n"
  },
  {
    "path": "clay/flow/animation/scroll_offset_animation_unittests.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstdint>\n#include <memory>\n\n#include \"clay/common/element_id.h\"\n#include \"clay/flow/animation/animation_mutator.h\"\n#include \"clay/flow/animation/scroll_offset_animation.h\"\n#include \"clay/flow/layers/transform_layer.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(ScrollOffsetAnimation, Empty) {\n  clay::ElementId element_id(1);\n  float start_value = 0;\n  auto animator = std::make_unique<clay::FlingAnimator>();\n  animator->SetFriction(1.0f);\n  animator->SetStartValue(start_value);\n  animator->SetStartVelocity(5000);\n  animator->FlingInitialize();\n  // Bind animation to RenderScroll.\n  auto scroll_offset_animation = std::make_shared<clay::ScrollOffsetAnimation>(\n      0, clay::ScrollDirection::kVertical, std::move(animator));\n  auto layer = std::make_shared<TransformLayer>(skity::Matrix::Translate(0, 0));\n  auto mutator = AnimationMutator::Create(\n      element_id, AnimationMutatorType::kScrollOffset, layer.get());\n  mutator->asScrollOffset()->Initialize(skity::Vec2(0, 0), skity::Vec2(0, 0),\n                                        skity::Rect::MakeEmpty(),\n                                        skity::Rect::MakeEmpty());\n  mutator->SetScrollOffsetAnimation(scroll_offset_animation);\n  scroll_offset_animation->StartIfNeeded();\n  int64_t now = scroll_offset_animation->GetAnimator()->GetLastAnimationTime();\n  ASSERT_EQ(mutator->HasAnimationRunning(), true);\n  ASSERT_EQ(mutator->DoAnimationFrame(now), true);\n}\n\nTEST(ScrollOffsetAnimation, ScrollAnimation) {\n  clay::ElementId element_id(1);\n  float start_value = 0;\n  auto animator = std::make_unique<clay::FlingAnimator>();\n  animator->SetFriction(1.0f);\n  animator->SetStartValue(start_value);\n  animator->SetStartVelocity(5000);\n  animator->FlingInitialize();\n  auto scroll_offset_animation = std::make_shared<clay::ScrollOffsetAnimation>(\n      0, clay::ScrollDirection::kVertical, std::move(animator));\n  auto layer = std::make_shared<TransformLayer>(skity::Matrix::Translate(0, 0));\n  auto mutator = AnimationMutator::Create(\n      element_id, AnimationMutatorType::kScrollOffset, layer.get());\n  mutator->asScrollOffset()->Initialize(skity::Vec2(0, 0), skity::Vec2(0, 0),\n                                        skity::Rect::MakeLTRB(0, 0, 600, 600),\n                                        skity::Rect::MakeLTRB(0, 0, 600, 600));\n  mutator->SetScrollOffsetAnimation(scroll_offset_animation);\n  scroll_offset_animation->StartIfNeeded();\n  int64_t now = scroll_offset_animation->GetAnimator()->GetLastAnimationTime();\n  ASSERT_EQ(mutator->HasAnimationRunning(), true);\n  ASSERT_EQ(mutator->DoAnimationFrame(now), false);\n  ASSERT_EQ(mutator->DoAnimationFrame(now + 100), false);\n  ASSERT_EQ(mutator->asScrollOffset()->GetScrollOffset().y, -470);\n  ASSERT_EQ(mutator->DoAnimationFrame(now + 1000), true);\n  ASSERT_EQ(mutator->HasAnimationRunning(), false);\n\n  ASSERT_EQ(mutator->asScrollOffset()->GetScrollOffset().y, -600);\n}\n\nTEST(ScrollOffsetAnimation, ScrollAnimationHorizontal) {\n  clay::ElementId element_id(1);\n  float start_value = 0;\n  auto animator = std::make_unique<clay::FlingAnimator>();\n  animator->SetFriction(1.0f);\n  animator->SetStartValue(start_value);\n  animator->SetStartVelocity(5000);\n  animator->FlingInitialize();\n  auto scroll_offset_animation = std::make_shared<clay::ScrollOffsetAnimation>(\n      0, clay::ScrollDirection::kHorizontal, std::move(animator));\n  auto layer = std::make_shared<TransformLayer>(skity::Matrix::Translate(0, 0));\n  auto mutator = AnimationMutator::Create(\n      element_id, AnimationMutatorType::kScrollOffset, layer.get());\n  mutator->asScrollOffset()->Initialize(skity::Vec2(0, 0), skity::Vec2(0, 0),\n                                        skity::Rect::MakeLTRB(0, 0, 600, 600),\n                                        skity::Rect::MakeLTRB(0, 0, 600, 600));\n  mutator->SetScrollOffsetAnimation(scroll_offset_animation);\n  scroll_offset_animation->StartIfNeeded();\n  int64_t now = scroll_offset_animation->GetAnimator()->GetLastAnimationTime();\n  ASSERT_EQ(mutator->HasAnimationRunning(), true);\n  ASSERT_EQ(mutator->DoAnimationFrame(now), false);\n  ASSERT_EQ(mutator->DoAnimationFrame(now + 100), false);\n  ASSERT_EQ(mutator->asScrollOffset()->GetScrollOffset().x, -470);\n  ASSERT_EQ(mutator->DoAnimationFrame(now + 1000), true);\n  ASSERT_EQ(mutator->HasAnimationRunning(), false);\n\n  ASSERT_EQ(mutator->asScrollOffset()->GetScrollOffset().x, -600);\n}\n\nTEST(ScrollOffsetAnimation, VisibleScrollOffset) {\n  clay::ElementId element_id(1);\n  float start_value = 0;\n  auto animator = std::make_unique<clay::FlingAnimator>();\n  animator->SetFriction(1.0f);\n  animator->SetStartValue(start_value);\n  animator->SetStartVelocity(5000);\n  animator->FlingInitialize();\n  auto scroll_offset_animation = std::make_shared<clay::ScrollOffsetAnimation>(\n      0, clay::ScrollDirection::kVertical, std::move(animator));\n  auto layer = std::make_shared<TransformLayer>(skity::Matrix::Translate(0, 0));\n  auto mutator = AnimationMutator::Create(\n      element_id, AnimationMutatorType::kScrollOffset, layer.get());\n  mutator->asScrollOffset()->Initialize(skity::Vec2(0, 0), skity::Vec2(0, 0),\n                                        skity::Rect::MakeLTRB(0, 0, 300, 300),\n                                        skity::Rect::MakeLTRB(0, 0, 600, 600));\n  mutator->SetScrollOffsetAnimation(scroll_offset_animation);\n  scroll_offset_animation->StartIfNeeded();\n  int64_t now = scroll_offset_animation->GetAnimator()->GetLastAnimationTime();\n  ASSERT_EQ(mutator->HasAnimationRunning(), true);\n  ASSERT_EQ(mutator->DoAnimationFrame(now), false);\n  ASSERT_EQ(mutator->DoAnimationFrame(now + 100), false);\n  // Limited by visible offset range.\n  ASSERT_EQ(mutator->asScrollOffset()->GetScrollOffset().y, -300);\n  ASSERT_EQ(mutator->DoAnimationFrame(now + 1000), true);\n  ASSERT_EQ(mutator->HasAnimationRunning(), false);\n\n  // Limited by visible offset range.\n  ASSERT_EQ(mutator->asScrollOffset()->GetScrollOffset().y, -300);\n}\n\nTEST(ScrollOffsetAnimation, VisibleScrollOffsetHorizontal) {\n  clay::ElementId element_id(1);\n  float start_value = 0;\n  auto animator = std::make_unique<clay::FlingAnimator>();\n  animator->SetFriction(1.0f);\n  animator->SetStartValue(start_value);\n  animator->SetStartVelocity(5000);\n  animator->FlingInitialize();\n  auto scroll_offset_animation = std::make_shared<clay::ScrollOffsetAnimation>(\n      0, clay::ScrollDirection::kHorizontal, std::move(animator));\n  auto layer = std::make_shared<TransformLayer>(skity::Matrix::Translate(0, 0));\n  auto mutator = AnimationMutator::Create(\n      element_id, AnimationMutatorType::kScrollOffset, layer.get());\n  mutator->asScrollOffset()->Initialize(skity::Vec2(0, 0), skity::Vec2(0, 0),\n                                        skity::Rect::MakeLTRB(0, 0, 300, 300),\n                                        skity::Rect::MakeLTRB(0, 0, 600, 600));\n  mutator->SetScrollOffsetAnimation(scroll_offset_animation);\n  scroll_offset_animation->StartIfNeeded();\n  int64_t now = scroll_offset_animation->GetAnimator()->GetLastAnimationTime();\n  ASSERT_EQ(mutator->HasAnimationRunning(), true);\n  ASSERT_EQ(mutator->DoAnimationFrame(now), false);\n  ASSERT_EQ(mutator->DoAnimationFrame(now + 100), false);\n  // Limited by visible offset range.\n  ASSERT_EQ(mutator->asScrollOffset()->GetScrollOffset().x, -300);\n  ASSERT_EQ(mutator->DoAnimationFrame(now + 1000), true);\n  ASSERT_EQ(mutator->HasAnimationRunning(), false);\n\n  // Limited by visible offset range.\n  ASSERT_EQ(mutator->asScrollOffset()->GetScrollOffset().x, -300);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/compositor/compositor_state.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/compositor/compositor_state.h\"\n\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n\nnamespace clay {\n\nCompositorState::CompositorState(skity::Vec2 frame_size)\n    : frame_size_(frame_size) {}\n\nCompositorState::~CompositorState() {}\n\nvoid CompositorState::PrerollCompositeEmbeddedView(\n    int view_id, std::unique_ptr<EmbeddedViewParams> params) {\n  TRACE_EVENT(\"clay\", \"CompositorState::PrerollCompositeEmbeddedView\");\n\n  skity::Rect view_bounds = skity::Rect::MakeSize(frame_size_);\n  std::unique_ptr<EmbedderViewSlice> view;\n#ifndef ENABLE_SKITY\n  view = std::make_unique<SkPictureEmbedderViewSlice>(view_bounds);\n#else\n  view = std::make_unique<SkityPictureEmbedderViewSlice>(view_bounds);\n#endif  // ENABLE_SKITY\n  composition_order_.push_back(view_id);\n  slices_.insert_or_assign(view_id, std::move(view));\n  view_params_[view_id] = std::move(params);\n}\n\nclay::GrCanvas* CompositorState::CompositeEmbeddedView(int view_id) {\n  if (slices_.count(view_id) == 1) {\n    return slices_.at(view_id)->canvas();\n  }\n  return nullptr;\n}\n\nvoid CompositorState::PushFilterToVisitedPlatformViews(\n    const std::shared_ptr<const clay::ImageFilter>& filter,\n    const skity::Rect& filter_rect) {\n  FML_UNIMPLEMENTED();\n}\n\nvoid CompositorState::PrerollOverlayView(\n    int view_id, std::unique_ptr<OverlayViewParams> params) {\n  TRACE_EVENT(\"clay\", \"CompositorState::PrerollCompositeEmbeddedView\");\n\n  std::unique_ptr<EmbedderViewSlice> view;\n#ifndef ENABLE_SKITY\n  view = std::make_unique<SkPictureEmbedderViewSlice>(params->bounds());\n#else\n  view = std::make_unique<SkityPictureEmbedderViewSlice>(params->bounds());\n#endif  // ENABLE_SKITY\n  composition_order_.push_back(view_id);\n  overlay_slices_.insert_or_assign(view_id, std::move(view));\n  overlay_view_params_[view_id] = std::move(params);\n}\n\nclay::GrCanvas* CompositorState::CompositeOverlayView(int view_id) {\n  if (overlay_slices_.count(view_id) == 1) {\n    return overlay_slices_.at(view_id)->canvas();\n  }\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/compositor/compositor_state.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_COMPOSITOR_COMPOSITOR_STATE_H_\n#define CLAY_FLOW_COMPOSITOR_COMPOSITOR_STATE_H_\n\n#include <memory>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/flow/compositor/overlay_views.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass CompositorState {\n public:\n  explicit CompositorState(skity::Vec2 frame_size);\n\n  ~CompositorState();\n\n  void PrerollCompositeEmbeddedView(int view_id,\n                                    std::unique_ptr<EmbeddedViewParams> params);\n\n  clay::GrCanvas* CompositeEmbeddedView(int view_id);\n\n  void PrerollOverlayView(int view_id,\n                          std::unique_ptr<OverlayViewParams> params);\n\n  clay::GrCanvas* CompositeOverlayView(int view_id);\n\n  void PushFilterToVisitedPlatformViews(\n      const std::shared_ptr<const clay::ImageFilter>& filter,\n      const skity::Rect& filter_rect);\n\n  const skity::Vec2& GetFrameSize() const { return frame_size_; }\n\n  std::vector<int64_t>& GetCompositionOrder() { return composition_order_; }\n\n  std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>& GetSlices() {\n    return slices_;\n  }\n\n  std::unordered_map<int64_t, std::unique_ptr<EmbeddedViewParams>>&\n  GetViewParams() {\n    return view_params_;\n  }\n\n private:\n  skity::Vec2 frame_size_;\n\n  // The order of composition. Each entry contains a unique id for the platform\n  // view or overlay view.\n  std::vector<int64_t> composition_order_;\n\n  // The |EmbedderViewSlice| implementation keyed off the platform view id,\n  // which contains any subsequent operations until the next platform view or\n  // the end of the last leaf node in the layer tree.\n  std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices_;\n\n  // The params for a platform view, which contains the size, position and\n  // mutation stack.\n  std::unordered_map<int64_t, std::unique_ptr<EmbeddedViewParams>> view_params_;\n\n  // slices for overlay views\n  std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>\n      overlay_slices_;\n\n  // The params for a overlay view, which contains the bounds rect.\n  std::unordered_map<int64_t, std::unique_ptr<OverlayViewParams>>\n      overlay_view_params_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_COMPOSITOR_COMPOSITOR_STATE_H_\n"
  },
  {
    "path": "clay/flow/compositor/compositor_state_unittests.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nnamespace testing {\n\nusing ::testing::Contains;\nusing ::testing::ElementsAre;\nusing ::testing::Key;\n\nTEST(CompositorStateTest, AddCompositorOrder) {\n  CompositorState compositor_state({64, 64});\n  compositor_state.PrerollCompositeEmbeddedView(\n      0, std::make_unique<EmbeddedViewParams>());\n  compositor_state.PrerollCompositeEmbeddedView(\n      1, std::make_unique<EmbeddedViewParams>());\n  EXPECT_THAT(compositor_state.GetCompositionOrder(), ElementsAre(0, 1));\n  EXPECT_THAT(compositor_state.GetViewParams(), Contains(Key(0)));\n  EXPECT_THAT(compositor_state.GetViewParams(), Contains(Key(1)));\n}\n\nTEST(CompositorStateTest, GetSlicingCanvas) {\n  CompositorState compositor_state({64, 64});\n  compositor_state.PrerollCompositeEmbeddedView(\n      0, std::make_unique<EmbeddedViewParams>());\n  EXPECT_NE(compositor_state.CompositeEmbeddedView(0), nullptr);\n}\n\n}  // namespace testing\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/compositor/overlay_views.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_COMPOSITOR_OVERLAY_VIEWS_H_\n#define CLAY_FLOW_COMPOSITOR_OVERLAY_VIEWS_H_\n\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nclass OverlayViewParams {\n public:\n  OverlayViewParams() = default;\n\n  explicit OverlayViewParams(skity::Rect bounds) : bounds_(bounds) {}\n\n  const skity::Rect& bounds() const { return bounds_; }\n\n private:\n  skity::Rect bounds_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_COMPOSITOR_OVERLAY_VIEWS_H_\n"
  },
  {
    "path": "clay/flow/compositor_context.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/compositor_context.h\"\n\n#include <optional>\n#include <utility>\n\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nstd::optional<skity::Rect> FrameDamage::ComputeClipRect(\n    clay::LayerTree& layer_tree, bool has_raster_cache) {\n  if (layer_tree.root_layer()) {\n    PaintRegionMap empty_paint_region_map;\n    DiffContext context(layer_tree.frame_size(),\n                        layer_tree.device_pixel_ratio(),\n                        layer_tree.paint_region_map(),\n                        prev_layer_tree_ ? prev_layer_tree_->paint_region_map()\n                                         : empty_paint_region_map,\n                        has_raster_cache);\n    context.PushCullRect(skity::Rect::MakeSize(layer_tree.frame_size()));\n    {\n      DiffContext::AutoSubtreeRestore subtree(&context);\n      const Layer* prev_root_layer = nullptr;\n      if (!prev_layer_tree_ ||\n          prev_layer_tree_->frame_size() != layer_tree.frame_size()) {\n        // If there is no previous layer tree assume the entire frame must be\n        // repainted.\n        context.MarkSubtreeDirty(\n            skity::Rect::MakeSize(layer_tree.frame_size()));\n      } else {\n        prev_root_layer = prev_layer_tree_->root_layer();\n      }\n      layer_tree.root_layer()->Diff(&context, prev_root_layer);\n    }\n\n    damage_ =\n        context.ComputeDamage(additional_damage_, horizontal_clip_alignment_,\n                              vertical_clip_alignment_);\n    return damage_->buffer_damage;\n  } else {\n    return std::nullopt;\n  }\n}\n\nCompositorContext::CompositorContext()\n    : drawable_image_registry_(std::make_shared<DrawableImageRegistry>()),\n      raster_time_(fixed_refresh_rate_updater_),\n      ui_time_(fixed_refresh_rate_updater_) {}\n\nCompositorContext::CompositorContext(Stopwatch::RefreshRateUpdater& updater)\n    : drawable_image_registry_(std::make_shared<DrawableImageRegistry>()),\n      raster_time_(updater),\n      ui_time_(updater) {}\n\nCompositorContext::~CompositorContext() = default;\n\nvoid CompositorContext::BeginFrame(ScopedFrame& frame,\n                                   bool enable_instrumentation) {\n  if (enable_instrumentation) {\n    frame_count_.Increment();\n    raster_time_.Start();\n  }\n}\n\nvoid CompositorContext::EndFrame(ScopedFrame& frame,\n                                 bool enable_instrumentation) {\n  if (enable_instrumentation) {\n    raster_time_.Stop();\n  }\n}\n\nstd::unique_ptr<CompositorContext::ScopedFrame> CompositorContext::AcquireFrame(\n    clay::GrContext* gr_context, clay::GrCanvas* canvas,\n    CompositorState* compositor_state,\n    const skity::Matrix& root_surface_transformation,\n    bool instrumentation_enabled, bool surface_supports_readback) {\n  return std::make_unique<ScopedFrame>(\n      *this, gr_context, canvas, compositor_state, root_surface_transformation,\n      instrumentation_enabled, surface_supports_readback);\n}\n\nCompositorContext::ScopedFrame::ScopedFrame(\n    CompositorContext& context, clay::GrContext* gr_context,\n    clay::GrCanvas* canvas, CompositorState* compositor_state,\n    const skity::Matrix& root_surface_transformation,\n    bool instrumentation_enabled, bool surface_supports_readback)\n    : context_(context),\n      compositor_state_(compositor_state),\n      gr_context_(gr_context),\n      canvas_(canvas),\n      root_surface_transformation_(root_surface_transformation),\n      instrumentation_enabled_(instrumentation_enabled),\n      surface_supports_readback_(surface_supports_readback) {\n  context_.BeginFrame(*this, instrumentation_enabled_);\n}\n\nCompositorContext::ScopedFrame::~ScopedFrame() {\n  context_.EndFrame(*this, instrumentation_enabled_);\n}\n\nRasterStatus CompositorContext::ScopedFrame::Raster(\n    clay::LayerTree& layer_tree, bool ignore_raster_cache,\n    FrameDamage* frame_damage, uint32_t background_color,\n    std::function<void()> before_draw_callback) {\n  TRACE_EVENT(\"clay\", \"CompositorContext::ScopedFrame::Raster\");\n\n  std::optional<skity::Rect> clip_rect =\n      frame_damage\n          ? frame_damage->ComputeClipRect(layer_tree, !ignore_raster_cache)\n          : std::nullopt;\n\n  bool root_needs_readback = layer_tree.Preroll(\n      *this, ignore_raster_cache, clip_rect ? *clip_rect : kGiantRect);\n  bool needs_save_layer = root_needs_readback && !surface_supports_readback();\n\n  if (before_draw_callback) {\n    before_draw_callback();\n  }\n\n  clay::GrAutoCanvasRestore restore(canvas(), clip_rect.has_value());\n  if (canvas()) {\n    if (clip_rect) {\n      CANVAS_CLIP_RECT(canvas(), *clip_rect);\n    }\n    if (needs_save_layer) {\n      TRACE_EVENT(\"clay\", \"Canvas::saveLayer\");\n      auto bounds = skity::Rect::MakeSize(layer_tree.frame_size());\n      clay::GrPaint paint;\n      PAINT_SET_BLEND_MODE(paint, clay::BlendMode::kSrc);\n      CANVAS_SAVELAYER(canvas(), bounds, paint);\n    }\n\n    CANVAS_CLEAR(canvas(), clay::Color(background_color));\n  }\n\n  layer_tree.Paint(*this, ignore_raster_cache);\n\n  if (canvas() && needs_save_layer) {\n    CANVAS_RESTORE(canvas());\n  }\n\n  return RasterStatus::kSuccess;\n}\n\nvoid CompositorContext::OnGrContextCreated() {\n  drawable_image_registry_->OnGrContextCreated();\n  raster_cache_.Clear();\n}\n\nvoid CompositorContext::OnGrContextDestroyed() {\n  drawable_image_registry_->OnGrContextDestroyed();\n  raster_cache_.Clear();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/compositor_context.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_COMPOSITOR_CONTEXT_H_\n#define CLAY_FLOW_COMPOSITOR_CONTEXT_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/raster_thread_merger.h\"\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"clay/flow/diff_context.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/flow/layer_snapshot_store.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/stopwatch.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass LayerTree;\n\nenum class RasterStatus {\n  // Frame has been successfully rasterized.\n  kSuccess,\n  // Failed to rasterize the frame.\n  kFailed,\n  // Layer tree was discarded due to LayerTreeDiscardCallback or inability to\n  // access the GPU.\n  kDiscarded,\n};\n\nclass FrameDamage {\n public:\n  // Sets previous layer tree for calculating frame damage. If not set, entire\n  // frame will be repainted.\n  void SetPreviousLayerTree(const LayerTree* prev_layer_tree) {\n    prev_layer_tree_ = prev_layer_tree;\n  }\n\n  // Adds additional damage (accumulated for double / triple buffering).\n  // This is area that will be repainted alongside any changed part.\n  void AddAdditionalDamage(const skity::Rect& damage) {\n    additional_damage_.Join(damage);\n  }\n\n  // Specifies clip rect alignment.\n  void SetClipAlignment(int horizontal, int vertical) {\n    horizontal_clip_alignment_ = horizontal;\n    vertical_clip_alignment_ = vertical;\n  }\n\n  // Calculates clip rect for current rasterization. This is diff of layer tree\n  // and previous layer tree + any additional provided damage.\n  // If previous layer tree is not specified, clip rect will be null optional,\n  // but the paint region of layer_tree will be calculated so that it can be\n  // used for diffing of subsequent frames.\n  std::optional<skity::Rect> ComputeClipRect(clay::LayerTree& layer_tree,\n                                             bool has_raster_cache);\n\n  // See Damage::frame_damage.\n  std::optional<skity::Rect> GetFrameDamage() const {\n    return damage_ ? std::make_optional(damage_->frame_damage) : std::nullopt;\n  }\n\n  // See Damage::buffer_damage.\n  std::optional<skity::Rect> GetBufferDamage() {\n    return damage_ ? std::make_optional(damage_->buffer_damage) : std::nullopt;\n  }\n\n private:\n  skity::Rect additional_damage_ = skity::Rect::MakeEmpty();\n  std::optional<Damage> damage_;\n  const LayerTree* prev_layer_tree_ = nullptr;\n  int vertical_clip_alignment_ = 1;\n  int horizontal_clip_alignment_ = 1;\n};\n\nclass CompositorContext {\n public:\n  class ScopedFrame {\n   public:\n    ScopedFrame(CompositorContext& context, clay::GrContext* gr_context,\n                clay::GrCanvas* canvas, CompositorState* compositor_state,\n                const skity::Matrix& root_surface_transformation,\n                bool instrumentation_enabled, bool surface_supports_readback);\n\n    ~ScopedFrame();\n\n    clay::GrCanvas* canvas() { return canvas_; }\n    clay::GrContext* gr_context() const { return gr_context_; }\n    CompositorState* compositor_state() const { return compositor_state_; }\n\n    CompositorContext& context() const { return context_; }\n\n    const skity::Matrix& root_surface_transformation() const {\n      return root_surface_transformation_;\n    }\n\n    bool surface_supports_readback() { return surface_supports_readback_; }\n\n    RasterStatus Raster(\n        LayerTree& layer_tree, bool ignore_raster_cache,\n        FrameDamage* frame_damage,\n        uint32_t background_color = Color::kTransparent().Value(),\n        std::function<void()> before_draw_callback = nullptr);\n\n   private:\n    CompositorContext& context_;\n    CompositorState* compositor_state_;\n    clay::GrContext* gr_context_;\n    clay::GrCanvas* canvas_;\n    const skity::Matrix root_surface_transformation_;\n    const bool instrumentation_enabled_;\n    const bool surface_supports_readback_;\n\n    BASE_DISALLOW_COPY_AND_ASSIGN(ScopedFrame);\n  };\n\n  CompositorContext();\n\n  explicit CompositorContext(Stopwatch::RefreshRateUpdater& updater);\n\n  virtual ~CompositorContext();\n\n  virtual std::unique_ptr<ScopedFrame> AcquireFrame(\n      clay::GrContext* gr_context, clay::GrCanvas* canvas,\n      CompositorState* compositor_state,\n      const skity::Matrix& root_surface_transformation,\n      bool instrumentation_enabled, bool surface_supports_readback);\n  void OnGrContextCreated();\n\n  void OnGrContextDestroyed();\n\n  RasterCache& raster_cache() { return raster_cache_; }\n\n  std::shared_ptr<DrawableImageRegistry> drawable_image_registry() {\n    return drawable_image_registry_;\n  }\n\n  const Counter& frame_count() const { return frame_count_; }\n\n  const Stopwatch& raster_time() const { return raster_time_; }\n\n  Stopwatch& ui_time() { return ui_time_; }\n\n  LayerSnapshotStore& snapshot_store() { return layer_snapshot_store_; }\n\n  std::vector<RasterCacheInfo>* GetRasterCacheInfo() {\n    return raster_cache_.GetRasterCacheInfo();\n  }\n\n private:\n  RasterCache raster_cache_;\n  std::shared_ptr<DrawableImageRegistry> drawable_image_registry_;\n  Counter frame_count_;\n  Stopwatch raster_time_;\n  Stopwatch ui_time_;\n  LayerSnapshotStore layer_snapshot_store_;\n\n  /// Only used by default constructor of `CompositorContext`.\n  FixedRefreshRateUpdater fixed_refresh_rate_updater_;\n\n  void BeginFrame(ScopedFrame& frame, bool enable_instrumentation);\n\n  void EndFrame(ScopedFrame& frame, bool enable_instrumentation);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(CompositorContext);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_COMPOSITOR_CONTEXT_H_\n"
  },
  {
    "path": "clay/flow/diff_context.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/diff_context.h\"\n\n#include <inttypes.h>\n\n#include <algorithm>\n\n#include \"clay/flow/layers/layer.h\"\n\nnamespace clay {\n\nDiffContext::DiffContext(skity::Vec2 frame_size,\n                         double frame_device_pixel_ratio,\n                         PaintRegionMap& this_frame_paint_region_map,\n                         const PaintRegionMap& last_frame_paint_region_map,\n                         bool has_raster_cache)\n    : rects_(std::make_shared<std::vector<skity::Rect>>()),\n      frame_size_(frame_size),\n      frame_device_pixel_ratio_(frame_device_pixel_ratio),\n      this_frame_paint_region_map_(this_frame_paint_region_map),\n      last_frame_paint_region_map_(last_frame_paint_region_map),\n      has_raster_cache_(has_raster_cache) {}\n\nvoid DiffContext::BeginSubtree() {\n  state_stack_.push_back(state_);\n  state_.rect_index_ = rects_->size();\n  state_.has_filter_bounds_adjustment = false;\n  state_.has_drawable_image = false;\n  state_.has_animation = false;\n  state_.has_deferred_image = false;\n  if (state_.transform_override) {\n    state_.transform = *state_.transform_override;\n    state_.transform_override = std::nullopt;\n  }\n}\n\nvoid DiffContext::EndSubtree() {\n  FML_DCHECK(!state_stack_.empty());\n  if (state_.has_filter_bounds_adjustment) {\n    filter_bounds_adjustment_stack_.pop_back();\n  }\n  state_ = state_stack_.back();\n  state_stack_.pop_back();\n}\n\nDiffContext::State::State()\n    : dirty(false),\n      cull_rect(kGiantRect),\n      rect_index_(0),\n      has_filter_bounds_adjustment(false),\n      has_drawable_image(false),\n      has_animation(false) {}\n\nvoid DiffContext::PushTransform(const skity::Matrix& transform) {\n  state_.transform.PreConcat(transform);\n}\n\nvoid DiffContext::SetTransform(const skity::Matrix& transform) {\n  state_.transform_override = transform;\n}\n\nvoid DiffContext::PushFilterBoundsAdjustment(\n    const FilterBoundsAdjustment& filter) {\n  FML_DCHECK(state_.has_filter_bounds_adjustment == false);\n  state_.has_filter_bounds_adjustment = true;\n  filter_bounds_adjustment_stack_.push_back(filter);\n}\n\nskity::Rect DiffContext::ApplyFilterBoundsAdjustment(skity::Rect rect) const {\n  // Apply filter bounds adjustment in reverse order\n  for (auto i = filter_bounds_adjustment_stack_.rbegin();\n       i != filter_bounds_adjustment_stack_.rend(); ++i) {\n    rect = (*i)(rect);\n  }\n  return rect;\n}\n\nvoid DiffContext::AlignRect(skity::Rect& rect, int horizontal_alignment,\n                            int vertical_alignment) const {\n  int top = rect.Top();\n  int left = rect.Left();\n  int right = rect.Right();\n  int bottom = rect.Bottom();\n  if (top % vertical_alignment != 0) {\n    top -= top % vertical_alignment;\n  }\n  if (left % horizontal_alignment != 0) {\n    left -= left % horizontal_alignment;\n  }\n  if (right % horizontal_alignment != 0) {\n    right += horizontal_alignment - right % horizontal_alignment;\n  }\n  if (bottom % vertical_alignment != 0) {\n    bottom += vertical_alignment - bottom % vertical_alignment;\n  }\n  right = std::min(right, static_cast<int>(frame_size_.x));\n  bottom = std::min(bottom, static_cast<int>(frame_size_.y));\n  rect = skity::Rect::MakeLTRB(left, top, right, bottom);\n}\n\nDamage DiffContext::ComputeDamage(const skity::Rect& accumulated_buffer_damage,\n                                  int horizontal_clip_alignment,\n                                  int vertical_clip_alignment) const {\n  Damage res;\n  skity::Rect& buffer_damage = res.buffer_damage;\n  buffer_damage = accumulated_buffer_damage;\n  buffer_damage.Join(damage_);\n  skity::Rect& frame_damage = res.frame_damage;\n  frame_damage = damage_;\n\n  for (const auto& r : readbacks_) {\n    skity::Rect rect = r.rect;\n    if (skity::Rect::Intersect(rect, frame_damage)) {\n      frame_damage.Join(rect);\n    }\n    if (skity::Rect::Intersect(rect, buffer_damage)) {\n      buffer_damage.Join(rect);\n    }\n  }\n\n  buffer_damage.RoundOut();\n  frame_damage.RoundOut();\n\n  skity::Rect frame_clip = skity::Rect::MakeSize(frame_size_);\n  buffer_damage.Intersect(frame_clip);\n  frame_damage.Intersect(frame_clip);\n\n  if (horizontal_clip_alignment > 1 || vertical_clip_alignment > 1) {\n    AlignRect(buffer_damage, horizontal_clip_alignment,\n              vertical_clip_alignment);\n    AlignRect(frame_damage, horizontal_clip_alignment, vertical_clip_alignment);\n  }\n  return res;\n}\n\nbool DiffContext::PushCullRect(const skity::Rect& clip) {\n  skity::Rect cull_rect = state_.transform.MapRect(clip);\n  return state_.cull_rect.Intersect(cull_rect);\n}\n\nskity::Rect DiffContext::GetCullRect() const {\n  skity::Matrix inverse_transform;\n  // Perspective projections don't produce rectangles that are useful for\n  // culling for some reason.\n  if (!state_.transform.HasPersp() &&\n      state_.transform.Invert(&inverse_transform)) {\n    return inverse_transform.MapRect(state_.cull_rect);\n  } else {\n    return kGiantRect;\n  }\n}\n\nvoid DiffContext::MarkSubtreeDirty(const PaintRegion& previous_paint_region) {\n  FML_DCHECK(!IsSubtreeDirty());\n  if (previous_paint_region.is_valid()) {\n    AddDamage(previous_paint_region);\n  }\n  state_.dirty = true;\n}\n\nvoid DiffContext::MarkSubtreeDirty(const skity::Rect& previous_paint_region) {\n  FML_DCHECK(!IsSubtreeDirty());\n  AddDamage(previous_paint_region);\n  state_.dirty = true;\n}\n\nvoid DiffContext::AddLayerBounds(const skity::Rect& rect) {\n  // During painting we cull based on non-overriden transform and then\n  // override the transform right before paint. Do the same thing here to get\n  // identical paint rect.\n  auto transformed_rect =\n      ApplyFilterBoundsAdjustment(state_.transform.MapRect(rect));\n  if (skity::Rect::Intersect(transformed_rect, state_.cull_rect)) {\n    auto paint_rect = state_.transform_override\n                          ? ApplyFilterBoundsAdjustment(\n                                state_.transform_override->MapRect(rect))\n                          : transformed_rect;\n    paint_rect.Intersect(state_.cull_rect);\n    rects_->push_back(paint_rect);\n    if (IsSubtreeDirty()) {\n      AddDamage(paint_rect);\n    }\n  }\n}\n\nvoid DiffContext::MarkSubtreeHasDrawableImageLayer() {\n  // Set the has_drawable_image flag on current state and all parent states.\n  // That way we'll know that we can't skip diff for retained layers because\n  // they contain a DrawableImageLayer.\n  for (auto& state : state_stack_) {\n    state.has_drawable_image = true;\n  }\n  state_.has_drawable_image = true;\n}\n\nvoid DiffContext::MarkSubtreeHasDeferredImage() {\n  for (auto& state : state_stack_) {\n    state.has_deferred_image = true;\n  }\n  state_.has_deferred_image = true;\n}\n\nvoid DiffContext::MarkSubtreeHasRasterAnimation() {\n  for (auto& state : state_stack_) {\n    state.has_animation = true;\n  }\n  state_.has_animation = true;\n}\n\nvoid DiffContext::AddExistingPaintRegion(const PaintRegion& region) {\n  // Adding paint region for retained layer implies that current subtree is not\n  // dirty, so we know, for example, that the inherited transforms must match\n  FML_DCHECK(!IsSubtreeDirty());\n  if (region.is_valid()) {\n    rects_->insert(rects_->end(), region.begin(), region.end());\n  }\n}\n\nvoid DiffContext::AddReadbackRegion(const skity::Rect& rect) {\n  Readback readback;\n  readback.rect = rect;\n  readback.position = rects_->size();\n  // Push empty rect as a placeholder for position in current subtree\n  rects_->push_back(skity::Rect::MakeEmpty());\n  readbacks_.push_back(readback);\n}\n\nPaintRegion DiffContext::CurrentSubtreeRegion() const {\n  bool has_readback = std::any_of(\n      readbacks_.begin(), readbacks_.end(),\n      [&](const Readback& r) { return r.position >= state_.rect_index_; });\n  return PaintRegion(rects_, state_.rect_index_, rects_->size(), has_readback,\n                     state_.has_drawable_image, state_.has_animation,\n                     state_.has_deferred_image);\n}\n\nvoid DiffContext::AddDamage(const PaintRegion& damage) {\n  FML_DCHECK(damage.is_valid());\n  for (const auto& r : damage) {\n    damage_.Join(r);\n  }\n}\n\nvoid DiffContext::AddDamage(const skity::Rect& rect) { damage_.Join(rect); }\n\nvoid DiffContext::SetLayerPaintRegion(const Layer* layer,\n                                      const PaintRegion& region) {\n  this_frame_paint_region_map_[layer->unique_id()] = region;\n}\n\nPaintRegion DiffContext::GetOldLayerPaintRegion(const Layer* layer) const {\n  auto i = last_frame_paint_region_map_.find(layer->unique_id());\n  if (i != last_frame_paint_region_map_.end()) {\n    return i->second;\n  } else {\n    // This is valid when Layer::PreservePaintRegion is called for retained\n    // layer with zero sized parent clip (these layers are not diffed)\n    return PaintRegion();\n  }\n}\n\nvoid DiffContext::Statistics::LogStatistics() {\n#if ENABLE_TRACE_PERFETTO\n  auto trace_func = [&](const char* name, size_t count) {\n    char buf[128] = {0};\n    std::sprintf(buf, \"DiffContext.%s_%\" PRIxPTR, name,\n                 reinterpret_cast<uintptr_t>(this));\n    TRACE_COUNTER(\"clay\", lynx::perfetto::CounterTrack(buf), count);\n  };\n  trace_func(\"NewPictures\", new_pictures_);\n  trace_func(\"PicturesTooComplexToCompare\", pictures_too_complex_to_compare_);\n  trace_func(\"DeepComparePictures\", deep_compare_pictures_);\n  trace_func(\"SameInstancePictures\", same_instance_pictures_);\n  trace_func(\"DifferentInstanceButEqualPictures\",\n             different_instance_but_equal_pictures_);\n#endif  // !FLUTTER_RELEASE\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/diff_context.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_DIFF_CONTEXT_H_\n#define CLAY_FLOW_DIFF_CONTEXT_H_\n\n#include <functional>\n#include <map>\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/paint_region.h\"\n#include \"skity/geometry/matrix.hpp\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nclass Layer;\n\n// Represents area that needs to be updated in front buffer (frame_damage) and\n// area that is going to be painted to in back buffer (buffer_damage).\nstruct Damage {\n  // This is the damage between current and previous frame;\n  // If embedder supports partial update, this is the region that needs to be\n  // repainted.\n  // Corresponds to \"surface damage\" from EGL_KHR_partial_update.\n  skity::Rect frame_damage;\n\n  // Reflects actual change to target framebuffer; This is frame_damage +\n  // damage previously accumulated for target framebuffer.\n  // All drawing will be clipped to this region. Knowing the affected area\n  // upfront may be useful for tile based GPUs.\n  // Corresponds to \"buffer damage\" from EGL_KHR_partial_update.\n  skity::Rect buffer_damage;\n};\n\n// Layer Unique Id to PaintRegion\nusing PaintRegionMap = std::map<uint64_t, PaintRegion>;\n\n// Tracks state during tree diffing process and computes resulting damage\nclass DiffContext {\n public:\n  explicit DiffContext(skity::Vec2 frame_size, double device_pixel_aspect_ratio,\n                       PaintRegionMap& this_frame_paint_region_map,\n                       const PaintRegionMap& last_frame_paint_region_map,\n                       bool has_raster_cache);\n\n  // Starts a new subtree.\n  void BeginSubtree();\n\n  // Ends current subtree; All modifications to state (transform, cull_rect,\n  // dirty) will be restored\n  void EndSubtree();\n\n  // Creates subtree in current scope and closes it on scope exit\n  class AutoSubtreeRestore {\n    BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(AutoSubtreeRestore);\n\n   public:\n    explicit AutoSubtreeRestore(DiffContext* context) : context_(context) {\n      context->BeginSubtree();\n    }\n    ~AutoSubtreeRestore() { context_->EndSubtree(); }\n\n   private:\n    DiffContext* context_;\n  };\n\n  // Pushes additional transform for current subtree\n  void PushTransform(const skity::Matrix& transform);\n\n  // Pushes cull rect for current subtree\n  bool PushCullRect(const skity::Rect& clip);\n\n  // Function that adjusts layer bounds (in device coordinates) depending\n  // on filter.\n  using FilterBoundsAdjustment = std::function<skity::Rect(skity::Rect)>;\n\n  // Pushes filter bounds adjustment to current subtree. Every layer in this\n  // subtree will have bounds adjusted by this function.\n  void PushFilterBoundsAdjustment(const FilterBoundsAdjustment& filter);\n\n  // Returns transform matrix for current subtree\n  const skity::Matrix& GetTransform() const { return state_.transform; }\n\n  // Overrides transform matrix for current subtree\n  void SetTransform(const skity::Matrix& transform);\n\n  // Return cull rect for current subtree (in local coordinates)\n  skity::Rect GetCullRect() const;\n\n  // Sets the dirty flag on current subtree;\n  //\n  // previous_paint_region, which should represent region of previous subtree\n  // at this level will be added to damage area.\n  //\n  // Each paint region added to dirty subtree (through AddPaintRegion) is also\n  // added to damage.\n  void MarkSubtreeDirty(\n      const PaintRegion& previous_paint_region = PaintRegion());\n  void MarkSubtreeDirty(const skity::Rect& previous_paint_region);\n\n  bool IsSubtreeDirty() const { return state_.dirty; }\n\n  // Marks that current subtree contains a DrawableImageLayer. This is needed to\n  // ensure that we'll Diff the DrawableImageLayer even if inside retained\n  // layer.\n  void MarkSubtreeHasDrawableImageLayer();\n\n  // Same reason as above\n  void MarkSubtreeHasRasterAnimation();\n\n  void MarkSubtreeHasDeferredImage();\n\n  // Add layer bounds to current paint region; rect is in \"local\" (layer)\n  // coordinates.\n  void AddLayerBounds(const skity::Rect& rect);\n\n  // Add entire paint region of retained layer for current subtree. This can\n  // only be used in subtrees that are not dirty, otherwise ancestor transforms\n  // or clips may result in different paint region.\n  void AddExistingPaintRegion(const PaintRegion& region);\n\n  // The idea of readback region is that if any part of the readback region\n  // needs to be repainted, then the whole readback region must be repainted;\n  //\n  // Readback rect is in screen coordinates.\n  void AddReadbackRegion(const skity::Rect& rect);\n\n  // Returns the paint region for current subtree; Each rect in paint region is\n  // in screen coordinates; Once a layer accumulates the paint regions of its\n  // children, this PaintRegion value can be associated with the current layer\n  // using DiffContext::SetLayerPaintRegion.\n  PaintRegion CurrentSubtreeRegion() const;\n\n  // Computes final damage\n  //\n  // additional_damage is the previously accumulated frame_damage for\n  // current framebuffer\n  //\n  // clip_alignment controls the alignment of resulting frame and surface\n  // damage.\n  Damage ComputeDamage(const skity::Rect& additional_damage,\n                       int horizontal_clip_alignment = 0,\n                       int vertical_clip_alignment = 0) const;\n\n  double frame_device_pixel_ratio() const { return frame_device_pixel_ratio_; };\n\n  // Adds the region to current damage. Used for removed layers, where instead\n  // of diffing the layer its paint region is directly added to damage.\n  void AddDamage(const PaintRegion& damage);\n\n  // Associates the paint region with specified layer and current layer tree.\n  // The paint region can not be stored directly in layer itself, because same\n  // retained layer instance can possibly paint in different locations depending\n  // on ancestor layers.\n  void SetLayerPaintRegion(const Layer* layer, const PaintRegion& region);\n\n  // Retrieves the paint region associated with specified layer and previous\n  // frame layer tree.\n  PaintRegion GetOldLayerPaintRegion(const Layer* layer) const;\n\n  // Whether or not a raster cache is being used. If so, we must snap\n  // all transformations to physical pixels if the layer may be raster\n  // cached.\n  bool has_raster_cache() const { return has_raster_cache_; }\n\n  class Statistics {\n   public:\n    // Picture replaced by different picture\n    void AddNewPicture() { ++new_pictures_; }\n\n    // Picture that would require deep comparison but was considered too complex\n    // to serialize and thus was treated as new picture\n    void AddPictureTooComplexToCompare() { ++pictures_too_complex_to_compare_; }\n\n    // Picture that has identical instance between frames\n    void AddSameInstancePicture() { ++same_instance_pictures_; };\n\n    // Picture that had to be serialized to compare for equality\n    void AddDeepComparePicture() { ++deep_compare_pictures_; }\n\n    // Picture that had to be serialized to compare (different instances),\n    // but were equal\n    void AddDifferentInstanceButEqualPicture() {\n      ++different_instance_but_equal_pictures_;\n    };\n\n    // Logs the statistics to trace counter\n    void LogStatistics();\n\n   private:\n    int new_pictures_ = 0;\n    int pictures_too_complex_to_compare_ = 0;\n    int same_instance_pictures_ = 0;\n    int deep_compare_pictures_ = 0;\n    int different_instance_but_equal_pictures_ = 0;\n  };\n\n  Statistics& statistics() { return statistics_; }\n\n private:\n  struct State {\n    State();\n\n    bool dirty;\n    skity::Rect cull_rect;  // in screen coordinates\n\n    // In order to replicate paint process closely, we need both the original\n    // transform, and the overriden transform (set for layers that need to paint\n    // on integer coordinates). The reason for this is that during paint the\n    // transform matrix is overriden only after layer passes the cull check\n    // first (with original transform). So to cull layer we use transform, but\n    // to get paint coordinates we use transform_override. Child layers are\n    // painted after transform override, so if set we use transform_override as\n    // base when diffing child layers.\n    skity::Matrix transform;\n    std::optional<skity::Matrix> transform_override;\n    size_t rect_index_;\n\n    // Whether this subtree has filter bounds adjustment function. If so,\n    // it will need to be removed from stack when subtree is closed.\n    bool has_filter_bounds_adjustment;\n\n    // Whether there is a drawable image layer in this subtree.\n    bool has_drawable_image;\n    bool has_animation;\n    bool has_deferred_image;\n  };\n\n  std::shared_ptr<std::vector<skity::Rect>> rects_;\n  State state_;\n  skity::Vec2 frame_size_;\n  double frame_device_pixel_ratio_;\n  std::vector<State> state_stack_;\n  std::vector<FilterBoundsAdjustment> filter_bounds_adjustment_stack_;\n\n  // Applies the filter bounds adjustment stack on provided rect.\n  // Rect must be in device coordinates.\n  skity::Rect ApplyFilterBoundsAdjustment(skity::Rect rect) const;\n\n  skity::Rect damage_ = skity::Rect::MakeEmpty();\n\n  PaintRegionMap& this_frame_paint_region_map_;\n  const PaintRegionMap& last_frame_paint_region_map_;\n  bool has_raster_cache_;\n\n  void AddDamage(const skity::Rect& rect);\n\n  void AlignRect(skity::Rect& rect, int horizontal_alignment,\n                 int vertical_clip_alignment) const;\n\n  struct Readback {\n    // Index of rects_ entry that this readback belongs to. Used to\n    // determine if subtree has any readback\n    size_t position;\n\n    // readback area, in screen coordinates\n    skity::Rect rect;\n  };\n\n  std::vector<Readback> readbacks_;\n  Statistics statistics_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_DIFF_CONTEXT_H_\n"
  },
  {
    "path": "clay/flow/diff_context_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/diff_context_test.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST_F(DiffContextTest, ClipAlignment) {\n  MockLayerTree t1;\n  t1.root()->Add(CreatePictureLayer(\n      CreatePicture(skity::Rect::MakeLTRB(30, 30, 50, 50), 1)));\n  auto damage =\n      DiffLayerTree(t1, MockLayerTree(), skity::Rect::MakeEmpty(), 0, 0);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(30, 30, 50, 50));\n  EXPECT_EQ(damage.buffer_damage, skity::Rect::MakeLTRB(30, 30, 50, 50));\n\n  damage = DiffLayerTree(t1, MockLayerTree(), skity::Rect::MakeEmpty(), 1, 1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(30, 30, 50, 50));\n  EXPECT_EQ(damage.buffer_damage, skity::Rect::MakeLTRB(30, 30, 50, 50));\n\n  damage = DiffLayerTree(t1, MockLayerTree(), skity::Rect::MakeEmpty(), 8, 1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(24, 30, 56, 50));\n  EXPECT_EQ(damage.buffer_damage, skity::Rect::MakeLTRB(24, 30, 56, 50));\n\n  damage = DiffLayerTree(t1, MockLayerTree(), skity::Rect::MakeEmpty(), 1, 8);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(30, 24, 50, 56));\n  EXPECT_EQ(damage.buffer_damage, skity::Rect::MakeLTRB(30, 24, 50, 56));\n\n  damage = DiffLayerTree(t1, MockLayerTree(), skity::Rect::MakeEmpty(), 16, 16);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(16, 16, 64, 64));\n  EXPECT_EQ(damage.buffer_damage, skity::Rect::MakeLTRB(16, 16, 64, 64));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/drawable_image_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/flow/testing/mock_drawable_image.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(DrawableImageRegistryTest, UnregisterDrawableImageCallbackTriggered) {\n  DrawableImageRegistry registry;\n  auto mock_image1 = std::make_shared<MockDrawableImage>();\n  int64_t id1 = mock_image1->Id();\n  auto mock_image2 = std::make_shared<MockDrawableImage>();\n  int64_t id2 = mock_image2->Id();\n\n  registry.RegisterDrawableImage(mock_image1);\n  registry.RegisterDrawableImage(mock_image2);\n  ASSERT_EQ(registry.GetDrawableImage(id1), mock_image1);\n  ASSERT_EQ(registry.GetDrawableImage(id2), mock_image2);\n  ASSERT_FALSE(mock_image1->unregistered());\n  ASSERT_FALSE(mock_image2->unregistered());\n\n  registry.UnregisterDrawableImage(id1);\n  ASSERT_EQ(registry.GetDrawableImage(id1), nullptr);\n  ASSERT_TRUE(mock_image1->unregistered());\n  ASSERT_FALSE(mock_image2->unregistered());\n\n  registry.UnregisterDrawableImage(id2);\n  ASSERT_EQ(registry.GetDrawableImage(id2), nullptr);\n  ASSERT_TRUE(mock_image1->unregistered());\n  ASSERT_TRUE(mock_image2->unregistered());\n}\n\nTEST(DrawableImageRegistryTest, GrContextCallbackTriggered) {\n  DrawableImageRegistry registry;\n  auto mock_image1 = std::make_shared<MockDrawableImage>();\n  int64_t id1 = mock_image1->Id();\n  auto mock_image2 = std::make_shared<MockDrawableImage>();\n\n  registry.RegisterDrawableImage(mock_image1);\n  registry.RegisterDrawableImage(mock_image2);\n  ASSERT_FALSE(mock_image1->gr_context_created());\n  ASSERT_FALSE(mock_image2->gr_context_created());\n  ASSERT_FALSE(mock_image1->gr_context_destroyed());\n  ASSERT_FALSE(mock_image2->gr_context_destroyed());\n\n  registry.OnGrContextCreated();\n  ASSERT_TRUE(mock_image1->gr_context_created());\n  ASSERT_TRUE(mock_image2->gr_context_created());\n\n  registry.UnregisterDrawableImage(id1);\n  registry.OnGrContextDestroyed();\n  ASSERT_FALSE(mock_image1->gr_context_destroyed());\n  ASSERT_TRUE(mock_image2->gr_context_created());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/embedded_view_params_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cmath>\n\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/fml/logging.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(EmbeddedViewParams, GetBoundingRectAfterMutationsWithNoMutations) {\n  MutatorsStack stack;\n  skity::Matrix matrix;\n\n  EmbeddedViewParams params(matrix, skity::Vec2(1, 1), stack);\n  const skity::Rect& rect = params.finalBoundingRect();\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.X(), 0));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Y(), 0));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Width(), 1));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Height(), 1));\n}\n\nTEST(EmbeddedViewParams, GetBoundingRectAfterMutationsWithScale) {\n  MutatorsStack stack;\n  skity::Matrix matrix = skity::Matrix::Scale(2, 2);\n  stack.PushTransform(matrix);\n\n  EmbeddedViewParams params(matrix, skity::Vec2(1, 1), stack);\n  const skity::Rect& rect = params.finalBoundingRect();\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.X(), 0));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Y(), 0));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Width(), 2));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Height(), 2));\n}\n\nTEST(EmbeddedViewParams, GetBoundingRectAfterMutationsWithTranslate) {\n  MutatorsStack stack;\n  skity::Matrix matrix = skity::Matrix::Translate(1, 1);\n  stack.PushTransform(matrix);\n\n  EmbeddedViewParams params(matrix, skity::Vec2(1, 1), stack);\n  const skity::Rect& rect = params.finalBoundingRect();\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.X(), 1));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Y(), 1));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Width(), 1));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Height(), 1));\n}\n\nTEST(EmbeddedViewParams, GetBoundingRectAfterMutationsWithRotation90) {\n  MutatorsStack stack;\n  skity::Matrix matrix = skity::Matrix::RotateDeg(90);\n  stack.PushTransform(matrix);\n\n  EmbeddedViewParams params(matrix, skity::Vec2(1, 1), stack);\n  const skity::Rect& rect = params.finalBoundingRect();\n\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.X(), -1));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Y(), 0));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Width(), 1));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Height(), 1));\n}\n\nTEST(EmbeddedViewParams, GetBoundingRectAfterMutationsWithRotation45) {\n  MutatorsStack stack;\n  skity::Matrix matrix = skity::Matrix::RotateDeg(45);\n  stack.PushTransform(matrix);\n\n  EmbeddedViewParams params(matrix, skity::Vec2(1, 1), stack);\n  const skity::Rect& rect = params.finalBoundingRect();\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.X(), -sqrt(2) / 2));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Y(), 0));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Width(), sqrt(2)));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Height(), sqrt(2)));\n}\n\nTEST(EmbeddedViewParams,\n     GetBoundingRectAfterMutationsWithTranslateScaleAndRotation) {\n  skity::Matrix matrix = skity::Matrix::Translate(2, 2);\n  matrix.PreScale(3, 3);\n  matrix.PreRotate(90);\n\n  MutatorsStack stack;\n  stack.PushTransform(matrix);\n\n  EmbeddedViewParams params(matrix, skity::Vec2(1, 1), stack);\n  const skity::Rect& rect = params.finalBoundingRect();\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.X(), -1));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Y(), 2));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Width(), 3));\n  ASSERT_TRUE(SkScalarNearlyEqual(rect.Height(), 3));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/embedded_views.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/embedded_views.h\"\n\n#ifndef ENABLE_SKITY\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#endif\n\nnamespace clay {\n#ifndef ENABLE_SKITY\nSkPictureEmbedderViewSlice::SkPictureEmbedderViewSlice(\n    skity::Rect view_bounds) {\n  auto rtree_factory = RTreeFactory();\n  rtree_ = rtree_factory.getInstance();\n  recorder_ = std::make_unique<SkPictureRecorder>();\n  recorder_->beginRecording(clay::ConvertSkityRectToSkRect(view_bounds),\n                            &rtree_factory);\n}\nSkCanvas* SkPictureEmbedderViewSlice::canvas() {\n  return recorder_->getRecordingCanvas();\n}\n\nvoid SkPictureEmbedderViewSlice::end_recording() {\n  picture_ = recorder_->finishRecordingAsPicture();\n}\n\nstd::list<skity::Rect>\nSkPictureEmbedderViewSlice::searchNonOverlappingDrawnRects(\n    const skity::Rect& query) const {\n  std::list<skity::Rect> result;\n  auto sk_result = rtree_->searchNonOverlappingDrawnRects(\n      clay::ConvertSkityRectToSkRect(query));\n  for (auto& rect : sk_result) {\n    result.push_back(clay::ConvertSkRectToSkityRect(rect));\n  }\n  return result;\n}\n\nvoid SkPictureEmbedderViewSlice::render_into(SkCanvas* canvas) {\n  canvas->drawPicture(picture_);\n}\n#else\nSkityPictureEmbedderViewSlice::SkityPictureEmbedderViewSlice(\n    skity::Rect view_bounds) {\n  recorder_ = std::make_unique<skity::PictureRecorder>();\n  recorder_->BeginRecording();\n}\n\nskity::Canvas* SkityPictureEmbedderViewSlice::canvas() {\n  return recorder_->GetRecordingCanvas();\n}\n\nvoid SkityPictureEmbedderViewSlice::end_recording() {\n  picture_ = recorder_->FinishRecording();\n}\n\nstd::list<skity::Rect>\nSkityPictureEmbedderViewSlice::searchNonOverlappingDrawnRects(\n    const skity::Rect& query) const {\n  return {query};\n}\n\nvoid SkityPictureEmbedderViewSlice::render_into(skity::Canvas* canvas) {\n  picture_->Draw(canvas);\n}\n#endif  // ENABLE_SKITY\n\nvoid MutatorsStack::PushClipRect(const skity::Rect& rect) {\n  std::shared_ptr<Mutator> element = std::make_shared<Mutator>(rect);\n  vector_.push_back(element);\n};\n\nvoid MutatorsStack::PushClipRRect(const skity::RRect& rrect) {\n  std::shared_ptr<Mutator> element = std::make_shared<Mutator>(rrect);\n  vector_.push_back(element);\n};\n\nvoid MutatorsStack::PushClipPath(const clay::GrPath& path) {\n  std::shared_ptr<Mutator> element = std::make_shared<Mutator>(path);\n  vector_.push_back(element);\n}\n\nvoid MutatorsStack::PushTransform(const skity::Matrix& matrix) {\n  std::shared_ptr<Mutator> element = std::make_shared<Mutator>(matrix);\n  vector_.push_back(element);\n};\n\nvoid MutatorsStack::PushOpacity(const int& alpha) {\n  std::shared_ptr<Mutator> element = std::make_shared<Mutator>(alpha);\n  vector_.push_back(element);\n};\n\nvoid MutatorsStack::PushBackdropFilter(\n    const std::shared_ptr<const clay::ImageFilter>& filter,\n    const skity::Rect& filter_rect) {\n  std::shared_ptr<Mutator> element =\n      std::make_shared<Mutator>(filter, filter_rect);\n  vector_.push_back(element);\n};\n\nvoid MutatorsStack::Pop() { vector_.pop_back(); };\n\nvoid MutatorsStack::PopTo(size_t stack_count) {\n  while (vector_.size() > stack_count) {\n    Pop();\n  }\n}\n\nconst std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator\nMutatorsStack::Top() const {\n  return vector_.rend();\n};\n\nconst std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator\nMutatorsStack::Bottom() const {\n  return vector_.rbegin();\n};\n\nconst std::vector<std::shared_ptr<Mutator>>::const_iterator\nMutatorsStack::Begin() const {\n  return vector_.begin();\n};\n\nconst std::vector<std::shared_ptr<Mutator>>::const_iterator MutatorsStack::End()\n    const {\n  return vector_.end();\n};\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/embedded_views.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_EMBEDDED_VIEWS_H_\n#define CLAY_FLOW_EMBEDDED_VIEWS_H_\n\n#include <list>\n#include <memory>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/raster_thread_merger.h\"\n#include \"clay/flow/surface_frame.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/image_filter.h\"\n\nnamespace clay {\n\nenum MutatorType {\n  kClipRect,\n  kClipRRect,\n  kClipPath,\n  kTransform,\n  kOpacity,\n  kBackdropFilter\n};\n\n// Represents an image filter mutation.\n//\n// Should be used for image_filter_layer and backdrop_filter_layer.\n// TODO(cyanglaz): Refactor this into a ImageFilterMutator class.\n// https://github.com/flutter/flutter/issues/108470\nclass ImageFilterMutation {\n public:\n  ImageFilterMutation(std::shared_ptr<const clay::ImageFilter> filter,\n                      const skity::Rect& filter_rect)\n      : filter_(filter), filter_rect_(filter_rect) {}\n\n  const clay::ImageFilter& GetFilter() const { return *filter_; }\n  const skity::Rect& GetFilterRect() const { return filter_rect_; }\n\n  bool operator==(const ImageFilterMutation& other) const {\n    return *filter_ == *other.filter_ && filter_rect_ == other.filter_rect_;\n  }\n\n  bool operator!=(const ImageFilterMutation& other) const {\n    return !operator==(other);\n  }\n\n private:\n  std::shared_ptr<const clay::ImageFilter> filter_;\n  const skity::Rect filter_rect_;\n};\n\n// Stores mutation information like clipping or kTransform.\n//\n// The `type` indicates the type of the mutation: kClipRect, kTransform and etc.\n// Each `type` is paired with an object that supports the mutation. For example,\n// if the `type` is kClipRect, `rect()` is used the represent the rect to be\n// clipped. One mutation object must only contain one type of mutation.\nclass Mutator {\n public:\n  Mutator(const Mutator& other) {\n    type_ = other.type_;\n    switch (other.type_) {\n      case kClipRect:\n        rect_ = other.rect_;\n        break;\n      case kClipRRect:\n        rrect_ = other.rrect_;\n        break;\n      case kClipPath:\n        path_ = new clay::GrPath(*other.path_);\n        break;\n      case kTransform:\n        matrix_ = other.matrix_;\n        break;\n      case kOpacity:\n        alpha_ = other.alpha_;\n        break;\n      case kBackdropFilter:\n        filter_mutation_ = other.filter_mutation_;\n        break;\n      default:\n        break;\n    }\n  }\n\n  explicit Mutator(const skity::Rect& rect) : type_(kClipRect), rect_(rect) {}\n  explicit Mutator(const skity::RRect& rrect)\n      : type_(kClipRRect), rrect_(rrect) {}\n  explicit Mutator(const clay::GrPath& path)\n      : type_(kClipPath), path_(new clay::GrPath(path)) {}\n  explicit Mutator(const skity::Matrix& matrix)\n      : type_(kTransform), matrix_(matrix) {}\n  explicit Mutator(const int& alpha) : type_(kOpacity), alpha_(alpha) {}\n  explicit Mutator(std::shared_ptr<const clay::ImageFilter> filter,\n                   const skity::Rect& filter_rect)\n      : type_(kBackdropFilter),\n        filter_mutation_(\n            std::make_shared<ImageFilterMutation>(filter, filter_rect)) {}\n\n  const MutatorType& GetType() const { return type_; }\n  const skity::Rect& GetRect() const { return rect_; }\n  const skity::RRect& GetRRect() const { return rrect_; }\n  const clay::GrPath& GetPath() const { return *path_; }\n  const skity::Matrix& GetMatrix() const { return matrix_; }\n  const ImageFilterMutation& GetFilterMutation() const {\n    return *filter_mutation_;\n  }\n  const int& GetAlpha() const { return alpha_; }\n  float GetAlphaFloat() const { return (alpha_ / 255.0); }\n\n  bool operator==(const Mutator& other) const {\n    if (type_ != other.type_) {\n      return false;\n    }\n    switch (type_) {\n      case kClipRect:\n        return rect_ == other.rect_;\n      case kClipRRect:\n        return rrect_ == other.rrect_;\n      case kClipPath:\n        return *path_ == *other.path_;\n      case kTransform:\n        return matrix_ == other.matrix_;\n      case kOpacity:\n        return alpha_ == other.alpha_;\n      case kBackdropFilter:\n        return *filter_mutation_ == *other.filter_mutation_;\n    }\n\n    return false;\n  }\n\n  bool operator!=(const Mutator& other) const { return !operator==(other); }\n\n  bool IsClipType() {\n    return type_ == kClipRect || type_ == kClipRRect || type_ == kClipPath;\n  }\n\n  ~Mutator() {\n    if (type_ == kClipPath) {\n      delete path_;\n    }\n  }\n\n private:\n  MutatorType type_;\n\n  // TODO(cyanglaz): Remove union.\n  //  https://github.com/flutter/flutter/issues/108470\n  union {\n    skity::Rect rect_;\n    skity::RRect rrect_;\n    skity::Matrix matrix_;\n    clay::GrPath* path_;\n    int alpha_;\n  };\n\n  std::shared_ptr<ImageFilterMutation> filter_mutation_;\n};  // Mutator\n\n// A stack of mutators that can be applied to an embedded platform view.\n//\n// The stack may include mutators like transforms and clips, each mutator\n// applies to all the mutators that are below it in the stack and to the\n// embedded view.\n//\n// For example consider the following stack: [T1, T2, T3], where T1 is the top\n// of the stack and T3 is the bottom of the stack. Applying this mutators stack\n// to a platform view P1 will result in T1(T2(T3(P1))).\nclass MutatorsStack {\n public:\n  MutatorsStack() = default;\n\n  void PushClipRect(const skity::Rect& rect);\n  void PushClipRRect(const skity::RRect& rrect);\n  void PushClipPath(const clay::GrPath& path);\n  void PushTransform(const skity::Matrix& matrix);\n  void PushOpacity(const int& alpha);\n  void PushBackdropFilter(\n      const std::shared_ptr<const clay::ImageFilter>& filter,\n      const skity::Rect& filter_rect);\n\n  // Removes the `Mutator` on the top of the stack\n  // and destroys it.\n  void Pop();\n\n  void PopTo(size_t stack_count);\n\n  // Returns a reverse iterator pointing to the top of the stack, which is the\n  // mutator that is furtherest from the leaf node.\n  const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator Top()\n      const;\n  // Returns a reverse iterator pointing to the bottom of the stack, which is\n  // the mutator that is closest from the leaf node.\n  const std::vector<std::shared_ptr<Mutator>>::const_reverse_iterator Bottom()\n      const;\n\n  // Returns an iterator pointing to the beginning of the mutator vector, which\n  // is the mutator that is furtherest from the leaf node.\n  const std::vector<std::shared_ptr<Mutator>>::const_iterator Begin() const;\n\n  // Returns an iterator pointing to the end of the mutator vector, which is the\n  // mutator that is closest from the leaf node.\n  const std::vector<std::shared_ptr<Mutator>>::const_iterator End() const;\n\n  bool is_empty() const { return vector_.empty(); }\n  size_t stack_count() const { return vector_.size(); }\n\n  bool operator==(const MutatorsStack& other) const {\n    if (vector_.size() != other.vector_.size()) {\n      return false;\n    }\n    for (size_t i = 0; i < vector_.size(); i++) {\n      if (*vector_[i] != *other.vector_[i]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  bool operator==(const std::vector<Mutator>& other) const {\n    if (vector_.size() != other.size()) {\n      return false;\n    }\n    for (size_t i = 0; i < vector_.size(); i++) {\n      if (*vector_[i] != other[i]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  bool operator!=(const MutatorsStack& other) const {\n    return !operator==(other);\n  }\n\n  bool operator!=(const std::vector<Mutator>& other) const {\n    return !operator==(other);\n  }\n\n private:\n  std::vector<std::shared_ptr<Mutator>> vector_;\n};  // MutatorsStack\n\nclass EmbeddedViewParams {\n public:\n  EmbeddedViewParams() = default;\n\n  EmbeddedViewParams(skity::Matrix matrix, skity::Vec2 size_points,\n                     MutatorsStack mutators_stack)\n      : matrix_(matrix),\n        size_points_(size_points),\n        mutators_stack_(mutators_stack) {\n    clay::GrPath path;\n    skity::Rect starting_rect =\n        skity::Rect::MakeWH(size_points.x, size_points.y);\n    PATH_ADD_RECT(path, starting_rect);\n    PATH_TRANSFORM(path, matrix);\n    RECT_ASSIGN(final_bounding_rect_, PATH_GET_BOUNDS(path));\n  }\n\n  // The transformation Matrix corresponding to the sum of all the\n  // transformations in the platform view's mutator stack.\n  const skity::Matrix& transformMatrix() const { return matrix_; }\n  // The original size of the platform view before any mutation matrix is\n  // applied.\n  const skity::Vec2& sizePoints() const { return size_points_; }\n  // The mutators stack contains the detailed step by step mutations for this\n  // platform view.\n  const MutatorsStack& mutatorsStack() const { return mutators_stack_; }\n  // The bounding rect of the platform view after applying all the mutations.\n  //\n  // Clippings are ignored.\n  const skity::Rect& finalBoundingRect() const { return final_bounding_rect_; }\n\n  // Pushes the stored DlImageFilter object to the mutators stack.\n  void PushImageFilter(std::shared_ptr<const clay::ImageFilter> filter,\n                       const skity::Rect& filter_rect) {\n    mutators_stack_.PushBackdropFilter(filter, filter_rect);\n  }\n\n  bool operator==(const EmbeddedViewParams& other) const {\n    return size_points_ == other.size_points_ &&\n           mutators_stack_ == other.mutators_stack_ &&\n           final_bounding_rect_ == other.final_bounding_rect_ &&\n           matrix_ == other.matrix_;\n  }\n\n private:\n  skity::Matrix matrix_;\n  skity::Vec2 size_points_;\n  MutatorsStack mutators_stack_;\n  skity::Rect final_bounding_rect_;\n};\n\n// The |PlatformViewLayer| calls |CompositeEmbeddedView| in its |Paint|\n// method to replace the leaf_nodes_canvas and leaf_nodes_builder in its\n// |PaintContext| for subsequent layers in the frame to render into.\n// The builder value will only be supplied if the associated ScopedFrame\n// is being rendered to DisplayLists. The |EmbedderPaintContext| struct\n// allows the method to return both values.\nstruct EmbedderPaintContext {\n  clay::GrCanvas* canvas;\n};\n\n// The |EmbedderViewSlice| represents the details of recording all of\n// the layer tree rendering operations that appear between before, after\n// and between the embedded views. The Slice may be recorded into an\n// SkPicture.\nclass EmbedderViewSlice {\n public:\n  virtual ~EmbedderViewSlice() = default;\n  virtual clay::GrCanvas* canvas() = 0;\n  virtual void render_into(clay::GrCanvas* canvas) = 0;\n  virtual void end_recording() = 0;\n  virtual std::list<skity::Rect> searchNonOverlappingDrawnRects(\n      const skity::Rect& query) const = 0;\n};\n\n#ifndef ENABLE_SKITY\nclass SkPictureEmbedderViewSlice : public EmbedderViewSlice {\n public:\n  explicit SkPictureEmbedderViewSlice(skity::Rect view_bounds);\n  ~SkPictureEmbedderViewSlice() override = default;\n\n  SkCanvas* canvas() override;\n  void end_recording() override;\n  std::list<skity::Rect> searchNonOverlappingDrawnRects(\n      const skity::Rect& query) const override;\n  void render_into(SkCanvas* canvas) override;\n\n private:\n  std::unique_ptr<SkPictureRecorder> recorder_;\n  sk_sp<RTree> rtree_;\n  sk_sp<SkPicture> picture_;\n};\n#else\nclass SkityPictureEmbedderViewSlice : public EmbedderViewSlice {\n public:\n  explicit SkityPictureEmbedderViewSlice(skity::Rect view_bounds);\n  ~SkityPictureEmbedderViewSlice() override = default;\n\n  skity::Canvas* canvas() override;\n  void end_recording() override;\n  std::list<skity::Rect> searchNonOverlappingDrawnRects(\n      const skity::Rect& query) const override;\n  void render_into(skity::Canvas* canvas) override;\n\n private:\n  std::unique_ptr<skity::PictureRecorder> recorder_;\n  std::unique_ptr<skity::DisplayList> picture_;\n};\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_EMBEDDED_VIEWS_H_\n"
  },
  {
    "path": "clay/flow/flow_rendering_backend.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_FLOW_RENDERING_BACKEND_H_\n#define CLAY_FLOW_FLOW_RENDERING_BACKEND_H_\n\n#ifndef ENABLE_SKITY\n#include \"clay/flow/layers/offscreen_surface.h\"\n#include \"clay/flow/layers/picture_complexity_helper_skia.h\"\n#else\n#include \"clay/flow/layers/picture_complexity_calculator_skity.h\"\n#include \"clay/flow/layers/picture_complexity_helper_skity.h\"\n#include \"clay/gfx/skity/picture_skity.h\"\n#endif  // ENABLE_SKITY\n\n#endif  // CLAY_FLOW_FLOW_RENDERING_BACKEND_H_\n"
  },
  {
    "path": "clay/flow/flow_run_all_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"build/build_config.h\"\n#include \"clay/flow/flow_test_utils.h\"\n#include \"clay/fml/backtrace.h\"\n#include \"clay/fml/command_line.h\"\n#include \"clay/fml/logging.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nint main(int argc, char** argv) {\n  fml::InstallCrashHandler();\n  testing::InitGoogleTest(&argc, argv);\n  fml::CommandLine cmd = fml::CommandLineFromPlatformOrArgcArgv(argc, argv);\n\n#if defined(OS_FUCHSIA)\n  clay::SetGoldenDir(cmd.GetOptionValueWithDefault(\n      \"golden-dir\", \"/pkg/data/clay/testing/resources\"));\n#else\n  clay::SetGoldenDir(cmd.GetOptionValueWithDefault(\n      \"golden-dir\", \"lynx/clay/testing/resources\"));\n#endif\n  clay::SetFontFile(cmd.GetOptionValueWithDefault(\n      \"font-file\",\n      \"lynx/clay/third_party/txt/third_party/fonts/Roboto-Regular.ttf\"));\n  return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "clay/flow/flow_test_utils.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string>\n\nnamespace clay {\n\nstatic std::string gGoldenDir;\nstatic std::string gFontFile;\n\nconst std::string& GetGoldenDir() { return gGoldenDir; }\n\nvoid SetGoldenDir(const std::string& dir) { gGoldenDir = dir; }\n\nconst std::string& GetFontFile() { return gFontFile; }\n\nvoid SetFontFile(const std::string& file) { gFontFile = file; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/flow_test_utils.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_FLOW_FLOW_TEST_UTILS_H_\n#define CLAY_FLOW_FLOW_TEST_UTILS_H_\n\n#include <string>\n\nnamespace clay {\n\nconst std::string& GetGoldenDir();\n\nvoid SetGoldenDir(const std::string& dir);\n\nconst std::string& GetFontFile();\n\nvoid SetFontFile(const std::string& dir);\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_FLOW_TEST_UTILS_H_\n"
  },
  {
    "path": "clay/flow/frame_timings.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/frame_timings.h\"\n\n#include <utility>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/timer/time_utils.h\"\n#include \"clay/common/settings.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nstd::atomic<uint64_t> FrameTimingsRecorder::frame_number_gen_ = {1};\n\nFrameTimingsRecorder::FrameTimingsRecorder()\n    : frame_number_(frame_number_gen_++),\n      frame_number_trace_arg_val_(std::to_string(frame_number_)) {}\n\nFrameTimingsRecorder::FrameTimingsRecorder(uint64_t frame_number)\n    : frame_number_(frame_number),\n      frame_number_trace_arg_val_(std::to_string(frame_number_)) {}\n\nFrameTimingsRecorder::~FrameTimingsRecorder() = default;\n\nfml::TimePoint FrameTimingsRecorder::GetVsyncStartTime() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kVsync);\n  return vsync_start_;\n}\n\nfml::TimePoint FrameTimingsRecorder::GetVsyncTargetTime() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kVsync);\n  return vsync_target_;\n}\n\nfml::TimePoint FrameTimingsRecorder::GetBuildStartTime() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kBuildStart);\n  return build_start_;\n}\n\nfml::TimePoint FrameTimingsRecorder::GetBuildEndTime() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kBuildEnd);\n  return build_end_;\n}\n\nfml::TimePoint FrameTimingsRecorder::GetRasterStartTime() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kRasterStart);\n  return raster_start_;\n}\n\nfml::TimePoint FrameTimingsRecorder::GetRasterEndTime() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kRasterEnd);\n  return raster_end_;\n}\n\nfml::TimePoint FrameTimingsRecorder::GetRasterEndWallTime() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kRasterEnd);\n  return raster_end_wall_time_;\n}\n\nbool FrameTimingsRecorder::GetForced() const {\n  std::scoped_lock state_lock(state_mutex_);\n  return forced_;\n}\n\nfml::TimeDelta FrameTimingsRecorder::GetBuildDuration() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kBuildEnd);\n  return build_end_ - build_start_;\n}\n\n/// Count of the layer cache entries\nsize_t FrameTimingsRecorder::GetLayerCacheCount() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kRasterEnd);\n  return layer_cache_count_;\n}\n\n/// Total bytes in all layer cache entries\nsize_t FrameTimingsRecorder::GetLayerCacheBytes() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kRasterEnd);\n  return layer_cache_bytes_;\n}\n\n/// Count of the picture cache entries\nsize_t FrameTimingsRecorder::GetPictureCacheCount() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kRasterEnd);\n  return picture_cache_count_;\n}\n\n/// Total bytes in all picture cache entries\nsize_t FrameTimingsRecorder::GetPictureCacheBytes() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ >= State::kRasterEnd);\n  return picture_cache_bytes_;\n}\n\nvoid FrameTimingsRecorder::RecordVsync(fml::TimePoint vsync_start,\n                                       fml::TimePoint vsync_target) {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ == State::kUninitialized);\n  state_ = State::kVsync;\n  vsync_start_ = vsync_start;\n  vsync_target_ = vsync_target;\n  frame_start_time_ = vsync_start;\n  frame_target_time_ = vsync_target;\n}\n\nvoid FrameTimingsRecorder::RecordForced(bool forced) {\n  std::scoped_lock state_lock(state_mutex_);\n  forced_ = forced;\n}\n\nvoid FrameTimingsRecorder::RecordLastDrawVsyncTime(\n    fml::TimePoint last_draw_vsync) {\n  std::scoped_lock state_lock(state_mutex_);\n  last_draw_vsync_time_ = last_draw_vsync;\n}\n\nvoid FrameTimingsRecorder::RecordVsyncTimeOnGeneration(fml::TimePoint time) {\n  std::scoped_lock state_lock(state_mutex_);\n  vsync_time_on_generation_ = time;\n}\n\nvoid FrameTimingsRecorder::RecordVsyncSequenceId(int id) {\n  std::scoped_lock state_lock(state_mutex_);\n  vsync_sequence_id_ = id;\n}\n\nfml::TimePoint FrameTimingsRecorder::GetVsyncTimeOnGeneration() const {\n  std::scoped_lock state_lock(state_mutex_);\n  return vsync_time_on_generation_;\n}\n\nfml::TimePoint FrameTimingsRecorder::GetLastDrawVsyncTime() const {\n  std::scoped_lock state_lock(state_mutex_);\n  return last_draw_vsync_time_;\n}\n\nint FrameTimingsRecorder::GetVsyncSequenceId() const {\n  std::scoped_lock state_lock(state_mutex_);\n  return vsync_sequence_id_;\n}\n\nbool FrameTimingsRecorder::HasValidVsyncSequenceId() const {\n  std::scoped_lock state_lock(state_mutex_);\n  return vsync_sequence_id_ != -1;\n}\n\nvoid FrameTimingsRecorder::RecordBuildStart(fml::TimePoint build_start) {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ == State::kVsync);\n  state_ = State::kBuildStart;\n  build_start_ = build_start;\n}\n\nvoid FrameTimingsRecorder::RecordBuildEnd(fml::TimePoint build_end) {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ == State::kBuildStart);\n  state_ = State::kBuildEnd;\n  build_end_ = build_end;\n}\n\nvoid FrameTimingsRecorder::RecordRasterStart(fml::TimePoint raster_start) {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ == State::kBuildEnd);\n  state_ = State::kRasterStart;\n  raster_start_ = raster_start;\n}\n\nFrameTiming FrameTimingsRecorder::RecordRasterEnd(const RasterCache* cache) {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ == State::kRasterStart);\n  state_ = State::kRasterEnd;\n  raster_end_ = fml::TimePoint::Now();\n  raster_end_wall_time_ = fml::TimePoint::CurrentWallTime();\n  if (cache) {\n    const RasterCacheMetrics& layer_metrics = cache->layer_metrics();\n    const RasterCacheMetrics& picture_metrics = cache->picture_metrics();\n    layer_cache_count_ = layer_metrics.total_count();\n    layer_cache_bytes_ = layer_metrics.total_bytes();\n    picture_cache_count_ = picture_metrics.total_count();\n    picture_cache_bytes_ = picture_metrics.total_bytes();\n  } else {\n    layer_cache_count_ = layer_cache_bytes_ = picture_cache_count_ =\n        picture_cache_bytes_ = 0;\n  }\n  timing_.Set(FrameTiming::kVsyncStart, vsync_start_);\n  timing_.Set(FrameTiming::kBuildStart, build_start_);\n  timing_.Set(FrameTiming::kBuildFinish, build_end_);\n  timing_.Set(FrameTiming::kRasterStart, raster_start_);\n  timing_.Set(FrameTiming::kRasterFinish, raster_end_);\n  timing_.Set(FrameTiming::kRasterFinishWallTime, raster_end_wall_time_);\n  timing_.SetFrameNumber(GetFrameNumber());\n  timing_.SetRasterCacheStatistics(layer_cache_count_, layer_cache_bytes_,\n                                   picture_cache_count_, picture_cache_bytes_);\n  return timing_;\n}\n\nFrameTiming FrameTimingsRecorder::GetRecordedTime() const {\n  std::scoped_lock state_lock(state_mutex_);\n  FML_DCHECK(state_ == State::kRasterEnd);\n  return timing_;\n}\n\nstd::unique_ptr<FrameTimingsRecorder> FrameTimingsRecorder::CloneUntil(\n    State state) {\n  std::scoped_lock state_lock(state_mutex_);\n  std::unique_ptr<FrameTimingsRecorder> recorder =\n      std::make_unique<FrameTimingsRecorder>(frame_number_);\n  FML_DCHECK(state_ >= state);\n  recorder->state_ = state;\n\n  if (state >= State::kVsync) {\n    recorder->vsync_start_ = vsync_start_;\n    recorder->vsync_target_ = vsync_target_;\n  }\n\n  if (state >= State::kBuildStart) {\n    recorder->build_start_ = build_start_;\n  }\n\n  if (state >= State::kBuildEnd) {\n    recorder->build_end_ = build_end_;\n  }\n\n  if (state >= State::kRasterStart) {\n    recorder->raster_start_ = raster_start_;\n  }\n\n  if (state >= State::kRasterEnd) {\n    recorder->raster_end_ = raster_end_;\n    recorder->raster_end_wall_time_ = raster_end_wall_time_;\n    recorder->layer_cache_count_ = layer_cache_count_;\n    recorder->layer_cache_bytes_ = layer_cache_bytes_;\n    recorder->picture_cache_count_ = picture_cache_count_;\n    recorder->picture_cache_bytes_ = picture_cache_bytes_;\n  }\n\n  return recorder;\n}\n\nuint64_t FrameTimingsRecorder::GetFrameNumber() const { return frame_number_; }\n\nconst char* FrameTimingsRecorder::GetFrameNumberTraceArg() const {\n  return frame_number_trace_arg_val_.c_str();\n}\n\nvoid FrameTimingsRecorder::RecordFrameTime(FrameTimingKey key) {\n  frame_timings_.emplace_back(\n      FrameTimingItem{key, lynx::base::CurrentSystemTimeMicroseconds()});\n}\n\nstd::vector<FrameTimingItem> FrameTimingsRecorder::TakeFrameTimings() {\n  std::vector<FrameTimingItem> timings = std::move(frame_timings_);\n  frame_timings_.clear();\n  return timings;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/frame_timings.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_FRAME_TIMINGS_H_\n#define CLAY_FLOW_FRAME_TIMINGS_H_\n\n#include <array>\n#include <memory>\n#include <mutex>\n#include <string>\n#include <string_view>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/common/settings.h\"\n\n#define TRACE_EVENT_WITH_FRAME_NUMBER(recorder, category_group, name) \\\n  TRACE_EVENT(category_group, name, \"frame_number\",                   \\\n              recorder->GetFrameNumberTraceArg())\n\nnamespace clay {\n\nenum class FrameTimingKey : uint8_t {\n  kLayoutStart,\n  kLayoutEnd,\n  kDoAnimationStart,\n  kDoAnimationEnd,\n  kPaintStart,\n  kPaintEnd,\n  kBuildFrameStart,\n  kBuildFrameEnd,\n  kDoDrawStart,\n  kAcquireFrameStart,\n  kAcquireFrameEnd,\n  kRasterStart,\n  kRasterEnd,\n  kSubmitFrameStart,\n  kSubmitFrameEnd,\n  kDoDrawEnd,\n  kCount,\n};\n\nstruct FrameTimingItem {\n  FrameTimingKey key;\n  uint64_t timestamp;\n};\n\ninline constexpr std::array<std::string_view,\n                            static_cast<size_t>(FrameTimingKey::kCount)>\n    kFrameTimingKeyStrings = {\n        \"layoutStart\",       \"layoutEnd\",        \"doAnimationStart\",\n        \"doAnimationEnd\",    \"clayPaintStart\",   \"clayPaintEnd\",\n        \"buildFrameStart\",   \"buildFrameEnd\",    \"doDrawStart\",\n        \"acquireFrameStart\", \"acquireFrameEnd\",  \"rasterStart\",\n        \"rasterEnd\",         \"submitFrameStart\", \"submitFrameEnd\",\n        \"doDrawEnd\",\n};\ninline constexpr std::string_view ToStringView(FrameTimingKey key) {\n  const auto index = static_cast<size_t>(key);\n  if (index < kFrameTimingKeyStrings.size()) {\n    return kFrameTimingKeyStrings[index];\n  } else {\n    return \"unknown\";\n  }\n}\n\nclass FrameTiming {\n public:\n  enum Phase {\n    kVsyncStart,\n    kBuildStart,\n    kBuildFinish,\n    kRasterStart,\n    kRasterFinish,\n    kRasterFinishWallTime,\n    kCount\n  };\n\n  static constexpr Phase kPhases[kCount] = {\n      kVsyncStart,  kBuildStart,   kBuildFinish,\n      kRasterStart, kRasterFinish, kRasterFinishWallTime};\n\n  static constexpr int kStatisticsCount = kCount + 5;\n\n  fml::TimePoint Get(Phase phase) const { return data_[phase]; }\n  fml::TimePoint Set(Phase phase, fml::TimePoint value) {\n    return data_[phase] = value;\n  }\n\n  uint64_t GetFrameNumber() const { return frame_number_; }\n  void SetFrameNumber(uint64_t frame_number) { frame_number_ = frame_number; }\n  uint64_t GetLayerCacheCount() const { return layer_cache_count_; }\n  uint64_t GetLayerCacheBytes() const { return layer_cache_bytes_; }\n  uint64_t GetPictureCacheCount() const { return picture_cache_count_; }\n  uint64_t GetPictureCacheBytes() const { return picture_cache_bytes_; }\n  void SetRasterCacheStatistics(size_t layer_cache_count,\n                                size_t layer_cache_bytes,\n                                size_t picture_cache_count,\n                                size_t picture_cache_bytes) {\n    layer_cache_count_ = layer_cache_count;\n    layer_cache_bytes_ = layer_cache_bytes;\n    picture_cache_count_ = picture_cache_count;\n    picture_cache_bytes_ = picture_cache_bytes;\n  }\n\n private:\n  fml::TimePoint data_[kCount];\n  uint64_t frame_number_;\n  size_t layer_cache_count_;\n  size_t layer_cache_bytes_;\n  size_t picture_cache_count_;\n  size_t picture_cache_bytes_;\n};\n\nclass RasterCache;\n\n/// Records timestamps for various phases of a frame rendering process.\n///\n/// Recorder is created on vsync and destroyed after the rasterization of the\n/// frame. This class is thread safe and doesn't require additional\n/// synchronization.\nclass FrameTimingsRecorder {\n public:\n  /// Various states that the recorder can be in. When created the recorder is\n  /// in an uninitialized state and transitions in sequential order of the\n  /// states.\n  enum class State : uint32_t {\n    kUninitialized,\n    kVsync,\n    kBuildStart,\n    kBuildEnd,\n    kRasterStart,\n    kRasterEnd,\n  };\n\n  /// Default constructor, initializes the recorder with State::kUninitialized.\n  FrameTimingsRecorder();\n\n  /// Constructor with a pre-populated frame number.\n  explicit FrameTimingsRecorder(uint64_t frame_number);\n\n  ~FrameTimingsRecorder();\n\n  /// Timestamp of the vsync signal.\n  fml::TimePoint GetVsyncStartTime() const;\n\n  /// Timestamp of when the frame was targeted to be presented.\n  ///\n  /// This is typically the next vsync signal timestamp.\n  fml::TimePoint GetVsyncTargetTime() const;\n\n  /// Timestamp of when the frame building started.\n  fml::TimePoint GetBuildStartTime() const;\n\n  /// Timestamp of when the frame was finished building.\n  fml::TimePoint GetBuildEndTime() const;\n\n  /// Timestamp of when the frame rasterization started.\n  fml::TimePoint GetRasterStartTime() const;\n\n  /// Timestamp of when the frame rasterization finished.\n  fml::TimePoint GetRasterEndTime() const;\n\n  /// Timestamp of when the frame rasterization is complete in wall-time.\n  fml::TimePoint GetRasterEndWallTime() const;\n\n  /// Duration of the frame build time.\n  fml::TimeDelta GetBuildDuration() const;\n\n  fml::TimePoint GetVsyncTimeOnGeneration() const;\n\n  fml::TimePoint GetLastDrawVsyncTime() const;\n\n  int GetVsyncSequenceId() const;\n\n  bool HasValidVsyncSequenceId() const;\n\n  bool GetForced() const;\n\n  /// Count of the layer cache entries\n  size_t GetLayerCacheCount() const;\n\n  /// Total Bytes in all layer cache entries\n  size_t GetLayerCacheBytes() const;\n\n  /// Count of the picture cache entries\n  size_t GetPictureCacheCount() const;\n\n  /// Total Bytes in all picture cache entries\n  size_t GetPictureCacheBytes() const;\n\n  /// Records a vsync event.\n  void RecordVsync(fml::TimePoint vsync_start, fml::TimePoint vsync_target);\n\n  /// Records a build start event.\n  void RecordBuildStart(fml::TimePoint build_start);\n\n  /// Records a build end event.\n  void RecordBuildEnd(fml::TimePoint build_end);\n\n  /// Records a raster start event.\n  void RecordRasterStart(fml::TimePoint raster_start);\n\n  void RecordLastDrawVsyncTime(fml::TimePoint last_draw_vsync);\n\n  void RecordVsyncTimeOnGeneration(fml::TimePoint time);\n\n  void RecordForced(bool forced);\n\n  void RecordVsyncSequenceId(int id);\n\n  /// Clones the recorder until (and including) the specified state.\n  std::unique_ptr<FrameTimingsRecorder> CloneUntil(State state);\n\n  /// Records a raster end event, and builds a `FrameTiming` that summarizes all\n  /// the events. This summary is sent to the framework.\n  FrameTiming RecordRasterEnd(const RasterCache* cache = nullptr);\n\n  /// Returns the frame number. Frame number is unique per frame and a frame\n  /// built earlier will have a frame number less than a frame that has been\n  /// built at a later point of time.\n  uint64_t GetFrameNumber() const;\n\n  /// Returns the frame number in a fml tracing friendly format.\n  const char* GetFrameNumberTraceArg() const;\n\n  /// Returns the recorded time from when `RecordRasterEnd` is called.\n  FrameTiming GetRecordedTime() const;\n\n  void RecordFrameTime(FrameTimingKey key);\n\n  std::vector<FrameTimingItem> TakeFrameTimings();\n\n private:\n  static std::atomic<uint64_t> frame_number_gen_;\n\n  mutable std::mutex state_mutex_;\n  State state_ = State::kUninitialized;\n\n  const uint64_t frame_number_;\n  const std::string frame_number_trace_arg_val_;\n\n  fml::TimePoint vsync_start_;\n  fml::TimePoint vsync_target_;\n  fml::TimePoint build_start_;\n  fml::TimePoint build_end_;\n  fml::TimePoint raster_start_;\n  fml::TimePoint raster_end_;\n  fml::TimePoint raster_end_wall_time_;\n  // time of vsync signal arrives.\n  fml::TimePoint frame_start_time_;\n  // `frame_start_time` + 1000ms/refreshRate.\n  fml::TimePoint frame_target_time_;\n  fml::TimePoint last_draw_vsync_time_;\n  fml::TimePoint vsync_time_on_generation_;\n  // force to produce layer_tree even the pipeLine is full.\n  bool forced_ = false;\n  int vsync_sequence_id_ = -1;\n  size_t layer_cache_count_;\n  size_t layer_cache_bytes_;\n  size_t picture_cache_count_;\n  size_t picture_cache_bytes_;\n\n  // Set when `RecordRasterEnd` is called. Cannot be reset once set.\n  FrameTiming timing_;\n\n  std::vector<FrameTimingItem> frame_timings_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(FrameTimingsRecorder);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_FRAME_TIMINGS_H_\n"
  },
  {
    "path": "clay/flow/frame_timings_recorder_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <thread>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/flow/testing/mock_raster_cache.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(FrameTimingsRecorderTest, RecordVsync) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n  const auto st = fml::TimePoint::Now();\n  const auto en = st + fml::TimeDelta::FromMillisecondsF(16);\n  recorder->RecordVsync(st, en);\n\n  ASSERT_EQ(st, recorder->GetVsyncStartTime());\n  ASSERT_EQ(en, recorder->GetVsyncTargetTime());\n}\n\nTEST(FrameTimingsRecorderTest, RecordBuildTimes) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto st = fml::TimePoint::Now();\n  const auto en = st + fml::TimeDelta::FromMillisecondsF(16);\n  recorder->RecordVsync(st, en);\n\n  const auto build_start = fml::TimePoint::Now();\n  const auto build_end = build_start + fml::TimeDelta::FromMillisecondsF(16);\n  recorder->RecordBuildStart(build_start);\n  recorder->RecordBuildEnd(build_end);\n\n  ASSERT_EQ(build_start, recorder->GetBuildStartTime());\n  ASSERT_EQ(build_end, recorder->GetBuildEndTime());\n}\n\nTEST(FrameTimingsRecorderTest, RecordRasterTimes) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto st = fml::TimePoint::Now();\n  const auto en = st + fml::TimeDelta::FromMillisecondsF(16);\n  recorder->RecordVsync(st, en);\n\n  const auto build_start = fml::TimePoint::Now();\n  const auto build_end = build_start + fml::TimeDelta::FromMillisecondsF(16);\n  recorder->RecordBuildStart(build_start);\n  recorder->RecordBuildEnd(build_end);\n\n  using std::literals::chrono_literals::operator\"\"ms;\n\n  const auto raster_start = fml::TimePoint::Now();\n  recorder->RecordRasterStart(raster_start);\n  const auto before_raster_end_wall_time = fml::TimePoint::CurrentWallTime();\n  std::this_thread::sleep_for(1ms);\n  const auto timing = recorder->RecordRasterEnd();\n  std::this_thread::sleep_for(1ms);\n  const auto after_raster_end_wall_time = fml::TimePoint::CurrentWallTime();\n\n  ASSERT_EQ(raster_start, recorder->GetRasterStartTime());\n  ASSERT_GT(recorder->GetRasterEndWallTime(), before_raster_end_wall_time);\n  ASSERT_LT(recorder->GetRasterEndWallTime(), after_raster_end_wall_time);\n  ASSERT_EQ(recorder->GetFrameNumber(), timing.GetFrameNumber());\n  ASSERT_EQ(recorder->GetLayerCacheCount(), 0u);\n  ASSERT_EQ(recorder->GetLayerCacheBytes(), 0u);\n  ASSERT_EQ(recorder->GetPictureCacheCount(), 0u);\n  ASSERT_EQ(recorder->GetPictureCacheBytes(), 0u);\n}\n\nTEST(FrameTimingsRecorderTest, RecordRasterTimesWithCache) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto st = fml::TimePoint::Now();\n  const auto en = st + fml::TimeDelta::FromMillisecondsF(16);\n  recorder->RecordVsync(st, en);\n\n  const auto build_start = fml::TimePoint::Now();\n  const auto build_end = build_start + fml::TimeDelta::FromMillisecondsF(16);\n  recorder->RecordBuildStart(build_start);\n  recorder->RecordBuildEnd(build_end);\n\n  using std::literals::chrono_literals::operator\"\"ms;\n\n  MockRasterCache cache(1, 10);\n  cache.BeginFrame();\n\n  const auto raster_start = fml::TimePoint::Now();\n  recorder->RecordRasterStart(raster_start);\n\n  cache.AddMockLayer(100, 100);\n  size_t layer_bytes = cache.EstimateLayerCacheByteSize();\n  EXPECT_GT(layer_bytes, 0u);\n  cache.AddMockPicture(100, 100);\n  size_t picture_bytes = cache.EstimatePictureCacheByteSize();\n  EXPECT_GT(picture_bytes, 0u);\n  cache.EvictUnusedCacheEntries();\n\n  cache.EndFrame();\n\n  const auto before_raster_end_wall_time = fml::TimePoint::CurrentWallTime();\n  std::this_thread::sleep_for(1ms);\n  const auto timing = recorder->RecordRasterEnd(&cache);\n  std::this_thread::sleep_for(1ms);\n  const auto after_raster_end_wall_time = fml::TimePoint::CurrentWallTime();\n\n  ASSERT_EQ(raster_start, recorder->GetRasterStartTime());\n  ASSERT_GT(recorder->GetRasterEndWallTime(), before_raster_end_wall_time);\n  ASSERT_LT(recorder->GetRasterEndWallTime(), after_raster_end_wall_time);\n  ASSERT_EQ(recorder->GetFrameNumber(), timing.GetFrameNumber());\n  ASSERT_EQ(recorder->GetLayerCacheCount(), 1u);\n  ASSERT_EQ(recorder->GetLayerCacheBytes(), layer_bytes);\n  ASSERT_EQ(recorder->GetPictureCacheCount(), 1u);\n  ASSERT_EQ(recorder->GetPictureCacheBytes(), picture_bytes);\n}\n\n// Windows and Fuchsia don't allow testing with killed by signal.\n#if !defined(OS_FUCHSIA) && !defined(OS_WIN) && \\\n    (CLAY_ENGINE_MODE == CLAY_ENGINE_MODE_DEBUG) && !NDEBUG\n\nTEST(FrameTimingsRecorderTest, ThrowWhenRecordBuildBeforeVsync) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto build_start = fml::TimePoint::Now();\n  EXPECT_EXIT(recorder->RecordBuildStart(build_start),\n              ::testing::KilledBySignal(SIGABRT),\n              \"Check failed: state_ == State::kVsync.\");\n}\n\nTEST(FrameTimingsRecorderTest, ThrowWhenRecordRasterBeforeBuildEnd) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto st = fml::TimePoint::Now();\n  const auto en = st + fml::TimeDelta::FromMillisecondsF(16);\n  recorder->RecordVsync(st, en);\n\n  const auto raster_start = fml::TimePoint::Now();\n  EXPECT_EXIT(recorder->RecordRasterStart(raster_start),\n              ::testing::KilledBySignal(SIGABRT),\n              \"Check failed: state_ == State::kBuildEnd.\");\n}\n\n#endif\n\nTEST(FrameTimingsRecorderTest, RecordersHaveUniqueFrameNumbers) {\n  auto recorder1 = std::make_unique<FrameTimingsRecorder>();\n  auto recorder2 = std::make_unique<FrameTimingsRecorder>();\n\n  ASSERT_TRUE(recorder2->GetFrameNumber() > recorder1->GetFrameNumber());\n}\n\nTEST(FrameTimingsRecorderTest, ClonedHasSameFrameNumber) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  auto cloned =\n      recorder->CloneUntil(FrameTimingsRecorder::State::kUninitialized);\n  ASSERT_EQ(recorder->GetFrameNumber(), cloned->GetFrameNumber());\n}\n\nTEST(FrameTimingsRecorderTest, ClonedHasSameVsyncStartAndTarget) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto now = fml::TimePoint::Now();\n  recorder->RecordVsync(now, now + fml::TimeDelta::FromMilliseconds(16));\n\n  auto cloned = recorder->CloneUntil(FrameTimingsRecorder::State::kVsync);\n  ASSERT_EQ(recorder->GetFrameNumber(), cloned->GetFrameNumber());\n  ASSERT_EQ(recorder->GetVsyncStartTime(), cloned->GetVsyncStartTime());\n  ASSERT_EQ(recorder->GetVsyncTargetTime(), cloned->GetVsyncTargetTime());\n}\n\nTEST(FrameTimingsRecorderTest, ClonedHasSameBuildStart) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto now = fml::TimePoint::Now();\n  recorder->RecordVsync(now, now + fml::TimeDelta::FromMilliseconds(16));\n  recorder->RecordBuildStart(fml::TimePoint::Now());\n\n  auto cloned = recorder->CloneUntil(FrameTimingsRecorder::State::kBuildStart);\n  ASSERT_EQ(recorder->GetFrameNumber(), cloned->GetFrameNumber());\n  ASSERT_EQ(recorder->GetVsyncStartTime(), cloned->GetVsyncStartTime());\n  ASSERT_EQ(recorder->GetVsyncTargetTime(), cloned->GetVsyncTargetTime());\n  ASSERT_EQ(recorder->GetBuildStartTime(), cloned->GetBuildStartTime());\n}\n\nTEST(FrameTimingsRecorderTest, ClonedHasSameBuildEnd) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto now = fml::TimePoint::Now();\n  recorder->RecordVsync(now, now + fml::TimeDelta::FromMilliseconds(16));\n  recorder->RecordBuildStart(fml::TimePoint::Now());\n  recorder->RecordBuildEnd(fml::TimePoint::Now());\n\n  auto cloned = recorder->CloneUntil(FrameTimingsRecorder::State::kBuildEnd);\n  ASSERT_EQ(recorder->GetFrameNumber(), cloned->GetFrameNumber());\n  ASSERT_EQ(recorder->GetVsyncStartTime(), cloned->GetVsyncStartTime());\n  ASSERT_EQ(recorder->GetVsyncTargetTime(), cloned->GetVsyncTargetTime());\n  ASSERT_EQ(recorder->GetBuildStartTime(), cloned->GetBuildStartTime());\n  ASSERT_EQ(recorder->GetBuildEndTime(), cloned->GetBuildEndTime());\n}\n\nTEST(FrameTimingsRecorderTest, ClonedHasSameRasterStart) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto now = fml::TimePoint::Now();\n  recorder->RecordVsync(now, now + fml::TimeDelta::FromMilliseconds(16));\n  recorder->RecordBuildStart(fml::TimePoint::Now());\n  recorder->RecordBuildEnd(fml::TimePoint::Now());\n  recorder->RecordRasterStart(fml::TimePoint::Now());\n\n  auto cloned = recorder->CloneUntil(FrameTimingsRecorder::State::kRasterStart);\n  ASSERT_EQ(recorder->GetFrameNumber(), cloned->GetFrameNumber());\n  ASSERT_EQ(recorder->GetVsyncStartTime(), cloned->GetVsyncStartTime());\n  ASSERT_EQ(recorder->GetVsyncTargetTime(), cloned->GetVsyncTargetTime());\n  ASSERT_EQ(recorder->GetBuildStartTime(), cloned->GetBuildStartTime());\n  ASSERT_EQ(recorder->GetBuildEndTime(), cloned->GetBuildEndTime());\n  ASSERT_EQ(recorder->GetRasterStartTime(), cloned->GetRasterStartTime());\n}\n\nTEST(FrameTimingsRecorderTest, ClonedHasSameRasterEnd) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  const auto now = fml::TimePoint::Now();\n  recorder->RecordVsync(now, now + fml::TimeDelta::FromMilliseconds(16));\n  recorder->RecordBuildStart(fml::TimePoint::Now());\n  recorder->RecordBuildEnd(fml::TimePoint::Now());\n  recorder->RecordRasterStart(fml::TimePoint::Now());\n  recorder->RecordRasterEnd();\n\n  auto cloned = recorder->CloneUntil(FrameTimingsRecorder::State::kRasterEnd);\n  ASSERT_EQ(recorder->GetFrameNumber(), cloned->GetFrameNumber());\n  ASSERT_EQ(recorder->GetVsyncStartTime(), cloned->GetVsyncStartTime());\n  ASSERT_EQ(recorder->GetVsyncTargetTime(), cloned->GetVsyncTargetTime());\n  ASSERT_EQ(recorder->GetBuildStartTime(), cloned->GetBuildStartTime());\n  ASSERT_EQ(recorder->GetBuildEndTime(), cloned->GetBuildEndTime());\n  ASSERT_EQ(recorder->GetRasterStartTime(), cloned->GetRasterStartTime());\n  ASSERT_EQ(recorder->GetRasterEndTime(), cloned->GetRasterEndTime());\n  ASSERT_EQ(recorder->GetRasterEndWallTime(), cloned->GetRasterEndWallTime());\n  ASSERT_EQ(recorder->GetLayerCacheCount(), cloned->GetLayerCacheCount());\n  ASSERT_EQ(recorder->GetLayerCacheBytes(), cloned->GetLayerCacheBytes());\n  ASSERT_EQ(recorder->GetPictureCacheCount(), cloned->GetPictureCacheCount());\n  ASSERT_EQ(recorder->GetPictureCacheBytes(), cloned->GetPictureCacheBytes());\n}\n\nTEST(FrameTimingsRecorderTest, ClonedHasSameRasterEndWithCache) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n  MockRasterCache cache(1, 10);\n  cache.BeginFrame();\n\n  const auto now = fml::TimePoint::Now();\n  recorder->RecordVsync(now, now + fml::TimeDelta::FromMilliseconds(16));\n  recorder->RecordBuildStart(fml::TimePoint::Now());\n  recorder->RecordBuildEnd(fml::TimePoint::Now());\n  recorder->RecordRasterStart(fml::TimePoint::Now());\n\n  cache.AddMockLayer(100, 100);\n  size_t layer_bytes = cache.EstimateLayerCacheByteSize();\n  EXPECT_GT(layer_bytes, 0u);\n  cache.AddMockPicture(100, 100);\n  size_t picture_bytes = cache.EstimatePictureCacheByteSize();\n  EXPECT_GT(picture_bytes, 0u);\n  cache.EvictUnusedCacheEntries();\n  cache.EndFrame();\n  recorder->RecordRasterEnd(&cache);\n\n  auto cloned = recorder->CloneUntil(FrameTimingsRecorder::State::kRasterEnd);\n  ASSERT_EQ(recorder->GetFrameNumber(), cloned->GetFrameNumber());\n  ASSERT_EQ(recorder->GetVsyncStartTime(), cloned->GetVsyncStartTime());\n  ASSERT_EQ(recorder->GetVsyncTargetTime(), cloned->GetVsyncTargetTime());\n  ASSERT_EQ(recorder->GetBuildStartTime(), cloned->GetBuildStartTime());\n  ASSERT_EQ(recorder->GetBuildEndTime(), cloned->GetBuildEndTime());\n  ASSERT_EQ(recorder->GetRasterStartTime(), cloned->GetRasterStartTime());\n  ASSERT_EQ(recorder->GetRasterEndTime(), cloned->GetRasterEndTime());\n  ASSERT_EQ(recorder->GetRasterEndWallTime(), cloned->GetRasterEndWallTime());\n  ASSERT_EQ(recorder->GetLayerCacheCount(), cloned->GetLayerCacheCount());\n  ASSERT_EQ(recorder->GetLayerCacheBytes(), cloned->GetLayerCacheBytes());\n  ASSERT_EQ(recorder->GetPictureCacheCount(), cloned->GetPictureCacheCount());\n  ASSERT_EQ(recorder->GetPictureCacheBytes(), cloned->GetPictureCacheBytes());\n}\n\nTEST(FrameTimingsRecorderTest, FrameNumberTraceArgIsValid) {\n  auto recorder = std::make_unique<FrameTimingsRecorder>();\n\n  char buff[50];\n  snprintf(buff, sizeof(buff), \"%d\",\n           static_cast<int>(recorder->GetFrameNumber()));\n  std::string actual_arg = buff;\n  std::string expected_arg = recorder->GetFrameNumberTraceArg();\n\n  ASSERT_EQ(actual_arg, expected_arg);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/gl_context_switch_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include <functional>\n#include <future>\n#include <memory>\n\n#include \"clay/common/graphics/gl_context_switch.h\"\n#include \"clay/flow/testing/gl_context_switch_test.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(GLContextSwitchTest, SwitchKeepsContextCurrentWhileInScope) {\n  {\n    auto test_gl_context = std::make_unique<TestSwitchableGLContext>(0);\n    auto context_switch = GLContextSwitch(std::move(test_gl_context));\n    ASSERT_EQ(TestSwitchableGLContext::GetCurrentContext(), 0);\n  }\n  ASSERT_EQ(TestSwitchableGLContext::GetCurrentContext(), -1);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layer_snapshot_store.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layer_snapshot_store.h\"\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace clay {\n\nLayerSnapshotData::LayerSnapshotData(int64_t layer_unique_id,\n                                     const fml::TimeDelta& duration,\n                                     const clay::GrDataPtr& snapshot,\n                                     const skity::Rect& bounds)\n    : layer_unique_id_(layer_unique_id),\n      duration_(duration),\n      snapshot_(snapshot),\n      bounds_(bounds) {}\n\nvoid LayerSnapshotStore::Clear() { layer_snapshots_.clear(); }\n\nvoid LayerSnapshotStore::Add(const LayerSnapshotData& data) {\n  layer_snapshots_.push_back(data);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layer_snapshot_store.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYER_SNAPSHOT_STORE_H_\n#define CLAY_FLOW_LAYER_SNAPSHOT_STORE_H_\n\n#include <vector>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\n/// Container for snapshot data pertaining to a given layer. A layer is\n/// identified by it's unique id.\nclass LayerSnapshotData {\n public:\n  LayerSnapshotData(int64_t layer_unique_id, const fml::TimeDelta& duration,\n                    const clay::GrDataPtr& snapshot, const skity::Rect& bounds);\n\n  ~LayerSnapshotData() = default;\n\n  int64_t GetLayerUniqueId() const { return layer_unique_id_; }\n\n  fml::TimeDelta GetDuration() const { return duration_; }\n\n  clay::GrDataPtr GetSnapshot() const { return snapshot_; }\n\n  skity::Rect GetBounds() const { return bounds_; }\n\n private:\n  const int64_t layer_unique_id_;\n  const fml::TimeDelta duration_;\n  const clay::GrDataPtr snapshot_;\n  const skity::Rect bounds_;\n};\n\n/// Collects snapshots of layers during frame rasterization.\nclass LayerSnapshotStore {\n public:\n  typedef std::vector<LayerSnapshotData> Snapshots;\n\n  LayerSnapshotStore() = default;\n\n  ~LayerSnapshotStore() = default;\n\n  /// Clears all the stored snapshots.\n  void Clear();\n\n  /// Adds snapshots for a given layer. `duration` marks the time taken to\n  /// rasterize this one layer.\n  void Add(const LayerSnapshotData& data);\n\n  // Returns the number of snapshots collected.\n  size_t Size() const { return layer_snapshots_.size(); }\n\n  // make this class iterable\n  Snapshots::iterator begin() { return layer_snapshots_.begin(); }\n  Snapshots::iterator end() { return layer_snapshots_.end(); }\n\n private:\n  Snapshots layer_snapshots_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(LayerSnapshotStore);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYER_SNAPSHOT_STORE_H_\n"
  },
  {
    "path": "clay/flow/layers/backdrop_filter_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/backdrop_filter_layer.h\"\n\n#include <utility>\n\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nBackdropFilterLayer::BackdropFilterLayer(\n    std::shared_ptr<const clay::ImageFilter> filter, clay::BlendMode blend_mode)\n    : filter_(std::move(filter)), blend_mode_(blend_mode) {}\n\nvoid BackdropFilterLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  auto* prev = static_cast<const BackdropFilterLayer*>(old_layer);\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(prev);\n    if (NotEquals(filter_, prev->filter_)) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n\n  // Backdrop filter paints everywhere in cull rect\n  skity::Rect paint_bounds = context->GetCullRect();\n  context->AddLayerBounds(paint_bounds);\n\n  if (filter_) {\n    context->GetTransform().MapRect(&paint_bounds, paint_bounds);\n    auto filter_target_bounds = paint_bounds;\n    filter_target_bounds.RoundOut();\n    skity::Rect filter_input_bounds;  // in screen coordinates\n    filter_->get_input_device_bounds(\n        filter_target_bounds, context->GetTransform(), filter_input_bounds);\n    context->AddReadbackRegion(filter_input_bounds);\n  }\n\n  DiffChildren(context, prev);\n\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid BackdropFilterLayer::Preroll(PrerollContext* context) {\n  Layer::AutoPrerollSaveLayerState save =\n      Layer::AutoPrerollSaveLayerState::Create(context, true, !!filter_);\n  if (context->compositor_state != nullptr) {\n    context->compositor_state->PushFilterToVisitedPlatformViews(\n        filter_, context->state_stack.local_cull_rect());\n  }\n  skity::Rect child_paint_bounds = skity::Rect::MakeEmpty();\n  PrerollChildren(context, &child_paint_bounds);\n  child_paint_bounds.Join(context->state_stack.local_cull_rect());\n  set_paint_bounds(child_paint_bounds);\n  context->renderable_state_flags = kSaveLayerRenderFlags;\n}\n\nvoid BackdropFilterLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n\n  auto mutator = context.state_stack.save();\n  mutator.applyBackdropFilter(paint_bounds(), filter_, blend_mode_);\n\n  PaintChildren(context);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/backdrop_filter_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_BACKDROP_FILTER_LAYER_H_\n#define CLAY_FLOW_LAYERS_BACKDROP_FILTER_LAYER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/flow/layers/container_layer.h\"\n\nnamespace clay {\n\nclass BackdropFilterLayer : public ContainerLayer {\n public:\n  BackdropFilterLayer(std::shared_ptr<const clay::ImageFilter> filter,\n                      clay::BlendMode blend_mode);\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  void Preroll(PrerollContext* context) override;\n\n  void Paint(PaintContext& context) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"BackdropFilterLayer\"; }\n#endif\n\n private:\n  std::shared_ptr<const clay::ImageFilter> filter_;\n  clay::BlendMode blend_mode_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(BackdropFilterLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_BACKDROP_FILTER_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/backdrop_filter_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/backdrop_filter_layer.h\"\n#include \"clay/flow/layers/clip_rect_layer.h\"\n#include \"clay/flow/layers/transform_layer.h\"\n#include \"clay/flow/testing/diff_context_test.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/skia/include/core/SkImageFilter.h\"\n#include \"third_party/skia/include/effects/SkImageFilters.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing BackdropFilterLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(BackdropFilterLayerTest, PaintingEmptyLayerDies) {\n  auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);\n  auto layer = std::make_shared<BackdropFilterLayer>(filter.shared(),\n                                                     DlBlendMode::kSrcOver);\n  auto parent = std::make_shared<ClipRectLayer>(kEmptyRect, Clip::hardEdge);\n  parent->Add(layer);\n\n  parent->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(BackdropFilterLayerTest, PaintBeforePrerollDies) {\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);\n  auto layer = std::make_shared<BackdropFilterLayer>(filter.shared(),\n                                                     DlBlendMode::kSrcOver);\n  layer->Add(mock_layer);\n\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(BackdropFilterLayerTest, EmptyFilter) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer =\n      std::make_shared<BackdropFilterLayer>(nullptr, DlBlendMode::kSrcOver);\n  layer->Add(mock_layer);\n  auto parent = std::make_shared<ClipRectLayer>(child_bounds, Clip::hardEdge);\n  parent->Add(layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  parent->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector({MockCanvas::DrawCall{\n                       0,\n                       MockCanvas::SaveLayerData{\n                           clay::ConvertSkityRectToSkRect(child_bounds),\n                           SkPaint(), nullptr, 1}},\n                   MockCanvas::DrawCall{\n                       1, MockCanvas::DrawPathData{child_path, child_paint}},\n                   MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(BackdropFilterLayerTest, SimpleFilter) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto layer_filter = SkImageFilters::Shader(\n      SkShaders::Color(SkColors::kMagenta, /*colorSpace=*/nullptr));\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<BackdropFilterLayer>(\n      std::make_shared<UnknownImageFilter>(layer_filter),\n      DlBlendMode::kSrcOver);\n  layer->Add(mock_layer);\n  auto parent = std::make_shared<ClipRectLayer>(child_bounds, Clip::hardEdge);\n  parent->Add(layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  parent->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector({MockCanvas::DrawCall{\n                       0,\n                       MockCanvas::SaveLayerData{\n                           clay::ConvertSkityRectToSkRect(child_bounds),\n                           SkPaint(), layer_filter, 1}},\n                   MockCanvas::DrawCall{\n                       1, MockCanvas::DrawPathData{child_path, child_paint}},\n                   MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(BackdropFilterLayerTest, NonSrcOverBlend) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto layer_filter = SkImageFilters::Shader(\n      SkShaders::Color(SkColors::kMagenta, /*colorSpace=*/nullptr));\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<BackdropFilterLayer>(\n      std::make_shared<UnknownImageFilter>(layer_filter), DlBlendMode::kSrc);\n  layer->Add(mock_layer);\n  auto parent = std::make_shared<ClipRectLayer>(child_bounds, Clip::hardEdge);\n  parent->Add(layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  parent->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n\n  SkPaint filter_paint = SkPaint();\n  filter_paint.setBlendMode(SkBlendMode::kSrc);\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector({MockCanvas::DrawCall{\n                       0,\n                       MockCanvas::SaveLayerData{\n                           clay::ConvertSkityRectToSkRect(child_bounds),\n                           filter_paint, layer_filter, 1}},\n                   MockCanvas::DrawCall{\n                       1, MockCanvas::DrawPathData{child_path, child_paint}},\n                   MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(BackdropFilterLayerTest, MultipleChildren) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);\n  const SkPath child_path1 =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath child_path2 = SkPath().addRect(\n      clay::ConvertSkityRectToSkRect(child_bounds.MakeOffset(3.0f, 0.0f)));\n  const SkPaint child_paint1 = SkPaint(SkColors::kYellow);\n  const SkPaint child_paint2 = SkPaint(SkColors::kCyan);\n  skity::Rect children_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  children_bounds.Join(clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  auto layer_filter = SkImageFilters::Shader(\n      SkShaders::Color(SkColors::kMagenta, /*colorSpace=*/nullptr));\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer = std::make_shared<BackdropFilterLayer>(\n      std::make_shared<UnknownImageFilter>(layer_filter),\n      DlBlendMode::kSrcOver);\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n  auto parent =\n      std::make_shared<ClipRectLayer>(children_bounds, Clip::hardEdge);\n  parent->Add(layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  parent->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), children_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), children_bounds);\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector({MockCanvas::DrawCall{\n                       0,\n                       MockCanvas::SaveLayerData{\n                           clay::ConvertSkityRectToSkRect(children_bounds),\n                           SkPaint(), layer_filter, 1}},\n                   MockCanvas::DrawCall{\n                       1, MockCanvas::DrawPathData{child_path1, child_paint1}},\n                   MockCanvas::DrawCall{\n                       1, MockCanvas::DrawPathData{child_path2, child_paint2}},\n                   MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(BackdropFilterLayerTest, Nested) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);\n  const SkPath child_path1 =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath child_path2 = SkPath().addRect(\n      clay::ConvertSkityRectToSkRect(child_bounds.MakeOffset(3.0f, 0.0f)));\n  const SkPaint child_paint1 = SkPaint(SkColors::kYellow);\n  const SkPaint child_paint2 = SkPaint(SkColors::kCyan);\n  skity::Rect children_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  children_bounds.Join(clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  auto layer_filter1 = SkImageFilters::Shader(\n      SkShaders::Color(SkColors::kMagenta, /*colorSpace=*/nullptr));\n  auto layer_filter2 = SkImageFilters::Shader(\n      SkShaders::Color(SkColors::kDkGray, /*colorSpace=*/nullptr));\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer1 = std::make_shared<BackdropFilterLayer>(\n      std::make_shared<UnknownImageFilter>(layer_filter1),\n      DlBlendMode::kSrcOver);\n  auto layer2 = std::make_shared<BackdropFilterLayer>(\n      std::make_shared<UnknownImageFilter>(layer_filter2),\n      DlBlendMode::kSrcOver);\n  layer2->Add(mock_layer2);\n  layer1->Add(mock_layer1);\n  layer1->Add(layer2);\n  auto parent =\n      std::make_shared<ClipRectLayer>(children_bounds, Clip::hardEdge);\n  parent->Add(layer1);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  parent->Preroll(preroll_context());\n\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer1->paint_bounds(), children_bounds);\n  EXPECT_EQ(layer2->paint_bounds(), children_bounds);\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(layer2->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n\n  layer1->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{\n               0, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                children_bounds),\n                                            SkPaint(), layer_filter1, 1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path1, child_paint1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                children_bounds),\n                                            SkPaint(), layer_filter2, 2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path2, child_paint2}},\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(BackdropFilterLayerTest, Readback) {\n  std::shared_ptr<DlImageFilter> no_filter;\n  auto layer_filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);\n  auto initial_transform = skity::Matrix();\n\n  // BDF with filter always reads from surface\n  auto layer1 = std::make_shared<BackdropFilterLayer>(layer_filter.shared(),\n                                                      DlBlendMode::kSrcOver);\n  preroll_context()->surface_needs_readback = false;\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer1->Preroll(preroll_context());\n  EXPECT_TRUE(preroll_context()->surface_needs_readback);\n\n  // BDF with no filter does not read from surface itself\n  auto layer2 =\n      std::make_shared<BackdropFilterLayer>(no_filter, DlBlendMode::kSrcOver);\n  preroll_context()->surface_needs_readback = false;\n  layer2->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n\n  // BDF with no filter does not block prior readback value\n  preroll_context()->surface_needs_readback = true;\n  layer2->Preroll(preroll_context());\n  EXPECT_TRUE(preroll_context()->surface_needs_readback);\n\n  // BDF with no filter blocks child with readback\n  auto mock_layer = std::make_shared<MockLayer>(SkPath(), SkPaint());\n  mock_layer->set_fake_reads_surface(true);\n  layer2->Add(mock_layer);\n  preroll_context()->surface_needs_readback = false;\n  layer2->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n}\n\nTEST_F(BackdropFilterLayerTest, OpacityInheritance) {\n  auto backdrop_filter = DlBlurImageFilter(5, 5, DlTileMode::kClamp);\n  const SkPath mock_path = SkPath().addRect(SkRect::MakeLTRB(0, 0, 10, 10));\n  const SkPaint mock_paint = SkPaint(SkColors::kRed);\n  const SkRect clip_rect = SkRect::MakeLTRB(0, 0, 100, 100);\n\n  auto clip = std::make_shared<ClipRectLayer>(\n      clay::ConvertSkRectToSkityRect(clip_rect), Clip::hardEdge);\n  auto parent = std::make_shared<OpacityLayer>(128, skity::Vec2(0, 0));\n  auto layer = std::make_shared<BackdropFilterLayer>(backdrop_filter.shared(),\n                                                     DlBlendMode::kSrcOver);\n  auto child = std::make_shared<MockLayer>(mock_path, mock_paint);\n  layer->Add(child);\n  parent->Add(layer);\n  clip->Add(parent);\n\n  clip->Preroll(preroll_context());\n\n  clip->Paint(display_list_paint_context());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  auto canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->clipRect(clip_rect, SkClipOp::kIntersect, false);\n      {\n        SkPaint save_paint;\n        save_paint.setAlpha(128);\n        save_paint.setImageFilter(backdrop_filter.gr_object());\n        canvas->saveLayer(clip_rect, &save_paint);\n        {\n          SkPaint child_paint;\n          child_paint.setColor(SK_ColorRED);\n          canvas->drawPath(mock_path, child_paint);\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  EXPECT_TRUE(expected_display_list->approximateOpCount() ==\n              display_list()->approximateOpCount());\n}\n\nusing BackdropLayerDiffTest = DiffContextTest;\n\nTEST_F(BackdropLayerDiffTest, BackdropLayer) {\n  auto filter = DlBlurImageFilter(10, 10, DlTileMode::kClamp);\n\n  {\n    // tests later assume 30px readback area, fail early if that's not the case\n    skity::Rect readback;\n    EXPECT_EQ(filter.get_input_device_bounds(skity::Rect::MakeWH(10, 10),\n                                             skity::Matrix(), readback),\n              &readback);\n    EXPECT_EQ(readback, skity::Rect::MakeLTRB(-30, -30, 40, 40));\n  }\n\n  MockLayerTree l1({100, 100});\n  l1.root()->Add(std::make_shared<BackdropFilterLayer>(filter.shared(),\n                                                       DlBlendMode::kSrcOver));\n\n  // no clip, effect over entire surface\n  auto damage = DiffLayerTree(l1, MockLayerTree({100, 100}));\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeWH(100, 100));\n\n  MockLayerTree l2({100, 100});\n\n  auto clip = std::make_shared<ClipRectLayer>(\n      skity::Rect::MakeLTRB(20, 20, 60, 60), Clip::hardEdge);\n  clip->Add(std::make_shared<BackdropFilterLayer>(filter.shared(),\n                                                  DlBlendMode::kSrcOver));\n  l2.root()->Add(clip);\n  damage = DiffLayerTree(l2, MockLayerTree({100, 100}));\n\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 90, 90));\n\n  MockLayerTree l3;\n  auto scale = std::make_shared<TransformLayer>(skity::Matrix::Scale(2.0, 2.0));\n  scale->Add(clip);\n  l3.root()->Add(scale);\n\n  damage = DiffLayerTree(l3, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 180, 180));\n\n  MockLayerTree l4;\n  l4.root()->Add(scale);\n\n  // path just outside of readback region, doesn't affect blur\n  auto path1 = SkPath().addRect(SkRect::MakeLTRB(180, 180, 190, 190));\n  l4.root()->Add(std::make_shared<MockLayer>(path1));\n  damage = DiffLayerTree(l4, l3);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(180, 180, 190, 190));\n\n  MockLayerTree l5;\n  l5.root()->Add(scale);\n\n  // path just inside of readback region, must trigger backdrop repaint\n  auto path2 = SkPath().addRect(SkRect::MakeLTRB(179, 179, 189, 189));\n  l5.root()->Add(std::make_shared<MockLayer>(path2));\n  damage = DiffLayerTree(l5, l4);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 190, 190));\n}\n\nTEST_F(BackdropLayerDiffTest, BackdropLayerInvalidTransform) {\n  auto filter = DlBlurImageFilter(10, 10, DlTileMode::kClamp);\n\n  {\n    // tests later assume 30px readback area, fail early if that's not the case\n    skity::Rect readback;\n    EXPECT_EQ(filter.get_input_device_bounds(skity::Rect::MakeWH(10, 10),\n                                             skity::Matrix(), readback),\n              &readback);\n    EXPECT_EQ(readback, skity::Rect::MakeLTRB(-30, -30, 40, 40));\n  }\n\n  MockLayerTree l1({100, 100});\n  skity::Matrix transform;\n  transform.SetPersp0(0.1);\n  transform.SetPersp1(0.1);\n\n  auto transform_layer = std::make_shared<TransformLayer>(transform);\n  l1.root()->Add(transform_layer);\n  transform_layer->Add(std::make_shared<BackdropFilterLayer>(\n      filter.shared(), DlBlendMode::kSrcOver));\n\n  auto damage = DiffLayerTree(l1, MockLayerTree({100, 100}));\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeWH(100, 100));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/cacheable_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/cacheable_layer.h\"\n\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/raster_cache_item.h\"\n\nnamespace clay {\n\nAutoCache::AutoCache(RasterCacheItem* raster_cache_item,\n                     PrerollContext* context, const skity::Matrix& matrix)\n    : raster_cache_item_(raster_cache_item),\n      context_(context),\n      matrix_(matrix) {\n  if (IsCacheEnabled()) {\n    raster_cache_item->PrerollSetup(context, matrix);\n  }\n}\n\nbool AutoCache::IsCacheEnabled() {\n  return raster_cache_item_ && context_ && context_->raster_cache;\n}\n\nAutoCache::~AutoCache() {\n  if (IsCacheEnabled()) {\n    raster_cache_item_->PrerollFinalize(context_, matrix_);\n  }\n}\n\nCacheableContainerLayer::CacheableContainerLayer(int layer_cached_threshold,\n                                                 bool can_cache_children) {\n  layer_raster_cache_item_ = LayerRasterCacheItem::Make(\n      this, layer_cached_threshold, can_cache_children);\n}\n\nvoid CacheableContainerLayer::UpdateRasterCacheItem(int layer_cached_threshold,\n                                                    bool can_cache_children) {\n  layer_raster_cache_item_ = LayerRasterCacheItem::Make(\n      this, layer_cached_threshold, can_cache_children);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/cacheable_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_CACHEABLE_LAYER_H_\n#define CLAY_FLOW_LAYERS_CACHEABLE_LAYER_H_\n\n#include <memory>\n\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/layer_raster_cache_item.h\"\n\nnamespace clay {\n\nclass AutoCache {\n public:\n  AutoCache(RasterCacheItem* raster_cache_item, PrerollContext* context,\n            const skity::Matrix& matrix);\n\n  void ShouldNotBeCached() { raster_cache_item_ = nullptr; }\n\n  ~AutoCache();\n\n private:\n  inline bool IsCacheEnabled();\n  RasterCacheItem* raster_cache_item_ = nullptr;\n  PrerollContext* context_ = nullptr;\n  const skity::Matrix matrix_;\n};\n\nclass CacheableContainerLayer : public ContainerLayer {\n public:\n  explicit CacheableContainerLayer(\n      int layer_cached_threshold =\n          RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer,\n      bool can_cache_children = false);\n\n  const LayerRasterCacheItem* raster_cache_item() const {\n    return layer_raster_cache_item_.get();\n  }\n\n protected:\n  void UpdateRasterCacheItem(int layer_cached_threshold,\n                             bool can_cache_children);\n\n  std::unique_ptr<LayerRasterCacheItem> layer_raster_cache_item_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_CACHEABLE_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/checkerboard_layertree_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/clip_path_layer.h\"\n#include \"clay/flow/layers/clip_rect_layer.h\"\n#include \"clay/flow/layers/clip_rrect_layer.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing CheckerBoardLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(CheckerBoardLayerTest, ClipRectSaveLayerCheckBoard) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(1.0, 2.0, 2.0, 2.0);\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipRectLayer>(layer_bounds,\n                                               Clip::antiAliasWithSaveLayer);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(),\n            std::vector({Mutator(layer_bounds)}));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(layer_bounds),\n                   SkClipOp::kIntersect, MockCanvas::kSoft_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                child_bounds),\n                                            SkPaint(), nullptr, 2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n\n  mock_canvas().reset_draw_calls();\n\n  layer->Paint(checkerboard_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(layer_bounds),\n                   SkClipOp::kIntersect, MockCanvas::kSoft_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                child_bounds),\n                                            SkPaint(), nullptr, 2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path, child_paint}},\n           // start DrawCheckerboard calls\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawRectData{clay::ConvertSkityRectToSkRect(\n                                               child_bounds),\n                                           checkerboard_paint()}},\n           // end DrawCheckerboard calls\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(CheckerBoardLayerTest, ClipPathSaveLayerCheckBoard) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(1.0, 2.0, 2.0, 2.0);\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath layer_path =\n      SkPath()\n          .addRect(clay::ConvertSkityRectToSkRect(layer_bounds))\n          .addRect(clay::ConvertSkityRectToSkRect(layer_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  const SkPaint clip_paint;\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer =\n      std::make_shared<ClipPathLayer>(layer_path, Clip::antiAliasWithSaveLayer);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)}));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::ClipPathData{layer_path, SkClipOp::kIntersect,\n                                           MockCanvas::kSoft_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                child_bounds),\n                                            clip_paint, nullptr, 2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n\n  mock_canvas().reset_draw_calls();\n\n  layer->Paint(checkerboard_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::ClipPathData{layer_path, SkClipOp::kIntersect,\n                                           MockCanvas::kSoft_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                child_bounds),\n                                            clip_paint, nullptr, 2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path, child_paint}},\n           // start DrawCheckerboard calls\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawRectData{clay::ConvertSkityRectToSkRect(\n                                               child_bounds),\n                                           checkerboard_paint()}},\n           // end DrawCheckerboard calls\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(CheckerBoardLayerTest, ClipRRectSaveLayerCheckBoard) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(1.0, 2.0, 2.0, 2.0);\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const skity::RRect layer_rrect =\n      skity::RRect::MakeRectXY(layer_bounds, .1, .1);\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  const SkPaint clip_paint;\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipRRectLayer>(layer_rrect,\n                                                Clip::antiAliasWithSaveLayer);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)}));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRRectData{\n                   clay::ConvertSkityRRectToSkia(layer_rrect),\n                   SkClipOp::kIntersect, MockCanvas::kSoft_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                child_bounds),\n                                            clip_paint, nullptr, 2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n\n  mock_canvas().reset_draw_calls();\n\n  layer->Paint(checkerboard_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRRectData{\n                   clay::ConvertSkityRRectToSkia(layer_rrect),\n                   SkClipOp::kIntersect, MockCanvas::kSoft_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                child_bounds),\n                                            clip_paint, nullptr, 2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path, child_paint}},\n           // start DrawCheckerboard calls\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawRectData{clay::ConvertSkityRectToSkRect(\n                                               child_bounds),\n                                           checkerboard_paint()}},\n           // end DrawCheckerboard calls\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\n#endif\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/clip_path_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/clip_path_layer.h\"\n\nnamespace clay {\n\nClipPathLayer::ClipPathLayer(const clay::GrPath& clip_path, Clip clip_behavior)\n    : ClipShapeLayer(clip_path, clip_behavior) {\n  RECT_ASSIGN(clip_shape_bounds_, PATH_GET_BOUNDS(clip_shape()));\n}\n\nconst skity::Rect& ClipPathLayer::clip_shape_bounds() const {\n  return clip_shape_bounds_;\n}\n\nvoid ClipPathLayer::ApplyClip(LayerStateStack::MutatorContext& mutator) const {\n  mutator.clipPath(clip_shape(), clip_behavior() != Clip::hardEdge);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/clip_path_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_CLIP_PATH_LAYER_H_\n#define CLAY_FLOW_LAYERS_CLIP_PATH_LAYER_H_\n\n#include <string>\n\n#include \"clay/flow/layers/clip_shape_layer.h\"\n\nnamespace clay {\n\nclass ClipPathLayer : public ClipShapeLayer<clay::GrPath> {\n public:\n  explicit ClipPathLayer(const clay::GrPath& clip_path,\n                         Clip clip_behavior = Clip::antiAlias);\n\n protected:\n  const skity::Rect& clip_shape_bounds() const override;\n\n  void ApplyClip(LayerStateStack::MutatorContext& mutator) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"ClipPathLayer\"; }\n#endif\n\n private:\n  skity::Rect clip_shape_bounds_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ClipPathLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_CLIP_PATH_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/clip_path_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"clay/flow/layers/clip_path_layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/layers/platform_view_layer.h\"\n#include \"clay/flow/raster_cache_item.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkPaint.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing ClipPathLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(ClipPathLayerTest, ClipNoneBehaviorDies) {\n  EXPECT_DEATH_IF_SUPPORTED(\n      auto clip = std::make_shared<ClipPathLayer>(SkPath(), Clip::none),\n      \"clip_behavior != Clip::none\");\n}\n\nTEST_F(ClipPathLayerTest, PaintingEmptyLayerDies) {\n  auto layer = std::make_shared<ClipPathLayer>(SkPath(), Clip::hardEdge);\n\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ClipPathLayerTest, PaintBeforePrerollDies) {\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath layer_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(layer_bounds));\n  auto layer = std::make_shared<ClipPathLayer>(layer_path, Clip::hardEdge);\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ClipPathLayerTest, PaintingCulledLayerDies) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(1.0, 2.0, 2.0, 2.0);\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const skity::Rect distant_bounds =\n      skity::Rect::MakeXYWH(100.0, 100.0, 10.0, 10.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath layer_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(layer_bounds));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer = std::make_shared<ClipPathLayer>(layer_path, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  // Cull these children\n  preroll_context()->state_stack.set_preroll_delegate(distant_bounds,\n                                                      initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), distant_bounds);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), kEmptyRect);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)}));\n\n  auto mutator = paint_context().state_stack.save();\n  mutator.clipRect(distant_bounds, false);\n  EXPECT_FALSE(mock_layer->needs_painting(paint_context()));\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(ClipPathLayerTest, ChildOutsideBounds) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect local_cull_bounds =\n      skity::Rect::MakeXYWH(0.0, 0.0, 2.0, 4.0);\n  const skity::Rect device_cull_bounds =\n      initial_matrix.MapRect(local_cull_bounds);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(2.5, 5.0, 4.5, 4.0);\n  const skity::Rect clip_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath clip_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(clip_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipPathLayer>(clip_path, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  skity::Rect clip_cull_rect = local_cull_bounds;\n  ASSERT_TRUE(clip_cull_rect.Intersect(clip_bounds));\n  skity::Rect clip_layer_bounds = child_bounds;\n  ASSERT_TRUE(clip_layer_bounds.Intersect(clip_bounds));\n\n  // Set up both contexts to cull clipped child\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,\n                                                      initial_matrix);\n  paint_context().canvas->clipRect(\n      clay::ConvertSkityRectToSkRect(device_cull_bounds));\n  paint_context().canvas->concat(\n      clay::ConvertSkityMatrixToSkMatrix(initial_matrix));\n\n  layer->Preroll(preroll_context());\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),\n            device_cull_bounds);\n  EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(),\n            local_cull_bounds);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_path)}));\n\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n  EXPECT_FALSE(mock_layer->needs_painting(paint_context()));\n  // Top level layer not visible so calling layer->Paint()\n  // would trip an FML_DCHECK\n}\n\nTEST_F(ClipPathLayerTest, FullyContainedChild) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(1.0, 2.0, 2.0, 2.0);\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath layer_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(layer_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipPathLayer>(layer_path, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)}));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(layer_bounds),\n                   SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(ClipPathLayerTest, PartiallyContainedChild) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect local_cull_bounds =\n      skity::Rect::MakeXYWH(0.0, 0.0, 4.0, 5.5);\n  const skity::Rect device_cull_bounds =\n      initial_matrix.MapRect(local_cull_bounds);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(2.5, 5.0, 4.5, 4.0);\n  const skity::Rect clip_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath clip_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(clip_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipPathLayer>(clip_path, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  skity::Rect clip_cull_rect = local_cull_bounds;\n  ASSERT_TRUE(clip_cull_rect.Intersect(clip_bounds));\n  skity::Rect clip_layer_bounds = child_bounds;\n  ASSERT_TRUE(clip_layer_bounds.Intersect(clip_bounds));\n\n  // Cull child\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,\n                                                      initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),\n            device_cull_bounds);\n  EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(),\n            local_cull_bounds);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_path)}));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(clip_bounds),\n                   SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nstatic bool ReadbackResult(PrerollContext* context, Clip clip_behavior,\n                           const std::shared_ptr<Layer>& child, bool before) {\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath layer_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(layer_bounds));\n  auto layer = std::make_shared<ClipPathLayer>(layer_path, clip_behavior);\n  if (child != nullptr) {\n    layer->Add(child);\n  }\n  context->surface_needs_readback = before;\n  layer->Preroll(context);\n  return context->surface_needs_readback;\n}\n\nTEST_F(ClipPathLayerTest, Readback) {\n  PrerollContext* context = preroll_context();\n  SkPath path;\n  SkPaint paint;\n\n  const Clip hard = Clip::hardEdge;\n  const Clip soft = Clip::antiAlias;\n  const Clip save_layer = Clip::antiAliasWithSaveLayer;\n\n  std::shared_ptr<MockLayer> nochild;\n  auto reader = std::make_shared<MockLayer>(path, paint);\n  reader->set_fake_reads_surface(true);\n  auto nonreader = std::make_shared<MockLayer>(path, paint);\n\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath layer_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(layer_bounds));\n  auto layer = std::make_shared<ClipPathLayer>(layer_path, hard);\n  if (nochild != nullptr) {\n    layer->Add(nochild);\n  }\n  context->surface_needs_readback = false;\n  layer->Preroll(context);\n  // No children, no prior readback -> no readback after\n  EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));\n  EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));\n\n  // No children, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));\n\n  // Non readback child, no prior readback -> no readback after\n  EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));\n  EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));\n\n  // Non readback child, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));\n\n  // Readback child, no prior readback -> readback after unless SaveLayer\n  EXPECT_TRUE(ReadbackResult(context, hard, reader, false));\n  EXPECT_TRUE(ReadbackResult(context, soft, reader, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));\n\n  // Readback child, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, reader, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, reader, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));\n}\n\nTEST_F(ClipPathLayerTest, OpacityInheritance) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto layer_clip = SkPath()\n                        .addRect(SkRect::MakeLTRB(5, 5, 25, 25))\n                        .addOval(SkRect::MakeLTRB(20, 20, 40, 50));\n  auto clip_path_layer =\n      std::make_shared<ClipPathLayer>(layer_clip, Clip::hardEdge);\n  clip_path_layer->Add(mock1);\n\n  // ClipRectLayer will pass through compatibility from a compatible child\n  PrerollContext* context = preroll_context();\n  clip_path_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path2 = SkPath().addRect({40, 40, 50, 50});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  clip_path_layer->Add(mock2);\n\n  // ClipRectLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  clip_path_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path3 = SkPath().addRect({20, 20, 40, 40});\n  auto mock3 = MockLayer::MakeOpacityCompatible(path3);\n  clip_path_layer->Add(mock3);\n\n  // ClipRectLayer will not pass through compatibility from multiple\n  // overlapping children even if they are individually compatible\n  clip_path_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, 0);\n\n  {\n    // ClipRectLayer(aa with saveLayer) will always be compatible\n    auto clip_path_savelayer = std::make_shared<ClipPathLayer>(\n        layer_clip, Clip::antiAliasWithSaveLayer);\n    clip_path_savelayer->Add(mock1);\n    clip_path_savelayer->Add(mock2);\n\n    // Double check first two children are compatible and non-overlapping\n    clip_path_savelayer->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n    // Now add the overlapping child and test again, should still be compatible\n    clip_path_savelayer->Add(mock3);\n    clip_path_savelayer->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n  }\n\n  // An incompatible, but non-overlapping child for the following tests\n  auto path4 = SkPath().addRect({60, 60, 70, 70});\n  auto mock4 = MockLayer::Make(path4);\n\n  {\n    // ClipRectLayer with incompatible child will not be compatible\n    auto clip_path_bad_child =\n        std::make_shared<ClipPathLayer>(layer_clip, Clip::hardEdge);\n    clip_path_bad_child->Add(mock1);\n    clip_path_bad_child->Add(mock2);\n\n    // Double check first two children are compatible and non-overlapping\n    clip_path_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags,\n              LayerStateStack::kCallerCanApplyOpacity);\n\n    clip_path_bad_child->Add(mock4);\n\n    // The third child is non-overlapping, but not compatible so the\n    // TransformLayer should end up incompatible\n    clip_path_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, 0);\n  }\n\n  {\n    // ClipRectLayer(aa with saveLayer) will always be compatible\n    auto clip_path_savelayer_bad_child = std::make_shared<ClipPathLayer>(\n        layer_clip, Clip::antiAliasWithSaveLayer);\n    clip_path_savelayer_bad_child->Add(mock1);\n    clip_path_savelayer_bad_child->Add(mock2);\n\n    // Double check first two children are compatible and non-overlapping\n    clip_path_savelayer_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n    // Now add the incompatible child and test again, should still be compatible\n    clip_path_savelayer_bad_child->Add(mock4);\n    clip_path_savelayer_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n  }\n}\n\nTEST_F(ClipPathLayerTest, OpacityInheritancePainting) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto path2 = SkPath().addRect({40, 40, 50, 50});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  auto layer_clip = SkPath()\n                        .addRect(SkRect::MakeLTRB(5, 5, 25, 25))\n                        .addOval(SkRect::MakeLTRB(45, 45, 55, 55));\n  auto clip_path_layer =\n      std::make_shared<ClipPathLayer>(layer_clip, Clip::antiAlias);\n  clip_path_layer->Add(mock1);\n  clip_path_layer->Add(mock2);\n\n  // ClipRectLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  PrerollContext* context = preroll_context();\n  clip_path_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(clip_path_layer);\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        canvas->save();\n        canvas->clipPath(layer_clip, SkClipOp::kIntersect, true);\n        {\n          SkPaint paint1;\n          paint1.setAlpha(opacity_alpha);\n          canvas->drawPath(path1, paint1);\n        }\n        {\n          SkPaint paint2;\n          paint2.setAlpha(opacity_alpha);\n          canvas->drawPath(path2, paint2);\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ClipPathLayerTest, OpacityInheritanceSaveLayerPainting) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto path2 = SkPath().addRect({20, 20, 40, 40});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  auto children_bounds = path1.getBounds();\n  children_bounds.join(path2.getBounds());\n  auto layer_clip = SkPath()\n                        .addRect(SkRect::MakeLTRB(5, 5, 25, 25))\n                        .addOval(SkRect::MakeLTRB(20, 20, 40, 50));\n  auto clip_path_layer =\n      std::make_shared<ClipPathLayer>(layer_clip, Clip::antiAliasWithSaveLayer);\n  clip_path_layer->Add(mock1);\n  clip_path_layer->Add(mock2);\n\n  // ClipRectLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  PrerollContext* context = preroll_context();\n  clip_path_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(clip_path_layer);\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        canvas->save();\n        canvas->clipPath(layer_clip, SkClipOp::kIntersect, true);\n        SkPaint paint;\n        paint.setColor(opacity_alpha << 24);\n        canvas->saveLayer(&children_bounds, &paint);\n        {\n          SkPaint paint1;\n          paint1.setColor(0xFF000000);\n          canvas->drawPath(path1, paint1);\n        }\n        { canvas->drawPath(path2, SkPaint()); }\n\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ClipPathLayerTest, LayerCached) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto layer_clip = SkPath()\n                        .addRect(SkRect::MakeLTRB(5, 5, 25, 25))\n                        .addOval(SkRect::MakeLTRB(20, 20, 40, 50));\n  auto layer =\n      std::make_shared<ClipPathLayer>(layer_clip, Clip::antiAliasWithSaveLayer);\n  layer->Add(mock1);\n\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n\n  use_mock_raster_cache();\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n\n  const auto* clip_cache_item = layer->raster_cache_item();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n  EXPECT_EQ(clip_cache_item->cache_state(),\n            RasterCacheItem::CacheState::kCurrent);\n  SkPaint paint;\n  EXPECT_TRUE(raster_cache()->Draw(clip_cache_item->GetId().value(),\n                                   cache_canvas, &paint));\n}\n\nTEST_F(ClipPathLayerTest, EmptyClipDoesNotCullPlatformView) {\n  const skity::Vec2 view_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 view_size = skity::Vec2(8.0f, 8.0f);\n  const int64_t view_id = 42;\n  auto platform_view =\n      std::make_shared<PlatformViewLayer>(view_offset, view_size, view_id);\n\n  auto layer_clip =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(kEmptyRect));\n  auto clip =\n      std::make_shared<ClipPathLayer>(layer_clip, Clip::antiAliasWithSaveLayer);\n  clip->Add(platform_view);\n\n  auto compositor_state = CompositorState({8, 8});\n  preroll_context()->compositor_state = &compositor_state;\n  paint_context().compositor_state = &compositor_state;\n\n  clip->Preroll(preroll_context());\n  EXPECT_EQ(compositor_state.GetCompositionOrder(),\n            std::vector<int64_t>({view_id}));\n\n  clip->Paint(paint_context());\n  EXPECT_EQ(paint_context().canvas,\n            compositor_state.GetSlices()[view_id]->canvas());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/clip_rect_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/clip_rect_layer.h\"\n\nnamespace clay {\n\nClipRectLayer::ClipRectLayer(const skity::Rect& clip_rect, Clip clip_behavior)\n    : ClipShapeLayer(clip_rect, clip_behavior) {}\n\nconst skity::Rect& ClipRectLayer::clip_shape_bounds() const {\n  return clip_shape();\n}\n\nvoid ClipRectLayer::ApplyClip(LayerStateStack::MutatorContext& mutator) const {\n  mutator.clipRect(clip_shape(), clip_behavior() != Clip::hardEdge);\n}\n\n#ifndef NDEBUG\nstd::string ClipRectLayer::ToString() const {\n  std::stringstream ss;\n  ss << ContainerLayer::ToString();\n  auto clip_rect = clip_shape_bounds();\n  ss << \" clip_rect_=(\" << clip_rect.Left() << \",\" << clip_rect.Top() << \",\"\n     << clip_rect.Width() << \",\" << clip_rect.Height() << \")\";\n  ss << \" clip_behavior_=\" << clip_behavior();\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/clip_rect_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_CLIP_RECT_LAYER_H_\n#define CLAY_FLOW_LAYERS_CLIP_RECT_LAYER_H_\n\n#include <string>\n\n#include \"clay/flow/layers/clip_shape_layer.h\"\n\nnamespace clay {\n\nclass ClipRectLayer : public ClipShapeLayer<skity::Rect> {\n public:\n  ClipRectLayer(const skity::Rect& clip_rect, Clip clip_behavior);\n\n protected:\n  const skity::Rect& clip_shape_bounds() const override;\n\n  void ApplyClip(LayerStateStack::MutatorContext& mutator) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"ClipRectLayer\"; }\n  std::string ToString() const override;\n#endif\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(ClipRectLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_CLIP_RECT_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/clip_rect_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"clay/flow/layers/clip_rect_layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/layers/platform_view_layer.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing ClipRectLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(ClipRectLayerTest, ClipNoneBehaviorDies) {\n  EXPECT_DEATH_IF_SUPPORTED(\n      auto clip = std::make_shared<ClipRectLayer>(kEmptyRect, Clip::none),\n      \"clip_behavior != Clip::none\");\n}\n\nTEST_F(ClipRectLayerTest, PaintingEmptyLayerDies) {\n  auto layer = std::make_shared<ClipRectLayer>(kEmptyRect, Clip::hardEdge);\n\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ClipRectLayerTest, PaintBeforePrerollDies) {\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  auto layer = std::make_shared<ClipRectLayer>(layer_bounds, Clip::hardEdge);\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ClipRectLayerTest, PaintingCulledLayerDies) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(1.0, 2.0, 2.0, 2.0);\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const skity::Rect distant_bounds =\n      skity::Rect::MakeXYWH(100.0, 100.0, 10.0, 10.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer = std::make_shared<ClipRectLayer>(layer_bounds, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  // Cull these children\n  preroll_context()->state_stack.set_preroll_delegate(distant_bounds,\n                                                      initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), distant_bounds);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), kEmptyRect);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(),\n            std::vector({Mutator(layer_bounds)}));\n\n  auto mutator = paint_context().state_stack.save();\n  mutator.clipRect(distant_bounds, false);\n  EXPECT_FALSE(mock_layer->needs_painting(paint_context()));\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(ClipRectLayerTest, ChildOutsideBounds) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect local_cull_bounds =\n      skity::Rect::MakeXYWH(0.0, 0.0, 2.0, 4.0);\n  const skity::Rect device_cull_bounds =\n      initial_matrix.MapRect(local_cull_bounds);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(2.5, 5.0, 4.5, 4.0);\n  const skity::Rect clip_rect = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipRectLayer>(clip_rect, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  skity::Rect clip_cull_rect = local_cull_bounds;\n  ASSERT_TRUE(clip_cull_rect.Intersect(clip_rect));\n  skity::Rect clip_layer_bounds = child_bounds;\n  ASSERT_TRUE(clip_layer_bounds.Intersect(clip_rect));\n\n  // Set up both contexts to cull clipped child\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,\n                                                      initial_matrix);\n  paint_context().canvas->clipRect(\n      clay::ConvertSkityRectToSkRect(device_cull_bounds));\n  paint_context().canvas->concat(\n      clay::ConvertSkityMatrixToSkMatrix(initial_matrix));\n\n  layer->Preroll(preroll_context());\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),\n            device_cull_bounds);\n  EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(),\n            local_cull_bounds);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_rect)}));\n\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n  EXPECT_FALSE(mock_layer->needs_painting(paint_context()));\n  // Top level layer not visible so calling layer->Paint()\n  // would trip an FML_DCHECK\n}\n\nTEST_F(ClipRectLayerTest, FullyContainedChild) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(1.0, 2.0, 2.0, 2.0);\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipRectLayer>(layer_bounds, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(),\n            std::vector({Mutator(layer_bounds)}));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(layer_bounds),\n                   SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(ClipRectLayerTest, PartiallyContainedChild) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect local_cull_bounds =\n      skity::Rect::MakeXYWH(0.0, 0.0, 4.0, 5.5);\n  const skity::Rect device_cull_bounds =\n      initial_matrix.MapRect(local_cull_bounds);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(2.5, 5.0, 4.5, 4.0);\n  const skity::Rect clip_rect = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipRectLayer>(clip_rect, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  skity::Rect clip_cull_rect = clip_rect;\n  ASSERT_TRUE(clip_cull_rect.Intersect(local_cull_bounds));\n  skity::Rect clip_layer_bounds = clip_rect;\n  ASSERT_TRUE(clip_layer_bounds.Intersect(child_bounds));\n\n  // Cull child\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,\n                                                      initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),\n            device_cull_bounds);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_rect)}));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(clip_rect),\n                   SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nstatic bool ReadbackResult(PrerollContext* context, Clip clip_behavior,\n                           const std::shared_ptr<Layer>& child, bool before) {\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  auto layer = std::make_shared<ClipRectLayer>(layer_bounds, clip_behavior);\n  if (child != nullptr) {\n    layer->Add(child);\n  }\n  context->surface_needs_readback = before;\n  layer->Preroll(context);\n  return context->surface_needs_readback;\n}\n\nTEST_F(ClipRectLayerTest, Readback) {\n  PrerollContext* context = preroll_context();\n  SkPath path;\n  SkPaint paint;\n\n  const Clip hard = Clip::hardEdge;\n  const Clip soft = Clip::antiAlias;\n  const Clip save_layer = Clip::antiAliasWithSaveLayer;\n\n  std::shared_ptr<MockLayer> nochild;\n  auto reader = std::make_shared<MockLayer>(path, paint);\n  reader->set_fake_reads_surface(true);\n  auto nonreader = std::make_shared<MockLayer>(path, paint);\n\n  // No children, no prior readback -> no readback after\n  EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));\n  EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));\n\n  // No children, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));\n\n  // Non readback child, no prior readback -> no readback after\n  EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));\n  EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));\n\n  // Non readback child, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));\n\n  // Readback child, no prior readback -> readback after unless SaveLayer\n  EXPECT_TRUE(ReadbackResult(context, hard, reader, false));\n  EXPECT_TRUE(ReadbackResult(context, soft, reader, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));\n\n  // Readback child, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, reader, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, reader, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));\n}\n\nTEST_F(ClipRectLayerTest, OpacityInheritance) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  skity::Rect clip_rect = skity::Rect::MakeWH(500, 500);\n  auto clip_rect_layer =\n      std::make_shared<ClipRectLayer>(clip_rect, Clip::hardEdge);\n  clip_rect_layer->Add(mock1);\n\n  // ClipRectLayer will pass through compatibility from a compatible child\n  PrerollContext* context = preroll_context();\n  clip_rect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path2 = SkPath().addRect({40, 40, 50, 50});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  clip_rect_layer->Add(mock2);\n\n  // ClipRectLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  clip_rect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path3 = SkPath().addRect({20, 20, 40, 40});\n  auto mock3 = MockLayer::MakeOpacityCompatible(path3);\n  clip_rect_layer->Add(mock3);\n\n  // ClipRectLayer will not pass through compatibility from multiple\n  // overlapping children even if they are individually compatible\n  clip_rect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, 0);\n\n  {\n    // ClipRectLayer(aa with saveLayer) will always be compatible\n    auto clip_path_savelayer = std::make_shared<ClipRectLayer>(\n        clip_rect, Clip::antiAliasWithSaveLayer);\n    clip_path_savelayer->Add(mock1);\n    clip_path_savelayer->Add(mock2);\n\n    // Double check first two children are compatible and non-overlapping\n    clip_path_savelayer->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n    // Now add the overlapping child and test again, should still be compatible\n    clip_path_savelayer->Add(mock3);\n    clip_path_savelayer->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n  }\n\n  // An incompatible, but non-overlapping child for the following tests\n  auto path4 = SkPath().addRect({60, 60, 70, 70});\n  auto mock4 = MockLayer::Make(path4);\n\n  {\n    // ClipRectLayer with incompatible child will not be compatible\n    auto clip_rect_bad_child =\n        std::make_shared<ClipRectLayer>(clip_rect, Clip::hardEdge);\n    clip_rect_bad_child->Add(mock1);\n    clip_rect_bad_child->Add(mock2);\n\n    // Double check first two children are compatible and non-overlapping\n    clip_rect_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags,\n              LayerStateStack::kCallerCanApplyOpacity);\n\n    clip_rect_bad_child->Add(mock4);\n\n    // The third child is non-overlapping, but not compatible so the\n    // TransformLayer should end up incompatible\n    clip_rect_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, 0);\n  }\n\n  {\n    // ClipRectLayer(aa with saveLayer) will always be compatible\n    auto clip_path_savelayer_bad_child = std::make_shared<ClipRectLayer>(\n        clip_rect, Clip::antiAliasWithSaveLayer);\n    clip_path_savelayer_bad_child->Add(mock1);\n    clip_path_savelayer_bad_child->Add(mock2);\n\n    // Double check first two children are compatible and non-overlapping\n    clip_path_savelayer_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n    // Now add the incompatible child and test again, should still be compatible\n    clip_path_savelayer_bad_child->Add(mock4);\n    clip_path_savelayer_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n  }\n}\n\nTEST_F(ClipRectLayerTest, OpacityInheritancePainting) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto path2 = SkPath().addRect({40, 40, 50, 50});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  skity::Rect clip_rect = skity::Rect::MakeWH(500, 500);\n  auto clip_rect_layer =\n      std::make_shared<ClipRectLayer>(clip_rect, Clip::antiAlias);\n  clip_rect_layer->Add(mock1);\n  clip_rect_layer->Add(mock2);\n\n  // ClipRectLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  PrerollContext* context = preroll_context();\n  clip_rect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(clip_rect_layer);\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        canvas->save();\n        canvas->clipRect(clay::ConvertSkityRectToSkRect(clip_rect),\n                         SkClipOp::kIntersect, true);\n        {\n          SkPaint paint1;\n          paint1.setAlpha(opacity_alpha);\n          canvas->drawPath(path1, paint1);\n        }\n        {\n          SkPaint paint2;\n          paint2.setAlpha(opacity_alpha);\n          canvas->drawPath(path2, paint2);\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(expected_display_list->approximateOpCount() ==\n              display_list()->approximateOpCount());\n}\n\nTEST_F(ClipRectLayerTest, OpacityInheritanceSaveLayerPainting) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto path2 = SkPath().addRect({20, 20, 40, 40});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  auto children_bounds = path1.getBounds();\n  children_bounds.join(path2.getBounds());\n  SkRect clip_rect = SkRect::MakeWH(500, 500);\n  auto clip_rect_layer = std::make_shared<ClipRectLayer>(\n      clay::ConvertSkRectToSkityRect(clip_rect), Clip::antiAliasWithSaveLayer);\n  clip_rect_layer->Add(mock1);\n  clip_rect_layer->Add(mock2);\n\n  // ClipRectLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  PrerollContext* context = preroll_context();\n  clip_rect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(clip_rect_layer);\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        canvas->save();\n        canvas->clipRect(clip_rect, SkClipOp::kIntersect, true);\n        SkPaint paint;\n        paint.setColor(opacity_alpha << 24);\n        canvas->saveLayer(&children_bounds, &paint);\n        {\n          SkPaint paint1;\n          paint1.setColor(0xFF000000);\n          canvas->drawPath(path1, paint1);\n        }\n        { canvas->drawPath(path2, SkPaint()); }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(expected_display_list->approximateOpCount() ==\n              display_list()->approximateOpCount());\n}\n\nTEST_F(ClipRectLayerTest, LayerCached) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  skity::Rect clip_rect = skity::Rect::MakeWH(500, 500);\n  auto layer =\n      std::make_shared<ClipRectLayer>(clip_rect, Clip::antiAliasWithSaveLayer);\n  layer->Add(mock1);\n\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n\n  use_mock_raster_cache();\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n\n  const auto* clip_cache_item = layer->raster_cache_item();\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n  EXPECT_EQ(clip_cache_item->cache_state(),\n            RasterCacheItem::CacheState::kCurrent);\n  SkPaint paint;\n  EXPECT_TRUE(raster_cache()->Draw(clip_cache_item->GetId().value(),\n                                   cache_canvas, &paint));\n}\n\nTEST_F(ClipRectLayerTest, EmptyClipDoesNotCullPlatformView) {\n  const skity::Vec2 view_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 view_size = skity::Vec2(8.0f, 8.0f);\n  const int64_t view_id = 42;\n  auto platform_view =\n      std::make_shared<PlatformViewLayer>(view_offset, view_size, view_id);\n\n  auto clip = std::make_shared<ClipRectLayer>(kEmptyRect, Clip::hardEdge);\n  clip->Add(platform_view);\n\n  CompositorState compositor_state({64, 64});\n  preroll_context()->compositor_state = &compositor_state;\n  paint_context().compositor_state = &compositor_state;\n\n  clip->Preroll(preroll_context());\n  // cspell:words prerolled\n  EXPECT_EQ(compositor_state.GetCompositionOrder(),\n            std::vector<int64_t>({view_id}));\n\n  clip->Paint(paint_context());\n  EXPECT_EQ(paint_context().canvas,\n            compositor_state.GetSlices()[view_id]->canvas());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/clip_rrect_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/clip_rrect_layer.h\"\n\nnamespace clay {\n\nClipRRectLayer::ClipRRectLayer(const skity::RRect& clip_rrect,\n                               Clip clip_behavior)\n    : ClipShapeLayer(clip_rrect, clip_behavior) {}\n\nconst skity::Rect& ClipRRectLayer::clip_shape_bounds() const {\n  return clip_shape().GetBounds();\n}\n\nvoid ClipRRectLayer::ApplyClip(LayerStateStack::MutatorContext& mutator) const {\n  mutator.clipRRect(clip_shape(), clip_behavior() != Clip::hardEdge);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/clip_rrect_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_CLIP_RRECT_LAYER_H_\n#define CLAY_FLOW_LAYERS_CLIP_RRECT_LAYER_H_\n\n#include <string>\n\n#include \"clay/flow/layers/clip_shape_layer.h\"\n\nnamespace clay {\n\nclass ClipRRectLayer : public ClipShapeLayer<skity::RRect> {\n public:\n  ClipRRectLayer(const skity::RRect& clip_rrect, Clip clip_behavior);\n\n protected:\n  const skity::Rect& clip_shape_bounds() const override;\n\n  void ApplyClip(LayerStateStack::MutatorContext& mutator) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"ClipRRectLayer\"; }\n#endif\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(ClipRRectLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_CLIP_RRECT_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/clip_rrect_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"clay/flow/layers/clip_rrect_layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/layers/platform_view_layer.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing ClipRRectLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(ClipRRectLayerTest, ClipNoneBehaviorDies) {\n  const skity::RRect layer_rrect = skity::RRect::MakeEmpty();\n  EXPECT_DEATH_IF_SUPPORTED(\n      auto clip = std::make_shared<ClipRRectLayer>(layer_rrect, Clip::none),\n      \"clip_behavior != Clip::none\");\n}\n\nTEST_F(ClipRRectLayerTest, PaintingEmptyLayerDies) {\n  const skity::RRect layer_rrect = skity::RRect::MakeEmpty();\n  auto layer = std::make_shared<ClipRRectLayer>(layer_rrect, Clip::hardEdge);\n\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ClipRRectLayerTest, PaintBeforePrerollDies) {\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const skity::RRect layer_rrect = skity::RRect::MakeRect(layer_bounds);\n  auto layer = std::make_shared<ClipRRectLayer>(layer_rrect, Clip::hardEdge);\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ClipRRectLayerTest, PaintingCulledLayerDies) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(1.0, 2.0, 2.0, 2.0);\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const skity::Rect distant_bounds =\n      skity::Rect::MakeXYWH(100.0, 100.0, 10.0, 10.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const skity::RRect layer_rrect = skity::RRect::MakeRect(layer_bounds);\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipRRectLayer>(layer_rrect, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  // Cull these children\n  preroll_context()->state_stack.set_preroll_delegate(distant_bounds,\n                                                      initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), distant_bounds);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), kEmptyRect);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)}));\n\n  auto mutator = paint_context().state_stack.save();\n  mutator.clipRect(distant_bounds, false);\n  EXPECT_FALSE(mock_layer->needs_painting(paint_context()));\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(ClipRRectLayerTest, ChildOutsideBounds) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect local_cull_bounds =\n      skity::Rect::MakeXYWH(0.0, 0.0, 2.0, 4.0);\n  const skity::Rect device_cull_bounds =\n      initial_matrix.MapRect(local_cull_bounds);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(2.5, 5.0, 4.5, 4.0);\n  const skity::Rect clip_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const skity::RRect clip_rrect = skity::RRect::MakeRect(clip_bounds);\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipRRectLayer>(clip_rrect, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  skity::Rect clip_cull_rect = clip_bounds;\n  ASSERT_TRUE(clip_cull_rect.Intersect(local_cull_bounds));\n  skity::Rect clip_layer_bounds = child_bounds;\n  ASSERT_TRUE(clip_layer_bounds.Intersect(clip_bounds));\n\n  // Set up both contexts to cull clipped child\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,\n                                                      initial_matrix);\n  paint_context().canvas->clipRect(\n      clay::ConvertSkityRectToSkRect(device_cull_bounds));\n  paint_context().canvas->concat(\n      clay::ConvertSkityMatrixToSkMatrix(initial_matrix));\n\n  layer->Preroll(preroll_context());\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),\n            device_cull_bounds);\n  EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(),\n            local_cull_bounds);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_rrect)}));\n\n  EXPECT_FALSE(mock_layer->needs_painting(paint_context()));\n  ASSERT_FALSE(layer->needs_painting(paint_context()));\n  // Top level layer not visible so calling layer->Paint()\n  // would trip an FML_DCHECK\n}\n\nTEST_F(ClipRRectLayerTest, FullyContainedChild) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(1.0, 2.0, 2.0, 2.0);\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const skity::RRect layer_rrect = skity::RRect::MakeRect(layer_bounds);\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipRRectLayer>(layer_rrect, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_matrix);\n  layer->Preroll(preroll_context());\n\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(), kGiantRect);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)}));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(layer_bounds),\n                   SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(ClipRRectLayerTest, PartiallyContainedChild) {\n  const skity::Matrix initial_matrix = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect local_cull_bounds =\n      skity::Rect::MakeXYWH(0.0, 0.0, 4.0, 5.5);\n  const skity::Rect device_cull_bounds =\n      initial_matrix.MapRect(local_cull_bounds);\n  const skity::Rect child_bounds = skity::Rect::MakeXYWH(2.5, 5.0, 4.5, 4.0);\n  const skity::Rect clip_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const skity::RRect clip_rrect = skity::RRect::MakeRect(clip_bounds);\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ClipRRectLayer>(clip_rrect, Clip::hardEdge);\n  layer->Add(mock_layer);\n\n  skity::Rect clip_cull_rect = clip_bounds;\n  ASSERT_TRUE(clip_cull_rect.Intersect(local_cull_bounds));\n  skity::Rect clip_layer_bounds = child_bounds;\n  ASSERT_TRUE(clip_layer_bounds.Intersect(clip_bounds));\n\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_bounds,\n                                                      initial_matrix);\n\n  layer->Preroll(preroll_context());\n  // Untouched\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),\n            device_cull_bounds);\n  EXPECT_EQ(preroll_context()->state_stack.local_cull_rect(),\n            local_cull_bounds);\n  EXPECT_TRUE(preroll_context()->state_stack.is_empty());\n\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), clip_layer_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_EQ(mock_layer->parent_cull_rect(), clip_cull_rect);\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix);\n  EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(clip_rrect)}));\n\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(clip_bounds),\n                   SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nstatic bool ReadbackResult(PrerollContext* context, Clip clip_behavior,\n                           const std::shared_ptr<Layer>& child, bool before) {\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const skity::RRect layer_rrect = skity::RRect::MakeRect(layer_bounds);\n  auto layer = std::make_shared<ClipRRectLayer>(layer_rrect, clip_behavior);\n  if (child != nullptr) {\n    layer->Add(child);\n  }\n  context->surface_needs_readback = before;\n  layer->Preroll(context);\n  return context->surface_needs_readback;\n}\n\nTEST_F(ClipRRectLayerTest, Readback) {\n  PrerollContext* context = preroll_context();\n  SkPath path;\n  SkPaint paint;\n\n  const Clip hard = Clip::hardEdge;\n  const Clip soft = Clip::antiAlias;\n  const Clip save_layer = Clip::antiAliasWithSaveLayer;\n\n  std::shared_ptr<MockLayer> nochild;\n  auto reader = std::make_shared<MockLayer>(path, paint);\n  reader->set_fake_reads_surface(true);\n  auto nonreader = std::make_shared<MockLayer>(path, paint);\n\n  // No children, no prior readback -> no readback after\n  EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));\n  EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));\n\n  // No children, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));\n\n  // Non readback child, no prior readback -> no readback after\n  EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));\n  EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));\n\n  // Non readback child, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));\n\n  // Readback child, no prior readback -> readback after unless SaveLayer\n  EXPECT_TRUE(ReadbackResult(context, hard, reader, false));\n  EXPECT_TRUE(ReadbackResult(context, soft, reader, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));\n\n  // Readback child, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, reader, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, reader, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));\n}\n\nTEST_F(ClipRRectLayerTest, OpacityInheritance) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  skity::Rect clip_rect = skity::Rect::MakeWH(500, 500);\n  skity::RRect clip_rrect = skity::RRect::MakeRectXY(clip_rect, 20, 20);\n  auto clip_rrect_layer =\n      std::make_shared<ClipRRectLayer>(clip_rrect, Clip::hardEdge);\n  clip_rrect_layer->Add(mock1);\n\n  // ClipRectLayer will pass through compatibility from a compatible child\n  PrerollContext* context = preroll_context();\n  clip_rrect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path2 = SkPath().addRect({40, 40, 50, 50});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  clip_rrect_layer->Add(mock2);\n\n  // ClipRectLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  clip_rrect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path3 = SkPath().addRect({20, 20, 40, 40});\n  auto mock3 = MockLayer::MakeOpacityCompatible(path3);\n  clip_rrect_layer->Add(mock3);\n\n  // ClipRectLayer will not pass through compatibility from multiple\n  // overlapping children even if they are individually compatible\n  clip_rrect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, 0);\n\n  {\n    // ClipRectLayer(aa with saveLayer) will always be compatible\n    auto clip_rrect_savelayer = std::make_shared<ClipRRectLayer>(\n        clip_rrect, Clip::antiAliasWithSaveLayer);\n    clip_rrect_savelayer->Add(mock1);\n    clip_rrect_savelayer->Add(mock2);\n\n    // Double check first two children are compatible and non-overlapping\n    clip_rrect_savelayer->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n    // Now add the overlapping child and test again, should still be compatible\n    clip_rrect_savelayer->Add(mock3);\n    clip_rrect_savelayer->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n  }\n\n  // An incompatible, but non-overlapping child for the following tests\n  auto path4 = SkPath().addRect({60, 60, 70, 70});\n  auto mock4 = MockLayer::Make(path4);\n\n  {\n    // ClipRectLayer with incompatible child will not be compatible\n    auto clip_rrect_bad_child =\n        std::make_shared<ClipRRectLayer>(clip_rrect, Clip::hardEdge);\n    clip_rrect_bad_child->Add(mock1);\n    clip_rrect_bad_child->Add(mock2);\n\n    // Double check first two children are compatible and non-overlapping\n    clip_rrect_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags,\n              LayerStateStack::kCallerCanApplyOpacity);\n\n    clip_rrect_bad_child->Add(mock4);\n\n    // The third child is non-overlapping, but not compatible so the\n    // TransformLayer should end up incompatible\n    clip_rrect_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, 0);\n  }\n\n  {\n    // ClipRectLayer(aa with saveLayer) will always be compatible\n    auto clip_rrect_savelayer_bad_child = std::make_shared<ClipRRectLayer>(\n        clip_rrect, Clip::antiAliasWithSaveLayer);\n    clip_rrect_savelayer_bad_child->Add(mock1);\n    clip_rrect_savelayer_bad_child->Add(mock2);\n\n    // Double check first two children are compatible and non-overlapping\n    clip_rrect_savelayer_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n    // Now add the incompatible child and test again, should still be compatible\n    clip_rrect_savelayer_bad_child->Add(mock4);\n    clip_rrect_savelayer_bad_child->Preroll(context);\n    EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n  }\n}\n\nTEST_F(ClipRRectLayerTest, OpacityInheritancePainting) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto path2 = SkPath().addRect({40, 40, 50, 50});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  SkRect clip_rect = SkRect::MakeWH(500, 500);\n  SkRRect clip_rrect = SkRRect::MakeRectXY(clip_rect, 20, 20);\n  auto clip_rect_layer = std::make_shared<ClipRRectLayer>(\n      clay::ConvertSkRRectToSkityRRect(clip_rrect), Clip::antiAlias);\n  clip_rect_layer->Add(mock1);\n  clip_rect_layer->Add(mock2);\n\n  // ClipRectLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  PrerollContext* context = preroll_context();\n  clip_rect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(clip_rect_layer);\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        canvas->save();\n        canvas->clipRRect(clip_rrect, SkClipOp::kIntersect, true);\n        {\n          SkPaint paint1;\n          paint1.setAlpha(opacity_alpha);\n          canvas->drawPath(path1, paint1);\n        }\n        {\n          SkPaint paint2;\n          paint2.setAlpha(opacity_alpha);\n          canvas->drawPath(path2, paint2);\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(expected_display_list->approximateOpCount() ==\n              display_list()->approximateOpCount());\n}\n\nTEST_F(ClipRRectLayerTest, OpacityInheritanceSaveLayerPainting) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto path2 = SkPath().addRect({20, 20, 40, 40});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  auto children_bounds = path1.getBounds();\n  children_bounds.join(path2.getBounds());\n  SkRect clip_rect = SkRect::MakeWH(500, 500);\n  SkRRect clip_rrect = SkRRect::MakeRectXY(clip_rect, 20, 20);\n  auto clip_rrect_layer = std::make_shared<ClipRRectLayer>(\n      clay::ConvertSkRRectToSkityRRect(clip_rrect),\n      Clip::antiAliasWithSaveLayer);\n  clip_rrect_layer->Add(mock1);\n  clip_rrect_layer->Add(mock2);\n\n  // ClipRectLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  PrerollContext* context = preroll_context();\n  clip_rrect_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(clip_rrect_layer);\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        canvas->save();\n        canvas->clipRRect(clip_rrect, SkClipOp::kIntersect, true);\n        SkPaint paint;\n        paint.setColor(opacity_alpha << 24);\n        canvas->saveLayer(&children_bounds, &paint);\n        {\n          SkPaint paint1;\n          paint1.setColor(0xFF000000);\n          canvas->drawPath(path1, paint1);\n        }\n        {\n          SkPaint paint2;\n          canvas->drawPath(path2, paint2);\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(expected_display_list->approximateOpCount() ==\n              display_list()->approximateOpCount());\n}\n\nTEST_F(ClipRRectLayerTest, LayerCached) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  SkPaint paint = SkPaint();\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  skity::Rect clip_rect = skity::Rect::MakeWH(500, 500);\n  skity::RRect clip_rrect = skity::RRect::MakeRectXY(clip_rect, 20, 20);\n  auto layer = std::make_shared<ClipRRectLayer>(clip_rrect,\n                                                Clip::antiAliasWithSaveLayer);\n  layer->Add(mock1);\n\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n\n  use_mock_raster_cache();\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n\n  const auto* clip_cache_item = layer->raster_cache_item();\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n  EXPECT_EQ(clip_cache_item->cache_state(),\n            RasterCacheItem::CacheState::kCurrent);\n  EXPECT_TRUE(raster_cache()->Draw(clip_cache_item->GetId().value(),\n                                   cache_canvas, &paint));\n}\n\nTEST_F(ClipRRectLayerTest, NoSaveLayerShouldNotCache) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  skity::Rect clip_rect = skity::Rect::MakeWH(500, 500);\n  skity::RRect clip_rrect = skity::RRect::MakeRectXY(clip_rect, 20, 20);\n  auto layer = std::make_shared<ClipRRectLayer>(clip_rrect, Clip::antiAlias);\n  layer->Add(mock1);\n\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n\n  use_mock_raster_cache();\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n\n  const auto* clip_cache_item = layer->raster_cache_item();\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);\n\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(clip_cache_item->cache_state(), RasterCacheItem::CacheState::kNone);\n}\n\nTEST_F(ClipRRectLayerTest, EmptyClipDoesNotCullPlatformView) {\n  const skity::Vec2 view_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 view_size = skity::Vec2(8.0f, 8.0f);\n  const int64_t view_id = 42;\n  auto platform_view =\n      std::make_shared<PlatformViewLayer>(view_offset, view_size, view_id);\n\n  skity::RRect clip_rrect = skity::RRect::MakeRectXY(kEmptyRect, 20, 20);\n  auto clip = std::make_shared<ClipRRectLayer>(clip_rrect, Clip::antiAlias);\n  clip->Add(platform_view);\n\n  CompositorState compositor_state({100, 100});\n\n  preroll_context()->compositor_state = &compositor_state;\n  paint_context().compositor_state = &compositor_state;\n\n  clip->Preroll(preroll_context());\n  EXPECT_EQ(compositor_state.GetCompositionOrder(),\n            std::vector<int64_t>({view_id}));\n\n  clip->Paint(paint_context());\n  EXPECT_EQ(paint_context().canvas,\n            compositor_state.GetSlices()[view_id]->canvas());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/clip_shape_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_CLIP_SHAPE_LAYER_H_\n#define CLAY_FLOW_LAYERS_CLIP_SHAPE_LAYER_H_\n\n#include \"clay/flow/layers/cacheable_layer.h\"\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/paint_utils.h\"\n#include \"skity/geometry/rrect.hpp\"\n\nnamespace clay {\n\ntemplate <class T>\nclass ClipShapeLayer : public CacheableContainerLayer {\n public:\n  using ClipShape = T;\n  ClipShapeLayer(const ClipShape& clip_shape, Clip clip_behavior)\n      : CacheableContainerLayer(),\n        clip_shape_(clip_shape),\n        clip_behavior_(clip_behavior) {\n    FML_DCHECK(clip_behavior != Clip::none);\n  }\n\n  void Diff(DiffContext* context, const Layer* old_layer) override {\n    DiffContext::AutoSubtreeRestore subtree(context);\n    auto* prev = static_cast<const ClipShapeLayer<ClipShape>*>(old_layer);\n    if (!context->IsSubtreeDirty()) {\n      FML_DCHECK(prev);\n      if (clip_behavior_ != prev->clip_behavior_ ||\n          clip_shape_ != prev->clip_shape_) {\n        context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n      }\n    }\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n    if (UsesSaveLayer() && context->has_raster_cache()) {\n      context->SetTransform(\n          RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));\n    }\n#endif\n    if (context->PushCullRect(clip_shape_bounds())) {\n      DiffChildren(context, prev);\n    }\n    context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n  }\n\n  void Preroll(PrerollContext* context) override {\n    bool uses_save_layer = UsesSaveLayer();\n\n    // We can use the raster_cache for children only when the use_save_layer is\n    // true so if use_save_layer is false we pass the layer_raster_item is\n    // nullptr which mean we don't do raster cache logic.\n    AutoCache cache =\n        AutoCache(uses_save_layer ? layer_raster_cache_item_.get() : nullptr,\n                  context, context->state_stack.transform_4x4());\n\n    Layer::AutoPrerollSaveLayerState save =\n        Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());\n\n    auto mutator = context->state_stack.save();\n    ApplyClip(mutator);\n\n    skity::Rect child_paint_bounds = skity::Rect::MakeEmpty();\n    PrerollChildren(context, &child_paint_bounds);\n    if (child_paint_bounds.Intersect(clip_shape_bounds())) {\n      set_paint_bounds(child_paint_bounds);\n    } else {\n      set_paint_bounds(skity::Rect::MakeEmpty());\n    }\n\n    // If we use a SaveLayer then we can accept opacity on behalf\n    // of our children and apply it in the saveLayer.\n    if (uses_save_layer) {\n      context->renderable_state_flags = kSaveLayerRenderFlags;\n    }\n  }\n\n  void Paint(PaintContext& context) const override {\n    FML_DCHECK(needs_painting(context));\n\n    auto mutator = context.state_stack.save();\n    ApplyClip(mutator);\n\n    if (!UsesSaveLayer()) {\n      PaintChildren(context);\n      return;\n    }\n\n    if (context.raster_cache) {\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n      mutator.integralTransform();\n#endif\n      auto restore_apply = context.state_stack.applyState(\n          paint_bounds(), LayerStateStack::kCallerCanApplyOpacity);\n      clay::GrPaint paint;\n      if (layer_raster_cache_item_->Draw(context,\n                                         context.state_stack.fill(paint))) {\n        return;\n      }\n    }\n\n    mutator.saveLayer(paint_bounds());\n    PaintChildren(context);\n\n#ifndef NDEBUG\n    if (context.enable_raster_cache_tag && layer_raster_cache_item_ &&\n        layer_raster_cache_item_->has_been_cached()) {\n      // Generated raster cache, but not used.\n      DrawRasterCacheTag(context.canvas, paint_bounds().Width() / 2,\n                         paint_bounds().Height() / 2, 0);\n    }\n#endif  // NDEBUG\n  }\n\n  bool UsesSaveLayer() const {\n    return clip_behavior_ == Clip::antiAliasWithSaveLayer;\n  }\n\n protected:\n  virtual const skity::Rect& clip_shape_bounds() const = 0;\n  virtual void ApplyClip(LayerStateStack::MutatorContext& mutator) const = 0;\n  virtual ~ClipShapeLayer() = default;\n\n  const ClipShape& clip_shape() const { return clip_shape_; }\n  Clip clip_behavior() const { return clip_behavior_; }\n\n private:\n  const ClipShape clip_shape_;\n  Clip clip_behavior_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ClipShapeLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_CLIP_SHAPE_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/color_filter_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/color_filter_layer.h\"\n\n#include <utility>\n\n#include \"clay/flow/raster_cache_item.h\"\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/gfx/comparable.h\"\n#include \"clay/gfx/paint.h\"\n\nnamespace clay {\n\nColorFilterLayer::ColorFilterLayer(\n    std::shared_ptr<const clay::ColorFilter> filter)\n    : CacheableContainerLayer(\n          RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer, true),\n      filter_(std::move(filter)) {}\n\nvoid ColorFilterLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  auto* prev = static_cast<const ColorFilterLayer*>(old_layer);\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(prev);\n    if (NotEquals(filter_, prev->filter_)) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n  if (context->has_raster_cache()) {\n    context->SetTransform(\n        RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));\n  }\n#endif\n\n  DiffChildren(context, prev);\n\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid ColorFilterLayer::Preroll(PrerollContext* context) {\n  Layer::AutoPrerollSaveLayerState save =\n      Layer::AutoPrerollSaveLayerState::Create(context);\n  AutoCache cache = AutoCache(layer_raster_cache_item_.get(), context,\n                              context->state_stack.transform_4x4());\n\n  ContainerLayer::Preroll(context);\n\n  // Our saveLayer would apply any outstanding opacity or any outstanding\n  // image filter before it applies our color filter, but that is in the\n  // wrong order compared to how these attributes were applied to the tree\n  // (they would have come from one of our ancestors). So we cannot apply\n  // those attributes with our saveLayer normally.\n  // However, some color filters can commute themselves with an opacity\n  // modulation so in that case we can apply the opacity on behalf of our\n  // ancestors - otherwise we can apply no attributes.\n  if (filter_) {\n    context->renderable_state_flags =\n        filter_->can_commute_with_opacity()\n            ? LayerStateStack::kCallerCanApplyOpacity\n            : 0;\n  }\n  // else - we can apply whatever our children can apply.\n}\n\nvoid ColorFilterLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n  if (subtree_has_punch_hole()) {\n    auto mutator = context.state_stack.save();\n    context.only_draw_punch_hole = true;\n    PaintChildren(context);\n    context.only_draw_punch_hole = false;\n  }\n\n  auto mutator = context.state_stack.save();\n\n  if (context.raster_cache) {\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n    // Always apply the integral transform in the presence of a raster cache\n    // whether or not we will draw from the cache\n    mutator.integralTransform();\n#endif\n\n    // Try drawing the layer cache item from the cache before applying the\n    // color filter if it was cached with the filter applied.\n    if (!layer_raster_cache_item_->IsCacheChildren()) {\n      clay::GrPaint paint;\n      if (layer_raster_cache_item_->Draw(context,\n                                         context.state_stack.fill(paint))) {\n        return;\n      }\n    }\n  }\n\n  // Now apply the color filter and then try rendering children either from\n  // cache or directly.\n  mutator.applyColorFilter(paint_bounds(), filter_);\n\n  if (context.raster_cache && layer_raster_cache_item_->IsCacheChildren()) {\n    clay::GrPaint paint;\n    if (layer_raster_cache_item_->Draw(context,\n                                       context.state_stack.fill(paint))) {\n      return;\n    }\n  }\n\n  PaintChildren(context);\n\n#ifndef NDEBUG\n  if (context.enable_raster_cache_tag && layer_raster_cache_item_ &&\n      layer_raster_cache_item_->has_been_cached()) {\n    // Generated raster cache, but not used.\n    DrawRasterCacheTag(context.canvas, paint_bounds().Width() / 2,\n                       paint_bounds().Height() / 2, 0);\n  }\n#endif  // NDEBUG\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/color_filter_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_COLOR_FILTER_LAYER_H_\n#define CLAY_FLOW_LAYERS_COLOR_FILTER_LAYER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/flow/layers/cacheable_layer.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/gfx/style/color_filter.h\"\n\nnamespace clay {\n\nclass ColorFilterLayer : public CacheableContainerLayer {\n public:\n  explicit ColorFilterLayer(std::shared_ptr<const clay::ColorFilter> filter);\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  void Preroll(PrerollContext* context) override;\n\n  void Paint(PaintContext& context) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"ColorFilterLayer\"; }\n#endif\n\n private:\n  std::shared_ptr<const clay::ColorFilter> filter_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ColorFilterLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_COLOR_FILTER_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/color_filter_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/compositor_context.h\"\n#include \"clay/flow/layers/color_filter_layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/raster_cache_key.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/paint.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n#include \"third_party/skia/include/core/SkColorFilter.h\"\n#include \"third_party/skia/include/effects/SkColorMatrixFilter.h\"\nnamespace clay {\nnamespace testing {\n\nusing ColorFilterLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(ColorFilterLayerTest, PaintingEmptyLayerDies) {\n  auto layer = std::make_shared<ColorFilterLayer>(\n      std::make_shared<DlUnknownColorFilter>(sk_sp<SkColorFilter>()));\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ColorFilterLayerTest, PaintBeforePrerollDies) {\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n\n  auto layer = std::make_shared<ColorFilterLayer>(\n      std::make_shared<DlUnknownColorFilter>(sk_sp<SkColorFilter>()));\n  layer->Add(mock_layer);\n\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(ColorFilterLayerTest, EmptyFilter) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ColorFilterLayer>(nullptr);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n\n  SkPaint filter_paint;\n  filter_paint.setColorFilter(nullptr);\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({\n                MockCanvas::DrawCall{\n                    0, MockCanvas::DrawPathData{child_path, child_paint}},\n            }));\n}\n\nTEST_F(ColorFilterLayerTest, SimpleFilter) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n\n  auto dl_color_filter = DlLinearToSrgbGammaColorFilter::instance;\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ColorFilterLayer>(dl_color_filter);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    SkPaint paint;\n    paint.setColorFilter(dl_color_filter->gr_object());\n    auto bounds_tmp = clay::ConvertSkityRectToSkRect(child_bounds);\n    canvas->saveLayer(bounds_tmp, &paint);\n    {\n      SkPaint tmp_paint;\n      tmp_paint.setColor(SK_ColorYELLOW);\n      canvas->drawPath(child_path, tmp_paint);\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ColorFilterLayerTest, MultipleChildren) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);\n  const SkPath child_path1 =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath child_path2 = SkPath().addRect(\n      clay::ConvertSkityRectToSkRect(child_bounds.MakeOffset(3.0f, 0.0f)));\n  const SkPaint child_paint1 = SkPaint(SkColors::kYellow);\n  const SkPaint child_paint2 = SkPaint(SkColors::kCyan);\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto dl_color_filter = DlSrgbToLinearGammaColorFilter::instance;\n  auto layer = std::make_shared<ColorFilterLayer>(dl_color_filter);\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  skity::Rect children_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  children_bounds.Join(clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), children_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), children_bounds);\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    SkPaint paint;\n    paint.setColorFilter(dl_color_filter->gr_object());\n    auto bounds_tmp = clay::ConvertSkityRectToSkRect(children_bounds);\n    canvas->saveLayer(bounds_tmp, &paint);\n    {\n      SkPaint tmp_paint;\n      tmp_paint.setColor(SK_ColorYELLOW);\n      canvas->drawPath(child_path1, tmp_paint);\n    }\n    {\n      SkPaint tmp_paint;\n      tmp_paint.setColor(SK_ColorCYAN);\n      canvas->drawPath(child_path2, tmp_paint);\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ColorFilterLayerTest, Nested) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);\n  const SkPath child_path1 =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath child_path2 = SkPath().addRect(\n      clay::ConvertSkityRectToSkRect(child_bounds.MakeOffset(3.0f, 0.0f)));\n  const SkPaint child_paint1 = SkPaint(SkColors::kYellow);\n  const SkPaint child_paint2 = SkPaint(SkColors::kCyan);\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto dl_color_filter = DlSrgbToLinearGammaColorFilter::instance;\n  auto layer1 = std::make_shared<ColorFilterLayer>(dl_color_filter);\n\n  auto layer2 = std::make_shared<ColorFilterLayer>(dl_color_filter);\n  layer2->Add(mock_layer2);\n  layer1->Add(mock_layer1);\n  layer1->Add(layer2);\n\n  skity::Rect children_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  children_bounds.Join(clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer1->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer1->paint_bounds(), children_bounds);\n  EXPECT_EQ(layer1->child_paint_bounds(), children_bounds);\n  EXPECT_EQ(layer2->paint_bounds(), mock_layer2->paint_bounds());\n  EXPECT_EQ(layer2->child_paint_bounds(), mock_layer2->paint_bounds());\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(layer2->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    SkPaint paint;\n    paint.setColorFilter(dl_color_filter->gr_object());\n    auto bounds_tmp = clay::ConvertSkityRectToSkRect(children_bounds);\n    canvas->saveLayer(bounds_tmp, &paint);\n    {\n      SkPaint tmp_paint;\n      tmp_paint.setColor(SK_ColorYELLOW);\n      canvas->drawPath(child_path1, tmp_paint);\n    }\n    {\n      SkPaint tmp_paint;\n      tmp_paint.setColor(SK_ColorBLACK);\n      tmp_paint.setColorFilter(dl_color_filter->gr_object());\n      canvas->saveLayer(&child_path2.getBounds(), &tmp_paint);\n      {\n        SkPaint inner_paint;\n        inner_paint.setColor(SK_ColorCYAN);\n        canvas->drawPath(child_path2, inner_paint);\n      }\n      canvas->restore();\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  layer1->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ColorFilterLayerTest, Readback) {\n  auto initial_transform = skity::Matrix();\n\n  // ColorFilterLayer does not read from surface\n  auto layer = std::make_shared<ColorFilterLayer>(\n      DlLinearToSrgbGammaColorFilter::instance);\n  preroll_context()->surface_needs_readback = false;\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n\n  // ColorFilterLayer blocks child with readback\n  auto mock_layer = std::make_shared<MockLayer>(SkPath(), SkPaint());\n  mock_layer->set_fake_reads_surface(true);\n  layer->Add(mock_layer);\n  preroll_context()->surface_needs_readback = false;\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n}\n\nTEST_F(ColorFilterLayerTest, CacheChild) {\n  auto layer_filter = DlSrgbToLinearGammaColorFilter::instance;\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  auto other_transform = SkMatrix::Scale(1.0, 2.0);\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  SkPaint paint = SkPaint();\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer = std::make_shared<ColorFilterLayer>(layer_filter);\n  layer->Add(mock_layer);\n\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n  SkCanvas other_canvas;\n  other_canvas.setMatrix(other_transform);\n\n  use_mock_raster_cache();\n  const auto* cacheable_color_filter_item = layer->raster_cache_item();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(cacheable_color_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_color_filter_item->GetId().has_value());\n\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n  EXPECT_EQ(cacheable_color_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kChildren);\n  EXPECT_EQ(\n      cacheable_color_filter_item->GetId().value(),\n      RasterCacheKeyID(RasterCacheKeyID::LayerChildrenIds(layer.get()).value(),\n                       RasterCacheKeyType::kLayerChildren));\n  EXPECT_FALSE(raster_cache()->Draw(\n      cacheable_color_filter_item->GetId().value(), other_canvas, &paint));\n  EXPECT_TRUE(raster_cache()->Draw(cacheable_color_filter_item->GetId().value(),\n                                   cache_canvas, &paint));\n}\n\nTEST_F(ColorFilterLayerTest, CacheChildren) {\n  auto layer_filter = DlSrgbToLinearGammaColorFilter::instance;\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  auto other_transform = SkMatrix::Scale(1.0, 2.0);\n  const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  const SkPath child_path2 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2);\n  auto layer = std::make_shared<ColorFilterLayer>(layer_filter);\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n  SkPaint paint = SkPaint();\n\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n  SkCanvas other_canvas;\n  other_canvas.setMatrix(other_transform);\n\n  use_mock_raster_cache();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  const auto* cacheable_color_filter_item = layer->raster_cache_item();\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n\n  EXPECT_EQ(cacheable_color_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_color_filter_item->GetId().has_value());\n\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n  EXPECT_EQ(cacheable_color_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kChildren);\n  EXPECT_EQ(\n      cacheable_color_filter_item->GetId().value(),\n      RasterCacheKeyID(RasterCacheKeyID::LayerChildrenIds(layer.get()).value(),\n                       RasterCacheKeyType::kLayerChildren));\n  EXPECT_FALSE(raster_cache()->Draw(\n      cacheable_color_filter_item->GetId().value(), other_canvas, &paint));\n  EXPECT_TRUE(raster_cache()->Draw(cacheable_color_filter_item->GetId().value(),\n                                   cache_canvas, &paint));\n}\n\nTEST_F(ColorFilterLayerTest, CacheColorFilterLayerSelf) {\n  auto layer_filter = DlSrgbToLinearGammaColorFilter::instance;\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  auto other_transform = SkMatrix::Scale(1.0, 2.0);\n  const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  const SkPath child_path2 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2);\n  auto layer = std::make_shared<ColorFilterLayer>(layer_filter);\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n  SkPaint paint = SkPaint();\n\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n  SkCanvas other_canvas;\n  other_canvas.setMatrix(other_transform);\n\n  use_mock_raster_cache();\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  const auto* cacheable_color_filter_item = layer->raster_cache_item();\n\n  // frame 1.\n  layer->Preroll(preroll_context());\n  layer->Paint(paint_context());\n  // frame 2.\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  // ColorFilterLayer default cache children.\n  EXPECT_EQ(cacheable_color_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kChildren);\n  EXPECT_TRUE(raster_cache()->Draw(cacheable_color_filter_item->GetId().value(),\n                                   cache_canvas, &paint));\n  EXPECT_FALSE(raster_cache()->Draw(\n      cacheable_color_filter_item->GetId().value(), other_canvas, &paint));\n  layer->Paint(paint_context());\n\n  // frame 3.\n  layer->Preroll(preroll_context());\n  layer->Paint(paint_context());\n\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  // frame1,2 cache the ColorFilterLayer's children layer, frame3 cache the\n  // ColorFilterLayer\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)2);\n\n  // ColorFilterLayer default cache itself.\n  EXPECT_EQ(cacheable_color_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kCurrent);\n  EXPECT_EQ(cacheable_color_filter_item->GetId(),\n            RasterCacheKeyID(layer->unique_id(), RasterCacheKeyType::kLayer));\n  EXPECT_TRUE(raster_cache()->Draw(cacheable_color_filter_item->GetId().value(),\n                                   cache_canvas, &paint));\n  EXPECT_FALSE(raster_cache()->Draw(\n      cacheable_color_filter_item->GetId().value(), other_canvas, &paint));\n}\n\nTEST_F(ColorFilterLayerTest, OpacityInheritance) {\n  // clang-format off\n  float matrix[20] = {\n    0, 1, 0, 0, 0,\n    0, 0, 1, 0, 0,\n    1, 0, 0, 0, 0,\n    0, 0, 0, 1, 0,\n  };\n  // clang-format on\n  auto layer_filter = DlMatrixColorFilter(matrix);\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto color_filter_layer = std::make_shared<ColorFilterLayer>(\n      std::make_shared<DlMatrixColorFilter>(matrix));\n  color_filter_layer->Add(mock_layer);\n\n  PrerollContext* context = preroll_context();\n  context->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  color_filter_layer->Preroll(preroll_context());\n  // ColorFilterLayer can always inherit opacity whether or not their\n  // children are compatible.\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(color_filter_layer);\n  preroll_context()->state_stack.set_preroll_delegate(skity::Matrix());\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        SkPaint paint;\n        paint.setColorFilter(layer_filter.gr_object());\n        paint.setColor(opacity_alpha << 24);\n        canvas->saveLayer(&child_path.getBounds(), &paint);\n        {\n          SkPaint inner_paint;\n          inner_paint.setColor(0xFF000000);\n          canvas->drawPath(child_path, inner_paint);\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(expected_display_list->approximateOpCount() ==\n              display_list()->approximateOpCount());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/container_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/container_layer.h\"\n\n#include <optional>\n#include <utility>\n\nnamespace clay {\n\nContainerLayer::ContainerLayer()\n    : child_paint_bounds_(skity::Rect::MakeEmpty()) {}\n\nvoid ContainerLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  auto old_container = static_cast<const ContainerLayer*>(old_layer);\n  DiffContext::AutoSubtreeRestore subtree(context);\n  DiffChildren(context, old_container);\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid ContainerLayer::PreservePaintRegion(DiffContext* context) {\n  Layer::PreservePaintRegion(context);\n  for (auto& layer : layers_) {\n    layer->PreservePaintRegion(context);\n  }\n}\n\nvoid ContainerLayer::DiffChildren(DiffContext* context,\n                                  const ContainerLayer* old_layer) {\n  if (context->IsSubtreeDirty()) {\n    for (auto& layer : layers_) {\n      layer->Diff(context, nullptr);\n    }\n    return;\n  }\n  FML_DCHECK(old_layer);\n\n  const auto& prev_layers = old_layer->layers_;\n\n  // first mismatched element\n  int new_children_top = 0;\n  int old_children_top = 0;\n\n  // last mismatched element\n  int new_children_bottom = layers_.size() - 1;\n  int old_children_bottom = prev_layers.size() - 1;\n\n  while ((old_children_top <= old_children_bottom) &&\n         (new_children_top <= new_children_bottom)) {\n    if (!layers_[new_children_top]->IsReplacing(\n            context, prev_layers[old_children_top].get())) {\n      break;\n    }\n    ++new_children_top;\n    ++old_children_top;\n  }\n\n  while ((old_children_top <= old_children_bottom) &&\n         (new_children_top <= new_children_bottom)) {\n    if (!layers_[new_children_bottom]->IsReplacing(\n            context, prev_layers[old_children_bottom].get())) {\n      break;\n    }\n    --new_children_bottom;\n    --old_children_bottom;\n  }\n\n  // old layers that don't match\n  for (int i = old_children_top; i <= old_children_bottom; ++i) {\n    auto layer = prev_layers[i];\n    context->AddDamage(context->GetOldLayerPaintRegion(layer.get()));\n  }\n\n  for (int i = 0; i < static_cast<int>(layers_.size()); ++i) {\n    if (i < new_children_top || i > new_children_bottom) {\n      int i_prev =\n          i < new_children_top ? i : prev_layers.size() - (layers_.size() - i);\n      auto layer = layers_[i];\n      auto prev_layer = prev_layers[i_prev];\n      auto paint_region = context->GetOldLayerPaintRegion(prev_layer.get());\n      if (layer == prev_layer && !paint_region.has_readback() &&\n          !paint_region.has_drawable_image() && !paint_region.has_animation() &&\n          !paint_region.has_deferred_image()) {\n        // for retained layers, stop processing the subtree and add existing\n        // region; We know current subtree is not dirty (every ancestor up to\n        // here matches) so the retained subtree will render identically to\n        // previous frame; We can only do this if there is no readback in the\n        // subtree. Layers that do readback must be able to register readback\n        // inside Diff\n        context->AddExistingPaintRegion(paint_region);\n\n        // While we don't need to diff retained layers, we still need to\n        // associate their paint region with current layer tree so that we can\n        // retrieve it in next frame diff\n        layer->PreservePaintRegion(context);\n      } else {\n        layer->Diff(context, prev_layer.get());\n      }\n    } else {\n      DiffContext::AutoSubtreeRestore subtree(context);\n      context->MarkSubtreeDirty();\n      auto layer = layers_[i];\n      layer->Diff(context, nullptr);\n    }\n  }\n}\n\nvoid ContainerLayer::Add(std::shared_ptr<Layer> layer) {\n  layers_.emplace_back(std::move(layer));\n}\n\nvoid ContainerLayer::Preroll(PrerollContext* context) {\n  skity::Rect child_paint_bounds = skity::Rect::MakeEmpty();\n  PrerollChildren(context, &child_paint_bounds);\n  set_paint_bounds(child_paint_bounds);\n}\n\nvoid ContainerLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n\n  PaintChildren(context);\n}\n\nstatic bool safe_intersection_test(const skity::Rect* rect1,\n                                   const skity::Rect& rect2) {\n  if (rect1->IsEmpty() || rect2.IsEmpty()) {\n    return false;\n  }\n  return skity::Rect::Intersect(*rect1, rect2);\n}\n\nvoid ContainerLayer::PrerollChildren(PrerollContext* context,\n                                     skity::Rect* child_paint_bounds) {\n  // Platform views have no children, so context->has_platform_view should\n  // always be false.\n  FML_DCHECK(!context->has_platform_view);\n  FML_DCHECK(!context->has_drawable_image_layer);\n  FML_DCHECK(!context->has_punch_hole_layer);\n\n  bool child_has_platform_view = false;\n  bool child_has_drawable_image_layer = false;\n  bool child_has_punch_hole_layer = false;\n  bool child_has_deferred_image = false;\n  bool child_has_running_picture_animation = false;\n  bool child_has_running_transform_animation = false;\n  bool all_renderable_state_flags = LayerStateStack::kCallerCanApplyAnything;\n\n  for (auto& layer : layers_) {\n    // Reset context->has_platform_view and context->has_drawable_image_layer to\n    // false so that layers aren't treated as if they have a platform view or\n    // drawable image layer based on one being previously found in a sibling\n    // tree.\n    context->has_platform_view = false;\n    context->has_drawable_image_layer = false;\n    context->has_punch_hole_layer = false;\n    context->has_deferred_image = false;\n    context->has_running_picture_animation = false;\n    context->has_running_transform_animation = false;\n\n    // Initialize the renderable state flags to false to force the layer to\n    // opt-in to applying state attributes during its |Preroll|\n    context->renderable_state_flags = 0;\n\n    layer->Preroll(context);\n\n    all_renderable_state_flags &= context->renderable_state_flags;\n    if (safe_intersection_test(child_paint_bounds, layer->paint_bounds())) {\n      // This will allow inheritance by a linear sequence of non-overlapping\n      // children, but will fail with a grid or other arbitrary 2D layout.\n      // See https://github.com/flutter/flutter/issues/93899\n      all_renderable_state_flags = false;\n    }\n    child_paint_bounds->Join(layer->paint_bounds());\n\n    child_has_platform_view |= context->has_platform_view;\n    child_has_drawable_image_layer |= context->has_drawable_image_layer;\n    child_has_punch_hole_layer |= context->has_punch_hole_layer;\n    child_has_deferred_image |= context->has_deferred_image;\n    child_has_running_picture_animation |=\n        context->has_running_picture_animation;\n    child_has_running_transform_animation |=\n        context->has_running_transform_animation;\n  }\n\n  context->has_platform_view = child_has_platform_view;\n  context->has_drawable_image_layer = child_has_drawable_image_layer;\n  context->has_punch_hole_layer = child_has_punch_hole_layer;\n  context->has_deferred_image = child_has_deferred_image;\n  context->has_running_picture_animation = child_has_running_picture_animation;\n  context->has_running_transform_animation =\n      child_has_running_transform_animation;\n  context->renderable_state_flags = all_renderable_state_flags;\n\n  set_subtree_has_platform_view(child_has_platform_view);\n  set_subtree_has_punch_hole(child_has_punch_hole_layer);\n  set_children_renderable_state_flags(all_renderable_state_flags);\n  set_child_paint_bounds(*child_paint_bounds);\n}\n\nvoid ContainerLayer::PaintChildren(PaintContext& context) const {\n  // We can no longer call FML_DCHECK here on the needs_painting(context)\n  // condition as that test is only valid for the PaintContext that\n  // is initially handed to a layer's Paint() method. By the time the\n  // layer calls PaintChildren(), though, it may have modified the\n  // PaintContext so the test doesn't work in this \"context\".\n\n  // Apply any outstanding state that the children cannot individually\n  // and collectively handle.\n  auto restore = context.state_stack.applyState(\n      child_paint_bounds(), children_renderable_state_flags());\n\n  // Intentionally not tracing here as there should be no self-time\n  // and the trace event on this common function has a small overhead.\n  for (auto& layer : layers_) {\n    if (layer->needs_painting(context)) {\n      if (context.only_draw_punch_hole && !layer->subtree_has_punch_hole()) {\n        continue;\n      }\n      layer->Paint(context);\n    }\n  }\n}\n\n#ifndef NDEBUG\nvoid ContainerLayer::DebugDumpTree(int depth) const {\n  Layer::DebugDumpTree(depth);\n  for (auto& layer : layers_) {\n    layer->DebugDumpTree(depth + 1);\n  }\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/container_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_CONTAINER_LAYER_H_\n#define CLAY_FLOW_LAYERS_CONTAINER_LAYER_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/flow/layers/layer.h\"\n\nnamespace clay {\n\nclass ContainerLayer : public Layer {\n public:\n  ContainerLayer();\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n  void PreservePaintRegion(DiffContext* context) override;\n\n  virtual void Add(std::shared_ptr<Layer> layer);\n\n  void Preroll(PrerollContext* context) override;\n  void Paint(PaintContext& context) const override;\n  const std::vector<std::shared_ptr<Layer>>& layers() const { return layers_; }\n\n  virtual void DiffChildren(DiffContext* context,\n                            const ContainerLayer* old_layer);\n\n  void PaintChildren(PaintContext& context) const override;\n\n  const ContainerLayer* as_container_layer() const override { return this; }\n\n  const skity::Rect& child_paint_bounds() const { return child_paint_bounds_; }\n  void set_child_paint_bounds(const skity::Rect& bounds) {\n    child_paint_bounds_ = bounds;\n  }\n\n  int children_renderable_state_flags() const {\n    return children_renderable_state_flags_;\n  }\n  void set_children_renderable_state_flags(int flags) {\n    children_renderable_state_flags_ = flags;\n  }\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"ContainerLayer\"; }\n  void DebugDumpTree(int depth) const override;\n#endif\n\n  bool IsContainer() const override { return true; }\n\n protected:\n  void PrerollChildren(PrerollContext* context,\n                       skity::Rect* child_paint_bounds);\n\n private:\n  std::vector<std::shared_ptr<Layer>> layers_;\n  skity::Rect child_paint_bounds_;\n  int children_renderable_state_flags_ = 0;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ContainerLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_CONTAINER_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/container_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/testing/diff_context_test.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing ContainerLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(ContainerLayerTest, LayerWithParentHasPlatformView) {\n  auto layer = std::make_shared<ContainerLayer>();\n\n  preroll_context()->has_platform_view = true;\n  EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context()),\n                            \"!context->has_platform_view\");\n}\n\nTEST_F(ContainerLayerTest, LayerWithParentHasDrawableImageLayer) {\n  auto layer = std::make_shared<ContainerLayer>();\n\n  preroll_context()->has_drawable_image_layer = true;\n  EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context()),\n                            \"!context->has_drawable_image_layer\");\n}\n\nTEST_F(ContainerLayerTest, LayerWithParentHasPunchHoleLayer) {\n  auto layer = std::make_shared<ContainerLayer>();\n  preroll_context()->has_punch_hole_layer = true;\n  EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context()),\n                            \"!context->has_punch_hole_layer\");\n}\n\nTEST_F(ContainerLayerTest, PaintingEmptyLayerDies) {\n  auto layer = std::make_shared<ContainerLayer>();\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_EQ(layer->child_paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ContainerLayerTest, PaintBeforePrerollDies) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer);\n\n  EXPECT_EQ(layer->paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_EQ(layer->child_paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(ContainerLayerTest, LayerWithParentHasDrawableImageLayerNeedsResetFlag) {\n  SkPath child_path1;\n  child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPath child_path2;\n  child_path2.addRect(8.0f, 2.0f, 16.5f, 14.5f);\n  SkPaint child_paint1(SkColors::kGray);\n  SkPaint child_paint2(SkColors::kGreen);\n\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  mock_layer1->set_fake_has_drawable_image_layer(true);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n\n  auto root = std::make_shared<ContainerLayer>();\n  auto container_layer1 = std::make_shared<ContainerLayer>();\n  auto container_layer2 = std::make_shared<ContainerLayer>();\n  root->Add(container_layer1);\n  root->Add(container_layer2);\n  container_layer1->Add(mock_layer1);\n  container_layer2->Add(mock_layer2);\n\n  EXPECT_EQ(preroll_context()->has_drawable_image_layer, false);\n  root->Preroll(preroll_context());\n  EXPECT_EQ(preroll_context()->has_drawable_image_layer, true);\n  // The flag for holding drawable image layer from parent needs to be clear\n  EXPECT_EQ(mock_layer2->parent_has_drawable_image_layer(), false);\n}\n\nTEST_F(ContainerLayerTest, LayerWithParentHasPunchHoleLayerNeedsResetFlag) {\n  SkPath child_path1;\n  child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPath child_path2;\n  child_path2.addRect(8.0f, 2.0f, 16.5f, 14.5f);\n  SkPaint child_paint1(SkColors::kGray);\n  SkPaint child_paint2(SkColors::kGreen);\n\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  mock_layer1->set_fake_has_punch_hole_layer(true);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n\n  auto root = std::make_shared<ContainerLayer>();\n  auto container_layer1 = std::make_shared<ContainerLayer>();\n  auto container_layer2 = std::make_shared<ContainerLayer>();\n  root->Add(container_layer1);\n  root->Add(container_layer2);\n  container_layer1->Add(mock_layer1);\n  container_layer2->Add(mock_layer2);\n\n  EXPECT_EQ(preroll_context()->has_punch_hole_layer, false);\n  root->Preroll(preroll_context());\n  EXPECT_EQ(preroll_context()->has_punch_hole_layer, true);\n  // The flag for holding punch hole layer from parent needs to be clear\n  EXPECT_EQ(mock_layer2->parent_has_punch_hole_layer(), false);\n}\n\nTEST_F(ContainerLayerTest, Simple) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPaint child_paint(SkColors::kGreen);\n  skity::Matrix initial_transform = skity::Matrix::Translate(-0.5f, -0.5f);\n\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->has_platform_view);\n  EXPECT_EQ(mock_layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer->child_paint_bounds(), layer->paint_bounds());\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer->parent_cull_rect(), kGiantRect);\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({MockCanvas::DrawCall{\n                0, MockCanvas::DrawPathData{child_path, child_paint}}}));\n}\n\nTEST_F(ContainerLayerTest, Multiple) {\n  SkPath child_path1;\n  child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPath child_path2;\n  child_path2.addRect(8.0f, 2.0f, 16.5f, 14.5f);\n  SkPaint child_paint1(SkColors::kGray);\n  SkPaint child_paint2(SkColors::kGreen);\n  skity::Matrix initial_transform = skity::Matrix::Translate(-0.5f, -0.5f);\n\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  mock_layer1->set_fake_has_platform_view(true);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  skity::Rect expected_total_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  expected_total_bounds.Join(\n      clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_TRUE(preroll_context()->has_platform_view);\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), expected_total_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), layer->paint_bounds());\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect);\n  EXPECT_EQ(mock_layer2->parent_cull_rect(),\n            kGiantRect);  // Siblings are independent\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector({MockCanvas::DrawCall{\n                       0, MockCanvas::DrawPathData{child_path1, child_paint1}},\n                   MockCanvas::DrawCall{0, MockCanvas::DrawPathData{\n                                               child_path2, child_paint2}}}));\n}\n\nTEST_F(ContainerLayerTest, MultipleWithEmpty) {\n  SkPath child_path1;\n  child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPaint child_paint1(SkColors::kGray);\n  SkPaint child_paint2(SkColors::kGreen);\n  skity::Matrix initial_transform = skity::Matrix::Translate(-0.5f, -0.5f);\n\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(SkPath(), child_paint2);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->has_platform_view);\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(SkPath().getBounds()));\n  EXPECT_EQ(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(layer->child_paint_bounds(), layer->paint_bounds());\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_FALSE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect);\n  EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect);\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({MockCanvas::DrawCall{\n                0, MockCanvas::DrawPathData{child_path1, child_paint1}}}));\n}\n\nTEST_F(ContainerLayerTest, NeedsSystemComposite) {\n  SkPath child_path1;\n  child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPath child_path2;\n  child_path2.addRect(8.0f, 2.0f, 16.5f, 14.5f);\n  SkPaint child_paint1(SkColors::kGray);\n  SkPaint child_paint2(SkColors::kGreen);\n  skity::Matrix initial_transform = skity::Matrix::Translate(-0.5f, -0.5f);\n\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  mock_layer1->set_fake_has_platform_view(false);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  skity::Rect expected_total_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  expected_total_bounds.Join(\n      clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->has_platform_view);\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), expected_total_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), layer->paint_bounds());\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect);\n  EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect);\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector({MockCanvas::DrawCall{\n                       0, MockCanvas::DrawPathData{child_path1, child_paint1}},\n                   MockCanvas::DrawCall{0, MockCanvas::DrawPathData{\n                                               child_path2, child_paint2}}}));\n}\n\nTEST_F(ContainerLayerTest, RasterCacheTest) {\n  // LTRB\n  const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path2 = SkPath().addRect(21.0f, 6.0f, 25.5f, 21.5f);\n  const SkPath child_path3 = SkPath().addRect(26.0f, 6.0f, 30.5f, 21.5f);\n  const SkPaint child_paint1(SkColors::kGray);\n  const SkPaint child_paint2(SkColors::kGreen);\n  const SkPaint paint;\n  auto cacheable_container_layer1 =\n      MockCacheableContainerLayer::CacheLayerOrChildren();\n  auto cacheable_container_layer2 =\n      MockCacheableContainerLayer::CacheLayerOnly();\n  auto cacheable_container_layer11 =\n      MockCacheableContainerLayer::CacheLayerOrChildren();\n\n  auto cacheable_layer111 =\n      std::make_shared<MockCacheableLayer>(child_path3, paint);\n  // if the frame had rendered 2 frames, we will cache the cacheable_layer21\n  // layer\n  auto cacheable_layer21 =\n      std::make_shared<MockCacheableLayer>(child_path1, paint, 2);\n  //                                     layer\n  //                                       |\n  //                 ______________________ ______________________\n  //                 |                     |                      |\n  //  cacheable_container_layer1      mock_layer2     cacheable_container_layer2\n  //                 |                                            |\n  // cacheable_container_layer11                         cacheable_layer21\n  //                 |\n  //        cacheable_layer111\n\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(SkPath(), child_paint2);\n  auto mock_layer3 = std::make_shared<MockLayer>(child_path2, paint);\n\n  cacheable_container_layer1->Add(mock_layer1);\n  cacheable_container_layer1->Add(mock_layer3);\n\n  cacheable_container_layer1->Add(cacheable_container_layer11);\n  cacheable_container_layer11->Add(cacheable_layer111);\n\n  cacheable_container_layer2->Add(cacheable_layer21);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(cacheable_container_layer1);\n  layer->Add(mock_layer2);\n  layer->Add(cacheable_container_layer2);\n\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(SkMatrix::I());\n\n  // Initial Preroll for check the layer paint bounds\n  layer->Preroll(preroll_context());\n\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            skity::Rect::MakeLTRB(5.f, 6.f, 20.5f, 21.5f));\n  EXPECT_EQ(mock_layer3->paint_bounds(),\n            skity::Rect::MakeLTRB(21.0f, 6.0f, 25.5f, 21.5f));\n  EXPECT_EQ(cacheable_layer111->paint_bounds(),\n            skity::Rect::MakeLTRB(26.0f, 6.0f, 30.5f, 21.5f));\n  EXPECT_EQ(cacheable_container_layer1->paint_bounds(),\n            skity::Rect::MakeLTRB(5.f, 6.f, 30.5f, 21.5f));\n\n  // the preroll context's raster cache is nullptr\n  EXPECT_EQ(preroll_context()->raster_cached_entries->size(),\n            static_cast<size_t>(0));\n  {\n    // frame1\n    use_mock_raster_cache();\n    preroll_context()->raster_cache->BeginFrame();\n    layer->Preroll(preroll_context());\n    preroll_context()->raster_cache->EvictUnusedCacheEntries();\n    // Cache the cacheable entries\n    LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries),\n                                &paint_context());\n\n    EXPECT_EQ(preroll_context()->raster_cached_entries->size(),\n              static_cast<size_t>(5));\n\n    // cacheable_container_layer1 will cache his children\n    EXPECT_EQ(cacheable_container_layer1->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kChildren);\n    EXPECT_TRUE(raster_cache()->HasEntry(\n        cacheable_container_layer1->raster_cache_item()->GetId().value(),\n        skity::Matrix()));\n\n    EXPECT_EQ(cacheable_container_layer11->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kChildren);\n    EXPECT_TRUE(raster_cache()->HasEntry(\n        cacheable_container_layer11->raster_cache_item()->GetId().value(),\n        skity::Matrix()));\n    EXPECT_FALSE(raster_cache()->Draw(\n        cacheable_container_layer11->raster_cache_item()->GetId().value(),\n        cache_canvas, &paint));\n\n    // The cacheable_layer111 should be cached when rended 3 frames\n    EXPECT_EQ(cacheable_layer111->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kNone);\n    // render count < 2 don't cache it\n    EXPECT_EQ(cacheable_container_layer2->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kNone);\n    preroll_context()->raster_cache->EndFrame();\n  }\n\n  {\n    // frame2\n    // new frame the layer tree will create new PrerollContext, so in here we\n    // clear the cached_entries\n    preroll_context()->raster_cached_entries->clear();\n    preroll_context()->raster_cache->BeginFrame();\n    layer->Preroll(preroll_context());\n    preroll_context()->raster_cache->EvictUnusedCacheEntries();\n\n    // Cache the cacheable entries\n    LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries),\n                                &paint_context());\n    EXPECT_EQ(preroll_context()->raster_cached_entries->size(),\n              static_cast<size_t>(5));\n    EXPECT_EQ(cacheable_container_layer1->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kChildren);\n    EXPECT_TRUE(raster_cache()->HasEntry(\n        cacheable_container_layer1->raster_cache_item()->GetId().value(),\n        skity::Matrix()));\n\n    EXPECT_EQ(cacheable_container_layer11->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kChildren);\n    EXPECT_TRUE(raster_cache()->HasEntry(\n        cacheable_container_layer11->raster_cache_item()->GetId().value(),\n        skity::Matrix()));\n    EXPECT_FALSE(raster_cache()->Draw(\n        cacheable_container_layer11->raster_cache_item()->GetId().value(),\n        cache_canvas, &paint));\n\n    EXPECT_EQ(cacheable_container_layer2->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kNone);\n\n    // render count == 2 cache it\n    EXPECT_EQ(cacheable_layer21->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kCurrent);\n    EXPECT_TRUE(raster_cache()->HasEntry(\n        cacheable_layer21->raster_cache_item()->GetId().value(),\n        skity::Matrix()));\n    EXPECT_TRUE(raster_cache()->Draw(\n        cacheable_layer21->raster_cache_item()->GetId().value(), cache_canvas,\n        &paint));\n    preroll_context()->raster_cache->EndFrame();\n  }\n\n  {\n    // frame3\n    // new frame the layer tree will create new PrerollContext, so in here we\n    // clear the cached_entries\n    preroll_context()->raster_cache->BeginFrame();\n    preroll_context()->raster_cached_entries->clear();\n    layer->Preroll(preroll_context());\n    preroll_context()->raster_cache->EvictUnusedCacheEntries();\n    // Cache the cacheable entries\n    LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries),\n                                &paint_context());\n    EXPECT_EQ(preroll_context()->raster_cached_entries->size(),\n              static_cast<size_t>(5));\n    EXPECT_EQ(cacheable_container_layer1->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kCurrent);\n    EXPECT_TRUE(raster_cache()->HasEntry(\n        cacheable_container_layer1->raster_cache_item()->GetId().value(),\n        skity::Matrix()));\n    EXPECT_TRUE(raster_cache()->HasEntry(\n        cacheable_container_layer11->raster_cache_item()->GetId().value(),\n        skity::Matrix()));\n    EXPECT_FALSE(raster_cache()->Draw(\n        cacheable_container_layer11->raster_cache_item()->GetId().value(),\n        cache_canvas, &paint));\n    // The 3td frame, we will cache the cacheable_layer111, but his ancestor has\n    // been cached, so cacheable_layer111 Draw is false\n    EXPECT_TRUE(raster_cache()->HasEntry(\n        cacheable_layer111->raster_cache_item()->GetId().value(),\n        skity::Matrix()));\n    EXPECT_FALSE(raster_cache()->Draw(\n        cacheable_layer111->raster_cache_item()->GetId().value(), cache_canvas,\n        &paint));\n\n    // The third frame, we will cache the cacheable_container_layer2\n    EXPECT_EQ(cacheable_container_layer2->raster_cache_item()->cache_state(),\n              RasterCacheItem::CacheState::kCurrent);\n\n    EXPECT_TRUE(raster_cache()->HasEntry(\n        cacheable_layer21->raster_cache_item()->GetId().value(),\n        skity::Matrix()));\n    preroll_context()->raster_cache->EndFrame();\n  }\n\n  {\n    preroll_context()->raster_cache->BeginFrame();\n    // frame4\n    preroll_context()->raster_cached_entries->clear();\n    layer->Preroll(preroll_context());\n    preroll_context()->raster_cache->EvictUnusedCacheEntries();\n    LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries),\n                                &paint_context());\n    preroll_context()->raster_cache->EndFrame();\n\n    // frame5\n    preroll_context()->raster_cache->BeginFrame();\n    preroll_context()->raster_cached_entries->clear();\n    layer->Preroll(preroll_context());\n    LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries),\n                                &paint_context());\n    preroll_context()->raster_cache->EndFrame();\n\n    // frame6\n    preroll_context()->raster_cache->BeginFrame();\n    preroll_context()->raster_cached_entries->clear();\n    layer->Preroll(preroll_context());\n    LayerTree::TryToRasterCache(*(preroll_context()->raster_cached_entries),\n                                &paint_context());\n    preroll_context()->raster_cache->EndFrame();\n  }\n}\n\nTEST_F(ContainerLayerTest, OpacityInheritance) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto container1 = std::make_shared<ContainerLayer>();\n  container1->Add(mock1);\n\n  // ContainerLayer will pass through compatibility\n  PrerollContext* context = preroll_context();\n  container1->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path2 = SkPath().addRect({40, 40, 50, 50});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  container1->Add(mock2);\n\n  // ContainerLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  container1->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path3 = SkPath().addRect({20, 20, 40, 40});\n  auto mock3 = MockLayer::MakeOpacityCompatible(path3);\n  container1->Add(mock3);\n\n  // ContainerLayer will not pass through compatibility from multiple\n  // overlapping children even if they are individually compatible\n  container1->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, 0);\n\n  auto container2 = std::make_shared<ContainerLayer>();\n  container2->Add(mock1);\n  container2->Add(mock2);\n\n  // Double check first two children are compatible and non-overlapping\n  container2->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path4 = SkPath().addRect({60, 60, 70, 70});\n  auto mock4 = MockLayer::Make(path4);\n  container2->Add(mock4);\n\n  // The third child is non-overlapping, but not compatible so the\n  // ContainerLayer should end up incompatible\n  container2->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, 0);\n}\n\nTEST_F(ContainerLayerTest, CollectionCacheableLayer) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPaint child_paint(SkColors::kGreen);\n  skity::Matrix initial_transform = skity::Matrix::Translate(-0.5f, -0.5f);\n\n  auto mock_layer1 = std::make_shared<MockLayer>(SkPath(), child_paint);\n  auto mock_cacheable_container_layer1 =\n      std::make_shared<MockCacheableContainerLayer>();\n  auto mock_container_layer = std::make_shared<ContainerLayer>();\n  auto mock_cacheable_layer =\n      std::make_shared<MockCacheableLayer>(child_path, child_paint);\n  mock_cacheable_container_layer1->Add(mock_cacheable_layer);\n\n  // ContainerLayer\n  //   |- MockLayer\n  //   |- MockCacheableContainerLayer\n  //        |- MockCacheableLayer\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_cacheable_container_layer1);\n  layer->Add(mock_layer1);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  // raster cache is null, so no entry\n  ASSERT_EQ(preroll_context()->raster_cached_entries->size(),\n            static_cast<const size_t>(0));\n\n  use_mock_raster_cache();\n  // preroll_context()->raster_cache = raster_cache();\n  layer->Preroll(preroll_context());\n  ASSERT_EQ(preroll_context()->raster_cached_entries->size(),\n            static_cast<const size_t>(2));\n}\n\nusing ContainerLayerDiffTest = DiffContextTest;\n\n// Insert PictureLayer amongst container layers\nTEST_F(ContainerLayerDiffTest, PictureLayerInsertion) {\n  auto pic1 = CreatePicture(skity::Rect::MakeLTRB(0, 0, 50, 50), 1);\n  auto pic2 = CreatePicture(skity::Rect::MakeLTRB(100, 0, 150, 50), 1);\n  auto pic3 = CreatePicture(skity::Rect::MakeLTRB(200, 0, 250, 50), 1);\n\n  MockLayerTree t1;\n\n  auto t1_c1 = CreateContainerLayer(CreatePictureLayer(pic1));\n  t1.root()->Add(t1_c1);\n\n  auto t1_c2 = CreateContainerLayer(CreatePictureLayer(pic2));\n  t1.root()->Add(t1_c2);\n\n  auto damage = DiffLayerTree(t1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 150, 50));\n\n  // Add in the middle\n\n  MockLayerTree t2;\n  auto t2_c1 = CreateContainerLayer(CreatePictureLayer(pic1));\n  t2_c1->AssignOldLayer(t1_c1.get());\n  t2.root()->Add(t2_c1);\n\n  t2.root()->Add(CreatePictureLayer(pic3));\n\n  auto t2_c2 = CreateContainerLayer(CreatePictureLayer(pic2));\n  t2_c2->AssignOldLayer(t1_c2.get());\n  t2.root()->Add(t2_c2);\n\n  damage = DiffLayerTree(t2, t1);\n  FML_LOG(ERROR) << \"frame_damage: \" << damage.frame_damage.Left() << \", \"\n                 << damage.frame_damage.Top() << \", \"\n                 << damage.frame_damage.Right() << \", \"\n                 << damage.frame_damage.Bottom();\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(200, 0, 250, 50));\n\n  // Add in the beginning\n\n  t2 = MockLayerTree();\n  t2.root()->Add(CreatePictureLayer(pic3));\n  t2.root()->Add(t2_c1);\n  t2.root()->Add(t2_c2);\n  damage = DiffLayerTree(t2, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(200, 0, 250, 50));\n\n  // Add at the end\n\n  t2 = MockLayerTree();\n  t2.root()->Add(t2_c1);\n  t2.root()->Add(t2_c2);\n  t2.root()->Add(CreatePictureLayer(pic3));\n  damage = DiffLayerTree(t2, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(200, 0, 250, 50));\n}\n\n// Insert picture layer amongst other picture layers\nTEST_F(ContainerLayerDiffTest, PictureInsertion) {\n  auto pic1 = CreatePicture(skity::Rect::MakeLTRB(0, 0, 50, 50), 1);\n  auto pic2 = CreatePicture(skity::Rect::MakeLTRB(100, 0, 150, 50), 1);\n  auto pic3 = CreatePicture(skity::Rect::MakeLTRB(200, 0, 250, 50), 1);\n\n  MockLayerTree t1;\n  t1.root()->Add(CreatePictureLayer(pic1));\n  t1.root()->Add(CreatePictureLayer(pic2));\n\n  auto damage = DiffLayerTree(t1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 150, 50));\n\n  MockLayerTree t2;\n  t2.root()->Add(CreatePictureLayer(pic3));\n  t2.root()->Add(CreatePictureLayer(pic1));\n  t2.root()->Add(CreatePictureLayer(pic2));\n\n  damage = DiffLayerTree(t2, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(200, 0, 250, 50));\n\n  MockLayerTree t3;\n  t3.root()->Add(CreatePictureLayer(pic1));\n  t3.root()->Add(CreatePictureLayer(pic3));\n  t3.root()->Add(CreatePictureLayer(pic2));\n\n  damage = DiffLayerTree(t3, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(200, 0, 250, 50));\n\n  MockLayerTree t4;\n  t4.root()->Add(CreatePictureLayer(pic1));\n  t4.root()->Add(CreatePictureLayer(pic2));\n  t4.root()->Add(CreatePictureLayer(pic3));\n\n  damage = DiffLayerTree(t4, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(200, 0, 250, 50));\n}\n\nTEST_F(ContainerLayerDiffTest, LayerDeletion) {\n  auto path1 = SkPath().addRect(SkRect::MakeLTRB(0, 0, 50, 50));\n  auto path2 = SkPath().addRect(SkRect::MakeLTRB(100, 0, 150, 50));\n  auto path3 = SkPath().addRect(SkRect::MakeLTRB(200, 0, 250, 50));\n\n  auto c1 = CreateContainerLayer(std::make_shared<MockLayer>(path1));\n  auto c2 = CreateContainerLayer(std::make_shared<MockLayer>(path2));\n  auto c3 = CreateContainerLayer(std::make_shared<MockLayer>(path3));\n\n  MockLayerTree t1;\n  t1.root()->Add(c1);\n  t1.root()->Add(c2);\n  t1.root()->Add(c3);\n\n  auto damage = DiffLayerTree(t1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 250, 50));\n\n  MockLayerTree t2;\n  t2.root()->Add(c2);\n  t2.root()->Add(c3);\n\n  damage = DiffLayerTree(t2, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 50, 50));\n\n  MockLayerTree t3;\n  t3.root()->Add(c1);\n  t3.root()->Add(c3);\n\n  damage = DiffLayerTree(t3, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(100, 0, 150, 50));\n\n  MockLayerTree t4;\n  t4.root()->Add(c1);\n  t4.root()->Add(c2);\n\n  damage = DiffLayerTree(t4, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(200, 0, 250, 50));\n\n  MockLayerTree t5;\n  t5.root()->Add(c1);\n\n  damage = DiffLayerTree(t5, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(100, 0, 250, 50));\n\n  MockLayerTree t6;\n  t6.root()->Add(c2);\n\n  damage = DiffLayerTree(t6, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 250, 50));\n\n  MockLayerTree t7;\n  t7.root()->Add(c3);\n\n  damage = DiffLayerTree(t7, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 150, 50));\n}\n\nTEST_F(ContainerLayerDiffTest, ReplaceLayer) {\n  auto path1 = SkPath().addRect(SkRect::MakeLTRB(0, 0, 50, 50));\n  auto path2 = SkPath().addRect(SkRect::MakeLTRB(100, 0, 150, 50));\n  auto path3 = SkPath().addRect(SkRect::MakeLTRB(200, 0, 250, 50));\n\n  auto path1a = SkPath().addRect(SkRect::MakeLTRB(0, 100, 50, 150));\n  auto path2a = SkPath().addRect(SkRect::MakeLTRB(100, 100, 150, 150));\n  auto path3a = SkPath().addRect(SkRect::MakeLTRB(200, 100, 250, 150));\n\n  auto c1 = CreateContainerLayer(std::make_shared<MockLayer>(path1));\n  auto c2 = CreateContainerLayer(std::make_shared<MockLayer>(path2));\n  auto c3 = CreateContainerLayer(std::make_shared<MockLayer>(path3));\n\n  MockLayerTree t1;\n  t1.root()->Add(c1);\n  t1.root()->Add(c2);\n  t1.root()->Add(c3);\n\n  auto damage = DiffLayerTree(t1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 250, 50));\n\n  MockLayerTree t2;\n  t2.root()->Add(c1);\n  t2.root()->Add(c2);\n  t2.root()->Add(c3);\n\n  damage = DiffLayerTree(t2, t1);\n  EXPECT_TRUE(damage.frame_damage.IsEmpty());\n\n  MockLayerTree t3;\n  t3.root()->Add(CreateContainerLayer({std::make_shared<MockLayer>(path1a)}));\n  t3.root()->Add(c2);\n  t3.root()->Add(c3);\n\n  damage = DiffLayerTree(t3, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 50, 150));\n\n  MockLayerTree t4;\n  t4.root()->Add(c1);\n  t4.root()->Add(CreateContainerLayer(std::make_shared<MockLayer>(path2a)));\n  t4.root()->Add(c3);\n\n  damage = DiffLayerTree(t4, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(100, 0, 150, 150));\n\n  MockLayerTree t5;\n  t5.root()->Add(c1);\n  t5.root()->Add(c2);\n  t5.root()->Add(CreateContainerLayer(std::make_shared<MockLayer>(path3a)));\n\n  damage = DiffLayerTree(t5, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(200, 0, 250, 150));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/drawable_image_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/drawable_image_layer.h\"\n\n#include <memory>\n\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/gfx/pixel_helper.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nnamespace {\nclass PixelConverter : public clay::PixelHelper<clay::kPixelTypeClay> {\n public:\n  explicit PixelConverter(float device_pixel_ratio)\n      : device_pixel_ratio_(device_pixel_ratio) {}\n  float DevicePixelRatio() const override { return device_pixel_ratio_; }\n\n private:\n  float device_pixel_ratio_;\n};\n}  // namespace\n\nDrawableImageLayer::DrawableImageLayer(const skity::Vec2& offset,\n                                       const skity::Vec2& size,\n                                       int64_t image_id, bool freeze,\n                                       clay::ImageSampling sampling,\n                                       DrawableImage::FitMode fit_mode)\n    : offset_(offset),\n      size_(size),\n      image_id_(image_id),\n      freeze_(freeze),\n      sampling_(sampling),\n      fit_mode_(fit_mode) {}\n\nvoid DrawableImageLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(old_layer);\n    auto prev = old_layer->as_drawable_image_layer();\n    // TODO(knopp) It would be nice to be able to determine that a texture is\n    // dirty\n    context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(prev));\n  }\n\n  // Make sure DiffContext knows there is a DrawableImageLayer in this subtree.\n  // This prevents ContainerLayer from skipping DrawableImageLayer diffing when\n  // DrawableImageLayer is inside retained layer.\n  // See ContainerLayer::DiffChildren\n  // https://github.com/flutter/flutter/issues/92925\n  context->MarkSubtreeHasDrawableImageLayer();\n  context->AddLayerBounds(\n      skity::Rect::MakeXYWH(offset_.x, offset_.y, size_.x, size_.y));\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid DrawableImageLayer::Preroll(PrerollContext* context) {\n  set_paint_bounds(\n      skity::Rect::MakeXYWH(offset_.x, offset_.y, size_.x, size_.y));\n  context->has_drawable_image_layer = true;\n  context->renderable_state_flags = LayerStateStack::kCallerCanApplyOpacity;\n}\n\nvoid DrawableImageLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n\n  std::shared_ptr<DrawableImage> image =\n      context.drawable_image_registry\n          ? context.drawable_image_registry->GetDrawableImage(image_id_)\n          : nullptr;\n  if (!image) {\n    TRACE_EVENT_INSTANT(\"clay\", \"null image\");\n    return;\n  }\n\n  clay::GrPaint sk_paint;\n  clay::Paint clay_paint;\n  DrawableImage::PaintContext ctx{\n      .canvas = context.canvas,\n      .gr_context = context.gr_context,\n      .sk_paint = context.state_stack.fill(sk_paint),\n      .clay_paint = context.state_stack.fill(clay_paint),\n  };\n\n  // Image coordinates are physical pixels. We need to apply scale and convert\n  // the bounds to physical pixels.\n  auto converter = PixelConverter(context.frame_device_pixel_ratio);\n  auto scale =\n      converter.GetPixelRatio<clay::kPixelTypePhysical, clay::kPixelTypeClay>();\n\n  clay::GrAutoCanvasRestore auto_restore(context.canvas, true);\n  CANVAS_SCALE(context.canvas, scale, scale);\n\n  auto physical_bounds = skity::Rect(\n      converter.ConvertTo<clay::kPixelTypePhysical>(paint_bounds().Left()),\n      converter.ConvertTo<clay::kPixelTypePhysical>(paint_bounds().Top()),\n      converter.ConvertTo<clay::kPixelTypePhysical>(paint_bounds().Right()),\n      converter.ConvertTo<clay::kPixelTypePhysical>(paint_bounds().Bottom()));\n  image->Paint(ctx, physical_bounds, freeze_, ToSk(sampling_), fit_mode_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/drawable_image_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_DRAWABLE_IMAGE_LAYER_H_\n#define CLAY_FLOW_LAYERS_DRAWABLE_IMAGE_LAYER_H_\n\n#include <string>\n\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/flow/layers/layer.h\"\n\nnamespace clay {\n\nclass DrawableImageLayer : public Layer {\n public:\n  DrawableImageLayer(\n      const skity::Vec2& offset, const skity::Vec2& size, int64_t image_id,\n      bool freeze, clay::ImageSampling sampling,\n      DrawableImage::FitMode fit_mode = DrawableImage::FitMode::kScaleToFill);\n\n  bool IsReplacing(DiffContext* context, const Layer* layer) const override {\n    return layer->as_drawable_image_layer() != nullptr;\n  }\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  const DrawableImageLayer* as_drawable_image_layer() const override {\n    return this;\n  }\n\n  void Preroll(PrerollContext* context) override;\n  void Paint(PaintContext& context) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"DrawableImageLayer\"; }\n#endif\n\n private:\n  skity::Vec2 offset_;\n  skity::Vec2 size_;\n  int64_t image_id_;\n  bool freeze_;\n  clay::ImageSampling sampling_;\n  DrawableImage::FitMode fit_mode_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(DrawableImageLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_DRAWABLE_IMAGE_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/drawable_image_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/drawable_image_layer.h\"\n#include \"clay/flow/testing/diff_context_test.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_drawable_image.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/testing/mock_canvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing DrawableImageLayerTest = LayerTest;\n\nTEST_F(DrawableImageLayerTest, InvalidDrawableImage) {\n  const skity::Vec2 layer_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 layer_size = skity::Vec2(8.0f, 8.0f);\n  auto layer = std::make_shared<DrawableImageLayer>(\n      layer_offset, layer_size, 0, false, DlImageSampling::kNearestNeighbor);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(),\n            (skity::Rect::MakeSize({layer_size.x, layer_size.y})\n                 .MakeOffset(layer_offset.x, layer_offset.y)));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(), std::vector<MockCanvas::DrawCall>());\n}\n\n#ifndef NDEBUG\nTEST_F(DrawableImageLayerTest, PaintingEmptyLayerDies) {\n  const skity::Vec2 layer_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 layer_size = skity::Vec2(0.0f, 0.0f);\n  auto mock_image = std::make_shared<MockDrawableImage>();\n  const int64_t image_id = mock_image->Id();\n  auto layer = std::make_shared<DrawableImageLayer>(\n      layer_offset, layer_size, image_id, false,\n      DlImageSampling::kNearestNeighbor);\n\n  // Ensure the image is located by the Layer.\n  preroll_context()->drawable_image_registry->RegisterDrawableImage(mock_image);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(DrawableImageLayerTest, PaintBeforePrerollDies) {\n  const skity::Vec2 layer_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 layer_size = skity::Vec2(8.0f, 8.0f);\n  auto mock_image = std::make_shared<MockDrawableImage>();\n  const int64_t image_id = mock_image->Id();\n  auto layer = std::make_shared<DrawableImageLayer>(\n      layer_offset, layer_size, image_id, false, DlImageSampling::kLinear);\n\n  // Ensure the image is located by the Layer.\n  preroll_context()->drawable_image_registry->RegisterDrawableImage(mock_image);\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(DrawableImageLayerTest, PaintingWithLinearSampling) {\n  const skity::Vec2 layer_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 layer_size = skity::Vec2(8.0f, 8.0f);\n  auto mock_image = std::make_shared<MockDrawableImage>();\n  const int64_t image_id = mock_image->Id();\n  auto layer = std::make_shared<DrawableImageLayer>(\n      layer_offset, layer_size, image_id, false, DlImageSampling::kLinear);\n\n  // Ensure the image is located by the Layer.\n  preroll_context()->drawable_image_registry->RegisterDrawableImage(mock_image);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(),\n            (skity::Rect::MakeSize({layer_size.x, layer_size.y})\n                 .MakeOffset(layer_offset.x, layer_offset.y)));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_image->paint_calls(),\n            std::vector({MockDrawableImage::PaintCall{\n                mock_canvas(), layer->paint_bounds(), false, nullptr,\n                SkSamplingOptions(SkFilterMode::kLinear), nullptr,\n                DrawableImage::FitMode::kScaleToFill}}));\n  EXPECT_EQ(mock_canvas().draw_calls(), std::vector<MockCanvas::DrawCall>());\n}\n\nusing DrawableImageLayerDiffTest = DiffContextTest;\n\nTEST_F(DrawableImageLayerDiffTest, DrawableImageInRetainedLayer) {\n  MockLayerTree tree1;\n  auto container = std::make_shared<ContainerLayer>();\n  tree1.root()->Add(container);\n  auto layer = std::make_shared<DrawableImageLayer>(\n      skity::Vec2(0, 0), skity::Vec2(100, 100), 0, false,\n      DlImageSampling::kLinear);\n  container->Add(layer);\n\n  MockLayerTree tree2;\n  tree2.root()->Add(container);  // retained layer\n\n  auto damage = DiffLayerTree(tree1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 100, 100));\n\n  damage = DiffLayerTree(tree2, tree1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 100, 100));\n}\n\nTEST_F(DrawableImageLayerTest, OpacityInheritance) {\n  const skity::Vec2 layer_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 layer_size = skity::Vec2(8.0f, 8.0f);\n  auto mock_image = std::make_shared<MockDrawableImage>();\n  const int64_t image_id = mock_image->Id();\n  auto layer = std::make_shared<DrawableImageLayer>(\n      layer_offset, layer_size, image_id, false, DlImageSampling::kLinear);\n\n  // Ensure the image is located by the Layer.\n  preroll_context()->drawable_image_registry->RegisterDrawableImage(mock_image);\n\n  // The drawable image layer always reports opacity compatibility.\n  PrerollContext* context = preroll_context();\n  context->drawable_image_registry->RegisterDrawableImage(mock_image);\n  layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  // MockDrawableImage has no actual textur to render into the\n  // PaintContext canvas so we have no way to verify its\n  // rendering.\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/external_view_layer.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/external_view_layer.h\"\n\n#include <memory>\n#include <utility>\n\nnamespace clay {\n\nExternalViewLayer::ExternalViewLayer(const clay::ElementId& element_id,\n                                     const skity::Vec2& size)\n    : element_id_(element_id), size_(size) {}\nvoid ExternalViewLayer::Preroll(PrerollContext* context) {\n  if (context->compositor_state == nullptr) {\n    FML_DLOG(ERROR) << \"Trying to embed a platform view but the PrerollContext \"\n                       \"does not support embedding\";\n    return;\n  }\n  ContainerLayer::Preroll(context);\n  context->has_platform_view = true;\n  set_subtree_has_platform_view(true);\n  MutatorsStack mutators;\n  context->state_stack.fill(&mutators);\n  std::unique_ptr<OverlayViewParams> params =\n      std::make_unique<OverlayViewParams>(\n          skity::Rect::MakeWH(size_.x, size_.y));\n  context->compositor_state->PrerollOverlayView(element_id_.view_id(),\n                                                std::move(params));\n}\n\nvoid ExternalViewLayer::Paint(clay::PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n  if (!context.compositor_state) {\n    return;\n  }\n  auto previous_canvas = context.canvas;\n  clay::GrCanvas* canvas =\n      context.compositor_state->CompositeOverlayView(element_id_.view_id());\n  context.canvas = canvas;\n  context.state_stack.set_delegate(context.canvas);\n  PaintChildren(context);\n  // restore\n  context.canvas = previous_canvas;\n  context.state_stack.set_delegate(previous_canvas);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/external_view_layer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_EXTERNAL_VIEW_LAYER_H_\n#define CLAY_FLOW_LAYERS_EXTERNAL_VIEW_LAYER_H_\n\n#include <string>\n\n#include \"clay/common/element_id.h\"\n#include \"clay/flow/layers/container_layer.h\"\n\nnamespace clay {\nclass ExternalViewLayer : public ContainerLayer {\n public:\n  ExternalViewLayer(const clay::ElementId& element_id, const skity::Vec2& size);\n\n  void Preroll(PrerollContext* context) override;\n  void Paint(PaintContext& context) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"ExternalViewLayer\"; }\n#endif\n\n private:\n  clay::ElementId element_id_;\n  skity::Vec2 size_;\n};\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_EXTERNAL_VIEW_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/image_filter_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/image_filter_layer.h\"\n\n#include <utility>\n\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/gfx/comparable.h\"\n\nnamespace clay {\n\nImageFilterLayer::ImageFilterLayer(\n    std::shared_ptr<const clay::ImageFilter> filter, const skity::Vec2& offset)\n    : CacheableContainerLayer(\n          RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer),\n      offset_(offset),\n      filter_(std::move(filter)),\n      transformed_filter_(nullptr) {}\n\nvoid ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  auto* prev = static_cast<const ImageFilterLayer*>(old_layer);\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(prev);\n    if (NotEquals(filter_, prev->filter_) || offset_ != prev->offset_) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n\n  context->PushTransform(skity::Matrix::Translate(offset_.x, offset_.y));\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n  if (context->has_raster_cache()) {\n    context->SetTransform(\n        RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));\n  }\n#endif\n\n  if (filter_) {\n    auto filter = filter_->makeWithLocalMatrix(context->GetTransform());\n    if (filter) {\n      // This transform will be applied to every child rect in the subtree\n      context->PushFilterBoundsAdjustment([filter](skity::Rect rect) {\n        skity::Rect filter_out_bounds;\n        filter->map_device_bounds(rect, skity::Matrix(), filter_out_bounds);\n        return filter_out_bounds;\n      });\n    }\n  }\n  DiffChildren(context, prev);\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid ImageFilterLayer::Preroll(PrerollContext* context) {\n  auto mutator = context->state_stack.save();\n  mutator.translate(offset_);\n\n  Layer::AutoPrerollSaveLayerState save =\n      Layer::AutoPrerollSaveLayerState::Create(context);\n\n  AutoCache cache = AutoCache(layer_raster_cache_item_.get(), context,\n                              context->state_stack.transform_4x4());\n\n  skity::Rect child_bounds = skity::Rect::MakeEmpty();\n\n  PrerollChildren(context, &child_bounds);\n\n  if (!filter_) {\n    set_paint_bounds(child_bounds);\n    return;\n  }\n\n  // Our saveLayer would apply any outstanding opacity or any outstanding\n  // color filter after it applies our image filter. So we can apply either\n  // of those attributes with our saveLayer.\n  context->renderable_state_flags =\n      (LayerStateStack::kCallerCanApplyOpacity |\n       LayerStateStack::kCallerCanApplyColorFilter);\n\n  child_bounds.RoundOut();\n  filter_->map_device_bounds(child_bounds, skity::Matrix(), child_bounds);\n  child_bounds.Offset(offset_.x, offset_.y);\n\n  set_paint_bounds(child_bounds);\n\n  // CacheChildren only when the transformed_filter_ doesn't equal null.\n  // So in here we reset the LayerRasterCacheItem cache state.\n  layer_raster_cache_item_->MarkNotCacheChildren();\n\n  // TODO(lijing.0511): LocalMatrixImageFilter is not supported in skity, so we\n  // do not cache children now. Once it is supported, will remove this macro.\n  // @see DlLocalMatrixImageFilter::gr_object\n#ifndef ENABLE_SKITY\n  transformed_filter_ =\n      filter_->makeWithLocalMatrix(context->state_stack.transform_4x4());\n  if (transformed_filter_) {\n    layer_raster_cache_item_->MarkCacheChildren();\n  }\n#endif  // ENABLE_SKITY\n}\n\nvoid ImageFilterLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n  if (subtree_has_punch_hole()) {\n    auto mutator = context.state_stack.save();\n    mutator.translate(offset_);\n    context.only_draw_punch_hole = true;\n    PaintChildren(context);\n    context.only_draw_punch_hole = false;\n  }\n\n  auto mutator = context.state_stack.save();\n  mutator.translate(offset_);\n\n  if (context.raster_cache) {\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n    // Always apply the integral transform in the presence of a raster cache\n    // whether or not we will draw from the cache\n    mutator.integralTransform();\n#endif\n\n    // Try drawing the layer cache item from the cache before applying the\n    // image filter if it was cached with the filter applied.\n    if (!layer_raster_cache_item_->IsCacheChildren()) {\n      clay::GrPaint paint;\n      if (layer_raster_cache_item_->Draw(context,\n                                         context.state_stack.fill(paint))) {\n        return;\n      }\n    }\n  }\n\n  if (context.raster_cache && layer_raster_cache_item_->IsCacheChildren()) {\n    // If we render the children from cache then we need the special\n    // transformed version of the filter so we must process it into the\n    // cache paint object manually.\n    FML_DCHECK(transformed_filter_ != nullptr);\n    clay::GrPaint paint;\n    context.state_stack.fill(paint);\n    PAINT_SET_IMAGE_FILTER(paint, transformed_filter_->gr_object());\n    if (layer_raster_cache_item_->Draw(context, &paint)) {\n      return;\n    }\n  }\n\n  // Now apply the image filter and then try rendering the children.\n  mutator.applyImageFilter(child_paint_bounds(), filter_);\n\n  PaintChildren(context);\n\n#ifndef NDEBUG\n  if (context.enable_raster_cache_tag && layer_raster_cache_item_ &&\n      layer_raster_cache_item_->has_been_cached()) {\n    // Generated raster cache, but not used.\n    DrawRasterCacheTag(context.canvas, paint_bounds().Width() / 2,\n                       paint_bounds().Height() / 2, 0);\n  }\n#endif  // NDEBUG\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/image_filter_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_\n#define CLAY_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/flow/layers/cacheable_layer.h\"\n#include \"clay/flow/layers/layer.h\"\n\nnamespace clay {\n\nclass ImageFilterLayer : public CacheableContainerLayer {\n public:\n  explicit ImageFilterLayer(std::shared_ptr<const clay::ImageFilter> filter,\n                            const skity::Vec2& offset = skity::Vec2(0, 0));\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  void Preroll(PrerollContext* context) override;\n\n  void Paint(PaintContext& context) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"ImageFilterLayer\"; }\n#endif\n\n private:\n  skity::Vec2 offset_;\n  std::shared_ptr<const clay::ImageFilter> filter_;\n  std::shared_ptr<const clay::ImageFilter> transformed_filter_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ImageFilterLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/image_filter_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/image_filter_layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/layers/transform_layer.h\"\n#include \"clay/flow/testing/diff_context_test.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkPaint.h\"\n#include \"third_party/skia/include/core/SkPath.h\"\n#include \"third_party/skia/include/effects/SkImageFilters.h\"\nnamespace clay {\nnamespace testing {\n\nusing ImageFilterLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(ImageFilterLayerTest, PaintingEmptyLayerDies) {\n  auto layer = std::make_shared<ImageFilterLayer>(nullptr);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ImageFilterLayerTest, PaintBeforePrerollDies) {\n  const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path = SkPath().addRect(child_bounds);\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer = std::make_shared<ImageFilterLayer>(nullptr);\n  layer->Add(mock_layer);\n\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(ImageFilterLayerTest, EmptyFilter) {\n  const SkMatrix initial_transform = SkMatrix::Translate(0.5f, 1.0f);\n  const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path = SkPath().addRect(child_bounds);\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ImageFilterLayer>(nullptr);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_bounds));\n  EXPECT_EQ(layer->child_paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_bounds));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(),\n            clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n\n  SkPaint filter_paint;\n  filter_paint.setImageFilter(nullptr);\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({\n                MockCanvas::DrawCall{\n                    0, MockCanvas::DrawPathData{child_path, child_paint}},\n            }));\n}\n\nTEST_F(ImageFilterLayerTest, SimpleFilter) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kMipmapLinear);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ImageFilterLayer>(dl_image_filter);\n  layer->Add(mock_layer);\n\n  const skity::Rect child_rounded_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 21.0f, 22.0f);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), child_rounded_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    SkPaint paint;\n    paint.setImageFilter(dl_image_filter->gr_object());\n    auto bounds_tmp = clay::ConvertSkityRectToSkRect(child_bounds);\n    canvas->saveLayer(&bounds_tmp, &paint);\n    {\n      SkPaint new_paint;\n      new_paint.setColor(SK_ColorYELLOW);\n      canvas->drawPath(child_path, new_paint);\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ImageFilterLayerTest, SimpleFilterWithOffset) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect initial_cull_rect = skity::Rect::MakeLTRB(0, 0, 100, 100);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  const skity::Vec2 layer_offset = skity::Vec2(5.5, 6.5);\n  auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kMipmapLinear);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer =\n      std::make_shared<ImageFilterLayer>(dl_image_filter, layer_offset);\n  layer->Add(mock_layer);\n\n  skity::Matrix child_matrix = initial_transform;\n  child_matrix.PreTranslate(layer_offset.x, layer_offset.y);\n  const skity::Rect child_rounded_bounds =\n      skity::Rect::MakeLTRB(10.5f, 12.5f, 26.5f, 28.5f);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_cull_rect,\n                                                      initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), child_rounded_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), child_matrix);\n  EXPECT_EQ(preroll_context()->state_stack.device_cull_rect(),\n            initial_cull_rect);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(layer_offset.x, layer_offset.y);\n      SkPaint sk_paint;\n      sk_paint.setImageFilter(dl_image_filter->gr_object());\n      auto bounds_tmp = clay::ConvertSkityRectToSkRect(child_bounds);\n      canvas->saveLayer(&bounds_tmp, &sk_paint);\n      {\n        SkPaint new_paint;\n        new_paint.setColor(SK_ColorYELLOW);\n        canvas->drawPath(child_path, new_paint);\n      }\n      canvas->restore();\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ImageFilterLayerTest, SimpleFilterBounds) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  const skity::Matrix filter_transform = skity::Matrix::Scale(2.0, 2.0);\n\n  auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(\n      filter_transform, DlImageSampling::kMipmapLinear);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ImageFilterLayer>(dl_image_filter);\n  layer->Add(mock_layer);\n\n  const skity::Rect filter_bounds =\n      skity::Rect::MakeLTRB(10.0f, 12.0f, 42.0f, 44.0f);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), filter_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    SkPaint sk_paint;\n    sk_paint.setImageFilter(dl_image_filter->gr_object());\n    auto bounds_tmp = clay::ConvertSkityRectToSkRect(child_bounds);\n    canvas->saveLayer(&bounds_tmp, &sk_paint);\n    {\n      SkPaint new_paint;\n      new_paint.setColor(SK_ColorYELLOW);\n      canvas->drawPath(child_path, new_paint);\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ImageFilterLayerTest, MultipleChildren) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);\n  const SkPath child_path1 =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath child_path2 = SkPath().addRect(\n      clay::ConvertSkityRectToSkRect(child_bounds.MakeOffset(3.0f, 0.0f)));\n  const SkPaint child_paint1 = SkPaint(SkColors::kYellow);\n  const SkPaint child_paint2 = SkPaint(SkColors::kCyan);\n  auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kMipmapLinear);\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer = std::make_shared<ImageFilterLayer>(dl_image_filter);\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  skity::Rect children_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  children_bounds.Join(clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  skity::Rect children_rounded_bounds = children_bounds;\n  children_rounded_bounds.RoundOut();\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), children_rounded_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), children_bounds);\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    SkPaint sk_paint;\n    sk_paint.setImageFilter(dl_image_filter->gr_object());\n    auto bounds_tmp = clay::ConvertSkityRectToSkRect(children_bounds);\n    canvas->saveLayer(&bounds_tmp, &sk_paint);\n    {\n      SkPaint new_paint;\n      new_paint.setColor(SK_ColorYELLOW);\n      canvas->drawPath(child_path1, new_paint);\n    }\n    {\n      SkPaint new_paint;\n      new_paint.setColor(SK_ColorCYAN);\n      canvas->drawPath(child_path2, new_paint);\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ImageFilterLayerTest, Nested) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f);\n  const SkPath child_path1 =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath child_path2 = SkPath().addRect(\n      clay::ConvertSkityRectToSkRect(child_bounds.MakeOffset(3.0f, 0.0f)));\n  const SkPaint child_paint1 = SkPaint(SkColors::kYellow);\n  const SkPaint child_paint2 = SkPaint(SkColors::kCyan);\n  auto dl_image_filter1 = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kMipmapLinear);\n  auto dl_image_filter2 = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kMipmapLinear);\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer1 = std::make_shared<ImageFilterLayer>(dl_image_filter1);\n  auto layer2 = std::make_shared<ImageFilterLayer>(dl_image_filter2);\n  layer2->Add(mock_layer2);\n  layer1->Add(mock_layer1);\n  layer1->Add(layer2);\n\n  skity::Rect children_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  skity::Rect child_bounds_tmp =\n      clay::ConvertSkRectToSkityRect(child_path2.getBounds());\n  child_bounds_tmp.RoundOut();\n  children_bounds.Join(child_bounds_tmp);\n  skity::Rect children_rounded_bounds = children_bounds;\n  children_rounded_bounds.RoundOut();\n  skity::Rect mock_layer2_rounded_bounds =\n      clay::ConvertSkRectToSkityRect(child_path2.getBounds());\n  mock_layer2_rounded_bounds.RoundOut();\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer1->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer1->paint_bounds(), children_rounded_bounds);\n  EXPECT_EQ(layer1->child_paint_bounds(), children_bounds);\n  EXPECT_EQ(layer2->paint_bounds(), mock_layer2_rounded_bounds);\n  EXPECT_EQ(layer2->child_paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(layer2->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    SkPaint sk_paint;\n    sk_paint.setImageFilter(dl_image_filter1->gr_object());\n    auto bounds_tmp = clay::ConvertSkityRectToSkRect(children_bounds);\n    canvas->saveLayer(&bounds_tmp, &sk_paint);\n    {\n      SkPaint new_paint;\n      new_paint.setColor(SK_ColorYELLOW);\n      canvas->drawPath(child_path1, new_paint);\n    }\n    {\n      SkPaint child_paint;\n      child_paint.setImageFilter(dl_image_filter2->gr_object());\n      canvas->saveLayer(&child_path2.getBounds(), &child_paint);\n      {\n        SkPaint new_paint;\n        new_paint.setColor(SK_ColorCYAN);\n        canvas->drawPath(child_path2, new_paint);\n      }\n      canvas->restore();\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  layer1->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(ImageFilterLayerTest, Readback) {\n  auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kLinear);\n\n  // ImageFilterLayer does not read from surface\n  auto layer = std::make_shared<ImageFilterLayer>(dl_image_filter);\n  preroll_context()->surface_needs_readback = false;\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n\n  // ImageFilterLayer blocks child with readback\n  auto mock_layer = std::make_shared<MockLayer>(SkPath(), SkPaint());\n  mock_layer->set_fake_reads_surface(true);\n  layer->Add(mock_layer);\n  preroll_context()->surface_needs_readback = false;\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n}\n\nTEST_F(ImageFilterLayerTest, CacheChild) {\n  auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kMipmapLinear);\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  auto other_transform = SkMatrix::Scale(1.0, 2.0);\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer = std::make_shared<ImageFilterLayer>(dl_image_filter);\n  layer->Add(mock_layer);\n\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n  SkCanvas other_canvas;\n  other_canvas.setMatrix(other_transform);\n  SkPaint paint = SkPaint();\n\n  use_mock_raster_cache();\n  const auto* cacheable_image_filter_item = layer->raster_cache_item();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  // ImageFilterLayer default cache itself.\n  EXPECT_EQ(cacheable_image_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_image_filter_item->Draw(paint_context(), &paint));\n\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n  // The layer_cache_item's strategy is Children, mean we will must cache\n  // his children\n  EXPECT_EQ(cacheable_image_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kChildren);\n  EXPECT_TRUE(raster_cache()->Draw(cacheable_image_filter_item->GetId().value(),\n                                   cache_canvas, &paint));\n  EXPECT_FALSE(raster_cache()->Draw(\n      cacheable_image_filter_item->GetId().value(), other_canvas, &paint));\n}\n\nTEST_F(ImageFilterLayerTest, CacheChildren) {\n  auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kMipmapLinear);\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  auto other_transform = SkMatrix::Scale(1.0, 2.0);\n  SkPaint paint = SkPaint();\n  const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  const SkPath child_path2 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2);\n  auto layer = std::make_shared<ImageFilterLayer>(dl_image_filter);\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n  SkCanvas other_canvas;\n  other_canvas.setMatrix(other_transform);\n\n  use_mock_raster_cache();\n\n  const auto* cacheable_image_filter_item = layer->raster_cache_item();\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n\n  // ImageFilterLayer default cache itself.\n  EXPECT_EQ(cacheable_image_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_image_filter_item->Draw(paint_context(), &paint));\n\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n\n  // The layer_cache_item's strategy is Children, mean we will must cache his\n  // children\n  EXPECT_EQ(cacheable_image_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kChildren);\n  EXPECT_TRUE(raster_cache()->Draw(cacheable_image_filter_item->GetId().value(),\n                                   cache_canvas, &paint));\n  EXPECT_FALSE(raster_cache()->Draw(\n      cacheable_image_filter_item->GetId().value(), other_canvas, &paint));\n}\n\nTEST_F(ImageFilterLayerTest, CacheImageFilterLayerSelf) {\n  auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kMipmapLinear);\n\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  auto other_transform = SkMatrix::Scale(1.0, 2.0);\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer = std::make_shared<ImageFilterLayer>(dl_image_filter);\n  layer->Add(mock_layer);\n\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n  SkCanvas other_canvas;\n  other_canvas.setMatrix(other_transform);\n  SkPaint paint = SkPaint();\n\n  use_mock_raster_cache();\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  const auto* cacheable_image_filter_item = layer->raster_cache_item();\n  // frame 1.\n  layer->Preroll(preroll_context());\n  layer->Paint(paint_context());\n  // frame 2.\n  layer->Preroll(preroll_context());\n  layer->Paint(paint_context());\n  // frame 3.\n  layer->Preroll(preroll_context());\n  layer->Paint(paint_context());\n\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  // frame1,2 cache the ImageFilter's children layer, frame3 cache the\n  // ImageFilterLayer\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)2);\n\n  // ImageFilterLayer default cache itself.\n  EXPECT_EQ(cacheable_image_filter_item->cache_state(),\n            RasterCacheItem::CacheState::kCurrent);\n  EXPECT_EQ(cacheable_image_filter_item->GetId(),\n            RasterCacheKeyID(layer->unique_id(), RasterCacheKeyType::kLayer));\n  EXPECT_TRUE(raster_cache()->Draw(cacheable_image_filter_item->GetId().value(),\n                                   cache_canvas, &paint));\n  EXPECT_FALSE(raster_cache()->Draw(\n      cacheable_image_filter_item->GetId().value(), other_canvas, &paint));\n}\n\nTEST_F(ImageFilterLayerTest, OpacityInheritance) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto dl_image_filter = std::make_shared<DlMatrixImageFilter>(\n      skity::Matrix(), DlImageSampling::kMipmapLinear);\n\n  // The mock_layer child will not be compatible with opacity\n  auto mock_layer = MockLayer::Make(child_path, child_paint);\n  auto image_filter_layer = std::make_shared<ImageFilterLayer>(dl_image_filter);\n  image_filter_layer->Add(mock_layer);\n\n  PrerollContext* context = preroll_context();\n  context->state_stack.set_preroll_delegate(initial_transform);\n  image_filter_layer->Preroll(preroll_context());\n  // ImageFilterLayers can always inherit opacity whether or not their\n  // children are compatible.\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity |\n                LayerStateStack::kCallerCanApplyColorFilter);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(image_filter_layer);\n  context->state_stack.set_preroll_delegate(skity::Matrix());\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        SkPaint sk_paint;\n        sk_paint.setColor(opacity_alpha << 24);\n        sk_paint.setImageFilter(dl_image_filter->gr_object());\n        canvas->saveLayer(&child_path.getBounds(), &sk_paint);\n        {\n          SkPaint new_paint;\n          new_paint.setColor(child_paint.getColor());\n          canvas->drawPath(child_path, new_paint);\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(expected_display_list->approximateOpCount() ==\n              display_list()->approximateOpCount());\n}\n\nusing ImageFilterLayerDiffTest = DiffContextTest;\n\nTEST_F(ImageFilterLayerDiffTest, ImageFilterLayer) {\n  auto dl_blur_filter =\n      std::make_shared<DlBlurImageFilter>(10, 10, DlTileMode::kClamp);\n  {\n    // tests later assume 30px paint area, fail early if that's not the case\n    skity::Rect input_bounds;\n    dl_blur_filter->get_input_device_bounds(skity::Rect::MakeWH(10, 10),\n                                            skity::Matrix(), input_bounds);\n    EXPECT_EQ(input_bounds, skity::Rect::MakeLTRB(-30, -30, 40, 40));\n  }\n\n  MockLayerTree l1;\n  auto filter_layer = std::make_shared<ImageFilterLayer>(dl_blur_filter);\n  auto path = SkPath().addRect(SkRect::MakeLTRB(100, 100, 110, 110));\n  filter_layer->Add(std::make_shared<MockLayer>(path));\n  l1.root()->Add(filter_layer);\n\n  auto damage = DiffLayerTree(l1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(70, 70, 140, 140));\n\n  MockLayerTree l2;\n  auto scale = std::make_shared<TransformLayer>(skity::Matrix::Scale(2.0, 2.0));\n  scale->Add(filter_layer);\n  l2.root()->Add(scale);\n\n  damage = DiffLayerTree(l2, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(140, 140, 280, 280));\n\n  MockLayerTree l3;\n  l3.root()->Add(scale);\n\n  // path outside of ImageFilterLayer\n  auto path1 = SkPath().addRect(SkRect::MakeLTRB(130, 130, 140, 140));\n  l3.root()->Add(std::make_shared<MockLayer>(path1));\n  damage = DiffLayerTree(l3, l2);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(130, 130, 140, 140));\n\n  // path intersecting ImageFilterLayer, shouldn't trigger entire\n  // ImageFilterLayer repaint\n  MockLayerTree l4;\n  l4.root()->Add(scale);\n  auto path2 = SkPath().addRect(SkRect::MakeLTRB(130, 130, 141, 141));\n  l4.root()->Add(std::make_shared<MockLayer>(path2));\n  damage = DiffLayerTree(l4, l3);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(130, 130, 141, 141));\n}\n\nTEST_F(ImageFilterLayerDiffTest, ImageFilterLayerInflatestChildSize) {\n  auto dl_blur_filter =\n      std::make_shared<DlBlurImageFilter>(10, 10, DlTileMode::kClamp);\n\n  {\n    // tests later assume 30px paint area, fail early if that's not the case\n    skity::Rect input_bounds;\n    dl_blur_filter->get_input_device_bounds(skity::Rect::MakeWH(10, 10),\n                                            skity::Matrix(), input_bounds);\n    EXPECT_EQ(input_bounds, skity::Rect::MakeLTRB(-30, -30, 40, 40));\n  }\n\n  MockLayerTree l1;\n\n  // Use nested filter layers to check if both contribute to child bounds\n  auto filter_layer_1_1 = std::make_shared<ImageFilterLayer>(dl_blur_filter);\n  auto filter_layer_1_2 = std::make_shared<ImageFilterLayer>(dl_blur_filter);\n  filter_layer_1_1->Add(filter_layer_1_2);\n  auto path = SkPath().addRect(SkRect::MakeLTRB(100, 100, 110, 110));\n  filter_layer_1_2->Add(\n      std::make_shared<MockLayer>(path, SkPaint(SkColors::kYellow)));\n  l1.root()->Add(filter_layer_1_1);\n\n  // second layer tree with identical filter layers but different child layer\n  MockLayerTree l2;\n  auto filter_layer2_1 = std::make_shared<ImageFilterLayer>(dl_blur_filter);\n  filter_layer2_1->AssignOldLayer(filter_layer_1_1.get());\n  auto filter_layer2_2 = std::make_shared<ImageFilterLayer>(dl_blur_filter);\n  filter_layer2_2->AssignOldLayer(filter_layer_1_2.get());\n  filter_layer2_1->Add(filter_layer2_2);\n  filter_layer2_2->Add(\n      std::make_shared<MockLayer>(path, SkPaint(SkColors::kRed)));\n  l2.root()->Add(filter_layer2_1);\n\n  DiffLayerTree(l1, MockLayerTree());\n  auto damage = DiffLayerTree(l2, l1);\n\n  // ensure that filter properly inflated child size\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(40, 40, 170, 170));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/layer.h\"\n\n#include \"clay/flow/animation/animation_host.h\"\n#include \"clay/flow/animation/animation_mutator.h\"\n#include \"clay/flow/paint_utils.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nLayer::Layer()\n    : paint_bounds_(skity::Rect::MakeEmpty()),\n      unique_id_(NextUniqueID()),\n      original_layer_id_(unique_id_),\n      subtree_has_platform_view_(false),\n      subtree_has_punch_hole_(false) {}\n\nLayer::~Layer() = default;\n\nuint64_t Layer::NextUniqueID() {\n  static std::atomic<uint64_t> next_id(1);\n  uint64_t id;\n  do {\n    id = next_id.fetch_add(1);\n  } while (id == 0);  // 0 is reserved for an invalid id.\n  return id;\n}\n\nvoid Layer::PreservePaintRegion(DiffContext* context) {\n  // retained layer means same instance so 'this' is used to index into both\n  // current and old region\n  context->SetLayerPaintRegion(this, context->GetOldLayerPaintRegion(this));\n}\n\nLayer::AutoPrerollSaveLayerState::AutoPrerollSaveLayerState(\n    PrerollContext* preroll_context, bool save_layer_is_active,\n    bool layer_itself_performs_readback)\n    : preroll_context_(preroll_context),\n      save_layer_is_active_(save_layer_is_active),\n      layer_itself_performs_readback_(layer_itself_performs_readback) {\n  if (save_layer_is_active_) {\n    prev_surface_needs_readback_ = preroll_context_->surface_needs_readback;\n    preroll_context_->surface_needs_readback = false;\n  }\n}\n\nLayer::AutoPrerollSaveLayerState Layer::AutoPrerollSaveLayerState::Create(\n    PrerollContext* preroll_context, bool save_layer_is_active,\n    bool layer_itself_performs_readback) {\n  return Layer::AutoPrerollSaveLayerState(preroll_context, save_layer_is_active,\n                                          layer_itself_performs_readback);\n}\n\nLayer::AutoPrerollSaveLayerState::~AutoPrerollSaveLayerState() {\n  if (save_layer_is_active_) {\n    preroll_context_->surface_needs_readback =\n        (prev_surface_needs_readback_ || layer_itself_performs_readback_);\n  }\n}\n\nbool Layer::HasAnimation() const {\n  return animation_host_ && animation_host_->HasAnimation(unique_id());\n}\n\nbool Layer::HasAnimationRunning() const {\n  return animation_host_ && animation_host_->HasAnimationRunning(unique_id());\n}\n\nconst std::shared_ptr<AnimationMutator>& Layer::GetAnimationMutator() const {\n  return animation_host_->GetAnimationMutator(unique_id());\n}\n\nvoid Layer::SetAnimationHost(\n    const std::shared_ptr<AnimationHost>& animation_host) {\n  animation_host_ = animation_host;\n}\n\n#ifndef NDEBUG\nstd::string Layer::ToString() const {\n  std::stringstream ss;\n  ss << \" unique_id_=\" << unique_id_;\n  if (unique_id_ != original_layer_id_) {\n    ss << \" original_layer_id_=\" << original_layer_id_;\n  }\n  // if (needs_system_composite_) {\n  //   ss << \" needs_system_composite_=\" << needs_system_composite_;\n  // }\n  ss << \" paint_bounds_=(\" << paint_bounds_.Left() << \",\" << paint_bounds_.Top()\n     << \",\" << paint_bounds_.Width() << \",\" << paint_bounds_.Height() << \")\";\n  return ss.str();\n}\n\nvoid Layer::DebugDumpTree(int depth) const {\n  std::string indent;\n  indent.append(2 * depth, ' ');\n  FML_LOG(ERROR) << indent << \"[\" << DebugName() << \"] -\" << ToString();\n}\n#endif\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_LAYER_H_\n#define CLAY_FLOW_LAYERS_LAYER_H_\n\n#include <algorithm>\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <vector>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"build/build_config.h\"\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"clay/flow/diff_context.h\"\n#include \"clay/flow/layer_snapshot_store.h\"\n#include \"clay/flow/layers/layer_state_stack.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/stopwatch.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nnamespace testing {\nclass MockLayer;\n}  // namespace testing\n\nclass AnimationHost;\nclass AnimationMutator;\nclass ContainerLayer;\nclass DiffContext;\nclass PictureLayer;\nclass PerformanceOverlayLayer;\n#ifndef ENABLE_SKITY\nclass PictureLayer;\n#endif  // ENABLE_SKITY\nclass PunchHoleLayer;\nclass DrawableImageLayer;\nclass RasterCacheItem;\n\nstatic constexpr skity::Rect kGiantRect =\n    skity::Rect::MakeLTRB(-1E9F, -1E9F, 1E9F, 1E9F);\n\nenum Clip { none, hardEdge, antiAlias, antiAliasWithSaveLayer };\n\nstruct PrerollContext {\n  RasterCache* raster_cache;\n  clay::GrContext* gr_context;\n\n  CompositorState* compositor_state;\n  LayerStateStack& state_stack;\n#ifndef ENABLE_SKITY\n  SkColorSpace* dst_color_space;\n#endif  // ENABLE_SKITY\n  bool surface_needs_readback;\n\n  // These allow us to paint in the end of subtree Preroll.\n  const Stopwatch& raster_time;\n  const Stopwatch& ui_time;\n  std::shared_ptr<DrawableImageRegistry> drawable_image_registry;\n  const float frame_device_pixel_ratio = 1.0f;\n\n  // These allow us to track properties like elevation, opacity, and the\n  // presence of a platform view during Preroll.\n  bool has_platform_view = false;\n  // These allow us to track properties like elevation, opacity, and the\n  // presence of a drawable image layer during Preroll.\n  bool has_drawable_image_layer = false;\n  // These allow us to track properties like elevation, opacity, and the\n  // presence of a punch hole layer during Preroll.\n  bool has_punch_hole_layer = false;\n  // This flag indicates there are picture animation in subtree.\n  bool has_running_picture_animation = false;\n  // This flag indicates there are transform animations in subtree.\n  bool has_running_transform_animation = false;\n  // This flag will be set to true if one of the parent of the current layer has\n  // running transform animation.\n  bool parent_has_running_transform_animation = false;\n  // This flag will be set to true if one of the parent of the current layer has\n  // a deferred image.\n  bool has_deferred_image = false;\n  // The list of flags that describe which rendering state attributes\n  // (such as opacity, ColorFilter, ImageFilter) a given layer can\n  // render itself without requiring the parent to perform a protective\n  // saveLayer with those attributes.\n  // For containers, the flags will be set to the intersection (logical\n  // and) of all of the state bits that all of the children can render\n  // or to 0 if some of the children overlap and, as such, cannot apply\n  // those attributes individually and separately.\n  int renderable_state_flags = 0;\n\n  std::vector<RasterCacheItem*>* raster_cached_entries;\n\n  std::function<void()> request_new_frame;\n};\n\nstruct PaintContext {\n  // When splitting the scene into multiple canvases (e.g when embedding\n  // a platform view on iOS) during the paint traversal we apply any state\n  // changes which affect children (i.e. saveLayer attributes) to the\n  // state_stack and any local rendering state changes for leaf layers to\n  // the canvas or builder.\n  // When we switch a canvas or builder (when painting a PlatformViewLayer)\n  // the new canvas receives all of the stateful changes from the state_stack\n  // to put it into the exact same state that the outgoing canvas had at the\n  // time it was swapped out.\n  // The state stack lazily applies saveLayer calls to its current canvas,\n  // allowing leaf layers to report that they can handle rendering some of\n  // its state attributes themselves via the |applyState| method.\n  LayerStateStack& state_stack;\n\n  clay::GrCanvas* canvas;\n  clay::GrContext* gr_context;\n#ifndef ENABLE_SKITY\n  SkColorSpace* dst_color_space;\n#endif  // ENABLE_SKITY\n  CompositorState* compositor_state;\n  const Stopwatch& raster_time;\n  const Stopwatch& ui_time;\n  std::shared_ptr<DrawableImageRegistry> drawable_image_registry;\n  const RasterCache* raster_cache;\n  const float frame_device_pixel_ratio = 1.0f;\n\n  // Snapshot store to collect leaf layer snapshots. The store is non-null\n  // only when leaf layer tracing is enabled.\n  LayerSnapshotStore* layer_snapshot_store = nullptr;\n  bool enable_leaf_layer_tracing = false;\n\n  bool only_draw_punch_hole = false;\n\n#if !defined(NDEBUG)\n  // Will add brown solid borders to every PictureLayer.\n  bool enable_debug_borders = false;\n  // Will add accesses count as a tag to every raster cache area.\n  bool enable_raster_cache_tag = false;\n#endif\n};\n\n// Represents a single composited layer. Created on the UI thread but then\n// subsequently used on the Rasterizer thread.\nclass Layer {\n public:\n  // The state attribute flags that represent which attributes a\n  // layer can render if it plans to use a saveLayer call in its\n  // |Paint| method.\n  static constexpr int kSaveLayerRenderFlags =\n      LayerStateStack::kCallerCanApplyOpacity |\n      LayerStateStack::kCallerCanApplyColorFilter |\n      LayerStateStack::kCallerCanApplyImageFilter;\n\n  // The state attribute flags that represent which attributes a\n  // layer can render if it will be rendering its content/children\n  // from a cached representation.\n  static constexpr int kRasterCacheRenderFlags =\n      LayerStateStack::kCallerCanApplyOpacity;\n\n  Layer();\n  virtual ~Layer();\n\n  void AssignOldLayer(Layer* old_layer) {\n    original_layer_id_ = old_layer->original_layer_id_;\n  }\n\n  // Used to establish link between old layer and new layer that replaces it.\n  // If this method returns true, it is assumed that this layer replaces the old\n  // layer in tree and is able to diff with it.\n  virtual bool IsReplacing(DiffContext* context, const Layer* old_layer) const {\n    return original_layer_id_ == old_layer->original_layer_id_;\n  }\n\n  // Performs diff with given layer\n  virtual void Diff(DiffContext* context, const Layer* old_layer) {}\n\n  // Used when diffing retained layer; In case the layer is identical, it\n  // doesn't need to be diffed, but the paint region needs to be stored in diff\n  // context so that it can be used in next frame\n  virtual void PreservePaintRegion(DiffContext* context);\n\n  virtual void Preroll(PrerollContext* context) = 0;\n\n  // Used during Preroll by layers that employ a saveLayer to manage the\n  // PrerollContext settings with values affected by the saveLayer mechanism.\n  // This object must be created before calling Preroll on the children to\n  // set up the state for the children and then restore the state upon\n  // destruction.\n  class AutoPrerollSaveLayerState {\n   public:\n    [[nodiscard]] static AutoPrerollSaveLayerState Create(\n        PrerollContext* preroll_context, bool save_layer_is_active = true,\n        bool layer_itself_performs_readback = false);\n\n    ~AutoPrerollSaveLayerState();\n\n   private:\n    AutoPrerollSaveLayerState(PrerollContext* preroll_context,\n                              bool save_layer_is_active,\n                              bool layer_itself_performs_readback);\n\n    PrerollContext* preroll_context_;\n    bool save_layer_is_active_;\n    bool layer_itself_performs_readback_;\n\n    bool prev_surface_needs_readback_;\n  };\n\n  virtual void Paint(PaintContext& context) const = 0;\n\n  virtual void PaintChildren(PaintContext& context) const { FML_DCHECK(false); }\n\n  bool subtree_has_platform_view() const { return subtree_has_platform_view_; }\n  void set_subtree_has_platform_view(bool value) {\n    subtree_has_platform_view_ = value;\n  }\n\n  bool subtree_has_punch_hole() const { return subtree_has_punch_hole_; }\n  void set_subtree_has_punch_hole(bool value) {\n    subtree_has_punch_hole_ = value;\n  }\n\n  // Returns the paint bounds in the layer's local coordinate system\n  // as determined during Preroll().  The bounds should include any\n  // transform, clip or distortions performed by the layer itself,\n  // but not any similar modifications inherited from its ancestors.\n  const skity::Rect& paint_bounds() const { return paint_bounds_; }\n\n  // This must be set by the time Preroll() returns otherwise the layer will\n  // be assumed to have empty paint bounds (paints no content).\n  // The paint bounds should be independent of the context outside of this\n  // layer as the layer may be painted under different conditions than\n  // the Preroll context. The most common example of this condition is\n  // that we might Preroll the layer with a cull_rect established by a\n  // clip layer above it but then we might be asked to paint anyway if\n  // another layer above us needs to cache its children. During the\n  // paint operation that arises due to the caching, the clip will\n  // be the bounds of the layer needing caching, not the cull_rect\n  // that we saw in the overall Preroll operation.\n  void set_paint_bounds(const skity::Rect& paint_bounds) {\n    paint_bounds_ = paint_bounds;\n  }\n\n  // Determines if the layer has any content.\n  bool is_empty() const { return paint_bounds_.IsEmpty(); }\n\n  // Determines if the Paint() method is necessary based on the properties\n  // of the indicated PaintContext object.\n  bool needs_painting(PaintContext& context) const {\n    if (subtree_has_platform_view_ || subtree_has_punch_hole_) {\n      // Workaround for the iOS embedder. The iOS embedder expects that\n      // if we preroll it, then we will later call its Paint() method.\n      // Now that we preroll all layers without any culling, we may\n      // call its Preroll() without calling its Paint(). For now, we\n      // will not perform paint culling on any subtree that has a\n      // platform view.\n      // See https://github.com/flutter/flutter/issues/81419\n\n      // subtree_has_punch_hole_ will notify if we need to draw\n      // punch hole on the old surface before skia's `SaveLayer`.\n      return true;\n    }\n    return !context.state_stack.painting_is_nop() &&\n           !context.state_stack.content_culled(paint_bounds_);\n  }\n\n  bool HasAnimation() const;\n  bool HasAnimationRunning() const;\n  const std::shared_ptr<AnimationMutator>& GetAnimationMutator() const;\n  void SetAnimationHost(const std::shared_ptr<AnimationHost>& animation_host);\n\n  // Propagated unique_id of the first layer in \"chain\" of replacement layers\n  // that can be diffed.\n  uint64_t original_layer_id() const { return original_layer_id_; }\n\n  uint64_t unique_id() const { return unique_id_; }\n\n  virtual RasterCacheKeyID caching_key_id() const {\n    return RasterCacheKeyID(unique_id_, RasterCacheKeyType::kLayer);\n  }\n  virtual const ContainerLayer* as_container_layer() const { return nullptr; }\n\n  virtual const PictureLayer* as_picture_layer() const { return nullptr; }\n\n  virtual const DrawableImageLayer* as_drawable_image_layer() const {\n    return nullptr;\n  }\n  virtual const PunchHoleLayer* as_punch_hole_layer() const { return nullptr; }\n  virtual const PerformanceOverlayLayer* as_performance_overlay_layer() const {\n    return nullptr;\n  }\n  virtual const testing::MockLayer* as_mock_layer() const { return nullptr; }\n\n  virtual bool IsContainer() const { return false; }\n#ifndef NDEBUG\n  virtual std::string DebugName() const { return \"Layer\"; }\n  virtual std::string ToString() const;\n  virtual void DebugDumpTree(int depth) const;\n#endif\n\n private:\n  skity::Rect paint_bounds_;\n  uint64_t unique_id_;\n  uint64_t original_layer_id_;\n  bool subtree_has_platform_view_;\n  bool subtree_has_punch_hole_;\n  std::shared_ptr<AnimationHost> animation_host_;\n\n  static uint64_t NextUniqueID();\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Layer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/layer_raster_cache_item.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/layer_raster_cache_item.h\"\n\n#include <utility>\n\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/raster_cache_item.h\"\n#include \"clay/flow/raster_cache_util.h\"\n\nnamespace clay {\n\nLayerRasterCacheItem::LayerRasterCacheItem(Layer* layer,\n                                           int layer_cached_threshold,\n                                           bool can_cache_children)\n    : RasterCacheItem(\n          RasterCacheKeyID(layer->unique_id(), RasterCacheKeyType::kLayer),\n          // The layer raster_cache_item's cache state default value is none.\n          CacheState::kNone),\n      layer_(layer),\n      layer_cached_threshold_(layer_cached_threshold),\n      can_cache_children_(can_cache_children) {}\n\nvoid LayerRasterCacheItem::PrerollSetup(PrerollContext* context,\n                                        const skity::Matrix& matrix) {\n  cache_state_ = CacheState::kNone;\n  if (context->raster_cache && context->raster_cached_entries) {\n    context->raster_cached_entries->push_back(this);\n    child_items_ = context->raster_cached_entries->size();\n    matrix_ = matrix;\n  }\n}\n\nstd::unique_ptr<LayerRasterCacheItem> LayerRasterCacheItem::Make(\n    Layer* layer, int layer_cache_threshold, bool can_cache_children) {\n  return std::make_unique<LayerRasterCacheItem>(layer, layer_cache_threshold,\n                                                can_cache_children);\n}\n\nvoid LayerRasterCacheItem::PrerollFinalize(PrerollContext* context,\n                                           const skity::Matrix& matrix) {\n  if (!context->raster_cache || !context->raster_cached_entries) {\n    return;\n  }\n  // We've marked the cache entry that we would like to cache so it stays\n  // alive, but if the following conditions apply then we need to set our\n  // state back to kDoNotCache so that we don't populate the entry later.\n  if (context->has_platform_view || context->has_drawable_image_layer ||\n      context->has_punch_hole_layer || context->has_running_picture_animation ||\n      context->has_running_transform_animation || context->has_deferred_image ||\n      context->state_stack.content_culled(layer_->paint_bounds())) {\n    return;\n  }\n  child_items_ = context->raster_cached_entries->size() - child_items_;\n  if (num_cache_attempts_ >= layer_cached_threshold_) {\n    // the layer can be cached\n    cache_state_ = CacheState::kCurrent;\n    context->raster_cache->MarkSeen(key_id_, matrix_, true);\n  } else {\n    num_cache_attempts_++;\n    // access current layer\n    if (can_cache_children_) {\n      if (!layer_children_id_.has_value()) {\n        auto ids = RasterCacheKeyID::LayerChildrenIds(layer_);\n        if (!ids.has_value()) {\n          return;\n        }\n        layer_children_id_.emplace(std::move(ids.value()),\n                                   RasterCacheKeyType::kLayerChildren);\n      }\n      cache_state_ = CacheState::kChildren;\n      context->raster_cache->MarkSeen(layer_children_id_.value(), matrix_,\n                                      true);\n    }\n  }\n}\n\nstd::optional<RasterCacheKeyID> LayerRasterCacheItem::GetId() const {\n  switch (cache_state_) {\n    case kCurrent:\n      return key_id_;\n    case kChildren:\n      return layer_children_id_;\n    default:\n      return {};\n  }\n}\n\nconst skity::Rect* LayerRasterCacheItem::GetPaintBoundsFromLayer() const {\n  switch (cache_state_) {\n    case CacheState::kCurrent:\n      return &(layer_->paint_bounds());\n    case CacheState::kChildren:\n      FML_DCHECK(layer_->as_container_layer());\n      return &(layer_->as_container_layer()->child_paint_bounds());\n    default:\n      FML_DCHECK(cache_state_ != CacheState::kNone);\n      return nullptr;\n  }\n}\n\nbool Rasterize(RasterCacheItem::CacheState cache_state, Layer* layer,\n               const PaintContext& paint_context, clay::GrCanvas* canvas) {\n  FML_DCHECK(cache_state != RasterCacheItem::CacheState::kNone);\n  LayerStateStack state_stack;\n  state_stack.set_delegate(canvas);\n  state_stack.set_checkerboard_func(\n      paint_context.state_stack.checkerboard_func());\n  PaintContext context = {\n      // clang-format off\n      .state_stack                   = state_stack,\n      .canvas                        = canvas,\n      .gr_context                    = paint_context.gr_context,\n#ifndef ENABLE_SKITY\n      .dst_color_space               = paint_context.dst_color_space,\n#endif  // ENABLE_SKITY\n      .compositor_state              = paint_context.compositor_state,\n      .raster_time                   = paint_context.raster_time,\n      .ui_time                       = paint_context.ui_time,\n      .drawable_image_registry       = paint_context.drawable_image_registry,\n      .raster_cache                  = paint_context.raster_cache,\n      .frame_device_pixel_ratio      = paint_context.frame_device_pixel_ratio,\n      // clang-format on\n  };\n\n  switch (cache_state) {\n    case RasterCacheItem::CacheState::kCurrent:\n      FML_DCHECK(layer->needs_painting(context));\n      layer->Paint(context);\n      break;\n    case RasterCacheItem::CacheState::kChildren:\n      layer->PaintChildren(context);\n      break;\n    case RasterCacheItem::CacheState::kNone:\n      FML_DCHECK(cache_state != RasterCacheItem::CacheState::kNone);\n      return false;\n  }\n  return true;\n}\n\nstatic const auto* flow_type = \"RasterCacheFlow::Layer\";\n\nbool LayerRasterCacheItem::TryToPrepareRasterCache(const PaintContext& context,\n                                                   bool parent_cached) const {\n  has_been_cached_ = false;\n  if (!context.raster_cache || parent_cached) {\n    return false;\n  }\n  if (cache_state_ != kNone) {\n    if (const skity::Rect* paint_bounds = GetPaintBoundsFromLayer()) {\n      RasterCache::Context r_context = {\n          // clang-format off\n          .gr_context         = context.gr_context,\n#ifndef ENABLE_SKITY\n          .dst_color_space    = context.dst_color_space,\n#endif  // ENABLE_SKITY\n          .matrix             = matrix_,\n          .logical_rect       = *paint_bounds,\n          .flow_type          = flow_type,\n          // clang-format on\n      };\n      has_been_cached_ = context.raster_cache->UpdateCacheEntry(\n          GetId().value(), r_context,\n          [ctx = context, cache_state = cache_state_,\n           layer = layer_](clay::GrCanvas* canvas) {\n            Rasterize(cache_state, layer, ctx, canvas);\n          });\n      return has_been_cached_;\n    }\n  }\n  return false;\n}\n\nbool LayerRasterCacheItem::Draw(const PaintContext& context,\n                                const clay::GrPaint* paint) const {\n  return Draw(context, context.canvas, paint);\n}\n\nbool LayerRasterCacheItem::Draw(const PaintContext& context,\n                                clay::GrCanvas* canvas,\n                                const clay::GrPaint* paint) const {\n  if (!context.raster_cache || !canvas) {\n    return false;\n  }\n  switch (cache_state_) {\n    case RasterCacheItem::kNone:\n      return false;\n    case RasterCacheItem::kCurrent: {\n      return context.raster_cache->Draw(key_id_, *canvas, paint);\n    }\n    case RasterCacheItem::kChildren: {\n      return context.raster_cache->Draw(layer_children_id_.value(), *canvas,\n                                        paint);\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/layer_raster_cache_item.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_LAYER_RASTER_CACHE_ITEM_H_\n#define CLAY_FLOW_LAYERS_LAYER_RASTER_CACHE_ITEM_H_\n\n#include <memory>\n#include <optional>\n\n#include \"clay/flow/raster_cache_item.h\"\n\nnamespace clay {\n\nclass LayerRasterCacheItem : public RasterCacheItem {\n public:\n  explicit LayerRasterCacheItem(Layer* layer, int layer_cached_threshold = 1,\n                                bool can_cache_children = false);\n\n  /**\n   * @brief Create a LayerRasterCacheItem, connect a layer and manage the\n   * Layer's raster cache\n   *\n   * @param layer_cache_threshold  after how many frames to start trying to\n   * cache the layer self\n   * @param can_cache_children the layer can do a cache for his children\n   */\n  static std::unique_ptr<LayerRasterCacheItem> Make(\n      Layer*, int layer_cache_threshold, bool can_cache_children = false);\n\n  std::optional<RasterCacheKeyID> GetId() const override;\n\n  void PrerollSetup(PrerollContext* context,\n                    const skity::Matrix& matrix) override;\n\n  void PrerollFinalize(PrerollContext* context,\n                       const skity::Matrix& matrix) override;\n\n  bool Draw(const PaintContext& context,\n            const clay::GrPaint* paint) const override;\n\n  bool Draw(const PaintContext& context, clay::GrCanvas* canvas,\n            const clay::GrPaint* paint) const override;\n\n  bool TryToPrepareRasterCache(const PaintContext& context,\n                               bool parent_cached = false) const override;\n\n  void MarkCacheChildren() { can_cache_children_ = true; }\n\n  void MarkNotCacheChildren() { can_cache_children_ = false; }\n\n  bool IsCacheChildren() const { return cache_state_ == CacheState::kChildren; }\n\n protected:\n  const skity::Rect* GetPaintBoundsFromLayer() const;\n\n  Layer* layer_;\n\n  // The id for cache the layer's children.\n  std::optional<RasterCacheKeyID> layer_children_id_;\n\n  int layer_cached_threshold_ = 1;\n\n  // if the layer's children can be directly cache, set the param is true;\n  bool can_cache_children_ = false;\n\n  mutable int num_cache_attempts_ = 1;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_LAYER_RASTER_CACHE_ITEM_H_\n"
  },
  {
    "path": "clay/flow/layers/layer_state_stack.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/layer_state_stack.h\"\n\n#include <algorithm>\n\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/matrix_clip_tracker.h\"\n#include \"clay/flow/paint_utils.h\"\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n// ==============================================================\n// Delegate subclasses\n// ==============================================================\n\n// The DummyDelegate class implements most Delegate methods as NOP\n// but throws errors if the caller starts executing query methods\n// that require an active delegate to be tracking. It is specifically\n// designed to be immutable, lightweight, and a singleton so that it\n// can be substituted into the delegate slot in a LayerStateStack\n// quickly and cheaply when no externally supplied delegates are present.\nclass DummyDelegate : public LayerStateStack::Delegate {\n public:\n  static const std::shared_ptr<DummyDelegate> kInstance;\n\n  void decommission() override {}\n\n  skity::Rect local_cull_rect() const override {\n    error();\n    return {};\n  }\n  skity::Rect device_cull_rect() const override {\n    error();\n    return {};\n  }\n  skity::Matrix matrix_4x4() const override {\n    error();\n    return {};\n  }\n  bool content_culled(const skity::Rect& content_bounds) const override {\n    error();\n    return true;\n  }\n\n  void save() override {}\n  void saveLayer(const skity::Rect& bounds,\n                 LayerStateStack::RenderingAttributes& attributes,\n                 clay::BlendMode blend,\n                 const clay::ImageFilter* backdrop) override {}\n  void restore() override {}\n\n  void translate(float tx, float ty) override {}\n  void transform(const skity::Matrix& m44) override {}\n  void integralTransform() override {}\n\n  void clipRect(const skity::Rect& rect, clay::GrClipOp op,\n                bool is_aa) override {}\n  void clipRRect(const skity::RRect& rrect, clay::GrClipOp op,\n                 bool is_aa) override {}\n  void clipPath(const clay::GrPath& path, clay::GrClipOp op,\n                bool is_aa) override {}\n\n private:\n  static void error() {\n    FML_DCHECK(false) << \"LayerStateStack state queried without a delegate\";\n  }\n};\nconst std::shared_ptr<DummyDelegate> DummyDelegate::kInstance =\n    std::make_shared<DummyDelegate>();\n\n#ifndef ENABLE_SKITY\nclass SkCanvasDelegate : public LayerStateStack::Delegate {\n public:\n  explicit SkCanvasDelegate(SkCanvas* canvas)\n      : canvas_(canvas), initial_save_level_(canvas->getSaveCount()) {}\n\n  void decommission() override { canvas_->restoreToCount(initial_save_level_); }\n\n  SkCanvas* canvas() const override { return canvas_; }\n\n  skity::Rect local_cull_rect() const override {\n    return clay::ConvertSkRectToSkityRect(canvas_->getLocalClipBounds());\n  }\n  skity::Rect device_cull_rect() const override {\n    return clay::ConvertSkIRectToSkityRect(canvas_->getDeviceClipBounds());\n  }\n  skity::Matrix matrix_4x4() const override {\n    return clay::ConvertSkM44ToMatrix(canvas_->getLocalToDevice());\n  }\n  bool content_culled(const skity::Rect& content_bounds) const override {\n    return canvas_->quickReject(clay::ConvertSkityRectToSkRect(content_bounds));\n  }\n\n  void save() override { canvas_->save(); }\n  void saveLayer(const skity::Rect& bounds,\n                 LayerStateStack::RenderingAttributes& attributes,\n                 clay::BlendMode blend_mode,\n                 const clay::ImageFilter* backdrop) override {\n    TRACE_EVENT(\"clay\", \"Canvas::saveLayer\");\n    SkPaint paint;\n    sk_sp<SkImageFilter> backdrop_filter =\n        backdrop ? backdrop->gr_object() : nullptr;\n    SkRect tmp = clay::ConvertSkityRectToSkRect(bounds);\n    canvas_->saveLayer(SkCanvas::SaveLayerRec(\n        &tmp, attributes.fill(paint, blend_mode), backdrop_filter.get(), 0));\n  }\n  void restore() override { canvas_->restore(); }\n\n  void translate(float tx, float ty) override { canvas_->translate(tx, ty); }\n  void transform(const skity::Matrix& m44) override {\n    canvas_->concat(clay::ConvertSkityMatrixToSkM44(m44));\n  }\n  void integralTransform() override {\n    skity::Matrix matrix = RasterCacheUtil::GetIntegralTransCTM(matrix_4x4());\n    canvas_->setMatrix(clay::ConvertSkityMatrixToSkM44(matrix));\n  }\n\n  void clipRect(const skity::Rect& rect, SkClipOp op, bool is_aa) override {\n    canvas_->clipRect(clay::ConvertSkityRectToSkRect(rect), op, is_aa);\n  }\n  void clipRRect(const skity::RRect& rrect, SkClipOp op, bool is_aa) override {\n    canvas_->clipRRect(clay::ConvertSkityRRectToSkia(rrect), op, is_aa);\n  }\n  void clipPath(const SkPath& path, SkClipOp op, bool is_aa) override {\n    canvas_->clipPath(path, op, is_aa);\n  }\n\n private:\n  SkCanvas* canvas_;\n  const int initial_save_level_;\n};\n#else\nclass SkityCanvasDelegate : public LayerStateStack::Delegate {\n public:\n  explicit SkityCanvasDelegate(skity::Canvas* canvas)\n      : canvas_(canvas), initial_save_level_(canvas->GetSaveCount()) {}\n\n  void decommission() override { canvas_->RestoreToCount(initial_save_level_); }\n\n  skity::Canvas* canvas() const override { return canvas_; }\n\n  skity::Rect local_cull_rect() const override {\n    auto rect = canvas_->GetLocalClipBounds();\n    return skity::Rect::MakeXYWH(rect.X(), rect.Y(), rect.Width(),\n                                 rect.Height());\n  }\n  skity::Rect device_cull_rect() const override {\n    auto rect = canvas_->GetGlobalClipBounds();\n    return skity::Rect::MakeXYWH(rect.X(), rect.Y(), rect.Width(),\n                                 rect.Height());\n  }\n  skity::Matrix matrix_4x4() const override {\n    return canvas_->GetTotalMatrix();\n  }\n  bool content_culled(const skity::Rect& content_bounds) const override {\n    return canvas_->QuickReject(content_bounds);\n  }\n\n  void save() override { canvas_->Save(); }\n  void saveLayer(const skity::Rect& bounds,\n                 LayerStateStack::RenderingAttributes& attributes,\n                 clay::BlendMode blend_mode,\n                 const clay::ImageFilter* backdrop) override {\n    TRACE_EVENT(\"flutter\", \"Canvas::saveLayer\");\n    skity::Paint paint;\n    attributes.fill(paint, blend_mode);\n    canvas_->SaveLayer(bounds, paint);\n  }\n  void restore() override { canvas_->Restore(); }\n\n  void translate(float tx, float ty) override { canvas_->Translate(tx, ty); }\n  void transform(const skity::Matrix& m44) override { canvas_->Concat(m44); }\n  void integralTransform() override {\n    skity::Matrix matrix = RasterCacheUtil::GetIntegralTransCTM(matrix_4x4());\n    canvas_->SetMatrix(matrix);\n  }\n\n  void clipRect(const skity::Rect& rect, clay::GrClipOp op,\n                bool is_aa) override {\n    canvas_->ClipRect(rect, static_cast<skity::Canvas::ClipOp>(op));\n  }\n  void clipRRect(const skity::RRect& rrect, clay::GrClipOp op,\n                 bool is_aa) override {\n    canvas_->ClipRRect(rrect, op);\n  }\n\n  void clipPath(const skity::Path& path, clay::GrClipOp op,\n                bool is_aa) override {\n    canvas_->ClipPath(path, op);\n  }\n\n private:\n  skity::Canvas* canvas_;\n  const int initial_save_level_;\n};\n#endif  // ENABLE_SKITY\n\nclass PrerollDelegate : public LayerStateStack::Delegate {\n public:\n  PrerollDelegate(const skity::Rect& cull_rect, const skity::Matrix& matrix)\n      : tracker_(cull_rect, matrix) {}\n\n  void decommission() override {}\n\n  skity::Matrix matrix_4x4() const override { return tracker_.matrix_4x4(); }\n  skity::Rect local_cull_rect() const override {\n    return tracker_.local_cull_rect();\n  }\n  skity::Rect device_cull_rect() const override {\n    return tracker_.device_cull_rect();\n  }\n  bool content_culled(const skity::Rect& content_bounds) const override {\n    return tracker_.content_culled(content_bounds);\n  }\n\n  void save() override { tracker_.save(); }\n  void saveLayer(const skity::Rect& bounds,\n                 LayerStateStack::RenderingAttributes& attributes,\n                 clay::BlendMode blend,\n                 const clay::ImageFilter* backdrop) override {\n    tracker_.save();\n  }\n  void restore() override { tracker_.restore(); }\n\n  void translate(float tx, float ty) override { tracker_.translate(tx, ty); }\n  void transform(const skity::Matrix& m44) override { tracker_.transform(m44); }\n  void integralTransform() override {\n    tracker_.setTransform(\n        RasterCacheUtil::GetIntegralTransCTM(tracker_.matrix_4x4()));\n  }\n\n  void clipRect(const skity::Rect& rect, clay::GrClipOp op,\n                bool is_aa) override {\n    tracker_.clipRect(rect, op, is_aa);\n  }\n  void clipRRect(const skity::RRect& rrect, clay::GrClipOp op,\n                 bool is_aa) override {\n    tracker_.clipRRect(rrect, op, is_aa);\n  }\n  void clipPath(const clay::GrPath& path, clay::GrClipOp op,\n                bool is_aa) override {\n    tracker_.clipPath(path, op, is_aa);\n  }\n\n private:\n  MatrixClipTracker tracker_;\n};\n\n// ==============================================================\n// StateEntry subclasses\n// ==============================================================\n\nclass SaveEntry : public LayerStateStack::StateEntry {\n public:\n  SaveEntry() = default;\n\n  void apply(LayerStateStack* stack) const override {\n    stack->delegate_->save();\n  }\n  void restore(LayerStateStack* stack) const override {\n    stack->delegate_->restore();\n  }\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(SaveEntry);\n};\n\nclass SaveLayerEntry : public LayerStateStack::StateEntry {\n public:\n  SaveLayerEntry(const skity::Rect& bounds, clay::BlendMode blend_mode,\n                 const LayerStateStack::RenderingAttributes& prev)\n      : bounds_(bounds), blend_mode_(blend_mode), old_attributes_(prev) {}\n\n  void apply(LayerStateStack* stack) const override {\n    stack->delegate_->saveLayer(bounds_, stack->outstanding_, blend_mode_,\n                                nullptr);\n    stack->outstanding_ = {};\n  }\n  void restore(LayerStateStack* stack) const override {\n    if (stack->checkerboard_func_) {\n      (*stack->checkerboard_func_)(stack->canvas_delegate(), bounds_);\n    }\n    stack->delegate_->restore();\n    stack->outstanding_ = old_attributes_;\n  }\n\n protected:\n  const skity::Rect bounds_;\n  const clay::BlendMode blend_mode_;\n  const LayerStateStack::RenderingAttributes old_attributes_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(SaveLayerEntry);\n};\n\nclass OpacityEntry : public LayerStateStack::StateEntry {\n public:\n  OpacityEntry(const skity::Rect& bounds, float opacity,\n               const LayerStateStack::RenderingAttributes& prev)\n      : bounds_(bounds),\n        opacity_(opacity),\n        old_opacity_(prev.opacity),\n        old_bounds_(prev.save_layer_bounds) {}\n\n  void apply(LayerStateStack* stack) const override {\n    stack->outstanding_.save_layer_bounds = bounds_;\n    stack->outstanding_.opacity *= opacity_;\n  }\n  void restore(LayerStateStack* stack) const override {\n    stack->outstanding_.save_layer_bounds = old_bounds_;\n    stack->outstanding_.opacity = old_opacity_;\n  }\n  void update_mutators(MutatorsStack* mutators_stack) const override {\n    mutators_stack->PushOpacity(clay::Color::toAlpha(opacity_));\n  }\n\n private:\n  const skity::Rect bounds_;\n  const float opacity_;\n  const float old_opacity_;\n  const skity::Rect old_bounds_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(OpacityEntry);\n};\n\nclass ImageFilterEntry : public LayerStateStack::StateEntry {\n public:\n  ImageFilterEntry(const skity::Rect& bounds,\n                   const std::shared_ptr<const clay::ImageFilter>& filter,\n                   const LayerStateStack::RenderingAttributes& prev)\n      : bounds_(bounds),\n        filter_(filter),\n        old_filter_(prev.image_filter),\n        old_bounds_(prev.save_layer_bounds) {}\n  ~ImageFilterEntry() override = default;\n\n  void apply(LayerStateStack* stack) const override {\n    stack->outstanding_.save_layer_bounds = bounds_;\n    stack->outstanding_.image_filter = filter_;\n  }\n  void restore(LayerStateStack* stack) const override {\n    stack->outstanding_.save_layer_bounds = old_bounds_;\n    stack->outstanding_.image_filter = old_filter_;\n  }\n\n  // There is no ImageFilter mutator currently\n  // void update_mutators(MutatorsStack* mutators_stack) const override;\n\n private:\n  const skity::Rect bounds_;\n  const std::shared_ptr<const clay::ImageFilter> filter_;\n  const std::shared_ptr<const clay::ImageFilter> old_filter_;\n  const skity::Rect old_bounds_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ImageFilterEntry);\n};\n\nclass ColorFilterEntry : public LayerStateStack::StateEntry {\n public:\n  ColorFilterEntry(const skity::Rect& bounds,\n                   const std::shared_ptr<const clay::ColorFilter>& filter,\n                   const LayerStateStack::RenderingAttributes& prev)\n      : bounds_(bounds),\n        filter_(filter),\n        old_filter_(prev.color_filter),\n        old_bounds_(prev.save_layer_bounds) {}\n  ~ColorFilterEntry() override = default;\n\n  void apply(LayerStateStack* stack) const override {\n    stack->outstanding_.save_layer_bounds = bounds_;\n    stack->outstanding_.color_filter = filter_;\n  }\n  void restore(LayerStateStack* stack) const override {\n    stack->outstanding_.save_layer_bounds = old_bounds_;\n    stack->outstanding_.color_filter = old_filter_;\n  }\n\n  // There is no ColorFilter mutator currently\n  // void update_mutators(MutatorsStack* mutators_stack) const override;\n\n private:\n  const skity::Rect bounds_;\n  const std::shared_ptr<const clay::ColorFilter> filter_;\n  const std::shared_ptr<const clay::ColorFilter> old_filter_;\n  const skity::Rect old_bounds_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ColorFilterEntry);\n};\n\nclass BackdropFilterEntry : public SaveLayerEntry {\n public:\n  BackdropFilterEntry(const skity::Rect& bounds,\n                      const std::shared_ptr<const clay::ImageFilter>& filter,\n                      clay::BlendMode blend_mode,\n                      const LayerStateStack::RenderingAttributes& prev)\n      : SaveLayerEntry(bounds, blend_mode, prev), filter_(filter) {}\n  ~BackdropFilterEntry() override = default;\n\n  void apply(LayerStateStack* stack) const override {\n    stack->delegate_->saveLayer(bounds_, stack->outstanding_, blend_mode_,\n                                filter_.get());\n    stack->outstanding_ = {};\n  }\n\n  void reapply(LayerStateStack* stack) const override {\n    // On the reapply for subsequent overlay layers, we do not\n    // want to reapply the backdrop filter, but we do need to\n    // do a saveLayer to encapsulate the contents and match the\n    // restore that will be forthcoming. Note that this is not\n    // perfect if the BlendMode is not associative as we will be\n    // compositing multiple parts of the content in batches.\n    // Luckily the most common SrcOver is associative.\n    SaveLayerEntry::apply(stack);\n  }\n\n private:\n  const std::shared_ptr<const clay::ImageFilter> filter_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(BackdropFilterEntry);\n};\n\nclass TranslateEntry : public LayerStateStack::StateEntry {\n public:\n  TranslateEntry(float tx, float ty) : tx_(tx), ty_(ty) {}\n\n  void apply(LayerStateStack* stack) const override {\n    stack->delegate_->translate(tx_, ty_);\n  }\n  void update_mutators(MutatorsStack* mutators_stack) const override {\n    mutators_stack->PushTransform(skity::Matrix::Translate(tx_, ty_));\n  }\n\n private:\n  const float tx_;\n  const float ty_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(TranslateEntry);\n};\n\nclass TransformM44Entry : public LayerStateStack::StateEntry {\n public:\n  explicit TransformM44Entry(const skity::Matrix& m44) : m44_(m44) {}\n\n  void apply(LayerStateStack* stack) const override {\n    stack->delegate_->transform(m44_);\n  }\n  void update_mutators(MutatorsStack* mutators_stack) const override {\n    mutators_stack->PushTransform(m44_);\n  }\n\n private:\n  const skity::Matrix m44_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(TransformM44Entry);\n};\n\nclass IntegralTransformEntry : public LayerStateStack::StateEntry {\n public:\n  IntegralTransformEntry() = default;\n\n  void apply(LayerStateStack* stack) const override {\n    stack->delegate_->integralTransform();\n  }\n\n private:\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(IntegralTransformEntry);\n};\n\nclass ClipRectEntry : public LayerStateStack::StateEntry {\n public:\n  ClipRectEntry(const skity::Rect& clip_rect, bool is_aa)\n      : clip_rect_(clip_rect), is_aa_(is_aa) {}\n\n  void apply(LayerStateStack* stack) const override {\n    stack->delegate_->clipRect(clip_rect_, clay::GrClipOp::kIntersect, is_aa_);\n  }\n  void update_mutators(MutatorsStack* mutators_stack) const override {\n    mutators_stack->PushClipRect(clip_rect_);\n  }\n\n private:\n  const skity::Rect clip_rect_;\n  const bool is_aa_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ClipRectEntry);\n};\n\nclass ClipRRectEntry : public LayerStateStack::StateEntry {\n public:\n  ClipRRectEntry(const skity::RRect& clip_rrect, bool is_aa)\n      : clip_rrect_(clip_rrect), is_aa_(is_aa) {}\n\n  void apply(LayerStateStack* stack) const override {\n    stack->delegate_->clipRRect(clip_rrect_, clay::GrClipOp::kIntersect,\n                                is_aa_);\n  }\n  void update_mutators(MutatorsStack* mutators_stack) const override {\n    mutators_stack->PushClipRRect(clip_rrect_);\n  }\n\n private:\n  const skity::RRect clip_rrect_;\n  const bool is_aa_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ClipRRectEntry);\n};\n\nclass ClipPathEntry : public LayerStateStack::StateEntry {\n public:\n  ClipPathEntry(const clay::GrPath& clip_path, bool is_aa)\n      : clip_path_(clip_path), is_aa_(is_aa) {}\n\n  ~ClipPathEntry() override = default;\n\n  void apply(LayerStateStack* stack) const override {\n    stack->delegate_->clipPath(clip_path_, clay::GrClipOp::kIntersect, is_aa_);\n  }\n  void update_mutators(MutatorsStack* mutators_stack) const override {\n    mutators_stack->PushClipPath(clip_path_);\n  }\n\n private:\n  clay::GrPath clip_path_;\n  const bool is_aa_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ClipPathEntry);\n};\n\n// ==============================================================\n// RenderingAttributes methods\n// ==============================================================\nclay::GrPaint* LayerStateStack::RenderingAttributes::fill(\n    clay::GrPaint& paint, clay::BlendMode mode) const {\n  clay::GrPaint* ret = nullptr;\n  if (opacity < 1.f) {\n    PAINT_SET_ALPHAF(paint, std::max(opacity, 0.0f));\n    ret = &paint;\n  } else {\n    PAINT_SET_ALPHAF(paint, 1.f);\n  }\n  if (color_filter) {\n    PAINT_SET_COLOR_FILTER(paint, color_filter->gr_object());\n    ret = &paint;\n  } else {\n    PAINT_SET_COLOR_FILTER(paint, nullptr);\n  }\n  if (image_filter) {\n    PAINT_SET_IMAGE_FILTER(paint, image_filter->gr_object());\n    ret = &paint;\n  } else {\n    PAINT_SET_IMAGE_FILTER(paint, nullptr);\n  }\n  PAINT_SET_BLEND_MODE(paint, mode);\n  if (mode != clay::BlendMode::kSrcOver) {\n    ret = &paint;\n  }\n  return ret;\n}\n\nclay::Paint* LayerStateStack::RenderingAttributes::fill(\n    clay::Paint& paint, clay::BlendMode mode) const {\n  clay::Paint* ret = nullptr;\n  if (opacity < 1.f) {\n    paint.setOpacity(std::max(opacity, 0.0f));\n    ret = &paint;\n  } else {\n    paint.setOpacity(1.f);\n  }\n  paint.setColorFilter(color_filter);\n  if (color_filter) {\n    ret = &paint;\n  }\n  paint.setImageFilter(image_filter);\n  if (image_filter) {\n    ret = &paint;\n  }\n  paint.setBlendMode(mode);\n  if (mode != clay::BlendMode::kSrcOver) {\n    ret = &paint;\n  }\n  return ret;\n}\n\n// ==============================================================\n// MutatorContext methods\n// ==============================================================\n\nusing MutatorContext = LayerStateStack::MutatorContext;\n\nvoid MutatorContext::saveLayer(const skity::Rect& bounds) {\n  layer_state_stack_->save_layer(bounds);\n}\n\nvoid MutatorContext::applyOpacity(const skity::Rect& bounds, float opacity) {\n  if (opacity < 1.f) {\n    layer_state_stack_->push_opacity(bounds, opacity);\n  }\n}\n\nvoid MutatorContext::applyImageFilter(\n    const skity::Rect& bounds,\n    const std::shared_ptr<const clay::ImageFilter>& filter) {\n  if (filter) {\n    layer_state_stack_->push_image_filter(bounds, filter);\n  }\n}\n\nvoid MutatorContext::applyColorFilter(\n    const skity::Rect& bounds,\n    const std::shared_ptr<const clay::ColorFilter>& filter) {\n  if (filter) {\n    layer_state_stack_->push_color_filter(bounds, filter);\n  }\n}\n\nvoid MutatorContext::applyBackdropFilter(\n    const skity::Rect& bounds,\n    const std::shared_ptr<const clay::ImageFilter>& filter,\n    clay::BlendMode blend_mode) {\n  layer_state_stack_->push_backdrop(bounds, filter, blend_mode);\n}\n\nvoid MutatorContext::translate(float tx, float ty) {\n  if (!(tx == 0 && ty == 0)) {\n    layer_state_stack_->maybe_save_layer_for_transform(save_needed_);\n    save_needed_ = false;\n    layer_state_stack_->push_translate(tx, ty);\n  }\n}\n\nvoid MutatorContext::transform(const skity::Matrix& m44) {\n  if (m44.OnlyTranslate()) {\n    translate(m44.GetTranslateX(), m44.GetTranslateY());\n  } else if (!m44.IsIdentity()) {\n    layer_state_stack_->maybe_save_layer_for_transform(save_needed_);\n    save_needed_ = false;\n    layer_state_stack_->push_transform(m44);\n  }\n}\n\nvoid MutatorContext::integralTransform() {\n  layer_state_stack_->maybe_save_layer_for_transform(save_needed_);\n  save_needed_ = false;\n  layer_state_stack_->push_integral_transform();\n}\n\nvoid MutatorContext::clipRect(const skity::Rect& rect, bool is_aa) {\n  layer_state_stack_->maybe_save_layer_for_clip(save_needed_);\n  save_needed_ = false;\n  layer_state_stack_->push_clip_rect(rect, is_aa);\n}\n\nvoid MutatorContext::clipRRect(const skity::RRect& rrect, bool is_aa) {\n  layer_state_stack_->maybe_save_layer_for_clip(save_needed_);\n  save_needed_ = false;\n  layer_state_stack_->push_clip_rrect(rrect, is_aa);\n}\n\nvoid MutatorContext::clipPath(const clay::GrPath& path, bool is_aa) {\n  layer_state_stack_->maybe_save_layer_for_clip(save_needed_);\n  save_needed_ = false;\n  layer_state_stack_->push_clip_path(path, is_aa);\n}\n\n// ==============================================================\n// LayerStateStack methods\n// ==============================================================\n\nLayerStateStack::LayerStateStack() : delegate_(DummyDelegate::kInstance) {}\n\nvoid LayerStateStack::clear_delegate() {\n  delegate_->decommission();\n  delegate_ = DummyDelegate::kInstance;\n}\n\nvoid LayerStateStack::set_delegate(clay::GrCanvas* canvas) {\n  if (delegate_) {\n    if (canvas == delegate_->canvas()) {\n      return;\n    }\n    clear_delegate();\n  }\n  if (canvas) {\n#ifndef ENABLE_SKITY\n    delegate_ = std::make_shared<SkCanvasDelegate>(canvas);\n#else\n    delegate_ = std::make_shared<SkityCanvasDelegate>(canvas);\n#endif  // ENABLE_SKITY\n    reapply_all();\n  }\n}\n\nvoid LayerStateStack::set_preroll_delegate(const skity::Rect& cull_rect) {\n  set_preroll_delegate(cull_rect, skity::Matrix());\n}\nvoid LayerStateStack::set_preroll_delegate(const skity::Matrix& matrix) {\n  set_preroll_delegate(kGiantRect, matrix);\n}\nvoid LayerStateStack::set_preroll_delegate(const skity::Rect& cull_rect,\n                                           const skity::Matrix& matrix) {\n  delegate_ = std::make_shared<PrerollDelegate>(cull_rect, matrix);\n  reapply_all();\n}\n\nvoid LayerStateStack::reapply_all() {\n  // We use a local RenderingAttributes instance so that it can track the\n  // necessary state changes independently as they occur in the stack.\n  // Reusing |outstanding_| would wreak havoc on the current state of\n  // the stack. When we are finished, though, the local attributes\n  // contents should match the current outstanding_ values;\n  RenderingAttributes attributes = outstanding_;\n  outstanding_ = {};\n  for (auto& state : state_stack_) {\n    state->reapply(this);\n  }\n  FML_DCHECK(attributes == outstanding_);\n}\n\nvoid LayerStateStack::fill(MutatorsStack* mutators) {\n  for (auto& state : state_stack_) {\n    state->update_mutators(mutators);\n  }\n}\n\nvoid LayerStateStack::restore_to_count(size_t restore_count) {\n  while (state_stack_.size() > restore_count) {\n    state_stack_.back()->restore(this);\n    state_stack_.pop_back();\n  }\n}\n\nvoid LayerStateStack::push_opacity(const skity::Rect& bounds, float opacity) {\n  maybe_save_layer(opacity);\n  state_stack_.emplace_back(\n      std::make_unique<OpacityEntry>(bounds, opacity, outstanding_));\n  apply_last_entry();\n}\n\nvoid LayerStateStack::push_color_filter(\n    const skity::Rect& bounds,\n    const std::shared_ptr<const clay::ColorFilter>& filter) {\n  maybe_save_layer(filter);\n  state_stack_.emplace_back(\n      std::make_unique<ColorFilterEntry>(bounds, filter, outstanding_));\n  apply_last_entry();\n}\n\nvoid LayerStateStack::push_image_filter(\n    const skity::Rect& bounds,\n    const std::shared_ptr<const clay::ImageFilter>& filter) {\n  maybe_save_layer(filter);\n  state_stack_.emplace_back(\n      std::make_unique<ImageFilterEntry>(bounds, filter, outstanding_));\n  apply_last_entry();\n}\n\nvoid LayerStateStack::push_backdrop(\n    const skity::Rect& bounds,\n    const std::shared_ptr<const clay::ImageFilter>& filter,\n    clay::BlendMode blend_mode) {\n  state_stack_.emplace_back(std::make_unique<BackdropFilterEntry>(\n      bounds, filter, blend_mode, outstanding_));\n  apply_last_entry();\n}\n\nvoid LayerStateStack::push_translate(float tx, float ty) {\n  state_stack_.emplace_back(std::make_unique<TranslateEntry>(tx, ty));\n  apply_last_entry();\n}\n\nvoid LayerStateStack::push_transform(const skity::Matrix& m44) {\n  state_stack_.emplace_back(std::make_unique<TransformM44Entry>(m44));\n  apply_last_entry();\n}\n\nvoid LayerStateStack::push_integral_transform() {\n  state_stack_.emplace_back(std::make_unique<IntegralTransformEntry>());\n  apply_last_entry();\n}\n\nvoid LayerStateStack::push_clip_rect(const skity::Rect& rect, bool is_aa) {\n  state_stack_.emplace_back(std::make_unique<ClipRectEntry>(rect, is_aa));\n  apply_last_entry();\n}\n\nvoid LayerStateStack::push_clip_rrect(const skity::RRect& rrect, bool is_aa) {\n  state_stack_.emplace_back(std::make_unique<ClipRRectEntry>(rrect, is_aa));\n  apply_last_entry();\n}\n\nvoid LayerStateStack::push_clip_path(const clay::GrPath& path, bool is_aa) {\n  state_stack_.emplace_back(std::make_unique<ClipPathEntry>(path, is_aa));\n  apply_last_entry();\n}\n\nbool LayerStateStack::needs_save_layer(int flags) const {\n  if (outstanding_.opacity < 1.f &&\n      (flags & LayerStateStack::kCallerCanApplyOpacity) == 0) {\n    return true;\n  }\n  if (outstanding_.image_filter &&\n      (flags & LayerStateStack::kCallerCanApplyImageFilter) == 0) {\n    return true;\n  }\n  if (outstanding_.color_filter &&\n      (flags & LayerStateStack::kCallerCanApplyColorFilter) == 0) {\n    return true;\n  }\n  return false;\n}\n\nvoid LayerStateStack::do_save() {\n  state_stack_.emplace_back(std::make_unique<SaveEntry>());\n  apply_last_entry();\n}\n\nvoid LayerStateStack::save_layer(const skity::Rect& bounds) {\n  state_stack_.emplace_back(std::make_unique<SaveLayerEntry>(\n      bounds, clay::BlendMode::kSrcOver, outstanding_));\n  apply_last_entry();\n}\n\nvoid LayerStateStack::maybe_save_layer_for_transform(bool save_needed) {\n  // Alpha and ColorFilter don't care about transform\n  if (outstanding_.image_filter) {\n    save_layer(outstanding_.save_layer_bounds);\n  } else if (save_needed) {\n    do_save();\n  }\n}\n\nvoid LayerStateStack::maybe_save_layer_for_clip(bool save_needed) {\n  // Alpha and ColorFilter don't care about clipping\n  // - Alpha of clipped content == clip of alpha content\n  // - Color-filtering of clipped content == clip of color-filtered content\n  if (outstanding_.image_filter) {\n    save_layer(outstanding_.save_layer_bounds);\n  } else if (save_needed) {\n    do_save();\n  }\n}\n\nvoid LayerStateStack::maybe_save_layer(int apply_flags) {\n  if (needs_save_layer(apply_flags)) {\n    save_layer(outstanding_.save_layer_bounds);\n  }\n}\n\nvoid LayerStateStack::maybe_save_layer(float opacity) {\n  if (outstanding_.image_filter) {\n    save_layer(outstanding_.save_layer_bounds);\n  }\n}\n\nvoid LayerStateStack::maybe_save_layer(\n    const std::shared_ptr<const clay::ColorFilter>& filter) {\n  if (outstanding_.color_filter || outstanding_.image_filter ||\n      (outstanding_.opacity < 1.f && !filter->can_commute_with_opacity())) {\n    // TBD: compose the 2 color filters together.\n    save_layer(outstanding_.save_layer_bounds);\n  }\n}\n\nvoid LayerStateStack::maybe_save_layer(\n    const std::shared_ptr<const clay::ImageFilter>& filter) {\n  if (outstanding_.image_filter) {\n    // TBD: compose the 2 image filters together.\n    save_layer(outstanding_.save_layer_bounds);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/layer_state_stack.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_LAYER_STATE_STACK_H_\n#define CLAY_FLOW_LAYERS_LAYER_STATE_STACK_H_\n\n#include <algorithm>\n#include <memory>\n#include <vector>\n\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/flow/paint_utils.h\"\n#include \"clay/gfx/paint.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n/// The LayerStateStack manages the inherited state passed down between\n/// |Layer| objects in a |LayerTree| during |Preroll| and |Paint|.\n///\n/// More specifically, it manages the clip and transform state during\n/// recursive rendering and will hold and lazily apply opacity, ImageFilter\n/// and ColorFilter attributes to recursive content. This is not a truly\n/// general state management mechnanism as it makes assumptions that code\n/// will be applying the attributes to rendered content that happens in\n/// recursive calls. The automatic save/restore mechanisms only work in\n/// a context where C++ auto-destruct calls will engage the restore at\n/// the end of a code block and that any applied attributes will only\n/// be applied to the content rendered inside that block. These restrictions\n/// match the organization of the |LayerTree| methods precisely.\n///\n/// The stack can manage a single state delegate. The delegate will provide\n/// tracking of the current transform and clip and will also execute\n/// saveLayer calls at the appropriate time if it is a rendering delegate.\n/// The delegate can be swapped out on the fly (as is typically done by\n/// PlatformViewLayer when recording the state for multiple \"overlay\"\n/// layers that occur between embedded view subtrees. The old delegate\n/// will be restored to its original state before it became a delegate\n/// and the new delegate will have all of the state recorded by the stack\n/// replayed into it to bring it up to speed with the current rendering\n/// context.\n///\n/// The delegate can be any one of:\n///   - Preroll delegate: used during Preroll to remember the outstanding\n///                       state for embedded platform layers\n///   - SkCanvas: used during Paint for the default output to a Skia\n///               surface\n///   - DisplayListBuilder: used during Paint to construct a DisplayList\n///                         for Impeller output\n/// The stack will know which state needs to be conveyed to any of these\n/// delegates and when is the best time to convey that state (i.e. lazy\n/// saveLayer calls for example).\n///\n/// The rendering state attributes will be automatically applied to the\n/// nested content using a |saveLayer| call at the point at which we\n/// encounter rendered content (i.e. various nested layers that exist only\n/// to apply new state will not trigger the |saveLayer| and the attributes\n/// can accumulate until we reach actual content that is rendered.) Some\n/// rendered content can avoid the |saveLayer| if it reports to the object\n/// that it is able to apply all of the attributes that happen to be\n/// outstanding (accumulated from parent state-modifiers). A |ContainerLayer|\n/// can also monitor the attribute rendering capabilities of a list of\n/// children and can ask the object to apply a protective |saveLayer| or\n/// not based on the negotiated capabilities of the entire group.\n///\n/// Any code that is planning to modify the clip, transform, or rendering\n/// attributes for its child content must start by calling the |save| method\n/// which returns a MutatorContext object. The methods that modify such\n/// state only exist on the MutatorContext object so it is difficult to get\n/// that wrong, but the caller must make sure that the call happens within\n/// a C++ code block that will define the \"rendering scope\" of those\n/// state changes as they will be automatically restored on exit from that\n/// block. Note that the layer might make similar state calls directly on\n/// the canvas or builder during the Paint cycle (via saveLayer, transform,\n/// or clip calls), but should avoid doing so if there is any nested content\n/// that needs to track or react to those state calls.\n///\n/// Code that needs to render content can simply inform the parent of their\n/// abilities by setting the |PrerollContext::renderable_state_flags| during\n/// |Preroll| and then render with those attributes during |Paint| by\n/// requesting the outstanding values of those attributes from the state_stack\n/// object. Individual leaf layers can ignore this feature as the default\n/// behavior during |Preroll| will have their parent |ContainerLayer| assume\n/// that they cannot render any outstanding state attributes and will apply\n/// the protective saveLayer on their behalf if needed. As such, this object\n/// only provides \"opt-in\" features for leaf layers and no responsibilities\n/// otherwise.\n/// See |LayerStateStack::fill|\n/// See |LayerStateStack::outstanding_opacity|\n/// See |LayerStateStack::outstanding_color_filter|\n/// See |LayerStateStack::outstanding_image_filter|\n///\n/// State-modifying layers should contain code similar to this pattern in both\n/// their |Preroll| and |Paint| methods.\n///\n/// void [LayerType]::[Preroll/Paint](context) {\n///   auto mutator = context.state_stack.save();\n///   mutator.translate(origin.x, origin.y);\n///   mutator.applyOpacity(content_bounds, opacity_value);\n///   mutator.applyColorFilter(content_bounds, color_filter);\n///   // or any of the mutator transform, clip or attribute methods\n///\n///   // Children will react to the state applied above during their\n///   // Preroll/Paint methods or ContainerLayer will protect them\n///   // conservatively by default.\n///   [Preroll/Paint]Children(context);\n///\n///   // here the mutator will be auto-destructed and the state accumulated\n///   // by it will be restored out of the state_stack and its associated\n///   // delegates.\n/// }\nclass LayerStateStack {\n public:\n  LayerStateStack();\n\n  // Clears out any old delegate to make room for a new one.\n  void clear_delegate();\n\n  // Return the SkCanvas delegate if the state stack has such a delegate.\n  // The state stack will only have one delegate at a time holding either\n  // an SkCanvas, DisplayListBuilder, or a preroll accumulator.\n  // See also |builder_delegate|.\n  clay::GrCanvas* canvas_delegate() { return delegate_->canvas(); }\n\n  // Clears the old delegate and sets the canvas delegate to the indicated\n  // canvas (if not nullptr). This ensures that only one delegate is present at\n  // any one time.\n  void set_delegate(clay::GrCanvas* canvas);\n\n  // Clears the old delegate and sets the state stack up to accumulate\n  // clip and transform information for a Preroll phase. This ensures\n  // that only one delegate - either a canvas, a builder, or a preroll\n  // accumulator - is present at any one time.\n  void set_preroll_delegate(const skity::Rect& cull_rect,\n                            const skity::Matrix& matrix);\n  void set_preroll_delegate(const skity::Rect& cull_rect);\n  void set_preroll_delegate(const skity::Matrix& matrix);\n\n  // Fills the supplied MatatorsStack object with the mutations recorded\n  // by this LayerStateStack in the order encountered.\n  void fill(MutatorsStack* mutators);\n\n  // Sets up a checkerboard function that will be used to checkerboard the\n  // contents of any saveLayer executed by the state stack.\n  CheckerboardFunc checkerboard_func() const { return checkerboard_func_; }\n  void set_checkerboard_func(CheckerboardFunc checkerboard_func) {\n    checkerboard_func_ = checkerboard_func;\n  }\n\n  class AutoRestore {\n   public:\n    ~AutoRestore() {\n      layer_state_stack_->restore_to_count(stack_restore_count_);\n    }\n\n   private:\n    AutoRestore(LayerStateStack* stack, const skity::Rect& bounds, int flags)\n        : layer_state_stack_(stack),\n          stack_restore_count_(stack->stack_count()) {\n      if (stack->needs_save_layer(flags)) {\n        stack->save_layer(bounds);\n      }\n    }\n    friend class LayerStateStack;\n\n    LayerStateStack* layer_state_stack_;\n    const size_t stack_restore_count_;\n\n    BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(AutoRestore);\n  };\n\n  class MutatorContext {\n   public:\n    ~MutatorContext() {\n      layer_state_stack_->restore_to_count(stack_restore_count_);\n    }\n\n    // Immediately executes a saveLayer with all accumulated state\n    // onto the canvas or builder to be applied at the next matching\n    // restore. A saveLayer is always executed by this method even if\n    // there are no outstanding attributes.\n    void saveLayer(const skity::Rect& bounds);\n\n    // Records the opacity for application at the next call to\n    // saveLayer or applyState. A saveLayer may be executed at\n    // this time if the opacity cannot be batched with other\n    // outstanding attributes.\n    void applyOpacity(const skity::Rect& bounds, float opacity);\n\n    // Records the image filter for application at the next call to\n    // saveLayer or applyState. A saveLayer may be executed at\n    // this time if the image filter cannot be batched with other\n    // outstanding attributes.\n    // (Currently only opacity is recorded for batching)\n    void applyImageFilter(\n        const skity::Rect& bounds,\n        const std::shared_ptr<const clay::ImageFilter>& filter);\n\n    // Records the color filter for application at the next call to\n    // saveLayer or applyState. A saveLayer may be executed at\n    // this time if the color filter cannot be batched with other\n    // outstanding attributes.\n    // (Currently only opacity is recorded for batching)\n    void applyColorFilter(\n        const skity::Rect& bounds,\n        const std::shared_ptr<const clay::ColorFilter>& filter);\n\n    // Saves the state stack and immediately executes a saveLayer\n    // with the indicated backdrop filter and any outstanding\n    // state attributes. Since the backdrop filter only applies\n    // to the pixels alrady on the screen when this call is made,\n    // the backdrop filter will only be applied to the canvas or\n    // builder installed at the time that this call is made, and\n    // subsequent canvas or builder objects that are made delegates\n    // will only see a saveLayer with the indicated blend_mode.\n    void applyBackdropFilter(\n        const skity::Rect& bounds,\n        const std::shared_ptr<const clay::ImageFilter>& filter,\n        clay::BlendMode blend_mode);\n\n    void translate(float tx, float ty);\n    void translate(skity::Vec2 tp) { translate(tp.x, tp.y); }\n    void transform(const skity::Matrix& m44);\n    void integralTransform();\n\n    void clipRect(const skity::Rect& rect, bool is_aa);\n    void clipRRect(const skity::RRect& rrect, bool is_aa);\n    void clipPath(const clay::GrPath& path, bool is_aa);\n\n   private:\n    MutatorContext(LayerStateStack* stack)\n        : layer_state_stack_(stack),\n          stack_restore_count_(stack->stack_count()),\n          save_needed_(true) {}\n    friend class LayerStateStack;\n\n    LayerStateStack* layer_state_stack_;\n    const size_t stack_restore_count_;\n    bool save_needed_;\n\n    BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(MutatorContext);\n  };\n\n  static constexpr int kCallerCanApplyOpacity = 0x1;\n  static constexpr int kCallerCanApplyColorFilter = 0x2;\n  static constexpr int kCallerCanApplyImageFilter = 0x4;\n  static constexpr int kCallerCanApplyAnything =\n      (kCallerCanApplyOpacity | kCallerCanApplyColorFilter |\n       kCallerCanApplyImageFilter);\n\n  // Apply the outstanding state via saveLayer if necessary,\n  // respecting the flags representing which potentially\n  // outstanding attributes the calling layer can apply\n  // themselves.\n  //\n  // A saveLayer may or may not be sent to the delegates depending\n  // on how the outstanding state intersects with the flags supplied\n  // by the caller.\n  //\n  // An AutoRestore instance will always be returned even if there\n  // was no saveLayer applied.\n  [[nodiscard]] inline AutoRestore applyState(const skity::Rect& bounds,\n                                              int can_apply_flags) {\n    return AutoRestore(this, bounds, can_apply_flags);\n  }\n\n  float outstanding_opacity() const { return outstanding_.opacity; }\n\n  std::shared_ptr<const clay::ColorFilter> outstanding_color_filter() const {\n    return outstanding_.color_filter;\n  }\n\n  std::shared_ptr<const clay::ImageFilter> outstanding_image_filter() const {\n    return outstanding_.image_filter;\n  }\n\n  // The outstanding bounds are the bounds recorded during the last\n  // attribute applied to this state stack. The assumption is that\n  // the nested calls to the state stack will each supply bounds relative\n  // to the content of that single attribute and the bounds of the content\n  // of any outstanding attributes will include the output bounds of\n  // applying any nested attributes. Thus, only the innermost content\n  // bounds received will be sufficient to apply all outstanding attributes.\n  skity::Rect outstanding_bounds() const {\n    return outstanding_.save_layer_bounds;\n  }\n\n  // Fill the provided paint object with any oustanding attributes and\n  // return a pointer to it, or return a nullptr if there were no\n  // outstanding attributes to paint with.\n  clay::GrPaint* fill(clay::GrPaint& paint) const {\n    return outstanding_.fill(paint);\n  }\n\n  // Fill the provided paint object with any oustanding attributes and\n  // return a pointer to it, or return a nullptr if there were no\n  // outstanding attributes to paint with.\n  clay::Paint* fill(clay::Paint& paint) const {\n    return outstanding_.fill(paint);\n  }\n\n  // The cull_rect (not the exact clip) relative to the device pixels.\n  // This rectangle may be a conservative estimate of the true clip region.\n  skity::Rect device_cull_rect() const { return delegate_->device_cull_rect(); }\n\n  // The cull_rect (not the exact clip) relative to the local coordinates.\n  // This rectangle may be a conservative estimate of the true clip region.\n  skity::Rect local_cull_rect() const { return delegate_->local_cull_rect(); }\n\n  // The transform from the local coordinates to the device coordinates\n  // in the most capable 4x4 matrix representation. This matrix may be\n  // more information than is needed to compute bounds for a 2D rendering\n  // primitive, but it will accurately concatenate with other 4x4 matrices\n  // without losing information.\n  skity::Matrix transform_4x4() const { return delegate_->matrix_4x4(); }\n\n  // Tests if painting content with the current outstanding attributes\n  // will produce any content. This method does not check the current\n  // transform or clip for being singular or empty.\n  // See |content_culled|\n  bool painting_is_nop() const { return outstanding_.opacity <= 0; }\n\n  // Tests if painting content with the given bounds will produce any output.\n  // This method does not check the outstanding attributes to verify that\n  // they produce visible results.\n  // See |painting_is_nop|\n  bool content_culled(const skity::Rect& content_bounds) const {\n    return delegate_->content_culled(content_bounds);\n  }\n\n  // Saves the current state of the state stack and returns a\n  // MutatorContext which can be used to manipulate the state.\n  // The state stack will be restored to its current state\n  // when the MutatorContext object goes out of scope.\n  [[nodiscard]] inline MutatorContext save() { return MutatorContext(this); }\n\n  // Returns true if the state stack is in, or has returned to,\n  // its initial state.\n  bool is_empty() const { return state_stack_.empty(); }\n\n private:\n  size_t stack_count() const { return state_stack_.size(); }\n  void restore_to_count(size_t restore_count);\n  void reapply_all();\n\n  void apply_last_entry() { state_stack_.back()->apply(this); }\n\n  // The push methods simply push an associated StateEntry on the stack\n  // and then apply it to the current canvas and builder.\n  // ---------------------\n  // void push_attributes();\n  void push_opacity(const skity::Rect& rect, float opacity);\n  void push_color_filter(\n      const skity::Rect& bounds,\n      const std::shared_ptr<const clay::ColorFilter>& filter);\n  void push_image_filter(\n      const skity::Rect& bounds,\n      const std::shared_ptr<const clay::ImageFilter>& filter);\n  void push_backdrop(const skity::Rect& bounds,\n                     const std::shared_ptr<const clay::ImageFilter>& filter,\n                     clay::BlendMode blend_mode);\n\n  void push_translate(float tx, float ty);\n  void push_transform(const skity::Matrix& matrix);\n  void push_integral_transform();\n\n  void push_clip_rect(const skity::Rect& rect, bool is_aa);\n  void push_clip_rrect(const skity::RRect& rrect, bool is_aa);\n  void push_clip_path(const clay::GrPath& path, bool is_aa);\n  // ---------------------\n\n  // The maybe/needs_save_layer methods will determine if the indicated\n  // attribute can be incorporated into the outstanding attributes as is,\n  // or if the apply_flags are compatible with the outstanding attributes.\n  // If the oustanding attributes are incompatible with the new attribute\n  // or the apply flags, then a protective saveLayer will be executed.\n  // ---------------------\n  bool needs_save_layer(int flags) const;\n  void do_save();\n  void save_layer(const skity::Rect& bounds);\n  void maybe_save_layer_for_transform(bool needs_save);\n  void maybe_save_layer_for_clip(bool needs_save);\n  void maybe_save_layer(int apply_flags);\n  void maybe_save_layer(float opacity);\n  void maybe_save_layer(const std::shared_ptr<const clay::ColorFilter>& filter);\n  void maybe_save_layer(const std::shared_ptr<const clay::ImageFilter>& filter);\n  // ---------------------\n\n  struct RenderingAttributes {\n    // We need to record the last bounds we received for the last\n    // attribute that we recorded so that we can perform a saveLayer\n    // on the proper area. When an attribute is applied that cannot\n    // be merged with the existing attributes, it will be submitted\n    // with a bounds for its own source content, not the bounds for\n    // the content that will be included in the saveLayer that applies\n    // the existing outstanding attributes - thus we need to record\n    // the bounds that were supplied with the most recent previous\n    // attribute to be applied.\n    skity::Rect save_layer_bounds{0, 0, 0, 0};\n\n    float opacity = 1.f;\n    std::shared_ptr<const clay::ColorFilter> color_filter;\n    std::shared_ptr<const clay::ImageFilter> image_filter;\n\n    clay::GrPaint* fill(clay::GrPaint& paint,\n                        clay::BlendMode mode = clay::BlendMode::kSrcOver) const;\n    clay::Paint* fill(clay::Paint& paint,\n                      clay::BlendMode mode = clay::BlendMode::kSrcOver) const;\n\n    bool operator==(const RenderingAttributes& other) const {\n      return save_layer_bounds == other.save_layer_bounds &&\n             opacity == other.opacity &&\n             Equals(color_filter, other.color_filter) &&\n             Equals(image_filter, other.image_filter);\n    }\n  };\n\n  class StateEntry {\n   public:\n    virtual ~StateEntry() = default;\n\n    virtual void apply(LayerStateStack* stack) const = 0;\n    virtual void reapply(LayerStateStack* stack) const { apply(stack); }\n    virtual void restore(LayerStateStack* stack) const {}\n    virtual void update_mutators(MutatorsStack* mutators_stack) const {}\n\n   protected:\n    StateEntry() = default;\n\n    BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(StateEntry);\n  };\n  friend class SaveEntry;\n  friend class SaveLayerEntry;\n  friend class BackdropFilterEntry;\n  friend class OpacityEntry;\n  friend class ImageFilterEntry;\n  friend class ColorFilterEntry;\n  friend class TranslateEntry;\n  friend class TransformMatrixEntry;\n  friend class TransformM44Entry;\n  friend class IntegralTransformEntry;\n  friend class ClipEntry;\n  friend class ClipRectEntry;\n  friend class ClipRRectEntry;\n  friend class ClipPathEntry;\n\n  class Delegate {\n   public:\n    virtual ~Delegate() = default;\n\n    // Mormally when a |Paint| or |Preroll| cycle is completed, the\n    // delegate will have been rewound to its initial state by the\n    // trailing recursive actions of the paint and preroll methods.\n    // When a delegate is swapped out, there may be unresolved state\n    // that the delegate received. This method is called when the\n    // delegate is cleared or swapped out to inform it to rewind its\n    // state and finalize all outstanding save or saveLayer operations.\n    virtual void decommission() = 0;\n\n    virtual clay::GrCanvas* canvas() const { return nullptr; }\n\n    virtual skity::Rect local_cull_rect() const = 0;\n    virtual skity::Rect device_cull_rect() const = 0;\n    virtual skity::Matrix matrix_4x4() const = 0;\n    virtual bool content_culled(const skity::Rect& content_bounds) const = 0;\n\n    virtual void save() = 0;\n    virtual void saveLayer(const skity::Rect& bounds,\n                           RenderingAttributes& attributes,\n                           clay::BlendMode blend,\n                           const clay::ImageFilter* backdrop) = 0;\n    virtual void restore() = 0;\n\n    virtual void translate(float tx, float ty) = 0;\n    virtual void transform(const skity::Matrix& m44) = 0;\n    virtual void integralTransform() = 0;\n\n    virtual void clipRect(const skity::Rect& rect, clay::GrClipOp op,\n                          bool is_aa) = 0;\n    virtual void clipRRect(const skity::RRect& rrect, clay::GrClipOp op,\n                           bool is_aa) = 0;\n    virtual void clipPath(const clay::GrPath& path, clay::GrClipOp op,\n                          bool is_aa) = 0;\n  };\n  friend class DummyDelegate;\n#ifndef ENABLE_SKITY\n  friend class SkCanvasDelegate;\n#else\n  friend class SkityCanvasDelegate;\n#endif  // ENABLE_SKITY\n  friend class DlBuilderDelegate;\n  friend class PrerollDelegate;\n\n  std::vector<std::unique_ptr<StateEntry>> state_stack_;\n  friend class MutatorContext;\n\n  std::shared_ptr<Delegate> delegate_;\n  RenderingAttributes outstanding_;\n  CheckerboardFunc checkerboard_func_ = nullptr;\n\n  friend class SaveLayerEntry;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_LAYER_STATE_STACK_H_\n"
  },
  {
    "path": "clay/flow/layers/layer_state_stack_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/layers/layer_state_stack.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\n#ifndef NDEBUG\nTEST(LayerStateStack, AccessorsDieWithoutDelegate) {\n  LayerStateStack state_stack;\n\n  EXPECT_DEATH_IF_SUPPORTED(state_stack.device_cull_rect(),\n                            \"LayerStateStack state queried without a delegate\");\n  EXPECT_DEATH_IF_SUPPORTED(state_stack.local_cull_rect(),\n                            \"LayerStateStack state queried without a delegate\");\n  EXPECT_DEATH_IF_SUPPORTED(state_stack.transform_4x4(),\n                            \"LayerStateStack state queried without a delegate\");\n  EXPECT_DEATH_IF_SUPPORTED(state_stack.content_culled({}),\n                            \"LayerStateStack state queried without a delegate\");\n  {\n    // state_stack.set_preroll_delegate(kGiantRect, skity::Matrix());\n    auto mutator = state_stack.save();\n    mutator.applyOpacity({}, 0.5);\n    state_stack.clear_delegate();\n    auto restore = state_stack.applyState({}, 0);\n  }\n}\n#endif\n\nTEST(LayerStateStack, Defaults) {\n  LayerStateStack state_stack;\n\n  ASSERT_EQ(state_stack.canvas_delegate(), nullptr);\n  ASSERT_EQ(state_stack.checkerboard_func(), nullptr);\n  ASSERT_EQ(state_stack.outstanding_opacity(), 1.f);\n  ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr);\n  ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n  ASSERT_EQ(state_stack.outstanding_bounds(), skity::Rect());\n\n  state_stack.set_preroll_delegate(kGiantRect, skity::Matrix());\n  ASSERT_EQ(state_stack.device_cull_rect(), kGiantRect);\n  ASSERT_EQ(state_stack.local_cull_rect(), kGiantRect);\n  ASSERT_EQ(state_stack.transform_4x4(), skity::Matrix());\n\n  SkPaint sk_paint;\n  state_stack.fill(sk_paint);\n  ASSERT_EQ(sk_paint, SkPaint());\n\n  DlPaint dl_paint;\n  state_stack.fill(dl_paint);\n  ASSERT_EQ(dl_paint, DlPaint());\n}\n\nTEST(LayerStateStack, SingularDelegate) {\n  LayerStateStack state_stack;\n  ASSERT_EQ(state_stack.canvas_delegate(), nullptr);\n\n  MockCanvas canvas;\n\n  // builder delegate -> canvas delegate\n  state_stack.set_delegate(&canvas);\n  ASSERT_EQ(state_stack.canvas_delegate(), &canvas);\n\n  // builder delegate -> no delegate\n  state_stack.clear_delegate();\n  ASSERT_EQ(state_stack.canvas_delegate(), nullptr);\n\n  // canvas delegate -> no delegate\n  state_stack.set_delegate(&canvas);\n  state_stack.clear_delegate();\n  ASSERT_EQ(state_stack.canvas_delegate(), nullptr);\n}\n\nTEST(LayerStateStack, Opacity) {\n  skity::Rect rect = {10, 10, 20, 20};\n\n  LayerStateStack state_stack;\n  state_stack.set_preroll_delegate(skity::Rect::MakeLTRB(0, 0, 50, 50));\n  {\n    auto mutator = state_stack.save();\n    mutator.applyOpacity(rect, 0.5f);\n\n    ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f);\n    ASSERT_EQ(state_stack.outstanding_bounds(), rect);\n\n    // Check nested opacities multiply with each other\n    {\n      auto mutator2 = state_stack.save();\n      mutator.applyOpacity(rect, 0.5f);\n\n      ASSERT_EQ(state_stack.outstanding_opacity(), 0.25f);\n      ASSERT_EQ(state_stack.outstanding_bounds(), rect);\n    }\n\n    ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f);\n    ASSERT_EQ(state_stack.outstanding_bounds(), rect);\n  }\n\n  ASSERT_EQ(state_stack.outstanding_opacity(), 1.f);\n  ASSERT_EQ(state_stack.outstanding_bounds(), skity::Rect());\n}\n\nTEST(LayerStateStack, ColorFilter) {\n  skity::Rect rect = {10, 10, 20, 20};\n  std::shared_ptr<DlBlendColorFilter> outer_filter =\n      std::make_shared<DlBlendColorFilter>(DlColor::kYellow(),\n                                           DlBlendMode::kColorBurn);\n  std::shared_ptr<DlBlendColorFilter> inner_filter =\n      std::make_shared<DlBlendColorFilter>(DlColor::kRed(),\n                                           DlBlendMode::kColorBurn);\n\n  LayerStateStack state_stack;\n  state_stack.set_preroll_delegate(skity::Rect::MakeLTRB(0, 0, 50, 50));\n  {\n    auto mutator = state_stack.save();\n    mutator.applyColorFilter(rect, outer_filter);\n\n    ASSERT_EQ(state_stack.outstanding_color_filter(), outer_filter);\n\n    // Check nested color filters result in nested saveLayers\n    {\n      auto mutator2 = state_stack.save();\n      mutator.applyColorFilter(rect, inner_filter);\n\n      ASSERT_EQ(state_stack.outstanding_color_filter(), inner_filter);\n    }\n\n    ASSERT_EQ(state_stack.outstanding_color_filter(), outer_filter);\n  }\n\n  ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr);\n}\n\nTEST(LayerStateStack, ImageFilter) {\n  skity::Rect rect = {10, 10, 20, 20};\n  std::shared_ptr<DlBlurImageFilter> outer_filter =\n      std::make_shared<DlBlurImageFilter>(2.0f, 2.0f, DlTileMode::kClamp);\n  std::shared_ptr<DlBlurImageFilter> inner_filter =\n      std::make_shared<DlBlurImageFilter>(3.0f, 3.0f, DlTileMode::kClamp);\n  skity::Rect outer_src_rect_tmp;\n  ASSERT_EQ(inner_filter->map_local_bounds(rect, outer_src_rect_tmp),\n            &outer_src_rect_tmp);\n  skity::Rect outer_src_rect = outer_src_rect_tmp;\n\n  LayerStateStack state_stack;\n  state_stack.set_preroll_delegate(skity::Rect::MakeLTRB(0, 0, 50, 50));\n  {\n    auto mutator = state_stack.save();\n    mutator.applyImageFilter(outer_src_rect, outer_filter);\n\n    ASSERT_EQ(state_stack.outstanding_image_filter(), outer_filter);\n\n    // Check nested color filters result in nested saveLayers\n    {\n      auto mutator2 = state_stack.save();\n      mutator.applyImageFilter(rect, inner_filter);\n\n      ASSERT_EQ(state_stack.outstanding_image_filter(), inner_filter);\n    }\n\n    ASSERT_EQ(state_stack.outstanding_image_filter(), outer_filter);\n  }\n\n  ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n}\n\nTEST(LayerStateStack, OpacityAndColorFilterInteraction) {\n  skity::Rect rect = {10, 10, 20, 20};\n  std::shared_ptr<DlBlendColorFilter> color_filter =\n      std::make_shared<DlBlendColorFilter>(DlColor::kYellow(),\n                                           DlBlendMode::kColorBurn);\n\n  SkCanvas builder;\n  LayerStateStack state_stack;\n  state_stack.set_delegate(&builder);\n  ASSERT_EQ(builder.getSaveCount(), 1);\n\n  {\n    auto mutator1 = state_stack.save();\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    mutator1.applyOpacity(rect, 0.5f);\n    ASSERT_EQ(builder.getSaveCount(), 1);\n\n    {\n      auto mutator2 = state_stack.save();\n      ASSERT_EQ(builder.getSaveCount(), 1);\n      mutator2.applyColorFilter(rect, color_filter);\n\n      // The opacity will have been resolved by a saveLayer\n      ASSERT_EQ(builder.getSaveCount(), 2);\n      ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter);\n      ASSERT_EQ(state_stack.outstanding_opacity(), 1.f);\n    }\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr);\n    ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f);\n  }\n  ASSERT_EQ(builder.getSaveCount(), 1);\n  ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr);\n  ASSERT_EQ(state_stack.outstanding_opacity(), 1.f);\n\n  {\n    auto mutator1 = state_stack.save();\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    mutator1.applyColorFilter(rect, color_filter);\n    ASSERT_EQ(builder.getSaveCount(), 1);\n\n    {\n      auto mutator2 = state_stack.save();\n      ASSERT_EQ(builder.getSaveCount(), 1);\n      mutator2.applyOpacity(rect, 0.5f);\n\n      // color filter applied to opacity can be applied together\n      ASSERT_EQ(builder.getSaveCount(), 1);\n      ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter);\n      ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f);\n    }\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter);\n    ASSERT_EQ(state_stack.outstanding_opacity(), 1.f);\n  }\n  ASSERT_EQ(builder.getSaveCount(), 1);\n  ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr);\n  ASSERT_EQ(state_stack.outstanding_opacity(), 1.f);\n}\n\nTEST(LayerStateStack, OpacityAndImageFilterInteraction) {\n  skity::Rect rect = {10, 10, 20, 20};\n  std::shared_ptr<DlBlurImageFilter> image_filter =\n      std::make_shared<DlBlurImageFilter>(2.0f, 2.0f, DlTileMode::kClamp);\n\n  SkCanvas builder;\n  LayerStateStack state_stack;\n  state_stack.set_delegate(&builder);\n  ASSERT_EQ(builder.getSaveCount(), 1);\n\n  {\n    auto mutator1 = state_stack.save();\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    mutator1.applyOpacity(rect, 0.5f);\n    ASSERT_EQ(builder.getSaveCount(), 1);\n\n    {\n      auto mutator2 = state_stack.save();\n      ASSERT_EQ(builder.getSaveCount(), 1);\n      mutator2.applyImageFilter(rect, image_filter);\n\n      // opacity applied to image filter can be applied together\n      ASSERT_EQ(builder.getSaveCount(), 1);\n      ASSERT_EQ(state_stack.outstanding_image_filter(), image_filter);\n      ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f);\n    }\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n    ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f);\n  }\n  ASSERT_EQ(builder.getSaveCount(), 1);\n  ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n  ASSERT_EQ(state_stack.outstanding_opacity(), 1.f);\n\n  {\n    auto mutator1 = state_stack.save();\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    mutator1.applyImageFilter(rect, image_filter);\n    ASSERT_EQ(builder.getSaveCount(), 1);\n\n    {\n      auto mutator2 = state_stack.save();\n      ASSERT_EQ(builder.getSaveCount(), 1);\n      mutator2.applyOpacity(rect, 0.5f);\n\n      // The image filter will have been resolved by a saveLayer\n      ASSERT_EQ(builder.getSaveCount(), 2);\n      ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n      ASSERT_EQ(state_stack.outstanding_opacity(), 0.5f);\n    }\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    ASSERT_EQ(state_stack.outstanding_image_filter(), image_filter);\n    ASSERT_EQ(state_stack.outstanding_opacity(), 1.f);\n  }\n  ASSERT_EQ(builder.getSaveCount(), 1);\n  ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n  ASSERT_EQ(state_stack.outstanding_opacity(), 1.f);\n}\n\nTEST(LayerStateStack, ColorFilterAndImageFilterInteraction) {\n  skity::Rect rect = {10, 10, 20, 20};\n  std::shared_ptr<DlBlendColorFilter> color_filter =\n      std::make_shared<DlBlendColorFilter>(DlColor::kYellow(),\n                                           DlBlendMode::kColorBurn);\n  std::shared_ptr<DlBlurImageFilter> image_filter =\n      std::make_shared<DlBlurImageFilter>(2.0f, 2.0f, DlTileMode::kClamp);\n\n  SkCanvas builder;\n  LayerStateStack state_stack;\n  state_stack.set_delegate(&builder);\n  ASSERT_EQ(builder.getSaveCount(), 1);\n\n  {\n    auto mutator1 = state_stack.save();\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    mutator1.applyColorFilter(rect, color_filter);\n    ASSERT_EQ(builder.getSaveCount(), 1);\n\n    {\n      auto mutator2 = state_stack.save();\n      ASSERT_EQ(builder.getSaveCount(), 1);\n      mutator2.applyImageFilter(rect, image_filter);\n\n      // color filter applied to image filter can be applied together\n      ASSERT_EQ(builder.getSaveCount(), 1);\n      ASSERT_EQ(state_stack.outstanding_image_filter(), image_filter);\n      ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter);\n    }\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n    ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter);\n  }\n  ASSERT_EQ(builder.getSaveCount(), 1);\n  ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n  ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr);\n\n  {\n    auto mutator1 = state_stack.save();\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    mutator1.applyImageFilter(rect, image_filter);\n    ASSERT_EQ(builder.getSaveCount(), 1);\n\n    {\n      auto mutator2 = state_stack.save();\n      ASSERT_EQ(builder.getSaveCount(), 1);\n      mutator2.applyColorFilter(rect, color_filter);\n\n      // The image filter will have been resolved by a saveLayer\n      ASSERT_EQ(builder.getSaveCount(), 2);\n      ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n      ASSERT_EQ(state_stack.outstanding_color_filter(), color_filter);\n    }\n    ASSERT_EQ(builder.getSaveCount(), 1);\n    ASSERT_EQ(state_stack.outstanding_image_filter(), image_filter);\n    ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr);\n  }\n  ASSERT_EQ(builder.getSaveCount(), 1);\n  ASSERT_EQ(state_stack.outstanding_image_filter(), nullptr);\n  ASSERT_EQ(state_stack.outstanding_color_filter(), nullptr);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/layer_tree.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/layer_tree.h\"\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/flow/layer_snapshot_store.h\"\n#include \"clay/flow/layers/cacheable_layer.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/paint_utils.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/gfx/animation/keyframe_set.h\"\n#include \"clay/gfx/animation/keyframes_manager.h\"\n#include \"clay/gfx/animation/transition_manager.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n\nnamespace clay {\n\nLayerTree::LayerTree(const skity::Vec2& frame_size, float device_pixel_ratio)\n    : frame_size_(frame_size),\n      device_pixel_ratio_(device_pixel_ratio),\n      rasterizer_tracing_threshold_(0),\n      checkerboard_raster_cache_images_(false),\n      checkerboard_offscreen_layers_(false) {\n  FML_DCHECK(device_pixel_ratio_ != 0.0f);\n}\n\nLayerTree::~LayerTree() { MarkFrameTimingsForPipelineIfNeeded(); }\n\nvoid LayerTree::AppendFrameTimings(std::vector<FrameTimingItem>&& timings,\n                                   bool pipeline_end) {\n  if (frame_timings_.empty()) {\n    frame_timings_ = std::move(timings);\n  } else {\n    frame_timings_.reserve(frame_timings_.size() + timings.size());\n    frame_timings_.insert(frame_timings_.end(),\n                          std::make_move_iterator(timings.begin()),\n                          std::make_move_iterator(timings.end()));\n  }\n\n  if (pipeline_end) {\n    MarkFrameTimingsForPipelineIfNeeded();\n  }\n}\n\nvoid LayerTree::MarkFrameTimingsForPipelineIfNeeded() {\n  if (pipeline_id_list_.empty() || !pipeline_end_callback_) {\n    frame_timings_.clear();\n    return;\n  }\n\n  pipeline_end_callback_(std::move(frame_timings_),\n                         std::move(pipeline_id_list_));\n}\n\n#ifndef ENABLE_SKITY\ninline SkColorSpace* GetColorSpace(SkCanvas* canvas) {\n  return canvas ? canvas->imageInfo().colorSpace() : nullptr;\n}\n#endif  // ENABLE_SKITY\n\nbool LayerTree::Preroll(CompositorContext::ScopedFrame& frame,\n                        bool ignore_raster_cache, skity::Rect cull_rect) {\n  TRACE_EVENT(\"clay\", \"LayerTree::Preroll\");\n\n  if (!root_layer_) {\n    FML_DLOG(ERROR) << \"The scene did not specify any layers.\";\n    return false;\n  }\n\n  frame.context().raster_cache().SetCheckboardCacheImages(\n      checkerboard_raster_cache_images_);\n  LayerStateStack state_stack;\n  state_stack.set_preroll_delegate(cull_rect,\n                                   frame.root_surface_transformation());\n  RasterCache* cache =\n      ignore_raster_cache ? nullptr : &frame.context().raster_cache();\n  raster_cache_items_.clear();\n\n  PrerollContext context = {\n      // clang-format off\n      .raster_cache                  = cache,\n      .gr_context                    = frame.gr_context(),\n      .compositor_state              = frame.compositor_state(),\n      .state_stack                   = state_stack,\n#ifndef ENABLE_SKITY\n      .dst_color_space               = GetColorSpace(frame.canvas()),\n#endif // ENABLE_SKITY\n      .surface_needs_readback        = false,\n      .raster_time                   = frame.context().raster_time(),\n      .ui_time                       = frame.context().ui_time(),\n      .drawable_image_registry       = frame.context().drawable_image_registry(),\n      .frame_device_pixel_ratio      = device_pixel_ratio_,\n      .raster_cached_entries         = &raster_cache_items_,\n      .request_new_frame             = request_new_frame_\n      // clang-format on\n  };\n\n  root_layer_->Preroll(&context);\n\n  return context.surface_needs_readback;\n}\n\nvoid LayerTree::TryToRasterCache(\n    const std::vector<RasterCacheItem*>& raster_cached_items,\n    const PaintContext* paint_context, bool ignore_raster_cache) {\n  unsigned i = 0;\n  const auto item_size = raster_cached_items.size();\n  while (i < item_size) {\n    auto* item = raster_cached_items[i];\n    if (item->need_caching()) {\n      // try to cache current layer\n      // If parent failed to cache, just proceed to the next entry\n      // cache current entry, this entry's parent must not cache\n      if (item->TryToPrepareRasterCache(*paint_context, false)) {\n        // if parent cached, then foreach child layer to touch them.\n        for (unsigned j = 0; j < item->child_items(); j++) {\n          auto* child_item = raster_cached_items[i + j + 1];\n          if (child_item->need_caching()) {\n            child_item->TryToPrepareRasterCache(*paint_context, true);\n          }\n        }\n        i += item->child_items() + 1;\n        continue;\n      }\n    }\n    i++;\n  }\n}\n\nvoid LayerTree::SetServiceManagerForAnimation(\n    std::shared_ptr<clay::ServiceManager> service_manager) {\n  if (!HasAnimations()) {\n    return;\n  }\n  animation_host_->SetServiceManager(service_manager);\n}\n\nvoid LayerTree::ResetServiceManagerForAnimation() {\n  if (!HasAnimations()) {\n    return;\n  }\n  animation_host_->ResetServiceManager();\n}\n\nvoid LayerTree::SetAnimationHost(\n    std::shared_ptr<clay::AnimationHost> animation_host) {\n  animation_host_ = std::move(animation_host);\n}\n\nvoid LayerTree::Paint(CompositorContext::ScopedFrame& frame,\n                      bool ignore_raster_cache) const {\n  TRACE_EVENT(\"clay\", \"LayerTree::Paint\");\n\n  if (!root_layer_) {\n    FML_DLOG(ERROR) << \"The scene did not specify any layers to paint.\";\n    return;\n  }\n\n  LayerStateStack state_stack;\n  if (checkerboard_offscreen_layers_) {\n    state_stack.set_checkerboard_func(DrawCheckerboard);\n  }\n\n  state_stack.set_delegate(frame.canvas());\n\n  // clear the previous snapshots.\n  LayerSnapshotStore* snapshot_store = nullptr;\n  if (enable_leaf_layer_tracing_) {\n    frame.context().snapshot_store().Clear();\n    snapshot_store = &frame.context().snapshot_store();\n  }\n\n  RasterCache* cache =\n      ignore_raster_cache ? nullptr : &frame.context().raster_cache();\n\n  PaintContext context = {\n      // clang-format off\n      .state_stack                   = state_stack,\n      .canvas                        = frame.canvas(),\n      .gr_context                    = frame.gr_context(),\n#ifndef ENABLE_SKITY\n      .dst_color_space               = GetColorSpace(frame.canvas()),\n#endif // ENABLE_SKITY\n      .compositor_state              = frame.compositor_state(),\n      .raster_time                   = frame.context().raster_time(),\n      .ui_time                       = frame.context().ui_time(),\n      .drawable_image_registry       = frame.context().drawable_image_registry(),\n      .raster_cache                  = cache,\n      .frame_device_pixel_ratio      = device_pixel_ratio_,\n      .layer_snapshot_store          = snapshot_store,\n      .enable_leaf_layer_tracing     = enable_leaf_layer_tracing_,\n      // clang-format on\n  };\n\n  if (cache) {\n    cache->EvictUnusedCacheEntries();\n    TryToRasterCache(raster_cache_items_, &context, ignore_raster_cache);\n  }\n\n  if (root_layer_->needs_painting(context)) {\n    root_layer_->Paint(context);\n  }\n}\n\nvoid LayerTree::MergeAnimations(LayerTree* prev_layer_tree) {\n  if (prev_layer_tree == nullptr || !HasAnimations() ||\n      !prev_layer_tree->HasAnimations() || prev_layer_tree == this) {\n    return;\n  }\n  animation_host_->MergeAnimations(prev_layer_tree->animation_host_.get());\n}\n\nbool LayerTree::DoAnimations() {\n  int64_t now = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  return animation_host_ && animation_host_->DoAnimationFrame(now);\n}\n\nbool LayerTree::HasAnimations() const {\n  return animation_host_ && animation_host_->HasAnimations();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/layer_tree.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_LAYER_TREE_H_\n#define CLAY_FLOW_LAYERS_LAYER_TREE_H_\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/flow/animation/animation_host.h\"\n#include \"clay/flow/compositor_context.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/raster_cache.h\"\n\nnamespace clay {\n\nusing PipelineID = std::string;\nusing PipelineEndCallback =\n    std::function<void(std::vector<FrameTimingItem>, std::vector<PipelineID>)>;\n\nclass AnimationHost;\n\nclass LayerTree {\n public:\n  LayerTree(const skity::Vec2& frame_size, float device_pixel_ratio);\n  ~LayerTree();\n\n  // Perform a preroll pass on the tree and return information about\n  // the tree that affects rendering this frame.\n  //\n  // Returns:\n  // - a boolean indicating whether or not the top level of the\n  //   layer tree performs any operations that require readback\n  //   from the root surface.\n  bool Preroll(CompositorContext::ScopedFrame& frame,\n               bool ignore_raster_cache = false,\n               skity::Rect cull_rect = kGiantRect);\n\n  static void TryToRasterCache(\n      const std::vector<RasterCacheItem*>& raster_cached_entries,\n      const PaintContext* paint_context, bool ignore_raster_cache = false);\n\n  void Paint(CompositorContext::ScopedFrame& frame,\n             bool ignore_raster_cache = false) const;\n\n  Layer* root_layer() const { return root_layer_.get(); }\n\n  void set_root_layer(std::shared_ptr<Layer> root_layer) {\n    root_layer_ = std::move(root_layer);\n  }\n\n  const skity::Vec2& frame_size() const { return frame_size_; }\n  float device_pixel_ratio() const { return device_pixel_ratio_; }\n\n  const PaintRegionMap& paint_region_map() const { return paint_region_map_; }\n  PaintRegionMap& paint_region_map() { return paint_region_map_; }\n\n  // The number of frame intervals missed after which the compositor must\n  // trace the rasterized picture to a trace file. Specify 0 to disable all\n  // tracing\n  void set_rasterizer_tracing_threshold(uint32_t interval) {\n    rasterizer_tracing_threshold_ = interval;\n  }\n\n  uint32_t rasterizer_tracing_threshold() const {\n    return rasterizer_tracing_threshold_;\n  }\n\n  void set_checkerboard_raster_cache_images(bool checkerboard) {\n    checkerboard_raster_cache_images_ = checkerboard;\n  }\n\n  void set_checkerboard_offscreen_layers(bool checkerboard) {\n    checkerboard_offscreen_layers_ = checkerboard;\n  }\n\n  /// When `Paint` is called, if leaf layer tracing is enabled, additional\n  /// metadata around rasterization of leaf layers is collected.\n  ///\n  /// See: `LayerSnapshotStore`\n  void enable_leaf_layer_tracing(bool enable) {\n    enable_leaf_layer_tracing_ = enable;\n  }\n\n  bool is_leaf_layer_tracing_enabled() const {\n    return enable_leaf_layer_tracing_;\n  }\n  void SetServiceManagerForAnimation(\n      std::shared_ptr<clay::ServiceManager> service_manager);\n  void ResetServiceManagerForAnimation();\n  void SetAnimationHost(std::shared_ptr<AnimationHost> animation_host);\n\n  bool DoAnimations();\n  bool HasAnimations() const;\n\n  // When a layer_tree from ui get consumed by the Raster Thread. It shouldn't\n  // be drawn in time to avoid repeat swap-buffer. Inside we will postpone it\n  // until the next Raster thread rendering triggered by the compositor\n  // animation, and during this process, we also need to synchronize the state\n  // from the previous compositor animation.\n  // Always get called in Raster Thread.\n  void MergeAnimations(LayerTree* old_layer_tree);\n\n  // The `last_draw_vsync_time` will be set after submit the layerTree to the\n  //  screen *successfully*\n  fml::TimePoint LastDrawVsyncTime() const { return last_draw_vsync_time_; }\n\n  void SetLastDrawVsyncTime(fml::TimePoint time) {\n    last_draw_vsync_time_ = time;\n  }\n\n  // The Vsync time of generate the layer_tree by the Vsync.\n  fml::TimePoint VsyncTimeOnGeneration() const {\n    return vsync_time_on_generation_;\n  }\n\n  void SetVsyncTimeOnGeneration(fml::TimePoint time) {\n    vsync_time_on_generation_ = time;\n  }\n\n  int VsyncSequenceId() const {\n    FML_DCHECK(vsync_sequence_id_ >= 0);\n    return vsync_sequence_id_;\n  }\n\n  void SetVsyncSequenceId(int id) { vsync_sequence_id_ = id; }\n\n  bool HasValidVsyncSequenceId() const { return vsync_sequence_id_ != -1; }\n\n  void SetRequestNewFrame(std::function<void()> request_new_frame) {\n    request_new_frame_ = request_new_frame;\n  }\n\n  void SetPipelineIdList(std::vector<std::string> pipeline_id_list) {\n    pipeline_id_list_ = std::move(pipeline_id_list);\n  }\n\n  void AppendFrameTimings(std::vector<FrameTimingItem>&& frame_timings,\n                          bool pipeline_end = false);\n\n  void SetPipelineEndCallback(PipelineEndCallback callback) {\n    pipeline_end_callback_ = std::move(callback);\n  }\n\n private:\n  void MarkFrameTimingsForPipelineIfNeeded();\n\n  std::shared_ptr<Layer> root_layer_;\n\n  fml::TimePoint last_draw_vsync_time_;\n  fml::TimePoint vsync_time_on_generation_;\n  int vsync_sequence_id_ = -1;\n\n  skity::Vec2 frame_size_ = {0, 0};  // Physical pixels.\n  const float device_pixel_ratio_;   // Logical / Physical pixels ratio.\n  uint32_t rasterizer_tracing_threshold_;\n  bool checkerboard_raster_cache_images_;\n  bool checkerboard_offscreen_layers_;\n  bool enable_leaf_layer_tracing_ = false;\n\n  PaintRegionMap paint_region_map_;\n\n  std::vector<RasterCacheItem*> raster_cache_items_;\n\n  std::shared_ptr<clay::AnimationHost> animation_host_;\n\n  std::function<void()> request_new_frame_;\n\n  std::vector<PipelineID> pipeline_id_list_;\n  std::vector<FrameTimingItem> frame_timings_;\n  PipelineEndCallback pipeline_end_callback_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(LayerTree);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_LAYER_TREE_H_\n"
  },
  {
    "path": "clay/flow/layers/layer_tree_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <stddef.h>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/compositor_context.h\"\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/canvas_test.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\nclass LayerTreeTest : public CanvasTest {\n public:\n  LayerTreeTest()\n      : layer_tree_({64, 64}, 1.0f),\n        root_transform_(skity::Matrix::Translate(1.0f, 1.0f)),\n        scoped_frame_(compositor_context_.AcquireFrame(\n            nullptr, &mock_canvas(), nullptr, root_transform_, false, true)) {}\n\n  LayerTree& layer_tree() { return layer_tree_; }\n  CompositorContext::ScopedFrame& frame() { return *scoped_frame_.get(); }\n  const skity::Matrix& root_transform() { return root_transform_; }\n\n private:\n  LayerTree layer_tree_;\n  CompositorContext compositor_context_;\n  skity::Matrix root_transform_;\n  std::unique_ptr<CompositorContext::ScopedFrame> scoped_frame_;\n};\n\nTEST_F(LayerTreeTest, PaintingEmptyLayerDies) {\n  auto layer = std::make_shared<ContainerLayer>();\n\n  layer_tree().set_root_layer(layer);\n  layer_tree().Preroll(frame());\n  EXPECT_EQ(layer->paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_TRUE(layer->is_empty());\n\n  layer_tree().Paint(frame());\n}\n\nTEST_F(LayerTreeTest, PaintBeforePrerollDies) {\n  const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPath child_path;\n  child_path.addRect(child_bounds);\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer);\n\n  layer_tree().set_root_layer(layer);\n  EXPECT_EQ(mock_layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_TRUE(mock_layer->is_empty());\n  EXPECT_TRUE(layer->is_empty());\n\n  layer_tree().Paint(frame());\n  EXPECT_EQ(mock_canvas().draw_calls(), std::vector<MockCanvas::DrawCall>());\n}\n\nTEST_F(LayerTreeTest, Simple) {\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kCyan);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer);\n\n  layer_tree().set_root_layer(layer);\n  layer_tree().Preroll(frame());\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_FALSE(mock_layer->is_empty());\n  EXPECT_FALSE(layer->is_empty());\n  EXPECT_EQ(mock_layer->parent_matrix(), root_transform());\n\n  layer_tree().Paint(frame());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({MockCanvas::DrawCall{\n                0, MockCanvas::DrawPathData{child_path, child_paint}}}));\n}\n\nTEST_F(LayerTreeTest, Multiple) {\n  const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path2 = SkPath().addRect(8.0f, 2.0f, 16.5f, 14.5f);\n  const SkPaint child_paint1(SkColors::kGray);\n  const SkPaint child_paint2(SkColors::kGreen);\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  mock_layer1->set_fake_has_platform_view(true);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  skity::Rect expected_total_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  expected_total_bounds.Join(\n      clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  layer_tree().set_root_layer(layer);\n  layer_tree().Preroll(frame());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), expected_total_bounds);\n  EXPECT_FALSE(mock_layer1->is_empty());\n  EXPECT_FALSE(mock_layer2->is_empty());\n  EXPECT_FALSE(layer->is_empty());\n  EXPECT_EQ(mock_layer1->parent_matrix(), root_transform());\n  EXPECT_EQ(mock_layer2->parent_matrix(), root_transform());\n  EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect);\n  EXPECT_EQ(mock_layer2->parent_cull_rect(),\n            kGiantRect);  // Siblings are independent\n\n  layer_tree().Paint(frame());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector({MockCanvas::DrawCall{\n                       0, MockCanvas::DrawPathData{child_path1, child_paint1}},\n                   MockCanvas::DrawCall{0, MockCanvas::DrawPathData{\n                                               child_path2, child_paint2}}}));\n}\n\nTEST_F(LayerTreeTest, MultipleWithEmpty) {\n  const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPaint child_paint1(SkColors::kGray);\n  const SkPaint child_paint2(SkColors::kGreen);\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(SkPath(), child_paint2);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  layer_tree().set_root_layer(layer);\n  layer_tree().Preroll(frame());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(SkPath().getBounds()));\n  EXPECT_EQ(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_FALSE(mock_layer1->is_empty());\n  EXPECT_TRUE(mock_layer2->is_empty());\n  EXPECT_FALSE(layer->is_empty());\n  EXPECT_EQ(mock_layer1->parent_matrix(), root_transform());\n  EXPECT_EQ(mock_layer2->parent_matrix(), root_transform());\n  EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect);\n  EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect);\n\n  layer_tree().Paint(frame());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({MockCanvas::DrawCall{\n                0, MockCanvas::DrawPathData{child_path1, child_paint1}}}));\n}\n\nTEST_F(LayerTreeTest, NeedsSystemComposite) {\n  const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path2 = SkPath().addRect(8.0f, 2.0f, 16.5f, 14.5f);\n  const SkPaint child_paint1(SkColors::kGray);\n  const SkPaint child_paint2(SkColors::kGreen);\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer = std::make_shared<ContainerLayer>();\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  skity::Rect expected_total_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  expected_total_bounds.Join(\n      clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  layer_tree().set_root_layer(layer);\n  layer_tree().Preroll(frame());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), expected_total_bounds);\n  EXPECT_FALSE(mock_layer1->is_empty());\n  EXPECT_FALSE(mock_layer2->is_empty());\n  EXPECT_FALSE(layer->is_empty());\n  EXPECT_EQ(mock_layer1->parent_matrix(), root_transform());\n  EXPECT_EQ(mock_layer2->parent_matrix(), root_transform());\n  EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect);\n  EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect);\n\n  layer_tree().Paint(frame());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector({MockCanvas::DrawCall{\n                       0, MockCanvas::DrawPathData{child_path1, child_paint1}},\n                   MockCanvas::DrawCall{0, MockCanvas::DrawPathData{\n                                               child_path2, child_paint2}}}));\n}\n\nTEST_F(LayerTreeTest, PrerollContextInitialization) {\n  LayerStateStack state_stack;\n  state_stack.set_preroll_delegate(kGiantRect, skity::Matrix());\n  FixedRefreshRateStopwatch mock_raster_time;\n  FixedRefreshRateStopwatch mock_ui_time;\n  std::shared_ptr<DrawableImageRegistry> mock_registry;\n\n  auto expect_defaults = [&state_stack, &mock_raster_time, &mock_ui_time,\n                          &mock_registry](const PrerollContext& context) {\n    EXPECT_EQ(context.raster_cache, nullptr);\n    EXPECT_EQ(context.gr_context, nullptr);\n    EXPECT_EQ(context.compositor_state, nullptr);\n    EXPECT_EQ(&context.state_stack, &state_stack);\n    EXPECT_EQ(context.dst_color_space, nullptr);\n    EXPECT_EQ(context.state_stack.device_cull_rect(), kGiantRect);\n    EXPECT_EQ(context.state_stack.transform_4x4(), skity::Matrix());\n    EXPECT_EQ(context.surface_needs_readback, false);\n\n    EXPECT_EQ(&context.raster_time, &mock_raster_time);\n    EXPECT_EQ(&context.ui_time, &mock_ui_time);\n    EXPECT_EQ(context.drawable_image_registry.get(), mock_registry.get());\n    EXPECT_EQ(context.frame_device_pixel_ratio, 1.0f);\n\n    EXPECT_EQ(context.has_platform_view, false);\n    EXPECT_EQ(context.has_drawable_image_layer, false);\n    EXPECT_EQ(context.has_punch_hole_layer, false);\n\n    EXPECT_EQ(context.renderable_state_flags, 0);\n    EXPECT_EQ(context.raster_cached_entries, nullptr);\n  };\n\n  // These 4 initializers are required because they are handled by reference\n  PrerollContext context{\n      .state_stack = state_stack,\n      .raster_time = mock_raster_time,\n      .ui_time = mock_ui_time,\n      .drawable_image_registry = mock_registry,\n  };\n  expect_defaults(context);\n}\n\nTEST_F(LayerTreeTest, PaintContextInitialization) {\n  LayerStateStack state_stack;\n  FixedRefreshRateStopwatch mock_raster_time;\n  FixedRefreshRateStopwatch mock_ui_time;\n  std::shared_ptr<DrawableImageRegistry> mock_registry;\n\n  auto expect_defaults = [&state_stack, &mock_raster_time, &mock_ui_time,\n                          &mock_registry](const PaintContext& context) {\n    EXPECT_EQ(&context.state_stack, &state_stack);\n    EXPECT_EQ(context.canvas, nullptr);\n    EXPECT_EQ(context.gr_context, nullptr);\n    EXPECT_EQ(context.compositor_state, nullptr);\n    EXPECT_EQ(&context.raster_time, &mock_raster_time);\n    EXPECT_EQ(&context.ui_time, &mock_ui_time);\n    EXPECT_EQ(context.drawable_image_registry.get(), mock_registry.get());\n    EXPECT_EQ(context.raster_cache, nullptr);\n    EXPECT_EQ(context.state_stack.checkerboard_func(), nullptr);\n    EXPECT_EQ(context.frame_device_pixel_ratio, 1.0f);\n\n    EXPECT_EQ(context.enable_leaf_layer_tracing, false);\n    EXPECT_EQ(context.layer_snapshot_store, nullptr);\n  };\n\n  // These 4 initializers are required because they are handled by reference\n  PaintContext context{\n      .state_stack = state_stack,\n      .raster_time = mock_raster_time,\n      .ui_time = mock_ui_time,\n      .drawable_image_registry = mock_registry,\n  };\n  expect_defaults(context);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/offscreen_surface.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/offscreen_surface.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"third_party/skia/include/core/SkData.h\"\n#include \"third_party/skia/include/core/SkImageEncoder.h\"\n#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n#include \"third_party/skia/include/core/SkSerialProcs.h\"\n#include \"third_party/skia/include/core/SkSurfaceCharacterization.h\"\n#include \"third_party/skia/include/utils/SkBase64.h\"\n\nnamespace clay {\n\nstatic sk_sp<SkSurface> CreateSnapshotSurface(GrDirectContext* surface_context,\n                                              const skity::Vec2& size,\n                                              bool opaque) {\n  const auto image_info = SkImageInfo::Make(\n      {static_cast<int32_t>(size.x), static_cast<int32_t>(size.y)},\n      kRGBA_8888_SkColorType,\n      opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType, nullptr);\n  if (surface_context) {\n    // There is a rendering surface that may contain textures that are going to\n    // be referenced in the layer tree about to be drawn.\n    return SkSurface::MakeRenderTarget(\n        reinterpret_cast<GrRecordingContext*>(surface_context),\n        skgpu::Budgeted::kNo, image_info);\n  }\n\n  // There is no rendering surface, assume no GPU textures are present and\n  // create a raster surface.\n  return SkSurface::MakeRaster(image_info);\n}\n\n/// Returns a buffer containing a snapshot of the surface.\n///\n/// If compressed is true the data is encoded as PNG.\nstatic sk_sp<SkData> GetRasterData(const sk_sp<SkSurface>& offscreen_surface,\n                                   bool compressed) {\n  // Prepare an image from the surface, this image may potentially be on th GPU.\n  auto potentially_gpu_snapshot = offscreen_surface->makeImageSnapshot();\n  if (!potentially_gpu_snapshot) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to make image screenshot\";\n    return nullptr;\n  }\n\n  // Copy the GPU image snapshot into CPU memory.\n  // TODO (https://github.com/flutter/flutter/issues/13498) // NOLINT\n  auto cpu_snapshot = potentially_gpu_snapshot->makeRasterImage();\n  if (!cpu_snapshot) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to make raster image\";\n    return nullptr;\n  }\n\n  // If the caller want the pixels to be compressed, there is a Skia utility to\n  // compress to PNG. Use that.\n  if (compressed) {\n    return cpu_snapshot->encodeToData();\n  }\n\n  // Copy it into a bitmap and return the same.\n  SkPixmap pixmap;\n  if (!cpu_snapshot->peekPixels(&pixmap)) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to obtain bitmap pixels\";\n    return nullptr;\n  }\n  return SkData::MakeWithCopy(pixmap.addr32(), pixmap.computeByteSize());\n}\n\nOffscreenSurface::OffscreenSurface(GrDirectContext* surface_context,\n                                   const skity::Vec2& size, bool opaque) {\n  offscreen_surface_ = CreateSnapshotSurface(surface_context, size, opaque);\n}\n\nsk_sp<SkData> OffscreenSurface::GetRasterData(bool compressed) const {\n  return clay::GetRasterData(offscreen_surface_, compressed);\n}\n\nsk_sp<SkImage> OffscreenSurface::GetRasterImage() const {\n  auto gpu_snapshot = offscreen_surface_->makeImageSnapshot();\n  if (!gpu_snapshot) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to make image screenshot\";\n    return nullptr;\n  }\n  return gpu_snapshot->makeRasterImage();\n}\n\nSkCanvas* OffscreenSurface::GetCanvas() const {\n  return offscreen_surface_->getCanvas();\n}\n\nbool OffscreenSurface::IsValid() const { return offscreen_surface_ != nullptr; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/offscreen_surface.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_OFFSCREEN_SURFACE_H_\n#define CLAY_FLOW_LAYERS_OFFSCREEN_SURFACE_H_\n\n#include \"base/include/fml/macros.h\"\n#include \"skity/geometry/point.hpp\"\n#include \"third_party/skia/include/core/SkImage.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n\nnamespace clay {\n\nclass OffscreenSurface {\n public:\n  explicit OffscreenSurface(GrDirectContext* surface_context,\n                            const skity::Vec2& size, bool opaque = true);\n\n  ~OffscreenSurface() = default;\n\n  sk_sp<SkData> GetRasterData(bool compressed) const;\n\n  sk_sp<SkImage> GetRasterImage() const;\n\n  SkCanvas* GetCanvas() const;\n\n  bool IsValid() const;\n\n private:\n  sk_sp<SkSurface> offscreen_surface_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(OffscreenSurface);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_OFFSCREEN_SURFACE_H_\n"
  },
  {
    "path": "clay/flow/layers/offscreen_surface_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/flow/layers/offscreen_surface.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n#include \"third_party/skia/include/core/SkData.h\"\n\nnamespace clay::testing {\n\nTEST(OffscreenSurfaceTest, EmptySurfaceIsInvalid) {\n  auto surface = std::make_unique<OffscreenSurface>(nullptr, skity::Vec2());\n  ASSERT_FALSE(surface->IsValid());\n}\n\nTEST(OffscreenSurfaceTest, OnexOneSurfaceIsValid) {\n  auto surface = std::make_unique<OffscreenSurface>(nullptr, skity::Vec2(1, 1));\n  ASSERT_TRUE(surface->IsValid());\n}\n\nTEST(OffscreenSurfaceTest, PaintSurfaceBlack) {\n  auto surface = std::make_unique<OffscreenSurface>(nullptr, skity::Vec2(1, 1));\n\n  SkCanvas* canvas = surface->GetCanvas();\n  canvas->clear(SK_ColorBLACK);\n  canvas->flush();\n\n  auto raster_data = surface->GetRasterData(false);\n  const uint32_t* actual =\n      reinterpret_cast<const uint32_t*>(raster_data->data());\n\n  // picking black as the color since byte ordering seems to matter.\n  ASSERT_EQ(actual[0], 0xFF000000u);\n}\n\n}  // namespace clay::testing\n"
  },
  {
    "path": "clay/flow/layers/opacity_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/opacity_layer.h\"\n\n#include <limits>\n#include <optional>\n\n#include \"clay/flow/animation/animation_mutator.h\"\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n// the opacity_layer couldn't cache itself, so the cache_threshold is the\n// max_int\nOpacityLayer::OpacityLayer(uint8_t alpha, const skity::Vec2& offset)\n    : alpha_(alpha), offset_(offset), children_can_accept_opacity_(false) {\n  UpdateRasterCacheItem(std::numeric_limits<int>::max(), true);\n}\n\nvoid OpacityLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  auto* prev = static_cast<const OpacityLayer*>(old_layer);\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(prev);\n    if (alpha() != prev->alpha() || offset_ != prev->offset_) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n  if (HasAnimationRunning()) {\n    context->MarkSubtreeHasRasterAnimation();\n    if (!context->IsSubtreeDirty()) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n  context->PushTransform(skity::Matrix::Translate(offset_.x, offset_.y));\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n  if (context->has_raster_cache()) {\n    context->SetTransform(\n        RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));\n  }\n#endif\n  DiffChildren(context, prev);\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid OpacityLayer::Preroll(PrerollContext* context) {\n  auto mutator = context->state_stack.save();\n  mutator.translate(offset_);\n  mutator.applyOpacity(skity::Rect(), clay::Color::toOpacity(alpha()));\n\n  AutoCache auto_cache = AutoCache(layer_raster_cache_item_.get(), context,\n                                   context->state_stack.transform_4x4());\n  Layer::AutoPrerollSaveLayerState save =\n      Layer::AutoPrerollSaveLayerState::Create(context);\n\n  ContainerLayer::Preroll(context);\n  // We store the inheritance ability of our children for |Paint|\n  set_children_can_accept_opacity((context->renderable_state_flags &\n                                   LayerStateStack::kCallerCanApplyOpacity) !=\n                                  0);\n\n  // Now we let our parent layers know that we, too, can inherit opacity\n  // regardless of what our children are capable of\n  context->renderable_state_flags |= LayerStateStack::kCallerCanApplyOpacity;\n\n  auto rect = paint_bounds();\n  rect.Offset(offset_.x, offset_.y);\n  set_paint_bounds(rect);\n\n  if (children_can_accept_opacity()) {\n    // For opacity layer, we can use raster_cache children only when the\n    // children can't accept opacity so if the children_can_accept_opacity we\n    // should tell the AutoCache object don't do raster_cache.\n    auto_cache.ShouldNotBeCached();\n  }\n}\n\nvoid OpacityLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n  if (subtree_has_punch_hole()) {\n    auto mutator = context.state_stack.save();\n    mutator.translate(offset_.x, offset_.y);\n    context.only_draw_punch_hole = true;\n    PaintChildren(context);\n    context.only_draw_punch_hole = false;\n  }\n\n  auto mutator = context.state_stack.save();\n  mutator.translate(offset_.x, offset_.y);\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n  if (context.raster_cache) {\n    mutator.integralTransform();\n  }\n#endif\n\n  mutator.applyOpacity(child_paint_bounds(), opacity());\n\n  clay::GrPaint paint;\n  if (!children_can_accept_opacity()) {\n    if (layer_raster_cache_item_->Draw(context,\n                                       context.state_stack.fill(paint))) {\n      return;\n    }\n  }\n\n  if (layers().empty()) {\n    return;\n  }\n\n  PaintChildren(context);\n\n#ifndef NDEBUG\n  if (context.enable_raster_cache_tag && layer_raster_cache_item_ &&\n      layer_raster_cache_item_->has_been_cached()) {\n    // Generated raster cache, but not used.\n    DrawRasterCacheTag(context.canvas, paint_bounds().Width() / 2,\n                       paint_bounds().Height() / 2, 0);\n  }\n#endif  // NDEBUG\n}\n\nfloat OpacityLayer::alpha() const {\n  if (HasAnimation()) {\n    const std::shared_ptr<AnimationMutator>& mutator = GetAnimationMutator();\n    return mutator->asOpacity()->alpha();\n  }\n  return alpha_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/opacity_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_OPACITY_LAYER_H_\n#define CLAY_FLOW_LAYERS_OPACITY_LAYER_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/flow/layers/cacheable_layer.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n\nnamespace clay {\n\n// Don't add an OpacityLayer with no children to the layer tree. Painting an\n// OpacityLayer is very costly due to the saveLayer call. If there's no child,\n// having the OpacityLayer or not has the same effect. In debug_unopt build,\n// |Preroll| will assert if there are no children.\nclass OpacityLayer : public CacheableContainerLayer {\n public:\n  // An offset is provided here because OpacityLayer.addToScene method in the\n  // Flutter framework can take an optional offset argument.\n  //\n  // By default, that offset is always zero, and all the offsets are handled by\n  // some parent TransformLayers. But we allow the offset to be non-zero for\n  // backward compatibility. If it's non-zero, the old behavior is to propagate\n  // that offset to all the leaf layers (e.g., DisplayListLayer). That will make\n  // the retained rendering inefficient as a small offset change could propagate\n  // to many leaf layers. Therefore we try to capture that offset here to stop\n  // the propagation as repainting the OpacityLayer is expensive.\n  OpacityLayer(uint8_t alpha, const skity::Vec2& offset);\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  void Preroll(PrerollContext* context) override;\n\n  void Paint(PaintContext& context) const override;\n\n  // Returns whether the children are capable of inheriting an opacity value\n  // and modifying their rendering accordingly. This value is only guaranteed\n  // to be valid after the local |Preroll| method is called.\n  bool children_can_accept_opacity() const {\n    return children_can_accept_opacity_;\n  }\n  void set_children_can_accept_opacity(bool value) {\n    children_can_accept_opacity_ = value;\n  }\n\n  float opacity() const { return alpha() * 1.0 / 0xFF; }\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"OpacityLayer\"; }\n#endif\n\n private:\n  float alpha() const;\n\n  uint8_t alpha_;\n  skity::Vec2 offset_;\n  bool children_can_accept_opacity_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(OpacityLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_OPACITY_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/opacity_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"clay/flow/layers/clip_rect_layer.h\"\n#include \"clay/flow/layers/image_filter_layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/layers/platform_view_layer.h\"\n#include \"clay/flow/layers/transform_layer.h\"\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/flow/testing/diff_context_test.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkPaint.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing OpacityLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(OpacityLayerTest, LeafLayer) {\n  auto layer =\n      std::make_shared<OpacityLayer>(SK_AlphaOPAQUE, skity::Vec2(0.0f, 0.0f));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context()),\n                            \"\\\\!layers\\\\(\\\\)\\\\.empty\\\\(\\\\)\");\n}\n\nTEST_F(OpacityLayerTest, PaintingEmptyLayerDies) {\n  auto mock_layer = std::make_shared<MockLayer>(SkPath());\n  auto layer =\n      std::make_shared<OpacityLayer>(SK_AlphaOPAQUE, skity::Vec2(0.0f, 0.0f));\n  layer->Add(mock_layer);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(SkPath().getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_EQ(layer->child_paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_FALSE(mock_layer->needs_painting(paint_context()));\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(OpacityLayerTest, PaintBeforePrerollDies) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer =\n      std::make_shared<OpacityLayer>(SK_AlphaOPAQUE, skity::Vec2(0.0f, 0.0f));\n  layer->Add(mock_layer);\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(OpacityLayerTest, TranslateChildren) {\n  SkPath child_path1;\n  child_path1.addRect(10.0f, 10.0f, 20.0f, 20.f);\n  SkPaint child_paint1(SkColors::kGray);\n  auto layer = std::make_shared<OpacityLayer>(0.5, skity::Vec2(10, 10));\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  layer->Add(mock_layer1);\n\n  auto initial_transform = SkMatrix::Scale(2.0, 2.0);\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n\n  skity::Rect layer_bounds = mock_layer1->paint_bounds();\n  mock_layer1->parent_matrix().MapRect(&layer_bounds, layer_bounds);\n\n  EXPECT_EQ(layer_bounds, skity::Rect::MakeXYWH(40, 40, 20, 20));\n}\n\nTEST_F(OpacityLayerTest, CacheChild) {\n  const SkAlpha alpha_half = 255 / 2;\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  auto other_transform = SkMatrix::Scale(1.0, 2.0);\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer =\n      std::make_shared<OpacityLayer>(alpha_half, skity::Vec2(0.0f, 0.0f));\n  layer->Add(mock_layer);\n  SkPaint paint;\n\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n  SkCanvas other_canvas;\n  other_canvas.setMatrix(other_transform);\n\n  use_mock_raster_cache();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n\n  const auto* cacheable_opacity_item = layer->raster_cache_item();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(cacheable_opacity_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_opacity_item->GetId().has_value());\n\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n\n  EXPECT_EQ(cacheable_opacity_item->cache_state(),\n            RasterCacheItem::CacheState::kChildren);\n  EXPECT_EQ(\n      cacheable_opacity_item->GetId().value(),\n      RasterCacheKeyID(RasterCacheKeyID::LayerChildrenIds(layer.get()).value(),\n                       RasterCacheKeyType::kLayerChildren));\n  EXPECT_FALSE(raster_cache()->Draw(cacheable_opacity_item->GetId().value(),\n                                    other_canvas, &paint));\n  EXPECT_TRUE(raster_cache()->Draw(cacheable_opacity_item->GetId().value(),\n                                   cache_canvas, &paint));\n}\n\nTEST_F(OpacityLayerTest, CacheChildren) {\n  const SkAlpha alpha_half = 255 / 2;\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  auto other_transform = SkMatrix::Scale(1.0, 2.0);\n  const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  const SkPath child_path2 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  SkPaint paint;\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2);\n  auto layer =\n      std::make_shared<OpacityLayer>(alpha_half, skity::Vec2(0.0f, 0.0f));\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n  SkCanvas other_canvas;\n  other_canvas.setMatrix(other_transform);\n\n  use_mock_raster_cache();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n\n  const auto* cacheable_opacity_item = layer->raster_cache_item();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(cacheable_opacity_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_opacity_item->GetId().has_value());\n\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n\n  EXPECT_EQ(cacheable_opacity_item->cache_state(),\n            RasterCacheItem::CacheState::kChildren);\n  EXPECT_EQ(\n      cacheable_opacity_item->GetId().value(),\n      RasterCacheKeyID(RasterCacheKeyID::LayerChildrenIds(layer.get()).value(),\n                       RasterCacheKeyType::kLayerChildren));\n  EXPECT_FALSE(raster_cache()->Draw(cacheable_opacity_item->GetId().value(),\n                                    other_canvas, &paint));\n  EXPECT_TRUE(raster_cache()->Draw(cacheable_opacity_item->GetId().value(),\n                                   cache_canvas, &paint));\n}\n\nTEST_F(OpacityLayerTest, ShouldNotCacheChildren) {\n  SkPaint paint;\n  auto opacity_layer = std::make_shared<OpacityLayer>(128, skity::Vec2(20, 20));\n  auto mock_layer = MockLayer::MakeOpacityCompatible(SkPath());\n  opacity_layer->Add(mock_layer);\n\n  PrerollContext* context = preroll_context();\n\n  use_mock_raster_cache();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n\n  const auto* cacheable_opacity_item = opacity_layer->raster_cache_item();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(cacheable_opacity_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_opacity_item->GetId().has_value());\n\n  opacity_layer->Preroll(preroll_context());\n\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(cacheable_opacity_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_opacity_item->Draw(paint_context(), &paint));\n}\n\nTEST_F(OpacityLayerTest, FullyOpaque) {\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  const skity::Vec2 layer_offset = skity::Vec2(0.5f, 1.5f);\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 0.5f);\n  const skity::Matrix layer_transform =\n      skity::Matrix::Translate(layer_offset.x, layer_offset.y);\n  const SkPaint child_paint = SkPaint(SkColors::kGreen);\n  skity::Rect expected_layer_bounds;\n  layer_transform.MapRect(\n      &expected_layer_bounds,\n      clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<OpacityLayer>(SK_AlphaOPAQUE, layer_offset);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform * layer_transform);\n  EXPECT_EQ(mock_layer->parent_mutators(),\n            std::vector({Mutator(layer_transform)}));\n\n  const SkPaint opacity_paint = SkPaint(SkColors::kBlack);  // A = 1.0f\n  skity::Rect opacity_bounds =\n      expected_layer_bounds.MakeOffset(-layer_offset.x, -layer_offset.y);\n  opacity_bounds.RoundOut();\n  auto expected_draw_calls = std::vector(\n      {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n       MockCanvas::DrawCall{\n           1, MockCanvas::ConcatMatrixData{clay::ConvertSkityMatrixToSkM44(\n                  layer_transform)}},\n       // NOLINTNEXTLINE\n       MockCanvas::DrawCall{\n           // NOLINTNEXTLINE\n           1, MockCanvas::DrawPathData{child_path, child_paint}},\n       MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}});\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls);\n}\n\nTEST_F(OpacityLayerTest, FullyTransparent) {\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  const skity::Vec2 layer_offset = skity::Vec2(0.5f, 1.5f);\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 0.5f);\n  const skity::Matrix layer_transform =\n      skity::Matrix::Translate(layer_offset.x, layer_offset.y);\n  const SkPaint child_paint = SkPaint(SkColors::kGreen);\n  skity::Rect expected_layer_bounds;\n  layer_transform.MapRect(\n      &expected_layer_bounds,\n      clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer =\n      std::make_shared<OpacityLayer>(SK_AlphaTRANSPARENT, layer_offset);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform * layer_transform);\n  EXPECT_EQ(\n      mock_layer->parent_mutators(),\n      std::vector({Mutator(layer_transform), Mutator(SK_AlphaTRANSPARENT)}));\n\n  auto expected_draw_calls = std::vector(\n      {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n       MockCanvas::DrawCall{\n           1, MockCanvas::ConcatMatrixData{clay::ConvertSkityMatrixToSkM44(\n                  layer_transform)}},\n       MockCanvas::DrawCall{1, MockCanvas::SaveData{2}},\n       MockCanvas::DrawCall{\n           2,\n           MockCanvas::ClipRectData{clay::ConvertSkityRectToSkRect(kEmptyRect),\n                                    SkClipOp::kIntersect,\n                                    MockCanvas::kHard_ClipEdgeStyle}},\n       MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n       MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}});\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls);\n}\n\nTEST_F(OpacityLayerTest, HalfTransparent) {\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  const skity::Vec2 layer_offset = skity::Vec2(0.5f, 1.5f);\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 0.5f);\n  const skity::Matrix layer_transform =\n      skity::Matrix::Translate(layer_offset.x, layer_offset.y);\n  const SkPaint child_paint = SkPaint(SkColors::kGreen);\n  skity::Rect expected_layer_bounds;\n  layer_transform.MapRect(\n      &expected_layer_bounds,\n      clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  const SkAlpha alpha_half = 255 / 2;\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<OpacityLayer>(alpha_half, layer_offset);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform * layer_transform);\n  EXPECT_EQ(mock_layer->parent_mutators(),\n            std::vector({Mutator(layer_transform), Mutator(alpha_half)}));\n\n  skity::Rect opacity_bounds =\n      expected_layer_bounds.MakeOffset(-layer_offset.x, -layer_offset.y);\n  opacity_bounds.RoundOut();\n  DlPaint save_paint = DlPaint().setAlpha(alpha_half);\n  DlPaint child_dl_paint = DlPaint().setColor(DlColor::kGreen());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  auto canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  auto bounds_tmp = clay::ConvertSkityRectToSkRect(opacity_bounds);\n  SkPaint sk_paint = save_paint.gr_object();\n  canvas->save();\n  canvas->translate(layer_offset.x, layer_offset.y);\n  canvas->saveLayer(&bounds_tmp, &sk_paint);\n  canvas->drawPath(child_path, child_dl_paint.gr_object());\n  canvas->restore();\n  canvas->restore();\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  layer->Paint(display_list_paint_context());\n  EXPECT_TRUE(display_list()->approximateOpCount() ==\n              expected_display_list->approximateOpCount());\n}\n\nTEST_F(OpacityLayerTest, Nested) {\n  const SkPath child1_path = SkPath().addRect(SkRect::MakeWH(5.0f, 6.0f));\n  const SkPath child2_path = SkPath().addRect(SkRect::MakeWH(2.0f, 7.0f));\n  const SkPath child3_path = SkPath().addRect(SkRect::MakeWH(6.0f, 6.0f));\n  const skity::Vec2 layer1_offset = skity::Vec2(0.5f, 1.5f);\n  const skity::Vec2 layer2_offset = skity::Vec2(2.5f, 0.5f);\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 0.5f);\n  const skity::Matrix layer1_transform =\n      skity::Matrix::Translate(layer1_offset.x, layer1_offset.y);\n  const skity::Matrix layer2_transform =\n      skity::Matrix::Translate(layer2_offset.x, layer2_offset.y);\n  const SkPaint child1_paint = SkPaint(SkColors::kRed);\n  const SkPaint child2_paint = SkPaint(SkColors::kBlue);\n  const SkPaint child3_paint = SkPaint(SkColors::kGreen);\n  const SkAlpha alpha1 = 155;\n  const SkAlpha alpha2 = 224;\n  auto mock_layer1 = std::make_shared<MockLayer>(child1_path, child1_paint);\n  auto mock_layer2 = std::make_shared<MockLayer>(child2_path, child2_paint);\n  auto mock_layer3 = std::make_shared<MockLayer>(child3_path, child3_paint);\n  auto layer1 = std::make_shared<OpacityLayer>(alpha1, layer1_offset);\n  auto layer2 = std::make_shared<OpacityLayer>(alpha2, layer2_offset);\n  layer2->Add(mock_layer2);\n  layer1->Add(mock_layer1);\n  layer1->Add(layer2);\n  layer1->Add(mock_layer3);  // Ensure something is processed after recursion\n\n  skity::Rect expected_layer2_bounds;\n  layer2_transform.MapRect(\n      &expected_layer2_bounds,\n      clay::ConvertSkRectToSkityRect(child2_path.getBounds()));\n  skity::Rect layer1_child_bounds = expected_layer2_bounds;\n  layer1_child_bounds.Join(\n      clay::ConvertSkRectToSkityRect(child1_path.getBounds()));\n  layer1_child_bounds.Join(\n      clay::ConvertSkRectToSkityRect(child3_path.getBounds()));\n  skity::Rect expected_layer1_bounds =\n      layer1_transform.MapRect(layer1_child_bounds);\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer1->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child1_path.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child2_path.getBounds()));\n  EXPECT_EQ(mock_layer3->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child3_path.getBounds()));\n  EXPECT_EQ(layer1->paint_bounds(), expected_layer1_bounds);\n  EXPECT_EQ(layer1->child_paint_bounds(), layer1_child_bounds);\n  EXPECT_EQ(layer2->paint_bounds(), expected_layer2_bounds);\n  EXPECT_EQ(layer2->child_paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child2_path.getBounds()));\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer3->needs_painting(paint_context()));\n  EXPECT_TRUE(layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(layer2->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform * layer1_transform);\n  //   EXPECT_EQ(mock_layer1->parent_mutators(),\n  //             std::vector({Mutator(layer1_transform), Mutator(alpha1)}));\n  EXPECT_EQ(mock_layer2->parent_matrix(),\n            (initial_transform * layer1_transform) * layer2_transform);\n  //   EXPECT_EQ(mock_layer2->parent_mutators(),\n  //             std::vector({Mutator(layer1_transform), Mutator(alpha1),\n  //                          Mutator(layer2_transform), Mutator(alpha2)}));\n  EXPECT_EQ(mock_layer3->parent_matrix(), initial_transform * layer1_transform);\n  //   EXPECT_EQ(mock_layer3->parent_mutators(),\n  //             std::vector({Mutator(layer1_transform), Mutator(alpha1)}));\n\n  SkPaint opacity1_paint;\n  opacity1_paint.setAlphaf(alpha1 * (1.0 / SK_AlphaOPAQUE));\n  SkPaint opacity2_paint;\n  opacity2_paint.setAlphaf(alpha2 * (1.0 / SK_AlphaOPAQUE));\n  skity::Rect opacity1_bounds =\n      expected_layer1_bounds.MakeOffset(-layer1_offset.x, -layer1_offset.y);\n  skity::Rect opacity2_bounds =\n      expected_layer2_bounds.MakeOffset(-layer2_offset.x, -layer2_offset.y);\n  auto expected_draw_calls = std::vector(\n      {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n       MockCanvas::DrawCall{\n           1, MockCanvas::ConcatMatrixData{clay::ConvertSkityMatrixToSkM44(\n                  layer1_transform)}},\n       MockCanvas::DrawCall{1,\n                            MockCanvas::SaveLayerData{\n                                clay::ConvertSkityRectToSkRect(opacity1_bounds),\n                                opacity1_paint, nullptr, 2}},\n       MockCanvas::DrawCall{\n           2, MockCanvas::DrawPathData{child1_path, child1_paint}},\n       MockCanvas::DrawCall{2, MockCanvas::SaveData{3}},\n       MockCanvas::DrawCall{\n           3, MockCanvas::ConcatMatrixData{clay::ConvertSkityMatrixToSkM44(\n                  layer2_transform)}},\n       MockCanvas::DrawCall{3,\n                            MockCanvas::SaveLayerData{\n                                clay::ConvertSkityRectToSkRect(opacity2_bounds),\n                                opacity2_paint, nullptr, 4}},\n       MockCanvas::DrawCall{\n           4, MockCanvas::DrawPathData{child2_path, child2_paint}},\n       MockCanvas::DrawCall{4, MockCanvas::RestoreData{3}},\n       MockCanvas::DrawCall{3, MockCanvas::RestoreData{2}},\n       MockCanvas::DrawCall{\n           2, MockCanvas::DrawPathData{child3_path, child3_paint}},\n       MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n       MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}});\n  layer1->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls);\n}\n\nTEST_F(OpacityLayerTest, Readback) {\n  auto layer =\n      std::make_shared<OpacityLayer>(kOpaque_SkAlphaType, skity::Vec2());\n  layer->Add(std::make_shared<MockLayer>(SkPath()));\n\n  // OpacityLayer does not read from surface\n  preroll_context()->surface_needs_readback = false;\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n\n  // OpacityLayer blocks child with readback\n  auto mock_layer = std::make_shared<MockLayer>(SkPath(), SkPaint());\n  mock_layer->set_fake_reads_surface(true);\n  layer->Add(mock_layer);\n  preroll_context()->surface_needs_readback = false;\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n}\n\nTEST_F(OpacityLayerTest, CullRectIsTransformed) {\n  auto clip_rect_layer = std::make_shared<ClipRectLayer>(\n      skity::Rect::MakeLTRB(0, 0, 10, 10), clay::hardEdge);\n  auto opacity_layer = std::make_shared<OpacityLayer>(128, skity::Vec2(20, 20));\n  auto mock_layer = std::make_shared<MockLayer>(SkPath());\n  clip_rect_layer->Add(opacity_layer);\n  opacity_layer->Add(mock_layer);\n  clip_rect_layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer->parent_cull_rect().Left(), -20);\n  EXPECT_EQ(mock_layer->parent_cull_rect().Top(), -20);\n}\n\nTEST_F(OpacityLayerTest, OpacityInheritanceCompatibleChild) {\n  auto opacity_layer = std::make_shared<OpacityLayer>(128, skity::Vec2(20, 20));\n  auto mock_layer = MockLayer::MakeOpacityCompatible(SkPath());\n  opacity_layer->Add(mock_layer);\n\n  PrerollContext* context = preroll_context();\n  opacity_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n}\n\nTEST_F(OpacityLayerTest, OpacityInheritanceIncompatibleChild) {\n  auto opacity_layer = std::make_shared<OpacityLayer>(128, skity::Vec2(20, 20));\n  auto mock_layer = MockLayer::Make(SkPath());\n  opacity_layer->Add(mock_layer);\n\n  PrerollContext* context = preroll_context();\n  opacity_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n  EXPECT_FALSE(opacity_layer->children_can_accept_opacity());\n}\n\nTEST_F(OpacityLayerTest, OpacityInheritanceThroughContainer) {\n  auto opacity_layer = std::make_shared<OpacityLayer>(128, skity::Vec2(20, 20));\n  auto container_layer = std::make_shared<ContainerLayer>();\n  auto mock_layer = MockLayer::MakeOpacityCompatible(SkPath());\n  container_layer->Add(mock_layer);\n  opacity_layer->Add(container_layer);\n\n  PrerollContext* context = preroll_context();\n  opacity_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n}\n\nTEST_F(OpacityLayerTest, OpacityInheritanceThroughTransform) {\n  auto opacity_layer = std::make_shared<OpacityLayer>(128, skity::Vec2(20, 20));\n  auto transformLayer =\n      std::make_shared<TransformLayer>(skity::Matrix::Scale(2, 2));\n  auto mock_layer = MockLayer::MakeOpacityCompatible(SkPath());\n  transformLayer->Add(mock_layer);\n  opacity_layer->Add(transformLayer);\n\n  PrerollContext* context = preroll_context();\n  opacity_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n}\n\nTEST_F(OpacityLayerTest, OpacityInheritanceThroughImageFilter) {\n  auto opacity_layer = std::make_shared<OpacityLayer>(128, skity::Vec2(20, 20));\n  auto filter_layer = std::make_shared<ImageFilterLayer>(\n      std::make_shared<DlBlurImageFilter>(5.0, 5.0, DlTileMode::kClamp));\n  auto mock_layer = MockLayer::MakeOpacityCompatible(SkPath());\n  filter_layer->Add(mock_layer);\n  opacity_layer->Add(filter_layer);\n\n  PrerollContext* context = preroll_context();\n  opacity_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n}\n\nTEST_F(OpacityLayerTest, OpacityInheritanceNestedWithCompatibleChild) {\n  skity::Vec2 offset1 = skity::Vec2(10, 20);\n  skity::Vec2 offset2 = skity::Vec2(20, 10);\n  SkPath mock_path = SkPath::Rect({10, 10, 20, 20});\n  auto opacity_layer_1 = std::make_shared<OpacityLayer>(128, offset1);\n  auto opacity_layer_2 = std::make_shared<OpacityLayer>(64, offset2);\n  auto mock_layer = MockLayer::MakeOpacityCompatible(mock_path);\n  opacity_layer_2->Add(mock_layer);\n  opacity_layer_1->Add(opacity_layer_2);\n\n  PrerollContext* context = preroll_context();\n  opacity_layer_1->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n  EXPECT_TRUE(opacity_layer_1->children_can_accept_opacity());\n  EXPECT_TRUE(opacity_layer_2->children_can_accept_opacity());\n\n  // cspell:words savelayer_paint\n  SkPaint savelayer_paint;\n  float inherited_opacity = 128 * 1.0 / SK_AlphaOPAQUE;\n  inherited_opacity *= 64 * 1.0 / SK_AlphaOPAQUE;\n  savelayer_paint.setAlphaf(inherited_opacity);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset1.x, offset1.y);\n      {\n        canvas->save();\n        {\n          canvas->translate(offset2.x, offset2.y);\n          {\n            SkPaint sk_paint;\n            sk_paint.setAlpha(inherited_opacity * 255.f);\n            canvas->drawPath(mock_path, sk_paint);\n          }\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer_1->Paint(display_list_paint_context());\n  EXPECT_TRUE(expected_display_list->approximateOpCount() ==\n              display_list()->approximateOpCount());\n}\n\nTEST_F(OpacityLayerTest, OpacityInheritanceNestedWithIncompatibleChild) {\n  skity::Vec2 offset1 = skity::Vec2(10, 20);\n  skity::Vec2 offset2 = skity::Vec2(20, 10);\n  SkPath mock_path = SkPath::Rect({10, 10, 20, 20});\n  auto opacity_layer_1 = std::make_shared<OpacityLayer>(128, offset1);\n  auto opacity_layer_2 = std::make_shared<OpacityLayer>(64, offset2);\n  auto mock_layer = MockLayer::Make(mock_path);\n  opacity_layer_2->Add(mock_layer);\n  opacity_layer_1->Add(opacity_layer_2);\n\n  PrerollContext* context = preroll_context();\n  opacity_layer_1->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n  EXPECT_TRUE(opacity_layer_1->children_can_accept_opacity());\n  EXPECT_FALSE(opacity_layer_2->children_can_accept_opacity());\n\n  SkPaint savelayer_paint;\n  float inherited_opacity = 128 * 1.0 / SK_AlphaOPAQUE;\n  inherited_opacity *= 64 * 1.0 / SK_AlphaOPAQUE;\n  savelayer_paint.setAlphaf(inherited_opacity);\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset1.x, offset1.y);\n      {\n        canvas->save();\n        {\n          canvas->translate(offset2.x, offset2.y);\n          SkPaint sk_paint;\n          sk_paint.setColor(savelayer_paint.getAlpha() << 24);\n          auto bounds_tmp =\n              clay::ConvertSkityRectToSkRect(mock_layer->paint_bounds());\n          canvas->saveLayer(&bounds_tmp, &sk_paint);\n          {\n            SkPaint new_paint;\n            new_paint.setColor(0xFF000000);\n            canvas->drawPath(mock_path, new_paint);\n          }\n          canvas->restore();\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer_1->Paint(display_list_paint_context());\n  EXPECT_TRUE(expected_display_list->approximateOpCount() ==\n              display_list()->approximateOpCount());\n}\n\nusing OpacityLayerDiffTest = DiffContextTest;\n\nTEST_F(OpacityLayerDiffTest, FractionalTranslation) {\n  auto picture = CreatePictureLayer(\n      CreatePicture(skity::Rect::MakeLTRB(10, 10, 60, 60), 1));\n  auto layer = CreateOpacityLater({picture}, 128, skity::Vec2(0.5, 0.5));\n\n  MockLayerTree tree1;\n  tree1.root()->Add(layer);\n\n  auto damage = DiffLayerTree(tree1, MockLayerTree(), skity::Rect::MakeEmpty(),\n                              0, 0, /*use_raster_cache=*/false);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(10, 10, 61, 61));\n}\n\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\nTEST_F(OpacityLayerDiffTest, FractionalTranslationWithRasterCache) {\n  auto picture = CreateDisplayListLayer(\n      CreateDisplayList(skity::Rect::MakeLTRB(10, 10, 60, 60), 1));\n  auto layer = CreateOpacityLater({picture}, 128, skity::Vec2(0.5, 0.5));\n\n  MockLayerTree tree1;\n  tree1.root()->Add(layer);\n\n  auto damage = DiffLayerTree(tree1, MockLayerTree(), skity::Rect::MakeEmpty(),\n                              0, 0, /*use_raster_cache=*/true);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(11, 11, 61, 61));\n}\n\nTEST_F(OpacityLayerTest, FullyOpaqueWithFractionalValues) {\n  use_mock_raster_cache();  // Ensure non-fractional alignment.\n\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  const skity::Vec2 layer_offset = skity::Vec2(0.5f, 1.5f);\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 0.5f);\n  const skity::Matrix layer_transform =\n      skity::Matrix::Translate(layer_offset.x, layer_offset.y);\n  const SkPaint child_paint = SkPaint(SkColors::kGreen);\n  const skity::Rect expected_layer_bounds =\n      layer_transform.MapRect(child_path.getBounds());\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<OpacityLayer>(SK_AlphaOPAQUE, layer_offset);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n\n  const SkPaint opacity_paint = SkPaint(SkColors::kBlack);  // A = 1.0f\n  skity::Rect opacity_bounds;\n  expected_layer_bounds.makeOffset(-layer_offset.x, -layer_offset.y)\n      .RoundOut(&opacity_bounds);\n  auto expected_draw_calls = std::vector(\n      {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n       MockCanvas::DrawCall{\n           1, MockCanvas::ConcatMatrixData{skity::Matrix(layer_transform)}},\n       MockCanvas::DrawCall{\n           1, MockCanvas::SetMatrixData{skity::Matrix(\n                  RasterCacheUtil::GetIntegralTransCTM(layer_transform))}},\n       MockCanvas::DrawCall{1,\n                            MockCanvas::DrawPathData{child_path, child_paint}},\n       MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}});\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls);\n}\n#endif\n\nTEST_F(OpacityLayerTest, FullyTransparentDoesNotCullPlatformView) {\n  const skity::Vec2 opacity_offset = skity::Vec2(0.5f, 1.5f);\n  const skity::Vec2 view_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 view_size = skity::Vec2(8.0f, 8.0f);\n  const int64_t view_id = 42;\n  auto platform_view =\n      std::make_shared<PlatformViewLayer>(view_offset, view_size, view_id);\n\n  auto opacity =\n      std::make_shared<OpacityLayer>(SK_AlphaTRANSPARENT, opacity_offset);\n  opacity->Add(platform_view);\n\n  CompositorState compositor_state({64, 64});\n  preroll_context()->compositor_state = &compositor_state;\n  paint_context().compositor_state = &compositor_state;\n\n  opacity->Preroll(preroll_context());\n  // cspell:words prerolled\n  EXPECT_EQ(compositor_state.GetCompositionOrder(),\n            std::vector<int64_t>({view_id}));\n\n  opacity->Paint(paint_context());\n  EXPECT_EQ(paint_context().canvas,\n            compositor_state.GetSlices()[view_id]->canvas());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/performance_overlay_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/performance_overlay_layer.h\"\n\n#include <iomanip>\n#include <iostream>\n#include <string>\n\nnamespace clay {\nnamespace {\n\nvoid VisualizeStopWatch(clay::GrCanvas* canvas, const Stopwatch& stopwatch,\n                        float x, float y, float width, float height,\n                        bool show_graph, bool show_labels,\n                        const std::string& label_prefix,\n                        const std::string& font_path) {\n  const int label_x = 8;    // distance from x\n  const int label_y = -10;  // distance from y+height\n\n  if (show_graph) {\n    skity::Rect visualization_rect = skity::Rect::MakeXYWH(x, y, width, height);\n    stopwatch.Visualize(canvas, visualization_rect);\n  }\n  if (show_labels) {\n    auto text = PerformanceOverlayLayer::MakeStatisticsText(\n        stopwatch, label_prefix, font_path);\n    clay::GrPaint paint;\n    PAINT_SET_COLOR(paint, clay::Color::kRed());\n    CANVAS_DRAW_TEXTBLOB(text, x + label_x, y + height + label_y, paint);\n  }\n}\n\n}  // namespace\n\nclay::GrTextBlobPtr PerformanceOverlayLayer::MakeStatisticsText(\n    const Stopwatch& stopwatch, const std::string& label_prefix,\n    const std::string& font_path) {\n  double max_ms_per_frame = stopwatch.MaxDelta().ToMillisecondsF();\n  double average_ms_per_frame = stopwatch.AverageDelta().ToMillisecondsF();\n  std::stringstream stream;\n  stream.setf(std::ios::fixed | std::ios::showpoint);\n  stream << std::setprecision(1);\n  stream << label_prefix << \"  \"\n         << \"max \" << max_ms_per_frame << \" ms/frame, \"\n         << \"avg \" << average_ms_per_frame << \" ms/frame\";\n  auto text = stream.str();\n#ifndef ENABLE_SKITY\n  SkFont font;\n  if (font_path != \"\") {\n    font = SkFont(SkTypeface::MakeFromFile(font_path.c_str()));\n  }\n  font.setSize(18);\n  return SkTextBlob::MakeFromText(text.c_str(), text.size(), font,\n                                  SkTextEncoding::kUTF8);\n#else\n  skity::Paint paint;\n  if (font_path != \"\") {\n    paint.SetTypeface(skity::Typeface::MakeFromFile(font_path.c_str()));\n  }\n  paint.SetTextSize(18);\n  return skity::TextBlobBuilder().BuildTextBlob(text.c_str(), paint);\n#endif  // ENABLE_SKITY\n}\n\nPerformanceOverlayLayer::PerformanceOverlayLayer(uint64_t options,\n                                                 const char* font_path)\n    : options_(options) {\n  if (font_path != nullptr) {\n    font_path_ = font_path;\n  }\n}\n\nvoid PerformanceOverlayLayer::Diff(DiffContext* context,\n                                   const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(old_layer);\n    auto prev = old_layer->as_performance_overlay_layer();\n    context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(prev));\n  }\n  context->AddLayerBounds(paint_bounds());\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid PerformanceOverlayLayer::Paint(PaintContext& context) const {\n  const int padding = 8;\n\n  if (!options_) {\n    return;\n  }\n\n  float x = paint_bounds().X() + padding;\n  float y = paint_bounds().Y() + padding;\n  float width = paint_bounds().Width() - (padding * 2);\n  float height = paint_bounds().Height() / 2;\n  auto mutator = context.state_stack.save();\n\n  VisualizeStopWatch(\n      context.canvas, context.raster_time, x, y, width, height - padding,\n      options_ & kVisualizeRasterizerStatistics,\n      options_ & kDisplayRasterizerStatistics, \"Raster\", font_path_);\n\n  VisualizeStopWatch(context.canvas, context.ui_time, x, y + height, width,\n                     height - padding, options_ & kVisualizeEngineStatistics,\n                     options_ & kDisplayEngineStatistics, \"UI\", font_path_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/performance_overlay_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PERFORMANCE_OVERLAY_LAYER_H_\n#define CLAY_FLOW_LAYERS_PERFORMANCE_OVERLAY_LAYER_H_\n\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/stopwatch.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nclass SkTextBlob;\n\nnamespace clay {\n\nconst int kDisplayRasterizerStatistics = 1 << 0;\nconst int kVisualizeRasterizerStatistics = 1 << 1;\nconst int kDisplayEngineStatistics = 1 << 2;\nconst int kVisualizeEngineStatistics = 1 << 3;\n\nclass PerformanceOverlayLayer : public Layer {\n public:\n  static clay::GrTextBlobPtr MakeStatisticsText(const Stopwatch& stopwatch,\n                                                const std::string& label_prefix,\n                                                const std::string& font_path);\n\n  bool IsReplacing(DiffContext* context, const Layer* layer) const override {\n    return layer->as_performance_overlay_layer() != nullptr;\n  }\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  const PerformanceOverlayLayer* as_performance_overlay_layer() const override {\n    return this;\n  }\n\n  explicit PerformanceOverlayLayer(uint64_t options,\n                                   const char* font_path = nullptr);\n\n  void Preroll(PrerollContext* context) override {}\n  void Paint(PaintContext& context) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"PerformanceOverlayLayer\"; }\n#endif\n\n private:\n  int options_;\n  std::string font_path_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PerformanceOverlayLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PERFORMANCE_OVERLAY_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/performance_overlay_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstdint>\n#include <sstream>\n\n#include \"clay/flow/flow_test_utils.h\"\n#include \"clay/flow/layers/performance_overlay_layer.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/skia/include/core/SkData.h\"\n#include \"third_party/skia/include/core/SkSerialProcs.h\"\n#include \"third_party/skia/include/core/SkStream.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n#include \"third_party/skia/include/core/SkTextBlob.h\"\n#include \"third_party/skia/include/utils/SkBase64.h\"\n\nnamespace clay {\nnamespace testing {\nnamespace {\n\n// To get the size of kMockedTimes in compile time.\ntemplate <class T, std::size_t N>\nconstexpr int size(const T (&array)[N]) noexcept {\n  return N;\n}\n\nconstexpr int kMockedTimes[] = {17, 1,  4,  24, 4,  25, 30, 4,  13, 34,\n                                14, 0,  18, 9,  32, 36, 26, 23, 5,  8,\n                                32, 18, 29, 16, 29, 18, 0,  36, 33, 10};\n\nstatic std::string GetGoldenFilePath(int refresh_rate, bool is_new) {\n  std::stringstream ss;\n  // This unit test should only be run on Linux (not even on Mac since it's a\n  // golden test). Hence we don't have to worry about the \"/\" vs. \"\\\".\n  ss << clay::GetGoldenDir() << \"/\"\n     << \"performance_overlay_gold_\" << refresh_rate << \"fps\"\n     << (is_new ? \"_new\" : \"\") << \".png\";\n  return ss.str();\n}\n\nstatic void TestPerformanceOverlayLayerGold(int refresh_rate) {\n  std::string golden_file_path = GetGoldenFilePath(refresh_rate, false);\n  std::string new_golden_file_path = GetGoldenFilePath(refresh_rate, true);\n\n  FixedRefreshRateStopwatch mock_stopwatch(\n      fml::RefreshRateToFrameBudget(refresh_rate));\n  for (int i = 0; i < size(kMockedTimes); ++i) {\n    mock_stopwatch.SetLapTime(\n        fml::TimeDelta::FromMilliseconds(kMockedTimes[i]));\n  }\n\n  const SkImageInfo image_info = SkImageInfo::MakeN32Premul(1000, 1000);\n  sk_sp<SkSurface> surface = SkSurface::MakeRaster(image_info);\n\n  ASSERT_TRUE(surface != nullptr);\n\n  LayerStateStack state_stack;\n  clay::PaintContext paint_context = {\n      // clang-format off\n      .state_stack                   = state_stack,\n      .canvas                        = surface->getCanvas(),\n      .gr_context                    = nullptr,\n      .compositor_state              = nullptr,\n      .raster_time                   = mock_stopwatch,\n      .ui_time                       = mock_stopwatch,\n      .drawable_image_registry       = nullptr,\n      .raster_cache                  = nullptr,\n      .frame_device_pixel_ratio      = 1.0f,\n      // clang-format on\n  };\n\n  // Specify font file to ensure the same font across different operation\n  // systems.\n  clay::PerformanceOverlayLayer layer(clay::kDisplayRasterizerStatistics |\n                                          clay::kVisualizeRasterizerStatistics |\n                                          clay::kDisplayEngineStatistics |\n                                          clay::kVisualizeEngineStatistics,\n                                      clay::GetFontFile().c_str());\n  layer.set_paint_bounds(skity::Rect::MakeWH(1000, 400));\n  surface->getCanvas()->clear(SK_ColorTRANSPARENT);\n  layer.Paint(paint_context);\n\n  sk_sp<SkImage> snapshot = surface->makeImageSnapshot();\n  sk_sp<SkData> snapshot_data = snapshot->encodeToData();\n\n  sk_sp<SkData> golden_data =\n      SkData::MakeFromFileName(golden_file_path.c_str());\n  EXPECT_TRUE(golden_data != nullptr)\n      << \"Golden file not found: \" << golden_file_path << \".\\n\"\n      << \"Please either set --golden-dir, or make sure that the unit test is \"\n      << \"run from the right directory (e.g., flutter/engine/src).\";\n\n  // TODO(https://github.com/flutter/flutter/issues/53784): enable this on all\n  // platforms.\n#if !defined(OS_LINUX)\n  GTEST_SKIP() << \"Skipping golden tests on non-Linux OSes\";\n#endif  // OS_LINUX\n  const bool golden_data_matches = golden_data->equals(snapshot_data.get());\n  if (!golden_data_matches) {\n    // cspell:words wstream FILEW\n    SkFILEWStream wstream(new_golden_file_path.c_str());\n    wstream.write(snapshot_data->data(), snapshot_data->size());\n    wstream.flush();\n\n    size_t b64_size =\n        SkBase64::Encode(snapshot_data->data(), snapshot_data->size(), nullptr);\n    sk_sp<SkData> b64_data = SkData::MakeUninitialized(b64_size + 1);\n    char* b64_char = static_cast<char*>(b64_data->writable_data());\n    SkBase64::Encode(snapshot_data->data(), snapshot_data->size(), b64_char);\n    b64_char[b64_size] = 0;  // make it null terminated for printing\n\n    EXPECT_TRUE(golden_data_matches)\n        << \"Golden file mismatch. Please check \"\n        << \"the difference between \" << golden_file_path << \" and \"\n        << new_golden_file_path << \", and  replace the former \"\n        << \"with the latter if the difference looks good.\\nS\\n\"\n        << \"See also the base64 encoded \" << new_golden_file_path << \":\\n\"\n        << b64_char;\n  }\n}\n\n}  // namespace\n\nusing PerformanceOverlayLayerTest = LayerTest;\n\nTEST_F(PerformanceOverlayLayerTest, PaintingEmptyLayerDies) {\n  const uint64_t overlay_opts = kVisualizeRasterizerStatistics;\n  auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  // Crashes reading a nullptr.\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), \"\");\n}\n\nTEST_F(PerformanceOverlayLayerTest, InvalidOptions) {\n  const skity::Rect layer_bounds =\n      skity::Rect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f);\n  const uint64_t overlay_opts = 0;\n  auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);\n\n  // TODO(): Note calling code has to call set_paint_bounds right now.  Make\n  // this a constructor parameter and move the set_paint_bounds into Preroll\n  layer->set_paint_bounds(layer_bounds);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), layer_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n\n  // Nothing is drawn if options are invalid (0).\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(), std::vector<MockCanvas::DrawCall>());\n}\n\nTEST_F(PerformanceOverlayLayerTest, SimpleRasterizerStatistics) {\n  const skity::Rect layer_bounds =\n      skity::Rect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f);\n  const uint64_t overlay_opts = kDisplayRasterizerStatistics;\n  auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);\n\n  // TODO(): Note calling code has to call set_paint_bounds right now.  Make\n  // this a constructor parameter and move the set_paint_bounds into Preroll\n  layer->set_paint_bounds(layer_bounds);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), layer_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n\n  layer->Paint(paint_context());\n  auto overlay_text = PerformanceOverlayLayer::MakeStatisticsText(\n      paint_context().raster_time, \"Raster\", \"\");\n  auto overlay_text_data = overlay_text->serialize(SkSerialProcs{});\n  SkPaint text_paint;\n  text_paint.setColor(SK_ColorRED);\n  SkPoint text_position = SkPoint::Make(16.0f, 22.0f);\n\n  // TODO(https://github.com/flutter/flutter/issues/82202): Remove once the\n  // performance overlay can use Fuchsia's font manager instead of the empty\n  // default.\n#if defined(OS_FUCHSIA)\n  GTEST_SKIP() << \"Expectation requires a valid default font manager\";\n#endif  // OS_FUCHSIA\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({MockCanvas::DrawCall{\n                0, MockCanvas::DrawTextData{overlay_text_data, text_paint,\n                                            text_position}}}));\n}\n\nTEST_F(PerformanceOverlayLayerTest, MarkAsDirtyWhenResized) {\n  // Regression test for https://github.com/flutter/flutter/issues/54188\n\n  // Create a PerformanceOverlayLayer.\n  const uint64_t overlay_opts = kVisualizeRasterizerStatistics;\n  auto layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);\n  layer->set_paint_bounds(skity::Rect::MakeLTRB(0.0f, 0.0f, 48.0f, 48.0f));\n  layer->Preroll(preroll_context());\n  layer->Paint(paint_context());\n  auto data = mock_canvas().draw_calls().front().data;\n  auto image_data = std::get<MockCanvas::DrawImageDataNoPaint>(data);\n  auto first_draw_width = image_data.image->width();\n\n  // Create a second PerformanceOverlayLayer with different bounds.\n  layer = std::make_shared<PerformanceOverlayLayer>(overlay_opts);\n  layer->set_paint_bounds(skity::Rect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f));\n  layer->Preroll(preroll_context());\n  layer->Paint(paint_context());\n  data = mock_canvas().draw_calls().back().data;\n  image_data = std::get<MockCanvas::DrawImageDataNoPaint>(data);\n  auto refreshed_draw_width = image_data.image->width();\n\n  EXPECT_NE(first_draw_width, refreshed_draw_width);\n}\n\nTEST(PerformanceOverlayLayerDefault, Gold) {\n  TestPerformanceOverlayLayerGold(60);\n}\n\nTEST(PerformanceOverlayLayer90fps, Gold) {\n  TestPerformanceOverlayLayerGold(90);\n}\n\nTEST(PerformanceOverlayLayer120fps, Gold) {\n  TestPerformanceOverlayLayerGold(120);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/physical_shape_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/physical_shape_layer.h\"\n\n#include \"clay/flow/paint_utils.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nnamespace {\n#ifndef ENABLE_SKITY\nconst SkScalar kLightHeight = 600;\nconst SkScalar kLightRadius = 800;\n\nSkRect ComputeShadowBounds(const SkPath& path, float elevation, SkScalar dpr,\n                           const SkMatrix& ctm) {\n  SkRect shadow_bounds(path.getBounds());\n  SkShadowUtils::GetLocalBounds(\n      ctm, path, SkPoint3::Make(0, 0, dpr * elevation),\n      SkPoint3::Make(0, -1, 1), kLightRadius / kLightHeight,\n      SkShadowFlags::kDirectionalLight_ShadowFlag, &shadow_bounds);\n  return shadow_bounds;\n}\n\nvoid DrawShadow(SkCanvas* canvas, const SkPath& path, clay::Color color,\n                float elevation, bool transparentOccluder, SkScalar dpr) {\n  const SkScalar kAmbientAlpha = 0.039f;\n  const SkScalar kSpotAlpha = 0.25f;\n\n  uint32_t flags = transparentOccluder\n                       ? SkShadowFlags::kTransparentOccluder_ShadowFlag\n                       : SkShadowFlags::kNone_ShadowFlag;\n  flags |= SkShadowFlags::kDirectionalLight_ShadowFlag;\n  SkColor in_ambient = SkColorSetA(color, kAmbientAlpha * SkColorGetA(color));\n  SkColor in_spot = SkColorSetA(color, kSpotAlpha * SkColorGetA(color));\n  SkColor ambient_color, spot_color;\n  SkShadowUtils::ComputeTonalColors(in_ambient, in_spot, &ambient_color,\n                                    &spot_color);\n  SkShadowUtils::DrawShadow(canvas, path, SkPoint3::Make(0, 0, dpr * elevation),\n                            SkPoint3::Make(0, -1, 1),\n                            kLightRadius / kLightHeight, ambient_color,\n                            spot_color, flags);\n}\n#endif  // ENABLE_SKITY\n}  // namespace\n\nPhysicalShapeLayer::PhysicalShapeLayer(clay::GrColor color,\n                                       clay::GrColor shadow_color,\n                                       float elevation,\n                                       const clay::GrPath& path,\n                                       Clip clip_behavior)\n    : color_(color),\n      shadow_color_(shadow_color),\n      elevation_(elevation),\n      path_(path),\n      clip_behavior_(clip_behavior) {}\n\nvoid PhysicalShapeLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  auto* prev = static_cast<const PhysicalShapeLayer*>(old_layer);\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(prev);\n    if (color_ != prev->color_ || shadow_color_ != prev->shadow_color_ ||\n        elevation_ != prev->elevation() || path_ != prev->path_ ||\n        clip_behavior_ != prev->clip_behavior_) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n\n  skity::Rect bounds;\n#ifndef ENABLE_SKITY\n  if (elevation_ == 0) {\n    bounds = clay::ConvertSkRectToSkityRect(path_.getBounds());\n  } else {\n    bounds = clay::ConvertSkRectToSkityRect(ComputeShadowBounds(\n        path_, elevation_, context->frame_device_pixel_ratio(),\n        clay::ConvertSkityMatrixToSkMatrix(context->GetTransform())));\n  }\n#else\n  if (elevation_ == 0) {\n    auto skity_bounds = path_.GetBounds();\n    bounds = skity::Rect::MakeXYWH(skity_bounds.X(), skity_bounds.Y(),\n                                   skity_bounds.Width(), skity_bounds.Height());\n  } else {\n    // TODO(zhangxiao.ninja) implement shadow bounds later\n    FML_DCHECK(false);\n  }\n#endif  // ENABLE_SKITY\n\n  context->AddLayerBounds(bounds);\n\n  // Only push cull rect if there is clip.\n  if (clip_behavior_ == Clip::none || context->PushCullRect(bounds)) {\n    DiffChildren(context, prev);\n  }\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid PhysicalShapeLayer::Preroll(PrerollContext* context) {\n  Layer::AutoPrerollSaveLayerState save =\n      Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());\n\n  skity::Rect child_paint_bounds = skity::Rect::MakeEmpty();\n  PrerollChildren(context, &child_paint_bounds);\n  context->renderable_state_flags =\n      UsesSaveLayer() ? Layer::kSaveLayerRenderFlags : 0;\n\n  skity::Rect paint_bounds;\n#ifndef ENABLE_SKITY\n  if (elevation_ == 0) {\n    paint_bounds = clay::ConvertSkRectToSkityRect(path_.getBounds());\n  } else {\n    // We will draw the shadow in Paint(), so add some margin to the paint\n    // bounds to leave space for the shadow.\n    paint_bounds = clay::ConvertSkRectToSkityRect(ComputeShadowBounds(\n        path_, elevation_, context->frame_device_pixel_ratio,\n        clay::ConvertSkityMatrixToSkMatrix(\n            context->state_stack.transform_4x4())));\n  }\n#else\n  if (elevation_ == 0) {\n    auto skity_bounds = path_.GetBounds();\n    paint_bounds =\n        skity::Rect::MakeXYWH(skity_bounds.X(), skity_bounds.Y(),\n                              skity_bounds.Width(), skity_bounds.Height());\n  } else {\n    // TODO(zhangxiao.ninja) implement shadow bounds later\n    FML_DCHECK(false);\n  }\n#endif  // ENABLE_SKITY\n\n  if (clip_behavior_ == Clip::none) {\n    paint_bounds.Join(child_paint_bounds);\n  }\n\n  set_paint_bounds(paint_bounds);\n}\n\nvoid PhysicalShapeLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n\n#ifndef ENABLE_SKITY\n  if (elevation_ != 0) {\n    DrawShadow(context.canvas, path_, shadow_color_, elevation_,\n               SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio);\n  }\n#endif  // ENABLE_SKITY\n\n  // Call drawPath without clip if possible for better performance.\n  clay::GrPaint paint;\n  PAINT_SET_COLOR(paint, color_);\n  PAINT_SET_ANTI_ALIAS(paint, true);\n  if (clip_behavior_ != Clip::antiAliasWithSaveLayer) {\n    CANVAS_DRAW_PATH(context.canvas, path_, paint);\n  }\n\n  auto mutator = context.state_stack.save();\n  switch (clip_behavior_) {\n    case Clip::hardEdge:\n      mutator.clipPath(path_, false);\n      break;\n    case Clip::antiAlias:\n      mutator.clipPath(path_, true);\n      break;\n    case Clip::antiAliasWithSaveLayer: {\n      TRACE_EVENT(\"clay\", \"Canvas::saveLayer\");\n      mutator.clipPath(path_, true);\n      mutator.saveLayer(paint_bounds());\n    } break;\n    case Clip::none:\n      break;\n  }\n\n  if (UsesSaveLayer()) {\n    // If we want to avoid the bleeding edge artifact\n    // (https://github.com/flutter/flutter/issues/18057#issue-328003931)\n    // using saveLayer, we have to call drawPaint instead of drawPath as\n    // anti-aliased drawPath will always have such artifacts.\n    CANVAS_DRAW_PAINT(context.canvas, paint);\n  }\n\n  PaintChildren(context);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/physical_shape_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PHYSICAL_SHAPE_LAYER_H_\n#define CLAY_FLOW_LAYERS_PHYSICAL_SHAPE_LAYER_H_\n\n#include <string>\n\n#include \"clay/flow/layers/container_layer.h\"\n\nnamespace clay {\n\nclass PhysicalShapeLayer : public ContainerLayer {\n public:\n  PhysicalShapeLayer(clay::GrColor color, clay::GrColor shadow_color,\n                     float elevation, const clay::GrPath& path,\n                     Clip clip_behavior);\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  void Preroll(PrerollContext* context) override;\n\n  void Paint(PaintContext& context) const override;\n\n  bool UsesSaveLayer() const {\n    return clip_behavior_ == Clip::antiAliasWithSaveLayer;\n  }\n\n  float elevation() const { return elevation_; }\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"PhysicalShapeLayer\"; }\n#endif\n\n private:\n  clay::GrColor color_;\n  clay::GrColor shadow_color_;\n  float elevation_ = 0.0f;\n  clay::GrPath path_;\n  Clip clip_behavior_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PHYSICAL_SHAPE_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/physical_shape_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/physical_shape_layer.h\"\n#include \"clay/flow/testing/diff_context_test.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/skia/include/utils/SkShadowUtils.h\"\n\nnamespace clay {\nnamespace testing {\n\nnamespace {\nconst SkScalar kLightHeight = 600;\nconst SkScalar kLightRadius = 800;\nSkRect ComputeShadowBounds(const SkPath& path, float elevation, SkScalar dpr,\n                           const SkMatrix& ctm) {\n  SkRect shadow_bounds(path.getBounds());\n  SkShadowUtils::GetLocalBounds(\n      ctm, path, SkPoint3::Make(0, 0, dpr * elevation),\n      SkPoint3::Make(0, -1, 1), kLightRadius / kLightHeight,\n      SkShadowFlags::kDirectionalLight_ShadowFlag, &shadow_bounds);\n  return shadow_bounds;\n}\n\nvoid DrawShadow(SkCanvas* canvas, const SkPath& path, clay::Color color,\n                float elevation, bool transparentOccluder, SkScalar dpr) {\n  const SkScalar kAmbientAlpha = 0.039f;\n  const SkScalar kSpotAlpha = 0.25f;\n\n  uint32_t flags = transparentOccluder\n                       ? SkShadowFlags::kTransparentOccluder_ShadowFlag\n                       : SkShadowFlags::kNone_ShadowFlag;\n  flags |= SkShadowFlags::kDirectionalLight_ShadowFlag;\n  SkColor in_ambient = SkColorSetA(color, kAmbientAlpha * SkColorGetA(color));\n  SkColor in_spot = SkColorSetA(color, kSpotAlpha * SkColorGetA(color));\n  SkColor ambient_color, spot_color;\n  SkShadowUtils::ComputeTonalColors(in_ambient, in_spot, &ambient_color,\n                                    &spot_color);\n  SkShadowUtils::DrawShadow(canvas, path, SkPoint3::Make(0, 0, dpr * elevation),\n                            SkPoint3::Make(0, -1, 1),\n                            kLightRadius / kLightHeight, ambient_color,\n                            spot_color, flags);\n}\n}  // namespace\n\nusing PhysicalShapeLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(PhysicalShapeLayerTest, PaintingEmptyLayerDies) {\n  auto layer =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorBLACK, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           SkPath(), Clip::none);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_EQ(layer->child_paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(PhysicalShapeLayerTest, PaintBeforePrerollDies) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, SkPaint());\n  auto layer =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorBLACK, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           SkPath(), Clip::none);\n  layer->Add(mock_layer);\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(PhysicalShapeLayerTest, NonEmptyLayer) {\n  SkPath layer_path;\n  layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  auto layer =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           layer_path, Clip::none);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(layer_path.getBounds()));\n  EXPECT_EQ(layer->child_paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n\n  SkPaint layer_paint;\n  layer_paint.setColor(SK_ColorGREEN);\n  layer_paint.setAntiAlias(true);\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({MockCanvas::DrawCall{\n                0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));\n}\n\nTEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPathClip) {\n  SkPath layer_path;\n  layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPath child1_path;\n  child1_path.addRect(4, 0, 12, 12).close();\n  SkPath child2_path;\n  child2_path.addRect(3, 2, 5, 15).close();\n  auto child1 = std::make_shared<PhysicalShapeLayer>(SK_ColorRED, SK_ColorBLACK,\n                                                     0.0f,  // elevation\n                                                     child1_path, Clip::none);\n  auto child2 =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorBLUE, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           child2_path, Clip::none);\n  auto layer =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           layer_path, Clip::hardEdge);\n  layer->Add(child1);\n  layer->Add(child2);\n\n  skity::Rect child_paint_bounds = skity::Rect::MakeEmpty();\n  layer->Preroll(preroll_context());\n  child_paint_bounds.Join(child1->paint_bounds());\n  child_paint_bounds.Join(child2->paint_bounds());\n  EXPECT_EQ(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(layer_path.getBounds()));\n  EXPECT_NE(layer->paint_bounds(), child_paint_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_paint_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n\n  SkPaint layer_paint;\n  layer_paint.setColor(SK_ColorGREEN);\n  layer_paint.setAntiAlias(true);\n  SkPaint child1_paint;\n  child1_paint.setColor(SK_ColorRED);\n  child1_paint.setAntiAlias(true);\n  SkPaint child2_paint;\n  child2_paint.setColor(SK_ColorBLUE);\n  child2_paint.setAntiAlias(true);\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({\n                MockCanvas::DrawCall{\n                    0, MockCanvas::DrawPathData{layer_path, layer_paint}},\n                MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n                MockCanvas::DrawCall{\n                    1, MockCanvas::ClipRectData{layer_path.getBounds(),\n                                                SkClipOp::kIntersect}},\n                MockCanvas::DrawCall{\n                    1, MockCanvas::DrawPathData{child1_path, child1_paint}},\n                // Child 2 is rendered when using Skia as a state delegate\n                // because the quickReject tests are conservative.\n                MockCanvas::DrawCall{\n                    1, MockCanvas::DrawPathData{child2_path, child2_paint}},\n                MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}},\n            }));\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  auto canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    SkPaint paint;\n    paint.setColor(SK_ColorGREEN);\n    paint.setAntiAlias(true);\n    canvas->drawPath(layer_path, paint);\n    canvas->save();\n    {\n      canvas->clipPath(layer_path, SkClipOp::kIntersect, false);\n      {\n        SkPaint child_paint_1;\n        child_paint_1.setColor(SK_ColorRED);\n        child_paint_1.setAntiAlias(true);\n        canvas->drawPath(child1_path, child_paint_1);\n      }\n      {\n        SkPaint child_paint_2;\n        child_paint_2.setColor(SK_ColorBLUE);\n        child_paint_2.setAntiAlias(true);\n        canvas->drawPath(child2_path, child_paint_2);\n      }\n      canvas->restore();\n    }\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n  layer->Paint(display_list_paint_context());\n  EXPECT_EQ(display_list()->approximateOpCount(),\n            expected_display_list->approximateOpCount());\n}\n\nTEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPathNoClip) {\n  SkPath layer_path;\n  layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  SkPath child1_path;\n  child1_path.addRect(4, 0, 12, 12).close();\n  SkPath child2_path;\n  child2_path.addRect(3, 2, 5, 15).close();\n  auto child1 = std::make_shared<PhysicalShapeLayer>(SK_ColorRED, SK_ColorBLACK,\n                                                     0.0f,  // elevation\n                                                     child1_path, Clip::none);\n  auto child2 =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorBLUE, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           child2_path, Clip::none);\n  auto layer =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           layer_path, Clip::none);\n  layer->Add(child1);\n  layer->Add(child2);\n\n  layer->Preroll(preroll_context());\n  skity::Rect child_bounds = child1->paint_bounds();\n  child_bounds.Join(child2->paint_bounds());\n  skity::Rect total_bounds = child_bounds;\n  total_bounds.Join(clay::ConvertSkRectToSkityRect(layer_path.getBounds()));\n  EXPECT_NE(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(layer_path.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), total_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n\n  SkPaint layer_paint;\n  layer_paint.setColor(SK_ColorGREEN);\n  layer_paint.setAntiAlias(true);\n  SkPaint child1_paint;\n  child1_paint.setColor(SK_ColorRED);\n  child1_paint.setAntiAlias(true);\n  SkPaint child2_paint;\n  child2_paint.setColor(SK_ColorBLUE);\n  child2_paint.setAntiAlias(true);\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector({MockCanvas::DrawCall{\n                       0, MockCanvas::DrawPathData{layer_path, layer_paint}},\n                   MockCanvas::DrawCall{\n                       0, MockCanvas::DrawPathData{child1_path, child1_paint}},\n                   MockCanvas::DrawCall{0, MockCanvas::DrawPathData{\n                                               child2_path, child2_paint}}}));\n}\n\nTEST_F(PhysicalShapeLayerTest, ElevationSimple) {\n  constexpr float initial_elevation = 20.0f;\n  SkPath layer_path;\n  layer_path.addRect(0, 0, 8, 8).close();\n  auto layer = std::make_shared<PhysicalShapeLayer>(\n      SK_ColorGREEN, SK_ColorBLACK, initial_elevation, layer_path, Clip::none);\n\n  layer->Preroll(preroll_context());\n  // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and\n  // their shadows , so we do not do any painting there.\n  EXPECT_EQ(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(ComputeShadowBounds(\n                layer_path, initial_elevation, 1.0f, SkMatrix())));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(layer->elevation(), initial_elevation);\n\n  SkPaint layer_paint;\n  layer_paint.setColor(SK_ColorGREEN);\n  layer_paint.setAntiAlias(true);\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},\n           MockCanvas::DrawCall{\n               0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));\n}\n\nTEST_F(PhysicalShapeLayerTest, ElevationComplex) {\n  // The layer tree should look like this:\n  // layers[0] +1.0f = 1.0f\n  // |       \\\n  // |        \\\n  // |         \\\n  // |       layers[2] +3.0f = 4.0f\n  // |          |\n  // |       layers[3] +4.0f = 8.0f\n  // |\n  // |\n  // layers[1] + 2.0f = 3.0f\n  constexpr float initial_elevations[4] = {1.0f, 2.0f, 3.0f, 4.0f};\n  SkPath layer_path;\n  layer_path.addRect(0, 0, 80, 80).close();\n\n  std::shared_ptr<PhysicalShapeLayer> layers[4];\n  for (int i = 0; i < 4; i += 1) {\n    layers[i] = std::make_shared<PhysicalShapeLayer>(\n        SK_ColorBLACK, SK_ColorBLACK, initial_elevations[i], layer_path,\n        Clip::none);\n  }\n  layers[0]->Add(layers[1]);\n  layers[0]->Add(layers[2]);\n  layers[2]->Add(layers[3]);\n\n  layers[0]->Preroll(preroll_context());\n  for (int i = 0; i < 4; i += 1) {\n    // On Fuchsia, the system compositor handles all elevated\n    // PhysicalShapeLayers and their shadows , so we do not do any painting\n    // there.\n    SkRect paint_bounds = ComputeShadowBounds(\n        layer_path, initial_elevations[i], 1.0f /* pixel_ratio */, SkMatrix());\n\n    // Without clipping the children will be painted as well\n    for (auto layer : layers[i]->layers()) {\n      paint_bounds.join(clay::ConvertSkityRectToSkRect(layer->paint_bounds()));\n    }\n    EXPECT_EQ(layers[i]->paint_bounds(),\n              clay::ConvertSkRectToSkityRect(paint_bounds));\n    EXPECT_TRUE(layers[i]->needs_painting(paint_context()));\n  }\n\n  SkPaint layer_paint;\n  layer_paint.setColor(SK_ColorBLACK);\n  layer_paint.setAntiAlias(true);\n  layers[0]->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},\n           MockCanvas::DrawCall{\n               0, MockCanvas::DrawPathData{layer_path, layer_paint}},\n           MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},\n           MockCanvas::DrawCall{\n               0, MockCanvas::DrawPathData{layer_path, layer_paint}},\n           MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},\n           MockCanvas::DrawCall{\n               0, MockCanvas::DrawPathData{layer_path, layer_paint}},\n           MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}},\n           MockCanvas::DrawCall{\n               0, MockCanvas::DrawPathData{layer_path, layer_paint}}}));\n}\n\nTEST_F(PhysicalShapeLayerTest, ShadowNotDependsCtm) {\n  constexpr SkScalar elevations[] = {1, 2, 3, 4, 5, 10};\n  constexpr SkScalar scales[] = {0.5, 1, 1.5, 2, 3, 5};\n  constexpr SkScalar translates[] = {0, 1, -1, 0.5, 2, 10};\n\n  SkPath path;\n  path.addRect(0, 0, 8, 8).close();\n\n  for (SkScalar elevation : elevations) {\n    SkRect baseline_bounds =\n        ComputeShadowBounds(path, elevation, 1.0f, SkMatrix());\n    for (SkScalar scale : scales) {\n      for (SkScalar translate_x : translates) {\n        for (SkScalar translate_y : translates) {\n          SkMatrix ctm;\n          ctm.setScaleTranslate(scale, scale, translate_x, translate_y);\n          SkRect bounds = ComputeShadowBounds(path, elevation, scale, ctm);\n          EXPECT_FLOAT_EQ(bounds.fLeft, baseline_bounds.fLeft);\n          EXPECT_FLOAT_EQ(bounds.fTop, baseline_bounds.fTop);\n          EXPECT_FLOAT_EQ(bounds.fRight, baseline_bounds.fRight);\n          EXPECT_FLOAT_EQ(bounds.fBottom, baseline_bounds.fBottom);\n        }\n      }\n    }\n  }\n}\n\nstatic int RasterizedDifferenceInPixels(\n    const std::function<void(SkCanvas*)>& actual_draw_function,\n    const std::function<void(SkCanvas*)>& expected_draw_function,\n    const SkSize& canvas_size) {\n  sk_sp<SkSurface> actual_surface =\n      SkSurface::MakeRasterN32Premul(canvas_size.width(), canvas_size.height());\n  sk_sp<SkSurface> expected_surface =\n      SkSurface::MakeRasterN32Premul(canvas_size.width(), canvas_size.height());\n\n  actual_surface->getCanvas()->drawColor(SK_ColorWHITE);\n  expected_surface->getCanvas()->drawColor(SK_ColorWHITE);\n\n  actual_draw_function(actual_surface->getCanvas());\n  expected_draw_function(expected_surface->getCanvas());\n\n  SkPixmap actual_pixels;\n  EXPECT_TRUE(actual_surface->peekPixels(&actual_pixels));\n\n  SkPixmap expected_pixels;\n  EXPECT_TRUE(expected_surface->peekPixels(&expected_pixels));\n\n  int different_pixels = 0;\n  for (int y = 0; y < canvas_size.height(); y++) {\n    const uint32_t* actual_row = actual_pixels.addr32(0, y);\n    const uint32_t* expected_row = expected_pixels.addr32(0, y);\n    for (int x = 0; x < canvas_size.width(); x++) {\n      if (actual_row[x] != expected_row[x]) {\n        different_pixels++;\n      }\n    }\n  }\n  return different_pixels;\n}\n\nTEST_F(PhysicalShapeLayerTest, ShadowNotDependsPathSize) {\n  constexpr SkRect test_cases[][2] = {\n      {{20, -100, 80, 80}, {20, -1000, 80, 80}},\n      {{20, 20, 80, 200}, {20, 20, 80, 2000}},\n  };\n\n  for (const SkRect* test_case : test_cases) {\n    EXPECT_EQ(RasterizedDifferenceInPixels(\n                  [=](SkCanvas* canvas) {\n                    SkPath path;\n                    path.addRect(test_case[0]).close();\n                    DrawShadow(canvas, path, SK_ColorBLACK, 1.0f, false, 1.0f);\n                  },\n                  [=](SkCanvas* canvas) {\n                    SkPath path;\n                    path.addRect(test_case[1]).close();\n                    DrawShadow(canvas, path, SK_ColorBLACK, 1.0f, false, 1.0f);\n                  },\n                  SkSize::Make(100, 100)),\n              0);\n  }\n}\n\nstatic bool ReadbackResult(PrerollContext* context, Clip clip_behavior,\n                           const std::shared_ptr<Layer>& child, bool before) {\n  const skity::Rect layer_bounds = skity::Rect::MakeXYWH(0.5, 1.0, 5.0, 6.0);\n  const SkPath layer_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(layer_bounds));\n  auto layer =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           layer_path, clip_behavior);\n  if (child != nullptr) {\n    layer->Add(child);\n  }\n  context->surface_needs_readback = before;\n  layer->Preroll(context);\n  return context->surface_needs_readback;\n}\n\nTEST_F(PhysicalShapeLayerTest, Readback) {\n  PrerollContext* context = preroll_context();\n  SkPath path;\n  SkPaint paint;\n\n  const Clip hard = Clip::hardEdge;\n  const Clip soft = Clip::antiAlias;\n  const Clip save_layer = Clip::antiAliasWithSaveLayer;\n\n  std::shared_ptr<MockLayer> nochild;\n  auto reader = std::make_shared<MockLayer>(path, paint);\n  reader->set_fake_reads_surface(true);\n  auto nonreader = std::make_shared<MockLayer>(path, paint);\n\n  // No children, no prior readback -> no readback after\n  EXPECT_FALSE(ReadbackResult(context, hard, nochild, false));\n  EXPECT_FALSE(ReadbackResult(context, soft, nochild, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false));\n\n  // No children, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, nochild, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, nochild, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true));\n\n  // Non readback child, no prior readback -> no readback after\n  EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false));\n  EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false));\n\n  // Non readback child, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true));\n\n  // Readback child, no prior readback -> readback after unless SaveLayer\n  EXPECT_TRUE(ReadbackResult(context, hard, reader, false));\n  EXPECT_TRUE(ReadbackResult(context, soft, reader, false));\n  EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false));\n\n  // Readback child, prior readback -> readback after\n  EXPECT_TRUE(ReadbackResult(context, hard, reader, true));\n  EXPECT_TRUE(ReadbackResult(context, soft, reader, true));\n  EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));\n}\n\nTEST_F(PhysicalShapeLayerTest, OpacityInheritance) {\n  SkPath layer_path;\n  layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  auto layer =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           layer_path, Clip::none);\n\n  PrerollContext* context = preroll_context();\n  context->renderable_state_flags = 0;\n  layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, 0);\n}\n\nusing PhysicalShapeLayerDiffTest = DiffContextTest;\n\nTEST_F(PhysicalShapeLayerDiffTest, NoClipPaintRegion) {\n  MockLayerTree tree1;\n  const SkPath layer_path = SkPath().addRect(SkRect::MakeXYWH(0, 0, 100, 100));\n  auto layer =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           layer_path, Clip::none);\n\n  const SkPath layer_path2 =\n      SkPath().addRect(SkRect::MakeXYWH(200, 200, 200, 200));\n  auto layer2 = std::make_shared<MockLayer>(layer_path2);\n  layer->Add(layer2);\n  tree1.root()->Add(layer);\n\n  auto damage = DiffLayerTree(tree1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 400, 400));\n}\n\nTEST_F(PhysicalShapeLayerDiffTest, ClipPaintRegion) {\n  MockLayerTree tree1;\n  const SkPath layer_path = SkPath().addRect(SkRect::MakeXYWH(0, 0, 100, 100));\n  auto layer =\n      std::make_shared<PhysicalShapeLayer>(SK_ColorGREEN, SK_ColorBLACK,\n                                           0.0f,  // elevation\n                                           layer_path, Clip::hardEdge);\n\n  const SkPath layer_path2 =\n      SkPath().addRect(SkRect::MakeXYWH(200, 200, 200, 200));\n  auto layer2 = std::make_shared<MockLayer>(layer_path2);\n  layer->Add(layer2);\n  tree1.root()->Add(layer);\n\n  auto damage = DiffLayerTree(tree1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 100, 100));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/picture_complexity.h\"\n\n#include \"clay/flow/layers/picture_complexity_gl.h\"\n#include \"clay/flow/layers/picture_complexity_metal.h\"\n\nnamespace clay {\n\nPictureNaiveComplexityCalculator* PictureNaiveComplexityCalculator::instance_ =\n    nullptr;\n\nPictureComplexityCalculator* PictureNaiveComplexityCalculator::GetInstance() {\n  if (instance_ == nullptr) {\n    instance_ = new PictureNaiveComplexityCalculator();\n  }\n  return instance_;\n}\n\nPictureComplexityCalculator* PictureComplexityCalculator::GetForBackend(\n    clay::GrGpuBackendType backend) {\n  switch (backend) {\n    case clay::GrGpuBackendType::kMetal:\n      return PictureMetalComplexityCalculator::GetInstance();\n    case clay::GrGpuBackendType::kOpenGL:\n      return PictureGLComplexityCalculator::GetInstance();\n    default:\n      return PictureNaiveComplexityCalculator::GetInstance();\n  }\n}\n\nPictureComplexityCalculator* PictureComplexityCalculator::GetForSoftware() {\n#ifndef ENABLE_SKITY\n  return PictureNaiveComplexityCalculator::GetInstance();\n#else\n  return PictureComplexityCalculatorSkity::GetInstance();\n#endif  // ENABLE_SKITY\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_H_\n#define CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_H_\n\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass PictureComplexityCalculator {\n public:\n  static PictureComplexityCalculator* GetForSoftware();\n\n  static PictureComplexityCalculator* GetForBackend(\n      clay::GrGpuBackendType backend);\n\n  virtual ~PictureComplexityCalculator() = default;\n\n  // Returns a calculated complexity score for a given DisplayList object\n#ifndef ENABLE_SKITY\n  virtual unsigned int Compute(SkPicture* picture) = 0;\n#else\n  virtual unsigned int Compute(skity::DisplayList* picture) = 0;\n#endif  // ENABLE_SKITY\n\n  // Returns whether a given complexity score meets the threshold for\n  // cachability for this particular ComplexityCalculator\n  virtual bool ShouldBeCached(unsigned int complexity_score) = 0;\n\n  // Sets a ceiling for the complexity score being calculated. By default\n  // this is the largest number representable by an unsigned int.\n  //\n  // This setting has no effect on non-accumulator based scorers such as\n  // the Naive calculator.\n  virtual void SetComplexityCeiling(unsigned int ceiling) = 0;\n};\n\nclass PictureNaiveComplexityCalculator : public PictureComplexityCalculator {\n public:\n  static PictureComplexityCalculator* GetInstance();\n\n#ifndef ENABLE_SKITY\n  unsigned int Compute(SkPicture* picture) override {\n    return picture->approximateOpCount(true);\n  }\n#else\n  unsigned int Compute(skity::DisplayList* picture) override {\n    return picture->OpCount();\n  }\n#endif  // ENABLE_SKITY\n\n  bool ShouldBeCached(unsigned int complexity_score) override {\n#ifndef ENABLE_SKITY\n    return complexity_score > 5u;\n#else\n    // TODO(feiyue.1998): Maybe there is a better threshold than 200.\n    // Now it is set 200 so that simple displaylist will not be cached.\n    return complexity_score > 200u;\n#endif  // ENABLE_SKITY\n  }\n\n  void SetComplexityCeiling(unsigned int ceiling) override {}\n\n private:\n  PictureNaiveComplexityCalculator() {}\n  static PictureNaiveComplexityCalculator* instance_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_H_\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity_calculator_skity.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/picture_complexity_calculator_skity.h\"\n\nnamespace clay {\n\nusing SkityReplayHelper = PictureComplexityCalculatorSkity::SkityReplayHelper;\n\nPictureComplexityCalculatorSkity*\nPictureComplexityCalculatorSkity::GetInstance() {\n  static PictureComplexityCalculatorSkity instance;\n  return &instance;\n}\n\nbool SkityReplayHelper::IsPaintComplex(const skity::Paint& paint) const {\n  return paint.GetColorFilter() != nullptr ||\n         paint.GetMaskFilter() != nullptr ||\n         paint.GetImageFilter() != nullptr || paint.GetShader() != nullptr ||\n         paint.GetPathEffect() != nullptr;\n}\n\nvoid SkityReplayHelper::CheckPaint(const skity::Paint& paint) {\n  if (!is_complex_ && IsPaintComplex(paint)) {\n    is_complex_ = true;\n  }\n}\n\nvoid SkityReplayHelper::OnDrawGlyphs(uint32_t count,\n                                     const skity::GlyphID glyphs[],\n                                     const float position_x[],\n                                     const float position_y[],\n                                     const skity::Font& font,\n                                     const skity::Paint& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnDrawPaint(skity::Paint const& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnDrawLine(float x0, float y0, float x1, float y1,\n                                   skity::Paint const& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnDrawCircle(float cx, float cy, float radius,\n                                     skity::Paint const& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnDrawOval(skity::Rect const& oval,\n                                   skity::Paint const& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnDrawRect(skity::Rect const& rect,\n                                   skity::Paint const& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnDrawRRect(skity::RRect const& rrect,\n                                    skity::Paint const& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnDrawPath(skity::Path const& path,\n                                   skity::Paint const& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnSaveLayer(const skity::Rect& bounds,\n                                    const skity::Paint& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnDrawBlob(const skity::TextBlob* blob, float x,\n                                   float y, skity::Paint const& paint) {\n  CheckPaint(paint);\n}\n\nvoid SkityReplayHelper::OnDrawImageRect(std::shared_ptr<skity::Image> image,\n                                        const skity::Rect& src,\n                                        const skity::Rect& dst,\n                                        const skity::SamplingOptions& sampling,\n                                        skity::Paint const* paint) {\n  if (paint) {\n    CheckPaint(*paint);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity_calculator_skity.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_CALCULATOR_SKITY_H_\n#define CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_CALCULATOR_SKITY_H_\n\n#include <memory>\n\n#include \"clay/flow/layers/picture_complexity.h\"\n\nnamespace clay {\n\nclass PictureComplexityCalculatorSkity : public PictureComplexityCalculator {\n public:\n  static PictureComplexityCalculatorSkity* GetInstance();\n\n  void SetComplexityCeiling(unsigned int ceiling) override {}\n\n  class SkityReplayHelper : public skity::Canvas {\n   public:\n    SkityReplayHelper() = default;\n    ~SkityReplayHelper() override = default;\n    using ClipOp = skity::Canvas::ClipOp;\n    void OnClipRect(skity::Rect const& rect, ClipOp op) override {}\n    void OnClipPath(skity::Path const& path, ClipOp op) override {}\n\n    void OnSave() override {}\n    void OnRestore() override {}\n    void OnRestoreToCount(int saveCount) override {}\n    void OnFlush() override {}\n    uint32_t OnGetWidth() const override { return 0; }\n    uint32_t OnGetHeight() const override { return 0; };\n\n    void OnDrawGlyphs(uint32_t count, const skity::GlyphID glyphs[],\n                      const float position_x[], const float position_y[],\n                      const skity::Font& font,\n                      const skity::Paint& paint) override;\n\n    void OnDrawPaint(skity::Paint const& paint) override;\n\n    void OnDrawLine(float x0, float y0, float x1, float y1,\n                    skity::Paint const& paint) override;\n    void OnDrawCircle(float cx, float cy, float radius,\n                      skity::Paint const& paint) override;\n    void OnDrawOval(skity::Rect const& oval,\n                    skity::Paint const& paint) override;\n    void OnDrawRect(skity::Rect const& rect,\n                    skity::Paint const& paint) override;\n    void OnDrawRRect(skity::RRect const& rrect,\n                     skity::Paint const& paint) override;\n    void OnDrawPath(skity::Path const& path,\n                    skity::Paint const& paint) override;\n    void OnSaveLayer(const skity::Rect& bounds,\n                     const skity::Paint& paint) override;\n    void OnDrawBlob(const skity::TextBlob* blob, float x, float y,\n                    skity::Paint const& paint) override;\n    void OnDrawImageRect(std::shared_ptr<skity::Image> image,\n                         const skity::Rect& src, const skity::Rect& dst,\n                         const skity::SamplingOptions& sampling,\n                         skity::Paint const* paint) override;\n\n    bool IsComplex() const { return is_complex_; }\n\n   private:\n    bool IsPaintComplex(const skity::Paint& paint) const;\n    void CheckPaint(const skity::Paint& paint);\n\n    bool is_complex_ = false;\n  };\n\n  unsigned int Compute(skity::DisplayList* display_list) override {\n    auto op_count = display_list->OpCount();\n    SkityReplayHelper helper;\n    display_list->Draw(&helper);\n    return helper.IsComplex() ? kDefaultCacheThreshold : op_count;\n  }\n\n  bool ShouldBeCached(unsigned int complexity_score) override {\n    return complexity_score >= kDefaultCacheThreshold;\n  }\n\n private:\n  const unsigned int kDefaultCacheThreshold = 200u;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_CALCULATOR_SKITY_H_\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity_gl.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/picture_complexity_gl.h\"\n\n#include <algorithm>\n#include <cmath>\n\n// The numbers and weightings used in this file stem from taking the\n// data from the DisplayListBenchmarks suite run on an Pixel 4 and\n// applying very rough analysis on them to identify the approximate\n// trends.\n//\n// See the comments in picture_complexity_helper.h for details on the\n// process and rationale behind coming up with these numbers.\n\nnamespace clay {\n\nPictureGLComplexityCalculator* PictureGLComplexityCalculator::instance_ =\n    nullptr;\n\nPictureGLComplexityCalculator* PictureGLComplexityCalculator::GetInstance() {\n  if (instance_ == nullptr) {\n    instance_ = new PictureGLComplexityCalculator();\n  }\n  return instance_;\n}\n\n#ifndef ENABLE_SKITY\nunsigned int PictureGLComplexityCalculator::GLHelper::BatchedComplexity() {\n  // Calculate the impact of saveLayer.\n  unsigned int save_layer_complexity;\n  if (save_layer_count_ == 0) {\n    save_layer_complexity = 0;\n  } else {\n    // m = 1/5\n    // c = 10\n    save_layer_complexity = (save_layer_count_ + 50) * 40000;\n  }\n\n  unsigned int draw_text_blob_complexity;\n  if (draw_text_blob_count_ == 0) {\n    draw_text_blob_complexity = 0;\n  } else {\n    // m = 1/240\n    // c = 0.25\n    draw_text_blob_complexity = (draw_text_blob_count_ + 60) * 2500 / 3;\n  }\n\n  return save_layer_complexity + draw_text_blob_complexity;\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawRect(const SkRect& rect,\n                                                         const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  unsigned int complexity;\n\n  // If stroked, cost scales linearly with the rectangle width/height.\n  // If filled, it scales with the area.\n  //\n  // Hairline stroke vs non hairline has no significant penalty.\n  //\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style() == SkPaint::Style::kFill_Style) {\n    // No real difference for AA with filled styles\n    unsigned int area = rect.width() * rect.height();\n\n    // m = 1/3500\n    // c = 0\n    complexity = area * 2 / 175;\n  } else {\n    // Take the average of the width and height.\n    unsigned int length = (rect.width() + rect.height()) / 2;\n\n    if (IsAntiAliased()) {\n      // m = 1/30\n      // c = 0\n      complexity = length * 4 / 3;\n    } else {\n      // If AA is disabled, the data shows that at larger sizes the overall\n      // cost comes down, peaking at around 1000px. As we don't anticipate\n      // rasterising rects with AA disabled to be all that frequent, just treat\n      // it as a straight line that peaks at 1000px, beyond which it stays\n      // constant. The rationale here is that it makes more sense to\n      // overestimate than to start decreasing the cost as the length goes up.\n      //\n      // This should be a reasonable approximation as it doesn't decrease by\n      // much from 1000px to 2000px.\n      //\n      // m = 1/20\n      // c = 0\n      complexity = std::min(length, 1000u) * 2;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawOval(const SkRect& bounds,\n                                                         const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // DrawOval scales very roughly linearly with the bounding box width/height\n  // (not area) for stroked styles without AA.\n  //\n  // Filled styles and stroked styles with AA scale linearly with the bounding\n  // box area.\n  unsigned int area = bounds.width() * bounds.height();\n\n  unsigned int complexity;\n\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style() == SkPaint::Style::kFill_Style) {\n    // With filled styles, there is no significant AA penalty.\n    // m = 1/6000\n    // c = 0\n    complexity = area / 30;\n  } else {\n    if (IsAntiAliased()) {\n      // m = 1/4000\n      // c = 0\n      complexity = area / 20;\n    } else {\n      // Take the average of the width and height.\n      unsigned int length = (bounds.width() + bounds.height()) / 2;\n\n      // m = 1/75\n      // c = 0\n      complexity = length * 8 / 3;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawRRect(\n    const SkRRect& rrect, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  current_paint_ = paint;\n\n  // Drawing RRects is split into three performance tiers:\n  //\n  // 1) All stroked styles without AA *except* simple/symmetric RRects.\n  // 2) All filled styles and symmetric stroked styles w/AA.\n  // 3) Remaining stroked styles with AA.\n  //\n  // 1) and 3) scale linearly with length, 2) scales with area.\n\n  unsigned int complexity;\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  if (Style() == SkPaint::Style::kFill_Style ||\n      ((rrect.getType() == SkRRect::Type::kSimple_Type) && IsAntiAliased())) {\n    unsigned int area = rrect.width() * rrect.height();\n    // m = 1/3200\n    // c = 0.5\n    complexity = (area + 1600) / 80;\n  } else {\n    // Take the average of the width and height.\n    unsigned int length = (rrect.width() + rrect.height()) / 2;\n\n    // There is some difference between hairline and non-hairline performance\n    // but the spread is relatively inconsistent and it's pretty much a wash.\n    if (IsAntiAliased()) {\n      // m = 1/25\n      // c = 1\n      complexity = (length + 25) * 8 / 5;\n    } else {\n      // m = 1/50\n      // c = 0.75\n      complexity = ((length * 2) + 75) * 2 / 5;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawDRRect(\n    const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // There are roughly four classes here:\n  // a) Filled style.\n  // b) Complex RRect type with AA enabled and filled style.\n  // c) Stroked style with AA enabled.\n  // d) Stroked style with AA disabled.\n  //\n  // a) and b) scale linearly with the area, c) and d) scale linearly with\n  // a single dimension (length). In all cases, the dimensions refer to\n  // the outer RRect.\n\n  unsigned int complexity;\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  //\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n\n  if (Style() == SkPaint::Style::kFill_Style) {\n    unsigned int area = outer.width() * outer.height();\n    if (outer.getType() == SkRRect::Type::kComplex_Type) {\n      // m = 1/500\n      // c = 0.5\n      complexity = (area + 250) / 5;\n    } else {\n      // m = 1/1600\n      // c = 2\n      complexity = (area + 3200) / 16;\n    }\n  } else {\n    unsigned int length = (outer.width() + outer.height()) / 2;\n    if (IsAntiAliased()) {\n      // m = 1/15\n      // c = 1\n      complexity = (length + 15) * 20 / 3;\n    } else {\n      // m = 1/27\n      // c = 0.5\n      complexity = ((length * 2) + 27) * 50 / 27;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawPath(const SkPath& path,\n                                                         const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // There is negligible effect on the performance for hairline vs. non-hairline\n  // stroke widths.\n  //\n  // The data for filled styles is currently suspicious, so for now we are going\n  // to assign scores based on stroked styles.\n\n  unsigned int line_verb_cost, quad_verb_cost, conic_verb_cost, cubic_verb_cost;\n  unsigned int complexity;\n\n  if (IsAntiAliased()) {\n    // There seems to be a fixed cost of around 1ms for calling drawPath with\n    // AA.\n    complexity = 200000;\n\n    line_verb_cost = 235;\n    quad_verb_cost = 365;\n    conic_verb_cost = 365;\n    cubic_verb_cost = 725;\n  } else {\n    // There seems to be a fixed cost of around 0.25ms for calling drawPath.\n    // without AA\n    complexity = 50000;\n\n    line_verb_cost = 135;\n    quad_verb_cost = 150;\n    conic_verb_cost = 200;\n    cubic_verb_cost = 235;\n  }\n\n  complexity += CalculatePathComplexity(path, line_verb_cost, quad_verb_cost,\n                                        conic_verb_cost, cubic_verb_cost);\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawArc(\n    const SkRect& oval_bounds, SkScalar start_degrees, SkScalar sweep_degrees,\n    bool use_center, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // Hairline vs non-hairline makes no difference to the performance.\n  // Stroked styles without AA scale linearly with the log of the diameter.\n  // Stroked styles with AA scale linearly with the area.\n  // Filled styles scale linearly with the area.\n  unsigned int area = oval_bounds.width() * oval_bounds.height();\n  unsigned int complexity;\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  //\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style() == SkPaint::Style::kStroke_Style) {\n    if (IsAntiAliased()) {\n      // m = 1/3800\n      // c = 12\n      complexity = (area + 45600) / 171;\n    } else {\n      unsigned int diameter = (oval_bounds.width() + oval_bounds.height()) / 2;\n      // m = 15\n      // c = -100\n      // This should never go negative though, so use std::max to ensure\n      // c is never larger than 15*log_diameter.\n      //\n      // Pre-multiply by 15 here so we get a little bit more precision.\n      unsigned int log_diameter = 15 * log(diameter);\n      complexity = (log_diameter - std::max(log_diameter, 100u)) * 200 / 9;\n    }\n  } else {\n    if (IsAntiAliased()) {\n      // m = 1/1000\n      // c = 10\n      complexity = (area + 10000) / 45;\n    } else {\n      // m = 1/6500\n      // c = 12\n      complexity = (area + 52000) * 2 / 585;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawPoints(\n    PointMode mode, size_t count, const SkPoint points[],\n    const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  unsigned int complexity;\n\n  if (IsAntiAliased()) {\n    if (mode == SkCanvas::kPoints_PointMode) {\n      if (IsHairline()) {\n        // This is a special case, it triggers an extremely fast path.\n        // m = 1/4500\n        // c = 0\n        complexity = count * 400 / 9;\n      } else {\n        // m = 1/500\n        // c = 0\n        complexity = count * 400;\n      }\n    } else if (mode == SkCanvas::kLines_PointMode) {\n      if (IsHairline()) {\n        // m = 1/750\n        // c = 0\n        complexity = count * 800 / 3;\n      } else {\n        // m = 1/500\n        // c = 0\n        complexity = count * 400;\n      }\n    } else {\n      if (IsHairline()) {\n        // m = 1/350\n        // c = 0\n        complexity = count * 4000 / 7;\n      } else {\n        // m = 1/250\n        // c = 0\n        complexity = count * 800;\n      }\n    }\n  } else {\n    if (mode == SkCanvas::kPoints_PointMode) {\n      // Hairline vs non hairline makes no difference for points without AA.\n      // m = 1/18000\n      // c = 0.25\n      complexity = (count + 4500) * 100 / 9;\n    } else if (mode == SkCanvas::kLines_PointMode) {\n      if (IsHairline()) {\n        // m = 1/8500\n        // c = 0.25\n        complexity = (count + 2125) * 400 / 17;\n      } else {\n        // m = 1/9000\n        // c = 0.25\n        complexity = (count + 2250) * 200 / 9;\n      }\n    } else {\n      // Polygon only really diverges for hairline vs non hairline at large\n      // point counts, and only by a few %.\n      // m = 1/7500\n      // c = 0.25\n      complexity = (count + 1875) * 80 / 3;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawVerticesObject(\n    const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) {\n  current_paint_ = paint;\n  // There is currently no way for us to get the VertexMode from the SkVertices\n  // object, but for future reference:\n  //\n  // TriangleStrip is roughly 25% more expensive than TriangleFan.\n  // TriangleFan is roughly 5% more expensive than Triangles.\n\n  // There is currently no way for us to get the vertex count from an SkVertices\n  // object, so we have to estimate it from the approximate size.\n  //\n  // Approximate size returns the sum of the sizes of the positions (SkPoint),\n  // texs (SkPoint), colors (SkColor) and indices (uint16_t) arrays multiplied\n  // by sizeof(type). As a very, very rough estimate, divide that by 20 to get\n  // an idea of the vertex count.\n  unsigned int approximate_vertex_count = vertices->approximateSize() / 20;\n\n  // For the baseline, it's hard to identify the trend. It might be O(n^1/2)\n  // For now, treat it as linear as an approximation.\n  //\n  // m = 1/1600\n  // c = 1\n  unsigned int complexity = (approximate_vertex_count + 1600) * 250 / 2;\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawImage2(\n    const SkImage* image, SkScalar left, SkScalar top,\n    const SkSamplingOptions& sampling, const SkPaint* paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint ? *paint : SkPaint();\n  // AA vs non-AA has a cost but it's dwarfed by the overall cost of the\n  // drawImage call.\n  //\n  // The main difference is if the image is backed by a texture already or not\n  // If we don't need to upload, then the cost scales linearly with the\n  // length of the image. If it needs uploading, the cost scales linearly\n  // with the square of the area (!!!).\n  SkISize dimensions = image->dimensions();\n  unsigned int length = (dimensions.width() + dimensions.height()) / 2;\n  unsigned int area = dimensions.width() * dimensions.height();\n\n  // m = 1/13\n  // c = 0\n  unsigned int complexity = length * 400 / 13;\n\n  if (!image->isTextureBacked()) {\n    // We can't square the area here as we'll overflow, so let's approximate\n    // by taking the calculated complexity score and applying a multiplier to\n    // it.\n    //\n    // (complexity * area / 60000) + 4000 gives a reasonable approximation with\n    // AA (complexity * area / 19000) gives a reasonable approximation without\n    // AA.\n    float multiplier;\n    if (IsAntiAliased()) {\n      multiplier = area / 60000.0f;\n      complexity = complexity * multiplier + 4000;\n    } else {\n      multiplier = area / 19000.0f;\n      complexity = complexity * multiplier;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawPicture(\n    const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint ? *paint : SkPaint();\n  GLHelper helper(\n      Ceiling() - CurrentComplexityScore(),\n      SkIRect::MakeLTRB(picture->cullRect().left(), picture->cullRect().top(),\n                        picture->cullRect().right(),\n                        picture->cullRect().bottom()));\n  picture->playback(&helper);\n  AccumulateComplexity(helper.ComplexityScore());\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::onDrawTextBlob(\n    const SkTextBlob* text_blob, SkScalar x, SkScalar y, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // DrawTextBlob has a high fixed cost, but if we call it multiple times\n  // per frame, that fixed cost is greatly reduced per subsequent call. This\n  // is likely because there is batching being done in SkCanvas.\n\n  // Increment draw_text_blob_count_ and calculate the cost at the end.\n  draw_text_blob_count_++;\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::ImageRect(\n    const SkISize& size, bool texture_backed,\n    SkCanvas::SrcRectConstraint constraint) {\n  if (IsComplex()) {\n    return;\n  }\n  // Two main groups here - texture-backed and non-texture-backed images.\n  //\n  // Within each group, they all perform within a few % of each other *except*\n  // when we have a strict constraint and anti-aliasing enabled.\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  unsigned int complexity;\n  if (!texture_backed ||\n      (texture_backed && current_paint_ != SkPaint() &&\n       constraint == SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint &&\n       IsAntiAliased())) {\n    unsigned int area = size.width() * size.height();\n    // m = 1/4000\n    // c = 5\n    complexity = (area + 20000) / 10;\n  } else {\n    unsigned int length = (size.width() + size.height()) / 2;\n    // There's a little bit of spread here but the numbers are pretty large\n    // anyway.\n    //\n    // m = 1/22\n    // c = 0\n    complexity = length * 200 / 11;\n  }\n\n  AccumulateComplexity(complexity);\n}\n#else\nunsigned int PictureGLComplexityCalculator::GLHelper::BatchedComplexity() {\n  // Calculate the impact of saveLayer.\n  unsigned int save_layer_complexity;\n  if (save_layer_count_ == 0) {\n    save_layer_complexity = 0;\n  } else {\n    // m = 1/5\n    // c = 10\n    save_layer_complexity = (save_layer_count_ + 50) * 40000;\n  }\n\n  unsigned int draw_text_blob_complexity;\n  if (draw_text_blob_count_ == 0) {\n    draw_text_blob_complexity = 0;\n  } else {\n    // m = 1/240\n    // c = 0.25\n    draw_text_blob_complexity = (draw_text_blob_count_ + 60) * 2500 / 3;\n  }\n\n  return save_layer_complexity + draw_text_blob_complexity;\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::OnDrawLine(\n    float x0, float y0, float x1, float y1, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  // There is a relatively high fixed overhead cost for drawLine on OpenGL.\n  // Further, there is a strange bump where the cost of drawing a line of\n  // length ~500px is actually more costly than drawing a line of length\n  // ~1000px. The calculations here will be for a linear graph that\n  // approximate the overall trend.\n\n  float non_hairline_penalty = 1.0f;\n  unsigned int aa_penalty = 1;\n\n  // The non-hairline penalty is insignificant when AA is on.\n  if (!IsHairline(paint) && !IsAntiAliased(paint)) {\n    non_hairline_penalty = 1.15f;\n  }\n  if (IsAntiAliased(paint)) {\n    aa_penalty = 2;\n  }\n\n  // Use an approximation for the distance to avoid floating point or\n  // sqrt() calls.\n  float distance = abs(x0 - x1) + abs(y0 - y1);\n\n  // The baseline complexity is for a hairline stroke with no AA.\n  // m = 1/40\n  // c = 13\n  unsigned int complexity =\n      ((distance + 520) / 2) * non_hairline_penalty * aa_penalty;\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::OnDrawCircle(\n    float cx, float cy, float radius, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  unsigned int complexity;\n\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style(paint) == skity::Paint::Style::kFill_Style) {\n    // We can ignore pi here\n    unsigned int area = radius * radius;\n    // m = 1/525\n    // c = 50\n    complexity = (area + 26250) * 8 / 105;\n\n    // Penalty of around 8% when AA is disabled.\n    if (!IsAntiAliased(paint)) {\n      complexity *= 1.08f;\n    }\n  } else {\n    // Hairline vs non-hairline has no significant performance difference.\n    if (IsAntiAliased(paint)) {\n      // m = 1/3\n      // c = 10\n      complexity = (radius + 30) * 40 / 3;\n    } else {\n      // m = 1/10\n      // c = 20\n      complexity = (radius + 200) * 4;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::OnDrawOval(\n    skity::Rect const& oval, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  // DrawOval scales very roughly linearly with the bounding box width/height\n  // (not area) for stroked styles without AA.\n  //\n  // Filled styles and stroked styles with AA scale linearly with the bounding\n  // box area.\n  unsigned int area = oval.Width() * oval.Height();\n\n  unsigned int complexity;\n\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style(paint) == skity::Paint::Style::kFill_Style) {\n    // With filled styles, there is no significant AA penalty.\n    // m = 1/6000\n    // c = 0\n    complexity = area / 30;\n  } else {\n    if (IsAntiAliased(paint)) {\n      // m = 1/4000\n      // c = 0\n      complexity = area / 20;\n    } else {\n      // Take the average of the width and height.\n      unsigned int length = (oval.Width() + oval.Height()) / 2;\n\n      // m = 1/75\n      // c = 0\n      complexity = length * 8 / 3;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::OnDrawRect(\n    skity::Rect const& rect, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  unsigned int complexity;\n\n  // If stroked, cost scales linearly with the rectangle width/height.\n  // If filled, it scales with the area.\n  //\n  // Hairline stroke vs non hairline has no significant penalty.\n  //\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style(paint) == skity::Paint::Style::kFill_Style) {\n    // No real difference for AA with filled styles\n    unsigned int area = rect.Width() * rect.Height();\n\n    // m = 1/3500\n    // c = 0\n    complexity = area * 2 / 175;\n  } else {\n    // Take the average of the width and height.\n    unsigned int length = (rect.Width() + rect.Height()) / 2;\n\n    if (IsAntiAliased(paint)) {\n      // m = 1/30\n      // c = 0\n      complexity = length * 4 / 3;\n    } else {\n      // If AA is disabled, the data shows that at larger sizes the overall\n      // cost comes down, peaking at around 1000px. As we don't anticipate\n      // rasterising rects with AA disabled to be all that frequent, just treat\n      // it as a straight line that peaks at 1000px, beyond which it stays\n      // constant. The rationale here is that it makes more sense to\n      // overestimate than to start decreasing the cost as the length goes up.\n      //\n      // This should be a reasonable approximation as it doesn't decrease by\n      // much from 1000px to 2000px.\n      //\n      // m = 1/20\n      // c = 0\n      complexity = std::min(length, 1000u) * 2;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::OnDrawRRect(\n    skity::RRect const& rrect, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  // Drawing RRects is split into three performance tiers:\n  //\n  // 1) All stroked styles without AA *except* simple/symmetric RRects.\n  // 2) All filled styles and symmetric stroked styles w/AA.\n  // 3) Remaining stroked styles with AA.\n  //\n  // 1) and 3) scale linearly with length, 2) scales with area.\n\n  unsigned int complexity;\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  if (Style(paint) == skity::Paint::Style::kFill_Style ||\n      ((rrect.GetType() == skity::RRect::Type::kSimple) &&\n       IsAntiAliased(paint))) {\n    unsigned int area = rrect.Width() * rrect.Height();\n    // m = 1/3200\n    // c = 0.5\n    complexity = (area + 1600) / 80;\n  } else {\n    // Take the average of the width and height.\n    unsigned int length = (rrect.Width() + rrect.Height()) / 2;\n\n    // There is some difference between hairline and non-hairline performance\n    // but the spread is relatively inconsistent and it's pretty much a wash.\n    if (IsAntiAliased(paint)) {\n      // m = 1/25\n      // c = 1\n      complexity = (length + 25) * 8 / 5;\n    } else {\n      // m = 1/50\n      // c = 0.75\n      complexity = ((length * 2) + 75) * 2 / 5;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::OnDrawPath(\n    skity::Path const& path, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  // There is negligible effect on the performance for hairline vs. non-hairline\n  // stroke widths.\n  //\n  // The data for filled styles is currently suspicious, so for now we are going\n  // to assign scores based on stroked styles.\n\n  unsigned int line_verb_cost, quad_verb_cost, conic_verb_cost, cubic_verb_cost;\n  unsigned int complexity;\n\n  if (IsAntiAliased(paint)) {\n    // There seems to be a fixed cost of around 1ms for calling drawPath with\n    // AA.\n    complexity = 200000;\n\n    line_verb_cost = 235;\n    quad_verb_cost = 365;\n    conic_verb_cost = 365;\n    cubic_verb_cost = 725;\n  } else {\n    // There seems to be a fixed cost of around 0.25ms for calling drawPath.\n    // without AA\n    complexity = 50000;\n\n    line_verb_cost = 135;\n    quad_verb_cost = 150;\n    conic_verb_cost = 200;\n    cubic_verb_cost = 235;\n  }\n\n  complexity += CalculatePathComplexity(path, line_verb_cost, quad_verb_cost,\n                                        conic_verb_cost, cubic_verb_cost);\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::OnSaveLayer(\n    const skity::Rect& bounds, const skity::Paint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  save_layer_count_++;\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::OnDrawBlob(\n    const skity::TextBlob* blob, float x, float y, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  // DrawTextBlob has a high fixed cost, but if we call it multiple times\n  // per frame, that fixed cost is greatly reduced per subsequent call. This\n  // is likely because there is batching being done in SkCanvas.\n\n  // Increment draw_text_blob_count_ and calculate the cost at the end.\n  draw_text_blob_count_++;\n}\n\nvoid PictureGLComplexityCalculator::GLHelper::OnDrawImageRect(\n    std::shared_ptr<skity::Image> image, const skity::Rect& src,\n    const skity::Rect& dst, const skity::SamplingOptions& sampling,\n    skity::Paint const* paint) {\n  if (IsComplex()) {\n    return;\n  }\n  // Two main groups here - texture-backed and non-texture-backed images.\n  //\n  // Within each group, they all perform within a few % of each other *except*\n  // when we have a strict constraint and anti-aliasing enabled.\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  unsigned int complexity;\n  bool texture_backed = image->IsTextureBackend();\n  size_t width = image->Width();\n  size_t height = image->Height();\n  if (!texture_backed || (paint && texture_backed && IsAntiAliased(*paint))) {\n    unsigned int area = width * height;\n    // m = 1/4000\n    // c = 5\n    complexity = (area + 20000) / 10;\n  } else {\n    unsigned int length = (width + height) / 2;\n    // There's a little bit of spread here but the numbers are pretty large\n    // anyway.\n    //\n    // m = 1/22\n    // c = 0\n    complexity = length * 200 / 11;\n  }\n\n  AccumulateComplexity(complexity);\n}\n\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity_gl.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_GL_H_\n#define CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_GL_H_\n\n#include <limits>\n#include <memory>\n\n#include \"clay/flow/flow_rendering_backend.h\"\n#include \"clay/flow/layers/picture_complexity.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass PictureGLComplexityCalculator : public PictureComplexityCalculator {\n public:\n  static PictureGLComplexityCalculator* GetInstance();\n\n#ifndef ENABLE_SKITY\n  unsigned int Compute(SkPicture* picture) override {\n    GLHelper helper(ceiling_, SkIRect::MakeLTRB(picture->cullRect().left(),\n                                                picture->cullRect().top(),\n                                                picture->cullRect().right(),\n                                                picture->cullRect().bottom()));\n    picture->playback(&helper);\n    return helper.ComplexityScore();\n  }\n#else\n  unsigned int Compute(skity::DisplayList* display_list) override {\n    GLHelper helper(ceiling_);\n    display_list->Draw(&helper);\n    return helper.ComplexityScore();\n  }\n#endif\n\n  bool ShouldBeCached(unsigned int complexity_score) override {\n    // Set cache threshold at 1ms\n    return complexity_score > 200000u;\n  }\n\n  void SetComplexityCeiling(unsigned int ceiling) override {\n    ceiling_ = ceiling;\n  }\n\n private:\n#ifndef ENABLE_SKITY\n  class GLHelper : public ComplexityCalculatorHelper {\n   public:\n    GLHelper(unsigned int ceiling, SkIRect bounds)\n        : ComplexityCalculatorHelper(ceiling, bounds),\n          save_layer_count_(0),\n          draw_text_blob_count_(0) {}\n\n    void onDrawRect(const SkRect&, const SkPaint&) override;\n    void onDrawOval(const SkRect&, const SkPaint&) override;\n    void onDrawRRect(const SkRRect&, const SkPaint&) override;\n    void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;\n    void onDrawPath(const SkPath&, const SkPaint&) override;\n    void onDrawArc(const SkRect&, SkScalar, SkScalar, bool,\n                   const SkPaint&) override;\n    void onDrawPoints(PointMode, size_t, const SkPoint[],\n                      const SkPaint&) override;\n    void onDrawVerticesObject(const SkVertices*, SkBlendMode,\n                              const SkPaint&) override;\n    void onDrawImage2(const SkImage*, SkScalar, SkScalar,\n                      const SkSamplingOptions&, const SkPaint*) override;\n    void onDrawPicture(const SkPicture*, const SkMatrix*,\n                       const SkPaint*) override;\n    void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar,\n                        const SkPaint&) override;\n\n   protected:\n    void ImageRect(const SkISize& size, bool texture_backed,\n                   SkCanvas::SrcRectConstraint constraint) override;\n\n    unsigned int BatchedComplexity() override;\n\n   private:\n    unsigned int save_layer_count_;\n    unsigned int draw_text_blob_count_;\n  };\n#else\n  class GLHelper : public ComplexityCalculatorHelperSkity {\n   public:\n    explicit GLHelper(unsigned int ceiling)\n        : ComplexityCalculatorHelperSkity(ceiling),\n          save_layer_count_(0),\n          draw_text_blob_count_(0) {}\n    void OnDrawLine(float x0, float y0, float x1, float y1,\n                    skity::Paint const& paint) override;\n    void OnDrawCircle(float cx, float cy, float radius,\n                      skity::Paint const& paint) override;\n    void OnDrawOval(skity::Rect const& oval,\n                    skity::Paint const& paint) override;\n    void OnDrawRect(skity::Rect const& rect,\n                    skity::Paint const& paint) override;\n    void OnDrawRRect(skity::RRect const& rrect,\n                     skity::Paint const& paint) override;\n    void OnDrawPath(skity::Path const& path,\n                    skity::Paint const& paint) override;\n    void OnSaveLayer(const skity::Rect& bounds,\n                     const skity::Paint& paint) override;\n    void OnDrawBlob(const skity::TextBlob* blob, float x, float y,\n                    skity::Paint const& paint) override;\n    void OnDrawImageRect(std::shared_ptr<skity::Image> image,\n                         const skity::Rect& src, const skity::Rect& dst,\n                         const skity::SamplingOptions& sampling,\n                         skity::Paint const* paint) override;\n\n    unsigned int BatchedComplexity() override;\n\n   private:\n    unsigned int save_layer_count_;\n    unsigned int draw_text_blob_count_;\n  };\n#endif\n\n  PictureGLComplexityCalculator()\n      : ceiling_(std::numeric_limits<unsigned int>::max()) {}\n  static PictureGLComplexityCalculator* instance_;\n\n  unsigned int ceiling_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_GL_H_\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity_helper_skia.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_HELPER_SKIA_H_\n#define CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_HELPER_SKIA_H_\n\n#include \"clay/flow/layers/picture_complexity.h\"\n#include \"clay/gfx/style/blend_mode.h\"\n#include \"third_party/skia/include/core/SkImage.h\"\n#include \"third_party/skia/include/core/SkPath.h\"\n#include \"third_party/skia/include/utils/SkNoDrawCanvas.h\"\n\nnamespace clay {\n\n// The Metal and OpenGL complexity calculators use benchmark data gathered\n// using the display_list_benchmarks test suite on real devices.\n//\n// Constants of proportionality and trends were chosen to better match\n// larger numbers rather than smaller ones. This may turn out to be the\n// wrong decision, but the rationale here is that with the smaller numbers,\n// we have:\n//\n// a) More noise.\n// b) Less absolute difference. If the constant we've chosen is out by 50%\n//    on a measurement that is 0.001ms, that's less of an issue than if\n//    the measurement is 15ms.\n// c) Smaller numbers affect the caching decision negligibly; the caching\n//    decision is likely to be driven by slower ops rather than faster ones.\n//\n// In some cases, a cost penalty is used to figure out the cost of an\n// attribute such as anti-aliasing or fill style. In some of these, the\n// penalty is proportional to some other value such as the radius of\n// a circle. In these cases, we ensure that the penalty is never smaller\n// than 1.0f.\n//\n// The error bars in measurement are likely quite large and will\n// vary from device to device, so the goal here is not to provide a\n// perfectly accurate representation of a given DisplayList's\n// complexity but rather a very rough estimate that improves upon our\n// previous cache admission policy (op_count > 5).\n//\n// There will likely be future work that will improve upon the figures\n// in here. Notably, we do not take matrices or clips into account yet.\n//\n// The scoring is based around a baseline score of 100 being roughly\n// equivalent to 0.0005ms of time. With a 32-bit unsigned integer, this\n// would set the maximum time estimate associated with the complexity score\n// at about 21 seconds, which far exceeds what we would ever expect a\n// DisplayList to take rasterising.\n//\n// Finally, care has been taken to keep the computations as cheap as possible.\n// We need to be able to calculate the complexity as quickly as possible\n// so that we don't end up wasting too much time figuring out if something\n// should be cached or not and eat into the time we could have just spent\n// rasterising the DisplayList.\n//\n// In order to keep the computations cheap, the following tradeoffs were made:\n//\n// a) Limit all computations to simple division, multiplication, addition\n//    and subtraction.\n// b) Try and use integer arithmetic as much as possible.\n// c) If a specific draw op is logarithmic in complexity, do a best fit\n//    onto a linear equation within the range we expect to see the variables\n//    fall within.\n//\n// As an example of the above, let's say we have some data that looks like the\n// complexity is something like O(n^1/3). We would like to avoid anything too\n// expensive to calculate, so taking the cube root of the value to try and\n// calculate the time cost should be avoided.\n//\n// In this case, the approach would be to take a straight line approximation\n// that maps closely in the range of n where we feel it is most likely to occur.\n// For example, if this were drawLine, and n were the line length, and we\n// expected the line lengths to typically be between 50 and 100, then we would\n// figure out the equation of the straight line graph that approximates the\n// n^1/3 curve, and if possible try and choose an approximation that is more\n// representative in the range of [50, 100] for n.\n//\n// Once that's done, we can develop the formula as follows:\n//\n// Take y = mx + c (straight line graph chosen as per guidelines above).\n// Divide by however many ops the benchmark ran for a single pass.\n// Multiply by 200,000 to normalise 0.0005ms = 100.\n// Simplify down the formula.\n//\n// So if we had m = 1/5 and c = 0, and the drawLines benchmark ran 10,000\n// drawLine calls per iteration:\n//\n//   y (time taken) = x (line length) / 5\n//   y = x / 5 * 200,000 / 10,000\n//   y = x / 5 * 20\n//   y = 4x\n\nclass ComplexityCalculatorHelper : public SkNoDrawCanvas {\n public:\n  ComplexityCalculatorHelper(unsigned int ceiling, SkIRect bounds)\n      : SkNoDrawCanvas(bounds),\n        is_complex_(false),\n        ceiling_(ceiling),\n        complexity_score_(0) {}\n  virtual ~ComplexityCalculatorHelper() = default;\n\n  void onDrawPaint(const SkPaint& paint) override {\n    if (IsComplex()) {\n      return;\n    }\n    current_paint_ = paint;\n    // Placeholder value here. This can be cheap (e.g. effectively a drawColor),\n    // or expensive (e.g. a bitmap shader with an image filter)\n    AccumulateComplexity(50);\n  }\n\n  void onDrawImageRect2(const SkImage* image, const SkRect& src,\n                        const SkRect& dst, const SkSamplingOptions& sampling,\n                        const SkPaint* paint,\n                        SrcRectConstraint constraint) override {\n    if (IsComplex()) {\n      return;\n    }\n    current_paint_ = paint ? *paint : SkPaint();\n    ImageRect(image->dimensions(), image->isTextureBacked(), constraint);\n  }\n\n  void onDrawImageLattice2(const SkImage* image, const Lattice& lattice,\n                           const SkRect& dst, SkFilterMode filter,\n                           const SkPaint* paint) override {\n    if (IsComplex()) {\n      return;\n    }\n    current_paint_ = paint ? *paint : SkPaint();\n    // This is not currently called from Flutter code, and this API is likely\n    // to be removed in the future. For now, just return what drawImageNine\n    // would\n    ImageRect(image->dimensions(), image->isTextureBacked(),\n              SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint);\n  }\n\n  void onDrawAtlas2(const SkImage* image, const SkRSXform xform[],\n                    const SkRect tex[], const SkColor colors[], int count,\n                    SkBlendMode mode, const SkSamplingOptions& sampling,\n                    const SkRect* cull_rect, const SkPaint* paint) override {\n    if (IsComplex()) {\n      return;\n    }\n    current_paint_ = paint ? *paint : SkPaint();\n    // This API just does a series of drawImage calls from the atlas\n    // This is equivalent to calling drawImageRect lots of times\n    for (int i = 0; i < count; i++) {\n      ImageRect(SkISize::Make(tex[i].width(), tex[i].height()), true,\n                SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint);\n    }\n  }\n\n  void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,\n                     const SkPaint* paint) override {\n    current_paint_ = paint ? *paint : SkPaint();\n    // This API shouldn't be used, but for now just take the\n    // approximateOpCount() and multiply by 50 as a placeholder.\n    AccumulateComplexity(picture->approximateOpCount() * 50);\n  }\n  // This method finalizes the complexity score calculation and returns it\n  unsigned int ComplexityScore() {\n    // We hit our ceiling, so return that\n    if (IsComplex()) {\n      return Ceiling();\n    }\n\n    // Calculate the impact of any draw ops where the complexity is dependent\n    // on the number of calls made.\n    unsigned int batched_complexity = BatchedComplexity();\n\n    // Check for overflow\n    if (Ceiling() - complexity_score_ < batched_complexity) {\n      return Ceiling();\n    }\n\n    return complexity_score_ + batched_complexity;\n  }\n\n protected:\n  void AccumulateComplexity(unsigned int complexity) {\n    // Check to see if we will overflow by accumulating this complexity score\n    if (ceiling_ - complexity_score_ < complexity) {\n      is_complex_ = true;\n      return;\n    }\n\n    complexity_score_ += complexity;\n  }\n\n  inline bool IsAntiAliased() { return current_paint_.isAntiAlias(); }\n  inline bool IsHairline() { return current_paint_.getStrokeWidth() == 0.0f; }\n  inline SkPaint::Style Style() { return current_paint_.getStyle(); }\n  inline bool IsComplex() { return is_complex_; }\n  inline unsigned int Ceiling() { return ceiling_; }\n  inline unsigned int CurrentComplexityScore() { return complexity_score_; }\n\n  unsigned int CalculatePathComplexity(const SkPath& path,\n                                       unsigned int line_verb_cost,\n                                       unsigned int quad_verb_cost,\n                                       unsigned int conic_verb_cost,\n                                       unsigned int cubic_verb_cost) {\n    int verb_count = path.countVerbs();\n    uint8_t verbs[verb_count];\n    path.getVerbs(verbs, verb_count);\n\n    unsigned int complexity = 0;\n    for (int i = 0; i < verb_count; i++) {\n      switch (verbs[i]) {\n        case SkPath::Verb::kLine_Verb:\n          complexity += line_verb_cost;\n          break;\n        case SkPath::Verb::kQuad_Verb:\n          complexity += quad_verb_cost;\n          break;\n        case SkPath::Verb::kConic_Verb:\n          complexity += conic_verb_cost;\n          break;\n        case SkPath::Verb::kCubic_Verb:\n          complexity += cubic_verb_cost;\n          break;\n      }\n    }\n    return complexity;\n  }\n\n  virtual void ImageRect(const SkISize& size, bool texture_backed,\n                         SkCanvas::SrcRectConstraint constraint) = 0;\n\n  // This calculates and returns the cost of draw calls which are batched and\n  // thus have a time cost proportional to the number of draw calls made, such\n  // as saveLayer and drawTextBlob.\n  virtual unsigned int BatchedComplexity() = 0;\n  SkPaint current_paint_;\n\n private:\n  // If we exceed the ceiling (defaults to the largest number representable\n  // by unsigned int), then set the is_complex_ bool and we no longer\n  // accumulate.\n  bool is_complex_;\n  unsigned int ceiling_;\n\n  unsigned int complexity_score_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_HELPER_SKIA_H_\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity_helper_skity.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_HELPER_SKITY_H_\n#define CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_HELPER_SKITY_H_\n\n#include \"skity/render/canvas.hpp\"\n\nnamespace clay {\n\nclass ComplexityCalculatorHelperSkity : public skity::Canvas {\n public:\n  explicit ComplexityCalculatorHelperSkity(unsigned int ceiling)\n      : is_complex_(false), ceiling_(ceiling), complexity_score_(0) {}\n  using ClipOp = skity::Canvas::ClipOp;\n  void OnClipRect(skity::Rect const& rect, ClipOp op) override {}\n  void OnClipPath(skity::Path const& path, ClipOp op) override {}\n\n  void OnDrawGlyphs(uint32_t count, const skity::GlyphID glyphs[],\n                    const float position_x[], const float position_y[],\n                    const skity::Font& font,\n                    const skity::Paint& paint) override {}\n\n  void OnDrawPaint(skity::Paint const& paint) override {\n    if (IsComplex()) {\n      return;\n    }\n    // Placeholder value here. This can be cheap (e.g. effectively a drawColor),\n    // or expensive (e.g. a bitmap shader with an image filter)\n    AccumulateComplexity(50);\n  }\n\n  void OnSave() override {}\n  void OnRestore() override {}\n  void OnRestoreToCount(int saveCount) override {}\n  void OnFlush() override {}\n  uint32_t OnGetWidth() const override { return 0; }\n  uint32_t OnGetHeight() const override { return 0; };\n\n  // This method finalizes the complexity score calculation and returns it\n  unsigned int ComplexityScore() {\n    // We hit our ceiling, so return that\n    if (IsComplex()) {\n      return Ceiling();\n    }\n\n    // Calculate the impact of any draw ops where the complexity is dependent\n    // on the number of calls made.\n    unsigned int batched_complexity = BatchedComplexity();\n\n    // Check for overflow\n    if (Ceiling() - complexity_score_ < batched_complexity) {\n      return Ceiling();\n    }\n\n    return complexity_score_ + batched_complexity;\n  }\n\n protected:\n  void AccumulateComplexity(unsigned int complexity) {\n    // Check to see if we will overflow by accumulating this complexity score\n    if (ceiling_ - complexity_score_ < complexity) {\n      is_complex_ = true;\n      return;\n    }\n\n    complexity_score_ += complexity;\n  }\n\n  unsigned int CalculatePathComplexity(const skity::Path& path,\n                                       unsigned int line_verb_cost,\n                                       unsigned int quad_verb_cost,\n                                       unsigned int conic_verb_cost,\n                                       unsigned int cubic_verb_cost) {\n    int verb_count = path.CountVerbs();\n    unsigned int complexity = 0;\n    for (int i = 0; i < verb_count; i++) {\n      switch (path.GetVerb(i)) {\n        case skity::Path::Verb::kLine:\n          complexity += line_verb_cost;\n          break;\n        case skity::Path::Verb::kQuad:\n          complexity += quad_verb_cost;\n          break;\n        case skity::Path::Verb::kConic:\n          complexity += conic_verb_cost;\n          break;\n        case skity::Path::Verb::kCubic:\n          complexity += cubic_verb_cost;\n          break;\n        default:\n          break;\n      }\n    }\n    return complexity;\n  }\n\n  // This calculates and returns the cost of draw calls which are batched and\n  // thus have a time cost proportional to the number of draw calls made, such\n  // as saveLayer and drawTextBlob.\n  virtual unsigned int BatchedComplexity() = 0;\n\n  inline bool IsAntiAliased(const skity::Paint& paint) {\n    return paint.IsAntiAlias();\n  }\n  inline bool IsHairline(const skity::Paint& paint) {\n    return paint.GetStrokeWidth() == 0.0f;\n  }\n  inline skity::Paint::Style Style(const skity::Paint& paint) {\n    return paint.GetStyle();\n  }\n\n  inline bool IsComplex() { return is_complex_; }\n  inline unsigned int Ceiling() { return ceiling_; }\n  inline unsigned int CurrentComplexityScore() { return complexity_score_; }\n\n private:\n  // If we exceed the ceiling (defaults to the largest number representable\n  // by unsigned int), then set the is_complex_ bool and we no longer\n  // accumulate.\n  bool is_complex_;\n  unsigned int ceiling_;\n\n  unsigned int complexity_score_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_HELPER_SKITY_H_\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity_metal.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can\n// be found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/picture_complexity_metal.h\"\n\n#include <cmath>\n\n// The numbers and weightings used in this file stem from taking the\n// data from the DisplayListBenchmarks suite run on an iPhone 12 and\n// applying very rough analysis on them to identify the approximate\n// trends.\n//\n// See the comments in display_list_complexity_helper.h for details on the\n// process and rationale behind coming up with these numbers.\n\nnamespace clay {\n\nPictureMetalComplexityCalculator* PictureMetalComplexityCalculator::instance_ =\n    nullptr;\n\nPictureMetalComplexityCalculator*\nPictureMetalComplexityCalculator::GetInstance() {\n  if (instance_ == nullptr) {\n    instance_ = new PictureMetalComplexityCalculator();\n  }\n  return instance_;\n}\n\n#ifndef ENABLE_SKITY\nunsigned int\nPictureMetalComplexityCalculator::MetalHelper::BatchedComplexity() {\n  // Calculate the impact of saveLayer.\n  unsigned int save_layer_complexity;\n  if (save_layer_count_ == 0) {\n    save_layer_complexity = 0;\n  } else {\n    // saveLayer seems to have two trends; if the count is < 200,\n    // then the individual cost of a saveLayer is higher than if\n    // the count is > 200.\n    //\n    // However, the trend is strange and we should gather more data to\n    // get a better idea of how to represent the trend. That being said, it's\n    // very unlikely we'll ever hit a DisplayList with 200+ saveLayer calls\n    // in it, so we will calculate based on the more reasonably anticipated\n    // range of less than 200, with the trend line more weighted towards the\n    // lower end of that range (as the data itself doesn't present as a straight\n    // line). Further, we will easily hit our cache thresholds with such a\n    // large number of saveLayer calls.\n    //\n    // m = 1/2\n    // c = 1\n    save_layer_complexity = (save_layer_count_ + 2) * 100000;\n  }\n\n  unsigned int draw_text_blob_complexity;\n  if (draw_text_blob_count_ == 0) {\n    draw_text_blob_complexity = 0;\n  } else {\n    // m = 1/240\n    // c = 0.75\n    draw_text_blob_complexity = (draw_text_blob_count_ + 180) * 2500 / 3;\n  }\n\n  return save_layer_complexity + draw_text_blob_complexity;\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawRect(\n    const SkRect& rect, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  unsigned int complexity;\n\n  // If stroked, cost scales linearly with the rectangle width/height.\n  // If filled, it scales with the area.\n  //\n  // Hairline stroke vs non hairline has no real penalty at smaller lengths,\n  // but it increases at larger lengths. There isn't enough data to get a good\n  // idea of the penalty at lengths > 1000px.\n  //\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style() == SkPaint::Style::kFill_Style) {\n    // No real difference for AA with filled styles.\n    unsigned int area = rect.width() * rect.height();\n\n    // m = 1/9000\n    // c = 0\n    complexity = area / 225;\n  } else {\n    // Take the average of the width and height.\n    unsigned int length = (rect.width() + rect.height()) / 2;\n\n    // There is a penalty for AA being *disabled*.\n    if (IsAntiAliased()) {\n      // m = 1/65\n      // c = 0\n      complexity = length * 8 / 13;\n    } else {\n      // m = 1/35\n      // c = 0\n      complexity = length * 8 / 7;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawOval(\n    const SkRect& bounds, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // DrawOval scales very roughly linearly with the bounding box width/height\n  // (not area) for stroked styles without AA.\n  //\n  // Filled styles and stroked styles with AA scale linearly with the bounding\n  // box area.\n  unsigned int area = bounds.width() * bounds.height();\n\n  unsigned int complexity;\n\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style() == SkPaint::Style::kFill_Style) {\n    // With filled styles, there is no significant AA penalty.\n    // m = 1/16000\n    // c = 0\n    complexity = area / 80;\n  } else {\n    if (IsAntiAliased()) {\n      // m = 1/7500\n      // c = 0\n      complexity = area * 2 / 75;\n    } else {\n      // Take the average of the width and height.\n      unsigned int length = (bounds.width() + bounds.height()) / 2;\n\n      // m = 1/80\n      // c = 0\n      complexity = length * 5 / 2;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawRRect(\n    const SkRRect& rrect, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // RRects scale linearly with the area of the bounding rect.\n  unsigned int area = rrect.width() * rrect.height();\n\n  // Drawing RRects is split into two performance tiers; an expensive\n  // one and a less expensive one. Both scale linearly with area.\n  //\n  // Expensive: All filled style, symmetric w/AA.\n  bool expensive =\n      (Style() == SkPaint::Style::kFill_Style) ||\n      ((rrect.getType() == SkRRect::Type::kSimple_Type) && IsAntiAliased());\n\n  unsigned int complexity;\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  if (expensive) {\n    // m = 1/25000\n    // c = 2\n    // An area of 7000px^2 ~= baseline timing of 0.0005ms.\n    complexity = (area + 10500) / 175;\n  } else {\n    // m = 1/7000\n    // c = 1.5\n    // An area of 16000px^2 ~= baseline timing of 0.0005ms.\n    complexity = (area + 50000) / 625;\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawDRRect(\n    const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // There are roughly four classes here:\n  // a) Filled style with AA enabled.\n  // b) Filled style with AA disabled.\n  // c) Complex RRect type with AA enabled and filled style.\n  // d) Everything else.\n  //\n  // a) and c) scale linearly with the area, b) and d) scale linearly with\n  // a single dimension (length). In all cases, the dimensions refer to\n  // the outer RRect.\n  unsigned int length = (outer.width() + outer.height()) / 2;\n\n  unsigned int complexity;\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  //\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style() == SkPaint::Style::kFill_Style) {\n    unsigned int area = outer.width() * outer.height();\n    if (outer.getType() == SkRRect::Type::kComplex_Type) {\n      // m = 1/1000\n      // c = 1\n      complexity = (area + 1000) / 10;\n    } else {\n      if (IsAntiAliased()) {\n        // m = 1/3500\n        // c = 1.5\n        complexity = (area + 5250) / 35;\n      } else {\n        // m = 1/30\n        // c = 1\n        complexity = (300 + (10 * length)) / 3;\n      }\n    }\n  } else {\n    // m = 1/60\n    // c = 1.75\n    complexity = ((10 * length) + 1050) / 6;\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawPath(\n    const SkPath& path, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // There is negligible effect on the performance for hairline vs. non-hairline\n  // stroke widths.\n  //\n  // The data for filled styles is currently suspicious, so for now we are going\n  // to assign scores based on stroked styles.\n\n  unsigned int line_verb_cost, quad_verb_cost, conic_verb_cost, cubic_verb_cost;\n\n  if (IsAntiAliased()) {\n    line_verb_cost = 75;\n    quad_verb_cost = 100;\n    conic_verb_cost = 160;\n    cubic_verb_cost = 210;\n  } else {\n    line_verb_cost = 67;\n    quad_verb_cost = 80;\n    conic_verb_cost = 140;\n    cubic_verb_cost = 210;\n  }\n\n  // There seems to be a fixed cost of around 1ms for calling drawPath.\n  unsigned int complexity =\n      200000 + CalculatePathComplexity(path, line_verb_cost, quad_verb_cost,\n                                       conic_verb_cost, cubic_verb_cost);\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawArc(\n    const SkRect& oval_bounds, SkScalar start_degrees, SkScalar sweep_degrees,\n    bool use_center, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // Hairline vs non-hairline makes no difference to the performance.\n  // Stroked styles without AA scale linearly with the diameter.\n  // Stroked styles with AA scale linearly with the area except for small\n  // values. Filled styles scale linearly with the area.\n  unsigned int diameter = (oval_bounds.width() + oval_bounds.height()) / 2;\n  unsigned int area = oval_bounds.width() * oval_bounds.height();\n\n  unsigned int complexity;\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  //\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style() == clay::GrPaint::Style::kStroke_Style) {\n    if (IsAntiAliased()) {\n      // m = 1/8500\n      // c = 16\n      complexity = (area + 136000) * 2 / 765;\n    } else {\n      // m = 1/60\n      // c = 3\n      complexity = (diameter + 180) * 10 / 27;\n    }\n  } else {\n    if (IsAntiAliased()) {\n      // m = 1/20000\n      // c = 20\n      complexity = (area + 400000) / 900;\n    } else {\n      // m = 1/2100\n      // c = 8\n      complexity = (area + 16800) * 2 / 189;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawPoints(\n    PointMode mode, size_t count, const SkPoint points[],\n    const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  unsigned int complexity;\n\n  // If AA is off then they all behave similarly, and scale\n  // linearly with the point count.\n  if (!IsAntiAliased()) {\n    // m = 1/16000\n    // c = 0.75\n    complexity = (count + 12000) * 25 / 2;\n  } else {\n    if (mode == SkCanvas::kPolygon_PointMode) {\n      // m = 1/1250\n      // c = 1\n      complexity = (count + 1250) * 160;\n    } else {\n      if (IsHairline() && mode == SkCanvas::kPoints_PointMode) {\n        // This is a special case, it triggers an extremely fast path.\n        // m = 1/14500\n        // c = 0\n        complexity = count * 400 / 29;\n      } else {\n        // m = 1/2200\n        // c = 0.75\n        complexity = (count + 1650) * 1000 / 11;\n      }\n    }\n  }\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawVerticesObject(\n    const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) {\n  current_paint_ = paint;\n  // There is currently no way for us to get the VertexMode from the SkVertices\n  // object, but for future reference:\n  //\n  // TriangleStrip is roughly 25% more expensive than TriangleFan.\n  // TriangleFan is roughly 5% more expensive than Triangles.\n\n  // There is currently no way for us to get the vertex count from an SkVertices\n  // object, so we have to estimate it from the approximate size.\n  //\n  // Approximate size returns the sum of the sizes of the positions (SkPoint),\n  // texs (SkPoint), colors (SkColor) and indices (uint16_t) arrays multiplied\n  // by sizeof(type). As a very, very rough estimate, divide that by 20 to get\n  // an idea of the vertex count.\n  unsigned int approximate_vertex_count = vertices->approximateSize() / 20;\n\n  // For the baseline, it's hard to identify the trend. It might be O(n^1/2).\n  // For now, treat it as linear as an approximation.\n  //\n  // m = 1/4000\n  // c = 1\n  unsigned int complexity = (approximate_vertex_count + 4000) * 50;\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawImage2(\n    const SkImage* image, SkScalar left, SkScalar top,\n    const SkSamplingOptions& sampling, const SkPaint* paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint ? *paint : SkPaint();\n  // AA vs non-AA has a cost but it's dwarfed by the overall cost of the\n  // drawImage call.\n  //\n  // The main difference is if the image is backed by a texture already or not\n  // If we don't need to upload, then the cost scales linearly with the\n  // area of the image. If it needs uploading, the cost scales linearly\n  // with the square of the area (!!!).\n  SkISize dimensions = image->dimensions();\n  unsigned int area = dimensions.width() * dimensions.height();\n\n  // m = 1/17000\n  // c = 3\n  unsigned int complexity = (area + 51000) * 4 / 170;\n\n  if (!image->isTextureBacked()) {\n    // We can't square the area here as we'll overflow, so let's approximate\n    // by taking the calculated complexity score and applying a multiplier to\n    // it.\n    //\n    // (complexity * area / 35000) + 1200 gives a reasonable approximation.\n    float multiplier = area / 35000.0f;\n    complexity = complexity * multiplier + 1200;\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawPicture(\n    const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint ? *paint : SkPaint();\n  MetalHelper helper(\n      Ceiling() - CurrentComplexityScore(),\n      SkIRect::MakeLTRB(picture->cullRect().left(), picture->cullRect().top(),\n                        picture->cullRect().right(),\n                        picture->cullRect().bottom()));\n  picture->playback(&helper);\n  AccumulateComplexity(helper.ComplexityScore());\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::onDrawTextBlob(\n    const SkTextBlob* text_blob, SkScalar x, SkScalar y, const SkPaint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  current_paint_ = paint;\n  // DrawTextBlob has a high fixed cost, but if we call it multiple times\n  // per frame, that fixed cost is greatly reduced per subsequent call. This\n  // is likely because there is batching being done in SkCanvas.\n\n  // Increment draw_text_blob_count_ and calculate the cost at the end.\n  draw_text_blob_count_++;\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::ImageRect(\n    const SkISize& size, bool texture_backed,\n    SkCanvas::SrcRectConstraint constraint) {\n  if (IsComplex()) {\n    return;\n  }\n  // Two main groups here - texture-backed and non-texture-backed images.\n  //\n  // Within each group, they all perform within a few % of each other *except*\n  // when we have a strict constraint and anti-aliasing enabled.\n  unsigned int area = size.width() * size.height();\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  unsigned int complexity;\n  if (texture_backed) {\n    // Baseline for texture-backed SkImages.\n    // m = 1/23000\n    // c = 2.3\n    complexity = (area + 52900) * 2 / 115;\n    if (current_paint_ != SkPaint() &&\n        constraint == SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint &&\n        IsAntiAliased()) {\n      // There's about a 30% performance penalty from the baseline.\n      complexity *= 1.3f;\n    }\n  } else {\n    if (current_paint_ != SkPaint() &&\n        constraint == SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint &&\n        IsAntiAliased()) {\n      // m = 1/12200\n      // c = 2.75\n      complexity = (area + 33550) * 2 / 61;\n    } else {\n      // m = 1/14500\n      // c = 2.5\n      complexity = (area + 36250) * 4 / 145;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n#else\n\nunsigned int\nPictureMetalComplexityCalculator::MetalHelper::BatchedComplexity() {\n  // Calculate the impact of saveLayer.\n  unsigned int save_layer_complexity;\n  if (save_layer_count_ == 0) {\n    save_layer_complexity = 0;\n  } else {\n    // saveLayer seems to have two trends; if the count is < 200,\n    // then the individual cost of a saveLayer is higher than if\n    // the count is > 200.\n    //\n    // However, the trend is strange and we should gather more data to\n    // get a better idea of how to represent the trend. That being said, it's\n    // very unlikely we'll ever hit a DisplayList with 200+ saveLayer calls\n    // in it, so we will calculate based on the more reasonably anticipated\n    // range of less than 200, with the trend line more weighted towards the\n    // lower end of that range (as the data itself doesn't present as a straight\n    // line). Further, we will easily hit our cache thresholds with such a\n    // large number of saveLayer calls.\n    //\n    // m = 1/2\n    // c = 1\n    save_layer_complexity = (save_layer_count_ + 2) * 100000;\n  }\n\n  unsigned int draw_text_blob_complexity;\n  if (draw_text_blob_count_ == 0) {\n    draw_text_blob_complexity = 0;\n  } else {\n    // m = 1/240\n    // c = 0.75\n    draw_text_blob_complexity = (draw_text_blob_count_ + 180) * 2500 / 3;\n  }\n\n  return save_layer_complexity + draw_text_blob_complexity;\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::OnDrawLine(\n    float x0, float y0, float x1, float y1, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  // The curve here may be log-linear, although it doesn't really match up that\n  // well. To avoid costly computations, try and do a best fit of the data onto\n  // a linear graph as a very rough first order approximation.\n\n  float non_hairline_penalty = 1.0f;\n  float aa_penalty = 1.0f;\n\n  if (!IsHairline(paint)) {\n    non_hairline_penalty = 1.15f;\n  }\n  if (IsAntiAliased(paint)) {\n    aa_penalty = 1.4f;\n  }\n\n  // Use an approximation for the distance to avoid floating point or\n  // sqrt() calls.\n  float distance = abs(x0 - x1) + abs(y0 - y1);\n\n  // The baseline complexity is for a hairline stroke with no AA.\n  // m = 1/45\n  // c = 5\n  unsigned int complexity =\n      ((distance + 225) * 4 / 9) * non_hairline_penalty * aa_penalty;\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::OnDrawCircle(\n    float cx, float cy, float radius, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  unsigned int complexity;\n\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style(paint) == skity::Paint::Style::kFill_Style) {\n    // We can ignore pi here.\n    unsigned int area = radius * radius;\n    // m = 1/1300\n    // c = 5\n    complexity = (area + 6500) * 2 / 65;\n\n    // Penalty of around 5% when AA is disabled.\n    if (!IsAntiAliased(paint)) {\n      complexity *= 1.05f;\n    }\n  } else {\n    // Hairline vs non-hairline has no significant performance difference.\n    if (IsAntiAliased(paint)) {\n      // m = 1/7\n      // c = 7\n      complexity = (radius + 49) * 40 / 7;\n    } else {\n      // m = 1/16\n      // c = 8\n      complexity = (radius + 128) * 5 / 2;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::OnDrawOval(\n    skity::Rect const& oval, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  // DrawOval scales very roughly linearly with the bounding box width/height\n  // (not area) for stroked styles without AA.\n  //\n  // Filled styles and stroked styles with AA scale linearly with the bounding\n  // box area.\n  unsigned int area = oval.Width() * oval.Height();\n\n  unsigned int complexity;\n\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style(paint) == skity::Paint::Style::kFill_Style) {\n    // With filled styles, there is no significant AA penalty.\n    // m = 1/16000\n    // c = 0\n    complexity = area / 80;\n  } else {\n    if (IsAntiAliased(paint)) {\n      // m = 1/7500\n      // c = 0\n      complexity = area * 2 / 75;\n    } else {\n      // Take the average of the width and height.\n      unsigned int length = (oval.Width() + oval.Height()) / 2;\n\n      // m = 1/80\n      // c = 0\n      complexity = length * 5 / 2;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::OnDrawRect(\n    skity::Rect const& rect, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  unsigned int complexity;\n\n  // If stroked, cost scales linearly with the rectangle width/height.\n  // If filled, it scales with the area.\n  //\n  // Hairline stroke vs non hairline has no real penalty at smaller lengths,\n  // but it increases at larger lengths. There isn't enough data to get a good\n  // idea of the penalty at lengths > 1000px.\n  //\n  // There is also a kStrokeAndFill_Style that Skia exposes, but we do not\n  // currently use it anywhere in Flutter.\n  if (Style(paint) == skity::Paint::Style::kFill_Style) {\n    // No real difference for AA with filled styles.\n    unsigned int area = rect.Width() * rect.Height();\n\n    // m = 1/9000\n    // c = 0\n    complexity = area / 225;\n  } else {\n    // Take the average of the width and height.\n    unsigned int length = (rect.Width() + rect.Height()) / 2;\n\n    // There is a penalty for AA being *disabled*.\n    if (IsAntiAliased(paint)) {\n      // m = 1/65\n      // c = 0\n      complexity = length * 8 / 13;\n    } else {\n      // m = 1/35\n      // c = 0\n      complexity = length * 8 / 7;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::OnDrawRRect(\n    skity::RRect const& rrect, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  // RRects scale linearly with the area of the bounding rect.\n  unsigned int area = rrect.Width() * rrect.Height();\n\n  // Drawing RRects is split into two performance tiers; an expensive\n  // one and a less expensive one. Both scale linearly with area.\n  //\n  // Expensive: All filled style, symmetric w/AA.\n  bool expensive = (Style(paint) == skity::Paint::Style::kFill_Style) ||\n                   ((rrect.GetType() == skity::RRect::Type::kSimple) &&\n                    IsAntiAliased(paint));\n\n  unsigned int complexity;\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  if (expensive) {\n    // m = 1/25000\n    // c = 2\n    // An area of 7000px^2 ~= baseline timing of 0.0005ms.\n    complexity = (area + 10500) / 175;\n  } else {\n    // m = 1/7000\n    // c = 1.5\n    // An area of 16000px^2 ~= baseline timing of 0.0005ms.\n    complexity = (area + 50000) / 625;\n  }\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::OnDrawPath(\n    skity::Path const& path, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n  // There is negligible effect on the performance for hairline vs. non-hairline\n  // stroke widths.\n  //\n  // The data for filled styles is currently suspicious, so for now we are going\n  // to assign scores based on stroked styles.\n\n  unsigned int line_verb_cost, quad_verb_cost, conic_verb_cost, cubic_verb_cost;\n\n  if (IsAntiAliased(paint)) {\n    line_verb_cost = 75;\n    quad_verb_cost = 100;\n    conic_verb_cost = 160;\n    cubic_verb_cost = 210;\n  } else {\n    line_verb_cost = 67;\n    quad_verb_cost = 80;\n    conic_verb_cost = 140;\n    cubic_verb_cost = 210;\n  }\n\n  // There seems to be a fixed cost of around 1ms for calling drawPath.\n  unsigned int complexity =\n      200000 + CalculatePathComplexity(path, line_verb_cost, quad_verb_cost,\n                                       conic_verb_cost, cubic_verb_cost);\n\n  AccumulateComplexity(complexity);\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::OnSaveLayer(\n    const skity::Rect& bounds, const skity::Paint& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  save_layer_count_++;\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::OnDrawBlob(\n    const skity::TextBlob* blob, float x, float y, skity::Paint const& paint) {\n  if (IsComplex()) {\n    return;\n  }\n\n  // DrawTextBlob has a high fixed cost, but if we call it multiple times\n  // per frame, that fixed cost is greatly reduced per subsequent call. This\n  // is likely because there is batching being done in SkCanvas.\n\n  // Increment draw_text_blob_count_ and calculate the cost at the end.\n  draw_text_blob_count_++;\n}\n\nvoid PictureMetalComplexityCalculator::MetalHelper::OnDrawImageRect(\n    std::shared_ptr<skity::Image> image, const skity::Rect& src,\n    const skity::Rect& dst, const skity::SamplingOptions& sampling,\n    skity::Paint const* paint) {\n  if (IsComplex()) {\n    return;\n  }\n  // Two main groups here - texture-backed and non-texture-backed images.\n  //\n  // Within each group, they all perform within a few % of each other *except*\n  // when we have a strict constraint and anti-aliasing enabled.\n  size_t width = image->Width();\n  size_t height = image->Height();\n  unsigned int area = width * height;\n\n  // These values were worked out by creating a straight line graph (y=mx+c)\n  // approximately matching the measured data, normalising the data so that\n  // 0.0005ms resulted in a score of 100 then simplifying down the formula.\n  unsigned int complexity;\n  bool texture_backed = image->IsTextureBackend();\n  if (texture_backed) {\n    // Baseline for texture-backed SkImages.\n    // m = 1/23000\n    // c = 2.3\n    complexity = (area + 52900) * 2 / 115;\n    if (paint && IsAntiAliased(*paint)) {\n      // There's about a 30% performance penalty from the baseline.\n      complexity *= 1.3f;\n    }\n  } else {\n    if (paint && IsAntiAliased(*paint)) {\n      // m = 1/12200\n      // c = 2.75\n      complexity = (area + 33550) * 2 / 61;\n    } else {\n      // m = 1/14500\n      // c = 2.5\n      complexity = (area + 36250) * 4 / 145;\n    }\n  }\n\n  AccumulateComplexity(complexity);\n}\n\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/picture_complexity_metal.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_METAL_H_\n#define CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_METAL_H_\n\n#include <limits>\n#include <memory>\n\n#include \"clay/flow/flow_rendering_backend.h\"\n#include \"clay/flow/layers/picture_complexity.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass PictureMetalComplexityCalculator : public PictureComplexityCalculator {\n public:\n  static PictureMetalComplexityCalculator* GetInstance();\n\n#ifndef ENABLE_SKITY\n  unsigned int Compute(SkPicture* picture) override {\n    MetalHelper helper(\n        ceiling_,\n        SkIRect::MakeLTRB(picture->cullRect().left(), picture->cullRect().top(),\n                          picture->cullRect().right(),\n                          picture->cullRect().bottom()));\n    picture->playback(&helper);\n    return helper.ComplexityScore();\n  }\n#else\n  unsigned int Compute(skity::DisplayList* display_list) override {\n    MetalHelper helper(ceiling_);\n    display_list->Draw(&helper);\n    return helper.ComplexityScore();\n  }\n#endif  // ENABLE_SKITY\n\n  bool ShouldBeCached(unsigned int complexity_score) override {\n    // Set cache threshold at 1ms\n    return complexity_score > 200000u;\n  }\n\n  void SetComplexityCeiling(unsigned int ceiling) override {\n    ceiling_ = ceiling;\n  }\n\n private:\n#ifndef ENABLE_SKITY\n  class MetalHelper : public ComplexityCalculatorHelper {\n   public:\n    MetalHelper(unsigned int ceiling, SkIRect bounds)\n        : ComplexityCalculatorHelper(ceiling, bounds),\n          save_layer_count_(0),\n          draw_text_blob_count_(0) {}\n\n    void onDrawRect(const SkRect&, const SkPaint&) override;\n    void onDrawOval(const SkRect&, const SkPaint&) override;\n    void onDrawRRect(const SkRRect&, const SkPaint&) override;\n    void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;\n    void onDrawPath(const SkPath&, const SkPaint&) override;\n    void onDrawArc(const SkRect&, SkScalar, SkScalar, bool,\n                   const SkPaint&) override;\n    void onDrawPoints(PointMode, size_t, const SkPoint[],\n                      const SkPaint&) override;\n    void onDrawVerticesObject(const SkVertices*, SkBlendMode,\n                              const SkPaint&) override;\n    void onDrawImage2(const SkImage*, SkScalar, SkScalar,\n                      const SkSamplingOptions&, const SkPaint*) override;\n    void onDrawPicture(const SkPicture*, const SkMatrix*,\n                       const SkPaint*) override;\n    void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar,\n                        const SkPaint&) override;\n\n   protected:\n    void ImageRect(const SkISize& size, bool texture_backed,\n                   SkCanvas::SrcRectConstraint constraint) override;\n\n    unsigned int BatchedComplexity() override;\n\n   private:\n    unsigned int save_layer_count_;\n    unsigned int draw_text_blob_count_;\n  };\n#else\n  class MetalHelper : public ComplexityCalculatorHelperSkity {\n   public:\n    MetalHelper(unsigned int ceiling)\n        : ComplexityCalculatorHelperSkity(ceiling),\n          save_layer_count_(0),\n          draw_text_blob_count_(0) {}\n\n    void OnDrawLine(float x0, float y0, float x1, float y1,\n                    skity::Paint const& paint) override;\n    void OnDrawCircle(float cx, float cy, float radius,\n                      skity::Paint const& paint) override;\n    void OnDrawOval(skity::Rect const& oval,\n                    skity::Paint const& paint) override;\n    void OnDrawRect(skity::Rect const& rect,\n                    skity::Paint const& paint) override;\n    void OnDrawRRect(skity::RRect const& rrect,\n                     skity::Paint const& paint) override;\n    void OnDrawPath(skity::Path const& path,\n                    skity::Paint const& paint) override;\n    void OnSaveLayer(const skity::Rect& bounds,\n                     const skity::Paint& paint) override;\n    void OnDrawBlob(const skity::TextBlob* blob, float x, float y,\n                    skity::Paint const& paint) override;\n    void OnDrawImageRect(std::shared_ptr<skity::Image> image,\n                         const skity::Rect& src, const skity::Rect& dst,\n                         const skity::SamplingOptions& sampling,\n                         skity::Paint const* paint) override;\n\n    unsigned int BatchedComplexity() override;\n\n   private:\n    unsigned int save_layer_count_;\n    unsigned int draw_text_blob_count_;\n  };\n#endif  // ENABLE_SKITY\n\n  PictureMetalComplexityCalculator()\n      : ceiling_(std::numeric_limits<unsigned int>::max()) {}\n  static PictureMetalComplexityCalculator* instance_;\n\n  unsigned int ceiling_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PICTURE_COMPLEXITY_METAL_H_\n"
  },
  {
    "path": "clay/flow/layers/picture_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/picture_layer.h\"\n\n#include <functional>\n#include <memory>\n#include <utility>\n\n#include \"clay/flow/flow_rendering_backend.h\"\n#include \"clay/flow/layers/cacheable_layer.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n#ifndef ENABLE_SKITY\nPictureLayer::PictureLayer(const skity::Vec2& offset,\n                           clay::GPUObject<clay::PictureSkia> picture,\n                           bool is_complex, bool will_change,\n                           CacheStrategy strategy, bool has_lazy_image)\n    : offset_(offset),\n      picture_(std::move(picture)),\n      strategy_(strategy),\n      has_lazy_image_(has_lazy_image),\n      weak_factory_(this) {\n  if (picture_.object() != nullptr) {\n    bounds_ =\n        clay::ConvertSkRectToSkityRect(picture_.object()->raw()->cullRect());\n    bounds_.Offset(offset_.x, offset_.y);\n    picture_raster_cache_item_ = PictureRasterCacheItem::Make(\n        picture_.object()->raw().get(), offset_, is_complex, will_change);\n  }\n}\n#else\nPictureLayer::PictureLayer(const skity::Vec2& offset,\n                           clay::GPUObject<clay::PictureSkity> picture,\n                           bool is_complex, bool will_change,\n                           CacheStrategy strategy, bool has_lazy_image)\n    : offset_(offset),\n      picture_(std::move(picture)),\n      strategy_(strategy),\n      has_lazy_image_(has_lazy_image),\n      weak_factory_(this) {\n  if (picture_.object() != nullptr) {\n    skity::Rect rect = picture_.object()->raw()->GetBounds();\n    rect.Offset(offset_.x, offset_.y);\n    bounds_ = skity::Rect::MakeLTRB(rect.Left(), rect.Top(), rect.Right(),\n                                    rect.Bottom());\n    picture_raster_cache_item_ = PictureRasterCacheItem::Make(\n        picture_.object()->raw().get(), picture_.object()->unique_id(), offset_,\n        is_complex, will_change);\n  }\n}\n#endif  // ENABLE_SKITY\n\nbool PictureLayer::IsReplacing(DiffContext* context, const Layer* layer) const {\n  // Only return true for identical picture; This way\n  // ContainerLayer::DiffChildren can detect when a picture layer\n  // got inserted between other picture layers\n  auto old_layer = layer->as_picture_layer();\n  return old_layer != nullptr && offset_ == old_layer->offset_ &&\n         Compare(context->statistics(), this, old_layer);\n}\n\nvoid PictureLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  if (!context->IsSubtreeDirty()) {\n#ifndef NDEBUG\n    FML_DCHECK(old_layer);\n    auto prev = old_layer->as_picture_layer();\n    DiffContext::Statistics dummy_statistics;\n    // IsReplacing has already determined that the display list is same\n    FML_DCHECK(prev->offset_ == offset_ &&\n               Compare(dummy_statistics, this, prev));\n#endif\n  }\n#ifndef ENABLE_SKITY\n  if (HasAnimationRunning()) {\n    context->MarkSubtreeHasRasterAnimation();\n    if (!context->IsSubtreeDirty()) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n#endif  //   ENABLE_SKITY\n  if (has_lazy_image_) {\n    context->MarkSubtreeHasDeferredImage();\n    if (!context->IsSubtreeDirty()) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n  context->PushTransform(skity::Matrix::Translate(offset_.x, offset_.y));\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n  if (context->has_raster_cache()) {\n    context->SetTransform(\n        RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));\n  }\n#endif\n#ifndef ENABLE_SKITY\n  auto bounds = clay::ConvertSkRectToSkityRect(picture()->cullRect());\n#else\n  auto bounds = picture()->GetBounds();\n#endif  // ENABLE_SKITY\n  context->AddLayerBounds(bounds);\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nbool PictureLayer::Compare(DiffContext::Statistics& statistics,\n                           const PictureLayer* l1, const PictureLayer* l2) {\n#ifndef ENABLE_SKITY\n  // If text color or background color is running, just returns false.\n  if (l1->HasAnimationRunning()) {\n    return false;\n  }\n  const auto& dl1 = l1->picture_.object();\n  const auto& dl2 = l2->picture_.object();\n  if (dl1.get() == dl2.get() || dl1->raw() == dl2->raw() ||\n      dl1->unique_id() == dl2->unique_id()) {\n    statistics.AddSameInstancePicture();\n    return true;\n  }\n  statistics.AddNewPicture();\n  return false;\n\n#else\n  // TODO(feiyue:1998): Wait skity to provide more information to determine if\n  // two pictures are same.\n  return l1->picture() == l2->picture();\n#endif  // ENABLE_SKITY\n}\n\nvoid PictureLayer::Preroll(PrerollContext* context) {\n  context->has_running_picture_animation = HasAnimationRunning();\n\n  std::function<void(bool)> decode_callback =\n      [self = weak_factory_.GetWeakPtr(),\n       request_new_frame = context->request_new_frame](bool success) {\n        if (self && !self->is_empty()) {\n          request_new_frame();\n        }\n      };\n\n#ifndef ENABLE_SKITY\n// TODO(feiyue.1998): Support lazy image logic in skia.\n#else\n  if (has_lazy_image_) {\n    has_lazy_image_ = clay::SkityLazyImageTraveller::Traversal(picture().get(),\n                                                               decode_callback);\n  }\n#endif  // ENABLE_SKITY\n\n  context->has_deferred_image = has_lazy_image_;\n\n  AutoCache cache = AutoCache(picture_raster_cache_item_.get(), context,\n                              context->state_stack.transform_4x4());\n\n  if (context->has_running_picture_animation || has_lazy_image_) {\n    cache.ShouldNotBeCached();\n  }\n\n  set_paint_bounds(bounds_);\n}\n\nvoid PictureLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(picture());\n  FML_DCHECK(needs_painting(context));\n\n  auto mutator = context.state_stack.save();\n  mutator.translate(offset_.x, offset_.y);\n\n  if (strategy_ != CacheStrategy::NotCache && context.raster_cache) {\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n    // Always apply the integral transform in the presence of a raster cache\n    // whether or not we successfully draw from the cache\n    mutator.integralTransform();\n#endif\n    if (picture_raster_cache_item_) {\n      clay::GrPaint paint;\n      if (picture_raster_cache_item_->Draw(context,\n                                           context.state_stack.fill(paint))) {\n        TRACE_EVENT_INSTANT(\"clay\", \"raster cache hit\");\n        return;\n      }\n    }\n  }\n\n#ifndef ENABLE_SKITY\n  if (context.enable_leaf_layer_tracing) {\n    const auto canvas_size = context.canvas->getBaseLayerSize();\n    auto offscreen_surface = std::make_unique<OffscreenSurface>(\n        context.gr_context,\n        skity::Vec2(canvas_size.fWidth, canvas_size.fHeight));\n\n    const auto& ctm = context.canvas->getTotalMatrix();\n\n    const auto start_time = fml::TimePoint::Now();\n    {\n      // render picture to offscreen surface.\n      auto* canvas = offscreen_surface->GetCanvas();\n      SkAutoCanvasRestore save(canvas, true);\n      canvas->clear(SK_ColorTRANSPARENT);\n      canvas->setMatrix(ctm);\n      picture()->playback(canvas);\n      canvas->flush();\n    }\n    const fml::TimeDelta offscreen_render_time =\n        fml::TimePoint::Now() - start_time;\n\n    const skity::Rect device_bounds = RasterCacheUtil::GetDeviceBounds(\n        paint_bounds(), clay::ConvertSkMatrixToSkityMatrix(ctm));\n    sk_sp<SkData> raster_data = offscreen_surface->GetRasterData(true);\n    LayerSnapshotData snapshot_data(unique_id(), offscreen_render_time,\n                                    raster_data, device_bounds);\n    context.layer_snapshot_store->Add(snapshot_data);\n  }\n\n  picture()->playback(context.canvas);\n#else\n  picture()->Draw(context.canvas);\n#endif  // ENABLE_SKITY\n\n#ifndef NDEBUG\n  if (context.enable_debug_borders) {\n    DrawDebugBorders(context.canvas, bounds_);\n  }\n\n  if (context.enable_raster_cache_tag && picture_raster_cache_item_ &&\n      picture_raster_cache_item_->has_been_cached()) {\n    // Generated raster cache, but not used.\n    DrawRasterCacheTag(context.canvas, paint_bounds().Width() / 2,\n                       paint_bounds().Height() / 2, 0);\n  }\n#endif  // NDEBUG\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/picture_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PICTURE_LAYER_H_\n#define CLAY_FLOW_LAYERS_PICTURE_LAYER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/layers/picture_raster_cache_item.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#ifdef ENABLE_SKITY\n#include \"clay/gfx/skity/picture_skity.h\"\n#endif  // ENABLE_SKITY\n\nnamespace clay {\n\nusing clay::GPUObject;\nclass Layer;\n\nclass PictureLayer : public Layer {\n public:\n  static constexpr size_t kMaxBytesToCompare = 10000;\n\n#ifndef ENABLE_SKITY\n  PictureLayer(const skity::Vec2& offset,\n               clay::GPUObject<clay::PictureSkia> picture, bool is_complex,\n               bool will_change, CacheStrategy strategy = CacheStrategy::None,\n               bool has_lazy_image = false);\n\n  sk_sp<SkPicture> picture() const { return picture_.object()->raw(); }\n\n  clay::PictureSkia* picture_skia() const { return picture_.object().get(); }\n\n  RasterCacheKeyID caching_key_id() const override {\n    return RasterCacheKeyID(picture_.object()->unique_id(),\n                            RasterCacheKeyType::kPicture);\n  }\n#else\n  PictureLayer(const skity::Vec2& offset,\n               clay::GPUObject<clay::PictureSkity> picture, bool is_complex,\n               bool will_change, CacheStrategy strategy = CacheStrategy::None,\n               bool has_lazy_image = false);\n\n  std::shared_ptr<skity::DisplayList> picture() const {\n    return picture_.object()->raw();\n  }\n\n  clay::PictureSkity* picture_skity() const { return picture_.object().get(); }\n\n  RasterCacheKeyID caching_key_id() const override {\n    return RasterCacheKeyID(picture_.object()->unique_id(),\n                            RasterCacheKeyType::kPicture);\n  }\n#endif  // ENABLE_SKITY\n\n  bool IsReplacing(DiffContext* context, const Layer* layer) const override;\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  const PictureLayer* as_picture_layer() const override { return this; }\n\n  void Preroll(PrerollContext* frame) override;\n\n  void Paint(PaintContext& context) const override;\n\n  const PictureRasterCacheItem* raster_cache_item() const {\n    return picture_raster_cache_item_.get();\n  }\n\n#ifndef NDEBUG\n  virtual std::string DebugName() const override { return \"PictureLayer\"; }\n#endif\n\n private:\n  std::unique_ptr<PictureRasterCacheItem> picture_raster_cache_item_;\n\n  skity::Vec2 offset_;\n  skity::Rect bounds_;\n\n#ifndef ENABLE_SKITY\n  clay::GPUObject<clay::PictureSkia> picture_;\n#else\n  clay::GPUObject<clay::PictureSkity> picture_;\n#endif  // ENABLE_SKITY\n\n  static bool Compare(DiffContext::Statistics& statistics,\n                      const PictureLayer* l1, const PictureLayer* l2);\n\n  [[maybe_unused]] CacheStrategy strategy_;\n  bool has_lazy_image_ = false;\n\n  fml::WeakPtrFactory<PictureLayer> weak_factory_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PictureLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PICTURE_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/picture_raster_cache_item.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/picture_raster_cache_item.h\"\n\n#include <optional>\n#include <utility>\n\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/raster_cache_item.h\"\n#include \"clay/flow/raster_cache_key.h\"\n#include \"clay/flow/raster_cache_util.h\"\n\nnamespace clay {\n\nstatic bool IsPictureWorthRasterizing(\n#ifndef ENABLE_SKITY\n    SkPicture* picture,\n#else\n    skity::DisplayList* picture,\n#endif\n    bool will_change, bool is_complex,\n    PictureComplexityCalculator* complexity_calculator,\n    skity::Vec2 frame_size) {\n  if (will_change) {\n    // If the display list is going to change in the future, there is no point\n    // in doing to extra work to rasterize.\n    return false;\n  }\n\n#ifndef ENABLE_SKITY\n  auto bounds = clay::ConvertSkRectToSkityRect(picture->cullRect());\n#else\n  auto bounds = picture->GetBounds();\n#endif  // ENABLE_SKITY\n  auto width = bounds.Width();\n  auto height = bounds.Height();\n\n  if (picture == nullptr || !RasterCacheUtil::CanRasterizeRect(bounds)) {\n    // No point in deciding whether the display list is worth rasterizing if it\n    // cannot be rasterized at all.\n    return false;\n  }\n\n  if (width > frame_size.x || height > frame_size.y) {\n    // Don't cache if the display list is larger than current frame.\n    return false;\n  }\n\n  if (is_complex) {\n    // The caller seems to have extra information about the display list and\n    // thinks the display list is always worth rasterizing.\n    return true;\n  }\n\n  unsigned int complexity_score = complexity_calculator->Compute(picture);\n\n  return complexity_calculator->ShouldBeCached(complexity_score);\n}\n\n#ifndef ENABLE_SKITY\nPictureRasterCacheItem::PictureRasterCacheItem(SkPicture* picture,\n                                               const skity::Vec2& offset,\n                                               bool is_complex,\n                                               bool will_change)\n    : RasterCacheItem(\n          RasterCacheKeyID(picture->uniqueID(), RasterCacheKeyType::kPicture),\n          CacheState::kCurrent),\n      picture_(picture),\n      offset_(offset),\n      is_complex_(is_complex),\n      will_change_(will_change) {}\n\nstd::unique_ptr<PictureRasterCacheItem> PictureRasterCacheItem::Make(\n    SkPicture* picture, const skity::Vec2& offset, bool is_complex,\n    bool will_change) {\n  return std::make_unique<PictureRasterCacheItem>(picture, offset, is_complex,\n                                                  will_change);\n}\n#else\nPictureRasterCacheItem::PictureRasterCacheItem(skity::DisplayList* picture,\n                                               uint32_t cache_key_id,\n                                               const skity::Vec2& offset,\n                                               bool is_complex,\n                                               bool will_change)\n    : RasterCacheItem(\n          RasterCacheKeyID(cache_key_id, RasterCacheKeyType::kPicture),\n          CacheState::kCurrent),\n      picture_(picture),\n      offset_(offset),\n      is_complex_(is_complex),\n      will_change_(will_change) {}\n\nstd::unique_ptr<PictureRasterCacheItem> PictureRasterCacheItem::Make(\n    skity::DisplayList* picture, uint32_t cache_key_id,\n    const skity::Vec2& offset, bool is_complex, bool will_change) {\n  return std::make_unique<PictureRasterCacheItem>(picture, cache_key_id, offset,\n                                                  is_complex, will_change);\n}\n#endif  // ENABLE_SKITY\n\nvoid PictureRasterCacheItem::PrerollSetup(PrerollContext* context,\n                                          const skity::Matrix& matrix) {\n  cache_state_ = CacheState::kNone;\n#ifndef ENABLE_SKITY\n  PictureComplexityCalculator* complexity_calculator =\n      context->gr_context ? PictureComplexityCalculator::GetForBackend(\n                                context->gr_context->backend())\n                          : PictureComplexityCalculator::GetForSoftware();\n#else\n  PictureComplexityCalculator* complexity_calculator =\n      PictureComplexityCalculator::GetForSoftware();\n#endif  // ENABLE_SKITY\n\n  auto frame_size = context->compositor_state\n                        ? context->compositor_state->GetFrameSize()\n                        : skity::Vec2{1e9, 1e9};\n  // If there is a deferred decoding image, skip raster cache.\n  if (!IsPictureWorthRasterizing(\n          picture_,\n          will_change_ || context->has_deferred_image ||\n              context->has_running_picture_animation,\n          is_complex_ || context->parent_has_running_transform_animation,\n          complexity_calculator, frame_size)) {\n    // We only deal with display lists that are worthy of rasterization.\n    return;\n  }\n\n  transformation_matrix_ = matrix;\n  transformation_matrix_.PreTranslate(offset_.x, offset_.y);\n\n  if (!transformation_matrix_.Invert(nullptr)) {\n    // The matrix was singular. No point in going further.\n    return;\n  }\n\n  if (context->raster_cached_entries && context->raster_cache) {\n    context->raster_cached_entries->push_back(this);\n    cache_state_ = CacheState::kCurrent;\n  }\n  return;\n}\n\nvoid PictureRasterCacheItem::PrerollFinalize(PrerollContext* context,\n                                             const skity::Matrix& matrix) {\n  if (cache_state_ == CacheState::kNone || !context->raster_cache ||\n      !context->raster_cached_entries) {\n    return;\n  }\n  auto* raster_cache = context->raster_cache;\n#ifndef ENABLE_SKITY\n  skity::Rect bounds = clay::ConvertSkRectToSkityRect(picture_->cullRect());\n#else\n  skity::Rect bounds = picture_->GetBounds();\n#endif\n  bounds.Offset(offset_.x, offset_.y);\n  bool visible = !context->state_stack.content_culled(bounds);\n  auto cache_info = raster_cache->MarkSeen(key_id_, matrix, visible);\n  if (!visible ||\n      cache_info.accesses_since_visible <= raster_cache->access_threshold()) {\n    cache_state_ = kNone;\n  } else {\n    // kCallerCanApplyOpacity can only be enabled when display list\n    // has cached.\n    // See issue: https://github.com/flutter/flutter/issues/120455\n    if (cache_info.has_image) {\n      context->renderable_state_flags |=\n          LayerStateStack::kCallerCanApplyOpacity;\n    }\n    cache_state_ = kCurrent;\n  }\n  return;\n}\n\nstatic const auto* flow_type = \"RasterCacheFlow::DisplayList\";\n\nbool PictureRasterCacheItem::Draw(const PaintContext& context,\n                                  const clay::GrPaint* paint) const {\n  return Draw(context, context.canvas, paint);\n}\n\nbool PictureRasterCacheItem::Draw(const PaintContext& context,\n                                  clay::GrCanvas* canvas,\n                                  const clay::GrPaint* paint) const {\n  if (!context.raster_cache || !canvas) {\n    return false;\n  }\n  if (cache_state_ == CacheState::kCurrent) {\n    return context.raster_cache->Draw(key_id_, *canvas, paint);\n  }\n  return false;\n}\n\nbool PictureRasterCacheItem::TryToPrepareRasterCache(\n    const PaintContext& context, bool parent_cached) const {\n  has_been_cached_ = false;\n  // If we don't have raster_cache we should not cache the current display_list.\n  // If the current node's ancestor has been cached we also should not cache the\n  // current node. In the current frame, the raster_cache will collect all\n  // display_list or picture_list to calculate the memory they used, we\n  // shouldn't cache the current node if the memory is more significant than the\n  // limit.\n  if (cache_state_ == kNone || !context.raster_cache || parent_cached ||\n      !context.raster_cache->GenerateNewCacheInThisFrame()) {\n    return false;\n  }\n#ifndef ENABLE_SKITY\n  skity::Rect bounds = clay::ConvertSkRectToSkityRect(picture_->cullRect());\n#else\n  skity::Rect bounds = picture_->GetBounds();\n#endif  // ENABLE_SKITY\n\n  bounds.Offset(offset_.x, offset_.y);\n  RasterCache::Context r_context = {\n      // clang-format off\n      .gr_context         = context.gr_context,\n#ifndef ENABLE_SKITY\n      .dst_color_space    = context.dst_color_space,\n#endif // ENABLE_SKITY\n      .matrix             = transformation_matrix_,\n      .logical_rect       = bounds,\n      .flow_type          = flow_type,\n      // clang-format on\n  };\n  has_been_cached_ = context.raster_cache->UpdateCacheEntry(\n      GetId().value(), r_context, [picture = picture_](clay::GrCanvas* canvas) {\n#ifndef ENABLE_SKITY\n        picture->playback(canvas);\n#else\n        picture->Draw(canvas);\n#endif  // ENABLE_SKITY\n      });\n  return has_been_cached_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/picture_raster_cache_item.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PICTURE_RASTER_CACHE_ITEM_H_\n#define CLAY_FLOW_LAYERS_PICTURE_RASTER_CACHE_ITEM_H_\n\n#include <memory>\n#include <optional>\n\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/flow/raster_cache_item.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass PictureRasterCacheItem : public RasterCacheItem {\n public:\n  void PrerollSetup(PrerollContext* context,\n                    const skity::Matrix& matrix) override;\n\n  void PrerollFinalize(PrerollContext* context,\n                       const skity::Matrix& matrix) override;\n\n#ifndef ENABLE_SKITY\n  PictureRasterCacheItem(SkPicture* picture, const skity::Vec2& offset,\n                         bool is_complex = true, bool will_change = false);\n\n  static std::unique_ptr<PictureRasterCacheItem> Make(SkPicture*,\n                                                      const skity::Vec2& offset,\n                                                      bool is_complex,\n                                                      bool will_change);\n#else\n  PictureRasterCacheItem(skity::DisplayList* display_list,\n                         uint32_t cache_key_id, const skity::Vec2& offset,\n                         bool is_complex = true, bool will_change = false);\n\n  static std::unique_ptr<PictureRasterCacheItem> Make(skity::DisplayList*,\n                                                      uint32_t cache_key_id,\n                                                      const skity::Vec2& offset,\n                                                      bool is_complex,\n                                                      bool will_change);\n#endif  // ENABLE_SKITY\n\n  bool Draw(const PaintContext& context,\n            const clay::GrPaint* paint) const override;\n\n  bool Draw(const PaintContext& context, clay::GrCanvas* canvas,\n            const clay::GrPaint* paint) const override;\n\n  bool TryToPrepareRasterCache(const PaintContext& context,\n                               bool parent_cached = false) const override;\n\n  void ModifyMatrix(skity::Vec2 offset) const {\n    matrix_ = matrix_.PreTranslate(offset.x, offset.y);\n  }\n\n#ifndef ENABLE_SKITY\n  const SkPicture* picture() const { return picture_; }\n#else\n  const skity::DisplayList* picture() const { return picture_; }\n#endif\n\n private:\n  skity::Matrix transformation_matrix_;\n#ifndef ENABLE_SKITY\n  SkPicture* picture_;\n#else\n  skity::DisplayList* picture_;\n#endif\n  skity::Vec2 offset_;\n  bool is_complex_;\n  bool will_change_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PICTURE_RASTER_CACHE_ITEM_H_\n"
  },
  {
    "path": "clay/flow/layers/platform_view_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/platform_view_layer.h\"\n\n#include <memory>\n#include <utility>\n\nnamespace clay {\n\nPlatformViewLayer::PlatformViewLayer(const skity::Vec2& offset,\n                                     const skity::Vec2& size, int64_t view_id)\n    : offset_(offset), size_(size), view_id_(view_id) {}\n\nvoid PlatformViewLayer::Preroll(PrerollContext* context) {\n  set_paint_bounds(\n      skity::Rect::MakeXYWH(offset_.x, offset_.y, size_.x, size_.y));\n\n  if (context->compositor_state == nullptr) {\n    FML_DLOG(ERROR) << \"Trying to embed a platform view but the PrerollContext \"\n                       \"does not support embedding\";\n    return;\n  }\n  context->has_platform_view = true;\n  set_subtree_has_platform_view(true);\n  MutatorsStack mutators;\n  context->state_stack.fill(&mutators);\n  std::unique_ptr<EmbeddedViewParams> params =\n      std::make_unique<EmbeddedViewParams>(context->state_stack.transform_4x4(),\n                                           size_, mutators);\n  context->compositor_state->PrerollCompositeEmbeddedView(view_id_,\n                                                          std::move(params));\n}\n\nvoid PlatformViewLayer::Paint(PaintContext& context) const {\n  if (context.compositor_state == nullptr) {\n    FML_DLOG(ERROR) << \"Trying to embed a platform view but the PaintContext \"\n                       \"does not support embedding\";\n    return;\n  }\n  clay::GrCanvas* canvas =\n      context.compositor_state->CompositeEmbeddedView(view_id_);\n  context.canvas = canvas;\n  context.state_stack.set_delegate(context.canvas);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/platform_view_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PLATFORM_VIEW_LAYER_H_\n#define CLAY_FLOW_LAYERS_PLATFORM_VIEW_LAYER_H_\n\n#include <string>\n\n#include \"clay/flow/layers/layer.h\"\n\nnamespace clay {\n\nclass PlatformViewLayer : public Layer {\n public:\n  PlatformViewLayer(const skity::Vec2& offset, const skity::Vec2& size,\n                    int64_t view_id);\n\n  void Preroll(PrerollContext* context) override;\n  void Paint(PaintContext& context) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"PlatformViewLayer\"; }\n#endif\n\n private:\n  skity::Vec2 offset_;\n  skity::Vec2 size_;\n  int64_t view_id_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PlatformViewLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PLATFORM_VIEW_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/platform_view_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"clay/flow/layers/clip_rect_layer.h\"\n#include \"clay/flow/layers/platform_view_layer.h\"\n#include \"clay/flow/layers/transform_layer.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing PlatformViewLayerTest = LayerTest;\n\nTEST_F(PlatformViewLayerTest, NullViewEmbedderDoesntPrerollCompositeOrPaint) {\n  const skity::Vec2 layer_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 layer_size = skity::Vec2(8.0f, 8.0f);\n  const int64_t view_id = 0;\n  auto layer =\n      std::make_shared<PlatformViewLayer>(layer_offset, layer_size, view_id);\n\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->has_platform_view);\n  EXPECT_EQ(layer->paint_bounds(),\n            skity::Rect::MakeSize({layer_size.x, layer_size.y})\n                .MakeOffset(layer_offset.x, layer_offset.y));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_FALSE(layer->subtree_has_platform_view());\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(paint_context().canvas, &mock_canvas());\n  EXPECT_EQ(mock_canvas().draw_calls(), std::vector<MockCanvas::DrawCall>());\n}\n\nTEST_F(PlatformViewLayerTest, ClippedPlatformViewPrerollsAndPaintsNothing) {\n  const skity::Vec2 layer_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 layer_size = skity::Vec2(8.0f, 8.0f);\n  const skity::Rect child_clip =\n      skity::Rect::MakeLTRB(20.0f, 20.0f, 40.0f, 40.0f);\n  const skity::Rect parent_clip =\n      skity::Rect::MakeLTRB(50.0f, 50.0f, 80.0f, 80.0f);\n  const int64_t view_id = 0;\n  auto layer =\n      std::make_shared<PlatformViewLayer>(layer_offset, layer_size, view_id);\n  auto child_clip_layer =\n      std::make_shared<ClipRectLayer>(child_clip, Clip::hardEdge);\n  auto parent_clip_layer =\n      std::make_shared<ClipRectLayer>(parent_clip, Clip::hardEdge);\n  parent_clip_layer->Add(child_clip_layer);\n  child_clip_layer->Add(layer);\n\n  CompositorState compositor_state({64, 64});\n  preroll_context()->compositor_state = &compositor_state;\n\n  parent_clip_layer->Preroll(preroll_context());\n  EXPECT_TRUE(preroll_context()->has_platform_view);\n  EXPECT_EQ(layer->paint_bounds(),\n            skity::Rect::MakeSize({layer_size.x, layer_size.y})\n                .MakeOffset(layer_offset.x, layer_offset.y));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_TRUE(child_clip_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(parent_clip_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->subtree_has_platform_view());\n  EXPECT_TRUE(child_clip_layer->subtree_has_platform_view());\n  EXPECT_TRUE(parent_clip_layer->subtree_has_platform_view());\n\n  parent_clip_layer->Paint(paint_context());\n  EXPECT_EQ(paint_context().canvas, &mock_canvas());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(parent_clip),\n                   SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}},\n           MockCanvas::DrawCall{1, MockCanvas::SaveData{2}},\n           MockCanvas::DrawCall{\n               2,\n               MockCanvas::ClipRectData{\n                   clay::ConvertSkityRectToSkRect(child_clip),\n                   SkClipOp::kIntersect, MockCanvas::kHard_ClipEdgeStyle}},\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(PlatformViewLayerTest, OpacityInheritance) {\n  const skity::Vec2 layer_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 layer_size = skity::Vec2(8.0f, 8.0f);\n  const int64_t view_id = 0;\n  auto layer =\n      std::make_shared<PlatformViewLayer>(layer_offset, layer_size, view_id);\n\n  PrerollContext* context = preroll_context();\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(context->renderable_state_flags, 0);\n}\n\nTEST_F(PlatformViewLayerTest, StateTransfer) {\n  const skity::Matrix transform1 = skity::Matrix::Translate(5, 5);\n  const skity::Matrix transform2 = skity::Matrix::Translate(15, 15);\n  const skity::Matrix combined_transform = skity::Matrix::Translate(20, 20);\n  const skity::Vec2 layer_offset = skity::Vec2(0.0f, 0.0f);\n  const skity::Vec2 layer_size = skity::Vec2(8.0f, 8.0f);\n  const int64_t view_id = 0;\n  const SkPath path1 = SkPath().addOval({10, 10, 20, 20});\n  const SkPath path2 = SkPath().addOval({15, 15, 30, 30});\n\n  // transform_layer1\n  //   |- child1\n  //   |- platform_layer\n  //   |- transform_layer2\n  //        |- child2\n  auto transform_layer1 = std::make_shared<TransformLayer>(transform1);\n  auto transform_layer2 = std::make_shared<TransformLayer>(transform2);\n  auto platform_layer =\n      std::make_shared<PlatformViewLayer>(layer_offset, layer_size, view_id);\n  auto child1 = std::make_shared<MockLayer>(path1);\n  child1->set_expected_paint_matrix(transform1);\n  auto child2 = std::make_shared<MockLayer>(path2);\n  child2->set_expected_paint_matrix(combined_transform);\n  transform_layer1->Add(child1);\n  transform_layer1->Add(platform_layer);\n  transform_layer1->Add(transform_layer2);\n  transform_layer2->Add(child2);\n\n  CompositorState compositor_state({64, 64});\n\n  PrerollContext* preroll_ctx = preroll_context();\n  preroll_ctx->compositor_state = &compositor_state;\n  transform_layer1->Preroll(preroll_ctx);\n\n  PaintContext& paint_ctx = paint_context();\n  transform_layer1->Paint(paint_ctx);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/punch_hole_layer.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/punch_hole_layer.h\"\n\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nPunchHoleLayer::PunchHoleLayer(const skity::Rect& punch_hole)\n    : punch_hole_(punch_hole) {}\n\nvoid PunchHoleLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(old_layer);\n    auto prev = old_layer->as_punch_hole_layer();\n    if (punch_hole_ != prev->PunchHoleRect()) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(prev));\n    }\n  }\n  context->AddLayerBounds(punch_hole_);\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid PunchHoleLayer::Preroll(PrerollContext* context) {\n  context->has_punch_hole_layer = true;\n  context->renderable_state_flags = LayerStateStack::kCallerCanApplyOpacity;\n  set_paint_bounds(punch_hole_);\n  set_subtree_has_punch_hole(true);\n}\n\nvoid PunchHoleLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n  if (punch_hole_.IsEmpty()) {\n    return;\n  }\n  clay::GrAutoCanvasRestore save_restore(context.canvas, true);\n  clay::GrPaint paint;\n  context.state_stack.fill(paint);\n  PAINT_SET_STYLE(paint, clay::GrPaint::kFill_Style);\n  PAINT_SET_BLEND_MODE(paint, clay::BlendMode::kClear);\n  CANVAS_DRAW_RECT(context.canvas, punch_hole_, paint);\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/punch_hole_layer.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_PUNCH_HOLE_LAYER_H_\n#define CLAY_FLOW_LAYERS_PUNCH_HOLE_LAYER_H_\n\n#include <string>\n\n#include \"clay/flow/layers/layer.h\"\n\nnamespace clay {\nclass PunchHoleLayer : public Layer {\n public:\n  explicit PunchHoleLayer(const skity::Rect& punch_hole);\n\n  bool IsReplacing(DiffContext* context, const Layer* layer) const override {\n    return layer->as_punch_hole_layer() != nullptr;\n  }\n\n  const PunchHoleLayer* as_punch_hole_layer() const override { return this; }\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  void Preroll(PrerollContext* context) override;\n  void Paint(PaintContext& context) const override;\n\n  const skity::Rect& PunchHoleRect() const { return punch_hole_; }\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"PunchHoleLayer\"; }\n#endif\n\n private:\n  skity::Rect punch_hole_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PunchHoleLayer);\n};\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_PUNCH_HOLE_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/punch_hole_layer_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/layers/punch_hole_layer.h\"\n#include \"clay/flow/testing/diff_context_test.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing PunchHoleLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(PunchHoleLayerTest, PaintingEmptyLayerDies) {\n  skity::Rect empty_rect{};\n  auto layer = std::make_shared<PunchHoleLayer>(empty_rect);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_TRUE(preroll_context()->has_punch_hole_layer);\n}\n#endif\n\nTEST_F(PunchHoleLayerTest, OpacityInheritance) {\n  const skity::Rect rect = skity::Rect::MakeXYWH(0, 0, 50, 50);\n  auto layer = std::make_shared<PunchHoleLayer>(rect);\n\n  // The punch hole layer always reports opacity compatibility.\n  PrerollContext* context = preroll_context();\n  layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n}\n\nTEST_F(PunchHoleLayerTest, PunchHoleRectSize) {\n  const skity::Rect rect = skity::Rect::MakeXYWH(0, 0, 50, 50);\n  auto layer = std::make_shared<PunchHoleLayer>(rect);\n  EXPECT_EQ(layer->PunchHoleRect(), skity::Rect::MakeXYWH(0, 0, 50, 50));\n}\n\nTEST_F(PunchHoleLayerTest, AsPunchHoleLayer) {\n  const skity::Rect rect = skity::Rect::MakeXYWH(0, 0, 50, 50);\n  auto layer1 = std::make_shared<PunchHoleLayer>(rect);\n  auto layer2 = std::make_shared<ContainerLayer>();\n\n  EXPECT_TRUE(layer1->as_punch_hole_layer() != nullptr);\n  EXPECT_TRUE(layer2->as_punch_hole_layer() == nullptr);\n}\n\nTEST_F(PunchHoleLayerTest, PaintNormalRect) {\n  const skity::Rect rect = skity::Rect::MakeXYWH(0, 0, 50, 50);\n  auto layer = std::make_shared<PunchHoleLayer>(rect);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), rect);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n\n  layer->Paint(paint_context());\n  SkPaint paint;\n  paint.setBlendMode(SkBlendMode::kClear);\n  paint.setStyle(SkPaint::kFill_Style);\n  const auto expected_draw_calls = std::vector{MockCanvas::DrawCall{\n      0,\n      MockCanvas::DrawRectData{clay::ConvertSkityRectToSkRect(rect), paint}}};\n  EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls);\n}\n\nusing PunchHoleLayerDiffTest = DiffContextTest;\n\nTEST_F(PunchHoleLayerDiffTest, PunchHoleInRetainedLayer) {\n  MockLayerTree tree1;\n  auto container = std::make_shared<ContainerLayer>();\n  tree1.root()->Add(container);\n  auto layer = std::make_shared<PunchHoleLayer>(skity::Rect::MakeWH(100, 100));\n  container->Add(layer);\n\n  MockLayerTree tree2;\n  tree2.root()->Add(container);  // retained layer\n\n  auto damage = DiffLayerTree(tree1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(0, 0, 100, 100));\n\n  damage = DiffLayerTree(tree2, tree1);\n  EXPECT_TRUE(damage.frame_damage.IsEmpty());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/shader_mask_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/shader_mask_layer.h\"\n\n#include <utility>\n\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nShaderMaskLayer::ShaderMaskLayer(\n    std::shared_ptr<clay::ColorSource> color_source,\n    const skity::Rect& mask_rect, clay::BlendMode blend_mode)\n    : CacheableContainerLayer(\n          RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer),\n      color_source_(std::move(color_source)),\n      mask_rect_(mask_rect),\n      blend_mode_(blend_mode) {}\n\nvoid ShaderMaskLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  auto* prev = static_cast<const ShaderMaskLayer*>(old_layer);\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(prev);\n    if (color_source_ != prev->color_source_ ||\n        mask_rect_ != prev->mask_rect_ || blend_mode_ != prev->blend_mode_) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n  if (context->has_raster_cache()) {\n    context->SetTransform(\n        RasterCacheUtil::GetIntegralTransCTM(context->GetTransform()));\n  }\n#endif\n  DiffChildren(context, prev);\n\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid ShaderMaskLayer::Preroll(PrerollContext* context) {\n  Layer::AutoPrerollSaveLayerState save =\n      Layer::AutoPrerollSaveLayerState::Create(context);\n  AutoCache cache = AutoCache(layer_raster_cache_item_.get(), context,\n                              context->state_stack.transform_4x4());\n\n  ContainerLayer::Preroll(context);\n  // We always paint with a saveLayer (or a cached rendering),\n  // so we can always apply opacity in any of those cases.\n  context->renderable_state_flags = kSaveLayerRenderFlags;\n}\n\nvoid ShaderMaskLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n\n  auto mutator = context.state_stack.save();\n\n  if (context.raster_cache) {\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n    mutator.integralTransform();\n#endif\n\n    clay::GrPaint paint;\n    if (layer_raster_cache_item_->Draw(context,\n                                       context.state_stack.fill(paint))) {\n      return;\n    }\n  }\n  auto shader_rect =\n      skity::Rect::MakeWH(mask_rect_.Width(), mask_rect_.Height());\n\n  mutator.saveLayer(paint_bounds());\n  PaintChildren(context);\n  clay::GrPaint paint;\n  PAINT_SET_BLEND_MODE(paint, blend_mode_);\n  if (color_source_) {\n    PAINT_SET_SHADER(paint, color_source_->gr_object());\n  }\n  CANVAS_TRANSLATE(context.canvas, mask_rect_.Left(), mask_rect_.Top());\n  CANVAS_DRAW_RECT(context.canvas, shader_rect, paint);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/shader_mask_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_SHADER_MASK_LAYER_H_\n#define CLAY_FLOW_LAYERS_SHADER_MASK_LAYER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/flow/layers/cacheable_layer.h\"\n#include \"clay/gfx/style/color_source.h\"\n\nnamespace clay {\n\nclass ShaderMaskLayer : public CacheableContainerLayer {\n public:\n  ShaderMaskLayer(std::shared_ptr<clay::ColorSource> color_source,\n                  const skity::Rect& mask_rect, clay::BlendMode blend_mode);\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  void Preroll(PrerollContext* context) override;\n\n  void Paint(PaintContext& context) const override;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"ShaderMaskLayer\"; }\n#endif\n\n private:\n  std::shared_ptr<clay::ColorSource> color_source_;\n  skity::Rect mask_rect_;\n  clay::BlendMode blend_mode_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ShaderMaskLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_SHADER_MASK_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/shader_mask_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/layers/shader_mask_layer.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkShader.h\"\n#include \"third_party/skia/include/effects/SkPerlinNoiseShader.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing ShaderMaskLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(ShaderMaskLayerTest, PaintingEmptyLayerDies) {\n  auto layer =\n      std::make_shared<ShaderMaskLayer>(nullptr, kEmptyRect, DlBlendMode::kSrc);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(ShaderMaskLayerTest, PaintBeforePrerollDies) {\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer =\n      std::make_shared<ShaderMaskLayer>(nullptr, kEmptyRect, DlBlendMode::kSrc);\n  layer->Add(mock_layer);\n\n  EXPECT_EQ(layer->paint_bounds(), kEmptyRect);\n  EXPECT_EQ(layer->child_paint_bounds(), kEmptyRect);\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(ShaderMaskLayerTest, EmptyFilter) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const skity::Rect layer_bounds =\n      skity::Rect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ShaderMaskLayer>(nullptr, layer_bounds,\n                                                 DlBlendMode::kSrc);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n\n  SkPaint filter_paint;\n  filter_paint.setBlendMode(SkBlendMode::kSrc);\n  filter_paint.setShader(nullptr);\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{\n               0, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                child_bounds),\n                                            SkPaint(), nullptr, 1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{1,\n                                MockCanvas::ConcatMatrixData{SkM44::Translate(\n                                    layer_bounds.Left(), layer_bounds.Top())}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::DrawRectData{\n                   SkRect::MakeWH(layer_bounds.Width(), layer_bounds.Height()),\n                   filter_paint}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(ShaderMaskLayerTest, SimpleFilter) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const skity::Rect layer_bounds =\n      skity::Rect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto layer_filter =\n      SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f);\n  auto dl_filter = std::make_shared<UnknownColorSource>(layer_filter);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ShaderMaskLayer>(dl_filter, layer_bounds,\n                                                 DlBlendMode::kSrc);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), child_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), child_bounds);\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform);\n\n  SkPaint filter_paint;\n  filter_paint.setBlendMode(SkBlendMode::kSrc);\n  filter_paint.setShader(layer_filter);\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{\n               0, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                child_bounds),\n                                            SkPaint(), nullptr, 1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{1,\n                                MockCanvas::ConcatMatrixData{SkM44::Translate(\n                                    layer_bounds.Left(), layer_bounds.Top())}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::DrawRectData{\n                   SkRect::MakeWH(layer_bounds.Width(), layer_bounds.Height()),\n                   filter_paint}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(ShaderMaskLayerTest, MultipleChildren) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const skity::Rect layer_bounds =\n      skity::Rect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f);\n  const SkPath child_path1 =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath child_path2 = SkPath().addRect(\n      clay::ConvertSkityRectToSkRect(child_bounds.MakeOffset(3.0f, 0.0f)));\n  const SkPaint child_paint1 = SkPaint(SkColors::kYellow);\n  const SkPaint child_paint2 = SkPaint(SkColors::kCyan);\n  auto layer_filter =\n      SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f);\n  auto dl_filter = std::make_shared<UnknownColorSource>(layer_filter);\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer = std::make_shared<ShaderMaskLayer>(dl_filter, layer_bounds,\n                                                 DlBlendMode::kSrc);\n  layer->Add(mock_layer1);\n  layer->Add(mock_layer2);\n\n  skity::Rect children_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  children_bounds.Join(clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), children_bounds);\n  EXPECT_EQ(layer->child_paint_bounds(), children_bounds);\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n\n  SkPaint filter_paint;\n  filter_paint.setBlendMode(SkBlendMode::kSrc);\n  filter_paint.setShader(layer_filter);\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{\n               0, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                children_bounds),\n                                            SkPaint(), nullptr, 1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path1, child_paint1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path2, child_paint2}},\n           MockCanvas::DrawCall{1,\n                                MockCanvas::ConcatMatrixData{SkM44::Translate(\n                                    layer_bounds.Left(), layer_bounds.Top())}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::DrawRectData{\n                   SkRect::MakeWH(layer_bounds.Width(), layer_bounds.Height()),\n                   filter_paint}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(ShaderMaskLayerTest, Nested) {\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 7.5f, 8.5f);\n  const skity::Rect layer_bounds =\n      skity::Rect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f);\n  const SkPath child_path1 =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPath child_path2 = SkPath().addRect(\n      clay::ConvertSkityRectToSkRect(child_bounds.MakeOffset(3.0f, 0.0f)));\n  const SkPaint child_paint1 = SkPaint(SkColors::kYellow);\n  const SkPaint child_paint2 = SkPaint(SkColors::kCyan);\n  auto layer_filter1 =\n      SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f);\n  auto dl_filter1 = std::make_shared<UnknownColorSource>(layer_filter1);\n  auto layer_filter2 =\n      SkPerlinNoiseShader::MakeFractalNoise(2.0f, 2.0f, 2, 2.0f);\n  auto dl_filter2 = std::make_shared<UnknownColorSource>(layer_filter2);\n  auto mock_layer1 = std::make_shared<MockLayer>(child_path1, child_paint1);\n  auto mock_layer2 = std::make_shared<MockLayer>(child_path2, child_paint2);\n  auto layer1 = std::make_shared<ShaderMaskLayer>(dl_filter1, layer_bounds,\n                                                  DlBlendMode::kSrc);\n  auto layer2 = std::make_shared<ShaderMaskLayer>(dl_filter2, layer_bounds,\n                                                  DlBlendMode::kSrc);\n  layer2->Add(mock_layer2);\n  layer1->Add(mock_layer1);\n  layer1->Add(layer2);\n\n  skity::Rect children_bounds =\n      clay::ConvertSkRectToSkityRect(child_path1.getBounds());\n  children_bounds.Join(clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  preroll_context()->state_stack.set_preroll_delegate(initial_transform);\n  layer1->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path1.getBounds()));\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path2.getBounds()));\n  EXPECT_EQ(layer1->paint_bounds(), children_bounds);\n  EXPECT_EQ(layer1->child_paint_bounds(), children_bounds);\n  EXPECT_EQ(layer2->paint_bounds(), mock_layer2->paint_bounds());\n  EXPECT_EQ(layer2->child_paint_bounds(), mock_layer2->paint_bounds());\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(layer2->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform);\n\n  SkPaint filter_paint1, filter_paint2;\n  filter_paint1.setBlendMode(SkBlendMode::kSrc);\n  filter_paint2.setBlendMode(SkBlendMode::kSrc);\n  filter_paint1.setShader(layer_filter1);\n  filter_paint2.setShader(layer_filter2);\n  layer1->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{\n               0, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                children_bounds),\n                                            SkPaint(), nullptr, 1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path1, child_paint1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::SaveLayerData{child_path2.getBounds(), SkPaint(),\n                                            nullptr, 2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path2, child_paint2}},\n           MockCanvas::DrawCall{2,\n                                MockCanvas::ConcatMatrixData{SkM44::Translate(\n                                    layer_bounds.Left(), layer_bounds.Top())}},\n           MockCanvas::DrawCall{\n               2,\n               MockCanvas::DrawRectData{\n                   SkRect::MakeWH(layer_bounds.Width(), layer_bounds.Height()),\n                   filter_paint2}},\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1,\n                                MockCanvas::ConcatMatrixData{SkM44::Translate(\n                                    layer_bounds.Left(), layer_bounds.Top())}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::DrawRectData{\n                   SkRect::MakeWH(layer_bounds.Width(), layer_bounds.Height()),\n                   filter_paint1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(ShaderMaskLayerTest, Readback) {\n  const skity::Rect layer_bounds =\n      skity::Rect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f);\n  auto layer_filter =\n      SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f);\n  auto dl_filter = std::make_shared<UnknownColorSource>(layer_filter);\n  auto layer = std::make_shared<ShaderMaskLayer>(dl_filter, layer_bounds,\n                                                 DlBlendMode::kSrc);\n\n  // ShaderMaskLayer does not read from surface\n  preroll_context()->surface_needs_readback = false;\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n\n  // ShaderMaskLayer blocks child with readback\n  auto mock_layer = std::make_shared<MockLayer>(SkPath(), SkPaint());\n  mock_layer->set_fake_reads_surface(true);\n  layer->Add(mock_layer);\n  preroll_context()->surface_needs_readback = false;\n  layer->Preroll(preroll_context());\n  EXPECT_FALSE(preroll_context()->surface_needs_readback);\n}\n\nTEST_F(ShaderMaskLayerTest, LayerCached) {\n  auto layer_filter =\n      SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f);\n  auto dl_filter = std::make_shared<UnknownColorSource>(layer_filter);\n  SkPaint paint;\n  const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f);\n  auto initial_transform = SkMatrix::Translate(50.0, 25.5);\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  auto layer = std::make_shared<ShaderMaskLayer>(\n      dl_filter, clay::ConvertSkRectToSkityRect(layer_bounds),\n      DlBlendMode::kSrc);\n  layer->Add(mock_layer);\n\n  SkMatrix cache_ctm = initial_transform;\n  SkCanvas cache_canvas;\n  cache_canvas.setMatrix(cache_ctm);\n\n  use_mock_raster_cache();\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  const auto* cacheable_shader_masker_item = layer->raster_cache_item();\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(cacheable_shader_masker_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_shader_masker_item->GetId().has_value());\n\n  // frame 1.\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(cacheable_shader_masker_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_shader_masker_item->GetId().has_value());\n\n  // frame 2.\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)0);\n  EXPECT_EQ(cacheable_shader_masker_item->cache_state(),\n            RasterCacheItem::CacheState::kNone);\n  EXPECT_FALSE(cacheable_shader_masker_item->GetId().has_value());\n\n  // frame 3.\n  layer->Preroll(preroll_context());\n  LayerTree::TryToRasterCache(cacheable_items(), &paint_context());\n  EXPECT_EQ(raster_cache()->GetLayerCachedEntriesCount(), (size_t)1);\n  EXPECT_EQ(cacheable_shader_masker_item->cache_state(),\n            RasterCacheItem::CacheState::kCurrent);\n\n  EXPECT_TRUE(raster_cache()->Draw(\n      cacheable_shader_masker_item->GetId().value(), cache_canvas, &paint));\n}\n\nTEST_F(ShaderMaskLayerTest, OpacityInheritance) {\n  const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPath child_path = SkPath().addRect(child_bounds);\n  auto mock_layer = MockLayer::Make(child_path);\n  const skity::Rect mask_rect = skity::Rect::MakeLTRB(10, 10, 20, 20);\n  auto shader_mask_layer =\n      std::make_shared<ShaderMaskLayer>(nullptr, mask_rect, DlBlendMode::kSrc);\n  shader_mask_layer->Add(mock_layer);\n\n  // ShaderMaskLayers can always support opacity despite incompatible children\n  PrerollContext* context = preroll_context();\n  shader_mask_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, Layer::kSaveLayerRenderFlags);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(shader_mask_layer);\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        SkPaint sk_paint;\n        sk_paint.setColor(opacity_alpha << 24);\n        canvas->saveLayer(&child_path.getBounds(), &sk_paint);\n        {\n          { canvas->drawPath(child_path, SkPaint()); }\n          canvas->translate(mask_rect.Left(), mask_rect.Top());\n          SkPaint new_paint;\n          new_paint.setBlendMode(SkBlendMode::kSrc);\n          canvas->drawRect(\n              SkRect::MakeWH(mask_rect.Width(), mask_rect.Height()), new_paint);\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE((expected_display_list->approximateOpCount() ==\n               display_list()->approximateOpCount()));\n}\n\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\nTEST_F(ShaderMaskLayerTest, SimpleFilterWithRasterCache) {\n  use_mock_raster_cache();  // Ensure non-fractional alignment.\n\n  const skity::Matrix initial_transform = skity::Matrix::Translate(0.5f, 1.0f);\n  const skity::Rect child_bounds =\n      skity::Rect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);\n  const skity::Rect layer_bounds =\n      skity::Rect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f);\n  const SkPath child_path =\n      SkPath().addRect(clay::ConvertSkityRectToSkRect(child_bounds));\n  const SkPaint child_paint = SkPaint(SkColors::kYellow);\n  auto layer_filter =\n      SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f);\n  auto dl_filter = DlColorSource::From(layer_filter);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, child_paint);\n  auto layer = std::make_shared<ShaderMaskLayer>(dl_filter, layer_bounds,\n                                                 DlBlendMode::kSrc);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(\n      clay::ConvertSkMatrixToSkityMatrix(initial_transform));\n  layer->Preroll(preroll_context());\n\n  SkPaint filter_paint;\n  filter_paint.setBlendMode(SkBlendMode::kSrc);\n  filter_paint.setShader(layer_filter);\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{skity::Matrix(\n                                       skity::Matrix::Translate(0.0, 0.0))}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::SaveLayerData{clay::ConvertSkityRectToSkRect(\n                                                child_bounds),\n                                            SkPaint(), nullptr, 2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path, child_paint}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::ConcatMatrixData{skity::Matrix::Translate(\n                      layer_bounds.fLeft, layer_bounds.fTop)}},\n           MockCanvas::DrawCall{2,\n                                MockCanvas::DrawRectData{\n                                    skity::Rect::MakeWH(layer_bounds.width(),\n                                                        layer_bounds.height()),\n                                    filter_paint}},\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n#endif\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/transform_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/transform_layer.h\"\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"clay/flow/animation/animation_mutator.h\"\n\nnamespace clay {\n\nTransformLayer::TransformLayer(const skity::Matrix& transform)\n    : transform_(transform) {\n  // Checks (in some degree) that skity::Matrix transform_ is valid and\n  // initialized.\n  //\n  // If transform_ is uninitialized, this assert may look flaky as it doesn't\n  // fail all the time, and some rerun may make it pass. But don't ignore it and\n  // just rerun the test if this is triggered, since even a flaky failure here\n  // may signify a potentially big problem in the code.\n  //\n  // We have to write this flaky test because there is no reliable way to test\n  // whether a variable is initialized or not in C++.\n  skity::Matrix& mat = std::get<skity::Matrix>(transform_);\n  FML_DCHECK(mat.IsFinite());\n  if (!mat.IsFinite()) {\n    FML_DLOG(ERROR) << \"TransformLayer is constructed with an invalid matrix.\";\n    mat.Reset();\n  }\n}\n\nTransformLayer::TransformLayer(const clay::TransformOperations& transform,\n                               skity::Vec2 origin, skity::Vec2 offset)\n    : transform_(transform), origin_(origin), offset_(offset) {}\n\nvoid TransformLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  auto* prev = static_cast<const TransformLayer*>(old_layer);\n  skity::Matrix transform = GetMatrix();\n  if (!context->IsSubtreeDirty()) {\n    FML_DCHECK(prev);\n    if (transform != prev->GetMatrix()) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n  if (HasAnimationRunning()) {\n    context->MarkSubtreeHasRasterAnimation();\n    if (!context->IsSubtreeDirty()) {\n      context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer));\n    }\n  }\n  context->PushTransform(transform);\n  DiffChildren(context, prev);\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid TransformLayer::Preroll(PrerollContext* context) {\n  bool prev = context->parent_has_running_transform_animation;\n  context->parent_has_running_transform_animation |= HasAnimationRunning();\n\n  auto mutator = context->state_stack.save();\n  skity::Matrix transform = GetMatrix();\n  mutator.transform(transform);\n\n  skity::Rect child_paint_bounds = skity::Rect::MakeEmpty();\n  PrerollChildren(context, &child_paint_bounds);\n\n  if (HasAnimationRunning()) {\n    context->has_running_transform_animation = true;\n  }\n\n  transform.MapRect(&child_paint_bounds, child_paint_bounds);\n  set_paint_bounds(child_paint_bounds);\n\n  context->parent_has_running_transform_animation = prev;\n}\n\nvoid TransformLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n\n  auto mutator = context.state_stack.save();\n  mutator.transform(GetMatrix());\n\n  PaintChildren(context);\n}\n\nskity::Matrix TransformLayer::GetMatrix() const {\n  if (HasAnimation()) {\n    const std::shared_ptr<AnimationMutator>& mutator = GetAnimationMutator();\n    if (mutator->asTransform()) {\n      clay::TransformOperations transform = mutator->asTransform()->transform();\n      return transform.Apply()\n          .matrix()\n          .PreTranslate(-origin_.x, -origin_.y)\n          .PostTranslate(origin_.x, origin_.y)\n          .PostTranslate(offset_.x, offset_.y);\n    } else if (mutator->asScrollOffset()) {\n      return mutator->asScrollOffset()->transform();\n    } else {\n      FML_DLOG(INFO) << \"TransformLayer::GetMatrix() called with an unexpected \"\n                        \"animation type:\"\n                     << static_cast<int>(mutator->GetType());\n    }\n  }\n  if (std::holds_alternative<skity::Matrix>(transform_)) {\n    return std::get<skity::Matrix>(transform_);\n  } else {\n    clay::TransformOperations transform =\n        std::get<clay::TransformOperations>(transform_);\n    return transform.Apply()\n        .matrix()\n        .PreTranslate(-origin_.x, -origin_.y)\n        .PostTranslate(origin_.x, origin_.y)\n        .PostTranslate(offset_.x, offset_.y);\n  }\n}\n\nclay::TransformOperations TransformLayer::GetTransform() const {\n  return std::get<clay::TransformOperations>(transform_);\n}\n\n#ifndef NDEBUG\nstd::string TransformLayer::ToString() const {\n  std::stringstream ss;\n  skity::Matrix transform = GetMatrix();\n  ss << ContainerLayer::ToString();\n  ss << \" transformX=\" << transform.GetTranslateX();\n  ss << \" transformY=\" << transform.GetTranslateY();\n  ss << \" scaleX=\" << transform.GetScaleX();\n  ss << \" scaleY=\" << transform.GetScaleY();\n  ss << \" transform_=\"\n     << \"(\" << transform.GetScaleX() << \",\" << transform.GetSkewX() << \",\"\n     << transform.GetTranslateX() << \",\" << transform.GetSkewY() << \",\"\n     << transform.GetScaleY() << \",\" << transform.GetTranslateY() << \",\"\n     << transform.GetPersp0() << \",\" << transform.GetPersp1() << \",\"\n     << transform.GetPersp2() << \")\";\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/layers/transform_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_LAYERS_TRANSFORM_LAYER_H_\n#define CLAY_FLOW_LAYERS_TRANSFORM_LAYER_H_\n\n#include <memory>\n#include <string>\n#include <variant>\n\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n\nnamespace clay {\n\n// Be careful that skity::Matrix's default constructor doesn't initialize the\n// matrix at all. Hence |set_transform| must be called with an initialized\n// skity::Matrix.\nclass TransformLayer : public ContainerLayer {\n public:\n  explicit TransformLayer(const skity::Matrix& transform = skity::Matrix());\n  TransformLayer(const clay::TransformOperations& transform, skity::Vec2 origin,\n                 skity::Vec2 offset);\n\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n\n  void Preroll(PrerollContext* context) override;\n\n  void Paint(PaintContext& context) const override;\n\n  skity::Matrix GetMatrix() const;\n  clay::TransformOperations GetTransform() const;\n\n#ifndef NDEBUG\n  std::string DebugName() const override { return \"TransformLayer\"; }\n  std::string ToString() const override;\n#endif\n\n private:\n  std::variant<skity::Matrix, clay::TransformOperations> transform_;\n  skity::Vec2 origin_;\n  skity::Vec2 offset_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TransformLayer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_LAYERS_TRANSFORM_LAYER_H_\n"
  },
  {
    "path": "clay/flow/layers/transform_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layers/transform_layer.h\"\n#include \"clay/flow/testing/diff_context_test.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing TransformLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(TransformLayerTest, PaintingEmptyLayerDies) {\n  auto layer = std::make_shared<TransformLayer>(skity::Matrix());  // identity\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_EQ(layer->child_paint_bounds(), skity::Rect::MakeEmpty());\n  EXPECT_FALSE(layer->needs_painting(paint_context()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(TransformLayerTest, PaintBeforePrerollDies) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, SkPaint());\n  auto layer = std::make_shared<TransformLayer>(skity::Matrix());  // identity\n  layer->Add(mock_layer);\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(TransformLayerTest, Identity) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  skity::Rect cull_rect = skity::Rect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f);\n  auto mock_layer = std::make_shared<MockLayer>(child_path, SkPaint());\n  auto layer = std::make_shared<TransformLayer>(skity::Matrix());  // identity\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(cull_rect);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_EQ(layer->child_paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), skity::Matrix());  // identity\n  EXPECT_EQ(mock_layer->parent_cull_rect(), cull_rect);\n  EXPECT_EQ(mock_layer->parent_mutators(), MutatorsStack());\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({MockCanvas::DrawCall{\n                0, MockCanvas::DrawPathData{child_path, SkPaint()}}}));\n}\n\nTEST_F(TransformLayerTest, Simple) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  skity::Matrix initial_transform = skity::Matrix::Translate(-0.5f, -0.5f);\n  skity::Rect local_cull_rect = skity::Rect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f);\n  skity::Rect device_cull_rect = initial_transform.MapRect(local_cull_rect);\n  skity::Matrix layer_transform = skity::Matrix::Translate(2.5f, 2.5f);\n  skity::Matrix inverse_layer_transform;\n  EXPECT_TRUE(layer_transform.Invert(&inverse_layer_transform));\n\n  auto mock_layer = std::make_shared<MockLayer>(child_path, SkPaint());\n  auto layer = std::make_shared<TransformLayer>(layer_transform);\n  layer->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_rect,\n                                                      initial_transform);\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer->paint_bounds(),\n            layer_transform.MapRect(mock_layer->paint_bounds()));\n  EXPECT_EQ(layer->child_paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(), initial_transform * layer_transform);\n  EXPECT_EQ(mock_layer->parent_cull_rect(),\n            inverse_layer_transform.MapRect(local_cull_rect));\n  EXPECT_EQ(mock_layer->parent_mutators(),\n            std::vector({Mutator(layer_transform)}));\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::ConcatMatrixData{clay::ConvertSkityMatrixToSkM44(\n                      layer_transform)}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::DrawPathData{child_path, SkPaint()}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(TransformLayerTest, Nested) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  skity::Matrix initial_transform = skity::Matrix::Translate(-0.5f, -0.5f);\n  skity::Rect local_cull_rect = skity::Rect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f);\n  skity::Rect device_cull_rect = initial_transform.MapRect(local_cull_rect);\n  skity::Matrix layer1_transform = skity::Matrix::Translate(2.5f, 2.5f);\n  skity::Matrix layer2_transform = skity::Matrix::Translate(2.5f, 2.5f);\n  skity::Matrix inverse_layer1_transform, inverse_layer2_transform;\n  EXPECT_TRUE(layer1_transform.Invert(&inverse_layer1_transform));\n  EXPECT_TRUE(layer2_transform.Invert(&inverse_layer2_transform));\n\n  auto mock_layer = std::make_shared<MockLayer>(child_path, SkPaint());\n  auto layer1 = std::make_shared<TransformLayer>(layer1_transform);\n  auto layer2 = std::make_shared<TransformLayer>(layer2_transform);\n  layer1->Add(layer2);\n  layer2->Add(mock_layer);\n\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_rect,\n                                                      initial_transform);\n  layer1->Preroll(preroll_context());\n  EXPECT_EQ(mock_layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer2->paint_bounds(),\n            layer2_transform.MapRect(mock_layer->paint_bounds()));\n  EXPECT_EQ(layer2->child_paint_bounds(), mock_layer->paint_bounds());\n  EXPECT_EQ(layer1->paint_bounds(),\n            layer1_transform.MapRect(layer2->paint_bounds()));\n  EXPECT_EQ(layer1->child_paint_bounds(), layer2->paint_bounds());\n  EXPECT_TRUE(mock_layer->needs_painting(paint_context()));\n  EXPECT_TRUE(layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer1->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer->parent_matrix(),\n            initial_transform * layer1_transform * layer2_transform);\n  EXPECT_EQ(mock_layer->parent_cull_rect(),\n            inverse_layer2_transform.MapRect(\n                inverse_layer1_transform.MapRect(local_cull_rect)));\n  EXPECT_EQ(\n      mock_layer->parent_mutators(),\n      std::vector({Mutator(layer2_transform), Mutator(layer1_transform)}));\n\n  layer1->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::ConcatMatrixData{clay::ConvertSkityMatrixToSkM44(\n                      layer1_transform)}},\n           MockCanvas::DrawCall{1, MockCanvas::SaveData{2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::ConcatMatrixData{clay::ConvertSkityMatrixToSkM44(\n                      layer2_transform)}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::DrawPathData{child_path, SkPaint()}},\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(TransformLayerTest, NestedSeparated) {\n  SkPath child_path;\n  child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  skity::Matrix initial_transform = skity::Matrix::Translate(-0.5f, -0.5f);\n  skity::Rect local_cull_rect = skity::Rect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f);\n  skity::Rect device_cull_rect = initial_transform.MapRect(local_cull_rect);\n  skity::Matrix layer1_transform = skity::Matrix::Translate(2.5f, 2.5f);\n  skity::Matrix layer2_transform = skity::Matrix::Translate(2.5f, 2.5f);\n  skity::Matrix inverse_layer1_transform, inverse_layer2_transform;\n  EXPECT_TRUE(layer1_transform.Invert(&inverse_layer1_transform));\n  EXPECT_TRUE(layer2_transform.Invert(&inverse_layer2_transform));\n\n  auto mock_layer1 =\n      std::make_shared<MockLayer>(child_path, SkPaint(SkColors::kBlue));\n  auto mock_layer2 =\n      std::make_shared<MockLayer>(child_path, SkPaint(SkColors::kGreen));\n  auto layer1 = std::make_shared<TransformLayer>(layer1_transform);\n  auto layer2 = std::make_shared<TransformLayer>(layer2_transform);\n  layer1->Add(mock_layer1);\n  layer1->Add(layer2);\n  layer2->Add(mock_layer2);\n\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_rect,\n                                                      initial_transform);\n  layer1->Preroll(preroll_context());\n  skity::Rect layer1_child_bounds = layer2->paint_bounds();\n  layer1_child_bounds.Join(mock_layer1->paint_bounds());\n  skity::Rect expected_layer1_bounds = layer1_child_bounds;\n  layer1_transform.MapRect(&expected_layer1_bounds, layer1_child_bounds);\n\n  EXPECT_EQ(mock_layer2->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer2->paint_bounds(),\n            layer2_transform.MapRect(mock_layer2->paint_bounds()));\n  EXPECT_EQ(layer2->child_paint_bounds(), mock_layer2->paint_bounds());\n  EXPECT_EQ(mock_layer1->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(child_path.getBounds()));\n  EXPECT_EQ(layer1->paint_bounds(), expected_layer1_bounds);\n  EXPECT_EQ(layer1->child_paint_bounds(), layer1_child_bounds);\n  EXPECT_TRUE(mock_layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(layer2->needs_painting(paint_context()));\n  EXPECT_TRUE(mock_layer1->needs_painting(paint_context()));\n  EXPECT_TRUE(layer1->needs_painting(paint_context()));\n  EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform * layer1_transform);\n  EXPECT_EQ(mock_layer2->parent_matrix(),\n            initial_transform * layer1_transform * layer2_transform);\n  EXPECT_EQ(mock_layer1->parent_cull_rect(),\n            inverse_layer1_transform.MapRect(local_cull_rect));\n  EXPECT_EQ(mock_layer2->parent_cull_rect(),\n            inverse_layer2_transform.MapRect(\n                inverse_layer1_transform.MapRect(local_cull_rect)));\n  EXPECT_EQ(mock_layer1->parent_mutators(),\n            std::vector({Mutator(layer1_transform)}));\n  EXPECT_EQ(\n      mock_layer2->parent_mutators(),\n      std::vector({Mutator(layer2_transform), Mutator(layer1_transform)}));\n\n  layer1->Paint(paint_context());\n  EXPECT_EQ(\n      mock_canvas().draw_calls(),\n      std::vector(\n          {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n           MockCanvas::DrawCall{\n               1, MockCanvas::ConcatMatrixData{clay::ConvertSkityMatrixToSkM44(\n                      layer1_transform)}},\n           MockCanvas::DrawCall{\n               1,\n               MockCanvas::DrawPathData{child_path, SkPaint(SkColors::kBlue)}},\n           MockCanvas::DrawCall{1, MockCanvas::SaveData{2}},\n           MockCanvas::DrawCall{\n               2, MockCanvas::ConcatMatrixData{clay::ConvertSkityMatrixToSkM44(\n                      layer2_transform)}},\n           MockCanvas::DrawCall{\n               2,\n               MockCanvas::DrawPathData{child_path, SkPaint(SkColors::kGreen)}},\n           MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}},\n           MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}));\n}\n\nTEST_F(TransformLayerTest, OpacityInheritance) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto transform1 =\n      std::make_shared<TransformLayer>(skity::Matrix::Scale(2, 2));\n  transform1->Add(mock1);\n\n  // TransformLayer will pass through compatibility from a compatible child\n  PrerollContext* context = preroll_context();\n  transform1->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path2 = SkPath().addRect({40, 40, 50, 50});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  transform1->Add(mock2);\n\n  // TransformLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  transform1->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path3 = SkPath().addRect({20, 20, 40, 40});\n  auto mock3 = MockLayer::MakeOpacityCompatible(path3);\n  transform1->Add(mock3);\n\n  // TransformLayer will not pass through compatibility from multiple\n  // overlapping children even if they are individually compatible\n  transform1->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, 0);\n\n  auto transform2 =\n      std::make_shared<TransformLayer>(skity::Matrix::Scale(2, 2));\n  transform2->Add(mock1);\n  transform2->Add(mock2);\n\n  // Double check first two children are compatible and non-overlapping\n  transform2->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  auto path4 = SkPath().addRect({60, 60, 70, 70});\n  auto mock4 = MockLayer::Make(path4);\n  transform2->Add(mock4);\n\n  // The third child is non-overlapping, but not compatible so the\n  // TransformLayer should end up incompatible\n  transform2->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, 0);\n}\n\nTEST_F(TransformLayerTest, OpacityInheritancePainting) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  auto mock1 = MockLayer::MakeOpacityCompatible(path1);\n  auto path2 = SkPath().addRect({40, 40, 50, 50});\n  auto mock2 = MockLayer::MakeOpacityCompatible(path2);\n  auto transform = SkMatrix::Scale(2, 2);\n  auto transform_layer = std::make_shared<TransformLayer>(\n      clay::ConvertSkMatrixToSkityMatrix(transform));\n  transform_layer->Add(mock1);\n  transform_layer->Add(mock2);\n\n  // TransformLayer will pass through compatibility from multiple\n  // non-overlapping compatible children\n  PrerollContext* context = preroll_context();\n  transform_layer->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n\n  int opacity_alpha = 0x7F;\n  skity::Vec2 offset = skity::Vec2(10, 10);\n  auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);\n  opacity_layer->Add(transform_layer);\n  opacity_layer->Preroll(context);\n  EXPECT_TRUE(opacity_layer->children_can_accept_opacity());\n\n  SkPictureRecorder recorder;\n  auto rtree = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(kDlBounds, rtree.get());\n  {\n    canvas->save();\n    {\n      canvas->translate(offset.x, offset.y);\n      {\n        canvas->save();\n        canvas->concat(transform);\n        {\n          SkPaint paint1;\n          paint1.setAlpha(opacity_alpha);\n          canvas->drawPath(path1, paint1);\n        }\n        {\n          SkPaint paint2;\n          paint2.setAlpha(opacity_alpha);\n          canvas->drawPath(path2, paint2);\n        }\n        canvas->restore();\n      }\n    }\n    canvas->restore();\n  }\n  auto expected_display_list = recorder.finishRecordingAsPicture();\n\n  opacity_layer->Paint(display_list_paint_context());\n  EXPECT_TRUE((display_list()->approximateOpCount() ==\n               expected_display_list->approximateOpCount()));\n}\n\nusing TransformLayerLayerDiffTest = DiffContextTest;\n\nTEST_F(TransformLayerLayerDiffTest, Transform) {\n  auto path1 = SkPath().addRect(SkRect::MakeLTRB(0, 0, 50, 50));\n  auto m1 = std::make_shared<MockLayer>(path1);\n\n  auto transform1 =\n      std::make_shared<TransformLayer>(skity::Matrix::Translate(10, 10));\n  transform1->Add(m1);\n\n  MockLayerTree t1;\n  t1.root()->Add(transform1);\n\n  auto damage = DiffLayerTree(t1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(10, 10, 60, 60));\n\n  auto transform2 =\n      std::make_shared<TransformLayer>(skity::Matrix::Translate(20, 20));\n  transform2->Add(m1);\n  transform2->AssignOldLayer(transform1.get());\n\n  MockLayerTree t2;\n  t2.root()->Add(transform2);\n\n  damage = DiffLayerTree(t2, t1);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(10, 10, 70, 70));\n\n  auto transform3 =\n      std::make_shared<TransformLayer>(skity::Matrix::Translate(20, 20));\n  transform3->Add(m1);\n  transform3->AssignOldLayer(transform2.get());\n\n  MockLayerTree t3;\n  t3.root()->Add(transform3);\n\n  damage = DiffLayerTree(t3, t2);\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeEmpty());\n}\n\nTEST_F(TransformLayerLayerDiffTest, TransformNested) {\n  auto path1 = SkPath().addRect(SkRect::MakeLTRB(0, 0, 50, 50));\n  auto m1 = CreateContainerLayer(std::make_shared<MockLayer>(path1));\n  auto m2 = CreateContainerLayer(std::make_shared<MockLayer>(path1));\n  auto m3 = CreateContainerLayer(std::make_shared<MockLayer>(path1));\n\n  auto transform1 =\n      std::make_shared<TransformLayer>(skity::Matrix::Scale(2.0, 2.0));\n\n  auto transform1_1 =\n      std::make_shared<TransformLayer>(skity::Matrix::Translate(10, 10));\n  transform1_1->Add(m1);\n  transform1->Add(transform1_1);\n\n  auto transform1_2 =\n      std::make_shared<TransformLayer>(skity::Matrix::Translate(100, 100));\n  transform1_2->Add(m2);\n  transform1->Add(transform1_2);\n\n  auto transform1_3 =\n      std::make_shared<TransformLayer>(skity::Matrix::Translate(200, 200));\n  transform1_3->Add(m3);\n  transform1->Add(transform1_3);\n\n  MockLayerTree l1;\n  l1.root()->Add(transform1);\n\n  auto damage = DiffLayerTree(l1, MockLayerTree());\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(20, 20, 500, 500));\n\n  auto transform2 =\n      std::make_shared<TransformLayer>(skity::Matrix::Scale(2.0, 2.0));\n\n  auto transform2_1 =\n      std::make_shared<TransformLayer>(skity::Matrix::Translate(10, 10));\n  transform2_1->Add(m1);\n  transform2_1->AssignOldLayer(transform1_1.get());\n  transform2->Add(transform2_1);\n\n  // Offset 1px from transform1_2 so that they're not the same\n  auto transform2_2 =\n      std::make_shared<TransformLayer>(skity::Matrix::Translate(100, 101));\n  transform2_2->Add(m2);\n  transform2_2->AssignOldLayer(transform1_2.get());\n  transform2->Add(transform2_2);\n\n  auto transform2_3 =\n      std::make_shared<TransformLayer>(skity::Matrix::Translate(200, 200));\n  transform2_3->Add(m3);\n  transform2_3->AssignOldLayer(transform1_3.get());\n  transform2->Add(transform2_3);\n\n  MockLayerTree l2;\n  l2.root()->Add(transform2);\n\n  damage = DiffLayerTree(l2, l1);\n\n  // transform2 has not transform1 assigned as old layer, so it should be\n  // invalidated completely\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(20, 20, 500, 500));\n\n  // now diff the tree properly, the only difference being transform2_2 and\n  // transform_2_1\n  transform2->AssignOldLayer(transform1.get());\n  damage = DiffLayerTree(l2, l1);\n\n  EXPECT_EQ(damage.frame_damage, skity::Rect::MakeLTRB(200, 200, 300, 302));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/matrix_clip_tracker.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/matrix_clip_tracker.h\"\n\n#include \"clay/fml/logging.h\"\n#ifndef ENABLE_SKITY\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#endif\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nclass Data4x4 : public MatrixClipTracker::Data {\n public:\n  Data4x4(const skity::Matrix& m44, const skity::Rect& rect)\n      : Data(rect), m44_(m44) {}\n  explicit Data4x4(const Data* copy)\n      : Data(copy->device_cull_rect()), m44_(copy->matrix_4x4()) {}\n\n  ~Data4x4() override = default;\n\n  bool is_4x4() const override { return true; }\n\n  skity::Matrix matrix_4x4() const override { return m44_; }\n  skity::Rect local_cull_rect() const override;\n\n  void translate(float tx, float ty) override { m44_.PreTranslate(tx, ty); }\n  void scale(float sx, float sy) override { m44_.PreScale(sx, sy); }\n  void skew(float skx, float sky) override {\n    m44_.PreConcat(skity::Matrix::Skew(skx, sky));\n  }\n  void rotate(float degrees) override {\n    m44_.PreConcat(skity::Matrix::RotateDeg(degrees));\n  }\n  void transform(const skity::Matrix& matrix) override {\n    m44_.PreConcat(matrix);\n  }\n  void setTransform(const skity::Matrix& matrix) override { m44_ = matrix; }\n  void setIdentity() override { m44_.Reset(); }\n  bool mapRect(const skity::Rect& rect, skity::Rect* mapped) const override {\n    return m44_.MapRect(mapped, rect);\n  }\n  bool canBeInverted() const override { return m44_.Invert(nullptr); }\n\n protected:\n  bool has_perspective() const override;\n\n private:\n  skity::Matrix m44_;\n};\n\nstatic constexpr skity::Rect kMaxCullRect =\n    skity::Rect::MakeLTRB(-1E9F, -1E9F, 1E9F, 1E9F);\n\nMatrixClipTracker::MatrixClipTracker(const skity::Rect& cull_rect,\n                                     const skity::Matrix& matrix) {\n  // isEmpty protects us against NaN as we normalize any empty cull rects\n  skity::Rect cull = cull_rect.IsEmpty() ? skity::Rect::MakeEmpty() : cull_rect;\n  saved_.emplace_back(std::make_unique<Data4x4>(matrix, cull));\n  current_ = saved_.back().get();\n}\n\nvoid MatrixClipTracker::transform2DAffine(float mxx, float mxy, float mxt,\n                                          float myx, float myy, float myt) {\n  transform(skity::Matrix(  //\n      mxx, myx, 0, 0,       //\n      mxy, myy, 0, 0,       //\n      0, 0, 1, 0,           //\n      mxt, myt, 0, 1));\n}\nvoid MatrixClipTracker::transformFullPerspective(\n    float mxx, float mxy, float mxz, float mxt, float myx, float myy, float myz,\n    float myt, float mzx, float mzy, float mzz, float mzt, float mwx, float mwy,\n    float mwz, float mwt) {\n  transform(skity::Matrix(  //\n      mxx, myx, mzx, mwx,   //\n      mxy, myy, mzy, mwy,   //\n      mxz, myz, mzz, mwz,   //\n      mxt, myt, mzt, mwt));\n}\n\nvoid MatrixClipTracker::save() {\n  saved_.emplace_back(std::make_unique<Data4x4>(current_));\n  current_ = saved_.back().get();\n}\n\nvoid MatrixClipTracker::restore() {\n  saved_.pop_back();\n  current_ = saved_.back().get();\n}\n\nvoid MatrixClipTracker::restoreToCount(int restore_count) {\n  FML_DCHECK(restore_count <= getSaveCount());\n  if (restore_count < 1) {\n    restore_count = 1;\n  }\n  while (restore_count < getSaveCount()) {\n    restore();\n  }\n}\n\nvoid MatrixClipTracker::clipRRect(const skity::RRect& rrect, clay::GrClipOp op,\n                                  bool is_aa) {\n  switch (op) {\n    case clay::GrClipOp::kIntersect:\n      break;\n    case clay::GrClipOp::kDifference:\n      if (!rrect.IsRect()) {\n        return;\n      }\n      break;\n    default:\n      break;\n  }\n  current_->clipBounds(rrect.GetBounds(), op, is_aa);\n}\n\nvoid MatrixClipTracker::clipPath(const clay::GrPath& path, clay::GrClipOp op,\n                                 bool is_aa) {\n  // Map \"kDifference of inverse path\" to \"kIntersect of the original path\" and\n  // map \"kIntersect of inverse path\" to \"kDifference of the original path\"\n#ifndef ENABLE_SKITY\n  if (path.isInverseFillType()) {\n    switch (op) {\n      case clay::GrClipOp::kIntersect:\n        op = clay::GrClipOp::kDifference;\n        break;\n      case clay::GrClipOp::kDifference:\n        op = clay::GrClipOp::kIntersect;\n        break;\n      default:\n        break;\n    }\n  }\n#endif  // ENABLE_SKITY\n\n  clay::GrRect bounds;\n  switch (op) {\n    case clay::GrClipOp::kIntersect:\n      // bounds = path.getBounds();\n      bounds = PATH_GET_BOUNDS(path);\n      break;\n    case clay::GrClipOp::kDifference:\n      if (!PATH_IS_RECT(path, &bounds)) {\n        return;\n      }\n      break;\n    default:\n      break;\n  }\n#ifndef ENABLE_SKITY\n  current_->clipBounds(ConvertSkRectToSkityRect(bounds), op, is_aa);\n#else\n  current_->clipBounds(bounds, op, is_aa);\n#endif  // ENABLE_SKITY\n}\n\nbool MatrixClipTracker::Data::content_culled(\n    const skity::Rect& content_bounds) const {\n  if (cull_rect_.IsEmpty() || content_bounds.IsEmpty()) {\n    return true;\n  }\n  if (!canBeInverted()) {\n    return true;\n  }\n  if (has_perspective()) {\n    return false;\n  }\n  skity::Rect mapped;\n  mapRect(content_bounds, &mapped);\n  return !mapped.Intersect(cull_rect_);\n}\n\nvoid MatrixClipTracker::Data::clipBounds(const skity::Rect& clip,\n                                         clay::GrClipOp op, bool is_aa) {\n  if (cull_rect_.IsEmpty()) {\n    // No point in intersecting further.\n    return;\n  }\n  if (has_perspective()) {\n    // We can conservatively ignore this clip.\n    return;\n  }\n  switch (op) {\n    case clay::GrClipOp::kIntersect: {\n      if (clip.IsEmpty()) {\n        cull_rect_.SetEmpty();\n        break;\n      }\n      skity::Rect rect;\n      mapRect(clip, &rect);\n      if (is_aa) {\n        rect.RoundOut();\n      }\n      if (!cull_rect_.Intersect(rect)) {\n        cull_rect_.SetEmpty();\n      }\n      break;\n    }\n    case clay::GrClipOp::kDifference: {\n      if (clip.IsEmpty()) {\n        break;\n      }\n      skity::Rect rect;\n      if (mapRect(clip, &rect)) {\n        // This technique only works if the transform is rect -> rect\n        if (is_aa) {\n          skity::Rect rounded = rect;\n          rounded.Round();\n          if (rounded.IsEmpty()) {\n            break;\n          }\n          rect = rounded;\n        }\n        if (!rect.Intersect(cull_rect_)) {\n          break;\n        }\n        if (rect.Left() <= cull_rect_.Left() &&\n            rect.Right() >= cull_rect_.Right()) {\n          // bounds spans entire width of cull_rect_\n          // therefore we can slice off a top or bottom\n          // edge of the cull_rect_.\n          float top = cull_rect_.Top();\n          float btm = cull_rect_.Bottom();\n          if (rect.Top() <= top) {\n            top = rect.Bottom();\n          }\n          if (rect.Bottom() >= btm) {\n            btm = rect.Top();\n          }\n          if (top < btm) {\n            cull_rect_.SetTop(top);\n            cull_rect_.SetBottom(btm);\n          } else {\n            cull_rect_.SetEmpty();\n          }\n        } else if (rect.Top() <= cull_rect_.Top() &&\n                   rect.Bottom() >= cull_rect_.Bottom()) {\n          // bounds spans entire height of cull_rect_\n          // therefore we can slice off a left or right\n          // edge of the cull_rect_.\n          float lft = cull_rect_.Left();\n          float rgt = cull_rect_.Right();\n          if (rect.Left() <= lft) {\n            lft = rect.Right();\n          }\n          if (rect.Right() >= rgt) {\n            rgt = rect.Left();\n          }\n          if (lft < rgt) {\n            cull_rect_.SetLeft(lft);\n            cull_rect_.SetRight(rgt);\n          } else {\n            cull_rect_.SetEmpty();\n          }\n        }\n      }\n      break;\n    }\n    default:\n      break;\n  }\n}\n\nskity::Rect Data4x4::local_cull_rect() const {\n  if (cull_rect_.IsEmpty()) {\n    return cull_rect_;\n  }\n  skity::Matrix inverse;\n  if (!m44_.Invert(&inverse)) {\n    return skity::Rect::MakeEmpty();\n  }\n  if (has_perspective()) {\n    // We could do a 4-point long-form conversion, but since this is\n    // only used for culling, let's just return a non-constricting\n    // cull rect.\n    return kMaxCullRect;\n  }\n  return inverse.MapRect(cull_rect_);\n}\n\nbool Data4x4::has_perspective() const { return m44_.HasPersp(); }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/matrix_clip_tracker.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_MATRIX_CLIP_TRACKER_H_\n#define CLAY_FLOW_MATRIX_CLIP_TRACKER_H_\n\n#include <algorithm>\n#include <memory>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/matrix.hpp\"\n#include \"skity/geometry/rect.hpp\"\n#include \"skity/geometry/rrect.hpp\"\n\nnamespace clay {\n\nclass MatrixClipTracker {\n public:\n  MatrixClipTracker(const skity::Rect& cull_rect, const skity::Matrix& matrix);\n\n  bool using_4x4_matrix() { return current_->is_4x4(); }\n\n  skity::Matrix matrix_4x4() const { return current_->matrix_4x4(); }\n  skity::Rect local_cull_rect() const { return current_->local_cull_rect(); }\n  skity::Rect device_cull_rect() const { return current_->device_cull_rect(); }\n  bool content_culled(const skity::Rect& content_bounds) const {\n    return current_->content_culled(content_bounds);\n  }\n\n  void save();\n  void restore();\n  int getSaveCount() { return saved_.size(); }\n  void restoreToCount(int restore_count);\n\n  void translate(float tx, float ty) { current_->translate(tx, ty); }\n  void scale(float sx, float sy) { current_->scale(sx, sy); }\n  void skew(float skx, float sky) { current_->skew(skx, sky); }\n  void rotate(float degrees) { current_->rotate(degrees); }\n  void transform(const skity::Matrix& matrix) { current_->transform(matrix); }\n  // clang-format off\n  void transform2DAffine(\n      float mxx, float mxy, float mxt,\n      float myx, float myy, float myt);\n  void transformFullPerspective(\n      float mxx, float mxy, float mxz, float mxt,\n      float myx, float myy, float myz, float myt,\n      float mzx, float mzy, float mzz, float mzt,\n      float mwx, float mwy, float mwz, float mwt);\n  // clang-format on\n  void setTransform(const skity::Matrix& matrix) {\n    current_->setTransform(matrix);\n  }\n  void setIdentity() { current_->setIdentity(); }\n  bool mapRect(skity::Rect* rect) const {\n    return current_->mapRect(*rect, rect);\n  }\n\n  void clipRect(const skity::Rect& rect, clay::GrClipOp op, bool is_aa) {\n    current_->clipBounds(rect, op, is_aa);\n  }\n  void clipRRect(const skity::RRect& rrect, clay::GrClipOp op, bool is_aa);\n  void clipPath(const clay::GrPath& path, clay::GrClipOp op, bool is_aa);\n\n private:\n  class Data {\n   public:\n    virtual ~Data() = default;\n\n    virtual bool is_4x4() const = 0;\n\n    virtual skity::Matrix matrix_4x4() const = 0;\n\n    virtual skity::Rect device_cull_rect() const { return cull_rect_; }\n    virtual skity::Rect local_cull_rect() const = 0;\n    virtual bool content_culled(const skity::Rect& content_bounds) const;\n\n    virtual void translate(float tx, float ty) = 0;\n    virtual void scale(float sx, float sy) = 0;\n    virtual void skew(float skx, float sky) = 0;\n    virtual void rotate(float degrees) = 0;\n    virtual void transform(const skity::Matrix& matrix) = 0;\n    virtual void setTransform(const skity::Matrix& matrix) = 0;\n    virtual void setIdentity() = 0;\n    virtual bool mapRect(const skity::Rect& rect,\n                         skity::Rect* mapped) const = 0;\n    virtual bool canBeInverted() const = 0;\n\n    virtual void clipBounds(const skity::Rect& clip, clay::GrClipOp op,\n                            bool is_aa);\n\n   protected:\n    Data(const skity::Rect& rect) : cull_rect_(rect) {}\n\n    virtual bool has_perspective() const = 0;\n\n    skity::Rect cull_rect_;\n  };\n  friend class Data3x3;\n  friend class Data4x4;\n\n  Data* current_;\n  std::vector<std::unique_ptr<Data>> saved_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_MATRIX_CLIP_TRACKER_H_\n"
  },
  {
    "path": "clay/flow/matrix_clip_tracker_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/matrix_clip_tracker.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkPath.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(DisplayListMatrixClipTracker, Constructor) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  const SkM44 m44 = SkM44::Scale(4, 4);\n  const SkRect local_cull_rect = SkRect::MakeLTRB(5, 5, 15, 15);\n\n  MatrixClipTracker tracker1(ConvertSkRectToSkityRect(cull_rect),\n                             ConvertSkM44ToMatrix(m44));\n\n  ASSERT_TRUE(tracker1.using_4x4_matrix());\n  ASSERT_EQ(tracker1.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker1.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker1.matrix_4x4(), ConvertSkM44ToMatrix(m44));\n}\n\nTEST(DisplayListMatrixClipTracker, Constructor4x4) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  // clang-format off\n  const SkM44 m44 = SkM44(4, 0, 0.5, 0,\n                          0, 4, 0.5, 0,\n                          0, 0, 4.0, 0,\n                          0, 0, 0.0, 1);\n  // clang-format on\n  const SkRect local_cull_rect = SkRect::MakeLTRB(5, 5, 15, 15);\n\n  MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                            ConvertSkM44ToMatrix(m44));\n\n  ASSERT_TRUE(tracker.using_4x4_matrix());\n  ASSERT_EQ(tracker.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker.matrix_4x4(), ConvertSkM44ToMatrix(m44));\n}\n\nTEST(DisplayListMatrixClipTracker, TransformTo4x4) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  // clang-format off\n  const SkM44 m44 = SkM44(4, 0, 0.5, 0,\n                          0, 4, 0.5, 0,\n                          0, 0, 4.0, 0,\n                          0, 0, 0.0, 1);\n  // clang-format on\n  const SkRect local_cull_rect = SkRect::MakeLTRB(5, 5, 15, 15);\n\n  MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                            ConvertSkMatrixToSkityMatrix(SkMatrix::I()));\n  ASSERT_TRUE(tracker.using_4x4_matrix());\n\n  tracker.transform(ConvertSkM44ToMatrix(m44));\n  ASSERT_TRUE(tracker.using_4x4_matrix());\n  ASSERT_EQ(tracker.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker.matrix_4x4(), ConvertSkM44ToMatrix(m44));\n}\n\nTEST(DisplayListMatrixClipTracker, SetTo4x4) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  // clang-format off\n  const SkM44 m44 = SkM44(4, 0, 0.5, 0,\n                          0, 4, 0.5, 0,\n                          0, 0, 4.0, 0,\n                          0, 0, 0.0, 1);\n  // clang-format on\n  const SkRect local_cull_rect = SkRect::MakeLTRB(5, 5, 15, 15);\n\n  MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                            ConvertSkMatrixToSkityMatrix(SkMatrix::I()));\n  ASSERT_TRUE(tracker.using_4x4_matrix());\n\n  tracker.setTransform(ConvertSkM44ToMatrix(m44));\n  ASSERT_TRUE(tracker.using_4x4_matrix());\n  ASSERT_EQ(tracker.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker.matrix_4x4(), ConvertSkM44ToMatrix(m44));\n}\n\nTEST(DisplayListMatrixClipTracker, UpgradeTo4x4SaveAndRestore) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  // clang-format off\n  const SkM44 m44 = SkM44(4, 0, 0.5, 0,\n                          0, 4, 0.5, 0,\n                          0, 0, 4.0, 0,\n                          0, 0, 0.0, 1);\n  // clang-format on\n  const SkRect local_cull_rect = SkRect::MakeLTRB(5, 5, 15, 15);\n\n  MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                            ConvertSkMatrixToSkityMatrix(SkMatrix::I()));\n  ASSERT_TRUE(tracker.using_4x4_matrix());\n\n  tracker.save();\n  ASSERT_TRUE(tracker.using_4x4_matrix());\n\n  tracker.transform(ConvertSkM44ToMatrix(m44));\n  ASSERT_TRUE(tracker.using_4x4_matrix());\n  ASSERT_EQ(tracker.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker.matrix_4x4(), ConvertSkM44ToMatrix(m44));\n\n  tracker.restore();\n  ASSERT_TRUE(tracker.using_4x4_matrix());\n  ASSERT_EQ(tracker.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker.local_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker.matrix_4x4(), ConvertSkM44ToMatrix(SkM44()));\n}\n\nTEST(DisplayListMatrixClipTracker, Translate) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  const SkMatrix matrix = SkMatrix::Scale(4, 4);\n  const SkM44 m44 = SkM44::Scale(4, 4);\n  const SkMatrix translated_matrix =\n      SkMatrix::Concat(matrix, SkMatrix::Translate(5, 1));\n  const SkM44 translated_m44 = SkM44(translated_matrix);\n  const SkRect local_cull_rect = SkRect::MakeLTRB(0, 4, 10, 14);\n\n  MatrixClipTracker tracker1(ConvertSkRectToSkityRect(cull_rect),\n                             ConvertSkM44ToMatrix(m44));\n  tracker1.translate(5, 1);\n\n  ASSERT_TRUE(tracker1.using_4x4_matrix());\n  ASSERT_EQ(tracker1.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker1.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker1.matrix_4x4(), ConvertSkM44ToMatrix(translated_m44));\n}\n\nTEST(DisplayListMatrixClipTracker, Scale) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  const SkMatrix matrix = SkMatrix::Scale(4, 4);\n  const SkM44 m44 = SkM44::Scale(4, 4);\n  const SkMatrix scaled_matrix =\n      SkMatrix::Concat(matrix, SkMatrix::Scale(5, 2.5));\n  const SkM44 scaled_m44 = SkM44(scaled_matrix);\n  const SkRect local_cull_rect = SkRect::MakeLTRB(1, 2, 3, 6);\n\n  MatrixClipTracker tracker1(ConvertSkRectToSkityRect(cull_rect),\n                             ConvertSkM44ToMatrix(m44));\n  tracker1.scale(5, 2.5);\n\n  ASSERT_TRUE(tracker1.using_4x4_matrix());\n  ASSERT_EQ(tracker1.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker1.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker1.matrix_4x4(), ConvertSkM44ToMatrix(scaled_m44));\n}\n\nTEST(DisplayListMatrixClipTracker, Skew) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  const SkMatrix matrix = SkMatrix::Scale(4, 4);\n  const SkM44 m44 = SkM44::Scale(4, 4);\n  const SkMatrix skewed_matrix =\n      SkMatrix::Concat(matrix, SkMatrix::Skew(.25, 0));\n  const SkM44 skewed_m44 = SkM44(skewed_matrix);\n  const SkRect local_cull_rect = SkRect::MakeLTRB(1.25, 5, 13.75, 15);\n\n  MatrixClipTracker tracker1(ConvertSkRectToSkityRect(cull_rect),\n                             ConvertSkM44ToMatrix(m44));\n  tracker1.skew(.25, 0);\n\n  ASSERT_TRUE(tracker1.using_4x4_matrix());\n  ASSERT_EQ(tracker1.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker1.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker1.matrix_4x4(), ConvertSkM44ToMatrix(skewed_m44));\n}\n\nTEST(DisplayListMatrixClipTracker, Rotate) {\n  const skity::Rect cull_rect = skity::Rect::MakeLTRB(20, 20, 60, 60);\n  const skity::Matrix matrix = skity::Matrix::Scale(4, 4);\n  skity::Matrix rotated_matrix = matrix;\n  rotated_matrix.PreConcat(skity::Matrix::RotateDeg(90));\n  const skity::Rect local_cull_rect = skity::Rect::MakeLTRB(5, -15, 15, -5);\n\n  MatrixClipTracker tracker1(cull_rect, matrix);\n  tracker1.rotate(90);\n\n  ASSERT_TRUE(tracker1.using_4x4_matrix());\n  ASSERT_EQ(tracker1.device_cull_rect(), cull_rect);\n  // tracker1.local_cull_rect() and tracker1.matrix_4x4() have some precision\n  // problems. So we cannot simply use ASSERT_EQ to compare them.\n  auto tracker1_local_cull_rect = tracker1.local_cull_rect();\n  tracker1_local_cull_rect.Round();\n  ASSERT_EQ(tracker1_local_cull_rect, local_cull_rect);\n  ASSERT_EQ(tracker1.matrix_4x4(), rotated_matrix);\n}\n\nTEST(DisplayListMatrixClipTracker, Transform2DAffine) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  const SkMatrix matrix = SkMatrix::Scale(4, 4);\n  const SkM44 m44 = SkM44::Scale(4, 4);\n\n  const SkMatrix transformed_matrix =\n      SkMatrix::Concat(matrix, SkMatrix::MakeAll(2, 0, 5,  //\n                                                 0, 2, 6,  //\n                                                 0, 0, 1));\n  const SkM44 transformed_m44 = SkM44(transformed_matrix);\n  const SkRect local_cull_rect = SkRect::MakeLTRB(0, -0.5, 5, 4.5);\n\n  MatrixClipTracker tracker1(ConvertSkRectToSkityRect(cull_rect),\n                             ConvertSkM44ToMatrix(m44));\n  tracker1.transform2DAffine(2, 0, 5,  //\n                             0, 2, 6);\n  ASSERT_TRUE(tracker1.using_4x4_matrix());\n  ASSERT_EQ(tracker1.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker1.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker1.matrix_4x4(), ConvertSkM44ToMatrix(transformed_m44));\n}\n\nTEST(DisplayListMatrixClipTracker, TransformFullPerspectiveUsing3x3Matrix) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  const SkMatrix matrix = SkMatrix::Scale(4, 4);\n  const SkM44 m44 = SkM44::Scale(4, 4);\n\n  const SkMatrix transformed_matrix =\n      SkMatrix::Concat(matrix, SkMatrix::MakeAll(2, 0, 5,  //\n                                                 0, 2, 6,  //\n                                                 0, 0, 1));\n  const SkM44 transformed_m44 = SkM44(transformed_matrix);\n  const SkRect local_cull_rect = SkRect::MakeLTRB(0, -0.5, 5, 4.5);\n\n  MatrixClipTracker tracker1(ConvertSkRectToSkityRect(cull_rect),\n                             ConvertSkM44ToMatrix(m44));\n  tracker1.transformFullPerspective(2, 0, 0, 5,  //\n                                    0, 2, 0, 6,  //\n                                    0, 0, 1, 0,  //\n                                    0, 0, 0, 1);\n  ASSERT_TRUE(tracker1.using_4x4_matrix());\n  ASSERT_EQ(tracker1.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker1.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker1.matrix_4x4(), ConvertSkM44ToMatrix(transformed_m44));\n}\n\nTEST(DisplayListMatrixClipTracker, TransformFullPerspectiveUsing4x4Matrix) {\n  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);\n  const SkM44 m44 = SkM44::Scale(4, 4);\n\n  const SkM44 transformed_m44 = SkM44(m44, SkM44(2, 0, 0, 5,  //\n                                                 0, 2, 0, 6,  //\n                                                 0, 0, 1, 7,  //\n                                                 0, 0, 0, 1));\n  const SkRect local_cull_rect = SkRect::MakeLTRB(0, -0.5, 5, 4.5);\n\n  MatrixClipTracker tracker1(ConvertSkRectToSkityRect(cull_rect),\n                             ConvertSkM44ToMatrix(m44));\n  tracker1.transformFullPerspective(2, 0, 0, 5,  //\n                                    0, 2, 0, 6,  //\n                                    0, 0, 1, 7,  //\n                                    0, 0, 0, 1);\n  ASSERT_TRUE(tracker1.using_4x4_matrix());\n  ASSERT_EQ(tracker1.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(tracker1.local_cull_rect(),\n            ConvertSkRectToSkityRect(local_cull_rect));\n  ASSERT_EQ(tracker1.matrix_4x4(), ConvertSkM44ToMatrix(transformed_m44));\n}\n\nTEST(DisplayListMatrixClipTracker, ClipDifference) {\n  SkRect cull_rect = SkRect::MakeLTRB(20, 20, 40, 40);\n\n  auto non_reducing = [&cull_rect](const SkRect& diff_rect,\n                                   const std::string& label) {\n    {\n      MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                                skity::Matrix());\n      tracker.clipRect(ConvertSkRectToSkityRect(diff_rect),\n                       SkClipOp::kDifference, false);\n      ASSERT_EQ(tracker.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect))\n          << label;\n    }\n    {\n      MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                                skity::Matrix());\n      const SkRRect diff_rrect = SkRRect::MakeRect(diff_rect);\n      tracker.clipRRect(ConvertSkRRectToSkityRRect(diff_rrect),\n                        SkClipOp::kDifference, false);\n      ASSERT_EQ(tracker.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect))\n          << label << \" (RRect)\";\n    }\n    {\n      MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                                skity::Matrix());\n      const SkPath diff_path = SkPath().addRect(diff_rect);\n      tracker.clipPath(diff_path, SkClipOp::kDifference, false);\n      ASSERT_EQ(tracker.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect))\n          << label << \" (RRect)\";\n    }\n  };\n\n  auto reducing = [&cull_rect](const SkRect& diff_rect,\n                               const SkRect& result_rect,\n                               const std::string& label) {\n    ASSERT_TRUE(result_rect.isEmpty() || cull_rect.contains(result_rect));\n    {\n      MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                                skity::Matrix());\n      tracker.clipRect(ConvertSkRectToSkityRect(diff_rect),\n                       SkClipOp::kDifference, false);\n      ASSERT_EQ(tracker.device_cull_rect(),\n                ConvertSkRectToSkityRect(result_rect))\n          << label;\n    }\n    {\n      MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                                skity::Matrix());\n      const SkRRect diff_rrect = SkRRect::MakeRect(diff_rect);\n      tracker.clipRRect(ConvertSkRRectToSkityRRect(diff_rrect),\n                        SkClipOp::kDifference, false);\n      ASSERT_EQ(tracker.device_cull_rect(),\n                ConvertSkRectToSkityRect(result_rect))\n          << label << \" (RRect)\";\n    }\n    {\n      MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                                skity::Matrix());\n      const SkPath diff_path = SkPath().addRect(diff_rect);\n      tracker.clipPath(diff_path, SkClipOp::kDifference, false);\n      ASSERT_EQ(tracker.device_cull_rect(),\n                ConvertSkRectToSkityRect(result_rect))\n          << label << \" (RRect)\";\n    }\n  };\n\n  // Skim the corners and edge\n  non_reducing(SkRect::MakeLTRB(10, 10, 20, 20), \"outside UL corner\");\n  non_reducing(SkRect::MakeLTRB(20, 10, 40, 20), \"Above\");\n  non_reducing(SkRect::MakeLTRB(40, 10, 50, 20), \"outside UR corner\");\n  non_reducing(SkRect::MakeLTRB(40, 20, 50, 40), \"Right\");\n  non_reducing(SkRect::MakeLTRB(40, 40, 50, 50), \"outside LR corner\");\n  non_reducing(SkRect::MakeLTRB(20, 40, 40, 50), \"Below\");\n  non_reducing(SkRect::MakeLTRB(10, 40, 20, 50), \"outside LR corner\");\n  non_reducing(SkRect::MakeLTRB(10, 20, 20, 40), \"Left\");\n\n  // Overlap corners\n  non_reducing(SkRect::MakeLTRB(15, 15, 25, 25), \"covering UL corner\");\n  non_reducing(SkRect::MakeLTRB(35, 15, 45, 25), \"covering UR corner\");\n  non_reducing(SkRect::MakeLTRB(35, 35, 45, 45), \"covering LR corner\");\n  non_reducing(SkRect::MakeLTRB(15, 35, 25, 45), \"covering LL corner\");\n\n  // Overlap edges, but not across an entire side\n  non_reducing(SkRect::MakeLTRB(20, 15, 39, 25), \"Top edge left-biased\");\n  non_reducing(SkRect::MakeLTRB(21, 15, 40, 25), \"Top edge, right biased\");\n  non_reducing(SkRect::MakeLTRB(35, 20, 45, 39), \"Right edge, top-biased\");\n  non_reducing(SkRect::MakeLTRB(35, 21, 45, 40), \"Right edge, bottom-biased\");\n  non_reducing(SkRect::MakeLTRB(20, 35, 39, 45), \"Bottom edge, left-biased\");\n  non_reducing(SkRect::MakeLTRB(21, 35, 40, 45), \"Bottom edge, right-biased\");\n  non_reducing(SkRect::MakeLTRB(15, 20, 25, 39), \"Left edge, top-biased\");\n  non_reducing(SkRect::MakeLTRB(15, 21, 25, 40), \"Left edge, bottom-biased\");\n\n  // Slice all the way through the middle\n  non_reducing(SkRect::MakeLTRB(25, 15, 35, 45), \"Vertical interior slice\");\n  non_reducing(SkRect::MakeLTRB(15, 25, 45, 35), \"Horizontal interior slice\");\n\n  // Slice off each edge\n  reducing(SkRect::MakeLTRB(20, 15, 40, 25),  //\n           SkRect::MakeLTRB(20, 25, 40, 40),  //\n           \"Slice off top\");\n  reducing(SkRect::MakeLTRB(35, 20, 45, 40),  //\n           SkRect::MakeLTRB(20, 20, 35, 40),  //\n           \"Slice off right\");\n  reducing(SkRect::MakeLTRB(20, 35, 40, 45),  //\n           SkRect::MakeLTRB(20, 20, 40, 35),  //\n           \"Slice off bottom\");\n  reducing(SkRect::MakeLTRB(15, 20, 25, 40),  //\n           SkRect::MakeLTRB(25, 20, 40, 40),  //\n           \"Slice off left\");\n\n  // cull rect contains diff rect\n  non_reducing(SkRect::MakeLTRB(21, 21, 39, 39), \"Contained, non-covering\");\n\n  // cull rect equals diff rect\n  reducing(cull_rect, SkRect::MakeEmpty(), \"Perfectly covering\");\n\n  // diff rect contains cull rect\n  reducing(SkRect::MakeLTRB(15, 15, 45, 45), SkRect::MakeEmpty(), \"Smothering\");\n}\n\nTEST(DisplayListMatrixClipTracker, ClipPathWithInvertFillType) {\n  SkRect cull_rect = SkRect::MakeLTRB(0, 0, 100.0, 100.0);\n  MatrixClipTracker builder(ConvertSkRectToSkityRect(cull_rect),\n                            skity::Matrix());\n  SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);\n  clip.setFillType(SkPathFillType::kInverseWinding);\n  builder.clipPath(clip, SkClipOp::kIntersect, false);\n\n  ASSERT_EQ(builder.local_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n  ASSERT_EQ(builder.device_cull_rect(), ConvertSkRectToSkityRect(cull_rect));\n}\n\nTEST(DisplayListMatrixClipTracker, DiffClipPathWithInvertFillType) {\n  SkRect cull_rect = SkRect::MakeLTRB(0, 0, 100.0, 100.0);\n  MatrixClipTracker tracker(ConvertSkRectToSkityRect(cull_rect),\n                            skity::Matrix());\n\n  SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);\n  clip.setFillType(SkPathFillType::kInverseWinding);\n  SkRect clip_bounds = SkRect::MakeLTRB(8.2, 9.3, 22.4, 27.7);\n  tracker.clipPath(clip, SkClipOp::kDifference, false);\n\n  ASSERT_EQ(tracker.local_cull_rect(), ConvertSkRectToSkityRect(clip_bounds));\n  ASSERT_EQ(tracker.device_cull_rect(), ConvertSkRectToSkityRect(clip_bounds));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/mutators_stack_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(MutatorsStack, Initialization) {\n  MutatorsStack stack;\n  ASSERT_TRUE(true);\n}\n\nTEST(MutatorsStack, CopyConstructor) {\n  MutatorsStack stack;\n  auto rrect = skity::RRect::MakeEmpty();\n  auto rect = skity::Rect::MakeEmpty();\n  stack.PushClipRect(rect);\n  stack.PushClipRRect(rrect);\n  MutatorsStack copy = MutatorsStack(stack);\n  ASSERT_TRUE(copy == stack);\n}\n\nTEST(MutatorsStack, CopyAndUpdateTheCopy) {\n  MutatorsStack stack;\n  auto rrect = skity::RRect::MakeEmpty();\n  auto rect = skity::Rect::MakeEmpty();\n  stack.PushClipRect(rect);\n  stack.PushClipRRect(rrect);\n  MutatorsStack copy = MutatorsStack(stack);\n  copy.Pop();\n  copy.Pop();\n  ASSERT_TRUE(copy != stack);\n  ASSERT_TRUE(copy.is_empty());\n  ASSERT_TRUE(!stack.is_empty());\n  auto iter = stack.Bottom();\n  ASSERT_TRUE(iter->get()->GetType() == MutatorType::kClipRRect);\n  ASSERT_TRUE(iter->get()->GetRRect() == rrect);\n  ++iter;\n  ASSERT_TRUE(iter->get()->GetType() == MutatorType::kClipRect);\n  ASSERT_TRUE(iter->get()->GetRect() == rect);\n}\n\nTEST(MutatorsStack, PushClipRect) {\n  MutatorsStack stack;\n  auto rect = skity::Rect::MakeEmpty();\n  stack.PushClipRect(rect);\n  auto iter = stack.Bottom();\n  ASSERT_TRUE(iter->get()->GetType() == MutatorType::kClipRect);\n  ASSERT_TRUE(iter->get()->GetRect() == rect);\n}\n\nTEST(MutatorsStack, PushClipRRect) {\n  MutatorsStack stack;\n  auto rrect = skity::RRect::MakeEmpty();\n  stack.PushClipRRect(rrect);\n  auto iter = stack.Bottom();\n  ASSERT_TRUE(iter->get()->GetType() == MutatorType::kClipRRect);\n  ASSERT_TRUE(iter->get()->GetRRect() == rrect);\n}\n\nTEST(MutatorsStack, PushClipPath) {\n  MutatorsStack stack;\n  SkPath path;\n  stack.PushClipPath(path);\n  auto iter = stack.Bottom();\n  ASSERT_TRUE(iter->get()->GetType() == clay::MutatorType::kClipPath);\n  ASSERT_TRUE(iter->get()->GetPath() == path);\n}\n\nTEST(MutatorsStack, PushTransform) {\n  MutatorsStack stack;\n  skity::Matrix matrix;\n  matrix.Reset();\n  stack.PushTransform(matrix);\n  auto iter = stack.Bottom();\n  ASSERT_TRUE(iter->get()->GetType() == MutatorType::kTransform);\n  ASSERT_TRUE(iter->get()->GetMatrix() == matrix);\n}\n\nTEST(MutatorsStack, PushOpacity) {\n  MutatorsStack stack;\n  int alpha = 240;\n  stack.PushOpacity(alpha);\n  auto iter = stack.Bottom();\n  ASSERT_TRUE(iter->get()->GetType() == MutatorType::kOpacity);\n  ASSERT_TRUE(iter->get()->GetAlpha() == 240);\n}\n\nTEST(MutatorsStack, PushBackdropFilter) {\n  MutatorsStack stack;\n  const int num_of_mutators = 10;\n  for (int i = 0; i < num_of_mutators; i++) {\n    auto filter = std::make_shared<DlBlurImageFilter>(i, 5, DlTileMode::kClamp);\n    stack.PushBackdropFilter(filter, skity::Rect::MakeXYWH(i, i, i, i));\n  }\n\n  auto iter = stack.Begin();\n  int i = 0;\n  while (iter != stack.End()) {\n    ASSERT_EQ(iter->get()->GetType(), MutatorType::kBackdropFilter);\n    ASSERT_EQ(iter->get()->GetFilterMutation().GetFilter().asBlur()->sigma_x(),\n              i);\n    ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().X(), i);\n    ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().X(), i);\n    ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().Width(), i);\n    ASSERT_EQ(iter->get()->GetFilterMutation().GetFilterRect().Height(), i);\n    ++iter;\n    ++i;\n  }\n  ASSERT_EQ(i, num_of_mutators);\n}\n\nTEST(MutatorsStack, Pop) {\n  MutatorsStack stack;\n  skity::Matrix matrix;\n  matrix.Reset();\n  stack.PushTransform(matrix);\n  stack.Pop();\n  auto iter = stack.Bottom();\n  ASSERT_TRUE(iter == stack.Top());\n}\n\nTEST(MutatorsStack, Traversal) {\n  MutatorsStack stack;\n  skity::Matrix matrix;\n  matrix.Reset();\n  stack.PushTransform(matrix);\n  auto rect = skity::Rect::MakeEmpty();\n  stack.PushClipRect(rect);\n  auto rrect = skity::RRect::MakeEmpty();\n  stack.PushClipRRect(rrect);\n  auto iter = stack.Bottom();\n  int index = 0;\n  while (iter != stack.Top()) {\n    switch (index) {\n      case 0:\n        ASSERT_TRUE(iter->get()->GetType() == MutatorType::kClipRRect);\n        ASSERT_TRUE(iter->get()->GetRRect() == rrect);\n        break;\n      case 1:\n        ASSERT_TRUE(iter->get()->GetType() == MutatorType::kClipRect);\n        ASSERT_TRUE(iter->get()->GetRect() == rect);\n        break;\n      case 2:\n        ASSERT_TRUE(iter->get()->GetType() == MutatorType::kTransform);\n        ASSERT_TRUE(iter->get()->GetMatrix() == matrix);\n        break;\n      default:\n        break;\n    }\n    ++iter;\n    ++index;\n  }\n}\n\nTEST(MutatorsStack, Equality) {\n  MutatorsStack stack;\n  skity::Matrix matrix = skity::Matrix::Scale(1, 1);\n  stack.PushTransform(matrix);\n  skity::Rect rect = skity::Rect::MakeEmpty();\n  stack.PushClipRect(rect);\n  skity::RRect rrect = skity::RRect::MakeEmpty();\n  stack.PushClipRRect(rrect);\n  SkPath path;\n  stack.PushClipPath(path);\n  int alpha = 240;\n  stack.PushOpacity(alpha);\n  auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);\n  stack.PushBackdropFilter(filter, skity::Rect::MakeEmpty());\n\n  MutatorsStack stack_other;\n  skity::Matrix matrix_other = skity::Matrix::Scale(1, 1);\n  stack_other.PushTransform(matrix_other);\n  skity::Rect rect_other = skity::Rect::MakeEmpty();\n  stack_other.PushClipRect(rect_other);\n  skity::RRect rrect_other = skity::RRect::MakeEmpty();\n  stack_other.PushClipRRect(rrect_other);\n  SkPath other_path;\n  stack_other.PushClipPath(other_path);\n  int other_alpha = 240;\n  stack_other.PushOpacity(other_alpha);\n  auto other_filter =\n      std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);\n  stack_other.PushBackdropFilter(other_filter, skity::Rect::MakeEmpty());\n\n  ASSERT_TRUE(stack == stack_other);\n}\n\nTEST(Mutator, Initialization) {\n  skity::Rect rect = skity::Rect::MakeEmpty();\n  Mutator mutator = Mutator(rect);\n  ASSERT_TRUE(mutator.GetType() == MutatorType::kClipRect);\n  ASSERT_TRUE(mutator.GetRect() == rect);\n\n  skity::RRect rrect = skity::RRect::MakeEmpty();\n  Mutator mutator2 = Mutator(rrect);\n  ASSERT_TRUE(mutator2.GetType() == MutatorType::kClipRRect);\n  ASSERT_TRUE(mutator2.GetRRect() == rrect);\n\n  SkPath path;\n  Mutator mutator3 = Mutator(path);\n  ASSERT_TRUE(mutator3.GetType() == MutatorType::kClipPath);\n  ASSERT_TRUE(mutator3.GetPath() == path);\n\n  skity::Matrix matrix;\n  matrix.Reset();\n  Mutator mutator4 = Mutator(matrix);\n  ASSERT_TRUE(mutator4.GetType() == MutatorType::kTransform);\n  ASSERT_TRUE(mutator4.GetMatrix() == matrix);\n\n  int alpha = 240;\n  Mutator mutator5 = Mutator(alpha);\n  ASSERT_TRUE(mutator5.GetType() == MutatorType::kOpacity);\n\n  auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);\n  Mutator mutator6 = Mutator(filter, skity::Rect::MakeEmpty());\n  ASSERT_TRUE(mutator6.GetType() == MutatorType::kBackdropFilter);\n  ASSERT_TRUE(mutator6.GetFilterMutation().GetFilter() == *filter);\n}\n\nTEST(Mutator, CopyConstructor) {\n  skity::Rect rect = skity::Rect::MakeEmpty();\n  Mutator mutator = Mutator(rect);\n  Mutator copy = Mutator(mutator);\n  ASSERT_TRUE(mutator == copy);\n\n  skity::RRect rrect = skity::RRect::MakeEmpty();\n  Mutator mutator2 = Mutator(rrect);\n  Mutator copy2 = Mutator(mutator2);\n  ASSERT_TRUE(mutator2 == copy2);\n\n  SkPath path;\n  Mutator mutator3 = Mutator(path);\n  Mutator copy3 = Mutator(mutator3);\n  ASSERT_TRUE(mutator3 == copy3);\n\n  skity::Matrix matrix;\n  matrix.Reset();\n  Mutator mutator4 = Mutator(matrix);\n  Mutator copy4 = Mutator(mutator4);\n  ASSERT_TRUE(mutator4 == copy4);\n\n  int alpha = 240;\n  Mutator mutator5 = Mutator(alpha);\n  Mutator copy5 = Mutator(mutator5);\n  ASSERT_TRUE(mutator5 == copy5);\n\n  auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);\n  Mutator mutator6 = Mutator(filter, skity::Rect::MakeEmpty());\n  Mutator copy6 = Mutator(mutator6);\n  ASSERT_TRUE(mutator6 == copy6);\n}\n\nTEST(Mutator, Equality) {\n  skity::Matrix matrix;\n  matrix.Reset();\n  Mutator mutator = Mutator(matrix);\n  Mutator other_mutator = Mutator(matrix);\n  ASSERT_TRUE(mutator == other_mutator);\n\n  skity::Rect rect = skity::Rect::MakeEmpty();\n  Mutator mutator2 = Mutator(rect);\n  Mutator other_mutator2 = Mutator(rect);\n  ASSERT_TRUE(mutator2 == other_mutator2);\n\n  skity::RRect rrect = skity::RRect::MakeEmpty();\n  Mutator mutator3 = Mutator(rrect);\n  Mutator other_mutator3 = Mutator(rrect);\n  ASSERT_TRUE(mutator3 == other_mutator3);\n\n  SkPath path;\n  clay::Mutator mutator4 = clay::Mutator(path);\n  clay::Mutator other_mutator4 = clay::Mutator(path);\n  ASSERT_TRUE(mutator4 == other_mutator4);\n  ASSERT_FALSE(mutator2 == mutator);\n  int alpha = 240;\n  Mutator mutator5 = Mutator(alpha);\n  Mutator other_mutator5 = Mutator(alpha);\n  ASSERT_TRUE(mutator5 == other_mutator5);\n\n  auto filter1 = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);\n  auto filter2 = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);\n  Mutator mutator6 = Mutator(filter1, skity::Rect::MakeEmpty());\n  Mutator other_mutator6 = Mutator(filter2, skity::Rect::MakeEmpty());\n  ASSERT_TRUE(mutator6 == other_mutator6);\n}\n\nTEST(Mutator, UnEquality) {\n  skity::Rect rect = skity::Rect::MakeEmpty();\n  Mutator mutator = Mutator(rect);\n  skity::Matrix matrix;\n  matrix.Reset();\n  Mutator not_equal_mutator = Mutator(matrix);\n  ASSERT_TRUE(not_equal_mutator != mutator);\n\n  int alpha = 240;\n  int alpha2 = 241;\n  Mutator mutator2 = Mutator(alpha);\n  Mutator other_mutator2 = Mutator(alpha2);\n  ASSERT_TRUE(mutator2 != other_mutator2);\n\n  auto filter = std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);\n  auto filter2 =\n      std::make_shared<DlBlurImageFilter>(10, 10, DlTileMode::kClamp);\n  Mutator mutator3 = Mutator(filter, skity::Rect::MakeEmpty());\n  Mutator other_mutator3 = Mutator(filter2, skity::Rect::MakeEmpty());\n  ASSERT_TRUE(mutator3 != other_mutator3);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/paint_region.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/paint_region.h\"\n\nnamespace clay {\n\nskity::Rect PaintRegion::ComputeBounds() const {\n  skity::Rect res = skity::Rect::MakeEmpty();\n  for (const auto& r : *this) {\n    res.Join(r);\n  }\n  return res;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/paint_region.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_FLOW_PAINT_REGION_H_\n#define CLAY_FLOW_PAINT_REGION_H_\n\n#include <memory>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\n// Corresponds to area on the screen where the layer subtree has painted to.\n//\n// The area is used when adding damage of removed or dirty layer to overall\n// damage.\n//\n// Because there is a PaintRegion for each layer, it must be able to represent\n// the area with minimal overhead. This is accomplished by having one\n// vector<skity::Rect> shared between all paint regions, and each paint region\n// keeping begin and end index of rects relevant to particular subtree.\n//\n// All rects are in screen coordinates.\nclass PaintRegion {\n public:\n  PaintRegion() = default;\n  PaintRegion(std::shared_ptr<std::vector<skity::Rect>> rects, size_t from,\n              size_t to, bool has_readback, bool has_drawable_image,\n              bool has_animation, bool has_deferred_image)\n      : rects_(rects),\n        from_(from),\n        to_(to),\n        has_readback_(has_readback),\n        has_drawable_image_(has_drawable_image),\n        has_animation_(has_animation),\n        has_deferred_image_(has_deferred_image) {}\n\n  std::vector<skity::Rect>::const_iterator begin() const {\n    FML_DCHECK(is_valid());\n    return rects_->begin() + from_;\n  }\n\n  std::vector<skity::Rect>::const_iterator end() const {\n    FML_DCHECK(is_valid());\n    return rects_->begin() + to_;\n  }\n\n  // Compute bounds for this region\n  skity::Rect ComputeBounds() const;\n\n  bool is_valid() const { return rects_ != nullptr; }\n\n  // Returns true if there is a layer in subtree represented by this region\n  // that performs readback\n  bool has_readback() const { return has_readback_; }\n\n  // Returns whether there is a DrawableImageLayer in subtree represented by\n  // this region.\n  bool has_drawable_image() const { return has_drawable_image_; }\n\n  bool has_animation() const { return has_animation_; }\n\n  bool has_deferred_image() const { return has_deferred_image_; }\n\n private:\n  std::shared_ptr<std::vector<skity::Rect>> rects_;\n  size_t from_ = 0;\n  size_t to_ = 0;\n  bool has_readback_ = false;\n  bool has_drawable_image_ = false;\n  bool has_animation_ = false;\n  bool has_deferred_image_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_PAINT_REGION_H_\n"
  },
  {
    "path": "clay/flow/paint_utils.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/paint_utils.h\"\n\n#include <stdlib.h>\n\n#include <string>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n\nnamespace {\n\n[[maybe_unused]] clay::GrShaderPtr CreateCheckerboardShader(Color c1, Color c2,\n                                                            int size) {\n#ifndef ENABLE_SKITY\n  SkBitmap bm;\n  bm.allocN32Pixels(2 * size, 2 * size);\n  bm.eraseColor(ToSk(c1));\n  bm.eraseArea(SkIRect::MakeLTRB(0, 0, size, size), c2);\n  bm.eraseArea(SkIRect::MakeLTRB(size, size, 2 * size, 2 * size), c2);\n  return bm.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,\n                       SkSamplingOptions());\n#else\n#if !OS_IOS\n  auto bitmap =\n      skity::Bitmap(size * 2, size * 2, skity::AlphaType::kPremul_AlphaType);\n  auto canvas = skity::Canvas::MakeSoftwareCanvas(&bitmap);\n  canvas->Clear(ToSk(c1));\n  skity::Paint paint;\n  paint.SetColor(ToSk(c2));\n  paint.SetBlendMode(skity::BlendMode::kSrc);\n  canvas->DrawRect(skity::Rect::MakeLTRB(0, 0, size, size), paint);\n  canvas->DrawRect(skity::Rect::MakeLTRB(size, size, size * 2, size * 2),\n                   paint);\n  auto shader = skity::Shader::MakeShader(\n      skity::Image::MakeImage(bitmap.GetPixmap()), skity::SamplingOptions(),\n      skity::TileMode::kRepeat, skity::TileMode::kRepeat);\n  return shader;\n#else\n  return nullptr;\n#endif  // OS_IOS\n#endif  // ENABLE_SKITY\n}\n\n}  // anonymous namespace\n\nvoid DrawCheckerboard(clay::GrCanvas* canvas, const skity::Rect& rect) {\n#if !ENABLE_SKITY\n  if (!canvas) {\n    return;\n  }\n  // Draw a checkerboard\n  CANVAS_SAVE(canvas);\n  CANVAS_CLIP_RECT(canvas, rect);\n\n  // Secure random number generation isn't needed here.\n  // NOLINTBEGIN(clang-analyzer-security.insecureAPI.rand)\n  auto checkerboard_color =\n      Color::ARGBColor(64, rand() % 256, rand() % 256, rand() % 256);\n  // NOLINTEND(clang-analyzer-security.insecureAPI.rand)\n\n  clay::GrPaint paint;\n  PAINT_SET_SHADER(\n      paint, CreateCheckerboardShader(checkerboard_color, 0x00000000, 12));\n  CANVAS_DRAW_PAINT(canvas, paint);\n  CANVAS_RESTORE(canvas);\n\n  // Stroke the drawn area\n  clay::GrPaint debug_paint;\n  PAINT_SET_STROKE_WIDTH(debug_paint, 8);\n  PAINT_SET_COLOR(debug_paint, Color::ColorSetA(checkerboard_color, 255));\n  PAINT_SET_STYLE(debug_paint, 1);\n  CANVAS_DRAW_RECT(canvas, rect, debug_paint);\n#endif\n}\n\n#if !defined(NDEBUG)\nvoid DrawRasterCacheTag(GrCanvas* canvas, float x, float y, int use_count) {\n  FML_DCHECK(use_count >= 0);\n  std::string tag = std::to_string(use_count);\n  GrPaint paint;\n  if (use_count == 0) {\n    PAINT_SET_COLOR(paint, Color::kRed());\n  } else {\n    PAINT_SET_COLOR(paint, Color::kGreen());\n  }\n#ifndef ENABLE_SKITY\n  SkFont font;\n  font.setSize(20.f);\n  canvas->drawString(tag.c_str(), x, y, font, paint);\n#else\n  paint.SetTextSize(20.f);\n  canvas->DrawSimpleText2(tag.c_str(), x, y, paint);\n#endif  // ENABLE_SKITY\n}\n\nvoid DrawDebugBorders(GrCanvas* canvas, const skity::Rect& bounds) {\n  GrPaint paint;\n  PAINT_SET_COLOR(paint, Color::ARGBColor(0xFF, 210, 105, 30));  // brown\n  PAINT_SET_STYLE(paint, GrPaint::kStroke_Style);\n  PAINT_SET_STROKE_WIDTH(paint, 3.f);\n  auto rect = bounds;\n  rect.Inset(0.5f, 0.5f);\n  CANVAS_DRAW_RECT(canvas, rect, paint);\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/paint_utils.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_PAINT_UTILS_H_\n#define CLAY_FLOW_PAINT_UTILS_H_\n\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\ntypedef void (*CheckerboardFunc)(clay::GrCanvas*, const skity::Rect&);\n\nvoid DrawCheckerboard(clay::GrCanvas* canvas, const skity::Rect& rect);\n\n#if !defined(NDEBUG)\nvoid DrawRasterCacheTag(GrCanvas* canvas, float x, float y, int use_count);\nvoid DrawDebugBorders(GrCanvas* canvas, const skity::Rect& bounds);\n#endif\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_PAINT_UTILS_H_\n"
  },
  {
    "path": "clay/flow/raster_cache.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/raster_cache.h\"\n\n#include <inttypes.h>\n\n#include <cstddef>\n#include <utility>\n#include <vector>\n\n#include \"clay/common/constants.h\"\n#include \"clay/common/graphics/graphic_meminfo.h\"\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/paint_utils.h\"\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nnamespace {\n\nstruct GpuTarget {\n  clay::GrCanvas* canvas = nullptr;\n#ifndef ENABLE_SKITY\n  sk_sp<SkSurface> surface;\n#else\n  std::unique_ptr<skity::GPURenderTarget> render_target;\n#endif  // ENABLE_SKITY\n};\n\nGpuTarget CreateGpuTarget(const RasterCache::Context& context, int width,\n                          int height) {\n#ifndef ENABLE_SKITY\n  const SkImageInfo image_info = SkImageInfo::MakeN32Premul(\n      width, height, sk_ref_sp(context.dst_color_space));\n\n  auto surface = context.gr_context ? SkSurface::MakeRenderTarget(\n                                          context.gr_context,\n                                          skgpu::Budgeted::kYes, image_info)\n                                    : SkSurface::MakeRaster(image_info);\n  if (!surface) {\n    return {nullptr, nullptr};\n  }\n  return {surface->getCanvas(), surface};\n#else\n  // TODO(Jinsong): Support raster cache for skity software rendering.\n  if (!context.gr_context) {\n    return {nullptr, nullptr};\n  }\n  skity::GPURenderTargetDescriptor desc;\n  desc.width = width;\n  desc.height = height;\n  desc.sample_count = 4;\n\n  auto render_target = context.gr_context->CreateRenderTarget(desc);\n  if (!render_target) {\n    return {nullptr, nullptr};\n  }\n  return {render_target->GetCanvas(), std::move(render_target)};\n#endif  // ENABLE_SKITY\n}\n\n}  // namespace\n\nRasterCacheResult::RasterCacheResult(clay::GrImagePtr image,\n                                     const skity::Rect& logical_rect,\n                                     const char* unused,\n                                     const skity::Matrix& matrix)\n    : image_(std::move(image)), logical_rect_(logical_rect), matrix_(matrix) {\n  flow_id_ = TRACE_FLOW_ID();\n  TRACE_EVENT_INSTANT(\"clay\", \"RasterCacheResult.Begin\",\n                      [&](lynx::perfetto::EventContext ctx) {\n                        ctx.event()->add_flow_ids(flow_id_);\n                      });\n}\n\nRasterCacheResult::~RasterCacheResult() {\n  TRACE_EVENT_INSTANT(\"clay\", \"RasterCacheResult.End\",\n                      [&](lynx::perfetto::EventContext ctx) {\n                        ctx.event()->add_terminating_flow_ids(flow_id_);\n                      });\n}\n\nvoid RasterCacheResult::draw(clay::GrCanvas& canvas,\n                             const clay::GrPaint* paint) const {\n  auto canvas_ptr = &canvas;\n  CANVAS_AUTO_RESTORE(canvas_ptr, true);\n  auto matrix = CANVAS_GET_TOTAL_MATRIX(canvas_ptr);\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n  matrix = RasterCacheUtil::GetIntegralTransCTM(matrix);\n#endif\n#ifdef ENABLE_RASTER_CACHE_SCALE\n  if (RasterCacheUtil::IsMatrixSimilarity(matrix_)) {\n    CANVAS_TRANSLATE(canvas_ptr, logical_rect_.Left(), logical_rect_.Top());\n    // Canvas already contains the transformations including scale and rotation,\n    // so if the cached matrix contains scale, we need to scale back. Blit the\n    // image to the target logical rect using DrawImageRect to scale back.\n    TRACE_EVENT(\"clay\", \"RasterCacheResult::draw()\",\n                [&](lynx::perfetto::EventContext ctx) {\n                  ctx.event()->add_flow_ids(flow_id_);\n                });\n    CANVAS_DRAW_IMAGE_RECT(canvas_ptr, image_,\n                           skity::Rect::MakeXYWH(0, 0, logical_rect_.Width(),\n                                                 logical_rect_.Height()),\n                           SAMPLING_OPTIONS(1, 0), paint);\n    return;\n  }\n#endif  // ENABLE_RASTER_CACHE_SCALE\n  auto bounds =\n      RasterCacheUtil::GetRoundedOutDeviceBounds(logical_rect_, matrix);\n  CANVAS_RESET_MATRIX(canvas_ptr);\n  TRACE_EVENT(\"clay\", \"RasterCacheResult::draw()\",\n              [&](lynx::perfetto::EventContext ctx) {\n                ctx.event()->add_flow_ids(flow_id_);\n              });\n  CANVAS_DRAW_IMAGE(canvas_ptr, image_, bounds, SAMPLING_OPTIONS(0, 0), paint);\n}\n\nRasterCache::RasterCache(size_t access_threshold,\n                         size_t display_list_cache_limit_per_frame)\n    : access_threshold_(access_threshold),\n      display_list_cache_limit_per_frame_(display_list_cache_limit_per_frame),\n      checkerboard_images_(false) {}\n\n/// @note Procedure doesn't copy all closures.\nstd::unique_ptr<RasterCacheResult> RasterCache::Rasterize(\n    const RasterCache::Context& context,\n    const std::function<void(clay::GrCanvas*)>& draw_function,\n    const std::function<void(clay::GrCanvas*, const skity::Rect& rect)>&\n        draw_checkerboard) const {\n  auto matrix = context.matrix;\n#ifndef SUPPORT_FRACTIONAL_TRANSLATION\n  matrix = RasterCacheUtil::GetIntegralTransCTM(matrix);\n#endif\n#ifdef ENABLE_RASTER_CACHE_SCALE\n  if (RasterCacheUtil::IsMatrixSimilarity(matrix)) {\n    // With similarity matrix, we only consider the scale factor to cache the\n    // texture, and drop translation and rotation transformations (if any). And\n    // when the cached texture is drawn, we need to scale back to the original\n    // size.\n    float scale = RasterCacheUtil::GetScaleFactor(matrix);\n    // Create a new surface with the scaled size.\n    auto target = CreateGpuTarget(\n        context, std::ceil(context.logical_rect.Width() * scale),\n        std::ceil(context.logical_rect.Height() * scale));\n    clay::GrCanvas* canvas = target.canvas;\n    if (!canvas) {\n      return nullptr;\n    }\n    CANVAS_CLEAR(canvas, clay::Color::kTransparent());\n\n    // Scale the canvas.\n    CANVAS_SCALE(canvas, scale, scale);\n    CANVAS_TRANSLATE(canvas, -context.logical_rect.Left(),\n                     -context.logical_rect.Top());\n\n    draw_function(canvas);\n\n    if (checkerboard_images_) {\n      draw_checkerboard(canvas, context.logical_rect);\n    }\n\n    return std::make_unique<RasterCacheResult>(\n#ifndef ENABLE_SKITY\n        target.surface->makeImageSnapshot(),\n#else\n        context.gr_context->MakeSnapshot(std::move(target.render_target)),\n#endif  // ENABLE_SKITY\n        context.logical_rect, context.flow_type, matrix);\n  }\n#endif  // ENABLE_RASTER_CACHE_SCALE\n  skity::Rect dest_rect =\n      RasterCacheUtil::GetRoundedOutDeviceBounds(context.logical_rect, matrix);\n  auto target = CreateGpuTarget(context, dest_rect.Width(), dest_rect.Height());\n  clay::GrCanvas* canvas = target.canvas;\n  if (!canvas) {\n    return nullptr;\n  }\n\n  CANVAS_CLEAR(canvas, clay::Color::kTransparent());\n  CANVAS_TRANSLATE(canvas, -dest_rect.Left(), -dest_rect.Top());\n  CANVAS_CONCAT(canvas, matrix);\n  draw_function(canvas);\n\n  if (checkerboard_images_) {\n    draw_checkerboard(canvas, context.logical_rect);\n  }\n\n  return std::make_unique<RasterCacheResult>(\n#ifndef ENABLE_SKITY\n      target.surface->makeImageSnapshot(),\n#else\n      context.gr_context->MakeSnapshot(std::move(target.render_target)),\n#endif  // ENABLE_SKITY\n      context.logical_rect, context.flow_type, matrix);\n}\n\nbool RasterCache::UpdateCacheEntry(\n    const RasterCacheKeyID& id, const Context& raster_cache_context,\n    const std::function<void(clay::GrCanvas*)>& render_function) const {\n  RasterCacheKey key = RasterCacheKey(id, raster_cache_context.matrix);\n  Entry& entry = cache_[key];\n  if (!entry.image) {\n    void (*func)(clay::GrCanvas*, const skity::Rect& rect) = DrawCheckerboard;\n    entry.image = Rasterize(raster_cache_context, render_function, func);\n    if (entry.image != nullptr) {\n      switch (id.type()) {\n        case RasterCacheKeyType::kPicture: {\n          display_list_cached_this_frame_++;\n          break;\n        }\n        default:\n          break;\n      }\n      return true;\n    }\n  }\n  return entry.image != nullptr;\n}\n\nRasterCache::CacheInfo RasterCache::MarkSeen(const RasterCacheKeyID& id,\n                                             const skity::Matrix& matrix,\n                                             bool visible) const {\n  RasterCacheKey key = RasterCacheKey(id, matrix);\n  Entry& entry = cache_[key];\n  entry.encountered_this_frame = true;\n  entry.visible_this_frame = visible;\n  if (visible || entry.accesses_since_visible > 0) {\n    entry.accesses_since_visible++;\n  }\n  return {entry.accesses_since_visible, entry.image != nullptr};\n}\n\nint RasterCache::GetAccessCount(const RasterCacheKeyID& id,\n                                const skity::Matrix& matrix) const {\n  RasterCacheKey key = RasterCacheKey(id, matrix);\n  auto entry = cache_.find(key);\n  if (entry != cache_.cend()) {\n    return entry->second.accesses_since_visible;\n  }\n  return -1;\n}\n\nbool RasterCache::HasEntry(const RasterCacheKeyID& id,\n                           const skity::Matrix& matrix) const {\n  RasterCacheKey key = RasterCacheKey(id, matrix);\n  if (cache_.find(key) != cache_.cend()) {\n    return true;\n  }\n  return false;\n}\n\nbool RasterCache::Draw(const RasterCacheKeyID& id, clay::GrCanvas& canvas,\n                       const clay::GrPaint* paint) const {\n  auto canvas_ptr = &canvas;\n  auto it =\n      cache_.find(RasterCacheKey(id, CANVAS_GET_TOTAL_MATRIX(canvas_ptr)));\n  if (it == cache_.end()) {\n    return false;\n  }\n\n  Entry& entry = it->second;\n\n  if (entry.image) {\n    entry.image->draw(canvas, paint);\n#ifndef NDEBUG\n    if (enable_debug_borders_) {\n      DrawDebugBorders(canvas_ptr, entry.image->logical_rect());\n    }\n\n    if (enable_raster_cache_tag_) {\n      DrawRasterCacheTag(canvas_ptr, entry.image->logical_rect().Width() / 2,\n                         entry.image->logical_rect().Height() / 2,\n                         entry.accesses_since_visible);\n    }\n#endif  // NDEBUG\n    return true;\n  }\n\n  return false;\n}\n\nvoid RasterCache::BeginFrame() {\n  display_list_cached_this_frame_ = 0;\n  picture_metrics_ = {};\n  layer_metrics_ = {};\n}\n\nvoid RasterCache::UpdateMetrics() {\n  for (auto& it : cache_) {\n    Entry& entry = it.second;\n    FML_DCHECK(entry.encountered_this_frame);\n    if (entry.image) {\n      RasterCacheMetrics& metrics = GetMetricsForKind(it.first.kind());\n      metrics.in_use_count++;\n      metrics.in_use_bytes += entry.image->image_bytes();\n    }\n    entry.encountered_this_frame = false;\n  }\n}\n\nvoid RasterCache::EvictUnusedCacheEntries() {\n  std::vector<RasterCacheKey::Map<Entry>::iterator> dead;\n\n  for (auto it = cache_.begin(); it != cache_.end(); ++it) {\n    Entry& entry = it->second;\n    if (!entry.encountered_this_frame) {\n      dead.push_back(it);\n    }\n  }\n\n  for (auto it : dead) {\n    if (it->second.image) {\n      RasterCacheMetrics& metrics = GetMetricsForKind(it->first.kind());\n      metrics.eviction_count++;\n      metrics.eviction_bytes += it->second.image->image_bytes();\n    }\n    cache_.erase(it);\n  }\n}\n\nvoid RasterCache::EndFrame() {\n  UpdateMetrics();\n  TraceStatsToTimeline();\n}\n\nvoid RasterCache::ClearRasterCacheInfo(std::vector<intptr_t>* cache_address) {\n  for (auto address : *cache_address) {\n    auto iter = raster_cache_infos_.begin();\n    while (iter != raster_cache_infos_.end()) {\n      if (iter->cache_address == static_cast<int64_t>(address)) {\n        iter = raster_cache_infos_.erase(iter);\n      } else {\n        iter++;\n      }\n    }\n  }\n  cache_address->clear();\n}\n\nvoid RasterCache::Clear() {\n  cache_.clear();\n  picture_metrics_ = {};\n  layer_metrics_ = {};\n}\n\nsize_t RasterCache::GetCachedEntriesCount() const { return cache_.size(); }\n\nsize_t RasterCache::GetLayerCachedEntriesCount() const {\n  size_t layer_cached_entries_count = 0;\n  for (const auto& item : cache_) {\n    if (item.first.kind() == RasterCacheKeyKind::kLayerMetrics) {\n      layer_cached_entries_count++;\n    }\n  }\n  return layer_cached_entries_count;\n}\n\nsize_t RasterCache::GetPictureCachedEntriesCount() const {\n  size_t picture_cached_entries_count = 0;\n  for (const auto& item : cache_) {\n    if (item.first.kind() == RasterCacheKeyKind::kPictureMetrics) {\n      picture_cached_entries_count++;\n    }\n  }\n  return picture_cached_entries_count;\n}\n\nvoid RasterCache::SetCheckboardCacheImages(bool checkerboard) {\n  if (checkerboard_images_ == checkerboard) {\n    return;\n  }\n\n  checkerboard_images_ = checkerboard;\n\n  // Clear all existing entries so previously rasterized items (with or without\n  // a checkerboard) will be refreshed in subsequent passes.\n  Clear();\n}\n\nvoid RasterCache::TraceStatsToTimeline() const {\n#if ENABLE_TRACE_PERFETTO\n  auto trace_func = [&](const char* name, size_t count) {\n    char buf[128] = {0};\n    std::sprintf(buf, \"RC.%s_%\" PRIxPTR, name,\n                 reinterpret_cast<uintptr_t>(this));\n    TRACE_COUNTER(\"clay\", lynx::perfetto::CounterTrack(buf), count);\n  };\n  trace_func(\"LayerCount\", layer_metrics_.total_count());\n  trace_func(\"LayerMBytes\",\n             layer_metrics_.total_bytes() / kMegaByteSizeInBytes);\n  trace_func(\"PictureCount\", picture_metrics_.total_count());\n  trace_func(\"PictureMBytes\",\n             picture_metrics_.total_bytes() / kMegaByteSizeInBytes);\n#endif\n}\n\nsize_t RasterCache::EstimateLayerCacheByteSize() const {\n  size_t layer_cache_bytes = 0;\n  for (const auto& item : cache_) {\n    if (item.first.kind() == RasterCacheKeyKind::kLayerMetrics &&\n        item.second.image) {\n      layer_cache_bytes += item.second.image->image_bytes();\n    }\n  }\n  return layer_cache_bytes;\n}\n\nsize_t RasterCache::EstimatePictureCacheByteSize() const {\n  size_t picture_cache_bytes = 0;\n  for (const auto& item : cache_) {\n    if (item.first.kind() == RasterCacheKeyKind::kPictureMetrics &&\n        item.second.image) {\n      picture_cache_bytes += item.second.image->image_bytes();\n    }\n  }\n  return picture_cache_bytes;\n}\n\nRasterCacheMetrics& RasterCache::GetMetricsForKind(RasterCacheKeyKind kind) {\n  switch (kind) {\n    case RasterCacheKeyKind::kPictureMetrics:\n      return picture_metrics_;\n    case RasterCacheKeyKind::kLayerMetrics:\n      return layer_metrics_;\n  }\n}\n\nbool RasterCache::RasterCacheInfoChanged() {\n  for (auto info : raster_cache_infos_) {\n    if (std::find(pre_raster_cache_images_.begin(),\n                  pre_raster_cache_images_.end(),\n                  info.image.get()) == pre_raster_cache_images_.end()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nstd::vector<RasterCacheInfo>* RasterCache::GetRasterCacheInfo() {\n  if (RasterCacheInfoChanged()) {\n    pre_raster_cache_images_.clear();\n    for (auto info : raster_cache_infos_) {\n      pre_raster_cache_images_.emplace_back(info.image.get());\n    }\n    return &raster_cache_infos_;\n  }\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/raster_cache.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_RASTER_CACHE_H_\n#define CLAY_FLOW_RASTER_CACHE_H_\n\n#include <memory>\n#include <optional>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/common/graphics/graphic_meminfo.h\"\n#include \"clay/flow/layers/picture_complexity.h\"\n#include \"clay/flow/raster_cache_key.h\"\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nenum CacheStrategy {\n  None,\n  ForceCache,\n  // not cache and not use cache.\n  NotCache,\n};\n\nenum class RasterCacheLayerStrategy { kLayer, kLayerChildren };\n\nclass RasterCacheResult {\n public:\n  RasterCacheResult(clay::GrImagePtr image, const skity::Rect& logical_rect,\n                    const char* type, const skity::Matrix& matrix);\n\n  virtual ~RasterCacheResult();\n\n  virtual void draw(clay::GrCanvas& canvas, const clay::GrPaint* paint) const;\n\n  virtual skity::Vec2 image_dimensions() const {\n    return image_ ? skity::Vec2(IMAGE_WIDTH(image_), IMAGE_HEIGHT(image_))\n                  : skity::Vec2(0, 0);\n  };\n\n  virtual int64_t image_bytes() const {\n    return image_ ? IMAGE_BYTE_SIZE(image_) : 0;\n  };\n\n  const skity::Rect& logical_rect() const { return logical_rect_; }\n\n private:\n  clay::GrImagePtr image_;\n  skity::Rect logical_rect_;\n  uint64_t flow_id_;\n  skity::Matrix matrix_;\n};\n\nclass Layer;\nclass RasterCacheItem;\nstruct PrerollContext;\nstruct PaintContext;\n\nstruct RasterCacheMetrics {\n  /**\n   * The number of cache entries with images evicted in this frame.\n   */\n  size_t eviction_count = 0;\n\n  /**\n   * The size of all of the images evicted in this frame.\n   */\n  size_t eviction_bytes = 0;\n\n  /**\n   * The number of cache entries with images used in this frame.\n   */\n  size_t in_use_count = 0;\n\n  /**\n   * The size of all of the images used in this frame.\n   */\n  size_t in_use_bytes = 0;\n\n  /**\n   * The total cache entries that had images during this frame.\n   */\n  size_t total_count() const { return in_use_count; }\n\n  /**\n   * The size of all of the cached images during this frame.\n   */\n  size_t total_bytes() const { return in_use_bytes; }\n};\n\n/**\n * RasterCache is used to cache rasterized layers or display lists to improve\n * performance.\n *\n * Life cycle of RasterCache methods:\n * - Preroll stage\n *   - LayerTree::Preroll - for each Layer in the tree:\n *     - RasterCacheItem::PrerollSetup\n *         At the start of each layer's preroll, add cache items to\n *         `PrerollContext::raster_cached_entries`.\n *     - RasterCacheItem::PrerollFinalize\n *         At the end of each layer's preroll, may mark cache entries as\n *         encountered by the current frame.\n * - Paint stage\n *   - RasterCache::EvictUnusedCacheEntries\n *       Evict cached images that are no longer used.\n *   - LayerTree::TryToPrepareRasterCache\n *       Create cache image for each cache entry if it does not exist.\n *   - LayerTree::Paint - for each layer in the tree:\n *       If layers or display lists are cached as cached images, the method\n *       `RasterCache::Draw` will be used to draw those cache images.\n *   - RasterCache::EndFrame:\n *       Computes used counts and memory then reports cache metrics.\n */\nclass RasterCache {\n public:\n  struct Context {\n    clay::GrContext* gr_context;\n#ifndef ENABLE_SKITY\n    const SkColorSpace* dst_color_space;\n#endif  // ENABLE_SKITY\n    const skity::Matrix& matrix;\n    const skity::Rect& logical_rect;\n    const char* flow_type;\n  };\n\n  struct CacheInfo {\n    const size_t accesses_since_visible;\n    const bool has_image;\n  };\n\n  std::unique_ptr<RasterCacheResult> Rasterize(\n      const RasterCache::Context& context,\n      const std::function<void(clay::GrCanvas*)>& draw_function,\n      const std::function<void(clay::GrCanvas*, const skity::Rect& rect)>&\n          draw_checkerboard) const;\n\n  explicit RasterCache(\n      size_t access_threshold = 2,\n      size_t picture_and_display_list_cache_limit_per_frame =\n          RasterCacheUtil::kDefaultPictureAndDispLayListCacheLimitPerFrame);\n\n  virtual ~RasterCache() = default;\n\n  // Draws this item if it should be rendered from the cache and returns\n  // true iff it was successfully drawn. Typically this should only fail\n  // if the item was disabled due to conditions discovered during |Preroll|\n  // or if the attempt to populate the entry failed due to bounds overflow\n  // conditions.\n  bool Draw(const RasterCacheKeyID& id, clay::GrCanvas& canvas,\n            const clay::GrPaint* paint) const;\n\n  bool HasEntry(const RasterCacheKeyID& id, const skity::Matrix&) const;\n\n  void BeginFrame();\n\n  void EvictUnusedCacheEntries();\n\n  void EndFrame();\n\n  void Clear();\n\n  void SetCheckboardCacheImages(bool checkerboard);\n\n  const RasterCacheMetrics& picture_metrics() const { return picture_metrics_; }\n  const RasterCacheMetrics& layer_metrics() const { return layer_metrics_; }\n\n  size_t GetCachedEntriesCount() const;\n\n  /**\n   * Return the number of map entries in the layer cache regardless of whether\n   * the entries have been populated with an image.\n   */\n  size_t GetLayerCachedEntriesCount() const;\n\n  /**\n   * Return the number of map entries in the picture caches (SkPicture and\n   * DisplayList) regardless of whether the entries have been populated with\n   * an image.\n   */\n  size_t GetPictureCachedEntriesCount() const;\n\n  /**\n   * @brief Estimate how much memory is used by picture raster cache entries in\n   * bytes, including cache entries in the SkPicture cache and the DisplayList\n   * cache.\n   *\n   * Only SkImage's memory usage is counted as other objects are often much\n   * smaller compared to SkImage. SkImageInfo::computeMinByteSize is used to\n   * estimate the SkImage memory usage.\n   */\n  size_t EstimatePictureCacheByteSize() const;\n\n  /**\n   * @brief Estimate how much memory is used by layer raster cache entries in\n   * bytes.\n   *\n   * Only SkImage's memory usage is counted as other objects are often much\n   * smaller compared to SkImage. SkImageInfo::computeMinByteSize is used to\n   * estimate the SkImage memory usage.\n   */\n  size_t EstimateLayerCacheByteSize() const;\n\n  /**\n   * @brief Return the number of frames that a picture must be prepared\n   * before it will be cached. If the number is 0, then no picture will\n   * ever be cached.\n   *\n   * If the number is one, then it must be prepared and drawn on 1 frame\n   * and it will then be cached on the next frame if it is prepared.\n   */\n  size_t access_threshold() const { return access_threshold_; }\n\n  bool GenerateNewCacheInThisFrame() const {\n    // Disabling caching when access_threshold is zero is historic behavior.\n    return access_threshold_ != 0 && display_list_cached_this_frame_ <\n                                         display_list_cache_limit_per_frame_;\n  }\n\n  /**\n   * @brief The entry whose RasterCacheKey is generated by RasterCacheKeyID\n   * and matrix is marked as encountered by the current frame. The entry\n   * will be created if it does not exist. Optionally the entry will be marked\n   * as visible in the current frame if the caller determines that it\n   * intersects the cull rect. The access_count of the entry will be\n   * increased if it is visible, or if it was ever visible.\n   * @return the number of times the entry has been hit since it was created.\n   * For a new entry that will be 1 if it is visible, or zero if non-visible.\n   */\n  CacheInfo MarkSeen(const RasterCacheKeyID& id, const skity::Matrix& matrix,\n                     bool visible) const;\n\n  /**\n   * Returns the access count (i.e. accesses_since_visible) for the given\n   * entry in the cache, or -1 if no such entry exists.\n   */\n  int GetAccessCount(const RasterCacheKeyID& id,\n                     const skity::Matrix& matrix) const;\n\n  bool UpdateCacheEntry(\n      const RasterCacheKeyID& id, const Context& raster_cache_context,\n      const std::function<void(clay::GrCanvas*)>& render_function) const;\n\n  void set_needs_build_all_caches(bool needs_build_all_caches) {\n    needs_build_all_caches_ = needs_build_all_caches;\n  }\n\n  bool RasterCacheInfoChanged();\n  std::vector<RasterCacheInfo>* GetRasterCacheInfo();\n\n  void ClearRasterCacheInfo(std::vector<intptr_t>* cache_address);\n\n private:\n  struct Entry {\n    bool encountered_this_frame = false;\n    bool visible_this_frame = false;\n    size_t accesses_since_visible = 0;\n    std::unique_ptr<RasterCacheResult> image;\n  };\n\n  void UpdateMetrics();\n\n  RasterCacheMetrics& GetMetricsForKind(RasterCacheKeyKind kind);\n\n  std::vector<intptr_t> sweep_cache_address_;\n\n  const size_t access_threshold_;\n  const size_t display_list_cache_limit_per_frame_;\n  mutable size_t display_list_cached_this_frame_ = 0;\n  RasterCacheMetrics layer_metrics_;\n  RasterCacheMetrics picture_metrics_;\n  mutable RasterCacheKey::Map<Entry> cache_;\n  bool checkerboard_images_;\n  bool needs_build_all_caches_ = false;\n  std::vector<RasterCacheInfo> raster_cache_infos_;\n  std::vector<clay::GrImage*> pre_raster_cache_images_;\n\n  void TraceStatsToTimeline() const;\n\n#if !defined(NDEBUG)\n  // Will add brown solid borders to every entry.\n  bool enable_debug_borders_ = false;\n  // Will add accesses count as a tag to every raster cache area.\n  bool enable_raster_cache_tag_ = false;\n#endif\n\n  friend class RasterCacheItem;\n  friend class LayerRasterCacheItem;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(RasterCache);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_RASTER_CACHE_H_\n"
  },
  {
    "path": "clay/flow/raster_cache_item.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_RASTER_CACHE_ITEM_H_\n#define CLAY_FLOW_RASTER_CACHE_ITEM_H_\n\n#include <memory>\n#include <optional>\n\n#include \"clay/flow/raster_cache_key.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nstruct PrerollContext;\nstruct PaintContext;\nclass RasterCache;\nclass LayerRasterCacheItem;\nclass PictureRasterCacheItem;\n\nclass RasterCacheItem {\n public:\n  enum CacheState {\n    kNone = 0,\n    kCurrent,\n    kChildren,\n  };\n\n  explicit RasterCacheItem(RasterCacheKeyID key_id,\n                           CacheState cache_state = CacheState::kNone,\n                           unsigned child_entries = 0)\n      : key_id_(key_id),\n        cache_state_(cache_state),\n        child_items_(child_entries) {}\n\n  virtual void PrerollSetup(PrerollContext* context,\n                            const skity::Matrix& matrix) = 0;\n\n  virtual void PrerollFinalize(PrerollContext* context,\n                               const skity::Matrix& matrix) = 0;\n\n  virtual bool Draw(const PaintContext& context,\n                    const clay::GrPaint* paint) const = 0;\n\n  virtual bool Draw(const PaintContext& context, clay::GrCanvas* canvas,\n                    const clay::GrPaint* paint) const = 0;\n\n  virtual std::optional<RasterCacheKeyID> GetId() const { return key_id_; }\n\n  virtual bool TryToPrepareRasterCache(const PaintContext& context,\n                                       bool parent_cached = false) const = 0;\n\n  unsigned child_items() const { return child_items_; }\n\n  void set_matrix(const skity::Matrix& matrix) { matrix_ = matrix; }\n\n  CacheState cache_state() const { return cache_state_; }\n\n  bool need_caching() const { return cache_state_ != CacheState::kNone; }\n\n  bool has_been_cached() const { return has_been_cached_; }\n\n  virtual ~RasterCacheItem() = default;\n\n protected:\n  // The id for cache the layer self.\n  RasterCacheKeyID key_id_;\n  CacheState cache_state_ = CacheState::kNone;\n  mutable skity::Matrix matrix_;\n  unsigned child_items_;\n\n  mutable bool has_been_cached_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_RASTER_CACHE_ITEM_H_\n"
  },
  {
    "path": "clay/flow/raster_cache_key.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/raster_cache_key.h\"\n\n#include <optional>\n\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/layer.h\"\nnamespace clay {\n\n//\n\nstd::optional<std::vector<RasterCacheKeyID>> RasterCacheKeyID::LayerChildrenIds(\n    const Layer* layer) {\n  FML_DCHECK(layer->as_container_layer());\n  auto& children_layers = layer->as_container_layer()->layers();\n  auto children_count = children_layers.size();\n  if (children_count == 0) {\n    return std::nullopt;\n  }\n  std::vector<RasterCacheKeyID> ids;\n  std::transform(\n      children_layers.begin(), children_layers.end(), std::back_inserter(ids),\n      [](auto& layer) -> RasterCacheKeyID { return layer->caching_key_id(); });\n  return ids;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/raster_cache_key.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef CLAY_FLOW_RASTER_CACHE_KEY_H_\n#define CLAY_FLOW_RASTER_CACHE_KEY_H_\n\n#include <optional>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/hash_combine.h\"\n#include \"clay/flow/raster_cache_util.h\"\n#include \"clay/fml/logging.h\"\n#include \"skity/geometry/matrix.hpp\"\n\nnamespace clay {\n\nclass Layer;\n\nenum class RasterCacheKeyType { kLayer, kPicture, kLayerChildren };\n\nclass RasterCacheKeyID {\n public:\n  static constexpr uint64_t kDefaultUniqueID = 0;\n\n  RasterCacheKeyID(uint64_t unique_id, RasterCacheKeyType type)\n      : unique_id_(unique_id), type_(type) {}\n\n  RasterCacheKeyID(std::vector<RasterCacheKeyID> child_ids,\n                   RasterCacheKeyType type)\n      : unique_id_(kDefaultUniqueID),\n        type_(type),\n        child_ids_(std::move(child_ids)) {}\n\n  uint64_t unique_id() const { return unique_id_; }\n\n  RasterCacheKeyType type() const { return type_; }\n\n  const std::vector<RasterCacheKeyID>& child_ids() const { return child_ids_; }\n\n  static std::optional<std::vector<RasterCacheKeyID>> LayerChildrenIds(\n      const Layer* layer);\n\n  std::size_t GetHash() const {\n    if (cached_hash_) {\n      return *cached_hash_;\n    }\n    std::size_t seed = fml::HashCombine();\n    fml::HashCombineSeed(seed, unique_id_);\n    fml::HashCombineSeed(seed, type_);\n    for (auto& child_id : child_ids_) {\n      fml::HashCombineSeed(seed, child_id.GetHash());\n    }\n    cached_hash_ = seed;\n    return seed;\n  }\n\n  bool operator==(const RasterCacheKeyID& other) const {\n    return unique_id_ == other.unique_id_ && type_ == other.type_ &&\n           GetHash() == other.GetHash() && child_ids_ == other.child_ids_;\n  }\n\n  bool operator!=(const RasterCacheKeyID& other) const {\n    return !operator==(other);\n  }\n\n private:\n  const uint64_t unique_id_;\n  const RasterCacheKeyType type_;\n  const std::vector<RasterCacheKeyID> child_ids_;\n  mutable std::optional<std::size_t> cached_hash_;\n};\n\nenum class RasterCacheKeyKind { kLayerMetrics, kPictureMetrics };\n\nclass RasterCacheKey {\n public:\n  RasterCacheKey(uint64_t unique_id, RasterCacheKeyType type,\n                 const skity::Matrix& ctm)\n      : RasterCacheKey(RasterCacheKeyID(unique_id, type), ctm) {}\n\n  RasterCacheKey(RasterCacheKeyID id, const skity::Matrix& ctm)\n      : id_(std::move(id)), matrix_(ctm) {\n    matrix_.SetTranslateX(0);\n    matrix_.SetTranslateY(0);\n  }\n\n  const RasterCacheKeyID& id() const { return id_; }\n  const skity::Matrix& matrix() const { return matrix_; }\n\n  RasterCacheKeyKind kind() const {\n    switch (id_.type()) {\n      case RasterCacheKeyType::kPicture:\n        return RasterCacheKeyKind::kPictureMetrics;\n      case RasterCacheKeyType::kLayer:\n      case RasterCacheKeyType::kLayerChildren:\n        return RasterCacheKeyKind::kLayerMetrics;\n    }\n  }\n\n  struct Hash {\n    std::size_t operator()(RasterCacheKey const& key) const {\n      return key.id_.GetHash();\n    }\n  };\n\n  struct Equal {\n    constexpr bool operator()(const RasterCacheKey& lhs,\n                              const RasterCacheKey& rhs) const {\n#ifdef ENABLE_RASTER_CACHE_SCALE\n      if (lhs.id_ == rhs.id_ &&\n          RasterCacheUtil::IsMatrixSimilarity(lhs.matrix_) &&\n          RasterCacheUtil::IsMatrixSimilarity(rhs.matrix_)) {\n        // The similarity matrix with small scale(<=1.25) could reuse the same\n        // cache. Since the matrix is similarity, we only need to compare the\n        // uniform scale.\n        float l_scale = RasterCacheUtil::GetScaleFactor(lhs.matrix_);\n        float r_scale = RasterCacheUtil::GetScaleFactor(rhs.matrix_);\n        auto scale_rate =\n            l_scale > r_scale ? (l_scale / r_scale) : (r_scale / l_scale);\n        return scale_rate <= 1.25;\n      }\n#endif\n      return lhs.id_ == rhs.id_ && lhs.matrix_ == rhs.matrix_;\n    }\n  };\n\n  template <class Value>\n  using Map = std::unordered_map<RasterCacheKey, Value, Hash, Equal>;\n\n private:\n  RasterCacheKeyID id_;\n\n  // ctm where only fractional (0-1) translations are preserved:\n  //   matrix_ = ctm;\n  //   matrix_[skity::Matrix::kMTransX] = SkScalarFraction(ctm.GetTranslateX());\n  //   matrix_[skity::Matrix::kMTransY] = SkScalarFraction(ctm.GetTranslateY());\n  skity::Matrix matrix_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_RASTER_CACHE_KEY_H_\n"
  },
  {
    "path": "clay/flow/raster_cache_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/image_filter_layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/layers/picture_layer.h\"\n#include \"clay/flow/layers/transform_layer.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/raster_cache_item.h\"\n#include \"clay/flow/testing/gpu_object_layer_test.h\"\n#include \"clay/flow/testing/mock_raster_cache.h\"\n#include \"clay/flow/testing/picture_test_utils.h\"\n#include \"clay/gfx/skia/picture_skia.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/assertions_skia.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkPaint.h\"\n#include \"third_party/skia/include/core/SkPicture.h\"\n#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing clay::GPUObject;\n\nTEST(RasterCache, SimpleInitialization) {\n  clay::RasterCache cache;\n  ASSERT_TRUE(true);\n}\n\nTEST(RasterCache, MetricsOmitUnpopulatedEntries) {\n  size_t threshold = 2;\n  clay::RasterCache cache(threshold);\n\n  skity::Matrix matrix = skity::Matrix();\n\n  auto display_list = GetSamplePicture();\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  cache.BeginFrame();\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, true, false);\n\n  // 1st access.\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n\n  cache.EndFrame();\n  ASSERT_EQ(cache.picture_metrics().total_count(), 0u);\n  ASSERT_EQ(cache.picture_metrics().total_bytes(), 0u);\n  cache.BeginFrame();\n\n  // 2nd access.\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n\n  cache.EndFrame();\n  ASSERT_EQ(cache.picture_metrics().total_count(), 0u);\n  ASSERT_EQ(cache.picture_metrics().total_bytes(), 0u);\n  cache.BeginFrame();\n\n  // Now Prepare should cache it.\n  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_TRUE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n\n  cache.EndFrame();\n  ASSERT_EQ(cache.picture_metrics().total_count(), 1u);\n  // 150w * 100h * 4bpp\n  ASSERT_EQ(cache.picture_metrics().total_bytes(), 60000u);\n}\n\nTEST(RasterCache, ThresholdIsRespectedForDisplayList) {\n  size_t threshold = 2;\n  clay::RasterCache cache(threshold);\n\n  skity::Matrix matrix = skity::Matrix();\n\n  auto display_list = GetSamplePicture();\n\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  cache.BeginFrame();\n\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, true, false);\n\n  // 1st access.\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n\n  cache.EndFrame();\n  cache.BeginFrame();\n\n  // 2nd access.\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n\n  cache.EndFrame();\n  cache.BeginFrame();\n\n  // Now Prepare should cache it.\n  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_TRUE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n}\n\nTEST(RasterCache, SetCheckboardCacheImages) {\n  size_t threshold = 1;\n  clay::RasterCache cache(threshold);\n\n  skity::Matrix matrix = skity::Matrix();\n  auto display_list = GetSamplePicture();\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  auto& paint_context = paint_context_holder.paint_context;\n  auto dummy_draw_function = [](SkCanvas* canvas) {};\n  bool did_draw_checkerboard = false;\n  auto draw_checkerboard = [&](SkCanvas* canvas, const skity::Rect&) {\n    did_draw_checkerboard = true;\n  };\n  RasterCache::Context r_context = {\n      // clang-format off\n      .gr_context         = paint_context.gr_context,\n      .dst_color_space    = paint_context.dst_color_space,\n      .matrix             = matrix,\n      .logical_rect       = clay::ConvertSkRectToSkityRect(display_list->cullRect()),\n      .flow_type          = \"RasterCacheFlow::DisplayList\",\n      // clang-format on\n  };\n\n  cache.SetCheckboardCacheImages(false);\n  cache.Rasterize(r_context, dummy_draw_function, draw_checkerboard);\n  ASSERT_FALSE(did_draw_checkerboard);\n\n  cache.SetCheckboardCacheImages(true);\n  cache.Rasterize(r_context, dummy_draw_function, draw_checkerboard);\n  ASSERT_TRUE(did_draw_checkerboard);\n}\n\nTEST(RasterCache, AccessThresholdOfZeroDisablesCachingForSkPicture) {\n  size_t threshold = 0;\n  clay::RasterCache cache(threshold);\n\n  skity::Matrix matrix = skity::Matrix();\n\n  auto display_list = GetSamplePicture();\n\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  cache.BeginFrame();\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, true, false);\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n}\n\nTEST(RasterCache, AccessThresholdOfZeroDisablesCachingForDisplayList) {\n  size_t threshold = 0;\n  clay::RasterCache cache(threshold);\n\n  skity::Matrix matrix = skity::Matrix();\n\n  auto display_list = GetSamplePicture();\n\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  cache.BeginFrame();\n\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, true, false);\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n}\n\nTEST(RasterCache, PictureCacheLimitPerFrameIsRespectedWhenZeroForSkPicture) {\n  size_t picture_cache_limit_per_frame = 0;\n  clay::RasterCache cache(3, picture_cache_limit_per_frame);\n\n  skity::Matrix matrix = skity::Matrix();\n\n  auto display_list = GetSamplePicture();\n\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  cache.BeginFrame();\n\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, true, false);\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n}\n\nTEST(RasterCache, PictureCacheLimitPerFrameIsRespectedWhenZeroForDisplayList) {\n  size_t picture_cache_limit_per_frame = 0;\n  clay::RasterCache cache(3, picture_cache_limit_per_frame);\n\n  skity::Matrix matrix = skity::Matrix();\n\n  auto display_list = GetSamplePicture();\n\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  cache.BeginFrame();\n\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, true, false);\n  // 1st access.\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n  // 2nd access.\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n  // the picture_cache_limit_per_frame = 0, so don't cache it\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n}\n\nTEST(RasterCache, EvictUnusedCacheEntries) {\n  size_t threshold = 1;\n  clay::RasterCache cache(threshold);\n\n  skity::Matrix matrix = skity::Matrix();\n\n  auto display_list_1 = GetSamplePicture();\n  auto display_list_2 = GetSamplePicture();\n\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  PictureRasterCacheItem display_list_item_1(display_list_1.get(),\n                                             skity::Vec2{0, 0}, true, false);\n  PictureRasterCacheItem display_list_item_2(display_list_2.get(),\n                                             skity::Vec2{0, 0}, true, false);\n\n  cache.BeginFrame();\n  RasterCacheItemPreroll(display_list_item_1, preroll_context, matrix);\n  RasterCacheItemPreroll(display_list_item_2, preroll_context, matrix);\n  cache.EvictUnusedCacheEntries();\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);\n  ASSERT_FALSE(\n      RasterCacheItemTryToRasterCache(display_list_item_1, paint_context));\n  ASSERT_FALSE(\n      RasterCacheItemTryToRasterCache(display_list_item_2, paint_context));\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);\n  ASSERT_FALSE(display_list_item_1.Draw(paint_context, &dummy_canvas, &paint));\n  ASSERT_FALSE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));\n  cache.EndFrame();\n\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);\n  ASSERT_EQ(cache.picture_metrics().total_count(), 0u);\n  ASSERT_EQ(cache.picture_metrics().total_bytes(), 0u);\n\n  cache.BeginFrame();\n  RasterCacheItemPreroll(display_list_item_1, preroll_context, matrix);\n  RasterCacheItemPreroll(display_list_item_2, preroll_context, matrix);\n  cache.EvictUnusedCacheEntries();\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);\n  ASSERT_TRUE(\n      RasterCacheItemTryToRasterCache(display_list_item_1, paint_context));\n  ASSERT_TRUE(\n      RasterCacheItemTryToRasterCache(display_list_item_2, paint_context));\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 120000u);\n  ASSERT_TRUE(display_list_item_1.Draw(paint_context, &dummy_canvas, &paint));\n  ASSERT_TRUE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));\n  cache.EndFrame();\n\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 120000u);\n  ASSERT_EQ(cache.picture_metrics().total_count(), 2u);\n  ASSERT_EQ(cache.picture_metrics().total_bytes(), 120000u);\n\n  cache.BeginFrame();\n  RasterCacheItemPreroll(display_list_item_1, preroll_context, matrix);\n  cache.EvictUnusedCacheEntries();\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 60000u);\n  ASSERT_TRUE(\n      RasterCacheItemTryToRasterCache(display_list_item_1, paint_context));\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 60000u);\n  ASSERT_TRUE(display_list_item_1.Draw(paint_context, &dummy_canvas, &paint));\n  cache.EndFrame();\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 60000u);\n  ASSERT_EQ(cache.picture_metrics().total_count(), 1u);\n  ASSERT_EQ(cache.picture_metrics().total_bytes(), 60000u);\n\n  cache.BeginFrame();\n  cache.EvictUnusedCacheEntries();\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);\n  cache.EndFrame();\n\n  ASSERT_EQ(cache.EstimatePictureCacheByteSize(), 0u);\n  ASSERT_EQ(cache.picture_metrics().total_count(), 0u);\n  ASSERT_EQ(cache.picture_metrics().total_bytes(), 0u);\n\n  cache.BeginFrame();\n  ASSERT_FALSE(\n      cache.Draw(display_list_item_1.GetId().value(), dummy_canvas, &paint));\n  ASSERT_FALSE(display_list_item_1.Draw(paint_context, &dummy_canvas, &paint));\n  ASSERT_FALSE(\n      cache.Draw(display_list_item_2.GetId().value(), dummy_canvas, &paint));\n  ASSERT_FALSE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));\n  cache.EndFrame();\n}\n\nTEST(RasterCache, ComputeDeviceRectBasedOnFractionalTranslation) {\n  skity::Rect logical_rect = skity::Rect::MakeLTRB(0, 0, 300.2, 300.3);\n  skity::Matrix ctm = skity::Matrix(2.0, 0, 0, 0, 2.0, 0, 0, 0, 1);\n  auto result = RasterCacheUtil::GetDeviceBounds(logical_rect, ctm);\n  ASSERT_EQ(result, skity::Rect::MakeLTRB(0.0, 0.0, 600.4, 600.6));\n}\n\n// Construct a cache result whose device target rectangle rounds out to be one\n// pixel wider than the cached image.  Verify that it can be drawn without\n// triggering any assertions.\nTEST(RasterCache, DeviceRectRoundOutForDisplayList) {\n  size_t threshold = 1;\n  clay::RasterCache cache(threshold);\n\n  SkRect logical_rect = SkRect::MakeLTRB(28, 0, 354.56731, 310.288);\n  SkPictureRecorder recorder;\n  SkCanvas* canvas_build = recorder.beginRecording(logical_rect);\n  SkPaint paint_build;\n  paint_build.setColor(SK_ColorRED);\n  canvas_build->drawRect(logical_rect, paint_build);\n  sk_sp<SkPicture> display_list = recorder.finishRecordingAsPicture();\n\n  skity::Matrix ctm = skity::Matrix(1.3312, 0, 233, 0, 1.3312, 206, 0, 0, 1);\n  SkPaint paint;\n\n  SkCanvas canvas(1000, 1000, nullptr);\n  canvas.setMatrix(clay::ConvertSkityMatrixToSkMatrix(ctm));\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, ctm);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  cache.BeginFrame();\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, true, false);\n\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, ctm));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &canvas, &paint));\n\n  cache.EndFrame();\n  cache.BeginFrame();\n\n  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, ctm));\n  ASSERT_TRUE(display_list_item.Draw(paint_context, &canvas, &paint));\n\n  canvas.translate(248, 0);\n  ASSERT_TRUE(cache.Draw(display_list_item.GetId().value(), canvas, &paint));\n  ASSERT_TRUE(display_list_item.Draw(paint_context, &canvas, &paint));\n}\n\nTEST(RasterCache, NestedOpCountMetricUsedForDisplayList) {\n  size_t threshold = 1;\n  clay::RasterCache cache(threshold);\n\n  skity::Matrix matrix = skity::Matrix();\n\n  auto display_list = GetSampleNestedPicture();\n  ASSERT_EQ(display_list->approximateOpCount(), 1);\n  ASSERT_EQ(display_list->approximateOpCount(true), 36);\n\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  cache.BeginFrame();\n\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, false, false);\n\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n\n  cache.EndFrame();\n  cache.BeginFrame();\n\n  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_TRUE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n}\n\nTEST(RasterCache, NaiveComplexityScoringDisplayList) {\n  PictureComplexityCalculator* calculator =\n      PictureNaiveComplexityCalculator::GetInstance();\n\n  size_t threshold = 1;\n  clay::RasterCache cache(threshold);\n\n  skity::Matrix matrix = skity::Matrix();\n\n  // Five raster ops will not be cached\n  auto display_list = GetSamplePicture(5);\n  ASSERT_EQ(display_list->approximateOpCount(), 5);\n  unsigned int complexity_score = calculator->Compute(display_list.get());\n\n  ASSERT_EQ(complexity_score, 5u);\n  ASSERT_FALSE(calculator->ShouldBeCached(complexity_score));\n\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  cache.BeginFrame();\n\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, false, false);\n\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n\n  cache.EndFrame();\n  cache.BeginFrame();\n\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n\n  // Six raster ops should be cached\n  display_list = GetSamplePicture(6);\n  ASSERT_EQ(display_list->approximateOpCount(), 6);\n  complexity_score = calculator->Compute(display_list.get());\n\n  ASSERT_EQ(complexity_score, 6u);\n  ASSERT_TRUE(calculator->ShouldBeCached(complexity_score));\n\n  PictureRasterCacheItem display_list_item_2 = PictureRasterCacheItem(\n      display_list.get(), skity::Vec2{0, 0}, false, false);\n  cache.BeginFrame();\n\n  ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item_2, preroll_context, paint_context, matrix));\n  ASSERT_FALSE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));\n\n  cache.EndFrame();\n  cache.BeginFrame();\n\n  ASSERT_TRUE(RasterCacheItemPrerollAndTryToRasterCache(\n      display_list_item_2, preroll_context, paint_context, matrix));\n  ASSERT_TRUE(display_list_item_2.Draw(paint_context, &dummy_canvas, &paint));\n}\n\nTEST(RasterCache, DisplayListWithSingularMatrixIsNotCached) {\n  size_t threshold = 2;\n  clay::RasterCache cache(threshold);\n\n  skity::Matrix matrices[] = {\n      skity::Matrix::Scale(0, 1),\n      skity::Matrix::Scale(1, 0),\n      skity::Matrix::Skew(1, 1),\n  };\n  int matrix_count = sizeof(matrices) / sizeof(matrices[0]);\n\n  auto display_list = GetSamplePicture();\n\n  SkCanvas dummy_canvas(1000, 1000);\n  SkPaint paint;\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, skity::Matrix());\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(\n      paint_state_stack, &cache, &raster_time, &ui_time);\n  auto& preroll_context = preroll_context_holder.preroll_context;\n  auto& paint_context = paint_context_holder.paint_context;\n\n  PictureRasterCacheItem display_list_item(display_list.get(),\n                                           skity::Vec2{0, 0}, true, false);\n\n  for (int i = 0; i < 10; i++) {\n    cache.BeginFrame();\n\n    for (int j = 0; j < matrix_count; j++) {\n      display_list_item.set_matrix(matrices[j]);\n      ASSERT_FALSE(RasterCacheItemPrerollAndTryToRasterCache(\n          display_list_item, preroll_context, paint_context, matrices[j]));\n    }\n\n    for (int j = 0; j < matrix_count; j++) {\n      dummy_canvas.setMatrix(clay::ConvertSkityMatrixToSkMatrix(matrices[j]));\n      ASSERT_FALSE(\n          display_list_item.Draw(paint_context, &dummy_canvas, &paint));\n    }\n\n    cache.EndFrame();\n  }\n}\n\nTEST(RasterCache, PrepareLayerTransform) {\n  SkRect child_bounds = SkRect::MakeLTRB(10, 10, 50, 50);\n  SkPath child_path = SkPath().addOval(child_bounds);\n  auto child_layer = MockLayer::Make(child_path);\n  auto blur_filter =\n      std::make_shared<DlBlurImageFilter>(5, 5, DlTileMode::kClamp);\n  auto blur_layer = std::make_shared<ImageFilterLayer>(blur_filter);\n  skity::Matrix matrix = skity::Matrix::Scale(2, 2);\n  auto transform_layer = std::make_shared<TransformLayer>(matrix);\n  skity::Matrix cache_matrix = skity::Matrix::Translate(-20, -20);\n  cache_matrix.PreConcat(matrix);\n  child_layer->set_expected_paint_matrix(cache_matrix);\n\n  blur_layer->Add(child_layer);\n  transform_layer->Add(blur_layer);\n\n  size_t threshold = 2;\n  MockRasterCache cache(threshold);\n  SkCanvas dummy_canvas(1000, 1000);\n\n  LayerStateStack preroll_state_stack;\n  preroll_state_stack.set_preroll_delegate(kGiantRect, matrix);\n  LayerStateStack paint_state_stack;\n  preroll_state_stack.set_delegate(&dummy_canvas);\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  std::vector<RasterCacheItem*> cache_items;\n\n  cache.BeginFrame();\n\n  auto preroll_holder = GetSamplePrerollContextHolder(\n      preroll_state_stack, &cache, &raster_time, &ui_time);\n  preroll_holder.preroll_context.raster_cached_entries = &cache_items;\n  transform_layer->Preroll(&preroll_holder.preroll_context);\n\n  auto paint_holder = GetSamplePaintContextHolder(paint_state_stack, &cache,\n                                                  &raster_time, &ui_time);\n\n  cache.EvictUnusedCacheEntries();\n  LayerTree::TryToRasterCache(\n      *preroll_holder.preroll_context.raster_cached_entries,\n      &paint_holder.paint_context);\n\n  // Condition tested inside MockLayer::Paint against expected paint matrix.\n}\n\nTEST(RasterCache, RasterCacheKeyHashFunction) {\n  RasterCacheKey::Map<int> map;\n  auto hash_function = map.hash_function();\n  skity::Matrix matrix = skity::Matrix();\n  uint64_t id = 5;\n  RasterCacheKey layer_key(id, RasterCacheKeyType::kLayer, matrix);\n  RasterCacheKey display_list_key(id, RasterCacheKeyType::kPicture, matrix);\n  RasterCacheKey layer_children_key(id, RasterCacheKeyType::kLayerChildren,\n                                    matrix);\n\n  auto layer_cache_key_id = RasterCacheKeyID(id, RasterCacheKeyType::kLayer);\n  auto layer_hash_code = hash_function(layer_key);\n  ASSERT_EQ(layer_hash_code, layer_cache_key_id.GetHash());\n\n  auto display_list_cache_key_id =\n      RasterCacheKeyID(id, RasterCacheKeyType::kPicture);\n  auto display_list_hash_code = hash_function(display_list_key);\n  ASSERT_EQ(display_list_hash_code, display_list_cache_key_id.GetHash());\n\n  auto layer_children_cache_key_id =\n      RasterCacheKeyID(id, RasterCacheKeyType::kLayerChildren);\n  auto layer_children_hash_code = hash_function(layer_children_key);\n  ASSERT_EQ(layer_children_hash_code, layer_children_cache_key_id.GetHash());\n}\n\nTEST(RasterCache, RasterCacheKeySameID) {\n  RasterCacheKey::Map<int> map;\n  skity::Matrix matrix = skity::Matrix();\n  uint64_t id = 5;\n  RasterCacheKey layer_key(id, RasterCacheKeyType::kLayer, matrix);\n  RasterCacheKey display_list_key(id, RasterCacheKeyType::kPicture, matrix);\n  RasterCacheKey layer_children_key(id, RasterCacheKeyType::kLayerChildren,\n                                    matrix);\n  map[layer_key] = 100;\n  map[display_list_key] = 300;\n  map[layer_children_key] = 400;\n\n  ASSERT_EQ(map[layer_key], 100);\n  ASSERT_EQ(map[display_list_key], 300);\n  ASSERT_EQ(map[layer_children_key], 400);\n}\n\nTEST(RasterCache, RasterCacheKeySameType) {\n  RasterCacheKey::Map<int> map;\n  skity::Matrix matrix = skity::Matrix();\n\n  RasterCacheKeyType type = RasterCacheKeyType::kLayer;\n  RasterCacheKey layer_first_key(5, type, matrix);\n  RasterCacheKey layer_second_key(10, type, matrix);\n  RasterCacheKey layer_third_key(15, type, matrix);\n  map[layer_first_key] = 50;\n  map[layer_second_key] = 100;\n  map[layer_third_key] = 150;\n  ASSERT_EQ(map[layer_first_key], 50);\n  ASSERT_EQ(map[layer_second_key], 100);\n  ASSERT_EQ(map[layer_third_key], 150);\n\n  type = RasterCacheKeyType::kPicture;\n  RasterCacheKey picture_first_key(20, type, matrix);\n  RasterCacheKey picture_second_key(25, type, matrix);\n  RasterCacheKey picture_third_key(30, type, matrix);\n  map[picture_first_key] = 200;\n  map[picture_second_key] = 250;\n  map[picture_third_key] = 300;\n  ASSERT_EQ(map[picture_first_key], 200);\n  ASSERT_EQ(map[picture_second_key], 250);\n  ASSERT_EQ(map[picture_third_key], 300);\n\n  type = RasterCacheKeyType::kPicture;\n  RasterCacheKey display_list_first_key(35, type, matrix);\n  RasterCacheKey display_list_second_key(40, type, matrix);\n  RasterCacheKey display_list_third_key(45, type, matrix);\n  map[display_list_first_key] = 350;\n  map[display_list_second_key] = 400;\n  map[display_list_third_key] = 450;\n  ASSERT_EQ(map[display_list_first_key], 350);\n  ASSERT_EQ(map[display_list_second_key], 400);\n  ASSERT_EQ(map[display_list_third_key], 450);\n\n  type = RasterCacheKeyType::kLayerChildren;\n  RasterCacheKeyID foo = RasterCacheKeyID(10, RasterCacheKeyType::kLayer);\n  RasterCacheKeyID bar = RasterCacheKeyID(20, RasterCacheKeyType::kLayer);\n  RasterCacheKeyID baz = RasterCacheKeyID(30, RasterCacheKeyType::kLayer);\n  RasterCacheKey layer_children_first_key(\n      RasterCacheKeyID({foo, bar, baz}, type), matrix);\n  RasterCacheKey layer_children_second_key(\n      RasterCacheKeyID({foo, baz, bar}, type), matrix);\n  RasterCacheKey layer_children_third_key(\n      RasterCacheKeyID({baz, bar, foo}, type), matrix);\n  map[layer_children_first_key] = 100;\n  map[layer_children_second_key] = 200;\n  map[layer_children_third_key] = 300;\n  ASSERT_EQ(map[layer_children_first_key], 100);\n  ASSERT_EQ(map[layer_children_second_key], 200);\n  ASSERT_EQ(map[layer_children_third_key], 300);\n}\n\nTEST(RasterCache, RasterCacheKeyIDEqual) {\n  RasterCacheKeyID first = RasterCacheKeyID(1, RasterCacheKeyType::kLayer);\n  RasterCacheKeyID second = RasterCacheKeyID(2, RasterCacheKeyType::kLayer);\n  RasterCacheKeyID third =\n      RasterCacheKeyID(1, RasterCacheKeyType::kLayerChildren);\n\n  ASSERT_NE(first, second);\n  ASSERT_NE(first, third);\n  ASSERT_NE(second, third);\n\n  RasterCacheKeyID fourth =\n      RasterCacheKeyID({first, second}, RasterCacheKeyType::kLayer);\n  RasterCacheKeyID fifth =\n      RasterCacheKeyID({first, second}, RasterCacheKeyType::kLayerChildren);\n  RasterCacheKeyID sixth =\n      RasterCacheKeyID({second, first}, RasterCacheKeyType::kLayerChildren);\n  ASSERT_NE(fourth, fifth);\n  ASSERT_NE(fifth, sixth);\n}\n\nTEST(RasterCache, RasterCacheKeyIDHashCode) {\n  uint64_t foo = 1;\n  uint64_t bar = 2;\n  RasterCacheKeyID first = RasterCacheKeyID(foo, RasterCacheKeyType::kLayer);\n  RasterCacheKeyID second = RasterCacheKeyID(bar, RasterCacheKeyType::kLayer);\n  std::size_t first_hash = first.GetHash();\n  std::size_t second_hash = second.GetHash();\n\n  ASSERT_EQ(first_hash, fml::HashCombine(foo, RasterCacheKeyType::kLayer));\n  ASSERT_EQ(second_hash, fml::HashCombine(bar, RasterCacheKeyType::kLayer));\n\n  RasterCacheKeyID third =\n      RasterCacheKeyID({first, second}, RasterCacheKeyType::kLayerChildren);\n  RasterCacheKeyID fourth =\n      RasterCacheKeyID({second, first}, RasterCacheKeyType::kLayerChildren);\n  std::size_t third_hash = third.GetHash();\n  std::size_t fourth_hash = fourth.GetHash();\n\n  ASSERT_EQ(third_hash, fml::HashCombine(RasterCacheKeyID::kDefaultUniqueID,\n                                         RasterCacheKeyType::kLayerChildren,\n                                         first.GetHash(), second.GetHash()));\n  ASSERT_EQ(fourth_hash, fml::HashCombine(RasterCacheKeyID::kDefaultUniqueID,\n                                          RasterCacheKeyType::kLayerChildren,\n                                          second.GetHash(), first.GetHash()));\n\n  // Verify that the cached hash code is correct.\n  ASSERT_EQ(first_hash, first.GetHash());\n  ASSERT_EQ(second_hash, second.GetHash());\n  ASSERT_EQ(third_hash, third.GetHash());\n  ASSERT_EQ(fourth_hash, fourth.GetHash());\n}\n\nusing RasterCacheTest = GPUObjectLayerTest;\n\nTEST_F(RasterCacheTest, RasterCacheKeyIDLayerChildrenIds) {\n  auto layer = std::make_shared<ContainerLayer>();\n\n  const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f));\n  auto mock_layer = std::make_shared<MockLayer>(child_path);\n  layer->Add(mock_layer);\n\n  auto display_list = GetSamplePicture();\n  clay::DynamicOps ops;\n  auto picture_skia =\n      fml::MakeRefCounted<clay::PictureSkia>(display_list, std::move(ops));\n  auto display_list_layer = std::make_shared<PictureLayer>(\n      skity::Vec2(0.0f, 0.0f), GPUObject(picture_skia, unref_queue()), false,\n      false);\n  auto id = display_list->uniqueID();\n  layer->Add(display_list_layer);\n\n  auto ids = RasterCacheKeyID::LayerChildrenIds(layer.get()).value();\n  std::vector<RasterCacheKeyID> expected_ids;\n  expected_ids.emplace_back(\n      RasterCacheKeyID(mock_layer->unique_id(), RasterCacheKeyType::kLayer));\n  expected_ids.emplace_back(RasterCacheKeyID(id, RasterCacheKeyType::kPicture));\n  ASSERT_EQ(expected_ids[0], mock_layer->caching_key_id());\n  ASSERT_EQ(expected_ids[1], display_list_layer->caching_key_id());\n  ASSERT_EQ(ids, expected_ids);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/raster_cache_util.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/raster_cache_util.h\"\n\nnamespace clay {}\n"
  },
  {
    "path": "clay/flow/raster_cache_util.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_RASTER_CACHE_UTIL_H_\n#define CLAY_FLOW_RASTER_CACHE_UTIL_H_\n\n#include <cmath>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/matrix.hpp\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nstruct RasterCacheUtil {\n  // The default max number of picture and display list raster caches to be\n  // generated per frame. Generating too many caches in one frame may cause jank\n  // on that frame. This limit allows us to throttle the cache and distribute\n  // the work across multiple frames.\n  static constexpr int kDefaultPictureAndDispLayListCacheLimitPerFrame = 4;\n\n  // The ImageFilterLayer might cache the filtered output of this layer\n  // if the layer remains stable (if it is not animating for instance).\n  // If the ImageFilterLayer is not the same between rendered frames,\n  // though, it will cache its children instead and filter their cached\n  // output on the fly.\n  // Caching just the children saves the time to render them and also\n  // avoids a rendering surface switch to draw them.\n  // Caching the layer itself avoids all of that and additionally avoids\n  // the cost of applying the filter, but can be worse than caching the\n  // children if the filter itself is not stable from frame to frame.\n  // This constant controls how many times we will Preroll and Paint this\n  // same ImageFilterLayer before we consider the layer and filter to be\n  // stable enough to switch from caching the children to caching the\n  // filtered output of this layer.\n  static constexpr int kMinimumRendersBeforeCachingFilterLayer = 3;\n\n  static bool CanRasterizeRect(const skity::Rect& cull_rect) {\n    if (cull_rect.IsEmpty()) {\n      // No point in ever rasterizing an empty display list.\n      return false;\n    }\n\n    if (!cull_rect.IsFinite()) {\n      // Cannot attempt to rasterize into an infinitely large surface.\n      FML_DLOG(INFO) << \"Attempted to raster cache non-finite display list\";\n      return false;\n    }\n\n    return true;\n  }\n\n  static skity::Rect GetDeviceBounds(const skity::Rect& rect,\n                                     const skity::Matrix& ctm) {\n    skity::Rect device_rect;\n    ctm.MapRect(&device_rect, rect);\n    return device_rect;\n  }\n\n  static skity::Rect GetRoundedOutDeviceBounds(const skity::Rect& rect,\n                                               const skity::Matrix& ctm) {\n    skity::Rect device_rect;\n    ctm.MapRect(&device_rect, rect);\n    device_rect.RoundOut();\n    return device_rect;\n  }\n\n  /**\n   * @brief Snap the translation components of the matrix to integers.\n   *\n   * The snapping will only happen if the matrix only has scale and translation\n   * transformations. This is used, along with GetRoundedOutDeviceBounds, to\n   * ensure that the textures drawn by the raster cache are exactly aligned to\n   * physical pixels. Any layers that participate in raster caching must align\n   * themselves to physical pixels even when not cached to prevent a change in\n   * apparent location if caching is later applied.\n   *\n   * @param ctm the current transformation matrix.\n   * @return skity::Matrix the snapped transformation matrix.\n   */\n  static skity::Matrix GetIntegralTransCTM(const skity::Matrix& ctm) {\n    // Avoid integral snapping if the matrix has complex transformation to avoid\n    // the artifact observed in https://github.com/flutter/flutter/issues/41654.\n    if (ctm.Get(0, 1) != 0 || ctm.Get(0, 2) != 0) {\n      // X multiplied by either Y or Z\n      return ctm;\n    }\n    if (ctm.Get(1, 0) != 0 || ctm.Get(1, 2) != 0) {\n      // Y multiplied by either X or Z\n      return ctm;\n    }\n    // We do not need to worry about the Z row unless the W row\n    // has perspective entries...\n    if (ctm.Get(3, 0) != 0 || ctm.Get(3, 1) != 0 || ctm.Get(3, 2) != 0 ||\n        ctm.Get(3, 3) != 1) {\n      // W not identity row, therefore perspective is applied\n      return ctm;\n    }\n\n    skity::Matrix result = ctm;\n    result.Set(0, 3, roundf(ctm.Get(0, 3)));\n    result.Set(1, 3, roundf(ctm.Get(1, 3)));\n    // No need to worry about Z translation because it has no effect\n    // without perspective entries...\n    return result;\n  }\n\n  /**\n   * Calculates the uniform scale factor from a similarity transformation\n   * matrix.\n   *\n   * @param similarity_ctm A transformation matrix that must be a similarity\n   * transform\n   * @return The absolute scale factor of the transformation\n   */\n  static float GetScaleFactor(const skity::Matrix& similarity_ctm) {\n    FML_DCHECK(similarity_ctm.IsSimilarity());\n\n    // For simple cases where the matrix only contains scale and translate\n    if (similarity_ctm.OnlyScaleAndTranslate()) {\n      // The scale factor is directly available in the ScaleX component\n      return similarity_ctm.GetScaleX();\n    } else {\n      // For matrices that include rotation:\n      // In a similarity transform with rotation, the scale factor is the\n      // length of the transformed unit vector, which can be calculated\n      // using the Pythagorean theorem on the scale and skew components\n      float scale_x = similarity_ctm.GetScaleX();\n      float skew_y = similarity_ctm.GetSkewY();\n      return std::sqrt(scale_x * scale_x + skew_y * skew_y);\n    }\n  }\n\n  /**\n   * @brief Check if the matrix is a similarity matrix and the scale factor is\n   * positive.\n   *\n   * The matrix is a similarity matrix if it has only translation, uniform scale\n   * and rotation transformations.\n   * This method is important for raster caching because similarity transforms\n   * are \"friendly\" for cached images - they maintain the visual quality of the\n   * cached content since they don't distort the shape. This helps ensure that\n   * using cached content will still look good when transformed.\n   *\n   * @param ctm the current transformation matrix.\n   * @return true if the matrix is a similarity matrix and the scale factor is\n   * positive.\n   */\n  static bool IsMatrixSimilarity(const skity::Matrix& ctm) {\n    return ctm.IsSimilarity() && GetScaleFactor(ctm) > 0.f;\n  }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_RASTER_CACHE_UTIL_H_\n"
  },
  {
    "path": "clay/flow/rtree.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/rtree.h\"\n\n#include <list>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n// NOLINTNEXTLINE\nRTree::RTree() : bbh_{SkRTreeFactory{}()}, all_ops_count_(0) {}\n\nvoid RTree::insert(const SkRect boundsArray[],\n                   const SkBBoxHierarchy::Metadata metadata[], int N) {\n  FML_DCHECK(0 == all_ops_count_);\n  bbh_->insert(boundsArray, metadata, N);\n  for (int i = 0; i < N; i++) {\n    if (metadata != nullptr && metadata[i].isDraw) {\n      draw_op_[i] = boundsArray[i];\n    }\n  }\n  all_ops_count_ = N;\n}\n\nvoid RTree::insert(const SkRect boundsArray[], int N) {\n  insert(boundsArray, nullptr, N);\n}\n\nvoid RTree::search(const SkRect& query, std::vector<int>* results) const {\n  bbh_->search(query, results);\n}\n\nstd::list<SkRect> RTree::searchNonOverlappingDrawnRects(\n    const SkRect& query) const {\n  // Get the indexes for the operations that intersect with the query rect.\n  std::vector<int> intermediary_results;\n  search(query, &intermediary_results);\n\n  std::list<SkRect> final_results;\n  for (int index : intermediary_results) {\n    auto draw_op = draw_op_.find(index);\n    // Ignore records that don't draw anything.\n    if (draw_op == draw_op_.end()) {\n      continue;\n    }\n    auto current_record_rect = draw_op->second;\n    auto replaced_existing_rect = false;\n    // // If the current record rect intersects with any of the rects in the\n    // // result list, then join them, and update the rect in final_results.\n    std::list<SkRect>::iterator curr_rect_itr = final_results.begin();\n    std::list<SkRect>::iterator first_intersecting_rect_itr;\n    while (!replaced_existing_rect && curr_rect_itr != final_results.end()) {\n      if (SkRect::Intersects(*curr_rect_itr, current_record_rect)) {\n        replaced_existing_rect = true;\n        first_intersecting_rect_itr = curr_rect_itr;\n        curr_rect_itr->join(current_record_rect);\n      }\n      curr_rect_itr++;\n    }\n    // It's possible that the result contains duplicated rects at this point.\n    // For example, consider a result list that contains rects A, B. If a\n    // new rect C is a superset of A and B, then A and B are the same set after\n    // the merge. As a result, find such cases and remove them from the result\n    // list.\n    while (replaced_existing_rect && curr_rect_itr != final_results.end()) {\n      if (SkRect::Intersects(*curr_rect_itr, *first_intersecting_rect_itr)) {\n        first_intersecting_rect_itr->join(*curr_rect_itr);\n        curr_rect_itr = final_results.erase(curr_rect_itr);\n      } else {\n        curr_rect_itr++;\n      }\n    }\n    if (!replaced_existing_rect) {\n      final_results.push_back(current_record_rect);\n    }\n  }\n  return final_results;\n}\n\nsize_t RTree::bytesUsed() const { return bbh_->bytesUsed(); }\n\nRTreeFactory::RTreeFactory() { r_tree_ = sk_make_sp<RTree>(); }\n\nsk_sp<RTree> RTreeFactory::getInstance() { return r_tree_; }\n\nsk_sp<SkBBoxHierarchy> RTreeFactory::operator()() const { return r_tree_; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/rtree.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_RTREE_H_\n#define CLAY_FLOW_RTREE_H_\n\n#include <list>\n#include <map>\n#include <vector>\n\n#include \"third_party/skia/include/core/SkBBHFactory.h\"\n\nnamespace clay {\n/**\n * An R-Tree implementation that forwards calls to an SkRTree.\n *\n * This implementation provides a searchNonOverlappingDrawnRects method,\n * which can be used to query the rects for the operations recorded in the tree.\n */\nclass RTree : public SkBBoxHierarchy {\n public:\n  RTree();\n\n  void insert(const SkRect[], const SkBBoxHierarchy::Metadata[],\n              int N) override;\n  void insert(const SkRect[], int N) override;\n  void search(const SkRect& query, std::vector<int>* results) const override;\n  size_t bytesUsed() const override;\n\n  // Finds the rects in the tree that represent drawing operations and intersect\n  // with the query rect.\n  //\n  // When two rects intersect with each other, they are joined into a single\n  // rect which also intersects with the query rect. In other words, the bounds\n  // of each rect in the result list are mutually exclusive.\n  std::list<SkRect> searchNonOverlappingDrawnRects(const SkRect& query) const;\n\n  // Insertion count (not overall node count, which may be greater).\n  int getCount() const { return all_ops_count_; }\n\n private:\n  // A map containing the draw operation rects keyed off the operation index\n  // in the insert call.\n  std::map<int, SkRect> draw_op_;\n  sk_sp<SkBBoxHierarchy> bbh_;\n  int all_ops_count_;\n};\n\nclass RTreeFactory : public SkBBHFactory {\n public:\n  RTreeFactory();\n\n  // Gets the instance to the R-tree.\n  sk_sp<RTree> getInstance();\n  sk_sp<SkBBoxHierarchy> operator()() const override;\n\n private:\n  sk_sp<RTree> r_tree_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_RTREE_H_\n"
  },
  {
    "path": "clay/flow/rtree_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/rtree.h\"\n#include \"clay/testing/testing.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(RTree, searchNonOverlappingDrawnRectsNoIntersection) {\n  auto rtree_factory = RTreeFactory();\n  auto recorder = std::make_unique<SkPictureRecorder>();\n  auto recording_canvas =\n      recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory);\n\n  auto rect_paint = SkPaint();\n  rect_paint.setColor(SkColors::kCyan);\n  rect_paint.setStyle(SkPaint::Style::kFill_Style);\n\n  // If no rect is intersected with the query rect, then the result list is\n  // empty.\n  recording_canvas->drawRect(SkRect::MakeLTRB(20, 20, 40, 40), rect_paint);\n  recorder->finishRecordingAsPicture();\n\n  auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects(\n      SkRect::MakeLTRB(40, 40, 80, 80));\n  ASSERT_TRUE(hits.empty());\n}\n\nTEST(RTree, searchNonOverlappingDrawnRectsSingleRectIntersection) {\n  auto rtree_factory = RTreeFactory();\n  auto recorder = std::make_unique<SkPictureRecorder>();\n  auto recording_canvas =\n      recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory);\n\n  auto rect_paint = SkPaint();\n  rect_paint.setColor(SkColors::kCyan);\n  rect_paint.setStyle(SkPaint::Style::kFill_Style);\n\n  // Given a single rect A that intersects with the query rect,\n  // the result list contains this rect.\n  recording_canvas->drawRect(SkRect::MakeLTRB(120, 120, 160, 160), rect_paint);\n\n  recorder->finishRecordingAsPicture();\n\n  auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects(\n      SkRect::MakeLTRB(140, 140, 150, 150));\n  ASSERT_EQ(1UL, hits.size());\n  ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(120, 120, 160, 160));\n}\n\nTEST(RTree, searchNonOverlappingDrawnRectsIgnoresNonDrawingRecords) {\n  auto rtree_factory = RTreeFactory();\n  auto recorder = std::make_unique<SkPictureRecorder>();\n  auto recording_canvas =\n      recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory);\n\n  auto rect_paint = SkPaint();\n  rect_paint.setColor(SkColors::kCyan);\n  rect_paint.setStyle(SkPaint::Style::kFill_Style);\n\n  // Creates two non drawing records.\n  recording_canvas->translate(100, 100);\n  // The result list should only contain the clipping rect.\n  recording_canvas->clipRect(SkRect::MakeLTRB(40, 40, 50, 50),\n                             SkClipOp::kIntersect);\n  recording_canvas->drawRect(SkRect::MakeLTRB(20, 20, 80, 80), rect_paint);\n\n  recorder->finishRecordingAsPicture();\n\n  // The rtree has a translate, a clip and a rect record.\n  ASSERT_EQ(3, rtree_factory.getInstance()->getCount());\n\n  auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects(\n      SkRect::MakeLTRB(0, 0, 1000, 1000));\n  ASSERT_EQ(1UL, hits.size());\n  ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(120, 120, 180, 180));\n}\n\nTEST(RTree, searchNonOverlappingDrawnRectsMultipleRectIntersection) {\n  auto rtree_factory = RTreeFactory();\n  auto recorder = std::make_unique<SkPictureRecorder>();\n  auto recording_canvas =\n      recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory);\n\n  auto rect_paint = SkPaint();\n  rect_paint.setColor(SkColors::kCyan);\n  rect_paint.setStyle(SkPaint::Style::kFill_Style);\n\n  // Given the A, B that intersect with the query rect,\n  // there should be A and B in the result list since\n  // they don't intersect with each other.\n  //\n  //  +-----+   +-----+\n  //  |  A  |   |  B  |\n  //  +-----+   +-----+\n  // A\n  recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 200, 200), rect_paint);\n  // B\n  recording_canvas->drawRect(SkRect::MakeLTRB(300, 100, 400, 200), rect_paint);\n\n  recorder->finishRecordingAsPicture();\n\n  auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects(\n      SkRect::MakeLTRB(0, 0, 1000, 1050));\n  ASSERT_EQ(2UL, hits.size());\n  ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(100, 100, 200, 200));\n  ASSERT_EQ(*std::next(hits.begin(), 1), SkRect::MakeLTRB(300, 100, 400, 200));\n}\n\nTEST(RTree, searchNonOverlappingDrawnRectsJoinRectsWhenIntersectedCase1) {\n  auto rtree_factory = RTreeFactory();\n  auto recorder = std::make_unique<SkPictureRecorder>();\n  auto recording_canvas =\n      recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory);\n\n  auto rect_paint = SkPaint();\n  rect_paint.setColor(SkColors::kCyan);\n  rect_paint.setStyle(SkPaint::Style::kFill_Style);\n\n  // Given the A, and B rects, which intersect with the query rect,\n  // the result list contains the rect resulting from the union of A and B.\n  //\n  // +-----+\n  // |  A  |\n  // |   +-----+\n  // |   |  C  |\n  // |   +-----+\n  // |     |\n  // +-----+\n\n  // A\n  recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 150, 150), rect_paint);\n  // B\n  recording_canvas->drawRect(SkRect::MakeLTRB(125, 125, 175, 175), rect_paint);\n\n  recorder->finishRecordingAsPicture();\n\n  auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects(\n      SkRect::MakeXYWH(120, 120, 126, 126));\n  ASSERT_EQ(1UL, hits.size());\n  ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(100, 100, 175, 175));\n}\n\nTEST(RTree, searchNonOverlappingDrawnRectsJoinRectsWhenIntersectedCase2) {\n  auto rtree_factory = RTreeFactory();\n  auto recorder = std::make_unique<SkPictureRecorder>();\n  auto recording_canvas =\n      recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory);\n\n  auto rect_paint = SkPaint();\n  rect_paint.setColor(SkColors::kCyan);\n  rect_paint.setStyle(SkPaint::Style::kFill_Style);\n\n  // Given the A, B, and C rects that intersect with the query rect,\n  // there should be only C in the result list,\n  // since A and B are contained in C.\n  //\n  // +---------------------+\n  // | C                   |\n  // |  +-----+   +-----+  |\n  // |  |  A  |   |  B  |  |\n  // |  +-----+   +-----+  |\n  // +---------------------+\n  //              +-----+\n  //              |  D  |\n  //              +-----+\n\n  // A\n  recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 200, 200), rect_paint);\n  // B\n  recording_canvas->drawRect(SkRect::MakeLTRB(300, 100, 400, 200), rect_paint);\n  // C\n  recording_canvas->drawRect(SkRect::MakeLTRB(50, 50, 500, 250), rect_paint);\n  // D\n  recording_canvas->drawRect(SkRect::MakeLTRB(280, 100, 280, 320), rect_paint);\n\n  recorder->finishRecordingAsPicture();\n\n  auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects(\n      SkRect::MakeLTRB(30, 30, 550, 270));\n  ASSERT_EQ(1UL, hits.size());\n  ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(50, 50, 500, 250));\n}\n\nTEST(RTree, searchNonOverlappingDrawnRectsJoinRectsWhenIntersectedCase3) {\n  auto rtree_factory = RTreeFactory();\n  auto recorder = std::make_unique<SkPictureRecorder>();\n  auto recording_canvas =\n      recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory);\n\n  auto rect_paint = SkPaint();\n  rect_paint.setColor(SkColors::kCyan);\n  rect_paint.setStyle(SkPaint::Style::kFill_Style);\n\n  // Given the A, B, C and D rects that intersect with the query rect,\n  // the result list contains a single rect, which is the union of\n  // these four rects.\n  //\n  // +------------------------------+\n  // | D                            |\n  // |  +-----+   +-----+   +-----+ |\n  // |  |  A  |   |  B  |   |  C  | |\n  // |  +-----+   +-----+   |     | |\n  // +----------------------|     |-+\n  //                        +-----+\n  //              +-----+\n  //              |  E  |\n  //              +-----+\n\n  // A\n  recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 200, 200), rect_paint);\n  // B\n  recording_canvas->drawRect(SkRect::MakeLTRB(300, 100, 400, 200), rect_paint);\n  // C\n  recording_canvas->drawRect(SkRect::MakeLTRB(500, 100, 600, 300), rect_paint);\n  // D\n  recording_canvas->drawRect(SkRect::MakeLTRB(50, 50, 620, 250), rect_paint);\n  // E\n  recording_canvas->drawRect(SkRect::MakeLTRB(280, 100, 280, 320), rect_paint);\n\n  recorder->finishRecordingAsPicture();\n\n  auto hits = rtree_factory.getInstance()->searchNonOverlappingDrawnRects(\n      SkRect::MakeLTRB(30, 30, 550, 270));\n  ASSERT_EQ(1UL, hits.size());\n  ASSERT_EQ(*hits.begin(), SkRect::MakeLTRB(50, 50, 620, 300));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/services/animation_event_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_SERVICES_ANIMATION_EVENT_SERVICE_H_\n#define CLAY_FLOW_SERVICES_ANIMATION_EVENT_SERVICE_H_\n\n#include \"clay/common/element_id.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/gfx/animation/animation_data.h\"\n\nnamespace clay {\n\nclass AnimationEventService\n    : public clay::Service<AnimationEventService, clay::Owner::kUI,\n                           clay::ServiceFlags::kManualRegister |\n                               clay::ServiceFlags::kMultiThread> {\n public:\n  virtual void OnAnimationEvent(\n      const clay::ElementId& element_id,\n      const clay::AnimationParams& animation_params) = 0;\n  virtual void OnTransitionEvent(const clay::ElementId& element_id,\n                                 const clay::AnimationParams& animation_params,\n                                 ClayAnimationPropertyType property_type) = 0;\n\n  virtual void OnScrolled(const clay::ElementId& element_id, int32_t session_id,\n                          float scroll_offset, bool ignore_ui_repaint) = 0;\n\n  virtual void OnScrollEnd(const clay::ElementId& element_id,\n                           int32_t session_id, float scroll_offset,\n                           float velocity) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_SERVICES_ANIMATION_EVENT_SERVICE_H_\n"
  },
  {
    "path": "clay/flow/stopwatch.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/stopwatch.h\"\n\n#include <algorithm>\n#include <limits>\n\n#ifndef ENABLE_SKITY\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#endif\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n\nstatic const size_t kMaxSamples = 120;\nstatic const size_t kMaxFrameMarkers = 8;\n\nStopwatch::Stopwatch(const RefreshRateUpdater& updater)\n    : refresh_rate_updater_(updater),\n      start_(fml::TimePoint::Now()),\n      current_sample_(0) {\n  const fml::TimeDelta delta = fml::TimeDelta::Zero();\n  laps_.resize(kMaxSamples, delta);\n  cache_dirty_ = true;\n  prev_drawn_sample_index_ = 0;\n}\n\nStopwatch::~Stopwatch() = default;\n\nFixedRefreshRateStopwatch::FixedRefreshRateStopwatch(\n    fml::Milliseconds frame_budget)\n    : Stopwatch(fixed_delegate_), fixed_delegate_(frame_budget) {}\n\nFixedRefreshRateUpdater::FixedRefreshRateUpdater(\n    fml::Milliseconds fixed_frame_budget)\n    : fixed_frame_budget_(fixed_frame_budget) {}\n\nvoid Stopwatch::Start(fml::TimePoint time) {\n  start_ = time;\n  current_sample_ = (current_sample_ + 1) % kMaxSamples;\n}\n\nvoid Stopwatch::Stop(fml::TimePoint time) {\n  laps_[current_sample_] = time - start_;\n}\n\nvoid Stopwatch::SetLapTime(const fml::TimeDelta& delta) {\n  current_sample_ = (current_sample_ + 1) % kMaxSamples;\n  laps_[current_sample_] = delta;\n}\n\nconst fml::TimeDelta& Stopwatch::LastLap() const {\n  return laps_[(current_sample_ - 1) % kMaxSamples];\n}\n\nbool Stopwatch::CheckIfLastSampleInCycle() const {\n  return (current_sample_ + 1) % kMaxSamples == 0;\n}\n\ndouble Stopwatch::UnitFrameInterval(double raster_time_ms) const {\n  return raster_time_ms / GetFrameBudget().count();\n}\n\ndouble Stopwatch::UnitHeight(double raster_time_ms,\n                             double max_unit_interval) const {\n  double unit_height = UnitFrameInterval(raster_time_ms) / max_unit_interval;\n  if (unit_height > 1.0) {\n    unit_height = 1.0;\n  }\n  return unit_height;\n}\n\nfml::TimeDelta Stopwatch::MaxDelta() const {\n  fml::TimeDelta max_delta;\n  for (size_t i = 0; i < kMaxSamples; i++) {\n    if (laps_[i] > max_delta) {\n      max_delta = laps_[i];\n    }\n  }\n  return max_delta;\n}\n\nfml::TimeDelta Stopwatch::AverageDelta() const {\n  fml::TimeDelta sum;  // default to 0\n  for (size_t i = 0; i < kMaxSamples; i++) {\n    sum = sum + laps_[i];\n  }\n  return sum / kMaxSamples;\n}\n\n// Initialize the SkSurface for drawing into. Draws the base background and any\n// timing data from before the initial Visualize() call.\n#ifndef ENABLE_SKITY\nvoid Stopwatch::InitVisualizeSurface(const skity::Rect& skity_rect) const {\n  // Mark as dirty if the size has changed.\n  SkRect rect = clay::ConvertSkityRectToSkRect(skity_rect);\n  if (visualize_cache_surface_) {\n    if (rect.width() != visualize_cache_surface_->width() ||\n        rect.height() != visualize_cache_surface_->height()) {\n      cache_dirty_ = true;\n    };\n  }\n\n  if (!cache_dirty_) {\n    return;\n  }\n  cache_dirty_ = false;\n\n  // TODO(garyq): Use a GPU surface instead of a CPU surface.\n  visualize_cache_surface_ =\n      SkSurface::MakeRasterN32Premul(rect.width(), rect.height());\n\n  SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas();\n\n  // Establish the graph position.\n  const SkScalar x = 0;\n  const SkScalar y = 0;\n  const SkScalar width = rect.width();\n  const SkScalar height = rect.height();\n\n  SkPaint paint;\n  paint.setColor(0x99FFFFFF);\n  cache_canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint);\n\n  // Scale the graph to show frame times up to those that are 3 times the frame\n  // time.\n  const double one_frame_ms = GetFrameBudget().count();\n  const double max_interval = one_frame_ms * 3.0;\n  const double max_unit_interval = UnitFrameInterval(max_interval);\n\n  // Draw the old data to initially populate the graph.\n  // Prepare a path for the data. We start at the height of the last point, so\n  // it looks like we wrap around\n  SkPath path;\n  path.setIsVolatile(true);\n  path.moveTo(x, height);\n  path.lineTo(x, y + height * (1.0 - UnitHeight(laps_[0].ToMillisecondsF(),\n                                                max_unit_interval)));\n  double unit_x;\n  double unit_next_x = 0.0;\n  for (size_t i = 0; i < kMaxSamples; i += 1) {\n    unit_x = unit_next_x;\n    unit_next_x = (static_cast<double>(i + 1) / kMaxSamples);\n    const double sample_y =\n        y + height * (1.0 - UnitHeight(laps_[i].ToMillisecondsF(),\n                                       max_unit_interval));\n    path.lineTo(x + width * unit_x, sample_y);\n    path.lineTo(x + width * unit_next_x, sample_y);\n  }\n  path.lineTo(\n      width,\n      y + height * (1.0 - UnitHeight(laps_[kMaxSamples - 1].ToMillisecondsF(),\n                                     max_unit_interval)));\n  path.lineTo(width, height);\n  path.close();\n\n  // Draw the graph.\n  paint.setColor(0xAA0000FF);\n  cache_canvas->drawPath(path, paint);\n}\n\nvoid Stopwatch::Visualize(SkCanvas* canvas,\n                          const skity::Rect& skity_rect) const {\n  // Initialize visualize cache if it has not yet been initialized.\n  InitVisualizeSurface(skity_rect);\n  SkRect rect = clay::ConvertSkityRectToSkRect(skity_rect);\n\n  SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas();\n  SkPaint paint;\n\n  // Establish the graph position.\n  const SkScalar x = 0;\n  const SkScalar y = 0;\n  const SkScalar width = rect.width();\n  const SkScalar height = rect.height();\n\n  // Scale the graph to show frame times up to those that are 3 times the frame\n  // time.\n  const double one_frame_ms = GetFrameBudget().count();\n  const double max_interval = one_frame_ms * 3.0;\n  const double max_unit_interval = UnitFrameInterval(max_interval);\n\n  const double sample_unit_width = (1.0 / kMaxSamples);\n\n  // Draw vertical replacement bar to erase old/stale pixels.\n  paint.setColor(0x99FFFFFF);\n  paint.setStyle(SkPaint::Style::kFill_Style);\n  paint.setBlendMode(SkBlendMode::kSrc);\n  double sample_x =\n      x + width * (static_cast<double>(prev_drawn_sample_index_) / kMaxSamples);\n  const auto eraser_rect = SkRect::MakeLTRB(\n      sample_x, y, sample_x + width * sample_unit_width, height);\n  cache_canvas->drawRect(eraser_rect, paint);\n\n  // Draws blue timing bar for new data.\n  paint.setColor(0xAA0000FF);\n  paint.setBlendMode(SkBlendMode::kSrcOver);\n  const auto bar_rect = SkRect::MakeLTRB(\n      sample_x,\n      y + height * (1.0 -\n                    UnitHeight(laps_[current_sample_ == 0 ? kMaxSamples - 1\n                                                          : current_sample_ - 1]\n                                   .ToMillisecondsF(),\n                               max_unit_interval)),\n      sample_x + width * sample_unit_width, height);\n  cache_canvas->drawRect(bar_rect, paint);\n\n  // Draw horizontal frame markers.\n  paint.setStrokeWidth(0);  // hairline\n  paint.setStyle(SkPaint::Style::kStroke_Style);\n  paint.setColor(0xCC000000);\n\n  if (max_interval > one_frame_ms) {\n    // Paint the horizontal markers\n    size_t frame_marker_count =\n        static_cast<size_t>(max_interval / one_frame_ms);\n\n    // Limit the number of markers displayed. After a certain point, the graph\n    // becomes crowded\n    if (frame_marker_count > kMaxFrameMarkers) {\n      frame_marker_count = 1;\n    }\n\n    for (size_t frame_index = 0; frame_index < frame_marker_count;\n         frame_index++) {\n      const double frame_height =\n          height * (1.0 - (UnitFrameInterval((frame_index + 1) * one_frame_ms) /\n                           max_unit_interval));\n      cache_canvas->drawLine(x, y + frame_height, width, y + frame_height,\n                             paint);\n    }\n  }\n\n  // Paint the vertical marker for the current frame.\n  // We paint it over the current frame, not after it, because when we\n  // paint this we don't yet have all the times for the current frame.\n  paint.setStyle(SkPaint::Style::kFill_Style);\n  paint.setBlendMode(SkBlendMode::kSrcOver);\n  if (UnitFrameInterval(LastLap().ToMillisecondsF()) > 1.0) {\n    // budget exceeded\n    paint.setColor(SK_ColorRED);\n  } else {\n    // within budget\n    paint.setColor(SK_ColorGREEN);\n  }\n  sample_x = x + width * (static_cast<double>(current_sample_) / kMaxSamples);\n  const auto marker_rect = SkRect::MakeLTRB(\n      sample_x, y, sample_x + width * sample_unit_width, height);\n  cache_canvas->drawRect(marker_rect, paint);\n  prev_drawn_sample_index_ = current_sample_;\n\n  // Draw the cached surface onto the output canvas.\n  visualize_cache_surface_->draw(canvas, rect.x(), rect.y());\n}\n#else\nvoid Stopwatch::InitVisualizeSurface(const skity::Rect& rect) const {\n#if !OS_IOS\n  // Mark as dirty if the size has changed.\n  if (visualize_cache_canvas_) {\n    if (rect.Width() != visualize_cache_canvas_->Width() ||\n        rect.Height() != visualize_cache_canvas_->Height()) {\n      cache_dirty_ = true;\n    };\n  }\n\n  if (!cache_dirty_) {\n    return;\n  }\n\n  if (!visualize_cache_canvas_) {\n    visualize_cache_bitmap_ = std::make_unique<skity::Bitmap>(\n        rect.Width(), rect.Height(), skity::AlphaType::kPremul_AlphaType);\n    visualize_cache_canvas_ =\n        skity::Canvas::MakeSoftwareCanvas(visualize_cache_bitmap_.get());\n  }\n\n  // Establish the graph position.\n  const float x = 0;\n  const float y = 0;\n  const float width = rect.Width();\n  const float height = rect.Height();\n\n  skity::Paint paint;\n  paint.SetColor(0x99FFFFFF);\n  visualize_cache_canvas_->DrawRect(skity::Rect::MakeXYWH(x, y, width, height),\n                                    paint);\n\n  // Scale the graph to show frame times up to those that are 3 times the frame\n  // time.\n  const double one_frame_ms = GetFrameBudget().count();\n  const double max_interval = one_frame_ms * 3.0;\n  const double max_unit_interval = UnitFrameInterval(max_interval);\n\n  // Draw the old data to initially populate the graph.\n  // Prepare a path for the data. We start at the height of the last point, so\n  // it looks like we wrap around\n  skity::Path path;\n  path.MoveTo(x, height);\n  path.LineTo(x, y + height * (1.0 - UnitHeight(laps_[0].ToMillisecondsF(),\n                                                max_unit_interval)));\n\n  double unit_x;\n  double unit_next_x = 0.0;\n  for (size_t i = 0; i < kMaxSamples; i += 1) {\n    unit_x = unit_next_x;\n    unit_next_x = (static_cast<double>(i + 1) / kMaxSamples);\n    const double sample_y =\n        y + height * (1.0 - UnitHeight(laps_[i].ToMillisecondsF(),\n                                       max_unit_interval));\n    path.LineTo(x + width * unit_x, sample_y);\n    path.LineTo(x + width * unit_next_x, sample_y);\n  }\n  path.LineTo(\n      width,\n      y + height * (1.0 - UnitHeight(laps_[kMaxSamples - 1].ToMillisecondsF(),\n                                     max_unit_interval)));\n  path.LineTo(width, height);\n  path.Close();\n\n  // Draw the graph.\n  paint.SetColor(0xAA0000FF);\n  visualize_cache_canvas_->DrawPath(path, paint);\n#endif  // !OS_IOS\n}\n\nvoid Stopwatch::Visualize(skity::Canvas* canvas,\n                          const skity::Rect& rect) const {\n#if !OS_IOS\n  // Initialize visualize cache if it has not yet been initialized.\n  InitVisualizeSurface(rect);\n\n  skity::Paint paint;\n\n  // Establish the graph position.\n  const float x = 0;\n  const float y = 0;\n  const float width = rect.Width();\n  const float height = rect.Height();\n\n  // Scale the graph to show frame times up to those that are 3 times the frame\n  // time.\n  const double one_frame_ms = GetFrameBudget().count();\n  const double max_interval = one_frame_ms * 3.0;\n  const double max_unit_interval = UnitFrameInterval(max_interval);\n\n  const double sample_unit_width = (1.0 / kMaxSamples);\n\n  // Draw vertical replacement bar to erase old/stale pixels.\n  paint.SetColor(0x99FFFFFF);\n  paint.SetStyle(skity::Paint::Style::kFill_Style);\n  paint.SetBlendMode(skity::BlendMode::kSrc);\n  double sample_x =\n      x + width * (static_cast<double>(prev_drawn_sample_index_) / kMaxSamples);\n  const auto eraser_rect = skity::Rect::MakeLTRB(\n      sample_x, y, sample_x + width * sample_unit_width, height);\n  visualize_cache_canvas_->DrawRect(eraser_rect, paint);\n\n  // Draws blue timing bar for new data.\n  paint.SetColor(0xAA0000FF);\n  paint.SetBlendMode(skity::BlendMode::kSrcOver);\n  const auto bar_rect = skity::Rect::MakeLTRB(\n      sample_x,\n      y + height * (1.0 -\n                    UnitHeight(laps_[current_sample_ == 0 ? kMaxSamples - 1\n                                                          : current_sample_ - 1]\n                                   .ToMillisecondsF(),\n                               max_unit_interval)),\n      sample_x + width * sample_unit_width, height);\n  visualize_cache_canvas_->DrawRect(bar_rect, paint);\n\n  // Draw horizontal frame markers.\n  paint.SetStrokeWidth(0);  // hairline\n  paint.SetStyle(skity::Paint::Style::kStroke_Style);\n  paint.SetColor(0xCC000000);\n\n  if (max_interval > one_frame_ms) {\n    // Paint the horizontal markers\n    size_t frame_marker_count =\n        static_cast<size_t>(max_interval / one_frame_ms);\n\n    // Limit the number of markers displayed. After a certain point, the graph\n    // becomes crowded\n    if (frame_marker_count > kMaxFrameMarkers) {\n      frame_marker_count = 1;\n    }\n\n    for (size_t frame_index = 0; frame_index < frame_marker_count;\n         frame_index++) {\n      const double frame_height =\n          height * (1.0 - (UnitFrameInterval((frame_index + 1) * one_frame_ms) /\n                           max_unit_interval));\n      visualize_cache_canvas_->DrawLine(x, y + frame_height, width,\n                                        y + frame_height, paint);\n    }\n  }\n\n  // Paint the vertical marker for the current frame.\n  // We paint it over the current frame, not after it, because when we\n  // paint this we don't yet have all the times for the current frame.\n  paint.SetStyle(skity::Paint::Style::kFill_Style);\n  paint.SetBlendMode(skity::BlendMode::kSrcOver);\n  if (UnitFrameInterval(LastLap().ToMillisecondsF()) > 1.0) {\n    // budget exceeded\n    paint.SetColor(clay::Color::kRed());\n  } else {\n    // within budget\n    paint.SetColor(clay::Color::kGreen());\n  }\n  sample_x = x + width * (static_cast<double>(current_sample_) / kMaxSamples);\n  const auto marker_rect = skity::Rect::MakeLTRB(\n      sample_x, y, sample_x + width * sample_unit_width, height);\n  visualize_cache_canvas_->DrawRect(marker_rect, paint);\n  prev_drawn_sample_index_ = current_sample_;\n\n  // Draw the cached surface onto the output canvas.\n  std::shared_ptr<skity::Image> image =\n      skity::Image::MakeImage(visualize_cache_bitmap_->GetPixmap());\n  visualize_cache_bitmap_ = nullptr;\n  visualize_cache_canvas_ = nullptr;\n  canvas->DrawImage(image, rect.X(), rect.Y());\n#endif  // !OS_IOS\n}\n#endif  // ENABLE_SKITY\n\nfml::Milliseconds Stopwatch::GetFrameBudget() const {\n  return refresh_rate_updater_.GetFrameBudget();\n}\n\nCounterValues::CounterValues() : current_sample_(kMaxSamples - 1) {\n  values_.resize(kMaxSamples, 0);\n}\n\nCounterValues::~CounterValues() = default;\n\nvoid CounterValues::Add(int64_t value) {\n  current_sample_ = (current_sample_ + 1) % kMaxSamples;\n  values_[current_sample_] = value;\n}\n\nvoid CounterValues::Visualize(clay::GrCanvas* canvas,\n                              const skity::Rect& rect) const {\n  size_t max_bytes = GetMaxValue();\n\n  if (max_bytes == 0) {\n    // The backend for this counter probably did not fill in any values.\n    return;\n  }\n\n  size_t min_bytes = GetMinValue();\n\n  clay::GrPaint paint;\n\n  // Paint the background.\n  PAINT_SET_COLOR(paint, 0x99FFFFFF);\n  CANVAS_DRAW_RECT(canvas, rect, paint);\n\n  // Establish the graph position.\n  const float x = rect.X();\n  const float y = rect.Y();\n  const float width = rect.Width();\n  const float height = rect.Height();\n  const float bottom = y + height;\n  const float right = x + width;\n\n  // Prepare a path for the data.\n  clay::GrPath path;\n  PATH_MOVE_TO(path, x, bottom);\n\n  for (size_t i = 0; i < kMaxSamples; ++i) {\n    int64_t current_bytes = values_[i];\n    double ratio = static_cast<double>(current_bytes - min_bytes) /\n                   static_cast<double>(max_bytes - min_bytes);\n    PATH_LINE_TO(\n        path,\n        x + ((static_cast<double>(i) / static_cast<double>(kMaxSamples)) *\n             width),\n        y + ((1.0 - ratio) * height));\n  }\n\n  int delta_x = 100;\n  int delta_y = 0;\n  PATH_RLINE_TO(path, delta_x, delta_y);\n  PATH_LINE_TO(path, right, bottom);\n  PATH_CLOSE(path);\n\n  // Draw the graph.\n  PAINT_SET_COLOR(paint, 0xAA0000FF);\n  CANVAS_DRAW_PATH(canvas, path, paint);\n\n  // Paint the vertical marker for the current frame.\n  const double sample_unit_width = (1.0 / kMaxSamples);\n  const double sample_margin_unit_width = sample_unit_width / 6.0;\n  const double sample_margin_width = width * sample_margin_unit_width;\n  PAINT_SET_STYLE(paint, 0);\n  PAINT_SET_COLOR(paint, clay::ToSk(clay::Color::kDarkGrey()));\n  double sample_x =\n      x + width * (static_cast<double>(current_sample_) / kMaxSamples) -\n      sample_margin_width;\n  const auto marker_rect = skity::Rect::MakeLTRB(\n      sample_x, y,\n      sample_x + width * sample_unit_width + sample_margin_width * 2, bottom);\n  CANVAS_DRAW_RECT(canvas, marker_rect, paint);\n}\n\nint64_t CounterValues::GetMaxValue() const {\n  auto max = std::numeric_limits<int64_t>::min();\n  for (size_t i = 0; i < kMaxSamples; ++i) {\n    max = std::max<int64_t>(max, values_[i]);\n  }\n  return max;\n}\n\nint64_t CounterValues::GetMinValue() const {\n  auto min = std::numeric_limits<int64_t>::max();\n  for (size_t i = 0; i < kMaxSamples; ++i) {\n    min = std::min<int64_t>(min, values_[i]);\n  }\n  return min;\n}\n\nfml::Milliseconds FixedRefreshRateUpdater::GetFrameBudget() const {\n  return fixed_frame_budget_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/stopwatch.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_STOPWATCH_H_\n#define CLAY_FLOW_STOPWATCH_H_\n\n#include <memory>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nclass Stopwatch {\n public:\n  /// The refresh rate interface for `Stopwatch`.\n  class RefreshRateUpdater {\n   public:\n    /// Time limit for a smooth frame.\n    /// See: `DisplayManager::GetMainDisplayRefreshRate`.\n    virtual fml::Milliseconds GetFrameBudget() const = 0;\n  };\n\n  /// The constructor with a updater parameter, it will update frame_budget\n  /// everytime when `GetFrameBudget()` is called.\n  explicit Stopwatch(const RefreshRateUpdater& updater);\n\n  ~Stopwatch();\n\n  const fml::TimeDelta& LastLap() const;\n\n  fml::TimeDelta MaxDelta() const;\n\n  fml::TimeDelta AverageDelta() const;\n\n  void InitVisualizeSurface(const skity::Rect& rect) const;\n\n  void Visualize(clay::GrCanvas* canvas, const skity::Rect& rect) const;\n\n  void Start(fml::TimePoint time = fml::TimePoint::Now());\n\n  void Stop(fml::TimePoint time = fml::TimePoint::Now());\n\n  void SetLapTime(const fml::TimeDelta& delta);\n\n  /// All places which want to get frame_budget should call this function.\n  fml::Milliseconds GetFrameBudget() const;\n  bool CheckIfLastSampleInCycle() const;\n\n  const std::vector<fml::TimeDelta>& GetLaps() const { return laps_; }\n\n private:\n  inline double UnitFrameInterval(double time_ms) const;\n  inline double UnitHeight(double time_ms, double max_height) const;\n\n  const RefreshRateUpdater& refresh_rate_updater_;\n  fml::TimePoint start_;\n  std::vector<fml::TimeDelta> laps_;\n  size_t current_sample_;\n\n  // Mutable data cache for performance optimization of the graphs. Prevents\n  // expensive redrawing of old data.\n  mutable bool cache_dirty_;\n  [[maybe_unused]] mutable clay::GrSurfacePtr visualize_cache_surface_;\n  [[maybe_unused]] mutable std::unique_ptr<clay::GrBitmap>\n      visualize_cache_bitmap_;\n  mutable std::unique_ptr<clay::GrCanvas> visualize_cache_canvas_;\n  mutable size_t prev_drawn_sample_index_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Stopwatch);\n};\n\n/// Used for fixed refresh rate query cases.\nclass FixedRefreshRateUpdater : public Stopwatch::RefreshRateUpdater {\n  fml::Milliseconds GetFrameBudget() const override;\n\n public:\n  explicit FixedRefreshRateUpdater(\n      fml::Milliseconds fixed_frame_budget = fml::kDefaultFrameBudget);\n\n private:\n  fml::Milliseconds fixed_frame_budget_;\n};\n\n/// Used for fixed refresh rate cases.\nclass FixedRefreshRateStopwatch : public Stopwatch {\n public:\n  explicit FixedRefreshRateStopwatch(\n      fml::Milliseconds fixed_frame_budget = fml::kDefaultFrameBudget);\n\n private:\n  FixedRefreshRateUpdater fixed_delegate_;\n};\n\nclass Counter {\n public:\n  Counter() : count_(0) {}\n\n  size_t count() const { return count_; }\n\n  void Reset(size_t count = 0) { count_ = count; }\n\n  void Increment(size_t count = 1) { count_ += count; }\n\n private:\n  size_t count_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Counter);\n};\n\nclass CounterValues {\n public:\n  CounterValues();\n\n  ~CounterValues();\n\n  void Add(int64_t value);\n\n  void Visualize(clay::GrCanvas* canvas, const skity::Rect& rect) const;\n\n  int64_t GetMaxValue() const;\n\n  int64_t GetMinValue() const;\n\n private:\n  std::vector<int64_t> values_;\n  size_t current_sample_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(CounterValues);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_STOPWATCH_H_\n"
  },
  {
    "path": "clay/flow/stopwatch_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/stopwatch.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nusing testing::Return;\n\nnamespace clay {\nnamespace testing {\n\nclass MockRefreshRateUpdater : public Stopwatch::RefreshRateUpdater {\n public:\n  MOCK_CONST_METHOD0(GetFrameBudget, fml::Milliseconds());\n};\n\nTEST(Stopwatch, GetDefaultFrameBudgetTest) {\n  fml::Milliseconds frame_budget_60fps = fml::RefreshRateToFrameBudget(60);\n  // The default constructor sets the frame_budget to 16.6667 (60 fps).\n  FixedRefreshRateStopwatch stopwatch;\n  fml::Milliseconds actual_frame_budget = stopwatch.GetFrameBudget();\n  EXPECT_EQ(frame_budget_60fps, actual_frame_budget);\n}\n\nTEST(Stopwatch, GetOneShotFrameBudgetTest) {\n  fml::Milliseconds frame_budget_90fps = fml::RefreshRateToFrameBudget(90);\n  FixedRefreshRateStopwatch stopwatch(frame_budget_90fps);\n  fml::Milliseconds actual_frame_budget = stopwatch.GetFrameBudget();\n  EXPECT_EQ(frame_budget_90fps, actual_frame_budget);\n}\n\nTEST(Stopwatch, GetFrameBudgetFromUpdaterTest) {\n  MockRefreshRateUpdater updater;\n  fml::Milliseconds frame_budget_90fps = fml::RefreshRateToFrameBudget(90);\n  EXPECT_CALL(updater, GetFrameBudget())\n      .Times(1)\n      .WillOnce(Return(frame_budget_90fps));\n  Stopwatch stopwatch(updater);\n  fml::Milliseconds actual_frame_budget = stopwatch.GetFrameBudget();\n  EXPECT_EQ(frame_budget_90fps, actual_frame_budget);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/surface.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/surface.h\"\n\nnamespace clay {\n\nSurface::Surface() = default;\n\nSurface::~Surface() = default;\n\nstd::unique_ptr<GLContextResult> Surface::MakeRenderContextCurrent() {\n  return std::make_unique<GLContextDefaultResult>(true);\n}\n\nbool Surface::ClearRenderContext() { return false; }\n\nbool Surface::AllowsDrawingWhenGpuDisabled() const { return true; }\n\nbool Surface::EnableRasterCache() const { return true; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/surface.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_SURFACE_H_\n#define CLAY_FLOW_SURFACE_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/common/graphics/gl_context_switch.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/flow/surface_frame.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n/// Abstract Base Class that represents where we will be rendering content.\nclass Surface {\n public:\n  Surface();\n\n  virtual ~Surface();\n\n  virtual bool IsValid() = 0;\n\n  virtual std::unique_ptr<SurfaceFrame> AcquireFrame(\n      const skity::Vec2& size) = 0;\n\n  virtual skity::Matrix GetRootTransformation() const = 0;\n  virtual clay::GrContext* GetContext() = 0;\n\n  virtual std::unique_ptr<GLContextResult> MakeRenderContextCurrent();\n\n  virtual bool ClearRenderContext();\n\n  virtual bool AllowsDrawingWhenGpuDisabled() const;\n\n  virtual bool EnableRasterCache() const;\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(Surface);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_SURFACE_H_\n"
  },
  {
    "path": "clay/flow/surface_frame.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/surface_frame.h\"\n\n#include <limits>\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nSurfaceFrame::SurfaceFrame(clay::GrSurfacePtr surface,\n                           FramebufferInfo framebuffer_info,\n                           const EncodeCallback& encode_callback,\n                           const SubmitCallback& submit_callback,\n                           skity::Vec2 frame_size,\n                           std::unique_ptr<GLContextResult> context_result)\n    : surface_(std::move(surface)),\n      framebuffer_info_(framebuffer_info),\n      encode_callback_(encode_callback),\n      submit_callback_(submit_callback),\n      context_result_(std::move(context_result)) {\n  FML_DCHECK(submit_callback_);\n  if (surface_) {\n    [[maybe_unused]] bool clear = !framebuffer_info.supports_partial_repaint;\n    canvas_ = SURFACE_GET_CANVAS(surface_, clear);\n  }\n}\n\nclay::GrCanvas* SurfaceFrame::GetCanvas() { return canvas_; }\n\nclay::GrSurfacePtr SurfaceFrame::GetSurface() const { return surface_; }\n\nvoid SurfaceFrame::Prepare(std::optional<skity::Rect> damage) {\n  if (prepared_) {\n    FML_LOG(ERROR) << \"Surface frame already prepared.\";\n    return;\n  }\n\n  if (prepared_callback_) {\n    prepared_callback_(damage);\n  }\n\n  prepared_ = true;\n}\n\nbool SurfaceFrame::Encode() {\n  TRACE_EVENT(\"clay\", \"SurfaceFrame::Encode\");\n  if (encoded_) {\n    return false;\n  }\n\n  encoded_ = PerformEncode();\n\n  return encoded_;\n}\n\nstd::pair<SurfaceFrame::SubmitCallback, SurfaceFrame::SubmitInfo>\nSurfaceFrame::PrepareSubmit() {\n  if (!encoded_ && !Encode()) {\n    return {nullptr, {}};\n  }\n\n  return {[submitted = submitted_,\n           submit_callback = submit_callback_](const SubmitInfo& info) {\n            if (*submitted) {\n              FML_LOG(ERROR) << \"Surface frame already submitted.\";\n              return false;\n            }\n            *submitted = true;\n            return submit_callback(info);\n          },\n          submit_info_};\n}\n\nbool SurfaceFrame::Submit() {\n  auto [submit_callback, submit_info] = PrepareSubmit();\n  if (submit_callback == nullptr) {\n    return false;\n  }\n  return submit_callback(std::move(submit_info));\n}\n\nbool SurfaceFrame::PerformEncode() {\n  if (encode_callback_ == nullptr) {\n    return false;\n  }\n\n  if (encode_callback_(*this, GetCanvas())) {\n    return true;\n  }\n  return false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/surface_frame.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_SURFACE_FRAME_H_\n#define CLAY_FLOW_SURFACE_FRAME_H_\n\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/common/graphics/gl_context_switch.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\n// This class represents a frame that has been fully configured for the\n// underlying client rendering API. A frame may only be submitted once.\nclass SurfaceFrame {\n public:\n  // This callback is called when the frame is prepared to be renderered.\n  // Since in EGL\n  // https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_partial_update.txt,\n  // any render commands must happen after eglSetDamageRegionKHR.\n  using PreparedCallback =\n      std::function<void(std::optional<skity::Rect> buffer_damage)>;\n  using EncodeCallback =\n      std::function<bool(SurfaceFrame& surface_frame, clay::GrCanvas* canvas)>;\n\n  struct SubmitInfo {\n    // The frame damage for frame n is the difference between frame n and\n    // frame (n-1), and represents the area that a compositor must recompose.\n    //\n    // Corresponds to EGL_KHR_swap_buffers_with_damage\n    std::optional<skity::Rect> frame_damage;\n\n    // The buffer damage for a frame is the area changed since that same buffer\n    // was last used. If the buffer has not been used before, the buffer damage\n    // is the entire area of the buffer.\n    //\n    // Corresponds to EGL_KHR_partial_update\n    std::optional<skity::Rect> buffer_damage;\n\n    // Time at which this frame is scheduled to be presented. This is a hint\n    // that can be passed to the platform to drop queued frames.\n    std::optional<fml::TimePoint> presentation_time;\n\n    // Whether this surface presents with a CATransaction on Apple platforms.\n    //\n    // When there are platform views in the scene, the drawable needs to be\n    // presented in the same CATransaction as the one created for platform view\n    // mutations.\n    //\n    // If the drawables are being presented from the raster thread, we cannot\n    // use a transaction as it will dirty the UIViews being presented. If there\n    // is a non-Flutter UIView active, such as in add2app or a\n    // presentViewController page transition, then this will cause CoreAnimation\n    // assertion errors and exit the app.\n    bool present_with_transaction = false;\n  };\n\n  // SubmitCallback may be called in Raster thread or Platform thread.\n  // The callback should not capture any resources whoese lifetime is bound to\n  // raster thread.\n  using SubmitCallback = std::function<bool(const SubmitInfo&)>;\n\n  // Information about the underlying framebuffer\n  struct FramebufferInfo {\n    // Indicates whether or not the surface supports pixel readback as used in\n    // circumstances such as a BackdropFilter.\n    bool supports_readback = false;\n\n    // Indicates that target device supports partial repaint. At very minimum\n    // this means that the surface will provide valid existing damage.\n    bool supports_partial_repaint = false;\n\n    // For some targets it may be beneficial or even required to snap clip\n    // rect to tile grid. I.e. repainting part of a tile may cause performance\n    // degradation if the tile needs to be decompressed first.\n    int vertical_clip_alignment = 1;\n    int horizontal_clip_alignment = 1;\n\n    // This is the area of framebuffer that lags behind the front buffer.\n    //\n    // Correctly providing exiting_damage is necessary for supporting double and\n    // triple buffering. Embedder is responsible for tracking this area for each\n    // of the back buffers used. When doing partial redraw, this area will be\n    // repainted alongside of dirty area determined by diffing current and\n    // last successfully rasterized layer tree;\n    //\n    // If existing damage is unspecified (nullopt), entire frame will be\n    // rasterized (no partial redraw). To signal that there is no existing\n    // damage use an empty skity::Rect.\n    std::optional<skity::Rect> existing_damage = std::nullopt;\n  };\n\n  SurfaceFrame(clay::GrSurfacePtr surface, FramebufferInfo framebuffer_info,\n               const EncodeCallback& encode_callback,\n               const SubmitCallback& submit_callback, skity::Vec2 frame_size,\n               std::unique_ptr<GLContextResult> context_result = nullptr);\n\n  void Prepare(std::optional<skity::Rect> buffer_damage);\n\n  bool Encode();\n\n  // It's the caller's responsibility to call SubmitCallback to do the real\n  // submit in the raster thread or platform thread.\n  [[nodiscard]] std::pair<SubmitCallback, SubmitInfo> PrepareSubmit();\n\n  bool Submit();\n\n  clay::GrSurfacePtr GetSurface() const;\n  clay::GrCanvas* GetCanvas();\n\n  const FramebufferInfo& framebuffer_info() const { return framebuffer_info_; }\n\n  void set_submit_info(const SubmitInfo& submit_info) {\n    submit_info_ = submit_info;\n  }\n  const SubmitInfo& submit_info() const { return submit_info_; }\n\n  void SetPreparedCallback(const PreparedCallback& callback) {\n    prepared_callback_ = callback;\n  }\n\n private:\n  bool prepared_ = false;\n  bool encoded_ = false;\n  std::shared_ptr<bool> submitted_ = std::make_shared<bool>(false);\n\n  clay::GrSurfacePtr surface_;\n  clay::GrCanvas* canvas_ = nullptr;\n  FramebufferInfo framebuffer_info_;\n  SubmitInfo submit_info_;\n  PreparedCallback prepared_callback_;\n  EncodeCallback encode_callback_;\n  SubmitCallback submit_callback_;\n  std::unique_ptr<GLContextResult> context_result_;\n\n  bool PerformEncode();\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SurfaceFrame);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_SURFACE_FRAME_H_\n"
  },
  {
    "path": "clay/flow/surface_frame_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"clay/flow/surface_frame.h\"\n#include \"clay/testing/testing.h\"\n\nnamespace clay {\n\nTEST(FlowTest, SurfaceFrameDoesNotSubmitInDtor) {\n  SurfaceFrame::FramebufferInfo framebuffer_info;\n  auto surface_frame = std::make_unique<SurfaceFrame>(\n      /*surface=*/nullptr, framebuffer_info,\n      /*encode_callback=*/[](const SurfaceFrame&, SkCanvas*) { return true; },\n      /*submit_callback=*/\n\n      [](const SurfaceFrame::SubmitInfo&) {\n        EXPECT_FALSE(true);\n        return true;\n      },\n      skity::Vec2{800, 600});\n  surface_frame.reset();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/diff_context_test.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/diff_context_test.h\"\n\n#include <utility>\n\n#include \"clay/gfx/skia/picture_skia.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing clay::GPUObject;\nusing clay::GPUUnrefQueue;\n\nDiffContextTest::DiffContextTest()\n    : unref_queue_(fml::MakeRefCounted<GPUUnrefQueue>(GetCurrentTaskRunner())) {\n}\n\nDiffContextTest::~DiffContextTest() { unref_queue_->Drain(); }\n\nDamage DiffContextTest::DiffLayerTree(MockLayerTree& layer_tree,\n                                      const MockLayerTree& old_layer_tree,\n                                      const skity::Rect& additional_damage,\n                                      int horizontal_clip_alignment,\n                                      int vertical_clip_alignment,\n                                      bool use_raster_cache) {\n  FML_CHECK(layer_tree.size() == old_layer_tree.size());\n\n  DiffContext dc(layer_tree.size(), 1, layer_tree.paint_region_map(),\n                 old_layer_tree.paint_region_map(), use_raster_cache);\n  dc.PushCullRect(\n      skity::Rect::MakeWH(layer_tree.size().x, layer_tree.size().y));\n  layer_tree.root()->Diff(&dc, old_layer_tree.root());\n  return dc.ComputeDamage(additional_damage, horizontal_clip_alignment,\n                          vertical_clip_alignment);\n}\n\nsk_sp<SkPicture> DiffContextTest::CreatePicture(const skity::Rect& bounds,\n                                                SkColor color) {\n  SkPictureRecorder recorder;\n  auto bbh_factory = std::make_unique<SkRTreeFactory>();\n  SkCanvas* canvas = recorder.beginRecording(\n      clay::ConvertSkityRectToSkRect(bounds), bbh_factory.get());\n  SkPaint paint;\n  paint.setColor(color);\n  canvas->drawRect(clay::ConvertSkityRectToSkRect(bounds), paint);\n  return recorder.finishRecordingAsPicture();\n}\n\nstd::shared_ptr<PictureLayer> DiffContextTest::CreatePictureLayer(\n    sk_sp<SkPicture> picture, const skity::Vec2& offset) {\n  clay::DynamicOps ops;\n  auto picture_skia =\n      fml::MakeRefCounted<clay::PictureSkia>(picture, std::move(ops));\n  return std::make_shared<PictureLayer>(\n      offset, GPUObject(std::move(picture_skia), unref_queue()), false, false);\n}\n\nstd::shared_ptr<ContainerLayer> DiffContextTest::CreateContainerLayer(\n    std::initializer_list<std::shared_ptr<Layer>> layers) {\n  auto res = std::make_shared<ContainerLayer>();\n  for (const auto& l : layers) {\n    res->Add(l);\n  }\n  return res;\n}\n\nstd::shared_ptr<OpacityLayer> DiffContextTest::CreateOpacityLater(\n    std::initializer_list<std::shared_ptr<Layer>> layers, SkAlpha alpha,\n    const skity::Vec2& offset) {\n  auto res = std::make_shared<OpacityLayer>(alpha, offset);\n  for (const auto& l : layers) {\n    res->Add(l);\n  }\n  return res;\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/diff_context_test.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_FLOW_TESTING_DIFF_CONTEXT_TEST_H_\n#define CLAY_FLOW_TESTING_DIFF_CONTEXT_TEST_H_\n\n#include <memory>\n\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/layers/picture_layer.h\"\n#include \"clay/flow/testing/gpu_object_layer_test.h\"\n#include \"third_party/skia/include/core/SkPicture.h\"\n#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n\nnamespace clay {\nnamespace testing {\n\nclass MockLayerTree {\n public:\n  explicit MockLayerTree(skity::Vec2 size = {1000, 1000})\n      : root_(std::make_shared<ContainerLayer>()), size_(size) {}\n\n  ContainerLayer* root() { return root_.get(); }\n  const ContainerLayer* root() const { return root_.get(); }\n\n  PaintRegionMap& paint_region_map() { return paint_region_map_; }\n  const PaintRegionMap& paint_region_map() const { return paint_region_map_; }\n\n  const skity::Vec2& size() const { return size_; }\n\n private:\n  std::shared_ptr<ContainerLayer> root_;\n  PaintRegionMap paint_region_map_;\n  skity::Vec2 size_;\n};\n\nclass DiffContextTest : public ThreadTest {\n public:\n  DiffContextTest();\n  ~DiffContextTest() override;\n\n  Damage DiffLayerTree(\n      MockLayerTree& layer_tree, const MockLayerTree& old_layer_tree,\n      const skity::Rect& additional_damage = skity::Rect::MakeEmpty(),\n      int horizontal_clip_alignment = 0, int vertical_alignment = 0,\n      bool use_raster_cache = true);\n\n  // Create SkPicture consisting of filled rect with given color; Being able\n  // to specify different color is useful to test deep comparison of pictures\n  sk_sp<SkPicture> CreatePicture(const skity::Rect& bounds, uint32_t color);\n\n  std::shared_ptr<PictureLayer> CreatePictureLayer(\n      sk_sp<SkPicture> picture, const skity::Vec2& offset = skity::Vec2(0, 0));\n\n  std::shared_ptr<ContainerLayer> CreateContainerLayer(\n      std::initializer_list<std::shared_ptr<Layer>> layers);\n\n  std::shared_ptr<ContainerLayer> CreateContainerLayer(\n      std::shared_ptr<Layer> l) {\n    return CreateContainerLayer({l});\n  }\n\n  std::shared_ptr<OpacityLayer> CreateOpacityLater(\n      std::initializer_list<std::shared_ptr<Layer>> layers, SkAlpha alpha,\n      const skity::Vec2& offset = skity::Vec2(0, 0));\n\n  fml::RefPtr<GPUUnrefQueue> unref_queue() { return unref_queue_; }\n\n private:\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_FLOW_TESTING_DIFF_CONTEXT_TEST_H_\n"
  },
  {
    "path": "clay/flow/testing/gl_context_switch_test.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/gl_context_switch_test.h\"\n\nnamespace clay {\nnamespace testing {\n\nstatic thread_local int current_context;\n\nTestSwitchableGLContext::TestSwitchableGLContext(int context)\n    : context_(context){};\n\nTestSwitchableGLContext::~TestSwitchableGLContext() = default;\n\nbool TestSwitchableGLContext::SetCurrent() {\n  SetCurrentContext(context_);\n  return true;\n};\n\nbool TestSwitchableGLContext::RemoveCurrent() {\n  SetCurrentContext(-1);\n  return true;\n};\n\nint TestSwitchableGLContext::GetContext() { return context_; };\n\nint TestSwitchableGLContext::GetCurrentContext() { return current_context; };\n\nvoid TestSwitchableGLContext::SetCurrentContext(int context) {\n  current_context = context;\n};\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/gl_context_switch_test.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_TESTING_GL_CONTEXT_SWITCH_TEST_H_\n#define CLAY_FLOW_TESTING_GL_CONTEXT_SWITCH_TEST_H_\n\n#include \"clay/common/graphics/gl_context_switch.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\n//------------------------------------------------------------------------------\n/// The renderer context used for testing\nclass TestSwitchableGLContext : public SwitchableGLContext {\n public:\n  explicit TestSwitchableGLContext(int context);\n\n  ~TestSwitchableGLContext() override;\n\n  bool SetCurrent() override;\n\n  bool RemoveCurrent() override;\n\n  int GetContext();\n\n  static int GetCurrentContext();\n\n  //------------------------------------------------------------------------------\n  /// Set the current context\n  ///\n  /// This is to mimic how other programs outside flutter sets the context.\n  static void SetCurrentContext(int context);\n\n private:\n  int context_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TestSwitchableGLContext);\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_FLOW_TESTING_GL_CONTEXT_SWITCH_TEST_H_\n"
  },
  {
    "path": "clay/flow/testing/gpu_object_layer_test.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/gpu_object_layer_test.h\"\n\n#include \"base/include/fml/time/time_delta.h\"\n\nnamespace clay {\nnamespace testing {\n\nGPUObjectLayerTest::GPUObjectLayerTest()\n    : unref_queue_(fml::MakeRefCounted<GPUUnrefQueue>(GetCurrentTaskRunner())) {\n}\n\nGPUObjectLayerTest::~GPUObjectLayerTest() { unref_queue_->Drain(); }\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/gpu_object_layer_test.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_TESTING_GPU_OBJECT_LAYER_TEST_H_\n#define CLAY_FLOW_TESTING_GPU_OBJECT_LAYER_TEST_H_\n\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/testing/thread_test.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing clay::GPUUnrefQueue;\n\n// This fixture allows generating tests that create |GPUObject|'s which\n// are destroyed on a |GPUUnrefQueue|.\nclass GPUObjectLayerTest : public LayerTestBase<ThreadTest> {\n public:\n  GPUObjectLayerTest();\n  ~GPUObjectLayerTest() override;\n\n  fml::RefPtr<GPUUnrefQueue> unref_queue() { return unref_queue_; }\n\n private:\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_FLOW_TESTING_GPU_OBJECT_LAYER_TEST_H_\n"
  },
  {
    "path": "clay/flow/testing/layer_test.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_TESTING_LAYER_TEST_H_\n#define CLAY_FLOW_TESTING_LAYER_TEST_H_\n\n#include <memory>\n#include <optional>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/layer_snapshot_store.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/testing/mock_raster_cache.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"clay/testing/canvas_test.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkImageInfo.h\"\n#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n#include \"third_party/skia/include/utils/SkNWayCanvas.h\"\n\nnamespace clay {\nnamespace testing {\n\n// This fixture allows generating tests which can |Paint()| and |Preroll()|\n// |Layer|'s.\n// |LayerTest| is a default implementation based on |::testing::Test|.\n//\n// By default the preroll and paint contexts will not use a raster cache.\n// If a test needs to verify the proper operation of a layer in the presence\n// of a raster cache then a number of options can be enabled by using the\n// methods |LayerTestBase::use_null_raster_cache()|,\n// |LayerTestBase::use_mock_raster_cache()| or\n// |LayerTestBase::use_skia_raster_cache()|\n//\n// |BaseT| should be the base test type, such as |::testing::Test| below.\ntemplate <typename BaseT>\nclass LayerTestBase : public CanvasTestBase<BaseT> {\n  using TestT = CanvasTestBase<BaseT>;\n\n public:\n  const SkRect kDlBounds = SkRect::MakeWH(500, 500);\n  LayerTestBase()\n      : drawable_image_registry_(std::make_shared<DrawableImageRegistry>()),\n        preroll_context_{\n            // clang-format off\n            .raster_cache                  = nullptr,\n            .gr_context                    = nullptr,\n            .compositor_state              = nullptr,\n            .state_stack                   = preroll_state_stack_,\n            .dst_color_space               = TestT::mock_color_space(),\n            .surface_needs_readback        = false,\n            .raster_time                   = raster_time_,\n            .ui_time                       = ui_time_,\n            .drawable_image_registry       = drawable_image_registry_,\n            .frame_device_pixel_ratio      = 1.0f,\n            .has_platform_view             = false,\n            .raster_cached_entries         = &cacheable_items_,\n            // clang-format on\n        },\n        paint_context_{\n            // clang-format off\n            .state_stack                   = paint_state_stack_,\n            .canvas                        = &TestT::mock_canvas(),\n            .gr_context                    = nullptr,\n            .compositor_state              = nullptr,\n            .raster_time                   = raster_time_,\n            .ui_time                       = ui_time_,\n            .drawable_image_registry       = drawable_image_registry_,\n            .raster_cache                  = nullptr,\n            .frame_device_pixel_ratio      = 1.0f,\n            // clang-format on\n        },\n        display_list_paint_context_{\n            // clang-format off\n            .state_stack                   = display_list_state_stack_,\n            .gr_context                    = nullptr,\n            .compositor_state              = nullptr,\n            .raster_time                   = raster_time_,\n            .ui_time                       = ui_time_,\n            .drawable_image_registry       = drawable_image_registry_,\n            .raster_cache                  = nullptr,\n            .frame_device_pixel_ratio      = 1.0f,\n            // clang-format on\n        },\n        checkerboard_context_{\n            // clang-format off\n            .state_stack                   = checkerboard_state_stack_,\n            .canvas                        = &TestT::mock_canvas(),\n            .gr_context                    = nullptr,\n            .compositor_state              = nullptr,\n            .raster_time                   = raster_time_,\n            .ui_time                       = ui_time_,\n            .drawable_image_registry       = drawable_image_registry_,\n            .raster_cache                  = nullptr,\n            .frame_device_pixel_ratio      = 1.0f,\n            // clang-format on\n        } {\n    rtree_factory_ = std::make_unique<SkRTreeFactory>();\n    display_list_paint_context_.canvas =\n        recorder_.beginRecording(kDlBounds, rtree_factory_.get());\n    use_null_raster_cache();\n    preroll_state_stack_.set_preroll_delegate(kGiantRect, skity::Matrix());\n    paint_state_stack_.set_delegate(&TestT::mock_canvas());\n    display_list_state_stack_.set_delegate(recorder_.getRecordingCanvas());\n    checkerboard_state_stack_.set_delegate(&TestT::mock_canvas());\n    checkerboard_state_stack_.set_checkerboard_func(draw_checkerboard);\n    checkerboard_paint_.setColor(checkerboard_color_);\n  }\n\n  /**\n   * @brief Use no raster cache in the preroll_context() and\n   * paint_context() structures.\n   *\n   * This method must be called before using the preroll_context() and\n   * paint_context() structures in calls to the Layer::Preroll() and\n   * Layer::Paint() methods. This is the default mode of operation.\n   *\n   * @see use_mock_raster_cache()\n   * @see use_skia_raster_cache()\n   */\n  void use_null_raster_cache() { set_raster_cache_(nullptr); }\n\n  /**\n   * @brief Use a mock raster cache in the preroll_context() and\n   * paint_context() structures.\n   *\n   * This method must be called before using the preroll_context() and\n   * paint_context() structures in calls to the Layer::Preroll() and\n   * Layer::Paint() methods. The mock raster cache behaves like a normal\n   * raster cache with respect to decisions about when layers and pictures\n   * should be cached, but it does not incur the overhead of rendering the\n   * layers or caching the resulting pixels.\n   *\n   * @see use_null_raster_cache()\n   * @see use_skia_raster_cache()\n   */\n  void use_mock_raster_cache() {\n    set_raster_cache_(std::make_unique<MockRasterCache>());\n  }\n\n  /**\n   * @brief Use a normal raster cache in the preroll_context() and\n   * paint_context() structures.\n   *\n   * This method must be called before using the preroll_context() and\n   * paint_context() structures in calls to the Layer::Preroll() and\n   * Layer::Paint() methods. The Skia raster cache will behave identically\n   * to the raster cache typically used when handling a frame on a device\n   * including rendering the contents of pictures and layers to an\n   * SkImage, but using a software rather than a hardware renderer.\n   *\n   * @see use_null_raster_cache()\n   * @see use_mock_raster_cache()\n   */\n  void use_skia_raster_cache() {\n    set_raster_cache_(std::make_unique<RasterCache>());\n  }\n\n  std::vector<RasterCacheItem*>& cacheable_items() { return cacheable_items_; }\n\n  std::shared_ptr<DrawableImageRegistry> drawable_image_registry() {\n    return drawable_image_registry_;\n  }\n  RasterCache* raster_cache() { return raster_cache_.get(); }\n  PrerollContext* preroll_context() { return &preroll_context_; }\n  PaintContext& paint_context() { return paint_context_; }\n  PaintContext& display_list_paint_context() {\n    return display_list_paint_context_;\n  }\n  const SkPaint& checkerboard_paint() { return checkerboard_paint_; }\n  PaintContext& checkerboard_context() { return checkerboard_context_; }\n  LayerSnapshotStore& layer_snapshot_store() { return snapshot_store_; }\n\n  sk_sp<SkPicture> display_list() {\n    if (display_list_ == nullptr) {\n      // null out the canvas and recorder fields of the PaintContext\n      // and the delegate of the state_stack to prevent future use.\n      display_list_paint_context_.state_stack.clear_delegate();\n      display_list_paint_context_.canvas = nullptr;\n      display_list_ = recorder_.finishRecordingAsPicture();\n      rtree_factory_.reset();\n    }\n    return display_list_;\n  }\n\n  void enable_leaf_layer_tracing() {\n    paint_context_.enable_leaf_layer_tracing = true;\n    paint_context_.layer_snapshot_store = &snapshot_store_;\n  }\n\n  void disable_leaf_layer_tracing() {\n    paint_context_.enable_leaf_layer_tracing = false;\n    paint_context_.layer_snapshot_store = nullptr;\n  }\n\n private:\n  void set_raster_cache_(std::unique_ptr<RasterCache> raster_cache) {\n    raster_cache_ = std::move(raster_cache);\n    preroll_context_.raster_cache = raster_cache_.get();\n    paint_context_.raster_cache = raster_cache_.get();\n    display_list_paint_context_.raster_cache = raster_cache_.get();\n  }\n\n  static constexpr SkColor checkerboard_color_ = 0x42424242;\n\n  static void draw_checkerboard(SkCanvas* canvas, const skity::Rect& rect) {\n    if (canvas) {\n      SkPaint paint;\n      paint.setColor(checkerboard_color_);\n      canvas->drawRect(clay::ConvertSkityRectToSkRect(rect), paint);\n    }\n  }\n\n  LayerStateStack preroll_state_stack_;\n  LayerStateStack paint_state_stack_;\n  LayerStateStack checkerboard_state_stack_;\n  FixedRefreshRateStopwatch raster_time_;\n  FixedRefreshRateStopwatch ui_time_;\n  std::shared_ptr<DrawableImageRegistry> drawable_image_registry_;\n\n  std::unique_ptr<RasterCache> raster_cache_;\n  PrerollContext preroll_context_;\n  PaintContext paint_context_;\n  SkPictureRecorder recorder_;\n  std::unique_ptr<SkBBHFactory> rtree_factory_;\n  LayerStateStack display_list_state_stack_;\n  sk_sp<SkPicture> display_list_;\n  PaintContext display_list_paint_context_;\n  SkPaint checkerboard_paint_;\n  PaintContext checkerboard_context_;\n  LayerSnapshotStore snapshot_store_;\n\n  std::vector<RasterCacheItem*> cacheable_items_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(LayerTestBase);\n};\nusing LayerTest = LayerTestBase<::testing::Test>;\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_FLOW_TESTING_LAYER_TEST_H_\n"
  },
  {
    "path": "clay/flow/testing/mock_drawable_image.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/mock_drawable_image.h\"\n\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/testing/gpu_object_layer_test.h\"\n\nnamespace clay {\nnamespace testing {\n\nvoid MockDrawableImage::Paint(PaintContext& context, const skity::Rect& bounds,\n                              bool freeze, const SkSamplingOptions& sampling,\n                              FitMode fit_mode) {\n  paint_calls_.emplace_back(PaintCall{*(context.canvas), bounds, freeze,\n                                      context.gr_context, sampling,\n                                      context.sk_paint, fit_mode});\n}\n\nbool operator==(const MockDrawableImage::PaintCall& a,\n                const MockDrawableImage::PaintCall& b) {\n  return &a.canvas == &b.canvas && a.bounds == b.bounds &&\n         a.context == b.context && a.freeze == b.freeze &&\n         a.sampling == b.sampling && a.paint == b.paint &&\n         a.fit_mode == b.fit_mode;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockDrawableImage::PaintCall& data) {\n  return os << &data.canvas << \" \" << data.bounds << \" \" << data.context << \" \"\n            << data.freeze << \" \" << data.sampling << \" \" << data.paint << \" \"\n            << static_cast<uint32_t>(data.fit_mode);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/mock_drawable_image.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_FLOW_TESTING_MOCK_DRAWABLE_IMAGE_H_\n#define CLAY_FLOW_TESTING_MOCK_DRAWABLE_IMAGE_H_\n\n#include <ostream>\n#include <vector>\n\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/testing/assertions_skia.h\"\n\nnamespace clay {\nnamespace testing {\n\n// Mock implementation of the |DrawableImage| interface that does not interact\n// with any actual rendering backend (such as GPU or CPU). It simply records the\n// list of various method calls made so that tests can later verify them against\n// expected data.\nclass MockDrawableImage : public DrawableImage {\n public:\n  struct PaintCall {\n    SkCanvas& canvas;\n    skity::Rect bounds;\n    bool freeze;\n    GrDirectContext* context;\n    SkSamplingOptions sampling;\n    const SkPaint* paint;\n    FitMode fit_mode;\n  };\n\n  MockDrawableImage() = default;\n\n  ImageType GetType() const override { return ImageType::kMock; }\n\n  void SetFrameAvailableCallback(const fml::closure& callback) override {}\n\n  // Called from raster thread.\n  void Paint(PaintContext& context, const skity::Rect& bounds, bool freeze,\n             const SkSamplingOptions& sampling, FitMode fit_mode) override;\n\n  void OnGrContextCreated() override { gr_context_created_ = true; }\n  void OnGrContextDestroyed() override { gr_context_destroyed_ = true; }\n  void MarkNewFrameAvailable() override {}\n  void OnDrawableImageUnregistered() override { unregistered_ = true; }\n\n  const std::vector<PaintCall>& paint_calls() { return paint_calls_; }\n  bool gr_context_created() { return gr_context_created_; }\n  bool gr_context_destroyed() { return gr_context_destroyed_; }\n  bool unregistered() { return unregistered_; }\n\n private:\n  std::vector<PaintCall> paint_calls_;\n  bool gr_context_created_ = false;\n  bool gr_context_destroyed_ = false;\n  bool unregistered_ = false;\n};\n\nextern bool operator==(const MockDrawableImage::PaintCall& a,\n                       const MockDrawableImage::PaintCall& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockDrawableImage::PaintCall& data);\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_FLOW_TESTING_MOCK_DRAWABLE_IMAGE_H_\n"
  },
  {
    "path": "clay/flow/testing/mock_drawable_image_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/mock_drawable_image.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(MockDrawableImageTest, Callbacks) {\n  auto drawable_image = std::make_shared<MockDrawableImage>();\n\n  ASSERT_FALSE(drawable_image->gr_context_created());\n  drawable_image->OnGrContextCreated();\n  ASSERT_TRUE(drawable_image->gr_context_created());\n\n  ASSERT_FALSE(drawable_image->gr_context_destroyed());\n  drawable_image->OnGrContextDestroyed();\n  ASSERT_TRUE(drawable_image->gr_context_destroyed());\n\n  ASSERT_FALSE(drawable_image->unregistered());\n  drawable_image->OnDrawableImageUnregistered();\n  ASSERT_TRUE(drawable_image->unregistered());\n}\n\nTEST(MockDrawableImageTest, PaintCalls) {\n  SkCanvas canvas;\n  const skity::Rect paint_bounds1 = skity::Rect::MakeWH(1.0f, 1.0f);\n  const skity::Rect paint_bounds2 = skity::Rect::MakeWH(2.0f, 2.0f);\n  const SkSamplingOptions sampling;\n  const auto expected_paint_calls = std::vector{\n      MockDrawableImage::PaintCall{canvas, paint_bounds1, false, nullptr,\n                                   sampling, nullptr,\n                                   DrawableImage::FitMode::kScaleToFill},\n      MockDrawableImage::PaintCall{canvas, paint_bounds2, true, nullptr,\n                                   sampling, nullptr,\n                                   DrawableImage::FitMode::kScaleToFill}};\n  auto drawable_image = std::make_shared<MockDrawableImage>();\n  DrawableImage::PaintContext context{\n      .canvas = &canvas,\n  };\n  drawable_image->Paint(context, paint_bounds1, false, sampling,\n                        DrawableImage::FitMode::kScaleToFill);\n  drawable_image->Paint(context, paint_bounds2, true, sampling,\n                        DrawableImage::FitMode::kScaleToFill);\n  EXPECT_EQ(drawable_image->paint_calls(), expected_paint_calls);\n}\n\nTEST(MockDrawableImageTest, PaintCallsWithLinearSampling) {\n  SkCanvas canvas;\n  const skity::Rect paint_bounds1 = skity::Rect::MakeWH(1.0f, 1.0f);\n  const skity::Rect paint_bounds2 = skity::Rect::MakeWH(2.0f, 2.0f);\n  const auto sampling = SkSamplingOptions(SkFilterMode::kLinear);\n  const auto expected_paint_calls = std::vector{\n      MockDrawableImage::PaintCall{canvas, paint_bounds1, false, nullptr,\n                                   sampling, nullptr,\n                                   DrawableImage::FitMode::kScaleToFill},\n      MockDrawableImage::PaintCall{canvas, paint_bounds2, true, nullptr,\n                                   sampling, nullptr,\n                                   DrawableImage::FitMode::kScaleToFill}};\n  auto drawable_image = std::make_shared<MockDrawableImage>();\n  DrawableImage::PaintContext context{\n      .canvas = &canvas,\n  };\n  drawable_image->Paint(context, paint_bounds1, false, sampling,\n                        DrawableImage::FitMode::kScaleToFill);\n  drawable_image->Paint(context, paint_bounds2, true, sampling,\n                        DrawableImage::FitMode::kScaleToFill);\n  EXPECT_EQ(drawable_image->paint_calls(), expected_paint_calls);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/mock_layer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/mock_layer.h\"\n\n#include <utility>\n\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/testing/mock_raster_cache.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n\nnamespace clay {\nnamespace testing {\n\nMockLayer::MockLayer(const SkPath& path, SkPaint paint)\n    : fake_paint_path_(path), fake_paint_(std::move(paint)) {}\n\nbool MockLayer::IsReplacing(DiffContext* context, const Layer* layer) const {\n  // Similar to PictureLayer, only return true for identical mock layers;\n  // That way ContainerLayer::DiffChildren can properly detect mock layer\n  // insertion\n  auto mock_layer = layer->as_mock_layer();\n  return mock_layer && mock_layer->fake_paint_ == fake_paint_ &&\n         mock_layer->fake_paint_path_ == fake_paint_path_;\n}\n\nvoid MockLayer::Diff(DiffContext* context, const Layer* old_layer) {\n  DiffContext::AutoSubtreeRestore subtree(context);\n  context->AddLayerBounds(\n      clay::ConvertSkRectToSkityRect(fake_paint_path_.getBounds()));\n  context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion());\n}\n\nvoid MockLayer::Preroll(PrerollContext* context) {\n  context->state_stack.fill(&parent_mutators_);\n  parent_matrix_ = context->state_stack.transform_4x4();\n  parent_cull_rect_ = context->state_stack.local_cull_rect();\n\n  set_parent_has_platform_view(context->has_platform_view);\n  set_parent_has_drawable_image_layer(context->has_drawable_image_layer);\n  set_parent_has_punch_hole_layer(context->has_punch_hole_layer);\n\n  context->has_platform_view = fake_has_platform_view();\n  context->has_drawable_image_layer = fake_has_drawable_image_layer();\n  context->has_punch_hole_layer = fake_has_punch_hole_layer();\n  set_paint_bounds(\n      clay::ConvertSkRectToSkityRect(fake_paint_path_.getBounds()));\n  if (fake_reads_surface()) {\n    context->surface_needs_readback = true;\n  }\n  if (fake_opacity_compatible()) {\n    context->renderable_state_flags = LayerStateStack::kCallerCanApplyOpacity;\n  }\n}\n\nvoid MockLayer::Paint(PaintContext& context) const {\n  FML_DCHECK(needs_painting(context));\n\n  if (expected_paint_matrix_.has_value()) {\n    skity::Matrix matrix =\n        clay::ConvertSkMatrixToSkityMatrix(context.canvas->getTotalMatrix());\n\n    EXPECT_EQ(matrix, expected_paint_matrix_.value());\n  }\n\n  SkPaint sk_paint = fake_paint_;\n  context.state_stack.fill(sk_paint);\n  context.canvas->drawPath(fake_paint_path_, sk_paint);\n}\n\nvoid MockCacheableContainerLayer::Preroll(PrerollContext* context) {\n  Layer::AutoPrerollSaveLayerState save =\n      Layer::AutoPrerollSaveLayerState::Create(context);\n  auto cache = AutoCache(layer_raster_cache_item_.get(), context,\n                         context->state_stack.transform_4x4());\n\n  ContainerLayer::Preroll(context);\n}\n\nvoid MockCacheableLayer::Preroll(PrerollContext* context) {\n  Layer::AutoPrerollSaveLayerState save =\n      Layer::AutoPrerollSaveLayerState::Create(context);\n  auto cache = AutoCache(raster_cache_item_.get(), context,\n                         context->state_stack.transform_4x4());\n\n  MockLayer::Preroll(context);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/mock_layer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_TESTING_MOCK_LAYER_H_\n#define CLAY_FLOW_TESTING_MOCK_LAYER_H_\n\n#include <functional>\n#include <memory>\n\n#include \"clay/flow/diff_context.h\"\n#include \"clay/flow/layers/cacheable_layer.h\"\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/layers/layer_raster_cache_item.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/raster_cache_item.h\"\n\nnamespace clay {\nnamespace testing {\n\n// Mock implementation of the |Layer| interface that does nothing but paint\n// the specified |path| into the canvas.  It records the |PrerollContext| and\n// |PaintContext| data passed in by its parent |Layer|, so the test can later\n// verify the data against expected values.\nclass MockLayer : public Layer {\n public:\n  explicit MockLayer(const SkPath& path, SkPaint paint = SkPaint());\n\n  static std::shared_ptr<MockLayer> Make(SkPath path,\n                                         SkPaint paint = SkPaint()) {\n    return std::make_shared<MockLayer>(path, paint);\n  }\n\n  static std::shared_ptr<MockLayer> MakeOpacityCompatible(SkPath path) {\n    auto mock_layer = std::make_shared<MockLayer>(path, SkPaint());\n    mock_layer->set_fake_opacity_compatible(true);\n    return mock_layer;\n  }\n\n  void Preroll(PrerollContext* context) override;\n  void Paint(PaintContext& context) const override;\n\n  const MutatorsStack& parent_mutators() { return parent_mutators_; }\n  const skity::Matrix& parent_matrix() { return parent_matrix_; }\n  const skity::Rect& parent_cull_rect() { return parent_cull_rect_; }\n\n  bool IsReplacing(DiffContext* context, const Layer* layer) const override;\n  void Diff(DiffContext* context, const Layer* old_layer) override;\n  const MockLayer* as_mock_layer() const override { return this; }\n\n  bool parent_has_platform_view() {\n    return mock_flags_ & kParentHasPlatformView;\n  }\n\n  bool parent_has_drawable_image_layer() {\n    return mock_flags_ & kParentHasDrawableImageLayer;\n  }\n\n  bool parent_has_punch_hole_layer() {\n    return mock_flags_ & kParentHasPunchHoleLayer;\n  }\n\n  bool fake_has_platform_view() { return mock_flags_ & kFakeHasPlatformView; }\n\n  bool fake_reads_surface() { return mock_flags_ & kFakeReadsSurface; }\n\n  bool fake_opacity_compatible() {\n    return mock_flags_ & kFakeOpacityCompatible;\n  }\n\n  bool fake_has_drawable_image_layer() {\n    return mock_flags_ & kFakeHasDrawableImageLayer;\n  }\n\n  bool fake_has_punch_hole_layer() {\n    return mock_flags_ & kFakeHasPunchHoleLayer;\n  }\n\n  MockLayer& set_parent_has_platform_view(bool flag) {\n    flag ? (mock_flags_ |= kParentHasPlatformView)\n         : (mock_flags_ &= ~(kParentHasPlatformView));\n    return *this;\n  }\n\n  MockLayer& set_parent_has_drawable_image_layer(bool flag) {\n    flag ? (mock_flags_ |= kParentHasDrawableImageLayer)\n         : (mock_flags_ &= ~(kParentHasDrawableImageLayer));\n    return *this;\n  }\n\n  MockLayer& set_parent_has_punch_hole_layer(bool flag) {\n    flag ? (mock_flags_ |= kParentHasPunchHoleLayer)\n         : (mock_flags_ &= ~(kParentHasPunchHoleLayer));\n    return *this;\n  }\n\n  MockLayer& set_fake_has_platform_view(bool flag) {\n    flag ? (mock_flags_ |= kFakeHasPlatformView)\n         : (mock_flags_ &= ~(kFakeHasPlatformView));\n    return *this;\n  }\n\n  MockLayer& set_fake_reads_surface(bool flag) {\n    flag ? (mock_flags_ |= kFakeReadsSurface)\n         : (mock_flags_ &= ~(kFakeReadsSurface));\n    return *this;\n  }\n\n  MockLayer& set_fake_opacity_compatible(bool flag) {\n    flag ? (mock_flags_ |= kFakeOpacityCompatible)\n         : (mock_flags_ &= ~(kFakeOpacityCompatible));\n    return *this;\n  }\n\n  MockLayer& set_fake_has_drawable_image_layer(bool flag) {\n    flag ? (mock_flags_ |= kFakeHasDrawableImageLayer)\n         : (mock_flags_ &= ~(kFakeHasDrawableImageLayer));\n    return *this;\n  }\n\n  MockLayer& set_fake_has_punch_hole_layer(bool flag) {\n    flag ? (mock_flags_ |= kFakeHasPunchHoleLayer)\n         : (mock_flags_ &= ~(kFakeHasPunchHoleLayer));\n    return *this;\n  }\n\n  void set_expected_paint_matrix(const skity::Matrix& matrix) {\n    expected_paint_matrix_ = matrix;\n  }\n\n private:\n  MutatorsStack parent_mutators_;\n  skity::Matrix parent_matrix_;\n  skity::Rect parent_cull_rect_ = skity::Rect::MakeEmpty();\n  SkPath fake_paint_path_;\n  SkPaint fake_paint_;\n  std::optional<skity::Matrix> expected_paint_matrix_;\n\n  static constexpr int kParentHasPlatformView = 1 << 0;\n  static constexpr int kParentHasDrawableImageLayer = 1 << 1;\n  static constexpr int kFakeHasPlatformView = 1 << 2;\n  static constexpr int kFakeReadsSurface = 1 << 3;\n  static constexpr int kFakeOpacityCompatible = 1 << 4;\n  static constexpr int kFakeHasDrawableImageLayer = 1 << 5;\n  static constexpr int kParentHasSharedImageLayer = 1 << 6;\n  static constexpr int kFakeHasSharedImageLayer = 1 << 7;\n  static constexpr int kParentHasPunchHoleLayer = 1 << 8;\n  static constexpr int kFakeHasPunchHoleLayer = 1 << 9;\n\n  int mock_flags_ = 0;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(MockLayer);\n};\n\nclass MockCacheableContainerLayer : public CacheableContainerLayer {\n public:\n  // if render more than 3 frames, try to cache itself.\n  // if less 3 frames, cache his children\n  static std::shared_ptr<MockCacheableContainerLayer> CacheLayerOrChildren() {\n    return std::make_shared<MockCacheableContainerLayer>(true);\n  }\n\n  // if render more than 3 frames, try to cache itself.\n  // if less 3 frames, cache nothing\n  static std::shared_ptr<MockCacheableContainerLayer> CacheLayerOnly() {\n    return std::make_shared<MockCacheableContainerLayer>();\n  }\n\n  void Preroll(PrerollContext* context) override;\n\n  explicit MockCacheableContainerLayer(bool cache_children = false)\n      : CacheableContainerLayer(3, cache_children) {}\n};\n\nclass MockLayerCacheableItem : public LayerRasterCacheItem {\n public:\n  using LayerRasterCacheItem::LayerRasterCacheItem;\n};\nclass MockCacheableLayer : public MockLayer {\n public:\n  explicit MockCacheableLayer(SkPath path, SkPaint paint = SkPaint(),\n                              int render_limit = 3)\n      : MockLayer(path, paint) {\n    raster_cache_item_ =\n        std::make_unique<MockLayerCacheableItem>(this, render_limit);\n  }\n\n  const LayerRasterCacheItem* raster_cache_item() const {\n    return raster_cache_item_.get();\n  }\n\n  void Preroll(PrerollContext* context) override;\n\n private:\n  std::unique_ptr<LayerRasterCacheItem> raster_cache_item_;\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_FLOW_TESTING_MOCK_LAYER_H_\n"
  },
  {
    "path": "clay/flow/testing/mock_layer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/testing/layer_test.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/testing/mock_canvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing MockLayerTest = LayerTest;\n\n#ifndef NDEBUG\nTEST_F(MockLayerTest, PaintBeforePrerollDies) {\n  SkPath path = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  auto layer = std::make_shared<MockLayer>(path, SkPaint());\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n\nTEST_F(MockLayerTest, PaintingEmptyLayerDies) {\n  auto layer = std::make_shared<MockLayer>(SkPath(), SkPaint());\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(SkPath().getBounds()));\n\n  EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()),\n                            \"needs_painting\\\\(context\\\\)\");\n}\n#endif\n\nTEST_F(MockLayerTest, SimpleParams) {\n  const SkPath path = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f);\n  const SkPaint paint = SkPaint(SkColors::kBlue);\n  const skity::Matrix start_matrix = skity::Matrix::Translate(1.0f, 2.0f);\n  const skity::Matrix scale_matrix = skity::Matrix::Scale(0.5f, 0.5f);\n  const skity::Matrix combined_matrix = start_matrix * scale_matrix;\n  const skity::Rect local_cull_rect = skity::Rect::MakeWH(5.0f, 5.0f);\n  const skity::Rect device_cull_rect = combined_matrix.MapRect(local_cull_rect);\n  const bool parent_has_platform_view = true;\n  auto layer = std::make_shared<MockLayer>(path, paint);\n\n  preroll_context()->state_stack.set_preroll_delegate(device_cull_rect,\n                                                      start_matrix);\n  auto mutator = preroll_context()->state_stack.save();\n  mutator.transform(scale_matrix);\n  preroll_context()->has_platform_view = parent_has_platform_view;\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(preroll_context()->has_platform_view, false);\n  EXPECT_EQ(layer->paint_bounds(),\n            clay::ConvertSkRectToSkityRect(path.getBounds()));\n  EXPECT_TRUE(layer->needs_painting(paint_context()));\n  EXPECT_EQ(layer->parent_mutators(), std::vector{Mutator(scale_matrix)});\n  EXPECT_EQ(layer->parent_matrix(), combined_matrix);\n  EXPECT_EQ(layer->parent_cull_rect(), local_cull_rect);\n  EXPECT_EQ(layer->parent_has_platform_view(), parent_has_platform_view);\n\n  layer->Paint(paint_context());\n  EXPECT_EQ(mock_canvas().draw_calls(),\n            std::vector({MockCanvas::DrawCall{\n                0, MockCanvas::DrawPathData{path, paint}}}));\n}\n\nTEST_F(MockLayerTest, FakePlatformView) {\n  auto layer = std::make_shared<MockLayer>(SkPath(), SkPaint());\n  layer->set_fake_has_platform_view(true);\n  EXPECT_EQ(preroll_context()->has_platform_view, false);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(preroll_context()->has_platform_view, true);\n}\n\nTEST_F(MockLayerTest, SaveLayerOnLeafNodesCanvas) {\n  auto layer = std::make_shared<MockLayer>(SkPath(), SkPaint());\n  layer->set_fake_has_platform_view(true);\n  EXPECT_EQ(preroll_context()->has_platform_view, false);\n\n  layer->Preroll(preroll_context());\n  EXPECT_EQ(preroll_context()->has_platform_view, true);\n}\n\nTEST_F(MockLayerTest, OpacityInheritance) {\n  auto path1 = SkPath().addRect({10, 10, 30, 30});\n  PrerollContext* context = preroll_context();\n\n  auto mock1 = std::make_shared<MockLayer>(path1);\n  mock1->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags, 0);\n\n  auto mock2 = MockLayer::MakeOpacityCompatible(path1);\n  mock2->Preroll(context);\n  EXPECT_EQ(context->renderable_state_flags,\n            LayerStateStack::kCallerCanApplyOpacity);\n}\n\nTEST_F(MockLayerTest, FlagGetSet) {\n  auto mock_layer = std::make_shared<MockLayer>(SkPath());\n\n  EXPECT_EQ(mock_layer->parent_has_platform_view(), false);\n  mock_layer->set_parent_has_platform_view(true);\n  EXPECT_EQ(mock_layer->parent_has_platform_view(), true);\n\n  EXPECT_EQ(mock_layer->parent_has_drawable_image_layer(), false);\n  mock_layer->set_parent_has_drawable_image_layer(true);\n  EXPECT_EQ(mock_layer->parent_has_drawable_image_layer(), true);\n\n  EXPECT_EQ(mock_layer->parent_has_punch_hole_layer(), false);\n  mock_layer->set_parent_has_punch_hole_layer(true);\n  EXPECT_EQ(mock_layer->parent_has_punch_hole_layer(), true);\n\n  EXPECT_EQ(mock_layer->fake_has_platform_view(), false);\n  mock_layer->set_fake_has_platform_view(true);\n  EXPECT_EQ(mock_layer->fake_has_platform_view(), true);\n\n  EXPECT_EQ(mock_layer->fake_reads_surface(), false);\n  mock_layer->set_fake_reads_surface(true);\n  EXPECT_EQ(mock_layer->fake_reads_surface(), true);\n\n  EXPECT_EQ(mock_layer->fake_opacity_compatible(), false);\n  mock_layer->set_fake_opacity_compatible(true);\n  EXPECT_EQ(mock_layer->fake_opacity_compatible(), true);\n\n  EXPECT_EQ(mock_layer->fake_has_drawable_image_layer(), false);\n  mock_layer->set_fake_has_drawable_image_layer(true);\n  EXPECT_EQ(mock_layer->fake_has_drawable_image_layer(), true);\n\n  EXPECT_EQ(mock_layer->fake_has_punch_hole_layer(), false);\n  mock_layer->set_fake_has_punch_hole_layer(true);\n  EXPECT_EQ(mock_layer->fake_has_punch_hole_layer(), true);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/mock_raster_cache.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/mock_raster_cache.h\"\n\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/layers/picture_raster_cache_item.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/raster_cache_item.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n#include \"third_party/skia/include/core/SkPoint.h\"\n\nnamespace clay {\nnamespace testing {\n\nMockRasterCacheResult::MockRasterCacheResult(skity::Rect device_rect)\n    : RasterCacheResult(nullptr, skity::Rect::MakeEmpty(),\n                        \"RasterCacheFlow::test\", skity::Matrix()),\n      device_rect_(device_rect) {}\n\nvoid MockRasterCache::AddMockLayer(int width, int height) {\n  skity::Matrix ctm = skity::Matrix();\n  SkPath path;\n  path.addRect(100, 100, 100 + width, 100 + height);\n  int layer_cached_threshold = 1;\n  MockCacheableLayer layer =\n      MockCacheableLayer(path, SkPaint(), layer_cached_threshold);\n  layer.Preroll(&preroll_context_);\n  layer.raster_cache_item()->TryToPrepareRasterCache(paint_context_);\n  RasterCache::Context r_context = {\n      // clang-format off\n      .gr_context         = preroll_context_.gr_context,\n      .dst_color_space    = preroll_context_.dst_color_space,\n      .matrix             = ctm,\n      .logical_rect       = layer.paint_bounds(),\n      // clang-format on\n  };\n  UpdateCacheEntry(\n      RasterCacheKeyID(layer.unique_id(), RasterCacheKeyType::kLayer),\n      r_context, [&](SkCanvas* canvas) {\n        skity::Rect cache_rect = RasterCacheUtil::GetDeviceBounds(\n            r_context.logical_rect, r_context.matrix);\n        return std::make_unique<MockRasterCacheResult>(cache_rect);\n      });\n}\n\nvoid MockRasterCache::AddMockPicture(int width, int height) {\n  FML_DCHECK(access_threshold() > 0);\n  skity::Matrix ctm = skity::Matrix();\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(\n      SkRect::MakeLTRB(0, 0, 200 + width, 200 + height));\n  SkPath path;\n  path.addRect(100, 100, 100 + width, 100 + height);\n  canvas->drawPath(path, SkPaint());\n  sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();\n\n  FixedRefreshRateStopwatch raster_time;\n  FixedRefreshRateStopwatch ui_time;\n  LayerStateStack state_stack;\n  PaintContextHolder holder =\n      GetSamplePaintContextHolder(state_stack, this, &raster_time, &ui_time);\n  holder.paint_context.dst_color_space = color_space_;\n\n  PictureRasterCacheItem display_list_item(picture.get(), skity::Vec2{0, 0},\n                                           true, false);\n  auto id = picture->uniqueID();\n  for (size_t i = 0; i < access_threshold(); i++) {\n    AutoCache(&display_list_item, &preroll_context_, ctm);\n  }\n  RasterCache::Context r_context = {\n      // clang-format off\n      .gr_context         = preroll_context_.gr_context,\n      .dst_color_space    = preroll_context_.dst_color_space,\n      .matrix             = ctm,\n      .logical_rect       = clay::ConvertSkRectToSkityRect(picture->cullRect()),\n      // clang-format on\n  };\n  UpdateCacheEntry(RasterCacheKeyID(id, RasterCacheKeyType::kPicture),\n                   r_context, [&](SkCanvas* canvas) {\n                     skity::Rect cache_rect = RasterCacheUtil::GetDeviceBounds(\n                         r_context.logical_rect, r_context.matrix);\n                     return std::make_unique<MockRasterCacheResult>(cache_rect);\n                   });\n}\n\nPrerollContextHolder GetSamplePrerollContextHolder(\n    LayerStateStack& state_stack, RasterCache* raster_cache,\n    FixedRefreshRateStopwatch* raster_time,\n    FixedRefreshRateStopwatch* ui_time) {\n  sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();\n\n  PrerollContextHolder holder = {\n      {\n          // clang-format off\n          .raster_cache                  = raster_cache,\n          .gr_context                    = nullptr,\n          .compositor_state              = nullptr,\n          .state_stack                   = state_stack,\n          .dst_color_space               = srgb.get(),\n          .surface_needs_readback        = false,\n          .raster_time                   = *raster_time,\n          .ui_time                       = *ui_time,\n          .drawable_image_registry       = nullptr,\n          .frame_device_pixel_ratio      = 1.0f,\n          .has_platform_view             = false,\n          .has_drawable_image_layer      = false,\n          .raster_cached_entries         = &raster_cache_items_,\n          // clang-format on\n      },\n      srgb};\n\n  return holder;\n}\n\nPaintContextHolder GetSamplePaintContextHolder(\n    LayerStateStack& state_stack, RasterCache* raster_cache,\n    FixedRefreshRateStopwatch* raster_time,\n    FixedRefreshRateStopwatch* ui_time) {\n  sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();\n  PaintContextHolder holder = {// clang-format off\n    {\n        .state_stack                   = state_stack,\n        .canvas                        = nullptr,\n        .gr_context                    = nullptr,\n        .dst_color_space               = srgb.get(),\n        .compositor_state              = nullptr,\n        .raster_time                   = *raster_time,\n        .ui_time                       = *ui_time,\n        .drawable_image_registry       = nullptr,\n        .raster_cache                  = raster_cache,\n        .frame_device_pixel_ratio      = 1.0f,\n    },\n                               // clang-format on\n                               srgb};\n\n  return holder;\n}\n\nbool RasterCacheItemPrerollAndTryToRasterCache(\n    PictureRasterCacheItem& display_list_item, PrerollContext& context,\n    PaintContext& paint_context, const skity::Matrix& matrix) {\n  RasterCacheItemPreroll(display_list_item, context, matrix);\n  context.raster_cache->EvictUnusedCacheEntries();\n  return RasterCacheItemTryToRasterCache(display_list_item, paint_context);\n}\n\nvoid RasterCacheItemPreroll(PictureRasterCacheItem& display_list_item,\n                            PrerollContext& context,\n                            const skity::Matrix& matrix) {\n  display_list_item.PrerollSetup(&context, matrix);\n  display_list_item.PrerollFinalize(&context, matrix);\n}\n\nbool RasterCacheItemTryToRasterCache(PictureRasterCacheItem& display_list_item,\n                                     PaintContext& paint_context) {\n  if (display_list_item.cache_state() ==\n      RasterCacheItem::CacheState::kCurrent) {\n    return display_list_item.TryToPrepareRasterCache(paint_context);\n  }\n  return false;\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/mock_raster_cache.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_TESTING_MOCK_RASTER_CACHE_H_\n#define CLAY_FLOW_TESTING_MOCK_RASTER_CACHE_H_\n\n#include <memory>\n#include <vector>\n\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/flow/raster_cache_item.h\"\n#include \"clay/flow/testing/mock_layer.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkColorSpace.h\"\n#include \"third_party/skia/include/core/SkImage.h\"\n#include \"third_party/skia/include/core/SkPicture.h\"\n\nnamespace clay {\nnamespace testing {\n\n/**\n * @brief A RasterCacheResult implementation that represents a cached Layer or\n * SkPicture without the overhead of storage.\n *\n * This implementation is used by MockRasterCache only for testing proper usage\n * of the RasterCache in layer unit tests.\n */\nclass MockRasterCacheResult : public RasterCacheResult {\n public:\n  explicit MockRasterCacheResult(skity::Rect device_rect);\n\n  void draw(SkCanvas& canvas, const SkPaint* paint = nullptr) const override{};\n\n  skity::Vec2 image_dimensions() const override {\n    return skity::Vec2(device_rect_.Width(), device_rect_.Height());\n  };\n\n  int64_t image_bytes() const override {\n    return image_dimensions().x * image_dimensions().y *\n           SkColorTypeBytesPerPixel(kBGRA_8888_SkColorType);\n  }\n\n private:\n  skity::Rect device_rect_;\n};\n\nstatic std::vector<RasterCacheItem*> raster_cache_items_;\n\n/**\n * @brief A RasterCache implementation that simulates the act of rendering a\n * Layer or SkPicture without the overhead of rasterization or pixel storage.\n * This implementation is used only for testing proper usage of the RasterCache\n * in layer unit tests.\n */\nclass MockRasterCache : public RasterCache {\n public:\n  explicit MockRasterCache(\n      size_t access_threshold = 3,\n      size_t picture_and_display_list_cache_limit_per_frame =\n          RasterCacheUtil::kDefaultPictureAndDispLayListCacheLimitPerFrame)\n      : RasterCache(access_threshold,\n                    picture_and_display_list_cache_limit_per_frame) {\n    preroll_state_stack_.set_preroll_delegate(skity::Matrix());\n    paint_state_stack_.set_delegate(&mock_canvas_);\n  }\n\n  void AddMockLayer(int width, int height);\n  void AddMockPicture(int width, int height);\n\n private:\n  LayerStateStack preroll_state_stack_;\n  LayerStateStack paint_state_stack_;\n  MockCanvas mock_canvas_;\n  SkColorSpace* color_space_ = mock_canvas_.imageInfo().colorSpace();\n  MutatorsStack mutators_stack_;\n  FixedRefreshRateStopwatch raster_time_;\n  FixedRefreshRateStopwatch ui_time_;\n  std::shared_ptr<DrawableImageRegistry> drawable_image_registry_;\n  PrerollContext preroll_context_ = {\n      // clang-format off\n      .raster_cache                  = this,\n      .gr_context                    = nullptr,\n      .compositor_state              = nullptr,\n      .state_stack                   = preroll_state_stack_,\n      .dst_color_space               = color_space_,\n      .surface_needs_readback        = false,\n      .raster_time                   = raster_time_,\n      .ui_time                       = ui_time_,\n      .drawable_image_registry       = drawable_image_registry_,\n      .frame_device_pixel_ratio      = 1.0f,\n      .has_platform_view             = false,\n      .has_drawable_image_layer      = false,\n      .raster_cached_entries         = &raster_cache_items_\n      // clang-format on\n  };\n\n  PaintContext paint_context_ = {\n      // clang-format off\n      .state_stack                   = paint_state_stack_,\n      .canvas                        = nullptr,\n      .gr_context                    = nullptr,\n      .dst_color_space               = color_space_,\n      .compositor_state              = nullptr,\n      .raster_time                   = raster_time_,\n      .ui_time                       = ui_time_,\n      .drawable_image_registry       = drawable_image_registry_,\n      .raster_cache                  = nullptr,\n      .frame_device_pixel_ratio      = 1.0f,\n      // clang-format on\n  };\n};\n\nstruct PrerollContextHolder {\n  PrerollContext preroll_context;\n  sk_sp<SkColorSpace> srgb;\n};\n\nstruct PaintContextHolder {\n  PaintContext paint_context;\n  sk_sp<SkColorSpace> srgb;\n};\n\nPrerollContextHolder GetSamplePrerollContextHolder(\n    LayerStateStack& state_stack, RasterCache* raster_cache,\n    FixedRefreshRateStopwatch* raster_time, FixedRefreshRateStopwatch* ui_time);\n\nPaintContextHolder GetSamplePaintContextHolder(\n    LayerStateStack& state_stack, RasterCache* raster_cache,\n    FixedRefreshRateStopwatch* raster_time, FixedRefreshRateStopwatch* ui_time);\n\nbool RasterCacheItemPrerollAndTryToRasterCache(\n    PictureRasterCacheItem& display_list_item, PrerollContext& context,\n    PaintContext& paint_context, const skity::Matrix& matrix);\n\nvoid RasterCacheItemPreroll(PictureRasterCacheItem& display_list_item,\n                            PrerollContext& context,\n                            const skity::Matrix& matrix);\n\nbool RasterCacheItemTryToRasterCache(PictureRasterCacheItem& display_list_item,\n                                     PaintContext& paint_context);\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_FLOW_TESTING_MOCK_RASTER_CACHE_H_\n"
  },
  {
    "path": "clay/flow/testing/mock_shared_image_backing.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/mock_shared_image_backing.h\"\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n#include \"third_party/skia/include/core/SkColorSpace.h\"\n#include \"third_party/skia/include/core/SkData.h\"\n\nnamespace clay {\nnamespace testing {\n\nclass MockFenceSync : public clay::FenceSync {\n  bool ClientWait() override { return true; }\n\n  Type GetType() const override {\n    return clay::FenceSync::Type::kClientWaitOnly;\n  }\n};\n\nclass MockSkiaImageRepresentation final : public clay::SkiaImageRepresentation {\n public:\n  explicit MockSkiaImageRepresentation(\n      fml::RefPtr<MockSharedImageBacking> backing)\n      : SkiaImageRepresentation(backing) {}\n\n  clay::ImageRepresentationType GetType() const override {\n    return clay::ImageRepresentationType::kSkia;\n  }\n\n  sk_sp<SkImage> GetSkImage() override {\n    if (sk_image_) {\n      return sk_image_;\n    }\n    MockSharedImageBacking* backing =\n        static_cast<MockSharedImageBacking*>(GetBacking());\n    SkColorInfo color_info(kRGBA_8888_SkColorType, kPremul_SkAlphaType,\n                           nullptr);\n    SkImageInfo image_info = SkImageInfo::Make(\n        SkISize::Make(backing->GetSize().x, backing->GetSize().y), color_info);\n    sk_sp<SkData> data = SkData::MakeWithoutCopy(backing->Bytes().data(),\n                                                 backing->Bytes().size());\n    sk_image_ =\n        SkImages::RasterFromData(image_info, data, backing->GetSize().x * 4);\n    backing->sk_image_id_ = sk_image_->uniqueID();\n    return sk_image_;\n  }\n\n  bool EndRead() override { return true; }\n\n  void ConsumeFence(std::unique_ptr<clay::FenceSync> fence_sync) override {\n    if (!fence_sync) {\n      return;\n    }\n    fence_sync->ClientWait();\n  }\n\n  std::unique_ptr<clay::FenceSync> ProduceFence() override {\n    return std::make_unique<MockFenceSync>();\n  }\n\n private:\n  sk_sp<SkImage> sk_image_;\n};\n\nMockSharedImageBacking::MockSharedImageBacking(skity::Vec2 size)\n    : SharedImageBacking(PixelFormat::kNative8888, size) {\n  bytes_.resize(size.x * size.y * 4);\n}\n\nMockSharedImageBacking::~MockSharedImageBacking() = default;\n\nclay::SharedImageBacking::BackingType MockSharedImageBacking::GetType() const {\n  return BackingType::kMocking;\n}\n\nclay::GraphicsMemoryHandle MockSharedImageBacking::GetGFXHandle() const {\n  return const_cast<uint8_t*>(bytes_.data());\n}\n\nfml::RefPtr<clay::SharedImageRepresentation>\nMockSharedImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig*) {\n  return fml::MakeRefCounted<MockSkiaImageRepresentation>(fml::Ref(this));\n}\n\nfml::RefPtr<clay::SkiaImageRepresentation>\nMockSharedImageBacking::CreateSkiaRepresentation(GrDirectContext* gr_context) {\n  return fml::MakeRefCounted<MockSkiaImageRepresentation>(fml::Ref(this));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/mock_shared_image_backing.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_TESTING_MOCK_SHARED_IMAGE_BACKING_H_\n#define CLAY_FLOW_TESTING_MOCK_SHARED_IMAGE_BACKING_H_\n\n#include <vector>\n\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\nnamespace testing {\n\nclass MockSharedImageBacking final : public clay::SharedImageBacking {\n public:\n  explicit MockSharedImageBacking(skity::Vec2 size);\n  ~MockSharedImageBacking() override;\n\n  BackingType GetType() const override;\n  clay::GraphicsMemoryHandle GetGFXHandle() const override;\n  fml::RefPtr<clay::SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig*) override;\n  fml::RefPtr<clay::SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n  const std::vector<uint8_t>& Bytes() const { return bytes_; }\n  uint32_t GetSkImageId() const { return sk_image_id_; }\n\n private:\n  std::vector<uint8_t> bytes_;\n  uint32_t sk_image_id_ = 0;\n\n  friend class MockSkiaImageRepresentation;\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_FLOW_TESTING_MOCK_SHARED_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/flow/testing/mock_shared_image_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/mock_shared_image_backing.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(MockSharedImageTests, QuerySize) {\n  auto shared_image =\n      fml::MakeRefCounted<MockSharedImageBacking>(skity::Vec2(100, 100));\n\n  EXPECT_EQ(shared_image->GetSize(), skity::Vec2(100, 100));\n}\n\nTEST(MockSharedImageTests, QueryTransformation) {\n  auto shared_image =\n      fml::MakeRefCounted<MockSharedImageBacking>(skity::Vec2(100, 100));\n\n  EXPECT_EQ(shared_image->GetTransformation(), skity::Matrix());\n\n  skity::Matrix y_flip_mat = skity::Matrix(1, 0, 0, 0, -1, 1, 0, 0, 1);\n  shared_image->SetTransformation(y_flip_mat);\n  EXPECT_EQ(shared_image->GetTransformation(), y_flip_mat);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/picture_complexity_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/layers/picture_complexity.h\"\n#include \"clay/flow/layers/picture_complexity_gl.h\"\n#include \"clay/flow/layers/picture_complexity_metal.h\"\n#include \"clay/flow/testing/picture_test_utils.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"clay/testing/testing.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n#include \"third_party/skia/include/core/SkRSXform.h\"\n\nnamespace clay {\nnamespace testing {\n\nnamespace {\n\nstd::vector<PictureComplexityCalculator*> Calculators() {\n  return {PictureMetalComplexityCalculator::GetInstance(),\n          PictureGLComplexityCalculator::GetInstance(),\n          PictureNaiveComplexityCalculator::GetInstance()};\n}\n\nstd::vector<PictureComplexityCalculator*> AccumulatorCalculators() {\n  return {PictureMetalComplexityCalculator::GetInstance(),\n          PictureGLComplexityCalculator::GetInstance()};\n}\n\nstd::vector<SkPoint> GetTestPoints() {\n  std::vector<SkPoint> points;\n  points.push_back(SkPoint::Make(0, 0));\n  points.push_back(SkPoint::Make(10, 0));\n  points.push_back(SkPoint::Make(10, 10));\n  points.push_back(SkPoint::Make(20, 10));\n  points.push_back(SkPoint::Make(20, 20));\n\n  return points;\n}\n\n}  // namespace\n\nTEST(DisplayListComplexity, EmptyDisplayList) {\n  auto display_list = GetSamplePicture(0);\n\n  auto calculators = Calculators();\n  for (auto calculator : calculators) {\n    ASSERT_EQ(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DisplayListCeiling) {\n  auto display_list = GetSamplePicture();\n\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    calculator->SetComplexityCeiling(10u);\n    ASSERT_EQ(calculator->Compute(display_list.get()), 10u);\n    calculator->SetComplexityCeiling(std::numeric_limits<unsigned int>::max());\n  }\n}\n\nTEST(DisplayListComplexity, NestedDisplayList) {\n  auto display_list = GetSampleNestedPicture();\n\n  auto calculators = Calculators();\n  for (auto calculator : calculators) {\n    // There's only one draw call in the \"outer\" DisplayList, which calls\n    // drawDisplayList with the \"inner\" DisplayList. To ensure we are\n    // recursing correctly into the inner DisplayList, check that we aren't\n    // returning 0 (if the function is a no-op) or 1 (as the op_count is 1)\n    ASSERT_GT(calculator->Compute(display_list.get()), 1u);\n  }\n}\n\nTEST(DisplayListComplexity, Style) {\n  SkPictureRecorder recorder_filled;\n  auto canvas_filled =\n      recorder_filled.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  SkPaint paint;\n  paint.setStyle(SkPaint::kFill_Style);\n  canvas_filled->drawRect(SkRect::MakeXYWH(10, 10, 80, 80), paint);\n  auto display_list_filled = recorder_filled.finishRecordingAsPicture();\n\n  SkPictureRecorder recorder_stroked;\n  auto canvas_stroked =\n      recorder_stroked.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  paint.setStyle(SkPaint::kStroke_Style);\n  canvas_stroked->drawRect(SkRect::MakeXYWH(10, 10, 80, 80), paint);\n  auto display_list_stroked = recorder_stroked.finishRecordingAsPicture();\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list_filled.get()),\n              calculator->Compute(display_list_stroked.get()));\n  }\n}\n\nTEST(DisplayListComplexity, DrawPath) {\n  SkPath line_path;\n  line_path.moveTo(SkPoint::Make(0, 0));\n  line_path.lineTo(SkPoint::Make(10, 10));\n  line_path.close();\n\n  SkPath quad_path;\n  quad_path.moveTo(SkPoint::Make(0, 0));\n  quad_path.quadTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20));\n  quad_path.close();\n\n  SkPath conic_path;\n  conic_path.moveTo(SkPoint::Make(0, 0));\n  conic_path.conicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20), 1.5f);\n  conic_path.close();\n\n  SkPath cubic_path;\n  cubic_path.moveTo(SkPoint::Make(0, 0));\n  cubic_path.cubicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20),\n                     SkPoint::Make(20, 20));\n  cubic_path.close();\n\n  SkPictureRecorder recorder_line;\n  auto canvas_line =\n      recorder_line.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas_line->drawPath(line_path, SkPaint());\n  auto display_list_line = recorder_line.finishRecordingAsPicture();\n\n  SkPictureRecorder recorder_quad;\n  auto canvas_quad =\n      recorder_quad.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas_quad->drawPath(quad_path, SkPaint());\n  auto display_list_quad = recorder_quad.finishRecordingAsPicture();\n\n  SkPictureRecorder recorder_conic;\n  auto canvas_conic =\n      recorder_conic.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas_conic->drawPath(conic_path, SkPaint());\n  auto display_list_conic = recorder_conic.finishRecordingAsPicture();\n\n  SkPictureRecorder recorder_cubic;\n  auto canvas_cubic =\n      recorder_cubic.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas_cubic->drawPath(cubic_path, SkPaint());\n  auto display_list_cubic = recorder_cubic.finishRecordingAsPicture();\n\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list_line.get()), 0u);\n    ASSERT_NE(calculator->Compute(display_list_quad.get()), 0u);\n    ASSERT_NE(calculator->Compute(display_list_conic.get()), 0u);\n    ASSERT_NE(calculator->Compute(display_list_cubic.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawOval) {\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawOval(SkRect::MakeXYWH(10, 10, 100, 80), SkPaint());\n  auto display_list = recorder.finishRecordingAsPicture();\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawCircle) {\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawCircle(SkPoint::Make(50, 50), 10.0f, SkPaint());\n  auto display_list = recorder.finishRecordingAsPicture();\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawRRect) {\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawRRect(\n      SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 80), 2.0f, 3.0f),\n      SkPaint());\n  auto display_list = recorder.finishRecordingAsPicture();\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawDRRect) {\n  SkRRect outer =\n      SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 80), 2.0f, 3.0f);\n  SkRRect inner =\n      SkRRect::MakeRectXY(SkRect::MakeXYWH(15, 15, 70, 70), 1.5f, 1.5f);\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawDRRect(outer, inner, SkPaint());\n  auto display_list = recorder.finishRecordingAsPicture();\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawArc) {\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawArc(SkRect::MakeXYWH(10, 10, 100, 80), 0.0f, 10.0f, true,\n                  SkPaint());\n  auto display_list = recorder.finishRecordingAsPicture();\n\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawVertices) {\n  auto points = GetTestPoints();\n  auto vertices = DlVertices::Make(DlVertexMode::kTriangles, points.size(),\n                                   points.data(), nullptr, nullptr);\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawVertices(vertices->gr_object(), SkBlendMode::kSrc, SkPaint());\n  auto display_list = recorder.finishRecordingAsPicture();\n\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawTextBlob) {\n  auto text_blob = SkTextBlob::MakeFromString(\n      \"The quick brown fox jumps over the lazy dog.\", SkFont());\n  auto dl_text_blob = DlTextBlob::Make(text_blob);\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawTextBlob(dl_text_blob->gr_text_blob(), 0.0f, 0.0f, SkPaint());\n  auto display_list = recorder.finishRecordingAsPicture();\n\n  SkPictureRecorder recorder_multiple;\n  auto canvas_multiple =\n      recorder_multiple.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas_multiple->drawTextBlob(dl_text_blob->gr_text_blob(), 0.0f, 0.0f,\n                                SkPaint());\n  canvas_multiple->drawTextBlob(dl_text_blob->gr_text_blob(), 0.0f, 0.0f,\n                                SkPaint());\n  auto display_list_multiple = recorder_multiple.finishRecordingAsPicture();\n\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n    ASSERT_GT(calculator->Compute(display_list_multiple.get()),\n              calculator->Compute(display_list.get()));\n  }\n}\n\nTEST(DisplayListComplexity, DrawPoints) {\n  auto points = GetTestPoints();\n  SkPictureRecorder recorder_lines;\n  auto canvas_lines =\n      recorder_lines.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas_lines->drawPoints(SkCanvas::kLines_PointMode, points.size(),\n                           points.data(), SkPaint());\n  auto display_list_lines = recorder_lines.finishRecordingAsPicture();\n\n  SkPictureRecorder recorder_points;\n  auto canvas_points =\n      recorder_points.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas_points->drawPoints(SkCanvas::kPoints_PointMode, points.size(),\n                            points.data(), SkPaint());\n  auto display_list_points = recorder_points.finishRecordingAsPicture();\n\n  SkPictureRecorder recorder_polygon;\n  auto canvas_polygon =\n      recorder_polygon.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas_polygon->drawPoints(SkCanvas::kPolygon_PointMode, points.size(),\n                             points.data(), SkPaint());\n  auto display_list_polygon = recorder_polygon.finishRecordingAsPicture();\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list_lines.get()), 0u);\n    ASSERT_NE(calculator->Compute(display_list_points.get()), 0u);\n    ASSERT_NE(calculator->Compute(display_list_polygon.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawImage) {\n  SkImageInfo info =\n      SkImageInfo::Make(50, 50, SkColorType::kRGBA_8888_SkColorType,\n                        SkAlphaType::kPremul_SkAlphaType);\n  SkBitmap bitmap;\n  bitmap.allocPixels(info, 0);\n  auto image = SkImages::RasterFromBitmap(bitmap);\n\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawImage(image, 0, 0);\n  auto display_list = recorder.finishRecordingAsPicture();\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawImageNine) {\n  SkImageInfo info =\n      SkImageInfo::Make(50, 50, SkColorType::kRGBA_8888_SkColorType,\n                        SkAlphaType::kPremul_SkAlphaType);\n  SkBitmap bitmap;\n  bitmap.allocPixels(info, 0);\n  auto image = SkImages::RasterFromBitmap(bitmap);\n\n  SkIRect center = SkIRect::MakeXYWH(5, 5, 20, 20);\n  SkRect dest = SkRect::MakeXYWH(0, 0, 50, 50);\n\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawImageNine(image.get(), center, dest, SkFilterMode::kNearest);\n  auto display_list = recorder.finishRecordingAsPicture();\n\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawImageRect) {\n  SkImageInfo info =\n      SkImageInfo::Make(50, 50, SkColorType::kRGBA_8888_SkColorType,\n                        SkAlphaType::kPremul_SkAlphaType);\n  SkBitmap bitmap;\n  bitmap.allocPixels(info, 0);\n  auto image = SkImages::RasterFromBitmap(bitmap);\n\n  SkRect src = SkRect::MakeXYWH(0, 0, 50, 50);\n  SkRect dest = SkRect::MakeXYWH(0, 0, 50, 50);\n\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  SkPaint paint;\n  canvas->drawImageRect(image, src, dest, {}, &paint,\n                        SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint);\n  auto display_list = recorder.finishRecordingAsPicture();\n\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\nTEST(DisplayListComplexity, DrawAtlas) {\n  SkImageInfo info =\n      SkImageInfo::Make(50, 50, SkColorType::kRGBA_8888_SkColorType,\n                        SkAlphaType::kPremul_SkAlphaType);\n  SkBitmap bitmap;\n  bitmap.allocPixels(info, 0);\n  auto image = SkImages::RasterFromBitmap(bitmap);\n\n  std::vector<SkRect> rects;\n  std::vector<SkRSXform> xforms;\n  for (int i = 0; i < 10; i++) {\n    rects.push_back(SkRect::MakeXYWH(0, 0, 10, 10));\n    xforms.push_back(SkRSXform::Make(0, 0, 0, 0));\n  }\n\n  SkPictureRecorder recorder;\n  auto canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  canvas->drawAtlas(image.get(), xforms.data(), rects.data(), nullptr, 10,\n                    SkBlendMode::kSrc, {}, nullptr, nullptr);\n  auto display_list = recorder.finishRecordingAsPicture();\n  auto calculators = AccumulatorCalculators();\n  for (auto calculator : calculators) {\n    ASSERT_NE(calculator->Compute(display_list.get()), 0u);\n  }\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/picture_test_utils.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/testing/picture_test_utils.h\"\n\n#include \"clay/gfx/style/blend_mode.h\"\n#include \"clay/gfx/testing_utils.h\"\n\nnamespace clay {\nnamespace testing {\n\nsk_sp<SkPicture> GetSamplePicture() {\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(SkRect::MakeWH(150, 100));\n  SkPaint paint;\n  paint.setColor(SK_ColorRED);\n  canvas->drawRect(SkRect::MakeXYWH(10, 10, 80, 80), paint);\n  return recorder.finishRecordingAsPicture();\n}\n\nsk_sp<SkPicture> GetSampleNestedPicture() {\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(SkRect::MakeWH(150, 100));\n  for (int y = 10; y <= 60; y += 10) {\n    for (int x = 10; x <= 60; x += 10) {\n      SkPaint paint;\n      paint.setColor(((x + y) % 20) == 10 ? SK_ColorRED : SK_ColorBLUE);\n      canvas->drawRect(SkRect::MakeXYWH(x, y, 80, 80), paint);\n    }\n  }\n  auto picture = recorder.finishRecordingAsPicture();\n  SkPictureRecorder outer_recorder;\n  canvas = outer_recorder.beginRecording(SkRect::MakeWH(150, 100));\n  canvas->drawPicture(picture);\n  return outer_recorder.finishRecordingAsPicture();\n}\n\nsk_sp<SkPicture> GetSamplePicture(int ops) {\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(SkRect::MakeWH(150, 100));\n  for (int i = 0; i < ops; i++) {\n    canvas->drawColor(SK_ColorRED, SkBlendMode::kSrc);\n  }\n  return recorder.finishRecordingAsPicture();\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/testing/picture_test_utils.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_TESTING_PICTURE_TEST_UTILS_H_\n#define CLAY_FLOW_TESTING_PICTURE_TEST_UTILS_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/testing_utils.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkPicture.h\"\n#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n#include \"third_party/skia/include/effects/SkDashPathEffect.h\"\n#include \"third_party/skia/include/effects/SkGradientShader.h\"\n#include \"third_party/skia/include/effects/SkImageFilters.h\"\n\nnamespace clay {\nnamespace testing {\n\nsk_sp<SkPicture> GetSamplePicture();\nsk_sp<SkPicture> GetSamplePicture(int ops);\nsk_sp<SkPicture> GetSampleNestedPicture();\n\nconstexpr skity::Vec2 kEndPoints[] = {\n    {0, 0},\n    {100, 100},\n};\nconst clay::Color kColors[] = {\n    clay::Color::kGreen(),\n    clay::Color::kYellow(),\n    clay::Color::kBlue(),\n};\nconstexpr float kStops[] = {\n    0.0,\n    0.5,\n    1.0,\n};\nstatic std::vector<uint32_t> color_vector(kColors, kColors + 3);\nstatic std::vector<float> stops_vector(kStops, kStops + 3);\n\n// clang-format off\nconstexpr float kRotateColorMatrix[20] = {\n    0, 1, 0, 0, 0,\n    0, 0, 1, 0, 0,\n    1, 0, 0, 0, 0,\n    0, 0, 0, 1, 0,\n};\nconstexpr float kInvertColorMatrix[20] = {\n    -1.0,    0,    0, 1.0,   0,\n       0, -1.0,    0, 1.0,   0,\n       0,    0, -1.0, 1.0,   0,\n     1.0,  1.0,  1.0, 1.0,   0,\n};\n// clang-format on\n\nconst SkScalar kTestDashes1[] = {4.0, 2.0};\nconst SkScalar kTestDashes2[] = {1.0, 1.5};\n\nconstexpr SkPoint TestPoints[] = {\n    {10, 10},\n    {20, 20},\n    {10, 20},\n    {20, 10},\n};\n#define TestPointCount sizeof(TestPoints) / (sizeof(TestPoints[0]))\n\nstatic clay::ImageSampling kNearestSampling =\n    clay::ImageSampling::kNearestNeighbor;\nstatic clay::ImageSampling kLinearSampling = clay::ImageSampling::kLinear;\n\nstatic fml::RefPtr<DlImage> MakeTestImage(int w, int h, int checker_size) {\n  sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(w, h);\n  SkCanvas* canvas = surface->getCanvas();\n  SkPaint p0, p1;\n  p0.setStyle(SkPaint::kFill_Style);\n  p0.setColor(SK_ColorGREEN);\n  p1.setStyle(SkPaint::kFill_Style);\n  p1.setColor(SK_ColorBLUE);\n  p1.setAlpha(128);\n  for (int y = 0; y < w; y += checker_size) {\n    for (int x = 0; x < h; x += checker_size) {\n      SkPaint& cellp = ((x + y) & 1) == 0 ? p0 : p1;\n      canvas->drawRect(SkRect::MakeXYWH(x, y, checker_size, checker_size),\n                       cellp);\n    }\n  }\n  return DlImage::Make(surface->makeImageSnapshot());\n}\n\nstatic auto TestImage1 = MakeTestImage(40, 40, 5);\nstatic auto TestImage2 = MakeTestImage(50, 50, 5);\n\nstatic const clay::ImageColorSource kTestSource1(TestImage1,\n                                                 clay::TileMode::kClamp,\n                                                 clay::TileMode::kMirror,\n                                                 kLinearSampling);\nstatic const std::shared_ptr<clay::ColorSource> kTestSource2 =\n    clay::ColorSource::MakeLinear(kEndPoints[0], kEndPoints[1], 3, kColors,\n                                  kStops, clay::TileMode::kMirror);\nstatic const std::shared_ptr<clay::ColorSource> kTestSource3 =\n    clay::ColorSource::MakeRadial(kEndPoints[0], 10.0, 3, kColors, kStops,\n                                  clay::TileMode::kMirror);\nstatic const std::shared_ptr<clay::ColorSource> kTestSource4 =\n    clay::ColorSource::MakeConical(kEndPoints[0], 10.0, kEndPoints[1], 200.0, 3,\n                                   kColors, kStops, clay::TileMode::kDecal);\nstatic const std::shared_ptr<clay::ColorSource> kTestSource5 =\n    clay::ColorSource::MakeSweep(kEndPoints[0], 0.0, 360.0, 3, kColors, kStops,\n                                 clay::TileMode::kDecal);\nstatic const clay::BlendColorFilter kTestBlendColorFilter1(\n    clay::Color::kRed(), clay::BlendMode::kDstATop);\nstatic const clay::BlendColorFilter kTestBlendColorFilter2(\n    clay::Color::kBlue(), clay::BlendMode::kDstATop);\nstatic const clay::BlendColorFilter kTestBlendColorFilter3(\n    clay::Color::kRed(), clay::BlendMode::kDstIn);\nstatic const clay::MatrixColorFilter kTestMatrixColorFilter1(\n    kRotateColorMatrix);\nstatic const clay::MatrixColorFilter kTestMatrixColorFilter2(\n    kInvertColorMatrix);\nstatic const clay::BlurImageFilter kTestBlurImageFilter1(\n    5.0, 5.0, clay::TileMode::kClamp);\nstatic const clay::BlurImageFilter kTestBlurImageFilter2(\n    6.0, 5.0, clay::TileMode::kClamp);\nstatic const clay::BlurImageFilter kTestBlurImageFilter3(\n    5.0, 6.0, clay::TileMode::kClamp);\nstatic const clay::BlurImageFilter kTestBlurImageFilter4(\n    5.0, 5.0, clay::TileMode::kDecal);\nstatic const clay::DilateImageFilter kTestDilateImageFilter1(5.0, 5.0);\nstatic const clay::DilateImageFilter kTestDilateImageFilter2(6.0, 5.0);\nstatic const clay::DilateImageFilter kTestDilateImageFilter3(5.0, 6.0);\nstatic const clay::ErodeImageFilter kTestErodeImageFilter1(5.0, 5.0);\nstatic const clay::ErodeImageFilter kTestErodeImageFilter2(6.0, 5.0);\nstatic const clay::ErodeImageFilter kTestErodeImageFilter3(5.0, 6.0);\nstatic const clay::MatrixImageFilter kTestMatrixImageFilter1(\n    skity::Matrix::RotateDeg(45), kNearestSampling);\nstatic const clay::MatrixImageFilter kTestMatrixImageFilter2(\n    skity::Matrix::RotateDeg(85), kNearestSampling);\nstatic const clay::MatrixImageFilter kTestMatrixImageFilter3(\n    skity::Matrix::RotateDeg(45), kLinearSampling);\nstatic const clay::ComposeImageFilter kTestComposeImageFilter1(\n    kTestBlurImageFilter1, kTestMatrixImageFilter1);\nstatic const clay::ComposeImageFilter kTestComposeImageFilter2(\n    kTestBlurImageFilter2, kTestMatrixImageFilter1);\nstatic const clay::ComposeImageFilter kTestComposeImageFilter3(\n    kTestBlurImageFilter1, kTestMatrixImageFilter2);\nstatic const clay::ColorFilterImageFilter kTestCFImageFilter1(\n    kTestBlendColorFilter1);\nstatic const clay::ColorFilterImageFilter kTestCFImageFilter2(\n    kTestBlendColorFilter2);\nstatic const std::shared_ptr<clay::PathEffect> kTestPathEffect1 =\n    clay::DashPathEffect::Make(kTestDashes1, 2, 0.0f);\nstatic const std::shared_ptr<clay::PathEffect> kTestPathEffect2 =\n    clay::DashPathEffect::Make(kTestDashes2, 2, 0.0f);\nstatic const clay::BlurMaskFilter kTestMaskFilter1(kNormal_SkBlurStyle, 3.0);\nstatic const clay::BlurMaskFilter kTestMaskFilter2(kNormal_SkBlurStyle, 5.0);\nstatic const clay::BlurMaskFilter kTestMaskFilter3(kSolid_SkBlurStyle, 3.0);\nstatic const clay::BlurMaskFilter kTestMaskFilter4(kInner_SkBlurStyle, 3.0);\nstatic const clay::BlurMaskFilter kTestMaskFilter5(kOuter_SkBlurStyle, 3.0);\nconstexpr SkRect kTestBounds = SkRect::MakeLTRB(10, 10, 50, 60);\nstatic const SkRRect kTestRRect = SkRRect::MakeRectXY(kTestBounds, 5, 5);\nstatic const SkRRect kTestRRectRect = SkRRect::MakeRect(kTestBounds);\nstatic const SkRRect kTestInnerRRect =\n    SkRRect::MakeRectXY(kTestBounds.makeInset(5, 5), 2, 2);\nstatic const SkPath kTestPathRect = SkPath::Rect(kTestBounds);\nstatic const SkPath kTestPathOval = SkPath::Oval(kTestBounds);\nstatic const SkPath kTestPath1 =\n    SkPath::Polygon({{0, 0}, {10, 10}, {10, 0}, {0, 10}}, true);\nstatic const SkPath kTestPath2 =\n    SkPath::Polygon({{0, 0}, {10, 10}, {0, 10}, {10, 0}}, true);\nstatic const SkPath kTestPath3 =\n    SkPath::Polygon({{0, 0}, {10, 10}, {10, 0}, {0, 10}}, false);\nstatic const SkMatrix kTestMatrix1 = SkMatrix::Scale(2, 2);\nstatic const SkMatrix kTestMatrix2 = SkMatrix::RotateDeg(45);\n\nstatic std::shared_ptr<const DlVertices> TestVertices1 =\n    DlVertices::Make(DlVertexMode::kTriangles,  //\n                     3, TestPoints, nullptr, kColors);\nstatic std::shared_ptr<const DlVertices> TestVertices2 =\n    DlVertices::Make(DlVertexMode::kTriangleFan,  //\n                     3, TestPoints, nullptr, kColors);\n\nstatic constexpr int kTestDivs1[] = {10, 20, 30};\nstatic constexpr int kTestDivs2[] = {15, 20, 25};\nstatic constexpr int kTestDivs3[] = {15, 25};\nstatic constexpr SkCanvas::Lattice::RectType kTestRTypes[] = {\n    SkCanvas::Lattice::RectType::kDefault,\n    SkCanvas::Lattice::RectType::kTransparent,\n    SkCanvas::Lattice::RectType::kFixedColor,\n    SkCanvas::Lattice::RectType::kDefault,\n    SkCanvas::Lattice::RectType::kTransparent,\n    SkCanvas::Lattice::RectType::kFixedColor,\n    SkCanvas::Lattice::RectType::kDefault,\n    SkCanvas::Lattice::RectType::kTransparent,\n    SkCanvas::Lattice::RectType::kFixedColor,\n};\nstatic constexpr SkColor kTestLatticeColors[] = {\n    SK_ColorBLUE, SK_ColorGREEN, SK_ColorYELLOW,\n    SK_ColorBLUE, SK_ColorGREEN, SK_ColorYELLOW,\n    SK_ColorBLUE, SK_ColorGREEN, SK_ColorYELLOW,\n};\nstatic constexpr SkIRect kTestLatticeSrcRect = {1, 1, 39, 39};\n\nstatic sk_sp<SkPicture> MakeTestPicture(int w, int h, SkColor color) {\n  SkPictureRecorder recorder;\n  SkRTreeFactory rtree_factory;\n  SkCanvas* cv = recorder.beginRecording(kTestBounds, &rtree_factory);\n  SkPaint paint;\n  paint.setColor(color);\n  paint.setStyle(SkPaint::kFill_Style);\n  cv->drawRect(SkRect::MakeWH(w, h), paint);\n  return recorder.finishRecordingAsPicture();\n}\nstatic sk_sp<SkPicture> TestPicture1 = MakeTestPicture(20, 20, SK_ColorGREEN);\nstatic sk_sp<SkPicture> TestPicture2 = MakeTestPicture(25, 25, SK_ColorBLUE);\n\nstatic sk_sp<SkTextBlob> MakeTextBlob(std::string string) {\n  return SkTextBlob::MakeFromText(string.c_str(), string.size(), SkFont(),\n                                  SkTextEncoding::kUTF8);\n}\nstatic sk_sp<SkTextBlob> TestBlob1 = MakeTextBlob(\"TestBlob1\");\nstatic sk_sp<SkTextBlob> TestBlob2 = MakeTextBlob(\"TestBlob2\");\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_FLOW_TESTING_PICTURE_TEST_UTILS_H_\n"
  },
  {
    "path": "clay/flow/view_slicer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/flow/view_slicer.h\"\n\n#include <list>\n\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nstd::unordered_map<int64_t, skity::Rect> SliceViews(\n    clay::GrCanvas* background_canvas,\n    const std::vector<int64_t>& composition_order,\n    const std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>&\n        slices,\n    const std::unordered_map<int64_t, skity::Rect>& view_rects) {\n  std::unordered_map<int64_t, skity::Rect> overlay_layers;\n\n  auto current_frame_view_count = composition_order.size();\n\n  // Restore the clip context after exiting this method since it's changed\n  // below.\n\n  clay::GrAutoCanvasRestore save_restore(background_canvas, true);\n\n  for (size_t i = 0; i < current_frame_view_count; i++) {\n    int64_t view_id = composition_order[i];\n    EmbedderViewSlice* slice = slices.at(view_id).get();\n    if (slice->canvas() == nullptr) {\n      continue;\n    }\n\n    slice->end_recording();\n\n    skity::Rect full_joined_rect = skity::Rect::MakeEmpty();\n\n    // Determinate if Flutter UI intersects with any of the previous\n    // platform views stacked by z position.\n    //\n    // This is done by querying the r-tree that holds the records for the\n    // picture recorder corresponding to the flow layers added after a platform\n    // view layer.\n    for (int j = i; j >= 0; j--) {\n      int64_t current_view_id = composition_order[j];\n      auto maybe_rect = view_rects.find(current_view_id);\n      FML_DCHECK(maybe_rect != view_rects.end());\n      if (maybe_rect == view_rects.end()) {\n        continue;\n      }\n\n      skity::Rect current_view_rect = maybe_rect->second;\n      skity::Rect rounded_in_platform_view_rect = current_view_rect;\n      rounded_in_platform_view_rect.RoundIn();\n\n      // Each rect corresponds to a native view that renders Flutter UI.\n      std::list<skity::Rect> intersection_rects =\n          slice->searchNonOverlappingDrawnRects(current_view_rect);\n\n      // Ignore intersections of single width/height on the edge of the platform\n      // view.\n      // This is to address the following performance issue when interleaving\n      // adjacent platform views and layers: Since we `roundOut` both platform\n      // view rects and the layer rects, as long as the coordinate is\n      // fractional, there will be an intersection of a single pixel width (or\n      // height) after rounding out, even if they do not intersect before\n      // rounding out. We have to round out both platform view rect and the\n      // layer rect. Rounding in platform view rect will result in missing pixel\n      // on the intersection edge. Rounding in layer rect will result in missing\n      // pixel on the edge of the layer on top of the platform view.\n      for (auto it = intersection_rects.begin(); it != intersection_rects.end();\n           /*no-op*/) {\n        // If intersection_rect does not intersect with the *rounded in*\n        // platform view rect, then the intersection must be a single pixel\n        // width (or height) on edge.\n        if (!skity::Rect::Intersect(*it, rounded_in_platform_view_rect)) {\n          it = intersection_rects.erase(it);\n        } else {\n          ++it;\n        }\n      }\n\n      // Limit the number of native views, so it doesn't grow forever.\n      //\n      // In this case, the rects are merged into a single one that is the union\n      // of all the rects.\n      skity::Rect partial_joined_rect = skity::Rect::MakeEmpty();\n      for (const skity::Rect& rect : intersection_rects) {\n        partial_joined_rect.Join(rect);\n      }\n\n      current_view_rect.RoundOut();\n      // Get the intersection rect with the `current_view_rect`,\n      if (partial_joined_rect.Intersect(current_view_rect)) {\n        // Join the `partial_joined_rect` into `full_joined_rect` to get the\n        // rect above the current `slice`, only if it intersects the indicated\n        // view. This should always be the case because we just deleted any\n        // rects that don't intersect the \"rounded-in\" view, so they must\n        // all intersect the \"rounded-out\" view (or the partial join could\n        // be empty in which case this would be a NOP). Either way, the\n        // penalty for not checking the return value of the intersect method\n        // would be to join a non-overlapping rectangle into the overlay\n        // bounds - if the above implementation ever changes - so we check it.\n        full_joined_rect.Join(partial_joined_rect);\n      }\n    }\n\n    if (!full_joined_rect.IsEmpty()) {\n      // Subpixels in the platform may not align with the canvas subpixels.\n      //\n      // To workaround it, round the floating point bounds and make the rect\n      // slightly larger.\n      //\n      // For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, 5}.\n      full_joined_rect.RoundOut();\n      overlay_layers.insert({view_id, full_joined_rect});\n\n      // Clip the background canvas, so it doesn't contain any of the pixels\n      // drawn on the overlay layer.\n      CANVAS_CLIP_RECT_WITH_OP(background_canvas, full_joined_rect,\n                               clay::GrClipOp::kDifference);\n    }\n    slice->render_into(background_canvas);\n  }\n\n  // Manually trigger the DlAutoCanvasRestore before we submit the frame\n  save_restore.restore();\n\n  return overlay_layers;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/flow/view_slicer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FLOW_VIEW_SLICER_H_\n#define CLAY_FLOW_VIEW_SLICER_H_\n\n#include <memory>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/flow/embedded_views.h\"\n\nnamespace clay {\n\n/// @brief Compute the required overlay layers and clip the view slices\n///        according to the size and position of the platform views.\nstd::unordered_map<int64_t, skity::Rect> SliceViews(\n    clay::GrCanvas* background_canvas,\n    const std::vector<int64_t>& composition_order,\n    const std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>&\n        slices,\n    const std::unordered_map<int64_t, skity::Rect>& view_rects);\n\n}  // namespace clay\n\n#endif  // CLAY_FLOW_VIEW_SLICER_H_\n"
  },
  {
    "path": "clay/flow/view_slicer_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <unordered_map>\n\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/flow/view_slicer.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nnamespace {\nvoid AddSliceOfSize(\n    std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>& slices,\n    int64_t id, skity::Rect rect) {\n  slices[id] = std::make_unique<SkPictureEmbedderViewSlice>(rect);\n  SkPaint paint;\n  paint.setColor(SkColors::kBlack);\n  slices[id]->canvas()->drawRect(clay::ConvertSkityRectToSkRect(rect), paint);\n}\n}  // namespace\n\nTEST(ViewSlicerTest, CanSlicerNonOverlappingViews) {\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  std::vector<int64_t> composition_order = {1};\n  std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;\n  AddSliceOfSize(slices, 1, skity::Rect::MakeLTRB(99, 99, 100, 100));\n\n  std::unordered_map<int64_t, skity::Rect> view_rects = {\n      {1, skity::Rect::MakeLTRB(50, 50, 60, 60)}};\n\n  auto computed_overlays =\n      SliceViews(canvas, composition_order, slices, view_rects);\n\n  EXPECT_TRUE(computed_overlays.empty());\n}\n\nTEST(ViewSlicerTest, IgnoresFractionalOverlaps) {\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  std::vector<int64_t> composition_order = {1};\n  std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;\n  AddSliceOfSize(slices, 1, skity::Rect::MakeLTRB(0, 0, 50.49, 50.49));\n\n  std::unordered_map<int64_t, skity::Rect> view_rects = {\n      {1, skity::Rect::MakeLTRB(50.5, 50.5, 100, 100)}};\n\n  auto computed_overlays =\n      SliceViews(canvas, composition_order, slices, view_rects);\n\n  EXPECT_TRUE(computed_overlays.empty());\n}\n\nTEST(ViewSlicerTest, ComputesOverlapWith1PV) {\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  std::vector<int64_t> composition_order = {1};\n  std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;\n  AddSliceOfSize(slices, 1, skity::Rect::MakeLTRB(0, 0, 50, 50));\n\n  std::unordered_map<int64_t, skity::Rect> view_rects = {\n      {1, skity::Rect::MakeLTRB(0, 0, 100, 100)}};\n\n  auto computed_overlays =\n      SliceViews(canvas, composition_order, slices, view_rects);\n\n  EXPECT_EQ(computed_overlays.size(), 1u);\n  auto overlay = computed_overlays.find(1);\n  ASSERT_NE(overlay, computed_overlays.end());\n\n  EXPECT_EQ(overlay->second, skity::Rect::MakeLTRB(0, 0, 50, 50));\n}\n\nTEST(ViewSlicerTest, ComputesOverlapWith2PV) {\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  std::vector<int64_t> composition_order = {1, 2};\n  std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;\n  AddSliceOfSize(slices, 1, skity::Rect::MakeLTRB(0, 0, 50, 50));\n  AddSliceOfSize(slices, 2, skity::Rect::MakeLTRB(50, 50, 100, 100));\n\n  std::unordered_map<int64_t, skity::Rect> view_rects = {\n      {1, skity::Rect::MakeLTRB(0, 0, 50, 50)},      //\n      {2, skity::Rect::MakeLTRB(50, 50, 100, 100)},  //\n  };\n\n  auto computed_overlays =\n      SliceViews(canvas, composition_order, slices, view_rects);\n\n  EXPECT_EQ(computed_overlays.size(), 2u);\n\n  auto overlay = computed_overlays.find(1);\n  ASSERT_NE(overlay, computed_overlays.end());\n\n  EXPECT_EQ(overlay->second, skity::Rect::MakeLTRB(0, 0, 50, 50));\n\n  overlay = computed_overlays.find(2);\n  ASSERT_NE(overlay, computed_overlays.end());\n  EXPECT_EQ(overlay->second, skity::Rect::MakeLTRB(50, 50, 100, 100));\n}\n\nTEST(ViewSlicerTest, OverlappingTwoPVs) {\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(SkRect::MakeLTRB(0, 0, 100, 100));\n  std::vector<int64_t> composition_order = {1, 2};\n  std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;\n  // This embeded view overlaps both platform views:\n  //\n  //   [  A  [   ]]\n  //   [_____[ C ]]\n  //   [  B  [   ]]\n  //   [          ]\n  AddSliceOfSize(slices, 1, skity::Rect::MakeLTRB(0, 0, 0, 0));\n  AddSliceOfSize(slices, 2, skity::Rect::MakeLTRB(0, 0, 100, 100));\n\n  std::unordered_map<int64_t, skity::Rect> view_rects = {\n      {1, skity::Rect::MakeLTRB(0, 0, 50, 50)},      //\n      {2, skity::Rect::MakeLTRB(50, 50, 100, 100)},  //\n  };\n\n  auto computed_overlays =\n      SliceViews(canvas, composition_order, slices, view_rects);\n\n  EXPECT_EQ(computed_overlays.size(), 1u);\n\n  auto overlay = computed_overlays.find(2);\n  ASSERT_NE(overlay, computed_overlays.end());\n\n  // We create a single overlay for both overlapping sections.\n  EXPECT_EQ(overlay->second, skity::Rect::MakeLTRB(0, 0, 100, 100));\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/fml/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"fml.gni\")\n\nconfig(\"fml_config\") {\n  # to be deleted if clay opensourced\n  include_dirs = [ \"../../\" ]\n}\n\nsource_set(\"fml\") {\n  sources = fml_common_shared_sources\n\n  if (is_mac || is_ios || is_tvos) {\n    sources += fml_darwin_shared_sources\n  } else if (is_win) {\n    sources += fml_windows_shared_sources\n  }\n\n  sources += fml_backtrace_sources\n\n  public_configs = [ \":fml_config\" ]\n  public_deps = [\n    \":file_util\",\n    \":fml_log\",\n    \":string_conversion\",\n  ]\n  deps = []\n\n  libs = []\n\n  if (is_ios || is_mac || is_tvos) {\n    fml_cflags_objc_arc = [\n      \"-Werror=overriding-method-mismatch\",\n      \"-Werror=undeclared-selector\",\n      \"-fobjc-arc\",\n    ]\n    fml_cflags_objcc_arc = fml_cflags_objc_arc\n\n    cflags_objc = fml_cflags_objc_arc\n    cflags_objcc = fml_cflags_objcc_arc\n\n    frameworks = [ \"Foundation.framework\" ]\n  }\n\n  if (is_android) {\n    libs += [ \"android\" ]\n  }\n\n  if (is_win) {\n    # For wstring_conversion. See issue #50053.\n    defines = [ \"_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING\" ]\n  }\n}\n\nsource_set(\"command_line\") {\n  sources = [\n    \"command_line.cc\",\n    \"command_line.h\",\n  ]\n  public_configs = [ \":fml_config\" ]\n}\n\nsource_set(\"string_conversion\") {\n  sources = fml_string_conversion_sources\n\n  # Current versions of libcxx have deprecated some of the UTF-16 string\n  # conversion APIs.\n  defines = [ \"_LIBCPP_DISABLE_DEPRECATION_WARNINGS\" ]\n\n  if (is_win) {\n    # TODO(cbracken): https://github.com/flutter/flutter/issues/50053\n    defines += [ \"_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING\" ]\n  }\n\n  public_configs = [ \":fml_config\" ]\n}\n\nsource_set(\"icu_util\") {\n  sources = fml_icu_util_sources\n\n  public_deps = [\n    \":file_util\",\n\n    # These need to be in sync with the Fuchsia buildroot.\n    \"//third_party/icu\",\n  ]\n  public_configs = [\n    \":fml_config\",\n    \"//third_party/icu:icu_config\",\n  ]\n}\n\nsource_set(\"fml_log\") {\n  sources = fml_log_sources\n  public_configs = [ \":fml_config\" ]\n}\n\nsource_set(\"file_util\") {\n  sources = fml_file_sources\n  public_deps = [ \":native_library_util\" ]\n  public_configs = [ \":fml_config\" ]\n  if (is_ios || is_mac || is_tvos) {\n    fml_cflags_objc = [\n      \"-Werror=overriding-method-mismatch\",\n      \"-Werror=undeclared-selector\",\n    ]\n    fml_cflags_objcc = fml_cflags_objc\n\n    cflags_objc = fml_cflags_objc\n    cflags_objcc = fml_cflags_objcc\n\n    frameworks = [ \"Foundation.framework\" ]\n  }\n  if (is_win) {\n    public_deps += [ \"../../base/src:base_group\" ]\n  }\n}\n\nsource_set(\"native_library_util\") {\n  sources = fml_native_library_sources\n  public_configs = [ \":fml_config\" ]\n}\n"
  },
  {
    "path": "clay/fml/LICENSE",
    "content": "Copyright 2021 The Lynx Authors. All rights reserved.\n\nCopyright 2013 The Flutter Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n    * Neither the name of Google Inc. nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "clay/fml/ascii_trie.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/ascii_trie.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace fml {\ntypedef AsciiTrie::TrieNode TrieNode;\ntypedef AsciiTrie::TrieNodePtr TrieNodePtr;\n\nnamespace {\nvoid Add(TrieNodePtr* trie, const char* entry) {\n  int ch = entry[0];\n  FML_DCHECK(ch < AsciiTrie::kMaxAsciiValue);\n  if (ch != 0) {\n    if (!*trie) {\n      *trie = std::make_unique<TrieNode>();\n    }\n    Add(&(*trie)->children[ch], entry + 1);\n  }\n}\n\nTrieNodePtr MakeTrie(const std::vector<std::string>& entries) {\n  TrieNodePtr result;\n  for (const std::string& entry : entries) {\n    Add(&result, entry.c_str());\n  }\n  return result;\n}\n}  // namespace\n\nvoid AsciiTrie::Fill(const std::vector<std::string>& entries) {\n  node_ = MakeTrie(entries);\n}\n\nbool AsciiTrie::Query(TrieNode* trie, const char* query) {\n  FML_DCHECK(trie);\n  const char* char_position = query;\n  TrieNode* trie_position = trie;\n  TrieNode* child = nullptr;\n  int ch;\n  while ((ch = *char_position) && (child = trie_position->children[ch].get())) {\n    char_position++;\n    trie_position = child;\n  }\n  return !child && trie_position != trie;\n}\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/ascii_trie.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_ASCII_TRIE_H_\n#define CLAY_FML_ASCII_TRIE_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace fml {\n\n/// A trie for looking for ASCII prefixes.\nclass AsciiTrie {\n public:\n  struct TrieNode;\n  typedef std::unique_ptr<TrieNode> TrieNodePtr;\n  /// The max Ascii value.\n  static const int kMaxAsciiValue = 128;\n\n  /// Clear and insert all the entries into the trie.\n  void Fill(const std::vector<std::string>& entries);\n\n  /// Returns true if \\p argument is prefixed by the contents of the trie.\n  inline bool Query(const char* argument) {\n    return !node_ || Query(node_.get(), argument);\n  }\n\n  struct TrieNode {\n    TrieNodePtr children[kMaxAsciiValue];\n  };\n\n private:\n  static bool Query(TrieNode* trie, const char* query);\n  TrieNodePtr node_;\n};\n}  // namespace fml\n\n#endif  // CLAY_FML_ASCII_TRIE_H_\n"
  },
  {
    "path": "clay/fml/ascii_trie_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/ascii_trie.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nusing fml::AsciiTrie;\n\nTEST(AsciiTableTest, Simple) {\n  AsciiTrie trie;\n  auto entries = std::vector<std::string>{\"foo\"};\n  trie.Fill(entries);\n  ASSERT_TRUE(trie.Query(\"foobar\"));\n  ASSERT_FALSE(trie.Query(\"google\"));\n}\n\nTEST(AsciiTableTest, ExactMatch) {\n  AsciiTrie trie;\n  auto entries = std::vector<std::string>{\"foo\"};\n  trie.Fill(entries);\n  ASSERT_TRUE(trie.Query(\"foo\"));\n}\n\nTEST(AsciiTableTest, Empty) {\n  AsciiTrie trie;\n  ASSERT_TRUE(trie.Query(\"foo\"));\n}\n\nTEST(AsciiTableTest, MultipleEntries) {\n  AsciiTrie trie;\n  auto entries = std::vector<std::string>{\"foo\", \"bar\"};\n  trie.Fill(entries);\n  ASSERT_TRUE(trie.Query(\"foozzz\"));\n  ASSERT_TRUE(trie.Query(\"barzzz\"));\n}\n"
  },
  {
    "path": "clay/fml/atomic_sequence_num.h",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_ATOMIC_SEQUENCE_NUM_H_\n#define CLAY_FML_ATOMIC_SEQUENCE_NUM_H_\n\n#include <atomic>\n\nnamespace fml {\n\n// AtomicSequenceNumber is a thread safe increasing sequence number generator.\n// Its constructor doesn't emit a static initializer, so it's safe to use as a\n// global variable or static member.\nclass AtomicSequenceNumber {\n public:\n  constexpr AtomicSequenceNumber() = default;\n  AtomicSequenceNumber(const AtomicSequenceNumber&) = delete;\n  AtomicSequenceNumber& operator=(const AtomicSequenceNumber&) = delete;\n\n  // Returns an increasing sequence number starts from 0 for each call.\n  // This function can be called from any thread without data race.\n  inline int GetNext() { return seq_.fetch_add(1, std::memory_order_relaxed); }\n\n private:\n  std::atomic_int seq_{1000};\n};\n\n}  // namespace fml\n\n#endif  // CLAY_FML_ATOMIC_SEQUENCE_NUM_H_\n"
  },
  {
    "path": "clay/fml/backtrace.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/backtrace.h\"\n\n#include <cxxabi.h>\n#include <dlfcn.h>\n#include <execinfo.h>\n\n#include <__config>\n#include <csignal>\n#include <sstream>\n\n#if OS_WIN\n#include <crtdbg.h>\n#include <debugapi.h>\n#endif\n\n#include <cstdlib>\n\n#include \"clay/fml/logging.h\"\n\nnamespace fml {\n\nstatic const char* kUnknownFrameName = \"Unknown\";\n\n#if !OS_IOS\nstatic std::string DemangleSymbolName(const std::string& mangled) {\n  if (std::strcmp(mangled.c_str(), kUnknownFrameName) == 0) {\n    return kUnknownFrameName;\n  }\n\n  int status = 0;\n  size_t length = 0;\n  char* demangled = __cxxabiv1::__cxa_demangle(\n      mangled.data(),  // mangled name\n      nullptr,         // output buffer (malloc-ed if nullptr)\n      &length,         // demangled length\n      &status);\n\n  if (demangled == nullptr || status != 0) {\n    return mangled;\n  }\n\n  auto demangled_string = std::string{demangled, length};\n  free(demangled);\n  return demangled_string;\n}\n#endif\n\nstatic std::string GetSymbolName(void* symbol) {\n#if !OS_IOS\n  Dl_info info = {};\n\n  if (::dladdr(symbol, &info) == 0) {\n    return kUnknownFrameName;\n  }\n  if (info.dli_sname == nullptr) {\n    return kUnknownFrameName;\n  }\n\n  return DemangleSymbolName({info.dli_sname});\n#endif\n  return kUnknownFrameName;\n}\n\nstatic int Backtrace(void** symbols, int size) {\n#if OS_WIN\n  return CaptureStackBackTrace(0, size, symbols, NULL);\n#else\n  return ::backtrace(symbols, size);\n#endif  // OS_WIN\n}\n\nstd::string BacktraceHere(size_t offset) {\n  constexpr size_t kMaxFrames = 256;\n  void* symbols[kMaxFrames];\n  const auto available_frames = Backtrace(symbols, kMaxFrames);\n  if (available_frames <= 0) {\n    return \"\";\n  }\n\n  // Exclude here.\n  offset += 2;\n\n  std::stringstream stream;\n  for (int i = offset; i < available_frames; ++i) {\n    stream << \"Frame \" << i - offset << \": \" << symbols[i] << \" \"\n           << GetSymbolName(symbols[i]) << std::endl;\n  }\n  return stream.str();\n}\n\nstatic size_t kKnownSignalHandlers[] = {\n    SIGABRT,  // abort program\n    SIGFPE,   // floating-point exception\n    SIGTERM,  // software termination signal\n    SIGSEGV,  // segmentation violation\n#if !OS_WIN\n    SIGBUS,   // bus error\n    SIGSYS,   // non-existent system call invoked\n    SIGPIPE,  // write on a pipe with no reader\n    SIGALRM,  // real-time timer expired\n#endif        // !OS_WIN\n};\n\nstatic std::string SignalNameToString(int signal) {\n  switch (signal) {\n    case SIGABRT:\n      return \"SIGABRT\";\n    case SIGFPE:\n      return \"SIGFPE\";\n    case SIGSEGV:\n      return \"SIGSEGV\";\n    case SIGTERM:\n      return \"SIGTERM\";\n#if !OS_WIN\n    case SIGBUS:\n      return \"SIGBUS\";\n    case SIGSYS:\n      return \"SIGSYS\";\n    case SIGPIPE:\n      return \"SIGPIPE\";\n    case SIGALRM:\n      return \"SIGALRM\";\n#endif  // !OS_WIN\n  }\n  return std::to_string(signal);\n}\n\nstatic void ToggleSignalHandlers(bool set);\n\nstatic void SignalHandler(int signal) {\n  // We are a crash signal handler. This can only happen once. Since we don't\n  // want to catch crashes while we are generating the crash reports, disable\n  // all set signal handlers to their default values before reporting the crash\n  // and re-raising the signal.\n  ToggleSignalHandlers(false);\n\n  FML_LOG(ERROR) << \"Caught signal \" << SignalNameToString(signal)\n                 << \" during program execution.\" << std::endl\n                 << BacktraceHere();\n\n  ::raise(signal);\n}\n\nstatic void ToggleSignalHandlers(bool set) {\n  for (size_t i = 0; i < sizeof(kKnownSignalHandlers) / sizeof(size_t); ++i) {\n    auto signal_name = kKnownSignalHandlers[i];\n    auto handler = set ? &SignalHandler : SIG_DFL;\n\n    if (::signal(signal_name, handler) == SIG_ERR) {\n      FML_LOG(ERROR) << \"Could not attach signal handler for \" << signal_name;\n    }\n  }\n}\n\nvoid InstallCrashHandler() {\n#if OS_WIN\n  if (!IsDebuggerPresent()) {\n    _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);\n    _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);\n  }\n#endif\n  ToggleSignalHandlers(true);\n}\n\nbool IsCrashHandlingSupported() { return true; }\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/backtrace.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_BACKTRACE_H_\n#define CLAY_FML_BACKTRACE_H_\n\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace fml {\n\n// Retrieve the backtrace, for debugging.\n//\n// If the |offset| is 0, the backtrace is included caller function.\nstd::string BacktraceHere(size_t offset = 0);\n\nvoid InstallCrashHandler();\n\nbool IsCrashHandlingSupported();\n\n}  // namespace fml\n\n#endif  // CLAY_FML_BACKTRACE_H_\n"
  },
  {
    "path": "clay/fml/backtrace_android.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <dlfcn.h>\n#include <unwind.h>\n\n#include <algorithm>\n#include <cstring>\n#include <memory>\n#include <sstream>\n#include <string>\n\n#include \"base/include/string/string_utils.h\"\n#include \"clay/fml/backtrace.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/platform/linux/proc_maps_linux.h\"\n\n#ifdef __LP64__\n#define FMT_ADDR \"0x%016lx\"\n#else\n#define FMT_ADDR \"0x%08x\"\n#endif\n\nnamespace fml {\n\nnamespace {\n\ntemplate <typename... Args>\nstd::string StringFormat(const std::string& format, Args... args) {\n  int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) +\n               1;  // Extra space for '\\0'\n  if (size_s <= 0) {\n    return \"\";\n  }\n  auto size = static_cast<size_t>(size_s);\n  auto buf = std::make_unique<char[]>(size);\n  std::snprintf(buf.get(), size, format.c_str(), args...);\n  return std::string(buf.get(),\n                     buf.get() + size - 1);  // We don't want the '\\0' inside\n}\n\nstruct StackCrawlState {\n  StackCrawlState(uintptr_t* frames, size_t max_depth)\n      : frames(frames),\n        frame_count(0),\n        max_depth(max_depth),\n        have_skipped_self(false) {}\n\n  uintptr_t* frames;\n  size_t frame_count;\n  size_t max_depth;\n  bool have_skipped_self;\n};\n\n_Unwind_Reason_Code TraceStackFrame(_Unwind_Context* context, void* arg) {\n  StackCrawlState* state = static_cast<StackCrawlState*>(arg);\n  uintptr_t ip = _Unwind_GetIP(context);\n\n  // The first stack frame is this function itself.  Skip it.\n  if (ip != 0 && !state->have_skipped_self) {\n    state->have_skipped_self = true;\n    return _URC_NO_REASON;\n  }\n\n  state->frames[state->frame_count++] = ip;\n  if (state->frame_count >= state->max_depth) {\n    return _URC_END_OF_STACK;\n  }\n  return _URC_NO_REASON;\n}\n\nsize_t CollectStackTrace(void** trace, size_t count) {\n  StackCrawlState state(reinterpret_cast<uintptr_t*>(trace), count);\n  _Unwind_Backtrace(&TraceStackFrame, &state);\n  return state.frame_count;\n}\n\nvoid DumpBacktrace(std::ostream* os, void** trace, size_t count) {\n  std::string proc_maps;\n  std::vector<MappedMemoryRegion> regions;\n\n  if (!ReadProcMaps(&proc_maps)) {\n    FML_LOG(ERROR) << \"Failed to read /proc/self/maps\";\n  } else if (!ParseProcMaps(proc_maps, &regions)) {\n    FML_LOG(ERROR) << \"Failed to parse /proc/self/maps\";\n  }\n\n  *os << \"\\n\";\n  for (size_t i = 0; i < count; ++i) {\n    // Subtract one as return address of function may be in the next\n    // function when a function is annotated as noreturn.\n    uintptr_t address = reinterpret_cast<uintptr_t>(trace[i]) - 1;\n\n    std::vector<MappedMemoryRegion>::iterator iter = regions.begin();\n    while (iter != regions.end()) {\n      if (address >= iter->start && address < iter->end &&\n          !iter->path.empty()) {\n        break;\n      }\n      ++iter;\n    }\n\n    const char* symbol = \"\";\n    Dl_info info;\n    if (dladdr(reinterpret_cast<const void*>(address), &info) &&\n        info.dli_sname) {\n      symbol = info.dli_sname;\n    }\n\n    // Adjust absolute address to be an offset within the mapped region, to\n    // match the format dumped by Android's crash output.\n    unsigned long relative_address = reinterpret_cast<char*>(address) -\n                                     reinterpret_cast<char*>(info.dli_fbase);\n    // The format below intentionally matches that of Android's debuggerd\n    // output. This simplifies decoding by scripts such as stack.py.\n    *os << StringFormat(\"#%02zd pc \", i);\n    *os << StringFormat(FMT_ADDR \" \", relative_address);\n\n    if (iter != regions.end()) {\n      *os << StringFormat(\"%s\", iter->path.c_str());\n      if (lynx::base::EndsWith(iter->path, \".apk\")) {\n        *os << StringFormat(\" (offset 0x%llx)\", iter->offset);\n      } else if (info.dli_sname) {\n        *os << StringFormat(\" (%s)\", symbol);\n      }\n    } else {\n      *os << \"<Unknown>\";\n    }\n\n    *os << \"\\n\";\n  }\n}\n\n}  // namespace\n\nstd::string BacktraceHere(size_t offset) {\n  static constexpr size_t kMaxTraces = 62;\n  void* trace[kMaxTraces];\n  memset(trace, 0, kMaxTraces * sizeof(trace[0]));\n\n  // The number of valid frames in |trace|\n  size_t count = CollectStackTrace(trace, std::min(offset, kMaxTraces));\n  std::stringstream stream;\n  DumpBacktrace(&stream, trace, count);\n  return stream.str();\n}\n\nvoid InstallCrashHandler() {\n  // Not supported.\n}\n\nbool IsCrashHandlingSupported() { return false; }\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/backtrace_stub.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/backtrace.h\"\n\nnamespace fml {\n\nstatic std::string kKUnknownFrameName = \"Unknown\";\n\nstd::string BacktraceHere(size_t offset) { return \"\"; }\n\nvoid InstallCrashHandler() {\n  // Not supported.\n}\n\nbool IsCrashHandlingSupported() { return false; }\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/backtrace_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/backtrace.h\"\n#include \"clay/fml/logging.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace fml {\nnamespace testing {\n\nTEST(BacktraceTest, CanGatherBacktrace) {\n  if (!IsCrashHandlingSupported()) {\n    GTEST_SKIP();\n    return;\n  }\n  {\n    auto trace = BacktraceHere(0);\n    ASSERT_GT(trace.size(), 0u);\n    ASSERT_NE(trace.find(\"Frame 0\"), std::string::npos);\n  }\n\n  {\n    auto trace = BacktraceHere(1);\n    ASSERT_GT(trace.size(), 0u);\n    ASSERT_NE(trace.find(\"Frame 0\"), std::string::npos);\n  }\n\n  {\n    auto trace = BacktraceHere(2);\n    ASSERT_GT(trace.size(), 0u);\n    ASSERT_NE(trace.find(\"Frame 0\"), std::string::npos);\n  }\n}\n\n}  // namespace testing\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/base32.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/base32.h\"\n\n#include <limits>\n#include <string>\n\nnamespace fml {\n\nstatic constexpr char kEncoding[] = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567\";\n\nstd::pair<bool, std::string> Base32Encode(std::string_view input) {\n  if (input.empty()) {\n    return {true, \"\"};\n  }\n\n  if (input.size() > std::numeric_limits<size_t>::max() / 8) {\n    return {false, \"\"};\n  }\n\n  std::string output;\n  const size_t encoded_length = (input.size() * 8 + 4) / 5;\n  output.reserve(encoded_length);\n\n  Base32EncodeConverter converter;\n  converter.Append(static_cast<uint8_t>(input[0]));\n  size_t next_byte_index = 1;\n\n  while (converter.CanExtract()) {\n    output.push_back(kEncoding[converter.Extract()]);\n    if (converter.CanAppend() && next_byte_index < input.size()) {\n      converter.Append(static_cast<uint8_t>(input[next_byte_index++]));\n    }\n  }\n\n  if (converter.BitsAvailable() > 0) {\n    output.push_back(kEncoding[converter.Peek()]);\n  }\n\n  return {true, output};\n}\n\nstatic constexpr signed char kDecodeMap[] = {\n    // starting from ASCII 50 '2'\n    26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1,\n    -1, 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12,\n    13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};\n\nstatic constexpr int kDecodeMapSize =\n    sizeof(kDecodeMap) / sizeof(kDecodeMap[0]);\n\nstd::pair<bool, std::string> Base32Decode(const std::string& input) {\n  std::string result;\n  Base32DecodeConverter converter;\n  for (char c : input) {\n    int map_index = c - '2';\n    if (map_index < 0 || map_index >= kDecodeMapSize ||\n        kDecodeMap[map_index] == -1) {\n      return {false, result};\n    }\n    converter.Append(kDecodeMap[map_index]);\n    if (converter.CanExtract()) {\n      result.push_back(converter.Extract());\n    }\n  }\n  if (converter.Peek() != 0) {\n    // The padding should always be zero. Return false if not.\n    return {false, result};\n  }\n  return {true, result};\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/base32.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_BASE32_H_\n#define CLAY_FML_BASE32_H_\n\n#include <string>\n#include <string_view>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace fml {\n\ntemplate <int from_length, int to_length, int buffer_length>\nclass BitConverter {\n public:\n  void Append(int bits) {\n    FML_DCHECK(bits >= 0 && bits < (1 << from_length));\n    FML_DCHECK(CanAppend());\n    lower_free_bits_ -= from_length;\n    buffer_ |= (bits << lower_free_bits_);\n  }\n\n  int Extract() {\n    FML_DCHECK(CanExtract());\n    int result = Peek();\n    buffer_ = (buffer_ << to_length) & mask_;\n    lower_free_bits_ += to_length;\n    return result;\n  }\n\n  int Peek() const { return (buffer_ >> (buffer_length - to_length)); }\n  int BitsAvailable() const { return buffer_length - lower_free_bits_; }\n  bool CanAppend() const { return lower_free_bits_ >= from_length; }\n  bool CanExtract() const { return BitsAvailable() >= to_length; }\n\n private:\n  static_assert(buffer_length >= 2 * from_length);\n  static_assert(buffer_length >= 2 * to_length);\n  static_assert(buffer_length < sizeof(int) * 8);\n\n  static constexpr int mask_ = (1 << buffer_length) - 1;\n\n  int buffer_ = 0;\n  int lower_free_bits_ = buffer_length;\n};\n\nusing Base32DecodeConverter = BitConverter<5, 8, 16>;\nusing Base32EncodeConverter = BitConverter<8, 5, 16>;\n\nstd::pair<bool, std::string> Base32Encode(std::string_view input);\nstd::pair<bool, std::string> Base32Decode(const std::string& input);\n\n}  // namespace fml\n\n#endif  // CLAY_FML_BASE32_H_\n"
  },
  {
    "path": "clay/fml/base32_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/base32.h\"\n\n#include <iostream>\n#include <vector>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nTEST(Base32Test, CanEncode) {\n  {\n    auto result = fml::Base32Encode(\"hello\");\n    ASSERT_TRUE(result.first);\n    ASSERT_EQ(result.second, \"NBSWY3DP\");\n  }\n\n  {\n    auto result = fml::Base32Encode(\"helLo\");\n    ASSERT_TRUE(result.first);\n    ASSERT_EQ(result.second, \"NBSWYTDP\");\n  }\n\n  {\n    auto result = fml::Base32Encode(\"\");\n    ASSERT_TRUE(result.first);\n    ASSERT_EQ(result.second, \"\");\n  }\n\n  {\n    auto result = fml::Base32Encode(\"1\");\n    ASSERT_TRUE(result.first);\n    ASSERT_EQ(result.second, \"GE\");\n  }\n\n  {\n    auto result = fml::Base32Encode(\"helLo\");\n    ASSERT_TRUE(result.first);\n    ASSERT_EQ(result.second, \"NBSWYTDP\");\n  }\n\n  {\n    auto result = fml::Base32Encode(\"\\xff\\xfe\\x7f\\x80\\x81\");\n    ASSERT_TRUE(result.first);\n    ASSERT_EQ(result.second, \"777H7AEB\");\n  }\n}\n\nTEST(Base32Test, CanEncodeDecodeStrings) {\n  std::vector<std::string> strings = {\"hello\", \"helLo\", \"\", \"1\", \"\\0\"};\n  for (size_t i = 0; i < strings.size(); i += 1) {\n    auto encode_result = fml::Base32Encode(strings[i]);\n    ASSERT_TRUE(encode_result.first);\n    auto decode_result = fml::Base32Decode(encode_result.second);\n    ASSERT_TRUE(decode_result.first);\n    const std::string& decoded = decode_result.second;\n    std::string decoded_string(decoded.data(), decoded.size());\n    ASSERT_EQ(strings[i], decoded_string);\n  }\n}\n\nTEST(Base32Test, DecodeReturnsFalseForInvalideInput) {\n  // \"B\" is invalid because it has a non-zero padding.\n  std::vector<std::string> invalid_inputs = {\"a\", \"1\", \"9\", \"B\"};\n  for (const std::string& input : invalid_inputs) {\n    auto decode_result = fml::Base32Decode(input);\n    if (decode_result.first) {\n      std::cout << \"Base32Decode should return false on \" << input << std::endl;\n    }\n    ASSERT_FALSE(decode_result.first);\n  }\n}\n\nTEST(Base32Test, CanDecodeSkSLKeys) {\n  std::vector<std::string> inputs = {\n      \"CAZAAAACAAAAADQAAAABKAAAAAJQAAIA7777777777776EYAAEAP777777777777AAAAAABA\"\n      \"ABTAAAAAAAAAAAAAAABAAAAAGQAGGAA\",\n      \"CAZAAAICAAAAAAAAAAADOAAAAAJQAAIA777777Y4AAKAAEYAAEAP777777777777EAAGMAAA\"\n      \"AAAAAAAAAAACQACNAAAAAAAAAAAAAAACAAAAAPAAMMAA\",\n      \"CAZACAACAAAABAYACAAAAAAAAAJQAAIADQABIAH777777777777RQAAOAAAAAAAAAAAAAABE\"\n      \"AANQAAAAAAAAAAAAAAYAAJYAAAAAAAANAAAQAAAAAAAEAAAHAAAAAAAAAAAAAAANAAAQAAAA\"\n      \"AAAFIADKAAAAAAAAAAAAAAACAAAAAZAAMMAA\"};\n  for (const std::string& input : inputs) {\n    auto decode_result = fml::Base32Decode(input);\n    if (!decode_result.first) {\n      std::cout << \"Base32Decode should return true on \" << input << std::endl;\n    }\n    ASSERT_TRUE(decode_result.first);\n    auto encode_result = fml::Base32Encode(decode_result.second);\n    ASSERT_TRUE(encode_result.first);\n    ASSERT_EQ(encode_result.second, input);\n  }\n}\n"
  },
  {
    "path": "clay/fml/base64.cc",
    "content": "/*\n * Copyright 2006 The Android Open Source Project\n *\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * Copyright 2022 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"clay/fml/base64.h\"\n\n#include <cstdint>\n\n#include \"clay/fml/logging.h\"\n\n#define DecodePad -2\n#define EncodePad 64\n\nstatic const char default_encode[] =\n    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n    \"abcdefghijklmnopqrstuvwxyz\"\n    \"0123456789+/=\";\n\nstatic const signed char decodeData[] = {\n    62, -1, -1,        -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1,\n    -1, -1, DecodePad, -1, -1, -1, 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,\n    10, 11, 12,        13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,\n    -1, -1, -1,        -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,\n    36, 37, 38,        39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};\n\n#if defined \\\n    _WIN32  // disable 'two', etc. may be used without having been initialized\n#pragma warning(push)\n#pragma warning(disable : 4701)\n#endif\n\nnamespace fml {\n\nBase64::Error Base64::Decode(const void* srcv, size_t srcLength, void* dstv,\n                             size_t* dstLength) {\n  const unsigned char* src = static_cast<const unsigned char*>(srcv);\n  unsigned char* dst = static_cast<unsigned char*>(dstv);\n\n  int i = 0;\n  bool padTwo = false;\n  bool padThree = false;\n  char unsigned const* const end = src + srcLength;\n  while (src < end) {\n    unsigned char bytes[4];\n    int byte = 0;\n    do {\n      unsigned char srcByte = *src++;\n      if (srcByte == 0) goto goHome;\n      if (srcByte <= ' ') continue;  // treat as white space\n      if (srcByte < '+' || srcByte > 'z') return kBadCharError;\n      signed char decoded = decodeData[srcByte - '+'];\n      bytes[byte] = decoded;\n      if (decoded < 0) {\n        if (decoded == DecodePad) goto handlePad;\n        return kBadCharError;\n      } else\n        byte++;\n      if (*src) continue;\n      if (byte == 0) goto goHome;\n      if (byte == 4) break;\n    handlePad:\n      if (byte < 2) return kPadError;\n      padThree = true;\n      if (byte == 2) padTwo = true;\n      break;\n    } while (byte < 4);\n    int two = 0;\n    int three = 0;\n    if (dst) {\n      int one = (uint8_t)(bytes[0] << 2);\n      two = bytes[1];\n      one |= two >> 4;\n      two = (uint8_t)((two << 4) & 0xFF);\n      three = bytes[2];\n      two |= three >> 2;\n      three = (uint8_t)((three << 6) & 0xFF);\n      three |= bytes[3];\n      FML_CHECK(one < 256 && two < 256 && three < 256);\n      dst[i] = (unsigned char)one;\n    }\n    i++;\n    if (padTwo) break;\n    if (dst) dst[i] = (unsigned char)two;\n    i++;\n    if (padThree) break;\n    if (dst) dst[i] = (unsigned char)three;\n    i++;\n  }\ngoHome:\n  *dstLength = i;\n  return kNoError;\n}\n\n#if defined _WIN32\n#pragma warning(pop)\n#endif\n\nsize_t Base64::Encode(const void* srcv, size_t length, void* dstv,\n                      const char* encodeMap) {\n  const unsigned char* src = static_cast<const unsigned char*>(srcv);\n  unsigned char* dst = static_cast<unsigned char*>(dstv);\n\n  const char* encode;\n  if (nullptr == encodeMap) {\n    encode = default_encode;\n  } else {\n    encode = encodeMap;\n  }\n  if (dst) {\n    size_t remainder = length % 3;\n    char unsigned const* const end = &src[length - remainder];\n    while (src < end) {\n      unsigned a = *src++;\n      unsigned b = *src++;\n      unsigned c = *src++;\n      int d = c & 0x3F;\n      c = (c >> 6 | b << 2) & 0x3F;\n      b = (b >> 4 | a << 4) & 0x3F;\n      a = a >> 2;\n      *dst++ = encode[a];\n      *dst++ = encode[b];\n      *dst++ = encode[c];\n      *dst++ = encode[d];\n    }\n    if (remainder > 0) {\n      int k1 = 0;\n      int k2 = EncodePad;\n      int a = (uint8_t)*src++;\n      if (remainder == 2) {\n        int b = *src++;\n        k1 = b >> 4;\n        k2 = (b << 2) & 0x3F;\n      }\n      *dst++ = encode[a >> 2];\n      *dst++ = encode[(k1 | a << 4) & 0x3F];\n      *dst++ = encode[k2];\n      *dst++ = encode[EncodePad];\n    }\n  }\n  return (length + 2) / 3 * 4;\n}\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/base64.h",
    "content": "/*\n * Copyright 2006 The Android Open Source Project\n *\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n *\n * Copyright 2022 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#ifndef CLAY_FML_BASE64_H_\n#define CLAY_FML_BASE64_H_\n\n#include <cstddef>\n\nnamespace fml {\nstruct Base64 {\n public:\n  enum Error { kNoError, kPadError, kBadCharError };\n\n  /**\n     Base64 encodes src into dst.\n\n     Normally this is called once with 'dst' nullptr to get the required size,\n     then again with an allocated 'dst' pointer to do the actual encoding.\n\n     @param dst nullptr or a pointer to a buffer large enough to receive the\n     result\n\n     @param encode nullptr for default encoding or a pointer to at least 65\n     chars. encode[64] will be used as the pad character. Encodings other than\n     the default encoding cannot be decoded.\n\n     @return the required length of dst for encoding.\n  */\n  static size_t Encode(const void* src, size_t length, void* dst,\n                       const char* encode = nullptr);\n\n  /**\n     Base64 decodes src into dst.\n\n     Normally this is called once with 'dst' nullptr to get the required size,\n     then again with an allocated 'dst' pointer to do the actual encoding.\n\n     @param dst nullptr or a pointer to a buffer large enough to receive the\n     result\n\n     @param dstLength assigned the length dst is required to be. Must not be\n     nullptr.\n  */\n  static Error Decode(const void* src, size_t srcLength, void* dst,\n                      size_t* dstLength);\n};\n}  // namespace fml\n\n#endif  // CLAY_FML_BASE64_H_\n"
  },
  {
    "path": "clay/fml/base64_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string>\n#include <vector>\n\n#include \"clay/fml/base64.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nTEST(Base64Test, CanEncodeAndDecode) {\n  const std::string original = \"abcdefghijklmnopqrstuvwxyz0123456789+/=\";\n  const std::string encoded =\n      \"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Ky89\";\n\n  size_t encoded_size =\n      fml::Base64::Encode(original.c_str(), original.length(), nullptr);\n  std::string encoded_result(encoded_size, '\\0');\n  fml::Base64::Encode(original.c_str(), original.length(),\n                      encoded_result.data());\n\n  ASSERT_EQ(encoded, encoded_result);\n\n  size_t decoded_size = 0;\n  fml::Base64::Decode(encoded.c_str(), encoded.length(), nullptr,\n                      &decoded_size);\n  std::string decoded_result(decoded_size, '\\0');\n  ASSERT_EQ(fml::Base64::kNoError,\n            fml::Base64::Decode(encoded.c_str(), encoded.length(),\n                                decoded_result.data(), &decoded_size));\n\n  ASSERT_EQ(original, decoded_result);\n}\n\nTEST(Base64Test, CanEncodeAndDecodeWithPadding) {\n  const std::string original = \"pleasure.\";\n  const std::string encoded = \"cGxlYXN1cmUu\";\n\n  size_t encoded_size =\n      fml::Base64::Encode(original.c_str(), original.length(), nullptr);\n  std::string encoded_result(encoded_size, '\\0');\n  fml::Base64::Encode(original.c_str(), original.length(),\n                      encoded_result.data());\n\n  ASSERT_EQ(encoded, encoded_result);\n\n  size_t decoded_size = 0;\n  fml::Base64::Decode(encoded.c_str(), encoded.length(), nullptr,\n                      &decoded_size);\n  std::string decoded_result(decoded_size, '\\0');\n  ASSERT_EQ(fml::Base64::kNoError,\n            fml::Base64::Decode(encoded.c_str(), encoded.length(),\n                                decoded_result.data(), &decoded_size));\n\n  ASSERT_EQ(original, decoded_result);\n}\n\nTEST(Base64Test, DecodeInvalidString) {\n  const std::string invalid_encoded = \"invalid~\";\n  size_t decoded_size = 0;\n  ASSERT_EQ(\n      fml::Base64::kBadCharError,\n      fml::Base64::Decode(invalid_encoded.c_str(), invalid_encoded.length(),\n                          nullptr, &decoded_size));\n}\n"
  },
  {
    "path": "clay/fml/command_line.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/command_line.h\"\n\nnamespace fml {\n\n// CommandLine -----------------------------------------------------------------\n\nCommandLine::Option::Option(const std::string& name) : name(name) {}\n\nCommandLine::Option::Option(const std::string& name, const std::string& value)\n    : name(name), value(value) {}\n\nCommandLine::CommandLine() = default;\n\nCommandLine::CommandLine(const CommandLine& from) = default;\n\nCommandLine::CommandLine(CommandLine&& from) = default;\n\nCommandLine::CommandLine(const std::string& argv0,\n                         const std::vector<Option>& options,\n                         const std::vector<std::string>& positional_args)\n    : has_argv0_(true),\n      argv0_(argv0),\n      options_(options),\n      positional_args_(positional_args) {\n  for (size_t i = 0; i < options_.size(); i++) {\n    option_index_[options_[i].name] = i;\n  }\n}\n\nCommandLine::~CommandLine() = default;\n\nCommandLine& CommandLine::operator=(const CommandLine& from) = default;\n\nCommandLine& CommandLine::operator=(CommandLine&& from) = default;\n\nbool CommandLine::HasOption(std::string_view name, size_t* index) const {\n  auto it = option_index_.find(name.data());\n  if (it == option_index_.end()) {\n    return false;\n  }\n  if (index) {\n    *index = it->second;\n  }\n  return true;\n}\n\nbool CommandLine::GetOptionValue(std::string_view name,\n                                 std::string* value) const {\n  size_t index;\n  if (!HasOption(name, &index)) {\n    return false;\n  }\n  *value = options_[index].value;\n  return true;\n}\n\nstd::vector<std::string_view> CommandLine::GetOptionValues(\n    std::string_view name) const {\n  std::vector<std::string_view> ret;\n  for (const auto& option : options_) {\n    if (option.name == name) {\n      ret.push_back(option.value);\n    }\n  }\n  return ret;\n}\n\nstd::string CommandLine::GetOptionValueWithDefault(\n    std::string_view name, std::string_view default_value) const {\n  size_t index;\n  if (!HasOption(name, &index)) {\n    return {default_value.data(), default_value.size()};\n  }\n  return options_[index].value;\n}\n\n// Factory functions (etc.) ----------------------------------------------------\n\nnamespace internal {\n\nCommandLineBuilder::CommandLineBuilder() {}\nCommandLineBuilder::~CommandLineBuilder() {}\n\nbool CommandLineBuilder::ProcessArg(const std::string& arg) {\n  if (!has_argv0_) {\n    has_argv0_ = true;\n    argv0_ = arg;\n    return false;\n  }\n\n  // If we've seen a positional argument, then the remaining arguments are also\n  // positional.\n  if (started_positional_args_) {\n    bool rv = positional_args_.empty();\n    positional_args_.push_back(arg);\n    return rv;\n  }\n\n  // Anything that doesn't start with \"--\" is a positional argument.\n  if (arg.size() < 2u || arg[0] != '-' || arg[1] != '-') {\n    bool rv = positional_args_.empty();\n    started_positional_args_ = true;\n    positional_args_.push_back(arg);\n    return rv;\n  }\n\n  // \"--\" ends option processing, but isn't stored as a positional argument.\n  if (arg.size() == 2u) {\n    started_positional_args_ = true;\n    return false;\n  }\n\n  // Note: The option name *must* be at least one character, so start at\n  // position 3 -- \"--=foo\" will yield a name of \"=foo\" and no value. (Passing a\n  // starting |pos| that's \"too big\" is OK.)\n  size_t equals_pos = arg.find('=', 3u);\n  if (equals_pos == std::string::npos) {\n    options_.push_back(CommandLine::Option(arg.substr(2u)));\n    return false;\n  }\n\n  options_.push_back(CommandLine::Option(arg.substr(2u, equals_pos - 2u),\n                                         arg.substr(equals_pos + 1u)));\n  return false;\n}\n\nCommandLine CommandLineBuilder::Build() const {\n  if (!has_argv0_) {\n    return CommandLine();\n  }\n  return CommandLine(argv0_, options_, positional_args_);\n}\n\n}  // namespace internal\n\nstd::vector<std::string> CommandLineToArgv(const CommandLine& command_line) {\n  if (!command_line.has_argv0()) {\n    return std::vector<std::string>();\n  }\n\n  std::vector<std::string> argv;\n  const std::vector<CommandLine::Option>& options = command_line.options();\n  const std::vector<std::string>& positional_args =\n      command_line.positional_args();\n  // Reserve space for argv[0], options, maybe a \"--\" (if needed), and the\n  // positional arguments.\n  argv.reserve(1u + options.size() + 1u + positional_args.size());\n\n  argv.push_back(command_line.argv0());\n  for (const auto& option : options) {\n    if (option.value.empty()) {\n      argv.push_back(\"--\" + option.name);\n    } else {\n      argv.push_back(\"--\" + option.name + \"=\" + option.value);\n    }\n  }\n\n  if (!positional_args.empty()) {\n    // Insert a \"--\" if necessary.\n    if (positional_args[0].size() >= 2u && positional_args[0][0] == '-' &&\n        positional_args[0][1] == '-') {\n      argv.push_back(\"--\");\n    }\n\n    argv.insert(argv.end(), positional_args.begin(), positional_args.end());\n  }\n\n  return argv;\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/command_line.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// Provides a simple class, |CommandLine|, for dealing with command lines (and\n// flags and positional arguments).\n//\n// * Options (a.k.a. flags or switches) are all of the form \"--name=<value>\" (or\n//   \"--name\", but this is indistinguishable from \"--name=\"), where <value> is a\n//   string. Not supported: \"-name\", \"-n\", \"--name <value>\", \"-n <value>\", etc.\n// * Option order is preserved.\n// * Option processing is stopped after the first positional argument[*]. Thus\n//   in the command line \"my_program --foo bar --baz\", only \"--foo\" is an option\n//   (\"bar\" and \"--baz\" are positional arguments).\n// * Options can be looked up by name. If the same option occurs multiple times,\n//   convention is to use the last occurrence (and the provided look-up\n//   functions behave this way).\n// * \"--\" may also be used to separate options from positional arguments. Thus\n//   in the command line \"my_program --foo -- --bar\", \"--bar\" is a positional\n//   argument.\n// * |CommandLine|s store |argv[0]| and distinguish between not having |argv[0]|\n//   and |argv[0]| being empty.\n// * Apart from being copyable and movable, |CommandLine|s are immutable.\n//\n// There are factory functions to turn raw arguments into |CommandLine|s, in\n// accordance with the above rules. However, |CommandLine|s may be used more\n// generically (with the user transforming arguments using different rules,\n// e.g., accepting \"-name\" as an option), subject to certain limitations (e.g.,\n// not being able to distinguish \"no value\" from \"empty value\").\n//\n// [*] This is somewhat annoying for users, but: a. it's standard Unix behavior\n// for most command line parsers, b. it makes \"my_program *\" (etc.) safer (which\n// mostly explains a.), c. it makes parsing \"subcommands\", like \"my_program\n// --flag_for_my_program subcommand --flag_for_subcommand\" saner.\n\n#ifndef CLAY_FML_COMMAND_LINE_H_\n#define CLAY_FML_COMMAND_LINE_H_\n\n#include <cstddef>\n#include <initializer_list>\n#include <optional>\n#include <string>\n#include <string_view>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace fml {\n\n// CommandLine -----------------------------------------------------------------\n\n// Class that stores processed command lines (\"argv[0]\", options, and positional\n// arguments) and provides access to them. For more details, see the file-level\n// comment above. This class is thread-safe.\nclass CommandLine final {\n private:\n  class ConstructionHelper;\n\n public:\n  struct Option {\n    Option() {}\n    explicit Option(const std::string& name);\n    Option(const std::string& name, const std::string& value);\n\n    bool operator==(const Option& other) const {\n      return name == other.name && value == other.value;\n    }\n    bool operator!=(const Option& other) const { return !operator==(other); }\n\n    std::string name;\n    std::string value;\n  };\n\n  // Default, copy, and move constructors (to be out-of-lined).\n  CommandLine();\n  CommandLine(const CommandLine& from);\n  CommandLine(CommandLine&& from);\n\n  // Constructs a |CommandLine| from its \"components\". This is especially useful\n  // for creating a new |CommandLine| based on an existing |CommandLine| (e.g.,\n  // adding options or arguments).\n  explicit CommandLine(const std::string& argv0,\n                       const std::vector<Option>& options,\n                       const std::vector<std::string>& positional_args);\n\n  ~CommandLine();\n\n  // Copy and move assignment (to be out-of-lined).\n  CommandLine& operator=(const CommandLine& from);\n  CommandLine& operator=(CommandLine&& from);\n\n  bool has_argv0() const { return has_argv0_; }\n  const std::string& argv0() const { return argv0_; }\n  const std::vector<Option>& options() const { return options_; }\n  const std::vector<std::string>& positional_args() const {\n    return positional_args_;\n  }\n\n  bool operator==(const CommandLine& other) const {\n    // No need to compare |option_index_|.\n    return has_argv0_ == other.has_argv0_ && argv0_ == other.argv0_ &&\n           options_ == other.options_ &&\n           positional_args_ == other.positional_args_;\n  }\n  bool operator!=(const CommandLine& other) const { return !operator==(other); }\n\n  // Returns true if this command line has the option |name| (and if |index| is\n  // non-null, sets |*index| to the index of the *last* occurrence of the given\n  // option in |options()|) and false if not.\n  bool HasOption(std::string_view name, size_t* index = nullptr) const;\n\n  // Gets the value of the option |name|. Returns true (and sets |*value|) on\n  // success and false (leaving |*value| alone) on failure.\n  bool GetOptionValue(std::string_view name, std::string* value) const;\n\n  // Gets all values of the option |name|. Returns all values, which may be\n  // empty if the option is not specified.\n  std::vector<std::string_view> GetOptionValues(std::string_view name) const;\n\n  // Gets the value of the option |name|, with a default if the option is not\n  // specified. (Note: This doesn't return a const reference, since this would\n  // make the |default_value| argument inconvenient/dangerous.)\n  std::string GetOptionValueWithDefault(std::string_view name,\n                                        std::string_view default_value) const;\n\n private:\n  bool has_argv0_ = false;\n  // The following should all be empty if |has_argv0_| is false.\n  std::string argv0_;\n  std::vector<Option> options_;\n  std::vector<std::string> positional_args_;\n\n  // Maps option names to position in |options_|. If a given name occurs\n  // multiple times, the index will be to the *last* occurrence.\n  std::unordered_map<std::string, size_t> option_index_;\n\n  // Allow copy and assignment.\n};\n\n// Factory functions (etc.) ----------------------------------------------------\n\nnamespace internal {\n\n// Helper class for building command lines (finding options, etc.) from raw\n// arguments.\nclass CommandLineBuilder final {\n public:\n  CommandLineBuilder();\n  ~CommandLineBuilder();\n\n  // Processes an additional argument in the command line. Returns true if |arg|\n  // is the *first* positional argument.\n  bool ProcessArg(const std::string& arg);\n\n  // Builds a |CommandLine| from the arguments processed so far.\n  CommandLine Build() const;\n\n private:\n  bool has_argv0_ = false;\n  std::string argv0_;\n  std::vector<CommandLine::Option> options_;\n  std::vector<std::string> positional_args_;\n\n  // True if we've started processing positional arguments.\n  bool started_positional_args_ = false;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(CommandLineBuilder);\n};\n\n}  // namespace internal\n\n// The following factory functions create |CommandLine|s from raw arguments in\n// accordance with the rules outlined at the top of this file. (Other ways of\n// transforming raw arguments into options and positional arguments are\n// possible.)\n\n// Like |CommandLineFromIterators()| (see below), but sets\n// |*first_positional_arg| to point to the first positional argument seen (or\n// |last| if none are seen). This is useful for processing \"subcommands\".\ntemplate <typename InputIterator>\ninline CommandLine CommandLineFromIteratorsFindFirstPositionalArg(\n    InputIterator first, InputIterator last,\n    InputIterator* first_positional_arg) {\n  if (first_positional_arg) *first_positional_arg = last;\n  internal::CommandLineBuilder builder;\n  for (auto it = first; it < last; ++it) {\n    if (builder.ProcessArg(*it)) {\n      if (first_positional_arg) *first_positional_arg = it;\n    }\n  }\n  return builder.Build();\n}\n\n// Builds a |CommandLine| from first/last iterators (where |last| is really\n// one-past-the-last, as usual) to |std::string|s or things that implicitly\n// convert to |std::string|.\ntemplate <typename InputIterator>\ninline CommandLine CommandLineFromIterators(InputIterator first,\n                                            InputIterator last) {\n  return CommandLineFromIteratorsFindFirstPositionalArg<InputIterator>(\n      first, last, nullptr);\n}\n\n// Builds a |CommandLine| from first/last iterators (where |last| is really\n// one-past-the-last, as usual) to |std::string|s or things that implicitly\n// convert to |std::string|, where argv[0] is provided separately.\ntemplate <typename InputIterator>\ninline CommandLine CommandLineFromIteratorsWithArgv0(const std::string& argv0,\n                                                     InputIterator first,\n                                                     InputIterator last) {\n  internal::CommandLineBuilder builder;\n  builder.ProcessArg(argv0);\n  for (auto it = first; it < last; ++it) builder.ProcessArg(*it);\n  return builder.Build();\n}\n\n// Builds a |CommandLine| by obtaining the arguments of the process using host\n// platform APIs. The resulting |CommandLine| will be encoded in UTF-8.\n// Returns an empty optional if this is not supported on the host platform.\n//\n// This can be useful on platforms where argv may not be provided as UTF-8.\nstd::optional<CommandLine> CommandLineFromPlatform();\n\n// Builds a |CommandLine| from the usual argc/argv.\ninline CommandLine CommandLineFromArgcArgv(int argc, const char* const* argv) {\n  return CommandLineFromIterators(argv, argv + argc);\n}\n\n// Builds a |CommandLine| by first trying the platform specific implementation,\n// and then falling back to the argc/argv.\n//\n// If the platform provides a special way of getting arguments, this method may\n// discard the values passed in to argc/argv.\ninline CommandLine CommandLineFromPlatformOrArgcArgv(int argc,\n                                                     const char* const* argv) {\n  auto command_line = CommandLineFromPlatform();\n  if (command_line.has_value()) {\n    return *command_line;\n  }\n  return CommandLineFromArgcArgv(argc, argv);\n}\n\n// Builds a |CommandLine| from an initializer list of |std::string|s or things\n// that implicitly convert to |std::string|.\ntemplate <typename StringType>\ninline CommandLine CommandLineFromInitializerList(\n    std::initializer_list<StringType> argv) {\n  return CommandLineFromIterators(argv.begin(), argv.end());\n}\n\n// This is the \"opposite\" of the above factory functions, transforming a\n// |CommandLine| into a vector of argument strings according to the rules\n// outlined at the top of this file.\nstd::vector<std::string> CommandLineToArgv(const CommandLine& command_line);\n\n}  // namespace fml\n\n#endif  // CLAY_FML_COMMAND_LINE_H_\n"
  },
  {
    "path": "clay/fml/command_line_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/command_line.h\"\n\n#include <string_view>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/size.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace fml {\nnamespace {\n\nTEST(CommandLineTest, Basic) {\n  // Making this const verifies that the methods called are const.\n  const auto cl = CommandLineFromInitializerList(\n      {\"my_program\", \"--flag1\", \"--flag2=value2\", \"arg1\", \"arg2\", \"arg3\"});\n\n  EXPECT_TRUE(cl.has_argv0());\n  EXPECT_EQ(\"my_program\", cl.argv0());\n\n  EXPECT_EQ(2u, cl.options().size());\n  EXPECT_EQ(\"flag1\", cl.options()[0].name);\n  EXPECT_EQ(std::string(), cl.options()[0].value);\n  EXPECT_EQ(\"flag2\", cl.options()[1].name);\n  EXPECT_EQ(\"value2\", cl.options()[1].value);\n\n  EXPECT_EQ(3u, cl.positional_args().size());\n  EXPECT_EQ(\"arg1\", cl.positional_args()[0]);\n  EXPECT_EQ(\"arg2\", cl.positional_args()[1]);\n  EXPECT_EQ(\"arg3\", cl.positional_args()[2]);\n\n  EXPECT_TRUE(cl.HasOption(\"flag1\"));\n  EXPECT_TRUE(cl.HasOption(\"flag1\", nullptr));\n  size_t index = static_cast<size_t>(-1);\n  EXPECT_TRUE(cl.HasOption(\"flag2\", &index));\n  EXPECT_EQ(1u, index);\n  EXPECT_FALSE(cl.HasOption(\"flag3\"));\n  EXPECT_FALSE(cl.HasOption(\"flag3\", nullptr));\n\n  std::string value = \"nonempty\";\n  EXPECT_TRUE(cl.GetOptionValue(\"flag1\", &value));\n  EXPECT_EQ(std::string(), value);\n  EXPECT_TRUE(cl.GetOptionValue(\"flag2\", &value));\n  EXPECT_EQ(\"value2\", value);\n  EXPECT_FALSE(cl.GetOptionValue(\"flag3\", &value));\n\n  EXPECT_EQ(std::string(), cl.GetOptionValueWithDefault(\"flag1\", \"nope\"));\n  EXPECT_EQ(\"value2\", cl.GetOptionValueWithDefault(\"flag2\", \"nope\"));\n  EXPECT_EQ(\"nope\", cl.GetOptionValueWithDefault(\"flag3\", \"nope\"));\n}\n\nTEST(CommandLineTest, DefaultConstructor) {\n  CommandLine cl;\n  EXPECT_FALSE(cl.has_argv0());\n  EXPECT_EQ(std::string(), cl.argv0());\n  EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());\n  EXPECT_EQ(std::vector<std::string>(), cl.positional_args());\n}\n\nTEST(CommandLineTest, ComponentConstructor) {\n  const std::string argv0 = \"my_program\";\n  const std::vector<CommandLine::Option> options = {\n      CommandLine::Option(\"flag\", \"value\")};\n  const std::vector<std::string> positional_args = {\"arg\"};\n\n  CommandLine cl(argv0, options, positional_args);\n  EXPECT_TRUE(cl.has_argv0());\n  EXPECT_EQ(argv0, cl.argv0());\n  EXPECT_EQ(options, cl.options());\n  EXPECT_EQ(positional_args, cl.positional_args());\n  EXPECT_EQ(\"value\", cl.GetOptionValueWithDefault(\"flag\", \"nope\"));\n}\n\nTEST(CommandLineTest, CommandLineFromIteratorsFindFirstPositionalArg) {\n  // This shows how one might process subcommands.\n  {\n    static std::vector<std::string> argv = {\"my_program\", \"--flag1\",\n                                            \"--flag2\",    \"subcommand\",\n                                            \"--subflag\",  \"subarg\"};\n    auto first = argv.cbegin();\n    auto last = argv.cend();\n    std::vector<std::string>::const_iterator sub_first;\n    auto cl =\n        CommandLineFromIteratorsFindFirstPositionalArg(first, last, &sub_first);\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(argv[0], cl.argv0());\n    std::vector<CommandLine::Option> expected_options = {\n        CommandLine::Option(\"flag1\"), CommandLine::Option(\"flag2\")};\n    EXPECT_EQ(expected_options, cl.options());\n    std::vector<std::string> expected_positional_args = {argv[3], argv[4],\n                                                         argv[5]};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n    EXPECT_TRUE(cl.HasOption(\"flag1\", nullptr));\n    EXPECT_TRUE(cl.HasOption(\"flag2\", nullptr));\n    EXPECT_FALSE(cl.HasOption(\"subflag\", nullptr));\n\n    EXPECT_EQ(first + 3, sub_first);\n    auto sub_cl = CommandLineFromIterators(sub_first, last);\n    EXPECT_TRUE(sub_cl.has_argv0());\n    EXPECT_EQ(argv[3], sub_cl.argv0());\n    std::vector<CommandLine::Option> expected_sub_options = {\n        CommandLine::Option(\"subflag\")};\n    EXPECT_EQ(expected_sub_options, sub_cl.options());\n    std::vector<std::string> expected_sub_positional_args = {argv[5]};\n    EXPECT_EQ(expected_sub_positional_args, sub_cl.positional_args());\n    EXPECT_FALSE(sub_cl.HasOption(\"flag1\", nullptr));\n    EXPECT_FALSE(sub_cl.HasOption(\"flag2\", nullptr));\n    EXPECT_TRUE(sub_cl.HasOption(\"subflag\", nullptr));\n  }\n\n  // No positional argument.\n  {\n    static std::vector<std::string> argv = {\"my_program\", \"--flag\"};\n    std::vector<std::string>::const_iterator sub_first;\n    auto cl = CommandLineFromIteratorsFindFirstPositionalArg(\n        argv.cbegin(), argv.cend(), &sub_first);\n    EXPECT_EQ(argv.cend(), sub_first);\n  }\n\n  // Multiple positional arguments.\n  {\n    static std::vector<std::string> argv = {\"my_program\", \"arg1\", \"arg2\"};\n    std::vector<std::string>::const_iterator sub_first;\n    auto cl = CommandLineFromIteratorsFindFirstPositionalArg(\n        argv.cbegin(), argv.cend(), &sub_first);\n    EXPECT_EQ(argv.cbegin() + 1, sub_first);\n  }\n\n  // \"--\".\n  {\n    static std::vector<std::string> argv = {\"my_program\", \"--\", \"--arg\"};\n    std::vector<std::string>::const_iterator sub_first;\n    auto cl = CommandLineFromIteratorsFindFirstPositionalArg(\n        argv.cbegin(), argv.cend(), &sub_first);\n    EXPECT_EQ(argv.cbegin() + 2, sub_first);\n  }\n}\n\nTEST(CommandLineTest, CommmandLineFromIterators) {\n  {\n    // Note (here and below): The |const| ensures that the factory method can\n    // accept const iterators.\n    const std::vector<std::string> argv = {\"my_program\", \"--flag=value\", \"arg\"};\n\n    auto cl = CommandLineFromIterators(argv.begin(), argv.end());\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(argv[0], cl.argv0());\n    std::vector<CommandLine::Option> expected_options = {\n        CommandLine::Option(\"flag\", \"value\")};\n    EXPECT_EQ(expected_options, cl.options());\n    std::vector<std::string> expected_positional_args = {argv[2]};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n    EXPECT_EQ(\"value\", cl.GetOptionValueWithDefault(\"flag\", \"nope\"));\n  }\n\n  // Can handle empty argv.\n  {\n    const std::vector<std::string> argv;\n\n    auto cl = CommandLineFromIterators(argv.begin(), argv.end());\n    EXPECT_FALSE(cl.has_argv0());\n    EXPECT_EQ(std::string(), cl.argv0());\n    EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());\n    EXPECT_EQ(std::vector<std::string>(), cl.positional_args());\n  }\n\n  // Can handle empty |argv[0]|.\n  {\n    const std::vector<std::string> argv = {\"\"};\n\n    auto cl = CommandLineFromIterators(argv.begin(), argv.end());\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(std::string(), cl.argv0());\n    EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());\n    EXPECT_EQ(std::vector<std::string>(), cl.positional_args());\n  }\n\n  // Can also take a vector of |const char*|s.\n  {\n    const std::vector<const char*> argv = {\"my_program\", \"--flag=value\", \"arg\"};\n\n    auto cl = CommandLineFromIterators(argv.begin(), argv.end());\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(argv[0], cl.argv0());\n    std::vector<CommandLine::Option> expected_options = {\n        CommandLine::Option(\"flag\", \"value\")};\n    EXPECT_EQ(expected_options, cl.options());\n    std::vector<std::string> expected_positional_args = {argv[2]};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n    EXPECT_EQ(\"value\", cl.GetOptionValueWithDefault(\"flag\", \"nope\"));\n  }\n\n  // Or a plain old array.\n  {\n    static const char* const argv[] = {\"my_program\", \"--flag=value\", \"arg\"};\n\n    auto cl = CommandLineFromIterators(argv, argv + fml::size(argv));\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(argv[0], cl.argv0());\n    std::vector<CommandLine::Option> expected_options = {\n        CommandLine::Option(\"flag\", \"value\")};\n    EXPECT_EQ(expected_options, cl.options());\n    std::vector<std::string> expected_positional_args = {argv[2]};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n    EXPECT_EQ(\"value\", cl.GetOptionValueWithDefault(\"flag\", \"nope\"));\n  }\n}\n\nTEST(CommandLineTest, CommandLineFromArgcArgv) {\n  static const char* const argv[] = {\"my_program\", \"--flag=value\", \"arg\"};\n  const int argc = static_cast<int>(fml::size(argv));\n\n  auto cl = CommandLineFromArgcArgv(argc, argv);\n  EXPECT_TRUE(cl.has_argv0());\n  EXPECT_EQ(argv[0], cl.argv0());\n  std::vector<CommandLine::Option> expected_options = {\n      CommandLine::Option(\"flag\", \"value\")};\n  EXPECT_EQ(expected_options, cl.options());\n  std::vector<std::string> expected_positional_args = {argv[2]};\n  EXPECT_EQ(expected_positional_args, cl.positional_args());\n  EXPECT_EQ(\"value\", cl.GetOptionValueWithDefault(\"flag\", \"nope\"));\n}\n\nTEST(CommandLineTest, CommandLineFromInitializerList) {\n  {\n    std::initializer_list<const char*> il = {\"my_program\", \"--flag=value\",\n                                             \"arg\"};\n    auto cl = CommandLineFromInitializerList(il);\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(\"my_program\", cl.argv0());\n    std::vector<CommandLine::Option> expected_options = {\n        CommandLine::Option(\"flag\", \"value\")};\n    EXPECT_EQ(expected_options, cl.options());\n    std::vector<std::string> expected_positional_args = {\"arg\"};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n    EXPECT_EQ(\"value\", cl.GetOptionValueWithDefault(\"flag\", \"nope\"));\n  }\n\n  {\n    std::initializer_list<std::string> il = {\"my_program\", \"--flag=value\",\n                                             \"arg\"};\n    auto cl = CommandLineFromInitializerList(il);\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(\"my_program\", cl.argv0());\n    std::vector<CommandLine::Option> expected_options = {\n        CommandLine::Option(\"flag\", \"value\")};\n    EXPECT_EQ(expected_options, cl.options());\n    std::vector<std::string> expected_positional_args = {\"arg\"};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n    EXPECT_EQ(\"value\", cl.GetOptionValueWithDefault(\"flag\", \"nope\"));\n  }\n}\n\nTEST(CommandLineTest, OddArguments) {\n  {\n    // Except for \"arg\", these are all options.\n    auto cl = CommandLineFromInitializerList(\n        {\"my_program\", \"--=\", \"--=foo\", \"--bar=\", \"--==\", \"--===\", \"--==x\",\n         \"arg\"});\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(\"my_program\", cl.argv0());\n    std::vector<CommandLine::Option> expected_options = {\n        CommandLine::Option(\"=\"),      CommandLine::Option(\"=foo\"),\n        CommandLine::Option(\"bar\"),    CommandLine::Option(\"=\"),\n        CommandLine::Option(\"=\", \"=\"), CommandLine::Option(\"=\", \"x\")};\n    EXPECT_EQ(expected_options, cl.options());\n    std::vector<std::string> expected_positional_args = {\"arg\"};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n  }\n\n  // \"-x\" is an argument, not an options.\n  {\n    auto cl = CommandLineFromInitializerList({\"\", \"-x\"});\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(std::string(), cl.argv0());\n    EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());\n    std::vector<std::string> expected_positional_args = {\"-x\"};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n  }\n\n  // Ditto for \"-\".\n  {\n    auto cl = CommandLineFromInitializerList({\"\", \"-\"});\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(std::string(), cl.argv0());\n    EXPECT_EQ(std::vector<CommandLine::Option>(), cl.options());\n    std::vector<std::string> expected_positional_args = {\"-\"};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n  }\n\n  // \"--\" terminates option processing, but isn't an argument in the first\n  // occurrence.\n  {\n    auto cl = CommandLineFromInitializerList(\n        {\"\", \"--flag=value\", \"--\", \"--not-a-flag\", \"arg\", \"--\"});\n    EXPECT_TRUE(cl.has_argv0());\n    EXPECT_EQ(std::string(), cl.argv0());\n    std::vector<CommandLine::Option> expected_options = {\n        CommandLine::Option(\"flag\", \"value\")};\n    std::vector<std::string> expected_positional_args = {\"--not-a-flag\", \"arg\",\n                                                         \"--\"};\n    EXPECT_EQ(expected_positional_args, cl.positional_args());\n  }\n}\n\nTEST(CommandLineTest, MultipleOccurrencesOfOption) {\n  auto cl = CommandLineFromInitializerList(\n      {\"my_program\", \"--flag1=value1\", \"--flag2=value2\", \"--flag1=value3\"});\n  std::vector<CommandLine::Option> expected_options = {\n      CommandLine::Option(\"flag1\", \"value1\"),\n      CommandLine::Option(\"flag2\", \"value2\"),\n      CommandLine::Option(\"flag1\", \"value3\")};\n  EXPECT_EQ(\"value3\", cl.GetOptionValueWithDefault(\"flag1\", \"nope\"));\n  EXPECT_EQ(\"value2\", cl.GetOptionValueWithDefault(\"flag2\", \"nope\"));\n  std::vector<std::string_view> values = cl.GetOptionValues(\"flag1\");\n  ASSERT_EQ(2u, values.size());\n  EXPECT_EQ(\"value1\", values[0]);\n  EXPECT_EQ(\"value3\", values[1]);\n}\n\n// |cl1| and |cl2| should be not equal.\nvoid ExpectNotEqual(const char* message, std::initializer_list<std::string> c1,\n                    std::initializer_list<std::string> c2) {\n  SCOPED_TRACE(message);\n\n  const auto cl1 = CommandLineFromInitializerList(c1);\n  const auto cl2 = CommandLineFromInitializerList(c2);\n\n  // These are tautological.\n  EXPECT_TRUE(cl1 == cl1);\n  EXPECT_FALSE(cl1 != cl1);\n  EXPECT_TRUE(cl2 == cl2);\n  EXPECT_FALSE(cl2 != cl2);\n\n  // These rely on |cl1| not being equal to |cl2|.\n  EXPECT_FALSE(cl1 == cl2);\n  EXPECT_TRUE(cl1 != cl2);\n  EXPECT_FALSE(cl2 == cl1);\n  EXPECT_TRUE(cl2 != cl1);\n}\n\nvoid ExpectEqual(const char* message, std::initializer_list<std::string> c1,\n                 std::initializer_list<std::string> c2) {\n  SCOPED_TRACE(message);\n\n  const auto cl1 = CommandLineFromInitializerList(c1);\n  const auto cl2 = CommandLineFromInitializerList(c2);\n\n  // These are tautological.\n  EXPECT_TRUE(cl1 == cl1);\n  EXPECT_FALSE(cl1 != cl1);\n  EXPECT_TRUE(cl2 == cl2);\n  EXPECT_FALSE(cl2 != cl2);\n\n  // These rely on |cl1| being equal to |cl2|.\n  EXPECT_TRUE(cl1 == cl2);\n  EXPECT_FALSE(cl1 != cl2);\n  EXPECT_TRUE(cl2 == cl1);\n  EXPECT_FALSE(cl2 != cl1);\n}\n\nTEST(CommandLineTest, ComparisonOperators) {\n  ExpectNotEqual(\"1\", {}, {\"\"});\n  ExpectNotEqual(\"2\", {\"abc\"}, {\"def\"});\n  ExpectNotEqual(\"3\", {\"abc\", \"--flag\"}, {\"abc\"});\n  ExpectNotEqual(\"4\", {\"abc\", \"--flag1\"}, {\"abc\", \"--flag2\"});\n  ExpectNotEqual(\"5\", {\"abc\", \"--flag1\", \"--flag2\"}, {\"abc\", \"--flag1\"});\n  ExpectNotEqual(\"6\", {\"abc\", \"arg\"}, {\"abc\"});\n  ExpectNotEqual(\"7\", {\"abc\", \"arg1\"}, {\"abc\", \"arg2\"});\n  ExpectNotEqual(\"8\", {\"abc\", \"arg1\", \"arg2\"}, {\"abc\", \"arg1\"});\n  ExpectNotEqual(\"9\", {\"abc\", \"--flag\", \"arg1\"}, {\"abc\", \"--flag\", \"arg2\"});\n\n  // However, the presence of an unnecessary \"--\" shouldn't affect what's\n  // constructed.\n  ExpectEqual(\"10\", {\"abc\", \"--flag\", \"arg\"}, {\"abc\", \"--flag\", \"--\", \"arg\"});\n}\n\nTEST(CommandLineTest, MoveAndCopy) {\n  const auto cl = CommandLineFromInitializerList(\n      {\"my_program\", \"--flag1=value1\", \"--flag2\", \"arg\"});\n\n  // Copy constructor.\n  CommandLine cl2(cl);\n  EXPECT_EQ(cl, cl2);\n  // Check that |option_index_| gets copied too.\n  EXPECT_EQ(\"value1\", cl2.GetOptionValueWithDefault(\"flag1\", \"nope\"));\n\n  // Move constructor.\n  CommandLine cl3(std::move(cl2));\n  EXPECT_EQ(cl, cl3);\n  EXPECT_EQ(\"value1\", cl3.GetOptionValueWithDefault(\"flag1\", \"nope\"));\n\n  // Copy assignment.\n  CommandLine cl4;\n  EXPECT_NE(cl, cl4);\n  cl4 = cl;\n  EXPECT_EQ(cl, cl4);\n  EXPECT_EQ(\"value1\", cl4.GetOptionValueWithDefault(\"flag1\", \"nope\"));\n\n  // Move assignment.\n  CommandLine cl5;\n  EXPECT_NE(cl, cl5);\n  cl5 = std::move(cl4);\n  EXPECT_EQ(cl, cl5);\n  EXPECT_EQ(\"value1\", cl5.GetOptionValueWithDefault(\"flag1\", \"nope\"));\n}\n\nvoid ToArgvHelper(const char* message, std::initializer_list<std::string> c) {\n  SCOPED_TRACE(message);\n  std::vector<std::string> argv = c;\n  auto cl = CommandLineFromInitializerList(c);\n  EXPECT_EQ(argv, CommandLineToArgv(cl));\n}\n\nTEST(CommandLineTest, CommandLineToArgv) {\n  ToArgvHelper(\"1\", {});\n  ToArgvHelper(\"2\", {\"\"});\n  ToArgvHelper(\"3\", {\"my_program\"});\n  ToArgvHelper(\"4\", {\"my_program\", \"--flag\"});\n  ToArgvHelper(\"5\", {\"my_program\", \"--flag1\", \"--flag2=value\"});\n  ToArgvHelper(\"6\", {\"my_program\", \"arg\"});\n  ToArgvHelper(\"7\", {\"my_program\", \"arg1\", \"arg2\"});\n  ToArgvHelper(\"8\", {\"my_program\", \"--flag1\", \"--flag2=value\", \"arg1\", \"arg2\"});\n  ToArgvHelper(\"9\", {\"my_program\", \"--flag\", \"--\", \"--not-a-flag\"});\n  ToArgvHelper(\"10\", {\"my_program\", \"--flag\", \"arg\", \"--\"});\n\n  // However, |CommandLineToArgv()| will \"strip\" an unneeded \"--\".\n  {\n    auto cl = CommandLineFromInitializerList({\"my_program\", \"--\"});\n    std::vector<std::string> argv = {\"my_program\"};\n    EXPECT_EQ(argv, CommandLineToArgv(cl));\n  }\n  {\n    auto cl =\n        CommandLineFromInitializerList({\"my_program\", \"--flag\", \"--\", \"arg\"});\n    std::vector<std::string> argv = {\"my_program\", \"--flag\", \"arg\"};\n    EXPECT_EQ(argv, CommandLineToArgv(cl));\n  }\n}\n\n}  // namespace\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/container.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_CONTAINER_H_\n#define CLAY_FML_CONTAINER_H_\n\n#include <functional>\n#include <map>\n#include <unordered_map>\n\nnamespace fml {\n\ntemplate <class Collection = std::unordered_map<class Key, class Value,\n                                                class Hash, class Equal>>\nvoid erase_if(Collection& container,  // NOLINT\n              std::function<bool(typename Collection::iterator)> predicate) {\n  auto it = container.begin();\n  while (it != container.end()) {\n    if (predicate(it)) {\n      it = container.erase(it);\n      continue;\n    }\n    it++;\n  }\n}\n\n}  // namespace fml\n\n#endif  // CLAY_FML_CONTAINER_H_\n"
  },
  {
    "path": "clay/fml/container_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <unordered_map>\n\n#include \"clay/fml/container.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace fml {\nnamespace {\n\nTEST(ContainerTest, MapEraseIf) {\n  std::unordered_map<int, int> map = {{0, 1}, {2, 3}, {4, 5}};\n\n  fml::erase_if(map, [](std::unordered_map<int, int>::iterator it) {\n    return it->first == 0 || it->second == 5;\n  });\n\n  EXPECT_EQ(map.size(), 1u);\n  EXPECT_TRUE(map.find(2) != map.end());\n}\n\n}  // namespace\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/endianness.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/endianness.h\"\n"
  },
  {
    "path": "clay/fml/endianness.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_ENDIANNESS_H_\n#define CLAY_FML_ENDIANNESS_H_\n\n#include <cstdint>\n#include <type_traits>\n#if defined(_MSC_VER)\n#include \"base/include/intrin.h\"  // NOLINT\n#endif\n\n#include \"build/build_config.h\"\n\n// Compiler intrinsics for flipping endianness.\n#if defined(_MSC_VER)\n#define FML_BYTESWAP_16(n) _byteswap_ushort(n)\n#define FML_BYTESWAP_32(n) _byteswap_ulong(n)\n#define FML_BYTESWAP_64(n) _byteswap_uint64(n)\n#else\n#define FML_BYTESWAP_16(n) __builtin_bswap16(n)\n#define FML_BYTESWAP_32(n) __builtin_bswap32(n)\n#define FML_BYTESWAP_64(n) __builtin_bswap64(n)\n#endif\n\nnamespace fml {\n\n/// @brief  Flips the endianness of the given value.\n///         The given value must be an integral type of size 1, 2, 4, or 8.\ntemplate <typename T, class = std::enable_if_t<std::is_integral_v<T>>>\nconstexpr T ByteSwap(T n) {\n  if constexpr (sizeof(T) == 1) {\n    return n;\n  } else if constexpr (sizeof(T) == 2) {\n    return (T)FML_BYTESWAP_16((uint16_t)n);\n  } else if constexpr (sizeof(T) == 4) {\n    return (T)FML_BYTESWAP_32((uint32_t)n);\n  } else if constexpr (sizeof(T) == 8) {\n    return (T)FML_BYTESWAP_64((uint64_t)n);\n  } else {\n    static_assert(!sizeof(T), \"Unsupported size\");\n  }\n}\n\n/// @brief  Convert a known big endian value to match the endianness of the\n///         current architecture. This is effectively a cross platform\n///         ntohl/ntohs (as network byte order is always Big Endian).\n///         The given value must be an integral type of size 1, 2, 4, or 8.\ntemplate <typename T, class = std::enable_if_t<std::is_integral_v<T>>>\nconstexpr T BigEndianToArch(T n) {\n#if FML_ARCH_CPU_LITTLE_ENDIAN\n  return ByteSwap<T>(n);\n#else\n  return n;\n#endif\n}\n\n/// @brief  Convert a known little endian value to match the endianness of the\n///         current architecture.\n///         The given value must be an integral type of size 1, 2, 4, or 8.\ntemplate <typename T, class = std::enable_if_t<std::is_integral_v<T>>>\nconstexpr T LittleEndianToArch(T n) {\n#if !FML_ARCH_CPU_LITTLE_ENDIAN\n  return ByteSwap<T>(n);\n#else\n  return n;\n#endif\n}\n\n}  // namespace fml\n\n#endif  // CLAY_FML_ENDIANNESS_H_\n"
  },
  {
    "path": "clay/fml/endianness_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/flutter/testing/testing.h\"\n#include \"clay/fml/endianness.h\"\n\nnamespace fml {\nnamespace testing {\n\nTEST(EndiannessTest, ByteSwap) {\n  ASSERT_EQ(ByteSwap<int16_t>(0x1122), 0x2211);\n  ASSERT_EQ(ByteSwap<int32_t>(0x11223344), 0x44332211);\n  ASSERT_EQ(ByteSwap<uint64_t>(0x1122334455667788), 0x8877665544332211);\n}\n\nTEST(EndiannessTest, BigEndianToArch) {\n#if FML_ARCH_CPU_LITTLE_ENDIAN\n  uint32_t expected = 0x44332211;\n#else\n  uint32_t expected = 0x11223344;\n#endif\n  ASSERT_EQ(BigEndianToArch(0x11223344u), expected);\n}\n\nTEST(EndiannessTest, LittleEndianToArch) {\n#if FML_ARCH_CPU_LITTLE_ENDIAN\n  uint32_t expected = 0x11223344;\n#else\n  uint32_t expected = 0x44332211;\n#endif\n  ASSERT_EQ(LittleEndianToArch(0x11223344u), expected);\n}\n\n}  // namespace testing\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/file.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/file.h\"\n\n#include \"base/include/fml/unique_fd.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace fml {\n\nstatic fml::UniqueFD CreateDirectory(const fml::UniqueFD& base_directory,\n                                     const std::vector<std::string>& components,\n                                     FilePermission permission, size_t index) {\n  FML_DCHECK(index <= components.size());\n\n  const char* file_path = components[index].c_str();\n\n  auto directory = OpenDirectory(base_directory, file_path, true, permission);\n\n  if (!directory.is_valid()) {\n    return {};\n  }\n\n  if (index == components.size() - 1) {\n    return directory;\n  }\n\n  return CreateDirectory(directory, components, permission, index + 1);\n}\n\nfml::UniqueFD CreateDirectory(const fml::UniqueFD& base_directory,\n                              const std::vector<std::string>& components,\n                              FilePermission permission) {\n  if (!IsDirectory(base_directory)) {\n    return {};\n  }\n\n  if (components.empty()) {\n    return {};\n  }\n\n  return CreateDirectory(base_directory, components, permission, 0);\n}\n\nScopedTemporaryDirectory::ScopedTemporaryDirectory()\n    : path_(CreateTemporaryDirectory()) {\n  if (path_ != \"\") {\n    dir_fd_ = OpenDirectory(path_.c_str(), false, FilePermission::kRead);\n  }\n}\n\nScopedTemporaryDirectory::~ScopedTemporaryDirectory() {\n  // POSIX requires the directory to be empty before UnlinkDirectory.\n  if (path_ != \"\") {\n    if (!RemoveFilesInDirectory(dir_fd_)) {\n      FML_LOG(ERROR) << \"Could not clean directory: \" << path_;\n    }\n  }\n\n  // Windows has to close UniqueFD first before UnlinkDirectory\n  dir_fd_.reset();\n  if (path_ != \"\") {\n    if (!UnlinkDirectory(path_.c_str())) {\n      FML_LOG(ERROR) << \"Could not remove directory: \" << path_;\n    }\n  }\n}\n\nbool VisitFilesRecursively(const fml::UniqueFD& directory,\n                           const FileVisitor& visitor) {\n  FileVisitor recursive_visitor = [&recursive_visitor, &visitor](\n                                      const UniqueFD& directory,\n                                      const std::string& filename) {\n    if (!visitor(directory, filename)) {\n      return false;\n    }\n    if (IsDirectory(directory, filename.c_str())) {\n      UniqueFD sub_dir = OpenDirectoryReadOnly(directory, filename.c_str());\n      if (!sub_dir.is_valid()) {\n        FML_LOG(ERROR) << \"Can't open sub-directory: \" << filename;\n        return true;\n      }\n      return VisitFiles(sub_dir, recursive_visitor);\n    }\n    return true;\n  };\n  return VisitFiles(directory, recursive_visitor);\n}\n\nfml::UniqueFD OpenFileReadOnly(const fml::UniqueFD& base_directory,\n                               const char* path) {\n  return OpenFile(base_directory, path, false, FilePermission::kRead);\n}\n\nfml::UniqueFD OpenDirectoryReadOnly(const fml::UniqueFD& base_directory,\n                                    const char* path) {\n  return OpenDirectory(base_directory, path, false, FilePermission::kRead);\n}\n\nbool RemoveFilesInDirectory(const fml::UniqueFD& directory) {\n  fml::FileVisitor recursive_cleanup = [&recursive_cleanup](\n                                           const fml::UniqueFD& directory,\n                                           const std::string& filename) {\n    bool removed;\n    if (fml::IsDirectory(directory, filename.c_str())) {\n      fml::UniqueFD sub_dir =\n          OpenDirectoryReadOnly(directory, filename.c_str());\n      removed = VisitFiles(sub_dir, recursive_cleanup) &&\n                fml::UnlinkDirectory(directory, filename.c_str());\n    } else {\n      removed = fml::UnlinkFile(directory, filename.c_str());\n    }\n    return removed;\n  };\n  return VisitFiles(directory, recursive_cleanup);\n}\n\nbool RemoveDirectoryRecursively(const fml::UniqueFD& parent,\n                                const char* directory_name) {\n  auto dir = fml::OpenDirectory(parent, directory_name, false,\n                                fml::FilePermission::kReadWrite);\n  return RemoveFilesInDirectory(dir) && UnlinkDirectory(parent, directory_name);\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/file.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_FILE_H_\n#define CLAY_FML_FILE_H_\n\n#include <functional>\n#include <initializer_list>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/fml/unique_fd.h\"\n\n#ifdef ERROR\n#undef ERROR\n#endif\n\nnamespace fml {\n\nclass Mapping;\n\nenum class FilePermission {\n  kRead,\n  kWrite,\n  kReadWrite,\n};\n\nstd::string CreateTemporaryDirectory();\n\n/// This can open a directory on POSIX, but not on Windows.\nfml::UniqueFD OpenFile(const char* path, bool create_if_necessary,\n                       FilePermission permission);\n\n/// This can open a directory on POSIX, but not on Windows.\nfml::UniqueFD OpenFile(const fml::UniqueFD& base_directory, const char* path,\n                       bool create_if_necessary, FilePermission permission);\n\n/// Helper method that calls `OpenFile` with create_if_necessary = false\n/// and permission = kRead.\n///\n/// This can open a directory on POSIX, but not on Windows.\nfml::UniqueFD OpenFileReadOnly(const fml::UniqueFD& base_directory,\n                               const char* path);\n\nfml::UniqueFD OpenDirectory(const char* path, bool create_if_necessary,\n                            FilePermission permission);\n\nfml::UniqueFD OpenDirectory(const fml::UniqueFD& base_directory,\n                            const char* path, bool create_if_necessary,\n                            FilePermission permission);\n\n/// Helper method that calls `OpenDirectory` with create_if_necessary = false\n/// and permission = kRead.\nfml::UniqueFD OpenDirectoryReadOnly(const fml::UniqueFD& base_directory,\n                                    const char* path);\n\nfml::UniqueFD Duplicate(fml::UniqueFD::element_type descriptor);\n\nbool IsDirectory(const fml::UniqueFD& directory);\n\nbool IsDirectory(const fml::UniqueFD& base_directory, const char* path);\n\n// Returns whether the given path is a file.\nbool IsFile(const std::string& path);\n\nbool TruncateFile(const fml::UniqueFD& file, size_t size);\n\nbool FileExists(const fml::UniqueFD& base_directory, const char* path);\n\nbool UnlinkDirectory(const char* path);\n\nbool UnlinkDirectory(const fml::UniqueFD& base_directory, const char* path);\n\nbool UnlinkFile(const char* path);\n\nbool UnlinkFile(const fml::UniqueFD& base_directory, const char* path);\n\nfml::UniqueFD CreateDirectory(const fml::UniqueFD& base_directory,\n                              const std::vector<std::string>& components,\n                              FilePermission permission);\n\nbool WriteAtomically(const fml::UniqueFD& base_directory, const char* file_name,\n                     const Mapping& mapping);\n\n/// Signature of a callback on a file in `directory` with `filename` (relative\n/// to `directory`). The returned bool should be false if and only if further\n/// traversal should be stopped. For example, a file-search visitor may return\n/// false when the file is found so no more visiting is needed.\nusing FileVisitor = std::function<bool(const fml::UniqueFD& directory,\n                                       const std::string& filename)>;\n\n/// Call `visitor` on all files inside the `directory` non-recursively. The\n/// trivial file \".\" and \"..\" will not be visited.\n///\n/// Return false if and only if the visitor returns false during the\n/// traversal.\n///\n/// If recursive visiting is needed, call `VisitFiles` inside the `visitor`, or\n/// use our helper method `VisitFilesRecursively`.\n///\n/// @see `VisitFilesRecursively`.\n/// @note Procedure doesn't copy all closures.\nbool VisitFiles(const fml::UniqueFD& directory, const FileVisitor& visitor);\n\n/// Recursively call `visitor` on all files inside the `directory`. Return false\n/// if and only if the visitor returns false during the traversal.\n///\n/// This is a helper method that wraps the general `VisitFiles` method. The\n/// `VisitFiles` is strictly more powerful as it has the access of the recursion\n/// stack to the file. For example, `VisitFiles` may be able to maintain a\n/// vector of directory names that lead to a file. That could be useful to\n/// compute the relative path between the root directory and the visited file.\n///\n/// @see `VisitFiles`.\n/// @note Procedure doesn't copy all closures.\nbool VisitFilesRecursively(const fml::UniqueFD& directory,\n                           const FileVisitor& visitor);\n\n/// Helper method to recursively remove files and subdirectories inside the\n/// directory. The directory itself will not be removed.\n///\n/// Return true if and only if all files have been successfully removed.\nbool RemoveFilesInDirectory(const fml::UniqueFD& directory);\n\n/// Helper method to recursively remove files and subdirectories inside the\n/// directory. The directory itself will also be removed.\n///\n/// Return true if and only if all files have been successfully removed.\nbool RemoveDirectoryRecursively(const fml::UniqueFD& parent,\n                                const char* directory_name);\n\nstruct FileInfo;\n\n/// Get size and last access time information from existing file.\nbool GetFileInfo(const fml::UniqueFD& file, FileInfo* info);\n\nstruct FileInfo {\n  size_t size = 0;\n  fml::TimePoint last_access_time = fml::TimePoint::Min();\n};\n\nclass ScopedTemporaryDirectory {\n public:\n  ScopedTemporaryDirectory();\n\n  ~ScopedTemporaryDirectory();\n\n  const std::string& path() const { return path_; }\n  const UniqueFD& fd() { return dir_fd_; }\n\n private:\n  std::string path_;\n  UniqueFD dir_fd_;\n};\n\n}  // namespace fml\n\n#endif  // CLAY_FML_FILE_H_\n"
  },
  {
    "path": "clay/fml/file_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/file.h\"\n\n#include <cstring>\n#include <memory>\n#include <set>\n#include <vector>\n\n#include \"base/include/fml/unique_fd.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/fml/paths.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nstatic bool WriteStringToFile(const fml::UniqueFD& fd,\n                              const std::string& contents) {\n  if (!fml::TruncateFile(fd, contents.size())) {\n    return false;\n  }\n\n  fml::FileMapping mapping(fd, {fml::FileMapping::Protection::kWrite});\n  if (mapping.GetSize() != contents.size()) {\n    return false;\n  }\n\n  if (mapping.GetMutableMapping() == nullptr) {\n    return false;\n  }\n\n  ::memmove(mapping.GetMutableMapping(), contents.data(), contents.size());\n  return true;\n}\n\nstatic std::string ReadStringFromFile(const fml::UniqueFD& fd) {\n  fml::FileMapping mapping(fd);\n\n  if (mapping.GetMapping() == nullptr) {\n    return nullptr;\n  }\n\n  return {reinterpret_cast<const char*>(mapping.GetMapping()),\n          mapping.GetSize()};\n}\n\nTEST(FileTest, CreateTemporaryAndUnlink) {\n  auto dir_name = fml::CreateTemporaryDirectory();\n  ASSERT_NE(dir_name, \"\");\n  auto dir =\n      fml::OpenDirectory(dir_name.c_str(), false, fml::FilePermission::kRead);\n  ASSERT_TRUE(dir.is_valid());\n  dir.reset();\n  ASSERT_TRUE(fml::UnlinkDirectory(dir_name.c_str()));\n}\n\nTEST(FileTest, ScopedTempDirIsValid) {\n  fml::ScopedTemporaryDirectory dir;\n  ASSERT_TRUE(dir.fd().is_valid());\n}\n\nTEST(FileTest, CanOpenFileForWriting) {\n  fml::ScopedTemporaryDirectory dir;\n  ASSERT_TRUE(dir.fd().is_valid());\n\n  auto fd =\n      fml::OpenFile(dir.fd(), \"some.txt\", true, fml::FilePermission::kWrite);\n  ASSERT_TRUE(fd.is_valid());\n  fd.reset();\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), \"some.txt\"));\n}\n\nTEST(FileTest, CanTruncateAndWrite) {\n  fml::ScopedTemporaryDirectory dir;\n  ASSERT_TRUE(dir.fd().is_valid());\n\n  std::string contents = \"some contents here\";\n\n  // On the first iteration, this tests writing and then reading a file that\n  // didn't exist yet. On the second iteration it tests truncating, writing,\n  // and reading a file that already existed.\n  for (int i = 0; i < 2; i++) {\n    {\n      auto fd = fml::OpenFile(dir.fd(), \"some.txt\", true,\n                              fml::FilePermission::kReadWrite);\n      ASSERT_TRUE(fd.is_valid());\n\n      ASSERT_TRUE(fml::TruncateFile(fd, contents.size()));\n\n      fml::FileMapping mapping(fd, {fml::FileMapping::Protection::kWrite});\n      ASSERT_EQ(mapping.GetSize(), contents.size());\n      ASSERT_NE(mapping.GetMutableMapping(), nullptr);\n\n      ::memcpy(mapping.GetMutableMapping(), contents.data(), contents.size());\n    }\n\n    {\n      auto fd = fml::OpenFile(dir.fd(), \"some.txt\", false,\n                              fml::FilePermission::kRead);\n      ASSERT_TRUE(fd.is_valid());\n\n      fml::FileMapping mapping(fd);\n      ASSERT_EQ(mapping.GetSize(), contents.size());\n\n      ASSERT_EQ(\n          0, ::memcmp(mapping.GetMapping(), contents.data(), contents.size()));\n    }\n  }\n\n  fml::UnlinkFile(dir.fd(), \"some.txt\");\n}\n\nTEST(FileTest, CreateDirectoryStructure) {\n  fml::ScopedTemporaryDirectory dir;\n\n  std::string contents = \"These are my contents\";\n  {\n    auto sub = fml::CreateDirectory(dir.fd(), {\"a\", \"b\", \"c\"},\n                                    fml::FilePermission::kReadWrite);\n    ASSERT_TRUE(sub.is_valid());\n    auto file = fml::OpenFile(sub, \"my_contents\", true,\n                              fml::FilePermission::kReadWrite);\n    ASSERT_TRUE(file.is_valid());\n    ASSERT_TRUE(WriteStringToFile(file, contents));\n  }\n\n  const char* file_path = \"a/b/c/my_contents\";\n\n  {\n    auto contents_file =\n        fml::OpenFile(dir.fd(), file_path, false, fml::FilePermission::kRead);\n    ASSERT_EQ(ReadStringFromFile(contents_file), contents);\n  }\n\n  // Cleanup.\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), file_path));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a/b/c\"));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a/b\"));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a\"));\n}\n\nTEST(FileTest, VisitFilesCanBeCalledTwice) {\n  fml::ScopedTemporaryDirectory dir;\n\n  {\n    auto file = fml::OpenFile(dir.fd(), \"my_contents\", true,\n                              fml::FilePermission::kReadWrite);\n    ASSERT_TRUE(file.is_valid());\n  }\n\n  int count;\n  fml::FileVisitor count_visitor = [&count](const fml::UniqueFD& directory,\n                                            const std::string& filename) {\n    count += 1;\n    return true;\n  };\n  count = 0;\n  fml::VisitFiles(dir.fd(), count_visitor);\n  ASSERT_EQ(count, 1);\n\n  // Without `rewinddir` in `VisitFiles`, the following check would fail.\n  count = 0;\n  fml::VisitFiles(dir.fd(), count_visitor);\n  ASSERT_EQ(count, 1);\n\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), \"my_contents\"));\n}\n\nTEST(FileTest, CanListFilesRecursively) {\n  fml::ScopedTemporaryDirectory dir;\n\n  {\n    auto c = fml::CreateDirectory(dir.fd(), {\"a\", \"b\", \"c\"},\n                                  fml::FilePermission::kReadWrite);\n    ASSERT_TRUE(c.is_valid());\n    auto file1 =\n        fml::OpenFile(c, \"file1\", true, fml::FilePermission::kReadWrite);\n    auto file2 =\n        fml::OpenFile(c, \"file2\", true, fml::FilePermission::kReadWrite);\n    auto d = fml::CreateDirectory(c, {\"d\"}, fml::FilePermission::kReadWrite);\n    ASSERT_TRUE(d.is_valid());\n    auto file3 =\n        fml::OpenFile(d, \"file3\", true, fml::FilePermission::kReadWrite);\n    ASSERT_TRUE(file1.is_valid());\n    ASSERT_TRUE(file2.is_valid());\n    ASSERT_TRUE(file3.is_valid());\n  }\n\n  std::set<std::string> names;\n  fml::FileVisitor visitor = [&names](const fml::UniqueFD& directory,\n                                      const std::string& filename) {\n    names.insert(filename);\n    return true;\n  };\n\n  fml::VisitFilesRecursively(dir.fd(), visitor);\n  ASSERT_EQ(names, std::set<std::string>(\n                       {\"a\", \"b\", \"c\", \"d\", \"file1\", \"file2\", \"file3\"}));\n\n  // Cleanup.\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), \"a/b/c/d/file3\"));\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), \"a/b/c/file1\"));\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), \"a/b/c/file2\"));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a/b/c/d\"));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a/b/c\"));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a/b\"));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a\"));\n}\n\nTEST(FileTest, CanStopVisitEarly) {\n  fml::ScopedTemporaryDirectory dir;\n\n  {\n    auto d = fml::CreateDirectory(dir.fd(), {\"a\", \"b\", \"c\", \"d\"},\n                                  fml::FilePermission::kReadWrite);\n    ASSERT_TRUE(d.is_valid());\n  }\n\n  std::set<std::string> names;\n  fml::FileVisitor visitor = [&names](const fml::UniqueFD& directory,\n                                      const std::string& filename) {\n    names.insert(filename);\n    return filename == \"c\" ? false : true;  // stop if c is found\n  };\n\n  // Check the d is not visited as we stop at c.\n  ASSERT_FALSE(fml::VisitFilesRecursively(dir.fd(), visitor));\n  ASSERT_EQ(names, std::set<std::string>({\"a\", \"b\", \"c\"}));\n\n  // Cleanup.\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a/b/c/d\"));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a/b/c\"));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a/b\"));\n  ASSERT_TRUE(fml::UnlinkDirectory(dir.fd(), \"a\"));\n}\n\nTEST(FileTest, AtomicWriteTest) {\n  fml::ScopedTemporaryDirectory dir;\n\n  const std::string contents = \"These are my contents.\";\n\n  auto data = std::make_unique<fml::DataMapping>(\n      std::vector<uint8_t>{contents.begin(), contents.end()});\n\n  // Write.\n  ASSERT_TRUE(fml::WriteAtomically(dir.fd(), \"precious_data\", *data));\n\n  // Read and verify.\n  ASSERT_EQ(contents,\n            ReadStringFromFile(fml::OpenFile(dir.fd(), \"precious_data\", false,\n                                             fml::FilePermission::kRead)));\n\n  // Cleanup.\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), \"precious_data\"));\n}\n\nTEST(FileTest, IgnoreBaseDirWhenPathIsAbsolute) {\n  fml::ScopedTemporaryDirectory dir;\n\n  // Make an absolute path.\n  std::string filename = \"filename.txt\";\n  std::string full_path =\n      fml::paths::AbsolutePath(fml::paths::JoinPaths({dir.path(), filename}));\n\n  const std::string contents = \"These are my contents.\";\n  auto data = std::make_unique<fml::DataMapping>(\n      std::vector<uint8_t>{contents.begin(), contents.end()});\n\n  // Write.\n  ASSERT_TRUE(fml::WriteAtomically(dir.fd(), full_path.c_str(), *data));\n\n  // Test existence.\n  ASSERT_TRUE(fml::FileExists(dir.fd(), full_path.c_str()));\n\n  // Read and verify.\n  ASSERT_EQ(contents,\n            ReadStringFromFile(fml::OpenFile(dir.fd(), full_path.c_str(), false,\n                                             fml::FilePermission::kRead)));\n\n  // Cleanup.\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), full_path.c_str()));\n}\n\nTEST(FileTest, EmptyMappingTest) {\n  fml::ScopedTemporaryDirectory dir;\n\n  {\n    auto file = fml::OpenFile(dir.fd(), \"my_contents\", true,\n                              fml::FilePermission::kReadWrite);\n\n    fml::FileMapping mapping(file);\n    ASSERT_TRUE(mapping.IsValid());\n    ASSERT_EQ(mapping.GetSize(), 0ul);\n    ASSERT_EQ(mapping.GetMapping(), nullptr);\n  }\n\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), \"my_contents\"));\n}\n\nTEST(FileTest, MappingDontNeedSafeTest) {\n  fml::ScopedTemporaryDirectory dir;\n\n  {\n    auto file = fml::OpenFile(dir.fd(), \"my_contents\", true,\n                              fml::FilePermission::kReadWrite);\n    WriteStringToFile(file, \"some content\");\n  }\n\n  {\n    auto file = fml::OpenFile(dir.fd(), \"my_contents\", false,\n                              fml::FilePermission::kRead);\n    fml::FileMapping mapping(file);\n    ASSERT_TRUE(mapping.IsValid());\n    ASSERT_EQ(mapping.GetMutableMapping(), nullptr);\n    ASSERT_TRUE(mapping.IsDontNeedSafe());\n  }\n\n  {\n    auto file = fml::OpenFile(dir.fd(), \"my_contents\", false,\n                              fml::FilePermission::kReadWrite);\n    fml::FileMapping mapping(file, {fml::FileMapping::Protection::kRead,\n                                    fml::FileMapping::Protection::kWrite});\n    ASSERT_TRUE(mapping.IsValid());\n    ASSERT_NE(mapping.GetMutableMapping(), nullptr);\n    ASSERT_FALSE(mapping.IsDontNeedSafe());\n  }\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), \"my_contents\"));\n}\n\nTEST(FileTest, FileTestsWork) {\n  fml::ScopedTemporaryDirectory dir;\n  ASSERT_TRUE(dir.fd().is_valid());\n  const char* filename = \"some.txt\";\n  auto fd =\n      fml::OpenFile(dir.fd(), filename, true, fml::FilePermission::kWrite);\n  ASSERT_TRUE(fd.is_valid());\n  fd.reset();\n  ASSERT_TRUE(fml::FileExists(dir.fd(), filename));\n  ASSERT_TRUE(\n      fml::IsFile(fml::paths::JoinPaths({dir.path(), filename}).c_str()));\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), filename));\n}\n\nTEST(FileTest, FileTestsSupportsUnicode) {\n  fml::ScopedTemporaryDirectory dir;\n  ASSERT_TRUE(dir.fd().is_valid());\n  const char* filename = u8\"äëïöüテスト☃\";\n  auto fd =\n      fml::OpenFile(dir.fd(), filename, true, fml::FilePermission::kWrite);\n  ASSERT_TRUE(fd.is_valid());\n  fd.reset();\n  ASSERT_TRUE(fml::FileExists(dir.fd(), filename));\n  ASSERT_TRUE(\n      fml::IsFile(fml::paths::JoinPaths({dir.path(), filename}).c_str()));\n  ASSERT_TRUE(fml::UnlinkFile(dir.fd(), filename));\n}\n"
  },
  {
    "path": "clay/fml/fml.gni",
    "content": "# Copyright 2022 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//build_overrides/fml.gni\")\nimport(\"../../config.gni\")\n\n_src = get_path_info(\"./\", \"abspath\")\n\n# common\nfml_common_shared_sources = [\n  \"$_src/ascii_trie.cc\",\n  \"$_src/ascii_trie.h\",\n  \"$_src/atomic_sequence_num.h\",\n  \"$_src/command_line.cc\",\n  \"$_src/command_line.h\",\n  \"$_src/posix_wrappers.h\",\n  \"$_src/size.h\",\n  \"$_src/status.h\",\n]\n\n# fml_darwin_shared_sources\nfml_darwin_shared_sources = [\n  \"$_src/platform/darwin/cf_utils.cc\",\n  \"$_src/platform/darwin/cf_utils.h\",\n  \"$_src/platform/darwin/platform_version.h\",\n  \"$_src/platform/darwin/platform_version.mm\",\n  \"$_src/platform/darwin/scoped_block.h\",\n  \"$_src/platform/darwin/scoped_block.mm\",\n  \"$_src/platform/darwin/scoped_nsobject.h\",\n  \"$_src/platform/darwin/scoped_nsobject.mm\",\n  \"$_src/platform/darwin/string_range_sanitization.h\",\n  \"$_src/platform/darwin/string_range_sanitization.mm\",\n]\n\n# fml_darwin_shared_sources end\n\nfml_windows_shared_sources = [ \"$_src/platform/win/posix_wrappers_win.cc\" ]\n\n# log\nfml_log_sources = [\n  \"$_src/log_level.h\",\n  \"$_src/log_settings.cc\",\n  \"$_src/log_settings.h\",\n  \"$_src/logging.cc\",\n  \"$_src/logging.h\",\n]\n\n# backtrace\nfml_backtrace_sources = [ \"$_src/backtrace.h\" ]\nif (is_mac || is_ios || is_linux || is_tvos) {\n  fml_backtrace_sources += [ \"backtrace.cc\" ]\n} else if (!is_android) {\n  fml_backtrace_sources += [ \"backtrace_stub.cc\" ]\n} else if (is_android) {\n  fml_backtrace_sources += [ \"backtrace_android.cc\" ]\n}\n\n# icu\nfml_icu_util_sources = [\n  \"$_src/icu_util.cc\",\n  \"$_src/icu_util.h\",\n]\n\n# native library\nfml_native_library_sources = [ \"$_src/native_library.h\" ]\nif (is_win) {\n  fml_native_library_sources += [ \"$_src/platform/win/native_library_win.cc\" ]\n} else {\n  fml_native_library_sources += [ native_library_posix ]\n}\n\n# file\nfml_file_sources = [\n  \"$_src/file.cc\",\n  \"$_src/file.h\",\n  \"$_src/mapping.cc\",\n  \"$_src/mapping.h\",\n  \"$_src/paths.cc\",\n  \"$_src/paths.h\",\n]\nif (is_ios || is_mac || is_tvos) {\n  fml_file_sources += [ \"$_src/platform/darwin/paths_darwin.mm\" ]\n}\n\nif (is_win) {\n  fml_file_sources += [\n    \"$_src/platform/win/command_line_win.cc\",\n    \"$_src/platform/win/errors_win.cc\",\n    \"$_src/platform/win/errors_win.h\",\n    \"$_src/platform/win/file_win.cc\",\n    \"$_src/platform/win/mapping_win.cc\",\n    \"$_src/platform/win/paths_win.cc\",\n  ]\n} else {\n  fml_file_sources += [\n    \"$_src/platform/posix/command_line_posix.cc\",\n    \"$_src/platform/posix/file_posix.cc\",\n    \"$_src/platform/posix/mapping_posix.cc\",\n    \"$_src/platform/posix/paths_posix.cc\",\n    \"$_src/platform/posix/posix_wrappers_posix.cc\",\n  ]\n}\n\nif (is_harmony) {\n  fml_file_sources += [\n    \"platform/harmony/paths_harmony.cc\",\n    \"platform/harmony/paths_harmony.h\",\n  ]\n} else if (is_linux) {\n  fml_file_sources += [ \"$_src/platform/linux/paths_linux.cc\" ]\n} else if (is_android) {\n  fml_file_sources += [\n    \"$_src/platform/android/jni_android.cc\",\n    \"$_src/platform/android/jni_android.h\",\n    \"$_src/platform/android/jni_weak_ref.cc\",\n    \"$_src/platform/android/jni_weak_ref.h\",\n    \"$_src/platform/android/paths_android.cc\",\n    \"$_src/platform/android/paths_android.h\",\n    \"$_src/platform/linux/proc_maps_linux.cc\",\n    \"$_src/platform/linux/proc_maps_linux.h\",\n  ]\n}\n\n# string\nfml_string_conversion_sources = [\n  \"$_src/base32.cc\",\n  \"$_src/base32.h\",\n  \"$_src/base64.cc\",\n  \"$_src/base64.h\",\n  \"$_src/hex_codec.cc\",\n  \"$_src/hex_codec.h\",\n]\nif (is_win) {\n  fml_string_conversion_sources += [\n    \"$_src/platform/win/wstring_conversion.cc\",\n    \"$_src/platform/win/wstring_conversion.h\",\n  ]\n}\n"
  },
  {
    "path": "clay/fml/hex_codec.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/hex_codec.h\"\n\n#include <string>\n\n#include \"clay/fml/base32.h\"\n\nnamespace fml {\n\nstatic constexpr char kEncoding[] = \"0123456789abcdef\";\n\nstd::string HexEncode(std::string_view input) {\n  std::string result;\n  result.reserve(input.size() * 2);\n  for (char c : input) {\n    uint8_t b = static_cast<uint8_t>(c);\n    result.push_back(kEncoding[b >> 4]);\n    result.push_back(kEncoding[b & 0xF]);\n  }\n  return result;\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/hex_codec.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_HEX_CODEC_H_\n#define CLAY_FML_HEX_CODEC_H_\n\n#include <string>\n#include <string_view>\n\nnamespace fml {\n\nstd::string HexEncode(std::string_view input);\n\n}  // namespace fml\n\n#endif  // CLAY_FML_HEX_CODEC_H_\n"
  },
  {
    "path": "clay/fml/hex_codec_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/hex_codec.h\"\n\n#include <iostream>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nTEST(HexCodecTest, CanEncode) {\n  {\n    auto result = fml::HexEncode(\"hello\");\n    ASSERT_EQ(result, \"68656c6c6f\");\n  }\n\n  {\n    auto result = fml::HexEncode(\"\");\n    ASSERT_EQ(result, \"\");\n  }\n\n  {\n    auto result = fml::HexEncode(\"1\");\n    ASSERT_EQ(result, \"31\");\n  }\n\n  {\n    auto result = fml::HexEncode(std::string_view(\"\\xFF\\xFE\\x00\\x01\", 4));\n    ASSERT_EQ(result, \"fffe0001\");\n  }\n}\n"
  },
  {
    "path": "clay/fml/icu_util.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/icu_util.h\"\n\n#include <memory>\n#include <mutex>\n#include <utility>\n\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/fml/native_library.h\"\n#include \"clay/fml/paths.h\"\n#include \"third_party/icu/source/common/unicode/udata.h\"\n\nnamespace fml {\nnamespace icu {\n\nclass ICUContext {\n public:\n  explicit ICUContext(const std::string& icu_data_path) : valid_(false) {\n    valid_ = SetupMapping(icu_data_path) && SetupICU();\n  }\n\n  explicit ICUContext(std::unique_ptr<Mapping> mapping)\n      : mapping_(std::move(mapping)) {\n    valid_ = SetupICU();\n  }\n\n  ~ICUContext() = default;\n\n  bool SetupMapping(const std::string& icu_data_path) {\n    // Check if the path exists and it readable directly.\n    auto fd =\n        fml::OpenFile(icu_data_path.c_str(), false, fml::FilePermission::kRead);\n\n    // Check the path relative to the current executable.\n    if (!fd.is_valid()) {\n      auto directory = fml::paths::GetExecutableDirectoryPath();\n\n      if (!directory.first) {\n        return false;\n      }\n\n      std::string path_relative_to_executable =\n          paths::JoinPaths({directory.second, icu_data_path});\n\n      fd = fml::OpenFile(path_relative_to_executable.c_str(), false,\n                         fml::FilePermission::kRead);\n    }\n\n    if (!fd.is_valid()) {\n      return false;\n    }\n\n    std::initializer_list<FileMapping::Protection> protection = {\n        fml::FileMapping::Protection::kRead};\n\n    auto file_mapping = std::make_unique<FileMapping>(fd, protection);\n\n    if (file_mapping->GetSize() != 0) {\n      mapping_ = std::move(file_mapping);\n      return true;\n    }\n\n    return false;\n  }\n\n  bool SetupICU() {\n    if (GetSize() == 0) {\n      return false;\n    }\n\n    UErrorCode err_code = U_ZERO_ERROR;\n    udata_setCommonData(GetMapping(), &err_code);\n    return (err_code == U_ZERO_ERROR);\n  }\n\n  const uint8_t* GetMapping() const {\n    return mapping_ ? mapping_->GetMapping() : nullptr;\n  }\n\n  size_t GetSize() const { return mapping_ ? mapping_->GetSize() : 0; }\n\n  bool IsValid() const { return valid_; }\n\n private:\n  bool valid_;\n  std::unique_ptr<Mapping> mapping_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ICUContext);\n};\n\nvoid InitializeICUOnce(const std::string& icu_data_path) {\n  static ICUContext* context = new ICUContext(icu_data_path);\n  FML_DCHECK(context->IsValid())\n      << \"Must be able to initialize the ICU context. Tried: \" << icu_data_path;\n}\n\nstd::once_flag g_icu_init_flag;\nvoid InitializeICU(const std::string& icu_data_path) {\n  std::call_once(g_icu_init_flag,\n                 // NOLINTNEXTLINE\n                 [&icu_data_path]() { InitializeICUOnce(icu_data_path); });\n}\n\nvoid InitializeICUFromMappingOnce(std::unique_ptr<Mapping> mapping) {\n  static ICUContext* context = new ICUContext(std::move(mapping));\n  FML_DCHECK(context->IsValid())\n      << \"Unable to initialize the ICU context from a mapping.\";\n}\n\nvoid InitializeICUFromMapping(std::unique_ptr<Mapping> mapping) {\n  std::call_once(g_icu_init_flag, [mapping = std::move(mapping)]() mutable {\n    InitializeICUFromMappingOnce(std::move(mapping));\n  });\n}\n\n}  // namespace icu\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/icu_util.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_ICU_UTIL_H_\n#define CLAY_FML_ICU_UTIL_H_\n\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/mapping.h\"\n\nnamespace fml {\nnamespace icu {\n\nvoid InitializeICU(const std::string& icu_data_path = \"\");\n\nvoid InitializeICUFromMapping(std::unique_ptr<Mapping> mapping);\n\n}  // namespace icu\n}  // namespace fml\n\n#endif  // CLAY_FML_ICU_UTIL_H_\n"
  },
  {
    "path": "clay/fml/log_level.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_LOG_LEVEL_H_\n#define CLAY_FML_LOG_LEVEL_H_\n\nnamespace fml {\n\ntypedef int LogSeverity;\n\n// Default log levels. Negative values can be used for verbose log levels.\nconstexpr LogSeverity LOG_INFO = 0;\nconstexpr LogSeverity LOG_WARNING = 1;\nconstexpr LogSeverity LOG_ERROR = 2;\nconstexpr LogSeverity LOG_FATAL = 3;\nconstexpr LogSeverity LOG_NUM_SEVERITIES = 4;\n\n// One of the Windows headers defines ERROR to 0. This makes the token\n// concatenation in FML_LOG(ERROR) to resolve to LOG_0. We define this back to\n// the appropriate log level. See more:\n// https://github.com/flutter/engine/pull/6677\n#if defined(_WIN32) && !defined(LOG_0)\n#define LOG_0 LOG_ERROR\n#endif\n\n// LOG_DFATAL is LOG_FATAL in debug mode, ERROR in normal mode\n#ifdef NDEBUG\nconst LogSeverity LOG_DFATAL = LOG_ERROR;\n#else\nconst LogSeverity LOG_DFATAL = LOG_FATAL;\n#endif\n\n}  // namespace fml\n\n#endif  // CLAY_FML_LOG_LEVEL_H_\n"
  },
  {
    "path": "clay/fml/log_settings.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// This file is intentionally left empty. The implementations have been moved\n// inline to log_settings.h to act as a bridge to the new logging system in\n// //lynx/base.\n"
  },
  {
    "path": "clay/fml/log_settings.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_LOG_SETTINGS_H_\n#define CLAY_FML_LOG_SETTINGS_H_\n\n#include \"clay/fml/logging.h\"  // The new, unified bridge header\n\nnamespace fml {\n\n// The original LogSettings struct. Kept for source compatibility.\nstruct LogSettings {\n  LogSeverity min_log_level = LOG_INFO;\n};\n\n// DEPRECATED: Redirecting to new base logging implementation.\ninline void SetLogSettings(const LogSettings& settings) {\n  ::lynx::base::logging::SetMinLogLevel(settings.min_log_level);\n}\n\n// DEPRECATED: Redirecting to new base logging implementation.\ninline LogSettings GetLogSettings() {\n  return {::lynx::base::logging::GetMinLogLevel()};\n}\n\n// DEPRECATED: Redirecting to new base logging implementation.\ninline int GetMinLogLevel() { return ::lynx::base::logging::GetMinLogLevel(); }\n\nclass ScopedSetLogSettings {\n public:\n  explicit ScopedSetLogSettings(const LogSettings& settings) {\n    old_level_ = ::lynx::base::logging::GetMinLogLevel();\n    ::lynx::base::logging::SetMinLogLevel(settings.min_log_level);\n  }\n  ~ScopedSetLogSettings() { ::lynx::base::logging::SetMinLogLevel(old_level_); }\n\n private:\n  int old_level_;\n};\n\n}  // namespace fml\n\n#endif  // CLAY_FML_LOG_SETTINGS_H_\n"
  },
  {
    "path": "clay/fml/logging.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// This file is intentionally left empty.\n// The FML logging implementation has been migrated to\n// lynx/base/include/log/logging.h and fml/logging.h now serves as a\n// compatibility bridge. All implementations have been removed to prevent\n// zombie code and compilation errors.\n"
  },
  {
    "path": "clay/fml/logging.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_LOGGING_H_\n#define CLAY_FML_LOGGING_H_\n\n// This file is a compatibility bridge to the new logging implementation in\n// lynx/base/include/log/logging.h. All FML_... macros are redefined in terms\n// of the new BASE_... and standard macros.\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/log/logging.h\"\n#include \"build/build_config.h\"\n// #include \"clay/fml/log_level.h\"\nnamespace fml {\nusing LogSeverity = ::lynx::base::logging::LogSeverity;\nconstexpr LogSeverity LOG_INFO = ::lynx::base::logging::LOG_INFO;\nconstexpr LogSeverity LOG_WARNING = ::lynx::base::logging::LOG_WARNING;\nconstexpr LogSeverity LOG_ERROR = ::lynx::base::logging::LOG_ERROR;\nconstexpr LogSeverity LOG_FATAL = ::lynx::base::logging::LOG_FATAL;\n}  // namespace fml\n\n// --- Stream-based Macros ---\n\n#define FML_LOG(severity) BASE_LOG(severity)\n\n#ifndef NDEBUG\n#define FML_DLOG(severity) FML_LOG(severity)\n#else\n#define FML_DLOG(severity)                            \\\n  true ? (void)0                                      \\\n       : ::lynx::base::logging::LogMessageVoidify() & \\\n             ::lynx::base::logging::NullLogStream()\n#endif\n\n// VLOG is for verbose debugging. We map it to a disabled stream to ensure\n// compatibility without adding complexity, as it's not critical.\n#define FML_VLOG(verbose_level)                       \\\n  true ? (void)0                                      \\\n       : ::lynx::base::logging::LogMessageVoidify() & \\\n             ::lynx::base::logging::NullLogStream()\n\n// --- Assertion and Check Macros ---\n\n#define FML_CHECK(condition) CHECK(condition)\n#define FML_DCHECK(condition) DCHECK(condition)\n\n// --- Utility Macros ---\n\n#define FML_UNREACHABLE() NOTREACHED()\n#define FML_UNIMPLEMENTED() UNIMPLEMENTED()\n\n// --- Deprecated Macros ---\n// These macros are part of the old implementation and are no longer needed.\n// Redefined here for any code that might still use them transitively.\n#define FML_EAT_STREAM_PARAMETERS(ignored)            \\\n  true ? (void)0                                      \\\n       : ::lynx::base::logging::LogMessageVoidify() & \\\n             ::lynx::base::logging::NullLogStream()\n\n#define FML_LOG_STREAM(severity) LOG_STREAM(severity)\n#define FML_VLOG_STREAM(verbose_level)                 \\\n  (true ? (void)0                                      \\\n        : ::lynx::base::logging::LogMessageVoidify() & \\\n              ::lynx::base::logging::NullLogStream())\n#define FML_LOG_IS_ON(severity) LOG_IS_ON(severity)\n#define FML_VLOG_IS_ON(verbose_level) false\n\n#endif  // CLAY_FML_LOGGING_H_\n"
  },
  {
    "path": "clay/fml/logging_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"build/build_config.h\"\n#include \"clay/fml/log_settings.h\"\n#include \"clay/fml/logging.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#if defined(OS_FUCHSIA)\n#include <lib/syslog/global.h>\n#include <lib/syslog/logger.h>\n#include <lib/syslog/wire_format.h>\n#include <lib/zx/socket.h>\n\n#include \"base/include/gmock/gmock.h\"\n#endif\n\nnamespace fml {\nnamespace testing {\n\nTEST(Logging, Unreachable) {\n#ifndef NDEBUG\n  ASSERT_DEATH(FML_UNREACHABLE(), \"\");\n#else\n  FML_UNREACHABLE();\n#endif\n}\n\n#if !defined(NDEBUG)\nTEST(Logging, Dcheck) {\n  bool called = false;\n  FML_DCHECK(true);\n  FML_DCHECK(true) << (called = true);\n  ASSERT_FALSE(called);\n  ASSERT_DEATH(FML_DCHECK(false), \"\");\n}\n#endif\n\nTEST(Logging, Fatal) { ASSERT_DEATH(FML_LOG(FATAL) << \"fatal\", \"\"); }\n\n#if defined(OS_FUCHSIA)\n\nstruct LogPacket {\n  fx_log_metadata_t metadata;\n  std::vector<std::string> tags;\n  std::string message;\n};\n\nclass LoggingSocketTest : public ::testing::Test {\n protected:\n  void SetUp() override {\n    zx::socket local;\n    ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_DATAGRAM, &local, &socket_));\n\n    fx_logger_config_t config = {\n        .min_severity = FX_LOG_INFO,\n        .log_sink_socket = local.release(),\n        .tags = nullptr,\n        .num_tags = 0,\n    };\n\n    fx_log_reconfigure(&config);\n  }\n\n  LogPacket ReadPacket() {\n    LogPacket result;\n    fx_log_packet_t packet;\n    zx_status_t res = socket_.read(0, &packet, sizeof(packet), nullptr);\n    EXPECT_EQ(ZX_OK, res);\n    result.metadata = packet.metadata;\n    int pos = 0;\n    while (packet.data[pos]) {\n      int tag_len = packet.data[pos++];\n      result.tags.emplace_back(packet.data + pos, tag_len);\n      pos += tag_len;\n    }\n    result.message.append(packet.data + pos + 1);\n    return result;\n  }\n\n  void ReadPacketAndCompare(fx_log_severity_t severity,\n                            const std::string& message,\n                            const std::vector<std::string>& tags = {}) {\n    LogPacket packet = ReadPacket();\n    EXPECT_EQ(severity, packet.metadata.severity);\n    EXPECT_THAT(packet.message, ::testing::EndsWith(message));\n    EXPECT_EQ(tags, packet.tags);\n  }\n\n  void CheckSocketEmpty() {\n    zx_info_socket_t info = {};\n    zx_status_t status =\n        socket_.get_info(ZX_INFO_SOCKET, &info, sizeof(info), nullptr, nullptr);\n    ASSERT_EQ(ZX_OK, status);\n    EXPECT_EQ(0u, info.rx_buf_available);\n  }\n\n  zx::socket socket_;\n};\n\nTEST_F(LoggingSocketTest, UseSyslogOnFuchsia) {\n  const char* msg1 = \"test message\";\n  const char* msg2 = \"hello\";\n  const char* msg3 = \"logging\";\n  const char* msg4 = \"Another message\";\n  const char* msg5 = \"Foo\";\n\n  fml::SetLogSettings({.min_log_level = -1});\n\n  FML_LOG(INFO) << msg1;\n  ReadPacketAndCompare(FX_LOG_INFO, msg1);\n  CheckSocketEmpty();\n\n  FML_LOG(WARNING) << msg2;\n  ReadPacketAndCompare(FX_LOG_WARNING, msg2);\n  CheckSocketEmpty();\n\n  FML_LOG(ERROR) << msg3;\n  ReadPacketAndCompare(FX_LOG_ERROR, msg3);\n  CheckSocketEmpty();\n\n  FML_VLOG(1) << msg4;\n  ReadPacketAndCompare(fx_log_severity_from_verbosity(1), msg4);\n  CheckSocketEmpty();\n\n  // VLOG(2) is not enabled so the log gets dropped.\n  FML_VLOG(2) << msg5;\n  CheckSocketEmpty();\n}\n\n#endif\n\n}  // namespace testing\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/mapping.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/mapping.h\"\n\n#include <algorithm>\n#include <cstdlib>\n#include <cstring>\n#include <memory>\n#include <sstream>\n#include <utility>\n\nnamespace fml {\n\n// FileMapping\n\nuint8_t* FileMapping::GetMutableMapping() { return mutable_mapping_; }\n\nstd::unique_ptr<FileMapping> FileMapping::CreateReadOnly(\n    const std::string& path) {\n  return CreateReadOnly(OpenFile(path.c_str(), false, FilePermission::kRead),\n                        \"\");\n}\n\nstd::unique_ptr<FileMapping> FileMapping::CreateReadOnly(\n    const fml::UniqueFD& base_fd, const std::string& sub_path) {\n  if (sub_path.size() != 0) {\n    return CreateReadOnly(\n        OpenFile(base_fd, sub_path.c_str(), false, FilePermission::kRead), \"\");\n  }\n\n  auto mapping = std::make_unique<FileMapping>(\n      base_fd, std::initializer_list<Protection>{Protection::kRead});\n\n  if (!mapping->IsValid()) {\n    return nullptr;\n  }\n\n  return mapping;\n}\n\nstd::unique_ptr<FileMapping> FileMapping::CreateReadExecute(\n    const std::string& path) {\n  return CreateReadExecute(\n      OpenFile(path.c_str(), false, FilePermission::kRead));\n}\n\nstd::unique_ptr<FileMapping> FileMapping::CreateReadExecute(\n    const fml::UniqueFD& base_fd, const std::string& sub_path) {\n  if (sub_path.size() != 0) {\n    return CreateReadExecute(\n        OpenFile(base_fd, sub_path.c_str(), false, FilePermission::kRead), \"\");\n  }\n\n  auto mapping = std::make_unique<FileMapping>(\n      base_fd, std::initializer_list<Protection>{Protection::kRead,\n                                                 Protection::kExecute});\n\n  if (!mapping->IsValid()) {\n    return nullptr;\n  }\n\n  return mapping;\n}\n\n// Data Mapping\n\nDataMapping::DataMapping(std::vector<uint8_t> data) : data_(std::move(data)) {}\n\nDataMapping::DataMapping(const std::string& string)\n    : data_(string.begin(), string.end()) {}\n\nDataMapping::~DataMapping() = default;\n\nsize_t DataMapping::GetSize() const { return data_.size(); }\n\nconst uint8_t* DataMapping::GetMapping() const { return data_.data(); }\n\nbool DataMapping::IsDontNeedSafe() const { return false; }\n\n// NonOwnedMapping\nNonOwnedMapping::NonOwnedMapping(const uint8_t* data, size_t size,\n                                 const ReleaseProc& release_proc,\n                                 bool dontneed_safe)\n    : data_(data),\n      size_(size),\n      release_proc_(release_proc),\n      dontneed_safe_(dontneed_safe) {}\n\nNonOwnedMapping::~NonOwnedMapping() {\n  if (release_proc_) {\n    release_proc_(data_, size_);\n  }\n}\n\nsize_t NonOwnedMapping::GetSize() const { return size_; }\n\nconst uint8_t* NonOwnedMapping::GetMapping() const { return data_; }\n\nbool NonOwnedMapping::IsDontNeedSafe() const { return dontneed_safe_; }\n\n// MallocMapping\nMallocMapping::MallocMapping() : data_(nullptr), size_(0) {}\n\nMallocMapping::MallocMapping(uint8_t* data, size_t size)\n    : data_(data), size_(size) {}\n\nMallocMapping::MallocMapping(fml::MallocMapping&& mapping)\n    : data_(mapping.data_), size_(mapping.size_) {\n  mapping.data_ = nullptr;\n  mapping.size_ = 0;\n}\n\nMallocMapping::~MallocMapping() {\n  free(data_);\n  data_ = nullptr;\n}\n\nMallocMapping MallocMapping::Copy(const void* begin, size_t length) {\n  auto result =\n      MallocMapping(reinterpret_cast<uint8_t*>(malloc(length)), length);\n  FML_CHECK(result.GetMapping() != nullptr);\n  memcpy(const_cast<uint8_t*>(result.GetMapping()), begin, length);\n  return result;\n}\n\nsize_t MallocMapping::GetSize() const { return size_; }\n\nconst uint8_t* MallocMapping::GetMapping() const { return data_; }\n\nbool MallocMapping::IsDontNeedSafe() const { return false; }\n\nuint8_t* MallocMapping::Release() {\n  uint8_t* result = data_;\n  data_ = nullptr;\n  size_ = 0;\n  return result;\n}\n\n// Symbol Mapping\n\nSymbolMapping::SymbolMapping(fml::RefPtr<fml::NativeLibrary> native_library,\n                             const char* symbol_name)\n    : native_library_(std::move(native_library)) {\n  if (native_library_ && symbol_name != nullptr) {\n    mapping_ = native_library_->ResolveSymbol(symbol_name);\n\n    if (mapping_ == nullptr) {\n      // Apparently, dart_bootstrap seems to account for the Mac behavior of\n      // requiring the underscore prefixed symbol name on non-Mac platforms as\n      // well. As a fallback, check the underscore prefixed variant of the\n      // symbol name and allow callers to not have handle this on a per platform\n      // toolchain quirk basis.\n\n      std::stringstream underscore_symbol_name;\n      underscore_symbol_name << \"_\" << symbol_name;\n      mapping_ =\n          native_library_->ResolveSymbol(underscore_symbol_name.str().c_str());\n    }\n  }\n}\n\nSymbolMapping::~SymbolMapping() = default;\n\nsize_t SymbolMapping::GetSize() const { return 0; }\n\nconst uint8_t* SymbolMapping::GetMapping() const { return mapping_; }\n\nbool SymbolMapping::IsDontNeedSafe() const { return true; }\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/mapping.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_MAPPING_H_\n#define CLAY_FML_MAPPING_H_\n\n#include <initializer_list>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/unique_fd.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/fml/native_library.h\"\n\nnamespace fml {\n\nclass Mapping {\n public:\n  Mapping();\n\n  virtual ~Mapping();\n\n  virtual size_t GetSize() const = 0;\n\n  virtual const uint8_t* GetMapping() const = 0;\n\n  // Whether calling madvise(DONTNEED) on the mapping is non-destructive.\n  // Generally true for file-mapped memory and false for anonymous memory.\n  virtual bool IsDontNeedSafe() const = 0;\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(Mapping);\n};\n\nclass FileMapping final : public Mapping {\n public:\n  enum class Protection {\n    kRead,\n    kWrite,\n    kExecute,\n  };\n\n  explicit FileMapping(const fml::UniqueFD& fd,\n                       std::initializer_list<Protection> protection = {\n                           Protection::kRead});\n\n  ~FileMapping() override;\n\n  static std::unique_ptr<FileMapping> CreateReadOnly(const std::string& path);\n\n  static std::unique_ptr<FileMapping> CreateReadOnly(\n      const fml::UniqueFD& base_fd, const std::string& sub_path = \"\");\n\n  static std::unique_ptr<FileMapping> CreateReadExecute(\n      const std::string& path);\n\n  static std::unique_ptr<FileMapping> CreateReadExecute(\n      const fml::UniqueFD& base_fd, const std::string& sub_path = \"\");\n\n  // |Mapping|\n  size_t GetSize() const override;\n\n  // |Mapping|\n  const uint8_t* GetMapping() const override;\n\n  // |Mapping|\n  bool IsDontNeedSafe() const override;\n\n  uint8_t* GetMutableMapping();\n\n  bool IsValid() const;\n\n private:\n  bool valid_ = false;\n  size_t size_ = 0;\n  uint8_t* mapping_ = nullptr;\n  uint8_t* mutable_mapping_ = nullptr;\n\n#if OS_WIN\n  fml::UniqueFD mapping_handle_;\n#endif\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(FileMapping);\n};\n\nclass DataMapping final : public Mapping {\n public:\n  explicit DataMapping(std::vector<uint8_t> data);\n\n  explicit DataMapping(const std::string& string);\n\n  ~DataMapping() override;\n\n  // |Mapping|\n  size_t GetSize() const override;\n\n  // |Mapping|\n  const uint8_t* GetMapping() const override;\n\n  // |Mapping|\n  bool IsDontNeedSafe() const override;\n\n private:\n  std::vector<uint8_t> data_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(DataMapping);\n};\n\nclass NonOwnedMapping final : public Mapping {\n public:\n  using ReleaseProc = std::function<void(const uint8_t* data, size_t size)>;\n  NonOwnedMapping(const uint8_t* data, size_t size,\n                  const ReleaseProc& release_proc = nullptr,\n                  bool dontneed_safe = false);\n\n  ~NonOwnedMapping() override;\n\n  // |Mapping|\n  size_t GetSize() const override;\n\n  // |Mapping|\n  const uint8_t* GetMapping() const override;\n\n  // |Mapping|\n  bool IsDontNeedSafe() const override;\n\n private:\n  const uint8_t* const data_;\n  const size_t size_;\n  const ReleaseProc release_proc_;\n  const bool dontneed_safe_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(NonOwnedMapping);\n};\n\n/// A Mapping like NonOwnedMapping, but uses Free as its release proc.\nclass MallocMapping final : public Mapping {\n public:\n  MallocMapping();\n\n  /// Creates a MallocMapping for a region of memory (without copying it).\n  /// The function will `abort()` if the malloc fails.\n  /// @param data The starting address of the mapping.\n  /// @param size The size of the mapping in bytes.\n  MallocMapping(uint8_t* data, size_t size);\n\n  MallocMapping(fml::MallocMapping&& mapping);  // NOLINT\n\n  ~MallocMapping() override;\n\n  /// Copies the data from `begin` to `end`.\n  /// It's templated since void* arithemetic isn't allowed and we want support\n  /// for `uint8_t` and `char`.\n  template <typename T>\n  static MallocMapping Copy(const T* begin, const T* end) {\n    FML_DCHECK(end >= begin);\n    size_t length = end - begin;\n    return Copy(begin, length);\n  }\n\n  /// Copies a region of memory into a MallocMapping.\n  /// The function will `abort()` if the malloc fails.\n  /// @param begin The starting address of where we will copy.\n  /// @param length The length of the region to copy in bytes.\n  static MallocMapping Copy(const void* begin, size_t length);\n\n  // |Mapping|\n  size_t GetSize() const override;\n\n  // |Mapping|\n  const uint8_t* GetMapping() const override;\n\n  // |Mapping|\n  bool IsDontNeedSafe() const override;\n\n  /// Removes ownership of the data buffer.\n  /// After this is called; the mapping will point to nullptr.\n  [[nodiscard]] uint8_t* Release();\n\n private:\n  uint8_t* data_;\n  size_t size_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(MallocMapping);\n};\n\nclass SymbolMapping final : public Mapping {\n public:\n  SymbolMapping(fml::RefPtr<fml::NativeLibrary> native_library,\n                const char* symbol_name);\n\n  ~SymbolMapping() override;\n\n  // |Mapping|\n  size_t GetSize() const override;\n\n  // |Mapping|\n  const uint8_t* GetMapping() const override;\n\n  // |Mapping|\n  bool IsDontNeedSafe() const override;\n\n private:\n  fml::RefPtr<fml::NativeLibrary> native_library_;\n  const uint8_t* mapping_ = nullptr;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SymbolMapping);\n};\n\n}  // namespace fml\n\n#endif  // CLAY_FML_MAPPING_H_\n"
  },
  {
    "path": "clay/fml/mapping_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstdlib>\n#include <cstring>\n\n#include \"base/include/flutter/testing/testing.h\"\n#include \"clay/fml/mapping.h\"\n\nnamespace fml {\n\nTEST(MallocMapping, EmptyContructor) {\n  MallocMapping mapping;\n  ASSERT_EQ(nullptr, mapping.GetMapping());\n  ASSERT_EQ(0u, mapping.GetSize());\n}\n\nTEST(MallocMapping, NotEmptyContructor) {\n  size_t length = 10;\n  MallocMapping mapping(reinterpret_cast<uint8_t*>(malloc(length)), length);\n  ASSERT_NE(nullptr, mapping.GetMapping());\n  ASSERT_EQ(length, mapping.GetSize());\n}\n\nTEST(MallocMapping, MoveConstructor) {\n  size_t length = 10;\n  MallocMapping mapping(reinterpret_cast<uint8_t*>(malloc(length)), length);\n  MallocMapping moved = std::move(mapping);\n\n  ASSERT_EQ(nullptr,\n            mapping.GetMapping());  // NOLINT(clang-analyzer-cplusplus.Move,\n                                    // bugprone-use-after-move)\n  ASSERT_EQ(0u, mapping.GetSize());\n  ASSERT_NE(nullptr, moved.GetMapping());\n  ASSERT_EQ(length, moved.GetSize());\n}\n\nTEST(MallocMapping, Copy) {\n  size_t length = 10;\n  MallocMapping mapping(reinterpret_cast<uint8_t*>(malloc(length)), length);\n  memset(const_cast<uint8_t*>(mapping.GetMapping()), 0xac, mapping.GetSize());\n  MallocMapping copied =\n      MallocMapping::Copy(mapping.GetMapping(), mapping.GetSize());\n\n  ASSERT_NE(mapping.GetMapping(), copied.GetMapping());\n  ASSERT_EQ(mapping.GetSize(), copied.GetSize());\n  ASSERT_EQ(\n      0, memcmp(mapping.GetMapping(), copied.GetMapping(), mapping.GetSize()));\n}\n\nTEST(MallocMapping, Release) {\n  size_t length = 10;\n  MallocMapping mapping(reinterpret_cast<uint8_t*>(malloc(length)), length);\n  free(const_cast<uint8_t*>(mapping.Release()));\n  ASSERT_EQ(nullptr, mapping.GetMapping());\n  ASSERT_EQ(0u, mapping.GetSize());\n}\n\nTEST(MallocMapping, IsDontNeedSafe) {\n  size_t length = 10;\n  MallocMapping mapping(reinterpret_cast<uint8_t*>(malloc(length)), length);\n  ASSERT_NE(nullptr, mapping.GetMapping());\n  ASSERT_FALSE(mapping.IsDontNeedSafe());\n}\n\nTEST(MallocMapping, CopySizeZero) {\n  char ch = 'a';\n  MallocMapping mapping = MallocMapping::Copy(&ch, &ch);\n  ASSERT_EQ(0u, mapping.GetSize());\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/math.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_MATH_H_\n#define CLAY_FML_MATH_H_\n\nnamespace clay {\nnamespace math {\n\n// e\nconstexpr float kE = 2.7182818284590452354;\n\n// log_2 e\nconstexpr float kLog2_E = 1.4426950408889634074;\n\n// log_10 e\nconstexpr float kLog10_E = 0.43429448190325182765;\n\n// log_e 2\nconstexpr float klogE_2 = 0.69314718055994530942;\n\n// log_e 10\nconstexpr float klogE_10 = 2.30258509299404568402;\n\n// pi\nconstexpr float kPi = 3.14159265358979323846;\n\n// pi/2\nconstexpr float kPiOver2 = 1.57079632679489661923;\n\n// pi/4\nconstexpr float kPiOver4 = 0.78539816339744830962;\n\n// 1/pi\nconstexpr float k1OverPi = 0.31830988618379067154;\n\n// 2/pi\nconstexpr float k2OverPi = 0.63661977236758134308;\n\n// 2/sqrt(pi)\nconstexpr float k2OverSqrtPi = 1.12837916709551257390;\n\n// sqrt(2)\nconstexpr float kSqrt2 = 1.41421356237309504880;\n\n// 1/sqrt(2)\nconstexpr float k1OverSqrt2 = 0.70710678118654752440;\n\n}  // namespace math\n}  // namespace clay\n\n#endif  // CLAY_FML_MATH_H_\n"
  },
  {
    "path": "clay/fml/math_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cmath>\n\n#include \"clay/fml/math.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(MathTest, Constants) {\n  // Don't use the constants in cmath as those aren't portable.\n  EXPECT_FLOAT_EQ(std::log2(math::kE), math::kLog2_E);\n  EXPECT_FLOAT_EQ(std::log10(math::kE), math::kLog10_E);\n  EXPECT_FLOAT_EQ(std::log(2.0f), math::klogE_2);\n  EXPECT_FLOAT_EQ(math::kPi / 2.0f, math::kPiOver2);\n  EXPECT_FLOAT_EQ(math::kPi / 4.0f, math::kPiOver4);\n  EXPECT_FLOAT_EQ(1.0f / math::kPi, math::k1OverPi);\n  EXPECT_FLOAT_EQ(2.0f / math::kPi, math::k2OverPi);\n  EXPECT_FLOAT_EQ(std::sqrt(2.0f), math::kSqrt2);\n  EXPECT_FLOAT_EQ(1.0f / std::sqrt(2.0f), math::k1OverSqrt2);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/fml/native_library.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_NATIVE_LIBRARY_H_\n#define CLAY_FML_NATIVE_LIBRARY_H_\n\n#include <optional>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n\n#if defined(OS_WIN)\n#include <windows.h>\n#endif  // defined(OS_WIN)\n\nnamespace fml {\nclass NativeLibrary : public fml::RefCountedThreadSafe<NativeLibrary> {\n public:\n#if OS_WIN\n  using Handle = HMODULE;\n  using SymbolHandle = FARPROC;\n#else   // OS_WIN\n  using Handle = void*;\n  using SymbolHandle = void*;\n#endif  // OS_WIN\n\n  static fml::RefPtr<NativeLibrary> Create(const char* path);\n\n  static fml::RefPtr<NativeLibrary> Create(const char* path, bool faker);\n\n  static fml::RefPtr<NativeLibrary> CreateWithHandle(\n      Handle handle, bool close_handle_when_done);\n\n  static fml::RefPtr<NativeLibrary> CreateForCurrentProcess();\n\n  template <typename T>\n  const std::optional<T> ResolveFunction(const char* symbol) {\n    auto* resolved_symbol = Resolve(symbol);\n    if (!resolved_symbol) {\n      return std::nullopt;\n    }\n    return std::optional<T>(reinterpret_cast<T>(resolved_symbol));\n  }\n\n  const uint8_t* ResolveSymbol(const char* symbol) {\n    auto* resolved_symbol = reinterpret_cast<const uint8_t*>(Resolve(symbol));\n    if (resolved_symbol == nullptr) {\n      FML_DLOG(INFO) << \"Could not resolve symbol in library: \" << symbol;\n    }\n    return resolved_symbol;\n  }\n\n private:\n  Handle handle_ = nullptr;\n  bool close_handle_ = true;\n  bool faker_ = false;\n\n  explicit NativeLibrary(const char* path);\n  NativeLibrary(const char* path, bool faker);\n\n  NativeLibrary(Handle handle, bool close_handle);\n\n  ~NativeLibrary();\n\n  Handle GetHandle() const;\n  SymbolHandle Resolve(const char* symbol) const;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(NativeLibrary);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(NativeLibrary);\n  FML_FRIEND_MAKE_REF_COUNTED(NativeLibrary);\n};\n\n}  // namespace fml\n\n#endif  // CLAY_FML_NATIVE_LIBRARY_H_\n"
  },
  {
    "path": "clay/fml/paths.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/paths.h\"\n\n#include <sstream>\n\n#include \"build/build_config.h\"\n\nnamespace fml {\nnamespace paths {\n\nstd::string JoinPaths(std::initializer_list<std::string> components) {\n  std::stringstream stream;\n  size_t i = 0;\n  const size_t size = components.size();\n  for (const auto& component : components) {\n    i++;\n    stream << component;\n    if (i != size) {\n#if OS_WIN\n      stream << \"\\\\\";\n#else   // OS_WIN\n      stream << \"/\";\n#endif  // OS_WIN\n    }\n  }\n  return stream.str();\n}\n\nstd::string SanitizeURIEscapedCharacters(const std::string& str) {\n  std::string result;\n  result.reserve(str.size());\n  for (std::string::size_type i = 0; i < str.size(); ++i) {\n    if (str[i] == '%') {\n      if (i > str.size() - 3 || !isxdigit(str[i + 1]) ||\n          !isxdigit(str[i + 2])) {\n        return \"\";\n      }\n      const std::string hex = str.substr(i + 1, 2);\n      const unsigned char c = strtoul(hex.c_str(), nullptr, 16);\n      if (!c) {\n        return \"\";\n      }\n      result += c;\n      i += 2;\n    } else {\n      result += str[i];\n    }\n  }\n  return result;\n}\n\nstd::pair<bool, std::string> GetExecutableDirectoryPath() {\n  auto path = GetExecutablePath();\n  if (!path.first) {\n    return {false, \"\"};\n  }\n  return {true, fml::paths::GetDirectoryName(path.second)};\n}\n\n}  // namespace paths\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/paths.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PATHS_H_\n#define CLAY_FML_PATHS_H_\n\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/unique_fd.h\"\n\nnamespace fml {\nnamespace paths {\n\nstd::pair<bool, std::string> GetExecutablePath();\nstd::pair<bool, std::string> GetExecutableDirectoryPath();\n\n// Get the directory to the application's caches directory.\nfml::UniqueFD GetCachesDirectory();\n\nstd::string JoinPaths(std::initializer_list<std::string> components);\n\n// Returns the absolute path of a possibly relative path.\n// It doesn't consult the filesystem or simplify the path.\nstd::string AbsolutePath(const std::string& path);\n\n// Returns the directory name component of the given path.\nstd::string GetDirectoryName(const std::string& path);\n\n// Decodes a URI encoded string.\nstd::string SanitizeURIEscapedCharacters(const std::string& str);\n\n// Converts a file URI to a path.\nstd::string FromURI(const std::string& uri);\n\n}  // namespace paths\n}  // namespace fml\n\n#endif  // CLAY_FML_PATHS_H_\n"
  },
  {
    "path": "clay/fml/paths_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/paths.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nTEST(Paths, SanitizeURI) {\n  ASSERT_EQ(fml::paths::SanitizeURIEscapedCharacters(\"hello\"), \"hello\");\n  ASSERT_EQ(fml::paths::SanitizeURIEscapedCharacters(\"\"), \"\");\n  ASSERT_EQ(fml::paths::SanitizeURIEscapedCharacters(\"hello%20world\"),\n            \"hello world\");\n  ASSERT_EQ(fml::paths::SanitizeURIEscapedCharacters(\n                \"%5Chello%5cworld%20foo%20bar%21\"),\n            \"\\\\hello\\\\world foo bar!\");\n}\n"
  },
  {
    "path": "clay/fml/platform/android/CPPLINT.cfg",
    "content": "set noparent\nfilter=-lynx_custom/new_java_ref,+build/namespaces,+build/c++14,+build/class,+build/c++tr1,+build/deprecated,+build/endif_comment,+build/explicit_make_pair\nexclude_files=.*\\.java\nexclude_files=.*\\.mm\nexclude_files=.*\\.m\nexclude_files=.*unittest\\.cc\nexclude_files=.*mock_element\\.cc\n"
  },
  {
    "path": "clay/fml/platform/android/jni_android.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/android/jni_android.h\"\n\n#include <string>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nnamespace android {\n\nbool CheckException(JNIEnv *env) {\n  std::string exception_msg;\n  bool ret = lynx::base::android::CheckException(env, exception_msg);\n  if (!exception_msg.empty()) {\n    FML_LOG(ERROR) << exception_msg;\n  }\n  return ret;\n}\n\n}  // namespace android\n}  // namespace clay\n"
  },
  {
    "path": "clay/fml/platform/android/jni_android.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_ANDROID_JNI_ANDROID_H_\n#define CLAY_FML_PLATFORM_ANDROID_JNI_ANDROID_H_\n\n#include <jni.h>\n\n#include <cstdint>\n\n#include \"base/include/platform/android/jni_utils.h\"\n\nnamespace clay {\nnamespace android {\n\nbool CheckException(JNIEnv* env);\n\n}  // namespace android\n}  // namespace clay\n\n#endif  // CLAY_FML_PLATFORM_ANDROID_JNI_ANDROID_H_\n"
  },
  {
    "path": "clay/fml/platform/android/jni_weak_ref.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/android/jni_weak_ref.h\"\n\n#include \"base/include/platform/android/jni_utils.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace fml {\nnamespace jni {\n\nJavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef() : obj_(NULL) {}\n\nJavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(\n    const JavaObjectWeakGlobalRef& orig)\n    : obj_(NULL) {\n  Assign(orig);\n}\n\nJavaObjectWeakGlobalRef::JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj)\n    : obj_(env->NewWeakGlobalRef(obj)) {\n  FML_DCHECK(obj_);\n}\n\nJavaObjectWeakGlobalRef::~JavaObjectWeakGlobalRef() { reset(); }\n\nvoid JavaObjectWeakGlobalRef::operator=(const JavaObjectWeakGlobalRef& rhs) {\n  Assign(rhs);\n}\n\nvoid JavaObjectWeakGlobalRef::reset() {\n  if (obj_) {\n    AttachCurrentThread()->DeleteWeakGlobalRef(obj_);\n    obj_ = NULL;\n  }\n}\n\nScopedLocalJavaRef<jobject> JavaObjectWeakGlobalRef::get(JNIEnv* env) const {\n  return GetRealObject(env, obj_);\n}\n\nScopedLocalJavaRef<jobject> GetRealObject(JNIEnv* env, jweak obj) {\n  jobject real = NULL;\n  if (obj) {\n    real = env->NewLocalRef(obj);\n    if (!real) {\n      FML_DLOG(ERROR) << \"The real object has been deleted!\";\n    }\n  }\n  return ScopedLocalJavaRef<jobject>(env, real);\n}\n\nvoid JavaObjectWeakGlobalRef::Assign(const JavaObjectWeakGlobalRef& other) {\n  if (&other == this) {\n    return;\n  }\n\n  JNIEnv* env = AttachCurrentThread();\n  if (obj_) {\n    env->DeleteWeakGlobalRef(obj_);\n  }\n\n  obj_ = other.obj_ ? env->NewWeakGlobalRef(other.obj_) : NULL;\n}\n\n}  // namespace jni\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/android/jni_weak_ref.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_ANDROID_JNI_WEAK_REF_H_\n#define CLAY_FML_PLATFORM_ANDROID_JNI_WEAK_REF_H_\n\n#include <jni.h>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n\nnamespace fml {\nnamespace jni {\n\n// Manages WeakGlobalRef lifecycle.\n// This class is not thread-safe w.r.t. get() and reset(). Multiple threads may\n// safely use get() concurrently, but if the user calls reset() (or of course,\n// calls the destructor) they'll need to provide their own synchronization.\nclass JavaObjectWeakGlobalRef {\n public:\n  JavaObjectWeakGlobalRef();\n\n  JavaObjectWeakGlobalRef(const JavaObjectWeakGlobalRef& orig);\n\n  JavaObjectWeakGlobalRef(JNIEnv* env, jobject obj);\n\n  virtual ~JavaObjectWeakGlobalRef();\n\n  void operator=(const JavaObjectWeakGlobalRef& rhs);\n\n  ScopedLocalJavaRef<jobject> get(JNIEnv* env) const;\n\n  bool is_empty() const { return obj_ == NULL; }\n\n  void reset();\n\n private:\n  void Assign(const JavaObjectWeakGlobalRef& rhs);\n\n  jweak obj_;\n};\n\n// Get the real object stored in the weak reference returned as a\n// ScopedLocalJavaRef.\nScopedLocalJavaRef<jobject> GetRealObject(JNIEnv* env, jweak obj);\n\n}  // namespace jni\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_ANDROID_JNI_WEAK_REF_H_\n"
  },
  {
    "path": "clay/fml/platform/android/paths_android.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/android/paths_android.h\"\n\n#include <utility>\n\n#include \"clay/fml/file.h\"\n\nnamespace fml {\nnamespace paths {\n\nstd::pair<bool, std::string> GetExecutablePath() { return {false, \"\"}; }\n\nstatic std::string gCachesPath;  // NOLINT\n\nvoid InitializeAndroidCachesPath(std::string caches_path) {\n  gCachesPath = std::move(caches_path);\n}\n\nfml::UniqueFD GetCachesDirectory() {\n  // If the caches path is not initialized, the FD will be invalid and caching\n  // will be disabled throughout the system.\n  return OpenDirectory(gCachesPath.c_str(), false, fml::FilePermission::kRead);\n}\n\n}  // namespace paths\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/android/paths_android.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_ANDROID_PATHS_ANDROID_H_\n#define CLAY_FML_PLATFORM_ANDROID_PATHS_ANDROID_H_\n\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/paths.h\"\n\nnamespace fml {\nnamespace paths {\n\nvoid InitializeAndroidCachesPath(std::string caches_path);\n\n}  // namespace paths\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_ANDROID_PATHS_ANDROID_H_\n"
  },
  {
    "path": "clay/fml/platform/darwin/cf_utils.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/darwin/cf_utils.h\"\n\nnamespace fml {\n\n//\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/darwin/cf_utils.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_DARWIN_CF_UTILS_H_\n#define CLAY_FML_PLATFORM_DARWIN_CF_UTILS_H_\n\n#include <CoreFoundation/CoreFoundation.h>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace fml {\n\ntemplate <class T>\nclass CFRef {\n public:\n  CFRef() : instance_(nullptr) {}\n\n  // NOLINTNEXTLINE\n  CFRef(T instance) : instance_(instance) {}\n\n  CFRef(const CFRef& other) : instance_(other.instance_) {\n    if (instance_) {\n      CFRetain(instance_);\n    }\n  }\n\n  CFRef(CFRef&& other) : instance_(other.instance_) {\n    other.instance_ = nullptr;\n  }\n\n  CFRef& operator=(CFRef&& other) {\n    Reset(other.Release());\n    return *this;\n  }\n\n  ~CFRef() {\n    if (instance_ != nullptr) {\n      CFRelease(instance_);\n    }\n    instance_ = nullptr;\n  }\n\n  void Reset(T instance = nullptr) {\n    if (instance_ != nullptr) {\n      CFRelease(instance_);\n    }\n\n    instance_ = instance;\n  }\n\n  [[nodiscard]] T Release() {\n    auto instance = instance_;\n    instance_ = nullptr;\n    return instance;\n  }\n\n  // NOLINTNEXTLINE\n  operator T() const { return instance_; }\n\n  operator bool() const { return instance_ != nullptr; }\n\n private:\n  T instance_;\n\n  CFRef& operator=(const CFRef&) = delete;\n};\n\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_DARWIN_CF_UTILS_H_\n"
  },
  {
    "path": "clay/fml/platform/darwin/cf_utils_unittests.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/darwin/cf_utils.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace fml {\nnamespace testing {\n\nTEST(CFTest, CanCreateRefs) {\n  CFRef<CFMutableStringRef> string(CFStringCreateMutable(kCFAllocatorDefault, 100u));\n  // Cast\n  ASSERT_TRUE(static_cast<bool>(string));\n  ASSERT_TRUE(string);\n\n  const auto ref_count = CFGetRetainCount(string);\n\n  // Copy & Reset\n  {\n    CFRef<CFMutableStringRef> string2 = string;\n    ASSERT_TRUE(string2);\n    ASSERT_EQ(ref_count + 1u, CFGetRetainCount(string));\n    ASSERT_EQ(CFGetRetainCount(string2), CFGetRetainCount(string));\n\n    string2.Reset();\n    ASSERT_FALSE(string2);\n    ASSERT_EQ(ref_count, CFGetRetainCount(string));\n  }\n\n  // Release\n  {\n    auto string3 = string;\n    ASSERT_TRUE(string3);\n    ASSERT_EQ(ref_count + 1u, CFGetRetainCount(string));\n    auto raw_string3 = string3.Release();\n    ASSERT_FALSE(string3);\n    ASSERT_EQ(ref_count + 1u, CFGetRetainCount(string));\n    CFRelease(raw_string3);\n    ASSERT_EQ(ref_count, CFGetRetainCount(string));\n  }\n\n  // Move\n  {\n    auto string_source = string;\n    ASSERT_TRUE(string_source);\n    auto string_move = std::move(string_source);\n    ASSERT_FALSE(string_source);  // NOLINT(bugprone-use-after-move)\n    ASSERT_EQ(ref_count + 1u, CFGetRetainCount(string));\n    string_move.Reset();\n    ASSERT_EQ(ref_count, CFGetRetainCount(string));\n  }\n\n  // Move assign.\n  {\n    auto string_move_assign = std::move(string);\n    ASSERT_FALSE(string);  // NOLINT(bugprone-use-after-move)\n    ASSERT_EQ(ref_count, CFGetRetainCount(string_move_assign));\n  }\n}\n\n}  // namespace testing\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/darwin/paths_darwin.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/paths.h\"\n\n#include <Foundation/Foundation.h>\n\n#include \"clay/fml/file.h\"\n\nnamespace fml {\nnamespace paths {\n\nstd::pair<bool, std::string> GetExecutablePath() {\n  @autoreleasepool {\n    return {true, [NSBundle mainBundle].executablePath.UTF8String};\n  }\n}\n\nfml::UniqueFD GetCachesDirectory() {\n  @autoreleasepool {\n    auto items = [[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory\n                                                        inDomains:NSUserDomainMask];\n    if (items.count == 0) {\n      return {};\n    }\n\n    return OpenDirectory(items[0].fileSystemRepresentation, false, FilePermission::kRead);\n  }\n}\n\n}  // namespace paths\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/darwin/platform_version.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_DARWIN_PLATFORM_VERSION_H_\n#define CLAY_FML_PLATFORM_DARWIN_PLATFORM_VERSION_H_\n\n#include <sys/types.h>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace fml {\n\nbool IsPlatformVersionAtLeast(size_t major, size_t minor = 0, size_t patch = 0);\n\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_DARWIN_PLATFORM_VERSION_H_\n"
  },
  {
    "path": "clay/fml/platform/darwin/platform_version.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/darwin/platform_version.h\"\n\n#include <Foundation/NSProcessInfo.h>\n\nnamespace fml {\n\nbool IsPlatformVersionAtLeast(size_t major, size_t minor, size_t patch) {\n  const NSOperatingSystemVersion version = {\n      .majorVersion = static_cast<NSInteger>(major),\n      .minorVersion = static_cast<NSInteger>(minor),\n      .patchVersion = static_cast<NSInteger>(patch),\n  };\n  return [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version];\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/darwin/scoped_block.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_DARWIN_SCOPED_BLOCK_H_\n#define CLAY_FML_PLATFORM_DARWIN_SCOPED_BLOCK_H_\n\n#include <utility>\n\n#include \"base/include/compiler_specific.h\"\n\n#if !__has_feature(objc_arc)\n#error \"ARC must be enabled\"\n#endif\n\nnamespace fml {\n\ntemplate <typename B>\nclass ScopedBlock {\n public:\n  explicit ScopedBlock(B block = nullptr) : block_(block) {}\n\n  ScopedBlock(const ScopedBlock<B>& that) : block_(that.block_) {}\n\n  ~ScopedBlock() {}\n\n  ScopedBlock& operator=(const ScopedBlock<B>& that) {\n    reset(that.get());\n    return *this;\n  }\n\n  void reset(B block = nullptr) { block_ = block; }\n\n  bool operator==(B that) const { return block_ == that; }\n\n  bool operator!=(B that) const { return block_ != that; }\n\n  // NOLINTNEXTLINE\n  operator B() const { return block_; }\n\n  B get() const { return block_; }\n\n  void swap(ScopedBlock& that) {\n    B temp = that.block_;\n    that.block_ = block_;\n    block_ = temp;\n  }\n\n  [[nodiscard]] B release() {\n    B temp = block_;\n    block_ = nullptr;\n    return temp;\n  }\n\n private:\n  B block_;\n};\n\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_DARWIN_SCOPED_BLOCK_H_\n"
  },
  {
    "path": "clay/fml/platform/darwin/scoped_block.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/darwin/scoped_block.h\"\n\nnamespace fml {\n\n//\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/darwin/scoped_nsobject.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_DARWIN_SCOPED_NSOBJECT_H_\n#define CLAY_FML_PLATFORM_DARWIN_SCOPED_NSOBJECT_H_\n\n// Include NSObject.h directly because Foundation.h pulls in many dependencies.\n// (Approx 100k lines of code versus 1.5k for NSObject.h). scoped_nsobject gets\n// singled out because it is most typically included from other header files.\n#import <Foundation/NSObject.h>\n\n#include <utility>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/fml/macros.h\"\n\n#if !__has_feature(objc_arc)\n#error \"ARC must be enabled\"\n#endif\n\nnamespace fml {\n\n// We enable ARC on all source set.\n// For compatibility with the exisiting codebase, we keep this class but\n// implement it as an pure wrapper.\n\ntemplate <typename NST>\nclass scoped_nsprotocol {\n public:\n  explicit scoped_nsprotocol(NST object = nil) : object_(object) {}\n  // NOLINTNEXTLINE\n  scoped_nsprotocol(const scoped_nsprotocol<NST>& that)\n      : object_(that.object_) {}\n\n  template <typename NSU>\n  // NOLINTNEXTLINE\n  scoped_nsprotocol(const scoped_nsprotocol<NSU>& that) : object_(that.get()) {}\n\n  ~scoped_nsprotocol() = default;\n\n  scoped_nsprotocol& operator=(const scoped_nsprotocol<NST>& that) {\n    reset(that.get());\n    return *this;\n  }\n\n  void reset(NST object = nil) {\n    // We intentionally do not check that object != object_ as the caller must\n    // either already have an ownership claim over whatever it passes to this\n    // method, or call it with the |RETAIN| policy which will have ensured that\n    // the object is retained once more when reaching this point.\n    object_ = object;\n  }\n\n  bool operator==(NST that) const { return object_ == that; }\n  bool operator!=(NST that) const { return object_ != that; }\n\n  operator NST() const { return object_; }  // NOLINT\n\n  NST get() const { return object_; }\n\n  void swap(scoped_nsprotocol& that) {\n    NST temp = that.object_;\n    that.object_ = object_;\n    object_ = temp;\n  }\n\n private:\n  NST object_;\n\n  // scoped_nsprotocol<>::release() is like scoped_ptr<>::release.  It is NOT a\n  // wrapper for [object_ release].  To force a scoped_nsprotocol<> to call\n  // [object_ release], use scoped_nsprotocol<>::reset().\n  [[nodiscard]] NST release() {\n    NST temp = object_;\n    object_ = nil;\n    return temp;\n  }\n};\n\n// Free functions\ntemplate <class C>\nvoid swap(scoped_nsprotocol<C>& p1, scoped_nsprotocol<C>& p2) {\n  p1.swap(p2);\n}\n\ntemplate <class C>\nbool operator==(C p1, const scoped_nsprotocol<C>& p2) {\n  return p1 == p2.get();\n}\n\ntemplate <class C>\nbool operator!=(C p1, const scoped_nsprotocol<C>& p2) {\n  return p1 != p2.get();\n}\n\ntemplate <typename NST>\nclass scoped_nsobject : public scoped_nsprotocol<NST*> {\n public:\n  // NOLINTNEXTLINE\n  explicit scoped_nsobject(NST* object = nil)\n      : scoped_nsprotocol<NST*>(object) {}\n  // NOLINTNEXTLINE\n  scoped_nsobject(const scoped_nsobject<NST>& that)\n      : scoped_nsprotocol<NST*>(that) {}\n\n  template <typename NSU>\n  // NOLINTNEXTLINE\n  scoped_nsobject(const scoped_nsobject<NSU>& that)\n      : scoped_nsprotocol<NST*>(that) {}\n\n  scoped_nsobject& operator=(const scoped_nsobject<NST>& that) {\n    scoped_nsprotocol<NST*>::operator=(that);\n    return *this;\n  }\n};\n\n// Specialization to make scoped_nsobject<id> work.\ntemplate <>\nclass scoped_nsobject<id> : public scoped_nsprotocol<id> {\n public:\n  explicit scoped_nsobject(id object = nil) : scoped_nsprotocol<id>(object) {}\n  // NOLINTNEXTLINE\n  scoped_nsobject(const scoped_nsobject<id>& that)\n      : scoped_nsprotocol<id>(that) {}\n\n  template <typename NSU>\n  // NOLINTNEXTLINE\n  scoped_nsobject(const scoped_nsobject<NSU>& that)\n      : scoped_nsprotocol<id>(that) {}\n\n  scoped_nsobject& operator=(const scoped_nsobject<id>& that) {\n    scoped_nsprotocol<id>::operator=(that);\n    return *this;\n  }\n};\n\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_DARWIN_SCOPED_NSOBJECT_H_\n"
  },
  {
    "path": "clay/fml/platform/darwin/scoped_nsobject.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/darwin/scoped_nsobject.h\"\n\nnamespace fml {\n\n//\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/darwin/string_range_sanitization.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_DARWIN_STRING_RANGE_SANITIZATION_H_\n#define CLAY_FML_PLATFORM_DARWIN_STRING_RANGE_SANITIZATION_H_\n\n#include <Foundation/Foundation.h>\n\nnamespace fml {\n\n// NOLINTNEXTLINE\n// Returns a range encompassing the grapheme cluster in which |index| is located.\n// NOLINTNEXTLINE\n// A nil |text| or an index greater than or equal to text.length will result in\n// `NSRange(NSNotFound, 0)`.\nNSRange RangeForCharacterAtIndex(NSString* text, NSUInteger index);\n\n// Returns a range encompassing the grapheme clusters falling in |range|.\n//\n// This method will not alter the length of the input range, but will ensure\n// that the range's location is not in the middle of a multi-byte unicode\n// sequence.\n//\n// An invalid range will result in `NSRange(NSNotFound, 0)`.\nNSRange RangeForCharactersInRange(NSString* text, NSRange range);\n\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_DARWIN_STRING_RANGE_SANITIZATION_H_\n"
  },
  {
    "path": "clay/fml/platform/darwin/string_range_sanitization.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/darwin/string_range_sanitization.h\"\n\nnamespace fml {\n\nNSRange RangeForCharacterAtIndex(NSString* text, NSUInteger index) {\n  if (text == nil || index > text.length) {\n    return NSMakeRange(NSNotFound, 0);\n  }\n  if (index < text.length) {\n    return [text rangeOfComposedCharacterSequenceAtIndex:index];\n  }\n  return NSMakeRange(index, 0);\n}\n\nNSRange RangeForCharactersInRange(NSString* text, NSRange range) {\n  if (text == nil || range.location + range.length > text.length) {\n    return NSMakeRange(NSNotFound, 0);\n  }\n  NSRange sanitizedRange = [text rangeOfComposedCharacterSequencesForRange:range];\n  // We don't want to override the length, we just want to make sure we don't\n  // select into the middle of a multi-byte character. Taking the\n  // `sanitizedRange`'s length will end up altering the actual selection.\n  return NSMakeRange(sanitizedRange.location, range.length);\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/darwin/string_range_sanitization_unittests.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <Foundation/Foundation.h>\n\n#include \"clay/fml/platform/darwin/string_range_sanitization.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nTEST(StringRangeSanitizationTest, CanHandleUnicode) {\n  auto result = fml::RangeForCharacterAtIndex(@\"😠\", 1);\n  EXPECT_EQ(result.location, 0UL);\n  EXPECT_EQ(result.length, 2UL);\n}\n\nTEST(StringRangeSanitizationTest, HandlesInvalidRanges) {\n  auto ns_not_found = static_cast<unsigned long>(NSNotFound);\n  EXPECT_EQ(fml::RangeForCharacterAtIndex(@\"😠\", 3).location, ns_not_found);\n  EXPECT_EQ(fml::RangeForCharacterAtIndex(@\"😠\", -1).location, ns_not_found);\n  EXPECT_EQ(fml::RangeForCharacterAtIndex(nil, 0).location, ns_not_found);\n  EXPECT_EQ(fml::RangeForCharactersInRange(@\"😠\", NSMakeRange(1, 2)).location, ns_not_found);\n  EXPECT_EQ(fml::RangeForCharactersInRange(@\"😠\", NSMakeRange(3, 0)).location, ns_not_found);\n  EXPECT_EQ(fml::RangeForCharactersInRange(nil, NSMakeRange(0, 0)).location, ns_not_found);\n}\n\nTEST(StringRangeSanitizationTest, CanHandleUnicodeRange) {\n  auto result = fml::RangeForCharactersInRange(@\"😠\", NSMakeRange(1, 0));\n  EXPECT_EQ(result.location, 0UL);\n  EXPECT_EQ(result.length, 0UL);\n}\n\nTEST(StringRangeSanitizationTest, HandlesEndOfRange) {\n  EXPECT_EQ(fml::RangeForCharacterAtIndex(@\"1234\", 4).location, 4UL);\n  EXPECT_EQ(fml::RangeForCharacterAtIndex(@\"1234\", 4).length, 0UL);\n}\n"
  },
  {
    "path": "clay/fml/platform/harmony/paths_harmony.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/harmony/paths_harmony.h\"\n\n#include <string>\n#include <utility>\n\n#include \"clay/fml/file.h\"\n\nnamespace fml {\nnamespace paths {\n\nstd::pair<bool, std::string> GetExecutablePath() { return {false, \"\"}; }\n\nstatic std::string gCachesPath;  // NOLINT\n\nvoid InitializeHarmonyCachesPath(std::string caches_path) {\n  gCachesPath = std::move(caches_path);\n}\n\nfml::UniqueFD GetCachesDirectory() {\n  // If the caches path is not initialized, the FD will be invalid and caching\n  // will be disabled throughout the system.\n  return OpenDirectory(gCachesPath.c_str(), false, fml::FilePermission::kRead);\n}\n\n}  // namespace paths\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/harmony/paths_harmony.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_HARMONY_PATHS_HARMONY_H_\n#define CLAY_FML_PLATFORM_HARMONY_PATHS_HARMONY_H_\n\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/paths.h\"\n\nnamespace fml {\nnamespace paths {\n\nvoid InitializeHarmonyCachesPath(std::string caches_path);\n\n}  // namespace paths\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_HARMONY_PATHS_HARMONY_H_\n"
  },
  {
    "path": "clay/fml/platform/linux/paths_linux.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <unistd.h>\n\n#include \"clay/fml/paths.h\"\n\nnamespace fml {\nnamespace paths {\n\nstd::pair<bool, std::string> GetExecutablePath() {\n  const int path_size = 255;\n  char path[path_size] = {0};\n  auto read_size = ::readlink(\"/proc/self/exe\", path, path_size);\n  if (read_size == -1) {\n    return {false, \"\"};\n  }\n  return {true, std::string{path, static_cast<size_t>(read_size)}};\n}\n\nfml::UniqueFD GetCachesDirectory() {\n  // Unsupported on this platform.\n  return {};\n}\n\n}  // namespace paths\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/linux/proc_maps_linux.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/linux/proc_maps_linux.h\"\n\n#include <fcntl.h>\n#include <stddef.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\n#include \"base/include/string/string_utils.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n\n#if defined(OS_LINUX) || defined(OS_ANDROID)\n#include <inttypes.h>\n#endif\n\nnamespace fml {\n\n// Scans |proc_maps| starting from |pos| returning true if the gate VMA was\n// found, otherwise returns false.\nstatic bool ContainsGateVMA(std::string* proc_maps, size_t pos) {\n#if defined(ARCH_CPU_ARM_FAMILY)\n  // The gate VMA on ARM kernels is the interrupt vectors page.\n  return proc_maps->find(\" [vectors]\\n\", pos) != std::string::npos;\n#elif defined(ARCH_CPU_X86_64)\n  // The gate VMA on x86 64-bit kernels is the virtual system call page.\n  return proc_maps->find(\" [vsyscall]\\n\", pos) != std::string::npos;\n#else\n  // Otherwise assume there is no gate VMA in which case we shouldn't\n  // get duplicate entires.\n  return false;\n#endif\n}\n\nbool ReadProcMaps(std::string* proc_maps) {\n  // seq_file only writes out a page-sized amount on each call. Refer to header\n  // file for details.\n  const long kReadSize = sysconf(_SC_PAGESIZE);  // NOLINT\n\n  // base::ScopedFD fd(HANDLE_EINTR(open(\"/proc/self/maps\", O_RDONLY)));\n  int fd = open(\"/proc/self/maps\", O_RDONLY);\n  if (fd == -1) {\n    FML_DLOG(ERROR) << \"Couldn't open /proc/self/maps\";\n    return false;\n  }\n  // if (!fd.is_valid()) {\n  //   FML_DLOG(ERROR) << \"Couldn't open /proc/self/maps\";\n  //   return false;\n  // }\n  proc_maps->clear();\n\n  while (true) {\n    // To avoid a copy, resize |proc_maps| so read() can write directly into it.\n    // Compute |buffer| afterwards since resize() may reallocate.\n    size_t pos = proc_maps->size();\n    proc_maps->resize(pos + kReadSize);\n    void* buffer = &(*proc_maps)[pos];\n\n    // ssize_t bytes_read = HANDLE_EINTR(read(fd, buffer, kReadSize));\n    ssize_t bytes_read = read(fd, buffer, kReadSize);\n    if (bytes_read < 0) {\n      FML_DLOG(ERROR) << \"Couldn't read /proc/self/maps\";\n      proc_maps->clear();\n      return false;\n    }\n\n    // ... and don't forget to trim off excess bytes.\n    proc_maps->resize(pos + bytes_read);\n\n    if (bytes_read == 0) break;\n\n    // The gate VMA is handled as a special case after seq_file has finished\n    // iterating through all entries in the virtual memory table.\n    //\n    // Unfortunately, if additional entries are added at this point in time\n    // seq_file gets confused and the next call to read() will return duplicate\n    // entries including the gate VMA again.\n    //\n    // Avoid this by searching for the gate VMA and breaking early.\n    if (ContainsGateVMA(proc_maps, pos)) break;\n  }\n\n  // Close FD.\n  close(fd);\n  return true;\n}\n\nbool ParseProcMaps(const std::string& input,\n                   std::vector<MappedMemoryRegion>* regions_out) {\n  FML_CHECK(regions_out);\n  std::vector<MappedMemoryRegion> regions;\n\n  // This isn't async safe nor terribly efficient, but it doesn't need to be at\n  // this point in time.\n  std::vector<std::string> lines =\n      lynx::base::SplitString<std::string, std::string>(input, \"\\n\", true);\n\n  lines.push_back(\"\");\n  for (size_t i = 0; i < lines.size(); ++i) {\n    std::string tmp = lynx::base::TrimString(lines[i]);\n    // Due to splitting on '\\n' the last line should be empty.\n    if (i == lines.size() - 1) {\n      if (!tmp.empty()) {\n        FML_DLOG(ERROR) << \"Last line not empty\";\n        return false;\n      }\n      break;\n    }\n\n    MappedMemoryRegion region;\n    const char* line = tmp.c_str();\n    char permissions[5] = {'\\0'};  // Ensure NUL-terminated string.\n    uint8_t dev_major = 0;\n    uint8_t dev_minor = 0;\n    long inode = 0;  // NOLINT\n    int path_index = 0;\n\n    // Sample format from man 5 proc:\n    //\n    // address           perms offset  dev   inode   pathname\n    // 08048000-08056000 r-xp 00000000 03:0c 64593   /usr/sbin/gpm\n    //\n    // The final %n term captures the offset in the input string, which is used\n    // to determine the path name. It *does not* increment the return value.\n    // Refer to man 3 sscanf for details.\n    if (sscanf(line, \"%\" SCNxPTR \"-%\" SCNxPTR \" %4c %llx %hhx:%hhx %ld %n\",\n               &region.start, &region.end, permissions, &region.offset,\n               &dev_major, &dev_minor, &inode, &path_index) < 7) {\n      FML_DLOG(ERROR) << \"sscanf failed for line: \" << line;\n      return false;\n    }\n\n    region.permissions = 0;\n\n    if (permissions[0] == 'r')\n      region.permissions |= MappedMemoryRegion::READ;\n    else if (permissions[0] != '-')\n      return false;\n\n    if (permissions[1] == 'w')\n      region.permissions |= MappedMemoryRegion::WRITE;\n    else if (permissions[1] != '-')\n      return false;\n\n    if (permissions[2] == 'x')\n      region.permissions |= MappedMemoryRegion::EXECUTE;\n    else if (permissions[2] != '-')\n      return false;\n\n    if (permissions[3] == 'p')\n      region.permissions |= MappedMemoryRegion::PRIVATE;\n    else if (permissions[3] != 's' && permissions[3] != 'S')  // Shared memory.\n      return false;\n\n    // Pushing then assigning saves us a string copy.\n    regions.push_back(region);\n    regions.back().path.assign(line + path_index);\n  }\n\n  regions_out->swap(regions);\n  return true;\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/linux/proc_maps_linux.h",
    "content": "// Copyright 2013 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_LINUX_PROC_MAPS_LINUX_H_\n#define CLAY_FML_PLATFORM_LINUX_PROC_MAPS_LINUX_H_\n\n#include <stdint.h>\n\n#include <string>\n#include <vector>\n\nnamespace fml {\n\n// Describes a region of mapped memory and the path of the file mapped.\nstruct MappedMemoryRegion {\n  enum Permission {\n    READ = 1 << 0,\n    WRITE = 1 << 1,\n    EXECUTE = 1 << 2,\n    PRIVATE = 1 << 3,  // If set, region is private, otherwise it is shared.\n  };\n\n  // The address range [start,end) of mapped memory.\n  uintptr_t start = 0;\n  uintptr_t end = 0;\n\n  // Byte offset into |path| of the range mapped into memory.\n  unsigned long long offset = 0;  // NOLINT\n\n  // Image base, if this mapping corresponds to an ELF image.\n  uintptr_t base = 0;\n\n  // Bitmask of read/write/execute/private/shared permissions.\n  uint8_t permissions;\n\n  // Name of the file mapped into memory.\n  //\n  // NOTE: path names aren't guaranteed to point at valid files. For example,\n  // \"[heap]\" and \"[stack]\" are used to represent the location of the process'\n  // heap and stack, respectively.\n  std::string path;\n};\n\n// Reads the data from /proc/self/maps and stores the result in |proc_maps|.\n// Returns true if successful, false otherwise.\n//\n// There is *NO* guarantee that the resulting contents will be free of\n// duplicates or even contain valid entries by time the method returns.\n//\n//\n// THE GORY DETAILS\n//\n// Did you know it's next-to-impossible to atomically read the whole contents\n// of /proc/<pid>/maps? You would think that if we passed in a large-enough\n// buffer to read() that It Should Just Work(tm), but sadly that's not the case.\n//\n// Linux's procfs uses seq_file [1] for handling iteration, text formatting,\n// and dealing with resulting data that is larger than the size of a page. That\n// last bit is especially important because it means that seq_file will never\n// return more than the size of a page in a single call to read().\n//\n// Unfortunately for a program like Chrome the size of /proc/self/maps is\n// larger than the size of page so we're forced to call read() multiple times.\n// If the virtual memory table changed in any way between calls to read() (e.g.,\n// a different thread calling mprotect()), it can make seq_file generate\n// duplicate entries or skip entries.\n//\n// Even if seq_file was changed to keep flushing the contents of its page-sized\n// buffer to the usermode buffer inside a single call to read(), it has to\n// release its lock on the virtual memory table to handle page faults while\n// copying data to usermode. This puts us in the same situation where the table\n// can change while we're copying data.\n//\n// Alternatives such as fork()-and-suspend-the-parent-while-child-reads were\n// attempted, but they present more subtle problems than it's worth. Depending\n// on your use case your best bet may be to read /proc/<pid>/maps prior to\n// starting other threads.\n//\n// [1] http://kernelnewbies.org/Documents/SeqFileHowTo\nbool ReadProcMaps(std::string* proc_maps);\n\n// Parses /proc/<pid>/maps input data and stores in |regions|. Returns true\n// and updates |regions| if and only if all of |input| was successfully parsed.\nbool ParseProcMaps(const std::string& input,\n                   std::vector<MappedMemoryRegion>* regions);\n\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_LINUX_PROC_MAPS_LINUX_H_\n"
  },
  {
    "path": "clay/fml/platform/posix/command_line_posix.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/command_line.h\"\n\nnamespace fml {\n\nstd::optional<CommandLine> CommandLineFromPlatform() { return std::nullopt; }\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/posix/file_posix.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <dirent.h>\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\n#include <cstring>\n#include <memory>\n#include <sstream>\n\n#include \"base/include/fml/eintr_wrapper.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/unique_fd.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/mapping.h\"\n\nnamespace fml {\n\nstd::string CreateTemporaryDirectory() {\n  char directory_name[] = \"/tmp/flutter_XXXXXXXX\";\n  auto* result = ::mkdtemp(directory_name);\n  if (result == nullptr) {\n    return \"\";\n  }\n  return {result};\n}\n\nstatic int ToPosixAccessFlags(FilePermission permission) {\n  int flags = 0;\n  switch (permission) {\n    case FilePermission::kRead:\n      flags |= O_RDONLY;  // read only\n      break;\n    case FilePermission::kWrite:\n      flags |= O_WRONLY;  // write only\n      break;\n    case FilePermission::kReadWrite:\n      flags |= O_RDWR;  // read-write\n      break;\n  }\n  return flags;\n}\n\nstatic int ToPosixCreateModeFlags(FilePermission permission) {\n  int mode = 0;\n  switch (permission) {\n    case FilePermission::kRead:\n      mode |= S_IRUSR;\n      break;\n    case FilePermission::kWrite:\n      mode |= S_IWUSR;\n      break;\n    case FilePermission::kReadWrite:\n      mode |= S_IRUSR | S_IWUSR;\n      break;\n  }\n  return mode;\n}\n\nfml::UniqueFD OpenFile(const char* path, bool create_if_necessary,\n                       FilePermission permission) {\n  return OpenFile(fml::UniqueFD{AT_FDCWD}, path, create_if_necessary,\n                  permission);\n}\n\nfml::UniqueFD OpenFile(const fml::UniqueFD& base_directory, const char* path,\n                       bool create_if_necessary, FilePermission permission) {\n  if (path == nullptr) {\n    return {};\n  }\n\n  int flags = 0;\n  int mode = 0;\n\n  if (create_if_necessary && !FileExists(base_directory, path)) {\n    flags = ToPosixAccessFlags(permission) | O_CREAT | O_TRUNC;\n    mode = ToPosixCreateModeFlags(permission);\n  } else {\n    flags = ToPosixAccessFlags(permission);\n    mode = 0;  // Not creating since it already exists.\n  }\n\n  return fml::UniqueFD{\n      FML_HANDLE_EINTR(::openat(base_directory.get(), path, flags, mode))};\n}\n\nfml::UniqueFD OpenDirectory(const char* path, bool create_if_necessary,\n                            FilePermission permission) {\n  return OpenDirectory(fml::UniqueFD{AT_FDCWD}, path, create_if_necessary,\n                       permission);\n}\n\nfml::UniqueFD OpenDirectory(const fml::UniqueFD& base_directory,\n                            const char* path, bool create_if_necessary,\n                            FilePermission permission) {\n  if (path == nullptr) {\n    return {};\n  }\n\n  if (create_if_necessary && !FileExists(base_directory, path)) {\n    if (::mkdirat(base_directory.get(), path,\n                  ToPosixCreateModeFlags(permission) | S_IXUSR) != 0) {\n      return {};\n    }\n  }\n\n  return fml::UniqueFD{FML_HANDLE_EINTR(\n      ::openat(base_directory.get(), path, O_RDONLY | O_DIRECTORY))};\n}\n\nfml::UniqueFD Duplicate(fml::UniqueFD::element_type descriptor) {\n  return fml::UniqueFD{FML_HANDLE_EINTR(::dup(descriptor))};\n}\n\nbool IsDirectory(const fml::UniqueFD& directory) {\n  if (!directory.is_valid()) {\n    return false;\n  }\n\n  struct stat stat_result = {};\n\n  if (::fstat(directory.get(), &stat_result) != 0) {\n    return false;\n  }\n\n  return S_ISDIR(stat_result.st_mode);\n}\n\nbool IsDirectory(const fml::UniqueFD& base_directory, const char* path) {\n  UniqueFD file = OpenFileReadOnly(base_directory, path);\n  return (file.is_valid() && IsDirectory(file));\n}\n\nbool IsFile(const std::string& path) {\n  struct stat buf;\n  if (stat(path.c_str(), &buf) != 0) {\n    return false;\n  }\n\n  return S_ISREG(buf.st_mode);\n}\n\nbool TruncateFile(const fml::UniqueFD& file, size_t size) {\n  if (!file.is_valid()) {\n    return false;\n  }\n\n  return ::ftruncate(file.get(), size) == 0;\n}\n\nbool UnlinkDirectory(const char* path) {\n  return UnlinkDirectory(fml::UniqueFD{AT_FDCWD}, path);\n}\n\nbool UnlinkDirectory(const fml::UniqueFD& base_directory, const char* path) {\n  return ::unlinkat(base_directory.get(), path, AT_REMOVEDIR) == 0;\n}\n\nbool UnlinkFile(const char* path) {\n  return UnlinkFile(fml::UniqueFD{AT_FDCWD}, path);\n}\n\nbool UnlinkFile(const fml::UniqueFD& base_directory, const char* path) {\n  int code = ::unlinkat(base_directory.get(), path, 0);\n  if (code != 0) {\n    FML_DLOG(ERROR) << strerror(errno);\n  }\n  return code == 0;\n}\n\nbool FileExists(const fml::UniqueFD& base_directory, const char* path) {\n  if (!base_directory.is_valid()) {\n    return false;\n  }\n\n  return ::faccessat(base_directory.get(), path, F_OK, 0) == 0;\n}\n\nbool WriteAtomically(const fml::UniqueFD& base_directory, const char* file_name,\n                     const Mapping& data) {\n  if (file_name == nullptr || data.GetMapping() == nullptr) {\n    return false;\n  }\n\n  std::stringstream stream;\n  stream << file_name << \".temp\";\n  const auto temp_file_name = stream.str();\n\n  auto temp_file = OpenFile(base_directory, temp_file_name.c_str(), true,\n                            FilePermission::kReadWrite);\n  if (!temp_file.is_valid()) {\n    return false;\n  }\n\n  if (!TruncateFile(temp_file, data.GetSize())) {\n    return false;\n  }\n\n  ssize_t remaining = data.GetSize();\n  ssize_t written = 0;\n  ssize_t offset = 0;\n\n  while (remaining > 0) {\n    written = FML_HANDLE_EINTR(\n        ::write(temp_file.get(), data.GetMapping() + offset, remaining));\n\n    if (written == -1) {\n      return false;\n    }\n\n    remaining -= written;\n    offset += written;\n  }\n\n  if (::fsync(temp_file.get()) != 0) {\n    return false;\n  }\n\n  return ::renameat(base_directory.get(), temp_file_name.c_str(),\n                    base_directory.get(), file_name) == 0;\n}\n\nbool VisitFiles(const fml::UniqueFD& directory, const FileVisitor& visitor) {\n  fml::UniqueFD dup_fd(dup(directory.get()));\n  if (!dup_fd.is_valid()) {\n    FML_DLOG(ERROR) << \"Can't dup the directory fd. Error: \" << strerror(errno);\n    return true;  // continue to visit other files\n  }\n\n  fml::UniqueDir dir(::fdopendir(dup_fd.get()));\n  if (!dir.is_valid()) {\n    FML_DLOG(ERROR) << \"Can't open the directory. Error: \" << strerror(errno);\n    return true;  // continue to visit other files\n  }\n\n  // The directory fd will be closed by `closedir`.\n  (void)dup_fd.release();\n\n  // Without `rewinddir`, `readir` will directly return NULL (end of dir is\n  // reached) after a previuos `VisitFiles` call for the same `const\n  // fml::UniqueFd& directory`.\n  rewinddir(dir.get());\n  while (dirent* ent = readdir(dir.get())) {\n    std::string filename = ent->d_name;\n    if (filename != \".\" && filename != \"..\") {\n      if (!visitor(directory, filename)) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\n// From chromium //base/files/file_posix.cc File::GetInfo\nbool GetFileInfo(const fml::UniqueFD& file, FileInfo* info) {\n  struct stat file_info;\n  if (!::fstat(file.get(), &file_info)) {\n    return false;\n  }\n  info->size = file_info.st_size;\n#if defined(OS_LINUX)\n  info->last_access_time = fml::TimePoint::FromEpochDelta(\n      fml::TimeDelta::FromTimespec(file_info.st_atim));\n#elif defined(OS_ANDROID)\n  info->last_access_time = fml::TimePoint::FromEpochDelta(\n      fml::TimeDelta::FromSeconds(file_info.st_atime) +\n      fml::TimeDelta::FromNanoseconds(file_info.st_atime_nsec));\n#elif defined(OS_APPLE)\n  info->last_access_time = fml::TimePoint::FromEpochDelta(\n      fml::TimeDelta::FromTimespec(file_info.st_atimespec));\n#endif\n  return true;\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/posix/mapping_posix.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\n#include <type_traits>\n\n#include \"base/include/fml/eintr_wrapper.h\"\n#include \"base/include/fml/unique_fd.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/mapping.h\"\n\nnamespace fml {\n\nstatic int ToPosixProtectionFlags(\n    std::initializer_list<FileMapping::Protection> protection_flags) {\n  int flags = 0;\n  for (auto protection : protection_flags) {\n    switch (protection) {\n      case FileMapping::Protection::kRead:\n        flags |= PROT_READ;\n        break;\n      case FileMapping::Protection::kWrite:\n        flags |= PROT_WRITE;\n        break;\n      case FileMapping::Protection::kExecute:\n        flags |= PROT_READ | PROT_EXEC;\n        break;\n    }\n  }\n  return flags;\n}\n\nstatic bool IsWritable(\n    std::initializer_list<FileMapping::Protection> protection_flags) {\n  for (auto protection : protection_flags) {\n    if (protection == FileMapping::Protection::kWrite) {\n      return true;\n    }\n  }\n  return false;\n}\n\nMapping::Mapping() = default;\n\nMapping::~Mapping() = default;\n\nFileMapping::FileMapping(const fml::UniqueFD& handle,\n                         std::initializer_list<Protection> protection) {\n  if (!handle.is_valid()) {\n    return;\n  }\n\n  struct stat stat_buffer = {};\n\n  if (::fstat(handle.get(), &stat_buffer) != 0) {\n    return;\n  }\n\n  if (stat_buffer.st_size == 0) {\n    valid_ = true;\n    return;\n  }\n\n  const auto is_writable = IsWritable(protection);\n\n  auto* mapping =\n      ::mmap(nullptr, stat_buffer.st_size, ToPosixProtectionFlags(protection),\n             is_writable ? MAP_SHARED : MAP_PRIVATE, handle.get(), 0);\n\n  if (mapping == MAP_FAILED) {\n    return;\n  }\n\n  mapping_ = static_cast<uint8_t*>(mapping);\n  size_ = stat_buffer.st_size;\n  valid_ = true;\n  if (is_writable) {\n    mutable_mapping_ = mapping_;\n  }\n}\n\nFileMapping::~FileMapping() {\n  if (mapping_ != nullptr) {\n    ::munmap(mapping_, size_);\n  }\n}\n\nsize_t FileMapping::GetSize() const { return size_; }\n\nconst uint8_t* FileMapping::GetMapping() const { return mapping_; }\n\nbool FileMapping::IsDontNeedSafe() const { return mutable_mapping_ == nullptr; }\n\nbool FileMapping::IsValid() const { return valid_; }\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/posix/native_library_posix.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <dlfcn.h>\n#include <fcntl.h>\n\n#include <climits>\n\n#include \"build/build_config.h\"\n#include \"clay/fml/native_library.h\"\n\n#if OS_IOS || OS_ANDROID\n#error This file is not for iOS & Android\n#endif\n\nnamespace fml {\n\nNativeLibrary::NativeLibrary(const char* path) : NativeLibrary(path, false) {}\n\nNativeLibrary::NativeLibrary(const char* path, bool faker) {\n  ::dlerror();\n\n  if (handle_ == nullptr) {\n    handle_ = ::dlopen(path, RTLD_NOW);\n  }\n\n  if (handle_ == nullptr) {\n    FML_DLOG(ERROR) << \"Could not open library '\" << path << \"' due to error '\"\n                    << ::dlerror() << \"'.\";\n  }\n\n  faker_ = faker;\n}\n\nNativeLibrary::NativeLibrary(Handle handle, bool close_handle)\n    : handle_(handle), close_handle_(close_handle) {}\n\nNativeLibrary::~NativeLibrary() {\n  if (handle_ == nullptr) {\n    return;\n  }\n\n  if (close_handle_) {\n    ::dlerror();\n\n    int close_status = INT_MAX;\n\n    if (close_status == INT_MAX) {\n      close_status = ::dlclose(handle_);\n    }\n\n    if (close_status != 0) {\n      handle_ = nullptr;\n      FML_LOG(ERROR) << \"Could not close library due to error '\" << ::dlerror()\n                     << \"'.\";\n    }\n  }\n}\n\nNativeLibrary::Handle NativeLibrary::GetHandle() const { return handle_; }\n\nfml::RefPtr<NativeLibrary> NativeLibrary::Create(const char* path) {\n  return NativeLibrary::Create(path, false);\n}\n\nfml::RefPtr<NativeLibrary> NativeLibrary::Create(const char* path, bool faker) {\n  auto library = fml::AdoptRef(new NativeLibrary(path, faker));\n  return library->GetHandle() != nullptr ? library : nullptr;\n}\n\nfml::RefPtr<NativeLibrary> NativeLibrary::CreateWithHandle(\n    Handle handle, bool close_handle_when_done) {\n  auto library =\n      fml::AdoptRef(new NativeLibrary(handle, close_handle_when_done));\n  return library->GetHandle() != nullptr ? library : nullptr;\n}\n\nfml::RefPtr<NativeLibrary> NativeLibrary::CreateForCurrentProcess() {\n  return fml::AdoptRef(new NativeLibrary(RTLD_DEFAULT, false));\n}\n\nNativeLibrary::SymbolHandle NativeLibrary::Resolve(const char* symbol) const {\n  return ::dlsym(handle_, symbol);\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/posix/paths_posix.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <unistd.h>\n\n#include <climits>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/paths.h\"\n\nnamespace fml {\nnamespace paths {\n\nnamespace {\n\nconstexpr char kFileURLPrefix[] = \"file://\";\nconstexpr size_t kFileURLPrefixLength = sizeof(kFileURLPrefix) - 1;\n\nstd::string GetCurrentDirectory() {\n  char buffer[PATH_MAX];\n  FML_CHECK(getcwd(buffer, sizeof(buffer)));\n  return std::string(buffer);\n}\n\n}  // namespace\n\nstd::string AbsolutePath(const std::string& path) {\n  if (!path.empty()) {\n    if (path[0] == '/') {\n      // Path is already absolute.\n      return path;\n    }\n    return GetCurrentDirectory() + \"/\" + path;\n  } else {\n    // Path is empty.\n    return GetCurrentDirectory();\n  }\n}\n\nstd::string GetDirectoryName(const std::string& path) {\n  size_t separator = path.rfind('/');\n  if (separator == 0u) {\n    return \"/\";\n  }\n  if (separator == std::string::npos) {\n    return std::string();\n  }\n  return path.substr(0, separator);\n}\n\nstd::string FromURI(const std::string& uri) {\n  if (uri.substr(0, kFileURLPrefixLength) != kFileURLPrefix) {\n    return uri;\n  }\n\n  std::string file_path = uri.substr(kFileURLPrefixLength);\n  return SanitizeURIEscapedCharacters(file_path);\n}\n\n}  // namespace paths\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/posix/posix_wrappers_posix.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstring>\n\n#include \"clay/fml/posix_wrappers.h\"\n\nnamespace fml {\n\nchar* strdup(const char* str1) { return ::strdup(str1); }\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/command_line_win.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <windows.h>\n// NOLINTNEXTLINE(may cause unknown type EXTERN_C error)\n#include <Shellapi.h>\n\n#include <memory>\n\n#include \"clay/fml/command_line.h\"\n\nnamespace fml {\n\nstd::optional<CommandLine> CommandLineFromPlatform() {\n  wchar_t* command_line = GetCommandLineW();\n  int unicode_argc;\n  std::unique_ptr<wchar_t*[], decltype(::LocalFree)*> unicode_argv(\n      CommandLineToArgvW(command_line, &unicode_argc), ::LocalFree);\n  if (!unicode_argv) {\n    return std::nullopt;\n  }\n\n  std::vector<std::string> utf8_argv;\n  for (int i = 0; i < unicode_argc; ++i) {\n    wchar_t* arg = unicode_argv[i];\n    int arg_len = WideCharToMultiByte(CP_UTF8, 0, arg, wcslen(arg), nullptr, 0,\n                                      nullptr, nullptr);\n    std::string utf8_arg(arg_len, 0);\n    WideCharToMultiByte(CP_UTF8, 0, arg, -1, utf8_arg.data(), utf8_arg.size(),\n                        nullptr, nullptr);\n    utf8_argv.push_back(std::move(utf8_arg));\n  }\n  return CommandLineFromIterators(utf8_argv.begin(), utf8_argv.end());\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/errors_win.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/win/errors_win.h\"\n\n#include <Windows.h>\n\n#include <sstream>\n\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n\nnamespace fml {\n\nstd::string GetLastErrorMessage() {\n  DWORD last_error = ::GetLastError();\n  if (last_error == 0) {\n    return {};\n  }\n\n  const DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |\n                      FORMAT_MESSAGE_FROM_SYSTEM |\n                      FORMAT_MESSAGE_IGNORE_INSERTS;\n\n  wchar_t* buffer = nullptr;\n  size_t size = ::FormatMessage(\n      flags,                                      // dwFlags\n      NULL,                                       // lpSource\n      last_error,                                 // dwMessageId\n      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),  // dwLanguageId\n      (LPWSTR)&buffer,                            // lpBuffer\n      0,                                          // nSize\n      NULL                                        // Arguments\n  );                                              // NOLINT\n\n  std::wstring message(buffer, size);\n\n  ::LocalFree(buffer);\n\n  std::wstringstream stream;\n  stream << message << \" (\" << last_error << \").\";\n\n  return WideStringToUtf8(stream.str());\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/errors_win.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_WIN_ERRORS_WIN_H_\n#define CLAY_FML_PLATFORM_WIN_ERRORS_WIN_H_\n\n#include <string>\n\nnamespace fml {\n\nstd::string GetLastErrorMessage();\n\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_WIN_ERRORS_WIN_H_\n"
  },
  {
    "path": "clay/fml/platform/win/file_win.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// clang-format off\n#include <Windows.h>\n// clang-format on\n#include <Fileapi.h>\n#include <Shlwapi.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <sys/stat.h>\n\n#include <algorithm>\n#include <climits>\n#include <cstring>\n#include <limits>\n#include <optional>\n#include <sstream>\n#include <string>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/fml/platform/win/errors_win.h\"\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n\nnamespace fml {\n\nstatic std::string GetFullHandlePath(const fml::UniqueFD& handle) {\n  wchar_t buffer[MAX_PATH] = {0};\n  const DWORD buffer_size = ::GetFinalPathNameByHandle(\n      handle.get(), buffer, MAX_PATH, FILE_NAME_NORMALIZED);\n  if (buffer_size == 0) {\n    return {};\n  }\n  return WideStringToUtf8({buffer, buffer_size});\n}\n\nstatic bool IsAbsolutePath(const char* path) {\n  if (path == nullptr || strlen(path) == 0) {\n    return false;\n  }\n\n  auto wpath = Utf8ToWideString({path});\n  if (wpath.empty()) {\n    return false;\n  }\n\n  return ::PathIsRelative(wpath.c_str()) == FALSE;\n}\n\nstatic std::string GetAbsolutePath(const fml::UniqueFD& base_directory,\n                                   const char* subpath) {\n  std::string path;\n  if (IsAbsolutePath(subpath)) {\n    path = subpath;\n  } else {\n    std::stringstream stream;\n    stream << GetFullHandlePath(base_directory) << \"\\\\\" << subpath;\n    path = stream.str();\n  }\n  std::replace(path.begin(), path.end(), '/', '\\\\');\n  return path;\n}\n\nstatic std::wstring GetTemporaryDirectoryPath() {\n  wchar_t wchar_path[MAX_PATH];\n  auto result_size = ::GetTempPath(MAX_PATH, wchar_path);\n  if (result_size > 0) {\n    return {wchar_path, result_size};\n  }\n  return {};\n}\n\nstatic DWORD GetDesiredAccessFlags(FilePermission permission) {\n  switch (permission) {\n    case FilePermission::kRead:\n      return GENERIC_READ;\n    case FilePermission::kWrite:\n      return GENERIC_WRITE;\n    case FilePermission::kReadWrite:\n      return GENERIC_READ | GENERIC_WRITE;\n  }\n  return GENERIC_READ;\n}\n\nstatic DWORD GetShareFlags(FilePermission permission) {\n  switch (permission) {\n    case FilePermission::kRead:\n      return FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;\n    case FilePermission::kWrite:\n      return 0;\n    case FilePermission::kReadWrite:\n      return 0;\n  }\n  return FILE_SHARE_READ;\n}\n\nstatic DWORD GetFileAttributesForUtf8Path(const char* absolute_path) {\n  return ::GetFileAttributes(Utf8ToWideString(absolute_path).c_str());\n}\n\nstatic DWORD GetFileAttributesForUtf8Path(const fml::UniqueFD& base_directory,\n                                          const char* path) {\n  std::string full_path = GetAbsolutePath(base_directory, path);\n  return GetFileAttributesForUtf8Path(full_path.c_str());\n}\n\nstd::string CreateTemporaryDirectory() {\n  // Get the system temporary directory.\n  auto temp_dir_container = GetTemporaryDirectoryPath();\n  if (temp_dir_container.empty()) {\n    FML_DLOG(ERROR) << \"Could not get system temporary directory.\";\n    return {};\n  }\n\n  // Create a UUID.\n  UUID uuid;\n  RPC_STATUS status = UuidCreateSequential(&uuid);\n  if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) {\n    FML_DLOG(ERROR) << \"Could not create UUID for temporary directory.\";\n    return {};\n  }\n\n  RPC_WSTR uuid_string;\n  status = UuidToString(&uuid, &uuid_string);\n  if (status != RPC_S_OK) {\n    FML_DLOG(ERROR) << \"Could not map UUID to string for temporary directory.\";\n    return {};\n  }\n\n  std::wstring uuid_str(reinterpret_cast<wchar_t*>(uuid_string));\n  RpcStringFree(&uuid_string);\n\n  // Join the two and create a path to the new temporary directory.\n\n  std::wstringstream stream;\n  stream << temp_dir_container << uuid_str;\n  auto temp_dir = stream.str();\n\n  auto dir_fd = OpenDirectory(WideStringToUtf8(temp_dir).c_str(), true,\n                              FilePermission::kReadWrite);\n  if (!dir_fd.is_valid()) {\n    FML_DLOG(ERROR) << \"Could not get temporary directory file descriptor. \"\n                    << GetLastErrorMessage();\n    return {};\n  }\n\n  return WideStringToUtf8(std::move(temp_dir));\n}\n\nfml::UniqueFD OpenFile(const fml::UniqueFD& base_directory, const char* path,\n                       bool create_if_necessary, FilePermission permission) {\n  return OpenFile(GetAbsolutePath(base_directory, path).c_str(),\n                  create_if_necessary, permission);\n}\n\nfml::UniqueFD OpenFile(const char* path, bool create_if_necessary,\n                       FilePermission permission) {\n  if (path == nullptr || strlen(path) == 0) {\n    return {};\n  }\n\n  auto file_name = Utf8ToWideString({path});\n\n  if (file_name.empty()) {\n    return {};\n  }\n\n  const DWORD creation_disposition =\n      create_if_necessary ? OPEN_ALWAYS : OPEN_EXISTING;\n\n  const DWORD flags = FILE_ATTRIBUTE_NORMAL;\n\n  auto handle =\n      CreateFile(file_name.c_str(),                  // lpFileName\n                 GetDesiredAccessFlags(permission),  // dwDesiredAccess\n                 GetShareFlags(permission),          // dwShareMode\n                 nullptr,                            // lpSecurityAttributes  //\n                 creation_disposition,               // dwCreationDisposition //\n                 flags,   // dwFlagsAndAttributes                  //\n                 nullptr  // hTemplateFile                         //\n      );                  // NOLINT\n\n  if (handle == INVALID_HANDLE_VALUE) {\n    return {};\n  }\n\n  return fml::UniqueFD{handle};\n}\n\nfml::UniqueFD OpenDirectory(const fml::UniqueFD& base_directory,\n                            const char* path, bool create_if_necessary,\n                            FilePermission permission) {\n  return OpenDirectory(GetAbsolutePath(base_directory, path).c_str(),\n                       create_if_necessary, permission);\n}\n\nfml::UniqueFD OpenDirectory(const char* path, bool create_if_necessary,\n                            FilePermission permission) {\n  if (path == nullptr || strlen(path) == 0) {\n    return {};\n  }\n\n  auto file_name = Utf8ToWideString({path});\n\n  if (file_name.empty()) {\n    return {};\n  }\n\n  if (create_if_necessary) {\n    if (!::CreateDirectory(file_name.c_str(), nullptr)) {\n      if (GetLastError() != ERROR_ALREADY_EXISTS) {\n        FML_DLOG(ERROR) << \"Could not create directory. \"\n                        << GetLastErrorMessage();\n        return {};\n      }\n    }\n  }\n\n  const DWORD creation_disposition = OPEN_EXISTING;\n\n  const DWORD flags = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS;\n\n  auto handle =\n      CreateFile(file_name.c_str(),                  // lpFileName\n                 GetDesiredAccessFlags(permission),  // dwDesiredAccess\n                 GetShareFlags(permission),          // dwShareMode\n                 nullptr,                            // lpSecurityAttributes  //\n                 creation_disposition,               // dwCreationDisposition //\n                 flags,   // dwFlagsAndAttributes                  //\n                 nullptr  // hTemplateFile                         //\n      );                  // NOLINT\n\n  if (handle == INVALID_HANDLE_VALUE) {\n    return {};\n  }\n\n  return fml::UniqueFD{handle};\n}\n\nfml::UniqueFD Duplicate(fml::UniqueFD::element_type descriptor) {\n  if (descriptor == INVALID_HANDLE_VALUE) {\n    return fml::UniqueFD{};\n  }\n\n  HANDLE duplicated = INVALID_HANDLE_VALUE;\n\n  if (!::DuplicateHandle(\n          GetCurrentProcess(),  // source process\n          descriptor,           // source handle\n          GetCurrentProcess(),  // target process\n          &duplicated,          // target handle\n          0,      // desired access (ignored because DUPLICATE_SAME_ACCESS)\n          FALSE,  // inheritable\n          DUPLICATE_SAME_ACCESS)  // options\n  ) {\n    return fml::UniqueFD{};\n  }\n\n  return fml::UniqueFD{duplicated};\n}\n\nbool IsDirectory(const fml::UniqueFD& directory) {\n  BY_HANDLE_FILE_INFORMATION info;\n  if (!::GetFileInformationByHandle(directory.get(), &info)) {\n    return false;\n  }\n  return info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;\n}\n\nbool IsDirectory(const fml::UniqueFD& base_directory, const char* path) {\n  return GetFileAttributesForUtf8Path(base_directory, path) &\n         FILE_ATTRIBUTE_DIRECTORY;\n}\n\nbool IsFile(const std::string& path) {\n  DWORD attributes = GetFileAttributesForUtf8Path(path.c_str());\n  if (attributes == INVALID_FILE_ATTRIBUTES) {\n    return false;\n  }\n  return !(attributes &\n           (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT));\n}\n\nbool UnlinkDirectory(const char* path) {\n  if (!::RemoveDirectory(Utf8ToWideString(path).c_str())) {\n    FML_DLOG(ERROR) << \"Could not remove directory: '\" << path << \"'. \"\n                    << GetLastErrorMessage();\n    return false;\n  }\n  return true;\n}\n\nbool UnlinkDirectory(const fml::UniqueFD& base_directory, const char* path) {\n  if (!::RemoveDirectory(\n          Utf8ToWideString(GetAbsolutePath(base_directory, path)).c_str())) {\n    FML_DLOG(ERROR) << \"Could not remove directory: '\" << path << \"'. \"\n                    << GetLastErrorMessage();\n    return false;\n  }\n  return true;\n}\n\nbool UnlinkFile(const char* path) {\n  if (!::DeleteFile(Utf8ToWideString(path).c_str())) {\n    FML_DLOG(ERROR) << \"Could not remove file: '\" << path << \"'. \"\n                    << GetLastErrorMessage();\n    return false;\n  }\n  return true;\n}\n\nbool UnlinkFile(const fml::UniqueFD& base_directory, const char* path) {\n  if (!::DeleteFile(\n          Utf8ToWideString(GetAbsolutePath(base_directory, path)).c_str())) {\n    FML_DLOG(ERROR) << \"Could not remove file: '\" << path << \"'. \"\n                    << GetLastErrorMessage();\n    return false;\n  }\n  return true;\n}\n\nbool TruncateFile(const fml::UniqueFD& file, size_t size) {\n  LARGE_INTEGER large_size;\n  large_size.QuadPart = size;\n  large_size.LowPart = SetFilePointer(file.get(), large_size.LowPart,\n                                      &large_size.HighPart, FILE_BEGIN);\n  if (large_size.LowPart == INVALID_SET_FILE_POINTER &&\n      GetLastError() != NO_ERROR) {\n    FML_DLOG(ERROR) << \"Could not update file size. \" << GetLastErrorMessage();\n    return false;\n  }\n\n  if (!::SetEndOfFile(file.get())) {\n    FML_DLOG(ERROR) << \"Could not commit file size update. \"\n                    << GetLastErrorMessage();\n    return false;\n  }\n  return true;\n}\n\nbool FileExists(const fml::UniqueFD& base_directory, const char* path) {\n  return GetFileAttributesForUtf8Path(base_directory, path) !=\n         INVALID_FILE_ATTRIBUTES;\n}\n\n// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/102838 this is\n// not atomic on Windows.\nbool WriteAtomically(const fml::UniqueFD& base_directory, const char* file_name,\n                     const Mapping& mapping) {\n  if (file_name == nullptr) {\n    return false;\n  }\n\n  auto file_path = GetAbsolutePath(base_directory, file_name);\n  auto temp_file =\n      OpenFile(file_path.c_str(), true, FilePermission::kReadWrite);\n\n  if (!temp_file.is_valid()) {\n    FML_DLOG(ERROR) << \"Could not create file: \" << file_path.c_str() << \" \"\n                    << GetLastError() << \" \" << GetLastErrorMessage();\n    return false;\n  }\n\n  if (!TruncateFile(temp_file, mapping.GetSize())) {\n    FML_DLOG(ERROR) << \"Could not truncate the file to the correct size. \"\n                    << GetLastErrorMessage();\n    return false;\n  }\n\n  {\n    FileMapping temp_file_mapping(temp_file, {FileMapping::Protection::kRead,\n                                              FileMapping::Protection::kWrite});\n    if (temp_file_mapping.GetSize() != mapping.GetSize()) {\n      FML_DLOG(ERROR) << \"Temporary file mapping size was incorrect. Is \"\n                      << temp_file_mapping.GetSize() << \". Should be \"\n                      << mapping.GetSize() << \".\";\n      return false;\n    }\n\n    if (temp_file_mapping.GetMutableMapping() == nullptr) {\n      FML_DLOG(ERROR) << \"Temporary file does not have a mutable mapping.\";\n      return false;\n    }\n\n    ::memcpy(temp_file_mapping.GetMutableMapping(), mapping.GetMapping(),\n             mapping.GetSize());\n\n    if (!::FlushViewOfFile(temp_file_mapping.GetMutableMapping(),\n                           mapping.GetSize())) {\n      FML_DLOG(ERROR) << \"Could not flush file view. \" << GetLastErrorMessage();\n      return false;\n    }\n\n    if (!::FlushFileBuffers(temp_file.get())) {\n      FML_DLOG(ERROR) << \"Could not flush file buffers. \"\n                      << GetLastErrorMessage();\n      return false;\n    }\n\n    // File mapping is detroyed.\n  }\n\n  temp_file.reset();\n\n  return true;\n}\n\nbool VisitFiles(const fml::UniqueFD& directory, const FileVisitor& visitor) {\n  std::string search_pattern = GetFullHandlePath(directory) + \"\\\\*\";\n  WIN32_FIND_DATA find_file_data;\n  HANDLE find_handle = ::FindFirstFile(Utf8ToWideString(search_pattern).c_str(),\n                                       &find_file_data);\n\n  if (find_handle == INVALID_HANDLE_VALUE) {\n    FML_DLOG(ERROR) << \"Can't open the directory. Error: \"\n                    << GetLastErrorMessage();\n    return true;  // continue to visit other files\n  }\n\n  do {\n    std::string filename = WideStringToUtf8(find_file_data.cFileName);\n    if (filename != \".\" && filename != \"..\") {\n      if (!visitor(directory, filename)) {\n        ::FindClose(find_handle);\n        return false;\n      }\n    }\n  } while (::FindNextFile(find_handle, &find_file_data));\n  ::FindClose(find_handle);\n  return true;\n}\n\n// From chromium //base/files/file_win.cc File::GetInfo\nbool GetFileInfo(const fml::UniqueFD& file, FileInfo* info) {\n  BY_HANDLE_FILE_INFORMATION file_info;\n  if (!GetFileInformationByHandle(file.get(), &file_info)) {\n    return false;\n  }\n\n  LARGE_INTEGER size;\n  size.HighPart = file_info.nFileSizeHigh;\n  size.LowPart = file_info.nFileSizeLow;\n  info->size = size.QuadPart;\n\n  const FILETIME& last_access_time = file_info.ftLastAccessTime;\n  ULARGE_INTEGER time{last_access_time.dwLowDateTime,\n                      last_access_time.dwHighDateTime};\n  if (time.QuadPart == 0) {\n    info->last_access_time = fml::TimePoint::Min();\n  } else if (last_access_time.dwHighDateTime ==\n                 std::numeric_limits<DWORD>::max() &&\n             last_access_time.dwLowDateTime ==\n                 std::numeric_limits<DWORD>::max()) {\n    info->last_access_time = fml::TimePoint::Max();\n  } else {\n    info->last_access_time = fml::TimePoint::FromEpochDelta(\n        fml::TimeDelta::FromMicroseconds(time.QuadPart / 10));\n  }\n  return true;\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/file_win_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <Fileapi.h>\n\n#include \"clay/fml/file.h\"\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace fml {\nnamespace testing {\n\nTEST(FileWin, OpenDirectoryShare) {\n  fml::ScopedTemporaryDirectory temp_dir;\n  auto temp_path = Utf8ToWideString({temp_dir.path()});\n  fml::UniqueFD handle(\n      CreateFile(temp_path.c_str(), GENERIC_WRITE,\n                 FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,\n                 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr));\n  ASSERT_TRUE(handle.is_valid());\n\n  // Check that fml::OpenDirectory(FilePermission::kRead) works with a\n  // directory that has also been opened for writing.\n  auto dir = fml::OpenDirectory(temp_dir.path().c_str(), false,\n                                fml::FilePermission::kRead);\n  ASSERT_TRUE(dir.is_valid());\n}\n\n}  // namespace testing\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/mapping_win.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <fcntl.h>\n#include <io.h>\n#include <windows.h>\n\n#include <type_traits>\n\n#include \"clay/fml/file.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/fml/platform/win/errors_win.h\"\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n\nnamespace fml {\n\nMapping::Mapping() = default;\n\nMapping::~Mapping() = default;\n\nstatic bool IsWritable(\n    std::initializer_list<FileMapping::Protection> protection_flags) {\n  for (auto protection : protection_flags) {\n    if (protection == FileMapping::Protection::kWrite) {\n      return true;\n    }\n  }\n  return false;\n}\n\nstatic bool IsExecutable(\n    std::initializer_list<FileMapping::Protection> protection_flags) {\n  for (auto protection : protection_flags) {\n    if (protection == FileMapping::Protection::kExecute) {\n      return true;\n    }\n  }\n  return false;\n}\n\nFileMapping::FileMapping(const fml::UniqueFD& fd,\n                         std::initializer_list<Protection> protections)\n    : size_(0), mapping_(nullptr) {\n  if (!fd.is_valid()) {\n    return;\n  }\n\n  const auto mapping_size = ::GetFileSize(fd.get(), nullptr);\n\n  if (mapping_size == INVALID_FILE_SIZE) {\n    FML_DLOG(ERROR) << \"Invalid file size. \" << GetLastErrorMessage();\n    return;\n  }\n\n  if (mapping_size == 0) {\n    valid_ = true;\n    return;\n  }\n\n  DWORD protect_flags = 0;\n  bool read_only = !IsWritable(protections);\n\n  if (IsExecutable(protections)) {\n    protect_flags = PAGE_EXECUTE_READ;\n  } else if (read_only) {\n    protect_flags = PAGE_READONLY;\n  } else {\n    protect_flags = PAGE_READWRITE;\n  }\n\n  mapping_handle_.reset(::CreateFileMapping(fd.get(),       // hFile\n                                            nullptr,        // lpAttributes\n                                            protect_flags,  // flProtect\n                                            0,              // dwMaximumSizeHigh\n                                            0,              // dwMaximumSizeLow\n                                            nullptr         // lpName\n                                            ));             // NOLINT\n\n  if (!mapping_handle_.is_valid()) {\n    return;\n  }\n\n  const DWORD desired_access = read_only ? FILE_MAP_READ : FILE_MAP_WRITE;\n\n  auto mapping = reinterpret_cast<uint8_t*>(\n      MapViewOfFile(mapping_handle_.get(), desired_access, 0, 0, mapping_size));\n\n  if (mapping == nullptr) {\n    FML_DLOG(ERROR) << \"Could not set up file mapping. \"\n                    << GetLastErrorMessage();\n    return;\n  }\n\n  mapping_ = mapping;\n  size_ = mapping_size;\n  valid_ = true;\n  if (IsWritable(protections)) {\n    mutable_mapping_ = mapping_;\n  }\n}\n\nFileMapping::~FileMapping() {\n  if (mapping_ != nullptr) {\n    UnmapViewOfFile(mapping_);\n  }\n}\n\nsize_t FileMapping::GetSize() const { return size_; }\n\nconst uint8_t* FileMapping::GetMapping() const { return mapping_; }\n\nbool FileMapping::IsDontNeedSafe() const { return mutable_mapping_ == nullptr; }\n\nbool FileMapping::IsValid() const { return valid_; }\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/native_library_win.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <windows.h>\n\n#include \"clay/fml/native_library.h\"\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n\nnamespace fml {\n\nNativeLibrary::NativeLibrary(const char* path)\n    : handle_(nullptr), close_handle_(true) {\n  if (path == nullptr) {\n    return;\n  }\n\n  handle_ = ::LoadLibrary(Utf8ToWideString(path).c_str());\n}\n\nNativeLibrary::NativeLibrary(Handle handle, bool close_handle)\n    : handle_(handle), close_handle_(close_handle) {}\n\nNativeLibrary::~NativeLibrary() {\n  if (handle_ != nullptr && close_handle_) {\n    ::FreeLibrary(handle_);\n  }\n}\n\nNativeLibrary::Handle NativeLibrary::GetHandle() const { return handle_; }\n\nfml::RefPtr<NativeLibrary> NativeLibrary::Create(const char* path) {\n  auto library = fml::AdoptRef(new NativeLibrary(path));\n  return library->GetHandle() != nullptr ? library : nullptr;\n}\n\nfml::RefPtr<NativeLibrary> NativeLibrary::CreateWithHandle(\n    Handle handle, bool close_handle_when_done) {\n  auto library =\n      fml::AdoptRef(new NativeLibrary(handle, close_handle_when_done));\n  return library->GetHandle() != nullptr ? library : nullptr;\n}\n\nfml::RefPtr<NativeLibrary> NativeLibrary::CreateForCurrentProcess() {\n  return fml::AdoptRef(new NativeLibrary(::GetModuleHandle(nullptr), false));\n}\n\nNativeLibrary::SymbolHandle NativeLibrary::Resolve(const char* symbol) const {\n  if (symbol == nullptr || handle_ == nullptr) {\n    return nullptr;\n  }\n  return ::GetProcAddress(handle_, symbol);\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/paths_win.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <windows.h>\n\n#include <algorithm>\n\n#include \"clay/fml/paths.h\"\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n\nnamespace fml {\nnamespace paths {\n\nnamespace {\n\nconstexpr char kFileURLPrefix[] = \"file:///\";\nconstexpr size_t kFileURLPrefixLength = sizeof(kFileURLPrefix) - 1;\n\nsize_t RootLength(const std::string& path) {\n  if (path.empty()) return 0;\n  if (path[0] == '/') return 1;\n  if (path[0] == '\\\\') {\n    if (path.size() < 2 || path[1] != '\\\\') return 1;\n    // The path is a network share. Search for up to two '\\'s, as they are\n    // the server and share - and part of the root part.\n    size_t index = path.find('\\\\', 2);\n    if (index > 0) {\n      index = path.find('\\\\', index + 1);\n      if (index > 0) return index;\n    }\n    return path.size();\n  }\n  // If the path is of the form 'C:/' or 'C:\\', with C being any letter, it's\n  // a root part.\n  if (path.length() >= 2 && path[1] == ':' &&\n      (path[2] == '/' || path[2] == '\\\\') &&\n      ((path[0] >= 'A' && path[0] <= 'Z') ||\n       (path[0] >= 'a' && path[0] <= 'z'))) {\n    return 3;\n  }\n  return 0;\n}\n\nsize_t LastSeparator(const std::string& path) {\n  return path.find_last_of(\"/\\\\\");\n}\n\n}  // namespace\n\nstd::pair<bool, std::string> GetExecutablePath() {\n  HMODULE module = GetModuleHandle(NULL);\n  if (module == NULL) {\n    return {false, \"\"};\n  }\n  wchar_t path[MAX_PATH];\n  DWORD read_size = GetModuleFileNameW(module, path, MAX_PATH);\n  if (read_size == 0 || read_size == MAX_PATH) {\n    return {false, \"\"};\n  }\n  return {true, WideStringToUtf8({path, read_size})};\n}\n\nstd::string AbsolutePath(const std::string& path) {\n  std::wstring w_path = Utf8ToWideString(path.c_str());\n  wchar_t abs_path[MAX_PATH];\n  if (_wfullpath(abs_path, w_path.c_str(), MAX_PATH) != NULL) {\n    return WideStringToUtf8(abs_path);\n  }\n  return \"\";\n}\n\nstd::string GetDirectoryName(const std::string& path) {\n  size_t rootLength = RootLength(path);\n  size_t separator = LastSeparator(path);\n  if (separator < rootLength) separator = rootLength;\n  if (separator == std::string::npos) return std::string();\n  return path.substr(0, separator);\n}\n\nstd::string FromURI(const std::string& uri) {\n  if (uri.substr(0, kFileURLPrefixLength) != kFileURLPrefix) return uri;\n\n  std::string file_path = uri.substr(kFileURLPrefixLength);\n  std::replace(file_path.begin(), file_path.end(), '/', '\\\\');\n  return SanitizeURIEscapedCharacters(file_path);\n}\n\nfml::UniqueFD GetCachesDirectory() {\n  // Unsupported on this platform.\n  return {};\n}\n\n}  // namespace paths\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/posix_wrappers_win.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstring>\n\n#include \"clay/fml/posix_wrappers.h\"\n\nnamespace fml {\n\nchar* strdup(const char* str1) { return _strdup(str1); }\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/wstring_conversion.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n\n#include \"base/include/string/string_conversion_win.h\"\n\nnamespace fml {\n\nstd::string WideStringToUtf8(const std::wstring_view str) {\n  return lynx::base::Utf8FromUtf16(str);\n}\n\nstd::wstring Utf8ToWideString(const std::string_view str) {\n  return lynx::base::Utf16FromUtf8(str);\n}\n\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/platform/win/wstring_conversion.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_PLATFORM_WIN_WSTRING_CONVERSION_H_\n#define CLAY_FML_PLATFORM_WIN_WSTRING_CONVERSION_H_\n\n#include <string>\n\nnamespace fml {\n\n// Returns a UTF-8 encoded equivalent of a UTF-16 encoded input wide string.\nstd::string WideStringToUtf8(const std::wstring_view str);\n\n// Returns a UTF-16 encoded wide string equivalent of a UTF-8 encoded input\n// string.\nstd::wstring Utf8ToWideString(const std::string_view str);\n\n}  // namespace fml\n\n#endif  // CLAY_FML_PLATFORM_WIN_WSTRING_CONVERSION_H_\n"
  },
  {
    "path": "clay/fml/platform/win/wstring_conversion_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace fml {\nnamespace testing {\n\nTEST(StringConversion, Utf16ToWideStringEmpty) {\n  EXPECT_EQ(Utf8ToWideString(\"\"), L\"\");\n}\n\nTEST(StringConversion, Utf8ToWideStringAscii) {\n  EXPECT_EQ(Utf8ToWideString(\"abc123\"), L\"abc123\");\n}\n\nTEST(StringConversion, Utf8ToWideStringUnicode) {\n  EXPECT_EQ(Utf8ToWideString(\"\\xe2\\x98\\x83\"), L\"\\x2603\");\n}\n\nTEST(StringConversion, WideStringToUtf8Empty) {\n  EXPECT_EQ(WideStringToUtf8(L\"\"), \"\");\n}\n\nTEST(StringConversion, WideStringToUtf8Ascii) {\n  EXPECT_EQ(WideStringToUtf8(L\"abc123\"), \"abc123\");\n}\n\nTEST(StringConversion, WideStringToUtf8Unicode) {\n  EXPECT_EQ(WideStringToUtf8(L\"\\x2603\"), \"\\xe2\\x98\\x83\");\n}\n\n}  // namespace testing\n}  // namespace fml\n"
  },
  {
    "path": "clay/fml/posix_wrappers.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_POSIX_WRAPPERS_H_\n#define CLAY_FML_POSIX_WRAPPERS_H_\n\n#include \"build/build_config.h\"\n\n// Provides wrappers for POSIX functions that have been renamed on Windows.\n// See\n// https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4996?view=vs-2019#posix-function-names\n// for context.\nnamespace fml {\n\nchar* strdup(const char* str1);\n\n}  // namespace fml\n\n#endif  // CLAY_FML_POSIX_WRAPPERS_H_\n"
  },
  {
    "path": "clay/fml/size.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_SIZE_H_\n#define CLAY_FML_SIZE_H_\n\n#include <cstddef>\n\nnamespace fml {\n\ntemplate <typename T, std::size_t N>\nconstexpr std::size_t size(T (&array)[N]) {\n  return N;\n}\n\n}  // namespace fml\n\n#endif  // CLAY_FML_SIZE_H_\n"
  },
  {
    "path": "clay/fml/status.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_FML_STATUS_H_\n#define CLAY_FML_STATUS_H_\n\n#include <string_view>\n\nnamespace fml {\n\nenum class StatusCode {\n  kOk,\n  kCancelled,\n  kUnknown,\n  kInvalidArgument,\n  kDeadlineExceeded,\n  kNotFound,\n  kAlreadyExists,\n  kPermissionDenied,\n  kResourceExhausted,\n  kFailedPrecondition,\n  kAborted,\n  kOutOfRange,\n  kUnimplemented,\n  kInternal,\n  kUnavailable,\n  kDataLoss,\n  kUnauthenticated\n};\n\n/// Class that represents the resolution of the execution of a procedure.  This\n/// is used similarly to how exceptions might be used, typically as the return\n/// value to a synchronous procedure or an argument to an asynchronous callback.\nclass Status final {\n public:\n  /// Creates an 'ok' status.\n  Status();\n\n  Status(fml::StatusCode code, std::string_view message);\n\n  fml::StatusCode code() const;\n\n  /// A noop that helps with static analysis tools if you decide to ignore an\n  /// error.\n  void IgnoreError() const;\n\n  /// @return 'true' when the code is kOk.\n  bool ok() const;\n\n  std::string_view message() const;\n\n private:\n  fml::StatusCode code_;\n  std::string_view message_;\n};\n\ninline Status::Status() : code_(fml::StatusCode::kOk), message_() {}\n\ninline Status::Status(fml::StatusCode code, std::string_view message)\n    : code_(code), message_(message) {}\n\ninline fml::StatusCode Status::code() const { return code_; }\n\ninline void Status::IgnoreError() const {\n  // noop\n}\n\ninline bool Status::ok() const { return code_ == fml::StatusCode::kOk; }\n\ninline std::string_view Status::message() const { return message_; }\n\n}  // namespace fml\n\n#endif  // CLAY_FML_STATUS_H_\n"
  },
  {
    "path": "clay/gfx/BUILD.gn",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../shell/config.gni\")\nimport(\"../testing/testing.gni\")\nimport(\"animation/build.gni\")\nimport(\"geometry/build.gni\")\nimport(\"image/build.gni\")\nimport(\"skia/build.gni\")\nimport(\"style/build.gni\")\nimport(\"vulkan/build.gni\")\n\nsource_set(\"gfx\") {\n  sources = [\n    \"attributes.h\",\n    \"comparable.h\",\n    \"gfx_rendering_backend.h\",\n    \"gfx_utils.cc\",\n    \"gfx_utils.h\",\n    \"gpu_object.h\",\n    \"gpu_ref_object.h\",\n    \"graphics_canvas.cc\",\n    \"graphics_canvas.h\",\n    \"graphics_context.cc\",\n    \"graphics_context.h\",\n    \"graphics_isolate.cc\",\n    \"graphics_isolate.h\",\n    \"paint.cc\",\n    \"paint.h\",\n    \"paint_decoding_image.h\",\n    \"paint_image.cc\",\n    \"paint_image.h\",\n    \"paint_recorder.h\",\n    \"picture.cc\",\n    \"picture.h\",\n    \"pixel_helper.h\",\n    \"rendering_backend.h\",\n    \"scroll_direction.h\",\n    \"skity_to_skia_utils.h\",\n    \"svg/svg_dom.h\",\n    \"text_blob.cc\",\n    \"text_blob.h\",\n  ]\n\n  sources += rebase_path(gfx_animation_sources, \"\", \"animation\")\n  sources += rebase_path(gfx_geometry_sources, \"\", \"geometry\")\n  sources += rebase_path(gfx_image_sources, \"\", \"image\")\n  sources += rebase_path(gfx_style_sources, \"\", \"style\")\n\n  deps = [\n    \"../common\",\n    \"../fml:fml\",\n  ]\n\n  configs += [ \"../:config\" ]\n\n  if (enable_skity) {\n    sources += [\n      \"paint_image_skity.cc\",\n      \"paint_image_skity.h\",\n      \"paint_recorder_skity.cc\",\n      \"text_blob_skity.cc\",\n      \"text_blob_skity.h\",\n    ]\n  } else {\n    sources += [\n      \"paint_image_skia.cc\",\n      \"paint_image_skia.h\",\n      \"paint_image_skia_lazy.cc\",\n      \"paint_image_skia_lazy.h\",\n      \"paint_recorder_skia.cc\",\n      \"text_blob_skia.cc\",\n      \"text_blob_skia.h\",\n    ]\n    sources += rebase_path(gfx_skia_sources, \"\", \"skia\")\n    deps += [\n      \"../third_party/skity_geometry:skity_geometry\",\n      \"//third_party/skia\",\n    ]\n  }\n\n  if (enable_svg && !enable_skity) {\n    deps += [ \"//third_party/skia/modules/svg\" ]\n  }\n\n  if (is_win) {\n    # Required for M_PI and others.\n    defines = [ \"_USE_MATH_DEFINES\" ]\n\n    # For wstring_conversion. See issue #50053.\n    defines += [ \"_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING\" ]\n  }\n\n  if (is_android) {\n    sources += rebase_path(gfx_vulkan_sources, \"\", \"vulkan\")\n  }\n}\n\nif (enable_unittests) {\n  test_fixtures(\"gfx_fixtures\") {\n    fixtures = []\n  }\n\n  executable(\"gfx_unittests\") {\n    testonly = true\n\n    sources = [\n      \"animation/animation_handler_unittests.cc\",\n      \"animation/cubic_bezier_interpolator_unittests.cc\",\n      \"animation/value_animator_unittests.cc\",\n      \"atrributes_testing.h\",\n      \"gpu_object_unittests.cc\",\n      \"image/image_data_cache_unittests.cc\",\n      \"paint_unittests.cc\",\n      \"style/color_filter_unittests.cc\",\n      \"style/color_source_unittests.cc\",\n      \"style/color_unittests.cc\",\n      \"style/color_unittests_helper.h\",\n      \"style/enum_unittests.cc\",\n      \"style/image_filter_unittests.cc\",\n      \"style/mask_filter_unittests.cc\",\n      \"style/path_effect_unittests.cc\",\n      \"style/vertices_unittests.cc\",\n      \"testing_utils.h\",\n    ]\n\n    deps = [\n      \":gfx\",\n      \":gfx_fixtures\",\n      \"../../base/src:base_static\",\n      \"../gfx\",\n      \"../testing\",\n      \"../testing:skia\",\n      \"../testing:testing_lib\",\n    ]\n  }\n}\n"
  },
  {
    "path": "clay/gfx/animation/accelerate_decelerate_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/accelerate_decelerate_interpolator.h\"\n\n#include <math.h>\n\n#include <algorithm>\n\nnamespace clay {\n\nfloat AccelerateDecelerateInterpolator::Interpolate(float input) {\n  return static_cast<float>(cosf((input + 1) * M_PI) / 2.0f) + 0.5f;\n}\n\nstd::unique_ptr<AccelerateDecelerateInterpolator>\nAccelerateDecelerateInterpolator::Create() {\n  return std::make_unique<AccelerateDecelerateInterpolator>();\n}\n\nstd::unique_ptr<Interpolator> AccelerateDecelerateInterpolator::Clone() {\n  return AccelerateDecelerateInterpolator::Create();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/accelerate_decelerate_interpolator.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ACCELERATE_DECELERATE_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_ACCELERATE_DECELERATE_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass AccelerateDecelerateInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<AccelerateDecelerateInterpolator> Create();\n  std::unique_ptr<Interpolator> Clone() override;\n\n  float Interpolate(float input) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ACCELERATE_DECELERATE_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/accelerate_interpolator.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/accelerate_interpolator.h\"\n\n#include <math.h>\n\n#include <algorithm>\n\nnamespace clay {\n\nfloat AccelerateInterpolator::Interpolate(float input) {\n  if (factor_ == 1.0f) {\n    return input * input;\n  } else {\n    return pow(input, doubleFactor_);\n  }\n}\n\nstd::unique_ptr<AccelerateInterpolator> AccelerateInterpolator::Create(\n    float factor) {\n  return std::make_unique<AccelerateInterpolator>(factor);\n}\n\nstd::unique_ptr<Interpolator> AccelerateInterpolator::Clone() {\n  return AccelerateInterpolator::Create(factor_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/accelerate_interpolator.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ACCELERATE_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_ACCELERATE_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass AccelerateInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<AccelerateInterpolator> Create(float factor);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  explicit AccelerateInterpolator(float factor)\n      : factor_(factor), doubleFactor_(factor * 2) {}\n  float Interpolate(float input) override;\n\n private:\n  const float factor_;\n  const float doubleFactor_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ACCELERATE_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/animation_data.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/animation_data.h\"\n\nnamespace clay {\n\nbool AnimationData::operator==(const AnimationData& rhs) const {\n  return std::tie(name, timing_func, iteration_count, fill_mode, duration,\n                  delay, direction, play_state) ==\n         std::tie(rhs.name, rhs.timing_func, rhs.iteration_count, rhs.fill_mode,\n                  rhs.duration, rhs.delay, rhs.direction, rhs.play_state);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/animation_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ANIMATION_DATA_H_\n#define CLAY_GFX_ANIMATION_ANIMATION_DATA_H_\n\n#include <string>\n\n#include \"clay/gfx/animation/timing_function_data.h\"\n#include \"clay/public/clay.h\"\n\nnamespace clay {\n\nstruct AnimationParams {\n  ClayEventType event_type;\n  const char* animation_name;\n};\n\nstruct AnimationData {\n  AnimationData() = default;\n  ~AnimationData() = default;\n\n  std::string name;\n  int duration = 0;\n  int delay = 0;\n  TimingFunctionData timing_func;\n  int iteration_count = 0;\n  ClayAnimationFillModeType fill_mode = ClayAnimationFillModeType::kNone;\n  ClayAnimationDirectionType direction = ClayAnimationDirectionType::kNormal;\n  ClayAnimationPlayStateType play_state = ClayAnimationPlayStateType::kRunning;\n\n  bool operator==(const AnimationData& rhs) const;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ANIMATION_DATA_H_\n"
  },
  {
    "path": "clay/gfx/animation/animation_event_handler.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ANIMATION_EVENT_HANDLER_H_\n#define CLAY_GFX_ANIMATION_ANIMATION_EVENT_HANDLER_H_\n\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\nclass AnimationEventHandler {\n public:\n  AnimationEventHandler() = default;\n  virtual ~AnimationEventHandler() = default;\n\n  virtual void OnAnimationEvent(const AnimationParams& animation_params) = 0;\n  virtual void OnTransitionEvent(const AnimationParams& animation_params,\n                                 ClayAnimationPropertyType property_type) = 0;\n};\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ANIMATION_EVENT_HANDLER_H_\n"
  },
  {
    "path": "clay/gfx/animation/animation_handler.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/animation_handler.h\"\n\n#include <algorithm>\n\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace clay {\n\n/**\n * Register to get a callback on the next frame after the delay.\n */\nvoid AnimationHandler::AddAnimationFrameCallback(\n    AnimationFrameCallback* callback, int64_t delay) {\n  if (callback_delay_time_map_.find(callback) ==\n      callback_delay_time_map_.end()) {\n    if (delay > 0) {\n      fml::TimePoint delayed_start_time =\n          fml::TimePoint::Now() + fml::TimeDelta::FromMilliseconds(delay);\n      delay = delayed_start_time.ToEpochDelta().ToMilliseconds();\n    }\n    callback_delay_time_map_[callback] = delay;\n    animation_callbacks_.push_front(callback);\n    if (on_new_animation_callback_) {\n      on_new_animation_callback_();\n    }\n  }\n}\n\n/**\n * Removes the given callback from the list, so it will no longer be called for\n * frame related timing.\n */\nvoid AnimationHandler::RemoveCallback(AnimationFrameCallback* callback) {\n  // To avoid invalidating iterators, set the item to nullptr instead of\n  // remove it.\n  auto ret = std::find(animation_callbacks_.begin(), animation_callbacks_.end(),\n                       callback);\n  if (ret != animation_callbacks_.end()) {\n    *ret = nullptr;\n    callback_list_dirty_ = true;\n  }\n\n  auto it = callback_delay_time_map_.find(callback);\n  if (it != callback_delay_time_map_.end()) {\n    callback_delay_time_map_.erase(it);\n  }\n}\n\nvoid AnimationHandler::DoAnimationFrame(int64_t frame_time) {\n  int64_t current_time = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  last_frame_time_ = frame_time;\n  for (auto& callback : animation_callbacks_) {\n    if (callback && IsCallbackDue(callback, current_time)) {\n      callback->DoAnimationFrame(frame_time);\n    }\n  }\n  // clean up removed callbacks\n  if (callback_list_dirty_) {\n    callback_list_dirty_ = false;\n    animation_callbacks_.remove(nullptr);\n  }\n}\n\n/**\n * Remove the callbacks from callback_delay_time_map_ once they have passed the\n * initial delay so that they can start getting frame callbacks.\n *\n * @return true if they have passed the initial delay or have no delay, false\n * otherwise.\n */\nbool AnimationHandler::IsCallbackDue(AnimationFrameCallback* callback,\n                                     int64_t current_time) {\n  auto it = callback_delay_time_map_.find(callback);\n  if (it == callback_delay_time_map_.end()) {\n    return false;\n  }\n  int64_t start_time = it->second;\n  if (start_time <= 0) {\n    return true;\n  }\n  return start_time < current_time;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/animation_handler.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ANIMATION_HANDLER_H_\n#define CLAY_GFX_ANIMATION_ANIMATION_HANDLER_H_\n\n#include <forward_list>\n#include <functional>\n#include <unordered_map>\n#include <utility>\n\nnamespace clay {\n\nclass AnimationHandler {\n public:\n  /**\n   * Callbacks that receives notifications for animation timing.\n   */\n  class AnimationFrameCallback {\n   public:\n    /**\n     * Run animation based on the frame time.\n     * @param frame_time The frame start time\n     * @return if the animation has finished.\n     */\n    virtual bool DoAnimationFrame(int64_t frame_time) = 0;\n\n    virtual ~AnimationFrameCallback() = default;\n  };\n\n  /**\n   * Return the number of callbacks that have registered for frame callbacks.\n   */\n  int GetAnimationCount() { return GetCallbackSize(); }\n\n  /**\n   * Register to get a callback on the next frame after the delay.\n   */\n  void AddAnimationFrameCallback(AnimationFrameCallback* callback,\n                                 int64_t delay);\n\n  /**\n   * Removes the given callback from the list, so it will no longer be called\n   * for frame related timing.\n   */\n  void RemoveCallback(AnimationFrameCallback* callback);\n\n  void DoAnimationFrame(int64_t frame_time);\n\n  // Return time in milliseconds\n  int64_t GetCurrentAnimationTime() { return last_frame_time_; }\n\n  void SetOnNewAnimationCallback(\n      std::function<void()> on_new_animation_callback) {\n    on_new_animation_callback_ = std::move(on_new_animation_callback);\n  }\n\n  void ClearCallbacks() {\n    callback_delay_time_map_.clear();\n    animation_callbacks_.clear();\n    on_new_animation_callback_ = nullptr;\n  }\n\n private:\n  bool IsCallbackDue(AnimationFrameCallback* callback, int64_t current_time);\n  int GetCallbackSize() { return callback_delay_time_map_.size(); }\n\n  std::unordered_map<AnimationFrameCallback*, int64_t> callback_delay_time_map_;\n  std::forward_list<AnimationFrameCallback*> animation_callbacks_;\n  std::function<void()> on_new_animation_callback_;\n  bool callback_list_dirty_ = false;\n  int64_t last_frame_time_ = -1;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ANIMATION_HANDLER_H_\n"
  },
  {
    "path": "clay/gfx/animation/animation_handler_unittests.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/gfx/animation/animation_handler.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nnamespace {\nclass MockAnimationFrameCallback\n    : public AnimationHandler::AnimationFrameCallback {\n public:\n  MOCK_METHOD(bool, DoAnimationFrame, (int64_t frame_time), (override));\n};\n}  // namespace\n\nusing ::testing::InSequence;\n\nTEST(AnimationHandlerTest, NoActiveAnimation) {\n  std::unique_ptr<AnimationHandler> handler =\n      std::make_unique<AnimationHandler>();\n  int64_t frame_time = 0;\n  for (size_t i = 0; i < 10; i++) {\n    frame_time += 16;\n    handler->DoAnimationFrame(frame_time);\n    EXPECT_EQ(handler->GetCurrentAnimationTime(), frame_time);\n  }\n\n  EXPECT_EQ(handler->GetAnimationCount(), 0);\n}\n\nTEST(AnimationHandlerTest, AnimationFrameCallback) {\n  MockAnimationFrameCallback anim1;\n  MockAnimationFrameCallback anim2;\n\n  int64_t frame_time = 0;\n  {\n    InSequence s;\n\n    for (int i = 1; i <= 10; i++) {\n      frame_time += 16;\n      EXPECT_CALL(anim1, DoAnimationFrame(frame_time))\n          .Times(1)\n          .RetiresOnSaturation();\n    }\n  }\n\n  frame_time = 0;\n  {\n    InSequence s;\n\n    for (int i = 1; i <= 10; i++) {\n      frame_time += 16;\n      if (i >= 6) {\n        EXPECT_CALL(anim2, DoAnimationFrame(frame_time))\n            .Times(1)\n            .RetiresOnSaturation();\n      }\n    }\n  }\n\n  std::unique_ptr<AnimationHandler> handler =\n      std::make_unique<AnimationHandler>();\n  EXPECT_EQ(handler->GetAnimationCount(), 0);\n  handler->AddAnimationFrameCallback(&anim1, 0);\n  EXPECT_EQ(handler->GetAnimationCount(), 1);\n\n  frame_time = 0;\n  for (size_t i = 1; i <= 10; i++) {\n    if (i == 6) {\n      handler->AddAnimationFrameCallback(&anim2, 0);\n      EXPECT_EQ(handler->GetAnimationCount(), 2);\n    }\n    frame_time += 16;\n    handler->DoAnimationFrame(frame_time);\n  }\n  EXPECT_EQ(handler->GetAnimationCount(), 2);\n  handler->RemoveCallback(&anim1);\n  handler->RemoveCallback(&anim2);\n  EXPECT_EQ(handler->GetAnimationCount(), 0);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/animation_properties_util.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ANIMATION_PROPERTIES_UTIL_H_\n#define CLAY_GFX_ANIMATION_ANIMATION_PROPERTIES_UTIL_H_\n\n#include <type_traits>\n\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\n\nusing AnimationPropertyUnderlyingType =\n    std::underlying_type<ClayAnimationPropertyType>::type;\n\nconstexpr inline AnimationPropertyUnderlyingType ToUnderlying(\n    ClayAnimationPropertyType value) {\n  return static_cast<AnimationPropertyUnderlyingType>(value);\n}\n\nconstexpr inline AnimationPropertyUnderlyingType MakeValid(\n    AnimationPropertyUnderlyingType value) {\n  return value & ToUnderlying(ClayAnimationPropertyType::kAll);\n}\n\nconstexpr inline ClayAnimationPropertyType ToAnimationProperty(\n    AnimationPropertyUnderlyingType value) {\n  return static_cast<ClayAnimationPropertyType>(MakeValid(value));\n}\n\nconstexpr inline bool AnimationPropertyTest(ClayAnimationPropertyType value,\n                                            ClayAnimationPropertyType to_test) {\n  return ToUnderlying(value) & ToUnderlying(to_test);\n}\n\nconstexpr inline void AnimationPropertySet(ClayAnimationPropertyType& value,\n                                           ClayAnimationPropertyType to_set) {\n  value = ToAnimationProperty(ToUnderlying(value) | ToUnderlying(to_set));\n}\n\nconstexpr inline void AnimationPropertyUnset(\n    ClayAnimationPropertyType& value, ClayAnimationPropertyType to_unset) {\n  value = ToAnimationProperty(ToUnderlying(value) & ~ToUnderlying(to_unset));\n}\n\nconstexpr inline void AnimationPropertySetIf(ClayAnimationPropertyType& value,\n                                             ClayAnimationPropertyType prop,\n                                             bool set) {\n  (set ? AnimationPropertySet : AnimationPropertyUnset)(value, prop);\n}\n\nconstexpr inline bool IsRasterAnimationProperty(\n    ClayAnimationPropertyType type) {\n  return type == ClayAnimationPropertyType::kOpacity ||\n         type == ClayAnimationPropertyType::kTransform ||\n         type == ClayAnimationPropertyType::kBackgroundColor ||\n         type == ClayAnimationPropertyType::kColor;\n}\n\ntemplate <typename Func, typename = std::enable_if_t<std::is_invocable_v<\n                             Func, ClayAnimationPropertyType>>>\nconstexpr inline void ForEachRasterAnimationProperty(Func&& func) {\n  func(ClayAnimationPropertyType::kOpacity);\n  func(ClayAnimationPropertyType::kTransform);\n  func(ClayAnimationPropertyType::kBackgroundColor);\n  func(ClayAnimationPropertyType::kColor);\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ANIMATION_PROPERTIES_UTIL_H_\n"
  },
  {
    "path": "clay/gfx/animation/animator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/animator.h\"\n\nnamespace clay {\n\nvoid Animator::Pause() {\n  if (IsStarted() && !paused_) {\n    paused_ = true;\n    std::forward_list<AnimatorPauseListener*> listeners = pause_listeners_;\n    for (auto listener : listeners) {\n      listener->OnAnimationPause(*this);\n    }\n  }\n}\n\nvoid Animator::Resume() {\n  if (paused_) {\n    paused_ = false;\n    std::forward_list<AnimatorPauseListener*> listeners = pause_listeners_;\n    for (auto listener : listeners) {\n      listener->OnAnimationResume(*this);\n    }\n  }\n}\n\nint64_t Animator::GetTotalDuration() {\n  int64_t duration = GetDuration();\n  if (duration == kDurationInfinite) {\n    return kDurationInfinite;\n  } else {\n    return GetStartDelay() + duration;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/animator.h",
    "content": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Copyright 2021 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#ifndef CLAY_GFX_ANIMATION_ANIMATOR_H_\n#define CLAY_GFX_ANIMATION_ANIMATOR_H_\n\n#include <forward_list>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/gfx/animation/animator_listener.h\"\n\nnamespace clay {\n\nclass Interpolator;\n\n/**\n * This is the superclass for classes which provide basic support for\n * animations which can be started, ended, and have AnimatorListeners\n * added to them.\n */\nclass Animator {\n public:\n  /**\n   * The value used to indicate infinite duration (e.g. when Animators repeat\n   * infinitely).\n   */\n  static constexpr int kDurationInfinite = -1;\n\n  virtual ~Animator() = default;\n\n  /**\n   * Starts this animation. If the animation has a nonzero startDelay, the\n   * animation will start running after that delay elapses. A non-delayed\n   * animation will have its initial value(s) set immediately, followed by\n   * calls to AnimatorListener#OnAnimationStart(Animator) for any listeners\n   * of this animator.\n   */\n  virtual void Start() = 0;\n\n  /**\n   * Cancels the animation. Unlike End(), Cancel() causes the animation to\n   * stop in its tracks, sending an\n   * AnimatorListener#OnAnimationCancel(Animator) to its listeners, followed\n   * by an AnimatorListener#OnAnimationEnd(Animator) message.\n   */\n  virtual void Cancel() = 0;\n\n  /**\n   * Ends the animation. This causes the animation to assign the end value of\n   * the property being animated, then calling the\n   * AnimatorListener#OnAnimationEnd(Animator) method on its listeners.\n   */\n  virtual void End() = 0;\n\n  /**\n   * Pauses a running animation. This method should only be called on the\n   * same thread on which the animation was started. If the animation has\n   * not yet been IsStarted() or has since ended, then the call is ignored.\n   * Paused animations can be resumed by calling Resume().\n   */\n  virtual void Pause();\n\n  /**\n   * Resumes a paused animation, causing the animator to pick up where it\n   * left off when it was paused. Calls to Resume() on an animator that is\n   * not currently paused will be ignored.\n   */\n  virtual void Resume();\n\n  /**\n   * The amount of time, in milliseconds, to delay processing the animation\n   * after Start() is called.\n   *\n   * @return the number of milliseconds to delay running the animation\n   */\n  virtual int64_t GetStartDelay() = 0;\n\n  /**\n   * The amount of time, in milliseconds, to delay processing the animation\n   * after Start() is called.\n\n   * @param startDelay The amount of the delay, in milliseconds\n   */\n  virtual void SetStartDelay(int64_t start_delay) = 0;\n\n  /**\n   * Sets the duration of the animation.\n   *\n   * @param duration The length of the animation, in milliseconds.\n   */\n  virtual void SetDuration(int64_t duration) = 0;\n\n  /**\n   * Gets the duration of the animation.\n   *\n   * @return The length of the animation, in milliseconds.\n   */\n  virtual int64_t GetDuration() = 0;\n\n  /**\n   * Gets the total duration of the animation, accounting for animation\n   * sequences, start delay, and repeating.\n   *\n   * @return  Total time an animation takes to finish, starting from the time\n   *          Start() is called. kDurationInfinite will be returned if the\n   *          animation or any child animation repeats infinite times.\n   */\n  virtual int64_t GetTotalDuration();\n\n  /**\n   * The time interpolator used in calculating the elapsed fraction of the\n   * animation. The interpolator determines whether the animation runs with\n   * linear or non-linear motion, such as acceleration and deceleration.\n   *\n   * @param value the interpolator to be used by this animation\n   */\n  virtual void SetInterpolator(std::unique_ptr<Interpolator> value) = 0;\n\n  virtual Interpolator* GetInterpolator() { return nullptr; }\n\n  /**\n   * Returns whether this Animator is currently running (having been started\n   * and gone past any initial startDelay period and not yet ended).\n   */\n  virtual bool IsRunning() = 0;\n\n  /**\n   * Whether the Animator has been started and not yet ended.\n   */\n  virtual bool IsStarted() {\n    // Default method returns value for IsRunning(). Subclasses should\n    // override to return a real value.\n    return IsRunning();\n  }\n\n  virtual bool IsInitialized() { return true; }\n\n  bool IsPaused() { return paused_; }\n\n  void AddListener(AnimatorListener* listener) {\n    listeners_.push_front(listener);\n  }\n\n  void RemoveListener(AnimatorListener* listener) {\n    listeners_.remove(listener);\n  }\n\n  std::forward_list<AnimatorListener*> GetListeners() { return listeners_; }\n\n  void AddPauseListener(AnimatorPauseListener* listener) {\n    pause_listeners_.push_front(listener);\n  }\n\n  void RemovePauseListener(AnimatorPauseListener* listener) {\n    pause_listeners_.remove(listener);\n  }\n\n  void RemoveAllListeners() {\n    listeners_.clear();\n    pause_listeners_.clear();\n  }\n\n  void SetAnimationName(std::string animation_name) {\n    animation_name_ = std::move(animation_name);\n  }\n\n  const std::string& GetAnimationName() const { return animation_name_; }\n\n protected:\n  virtual bool CanReverse() { return false; }\n\n  virtual void Reverse() {}\n\n  // Pulse an animation frame into the animation.\n  virtual bool PulseAnimationFrame(int64_t frame_time) { return false; }\n\n  /**\n   * Internal use only.\n   * This call starts the animation in regular or reverse direction without\n   * requiring them to register frame callbacks. The caller will be\n   * responsible for all the subsequent animation pulses. Specifically,\n   * the caller needs to call doAnimationFrame(...) for the animation on\n   * every frame.\n   *\n   * @param in_reverse whether the animation should play in reverse direction\n   */\n  virtual void StartWithoutPulsing(bool in_reverse) {\n    if (in_reverse) {\n      Reverse();\n    } else {\n      Start();\n    }\n  }\n\n  /**\n   * Internal use only.\n   * Skips the animation value to end/start, depending on whether the play\n   * direction is forward or backward.\n   *\n   * @param in_reverse whether the end value is based on a reverse direction.\n   *                   If yes, this is equivalent to skip to start value in\n   *                   a forward playing direction.\n   */\n  virtual void SkipToEndValue(bool in_reverse) {}\n\n  /**\n   * Internal use only.\n   */\n  virtual void AnimateBasedOnPlayTime(int64_t current_play_time,\n                                      int64_t last_play_time, bool in_reverse) {\n  }\n\n  /**\n   * The set of listeners to be sent events through the life of an animation.\n   */\n  std::forward_list<AnimatorListener*> listeners_;\n\n  /**\n   * The set of listeners to be sent pause/resume events through the life\n   * of an animation.\n   */\n  std::forward_list<AnimatorPauseListener*> pause_listeners_;\n\n  /**\n   * Whether this animator is currently in a paused state.\n   */\n  bool paused_ = false;\n\n  // bind with a specific animation_name\n  std::string animation_name_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ANIMATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/animator_listener.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ANIMATOR_LISTENER_H_\n#define CLAY_GFX_ANIMATION_ANIMATOR_LISTENER_H_\n\nnamespace clay {\n\nclass Animator;\nclass ValueAnimator;\n\n/**\n * An animation listener receives notifications from an animation.\n * Notifications indicate animation related events, such as the end or the\n * repetition of the animation.\n */\nclass AnimatorListener {\n public:\n  virtual void OnAnimationStart(Animator& animation) = 0;\n  virtual void OnAnimationEnd(Animator& animation) = 0;\n  virtual void OnAnimationCancel(Animator& animation) = 0;\n  virtual void OnAnimationRepeat(Animator& animation) = 0;\n  // the OnAnimationRemove tells the callback removed from handler. so, the\n  // value needs to reset in this method. On the other hand, OnAnimationEnd is\n  // used to fire event to frontend.\n  virtual void OnAnimationRemove(Animator& animation) {}\n\n  virtual void OnAnimationStart(Animator& animation, bool isReverse) {\n    OnAnimationStart(animation);\n  }\n\n  virtual ~AnimatorListener() = default;\n};\n\n/**\n * A pause listener receives notifications from an animation when the\n * animation is paused or resumed.\n *\n */\nclass AnimatorPauseListener {\n public:\n  virtual void OnAnimationPause(Animator& animation) = 0;\n  virtual void OnAnimationResume(Animator& animation) = 0;\n\n  virtual ~AnimatorPauseListener() = default;\n};\n\n/**\n * Implementors of this interface can add themselves as update listeners\n * to an ValueAnimator instance to receive callbacks on every animation\n * frame, after the current frame's values have been calculated for that\n * ValueAnimator.\n */\nclass AnimatorUpdateListener {\n public:\n  virtual void OnAnimationUpdate(ValueAnimator& animation) = 0;\n\n  virtual ~AnimatorUpdateListener() = default;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ANIMATOR_LISTENER_H_\n"
  },
  {
    "path": "clay/gfx/animation/animator_listener_adapter.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ANIMATOR_LISTENER_ADAPTER_H_\n#define CLAY_GFX_ANIMATION_ANIMATOR_LISTENER_ADAPTER_H_\n\n#include \"clay/gfx/animation/animator_listener.h\"\n\nnamespace clay {\n\n/**\n * This adapter class provides empty implementations of the methods from\n * AnimatorListener and AnimatorPauseListener.\n */\nclass AnimatorListenerAdapter : public AnimatorListener,\n                                public AnimatorPauseListener,\n                                public AnimatorUpdateListener {\n public:\n  void OnAnimationStart(Animator& animation) override {}\n  void OnAnimationEnd(Animator& animation) override {}\n  void OnAnimationCancel(Animator& animation) override {}\n  void OnAnimationRepeat(Animator& animation) override {}\n\n  void OnAnimationPause(Animator& animation) override {}\n  void OnAnimationResume(Animator& animation) override {}\n\n  void OnAnimationUpdate(ValueAnimator& animation) override {}\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ANIMATOR_LISTENER_ADAPTER_H_\n"
  },
  {
    "path": "clay/gfx/animation/animator_target.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ANIMATOR_TARGET_H_\n#define CLAY_GFX_ANIMATION_ANIMATOR_TARGET_H_\n\n#include <string>\n\n#include \"clay/gfx/animation/keyframe_set.h\"\n#include \"clay/gfx/geometry/box_shadow_operations.h\"\n#include \"clay/gfx/geometry/filter_operations.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\n\nclass AnimationHandler;\n\nclass AnimatorTarget {\n public:\n  virtual ~AnimatorTarget() = default;\n\n  virtual void GetProperty(ClayAnimationPropertyType type, float& value) {}\n  virtual void GetProperty(ClayAnimationPropertyType type, Color& value) {}\n  virtual void GetProperty(ClayAnimationPropertyType type,\n                           TransformOperations& value) {}\n  virtual void GetProperty(ClayAnimationPropertyType type,\n                           FilterOperations& value) {}\n  virtual void GetProperty(ClayAnimationPropertyType type,\n                           BoxShadowOperations& value) {}\n  // SetProperty will modify value without triggering transition animation\n  virtual void SetProperty(ClayAnimationPropertyType type, float value,\n                           bool skip_update_for_raster_animation) {}\n  virtual void SetProperty(ClayAnimationPropertyType type, const Color& value,\n                           bool skip_update_for_raster_animation) {}\n  virtual void SetProperty(ClayAnimationPropertyType type,\n                           const TransformOperations& value,\n                           bool skip_update_for_raster_animation) {}\n\n  virtual void SetProperty(ClayAnimationPropertyType type,\n                           const FilterOperations& value) {}\n  virtual void SetProperty(ClayAnimationPropertyType type,\n                           const BoxShadowOperations& value) {}\n\n  virtual AnimationHandler* GetAnimationHandler() { return nullptr; }\n  virtual const KeyframesMap* GetKeyframesMap(\n      const std::string& animation_name) {\n    return nullptr;\n  }\n  virtual FloatSize PercentageResolutionSize() = 0;\n};\n\n}  // namespace clay\n#endif  // CLAY_GFX_ANIMATION_ANIMATOR_TARGET_H_\n"
  },
  {
    "path": "clay/gfx/animation/anticipate_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/anticipate_interpolator.h\"\n\nnamespace clay {\n\nfloat AnticipateInterpolator::Interpolate(float t) {\n  return t * t * ((tension_ + 1) * t - tension_);\n}\n\nstd::unique_ptr<AnticipateInterpolator> AnticipateInterpolator::Create(\n    float tension) {\n  return std::make_unique<AnticipateInterpolator>(tension);\n}\n\nstd::unique_ptr<Interpolator> AnticipateInterpolator::Clone() {\n  return AnticipateInterpolator::Create(tension_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/anticipate_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ANTICIPATE_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_ANTICIPATE_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass AnticipateInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<AnticipateInterpolator> Create(float tension);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  explicit AnticipateInterpolator(float tension) : tension_(tension) {}\n  float Interpolate(float input) override;\n\n private:\n  const float tension_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ANTICIPATE_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/anticipate_overshoot_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/anticipate_overshoot_interpolator.h\"\n\nnamespace clay {\n\nnamespace {\nfloat a(float t, float s) { return t * t * ((s + 1) * t - s); }\n\nfloat o(float t, float s) { return t * t * ((s + 1) * t + s); }\n}  // namespace\n\nfloat AnticipateOvershootInterpolator::Interpolate(float t) {\n  if (t < 0.5f) {\n    return 0.5f * a(t * 2.0f, tension_);\n  } else {\n    return 0.5f * (o(t * 2.0f - 2.0f, tension_) + 2.0f);\n  }\n}\n\nstd::unique_ptr<AnticipateOvershootInterpolator>\nAnticipateOvershootInterpolator::Create(float tension) {\n  return std::make_unique<AnticipateOvershootInterpolator>(tension);\n}\n\nstd::unique_ptr<Interpolator> AnticipateOvershootInterpolator::Clone() {\n  return AnticipateOvershootInterpolator::Create(tension_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/anticipate_overshoot_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_ANTICIPATE_OVERSHOOT_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_ANTICIPATE_OVERSHOOT_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass AnticipateOvershootInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<AnticipateOvershootInterpolator> Create(float tension);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  explicit AnticipateOvershootInterpolator(float tension) : tension_(tension) {}\n  float Interpolate(float input) override;\n\n private:\n  const float tension_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_ANTICIPATE_OVERSHOOT_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/bounce_animator.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/bounce_animator.h\"\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nvoid BounceAnimator::Init() {\n  start_time_ = fml::TimePoint::Now().ToEpochDelta().ToMillisecondsF();\n}\n\nbool BounceAnimator::UpdateValueAndVelocity(int64_t delta_time) {\n  // // FIXME: sometimes `delta_time` is negative, need to fix it.\n  // if (delta_time <= 0) {\n  //   return false;\n  // }\n\n  FML_DCHECK(last_frame_time_ - start_time_ >= 0);\n  float t = (last_frame_time_ - start_time_) / 1000.f;\n  float c1 = start_value_ - final_value_;\n  float c2 = velocity_ + 10 * c1;\n  value_ = final_value_ + exp(-10 * t) * (c1 + c2 * t);\n\n  if (IsAtEquilibrium(value_, velocity_)) {\n    value_ = final_value_;\n    return true;\n  }\n  return false;\n}\n\nbool BounceAnimator::IsAtEquilibrium(float value, float velocity) {\n  // A bounce animation may have the same start and end value. In this case,\n  // checking the distance to the final value is not enough, which may cause the\n  // animation to be finished at the beginning. So we also check the elapsed\n  // time.\n  return std::abs(value - final_value_) <= 1 &&\n         (last_frame_time_ - start_time_) >= 100;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/bounce_animator.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_BOUNCE_ANIMATOR_H_\n#define CLAY_GFX_ANIMATION_BOUNCE_ANIMATOR_H_\n\n#include \"clay/gfx/animation/dynamic_animator.h\"\n\nnamespace clay {\n\n// ref:\n// https://github.com/super-ultra/ScrollMechanics/blob/master/ScrollMechanics/Sources/SpringTimingParameters.swift\nclass BounceAnimator : public DynamicAnimator {\n public:\n  void SetTargetValue(float value) { final_value_ = value; }\n  void Init();\n\n protected:\n  bool UpdateValueAndVelocity(int64_t delta_time) override;\n\n  float GetAcceleration(float value, float velocity) override { return 0; }\n\n  bool IsAtEquilibrium(float value, float velocity) override;\n\n  void SetValueThreshold(float threshold) override {}\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_BOUNCE_ANIMATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/bounce_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/bounce_interpolator.h\"\n\nnamespace clay {\n\nnamespace {\nfloat bounce(float t) { return t * t * 8.0f; }\n}  // namespace\n\nfloat BounceInterpolator::Interpolate(float t) {\n  t *= 1.1226f;\n  if (t < 0.3535f) {\n    return bounce(t);\n  } else if (t < 0.7408f) {\n    return bounce(t - 0.54719f) + 0.7f;\n  } else if (t < 0.9644f) {\n    return bounce(t - 0.8526f) + 0.9f;\n  } else {\n    return bounce(t - 1.0435f) + 0.95f;\n  }\n}\n\nstd::unique_ptr<BounceInterpolator> BounceInterpolator::Create() {\n  return std::make_unique<BounceInterpolator>();\n}\n\nstd::unique_ptr<Interpolator> BounceInterpolator::Clone() {\n  return BounceInterpolator::Create();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/bounce_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_BOUNCE_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_BOUNCE_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass BounceInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<BounceInterpolator> Create();\n  std::unique_ptr<Interpolator> Clone() override;\n\n  float Interpolate(float input) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_BOUNCE_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/build.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\ngfx_animation_sources = [\n  \"accelerate_decelerate_interpolator.cc\",\n  \"accelerate_decelerate_interpolator.h\",\n  \"accelerate_interpolator.cc\",\n  \"accelerate_interpolator.h\",\n  \"animation_data.cc\",\n  \"animation_data.h\",\n  \"animation_event_handler.h\",\n  \"animation_handler.cc\",\n  \"animation_handler.h\",\n  \"animation_properties_util.h\",\n  \"animator.cc\",\n  \"animator.h\",\n  \"animator_listener.h\",\n  \"animator_listener_adapter.h\",\n  \"animator_target.h\",\n  \"anticipate_interpolator.cc\",\n  \"anticipate_interpolator.h\",\n  \"anticipate_overshoot_interpolator.cc\",\n  \"anticipate_overshoot_interpolator.h\",\n  \"bounce_animator.cc\",\n  \"bounce_animator.h\",\n  \"bounce_interpolator.cc\",\n  \"bounce_interpolator.h\",\n  \"cubic_bezier_interpolator.cc\",\n  \"cubic_bezier_interpolator.h\",\n  \"cycle_interpolator.cc\",\n  \"cycle_interpolator.h\",\n  \"decelerate_interpolator.cc\",\n  \"decelerate_interpolator.h\",\n  \"dynamic_animator.cc\",\n  \"dynamic_animator.h\",\n  \"fling_animator.cc\",\n  \"fling_animator.h\",\n  \"interpolator.cc\",\n  \"interpolator.h\",\n  \"keyframe.cc\",\n  \"keyframe.h\",\n  \"keyframe_set.cc\",\n  \"keyframe_set.h\",\n  \"keyframes_manager.cc\",\n  \"keyframes_manager.h\",\n  \"lut_interpolator.cc\",\n  \"lut_interpolator.h\",\n  \"overshoot_interpolator.cc\",\n  \"overshoot_interpolator.h\",\n  \"path_interpolator.cc\",\n  \"path_interpolator.h\",\n  \"picture_animation_type.h\",\n  \"steps_interpolator.cc\",\n  \"steps_interpolator.h\",\n  \"timing_function_data.cc\",\n  \"timing_function_data.h\",\n  \"transition_data.cc\",\n  \"transition_data.h\",\n  \"transition_manager.cc\",\n  \"transition_manager.h\",\n  \"type_evaluator.h\",\n  \"value_animator.cc\",\n  \"value_animator.h\",\n  \"viscous_fluid_interpolator.cc\",\n  \"viscous_fluid_interpolator.h\",\n]\n"
  },
  {
    "path": "clay/gfx/animation/cubic_bezier_interpolator.cc",
    "content": "// Copyright 2014 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/cubic_bezier_interpolator.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <limits>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n\nnamespace clay {\n\nstatic const double kBezierEpsilon = 1e-7;\n\nconst int kMaxNewtonIterations = 4;\n\nvoid CubicBezierInterpolator::InitSpline() {\n  double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1);\n  for (int i = 0; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) {\n    spline_samples_[i] = SampleCurveX(i * delta_t);\n  }\n}\n\ndouble CubicBezierInterpolator::ToFinite(double value) const {\n  if (std::isinf(value)) {\n    if (value > 0) return std::numeric_limits<double>::max();\n    return std::numeric_limits<double>::lowest();\n  }\n  return value;\n}\n\nvoid CubicBezierInterpolator::InitCoefficients(\n    double p1x, double p1y, double p2x,\n    double p2y) {  // Calculate the polynomial coefficients, implicit first and\n                   // last control\n  // points are (0,0) and (1,1).\n  cx_ = 3.0 * p1x;\n  bx_ = 3.0 * (p2x - p1x) - cx_;\n  ax_ = 1.0 - cx_ - bx_;\n\n  cy_ = ToFinite(3.0 * p1y);\n  by_ = ToFinite(3.0 * (p2y - p1y) - cy_);\n  ay_ = ToFinite(1.0 - cy_ - by_);\n}\n\nvoid CubicBezierInterpolator::InitGradients(double p1x, double p1y, double p2x,\n                                            double p2y) {\n  // End-point gradients are used to calculate timing function results\n  // outside the range [0, 1].\n  //\n  // There are four possibilities for the gradient at each end:\n  // (1) the closest control point is not horizontally coincident with regard to\n  //     (0, 0) or (1, 1). In this case the line between the end point and\n  //     the control point is tangent to the bezier at the end point.\n  // (2) the closest control point is coincident with the end point. In\n  //     this case the line between the end point and the far control\n  //     point is tangent to the bezier at the end point.\n  // (3) both internal control points are coincident with an endpoint. There\n  //     are two special case that fall into this category:\n  //     CubicBezier(0, 0, 0, 0) and CubicBezier(1, 1, 1, 1). Both are\n  //     equivalent to linear.\n  // (4) the closest control point is horizontally coincident with the end\n  //     point, but vertically distinct. In this case the gradient at the\n  //     end point is Infinite. However, this causes issues when\n  //     interpolating. As a result, we break down to a simple case of\n  //     0 gradient under these conditions.\n\n  if (p1x > 0)\n    start_gradient_ = p1y / p1x;\n  else if (!p1y && p2x > 0)\n    start_gradient_ = p2y / p2x;\n  else if (!p1y && !p2y)\n    start_gradient_ = 1;\n  else\n    start_gradient_ = 0;\n\n  if (p2x < 1)\n    end_gradient_ = (p2y - 1) / (p2x - 1);\n  else if (p2y == 1 && p1x < 1)\n    end_gradient_ = (p1y - 1) / (p1x - 1);\n  else if (p2y == 1 && p1y == 1)\n    end_gradient_ = 1;\n  else\n    end_gradient_ = 0;\n}\n\nCubicBezierInterpolator::CubicBezierInterpolator(float a, float b, float c,\n                                                 float d)\n    : a_(a), b_(b), c_(c), d_(d) {\n  InitCoefficients(a, b, c, d);\n  InitGradients(a, b, c, d);\n  InitSpline();\n}\n\nstd::unique_ptr<CubicBezierInterpolator> CubicBezierInterpolator::CreatePreset(\n    Type type) {\n  switch (type) {\n    case Ease:\n      return CubicBezierInterpolator::Create(0.25f, 0.1f, 0.25f, 1.0f);\n    case EaseIn:\n      return CubicBezierInterpolator::Create(0.42f, 0.0f, 1.0f, 1.0f);\n    case EaseOut:\n      return CubicBezierInterpolator::Create(0.0f, 0.0f, 0.58f, 1.0f);\n    case EaseInOut:\n      return CubicBezierInterpolator::Create(0.42f, 0.0f, 0.58f, 1.0f);\n    default:\n      FML_UNREACHABLE();\n  }\n  return nullptr;\n}\n\nstd::unique_ptr<CubicBezierInterpolator> CubicBezierInterpolator::Create(\n    float a, float b, float c, float d) {\n  return std::make_unique<CubicBezierInterpolator>(a, b, c, d);\n}\n\nstd::unique_ptr<CubicBezierInterpolator> CubicBezierInterpolator::Create(\n    float x, float y) {\n  // A cubic Bézier can be made identical to a quadratic one by\n  // copying the end points, and placing its 2 middle control 2/3 along line\n  // segments from the end points to the quadratic curve's middle control point.\n  constexpr float kScaleFactor = 2.0f / 3.0f;\n  FloatPoint p1(x, y), p2(1.0f, 1.0f);\n  FloatPoint q1 = p1;\n  q1.Scale(kScaleFactor, kScaleFactor);\n  FloatPoint v2 = p1 - p2;\n  v2.Scale(kScaleFactor, kScaleFactor);\n  FloatPoint q2 = p2 + v2;\n  return std::make_unique<CubicBezierInterpolator>(q1.x(), q1.y(), q2.x(),\n                                                   q2.y());\n}\n\nstd::unique_ptr<Interpolator> CubicBezierInterpolator::Clone() {\n  return CubicBezierInterpolator::Create(a_, b_, c_, d_);\n}\n\nfloat CubicBezierInterpolator::Interpolate(float t) {\n  if (t < 0.0) return ToFinite(0.0 + start_gradient_ * t);\n  if (t > 1.0) return ToFinite(1.0 + end_gradient_ * (t - 1.0));\n  return SampleCurveY(SolveCurveX(t, kBezierEpsilon));\n}\n\ndouble CubicBezierInterpolator::SolveCurveX(double x, double epsilon) const {\n  double t0;\n  double t1;\n  double t2 = x;\n  double x2;\n  double d2;\n  int i;\n\n  // Linear interpolation of spline curve for initial guess.\n  double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1);\n  for (i = 1; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) {\n    if (x <= spline_samples_[i]) {\n      t1 = delta_t * i;\n      t0 = t1 - delta_t;\n      t2 = t0 + (t1 - t0) * (x - spline_samples_[i - 1]) /\n                    (spline_samples_[i] - spline_samples_[i - 1]);\n      break;\n    }\n  }\n\n  // Perform a few iterations of Newton's method -- normally very fast.\n  // See https://en.wikipedia.org/wiki/Newton%27s_method.\n  double newton_epsilon = std::min(kBezierEpsilon, epsilon);\n  for (i = 0; i < kMaxNewtonIterations; i++) {\n    x2 = SampleCurveX(t2) - x;\n    if (fabs(x2) < newton_epsilon) return t2;\n    d2 = SampleCurveDerivativeX(t2);\n    if (fabs(d2) < kBezierEpsilon) break;\n    t2 = t2 - x2 / d2;\n  }\n  if (fabs(x2) < epsilon) return t2;\n\n  // Fall back to the bisection method for reliability.\n  while (t0 < t1) {\n    x2 = SampleCurveX(t2);\n    if (fabs(x2 - x) < epsilon) return t2;\n    if (x > x2)\n      t0 = t2;\n    else\n      t1 = t2;\n    t2 = (t1 + t0) * .5;\n  }\n\n  // Failure.\n  return t2;\n}\n\ndouble CubicBezierInterpolator::Slop(double x) const {\n  x = std::clamp(x, 0.0, 1.0);\n  double t = SolveCurveX(x, kBezierEpsilon);\n  double dx = SampleCurveDerivativeX(t);\n  double dy = SampleCurveDerivativeY(t);\n  // TODO(crbug.com/40207101): We should clamp NaN to a proper value.\n  // Please see the issue for detail.\n  if (!dx && !dy) return 0;\n  return ToFinite(dy / dx);\n}\n\ndouble CubicBezierInterpolator::Velocity(double time) const {\n  return Slop(time);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/cubic_bezier_interpolator.h",
    "content": "// Copyright 2014 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_CUBIC_BEZIER_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_CUBIC_BEZIER_INTERPOLATOR_H_\n\n#include <algorithm>\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\n#define CUBIC_BEZIER_SPLINE_SAMPLES 11\n\nclass CubicBezierInterpolator : public Interpolator {\n public:\n  enum Type { Ease, EaseIn, EaseOut, EaseInOut };\n\n  static std::unique_ptr<CubicBezierInterpolator> CreatePreset(Type type);\n  static std::unique_ptr<CubicBezierInterpolator> Create(float a, float b,\n                                                         float c, float d);\n  // Create a quadratic Bézier curve using only one control point.\n  static std::unique_ptr<CubicBezierInterpolator> Create(float x, float y);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  CubicBezierInterpolator(float a, float b, float c, float d);\n\n  void InitSpline();\n\n  float Interpolate(float input) override;\n\n  double ToFinite(double value) const;\n\n  double Velocity(double time) const override;\n\n  double SampleCurveX(double t) const {\n    return ((ax_ * t + bx_) * t + cx_) * t;\n  }\n  double SampleCurveY(double t) const {\n    return ToFinite(((ay_ * t + by_) * t + cy_) * t);\n  }\n\n  double SolveCurveX(double x, double epsilon) const;\n\n  double SampleCurveDerivativeX(double t) const {\n    return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_;\n  }\n\n  double SampleCurveDerivativeY(double t) const {\n    return ToFinite(\n        ToFinite(ToFinite(3.0 * ay_) * t + ToFinite(2.0 * by_)) * t + cy_);\n  }\n\n  double Slop(double x) const;\n\n private:\n  void InitCoefficients(double p1x, double p1y, double p2x, double p2y);\n\n  void InitGradients(double p1x, double p1y, double p2x, double p2y);\n\n  /// The x coordinate of the first control point.\n  ///\n  /// The line through the point (0, 0) and the first control point is tangent\n  /// to the curve at the point (0, 0).\n  const float a_;\n\n  /// The y coordinate of the first control point.\n  ///\n  /// The line through the point (0, 0) and the first control point is tangent\n  /// to the curve at the point (0, 0).\n  const float b_;\n\n  /// The x coordinate of the second control point.\n  ///\n  /// The line through the point (1, 1) and the second control point is\n  /// tangent to the curve at the point (1, 1).\n  const float c_;\n\n  /// The y coordinate of the second control point.\n  ///\n  /// The line through the point (1, 1) and the second control point is\n  /// tangent to the curve at the point (1, 1).\n  const float d_;\n\n  double ax_;\n  double bx_;\n  double cx_;\n\n  double ay_;\n  double by_;\n  double cy_;\n\n  double start_gradient_;\n  double end_gradient_;\n\n  double spline_samples_[CUBIC_BEZIER_SPLINE_SAMPLES];\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_CUBIC_BEZIER_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/cubic_bezier_interpolator_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/cubic_bezier_interpolator.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(CubicBezierInterpolatorTest, Basic) {\n  float tolerance = 0.01f;\n  std::unique_ptr<Interpolator> cubic_ease_timing =\n      CubicBezierInterpolator::CreatePreset(CubicBezierInterpolator::Ease);\n  EXPECT_NEAR(0.418f, cubic_ease_timing->Interpolate(0.25), tolerance);\n  EXPECT_NEAR(0.805f, cubic_ease_timing->Interpolate(0.50), tolerance);\n  EXPECT_NEAR(0.960f, cubic_ease_timing->Interpolate(0.75), tolerance);\n\n  std::unique_ptr<Interpolator> cubic_ease_in_timing =\n      CubicBezierInterpolator::CreatePreset(CubicBezierInterpolator::EaseIn);\n  EXPECT_NEAR(0.093f, cubic_ease_in_timing->Interpolate(0.25), tolerance);\n  EXPECT_NEAR(0.315f, cubic_ease_in_timing->Interpolate(0.50), tolerance);\n  EXPECT_NEAR(0.620f, cubic_ease_in_timing->Interpolate(0.75), tolerance);\n\n  std::unique_ptr<Interpolator> cubic_ease_out_timing =\n      CubicBezierInterpolator::CreatePreset(CubicBezierInterpolator::EaseOut);\n  EXPECT_NEAR(0.379f, cubic_ease_out_timing->Interpolate(0.25), tolerance);\n  EXPECT_NEAR(0.694f, cubic_ease_out_timing->Interpolate(0.50), tolerance);\n  EXPECT_NEAR(0.906f, cubic_ease_out_timing->Interpolate(0.75), tolerance);\n\n  std::unique_ptr<Interpolator> cubic_ease_in_out_timing =\n      CubicBezierInterpolator::CreatePreset(CubicBezierInterpolator::EaseInOut);\n  EXPECT_NEAR(0.128f, cubic_ease_in_out_timing->Interpolate(0.25), tolerance);\n  EXPECT_NEAR(0.500f, cubic_ease_in_out_timing->Interpolate(0.50), tolerance);\n  EXPECT_NEAR(0.871f, cubic_ease_in_out_timing->Interpolate(0.75), tolerance);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/cycle_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/cycle_interpolator.h\"\n\n#include <math.h>\n\n#include <algorithm>\n\nnamespace clay {\n\nfloat CycleInterpolator::Interpolate(float input) {\n  return sinf(2 * cycles_ * M_PI * input);\n}\n\nstd::unique_ptr<CycleInterpolator> CycleInterpolator::Create(float cycles) {\n  return std::make_unique<CycleInterpolator>(cycles);\n}\n\nstd::unique_ptr<Interpolator> CycleInterpolator::Clone() {\n  return CycleInterpolator::Create(cycles_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/cycle_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_CYCLE_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_CYCLE_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass CycleInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<CycleInterpolator> Create(float cycles);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  explicit CycleInterpolator(float cycles) : cycles_(cycles) {}\n  float Interpolate(float input) override;\n\n private:\n  const float cycles_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_CYCLE_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/decelerate_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/decelerate_interpolator.h\"\n\n#include <math.h>\n\nnamespace clay {\n\nfloat DecelerateInterpolator::Interpolate(float input) {\n  float result;\n  if (factor_ == 1.0f) {\n    result = 1.0f - (1.0f - input) * (1.0f - input);\n  } else {\n    result = 1.0f - pow((1.0f - input), 2 * factor_);\n  }\n  return result;\n}\n\nstd::unique_ptr<DecelerateInterpolator> DecelerateInterpolator::Create(\n    float factor) {\n  return std::make_unique<DecelerateInterpolator>(factor);\n}\n\nstd::unique_ptr<Interpolator> DecelerateInterpolator::Clone() {\n  return DecelerateInterpolator::Create(factor_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/decelerate_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_DECELERATE_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_DECELERATE_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass DecelerateInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<DecelerateInterpolator> Create(float factor);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  explicit DecelerateInterpolator(float factor) : factor_(factor) {}\n  float Interpolate(float input) override;\n\n private:\n  const float factor_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_DECELERATE_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/dynamic_animator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/dynamic_animator.h\"\n\n#include <algorithm>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nDynamicAnimator::~DynamicAnimator() {\n  if (running_) {\n    GetAnimationHandler()->RemoveCallback(this);\n  }\n}\n\nvoid DynamicAnimator::SetStartValue(float start_value) {\n  value_ = start_value;\n  start_value_ = start_value;\n  start_value_is_set_ = true;\n}\n\nvoid DynamicAnimator::SetMinimumVisibleChange(float minimum_visible_change) {\n  FML_DCHECK(minimum_visible_change > 0)\n      << \"Minimum visible change must be positive.\";\n\n  min_visible_change_ = minimum_visible_change;\n  SetValueThreshold(minimum_visible_change * kThresholdMultiplier);\n}\n\nvoid DynamicAnimator::Start() { StartAnimationInternal(); }\n\nvoid DynamicAnimator::Cancel() { EndAnimationInternal(true); }\n\nvoid DynamicAnimator::StartAnimationInternal() {\n  if (!running_) {\n    running_ = true;\n    // Sanity check\n    FML_DCHECK(start_value_is_set_)\n        << \"Starting value need to be set before starting animation.\";\n    FML_DCHECK(value_ >= min_value_ && value_ <= max_value_)\n        << \"Starting value need to be in between min value and max value\";\n    last_frame_time_ = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n    GetAnimationHandler()->AddAnimationFrameCallback(this, 0);\n  }\n}\n\nvoid DynamicAnimator::EndAnimationInternal(bool canceled) {\n  if (!running_) {\n    return;\n  }\n  running_ = false;\n  GetAnimationHandler()->RemoveCallback(this);\n  last_frame_time_ = 0;\n  delta_ = 0;\n  start_value_is_set_ = false;\n  for (auto l : listeners_) {\n    if (l) {\n      l->OnDynamicAnimationEnd(*this, canceled, value_, velocity_);\n    }\n  }\n}\n\nbool DynamicAnimator::DoAnimationFrame(int64_t frame_time) {\n  FML_DCHECK(last_frame_time_ > 0) << \"last_frame_time_ should be set on start\";\n  int64_t delta_time = frame_time - last_frame_time_;\n  last_frame_time_ = frame_time;\n  if (start_time_ > last_frame_time_) {\n    start_time_ = last_frame_time_;\n  }\n  float prev_value = value_;\n  bool finished = UpdateValueAndVelocity(delta_time);\n  value_ = std::clamp(value_, min_value_, max_value_);\n  delta_ = value_ - prev_value;\n  for (auto l : listeners_) {\n    if (l) {\n      l->OnDynamicAnimationUpdate(*this, value_, velocity_);\n    }\n  }\n  if (finished) {\n    EndAnimationInternal(false);\n  }\n  CleanupList();\n  return finished;\n}\n\nvoid DynamicAnimator::RemoveListener(AnimationListener* listener) {\n  // To avoid invalidating iterators, set the item to nullptr instead of\n  // remove it.\n  auto ret = std::find(listeners_.begin(), listeners_.end(), listener);\n  if (ret != listeners_.end()) {\n    *ret = nullptr;\n    listener_list_dirty_ = true;\n  }\n}\n\nvoid DynamicAnimator::RemoveAllListeners() {\n  for (auto& l : listeners_) {\n    l = nullptr;\n  }\n  listener_list_dirty_ = true;\n}\n\nvoid DynamicAnimator::CleanupList() {\n  if (listener_list_dirty_) {\n    listener_list_dirty_ = false;\n    listeners_.remove(nullptr);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/dynamic_animator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_DYNAMIC_ANIMATOR_H_\n#define CLAY_GFX_ANIMATION_DYNAMIC_ANIMATOR_H_\n\n#include <forward_list>\n#include <limits>\n\n#include \"clay/gfx/animation/animation_handler.h\"\n\nnamespace clay {\n\n/**\n * This class is the base class of physics-based animations. It manages the\n * animation's lifecycle such as {@link #Start()} and {@link #Cancel()}. This\n * base class also handles the common setup for all the subclass animations.\n * For example, DynamicAnimator supports adding listeners so that the important\n * animation events can be observed through the callbacks. The start conditions\n * for any subclass of DynamicAnimator can be set using SetStartValue(float)\n * and setStartVelocity(float).\n */\nclass DynamicAnimator : public AnimationHandler::AnimationFrameCallback {\n public:\n  /**\n   * Implementors of this interface can add themselves as update listeners\n   * to an DynamicAnimator instance to receive callbacks on every animation\n   * frame, after the current frame's values have been calculated for that\n   * DynamicAnimator.\n   */\n  class AnimationListener {\n   public:\n    /**\n     * Notifies the occurrence of another frame of the animation.\n     *\n     * @param animation animation that the update listener is added to\n     * @param value the current value of the animation\n     * @param velocity the current velocity of the animation\n     */\n\n    virtual void OnDynamicAnimationUpdate(DynamicAnimator& animation,\n                                          float value, float velocity) {}\n    /**\n     * Notifies the end of an animation. Note that this callback will be\n     * invoked not only when an animation reach equilibrium, but also when the\n     * animation is canceled.\n     *\n     * @param animation animation that has ended or was canceled\n     * @param canceled whether the animation has been canceled\n     * @param value the final value when the animation stopped\n     * @param velocity the final velocity when the animation stopped\n     */\n    virtual void OnDynamicAnimationEnd(DynamicAnimator& animation,\n                                       bool canceled, float value,\n                                       float velocity) {}\n\n    virtual ~AnimationListener() = default;\n  };\n\n  /**\n   * The minimum visible change in pixels that can be visible to users.\n   */\n\n  static constexpr float kMinVisibleChangePixels = 1.f;\n\n  // Multiplier to the min visible change value for value threshold\n  static constexpr float kThresholdMultiplier = 0.75f;\n\n  virtual ~DynamicAnimator();\n\n  /**\n   * Sets the start value of the animation. If start value is not set, the\n   * animation will get the current value for the view's property, and use that\n   * as the start value.\n   */\n\n  void SetStartValue(float start_value);\n\n  /**\n   * Start velocity of the animation. Default velocity is 0. Unit: change in\n   * property per second (e.g. pixels per second, scale/alpha value change per\n   * second).\n   *\n   * Note when using a fixed value as the start velocity (as opposed to getting\n   * the velocity through touch events), it is recommended to define such a\n   * value in dp/second and convert it to pixel/second based on the density of\n   * the screen to achieve a consistent look across different screens.\n   */\n\n  void SetStartVelocity(float start_velocity) { velocity_ = start_velocity; }\n  float GetCurrentVelocity() const { return velocity_; }\n\n  /**\n   * Sets the max value of the animation. Animations will not animate beyond\n   * their max value. Whether or not animation will come to an end when max\n   * value is reached is dependent on the child animation's implementation.\n   */\n\n  void SetMaxValue(float max) {\n    // This max value should be checked and handled in the subclass animations,\n    // instead of assuming the end of the animations when the max/min value is\n    // hit in the base class. The reason is that hitting max/min value may just\n    // be a transient state, such as during the spring oscillation.\n    max_value_ = max;\n  }\n  float GetMaxValue() { return max_value_; }\n\n  /**\n   * Sets the min value of the animation. Animations will not animate beyond\n   * their min value. Whether or not animation will come to an end when min\n   * value is reached is dependent on the child animation's implementation.\n   */\n  void SetMinValue(float min) { min_value_ = min; }\n  float GetMinValue() { return min_value_; }\n\n  /**\n   * This method sets the minimal change of animation value that is visible to\n   * users, which helps determine a reasonable threshold for the animation's\n   * termination condition. It is critical to set the minimal visible change\n   * for custom properties unless the custom property is in pixels.\n   */\n  void SetMinimumVisibleChange(float minimum_visible_change);\n\n  /**\n   * Returns the minimum change in the animation property that could be visibly\n   * different to users.\n   *\n   * @return minimum change in property value that is visible to users\n   */\n  float GetMinimumVisibleChange() { return min_visible_change_; }\n\n  /**\n   * Adds a listener to the set of listeners that are sent update events through\n   * the life of an animation. This method is called on all listeners for every\n   * frame of the animation, after the values for the animation have been\n   * calculated.\n   */\n  void AddListener(AnimationListener* listener) {\n    listeners_.push_front(listener);\n  }\n\n  /**\n   * Removes a listener from the set listening to frame updates for this\n   * animation.\n   */\n  void RemoveListener(AnimationListener* listener);\n\n  void RemoveAllListeners();\n\n  // Return the AnimationHandler that will be used to schedule updates for\n  // this animator.\n  AnimationHandler* GetAnimationHandler() { return animation_handler_; }\n\n  // Sets the animation handler used to schedule updates for this animator\n  // or null to use the default handler.\n  void SetAnimationHandler(AnimationHandler* animation_handler) {\n    animation_handler_ = animation_handler;\n  }\n\n  /**\n   * Starts an animation. If the animation has already been started, no op. Note\n   * that calling\n   * Start() will not immediately set the property value to start value\n   * of the animation. The property values will be changed at each animation\n   * pulse, which happens before the draw pass. As a result, the changes will be\n   * reflected in the next frame, the same as if the values were set\n   * immediately. This method should only be called on main thread.\n   */\n  void Start();\n\n  /**\n   * Cancels the on-going animation. If the animation hasn't started, no op.\n   */\n  void Cancel();\n\n  /**\n   * Returns whether the animation is currently running.\n   */\n  bool IsRunning() { return running_; }\n\n  float GetValue() const { return value_; }\n\n  /**\n   * Returns the default threshold.\n   */\n  float GetValueThreshold() {\n    return min_visible_change_ * kThresholdMultiplier;\n  }\n\n  void SetDensity(float density) { density_ = density; }\n  /**\n   * This gets call on each frame of the animation. Animation value and\n   * velocity are updated in this method based on the new frame time. The\n   * property value of the view being animated is then updated. The animation's\n   * ending conditions are also checked in this method. Once the animation\n   * reaches equilibrium, the animation will come to its end, and end listeners\n   * will be notified, if any.\n   */\n  bool DoAnimationFrame(int64_t frame_time) override;\n\n  // Return time in milliseconds\n  int64_t GetLastAnimationTime() const { return last_frame_time_; }\n\n  /**\n   * Get the delta value of the last frame.\n   */\n  float GetDelta() { return delta_; }\n\n protected:\n  /**\n   * Updates the animation state (i.e. value and velocity). Subclasses can\n   * override this method to calculate the new value and velocity in their\n   * custom way.\n   *\n   * @param delta_time time elapsed in millisecond since last frame\n   * @return whether the animation has finished\n   */\n  virtual bool UpdateValueAndVelocity(int64_t delta_time) = 0;\n\n  /**\n   * Returns the acceleration at the given value with the given velocity.\n   **/\n  virtual float GetAcceleration(float value, float velocity) = 0;\n\n  /**\n   * Returns whether the animation has reached equilibrium.\n   */\n  virtual bool IsAtEquilibrium(float value, float velocity) = 0;\n\n  /**\n   * Updates the default value threshold for the animation based on the\n   * property to be animated.\n   */\n  virtual void SetValueThreshold(float threshold) = 0;\n\n private:\n  // This gets called when the animation is started, to finish the setup of the\n  // animation before the animation pulsing starts.\n  void StartAnimationInternal();\n\n  /**\n   * Internal method to reset the animation states when animation is\n   * finished/canceled.\n   */\n  void EndAnimationInternal(bool canceled);\n\n protected:\n  // Internal tracking for velocity.\n  float velocity_ = 0;\n\n  // Internal tracking for value.\n  float value_ = std::numeric_limits<float>::max();\n  float delta_ = 0;\n\n  int64_t start_time_ = 0;\n  // Last frame time. Always gets reset to 0 at the end of the animation.\n  int64_t last_frame_time_ = 0;\n\n  float start_value_ = 0;\n  float final_value_ = 0;\n  float density_ = 2.75;\n\n private:\n  void CleanupList();\n\n  bool listener_list_dirty_ = false;\n\n  // Tracks whether start value is set.\n  bool start_value_is_set_ = false;\n\n  bool running_ = false;\n\n  // Min and max values that defines the range of the animation values.\n  float max_value_ = std::numeric_limits<float>::max();\n  float min_value_ = std::numeric_limits<float>::lowest();\n\n  float min_visible_change_ = kMinVisibleChangePixels;\n\n  /**\n   * The set of listeners to be sent events through the life of an animation.\n   */\n  std::forward_list<AnimationListener*> listeners_;\n\n  /**\n   * Animation handler used to schedule updates for this animation.\n   */\n  AnimationHandler* animation_handler_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_DYNAMIC_ANIMATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/fling_animator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/fling_animator.h\"\n\n#include <algorithm>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nnamespace {\nconstexpr int kNBSamples = 100;\nfloat kSplinePosition[kNBSamples + 1];\nconstexpr float kStartTension = 0.5f;\nconstexpr float kEndTension = 1.0f;\nconst float kDecelerationRate = std::log(0.75) / std::log(0.9);\nconstexpr float kInflexion = 0.35f;\nconstexpr float kGravityEarth = 9.80665f;\nconstexpr float P1 = kStartTension * kInflexion;\nconstexpr float P2 = 1.0f - kEndTension * (1.0f - kInflexion);\n}  // namespace\n\nFlingAnimator::FlingAnimator() {\n  SetValueThreshold(GetValueThreshold());\n  physical_coeff_ = ComputeDeceleration(friction_);\n  InitParams();\n}\n\nvoid FlingAnimator::InitParams() {\n  static bool param_setup = false;\n  if (param_setup) {\n    return;\n  }\n  float x_min = 0.0f;\n  for (int i = 0; i < kNBSamples; i++) {\n    const float alpha = static_cast<float>(i) / kNBSamples;\n    float x_max = 1.0f;\n    float x, tx, coeff;\n    while (true) {\n      x = x_min + (x_max - x_min) / 2.0f;\n      coeff = 3.0f * x * (1.0f - x);\n      tx = coeff * ((1.0f - x) * P1 + x * P2) + x * x * x;\n      if (std::abs(tx - alpha) < 1E-5) {\n        break;\n      }\n      if (tx > alpha) {\n        x_max = x;\n      } else {\n        x_min = x;\n      }\n    }\n    kSplinePosition[i] = coeff * ((1.0f - x) * kStartTension + x) + x * x * x;\n  }\n  kSplinePosition[kNBSamples] = 1.0f;\n  param_setup = true;\n}\n\nvoid FlingAnimator::SetFriction(float friction) { friction_ = friction; }\n\nfloat FlingAnimator::GetFriction() { return friction_ / kDefaultFriction; }\n\nfloat FlingAnimator::GetDistance() { return distance_; }\n\nbool FlingAnimator::UpdateValueAndVelocity(int64_t delta_time) {\n  if (!IsRunning()) {\n    return false;\n  }\n  FML_DCHECK(last_frame_time_ - start_time_ >= 0);\n  int64_t time_passed = last_frame_time_ - start_time_;\n  if (time_passed < duration_) {\n    const float t = time_passed / duration_;\n    const int index = static_cast<int>(kNBSamples * t);\n    float distance_coeff = 1.f;\n    float velocity_coeff = 0.f;\n    if (index < kNBSamples) {\n      const float t_inf = static_cast<float>(index) / kNBSamples;\n      const float t_sup = static_cast<float>(index + 1) / kNBSamples;\n      const float d_inf = kSplinePosition[index];\n      const float d_sup = kSplinePosition[index + 1];\n      velocity_coeff = (d_sup - d_inf) / (t_sup - t_inf);\n      distance_coeff = d_inf + (t - t_inf) * velocity_coeff;\n    }\n    velocity_ = velocity_coeff * distance_ / duration_ * 1000.0f;\n    value_ = start_value_ + distance_coeff * (final_value_ - start_value_);\n    // Pin to mMinX <= mCurrX <= mMaxX\n    value_ = std::min(value_, GetMaxValue());\n    value_ = std::max(value_, GetMinValue());\n    if (IsAtEquilibrium(value_, velocity_)) {\n      return true;\n    }\n  } else {\n    value_ = final_value_;\n    return true;\n  }\n  return false;\n}\nfloat FlingAnimator::ComputeDeceleration(float friction) {\n  return kGravityEarth       // g (m/s^2)\n         * 39.37f            // inch/meter\n         * density_ * 160.f  // pixels per inch\n         * friction;\n}\n\nfloat FlingAnimator::GetSplineFlingDistance(float velocity) {\n  const double l = GetSplineDeceleration(velocity);\n  return friction_ * physical_coeff_ *\n         std::exp(kDecelerationRate / (kDecelerationRate - 1.0) * l);\n}\n\nfloat FlingAnimator::GetSplineDeceleration(float velocity) {\n  return std::log(kInflexion * std::abs(velocity) /\n                  (friction_ * physical_coeff_));\n}\n\nfloat FlingAnimator::GetSplineFlingDuration(float velocity) {\n  const double l = GetSplineDeceleration(velocity);\n  return 1000.0 * std::exp(l / (kDecelerationRate - 1.0));\n}\n\nfloat FlingAnimator::GetAcceleration(float value, float velocity) {\n  return velocity * friction_;\n}\n\nbool FlingAnimator::IsAtEquilibrium(float value, float velocity) {\n  return value >= GetMaxValue() || value <= GetMinValue() ||\n         std::abs(velocity) < velocity_threshold_;\n}\n\nvoid FlingAnimator::SetValueThreshold(float threshold) {\n  velocity_threshold_ = threshold * kVelocityThresholdMultiplier;\n}\n\nvoid FlingAnimator::FlingInitialize() {\n  auto velocity = velocity_;\n  duration_ = GetSplineFlingDuration(velocity);\n  start_time_ = fml::TimePoint::Now().ToEpochDelta().ToMillisecondsF();\n  double total_distance = velocity_ > 0 ? GetSplineFlingDistance(velocity)\n                                        : -1 * GetSplineFlingDistance(velocity);\n  distance_ = total_distance;\n  final_value_ = start_value_ + total_distance;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/fling_animator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_FLING_ANIMATOR_H_\n#define CLAY_GFX_ANIMATION_FLING_ANIMATOR_H_\n\n#include <forward_list>\n#include <memory>\n\n#include \"clay/gfx/animation/dynamic_animator.h\"\n\nnamespace clay {\n\n/**\n * Fling animation is an animation that continues an initial momentum (most\n * often from gesture velocity) and gradually slows down. The fling animation\n * will come to a stop when the velocity of the animation is below the threshold\n * derived from SetMinimumVisibleChange(float), or when the value of the\n * animation has gone beyond the min or max value defined via SetMinValue(float)\n * or SetMaxValue(float). It is recommended to restrict the fling animation with\n * min and/or max value, such that the animation can end when it goes beyond\n * screen bounds, thus preserving CPU cycles and resources.\n */\nclass FlingAnimator : public DynamicAnimator {\n public:\n  FlingAnimator();\n\n  static void InitParams();\n\n  /**\n   * Sets the friction for the fling animation. The greater the friction is, the\n   * sooner the animation will slow down. When not set, the friction defaults\n   * to 1.\n   */\n  void SetFriction(float friction_scalar);\n\n  /**\n   * Returns the friction being set on the animation.\n   */\n  float GetFriction();\n\n  /**\n   * Returns the estimated distance that the animation will travel.\n   */\n  float GetDistance();\n\n  void FlingInitialize();\n\n protected:\n  /**\n   * Updates the animation state (i.e. value and velocity). Subclasses can\n   * override this method to calculate the new value and velocity in their\n   * custom way.\n   *\n   * @param delta_time time elapsed in millisecond since last frame\n   * @return whether the animation has finished\n   */\n  bool UpdateValueAndVelocity(int64_t delta_time) override;\n\n  /**\n   * Returns the acceleration at the given value with the given velocity.\n   **/\n  float GetAcceleration(float value, float velocity) override;\n\n  /**\n   * Returns whether the animation has reached equilibrium.\n   */\n  bool IsAtEquilibrium(float value, float velocity) override;\n\n  /**\n   * Updates the default value threshold for the animation based on the property\n   * to be animated.\n   */\n  void SetValueThreshold(float threshold) override;\n\n private:\n  float GetSplineDeceleration(float velocity);\n  float GetSplineFlingDuration(float velocity);\n  float ComputeDeceleration(float friction);\n  float GetSplineFlingDistance(float velocity);\n\n  static constexpr float kDefaultFriction = 0.015f;\n\n  // This multiplier is used to calculate the velocity threshold given a certain\n  // value threshold. The idea is that if it takes >= 1 frame to move the value\n  // threshold amount, then the velocity is a reasonable threshold.\n  static constexpr float kVelocityThresholdMultiplier = 1000.f / 16.f;\n\n  float friction_ = kDefaultFriction;\n  float velocity_threshold_;\n  float duration_ = 0.f;\n  float distance_ = 0.f;\n  float physical_coeff_ = 0.f;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_FLING_ANIMATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/interpolator.h\"\n\n#include \"clay/gfx/animation/accelerate_decelerate_interpolator.h\"\n#include \"clay/gfx/animation/accelerate_interpolator.h\"\n#include \"clay/gfx/animation/cubic_bezier_interpolator.h\"\n#include \"clay/gfx/animation/decelerate_interpolator.h\"\n#include \"clay/gfx/animation/steps_interpolator.h\"\n#include \"clay/gfx/animation/timing_function_data.h\"\n\nnamespace clay {\n\nstd::unique_ptr<Interpolator> Interpolator::CreateDefaultInterpolator() {\n  return LinearInterpolator::Create();\n}\n\nstd::unique_ptr<Interpolator> Interpolator::Create(\n    const TimingFunctionData& data) {\n  switch (data.timing_func) {\n    case ClayTimingFunctionType::kLinear:\n      return LinearInterpolator::Create();\n    case ClayTimingFunctionType::kEaseIn:\n      return AccelerateInterpolator::Create(1.0f);\n    case ClayTimingFunctionType::kEaseOut:\n      return DecelerateInterpolator::Create(1.0f);\n    case ClayTimingFunctionType::kEaseInEaseOut:\n      return AccelerateDecelerateInterpolator::Create();\n    case ClayTimingFunctionType::kSquareBezier:\n      return CubicBezierInterpolator::Create(data.x1, data.y1);\n    case ClayTimingFunctionType::kCubicBezier:\n      return CubicBezierInterpolator::Create(data.x1, data.y1, data.x2,\n                                             data.y2);\n    case ClayTimingFunctionType::kSteps:\n      return StepsInterpolator::Create(data.x1, data.steps_type);\n    default:\n      return nullptr;\n  }\n}\n\nstd::unique_ptr<LinearInterpolator> LinearInterpolator::Create() {\n  return std::make_unique<LinearInterpolator>();\n}\n\nstd::unique_ptr<Interpolator> LinearInterpolator::Clone() {\n  return LinearInterpolator::Create();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_INTERPOLATOR_H_\n\n#include <memory>\n\nnamespace clay {\n\nstruct TimingFunctionData;\n\nclass Interpolator {\n public:\n  virtual ~Interpolator() = default;\n\n  virtual float Interpolate(float input) = 0;\n\n  virtual std::unique_ptr<Interpolator> Clone() = 0;\n\n  static std::unique_ptr<Interpolator> Create(const TimingFunctionData& data);\n\n  static std::unique_ptr<Interpolator> CreateDefaultInterpolator();\n  virtual double Velocity(double time) const { return 0; }\n\n protected:\n  Interpolator() = default;\n};\n\nclass LinearInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<LinearInterpolator> Create();\n  std::unique_ptr<Interpolator> Clone() override;\n\n  float Interpolate(float input) override { return input; }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/keyframe.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/keyframe.h\"\n\n#include <memory>\n#include <sstream>\n\n#include \"clay/gfx/geometry/filter_operations.h\"\n\nnamespace clay {\n\nKeyframe::Keyframe(float fraction, std::unique_ptr<Interpolator> interpolator)\n    : fraction_(fraction), interpolator_(std::move(interpolator)) {}\n\nKeyframe::~Keyframe() = default;\n\nstd::unique_ptr<FloatKeyframe> FloatKeyframe::Create(\n    float fraction, float value, std::unique_ptr<Interpolator> interpolator) {\n  return std::unique_ptr<FloatKeyframe>(\n      new FloatKeyframe(fraction, value, std::move(interpolator)));\n}\n\nFloatKeyframe::FloatKeyframe(float fraction, float value,\n                             std::unique_ptr<Interpolator> interpolator)\n    : Keyframe(fraction, std::move(interpolator)), value_(value) {}\n\nFloatKeyframe::~FloatKeyframe() = default;\n\nfloat FloatKeyframe::Value() const { return value_; }\n\nstd::unique_ptr<FloatKeyframe> FloatKeyframe::Clone() const {\n  std::unique_ptr<Interpolator> func;\n  if (GetInterpolator()) {\n    func = GetInterpolator()->Clone();\n  }\n  return FloatKeyframe::Create(GetFraction(), Value(), std::move(func));\n}\n\n#ifndef NDEBUG\nstd::string FloatKeyframe::ToString() const {\n  std::ostringstream os;\n  os << \"FloatKeyframe: fraction=\" << GetFraction() << \" value=\" << Value();\n  return os.str();\n}\n#endif\n\nstd::unique_ptr<ColorKeyframe> ColorKeyframe::Create(\n    float fraction, Color value, std::unique_ptr<Interpolator> interpolator) {\n  return std::unique_ptr<ColorKeyframe>(\n      new ColorKeyframe(fraction, value, std::move(interpolator)));\n}\n\nColorKeyframe::ColorKeyframe(float fraction, Color value,\n                             std::unique_ptr<Interpolator> interpolator)\n    : Keyframe(fraction, std::move(interpolator)), value_(value) {}\n\nColorKeyframe::~ColorKeyframe() = default;\n\nColor ColorKeyframe::Value() const { return value_; }\n\nstd::unique_ptr<ColorKeyframe> ColorKeyframe::Clone() const {\n  std::unique_ptr<Interpolator> func;\n  if (GetInterpolator()) {\n    func = GetInterpolator()->Clone();\n  }\n  return ColorKeyframe::Create(GetFraction(), Value(), std::move(func));\n}\n\n#ifndef NDEBUG\nstd::string ColorKeyframe::ToString() const {\n  std::ostringstream os;\n  os << \"ColorKeyframe: fraction=\" << GetFraction() << \" value=\" << std::hex\n     << Value().Value();\n  return os.str();\n}\n#endif\n\nstd::unique_ptr<RawTransformKeyframe> RawTransformKeyframe::Create(\n    float fraction, const ClayTransform& transform,\n    std::unique_ptr<Interpolator> interpolator) {\n  return std::unique_ptr<RawTransformKeyframe>(\n      new RawTransformKeyframe(fraction, transform, std::move(interpolator)));\n}\n\nstd::unique_ptr<RawTransformKeyframe> RawTransformKeyframe::Create(\n    float fraction, const std::vector<ClayTransformOP>& transform,\n    std::unique_ptr<Interpolator> interpolator) {\n  return std::unique_ptr<RawTransformKeyframe>(\n      new RawTransformKeyframe(fraction, transform, std::move(interpolator)));\n}\n\nRawTransformKeyframe::RawTransformKeyframe(\n    float fraction, const ClayTransform& transform,\n    std::unique_ptr<Interpolator> interpolator)\n    : Keyframe(fraction, std::move(interpolator)) {\n  operations_.reserve(transform.size);\n  for (int i = 0; i < transform.size; i++) {\n    operations_.emplace_back(transform.op[i]);\n  }\n}\n\nRawTransformKeyframe::RawTransformKeyframe(\n    float fraction, const std::vector<ClayTransformOP>& transform,\n    std::unique_ptr<Interpolator> interpolator)\n    : Keyframe(fraction, std::move(interpolator)), operations_(transform) {}\n\n#ifndef NDEBUG\nstd::string RawTransformKeyframe::ToString() const {\n  std::ostringstream os;\n  os << \"RawTransformKeyframe: fraction=\" << GetFraction()\n     << \" operation_count=\" << operations_.size();\n  return os.str();\n}\n#endif\n\nstd::unique_ptr<TransformKeyframe> TransformKeyframe::Create(\n    float fraction, const TransformOperations& value,\n    std::unique_ptr<Interpolator> interpolator) {\n  return std::unique_ptr<TransformKeyframe>(\n      new TransformKeyframe(fraction, value, std::move(interpolator)));\n}\n\nTransformKeyframe::TransformKeyframe(float fraction,\n                                     const TransformOperations& value,\n                                     std::unique_ptr<Interpolator> interpolator)\n    : Keyframe(fraction, std::move(interpolator)), value_(value) {}\n\nTransformKeyframe::~TransformKeyframe() = default;\n\nconst TransformOperations& TransformKeyframe::Value() const { return value_; }\n\nstd::unique_ptr<TransformKeyframe> TransformKeyframe::Clone() const {\n  std::unique_ptr<Interpolator> func;\n  if (GetInterpolator()) {\n    func = GetInterpolator()->Clone();\n  }\n  return TransformKeyframe::Create(GetFraction(), Value(), std::move(func));\n}\n\n#ifndef NDEBUG\nstd::string TransformKeyframe::ToString() const {\n  std::ostringstream os;\n  os << \"TransformKeyframe: fraction=\" << GetFraction()\n     << \" value=\" << Value().Apply().ToString();\n  return os.str();\n}\n#endif\n\nFilterKeyframe::FilterKeyframe(float fraction, const FilterOperations& value,\n                               std::unique_ptr<Interpolator> interpolator)\n    : Keyframe(fraction, std::move(interpolator)), value_(value) {}\n\nstd::unique_ptr<FilterKeyframe> FilterKeyframe::Create(\n    float fraction, const FilterOperations& value,\n    std::unique_ptr<Interpolator> interpolator) {\n  return std::unique_ptr<FilterKeyframe>(\n      new FilterKeyframe(fraction, value, std::move(interpolator)));\n}\n\nFilterKeyframe::~FilterKeyframe() = default;\n\n#ifndef NDEBUG\nstd::string FilterKeyframe::ToString() const { return \"FilterKeyframe\"; }\n#endif\n\nstd::unique_ptr<FilterKeyframe> FilterKeyframe::Clone() const {\n  std::unique_ptr<Interpolator> func;\n  if (GetInterpolator()) {\n    func = GetInterpolator()->Clone();\n  }\n  return FilterKeyframe::Create(GetFraction(), Value(), std::move(func));\n}\n\nconst FilterOperations& FilterKeyframe::Value() const { return value_; }\n\nstd::unique_ptr<BoxShadowKeyframe> BoxShadowKeyframe::Create(\n    float fraction, const BoxShadowOperations& value,\n    std::unique_ptr<Interpolator> interpolator) {\n  return std::unique_ptr<BoxShadowKeyframe>(\n      new BoxShadowKeyframe(fraction, value, std::move(interpolator)));\n}\nBoxShadowKeyframe::~BoxShadowKeyframe() {}\n\nconst BoxShadowOperations& BoxShadowKeyframe::Value() const { return value_; }\n\nstd::unique_ptr<BoxShadowKeyframe> BoxShadowKeyframe::Clone() const {\n  std::unique_ptr<Interpolator> func;\n  if (GetInterpolator()) {\n    func = GetInterpolator()->Clone();\n  }\n  return BoxShadowKeyframe::Create(GetFraction(), Value(), std::move(func));\n}\n\n#ifndef NDEBUG\nstd::string BoxShadowKeyframe::ToString() const { return \"BoxShadowKeyframe\"; }\n#endif\n\nBoxShadowKeyframe::BoxShadowKeyframe(float fraction,\n                                     const BoxShadowOperations& value,\n                                     std::unique_ptr<Interpolator> interpolator)\n    : Keyframe(fraction, std::move(interpolator)), value_(value) {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/keyframe.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_KEYFRAME_H_\n#define CLAY_GFX_ANIMATION_KEYFRAME_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/animation/interpolator.h\"\n#include \"clay/gfx/geometry/box_shadow_operations.h\"\n#include \"clay/gfx/geometry/filter_operations.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n\nclass Keyframe {\n public:\n  Keyframe(const Keyframe&) = delete;\n  Keyframe& operator=(const Keyframe&) = delete;\n\n  /**\n   * Gets the time for this keyframe, as a fraction of the overall animation\n   * duration.\n   *\n   * @return The time associated with this keyframe, as a fraction of the\n   * overall animation duration. This should be a value between 0 and 1.\n   */\n  float GetFraction() const { return fraction_; }\n\n  /**\n   * Gets the optional interpolator for this Keyframe. A null pointer\n   * indicates that there is no interpolation, which is the same as linear\n   * interpolation.\n   *\n   * @return The optional interpolator for this Keyframe.\n   */\n  Interpolator* GetInterpolator() const { return interpolator_.get(); }\n\n#ifndef NDEBUG\n  virtual std::string ToString() const = 0;\n#endif\n\n protected:\n  Keyframe(float fraction, std::unique_ptr<Interpolator> interpolator);\n  virtual ~Keyframe();\n\n private:\n  /**\n   * The time at which value_ will hold true.\n   */\n  float fraction_ = 0.f;\n\n  /**\n   * The optional time interpolator for the interval preceding this keyframe.\n   * A null interpolator (the default) results in linear interpolation\n   * over the interval.\n   */\n  std::unique_ptr<Interpolator> interpolator_;\n};\n\nclass FloatKeyframe : public Keyframe {\n public:\n  typedef float ValueType;\n\n  FloatKeyframe(const FloatKeyframe&) = delete;\n  FloatKeyframe& operator=(const FloatKeyframe&) = delete;\n\n  static std::unique_ptr<FloatKeyframe> Create(\n      float fraction, float value, std::unique_ptr<Interpolator> interpolator);\n  ~FloatKeyframe() override;\n\n  float Value() const;\n\n  std::unique_ptr<FloatKeyframe> Clone() const;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  FloatKeyframe(float fraction, float value,\n                std::unique_ptr<Interpolator> interpolator);\n\n  /**\n   * The value of the animation at the time fraction_.\n   */\n  float value_;\n};\n\nclass ColorKeyframe : public Keyframe {\n public:\n  typedef Color ValueType;\n\n  ColorKeyframe(const ColorKeyframe&) = delete;\n  ColorKeyframe& operator=(const ColorKeyframe&) = delete;\n\n  static std::unique_ptr<ColorKeyframe> Create(\n      float fraction, Color value, std::unique_ptr<Interpolator> interpolator);\n  ~ColorKeyframe() override;\n\n  Color Value() const;\n\n  std::unique_ptr<ColorKeyframe> Clone() const;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  ColorKeyframe(float fraction, Color value,\n                std::unique_ptr<Interpolator> interpolator);\n\n  /**\n   * The value of the animation at the time fraction_.\n   */\n  Color value_;\n};\n\n// See `RawTransformKeyframeSet` class\nclass RawTransformKeyframe : public Keyframe {\n public:\n  static std::unique_ptr<RawTransformKeyframe> Create(\n      float fraction, const ClayTransform& transform,\n      std::unique_ptr<Interpolator> interpolator);\n  static std::unique_ptr<RawTransformKeyframe> Create(\n      float fraction, const std::vector<ClayTransformOP>& transform,\n      std::unique_ptr<Interpolator> interpolator);\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n  const std::vector<ClayTransformOP>& Operations() const { return operations_; }\n\n private:\n  RawTransformKeyframe(float fraction, const ClayTransform& transform,\n                       std::unique_ptr<Interpolator> interpolator);\n  RawTransformKeyframe(float fraction,\n                       const std::vector<ClayTransformOP>& transform,\n                       std::unique_ptr<Interpolator> interpolator);\n\n  std::vector<ClayTransformOP> operations_;\n};\n\nclass TransformKeyframe : public Keyframe {\n public:\n  typedef TransformOperations ValueType;\n\n  TransformKeyframe(const TransformKeyframe&) = delete;\n  TransformKeyframe& operator=(const TransformKeyframe&) = delete;\n\n  static std::unique_ptr<TransformKeyframe> Create(\n      float fraction, const TransformOperations& value,\n      std::unique_ptr<Interpolator> interpolator);\n  ~TransformKeyframe() override;\n\n  const TransformOperations& Value() const;\n\n  std::unique_ptr<TransformKeyframe> Clone() const;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  TransformKeyframe(float fraction, const TransformOperations& value,\n                    std::unique_ptr<Interpolator> interpolator);\n\n  /**\n   * The value of the animation at the time fraction_.\n   */\n  TransformOperations value_;\n};\n\nclass FilterKeyframe : public Keyframe {\n public:\n  typedef FilterOperations ValueType;\n  FilterKeyframe(const FilterKeyframe&) = delete;\n  FilterKeyframe& operator=(const FilterKeyframe&) = delete;\n\n  static std::unique_ptr<FilterKeyframe> Create(\n      float fraction, const FilterOperations& value,\n      std::unique_ptr<Interpolator> interpolator);\n  ~FilterKeyframe() override;\n\n  const FilterOperations& Value() const;\n\n  std::unique_ptr<FilterKeyframe> Clone() const;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  FilterKeyframe(float fraction, const FilterOperations& value,\n                 std::unique_ptr<Interpolator> interpolator);\n\n  /**\n   * The value of the animation at the time fraction_.\n   */\n  FilterOperations value_;\n\n  friend class FilterKeyframeSet;\n};\n\nclass BoxShadowKeyframe : public Keyframe {\n public:\n  typedef BoxShadowOperations ValueType;\n  BoxShadowKeyframe(const BoxShadowKeyframe&) = delete;\n  BoxShadowKeyframe& operator=(const BoxShadowKeyframe&) = delete;\n\n  static std::unique_ptr<BoxShadowKeyframe> Create(\n      float fraction, const BoxShadowOperations& value,\n      std::unique_ptr<Interpolator> interpolator);\n  ~BoxShadowKeyframe() override;\n\n  const BoxShadowOperations& Value() const;\n\n  std::unique_ptr<BoxShadowKeyframe> Clone() const;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  BoxShadowKeyframe(float fraction, const BoxShadowOperations& value,\n                    std::unique_ptr<Interpolator> interpolator);\n\n  /**\n   * The value of the animation at the time fraction_.\n   */\n  BoxShadowOperations value_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_KEYFRAME_H_\n"
  },
  {
    "path": "clay/gfx/animation/keyframe_set.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/keyframe_set.h\"\n\n#include <limits>\n#include <optional>\n#include <sstream>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/animator_target.h\"\n#include \"clay/gfx/animation/keyframe.h\"\n#include \"clay/gfx/animation/keyframes_manager.h\"\n#include \"clay/gfx/animation/type_evaluator.h\"\n#include \"clay/gfx/geometry/filter_operations.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\n\nnamespace {\n\n// template function used to sort keyframes\ntemplate <typename KeyframeType>\nvoid InsertKeyframe(std::unique_ptr<KeyframeType> keyframe,\n                    std::vector<std::unique_ptr<KeyframeType>>* keyframes) {\n  // Usually, the keyframes will be added in order, so this loop would be\n  // unnecessary and we should skip it if possible.\n  if (!keyframes->empty() &&\n      keyframe->GetFraction() < keyframes->back()->GetFraction()) {\n    for (size_t i = 0; i < keyframes->size(); ++i) {\n      if (keyframe->GetFraction() < keyframes->at(i)->GetFraction()) {\n        keyframes->insert(keyframes->begin() + i, std::move(keyframe));\n        return;\n      }\n    }\n  }\n\n  keyframes->push_back(std::move(keyframe));\n}\n\n/**\n * Gets the animated value, given the elapsed fraction of the animation\n * (interpolated by the animation's interpolator) and the evaluator used\n * to calculate in-between values. This function maps the input fraction\n * to the appropriate keyframe interval and a fraction between them and\n * returns the interpolated value. Note that the input fraction may fall\n * outside the [0-1] bounds, if the animation's interpolator made that\n * happen (e.g., a spring interpolation that might send the fraction past\n * 1.0). We handle this situation by just using the two keyframes at the\n * appropriate end when the value is outside those bounds.\n *\n * @param fraction The elapsed fraction of the animation\n * @return The animated value.\n */\ntemplate <typename KeyframeType>\ntypename KeyframeType::ValueType GetValue(\n    float fraction,\n    const std::vector<std::unique_ptr<KeyframeType>>& keyframes) {\n  FML_DCHECK(keyframes.front()->GetFraction() == 0.f);\n  FML_DCHECK(keyframes.back()->GetFraction() == 1.f);\n  for (size_t i = 1; i < keyframes.size(); ++i) {\n    const auto& prev_keyframe = keyframes[i - 1];\n    float prev_fraction = prev_keyframe->GetFraction();\n    const auto& next_keyframe = keyframes[i];\n    if (fraction <= next_keyframe->GetFraction() || i == keyframes.size() - 1) {\n      Interpolator* interpolator = prev_keyframe->GetInterpolator();\n      float intervalFraction = (fraction - prev_fraction) /\n                               (next_keyframe->GetFraction() - prev_fraction);\n\n      // Apply interpolator on the proportional duration.\n      if (interpolator != nullptr) {\n        intervalFraction = interpolator->Interpolate(intervalFraction);\n      }\n      return TypeEvaluator<typename KeyframeType::ValueType>::Evaluate(\n          intervalFraction, prev_keyframe->Value(), next_keyframe->Value());\n    }\n  }\n  FML_UNREACHABLE();\n  return keyframes.back()->Value();\n}\n\n}  // namespace\n\nKeyframeSet::~KeyframeSet() = default;\n\nKeyframeSet::KeyframeSet(ClayAnimationPropertyType property_type)\n    : property_type_(property_type) {}\n\nvoid KeyframeSet::SetKeyframesManager(KeyframesManager* keyframes_manager) {\n  keyframes_manager_ = keyframes_manager;\n}\n\nstd::unique_ptr<FloatKeyframeSet> FloatKeyframeSet::Create(\n    ClayAnimationPropertyType property_type) {\n  return std::unique_ptr<FloatKeyframeSet>(new FloatKeyframeSet(property_type));\n}\n\nFloatKeyframeSet::FloatKeyframeSet(ClayAnimationPropertyType property_type)\n    : KeyframeSet(property_type) {}\n\nFloatKeyframeSet::~FloatKeyframeSet() = default;\n\nvoid FloatKeyframeSet::AddKeyframe(std::unique_ptr<FloatKeyframe> keyframe) {\n  InsertKeyframe(std::move(keyframe), &keyframes_);\n}\n\nstd::unique_ptr<KeyframeSet> FloatKeyframeSet::Clone(\n    KeyframesManager* manager) const {\n  std::unique_ptr<FloatKeyframeSet> to_return =\n      FloatKeyframeSet::Create(Type());\n  to_return->SetKeyframesManager(manager);\n  for (const auto& keyframe : keyframes_) {\n    to_return->AddKeyframe(keyframe->Clone());\n  }\n\n  return to_return;\n}\n\nvoid FloatKeyframeSet::OnAnimationStart(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->GetProperty(Type(), original_value_);\n  }\n}\n\nvoid FloatKeyframeSet::OnAnimationRemove(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->SetProperty(Type(), original_value_, false);\n  }\n}\n\nvoid FloatKeyframeSet::OnAnimationUpdate(ValueAnimator& animation) {\n  if (keyframes_.front()->GetFraction() > 0) {\n    auto* interpolator = keyframes_.front()->GetInterpolator();\n    AddKeyframe(\n        FloatKeyframe::Create(0.f, original_value_, interpolator->Clone()));\n  }\n  if (keyframes_.back()->GetFraction() < 1) {\n    auto* interpolator = keyframes_.back()->GetInterpolator();\n    AddKeyframe(\n        FloatKeyframe::Create(1.f, original_value_, interpolator->Clone()));\n  }\n  if (auto manager = GetKeyframesManager()) {\n    float current_fraction = animation.GetAnimatedFraction();\n    manager->GetTarget()->SetProperty(Type(), GetValue(current_fraction), true);\n  }\n}\n\nfloat FloatKeyframeSet::GetValue(float fraction) const {\n  return clay::GetValue(fraction, keyframes_);\n}\n\n#ifndef NDEBUG\nstd::string FloatKeyframeSet::ToString() const {\n  std::ostringstream os;\n  os << \"FloatKeyframeSet: size=\" << keyframes_.size()\n     << \" type=\" << static_cast<int>(Type()) << std::endl;\n  for (const auto& keyframe : keyframes_) {\n    os << \"\\t\" << keyframe->ToString() << std::endl;\n  }\n  return os.str();\n}\n#endif\n\nstd::unique_ptr<ColorKeyframeSet> ColorKeyframeSet::Create(\n    ClayAnimationPropertyType property_type) {\n  return std::unique_ptr<ColorKeyframeSet>(new ColorKeyframeSet(property_type));\n}\n\nColorKeyframeSet::ColorKeyframeSet(ClayAnimationPropertyType property_type)\n    : KeyframeSet(property_type) {}\n\nColorKeyframeSet::~ColorKeyframeSet() = default;\n\nvoid ColorKeyframeSet::AddKeyframe(std::unique_ptr<ColorKeyframe> keyframe) {\n  InsertKeyframe(std::move(keyframe), &keyframes_);\n}\n\nstd::unique_ptr<KeyframeSet> ColorKeyframeSet::Clone(\n    KeyframesManager* manager) const {\n  std::unique_ptr<ColorKeyframeSet> to_return =\n      ColorKeyframeSet::Create(Type());\n  to_return->SetKeyframesManager(manager);\n  for (const auto& keyframe : keyframes_) {\n    to_return->AddKeyframe(keyframe->Clone());\n  }\n\n  return to_return;\n}\n\nvoid ColorKeyframeSet::OnAnimationStart(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->GetProperty(Type(), original_value_);\n  }\n}\n\nvoid ColorKeyframeSet::OnAnimationRemove(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->SetProperty(Type(), original_value_, false);\n  }\n}\n\nvoid ColorKeyframeSet::OnAnimationUpdate(ValueAnimator& animation) {\n  if (keyframes_.front()->GetFraction() > 0) {\n    auto* interpolator = keyframes_.front()->GetInterpolator();\n    AddKeyframe(\n        ColorKeyframe::Create(0.f, original_value_, interpolator->Clone()));\n  }\n  if (keyframes_.back()->GetFraction() < 1) {\n    auto* interpolator = keyframes_.back()->GetInterpolator();\n    AddKeyframe(\n        ColorKeyframe::Create(1.f, original_value_, interpolator->Clone()));\n  }\n  if (auto manager = GetKeyframesManager()) {\n    float current_fraction = animation.GetAnimatedFraction();\n    manager->GetTarget()->SetProperty(Type(), GetValue(current_fraction), true);\n  }\n}\n\nColor ColorKeyframeSet::GetValue(float fraction) const {\n  return clay::GetValue(fraction, keyframes_);\n}\n\n#ifndef NDEBUG\nstd::string ColorKeyframeSet::ToString() const {\n  std::ostringstream os;\n  os << \"ColorKeyframeSet: size=\" << keyframes_.size()\n     << \" type=\" << static_cast<int>(Type()) << std::endl;\n  for (const auto& keyframe : keyframes_) {\n    os << \"\\t\" << keyframe->ToString() << std::endl;\n  }\n  return os.str();\n}\n#endif\n\nstd::unique_ptr<RawTransformKeyframeSet> RawTransformKeyframeSet::Create() {\n  return std::unique_ptr<RawTransformKeyframeSet>(\n      new RawTransformKeyframeSet());\n}\n\nRawTransformKeyframeSet::RawTransformKeyframeSet()\n    : KeyframeSet(ClayAnimationPropertyType::kTransform) {}\n\nvoid RawTransformKeyframeSet::AddKeyframe(\n    std::unique_ptr<RawTransformKeyframe> keyframe) {\n  for (const auto& op : keyframe->Operations()) {\n    switch (op.type) {\n      case ClayTransformType::kTranslateX:\n        has_percentage_values_ |=\n            op.unit[0] == ClayPlatformLengthUnit::kPercentage;\n        break;\n      case ClayTransformType::kTranslateY:\n        has_percentage_values_ |=\n            op.unit[1] == ClayPlatformLengthUnit::kPercentage;\n        break;\n      case ClayTransformType::kTranslate:\n      case ClayTransformType::kTranslate3d:\n        has_percentage_values_ |=\n            op.unit[0] == ClayPlatformLengthUnit::kPercentage;\n        has_percentage_values_ |=\n            op.unit[1] == ClayPlatformLengthUnit::kPercentage;\n        break;\n      default:\n        FML_LOG(ERROR) << \"Unsupported transform type \"\n                       << static_cast<int>(op.type);\n        break;\n    }\n  }\n\n  InsertKeyframe(std::move(keyframe), &keyframes_);\n}\n\nstd::unique_ptr<KeyframeSet> RawTransformKeyframeSet::Clone(\n    KeyframesManager* manager) const {\n  std::unique_ptr<TransformKeyframeSet> to_return =\n      TransformKeyframeSet::Create(Type());\n  to_return->SetKeyframesManager(manager);\n  for (const auto& keyframe : keyframes_) {\n    ClayTransform transform;\n    transform.op = const_cast<ClayTransformOP*>(keyframe->Operations().data());\n    transform.size = keyframe->Operations().size();\n    to_return->AddKeyframe(TransformKeyframe::Create(\n        keyframe->GetFraction(),\n        TransformOperations(transform,\n                            manager->GetTarget()->PercentageResolutionSize()),\n        keyframe->GetInterpolator()->Clone()));\n  }\n\n  return to_return;\n}\n\n#ifndef NDEBUG\nstd::string RawTransformKeyframeSet::ToString() const {\n  std::ostringstream os;\n  os << \"RawTransformKeyframeSet: size=\" << keyframes_.size()\n     << \" type=\" << static_cast<int>(Type()) << std::endl;\n  for (const auto& keyframe : keyframes_) {\n    os << \"\\t\" << keyframe->ToString() << std::endl;\n  }\n  return os.str();\n}\n#endif\n\nstd::unique_ptr<TransformKeyframeSet> TransformKeyframeSet::Create(\n    ClayAnimationPropertyType property_type) {\n  return std::unique_ptr<TransformKeyframeSet>(\n      new TransformKeyframeSet(property_type));\n}\n\nTransformKeyframeSet::TransformKeyframeSet(\n    ClayAnimationPropertyType property_type)\n    : KeyframeSet(property_type) {}\n\nTransformKeyframeSet::~TransformKeyframeSet() = default;\n\nvoid TransformKeyframeSet::AddKeyframe(\n    std::unique_ptr<TransformKeyframe> keyframe) {\n  InsertKeyframe(std::move(keyframe), &keyframes_);\n}\n\nstd::unique_ptr<KeyframeSet> TransformKeyframeSet::Clone(\n    KeyframesManager* manager) const {\n  std::unique_ptr<TransformKeyframeSet> to_return =\n      TransformKeyframeSet::Create(Type());\n  to_return->SetKeyframesManager(manager);\n  for (const auto& keyframe : keyframes_) {\n    to_return->AddKeyframe(keyframe->Clone());\n  }\n\n  return to_return;\n}\n\nvoid TransformKeyframeSet::OnAnimationStart(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->GetProperty(Type(), original_value_);\n  }\n}\n\nvoid TransformKeyframeSet::OnAnimationRemove(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->SetProperty(Type(), original_value_, false);\n  }\n}\n\nvoid TransformKeyframeSet::OnAnimationUpdate(ValueAnimator& animation) {\n  if (keyframes_.front()->GetFraction() > 0) {\n    auto* interpolator = keyframes_.front()->GetInterpolator();\n    AddKeyframe(\n        TransformKeyframe::Create(0.f, original_value_, interpolator->Clone()));\n  }\n  if (keyframes_.back()->GetFraction() < 1) {\n    auto* interpolator = keyframes_.back()->GetInterpolator();\n    AddKeyframe(\n        TransformKeyframe::Create(1.f, original_value_, interpolator->Clone()));\n  }\n  if (auto manager = GetKeyframesManager()) {\n    float current_fraction = animation.GetAnimatedFraction();\n    manager->GetTarget()->SetProperty(Type(), GetValue(current_fraction), true);\n  }\n}\n\nTransformOperations TransformKeyframeSet::GetValue(float fraction) const {\n  return clay::GetValue(fraction, keyframes_);\n}\n\n#ifndef NDEBUG\nstd::string TransformKeyframeSet::ToString() const {\n  std::ostringstream os;\n  os << \"TransformKeyframeSet: size=\" << keyframes_.size()\n     << \" type=\" << static_cast<int>(Type()) << std::endl;\n  for (const auto& keyframe : keyframes_) {\n    os << \"\\t\" << keyframe->ToString() << std::endl;\n  }\n  return os.str();\n}\n#endif\n\nstd::unique_ptr<KeyframeSet> FilterKeyframeSet::Clone(\n    KeyframesManager* manager) const {\n  std::unique_ptr<FilterKeyframeSet> to_return = FilterKeyframeSet::Create();\n  to_return->SetKeyframesManager(manager);\n  for (const auto& keyframe : keyframes_) {\n    to_return->AddKeyframe(\n        FilterKeyframe::Create(keyframe->GetFraction(), keyframe->Value(),\n                               keyframe->GetInterpolator()->Clone()));\n  }\n  return to_return;\n}\n\nstd::unique_ptr<FilterKeyframeSet> FilterKeyframeSet::Create() {\n  return std::unique_ptr<FilterKeyframeSet>(new FilterKeyframeSet());\n}\n\nFilterKeyframeSet::FilterKeyframeSet()\n    : KeyframeSet(ClayAnimationPropertyType::kFilter) {}\n\nFilterKeyframeSet::~FilterKeyframeSet() = default;\n\n#ifndef NDEBUG\nstd::string FilterKeyframeSet::ToString() const { return \"FilterKeyframeSet\"; }\n#endif\n\n// AnimatorListenerAdapter overrides\nvoid FilterKeyframeSet::OnAnimationStart(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->GetProperty(Type(), original_value_);\n  }\n}\nvoid FilterKeyframeSet::OnAnimationUpdate(ValueAnimator& animation) {\n  if (keyframes_.front()->GetFraction() > 0) {\n    auto* interpolator = keyframes_.front()->GetInterpolator();\n    AddKeyframe(\n        FilterKeyframe::Create(0.f, original_value_, interpolator->Clone()));\n  }\n  if (keyframes_.back()->GetFraction() < 1) {\n    auto* interpolator = keyframes_.back()->GetInterpolator();\n    AddKeyframe(\n        FilterKeyframe::Create(1.f, original_value_, interpolator->Clone()));\n  }\n  if (auto manager = GetKeyframesManager()) {\n    float current_fraction = animation.GetAnimatedFraction();\n    manager->GetTarget()->SetProperty(Type(), GetValue(current_fraction));\n  }\n}\nvoid FilterKeyframeSet::OnAnimationRemove(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->SetProperty(Type(), original_value_);\n  }\n}\n\nFilterOperations FilterKeyframeSet::GetValue(float fraction) const {\n  return clay::GetValue(fraction, keyframes_);\n}\n\nvoid FilterKeyframeSet::AddKeyframe(std::unique_ptr<FilterKeyframe> keyframe) {\n  InsertKeyframe(std::move(keyframe), &keyframes_);\n}\n\nstd::unique_ptr<BoxShadowKeyframeSet> BoxShadowKeyframeSet::Create() {\n  return std::unique_ptr<BoxShadowKeyframeSet>(new BoxShadowKeyframeSet());\n}\n\nBoxShadowKeyframeSet::BoxShadowKeyframeSet()\n    : KeyframeSet(ClayAnimationPropertyType::kBoxShadow) {}\n\nBoxShadowKeyframeSet::~BoxShadowKeyframeSet() = default;\n\nstd::unique_ptr<KeyframeSet> BoxShadowKeyframeSet::Clone(\n    KeyframesManager* manager) const {\n  std::unique_ptr<BoxShadowKeyframeSet> to_return =\n      BoxShadowKeyframeSet::Create();\n  to_return->SetKeyframesManager(manager);\n  for (const auto& keyframe : keyframes_) {\n    to_return->AddKeyframe(\n        BoxShadowKeyframe::Create(keyframe->GetFraction(), keyframe->Value(),\n                                  keyframe->GetInterpolator()->Clone()));\n  }\n  return to_return;\n}\n\n#ifndef NDEBUG\nstd::string BoxShadowKeyframeSet::ToString() const {\n  return \"BoxShadowKeyframeSet\";\n}\n#endif\n\nvoid BoxShadowKeyframeSet::OnAnimationStart(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->GetProperty(Type(), original_value_);\n  }\n}\nvoid BoxShadowKeyframeSet::OnAnimationUpdate(ValueAnimator& animation) {\n  if (keyframes_.front()->GetFraction() > 0) {\n    auto* interpolator = keyframes_.front()->GetInterpolator();\n    AddKeyframe(\n        BoxShadowKeyframe::Create(0.f, original_value_, interpolator->Clone()));\n  }\n  if (keyframes_.back()->GetFraction() < 1) {\n    auto* interpolator = keyframes_.back()->GetInterpolator();\n    AddKeyframe(\n        BoxShadowKeyframe::Create(1.f, original_value_, interpolator->Clone()));\n  }\n  if (auto manager = GetKeyframesManager()) {\n    float current_fraction = animation.GetAnimatedFraction();\n    manager->GetTarget()->SetProperty(Type(), GetValue(current_fraction));\n  }\n}\nvoid BoxShadowKeyframeSet::OnAnimationRemove(Animator& animation) {\n  if (auto manager = GetKeyframesManager()) {\n    manager->GetTarget()->SetProperty(Type(), original_value_);\n  }\n}\n\nBoxShadowOperations BoxShadowKeyframeSet::GetValue(float fraction) const {\n  return clay::GetValue(fraction, keyframes_);\n}\n\nvoid BoxShadowKeyframeSet::AddKeyframe(\n    std::unique_ptr<BoxShadowKeyframe> keyframe) {\n  InsertKeyframe(std::move(keyframe), &keyframes_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/keyframe_set.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_KEYFRAME_SET_H_\n#define CLAY_GFX_ANIMATION_KEYFRAME_SET_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/gfx/animation/animator_listener_adapter.h\"\n#include \"clay/gfx/animation/keyframe.h\"\n#include \"clay/gfx/geometry/box_shadow_operations.h\"\n#include \"clay/gfx/geometry/filter_operations.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass KeyframesManager;\n\nclass KeyframeSet : public AnimatorListenerAdapter {\n public:\n  KeyframeSet(const KeyframeSet&) = delete;\n  KeyframeSet& operator=(const KeyframeSet&) = delete;\n\n  virtual ~KeyframeSet();\n\n  ClayAnimationPropertyType Type() const { return property_type_; }\n\n  // Create an instance of the keyframes and associate a manager to it. This\n  // will resolve the percentage size (if any)\n  virtual std::unique_ptr<KeyframeSet> Clone(\n      KeyframesManager* manager) const = 0;\n\n#ifndef NDEBUG\n  virtual std::string ToString() const = 0;\n#endif\n\n  // Return true if there is any percentage value. Currently only\n  // `RawTransformKeyframeSet` may return true\n  virtual bool HasPercentageValues() const { return false; }\n\n  void SetKeyframesManager(KeyframesManager* keyframes_manager);\n  KeyframesManager* GetKeyframesManager() const { return keyframes_manager_; }\n\n protected:\n  explicit KeyframeSet(ClayAnimationPropertyType property_type);\n\n private:\n  // The name of the property associated with keyframes_.\n  ClayAnimationPropertyType property_type_;\n  KeyframesManager* keyframes_manager_ = nullptr;\n};\n\nclass FloatKeyframeSet : public KeyframeSet {\n public:\n  FloatKeyframeSet(const FloatKeyframeSet&) = delete;\n  FloatKeyframeSet& operator=(const FloatKeyframeSet&) = delete;\n\n  static std::unique_ptr<FloatKeyframeSet> Create(\n      ClayAnimationPropertyType property_type);\n  ~FloatKeyframeSet() override;\n\n  std::unique_ptr<KeyframeSet> Clone(KeyframesManager* manager) const override;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n  // AnimatorListenerAdapter overrides\n  void OnAnimationStart(Animator& animation) override;\n  void OnAnimationUpdate(ValueAnimator& animation) override;\n  void OnAnimationRemove(Animator& animation) override;\n  /**\n   * Gets the animated value, given the elapsed fraction of the animation\n   * (interpolated by the animation's interpolator) and the evaluator used\n   * to calculate in-between values.\n   *\n   * @param fraction The elapsed fraction of the animation\n   * @return The animated value.\n   */\n  float GetValue(float fraction) const;\n\n  void AddKeyframe(std::unique_ptr<FloatKeyframe> keyframe);\n\n  using Keyframes = std::vector<std::unique_ptr<FloatKeyframe>>;\n\n private:\n  FRIEND_TEST(KeyFrameTest, DefaultStartAndEnd);\n  FRIEND_TEST(KeyFrameTest, UpdateAnimation);\n\n  explicit FloatKeyframeSet(ClayAnimationPropertyType property_type);\n\n  // Always sorted in order of increasing time. No two keyframes have the\n  // same time.\n  Keyframes keyframes_;\n\n  float original_value_ = 0.f;\n};\n\nclass ColorKeyframeSet : public KeyframeSet {\n public:\n  ColorKeyframeSet(const ColorKeyframeSet&) = delete;\n  ColorKeyframeSet& operator=(const ColorKeyframeSet&) = delete;\n\n  static std::unique_ptr<ColorKeyframeSet> Create(\n      ClayAnimationPropertyType property_type);\n  ~ColorKeyframeSet() override;\n\n  std::unique_ptr<KeyframeSet> Clone(KeyframesManager* manager) const override;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n  // AnimatorListenerAdapter overrides\n  void OnAnimationStart(Animator& animation) override;\n  void OnAnimationUpdate(ValueAnimator& animation) override;\n  void OnAnimationRemove(Animator& animation) override;\n\n  /**\n   * Gets the animated value, given the elapsed fraction of the animation\n   * (interpolated by the animation's interpolator) and the evaluator used\n   * to calculate in-between values.\n   *\n   * @param fraction The elapsed fraction of the animation\n   * @return The animated value.\n   */\n  Color GetValue(float fraction) const;\n\n  void AddKeyframe(std::unique_ptr<ColorKeyframe> keyframe);\n\n  using Keyframes = std::vector<std::unique_ptr<ColorKeyframe>>;\n\n private:\n  explicit ColorKeyframeSet(ClayAnimationPropertyType property_type);\n\n  // Always sorted in order of increasing time. No two keyframes have the\n  // same time.\n  Keyframes keyframes_;\n\n  Color original_value_ = Color();\n};\n\n// This class is only used to store the raw data of transform operations\n// (without resolving percentage size) in the `PageView::keyframes_data_`. The\n// real animations is executed on the `TransformKeyframeSet` objects which can\n// be created by calling the `Clone` method of this class.\nclass RawTransformKeyframeSet : public KeyframeSet {\n public:\n  static std::unique_ptr<RawTransformKeyframeSet> Create();\n\n  std::unique_ptr<KeyframeSet> Clone(KeyframesManager* manager) const override;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n  bool HasPercentageValues() const override { return has_percentage_values_; }\n\n  void AddKeyframe(std::unique_ptr<RawTransformKeyframe> keyframe);\n\n  using Keyframes = std::vector<std::unique_ptr<RawTransformKeyframe>>;\n\n private:\n  RawTransformKeyframeSet();\n\n  Keyframes keyframes_;\n  bool has_percentage_values_ = false;\n};\n\nclass TransformKeyframeSet : public KeyframeSet {\n public:\n  TransformKeyframeSet(const TransformKeyframeSet&) = delete;\n  TransformKeyframeSet& operator=(const TransformKeyframeSet&) = delete;\n\n  static std::unique_ptr<TransformKeyframeSet> Create(\n      ClayAnimationPropertyType property_type);\n  ~TransformKeyframeSet() override;\n\n  std::unique_ptr<KeyframeSet> Clone(KeyframesManager* manager) const override;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n  // AnimatorListenerAdapter overrides\n  void OnAnimationStart(Animator& animation) override;\n  void OnAnimationUpdate(ValueAnimator& animation) override;\n  void OnAnimationRemove(Animator& animation) override;\n\n  /**\n   * Gets the animated value, given the elapsed fraction of the animation\n   * (interpolated by the animation's interpolator) and the evaluator used\n   * to calculate in-between values.\n   *\n   * @param fraction The elapsed fraction of the animation\n   * @return The animated value.\n   */\n  TransformOperations GetValue(float fraction) const;\n\n  void AddKeyframe(std::unique_ptr<TransformKeyframe> keyframe);\n\n  using Keyframes = std::vector<std::unique_ptr<TransformKeyframe>>;\n\n private:\n  explicit TransformKeyframeSet(ClayAnimationPropertyType property_type);\n\n  // Always sorted in order of increasing time. No two keyframes have the\n  // same time.\n  Keyframes keyframes_;\n\n  TransformOperations original_value_ = TransformOperations();\n};\n\nclass FilterKeyframeSet : public KeyframeSet {\n public:\n  FilterKeyframeSet(const FilterKeyframeSet&) = delete;\n  FilterKeyframeSet& operator=(const FilterKeyframeSet&) = delete;\n\n  static std::unique_ptr<FilterKeyframeSet> Create();\n  ~FilterKeyframeSet() override;\n\n  std::unique_ptr<KeyframeSet> Clone(KeyframesManager* manager) const override;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n  // AnimatorListenerAdapter overrides\n  void OnAnimationStart(Animator& animation) override;\n  void OnAnimationUpdate(ValueAnimator& animation) override;\n  void OnAnimationRemove(Animator& animation) override;\n\n  FilterOperations GetValue(float fraction) const;\n\n  void AddKeyframe(std::unique_ptr<FilterKeyframe> keyframe);\n\n  using Keyframes = std::vector<std::unique_ptr<FilterKeyframe>>;\n\n private:\n  explicit FilterKeyframeSet();\n\n  // Always sorted in order of increasing time. No two keyframes have the\n  // same time.\n  Keyframes keyframes_;\n\n  FilterOperations original_value_ = FilterOperations();\n};\n\nclass BoxShadowKeyframeSet : public KeyframeSet {\n public:\n  BoxShadowKeyframeSet(const BoxShadowKeyframeSet&) = delete;\n  BoxShadowKeyframeSet& operator=(const BoxShadowKeyframeSet&) = delete;\n\n  static std::unique_ptr<BoxShadowKeyframeSet> Create();\n  ~BoxShadowKeyframeSet() override;\n\n  std::unique_ptr<KeyframeSet> Clone(KeyframesManager* manager) const override;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n  // AnimatorListenerAdapter overrides\n  void OnAnimationStart(Animator& animation) override;\n  void OnAnimationUpdate(ValueAnimator& animation) override;\n  void OnAnimationRemove(Animator& animation) override;\n\n  BoxShadowOperations GetValue(float fraction) const;\n\n  void AddKeyframe(std::unique_ptr<BoxShadowKeyframe> keyframe);\n\n  using Keyframes = std::vector<std::unique_ptr<BoxShadowKeyframe>>;\n\n private:\n  explicit BoxShadowKeyframeSet();\n\n  // Always sorted in order of increasing time. No two keyframes have the\n  // same time.\n  Keyframes keyframes_;\n\n  BoxShadowOperations original_value_ = BoxShadowOperations();\n};\n\nusing KeyframesMap =\n    std::unordered_map<ClayAnimationPropertyType, std::unique_ptr<KeyframeSet>>;\nusing KeyframesMapData = std::unordered_map<std::string, KeyframesMap>;\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_KEYFRAME_SET_H_\n"
  },
  {
    "path": "clay/gfx/animation/keyframes_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/keyframes_manager.h\"\n\n#include <utility>\n\n#include \"clay/gfx/animation/animation_properties_util.h\"\n#include \"clay/gfx/animation/animator_target.h\"\n#include \"clay/public/clay.h\"\n\nnamespace clay {\n\nnamespace {\n\n// Compare animation data properties except play_state\nbool IsSame(const AnimationData& lhs, const AnimationData& rhs) {\n  return std::tie(lhs.name, lhs.timing_func, lhs.iteration_count, lhs.fill_mode,\n                  lhs.duration, lhs.delay, lhs.direction) ==\n         std::tie(rhs.name, rhs.timing_func, rhs.iteration_count, rhs.fill_mode,\n                  rhs.duration, rhs.delay, rhs.direction);\n}\n\n// Returns true when the view has both raster animation and UI animation\nbool IsMixedAnimation(const KeyframesManager::KeyframeAnimation& animation) {\n  bool has_raster_animation = false;\n  bool has_ui_animation = false;\n  for (auto& [type, keyframe] : animation.keyframes_map) {\n    if (IsRasterAnimationProperty(type)) {\n      has_raster_animation = true;\n    } else {\n      has_ui_animation = true;\n    }\n    if (has_raster_animation && has_ui_animation) {\n      return true;\n    }\n  }\n  return false;\n}\n\n}  // namespace\n\nKeyframesManager::KeyframesManager(AnimatorTarget* target) : target_(target) {}\n\nKeyframesManager::~KeyframesManager() { CancelAllAnimators(); }\n\nKeyframesManager::UpdateDataResult KeyframesManager::UpdateData(\n    const std::vector<AnimationData>& data) {\n  bool data_has_changed = data.size() != animations_.size();\n  for (size_t i = 0; i < data.size(); ++i) {\n    if (data_has_changed) {\n      break;\n    }\n    data_has_changed = !IsSame(data[i], animations_[i].data);\n  }\n  if (!data_has_changed) {\n    // Check if play_state has changed.\n    bool play_state_has_changed = false;\n    for (size_t i = 0; i < data.size(); ++i) {\n      if (data[i].play_state == animations_[i].data.play_state) {\n        continue;\n      }\n      if (data[i].play_state == ClayAnimationPlayStateType::kPaused) {\n        animations_[i].animator->Pause();\n      } else if (data[i].play_state == ClayAnimationPlayStateType::kRunning) {\n        animations_[i].animator->Resume();\n      }\n      animations_[i].data.play_state = data[i].play_state;\n      play_state_has_changed = true;\n    }\n    return {false, play_state_has_changed};\n  }\n\n  StartAnimations(data);\n  return {true, true};\n}\n\nbool KeyframesManager::HasAnimationForType(\n    ClayAnimationPropertyType type) const {\n  for (auto& animation : animations_) {\n    if (animation.keyframes_map.find(type) != animation.keyframes_map.end()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool KeyframesManager::StartListenersNotified(\n    ClayAnimationPropertyType type) const {\n  for (auto& animation : animations_) {\n    if (auto iter = animation.keyframes_map.find(type);\n        iter != animation.keyframes_map.end()) {\n      return !animation.animator->StartListenersCalled();\n    }\n  }\n  return true;\n}\n\nvoid KeyframesManager::SyncProperties(KeyframesManager* manager) {\n  if (manager == nullptr) {\n    return;\n  }\n  // For every type of raster animation, `KeyframesManager` will be cloned each\n  // time. And we don't need to compare type here. Besides when raster\n  // animation get changed in its process (e.g. a new animation with a\n  // longer duration) ,it won't invoke new animation event (e.g.\n  // onAnimationStart).\n  for (auto& animation : animations_) {\n    for (auto& kv : animation.keyframes_map) {\n      animation.animator->SetStartListenersCalled(\n          manager->StartListenersNotified(kv.first));\n    }\n  }\n}\n\nstd::unique_ptr<KeyframesManager> KeyframesManager::CloneForRasterAnimation(\n    ClayAnimationPropertyType type, AnimatorTarget* target) const {\n  std::unique_ptr<KeyframesManager> clone =\n      std::make_unique<KeyframesManager>(target);\n  for (auto& animation : animations_) {\n    if (auto iter = animation.keyframes_map.find(type);\n        iter != animation.keyframes_map.end()) {\n      KeyframeAnimation clone_animation;\n      clone_animation.animator = animation.animator->Clone();\n      std::unique_ptr<KeyframeSet> clone_keyframe_set =\n          iter->second->Clone(clone.get());\n      if (!IsMixedAnimation(animation)) {\n        clone_animation.animator->AddListener(clone_keyframe_set.get());\n      }\n      clone_animation.listener =\n          std::make_unique<KeyframeListener>(clone.get());\n      clone_animation.animator->AddListener(clone_animation.listener.get());\n      clone_animation.animator->AddUpdateListener(clone_keyframe_set.get());\n      clone_animation.keyframes_map.emplace(type,\n                                            std::move(clone_keyframe_set));\n      clone->animations_.emplace_back(std::move(clone_animation));\n    }\n  }\n  return clone;\n}\n\nvoid KeyframesManager::UpdateAnimator(ValueAnimator* animator,\n                                      AnimationData data) {\n  animator->SetAnimationData(data);\n  if (data.play_state == ClayAnimationPlayStateType::kPaused) {\n    animator->Pause();\n  } else if (data.play_state == ClayAnimationPlayStateType::kRunning) {\n    animator->Resume();\n  }\n  animator->AddAnimationCallback(0);\n}\n\nvoid KeyframesManager::StartAnimations(const std::vector<AnimationData>& data) {\n  std::vector<KeyframeAnimation> new_animations;\n\n  for (const auto& item : data) {\n    bool add_new_animation = true;\n    auto it = animations_.begin();\n    // For new animations and old animations of the same name we will only\n    // update instead of reset,  for animations with different names we will\n    // cancel the old animation and start the new one.\n    while (it != animations_.end()) {\n      if (it->data.name == item.name) {\n        it->SetAnimationData(item);\n        UpdateAnimator(it->animator.get(), item);\n        add_new_animation = false;\n        new_animations.push_back(std::move(*it));\n        it = animations_.erase(it);\n        break;\n      } else {\n        it++;\n      }\n    }\n\n    if (add_new_animation) {\n      KeyframeAnimation animation;\n      animation.data = item;\n      animation.animator = CreateAnimator(item);\n      animation.listener = std::make_unique<KeyframeListener>(this);\n      animation.animator->AddListener(animation.listener.get());\n      if (!InitKeyframesMap(item, animation.animator.get(),\n                            animation.keyframes_map,\n                            &animation.has_percentage_values)) {\n        continue;\n      }\n      new_animations.push_back(std::move(animation));\n    }\n  }\n\n  CancelAllAnimators();\n\n  for (const auto& animation : new_animations) {\n    if (animation.animator->IsRunning()) {\n      continue;\n    }\n    animation.animator->Start();\n    if (animation.data.play_state == ClayAnimationPlayStateType::kPaused) {\n      animation.animator->Pause();\n    }\n  }\n  animations_ = std::move(new_animations);\n}\n\nstd::unique_ptr<ValueAnimator> KeyframesManager::CreateAnimator(\n    const AnimationData& data) {\n  std::unique_ptr<ValueAnimator> animator =\n      std::make_unique<ValueAnimator>(data);\n  animator->SetAnimationHandler(target_->GetAnimationHandler());\n  return animator;\n}\n\nbool KeyframesManager::InitKeyframesMap(const AnimationData& data,\n                                        ValueAnimator* animator,\n                                        KeyframesMap& keyframes_map,\n                                        bool* out_has_percentage_values) {\n  const KeyframesMap* src_map = target_->GetKeyframesMap(data.name);\n  if (!src_map) {\n    return false;\n  }\n\n  for (const auto& keyframes : keyframes_map) {\n    animator->RemoveUpdateListener(keyframes.second.get());\n    animator->RemoveListener(keyframes.second.get());\n  }\n  keyframes_map.clear();\n\n  bool has_percentage_values = false;\n  for (const auto& keyframes : *src_map) {\n    has_percentage_values |= keyframes.second->HasPercentageValues();\n    std::unique_ptr<KeyframeSet> clone = keyframes.second->Clone(this);\n    animator->AddUpdateListener(clone.get());\n    animator->AddListener(clone.get());\n    keyframes_map.emplace(keyframes.first, std::move(clone));\n  }\n\n  if (out_has_percentage_values) {\n    *out_has_percentage_values = has_percentage_values;\n  }\n\n  return !keyframes_map.empty();\n}\n\nbool KeyframesManager::HasAnimationRunning() const {\n  for (const auto& animation : animations_) {\n    if (animation.animator->IsStarted()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid KeyframesManager::EndAllAnimators() {\n  for (const auto& animation : animations_) {\n    if (animation.animator->IsStarted()) {\n      animation.animator->End();\n    }\n  }\n}\n\nvoid KeyframesManager::EndAnimator(const std::string& name) {\n  for (const auto& animation : animations_) {\n    if (animation.data.name == name && animation.animator->IsStarted()) {\n      animation.animator->End();\n    }\n  }\n}\n\nvoid KeyframesManager::CancelAllAnimators() {\n  for (const auto& animation : animations_) {\n    if (animation.animator->IsStarted()) {\n      animation.animator->Cancel();\n    }\n  }\n}\n\nvoid KeyframesManager::CancelAnimator(const std::string& name) {\n  for (const auto& animation : animations_) {\n    if (animation.data.name == name && animation.animator->IsStarted()) {\n      animation.animator->Cancel();\n    }\n  }\n}\nvoid KeyframesManager::SetEventHandler(AnimationEventHandler* event_handler) {\n  event_handler_ = event_handler;\n}\nvoid KeyframesManager::OnAnimationStart(const Animator& animation) {\n  if (event_handler_) {\n    const std::string& animation_name = animation.GetAnimationName();\n    AnimationParams animation_params{\n        ClayEventType::kClayEventTypeAnimationStart, animation_name.c_str()};\n    event_handler_->OnAnimationEvent(animation_params);\n  }\n}\nvoid KeyframesManager::OnAnimationRepeat(const Animator& animation) {\n  if (event_handler_) {\n    const std::string& animation_name = animation.GetAnimationName();\n    AnimationParams animation_params{\n        ClayEventType::kClayEventTypeAnimationRepeat, animation_name.c_str()};\n    event_handler_->OnAnimationEvent(animation_params);\n  }\n}\nvoid KeyframesManager::OnAnimationEnd(const Animator& animation) {\n  if (event_handler_) {\n    const std::string& animation_name = animation.GetAnimationName();\n    AnimationParams animation_params{ClayEventType::kClayEventTypeAnimationEnd,\n                                     animation_name.c_str()};\n    event_handler_->OnAnimationEvent(animation_params);\n  }\n}\nvoid KeyframesManager::OnAnimationCancel(const Animator& animation) {\n  if (event_handler_) {\n    const std::string& animation_name = animation.GetAnimationName();\n    AnimationParams animation_params{\n        ClayEventType::kClayEventTypeAnimationCancel, animation_name.c_str()};\n    event_handler_->OnAnimationEvent(animation_params);\n  }\n}\n\nvoid KeyframesManager::UpdateLayoutSize() {\n  for (auto& animation : animations_) {\n    if (animation.has_percentage_values) {\n      // We should re-init the keyframes of the animation if it has percentage\n      // values and the element size has changed\n      InitKeyframesMap(animation.data, animation.animator.get(),\n                       animation.keyframes_map);\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/keyframes_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_KEYFRAMES_MANAGER_H_\n#define CLAY_GFX_ANIMATION_KEYFRAMES_MANAGER_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/gfx/animation/animation_event_handler.h\"\n#include \"clay/gfx/animation/animator_listener.h\"\n#include \"clay/gfx/animation/keyframe_set.h\"\n#include \"clay/gfx/animation/value_animator.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass AnimatorTarget;\n\nclass KeyframesManager {\n public:\n  explicit KeyframesManager(AnimatorTarget* target);\n  ~KeyframesManager();\n\n  struct UpdateDataResult {\n    bool data_has_changed;\n    bool state_has_changed;\n  };\n\n  // Return true if data has changed\n  UpdateDataResult UpdateData(const std::vector<AnimationData>& data);\n  bool HasAnimationRunning() const;\n  void EndAllAnimators();\n  void EndAnimator(const std::string& name);\n  void CancelAllAnimators();\n  void CancelAnimator(const std::string& name);\n\n  AnimatorTarget* GetTarget() const { return target_; }\n  void SetEventHandler(AnimationEventHandler* event_handler);\n\n  void OnAnimationStart(const Animator& animation);\n  void OnAnimationRepeat(const Animator& animation);\n  void OnAnimationEnd(const Animator& animator);\n  void OnAnimationCancel(const Animator& animator);\n\n  // If there are some animations with percentage value, we need to recalculate\n  // these values.\n  void UpdateLayoutSize();\n\n  struct KeyframeAnimation {\n   public:\n    void SetAnimationData(AnimationData animation_data) {\n      data = animation_data;\n    }\n    AnimationData data;\n    KeyframesMap keyframes_map;\n    std::unique_ptr<ValueAnimator> animator;\n    std::unique_ptr<AnimatorListener> listener;\n    bool has_percentage_values;\n  };\n\n  bool HasAnimationForType(ClayAnimationPropertyType type) const;\n\n  std::unique_ptr<KeyframesManager> CloneForRasterAnimation(\n      ClayAnimationPropertyType type, AnimatorTarget* target) const;\n\n  // whether the animator of a certain type has called its `OnAnimationStart`.\n  bool StartListenersNotified(ClayAnimationPropertyType type) const;\n\n  const std::vector<KeyframeAnimation>& animations() const {\n    return animations_;\n  }\n\n  void UpdateAnimator(ValueAnimator* animator, AnimationData data);\n\n  // Avoid access to KeyframesManager of AnimationLayer in UI Thread, may cause\n  // multi-threading issues.\n  void SyncProperties(KeyframesManager* manager);\n\n private:\n  void StartAnimations(const std::vector<AnimationData>& data);\n  std::unique_ptr<ValueAnimator> CreateAnimator(const AnimationData& data);\n  bool InitKeyframesMap(const AnimationData& data, ValueAnimator* animator,\n                        KeyframesMap& keyframes_map,\n                        bool* out_has_percentage_values = nullptr);\n\n  FRIEND_TEST(KeyFrameTest, DefaultStartAndEnd);\n  FRIEND_TEST(KeyFrameTest, UpdateAnimation);\n  FRIEND_TEST(KeyFrameTest, ChangeFillmode);\n  FRIEND_TEST(KeyFrameTest, AnimationDelay);\n  FRIEND_TEST(KeyFrameTest, AnimationDelayCombineForwards);\n  FRIEND_TEST(KeyFrameTest, AnimationDelayCombineBackwards);\n  FRIEND_TEST(KeyFrameTest, AnimationStartEvent);\n  FRIEND_TEST(KeyFrameTest, AnimationCancelEvent);\n  FRIEND_TEST(KeyFrameTest, AnimationEndEvent);\n\n  AnimatorTarget* target_;\n  AnimationEventHandler* event_handler_;\n  std::vector<KeyframeAnimation> animations_;\n};\n\nclass KeyframeListener : public AnimatorListenerAdapter {\n public:\n  explicit KeyframeListener(KeyframesManager* mgr) : mgr_(mgr) {}\n  void OnAnimationStart(Animator& animation) override {\n    mgr_->OnAnimationStart(animation);\n  }\n  void OnAnimationEnd(Animator& animation) override {\n    mgr_->OnAnimationEnd(animation);\n  }\n  void OnAnimationCancel(Animator& animation) override {\n    mgr_->OnAnimationCancel(animation);\n  }\n  void OnAnimationRepeat(Animator& animation) override {\n    mgr_->OnAnimationRepeat(animation);\n  }\n\n private:\n  KeyframesManager* mgr_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_KEYFRAMES_MANAGER_H_\n"
  },
  {
    "path": "clay/gfx/animation/lut_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/lut_interpolator.h\"\n\n#include <algorithm>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/type_evaluator.h\"\n\nnamespace clay {\n\nLUTInterpolator::LUTInterpolator(std::unique_ptr<float[]> values, size_t size)\n    : values_(std::move(values)), size_(size) {}\n\nLUTInterpolator::~LUTInterpolator() = default;\n\nstd::unique_ptr<LUTInterpolator> LUTInterpolator::Create(\n    std::unique_ptr<float[]> values, size_t size) {\n  return std::make_unique<LUTInterpolator>(std::move(values), size);\n}\n\nstd::unique_ptr<Interpolator> LUTInterpolator::Clone() {\n  std::unique_ptr<float[]> values = std::make_unique<float[]>(size_);\n  for (size_t i = 0; i < size_; i++) {\n    values[i] = values_[i];\n  }\n  return LUTInterpolator::Create(std::move(values), size_);\n}\n\nfloat LUTInterpolator::Interpolate(float input) {\n  // lut position should only be at the end of the table when input is 1f.\n  float lut_pos = input * (size_ - 1);\n  if (lut_pos >= (size_ - 1)) {\n    return values_[size_ - 1];\n  }\n\n  float part, weight;\n  weight = modff(lut_pos, &part);\n\n  int i1 = static_cast<int>(part);\n  int i2 = std::min(i1 + 1, static_cast<int>(size_) - 1);\n\n  FML_DCHECK(i1 < 0 || i2 < 0) << \"negatives in interpolation!\";\n\n  float v1 = values_[i1];\n  float v2 = values_[i2];\n\n  return TypeEvaluator<float>::Evaluate(weight, v1, v2);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/lut_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_LUT_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_LUT_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass LUTInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<LUTInterpolator> Create(\n      std::unique_ptr<float[]> values, size_t size);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  LUTInterpolator(std::unique_ptr<float[]> values, size_t size);\n  ~LUTInterpolator();\n\n  float Interpolate(float input) override;\n\n private:\n  std::unique_ptr<float[]> values_;\n  size_t size_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_LUT_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/overshoot_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/overshoot_interpolator.h\"\n\nnamespace clay {\n\nfloat OvershootInterpolator::Interpolate(float t) {\n  t -= 1.0f;\n  return t * t * ((tension_ + 1) * t + tension_) + 1.0f;\n}\n\nstd::unique_ptr<OvershootInterpolator> OvershootInterpolator::Create(\n    float tension) {\n  return std::make_unique<OvershootInterpolator>(tension);\n}\n\nstd::unique_ptr<Interpolator> OvershootInterpolator::Clone() {\n  return OvershootInterpolator::Create(tension_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/overshoot_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_OVERSHOOT_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_OVERSHOOT_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass OvershootInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<OvershootInterpolator> Create(float tension);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  explicit OvershootInterpolator(float tension) : tension_(tension) {}\n  float Interpolate(float input) override;\n\n private:\n  const float tension_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_OVERSHOOT_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/path_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/path_interpolator.h\"\n\nnamespace clay {\n\nstd::unique_ptr<PathInterpolator> PathInterpolator::Create(\n    std::vector<float>&& x, std::vector<float>&& y) {\n  return std::make_unique<PathInterpolator>(std::move(x), std::move(y));\n}\n\nstd::unique_ptr<Interpolator> PathInterpolator::Clone() {\n  std::vector<float> x = x_;\n  std::vector<float> y = y_;\n  return PathInterpolator::Create(std::move(x), std::move(y));\n}\n\nfloat PathInterpolator::Interpolate(float t) {\n  if (t <= 0) {\n    return 0;\n  } else if (t >= 1) {\n    return 1;\n  }\n  // Do a binary search for the correct x to interpolate between.\n  size_t start_index = 0;\n  size_t end_index = x_.size() - 1;\n\n  while (end_index > start_index + 1) {\n    int mid_index = (start_index + end_index) / 2;\n    if (t < x_[mid_index]) {\n      end_index = mid_index;\n    } else {\n      start_index = mid_index;\n    }\n  }\n\n  float x_range = x_[end_index] - x_[start_index];\n  if (x_range == 0) {\n    return y_[start_index];\n  }\n\n  float t_in_range = t - x_[start_index];\n  float fraction = t_in_range / x_range;\n\n  float start_y = y_[start_index];\n  float end_y = y_[end_index];\n  return start_y + (fraction * (end_y - start_y));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/path_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_PATH_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_PATH_INTERPOLATOR_H_\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nclass PathInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<PathInterpolator> Create(std::vector<float>&& x,\n                                                  std::vector<float>&& y);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  PathInterpolator(std::vector<float>&& x, std::vector<float>&& y)\n      : x_(std::move(x)), y_(std::move(y)) {}\n  float Interpolate(float input) override;\n\n private:\n  std::vector<float> x_;\n  std::vector<float> y_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_PATH_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/picture_animation_type.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_PICTURE_ANIMATION_TYPE_H_\n#define CLAY_GFX_ANIMATION_PICTURE_ANIMATION_TYPE_H_\n\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nenum class DynamicOpType {\n  kNone = 0,\n  kSetBackgroundColor = 1,\n  kSetTextColor = 2\n};\n\n#ifndef ENABLE_SKITY\nusing DynamicOps = std::vector<std::pair<DynamicOpType, int32_t>>;\n#else\nusing DynamicOps =\n    std::vector<std::pair<DynamicOpType, skity::RecordedOpOffset>>;\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_PICTURE_ANIMATION_TYPE_H_\n"
  },
  {
    "path": "clay/gfx/animation/steps_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/steps_interpolator.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nstd::unique_ptr<StepsInterpolator> StepsInterpolator::Create(\n    int steps, ClayStepsType type) {\n  return std::make_unique<StepsInterpolator>(steps, type);\n}\n\nstd::unique_ptr<Interpolator> StepsInterpolator::Clone() {\n  return StepsInterpolator::Create(steps_, type_);\n}\n\nfloat StepsInterpolator::Interpolate(float input) {\n  int state;\n  switch (type_) {\n    case ClayStepsType::kStart:\n      state = static_cast<int>(input * steps_) + 1;\n      if (state > steps_) {\n        state = steps_;\n      }\n      return (static_cast<float>(state)) / steps_;\n    case ClayStepsType::kEnd:\n      state = static_cast<int>(input * steps_);\n      if (state == steps_) {\n        state -= 1;\n      }\n      return (static_cast<float>(state)) / steps_;\n    case ClayStepsType::kJumpBoth:\n      state = static_cast<int>(input * steps_) + 1;\n      if (state > steps_) {\n        state = steps_;\n      }\n      return (static_cast<float>(state)) / (steps_ + 1);\n    case ClayStepsType::kJumpNone:\n      state = static_cast<int>(input * steps_);\n      if (state == steps_) {\n        state -= 1;\n      }\n      return (static_cast<float>(state)) / (steps_ - 1);\n    default:\n      FML_UNREACHABLE();\n      return 0;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/steps_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_STEPS_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_STEPS_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\n\nclass StepsInterpolator : public Interpolator {\n public:\n  static std::unique_ptr<StepsInterpolator> Create(int steps,\n                                                   ClayStepsType type);\n  std::unique_ptr<Interpolator> Clone() override;\n\n  StepsInterpolator(int steps, ClayStepsType type)\n      : steps_(steps), type_(type) {}\n  float Interpolate(float input) override;\n\n private:\n  const int steps_;\n  const ClayStepsType type_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_STEPS_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/timing_function_data.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/timing_function_data.h\"\n\nnamespace clay {\n\nTimingFunctionData::TimingFunctionData()\n    : timing_func(ClayTimingFunctionType::kLinear),\n      x1(0.0f),\n      y1(0.0f),\n      x2(0.0f),\n      y2(0.0f),\n      steps_type(ClayStepsType::kInvalid) {}\n\nvoid TimingFunctionData::Reset() {\n  timing_func = ClayTimingFunctionType::kLinear;\n  x1 = 0.0f;\n  y1 = 0.0f;\n  x2 = 0.0f;\n  y2 = 0.0f;\n  steps_type = ClayStepsType::kInvalid;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/timing_function_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_TIMING_FUNCTION_DATA_H_\n#define CLAY_GFX_ANIMATION_TIMING_FUNCTION_DATA_H_\n\n#include <tuple>\n\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\n\nstruct TimingFunctionData {\n  constexpr static int kIndexType = 0;\n  constexpr static int kIndexX1 = 1;\n  constexpr static int kIndexY1 = 2;\n  constexpr static int kIndexX2 = 3;\n  constexpr static int kIndexY2 = 4;\n  constexpr static int kIndexStepsType = 2;\n\n  ClayTimingFunctionType timing_func;\n  float x1;\n  float y1;\n  float x2;\n  float y2;\n  ClayStepsType steps_type;\n\n  TimingFunctionData();\n  ~TimingFunctionData() = default;\n  void Reset();\n  bool operator==(const TimingFunctionData& rhs) const {\n    return std::tie(timing_func, x1, y1, x2, y2, steps_type) ==\n           std::tie(rhs.timing_func, rhs.x1, rhs.y1, rhs.x2, rhs.y2,\n                    rhs.steps_type);\n  }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_TIMING_FUNCTION_DATA_H_\n"
  },
  {
    "path": "clay/gfx/animation/transition_data.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/transition_data.h\"\n\nnamespace clay {\n\nbool TransitionData::operator==(const TransitionData& rhs) const {\n  return std::tie(duration, delay, property, timing_func) ==\n         std::tie(rhs.duration, rhs.delay, rhs.property, rhs.timing_func);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/transition_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_TRANSITION_DATA_H_\n#define CLAY_GFX_ANIMATION_TRANSITION_DATA_H_\n\n#include \"clay/gfx/animation/timing_function_data.h\"\n\nnamespace clay {\n\nstruct TransitionData {\n  int duration = 0;\n  int delay = 0;\n  ClayAnimationPropertyType property = ClayAnimationPropertyType::kNone;\n  TimingFunctionData timing_func;\n  bool operator==(const TransitionData& rhs) const;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_TRANSITION_DATA_H_\n"
  },
  {
    "path": "clay/gfx/animation/transition_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/transition_manager.h\"\n\n#include <string>\n\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/gfx/animation/animator_listener_adapter.h\"\n#include \"clay/gfx/animation/interpolator.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n\nnamespace clay {\n\nTransitionManager::TransitionManager(AnimatorTarget* target)\n    : target_(target) {}\nTransitionManager::~TransitionManager() { CancelAllAnimators(); }\n\nvoid TransitionManager::AppendData(const TransitionData& data) {\n  EndAllAnimators();\n  data_.push_back(data);\n}\n\nvoid TransitionManager::UpdateData(const std::vector<TransitionData>& data) {\n  EndAllAnimators();\n  data_ = data;\n}\n\nbool TransitionManager::Enabled(ClayAnimationPropertyType type) const {\n  for (auto& transition : data_) {\n    if (static_cast<int>(transition.property) & static_cast<int>(type)) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool TransitionManager::HasAnimationRunning() const {\n  for (const auto& it : active_transitions_) {\n    if (it.second.first->IsStarted()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool TransitionManager::IsAnimationRunning(ClayAnimationPropertyType type) {\n  auto it = active_transitions_.find(type);\n  if (it != active_transitions_.end()) {\n    ValueAnimator* animator = it->second.first.get();\n    return animator->IsStarted();\n  }\n  return false;\n}\n\nstd::vector<ValueAnimator*> TransitionManager::GetRunningAnimators() {\n  std::vector<ValueAnimator*> animators;\n  for (const auto& [type, transition] : active_transitions_) {\n    if (transition.first->IsRunning()) {\n      animators.push_back(transition.first.get());\n    }\n  }\n  return animators;\n}\n\nvoid TransitionManager::EndAllAnimators() {\n  for (auto& it : active_transitions_) {\n    ValueAnimator* animator = it.second.first.get();\n    if (animator->IsStarted()) {\n      animator->End();\n    }\n  }\n  active_transitions_.clear();\n}\n\nvoid TransitionManager::EndAnimator(ClayAnimationPropertyType type) {\n  auto it = active_transitions_.find(type);\n  if (it != active_transitions_.end()) {\n    ValueAnimator* animator = it->second.first.get();\n    if (animator->IsStarted()) {\n      animator->End();\n    }\n  }\n}\n\nvoid TransitionManager::CancelAnimator(ClayAnimationPropertyType type) {\n  auto it = active_transitions_.find(type);\n  if (it != active_transitions_.end()) {\n    ValueAnimator* animator = it->second.first.get();\n    if (animator->IsStarted()) {\n      animator->Cancel();\n    }\n  }\n}\n\nvoid TransitionManager::CancelAllAnimators() {\n  for (auto& it : active_transitions_) {\n    ValueAnimator* animator = it.second.first.get();\n    if (animator->IsStarted()) {\n      animator->Cancel();\n    }\n  }\n  active_transitions_.clear();\n}\n\nvoid TransitionManager::OnAnimationStart(const Animator& animation,\n                                         ClayAnimationPropertyType type) {\n  if (event_handler_) {\n    const std::string& animation_name = animation.GetAnimationName();\n    AnimationParams params{ClayEventType::kClayEventTypeTransitionStart,\n                           animation_name.c_str()};\n    event_handler_->OnTransitionEvent(params, type);\n  }\n}\nvoid TransitionManager::OnAnimationEnd(const Animator& animation,\n                                       ClayAnimationPropertyType type) {\n  if (event_handler_) {\n    const std::string& animation_name = animation.GetAnimationName();\n    AnimationParams params{ClayEventType::kClayEventTypeTransitionEnd,\n                           animation_name.c_str()};\n    event_handler_->OnTransitionEvent(params, type);\n  }\n}\n\nvoid TransitionManager::SetEventHandler(AnimationEventHandler* event_handler) {\n  event_handler_ = event_handler;\n}\n\nbool TransitionManager::StartListenersNotified(\n    ClayAnimationPropertyType type) const {\n  if (auto iter = active_transitions_.find(type);\n      iter != active_transitions_.end()) {\n    return !iter->second.first->StartListenersCalled();\n  }\n  return true;\n}\n\nstd::unique_ptr<TransitionManager> TransitionManager::CloneForRasterAnimation(\n    ClayAnimationPropertyType type, AnimatorTarget* target) const {\n  std::unique_ptr<TransitionManager> clone;\n  if (auto iter = active_transitions_.find(type);\n      iter != active_transitions_.end()) {\n    clone = std::make_unique<TransitionManager>(target);\n    std::unique_ptr<AnimatorListenerAdapter> listener;\n    if (type == ClayAnimationPropertyType::kOpacity) {\n      listener =\n          static_cast<TransitionListener<float>*>(iter->second.second.get())\n              ->CloneForRasterAnimation(clone.get());\n    } else if (type == ClayAnimationPropertyType::kTransform) {\n      listener = static_cast<TransitionListener<TransformOperations>*>(\n                     iter->second.second.get())\n                     ->CloneForRasterAnimation(clone.get());\n    } else if (type == ClayAnimationPropertyType::kBackgroundColor ||\n               type == ClayAnimationPropertyType::kColor) {\n      listener =\n          static_cast<TransitionListener<Color>*>(iter->second.second.get())\n              ->CloneForRasterAnimation(clone.get());\n    } else {\n      return nullptr;\n    }\n    auto animator = iter->second.first->Clone();\n    animator->AddListener(listener.get());\n    animator->AddUpdateListener(listener.get());\n    clone->active_transitions_.emplace(\n        type, std::make_pair(std::move(animator), std::move(listener)));\n  }\n  return clone;\n}\n\nvoid TransitionManager::SyncProperties(TransitionManager* manager) {\n  if (manager == nullptr) {\n    return;\n  }\n  for (auto& transition : active_transitions_) {\n    if (auto prev_transition =\n            manager->active_transitions_.find(transition.first);\n        prev_transition != active_transitions_.end()) {\n      transition.second.first->SetStartListenersCalled(\n          prev_transition->second.first->StartListenersCalled());\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/transition_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_TRANSITION_MANAGER_H_\n#define CLAY_GFX_ANIMATION_TRANSITION_MANAGER_H_\n\n#include <map>\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/animation/animation_event_handler.h\"\n#include \"clay/gfx/animation/animator_listener_adapter.h\"\n#include \"clay/gfx/animation/animator_target.h\"\n#include \"clay/gfx/animation/transition_data.h\"\n#include \"clay/gfx/animation/type_evaluator.h\"\n#include \"clay/gfx/animation/value_animator.h\"\n\nnamespace clay {\n\nclass TransitionManager {\n public:\n  explicit TransitionManager(AnimatorTarget* target);\n  ~TransitionManager();\n  void AppendData(const TransitionData& data);\n  void UpdateData(const std::vector<TransitionData>& data);\n  bool Enabled(ClayAnimationPropertyType type) const;\n  bool HasAnimationRunning() const;\n  bool IsAnimationRunning(ClayAnimationPropertyType type);\n  template <typename T>\n  void UpdateAnimationValue(ClayAnimationPropertyType type, const T& value);\n  void EndAllAnimators();\n  void EndAnimator(ClayAnimationPropertyType type);\n  void CancelAnimator(ClayAnimationPropertyType type);\n  void CancelAllAnimators();\n  void OnAnimationStart(const Animator& animation,\n                        ClayAnimationPropertyType type);\n  void OnAnimationEnd(const Animator& animation,\n                      ClayAnimationPropertyType type);\n  template <typename T>\n  bool TransitionTo(ClayAnimationPropertyType type, const T& value);\n  AnimatorTarget* GetTarget() const { return target_; }\n  void SetEventHandler(AnimationEventHandler* event_handler);\n  std::vector<ValueAnimator*> GetRunningAnimators();\n\n  bool StartListenersNotified(ClayAnimationPropertyType type) const;\n  std::unique_ptr<TransitionManager> CloneForRasterAnimation(\n      ClayAnimationPropertyType type, AnimatorTarget* target) const;\n\n  // See `SyncProperties` in `KeyframesManager`.\n  void SyncProperties(TransitionManager* manager);\n\n private:\n  AnimatorTarget* target_;\n  AnimationEventHandler* event_handler_{};\n  std::vector<TransitionData> data_;\n  using ActiveTransition = std::pair<std::unique_ptr<ValueAnimator>,\n                                     std::unique_ptr<AnimatorListenerAdapter>>;\n  std::map<ClayAnimationPropertyType, ActiveTransition> active_transitions_;\n};\n\ntemplate <typename T>\nclass TransitionListener : public AnimatorListenerAdapter {\n public:\n  TransitionListener(TransitionManager* mgr, ClayAnimationPropertyType type,\n                     T old_value, T new_value,\n                     bool is_raster_transition = false)\n      : mgr_(mgr), type_(type), old_value_(old_value), new_value_(new_value) {}\n\n  void OnAnimationUpdate(ValueAnimator& animation) override {\n    float current_fraction = animation.GetAnimatedFraction();\n    T animated_value =\n        TypeEvaluator<T>::Evaluate(current_fraction, old_value_, new_value_);\n    mgr_->GetTarget()->SetProperty(type_, animated_value, true);\n  }\n  void OnAnimationStart(Animator& animation) override {\n    mgr_->OnAnimationStart(animation, type_);\n  }\n  void OnAnimationEnd(Animator& animation) override {\n    mgr_->GetTarget()->SetProperty(type_, new_value_, false);\n    mgr_->OnAnimationEnd(animation, type_);\n  }\n  void OnAnimationCancel(Animator& animation) override {}\n  void SetNewValue(const T& value) { new_value_ = value; }\n\n  std::unique_ptr<TransitionListener> CloneForRasterAnimation(\n      TransitionManager* manager) const {\n    return std::make_unique<TransitionListener>(manager, type_, old_value_,\n                                                new_value_, true);\n  }\n\n private:\n  TransitionManager* mgr_;\n  ClayAnimationPropertyType type_;\n  T old_value_;\n  T new_value_;\n};\n\ntemplate <typename T>\nbool TransitionManager::TransitionTo(ClayAnimationPropertyType type,\n                                     const T& value) {\n  // End current transition animation if there is one\n  CancelAnimator(type);\n  for (auto& transition : data_) {\n    if (static_cast<int>(transition.property) & static_cast<int>(type)) {\n      T old_value;\n      target_->GetProperty(type, old_value);\n      active_transitions_[type].second =\n          std::make_unique<TransitionListener<T>>(this, type, old_value, value);\n      active_transitions_[type].first = std::make_unique<ValueAnimator>();\n      auto& animator = active_transitions_[type].first;\n      animator->SetDuration(transition.duration);\n      animator->SetStartDelay(transition.delay);\n      animator->SetInterpolator(Interpolator::Create(transition.timing_func));\n      animator->AddUpdateListener(active_transitions_[type].second.get());\n      animator->AddListener(active_transitions_[type].second.get());\n      animator->SetAnimationHandler(target_->GetAnimationHandler());\n      animator->Start();\n      return true;\n    }\n  }\n  return false;\n}\n\ntemplate <typename T>\nvoid TransitionManager::UpdateAnimationValue(ClayAnimationPropertyType type,\n                                             const T& value) {\n  auto it = active_transitions_.find(ClayAnimationPropertyType::kTransform);\n  if (it != active_transitions_.end()) {\n    static_cast<TransitionListener<T>*>(it->second.second.get())\n        ->SetNewValue(value);\n  }\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_TRANSITION_MANAGER_H_\n"
  },
  {
    "path": "clay/gfx/animation/type_evaluator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_TYPE_EVALUATOR_H_\n#define CLAY_GFX_ANIMATION_TYPE_EVALUATOR_H_\n\n#include \"clay/gfx/geometry/box_shadow_operations.h\"\n#include \"clay/gfx/geometry/filter_operations.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n\ntemplate <typename T>\nclass TypeEvaluator {\n public:\n  // This function returns the result of linearly interpolating the start and\n  // end values. The calculation is a simple parametric calculation.\n  // For non primitive types, such as transformation and color, a template\n  // specialization should be defined.\n  static T Evaluate(float fraction, T start_value, T end_value) {\n    return start_value + (end_value - start_value) * fraction;\n  }\n};\n\ntemplate <>\nclass TypeEvaluator<Color> {\n public:\n  static Color Evaluate(float fraction, Color start_value, Color end_value) {\n    return Color::Lerp(start_value, end_value, fraction);\n  }\n};\n\ntemplate <>\nclass TypeEvaluator<TransformOperations> {\n public:\n  static TransformOperations Evaluate(float fraction,\n                                      const TransformOperations& start_value,\n                                      const TransformOperations& end_value) {\n    return end_value.Blend(start_value, fraction);\n  }\n};\n\ntemplate <>\nclass TypeEvaluator<FilterOperations> {\n public:\n  static FilterOperations Evaluate(float fraction,\n                                   const FilterOperations& start_value,\n                                   const FilterOperations& end_value) {\n    return end_value.Blend(start_value, fraction);\n  }\n};\n\ntemplate <>\nclass TypeEvaluator<BoxShadowOperations> {\n public:\n  static BoxShadowOperations Evaluate(float fraction,\n                                      const BoxShadowOperations& start_value,\n                                      const BoxShadowOperations& end_value) {\n    return end_value.Blend(start_value, fraction);\n  }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_TYPE_EVALUATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/value_animator.cc",
    "content": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Copyright 2021 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"clay/gfx/animation/value_animator.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <optional>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/gfx/animation/animation_handler.h\"\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\nfloat ValueAnimator::duration_scale_s_ = 1.0f;\nstatic ValueAnimator::RepeatMode FromClayAnimationDirectionType(\n    ClayAnimationDirectionType type) {\n  ValueAnimator::RepeatMode mode;\n  switch (type) {\n    case ClayAnimationDirectionType::kNormal:\n      mode = ValueAnimator::kNormal;\n      break;\n    case ClayAnimationDirectionType::kReverse:\n      mode = ValueAnimator::kReverse;\n      break;\n    case ClayAnimationDirectionType::kAlternate:\n      mode = ValueAnimator::kAlternate;\n      break;\n    case ClayAnimationDirectionType::kAlternateReverse:\n      mode = ValueAnimator::kAlternateReverse;\n      break;\n    default:\n      mode = ValueAnimator::kNormal;\n      break;\n  }\n  return mode;\n}\n\nstatic ValueAnimator::FillMode FromClayAnimationFillModeType(\n    ClayAnimationFillModeType type) {\n  ValueAnimator::FillMode mode;\n  switch (type) {\n    case ClayAnimationFillModeType::kNone:\n      mode = ValueAnimator::kNone;\n      break;\n    case ClayAnimationFillModeType::kForwards:\n      mode = ValueAnimator::kForwards;\n      break;\n    case ClayAnimationFillModeType::kBackwards:\n      mode = ValueAnimator::kBackward;\n      break;\n    case ClayAnimationFillModeType::kBoth:\n      mode = ValueAnimator::kBoth;\n      break;\n    default:\n      mode = ValueAnimator::kNone;\n      break;\n  }\n  return mode;\n}\n\n// NOTE: There is a known issue: When `animation-iteration-count` is set to 0,\n// `iteration_count` will be `-1`, which will cause the animation to play\n// infinitely. This problem should be solved in Lynx, and then we may need to\n// update the related logic.\nValueAnimator::ValueAnimator(const AnimationData& animation_data) {\n  Animator::SetAnimationName(animation_data.name);\n  SetAnimationData(animation_data);\n}\n\nValueAnimator::~ValueAnimator() { RemoveAnimationCallback(); }\n\n/**\n * This function is called immediately before processing the first\n * animation frame of an animation. If there is a nonzero start_delay, the\n * function is called after that delay ends. It takes care of the final\n * initialization steps for the animation.\n */\nvoid ValueAnimator::InitAnimation() {\n  if (!initialized_) {\n    initialized_ = true;\n  }\n}\n\nint64_t ValueAnimator::GetTotalDuration() {\n  if (repeat_count_ == kInfinite) {\n    return kDurationInfinite;\n  } else {\n    return start_delay_ + (duration_ * (repeat_count_ + 1));\n  }\n}\n\n/**\n * Sets the position of the animation to the specified point in time. This time\n * should be between 0 and the total duration of the animation, including any\n * repetition. If the animation has not yet been started, then it will not\n * advance forward after it is set to this time; it will simply set the time to\n * this value and perform any appropriate actions based on that time. If the\n * animation is already running, then SetCurrentPlayTime() will set the current\n * playing time to this value and continue playing from that point.\n *\n * @param play_time The time, in milliseconds, to which the animation is\n * advanced or rewound.\n */\nvoid ValueAnimator::SetCurrentPlayTime(int64_t play_time) {\n  float fraction =\n      duration_ > 0 ? static_cast<float>(play_time) / duration_ : 1;\n  SetCurrentFraction(fraction);\n}\n\n/**\n * Sets the position of the animation to the specified fraction. This fraction\n * should be between 0 and the total fraction of the animation, including any\n * repetition. That is, a fraction of 0 will position the animation at the\n * beginning, a value of 1 at the end, and a value of 2 at the end of a\n * reversing animator that repeats once. If the animation has not yet been\n * started, then it will not advance forward after it is set to this fraction;\n * it will simply set the fraction to this value and perform any appropriate\n * actions based on that fraction. If the animation is already running, then\n * SetCurrentFraction() will set the current fraction to this value and continue\n * playing from that point. {@link Animator.AnimatorListener} events are not\n * called due to changing the fraction; those events are only processed while\n * the animation is running.\n *\n * @param fraction The fraction to which the animation is advanced or rewound.\n * Values outside the range of 0 to the maximum fraction for the animator will\n * be clamped to the correct range.\n */\nvoid ValueAnimator::SetCurrentFraction(float fraction) {\n  InitAnimation();\n  fraction = ClampFraction(fraction);\n  start_time_committed_ =\n      true;  // do not allow start time to be compensated for jank\n  if (IsPulsingInternal()) {\n    int64_t seekTime = static_cast<int64_t>(GetScaledDuration() * fraction);\n    int64_t current_time = GetAnimationHandler()->GetCurrentAnimationTime();\n    // Only modify the start time when the animation is running. Seek fraction\n    // will ensure non-running animations skip to the correct start time.\n    start_time_ = current_time - seekTime;\n  } else {\n    // If the animation loop hasn't started, or during start delay, the\n    // startTime will be adjusted once the delay has passed based on seek\n    // fraction.\n    seek_fraction_ = fraction;\n  }\n  overall_fraction_ = fraction;\n  float currentIterationFraction =\n      GetCurrentIterationFraction(fraction, reversing_);\n  AnimateValue(currentIterationFraction);\n}\n\n/**\n * Calculates current iteration based on the overall fraction. The overall\n * fraction will be in the range of [0, repeat_count_ + 1]. Both current\n * iteration and fraction in the current iteration can be derived from it.\n */\nint ValueAnimator::GetCurrentIteration(float fraction) {\n  fraction = ClampFraction(fraction);\n  // If the overall fraction is a positive integer, we consider the current\n  // iteration to be complete. In other words, the fraction for the current\n  // iteration would be 1, and the current iteration would be overall fraction\n  // - 1.\n  float iteration = std::floor(fraction);\n  if (fraction == iteration && fraction > 0) {\n    iteration--;\n  }\n  return static_cast<int>(iteration);\n}\n\n/**\n * Calculates the fraction of the current iteration, taking into account whether\n * the animation should be played backwards. E.g. When the animation is played\n * backwards in an iteration, the fraction for that iteration will go from 1.f\n * to 0.f.\n */\nfloat ValueAnimator::GetCurrentIterationFraction(float fraction,\n                                                 bool in_reverse) {\n  fraction = ClampFraction(fraction);\n  int iteration = GetCurrentIteration(fraction);\n  float currentFraction = fraction - iteration;\n  return ShouldPlayBackward(iteration, in_reverse) ? 1.f - currentFraction\n                                                   : currentFraction;\n}\n\n/**\n * Clamps fraction into the correct range: [0, repeat_count_ + 1]. If repeat\n * count is infinite, no upper bound will be set for the fraction.\n *\n * @param fraction fraction to be clamped\n * @return fraction clamped into the range of [0, repeat_count_ + 1]\n */\nfloat ValueAnimator::ClampFraction(float fraction) {\n  if (fraction < 0) {\n    fraction = 0;\n  } else if (repeat_count_ != kInfinite) {\n    fraction = std::min(fraction, repeat_count_ + 1.f);\n  }\n  return fraction;\n}\n\n/**\n * Calculates the direction of animation playing (i.e. forward or backward),\n * based on 1) whether the entire animation is being reversed, 2) repeat mode\n * applied to the current iteration.\n */\nbool ValueAnimator::ShouldPlayBackward(int iteration, bool in_reverse) {\n  if (iteration > 0 &&\n      (repeat_mode_ == kAlternate || repeat_mode_ == kAlternateReverse) &&\n      (iteration < (repeat_count_ + 1) || repeat_count_ == kInfinite)) {\n    // if we were seeked to some other iteration in a reversing animator,\n    // figure out the correct direction to start playing based on the iteration\n    if (in_reverse) {\n      return (iteration % 2) == 0;\n    } else {\n      return (iteration % 2) != 0;\n    }\n  } else {\n    return in_reverse;\n  }\n}\n\n/**\n * Gets the current position of the animation in time, which is equal to the\n * current time minus the time that the animation started. An animation that is\n * not yet started will return a value of zero, unless the animation has has its\n * play time set via\n * {@link #SetCurrentPlayTime(int64_t)} or {@link #SetCurrentFraction(float)},\n * in which case it will return the time that was set.\n *\n * @return The current position in time of the animation.\n */\nint64_t ValueAnimator::GetCurrentPlayTime() {\n  if (!initialized_ || (!started_ && seek_fraction_ < 0)) {\n    return 0;\n  }\n  if (seek_fraction_ >= 0) {\n    return static_cast<int64_t>(duration_ * seek_fraction_);\n  }\n  float durationScale = ResolveDurationScale();\n  if (durationScale == 0.f) {\n    durationScale = 1.f;\n  }\n  return static_cast<int64_t>(\n      (GetAnimationHandler()->GetCurrentAnimationTime() - start_time_) /\n      durationScale);\n}\n\nvoid ValueAnimator::SetStartDelay(int64_t start_delay) {\n  // Clamp start delay to non-negative range.\n  if (start_delay < 0) {\n    start_delay = 0;\n  }\n  start_delay_ = start_delay;\n}\n\nvoid ValueAnimator::SetInterpolator(std::unique_ptr<Interpolator> value) {\n  if (value) {\n    interpolator_ = std::move(value);\n  } else {\n    interpolator_ = Interpolator::CreateDefaultInterpolator();\n  }\n}\n\nvoid ValueAnimator::NotifyStartListeners() {\n  if (!start_listeners_called_) {\n    std::forward_list<AnimatorListener*> tmp_listeners = listeners_;\n    for (AnimatorListener* listener : tmp_listeners) {\n      listener->OnAnimationStart(*this);\n    }\n  }\n  start_listeners_called_ = true;\n}\n\n/**\n * Start the animation playing. This version of Start() takes a bool flag that\n * indicates whether the animation should play in reverse. The flag is usually\n * false, but may be set to true if called from the Reverse() method.\n *\n * <p>The animation started by calling this method will be run on the thread\n * that called this method. This thread should have a Looper on it (a runtime\n * exception will be thrown if this is not the case). Also, if the animation\n * will animate properties of objects in the view hierarchy, then the calling\n * thread should be the UI thread for that view hierarchy.</p>\n *\n * @param play_backwards Whether the ValueAnimator should start playing in\n * reverse.\n */\nvoid ValueAnimator::Start(bool play_backwards) {\n  reversing_ = play_backwards;\n  self_pulse_ = !suppress_self_pulse_requested_;\n  // Special case: reversing from seek-to-0 should act as if not seeked at all.\n  if (play_backwards && seek_fraction_ != -1 && seek_fraction_ != 0) {\n    if (repeat_count_ == kInfinite) {\n      // Calculate the fraction of the current iteration.\n      float fraction =\n          static_cast<float>(seek_fraction_ - std::floor(seek_fraction_));\n      seek_fraction_ = 1 - fraction;\n    } else {\n      seek_fraction_ = 1 + repeat_count_ - seek_fraction_;\n    }\n  }\n  started_ = true;\n  end_listeners_called_ = false;\n  paused_ = false;\n  running_ = false;\n  animation_end_requested_ = false;\n  // Resets last_frame_time_ when Start() is called, so that if the animation\n  // was running, calling Start() would put the animation in the\n  // started-but-not-yet-reached-the-first-frame phase.\n  last_frame_time_ = -1;\n  first_frame_time_ = -1;\n  start_time_ = -1;\n  AddAnimationCallback(0);\n\n  if (start_delay_ == 0 || seek_fraction_ >= 0 || reversing_) {\n    // If there's no start delay, init the animation and notify start listeners\n    // right away to be consistent with the previous behavior. Otherwise,\n    // postpone this until the first frame after the start delay.\n    StartAnimation();\n    if (seek_fraction_ == -1) {\n      // No seek, start at play time 0. Note that the reason we are not using\n      // fraction 0 is because for animations with 0 duration, we want to be\n      // consistent with pre-N behavior: skip to the final value immediately.\n      SetCurrentPlayTime(0);\n    } else {\n      SetCurrentFraction(seek_fraction_);\n    }\n  }\n}\n\nvoid ValueAnimator::StartWithoutPulsing(bool in_reverse) {\n  suppress_self_pulse_requested_ = true;\n  if (in_reverse) {\n    Reverse();\n  } else {\n    Start();\n  }\n  suppress_self_pulse_requested_ = false;\n}\n\nvoid ValueAnimator::Cancel() {\n  // If end has already been requested, through a previous End() or Cancel()\n  // call, no-op until animation starts again.\n  if (animation_end_requested_) {\n    return;\n  }\n  // Only cancel if the animation is actually running or has been started and is\n  // about to run Only notify listeners if the animator has actually started\n  if ((started_ || running_) && !end_listeners_called_) {\n    if (!running_) {\n      // If it's not yet running, then start listeners weren't called. Call them\n      // now.\n      NotifyStartListeners();\n    }\n    std::forward_list<AnimatorListener*> tmp_listeners = listeners_;\n    for (AnimatorListener* listener : tmp_listeners) {\n      listener->OnAnimationCancel(*this);\n    }\n  }\n  std::forward_list<AnimatorListener*> tmp_listeners = listeners_;\n  for (AnimatorListener* listener : tmp_listeners) {\n    listener->OnAnimationRemove(*this);\n  }\n  RemoveAnimationCallback();\n  Reset();\n}\n\nvoid ValueAnimator::End() {\n  if (!running_) {\n    // Special case if the animation has not yet started; get it ready for\n    // ending\n    StartAnimation();\n    started_ = true;\n  } else if (!initialized_) {\n    InitAnimation();\n  }\n  AnimateValue(ShouldPlayBackward(repeat_count_, reversing_) ? 0.f : 1.f);\n  EndAnimation();\n}\n\nvoid ValueAnimator::Resume() {\n  if (paused_ && !resumed_) {\n    resumed_ = true;\n    if (pause_time_ > 0) {\n      AddAnimationCallback(0);\n    }\n  }\n  Animator::Resume();\n}\n\nvoid ValueAnimator::Pause() {\n  bool previouslyPaused = paused_;\n  Animator::Pause();\n  if (!previouslyPaused && paused_) {\n    pause_time_ = -1;\n    resumed_ = false;\n  }\n}\n\nbool ValueAnimator::IsRunning() { return running_; }\n\nbool ValueAnimator::IsStarted() { return started_; }\n\nvoid ValueAnimator::Reverse() {\n  if (IsPulsingInternal()) {\n    int64_t current_time = GetAnimationHandler()->GetCurrentAnimationTime();\n    int64_t current_play_time = current_time - start_time_;\n    int64_t timeLeft = GetScaledDuration() - current_play_time;\n    start_time_ = current_time - timeLeft;\n    start_time_committed_ =\n        true;  // do not allow start time to be compensated for jank\n    reversing_ = !reversing_;\n  } else if (started_) {\n    reversing_ = !reversing_;\n    End();\n  } else {\n    Start(true);\n  }\n}\n\nbool ValueAnimator::CanReverse() { return true; }\n\nstd::unique_ptr<ValueAnimator> ValueAnimator::Clone() const {\n  std::unique_ptr<ValueAnimator> clone = std::make_unique<ValueAnimator>();\n  clone->animation_name_ = animation_name_;\n  clone->paused_ = paused_;\n  clone->start_time_ = start_time_;\n  clone->start_time_committed_ = start_time_committed_;\n  clone->seek_fraction_ = seek_fraction_;\n  clone->pause_time_ = pause_time_;\n  clone->resumed_ = resumed_;\n  clone->reversing_ = reversing_;\n  clone->overall_fraction_ = overall_fraction_;\n  clone->current_fraction_ = current_fraction_;\n  clone->last_frame_time_ = last_frame_time_;\n  clone->first_frame_time_ = first_frame_time_;\n  clone->running_ = running_;\n  clone->started_ = started_;\n  clone->start_listeners_called_ = false;\n  clone->initialized_ = initialized_;\n  clone->animation_end_requested_ = animation_end_requested_;\n  clone->duration_ = duration_;\n  clone->start_delay_ = start_delay_;\n  clone->repeat_count_ = repeat_count_;\n  clone->repeat_mode_ = repeat_mode_;\n  clone->fill_mode_ = fill_mode_;\n  clone->self_pulse_ = self_pulse_;\n  clone->suppress_self_pulse_requested_ = suppress_self_pulse_requested_;\n  clone->interpolator_ = interpolator_->Clone();\n  clone->duration_scale_ = duration_scale_;\n  return clone;\n}\n\nvoid ValueAnimator::Reset() {\n  animation_end_requested_ = true;\n  paused_ = false;\n  running_ = false;\n  started_ = false;\n  start_listeners_called_ = false;\n  last_frame_time_ = -1;\n  first_frame_time_ = -1;\n  start_time_ = -1;\n  // reversing_ needs to be reset *after* notifying the listeners for the end\n  // callbacks.\n  reversing_ = false;\n}\n\n/**\n * Called internally to end an animation by removing it from the animations\n * list. Must be called on the UI thread.\n */\nvoid ValueAnimator::EndAnimation(bool remove) {\n  if ((animation_end_requested_ || end_listeners_called_) && !remove) {\n    return;\n  }\n  if (started_ && !running_) {\n    // If it's not yet running, then start listeners weren't called. Call them\n    // now.\n    NotifyStartListeners();\n  }\n  // `running_` should remain true when forwarding.\n  if (remove) {\n    running_ = false;\n  }\n  // should not notify end event duplicated.\n  // end_listeners_called_ will be set false when animator restart.\n  if ((started_ || running_) && !end_listeners_called_) {\n    std::forward_list<AnimatorListener*> tmp_listeners = listeners_;\n    for (AnimatorListener* l : tmp_listeners) {\n      l->OnAnimationEnd(*this);\n    }\n  }\n  end_listeners_called_ = true;\n  if (!remove) {\n    return;\n  }\n  std::forward_list<AnimatorListener*> tmp_listeners = listeners_;\n  for (AnimatorListener* l : tmp_listeners) {\n    l->OnAnimationRemove(*this);\n  }\n  RemoveAnimationCallback();\n  Reset();\n}\n\n/**\n * Called internally to start an animation by adding it to the active animations\n * list. Must be called on the UI thread.\n */\nvoid ValueAnimator::StartAnimation() {\n  animation_end_requested_ = false;\n  InitAnimation();\n  running_ = true;\n  if (seek_fraction_ >= 0) {\n    overall_fraction_ = seek_fraction_;\n  } else {\n    overall_fraction_ = 0.f;\n  }\n  NotifyStartListeners();\n}\n\n/**\n * Internal only: This tracks whether the animation has gotten on the animation\n * loop. Note this is different than {@link #IsRunning()} in that the latter\n * tracks the time after Start() is called (or after start delay if any), which\n * may be before the animation loop starts.\n */\nbool ValueAnimator::IsPulsingInternal() { return last_frame_time_ >= 0; }\n\n/**\n * This internal function processes a single animation frame for a given\n * animation. The current_time parameter is the timing pulse sent by the\n * handler, used to calculate the elapsed duration, and therefore the elapsed\n * fraction, of the animation. The return value indicates whether the animation\n * should be ended (which happens when the elapsed time of the animation exceeds\n * the animation's duration, including the repeatCount).\n *\n * @param current_time The current time, as tracked by the static timing handler\n * @return true if the animation's duration, including any repetitions due to\n * <code>repeatCount</code> has been exceeded and the animation should be ended.\n */\nbool ValueAnimator::AnimateBasedOnTime(int64_t current_time) {\n  bool done = false;\n  if (running_) {\n    int64_t scaledDuration = GetScaledDuration();\n    float fraction =\n        scaledDuration > 0\n            ? static_cast<float>(current_time - start_time_) / scaledDuration\n            : 1.f;\n    float lastFraction = overall_fraction_;\n    bool newIteration =\n        static_cast<int>(fraction) > static_cast<int>(lastFraction);\n    bool lastIterationFinished =\n        (fraction >= repeat_count_ + 1) && (repeat_count_ != kInfinite);\n    if (scaledDuration == 0) {\n      // 0 duration animator, ignore the repeat count and skip to the end\n      done = true;\n    } else if (newIteration && !lastIterationFinished) {\n      // Time to repeat\n      for (auto l : listeners_) {\n        l->OnAnimationRepeat(*this);\n      }\n    } else if (lastIterationFinished) {\n      done = true;\n    }\n    overall_fraction_ = ClampFraction(fraction);\n    float currentIterationFraction =\n        GetCurrentIterationFraction(overall_fraction_, reversing_);\n    AnimateValue(currentIterationFraction);\n  }\n  return done;\n}\n\n/**\n * Internal use only.\n *\n * This method does not modify any fields of the animation. It should be called\n * when seeking in an AnimatorSet. When the last play time and current play time\n * are of different repeat iterations,\n * {@link\n * android.view.animation.Animation.AnimationListener#OnAnimationRepeat(Animation)}\n * will be called.\n */\nvoid ValueAnimator::AnimateBasedOnPlayTime(int64_t current_play_time,\n                                           int64_t last_play_time,\n                                           bool in_reverse) {\n  if (current_play_time < 0 || last_play_time < 0) {\n    FML_UNREACHABLE();\n  }\n\n  InitAnimation();\n  // Check whether repeat callback is needed only when repeat count is non-zero\n  if (repeat_count_ > 0) {\n    int iteration = static_cast<int>(current_play_time / duration_);\n    int lastIteration = static_cast<int>(last_play_time / duration_);\n\n    // Clamp iteration to [0, repeat_count_]\n    iteration = std::min(iteration, repeat_count_);\n    lastIteration = std::min(lastIteration, repeat_count_);\n\n    if (iteration != lastIteration) {\n      for (auto l : listeners_) {\n        l->OnAnimationRepeat(*this);\n      }\n    }\n  }\n\n  if (repeat_count_ != kInfinite &&\n      current_play_time >= (repeat_count_ + 1) * duration_) {\n    SkipToEndValue(in_reverse);\n  } else {\n    // Find the current fraction:\n    float fraction = current_play_time / static_cast<float>(duration_);\n    fraction = GetCurrentIterationFraction(fraction, in_reverse);\n    AnimateValue(fraction);\n  }\n}\n\n/**\n * Internal use only.\n * Skips the animation value to end/start, depending on whether the play\n * direction is forward or backward.\n *\n * @param in_reverse whether the end value is based on a reverse direction. If\n * yes, this is equivalent to skip to start value in a forward playing\n * direction.\n */\nvoid ValueAnimator::SkipToEndValue(bool in_reverse) {\n  InitAnimation();\n  float endFraction = in_reverse ? 0.f : 1.f;\n  if (repeat_count_ % 2 == 1 &&\n      (repeat_mode_ == kAlternate || repeat_mode_ == kAlternateReverse)) {\n    // This would end on fraction = 0\n    endFraction = 0.f;\n  }\n  AnimateValue(endFraction);\n}\n\n/**\n * Processes a frame of the animation, adjusting the start time if needed.\n *\n * @param frame_time The frame time.\n * @return true if the animation has ended.\n * @hide\n */\nbool ValueAnimator::DoAnimationFrame(int64_t frame_time) {\n  if (start_time_ < 0) {\n    // First frame. If there is start delay, start delay count down will happen\n    // *after* this frame.\n    start_time_ = std::max(\n        static_cast<int64_t>(0),\n        reversing_ ? frame_time\n                   : frame_time + static_cast<int64_t>(start_delay_ *\n                                                       ResolveDurationScale()));\n  }\n\n  RestartAnimationIfNeeded(frame_time);\n\n  bool fill_backwards = fill_mode_ & kBackward;\n  // Handle pause/resume\n  if (paused_) {\n    if (pause_time_ < 0) {\n      pause_time_ = frame_time;\n    }\n\n    if (pause_time_ >= start_time_ || fill_backwards) {\n      running_ = true;\n      AnimateBasedOnTime(std::max(pause_time_, start_time_));\n    }\n    return false;\n  } else if (resumed_) {\n    resumed_ = false;\n    if (pause_time_ > 0) {\n      // Offset by the duration that the animation was paused\n      start_time_ += (frame_time - pause_time_);\n    }\n  }\n\n  if (!running_) {\n    // If not running, that means the animation is in the start delay phase of a\n    // forward running animation. In the case of reversing, we want to run start\n    // delay in the end.\n    if (!fill_backwards && start_time_ > frame_time && seek_fraction_ == -1) {\n      // This is when no seek fraction is set during start delay. If developers\n      // change the seek fraction during the delay, animation will start from\n      // the seeked position right away.\n      return false;\n    } else {\n      // If running_ is not set by now, that means non-zero start delay,\n      // no seeking, not reversing. At this point, start delay has passed.\n      StartAnimation();\n    }\n  }\n\n  if (!start_listeners_called_) {\n    NotifyStartListeners();\n  }\n\n  if (last_frame_time_ < 0) {\n    if (seek_fraction_ >= 0) {\n      int64_t seekTime =\n          static_cast<int64_t>(GetScaledDuration() * seek_fraction_);\n      start_time_ = frame_time - seekTime;\n      seek_fraction_ = -1;\n    }\n    start_time_committed_ =\n        false;  // allow start time to be compensated for jank\n  }\n  last_frame_time_ = frame_time;\n  // The frame time might be before the start time during the first frame of\n  // an animation.  The \"current time\" must always be on or after the start\n  // time to avoid animating frames at negative time intervals.  In practice,\n  // this is very rare and only happens when seeking backwards.\n  int64_t current_time = std::max(frame_time, start_time_);\n  bool finished = AnimateBasedOnTime(current_time);\n\n  if (finished) {\n    EndAnimation(!(fill_mode_ & kForwards));\n  }\n  return finished;\n}\n\nbool ValueAnimator::PulseAnimationFrame(int64_t frame_time) {\n  if (self_pulse_) {\n    // Pulse animation frame will *always* be after calling Start(). If\n    // self_pulse_ isn't set to false at this point, that means child animators\n    // did not call super's Start(). This can happen when the Animator is just a\n    // non-animating wrapper around a real functional animation. In this case,\n    // we can't really pulse a frame into the animation, because the animation\n    // cannot necessarily be properly initialized (i.e. no start/end values\n    // set).\n    return false;\n  }\n  return DoAnimationFrame(frame_time);\n}\n\nvoid ValueAnimator::RemoveAnimationCallback() {\n  if (!self_pulse_) {\n    return;\n  }\n  GetAnimationHandler()->RemoveCallback(this);\n}\n\nvoid ValueAnimator::AddAnimationCallback(int64_t delay) {\n  if (!self_pulse_) {\n    return;\n  }\n  GetAnimationHandler()->AddAnimationFrameCallback(this, delay);\n}\n\n/**\n * This method is called with the elapsed fraction of the animation during every\n * animation frame. This function turns the elapsed fraction into an\n * interpolated fraction and then into an animated value (from the evaluator.\n * The function is called mostly during animation updates, but it is also called\n * when the End() function is called, to set the final value on the property.\n *\n * @param fraction The elapsed fraction of the animation.\n */\nvoid ValueAnimator::AnimateValue(float fraction) {\n  if (interpolator_) {\n    fraction = interpolator_->Interpolate(fraction);\n  }\n  current_fraction_ = fraction;\n  for (auto l : update_listeners_) {\n    l->OnAnimationUpdate(*this);\n  }\n}\n\n/**\n * Returns the current animation fraction, which is the elapsed/interpolated\n * fraction used in the most recent frame update on the animation.\n */\nfloat ValueAnimator::GetAnimatedFraction() {\n  // For the following reverse modes, we just return a different elapsed\n  // fraction, apart from this, these modes are handled just like their\n  // non-reverse counterparts.\n  if (repeat_mode_ == kReverse || repeat_mode_ == kAlternateReverse) {\n    return 1.0f - current_fraction_;\n  } else {\n    return current_fraction_;\n  }\n}\n\n// Return the number of animations currently running.\nint ValueAnimator::GetCurrentAnimationsCount() {\n  return GetAnimationHandler()->GetAnimationCount();\n}\n\n// Return the AnimationHandler that will be used to schedule updates for\n// this animator.\nAnimationHandler* ValueAnimator::GetAnimationHandler() {\n  static AnimationHandler sDefaultHandler;\n  return animation_handler_ ? animation_handler_ : &sDefaultHandler;\n}\n\n// Sets the animation handler used to schedule updates for this animator\n// or null to use the default handler.\nvoid ValueAnimator::SetAnimationHandler(AnimationHandler* animation_handler) {\n  animation_handler_ = animation_handler;\n}\n\nvoid ValueAnimator::SetAnimationData(AnimationData animation_data) {\n  duration_ = animation_data.duration;\n  repeat_mode_ = FromClayAnimationDirectionType(animation_data.direction);\n  fill_mode_ = FromClayAnimationFillModeType(animation_data.fill_mode);\n  repeat_count_ = animation_data.iteration_count;\n  start_delay_ = animation_data.delay;\n  start_time_ += start_delay_;\n  std::unique_ptr<Interpolator> value =\n      Interpolator::Create(animation_data.timing_func);\n  if (value) {\n    interpolator_ = std::move(value);\n  } else {\n    interpolator_ = Interpolator::CreateDefaultInterpolator();\n  }\n}\n\nvoid ValueAnimator::RestartAnimationIfNeeded(int64_t current_time) {\n  auto fill_forwards = fill_mode_ & kForwards;\n  // when animation fillmode change forwards from backwards ,\"running_\" needs to\n  // be set to true\n  if (fill_forwards) {\n    running_ = true;\n  }\n\n  if (start_time_ + duration_ > current_time && end_listeners_called_) {\n    start_listeners_called_ = false;\n    end_listeners_called_ = false;\n    // When the fillmode changes from forwards to non-forwards we need to\n    // set view value with original_value_\n    if (!fill_forwards) {\n      std::forward_list<AnimatorListener*> tmp_listeners = listeners_;\n      for (AnimatorListener* listener : tmp_listeners) {\n        listener->OnAnimationRemove(*this);\n      }\n    }\n    NotifyStartListeners();\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/value_animator.h",
    "content": "/*\n * Copyright (C) 2010 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Copyright 2021 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\n#ifndef CLAY_GFX_ANIMATION_VALUE_ANIMATOR_H_\n#define CLAY_GFX_ANIMATION_VALUE_ANIMATOR_H_\n\n#include <forward_list>\n#include <memory>\n\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/gfx/animation/animation_handler.h\"\n#include \"clay/gfx/animation/animator.h\"\n#include \"clay/gfx/animation/interpolator.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\n/**\n * This class provides a simple timing engine for running animations\n * which calculate animated values and set them on target objects.\n *\n * There is a single timing pulse that all animations use.\n */\nclass ValueAnimator : public Animator,\n                      public AnimationHandler::AnimationFrameCallback {\n public:\n  ~ValueAnimator() override;\n  ValueAnimator() = default;\n  explicit ValueAnimator(const AnimationData& animation_data);\n\n  /**\n   * Starts this animation. If the animation has a nonzero startDelay, the\n   * animation will start running after that delay elapses. A non-delayed\n   * animation will have its initial value(s) set immediately, followed by\n   * calls to AnimatorListener#OnAnimationStart(Animator) for any listeners\n   * of this animator.\n   */\n  void Start() override { Start(false); }\n\n  /**\n   * Cancels the animation. Unlike End(), Cancel() causes the animation to\n   * stop in its tracks, sending an\n   * AnimatorListener#OnAnimationCancel(Animator) to its listeners, followed\n   * by an AnimatorListener#OnAnimationEnd(Animator) message.\n   */\n  void Cancel() override;\n\n  /**\n   * Ends the animation. This causes the animation to assign the end value of\n   * the property being animated, then calling the\n   * AnimatorListener#OnAnimationEnd(Animator) method on its listeners.\n   */\n  void End() override;\n\n  /**\n   * Pauses a running animation. This method should only be called on the\n   * same thread on which the animation was started. If the animation has\n   * not yet been IsStarted() or has since ended, then the call is ignored.\n   * Paused animations can be resumed by calling Resume().\n   */\n  void Pause() override;\n\n  /**\n   * Resumes a paused animation, causing the animator to pick up where it\n   * left off when it was paused. Calls to Resume() on an animator that is\n   * not currently paused will be ignored.\n   */\n  void Resume() override;\n\n  /**\n   * The amount of time, in milliseconds, to delay processing the animation\n   * after Start() is called.\n   *\n   * @return the number of milliseconds to delay running the animation\n   */\n  int64_t GetStartDelay() override { return start_delay_; }\n\n  /**\n   * The amount of time, in milliseconds, to delay processing the animation\n   * after Start() is called.\n\n   * @param startDelay The amount of the delay, in milliseconds\n   */\n  void SetStartDelay(int64_t start_delay) override;\n\n  /**\n   * Sets the duration of the animation.\n   *\n   * @param duration The length of the animation, in milliseconds.\n   */\n  void SetDuration(int64_t duration) override { duration_ = duration; }\n\n  /**\n   * Gets the duration of the animation.\n   *\n   * @return The length of the animation, in milliseconds.\n   */\n  int64_t GetDuration() override { return duration_; }\n\n  /**\n   * Gets the total duration of the animation, accounting for animation\n   * sequences, start delay, and repeating.\n   *\n   * @return  Total time an animation takes to finish, starting from the time\n   *          Start() is called. kDurationInfinite will be returned if the\n   *          animation or any child animation repeats infinite times.\n   */\n  int64_t GetTotalDuration() override;\n\n  /**\n   * The time interpolator used in calculating the elapsed fraction of the\n   * animation. The interpolator determines whether the animation runs with\n   * linear or non-linear motion, such as acceleration and deceleration.\n   *\n   * @param value the interpolator to be used by this animation\n   */\n  void SetInterpolator(std::unique_ptr<Interpolator> value) override;\n\n  Interpolator* GetInterpolator() override { return interpolator_.get(); }\n\n  /**\n   * Returns whether this Animator is currently running (having been started\n   * and gone past any initial startDelay period and not yet ended).\n   */\n  bool IsRunning() override;\n\n  /**\n   * Returns whether this Animator is currently running reversed.\n   */\n  bool IsReversing() { return IsRunning() && reversing_; }\n\n  /**\n   * Whether the Animator has been started and not yet ended.\n   */\n  bool IsStarted() override;\n\n  bool IsInitialized() override { return initialized_; }\n\n  bool DoAnimationFrame(int64_t frame_time) override;\n\n  void RestartAnimationIfNeeded(int64_t current_time);\n\n  /**\n   * Adds a listener to the set of listeners that are sent update events through\n   * the life of an animation. This method is called on all listeners for every\n   * frame of the animation, after the values for the animation have been\n   * calculated.\n   *\n   * @param listener the listener to be added to the current set of listeners\n   * for this animation.\n   */\n  void AddUpdateListener(AnimatorUpdateListener* listener) {\n    update_listeners_.push_front(listener);\n  }\n\n  void RemoveAllUpdateListeners() { update_listeners_.clear(); }\n\n  /**\n   * Removes a listener from the set listening to frame updates for this\n   * animation.\n   *\n   * @param listener the listener to be removed from the current set of update\n   * listeners for this animation.\n   */\n  void RemoveUpdateListener(AnimatorUpdateListener* listener) {\n    update_listeners_.remove(listener);\n  }\n\n  /**\n   * Sets how many times the animation should be repeated. If the repeat\n   * count is 0, the animation is never repeated. If the repeat count is\n   * greater than 0 or {@link #kInfinite}, the repeat mode will be taken\n   * into account. The repeat count is 0 by default.\n   *\n   * @param value the number of times the animation should be repeated\n   */\n  void SetRepeatCount(int value) { repeat_count_ = value; }\n  /**\n   * Defines how many times the animation should repeat. The default value\n   * is 0.\n   *\n   * @return the number of times the animation should repeat, or {@link\n   * #kInfinite}\n   */\n  int GetRepeatCount() { return repeat_count_; }\n\n  enum RepeatMode {\n    /**\n     * The animation plays forwards each cycle. In other words, each time the\n     * animation cycles, the animation will reset to the beginning state and\n     * start over again. This is the default value.\n     */\n    kNormal = 1,\n    /**\n     * The animation plays backwards each cycle. In other words, each time the\n     * animation cycles, the animation will reset to the end state and start\n     * over again. Animation steps are performed backwards, and timing functions\n     * are also reversed.\n     */\n    kReverse = 2,\n    /**\n     * The animation reverses direction each cycle, with the first iteration\n     * being played forwards. The count to determine if a cycle is even or odd\n     * starts at one.\n     */\n    kAlternate = 3,\n    /**\n     * The animation reverses direction each cycle, with the first iteration\n     * being played backwards. The count to determine if a cycle is even or\n     * odd starts at one.\n     */\n    kAlternateReverse = 4,\n    /**\n     * This value used with the setRepeatCount(int) property to repeat\n     * the animation indefinitely.\n     */\n    kInfinite = -1\n  };\n\n  enum FillMode {\n    /**\n     * The animation will not apply any styles to the target when it's not\n     * executing. The element will instead be displayed using any other CSS\n     * rules applied to it. This is the default value.\n     */\n    kNone = 0,\n    /**\n     * The target will retain the computed values set by the last keyframe\n     * encountered during execution. The last keyframe depends on the value of\n     * animation-direction and animation-iteration-count.\n     */\n    kForwards = 1 << 0,\n    /**\n     * The animation will apply the values defined in the first relevant\n     * keyframe as soon as it is applied to the target, and retain this during\n     * the animation-delay period. The first relevant keyframe depends on the\n     * value of animation-direction.\n     */\n    kBackward = 1 << 1,\n    /**\n     * The animation will follow the rules for both forwards and backwards, thus\n     * extending the animation properties in both directions.\n     */\n    kBoth = kForwards | kBackward,\n  };\n\n  /**\n   * Defines what this animation should do when it reaches the end. This\n   * setting is applied only when the repeat count is either greater than\n   * 0 or kInfinite. Defaults to kNormal.\n   */\n  void SetRepeatMode(RepeatMode value) { repeat_mode_ = value; }\n\n  RepeatMode GetRepeatMode() { return repeat_mode_; }\n\n  void SetFillMode(FillMode value) { fill_mode_ = value; }\n\n  FillMode GetFillMode() const { return fill_mode_; }\n\n  /**\n   * Returns the current animation fraction, which is the elapsed/interpolated\n   * fraction used in the most recent frame update on the animation.\n   *\n   * @return Elapsed/interpolated fraction of the animation.\n   */\n  float GetAnimatedFraction();\n\n  void SetCurrentPlayTime(int64_t play_time);\n  void SetCurrentFraction(float fraction);\n\n  // Return the number of animations currently running.\n  int GetCurrentAnimationsCount();\n\n  // Return the AnimationHandler that will be used to schedule updates for\n  // this animator.\n  AnimationHandler* GetAnimationHandler();\n\n  // Sets the animation handler used to schedule updates for this animator\n  // or null to use the default handler.\n  void SetAnimationHandler(AnimationHandler* animation_handler);\n\n  /**\n   * Overrides the global duration scale by a custom value.\n   *\n   * @param duration_scale The duration scale to set;\n   *                      or -1.f to use the global duration scale.\n   */\n  void OverrideDurationScale(float duration_scale) {\n    duration_scale_ = duration_scale;\n  }\n\n  static void SetDurationScale(float duration_scale) {\n    duration_scale_s_ = duration_scale;\n  }\n\n  static float GetDurationScale() { return duration_scale_s_; }\n\n  // Returns whether animators are currently enabled, system-wide.\n  static bool AreAnimatorsEnabled() { return !(duration_scale_s_ == 0); }\n\n  /**\n   * Plays the ValueAnimator in reverse. If the animation is already running,\n   * it will stop itself and play backwards from the point reached when reverse\n   * was called. If the animation is not currently running, then it will start\n   * from the end and play backwards. This behavior is only set for the current\n   * animation; future playing of the animation will use the default behavior of\n   * playing forward.\n   */\n  void Reverse() override;\n\n  // return A ValueAnimator with `start_listeners_called_` is false while other\n  // properties remains.\n  virtual std::unique_ptr<ValueAnimator> Clone() const;\n\n  bool StartListenersCalled() const { return start_listeners_called_; }\n\n  void SetStartListenersCalled(bool start_listeners_called) {\n    start_listeners_called_ = start_listeners_called;\n  }\n\n  void SetAnimationData(AnimationData animation_data);\n\n  void AddAnimationCallback(int64_t delay);\n\n  // TODO(wangyanyi) Need to consider situations such as pausing、 iteration\n  int64_t GetActivatedTime() { return last_frame_time_ - start_time_; }\n\n protected:\n  bool CanReverse() override;\n\n  // Pulse an animation frame into the animation.\n  bool PulseAnimationFrame(int64_t frame_time) override;\n\n  void StartWithoutPulsing(bool in_reverse) override;\n\n  void SkipToEndValue(bool in_reverse) override;\n\n  void AnimateBasedOnPlayTime(int64_t current_play_time, int64_t last_play_time,\n                              bool in_reverse) override;\n\n private:\n  void InitAnimation();\n  void Reset();\n  int GetCurrentIteration(float fraction);\n  float GetCurrentIterationFraction(float fraction, bool in_reverse);\n  float ClampFraction(float fraction);\n  bool ShouldPlayBackward(int iteration, bool in_reverse);\n  int64_t GetCurrentPlayTime();\n\n  void NotifyStartListeners();\n  void Start(bool play_backwards);\n\n  // the remove param is used to solved to keyframe animation stop when\n  // fill-mode=forward\n  void EndAnimation(bool remove = true);\n  void StartAnimation();\n  bool IsPulsingInternal();\n  bool AnimateBasedOnTime(int64_t current_time);\n\n  void AnimateValue(float fraction);\n\n  void AddOneShotCommitCallback();\n  void RemoveAnimationCallback();\n\n  float ResolveDurationScale() {\n    return duration_scale_ >= 0.f ? duration_scale_ : duration_scale_s_;\n  }\n\n  int64_t GetScaledDuration() {\n    return (int64_t)(duration_ * ResolveDurationScale());\n  }\n\n  FRIEND_TEST(KeyFrameTest, UpdateAnimation);\n\n  /**\n   * The first time that the animation's animateFrame() method is called. This\n   * time is used to determine elapsed time (and therefore the elapsed fraction)\n   * in subsequent calls to animateFrame().\n   *\n   * Whenever start_time_ is set, you must also update start_time_committed_.\n   */\n  int64_t start_time_ = -1;\n\n  /**\n   * When true, the start time has been firmly committed as a chosen reference\n   * point in time by which the progress of the animation will be evaluated.\n   * When false, the start time may be updated when the first animation frame is\n   * committed so as to compensate for jank that may have occurred between when\n   * the start time was initialized and when the frame was actually drawn.\n   *\n   * This flag is generally set to false during the first frame of the animation\n   * when the animation playing state transitions from STOPPED to RUNNING or\n   * resumes after having been paused.  This flag is set to true when the start\n   * time is firmly committed and should not be further compensated for jank.\n   */\n  bool start_time_committed_;\n\n  /**\n   * Set when setCurrentPlayTime() is called. If negative, animation is not\n   * currently seeked to a value.\n   */\n  float seek_fraction_ = -1;\n\n  /**\n   * Set on the next frame after pause() is called, used to calculate a new\n   * startTime or delayStartTime which allows the animator to continue from the\n   * point at which it was paused. If negative, has not yet been set.\n   */\n  int64_t pause_time_;\n\n  /**\n   * Set when an animator is resumed. This triggers logic in the next frame\n   * which actually resumes the animator.\n   */\n  bool resumed_ = false;\n\n  /**\n   * Flag to indicate whether this animator is playing in reverse mode,\n   * specifically by being started or interrupted by a call to reverse(). This\n   * flag is different than mPlayingBackwards, which indicates merely whether\n   * the current iteration of the animator is playing in reverse. It is used in\n   * corner cases to determine proper end behavior.\n   */\n  bool reversing_;\n\n  /**\n   * Tracks the overall fraction of the animation, ranging from 0 to\n   * repeat_count_ + 1\n   */\n  float overall_fraction_ = 0.f;\n\n  /**\n   * Tracks current elapsed/eased fraction, for querying in\n   * getAnimatedFraction(). This is calculated by interpolating the fraction\n   * (range: [0, 1]) in the current iteration.\n   */\n  float current_fraction_ = 0.f;\n\n  /**\n   * Tracks the time (in milliseconds) when the last frame arrived.\n   */\n  int64_t last_frame_time_ = -1;\n\n  /**\n   * Tracks the time (in milliseconds) when the first frame arrived. Note the\n   * frame may arrive during the start delay.\n   */\n  int64_t first_frame_time_ = -1;\n\n  /**\n   * Additional playing state to indicate whether an animator has been\n   * start()'d. There is some lag between a call to start() and the first\n   * animation frame. We should still note that the animation has been started,\n   * even if it's first animation frame has not yet happened, and reflect that\n   * state in isRunning(). Note that delayed animations are different: they are\n   * not started until their first animation frame, which occurs after their\n   * delay elapses.\n   */\n  bool running_ = false;\n\n  /**\n   * Additional playing state to indicate whether an animator has been\n   * start()'d, whether or not there is a nonzero startDelay.\n   */\n  bool started_ = false;\n\n  /**\n   * Tracks whether we've notified listeners of the OnAnimationStart() event.\n   * This can be complex to keep track of since we notify listeners at different\n   * times depending on startDelay and whether start() was called before end().\n   */\n  bool start_listeners_called_ = false;\n\n  /**\n   * Flag that denotes whether the animation is set up and ready to go. Used to\n   * set up animation that has not yet been started.\n   */\n  bool initialized_ = false;\n\n  bool animation_end_requested_ = false;\n\n  // How long the animation should last in ms\n  int64_t duration_ = 300;\n\n  // The amount of time in ms to delay starting the animation after start() is\n  // called. Note that this start delay is unscaled. When there is a duration\n  // scale set on the animator, the scaling factor will be applied to this\n  // delay.\n  int64_t start_delay_ = 0;\n\n  // The number of times the animation will repeat. The default is 0, which\n  // means the animation will play only once\n  int repeat_count_ = 0;\n\n  /**\n   * The type of repetition that will occur when repeatMode is nonzero. kNormal\n   * means the animation will start from the beginning on every new cycle.\n   * kAlternate means the animation will reverse directions on each iteration.\n   */\n  RepeatMode repeat_mode_ = kNormal;\n\n  /**\n   * Sets how a animation applies styles to its target before and after its\n   * execution.\n   */\n  FillMode fill_mode_ = kNone;\n\n  /**\n   * Whether or not the animator should register for its own animation callback\n   * to receive animation pulse.\n   */\n  bool self_pulse_ = true;\n\n  /**\n   * Whether or not the animator has been requested to start without pulsing.\n   * This flag gets set in startWithoutPulsing(), and reset in start().\n   */\n  bool suppress_self_pulse_requested_ = false;\n\n  /**\n   * The time interpolator to be used. The elapsed fraction of the animation\n   * will be passed through this interpolator to calculate the interpolated\n   * fraction, which is then used to calculate the animated values.\n   */\n  std::unique_ptr<Interpolator> interpolator_;\n\n  /**\n   * The set of listeners to be sent events through the life of an animation.\n   */\n  std::forward_list<AnimatorUpdateListener*> update_listeners_;\n\n  /**\n   * Animation handler used to schedule updates for this animation.\n   */\n  AnimationHandler* animation_handler_ = nullptr;\n\n  /**\n   * If set to non-negative value, this will override duration_scale_s_.\n   */\n  float duration_scale_ = -1.f;\n\n  // System-wide animation scale.\n  static float duration_scale_s_;\n\n  // in case the end listener called more than once under forwards mode\n  bool end_listeners_called_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_VALUE_ANIMATOR_H_\n"
  },
  {
    "path": "clay/gfx/animation/value_animator_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/gfx/animation/value_animator.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nnamespace {\nclass MockAnimatorUpdateListener : public AnimatorUpdateListener {\n public:\n  // NOLINTNEXTLINE\n  MOCK_METHOD(void, OnAnimationUpdate, (ValueAnimator & animation), (override));\n};\n}  // namespace\n\nTEST(ValueAnimatorTest, AnimatorUpdateEvents) {\n  MockAnimatorUpdateListener update_listener;\n  EXPECT_CALL(update_listener, OnAnimationUpdate(::testing::_)).Times(6);\n\n  std::unique_ptr<AnimationHandler> handler =\n      std::make_unique<AnimationHandler>();\n  EXPECT_EQ(handler->GetAnimationCount(), 0);\n\n  ValueAnimator animator;\n  animator.SetDuration(160);\n  animator.AddUpdateListener(&update_listener);\n\n  animator.SetAnimationHandler(handler.get());\n  animator.Start();\n  EXPECT_EQ(handler->GetAnimationCount(), 1);\n\n  int64_t frame_time = 0;\n  for (size_t i = 1; i <= 5; i++) {\n    frame_time += 16;\n    handler->DoAnimationFrame(frame_time);\n  }\n\n  EXPECT_EQ(handler->GetAnimationCount(), 1);\n  animator.RemoveAllUpdateListeners();\n  animator.End();\n  EXPECT_EQ(handler->GetAnimationCount(), 0);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/viscous_fluid_interpolator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/animation/viscous_fluid_interpolator.h\"\n\n#include <cmath>\n#include <memory>\n#include <utility>\n\nnamespace clay {\nnamespace {\n\nconstexpr float kViscousFluidScale = 8.0f;\n\nfloat ViscousFluid(float x) {\n  x *= kViscousFluidScale;\n  if (x < 1.0f) {\n    x -= (1.0f - std::exp(-x));\n  } else {\n    float start = 0.36787944117f;  // 1/e == exp(-1)\n    x = 1.0f - std::exp(1.0f - x);\n    x = start + x * (1.0f - start);\n  }\n  return x;\n}\n\nstatic const float kViscousFluidNormalize = 1.0f / ViscousFluid(1.0f);\nstatic const float kViscousFluidOffset =\n    1.0f - kViscousFluidNormalize * ViscousFluid(1.0f);\n\n}  // namespace\n\nstd::unique_ptr<Interpolator> ViscousFluidInterpolator::Clone() {\n  return std::make_unique<ViscousFluidInterpolator>();\n}\n\nfloat ViscousFluidInterpolator::Interpolate(float input) {\n  const float interpolated = kViscousFluidNormalize * ViscousFluid(input);\n  if (interpolated > 0) {\n    return interpolated + kViscousFluidOffset;\n  }\n  return interpolated;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/animation/viscous_fluid_interpolator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ANIMATION_VISCOUS_FLUID_INTERPOLATOR_H_\n#define CLAY_GFX_ANIMATION_VISCOUS_FLUID_INTERPOLATOR_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n\nnamespace clay {\n\n// Copy from Android. Used by ScrollView.\nclass ViscousFluidInterpolator : public Interpolator {\n public:\n  float Interpolate(float input) override;\n  std::unique_ptr<Interpolator> Clone() override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ANIMATION_VISCOUS_FLUID_INTERPOLATOR_H_\n"
  },
  {
    "path": "clay/gfx/attributes.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ATTRIBUTES_H_\n#define CLAY_GFX_ATTRIBUTES_H_\n\n#include <cstddef>\n#include <memory>\n\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n// |D| is the base type for the attribute\n//     (i.e. DlColorFilter, etc.)\n// |S| is the base type for the Skia version of the attribute\n//     (i.e. SkColorFilter, etc.)\n// |T| is the enum that describes the specific subclasses\n//     (i.e DlColorFilterType, etc.)\ntemplate <class D, class S, typename T>\nclass Attribute {\n public:\n#ifndef ENABLE_SKITY\n  using GrPtr = sk_sp<S>;\n#else\n  using GrPtr = std::shared_ptr<S>;\n#endif  // ENABLE_SKITY\n\n  // Return the recognized specific type of the attribute.\n  virtual T type() const = 0;\n\n  // Return the size of the instantiated data (typically used to allocate)\n  // storage in the DisplayList buffer.\n  virtual size_t size() const = 0;\n\n  // Return a shared version of |this| attribute. The |shared_ptr| returned\n  // will reference a copy of this object so that the lifetime of the shared\n  // version is not tied to the storage of this particular instance.\n  virtual std::shared_ptr<D> shared() const = 0;\n\n  // Return an equivalent GrPtr version of this object.\n  virtual GrPtr gr_object() const = 0;\n\n  // Perform a content aware |==| comparison of the Attribute.\n  bool operator==(D const& other) const {\n    return type() == other.type() && equals_(other);\n  }\n  // Perform a content aware |!=| comparison of the Attribute.\n  bool operator!=(D const& other) const { return !(*this == other); }\n\n  virtual ~Attribute() = default;\n\n protected:\n  // Virtual comparison method to support |==| and |!=|.\n  virtual bool equals_(D const& other) const = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_ATTRIBUTES_H_\n"
  },
  {
    "path": "clay/gfx/attributes_testing.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_ATTRIBUTES_TESTING_H_\n#define CLAY_GFX_ATTRIBUTES_TESTING_H_\n\n#include <string>\n\n#include \"clay/gfx/attributes.h\"\n#include \"clay/gfx/comparable.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\ntemplate <class T>\nstatic void TestEquals(T& source1, T& source2) {\n  ASSERT_TRUE(source1 == source2);\n  ASSERT_TRUE(source2 == source1);\n  ASSERT_FALSE(source1 != source2);\n  ASSERT_FALSE(source2 != source1);\n  ASSERT_EQ(source1, source2);\n  ASSERT_EQ(source2, source1);\n  ASSERT_TRUE(clay::Equals(&source1, &source2));\n  ASSERT_TRUE(clay::Equals(&source2, &source1));\n}\n\ntemplate <class T>\nstatic void TestNotEquals(T& source1, T& source2, std::string label) {\n  ASSERT_FALSE(source1 == source2) << label;\n  ASSERT_FALSE(source2 == source1) << label;\n  ASSERT_TRUE(source1 != source2) << label;\n  ASSERT_TRUE(source2 != source1) << label;\n  ASSERT_NE(source1, source2) << label;\n  ASSERT_NE(source2, source1) << label;\n  ASSERT_TRUE(clay::NotEquals(&source1, &source2));\n  ASSERT_TRUE(clay::NotEquals(&source2, &source1));\n}\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_GFX_ATTRIBUTES_TESTING_H_\n"
  },
  {
    "path": "clay/gfx/comparable.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_COMPARABLE_H_\n#define CLAY_GFX_COMPARABLE_H_\n\n#include <memory>\n\nnamespace clay {\n\n// These templates implement deep pointer comparisons that compare not\n// just the pointers to the objects, but also their contents (provided\n// that the <T> class implements the == operator override).\n// Any combination of shared_ptr<T> or T* are supported and null pointers\n// are not equal to anything but another null pointer.\n\ntemplate <class T>\nbool Equals(const T* a, const T* b) {\n  if (a == b) {\n    return true;\n  }\n  if (!a || !b) {\n    return false;\n  }\n  return *a == *b;\n}\n\ntemplate <class T>\nbool Equals(std::shared_ptr<const T> a, const T* b) {\n  return Equals(a.get(), b);\n}\n\ntemplate <class T>\nbool Equals(std::shared_ptr<T> a, const T* b) {\n  return Equals(a.get(), b);\n}\n\ntemplate <class T>\nbool Equals(const T* a, std::shared_ptr<const T> b) {\n  return Equals(a, b.get());\n}\n\ntemplate <class T>\nbool Equals(const T* a, std::shared_ptr<T> b) {\n  return Equals(a, b.get());\n}\n\ntemplate <class T>\nbool Equals(std::shared_ptr<const T> a, std::shared_ptr<const T> b) {\n  return Equals(a.get(), b.get());\n}\n\ntemplate <class T>\nbool Equals(std::shared_ptr<T> a, std::shared_ptr<const T> b) {\n  return Equals(a.get(), b.get());\n}\n\ntemplate <class T>\nbool Equals(std::shared_ptr<const T> a, std::shared_ptr<T> b) {\n  return Equals(a.get(), b.get());\n}\n\ntemplate <class T>\nbool Equals(std::shared_ptr<T> a, std::shared_ptr<T> b) {\n  return Equals(a.get(), b.get());\n}\n\ntemplate <class T>\nbool NotEquals(const T* a, const T* b) {\n  return !Equals<T>(a, b);\n}\n\ntemplate <class T>\nbool NotEquals(std::shared_ptr<const T> a, const T* b) {\n  return !Equals(a.get(), b);\n}\n\ntemplate <class T>\nbool NotEquals(std::shared_ptr<T> a, const T* b) {\n  return !Equals(a.get(), b);\n}\n\ntemplate <class T>\nbool NotEquals(const T* a, std::shared_ptr<const T> b) {\n  return !Equals(a, b.get());\n}\n\ntemplate <class T>\nbool NotEquals(const T* a, std::shared_ptr<T> b) {\n  return !Equals(a, b.get());\n}\n\ntemplate <class T>\nbool NotEquals(std::shared_ptr<const T> a, std::shared_ptr<const T> b) {\n  return !Equals(a.get(), b.get());\n}\n\ntemplate <class T>\nbool NotEquals(std::shared_ptr<T> a, std::shared_ptr<const T> b) {\n  return !Equals(a.get(), b.get());\n}\n\ntemplate <class T>\nbool NotEquals(std::shared_ptr<const T> a, std::shared_ptr<T> b) {\n  return !Equals(a.get(), b.get());\n}\n\ntemplate <class T>\nbool NotEquals(std::shared_ptr<T> a, std::shared_ptr<T> b) {\n  return !Equals(a.get(), b.get());\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_COMPARABLE_H_\n"
  },
  {
    "path": "clay/gfx/geometry/axis.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_AXIS_H_\n#define CLAY_GFX_GEOMETRY_AXIS_H_\n\nnamespace clay {\n\nenum class Axis {\n  kX = 0,\n  kY,\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_AXIS_H_\n"
  },
  {
    "path": "clay/gfx/geometry/box_shadow_operations.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/box_shadow_operations.h\"\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/math_util.h\"\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n\nenum BoxShadowType {\n  kNone = 0,\n  kInset = 1,\n  kInitial,\n  kInherit,\n};\n\nBoxShadowOperation::BoxShadowOperation(const BoxShadowValue& value)\n    : shadow({BoxShadowType::kInset == static_cast<BoxShadowType>(value.option)\n                  ? true\n                  : false,\n              value.h_offset, value.v_offset, value.blur, value.spread,\n              value.color}) {}\n\nbool BoxShadowOperation::BlendOperations(const BoxShadowOperation* from,\n                                         const BoxShadowOperation* to,\n                                         float progress,\n                                         BoxShadowOperation* result) {\n  std::unique_ptr<BoxShadowOperation> op = nullptr;\n  FML_DCHECK(result);\n  if (from == nullptr) {\n    FML_DCHECK(to);\n    op = std::make_unique<BoxShadowOperation>();\n    from = op.get();\n  } else if (to == nullptr) {\n    FML_DCHECK(from);\n    op = std::make_unique<BoxShadowOperation>();\n    to = op.get();\n  }\n  FML_DCHECK(from->shadow.inset == to->shadow.inset);\n  result->shadow.inset = from->shadow.inset;\n  result->shadow.offset_x =\n      linearInterpolate(from->shadow.offset_x, to->shadow.offset_x, progress);\n  result->shadow.offset_y =\n      linearInterpolate(from->shadow.offset_y, to->shadow.offset_y, progress);\n  result->shadow.blur_radius = linearInterpolate(\n      from->shadow.blur_radius, to->shadow.blur_radius, progress);\n  result->shadow.spread_radius = linearInterpolate(\n      from->shadow.spread_radius, to->shadow.spread_radius, progress);\n  result->shadow.color =\n      Color::Lerp(from->shadow.color, to->shadow.color, progress);\n  return true;\n}\n\nBoxShadowOperations::BoxShadowOperations(\n    const std::vector<BoxShadowValue>& values) {\n  for (auto& value : values) {\n    operations_.emplace_back(value);\n  }\n}\n\nBoxShadowOperations BoxShadowOperations::Blend(const BoxShadowOperations& other,\n                                               float fraction) const {\n  BoxShadowOperations to_return;\n  if (!BlendInternal(other, fraction, &to_return)) {\n    // If the matrices cannot be blended, fallback to discrete animation\n    // logic. See\n    // https://drafts.csswg.org/css-transforms/#matrix-interpolation\n    to_return = fraction < 0.5 ? other : *this;\n  }\n  return to_return;\n}\n\nbool BoxShadowOperations::BlendInternal(const BoxShadowOperations& other,\n                                        float progress,\n                                        BoxShadowOperations* result) const {\n  size_t matching_prefix_length = MatchingPrefixLength(other);\n  size_t from_size = other.operations_.size();\n  size_t to_size = operations_.size();\n  for (size_t i = 0; i < matching_prefix_length; ++i) {\n    BoxShadowOperation blended;\n    if (!BoxShadowOperation::BlendOperations(\n            i >= from_size ? nullptr : &other.operations_[i],\n            i >= to_size ? nullptr : &operations_[i], progress, &blended)) {\n      return false;\n    }\n    result->Append(std::move(blended));\n  }\n  return true;\n}\n\nsize_t BoxShadowOperations::MatchingPrefixLength(\n    const BoxShadowOperations& other) const {\n  // If the operations match to the length of the shorter list, then pad its\n  // length with the matching identity operations.\n  // https://drafts.csswg.org/css-transforms/#transform-function-lists\n  return std::max(operations_.size(), other.operations_.size());\n}\n\nvoid BoxShadowOperations::Append(BoxShadowOperation op) {\n  operations_.emplace_back(op);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/box_shadow_operations.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_BOX_SHADOW_OPERATIONS_H_\n#define CLAY_GFX_GEOMETRY_BOX_SHADOW_OPERATIONS_H_\n\n#include <vector>\n\n#include \"clay/gfx/geometry/box_shadow_value.h\"\n#include \"clay/gfx/style/shadow.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\n\nstruct BoxShadowOperation {\n  BoxShadowOperation() = default;\n\n  explicit BoxShadowOperation(const BoxShadowValue& value);\n\n  static bool BlendOperations(const BoxShadowOperation* from,\n                              const BoxShadowOperation* to, float progress,\n                              BoxShadowOperation* result);\n\n  Shadow shadow;\n};\n\nclass BoxShadowOperations {\n public:\n  BoxShadowOperations() = default;\n  ~BoxShadowOperations() = default;\n\n  explicit BoxShadowOperations(const std::vector<BoxShadowValue>& values);\n\n  BoxShadowOperations Blend(const BoxShadowOperations& other,\n                            float fraction) const;\n\n  bool BlendInternal(const BoxShadowOperations& from, float progress,\n                     BoxShadowOperations* result) const;\n\n  size_t MatchingPrefixLength(const BoxShadowOperations& other) const;\n\n  void Append(BoxShadowOperation ops);\n\n  std::vector<Shadow> apply() const {\n    std::vector<Shadow> result;\n    for (auto& o : operations_) {\n      result.emplace_back(o.shadow);\n    }\n    return result;\n  }\n\n private:\n  std::vector<BoxShadowOperation> operations_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_BOX_SHADOW_OPERATIONS_H_\n"
  },
  {
    "path": "clay/gfx/geometry/box_shadow_value.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_BOX_SHADOW_VALUE_H_\n#define CLAY_GFX_GEOMETRY_BOX_SHADOW_VALUE_H_\n\nnamespace clay {\n\nstruct BoxShadowValue {\n  float h_offset;\n  float v_offset;\n  float blur;\n  float spread;\n  unsigned int color;\n  unsigned int option;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_BOX_SHADOW_VALUE_H_\n"
  },
  {
    "path": "clay/gfx/geometry/build.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\ngfx_geometry_sources = [\n  \"axis.h\",\n  \"box_shadow_operations.cc\",\n  \"box_shadow_operations.h\",\n  \"box_shadow_value.h\",\n  \"direction.h\",\n  \"filter_operations.cc\",\n  \"filter_operations.h\",\n  \"filter_value.h\",\n  \"float_point.cc\",\n  \"float_point.h\",\n  \"float_point3d.cc\",\n  \"float_point3d.h\",\n  \"float_rect.cc\",\n  \"float_rect.h\",\n  \"float_rounded_rect.cc\",\n  \"float_rounded_rect.h\",\n  \"float_size.h\",\n  \"float_vector2d.cc\",\n  \"float_vector2d.h\",\n  \"float_vector3d.cc\",\n  \"float_vector3d.h\",\n  \"math_util.h\",\n  \"path.cc\",\n  \"path.h\",\n  \"point.h\",\n  \"quaternion.cc\",\n  \"quaternion.h\",\n  \"rect.h\",\n  \"size.h\",\n  \"sticky_info.h\",\n  \"transform.cc\",\n  \"transform.h\",\n  \"transform_operation.cc\",\n  \"transform_operation.h\",\n  \"transform_operations.cc\",\n  \"transform_operations.h\",\n  \"transform_origin.h\",\n  \"transform_raw.h\",\n  \"transform_util.cc\",\n  \"transform_util.h\",\n]\n"
  },
  {
    "path": "clay/gfx/geometry/direction.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_DIRECTION_H_\n#define CLAY_GFX_GEOMETRY_DIRECTION_H_\n\nnamespace clay {\n\nenum class Direction {\n  kTop = 0,\n  kBottom,\n  kRight,\n  kLeft,\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_DIRECTION_H_\n"
  },
  {
    "path": "clay/gfx/geometry/filter_operations.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/filter_operations.h\"\n\n#include <algorithm>\n#include <cstddef>\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/transform_operation.h\"\n\nnamespace clay {\n\nnamespace {\nfloat linearInterpolate(float from, float to, float progress) {\n  return from * (1 - progress) + to * progress;\n}\nvoid ColorMatrixSetIdentity(std::array<float, 20>& matrix) {\n  matrix.fill(0.f);\n  matrix[0] = matrix[6] = matrix[12] = matrix[18] = 1.f;\n}\n\nvoid ColorMatrixPreConcat(float result[20], const float mat_a[20],\n                          const float mat_b[20]) {\n  float tmp[20];\n  float* target;\n\n  if (mat_a == result || mat_b == result) {\n    target = tmp;  // will memcpy answer when we're done into result\n  } else {\n    target = result;\n  }\n\n  int index = 0;\n  for (int j = 0; j < 20; j += 5) {\n    for (int i = 0; i < 4; i++) {\n      target[index++] =\n          mat_a[j + 0] * mat_b[i + 0] + mat_a[j + 1] * mat_b[i + 5] +\n          mat_a[j + 2] * mat_b[i + 10] + mat_a[j + 3] * mat_b[i + 15];\n    }\n    target[index++] = mat_a[j + 0] * mat_b[4] + mat_a[j + 1] * mat_b[9] +\n                      mat_a[j + 2] * mat_b[14] + mat_a[j + 3] * mat_b[19] +\n                      mat_a[j + 4];\n  }\n\n  if (target != result) {\n    std::copy_n(target, 20, result);\n  }\n}\n\n}  // namespace\n\nFilterOperation::FilterOperation(Type type, float value) : type(type) {\n  switch (type) {\n    case kTypeGrayscale:\n    case kTypeBlur:\n    case kTypeBrightness:\n    case kTypeContrast:\n    case kTypeSaturate:\n    case kTypeHueRotate:\n      this->value = value;\n      break;\n    case kTypeNone:\n    case kTypeMatrix:\n    case kTypeIdentity:\n      break;\n  }\n}\n\nFilterOperations::FilterOperations(const std::vector<FilterValue>& values) {\n  for (auto& value : values) {\n    FilterOperation op = FilterOperation(\n        static_cast<FilterOperation::Type>(value.type), value.value);\n    operations_.emplace_back(std::move(op));\n  }\n}\n\nvoid FilterOperation::Bake() {\n  ColorMatrixSetIdentity(color_matrix);\n  switch (type) {\n    case FilterOperation::kTypeGrayscale: {\n      auto v = 1 - value;\n      auto m01 = 0.2126f + 0.7874f * v;\n      auto m02 = 0.7152f - 0.7152f * v;\n      auto m10 = 0.2126f - 0.2126f * v;\n      auto m11 = 0.7152f + 0.2848f * v;\n      color_matrix = {m01,\n                      m02,\n                      1.f - (m01 + m02),\n                      0.f,\n                      0.f,\n                      m10,\n                      m11,\n                      1.f - (m10 + m11),\n                      0.f,\n                      0.f,\n                      m10,\n                      m02,\n                      1.f - (m10 + m02),\n                      0.f,\n                      0.f,\n                      0.f,\n                      0.f,\n                      0.f,\n                      1.f,\n                      0.f};\n      break;\n    }\n    case FilterOperation::kTypeBrightness: {\n      if (value != 1) {\n        // ref: cc/paint/render_surface_filters.cc\n        // [0, +inf]\n        color_matrix = {value, 0.f, 0.f,   0.f, 0.f, 0.f, value, 0.f, 0.f, 0.f,\n                        0.f,   0.f, value, 0.f, 0.f, 0.f, 0.f,   0.f, 1.f, 0.f};\n      }\n      break;\n    }\n    case FilterOperation::kTypeContrast: {\n      if (value != 1.0f) {\n        // ref: cc/paint/render_surface_filters.cc\n        auto v = -0.5f * value + 0.5f;\n        color_matrix = {\n            value, 0.f, 0.f,   0.f, v, 0.f, value, 0.f, 0.f, v,\n            0.f,   0.f, value, 0.f, v, 0.f, 0.f,   0.f, 1.f, 0.f,\n        };\n      }\n      break;\n    }\n    case FilterOperation::kTypeSaturate: {\n      if (value != 1.0f) {\n        auto m01 = 0.213f + 0.787f * value;\n        auto m02 = 0.715f - 0.715f * value;\n        auto m10 = 0.213f - 0.213f * value;\n        auto m11 = 0.715f + 0.285f * value;\n        color_matrix = {m01,\n                        m02,\n                        1.f - (m01 + m02),\n                        0.f,\n                        0.f,\n                        m10,\n                        m11,\n                        1.f - (m10 + m11),\n                        0.f,\n                        0.f,\n                        m10,\n                        m02,\n                        1.f - (m10 + m02),\n                        0.f,\n                        0.f,\n                        0.f,\n                        0.f,\n                        0.f,\n                        1.f,\n                        0.f};\n      }\n      break;\n    }\n    case FilterOperation::kTypeHueRotate: {\n      if (value != 0) {\n        float rad = value * 3.1415926 / 180;\n        float cos_hue = std::cosf(rad);\n        float sin_hue = std::sinf(rad);\n        color_matrix = {0.213f + cos_hue * 0.787f - sin_hue * 0.213f,\n                        0.715f - cos_hue * 0.715f - sin_hue * 0.715f,\n                        0.072f - cos_hue * 0.072f + sin_hue * 0.928f,\n                        0.f,\n                        0.f,\n                        0.213f - cos_hue * 0.213f + sin_hue * 0.143f,\n                        0.715f + cos_hue * 0.285f + sin_hue * 0.140f,\n                        0.072f - cos_hue * 0.072f - sin_hue * 0.283f,\n                        0.f,\n                        0.f,\n                        0.213f - cos_hue * 0.213f - sin_hue * 0.787f,\n                        0.715f - cos_hue * 0.715f + sin_hue * 0.715f,\n                        0.072f + cos_hue * 0.928f + sin_hue * 0.072f,\n                        0.f,\n                        0.f,\n                        0.f,\n                        0.f,\n                        0.f,\n                        1.f,\n                        0.f};\n      }\n      break;\n    }\n    case FilterOperation::kTypeBlur: {\n      break;\n    }\n    case FilterOperation::kTypeMatrix: {\n      break;\n    }\n    default:\n      break;\n  }\n}\n\nvoid FilterOperation::MakeDefault(Type type) {\n  switch (type) {\n    case kTypeGrayscale:\n    case kTypeHueRotate:\n    case kTypeBlur:\n      value = 0.f;\n      break;\n    case kTypeBrightness:\n    case kTypeContrast:\n    case kTypeSaturate:\n      value = 1.f;\n      break;\n    case kTypeNone:\n    case kTypeMatrix:\n    case kTypeIdentity:\n      break;\n  }\n}\n\nbool FilterOperation::BlendOperations(const FilterOperation* from,\n                                      const FilterOperation* to, float fraction,\n                                      FilterOperation* result) {\n  FilterOperation::Type interpolation_type = FilterOperation::kTypeIdentity;\n  std::unique_ptr<FilterOperation> op = nullptr;\n  if (from == nullptr) {\n    FML_DCHECK(to);\n    op = std::make_unique<FilterOperation>();\n    op->MakeDefault(to->type);\n    from = op.get();\n    interpolation_type = to->type;\n  } else if (to == nullptr) {\n    FML_DCHECK(from);\n    op = std::make_unique<FilterOperation>();\n    op->MakeDefault(from->type);\n    to = op.get();\n    interpolation_type = from->type;\n  } else {\n    interpolation_type = from->type;\n  }\n\n  result->type = interpolation_type;\n  result->value = linearInterpolate(from->value, to->value, fraction);\n  result->Bake();\n  return true;\n}\n\nstd::array<float, 20> FilterOperations::Apply() const {\n  std::array<float, 20> result;\n  ColorMatrixSetIdentity(result);\n  for (const auto& operation : operations_) {\n    ColorMatrixPreConcat(result.data(), result.data(),\n                         operation.color_matrix.data());\n  }\n  return result;\n}\n\nvoid FilterOperations::Grayscale(float value) {\n  FilterOperation operation;\n  operation.type = FilterOperation::kTypeGrayscale;\n  operation.value = value;\n  operation.Bake();\n  operations_.emplace_back(std::move(operation));\n}\n\nvoid FilterOperations::Contrast(float value) {\n  FilterOperation operation;\n  operation.type = FilterOperation::kTypeContrast;\n  operation.value = value;\n  operation.Bake();\n  operations_.emplace_back(std::move(operation));\n}\n\nvoid FilterOperations::Brightness(float value) {\n  FilterOperation operation;\n  operation.type = FilterOperation::kTypeBrightness;\n  operation.value = value;\n  operation.Bake();\n  operations_.emplace_back(std::move(operation));\n}\nvoid FilterOperations::Saturate(float value) {\n  FilterOperation operation;\n  operation.type = FilterOperation::kTypeSaturate;\n  operation.value = value;\n  operation.Bake();\n  operations_.emplace_back(std::move(operation));\n}\nvoid FilterOperations::HueRotate(float value) {\n  FilterOperation operation;\n  operation.type = FilterOperation::kTypeHueRotate;\n  operation.value = value;\n  operation.Bake();\n  operations_.emplace_back(std::move(operation));\n}\n\nsize_t FilterOperations::MatchingPrefixLength(\n    const FilterOperations& other) const {\n  size_t num_operations =\n      std::min(operations_.size(), other.operations_.size());\n  for (size_t i = 0; i < num_operations; ++i) {\n    if (operations_[i].type != other.operations_[i].type) {\n      // Remaining operations in each operations list require matrix/matrix3d\n      // interpolation.\n      return i;\n    }\n  }\n  // If the operations match to the length of the shorter list, then pad its\n  // length with the matching identity operations.\n  // https://drafts.csswg.org/css-transforms/#transform-function-lists\n  return std::max(operations_.size(), other.operations_.size());\n}\n\nvoid FilterOperations::Append(const FilterOperations& ops) {\n  for (const auto& operation : ops.operations_) {\n    operations_.emplace_back(operation);\n  }\n}\n\nvoid FilterOperations::Append(const FilterOperation& operation) {\n  operations_.push_back(operation);\n}\n\nFilterOperations FilterOperations::Blend(const FilterOperations& other,\n                                         float fraction) const {\n  FilterOperations to_return;\n  if (!BlendInternal(other, fraction, &to_return)) {\n    // If the matrices cannot be blended, fallback to discrete animation\n    // logic. See\n    // https://drafts.csswg.org/css-transforms/#matrix-interpolation\n    to_return = fraction < 0.5 ? other : *this;\n  }\n  return to_return;\n}\nbool FilterOperations::BlendInternal(const FilterOperations& other,\n                                     float fraction,\n                                     FilterOperations* result) const {\n  size_t matching_prefix_length = MatchingPrefixLength(other);\n  size_t from_size = other.operations_.size();\n  size_t to_size = operations_.size();\n  for (size_t i = 0; i < matching_prefix_length; ++i) {\n    FilterOperation blended;\n    if (!FilterOperation::BlendOperations(\n            i >= from_size ? nullptr : &other.operations_[i],\n            i >= to_size ? nullptr : &operations_[i], fraction, &blended)) {\n      return false;\n    }\n    if (blended.type == FilterOperation::kTypeBlur) {\n      result->blur_radius_ = blended.value;\n    } else {\n      result->Append(blended);\n    }\n  }\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/filter_operations.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_FILTER_OPERATIONS_H_\n#define CLAY_GFX_GEOMETRY_FILTER_OPERATIONS_H_\n\n#include <array>\n#include <vector>\n\n#include \"clay/gfx/geometry/filter_value.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\n\nstruct FilterOperation {\n  enum Type {\n    kTypeNone = 0,\n    kTypeGrayscale = 1,\n    kTypeBlur = 2,\n    kTypeBrightness = 3,\n    kTypeContrast = 4,\n    kTypeSaturate = 5,\n    kTypeHueRotate = 6,\n    kTypeMatrix = 7,\n    kTypeIdentity = 8,\n  };\n\n  FilterOperation() = default;\n\n  FilterOperation(Type type, float value);\n\n  Type type = kTypeIdentity;\n\n  // Is similar to Skia's SkColorMatrix.\n  std::array<float, 20> color_matrix = {0.f};\n\n  bool IsIdentity() const { return type == kTypeIdentity; }\n\n  float value;\n\n  void Bake();\n\n  void MakeDefault(Type type);\n\n  static bool BlendOperations(const FilterOperation* from,\n                              const FilterOperation* to, float progress,\n                              FilterOperation* result);\n};\n\nclass FilterOperations {\n public:\n  FilterOperations() = default;\n  ~FilterOperations() = default;\n\n  explicit FilterOperations(const std::vector<FilterValue>& values);\n\n  std::array<float, 20> Apply() const;\n\n  void Grayscale(float value);\n  void Contrast(float value);\n  void Brightness(float value);\n  void Saturate(float value);\n  void HueRotate(float value);\n\n  FilterOperations Blend(const FilterOperations& other, float fraction) const;\n\n  bool BlendInternal(const FilterOperations& from, float progress,\n                     FilterOperations* result) const;\n\n  size_t MatchingPrefixLength(const FilterOperations& other) const;\n\n  void Append(const FilterOperations& ops);\n\n  void Append(const FilterOperation& operation);\n\n  float GetBlurRadius() const { return blur_radius_; }\n\n private:\n  std::vector<FilterOperation> operations_;\n  float blur_radius_ = 0.f;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_FILTER_OPERATIONS_H_\n"
  },
  {
    "path": "clay/gfx/geometry/filter_value.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_FILTER_VALUE_H_\n#define CLAY_GFX_GEOMETRY_FILTER_VALUE_H_\n\nnamespace clay {\n\nstruct FilterValue {\n  int type;\n  double value;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_FILTER_VALUE_H_\n"
  },
  {
    "path": "clay/gfx/geometry/float_point.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/float_point.h\"\n\n#include \"clay/gfx/geometry/point.h\"\n\nnamespace clay {\n\nFloatPoint::FloatPoint(const Point& point) : x_(point.x()), y_(point.y()) {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/float_point.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_FLOAT_POINT_H_\n#define CLAY_GFX_GEOMETRY_FLOAT_POINT_H_\n\n#include <math.h>\n\n#include <string>\n\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/geometry/size.h\"\n\nnamespace clay {\nclass Point;\nclass Size;\n\nclass FloatPoint {\n public:\n  FloatPoint() : x_(0.0f), y_(0.0f) {}\n  FloatPoint(float x, float y) : x_(x), y_(y) {}\n  explicit FloatPoint(const Size& size) : x_(size.width()), y_(size.height()) {}\n  explicit FloatPoint(const Point& location);\n\n  float x() const { return x_; }\n  float y() const { return y_; }\n  float distance() const { return hypotf(x(), y()); }\n\n  void SetX(float x) { x_ = x; }\n  void SetY(float y) { y_ = y; }\n\n  void MoveBy(const FloatSize& float_size) {\n    Move(float_size.width(), float_size.height());\n  }\n  void MoveBy(const FloatPoint& offset) { Move(offset.x(), offset.y()); }\n  void Move(int dx, float dy) {\n    x_ += dx;\n    y_ += dy;\n  }\n  void Scale(float sx, float sy) {\n    x_ = x_ * sx;\n    y_ = y_ * sy;\n  }\n\n  bool IsOrigin() const { return (x_ == 0 && y_ == 0); }\n\n  FloatPoint ExpandedTo(const FloatPoint& other) const {\n    return FloatPoint(x_ > other.x_ ? x_ : other.x_,\n                      y_ > other.y_ ? y_ : other.y_);\n  }\n\n  FloatPoint ShrunkTo(const FloatPoint& other) const {\n    return FloatPoint(x_ < other.x_ ? x_ : other.x_,\n                      y_ < other.y_ ? y_ : other.y_);\n  }\n\n  std::string ToString() const {\n    return \"FloatPoint{\" + std::to_string(x_) + \",\" + std::to_string(y_) + \"}\";\n  }\n\n  static FloatPoint Lerp(FloatPoint a, FloatPoint b, float t) {\n    return {a.x_ + (b.x_ - a.x_) * t, a.y_ + (b.y_ - a.y_) * t};\n  }\n\n private:\n  float x_, y_;\n};\n\ninline FloatPoint& operator+=(FloatPoint& a, const FloatPoint& b) {\n  a.Move(b.x(), b.y());\n  return a;\n}\n\ninline FloatPoint& operator+=(FloatPoint& a, const FloatSize& b) {\n  a.Move(b.width(), b.height());\n  return a;\n}\n\ninline FloatPoint& operator-=(FloatPoint& a, const FloatPoint& b) {\n  a.Move(-b.x(), -b.y());\n  return a;\n}\n\ninline FloatPoint& operator-=(FloatPoint& a, const FloatSize& b) {\n  a.Move(-b.width(), -b.height());\n  return a;\n}\n\ninline FloatPoint operator+(const FloatPoint& a, const FloatPoint& b) {\n  return FloatPoint(a.x() + b.x(), a.y() + b.y());\n}\n\ninline FloatPoint operator-(const FloatPoint& a, const FloatPoint& b) {\n  return FloatPoint(a.x() - b.x(), a.y() - b.y());\n}\n\ninline FloatPoint operator*(const FloatPoint& a, float factor) {\n  return FloatPoint(a.x() * factor, a.y() * factor);\n}\n\ninline bool operator==(const FloatPoint& a, const FloatPoint& b) {\n  return a.x() == b.x() && a.y() == b.y();\n}\n\ninline bool operator!=(const FloatPoint& a, const FloatPoint& b) {\n  return a.x() != b.x() || a.y() != b.y();\n}\n\ninline FloatPoint operator-(const FloatPoint& a) { return {-a.x(), -a.y()}; }\n\ninline bool FindIntersection(const FloatPoint& p1, const FloatPoint& p2,\n                             const FloatPoint& d1, const FloatPoint& d2,\n                             FloatPoint* intersection) {\n  float px_length = p2.x() - p1.x();\n  float py_length = p2.y() - p1.y();\n\n  float dx_length = d2.x() - d1.x();\n  float dy_length = d2.y() - d1.y();\n\n  float denom = px_length * dy_length - py_length * dx_length;\n  if (!denom) {\n    return false;\n  }\n\n  float param =\n      ((d1.x() - p1.x()) * dy_length - (d1.y() - p1.y()) * dx_length) / denom;\n\n  intersection->SetX(p1.x() + param * px_length);\n  intersection->SetY(p1.y() + param * py_length);\n  return true;\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_FLOAT_POINT_H_\n"
  },
  {
    "path": "clay/gfx/geometry/float_point3d.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/float_point3d.h\"\n\n#include <vector>\n\nnamespace clay {\n\nstd::string FloatPoint3d::ToString() const {\n  auto str_printf = [=](char* str_buf, size_t size) {\n    return std::snprintf(str_buf, size, \"%f,%f,%f\", x_, y_, z_);\n  };\n  int sz = str_printf(nullptr, 0);\n  std::vector<char> buf(sz + 1);\n  str_printf(&buf[0], buf.size());\n  return std::string(buf.data());\n}\n\nFloatPoint3d operator+(const FloatPoint3d& lhs, const FloatVector3d& rhs) {\n  float x = lhs.x() + rhs.x();\n  float y = lhs.y() + rhs.y();\n  float z = lhs.z() + rhs.z();\n  return FloatPoint3d(x, y, z);\n}\n\n// Subtract a vector from a point, producing a new point offset by the vector's\n// inverse.\nFloatPoint3d operator-(const FloatPoint3d& lhs, const FloatVector3d& rhs) {\n  float x = lhs.x() - rhs.x();\n  float y = lhs.y() - rhs.y();\n  float z = lhs.z() - rhs.z();\n  return FloatPoint3d(x, y, z);\n}\n\n// Subtract one point from another, producing a vector that represents the\n// distances between the two points along each axis.\nFloatVector3d operator-(const FloatPoint3d& lhs, const FloatPoint3d& rhs) {\n  float x = lhs.x() - rhs.x();\n  float y = lhs.y() - rhs.y();\n  float z = lhs.z() - rhs.z();\n  return FloatVector3d(x, y, z);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/float_point3d.h",
    "content": "// Copyright (c) 2011 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_FLOAT_POINT3D_H_\n#define CLAY_GFX_GEOMETRY_FLOAT_POINT3D_H_\n\n#include <string>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_vector3d.h\"\n\nnamespace clay {\n\n// A point has an x, y and z coordinate.\nclass FloatPoint3d {\n public:\n  constexpr FloatPoint3d() : x_(0), y_(0), z_(0) {}\n  constexpr FloatPoint3d(float x, float y, float z) : x_(x), y_(y), z_(z) {}\n\n  constexpr explicit FloatPoint3d(const FloatPoint& point)\n      : x_(point.x()), y_(point.y()), z_(0) {}\n\n  void Scale(float scale) { Scale(scale, scale, scale); }\n\n  void Scale(float x_scale, float y_scale, float z_scale) {\n    SetPoint(x() * x_scale, y() * y_scale, z() * z_scale);\n  }\n\n  constexpr float x() const { return x_; }\n  constexpr float y() const { return y_; }\n  constexpr float z() const { return z_; }\n\n  void SetX(float x) { x_ = x; }\n  void SetY(float y) { y_ = y; }\n  void SetZ(float z) { z_ = z; }\n\n  void SetPoint(float x, float y, float z) {\n    x_ = x;\n    y_ = y;\n    z_ = z;\n  }\n\n  // Offset the point by the given vector.\n  void operator+=(const FloatVector3d& v) {\n    x_ += v.x();\n    y_ += v.y();\n    z_ += v.z();\n  }\n\n  // Offset the point by the given vector's inverse.\n  void operator-=(const FloatVector3d& v) {\n    x_ -= v.x();\n    y_ -= v.y();\n    z_ -= v.z();\n  }\n\n  // Returns the squared euclidean distance between two points.\n  float SquaredDistanceTo(const FloatPoint3d& other) const {\n    float dx = x_ - other.x_;\n    float dy = y_ - other.y_;\n    float dz = z_ - other.z_;\n    return dx * dx + dy * dy + dz * dz;\n  }\n\n  FloatPoint AsPointF() const { return FloatPoint(x_, y_); }\n\n  // Returns a string representation of 3d point.\n  std::string ToString() const;\n\n private:\n  float x_;\n  float y_;\n  float z_;\n\n  // copy/assign are allowed.\n};\n\ninline bool operator==(const FloatPoint3d& lhs, const FloatPoint3d& rhs) {\n  return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z();\n}\n\ninline bool operator!=(const FloatPoint3d& lhs, const FloatPoint3d& rhs) {\n  return !(lhs == rhs);\n}\n\n// Add a vector to a point, producing a new point offset by the vector.\nFloatPoint3d operator+(const FloatPoint3d& lhs, const FloatVector3d& rhs);\n\n// Subtract a vector from a point, producing a new point offset by the vector's\n// inverse.\nFloatPoint3d operator-(const FloatPoint3d& lhs, const FloatVector3d& rhs);\n\n// Subtract one point from another, producing a vector that represents the\n// distances between the two points along each axis.\nFloatVector3d operator-(const FloatPoint3d& lhs, const FloatPoint3d& rhs);\n\ninline FloatPoint3d PointAtOffsetFromOrigin(const FloatVector3d& offset) {\n  return FloatPoint3d(offset.x(), offset.y(), offset.z());\n}\n\ninline FloatPoint3d ScalePoint(const FloatPoint3d& p, float x_scale,\n                               float y_scale, float z_scale) {\n  return FloatPoint3d(p.x() * x_scale, p.y() * y_scale, p.z() * z_scale);\n}\n\ninline FloatPoint3d ScalePoint(const FloatPoint3d& p, const FloatVector3d& v) {\n  return FloatPoint3d(p.x() * v.x(), p.y() * v.y(), p.z() * v.z());\n}\n\ninline FloatPoint3d ScalePoint(const FloatPoint3d& p, float scale) {\n  return ScalePoint(p, scale, scale, scale);\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_FLOAT_POINT3D_H_\n"
  },
  {
    "path": "clay/gfx/geometry/float_rect.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/float_rect.h\"\n\n#include <algorithm>\n#include <sstream>\n\n#include \"clay/gfx/geometry/rect.h\"\n\nnamespace clay {\n\nFloatRect::FloatRect(const Rect& rect)\n    : location_(rect.location()), size_(rect.size()) {}\n\nFloatRect::FloatRect(const skity::Rect& r)\n    : location_(r.Left(), r.Top()), size_(r.Width(), r.Height()) {}\n\nbool FloatRect::Contains(const FloatRect& other) const {\n  return x() <= other.x() && MaxX() >= other.MaxX() && y() <= other.y() &&\n         MaxY() >= other.MaxY();\n}\n\nvoid FloatRect::Intersect(const FloatRect& other) {\n  float left = std::max(x(), other.x());\n  float top = std::max(y(), other.y());\n  float right = std::min(MaxX(), other.MaxX());\n  float bottom = std::min(MaxY(), other.MaxY());\n\n  // Return a clean empty rectangle for non-intersecting cases.\n  if (left >= right || top >= bottom) {\n    left = 0;\n    top = 0;\n    right = 0;\n    bottom = 0;\n  }\n\n  SetLocationAndSizeFromEdges(left, top, right, bottom);\n}\n\nbool FloatRect::Intersects(const FloatRect& other) const {\n  return x() < other.MaxX() && MaxX() > other.x() && y() < other.MaxY() &&\n         MaxY() > other.y();\n}\n\nvoid FloatRect::Extend(const FloatPoint& p) {\n  float min_x = std::min(x(), p.x());\n  float min_y = std::min(y(), p.y());\n  float max_x = std::max(this->MaxX(), p.x());\n  float max_y = std::max(this->MaxY(), p.y());\n\n  SetLocationAndSizeFromEdges(min_x, min_y, max_x, max_y);\n}\n\nvoid FloatRect::ExpandToInclude(const FloatRect& rect, bool exclude_empty) {\n  if (exclude_empty) {\n    if (IsEmpty()) {\n      *this = rect;\n      return;\n    } else if (rect.IsEmpty()) {\n      return;\n    }\n  }\n\n  float min_x = std::min(x(), rect.x());\n  float min_y = std::min(y(), rect.y());\n  float max_x = std::max(this->MaxX(), rect.MaxX());\n  float max_y = std::max(this->MaxY(), rect.MaxY());\n\n  SetLocationAndSizeFromEdges(min_x, min_y, max_x, max_y);\n}\n\nstd::string FloatRect::ToString() const {\n  std::stringstream ss;\n  ss << location_.x() << \",\" << location_.y() << \" \" << size_.width() << \"*\"\n     << size_.height();\n  return ss.str();\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/float_rect.h",
    "content": "/*\n * Copyright (C) 2003, 2006, 2007 Apple Inc.  All rights reserved.\n * Copyright (C) 2005 Nokia.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * Copyright 2021 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#ifndef CLAY_GFX_GEOMETRY_FLOAT_RECT_H_\n#define CLAY_GFX_GEOMETRY_FLOAT_RECT_H_\n\n#include <algorithm>\n#include <string>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nclass Rect;\n\nclass FloatRect {\n public:\n  FloatRect() = default;\n  FloatRect(const FloatPoint& location, const FloatSize& size)\n      : location_(location), size_(size) {}\n  FloatRect(float x, float y, float width, float height)\n      : location_(FloatPoint(x, y)), size_(FloatSize(width, height)) {}\n  explicit FloatRect(const Rect&);\n  explicit FloatRect(const skity::Rect&);\n\n  FloatPoint location() const { return location_; }\n  FloatSize size() const { return size_; }\n\n  void SetLocation(const FloatPoint& location) { location_ = location; }\n  void SetSize(const FloatSize& size) { size_ = size; }\n\n  float x() const { return location_.x(); }\n  float y() const { return location_.y(); }\n  float MaxX() const { return x() + width(); }\n  float MaxY() const { return y() + height(); }\n  float width() const { return size_.width(); }\n  float height() const { return size_.height(); }\n\n  float left() const { return location_.x(); }\n  float top() const { return location_.y(); }\n  float right() const { return location_.x() + size_.width(); }\n  float bottom() const { return location_.y() + size_.height(); }\n  FloatPoint origin() const { return location_; }\n\n  void SetX(float x) { location_.SetX(x); }\n  void SetY(float y) { location_.SetY(y); }\n  void SetWidth(float width) { size_.SetWidth(width); }\n  void SetHeight(float height) { size_.SetHeight(height); }\n\n  bool IsEmpty() const { return size_.IsEmpty(); }\n\n  FloatPoint Center() const {\n    return FloatPoint(x() + width() / 2, y() + height() / 2);\n  }\n\n  void Move(const FloatSize& delta) { location_ += delta; }\n  void MoveBy(const FloatPoint& delta) { location_.Move(delta.x(), delta.y()); }\n  void Move(float dx, float dy) { location_.Move(dx, dy); }\n\n  void Expand(float top, float right, float bottom, float left) {\n    location_.Move(-left, -top);\n    size_.Expand(left + right, top + bottom);\n  }\n  void Expand(const FloatSize& size) { size_ += size; }\n  void Expand(float dw, float dh) { size_.Expand(dw, dh); }\n  void Contract(const FloatSize& size) { size_ -= size; }\n  void Contract(float dw, float dh) { size_.Expand(-dw, -dh); }\n\n  void ShiftXEdgeTo(float edge) {\n    float delta = edge - x();\n    SetX(edge);\n    SetWidth(std::max(0.0f, width() - delta));\n  }\n  void ShiftMaxXEdgeTo(float edge) {\n    float delta = edge - MaxX();\n    SetWidth(std::max(0.0f, width() + delta));\n  }\n  void ShiftYEdgeTo(float edge) {\n    float delta = edge - y();\n    SetY(edge);\n    SetHeight(std::max(0.0f, height() - delta));\n  }\n  void ShiftMaxYEdgeTo(float edge) {\n    float delta = edge - MaxY();\n    SetHeight(std::max(0.0f, height() + delta));\n  }\n\n  void InflateX(float dx) {\n    location_.SetX(location_.x() - dx);\n    size_.SetWidth(size_.width() + dx + dx);\n  }\n  void InflateY(float dy) {\n    location_.SetY(location_.y() - dy);\n    size_.SetHeight(size_.height() + dy + dy);\n  }\n\n  void Inflate(float d) {\n    InflateX(d);\n    InflateY(d);\n  }\n\n  FloatPoint MinXMinYCorner() const { return location_; }  // typically topLeft\n  FloatPoint MaxXMinYCorner() const {\n    return FloatPoint(location_.x() + size_.width(), location_.y());\n  }  // typically topRight\n  FloatPoint MinXMaxYCorner() const {\n    return FloatPoint(location_.x(), location_.y() + size_.height());\n  }  // typically bottomLeft\n  FloatPoint MaxXMaxYCorner() const {\n    return FloatPoint(location_.x() + size_.width(),\n                      location_.y() + size_.height());\n  }  // typically bottomRight\n\n  bool Contains(const FloatRect&) const;\n\n  void Intersect(const FloatRect&);\n  bool Intersects(const FloatRect&) const;\n  void Extend(const FloatPoint&);\n  void ExpandToInclude(const FloatRect&, bool exclude_empty = false);\n\n  bool Contains(const FloatPoint& point) const {\n    return Contains(point.x(), point.y());\n  }\n\n  bool Contains(float px, float py) const {\n    return px >= x() && px <= MaxX() && py >= y() && py <= MaxY();\n  }\n  std::string ToString() const;\n\n  operator skity::Rect() const {\n    return skity::Rect::MakeXYWH(x(), y(), width(), height());\n  }\n\n private:\n  void SetLocationAndSizeFromEdges(float left, float top, float right,\n                                   float bottom) {\n    location_.SetX(left);\n    location_.SetY(top);\n    size_.SetWidth(right - left);\n    size_.SetHeight(bottom - top);\n  }\n\n  FloatPoint location_;\n  FloatSize size_;\n};\n\ninline FloatRect& operator+=(FloatRect& a, const FloatRect& b) {\n  a.Move(b.x(), b.y());\n  a.SetWidth(a.width() + b.width());\n  a.SetHeight(a.height() + b.height());\n  return a;\n}\n\ninline FloatRect operator+(const FloatRect& a, const FloatRect& b) {\n  FloatRect c = a;\n  c += b;\n  return c;\n}\n\ninline FloatRect operator*(const FloatRect& a, float factor) {\n  return FloatRect(a.x() * factor, a.y() * factor, a.width() * factor,\n                   a.height() * factor);\n}\n\ninline bool operator==(const FloatRect& a, const FloatRect& b) {\n  return a.location() == b.location() && a.size() == b.size();\n}\n\ninline bool operator!=(const FloatRect& a, const FloatRect& b) {\n  return a.location() != b.location() || a.size() != b.size();\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_FLOAT_RECT_H_\n"
  },
  {
    "path": "clay/gfx/geometry/float_rounded_rect.cc",
    "content": "/*\n * Copyright 2021 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n *\n * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above\n *    copyright notice, this list of conditions and the following\n *    disclaimer.\n * 2. Redistributions in binary form must reproduce the above\n *    copyright notice, this list of conditions and the following\n *    disclaimer in the documentation and/or other materials\n *    provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n\n#include <algorithm>\n\nnamespace clay {\n\nFloatRoundedRect::FloatRoundedRect(float x, float y, float width, float height)\n    : rect_(x, y, width, height) {}\n\nFloatRoundedRect::FloatRoundedRect(const FloatRect& rect, const Radii& radii)\n    : rect_(rect), radii_(radii) {}\n\nFloatRoundedRect::FloatRoundedRect(const FloatRect& rect,\n                                   const FloatSize& top_left,\n                                   const FloatSize& top_right,\n                                   const FloatSize& bottom_left,\n                                   const FloatSize& bottom_right)\n    : rect_(rect), radii_(top_left, top_right, bottom_left, bottom_right) {}\n\nbool FloatRoundedRect::Radii::IsZero() const {\n  return top_left_.IsZero() && top_right_.IsZero() && bottom_left_.IsZero() &&\n         bottom_right_.IsZero();\n}\n\nvoid FloatRoundedRect::SetOval(const FloatRect& rect) {\n  rect_ = rect;\n  FloatSize fs = {rect.width() / 2.0f, rect.height() / 2.0f};\n  radii_ = {fs, fs, fs, fs};\n}\n\nvoid FloatRoundedRect::Radii::Expand(float top_width, float right_width,\n                                     float bottom_width, float left_width) {\n  if (top_left_.width() > 0 && top_left_.height() > 0) {\n    top_left_.SetWidth(std::max<float>(0, top_left_.width() + left_width));\n    top_left_.SetHeight(std::max<float>(0, top_left_.height() + top_width));\n  }\n  if (top_right_.width() > 0 && top_right_.height() > 0) {\n    top_right_.SetWidth(std::max<float>(0, top_right_.width() + right_width));\n    top_right_.SetHeight(std::max<float>(0, top_right_.height() + top_width));\n  }\n  if (bottom_left_.width() > 0 && bottom_left_.height() > 0) {\n    bottom_left_.SetWidth(\n        std::max<float>(0, bottom_left_.width() + left_width));\n    bottom_left_.SetHeight(\n        std::max<float>(0, bottom_left_.height() + bottom_width));\n  }\n  if (bottom_right_.width() > 0 && bottom_right_.height() > 0) {\n    bottom_right_.SetWidth(\n        std::max<float>(0, bottom_right_.width() + right_width));\n    bottom_right_.SetHeight(\n        std::max<float>(0, bottom_right_.height() + bottom_width));\n  }\n}\n\nvoid FloatRoundedRect::Radii::Shrink(float top_width, float right_width,\n                                     float bottom_width, float left_width) {\n  top_left_.SetWidth(std::max<float>(0, top_left_.width() - left_width));\n  top_left_.SetHeight(std::max<float>(0, top_left_.height() - top_width));\n\n  top_right_.SetWidth(std::max<float>(0, top_right_.width() - right_width));\n  top_right_.SetHeight(std::max<float>(0, top_right_.height() - top_width));\n\n  bottom_left_.SetWidth(std::max<float>(0, bottom_left_.width() - left_width));\n  bottom_left_.SetHeight(\n      std::max<float>(0, bottom_left_.height() - bottom_width));\n\n  bottom_right_.SetWidth(\n      std::max<float>(0, bottom_right_.width() - right_width));\n  bottom_right_.SetHeight(\n      std::max<float>(0, bottom_right_.height() - bottom_width));\n}\n\nbool FloatRoundedRect::IsRenderable() const {\n  return radii_.TopLeft().width() + radii_.TopRight().width() <=\n             rect_.width() + 0.0001 &&\n         radii_.BottomLeft().width() + radii_.BottomRight().width() <=\n             rect_.width() + 0.0001 &&\n         radii_.TopLeft().height() + radii_.BottomLeft().height() <=\n             rect_.height() + 0.0001 &&\n         radii_.TopRight().height() + radii_.BottomRight().height() <=\n             rect_.height() + 0.0001;\n}\n\nvoid FloatRoundedRect::Radii::Scale(float factor) {\n  if (factor == 1) {\n    return;\n  }\n\n  top_left_.ScaleSize(factor);\n  if (!top_left_.width() || !top_left_.height()) {\n    top_left_ = FloatSize();\n  }\n  top_right_.ScaleSize(factor);\n  if (!top_right_.width() || !top_right_.height()) {\n    top_right_ = FloatSize();\n  }\n  bottom_left_.ScaleSize(factor);\n  if (!bottom_left_.width() || !bottom_left_.height()) {\n    bottom_left_ = FloatSize();\n  }\n  bottom_right_.ScaleSize(factor);\n  if (!bottom_right_.width() || !bottom_right_.height()) {\n    bottom_right_ = FloatSize();\n  }\n}\n\nvoid FloatRoundedRect::ConstraintRadii() {\n  // Consider precision problem here. When factor is float,\n  // IsRenderable() may return false.\n  float factor = 1;\n  float horizontal_sum =\n      std::max(radii().TopLeft().width() + radii().TopRight().width(),\n               radii().BottomLeft().width() + radii().BottomRight().width());\n  if (horizontal_sum > rect().width()) {\n    factor = std::min(rect().width() / horizontal_sum, factor);\n  }\n  float vertical_sum =\n      std::max(radii().TopLeft().height() + radii().BottomLeft().height(),\n               radii().TopRight().height() + radii().BottomRight().height());\n  if (vertical_sum > rect().height()) {\n    factor = std::min(rect().height() / vertical_sum, factor);\n  }\n  int precision = 1e6 * factor;\n  factor = precision / 1e6;\n  radii_.Scale(factor);\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/float_rounded_rect.h",
    "content": "/*\n * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n *\n * 1. Redistributions of source code must retain the above\n *    copyright notice, this list of conditions and the following\n *    disclaimer.\n * 2. Redistributions in binary form must reproduce the above\n *    copyright notice, this list of conditions and the following\n *    disclaimer in the documentation and/or other materials\n *    provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n * OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * Copyright 2021 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#ifndef CLAY_GFX_GEOMETRY_FLOAT_ROUNDED_RECT_H_\n#define CLAY_GFX_GEOMETRY_FLOAT_ROUNDED_RECT_H_\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"skity/geometry/rrect.hpp\"\n\nnamespace clay {\n\nclass FloatRoundedRect {\n public:\n  class Radii {\n   public:\n    Radii() {}\n    Radii(const FloatSize& top_left, const FloatSize& top_right,\n          const FloatSize& bottom_left, const FloatSize& bottom_right)\n        : top_left_(top_left),\n          top_right_(top_right),\n          bottom_left_(bottom_left),\n          bottom_right_(bottom_right) {}\n\n    void SetTopLeft(const FloatSize& size) { top_left_ = size; }\n    void SetTopRight(const FloatSize& size) { top_right_ = size; }\n    void SetBottomLeft(const FloatSize& size) { bottom_left_ = size; }\n    void SetBottomRight(const FloatSize& size) { bottom_right_ = size; }\n    const FloatSize& TopLeft() const { return top_left_; }\n    const FloatSize& TopRight() const { return top_right_; }\n    const FloatSize& BottomLeft() const { return bottom_left_; }\n    const FloatSize& BottomRight() const { return bottom_right_; }\n\n    bool IsZero() const;\n\n    void Expand(float top_width, float bottom_width, float left_width,\n                float right_width);\n    void Expand(float size) { Expand(size, size, size, size); }\n    void Shrink(float top_width, float bottom_width, float left_width,\n                float right_width);\n    void Shrink(float size) { Shrink(size, size, size, size); }\n    void Scale(float factor);\n\n   private:\n    FloatSize top_left_;\n    FloatSize top_right_;\n    FloatSize bottom_left_;\n    FloatSize bottom_right_;\n  };\n\n  FloatRoundedRect() {}\n  explicit FloatRoundedRect(const FloatRect&, const Radii& = Radii());\n  FloatRoundedRect(float x, float y, float width, float height);\n  FloatRoundedRect(const FloatRect&, const FloatSize& top_left,\n                   const FloatSize& top_right, const FloatSize& bottom_left,\n                   const FloatSize& bottom_right);\n\n  const FloatRect& rect() const { return rect_; }\n  const Radii& radii() const { return radii_; }\n  bool IsRounded() const { return !radii_.IsZero(); }\n  bool IsEmpty() const { return rect_.IsEmpty(); }\n\n  void SetRect(const FloatRect& rect) { rect_ = rect; }\n  void SetRadii(const Radii& radii) { radii_ = radii; }\n  void SetOval(const FloatRect& rect);\n\n  void Move(const FloatSize& size) { rect_.Move(size); }\n  void ExpandRadii(float size) { radii_.Expand(size); }\n  void ShrinkRadii(float size) { radii_.Shrink(size); }\n\n  FloatRect TopLeftCorner() const {\n    return FloatRect(rect_.x(), rect_.y(), radii_.TopLeft().width(),\n                     radii_.TopLeft().height());\n  }\n  FloatRect TopRightCorner() const {\n    return FloatRect(rect_.MaxX() - radii_.TopRight().width(), rect_.y(),\n                     radii_.TopRight().width(), radii_.TopRight().height());\n  }\n  FloatRect BottomLeftCorner() const {\n    return FloatRect(rect_.x(), rect_.MaxY() - radii_.BottomLeft().height(),\n                     radii_.BottomLeft().width(), radii_.BottomLeft().height());\n  }\n  FloatRect BottomRightCorner() const {\n    return FloatRect(rect_.MaxX() - radii_.BottomRight().width(),\n                     rect_.MaxY() - radii_.BottomRight().height(),\n                     radii_.BottomRight().width(),\n                     radii_.BottomRight().height());\n  }\n\n  void ConstraintRadii();\n  bool IsRenderable() const;\n  operator skity::RRect() const;\n\n private:\n  FloatRect rect_;\n  Radii radii_;\n};\n\ninline FloatRoundedRect::operator skity::RRect() const {\n  skity::RRect rrect;\n\n  if (IsRounded()) {\n    skity::Vec2 radii[4];\n    radii[skity::RRect::Corner::kUpperLeft] = {TopLeftCorner().width(),\n                                               TopLeftCorner().height()};\n    radii[skity::RRect::Corner::kUpperRight] = {TopRightCorner().width(),\n                                                TopRightCorner().height()};\n    radii[skity::RRect::Corner::kLowerRight] = {BottomRightCorner().width(),\n                                                BottomRightCorner().height()};\n    radii[skity::RRect::Corner::kLowerLeft] = {BottomLeftCorner().width(),\n                                               BottomLeftCorner().height()};\n\n    rrect.SetRectRadii(rect(), radii);\n  } else {\n    rrect.SetRect(rect());\n  }\n\n  return rrect;\n}\n\ninline bool operator==(const FloatRoundedRect::Radii& a,\n                       const FloatRoundedRect::Radii& b) {\n  return a.TopLeft() == b.TopLeft() && a.TopRight() == b.TopRight() &&\n         a.BottomLeft() == b.BottomLeft() && a.BottomRight() == b.BottomRight();\n}\n\ninline bool operator==(const FloatRoundedRect& a, const FloatRoundedRect& b) {\n  return a.rect() == b.rect() && a.radii() == b.radii();\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_FLOAT_ROUNDED_RECT_H_\n"
  },
  {
    "path": "clay/gfx/geometry/float_size.h",
    "content": "/*\n * Copyright (C) 2003, 2006 Apple Computer, Inc.  All rights reserved.\n * Copyright (C) 2005 Nokia.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * Copyright 2021 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#ifndef CLAY_GFX_GEOMETRY_FLOAT_SIZE_H_\n#define CLAY_GFX_GEOMETRY_FLOAT_SIZE_H_\n\n#include <math.h>\n\n#include <cmath>\n#include <limits>\n#include <sstream>\n#include <string>\n\n#include \"clay/gfx/geometry/size.h\"\n\nnamespace clay {\n\nclass FloatSize {\n public:\n  FloatSize() : width_(0.0f), height_(0.0f) {}\n\n  FloatSize(float width, float height) : width_(width), height_(height) {}\n  explicit FloatSize(const Size& size)\n      : width_(size.width()), height_(size.height()) {}\n\n  float width() const { return width_; }\n  float height() const { return height_; }\n\n  float distance() const { return hypotf(width(), height()); }\n\n  void SetWidth(float width) { width_ = width; }\n  void SetHeight(float height) { height_ = height; }\n\n  bool IsEmpty() const { return width_ <= 0.0f || height_ <= 0.0f; }\n\n  bool IsZero() const {\n    return fabs(width_) < std::numeric_limits<float>::epsilon() &&\n           fabs(height_) < std::numeric_limits<float>::epsilon();\n  }\n\n  void Expand(float width, float height) {\n    width_ += width;\n    height_ += height;\n  }\n\n  void ExpandByRatio(float ratio) {\n    width_ *= ratio;\n    height_ *= ratio;\n  }\n\n  std::string ToString() const {\n    std::stringstream ss;\n    ss << \"(width: \" << width() << \", height: \" << height() << \")\";\n    return ss.str();\n  }\n\n  void ScaleSize(float factor_x, float factor_y) {\n    width_ *= factor_x;\n    height_ *= factor_y;\n  }\n\n  void ScaleSize(float factor) { ScaleSize(factor, factor); }\n\n private:\n  float width_;\n  float height_;\n};\n\ninline FloatSize& operator+=(FloatSize& a, const FloatSize& b) {\n  a.SetWidth(a.width() + b.width());\n  a.SetHeight(a.height() + b.height());\n  return a;\n}\n\ninline FloatSize& operator-=(FloatSize& a, const FloatSize& b) {\n  a.SetWidth(a.width() - b.width());\n  a.SetHeight(a.height() - b.height());\n  return a;\n}\n\ninline FloatSize operator+(const FloatSize& a, const FloatSize& b) {\n  return FloatSize(a.width() + b.width(), a.height() + b.height());\n}\n\ninline FloatSize operator-(const FloatSize& a, const FloatSize& b) {\n  return FloatSize(a.width() - b.width(), a.height() - b.height());\n}\n\ninline FloatSize operator-(const FloatSize& size) {\n  return FloatSize(-size.width(), -size.height());\n}\n\ninline FloatSize operator*(const FloatSize& a, float factor) {\n  return FloatSize(a.width() * factor, a.height() * factor);\n}\n\ninline bool operator==(const FloatSize& a, const FloatSize& b) {\n  return a.width() == b.width() && a.height() == b.height();\n}\n\ninline bool operator!=(const FloatSize& a, const FloatSize& b) {\n  return a.width() != b.width() || a.height() != b.height();\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_FLOAT_SIZE_H_\n"
  },
  {
    "path": "clay/gfx/geometry/float_vector2d.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/float_vector2d.h\"\n\n#include <cmath>\n#include <cstdio>\n#include <vector>\n\nnamespace clay {\n\nstd::string FloatVector2d::ToString() const {\n  const char* fmt = \"[%f %f]\";\n  int sz = std::snprintf(nullptr, 0, fmt, x_, y_);\n  std::vector<char> buf(sz + 1);\n  std::snprintf(&buf[0], buf.size(), fmt, x_, y_);\n  return std::string(buf.data());\n}\n\nbool FloatVector2d::IsZero() const { return x_ == 0 && y_ == 0; }\n\nvoid FloatVector2d::Add(const FloatVector2d& other) {\n  x_ += other.x_;\n  y_ += other.y_;\n}\n\nvoid FloatVector2d::Subtract(const FloatVector2d& other) {\n  x_ -= other.x_;\n  y_ -= other.y_;\n}\n\ndouble FloatVector2d::LengthSquared() const {\n  return static_cast<double>(x_) * x_ + static_cast<double>(y_) * y_;\n}\n\nfloat FloatVector2d::Length() const {\n  return static_cast<float>(std::sqrt(LengthSquared()));\n}\n\nvoid FloatVector2d::Scale(float x_scale, float y_scale) {\n  x_ *= x_scale;\n  y_ *= y_scale;\n}\n\ndouble CrossProduct(const FloatVector2d& lhs, const FloatVector2d& rhs) {\n  return static_cast<double>(lhs.x()) * rhs.y() -\n         static_cast<double>(lhs.y()) * rhs.x();\n}\n\ndouble DotProduct(const FloatVector2d& lhs, const FloatVector2d& rhs) {\n  return static_cast<double>(lhs.x()) * rhs.x() +\n         static_cast<double>(lhs.y()) * rhs.y();\n}\n\nFloatVector2d ScaleVector2d(const FloatVector2d& v, float x_scale,\n                            float y_scale) {\n  FloatVector2d scaled_v(v);\n  scaled_v.Scale(x_scale, y_scale);\n  return scaled_v;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/float_vector2d.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_FLOAT_VECTOR2D_H_\n#define CLAY_GFX_GEOMETRY_FLOAT_VECTOR2D_H_\n\n#include <string>\n\nnamespace clay {\n\nclass FloatVector2d {\n public:\n  constexpr FloatVector2d() : x_(0), y_(0) {}\n  constexpr FloatVector2d(float x, float y) : x_(x), y_(y) {}\n\n  constexpr float x() const { return x_; }\n  void SetX(float x) { x_ = x; }\n\n  constexpr float y() const { return y_; }\n  void SetY(float y) { y_ = y; }\n\n  // True if both components of the vector are 0.\n  bool IsZero() const;\n\n  // Add the components of the |other| vector to the current vector.\n  void Add(const FloatVector2d& other);\n  // Subtract the components of the |other| vector from the current vector.\n  void Subtract(const FloatVector2d& other);\n\n  void operator+=(const FloatVector2d& other) { Add(other); }\n  void operator-=(const FloatVector2d& other) { Subtract(other); }\n\n  void SetToMin(const FloatVector2d& other) {\n    x_ = x_ <= other.x_ ? x_ : other.x_;\n    y_ = y_ <= other.y_ ? y_ : other.y_;\n  }\n\n  void SetToMax(const FloatVector2d& other) {\n    x_ = x_ >= other.x_ ? x_ : other.x_;\n    y_ = y_ >= other.y_ ? y_ : other.y_;\n  }\n\n  // Gives the square of the diagonal length of the vector.\n  double LengthSquared() const;\n  // Gives the diagonal length of the vector.\n  float Length() const;\n\n  // Scale the x and y components of the vector by |scale|.\n  void Scale(float scale) { Scale(scale, scale); }\n  // Scale the x and y components of the vector by |x_scale| and |y_scale|\n  // respectively.\n  void Scale(float x_scale, float y_scale);\n\n  std::string ToString() const;\n\n private:\n  float x_;\n  float y_;\n};\n\ninline constexpr bool operator==(const FloatVector2d& lhs,\n                                 const FloatVector2d& rhs) {\n  return lhs.x() == rhs.x() && lhs.y() == rhs.y();\n}\n\ninline constexpr bool operator!=(const FloatVector2d& lhs,\n                                 const FloatVector2d& rhs) {\n  return !(lhs == rhs);\n}\n\ninline constexpr FloatVector2d operator-(const FloatVector2d& v) {\n  return FloatVector2d(-v.x(), -v.y());\n}\n\ninline FloatVector2d operator+(const FloatVector2d& lhs,\n                               const FloatVector2d& rhs) {\n  FloatVector2d result = lhs;\n  result.Add(rhs);\n  return result;\n}\n\ninline FloatVector2d operator-(const FloatVector2d& lhs,\n                               const FloatVector2d& rhs) {\n  FloatVector2d result = lhs;\n  result.Add(-rhs);\n  return result;\n}\n\n// Return the cross product of two vectors.\ndouble CrossProduct(const FloatVector2d& lhs, const FloatVector2d& rhs);\n\n// Return the dot product of two vectors.\ndouble DotProduct(const FloatVector2d& lhs, const FloatVector2d& rhs);\n\n// Return a vector that is |v| scaled by the given scale factors along each\n// axis.\nFloatVector2d ScaleVector2d(const FloatVector2d& v, float x_scale,\n                            float y_scale);\n\n// Return a vector that is |v| scaled by the given scale factor.\ninline FloatVector2d ScaleVector2d(const FloatVector2d& v, float scale) {\n  return ScaleVector2d(v, scale, scale);\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_FLOAT_VECTOR2D_H_\n"
  },
  {
    "path": "clay/gfx/geometry/float_vector3d.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/float_vector3d.h\"\n\n#include <cmath>\n#include <cstdio>\n#include <vector>\n\n#include \"clay/gfx/geometry/math_util.h\"\n\nnamespace {\nconst double kEpsilon = 1.0e-6;\n}\n\nnamespace clay {\n\nstd::string FloatVector3d::ToString() const {\n  const char* fmt = \"[%f %f %f]\";\n  int sz = std::snprintf(nullptr, 0, fmt, x_, y_, z_);\n  std::vector<char> buf(sz + 1);\n  std::snprintf(&buf[0], buf.size(), fmt, x_, y_, z_);\n  return std::string(buf.data());\n}\n\nbool FloatVector3d::IsZero() const { return x_ == 0 && y_ == 0 && z_ == 0; }\n\nvoid FloatVector3d::Add(const FloatVector3d& other) {\n  x_ += other.x_;\n  y_ += other.y_;\n  z_ += other.z_;\n}\n\nvoid FloatVector3d::Subtract(const FloatVector3d& other) {\n  x_ -= other.x_;\n  y_ -= other.y_;\n  z_ -= other.z_;\n}\n\ndouble FloatVector3d::LengthSquared() const {\n  return static_cast<double>(x_) * x_ + static_cast<double>(y_) * y_ +\n         static_cast<double>(z_) * z_;\n}\n\nfloat FloatVector3d::Length() const {\n  return static_cast<float>(std::sqrt(LengthSquared()));\n}\n\nvoid FloatVector3d::Scale(float x_scale, float y_scale, float z_scale) {\n  x_ *= x_scale;\n  y_ *= y_scale;\n  z_ *= z_scale;\n}\n\nvoid FloatVector3d::Cross(const FloatVector3d& other) {\n  double dx = x_;\n  double dy = y_;\n  double dz = z_;\n  float x = static_cast<float>(dy * other.z() - dz * other.y());\n  float y = static_cast<float>(dz * other.x() - dx * other.z());\n  float z = static_cast<float>(dx * other.y() - dy * other.x());\n  x_ = x;\n  y_ = y;\n  z_ = z;\n}\n\nbool FloatVector3d::GetNormalized(FloatVector3d* out) const {\n  double length_squared = LengthSquared();\n  *out = *this;\n  if (length_squared < kEpsilon * kEpsilon) {\n    return false;\n  }\n  out->Scale(1 / sqrt(length_squared));\n  return true;\n}\n\nfloat DotProduct(const FloatVector3d& lhs, const FloatVector3d& rhs) {\n  return lhs.x() * rhs.x() + lhs.y() * rhs.y() + lhs.z() * rhs.z();\n}\n\nFloatVector3d ScaleVector3d(const FloatVector3d& v, float x_scale,\n                            float y_scale, float z_scale) {\n  FloatVector3d scaled_v(v);\n  scaled_v.Scale(x_scale, y_scale, z_scale);\n  return scaled_v;\n}\n\nfloat AngleBetweenVectorsInDegrees(const FloatVector3d& base,\n                                   const FloatVector3d& other) {\n  // Clamp the resulting value to prevent potential NANs from floating point\n  // precision issues.\n  return RadToDeg(std::acos(\n      fmax(fmin(DotProduct(base, other) / base.Length() / other.Length(), 1.f),\n           -1.f)));\n}\n\nfloat ClockwiseAngleBetweenVectorsInDegrees(const FloatVector3d& base,\n                                            const FloatVector3d& other,\n                                            const FloatVector3d& normal) {\n  float angle = AngleBetweenVectorsInDegrees(base, other);\n  FloatVector3d cross(base);\n  cross.Cross(other);\n\n  // If the dot product of this cross product is normal, it means that the\n  // shortest angle between |base| and |other| was counterclockwise with respect\n  // to the surface represented by |normal| and this angle must be reversed.\n  if (DotProduct(cross, normal) > 0.0f) {\n    angle = 360.0f - angle;\n  }\n  return angle;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/float_vector3d.h",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_FLOAT_VECTOR3D_H_\n#define CLAY_GFX_GEOMETRY_FLOAT_VECTOR3D_H_\n\n#include <string>\n\n#include \"clay/gfx/geometry/float_vector2d.h\"\n\nnamespace clay {\n\nclass FloatVector3d {\n public:\n  constexpr FloatVector3d() : x_(0), y_(0), z_(0) {}\n  constexpr FloatVector3d(float x, float y, float z) : x_(x), y_(y), z_(z) {}\n\n  constexpr explicit FloatVector3d(const FloatVector2d& other)\n      : x_(other.x()), y_(other.y()), z_(0) {}\n\n  constexpr float x() const { return x_; }\n  void SetX(float x) { x_ = x; }\n\n  constexpr float y() const { return y_; }\n  void SetY(float y) { y_ = y; }\n\n  constexpr float z() const { return z_; }\n  void SetZ(float z) { z_ = z; }\n\n  // True if all components of the vector are 0.\n  bool IsZero() const;\n\n  // Add the components of the |other| vector to the current vector.\n  void Add(const FloatVector3d& other);\n  // Subtract the components of the |other| vector from the current vector.\n  void Subtract(const FloatVector3d& other);\n\n  void operator+=(const FloatVector3d& other) { Add(other); }\n  void operator-=(const FloatVector3d& other) { Subtract(other); }\n\n  void SetToMin(const FloatVector3d& other) {\n    x_ = x_ <= other.x_ ? x_ : other.x_;\n    y_ = y_ <= other.y_ ? y_ : other.y_;\n    z_ = z_ <= other.z_ ? z_ : other.z_;\n  }\n\n  void SetToMax(const FloatVector3d& other) {\n    x_ = x_ >= other.x_ ? x_ : other.x_;\n    y_ = y_ >= other.y_ ? y_ : other.y_;\n    z_ = z_ >= other.z_ ? z_ : other.z_;\n  }\n\n  // Gives the square of the diagonal length of the vector.\n  double LengthSquared() const;\n  // Gives the diagonal length of the vector.\n  float Length() const;\n\n  // Scale all components of the vector by |scale|.\n  void Scale(float scale) { Scale(scale, scale, scale); }\n  // Scale the each component of the vector by the given scale factors.\n  void Scale(float x_scale, float y_scale, float z_scale);\n\n  // Take the cross product of this vector with |other| and become the result.\n  void Cross(const FloatVector3d& other);\n\n  // |out| is assigned a unit-length vector in the direction of |this| iff\n  // this function returns true. It can return false if |this| is too short.\n  bool GetNormalized(FloatVector3d* out) const;\n\n  std::string ToString() const;\n\n private:\n  float x_;\n  float y_;\n  float z_;\n};\n\ninline bool operator==(const FloatVector3d& lhs, const FloatVector3d& rhs) {\n  return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z();\n}\n\ninline bool operator!=(const FloatVector3d& lhs, const FloatVector3d& rhs) {\n  return !(lhs == rhs);\n}\n\ninline FloatVector3d operator-(const FloatVector3d& v) {\n  return FloatVector3d(-v.x(), -v.y(), -v.z());\n}\n\ninline FloatVector3d operator+(const FloatVector3d& lhs,\n                               const FloatVector3d& rhs) {\n  FloatVector3d result = lhs;\n  result.Add(rhs);\n  return result;\n}\n\ninline FloatVector3d operator-(const FloatVector3d& lhs,\n                               const FloatVector3d& rhs) {\n  FloatVector3d result = lhs;\n  result.Add(-rhs);\n  return result;\n}\n\n// Return the cross product of two vectors.\ninline FloatVector3d CrossProduct(const FloatVector3d& lhs,\n                                  const FloatVector3d& rhs) {\n  FloatVector3d result = lhs;\n  result.Cross(rhs);\n  return result;\n}\n\n// Return the dot product of two vectors.\nfloat DotProduct(const FloatVector3d& lhs, const FloatVector3d& rhs);\n\n// Return a vector that is |v| scaled by the given scale factors along each\n// axis.\nFloatVector3d ScaleVector3d(const FloatVector3d& v, float x_scale,\n                            float y_scale, float z_scale);\n\n// Return a vector that is |v| scaled by the components of |s|\ninline FloatVector3d ScaleVector3d(const FloatVector3d& v,\n                                   const FloatVector3d& s) {\n  return ScaleVector3d(v, s.x(), s.y(), s.z());\n}\n\n// Return a vector that is |v| scaled by the given scale factor.\ninline FloatVector3d ScaleVector3d(const FloatVector3d& v, float scale) {\n  return ScaleVector3d(v, scale, scale, scale);\n}\n\n// Returns the angle between |base| and |other| in degrees.\nfloat AngleBetweenVectorsInDegrees(const FloatVector3d& base,\n                                   const FloatVector3d& other);\n\n// Returns the clockwise angle between |base| and |other| where |normal| is the\n// normal of the virtual surface to measure clockwise according to.\nfloat ClockwiseAngleBetweenVectorsInDegrees(const FloatVector3d& base,\n                                            const FloatVector3d& other,\n                                            const FloatVector3d& normal);\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_FLOAT_VECTOR3D_H_\n"
  },
  {
    "path": "clay/gfx/geometry/math_util.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_MATH_UTIL_H_\n#define CLAY_GFX_GEOMETRY_MATH_UTIL_H_\n\n#include <cmath>\n#include <limits>\n\nnamespace clay {\n\nconstexpr float linearInterpolate(float from, float to, float progress) {\n  return from * (1 - progress) + to * progress;\n}\n\nconstexpr double DegToRad(double deg) { return deg * M_PI / 180.0; }\nconstexpr float DegToRad(float deg) { return deg * M_PI / 180.0f; }\n\nconstexpr double RadToDeg(double rad) { return rad * 180.0 / M_PI; }\nconstexpr float RadToDeg(float rad) { return rad * 180.0f / M_PI; }\n\ntemplate <typename T>\nconstexpr bool IsApproximatelyEqual(T lhs, T rhs, T tolerance) {\n  static_assert(std::is_arithmetic<T>::value, \"Argument must be arithmetic\");\n  return std::abs(rhs - lhs) <= tolerance;\n}\n\n// Convenience function that returns true if the supplied value is in range\n// for the destination type.\ntemplate <typename Dst, typename Src>\nconstexpr bool IsValueInRangeForNumericType(Src value) {\n  static_assert(std::is_arithmetic<Src>::value, \"Argument must be numeric.\");\n  static_assert(std::is_arithmetic<Dst>::value, \"Result must be numeric.\");\n  static_assert(\n      std::numeric_limits<Src>::lowest() < std::numeric_limits<Dst>::lowest(),\n      \"\");\n  static_assert(\n      std::numeric_limits<Src>::max() > std::numeric_limits<Dst>::max(), \"\");\n  return std::numeric_limits<Dst>::lowest() < value &&\n         value < static_cast<Src>(std::numeric_limits<Dst>::max());\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_MATH_UTIL_H_\n"
  },
  {
    "path": "clay/gfx/geometry/path.cc",
    "content": "/*\n * Copyright 2011 Google Inc.\n *\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n */\n\n#include \"clay/gfx/geometry/path.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/math_util.h\"\n\nnamespace clay {\n\nstatic inline bool is_between(int c, int min, int max) {\n  return (unsigned)(c - min) <= (unsigned)(max - min);\n}\n\nstatic inline bool is_ws(int c) { return is_between(c, 1, 32); }\n\nstatic inline bool is_digit(int c) { return is_between(c, '0', '9'); }\n\nstatic inline bool is_sep(int c) { return is_ws(c) || c == ','; }\n\nstatic inline bool is_lower(int c) { return is_between(c, 'a', 'z'); }\n\nstatic inline int to_upper(int c) { return c - 'a' + 'A'; }\n\nstatic const char* skip_ws(const char str[]) {\n  FML_DCHECK(str);\n  while (is_ws(*str)) str++;\n  return str;\n}\n\nstatic const char* skip_sep(const char str[]) {\n  if (!str) {\n    return nullptr;\n  }\n  while (is_sep(*str)) str++;\n  return str;\n}\n\nconst char* FindScalar(const char str[], float* value) {\n  FML_DCHECK(str);\n  str = skip_ws(str);\n\n  char* stop;\n  float v = (float)strtod(str, &stop);\n  if (str == stop) {\n    return nullptr;\n  }\n  if (value) {\n    *value = v;\n  }\n  return stop;\n}\n\nconst char* FindScalars(const char str[], float value[], int count) {\n  FML_DCHECK(count >= 0);\n\n  if (count > 0) {\n    for (;;) {\n      str = FindScalar(str, value);\n      if (--count == 0 || str == nullptr) break;\n\n      // keep going\n      str = skip_sep(str);\n      if (value) value += 1;\n    }\n  }\n  return str;\n}\n\nstatic const char* find_points(const char str[], GrPoint value[], int count,\n                               bool isRelative, GrPoint* relative) {\n#ifndef ENABLE_SKITY\n  str = FindScalars(str, &value[0].fX, count * 2);\n  if (isRelative) {\n    for (int index = 0; index < count; index++) {\n      value[index].fX += relative->fX;\n      value[index].fY += relative->fY;\n    }\n  }\n#else\n  for (int i = 0; i < count; i++) {\n    str = FindScalars(str, &value[i].x, 2);\n    if (isRelative) {\n      for (int index = 0; index < count; index++) {\n        value[index].x += relative->x;\n        value[index].y += relative->y;\n      }\n    }\n  }\n#endif  // ENABLE_SKITY\n  return str;\n}\n\nstatic const char* find_scalar(const char str[], float* value, bool isRelative,\n                               float relative) {\n  str = FindScalar(str, value);\n  if (!str) {\n    return nullptr;\n  }\n  if (isRelative) {\n    *value += relative;\n  }\n  str = skip_sep(str);\n  return str;\n}\n\n// https://www.w3.org/TR/SVG11/paths.html#PathDataBNF\n//\n// flag:\n//    \"0\" | \"1\"\nstatic const char* find_flag(const char str[], bool* value) {\n  if (!str) {\n    return nullptr;\n  }\n  if (str[0] != '1' && str[0] != '0') {\n    return nullptr;\n  }\n  *value = str[0] != '0';\n  str = skip_sep(str + 1);\n  return str;\n}\n\nbool PathBuilder::ParsePathString(const char data[], GrPath* result) {\n  GrPath path;\n#ifndef ENABLE_SKITY\n  GrPoint first = {0, 0};\n  GrPoint c = {0, 0};\n  GrPoint last_c = {0, 0};\n#else\n  GrPoint first = {0, 0, 0, 0};\n  GrPoint c = {0, 0, 0, 0};\n  GrPoint last_c = {0, 0, 0, 0};\n#endif  // ENABLE_SKITY\n  GrPoint points[3];\n  char op = '\\0';\n  char previousOp = '\\0';\n  bool relative = false;\n  for (;;) {\n    if (!data) {\n      // Truncated data\n      return false;\n    }\n    data = skip_ws(data);\n    if (data[0] == '\\0') {\n      break;\n    }\n    char ch = data[0];\n    if (is_digit(ch) || ch == '-' || ch == '+' || ch == '.') {\n      if (op == '\\0' || op == 'Z') {\n        return false;\n      }\n    } else if (is_sep(ch)) {\n      data = skip_sep(data);\n    } else {\n      op = ch;\n      relative = false;\n      if (is_lower(op)) {\n        op = (char)to_upper(op);\n        relative = true;\n      }\n      data++;\n      data = skip_sep(data);\n    }\n    switch (op) {\n      case 'M':\n        data = find_points(data, points, 1, relative, &c);\n        PATH_MOVE_TO_POINT(path, points[0]);\n        previousOp = '\\0';\n        op = 'L';\n        c = points[0];\n        break;\n      case 'L':\n        data = find_points(data, points, 1, relative, &c);\n        PATH_LINE_TO_POINT(path, points[0]);\n        c = points[0];\n        break;\n      case 'H': {\n        float x;\n        data = find_scalar(data, &x, relative, POINT_GET_X(c));\n        PATH_LINE_TO(path, x, POINT_GET_Y(c));\n        POINT_SET_X(c, x);\n      } break;\n      case 'V': {\n        float y;\n        data = find_scalar(data, &y, relative, POINT_GET_Y(c));\n        PATH_LINE_TO(path, POINT_GET_X(c), y);\n        POINT_SET_Y(c, y);\n      } break;\n      case 'C':\n        data = find_points(data, points, 3, relative, &c);\n        goto cubicCommon;\n      case 'S':\n        data = find_points(data, &points[1], 2, relative, &c);\n        points[0] = c;\n        if (previousOp == 'C' || previousOp == 'S') {\n          auto c_x = POINT_GET_X(c);\n          auto c_y = POINT_GET_Y(c);\n          auto last_c_x = POINT_GET_X(last_c);\n          auto last_c_y = POINT_GET_Y(last_c);\n          auto new_x = POINT_GET_X(points[0]) - (last_c_x - c_x);\n          auto new_y = POINT_GET_Y(points[0]) - (last_c_y - c_y);\n          POINT_SET_X(points[0], new_x);\n          POINT_SET_Y(points[0], new_y);\n        }\n      cubicCommon:\n        PATH_CUBIC_TO_POINT(path, points[0], points[1], points[2]);\n        last_c = points[1];\n        c = points[2];\n        break;\n      case 'Q':  // Quadratic Bezier Curve\n        data = find_points(data, points, 2, relative, &c);\n        goto quadraticCommon;\n      case 'T':\n        data = find_points(data, &points[1], 1, relative, &c);\n        points[0] = c;\n        if (previousOp == 'Q' || previousOp == 'T') {\n          auto c_x = POINT_GET_X(c);\n          auto c_y = POINT_GET_Y(c);\n          auto last_c_x = POINT_GET_X(last_c);\n          auto last_c_y = POINT_GET_Y(last_c);\n          auto new_x = POINT_GET_X(points[0]) - (last_c_x - c_x);\n          auto new_y = POINT_GET_Y(points[0]) - (last_c_y - c_y);\n          POINT_SET_X(points[0], new_x);\n          POINT_SET_Y(points[0], new_y);\n        }\n      quadraticCommon:\n        PATH_QUAD_TO_POINT(path, points[0], points[1]);\n        last_c = points[0];\n        c = points[1];\n        break;\n      case 'A': {\n        GrPoint radii;\n        float angle;\n        bool largeArc, sweep;\n        if ((data = find_points(data, &radii, 1, false, nullptr)) &&\n            (data = skip_sep(data)) &&\n            (data = find_scalar(data, &angle, false, 0)) &&\n            (data = skip_sep(data)) && (data = find_flag(data, &largeArc)) &&\n            (data = skip_sep(data)) && (data = find_flag(data, &sweep)) &&\n            (data = skip_sep(data)) &&\n            (data = find_points(data, &points[0], 1, relative, &c))) {\n          PATH_ARC_TO(path, radii, angle, (GrPath::ArcSize)largeArc,\n                      (GrPathDirection)!sweep, points[0]);\n          PATH_GET_LAST_POINT(path, &c);\n        }\n      } break;\n      case 'Z':\n        PATH_CLOSE(path);\n        c = first;\n        break;\n      case '~': {\n        GrPoint args[2];\n        data = find_points(data, args, 2, false, nullptr);\n        PATH_MOVE_TO_POINT(path, args[0]);\n        PATH_LINE_TO_POINT(path, args[1]);\n      } break;\n      default:\n        return false;\n    }\n    if (previousOp == 0) {\n      first = c;\n    }\n    previousOp = op;\n  }\n  // we're good, go ahead and swap in the result\n  PATH_SWAP(result, path);\n  return true;\n}\n\nMotionState PathBuilder::CalculateMotionState(const GrPath& path,\n                                              float percent) {\n  percent = std::clamp(percent, 0.0f, 1.0f);\n\n  GrPathMeasure measure(path, false);\n\n  float total_length = 0;\n  do {\n    total_length += PATH_MEASURE_GET_LENGTH(measure);\n  } while (PATH_MEASURE_NEXT_CONTOUR(measure));\n\n  if (IsApproximatelyEqual(total_length, 0.0f, 1e-5f)) {\n    return {0, 0, 0};\n  }\n\n  float target_distance = total_length * percent;\n\n  // Reset measure to the start of the path\n  PATH_MEASURE_SET_PATH(measure, &path);\n\n#ifndef ENABLE_SKITY\n  SkPoint last_valid_pos = {0, 0};\n  SkVector last_valid_tan = {0, 0};\n#else\n  skity::Point last_valid_pos = {0, 0, 0, 0};\n  skity::Vector last_valid_tan = {0, 0, 0, 0};\n#endif\n  bool has_valid_data = false;\n\n  do {\n    float current_length = PATH_MEASURE_GET_LENGTH(measure);\n    if (target_distance <= current_length) {\n#ifndef ENABLE_SKITY\n      SkPoint position = {0, 0};\n      SkVector tangent = {0, 0};\n#else\n      skity::Point position = {0, 0, 0, 0};\n      skity::Vector tangent = {0, 0, 0, 0};\n#endif\n      if (PATH_MEASURE_GET_POS_TAN(measure, target_distance, &position,\n                                   &tangent)) {\n        float radian = atan2(POINT_GET_Y(tangent), POINT_GET_X(tangent));\n        float deg = RadToDeg(radian);\n        return {\n            POINT_GET_X(position),\n            POINT_GET_Y(position),\n            deg,\n        };\n      }\n    }\n\n    if (PATH_MEASURE_GET_POS_TAN(measure, current_length, &last_valid_pos,\n                                 &last_valid_tan)) {\n      has_valid_data = true;\n    }\n\n    target_distance -= current_length;\n  } while (PATH_MEASURE_NEXT_CONTOUR(measure));\n\n  // Fallback to the end of the path.\n  if (has_valid_data) {\n    float radian =\n        atan2(POINT_GET_Y(last_valid_tan), POINT_GET_X(last_valid_tan));\n    float deg = RadToDeg(radian);\n    return {\n        POINT_GET_X(last_valid_pos),\n        POINT_GET_Y(last_valid_pos),\n        deg,\n    };\n  }\n\n  return {0, 0, 0};\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/path.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_PATH_H_\n#define CLAY_GFX_GEOMETRY_PATH_H_\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\n\nstruct ClipPathData {\n  enum class ClipType {\n    kUnknown = 0,\n    kCircle,\n    kEllipse,\n    kPath,\n    kSuperEllipse,\n    kInset,\n  };\n  enum class CornerType {\n    kUnknown,\n    kCornerRect,\n    kCornerRounded,\n    kCornerSuperElliptical,\n  };\n  struct BorderRadius {\n    ClayPlatformLength x;\n    ClayPlatformLength y;\n  };\n  ClipType clip_type = ClipType::kUnknown;\n  CornerType corner_type = CornerType::kUnknown;\n  std::vector<ClayPlatformLength> params = {};\n  std::vector<double> exponents = {};\n  std::vector<BorderRadius> radius = {};\n};\n\nusing OffsetPathData = ClipPathData;\n\nstruct MotionState {\n  float x;\n  float y;\n  float deg;\n};\n\nstruct PathBuilder {\n  static bool ParsePathString(const char data[], GrPath* result);\n\n  static MotionState CalculateMotionState(const GrPath& path, float percent);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_PATH_H_\n"
  },
  {
    "path": "clay/gfx/geometry/point.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_POINT_H_\n#define CLAY_GFX_GEOMETRY_POINT_H_\n\n#include <math.h>\n\n#include <string>\n\n#include \"clay/gfx/geometry/size.h\"\n\nnamespace clay {\n\nclass Point {\n public:\n  Point() : x_(0), y_(0) {}\n  Point(int x, int y) : x_(x), y_(y) {}\n\n  int x() const { return x_; }\n  int y() const { return y_; }\n\n  void SetX(int x) { x_ = x; }\n  void SetY(int y) { y_ = y; }\n\n  void MoveBy(const Point& offset) { Move(offset.x(), offset.y()); }\n  void Move(int dx, int dy) {\n    x_ += dx;\n    y_ += dy;\n  }\n  void Scale(float sx, float sy) {\n    x_ = lroundf(static_cast<float>(x_ * sx));\n    y_ = lroundf(static_cast<float>(y_ * sy));\n  }\n\n  Point ExpandedTo(const Point& other) const {\n    return Point(x_ > other.x_ ? x_ : other.x_, y_ > other.y_ ? y_ : other.y_);\n  }\n\n  Point ShrunkTo(const Point& other) const {\n    return Point(x_ < other.x_ ? x_ : other.x_, y_ < other.y_ ? y_ : other.y_);\n  }\n\n  void ClampNegativeToZero() { *this = ExpandedTo(Point()); }\n\n  std::string ToString() const {\n    return \"Point{\" + std::to_string(x_) + \",\" + std::to_string(y_) + \"}\";\n  }\n\n private:\n  int x_, y_;\n};\n\ninline Point operator+(const Point& a, const Point& b) {\n  return Point(a.x() + b.x(), a.y() + b.y());\n}\n\ninline Point operator-(const Point& point) {\n  return Point(-point.x(), -point.y());\n}\n\ninline Size operator-(const Point& a, const Point& b) {\n  return Size(a.x() - b.x(), a.y() - b.y());\n}\n\ninline bool operator==(const Point& a, const Point& b) {\n  return a.x() == b.x() && a.y() == b.y();\n}\n\ninline bool operator!=(const Point& a, const Point& b) {\n  return a.x() != b.x() || a.y() != b.y();\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_POINT_H_\n"
  },
  {
    "path": "clay/gfx/geometry/quaternion.cc",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/quaternion.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_vector3d.h\"\n\nnamespace clay {\n\nnamespace {\n\nconst double kEpsilon = 1e-5;\n\n}  // namespace\n\nQuaternion::Quaternion(const FloatVector3d& axis, double theta) {\n  // Rotation angle is the product of |angle| and the magnitude of |axis|.\n  double length = axis.Length();\n  if (std::abs(length) < kEpsilon) {\n    return;\n  }\n\n  FloatVector3d normalized = axis;\n  normalized.Scale(1.0 / length);\n\n  theta *= 0.5;\n  double s = sin(theta);\n  x_ = normalized.x() * s;\n  y_ = normalized.y() * s;\n  z_ = normalized.z() * s;\n  w_ = cos(theta);\n}\n\nQuaternion::Quaternion(const FloatVector3d& from, const FloatVector3d& to) {\n  double dot = DotProduct(from, to);\n  double norm = sqrt(from.LengthSquared() * to.LengthSquared());\n  double real = norm + dot;\n  FloatVector3d axis;\n  if (real < kEpsilon * norm) {\n    real = 0.0f;\n    axis = std::abs(from.x()) > std::abs(from.z())\n               ? FloatVector3d{-from.y(), from.x(), 0.0}\n               : FloatVector3d{0.0, -from.z(), from.y()};\n  } else {\n    axis = CrossProduct(from, to);\n  }\n  x_ = axis.x();\n  y_ = axis.y();\n  z_ = axis.z();\n  w_ = real;\n  *this = this->Normalized();\n}\n\nQuaternion Quaternion::FromAxisAngle(double x, double y, double z,\n                                     double angle) {\n  double length = std::sqrt(x * x + y * y + z * z);\n  if (std::abs(length) < kEpsilon) {\n    return Quaternion(0, 0, 0, 1);\n  }\n\n  double scale = std::sin(0.5 * angle) / length;\n  return Quaternion(scale * x, scale * y, scale * z, std::cos(0.5 * angle));\n}\n\n// Adapted from https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/\n// quaternions/slerp/index.htm\nQuaternion Quaternion::Slerp(const Quaternion& to, double t) const {\n  Quaternion from = *this;\n\n  double cos_half_angle =\n      from.x_ * to.x_ + from.y_ * to.y_ + from.z_ * to.z_ + from.w_ * to.w_;\n  if (cos_half_angle < 0) {\n    // Since the half angle is > 90 degrees, the full rotation angle would\n    // exceed 180 degrees. The quaternions (x, y, z, w) and (-x, -y, -z, -w)\n    // represent the same rotation. Flipping the orientation of either\n    // quaternion ensures that the half angle is less than 90 and that we are\n    // taking the shortest path.\n    from = from.flip();\n    cos_half_angle = -cos_half_angle;\n  }\n\n  // Ensure that acos is well behaved at the boundary.\n  if (cos_half_angle > 1) {\n    cos_half_angle = 1;\n  }\n\n  double sin_half_angle = std::sqrt(1.0 - cos_half_angle * cos_half_angle);\n  if (sin_half_angle < kEpsilon) {\n    // Quaternions share common axis and angle.\n    return *this;\n  }\n\n  double half_angle = std::acos(cos_half_angle);\n\n  double scaleA = std::sin((1 - t) * half_angle) / sin_half_angle;\n  double scaleB = std::sin(t * half_angle) / sin_half_angle;\n\n  return (scaleA * from) + (scaleB * to);\n}\n\nQuaternion Quaternion::Lerp(const Quaternion& q, double t) const {\n  return (((1.0 - t) * *this) + (t * q)).Normalized();\n}\n\ndouble Quaternion::Length() const {\n  return x_ * x_ + y_ * y_ + z_ * z_ + w_ * w_;\n}\n\nQuaternion Quaternion::Normalized() const {\n  double length = Length();\n  if (length < kEpsilon) {\n    return *this;\n  }\n  return *this / sqrt(length);\n}\n\nstd::string Quaternion::ToString() const {\n  // q = (con(abs(v_theta)/2), v_theta/abs(v_theta) * sin(abs(v_theta)/2))\n  float abs_theta = acos(w_) * 2;\n  float scale = 1. / sin(abs_theta * .5);\n  FloatVector3d v(x_, y_, z_);\n  v.Scale(scale);\n\n  auto str_printf = [=](char* str_buf, size_t size) {\n    return std::snprintf(str_buf, size, \"[%f %f %f %f], v:%s, θ:%fπ\", x_, y_,\n                         z_, w_, v.ToString().c_str(), abs_theta / M_PI);\n  };\n  int sz = str_printf(nullptr, 0);\n  std::vector<char> buf(sz + 1);\n  str_printf(&buf[0], buf.size());\n  return std::string(buf.data());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/quaternion.h",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_QUATERNION_H_\n#define CLAY_GFX_GEOMETRY_QUATERNION_H_\n\n#include <string>\n\nnamespace clay {\n\nclass FloatVector3d;\n\nclass Quaternion {\n public:\n  constexpr Quaternion() = default;\n  constexpr Quaternion(double x, double y, double z, double w)\n      : x_(x), y_(y), z_(z), w_(w) {}\n  Quaternion(const FloatVector3d& axis, double angle);\n\n  // Constructs a quaternion representing a rotation between |from| and |to|.\n  Quaternion(const FloatVector3d& from, const FloatVector3d& to);\n\n  static Quaternion FromAxisAngle(double x, double y, double z, double angle);\n\n  constexpr double x() const { return x_; }\n  void SetX(double x) { x_ = x; }\n\n  constexpr double y() const { return y_; }\n  void SetY(double y) { y_ = y; }\n\n  constexpr double z() const { return z_; }\n  void SetZ(double z) { z_ = z; }\n\n  constexpr double w() const { return w_; }\n  void set_w(double w) { w_ = w; }\n\n  Quaternion operator+(const Quaternion& q) const {\n    return {q.x_ + x_, q.y_ + y_, q.z_ + z_, q.w_ + w_};\n  }\n\n  Quaternion operator*(const Quaternion& q) const {\n    return {w_ * q.x_ + x_ * q.w_ + y_ * q.z_ - z_ * q.y_,\n            w_ * q.y_ - x_ * q.z_ + y_ * q.w_ + z_ * q.x_,\n            w_ * q.z_ + x_ * q.y_ - y_ * q.x_ + z_ * q.w_,\n            w_ * q.w_ - x_ * q.x_ - y_ * q.y_ - z_ * q.z_};\n  }\n\n  Quaternion inverse() const { return {-x_, -y_, -z_, w_}; }\n\n  Quaternion flip() const { return {-x_, -y_, -z_, -w_}; }\n\n  // Blends with the given quaternion, |q|, via spherical linear interpolation.\n  // Values of |t| in the range [0, 1] will interpolate between |this| and |q|,\n  // and values outside that range will extrapolate beyond in either direction.\n  Quaternion Slerp(const Quaternion& q, double t) const;\n\n  // Blends with the given quaternion, |q|, via linear interpolation. This is\n  // rarely what you want. Use only if you know what you're doing.\n  // Values of |t| in the range [0, 1] will interpolate between |this| and |q|,\n  // and values outside that range will extrapolate beyond in either direction.\n  Quaternion Lerp(const Quaternion& q, double t) const;\n\n  double Length() const;\n\n  Quaternion Normalized() const;\n\n  std::string ToString() const;\n\n private:\n  double x_ = 0.0;\n  double y_ = 0.0;\n  double z_ = 0.0;\n  double w_ = 1.0;\n};\n\n// |s| is an arbitrary, real constant.\ninline Quaternion operator*(const Quaternion& q, double s) {\n  return Quaternion(q.x() * s, q.y() * s, q.z() * s, q.w() * s);\n}\n\n// |s| is an arbitrary, real constant.\ninline Quaternion operator*(double s, const Quaternion& q) {\n  return Quaternion(q.x() * s, q.y() * s, q.z() * s, q.w() * s);\n}\n\n// |s| is an arbitrary, real constant.\ninline Quaternion operator/(const Quaternion& q, double s) {\n  double inv = 1.0 / s;\n  return q * inv;\n}\n\n// Returns true if the x, y, z, w values of |lhs| and |rhs| are equal. Note that\n// two quaternions can represent the same orientation with different values.\n// This operator will return false in that scenario.\ninline bool operator==(const Quaternion& lhs, const Quaternion& rhs) {\n  return lhs.x() == rhs.x() && lhs.y() == rhs.y() && lhs.z() == rhs.z() &&\n         lhs.w() == rhs.w();\n}\n\ninline bool operator!=(const Quaternion& lhs, const Quaternion& rhs) {\n  return !(lhs == rhs);\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_QUATERNION_H_\n"
  },
  {
    "path": "clay/gfx/geometry/rect.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_RECT_H_\n#define CLAY_GFX_GEOMETRY_RECT_H_\n\n#include <algorithm>\n\n#include \"clay/gfx/geometry/point.h\"\n#include \"clay/gfx/geometry/size.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nclass Rect {\n public:\n  Rect() = default;\n  Rect(const Point& location, const Size& size)\n      : location_(location), size_(size) {}\n  Rect(const Size& size) : size_(size) {}\n  Rect(int x, int y, int w, int h) : location_(x, y), size_(w, h) {}\n\n  int x() const { return location_.x(); }\n  int y() const { return location_.y(); }\n  int MaxX() const { return x() + width(); }\n  int MaxY() const { return y() + height(); }\n  int width() const { return size_.width(); }\n  int height() const { return size_.height(); }\n\n  void SetX(int x) { location_.SetX(x); }\n  void SetY(int y) { location_.SetY(y); }\n  void SetWidth(int width) { size_.SetWidth(width); }\n  void SetHeight(int height) { size_.SetHeight(height); }\n\n  const Point& location() const { return location_; }\n  const Size& size() const { return size_; }\n  void SetLocation(const Point& location) { location_ = location; }\n  void SetSize(const Size& size) { size_ = size; }\n\n  bool IsEmpty() const { return size_.IsEmpty(); }\n\n  void Move(int dx, int dy) { location_.Move(dx, dy); }\n\n  void Expand(int dw, int dh) { size_.Expand(dw, dh); }\n\n  void Expand(float top, float right, float bottom, float left) {\n    location_.Move(-left, -top);\n    size_.Expand(left + right, top + bottom);\n  }\n\n  void Contract(int dw, int dh) { size_.Expand(-dw, -dh); }\n\n  bool Contains(int px, int py) const {\n    return px >= x() && px < MaxX() && py >= y() && py < MaxY();\n  }\n\n  bool Contains(const Point& point) const {\n    return Contains(point.x(), point.y());\n  }\n\n  bool Contains(const Rect& other) const {\n    return x() <= other.x() && MaxX() >= other.MaxX() && y() <= other.y() &&\n           MaxY() >= other.MaxY();\n  }\n\n  bool IsIntersects(const Rect& other) const {\n    return !IsEmpty() && !other.IsEmpty() && x() < other.MaxX() &&\n           other.x() < MaxX() && y() < other.MaxY() && other.y() < MaxY();\n  }\n\n  void Intersect(const Rect& other) {\n    int left = std::max(x(), other.x());\n    int top = std::max(y(), other.y());\n    int right = std::min(MaxX(), other.MaxX());\n    int bottom = std::min(MaxY(), other.MaxY());\n\n    // Return a clean empty rectangle for non-intersecting cases.\n    if (left >= right || top >= bottom) {\n      left = 0;\n      top = 0;\n      right = 0;\n      bottom = 0;\n    }\n\n    location_ = Point(left, top);\n    size_ = Size(right - left, bottom - top);\n  }\n\n  void Unite(const Rect& other) {\n    // Handle empty special cases first.\n    if (other.IsEmpty()) return;\n    if (IsEmpty()) {\n      *this = other;\n      return;\n    }\n\n    Point new_location(std::min(x(), other.x()), std::min(y(), other.y()));\n    Point new_max_point(std::max(MaxX(), other.MaxX()),\n                        std::max(MaxY(), other.MaxY()));\n\n    location_ = new_location;\n    size_ = new_max_point - new_location;\n  }\n\n  void Clear() {\n    location_.SetX(0);\n    location_.SetY(0);\n    size_.SetWidth(0);\n    size_.SetHeight(0);\n  }\n\n  operator skity::Rect() const {\n    skity::Rect rect =\n        skity::Rect::MakeXYWH(x(), y(), MaxX() - x(), MaxY() - y());\n    return rect;\n  }\n\n private:\n  Point location_;\n  Size size_;\n};\n\ninline bool operator==(const Rect& lhs, const Rect& rhs) {\n  return lhs.location() == rhs.location() && lhs.size() == rhs.size();\n}\n\ninline bool operator!=(const Rect& lhs, const Rect& rhs) {\n  return !(lhs == rhs);\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_RECT_H_\n"
  },
  {
    "path": "clay/gfx/geometry/size.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_SIZE_H_\n#define CLAY_GFX_GEOMETRY_SIZE_H_\n\nnamespace clay {\n\nclass Size {\n public:\n  Size() : width_(0), height_(0) {}\n\n  Size(int width, int height) : width_(width), height_(height) {}\n\n  int width() const { return width_; }\n  int height() const { return height_; }\n\n  void SetWidth(int width) { width_ = width; }\n  void SetHeight(int height) { height_ = height; }\n\n  bool IsEmpty() const { return width_ <= 0 || height_ <= 0; }\n  bool IsZero() const { return !width_ && !height_; }\n\n  float AspectRatio() const {\n    return static_cast<float>(width_) / static_cast<float>(height_);\n  }\n\n  float GetArea() const { return width() * height(); }\n\n  void Expand(int width, int height) {\n    width_ += width;\n    height_ += height;\n  }\n\n private:\n  int width_;\n  int height_;\n};\n\ninline Size& operator+=(Size& a, const Size& b) {\n  a.SetWidth(a.width() + b.width());\n  a.SetHeight(a.height() + b.height());\n  return a;\n}\n\ninline Size& operator-=(Size& a, const Size& b) {\n  a.SetWidth(a.width() - b.width());\n  a.SetHeight(a.height() - b.height());\n  return a;\n}\n\ninline Size operator+(const Size& a, const Size& b) {\n  return Size(a.width() + b.width(), a.height() + b.height());\n}\n\ninline Size operator-(const Size& a, const Size& b) {\n  return Size(a.width() - b.width(), a.height() - b.height());\n}\n\ninline Size operator-(const Size& size) {\n  return Size(-size.width(), -size.height());\n}\n\ninline bool operator==(const Size& a, const Size& b) {\n  return a.width() == b.width() && a.height() == b.height();\n}\n\ninline bool operator!=(const Size& a, const Size& b) {\n  return a.width() != b.width() || a.height() != b.height();\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_SIZE_H_\n"
  },
  {
    "path": "clay/gfx/geometry/sticky_info.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_STICKY_INFO_H_\n#define CLAY_GFX_GEOMETRY_STICKY_INFO_H_\n\nnamespace clay {\n\nclass StickyInfo {\n public:\n  float left;\n  float top;\n  float right;\n  float bottom;\n  float offset_x;\n  float offset_y;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_STICKY_INFO_H_\n"
  },
  {
    "path": "clay/gfx/geometry/transform.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/transform.h\"\n\n#include <cmath>\n#include <cstdio>\n#include <limits>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_point3d.h\"\n#include \"clay/gfx/geometry/float_vector3d.h\"\n#include \"clay/gfx/geometry/math_util.h\"\n#include \"clay/gfx/geometry/quaternion.h\"\n#include \"clay/gfx/geometry/transform_util.h\"\n\nnamespace clay {\n\nnamespace {\n\nconst float kEpsilon = std::numeric_limits<float>::epsilon();\n\nfloat TanDegrees(double degrees) { return std::tan(DegToRad(degrees)); }\n\ninline bool ApproximatelyZero(float x, float tolerance) {\n  return std::abs(x) <= tolerance;\n}\n\ninline bool ApproximatelyOne(float x, float tolerance) {\n  return std::abs(x - 1) <= tolerance;\n}\n\n// Referenced from skity::Quaternion::EulerToMatrix\nskity::Matrix EulerToMatrix(float alpha, float beta, float gamma) {\n  const auto cos_alpha = std::cos(alpha);\n  const auto sin_alpha = std::sin(alpha);\n  const auto cos_beta = std::cos(beta);\n  const auto sin_beta = std::sin(beta);\n  const auto cos_gamma = std::cos(gamma);\n  const auto sin_gamma = std::sin(gamma);\n\n  return skity::Matrix(cos_beta * cos_gamma,\n                       sin_alpha * sin_beta * cos_gamma + cos_alpha * sin_gamma,\n                       sin_alpha * sin_gamma - cos_alpha * sin_beta * cos_gamma,\n                       0, -cos_beta * sin_gamma,\n                       cos_alpha * cos_gamma - sin_alpha * sin_beta * sin_gamma,\n                       cos_alpha * sin_beta * sin_gamma + sin_alpha * cos_gamma,\n                       0, sin_beta, -sin_alpha * cos_beta, cos_alpha * cos_beta,\n                       0, 0, 0, 0, 1);\n}\n\n}  // namespace\n\nTransform::Transform(float col1row1, float col2row1, float col3row1,\n                     float col4row1, float col1row2, float col2row2,\n                     float col3row2, float col4row2, float col1row3,\n                     float col2row3, float col3row3, float col4row3,\n                     float col1row4, float col2row4, float col3row4,\n                     float col4row4) {\n  matrix_ =\n      skity::Matrix(col1row1, col1row2, col1row3, col1row4, col2row1, col2row2,\n                    col2row3, col2row4, col3row1, col3row2, col3row3, col3row4,\n                    col4row1, col4row2, col4row3, col4row4);\n}\n\nTransform::Transform(float col1row1, float col2row1, float col1row2,\n                     float col2row2, float x_translation, float y_translation) {\n  matrix_.Set(0, 0, col1row1);\n  matrix_.Set(1, 0, col1row2);\n  matrix_.Set(0, 1, col2row1);\n  matrix_.Set(1, 1, col2row2);\n  matrix_.Set(0, 3, x_translation);\n  matrix_.Set(1, 3, y_translation);\n}\n\nTransform::Transform(const Quaternion& q) {\n  float x = q.x();\n  float y = q.y();\n  float z = q.z();\n  float w = q.w();\n\n  // Implicitly calls matrix.Reset()\n  const float buffer[9] = {1.f - 2.f * (y * y + z * z), 2.f * (x * y + z * w),\n                           2.f * (x * z - y * w),       2.f * (x * y - z * w),\n                           1.f - 2.f * (x * x + z * z), 2.f * (y * z + x * w),\n                           2.f * (x * z + y * w),       2.f * (y * z - x * w),\n                           1.f - 2.f * (x * x + y * y)};\n  matrix_.Set9(buffer);\n}\n\nvoid Transform::RotateAboutXAxis(double degrees) {\n  double radians = DegToRad(degrees);\n  float cosTheta = std::cos(radians);\n  float sinTheta = std::sin(radians);\n  const float buffer[9] = {1,        0, 0,         0,       cosTheta,\n                           sinTheta, 0, -sinTheta, cosTheta};\n  if (matrix_.IsIdentity()) {\n    matrix_.Set9(buffer);\n  } else {\n    skity::Matrix rot;\n    rot.Set9(buffer);\n    matrix_.PreConcat(rot);\n  }\n}\n\nvoid Transform::RotateAboutYAxis(double degrees) {\n  double radians = DegToRad(degrees);\n  float cosTheta = std::cos(radians);\n  float sinTheta = std::sin(radians);\n  const float buffer[9] = {cosTheta, 0,        -sinTheta, 0,       1,\n                           0,        sinTheta, 0,         cosTheta};\n  if (matrix_.IsIdentity()) {\n    // Note carefully the placement of the -sinTheta for rotation about\n    // y-axis is different than rotation about x-axis or z-axis.\n    matrix_.Set9(buffer);\n  } else {\n    skity::Matrix rot;\n    rot.Set9(buffer);\n    matrix_.PreConcat(rot);\n  }\n}\n\nvoid Transform::RotateAboutZAxis(double degrees) {\n  double radians = DegToRad(degrees);\n  float cosTheta = std::cos(radians);\n  float sinTheta = std::sin(radians);\n  const float buffer[9] = {cosTheta, sinTheta, 0, -sinTheta, cosTheta,\n                           0,        0,        0, 1};\n  if (matrix_.IsIdentity()) {\n    matrix_.Set9(buffer);\n  } else {\n    skity::Matrix rot;\n    rot.Set9(buffer);\n    matrix_.PreConcat(rot);\n  }\n}\n\nvoid Transform::RotateAbout(const FloatVector3d& axis, double degrees) {\n  float x = axis.x();\n  float y = axis.y();\n  float z = axis.z();\n  double radians = DegToRad(degrees);\n  // We always use separated x, y, z for the rotate transform animation.\n  // See TransformOperations::AppendRotate.\n  if (z == 1 && y == 0 && x == 0) {\n    const auto matrix = EulerToMatrix(0, 0, radians);\n    matrix_.PreConcat(matrix);\n  } else if (z == 0 && y == 1 && x == 0) {\n    const auto matrix = EulerToMatrix(0, radians, 0);\n    matrix_.PreConcat(matrix);\n  } else if (z == 0 && y == 0 && x == 1) {\n    const auto matrix = EulerToMatrix(radians, 0, 0);\n    matrix_.PreConcat(matrix);\n  } else {\n    FML_UNREACHABLE();\n  }\n}\n\nvoid Transform::Scale(float x, float y) { matrix_.PreScale(x, y); }\n\nvoid Transform::PostScale(float x, float y) { matrix_.PostScale(x, y); }\n\nvoid Transform::Scale3d(float x, float y, float z) {\n  // Skity has ignored the element related z when Scale3D.\n  matrix_.PreScale(x, y);\n}\n\nvoid Transform::Translate(const FloatVector2d& offset) {\n  Translate(offset.x(), offset.y());\n}\n\nvoid Transform::Translate(float x, float y) { matrix_.PreTranslate(x, y); }\n\nvoid Transform::PostTranslate(const FloatVector2d& offset) {\n  PostTranslate(offset.x(), offset.y());\n}\n\nvoid Transform::PostTranslate(float x, float y) { matrix_.PostTranslate(x, y); }\n\nvoid Transform::Translate3d(const FloatVector3d& offset) {\n  Translate3d(offset.x(), offset.y(), offset.z());\n}\n\nvoid Transform::Translate3d(float x, float y, float z) {\n  // Skity has ignored the element related z when Translate3d.\n  matrix_.PreTranslate(x, y);\n}\n\nvoid Transform::Skew(double angle_x, double angle_y) {\n  if (matrix_.IsIdentity()) {\n    matrix_.Set(0, 1, TanDegrees(angle_x));\n    matrix_.Set(1, 0, TanDegrees(angle_y));\n  } else {\n    skity::Matrix skew;\n    skew.Set(0, 1, TanDegrees(angle_x));\n    skew.Set(1, 0, TanDegrees(angle_y));\n    matrix_.PreConcat(skew);\n  }\n}\n\nvoid Transform::ApplyPerspectiveDepth(float depth) {\n  if (depth == 0) {\n    return;\n  }\n  if (matrix_.IsIdentity()) {\n    matrix_.Set(3, 2, -1.f / depth);\n  } else {\n    skity::Matrix m;\n    m.Set(3, 2, -1.f / depth);\n    matrix_.PreConcat(m);\n  }\n}\n\nvoid Transform::PreconcatTransform(const Transform& transform) {\n  matrix_ = matrix_ * transform.matrix_;\n}\n\nvoid Transform::ConcatTransform(const Transform& transform) {\n  matrix_.PostConcat(transform.matrix_);\n}\n\nbool Transform::IsApproximatelyIdentityOrTranslation(float tolerance) const {\n  FML_DCHECK(tolerance >= 0);\n  return ApproximatelyOne(matrix_.Get(0, 0), tolerance) &&\n         ApproximatelyZero(matrix_.Get(1, 0), tolerance) &&\n         ApproximatelyZero(matrix_.Get(2, 0), tolerance) &&\n         matrix_.Get(3, 0) == 0 &&\n         ApproximatelyZero(matrix_.Get(0, 1), tolerance) &&\n         ApproximatelyOne(matrix_.Get(1, 1), tolerance) &&\n         ApproximatelyZero(matrix_.Get(2, 1), tolerance) &&\n         matrix_.Get(3, 1) == 0 &&\n         ApproximatelyZero(matrix_.Get(0, 2), tolerance) &&\n         ApproximatelyZero(matrix_.Get(1, 2), tolerance) &&\n         ApproximatelyOne(matrix_.Get(2, 2), tolerance) &&\n         matrix_.Get(3, 2) == 0 && matrix_.Get(3, 3) == 1;\n}\n\nbool Transform::IsApproximatelyIdentityOrIntegerTranslation(\n    float tolerance) const {\n  if (!IsApproximatelyIdentityOrTranslation(tolerance)) {\n    return false;\n  }\n\n  for (float t : {matrix_.Get(0, 3), matrix_.Get(1, 3), matrix_.Get(2, 3)}) {\n    if (!IsValueInRangeForNumericType<int>(t) ||\n        std::abs(std::round(t) - t) > tolerance) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool Transform::IsIdentityOrIntegerTranslation() const {\n  if (!IsIdentityOrTranslation()) {\n    return false;\n  }\n\n  for (float t : {matrix_.Get(0, 3), matrix_.Get(1, 3), matrix_.Get(2, 3)}) {\n    if (!IsValueInRangeForNumericType<int>(t) || static_cast<int>(t) != t) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool Transform::IsBackFaceVisible() const {\n  // Compute whether a layer with a forward-facing normal of (0, 0, 1, 0)\n  // would have its back face visible after applying the transform.\n  if (matrix_.IsIdentity()) {\n    return false;\n  }\n\n  // This is done by transforming the normal and seeing if the resulting z\n  // value is positive or negative. However, note that transforming a normal\n  // actually requires using the inverse-transpose of the original transform.\n  //\n  // We can avoid inverting and transposing the matrix since we know we want\n  // to transform only the specific normal vector (0, 0, 1, 0). In this case,\n  // we only need the 3rd row, 3rd column of the inverse-transpose. We can\n  // calculate only the 3rd row 3rd column element of the inverse, skipping\n  // everything else.\n  //\n  // For more information, refer to:\n  //   http://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution\n  //\n\n  double determinant = matrix_.Determinant();\n\n  // If matrix was not invertible, then just assume back face is not visible.\n  if (determinant == 0) {\n    return false;\n  }\n\n  // Compute the cofactor of the 3rd row, 3rd column.\n  double cofactor_part_1 =\n      matrix_.Get(0, 0) * matrix_.Get(1, 1) * matrix_.Get(3, 3);\n\n  double cofactor_part_2 =\n      matrix_.Get(0, 1) * matrix_.Get(1, 3) * matrix_.Get(3, 0);\n\n  double cofactor_part_3 =\n      matrix_.Get(0, 3) * matrix_.Get(1, 0) * matrix_.Get(3, 1);\n\n  double cofactor_part_4 =\n      matrix_.Get(0, 0) * matrix_.Get(1, 3) * matrix_.Get(3, 1);\n\n  double cofactor_part_5 =\n      matrix_.Get(0, 1) * matrix_.Get(1, 0) * matrix_.Get(3, 3);\n\n  double cofactor_part_6 =\n      matrix_.Get(0, 3) * matrix_.Get(1, 1) * matrix_.Get(3, 0);\n\n  double cofactor33 = cofactor_part_1 + cofactor_part_2 + cofactor_part_3 -\n                      cofactor_part_4 - cofactor_part_5 - cofactor_part_6;\n\n  // Technically the transformed z component is cofactor33 / determinant.  But\n  // we can avoid the costly division because we only care about the resulting\n  // +/- sign; we can check this equivalently by multiplication.\n  return cofactor33 * determinant < -kEpsilon;\n}\n\nbool Transform::GetInverse(Transform* transform) const {\n  if (!matrix_.Invert(&transform->matrix_)) {\n    // Initialize the return value to identity if this matrix turned\n    // out to be un-invertible.\n    transform->MakeIdentity();\n    return false;\n  }\n\n  return true;\n}\n\nbool Transform::Preserves2dAxisAlignment() const {\n  // Check whether an axis aligned 2-dimensional rect would remain axis-aligned\n  // after being transformed by this matrix (and implicitly projected by\n  // dropping any non-zero z-values).\n  //\n  // The 4th column can be ignored because translations don't affect axis\n  // alignment. The 3rd column can be ignored because we are assuming 2d\n  // inputs, where z-values will be zero. The 3rd row can also be ignored\n  // because we are assuming 2d outputs, and any resulting z-value is dropped\n  // anyway. For the inner 2x2 portion, the only effects that keep a rect axis\n  // aligned are (1) swapping axes and (2) scaling axes. This can be checked by\n  // verifying only 1 element of every column and row is non-zero.  Degenerate\n  // cases that project the x or y dimension to zero are considered to preserve\n  // axis alignment.\n  //\n  // If the matrix does have perspective component that is affected by x or y\n  // values: The current implementation conservatively assumes that axis\n  // alignment is not preserved.\n\n  bool has_x_or_y_perspective =\n      matrix_.Get(3, 0) != 0 || matrix_.Get(3, 1) != 0;\n\n  int num_non_zero_in_row_0 = 0;\n  int num_non_zero_in_row_1 = 0;\n  int num_non_zero_in_col_0 = 0;\n  int num_non_zero_in_col_1 = 0;\n\n  if (std::abs(matrix_.Get(0, 0)) > kEpsilon) {\n    num_non_zero_in_row_0++;\n    num_non_zero_in_col_0++;\n  }\n\n  if (std::abs(matrix_.Get(0, 1)) > kEpsilon) {\n    num_non_zero_in_row_0++;\n    num_non_zero_in_col_1++;\n  }\n\n  if (std::abs(matrix_.Get(1, 0)) > kEpsilon) {\n    num_non_zero_in_row_1++;\n    num_non_zero_in_col_0++;\n  }\n\n  if (std::abs(matrix_.Get(1, 1)) > kEpsilon) {\n    num_non_zero_in_row_1++;\n    num_non_zero_in_col_1++;\n  }\n\n  return num_non_zero_in_row_0 <= 1 && num_non_zero_in_row_1 <= 1 &&\n         num_non_zero_in_col_0 <= 1 && num_non_zero_in_col_1 <= 1 &&\n         !has_x_or_y_perspective;\n}\n\nvoid Transform::Transpose() { matrix_.Transpose(); }\n\nvoid Transform::FlattenTo2d() {\n  matrix_.Set(2, 0, 0.0);\n  matrix_.Set(2, 1, 0.0);\n  matrix_.Set(0, 2, 0.0);\n  matrix_.Set(1, 2, 0.0);\n  matrix_.Set(2, 2, 1.0);\n  matrix_.Set(3, 2, 0.0);\n  matrix_.Set(2, 3, 0.0);\n}\n\nbool Transform::IsFlat() const {\n  return matrix_.Get(2, 0) == 0.0 && matrix_.Get(2, 1) == 0.0 &&\n         matrix_.Get(0, 2) == 0.0 && matrix_.Get(1, 2) == 0.0 &&\n         matrix_.Get(2, 2) == 1.0 && matrix_.Get(3, 2) == 0.0 &&\n         matrix_.Get(2, 3) == 0.0;\n}\n\nFloatVector2d Transform::To2dTranslation() const {\n  return FloatVector2d(matrix_.Get(0, 3), matrix_.Get(1, 3));\n}\n\nvoid Transform::TransformPoint(Point* point) const {\n  FML_DCHECK(point);\n  TransformPointInternal(matrix_, point);\n}\n\nvoid Transform::TransformPoint(FloatPoint* point) const {\n  FML_DCHECK(point);\n  TransformPointInternal(matrix_, point);\n}\n\nvoid Transform::TransformPoint(FloatPoint3d* point) const {\n  FML_DCHECK(point);\n  TransformPointInternal(matrix_, point);\n}\n\nvoid Transform::TransformVector(FloatVector3d* vector) const {\n  FML_DCHECK(vector);\n  TransformVectorInternal(matrix_, vector);\n}\n\nbool Transform::TransformPointReverse(Point* point) const {\n  FML_DCHECK(point);\n\n  // TODO(sad): Try to avoid trying to invert the matrix.\n  skity::Matrix inverse;\n  if (!matrix_.Invert(&inverse)) {\n    return false;\n  }\n\n  TransformPointInternal(inverse, point);\n  return true;\n}\n\nbool Transform::TransformPointReverse(FloatPoint* point) const {\n  FML_DCHECK(point);\n\n  // TODO(sad): Try to avoid trying to invert the matrix.\n  skity::Matrix inverse;\n  if (!matrix_.Invert(&inverse)) {\n    return false;\n  }\n\n  TransformPointInternal(inverse, point);\n  return true;\n}\n\nbool Transform::TransformPointReverse(FloatPoint3d* point) const {\n  FML_DCHECK(point);\n\n  // TODO(sad): Try to avoid trying to invert the matrix.\n  skity::Matrix inverse;\n  if (!matrix_.Invert(&inverse)) {\n    return false;\n  }\n\n  TransformPointInternal(inverse, point);\n  return true;\n}\n\nbool Transform::Blend(const Transform& from, double progress) {\n  DecomposedTransform to_decomp;\n  DecomposedTransform from_decomp;\n  if (!DecomposeTransform(&to_decomp, *this) ||\n      !DecomposeTransform(&from_decomp, from)) {\n    return false;\n  }\n\n  to_decomp = BlendDecomposedTransforms(to_decomp, from_decomp, progress);\n\n  matrix_ = ComposeTransform(to_decomp).matrix();\n  return true;\n}\n\nvoid Transform::RoundTranslationComponents() {\n  matrix_.Set(0, 3, std::round(matrix_.Get(0, 3)));\n  matrix_.Set(1, 3, std::round(matrix_.Get(1, 3)));\n}\n\nvoid Transform::TransformPointInternal(const skity::Matrix& xform,\n                                       FloatPoint3d* point) const {\n  if (xform.IsIdentity()) {\n    return;\n  }\n\n  skity::Vec4 p = {point->x(), point->y(), point->z(), 1};\n\n  p = xform * p;\n  if (p[3] != 1.f && p[3] != 0.f) {\n    float w_inverse = 1.f / p[3];\n    point->SetPoint(p[0] * w_inverse, p[1] * w_inverse, p[2] * w_inverse);\n  } else {\n    point->SetPoint(p[0], p[1], p[2]);\n  }\n}\n\nvoid Transform::TransformVectorInternal(const skity::Matrix& xform,\n                                        FloatVector3d* vector) const {\n  if (xform.IsIdentity()) {\n    return;\n  }\n\n  skity::Vec4 p = {vector->x(), vector->y(), vector->z(), 0};\n\n  p = xform * p;\n\n  vector->SetX(p[0]);\n  vector->SetY(p[1]);\n  vector->SetZ(p[2]);\n}\n\nvoid Transform::TransformPointInternal(const skity::Matrix& xform,\n                                       FloatPoint* point) const {\n  if (xform.IsIdentity()) {\n    return;\n  }\n\n  skity::Vec4 p = {point->x(), point->y(), 0, 1};\n\n  p = xform * p;\n\n  point->SetX(p[0]);\n  point->SetY(p[1]);\n}\n\nvoid Transform::TransformPointInternal(const skity::Matrix& xform,\n                                       Point* point) const {\n  FloatPoint point_float(*point);\n  TransformPointInternal(xform, &point_float);\n  point->SetX(std::lroundf(point_float.x()));\n  point->SetY(std::lroundf(point_float.y()));\n}\n\nbool Transform::ApproximatelyEqual(const Transform& transform) const {\n  static const float component_tolerance = 0.1f;\n\n  // We may have a larger discrepancy in the scroll components due to snapping\n  // (floating point error might round the other way).\n  static const float translation_tolerance = 1.f;\n\n  for (int row = 0; row < 4; row++) {\n    for (int col = 0; col < 4; col++) {\n      const float delta =\n          std::abs(matrix().Get(row, col) - transform.matrix().Get(row, col));\n      const float tolerance =\n          col == 3 && row < 3 ? translation_tolerance : component_tolerance;\n      if (delta > tolerance) {\n        return false;\n      }\n    }\n  }\n\n  return true;\n}\n\nstd::string Transform::ToString() const {\n  auto str_printf = [=](char* str_buf, size_t size) {\n    return std::snprintf(\n        str_buf, size,\n        \"[ %+0.4f %+0.4f %+0.4f %+0.4f  \\n\"\n        \"  %+0.4f %+0.4f %+0.4f %+0.4f  \\n\"\n        \"  %+0.4f %+0.4f %+0.4f %+0.4f  \\n\"\n        \"  %+0.4f %+0.4f %+0.4f %+0.4f ]\\n\",\n        matrix_.Get(0, 0), matrix_.Get(0, 1), matrix_.Get(0, 2),\n        matrix_.Get(0, 3), matrix_.Get(1, 0), matrix_.Get(1, 1),\n        matrix_.Get(1, 2), matrix_.Get(1, 3), matrix_.Get(2, 0),\n        matrix_.Get(2, 1), matrix_.Get(2, 2), matrix_.Get(2, 3),\n        matrix_.Get(3, 0), matrix_.Get(3, 1), matrix_.Get(3, 2),\n        matrix_.Get(3, 3));\n  };\n  int sz = str_printf(nullptr, 0);\n  std::vector<char> buf(sz + 1);\n  str_printf(&buf[0], buf.size());\n  return std::string(buf.data());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/transform.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_TRANSFORM_H_\n#define CLAY_GFX_GEOMETRY_TRANSFORM_H_\n\n#include <string>\n\n#include \"clay/gfx/geometry/float_vector2d.h\"\n#include \"skity/geometry/matrix.hpp\"\n\nnamespace clay {\n\nclass Point;\nclass FloatPoint;\nclass FloatPoint3d;\nclass Quaternion;\nclass FloatVector3d;\n\n// 4x4 transformation matrix. Transform is cheap and explicitly allows\n// copy/assign.\nclass Transform {\n public:\n  enum SkipInitialization { kSkipInitialization };\n\n  Transform() = default;\n\n  // Initialize with the concatenation of lhs * rhs.\n  Transform(const Transform& lhs, const Transform& rhs)\n      : matrix_(lhs.matrix_ * rhs.matrix_) {}\n  explicit Transform(const skity::Matrix& matrix) : matrix_(matrix) {}\n  // Constructs a transform from explicit 16 matrix elements. Elements\n  // should be given in row-major order.\n  Transform(float col1row1, float col2row1, float col3row1, float col4row1,\n            float col1row2, float col2row2, float col3row2, float col4row2,\n            float col1row3, float col2row3, float col3row3, float col4row3,\n            float col1row4, float col2row4, float col3row4, float col4row4);\n  // Constructs a transform from explicit 2d elements. All other matrix\n  // elements remain the same as the corresponding elements of an identity\n  // matrix.\n  Transform(float col1row1, float col2row1, float col1row2, float col2row2,\n            float x_translation, float y_translation);\n\n  // Constructs a transform corresponding to the given quaternion.\n  explicit Transform(const Quaternion& q);\n\n  bool operator==(const Transform& rhs) const { return matrix_ == rhs.matrix_; }\n  bool operator!=(const Transform& rhs) const { return matrix_ != rhs.matrix_; }\n\n  // Resets this transform to the identity transform.\n  void MakeIdentity() { matrix_.Reset(); }\n\n  // Applies the current transformation on a 2d rotation and assigns the result\n  // to |this|.\n  void Rotate(double degrees) { RotateAboutZAxis(degrees); }\n\n  // Applies the current transformation on an axis-angle rotation and assigns\n  // the result to |this|.\n  void RotateAboutXAxis(double degrees);\n  void RotateAboutYAxis(double degrees);\n  void RotateAboutZAxis(double degrees);\n  void RotateAbout(const FloatVector3d& axis, double degrees);\n\n  // Applies the current transformation on a scaling and assigns the result\n  // to |this|.\n  void Scale(float x, float y);\n  void Scale3d(float x, float y, float z);\n  FloatVector2d Scale2d() const {\n    return FloatVector2d(matrix_.Get(0, 0), matrix_.Get(1, 1));\n  }\n\n  // Applies a scale to the current transformation and assigns the result to\n  // |this|.\n  void PostScale(float x, float y);\n\n  // Applies the current transformation on a translation and assigns the result\n  // to |this|.\n  void Translate(const FloatVector2d& offset);\n  void Translate(float x, float y);\n  void Translate3d(const FloatVector3d& offset);\n  void Translate3d(float x, float y, float z);\n\n  // Applies a translation to the current transformation and assigns the result\n  // to |this|.\n  void PostTranslate(const FloatVector2d& offset);\n  void PostTranslate(float x, float y);\n\n  // Applies the current transformation on a skew and assigns the result\n  // to |this|.\n  void Skew(double angle_x, double angle_y);\n\n  // Applies the current transformation on a perspective transform and assigns\n  // the result to |this|.\n  void ApplyPerspectiveDepth(float depth);\n\n  // Applies a transformation on the current transformation\n  // (i.e. 'this = this * transform;').\n  void PreconcatTransform(const Transform& transform);\n\n  // Applies a transformation on the current transformation\n  // (i.e. 'this = transform * this;').\n  void ConcatTransform(const Transform& transform);\n\n  // Returns true if this is the identity matrix.\n  // This function modifies a mutable variable in |matrix_|.\n  bool IsIdentity() const { return matrix_.IsIdentity(); }\n\n  // Returns true if the matrix is either identity or pure translation.\n  bool IsIdentityOrTranslation() const { return matrix_.OnlyTranslate(); }\n\n  // Returns true if the matrix is either the identity or a 2d translation.\n  bool IsIdentityOr2DTranslation() const {\n    return matrix_.OnlyTranslate() && matrix_.Get(2, 3) == 0;\n  }\n\n  // Returns true if the matrix is either identity or pure translation,\n  // allowing for an amount of inaccuracy as specified by the parameter.\n  bool IsApproximatelyIdentityOrTranslation(float tolerance) const;\n  bool IsApproximatelyIdentityOrIntegerTranslation(float tolerance) const;\n\n  // Returns true if the matrix is either a positive scale and/or a translation.\n  bool IsPositiveScaleOrTranslation() const {\n    if (!IsScaleOrTranslation()) return false;\n    return matrix_.Get(0, 0) > 0.0 && matrix_.Get(1, 1) > 0.0 &&\n           matrix_.Get(2, 2) > 0.0;\n  }\n\n  // Returns true if the matrix is identity or, if the matrix consists only\n  // of a translation whose components can be represented as integers. Returns\n  // false if the translation contains a fractional component or is too large to\n  // fit in an integer.\n  bool IsIdentityOrIntegerTranslation() const;\n\n  // Returns true if the matrix had only scaling components.\n  bool IsScale2d() const { return matrix_.OnlyScale(); }\n\n  // Returns true if the matrix is has only scaling and translation components.\n  bool IsScaleOrTranslation() const { return matrix_.OnlyScaleAndTranslate(); }\n\n  // Returns true if axis-aligned 2d rects will remain axis-aligned after being\n  // transformed by this matrix.\n  bool Preserves2dAxisAlignment() const;\n\n  // Returns true if the matrix has any perspective component that would\n  // change the w-component of a homogeneous point.\n  bool HasPerspective() const { return matrix_.HasPersp(); }\n\n  // Returns true if this transform is non-singular.\n  bool IsInvertible() const { return matrix_.Invert(NULL); }\n\n  // Returns true if a layer with a forward-facing normal of (0, 0, 1) would\n  // have its back side facing frontwards after applying the transform.\n  bool IsBackFaceVisible() const;\n\n  // Inverts the transform which is passed in. Returns true if successful, or\n  // sets |transform| to the identify matrix on failure.\n  bool GetInverse(Transform* transform) const\n      __attribute__((warn_unused_result));\n\n  // Transposes this transform in place.\n  void Transpose();\n\n  // Set 3rd row and 3rd colum to (0, 0, 1, 0). Note that this flattening\n  // operation is not quite the same as an orthographic projection and is\n  // technically not a linear operation.\n  //\n  // One useful interpretation of doing this operation:\n  //  - For x and y values, the new transform behaves effectively like an\n  //    orthographic projection was added to the matrix sequence.\n  //  - For z values, the new transform overrides any effect that the transform\n  //    had on z, and instead it preserves the z value for any points that are\n  //    transformed.\n  //  - Because of linearity of transforms, this flattened transform also\n  //    preserves the effect that any subsequent (multiplied from the right)\n  //    transforms would have on z values.\n  //\n  void FlattenTo2d();\n\n  // Returns true if the 3rd row and 3rd column are both (0, 0, 1, 0).\n  bool IsFlat() const;\n\n  // Returns the x and y translation components of the matrix.\n  FloatVector2d To2dTranslation() const;\n\n  // Applies the transformation to the point.\n  void TransformPoint(FloatPoint3d* point) const;\n\n  // Applies the transformation to the point.\n  void TransformPoint(FloatPoint* point) const;\n\n  // Applies the transformation to the point.\n  void TransformPoint(Point* point) const;\n\n  // Applies the transformation to the vector.\n  void TransformVector(FloatVector3d* vector) const;\n\n  // Applies the reverse transformation on the point. Returns true if the\n  // transformation can be inverted.\n  bool TransformPointReverse(FloatPoint3d* point) const;\n\n  // Applies the reverse transformation on the point. Returns true if the\n  // transformation can be inverted.\n  bool TransformPointReverse(FloatPoint* point) const;\n\n  // Applies the reverse transformation on the point. Returns true if the\n  // transformation can be inverted. Rounds the result to the nearest point.\n  bool TransformPointReverse(Point* point) const;\n\n  // Decomposes |this| and |from|, interpolates the decomposed values, and\n  // sets |this| to the reconstituted result. Returns false if either matrix\n  // can't be decomposed. Uses routines described in this spec:\n  // http://www.w3.org/TR/css3-3d-transforms/.\n  //\n  // Note: this call is expensive since we need to decompose the transform. If\n  // you're going to be calling this rapidly (e.g., in an animation) you should\n  // decompose once using DecomposeTransforms and reuse your\n  // DecomposedTransform.\n  bool Blend(const Transform& from, double progress);\n\n  void RoundTranslationComponents();\n\n  // Returns |this| * |other|.\n  Transform operator*(const Transform& other) const {\n    return Transform(*this, other);\n  }\n\n  // Sets |this| = |this| * |other|\n  Transform& operator*=(const Transform& other) {\n    PreconcatTransform(other);\n    return *this;\n  }\n\n  // Returns the underlying matrix.\n  const skity::Matrix& matrix() const { return matrix_; }\n  skity::Matrix& matrix() { return matrix_; }\n\n  bool ApproximatelyEqual(const Transform& transform) const;\n\n  std::string ToString() const;\n\n private:\n  void TransformPointInternal(const skity::Matrix& xform, Point* point) const;\n\n  void TransformPointInternal(const skity::Matrix& xform,\n                              FloatPoint* point) const;\n\n  void TransformPointInternal(const skity::Matrix& xform,\n                              FloatPoint3d* point) const;\n\n  void TransformVectorInternal(const skity::Matrix& xform,\n                               FloatVector3d* vector) const;\n\n  skity::Matrix matrix_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_TRANSFORM_H_\n"
  },
  {
    "path": "clay/gfx/geometry/transform_operation.cc",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/transform_operation.h\"\n\n#include <limits>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_vector3d.h\"\n#include \"clay/gfx/geometry/math_util.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n#include \"clay/gfx/geometry/transform_util.h\"\n\nnamespace {\nconst float kAngleEpsilon = 1e-4f;\n}\n\nnamespace clay {\n\nbool TransformOperation::IsIdentity() const { return matrix.IsIdentity(); }\n\nstatic bool IsOperationIdentity(const TransformOperation* operation) {\n  return !operation || operation->IsIdentity();\n}\n\nstatic bool ShareSameAxis(const TransformOperation* from,\n                          const TransformOperation* to, float* axis_x,\n                          float* axis_y, float* axis_z, float* angle_from) {\n  if (IsOperationIdentity(from) && IsOperationIdentity(to)) {\n    return false;\n  }\n\n  if (IsOperationIdentity(from) && !IsOperationIdentity(to)) {\n    *axis_x = to->rotate.axis.x;\n    *axis_y = to->rotate.axis.y;\n    *axis_z = to->rotate.axis.z;\n    *angle_from = 0;\n    return true;\n  }\n\n  if (!IsOperationIdentity(from) && IsOperationIdentity(to)) {\n    *axis_x = from->rotate.axis.x;\n    *axis_y = from->rotate.axis.y;\n    *axis_z = from->rotate.axis.z;\n    *angle_from = from->rotate.angle;\n    return true;\n  }\n\n  float length_2 = from->rotate.axis.x * from->rotate.axis.x +\n                   from->rotate.axis.y * from->rotate.axis.y +\n                   from->rotate.axis.z * from->rotate.axis.z;\n  float other_length_2 = to->rotate.axis.x * to->rotate.axis.x +\n                         to->rotate.axis.y * to->rotate.axis.y +\n                         to->rotate.axis.z * to->rotate.axis.z;\n\n  if (length_2 <= kAngleEpsilon || other_length_2 <= kAngleEpsilon) {\n    return false;\n  }\n\n  float dot = to->rotate.axis.x * from->rotate.axis.x +\n              to->rotate.axis.y * from->rotate.axis.y +\n              to->rotate.axis.z * from->rotate.axis.z;\n  float error = std::abs(1.f - (dot * dot) / (length_2 * other_length_2));\n  bool result = error < kAngleEpsilon;\n  if (result) {\n    *axis_x = to->rotate.axis.x;\n    *axis_y = to->rotate.axis.y;\n    *axis_z = to->rotate.axis.z;\n    // If the axes are pointing in opposite directions, we need to reverse\n    // the angle.\n    *angle_from = dot > 0 ? from->rotate.angle : -from->rotate.angle;\n  }\n  return result;\n}\n\nstatic float BlendFloats(float from, float to, float progress) {\n  return from * (1 - progress) + to * progress;\n}\n\nvoid TransformOperation::Bake() {\n  matrix.MakeIdentity();\n  switch (type) {\n    case TransformOperation::kTypeTranslate:\n      matrix.Translate3d(translate.x, translate.y, translate.z);\n      break;\n    case TransformOperation::kTypeRotate:\n      matrix.RotateAbout(\n          FloatVector3d(rotate.axis.x, rotate.axis.y, rotate.axis.z),\n          rotate.angle);\n      break;\n    case TransformOperation::kTypeScale:\n      matrix.Scale3d(scale.x, scale.y, scale.z);\n      break;\n    case TransformOperation::kTypeSkewX:\n    case TransformOperation::kTypeSkewY:\n    case TransformOperation::kTypeSkew:\n      matrix.Skew(skew.x, skew.y);\n      break;\n    case TransformOperation::kTypePerspective:\n      matrix.ApplyPerspectiveDepth(perspective_depth);\n      break;\n    case TransformOperation::kTypeMatrix:\n    case TransformOperation::kTypeMatrix3d:\n    case TransformOperation::kTypeIdentity:\n      break;\n  }\n}\n\nbool TransformOperation::ApproximatelyEqual(const TransformOperation& other,\n                                            float tolerance) const {\n  FML_DCHECK(0 <= tolerance);\n  if (type != other.type) {\n    return false;\n  }\n  switch (type) {\n    case TransformOperation::kTypeTranslate:\n      return IsApproximatelyEqual(translate.x, other.translate.x, tolerance) &&\n             IsApproximatelyEqual(translate.y, other.translate.y, tolerance) &&\n             IsApproximatelyEqual(translate.z, other.translate.z, tolerance);\n    case TransformOperation::kTypeRotate:\n      return IsApproximatelyEqual(rotate.axis.x, other.rotate.axis.x,\n                                  tolerance) &&\n             IsApproximatelyEqual(rotate.axis.y, other.rotate.axis.y,\n                                  tolerance) &&\n             IsApproximatelyEqual(rotate.axis.z, other.rotate.axis.z,\n                                  tolerance) &&\n             IsApproximatelyEqual(rotate.angle, other.rotate.angle, tolerance);\n    case TransformOperation::kTypeScale:\n      return IsApproximatelyEqual(scale.x, other.scale.x, tolerance) &&\n             IsApproximatelyEqual(scale.y, other.scale.y, tolerance) &&\n             IsApproximatelyEqual(scale.z, other.scale.z, tolerance);\n    case TransformOperation::kTypeSkewX:\n    case TransformOperation::kTypeSkewY:\n    case TransformOperation::kTypeSkew:\n      return IsApproximatelyEqual(skew.x, other.skew.x, tolerance) &&\n             IsApproximatelyEqual(skew.y, other.skew.y, tolerance);\n    case TransformOperation::kTypePerspective:\n      return IsApproximatelyEqual(perspective_depth, other.perspective_depth,\n                                  tolerance);\n    case TransformOperation::kTypeMatrix:\n    case TransformOperation::kTypeMatrix3d:\n      // TODO(vollick): we could expose a tolerance on Transform, but it's\n      // complex since we need a different tolerance per component. Driving this\n      // with a single tolerance will take some care. For now, we will check\n      // exact equality where the tolerance is 0.0f, otherwise we will use the\n      // unparameterized version of Transform::ApproximatelyEqual.\n      if (tolerance == 0.0f) {\n        return matrix == other.matrix;\n      } else {\n        return matrix.ApproximatelyEqual(other.matrix);\n      }\n    case TransformOperation::kTypeIdentity:\n      return other.matrix.IsIdentity();\n  }\n  FML_UNREACHABLE();\n  return false;\n}\n\nbool TransformOperation::BlendTransformOperations(\n    const TransformOperation* from, const TransformOperation* to,\n    float progress, TransformOperation* result) {\n  if (IsOperationIdentity(from) && IsOperationIdentity(to)) {\n    return true;\n  }\n\n  TransformOperation::Type interpolation_type =\n      TransformOperation::kTypeIdentity;\n  if (IsOperationIdentity(to)) {\n    interpolation_type = from->type;\n  } else {\n    interpolation_type = to->type;\n  }\n  result->type = interpolation_type;\n\n  switch (interpolation_type) {\n    case TransformOperation::kTypeTranslate: {\n      float from_x = IsOperationIdentity(from) ? 0 : from->translate.x;\n      float from_y = IsOperationIdentity(from) ? 0 : from->translate.y;\n      float from_z = IsOperationIdentity(from) ? 0 : from->translate.z;\n      float to_x = IsOperationIdentity(to) ? 0 : to->translate.x;\n      float to_y = IsOperationIdentity(to) ? 0 : to->translate.y;\n      float to_z = IsOperationIdentity(to) ? 0 : to->translate.z;\n      result->translate.x = BlendFloats(from_x, to_x, progress),\n      result->translate.y = BlendFloats(from_y, to_y, progress),\n      result->translate.z = BlendFloats(from_z, to_z, progress), result->Bake();\n      break;\n    }\n    case TransformOperation::kTypeRotate: {\n      float axis_x = 0;\n      float axis_y = 0;\n      float axis_z = 1;\n      float from_angle = 0;\n      float to_angle = IsOperationIdentity(to) ? 0 : to->rotate.angle;\n      if (ShareSameAxis(from, to, &axis_x, &axis_y, &axis_z, &from_angle)) {\n        result->rotate.axis.x = axis_x;\n        result->rotate.axis.y = axis_y;\n        result->rotate.axis.z = axis_z;\n        result->rotate.angle = BlendFloats(from_angle, to_angle, progress);\n        result->Bake();\n      } else {\n        if (!IsOperationIdentity(to)) {\n          result->matrix = to->matrix;\n        }\n        Transform from_matrix;\n        if (!IsOperationIdentity(from)) {\n          from_matrix = from->matrix;\n        }\n        if (!result->matrix.Blend(from_matrix, progress)) {\n          return false;\n        }\n      }\n      break;\n    }\n    case TransformOperation::kTypeScale: {\n      float from_x = IsOperationIdentity(from) ? 1 : from->scale.x;\n      float from_y = IsOperationIdentity(from) ? 1 : from->scale.y;\n      float from_z = IsOperationIdentity(from) ? 1 : from->scale.z;\n      float to_x = IsOperationIdentity(to) ? 1 : to->scale.x;\n      float to_y = IsOperationIdentity(to) ? 1 : to->scale.y;\n      float to_z = IsOperationIdentity(to) ? 1 : to->scale.z;\n      result->scale.x = BlendFloats(from_x, to_x, progress);\n      result->scale.y = BlendFloats(from_y, to_y, progress);\n      result->scale.z = BlendFloats(from_z, to_z, progress);\n      result->Bake();\n      break;\n    }\n    case TransformOperation::kTypeSkewX:\n    case TransformOperation::kTypeSkewY:\n    case TransformOperation::kTypeSkew: {\n      float from_x = IsOperationIdentity(from) ? 0 : from->skew.x;\n      float from_y = IsOperationIdentity(from) ? 0 : from->skew.y;\n      float to_x = IsOperationIdentity(to) ? 0 : to->skew.x;\n      float to_y = IsOperationIdentity(to) ? 0 : to->skew.y;\n      result->skew.x = BlendFloats(from_x, to_x, progress);\n      result->skew.y = BlendFloats(from_y, to_y, progress);\n      result->Bake();\n      break;\n    }\n    case TransformOperation::kTypePerspective: {\n      float from_perspective_depth = IsOperationIdentity(from)\n                                         ? std::numeric_limits<float>::max()\n                                         : from->perspective_depth;\n      float to_perspective_depth = IsOperationIdentity(to)\n                                       ? std::numeric_limits<float>::max()\n                                       : to->perspective_depth;\n      if (from_perspective_depth == 0.f || to_perspective_depth == 0.f) {\n        return false;\n      }\n\n      float blended_perspective_depth = BlendFloats(\n          1.f / from_perspective_depth, 1.f / to_perspective_depth, progress);\n\n      if (blended_perspective_depth == 0.f) {\n        return false;\n      }\n\n      result->perspective_depth = 1.f / blended_perspective_depth;\n      result->Bake();\n      break;\n    }\n    case TransformOperation::kTypeMatrix:\n    case TransformOperation::kTypeMatrix3d: {\n      if (!IsOperationIdentity(to)) {\n        result->matrix = to->matrix;\n      }\n      Transform from_matrix;\n      if (!IsOperationIdentity(from)) {\n        from_matrix = from->matrix;\n      }\n      if (!result->matrix.Blend(from_matrix, progress)) {\n        return false;\n      }\n      break;\n    }\n    case TransformOperation::kTypeIdentity:\n      // Do nothing.\n      break;\n  }\n\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/transform_operation.h",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_TRANSFORM_OPERATION_H_\n#define CLAY_GFX_GEOMETRY_TRANSFORM_OPERATION_H_\n\n#include \"clay/gfx/geometry/transform.h\"\n\nnamespace clay {\n\nstruct TransformOperation {\n  enum Type {\n    kTypeTranslate,\n    kTypeRotate,\n    kTypeScale,\n    kTypeSkewX,\n    kTypeSkewY,\n    kTypeSkew,\n    kTypePerspective,\n    kTypeMatrix,\n    kTypeMatrix3d,\n    kTypeIdentity\n  };\n\n  TransformOperation() : type(kTypeIdentity) {}\n\n  Type type;\n  Transform matrix;\n\n  union {\n    float perspective_depth;\n\n    struct {\n      float x, y;\n    } skew;\n\n    struct {\n      float x, y, z;\n    } scale;\n\n    struct {\n      float x, y, z;\n    } translate;\n\n    struct {\n      struct {\n        float x, y, z;\n      } axis;\n\n      float angle;\n    } rotate;\n  };\n\n  bool IsIdentity() const;\n\n  // Sets |matrix| based on type and the union values.\n  void Bake();\n\n  bool ApproximatelyEqual(const TransformOperation& other,\n                          float tolerance) const;\n\n  static bool BlendTransformOperations(const TransformOperation* from,\n                                       const TransformOperation* to,\n                                       float progress,\n                                       TransformOperation* result);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_TRANSFORM_OPERATION_H_\n"
  },
  {
    "path": "clay/gfx/geometry/transform_operations.cc",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/transform_operations.h\"\n\n#include <stddef.h>\n\n#include <algorithm>\n#include <cmath>\n#include <utility>\n\n#include \"clay/gfx/animation/animator_target.h\"\n#include \"clay/gfx/geometry/float_vector3d.h\"\n#include \"clay/gfx/geometry/math_util.h\"\n#include \"clay/gfx/geometry/transform_util.h\"\n\nnamespace clay {\n\nTransformOperations::TransformOperations() = default;\n\nTransformOperations::TransformOperations(const TransformOperations& other) {\n  operations_ = other.operations_;\n  perspective_ = other.perspective_;\n}\n\nTransformOperations::~TransformOperations() = default;\n\nTransformOperations& TransformOperations::operator=(\n    const TransformOperations& other) {\n  operations_ = other.operations_;\n  perspective_ = other.perspective_;\n  return *this;\n}\n\nTransformOperations::TransformOperations(const ClayTransform& rk_transform,\n                                         FloatSize percentage_resolution_size) {\n#define GET_VALUE(index, relative_to)                    \\\n  (op.unit[index] == ClayPlatformLengthUnit::kPercentage \\\n       ? op.value[index] * relative_to                   \\\n       : op.value[index])\n  auto width = percentage_resolution_size.width();\n  auto height = percentage_resolution_size.height();\n\n  for (int i = 0; i < rk_transform.size; ++i) {\n    const ClayTransformOP& op = rk_transform.op[i];\n    switch (op.type) {\n      case ClayTransformType::kTranslate: {\n        AppendTranslate(GET_VALUE(0, width), GET_VALUE(1, height), 0.0f);\n      } break;\n      case ClayTransformType::kTranslateX: {\n        AppendTranslate(GET_VALUE(0, width), 0.0f, 0.0f);\n      } break;\n      case ClayTransformType::kTranslateY: {\n        AppendTranslate(0.0f, GET_VALUE(0, height), 0.0f);\n      } break;\n      case ClayTransformType::kTranslateZ: {\n        AppendTranslate(0.0f, 0.0f, GET_VALUE(0, 0));\n      } break;\n      case ClayTransformType::kTranslate3d: {\n        AppendTranslate(GET_VALUE(0, width), GET_VALUE(1, height),\n                        GET_VALUE(2, 0));\n      } break;\n      case ClayTransformType::kRotateX: {\n        AppendRotate(1.0f, 0.0f, 0.0f, op.value[0]);\n      } break;\n      case ClayTransformType::kRotateY: {\n        AppendRotate(0.0f, 1.0f, 0.0f, op.value[0]);\n      } break;\n      case ClayTransformType::kRotate:\n      case ClayTransformType::kRotateZ: {\n        AppendRotate(0.0f, 0.0f, 1.0f, op.value[0]);\n      } break;\n      case ClayTransformType::kScale: {\n        AppendScale(op.value[0], op.value[1], 1.0f);\n      } break;\n      case ClayTransformType::kScaleX: {\n        AppendScale(op.value[0], 1.0f, 1.0f);\n      } break;\n      case ClayTransformType::kScaleY: {\n        AppendScale(1.0f, op.value[0], 1.0f);\n      } break;\n      case ClayTransformType::kSkew: {\n        AppendSkew(op.value[0], op.value[1]);\n      } break;\n      case ClayTransformType::kSkewX: {\n        AppendSkewX(op.value[0]);\n      } break;\n      case ClayTransformType::kSkewY: {\n        AppendSkewY(op.value[0]);\n      } break;\n      case ClayTransformType::kMatrix:\n      case ClayTransformType::kMatrix3d: {\n        AppendMatrix(op.matrix);\n      } break;\n      case ClayTransformType::kNone:\n      default:\n        break;\n    }\n  }\n#undef GET_VALUE\n}\n\nTransformOperations::TransformOperations(\n    const std::vector<TransformRaw>& transform_raws, float width,\n    float height) {\n  for (const auto& transform_raw : transform_raws) {\n    switch (static_cast<ClayTransformType>(transform_raw.type)) {\n      case ClayTransformType::kTranslate: {\n        AppendTranslate(transform_raw.values[0].GetValue(width),\n                        transform_raw.values[1].GetValue(height), 0.0f);\n      } break;\n      case ClayTransformType::kTranslateX: {\n        AppendTranslate(transform_raw.values[0].GetValue(width), 0.0f, 0.0f);\n      } break;\n      case ClayTransformType::kTranslateY: {\n        AppendTranslate(0.0f, transform_raw.values[0].GetValue(height), 0.0f);\n      } break;\n      case ClayTransformType::kTranslateZ: {\n        AppendTranslate(0.0f, 0.0f, transform_raw.values[0].GetValue(0));\n      } break;\n      case ClayTransformType::kTranslate3d: {\n        AppendTranslate(transform_raw.values[0].GetValue(width),\n                        transform_raw.values[1].GetValue(height),\n                        transform_raw.values[2].GetValue(0));\n      } break;\n      case ClayTransformType::kRotateX: {\n        AppendRotate(1.0f, 0.0f, 0.0f, transform_raw.values[0].GetValue(0));\n      } break;\n      case ClayTransformType::kRotateY: {\n        AppendRotate(0.0f, 1.0f, 0.0f, transform_raw.values[0].GetValue(0));\n      } break;\n      case ClayTransformType::kRotate:\n      case ClayTransformType::kRotateZ: {\n        AppendRotate(0.0f, 0.0f, 1.0f, transform_raw.values[0].GetValue(0));\n      } break;\n      case ClayTransformType::kScale: {\n        AppendScale(transform_raw.values[0].GetValue(0),\n                    transform_raw.values[1].GetValue(0), 1.0f);\n      } break;\n      case ClayTransformType::kScaleX: {\n        AppendScale(transform_raw.values[0].GetValue(0), 1.0f, 1.0f);\n      } break;\n      case ClayTransformType::kScaleY: {\n        AppendScale(1.0f, transform_raw.values[0].GetValue(0), 1.0f);\n      } break;\n      case ClayTransformType::kSkew: {\n        AppendSkew(transform_raw.values[0].GetValue(0),\n                   transform_raw.values[1].GetValue(0));\n      } break;\n      case ClayTransformType::kSkewX: {\n        AppendSkewX(transform_raw.values[0].GetValue(0));\n      } break;\n      case ClayTransformType::kSkewY: {\n        AppendSkewY(transform_raw.values[0].GetValue(0));\n      } break;\n      case ClayTransformType::kMatrix:\n      case ClayTransformType::kMatrix3d: {\n        AppendMatrix(transform_raw.matrix);\n      } break;\n      case ClayTransformType::kNone:\n      default:\n        break;\n    }\n  }\n}\n\nTransform TransformOperations::Apply() const {\n  ApplyPerspective();\n  return ApplyRemaining(0);\n}\n\nvoid TransformOperations::ApplyPerspective() const {\n  if (fabsf(perspective_) <= 1e-6f) {\n    return;\n  }\n  TransformOperation to_add;\n  to_add.type = TransformOperation::kTypePerspective;\n  to_add.perspective_depth = perspective_;\n  to_add.Bake();\n  // Perspective should be at front.\n  operations_.push_front(to_add);\n  decomposed_transforms_.clear();\n}\n\nTransform TransformOperations::ApplyRemaining(size_t start) const {\n  Transform to_return;\n  for (size_t i = start; i < operations_.size(); i++) {\n    to_return.PreconcatTransform(operations_[i].matrix);\n  }\n  return to_return;\n}\n\nTransformOperations TransformOperations::Blend(const TransformOperations& from,\n                                               float progress) const {\n  TransformOperations to_return;\n  if (!BlendInternal(from, progress, &to_return)) {\n    // If the matrices cannot be blended, fallback to discrete animation logic.\n    // See https://drafts.csswg.org/css-transforms/#matrix-interpolation\n    to_return = progress < 0.5 ? from : *this;\n  }\n  return to_return;\n}\n\nbool TransformOperations::PreservesAxisAlignment() const {\n  for (const auto& operation : operations_) {\n    switch (operation.type) {\n      case TransformOperation::kTypeIdentity:\n      case TransformOperation::kTypeTranslate:\n      case TransformOperation::kTypeScale:\n        continue;\n      case TransformOperation::kTypeMatrix:\n      case TransformOperation::kTypeMatrix3d:\n        if (!operation.matrix.IsIdentity() &&\n            !operation.matrix.IsScaleOrTranslation()) {\n          return false;\n        }\n        continue;\n      case TransformOperation::kTypeRotate:\n      case TransformOperation::kTypeSkewX:\n      case TransformOperation::kTypeSkewY:\n      case TransformOperation::kTypeSkew:\n      case TransformOperation::kTypePerspective:\n        return false;\n    }\n  }\n  return true;\n}\n\nbool TransformOperations::IsTranslation() const {\n  for (const auto& operation : operations_) {\n    switch (operation.type) {\n      case TransformOperation::kTypeIdentity:\n      case TransformOperation::kTypeTranslate:\n        continue;\n      case TransformOperation::kTypeMatrix:\n      case TransformOperation::kTypeMatrix3d:\n        if (!operation.matrix.IsIdentityOrTranslation()) {\n          return false;\n        }\n        continue;\n      case TransformOperation::kTypeRotate:\n      case TransformOperation::kTypeScale:\n      case TransformOperation::kTypeSkewX:\n      case TransformOperation::kTypeSkewY:\n      case TransformOperation::kTypeSkew:\n      case TransformOperation::kTypePerspective:\n        return false;\n    }\n  }\n  return true;\n}\n\nstatic float TanDegrees(double degrees) { return std::tan(DegToRad(degrees)); }\n\nbool TransformOperations::ScaleComponent(float* scale) const {\n  float operations_scale = 1.f;\n  for (const auto& operation : operations_) {\n    switch (operation.type) {\n      case TransformOperation::kTypeIdentity:\n      case TransformOperation::kTypeTranslate:\n      case TransformOperation::kTypeRotate:\n        continue;\n      case TransformOperation::kTypeMatrix:\n      case TransformOperation::kTypeMatrix3d: {\n        if (operation.matrix.HasPerspective()) {\n          return false;\n        }\n        FloatVector2d scale_components =\n            ComputeTransform2dScaleComponents(operation.matrix, 1.f);\n        operations_scale *=\n            std::max(scale_components.x(), scale_components.y());\n        break;\n      }\n      case TransformOperation::kTypeSkewX:\n      case TransformOperation::kTypeSkewY:\n      case TransformOperation::kTypeSkew: {\n        float x_component = TanDegrees(operation.skew.x);\n        float y_component = TanDegrees(operation.skew.y);\n        float x_scale = std::sqrt(x_component * x_component + 1);\n        float y_scale = std::sqrt(y_component * y_component + 1);\n        operations_scale *= std::max(x_scale, y_scale);\n        break;\n      }\n      case TransformOperation::kTypePerspective:\n        return false;\n      case TransformOperation::kTypeScale:\n        operations_scale *= std::max(\n            std::abs(operation.scale.x),\n            std::max(std::abs(operation.scale.y), std::abs(operation.scale.z)));\n    }\n  }\n  *scale = operations_scale;\n  return true;\n}\n\nbool TransformOperations::MatchesTypes(const TransformOperations& other) const {\n  if (operations_.size() == 0 || other.operations_.size() == 0) {\n    return true;\n  }\n\n  if (operations_.size() != other.operations_.size()) {\n    return false;\n  }\n\n  for (size_t i = 0; i < operations_.size(); ++i) {\n    if (operations_[i].type != other.operations_[i].type) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nsize_t TransformOperations::MatchingPrefixLength(\n    const TransformOperations& other) const {\n  size_t num_operations =\n      std::min(operations_.size(), other.operations_.size());\n  for (size_t i = 0; i < num_operations; ++i) {\n    if (operations_[i].type != other.operations_[i].type) {\n      // Remaining operations in each operations list require matrix/matrix3d\n      // interpolation.\n      return i;\n    }\n  }\n  // If the operations match to the length of the shorter list, then pad its\n  // length with the matching identity operations.\n  // https://drafts.csswg.org/css-transforms/#transform-function-lists\n  return std::max(operations_.size(), other.operations_.size());\n}\n\nbool TransformOperations::CanBlendWith(const TransformOperations& other) const {\n  TransformOperations dummy;\n  return BlendInternal(other, 0.5, &dummy);\n}\n\nvoid TransformOperations::AppendTranslate(float x, float y, float z) {\n  TransformOperation to_add;\n  to_add.matrix.Translate3d(x, y, z);\n  to_add.type = TransformOperation::kTypeTranslate;\n  to_add.translate.x = x;\n  to_add.translate.y = y;\n  to_add.translate.z = z;\n  operations_.push_back(to_add);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::AppendRotate(float x, float y, float z,\n                                       float degrees) {\n  TransformOperation to_add;\n  to_add.type = TransformOperation::kTypeRotate;\n  to_add.rotate.axis.x = x;\n  to_add.rotate.axis.y = y;\n  to_add.rotate.axis.z = z;\n  to_add.rotate.angle = degrees;\n  to_add.Bake();\n  operations_.push_back(to_add);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::AppendScale(float x, float y, float z) {\n  TransformOperation to_add;\n  to_add.type = TransformOperation::kTypeScale;\n  to_add.scale.x = x;\n  to_add.scale.y = y;\n  to_add.scale.z = z;\n  to_add.Bake();\n  operations_.push_back(to_add);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::AppendSkewX(float x) {\n  TransformOperation to_add;\n  to_add.type = TransformOperation::kTypeSkewX;\n  to_add.skew.x = x;\n  to_add.skew.y = 0;\n  to_add.Bake();\n  operations_.push_back(to_add);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::AppendSkewY(float y) {\n  TransformOperation to_add;\n  to_add.type = TransformOperation::kTypeSkewY;\n  to_add.skew.x = 0;\n  to_add.skew.y = y;\n  to_add.Bake();\n  operations_.push_back(to_add);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::AppendSkew(float x, float y) {\n  TransformOperation to_add;\n  to_add.type = TransformOperation::kTypeSkew;\n  to_add.skew.x = x;\n  to_add.skew.y = y;\n  to_add.Bake();\n  operations_.push_back(to_add);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::AppendPerspective(float depth) {\n  TransformOperation to_add;\n  to_add.type = TransformOperation::kTypePerspective;\n  to_add.perspective_depth = depth;\n  to_add.Bake();\n  operations_.push_back(to_add);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::AppendMatrix(const double matrix[16]) {\n  skity::Matrix m44(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4],\n                    matrix[5], matrix[6], matrix[7], matrix[8], matrix[9],\n                    matrix[10], matrix[11], matrix[12], matrix[13], matrix[14],\n                    matrix[15]);\n  Transform transform(m44);\n  AppendMatrix(transform);\n}\n\nvoid TransformOperations::AppendMatrix(const Transform& matrix) {\n  TransformOperation to_add;\n  to_add.matrix = matrix;\n  to_add.type = TransformOperation::kTypeMatrix;\n  operations_.push_back(to_add);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::AppendIdentity() { operations_.emplace_back(); }\n\nvoid TransformOperations::Append(const TransformOperation& operation) {\n  operations_.push_back(operation);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::Append(const TransformOperations& ops) {\n  for (const auto& operation : ops.operations_) {\n    operations_.push_back(operation);\n  }\n  decomposed_transforms_.clear();\n}\n\nbool TransformOperations::IsIdentity() const {\n  for (const auto& operation : operations_) {\n    if (!operation.IsIdentity()) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool TransformOperations::ApproximatelyEqual(const TransformOperations& other,\n                                             float tolerance) const {\n  if (size() != other.size()) {\n    return false;\n  }\n  for (size_t i = 0; i < operations_.size(); ++i) {\n    if (!operations_[i].ApproximatelyEqual(other.operations_[i], tolerance)) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool TransformOperations::BlendInternal(const TransformOperations& from,\n                                        float progress,\n                                        TransformOperations* result) const {\n  bool from_identity = from.IsIdentity();\n  bool to_identity = IsIdentity();\n  if (from_identity && to_identity) {\n    return true;\n  }\n\n  size_t matching_prefix_length = MatchingPrefixLength(from);\n  size_t from_size = from_identity ? 0 : from.operations_.size();\n  size_t to_size = to_identity ? 0 : operations_.size();\n  size_t num_operations = std::max(from_size, to_size);\n\n  for (size_t i = 0; i < matching_prefix_length; ++i) {\n    TransformOperation blended;\n    if (!TransformOperation::BlendTransformOperations(\n            i >= from_size ? nullptr : &from.operations_[i],\n            i >= to_size ? nullptr : &operations_[i], progress, &blended)) {\n      return false;\n    }\n    result->Append(blended);\n  }\n\n  if (matching_prefix_length < num_operations) {\n    if (!ComputeDecomposedTransform(matching_prefix_length) ||\n        !from.ComputeDecomposedTransform(matching_prefix_length)) {\n      return false;\n    }\n    DecomposedTransform matrix_transform = BlendDecomposedTransforms(\n        *decomposed_transforms_[matching_prefix_length].get(),\n        *from.decomposed_transforms_[matching_prefix_length].get(), progress);\n    result->AppendMatrix(ComposeTransform(matrix_transform));\n  }\n  return true;\n}\n\nbool TransformOperations::ComputeDecomposedTransform(\n    size_t start_offset) const {\n  auto it = decomposed_transforms_.find(start_offset);\n  if (it == decomposed_transforms_.end()) {\n    std::unique_ptr<DecomposedTransform> decomposed_transform =\n        std::make_unique<DecomposedTransform>();\n    Transform transform = ApplyRemaining(start_offset);\n    if (!DecomposeTransform(decomposed_transform.get(), transform)) {\n      return false;\n    }\n    decomposed_transforms_[start_offset] = std::move(decomposed_transform);\n  }\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/transform_operations.h",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_TRANSFORM_OPERATIONS_H_\n#define CLAY_GFX_GEOMETRY_TRANSFORM_OPERATIONS_H_\n\n#include <cmath>\n#include <deque>\n#include <memory>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/geometry/transform.h\"\n#include \"clay/gfx/geometry/transform_operation.h\"\n#include \"clay/gfx/geometry/transform_raw.h\"\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\n\nstruct DecomposedTransform;\n\n// Transform operations are a decomposed transformation matrix. It can be\n// applied to obtain a Transform at any time, and can be blended\n// intelligently with other transform operations, so long as they represent the\n// same decomposition. For example, if we have a transform that is made up of\n// a rotation followed by skew, it can be blended intelligently with another\n// transform made up of a rotation followed by a skew. Blending is possible if\n// we have two dissimilar sets of transform operations, but the effect may not\n// be what was intended. For more information, see the comments for the blend\n// function below.\nclass TransformOperations {\n public:\n  constexpr static float kApproximatelyEqualTolerance = 1e-3;\n\n  TransformOperations();\n  TransformOperations(const TransformOperations& other);\n  ~TransformOperations();\n\n  TransformOperations& operator=(const TransformOperations& other);\n\n  explicit TransformOperations(const ClayTransform& rk_transform,\n                               FloatSize percentage_resolution_size);\n\n  explicit TransformOperations(const std::vector<TransformRaw>& transform_raws,\n                               float width, float height);\n\n  // Returns a transformation matrix representing these transform operations.\n  Transform Apply() const;\n\n  // Returns a transformation matrix representing the set of transform\n  // operations from index |start| to the end of the list.\n  Transform ApplyRemaining(size_t start) const;\n\n  // Given another set of transform operations and a progress in the range\n  // [0, 1], returns a transformation matrix representing the intermediate\n  // value. If this->MatchesTypes(from), then each of the operations are\n  // blended separately and then combined. Otherwise, the two sets of\n  // transforms are baked to matrices (using apply), and the matrices are\n  // then decomposed and interpolated. For more information, see\n  // http://www.w3.org/TR/2011/WD-css3-2d-transforms-20111215/#matrix-decomposition.\n  //\n  // If either of the matrices are non-decomposable for the blend, Blend applies\n  // discrete interpolation between them based on the progress value.\n  TransformOperations Blend(const TransformOperations& from,\n                            float progress) const;\n\n  // Returns true if these operations are only translations.\n  bool IsTranslation() const;\n\n  // Returns false if the operations affect 2d axis alignment.\n  bool PreservesAxisAlignment() const;\n\n  // Returns true if this operation and its descendants have the same types\n  // as other and its descendants.\n  bool MatchesTypes(const TransformOperations& other) const;\n\n  // Returns the number of matching transform operations at the start of the\n  // transform lists. If one list is shorter but pairwise compatible, it will be\n  // extended with matching identity operators per spec\n  // (https://drafts.csswg.org/css-transforms/#interpolation-of-transforms).\n  size_t MatchingPrefixLength(const TransformOperations& other) const;\n\n  // Returns true if these operations can be blended. It will only return\n  // false if we must resort to matrix interpolation, and matrix interpolation\n  // fails (this can happen if either matrix cannot be decomposed).\n  bool CanBlendWith(const TransformOperations& other) const;\n\n  // If none of these operations have a perspective component, sets |scale| to\n  // be the product of the scale component of every operation. Otherwise,\n  // returns false.\n  bool ScaleComponent(float* scale) const;\n\n  void AppendTranslate(float x, float y, float z);\n  void AppendRotate(float x, float y, float z, float degrees);\n  void AppendScale(float x, float y, float z);\n  void AppendSkewX(float x);\n  void AppendSkewY(float y);\n  void AppendSkew(float x, float y);\n  void AppendPerspective(float depth);\n  void AppendMatrix(const double matrix[16]);\n  void AppendMatrix(const Transform& matrix);\n  void AppendIdentity();\n  void Append(const TransformOperation& operation);\n  void Append(const TransformOperations& ops);\n  bool IsIdentity() const;\n\n  size_t size() const { return operations_.size(); }\n\n  const TransformOperation& at(size_t index) const {\n    FML_DCHECK(index < size());\n    return operations_[index];\n  }\n  TransformOperation& at(size_t index) {\n    FML_DCHECK(index < size());\n    return operations_[index];\n  }\n\n  bool ApproximatelyEqual(const TransformOperations& other,\n                          float tolerance) const;\n\n  float GetTranslateZ() const {\n    float result = 0.f;\n    for (auto& operation : operations_) {\n      if (operation.type == TransformOperation::kTypeTranslate &&\n          std::fabs(operation.translate.z) > 1e-6) {\n        result += operation.translate.z;\n      }\n    }\n    return result;\n  }\n\n  void SetPerspective(float perspective) { perspective_ = perspective; }\n\n private:\n  void ApplyPerspective() const;\n  bool BlendInternal(const TransformOperations& from, float progress,\n                     TransformOperations* result) const;\n\n  mutable std::deque<TransformOperation> operations_;\n\n  bool ComputeDecomposedTransform(size_t start_offset) const;\n\n  // For efficiency, we cache the decomposed transforms.\n  mutable std::unordered_map<size_t, std::unique_ptr<DecomposedTransform>>\n      decomposed_transforms_;\n\n  float perspective_ = 0.f;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_TRANSFORM_OPERATIONS_H_\n"
  },
  {
    "path": "clay/gfx/geometry/transform_origin.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_TRANSFORM_ORIGIN_H_\n#define CLAY_GFX_GEOMETRY_TRANSFORM_ORIGIN_H_\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/style/length.h\"\n\nnamespace clay {\n\nclass TransformOrigin {\n public:\n  TransformOrigin()\n      : x_(0.5f, LengthUnit::kPercent), y_(0.5f, LengthUnit::kPercent) {}\n  TransformOrigin(float x, float y) : x_(x), y_(y) {}\n  FloatPoint GetValue(float width, float height) const {\n    return FloatPoint(x_.GetValue(width), y_.GetValue(height));\n  }\n  void Reset() {\n    x_ = Length(0.5f, LengthUnit::kPercent);\n    y_ = Length(0.5f, LengthUnit::kPercent);\n  }\n\n  void SetX(const Length& x) { x_ = x; }\n  void SetY(const Length& y) { y_ = y; }\n  void SetX(float v, LengthUnit u) { x_ = Length(v, u); }\n  void SetY(float v, LengthUnit u) { y_ = Length(v, u); }\n\n private:\n  Length x_;\n  Length y_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_TRANSFORM_ORIGIN_H_\n"
  },
  {
    "path": "clay/gfx/geometry/transform_raw.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_TRANSFORM_RAW_H_\n#define CLAY_GFX_GEOMETRY_TRANSFORM_RAW_H_\n\n#include \"clay/gfx/style/length.h\"\n\nnamespace clay {\n\nstruct TransformRaw {\n  int type;\n  Length values[3];\n  double matrix[16];\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_TRANSFORM_RAW_H_\n"
  },
  {
    "path": "clay/gfx/geometry/transform_util.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/transform_util.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <limits>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point3d.h\"\n\nnamespace clay {\n\nnamespace {\n\nfloat Length3(float v[3]) {\n  double vd[3] = {v[0], v[1], v[2]};\n  return std::sqrt(vd[0] * vd[0] + vd[1] * vd[1] + vd[2] * vd[2]);\n}\n\ntemplate <int n>\nfloat Dot(const float* a, const float* b) {\n  double total = 0.0;\n  for (int i = 0; i < n; ++i) {\n    total += a[i] * b[i];\n  }\n  return total;\n}\n\ntemplate <int n>\nvoid Combine(float* out, const float* a, const float* b, double scale_a,\n             double scale_b) {\n  for (int i = 0; i < n; ++i) {\n    out[i] = a[i] * scale_a + b[i] * scale_b;\n  }\n}\n\nvoid Cross3(float out[3], float a[3], float b[3]) {\n  float x = a[1] * b[2] - a[2] * b[1];\n  float y = a[2] * b[0] - a[0] * b[2];\n  float z = a[0] * b[1] - a[1] * b[0];\n  out[0] = x;\n  out[1] = y;\n  out[2] = z;\n}\n\n// Returns false if the matrix cannot be normalized.\nbool Normalize(skity::Matrix& m) {\n  if (std::abs(m.Get(3, 3)) < std::numeric_limits<float>::epsilon()) {\n    // Cannot normalize.\n    return false;\n  }\n\n  float scale = 1.f / m.Get(3, 3);\n  for (int i = 0; i < 4; i++) {\n    for (int j = 0; j < 4; j++) {\n      m.Set(i, j, m.Get(i, j) * scale);\n    }\n  }\n\n  return true;\n}\n\nskity::Matrix BuildPerspectiveMatrix(const DecomposedTransform& decomp) {\n  skity::Matrix matrix;\n\n  for (int i = 0; i < 4; i++) {\n    matrix.Set(3, i, decomp.perspective[i]);\n  }\n  return matrix;\n}\n\nskity::Matrix BuildTranslationMatrix(const DecomposedTransform& decomp) {\n  skity::Matrix matrix;\n  // Implicitly calls matrix.Reset()\n  matrix.SetTranslateX(decomp.translate[0]);\n  matrix.SetTranslateY(decomp.translate[1]);\n  matrix.Set(2, 3, decomp.translate[2]);\n  return matrix;\n}\n\nskity::Matrix BuildRotationMatrix(const DecomposedTransform& decomp) {\n  return Transform(decomp.quaternion).matrix();\n}\n\nskity::Matrix BuildSkewMatrix(const DecomposedTransform& decomp) {\n  skity::Matrix matrix;\n\n  skity::Matrix temp;\n  if (decomp.skew[2]) {\n    temp.Set(1, 2, decomp.skew[2]);\n    matrix.PreConcat(temp);\n  }\n\n  if (decomp.skew[1]) {\n    temp.Set(1, 2, 0);\n    temp.Set(0, 2, decomp.skew[1]);\n    matrix.PreConcat(temp);\n  }\n\n  if (decomp.skew[0]) {\n    temp.Set(0, 2, 0);\n    temp.Set(0, 1, decomp.skew[0]);\n    matrix.PreConcat(temp);\n  }\n  return matrix;\n}\n\nskity::Matrix BuildScaleMatrix(const DecomposedTransform& decomp) {\n  skity::Matrix matrix;\n  matrix.SetScaleX(decomp.scale[0]);\n  matrix.SetScaleY(decomp.scale[1]);\n  matrix.Set(2, 2, decomp.scale[2]);\n  return matrix;\n}\n\nTransform ComposeTransform(const skity::Matrix& perspective,\n                           const skity::Matrix& translation,\n                           const skity::Matrix& rotation,\n                           const skity::Matrix& skew,\n                           const skity::Matrix& scale) {\n  skity::Matrix matrix;\n\n  matrix.PreConcat(perspective);\n  matrix.PreConcat(translation);\n  matrix.PreConcat(rotation);\n  matrix.PreConcat(skew);\n  matrix.PreConcat(scale);\n\n  Transform to_return;\n  to_return.matrix() = matrix;\n  return to_return;\n}\n\nbool Is2dTransform(const Transform& transform) {\n  const skity::Matrix matrix = transform.matrix();\n  if (matrix.HasPersp()) {\n    return false;\n  }\n\n  return matrix.Get(2, 0) == 0 && matrix.Get(2, 1) == 0 &&\n         matrix.Get(0, 2) == 0 && matrix.Get(1, 2) == 0 &&\n         matrix.Get(2, 2) == 1 && matrix.Get(3, 2) == 0 &&\n         matrix.Get(2, 3) == 0;\n}\n\nbool Decompose2DTransform(DecomposedTransform* decomp,\n                          const Transform& transform) {\n  if (!Is2dTransform(transform)) {\n    return false;\n  }\n\n  const skity::Matrix matrix = transform.matrix();\n  double m11 = matrix.Get(0, 0);\n  double m21 = matrix.Get(0, 1);\n  double m12 = matrix.Get(1, 0);\n  double m22 = matrix.Get(1, 1);\n\n  double determinant = m11 * m22 - m12 * m21;\n  // Test for matrix being singular.\n  if (determinant == 0) {\n    return false;\n  }\n\n  // Translation transform.\n  // [m11 m21 0 m41]    [1 0 0 Tx] [m11 m21 0 0]\n  // [m12 m22 0 m42]  = [0 1 0 Ty] [m12 m22 0 0]\n  // [ 0   0  1  0 ]    [0 0 1 0 ] [ 0   0  1 0]\n  // [ 0   0  0  1 ]    [0 0 0 1 ] [ 0   0  0 1]\n  decomp->translate[0] = matrix.Get(0, 3);\n  decomp->translate[1] = matrix.Get(1, 3);\n\n  // For the remainder of the decomposition process, we can focus on the upper\n  // 2x2 sub-matrix\n  // [m11 m21] = [cos(R) -sin(R)] [1 K] [Sx 0 ]\n  // [m12 m22]   [sin(R)  cos(R)] [0 1] [0  Sy]\n  //           = [Sx*cos(R) Sy*(K*cos(R) - sin(R))]\n  //             [Sx*sin(R) Sy*(K*sin(R) + cos(R))]\n\n  // Determine sign of the x and y scale.\n  if (determinant < 0) {\n    // If the determinant is negative, we need to flip either the x or y scale.\n    // Flipping both is equivalent to rotating by 180 degrees.\n    if (m11 < m22) {\n      decomp->scale[0] *= -1;\n    } else {\n      decomp->scale[1] *= -1;\n    }\n  }\n\n  // X Scale.\n  // m11^2 + m12^2 = Sx^2*(cos^2(R) + sin^2(R)) = Sx^2.\n  // Sx = +/-sqrt(m11^2 + m22^2)\n  decomp->scale[0] *= sqrt(m11 * m11 + m12 * m12);\n  m11 /= decomp->scale[0];\n  m12 /= decomp->scale[0];\n\n  // Post normalization, the sub-matrix is now of the form:\n  // [m11 m21] = [cos(R)  Sy*(K*cos(R) - sin(R))]\n  // [m12 m22]   [sin(R)  Sy*(K*sin(R) + cos(R))]\n\n  // XY Shear.\n  // m11 * m21 + m12 * m22 = Sy*K*cos^2(R) - Sy*sin(R)*cos(R) +\n  //                         Sy*K*sin^2(R) + Sy*cos(R)*sin(R)\n  //                       = Sy*K\n  double scaledShear = m11 * m21 + m12 * m22;\n  m21 -= m11 * scaledShear;\n  m22 -= m12 * scaledShear;\n\n  // Post normalization, the sub-matrix is now of the form:\n  // [m11 m21] = [cos(R)  -Sy*sin(R)]\n  // [m12 m22]   [sin(R)   Sy*cos(R)]\n\n  // Y Scale.\n  // Similar process to determining x-scale.\n  decomp->scale[1] *= sqrt(m21 * m21 + m22 * m22);\n  m21 /= decomp->scale[1];\n  m22 /= decomp->scale[1];\n  decomp->skew[0] = scaledShear / decomp->scale[1];\n\n  // Rotation transform.\n  // [1-2(yy+zz)  2(xy-zw)    2(xz+yw) ]   [cos(R) -sin(R)  0]\n  // [2(xy+zw)   1-2(xx+zz)   2(yz-xw) ] = [sin(R)  cos(R)  0]\n  // [2(xz-yw)    2*(yz+xw)  1-2(xx+yy)]   [  0       0     1]\n  // Comparing terms, we can conclude that x = y = 0.\n  // [1-2zz   -2zw  0]   [cos(R) -sin(R)  0]\n  // [ 2zw   1-2zz  0] = [sin(R)  cos(R)  0]\n  // [  0     0     1]   [  0       0     1]\n  // cos(R) = 1 - 2*z^2\n  // From the double angle formula: cos(2a) = 1 - 2 sin(a)^2\n  // cos(R) = 1 - 2*sin(R/2)^2 = 1 - 2*z^2 ==> z = sin(R/2)\n  // sin(R) = 2*z*w\n  // But sin(2a) = 2 sin(a) cos(a)\n  // sin(R) = 2 sin(R/2) cos(R/2) = 2*z*w ==> w = cos(R/2)\n  double angle = atan2(m12, m11);\n  decomp->quaternion.SetX(0);\n  decomp->quaternion.SetY(0);\n  decomp->quaternion.SetZ(sin(0.5 * angle));\n  decomp->quaternion.set_w(cos(0.5 * angle));\n\n  return true;\n}\n\n}  // namespace\n\nTransform GetScaleTransform(const Point& anchor, float scale) {\n  Transform transform;\n  transform.Translate(anchor.x() * (1 - scale), anchor.y() * (1 - scale));\n  transform.Scale(scale, scale);\n  return transform;\n}\n\nDecomposedTransform::DecomposedTransform() {\n  translate[0] = translate[1] = translate[2] = 0.0;\n  scale[0] = scale[1] = scale[2] = 1.0;\n  skew[0] = skew[1] = skew[2] = 0.0;\n  perspective[0] = perspective[1] = perspective[2] = 0.0;\n  perspective[3] = 1.0;\n}\n\nDecomposedTransform BlendDecomposedTransforms(const DecomposedTransform& to,\n                                              const DecomposedTransform& from,\n                                              double progress) {\n  DecomposedTransform out;\n  double scalea = progress;\n  double scaleb = 1.0 - progress;\n  Combine<3>(out.translate, to.translate, from.translate, scalea, scaleb);\n  Combine<3>(out.scale, to.scale, from.scale, scalea, scaleb);\n  Combine<3>(out.skew, to.skew, from.skew, scalea, scaleb);\n  Combine<4>(out.perspective, to.perspective, from.perspective, scalea, scaleb);\n  out.quaternion = from.quaternion.Slerp(to.quaternion, progress);\n  return out;\n}\n\n// Taken from http://www.w3.org/TR/css3-transforms/.\n// TODO(crbug/937296): This implementation is virtually identical to the\n// implementation in blink::TransformationMatrix with the main difference being\n// the representation of the underlying matrix. These implementations should be\n// consolidated.\nbool DecomposeTransform(DecomposedTransform* decomp,\n                        const Transform& transform) {\n  if (!decomp) {\n    return false;\n  }\n\n  if (Decompose2DTransform(decomp, transform)) {\n    return true;\n  }\n\n  // We'll operate on a copy of the matrix.\n  skity::Matrix matrix = transform.matrix();\n\n  // If we cannot normalize the matrix, then bail early as we cannot decompose.\n  if (!Normalize(matrix)) {\n    return false;\n  }\n\n  skity::Matrix perspectiveMatrix = matrix;\n\n  for (int i = 0; i < 3; ++i) {\n    perspectiveMatrix.Set(3, i, 0.0);\n  }\n\n  perspectiveMatrix.Set(3, 3, 1.0);\n\n  // If the perspective matrix is not invertible, we are also unable to\n  // decompose, so we'll bail early. Constant taken from skity::Matrix::invert.\n  if (std::abs(perspectiveMatrix.Determinant()) < 1e-8) {\n    return false;\n  }\n\n  if (matrix.Get(3, 0) != 0.0 || matrix.Get(3, 1) != 0.0 ||\n      matrix.Get(3, 2) != 0.0) {\n    // rhs is the right hand side of the equation.\n    skity::Vec4 rhs = {matrix.Get(3, 0), matrix.Get(3, 1), matrix.Get(3, 2),\n                       matrix.Get(3, 3)};\n\n    // Solve the equation by inverting perspectiveMatrix and multiplying\n    // rhs by the inverse.\n    skity::Matrix inversePerspectiveMatrix;\n    if (!perspectiveMatrix.Invert(&inversePerspectiveMatrix)) {\n      return false;\n    }\n\n    skity::Matrix transposedInversePerspectiveMatrix = inversePerspectiveMatrix;\n\n    transposedInversePerspectiveMatrix.Transpose();\n    rhs = transposedInversePerspectiveMatrix * rhs;\n\n    for (int i = 0; i < 4; ++i) {\n      decomp->perspective[i] = rhs[i];\n    }\n\n  } else {\n    // No perspective.\n    for (int i = 0; i < 3; ++i) {\n      decomp->perspective[i] = 0.0;\n    }\n    decomp->perspective[3] = 1.0;\n  }\n\n  for (int i = 0; i < 3; i++) {\n    decomp->translate[i] = matrix.Get(i, 3);\n  }\n\n  // Copy of matrix is stored in column major order to facilitate column-level\n  // operations.\n  float column[3][3];\n  for (int i = 0; i < 3; i++) {\n    for (int j = 0; j < 3; ++j) {\n      column[i][j] = matrix.Get(j, i);\n    }\n  }\n\n  // Compute X scale factor and normalize first column.\n  decomp->scale[0] = Length3(column[0]);\n  if (decomp->scale[0] != 0.0) {\n    column[0][0] /= decomp->scale[0];\n    column[0][1] /= decomp->scale[0];\n    column[0][2] /= decomp->scale[0];\n  }\n\n  // Compute XY shear factor and make 2nd column orthogonal to 1st.\n  decomp->skew[0] = Dot<3>(column[0], column[1]);\n  Combine<3>(column[1], column[1], column[0], 1.0, -decomp->skew[0]);\n\n  // Now, compute Y scale and normalize 2nd column.\n  decomp->scale[1] = Length3(column[1]);\n  if (decomp->scale[1] != 0.0) {\n    column[1][0] /= decomp->scale[1];\n    column[1][1] /= decomp->scale[1];\n    column[1][2] /= decomp->scale[1];\n  }\n\n  decomp->skew[0] /= decomp->scale[1];\n\n  // Compute XZ and YZ shears, orthogonalize the 3rd column.\n  decomp->skew[1] = Dot<3>(column[0], column[2]);\n  Combine<3>(column[2], column[2], column[0], 1.0, -decomp->skew[1]);\n  decomp->skew[2] = Dot<3>(column[1], column[2]);\n  Combine<3>(column[2], column[2], column[1], 1.0, -decomp->skew[2]);\n\n  // Next, get Z scale and normalize the 3rd column.\n  decomp->scale[2] = Length3(column[2]);\n  if (decomp->scale[2] != 0.0) {\n    column[2][0] /= decomp->scale[2];\n    column[2][1] /= decomp->scale[2];\n    column[2][2] /= decomp->scale[2];\n  }\n\n  decomp->skew[1] /= decomp->scale[2];\n  decomp->skew[2] /= decomp->scale[2];\n\n  // At this point, the matrix is orthonormal.\n  // Check for a coordinate system flip.  If the determinant\n  // is -1, then negate the matrix and the scaling factors.\n  // TODO(kevers): This is inconsistent from the 2D specification, in which\n  // only 1 axis is flipped when the determinant is negative. Verify if it is\n  // correct to flip all of the scales and matrix elements, as this introduces\n  // rotation for the simple case of a single axis scale inversion.\n  float pdum3[3];\n  Cross3(pdum3, column[1], column[2]);\n  if (Dot<3>(column[0], pdum3) < 0) {\n    for (int i = 0; i < 3; i++) {\n      decomp->scale[i] *= -1.0;\n      for (int j = 0; j < 3; ++j) {\n        column[i][j] *= -1.0;\n      }\n    }\n  }\n\n  // See https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion.\n  // Note: deviating from spec (http://www.w3.org/TR/css3-transforms/)\n  // which has a degenerate case of zero off-diagonal elements in the\n  // orthonormal matrix, which leads to errors in determining the sign\n  // of the quaternions.\n  double q_xx = column[0][0];\n  double q_xy = column[1][0];\n  double q_xz = column[2][0];\n  double q_yx = column[0][1];\n  double q_yy = column[1][1];\n  double q_yz = column[2][1];\n  double q_zx = column[0][2];\n  double q_zy = column[1][2];\n  double q_zz = column[2][2];\n\n  double r, s, t, x, y, z, w;\n  t = q_xx + q_yy + q_zz;\n  if (t > 0) {\n    r = std::sqrt(1.0 + t);\n    s = 0.5 / r;\n    w = 0.5 * r;\n    x = (q_zy - q_yz) * s;\n    y = (q_xz - q_zx) * s;\n    z = (q_yx - q_xy) * s;\n  } else if (q_xx > q_yy && q_xx > q_zz) {\n    r = std::sqrt(1.0 + q_xx - q_yy - q_zz);\n    s = 0.5 / r;\n    x = 0.5 * r;\n    y = (q_xy + q_yx) * s;\n    z = (q_xz + q_zx) * s;\n    w = (q_zy - q_yz) * s;\n  } else if (q_yy > q_zz) {\n    r = std::sqrt(1.0 - q_xx + q_yy - q_zz);\n    s = 0.5 / r;\n    x = (q_xy + q_yx) * s;\n    y = 0.5 * r;\n    z = (q_yz + q_zy) * s;\n    w = (q_xz - q_zx) * s;\n  } else {\n    r = std::sqrt(1.0 - q_xx - q_yy + q_zz);\n    s = 0.5 / r;\n    x = (q_xz + q_zx) * s;\n    y = (q_yz + q_zy) * s;\n    z = 0.5 * r;\n    w = (q_yx - q_xy) * s;\n  }\n\n  decomp->quaternion.SetX(x);\n  decomp->quaternion.SetY(y);\n  decomp->quaternion.SetZ(z);\n  decomp->quaternion.set_w(w);\n\n  return true;\n}\n\n// Taken from http://www.w3.org/TR/css3-transforms/.\nTransform ComposeTransform(const DecomposedTransform& decomp) {\n  skity::Matrix perspective = BuildPerspectiveMatrix(decomp);\n  skity::Matrix translation = BuildTranslationMatrix(decomp);\n  skity::Matrix rotation = BuildRotationMatrix(decomp);\n  skity::Matrix skew = BuildSkewMatrix(decomp);\n  skity::Matrix scale = BuildScaleMatrix(decomp);\n\n  return ComposeTransform(perspective, translation, rotation, skew, scale);\n}\n\nTransform TransformAboutPivot(const Point& pivot, const Transform& transform) {\n  Transform result;\n  result.Translate(pivot.x(), pivot.y());\n  result.PreconcatTransform(transform);\n  result.Translate(-pivot.x(), -pivot.y());\n  return result;\n}\n\nstd::string DecomposedTransform::ToString() const {\n  auto str_printf = [=](char* str_buf, size_t size) {\n    return std::snprintf(str_buf, size,\n                         \"translate: %+0.4f %+0.4f %+0.4f\\n\"\n                         \"scale: %+0.4f %+0.4f %+0.4f\\n\"\n                         \"skew: %+0.4f %+0.4f %+0.4f\\n\"\n                         \"perspective: %+0.4f %+0.4f %+0.4f %+0.4f\\n\"\n                         \"quaternion: %+0.4f %+0.4f %+0.4f %+0.4f\\n\",\n                         translate[0], translate[1], translate[2], scale[0],\n                         scale[1], scale[2], skew[0], skew[1], skew[2],\n                         perspective[0], perspective[1], perspective[2],\n                         perspective[3], quaternion.x(), quaternion.y(),\n                         quaternion.z(), quaternion.w());\n  };\n  int sz = str_printf(nullptr, 0);\n  std::vector<char> buf(sz + 1);\n  str_printf(&buf[0], buf.size());\n  return std::string(buf.data());\n}\n\nTransform OrthoProjectionMatrix(float left, float right, float bottom,\n                                float top) {\n  // Use the standard formula to map the clipping frustum to the cube from\n  // [-1, -1, -1] to [1, 1, 1].\n  float delta_x = right - left;\n  float delta_y = top - bottom;\n  Transform proj;\n  if (!delta_x || !delta_y) {\n    return proj;\n  }\n  proj.matrix().Set(0, 0, 2.0f / delta_x);\n  proj.matrix().Set(0, 3, -(right + left) / delta_x);\n  proj.matrix().Set(1, 1, 2.0f / delta_y);\n  proj.matrix().Set(1, 3, -(top + bottom) / delta_y);\n\n  // Z component of vertices is always set to zero as we don't use the depth\n  // buffer while drawing.\n  proj.matrix().Set(2, 2, 0);\n\n  return proj;\n}\n\nTransform WindowMatrix(int x, int y, int width, int height) {\n  Transform canvas;\n\n  // Map to window position and scale up to pixel coordinates.\n  canvas.Translate3d(x, y, 0);\n  canvas.Scale3d(width, height, 0);\n\n  // Map from ([-1, -1] to [1, 1]) -> ([0, 0] to [1, 1])\n  canvas.Translate3d(0.5, 0.5, 0.5);\n  canvas.Scale3d(0.5, 0.5, 0.5);\n\n  return canvas;\n}\n\nstatic inline bool NearlyZeroDouble(double value) {\n  return std::abs(value) < std::numeric_limits<double>::epsilon();\n}\n\nstatic inline float ScaleOnAxis(double a, double b, double c) {\n  if (NearlyZeroDouble(b) && NearlyZeroDouble(c)) {\n    return std::abs(a);\n  }\n  if (NearlyZeroDouble(a) && NearlyZeroDouble(c)) {\n    return std::abs(b);\n  }\n  if (NearlyZeroDouble(a) && NearlyZeroDouble(b)) {\n    return std::abs(c);\n  }\n\n  // Do the sqrt as a double to not lose precision.\n  return static_cast<float>(std::sqrt(a * a + b * b + c * c));\n}\n\nFloatVector2d ComputeTransform2dScaleComponents(const Transform& transform,\n                                                float fallback_value) {\n  if (transform.HasPerspective()) {\n    return FloatVector2d(fallback_value, fallback_value);\n  }\n  float x_scale =\n      ScaleOnAxis(transform.matrix().Get(0, 0), transform.matrix().Get(1, 0),\n                  transform.matrix().Get(2, 0));\n  float y_scale =\n      ScaleOnAxis(transform.matrix().Get(0, 1), transform.matrix().Get(1, 1),\n                  transform.matrix().Get(2, 1));\n  return FloatVector2d(x_scale, y_scale);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/geometry/transform_util.h",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GEOMETRY_TRANSFORM_UTIL_H_\n#define CLAY_GFX_GEOMETRY_TRANSFORM_UTIL_H_\n\n#include <string>\n\n#include \"clay/gfx/geometry/point.h\"\n#include \"clay/gfx/geometry/quaternion.h\"\n#include \"clay/gfx/geometry/transform.h\"\n\nnamespace clay {\n\n// Returns a scale transform at |anchor| point.\nTransform GetScaleTransform(const Point& anchor, float scale);\n\n// Contains the components of a factored transform. These components may be\n// blended and recomposed.\nstruct DecomposedTransform {\n  // The default constructor initializes the components in such a way that\n  // if used with Compose below, will produce the identity transform.\n  DecomposedTransform();\n\n  float translate[3];\n  float scale[3];\n  float skew[3];\n  float perspective[4];\n  Quaternion quaternion;\n\n  std::string ToString() const;\n\n  // Copy and assign are allowed.\n};\n\n// Interpolates the decomposed components |to| with |from| using the\n// routines described in http://www.w3.org/TR/css3-3d-transform/.\n// |progress| is in the range [0, 1]. If 0 we will return |from|, if 1, we will\n// return |to|.\nDecomposedTransform BlendDecomposedTransforms(const DecomposedTransform& to,\n                                              const DecomposedTransform& from,\n                                              double progress);\n\n// Decomposes this transform into its translation, scale, skew, perspective,\n// and rotation components following the routines detailed in this spec:\n// http://www.w3.org/TR/css3-3d-transforms/.\nbool DecomposeTransform(DecomposedTransform* out, const Transform& transform);\n\n// Composes a transform from the given translation, scale, skew, perspective,\n// and rotation components following the routines detailed in this spec:\n// http://www.w3.org/TR/css3-3d-transforms/.\nTransform ComposeTransform(const DecomposedTransform& decomp);\n\n// Calculates a transform with a transformed origin. The resulting transform is\n// created by composing P * T * P^-1 where P is a constant transform to the new\n// origin.\nTransform TransformAboutPivot(const Point& pivot, const Transform& transform);\n\n// Generates projection matrix and returns it as a Transform.\nTransform OrthoProjectionMatrix(float left, float right, float bottom,\n                                float top);\n\n// Generates window matrix and returns it as a Transform.\nTransform WindowMatrix(int x, int y, int width, int height);\n\nFloatVector2d ComputeTransform2dScaleComponents(const Transform& transform,\n                                                float fallback_value);\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GEOMETRY_TRANSFORM_UTIL_H_\n"
  },
  {
    "path": "clay/gfx/gfx_rendering_backend.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GFX_RENDERING_BACKEND_H_\n#define CLAY_GFX_GFX_RENDERING_BACKEND_H_\n\n#ifndef ENABLE_SKITY\n#ifdef ENABLE_SVG\n#include \"clay/gfx/image/svg_image_holder.h\"\n#endif  // ENABLE_SVG\n#include \"clay/gfx/image/graphics_image_skia.h\"\n#include \"clay/gfx/image/graphics_image_skia_lazy.h\"\n#include \"clay/gfx/paint_image_skia.h\"\n#include \"clay/gfx/paint_image_skia_lazy.h\"\n#include \"clay/gfx/shared_image/skia_gl_image_representation.h\"\n#include \"clay/gfx/skia/picture_skia.h\"\n#include \"clay/gfx/text_blob_skia.h\"\n#else\n#ifdef ENABLE_SVG\n#include \"clay/gfx/svg/svg_dom.h\"\n#endif  // ENABLE_SVG\n#include \"clay/gfx/image/graphics_image_skity.h\"\n#include \"clay/gfx/image/graphics_image_skity_lazy.h\"\n#include \"clay/gfx/image/image_info.h\"\n#include \"clay/gfx/paint_image_skity.h\"\n#ifdef OS_ANDROID\n#include \"clay/gfx/shared_image/skity_gl_image_representation.h\"\n#endif  // OS_ANDROID\n#include \"clay/gfx/skity/picture_skity.h\"\n#include \"clay/gfx/text_blob_skity.h\"\n#endif  // ENABLE_SKITY\n\n#endif  // CLAY_GFX_GFX_RENDERING_BACKEND_H_\n"
  },
  {
    "path": "clay/gfx/gfx_utils.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/gfx_utils.h\"\n\n#include <limits>\n\nnamespace clay {\nvoid RectBoundsAccumulator::accumulate(const skity::Rect& r, int index) {\n  if (r.Left() < r.Right() && r.Top() < r.Bottom()) {\n    rect_.accumulate(r.Left(), r.Top());\n    rect_.accumulate(r.Right(), r.Bottom());\n  }\n}\n\nvoid RectBoundsAccumulator::save() {\n  saved_rects_.emplace_back(rect_);\n  rect_ = AccumulationRect();\n}\nvoid RectBoundsAccumulator::restore() {\n  if (!saved_rects_.empty()) {\n    skity::Rect layer_bounds = rect_.bounds();\n    pop_and_accumulate(layer_bounds, nullptr);\n  }\n}\nbool RectBoundsAccumulator::restore(\n    std::function<bool(const skity::Rect&, skity::Rect&)> mapper,\n    const skity::Rect* clip) {\n  bool success = true;\n  if (!saved_rects_.empty()) {\n    skity::Rect layer_bounds = rect_.bounds();\n    success = mapper(layer_bounds, layer_bounds);\n    pop_and_accumulate(layer_bounds, clip);\n  }\n  return success;\n}\nvoid RectBoundsAccumulator::pop_and_accumulate(skity::Rect& layer_bounds,\n                                               const skity::Rect* clip) {\n  FML_DCHECK(!saved_rects_.empty());\n\n  rect_ = saved_rects_.back();\n  saved_rects_.pop_back();\n\n  if (clip == nullptr || layer_bounds.Intersect(*clip)) {\n    accumulate(layer_bounds, -1);\n  }\n}\n\nRectBoundsAccumulator::AccumulationRect::AccumulationRect() {\n  min_x_ = std::numeric_limits<float>::infinity();\n  min_y_ = std::numeric_limits<float>::infinity();\n  max_x_ = -std::numeric_limits<float>::infinity();\n  max_y_ = -std::numeric_limits<float>::infinity();\n}\nvoid RectBoundsAccumulator::AccumulationRect::accumulate(float x, float y) {\n  if (min_x_ > x) {\n    min_x_ = x;\n  }\n  if (min_y_ > y) {\n    min_y_ = y;\n  }\n  if (max_x_ < x) {\n    max_x_ = x;\n  }\n  if (max_y_ < y) {\n    max_y_ = y;\n  }\n}\nskity::Rect RectBoundsAccumulator::AccumulationRect::bounds() const {\n  return (max_x_ >= min_x_ && max_y_ >= min_y_)\n             ? skity::Rect::MakeLTRB(min_x_, min_y_, max_x_, max_y_)\n             : skity::Rect::MakeEmpty();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/gfx_utils.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GFX_UTILS_H_\n#define CLAY_GFX_GFX_UTILS_H_\n\n#include <functional>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"skity/geometry/point.hpp\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nenum class BoundsAccumulatorType {\n  kRect,\n};\n\nclass BoundsAccumulator {\n public:\n  /// function definition for modifying the bounds of a rectangle\n  /// during a restore operation. The function is used primarily\n  /// to account for the bounds impact of an ImageFilter on a\n  /// saveLayer on a per-rect basis. The implementation may apply\n  /// this function at whatever granularity it can manage easily\n  /// (for example, a Rect accumulator might apply it to the entire\n  /// local bounds being restored, whereas an RTree accumulator might\n  /// apply it individually to each element in the local RTree).\n  ///\n  /// The function will do a best faith attempt at determining the\n  /// modified bounds and store the results in the supplied |dest|\n  /// rectangle and return true. If the function is unable to\n  /// accurately determine the modifed bounds, it will set the\n  /// |dest| rectangle to a copy of the input bounds (or a best\n  /// guess) and return false to indicate that the bounds should not\n  /// be trusted.\n  typedef bool BoundsModifier(const skity::Rect& original, skity::Rect* dest);\n\n  virtual ~BoundsAccumulator() = default;\n\n  virtual void accumulate(const skity::Rect& r, int index = 0) = 0;\n\n  /// Save aside the rects/bounds currently being accumulated and start\n  /// accumulating a new set of rects/bounds. When restore is called,\n  /// some additional modifications may be applied to these new bounds\n  /// before they are accumulated back into the surrounding bounds.\n  virtual void save() = 0;\n\n  /// Restore to the previous accumulation and incorporate the bounds of\n  /// the primitives that were recorded since the last save (if needed).\n  virtual void restore() = 0;\n\n  /// Restore the previous set of accumulation rects/bounds and accumulate\n  /// the current rects/bounds that were accumulated since the most recent\n  /// call to |save| into them with modifications specified by the |map|\n  /// parameter and clipping to the clip parameter if it is not null.\n  ///\n  /// The indicated map function is applied to the various rects and bounds\n  /// that have been accumulated in this save/restore cycle before they\n  /// are then accumulated into the previous accumulations. The granularity\n  /// of the application of the map function to the rectangles that were\n  /// accumulated during the save period is left up to the implementation.\n  ///\n  /// This method will return true if the map function returned true on\n  /// every single invocation. A false return value means that the\n  /// bounds accumulated during this restore may not be trusted (as\n  /// determined by the map function).\n  ///\n  /// If there are no saved accumulations to restore to, this method will\n  /// NOP ignoring the map function and the optional clip entirely.\n  virtual bool restore(\n      std::function<bool(const skity::Rect& original, skity::Rect& modified)>\n          map,\n      const skity::Rect* clip = nullptr) = 0;\n\n  virtual skity::Rect bounds() const = 0;\n\n  virtual BoundsAccumulatorType type() const = 0;\n};\n\nclass RectBoundsAccumulator final : public virtual BoundsAccumulator {\n public:\n  void accumulate(float x, float y) { rect_.accumulate(x, y); }\n  void accumulate(const skity::Point& p) { rect_.accumulate(p.x, p.y); }\n  void accumulate(const skity::Rect& r, int index) override;\n\n  bool is_empty() const { return rect_.is_empty(); }\n  bool is_not_empty() const { return rect_.is_not_empty(); }\n\n  void save() override;\n  void restore() override;\n  bool restore(std::function<bool(const skity::Rect&, skity::Rect&)> mapper,\n               const skity::Rect* clip) override;\n\n  skity::Rect bounds() const override {\n    FML_DCHECK(saved_rects_.empty());\n    return rect_.bounds();\n  }\n\n  BoundsAccumulatorType type() const override {\n    return BoundsAccumulatorType::kRect;\n  }\n\n private:\n  class AccumulationRect {\n   public:\n    AccumulationRect();\n\n    void accumulate(float x, float y);\n\n    bool is_empty() const { return min_x_ >= max_x_ || min_y_ >= max_y_; }\n    bool is_not_empty() const { return min_x_ < max_x_ && min_y_ < max_y_; }\n\n    skity::Rect bounds() const;\n\n   private:\n    float min_x_;\n    float min_y_;\n    float max_x_;\n    float max_y_;\n  };\n\n  void pop_and_accumulate(skity::Rect& layer_bounds, const skity::Rect* clip);\n\n  AccumulationRect rect_;\n  std::vector<AccumulationRect> saved_rects_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GFX_UTILS_H_\n"
  },
  {
    "path": "clay/gfx/gpu_object.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GPU_OBJECT_H_\n#define CLAY_GFX_GPU_OBJECT_H_\n\n#include <deque>\n#include <memory>\n#include <mutex>\n#include <queue>\n#include <utility>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/gpu_ref_object.h\"\n#include \"clay/gfx/rendering_backend.h\"\nnamespace clay {\n\n// A queue that holds gpu objects that must be destructed on the given task\n// runner.\ntemplate <class T>\nclass UnrefQueue : public fml::RefCountedThreadSafe<UnrefQueue<T>> {\n public:\n  using Context = T;\n#ifndef ENABLE_SKITY\n  using ContextPtr = sk_sp<Context>;\n#else\n  using ContextPtr = std::shared_ptr<Context>;\n#endif  // ENABLE_SKITY\n\n  bool IsShared() const { return is_shared_; }\n\n  void Unref(GPURefObject* object) {\n    std::scoped_lock lock(mutex_);\n    objects_.push_back(object);\n    if (start_auto_pending_drain_) {\n      TriggerPendingDrainIfNeeded();\n    }\n  }\n\n  void StartAutoPendingDrain() {\n    std::scoped_lock lock(mutex_);\n    if (start_auto_pending_drain_) {\n      return;\n    }\n    start_auto_pending_drain_ = true;\n#ifndef ENABLE_SKITY\n    if (objects_.empty() && textures_.empty()) {\n      return;\n    }\n\n#else\n    if (objects_.empty()) {\n      return;\n    }\n#endif  // ENABLE_SKITY\n    TriggerPendingDrainIfNeeded();\n  }\n\n  fml::RefPtr<fml::TaskRunner> GetTaskRunner() { return task_runner_; }\n\n#ifndef ENABLE_SKITY\n  void DeleteTexture(GrBackendTexture texture) {\n    std::scoped_lock lock(mutex_);\n    textures_.push_back(texture);\n    if (start_auto_pending_drain_) {\n      TriggerPendingDrainIfNeeded();\n    }\n  }\n#endif  // ENABLE_SKITY\n\n  // Usually, the drain is called automatically. However, during IO manager\n  // shutdown (when the platform side reference to the OpenGL context is about\n  // to go away), we may need to pre-emptively drain the unref queue. It is the\n  // responsibility of the caller to ensure that no further unref are queued\n  // after this call.\n  void Drain() {\n#if defined(TRACE_EVENT)\n    TRACE_EVENT(\"clay\", \"GPUUnrefQueue::Drain\");\n#endif  // TRACE_EVENT\n    std::deque<GPURefObject*> objects;\n#ifndef ENABLE_SKITY\n    std::deque<GrBackendTexture> textures;\n    {\n      std::scoped_lock lock(mutex_);\n      objects_.swap(objects);\n      textures_.swap(textures);\n      drain_pending_ = false;\n    }\n    DoDrain(objects, textures, context_);\n#else\n    {\n      std::scoped_lock lock(mutex_);\n      objects_.swap(objects);\n      drain_pending_ = false;\n    }\n    DoDrain(objects, context_);\n#endif  // ENABLE_SKITY\n  }\n\n  void SetContext(ContextPtr context) {\n    std::scoped_lock lock(mutex_);\n    context_ = context;\n  }\n\n  ContextPtr GetContext() {\n    std::scoped_lock lock(mutex_);\n    return context_;\n  }\n\n private:\n  const fml::RefPtr<fml::TaskRunner> task_runner_;\n  bool is_shared_;\n  const fml::TimeDelta drain_delay_;\n  bool start_auto_pending_drain_ = false;\n  bool drain_pending_ = false;\n  std::recursive_mutex mutex_;\n  std::deque<GPURefObject*> objects_;\n  ContextPtr context_;\n#ifndef ENABLE_SKITY\n  std::deque<GrBackendTexture> textures_;\n#endif  // ENABLE_SKITY\n  explicit UnrefQueue(\n      fml::RefPtr<fml::TaskRunner> task_runner, bool is_shared = true,\n      fml::TimeDelta delay = fml::TimeDelta::FromMilliseconds(8))\n      : task_runner_(task_runner), is_shared_(is_shared), drain_delay_(delay) {\n    FML_DCHECK(task_runner_);\n  }\n\n  ~UnrefQueue() {\n    // The Context must be deleted on the task runner thread.\n    // Transfer ownership of the UnrefQueue's Context reference\n    // into a task queued to that thread.\n#ifndef ENABLE_SKITY\n    Context* raw_context = context_.release();\n    fml::TaskRunner::RunNowOrPostTask(\n        task_runner_,\n        [raw_context, objects = std::move(objects_),\n         textures = std::move(textures_), task_runner = task_runner_]() {\n          sk_sp<Context> context(raw_context);\n          DoDrain(objects, textures, context);\n          context.reset();\n        });\n#else\n    fml::TaskRunner::RunNowOrPostTask(\n        task_runner_,\n        [context = context_, objects = std::move(objects_),\n         task_runner = task_runner_]() { DoDrain(objects, context); });\n#endif  // ENABLE_SKITY\n  }\n\n  void TriggerPendingDrainIfNeeded() {\n    if (is_shared_) {\n      if (!drain_pending_) {\n        drain_pending_ = true;\n        task_runner_->PostDelayedTask(\n            [strong = fml::Ref(this)]() { strong->Drain(); }, drain_delay_);\n      }\n    } else {\n      // On Windows or Mac, the GPUUnRefQueue is not shared, the raster task\n      // runner will be terminated after the destruction of the Rasterizer\n      // object.\n      fml::TaskRunner::RunNowOrPostTask(\n          task_runner_, [strong = fml::Ref(this)]() { strong->Drain(); });\n    }\n  }\n\n#ifndef ENABLE_SKITY\n  static void DoDrain(const std::deque<GPURefObject*>& gpu_objects,\n                      const std::deque<GrBackendTexture>& textures,\n                      ContextPtr context) {\n    for (GPURefObject* gpu_object : gpu_objects) {\n      gpu_object->Release();\n    }\n    if (context) {\n      for (GrBackendTexture texture : textures) {\n        context->deleteBackendTexture(texture);\n      }\n\n      if (!gpu_objects.empty()) {\n        context->performDeferredCleanup(std::chrono::milliseconds(0));\n      }\n    }\n  }\n#else\n  static void DoDrain(const std::deque<GPURefObject*>& gpu_objects,\n                      ContextPtr context) {\n    for (GPURefObject* gpu_object : gpu_objects) {\n      gpu_object->Release();\n    }\n  }\n#endif  // ENABLE_SKITY\n\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(UnrefQueue);\n  FML_FRIEND_MAKE_REF_COUNTED(UnrefQueue);\n  BASE_DISALLOW_COPY_AND_ASSIGN(UnrefQueue);\n};\n\nusing GPUUnrefQueue = UnrefQueue<GrContext>;\n\n// An object whose deallocation needs to be performed on an specific unref\n// queue. The template argument U need to have a call operator that returns\n// that unref queue.\ntemplate <class T>\nclass GPUObject {\n public:\n  using ObjectType = T;\n\n  GPUObject() = default;\n  GPUObject(fml::RefPtr<ObjectType> object, fml::RefPtr<GPUUnrefQueue> queue)\n      : object_(std::move(object)), queue_(std::move(queue)) {\n    FML_DCHECK(object_);\n  }\n  GPUObject(GPUObject&&) = default;\n  ~GPUObject() { reset(); }\n\n  GPUObject& operator=(GPUObject&&) = default;\n\n  fml::RefPtr<ObjectType> object() const { return object_; }\n\n  void reset() {\n    if (object_ && queue_) {\n      queue_->Unref(object_.AbandonRef());\n    }\n    queue_ = nullptr;\n    FML_DCHECK(object_ == nullptr);\n  }\n\n private:\n  fml::RefPtr<ObjectType> object_;\n  fml::RefPtr<GPUUnrefQueue> queue_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(GPUObject);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GPU_OBJECT_H_\n"
  },
  {
    "path": "clay/gfx/gpu_object_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <future>\n#include <utility>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/testing/thread_test.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkRefCnt.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing clay::GPUUnrefQueue;\n\nclass TestGPUObject : public clay::GPURefObject {\n public:\n  TestGPUObject(std::shared_ptr<fml::AutoResetWaitableEvent> latch,\n                fml::TaskQueueId* dtor_task_queue_id)\n      : latch_(std::move(latch)), dtor_task_queue_id_(dtor_task_queue_id) {}\n\n  virtual ~TestGPUObject() {\n    if (dtor_task_queue_id_) {\n      *dtor_task_queue_id_ = fml::MessageLoop::GetCurrentTaskQueueId();\n    }\n    latch_->Signal();\n  }\n\n private:\n  std::shared_ptr<fml::AutoResetWaitableEvent> latch_;\n  fml::TaskQueueId* dtor_task_queue_id_;\n};\n\nclass TestContext : public SkRefCnt {\n public:\n  TestContext(std::shared_ptr<fml::AutoResetWaitableEvent> latch,\n              fml::TaskQueueId* dtor_task_queue_id)\n      : latch_(std::move(latch)), dtor_task_queue_id_(dtor_task_queue_id) {}\n  virtual ~TestContext() {\n    if (dtor_task_queue_id_) {\n      *dtor_task_queue_id_ = fml::MessageLoop::GetCurrentTaskQueueId();\n    }\n    latch_->Signal();\n  }\n#ifndef ENABLE_SKITY\n  void performDeferredCleanup(std::chrono::milliseconds msNotUsed) {}\n  void deleteBackendTexture(const GrBackendTexture& texture) {}\n#endif  // ENABLE_SKITY\n\n private:\n  std::shared_ptr<fml::AutoResetWaitableEvent> latch_;\n  fml::TaskQueueId* dtor_task_queue_id_;\n};\n\nclass GpuObjectTest : public ThreadTest {\n public:\n  GpuObjectTest()\n      : unref_task_runner_(CreateNewThread()),\n#if OS_WIN || OS_MAC\n        unref_queue_(fml::MakeRefCounted<GPUUnrefQueue>(\n            unref_task_runner(), false, fml::TimeDelta::FromMilliseconds(0))) {\n#else\n        unref_queue_(fml::MakeRefCounted<GPUUnrefQueue>(\n            unref_task_runner(), true, fml::TimeDelta::FromMilliseconds(0))) {\n#endif\n    // The unref queues must be created in the same thread of the\n    // unref_task_runner so the queue can access the same-thread-only WeakPtr\n    // of the GrContext constructed during the creation.\n    std::promise<bool> queues_created;\n    unref_task_runner_->PostTask([this, &queues_created]() {\n      unref_queue_ = fml::MakeRefCounted<GPUUnrefQueue>(unref_task_runner());\n      queues_created.set_value(true);\n    });\n    queues_created.get_future().wait();\n    ::testing::FLAGS_gtest_death_test_style = \"threadsafe\";\n  }\n\n  fml::RefPtr<fml::TaskRunner> unref_task_runner() {\n    return unref_task_runner_;\n  }\n  fml::RefPtr<GPUUnrefQueue> unref_queue() { return unref_queue_; }\n\n private:\n  fml::RefPtr<fml::TaskRunner> unref_task_runner_;\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n};\n\nTEST_F(GpuObjectTest, QueueSimple) {\n  std::shared_ptr<fml::AutoResetWaitableEvent> latch =\n      std::make_shared<fml::AutoResetWaitableEvent>();\n  fml::TaskQueueId dtor_task_queue_id(0);\n  clay::GPURefObject* ref_object =\n      new TestGPUObject(latch, &dtor_task_queue_id);\n  unref_queue()->Unref(ref_object);\n\n  // Drain the queue on the unref task runner thread.\n  unref_task_runner()->PostTask(\n      [unref_queue = unref_queue(), ref_object]() { unref_queue->Drain(); });\n  latch->Wait();\n  ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId());\n}\n\nTEST_F(GpuObjectTest, ObjectDestructor) {\n  std::shared_ptr<fml::AutoResetWaitableEvent> latch =\n      std::make_shared<fml::AutoResetWaitableEvent>();\n  fml::TaskQueueId dtor_task_queue_id(0);\n  auto object = fml::MakeRefCounted<TestGPUObject>(latch, &dtor_task_queue_id);\n  {\n    clay::GPUObject<TestGPUObject> gpu_object(std::move(object), unref_queue());\n  }\n\n  // Drain the queue on the unref task runner thread.\n  unref_task_runner()->PostTask(\n      [unref_queue = unref_queue()]() { unref_queue->Drain(); });\n\n  latch->Wait();\n  ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId());\n}\n\nTEST_F(GpuObjectTest, ObjectReset) {\n  std::shared_ptr<fml::AutoResetWaitableEvent> latch =\n      std::make_shared<fml::AutoResetWaitableEvent>();\n  fml::TaskQueueId dtor_task_queue_id(0);\n  clay::GPUObject<TestGPUObject> gpu_object(\n      fml::MakeRefCounted<TestGPUObject>(latch, &dtor_task_queue_id),\n      unref_queue());\n  // Verify that explicitly resetting the GPU object queues and unref.\n  gpu_object.reset();\n  ASSERT_EQ(gpu_object.object(), nullptr);\n\n  // Drain the queue on the unref task runner thread.\n  unref_task_runner()->PostTask(\n      [unref_queue = unref_queue()]() { unref_queue->Drain(); });\n\n  latch->Wait();\n  ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId());\n}\n\nTEST_F(GpuObjectTest, AutoPendingDrain) {\n  std::shared_ptr<fml::AutoResetWaitableEvent> latch =\n      std::make_shared<fml::AutoResetWaitableEvent>();\n  fml::TaskQueueId dtor_task_queue_id(0);\n  unref_queue()->StartAutoPendingDrain();\n  auto object = fml::MakeRefCounted<TestGPUObject>(latch, &dtor_task_queue_id);\n  {\n    // This object will be auto drained.\n    clay::GPUObject<TestGPUObject> gpu_object(std::move(object), unref_queue());\n  }\n\n  latch->Wait();\n  ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId());\n}\n\nTEST_F(GpuObjectTest, ObjectResetTwice) {\n  std::shared_ptr<fml::AutoResetWaitableEvent> latch =\n      std::make_shared<fml::AutoResetWaitableEvent>();\n  fml::TaskQueueId dtor_task_queue_id(0);\n  clay::GPUObject<TestGPUObject> gpu_object(\n      fml::MakeRefCounted<TestGPUObject>(latch, &dtor_task_queue_id),\n      unref_queue());\n\n  gpu_object.reset();\n  ASSERT_EQ(gpu_object.object(), nullptr);\n  gpu_object.reset();\n  ASSERT_EQ(gpu_object.object(), nullptr);\n\n  // Drain the queue on the unref task runner thread.\n  unref_task_runner()->PostTask(\n      [unref_queue = unref_queue()]() { unref_queue->Drain(); });\n\n  latch->Wait();\n  ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId());\n}\n\nTEST_F(GpuObjectTest, UnrefContextInTaskRunnerThread) {\n  std::shared_ptr<fml::AutoResetWaitableEvent> latch =\n      std::make_shared<fml::AutoResetWaitableEvent>();\n  fml::RefPtr<clay::UnrefQueue<TestContext>> unref_queue;\n  fml::TaskQueueId dtor_task_queue_id(0);\n  unref_task_runner()->PostTask([&]() {\n    auto context = sk_make_sp<TestContext>(latch, &dtor_task_queue_id);\n    unref_queue =\n        fml::MakeRefCounted<clay::UnrefQueue<TestContext>>(unref_task_runner());\n    unref_queue->SetContext(std::move(context));\n    latch->Signal();\n  });\n  latch->Wait();\n\n  // Delete the unref queue, it will schedule a task to unref the context in\n  // the task runner's thread.\n  unref_queue = nullptr;\n  latch->Wait();\n  // Verify that the context was destroyed in the task runner's thread.\n  ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/gpu_ref_object.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GPU_REF_OBJECT_H_\n#define CLAY_GFX_GPU_REF_OBJECT_H_\n\n#include \"base/include/fml/memory/ref_counted.h\"\n\nnamespace clay {\n\nclass GPURefObject : public fml::RefCountedThreadSafe<GPURefObject> {\n public:\n  GPURefObject() = default;\n  virtual ~GPURefObject() = default;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GPU_REF_OBJECT_H_\n"
  },
  {
    "path": "clay/gfx/graphics_canvas.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/graphics_canvas.h\"\n\nnamespace clay {}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/graphics_canvas.h",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GRAPHICS_CANVAS_H_\n#define CLAY_GFX_GRAPHICS_CANVAS_H_\n\n#include <memory>\n#include <vector>\n\n#include \"clay/gfx/image/graphics_image.h\"\n#include \"clay/gfx/paint.h\"\n#include \"clay/gfx/picture.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/text_blob.h\"\n#include \"skity/geometry/rect.hpp\"\n\nclass SkCanvas;\nclass GrRecordingContext;\n\nnamespace clay {\n\nclass GraphicsCanvas {\n public:\n  GraphicsCanvas() = default;\n  virtual ~GraphicsCanvas() = default;\n\n  virtual clay::GrCanvas* GetGrCanvas() = 0;\n\n#ifndef ENABLE_SKITY\n  virtual GrRecordingContext* RecordingContext() = 0;\n#endif  // ENABLE_SKITY\n\n  virtual int Save() = 0;\n  virtual void Restore() = 0;\n  virtual int SaveLayer(const skity::Rect& bounds, const Paint* paint) = 0;\n  virtual int GetSaveCount() = 0;\n  virtual void RestoreToCount(int save_count) = 0;\n\n  virtual void Translate(float dx, float dy) = 0;\n  virtual void Scale(float sx, float sy) = 0;\n  virtual void Rotate(float degrees) = 0;\n  virtual void Rotate(float degrees, float px, float py) = 0;\n  virtual void Skew(float sx, float sy) = 0;\n  virtual void Concat(const skity::Matrix& matrix) = 0;\n  virtual void SetMatrix(const skity::Matrix& matrix) = 0;\n  void ResetMatrix() { SetMatrix(skity::Matrix()); }\n\n  virtual void ClipRect(const skity::Rect& rect, GrClipOp op,\n                        bool do_anti_alias) = 0;\n  void ClipRect(const skity::Rect& rect, GrClipOp op) {\n    ClipRect(rect, op, false);\n  }\n  void ClipRect(const skity::Rect& rect, bool do_anti_alias) {\n    ClipRect(rect, GrClipOp::kIntersect, false);\n  }\n\n  virtual void ClipRRect(const skity::RRect& rrect, GrClipOp op,\n                         bool do_anti_alias) = 0;\n  void ClipRRect(const skity::RRect& rrect, bool do_anti_alias) {\n    ClipRRect(rrect, GrClipOp::kIntersect, do_anti_alias);\n  }\n  void ClipRRect(const skity::RRect& rrect, GrClipOp op) {\n    ClipRRect(rrect, op, false);\n  }\n  void ClipRRect(const skity::RRect& rrect) {\n    ClipRRect(rrect, GrClipOp::kIntersect, false);\n  }\n\n  virtual void ClipPath(const GrPath& path, GrClipOp op,\n                        bool do_anti_alias) = 0;\n  void ClipPath(const GrPath& path, GrClipOp op) { ClipPath(path, op, false); }\n  void ClipPath(const GrPath& path, bool do_anti_alias) {\n    ClipPath(path, GrClipOp::kIntersect, do_anti_alias);\n  }\n\n  virtual void OnDrawDynamicTextBlobsStart() {}\n  virtual void OnDrawDynamicTextBlobsEnd() {}\n\n  virtual bool QuickReject(const skity::Rect& rect) = 0;\n\n  virtual bool GetDeviceClipBounds(skity::Rect* bounds) = 0;\n\n  virtual void Clear(uint32_t color) = 0;\n\n  virtual void DrawPaint(const Paint& paint) = 0;\n\n  virtual void DrawLine(float x0, float y0, float x1, float y1,\n                        const Paint& paint) = 0;\n  void DrawLine(GrPoint p0, GrPoint p1, const Paint& paint) {\n#if defined(ENABLE_SKITY)\n    DrawLine(p0.x, p0.y, p1.x, p1.y, paint);\n#else\n    DrawLine(p0.x(), p0.y(), p1.x(), p1.y(), paint);\n#endif  // ENABLE_SKITY\n  }\n\n  virtual void DrawRect(const skity::Rect& rect, const Paint& paint) = 0;\n\n  virtual void DrawRRect(const skity::RRect& rrect, const Paint& paint) = 0;\n  virtual void DrawDRRect(const skity::RRect& outer, const skity::RRect& inner,\n                          const Paint& paint) = 0;\n  virtual void DrawCircle(float cx, float cy, float radius,\n                          const Paint& paint) = 0;\n  virtual void DrawArc(const skity::Rect& oval, float start_angle,\n                       float sweep_angle, bool use_center,\n                       const Paint& paint) = 0;\n  virtual void DrawPath(const GrPath& path, const Paint& paint) = 0;\n\n  virtual void DrawImage(const GraphicsImage* image, float x, float y,\n                         const GrSamplingOptions& sampling,\n                         const Paint* paint) = 0;\n  void DrawImage(const fml::RefPtr<GraphicsImage>& image, float x, float y,\n                 const GrSamplingOptions& sampling, const Paint* paint) {\n    DrawImage(image.get(), x, y, sampling, paint);\n  }\n  void DrawImage(GraphicsImage* image, double left, double top) {\n    DrawImage(image, left, top, GrSamplingOptions(), nullptr);\n  }\n  void DrawImage(const fml::RefPtr<GraphicsImage>& image, float left,\n                 float top) {\n    DrawImage(image.get(), left, top, GrSamplingOptions(), nullptr);\n  }\n\n  virtual void DrawImageRect(const GraphicsImage* image, const skity::Rect& src,\n                             const skity::Rect& dst,\n                             const GrSamplingOptions& sampling,\n                             const Paint* paint) = 0;\n  void DrawImageRect(const fml::RefPtr<GraphicsImage>& image,\n                     const skity::Rect& src, const skity::Rect& dst,\n                     const GrSamplingOptions& sampling, const Paint* paint) {\n    DrawImageRect(image.get(), src, dst, sampling, paint);\n  }\n  void DrawImageRect(const GraphicsImage* image, const skity::Rect& dst,\n                     const GrSamplingOptions& sampling, const Paint* paint) {\n    if (image == nullptr) {\n      return;\n    }\n    DrawImageRect(image, skity::Rect::MakeWH(image->width(), image->height()),\n                  dst, sampling, paint);\n  }\n  void DrawImageRect(const fml::RefPtr<GraphicsImage>& image,\n                     const skity::Rect& dst, const GrSamplingOptions& sampling,\n                     const Paint* paint) {\n    DrawImageRect(image.get(), dst, sampling, paint);\n  }\n\n  virtual void DrawImageNine(const GraphicsImage* image,\n                             const skity::Rect& center, const skity::Rect& dst,\n                             FilterMode filter, const Paint* paint) = 0;\n  virtual void DrawTextBlob(const fml::RefPtr<TextBlob>& blob, float x, float y,\n                            const Paint& paint) = 0;\n\n  virtual void DrawPicture(const Picture* picture) = 0;\n\n  virtual skity::Matrix GetTotalMatrix() = 0;\n\n  virtual std::unique_ptr<Picture> FinishRecordingAsPicture() = 0;\n\n protected:\n  bool has_lazy_image_ = false;\n\n private:\n  GraphicsCanvas(const GraphicsCanvas&) = delete;\n  GraphicsCanvas(GraphicsCanvas&&) = delete;\n  GraphicsCanvas& operator=(GraphicsCanvas&&) = delete;\n  GraphicsCanvas& operator=(const GraphicsCanvas&) = delete;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GRAPHICS_CANVAS_H_\n"
  },
  {
    "path": "clay/gfx/graphics_context.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/graphics_context.h\"\n\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/image/image_resource.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nGraphicsContext::AutoRestore::AutoRestore(GraphicsContext* context,\n                                          bool do_save)\n    : canvas_(context->Canvas()) {\n  if (canvas_) {\n    save_count_ = canvas_->GetSaveCount();\n    if (do_save) {\n      canvas_->Save();\n    }\n  }\n}\n\nGraphicsContext::AutoRestore::~AutoRestore() {\n  if (canvas_) {\n    canvas_->RestoreToCount(save_count_);\n  }\n}\n\nvoid GraphicsContext::AutoRestore::Restore() {\n  if (canvas_) {\n    canvas_->RestoreToCount(save_count_);\n    canvas_ = nullptr;\n  }\n}\n\nstatic const int dash_ratio = 3;  // Ratio of the length of a dash to its width.\n\nbool GraphicsContext::BeginRecording(const skity::Rect& bounds) {\n  return paint_recorder_.BeginRecording(bounds) != nullptr;\n}\n\n#ifndef ENABLE_SKITY\nbool GraphicsContext::BeginRecording(const SkBitmap& bitmap) {\n  return paint_recorder_.BeginRecording(bitmap) != nullptr;\n}\n#endif  // ENABLE_SKITY\n\nbool GraphicsContext::IsRecording() const {\n  return paint_recorder_.IsRecording();\n}\n\nstd::unique_ptr<Picture> GraphicsContext::FinishRecording() {\n  return paint_recorder_.FinishRecordingAsPicture();\n}\n\nvoid GraphicsContext::DrawNinePatch(const GraphicsImage* image,\n                                    const std::array<float, 4>& cap_insets,\n                                    float cap_insets_scale, float pixel_ratio,\n                                    const skity::Rect& dst_rect) {\n  TRACE_EVENT(\"clay\", \"DrawNinePatch\");\n  FML_DCHECK(IsRecording());\n  FML_DCHECK(image);\n\n  // Apply CapInsetsScale:\n  if (cap_insets_scale <= 0) {\n    FML_DLOG(ERROR) << \"Get invalid cap-insets-scale value.\";\n    cap_insets_scale = 1.f;\n  }\n  float border_left_w = cap_insets[3];\n  float border_top_h = cap_insets[0];\n  float center_rect_scale_left = border_left_w * cap_insets_scale;\n  float center_rect_scale_top = border_top_h * cap_insets_scale;\n\n  float border_right_w = cap_insets[1];\n  float border_bottom_h = cap_insets[2];\n  float center_rect_scale_right =\n      image->width() - border_right_w * cap_insets_scale;\n  float center_rect_scale_bottom =\n      image->height() - border_bottom_h * cap_insets_scale;\n\n  skity::Rect center_rect_scale =\n      skity::Rect::MakeLTRB(center_rect_scale_left, center_rect_scale_top,\n                            center_rect_scale_right, center_rect_scale_bottom);\n\n  // Failed to apply |cap_insets_scale|, ignore it.\n  if (!center_rect_scale.IsSorted()) {\n    center_rect_scale = skity::Rect::MakeWH(image->width(), image->height());\n  }\n\n  center_rect_scale.RoundIn();\n  if (center_rect_scale ==\n      skity::Rect::MakeWH(image->width(), image->height())) {\n    Canvas()->DrawImageRect(image, dst_rect,\n                            SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n  } else {\n    // 'SkCanvas->drawImageNine' can only handle images which would be divided\n    // into 9 parts. And images expected to divided into 3 or less parts would\n    // be paint as normal images through 'SkCanvas->drawImageRect'. To handle\n    // all cases, we draw parts individually by ourselves.\n    DrawNinePatchIndividually(image, pixel_ratio, cap_insets, center_rect_scale,\n                              dst_rect);\n  }\n}\n\nvoid GraphicsContext::DrawNinePatchIndividually(\n    const GraphicsImage* image, float pixel_ratio,\n    const std::array<float, 4>& cap_insets, const skity::Rect& center,\n    const skity::Rect& dst_rect) {\n  skity::Rect src0 = skity::Rect::MakeLTRB(0, 0, center.Left(), center.Top());\n  skity::Rect src1 = skity::Rect::MakeLTRB(src0.Right(), src0.Top(),\n                                           center.Right(), src0.Bottom());\n  skity::Rect src2 = skity::Rect::MakeLTRB(src1.Right(), src0.Top(),\n                                           image->width(), src0.Bottom());\n  skity::Rect src3 = skity::Rect::MakeLTRB(src0.Left(), src0.Bottom(),\n                                           src0.Right(), center.Bottom());\n  skity::Rect src4 = skity::Rect::MakeLTRB(src3.Right(), src3.Top(),\n                                           src1.Right(), src3.Bottom());\n  skity::Rect src5 = skity::Rect::MakeLTRB(src4.Right(), src3.Top(),\n                                           src2.Right(), src3.Bottom());\n  skity::Rect src6 = skity::Rect::MakeLTRB(src3.Left(), src3.Bottom(),\n                                           src3.Right(), image->height());\n  skity::Rect src7 = skity::Rect::MakeLTRB(src6.Right(), src6.Top(),\n                                           src4.Right(), src6.Bottom());\n  skity::Rect src8 = skity::Rect::MakeLTRB(src7.Right(), src6.Top(),\n                                           src5.Right(), src6.Bottom());\n\n  float offset_x = dst_rect.Left();\n  float offset_y = dst_rect.Top();\n  skity::Rect dst0 = skity::Rect::MakeLTRB(0, 0, cap_insets[3] * pixel_ratio,\n                                           cap_insets[0] * pixel_ratio);\n  skity::Rect dst1 = skity::Rect::MakeLTRB(\n      dst0.Right(), dst0.Top(), dst_rect.Width() - cap_insets[1] * pixel_ratio,\n      dst0.Bottom());\n  skity::Rect dst2 = skity::Rect::MakeLTRB(dst1.Right(), dst0.Top(),\n                                           dst_rect.Width(), dst0.Bottom());\n  skity::Rect dst3 =\n      skity::Rect::MakeLTRB(dst0.Left(), dst0.Bottom(), dst0.Right(),\n                            dst_rect.Height() - cap_insets[2] * pixel_ratio);\n  skity::Rect dst4 = skity::Rect::MakeLTRB(dst3.Right(), dst3.Top(),\n                                           dst1.Right(), dst3.Bottom());\n  skity::Rect dst5 = skity::Rect::MakeLTRB(dst4.Right(), dst2.Bottom(),\n                                           dst2.Right(), dst3.Bottom());\n  skity::Rect dst6 = skity::Rect::MakeLTRB(dst0.Left(), dst3.Bottom(),\n                                           dst3.Right(), dst_rect.Height());\n  skity::Rect dst7 = skity::Rect::MakeLTRB(dst6.Right(), dst4.Bottom(),\n                                           dst4.Right(), dst6.Bottom());\n  skity::Rect dst8 = skity::Rect::MakeLTRB(dst7.Right(), dst5.Bottom(),\n                                           dst5.Right(), dst7.Bottom());\n\n  dst0.Offset(offset_x, offset_y);\n  dst1.Offset(offset_x, offset_y);\n  dst2.Offset(offset_x, offset_y);\n  dst3.Offset(offset_x, offset_y);\n  dst4.Offset(offset_x, offset_y);\n  dst5.Offset(offset_x, offset_y);\n  dst6.Offset(offset_x, offset_y);\n  dst7.Offset(offset_x, offset_y);\n  dst8.Offset(offset_x, offset_y);\n\n  Canvas()->DrawImageRect(image, src0, dst0,\n                          SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n  Canvas()->DrawImageRect(image, src1, dst1,\n                          SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n  Canvas()->DrawImageRect(image, src2, dst2,\n                          SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n  Canvas()->DrawImageRect(image, src3, dst3,\n                          SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n  Canvas()->DrawImageRect(image, src4, dst4,\n                          SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n  Canvas()->DrawImageRect(image, src5, dst5,\n                          SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n  Canvas()->DrawImageRect(image, src6, dst6,\n                          SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n  Canvas()->DrawImageRect(image, src7, dst7,\n                          SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n  Canvas()->DrawImageRect(image, src8, dst8,\n                          SAMPLING_OPTIONS(FilterMode::kLinear, 0), nullptr);\n}\n\nvoid GraphicsContext::ApplyBlurEffect(float blur, Paint* paint) const {\n  TRACE_EVENT(\"clay\", \"ApplyBlurEffect\");\n\n  if (blur <= 0.0f) {\n    return;\n  }\n\n  // float sigma = GraphicsContext::ConvertRadiusToSigma(blur);\n\n  // In <image> widget May be not necessary to convert blur_radius\n  // to sigma, or the sigma value is apparently smaller than wanted.\n  // This is probably related to the skia api behavior.\n\n  auto blur_filter =\n      std::make_shared<BlurImageFilter>(blur, blur, TileMode::kClamp);\n\n  paint->setImageFilter(blur_filter);\n}\n\nvoid GraphicsContext::ApplyDropShadowEffect(Paint* paint,\n                                            const ImageData& image_data) {\n  if (image_data.drop_shadow_blur_radius == 0.0f &&\n      image_data.drop_shadow_offset_x == 0.0f &&\n      image_data.drop_shadow_offset_y == 0.0f) {\n    return;\n  }\n\n  auto image_filter = std::make_shared<DropShadowImageFilter>(\n      image_data.drop_shadow_offset_x, image_data.drop_shadow_offset_y,\n      image_data.drop_shadow_blur_radius, image_data.drop_shadow_blur_radius,\n      image_data.drop_shadow_color);\n\n  paint->setImageFilter(image_filter);\n}\n\n// TODO(yudingqian): Decouple incompletely\nbool GraphicsContext::DrawImageWithDropShadow(\n    fml::RefPtr<GraphicsImage> image, const ImageData& image_data,\n    const skity::Rect& src_rect, const skity::Rect& dst_rect,\n    const skity::Rect& padding_rect, float padding_left, float padding_top) {\n  TRACE_EVENT(\"clay\", \"DrawWithDropShadow\");\n  FML_DCHECK(image);\n\n  if (image_data.drop_shadow_blur_radius == 0.0f &&\n      image_data.drop_shadow_offset_x == 0.0f &&\n      image_data.drop_shadow_offset_y == 0.0f) {\n    return false;\n  }\n\n  float sigma =\n      GraphicsContext::ConvertRadiusToSigma(image_data.drop_shadow_blur_radius);\n\n  if (sigma < 0.5f) {\n    return false;\n  }\n\n#ifndef ENABLE_SKITY\n  // Resize the image before drop shadow on it.\n  auto resize_filter =\n      std::make_shared<UnknownImageFilter>(SkImageFilters::Image(\n          image->gr_image(), ConvertSkityRectToSkRect(src_rect),\n          ConvertSkityRectToSkRect(dst_rect),\n          SAMPLING_OPTIONS(FilterMode::kLinear, 0)));\n  SkIRect out_image_rect = SkIRect::MakeEmpty();\n  SkIPoint out_image_offset = SkIPoint::Make(0, 0);\n  auto src_round = src_rect;\n  src_round.Round();\n  auto dst_round = dst_rect;\n  dst_round.Round();\n  fml::RefPtr<GraphicsImage> resized_img = image->makeWithFilter(\n      Canvas()->RecordingContext(), resize_filter.get(),\n      ConvertSkityRectToSkIRect(src_round),\n      ConvertSkityRectToSkIRect(dst_round), &out_image_rect, &out_image_offset);\n\n  if (!resized_img) {\n    return false;\n  }\n\n  // Drop Shadow on image by shadow_filter.\n  auto shadow_filter = std::make_shared<DropShadowImageFilter>(\n      image_data.drop_shadow_offset_x, image_data.drop_shadow_offset_y, sigma,\n      sigma, image_data.drop_shadow_color);\n\n  // We don't know how large the shadow-image will be, just tell filter\n  // to clip by padding size. It is in the coordinate system of the\n  // |resized_img|, where (0,0) corresponds to the |resized_img|'s top left\n  // corner, so |padding_clip_x| and |padding_clip_y| is less or equal to zero.\n  int32_t padding_clip_x = -(padding_rect.Width() - resized_img->width()) * 0.5;\n  int32_t padding_clip_y =\n      -(padding_rect.Height() - resized_img->height()) * 0.5;\n\n  FML_DCHECK(padding_clip_x <= 0);\n  FML_DCHECK(padding_clip_y <= 0);\n\n  SkIRect padding_clip =\n      SkIRect::MakeXYWH(padding_clip_x, padding_clip_y, padding_rect.Width(),\n                        padding_rect.Height());\n  out_image_rect = SkIRect::MakeEmpty();\n  out_image_offset = SkIPoint::Make(0, 0);\n  fml::RefPtr<GraphicsImage> shadow_img = resized_img->makeWithFilter(\n      Canvas()->RecordingContext(), shadow_filter.get(),\n      SkIRect::MakeXYWH(resized_img->bounds().X(), resized_img->bounds().Y(),\n                        resized_img->bounds().Width(),\n                        resized_img->bounds().Height()),\n      padding_clip, &out_image_rect, &out_image_offset);\n\n  if (!shadow_img) {\n    return false;\n  }\n\n  FML_DCHECK(out_image_rect.width() <= padding_rect.Width());\n  FML_DCHECK(out_image_rect.height() <= padding_rect.Height());\n\n  // Calculate the |shadow_img| position, and draw it.\n  float dest_x = padding_rect.X() + (out_image_offset.x() - padding_clip_x);\n  float dest_y = padding_rect.Y() + (out_image_offset.y() - padding_clip_y);\n  // Offset has contained padding left/top, we should remove them.\n  Canvas()->Translate(-padding_left, -padding_top);\n  Canvas()->DrawImage(shadow_img.get(), dest_x, dest_y);\n#endif\n  return true;\n}\n\nvoid GraphicsContext::DrawImageRect(const fml::RefPtr<GraphicsImage>& image,\n                                    const skity::Rect& src,\n                                    const skity::Rect& dst,\n                                    const GrSamplingOptions& sampling,\n                                    const Paint* paint) {\n  Canvas()->DrawImageRect(image, src, dst, sampling, paint);\n}\n\nvoid GraphicsContext::DrawImageRect(const fml::RefPtr<GraphicsImage>& image,\n                                    const skity::Rect& dst,\n                                    const GrSamplingOptions& sampling,\n                                    const Paint* paint) {\n  Canvas()->DrawImageRect(image, dst, sampling, paint);\n}\n\nvoid GraphicsContext::DrawLine(Paint stroke_paint, const FloatPoint& point1,\n                               const FloatPoint& point2, float thickness,\n                               BorderStyleType style) {\n  FloatPoint p1 = point1;\n  FloatPoint p2 = point2;\n  bool is_vertical_line = (p1.x() == p2.x());\n  int width = roundf(thickness);\n\n  // We know these are vertical or horizontal lines, so the length will just\n  // be the sum of the displacement component vectors give or take 1 -\n  // probably worth the speed up of no square root, which also won't be exact.\n  FloatPoint delta = p2 - p1;\n  FloatSize disp(delta.x(), delta.y());\n\n  if (style == BorderStyleType::kDotted || style == BorderStyleType::kDashed) {\n    // Do a rect fill of our endpoints.  This ensures we always have the\n    // appearance of being a border.  We then draw the actual dotted/dashed\n    // line.\n    skity::Rect r1 =\n        skity::Rect::MakeLTRB(p1.x(), p1.y(), p1.x() + width, p1.y() + width);\n    skity::Rect r2 =\n        skity::Rect::MakeLTRB(p2.x(), p2.y(), p2.x() + width, p2.y() + width);\n\n    if (is_vertical_line) {\n      r1.Offset(-width / 2, 0);\n      r2.Offset(-width / 2, -width);\n    } else {\n      r1.Offset(0, -width / 2);\n      r2.Offset(-width, -width / 2);\n    }\n    Paint fill_paint;\n    fill_paint.setColor(stroke_paint.getColor());\n    DrawRect(fill_paint, r1);\n    DrawRect(fill_paint, r2);\n  }\n\n  AdjustLineToPixelBoundaries(&p1, &p2, width, style);\n  Canvas()->DrawLine(p1.x(), p1.y(), p2.x(), p2.y(), stroke_paint);\n}\n\nvoid GraphicsContext::DrawRect(Paint paint, const skity::Rect& rect) {\n  if (rect.IsEmpty()) {\n    return;\n  }\n\n  Canvas()->DrawRect(rect, paint);\n}\n\nvoid GraphicsContext::FillPolygon(size_t num_points, const FloatPoint* points,\n                                  const Color& color, bool should_antialias) {\n  clay::GrPath path;\n  SetPathFromPoints(&path, num_points, points);\n\n  Paint paint;\n  paint.setAntiAlias(should_antialias);\n  paint.setColor(color);\n\n  Canvas()->DrawPath(path, paint);\n}\n\nvoid GraphicsContext::SetupPaintDashPathEffect(Paint* paint, int length,\n                                               float thickness,\n                                               BorderStyleType style) {\n  float width = thickness;\n  switch (style) {\n    case BorderStyleType::kNone:\n    case BorderStyleType::kSolid:\n    case BorderStyleType::kDouble:\n      paint->setPathEffect(nullptr);\n      return;\n    case BorderStyleType::kDashed:\n      width = dash_ratio * width;\n      // Fall through.\n    case BorderStyleType::kDotted: {\n      // Truncate the width, since we don't want fuzzy dots or dashes.\n      int dash_length = static_cast<int>(width);\n      // Subtract off the endcaps, since they're rendered separately.\n      int distance = length - 2 * static_cast<int>(thickness);\n      int phase = 1;\n      if (dash_length > 1) {\n        // Determine how many dashes or dots we should have.\n        int num_dashes = distance / dash_length;\n        int remainder = distance % dash_length;\n        // Adjust the phase to center the dashes within the line.\n        if (num_dashes % 2) {\n          // Odd: shift right a full dash, minus half the remainder.\n          phase = dash_length - remainder / 2;\n        } else {\n          // Even: shift right half a dash, minus half the remainder.\n          phase = (dash_length - remainder) / 2;\n        }\n      }\n      float dash_length_sk = dash_length;\n      float intervals[2] = {dash_length_sk, dash_length_sk};\n      paint->setPathEffect(\n          DashPathEffect::Make(intervals, 2, static_cast<float>(phase)));\n      return;\n    }\n    default:\n      return;\n  }\n}\n\nfloat GraphicsContext::GetPathLength(const GrPath& path) {\n  float length = 0;\n  GrPathMeasure measure(path, false);\n\n  do {\n    length += PATH_MEASURE_GET_LENGTH(measure);\n  } while (PATH_MEASURE_NEXT_CONTOUR(measure));\n\n  return length;\n}\n\nvoid GraphicsContext::SetPathFromPoints(clay::GrPath* path, size_t num_points,\n                                        const FloatPoint* points) {\n#ifndef ENABLE_SKITY\n  path->incReserve(num_points);\n#endif  // ENABLE_SKITY\n  PATH_MOVE_TO((*path), points[0].x(), points[0].y());\n  for (size_t i = 1; i < num_points; ++i) {\n    PATH_LINE_TO((*path), points[i].x(), points[i].y());\n  }\n}\n\nvoid GraphicsContext::AdjustLineToPixelBoundaries(FloatPoint* p1,\n                                                  FloatPoint* p2,\n                                                  float stroke_width,\n                                                  BorderStyleType style) {\n  FML_DCHECK(p1 && p2);\n  // For odd widths, we add in 0.5 to the appropriate x/y so that the float\n  // arithmetic works out.  For example, with a border width of 3, WebKit will\n  // pass us (y1+y2)/2, e.g., (50+53)/2 = 103/2 = 51 when we want 51.5.  It is\n  // always true that an even width gave us a perfect position, but an odd width\n  // gave us a position that is off by exactly 0.5.\n  if (style == BorderStyleType::kDotted || style == BorderStyleType::kDashed) {\n    if (p1->x() == p2->x()) {\n      p1->SetY(p1->y() + stroke_width);\n      p2->SetY(p2->y() - stroke_width);\n    } else {\n      p1->SetX(p1->x() + stroke_width);\n      p2->SetX(p2->x() - stroke_width);\n    }\n  }\n\n  if (static_cast<int>(stroke_width) % 2) {  // odd\n    if (p1->x() == p2->x()) {\n      // We're a vertical line.  Adjust our x.\n      p1->SetX(p1->x() + 0.5f);\n      p2->SetX(p2->x() + 0.5f);\n    } else {\n      // We're a horizontal line. Adjust our y.\n      p1->SetY(p1->y() + 0.5f);\n      p2->SetY(p2->y() + 0.5f);\n    }\n  }\n}\n\n// Copy from third_party/skia/src/core/SkBlurMask.cpp.\n// Converts a blur radius in pixels to sigma.\nfloat GraphicsContext::ConvertRadiusToSigma(float radius) {\n  return radius > 0.f ? radius * 0.57735f + 0.5f : 0.f;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/graphics_context.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GRAPHICS_CONTEXT_H_\n#define CLAY_GFX_GRAPHICS_CONTEXT_H_\n\n#include <array>\n#include <memory>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/graphics_canvas.h\"\n#include \"clay/gfx/image/image_data.h\"\n#include \"clay/gfx/image/image_resource.h\"\n#include \"clay/gfx/paint_recorder.h\"\n#include \"clay/gfx/picture.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/borders_data.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/gfx/style/length.h\"\n\nnamespace clay {\n\nclass GraphicsContext final {\n public:\n  class AutoRestore {\n   public:\n    AutoRestore(GraphicsContext* context, bool do_save);\n    ~AutoRestore();\n    void Restore();\n\n   private:\n    GraphicsCanvas* canvas_ = nullptr;\n    int save_count_ = 0;\n  };\n\n  explicit GraphicsContext(fml::RefPtr<GPUUnrefQueue> unref_queue)\n      : unref_queue_(unref_queue), paint_recorder_(unref_queue) {}\n\n  bool BeginRecording(const skity::Rect& bounds);\n#ifndef ENABLE_SKITY\n  bool BeginRecording(const SkBitmap& bitmap);\n#endif  // ENABLE_SKITY\n  bool IsRecording() const;\n  std::unique_ptr<Picture> FinishRecording();\n\n  void DrawNinePatch(const GraphicsImage* image,\n                     const std::array<float, 4>& cap_insets,\n                     float cap_insets_scale, float pixel_ratio,\n                     const skity::Rect& dst_rect);\n\n  void ApplyBlurEffect(float blur, Paint* paint) const;\n  void ApplyDropShadowEffect(Paint* paint, const ImageData& image_data);\n\n  bool DrawImageWithDropShadow(fml::RefPtr<GraphicsImage> image,\n                               const ImageData& image_data,\n                               const skity::Rect& src_rect,\n                               const skity::Rect& dst_rect,\n                               const skity::Rect& padding_rect,\n                               float padding_left, float padding_top);\n\n  void DrawImageRect(const fml::RefPtr<GraphicsImage>& image,\n                     const skity::Rect& src, const skity::Rect& dst,\n                     const GrSamplingOptions& sampling, const Paint* paint);\n\n  void DrawImageRect(const fml::RefPtr<GraphicsImage>& image,\n                     const skity::Rect& dst, const GrSamplingOptions& sampling,\n                     const Paint* paint);\n\n  void DrawLine(Paint stroke_paint, const FloatPoint& point1,\n                const FloatPoint& point2, float thickness,\n                BorderStyleType style);\n\n  void DrawRect(Paint paint, const skity::Rect& rect);\n\n  void FillPolygon(size_t num_points, const FloatPoint* points,\n                   const Color& color, bool should_antialias);\n\n  static void SetupPaintDashPathEffect(Paint* paint, int length,\n                                       float thickness, BorderStyleType style);\n  static float GetPathLength(const GrPath& path);\n  static void SetPathFromPoints(GrPath* path, size_t num_points,\n                                const FloatPoint* points);\n  static void AdjustLineToPixelBoundaries(FloatPoint* p1, FloatPoint* p2,\n                                          float stroke_width,\n                                          BorderStyleType style);\n  static float ConvertRadiusToSigma(float radius);\n\n  // GraphicsCanvas wrappers\n  GraphicsCanvas* Canvas() { return paint_recorder_.Canvas(); }\n\n  clay::GrCanvas* GetGrCanvas() {\n    return paint_recorder_.Canvas()->GetGrCanvas();\n  }\n\n  fml::RefPtr<GPUUnrefQueue> GetUnrefQueue() { return unref_queue_; }\n  int Save() { return Canvas()->Save(); }\n  int SaveLayer(const skity::Rect& bounds, const Paint* paint) {\n    return Canvas()->SaveLayer(bounds, paint);\n  }\n  void Restore() { Canvas()->Restore(); }\n  void Translate(float dx, float dy) { Canvas()->Translate(dx, dy); }\n  void Scale(float sx, float sy) { Canvas()->Scale(sx, sy); }\n  void Rotate(float degrees) { Canvas()->Rotate(degrees); }\n  void Concat(const skity::Matrix& matrix) { Canvas()->Concat(matrix); }\n  void SetMatrix(const skity::Matrix& matrix) { Canvas()->SetMatrix(matrix); }\n  void ResetMatrix() { Canvas()->ResetMatrix(); }\n  void ClipRect(const skity::Rect& rect, GrClipOp op, bool do_anti_alias) {\n    Canvas()->ClipRect(rect, op, do_anti_alias);\n  }\n  void DrawLine(float x0, float y0, float x1, float y1, const Paint& paint) {\n    Canvas()->DrawLine(x0, y0, x1, y1, paint);\n  }\n  void ClipRRect(const skity::RRect& rrect, GrClipOp op, bool do_anti_alias) {\n    Canvas()->ClipRRect(rrect, op, do_anti_alias);\n  }\n  void DrawRect(const skity::Rect& rect, const Paint& paint) {\n    Canvas()->DrawRect(rect, paint);\n  }\n  void DrawRRect(const skity::RRect& rrect, const Paint& paint) {\n    Canvas()->DrawRRect(rrect, paint);\n  }\n  void ClipPath(const GrPath& path, GrClipOp op, bool do_anti_alias) {\n    Canvas()->ClipPath(path, op, do_anti_alias);\n  }\n  bool GetDeviceClipBounds(skity::Rect* bounds) {\n    return Canvas()->GetDeviceClipBounds(bounds);\n  }\n  void DrawDRRect(const skity::RRect& outer, const skity::RRect& inner,\n                  const Paint& paint) {\n    Canvas()->DrawDRRect(outer, inner, paint);\n  }\n  void DrawPath(const GrPath& path, const Paint& paint) {\n    Canvas()->DrawPath(path, paint);\n  }\n  void DrawPicture(const Picture* picture) { Canvas()->DrawPicture(picture); }\n\n private:\n  void DrawNinePatchIndividually(const GraphicsImage* image, float pixel_ratio,\n                                 const std::array<float, 4>& cap_insets,\n                                 const skity::Rect& center,\n                                 const skity::Rect& dst_rect);\n\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n  PaintRecorder paint_recorder_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GRAPHICS_CONTEXT_H_\n"
  },
  {
    "path": "clay/gfx/graphics_isolate.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/graphics_isolate.h\"\n\n#include \"base/include/no_destructor.h\"\n\nnamespace clay {\n\nGraphicsIsolate& GraphicsIsolate::Instance() {\n  static fml::NoDestructor<GraphicsIsolate> instance;\n  return *(instance.get());\n}\n\nfml::RefPtr<GPUUnrefQueue> GraphicsIsolate::GetOrCreateUnrefQueue(\n    fml::RefPtr<fml::TaskRunner> task_runner) {\n  auto gpu_queue_id = task_runner->GetTaskQueueId();\n  auto iter = unref_queue_map_.find(gpu_queue_id);\n  if (iter != unref_queue_map_.end()) {\n    return iter->second;\n  }\n  // On Windows or Mac, each page is represented by a separate window, with\n  // each window running its own raster thread. Each raster thread, in turn\n  // is bound to a dedicated GPU queue, so the GPUUnRefQueue is not shared.\n#if OS_WIN || OS_MAC\n  auto unref_queue = fml::MakeRefCounted<GPUUnrefQueue>(task_runner, false);\n#else\n  auto unref_queue = fml::MakeRefCounted<GPUUnrefQueue>(task_runner);\n#endif\n\n  unref_queue_map_.emplace(gpu_queue_id, unref_queue);\n  return unref_queue;\n}\n\nvoid GraphicsIsolate::RemoveUnrefQueue(\n    fml::RefPtr<fml::TaskRunner> task_runner) {\n  auto gpu_queue_id = task_runner->GetTaskQueueId();\n  auto iter = unref_queue_map_.find(gpu_queue_id);\n  if (iter != unref_queue_map_.end()) {\n    unref_queue_map_.erase(iter);\n  }\n}\n\nstd::shared_ptr<ImageDecoder> GraphicsIsolate::GetImageDecoder() {\n  if (!image_decoder_) {\n    image_decoder_ =\n        std::make_shared<ImageDecoder>(GetConcurrentWorkerTaskRunner());\n  }\n  return image_decoder_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/graphics_isolate.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_GRAPHICS_ISOLATE_H_\n#define CLAY_GFX_GRAPHICS_ISOLATE_H_\n\n#include <memory>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/fml/concurrent_message_loop.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/image_decoder.h\"\n#include \"clay/gfx/image/image_descriptor.h\"\n#include \"clay/gfx/image/skimage_holder.h\"\n\nnamespace clay {\n\nclass GraphicsDelegate {\n public:\n  virtual void CacheStoreImage(fml::RefPtr<SkImageHolder> img) = 0;\n  virtual void CacheRemoveImage(fml::RefPtr<SkImageHolder> img) = 0;\n\n  virtual std::shared_ptr<fml::ConcurrentTaskRunner>\n  GetConcurrentWorkerTaskRunner() const = 0;\n};\n\nclass GraphicsIsolate final : public GraphicsDelegate {\n public:\n  static GraphicsIsolate& Instance();\n\n  void SetDelegate(GraphicsDelegate* delegate) { delegate_ = delegate; }\n\n  void CacheStoreImage(fml::RefPtr<SkImageHolder> img) override {\n    FML_DCHECK(delegate_);\n    delegate_->CacheStoreImage(img);\n  }\n  void CacheRemoveImage(fml::RefPtr<SkImageHolder> img) override {\n    FML_DCHECK(delegate_);\n    delegate_->CacheRemoveImage(img);\n  }\n  std::shared_ptr<fml::ConcurrentTaskRunner> GetConcurrentWorkerTaskRunner()\n      const override {\n    FML_DCHECK(delegate_);\n    return delegate_->GetConcurrentWorkerTaskRunner();\n  }\n\n  fml::RefPtr<GPUUnrefQueue> GetOrCreateUnrefQueue(\n      fml::RefPtr<fml::TaskRunner> task_runner);\n\n  void RemoveUnrefQueue(fml::RefPtr<fml::TaskRunner> task_runner);\n\n  std::shared_ptr<ImageDecoder> GetImageDecoder();\n\n private:\n  GraphicsDelegate* delegate_ = nullptr;\n  std::shared_ptr<ImageDecoder> image_decoder_;\n  std::unordered_map<int, fml::RefPtr<GPUUnrefQueue>> unref_queue_map_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_GRAPHICS_ISOLATE_H_\n"
  },
  {
    "path": "clay/gfx/image/animated_image.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/animated_image.h\"\n\n#include <utility>\n\n#include \"clay/gfx/graphics_context.h\"\n\nnamespace clay {\n\nstd::shared_ptr<AnimatedImage> AnimatedImage::Make(\n    fml::WeakPtr<ImageFetcher> image_fetcher, std::string url,\n    fml::RefPtr<fml::TaskRunner> task_runner,\n    std::shared_ptr<PlatformImage> platform_image) {\n  auto image = std::shared_ptr<AnimatedImage>(new AnimatedImage);\n  image->type_ = ImageType::kAnimated;\n  image->image_fetcher_ = image_fetcher;\n  image->url_ = std::move(url);\n  image->image_ = platform_image;\n  image->frame_timer_ = std::make_unique<fml::OneshotTimer>(task_runner);\n  image->orig_info_ = ImageInfo::makeWH(platform_image->GetWidth(),\n                                        platform_image->GetHeight());\n  image->StartAnimate();\n  return image;\n}\n\nvoid AnimatedImage::Upload(fml::RefPtr<GPUUnrefQueue> unref_queue, Size size) {\n  if (!unref_queue || !unref_queue->GetContext()) {\n    FML_LOG(ERROR) << \"AnimatedImage::Upload: unref_queue or context is null\";\n    return;\n  }\n  if (!gpu_image_.object()) {\n    auto pixmap = image_->ToBitmap();\n    if (!pixmap) {\n      return;\n    }\n    auto image = skity::Image::MakeDeferredTextureImage(\n        skity::Texture::FormatFromColorType(pixmap->GetColorType()),\n        pixmap->Width(), pixmap->Height(), pixmap->GetAlphaType());\n    gpu_image_ = GPUObject(GraphicsImage::Make(image), unref_queue);\n    unref_queue->GetTaskRunner()->PostTask([context = unref_queue->GetContext(),\n                                            image, pixmap,\n                                            weak = weak_from_this()]() {\n      if (auto self = weak.lock()) {\n        auto texture = context->CreateTexture(\n            skity::Texture::FormatFromColorType(pixmap->GetColorType()),\n            pixmap->Width(), pixmap->Height(), pixmap->GetAlphaType());\n        if (texture) {\n          texture->DeferredUploadImage(std::move(pixmap));\n          image->SetTexture(texture);\n        }\n      }\n    });\n  }\n}\n\nvoid AnimatedImage::NextFrame() {\n  if (image_->GetDuration() <= 0) {\n    return;\n  }\n  frame_timer_->Start(\n      fml::TimeDelta::FromMilliseconds(image_->GetDuration()),\n      [weak = weak_from_this()] {\n        if (auto self = weak.lock()) {\n          auto animated_image = static_cast<AnimatedImage*>(self.get());\n          animated_image->image_->DrawFrame(\n              fml::TimePoint::Now().ToEpochDelta().ToMilliseconds(),\n              [animated_image] { animated_image->OnNotifyAnimationFrame(); });\n        }\n      });\n}\n\nvoid AnimatedImage::SetAutoPlay(bool auto_play) {\n  image_->SetAutoPlay(auto_play);\n}\nvoid AnimatedImage::SetLoopCount(int loop_count) {\n  image_->SetLoopCount(loop_count);\n}\nvoid AnimatedImage::StartAnimate() {\n  StopAnimation();\n  image_->StartAnimation();\n  OnNotifyAnimationFrame();\n}\nvoid AnimatedImage::StopAnimation() { image_->StopAnimation(); }\nvoid AnimatedImage::PauseAnimation() { image_->PauseAnimation(); }\nvoid AnimatedImage::ResumeAnimation() {\n  image_->ResumeAnimation();\n  OnNotifyAnimationFrame();\n}\n\nvoid AnimatedImage::OnNotifyAnimationFrame() {\n  for (auto& instance : instances_) {\n    instance->OnNotifyAnimationFrame();\n  }\n  NextFrame();\n  gpu_image_.reset();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/animated_image.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_ANIMATED_IMAGE_H_\n#define CLAY_GFX_IMAGE_ANIMATED_IMAGE_H_\n\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/base_image.h\"\n\nnamespace clay {\n\nclass AnimatedImage : public BaseImage {\n public:\n  static std::shared_ptr<AnimatedImage> Make(\n      fml::WeakPtr<ImageFetcher> image_fetcher, std::string url,\n      fml::RefPtr<fml::TaskRunner> task_runner,\n      std::shared_ptr<PlatformImage> image);\n\n  void Upload(fml::RefPtr<GPUUnrefQueue> unref_queue, Size size) override;\n\n  void SetAutoPlay(bool auto_play);\n  void SetLoopCount(int loop_count);\n  void StartAnimate();\n  void StopAnimation();\n  void PauseAnimation();\n  void ResumeAnimation();\n\n private:\n  AnimatedImage() = default;\n\n  void NextFrame();\n  void OnNotifyAnimationFrame();\n\n private:\n  std::unique_ptr<fml::OneshotTimer> frame_timer_;\n};\n\n}  // namespace clay\n#endif  // CLAY_GFX_IMAGE_ANIMATED_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/image/base_image.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/base_image.h\"\n\n#include \"clay/ui/resource/image_fetcher.h\"\n\nnamespace clay {\n\nstd::unique_ptr<BaseImageInstance> BaseImage::NewInstance() {\n  return std::make_unique<BaseImageInstance>(shared_from_this());\n}\n\nvoid BaseImage::OnInstanceCreated(BaseImageInstance* instance) {\n  instances_.insert(instance);\n}\n\nvoid BaseImage::OnInstanceDestroyed(BaseImageInstance* instance) {\n  instances_.erase(instance);\n  if (instances_.empty() && image_fetcher_) {\n    image_fetcher_->OnImageHasNoAccessor(this);\n  }\n}\n\nvoid BaseImage::SetExpectSizeCalculator(\n    std::function<skity::Vec2(skity::Vec2)> calculator,\n    bool force_use_original_size) {\n  skity::Vec2 expect_size =\n      calculator(skity::Vec2(orig_info_.width(), orig_info_.height()));\n  // if origin_size can contain expect_size, use expect_size, else origin_size.\n  auto chooser = [this, &expect_size, force_use_original_size]() {\n    if (expect_size.x > orig_info_.width() ||\n        expect_size.y > orig_info_.height() || force_use_original_size) {\n      render_info_ = orig_info_;\n    } else {\n      render_info_ = ImageInfo::makeWH(expect_size.x, expect_size.y);\n    }\n  };\n\n  // If hasn't set, just choose a proper size.\n  if (render_info_.isEmpty()) {\n    chooser();\n    return;\n  }\n\n  // If expect_size enlarged, choose a new proper size.\n  if (expect_size.x > render_info_.width() ||\n      expect_size.y > render_info_.height() || force_use_original_size) {\n    int old_width = render_info_.width();\n    int old_height = render_info_.height();\n    chooser();\n    // If size changed, release graphics image.\n    if (old_width != render_info_.width() ||\n        old_height != render_info_.height()) {\n      gpu_image_.reset();\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/base_image.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_BASE_IMAGE_H_\n#define CLAY_GFX_IMAGE_BASE_IMAGE_H_\n\n#include <list>\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <utility>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/rect.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/base_image_instance.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n#include \"clay/gfx/image/image_info.h\"\n#include \"clay/gfx/image/platform_image.h\"\n\nnamespace clay {\nclass ImageFetcher;\n\nenum class ImageType {\n  kStatic,\n  kAnimated,\n  kSVG,\n};\n\nclass BaseImage : public std::enable_shared_from_this<BaseImage> {\n public:\n  virtual ~BaseImage() = default;\n\n  ImageType GetType() const { return type_; }\n  virtual int GetWidth() const { return orig_info_.width(); }\n  virtual int GetHeight() const { return orig_info_.height(); }\n  virtual void Upload(fml::RefPtr<GPUUnrefQueue> unref_queue, Size size) = 0;\n  fml::RefPtr<GraphicsImage> GetGraphicsImage() const {\n    return gpu_image_.object();\n  }\n  size_t GetGraphicsImageAllocSize() const {\n    return gpu_image_.object() ? gpu_image_.object()->width() *\n                                     gpu_image_.object()->height() * 4\n                               : 0;\n  }\n\n  void SetExpectSizeCalculator(\n      std::function<skity::Vec2(skity::Vec2)> calculator,\n      bool force_use_original_size);\n  const std::string& GetUrl() const { return url_; }\n  const std::string& GetCacheIdentifier() const { return cache_identifier_; }\n  void SetCacheIdentifier(const std::string& cache_identifier) {\n    cache_identifier_ = cache_identifier;\n  }\n\n  bool IsSVG() const { return type_ == ImageType::kSVG; }\n\n  std::unique_ptr<BaseImageInstance> NewInstance();\n  void OnInstanceCreated(BaseImageInstance* instance);\n  void OnInstanceDestroyed(BaseImageInstance* instance);\n\n protected:\n  fml::WeakPtr<ImageFetcher> image_fetcher_;\n  std::unordered_set<BaseImageInstance*> instances_;\n  std::string url_;\n  // For Data images, this is the MD5 of the Data content. For other images,\n  // this is the trimmed URL.\n  std::string cache_identifier_;\n  ImageType type_ = ImageType::kStatic;\n  std::shared_ptr<PlatformImage> image_;\n  GPUObject<GraphicsImage> gpu_image_;\n  ImageInfo render_info_;\n  ImageInfo orig_info_;\n};\n\n}  // namespace clay\n#endif  // CLAY_GFX_IMAGE_BASE_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/image/base_image_instance.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/base_image_instance.h\"\n\n#include \"clay/gfx/image/base_image.h\"\n\nnamespace clay {\n\nBaseImageInstance::BaseImageInstance(std::shared_ptr<BaseImage> image)\n    : image_(image) {\n  image_->OnInstanceCreated(this);\n}\n\nBaseImageInstance::BaseImageInstance(const BaseImageInstance& other)\n    : image_(other.image_),\n      animation_frame_callback_(other.animation_frame_callback_) {\n  if (image_) {\n    image_->OnInstanceCreated(this);\n  }\n}\n\nBaseImageInstance::~BaseImageInstance() { image_->OnInstanceDestroyed(this); }\n\nint BaseImageInstance::GetWidth() const { return image_->GetWidth(); }\n\nint BaseImageInstance::GetHeight() const { return image_->GetHeight(); }\n\nsize_t BaseImageInstance::GetGraphicsImageAllocSize() const {\n  return image_->GetGraphicsImageAllocSize();\n}\n\nfml::RefPtr<GraphicsImage> BaseImageInstance::GetGraphicsImage() const {\n  return image_->GetGraphicsImage();\n}\n\nvoid BaseImageInstance::SetAnimationFrameCallback(std::function<void()> func) {\n  animation_frame_callback_ = std::move(func);\n}\n\nvoid BaseImageInstance::OnNotifyAnimationFrame() {\n  if (animation_frame_callback_) {\n    animation_frame_callback_();\n  }\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/base_image_instance.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_BASE_IMAGE_INSTANCE_H_\n#define CLAY_GFX_IMAGE_BASE_IMAGE_INSTANCE_H_\n\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/image/graphics_image.h\"\n\nnamespace clay {\nclass BaseImage;\n\nclass BaseImageInstance {\n public:\n  explicit BaseImageInstance(std::shared_ptr<BaseImage> image);\n  BaseImageInstance(const BaseImageInstance& other);\n  BaseImageInstance& operator=(const BaseImageInstance& other) = delete;\n  ~BaseImageInstance();\n\n  std::shared_ptr<BaseImage> GetImage() const { return image_; }\n  int GetWidth() const;\n  int GetHeight() const;\n  size_t GetGraphicsImageAllocSize() const;\n  fml::RefPtr<GraphicsImage> GetGraphicsImage() const;\n\n  void SetAnimationFrameCallback(std::function<void()> func);\n\n  void OnNotifyAnimationFrame();\n\n protected:\n  std::shared_ptr<BaseImage> image_;\n  std::function<void()> animation_frame_callback_;\n};\n\n}  // namespace clay\n#endif  // CLAY_GFX_IMAGE_BASE_IMAGE_INSTANCE_H_\n"
  },
  {
    "path": "clay/gfx/image/build.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\ngfx_image_sources = [\n  \"codec.h\",\n  \"decode_priority.h\",\n  \"decoding_image.h\",\n  \"frame_info.h\",\n  \"graphics_image.cc\",\n  \"graphics_image.h\",\n  \"image.cc\",\n  \"image.h\",\n  \"image_data.h\",\n  \"image_data_cache.cc\",\n  \"image_data_cache.h\",\n  \"image_decoder.cc\",\n  \"image_decoder.h\",\n  \"image_descriptor.cc\",\n  \"image_descriptor.h\",\n  \"image_produce_context.h\",\n  \"image_producer.cc\",\n  \"image_producer.h\",\n  \"image_resource.cc\",\n  \"image_resource.h\",\n  \"image_resource_client.h\",\n  \"image_upload_manager.cc\",\n  \"image_upload_manager.h\",\n  \"platform_image.h\",\n  \"skimage_holder.cc\",\n  \"skimage_holder.h\",\n]\n\nif (enable_skity) {\n  gfx_image_sources += [\n    \"animated_image.cc\",\n    \"animated_image.h\",\n    \"base_image.cc\",\n    \"base_image.h\",\n    \"base_image_instance.cc\",\n    \"base_image_instance.h\",\n    \"graphics_image_skity.cc\",\n    \"graphics_image_skity.h\",\n    \"graphics_image_skity_lazy.cc\",\n    \"graphics_image_skity_lazy.h\",\n    \"image_info.h\",\n    \"static_image.cc\",\n    \"static_image.h\",\n    \"svg_image.cc\",\n    \"svg_image.h\",\n  ]\n} else {\n  gfx_image_sources += [\n    \"decoding_image.cc\",\n    \"decoding_image.h\",\n    \"graphics_image_skia.cc\",\n    \"graphics_image_skia.h\",\n    \"graphics_image_skia_lazy.cc\",\n    \"graphics_image_skia_lazy.h\",\n    \"multi_frame_codec.cc\",\n    \"multi_frame_codec.h\",\n    \"single_frame_codec.cc\",\n    \"single_frame_codec.h\",\n  ]\n}\n\nif (use_platform_decode) {\n  gfx_image_sources += [\n    \"image_descriptor_platform.cc\",\n    \"image_descriptor_platform.h\",\n  ]\n} else {\n  gfx_image_sources += [\n    \"image_descriptor_skia.cc\",\n    \"image_descriptor_skia.h\",\n  ]\n}\n\nif (enable_svg) {\n  gfx_image_sources += [\n    \"svg_image_holder.cc\",\n    \"svg_image_holder.h\",\n  ]\n}\n"
  },
  {
    "path": "clay/gfx/image/codec.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_CODEC_H_\n#define CLAY_GFX_IMAGE_CODEC_H_\n\n#include <functional>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/gfx/image/frame_info.h\"\n\nnamespace clay {\n\ntypedef std::function<void(FrameInfo)> CodecCallback;\n\n// A handle to an SkCodec object.\n//\n// Doesn't mirror SkCodec's API but provides a simple sequential access API.\nclass Codec : public fml::RefCountedThreadSafe<Codec> {\n public:\n  virtual ~Codec() = default;\n  virtual int FrameCount() const = 0;\n\n  virtual void NextFrame(const CodecCallback& callback) = 0;\n\n  virtual int FrameDuration(int index) const { return -1; }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_CODEC_H_\n"
  },
  {
    "path": "clay/gfx/image/decode_priority.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_DECODE_PRIORITY_H_\n#define CLAY_GFX_IMAGE_DECODE_PRIORITY_H_\n\nenum class DecodePriority {\n  kPending,    // Do not trigger decode\n  kDeferred,   // Trigger decode later\n  kImmediate,  // Trigger decode immediately\n};\n\n#endif  // CLAY_GFX_IMAGE_DECODE_PRIORITY_H_\n"
  },
  {
    "path": "clay/gfx/image/decoding_image.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/decoding_image.h\"\n\n#include <utility>\n\nnamespace clay {\n\nDecodingImage::DecodingImage(std::shared_ptr<Image> image)\n    : GraphicsImageSkia(nullptr), raw_image_(image) {}\n\nDecodingImage::~DecodingImage() {\n  if (image_resource_) {\n    image_resource_->RemoveImageResourceClient(this);\n  }\n}\n\nint DecodingImage::GetWidth() const { return raw_image_->GetWidth(); }\n\nint DecodingImage::GetHeight() const { return raw_image_->GetHeight(); }\n\nskity::Vec2 DecodingImage::dimensions() const {\n  return skity::Vec2(GetWidth(), GetHeight());\n}\n\nvoid DecodingImage::ScheduleDecodeAndUpload(\n    const LazyImageDecodeCallback& callback) {\n  callback_ = callback;\n  if (!image_resource_) {\n    image_resource_ = raw_image_->GetAccessor(true);\n    image_resource_->AddImageResourceClient(this);\n  }\n  if (auto g_image = image_resource_->GetImage()->GetGraphicsImage()) {\n    if (auto sk_image = g_image->gr_image()) {\n      image_ = g_image->gr_image();\n    }\n  }\n}\n\nbool DecodingImage::MaybeAnimated() const {\n  return raw_image_->MaybeAnimated();\n}\n\nvoid DecodingImage::DecodeImageFinish(bool success, const std::string& url) {\n  // Save the latest decoded image, if decoding fails, continue to use the\n  // previous frame or empty.\n  if (auto g_image = image_resource_->GetImage()->GetGraphicsImage()) {\n    if (auto sk_image = g_image->gr_image()) {\n      image_ = g_image->gr_image();\n    }\n  }\n  // Decoding is finished, notify Rasterizer to redraw LayerTree.\n  if (callback_) {\n    callback_(success);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/decoding_image.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_DECODING_IMAGE_H_\n#define CLAY_GFX_IMAGE_DECODING_IMAGE_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n\n#include \"clay/gfx/image/graphics_image_skia.h\"\n#include \"clay/gfx/image/image.h\"\n#include \"clay/gfx/image/image_resource.h\"\n#include \"clay/gfx/image/image_resource_client.h\"\n\nnamespace clay {\n\nclass DecodingImage final : public GraphicsImageSkia,\n                            public ImageResourceClient {\n public:\n  explicit DecodingImage(std::shared_ptr<Image> image);\n  ~DecodingImage() override;\n\n  int GetWidth() const;\n  int GetHeight() const;\n\n private:\n  void ScheduleDecodeAndUpload(\n      const LazyImageDecodeCallback& callback) override;\n\n  bool MaybeAnimated() const override;\n\n  skity::Vec2 dimensions() const override;\n  bool WillRenderImage() override { return true; }\n  void RequestRenderImage(ImageResource* image_resource,\n                          bool success) override {}\n  void OnImageChanged() override {}\n  void DecodeImageFinish(bool success, const std::string& url) override;\n\n  LazyImageDecodeCallback callback_;\n  std::shared_ptr<Image> raw_image_ = nullptr;\n  std::unique_ptr<ImageResource> image_resource_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(DecodingImage);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_DECODING_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/image/frame_info.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_FRAME_INFO_H_\n#define CLAY_GFX_IMAGE_FRAME_INFO_H_\n\n#include \"clay/gfx/image/graphics_image.h\"\n\nnamespace clay {\n\n// Information for a single frame of an animation.\nstruct FrameInfo {\n  // The [GraphicsImage] object for this frame.\n  fml::RefPtr<GraphicsImage> image;\n\n  // The duration this frame should be shown.\n  int duration = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_FRAME_INFO_H_\n"
  },
  {
    "path": "clay/gfx/image/graphics_image.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/graphics_image.h\"\n\n#include <utility>\n\n#include \"clay/gfx/gfx_rendering_backend.h\"\n\nnamespace clay {\n\nfml::RefPtr<GraphicsImage> GraphicsImage::MakeLazy(\n    std::shared_ptr<Image> image) {\n#ifndef ENABLE_SKITY\n  return fml::MakeRefCounted<GraphicsImageSkiaLazy>(image);\n#else\n  return fml::MakeRefCounted<GraphicsImageSkityLazy>(image);\n#endif\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<GraphicsImage> GraphicsImage::Make(const SkImage* image) {\n  return Make(sk_ref_sp(image));\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImage::Make(sk_sp<SkImage> image) {\n  return fml::MakeRefCounted<GraphicsImageSkia>(std::move(image));\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImage::MakeFromBitmap(\n    const SkBitmap& bitmap) {\n  return fml::MakeRefCounted<GraphicsImageSkia>(\n      SkImages::RasterFromBitmap(bitmap));\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImage::MakeRasterData(\n    const SkImageInfo& info, sk_sp<SkData> pixels, size_t rowBytes) {\n  return fml::MakeRefCounted<GraphicsImageSkia>(\n      SkImages::RasterFromData(info, pixels, rowBytes));\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImage::MakeRasterCopy(\n    const SkPixmap& pixmap) {\n  return fml::MakeRefCounted<GraphicsImageSkia>(\n      SkImages::RasterFromPixmapCopy(pixmap));\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImage::MakePromiseTexture(\n    sk_sp<GrContextThreadSafeProxy> gpuContextProxy,\n    const GrBackendFormat& backendFormat, SkISize dimensions,\n    GrMipmapped mipMapped, GrSurfaceOrigin origin, SkColorType colorType,\n    SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace,\n    SkImages::PromiseImageTextureFulfillProc textureFulfillProc,\n    SkImages::PromiseImageTextureReleaseProc textureReleaseProc,\n    SkImages::PromiseImageTextureContext textureContext) {\n  return fml::MakeRefCounted<GraphicsImageSkia>(SkImages::PromiseTextureFrom(\n      gpuContextProxy, backendFormat, dimensions, mipMapped, origin, colorType,\n      alphaType, colorSpace, textureFulfillProc, textureReleaseProc,\n      textureContext));\n}\n#else\nfml::RefPtr<GraphicsImage> GraphicsImage::Make(\n    std::shared_ptr<skity::Image> image) {\n  return fml::MakeRefCounted<GraphicsImageSkity>(std::move(image));\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImage::MakeFromBitmap(\n    const skity::Bitmap& bitmap) {\n  return fml::MakeRefCounted<GraphicsImageSkity>(\n      skity::Image::MakeImage(bitmap.GetPixmap()));\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImage::MakeRasterData(\n    const ImageInfo& info, std::shared_ptr<skity::Data> pixels,\n    size_t rowBytes) {\n  auto skity_pixmap = std::make_shared<skity::Pixmap>(\n      pixels, rowBytes, info.width(), info.height());\n  return fml::MakeRefCounted<GraphicsImageSkity>(\n      skity::Image::MakeImage(skity_pixmap));\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImage::MakePromiseTexture(\n    const skity::TextureFormat& texture_format, size_t width, size_t height,\n    skity::AlphaType alpha_type, skity::GetPromiseTexture texture_fulfill_proc,\n    skity::ReleaseCallback texture_release_proc,\n    skity::PromiseTextureContext texture_context) {\n  return fml::MakeRefCounted<GraphicsImageSkity>(\n      skity::Image::MakePromiseTextureImage(\n          texture_format, width, height, alpha_type, texture_fulfill_proc,\n          texture_release_proc, texture_context));\n}\n#endif  // ENABLE_SKITY\n\nGraphicsImage::GraphicsImage() = default;\n\nGraphicsImage::~GraphicsImage() = default;\n\nint GraphicsImage::width() const { return dimensions().x; }\n\nint GraphicsImage::height() const { return dimensions().y; }\n\nskity::Rect GraphicsImage::bounds() const {\n  return skity::Rect::MakeSize(dimensions());\n}\n\nstd::optional<std::string> GraphicsImage::get_error() const {\n  return std::nullopt;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/graphics_image.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_GRAPHICS_IMAGE_H_\n#define CLAY_GFX_IMAGE_GRAPHICS_IMAGE_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/image/image_info.h\"\n#include \"clay/gfx/paint_decoding_image.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/image_filter.h\"\n#include \"skity/geometry/vector.hpp\"\n\nnamespace clay {\n\nclass Image;\n\n//------------------------------------------------------------------------------\n/// @brief      Represents an image whose allocation is (usually) resident on\n///             device memory.\n///\n///             Since it is usually impossible or expensive to transmute images\n///             for one rendering backend to another, these objects are backend\n///             specific.\n///\nclass GraphicsImage : public PaintDecodingImage {\n public:\n  // Describes which GPU context owns this image.\n  enum class OwningContext { kRaster, kIO };\n\n  static fml::RefPtr<GraphicsImage> MakeLazy(std::shared_ptr<Image> image);\n\n#ifndef ENABLE_SKITY\n  static fml::RefPtr<GraphicsImage> Make(const SkImage* image);\n\n  static fml::RefPtr<GraphicsImage> Make(sk_sp<SkImage> image);\n\n  static fml::RefPtr<GraphicsImage> MakeFromBitmap(const SkBitmap& bitmap);\n\n  static fml::RefPtr<GraphicsImage> MakeRasterData(const SkImageInfo& info,\n                                                   sk_sp<SkData> pixels,\n                                                   size_t rowBytes);\n\n  static fml::RefPtr<GraphicsImage> MakeRasterCopy(const SkPixmap& pixmap);\n\n  static fml::RefPtr<GraphicsImage> MakePromiseTexture(\n      sk_sp<GrContextThreadSafeProxy> gpuContextProxy,\n      const GrBackendFormat& backendFormat, SkISize dimensions,\n      GrMipmapped mipMapped, GrSurfaceOrigin origin, SkColorType colorType,\n      SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace,\n      SkImages::PromiseImageTextureFulfillProc textureFulfillProc,\n      SkImages::PromiseImageTextureReleaseProc textureReleaseProc,\n      SkImages::PromiseImageTextureContext textureContext);\n#else\n  static fml::RefPtr<GraphicsImage> Make(std::shared_ptr<skity::Image> image);\n  static fml::RefPtr<GraphicsImage> MakeFromBitmap(const skity::Bitmap& bitmap);\n  static fml::RefPtr<GraphicsImage> MakeRasterData(\n      const ImageInfo& info, std::shared_ptr<skity::Data> pixels,\n      size_t rowBytes);\n  static fml::RefPtr<GraphicsImage> MakePromiseTexture(\n      const skity::TextureFormat& texture_format, size_t width, size_t height,\n      skity::AlphaType alpha_type,\n      skity::GetPromiseTexture texture_fulfill_proc,\n      skity::ReleaseCallback texture_release_proc,\n      skity::PromiseTextureContext texture_context);\n#endif  // ENABLE_SKITY\n\n  ~GraphicsImage() override;\n\n  //----------------------------------------------------------------------------\n  /// @brief      If the pixel format of this image ignores alpha, this returns\n  ///             true. This method might conservatively return false when it\n  ///             cannot guarantee an opaque image, for example when the pixel\n  ///             format of the image supports alpha but the image is made up of\n  ///             entirely opaque pixels.\n  ///\n  /// @return     True if the pixel format of this image ignores alpha.\n  ///\n  virtual bool isOpaque() const = 0;\n\n  virtual bool isTextureBacked() const = 0;\n\n  //----------------------------------------------------------------------------\n  /// @return     The dimensions of the pixel grid.\n  ///\n  virtual skity::Vec2 dimensions() const = 0;\n\n  //----------------------------------------------------------------------------\n  /// @return     The approximate byte size of the allocation of this image.\n  ///             This takes into account details such as mip-mapping. The\n  ///             allocation is usually resident in device memory.\n  ///\n  virtual size_t GetApproximateByteSize() const = 0;\n\n#ifndef ENABLE_SKITY\n  virtual fml::RefPtr<GraphicsImage> makeWithFilter(GrRecordingContext* context,\n                                                    const ImageFilter* filter,\n                                                    const SkIRect& subset,\n                                                    const SkIRect& clipBounds,\n                                                    SkIRect* outSubset,\n                                                    SkIPoint* offset) const = 0;\n\n  virtual bool peekPixels(SkPixmap* pixmap) const = 0;\n\n  virtual fml::RefPtr<GraphicsImage> makeTextureImage(\n      GrDirectContext*, GrMipmapped = GrMipmapped::kNo,\n      skgpu::Budgeted = skgpu::Budgeted::kYes) const = 0;\n\n  virtual fml::RefPtr<GraphicsImage> makeRasterImage(\n      SkImage::CachingHint cachingHint =\n          SkImage::CachingHint::kDisallow_CachingHint) const = 0;\n\n  virtual void flushAndSubmit(GrDirectContext*) = 0;\n\n  virtual bool scalePixels(\n      const SkPixmap& dst, const SkSamplingOptions&,\n      SkImage::CachingHint cachingHint = SkImage::kAllow_CachingHint) const = 0;\n\n  virtual const SkImageInfo& imageInfo() const = 0;\n#else\n  virtual fml::RefPtr<GraphicsImage> makeWithFilter(\n      skity::GPUContext* context, const ImageFilter* filter,\n      const skity::Rect& subset, const skity::Rect& clipBounds,\n      skity::Rect* outSubset, GrPoint* offset) const = 0;\n\n  virtual std::shared_ptr<skity::Pixmap> peekPixels() const = 0;\n\n  virtual fml::RefPtr<GraphicsImage> makeRasterImage() const = 0;\n\n  virtual const ImageInfo& imageInfo() const = 0;\n\n  virtual fml::RefPtr<GraphicsImage> makeTextureImage(\n      skity::GPUContext*) const {\n    return nullptr;\n  }\n\n  virtual void flushAndSubmit(skity::GPUContext*) {}\n\n  virtual bool scalePixels(\n      std::shared_ptr<skity::Pixmap> dst, skity::GPUContext* context,\n      const skity::SamplingOptions& sampling_options) const = 0;\n#endif  // ENABLE_SKITY\n\n  //----------------------------------------------------------------------------\n  /// @return     The width of the pixel grid. A convenience method that\n  /// calls\n  ///             |GraphicsImage::dimensions|.\n  ///\n  int width() const;\n\n  //----------------------------------------------------------------------------\n  /// @return     The height of the pixel grid. A convenience method that calls\n  ///             |GraphicsImage::dimensions|.\n  ///\n  int height() const;\n\n  //----------------------------------------------------------------------------\n  /// @return     The bounds of the pixel grid with 0, 0 as origin. A\n  ///             convenience method that calls |GraphicsImage::dimensions|.\n  ///\n  skity::Rect bounds() const;\n\n  //----------------------------------------------------------------------------\n  /// @return     Specifies which context was used to create this image. The\n  ///             image must be collected on the same task runner as its\n  ///             context.\n  virtual OwningContext owning_context() const { return OwningContext::kIO; }\n\n  //----------------------------------------------------------------------------\n  /// @return     An error, if any, that occurred when trying to create the\n  ///             image.\n  virtual std::optional<std::string> get_error() const;\n\n  virtual bool IsLazyImage() const { return false; }\n\n  bool Equals(const GraphicsImage* other) const {\n    if (!other) {\n      return false;\n    }\n    if (this == other) {\n      return true;\n    }\n    return gr_image() == other->gr_image();\n  }\n\n  bool Equals(const GraphicsImage& other) const { return Equals(&other); }\n\n  bool Equals(fml::RefPtr<const GraphicsImage> other) const {\n    return Equals(other.get());\n  }\n\n protected:\n  GraphicsImage();\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_GRAPHICS_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/image/graphics_image_skia.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/graphics_image_skia.h\"\n\n#include <utility>\n\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n\nnamespace clay {\n\nGraphicsImageSkia::GraphicsImageSkia(sk_sp<SkImage> image)\n    : image_(std::move(image)) {}\n\n// |GraphicsImage|\nGraphicsImageSkia::~GraphicsImageSkia() = default;\n\n// |GraphicsImage|\nsk_sp<SkImage> GraphicsImageSkia::gr_image() const { return image_; }\n\n// |GraphicsImage|\nbool GraphicsImageSkia::isOpaque() const {\n  return image_ ? image_->isOpaque() : false;\n}\n\n// |GraphicsImage|\nbool GraphicsImageSkia::isTextureBacked() const {\n  return image_ ? image_->isTextureBacked() : false;\n}\n\n// |GraphicsImage|\nskity::Vec2 GraphicsImageSkia::dimensions() const {\n  return image_ ? skity::Vec2{image_->dimensions().width(),\n                              image_->dimensions().height()}\n                : skity::Vec2{0, 0};\n}\n\n// |GraphicsImage|\nsize_t GraphicsImageSkia::GetApproximateByteSize() const {\n  auto size = sizeof(this);\n  if (image_) {\n    const auto& info = image_->imageInfo();\n    const auto kMipmapOverhead = 4.0 / 3.0;\n    const size_t image_byte_size = info.computeMinByteSize() * kMipmapOverhead;\n    size += image_byte_size;\n  }\n  return size;\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImageSkia::makeWithFilter(\n    GrRecordingContext* context, const ImageFilter* filter,\n    const SkIRect& subset, const SkIRect& clipBounds, SkIRect* outSubset,\n    SkIPoint* offset) const {\n  return image_ ? GraphicsImage::Make(image_->makeWithFilter(\n                      context, filter->gr_object().get(), subset, clipBounds,\n                      outSubset, offset))\n                : nullptr;\n}\n\nbool GraphicsImageSkia::peekPixels(SkPixmap* pixmap) const {\n  return image_ ? image_->peekPixels(pixmap) : false;\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImageSkia::makeTextureImage(\n    GrDirectContext* dContext, GrMipmapped mipmapped,\n    skgpu::Budgeted budgeted) const {\n  return image_ ? GraphicsImage::Make(SkImages::TextureFromImage(\n                      dContext, image_, mipmapped, budgeted))\n                : nullptr;\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImageSkia::makeRasterImage(\n    SkImage::CachingHint cachingHint) const {\n  return image_ ? GraphicsImage::Make(image_->makeRasterImage(cachingHint))\n                : nullptr;\n}\n\nconst SkImageInfo& GraphicsImageSkia::imageInfo() const {\n  static const SkImageInfo default_image_info = SkImageInfo();\n  return image_ ? image_->imageInfo() : default_image_info;\n}\n\nvoid GraphicsImageSkia::flushAndSubmit(GrDirectContext* dContext) {\n  if (image_) {\n    dContext->flushAndSubmit(image_);\n  }\n}\n\nbool GraphicsImageSkia::scalePixels(const SkPixmap& dst,\n                                    const SkSamplingOptions& sampling,\n                                    SkImage::CachingHint cachingHint) const {\n  return image_ ? image_->scalePixels(dst, sampling, cachingHint) : false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/graphics_image_skia.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKIA_H_\n#define CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKIA_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n\nnamespace clay {\n\nclass GraphicsImageSkia : public GraphicsImage {\n public:\n  explicit GraphicsImageSkia(sk_sp<SkImage> image);\n\n  // |GraphicsImage|\n  ~GraphicsImageSkia() override;\n\n  // |GraphicsImage|\n  sk_sp<SkImage> gr_image() const override;\n\n  // |GraphicsImage|\n  bool isOpaque() const override;\n\n  // |GraphicsImage|\n  bool isTextureBacked() const override;\n\n  // |GraphicsImage|\n  skity::Vec2 dimensions() const override;\n\n  // |GraphicsImage|\n  size_t GetApproximateByteSize() const override;\n\n  fml::RefPtr<GraphicsImage> makeWithFilter(GrRecordingContext* context,\n                                            const ImageFilter* filter,\n                                            const SkIRect& subset,\n                                            const SkIRect& clipBounds,\n                                            SkIRect* outSubset,\n                                            SkIPoint* offset) const override;\n\n  bool peekPixels(SkPixmap* pixmap) const override;\n\n  fml::RefPtr<GraphicsImage> makeTextureImage(\n      GrDirectContext*, GrMipmapped = GrMipmapped::kNo,\n      skgpu::Budgeted = skgpu::Budgeted::kYes) const override;\n\n  fml::RefPtr<GraphicsImage> makeRasterImage(\n      SkImage::CachingHint cachingHint =\n          SkImage::CachingHint::kDisallow_CachingHint) const override;\n\n  const SkImageInfo& imageInfo() const override;\n\n  void flushAndSubmit(GrDirectContext*) override;\n\n  bool scalePixels(const SkPixmap& dst, const SkSamplingOptions&,\n                   SkImage::CachingHint cachingHint =\n                       SkImage::kAllow_CachingHint) const override;\n\n protected:\n  sk_sp<SkImage> image_;\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(GraphicsImageSkia);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKIA_H_\n"
  },
  {
    "path": "clay/gfx/image/graphics_image_skia_lazy.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/graphics_image_skia_lazy.h\"\n\nnamespace clay {\n\nGraphicsImageSkiaLazy::GraphicsImageSkiaLazy(std::shared_ptr<Image> image)\n    : GraphicsImageSkia(nullptr),\n      decoding_image_(fml::MakeRefCounted<DecodingImage>(image)) {}\n\nGraphicsImageSkiaLazy::~GraphicsImageSkiaLazy() {}\n\nskity::Vec2 GraphicsImageSkiaLazy::dimensions() const {\n  return skity::Vec2(decoding_image_->GetWidth(), decoding_image_->GetHeight());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/graphics_image_skia_lazy.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKIA_LAZY_H_\n#define CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKIA_LAZY_H_\n\n#include <functional>\n#include <memory>\n\n#include \"clay/gfx/image/decoding_image.h\"\n#include \"clay/gfx/image/graphics_image_skia.h\"\n#include \"clay/gfx/image/image.h\"\n\nnamespace clay {\n\nclass GraphicsImageSkiaLazy final : public GraphicsImageSkia {\n public:\n  explicit GraphicsImageSkiaLazy(std::shared_ptr<Image> image);\n  ~GraphicsImageSkiaLazy() override;\n\n  fml::RefPtr<DecodingImage> decoding_image() const { return decoding_image_; }\n\n  bool IsLazyImage() const override { return true; }\n\n private:\n  skity::Vec2 dimensions() const override;\n\n  fml::RefPtr<DecodingImage> decoding_image_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(GraphicsImageSkiaLazy);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKIA_LAZY_H_\n"
  },
  {
    "path": "clay/gfx/image/graphics_image_skity.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/graphics_image_skity.h\"\n\n#include <utility>\n\n#include \"skity/geometry/rect.hpp\"\n#include \"skity/graphic/alpha_type.hpp\"\n#include \"skity/graphic/image.hpp\"\n#include \"skity/include/skity/geometry/vector.hpp\"\n\nnamespace clay {\n\nGraphicsImageSkity::GraphicsImageSkity(std::shared_ptr<skity::Image> image)\n    : image_(std::move(image)) {\n  if (image_) {\n    image_info_ = ImageInfo::makeWH(image_->Width(), image_->Height());\n  }\n}\n\n// |GraphicsImage|\nGraphicsImageSkity::~GraphicsImageSkity() = default;\n\nstd::shared_ptr<skity::Image> GraphicsImageSkity::gr_image() const {\n  return image_;\n}\n\n// |GraphicsImage|\nbool GraphicsImageSkity::isOpaque() const {\n  return image_ ? image_->GetAlphaType() == skity::AlphaType::kOpaque_AlphaType\n                : false;\n}\n\n// |GraphicsImage|\nbool GraphicsImageSkity::isTextureBacked() const {\n  return image_ ? image_->IsTextureBackend() : false;\n}\n\n// |GraphicsImage|\nskity::Vec2 GraphicsImageSkity::dimensions() const {\n  return image_ ? skity::Vec2(image_->Width(), image_->Height())\n                : skity::Vec2{0, 0};\n}\n\n// |GraphicsImage|\nsize_t GraphicsImageSkity::GetApproximateByteSize() const {\n  auto size = sizeof(this);\n  if (image_) {\n    const size_t image_byte_size =\n        width() * height() * imageInfo().bytesPerPixel();\n    size += image_byte_size;\n  }\n  return size;\n}\n\nconst ImageInfo& GraphicsImageSkity::imageInfo() const {\n  static const ImageInfo default_image_info = ImageInfo();\n  return image_ ? image_info_ : default_image_info;\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImageSkity::makeWithFilter(\n    skity::GPUContext* context, const ImageFilter* filter,\n    const skity::Rect& subset, const skity::Rect& clipBounds,\n    skity::Rect* outSubset, GrPoint* offset) const {\n  // TODO(zhangxiao.ninja) implement later\n  FML_UNIMPLEMENTED()\n  return nullptr;\n}\n\nstd::shared_ptr<skity::Pixmap> GraphicsImageSkity::peekPixels() const {\n  return image_ ? *image_->GetPixmap() : nullptr;\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImageSkity::makeRasterImage() const {\n  // TODO(zhangxiao.ninja) implement later\n  FML_UNIMPLEMENTED()\n  return nullptr;\n}\n\nfml::RefPtr<GraphicsImage> GraphicsImageSkity::makeTextureImage(\n    skity::GPUContext* context) const {\n  return image_ ? GraphicsImage::Make(\n                      skity::Image::MakeImage(*image_->GetPixmap(), context))\n                : nullptr;\n}\n\nbool GraphicsImageSkity::scalePixels(\n    std::shared_ptr<skity::Pixmap> dst, skity::GPUContext* context,\n    const GrSamplingOptions& sampling_options) const {\n  return image_ ? image_->ScalePixels(dst, context, sampling_options) : false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/graphics_image_skity.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKITY_H_\n#define CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKITY_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n#include \"clay/gfx/image/image_info.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nclass GraphicsImageSkity : public GraphicsImage {\n public:\n  explicit GraphicsImageSkity(std::shared_ptr<skity::Image> image);\n\n  // |GraphicsImage|\n  ~GraphicsImageSkity() override;\n\n  std::shared_ptr<skity::Image> gr_image() const override;\n\n  // |GraphicsImage|\n  bool isOpaque() const override;\n\n  // |GraphicsImage|\n  bool isTextureBacked() const override;\n\n  // |GraphicsImage|\n  skity::Vec2 dimensions() const override;\n\n  // |GraphicsImage|\n  size_t GetApproximateByteSize() const override;\n\n  // |GraphicsImage|\n  const ImageInfo& imageInfo() const override;\n\n  // |GraphicsImage|\n  fml::RefPtr<GraphicsImage> makeWithFilter(skity::GPUContext* context,\n                                            const ImageFilter* filter,\n                                            const skity::Rect& subset,\n                                            const skity::Rect& clipBounds,\n                                            skity::Rect* outSubset,\n                                            GrPoint* offset) const override;\n\n  std::shared_ptr<skity::Pixmap> peekPixels() const override;\n\n  // |GraphicsImage|\n  fml::RefPtr<GraphicsImage> makeRasterImage() const override;\n\n  fml::RefPtr<GraphicsImage> makeTextureImage(\n      skity::GPUContext* context) const override;\n\n  bool scalePixels(std::shared_ptr<skity::Pixmap> dst,\n                   skity::GPUContext* context,\n                   const GrSamplingOptions& sampling_options) const override;\n\n protected:\n  std::shared_ptr<skity::Image> image_;\n\n private:\n  ImageInfo image_info_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(GraphicsImageSkity);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKITY_H_\n"
  },
  {
    "path": "clay/gfx/image/graphics_image_skity_lazy.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/graphics_image_skity_lazy.h\"\n\n#include \"skity/include/skity/geometry/vector.hpp\"\n\nnamespace clay {\n\nGraphicsImageSkityLazy::GraphicsImageSkityLazy(std::shared_ptr<Image> image)\n    : GraphicsImageSkity(nullptr),\n      decoding_image_(std::make_shared<SkityDecodingImage>(image)) {}\n\nGraphicsImageSkityLazy::~GraphicsImageSkityLazy() = default;\n\nskity::Vec2 GraphicsImageSkityLazy::dimensions() const {\n  return skity::Vec2(decoding_image_->GetWidth(), decoding_image_->GetHeight());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/graphics_image_skity_lazy.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKITY_LAZY_H_\n#define CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKITY_LAZY_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/image/graphics_image_skity.h\"\n#include \"clay/gfx/image/image.h\"\n#include \"clay/gfx/skity/skity_decoding_image.h\"\n\nnamespace clay {\n\nclass GraphicsImageSkityLazy final : public GraphicsImageSkity {\n public:\n  explicit GraphicsImageSkityLazy(std::shared_ptr<clay::Image> image);\n  ~GraphicsImageSkityLazy() override;\n\n  std::shared_ptr<SkityDecodingImage> decoding_image() const {\n    return decoding_image_;\n  }\n\n  bool IsLazyImage() const override { return true; }\n\n private:\n  skity::Vec2 dimensions() const override;\n\n  std::shared_ptr<SkityDecodingImage> decoding_image_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(GraphicsImageSkityLazy);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_GRAPHICS_IMAGE_SKITY_LAZY_H_\n"
  },
  {
    "path": "clay/gfx/image/image.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image.h\"\n\n#include <algorithm>\n#include <limits>\n#include <memory>\n#include <utility>\n\n#include \"base/include/closure.h\"\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"clay/common/thread_host.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n#include \"clay/gfx/image/image_data_cache.h\"\n#include \"clay/gfx/image/image_producer.h\"\n#include \"clay/gfx/image/image_upload_manager.h\"\n#include \"clay/gfx/image/skimage_holder.h\"\n#include \"clay/shell/common/one_shot_callback.h\"\n\n#if defined(ENABLE_SVG)\n#include \"clay/gfx/image/svg_image_holder.h\"\n#endif\n\nnamespace clay {\n\nImage::Image(const std::string& url, GrDataPtr data,\n             std::weak_ptr<Image::Notifier> weak_notifier,\n             fml::RefPtr<fml::TaskRunner> ui_runner,\n             fml::RefPtr<fml::TaskRunner> raster_runner, bool is_svg,\n             bool is_promise, bool enable_low_quality_image,\n             bool use_texture_backend, bool is_deferred,\n             bool decode_with_priority)\n    : url_(url),\n      raw_data_(data),\n      is_svg_(lynx::base::EndsWith(url, \".svg\") || is_svg),\n      is_promise_(is_promise),\n      weak_notifier_(weak_notifier),\n      ui_runner_(ui_runner),\n      raster_runner_(raster_runner),\n      use_texture_backend_(use_texture_backend),\n      is_deferred_(is_deferred),\n      decode_with_priority_(decode_with_priority),\n      image_id_(NextUniqueId()) {\n  if (data) {\n    ImageDataCache::GetInstance().CacheImageData(url, data);\n  }\n}\n\nstd::shared_ptr<Image> Image::CreateImage(\n    const std::string& url, GrDataPtr data,\n    fml::RefPtr<ImageDescriptor> descriptor,\n    std::weak_ptr<Notifier> weak_notifier,\n    fml::RefPtr<fml::TaskRunner> ui_runner,\n    fml::RefPtr<fml::TaskRunner> raster_runner, bool is_svg,\n    bool use_texture_backend, bool is_promise, bool enable_low_quality_image,\n    bool is_deferred, bool decode_with_priority) {\n  // The resource context is set up in the raster thread. For non-promise\n  // images, texture creation also occurs in the raster thread. Therefore, it is\n  // sufficient to check the context only when the texture needs to be created.\n  // However, for non-deferred promise images, decoding is triggered on the UI\n  // thread. If the texture is used, it will be accessed from the context's\n  // threadSafeProxy on the UI thread. Hence, it is necessary to perform a check\n  // here first.\n  auto notifier = weak_notifier.lock();\n  if (!notifier || !notifier->GetUnrefQueue() ||\n      (is_promise && !is_deferred &&\n       !notifier->GetUnrefQueue()->GetContext())) {\n    use_texture_backend = false;\n  }\n\n  auto image =\n      new Image(url, data, weak_notifier, ui_runner, raster_runner, is_svg,\n                is_promise, enable_low_quality_image, use_texture_backend,\n                is_deferred, decode_with_priority);\n  std::shared_ptr<Image> ptr(image);\n\n  if (!notifier) {\n    image->image_producer_ = ImageProducer::CreateImageProducer(\n        image->is_svg_, data, descriptor, ImageProduceContext(), image->mutex_,\n        use_texture_backend, enable_low_quality_image);\n  } else {\n    std::function<void(bool)> decode_callback =\n        [weak = image->weak_from_this()](bool success) {\n          if (auto self = weak.lock()) {\n            self->DecodeFinish(success);\n            if (!self->IsSVG() && !self->decode_with_priority_) {\n              self->StartAnimation();\n            }\n          }\n        };\n    std::function<void(bool)> upload_callback = nullptr;\n    if (decode_with_priority) {\n      upload_callback = [weak = image->weak_from_this()](bool success) {\n        if (auto self = weak.lock()) {\n          self->UploadFinish(success);\n          if (!self->IsSVG()) {\n            self->StartAnimation();\n          }\n        }\n      };\n    }\n    std::function<void(const std::function<void()>&)> register_upload_callback =\n        [weak = image->weak_from_this()](const std::function<void()>& f) {\n          if (auto self = weak.lock()) {\n            self->WillRegisterUploadTask(std::move(f));\n          }\n        };\n    ImageProduceContext produce_context = {\n        .use_promise = is_promise,\n        .is_deferred = image->is_deferred_,\n        .decode_with_priority = image->decode_with_priority_,\n        .decode_callback = decode_callback,\n        .upload_callback = upload_callback,\n        .register_upload_callback = register_upload_callback,\n        .unref_queue = notifier->GetUnrefQueue(),\n        .ui_task_runner = ui_runner,\n        .raster_task_runner = raster_runner};\n    image->image_producer_ = ImageProducer::CreateImageProducer(\n        image->is_svg_, data, descriptor, produce_context, image->mutex_,\n        use_texture_backend, enable_low_quality_image);\n  }\n  return ptr;\n}\n\nstd::shared_ptr<Image> Image::CloneImage(std::shared_ptr<Image> image) {\n  if (!image) {\n    return nullptr;\n  }\n  return Image::CreateImage(image->url_, image->image_producer_->GetData(),\n#ifndef ENABLE_SKITY\n                            nullptr,\n#else\n                            image->image_producer_->GetDescriptor(),\n#endif  // ENABLE_SKITY\n                            image->weak_notifier_, image->ui_runner_,\n                            image->raster_runner_, image->is_svg_,\n                            image->use_texture_backend_, image->is_promise_,\n                            image->image_producer_->EnableLowQualityImage(),\n                            image->is_deferred_, image->decode_with_priority_);\n}\n\nImage::~Image() {\n  if (raw_data_) {\n    ImageDataCache::GetInstance().ReleaseOrArchiveImageData(url_);\n  }\n\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  ResetAnimation();\n}\n\n// If fetching an image by FetchPromiseImage, who has not been downloaded, an\n// ImageResource without image data will be return immediately, and a\n// asynchronous loading task will be post, then the image data will be put into\n// the ImageResource on UI thread when finishing the download.\nvoid Image::SetRawData(GrDataPtr data) {\n  FML_DCHECK(ui_runner_->RunsTasksOnCurrentThread());\n\n  // Image already has data(the image maybe already created by another\n  // Page).\n  if (raw_data_) {\n    // Equals with old data, do nothing.\n    if (memcmp(DATA_GET_DATA(raw_data_), DATA_GET_DATA(data),\n               DATA_GET_SIZE(data)) == 0) {\n      return;\n    } else {\n      // Data changed, remove old data.\n      ImageDataCache::GetInstance().RemoveImageDataIfExist(url_);\n    }\n  }\n\n  raw_data_ = data;\n  // Ref new data.\n  if (data) {\n    ImageDataCache::GetInstance().CacheImageData(url_, data);\n  }\n\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  ResetAnimation();\n  image_producer_->SetData(is_svg_, data);\n  for (auto accessor : ui_accessors_) {\n    accessor->AnimationAdvanced();\n  }\n}\n\nstd::unique_ptr<ImageResource> Image::GetAccessor(bool from_raster) {\n  if (!from_raster && !IsSingleFrameImage()) {\n    FML_DCHECK(ui_accessors_.empty());\n  }\n  return std::make_unique<ImageResource>(shared_from_this(), from_raster);\n}\n\nvoid Image::SetExpectSizeCalculator(\n    std::function<skity::Vec2(skity::Vec2)> calculator,\n    bool force_use_original_size) {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  image_producer_->SetExpectSizeCalculator(calculator, force_use_original_size);\n}\n\nvoid Image::AccessorCreated(ImageResource* accessor) {\n  if (accessor->OwnedByRaster()) {\n    if (raster_runner_) {\n      FML_DCHECK(raster_runner_->RunsTasksOnCurrentThread());\n    }\n    raster_accessors_.insert(accessor);\n  } else {\n    if (ui_runner_) {\n      FML_DCHECK(ui_runner_->RunsTasksOnCurrentThread());\n    }\n    ui_accessors_.insert(accessor);\n  }\n}\n\nvoid Image::AccessorDestroyed(ImageResource* accessor) {\n  if (accessor->OwnedByRaster()) {\n    if (raster_runner_) {\n      FML_DCHECK(raster_runner_->RunsTasksOnCurrentThread());\n    }\n    raster_accessors_.erase(accessor);\n  } else {\n    if (ui_runner_) {\n      FML_DCHECK(ui_runner_->RunsTasksOnCurrentThread());\n    }\n    ui_accessors_.erase(accessor);\n    if (ui_accessors_.empty()) {\n      // for multi frame image, need to reset animation when all ui accessors\n      // are destroyed, as we may cache the image.\n      if (!IsSingleFrameImage()) {\n        std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n        if (is_deferred_) {\n          lock =\n              std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n        }\n        ResetAnimation(true);\n      }\n\n      if (ui_runner_) {\n        // Image will be destructed if not cached.\n        // Sometimes an imageview is assigned a different url, and previous url\n        // will be set to another imageview. If release resource immediately,\n        // the other imageview need decode and make texture again, which is\n        // obviously redundant. To avoid that, just release resource in next\n        // runloop.\n        ui_runner_->PostTask([weak_self = weak_from_this()] {\n          auto self = weak_self.lock();\n          if (self && self->ui_accessors_.empty()) {\n            auto notifier = self->weak_notifier_.lock();\n            if (notifier) {\n              notifier->ImageHasNoAccessor(self.get());\n            }\n          }\n        });\n      }\n    }\n  }\n}\n\nfml::RefPtr<GraphicsImage> Image::GetGraphicsImage(DecodePriority priority) {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  if (decode_with_priority_) {\n    auto graphics_image = TryGetGraphicsImage();\n    if (graphics_image) {\n      return graphics_image;\n    }\n\n    switch (priority) {\n      case DecodePriority::kPending:\n        // Do not decode when priority is pending.\n        return nullptr;\n      case DecodePriority::kDeferred: {\n        // If image is multi frame, do not decode it in deferred priority.\n        if (!IsSingleFrameImage()) {\n          return nullptr;\n        }\n        auto deferred_decode_callback =\n            [](std::weak_ptr<Image> weak, fml::RefPtr<fml::TaskRunner> runner) {\n              runner->PostDelayedTask(\n                  [weak]() {\n                    if (auto image = weak.lock()) {\n                      if (!image->IsActive()) {\n                        return;\n                      }\n                      if (!image->NeedDecode()) {\n                        return;\n                      }\n                      // Check if this image is still in kDeferred/kImmediate\n                      // decode priority, otherwise there is no need to decode.\n                      // Only needs to check ui_accessors.\n                      bool should_decode = false;\n                      for (auto accessor : image->ui_accessors_) {\n                        for (auto client : accessor->GetClients()) {\n                          DecodePriority prior = client->GetDecodePriority();\n                          if (prior == DecodePriority::kImmediate ||\n                              prior == DecodePriority::kDeferred) {\n                            should_decode = true;\n                            break;\n                          }\n                        }\n                        if (should_decode) {\n                          break;\n                        }\n                      }\n                      if (should_decode) {\n                        image->GetGraphicsImage(DecodePriority::kImmediate);\n                      }\n                    }\n                  },\n                  fml::TimeDelta::FromSeconds(2));\n            };\n        deferred_decode_callback(weak_from_this(), ui_runner_);\n        return nullptr;\n      }\n      case DecodePriority::kImmediate:\n        // Continue to decode.\n        break;\n    }\n  }\n\n#if defined(ENABLE_SVG)\n  if (IsSVG()) {\n    if (image_producer_->SVGFrameReady()) {\n      return image_producer_->GetSVGFrame()->GetGraphicsImage();\n    } else {\n      // Async decode SVG frame.\n      image_producer_->DecodeSVG();\n      return nullptr;\n    }\n  }\n#endif\n\n  FML_DCHECK(image_producer_);\n  StartAnimation();\n\n  return image_producer_->CurrentFrameReady()\n             ? image_producer_->GetCurrentFrame()->GetGraphicsImage()\n             : nullptr;\n}\n\n// static\nint Image::NextUniqueId() {\n  static std::atomic<int> id = 0;\n  int current = id.fetch_add(1);\n  return current % (std::numeric_limits<int>::max() / 2);\n}\n\nvoid Image::CancelUpload() {\n#ifndef ENABLE_SKITY\n  // Cancel upload task, will affect only when ImageDecodeWithPriority is\n  // enabled.\n  ImageUploadManager::GetInstance().RemoveImageUploadTaskByImageId(image_id_);\n#endif  // ENABLE_SKITY\n}\n\nvoid Image::SetIsActive(bool is_active) {\n  is_active_ = is_active;\n  if (!is_active_) {\n    // If image is decoded but not uploaded, the upload tasks may be removed\n    // from ImageUploadManager. We need make sure when image is active again, it\n    // will trigger a new upload task.\n    if (!TryGetGraphicsImage()) {\n      force_decode_ = true;\n    }\n    // Also cancel current upload.\n    CancelUpload();\n  }\n}\n\nbool Image::NeedDecode() const {\n  // If TryGetGraphicsImage returns a valid image, it means the image has\n  // already been decoded and uploaded.\n  if (TryGetGraphicsImage()) {\n    return false;\n  }\n\n  if (force_decode_) {\n    force_decode_ = false;\n    return true;\n  }\n\n  return image_producer_->NeedDecode();\n}\n\nfml::RefPtr<GraphicsImage> Image::TryGetGraphicsImage() const {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n  // If there are pending operations, we may use new graphics image.\n  if (!pending_operations_.empty()) {\n    return nullptr;\n  }\n\n#if defined(ENABLE_SVG)\n  if (IsSVG()) {\n    return image_producer_->SVGFrameReady()\n               ? image_producer_->GetSVGFrame()->GetGraphicsImage()\n               : nullptr;\n  }\n#endif\n\n  FML_DCHECK(image_producer_);\n\n  return image_producer_->CurrentFrameReady()\n             ? image_producer_->GetCurrentFrame()->GetGraphicsImage()\n             : nullptr;\n}\n\nsize_t Image::GetGraphicsImageAllocSize() const {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n#if defined(ENABLE_SVG)\n  if (IsSVG()) {\n    return image_producer_->SVGFrameReady()\n               ? image_producer_->GetSVGFrame()->GetAllocationSize()\n               : 0;\n  }\n#endif\n\n  FML_DCHECK(image_producer_);\n  return image_producer_->GetAllocationSize();\n}\n\nint Image::GetWidth() {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  return image_producer_->GetInfo().width();\n}\n\nint Image::GetHeight() {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  return image_producer_->GetInfo().height();\n}\n\nsize_t Image::FrameCount() const { return image_producer_->FrameCount(); }\n\nvoid Image::SetLoopCount(int loop_count) {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  loop_count_ = loop_count;\n}\n\nvoid Image::SetAutoPlay(bool auto_play) {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  auto_play_ = auto_play;\n}\n\nvoid Image::ResetAnimation(bool reuse) {\n  if (frame_timer_) {\n    if (IsDeferred() && raster_runner_) {\n      raster_runner_->PostTask(fml::MakeCopyable(\n          [timer = std::move(frame_timer_)]() mutable { timer.reset(); }));\n    } else {\n      frame_timer_.reset();\n    }\n  }\n\n  current_frame_ = -1;\n  repetitions_complete_ = 0;\n  animation_finished_ = false;\n  running_state_ = kUndefined;\n  current_frame_start_time_ = 0;\n  current_frame_duration_ = 0;\n  // For extremely large animations, when the animation is reset, we just throw\n  // everything away.\n  image_producer_->Reset(reuse);\n}\n\nbool Image::IsAnimationFinished() const {\n  return animation_finished_ || IsSingleFrameImage();\n}\n\nbool Image::MaybeAnimated() const {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  return running_state_ == kRunning && !IsAnimationFinished();\n}\n\nbool Image::IsFirstFrame() const { return current_frame_ == -1; }\n\nbool Image::IsSingleFrameImage() const {\n  if (!image_producer_ || is_svg_) {\n    return true;\n  }\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  return image_producer_->IsSingleFrameImage();\n}\n\nbool Image::HasCurrentFrameDurationPassed(const int64_t timestamp) const {\n  return timestamp - current_frame_start_time_ >= current_frame_duration_;\n}\n\n// called from StartAnimation, maybe already locked mutex_;\nvoid Image::ScheduleNewFrame() { image_producer_->DecodeNextFrame(); }\n\nvoid Image::StartAnimation() {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  HandlePendingOperations();\n\n  // TODO(yudingqian): This is a workaround for images whose graphics resource\n  // might be release by GPUResourceCache(e.g. when up to cache limit) but\n  // haven't been notified.\n  if (!image_producer_->CurrentFrameReady()) {\n    if (image_producer_->NextFrameReady()) {\n      AdvanceAnimation();\n    } else {\n      ScheduleNewFrame();\n      if (is_promise_ && image_producer_->NextFrameReady()) {\n        image_producer_->UpdateCurrentFrame();\n      }\n    }\n    return;\n  }\n\n  if (UNLIKELY(running_state_ == kUndefined)) {\n    if (auto_play_) {\n      running_state_ = kRunning;\n      OnStartPlay();\n    } else {\n      running_state_ = kPause;\n    }\n  }\n\n  // Image is already animating\n  if (frame_timer_ && !frame_timer_->Stopped()) {\n    return;\n  }\n\n  // Animation has finished\n  if (IsAnimationFinished()) {\n    return;\n  }\n\n  // Animation has been paused by front-end\n  if (running_state_ == kPause) {\n    return;\n  }\n\n  // All accessors are invisible\n  if (ShouldPauseAnimation()) {\n    return;\n  }\n\n  const int64_t now = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  if (IsFirstFrame() || HasCurrentFrameDurationPassed(now)) {\n    // In `use_texture_backend_` mode, we always prepare next frame in advance.\n    // So here we can get next frame unless the decoding is not finished yet.\n    //\n    // In `software rendering` mode, we will not start to decode next frame\n    // until current frame duration is passed. So if the duration passed, we\n    // `ScheduleNewFrame` now.\n    if (image_producer_->NextFrameReady()) {\n      AdvanceAnimation();\n    } else {\n      ScheduleNewFrame();\n    }\n    return;\n  }\n\n  // The animation painting progress has caught up with the animated image. So\n  // wait for next frame and then advance.\n  if (!frame_timer_) {\n    frame_timer_ = std::make_unique<fml::OneshotTimer>(\n        IsDeferred() ? raster_runner_ : ui_runner_);\n  }\n\n  frame_timer_->Start(\n      fml::TimeDelta::FromMilliseconds(current_frame_duration_ -\n                                       (now - current_frame_start_time_)),\n      [weak = weak_from_this()] {\n        if (auto self = weak.lock()) {\n          self->StartAnimation();\n        }\n      });\n}\n\n// called from StartAnimation, maybe already locked mutex_;\nvoid Image::AdvanceAnimation() {\n  if (!image_producer_->NextFrameReady()) {\n    return;\n  }\n\n  image_producer_->UpdateCurrentFrame();\n\n  ++current_frame_;\n  if (current_frame_ == static_cast<int>(FrameCount()) - 1) {\n    ++repetitions_complete_;\n    OnCurrentLoopComplete();\n    if (loop_count_ > 0 && repetitions_complete_ >= loop_count_) {\n      animation_finished_ = true;\n      OnFinalLoopComplete();\n    }\n  } else if (current_frame_ > static_cast<int>(FrameCount()) - 1) {\n    current_frame_ = 0;\n  }\n\n  current_frame_start_time_ =\n      fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  current_frame_duration_ = image_producer_->GetCurrentFrame()->FrameDuration();\n\n  AnimationAdvanced();\n}\n\n// called from StartAnimation, maybe already locked mutex_;\nbool Image::ShouldPauseAnimation() const {\n  if (IsDeferred()) {\n    for (auto accessor : raster_accessors_) {\n      if (!accessor->ShouldPauseAnimation()) {\n        return false;\n      }\n    }\n  } else {\n    for (auto accessor : ui_accessors_) {\n      if (!accessor->ShouldPauseAnimation()) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\n// called from decode_callback, maybe already locked mutex_ by ImageProducer.;\nvoid Image::DecodeFinish(bool success) {\n  if (IsDeferred()) {\n    // If the `render_info_` of `ImageProducer` has been changed, we need to\n    // recreate the displaylist to make sure `drawImageRect` method has the\n    // correct `SrcRect` parameter. So notify ui accessors to `MarkNeedsPaint`\n    // here.\n    if (success && image_producer_->WillNextFrameSizeChange()) {\n      ui_runner_->PostTask([weak_self = weak_from_this()] {\n        auto self = weak_self.lock();\n        if (self) {\n          for (auto accessor : self->ui_accessors_) {\n            accessor->DecodeFinish(true);\n          }\n        }\n      });\n    }\n\n    for (auto accessor : raster_accessors_) {\n      accessor->DecodeFinish(success);\n    }\n  } else {\n    for (auto accessor : ui_accessors_) {\n      accessor->DecodeFinish(success);\n    }\n  }\n}\n\nvoid Image::UploadFinish(bool success) {\n  for (auto accessor : ui_accessors_) {\n    accessor->UploadFinish(success);\n  }\n}\n\nvoid Image::WillRegisterUploadTask(const std::function<void()>& task) {\n  OneShotCallback one_shot_task = OneShotCallback(task);\n  for (auto accessor : ui_accessors_) {\n    accessor->RegisterUploadTask(std::move(one_shot_task), image_id_);\n  }\n}\n\n// called from StartAnimation, maybe already locked mutex_;\nvoid Image::AnimationAdvanced() {\n  if (IsDeferred()) {\n    for (auto accessor : raster_accessors_) {\n      accessor->AnimationAdvanced();\n    }\n  } else {\n    for (auto accessor : ui_accessors_) {\n      accessor->AnimationAdvanced();\n    }\n  }\n\n  // If `use_texture_backend_`, we can start decoding next frame in advance,\n  // cause the decoding progress is asynchronous.\n  // But the decoding progress is synchronous in `software rendering` mode. So\n  // here we just call `StartAnimation` and start a timer to wait next frame.\n  if (use_texture_backend_) {\n    ScheduleNewFrame();\n  } else {\n    StartAnimation();\n  }\n}\n\nvoid Image::StartAnimate() { PushOperation(kStartAnimate); }\nvoid Image::StopAnimation() { PushOperation(kStopAnimation); }\nvoid Image::PauseAnimation() { PushOperation(kPauseAnimation); }\nvoid Image::ResumeAnimation() { PushOperation(kResumeAnimation); }\n\nvoid Image::PushOperation(Operation operation) {\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (is_deferred_) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  if (!pending_operations_.empty() && pending_operations_.back() == operation) {\n    return;\n  }\n  pending_operations_.push_back(operation);\n}\n\n// called from StartAnimation, maybe already locked mutex_;\nvoid Image::HandlePendingOperations() {\n  for (const auto operation : pending_operations_) {\n    switch (operation) {\n      case kStartAnimate:\n        ResetAnimation(true);\n        running_state_ = kRunning;\n        OnStartPlay();\n        break;\n      case kStopAnimation:\n        ResetAnimation(true);\n        running_state_ = kPause;\n        break;\n      case kPauseAnimation:\n        running_state_ = kPause;\n        break;\n      case kResumeAnimation:\n        if (running_state_ == kRunning) {\n          break;\n        }\n        running_state_ = kRunning;\n        OnStartPlay();\n        break;\n      default:\n        FML_LOG(ERROR) << \"Unrecognized operation\";\n    }\n  }\n  pending_operations_.clear();\n}\n\nvoid Image::OnStartPlay() {\n  fml::TaskRunner::RunNowOrPostTask(ui_runner_, [weak_self = weak_from_this()] {\n    auto self = weak_self.lock();\n    if (self) {\n      for (auto accessor : self->ui_accessors_) {\n        accessor->OnStartPlay();\n      }\n    }\n  });\n}\n\nvoid Image::OnCurrentLoopComplete() {\n  fml::TaskRunner::RunNowOrPostTask(ui_runner_, [weak_self = weak_from_this()] {\n    auto self = weak_self.lock();\n    if (self) {\n      for (auto accessor : self->ui_accessors_) {\n        accessor->OnCurrentLoopComplete();\n      }\n    }\n  });\n}\n\nvoid Image::OnFinalLoopComplete() {\n  fml::TaskRunner::RunNowOrPostTask(ui_runner_, [weak_self = weak_from_this()] {\n    auto self = weak_self.lock();\n    if (self) {\n      for (auto accessor : self->ui_accessors_) {\n        accessor->OnFinalLoopComplete();\n      }\n    }\n  });\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_H_\n#define CLAY_GFX_IMAGE_IMAGE_H_\n\n#include <list>\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <utility>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n#include \"clay/gfx/image/image_resource.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass ImageDescriptor;\nclass ImageProducer;\nclass SkImageHolder;\n\n// Image represents a image, which can be a single frame image (such as png,\n// jpg, etc.) or a multi-frame image (such as GIF).\nclass Image : public std::enable_shared_from_this<Image> {\n public:\n  class Notifier {\n   public:\n    virtual ~Notifier() = default;\n    virtual void ImageHasNoAccessor(const Image* image) = 0;\n    virtual fml::RefPtr<GPUUnrefQueue> GetUnrefQueue() = 0;\n  };\n\n  static std::shared_ptr<Image> CreateImage(\n      const std::string& url, GrDataPtr data,\n      fml::RefPtr<ImageDescriptor> descriptor,\n      std::weak_ptr<Notifier> weak_notifier,\n      fml::RefPtr<fml::TaskRunner> ui_runner,\n      fml::RefPtr<fml::TaskRunner> raster_runner, bool is_svg,\n      bool use_texture_backend, bool is_promise = false,\n      bool enable_low_quality_image = false, bool is_deferred = false,\n      bool decode_with_priority = false);\n\n  static std::shared_ptr<Image> CloneImage(std::shared_ptr<Image> image);\n\n  ~Image();\n\n  const std::string& GetUrl() const { return url_; }\n  const std::string& GetCacheIdentifier() const { return cache_identifier_; }\n  void SetCacheIdentifier(const std::string& cache_identifier) {\n    cache_identifier_ = cache_identifier;\n  }\n  void SetRawData(GrDataPtr data);\n\n  int GetWidth();\n  int GetHeight();\n  void SetGraphicsImage(fml::RefPtr<GraphicsImage> graphics_image);\n  fml::RefPtr<GraphicsImage> GetGraphicsImage(\n      DecodePriority priority = DecodePriority::kImmediate);\n  // Try to get GraphicsImage without triggering decode\n  fml::RefPtr<GraphicsImage> TryGetGraphicsImage() const;\n  size_t GetGraphicsImageAllocSize() const;\n\n  std::unique_ptr<ImageResource> GetAccessor(bool from_raster = false);\n  size_t GetUIAccessorCount() const { return ui_accessors_.size(); }\n\n#if defined(ENABLE_SVG)\n  void SetCacheKeyHash(size_t hash_string) {\n    cache_key_hash_ = std::move(hash_string);\n  }\n  size_t GetCacheKeyHash() const { return cache_key_hash_; }\n\n  void SetContentMD5(std::string md5_string) {\n    content_md5_ = std::move(md5_string);\n  }\n  const std::string& GetContentMD5() const { return content_md5_; }\n#endif\n\n  // Support SVG.\n  bool IsSVG() const { return is_svg_; }\n\n  bool IsSingleFrameImage() const;\n\n  void AccessorCreated(ImageResource* accessor);\n  void AccessorDestroyed(ImageResource* accessor);\n\n  void SetExpectSizeCalculator(\n      std::function<skity::Vec2(skity::Vec2)> calculator,\n      bool force_use_original_size);\n\n  bool MaybeAnimated() const;\n\n  bool UseTextureBackend() const { return use_texture_backend_; }\n  bool UsePromise() const { return is_promise_; }\n  bool IsDeferred() const { return is_deferred_; }\n  bool DecodeWithPriority() const { return decode_with_priority_; }\n\n  void SetAutoPlay(bool auto_play);\n  void SetLoopCount(int loop_count);\n\n  void StartAnimate();\n  void StopAnimation();\n  void PauseAnimation();\n  void ResumeAnimation();\n\n  void SetIsActive(bool is_active);\n  bool IsActive() const { return is_active_; }\n\n  bool NeedDecode() const;\n  void CancelUpload();\n\n private:\n  enum RunningState {\n    kUndefined = 0,\n    kRunning = 1,\n    kPause = 2,\n  };\n  enum Operation {\n    kStartAnimate = 0,\n    kStopAnimation,\n    kPauseAnimation,\n    kResumeAnimation,\n  };\n  Image(const std::string& url, GrDataPtr data,\n        std::weak_ptr<Notifier> weak_notifier,\n        fml::RefPtr<fml::TaskRunner> ui_runner,\n        fml::RefPtr<fml::TaskRunner> raster_runner, bool is_svg,\n        bool is_promise, bool enable_low_quality_image,\n        bool use_texture_backend, bool is_deferred, bool decode_with_priority);\n\n  static int NextUniqueId();\n\n  bool IsAnimationFinished() const;\n  void ResetAnimation(bool reuse = false);\n\n  size_t FrameCount() const;\n  int RepetitionCount() const;\n  void StartAnimation();\n  bool IsAnimating() const;\n  bool IsFirstFrame() const;\n  bool HasCurrentFrameDurationPassed(const int64_t timestamp) const;\n  size_t CurrentFrame() const { return current_frame_; }\n\n  // Animation.\n  void ScheduleNewFrame();\n  void AdvanceAnimation();\n  bool ShouldPauseAnimation() const;\n  void AnimationAdvanced();\n  void DecodeFinish(bool success);\n  void UploadFinish(bool success);\n  void WillRegisterUploadTask(const std::function<void()>& task);\n  void OnStartPlay();\n  void OnCurrentLoopComplete();\n  void OnFinalLoopComplete();\n  void PushOperation(Operation operation);\n  void HandlePendingOperations();\n\n  // The index of the current frame of animation.\n  int current_frame_ = -1;\n  // How many total animation loops we should do.\n  int loop_count_ = 0;\n  // How many repetitions we've finished.\n  int repetitions_complete_ = 0;\n  // Whether or not we've completed the entire animation.\n  bool animation_finished_ = false;\n  // Whether or not the animation is running. It is controlled by front-end.\n  RunningState running_state_ = kUndefined;\n\n  bool auto_play_ = true;\n\n  int64_t current_frame_start_time_ = 0;\n  int64_t current_frame_duration_ = 0;\n\n  const std::string url_;\n  GrDataPtr raw_data_;\n  // Support SVG.\n  const bool is_svg_ = false;\n#if defined(ENABLE_SVG)\n  // For SVG images, this is the MD5 of the SVG content.\n  // This MD5 is primarily used to cache images within ImageCache.\n  std::string content_md5_;\n  // For SVG images, this is the hash of the SVG content or its MD5.\n  // This hash is primarily used to cache images within the ImageCache.\n  size_t cache_key_hash_ = 0;\n#endif\n  // For Data images, this is the MD5 of the Data content. For other images,\n  // this is the trimmed URL.\n  std::string cache_identifier_;\n  const bool is_promise_ = false;\n  std::weak_ptr<Notifier> weak_notifier_;\n  std::unique_ptr<fml::OneshotTimer> frame_timer_;\n  std::shared_ptr<ImageProducer> image_producer_;\n  std::unordered_set<ImageResource*> ui_accessors_;\n  std::unordered_set<ImageResource*> raster_accessors_;\n  fml::RefPtr<fml::TaskRunner> ui_runner_;\n  fml::RefPtr<fml::TaskRunner> raster_runner_;\n  std::list<Operation> pending_operations_;\n\n  const bool use_texture_backend_ = true;\n  // TODO: support deferred decode for svg.\n  const bool is_deferred_ = false;\n  const bool decode_with_priority_ = false;\n\n  std::shared_ptr<std::recursive_mutex> mutex_ =\n      std::make_shared<std::recursive_mutex>();\n\n  // is_active_ refer to whether the image is still bound with any views that\n  // are currently attached to the view tree.\n  bool is_active_ = true;\n  mutable bool force_decode_ = false;\n  int image_id_;\n};\n\n}  // namespace clay\n#endif  // CLAY_GFX_IMAGE_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/image/image_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_DATA_H_\n#define CLAY_GFX_IMAGE_IMAGE_DATA_H_\n\n#include <array>\n\n#include \"clay/gfx/image/image_resource.h\"\n#include \"clay/gfx/style/length.h\"\n#if defined(ENABLE_SKITY)\n#include \"clay/gfx/image/base_image.h\"\n#endif\n\nnamespace clay {\n\nenum class FillMode {\n  // fill whole rect, can display whole image content.\n  kScaleToFill = 0,\n  // keep aspect ratio, can display whole image content.\n  kAspectFit,\n  // keep aspect ratio, can not display whole image content.\n  kAspectFill,\n  // don't zoom, only show the center.\n  kCenter,\n};\n\n/// How to paint any portions of a box not covered by an image.\nenum class ImageRepeat {\n  // Repeat the image in both the x and y directions until the box is filled.\n  kRepeat = 0,\n  // Leave uncovered portions of the box transparent.\n  kNoRepeat,\n  // Repeat the image in the x direction until the box is filled horizontally.\n  kRepeatX,\n  // Repeat the image in the y direction until the box is filled vertically.\n  kRepeatY,\n};\n\n// Provide some simple and easy use effects like inverse color / blur ...\nenum class ImageEffect {\n  kEffectNone = 0,\n  kInverseColor = 1,\n};\n\nstruct ImageData {\n#ifndef ENABLE_SKITY\n  ImageResource* image_resource = nullptr;\n#else\n  BaseImageInstance* image_resource = nullptr;\n#endif  // ENABLE_SKITY\n  FillMode mode = FillMode::kScaleToFill;\n  ImageRepeat repeat = ImageRepeat::kRepeat;\n  float drop_shadow_offset_x = 0.0f;\n  float drop_shadow_offset_y = 0.0f;\n  float drop_shadow_blur_radius = 0.0f;\n  GrColor drop_shadow_color = ToSk(Color::kBlack());\n  bool has_cap_insets = false;\n  std::array<Length, 4> cap_insets = {};\n  float cap_insets_scale = 1.0f;\n  float image_opacity = 1.0f;\n  std::optional<Color> tint_color = std::nullopt;\n  skity::RRect round_rect = {};\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_DATA_H_\n"
  },
  {
    "path": "clay/gfx/image/image_data_cache.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image_data_cache.h\"\n\n#include \"base/include/no_destructor.h\"\n#include \"clay/common/sys_info.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nnamespace {\nconstexpr size_t kImageCacheMaxBytes = 8 * 1024 * 1024;        // 8MB\nconstexpr size_t kImageCacheMaxBytesLowMem = 4 * 1024 * 1024;  // 4MB\nconstexpr float kDesiredOccupancyRatio = 0.8f;\n}  // namespace\n\nImageDataCache& ImageDataCache::GetInstance() {\n  static fml::NoDestructor<ImageDataCache> instance;\n  return *(instance.get());\n}\n\nImageDataCache::ImageDataCache()\n    : max_cached_bytes_(SysInfo::IsLowEndDevice() ? kImageCacheMaxBytesLowMem\n                                                  : kImageCacheMaxBytes) {\n  desired_cache_bytes_ = max_cached_bytes_ * kDesiredOccupancyRatio;\n}\n\nvoid ImageDataCache::SetMaxCachedBytes(size_t max_bytes) {\n  std::lock_guard<std::mutex> lock(mutex_);\n  max_cached_bytes_ = max_bytes;\n  desired_cache_bytes_ = max_cached_bytes_ * kDesiredOccupancyRatio;\n}\n\nvoid ImageDataCache::ClearCache() {\n  std::lock_guard<std::mutex> lock(mutex_);\n  ClearCacheInternal();\n}\n\nvoid ImageDataCache::ClearCacheInternal() {\n  active_cache_.clear();\n  inactive_cache_list_.clear();\n  inactive_cache_map_.clear();\n  inactive_cache_bytes_ = 0;\n}\n\nvoid ImageDataCache::CacheImageData(const std::string& url,\n                                    GrDataPtr image_data) {\n  // url is empty or data url, do not cache.\n  if (url.empty() || (url.compare(0, 5, \"data:\") == 0)) {\n    return;\n  }\n  std::lock_guard<std::mutex> lock(mutex_);\n  auto found = active_cache_.find(url);\n  if (found != active_cache_.end()) {\n    // Already in active cache, add ref count.\n    found->second.second++;\n  } else {\n    // Not in active cache, add to active cache.\n    active_cache_[url] = {image_data, 1};\n  }\n}\n\nvoid ImageDataCache::ReleaseOrArchiveImageData(const std::string& url) {\n  if (url.empty() || (url.compare(0, 5, \"data:\") == 0)) {\n    return;\n  }\n\n  std::lock_guard<std::mutex> lock(mutex_);\n  auto found = active_cache_.find(url);\n  if (found != active_cache_.end()) {\n    // Already in active cache, remove ref count.\n    found->second.second--;\n    // Remove from active cache if ref count is zero.\n    if (found->second.second == 0) {\n      MoveToInactiveCacheIfNeeded(url, found->second.first);\n      active_cache_.erase(found);\n    }\n  }\n}\n\nvoid ImageDataCache::RemoveImageDataIfExist(const std::string& url) {\n  if (url.empty() || (url.compare(0, 5, \"data:\") == 0)) {\n    return;\n  }\n  std::lock_guard<std::mutex> lock(mutex_);\n  auto found = active_cache_.find(url);\n  if (found != active_cache_.end()) {\n    // Already in active cache, remove from active cache.\n    active_cache_.erase(found);\n  }\n}\n\nGrDataPtr ImageDataCache::GetImageData(const std::string& url) {\n  std::lock_guard<std::mutex> lock(mutex_);\n\n  auto found = active_cache_.find(url);\n  if (found != active_cache_.end()) {\n    // Already in active cache, return data.\n    return found->second.first;\n  }\n  // Not in active cache, try to get from inactive cache.\n  return TakeFromInactiveCache(url);\n}\n\nGrDataPtr ImageDataCache::TakeFromInactiveCache(const std::string& url) {\n  auto found = inactive_cache_map_.find(url);\n  // Not in inactive cache.\n  if (found == inactive_cache_map_.end()) {\n    return nullptr;\n  }\n\n  // Found in inactive cache.\n  auto data = found->second->second;\n  // Delete from inactive cache.\n  inactive_cache_list_.erase(found->second);\n  inactive_cache_map_.erase(found);\n\n  if (data) {\n    // update inactive cache bytes.\n    inactive_cache_bytes_ -= DATA_GET_SIZE(data);\n  }\n\n  return data;\n}\n\nvoid ImageDataCache::MoveToInactiveCacheIfNeeded(const std::string& url,\n                                                 GrDataPtr data) {\n  // Data is too large, don't add to inactive cache.\n  if (DATA_GET_SIZE(data) > max_cached_bytes_) {\n    return;\n  }\n\n  FML_DCHECK(inactive_cache_map_.find(url) == inactive_cache_map_.end());\n\n  // add to inactive cache and update inactive cache bytes.\n  inactive_cache_list_.emplace_front(url, data);\n  inactive_cache_map_[url] = inactive_cache_list_.begin();\n  inactive_cache_bytes_ += DATA_GET_SIZE(data);\n\n  // Clean to max bytes if inactive cache bytes is too large.\n  if (inactive_cache_bytes_ > max_cached_bytes_) {\n    CleanTo(desired_cache_bytes_);\n  }\n}\n\n// Clean to max bytes.\nvoid ImageDataCache::CleanTo(size_t bytes) {\n  // Note: the inactive cache is a list, so the least recently used data is at\n  // the front.\n  auto iter = inactive_cache_list_.begin();\n  while (iter != inactive_cache_list_.end()) {\n    auto data_size = DATA_GET_SIZE(iter->second);\n    if (data_size > bytes) {\n      // Exceed available bytes, remove from inactive cache.\n      inactive_cache_bytes_ -= data_size;\n      inactive_cache_map_.erase(iter->first);\n      iter = inactive_cache_list_.erase(iter);\n    } else {\n      // Keep this data, decrease available bytes.\n      bytes -= data_size;\n      iter++;\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image_data_cache.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_DATA_CACHE_H_\n#define CLAY_GFX_IMAGE_IMAGE_DATA_CACHE_H_\n\n#include <list>\n#include <memory>\n#include <mutex>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass ImageDataCache {\n public:\n  static ImageDataCache& GetInstance();\n\n  ImageDataCache();\n\n  void SetMaxCachedBytes(size_t bytes);\n  void ClearCache();\n\n  GrDataPtr GetImageData(const std::string& url);\n  void CacheImageData(const std::string& url, GrDataPtr image_data);\n  void ReleaseOrArchiveImageData(const std::string& url);\n  void RemoveImageDataIfExist(const std::string& url);\n\n private:\n  friend class ImageDataCacheTest_SetMaxCachedBytesTest_Test;\n  friend class ImageDataCacheTest_CacheImageDataTest_Test;\n  friend class ImageDataCacheTest_RemoveImageDataIfExistTest_Test;\n  friend class ImageDataCacheTest_ClearCacheTest_Test;\n  friend class ImageDataCacheTest_CleanToTest_Test;\n\n  GrDataPtr TakeFromInactiveCache(const std::string& url);\n  void MoveToInactiveCacheIfNeeded(const std::string& url, GrDataPtr data);\n\n  void CleanTo(size_t bytes);\n  void ClearCacheInternal();\n\n  size_t max_cached_bytes_;\n  size_t desired_cache_bytes_;\n  size_t inactive_cache_bytes_ = 0;\n\n  std::mutex mutex_;\n\n  // key: url, value: {image_data, ref_count}\n  std::unordered_map<std::string, std::pair<GrDataPtr, size_t>> active_cache_;\n  std::list<std::pair<std::string, GrDataPtr>> inactive_cache_list_;\n  std::unordered_map<std::string, decltype(inactive_cache_list_.begin())>\n      inactive_cache_map_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_DATA_CACHE_H_\n"
  },
  {
    "path": "clay/gfx/image/image_data_cache_unittests.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image_data_cache.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(ImageDataCacheTest, SetMaxCachedBytesTest) {\n  size_t cache_size = 1024 * 1024;\n  ImageDataCache::GetInstance().SetMaxCachedBytes(cache_size);\n  EXPECT_EQ(ImageDataCache::GetInstance().max_cached_bytes_, cache_size);\n}\n\nTEST(ImageDataCacheTest, CacheImageDataTest) {\n  // Clear old cache.\n  ImageDataCache::GetInstance().ClearCache();\n\n  std::string url = \"url\";\n  std::string raw_data = \"data\";\n  sk_sp<SkData> sk_data = SkData::MakeWithCString(raw_data.c_str());\n  ImageDataCache::GetInstance().CacheImageData(url, sk_data);\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_[url].second == 1);\n  EXPECT_TRUE(sk_data->equals(\n      ImageDataCache::GetInstance().active_cache_[url].first.get()));\n\n  ImageDataCache::GetInstance().CacheImageData(url, sk_data);\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_[url].second == 2);\n\n  // get data from active cache.\n  sk_sp<SkData> get_cached_data =\n      ImageDataCache::GetInstance().GetImageData(url);\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_[url].second == 2);\n\n  ImageDataCache::GetInstance().ReleaseOrArchiveImageData(url);\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_[url].second == 1);\n\n  ImageDataCache::GetInstance().ReleaseOrArchiveImageData(url);\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_.find(url) ==\n              ImageDataCache::GetInstance().active_cache_.end());\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_map_.find(url) !=\n              ImageDataCache::GetInstance().inactive_cache_map_.end());\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_map_.size() == 1);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_list_.size() == 1);\n\n  size_t data_size = sk_data->size();\n  EXPECT_EQ(ImageDataCache::GetInstance().inactive_cache_bytes_, data_size);\n\n  // get data from inactive cache.\n  get_cached_data = ImageDataCache::GetInstance().GetImageData(url);\n  size_t expected_inactive_cache_bytes_ = 0;\n  EXPECT_EQ(ImageDataCache::GetInstance().inactive_cache_bytes_,\n            expected_inactive_cache_bytes_);\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_.find(url) ==\n              ImageDataCache::GetInstance().active_cache_.end());\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_map_.find(url) ==\n              ImageDataCache::GetInstance().inactive_cache_map_.end());\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_map_.size() == 0);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_list_.size() == 0);\n}\n\nTEST(ImageDataCacheTest, RemoveImageDataIfExistTest) {\n  // Clear old cache.\n  ImageDataCache::GetInstance().ClearCache();\n  std::string url = \"url\";\n  std::string raw_data = \"data\";\n  sk_sp<SkData> sk_data = SkData::MakeWithCString(raw_data.c_str());\n  ImageDataCache::GetInstance().CacheImageData(url, sk_data);\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_[url].second == 1);\n\n  ImageDataCache::GetInstance().RemoveImageDataIfExist(url);\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_.size() == 0);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_map_.size() == 0);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_list_.size() == 0);\n}\n\nTEST(ImageDataCacheTest, CleanToTest) {\n  // Clear old cache.\n  ImageDataCache::GetInstance().ClearCache();\n\n  // max_cached_bytes_: 10, desired_cache_bytes_: 8\n  ImageDataCache::GetInstance().SetMaxCachedBytes(10);\n\n  std::string url1 = \"url1\";\n  std::string raw_data1(2, 'a');\n  // sk_data1 size: 3\n  sk_sp<SkData> sk_data1 = SkData::MakeWithCString(raw_data1.c_str());\n  ImageDataCache::GetInstance().CacheImageData(url1, sk_data1);\n  ImageDataCache::GetInstance().ReleaseOrArchiveImageData(url1);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_bytes_ == 3);\n\n  std::string url2 = \"url2\";\n  std::string raw_data2(2, 'a');\n  // sk_data2 size: 3\n  sk_sp<SkData> sk_data2 = SkData::MakeWithCString(raw_data2.c_str());\n  ImageDataCache::GetInstance().CacheImageData(url2, sk_data2);\n  ImageDataCache::GetInstance().ReleaseOrArchiveImageData(url2);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_bytes_ == 6);\n\n  std::string url3 = \"url3\";\n  std::string raw_data3(1, 'a');\n  // sk_data2 size: 2\n  sk_sp<SkData> sk_data3 = SkData::MakeWithCString(raw_data3.c_str());\n  ImageDataCache::GetInstance().CacheImageData(url3, sk_data3);\n  ImageDataCache::GetInstance().ReleaseOrArchiveImageData(url3);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_bytes_ == 8);\n\n  std::string url4 = \"url4\";\n  std::string raw_data4(2, 'a');\n  // sk_data2 size: 3\n  sk_sp<SkData> sk_data4 = SkData::MakeWithCString(raw_data4.c_str());\n  ImageDataCache::GetInstance().CacheImageData(url4, sk_data4);\n  // current inactive list: [url4, url3, url2, url], size: [3, 2, 3, 3], total\n  // size: 11 will trigger CleanTo.\n  ImageDataCache::GetInstance().ReleaseOrArchiveImageData(url4);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_bytes_ == 8);\n}\n\nTEST(ImageDataCacheTest, ClearCacheTest) {\n  // Clear old cache.\n  ImageDataCache::GetInstance().ClearCache();\n\n  std::string url1 = \"url1\";\n  std::string raw_data1 = \"data\";\n  sk_sp<SkData> sk_data1 = SkData::MakeWithCString(raw_data1.c_str());\n  ImageDataCache::GetInstance().CacheImageData(url1, sk_data1);\n\n  std::string url2 = \"url2\";\n  std::string raw_data2 = \"data\";\n  sk_sp<SkData> sk_data2 = SkData::MakeWithCString(raw_data2.c_str());\n  ImageDataCache::GetInstance().CacheImageData(url2, sk_data2);\n\n  ImageDataCache::GetInstance().ReleaseOrArchiveImageData(url1);\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_.size() == 1);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_map_.size() == 1);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_list_.size() == 1);\n\n  ImageDataCache::GetInstance().ClearCache();\n  EXPECT_TRUE(ImageDataCache::GetInstance().active_cache_.size() == 0);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_map_.size() == 0);\n  EXPECT_TRUE(ImageDataCache::GetInstance().inactive_cache_list_.size() == 0);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image_decoder.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image_decoder.h\"\n\n#include <algorithm>\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/gfx/image/image_descriptor.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nfml::RefPtr<GraphicsImage> ResizeRasterImage(\n    fml::RefPtr<GraphicsImage> image, const skity::Vec2& resized_dimensions) {\n  FML_DCHECK(!image->isTextureBacked());\n\n  if (resized_dimensions.x <= 0 || resized_dimensions.y <= 0) {\n    FML_DLOG(ERROR) << \"Could not resize to empty dimensions.\";\n    return nullptr;\n  }\n\n#ifndef ENABLE_SKITY\n  if (image->dimensions() == resized_dimensions) {\n    return image->makeRasterImage();\n  }\n#else\n  FML_UNIMPLEMENTED();\n#endif  // ENABLE_SKITY\n\n  const auto scaled_image_info =\n      IMAGE_INFO_MAKE_DIMENSIONS(image->imageInfo(), resized_dimensions);\n#ifndef ENABLE_SKITY\n  SkBitmap scaled_bitmap;\n  if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) {\n    FML_LOG(ERROR) << \"Failed to allocate memory for bitmap of size \"\n                   << scaled_image_info.computeMinByteSize() << \"B\";\n    return nullptr;\n  }\n\n  if (!image->scalePixels(\n          scaled_bitmap.pixmap(),\n          SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),\n          SkImage::kDisallow_CachingHint)) {\n    FML_LOG(ERROR) << \"Could not scale pixels\";\n    return nullptr;\n  }\n\n  // Marking this as immutable makes the MakeFromBitmap call share the pixels\n  // instead of copying.\n  scaled_bitmap.setImmutable();\n#else\n  skity::Bitmap scaled_bitmap(scaled_image_info.width(),\n                              scaled_image_info.height());\n  if (!image->scalePixels(scaled_bitmap.GetPixmap(), nullptr,\n                          skity::SamplingOptions(skity::FilterMode::kLinear,\n                                                 skity::MipmapMode::kNone))) {\n    FML_LOG(ERROR) << \"Could not scale pixels\";\n    return nullptr;\n  }\n#endif  // ENABLE_SKITY\n\n  auto scaled_image = GraphicsImage::MakeFromBitmap(scaled_bitmap);\n  if (!scaled_image) {\n    FML_LOG(ERROR) << \"Could not create a scaled image from a scaled bitmap.\";\n    return nullptr;\n  }\n\n  return scaled_image;\n}\n\nstatic fml::RefPtr<GraphicsImage> ImageFromDecompressedData(\n    ImageDescriptor* descriptor, uint32_t target_width,\n    uint32_t target_height) {\n  auto image = GraphicsImage::MakeRasterData(\n      descriptor->image_info(), descriptor->data(), descriptor->RowBytes());\n\n  if (!image) {\n    FML_LOG(ERROR) << \"Could not create image from decompressed bytes.\";\n    return nullptr;\n  }\n\n  if (!target_width && !target_height) {\n    // No resizing requested. Just rasterize the image.\n    return image->makeRasterImage();\n  }\n\n  return ResizeRasterImage(std::move(image),\n                           skity::Vec2{target_width, target_height});\n}\n\nfml::RefPtr<GraphicsImage> ImageFromCompressedData(ImageDescriptor* descriptor,\n                                                   uint32_t target_width,\n                                                   uint32_t target_height) {\n  if (!descriptor->ShouldResize(target_width, target_height)) {\n    // No resizing requested. Just decode & rasterize the image.\n    fml::RefPtr<GraphicsImage> image = descriptor->image();\n    return image ? image->makeRasterImage() : nullptr;\n  }\n\n  const skity::Vec2 source_dimensions = {descriptor->width(),\n                                         descriptor->height()};\n  const skity::Vec2 resized_dimensions = {target_width, target_height};\n\n  float scale =\n      std::max(static_cast<double>(resized_dimensions.x) / source_dimensions.x,\n               static_cast<double>(resized_dimensions.y) / source_dimensions.y);\n\n  // The SkJpegCodec only supports scaled decoding to integer multiples of 1/8,\n  // and rather than rounding up, it rounds off at the boundary of 1/16.\n  // When `decode_dimensions` are smaller than `resized_dimensions`,\n  // the image quality is poor, so we attempt to round up.\n  skity::Vec2 decode_dimensions = descriptor->GetScaledDimensions(scale);\n  while (scale < 1.0 && (decode_dimensions.x < resized_dimensions.x ||\n                         decode_dimensions.y < resized_dimensions.y)) {\n    scale += 0.0625;\n    decode_dimensions = descriptor->GetScaledDimensions(scale);\n  }\n\n  // If the codec supports efficient sub-pixel decoding, decoded at a resolution\n  // close to the target resolution before resizing.\n  if (decode_dimensions != source_dimensions) {\n    auto scaled_image_info =\n        IMAGE_INFO_MAKE_DIMENSIONS(descriptor->image_info(), decode_dimensions);\n\n#ifndef ENABLE_SKITY\n    SkBitmap scaled_bitmap;\n    if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) {\n      FML_LOG(ERROR) << \"Failed to allocate memory for bitmap of size \"\n                     << scaled_image_info.computeMinByteSize() << \"B\";\n      return nullptr;\n    }\n\n    const auto& pixmap = scaled_bitmap.pixmap();\n    if (descriptor->GetPixels(pixmap)) {\n      // Marking this as immutable makes the MakeFromBitmap call share\n      // the pixels instead of copying.\n      scaled_bitmap.setImmutable();\n\n      auto decoded_image = GraphicsImage::MakeFromBitmap(scaled_bitmap);\n      FML_DCHECK(decoded_image);\n      if (!decoded_image) {\n        FML_LOG(ERROR)\n            << \"Could not create a scaled image from a scaled bitmap.\";\n        return nullptr;\n      }\n      return ResizeRasterImage(std::move(decoded_image), resized_dimensions);\n    }\n#else\n    // TODO(zhangxiao.ninja) these all are mock logic for compile, implement\n    // later\n    FML_UNIMPLEMENTED();\n    skity::Bitmap scaled_bitmap(scaled_image_info.width(),\n                                scaled_image_info.height());\n    auto decoded_image = GraphicsImage::MakeFromBitmap(scaled_bitmap);\n    FML_DCHECK(decoded_image);\n    if (!decoded_image) {\n      FML_LOG(ERROR) << \"Could not create a scaled image from a scaled bitmap.\";\n      return nullptr;\n    }\n    return ResizeRasterImage(std::move(decoded_image), resized_dimensions);\n#endif  // ENABLE_SKITY\n  }\n\n  auto image = descriptor->image();\n  if (!image) {\n    return nullptr;\n  }\n\n  return ResizeRasterImage(std::move(image), resized_dimensions);\n}\n\nImageDecoder::ImageDecoder(\n    std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner)\n    : concurrent_task_runner_(std::move(concurrent_task_runner)),\n      weak_factory_(this) {}\n\nImageDecoder::~ImageDecoder() = default;\n\nvoid ImageDecoder::Decode(fml::RefPtr<ImageDescriptor> descriptor_ref_ptr,\n                          uint32_t target_width, uint32_t target_height,\n                          const ImageResult& callback) {\n  // `ImageDecoder::Decode` itself is invoked on the UI thread, so the\n  // collection of the smart pointer from which we obtained the raw descriptor\n  // is fine in this scope.\n  auto raw_descriptor = descriptor_ref_ptr.get();\n  raw_descriptor->AddRef();\n\n  FML_DCHECK(callback);\n\n  // Always service the callback (and cleanup the descriptor) on the UI thread.\n  auto result = [callback, raw_descriptor](fml::RefPtr<GraphicsImage> image) {\n    callback(std::move(image));\n    raw_descriptor->Release();\n  };\n\n  if (!raw_descriptor->data() || DATA_GET_SIZE(raw_descriptor->data()) == 0) {\n    result({});\n    return;\n  }\n\n  concurrent_task_runner_->PostTask(\n      fml::MakeCopyable([raw_descriptor,                //\n                         result,                        //\n                         target_width = target_width,   //\n                         target_height = target_height  //\n  ]() mutable {\n        // Decompress the image.\n        // On Worker.\n        TRACE_EVENT(\"clay\", \"RK::DecodeImage\");\n        auto decompressed = raw_descriptor->IsCompressed()\n                                ? ImageFromCompressedData(raw_descriptor,  //\n                                                          target_width,    //\n                                                          target_height)\n                                : ImageFromDecompressedData(raw_descriptor,  //\n                                                            target_width,    //\n                                                            target_height);\n\n        if (!decompressed) {\n          FML_LOG(ERROR) << \"Could not decompress image.\";\n          result({});\n          return;\n        }\n        // All done.\n        result(std::move(decompressed));\n      }));\n}\n\nfml::WeakPtr<ImageDecoder> ImageDecoder::GetWeakPtr() const {\n  return weak_factory_.GetWeakPtr();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image_decoder.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_DECODER_H_\n#define CLAY_GFX_IMAGE_IMAGE_DECODER_H_\n\n#include <memory>\n#include <optional>\n\n#include \"base/include/fml/concurrent_message_loop.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass ImageDescriptor;\n\nclass ImageDecoder {\n public:\n  explicit ImageDecoder(\n      std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner);\n\n  ~ImageDecoder();\n\n  using ImageResult = std::function<void(fml::RefPtr<GraphicsImage>)>;\n\n  // Takes an image descriptor and returns a SkImage. All image decompression\n  // and resizes are done on a worker thread concurrently.\n  void Decode(fml::RefPtr<ImageDescriptor> descriptor_ref_ptr,\n              uint32_t target_width, uint32_t target_height,\n              const ImageResult& result);\n\n  fml::WeakPtr<ImageDecoder> GetWeakPtr() const;\n\n private:\n  std::shared_ptr<fml::ConcurrentTaskRunner> concurrent_task_runner_;\n  fml::WeakPtrFactory<ImageDecoder> weak_factory_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ImageDecoder);\n};\n\nfml::RefPtr<GraphicsImage> ImageFromCompressedData(ImageDescriptor* descriptor,\n                                                   uint32_t target_width,\n                                                   uint32_t target_height);\n\nfml::RefPtr<GraphicsImage> ResizeRasterImage(\n    fml::RefPtr<GraphicsImage> image, const skity::Vec2& resized_dimensions);\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_DECODER_H_\n"
  },
  {
    "path": "clay/gfx/image/image_descriptor.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image_descriptor.h\"\n\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n\n#ifdef ENABLE_TT_HEIF_DECODER\n#include \"clay/gfx/image/heif_decoder.h\"\n#endif\n\nnamespace clay {\n\nImageDescriptor::ImageDescriptor(GrDataPtr buffer)\n    : buffer_(std::move(buffer)), mutex_(fml::SharedMutex::Create()) {}\n\nImageDescriptor::ImageDescriptor(GrDataPtr buffer,\n                                 std::optional<size_t> row_bytes)\n    : buffer_(std::move(buffer)),\n      row_bytes_(row_bytes),\n      mutex_(fml::SharedMutex::Create()) {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image_descriptor.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_DESCRIPTOR_H_\n#define CLAY_GFX_IMAGE_IMAGE_DESCRIPTOR_H_\n\n#include <cstdint>\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#include \"clay/gfx/image/codec.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n// Creates an image descriptor for encoded or decoded image data, describing\n// the width, height, and bytes per pixel for that image.\n//\n// This class will hold a reference on the underlying image data, and in the\n// case of compressed data, an SkCodec and SkImageGenerator for the data.\n// The Codec initialization actually happens in initEncoded, making\n// instantiateCodec a lightweight operation.\nclass ImageDescriptor : public fml::RefCountedThreadSafe<ImageDescriptor> {\n public:\n  explicit ImageDescriptor(GrDataPtr buffer);\n\n  ImageDescriptor(GrDataPtr buffer, std::optional<size_t> row_bytes);\n\n  virtual ~ImageDescriptor() = default;\n\n  ImageDescriptor(const ImageDescriptor&) = delete;\n  const ImageDescriptor& operator=(const ImageDescriptor&) = delete;\n\n  enum PixelFormat {\n    kRGBA8888,\n    kBGRA8888,\n    kRGB565,\n  };\n\n  // Asynchronously initializes an ImageDescriptor for an encoded image, as\n  // long as the format is supported by Skia.\n  //\n  // Calling this method will result in creating an SkCodec and\n  // SkImageGenerator to read EXIF corrected dimensions from the image data.\n  static fml::RefPtr<ImageDescriptor> Create(\n      GrDataPtr data, bool enable_low_quality_image = false);\n\n  // Associates a Clay Codec object.\n  virtual fml::RefPtr<Codec> InstantiateCodec(int target_width,\n                                              int target_height) = 0;\n\n  virtual fml::RefPtr<GraphicsImage> image() const = 0;\n\n  virtual skity::Vec2 GetScaledDimensions(float scale) = 0;\n\n#ifndef ENABLE_SKITY\n  virtual bool GetPixels(const SkPixmap& pixmap) const = 0;\n#endif  // ENABLE_SKITY\n\n  // Whether this descriptor represents compressed (encoded) data or not.\n  virtual bool IsCompressed() const = 0;\n\n  // The width of this image, EXIF oriented if applicable.\n  int width() const { return image_info_.width(); }\n\n  // The height of this image. EXIF oriented if applicable.\n  int height() const { return image_info_.height(); }\n\n  // The bytes per pixel of the image.\n  int BytesPerPixel() const { return image_info_.bytesPerPixel(); }\n\n  // The byte length of the first row of the image.\n  //\n  // Defaults to width() * 4.\n  int RowBytes() const {\n    return row_bytes_.value_or(\n        static_cast<size_t>(image_info_.width() * image_info_.bytesPerPixel()));\n  }\n\n  // Whether the given target_width or target_height differ from width() and\n  // height() respectively.\n  bool ShouldResize(int target_width, int target_height) const {\n    return target_width != width() || target_height != height();\n  }\n\n  // The underlying buffer for this image.\n  GrDataPtr data() const { return buffer_; }\n\n  const GrImageInfo& image_info() const { return image_info_; }\n\n  bool IsSingleFrame() const { return single_frame_; }\n\n  size_t GetAllocationSize() const {\n    return sizeof(ImageDescriptor) + sizeof(ImageInfo) + DATA_GET_SIZE(buffer_);\n  }\n\n protected:\n  GrDataPtr buffer_;\n  GrImageInfo image_info_;\n  std::optional<size_t> row_bytes_;\n  bool single_frame_ = true;\n  std::unique_ptr<fml::SharedMutex> mutex_;\n\n  FML_FRIEND_MAKE_REF_COUNTED(ImageDescriptor);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(ImageDescriptor);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_DESCRIPTOR_H_\n"
  },
  {
    "path": "clay/gfx/image/image_descriptor_platform.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image_descriptor_platform.h\"\n\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/gfx/image/image_decoder.h\"\n#include \"clay/ui/common/isolate.h\"\n\nnamespace clay {\n\nclass FrameCodec : public Codec {\n public:\n  explicit FrameCodec(fml::RefPtr<ImageDescriptorPlatform> descriptor,\n                      int target_width, int target_height)\n      : descriptor_(descriptor),\n        target_width_(target_width),\n        target_height_(target_height) {}\n\n  int FrameCount() const override { return 0; }\n\n  void NextFrame(const CodecCallback& callback) override {}\n\n  int FrameDuration(int index) const override { return 0; }\n\n private:\n  fml::RefPtr<ImageDescriptorPlatform> descriptor_;\n  [[maybe_unused]] int target_width_;\n  [[maybe_unused]] int target_height_;\n};\n\n// static\nfml::RefPtr<ImageDescriptor> ImageDescriptor::Create(\n    GrDataPtr data, bool enable_low_quality_image) {\n  return nullptr;\n}\n\nfml::RefPtr<ImageDescriptor> ImageDescriptorPlatform::Create(\n    std::shared_ptr<PlatformImage> codec) {\n  return fml::MakeRefCounted<ImageDescriptorPlatform>(nullptr, codec);\n}\n\nImageDescriptorPlatform::ImageDescriptorPlatform(\n    GrDataPtr buffer, std::shared_ptr<PlatformImage> codec)\n    : ImageDescriptorPlatform(\n          buffer, IMAGE_INFO_MAKE_WH(codec->GetWidth(), codec->GetHeight()),\n          codec) {}\n\nImageDescriptorPlatform::ImageDescriptorPlatform(\n    GrDataPtr buffer, const GrImageInfo& image_info,\n    std::shared_ptr<PlatformImage> image)\n    : ImageDescriptor(buffer), image_(image) {\n  image_info_ = image_info;\n  // single_frame_ = codec_->GetFrameCount() == 1;\n}\n\nfml::RefPtr<Codec> ImageDescriptorPlatform::InstantiateCodec(\n    int target_width, int target_height) {\n  return fml::MakeRefCounted<FrameCodec>(\n      fml::RefPtr<ImageDescriptorPlatform>(this), target_width, target_height);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image_descriptor_platform.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_DESCRIPTOR_PLATFORM_H_\n#define CLAY_GFX_IMAGE_IMAGE_DESCRIPTOR_PLATFORM_H_\n\n#include <cstdint>\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n#include \"clay/gfx/image/image_descriptor.h\"\n#include \"clay/gfx/image/platform_image.h\"\n\nnamespace clay {\n\nclass ImageDescriptorPlatform : public ImageDescriptor {\n public:\n  ImageDescriptorPlatform(GrDataPtr buffer,\n                          std::shared_ptr<PlatformImage> image);\n  ImageDescriptorPlatform(GrDataPtr buffer, const GrImageInfo& image_info,\n                          std::shared_ptr<PlatformImage> image);\n\n  static fml::RefPtr<ImageDescriptor> Create(\n      std::shared_ptr<PlatformImage> codec);\n\n  ~ImageDescriptorPlatform() = default;\n\n  ImageDescriptorPlatform(const ImageDescriptorPlatform&) = delete;\n  const ImageDescriptorPlatform& operator=(const ImageDescriptorPlatform&) =\n      delete;\n\n  // Associates a Clay Codec object.\n  fml::RefPtr<Codec> InstantiateCodec(int target_width,\n                                      int target_height) override;\n\n  // Whether this descriptor represents compressed (encoded) data or not.\n  bool IsCompressed() const override { return false; }\n\n  fml::RefPtr<GraphicsImage> image() const override { return nullptr; }\n\n  // Gets the scaled dimensions of this image, if backed by a codec that can\n  // perform efficient subpixel scaling.\n  skity::Vec2 GetScaledDimensions(float scale) override {\n    return {image_info_.width() * scale, image_info_.height() * scale};\n  }\n\n  std::shared_ptr<PlatformImage> GetPlatformImage() { return image_; }\n\n private:\n  std::shared_ptr<PlatformImage> image_;\n  FML_FRIEND_MAKE_REF_COUNTED(ImageDescriptorPlatform);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(ImageDescriptorPlatform);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_DESCRIPTOR_PLATFORM_H_\n"
  },
  {
    "path": "clay/gfx/image/image_descriptor_skia.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image_descriptor_skia.h\"\n\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/image/multi_frame_codec.h\"\n#include \"clay/gfx/image/single_frame_codec.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n#include \"third_party/skia/include/core/SkColorPriv.h\"\n#include \"third_party/skia/include/core/SkImageInfo.h\"\n\n#ifdef ENABLE_TT_HEIF_DECODER\n#include \"clay/gfx/image/heif_decoder.h\"\n#endif\n\n#ifdef OS_MACOSX\n#include \"third_party/skia/include/ports/SkImageGeneratorCG.h\"\n#define PLATFORM_IMAGE_GENERATOR(data) \\\n  SkImageGeneratorCG::MakeFromEncodedCG(data)\n#elif OS_WIN\n#include \"third_party/skia/include/ports/SkImageGeneratorWIC.h\"\n#define PLATFORM_IMAGE_GENERATOR(data) \\\n  SkImageGeneratorWIC::MakeFromEncodedWIC(data)\n#else\n#define PLATFORM_IMAGE_GENERATOR(data) \\\n  std::unique_ptr<SkImageGenerator>(nullptr)\n#endif\n\nnamespace clay {\n\n// static\nfml::RefPtr<ImageDescriptor> ImageDescriptor::Create(\n    sk_sp<SkData> data, bool enable_low_quality_image) {\n  if (!data) {\n    FML_DLOG(ERROR) << \"Buffer parameter must not be null\";\n    return nullptr;\n  }\n\n  // This call will succeed if Skia has a built-in codec for this.\n  // If it fails, we will check if the platform knows how to decode this image.\n  std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(data);\n  fml::RefPtr<ImageDescriptor> descriptor;\n  if (!codec) {\n    std::unique_ptr<SkImageGenerator> generator =\n        PLATFORM_IMAGE_GENERATOR(data);\n    if (!generator) {\n      // We don't have a Skia codec for this image, and the platform doesn't\n      // know how to decode it.\n#ifdef ENABLE_TT_HEIF_DECODER\n      // Try using fresco decoder\n      const uint8_t* heif_data = data->bytes();\n      uint32_t data_size = data->size();\n      if (HeifDecoder::IsHeifFile(heif_data, data_size)) {\n        uint32_t width, height, rotation = 0;\n        uint64_t duration = 0;\n        bool is_sequence = false;\n        HeifDecoder::GetHeifImageInfo(heif_data, data_size, &width, &height,\n                                      &rotation, &is_sequence, &duration);\n        if (!is_sequence) {\n          HeifOutputStream output =\n              enable_low_quality_image\n                  ? HeifDecoder::DecodeStaticHeifImgToRgb(heif_data, data_size,\n                                                          width, height)\n                  : HeifDecoder::DecodeStaticHeifImgToRgba(heif_data, data_size,\n                                                           width, height);\n          if (output.size == 0 || output.data == nullptr) {\n            FML_DLOG(ERROR) << \"Fresco decode failed\";\n            return nullptr;\n          }\n          sk_sp<SkData> data = SkData::MakeWithCopy(output.data, output.size);\n          return ImageDescriptorSkia::Create(\n              data, width, height, -1,\n              enable_low_quality_image\n                  ? ImageDescriptorSkia::PixelFormat::kRGB565\n                  : ImageDescriptorSkia::PixelFormat::kRGBA8888);\n        } else {\n          // TODO(fangzheyuan): support anim heic.\n          FML_DLOG(ERROR) << \"UnSupport decoding anim heic format current\";\n          return nullptr;\n        }\n      }\n#endif\n      FML_DLOG(ERROR) << \"Invalid image data\";\n      return nullptr;\n    }\n    descriptor =\n        fml::MakeRefCounted<ImageDescriptorSkia>(data, std::move(generator));\n  } else {\n    if (enable_low_quality_image) {\n      auto width = codec->dimensions().width();\n      auto height = codec->dimensions().height();\n      auto image_info = SkImageInfo::Make(width, height, kRGB_565_SkColorType,\n                                          kOpaque_SkAlphaType);\n      descriptor = fml::MakeRefCounted<ImageDescriptorSkia>(data, image_info,\n                                                            std::move(codec));\n    } else {\n      descriptor =\n          fml::MakeRefCounted<ImageDescriptorSkia>(data, std::move(codec));\n    }\n  }\n  FML_DCHECK(descriptor);\n  return descriptor;\n}\n\nfml::RefPtr<ImageDescriptorSkia> ImageDescriptorSkia::Create(\n    sk_sp<SkData> data, int width, int height, int row_bytes,\n    PixelFormat pixel_format) {\n  if (!data) {\n    FML_DLOG(ERROR) << \"Buffer parameter must not be null\";\n    return nullptr;\n  }\n\n  SkColorType color_type = kUnknown_SkColorType;\n  SkAlphaType alpha_type = kPremul_SkAlphaType;\n  switch (pixel_format) {\n    case PixelFormat::kRGBA8888:\n      color_type = kRGBA_8888_SkColorType;\n      break;\n    case PixelFormat::kBGRA8888:\n      color_type = kBGRA_8888_SkColorType;\n      break;\n    case PixelFormat::kRGB565:\n      color_type = kRGB_565_SkColorType;\n      alpha_type = kOpaque_SkAlphaType;\n      break;\n  }\n  FML_DCHECK(color_type != kUnknown_SkColorType);\n  auto image_info = SkImageInfo::Make(width, height, color_type, alpha_type);\n  return fml::MakeRefCounted<ImageDescriptorSkia>(\n      data, std::move(image_info),\n      row_bytes == -1 ? std::nullopt : std::optional<size_t>(row_bytes));\n}\n\nImageDescriptorSkia::ImageDescriptorSkia(sk_sp<SkData> buffer,\n                                         const SkImageInfo& image_info,\n                                         std::optional<size_t> row_bytes)\n    : ImageDescriptor(buffer, row_bytes) {\n  image_info_ = std::move(image_info);\n}\n\nImageDescriptorSkia::ImageDescriptorSkia(sk_sp<SkData> buffer,\n                                         std::unique_ptr<SkCodec> codec)\n    : ImageDescriptor(buffer),\n      generator_(std::shared_ptr<SkCodecImageGenerator>(\n          static_cast<SkCodecImageGenerator*>(\n              SkCodecImageGenerator::MakeFromCodec(std::move(codec))\n                  .release()))),\n      platform_image_generator_(nullptr) {\n  image_info_ = CreateImageInfo();\n  single_frame_ = !generator_ || generator_->getFrameCount() == 1;\n}\n\nImageDescriptorSkia::ImageDescriptorSkia(sk_sp<SkData> buffer,\n                                         const SkImageInfo& image_info,\n                                         std::unique_ptr<SkCodec> codec)\n    : ImageDescriptor(buffer),\n      generator_(std::shared_ptr<SkCodecImageGenerator>(\n          static_cast<SkCodecImageGenerator*>(\n              SkCodecImageGenerator::MakeFromCodec(std::move(codec))\n                  .release()))),\n      platform_image_generator_(nullptr) {\n  image_info_ = image_info;\n  single_frame_ = !generator_ || generator_->getFrameCount() == 1;\n}\n\nImageDescriptorSkia::ImageDescriptorSkia(\n    sk_sp<SkData> buffer, std::unique_ptr<SkImageGenerator> generator)\n    : ImageDescriptor(buffer),\n      generator_(nullptr),\n      platform_image_generator_(std::move(generator)) {\n  image_info_ = CreateImageInfo();\n}\n\nconst SkImageInfo ImageDescriptorSkia::CreateImageInfo() const {\n  if (generator_) {\n    return generator_->getInfo();\n  }\n  if (platform_image_generator_) {\n    return platform_image_generator_->getInfo();\n  }\n  return SkImageInfo::MakeUnknown();\n}\n\nfml::RefPtr<Codec> ImageDescriptorSkia::InstantiateCodec(int target_width,\n                                                         int target_height) {\n  fml::RefPtr<Codec> ui_codec;\n  if (!generator_ || generator_->getFrameCount() == 1) {\n    ui_codec = fml::MakeRefCounted<SingleFrameCodec>(\n        fml::RefPtr<ImageDescriptor>(this), target_width, target_height);\n  } else {\n    ui_codec = fml::MakeRefCounted<MultiFrameCodec>(generator_);\n  }\n  FML_DCHECK(ui_codec);\n  return ui_codec;\n}\n\nfml::RefPtr<GraphicsImage> ImageDescriptorSkia::image() const {\n  SkAlphaType codec_alpha_type = GetCodecAlphaType();\n  // When using skia generator, if the origin image has alpha channel, we need\n  // to convert it to 565 manually. Otherwise decode will fail.\n  if (generator_ != nullptr &&\n      image_info_.colorType() == kRGB_565_SkColorType &&\n      codec_alpha_type != kOpaque_SkAlphaType) {\n    return Get565Image();\n  }\n  SkBitmap bitmap;\n  if (!bitmap.tryAllocPixels(image_info_)) {\n    FML_DLOG(ERROR) << \"Failed to allocate memory for bitmap of size \"\n                    << image_info_.computeMinByteSize() << \"B\";\n    return nullptr;\n  }\n\n  const auto& pixmap = bitmap.pixmap();\n  if (!GetPixels(pixmap)) {\n    FML_DLOG(ERROR) << \"Failed to get pixels for image.\";\n    return nullptr;\n  }\n  bitmap.setImmutable();\n  return GraphicsImage::MakeFromBitmap(bitmap);\n}\n\nbool ImageDescriptorSkia::GetPixels(const SkPixmap& pixmap) const {\n  fml::UniqueLock lock(*mutex_);\n  if (generator_) {\n    return generator_->getPixels(pixmap.info(), pixmap.writable_addr(),\n                                 pixmap.rowBytes());\n  }\n  FML_DCHECK(platform_image_generator_);\n  return platform_image_generator_->getPixels(pixmap);\n}\n\nSkAlphaType ImageDescriptorSkia::GetCodecAlphaType() const {\n  if (generator_) {\n    return generator_->getInfo().alphaType();\n  }\n  FML_DCHECK(platform_image_generator_);\n  return platform_image_generator_->getInfo().alphaType();\n}\n\nfml::RefPtr<GraphicsImage> ImageDescriptorSkia::Get565Image() const {\n  FML_DCHECK(generator_);\n  SkImageInfo info = generator_->getInfo();\n  int width = info.width();\n  int height = info.height();\n\n  SkBitmap temp_bitmap;\n  SkImageInfo temp_info =\n      info.makeColorType(kN32_SkColorType).makeAlphaType(kPremul_SkAlphaType);\n  if (!temp_bitmap.tryAllocPixels(temp_info)) {\n    FML_DLOG(ERROR) << \"Failed to allocate memory for bitmap of size \"\n                    << temp_info.computeMinByteSize() << \"B\";\n    return nullptr;\n  }\n\n  if (!generator_->getPixels(temp_info, temp_bitmap.getPixels(),\n                             temp_bitmap.rowBytes())) {\n    FML_DLOG(ERROR) << \"Failed to get pixels for image.\";\n    return nullptr;\n  }\n\n  SkImageInfo dst_info = info.makeColorType(kRGB_565_SkColorType)\n                             .makeAlphaType(kOpaque_SkAlphaType);\n  SkBitmap dst_bitmap;\n  if (!dst_bitmap.tryAllocPixels(dst_info)) {\n    FML_DLOG(ERROR) << \"Failed to allocate memory for bitmap of size \"\n                    << dst_info.computeMinByteSize() << \"B\";\n    return nullptr;\n  }\n\n  for (int y = 0; y < height; ++y) {\n    uint32_t* src_row = temp_bitmap.getAddr32(0, y);\n    uint16_t* dst_row = dst_bitmap.getAddr16(0, y);\n    for (int x = 0; x < width; ++x) {\n      uint32_t c = src_row[x];\n      // Convert 8888->565\n      unsigned r = SkGetPackedR32(c);\n      unsigned g = SkGetPackedG32(c);\n      unsigned b = SkGetPackedB32(c);\n      dst_row[x] = (uint16_t)(((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3));\n    }\n  }\n\n  dst_bitmap.setImmutable();\n  return GraphicsImage::MakeFromBitmap(dst_bitmap);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image_descriptor_skia.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_DESCRIPTOR_SKIA_H_\n#define CLAY_GFX_IMAGE_IMAGE_DESCRIPTOR_SKIA_H_\n\n#include <cstdint>\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"clay/gfx/image/image_descriptor.h\"\n#include \"third_party/skia/include/codec/SkCodec.h\"\n#include \"third_party/skia/include/core/SkData.h\"\n#include \"third_party/skia/include/core/SkImageGenerator.h\"\n#include \"third_party/skia/include/core/SkImageInfo.h\"\n#include \"third_party/skia/src/codec/SkCodecImageGenerator.h\"  // nogncheck\n\nnamespace clay {\n\nclass ImageDescriptorSkia : public ImageDescriptor {\n public:\n  enum PixelFormat {\n    kRGBA8888,\n    kBGRA8888,\n    kRGB565,\n  };\n\n  ImageDescriptorSkia(sk_sp<SkData> buffer, std::unique_ptr<SkCodec> codec);\n  ImageDescriptorSkia(sk_sp<SkData> buffer, const SkImageInfo& image_info,\n                      std::unique_ptr<SkCodec> codec);\n  ImageDescriptorSkia(sk_sp<SkData> buffer,\n                      std::unique_ptr<SkImageGenerator> generator);\n  ImageDescriptorSkia(sk_sp<SkData> buffer, const SkImageInfo& image_info,\n                      std::optional<size_t> row_bytes);\n\n  static fml::RefPtr<ImageDescriptorSkia> Create(sk_sp<SkData> data, int width,\n                                                 int height, int row_bytes,\n                                                 PixelFormat pixel_format);\n\n  ~ImageDescriptorSkia() = default;\n\n  ImageDescriptorSkia(const ImageDescriptorSkia&) = delete;\n  const ImageDescriptorSkia& operator=(const ImageDescriptorSkia&) = delete;\n\n  // Associates a Clay Codec object.\n  fml::RefPtr<Codec> InstantiateCodec(int target_width,\n                                      int target_height) override;\n\n  fml::RefPtr<GraphicsImage> image() const override;\n\n  // Whether this descriptor represents compressed (encoded) data or not.\n  bool IsCompressed() const override {\n    return generator_ || platform_image_generator_;\n  }\n\n  // Gets the scaled dimensions of this image, if backed by a codec that can\n  // perform efficient subpixel scaling.\n  skity::Vec2 GetScaledDimensions(float scale) override {\n    if (generator_) {\n      return {\n          static_cast<float>(generator_->getScaledDimensions(scale).width()),\n          static_cast<float>(generator_->getScaledDimensions(scale).height())};\n    }\n    return {static_cast<float>(image_info_.width()),\n            static_cast<float>(image_info_.height())};\n  }\n\n  // Gets pixels for this image transformed based on the EXIF orientation tag,\n  // if applicable.\n  bool GetPixels(const SkPixmap& pixmap) const override;\n\n  std::shared_ptr<SkCodecImageGenerator> GetGenerator() { return generator_; }\n\n private:\n  const SkImageInfo CreateImageInfo() const;\n  SkAlphaType GetCodecAlphaType() const;\n  fml::RefPtr<GraphicsImage> Get565Image() const;\n\n  std::shared_ptr<SkCodecImageGenerator> generator_;\n  std::unique_ptr<SkImageGenerator> platform_image_generator_;\n\n  FML_FRIEND_MAKE_REF_COUNTED(ImageDescriptorSkia);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(ImageDescriptorSkia);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_DESCRIPTOR_SKIA_H_\n"
  },
  {
    "path": "clay/gfx/image/image_info.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_INFO_H_\n#define CLAY_GFX_IMAGE_IMAGE_INFO_H_\n\n#include \"clay/fml/logging.h\"\n#include \"skity/geometry/vector.hpp\"\n\nnamespace clay {\n\nenum ColorType : int {\n  kUnknown_ColorType,  //!< uninitialized\n  kAlpha_8_ColorType,  //!< pixel with alpha in 8-bit byte\n  kRGB_565_ColorType,  //!< pixel with 5 bits red, 6 bits green, 5 bits blue, in\n                       //!< 16-bit word\n  kARGB_4444_ColorType,  //!< pixel with 4 bits for alpha, red, green, blue; in\n                         //!< 16-bit word\n  kRGBA_8888_ColorType,  //!< pixel with 8 bits for red, green, blue, alpha; in\n                         //!< 32-bit word\n  kRGB_888x_ColorType,   //!< pixel with 8 bits each for red, green, blue; in\n                         //!< 32-bit word\n  kBGRA_8888_ColorType,  //!< pixel with 8 bits for blue, green, red, alpha; in\n                         //!< 32-bit word\n  kRGBA_1010102_ColorType,  //!< 10 bits for red, green, blue; 2 bits for alpha;\n                            //!< in 32-bit word\n  kBGRA_1010102_ColorType,  //!< 10 bits for blue, green, red; 2 bits for alpha;\n                            //!< in 32-bit word\n  kRGB_101010x_ColorType,  //!< pixel with 10 bits each for red, green, blue; in\n                           //!< 32-bit word\n  kBGR_101010x_ColorType,  //!< pixel with 10 bits each for blue, green, red; in\n                           //!< 32-bit word\n  kBGR_101010x_XR_ColorType,  //!< pixel with 10 bits each for blue, green, red;\n                              //!< in 32-bit word, extended range\n  kGray_8_ColorType,          //!< pixel with grayscale level in 8-bit byte\n  kRGBA_F16Norm_ColorType,  //!< pixel with half floats in [0,1] for red, green,\n                            //!< blue, alpha;\n                            //   in 64-bit word\n  kRGBA_F16_ColorType,  //!< pixel with half floats for red, green, blue, alpha;\n                        //   in 64-bit word\n  kRGBA_F32_ColorType,  //!< pixel using C float for red, green, blue, alpha; in\n                        //!< 128-bit word\n\n  // The following 6 colortypes are just for reading from - not for rendering to\n  kR8G8_unorm_ColorType,  //!< pixel with a uint8_t for red and green\n\n  kA16_float_ColorType,     //!< pixel with a half float for alpha\n  kR16G16_float_ColorType,  //!< pixel with a half float for red and green\n\n  kA16_unorm_ColorType,     //!< pixel with a little endian uint16_t for alpha\n  kR16G16_unorm_ColorType,  //!< pixel with a little endian uint16_t for red and\n                            //!< green\n  kR16G16B16A16_unorm_ColorType,  //!< pixel with a little endian uint16_t for\n                                  //!< red, green, blue\n                                  //   and alpha\n\n  kSRGBA_8888_ColorType,\n  kR8_unorm_ColorType,\n\n  kLastEnum_ColorType = kR8_unorm_ColorType,  //!< last valid value\n};\n\nenum AlphaType : int {\n  kUnknown_AlphaType,   //!< uninitialized\n  kOpaque_AlphaType,    //!< pixel is opaque\n  kPremul_AlphaType,    //!< pixel components are premultiplied by alpha\n  kUnpremul_AlphaType,  //!< pixel components are independent of alpha\n  kLastEnum_AlphaType = kUnpremul_AlphaType,  //!< last valid value\n};\n\n// Information for a single frame of an animation.\nstruct ImageInfo {\n  ImageInfo() = default;\n  ImageInfo(int width, int height, ColorType color_type, AlphaType alpha_type)\n      : width_(width),\n        height_(height),\n        color_type_(color_type),\n        alpha_type_(alpha_type) {}\n\n  int width() const { return width_; }\n  int height() const { return height_; }\n\n  size_t bytesPerPixel() const {\n    switch (color_type_) {\n      case kUnknown_ColorType:\n        return 0;\n      case kAlpha_8_ColorType:\n        return 1;\n      case kRGB_565_ColorType:\n        return 2;\n      case kARGB_4444_ColorType:\n        return 2;\n      case kRGBA_8888_ColorType:\n        return 4;\n      case kBGRA_8888_ColorType:\n        return 4;\n      case kRGB_888x_ColorType:\n        return 4;\n      case kRGBA_1010102_ColorType:\n        return 4;\n      case kRGB_101010x_ColorType:\n        return 4;\n      case kBGRA_1010102_ColorType:\n        return 4;\n      case kBGR_101010x_ColorType:\n        return 4;\n      case kBGR_101010x_XR_ColorType:\n        return 4;\n      case kGray_8_ColorType:\n        return 1;\n      case kRGBA_F16Norm_ColorType:\n        return 8;\n      case kRGBA_F16_ColorType:\n        return 8;\n      case kRGBA_F32_ColorType:\n        return 16;\n      case kR8G8_unorm_ColorType:\n        return 2;\n      case kA16_unorm_ColorType:\n        return 2;\n      case kR16G16_unorm_ColorType:\n        return 4;\n      case kA16_float_ColorType:\n        return 2;\n      case kR16G16_float_ColorType:\n        return 4;\n      case kR16G16B16A16_unorm_ColorType:\n        return 8;\n      case kSRGBA_8888_ColorType:\n        return 4;\n      case kR8_unorm_ColorType:\n        return 1;\n    }\n    FML_UNREACHABLE();\n  }\n\n  bool isEmpty() const { return width_ == 0 || height_ == 0; }\n\n  // To align with Skia's API\n  static ImageInfo makeWH(int width, int height) {\n    return ImageInfo(width, height, kRGBA_8888_ColorType, kPremul_AlphaType);\n  }\n\n  static ImageInfo makeDimensions(skity::Vec2 newSize) {\n    return makeWH(newSize.x, newSize.y);\n  }\n\n  skity::Vec2 dimensions() const { return skity::Vec2(width_, height_); }\n\n  ColorType colorType() const { return color_type_; }\n  AlphaType alphaType() const { return alpha_type_; }\n\n  void reset() {\n    width_ = 0;\n    height_ = 0;\n  }\n\n  // image width\n  int width_ = 0;\n\n  // image height\n  int height_ = 0;\n\n  ColorType color_type_ = kRGBA_8888_ColorType;\n  AlphaType alpha_type_ = kPremul_AlphaType;\n};\n\nstatic inline bool operator==(const ImageInfo& a, const ImageInfo& b) {\n  return a.width() == b.width() && a.height() == b.height() &&\n         a.colorType() == b.colorType() && a.alphaType() == b.alphaType();\n}\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_INFO_H_\n"
  },
  {
    "path": "clay/gfx/image/image_produce_context.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_PRODUCE_CONTEXT_H_\n#define CLAY_GFX_IMAGE_IMAGE_PRODUCE_CONTEXT_H_\n\n#include <memory>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/gpu_object.h\"\n\nnamespace clay {\n\n// Produce context for construct a SkImage and potential texture uploading.\nstruct ImageProduceContext {\n  bool use_promise = false;\n  bool is_deferred = false;\n  bool decode_with_priority = false;\n  std::function<void(bool)> decode_callback;\n  std::function<void(bool)> upload_callback;\n  std::function<void(const std::function<void()>&)> register_upload_callback;\n  fml::RefPtr<GPUUnrefQueue> unref_queue;\n  fml::RefPtr<fml::TaskRunner> ui_task_runner;\n  fml::RefPtr<fml::TaskRunner> raster_task_runner;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_PRODUCE_CONTEXT_H_\n"
  },
  {
    "path": "clay/gfx/image/image_producer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image_producer.h\"\n\n#include <algorithm>\n#include <future>\n#include <memory>\n#include <utility>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/image/codec.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n#include \"clay/gfx/image/image_descriptor.h\"\n#include \"clay/gfx/image/skimage_holder.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n\n#define CACHE_IMAGE_BY_DRAWING_SIZE 1\n\n#ifdef ENABLE_SKITY\n#define IMAGE_DIMENSION(image) image\n#else\n#define IMAGE_DIMENSION(image) \\\n  SkISize { (int32_t) image.x, (int32_t)image.y }\n#endif\n\nnamespace clay {\nnamespace {\nstatic const size_t kMaxMemoryLimitPerFrame = 24 * 1024 * 1024;\n\nclass PromiseImageContext\n    : public fml::RefCountedThreadSafe<PromiseImageContext> {\n public:\n  fml::RefPtr<GPUUnrefQueue> unref_queue;\n  fml::RefPtr<fml::TaskRunner> raster_runner;\n  std::future<fml::RefPtr<GraphicsImage>> image_future;\n  fml::RefPtr<GraphicsImage> texture_image;\n};\n\ntemplate <class T>\nclass SafePromise {\n public:\n  SafePromise() = default;\n\n  ~SafePromise() {\n    if (!has_set_) {\n      promise_.set_value(nullptr);\n    }\n  }\n\n  void SetValue(T value) {\n    promise_.set_value(value);\n    has_set_ = true;\n  }\n\n  std::future<T> GetFuture() { return promise_.get_future(); }\n\n private:\n  std::promise<T> promise_;\n  bool has_set_ = false;\n};\n\n#ifndef ENABLE_SKITY\nsk_sp<SkPromiseImageTexture> GetPromiseImage(void* context) {\n  TRACE_EVENT(\"clay\", \"GetPromiseImage\");\n  PromiseImageContext* image_context =\n      static_cast<PromiseImageContext*>(context);\n\n  auto decode_image = image_context->image_future.get();\n  if (!decode_image) {\n    return nullptr;\n  }\n\n  image_context->texture_image = decode_image->makeTextureImage(\n      image_context->unref_queue->GetContext().get());\n  if (!image_context->texture_image ||\n      !image_context->texture_image->gr_image()) {\n    return nullptr;\n  }\n  GrBackendTexture backend_texture;\n  if (!SkImages::GetBackendTextureFromImage(\n          image_context->texture_image->gr_image(), &backend_texture, false)) {\n    FML_DLOG(ERROR) << \"PromiseImage get backend texture failed\";\n    return nullptr;\n  }\n\n  if (!backend_texture.isValid()) {\n    FML_DLOG(ERROR) << \"PromiseImage backend texture is invalid\";\n    return nullptr;\n  }\n  return SkPromiseImageTexture::Make(backend_texture);\n}\n#else\nstd::shared_ptr<skity::Texture> GetPromiseImage(void* context) {\n  TRACE_EVENT(\"clay\", \"GetPromiseImage\");\n  PromiseImageContext* image_context =\n      static_cast<PromiseImageContext*>(context);\n\n  auto decode_image = image_context->image_future.get();\n  if (!decode_image) {\n    return nullptr;\n  }\n  if (decode_image->isTextureBacked()) {\n    return *(decode_image->gr_image()->GetTexture());\n  }\n\n  image_context->texture_image = decode_image->makeTextureImage(\n      image_context->unref_queue->GetContext().get());\n  if (!image_context->texture_image ||\n      !image_context->texture_image->gr_image()) {\n    return nullptr;\n  }\n  return *(image_context->texture_image->gr_image()->GetTexture());\n}\n#endif  // ENABLE_SKITY\n\nvoid ReleasePromiseImage(void* context) {\n  TRACE_EVENT(\"clay\", \"ReleasePromiseImage\");\n\n  fml::RefPtr<PromiseImageContext> image_context =\n      fml::AdoptRef(static_cast<PromiseImageContext*>(context));\n  // In most cases, this function is called in Raster Thread.\n  // However, currently `skottie::Animation` holds `sk_sp<SkImage>` in UI\n  // thread, makes it possible to release SkImage in UI thread, we must post the\n  // release task to raster thread in this case.\n  fml::TaskRunner::RunNowOrPostTask(image_context->raster_runner,\n                                    [image_context] {});\n}\n}  // namespace\n\nstd::shared_ptr<ImageProducer> ImageProducer::CreateImageProducer(\n    bool is_svg, GrDataPtr data, fml::RefPtr<ImageDescriptor> descriptor,\n    const ImageProduceContext& produce_context,\n    const std::shared_ptr<std::recursive_mutex>& mutex,\n    bool use_texture_backend, bool enable_low_quality_image) {\n  auto ptr = new ImageProducer(is_svg, data, descriptor, produce_context, mutex,\n                               use_texture_backend, enable_low_quality_image);\n  return std::shared_ptr<ImageProducer>(ptr);\n}\n\nImageProducer::ImageProducer(bool is_svg, GrDataPtr data,\n                             fml::RefPtr<ImageDescriptor> descriptor,\n                             const ImageProduceContext& produce_context,\n                             const std::shared_ptr<std::recursive_mutex>& mutex,\n                             bool use_texture_backend,\n                             bool enable_low_quality_image)\n    : is_svg_(is_svg),\n      raw_data_(data),\n      descriptor_(descriptor),\n      produce_context_(produce_context),\n      mutex_(mutex),\n      use_texture_backend_(use_texture_backend) {\n  if (!use_texture_backend_) {\n    // In fact, \"PromiseImage\" is available only when we pretend to use\n    // texture-backend image.\n    produce_context_.use_promise = false;\n  }\n  if (!is_svg) {\n    enable_low_quality_image_ = enable_low_quality_image;\n    if (!descriptor_ && raw_data_) {\n      descriptor_ =\n          ImageDescriptor::Create(raw_data_, enable_low_quality_image);\n    }\n    if (descriptor_) {\n      orig_info_ = descriptor_->image_info();\n    }\n  } else if (is_svg) {\n    // Just a (1, 1) placeholder for EMPTY ImageInfo check. Because `orig_info_`\n    // is meaningless for SVG. We should never use `orig_info_` to do\n    // anything.\n#ifndef ENABLE_SKITY\n    orig_info_ =\n        SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, kPremul_SkAlphaType);\n#else\n    orig_info_ = ImageInfo::makeWH(1, 1);\n#endif  // ENABLE_SKITY\n  }\n}\n\nImageProducer::~ImageProducer() { Reset(); }\n\nbool ImageProducer::IsSingleFrameImage() const {\n  if (!descriptor_) {\n    return true;\n  }\n  return descriptor_->IsSingleFrame();\n}\n\nvoid ImageProducer::SetData(bool is_svg, GrDataPtr data) {\n  Reset();\n\n  if (!data) {\n    return;\n  }\n  raw_data_ = data;\n  if (!is_svg) {\n    EnsureDescriptorInitialized();\n  } else {\n    // Just a (1, 1) placeholder for EMPTY ImageInfo check. Because `orig_info_`\n    // is meaningless for SVG. We should never use `orig_info_` to do\n    // anything.\n#ifndef ENABLE_SKITY\n    orig_info_ =\n        SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, kPremul_SkAlphaType);\n#else\n    orig_info_ = ImageInfo::makeWH(1, 1);\n#endif  // ENABLE_SKITY\n  }\n}\n\n#if defined(ENABLE_SVG)\nvoid ImageProducer::DecodeSVG() {\n  if (!raw_data_) {\n    return;\n  }\n\n  if (!svg_frame_) {\n    svg_frame_ = fml::MakeRefCounted<SVGImageHolder>();\n  }\n\n  svg_frame_->CreateSVGDOM(raw_data_);\n  if (use_texture_backend_) {\n    FetchHardwareSVGImage();\n  } else {\n    FetchSoftwareSVGImage();\n  }\n}\n\n#ifndef ENABLE_SKITY\nstatic void RenderSVG(SkCanvas* canvas, SkSVGDOM* dom,\n                      const SkImageInfo& image_info) {\n  canvas->clear(SK_AlphaTRANSPARENT);\n  auto root = dom->getRoot();\n  auto svg_height = root->getHeight(), svg_width = root->getWidth();\n  if (svg_height.unit() != SkSVGLength::Unit::kPercentage &&\n      svg_width.unit() != SkSVGLength::Unit::kPercentage) {\n    canvas->scale(image_info.width() / root->getWidth().value(),\n                  image_info.height() / root->getHeight().value());\n  } else {\n    dom->setContainerSize(\n        SkSize::Make(image_info.width(), image_info.height()));\n  }\n  dom->render(canvas);\n}\n#endif  // ENABLE_SKITY\n\nvoid ImageProducer::FetchHardwareSVGImage() {\n  if (render_info_.isEmpty()) {\n    FML_DLOG(WARNING) << \"The size of SVG is empty\";\n    return;\n  }\n  auto graphics_image = svg_frame_->GetGraphicsImage();\n  if (!graphics_image || graphics_image->width() < render_info_.width() ||\n      graphics_image->height() < render_info_.height()) {\n    fml::TaskRunner::RunNowOrPostTask(\n        produce_context_.raster_task_runner,\n        [weak = weak_from_this(), unref_queue = produce_context_.unref_queue,\n         render_info = render_info_, svg_frame = svg_frame_,\n         callback_runner = produce_context_.is_deferred\n                               ? produce_context_.raster_task_runner\n                               : produce_context_.ui_task_runner] {\n          if (auto self = weak.lock()) {\n            auto svg_dom = svg_frame->GetSVGDOM();\n            if (!svg_dom) {\n              FML_LOG(WARNING) << \"SVGDOM is null, cannot decode an empty svg.\";\n              return;\n            }\n#ifndef ENABLE_SKITY\n            // Ensure that the Context for the unref_queue has been\n            // properly set.\n            FML_DCHECK(unref_queue->GetContext());\n            sk_sp<SkSurface> surface =\n                SkSurface::MakeRenderTarget(unref_queue->GetContext().get(),\n                                            skgpu::Budgeted::kYes, render_info);\n            if (!surface) {\n              FML_LOG(ERROR) << \"Failed to create SkSurface of SVG\";\n              return;\n            }\n            RenderSVG(surface->getCanvas(), svg_dom.get(), render_info);\n\n            auto gpu_object = GPUObject(\n                GraphicsImage::Make(surface->makeImageSnapshot()), unref_queue);\n#else\n            auto svg_image = svg_dom->Render(render_info.width(),\n                                             render_info.height(), unref_queue);\n            if (!svg_image) {\n              FML_LOG(ERROR) << \"Failed to render SVG\";\n              return;\n            }\n            auto image = GraphicsImage::Make(svg_image);\n            auto gpu_object = GPUObject(\n                image->makeTextureImage(unref_queue->GetContext().get()),\n                unref_queue);\n#endif  // ENABLE_SKITY\n            svg_frame->SetGraphicsImage(\n                fml::MakeRefCounted<GpuGraphicsImageWrapper>(\n                    std::move(gpu_object)));\n            fml::TaskRunner::RunNowOrPostTask(callback_runner, [weak, svg_frame,\n                                                                render_info] {\n              if (auto self = weak.lock()) {\n                // For deferred images, modifications to the frame can occur on\n                // either the UI thread or the raster thread, so locking is\n                // necessary to ensure safe access. For the ImageProducer, all\n                // public methods are accessed through the Image, and the Image\n                // already locks when accessing the ImageProducer. Therefore,\n                // most methods do not require additional locking. However, this\n                // piece of code is called by the ImageProducer's own member\n                // function via a post task, so it requires locking.\n                std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n                if (self->produce_context_.is_deferred) {\n                  lock =\n                      std::make_unique<std::lock_guard<std::recursive_mutex>>(\n                          *self->mutex_);\n                }\n\n                if (self->produce_context_.decode_callback) {\n                  self->produce_context_.decode_callback(true);\n                }\n                if (self->produce_context_.decode_with_priority &&\n                    self->produce_context_.upload_callback) {\n                  self->produce_context_.upload_callback(true);\n                }\n              }\n            });\n          }\n        });\n  }\n}\n\nvoid ImageProducer::FetchSoftwareSVGImage() {\n  if (render_info_.isEmpty()) {\n    FML_DLOG(WARNING) << \"The size of SVG is empty\";\n    return;\n  }\n  auto graphics_image = svg_frame_->GetGraphicsImage();\n  if (!graphics_image || graphics_image->width() < render_info_.width() ||\n      graphics_image->height() < render_info_.height()) {\n    GraphicsIsolate::Instance().GetConcurrentWorkerTaskRunner()->PostTask(\n        [weak = weak_from_this(), render_info = render_info_,\n         svg_frame = svg_frame_,\n         callback_runner = produce_context_.is_deferred\n                               ? produce_context_.raster_task_runner\n                               : produce_context_.ui_task_runner]() {\n          if (auto self = weak.lock()) {\n            auto svg_dom = svg_frame->GetSVGDOM();\n            if (!svg_dom) {\n              FML_LOG(WARNING) << \"SVGDOM is null, cannot decode an empty svg.\";\n              return;\n            }\n#ifndef ENABLE_SKITY\n            const size_t min_row_bytes =\n                render_info.minRowBytes();  // bytes used by one bitmap row\n            const size_t size = render_info.computeMinByteSize();\n            // bytes used by all rows\n            SkColor* pixels = new SkColor[size];\n            auto canvas =\n                SkCanvas::MakeRasterDirect(render_info, pixels, min_row_bytes);\n            if (!canvas) {\n              FML_LOG(ERROR) << \"Failed to create SkCanvas of SVG\";\n              delete[] pixels;\n              return;\n            }\n            RenderSVG(canvas.get(), svg_dom.get(), render_info);\n\n            auto sk_data = SkData::MakeWithProc(\n                pixels, size * sizeof(SkColor),\n                [](const void* ptr, void* context) {\n                  delete[] static_cast<const SkColor*>(context);\n                },\n                pixels);\n            auto image = GraphicsImage::MakeRasterData(render_info, sk_data,\n                                                       min_row_bytes);\n#else\n            auto svg_image = svg_dom->Render(render_info.width(),\n                                             render_info.height(), nullptr);\n            if (!svg_image) {\n              FML_LOG(ERROR) << \"Failed to render SVG\";\n              return;\n            }\n            auto image = GraphicsImage::Make(svg_image);\n#endif  // ENABLE_SKITY\n            svg_frame->SetGraphicsImage(\n                fml::MakeRefCounted<CpuGraphicsImageWrapper>(image));\n            fml::TaskRunner::RunNowOrPostTask(callback_runner, [weak, svg_frame,\n                                                                render_info] {\n              if (auto self = weak.lock()) {\n                // For deferred images, modifications to the frame can occur on\n                // either the UI thread or the raster thread, so locking is\n                // necessary to ensure safe access. For the ImageProducer, all\n                // public methods are accessed through the Image, and the Image\n                // already locks when accessing the ImageProducer. Therefore,\n                // most methods do not require additional locking. However, this\n                // piece of code is called by the ImageProducer's own member\n                // function via a post task, so it requires locking.\n                std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n                if (self->produce_context_.is_deferred) {\n                  lock =\n                      std::make_unique<std::lock_guard<std::recursive_mutex>>(\n                          *self->mutex_);\n                }\n\n                if (self->produce_context_.decode_callback) {\n                  self->produce_context_.decode_callback(true);\n                }\n              }\n            });\n          }\n        });\n  }\n}\n\nfml::RefPtr<SVGImageHolder> ImageProducer::GetSVGFrame() const {\n  return svg_frame_;\n}\n#endif\n\nbool ImageProducer::SVGFrameReady() const {\n#if defined(ENABLE_SVG)\n  if (svg_frame_ && svg_frame_->GetSVGDOM() && svg_frame_->GetGraphicsImage() &&\n      IMAGE_DIMENSION(svg_frame_->GetGraphicsImage()->dimensions()) ==\n          render_info_.dimensions()) {\n    return true;\n  }\n#endif\n  return false;\n}\n\nvoid ImageProducer::Reset(bool reuse) {\n#if defined(ENABLE_SVG)\n  svg_frame_ = nullptr;\n#endif\n  decoding_ = false;\n  codec_ = nullptr;\n  current_frame_index_ = -1;\n  next_frame_ = nullptr;\n  if (!reuse) {\n    frame_count_ = 1;\n    should_use_cache_ = true;\n    descriptor_ = nullptr;\n    orig_info_.reset();\n    render_info_.reset();\n    ReleaseAllFrames();\n  }\n}\n\nvoid ImageProducer::EnsureDescriptorInitialized() {\n  if (is_svg_) {\n    return;\n  }\n  if (!raw_data_) {\n    FML_LOG(ERROR) << \"The data is null, cannot create ImageDescriptor\";\n    return;\n  }\n  if (descriptor_) {\n    return;\n  }\n\n  descriptor_ = ImageDescriptor::Create(raw_data_, enable_low_quality_image_);\n  if (!descriptor_) {\n    FML_LOG(ERROR) << \"Failed to create ImageDescriptor\";\n    return;\n  }\n  orig_info_ = descriptor_->image_info();\n}\n\nbool ImageProducer::PrepareCodec() {\n  EnsureDescriptorInitialized();\n  if (!descriptor_ || orig_info_.isEmpty()) {\n    return false;\n  }\n  if (render_info_.isEmpty() || !descriptor_->IsSingleFrame()) {\n    // SetExpectSizeCalculator not be called. so use orig_info_.\n    render_info_ = orig_info_;\n  }\n\n  if (!codec_) {\n    codec_ = descriptor_->InstantiateCodec(render_info_.width(),\n                                           render_info_.height());\n    frame_count_ = codec_->FrameCount();\n    if (frame_count_ > 1) {\n      should_use_cache_ = false;\n    }\n  }\n\n  return !!codec_;\n}\n\nvoid ImageProducer::DecodeNextFrame() {\n  // For single-frame image, if it have already been decoded, there is no\n  // need to trigger decoding for the next frame. This return statement prevents\n  // a second decoding from being triggered, ensuring that each single-frame\n  // image is decoded only once.\n  if (CurrentFrameReady() && frame_count_ == 1) {\n    return;\n  }\n\n  if (use_texture_backend_) {\n    AsyncDecodeNextFrame();\n  } else {\n    SyncDecodeNextFrame();\n  }\n}\n\nvoid ImageProducer::AsyncDecodeNextFrame() {\n  if (!codec_) {\n    if (!PrepareCodec()) {\n      FML_LOG(ERROR) << \"Image codec must not be null\";\n      return;\n    }\n  }\n\n  if (decoding_) {\n    return;\n  }\n  decoding_ = true;\n\n  auto next_frame =\n      fml::MakeRefCounted<SkImageHolder>(produce_context_.decode_with_priority);\n  next_frame->MarkLoadStart();\n  if (produce_context_.use_promise) {\n    DecodePromiseFrame(next_frame);\n  } else {\n    if (produce_context_.decode_with_priority) {\n      DecodeBackendTextureFrameWithPriority(next_frame);\n    } else {\n      DecodeBackendTextureFrame(next_frame);\n    }\n  }\n}\n\nvoid ImageProducer::DecodeBackendTextureFrame(\n    const fml::RefPtr<clay::SkImageHolder>& frame) {\n  codec_->NextFrame([weak = weak_from_this(), next_frame = frame,\n                     unref_queue = produce_context_.unref_queue,\n                     raster_runner = produce_context_.raster_task_runner,\n                     callback_runner = produce_context_.is_deferred\n                                           ? produce_context_.raster_task_runner\n                                           : produce_context_.ui_task_runner](\n                        FrameInfo frame_info) {\n    TRACE_EVENT(\"clay\", \"DecodeFinish\");\n    if (!weak.lock()) {\n      return;\n    }\n\n    if (!frame_info.image) {\n      callback_runner->PostTask([weak, next_frame]() {\n        if (auto self = weak.lock()) {\n          self->OnFrameDecoded(next_frame, false, true);\n        }\n      });\n      return;\n    }\n\n    raster_runner->PostTask([weak, next_frame, unref_queue, callback_runner,\n                             frame_info] {\n      FrameInfo new_frame_info = frame_info;\n      TRACE_EVENT(\"clay\", \"makeTextureImage\");\n      // Ensure that the Context for the unref_queue has been\n      // properly set.\n      FML_DCHECK(unref_queue->GetContext());\n      new_frame_info.image = new_frame_info.image->makeTextureImage(\n          unref_queue->GetContext().get());\n      if (new_frame_info.image) {\n        new_frame_info.image->flushAndSubmit(unref_queue->GetContext().get());\n        callback_runner->PostTask(\n            [weak, next_frame, unref_queue, new_frame_info] {\n              // Create GPUObject with UnrefQueue before 'if' to\n              // make sure it can be destroyed in UI thread safely\n              auto gpu_object = GPUObject(new_frame_info.image, unref_queue);\n              if (auto self = weak.lock()) {\n                next_frame->CacheFrame(new_frame_info.duration,\n                                       std::move(gpu_object));\n                self->OnFrameDecoded(next_frame, true, true);\n              }\n            });\n      } else {\n        callback_runner->PostTask([weak, next_frame] {\n          if (auto self = weak.lock()) {\n            self->OnFrameDecoded(next_frame, false, true);\n          }\n        });\n      }\n    });\n  });\n}\n\nvoid ImageProducer::DecodePromiseFrame(\n    const fml::RefPtr<clay::SkImageHolder>& frame) {\n  fml::RefPtr<PromiseImageContext> promise_context =\n      fml::MakeRefCounted<PromiseImageContext>();\n  promise_context->unref_queue = produce_context_.unref_queue;\n  std::shared_ptr<SafePromise<fml::RefPtr<GraphicsImage>>> promise_image =\n      std::make_shared<SafePromise<fml::RefPtr<GraphicsImage>>>();\n  promise_context->image_future = promise_image->GetFuture();\n  promise_context->raster_runner = produce_context_.raster_task_runner;\n\n  codec_->NextFrame([weak = weak_from_this(), next_frame = frame,\n                     promise_image = std::move(promise_image),\n                     unref_queue = produce_context_.unref_queue,\n                     raster_runner = produce_context_.raster_task_runner,\n                     callback_runner = produce_context_.is_deferred\n                                           ? produce_context_.raster_task_runner\n                                           : produce_context_.ui_task_runner](\n                        FrameInfo frame_info) {\n    TRACE_EVENT(\"clay\", \"DecodeFinish\");\n    if (!weak.lock()) {\n      return;\n    }\n    if (!frame_info.image) {\n      callback_runner->PostTask([weak, next_frame]() {\n        if (auto self = weak.lock()) {\n          self->OnFrameDecoded(next_frame, false, true);\n        }\n      });\n      return;\n    }\n    promise_image->SetValue(frame_info.image);\n    callback_runner->PostTask([unref_queue, frame_info, next_frame, weak] {\n      if (auto self = weak.lock()) {\n        next_frame->CacheFrame(frame_info.duration,\n                               {frame_info.image, unref_queue}, true);\n        self->OnFrameDecoded(next_frame, true, true);\n      }\n    });\n  });\n\n  // Ensure that the Context for the unref_queue has been\n  // properly set.\n  FML_DCHECK(produce_context_.unref_queue->GetContext());\n  auto info = GetInfo();\n#ifndef ENABLE_SKITY\n  sk_sp<GrContextThreadSafeProxy> gpu_context_proxy =\n      produce_context_.unref_queue->GetContext()->threadSafeProxy();\n  GrBackendFormat backend_format = gpu_context_proxy->defaultBackendFormat(\n      info.colorType(), GrRenderable::kYes);\n  promise_context->AddRef();\n  frame->SetPromiseImage(\n      {GraphicsImage::MakePromiseTexture(\n           gpu_context_proxy, backend_format, {info.width(), info.height()},\n           GrMipmapped::kNo, kTopLeft_GrSurfaceOrigin, info.colorType(),\n           info.alphaType(), info.refColorSpace(), GetPromiseImage,\n           ReleasePromiseImage, promise_context.get()),\n       produce_context_.unref_queue});\n#else\n  promise_context->AddRef();\n  frame->SetPromiseImage(\n      {GraphicsImage::MakePromiseTexture(\n           skity::TextureFormat::kRGBA, info.width(), info.height(),\n           ConvertToSkityAlphaType(info.alphaType()), GetPromiseImage,\n           ReleasePromiseImage, promise_context.get()),\n       produce_context_.unref_queue});\n#endif  // ENABLE_SKITY\n  frame->MarkLoadResult(true);\n  next_frame_ = frame;\n}\n\nvoid ImageProducer::DecodeBackendTextureFrameWithPriority(\n    const fml::RefPtr<clay::SkImageHolder>& frame) {\n  codec_->NextFrame([weak = weak_from_this(), next_frame = frame,\n                     callback_runner = produce_context_.is_deferred\n                                           ? produce_context_.raster_task_runner\n                                           : produce_context_.ui_task_runner](\n                        FrameInfo frame_info) {\n    TRACE_EVENT(\"clay\", \"DecodeFinish\");\n    if (!weak.lock()) {\n      return;\n    }\n\n    if (!frame_info.image) {\n      callback_runner->PostTask([weak, next_frame]() {\n        if (auto self = weak.lock()) {\n          self->OnFrameDecoded(next_frame, false, true);\n        }\n      });\n      return;\n    }\n    callback_runner->PostTask([weak, next_frame, frame_info] {\n      if (auto self = weak.lock()) {\n        self->RegisterUploadTask(next_frame, frame_info);\n        self->OnFrameDecoded(next_frame, true, true);\n      }\n    });\n  });\n}\n\nvoid ImageProducer::SyncDecodeNextFrame() {\n  if (!codec_) {\n    if (!PrepareCodec()) {\n      FML_LOG(ERROR) << \"Image codec must not be null\";\n      return;\n    }\n  }\n\n  auto next_frame = fml::MakeRefCounted<SkImageHolder>();\n\n  std::promise<fml::RefPtr<SkImageHolder>> next_frame_promise;\n  std::future<fml::RefPtr<SkImageHolder>> next_frame_future =\n      next_frame_promise.get_future();\n  next_frame->MarkLoadStart();\n  codec_->NextFrame([weak = weak_from_this(), next_frame = next_frame,\n                     unref_queue = produce_context_.unref_queue,\n                     &next_frame_promise](FrameInfo frame_info) {\n    TRACE_EVENT(\"clay\", \"DecodeFinish\");\n    if (!weak.lock()) {\n      next_frame_promise.set_value(nullptr);\n      return;\n    }\n    if (frame_info.image) {\n      next_frame->CacheFrame(frame_info.duration,\n                             {frame_info.image, unref_queue});\n    }\n    next_frame_promise.set_value(next_frame);\n  });\n  auto next_frame_result = next_frame_future.get();\n  if (!next_frame_result) {\n    return;\n  }\n  OnFrameDecoded(next_frame_result, next_frame_result->FrameIsReady());\n}\n\nvoid ImageProducer::UpdateCurrentFrame() {\n  current_frame_index_ = (current_frame_index_ + 1) % FrameCount();\n\n  if (next_frame_ && next_frame_ != current_frame_ &&\n      next_frame_->GetGraphicsImage()) {\n    if (current_frame_) {\n      current_frame_->ReleaseResource();\n    }\n    current_frame_ = next_frame_;\n  }\n  next_frame_ = nullptr;\n}\n\nvoid ImageProducer::RegisterUploadTask(\n    const fml::RefPtr<clay::SkImageHolder>& frame,\n    const FrameInfo& frame_info) {\n  FML_DCHECK(!produce_context_.is_deferred);\n\n  auto upload_task = [weak = weak_from_this(), next_frame = frame, frame_info,\n                      unref_queue = produce_context_.unref_queue,\n                      callback_runner = produce_context_.ui_task_runner] {\n    FrameInfo new_frame_info = frame_info;\n    TRACE_EVENT(\"clay\", \"makeTextureImage\");\n    // Ensure that the Context for the unref_queue has been\n    // properly set.\n    FML_DCHECK(unref_queue->GetContext());\n    new_frame_info.image =\n        new_frame_info.image->makeTextureImage(unref_queue->GetContext().get());\n    if (new_frame_info.image) {\n      new_frame_info.image->flushAndSubmit(unref_queue->GetContext().get());\n      callback_runner->PostTask(\n          [weak, next_frame, unref_queue, new_frame_info] {\n            // Create GPUObject with UnrefQueue before 'if' to\n            // make sure it can be destroyed in UI thread safely\n            auto gpu_object = GPUObject(new_frame_info.image, unref_queue);\n            if (auto self = weak.lock()) {\n              next_frame->CacheFrame(new_frame_info.duration,\n                                     std::move(gpu_object));\n              self->OnFrameUploaded(next_frame, true);\n            }\n          });\n    } else {\n      callback_runner->PostTask([weak, next_frame] {\n        if (auto self = weak.lock()) {\n          self->OnFrameUploaded(next_frame, false);\n        }\n      });\n    }\n  };\n\n  if (produce_context_.register_upload_callback) {\n    produce_context_.register_upload_callback(upload_task);\n  }\n}\n\nfml::RefPtr<SkImageHolder> ImageProducer::GetCurrentFrame() const {\n  return current_frame_;\n}\n\nbool ImageProducer::NextFrameReady() const {\n  return next_frame_ && next_frame_->FrameIsReady() &&\n         IMAGE_DIMENSION(next_frame_->GetGraphicsImage()->dimensions()) ==\n             render_info_.dimensions();\n}\n\nbool ImageProducer::CurrentFrameReady() const {\n  return current_frame_ && current_frame_->FrameIsReady() &&\n         IMAGE_DIMENSION(current_frame_->GetGraphicsImage()->dimensions()) ==\n             render_info_.dimensions();\n}\n\nvoid ImageProducer::TryDestroyCodec() {\n  if (FrameCount() == 1) {\n    codec_ = nullptr;\n  }\n}\n\nconst GrImageInfo& ImageProducer::GetInfo() {\n  EnsureDescriptorInitialized();\n  if (!descriptor_ && !is_svg_) {\n    render_info_.reset();\n    return render_info_;\n  }\n  if (!render_info_.isEmpty()) {\n    return render_info_;\n  }\n  return orig_info_;\n}\n\nbool ImageProducer::ShouldCacheImage(const fml::RefPtr<SkImageHolder>& holder) {\n  return should_use_cache_ &&\n         holder->GetAllocationSize() < kMaxMemoryLimitPerFrame;\n}\n\nvoid ImageProducer::OnFrameUploaded(const fml::RefPtr<SkImageHolder>& holder,\n                                    bool result) {\n  // holder can't be null\n  FML_DCHECK(holder);\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (produce_context_.is_deferred) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  holder->MarkUploadResult(result);\n\n  if (!produce_context_.use_promise) {\n    next_frame_ = holder;\n  }\n\n  if (produce_context_.upload_callback) {\n    produce_context_.upload_callback(result);\n  }\n}\n\nvoid ImageProducer::OnFrameDecoded(const fml::RefPtr<SkImageHolder>& holder,\n                                   bool result, bool is_async) {\n  // holder can't be null\n  FML_DCHECK(holder);\n\n  // For deferred image, modifications to the frame can occur on either the ui\n  // thread or the raster thread, so locking is necessary to ensure safe access.\n  // For the ImageProducer, all public methods are accessed through the Image,\n  // and the Image already locks when accessing the ImageProducer. Therefore,\n  // most methods do not require additional locking. However, the OnFrameDecoded\n  // method is called by the ImageProducer's own member function via a post\n  // task, so it requires locking.\n  std::unique_ptr<std::lock_guard<std::recursive_mutex>> lock;\n  if (produce_context_.is_deferred && is_async) {\n    lock = std::make_unique<std::lock_guard<std::recursive_mutex>>(*mutex_);\n  }\n\n  if (use_texture_backend_) {\n    decoding_ = false;\n    // If it is a multi frame image, there might be more images needing decode.\n    if (IsSingleFrameImage()) {\n      decoded_ = true;\n    }\n  }\n\n  TryDestroyCodec();\n\n  holder->MarkLoadResult(result);\n\n  if (use_texture_backend_) {\n    // Won't use GPUResourceCache to manager image texture resource to avoid\n    // image blink or other problems. if (FrameCount() == 1) {\n    //   holder->SetUseCache(ShouldCacheImage(holder));\n    //   GraphicsIsolate::Instance().CacheStoreImage(holder);\n    // }\n  }\n\n  if (!produce_context_.use_promise && !produce_context_.decode_with_priority) {\n    next_frame_ = holder;\n  }\n\n  if (produce_context_.decode_callback) {\n    produce_context_.decode_callback(result);\n  }\n}\n\nvoid ImageProducer::ReleaseAllFrames() {\n  current_frame_ = nullptr;\n  next_frame_ = nullptr;\n#if defined(ENABLE_SVG)\n  svg_frame_ = nullptr;\n#endif\n}\n\nsize_t ImageProducer::GetAllocationSize() const {\n  if (is_svg_) {\n#if defined(ENABLE_SVG)\n    return svg_frame_->GetAllocationSize();\n#else\n    return 0;\n#endif\n  }\n  if (current_frame_ && current_frame_->FrameIsReady()) {\n    return current_frame_->GetAllocationSize();\n  }\n  if (next_frame_ && next_frame_->FrameIsReady()) {\n    return next_frame_->GetAllocationSize();\n  }\n  return 0;\n}\n\nbool ImageProducer::WillNextFrameSizeChange() const {\n  if (is_svg_) {\n    return true;\n  }\n  if (!next_frame_ || !next_frame_->FrameIsReady()) {\n    return false;\n  }\n  if (!current_frame_ || !current_frame_->FrameIsReady()) {\n    return true;\n  }\n  return current_frame_->GetGraphicsImage()->dimensions() !=\n         next_frame_->GetGraphicsImage()->dimensions();\n}\n\nvoid ImageProducer::SetExpectSizeCalculator(\n    std::function<skity::Vec2(skity::Vec2)> calculator,\n    bool force_use_original_size) {\n  EnsureDescriptorInitialized();\n  if (!descriptor_ && !is_svg_) {\n    return;\n  }\n\n  if (descriptor_ && !descriptor_->IsSingleFrame()) {\n    return;\n  }\n\n  // orig_info_.isEmpty() means raw data is broken.\n  if (orig_info_.isEmpty()) {\n    return;\n  }\n\n  if (orig_info_ == render_info_ && !is_svg_) {\n    // Already max, ignore expect size to avoid recursively fallback.\n    return;\n  }\n\n#if CACHE_IMAGE_BY_DRAWING_SIZE\n#ifndef ENABLE_SKITY\n  skity::Vec2 expect_size = calculator(skity::Vec2(\n      orig_info_.dimensions().width(), orig_info_.dimensions().height()));\n#else\n  skity::Vec2 expect_size =\n      calculator(skity::Vec2(orig_info_.width(), orig_info_.height()));\n#endif  // ENABLE_SKITY\n  // if origin_size can contain expect_size, use expect_size, else origin_size.\n  auto chooser = [this, &expect_size, force_use_original_size]() {\n    if (expect_size.x > orig_info_.width() ||\n        expect_size.y > orig_info_.height() || force_use_original_size) {\n      if (is_svg_) {\n        render_info_ = orig_info_.makeWH(\n            std::max(static_cast<int>(expect_size.x), render_info_.width()),\n            std::max(static_cast<int>(expect_size.y), render_info_.height()));\n      } else {\n        render_info_ = orig_info_;\n      }\n    } else {\n#ifndef ENABLE_SKITY\n      render_info_ = orig_info_.makeDimensions(\n          SkISize::Make(expect_size.x, expect_size.y));\n#else\n      render_info_ = ImageInfo::makeWH(expect_size.x, expect_size.y);\n#endif  // ENABLE_SKITY\n    }\n  };\n\n  // If hasn't set, just choose a proper size.\n  if (render_info_.isEmpty()) {\n    chooser();\n    return;\n  }\n\n  // If expect_size enlarged, choose a new proper size.\n  if (expect_size.x > render_info_.width() ||\n      expect_size.y > render_info_.height() || force_use_original_size) {\n    int old_width = render_info_.width();\n    int old_height = render_info_.height();\n    chooser();\n    // If size changed, release all frames.\n    if (old_width != render_info_.width() ||\n        old_height != render_info_.height()) {\n      ReleaseAllFrames();\n    }\n  }\n#endif  // CACHE_IMAGE_BY_DRAWING_SIZE\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image_producer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_PRODUCER_H_\n#define CLAY_GFX_IMAGE_IMAGE_PRODUCER_H_\n\n#include <memory>\n#include <vector>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#include \"clay/gfx/image/frame_info.h\"\n#include \"clay/gfx/image/image.h\"\n#include \"clay/gfx/image/image_produce_context.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\n#if defined(ENABLE_SVG)\n#include \"clay/gfx/image/svg_image_holder.h\"\n#endif\n\nnamespace clay {\n\nclass Codec;\nclass ImageDescriptor;\n#if defined(ENABLE_SVG)\nclass SVGImageHolder;\n#endif\nclass SkImageHolder;\n\n// ImageProducer is responsible for decoding image raw data and caching all\n// frames of the image.\nclass ImageProducer : public std::enable_shared_from_this<ImageProducer> {\n public:\n  static std::shared_ptr<ImageProducer> CreateImageProducer(\n      bool is_svg, GrDataPtr data, fml::RefPtr<ImageDescriptor> descriptor,\n      const ImageProduceContext& produce_context,\n      const std::shared_ptr<std::recursive_mutex>& mutex,\n      bool use_texture_backend, bool enable_low_quality_image = false);\n  ~ImageProducer();\n\n  void SetData(bool is_svg, GrDataPtr data);\n  GrDataPtr GetData() { return raw_data_; }\n  size_t GetDataSize() { return raw_data_ ? DATA_GET_SIZE(raw_data_) : 0; }\n  fml::RefPtr<ImageDescriptor> GetDescriptor() const { return descriptor_; }\n  void SetExpectSizeCalculator(\n      std::function<skity::Vec2(skity::Vec2)> calculator,\n      bool force_use_original_size);\n\n#if defined(ENABLE_SVG)\n  void DecodeSVG();\n  fml::RefPtr<SVGImageHolder> GetSVGFrame() const;\n#endif  // ENABLE_SVG\n\n  const GrImageInfo& GetInfo();\n  size_t FrameCount() const { return frame_count_; }\n\n  void OnFrameDecoded(const fml::RefPtr<SkImageHolder>& holder, bool result,\n                      bool is_async = false);\n  void OnFrameUploaded(const fml::RefPtr<SkImageHolder>& holder, bool result);\n\n  void Reset(bool reuse = false);\n\n  void DecodeNextFrame();\n  void UpdateCurrentFrame();\n  fml::RefPtr<SkImageHolder> GetCurrentFrame() const;\n  bool NextFrameReady() const;\n  bool CurrentFrameReady() const;\n  bool SVGFrameReady() const;\n  bool IsSingleFrameImage() const;\n  bool EnableLowQualityImage() const { return enable_low_quality_image_; }\n  bool WillNextFrameSizeChange() const;\n\n  bool NeedDecode() const { return !decoded_ && !decoding_; }\n\n  size_t GetAllocationSize() const;\n\n private:\n  ImageProducer(bool is_svg, GrDataPtr data,\n                fml::RefPtr<ImageDescriptor> descriptor,\n                const ImageProduceContext& produce_context,\n                const std::shared_ptr<std::recursive_mutex>& mutex,\n                bool use_texture_backend,\n                bool enable_low_quality_image = false);\n\n  void AsyncDecodeNextFrame();\n  void DecodePromiseFrame(const fml::RefPtr<clay::SkImageHolder>& frame);\n  void DecodeBackendTextureFrame(const fml::RefPtr<clay::SkImageHolder>& frame);\n  void DecodeBackendTextureFrameWithPriority(\n      const fml::RefPtr<clay::SkImageHolder>& frame);\n  void SyncDecodeNextFrame();\n\n  bool PrepareCodec();\n  void ReleaseAllFrames();\n  void TryDestroyCodec();\n  void EnsureDescriptorInitialized();\n  bool ShouldCacheImage(const fml::RefPtr<SkImageHolder>& holder);\n  void RegisterUploadTask(const fml::RefPtr<clay::SkImageHolder>& frame,\n                          const FrameInfo& frame_info);\n#if defined(ENABLE_SVG)\n  void FetchSoftwareSVGImage();\n  void FetchHardwareSVGImage();\n#endif\n\n  size_t frame_count_ = 0;\n  int current_frame_index_ = -1;\n  GrImageInfo orig_info_;    // Original image size.\n  GrImageInfo render_info_;  // The actual image size after decoding.\n  bool is_svg_;\n  GrDataPtr raw_data_;\n#if defined(ENABLE_SVG)\n  fml::RefPtr<SVGImageHolder> svg_frame_;\n#endif\n  fml::RefPtr<SkImageHolder> current_frame_;\n  fml::RefPtr<SkImageHolder> next_frame_;\n  fml::RefPtr<Codec> codec_;\n  fml::RefPtr<ImageDescriptor> descriptor_;\n  bool enable_low_quality_image_ = false;\n  bool decoding_ = false;\n  // `decoded_` only affect single frame image.\n  bool decoded_ = false;\n  ImageProduceContext produce_context_;\n  std::shared_ptr<std::recursive_mutex> mutex_;\n\n  bool should_use_cache_ = true;\n  bool use_texture_backend_ = true;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_PRODUCER_H_\n"
  },
  {
    "path": "clay/gfx/image/image_resource.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image_resource.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/image/image.h\"\n#include \"clay/gfx/image/image_resource_client.h\"\n\nnamespace clay {\n\nImageResource::ImageResource(std::shared_ptr<Image> image, bool owned_by_raster)\n    : owned_by_raster_(owned_by_raster), image_(image) {\n  FML_DCHECK(image_);\n  image_->AccessorCreated(this);\n}\n\nImageResource::ImageResource(const ImageResource& other)\n    : image_(other.image_) {\n  FML_DCHECK(image_);\n  image_->AccessorCreated(this);\n}\n\nImageResource::~ImageResource() { ReleaseImage(); }\n\nbool ImageResource::IsSVG() const {\n#if defined(ENABLE_SVG)\n  return image_ && image_->IsSVG();\n#else\n  return false;\n#endif\n}\n\nvoid ImageResource::ReleaseImage() {\n  if (image_) {\n    image_->AccessorDestroyed(this);\n    image_ = nullptr;\n  }\n}\n\nstd::string ImageResource::GetUrl() const {\n  if (image_) {\n    return image_->GetUrl();\n  }\n  return \"\";\n}\n\nvoid ImageResource::SetResourceReleased() { image_ = nullptr; }\n\nfml::RefPtr<GraphicsImage> ImageResource::GetGraphicsImage(\n    DecodePriority priority) const {\n  if (image_for_test_) {\n    return image_for_test_;\n  }\n\n  if (image_ && image_->IsDeferred() && !image_->DecodeWithPriority()) {\n    return GraphicsImage::MakeLazy(image_->shared_from_this());\n  }\n\n  if (image_) {\n    return image_->GetGraphicsImage(priority);\n  }\n  return nullptr;\n}\n\nvoid ImageResource::SetTestImage(fml::RefPtr<GraphicsImage> img) {\n  image_for_test_ = img;\n}\n\nint ImageResource::GetWidth() const {\n  if (image_) {\n    return image_->GetWidth();\n  }\n  return 0;\n}\n\nint ImageResource::GetHeight() const {\n  if (image_) {\n    return image_->GetHeight();\n  }\n  return 0;\n}\n\nvoid ImageResource::AddImageResourceClient(ImageResourceClient* client) {\n  if (!client) {\n    return;\n  }\n  clients_.emplace(client);\n}\n\nvoid ImageResource::RemoveImageResourceClient(ImageResourceClient* client) {\n  if (!client) {\n    return;\n  }\n  clients_.erase(client);\n}\n\nvoid ImageResource::DecodeFinish(bool success) {\n  for (auto client : clients_) {\n    if (image_ && image_->UseTextureBackend() &&\n        !image_->DecodeWithPriority()) {\n      client->RequestRenderImage(this, success);\n    }\n    client->DecodeImageFinish(success, GetUrl());\n  }\n}\n\nvoid ImageResource::UploadFinish(bool success) {\n  for (auto client : clients_) {\n    if (image_ && image_->UseTextureBackend()) {\n      client->RequestRenderImage(this, success);\n    }\n  }\n}\n\nvoid ImageResource::RegisterUploadTask(OneShotCallback<>&& task, int image_id) {\n  for (auto client : clients_) {\n    client->RegisterUploadTask(std::move(task), image_id);\n  }\n}\n\nvoid ImageResource::AnimationAdvanced() {\n  for (auto client : clients_) {\n    client->OnImageChanged();\n  }\n}\n\nbool ImageResource::ShouldPauseAnimation() {\n  for (auto* client : clients_) {\n    if (client->WillRenderImage()) {\n      return false;\n    }\n  }\n  return true;\n}\n\nvoid ImageResource::OnStartPlay() {\n  for (auto* client : clients_) {\n    client->OnStartPlay();\n  }\n}\n\nvoid ImageResource::OnCurrentLoopComplete() {\n  for (auto* client : clients_) {\n    client->OnCurrentLoopComplete();\n  }\n}\n\nvoid ImageResource::OnFinalLoopComplete() {\n  for (auto* client : clients_) {\n    client->OnFinalLoopComplete();\n  }\n}\n\nsize_t ImageResource::GetGraphicsImageAllocSize() const {\n  if (image_) {\n    return image_->GetGraphicsImageAllocSize();\n  }\n  return 0;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image_resource.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_RESOURCE_H_\n#define CLAY_GFX_IMAGE_IMAGE_RESOURCE_H_\n\n#include <memory>\n#include <string>\n#include <unordered_set>\n\n#include \"clay/gfx/image/skimage_holder.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/shell/common/one_shot_callback.h\"\n#include \"clay/ui/rendering/decode_utils.h\"\n\nnamespace clay {\n\nclass Image;\nclass ImageResourceClient;\n\n// Image reference diagram:\n//\n// Outer --> ImageResource [ GetSkImage ]\n//              |___Image [ Animation & Manager data ]\n//                    |___ImageProducer [ Decoding and caching ]\n//                          |__-->SkImageHolder [ GPU Resource cache ]\n//\n// Component/RenderObject will hold ImageResource to access or release SkImage.\nclass ImageResource {\n public:\n  explicit ImageResource(std::shared_ptr<Image>, bool owned_by_raster = false);\n  ImageResource(const ImageResource& other);\n  ~ImageResource();\n\n  bool IsSVG() const;\n  std::string GetUrl() const;\n  Image* GetImage() const { return image_.get(); }\n  std::shared_ptr<Image> GetImageShared() const { return image_; }\n  fml::RefPtr<GraphicsImage> GetGraphicsImage(\n      DecodePriority priority = DecodePriority::kImmediate) const;\n  int GetWidth() const;\n  int GetHeight() const;\n  void SetResourceReleased();\n\n  void AddImageResourceClient(ImageResourceClient*);\n  void RemoveImageResourceClient(ImageResourceClient*);\n\n  void AnimationAdvanced();\n  void DecodeFinish(bool success);\n  void UploadFinish(bool success);\n  void RegisterUploadTask(OneShotCallback<>&& task, int image_id);\n  bool ShouldPauseAnimation();\n\n  bool OwnedByRaster() const { return owned_by_raster_; }\n\n  void OnStartPlay();\n  void OnCurrentLoopComplete();\n  void OnFinalLoopComplete();\n\n  // Only for testing\n  void SetTestImage(fml::RefPtr<GraphicsImage> img);\n\n  const std::unordered_set<ImageResourceClient*>& GetClients() const {\n    return clients_;\n  }\n\n  size_t GetGraphicsImageAllocSize() const;\n\n private:\n  void ReleaseImage();\n  bool owned_by_raster_;\n  fml::RefPtr<GraphicsImage> image_for_test_;\n  std::shared_ptr<Image> image_ = nullptr;\n  std::unordered_set<ImageResourceClient*> clients_;\n};\n}  // namespace clay\n#endif  // CLAY_GFX_IMAGE_IMAGE_RESOURCE_H_\n"
  },
  {
    "path": "clay/gfx/image/image_resource_client.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_RESOURCE_CLIENT_H_\n#define CLAY_GFX_IMAGE_IMAGE_RESOURCE_CLIENT_H_\n\n#include <string>\n\n#include \"clay/shell/common/one_shot_callback.h\"\n#include \"clay/ui/rendering/decode_utils.h\"\n\nnamespace clay {\n\nclass ImageResource;\n\nclass ImageResourceClient {\n public:\n  virtual bool WillRenderImage() = 0;\n  virtual void RequestRenderImage(ImageResource* image_resource,\n                                  bool success) = 0;\n  virtual void OnImageChanged() = 0;\n  virtual void DecodeImageFinish(bool success, const std::string& url) {}\n\n  virtual void RegisterUploadTask(OneShotCallback<>&& task, int image_id) {}\n\n  virtual void OnStartPlay() {}\n  virtual void OnCurrentLoopComplete() {}\n  virtual void OnFinalLoopComplete() {}\n\n  virtual DecodePriority GetDecodePriority() {\n    return DecodePriority::kImmediate;\n  }\n\n protected:\n  virtual ~ImageResourceClient() = default;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_RESOURCE_CLIENT_H_\n"
  },
  {
    "path": "clay/gfx/image/image_upload_manager.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/image_upload_manager.h\"\n\n#include <utility>\n\nnamespace clay {\n\nImageUploadManager& ImageUploadManager::GetInstance() {\n  static ImageUploadManager instance;\n  return instance;\n}\n\nvoid ImageUploadManager::AddImageUploadTask(uint64_t queue_id,\n                                            OneShotCallback<>&& task,\n                                            int image_id) {\n  std::unique_lock<std::mutex> lock(image_upload_tasks_mutex_);\n\n  TaskList& task_list = image_upload_tasks_[queue_id];\n  task_list.emplace_back(image_id, std::move(task));\n\n  TaskIter task_iter = --task_list.end();\n  image_to_task_entries_[image_id].emplace_back(queue_id, task_iter);\n}\n\nvoid ImageUploadManager::RemoveImageUploadTaskByQueueId(uint64_t queue_id) {\n  std::unique_lock<std::mutex> lock(image_upload_tasks_mutex_);\n\n  auto queue_entry = image_upload_tasks_.find(queue_id);\n  if (queue_entry == image_upload_tasks_.end()) {\n    return;\n  }\n\n  TaskList& task_list = queue_entry->second;\n  // Remove the indexes in image_to_task_entries_\n  for (auto task_iter = task_list.begin(); task_iter != task_list.end();\n       ++task_iter) {\n    int image_id = task_iter->first;\n    RemoveSingleTaskFromIndex(image_id, queue_id, task_iter);\n  }\n\n  image_upload_tasks_.erase(queue_entry);\n}\n\nvoid ImageUploadManager::RemoveImageUploadTaskByImageId(int image_id) {\n  std::unique_lock<std::mutex> lock(image_upload_tasks_mutex_);\n\n  auto image_entry = image_to_task_entries_.find(image_id);\n  if (image_entry == image_to_task_entries_.end()) {\n    return;\n  }\n\n  ImageTaskEntries& all_task_entries = image_entry->second;\n  for (const auto& [queue_id, task_iter] : all_task_entries) {\n    auto queue_entry = image_upload_tasks_.find(queue_id);\n    if (queue_entry == image_upload_tasks_.end()) {\n      continue;\n    }\n\n    TaskList& task_list = queue_entry->second;\n    task_list.erase(task_iter);\n  }\n\n  image_to_task_entries_.erase(image_id);\n}\n\n// @param queue_id: page id that tasks belong to.\n// @return: number of tasks that are left.\nuint64_t ImageUploadManager::ProcessSingleTask(uint64_t queue_id) {\n  std::unique_lock<std::mutex> lock(image_upload_tasks_mutex_);\n\n  auto queue_entry = image_upload_tasks_.find(queue_id);\n  if (queue_entry == image_upload_tasks_.end()) {\n    return 0;\n  }\n  // Upload no more than one task at each call.\n  auto& tasks = queue_entry->second;\n  if (tasks.empty()) {\n    return 0;\n  }\n  uint64_t tasks_left = 0;\n  do {\n    TaskIter task_iter = tasks.begin();\n    int image_id = task_iter->first;\n\n    auto task = std::move(task_iter->second);\n\n    // Clean the indexes in image_to_task_entries before pop the task.\n    RemoveSingleTaskFromIndex(image_id, queue_id, task_iter);\n    tasks.pop_front();\n    tasks_left = tasks.size();\n\n    if (task.UnDone()) {\n      lock.unlock();\n      task();\n      return tasks_left;\n    }\n  } while (!tasks.empty());\n  return 0;\n}\n\nvoid ImageUploadManager::RemoveSingleTaskFromIndex(int image_id,\n                                                   uint64_t queue_id,\n                                                   TaskIter task_iter) {\n  auto image_entry = image_to_task_entries_.find(image_id);\n  if (image_entry == image_to_task_entries_.end()) {\n    return;\n  }\n\n  ImageTaskEntries& entries = image_entry->second;\n  for (auto iter = entries.begin(); iter != entries.end(); ++iter) {\n    if (iter->first == queue_id && iter->second == task_iter) {\n      entries.erase(iter);\n      break;\n    }\n  }\n  if (entries.empty()) {\n    image_to_task_entries_.erase(image_id);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/image_upload_manager.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_IMAGE_UPLOAD_MANAGER_H_\n#define CLAY_GFX_IMAGE_IMAGE_UPLOAD_MANAGER_H_\n\n#include <list>\n#include <mutex>\n#include <unordered_map>\n#include <utility>\n\n#include \"clay/shell/common/one_shot_callback.h\"\n\nnamespace clay {\nclass Image;\n\nclass ImageUploadManager {\n public:\n  static ImageUploadManager& GetInstance();\n\n  void AddImageUploadTask(uint64_t queue_id, OneShotCallback<>&& task,\n                          int image_id);\n  void RemoveImageUploadTaskByQueueId(uint64_t queue_id);\n  void RemoveImageUploadTaskByImageId(int image_id);\n\n  uint64_t ProcessSingleTask(uint64_t queue_id);\n\n private:\n  using Task = std::pair<int, OneShotCallback<>>;\n  using TaskList = std::list<Task>;\n  using TaskIter = TaskList::iterator;\n  using ImageTaskEntries = std::list<std::pair<uint64_t, TaskIter>>;\n\n  void RemoveSingleTaskFromIndex(int image_id, uint64_t queue_id,\n                                 TaskIter task_iter);\n\n  ImageUploadManager() = default;\n  ~ImageUploadManager() = default;\n\n  // key: queue id, which is page view's unique id.\n  // value: image upload tasks that belong to the same page view.\n  std::unordered_map<uint64_t, TaskList> image_upload_tasks_;\n  // key: image id.\n  // value: a list of queue id and task iterator.\n  std::unordered_map<int, ImageTaskEntries> image_to_task_entries_;\n\n  std::mutex image_upload_tasks_mutex_;\n};\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_IMAGE_UPLOAD_MANAGER_H_\n"
  },
  {
    "path": "clay/gfx/image/multi_frame_codec.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/multi_frame_codec.h\"\n\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"third_party/skia/include/codec/SkCodecAnimation.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n\nnamespace clay {\n\nnamespace {\n\n// Copied the source bitmap to the destination. If this cannot occur due to\n// running out of memory or the image info not being compatible, returns false.\nstatic bool CopyToBitmap(SkBitmap* dst, SkColorType dstColorType,\n                         const SkBitmap& src) {\n  SkPixmap srcPM;\n  if (!src.peekPixels(&srcPM)) {\n    return false;\n  }\n\n  SkBitmap tmpDst;\n  SkImageInfo dstInfo = srcPM.info().makeColorType(dstColorType);\n  if (!tmpDst.setInfo(dstInfo)) {\n    return false;\n  }\n\n  if (!tmpDst.tryAllocPixels()) {\n    return false;\n  }\n\n  SkPixmap dstPM;\n  if (!tmpDst.peekPixels(&dstPM)) {\n    return false;\n  }\n\n  if (!srcPM.readPixels(dstPM)) {\n    return false;\n  }\n\n  dst->swap(tmpDst);\n  return true;\n}\n\n}  // namespace\n\nMultiFrameCodec::MultiFrameCodec(\n    std::shared_ptr<SkCodecImageGenerator> generator)\n    : state_(new State(std::move(generator))) {}\n\nMultiFrameCodec::~MultiFrameCodec() = default;\n\nMultiFrameCodec::State::State(std::shared_ptr<SkCodecImageGenerator> generator)\n    : generator_(std::move(generator)),\n      frame_count_(generator_->getFrameCount()),\n      next_frame_index_(0) {}\n\nfml::RefPtr<GraphicsImage> MultiFrameCodec::State::GetNextFrameImage() {\n  SkBitmap bitmap = SkBitmap();\n  SkImageInfo info = generator_->getInfo().makeColorType(kN32_SkColorType);\n  if (info.alphaType() == kUnpremul_SkAlphaType) {\n    SkImageInfo updated = info.makeAlphaType(kPremul_SkAlphaType);\n    info = updated;\n  }\n  bitmap.allocPixels(info);\n\n  SkCodec::Options options;\n  options.fFrameIndex = next_frame_index_;\n  SkCodec::FrameInfo frameInfo{0};\n  generator_->getFrameInfo(next_frame_index_, &frameInfo);\n  const int requiredFrameIndex = frameInfo.fRequiredFrame;\n  if (requiredFrameIndex != SkCodec::kNoFrame) {\n    if (last_required_frame_ == nullptr) {\n      FML_LOG(INFO)\n          << \"Frame \" << next_frame_index_ << \" depends on frame \"\n          << requiredFrameIndex\n          << \" and no required frames are cached. Using blank slate instead.\";\n    } else {\n      // Copy the previous frame's output buffer into the current frame as the\n      // starting point.\n      if (last_required_frame_->getPixels() &&\n          CopyToBitmap(&bitmap, last_required_frame_->colorType(),\n                       *last_required_frame_)) {\n        options.fPriorFrame = requiredFrameIndex;\n      }\n    }\n  }\n\n  // Write the new frame to the output buffer. The bitmap pixels as supplied\n  // are already set in accordance with the previous frame's disposal policy.\n  if (!generator_->getPixels(info, bitmap.getPixels(), bitmap.rowBytes(),\n                             &options)) {\n    FML_LOG(ERROR) << \"Could not getPixels for frame \" << next_frame_index_;\n    return nullptr;\n  }\n\n  // Hold onto this if we need it to decode future frames.\n  if (frameInfo.fDisposalMethod == SkCodecAnimation::DisposalMethod::kKeep ||\n      last_required_frame_) {\n    last_required_frame_ = std::make_unique<SkBitmap>(bitmap);\n    last_required_frame_index_ = next_frame_index_;\n  }\n\n  // Defer decoding until time of draw later on the raster thread. Can happen\n  // when GL operations are currently forbidden such as in the background\n  // on iOS.\n  return GraphicsImage::MakeFromBitmap(bitmap);\n}\n\nvoid MultiFrameCodec::State::GetNextFrameAndInvokeCallback(\n    const CodecCallback& callback) {\n  int duration = 0;\n  fml::RefPtr<GraphicsImage> image = GetNextFrameImage();\n  if (image) {\n    SkCodec::FrameInfo skFrameInfo{0};\n    generator_->getFrameInfo(next_frame_index_, &skFrameInfo);\n    duration = skFrameInfo.fDuration;\n  }\n  next_frame_index_ = (next_frame_index_ + 1) % frame_count_;\n\n  // Invoke next frame callback.\n  callback({image, duration});\n}\n\nvoid MultiFrameCodec::NextFrame(const CodecCallback& callback) {\n  GraphicsIsolate::Instance().GetConcurrentWorkerTaskRunner()->PostTask(\n      fml::MakeCopyable([callback = std::move(callback),\n                         weak_state = std::weak_ptr<MultiFrameCodec::State>(\n                             state_)]() mutable {\n        auto state = weak_state.lock();\n        if (!state) {\n          callback({nullptr, 0});\n          return;\n        }\n        state->GetNextFrameAndInvokeCallback(std::move(callback));\n      }));\n}\n\nint MultiFrameCodec::FrameCount() const { return state_->frame_count_; }\n\nint MultiFrameCodec::FrameDuration(int index) const {\n  SkCodec::FrameInfo sk_frame_info{0};\n  state_->generator_->getFrameInfo(index, &sk_frame_info);\n  return sk_frame_info.fDuration;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/multi_frame_codec.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_MULTI_FRAME_CODEC_H_\n#define CLAY_GFX_IMAGE_MULTI_FRAME_CODEC_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/image/codec.h\"\n#include \"clay/gfx/image/frame_info.h\"\n#include \"third_party/skia/src/codec/SkCodecImageGenerator.h\"  // nogncheck\n\nnamespace clay {\n\nclass MultiFrameCodec : public Codec {\n public:\n  explicit MultiFrameCodec(std::shared_ptr<SkCodecImageGenerator> generator);\n\n  ~MultiFrameCodec() override;\n\n  // |Codec|\n  int FrameCount() const override;\n\n  // |Codec|\n  int FrameDuration(int index) const override;\n\n  // |Codec|\n  void NextFrame(const CodecCallback& callback) override;\n\n private:\n  // Captures the state shared between the IO and UI task runners.\n  //\n  // Decoding occurs on the IO task runner. Since it is possible for the UI\n  // object to be collected independently of the IO task runner work, it is not\n  // safe for this state to live directly on the MultiFrameCodec.\n  // Instead, the MultiFrameCodec creates this object when it is constructed,\n  // shares it with the IO task runner's decoding work, and sets the live_\n  // member to false when it is destructed.\n  struct State {\n    explicit State(std::shared_ptr<SkCodecImageGenerator> generator);\n\n    const std::shared_ptr<SkCodecImageGenerator> generator_;\n    const int frame_count_;\n\n    // The non-const members and functions below here are only read or written\n    // to on the IO thread. They are not safe to access or write on the UI\n    // thread.\n    int next_frame_index_;\n\n    // The last decoded frame that's required to decode any subsequent frames.\n    std::unique_ptr<SkBitmap> last_required_frame_;\n\n    // The index of the last decoded required frame.\n    int last_required_frame_index_ = -1;\n\n    fml::RefPtr<GraphicsImage> GetNextFrameImage();\n\n    void GetNextFrameAndInvokeCallback(const CodecCallback& callback);\n  };\n\n  // Shared across the UI and IO task runners.\n  std::shared_ptr<State> state_;\n\n  FML_FRIEND_MAKE_REF_COUNTED(MultiFrameCodec);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(MultiFrameCodec);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_MULTI_FRAME_CODEC_H_\n"
  },
  {
    "path": "clay/gfx/image/platform_image.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_PLATFORM_IMAGE_H_\n#define CLAY_GFX_IMAGE_PLATFORM_IMAGE_H_\n\n#include <memory>\n#include <tuple>\n\n#include \"clay/gfx/image/image_info.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n\nnamespace clay {\n\nclass PlatformImage : public std::enable_shared_from_this<PlatformImage> {\n public:\n  virtual ~PlatformImage() = default;\n\n  virtual int GetWidth() = 0;\n  virtual int GetHeight() = 0;\n  virtual int64_t GetDuration() = 0;\n\n  virtual std::shared_ptr<skity::Pixmap> ToBitmap() = 0;\n\n  virtual void DrawFrame(int64_t frame_time,\n                         std::function<void()> on_frame_changed) = 0;\n  virtual bool IsAnimated() = 0;\n  virtual void SetAutoPlay(bool auto_play) = 0;\n  virtual void SetLoopCount(int loop_count) = 0;\n  virtual void StartAnimation() = 0;\n  virtual void StopAnimation() = 0;\n  virtual void PauseAnimation() = 0;\n  virtual void ResumeAnimation() = 0;\n};\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_PLATFORM_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/image/single_frame_codec.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/single_frame_codec.h\"\n\n#include <utility>\n\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/image/image_decoder.h\"\n\nnamespace clay {\n\nSingleFrameCodec::SingleFrameCodec(fml::RefPtr<ImageDescriptor> descriptor,\n                                   uint32_t target_width,\n                                   uint32_t target_height)\n    : status_(Status::kNew),\n      descriptor_(std::move(descriptor)),\n      target_width_(target_width),\n      target_height_(target_height),\n      mutex_(fml::SharedMutex::Create()) {}\n\nSingleFrameCodec::~SingleFrameCodec() = default;\n\nint SingleFrameCodec::FrameCount() const { return 1; }\n\nvoid SingleFrameCodec::NextFrame(const CodecCallback& callback) {\n  {\n    fml::UniqueLock lock(*mutex_);\n    if (status_ == Status::kComplete) {\n      callback(FrameInfo{cached_image_, 0L});\n      return;\n    }\n\n    pending_callbacks_.emplace_back(std::move(callback));\n\n    if (status_ == Status::kInProgress) {\n      // Another call to NextFrame is in progress and will invoke the pending\n      // callbacks when decoding completes.\n      return;\n    }\n\n    status_ = Status::kInProgress;\n  }\n\n  auto decoder = GraphicsIsolate::Instance().GetImageDecoder();\n  if (!decoder) {\n    FML_DLOG(ERROR) << \"Image decoder not available.\";\n    status_ = Status::kComplete;\n    for (const CodecCallback& callback : pending_callbacks_) {\n      callback(FrameInfo{nullptr, 0L});\n    }\n    pending_callbacks_.clear();\n    return;\n  }\n\n  // The SingleFrameCodec must be deleted on the UI thread.  Allocate a RefPtr\n  // on the heap to ensure that the SingleFrameCodec remains alive until the\n  // decoder callback is invoked on the IO thread.  The callback can then\n  // drop the reference.\n  fml::RefPtr<SingleFrameCodec>* raw_codec_ref =\n      new fml::RefPtr<SingleFrameCodec>(this);\n\n  decoder->Decode(\n      descriptor_, target_width_, target_height_, [raw_codec_ref](auto image) {\n        std::unique_ptr<fml::RefPtr<SingleFrameCodec>> codec_ref(raw_codec_ref);\n        fml::RefPtr<SingleFrameCodec> codec(std::move(*codec_ref));\n        {\n          fml::UniqueLock lock(*codec->mutex_);\n          if (image.get()) {\n            codec->cached_image_ = std::move(image);\n          }\n\n          // The cached frame is now available and should be returned to any\n          // future callers.\n          codec->status_ = Status::kComplete;\n        }\n\n        // Invoke any callbacks that were provided before the frame was decoded.\n        for (const CodecCallback& callback : codec->pending_callbacks_) {\n          callback(FrameInfo{codec->cached_image_, 0L});\n        }\n        codec->pending_callbacks_.clear();\n      });\n\n  // The encoded data is no longer needed now that it has been handed off\n  // to the decoder.\n  descriptor_ = nullptr;\n}\n\nsize_t SingleFrameCodec::GetAllocationSize() const {\n  FML_DCHECK(descriptor_);\n  const auto& data_size = descriptor_->GetAllocationSize();\n  size_t frame_byte_size = 0;\n  fml::UniqueLock lock(*mutex_);\n  if (cached_image_) {\n    const auto& info = cached_image_->imageInfo();\n    const auto kMipmapOverhead = 4.0 / 3.0;\n    frame_byte_size = info.computeMinByteSize() * kMipmapOverhead;\n  }\n  return data_size + frame_byte_size + sizeof(this);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/single_frame_codec.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_SINGLE_FRAME_CODEC_H_\n#define CLAY_GFX_IMAGE_SINGLE_FRAME_CODEC_H_\n\n#include <memory>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n#include \"clay/gfx/image/codec.h\"\n#include \"clay/gfx/image/frame_info.h\"\n#include \"clay/gfx/image/image_descriptor.h\"\n\nnamespace clay {\n\nclass SingleFrameCodec : public Codec {\n public:\n  SingleFrameCodec(fml::RefPtr<ImageDescriptor> descriptor,\n                   uint32_t target_width, uint32_t target_height);\n\n  ~SingleFrameCodec() override;\n\n  // |Codec|\n  int FrameCount() const override;\n\n  // |Codec|\n  void NextFrame(const CodecCallback& callback) override;\n\n  size_t GetAllocationSize() const;\n\n private:\n  enum class Status { kNew, kInProgress, kComplete };\n  Status status_;\n  fml::RefPtr<ImageDescriptor> descriptor_;\n  uint32_t target_width_;\n  uint32_t target_height_;\n  fml::RefPtr<GraphicsImage> cached_image_;\n  std::vector<CodecCallback> pending_callbacks_;\n  std::unique_ptr<fml::SharedMutex> mutex_;\n\n  FML_FRIEND_MAKE_REF_COUNTED(SingleFrameCodec);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(SingleFrameCodec);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_IMAGE_SINGLE_FRAME_CODEC_H_\n"
  },
  {
    "path": "clay/gfx/image/skimage_holder.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/skimage_holder.h\"\n\n#include <utility>\n\n#include \"clay/gfx/graphics_isolate.h\"\n\nnamespace clay {\n\nSkImageHolder::SkImageHolder(bool decode_with_hierarchy)\n    : decode_with_hierarchy_(decode_with_hierarchy) {}\n\nSkImageHolder::~SkImageHolder() { ReleaseResource(); }\n\nfml::RefPtr<GraphicsImage> SkImageHolder::GetGraphicsImage() {\n  return image_.object();\n}\n\nvoid SkImageHolder::CacheFrame(int duration, GPUObject<GraphicsImage> image,\n                               bool promise) {\n  duration_ = duration;\n  if (!promise) {\n    // if promise, frame_ already has a promise image with texture backend.\n    image_ = std::move(image);\n  }\n  if (decode_with_hierarchy_) {\n    state_ = State::kUploaded;\n  } else {\n    state_ = State::kDecoded;\n  }\n  UpdateAllocationSize();\n}\n\nvoid SkImageHolder::ReleaseResource() {\n  state_ = State::kNotDecode;\n  duration_ = 0;\n  image_.reset();\n}\n\nint SkImageHolder::FrameDuration() const { return duration_; }\n\nvoid SkImageHolder::SetFrameDuration(int duration) { duration_ = duration; }\n\nvoid SkImageHolder::UpdateAllocationSize() {\n  if (image_.object()) {\n    const auto& info = image_.object()->imageInfo();\n#ifndef ENABLE_SKITY\n    const size_t image_byte_size = info.computeMinByteSize();\n#else\n    const size_t image_byte_size =\n        info.width() * info.height() * info.bytesPerPixel();\n#endif  // ENABLE_SKITY\n    allocation_size_ = image_byte_size + sizeof(*this);\n  } else {\n    allocation_size_ = sizeof(*this);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/skimage_holder.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_SKIMAGE_HOLDER_H_\n#define CLAY_GFX_IMAGE_SKIMAGE_HOLDER_H_\n\n#include <utility>\n\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n\nnamespace clay {\n\nclass Codec;\n\nnamespace testing {\nclass ImagePainterTest;\n}  // namespace testing\n\n// SkImageHolder represents a SkImage holding GPU resources.\nclass SkImageHolder : public fml::RefCountedThreadSafe<SkImageHolder> {\n public:\n  explicit SkImageHolder(bool decode_with_hierarchy = false);\n  ~SkImageHolder();\n\n  enum class State { kNotDecode = 0, kDecoding, kDecoded, kUploaded };\n\n  fml::RefPtr<GraphicsImage> GetGraphicsImage();\n  void SetPromiseImage(GPUObject<GraphicsImage> image) {\n    image_ = std::move(image);\n  }\n  void ReleaseResource();\n\n  int FrameDuration() const;\n  void SetFrameDuration(int);\n\n  bool FrameIsReady() const {\n    return decode_with_hierarchy_ ? state_ == State::kUploaded\n                                  : state_ == State::kDecoded;\n  }\n  bool FrameIsNotLoad() const { return state_ == State::kNotDecode; }\n  void MarkLoadStart() {\n    FML_DCHECK(FrameIsNotLoad());\n    state_ = State::kDecoding;\n  }\n  void MarkLoadResult(bool result) {\n    state_ = result ? State::kDecoded : State::kNotDecode;\n  }\n  void MarkUploadResult(bool result) {\n    state_ = result ? State::kUploaded : State::kNotDecode;\n  }\n\n  void CacheFrame(int duration, GPUObject<GraphicsImage> image,\n                  bool promise = false);\n\n  size_t GetAllocationSize() const { return allocation_size_; }\n\n  bool ShouldUseCache() const { return should_use_cache_; }\n  void SetUseCache(bool use) { should_use_cache_ = use; }\n\n  void set_timestamp(fml::TimePoint time) { timestamp_ = time; }\n  fml::TimePoint timestamp() { return timestamp_; }\n\n private:\n  friend class testing::ImagePainterTest;\n\n  void UpdateAllocationSize();\n\n  State state_ = State::kNotDecode;\n  bool should_use_cache_ = true;\n  size_t allocation_size_ = sizeof(SkImageHolder);\n  GPUObject<GraphicsImage> image_;\n  int duration_ = 0;\n  fml::TimePoint timestamp_;\n\n  bool decode_with_hierarchy_ = false;\n};\n\n}  // namespace clay\n#endif  // CLAY_GFX_IMAGE_SKIMAGE_HOLDER_H_\n"
  },
  {
    "path": "clay/gfx/image/static_image.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"clay/gfx/image/static_image.h\"\n\n#include <algorithm>\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n\nnamespace {\n\nstd::shared_ptr<skity::Pixmap> ScaleImage(std::shared_ptr<skity::Pixmap> pixmap,\n                                          clay::ImageInfo render_info) {\n  int image_width = pixmap->Width();\n  int image_height = pixmap->Height();\n\n  if (image_width == render_info.width() &&\n      image_height == render_info.height()) {\n    return pixmap;\n  }\n\n  auto image = skity::Image::MakeImage(pixmap);\n  if (!image) {\n    return pixmap;\n  }\n\n  std::shared_ptr<skity::Pixmap> scaled_pixmap =\n      std::make_shared<skity::Pixmap>(render_info.width(), render_info.height(),\n                                      pixmap->GetAlphaType(),\n                                      pixmap->GetColorType());\n\n  if (!image->ScalePixels(scaled_pixmap, nullptr,\n                          skity::SamplingOptions(skity::FilterMode::kLinear,\n                                                 skity::MipmapMode::kNone))) {\n    return pixmap;\n  }\n\n  return scaled_pixmap;\n}\n}  // namespace\n\nnamespace clay {\nstd::shared_ptr<StaticImage> StaticImage::Make(\n    fml::WeakPtr<ImageFetcher> image_fetcher, std::string url,\n    std::shared_ptr<PlatformImage> platform_image) {\n  auto image = std::shared_ptr<StaticImage>(new StaticImage);\n  image->type_ = ImageType::kStatic;\n  image->image_fetcher_ = image_fetcher;\n  image->url_ = std::move(url);\n  image->image_ = platform_image;\n  image->orig_info_ = ImageInfo::makeWH(platform_image->GetWidth(),\n                                        platform_image->GetHeight());\n  return image;\n}\n\nvoid StaticImage::Upload(fml::RefPtr<GPUUnrefQueue> unref_queue, Size size) {\n  if (!unref_queue || !unref_queue->GetContext()) {\n    FML_LOG(ERROR) << \"StaticImage::Upload: unref_queue or context is null\";\n    return;\n  }\n\n  if (!gpu_image_.object()) {\n    auto pixmap = image_->ToBitmap();\n    if (!pixmap) {\n      FML_LOG(ERROR) << \"StaticImage::Upload: Bitmap is null\";\n      return;\n    }\n\n    if (render_info_.isEmpty()) {\n      render_info_ = orig_info_;\n    }\n\n    auto image = skity::Image::MakeDeferredTextureImage(\n        skity::Texture::FormatFromColorType(pixmap->GetColorType()),\n        render_info_.width(), render_info_.height(), pixmap->GetAlphaType());\n    gpu_image_ = GPUObject(GraphicsImage::Make(image), unref_queue);\n    unref_queue->GetTaskRunner()->PostTask([context = unref_queue->GetContext(),\n                                            image, pixmap,\n                                            render_info = render_info_,\n                                            weak = weak_from_this()]() {\n      if (auto self = weak.lock()) {\n        auto final_pixmap = ScaleImage(pixmap, render_info);\n        auto texture = context->CreateTexture(\n            skity::Texture::FormatFromColorType(final_pixmap->GetColorType()),\n            final_pixmap->Width(), final_pixmap->Height(),\n            final_pixmap->GetAlphaType());\n        if (texture) {\n          texture->DeferredUploadImage(std::move(final_pixmap));\n          image->SetTexture(texture);\n        }\n      }\n    });\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/static_image.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_STATIC_IMAGE_H_\n#define CLAY_GFX_IMAGE_STATIC_IMAGE_H_\n\n#include <list>\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <utility>\n\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/base_image.h\"\n\nnamespace clay {\n\nclass StaticImage : public BaseImage {\n public:\n  static std::shared_ptr<StaticImage> Make(\n      fml::WeakPtr<ImageFetcher> image_fetcher, std::string url,\n      std::shared_ptr<PlatformImage> image);\n\n  void Upload(fml::RefPtr<GPUUnrefQueue> unref_queue, Size size) override;\n\n private:\n  StaticImage() = default;\n};\n\n}  // namespace clay\n#endif  // CLAY_GFX_IMAGE_STATIC_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/image/svg_image.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"clay/gfx/image/svg_image.h\"\n\n#include \"base/include/md5.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/gfx/image/base_image.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n\nnamespace clay {\nstd::shared_ptr<SVGImage> SVGImage::Make(\n    fml::WeakPtr<ImageFetcher> image_fetcher, std::string url,\n    const std::string& content) {\n  auto image = std::shared_ptr<SVGImage>(new SVGImage(content));\n  image->type_ = ImageType::kSVG;\n  image->image_fetcher_ = image_fetcher;\n  image->url_ = std::move(url);\n  return image;\n}\n\nSVGImage::SVGImage(const std::string& content) : content_(content) {\n  svg_dom_ = SVGDom::Create(\n      GrData::MakeWithProc(content_.data(), content_.size(), nullptr, nullptr),\n      [](std::string url) { return nullptr; });\n}\n\nvoid SVGImage::Upload(fml::RefPtr<GPUUnrefQueue> unref_queue, Size size) {\n  if (!unref_queue || !unref_queue->GetContext()) {\n    FML_LOG(ERROR) << \"SVGImage::Upload: unref_queue or context is null\";\n    return;\n  }\n  if (!gpu_image_.object() || gpu_image_.object()->width() < size.width() ||\n      gpu_image_.object()->height() < size.height()) {\n    auto image = svg_dom_->Render(size.width(), size.height(), unref_queue);\n    if (!image) {\n      FML_LOG(ERROR) << \"SVGImage::Paint: \" << size.width() << \" \"\n                     << size.height();\n      return;\n    }\n    gpu_image_ = GPUObject(GraphicsImage::Make(image), unref_queue);\n  }\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/svg_image.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_SVG_IMAGE_H_\n#define CLAY_GFX_IMAGE_SVG_IMAGE_H_\n\n#include <list>\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <utility>\n\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/base_image.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n#include \"clay/gfx/svg/svg_dom.h\"\n\nnamespace clay {\n\nclass SVGImage : public BaseImage {\n public:\n  static std::shared_ptr<SVGImage> Make(\n      fml::WeakPtr<ImageFetcher> image_fetcher, std::string url,\n      const std::string& content);\n\n  explicit SVGImage(const std::string& content);\n\n  void SetCacheKeyHash(size_t cache_key_hash) {\n    cache_key_hash_ = cache_key_hash;\n  }\n  size_t GetCacheKeyHash() const { return cache_key_hash_; }\n\n  void SetContentMD5(const std::string& content_md5) {\n    content_md5_ = content_md5;\n  }\n  const std::string& GetContentMD5() const { return content_md5_; }\n\n  int GetWidth() const override {\n    return gpu_image_.object() ? gpu_image_.object()->width() : 1;\n  }\n  int GetHeight() const override {\n    return gpu_image_.object() ? gpu_image_.object()->height() : 1;\n  }\n  void Upload(fml::RefPtr<GPUUnrefQueue> unref_queue, Size size) override;\n\n private:\n  SVGImage() = default;\n\n private:\n  std::string content_;\n  // For SVG images, this is the MD5 of the SVG content.\n  // This MD5 is primarily used to cache images within ImageCache.\n  std::string content_md5_;\n  // For SVG images, this is the hash of the SVG content MD5.\n  // This hash is primarily used to cache images within the ImageCache.\n  size_t cache_key_hash_ = 0;\n  std::shared_ptr<SVGDom> svg_dom_;\n};\n\n}  // namespace clay\n#endif  // CLAY_GFX_IMAGE_SVG_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/image/svg_image_holder.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/image/svg_image_holder.h\"\n\n#include <algorithm>\n#include <future>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\nSVGImageHolder::SVGImageHolder()\n    : status_(SVGStatus::kNew), mutex_(fml::SharedMutex::Create()) {\n  svg_dom_future_ = svg_dom_promise_.get_future().share();\n}\n\nSVGImageHolder::~SVGImageHolder() {\n  {\n    fml::UniqueLock lock(*mutex_);\n    if (status_ == SVGStatus::kInProgress) {\n      svg_dom_promise_.set_value(nullptr);\n    }\n  }\n}\n\nSVGDomPtr SVGImageHolder::GetSVGDOM() {\n  {\n    fml::SharedLock lock(*mutex_);\n    if (svg_dom_) {\n      return svg_dom_;\n    }\n  }\n  if (svg_dom_future_.valid()) {\n    SVGDomPtr svg_dom = svg_dom_future_.get();\n    fml::UniqueLock lock(*mutex_);\n    if (!svg_dom_) {\n      svg_dom_ = svg_dom;\n    }\n  }\n  return svg_dom_;\n}\n\nvoid SVGImageHolder::CreateSVGDOM(GrDataPtr data) {\n  FML_CHECK(data);\n  {\n    fml::UniqueLock lock(*mutex_);\n    if (status_ == SVGStatus::kComplete || status_ == SVGStatus::kInProgress) {\n      return;\n    }\n\n    status_ = SVGStatus::kInProgress;\n  }\n\n  fml::RefPtr<SVGImageHolder>* raw_holder_ref =\n      new fml::RefPtr<SVGImageHolder>(this);\n\n  GraphicsIsolate::Instance().GetConcurrentWorkerTaskRunner()->PostTask(\n      fml::MakeCopyable([raw_holder_ref, data]() {\n        TRACE_EVENT(\"clay\", \"CreateSVGDOM\");\n        std::unique_ptr<fml::RefPtr<SVGImageHolder> > holder_ref(\n            raw_holder_ref);\n        fml::RefPtr<SVGImageHolder> holder(std::move(*holder_ref));\n        {\n          fml::UniqueLock lock(*holder->mutex_);\n\n#ifndef ENABLE_SKITY\n          std::unique_ptr<SkStream> stream = SkMemoryStream::Make(data);\n          if (stream) {\n            holder->svg_dom_promise_.set_value(\n                SkSVGDOM::MakeFromStream(*stream));\n          } else {\n            FML_LOG(ERROR) << \"Invalid svg data.\";\n            holder->svg_dom_promise_.set_value(nullptr);\n          }\n#else\n          if (data) {\n            holder->svg_dom_promise_.set_value(\n                SVGDom::Create(data, [](std::string url) { return nullptr; }));\n          } else {\n            FML_LOG(ERROR) << \"Invalid svg data.\";\n            holder->svg_dom_promise_.set_value(nullptr);\n          }\n#endif  // ENABLE_SKITY\n\n          holder->status_ = SVGStatus::kComplete;\n        }\n      }));\n}\n\nfml::RefPtr<GraphicsImage> SVGImageHolder::GetGraphicsImage() const {\n  return svg_image_wrapper_ ? svg_image_wrapper_->GetGraphicsImage() : nullptr;\n}\n\nvoid SVGImageHolder::SetGraphicsImage(\n    fml::RefPtr<GraphicsImageWrapper> image_wrapper) {\n  svg_image_wrapper_ = image_wrapper;\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/image/svg_image_holder.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_IMAGE_SVG_IMAGE_HOLDER_H_\n#define CLAY_GFX_IMAGE_SVG_IMAGE_HOLDER_H_\n\n#include <functional>\n#include <future>\n#include <memory>\n#include <utility>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/graphics_image.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass GraphicsImageWrapper\n    : public fml::RefCountedThreadSafe<GraphicsImageWrapper> {\n public:\n  virtual fml::RefPtr<GraphicsImage> GetGraphicsImage() const = 0;\n  virtual ~GraphicsImageWrapper() = default;\n};\n\nclass GpuGraphicsImageWrapper : public GraphicsImageWrapper {\n public:\n  explicit GpuGraphicsImageWrapper(GPUObject<GraphicsImage> image)\n      : gpu_image_(std::move(image)) {}\n  ~GpuGraphicsImageWrapper() override = default;\n\n  fml::RefPtr<GraphicsImage> GetGraphicsImage() const override {\n    return gpu_image_.object();\n  }\n\n private:\n  GPUObject<GraphicsImage> gpu_image_;\n};\n\nclass CpuGraphicsImageWrapper : public GraphicsImageWrapper {\n public:\n  explicit CpuGraphicsImageWrapper(fml::RefPtr<GraphicsImage> image)\n      : image_(image) {}\n  ~CpuGraphicsImageWrapper() override = default;\n\n  fml::RefPtr<GraphicsImage> GetGraphicsImage() const override {\n    return image_;\n  }\n\n private:\n  fml::RefPtr<GraphicsImage> image_;\n};\n\nclass SVGImageHolder : public fml::RefCountedThreadSafe<SVGImageHolder> {\n public:\n  SVGImageHolder();\n  ~SVGImageHolder();\n\n  SVGDomPtr GetSVGDOM();\n\n  fml::RefPtr<GraphicsImage> GetGraphicsImage() const;\n  void SetGraphicsImage(fml::RefPtr<GraphicsImageWrapper> image_wrapper);\n\n  void CreateSVGDOM(GrDataPtr data);\n  size_t GetAllocationSize() const {\n    return svg_image_wrapper_ ? svg_image_wrapper_->GetGraphicsImage()\n                                    ->GetApproximateByteSize()\n                              : 0;\n  }\n\n private:\n  enum class SVGStatus { kNew, kInProgress, kComplete };\n  SVGStatus status_;\n  SVGDomPtr svg_dom_;\n  std::promise<SVGDomPtr> svg_dom_promise_;\n  std::shared_future<SVGDomPtr> svg_dom_future_;\n\n  std::unique_ptr<fml::SharedMutex> mutex_;\n\n  fml::RefPtr<GraphicsImageWrapper> svg_image_wrapper_;\n\n  FML_FRIEND_MAKE_REF_COUNTED(SVGImageHolder);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(SVGImageHolder);\n};\n\n}  // namespace clay\n#endif  // CLAY_GFX_IMAGE_SVG_IMAGE_HOLDER_H_\n"
  },
  {
    "path": "clay/gfx/paint.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/paint.h\"\n\nnamespace clay {\n\nPaint::Paint()\n    : blendMode_(static_cast<unsigned>(clay::BlendMode::kDefaultMode)),\n      drawStyle_(static_cast<unsigned>(DrawStyle::kDefaultStyle)),\n      strokeCap_(static_cast<unsigned>(StrokeCap::kDefaultCap)),\n      strokeJoin_(static_cast<unsigned>(StrokeJoin::kDefaultJoin)),\n      isAntiAlias_(false),\n      isDither_(false),\n      isInvertColors_(false),\n      strokeWidth_(kDefaultWidth),\n      strokeMiter_(kDefaultMiter) {}\n\nbool Paint::operator==(Paint const& other) const {\n  return blendMode_ == other.blendMode_ &&            //\n         drawStyle_ == other.drawStyle_ &&            //\n         strokeCap_ == other.strokeCap_ &&            //\n         strokeJoin_ == other.strokeJoin_ &&          //\n         isAntiAlias_ == other.isAntiAlias_ &&        //\n         isDither_ == other.isDither_ &&              //\n         isInvertColors_ == other.isInvertColors_ &&  //\n         color_ == other.color_ &&                    //\n         strokeWidth_ == other.strokeWidth_ &&        //\n         strokeMiter_ == other.strokeMiter_ &&        //\n         Equals(colorSource_, other.colorSource_) &&  //\n         Equals(colorFilter_, other.colorFilter_) &&  //\n         Equals(imageFilter_, other.imageFilter_) &&  //\n         Equals(maskFilter_, other.maskFilter_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/paint.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_PAINT_H_\n#define CLAY_GFX_PAINT_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/picture_animation_type.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/gfx/style/color_filter.h\"\n#include \"clay/gfx/style/color_source.h\"\n#include \"clay/gfx/style/image_filter.h\"\n#include \"clay/gfx/style/mask_filter.h\"\n#include \"clay/gfx/style/path_effect.h\"\n\nnamespace clay {\nenum class DrawStyle {\n  kFill,           //!< fills interior of shapes\n  kStroke,         //!< strokes boundary of shapes\n  kStrokeAndFill,  //!< both strokes and fills shapes\n#ifdef ENABLE_SKITY\n  kStrokeThenFill_Style,  //!< set to stroke then fill geometry\n#endif                    // ENABLE_SKITY\n\n  kLastStyle = kStrokeAndFill,\n  kDefaultStyle = kFill,\n};\n\n#ifndef ENABLE_SKITY\ninline DrawStyle ToClay(SkPaint::Style style) {\n  return static_cast<DrawStyle>(style);\n}\n\ninline SkPaint::Style ToSk(DrawStyle style) {\n  return static_cast<SkPaint::Style>(style);\n}\n#else\ninline DrawStyle ToClay(skity::Paint::Style style) {\n  return static_cast<DrawStyle>(style);\n}\n\ninline skity::Paint::Style ToSk(DrawStyle style) {\n  return static_cast<skity::Paint::Style>(style);\n}\n#endif  // ENABLE_SKITY\n\nenum class StrokeCap {\n  kButt,    //!< no stroke extension\n  kRound,   //!< adds circle\n  kSquare,  //!< adds square\n\n  kLastCap = kSquare,\n  kDefaultCap = kButt,\n};\n\n#ifndef ENABLE_SKITY\ninline StrokeCap ToClay(SkPaint::Cap cap) {\n  return static_cast<StrokeCap>(cap);\n}\n\ninline SkPaint::Cap ToSk(StrokeCap cap) {\n  return static_cast<SkPaint::Cap>(cap);\n}\n#else\ninline StrokeCap ToClay(skity::Paint::Cap cap) {\n  return static_cast<StrokeCap>(cap);\n}\n\ninline skity::Paint::Cap ToSk(StrokeCap cap) {\n  return static_cast<skity::Paint::Cap>(cap);\n}\n#endif  // ENABLE_SKITY\n\nenum class StrokeJoin {\n  kMiter,  //!< extends to miter limit\n  kRound,  //!< adds circle\n  kBevel,  //!< connects outside edges\n\n  kLastJoin = kBevel,\n  kDefaultJoin = kMiter,\n};\n\n#ifndef ENABLE_SKITY\ninline StrokeJoin ToClay(SkPaint::Join join) {\n  return static_cast<StrokeJoin>(join);\n}\n\ninline SkPaint::Join ToSk(StrokeJoin join) {\n  return static_cast<SkPaint::Join>(join);\n}\n#else\ninline StrokeJoin ToClay(skity::Paint::Join join) {\n  return static_cast<StrokeJoin>(join);\n}\n\ninline skity::Paint::Join ToSk(StrokeJoin join) {\n  return static_cast<skity::Paint::Join>(join);\n}\n#endif  // ENABLE_SKITY\n\nclass Paint {\n public:\n  static constexpr Color kDefaultColor = Color::kBlack();\n  static constexpr float kDefaultWidth = 0.0;\n  static constexpr float kDefaultMiter = 4.0;\n\n  Paint();\n\n  bool isAntiAlias() const { return isAntiAlias_; }\n  Paint& setAntiAlias(bool isAntiAlias) {\n    isAntiAlias_ = isAntiAlias;\n    return *this;\n  }\n\n  bool isDither() const { return isDither_; }\n  Paint& setDither(bool isDither) {\n    isDither_ = isDither;\n    return *this;\n  }\n\n  bool isInvertColors() const { return isInvertColors_; }\n  Paint& setInvertColors(bool isInvertColors) {\n    isInvertColors_ = isInvertColors;\n    return *this;\n  }\n\n  Color getColor() const { return color_; }\n  Paint& setColor(Color color) {\n    color_ = color;\n    return *this;\n  }\n\n  uint8_t getAlpha() const { return color_.argb >> 24; }\n  Paint& setAlpha(uint8_t alpha) {\n    color_.argb = alpha << 24 | (color_.argb & 0x00FFFFFF);\n    return *this;\n  }\n  Paint& setOpacity(float opacity) {\n    setAlpha(static_cast<int>(opacity * 0xff));\n    return *this;\n  }\n\n  BlendMode getBlendMode() const { return static_cast<BlendMode>(blendMode_); }\n  Paint& setBlendMode(BlendMode mode) {\n    blendMode_ = static_cast<unsigned>(mode);\n    return *this;\n  }\n\n  DrawStyle getDrawStyle() const { return static_cast<DrawStyle>(drawStyle_); }\n  Paint& setDrawStyle(DrawStyle style) {\n    drawStyle_ = static_cast<unsigned>(style);\n    return *this;\n  }\n\n  StrokeCap getStrokeCap() const { return static_cast<StrokeCap>(strokeCap_); }\n  Paint& setStrokeCap(StrokeCap cap) {\n    strokeCap_ = static_cast<unsigned>(cap);\n    return *this;\n  }\n\n  StrokeJoin getStrokeJoin() const {\n    return static_cast<StrokeJoin>(strokeJoin_);\n  }\n  Paint& setStrokeJoin(StrokeJoin join) {\n    strokeJoin_ = static_cast<unsigned>(join);\n    return *this;\n  }\n\n  float getStrokeWidth() const { return strokeWidth_; }\n  Paint& setStrokeWidth(float width) {\n    strokeWidth_ = width;\n    return *this;\n  }\n\n  float getStrokeMiter() const { return strokeMiter_; }\n  Paint& setStrokeMiter(float miter) {\n    strokeMiter_ = miter;\n    return *this;\n  }\n\n  std::shared_ptr<const ColorSource> getColorSource() const {\n    return colorSource_;\n  }\n  const ColorSource* getColorSourcePtr() const { return colorSource_.get(); }\n  Paint& setColorSource(std::shared_ptr<const ColorSource> source) {\n    colorSource_ = source;\n    return *this;\n  }\n  Paint& setColorSource(const ColorSource* source) {\n    colorSource_ = source ? source->shared() : nullptr;\n    return *this;\n  }\n\n  std::shared_ptr<const ColorFilter> getColorFilter() const {\n    return colorFilter_;\n  }\n  const ColorFilter* getColorFilterPtr() const { return colorFilter_.get(); }\n  Paint& setColorFilter(const std::shared_ptr<const ColorFilter> filter) {\n    colorFilter_ = filter ? filter->shared() : nullptr;\n    return *this;\n  }\n  Paint& setColorFilter(const ColorFilter* filter) {\n    colorFilter_ = filter ? filter->shared() : nullptr;\n    return *this;\n  }\n\n  std::shared_ptr<const ImageFilter> getImageFilter() const {\n    return imageFilter_;\n  }\n  const ImageFilter* getImageFilterPtr() const { return imageFilter_.get(); }\n  Paint& setImageFilter(const std::shared_ptr<const ImageFilter> filter) {\n    imageFilter_ = filter;\n    return *this;\n  }\n  Paint& setImageFilter(const ImageFilter* filter) {\n    imageFilter_ = filter ? filter->shared() : nullptr;\n    return *this;\n  }\n\n  std::shared_ptr<const MaskFilter> getMaskFilter() const {\n    return maskFilter_;\n  }\n  const MaskFilter* getMaskFilterPtr() const { return maskFilter_.get(); }\n  Paint& setMaskFilter(std::shared_ptr<MaskFilter> filter) {\n    maskFilter_ = filter;\n    return *this;\n  }\n  Paint& setMaskFilter(const MaskFilter* filter) {\n    maskFilter_ = filter ? filter->shared() : nullptr;\n    return *this;\n  }\n\n  std::shared_ptr<const PathEffect> getPathEffect() const {\n    return pathEffect_;\n  }\n  const PathEffect* getPathEffectPtr() const { return pathEffect_.get(); }\n  Paint& setPathEffect(std::shared_ptr<PathEffect> pathEffect) {\n    pathEffect_ = pathEffect;\n    return *this;\n  }\n\n  GrPaint gr_object() const {\n    GrPaint paint;\n    PAINT_SET_BLEND_MODE(paint, getBlendMode());\n    PAINT_SET_STYLE(paint, getDrawStyle());\n    PAINT_SET_STROKE_CAP(paint, ToSk(getStrokeCap()));\n    PAINT_SET_STROKE_JOIN(paint, ToSk(getStrokeJoin()));\n    PAINT_SET_ANTI_ALIAS(paint, isAntiAlias());\n#ifndef ENABLE_SKITY\n    // Skity paint not supports dither yet.\n    paint.setDither(isDither());\n#endif  // ENABLE_SKITY\n    PAINT_SET_COLOR(paint, getColor());\n    PAINT_SET_STROKE_WIDTH(paint, getStrokeWidth());\n    PAINT_SET_STROKE_MITER(paint, getStrokeMiter());\n    if (colorSource_) {\n      PAINT_SET_SHADER(paint, colorSource_->gr_object());\n    }\n    if (colorFilter_) {\n      PAINT_SET_COLOR_FILTER(paint, colorFilter_->gr_object());\n    }\n    if (imageFilter_) {\n      PAINT_SET_IMAGE_FILTER(paint, imageFilter_->gr_object());\n    }\n    if (maskFilter_) {\n      PAINT_SET_MASK_FILTER(paint, maskFilter_->gr_object());\n    }\n    if (pathEffect_) {\n      PAINT_SET_PATH_EFFECT(paint, pathEffect_->gr_object());\n    }\n    return paint;\n  }\n\n  bool operator==(Paint const& other) const;\n  bool operator!=(Paint const& other) const { return !(*this == other); }\n\n  void setDynamicOpType(DynamicOpType type) { dynamic_op_type_ = type; }\n\n  const DynamicOpType& getDynamicOpType() const { return dynamic_op_type_; }\n\n private:\n#define ASSERT_ENUM_FITS(last_enum, num_bits)                    \\\n  static_assert(static_cast<int>(last_enum) < (1 << num_bits) && \\\n                static_cast<int>(last_enum) * 2 >= (1 << num_bits))\n\n  static constexpr int kBlendModeBits = 5;\n  static constexpr int kDrawStyleBits = 2;\n  static constexpr int kStrokeCapBits = 2;\n  static constexpr int kStrokeJoinBits = 2;\n  ASSERT_ENUM_FITS(BlendMode::kLastMode, kBlendModeBits);\n  ASSERT_ENUM_FITS(DrawStyle::kLastStyle, kDrawStyleBits);\n  ASSERT_ENUM_FITS(StrokeCap::kLastCap, kStrokeCapBits);\n  ASSERT_ENUM_FITS(StrokeJoin::kLastJoin, kStrokeJoinBits);\n\n  union {\n    struct {\n      unsigned blendMode_ : kBlendModeBits;\n      unsigned drawStyle_ : kDrawStyleBits;\n      unsigned strokeCap_ : kStrokeCapBits;\n      unsigned strokeJoin_ : kStrokeJoinBits;\n      unsigned isAntiAlias_ : 1;\n      unsigned isDither_ : 1;\n      unsigned isInvertColors_ : 1;\n    };\n  };\n\n  Color color_;\n  float strokeWidth_;\n  float strokeMiter_;\n  DynamicOpType dynamic_op_type_ = DynamicOpType::kNone;\n\n  std::shared_ptr<const ColorSource> colorSource_;\n  std::shared_ptr<const ColorFilter> colorFilter_;\n  std::shared_ptr<const ImageFilter> imageFilter_;\n  std::shared_ptr<const MaskFilter> maskFilter_;\n  std::shared_ptr<const PathEffect> pathEffect_;\n  // missing (as compared to SkPaint):\n  // DlBlender - not planning on using that object in a pure DisplayList world\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_PAINT_H_\n"
  },
  {
    "path": "clay/gfx/paint_decoding_image.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_PAINT_DECODING_IMAGE_H_\n#define CLAY_GFX_PAINT_DECODING_IMAGE_H_\n\n#include <functional>\n#include <memory>\n\n#include \"clay/gfx/gpu_ref_object.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nusing LazyImageDecodeCallback = std::function<void(bool)>;\n\nclass PaintDecodingImage : public GPURefObject {\n public:\n  virtual ~PaintDecodingImage() = default;\n  virtual void ScheduleDecodeAndUpload(\n      const LazyImageDecodeCallback& callback) {}\n\n  virtual bool MaybeAnimated() const { return false; }\n\n  virtual clay::GrImagePtr gr_image() const = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_PAINT_DECODING_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/paint_image.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/paint_image.h\"\n\n#include <utility>\n\n#include \"clay/gfx/gfx_rendering_backend.h\"\n\nnamespace clay {\n\nfml::RefPtr<PaintImage> PaintImage::Make(PaintDecodingImage* image) {\n  return Make(fml::RefPtr<PaintDecodingImage>(image));\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<PaintImage> PaintImage::Make(const SkImage* image) {\n  return Make(sk_ref_sp(image));\n}\n\nfml::RefPtr<PaintImage> PaintImage::Make(sk_sp<SkImage> image) {\n  return fml::MakeRefCounted<PaintImageSkia>(std::move(image));\n}\n\nfml::RefPtr<PaintImage> PaintImage::Make(\n    fml::RefPtr<PaintDecodingImage> image) {\n  return fml::MakeRefCounted<PaintImageSkiaLazy>(std::move(image));\n}\n#else\nfml::RefPtr<PaintImage> PaintImage::Make(\n    fml::RefPtr<PaintDecodingImage> image) {\n  return fml::MakeRefCounted<PaintImageSkity>(image->gr_image());\n}\n#endif  // ENABLE_SKITY\n\nPaintImage::PaintImage() = default;\n\nPaintImage::~PaintImage() = default;\n\nint PaintImage::width() const { return dimensions().x; };\n\nint PaintImage::height() const { return dimensions().y; };\n\nskity::Rect PaintImage::bounds() const {\n  return skity::Rect::MakeSize(dimensions());\n}\n\nstd::optional<std::string> PaintImage::get_error() const {\n  return std::nullopt;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/paint_image.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_PAINT_IMAGE_H_\n#define CLAY_GFX_PAINT_IMAGE_H_\n\n#include <cstddef>\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/paint_decoding_image.h\"\n#include \"skity/geometry/rect.hpp\"\n#include \"skity/geometry/vector.hpp\"\n\nnamespace skity {\nclass Image;\n}\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// @brief      Represents an image whose allocation is (usually) resident on\n///             device memory.\n///\n///             Since it is usually impossible or expensive to transmute images\n///             for one rendering backend to another, these objects are backend\n///             specific.\n///\nclass PaintImage : public fml::RefCountedThreadSafe<PaintImage> {\n public:\n  // Describes which GPU context owns this image.\n  enum class OwningContext { kRaster, kIO };\n\n#ifndef ENABLE_SKITY\n  static fml::RefPtr<PaintImage> Make(const SkImage* image);\n\n  static fml::RefPtr<PaintImage> Make(sk_sp<SkImage> image);\n#endif  // ENABLE_SKITY\n\n  static fml::RefPtr<PaintImage> Make(PaintDecodingImage* image);\n\n  static fml::RefPtr<PaintImage> Make(fml::RefPtr<PaintDecodingImage> image);\n\n  virtual ~PaintImage();\n\n  //----------------------------------------------------------------------------\n  /// @brief      If this paint image is meant to be used by the Skia/Skity\n  ///             backend, an SkImage instance. Null otherwise.\n  ///\n  /// @return     A Skia/Skity image instance or null.\n  ///\n  virtual clay::GrImagePtr gr_image() const = 0;\n\n  //----------------------------------------------------------------------------\n  /// @brief      If the pixel format of this image ignores alpha, this returns\n  ///             true. This method might conservatively return false when it\n  ///             cannot guarantee an opaque image, for example when the pixel\n  ///             format of the image supports alpha but the image is made up of\n  ///             entirely opaque pixels.\n  ///\n  /// @return     True if the pixel format of this image ignores alpha.\n  ///\n  virtual bool isOpaque() const = 0;\n\n  virtual bool isTextureBacked() const = 0;\n\n  //----------------------------------------------------------------------------\n  /// @return     The dimensions of the pixel grid.\n  ///\n  virtual skity::Vec2 dimensions() const = 0;\n\n  //----------------------------------------------------------------------------\n  /// @return     The approximate byte size of the allocation of this image.\n  ///             This takes into account details such as mip-mapping. The\n  ///             allocation is usually resident in device memory.\n  ///\n  virtual size_t GetApproximateByteSize() const = 0;\n\n  //----------------------------------------------------------------------------\n  /// @return     The width of the pixel grid. A convenience method that calls\n  ///             |PaintImage::dimensions|.\n  ///\n  int width() const;\n\n  //----------------------------------------------------------------------------\n  /// @return     The height of the pixel grid. A convenience method that calls\n  ///             |PaintImage::dimensions|.\n  ///\n  int height() const;\n\n  //----------------------------------------------------------------------------\n  /// @return     The bounds of the pixel grid with 0, 0 as origin. A\n  ///             convenience method that calls |PaintImage::dimensions|.\n  ///\n  skity::Rect bounds() const;\n\n  //----------------------------------------------------------------------------\n  /// @return     Specifies which context was used to create this image. The\n  ///             image must be collected on the same task runner as its\n  ///             context.\n  virtual OwningContext owning_context() const { return OwningContext::kIO; }\n\n  //----------------------------------------------------------------------------\n  /// @return     An error, if any, that occurred when trying to create the\n  ///             image.\n  virtual std::optional<std::string> get_error() const;\n\n  virtual fml::RefPtr<PaintDecodingImage> decoding_image() const {\n    return nullptr;\n  }\n\n  bool Equals(const PaintImage* other) const {\n    if (!other) {\n      return false;\n    }\n    if (this == other) {\n      return true;\n    }\n    return decoding_image() == other->decoding_image() &&\n           gr_image() == other->gr_image();\n  }\n\n  bool Equals(const PaintImage& other) const { return Equals(&other); }\n\n  bool Equals(fml::RefPtr<const PaintImage> other) const {\n    return Equals(other.get());\n  }\n\n protected:\n  PaintImage();\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_PAINT_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/paint_image_skia.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/paint_image_skia.h\"\n\n#include <utility>\n\nnamespace clay {\n\nPaintImageSkia::PaintImageSkia(sk_sp<SkImage> image)\n    : image_(std::move(image)) {}\n\n// |PaintImage|\nPaintImageSkia::~PaintImageSkia() = default;\n\n// |PaintImage|\nclay::GrImagePtr PaintImageSkia::gr_image() const { return image_; };\n\n// |PaintImage|\nbool PaintImageSkia::isOpaque() const {\n  if (auto sk_img = gr_image()) {\n    return sk_img->isOpaque();\n  }\n  return false;\n}\n\n// |PaintImage|\nbool PaintImageSkia::isTextureBacked() const {\n  if (auto sk_img = gr_image()) {\n    return sk_img->isTextureBacked();\n  }\n  return false;\n}\n\n// |PaintImage|\nskity::Vec2 PaintImageSkia::dimensions() const {\n  if (auto sk_img = gr_image()) {\n    return skity::Vec2(sk_img->dimensions().width(),\n                       sk_img->dimensions().height());\n  }\n  return skity::Vec2{};\n}\n\n// |PaintImage|\nsize_t PaintImageSkia::GetApproximateByteSize() const {\n  auto size = sizeof(this);\n  if (auto sk_img = gr_image()) {\n    const auto& info = sk_img->imageInfo();\n    const auto kMipmapOverhead = 4.0 / 3.0;\n    const size_t image_byte_size = info.computeMinByteSize() * kMipmapOverhead;\n    size += image_byte_size;\n  }\n  return size;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/paint_image_skia.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_PAINT_IMAGE_SKIA_H_\n#define CLAY_GFX_PAINT_IMAGE_SKIA_H_\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/paint_image.h\"\n\nnamespace clay {\n\nclass PaintImageSkia : public PaintImage {\n public:\n  PaintImageSkia(sk_sp<SkImage> image);\n\n  // |PaintImage|\n  ~PaintImageSkia() override;\n\n  // |PaintImage|\n  clay::GrImagePtr gr_image() const override;\n\n  // |PaintImage|\n  bool isOpaque() const override;\n\n  // |PaintImage|\n  bool isTextureBacked() const override;\n\n  // |PaintImage|\n  skity::Vec2 dimensions() const override;\n\n  // |PaintImage|\n  size_t GetApproximateByteSize() const override;\n\n private:\n  clay::GrImagePtr image_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PaintImageSkia);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_PAINT_IMAGE_SKIA_H_\n"
  },
  {
    "path": "clay/gfx/paint_image_skia_lazy.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/paint_image_skia_lazy.h\"\n\n#include <utility>\n\n#include \"clay/gfx/paint_decoding_image.h\"\n\nnamespace clay {\n\nPaintImageSkiaLazy::PaintImageSkiaLazy(fml::RefPtr<PaintDecodingImage> image)\n    : PaintImageSkia(nullptr), decoding_image_(std::move(image)) {}\n\n// |PaintImage|\nPaintImageSkiaLazy::~PaintImageSkiaLazy() = default;\n\n// |PaintImage|\nsk_sp<SkImage> PaintImageSkiaLazy::gr_image() const {\n  return decoding_image_ ? decoding_image_->gr_image() : nullptr;\n};\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/paint_image_skia_lazy.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_PAINT_IMAGE_SKIA_LAZY_H_\n#define CLAY_GFX_PAINT_IMAGE_SKIA_LAZY_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/paint_decoding_image.h\"\n#include \"clay/gfx/paint_image_skia.h\"\n\nnamespace clay {\n\nclass PaintImageSkiaLazy final : public PaintImageSkia {\n public:\n  explicit PaintImageSkiaLazy(fml::RefPtr<PaintDecodingImage> image);\n\n  // |PaintImage|\n  ~PaintImageSkiaLazy() override;\n\n  // |PaintImage|\n  sk_sp<SkImage> gr_image() const override;\n\n  fml::RefPtr<PaintDecodingImage> decoding_image() const override {\n    return decoding_image_;\n  }\n\n private:\n  fml::RefPtr<PaintDecodingImage> decoding_image_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PaintImageSkiaLazy);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_PAINT_IMAGE_SKIA_LAZY_H_\n"
  },
  {
    "path": "clay/gfx/paint_image_skity.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/paint_image_skity.h\"\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nfml::RefPtr<PaintImage> PaintImageSkity::Make(clay::GrImagePtr image) {\n  return fml::MakeRefCounted<PaintImageSkity>(std::move(image));\n}\n\nPaintImageSkity::PaintImageSkity(clay::GrImagePtr image)\n    : image_(std::move(image)) {}\n\n// |PaintImage|\nPaintImageSkity::~PaintImageSkity() = default;\n\n// |PaintImage|\nclay::GrImagePtr PaintImageSkity::gr_image() const { return image_; }\n\n// |PaintImage|\nbool PaintImageSkity::isOpaque() const {\n  return image_ ? image_->GetAlphaType() == skity::kOpaque_AlphaType : false;\n}\n\n// |PaintImage|\nbool PaintImageSkity::isTextureBacked() const {\n  return image_ ? image_->IsTextureBackend() : false;\n}\n\n// |PaintImage|\nskity::Vec2 PaintImageSkity::dimensions() const {\n  return image_ ? skity::Vec2(image_->Width(), image_->Height())\n                : skity::Vec2();\n}\n\n// |PaintImage|\nsize_t PaintImageSkity::GetApproximateByteSize() const {\n  FML_UNIMPLEMENTED()\n  return 0;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/paint_image_skity.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_PAINT_IMAGE_SKITY_H_\n#define CLAY_GFX_PAINT_IMAGE_SKITY_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/paint_image.h\"\n\nnamespace clay {\n\nclass PaintImageSkity final : public PaintImage {\n public:\n  static fml::RefPtr<PaintImage> Make(clay::GrImagePtr image);\n\n  PaintImageSkity(clay::GrImagePtr image);\n\n  // |PaintImage|\n  ~PaintImageSkity() override;\n\n  // |PaintImage|\n  clay::GrImagePtr gr_image() const override;\n\n  // |PaintImage|\n  bool isOpaque() const override;\n\n  // |PaintImage|\n  bool isTextureBacked() const override;\n\n  // |PaintImage|\n  skity::Vec2 dimensions() const override;\n\n  // |PaintImage|\n  size_t GetApproximateByteSize() const override;\n\n private:\n  clay::GrImagePtr image_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PaintImageSkity);\n};\n}  // namespace clay\n\n#endif  // CLAY_GFX_PAINT_IMAGE_SKITY_H_\n"
  },
  {
    "path": "clay/gfx/paint_recorder.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_PAINT_RECORDER_H_\n#define CLAY_GFX_PAINT_RECORDER_H_\n\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/picture.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nclass GraphicsCanvas;\n\nclass PaintRecorder {\n public:\n  explicit PaintRecorder(fml::RefPtr<GPUUnrefQueue> unref_queue)\n      : unref_queue_(unref_queue) {}\n\n  PaintRecorder(const PaintRecorder&) = delete;\n  ~PaintRecorder() = default;\n  PaintRecorder& operator=(const PaintRecorder&) = delete;\n\n  GraphicsCanvas* BeginRecording(const skity::Rect& bounds);\n#ifndef ENABLE_SKITY\n  GraphicsCanvas* BeginRecording(const SkBitmap& bitmap);\n#endif  // ENABLE_SKITY\n  GraphicsCanvas* BeginRecording(float width, float height) {\n    return BeginRecording(skity::Rect::MakeWH(width, height));\n  }\n  bool IsRecording() const;\n\n  std::unique_ptr<Picture> FinishRecordingAsPicture();\n\n  GraphicsCanvas* Canvas() { return canvas_.get(); }\n\n private:\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n  std::unique_ptr<GraphicsCanvas> canvas_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_PAINT_RECORDER_H_\n"
  },
  {
    "path": "clay/gfx/paint_recorder_skia.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/paint_recorder.h\"\n#include \"clay/gfx/skia/skia_canvas.h\"\n\nnamespace clay {\n\nGraphicsCanvas* PaintRecorder::BeginRecording(const skity::Rect& bounds) {\n  skity::Rect rect = skity::Rect::MakeLTRB(bounds.Left(), bounds.Top(),\n                                           bounds.Right(), bounds.Bottom());\n  canvas_ = std::make_unique<SkiaRecorderCanvas>(rect, unref_queue_);\n  return canvas_.get();\n}\n\nGraphicsCanvas* PaintRecorder::BeginRecording(const SkBitmap& bitmap) {\n  canvas_ = std::make_unique<SkiaBitmapCanvas>(bitmap);\n  return canvas_.get();\n}\n\nbool PaintRecorder::IsRecording() const { return canvas_.get() != nullptr; }\n\nstd::unique_ptr<Picture> PaintRecorder::FinishRecordingAsPicture() {\n  if (!canvas_.get()) {\n    return nullptr;\n  }\n  auto result = canvas_->FinishRecordingAsPicture();\n  canvas_.reset();\n  return result;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/paint_recorder_skity.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/paint_recorder.h\"\n#include \"clay/gfx/skity/skity_canvas.h\"\n\nnamespace clay {\nGraphicsCanvas* PaintRecorder::BeginRecording(const skity::Rect& bounds) {\n  canvas_ = std::make_unique<SkityRecorderCanvas>(bounds, unref_queue_);\n  return canvas_.get();\n}\n\nbool PaintRecorder::IsRecording() const { return canvas_.get() != nullptr; }\n\nstd::unique_ptr<Picture> PaintRecorder::FinishRecordingAsPicture() {\n  if (!canvas_.get()) {\n    return nullptr;\n  }\n  auto result = canvas_->FinishRecordingAsPicture();\n  canvas_.reset();\n  return result;\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/paint_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/paint.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(DisplayListPaint, ConstructorDefaults) {\n  DlPaint paint;\n  EXPECT_FALSE(paint.isAntiAlias());\n  EXPECT_FALSE(paint.isDither());\n  EXPECT_FALSE(paint.isInvertColors());\n  EXPECT_EQ(paint.getColor(), DlPaint::kDefaultColor);\n  EXPECT_EQ(paint.getAlpha(), 0xFF);\n  EXPECT_EQ(paint.getBlendMode(), DlBlendMode::kDefaultMode);\n  EXPECT_EQ(paint.getDrawStyle(), DlDrawStyle::kDefaultStyle);\n  EXPECT_EQ(paint.getStrokeCap(), DlStrokeCap::kDefaultCap);\n  EXPECT_EQ(paint.getStrokeJoin(), DlStrokeJoin::kDefaultJoin);\n  EXPECT_EQ(paint.getStrokeWidth(), DlPaint::kDefaultWidth);\n  EXPECT_EQ(paint.getStrokeMiter(), DlPaint::kDefaultMiter);\n  EXPECT_EQ(paint.getColorSource(), nullptr);\n  EXPECT_EQ(paint.getColorFilter(), nullptr);\n  EXPECT_EQ(paint.getImageFilter(), nullptr);\n  EXPECT_EQ(paint.getMaskFilter(), nullptr);\n\n  EXPECT_EQ(DlBlendMode::kDefaultMode, DlBlendMode::kSrcOver);\n  EXPECT_EQ(DlDrawStyle::kDefaultStyle, DlDrawStyle::kFill);\n  EXPECT_EQ(DlStrokeCap::kDefaultCap, DlStrokeCap::kButt);\n  EXPECT_EQ(DlStrokeJoin::kDefaultJoin, DlStrokeJoin::kMiter);\n\n  EXPECT_EQ(DlPaint::kDefaultColor, DlColor::kBlack());\n  EXPECT_EQ(DlPaint::kDefaultWidth, 0.0);\n  EXPECT_EQ(DlPaint::kDefaultMiter, 4.0);\n\n  EXPECT_EQ(paint, DlPaint());\n\n  EXPECT_NE(paint, DlPaint().setAntiAlias(true));\n  EXPECT_NE(paint, DlPaint().setDither(true));\n  EXPECT_NE(paint, DlPaint().setInvertColors(true));\n  EXPECT_NE(paint, DlPaint().setColor(DlColor::kGreen()));\n  EXPECT_NE(paint, DlPaint().setAlpha(0x7f));\n  EXPECT_NE(paint, DlPaint().setBlendMode(DlBlendMode::kDstIn));\n  EXPECT_NE(paint, DlPaint().setDrawStyle(DlDrawStyle::kStrokeAndFill));\n  EXPECT_NE(paint, DlPaint().setStrokeCap(DlStrokeCap::kRound));\n  EXPECT_NE(paint, DlPaint().setStrokeJoin(DlStrokeJoin::kRound));\n  EXPECT_NE(paint, DlPaint().setStrokeWidth(6));\n  EXPECT_NE(paint, DlPaint().setStrokeMiter(7));\n\n  DlColorColorSource color_source(DlColor::kMagenta());\n  EXPECT_NE(paint, DlPaint().setColorSource(color_source.shared()));\n\n  DlBlendColorFilter color_filter(DlColor::kYellow(), DlBlendMode::kDstIn);\n  EXPECT_NE(paint, DlPaint().setColorFilter(color_filter.shared()));\n\n  DlBlurImageFilter image_filter(1.3, 4.7, DlTileMode::kClamp);\n  EXPECT_NE(paint, DlPaint().setImageFilter(image_filter.shared()));\n\n  DlBlurMaskFilter mask_filter(SkBlurStyle::kInner_SkBlurStyle, 3.14);\n  EXPECT_NE(paint, DlPaint().setMaskFilter(mask_filter.shared()));\n}\n\nTEST(DisplayListPaint, NullPointerSetGet) {\n  DlColorSource* null_color_source = nullptr;\n  DlColorFilter* null_color_filter = nullptr;\n  DlImageFilter* null_image_filter = nullptr;\n  DlMaskFilter* null_mask_filter = nullptr;\n  DlPaint paint;\n  EXPECT_EQ(paint.setColorSource(null_color_source).getColorSource(), nullptr);\n  EXPECT_EQ(paint.setColorFilter(null_color_filter).getColorFilter(), nullptr);\n  EXPECT_EQ(paint.setImageFilter(null_image_filter).getImageFilter(), nullptr);\n  EXPECT_EQ(paint.setMaskFilter(null_mask_filter).getMaskFilter(), nullptr);\n}\n\nTEST(DisplayListPaint, NullSharedPointerSetGet) {\n  std::shared_ptr<DlColorSource> null_color_source;\n  std::shared_ptr<DlColorFilter> null_color_filter;\n  std::shared_ptr<DlImageFilter> null_image_filter;\n  std::shared_ptr<DlMaskFilter> null_mask_filter;\n  DlPaint paint;\n  EXPECT_EQ(paint.setColorSource(null_color_source).getColorSource(), nullptr);\n  EXPECT_EQ(paint.setColorFilter(null_color_filter).getColorFilter(), nullptr);\n  EXPECT_EQ(paint.setImageFilter(null_image_filter).getImageFilter(), nullptr);\n  EXPECT_EQ(paint.setMaskFilter(null_mask_filter).getMaskFilter(), nullptr);\n}\n\nTEST(DisplayListPaint, ChainingConstructor) {\n  DlPaint paint =\n      DlPaint()                                                              //\n          .setAntiAlias(true)                                                //\n          .setDither(true)                                                   //\n          .setInvertColors(true)                                             //\n          .setColor(DlColor::kGreen())                                       //\n          .setAlpha(0x7F)                                                    //\n          .setBlendMode(DlBlendMode::kLuminosity)                            //\n          .setDrawStyle(DlDrawStyle::kStrokeAndFill)                         //\n          .setStrokeCap(DlStrokeCap::kSquare)                                //\n          .setStrokeJoin(DlStrokeJoin::kBevel)                               //\n          .setStrokeWidth(42)                                                //\n          .setStrokeMiter(1.5)                                               //\n          .setColorSource(DlColorColorSource(DlColor::kMagenta()).shared())  //\n          .setColorFilter(\n              DlBlendColorFilter(DlColor::kYellow(), DlBlendMode::kDstIn)\n                  .shared())\n          .setImageFilter(\n              DlBlurImageFilter(1.3, 4.7, DlTileMode::kClamp).shared())\n          .setMaskFilter(\n              DlBlurMaskFilter(SkBlurStyle::kInner_SkBlurStyle, 3.14).shared());\n  EXPECT_TRUE(paint.isAntiAlias());\n  EXPECT_TRUE(paint.isDither());\n  EXPECT_TRUE(paint.isInvertColors());\n  EXPECT_EQ(paint.getColor(), DlColor::kGreen().withAlpha(0x7F));\n  EXPECT_EQ(paint.getAlpha(), 0x7F);\n  EXPECT_EQ(paint.getBlendMode(), DlBlendMode::kLuminosity);\n  EXPECT_EQ(paint.getDrawStyle(), DlDrawStyle::kStrokeAndFill);\n  EXPECT_EQ(paint.getStrokeCap(), DlStrokeCap::kSquare);\n  EXPECT_EQ(paint.getStrokeJoin(), DlStrokeJoin::kBevel);\n  EXPECT_EQ(paint.getStrokeWidth(), 42);\n  EXPECT_EQ(paint.getStrokeMiter(), 1.5);\n  EXPECT_EQ(*paint.getColorSource(), DlColorColorSource(DlColor::kMagenta()));\n  EXPECT_EQ(*paint.getColorFilter(),\n            DlBlendColorFilter(DlColor::kYellow(), DlBlendMode::kDstIn));\n  EXPECT_EQ(*paint.getImageFilter(),\n            DlBlurImageFilter(1.3, 4.7, DlTileMode::kClamp));\n  EXPECT_EQ(*paint.getMaskFilter(),\n            DlBlurMaskFilter(SkBlurStyle::kInner_SkBlurStyle, 3.14));\n\n  EXPECT_NE(paint, DlPaint());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/picture.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/picture.h\"\n\n#include <memory>\n#include <utility>\n\nnamespace clay {\n\n#ifndef ENABLE_SKITY\nPicture::Picture(GPUObject<PictureSkia> picture, bool has_lazy_image)\n    : picture_(std::move(picture)), has_lazy_image_(has_lazy_image) {}\n#else\nPicture::Picture(GPUObject<PictureSkity> picture, bool has_lazy_image)\n    : picture_(std::move(picture)), has_lazy_image_(has_lazy_image) {}\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/picture.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_PICTURE_H_\n#define CLAY_GFX_PICTURE_H_\n\n#include <memory>\n\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#include \"clay/gfx/gpu_object.h\"\n\nnamespace clay {\n\nclass Picture {\n public:\n#ifndef ENABLE_SKITY\n  explicit Picture(GPUObject<PictureSkia> picture, bool has_lazy_image = false);\n\n  fml::RefPtr<PictureSkia> picture() const { return picture_.object(); }\n#else\n  explicit Picture(GPUObject<PictureSkity> picture,\n                   bool has_lazy_image = false);\n  fml::RefPtr<PictureSkity> picture() const { return picture_.object(); }\n\n#endif  // ENABLE_SKITY\n  bool HasLazyImage() const { return has_lazy_image_; }\n\n  bool IsEmpty() const {\n    return picture_.object() == nullptr ||\n#ifndef ENABLE_SKITY\n           picture_.object()->raw()->approximateOpCount() == 0;\n#else\n           picture_.object()->raw()->OpCount() == 0;\n#endif  // ENABLE_SKITY\n  }\n\n private:\n#ifndef ENABLE_SKITY\n  GPUObject<PictureSkia> picture_;\n#else\n  GPUObject<PictureSkity> picture_;\n#endif  // ENABLE_SKITY\n  bool has_lazy_image_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_PICTURE_H_\n"
  },
  {
    "path": "clay/gfx/pixel_helper.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_PIXEL_HELPER_H_\n#define CLAY_GFX_PIXEL_HELPER_H_\n\n#include \"build/build_config.h\"\n\nnamespace clay {\n\n// NOTE: Here we use enum instead of enum class for convenience.\nenum PixelType {\n  kPixelTypeLogical,\n  kPixelTypePhysical,\n};\n\n// The pixel type used on the framework side (i.e. Lynx).\n// Currently we use physical pixels on Android and logical pixels on others.\n#if defined(OS_ANDROID)\nstatic constexpr PixelType kPixelTypeFramework = kPixelTypePhysical;\n#else\nstatic constexpr PixelType kPixelTypeFramework = kPixelTypeLogical;\n#endif\n\n// The pixel type used on the Clay side, such as view bounds, touch positions.\n// For now we use the same pixel type with the framework side.\nstatic constexpr PixelType kPixelTypeClay = kPixelTypeFramework;\n\n#if defined(OS_ANDROID)\nstatic constexpr PixelType kPixelTypePlatform = kPixelTypePhysical;\n#else\nstatic constexpr PixelType kPixelTypePlatform = kPixelTypeLogical;\n#endif\n\n// A helper class to convert pixel values between different pixel types.\ntemplate <PixelType Current>\nclass PixelHelper {\n public:\n  virtual ~PixelHelper() = default;\n  virtual float DevicePixelRatio() const = 0;\n\n  // Get the pixel ratio from one pixel type to another. For example\n  // `GetPixelRatio<kPixelTypeLogical, kPixelTypePhysical>` returns the real\n  // device pixel ratio.\n  template <PixelType From, PixelType To>\n  constexpr float GetPixelRatio() const {\n    if constexpr (From == To) {\n      return 1;\n    } else if constexpr (From == kPixelTypeLogical) {\n      static_assert(To == kPixelTypePhysical);\n      return DevicePixelRatio();\n    } else {\n      static_assert(From == kPixelTypePhysical && To == kPixelTypeLogical);\n      return 1 / DevicePixelRatio();\n    }\n  }\n\n  // Convert the given value from one pixel type to another.\n  template <PixelType From, PixelType To, typename T>\n  constexpr T Convert(T value) const {\n    return value * GetPixelRatio<From, To>();\n  }\n\n  // Convert multiple values from one pixel type to another.\n  template <PixelType From, PixelType To, typename... Args>\n  void ConvertValues(Args&... value) const {\n    ((value = Convert<From, To>(value)), ...);\n  }\n\n  // Convert the given value from the clay pixel type to the given pixel type.\n  template <PixelType To, typename T>\n  constexpr T ConvertTo(T value) const {\n    return Convert<Current, To>(value);\n  }\n\n  // Convert the given value from the given pixel type to the clay pixel type.\n  template <PixelType From, typename T>\n  constexpr T ConvertFrom(T value) const {\n    return Convert<From, Current>(value);\n  }\n\n  // Round the given value by physical pixels regardless of the input pixel\n  // type. For example, if we have a logical pixel value of 5.3 and the dpr is\n  // 2, then we will get a result value of 5.5.\n  template <PixelType Type = Current>\n  float RoundPixels(float value) const {\n    return Convert<kPixelTypePhysical, Type>(\n        roundf(Convert<Type, kPixelTypePhysical>(value)));\n  }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_PIXEL_HELPER_H_\n"
  },
  {
    "path": "clay/gfx/rendering_backend.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_RENDERING_BACKEND_H_\n#define CLAY_GFX_RENDERING_BACKEND_H_\n\n#include <memory>\n\n#ifndef ENABLE_SKITY\n#ifdef ENABLE_SVG\n#include \"third_party/skia/modules/svg/include/SkSVGDOM.h\"\n#include \"third_party/skia/modules/svg/include/SkSVGRenderContext.h\"\n#include \"third_party/skia/modules/svg/include/SkSVGSVG.h\"\n#endif  // ENABLE_SVG\n#include \"clay/flow/rtree.h\"\n#include \"clay/gfx/skia/skia_concurrent_executor.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"third_party/skia/include/codec/SkEncodedImageFormat.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n#include \"third_party/skia/include/core/SkBlendMode.h\"\n#include \"third_party/skia/include/core/SkBlurTypes.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n#include \"third_party/skia/include/core/SkColorFilter.h\"\n#include \"third_party/skia/include/core/SkFont.h\"\n#include \"third_party/skia/include/core/SkFontMgr.h\"\n#include \"third_party/skia/include/core/SkGraphics.h\"\n#include \"third_party/skia/include/core/SkImage.h\"\n#include \"third_party/skia/include/core/SkImageInfo.h\"\n#include \"third_party/skia/include/core/SkMaskFilter.h\"\n#include \"third_party/skia/include/core/SkPaint.h\"\n#include \"third_party/skia/include/core/SkPath.h\"\n#include \"third_party/skia/include/core/SkPathEffect.h\"\n#include \"third_party/skia/include/core/SkPathMeasure.h\"\n#include \"third_party/skia/include/core/SkPathTypes.h\"\n#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n#include \"third_party/skia/include/core/SkPixmap.h\"\n#include \"third_party/skia/include/core/SkPoint.h\"\n#include \"third_party/skia/include/core/SkPoint3.h\"\n#include \"third_party/skia/include/core/SkPromiseImageTexture.h\"\n#include \"third_party/skia/include/core/SkRRect.h\"\n#include \"third_party/skia/include/core/SkSamplingOptions.h\"\n#include \"third_party/skia/include/core/SkSerialProcs.h\"\n#include \"third_party/skia/include/core/SkShader.h\"\n#include \"third_party/skia/include/core/SkStream.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n#include \"third_party/skia/include/core/SkTypeface.h\"\n#include \"third_party/skia/include/core/SkVertices.h\"\n#include \"third_party/skia/include/effects/SkDashPathEffect.h\"\n#include \"third_party/skia/include/effects/SkGradientShader.h\"\n#include \"third_party/skia/include/effects/SkImageFilters.h\"\n#include \"third_party/skia/include/effects/SkRuntimeEffect.h\"\n#include \"third_party/skia/include/encode/SkJpegEncoder.h\"\n#include \"third_party/skia/include/encode/SkPngEncoder.h\"\n#include \"third_party/skia/include/gpu/GrBackendSurface.h\"\n#include \"third_party/skia/include/gpu/GrContextOptions.h\"\n#include \"third_party/skia/include/gpu/GrContextThreadSafeProxy.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n#include \"third_party/skia/include/gpu/ganesh/SkImageGanesh.h\"\n#include \"third_party/skia/include/gpu/gl/GrGLInterface.h\"\n#include \"third_party/skia/include/pathops/SkPathOps.h\"\n#include \"third_party/skia/include/utils/SkShadowUtils.h\"\n#include \"third_party/skia/src/gpu/ganesh/GrBackendUtils.h\"\n#else\n#include \"clay/gfx/skity/skity_auto_canvas_save.h\"\n#include \"clay/gfx/skity/skity_lazy_image_traveller.h\"\n#include \"skity/codec/codec.hpp\"\n#include \"skity/effect/color_filter.hpp\"\n#include \"skity/effect/image_filter.hpp\"\n#include \"skity/effect/mask_filter.hpp\"\n#include \"skity/effect/path_effect.hpp\"\n#include \"skity/effect/shader.hpp\"\n#include \"skity/geometry/point.hpp\"\n#include \"skity/gpu/gpu_context.hpp\"\n#include \"skity/gpu/gpu_render_target.hpp\"\n#include \"skity/gpu/gpu_surface.hpp\"\n#include \"skity/graphic/bitmap.hpp\"\n#include \"skity/graphic/blend_mode.hpp\"\n#include \"skity/graphic/color.hpp\"\n#include \"skity/graphic/image.hpp\"\n#include \"skity/graphic/paint.hpp\"\n#include \"skity/graphic/path.hpp\"\n#include \"skity/graphic/path_measure.hpp\"\n#include \"skity/graphic/path_op.hpp\"\n#include \"skity/graphic/sampling_options.hpp\"\n#include \"skity/include/skity/io/data.hpp\"\n#include \"skity/include/skity/text/font_manager.hpp\"\n#include \"skity/recorder/picture_recorder.hpp\"\n#include \"skity/render/canvas.hpp\"\n#include \"skity/text/font.hpp\"\n#include \"skity/text/text_blob.hpp\"\n#endif  // ENABLE_SKITY\n\nnamespace clay {\n\n#ifndef ENABLE_SKITY\n// PROTOTYPES\nusing GrAutoCanvasRestore = SkAutoCanvasRestore;\nusing GrBitmap = SkBitmap;\nusing GrContext = GrDirectContext;\nusing GrContextPtr = sk_sp<GrDirectContext>;\nusing GrCanvas = SkCanvas;\nusing GrPaint = SkPaint;\nusing GrSurface = SkSurface;\nusing GrSurfacePtr = sk_sp<SkSurface>;\nusing GrPath = SkPath;\nusing GrImage = SkImage;\nusing GrImagePtr = sk_sp<SkImage>;\nusing GrSamplingOptions = SkSamplingOptions;\nusing GrShaderPtr = sk_sp<SkShader>;\nusing GrColor = SkColor;\nusing GrColorFilterPtr = sk_sp<SkColorFilter>;\nusing GrPoint = SkPoint;\nusing GrTextBlobPtr = sk_sp<SkTextBlob>;\nusing GrGpuBackendType = GrBackendApi;\nusing GrImageInfo = SkImageInfo;\n#ifdef ENABLE_SVG\nusing SVGDomPtr = sk_sp<SkSVGDOM>;\n#endif  // ENABLE_SVG\nusing GrColorFilter = SkColorFilter;\nusing GrColorFilterPtr = sk_sp<SkColorFilter>;\nusing GrColorFilters = SkColorFilters;\nusing GrShader = SkShader;\nusing GrShaderPtr = sk_sp<SkShader>;\nusing GrImageFilter = SkImageFilter;\nusing GrImageFilterPtr = sk_sp<SkImageFilter>;\nusing GrImageFilters = SkImageFilters;\nusing GrMaskFilter = SkMaskFilter;\nusing GrMaskFilterPtr = sk_sp<SkMaskFilter>;\nusing GrPathEffect = SkPathEffect;\nusing GrPathEffectPtr = sk_sp<SkPathEffect>;\nusing GrData = SkData;\nusing GrDataPtr = sk_sp<SkData>;\nusing GrClipOp = SkClipOp;\nusing GrPathMeasure = SkPathMeasure;\nusing GrPathDirection = SkPathDirection;\nusing GrFontMgr = SkFontMgr;\nusing GrRect = SkRect;\nusing GrBlurStyle = SkBlurStyle;\nusing GrPicturePtr = sk_sp<SkPicture>;\n\n// FUNCTIONS\n// GrContext\n\n// GrSurface\n#define SURFACE_GET_CANVAS(surface, clear) surface->getCanvas()\n#define SURFACE_GET_WIDTH(surface) surface->width()\n#define SURFACE_GET_HEIGHT(surface) surface->height()\n\n// GrCanvas\n#define CANVAS_SAVE(canvas) canvas->save()\n#define CANVAS_FLUSH(canvas) canvas->flush()\n#define CANVAS_RESTORE(canvas) canvas->restore()\n#define CANVAS_AUTO_RESTORE(canvas, do_save) \\\n  SkAutoCanvasRestore auto_restore(canvas, do_save)\n#define CANVAS_RESET_MATRIX(canvas) canvas->resetMatrix()\n#define CANVAS_DRAW_RECT(canvas, rect, paint) \\\n  canvas->drawRect(clay::ConvertSkityRectToSkRect(rect), paint)\n#define CANVAS_DRAW_PATH(canvas, path, paint) canvas->drawPath(path, paint)\n#define CANVAS_CLIP_RECT(canvas, rect) \\\n  canvas->clipRect(clay::ConvertSkityRectToSkRect(rect))\n#define CANVAS_CLIP_RECT_WITH_OP(canvas, rect, op) \\\n  canvas->clipRect(clay::ConvertSkityRectToSkRect(rect), op)\n#define CANVAS_DRAW_PAINT(canvas, paint) canvas->drawPaint(paint)\n#define CANVAS_DRAW_IMAGE(canvas, image, bound, sampling_options, paint) \\\n  canvas->drawImage(image, bound.Left(), bound.Top(), sampling_options, paint)\n#define CANVAS_DRAW_IMAGE_RECT(canvas, image, rect, sampling_options, paint) \\\n  canvas->drawImageRect(image, clay::ConvertSkityRectToSkRect(rect),         \\\n                        sampling_options, paint)\n#define CANVAS_DRAW_IMAGE_SRC_RECT(canvas, image, src_rect, dst_rect,    \\\n                                   sampling_options, paint)              \\\n  canvas->drawImageRect(image, clay::ConvertSkityRectToSkRect(src_rect), \\\n                        clay::ConvertSkityRectToSkRect(dst_rect),        \\\n                        sampling_options, &paint,                        \\\n                        SkCanvas::kFast_SrcRectConstraint);\n#define CANVAS_TRANSLATE(canvas, left, top) canvas->translate(left, top)\n#define CANVAS_GET_TOTAL_MATRIX(canvas) \\\n  clay::ConvertSkMatrixToSkityMatrix(canvas->getTotalMatrix())\n#define CANVAS_CLEAR(canvas, color) canvas->clear(clay::ToSk(color))\n#define CANVAS_SCALE(canvas, x, y) canvas->scale(x, y)\n#define CANVAS_CONCAT(canvas, matrix) \\\n  canvas->concat(clay::ConvertSkityMatrixToSkMatrix(matrix))\n#define CANVAS_SAVELAYER(canvas, bounds, paint) \\\n  canvas->saveLayer(clay::ConvertSkityRectToSkRect(bounds), &paint)\n#define CANVAS_DRAW_TEXTBLOB(blob, x, y, paint) \\\n  canvas->drawTextBlob(blob, x, y, paint)\n#define CANVAS_GET_SAVE_COUNT(canvas) canvas->getSaveCount()\n#define CANVAS_RESTORE_TO_COUNT(canvas, count) canvas->restoreToCount(count)\n\n// GrPaint\n#define PAINT_SET_COLOR(paint, color) paint.setColor(color)\n#define PAINT_SET_STYLE(paint, style) \\\n  paint.setStyle(static_cast<SkPaint::Style>(style))\n#define PAINT_SET_BLEND_MODE(paint, blend_mode) \\\n  paint.setBlendMode(ToSk(blend_mode))\n#define PAINT_SET_STROKE_WIDTH(paint, width) paint.setStrokeWidth(width)\n#define PAINT_SET_SHADER(paint, shader) paint.setShader(shader)\n#define PAINT_SET_IMAGE_FILTER(paint, image_filter) \\\n  paint.setImageFilter(image_filter)\n#define PAINT_SET_ALPHAF(paint, alphaf) paint.setAlphaf(alphaf)\n#define PAINT_SET_COLOR_FILTER(paint, color_filter) \\\n  paint.setColorFilter(color_filter)\n#define PAINT_SET_MASK_FILTER(paint, mask_filter) \\\n  paint.setMaskFilter(mask_filter)\n#define PAINT_SET_ANTI_ALIAS(paint, aa) paint.setAntiAlias(aa)\n#define PAINT_SET_STROKE_CAP(paint, cap) paint.setStrokeCap(cap)\n#define PAINT_SET_STROKE_JOIN(paint, join) paint.setStrokeJoin(join)\n#define PAINT_SET_STROKE_MITER(paint, miter) paint.setStrokeMiter(miter)\n#define PAINT_SET_STROKE_WIDTH(paint, width) paint.setStrokeWidth(width)\n#define PAINT_SET_PATH_EFFECT(paint, path_effect) \\\n  paint.setPathEffect(path_effect)\n#define PAINT_CAN_COMPUTE_FAST_BOUNDS(paint) paint.canComputeFastBounds()\n#define PAINT_COMPUTE_FAST_BOUNDS(paint, orig, storage) \\\n  paint.computeFastBounds(orig, &storage)\n\n// GrPath\n#define PATH_ADD_RECT(path, rect) \\\n  path.addRect(clay::ConvertSkityRectToSkRect(rect))\n#define PATH_ADD_RRECT(path, round_rect) \\\n  path.addRRect(clay::ConvertSkityRRectToSkia(round_rect))\n#define PATH_ADD_PATH(path, drawing_path) path.addPath(drawing_path)\n#define PATH_ADD_OVAL(path, oval, direction) \\\n  path.addOval(clay::ConvertSkityRectToSkRect(oval), direction)\n#define PATH_ADD_OVAL_START(path, oval, direction, start) \\\n  path.addOval(clay::ConvertSkityRectToSkRect(oval), direction, start)\n#define PATH_ADD_ROUND_RECT(path, rect, radius_x, radius_y, direction)        \\\n  path.addRoundRect(clay::ConvertSkityRectToSkRect(rect), radius_x, radius_y, \\\n                    direction)\n#define PATH_IS_EMPTY(path) path.isEmpty()\n#define PATH_IS_RECT(path, rect) path.isRect(rect)\n#define PATH_SET_FILL_TYPE(path, fill_type) \\\n  path.setFillType(static_cast<SkPathFillType>(fill_type))\n#define PATH_OP(hole_path, inner_path, path_op, intersect_path) \\\n  Op(hole_path, inner_path, static_cast<SkPathOp>(path_op), &intersect_path)\n#define PATH_MOVE_TO(path, x, y) path.moveTo(x, y)\n#define PATH_MOVE_TO_POINT(path, point) path.moveTo(point)\n#define PATH_LINE_TO(path, x, y) path.lineTo(x, y)\n#define PATH_LINE_TO_POINT(path, point) path.lineTo(point)\n#define PATH_RLINE_TO(path, x, y) path.rLineTo(x, y)\n#define PATH_CLOSE(path) path.close()\n#define PATH_GET_BOUNDS(path) path.getBounds()\n#define PATH_TRANSFORM(path, matrix) \\\n  path.transform(clay::ConvertSkityMatrixToSkMatrix(matrix))\n#define PATH_CUBIC_TO_POINT(path, point1, point2, point3) \\\n  path.cubicTo(point1, point2, point3)\n#define PATH_CUBIC_TO(path, x1, y1, x2, y2, x3, y3) \\\n  path.cubicTo(x1, y1, x2, y2, x3, y3)\n#define PATH_QUAD_TO_POINT(path, point1, point2) path.quadTo(point1, point2)\n#define PATH_QUAD_TO(path, x1, y1, x2, y2) path.quadTo(x1, y1, x2, y2)\n#define PATH_ARC_TO(path, point1, axis, arc, sweep, point2) \\\n  path.arcTo(point1, axis, arc, sweep, point2)\n#define PATH_ARC_TO_ANGLE(path, oval, start_angle, sweep_angle, force_move)  \\\n  path.arcTo(clay::ConvertSkityRectToSkRect(oval), start_angle, sweep_angle, \\\n             force_move)\n#define PATH_GET_LAST_POINT(path, point) path.getLastPt(point)\n#define PATH_SWAP(path, other) path->swap(other)\n\n// GrPathMeasure\n#define PATH_MEASURE_GET_LENGTH(measure) measure.getLength()\n#define PATH_MEASURE_NEXT_CONTOUR(measure) measure.nextContour()\n#define PATH_MEASURE_SET_PATH(measure, path) measure.setPath(path, false)\n#define PATH_MEASURE_GET_POS_TAN(measure, distance, position, tangent) \\\n  measure.getPosTan(distance, position, tangent)\n\n// GrImage\n#define IMAGE_WIDTH(image) image->width()\n#define IMAGE_HEIGHT(image) image->height()\n#define IMAGE_BYTE_SIZE(image) image->imageInfo().computeMinByteSize()\n\n// GrSamplingOptions\n#define SAMPLING_OPTIONS(filter_mode, mipmap_mode)          \\\n  SkSamplingOptions(static_cast<SkFilterMode>(filter_mode), \\\n                    static_cast<SkMipmapMode>(mipmap_mode))\n\n// GrColorFilter\n#define COLOR_FILTER_MATRIX(m) SkColorFilters::Matrix(m)\n#define COLOR_FILTER_FILTER_COLOR(filter, color) \\\n  filter->filterColor(ToSk(color))\n\n// GrImageFilter\n#define IMAGE_FILTERS_BLUR(sigma_x, sigma_y, tile_mode, input) \\\n  SkImageFilters::Blur(sigma_x, sigma_y, tile_mode, input)\n#define IMAGE_FILTERS_DILATE(radius_x, radius_y, input) \\\n  SkImageFilters::Dilate(radius_x, radius_y, input)\n#define IMAGE_FILTERS_ERODE(radius_x, radius_y, input) \\\n  SkImageFilters::Erode(radius_x, radius_y, input)\n#define IMAGE_FILTERS_MATRIX_TRANSFORM(matrix, sampling, input)         \\\n  SkImageFilters::MatrixTransform(ConvertSkityMatrixToSkMatrix(matrix), \\\n                                  sampling, input)\n#define IMAGE_FILTERS_COLOR_FILTER(color_filter, input) \\\n  SkImageFilters::ColorFilter(color_filter, input)\n\n// GrRect\n#define RECT_ASSIGN(dst_rect, src_rect) \\\n  dst_rect = clay::ConvertSkRectToSkityRect(src_rect)\n#define RECT_IS_SORTED(rect) rect.isSorted()\n\n// GrImageInfo\n#define IMAGE_INFO_MAKE_WH(width, height) \\\n  SkImageInfo::MakeN32Premul(width, height)\n#define IMAGE_INFO_MAKE_DIMENSIONS(image_info, dimensions) \\\n  image_info.makeDimensions(SkISize::Make((int)dimensions.x, (int)dimensions.y))\n\n// GrShader\n#define SHADER_IS_OPAQUE(shader) shader->isOpaque()\n\n// GrPathEffect\n#define PATH_EFFECT_MAKE_DASH(intervals, count, phase) \\\n  SkDashPathEffect::Make(intervals, count, phase)\n\n// GrFontMgr\n#define FONT_MANAGER_MATCH_FAMILY(mgr_ptr, family_name) \\\n  mgr_ptr->matchFamily(family_name)\n\n// GrFontStyleSet\n#define FONT_STYLE_SET_COUNT(style_set) style_set->count()\n\n// GrPoint\n#define POINT_GET_X(point) point.fX\n#define POINT_GET_Y(point) point.fY\n#define POINT_SET_X(point, value) point.fX = value\n#define POINT_SET_Y(point, value) point.fY = value\n\n// GrBlurStyle\n#define BLUR_STYLE_NORMAL SkBlurStyle::kNormal_SkBlurStyle\n#define BLUR_STYLE_SOLID SkBlurStyle::kSolid_SkBlurStyle\n#define BLUR_STYLE_OUTER SkBlurStyle::kOuter_SkBlurStyle\n#define BLUR_STYLE_INNER SkBlurStyle::kInner_SkBlurStyle\n\n// GrData\n#define DATA_GET_DATA(pdata) pdata->data()\n#define DATA_GET_SIZE(data) data->size()\n#define DATA_GET_WRITABLE_DATA(data) data->writable_data()\n#define DATA_GET_BYTES(data) data->bytes()\n#define DATA_IS_EMPTY(data) data->isEmpty()\n\n#else\n// PROTOTYPES\nusing GrAutoCanvasRestore = SkityAutoCanvasRestore;\nusing GrBitmap = skity::Bitmap;\nusing GrContext = skity::GPUContext;\nusing GrContextPtr = std::shared_ptr<skity::GPUContext>;\nusing GrCanvas = skity::Canvas;\nusing GrPaint = skity::Paint;\nusing GrSurface = skity::GPUSurface;\nusing GrSurfacePtr = std::shared_ptr<skity::GPUSurface>;\nusing GrPath = skity::Path;\nusing GrImage = skity::Image;\nusing GrImagePtr = std::shared_ptr<skity::Image>;\nusing GrSamplingOptions = skity::SamplingOptions;\nusing GrShaderPtr = std::shared_ptr<skity::Shader>;\nusing GrColor = skity::Color;\nusing GrColorFilterPtr = std::shared_ptr<skity::ColorFilter>;\nusing GrPoint = skity::Point;\nusing GrTextBlobPtr = std::shared_ptr<skity::TextBlob>;\nusing GrGpuBackendType = skity::GPUBackendType;\nusing GrImageInfo = struct ImageInfo;\n#ifdef ENABLE_SVG\nusing SVGDomPtr = std::shared_ptr<class SVGDom>;\n#endif  // ENABLE_SVG\nusing GrColorFilter = skity::ColorFilter;\nusing GrColorFilterPtr = std::shared_ptr<skity::ColorFilter>;\nusing GrColorFilters = skity::ColorFilters;\nusing GrShader = skity::Shader;\nusing GrShaderPtr = std::shared_ptr<skity::Shader>;\nusing GrImageFilter = skity::ImageFilter;\nusing GrImageFilterPtr = std::shared_ptr<skity::ImageFilter>;\nusing GrImageFilters = skity::ImageFilters;\nusing GrMaskFilter = skity::MaskFilter;\nusing GrMaskFilterPtr = std::shared_ptr<skity::MaskFilter>;\nusing GrPathEffect = skity::PathEffect;\nusing GrPathEffectPtr = std::shared_ptr<skity::PathEffect>;\nusing GrData = skity::Data;\nusing GrDataPtr = std::shared_ptr<skity::Data>;\nusing GrClipOp = skity::Canvas::ClipOp;\nusing GrPathMeasure = skity::PathMeasure;\nusing GrPathDirection = skity::Path::Direction;\nusing GrFontMgr = skity::FontManager;\nusing GrRect = skity::Rect;\nusing GrBlurStyle = skity::BlurStyle;\nusing GrPicturePtr = std::shared_ptr<skity::DisplayList>;\n\n// FUNCTIONS\n// GrContext\n\n// GrSurface\n#define SURFACE_GET_CANVAS(surface, clear) surface->LockCanvas(clear)\n#define SURFACE_GET_WIDTH(surface) surface->GetWidth()\n#define SURFACE_GET_HEIGHT(surface) surface->GetHeight()\n\n// GrCanvas\n#define CANVAS_SAVE(canvas) canvas->Save()\n#define CANVAS_FLUSH(canvas) canvas->Flush()\n#define CANVAS_RESTORE(canvas) canvas->Restore()\n#define CANVAS_AUTO_RESTORE(canvas, do_save) \\\n  clay::SkityAutoCanvasRestore auto_restore(canvas, do_save)\n#define CANVAS_RESET_MATRIX(canvas) canvas->ResetMatrix()\n#define CANVAS_DRAW_RECT(canvas, rect, paint) canvas->DrawRect(rect, paint)\n#define CANVAS_DRAW_PATH(canvas, path, paint) canvas->DrawPath(path, paint)\n#define CANVAS_CLIP_RECT(canvas, rect) canvas->ClipRect(rect)\n#define CANVAS_CLIP_RECT_WITH_OP(canvas, rect, op) canvas->ClipRect(rect, op)\n#define CANVAS_DRAW_PAINT(canvas, paint) canvas->DrawPaint(paint)\n#define CANVAS_DRAW_IMAGE(canvas, image, bound, sampling_options, paint) \\\n  canvas->DrawImage(image, bound, sampling_options, paint)\n#define CANVAS_DRAW_IMAGE_RECT(canvas, image, rect, sampling_options, paint) \\\n  canvas->DrawImage(image, rect, sampling_options, paint)\n#define CANVAS_DRAW_IMAGE_SRC_RECT(canvas, image, src_rect, dst_rect, \\\n                                   sampling_options, paint)           \\\n  canvas->DrawImageRect(image, src_rect, dst_rect, sampling_options, &paint)\n#define CANVAS_TRANSLATE(canvas, left, top) canvas->Translate(left, top)\n#define CANVAS_GET_TOTAL_MATRIX(canvas) canvas->GetTotalMatrix()\n#define CANVAS_CLEAR(canvas, color) canvas->Clear(clay::ToSk(color))\n#define CANVAS_SCALE(canvas, x, y) canvas->Scale(x, y)\n#define CANVAS_CONCAT(canvas, matrix) canvas->Concat(matrix)\n#define CANVAS_SAVELAYER(canvas, bounds, paint) canvas->SaveLayer(bounds, paint)\n#define CANVAS_DRAW_TEXTBLOB(blob, x, y, paint) \\\n  canvas->DrawTextBlob(blob, x, y, paint)\n#define CANVAS_GET_SAVE_COUNT(canvas) canvas->GetSaveCount()\n#define CANVAS_RESTORE_TO_COUNT(canvas, count) canvas->RestoreToCount(count)\n\n// GrPaint\n#define PAINT_SET_COLOR(paint, color) paint.SetColor(color)\n#define PAINT_SET_STYLE(paint, style) \\\n  paint.SetStyle(static_cast<skity::Paint::Style>(style))\n#define PAINT_SET_BLEND_MODE(paint, blend_mode) \\\n  paint.SetBlendMode(ToSk(blend_mode))\n#define PAINT_SET_STROKE_WIDTH(paint, width) paint.SetStrokeWidth(width)\n#define PAINT_SET_SHADER(paint, shader) paint.SetShader(shader)\n#define PAINT_SET_IMAGE_FILTER(paint, image_filter) \\\n  paint.SetImageFilter(image_filter)\n#define PAINT_SET_ALPHAF(paint, alphaf) paint.SetAlphaF(alphaf)\n#define PAINT_SET_COLOR_FILTER(paint, color_filter) \\\n  paint.SetColorFilter(color_filter)\n#define PAINT_SET_MASK_FILTER(paint, mask_filter) \\\n  paint.SetMaskFilter(mask_filter)\n#define PAINT_SET_ANTI_ALIAS(paint, aa) paint.SetAntiAlias(aa)\n#define PAINT_SET_STROKE_CAP(paint, cap) paint.SetStrokeCap(cap)\n#define PAINT_SET_STROKE_JOIN(paint, join) paint.SetStrokeJoin(join)\n#define PAINT_SET_STROKE_MITER(paint, miter) paint.SetStrokeMiter(miter)\n#define PAINT_SET_STROKE_WIDTH(paint, width) paint.SetStrokeWidth(width)\n#define PAINT_SET_PATH_EFFECT(paint, path_effect) \\\n  paint.SetPathEffect(path_effect)\n#define PAINT_CAN_COMPUTE_FAST_BOUNDS(paint) paint.CanComputeFastBounds()\n#define PAINT_COMPUTE_FAST_BOUNDS(paint, orig, storage) \\\n  paint.ComputeFastBounds(orig)\n\n// GrPath\n#define PATH_ADD_RECT(path, rect) path.AddRect(rect)\n#define PATH_ADD_RRECT(path, round_rect) path.AddRRect(round_rect)\n#define PATH_ADD_PATH(path, drawing_path) path.AddPath(drawing_path)\n#define PATH_ADD_OVAL(path, oval, direction) path.AddOval(oval, direction)\n#define PATH_ADD_OVAL_START(path, oval, direction, start) \\\n  path.AddOval(oval, direction, start)\n#define PATH_ADD_ROUND_RECT(path, rect, radius_x, radius_y, direction) \\\n  path.AddRoundRect(rect, radius_x, radius_y, direction)\n#define PATH_IS_EMPTY(path) path.IsEmpty()\n#define PATH_IS_RECT(path, rect) path.IsRect(rect)\n#define PATH_SET_FILL_TYPE(path, fill_type) \\\n  path.SetFillType(static_cast<skity::Path::PathFillType>(fill_type))\n#define PATH_OP(hole_path, inner_path, path_op, intersect_path)   \\\n  skity::PathOp::Execute(hole_path, inner_path,                   \\\n                         static_cast<skity::PathOp::Op>(path_op), \\\n                         &intersect_path)\n#define PATH_MOVE_TO(path, x, y) path.MoveTo(x, y)\n#define PATH_MOVE_TO_POINT(path, point) path.MoveTo(point)\n#define PATH_LINE_TO(path, x, y) path.LineTo(x, y)\n#define PATH_LINE_TO_POINT(path, point) path.LineTo(point)\n#define PATH_RLINE_TO(path, delta_x, delta_y)                    \\\n  {                                                              \\\n    skity::Point last_point;                                     \\\n    path.GetLastPt(&last_point);                                 \\\n    path.LineTo(last_point.x + delta_x, last_point.y + delta_y); \\\n  }\n#define PATH_CLOSE(path) path.Close()\n#define PATH_GET_BOUNDS(path) path.GetBounds()\n#define PATH_TRANSFORM(path, matrix) path = path.CopyWithMatrix(matrix)\n#define PATH_CUBIC_TO_POINT(path, point1, point2, point3) \\\n  path.CubicTo(point1, point2, point3)\n#define PATH_CUBIC_TO(path, x1, y1, x2, y2, x3, y3) \\\n  path.CubicTo(x1, y1, x2, y2, x3, y3)\n#define PATH_QUAD_TO_POINT(path, point1, point2) path.QuadTo(point1, point2)\n#define PATH_QUAD_TO(path, x1, y1, x2, y2) path.QuadTo(x1, y1, x2, y2)\n#define PATH_ARC_TO(path, point1, axis, arc, sweep, point2) \\\n  path.ArcTo(point1.x, point1.y, axis, arc, sweep, point2.x, point2.y)\n#define PATH_ARC_TO_ANGLE(path, oval, start_angle, sweep_angle, force_move) \\\n  path.ArcTo(oval, start_angle, sweep_angle, force_move)\n#define PATH_GET_LAST_POINT(path, point) path.GetLastPt(point)\n#define PATH_SWAP(path, other) path->Swap(other)\n\n// GrPathMeasure\n#define PATH_MEASURE_GET_LENGTH(measure) measure.GetLength()\n#define PATH_MEASURE_NEXT_CONTOUR(measure) measure.NextContour()\n#define PATH_MEASURE_SET_PATH(measure, path) measure.SetPath(path, false)\n#define PATH_MEASURE_GET_POS_TAN(measure, distance, position, tangent) \\\n  measure.GetPosTan(distance, position, tangent)\n\n// GrImage\n#define IMAGE_WIDTH(image) image->Width()\n#define IMAGE_HEIGHT(image) image->Height()\n#define IMAGE_BYTE_SIZE(image) image->Width() * image->Height() * 4\n\n// GrSamplingOptions\n#define SAMPLING_OPTIONS(filter_mode, mipmap_mode)                    \\\n  skity::SamplingOptions(static_cast<skity::FilterMode>(filter_mode), \\\n                         static_cast<skity::MipmapMode>(mipmap_mode))\n\n// GrColorFilter\n#define COLOR_FILTER_MATRIX(m) skity::ColorFilters::Matrix(m)\n#define COLOR_FILTER_FILTER_COLOR(filter, color) \\\n  filter->FilterColor(ToSk(color))\n\n// GrImageFilter\n#define IMAGE_FILTERS_BLUR(sigma_x, sigma_y, tile_mode, input) \\\n  skity::ImageFilters::Blur(sigma_x, sigma_y)\n#define IMAGE_FILTERS_DILATE(radius_x, radius_y, input) \\\n  skity::ImageFilters::Dilate(radius_x, radius_y)\n#define IMAGE_FILTERS_ERODE(radius_x, radius_y, input) \\\n  skity::ImageFilters::Erode(radius_x, radius_y)\n#define IMAGE_FILTERS_MATRIX_TRANSFORM(matrix, sampling, input) \\\n  skity::ImageFilters::MatrixTransform(matrix)\n#define IMAGE_FILTERS_COLOR_FILTER(color_filter, input) \\\n  skity::ImageFilters::ColorFilter(color_filter)\n\n// GrRect\n#define RECT_ASSIGN(dst_rect, src_rect) dst_rect = src_rect\n#define RECT_IS_SORTED(rect) rect.IsSorted()\n\n// GrImageInfo\n#define IMAGE_INFO_MAKE_WH(width, height) ImageInfo::makeWH(width, height)\n#define IMAGE_INFO_MAKE_DIMENSIONS(image_info, dimensions) \\\n  image_info.makeDimensions(dimensions)\n\n// GrShader\n#define SHADER_IS_OPAQUE(shader) shader->IsOpaque()\n\n// GrPathEffect\n#define PATH_EFFECT_MAKE_DASH(intervals, count, phase) \\\n  skity::PathEffect::MakeDashPathEffect(intervals, count, phase)\n\n// GrFontMgr\n#define FONT_MANAGER_MATCH_FAMILY(mgr_ptr, family_name) \\\n  mgr_ptr->MatchFamily(family_name)\n\n// GrFontStyleSet\n#define FONT_STYLE_SET_COUNT(style_set) style_set->Count()\n\n// GrPoint\n#define POINT_GET_X(point) point.x\n#define POINT_GET_Y(point) point.y\n#define POINT_SET_X(point, value) point.x = value\n#define POINT_SET_Y(point, value) point.y = value\n\n// GrBlurStyle\n#define BLUR_STYLE_NORMAL skity::BlurStyle::kNormal\n#define BLUR_STYLE_SOLID skity::BlurStyle::kSolid\n#define BLUR_STYLE_OUTER skity::BlurStyle::kOuter\n#define BLUR_STYLE_INNER skity::BlurStyle::kInner\n\n// GrData\n#define DATA_GET_DATA(pdata) pdata->RawData()\n#define DATA_GET_SIZE(data) data->Size()\n#define DATA_GET_WRITABLE_DATA(data) const_cast<void*>(data->RawData())\n#define DATA_GET_BYTES(data) data->Bytes()\n#define DATA_IS_EMPTY(data) data->IsEmpty()\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_RENDERING_BACKEND_H_\n"
  },
  {
    "path": "clay/gfx/scroll_direction.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SCROLL_DIRECTION_H_\n#define CLAY_GFX_SCROLL_DIRECTION_H_\n\nnamespace clay {\n// used by both scroll view and list view\nenum class ScrollDirection {\n  kNone = 0,\n  kHorizontal = 1,\n  kVertical = kHorizontal << 1,\n  // Not support now\n  // kBoth = kHorizontal | kVertical,\n};\n\nstatic constexpr ScrollDirection kDefaultScrollDirection =\n    ScrollDirection::kVertical;\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SCROLL_DIRECTION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\nif (!enable_skity) {\n  import(\"//third_party/skia/gn/skia.gni\")\n}\n\nsource_set(\"shared_image\") {\n  sources = [\n    \"fence_sync.h\",\n    \"shared_image_backing.cc\",\n    \"shared_image_backing.h\",\n    \"shared_image_representation.cc\",\n    \"shared_image_representation.h\",\n    \"shared_image_sink.cc\",\n    \"shared_image_sink.h\",\n    \"shared_image_sink_accessor.cc\",\n    \"shared_image_sink_accessor.h\",\n    \"utils/image_utils.cc\",\n    \"utils/image_utils.h\",\n  ]\n\n  public_deps = [ \"../../fml:fml\" ]\n\n  defines = []\n\n  deps = [ \"../../common/graphics:gl_scoped_binder\" ]\n\n  public_configs = [ \"../../:config\" ]\n\n  if (!enable_skity && skia_use_gl) {\n    defines += [ \"SKIA_ENABLE_GL\" ]\n    sources += [\n      \"skia_gl_image_representation.cc\",\n      \"skia_gl_image_representation.h\",\n    ]\n  }\n  if (!enable_skity && skia_use_metal) {\n    defines += [ \"SKIA_ENABLE_METAL\" ]\n    sources += [\n      \"skia_mtl_image_representation.h\",\n      \"skia_mtl_image_representation.mm\",\n    ]\n  }\n\n  if (enable_skity) {\n    if (is_android || is_win) {\n      sources += [\n        \"skity_gl_image_representation.cc\",\n        \"skity_gl_image_representation.h\",\n        \"utils/gl_texture_converter.cc\",\n        \"utils/gl_texture_converter.h\",\n      ]\n    } else if (is_ios || is_mac) {\n      sources += [\n        \"skity_mtl_image_representation.h\",\n        \"skity_mtl_image_representation.mm\",\n      ]\n    }\n  } else {\n    public_deps += [ \"//third_party/skia\" ]\n  }\n\n  if (is_apple) {\n    sources += [\n      \"cv_pixelbuffer_image_backing.h\",\n      \"cv_pixelbuffer_image_backing.mm\",\n      \"iosurface_image_backing.h\",\n      \"iosurface_image_backing.mm\",\n    ]\n\n    sources += [\n      \"mtl_fence_sync.h\",\n      \"mtl_fence_sync.mm\",\n      \"mtl_image_representation.h\",\n      \"mtl_image_representation.mm\",\n    ]\n\n    frameworks = [\n      \"IOSurface.framework\",\n      \"Metal.framework\",\n      \"CoreVideo.framework\",\n    ]\n\n    cflags_objcc = [ \"-fobjc-weak\" ]  # tweak Skia include files\n\n    if (is_mac) {\n      sources += [\n        \"cgl_image_representation.h\",\n        \"cgl_image_representation.mm\",\n      ]\n\n      frameworks += [ \"OpenGL.framework\" ]\n    }\n\n    if (is_ios) {\n      sources += [\n        \"eagl_image_representation.h\",\n        \"eagl_image_representation.mm\",\n      ]\n\n      frameworks += [ \"OpenGLES.framework\" ]\n    }\n  }\n\n  if (is_win) {\n    defines += [ \"NOMINMAX\" ]\n\n    sources += [\n      \"angle_d3d_image_representation.cc\",\n      \"angle_d3d_image_representation.h\",\n      \"d3d9_texture_image_backing.cc\",\n      \"d3d9_texture_image_backing.h\",\n      \"d3d_image_representation.cc\",\n      \"d3d_image_representation.h\",\n      \"d3d_texture_image_backing.cc\",\n      \"d3d_texture_image_backing.h\",\n      \"utils/angle_get_proc.cc\",\n      \"utils/angle_get_proc.h\",\n      \"utils/dxgi_utils.cc\",\n      \"utils/dxgi_utils.h\",\n      \"utils/functions_angle.cc\",\n      \"utils/functions_angle.h\",\n    ]\n\n    if (clay_force_d3d9) {\n      defines += [ \"CLAY_FORCE_D3D9\" ]\n    }\n\n    # prevent using ANGLE symbols directly\n    configs += [ \"//third_party/angle:gl_prototypes\" ]\n\n    deps += [\n      \"//third_party/angle:includes\",\n      \"//third_party/angle:libEGL_static\",  # the order of libEGL_static and\n                                            # libGLESv2_static is important.. if\n                                            # reversed, will cause a linker\n                                            # error\n                                            # DllMain already defined in\n                                            # LIBCMTD.lib\n      \"//third_party/angle:libGLESv2_static\",\n    ]\n  }\n\n  if (is_android) {\n    defines += [\n      \"EGL_EGLEXT_PROTOTYPES\",\n      \"GL_GLEXT_PROTOTYPES\",\n    ]\n    sources += [\n      \"android/android_hardwarebuffer_utils.cc\",\n      \"android/android_hardwarebuffer_utils.h\",\n      \"android/scoped_a_native_window.cc\",\n      \"android/scoped_a_native_window.h\",\n      \"android/scoped_java_surface.cc\",\n      \"android/scoped_java_surface.h\",\n      \"android/surface_texture.cc\",\n      \"android/surface_texture.h\",\n      \"android/surface_texture_listener.cc\",\n      \"android/surface_texture_listener.h\",\n      \"android_egl_image_representation.cc\",\n      \"android_egl_image_representation.h\",\n      \"android_hardwarebuffer_image_backing.cc\",\n      \"android_hardwarebuffer_image_backing.h\",\n      \"android_surface_texture_image_backing.cc\",\n      \"android_surface_texture_image_backing.h\",\n      \"egl_image_backing.cc\",\n      \"egl_image_backing.h\",\n      \"vulkan_image_hardwarebuffer_representation.cc\",\n      \"vulkan_image_hardwarebuffer_representation.h\",\n    ]\n    deps += [ \"../../fml:fml\" ]\n    configs += [ \"../../:config\" ]\n  }\n\n  if (is_linux) {\n    sources += [\n      \"epoxy_shm_image_backing.cc\",\n      \"epoxy_shm_image_backing.h\",\n      \"epoxy_shm_image_representation.cc\",\n      \"epoxy_shm_image_representation.h\",\n      \"linux_shm_image_representation.cc\",\n      \"linux_shm_image_representation.h\",\n    ]\n\n    if (enable_software_rendering) {\n      sources += [\n        \"angle_software_shm_image_backing.cc\",\n        \"angle_software_shm_image_backing.h\",\n        \"angle_sw_shm_image_representation.cc\",\n        \"angle_sw_shm_image_representation.h\",\n        \"skia_shm_image_representation.cc\",\n        \"skia_shm_image_representation.h\",\n      ]\n      deps += [\n        \"//third_party/angle:includes\",\n        \"//third_party/angle:libEGL_static\",\n        \"//third_party/angle:libGLESv2_static\",\n      ]\n    }\n    deps += [ \"../../fml:fml\" ]\n    configs += [ \"../../:config\" ]\n  }\n\n  if (is_harmony) {\n    sources += [\n      \"native_image_egl_image_representation.cc\",\n      \"native_image_egl_image_representation.h\",\n      \"native_image_image_backing.cc\",\n      \"native_image_image_backing.h\",\n    ]\n  }\n}\n"
  },
  {
    "path": "clay/gfx/shared_image/android/android_hardwarebuffer_utils.cc",
    "content": "// Copyright 2017 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/android/android_hardwarebuffer_utils.h\"\n\n#include <dlfcn.h>\n#include <sys/system_properties.h>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nAHardwareBufferUtils::AHardwareBufferUtils() {\n  char sdk_version_string[PROP_VALUE_MAX];\n  if (__system_property_get(\"ro.build.version.sdk\", sdk_version_string)) {\n    int api_level = atoi(sdk_version_string);\n    if (api_level < 26) {\n      FML_LOG(ERROR) << \"Sdk version < 26, AHardwareBuffer is not supported.\";\n      return;\n    }\n  }\n  const char* lib = \"libandroid.so\";\n  void* handle = dlopen(lib, RTLD_NOW | RTLD_NODELETE | RTLD_LOCAL);\n  if (!handle) {\n    FML_DLOG(ERROR) << \"Failed to open libandroid.so for AHardwareBuffer!\";\n    return;\n  }\n  AHardwareBuffer_describe_ = reinterpret_cast<AHardwareBuffer_describe_type>(\n      dlsym(handle, \"AHardwareBuffer_describe\"));\n  if (!AHardwareBuffer_describe_) {\n    FML_DLOG(ERROR) << \"Failed to find AHardwareBuffer_describe!\";\n    return;\n  }\n  AHardwareBuffer_acquire_ = reinterpret_cast<AHardwareBuffer_acquire_type>(\n      dlsym(handle, \"AHardwareBuffer_acquire\"));\n  if (!AHardwareBuffer_acquire_) {\n    FML_DLOG(ERROR) << \"Failed to find AHardwareBuffer_acquire!\";\n    return;\n  }\n  AHardwareBuffer_release_ = reinterpret_cast<AHardwareBuffer_release_type>(\n      dlsym(handle, \"AHardwareBuffer_release\"));\n  if (!AHardwareBuffer_release_) {\n    FML_DLOG(ERROR) << \"Failed to find AHardwareBuffer_release!\";\n    return;\n  }\n  AHardwareBuffer_allocate_ = reinterpret_cast<AHardwareBuffer_allocate_type>(\n      dlsym(handle, \"AHardwareBuffer_allocate\"));\n  if (!AHardwareBuffer_allocate_) {\n    FML_DLOG(ERROR) << \"Failed to find AHardwareBuffer_allocate!\";\n    return;\n  }\n  AHardwareBuffer_lock_ = reinterpret_cast<AHardwareBuffer_lock_type>(\n      dlsym(handle, \"AHardwareBuffer_lock\"));\n  if (!AHardwareBuffer_lock_) {\n    FML_DLOG(ERROR) << \"Failed to find AHardwareBuffer_lock!\";\n    return;\n  }\n  AHardwareBuffer_unlock_ = reinterpret_cast<AHardwareBuffer_unlock_type>(\n      dlsym(handle, \"AHardwareBuffer_unlock\"));\n  if (!AHardwareBuffer_unlock_) {\n    FML_DLOG(ERROR) << \"Failed to find AHardwareBuffer_unlock!\";\n    return;\n  }\n  handle = dlopen(\"libEGL.so\", RTLD_NOW | RTLD_NODELETE | RTLD_LOCAL);\n  if (!handle) {\n    FML_DLOG(ERROR) << \"Failed to open libEGL.so for AHardwareBuffer!\";\n    return;\n  }\n  eglGetNativeClientBufferANDROID_ =\n      reinterpret_cast<eglGetNativeClientBufferANDROID_type>(\n          dlsym(handle, \"eglGetNativeClientBufferANDROID\"));\n  if (!eglGetNativeClientBufferANDROID_) {\n    FML_DLOG(ERROR) << \"Failed to find eglGetNativeClientBufferANDROID!\";\n    return;\n  }\n  is_support_available_ = true;\n}\n\n// static\nAHardwareBufferUtils& AHardwareBufferUtils::GetInstance() {\n  static AHardwareBufferUtils instance;\n  return instance;\n}\n\nvoid AHardwareBufferUtils::Describe(const AHardwareBuffer* buffer,\n                                    AHardwareBuffer_Desc* outDesc) {\n  FML_DCHECK(AHardwareBuffer_describe_);\n  AHardwareBuffer_describe_(buffer, outDesc);\n}\n\nvoid AHardwareBufferUtils::Acquire(AHardwareBuffer* buffer) {\n  FML_DCHECK(AHardwareBuffer_acquire_);\n  AHardwareBuffer_acquire_(buffer);\n}\n\nvoid AHardwareBufferUtils::Release(AHardwareBuffer* buffer) {\n  FML_DCHECK(AHardwareBuffer_release_);\n  AHardwareBuffer_release_(buffer);\n}\n\nint AHardwareBufferUtils::Allocate(const AHardwareBuffer_Desc* desc,\n                                   AHardwareBuffer** outBuffer) {\n  FML_DCHECK(AHardwareBuffer_allocate_);\n  return AHardwareBuffer_allocate_(desc, outBuffer);\n}\n\nint AHardwareBufferUtils::Lock(AHardwareBuffer* buffer, uint64_t usage,\n                               int32_t fence, const ARect* rect,\n                               void** out_virtual_address) {\n  FML_DCHECK(AHardwareBuffer_lock_);\n  return AHardwareBuffer_lock_(buffer, usage, fence, rect, out_virtual_address);\n}\n\nint AHardwareBufferUtils::Unlock(AHardwareBuffer* buffer, int32_t* fence) {\n  FML_DCHECK(AHardwareBuffer_unlock_);\n  return AHardwareBuffer_unlock_(buffer, fence);\n}\n\nEGLClientBuffer AHardwareBufferUtils::GetNativeClientBuffer(\n    const AHardwareBuffer* buffer) {\n  return eglGetNativeClientBufferANDROID_(buffer);\n}\n\nbool AHardwareBufferUtils::IsSupportAvailable() {\n  return is_support_available_;\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/android/android_hardwarebuffer_utils.h",
    "content": "// Copyright 2017 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANDROID_ANDROID_HARDWAREBUFFER_UTILS_H_\n#define CLAY_GFX_SHARED_IMAGE_ANDROID_ANDROID_HARDWAREBUFFER_UTILS_H_\n\n#include <EGL/egl.h>\n#include <android/hardware_buffer.h>\n\nnamespace clay {\n\ntypedef void (*AHardwareBuffer_describe_type)(const AHardwareBuffer*,\n                                              AHardwareBuffer_Desc* desc);\ntypedef void (*AHardwareBuffer_acquire_type)(AHardwareBuffer*);\ntypedef void (*AHardwareBuffer_release_type)(AHardwareBuffer*);\ntypedef int (*AHardwareBuffer_allocate_type)(const AHardwareBuffer_Desc*,\n                                             AHardwareBuffer**);\ntypedef int (*AHardwareBuffer_lock_type)(AHardwareBuffer* buffer,\n                                         uint64_t usage, int32_t fence,\n                                         const ARect* rect,\n                                         void** outVirtualAddress);\ntypedef int (*AHardwareBuffer_unlock_type)(AHardwareBuffer* buffer,\n                                           int32_t* fence);\n\ntypedef EGLClientBuffer (*eglGetNativeClientBufferANDROID_type)(\n    const AHardwareBuffer*);\n\nclass AHardwareBufferUtils {\n public:\n  static AHardwareBufferUtils& GetInstance();\n\n  AHardwareBufferUtils(const AHardwareBufferUtils&) = delete;\n  AHardwareBufferUtils& operator=(const AHardwareBufferUtils&) = delete;\n\n  bool IsSupportAvailable();\n  int Allocate(const AHardwareBuffer_Desc* desc, AHardwareBuffer** outBuffer);\n  void Acquire(AHardwareBuffer* buffer);\n  void Release(AHardwareBuffer* buffer);\n  void Describe(const AHardwareBuffer* buffer, AHardwareBuffer_Desc* outDesc);\n  int Lock(AHardwareBuffer* buffer, uint64_t usage, int32_t fence,\n           const ARect* rect, void** out_virtual_address);\n  int Unlock(AHardwareBuffer* buffer, int32_t* fence);\n\n  EGLClientBuffer GetNativeClientBuffer(const AHardwareBuffer* buffer);\n\n private:\n  AHardwareBufferUtils();\n\n  AHardwareBuffer_describe_type AHardwareBuffer_describe_;\n  AHardwareBuffer_acquire_type AHardwareBuffer_acquire_;\n  AHardwareBuffer_release_type AHardwareBuffer_release_;\n  AHardwareBuffer_allocate_type AHardwareBuffer_allocate_;\n  AHardwareBuffer_lock_type AHardwareBuffer_lock_;\n  AHardwareBuffer_unlock_type AHardwareBuffer_unlock_;\n  eglGetNativeClientBufferANDROID_type eglGetNativeClientBufferANDROID_;\n\n  bool is_support_available_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANDROID_ANDROID_HARDWAREBUFFER_UTILS_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/android/scoped_a_native_window.cc",
    "content": "// Copyright 2022 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/android/scoped_a_native_window.h\"\n\n#include <android/native_window_jni.h>\n\n#include \"base/include/platform/android/jni_convert_helper.h\"\n#include \"base/include/platform/android/jni_utils.h\"\n#include \"clay/gfx/shared_image/android/scoped_java_surface.h\"\n\nnamespace clay {\n\n// static\nScopedANativeWindow ScopedANativeWindow::Wrap(ANativeWindow* a_native_window) {\n  return ScopedANativeWindow(a_native_window);\n}\n\nScopedANativeWindow::ScopedANativeWindow(const ScopedJavaSurface& surface) {\n  if (surface.j_surface().IsNull()) {\n    return;\n  }\n\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  // Note: This ensures that any local references used by\n  // ANativeWindow_fromSurface are released immediately. This is needed as a\n  // workaround for https://code.google.com/p/android/issues/detail?id=68174\n  fml::jni::ScopedJavaLocalFrame scoped_local_reference_frame(env);\n  a_native_window_ = ANativeWindow_fromSurface(env, surface.j_surface().Get());\n}\n\nScopedANativeWindow::ScopedANativeWindow(ANativeWindow* a_native_window)\n    : a_native_window_(a_native_window) {\n  if (a_native_window_) {\n    ANativeWindow_acquire(a_native_window_);\n  }\n}\n\nScopedANativeWindow::~ScopedANativeWindow() { DestroyIfNeeded(); }\n\nScopedANativeWindow::ScopedANativeWindow(ScopedANativeWindow&& other)\n    : a_native_window_(other.a_native_window_) {\n  other.a_native_window_ = nullptr;\n}\n\nScopedANativeWindow& ScopedANativeWindow::operator=(\n    ScopedANativeWindow&& other) {\n  if (this != &other) {\n    DestroyIfNeeded();\n    a_native_window_ = other.a_native_window_;\n    other.a_native_window_ = nullptr;\n  }\n  return *this;\n}\n\nvoid ScopedANativeWindow::DestroyIfNeeded() {\n  if (a_native_window_) {\n    ANativeWindow_release(a_native_window_);\n  }\n  a_native_window_ = nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/android/scoped_a_native_window.h",
    "content": "// Copyright 2022 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANDROID_SCOPED_A_NATIVE_WINDOW_H_\n#define CLAY_GFX_SHARED_IMAGE_ANDROID_SCOPED_A_NATIVE_WINDOW_H_\n\n#include <cstddef>\n\n#include \"base/include/fml/macros.h\"\n\nstruct ANativeWindow;\n\nnamespace clay {\n\nclass ScopedJavaSurface;\n\nclass ScopedANativeWindow {\n public:\n  static ScopedANativeWindow Wrap(ANativeWindow* a_native_window);\n  constexpr ScopedANativeWindow() = default;\n  constexpr explicit ScopedANativeWindow(std::nullptr_t) {}\n  explicit ScopedANativeWindow(const ScopedJavaSurface& surface);\n  ~ScopedANativeWindow();\n\n  ScopedANativeWindow(ScopedANativeWindow&& other);\n  ScopedANativeWindow& operator=(ScopedANativeWindow&& other);\n\n  explicit operator bool() const { return !!a_native_window_; }\n\n  ANativeWindow* a_native_window() const { return a_native_window_; }\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ScopedANativeWindow);\n\n private:\n  explicit ScopedANativeWindow(ANativeWindow* a_native_window);\n\n  void DestroyIfNeeded();\n\n  ANativeWindow* a_native_window_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANDROID_SCOPED_A_NATIVE_WINDOW_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/android/scoped_java_surface.cc",
    "content": "// Copyright 2013 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/android/scoped_java_surface.h\"\n\n#include \"base/include/platform/android/jni_convert_helper.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/android/surface_texture.h\"\n#include \"platform/android/clay/src/main/jni/gen/SurfacePlatformWrapper_jni.h\"\n#include \"platform/android/clay/src/main/jni/gen/SurfacePlatformWrapper_register_jni.h\"\n\nnamespace clay {\nnamespace jni {\nbool RegisterJNIForSurfacePlatformWrapper(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace clay\n\nnamespace clay {\n\nScopedJavaSurface::ScopedJavaSurface() = default;\nScopedJavaSurface::ScopedJavaSurface(std::nullptr_t) {}\n\nScopedJavaSurface::ScopedJavaSurface(const fml::jni::JavaRef<jobject>& surface,\n                                     bool auto_release)\n    : auto_release_(auto_release), j_surface_(surface) {}\n\nScopedJavaSurface::ScopedJavaSurface(const SurfaceTexture* surface_texture) {\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  fml::jni::ScopedLocalJavaRef<jobject> tmp(\n      Java_SurfacePlatformWrapper_createSurface(\n          env, surface_texture->j_surface_texture().Get()));\n  FML_DCHECK(!tmp.IsNull());\n  j_surface_.Reset(tmp);\n}\n\nScopedJavaSurface::ScopedJavaSurface(ScopedJavaSurface&& rvalue) {\n  MoveFrom(rvalue);\n}\n\nScopedJavaSurface& ScopedJavaSurface::operator=(ScopedJavaSurface&& rhs) {\n  MoveFrom(rhs);\n  return *this;\n}\n\nScopedJavaSurface::~ScopedJavaSurface() { ReleaseSurfaceIfNeeded(); }\n\nScopedJavaSurface ScopedJavaSurface::CopyRetainOwnership() const {\n  return ScopedJavaSurface(j_surface_, /*auto_release=*/false);\n}\n\nvoid ScopedJavaSurface::ReleaseSurfaceIfNeeded() {\n  if (auto_release_ && !j_surface_.IsNull()) {\n    JNIEnv* env = fml::jni::AttachCurrentThread();\n    Java_SurfacePlatformWrapper_releaseSurface(env, j_surface_.Get());\n  }\n}\n\nvoid ScopedJavaSurface::MoveFrom(ScopedJavaSurface& other) {\n  if (this == &other) {\n    return;\n  }\n  ReleaseSurfaceIfNeeded();\n  j_surface_.Reset(other.j_surface_);\n  other.j_surface_.Reset();\n  auto_release_ = other.auto_release_;\n}\n\nbool ScopedJavaSurface::IsEmpty() const { return j_surface_.IsNull(); }\n\nbool ScopedJavaSurface::IsValid() const {\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  return !IsEmpty() &&\n         Java_SurfacePlatformWrapper_surfaceIsValid(env, j_surface_.Get());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/android/scoped_java_surface.h",
    "content": "// Copyright 2013 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANDROID_SCOPED_JAVA_SURFACE_H_\n#define CLAY_GFX_SHARED_IMAGE_ANDROID_SCOPED_JAVA_SURFACE_H_\n\n#include <jni.h>\n\n#include <cstddef>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/platform/android/scoped_java_ref.h\"\n\nnamespace clay {\n\nclass SurfaceTexture;\n\n// A helper class for holding a scoped reference to a Java Surface instance.\n// When going out of scope, Surface.release() is called on the Java object to\n// make sure server-side references (esp. wrt graphics memory) are released.\nclass ScopedJavaSurface {\n public:\n  ScopedJavaSurface();\n  explicit ScopedJavaSurface(std::nullptr_t);\n\n  // Wraps an existing Java Surface object in a ScopedJavaSurface.\n  ScopedJavaSurface(const fml::jni::JavaRef<jobject>& surface,\n                    bool auto_release);\n\n  // Creates a Java Surface from a SurfaceTexture and wraps it in a\n  // ScopedJavaSurface.\n  explicit ScopedJavaSurface(const SurfaceTexture* surface_texture);\n\n  // Move constructor. Take the surface from another ScopedJavaSurface object,\n  // the latter no longer owns the surface afterwards.\n  ScopedJavaSurface(ScopedJavaSurface&& rvalue);\n  ScopedJavaSurface& operator=(ScopedJavaSurface&& rhs);\n\n  ~ScopedJavaSurface();\n\n  // Make a copy that does not retain ownership. Client is responsible for not\n  // using the copy after this is destroyed.\n  ScopedJavaSurface CopyRetainOwnership() const;\n\n  // Checks whether the surface is an empty one.\n  bool IsEmpty() const;\n\n  // Checks whether this object references a valid surface.\n  bool IsValid() const;\n\n  const fml::jni::JavaRef<jobject>& j_surface() const { return j_surface_; }\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ScopedJavaSurface);\n\n private:\n  // Performs destructive move from |other| to this.\n  void MoveFrom(ScopedJavaSurface& other);\n  void ReleaseSurfaceIfNeeded();\n\n  bool auto_release_ = true;\n\n  fml::jni::ScopedGlobalJavaRef<jobject> j_surface_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANDROID_SCOPED_JAVA_SURFACE_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/android/surface_texture.cc",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/android/surface_texture.h\"\n\n#include <GLES2/gl2.h>\n#include <GLES2/gl2ext.h>\n\n#include <utility>\n\n#include \"base/include/platform/android/jni_convert_helper.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/android/scoped_a_native_window.h\"\n#include \"clay/gfx/shared_image/android/scoped_java_surface.h\"\n#include \"clay/gfx/shared_image/android/surface_texture_listener.h\"\n#include \"platform/android/clay/src/main/jni/gen/SurfaceTexturePlatformWrapper_jni.h\"\n#include \"platform/android/clay/src/main/jni/gen/SurfaceTexturePlatformWrapper_register_jni.h\"\n\nnamespace clay {\nnamespace jni {\nbool RegisterJNIForSurfaceTexturePlatformWrapper(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace clay\n\nnamespace clay {\n\nfml::RefPtr<SurfaceTexture> SurfaceTexture::Create() {\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  auto j_surface_texture = Java_SurfaceTexturePlatformWrapper_create(env);\n  return fml::AdoptRef(new SurfaceTexture(j_surface_texture, true));\n}\n\nfml::RefPtr<SurfaceTexture> SurfaceTexture::Retain(jobject j_surface_texture,\n                                                   bool auto_release) {\n  fml::jni::ScopedLocalJavaRef<jobject> j_surface_texture_ref;\n  j_surface_texture_ref.Reset(fml::jni::AttachCurrentThread(),\n                              j_surface_texture);\n  return fml::AdoptRef(new SurfaceTexture(j_surface_texture_ref, auto_release));\n}\n\nSurfaceTexture::SurfaceTexture(\n    const fml::jni::JavaRef<jobject>& j_surface_texture, bool auto_release)\n    : j_surface_texture_(j_surface_texture), auto_release_(auto_release) {}\n\nSurfaceTexture::~SurfaceTexture() {\n  if (auto_release_) {\n    JNIEnv* env = fml::jni::AttachCurrentThread();\n    Java_SurfaceTexturePlatformWrapper_destroy(env, j_surface_texture_.Get());\n  }\n}\n\nvoid SurfaceTexture::SetFrameAvailableCallback(fml::closure callback) {\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  SurfaceTextureListener* listener = nullptr;\n  if (callback) {\n    listener = new SurfaceTextureListener(std::move(callback));\n  }\n  Java_SurfaceTexturePlatformWrapper_setFrameAvailableCallback(\n      env, j_surface_texture_.Get(), reinterpret_cast<intptr_t>(listener));\n}\n\nvoid SurfaceTexture::UpdateTexImage() {\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  Java_SurfaceTexturePlatformWrapper_updateTexImage(env,\n                                                    j_surface_texture_.Get());\n}\n\nvoid SurfaceTexture::GetTransformMatrix(float mtx[16]) {\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n\n  fml::jni::ScopedLocalJavaRef<jfloatArray> j_matrix(env,\n                                                     env->NewFloatArray(16));\n  Java_SurfaceTexturePlatformWrapper_getTransformMatrix(\n      env, j_surface_texture_.Get(), j_matrix.Get());\n\n  jfloat* elements = env->GetFloatArrayElements(j_matrix.Get(), nullptr);\n  for (int i = 0; i < 16; ++i) {\n    mtx[i] = static_cast<float>(elements[i]);\n  }\n  env->ReleaseFloatArrayElements(j_matrix.Get(), elements, JNI_ABORT);\n}\n\nvoid SurfaceTexture::AttachToGLContext(int texture_id) {\n  FML_DCHECK(texture_id);\n\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  Java_SurfaceTexturePlatformWrapper_attachToGLContext(\n      env, j_surface_texture_.Get(), texture_id);\n}\n\nvoid SurfaceTexture::DetachFromGLContext() {\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  Java_SurfaceTexturePlatformWrapper_detachFromGLContext(\n      env, j_surface_texture_.Get());\n}\n\nScopedANativeWindow SurfaceTexture::CreateSurface() {\n  ScopedJavaSurface surface(this);\n  return ScopedANativeWindow(surface);\n}\n\nvoid SurfaceTexture::ReleaseBackBuffers() {\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  Java_SurfaceTexturePlatformWrapper_release(env, j_surface_texture_.Get());\n}\n\nvoid SurfaceTexture::SetDefaultBufferSize(int width, int height) {\n  JNIEnv* env = fml::jni::AttachCurrentThread();\n  Java_SurfaceTexturePlatformWrapper_setDefaultBufferSize(\n      env, j_surface_texture_.Get(), width, height);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/android/surface_texture.h",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANDROID_SURFACE_TEXTURE_H_\n#define CLAY_GFX_SHARED_IMAGE_ANDROID_SURFACE_TEXTURE_H_\n\n#include <android/native_window.h>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"clay/gfx/shared_image/android/scoped_a_native_window.h\"\n\nnamespace clay {\nclass SurfaceTexture : public fml::RefCountedThreadSafe<SurfaceTexture> {\n public:\n  static fml::RefPtr<SurfaceTexture> Create();\n  static fml::RefPtr<SurfaceTexture> Retain(jobject j_surface_texture,\n                                            bool auto_release);\n\n  // Set the listener callback, it may be invoked on any thread.\n  void SetFrameAvailableCallback(fml::closure callback);\n\n  // Update the texture image to the most recent frame from the image stream.\n  void UpdateTexImage();\n\n  // Retrieve the 4x4 texture coordinate transform matrix associated with the\n  // texture image set by the most recent call to updateTexImage.\n  void GetTransformMatrix(float mtx[16]);\n\n  // Attach the SurfaceTexture to the texture currently bound to\n  // GL_TEXTURE_EXTERNAL_OES.\n  void AttachToGLContext(int texture_id);\n\n  // Detaches the SurfaceTexture from the context that owns its current GL\n  // texture. Must be called with that context current on the calling thread.\n  void DetachFromGLContext();\n\n  // Creates a native render surface for this surface texture.\n  ScopedANativeWindow CreateSurface();\n\n  // Release the SurfaceTexture back buffers.  The SurfaceTexture is no longer\n  // usable after calling this but the front buffer is still valid. Note that\n  // this is not called 'Release', like the Android API, because scoped_refptr\n  // calls that quite a bit.\n  void ReleaseBackBuffers();\n\n  // Set the default buffer size for the surface texture.\n  void SetDefaultBufferSize(int width, int height);\n\n  const fml::jni::JavaRef<jobject>& j_surface_texture() const {\n    return j_surface_texture_;\n  }\n\n protected:\n  explicit SurfaceTexture(const fml::jni::JavaRef<jobject>& j_surface_texture,\n                          bool auto_release);\n\n private:\n  friend class fml::RefCountedThreadSafe<SurfaceTexture>;\n  virtual ~SurfaceTexture();\n\n  // Java SurfaceTexture instance.\n  fml::jni::ScopedGlobalJavaRef<jobject> j_surface_texture_;\n  bool auto_release_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(SurfaceTexture);\n};\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANDROID_SURFACE_TEXTURE_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/android/surface_texture_listener.cc",
    "content": "// Copyright 2013 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/android/surface_texture_listener.h\"\n\n#include <utility>\n\n#include \"platform/android/clay/src/main/jni/gen/SurfaceTextureListener_jni.h\"\n#include \"platform/android/clay/src/main/jni/gen/SurfaceTextureListener_register_jni.h\"\n\nnamespace clay {\nnamespace jni {\nbool RegisterJNIForSurfaceTextureListener(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace clay\n\nnamespace clay {\n\nSurfaceTextureListener::SurfaceTextureListener(fml::closure callback)\n    : callback_(std::move(callback)) {}\n\nSurfaceTextureListener::~SurfaceTextureListener() {}\n\nvoid SurfaceTextureListener::Destroy(JNIEnv* env) { delete this; }\n\nvoid SurfaceTextureListener::FrameAvailable(JNIEnv* env) { callback_(); }\n\n}  // namespace clay\n\nstatic void FrameAvailable(JNIEnv* env, jobject jcaller,\n                           jlong nativeSurfaceTextureListener, jobject caller) {\n  auto listener = reinterpret_cast<clay::SurfaceTextureListener*>(\n      static_cast<intptr_t>(nativeSurfaceTextureListener));\n  listener->FrameAvailable(env);\n}\n\nstatic void Destroy(JNIEnv* env, jobject jcaller,\n                    jlong nativeSurfaceTextureListener, jobject caller) {\n  auto listener = reinterpret_cast<clay::SurfaceTextureListener*>(\n      static_cast<intptr_t>(nativeSurfaceTextureListener));\n  listener->Destroy(env);\n}\n"
  },
  {
    "path": "clay/gfx/shared_image/android/surface_texture_listener.h",
    "content": "// Copyright 2013 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANDROID_SURFACE_TEXTURE_LISTENER_H_\n#define CLAY_GFX_SHARED_IMAGE_ANDROID_SURFACE_TEXTURE_LISTENER_H_\n\n#include <jni.h>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/macros.h\"\n\nnamespace clay {\n\n// Listener class for all the callbacks from android SurfaceTexture.\nclass SurfaceTextureListener {\n public:\n  SurfaceTextureListener() = delete;\n\n  // Destroy this listener.\n  void Destroy(JNIEnv* env);\n\n  // A new frame is available to consume.\n  void FrameAvailable(JNIEnv* env);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SurfaceTextureListener);\n\n private:\n  // Native code should not hold any reference to this object, and instead pass\n  // it up to Java for being referenced by a SurfaceTexture instance.\n  // If use_any_thread is true, then the FrameAvailable callback will happen\n  // on whatever thread calls us.  Otherwise, we will call it back on the same\n  // thread that was used to construct us.\n  explicit SurfaceTextureListener(fml::closure callback);\n  ~SurfaceTextureListener();\n\n  friend class SurfaceTexture;\n\n  fml::closure callback_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANDROID_SURFACE_TEXTURE_LISTENER_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/android_egl_image_representation.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/android_egl_image_representation.h\"\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <GLES/gl.h>\n#include <GLES/glext.h>\n#include <GLES3/gl3.h>\n\n#include <memory>\n#include <mutex>\n#include <utility>\n\n#include \"clay/common/graphics/gl/scoped_framebuffer_binder.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/android/surface_texture.h\"\n#include \"clay/gfx/shared_image/android_hardwarebuffer_image_backing.h\"\n#include \"clay/gfx/shared_image/android_surface_texture_image_backing.h\"\n#include \"clay/gfx/shared_image/egl_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n#include \"clay/gfx/shared_image/vulkan_image_hardwarebuffer_representation.h\"\n\nnamespace clay {\n\nAndroidEGLFenceSync::AndroidEGLFenceSync() {\n  FML_DCHECK(display != EGL_NO_DISPLAY);\n  FML_DCHECK(eglCreateSyncKHR);\n  if (CheckEGLFenceSupported()) {\n    if (CheckAndroidNativeFenceSupported()) {\n      fence_ =\n          eglCreateSyncKHR(display, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);\n    } else {\n      fence_ = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr);\n    }\n  }\n  if (fence_ == EGL_NO_SYNC_KHR) {\n    FML_LOG(ERROR) << \"Failed to create fence sync in EGL env, error: \"\n                   << eglGetError();\n  }\n  glFlush();\n}\n\nAndroidEGLFenceSync::~AndroidEGLFenceSync() {\n  if (fence_ == EGL_NO_SYNC_KHR) {\n    return;\n  }\n  FML_DCHECK(eglDestroySyncKHR);\n  EGLBoolean success = eglDestroySyncKHR(display, fence_);\n  if (success == EGL_FALSE) {\n    FML_LOG(ERROR) << \"Failed to delete fence sync in EGL env, error: \"\n                   << eglGetError();\n  }\n}\n\nint32_t AndroidEGLFenceSync::GetCurrentFD() {\n  if (!CheckAndroidNativeFenceSupported() || fence_ == EGL_NO_SYNC_KHR) {\n    FML_LOG(ERROR) << \"EGL Android native fence sync is not supported.\";\n    return EGL_NO_NATIVE_FENCE_FD_ANDROID;\n  }\n  FML_DCHECK(eglGetSyncAttribKHR);\n  FML_DCHECK(display != EGL_NO_DISPLAY);\n  EGLint sync_fd = eglDupNativeFenceFDANDROID(display, fence_);\n  if (sync_fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {\n    FML_LOG(ERROR) << \"eglDupNativeFenceFDANDROID copy fd failed \"\n                   << eglGetError();\n  }\n  return sync_fd;\n}\n\nbool AndroidEGLFenceSync::ClientWait() {\n  if (fence_ == EGL_NO_SYNC_KHR) {\n    return false;\n  }\n  FML_DCHECK(eglClientWaitSyncKHR);\n  EGLint result = eglClientWaitSyncKHR(display, fence_, 0, EGL_FOREVER_KHR);\n  if (result != EGL_CONDITION_SATISFIED_KHR) {\n    FML_LOG(ERROR) << \"Failed to eglClientWaitSync in EGL env, error: \"\n                   << eglGetError();\n  }\n  return result == EGL_CONDITION_SATISFIED_KHR;\n}\n\nvoid AndroidEGLFenceSync::ServerWait() {\n  if (fence_ == EGL_NO_SYNC_KHR) {\n    return;\n  }\n  FML_DCHECK(eglWaitSyncKHR);\n  EGLint result = eglWaitSyncKHR(display, fence_, 0);\n  if (result == EGL_FALSE) {\n    FML_LOG(ERROR) << \"Failed to eglWaitSyncKHR in EGL env, error: \"\n                   << eglGetError();\n  }\n}\n\n// static\nbool AndroidEGLFenceSync::CheckAndroidNativeFenceSupported() {\n  static std::once_flag flag;\n  static bool supported;\n  auto func = [] {\n    const char* extensions = eglQueryString(display, EGL_EXTENSIONS);\n    if (!strstr(extensions, \"EGL_ANDROID_native_fence_sync\")) {\n      supported = false;\n    } else {\n      supported = true;\n    }\n  };\n  std::call_once(flag, func);\n  return supported;\n}\n\n// static\nbool AndroidEGLFenceSync::CheckEGLFenceSupported() {\n  static std::once_flag flag;\n  static bool supported;\n  auto init_func = [] {\n    display = eglGetCurrentDisplay();\n    const char* extensions = eglQueryString(display, EGL_EXTENSIONS);\n    if (!strstr(extensions, \"EGL_KHR_fence_sync\")) {\n      supported = false;\n    } else {\n      eglCreateSyncKHR = reinterpret_cast<PFNEGLCREATESYNCKHRPROC>(\n          eglGetProcAddress(\"eglCreateSyncKHR\"));\n      eglDestroySyncKHR = reinterpret_cast<PFNEGLDESTROYSYNCKHRPROC>(\n          eglGetProcAddress(\"eglDestroySyncKHR\"));\n      eglClientWaitSyncKHR = reinterpret_cast<PFNEGLCLIENTWAITSYNCKHRPROC>(\n          eglGetProcAddress(\"eglClientWaitSyncKHR\"));\n      eglWaitSyncKHR = reinterpret_cast<PFNEGLWAITSYNCKHRPROC>(\n          eglGetProcAddress(\"eglWaitSyncKHR\"));\n      eglDupNativeFenceFDANDROID =\n          reinterpret_cast<PFNEGLDUPNATIVEFENCEFDANDROIDPROC>(\n              eglGetProcAddress(\"eglDupNativeFenceFDANDROID\"));\n      supported = eglCreateSyncKHR && eglDestroySyncKHR &&\n                  eglClientWaitSyncKHR && eglWaitSyncKHR &&\n                  eglDupNativeFenceFDANDROID;\n    }\n    if (!supported) {\n      FML_LOG(ERROR) << \"EGLFenceSync is not supported.\";\n    }\n  };\n  std::call_once(flag, init_func);\n  return supported;\n}\n\nNativeBufferEGLImageRepresentation::NativeBufferEGLImageRepresentation(\n    fml::RefPtr<AHardwareBufferImageBacking> backing)\n    : GLImageRepresentation(backing) {}\n\nNativeBufferEGLImageRepresentation::NativeBufferEGLImageRepresentation(\n    fml::RefPtr<EGLImageBacking> backing)\n    : GLImageRepresentation(backing) {}\n\nImageRepresentationType NativeBufferEGLImageRepresentation::GetType() const {\n  return ImageRepresentationType::kEGL;\n}\n\nstd::optional<GLImageRepresentation::TextureInfo>\nNativeBufferEGLImageRepresentation::GetTexImage() {\n  if (texture_id_ == 0) {\n    SharedImageBacking* backing = GetBacking();\n    if (!backing) {\n      return std::nullopt;\n    }\n\n    GLuint texture;\n    glGenTextures(1, &texture);\n    clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D, texture);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\n    switch (backing->GetType()) {\n      case SharedImageBacking::BackingType::kAHardwareBuffer: {\n        egl_display_ = eglGetCurrentDisplay();\n        egl_image_ =\n            static_cast<AHardwareBufferImageBacking*>(backing)->CreateEGLImage(\n                egl_display_);\n        FML_DCHECK(egl_image_ != EGL_NO_IMAGE_KHR);\n        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image_);\n        FML_DCHECK(static_cast<EGLint>(EGL_SUCCESS) == eglGetError());\n        break;\n      }\n      case SharedImageBacking::BackingType::kEGLImage: {\n        static_cast<EGLImageBacking*>(backing)->BindToTexture(GL_TEXTURE_2D);\n        break;\n      }\n      default: {\n        // NOT SUPPORTED.\n        FML_UNREACHABLE();\n      }\n    }\n    texture_id_ = texture;\n  }\n  return TextureInfo{.target = GL_TEXTURE_2D,\n                     .name = texture_id_,\n                     .format = GL_RGBA,\n                     .size = GetSize()};\n}\n\nbool NativeBufferEGLImageRepresentation::ReleaseTexImage() {\n  if (texture_id_ == 0) {\n    return false;\n  }\n  glDeleteTextures(1, &texture_id_);\n  texture_id_ = 0;\n  return true;\n}\n\nbool NativeBufferEGLImageRepresentation::UnbindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    return false;\n  }\n  glDeleteFramebuffers(1, &fbo_id_);\n  glDeleteRenderbuffers(1, &depth_stencil_id_);\n  fbo_id_ = 0;\n  depth_stencil_id_ = 0;\n  return true;\n}\n\nstd::optional<GLImageRepresentation::FramebufferInfo>\nNativeBufferEGLImageRepresentation::BindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    auto texture_info = GetTexImage();\n    GLuint backend_texture = texture_info->name;\n    GLuint fbo_id;\n    glGenFramebuffers(1, &fbo_id);\n    clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D,\n                                                    backend_texture);\n    clay::ScopedFramebufferBinder scoped_framebuffer_binder(GL_FRAMEBUFFER,\n                                                            fbo_id);\n\n    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,\n                           backend_texture, 0);\n\n    GLint old_rbo;\n    glGetIntegerv(GL_RENDERBUFFER_BINDING, &old_rbo);\n\n    glGenRenderbuffers(1, &depth_stencil_id_);\n    glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil_id_);\n    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, GetSize().x,\n                          GetSize().y);\n    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,\n                              GL_RENDERBUFFER, depth_stencil_id_);\n    glBindRenderbuffer(GL_RENDERBUFFER, old_rbo);\n\n#ifndef NDEBUG\n    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n#else\n    GLenum status = GL_FRAMEBUFFER_COMPLETE;\n#endif\n    if (status != GL_FRAMEBUFFER_COMPLETE) {\n      FML_LOG(ERROR) << \"Failed to create FBO, error: \" << status;\n      glDeleteFramebuffers(1, &fbo_id);\n      return std::nullopt;\n    }\n    fbo_id_ = fbo_id;\n  }\n  return FramebufferInfo{.target = GL_FRAMEBUFFER, .name = fbo_id_};\n}\n\nNativeBufferEGLImageRepresentation::~NativeBufferEGLImageRepresentation() {\n  UnbindFrameBuffer();\n  ReleaseTexImage();\n  if (egl_image_ && egl_display_) {\n    FML_DCHECK(eglGetCurrentContext() != EGL_NO_CONTEXT);\n    eglDestroyImageKHR(egl_display_, egl_image_);\n  }\n}\n\nEGLDisplay AndroidEGLFenceSync::display = EGL_NO_DISPLAY;\nPFNEGLCREATESYNCKHRPROC AndroidEGLFenceSync::eglCreateSyncKHR = nullptr;\nPFNEGLDESTROYSYNCKHRPROC AndroidEGLFenceSync::eglDestroySyncKHR = nullptr;\nPFNEGLCLIENTWAITSYNCKHRPROC AndroidEGLFenceSync::eglClientWaitSyncKHR = nullptr;\nPFNEGLWAITSYNCKHRPROC AndroidEGLFenceSync::eglWaitSyncKHR = nullptr;\nPFNEGLDUPNATIVEFENCEFDANDROIDPROC\nAndroidEGLFenceSync::eglDupNativeFenceFDANDROID = nullptr;\n\nclass DummyFenceSync final : public FenceSync {\n  bool ClientWait() override { return true; }\n\n  Type GetType() const override { return Type::kClientWaitOnly; }\n};\n\nvoid NativeBufferEGLImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  if (fence_sync->GetType() == FenceSync::Type::kEGL) {\n    AndroidEGLFenceSync* egl_fence_sync =\n        static_cast<AndroidEGLFenceSync*>(fence_sync.get());\n    egl_fence_sync->ServerWait();\n    return;\n  } else if (fence_sync->GetType() == FenceSync::Type::kVulkan) {\n    // Try server wait.\n    VkFenceSync* vulkan_fence_sync =\n        static_cast<VkFenceSync*>(fence_sync.get());\n    vulkan_fence_sync->ServerWait();\n    return;\n  }\n  fence_sync->ClientWait();\n}\n\nstd::unique_ptr<FenceSync> NativeBufferEGLImageRepresentation::ProduceFence() {\n  if (AndroidEGLFenceSync::CheckEGLFenceSupported()) {\n    return std::make_unique<AndroidEGLFenceSync>();\n  }\n  glFinish();\n  return std::make_unique<DummyFenceSync>();\n}\n\nSurfaceTextureEGLImageRepresentation::SurfaceTextureEGLImageRepresentation(\n    fml::RefPtr<SurfaceTextureImageBacking> backing)\n    : GLImageRepresentation(backing), backing_(backing) {}\n\nSurfaceTextureEGLImageRepresentation::~SurfaceTextureEGLImageRepresentation() {\n  UnbindFrameBuffer();\n  ReleaseTexImage();\n}\n\nImageRepresentationType SurfaceTextureEGLImageRepresentation::GetType() const {\n  return ImageRepresentationType::kEGL;\n}\n\nvoid SurfaceTextureEGLImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence) {\n  if (fence) {\n    fence->ClientWait();\n  }\n}\n\nstd::unique_ptr<FenceSync>\nSurfaceTextureEGLImageRepresentation::ProduceFence() {\n  // The fence is created in SurfaceTexture's internal implementations\n  return nullptr;\n}\n\nstd::optional<GLImageRepresentation::TextureInfo>\nSurfaceTextureEGLImageRepresentation::GetTexImage() {\n  return TextureInfo{.target = GL_TEXTURE_EXTERNAL_OES,\n                     .name = backing_->EnsureAttachedToGLContext(),\n                     .format = GL_RGBA8_OES,\n                     .size = GetSize()};\n}\n\nbool SurfaceTextureEGLImageRepresentation::ReleaseTexImage() {\n  backing_->DetachGLContext();\n\n  return true;\n}\n\nstd::optional<GLImageRepresentation::FramebufferInfo>\nSurfaceTextureEGLImageRepresentation::BindFrameBuffer() {\n  // TODO(youfeng) support write to Surface\n  return {};\n}\n\nbool SurfaceTextureEGLImageRepresentation::UnbindFrameBuffer() {\n  // TODO(youfeng) support write to Surface\n  return false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/android_egl_image_representation.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANDROID_EGL_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_ANDROID_EGL_IMAGE_REPRESENTATION_H_\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass EGLImageBacking;\nclass AHardwareBufferImageBacking;\nclass SurfaceTextureImageBacking;\n\nclass AndroidEGLFenceSync final : public FenceSync {\n public:\n  AndroidEGLFenceSync();\n\n  ~AndroidEGLFenceSync() override;\n\n  int32_t GetSyncFD() { return GetCurrentFD(); }\n\n  bool ClientWait() override;\n\n  Type GetType() const override { return Type::kEGL; }\n\n  void ServerWait();\n\n  static bool CheckEGLFenceSupported();\n  static bool CheckAndroidNativeFenceSupported();\n\n private:\n  friend class NativeBufferEGLImageRepresentation;\n  friend class VulkanFenceHelper;\n  int32_t GetCurrentFD();\n\n  EGLSyncKHR fence_ = EGL_NO_SYNC_KHR;\n\n  static EGLDisplay display;\n  static PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR;\n  static PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR;\n  static PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR;\n  static PFNEGLWAITSYNCKHRPROC eglWaitSyncKHR;\n  static PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID;\n};\n\nclass NativeBufferEGLImageRepresentation final : public GLImageRepresentation {\n public:\n  explicit NativeBufferEGLImageRepresentation(\n      fml::RefPtr<AHardwareBufferImageBacking> backing);\n  explicit NativeBufferEGLImageRepresentation(\n      fml::RefPtr<EGLImageBacking> backing);\n\n  ~NativeBufferEGLImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n  void ConsumeFence(std::unique_ptr<FenceSync>) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n private:\n  std::optional<TextureInfo> GetTexImage() override;\n  bool ReleaseTexImage() override;\n  std::optional<FramebufferInfo> BindFrameBuffer() override;\n  bool UnbindFrameBuffer() override;\n\n private:\n  uint32_t texture_id_ = 0;\n  uint32_t fbo_id_ = 0;\n  uint32_t depth_stencil_id_ = 0;\n\n  // For AHardwareBufferImageBacking.\n  EGLImageKHR egl_image_ = nullptr;\n  EGLDisplay egl_display_ = nullptr;\n};\n\nclass SurfaceTextureEGLImageRepresentation final\n    : public GLImageRepresentation {\n public:\n  explicit SurfaceTextureEGLImageRepresentation(\n      fml::RefPtr<SurfaceTextureImageBacking> backing);\n\n  ~SurfaceTextureEGLImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n  void ConsumeFence(std::unique_ptr<FenceSync>) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n private:\n  std::optional<TextureInfo> GetTexImage() override;\n  bool ReleaseTexImage() override;\n  std::optional<FramebufferInfo> BindFrameBuffer() override;\n  bool UnbindFrameBuffer() override;\n\n  fml::RefPtr<SurfaceTextureImageBacking> backing_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANDROID_EGL_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/android_hardwarebuffer_image_backing.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/android_hardwarebuffer_image_backing.h\"\n\n#ifndef NDEBUG\n#include <GLES2/gl2.h>\n#endif\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/shared_image/android/android_hardwarebuffer_utils.h\"\n#include \"clay/gfx/shared_image/android_egl_image_representation.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n#include \"clay/gfx/shared_image/vulkan_image_hardwarebuffer_representation.h\"\n#include \"clay/public/clay.h\"\nnamespace clay {\n\nAHardwareBufferImageBacking::AHardwareBufferImageBacking(\n    PixelFormat pixel_format, skity::Vec2 size,\n    std::optional<GraphicsMemoryHandle> gfx_handle)\n    : SharedImageBacking(pixel_format, size) {\n  FML_DCHECK(AHardwareBufferUtils::GetInstance().IsSupportAvailable());\n  AHardwareBuffer_Desc desc = {\n      static_cast<uint32_t>(size.x),\n      static_cast<uint32_t>(size.y),\n      1,\n      AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,\n      AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |\n          AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT,\n      0,\n      0,\n      0};\n  int error = AHardwareBufferUtils::GetInstance().Allocate(&desc, &buffer_);\n  if (error != 0) {\n    FML_LOG(ERROR) << \"AHardwareBuffer allocate error: \" << error;\n    return;\n  }\n#ifndef NDEBUG\n  GLint max_texture_size = 0;\n  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);\n  if (size.x > max_texture_size || size.y > max_texture_size) {\n    FML_LOG(ERROR)\n        << \"AHardwareBuffer size is too large, supported max size is: \"\n        << max_texture_size;\n  }\n#endif\n}\n\nEGLImageKHR AHardwareBufferImageBacking::CreateEGLImage(\n    EGLDisplay egl_display) {\n  EGLClientBuffer client_buffer =\n      AHardwareBufferUtils::GetInstance().GetNativeClientBuffer(buffer_);\n  if (!client_buffer) {\n    return {};\n  }\n  EGLImageKHR egl_image;\n  const EGLint egl_attrib_list[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,\n                                    EGL_NONE};\n  egl_image =\n      eglCreateImageKHR(egl_display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,\n                        client_buffer, egl_attrib_list);\n  return egl_image;\n}\n\nAHardwareBufferImageBacking::BackingType AHardwareBufferImageBacking::GetType()\n    const {\n  return SharedImageBacking::BackingType::kAHardwareBuffer;\n}\n\nfml::RefPtr<SharedImageRepresentation>\nAHardwareBufferImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  auto type = config->type;\n  switch (type) {\n    case kClaySharedImageRepresentationTypeGL: {\n      return fml::MakeRefCounted<NativeBufferEGLImageRepresentation>(\n          fml::Ref(this));\n    }\n    case kClaySharedImageRepresentationTypeVulkan: {\n      return fml::MakeRefCounted<VulkanImageHardwareBufferRepresentation>(\n          fml::Ref(this), config->vk_config.device,\n          config->vk_config.physical_device, config->vk_config.queue);\n    }\n    default: {\n      // NOT SUPPORTED.\n      FML_DLOG(ERROR)\n          << \"Unable to call AHardwareBufferImageBacking::CreateRepresentation \"\n             \"with type: \"\n          << static_cast<uint32_t>(type);\n      return nullptr;\n    }\n  }\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<SkiaImageRepresentation>\nAHardwareBufferImageBacking::CreateSkiaRepresentation(\n    GrDirectContext* gr_context) {\n  switch (gr_context->backend()) {\n    case GrBackendApi::kOpenGL: {\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context, fml::MakeRefCounted<NativeBufferEGLImageRepresentation>(\n                          fml::Ref(this)));\n    }\n    default: {\n      FML_DLOG(ERROR)\n          << \"Unable to call \"\n             \"AHardwareBufferImageBacking::CreateSkiaRepresentation with \"\n             \"backend: \"\n          << static_cast<uint32_t>(gr_context->backend());\n      return nullptr;\n    }\n  }\n}\n#else\nfml::RefPtr<SkityImageRepresentation>\nAHardwareBufferImageBacking::CreateSkityRepresentation(\n    skity::GPUContext* skity_context) {\n  switch (skity_context->GetBackendType()) {\n    case skity::GPUBackendType::kOpenGL: {\n      return fml::MakeRefCounted<SkityGLImageRepresentation>(\n          skity_context,\n          fml::MakeRefCounted<NativeBufferEGLImageRepresentation>(\n              fml::Ref(this)));\n    }\n    default: {\n      FML_LOG(ERROR)\n          << \"Unable to call \"\n             \"AHardwareBufferImageBacking::CreateSkityRepresentation with \"\n             \"backend: \"\n          << static_cast<uint32_t>(skity_context->GetBackendType());\n      return nullptr;\n    }\n  }\n}\n#endif  // ENABLE_SKITY\n\nAHardwareBufferImageBacking::~AHardwareBufferImageBacking() {\n  if (buffer_) {\n    AHardwareBufferUtils::GetInstance().Release(buffer_);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/android_hardwarebuffer_image_backing.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANDROID_HARDWAREBUFFER_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_ANDROID_HARDWAREBUFFER_IMAGE_BACKING_H_\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <android/hardware_buffer.h>\n\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/public/clay.h\"\n\nnamespace clay {\n\nclass SharedImageRepresentation;\n\nclass AHardwareBufferImageBacking : public SharedImageBacking {\n public:\n  AHardwareBufferImageBacking(\n      PixelFormat pixel_format, skity::Vec2 size,\n      std::optional<GraphicsMemoryHandle> gfx_handle = {});\n  ~AHardwareBufferImageBacking() override;\n\n  AHardwareBuffer* GetAHardwareBuffer() const { return buffer_; }\n  BackingType GetType() const override;\n  GraphicsMemoryHandle GetGFXHandle() const override { return buffer_; }\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n#ifndef ENABLE_SKITY\n  fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n#else\n  fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      skity::GPUContext* skity_context) override;\n#endif  // ENABLE_SKITY\n\n  static bool IsAHardwareBufferAvailable();\n\n  EGLImageKHR CreateEGLImage(EGLDisplay egl_display);\n\n private:\n  AHardwareBuffer* buffer_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANDROID_HARDWAREBUFFER_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/android_surface_texture_image_backing.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/android_surface_texture_image_backing.h\"\n\n#include <GLES2/gl2.h>\n#include <GLES2/gl2ext.h>\n\n#include \"base/include/platform/android/jni_convert_helper.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#include \"clay/gfx/shared_image/android/surface_texture.h\"\n#include \"clay/gfx/shared_image/android_egl_image_representation.h\"\n\nnamespace clay {\n\nSurfaceTextureImageBacking::SurfaceTextureImageBacking(\n    PixelFormat pixel_format, skity::Vec2 size,\n    std::optional<GraphicsMemoryHandle> gfx_handle)\n    : SharedImageBackingUnmanaged(pixel_format, size),\n      surface_texture_(\n          gfx_handle.has_value()\n              ? SurfaceTexture::Retain(\n                    reinterpret_cast<jobject>(gfx_handle.value()), false)\n              : SurfaceTexture::Create()) {}\n\nSurfaceTextureImageBacking::~SurfaceTextureImageBacking() {\n  if (texture_ != 0) {\n    FML_LOG(ERROR) << \"Texture not detached, maybe leaked!\";\n  }\n}\n\nconst skity::Vec2 SurfaceTextureImageBacking::GetSize() const {\n  if (size_.x <= 0 || size_.y <= 0) {\n    // If the size is not set, we assume the default size is 256x256.\n    return skity::Vec2(256, 256);\n  }\n  return size_;\n}\n\nSharedImageBacking::BackingType SurfaceTextureImageBacking::GetType() const {\n  return SharedImageBacking::BackingType::kSurfaceTexture;\n}\n\nGraphicsMemoryHandle SurfaceTextureImageBacking::GetGFXHandle() const {\n  return surface_texture_->j_surface_texture().Get();\n}\n\nfml::RefPtr<SharedImageRepresentation>\nSurfaceTextureImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  auto type = config->type;\n  switch (type) {\n    case kClaySharedImageRepresentationTypeGL: {\n      return fml::MakeRefCounted<SurfaceTextureEGLImageRepresentation>(\n          fml::Ref(this));\n    }\n    default: {\n      // NOT SUPPORTED.\n      FML_LOG(ERROR)\n          << \"Unable to call AHardwareBufferImageBacking::CreateRepresentation \"\n             \"with type: \"\n          << static_cast<uint32_t>(type);\n      return nullptr;\n    }\n  }\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<SkiaImageRepresentation>\nSurfaceTextureImageBacking::CreateSkiaRepresentation(\n    GrDirectContext* gr_context) {\n  switch (gr_context->backend()) {\n    case GrBackendApi::kOpenGL: {\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context, fml::MakeRefCounted<SurfaceTextureEGLImageRepresentation>(\n                          fml::Ref(this)));\n    }\n    default: {\n      FML_LOG(ERROR)\n          << \"Unable to call \"\n             \"SurfaceTextureImageBacking::CreateSkiaRepresentation with \"\n             \"backend: \"\n          << static_cast<uint32_t>(gr_context->backend());\n      return nullptr;\n    }\n  }\n}\n#else\nfml::RefPtr<SkityImageRepresentation>\nSurfaceTextureImageBacking::CreateSkityRepresentation(\n    skity::GPUContext* skity_context) {\n  switch (skity_context->GetBackendType()) {\n    case skity::GPUBackendType::kOpenGL: {\n      return fml::MakeRefCounted<SkityGLImageRepresentation>(\n          skity_context,\n          fml::MakeRefCounted<SurfaceTextureEGLImageRepresentation>(\n              fml::Ref(this)));\n    }\n    default: {\n      FML_LOG(ERROR)\n          << \"Unable to call \"\n             \"SurfaceTextureImageBacking::CreateSkityRepresentation with \"\n             \"backend: \"\n          << static_cast<uint32_t>(skity_context->GetBackendType());\n      return nullptr;\n    }\n  }\n}\n#endif  // ENABLE_SKITY\n\n// This is a workaround to address the limitation that the surface texture\n// provides an API to `setDefaultBufferSize` without offering a method to\n// `getBufferSize`.\n// Utilizing `glGetTexLevelParameterfv` is also not reliable\n// on certain devices, as discussed here: https://stackoverflow.com/q/53536401.\n// So the SurfaceTextureImageBacking owner should set the size during layout (as\n// with `VideoPlayerAndroid` or `PlatformViewPlugin`).\nbool SurfaceTextureImageBacking::SetSize(skity::Vec2 size) {\n  size_ = size;\n  return true;\n}\n\nconst skity::Matrix SurfaceTextureImageBacking::GetTransformation() const {\n  float mat[16];\n  surface_texture_->GetTransformMatrix(mat);\n  return skity::Matrix{mat[0],  mat[1],  mat[2],  mat[3],   //\n                       mat[4],  mat[5],  mat[6],  mat[7],   //\n                       mat[8],  mat[9],  mat[10], mat[11],  //\n                       mat[12], mat[13], mat[14], mat[15]};\n}\n\nvoid SurfaceTextureImageBacking::SetTransformation(const skity::Matrix& mat) {\n  FML_UNIMPLEMENTED();\n}\n\n/// `SharedImageBackingUnmanaged`\nvoid SurfaceTextureImageBacking::SetFrameAvailableCallback(\n    const fml::closure& callback) {\n  surface_texture_->SetFrameAvailableCallback(callback);\n}\n\nbool SurfaceTextureImageBacking::UpdateFront() {\n  if (EnsureAttachedToGLContext() == 0) {\n    FML_LOG(ERROR) << \"Failed to attach to GL context\";\n    return false;\n  }\n  // https://developer.android.com/reference/android/graphics/SurfaceTexture#updateTexImage()\n  // In Android docs, updateTexImage will implicitly bind\n  // GL_TEXTURE_EXTERNAL_OES. To make Skia happy, we must reset the state.\n  clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_EXTERNAL_OES,\n                                                  texture_);\n  surface_texture_->UpdateTexImage();\n  return true;\n}\n\nvoid SurfaceTextureImageBacking::ReleaseFront() { FML_UNIMPLEMENTED(); }\n\nuint32_t SurfaceTextureImageBacking::AcquireBack() {\n  // No need to call any functions, since eglMakeCurrent automatically requires\n  // a new buffer.\n  return 0;  // TODO(youfeng) support buffer age for surface texture.\n}\n\nbool SurfaceTextureImageBacking::SwapBack() {\n  // No need to call any functions, since eglSwap automatically swap back\n  return true;\n}\n\nuint32_t SurfaceTextureImageBacking::Capacity() const {\n  // Currently only support double buffering\n  return 2;\n}\n\nconst fml::jni::JavaRef<jobject>&\nSurfaceTextureImageBacking::GetSurfaceTexture() const {\n  return surface_texture_->j_surface_texture();\n}\n\nGLuint SurfaceTextureImageBacking::EnsureAttachedToGLContext() {\n  if (texture_ == 0) {\n    glGenTextures(1, &texture_);\n    clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_EXTERNAL_OES,\n                                                    texture_);\n    surface_texture_->AttachToGLContext(texture_);\n  }\n  return texture_;\n}\n\nvoid SurfaceTextureImageBacking::DetachGLContext() {\n  if (texture_ != 0) {\n    surface_texture_->DetachFromGLContext();\n    glDeleteTextures(1, &texture_);\n    texture_ = 0;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/android_surface_texture_image_backing.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANDROID_SURFACE_TEXTURE_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_ANDROID_SURFACE_TEXTURE_IMAGE_BACKING_H_\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <GLES/gl.h>\n#include <android/hardware_buffer.h>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/public/clay.h\"\n\nnamespace clay {\n\nclass SurfaceTexture;\nclass SharedImageRepresentation;\n\nclass SurfaceTextureImageBacking final : public SharedImageBackingUnmanaged {\n public:\n  SurfaceTextureImageBacking(\n      PixelFormat pixel_format, skity::Vec2 size,\n      std::optional<GraphicsMemoryHandle> gfx_handle = {});\n  ~SurfaceTextureImageBacking() override;\n\n  const skity::Vec2 GetSize() const override;\n  BackingType GetType() const override;\n  GraphicsMemoryHandle GetGFXHandle() const override;\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n#ifndef ENABLE_SKITY\n  fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n#else\n  fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      skity::GPUContext* skity_context) override;\n#endif  // ENABLE_SKITY\n  const skity::Matrix GetTransformation() const override;\n  void SetTransformation(const skity::Matrix& mat) override;\n\n  /// `SharedImageBackingUnmanaged`\n  void SetFrameAvailableCallback(const fml::closure& callback) override;\n  bool UpdateFront() override;\n  void ReleaseFront() override;\n  uint32_t AcquireBack() override;\n  bool SwapBack() override;\n  uint32_t Capacity() const override;\n\n  bool SetSize(skity::Vec2 size) override;\n  const fml::jni::JavaRef<jobject>& GetSurfaceTexture() const;\n  GLuint EnsureAttachedToGLContext();\n  void DetachGLContext();\n\n private:\n  fml::RefPtr<SurfaceTexture> surface_texture_;\n  GLuint texture_ = 0;\n};\n\n}  // namespace clay\n   //\n#endif  // CLAY_GFX_SHARED_IMAGE_ANDROID_SURFACE_TEXTURE_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/angle_d3d_image_representation.cc",
    "content": "\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/angle_d3d_image_representation.h\"\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <EGL/eglext_angle.h>\n#include <GLES2/gl2.h>\n#include <GLES2/gl2ext.h>\n#include <Windows.h>\n#include <d3d11_1.h>\n\n#include <vector>\n\n#include \"clay/common/graphics/gl/scoped_framebuffer_binder.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/d3d9_texture_image_backing.h\"\n#include \"clay/gfx/shared_image/d3d_texture_image_backing.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/utils/functions_angle.h\"\n\nnamespace clay {\n\nAngleD3DImageRepresentation::AngleD3DImageRepresentation(\n    fml::RefPtr<D3DTextureImageBacking> backing,\n    PFNEGLGETPROCADDRESSPROC egl_get_proc_address_proc)\n    : GLImageRepresentation(backing) {\n  EGLAttrib device_result = SetupEGL(egl_get_proc_address_proc, true);\n\n  ID3D11Device* d3d11_device = reinterpret_cast<ID3D11Device*>(device_result);\n\n  if (d3d11_device == nullptr) {\n    FML_LOG(ERROR) << \"Failed to get d3d11 device.\";\n    return;\n  }\n\n  if (!backing->OpenForDevice(d3d11_device, &d3d11_texture_, &keyed_mutex_)) {\n    FML_LOG(ERROR) << \"Failed to open for device.\";\n  }\n}\n\nAngleD3DImageRepresentation::AngleD3DImageRepresentation(\n    fml::RefPtr<D3D9TextureImageBacking> backing,\n    PFNEGLGETPROCADDRESSPROC egl_get_proc_address_proc)\n    : GLImageRepresentation(backing) {\n  EGLAttrib device_result = SetupEGL(egl_get_proc_address_proc, false);\n  Microsoft::WRL::ComPtr<IDirect3DDevice9> d3d9_device(\n      reinterpret_cast<IDirect3DDevice9*>(device_result));\n\n  if (d3d9_device == nullptr) {\n    FML_LOG(ERROR) << \"Failed to get d3d9 device.\";\n    return;\n  }\n\n  if (!backing->OpenForDevice(d3d9_device.Get(), &d3d9_texture_)) {\n    FML_LOG(ERROR) << \"Failed to open for device.\";\n  }\n}\n\nEGLAttrib AngleD3DImageRepresentation::SetupEGL(\n    PFNEGLGETPROCADDRESSPROC egl_get_proc_address_proc, bool is_d3d11) {\n  functions_angle_ =\n      std::make_unique<FunctionsAngle>(egl_get_proc_address_proc);\n  gl_api_windows_ = std::make_unique<clay::GlApiWindows>();\n  gl_api_windows_->gl_get_integerv_proc = functions_angle_->glGetIntegervProc;\n  gl_api_windows_->gl_bind_texture_proc = functions_angle_->glBindTextureProc;\n  gl_api_windows_->gl_bind_framebuffer_proc =\n      functions_angle_->glBindFramebufferProc;\n\n  egl_display_ = functions_angle_->eglGetCurrentDisplay();\n  if (egl_display_ == EGL_NO_DISPLAY) {\n    FML_LOG(ERROR) << \"Failed to get current display.\";\n    return 0;\n  }\n  EGLDeviceEXT device = EGL_NO_DEVICE_EXT;\n  EGLAttrib display_result = 0;\n\n  PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXTProc =\n      reinterpret_cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>(\n          functions_angle_->eglGetProcAddressProc(\"eglQueryDisplayAttribEXT\"));\n  if (!eglQueryDisplayAttribEXTProc) {\n    FML_LOG(ERROR) << \"Failed to get proc address of eglQueryDisplayAttribEXT.\";\n    return 0;\n  }\n\n  eglQueryDisplayAttribEXTProc(egl_display_, EGL_DEVICE_EXT, &display_result);\n  device = reinterpret_cast<EGLDeviceEXT>(display_result);\n\n  if (device == EGL_NO_DEVICE_EXT) {\n    FML_LOG(ERROR) << \"Failed to get egl device.\";\n    return 0;\n  }\n\n  PFNEGLQUERYDEVICEATTRIBEXTPROC\n  eglQueryDeviceAttribEXTProc =\n      reinterpret_cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>(\n          functions_angle_->eglGetProcAddressProc(\"eglQueryDeviceAttribEXT\"));\n  if (!eglQueryDeviceAttribEXTProc) {\n    FML_LOG(ERROR) << \"Failed to get proc address of eglQueryDeviceAttribEXT.\";\n    return 0;\n  }\n\n  EGLAttrib device_result = 0;\n\n  if (!eglQueryDeviceAttribEXTProc(\n          device, is_d3d11 ? EGL_D3D11_DEVICE_ANGLE : EGL_D3D9_DEVICE_ANGLE,\n          &device_result)) {\n    FML_LOG(ERROR) << \"Failed to call eglQueryDeviceAttribEXTProc.\";\n  }\n\n  return device_result;\n}\n\nAngleD3DImageRepresentation::~AngleD3DImageRepresentation() {\n  if (fbo_id_ != 0) {\n    UnbindFrameBuffer();\n  }\n  if (texture_id_ != 0) {\n    ReleaseTexImage();\n  }\n  if (scoped_keyed_mutex_) {\n    FML_LOG(ERROR) << \"Keyed mutex is still locked.\";\n    scoped_keyed_mutex_.reset();\n  }\n}\n\nbool AngleD3DImageRepresentation::BeginRead(ClaySharedImageReadResult* out) {\n  if (!GLImageRepresentation::BeginRead(out)) {\n    return false;\n  }\n  return LockKeyedMutex();\n}\n\nbool AngleD3DImageRepresentation::EndRead() {\n  if (!GLImageRepresentation::EndRead()) {\n    return false;\n  }\n  return UnlockKeyedMutex();\n}\n\nbool AngleD3DImageRepresentation::BeginWrite(ClaySharedImageWriteResult* out) {\n  if (!GLImageRepresentation::BeginWrite(out)) {\n    return false;\n  }\n  return LockKeyedMutex();\n}\n\nbool AngleD3DImageRepresentation::EndWrite() {\n  if (!GLImageRepresentation::EndWrite()) {\n    return false;\n  }\n  return UnlockKeyedMutex();\n}\n\nbool AngleD3DImageRepresentation::LockKeyedMutex() {\n  if (!keyed_mutex_) {\n    return true;\n  }\n  if (scoped_keyed_mutex_) {\n    FML_LOG(ERROR) << \"Keyed mutex is already locked.\";\n  }\n  scoped_keyed_mutex_.emplace(keyed_mutex_.Get());\n  return true;\n}\n\nbool AngleD3DImageRepresentation::UnlockKeyedMutex() {\n  if (!keyed_mutex_) {\n    return true;\n  }\n  if (!scoped_keyed_mutex_) {\n    FML_LOG(ERROR) << \"Keyed mutex is not locked.\";\n  }\n  scoped_keyed_mutex_.reset();\n  return true;\n}\n\nImageRepresentationType AngleD3DImageRepresentation::GetType() const {\n  return ImageRepresentationType::kANGLE;\n}\n\nstd::optional<uint32_t> AngleD3DImageRepresentation::GetTexFromD3D9Texture() {\n  FML_DCHECK(d3d9_texture_);\n\n  EGLConfig config = EGL_NO_CONFIG_KHR;\n  EGLint value;\n\n  PFNEGLQUERYCONTEXTPROC\n  eglQueryContextProc = reinterpret_cast<PFNEGLQUERYCONTEXTPROC>(\n      functions_angle_->eglGetProcAddressProc(\"eglQueryContext\"));\n  if (!eglQueryContextProc) {\n    FML_LOG(ERROR) << \"Failed to get proc address of eglQueryContext.\";\n    return {};\n  }\n\n  if (!eglQueryContextProc(egl_display_,\n                           functions_angle_->eglGetCurrentContext(),\n                           EGL_CONFIG_ID, &value)) {\n    FML_LOG(ERROR) << \"Failed to query EGL_CONFIG_ID.\";\n    return {};\n  }\n\n  // Retrieve the EGLConfig that has the obtained EGL_CONFIG_ID.\n  EGLint num_configs;\n\n  if (!functions_angle_->eglGetConfigs(egl_display_, nullptr, 0,\n                                       &num_configs)) {\n    FML_LOG(ERROR) << \"Failed to eglGetConfigs.\";\n    return {};\n  }\n\n  std::vector<EGLConfig> configs(num_configs);\n\n  if (!functions_angle_->eglGetConfigs(egl_display_, configs.data(),\n                                       num_configs, &num_configs)) {\n    FML_LOG(ERROR) << \"Failed to eglGetConfigs.\";\n    return {};\n  }\n\n  for (int i = 0; i < num_configs; ++i) {\n    EGLint configID;\n    functions_angle_->eglGetConfigAttrib(egl_display_, configs[i],\n                                         EGL_CONFIG_ID, &configID);\n    if (configID == value) {\n      config = configs[i];\n      break;\n    }\n  }\n\n  if (config == EGL_NO_CONFIG_KHR) {\n    FML_LOG(ERROR) << \"Failed to find current EGLConfig.\";\n    return {};\n  }\n\n  GLuint texture;\n  functions_angle_->glGenTextures(1, &texture);\n  clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D, texture,\n                                                  gl_api_windows_.get());\n  functions_angle_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,\n                                    GL_LINEAR);\n  functions_angle_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,\n                                    GL_LINEAR);\n  functions_angle_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,\n                                    GL_CLAMP_TO_EDGE);\n  functions_angle_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,\n                                    GL_CLAMP_TO_EDGE);\n  GLenum error = functions_angle_->glGetError();\n  if (error != GL_NO_ERROR) {\n    functions_angle_->glDeleteTextures(1, &texture);\n    FML_LOG(ERROR) << \"GL generate and initialize texture failed: \" << error;\n    return {};\n  }\n\n  EGLint attributes[] = {EGL_WIDTH,\n                         static_cast<EGLint>(GetBacking()->GetSize().x),\n                         EGL_HEIGHT,\n                         static_cast<EGLint>(GetBacking()->GetSize().y),\n                         EGL_TEXTURE_TARGET,\n                         EGL_TEXTURE_2D,\n                         EGL_TEXTURE_FORMAT,\n                         EGL_TEXTURE_RGBA,  // always EGL_TEXTURE_RGBA\n                         EGL_NONE};\n\n  PFNEGLCREATEPBUFFERFROMCLIENTBUFFERPROC eglCreatePbufferFromClientBufferProc =\n      reinterpret_cast<PFNEGLCREATEPBUFFERFROMCLIENTBUFFERPROC>(\n          functions_angle_->eglGetProcAddressProc(\n              \"eglCreatePbufferFromClientBuffer\"));\n  if (eglCreatePbufferFromClientBufferProc == nullptr) {\n    FML_LOG(ERROR)\n        << \"Failed to get 'eglCreatePbufferFromClientBufferProc' proc.\";\n    return {};\n  }\n\n  egl_surface_ = eglCreatePbufferFromClientBufferProc(\n      egl_display_, EGL_D3D_TEXTURE_ANGLE, d3d9_texture_.Get(), config,\n      attributes);\n\n  if (egl_surface_ == EGL_NO_SURFACE ||\n      functions_angle_->eglBindTexImage(egl_display_, egl_surface_,\n                                        EGL_BACK_BUFFER) == EGL_FALSE) {\n    functions_angle_->glDeleteTextures(1, &texture);\n    FML_LOG(ERROR) << \"Binding D3D surface failed.\";\n    return {};\n  }\n\n  return texture;\n}\n\nstd::optional<uint32_t> AngleD3DImageRepresentation::GetTexFromD3D11Texture() {\n  FML_DCHECK(d3d11_texture_);\n\n  SharedImageBacking::PixelFormat pixel_format = GetBacking()->GetPixelFormat();\n  EGLint internal_format = GL_NONE;\n  switch (pixel_format) {\n    case SharedImageBacking::PixelFormat::kNative8888:\n      internal_format = GL_BGRA_EXT;\n      break;\n    case SharedImageBacking::PixelFormat::kRGBA8888:\n      internal_format = GL_RGBA;\n      break;\n    default:\n      FML_LOG(ERROR) << \"Invalid pixel format.\";\n      return {};\n  }\n\n  const EGLint attribs[] = {EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, internal_format,\n                            EGL_NONE};\n  egl_image_ = functions_angle_->eglCreateImageKHR(\n      egl_display_, EGL_NO_CONTEXT, EGL_D3D11_TEXTURE_ANGLE,\n      static_cast<EGLClientBuffer>(d3d11_texture_.Get()), attribs);\n  if (egl_image_ == EGL_NO_IMAGE_KHR) {\n    FML_LOG(ERROR) << \"Failed to create egl image.\";\n    return {};\n  }\n  EGLint egl_error = functions_angle_->eglGetError();\n  if (egl_error != EGL_SUCCESS) {\n    FML_LOG(ERROR) << \"eglCreateImageKHR failed: \" << egl_error;\n    return {};\n  }\n  PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOESProc =\n      reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(\n          functions_angle_->eglGetProcAddressProc(\n              \"glEGLImageTargetTexture2DOES\"));\n  if (glEGLImageTargetTexture2DOESProc == nullptr) {\n    FML_LOG(ERROR) << \"Failed to get 'glEGLImageTargetTexture2DOES' proc.\";\n    return {};\n  }\n\n  GLuint texture;\n  functions_angle_->glGenTextures(1, &texture);\n  clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D, texture,\n                                                  gl_api_windows_.get());\n  functions_angle_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,\n                                    GL_LINEAR);\n  functions_angle_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,\n                                    GL_LINEAR);\n  functions_angle_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,\n                                    GL_CLAMP_TO_EDGE);\n  functions_angle_->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,\n                                    GL_CLAMP_TO_EDGE);\n  GLenum error = functions_angle_->glGetError();\n  if (error != GL_NO_ERROR) {\n    functions_angle_->glDeleteTextures(1, &texture);\n    FML_LOG(ERROR) << \"GL generate and initialize texture failed: \" << error;\n    return {};\n  }\n\n  glEGLImageTargetTexture2DOESProc(GL_TEXTURE_2D, egl_image_);\n  egl_error = functions_angle_->eglGetError();\n  if (egl_error != EGL_SUCCESS) {\n    functions_angle_->glDeleteTextures(1, &texture);\n    FML_LOG(ERROR) << \"glEGLImageTargetTexture2DOES failed: \" << egl_error;\n    return {};\n  }\n\n  return texture;\n}\n\nstd::optional<GLImageRepresentation::TextureInfo>\nAngleD3DImageRepresentation::GetTexImage() {\n  if (!d3d11_texture_ && !d3d9_texture_) {\n    FML_LOG(ERROR) << \"Has invalid texture image backing.\";\n    return {};\n  }\n  if (texture_id_ == 0) {\n    std::optional<uint32_t> texture =\n        d3d11_texture_ ? GetTexFromD3D11Texture() : GetTexFromD3D9Texture();\n    if (!texture) {\n      return {};\n    }\n    texture_id_ = texture.value();\n  }\n\n  return TextureInfo{.target = GL_TEXTURE_2D,\n                     .name = texture_id_,\n                     .format = GL_RGBA8,\n                     .size = GetSize()};\n}\n\nbool AngleD3DImageRepresentation::ReleaseTexImage() {\n  if (texture_id_ == 0) {\n    return false;\n  }\n  functions_angle_->glDeleteTextures(1, &texture_id_);\n  if (egl_image_ != EGL_NO_IMAGE_KHR) {\n    functions_angle_->eglDestroyImageKHR(egl_display_, egl_image_);\n    egl_image_ = EGL_NO_IMAGE_KHR;\n  }\n\n  if (egl_surface_ != EGL_NO_SURFACE) {\n    functions_angle_->eglReleaseTexImage(egl_display_, egl_surface_,\n                                         EGL_BACK_BUFFER);\n    functions_angle_->eglDestroySurface(egl_display_, egl_surface_);\n    egl_surface_ = EGL_NO_SURFACE;\n  }\n\n  texture_id_ = 0;\n  return true;\n}\n\nstd::optional<GLImageRepresentation::FramebufferInfo>\nAngleD3DImageRepresentation::BindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    auto texture_info = GetTexImage();\n    if (!texture_info) {\n      return {};\n    }\n\n    GLuint fbo_id;\n    functions_angle_->glGenFramebuffers(1, &fbo_id);\n    clay::ScopedFramebufferBinder scoped_framebuffer_binder(\n        GL_FRAMEBUFFER, fbo_id, gl_api_windows_.get());\n    clay::ScopedTextureBinder scoped_texture_binder(\n        GL_TEXTURE_2D, texture_info->name, gl_api_windows_.get());\n\n    functions_angle_->glFramebufferTexture2D(\n        GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_info->name,\n        0);\n    GLenum framebuffer_status =\n        functions_angle_->glCheckFramebufferStatus(GL_FRAMEBUFFER);\n\n    if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {\n      FML_LOG(ERROR) << \"Failed to create framebuffer.\";\n      functions_angle_->glDeleteFramebuffers(1, &fbo_id);\n      return {};\n    }\n\n    fbo_id_ = fbo_id;\n  }\n\n  return FramebufferInfo{.target = GL_TEXTURE_2D, .name = fbo_id_};\n}\n\nbool AngleD3DImageRepresentation::UnbindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    return false;\n  }\n  functions_angle_->glDeleteFramebuffers(1, &fbo_id_);\n  fbo_id_ = 0;\n\n  return true;\n}\n\nclass AngleFenceSync final : public FenceSync {\n public:\n  explicit AngleFenceSync(FunctionsAngle* functions_angle)\n      : functions_angle_(functions_angle) {\n    FML_DCHECK(functions_angle_->angle_fence_supported_);\n    display_ = functions_angle_->eglGetCurrentDisplay();\n    FML_DCHECK(display_ != EGL_NO_DISPLAY);\n    fence_ = functions_angle_->eglCreateSyncKHR(display_, EGL_SYNC_FENCE_KHR,\n                                                nullptr);\n    if (fence_ == EGL_NO_SYNC_KHR) {\n      FML_LOG(ERROR) << \"Failed to create fence sync in ANGLE env, error: \"\n                     << functions_angle_->eglGetError();\n    }\n    functions_angle_->glFlush();\n  }\n\n  ~AngleFenceSync() override {\n    FML_DCHECK(fence_ != EGL_NO_SYNC_KHR);\n    EGLBoolean success = functions_angle_->eglDestroySyncKHR(display_, fence_);\n    if (success == EGL_FALSE) {\n      FML_LOG(ERROR) << \"Failed to delete fence sync in ANGLE env, error: \"\n                     << functions_angle_->eglGetError();\n    }\n  }\n\n  bool ClientWait() override {\n    FML_DCHECK(fence_ != EGL_NO_SYNC_KHR);\n    EGLint result = functions_angle_->eglClientWaitSyncKHR(display_, fence_, 0,\n                                                           EGL_FOREVER_KHR);\n    if (result != EGL_CONDITION_SATISFIED_KHR) {\n      FML_LOG(ERROR) << \"Failed to eglClientWaitSync in ANGLE env, error: \"\n                     << functions_angle_->eglGetError();\n    }\n    return result == EGL_CONDITION_SATISFIED_KHR;\n  }\n\n  Type GetType() const override { return Type::kANGLE; }\n\n  void ServerWait() {\n    FML_DCHECK(fence_ != EGL_NO_SYNC_KHR);\n    EGLint result = functions_angle_->eglWaitSyncKHR(display_, fence_, 0);\n    if (result == EGL_FALSE) {\n      FML_LOG(ERROR) << \"Failed to eglWaitSyncKHR in ANGLE env, error: \"\n                     << functions_angle_->eglGetError();\n    }\n  }\n\n private:\n  EGLSyncKHR fence_;\n  EGLDisplay display_ = EGL_NO_DISPLAY;\n  FunctionsAngle* functions_angle_ = nullptr;\n};\n\nclass DummyFenceSync final : public FenceSync {\n  bool ClientWait() override { return true; }\n\n  Type GetType() const override { return Type::kClientWaitOnly; }\n};\n\nvoid AngleD3DImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  if (fence_sync->GetType() == FenceSync::Type::kANGLE) {\n    AngleFenceSync* angle_fence_sync =\n        static_cast<AngleFenceSync*>(fence_sync.get());\n    angle_fence_sync->ServerWait();\n    return;\n  }\n  fence_sync->ClientWait();\n}\n\nstd::unique_ptr<FenceSync> AngleD3DImageRepresentation::ProduceFence() {\n  // Sadly it seems that Angle sync doesn't support usage without context\n  // if (functions_angle_->angle_fence_supported_) {\n  //   return std::make_unique<AngleFenceSync>();\n  // }\n  // We use keyed mutex when BeginRW/EndRW instead of fence\n  functions_angle_->glFlush();\n  return std::make_unique<DummyFenceSync>();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/angle_d3d_image_representation.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANGLE_D3D_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_ANGLE_D3D_IMAGE_REPRESENTATION_H_\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <d3d11.h>\n#include <d3d9.h>\n#include <wrl/client.h>\n\n#include <memory>\n#include <optional>\n\n#include \"clay/common/graphics/gl/gl_api_windows.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n#include \"clay/gfx/shared_image/utils/dxgi_utils.h\"\n#include \"clay/gfx/shared_image/utils/functions_angle.h\"\n\nnamespace clay {\n\nclass D3DTextureImageBacking;\nclass D3D9TextureImageBacking;\n\nclass AngleD3DImageRepresentation : public GLImageRepresentation {\n public:\n  AngleD3DImageRepresentation(\n      fml::RefPtr<D3DTextureImageBacking> backing,\n      PFNEGLGETPROCADDRESSPROC egl_get_proc_address_proc);\n\n  AngleD3DImageRepresentation(\n      fml::RefPtr<D3D9TextureImageBacking> backing,\n      PFNEGLGETPROCADDRESSPROC egl_get_proc_address_proc);\n\n  ~AngleD3DImageRepresentation() override;\n\n  bool BeginRead(ClaySharedImageReadResult* out) override;\n  bool EndRead() override;\n  bool BeginWrite(ClaySharedImageWriteResult* out) override;\n  bool EndWrite() override;\n\n  ImageRepresentationType GetType() const override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n private:\n  EGLAttrib SetupEGL(PFNEGLGETPROCADDRESSPROC egl_get_proc_address_proc,\n                     bool is_d3d11);\n\n  std::optional<TextureInfo> GetTexImage() override;\n  bool ReleaseTexImage() override;\n  std::optional<FramebufferInfo> BindFrameBuffer() override;\n  bool UnbindFrameBuffer() override;\n\n  bool LockKeyedMutex();\n  bool UnlockKeyedMutex();\n\n  std::optional<uint32_t> GetTexFromD3D11Texture();\n  std::optional<uint32_t> GetTexFromD3D9Texture();\n\n  std::unique_ptr<FunctionsAngle> functions_angle_;\n  std::unique_ptr<clay::GlApiWindows> gl_api_windows_;\n  EGLDisplay egl_display_ = EGL_NO_DISPLAY;\n\n  // only used in D3D11Texture\n  EGLImage egl_image_ = EGL_NO_IMAGE_KHR;\n  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture_;\n  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex_;\n  std::optional<ScopedDXGIKeyedMutex> scoped_keyed_mutex_;\n\n  // only used in D3D9Texture\n  EGLSurface egl_surface_ = EGL_NO_SURFACE;\n  Microsoft::WRL::ComPtr<IDirect3DTexture9> d3d9_texture_;\n\n  uint32_t texture_id_ = 0;\n  uint32_t fbo_id_ = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANGLE_D3D_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/angle_software_shm_image_backing.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/angle_software_shm_image_backing.h\"\n\n#include <fcntl.h>\n#include <sys/mman.h>\n\n#include <tuple>\n\n#include \"angle_gl.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/angle_sw_shm_image_representation.h\"\n#ifndef ENABLE_SKITY\n#include \"clay/gfx/shared_image/skia_shm_image_representation.h\"\n#endif\n\nnamespace clay {\n\nnamespace {\n\nstd::tuple<void*, int> CreateShmBuffer(\n    SharedImageBacking::PixelFormat pixel_format, skity::Vec2 size) {\n  static size_t count = 0;\n  std::string shm_name =\n      \"/angle_shared_image_backing\" + std::to_string(getpid()) + \"_\" +\n      std::to_string(pthread_self()) + \"_\" + std::to_string(count++);\n  size_t shm_size = size.x * size.y * 4;\n\n  void* buffer = nullptr;\n  int shm_fd = shm_open(shm_name.c_str(), O_CREAT | O_RDWR, 0666);\n  FML_DCHECK(shm_fd != -1);\n  ftruncate(shm_fd, shm_size);\n  buffer = mmap(NULL, shm_size, PROT_WRITE, MAP_SHARED, shm_fd, 0);\n  FML_DCHECK(buffer);\n  shm_unlink(shm_name.c_str());\n\n  return {buffer, shm_fd};\n}\n\n}  // namespace\n\nAngleSoftwareShmImageBacking::AngleSoftwareShmImageBacking(\n    PixelFormat pixel_format, skity::Vec2 size)\n    : SharedImageBacking(pixel_format, size) {\n  std::tuple<void*, int> result = CreateShmBuffer(pixel_format, size);\n  shm_buffer_ = std::get<0>(result);\n  shm_fd_ = std::get<1>(result);\n}\n\nSharedImageBacking::BackingType AngleSoftwareShmImageBacking::GetType() const {\n  return BackingType::kAngleShmImage;\n}\n\nfml::RefPtr<SharedImageRepresentation>\nAngleSoftwareShmImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  ClaySharedImageRepresentationType type = config->type;\n  switch (type) {\n    case ClaySharedImageRepresentationType::\n        kClaySharedImageRepresentationTypeShm: {\n      return fml::MakeRefCounted<AngleSwShmImageRepresentation>(fml::Ref(this));\n    }\n    default: {\n      // NOT SUPPORTED.\n      FML_LOG(ERROR) << \"Unable to call CreateRepresentation with type: \"\n                     << static_cast<uint32_t>(type);\n      return nullptr;\n    }\n  }\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<SkiaImageRepresentation>\nAngleSoftwareShmImageBacking::CreateSkiaRepresentation(\n    GrDirectContext* gr_context) {\n  return fml::MakeRefCounted<SkiaShmImageRepresentation>(\n      fml::MakeRefCounted<AngleSwShmImageRepresentation>(fml::Ref(this)));\n}\n#else\nfml::RefPtr<SkityImageRepresentation>\nAngleSoftwareShmImageBacking::CreateSkityRepresentation(\n    skity::GPUContext* skity_context) override {\n  return nullptr;\n}\n#endif  // ENABLE_SKITY\n\nvoid AngleSoftwareShmImageBacking::CopyPixelsToShm() {\n  glFinish();\n  glReadPixels(0, 0, size_.x, size_.y, GL_RGBA, GL_UNSIGNED_BYTE, shm_buffer_);\n}\n\nAngleSoftwareShmImageBacking::~AngleSoftwareShmImageBacking() {\n  if (munmap(shm_buffer_, size_.x * size_.y * 4) == -1) {\n    FML_LOG(ERROR) << \"munmap failed\";\n  }\n\n  close(shm_fd_);\n  shm_buffer_ = nullptr;\n  shm_fd_ = 0;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/angle_software_shm_image_backing.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANGLE_SOFTWARE_SHM_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_ANGLE_SOFTWARE_SHM_IMAGE_BACKING_H_\n\n#include <string>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nclass SharedImageRepresentation;\n\nclass AngleSoftwareShmImageBacking : public SharedImageBacking {\n public:\n  AngleSoftwareShmImageBacking(PixelFormat pixel_format, skity::Vec2 size);\n  ~AngleSoftwareShmImageBacking() override;\n  BackingType GetType() const override;\n\n  GraphicsMemoryHandle GetGFXHandle() const override { return shm_buffer_; }\n\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n\n#ifndef ENABLE_SKITY\n  fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n#else\n  fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      skity::GPUContext* skity_context) override;\n#endif  // ENABLE_SKITY\n\n  void CopyPixelsToShm();\n\n  int GetShmFd() const { return shm_fd_; }\n\n private:\n  GraphicsMemoryHandle shm_buffer_ = nullptr;\n  std::string shm_name_;\n  int shm_fd_ = -1;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANGLE_SOFTWARE_SHM_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/angle_sw_shm_image_representation.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/angle_sw_shm_image_representation.h\"\n\n#include <cstring>\n#include <utility>\n\n#include \"clay/gfx/shared_image/angle_software_shm_image_backing.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n\nnamespace clay {\n\nAngleSwShmImageRepresentation::AngleSwShmImageRepresentation(\n    fml::RefPtr<SharedImageBacking> backing)\n    : SharedImageRepresentation(std::move(backing)) {}\n\nAngleSwShmImageRepresentation::~AngleSwShmImageRepresentation() {}\n\nImageRepresentationType AngleSwShmImageRepresentation::GetType() const {\n  return ImageRepresentationType::kLinuxShm;\n}\n\nvoid AngleSwShmImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  fence_sync->ClientWait();\n}\n\nstd::unique_ptr<FenceSync> AngleSwShmImageRepresentation::ProduceFence() {\n  return nullptr;\n}\n\nbool AngleSwShmImageRepresentation::BeginRead(ClaySharedImageReadResult* out) {\n  memset(out, 0, sizeof(ClaySharedImageReadResult));\n  out->struct_size = sizeof(ClaySharedImageReadResult);\n  out->type = kClaySharedImageRepresentationTypeShm;\n  out->shm_image.struct_size = sizeof(ClaySharedMemoryImage);\n  out->shm_image.shm_fd =\n      static_cast<AngleSoftwareShmImageBacking*>(GetBacking())->GetShmFd();\n  out->shm_image.width = GetBacking()->GetSize().x;\n  out->shm_image.height = GetBacking()->GetSize().y;\n  out->shm_image.user_data = this;\n  this->AddRef();\n  out->shm_image.destruction_callback = [](void* user_data) {\n    static_cast<AngleSwShmImageRepresentation*>(user_data)->Release();\n  };\n  return true;\n}\n\nbool AngleSwShmImageRepresentation::EndRead() { return true; }\n\nbool AngleSwShmImageRepresentation::BeginWrite(\n    ClaySharedImageWriteResult* out) {\n  memset(out, 0, sizeof(ClaySharedImageWriteResult));\n  out->struct_size = sizeof(ClaySharedImageWriteResult);\n  out->type = kClaySharedImageRepresentationTypeShm;\n  out->shm_image.struct_size = sizeof(ClaySharedMemoryImage);\n  out->shm_image.shm_fd =\n      static_cast<AngleSoftwareShmImageBacking*>(GetBacking())->GetShmFd();\n  out->shm_image.width = GetBacking()->GetSize().x;\n  out->shm_image.height = GetBacking()->GetSize().y;\n  out->shm_image.user_data = this;\n  this->AddRef();\n  out->shm_image.destruction_callback = [](void* user_data) {\n    static_cast<AngleSwShmImageRepresentation*>(user_data)->Release();\n  };\n  return true;\n}\n\nbool AngleSwShmImageRepresentation::EndWrite() {\n  static_cast<AngleSoftwareShmImageBacking*>(GetBacking())->CopyPixelsToShm();\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/angle_sw_shm_image_representation.h",
    "content": "\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_ANGLE_SW_SHM_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_ANGLE_SW_SHM_IMAGE_REPRESENTATION_H_\n\n#include <memory>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass FenceSync;\n\nclass AngleSwShmImageRepresentation : public SharedImageRepresentation {\n public:\n  explicit AngleSwShmImageRepresentation(\n      fml::RefPtr<SharedImageBacking> backing);\n  ~AngleSwShmImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n  void ConsumeFence(std::unique_ptr<FenceSync>) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  bool BeginRead(ClaySharedImageReadResult* out) override;\n  bool EndRead() override;\n  bool BeginWrite(ClaySharedImageWriteResult* out) override;\n  bool EndWrite() override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_ANGLE_SW_SHM_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/cgl_image_representation.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_CGL_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_CGL_IMAGE_REPRESENTATION_H_\n\n#import <CoreVideo/CoreVideo.h>\n#import <OpenGL/OpenGL.h>\n\n#include <memory>\n#include <optional>\n\n#include \"clay/fml/platform/darwin/cf_utils.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass SharedImageBacking;\n\nclass CGLStorageManager : public RepresentationStorageManager {\n public:\n  explicit CGLStorageManager(CGLContextObj gl_context);\n  ~CGLStorageManager() override;\n\n  CVOpenGLTextureRef CreateTextureFromStorage(CVPixelBufferRef pixel_buffer);\n  void FlushStorageRecycle();\n\n private:\n  fml::CFRef<CVOpenGLTextureCacheRef> cv_gl_texture_cache_;\n};\n\nclass CGLImageRepresentation final : public GLImageRepresentation {\n public:\n  explicit CGLImageRepresentation(fml::RefPtr<SharedImageBacking> backing);\n  ~CGLImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  fml::RefPtr<RepresentationStorageManager> GetTextureManager() const override;\n  void SetTextureManager(fml::RefPtr<RepresentationStorageManager>) override;\n\n private:\n  std::optional<TextureInfo> GetTexImage() override;\n  bool ReleaseTexImage() override;\n  std::optional<FramebufferInfo> BindFrameBuffer() override;\n  bool UnbindFrameBuffer() override;\n\n  bool GetTexImageFromIOSurface();\n  bool GetTexImageFromCVPixelBuffer();\n\n  CGLContextObj gl_context_ = nullptr;\n  uint32_t texture_id_ = 0;\n  uint32_t fbo_id_ = 0;\n  uint32_t internal_format_ = 0;\n  fml::RefPtr<CGLStorageManager> storage_manager_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_CGL_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/cgl_image_representation.mm",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/cgl_image_representation.h\"\n\n#import <CoreVideo/CoreVideo.h>\n#import <OpenGL/gl3.h>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/common/graphics/gl/scoped_framebuffer_binder.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/cv_pixelbuffer_image_backing.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/iosurface_image_backing.h\"\n\nnamespace clay {\n\nCGLStorageManager::CGLStorageManager(CGLContextObj gl_context) {\n  CVOpenGLTextureCacheRef out_texture_cache;\n  if (CVOpenGLTextureCacheCreate(kCFAllocatorDefault, nil, gl_context,\n                                 CGLGetPixelFormat(gl_context), nil,\n                                 &out_texture_cache) != kCVReturnSuccess) {\n    FML_LOG(ERROR) << \"Failed to create CVOpenGLTextureCache.\";\n    return;\n  }\n  cv_gl_texture_cache_ = out_texture_cache;\n}\n\nCGLStorageManager::~CGLStorageManager() { FlushStorageRecycle(); }\n\nCVOpenGLTextureRef CGLStorageManager::CreateTextureFromStorage(CVPixelBufferRef pixel_buffer) {\n  if (!cv_gl_texture_cache_) {\n    FML_LOG(ERROR) << \"No available CVOpenGLTextureCacheRef to create texture\";\n    return nil;\n  }\n  CVOpenGLTextureRef out_texture = nil;\n  if (CVOpenGLTextureCacheCreateTextureFromImage(kCFAllocatorDefault, cv_gl_texture_cache_,\n                                                 pixel_buffer, nil,\n                                                 &out_texture) != kCVReturnSuccess) {\n    FML_LOG(ERROR) << \"Failed to create CVOpenGLTexture from CVPixelBuffer.\";\n    return nil;\n  }\n  return out_texture;\n}\n\nvoid CGLStorageManager::FlushStorageRecycle() {\n  if (cv_gl_texture_cache_) {\n    CVOpenGLTextureCacheFlush(cv_gl_texture_cache_, 0);\n  }\n}\n\nCGLImageRepresentation::CGLImageRepresentation(fml::RefPtr<SharedImageBacking> backing)\n    : GLImageRepresentation(backing), gl_context_(CGLGetCurrentContext()) {\n  if (gl_context_ == nil) {\n    FML_LOG(ERROR) << \"Failed to get current context\";\n  }\n}\n\nCGLImageRepresentation::~CGLImageRepresentation() {\n  if (fbo_id_ != 0) {\n    UnbindFrameBuffer();\n  }\n  if (texture_id_ != 0) {\n    ReleaseTexImage();\n  }\n}\n\nImageRepresentationType CGLImageRepresentation::GetType() const {\n  return ImageRepresentationType::kCGL;\n}\n\nstd::optional<GLImageRepresentation::TextureInfo> CGLImageRepresentation::GetTexImage() {\n  if (texture_id_ == 0) {\n    switch (GLImageRepresentation::GetBacking()->GetType()) {\n      case SharedImageBacking::BackingType::kIOSurface:\n        if (!GetTexImageFromIOSurface()) {\n          return {};\n        }\n        break;\n      case SharedImageBacking::BackingType::kCVPixelBuffer:\n        if (!GetTexImageFromCVPixelBuffer()) {\n          return {};\n        }\n        break;\n      default:\n        FML_LOG(ERROR) << \"Invalid shared image backing type for CGLImageRepr.\";\n        return {};\n    }\n  }\n\n  return TextureInfo{.target = GL_TEXTURE_RECTANGLE,\n                     .name = texture_id_,\n                     .format = internal_format_,\n                     .size = GetSize()};\n}\n\nbool CGLImageRepresentation::GetTexImageFromIOSurface() {\n  GLuint texture;\n  glGenTextures(1, &texture);\n  clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_RECTANGLE, texture);\n  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n  internal_format_ = GL_RGBA8;\n  GLenum type = GL_UNSIGNED_INT_8_8_8_8_REV;\n  GLenum pixel_format;\n  switch (GetBacking()->GetPixelFormat()) {\n    case SharedImageBacking::PixelFormat::kNative8888:\n      pixel_format = GL_BGRA;\n      break;\n    default:\n      FML_LOG(ERROR) << \"Unsupported pixel format.\";\n      return false;\n  }\n  CGLError error = CGLTexImageIOSurface2D(\n      gl_context_, GL_TEXTURE_RECTANGLE, internal_format_, GetSize().x, GetSize().y, pixel_format,\n      type, static_cast<IOSurfaceRef>(GetBacking()->GetGFXHandle()), 0);\n  if (error != kCGLNoError) {\n    FML_LOG(ERROR) << \"Failed to create CGL texture\";\n    glDeleteTextures(1, &texture);\n    return false;\n  }\n\n  texture_id_ = texture;\n  return true;\n}\n\nbool CGLImageRepresentation::GetTexImageFromCVPixelBuffer() {\n  if (!storage_manager_) {\n    FML_LOG(ERROR) << \"No available CGLStorageManager to create texture\";\n    return false;\n  }\n  storage_manager_->FlushStorageRecycle();\n  CVOpenGLTextureRef out_texture = storage_manager_->CreateTextureFromStorage(\n      static_cast<CVPixelBufferRef>(GetBacking()->GetGFXHandle()));\n  if (!out_texture) {\n    return false;\n  }\n  GLuint texture;\n  texture = CVOpenGLTextureGetName(out_texture);\n  CFRelease(out_texture);\n  clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_RECTANGLE, texture);\n  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n  glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n  internal_format_ = GL_RGBA8;\n  texture_id_ = texture;\n  return true;\n}\n\nbool CGLImageRepresentation::ReleaseTexImage() {\n  if (texture_id_ == 0) {\n    return false;\n  }\n  texture_id_ = 0;\n\n  // The texture is actually owned by the cache so there is no need to explicitly call\n  // 'glDeleteTexture'. Just flush cache.\n  if (storage_manager_) {\n    storage_manager_->FlushStorageRecycle();\n  }\n  return true;\n}\n\nstd::optional<GLImageRepresentation::FramebufferInfo> CGLImageRepresentation::BindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    auto texture_info = GetTexImage();\n    if (!texture_info) {\n      return {};\n    }\n\n    GLuint fbo_id;\n    glGenFramebuffers(1, &fbo_id);\n    clay::ScopedFramebufferBinder scoped_framebuffer_binder(GL_FRAMEBUFFER, fbo_id);\n    clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_RECTANGLE, texture_info->name);\n\n    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE,\n                           texture_info->name, 0);\n#ifndef NDEBUG\n    GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n#else\n    GLenum framebuffer_status = GL_FRAMEBUFFER_COMPLETE;\n#endif\n\n    if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {\n      FML_LOG(ERROR) << \"Failed to create frame buffer\";\n      glDeleteFramebuffers(1, &fbo_id);\n      return {};\n    }\n\n    fbo_id_ = fbo_id;\n  }\n\n  return FramebufferInfo{.target = GL_TEXTURE_RECTANGLE, .name = fbo_id_};\n}\n\nbool CGLImageRepresentation::UnbindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    return false;\n  }\n  glDeleteFramebuffers(1, &fbo_id_);\n  fbo_id_ = 0;\n\n  return true;\n}\n\n// Note that CGL fence sync requires producer and consumer\n// in the same shared group\nclass CGLFenceSync final : public FenceSync {\n public:\n  CGLFenceSync() {\n    fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);\n    GLenum error = glGetError();\n    if (!fence_ || error != GL_NO_ERROR) {\n      FML_LOG(ERROR) << \"Failed to create fence sync in CGL env, glGetError: \" << error;\n    }\n    FML_DCHECK(glIsSync(fence_));\n    glFlush();\n  }\n\n  ~CGLFenceSync() override {\n    FML_DCHECK(glIsSync(fence_));\n    glDeleteSync(fence_);\n    GLenum error = glGetError();\n    if (error != GL_NO_ERROR) {\n      FML_LOG(ERROR) << \"Failed to delete fence sync in CGL env, glGetError: \" << error;\n    }\n  }\n\n  bool ClientWait() override {\n    FML_DCHECK(glIsSync(fence_));\n    GLenum result = glClientWaitSync(fence_, 0, GL_TIMEOUT_IGNORED);\n    GLenum error = glGetError();\n    if (error != GL_NO_ERROR) {\n      FML_LOG(ERROR) << \"Failed to glClientWaitSync in CGL env, glGetError: \" << error;\n    }\n    return result == GL_CONDITION_SATISFIED || result == GL_ALREADY_SIGNALED;\n  }\n\n  Type GetType() const override { return Type::kCGL; }\n\n  void ServerWait() {\n    FML_DCHECK(glIsSync(fence_));\n    glWaitSync(fence_, 0, GL_TIMEOUT_IGNORED);\n    GLenum gl_error = glGetError();\n    if (gl_error != GL_NO_ERROR) {\n      FML_LOG(ERROR) << \"Failed to glWaitSync in CGL env, glGetError: \" << gl_error;\n    }\n  }\n\n private:\n  GLsync fence_;\n};\n\nvoid CGLImageRepresentation::ConsumeFence(std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  if (fence_sync->GetType() == FenceSync::Type::kCGL) {\n    CGLFenceSync* cgl_fence_sync = static_cast<CGLFenceSync*>(fence_sync.get());\n    cgl_fence_sync->ServerWait();\n    return;\n  }\n  fence_sync->ClientWait();\n}\n\nclass DummyFenceSync final : public FenceSync {\n  bool ClientWait() override { return true; }\n\n  Type GetType() const override { return Type::kClientWaitOnly; }\n};\n\nstd::unique_ptr<FenceSync> CGLImageRepresentation::ProduceFence() {\n  // It seems that glFlush is enough for gl<->gl\n  glFlush();\n  // TODO(youfeng): use CGLFenceSync if consumer and producer are in the same\n  // group\n  return std::make_unique<DummyFenceSync>();\n}\n\nfml::RefPtr<RepresentationStorageManager> CGLImageRepresentation::GetTextureManager() const {\n  return fml::MakeRefCounted<CGLStorageManager>(gl_context_);\n}\n\nvoid CGLImageRepresentation::SetTextureManager(\n    fml::RefPtr<RepresentationStorageManager> storage_manager) {\n  storage_manager_ = fml::Ref(static_cast<CGLStorageManager*>(storage_manager.get()));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/cv_pixelbuffer_image_backing.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_CV_PIXELBUFFER_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_CV_PIXELBUFFER_IMAGE_BACKING_H_\n\n#import <CoreVideo/CoreVideo.h>\n\n#include <memory>\n#include <optional>\n\n#include \"clay/fml/platform/darwin/cf_utils.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nclass CVPixelBufferImageBacking : public SharedImageBacking {\n public:\n  CVPixelBufferImageBacking(\n      PixelFormat pixel_format, skity::Vec2 size,\n      std::optional<GraphicsMemoryHandle> gfx_handle = {});\n\n  BackingType GetType() const override;\n  GraphicsMemoryHandle GetGFXHandle() const override;\n\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n\n#ifndef ENABLE_SKITY\n  fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n  bool ReadbackToMemory(const SkPixmap* pixmaps, uint32_t planes) override;\n#else\n  fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      skity::GPUContext* skity_context) override;\n#endif  // ENABLE_SKITY\n\n private:\n  static fml::CFRef<CVPixelBufferRef> CreateCVPixelBuffer(\n      SharedImageBacking::PixelFormat pixel_format, skity::Vec2 size);\n\n  fml::CFRef<CVPixelBufferRef> pixel_buffer_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_CV_PIXELBUFFER_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/cv_pixelbuffer_image_backing.mm",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/cv_pixelbuffer_image_backing.h\"\n\n#include <CoreVideo/CVPixelBuffer.h>\n#import <CoreVideo/CoreVideo.h>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/mtl_image_representation.h\"\n#include \"clay/gfx/shared_image/utils/image_utils.h\"\n\n#if OS_IOS\n#include \"clay/gfx/shared_image/eagl_image_representation.h\"\n#endif\n\n#if OS_MAC\n#include \"clay/gfx/shared_image/cgl_image_representation.h\"\n#endif\n\n#if ENABLE_SKITY\n\n#include \"clay/gfx/shared_image/skity_mtl_image_representation.h\"\n#include \"skity/gpu/gpu_context.hpp\"\n\n#else\n\n#if SKIA_ENABLE_GL\n#include \"clay/gfx/shared_image/skia_gl_image_representation.h\"\n#endif  // GL\n#if SKIA_ENABLE_METAL\n#include \"clay/gfx/shared_image/skia_mtl_image_representation.h\"\n#endif  // METAl\n\n#endif  // SKITY\n\nnamespace clay {\n\nfml::CFRef<CVPixelBufferRef> CVPixelBufferImageBacking::CreateCVPixelBuffer(\n    SharedImageBacking::PixelFormat pixel_format, skity::Vec2 size) {\n  // TODO support other formats\n  OSType format = kCVPixelFormatType_32BGRA;\n  NSDictionary* options = @{\n    (id)kCVPixelBufferOpenGLCompatibilityKey : @(true),\n    (id)kCVPixelBufferMetalCompatibilityKey : @(true),\n  };\n\n  CVPixelBufferRef pixel_buffer = nullptr;\n  CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.x, size.y, format,\n                                        (__bridge CFDictionaryRef)options, &pixel_buffer);\n  if (status != kCVReturnSuccess || pixel_buffer == nullptr) {\n    FML_LOG(ERROR) << \"Failed to create CVPixelBuffer\";\n  }\n  return pixel_buffer;\n}\n\nCVPixelBufferImageBacking::CVPixelBufferImageBacking(PixelFormat pixel_format, skity::Vec2 size,\n                                                     std::optional<GraphicsMemoryHandle> gfx_handle)\n    : SharedImageBacking(pixel_format, size) {\n  if (gfx_handle) {\n    pixel_buffer_.Reset((CVPixelBufferRef)(*gfx_handle));\n  } else {\n    pixel_buffer_ = CreateCVPixelBuffer(pixel_format, size);\n  }\n}\n\nSharedImageBacking::BackingType CVPixelBufferImageBacking::GetType() const {\n  return BackingType::kCVPixelBuffer;\n}\n\nGraphicsMemoryHandle CVPixelBufferImageBacking::GetGFXHandle() const { return pixel_buffer_; }\n\nfml::RefPtr<SharedImageRepresentation> CVPixelBufferImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  FML_CHECK(config->struct_size == sizeof(ClaySharedImageRepresentationConfig));\n  switch (config->type) {\n    case kClaySharedImageRepresentationTypeGL:\n#if OS_MAC\n      return fml::MakeRefCounted<CGLImageRepresentation>(fml::Ref(this));\n#elif OS_IOS\n      return fml::MakeRefCounted<EAGLImageRepresentation>(fml::Ref(this));\n#else\n      break;\n#endif\n    case kClaySharedImageRepresentationTypeMetal:\n      FML_CHECK(config->metal_config.struct_size ==\n                sizeof(ClaySharedImageMetalRepresentationConfig));\n      if (@available(macos 10.6, ios 11.0, tvos 11.0, *)) {\n        return fml::MakeRefCounted<MTLImageRepresentation>(\n            (__bridge id<MTLDevice>)config->metal_config.device,\n            (__bridge id<MTLCommandQueue>)config->metal_config.command_queue, fml::Ref(this));\n      }\n      break;\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR) << \"Unable to call CVPixelBufferImageBacking::CreateRepresentation with type: \"\n                 << static_cast<uint32_t>(config->type);\n  return nullptr;\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<SkiaImageRepresentation> CVPixelBufferImageBacking::CreateSkiaRepresentation(\n    GrDirectContext* gr_context) {\n  switch (gr_context->backend()) {\n#if SKIA_ENABLE_GL\n    case GrBackendApi::kOpenGL:\n#if OS_MAC\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context, fml::MakeRefCounted<CGLImageRepresentation>(fml::Ref(this)));\n#elif OS_IOS\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context, fml::MakeRefCounted<EAGLImageRepresentation>(fml::Ref(this)));\n#endif\n      break;\n#endif\n#if SKIA_ENABLE_METAL\n    case GrBackendApi::kMetal:\n      if (@available(macos 10.6, ios 11.0, tvos 11.0, *)) {\n        return fml::MakeRefCounted<SkiaMTLImageRepresentation>(gr_context, fml::Ref(this));\n      }\n      break;\n#endif\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR)\n      << \"Unable to call CVPixelBufferImageBacking::CreateSkiaRepresentation with backend: \"\n      << static_cast<uint32_t>(gr_context->backend());\n  return nullptr;\n}\n#else\nfml::RefPtr<SkityImageRepresentation> CVPixelBufferImageBacking::CreateSkityRepresentation(\n    skity::GPUContext* skity_context) {\n  switch (skity_context->GetBackendType()) {\n    case skity::GPUBackendType::kMetal:\n      if (@available(macos 10.6, ios 11.0, tvos 11.0, *)) {\n        return fml::MakeRefCounted<SkityMTLImageRepresentation>(skity_context, fml::Ref(this));\n      }\n      break;\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR)\n      << \"Unable to call CVPixelBufferImageBacking::CreateSkityRepresentation with backend: \"\n      << static_cast<uint32_t>(skity_context->GetBackendType());\n  return nullptr;\n}\n#endif  // ENABLE_SKITY\n\n#ifndef ENABLE_SKITY\nbool CVPixelBufferImageBacking::ReadbackToMemory(const SkPixmap* pixmaps, uint32_t planes) {\n  CVReturn lockStatus = CVPixelBufferLockBaseAddress(pixel_buffer_, 0);\n  if (lockStatus != kCVReturnSuccess) {\n    FML_LOG(ERROR) << \"Failed to lock CVPixelBuffer\";\n    return false;\n  }\n\n  void* baseAddress = CVPixelBufferGetBaseAddress(pixel_buffer_);\n  size_t stride = CVPixelBufferGetBytesPerRow(pixel_buffer_);\n  size_t width = CVPixelBufferGetWidth(pixel_buffer_);\n  size_t height = CVPixelBufferGetHeight(pixel_buffer_);\n  OSType pixel_format = CVPixelBufferGetPixelFormatType(pixel_buffer_);\n  uint8_t* typeAsChars = (uint8_t*)&pixel_format;\n  FML_LOG(INFO) << \"CVPixelBufferImageBacking::ReadbackToMemory: \"\n                << \"pixel_format: \" << typeAsChars[3] << typeAsChars[2] << typeAsChars[1]\n                << typeAsChars[0] << \" width: \" << width << \" height: \" << height\n                << \" stride: \" << stride;\n  size_t source_offset = 0;\n  for (uint32_t plane = 0; plane < planes; ++plane) {\n    auto& pixmap = pixmaps[plane];\n    uint8_t* dest_memory = static_cast<uint8_t*>(pixmap.writable_addr());\n    const size_t dest_stride = pixmap.rowBytes();\n    const uint8_t* source_memory = static_cast<uint8_t*>(baseAddress) + source_offset;\n    const size_t source_stride = stride;\n\n    CopyPlane(source_memory, source_stride, dest_memory, dest_stride, pixmap.info().minRowBytes(),\n              size_.y);\n\n    source_offset += stride * size_.y;\n  }\n  CVPixelBufferUnlockBaseAddress(pixel_buffer_, kCVPixelBufferLock_ReadOnly);\n  return true;\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/d3d9_texture_image_backing.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/d3d9_texture_image_backing.h\"\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/utils/image_utils.h\"\n#if SKIA_ENABLE_GL\n#include \"clay/gfx/shared_image/angle_d3d_image_representation.h\"\n#include \"clay/gfx/shared_image/skia_gl_image_representation.h\"\n#endif\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/native_library.h\"\n#include \"clay/gfx/shared_image/d3d_image_representation.h\"\n#include \"clay/gfx/shared_image/utils/angle_get_proc.h\"\n\n#if ENABLE_SKITY\n#include \"clay/gfx/shared_image/angle_d3d_image_representation.h\"\n#include \"clay/gfx/shared_image/skity_gl_image_representation.h\"\n#else\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n#include \"third_party/skia/src/gpu/ganesh/GrDirectContextPriv.h\"  // nogncheck\n#include \"third_party/skia/src/gpu/ganesh/gl/GrGLGpu.h\"           // nogncheck\n#endif\n\nnamespace clay {\n\nnamespace {\n\nclass D3D9TextureFactory {\n public:\n  static D3D9TextureFactory& Instance();\n\n  ~D3D9TextureFactory() = default;\n\n  IDirect3DDevice9* GetDevice() { return d3d9_device_.Get(); }\n\n private:\n  D3D9TextureFactory();\n  bool InitializeD3D9Device();\n\n  fml::RefPtr<fml::NativeLibrary> d3d9_;\n  Microsoft::WRL::ComPtr<IDirect3D9> d3d9_api_;\n  Microsoft::WRL::ComPtr<IDirect3DDevice9> d3d9_device_;\n};\n\nD3D9TextureFactory& D3D9TextureFactory::Instance() {\n  // It seems that one D3D device binding to multiple WGL context\n  // can not access multiple texture simultaneously (even they are safely\n  // lock/unlocked).\n  // To support buffer queue, we must create device per thread.\n  static thread_local std::unique_ptr<D3D9TextureFactory> instance;\n  if (!instance.get()) {\n    instance.reset(new D3D9TextureFactory());\n  }\n  return *(instance.get());\n}\n\nD3D9TextureFactory::D3D9TextureFactory() {\n  if (!InitializeD3D9Device()) {\n    FML_LOG(ERROR)\n        << \"D3D9TextureImageBacking failed to initialize D3D Device.\";\n  }\n}\n\nbool D3D9TextureFactory::InitializeD3D9Device() {\n  d3d9_ = fml::NativeLibrary::Create(\"d3d9.dll\");\n\n  if (!d3d9_) {\n    FML_LOG(ERROR) << \"Could not load D3D9 library.\";\n    return false;\n  }\n\n  auto Direct3DCreate9ExFn =\n      d3d9_->ResolveFunction<decltype(&Direct3DCreate9Ex)>(\"Direct3DCreate9Ex\");\n\n  if (!Direct3DCreate9ExFn.has_value()) {\n    FML_LOG(ERROR) << \"Could not retrieve Direct3DCreate9Ex address.\";\n    return false;\n  }\n  // Use Direct3D9Ex, in ANGLE Renderer9.cpp, it's said that\n  // \"this version is less inclined to report a lost context,\"\n  Microsoft::WRL::ComPtr<IDirect3D9Ex> d3d9ex_api;\n\n  HRESULT hr = Direct3DCreate9ExFn.value()(D3D_SDK_VERSION, &d3d9ex_api);\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"D3D9TextureImageBacking could not create D3D9Ex api.\";\n    return false;\n  }\n  if (FAILED(d3d9ex_api.As(&d3d9_api_))) {\n    FML_LOG(ERROR) << \"D3D9TextureImageBacking could not create D3D9 api.\";\n    return false;\n  }\n  D3DPRESENT_PARAMETERS present_parameters = {};\n\n  // The default swap chain is never actually used. Surface will create a new\n  // swap chain with the proper parameters.\n  present_parameters.AutoDepthStencilFormat = D3DFMT_UNKNOWN;\n  present_parameters.BackBufferCount = 1;\n  present_parameters.BackBufferFormat = D3DFMT_UNKNOWN;\n  present_parameters.BackBufferWidth = 1;\n  present_parameters.BackBufferHeight = 1;\n  present_parameters.EnableAutoDepthStencil = FALSE;\n  present_parameters.Flags = 0;\n  present_parameters.hDeviceWindow = GetDesktopWindow();\n  present_parameters.MultiSampleQuality = 0;\n  present_parameters.MultiSampleType = D3DMULTISAMPLE_NONE;\n  present_parameters.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;\n  present_parameters.SwapEffect = D3DSWAPEFFECT_DISCARD;\n  present_parameters.Windowed = TRUE;\n\n  hr = d3d9_api_->CreateDevice(\n      D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, GetDesktopWindow(),\n      D3DCREATE_FPU_PRESERVE | D3DCREATE_NOWINDOWCHANGES |\n          D3DCREATE_MULTITHREADED | D3DCREATE_HARDWARE_VERTEXPROCESSING |\n          D3DCREATE_PUREDEVICE,\n      &present_parameters, &d3d9_device_);\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"D3D9TextureImageBacking could not create D3D9Ex Device.\";\n    return false;\n  }\n  return true;\n}\n\nstd::optional<D3DFORMAT> ToD3DFormat(SharedImageBacking::PixelFormat format) {\n  switch (format) {\n    case SharedImageBacking::PixelFormat::kNative8888:\n      // TODO(youfeng): D3D9 only support A8R8G8B8, but other platforms use BGRA\n      // as default.\n      return D3DFMT_A8R8G8B8;\n      break;\n    default:\n      FML_LOG(ERROR) << \"Invalid pixel format\" << static_cast<uint32_t>(format);\n      return {};\n  }\n}\n\nbool CreateD3D9TextureSharedHandle(IDirect3DDevice9* device, D3DFORMAT format,\n                                   skity::Vec2 size,\n                                   IDirect3DTexture9** out_texture,\n                                   HANDLE* out_handle) {\n  // Use the shared handle with another Direct3D device (otherDevice)\n  HRESULT hr =\n      device->CreateTexture(size.x, size.y, 1, D3DUSAGE_RENDERTARGET, format,\n                            D3DPOOL_DEFAULT, out_texture, out_handle);\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"Failed to create D3D9 texture.\";\n    return false;\n  }\n  return true;\n}\n\nbool OpenD3D9SharedHandle(IDirect3DDevice9* device, D3DFORMAT format,\n                          skity::Vec2 size, HANDLE shared_handle,\n                          IDirect3DTexture9** out_texture) {\n  HRESULT hr =\n      device->CreateTexture(size.x, size.y, 1, D3DUSAGE_RENDERTARGET, format,\n                            D3DPOOL_DEFAULT, out_texture, &shared_handle);\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"Failed to open D3D9 texture.\";\n    return false;\n  }\n  return true;\n}\n}  // namespace\n\nD3D9TextureImageBacking::D3D9TextureImageBacking(\n    PixelFormat pixel_format, skity::Vec2 size,\n    std::optional<GraphicsMemoryHandle> gfx_handle)\n    : SharedImageBacking(pixel_format, size) {\n  d3d9_device_ = D3D9TextureFactory::Instance().GetDevice();\n  auto opt_format = ToD3DFormat(pixel_format);\n  if (!opt_format) {\n    return;\n  }\n  d3d_format_ = opt_format.value();\n  if (gfx_handle) {\n    shared_handle_ = static_cast<HANDLE>(*gfx_handle);\n    bool result = OpenD3D9SharedHandle(d3d9_device_.Get(), d3d_format_, size,\n                                       shared_handle_, &d3d9_texture_);\n    FML_CHECK(result);\n  } else {\n    bool result = CreateD3D9TextureSharedHandle(\n        d3d9_device_.Get(), d3d_format_, size, &d3d9_texture_, &shared_handle_);\n    FML_CHECK(result);\n  }\n  FML_CHECK(d3d9_texture_);\n}\n\nD3D9TextureImageBacking::~D3D9TextureImageBacking() = default;\n\nbool D3D9TextureImageBacking::OpenForDevice(\n    IDirect3DDevice9* device, IDirect3DTexture9** out_texture) const {\n  return OpenD3D9SharedHandle(device, d3d_format_, size_, shared_handle_,\n                              out_texture);\n}\n\nSharedImageBacking::BackingType D3D9TextureImageBacking::GetType() const {\n  return BackingType::kD3DTexture;\n}\n\nGraphicsMemoryHandle D3D9TextureImageBacking::GetGFXHandle() const {\n  return shared_handle_;\n}\n\nfml::RefPtr<SharedImageRepresentation>\nD3D9TextureImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  FML_CHECK(config->struct_size == sizeof(ClaySharedImageRepresentationConfig));\n\n  switch (config->type) {\n    case kClaySharedImageRepresentationTypeGL: {\n      PFNEGLGETPROCADDRESSPROC eglGetProcAddressProc =\n          reinterpret_cast<PFNEGLGETPROCADDRESSPROC>(\n              config->gl_config.get_proc_address);\n      if (!eglGetProcAddressProc) {\n        eglGetProcAddressProc = GetAngleEglGetProcAddressProc();\n      }\n      return fml::MakeRefCounted<AngleD3DImageRepresentation>(\n          fml::Ref(this), eglGetProcAddressProc);\n    }\n    case kClaySharedImageRepresentationTypeD3D:\n      FML_CHECK(config->d3d_config.struct_size ==\n                sizeof(ClaySharedImageD3DRepresentationConfig));\n      return fml::MakeRefCounted<D3DImageRepresentation>(\n          static_cast<IDirect3DDevice9*>(config->d3d_config.device),\n          fml::Ref(this));\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR) << \"Unable to call \"\n                    \"D3D9TextureImageBacking::CreateRepresentation with type: \"\n                 << static_cast<uint32_t>(config->type);\n  return nullptr;\n}\n\n#ifdef ENABLE_SKITY\nfml::RefPtr<SkityImageRepresentation>\nD3D9TextureImageBacking::CreateSkityRepresentation(\n    skity::GPUContext* skity_context) {\n  switch (skity_context->GetBackendType()) {\n    case skity::GPUBackendType::kOpenGL: {\n      return fml::MakeRefCounted<SkityGLImageRepresentation>(\n          skity_context, fml::MakeRefCounted<AngleD3DImageRepresentation>(\n                             fml::Ref(this), GetAngleEglGetProcAddressProc()));\n    }\n    default:\n      break;\n  }\n  FML_LOG(ERROR) << \"Unable to call \"\n                    \"D3D9TextureImageBacking::CreateSkityRepresentation with \"\n                    \"backend: \"\n                 << static_cast<uint32_t>(skity_context->GetBackendType());\n  return nullptr;\n}\n#else\nfml::RefPtr<SkiaImageRepresentation>\nD3D9TextureImageBacking::CreateSkiaRepresentation(GrDirectContext* gr_context) {\n  switch (gr_context->backend()) {\n#if SKIA_ENABLE_GL\n    case GrBackendApi::kOpenGL: {\n      GrGLGpu* gpu = static_cast<GrGLGpu*>(gr_context->priv().getGpu());\n      // Skia repr is always internal, so we always use internal Angle symbols\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context, fml::MakeRefCounted<AngleD3DImageRepresentation>(\n                          fml::Ref(this), GetAngleEglGetProcAddressProc()));\n    }\n#endif\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR) << \"Unable to call \"\n                    \"D3D9TextureImageBacking::CreateSkiaRepresentation with \"\n                    \"backend: \"\n                 << static_cast<uint32_t>(gr_context->backend());\n  return nullptr;\n}\n\nbool D3D9TextureImageBacking::ReadbackToMemory(const SkPixmap* pixmaps,\n                                               uint32_t planes) {\n  IDirect3DTexture9* staging_texture = GetOrCreateStagingTexture();\n  if (!staging_texture) {\n    return false;\n  }\n\n  for (int plane = 0; plane < planes; ++plane) {\n    Microsoft::WRL::ComPtr<IDirect3DSurface9> src_surface, dst_surface;\n    if (FAILED(d3d9_texture_->GetSurfaceLevel(plane, &src_surface)) ||\n        FAILED(staging_texture->GetSurfaceLevel(plane, &dst_surface))) {\n      FML_LOG(ERROR) << \"Failed to get surface.\";\n      return false;\n    }\n    HRESULT hr =\n        d3d9_device_->GetRenderTargetData(src_surface.Get(), dst_surface.Get());\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to update texture.\";\n      return false;\n    }\n\n    D3DLOCKED_RECT locked_rect;\n    hr = dst_surface->LockRect(&locked_rect, nullptr, D3DLOCK_READONLY);\n\n    if (FAILED(hr)) {\n      staging_texture->UnlockRect(plane);\n      FML_LOG(ERROR) << \"Failed to lock texture.\";\n      return false;\n    }\n\n    auto& pixmap = pixmaps[plane];\n    uint8_t* dest_memory = static_cast<uint8_t*>(pixmap.writable_addr());\n    const size_t dest_stride = pixmap.rowBytes();\n    const uint8_t* source_memory = static_cast<uint8_t*>(locked_rect.pBits);\n    const size_t source_stride = locked_rect.Pitch;\n\n    CopyPlane(source_memory, source_stride, dest_memory, dest_stride,\n              pixmap.info().minRowBytes(), size_.y);\n\n    hr = staging_texture->UnlockRect(plane);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to unlock texture.\";\n      return false;\n    }\n  }\n\n  return true;\n}\n#endif  // ENABLE_SKITY\n\nIDirect3DTexture9* D3D9TextureImageBacking::GetOrCreateStagingTexture() {\n  if (!staging_texture_) {\n    HRESULT hr = d3d9_device_->CreateTexture(size_.x, size_.y, 1, 0,\n                                             d3d_format_, D3DPOOL_SYSTEMMEM,\n                                             &staging_texture_, nullptr);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to create staging texture.\";\n      return nullptr;\n    }\n  }\n\n  return staging_texture_.Get();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/d3d9_texture_image_backing.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_D3D9_TEXTURE_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_D3D9_TEXTURE_IMAGE_BACKING_H_\n\n#include <d3d9.h>\n#include <wrl/client.h>\n\n#include <memory>\n#include <optional>\n\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nclass SharedImageRepresentation;\n\nclass D3D9TextureImageBacking final : public SharedImageBacking {\n public:\n  D3D9TextureImageBacking(PixelFormat pixel_format, skity::Vec2 size,\n                          std::optional<GraphicsMemoryHandle> gfx_handle = {});\n\n  ~D3D9TextureImageBacking() override;\n\n  bool OpenForDevice(IDirect3DDevice9* device,\n                     IDirect3DTexture9** out_texture) const;\n\n  BackingType GetType() const override;\n  GraphicsMemoryHandle GetGFXHandle() const override;\n\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n#ifdef ENABLE_SKITY\n  virtual fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      skity::GPUContext* skity_context) override;\n#else\n  bool ReadbackToMemory(const SkPixmap* pixmaps, uint32_t planes) override;\n  virtual fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n#endif  // ENABLE_SKITY\n\n private:\n  HANDLE shared_handle_ = nullptr;\n\n  IDirect3DTexture9* GetOrCreateStagingTexture();\n\n  D3DFORMAT d3d_format_;\n\n  // The texture and device should never be used outside the class.\n  // User MUST use OpenSharedResource on its own D3D9Device\n  Microsoft::WRL::ComPtr<IDirect3DDevice9> d3d9_device_;\n  Microsoft::WRL::ComPtr<IDirect3DTexture9> d3d9_texture_;\n  Microsoft::WRL::ComPtr<IDirect3DTexture9> staging_texture_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_D3D9_TEXTURE_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/d3d_image_representation.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/d3d_image_representation.h\"\n\n#include <Windows.h>\n#include <d3d11_1.h>\n\n#include <cstring>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/d3d9_texture_image_backing.h\"\n#include \"clay/gfx/shared_image/d3d_texture_image_backing.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n\nnamespace clay {\n\nD3DImageRepresentation::D3DImageRepresentation(\n    ID3D11Device* d3d11_device, fml::RefPtr<D3DTextureImageBacking> backing)\n    : SharedImageRepresentation(backing) {\n  if (!d3d11_device) {\n    FML_LOG(ERROR) << \"Has no d3d11 device.\";\n    return;\n  }\n  device_ = d3d11_device;\n\n  if (!backing->OpenForDevice(d3d11_device, &d3d11_texture_, &keyed_mutex_)) {\n    FML_LOG(ERROR) << \"Failed to open for device.\";\n  }\n}\n\nD3DImageRepresentation::D3DImageRepresentation(\n    IDirect3DDevice9* d3d9_device, fml::RefPtr<D3D9TextureImageBacking> backing)\n    : SharedImageRepresentation(backing) {\n  if (!d3d9_device) {\n    FML_LOG(ERROR) << \"Has no d3d9 device.\";\n    return;\n  }\n  device9_ = d3d9_device;\n\n  if (!backing->OpenForDevice(d3d9_device, &d3d9_texture_)) {\n    FML_LOG(ERROR) << \"Failed to open for device.\";\n  }\n}\n\nD3DImageRepresentation::~D3DImageRepresentation() {\n  if (scoped_keyed_mutex_) {\n    FML_LOG(ERROR) << \"Keyed mutex is still locked.\";\n    scoped_keyed_mutex_.reset();\n  }\n  keyed_mutex_.Reset();\n  d3d11_texture_.Reset();\n  device_.Reset();\n\n  d3d9_query_.Reset();\n  d3d9_texture_.Reset();\n  device9_.Reset();\n}\n\nImageRepresentationType D3DImageRepresentation::GetType() const {\n  return ImageRepresentationType::kD3D;\n}\n\nbool D3DImageRepresentation::BeginRead(ClaySharedImageReadResult* out) {\n  if (!d3d11_texture_ && !d3d9_texture_) {\n    return false;\n  }\n  memset(out, 0, sizeof(ClaySharedImageReadResult));\n  out->struct_size = sizeof(ClaySharedImageReadResult);\n  out->type = kClaySharedImageRepresentationTypeD3D;\n  out->d3d_texture.struct_size = sizeof(ClayD3DTexture);\n  out->d3d_texture.texture =\n      d3d11_texture_ ? static_cast<ClayD3DTextureHandle>(d3d11_texture_.Get())\n                     : static_cast<ClayD3DTextureHandle>(d3d9_texture_.Get());\n  out->d3d_texture.user_data = this;\n  this->AddRef();\n  out->d3d_texture.destruction_callback = [](void* user_data) {\n    static_cast<D3DImageRepresentation*>(user_data)->Release();\n  };\n\n  return LockKeyedMutex();\n}\n\nbool D3DImageRepresentation::EndRead() { return UnlockKeyedMutex(); }\n\nbool D3DImageRepresentation::BeginWrite(ClaySharedImageWriteResult* out) {\n  if (!d3d11_texture_ && !d3d9_texture_) {\n    return false;\n  }\n  memset(out, 0, sizeof(ClaySharedImageWriteResult));\n  out->struct_size = sizeof(ClaySharedImageWriteResult);\n  out->type = kClaySharedImageRepresentationTypeD3D;\n  out->d3d_texture.struct_size = sizeof(ClayD3DTexture);\n  out->d3d_texture.texture =\n      d3d11_texture_ ? static_cast<ClayD3DTextureHandle>(d3d11_texture_.Get())\n                     : static_cast<ClayD3DTextureHandle>(d3d9_texture_.Get());\n  out->d3d_texture.user_data = this;\n  this->AddRef();\n  out->d3d_texture.destruction_callback = [](void* user_data) {\n    static_cast<D3DImageRepresentation*>(user_data)->Release();\n  };\n\n  return LockKeyedMutex();\n}\n\nbool D3DImageRepresentation::EndWrite() { return UnlockKeyedMutex(); }\n\nbool D3DImageRepresentation::LockKeyedMutex() {\n  if (!keyed_mutex_) {\n    return true;\n  }\n  if (scoped_keyed_mutex_) {\n    FML_LOG(ERROR) << \"Keyed mutex is already locked.\";\n  }\n  scoped_keyed_mutex_.emplace(keyed_mutex_.Get());\n  return true;\n}\n\nbool D3DImageRepresentation::UnlockKeyedMutex() {\n  if (!keyed_mutex_) {\n    return true;\n  }\n  if (!scoped_keyed_mutex_) {\n    FML_LOG(ERROR) << \"Keyed mutex is not locked.\";\n  }\n  scoped_keyed_mutex_.reset();\n  return true;\n}\n\nclass DummyFenceSync final : public FenceSync {\n  bool ClientWait() override { return true; }\n\n  Type GetType() const override { return Type::kClientWaitOnly; }\n};\n\nvoid D3DImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  fence_sync->ClientWait();\n}\n\nbool D3DImageRepresentation::FlushD3D9() {\n  FML_DCHECK(device9_);\n  HRESULT hr;\n  if (!d3d9_query_) {\n    hr = device9_->CreateQuery(D3DQUERYTYPE_EVENT, &d3d9_query_);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to create query.\";\n      return false;\n    }\n  }\n  hr = d3d9_query_->Issue(D3DISSUE_END);\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"Failed to issue D3DISSUE_END.\";\n    return false;\n  }\n  hr = d3d9_query_->GetData(nullptr, 0, D3DGETDATA_FLUSH);\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"Failed to get event D3DGETDATA_FLUSH.\";\n    return false;\n  }\n  return true;\n}\n\nstd::unique_ptr<FenceSync> D3DImageRepresentation::ProduceFence() {\n  // We use keyed mutex when BeginRW/EndRW instead of fence\n  if (device_) {\n    Microsoft::WRL::ComPtr<ID3D11DeviceContext> device_context;\n    device_->GetImmediateContext(&device_context);\n    device_context->Flush();\n  } else if (device9_) {\n    FlushD3D9();\n  }\n\n  return std::make_unique<DummyFenceSync>();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/d3d_image_representation.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_D3D_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_D3D_IMAGE_REPRESENTATION_H_\n\n#include <d3d11.h>\n#include <d3d9.h>\n#include <wrl/client.h>\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n#include \"clay/gfx/shared_image/utils/dxgi_utils.h\"\n\nnamespace clay {\n\nclass D3DTextureImageBacking;\nclass D3D9TextureImageBacking;\n\nclass D3DImageRepresentation final : public SharedImageRepresentation {\n public:\n  D3DImageRepresentation(ID3D11Device* d3d11_device,\n                         fml::RefPtr<D3DTextureImageBacking> backing);\n\n  D3DImageRepresentation(IDirect3DDevice9* d3d9_device,\n                         fml::RefPtr<D3D9TextureImageBacking> backing);\n\n  ~D3DImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n\n  bool BeginRead(ClaySharedImageReadResult* out) override;\n  bool EndRead() override;\n  bool BeginWrite(ClaySharedImageWriteResult* out) override;\n  bool EndWrite() override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n private:\n  bool LockKeyedMutex();\n  bool UnlockKeyedMutex();\n\n  bool FlushD3D9();\n\n  // Only used in D3D11\n  Microsoft::WRL::ComPtr<ID3D11Device> device_;\n  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture_;\n  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex_;\n  std::optional<ScopedDXGIKeyedMutex> scoped_keyed_mutex_;\n\n  // Only used in D3D9\n  Microsoft::WRL::ComPtr<IDirect3DDevice9> device9_;\n  Microsoft::WRL::ComPtr<IDirect3DTexture9> d3d9_texture_;\n  Microsoft::WRL::ComPtr<IDirect3DQuery9> d3d9_query_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_D3D_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/d3d_texture_image_backing.cc",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/d3d_texture_image_backing.h\"\n\n#include <d3d11_3.h>\n\n#include <memory>\n\n#include \"base/include/no_destructor.h\"\n#include \"clay/gfx/shared_image/utils/image_utils.h\"\n#if SKIA_ENABLE_GL\n#include \"clay/gfx/shared_image/angle_d3d_image_representation.h\"\n#include \"clay/gfx/shared_image/skia_gl_image_representation.h\"\n#endif\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/native_library.h\"\n#include \"clay/gfx/shared_image/d3d_image_representation.h\"\n#include \"clay/gfx/shared_image/utils/angle_get_proc.h\"\n\n#if ENABLE_SKITY\n#include \"clay/gfx/shared_image/angle_d3d_image_representation.h\"\n#include \"clay/gfx/shared_image/skity_gl_image_representation.h\"\n#else\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n#include \"third_party/skia/src/gpu/ganesh/GrDirectContextPriv.h\"  // nogncheck\n#include \"third_party/skia/src/gpu/ganesh/gl/GrGLGpu.h\"           // nogncheck\n#endif\n\n#define __FORCE_DEDICATED_GPU 0\n\n// For testing read memory from dedicated GPU\n// https://stackoverflow.com/a/39047129\n#if __FORCE_DEDICATED_GPU\nextern \"C\" {\n// NOLINTNEXTLINE(runtime/int) cspell:disable-next-line\n__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;\n\n__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;\n}\n#endif  // __FORCE_DEDICATED_GPU\n\nnamespace clay {\n\nnamespace {\n\nclass D3DTextureFactory {\n public:\n  class ScopedDevice {\n   public:\n    ScopedDevice(std::mutex& m, ID3D11Device* device)\n        : lock_(m), device_(device) {}\n\n    ID3D11Device* GetDevice() const { return device_; }\n\n   private:\n    std::lock_guard<std::mutex> lock_;\n    ID3D11Device* device_;\n  };\n  static D3DTextureFactory& Instance();\n\n  ~D3DTextureFactory() = default;\n\n  ScopedDevice GetDeviceScoped() {\n    return ScopedDevice(mutex_, d3d11_device_.Get());\n  }\n\n  bool IsSupported() const { return d3d11_device_ != nullptr; }\n\n private:\n  D3DTextureFactory();\n  bool InitializeD3DDevice();\n\n  fml::RefPtr<fml::NativeLibrary> d3d11_;\n  fml::RefPtr<fml::NativeLibrary> dxgi_;\n  std::mutex mutex_;\n  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;\n\n  friend class fml::NoDestructor<D3DTextureFactory>;\n};\n\nD3DTextureFactory& D3DTextureFactory::Instance() {\n  static fml::NoDestructor<D3DTextureFactory> instance;\n  return *(instance.get());\n}\n\nD3DTextureFactory::D3DTextureFactory() {\n  if (!InitializeD3DDevice()) {\n    FML_LOG(ERROR) << \"D3DTextureImageBacking failed to initialize D3D Device.\";\n  }\n}\n\nbool D3DTextureFactory::InitializeD3DDevice() {\n  d3d11_ = fml::NativeLibrary::Create(\"d3d11.dll\");\n  dxgi_ = fml::NativeLibrary::Create(\"dxgi.dll\");\n\n  if (!d3d11_ || !dxgi_) {\n    FML_LOG(ERROR) << \"Could not load D3D11 or DXGI library.\";\n    return false;\n  }\n\n  std::optional<PFN_D3D11_CREATE_DEVICE> D3D11CreateDevice =\n      d3d11_->ResolveFunction<PFN_D3D11_CREATE_DEVICE>(\"D3D11CreateDevice\");\n\n  if (!D3D11CreateDevice.has_value()) {\n    FML_LOG(ERROR) << \"Could not retrieve D3D11CreateDevice address.\";\n    return false;\n  }\n  HRESULT hr = D3D11CreateDevice.value()(\n      nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0,\n      D3D11_SDK_VERSION, &d3d11_device_, nullptr, nullptr);\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"D3DTextureImageBacking could not create D3D11 Device.\";\n    return false;\n  }\n  return true;\n}\n\nbool CreateD3DTextureSharedHandle(ID3D11Device* device,\n                                  SharedImageBacking::PixelFormat pixel_format,\n                                  skity::Vec2 size,\n                                  ID3D11Texture2D** out_texture,\n                                  HANDLE* out_handle, bool* is_nt_handle) {\n  DXGI_FORMAT dxgi_format;\n  switch (pixel_format) {\n    case SharedImageBacking::PixelFormat::kNative8888:\n      dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM;\n      break;\n    case SharedImageBacking::PixelFormat::kRGBA8888:\n      dxgi_format = DXGI_FORMAT_R8G8B8A8_UNORM;\n      break;\n    default:\n      FML_LOG(ERROR) << \"Invalid pixel format\";\n      return false;\n  }\n\n  D3D11_TEXTURE2D_DESC desc = {\n      static_cast<UINT>(size.x),\n      static_cast<UINT>(size.y),\n      1,\n      1,\n      dxgi_format,\n      {1, 0},\n      D3D11_USAGE_DEFAULT,\n      D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,\n      0,\n      D3D11_RESOURCE_MISC_SHARED_NTHANDLE |\n          D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX};\n\n  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;\n  if (FAILED(device->CreateTexture2D(&desc, nullptr, &d3d11_texture))) {\n    FML_LOG(ERROR) << \"Could not create D3D texture2D.\";\n    return false;\n  }\n\n  d3d11_texture.CopyTo(out_texture);\n\n  if (Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource1;\n      SUCCEEDED(d3d11_texture.As(&dxgi_resource1))) {\n    if (SUCCEEDED(dxgi_resource1->CreateSharedHandle(\n            nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,\n            nullptr, out_handle))) {\n      *is_nt_handle = true;\n      return true;\n    } else {\n      FML_LOG(ERROR) << \"Could not create shared handle from IDXGIResource1.\";\n    }\n  }\n\n  if (Microsoft::WRL::ComPtr<IDXGIResource> dxgi_resource;\n      SUCCEEDED(d3d11_texture.As(&dxgi_resource))) {\n    if (SUCCEEDED(dxgi_resource->GetSharedHandle(out_handle))) {\n      return true;\n    } else {\n      FML_LOG(ERROR) << \"Could not create shared handle from IDXGIResource.\";\n    }\n  }\n\n  return false;\n}\n\nbool OpenD3DSharedHandle(ID3D11Device* device, HANDLE shared_handle,\n                         ID3D11Texture2D** out_texture) {\n  HRESULT hr =\n      device->OpenSharedResource(shared_handle, __uuidof(ID3D11Texture2D),\n                                 reinterpret_cast<void**>(out_texture));\n\n  if (SUCCEEDED(hr)) {\n    return true;\n  }\n\n  Microsoft::WRL::ComPtr<ID3D11Device1> d3d11_device1;\n  device->QueryInterface(__uuidof(ID3D11Device1), &d3d11_device1);\n  if (d3d11_device1) {\n    hr = d3d11_device1->OpenSharedResource1(\n        shared_handle, __uuidof(ID3D11Texture2D),\n        reinterpret_cast<void**>(out_texture));\n    return SUCCEEDED(hr);\n  }\n\n  return false;\n}\n}  // namespace\n\nD3DTextureImageBacking::D3DTextureImageBacking(\n    PixelFormat pixel_format, skity::Vec2 size,\n    std::optional<GraphicsMemoryHandle> gfx_handle)\n    : SharedImageBacking(pixel_format, size) {\n  D3DTextureFactory::ScopedDevice scoped_device =\n      D3DTextureFactory::Instance().GetDeviceScoped();\n  ID3D11Device* d3d11_device = scoped_device.GetDevice();\n  if (gfx_handle) {\n    DuplicateHandle(GetCurrentProcess(), static_cast<HANDLE>(*gfx_handle),\n                    GetCurrentProcess(), &shared_handle_, 0, FALSE,\n                    DUPLICATE_SAME_ACCESS);\n    owned_nt_handle_ = true;\n    bool result =\n        OpenD3DSharedHandle(d3d11_device, shared_handle_, &d3d11_texture_);\n    FML_CHECK(result);\n  } else {\n    bool result = CreateD3DTextureSharedHandle(d3d11_device, pixel_format, size,\n                                               &d3d11_texture_, &shared_handle_,\n                                               &owned_nt_handle_);\n    FML_CHECK(result);\n  }\n  FML_CHECK(d3d11_texture_);\n  d3d11_texture_->GetDesc(&d3d11_texture_desc_);\n  if (gfx_handle && size_.x == 0 && size_.y == 0) {\n    size_.x = d3d11_texture_desc_.Width;\n    size_.y = d3d11_texture_desc_.Height;\n  }\n}\n\nD3DTextureImageBacking::~D3DTextureImageBacking() {\n  D3DTextureFactory::ScopedDevice scoped_device =\n      D3DTextureFactory::Instance().GetDeviceScoped();\n  if (owned_nt_handle_ && shared_handle_) {\n    CloseHandle(shared_handle_);\n    shared_handle_ = nullptr;\n  }\n  d3d11_texture_.Reset();\n  staging_texture_.Reset();\n  keyed_mutex_.Reset();\n  Microsoft::WRL::ComPtr<ID3D11DeviceContext> device_context;\n  scoped_device.GetDevice()->GetImmediateContext(&device_context);\n  if (device_context) {\n    device_context->Flush();\n  }\n}\n\nbool D3DTextureImageBacking::OpenForDevice(\n    ID3D11Device* device, ID3D11Texture2D** out_texture,\n    IDXGIKeyedMutex** out_keyed_mutex) const {\n  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;\n  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex;\n\n  bool result = OpenD3DSharedHandle(device, shared_handle_, &d3d11_texture);\n  if (!result) {\n    FML_LOG(ERROR) << \"Failed to OpenD3DSharedHandle.\";\n    return false;\n  }\n  if (HasKeyedMutex()) {\n    if (FAILED(d3d11_texture.As(&keyed_mutex))) {\n      FML_LOG(ERROR) << \"Failed to get keyed mutex.\";\n      return false;\n    }\n  }\n\n  d3d11_texture.CopyTo(out_texture);\n  keyed_mutex.CopyTo(out_keyed_mutex);\n  return true;\n}\n\nSharedImageBacking::BackingType D3DTextureImageBacking::GetType() const {\n  return BackingType::kD3DTexture;\n}\n\nGraphicsMemoryHandle D3DTextureImageBacking::GetGFXHandle() const {\n  return shared_handle_;\n}\n\nfml::RefPtr<SharedImageRepresentation>\nD3DTextureImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  FML_CHECK(config->struct_size == sizeof(ClaySharedImageRepresentationConfig));\n\n  switch (config->type) {\n    case kClaySharedImageRepresentationTypeGL: {\n      PFNEGLGETPROCADDRESSPROC eglGetProcAddressProc =\n          reinterpret_cast<PFNEGLGETPROCADDRESSPROC>(\n              config->gl_config.get_proc_address);\n      if (!eglGetProcAddressProc) {\n        eglGetProcAddressProc = GetAngleEglGetProcAddressProc();\n      }\n      return fml::MakeRefCounted<AngleD3DImageRepresentation>(\n          fml::Ref(this), eglGetProcAddressProc);\n    }\n    case kClaySharedImageRepresentationTypeD3D:\n      FML_CHECK(config->d3d_config.struct_size ==\n                sizeof(ClaySharedImageD3DRepresentationConfig));\n      return fml::MakeRefCounted<D3DImageRepresentation>(\n          static_cast<ID3D11Device*>(config->d3d_config.device),\n          fml::Ref(this));\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR) << \"Unable to call \"\n                    \"D3DTextureImageBacking::CreateRepresentation with type: \"\n                 << static_cast<uint32_t>(config->type);\n  return nullptr;\n}\n\n#ifdef ENABLE_SKITY\nfml::RefPtr<SkityImageRepresentation>\nD3DTextureImageBacking::CreateSkityRepresentation(\n    skity::GPUContext* skity_context) {\n  switch (skity_context->GetBackendType()) {\n    case skity::GPUBackendType::kOpenGL: {\n      return fml::MakeRefCounted<SkityGLImageRepresentation>(\n          skity_context, fml::MakeRefCounted<AngleD3DImageRepresentation>(\n                             fml::Ref(this), GetAngleEglGetProcAddressProc()));\n    }\n    default:\n      break;\n  }\n  FML_LOG(ERROR) << \"Unable to call \"\n                    \"D3D9TextureImageBacking::CreateSkityRepresentation with \"\n                    \"backend: \"\n                 << static_cast<uint32_t>(skity_context->GetBackendType());\n  return nullptr;\n}\n#else\nfml::RefPtr<SkiaImageRepresentation>\nD3DTextureImageBacking::CreateSkiaRepresentation(GrDirectContext* gr_context) {\n  switch (gr_context->backend()) {\n#if SKIA_ENABLE_GL\n    case GrBackendApi::kOpenGL: {\n      GrGLGpu* gpu = static_cast<GrGLGpu*>(gr_context->priv().getGpu());\n      // Skia repr is always internal, so we always use internal Angle symbols\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context, fml::MakeRefCounted<AngleD3DImageRepresentation>(\n                          fml::Ref(this), GetAngleEglGetProcAddressProc()));\n    }\n#endif\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR)\n      << \"Unable to call D3DTextureImageBacking::CreateSkiaRepresentation with \"\n         \"backend: \"\n      << static_cast<uint32_t>(gr_context->backend());\n  return nullptr;\n}\n\nbool D3DTextureImageBacking::ReadbackToMemory(const SkPixmap* pixmaps,\n                                              uint32_t planes) {\n  D3DTextureFactory::ScopedDevice scoped_device =\n      D3DTextureFactory::Instance().GetDeviceScoped();\n  ID3D11Device* d3d11_device = scoped_device.GetDevice();\n  Microsoft::WRL::ComPtr<ID3D11DeviceContext> device_context;\n  d3d11_device->GetImmediateContext(&device_context);\n\n  FML_DCHECK(device_context);\n\n  if (d3d11_texture_desc_.CPUAccessFlags & D3D11_CPU_ACCESS_READ) {\n    Microsoft::WRL::ComPtr<ID3D11Device3> device3;\n    HRESULT hr =\n        d3d11_device->QueryInterface(__uuidof(ID3D11Device3), &device3);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to retrieve ID3D11Device3. hr=\" << std::hex\n                     << hr;\n      return false;\n    }\n    hr = device_context->Map(d3d11_texture_.Get(), 0, D3D11_MAP_READ, 0,\n                             nullptr);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to map texture for read. hr=\" << std::hex << hr;\n      return false;\n    }\n\n    uint8_t* dest_memory = static_cast<uint8_t*>(pixmaps[0].writable_addr());\n    const size_t dest_stride = pixmaps[0].rowBytes();\n    device3->ReadFromSubresource(dest_memory, dest_stride, 0,\n                                 d3d11_texture_.Get(), 0, nullptr);\n    device_context->Unmap(d3d11_texture_.Get(), 0);\n  } else {\n    ID3D11Texture2D* staging_texture = GetOrCreateStagingTexture(d3d11_device);\n    if (!staging_texture) {\n      return false;\n    }\n\n    IDXGIKeyedMutex* keyed_mutex = GetOrCreateKeyedMutex();\n\n    {\n      std::optional<ScopedDXGIKeyedMutex> scoped_keyed_mutex;\n      if (keyed_mutex) {\n        scoped_keyed_mutex.emplace(keyed_mutex);\n      }\n      device_context->CopyResource(staging_texture, d3d11_texture_.Get());\n    }\n\n    D3D11_MAPPED_SUBRESOURCE mapped_resource = {};\n    HRESULT hr = device_context->Map(staging_texture, 0, D3D11_MAP_READ, 0,\n                                     &mapped_resource);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to map texture for read. hr=\" << std::hex << hr;\n      return false;\n    }\n\n    // The mapped staging texture pData points to the first plane's data so an\n    // offset is needed for subsequent planes.\n    size_t source_offset = 0;\n\n    for (int plane = 0; plane < planes; ++plane) {\n      auto& pixmap = pixmaps[plane];\n      uint8_t* dest_memory = static_cast<uint8_t*>(pixmap.writable_addr());\n      const size_t dest_stride = pixmap.rowBytes();\n      const uint8_t* source_memory =\n          static_cast<uint8_t*>(mapped_resource.pData) + source_offset;\n      const size_t source_stride = mapped_resource.RowPitch;\n\n      CopyPlane(source_memory, source_stride, dest_memory, dest_stride,\n                pixmap.info().minRowBytes(), size_.y);\n\n      source_offset += mapped_resource.RowPitch * size_.y;\n    }\n\n    device_context->Unmap(staging_texture, 0);\n  }\n  return true;\n}\n#endif  // ENABLE_SKITY\n\nID3D11Texture2D* D3DTextureImageBacking::GetOrCreateStagingTexture(\n    ID3D11Device* device) {\n  if (!staging_texture_) {\n    D3D11_TEXTURE2D_DESC staging_desc = {};\n    staging_desc.Width = d3d11_texture_desc_.Width;\n    staging_desc.Height = d3d11_texture_desc_.Height;\n    staging_desc.Format = d3d11_texture_desc_.Format;\n    staging_desc.MipLevels = 1;\n    staging_desc.ArraySize = 1;\n    staging_desc.SampleDesc.Count = 1;\n    staging_desc.Usage = D3D11_USAGE_STAGING;\n    staging_desc.CPUAccessFlags =\n        D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;\n\n    HRESULT hr =\n        device->CreateTexture2D(&staging_desc, nullptr, &staging_texture_);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to create staging texture. hr=\" << std::hex\n                     << hr;\n      return nullptr;\n    }\n\n    constexpr char kStagingTextureLabel[] = \"SharedImageD3D_StagingTexture\";\n    // Add debug label to the long lived texture.\n    /* cspell:disable-next-line */\n    staging_texture_->SetPrivateData(WKPDID_D3DDebugObjectName,\n                                     strlen(kStagingTextureLabel),\n                                     kStagingTextureLabel);\n  }\n  return staging_texture_.Get();\n}\n\nIDXGIKeyedMutex* D3DTextureImageBacking::GetOrCreateKeyedMutex() {\n  if (!HasKeyedMutex()) {\n    return nullptr;\n  }\n  if (!keyed_mutex_) {\n    if (FAILED(d3d11_texture_.As(&keyed_mutex_))) {\n      FML_LOG(ERROR) << \"Failed to get keyed mutex.\";\n    }\n  }\n  return keyed_mutex_.Get();\n}\n\nbool D3DTextureImageBacking::IsSupported() {\n#ifdef CLAY_FORCE_D3D9\n  return false;\n#else\n  return D3DTextureFactory::Instance().IsSupported();\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/d3d_texture_image_backing.h",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_D3D_TEXTURE_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_D3D_TEXTURE_IMAGE_BACKING_H_\n\n#include <d3d11.h>\n#include <wrl/client.h>\n\n#include <memory>\n#include <mutex>\n#include <optional>\n\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nclass SharedImageRepresentation;\n\nclass D3DTextureImageBacking final : public SharedImageBacking {\n public:\n  D3DTextureImageBacking(PixelFormat pixel_format, skity::Vec2 size,\n                         std::optional<GraphicsMemoryHandle> gfx_handle = {});\n\n  ~D3DTextureImageBacking() override;\n\n  bool HasKeyedMutex() const {\n    return (d3d11_texture_desc_.MiscFlags &\n            D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX) != 0;\n  }\n\n  bool OpenForDevice(ID3D11Device* device, ID3D11Texture2D** out_texture,\n                     IDXGIKeyedMutex** out_keyed_mutex) const;\n\n  BackingType GetType() const override;\n  GraphicsMemoryHandle GetGFXHandle() const override;\n\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n#ifdef ENABLE_SKITY\n  virtual fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      skity::GPUContext* skity_context) override;\n#else\n  bool ReadbackToMemory(const SkPixmap* pixmaps, uint32_t planes) override;\n  virtual fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n#endif  // ENABLE_SKITY\n\n  static bool IsSupported();\n\n private:\n  HANDLE shared_handle_ = nullptr;\n  bool owned_nt_handle_ = false;\n\n  ID3D11Texture2D* GetOrCreateStagingTexture(ID3D11Device* device);\n  IDXGIKeyedMutex* GetOrCreateKeyedMutex();\n\n  // The texture and device should never be used outside the class.\n  // User MUST use OpenSharedResource on its own D3D11Device\n  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture_;\n  D3D11_TEXTURE2D_DESC d3d11_texture_desc_;\n  // Staging texture used for copy to/from shared memory GMB.\n  Microsoft::WRL::ComPtr<ID3D11Texture2D> staging_texture_;\n  // Keyed mutex to for usage of staging texture read/write\n  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_D3D_TEXTURE_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/eagl_image_representation.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_EAGL_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_EAGL_IMAGE_REPRESENTATION_H_\n\n#import <CoreVideo/CoreVideo.h>\n#import <OpenGLES/EAGL.h>\n\n#include <memory>\n#include <optional>\n\n#include \"clay/fml/platform/darwin/cf_utils.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass CVPixelBufferImageBacking;\n\nclass EAGLStorageManager : public RepresentationStorageManager {\n public:\n  explicit EAGLStorageManager(EAGLContext* gl_context);\n  ~EAGLStorageManager() override;\n\n  CVOpenGLESTextureRef CreateTextureFromStorage(CVPixelBufferRef pixel_buffer);\n  void FlushStorageRecycle();\n\n private:\n  fml::CFRef<CVOpenGLESTextureCacheRef> eagl_texture_cache_;\n};\n\nclass EAGLImageRepresentation final : public GLImageRepresentation {\n public:\n  explicit EAGLImageRepresentation(\n      fml::RefPtr<CVPixelBufferImageBacking> backing);\n  ~EAGLImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  fml::RefPtr<RepresentationStorageManager> GetTextureManager() const override;\n  void SetTextureManager(fml::RefPtr<RepresentationStorageManager>) override;\n\n private:\n  std::optional<TextureInfo> GetTexImage() override;\n  bool ReleaseTexImage() override;\n  std::optional<FramebufferInfo> BindFrameBuffer() override;\n  bool UnbindFrameBuffer() override;\n\n  bool GetTexImageFromCVPixelBuffer();\n\n  EAGLContext* gl_context_ = nil;\n  uint32_t texture_id_ = 0;\n  uint32_t fbo_id_ = 0;\n  uint32_t internal_format_ = 0;\n  fml::RefPtr<EAGLStorageManager> storage_manager_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_EAGL_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/eagl_image_representation.mm",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/eagl_image_representation.h\"\n\n#import <CoreVideo/CoreVideo.h>\n#import <OpenGLES/ES2/glext.h>\n#include <OpenGLES/ES3/gl.h>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/common/graphics/gl/scoped_framebuffer_binder.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/cv_pixelbuffer_image_backing.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n\nnamespace clay {\n\nEAGLStorageManager::EAGLStorageManager(EAGLContext* gl_context) {\n  CVOpenGLESTextureCacheRef out_texture_cache;\n\n  if (CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, nil, gl_context, nil, &out_texture_cache) !=\n      kCVReturnSuccess) {\n    FML_LOG(ERROR) << \"Failed to create CVOpenGLESTextureCache.\";\n    return;\n  }\n  eagl_texture_cache_ = out_texture_cache;\n}\n\nEAGLStorageManager::~EAGLStorageManager() { FlushStorageRecycle(); }\n\nCVOpenGLESTextureRef EAGLStorageManager::CreateTextureFromStorage(CVPixelBufferRef pixel_buffer) {\n  if (!eagl_texture_cache_) {\n    FML_LOG(ERROR) << \"No available CVOpenGLESTextureCacheRef to create texture\";\n    return nil;\n  }\n  CVOpenGLESTextureRef out_texture = nil;\n  size_t width = CVPixelBufferGetWidthOfPlane(pixel_buffer, 0);\n  size_t height = CVPixelBufferGetHeightOfPlane(pixel_buffer, 0);\n  if (CVOpenGLESTextureCacheCreateTextureFromImage(\n          kCFAllocatorDefault, eagl_texture_cache_, pixel_buffer, nullptr, GL_TEXTURE_2D, GL_RGBA,\n          (GLsizei)width, (GLsizei)height, GL_BGRA, GL_UNSIGNED_BYTE, 0,\n          &out_texture) != kCVReturnSuccess) {\n    FML_LOG(ERROR) << \"Failed to create CVOpenGLTexture from CVPixelBuffer.\";\n    return nil;\n  }\n  return out_texture;\n}\n\nvoid EAGLStorageManager::FlushStorageRecycle() {\n  if (eagl_texture_cache_) {\n    CVOpenGLESTextureCacheFlush(eagl_texture_cache_, 0);\n  }\n}\n\nEAGLImageRepresentation::EAGLImageRepresentation(fml::RefPtr<CVPixelBufferImageBacking> backing)\n    : GLImageRepresentation(backing), gl_context_([EAGLContext currentContext]) {\n  if (gl_context_ == nil) {\n    FML_LOG(ERROR) << \"Failed to get current context\";\n  }\n}\n\nEAGLImageRepresentation::~EAGLImageRepresentation() {\n  if (fbo_id_ != 0) {\n    UnbindFrameBuffer();\n  }\n  if (texture_id_ != 0) {\n    ReleaseTexImage();\n  }\n}\n\nImageRepresentationType EAGLImageRepresentation::GetType() const {\n  return ImageRepresentationType::kEAGL;\n}\n\nstd::optional<GLImageRepresentation::TextureInfo> EAGLImageRepresentation::GetTexImage() {\n  if (texture_id_ == 0) {\n    switch (GLImageRepresentation::GetBacking()->GetType()) {\n      case SharedImageBacking::BackingType::kCVPixelBuffer:\n        if (!GetTexImageFromCVPixelBuffer()) {\n          return {};\n        }\n        break;\n      default:\n        FML_LOG(ERROR) << \"Invalid shared image backing type for EAGL09ImageRepr.\";\n        return {};\n    }\n  }\n\n  return TextureInfo{\n      .target = GL_TEXTURE_2D, .name = texture_id_, .format = internal_format_, .size = GetSize()};\n}\n\nbool EAGLImageRepresentation::GetTexImageFromCVPixelBuffer() {\n  if (!storage_manager_) {\n    FML_LOG(ERROR) << \"No available EAGLStorageManager to create texture\";\n    return false;\n  }\n  storage_manager_->FlushStorageRecycle();\n  fml::CFRef<CVOpenGLESTextureRef> out_texture = storage_manager_->CreateTextureFromStorage(\n      static_cast<CVPixelBufferRef>(GetBacking()->GetGFXHandle()));\n  if (!out_texture) {\n    return false;\n  }\n  GLuint texture;\n  texture = CVOpenGLESTextureGetName(out_texture);\n  internal_format_ = GL_RGBA;\n  texture_id_ = texture;\n  return true;\n}\n\nbool EAGLImageRepresentation::ReleaseTexImage() {\n  if (texture_id_ == 0) {\n    return false;\n  }\n  texture_id_ = 0;\n\n  // The texture is actually owned by the cache so there is no need to explicitly call\n  // 'glDeleteTexture'. Just flush cache.\n  if (storage_manager_) {\n    storage_manager_->FlushStorageRecycle();\n  }\n  return true;\n}\n\nstd::optional<GLImageRepresentation::FramebufferInfo> EAGLImageRepresentation::BindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    auto texture_info = GetTexImage();\n    if (!texture_info) {\n      return {};\n    }\n\n    GLuint fbo_id;\n    glGenFramebuffers(1, &fbo_id);\n    clay::ScopedFramebufferBinder scoped_framebuffer_binder(GL_FRAMEBUFFER, fbo_id);\n    clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D, texture_info->name);\n\n    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_info->name,\n                           0);\n#ifndef NDEBUG\n    GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n#else\n    GLenum framebuffer_status = GL_FRAMEBUFFER_COMPLETE;\n#endif\n\n    if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {\n      FML_LOG(ERROR) << \"Failed to create frame buffer\";\n      glDeleteFramebuffers(1, &fbo_id);\n      return {};\n    }\n\n    fbo_id_ = fbo_id;\n  }\n\n  return FramebufferInfo{.target = GL_TEXTURE_2D, .name = fbo_id_};\n}\n\nbool EAGLImageRepresentation::UnbindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    return false;\n  }\n  glDeleteFramebuffers(1, &fbo_id_);\n  fbo_id_ = 0;\n\n  return true;\n}\n\n// Note that EAGL fence sync requires producer and consumer\n// in the same shared group\nclass EAGLFenceSync final : public FenceSync {\n public:\n  EAGLFenceSync() {\n    fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);\n    GLenum error = glGetError();\n    if (!fence_ || error != GL_NO_ERROR) {\n      FML_LOG(ERROR) << \"Failed to create fence sync in EAGL env, glGetError: \" << error;\n    }\n    FML_DCHECK(glIsSync(fence_));\n    glFlush();\n  }\n\n  ~EAGLFenceSync() override {\n    FML_DCHECK(glIsSync(fence_));\n    glDeleteSync(fence_);\n    GLenum error = glGetError();\n    if (error != GL_NO_ERROR) {\n      FML_LOG(ERROR) << \"Failed to delete fence sync in EAGL env, glGetError: \" << error;\n    }\n  }\n\n  bool ClientWait() override {\n    FML_DCHECK(glIsSync(fence_));\n    GLenum result = glClientWaitSync(fence_, 0, GL_TIMEOUT_IGNORED);\n    GLenum error = glGetError();\n    if (error != GL_NO_ERROR) {\n      FML_LOG(ERROR) << \"Failed to glClientWaitSync in EAGL env, glGetError: \" << error;\n    }\n    return result == GL_CONDITION_SATISFIED || result == GL_ALREADY_SIGNALED;\n  }\n\n  Type GetType() const override { return Type::kEAGL; }\n\n  void ServerWait() {\n    FML_DCHECK(glIsSync(fence_));\n    glWaitSync(fence_, 0, GL_TIMEOUT_IGNORED);\n    GLenum gl_error = glGetError();\n    if (gl_error != GL_NO_ERROR) {\n      FML_LOG(ERROR) << \"Failed to glWaitSync in EAGL env, glGetError: \" << gl_error;\n    }\n  }\n\n private:\n  GLsync fence_;\n};\n\nvoid EAGLImageRepresentation::ConsumeFence(std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  if (fence_sync->GetType() == FenceSync::Type::kEAGL) {\n    EAGLFenceSync* eagl_fence_sync = static_cast<EAGLFenceSync*>(fence_sync.get());\n    eagl_fence_sync->ServerWait();\n    return;\n  }\n  fence_sync->ClientWait();\n}\n\nclass DummyFenceSync final : public FenceSync {\n  bool ClientWait() override { return true; }\n\n  Type GetType() const override { return Type::kClientWaitOnly; }\n};\n\nstd::unique_ptr<FenceSync> EAGLImageRepresentation::ProduceFence() {\n  // It seems that glFlush is enough for gl<->gl\n  glFlush();\n  // TODO(youfeng): use EAGLFenceSync if consumer and producer are in the same\n  // group\n  return std::make_unique<DummyFenceSync>();\n}\n\nfml::RefPtr<RepresentationStorageManager> EAGLImageRepresentation::GetTextureManager() const {\n  return fml::MakeRefCounted<EAGLStorageManager>(gl_context_);\n}\n\nvoid EAGLImageRepresentation::SetTextureManager(\n    fml::RefPtr<RepresentationStorageManager> storage_manager) {\n  storage_manager_ = fml::Ref(static_cast<EAGLStorageManager*>(storage_manager.get()));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/egl_image_backing.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/egl_image_backing.h\"\n\n#include <GLES/gl.h>\n#include <GLES/glext.h>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#include \"clay/gfx/shared_image/android_egl_image_representation.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/public/clay.h\"\nnamespace clay {\n\nEGLImageBacking::EGLImageBacking(PixelFormat pixel_format, skity::Vec2 size,\n                                 std::optional<GraphicsMemoryHandle> gfx_handle)\n    : SharedImageBacking(pixel_format, size) {\n  egl_display_ = eglGetCurrentDisplay();\n  egl_context_ = eglGetCurrentContext();\n\n  FML_DCHECK(EGL_NO_CONTEXT != egl_context_);\n  FML_DCHECK(EGL_NO_DISPLAY != egl_display_);\n\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  fml::RefPtr<fml::TaskRunner> task_runner =\n      fml::MessageLoop::GetCurrent().GetTaskRunner();\n  FML_DCHECK(task_runner);\n  task_runner_ = task_runner;\n\n  [[maybe_unused]] const char* extensions =\n      eglQueryString(egl_display_, EGL_EXTENSIONS);\n  FML_DCHECK(strstr(extensions, \"EGL_KHR_image\") ||\n             strstr(extensions, \"EGL_KHR_gl_texture_2D_image\") ||\n             strstr(extensions, \"GL_OES_EGL_image\"));\n\n  GLuint texture;\n  glGenTextures(1, &texture);\n  clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D, texture);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA,\n               GL_UNSIGNED_BYTE, nullptr);\n\n  const EGLint egl_attrib_list[] = {\n      EGL_GL_TEXTURE_LEVEL_KHR, 0, EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE};\n  EGLClientBuffer egl_buffer = reinterpret_cast<EGLClientBuffer>(texture);\n  EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR;\n\n  egl_image_ = eglCreateImageKHR(egl_display_, egl_context_, egl_target,\n                                 egl_buffer, egl_attrib_list);\n\n  if (egl_image_ == EGL_NO_IMAGE_KHR) {\n    FML_LOG(ERROR) << \"eglCreateImageKHR for cross-thread sharing failed: \"\n                   << eglGetError();\n    glDeleteTextures(1, &texture);\n    return;\n  }\n  texture_id_ = texture;\n\n#ifndef NDEBUG\n  GLint max_texture_size = 0;\n  glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);\n  if (size.x > max_texture_size || size.y > max_texture_size) {\n    FML_LOG(ERROR) << \"texture size is too large, supported max size is: \"\n                   << max_texture_size;\n  }\n#endif\n}\n\nSharedImageBacking::BackingType EGLImageBacking::GetType() const {\n  return BackingType::kEGLImage;\n}\n\nvoid EGLImageBacking::BindToTexture(GLenum target) {\n  FML_DCHECK(egl_image_ != EGL_NO_IMAGE_KHR);\n  glEGLImageTargetTexture2DOES(target, egl_image_);\n  FML_DCHECK(static_cast<EGLint>(EGL_SUCCESS) == eglGetError());\n}\n\nfml::RefPtr<SharedImageRepresentation> EGLImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  ClaySharedImageRepresentationType type = config->type;\n  switch (type) {\n    case kClaySharedImageRepresentationTypeGL: {\n      return fml::MakeRefCounted<NativeBufferEGLImageRepresentation>(\n          fml::Ref(this));\n    }\n    default: {\n      // NOT SUPPORTED.\n      FML_LOG(ERROR)\n          << \"Unable to call EGLImageBacking::CreateRepresentation with type: \"\n          << static_cast<uint32_t>(type);\n      return nullptr;\n    }\n  }\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<SkiaImageRepresentation> EGLImageBacking::CreateSkiaRepresentation(\n    GrDirectContext* gr_context) {\n  switch (gr_context->backend()) {\n    case GrBackendApi::kOpenGL: {\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context, fml::MakeRefCounted<NativeBufferEGLImageRepresentation>(\n                          fml::Ref(this)));\n    }\n    default: {\n      FML_LOG(ERROR) << \"Unable to call \"\n                        \"EGLImageBacking::CreateSkiaRepresentation with \"\n                        \"backend: \"\n                     << static_cast<uint32_t>(gr_context->backend());\n      return nullptr;\n    }\n  }\n}\n#else\nfml::RefPtr<SkityImageRepresentation>\nEGLImageBacking::CreateSkityRepresentation(skity::GPUContext* skity_context) {\n  switch (skity_context->GetBackendType()) {\n    case skity::GPUBackendType::kOpenGL: {\n      return fml::MakeRefCounted<SkityGLImageRepresentation>(\n          skity_context,\n          fml::MakeRefCounted<NativeBufferEGLImageRepresentation>(\n              fml::Ref(this)));\n    }\n    default: {\n      FML_LOG(ERROR) << \"Unable to call \"\n                        \"EGLImageBacking::CreateSkityRepresentation with \"\n                        \"backend: \"\n                     << static_cast<uint32_t>(skity_context->GetBackendType());\n      return nullptr;\n    }\n  }\n}\n#endif  // ENABLE_SKITY\n\nEGLImageBacking::~EGLImageBacking() {\n  if (egl_image_ == EGL_NO_IMAGE_KHR) {\n    return;\n  }\n  if (task_runner_) {\n    fml::TaskRunner::RunNowOrPostTask(\n        task_runner_, [egl_context = egl_context_, egl_display = egl_display_,\n                       egl_image = egl_image_, texture_id = texture_id_] {\n          FML_DCHECK(eglGetCurrentContext() == egl_context);\n          eglDestroyImageKHR(egl_display, egl_image);\n          glDeleteTextures(1, &texture_id);\n        });\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/egl_image_backing.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_EGL_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_EGL_IMAGE_BACKING_H_\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <GLES2/gl2.h>\n#include <android/hardware_buffer.h>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nclass SharedImageRepresentation;\n\nclass EGLImageBacking : public SharedImageBacking {\n public:\n  EGLImageBacking(PixelFormat pixel_format, skity::Vec2 size,\n                  std::optional<GraphicsMemoryHandle> gfx_handle = {});\n  ~EGLImageBacking() override;\n  BackingType GetType() const override;\n  GraphicsMemoryHandle GetGFXHandle() const override { return nullptr; }\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n#ifndef ENABLE_SKITY\n  fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n#else\n  fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      skity::GPUContext* skity_context) override;\n#endif  // ENABLE_SKITY\n\n  void BindToTexture(GLenum target);\n\n private:\n  EGLImageKHR egl_image_ = nullptr;\n  EGLDisplay egl_display_ = nullptr;\n  EGLContext egl_context_ = nullptr;\n\n  GLuint texture_id_ = 0;\n\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_EGL_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/epoxy_shm_image_backing.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/epoxy_shm_image_backing.h\"\n\n#include <epoxy/egl.h>\n#include <epoxy/gl.h>\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <unistd.h>\n\n#include <tuple>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/shared_image/epoxy_shm_image_representation.h\"\n#include \"clay/gfx/shared_image/linux_shm_image_representation.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/public/clay.h\"\n\nnamespace clay {\n\nnamespace {\n\nstd::tuple<void*, int> CreateShmBuffer(\n    SharedImageBacking::PixelFormat pixel_format, skity::Vec2 size) {\n  static size_t count = 0;\n  std::string shm_name =\n      \"/clay_shared_image_backing\" + std::to_string(getpid()) + \"_\" +\n      std::to_string(pthread_self()) + \"_\" + std::to_string(count++);\n  size_t shm_size = size.x * size.y * 4;\n\n  int shm_fd = shm_open(shm_name.c_str(), O_CREAT | O_RDWR, 0666);\n  ftruncate(shm_fd, shm_size);\n  shm_unlink(shm_name.c_str());\n\n  return {mmap(NULL, shm_size, PROT_WRITE, MAP_SHARED, shm_fd, 0), shm_fd};\n}\n\n}  // namespace\n\nbool EpoxyShmImageBacking::CreateEGLImageIfNeeded() {\n  if (egl_image_ == EGL_NO_IMAGE_KHR) {\n    egl_display_ = eglGetCurrentDisplay();\n    egl_context_ = eglGetCurrentContext();\n\n    if (egl_context_ == EGL_NO_CONTEXT || egl_display_ == EGL_NO_DISPLAY) {\n      return false;\n    }\n\n    fml::MessageLoop::EnsureInitializedForCurrentThread();\n    fml::RefPtr<fml::TaskRunner> task_runner =\n        fml::MessageLoop::GetCurrent().GetTaskRunner();\n    FML_DCHECK(task_runner);\n    task_runner_ = task_runner;\n\n    [[maybe_unused]] const char* extensions =\n        eglQueryString(egl_display_, EGL_EXTENSIONS);\n    FML_DCHECK(strstr(extensions, \"EGL_KHR_image\") ||\n               strstr(extensions, \"EGL_KHR_gl_texture_2D_image\") ||\n               strstr(extensions, \"GL_OES_EGL_image\"));\n\n    GLuint texture;\n    glGenTextures(1, &texture);\n    clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D, texture);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size_.x, size_.y, 0, GL_RGBA,\n                 GL_UNSIGNED_BYTE, nullptr);\n\n    const EGLint egl_attrib_list[] = {EGL_GL_TEXTURE_LEVEL_KHR, 0,\n                                      EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,\n                                      EGL_NONE};\n    EGLClientBuffer egl_buffer = reinterpret_cast<EGLClientBuffer>(texture);\n    EGLenum egl_target = EGL_GL_TEXTURE_2D_KHR;\n\n    egl_image_ = eglCreateImageKHR(egl_display_, egl_context_, egl_target,\n                                   egl_buffer, egl_attrib_list);\n\n    if (egl_image_ == EGL_NO_IMAGE_KHR) {\n      FML_LOG(ERROR) << \"eglCreateImageKHR for cross-thread sharing failed: \"\n                     << eglGetError();\n      glDeleteTextures(1, &texture);\n      return false;\n    }\n    texture_id_ = texture;\n\n#ifndef NDEBUG\n    GLint max_texture_size = 0;\n    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);\n    if (size_.x > max_texture_size || size_.y > max_texture_size) {\n      FML_LOG(ERROR) << \"texture size is too large, supported max size is: \"\n                     << max_texture_size;\n    }\n#endif\n  }\n  return true;\n}\n\nEpoxyShmImageBacking::EpoxyShmImageBacking(\n    PixelFormat pixel_format, skity::Vec2 size,\n    std::optional<GraphicsMemoryHandle> gfx_handle)\n    : SharedImageBacking(pixel_format, size) {\n  if (gfx_handle) {\n    shm_buffer_ = gfx_handle.value();\n  } else {\n    std::tuple<void*, int> result = CreateShmBuffer(pixel_format, size);\n    shm_buffer_ = std::get<0>(result);\n    shm_fd_ = std::get<1>(result);\n  }\n}\n\nSharedImageBacking::BackingType EpoxyShmImageBacking::GetType() const {\n  return BackingType::kShmImage;\n}\n\nvoid EpoxyShmImageBacking::BindToTexture(EGLenum target) {\n  CreateEGLImageIfNeeded();\n  glEGLImageTargetTexture2DOES(target, egl_image_);\n  FML_DCHECK(static_cast<EGLint>(EGL_SUCCESS) == eglGetError());\n}\n\nfml::RefPtr<SharedImageRepresentation>\nEpoxyShmImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  ClaySharedImageRepresentationType type = config->type;\n  switch (type) {\n    case kClaySharedImageRepresentationTypeShm: {\n      return fml::MakeRefCounted<LinuxShmImageRepresentation>(fml::Ref(this));\n    }\n    case kClaySharedImageRepresentationTypeGL: {\n      return fml::MakeRefCounted<EpoxyShmImageRepresentation>(fml::Ref(this));\n    }\n    default: {\n      // NOT SUPPORTED.\n      FML_LOG(ERROR) << \"Unable to call \"\n                        \"EpoxyShmImageBacking::CreateRepresentation with type: \"\n                     << static_cast<uint32_t>(type);\n      return nullptr;\n    }\n  }\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<SkiaImageRepresentation>\nEpoxyShmImageBacking::CreateSkiaRepresentation(GrDirectContext* gr_context) {\n  switch (gr_context->backend()) {\n    case GrBackendApi::kOpenGL: {\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context,\n          fml::MakeRefCounted<EpoxyShmImageRepresentation>(fml::Ref(this)));\n    }\n    default: {\n      FML_LOG(ERROR) << \"Unable to call \"\n                        \"EpoxyShmImageBacking::CreateSkiaRepresentation with \"\n                        \"backend: \"\n                     << static_cast<uint32_t>(gr_context->backend());\n      return nullptr;\n    }\n  }\n}\n#else\nfml::RefPtr<SkityImageRepresentation>\nEpoxyShmImageBacking::CreateSkityRepresentation(\n    skity::GPUContext* skity_context) {\n  switch (skity_context->GetBackendType()) {\n    case skity::GPUBackendType::kOpenGL: {\n      return fml::MakeRefCounted<SkityGLImageRepresentation>(\n          skity_context,\n          fml::MakeRefCounted<EpoxyShmImageRepresentation>(fml::Ref(this)));\n    }\n    default: {\n      FML_LOG(ERROR) << \"Unable to call \"\n                        \"EpoxyShmImageBacking::CreateSkityRepresentation with \"\n                        \"backend: \"\n                     << static_cast<uint32_t>(skity_context->GetBackendType());\n      return nullptr;\n    }\n  }\n}\n#endif  // ENABLE_SKITY\n\nvoid EpoxyShmImageBacking::CopyPixelsToTexture() {\n  if (CreateEGLImageIfNeeded()) {\n    clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D, texture_id_);\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size_.x, size_.y, 0, GL_RGBA,\n                 GL_UNSIGNED_BYTE, shm_buffer_);\n  }\n}\n\nvoid EpoxyShmImageBacking::CopyPixelsToShm() {\n  glFinish();\n  glReadPixels(0, 0, size_.x, size_.y, GL_RGBA, GL_UNSIGNED_BYTE, shm_buffer_);\n}\n\nint EpoxyShmImageBacking::GetShmFd() const { return shm_fd_; }\n\nEpoxyShmImageBacking::~EpoxyShmImageBacking() {\n  if (egl_image_ == EGL_NO_IMAGE_KHR) {\n    return;\n  }\n  if (task_runner_) {\n    fml::TaskRunner::RunNowOrPostTask(\n        task_runner_, [task_runner = task_runner_, egl_display = egl_display_,\n                       egl_image = egl_image_, texture_id = texture_id_,\n                       shm_buffer = shm_buffer_, shm_size = size_,\n                       shm_fd = shm_fd_]() mutable {\n          eglDestroyImageKHR(egl_display, egl_image);\n          glDeleteTextures(1, &texture_id);\n\n          if (munmap(shm_buffer, shm_size.x * shm_size.y * 4) == -1) {\n            FML_LOG(ERROR) << \"EpoxyShmImageBacking munmap failed\";\n            return;\n          }\n          close(shm_fd);\n          shm_buffer = nullptr;\n          shm_fd = 0;\n        });\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/epoxy_shm_image_backing.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_EPOXY_SHM_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_EPOXY_SHM_IMAGE_BACKING_H_\n\n#include <epoxy/egl.h>\n\n#include <string>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nclass SharedImageRepresentation;\n\nclass EpoxyShmImageBacking : public SharedImageBacking {\n public:\n  EpoxyShmImageBacking(PixelFormat pixel_format, skity::Vec2 size,\n                       std::optional<GraphicsMemoryHandle> gfx_handle = {});\n  ~EpoxyShmImageBacking() override;\n  BackingType GetType() const override;\n  GraphicsMemoryHandle GetGFXHandle() const override { return shm_buffer_; }\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n#ifndef ENABLE_SKITY\n  fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n#else\n  fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      skity::GPUContext* skity_context) override;\n#endif  // ENABLE_SKITY\n\n  void BindToTexture(EGLenum target);\n\n  void CopyPixelsToTexture();\n  void CopyPixelsToShm();\n\n  int GetShmFd() const;\n\n private:\n  EGLImageKHR egl_image_ = nullptr;\n  EGLDisplay egl_display_ = nullptr;\n  EGLContext egl_context_ = nullptr;\n\n  GLuint texture_id_ = 0;\n  GraphicsMemoryHandle shm_buffer_ = nullptr;\n  int shm_fd_ = 0;\n\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n\n  bool CreateEGLImageIfNeeded();\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_EPOXY_SHM_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/epoxy_shm_image_representation.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/epoxy_shm_image_representation.h\"\n\n#include <epoxy/egl.h>\n#include <epoxy/gl.h>\n\n#include <utility>\n\n#include \"clay/common/graphics/gl/scoped_framebuffer_binder.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/epoxy_shm_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nEpoxyShmImageRepresentation::EpoxyShmImageRepresentation(\n    fml::RefPtr<SharedImageBacking> backing)\n    : GLImageRepresentation(std::move(backing)) {}\n\nEpoxyShmImageRepresentation::~EpoxyShmImageRepresentation() {\n  UnbindFrameBuffer();\n  ReleaseTexImage();\n}\n\nImageRepresentationType EpoxyShmImageRepresentation::GetType() const {\n  return ImageRepresentationType::kGL;\n}\n\nvoid EpoxyShmImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  fence_sync->ClientWait();\n}\n\nstd::unique_ptr<FenceSync> EpoxyShmImageRepresentation::ProduceFence() {\n  glFinish();\n  return nullptr;\n}\n\nstd::optional<GLImageRepresentation::TextureInfo>\nEpoxyShmImageRepresentation::GetTexImage() {\n  if (texture_id_ == 0) {\n    SharedImageBacking* backing = GetBacking();\n    if (!backing) {\n      return std::nullopt;\n    }\n\n    GLuint texture;\n    glGenTextures(1, &texture);\n    clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D, texture);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\n    switch (backing->GetType()) {\n      case SharedImageBacking::BackingType::kShmImage: {\n        static_cast<EpoxyShmImageBacking*>(backing)->BindToTexture(\n            GL_TEXTURE_2D);\n        break;\n      }\n      default: {\n        // NOT SUPPORTED.\n        FML_UNREACHABLE();\n      }\n    }\n    texture_id_ = texture;\n  }\n  return TextureInfo{.target = GL_TEXTURE_2D,\n                     .name = texture_id_,\n                     .format = GL_RGBA8,\n                     .size = GetSize()};\n}\n\nbool EpoxyShmImageRepresentation::ReleaseTexImage() {\n  if (texture_id_ == 0) {\n    return false;\n  }\n  glDeleteTextures(1, &texture_id_);\n  texture_id_ = 0;\n  return true;\n}\n\nstd::optional<GLImageRepresentation::FramebufferInfo>\nEpoxyShmImageRepresentation::BindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    auto texture_info = GetTexImage();\n    GLuint backend_texture = texture_info->name;\n    GLuint fbo_id;\n    glGenFramebuffers(1, &fbo_id);\n    clay::ScopedTextureBinder scoped_texture_binder(GL_TEXTURE_2D,\n                                                    backend_texture);\n    clay::ScopedFramebufferBinder scoped_framebuffer_binder(GL_FRAMEBUFFER,\n                                                            fbo_id);\n\n    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,\n                           backend_texture, 0);\n#ifndef NDEBUG\n    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n#else\n    GLenum status = GL_FRAMEBUFFER_COMPLETE;\n#endif\n    if (status != GL_FRAMEBUFFER_COMPLETE) {\n      FML_LOG(ERROR) << \"Failed to create FBO, error: \" << status;\n      glDeleteFramebuffers(1, &fbo_id);\n      return std::nullopt;\n    }\n    fbo_id_ = fbo_id;\n  }\n  return FramebufferInfo{.target = GL_FRAMEBUFFER, .name = fbo_id_};\n}\n\nbool EpoxyShmImageRepresentation::UnbindFrameBuffer() {\n  if (fbo_id_ == 0) {\n    return false;\n  }\n  glDeleteFramebuffers(1, &fbo_id_);\n  fbo_id_ = 0;\n  return true;\n}\n\nbool EpoxyShmImageRepresentation::BeginRead(ClaySharedImageReadResult* out) {\n  static_cast<EpoxyShmImageBacking*>(GetBacking())->CopyPixelsToTexture();\n  return GLImageRepresentation::BeginRead(out);\n}\n\nbool EpoxyShmImageRepresentation::EndWrite() {\n  clay::ScopedFramebufferBinder scoped_framebuffer_binder(GL_FRAMEBUFFER,\n                                                          fbo_id_);\n  static_cast<EpoxyShmImageBacking*>(GetBacking())->CopyPixelsToShm();\n  return GLImageRepresentation::EndWrite();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/epoxy_shm_image_representation.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_EPOXY_SHM_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_EPOXY_SHM_IMAGE_REPRESENTATION_H_\n\n#include <memory>\n#include <optional>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass EpoxyShmImageRepresentation : public GLImageRepresentation {\n public:\n  explicit EpoxyShmImageRepresentation(fml::RefPtr<SharedImageBacking> backing);\n  ~EpoxyShmImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n  void ConsumeFence(std::unique_ptr<FenceSync>) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  bool BeginRead(ClaySharedImageReadResult* out) override;\n  bool EndWrite() override;\n\n private:\n  std::optional<TextureInfo> GetTexImage() override;\n  bool ReleaseTexImage() override;\n  std::optional<FramebufferInfo> BindFrameBuffer() override;\n  bool UnbindFrameBuffer() override;\n\n  uint32_t texture_id_ = 0;\n  uint32_t fbo_id_ = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_EPOXY_SHM_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/fence_sync.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_FENCE_SYNC_H_\n#define CLAY_GFX_SHARED_IMAGE_FENCE_SYNC_H_\n\n#include \"base/include/fml/macros.h\"\n\nnamespace clay {\n\nclass FenceSync {\n public:\n  enum class Type {\n    kCGL,\n    kEAGL,\n    kEGL,\n    kANGLE,\n    kMetalEvent,\n    kClientWaitOnly,\n    kVulkan,\n  };\n\n  FenceSync() = default;\n\n  virtual ~FenceSync() = default;\n\n  virtual bool ClientWait() = 0;\n\n  virtual Type GetType() const = 0;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(FenceSync);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_FENCE_SYNC_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/iosurface_image_backing.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_IOSURFACE_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_IOSURFACE_IMAGE_BACKING_H_\n\n#import <IOSurface/IOSurfaceRef.h>\n\n#include <memory>\n#include <optional>\n\n#include \"clay/fml/platform/darwin/cf_utils.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nclass SharedImageRepresentation;\n\nclass API_AVAILABLE(macos(10.6), ios(11.0), tvos(11.0)) IOSurfaceImageBacking\n    : public SharedImageBacking {\n public:\n  IOSurfaceImageBacking(PixelFormat pixel_format, skity::Vec2 size,\n                        std::optional<GraphicsMemoryHandle> gfx_handle = {});\n\n  BackingType GetType() const override;\n\n  GraphicsMemoryHandle GetGFXHandle() const override;\n\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n\n#ifndef ENABLE_SKITY\n  fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrContext* gr_context) override;\n\n  bool ReadbackToMemory(const SkPixmap* pixmaps, uint32_t planes) override;\n#else\n  fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      GrContext* skity_context) override;\n#endif  // ENABLE_SKITY\n\n private:\n  static fml::CFRef<IOSurfaceRef> CreateIOSurface(\n      SharedImageBacking::PixelFormat pixel_format, skity::Vec2 size);\n\n  fml::CFRef<IOSurfaceRef> io_surface_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_IOSURFACE_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/iosurface_image_backing.mm",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/iosurface_image_backing.h\"\n\n#import <Foundation/Foundation.h>\n\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/mtl_image_representation.h\"\n#include \"clay/gfx/shared_image/utils/image_utils.h\"\n\n#if ENABLE_SKITY\n#include \"clay/gfx/shared_image/skity_mtl_image_representation.h\"\n#include \"skity/gpu/gpu_context.hpp\"\n#else\n#if OS_MAC\n#include \"clay/gfx/shared_image/cgl_image_representation.h\"\n#endif\n\n#if SKIA_ENABLE_GL\n#include \"clay/gfx/shared_image/skia_gl_image_representation.h\"\n#endif\n#if SKIA_ENABLE_METAL\n#include \"clay/gfx/shared_image/skia_mtl_image_representation.h\"\n#endif\n#endif\n\nstatic_assert(__has_feature(objc_arc), \"ARC must be enabled.\");\n\nnamespace clay {\n\nnamespace {\n\nstruct API_AVAILABLE(macos(10.6), ios(11.0), tvos(11.0)) ScopedIOSurfaceLock {\n  ScopedIOSurfaceLock(IOSurfaceRef iosurface, IOSurfaceLockOptions options)\n      : io_surface_(iosurface), options_(options) {\n    kern_return_t r = IOSurfaceLock(io_surface_, options_, nullptr);\n    FML_CHECK(KERN_SUCCESS == r);\n  }\n\n  ~ScopedIOSurfaceLock() {\n    kern_return_t r = IOSurfaceUnlock(io_surface_, options_, nullptr);\n    FML_CHECK(KERN_SUCCESS == r);\n  }\n\n  ScopedIOSurfaceLock(const ScopedIOSurfaceLock&) = delete;\n  ScopedIOSurfaceLock& operator=(const ScopedIOSurfaceLock&) = delete;\n\n private:\n  IOSurfaceRef io_surface_;\n  IOSurfaceLockOptions options_;\n};\n}  // namespace\n\nfml::CFRef<IOSurfaceRef> IOSurfaceImageBacking::CreateIOSurface(\n    SharedImageBacking::PixelFormat pixel_format, skity::Vec2 size) {\n  // TODO support other formats\n  unsigned pixelFormat = 'BGRA';\n  unsigned bytesPerElement = 4;\n\n  size_t bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.x * bytesPerElement);\n  size_t totalBytes = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.y * bytesPerRow);\n  NSDictionary* options = @{\n    (id)kIOSurfaceWidth : @(size.x),\n    (id)kIOSurfaceHeight : @(size.y),\n    (id)kIOSurfacePixelFormat : @(pixelFormat),\n    (id)kIOSurfaceBytesPerElement : @(bytesPerElement),\n    (id)kIOSurfaceBytesPerRow : @(bytesPerRow),\n    (id)kIOSurfaceAllocSize : @(totalBytes),\n    (id)kIOSurfaceIsGlobal : @YES,\n  };\n\n  return IOSurfaceCreate((CFDictionaryRef)options);\n}\n\nIOSurfaceImageBacking::IOSurfaceImageBacking(PixelFormat pixel_format, skity::Vec2 size,\n                                             std::optional<GraphicsMemoryHandle> gfx_handle)\n    : SharedImageBacking(pixel_format, size) {\n  if (gfx_handle) {\n    io_surface_.Reset((IOSurfaceRef)CFRetain(*gfx_handle));\n  } else {\n    io_surface_ = CreateIOSurface(pixel_format, size);\n  }\n}\n\nSharedImageBacking::BackingType IOSurfaceImageBacking::GetType() const {\n  return BackingType::kIOSurface;\n}\n\nGraphicsMemoryHandle IOSurfaceImageBacking::GetGFXHandle() const { return io_surface_; }\n\nfml::RefPtr<SharedImageRepresentation> IOSurfaceImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  FML_CHECK(config->struct_size == sizeof(ClaySharedImageRepresentationConfig));\n  switch (config->type) {\n#if OS_MAC && !defined(ENABLE_SKITY)\n    case kClaySharedImageRepresentationTypeGL:\n      return fml::MakeRefCounted<CGLImageRepresentation>(fml::Ref(this));\n#endif\n    case kClaySharedImageRepresentationTypeMetal:\n      FML_CHECK(config->metal_config.struct_size ==\n                sizeof(ClaySharedImageMetalRepresentationConfig));\n      return fml::MakeRefCounted<MTLImageRepresentation>(\n          (__bridge id<MTLDevice>)config->metal_config.device,\n          (__bridge id<MTLCommandQueue>)config->metal_config.command_queue, fml::Ref(this));\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR) << \"Unable to call IOSurfaceImageBacking::CreateRepresentation with type: \"\n                 << static_cast<uint32_t>(config->type);\n  return nullptr;\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<SkiaImageRepresentation> IOSurfaceImageBacking::CreateSkiaRepresentation(\n    GrDirectContext* gr_context) {\n  switch (gr_context->backend()) {\n#if SKIA_ENABLE_GL\n    case GrBackendApi::kOpenGL:\n#if OS_MAC\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context, fml::MakeRefCounted<CGLImageRepresentation>(fml::Ref(this)));\n#endif\n      break;\n#endif\n#if SKIA_ENABLE_METAL\n    case GrBackendApi::kMetal:\n      return fml::MakeRefCounted<SkiaMTLImageRepresentation>(gr_context, fml::Ref(this));\n#endif\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR) << \"Unable to call IOSurfaceImageBacking::CreateSkiaRepresentation with backend: \"\n                 << static_cast<uint32_t>(gr_context->backend());\n  return nullptr;\n}\n\nbool IOSurfaceImageBacking::ReadbackToMemory(const SkPixmap* pixmaps, uint32_t planes) {\n  ScopedIOSurfaceLock io_surface_lock(io_surface_, kIOSurfaceLockReadOnly);\n\n  for (uint32_t plane_index = 0; plane_index < planes; plane_index++) {\n    const void* io_surface_base_address = IOSurfaceGetBaseAddressOfPlane(io_surface_, plane_index);\n    FML_DCHECK(size_.x == static_cast<int32_t>(IOSurfaceGetWidthOfPlane(io_surface_, plane_index)));\n    FML_DCHECK(size_.y ==\n               static_cast<int32_t>(IOSurfaceGetHeightOfPlane(io_surface_, plane_index)));\n\n    size_t io_surface_row_bytes = IOSurfaceGetBytesPerRowOfPlane(io_surface_, plane_index);\n    size_t dst_bytes_per_row = pixmaps[plane_index].rowBytes();\n\n    const uint8_t* src_ptr = static_cast<const uint8_t*>(io_surface_base_address);\n    uint8_t* dst_ptr = static_cast<uint8_t*>(pixmaps[plane_index].writable_addr());\n\n    size_t copy_bytes = pixmaps[plane_index].info().minRowBytes();\n    FML_DCHECK(copy_bytes <= io_surface_row_bytes);\n    FML_DCHECK(copy_bytes <= dst_bytes_per_row);\n\n    CopyPlane(src_ptr, io_surface_row_bytes, dst_ptr, dst_bytes_per_row, copy_bytes, size_.y);\n  }\n\n  return true;\n}\n#else\nfml::RefPtr<SkityImageRepresentation> IOSurfaceImageBacking::CreateSkityRepresentation(\n    skity::GPUContext* skity_context) {\n  switch (skity_context->GetBackendType()) {\n    case skity::GPUBackendType::kMetal: {\n      return fml::MakeRefCounted<SkityMTLImageRepresentation>(skity_context, fml::Ref(this));\n    }\n    default: {\n      FML_LOG(ERROR) << \"Unable to call \"\n                        \"IOSurfaceImageBacking::CreateSkityRepresentation with \"\n                        \"backend: \"\n                     << static_cast<uint32_t>(skity_context->GetBackendType());\n      return nullptr;\n    }\n  }\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/linux_shm_image_representation.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/linux_shm_image_representation.h\"\n\n#include <cstring>\n#include <utility>\n\n#include \"clay/common/graphics/gl/scoped_framebuffer_binder.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/epoxy_shm_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nLinuxShmImageRepresentation::LinuxShmImageRepresentation(\n    fml::RefPtr<SharedImageBacking> backing)\n    : SharedImageRepresentation(std::move(backing)) {}\n\nLinuxShmImageRepresentation::~LinuxShmImageRepresentation() {}\n\nImageRepresentationType LinuxShmImageRepresentation::GetType() const {\n  return ImageRepresentationType::kLinuxShm;\n}\n\nvoid LinuxShmImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  fence_sync->ClientWait();\n}\n\nstd::unique_ptr<FenceSync> LinuxShmImageRepresentation::ProduceFence() {\n  return nullptr;\n}\n\nbool LinuxShmImageRepresentation::BeginRead(ClaySharedImageReadResult* out) {\n  memset(out, 0, sizeof(ClaySharedImageReadResult));\n  out->struct_size = sizeof(ClaySharedImageReadResult);\n  out->type = kClaySharedImageRepresentationTypeShm;\n  out->shm_image.struct_size = sizeof(ClaySharedMemoryImage);\n  out->shm_image.shm_fd =\n      static_cast<EpoxyShmImageBacking*>(GetBacking())->GetShmFd();\n  out->shm_image.width = GetBacking()->GetSize().x;\n  out->shm_image.height = GetBacking()->GetSize().y;\n  out->shm_image.user_data = this;\n  this->AddRef();\n  out->shm_image.destruction_callback = [](void* user_data) {\n    static_cast<LinuxShmImageRepresentation*>(user_data)->Release();\n  };\n  return true;\n}\n\nbool LinuxShmImageRepresentation::EndRead() { return true; }\n\nbool LinuxShmImageRepresentation::BeginWrite(ClaySharedImageWriteResult* out) {\n  memset(out, 0, sizeof(ClaySharedImageWriteResult));\n  out->struct_size = sizeof(ClaySharedImageWriteResult);\n  out->type = kClaySharedImageRepresentationTypeShm;\n  out->shm_image.struct_size = sizeof(ClaySharedMemoryImage);\n  out->shm_image.shm_fd =\n      static_cast<EpoxyShmImageBacking*>(GetBacking())->GetShmFd();\n  out->shm_image.width = GetBacking()->GetSize().x;\n  out->shm_image.height = GetBacking()->GetSize().y;\n  out->shm_image.user_data = this;\n  this->AddRef();\n  out->shm_image.destruction_callback = [](void* user_data) {\n    static_cast<LinuxShmImageRepresentation*>(user_data)->Release();\n  };\n  return true;\n}\n\nbool LinuxShmImageRepresentation::EndWrite() {\n  static_cast<EpoxyShmImageBacking*>(GetBacking())->CopyPixelsToShm();\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/linux_shm_image_representation.h",
    "content": "\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_LINUX_SHM_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_LINUX_SHM_IMAGE_REPRESENTATION_H_\n\n#include <memory>\n#include <optional>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass LinuxShmImageRepresentation : public SharedImageRepresentation {\n public:\n  explicit LinuxShmImageRepresentation(fml::RefPtr<SharedImageBacking> backing);\n  ~LinuxShmImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n  void ConsumeFence(std::unique_ptr<FenceSync>) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  bool BeginRead(ClaySharedImageReadResult* out) override;\n  bool EndRead() override;\n  bool BeginWrite(ClaySharedImageWriteResult* out) override;\n  bool EndWrite() override;\n\n private:\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_LINUX_SHM_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/mtl_fence_sync.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_MTL_FENCE_SYNC_H_\n#define CLAY_GFX_SHARED_IMAGE_MTL_FENCE_SYNC_H_\n\n#import <Metal/Metal.h>\n\n#include <future>\n#include <string>\n\n#include \"clay/fml/platform/darwin/scoped_nsobject.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n\nnamespace clay {\n\nclass MTLCompleteFenceSync final : public FenceSync {\n public:\n  explicit MTLCompleteFenceSync(id<MTLCommandBuffer> buffer);\n\n  bool ClientWait() override;\n\n  Type GetType() const override;\n\n  ~MTLCompleteFenceSync() override;\n\n private:\n  fml::scoped_nsprotocol<id<MTLCommandBuffer>> buffer_;\n};\n\nclass API_AVAILABLE(macos(10.14), ios(12.0),\n                    tvos(12.0)) MTLSharedEventFenceSync final\n    : public FenceSync {\n public:\n  MTLSharedEventFenceSync(id<MTLCommandBuffer> buffer, id<MTLSharedEvent> event,\n                          uint64_t value);\n  ~MTLSharedEventFenceSync() override;\n\n  bool ClientWait() override;\n\n  Type GetType() const override;\n\n  id<MTLSharedEvent> Event() const { return event_; }\n\n  uint64_t Value() const { return value_; }\n\n private:\n  fml::scoped_nsprotocol<id<MTLCommandBuffer>> buffer_;\n  fml::scoped_nsprotocol<id<MTLSharedEvent>> event_;\n  uint64_t value_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_MTL_FENCE_SYNC_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/mtl_fence_sync.mm",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/mtl_fence_sync.h\"\n\nnamespace clay {\n\nMTLCompleteFenceSync::MTLCompleteFenceSync(id<MTLCommandBuffer> buffer) : buffer_(buffer) {}\n\nbool MTLCompleteFenceSync::ClientWait() {\n  if ([buffer_ status] != MTLCommandBufferStatusCompleted) {\n    [buffer_ waitUntilCompleted];\n  }\n  return true;\n}\n\nFenceSync::Type MTLCompleteFenceSync::GetType() const { return Type::kClientWaitOnly; }\n\nMTLCompleteFenceSync::~MTLCompleteFenceSync() = default;\n\nMTLSharedEventFenceSync::MTLSharedEventFenceSync(id<MTLCommandBuffer> buffer,\n                                                 id<MTLSharedEvent> event, uint64_t value)\n    : buffer_(buffer), event_(event), value_(value) {}\n\nMTLSharedEventFenceSync::~MTLSharedEventFenceSync() = default;\n\nbool MTLSharedEventFenceSync::ClientWait() {\n  if ([buffer_ status] != MTLCommandBufferStatusCompleted) {\n    [buffer_ waitUntilCompleted];\n  }\n  return true;\n}\n\nFenceSync::Type MTLSharedEventFenceSync::GetType() const { return Type::kMetalEvent; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/mtl_image_representation.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_MTL_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_MTL_IMAGE_REPRESENTATION_H_\n\n#import <CoreVideo/CoreVideo.h>\n#import <Metal/Metal.h>\n\n#include <memory>\n\n#include \"clay/fml/platform/darwin/cf_utils.h\"\n#include \"clay/fml/platform/darwin/scoped_nsobject.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\nclass SharedImageBacking;\n\nclass MTLStorageManager : public RepresentationStorageManager {\n public:\n  explicit MTLStorageManager(id<MTLDevice> device);\n\n  ~MTLStorageManager() override;\n\n  CVMetalTextureRef CreateTextureFromStorage(CVPixelBufferRef pixel_buffer,\n                                             MTLPixelFormat pixelFormat,\n                                             size_t width, size_t height);\n\n  void FlushStorageRecycle();\n\n private:\n  fml::CFRef<CVMetalTextureCacheRef> cv_mtl_texture_cache_;\n};\n\nclass API_AVAILABLE(macos(10.6), ios(11.0),\n                    tvos(11.0)) MTLImageRepresentation final :\n\n    public SharedImageRepresentation {\n public:\n  MTLImageRepresentation(id<MTLDevice> device, id<MTLCommandQueue> queue,\n                         fml::RefPtr<SharedImageBacking> backing);\n  ~MTLImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n\n  bool BeginRead(ClaySharedImageReadResult* out) override;\n  bool EndRead() override;\n  bool BeginWrite(ClaySharedImageWriteResult* out) override;\n  bool EndWrite() override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  fml::RefPtr<RepresentationStorageManager> GetTextureManager() const override;\n  void SetTextureManager(fml::RefPtr<RepresentationStorageManager>) override;\n\n private:\n  id<MTLTexture> GetMTLTexture();\n\n  bool GetMTLTextureFromIOSurface();\n  bool GetMTLTextureFromCVPixelBuffer();\n\n  fml::scoped_nsprotocol<id<MTLDevice> > device_;\n  fml::scoped_nsprotocol<id<MTLCommandQueue> > queue_;\n  fml::scoped_nsprotocol<id> event_;  // MTLSharedEvent\n  uint64_t event_value_ = 0;\n  fml::scoped_nsprotocol<id<MTLTexture> > texture_;\n  fml::RefPtr<MTLStorageManager> storage_manager_;\n};\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_MTL_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/mtl_image_representation.mm",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/mtl_image_representation.h\"\n\n#import <CoreVideo/CoreVideo.h>\n#import <Metal/Metal.h>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/cv_pixelbuffer_image_backing.h\"\n#include \"clay/gfx/shared_image/iosurface_image_backing.h\"\n#include \"clay/gfx/shared_image/mtl_fence_sync.h\"\n\nstatic_assert(__has_feature(objc_arc), \"ARC must be enabled.\");\n\nnamespace clay {\n\nMTLStorageManager::MTLStorageManager(id<MTLDevice> device) {\n  CVMetalTextureCacheRef out_texture_cache = nil;\n  if (CVMetalTextureCacheCreate(kCFAllocatorDefault, (__bridge CFDictionaryRef)(@{\n                                  (id)kCVMetalTextureCacheMaximumTextureAgeKey : @0,\n                                }),\n                                device, nil, &out_texture_cache) != kCVReturnSuccess) {\n    FML_LOG(ERROR) << \"Failed to create CVMetalTextureCache.\";\n    return;\n  }\n  cv_mtl_texture_cache_ = out_texture_cache;\n}\n\nMTLStorageManager::~MTLStorageManager() { FlushStorageRecycle(); }\n\nCVMetalTextureRef MTLStorageManager::CreateTextureFromStorage(CVPixelBufferRef pixel_buffer,\n                                                              MTLPixelFormat pixel_format,\n                                                              size_t width, size_t height) {\n  if (!cv_mtl_texture_cache_) {\n    FML_LOG(ERROR) << \"No available CVMetalTextureCacheRef to create texture\";\n    return nil;\n  }\n  CVMetalTextureRef out_texture = nil;\n  if (CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, cv_mtl_texture_cache_,\n                                                pixel_buffer, nil, pixel_format, width, height, 0,\n                                                &out_texture) != kCVReturnSuccess) {\n    FML_LOG(ERROR) << \"Failed to create CVMetalTexture from CVPixelBuffer.\";\n    return nil;\n  }\n  return out_texture;\n}\n\nvoid MTLStorageManager::FlushStorageRecycle() {\n  if (cv_mtl_texture_cache_) {\n    CVMetalTextureCacheFlush(cv_mtl_texture_cache_, 0);\n  }\n}\n\nMTLImageRepresentation::MTLImageRepresentation(id<MTLDevice> device, id<MTLCommandQueue> queue,\n                                               fml::RefPtr<SharedImageBacking> backing)\n    : SharedImageRepresentation(std::move(backing)), device_(device), queue_(queue) {}\n\nMTLImageRepresentation::~MTLImageRepresentation() {\n  id<MTLTexture> tex = GetMTLTexture();\n  if (tex == nil) {\n    return;\n  }\n  auto command_buffer = fml::scoped_nsprotocol<id<MTLCommandBuffer>>([queue_ commandBuffer]);\n  fml::RefPtr<RepresentationStorageManager> temp_mlt_storage_manager = storage_manager_;\n  [command_buffer.get() addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer) {\n    // GPU work is complete，safely release texture.\n    if (temp_mlt_storage_manager) {\n      static_cast<MTLStorageManager*>(temp_mlt_storage_manager.get())->FlushStorageRecycle();\n    }\n  }];\n  [command_buffer.get() commit];\n}\n\nstatic MTLPixelFormat GetMetalPixelFormat(SharedImageBacking::PixelFormat pixel_format) {\n  switch (pixel_format) {\n    case SharedImageBacking::PixelFormat::kNative8888:\n      return MTLPixelFormatBGRA8Unorm;\n    default:\n      break;\n  }\n  FML_UNREACHABLE();\n}\n\nImageRepresentationType MTLImageRepresentation::GetType() const {\n  return ImageRepresentationType::kMetal;\n}\n\nid<MTLTexture> MTLImageRepresentation::GetMTLTexture() {\n  if (texture_) {\n    return texture_;\n  }\n  switch (GetBacking()->GetType()) {\n    case SharedImageBacking::BackingType::kIOSurface:\n      if (!GetMTLTextureFromIOSurface()) {\n        return nil;\n      }\n      break;\n    case SharedImageBacking::BackingType::kCVPixelBuffer:\n      if (!GetMTLTextureFromCVPixelBuffer()) {\n        return nil;\n      }\n      break;\n    default:\n      FML_LOG(ERROR) << \"Invalid shared image backing type for MTLImageRepr.\";\n      return nil;\n  }\n  return texture_;\n}\n\nbool MTLImageRepresentation::GetMTLTextureFromIOSurface() {\n  if (@available(macos 10.6, ios 11.0, tvos 11.0, *)) {\n    auto* backing = GetBacking();\n    IOSurfaceRef surface = static_cast<IOSurfaceRef>(backing->GetGFXHandle());\n    MTLPixelFormat format = GetMetalPixelFormat(backing->GetPixelFormat());\n    MTLTextureDescriptor* textureDescriptor =\n        [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:format\n                                                           width:IOSurfaceGetWidth(surface)\n                                                          height:IOSurfaceGetHeight(surface)\n                                                       mipmapped:NO];\n    textureDescriptor.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;\n    texture_.reset([device_ newTextureWithDescriptor:textureDescriptor iosurface:surface plane:0]);\n    return true;\n  } else {\n    return false;\n  }\n}\n\nbool MTLImageRepresentation::GetMTLTextureFromCVPixelBuffer() {\n  if (!storage_manager_) {\n    FML_LOG(ERROR) << \"No available MTLStorageManager to create texture\";\n    return false;\n  }\n  MTLPixelFormat format = GetMetalPixelFormat(GetBacking()->GetPixelFormat());\n  CVMetalTextureRef out_texture = storage_manager_->CreateTextureFromStorage(\n      static_cast<CVPixelBufferRef>(GetBacking()->GetGFXHandle()), format,\n      GetBacking()->GetSize().x, GetBacking()->GetSize().y);\n  if (!out_texture) {\n    return false;\n  }\n  texture_.reset(CVMetalTextureGetTexture(out_texture));\n  CFRelease(out_texture);\n  return true;\n}\n\nbool MTLImageRepresentation::BeginRead(ClaySharedImageReadResult* out) {\n  id<MTLTexture> texture = GetMTLTexture();\n  if (texture == nil) {\n    return false;\n  }\n  memset(out, 0, sizeof(ClaySharedImageReadResult));\n  out->struct_size = sizeof(ClaySharedImageReadResult);\n  out->type = kClaySharedImageRepresentationTypeMetal;\n  out->metal_texture.struct_size = sizeof(ClayMetalTexture);\n  out->metal_texture.texture = (__bridge ClayMetalTextureHandle)texture;\n  out->metal_texture.user_data = this;\n  this->AddRef();\n  out->metal_texture.destruction_callback = [](void* user_data) {\n    static_cast<MTLImageRepresentation*>(user_data)->Release();\n  };\n  return true;\n}\n\nbool MTLImageRepresentation::EndRead() { return true; }\nbool MTLImageRepresentation::BeginWrite(ClaySharedImageWriteResult* out) {\n  id<MTLTexture> texture = GetMTLTexture();\n  if (texture == nil) {\n    return false;\n  }\n  memset(out, 0, sizeof(ClaySharedImageWriteResult));\n  out->struct_size = sizeof(ClaySharedImageWriteResult);\n  out->type = kClaySharedImageRepresentationTypeMetal;\n  out->metal_texture.struct_size = sizeof(ClayMetalTexture);\n  out->metal_texture.texture = (__bridge ClayMetalTextureHandle)texture;\n  out->metal_texture.user_data = this;\n  this->AddRef();\n  out->metal_texture.destruction_callback = [](void* user_data) {\n    static_cast<MTLImageRepresentation*>(user_data)->Release();\n  };\n  return true;\n}\nbool MTLImageRepresentation::EndWrite() { return true; }\n\nvoid MTLImageRepresentation::ConsumeFence(std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  if (@available(macos 10.14, ios 12.0, tvos 12.0, *)) {\n    if (fence_sync->GetType() == FenceSync::Type::kMetalEvent) {\n      MTLSharedEventFenceSync* mtl_fence_sync =\n          static_cast<MTLSharedEventFenceSync*>(fence_sync.get());\n      FML_DCHECK(mtl_fence_sync->Event() != nil);\n      id<MTLCommandBuffer> buffer = [queue_ commandBuffer];\n      [buffer encodeWaitForEvent:mtl_fence_sync->Event() value:mtl_fence_sync->Value()];\n      [buffer commit];\n      return;\n    }\n  }\n  fence_sync->ClientWait();\n}\n\nstd::unique_ptr<FenceSync> MTLImageRepresentation::ProduceFence() {\n  std::unique_ptr<FenceSync> fence_sync;\n  if (@available(macos 10.14, ios 12.0, tvos 12.0, *)) {\n    if (event_ == nil) {\n      // TODO(youfeng) Use MTLEvent instead of MTLSharedEvent for shared device\n      event_.reset([device_ newSharedEvent]);\n    }\n    event_value_++;\n    id<MTLCommandBuffer> buffer = [queue_ commandBuffer];\n    fence_sync = std::make_unique<MTLSharedEventFenceSync>(buffer, event_, event_value_);\n    [buffer encodeSignalEvent:event_ value:event_value_];\n    [buffer commit];\n  } else {\n    id<MTLCommandBuffer> buffer = [queue_ commandBuffer];\n    fence_sync = std::make_unique<MTLCompleteFenceSync>(buffer);\n    [buffer commit];\n  }\n\n  return fence_sync;\n}\n\nfml::RefPtr<RepresentationStorageManager> MTLImageRepresentation::GetTextureManager() const {\n  return fml::MakeRefCounted<MTLStorageManager>(device_);\n}\n\nvoid MTLImageRepresentation::SetTextureManager(\n    fml::RefPtr<RepresentationStorageManager> storage_manager) {\n  storage_manager_ = fml::Ref(static_cast<MTLStorageManager*>(storage_manager.get()));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/native_image_egl_image_representation.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/native_image_egl_image_representation.h\"\n\n#include <GLES2/gl2.h>\n#include <GLES2/gl2ext.h>\n\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/native_image_image_backing.h\"\n\nnamespace clay {\n\nNativeImageEGLImageRepresentation::NativeImageEGLImageRepresentation(\n    fml::RefPtr<NativeImageImageBacking> backing)\n    : GLImageRepresentation(backing), backing_(backing) {}\n\nNativeImageEGLImageRepresentation::~NativeImageEGLImageRepresentation() {\n  UnbindFrameBuffer();\n  ReleaseTexImage();\n}\n\nImageRepresentationType NativeImageEGLImageRepresentation::GetType() const {\n  return ImageRepresentationType::kEGL;\n}\n\nvoid NativeImageEGLImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence) {\n  if (fence) {\n    fence->ClientWait();\n  }\n}\n\nstd::unique_ptr<FenceSync> NativeImageEGLImageRepresentation::ProduceFence() {\n  // The fence is created in NativeImage's internal implementations\n  return nullptr;\n}\n\nstd::optional<GLImageRepresentation::TextureInfo>\nNativeImageEGLImageRepresentation::GetTexImage() {\n  return TextureInfo{.target = GL_TEXTURE_EXTERNAL_OES,\n                     .name = backing_->EnsureAttachedToGLContext(),\n                     .format = GL_RGBA8_OES,\n                     .size = GetSize()};\n}\n\nbool NativeImageEGLImageRepresentation::ReleaseTexImage() {\n  backing_->DetachGLContext();\n\n  return true;\n}\n\nstd::optional<GLImageRepresentation::FramebufferInfo>\nNativeImageEGLImageRepresentation::BindFrameBuffer() {\n  // TODO(youfeng) support write to NativeImage\n  return {};\n}\n\nbool NativeImageEGLImageRepresentation::UnbindFrameBuffer() {\n  // TODO(youfeng) support write to NativeImage\n  return false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/native_image_egl_image_representation.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_NATIVE_IMAGE_EGL_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_NATIVE_IMAGE_EGL_IMAGE_REPRESENTATION_H_\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass NativeImageImageBacking;\n\nclass NativeImageEGLImageRepresentation final : public GLImageRepresentation {\n public:\n  explicit NativeImageEGLImageRepresentation(\n      fml::RefPtr<NativeImageImageBacking> backing);\n\n  ~NativeImageEGLImageRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n  void ConsumeFence(std::unique_ptr<FenceSync>) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n private:\n  std::optional<TextureInfo> GetTexImage() override;\n  bool ReleaseTexImage() override;\n  std::optional<FramebufferInfo> BindFrameBuffer() override;\n  bool UnbindFrameBuffer() override;\n\n  fml::RefPtr<NativeImageImageBacking> backing_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_NATIVE_IMAGE_EGL_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/native_image_image_backing.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/native_image_image_backing.h\"\n\n#include <GLES2/gl2ext.h>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/native_image_egl_image_representation.h\"\n#include \"clay/gfx/shared_image/skia_gl_image_representation.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n\nnamespace clay {\n\nNativeImageImageBacking::NativeImageImageBacking(\n    PixelFormat pixel_format, skity::Vec2 size,\n    std::optional<GraphicsMemoryHandle> gfx_handle)\n    : SharedImageBackingUnmanaged(pixel_format, size) {\n  if (gfx_handle.has_value()) {\n    native_image_ = reinterpret_cast<OH_NativeImage*>(gfx_handle.value());\n  } else {\n    native_image_ = OH_NativeImage_Create(0, GL_TEXTURE_EXTERNAL_OES);\n  }\n  OH_NativeImage_SetOnFrameAvailableListener(\n      native_image_,\n      {.context = this, .onFrameAvailable = [](void* context) {\n         static_cast<NativeImageImageBacking*>(context)->TriggerFrameCallback();\n       }});\n}\n\nNativeImageImageBacking::~NativeImageImageBacking() {\n  if (texture_ != 0) {\n    FML_LOG(ERROR) << \"Texture not detached, maybe leaked!\";\n  }\n  if (native_image_) {\n    OH_NativeImage_UnsetOnFrameAvailableListener(native_image_);\n    OH_NativeImage_Destroy(&native_image_);\n  }\n}\n\nconst skity::Vec2 NativeImageImageBacking::GetSize() const {\n  if (size_.x <= 0 || size_.y <= 0) {\n    // If the size is not set, we assume the default size is 256x256.\n    return skity::Vec2(256, 256);\n  }\n  return size_;\n}\n\nSharedImageBacking::BackingType NativeImageImageBacking::GetType() const {\n  return BackingType::kNativeImage;\n}\n\nGraphicsMemoryHandle NativeImageImageBacking::GetGFXHandle() const {\n  return native_image_;\n}\n\nfml::RefPtr<SharedImageRepresentation>\nNativeImageImageBacking::CreateRepresentation(\n    const ClaySharedImageRepresentationConfig* config) {\n  auto type = config->type;\n  switch (type) {\n    case kClaySharedImageRepresentationTypeGL: {\n      return fml::MakeRefCounted<NativeImageEGLImageRepresentation>(\n          fml::Ref(this));\n    }\n    default: {\n      // NOT SUPPORTED.\n      FML_LOG(ERROR)\n          << \"Unable to call AHardwareBufferImageBacking::CreateRepresentation \"\n             \"with type: \"\n          << static_cast<uint32_t>(type);\n      return nullptr;\n    }\n  }\n}\n\nfml::RefPtr<SkiaImageRepresentation>\nNativeImageImageBacking::CreateSkiaRepresentation(GrDirectContext* gr_context) {\n  switch (gr_context->backend()) {\n    case GrBackendApi::kOpenGL: {\n      return fml::MakeRefCounted<SkiaGLImageRepresentation>(\n          gr_context, fml::MakeRefCounted<NativeImageEGLImageRepresentation>(\n                          fml::Ref(this)));\n    }\n    default: {\n      FML_LOG(ERROR)\n          << \"Unable to call \"\n             \"AHardwareBufferImageBacking::CreateSkiaRepresentation with \"\n             \"backend: \"\n          << static_cast<uint32_t>(gr_context->backend());\n      return nullptr;\n    }\n  }\n}\n\nconst skity::Matrix NativeImageImageBacking::GetTransformation() const {\n  float mat[16];\n  int ret = OH_NativeImage_GetTransformMatrix(native_image_, mat);\n  if (ret) {\n    FML_LOG(ERROR) << \"NativeImageImageBacking getTransformMatrix failed \"\n                   << ret;\n    return skity::Matrix();\n  }\n  // NativeImage buffer is always Y-down, so we need to flip y-axis.\n  skity::Matrix flip_y_mat = skity::Matrix(1, 0, 0, 0, -1, 1, 0, 0, 1);\n  skity::Matrix transformation = skity::Matrix();\n  for (int col = 0; col < 4; ++col) {\n    for (int row = 0; row < 4; ++row) {\n      transformation.Set(row, col, mat[col * 4 + row]);\n    }\n  }\n  return flip_y_mat * transformation;\n}\n\nvoid NativeImageImageBacking::SetTransformation(const skity::Matrix& mat) {\n  FML_UNIMPLEMENTED();\n}\n\n/// `SharedImageBackingUnmanaged`\nvoid NativeImageImageBacking::SetFrameAvailableCallback(\n    const fml::closure& callback) {\n  std::lock_guard<std::mutex> l(frame_callback_mutex_);\n  frame_callback_ = callback;\n}\n\nbool NativeImageImageBacking::UpdateFront() {\n  EnsureAttachedToGLContext();\n  return OH_NativeImage_UpdateSurfaceImage(native_image_) == 0;\n}\n\nvoid NativeImageImageBacking::ReleaseFront() { FML_UNIMPLEMENTED(); }\n\nuint32_t NativeImageImageBacking::AcquireBack() {\n  // No need to call any functions, since eglMakeCurrent automatically requires\n  // a new buffer.\n  return 0;  // TODO(youfeng) support buffer age for native image.\n}\n\nbool NativeImageImageBacking::SwapBack() {\n  // No need to call any functions, since eglSwap automatically swap back\n  return true;\n}\n\nuint32_t NativeImageImageBacking::Capacity() const {\n  // We assume it's multiple buffers backed\n  return 2;\n}\n\nbool NativeImageImageBacking::SetSize(skity::Vec2 size) {\n  size_ = size;\n  return true;\n}\n\nGLuint NativeImageImageBacking::EnsureAttachedToGLContext() {\n  if (texture_ == 0) {\n    glGenTextures(1, &texture_);\n    int ret = OH_NativeImage_AttachContext(native_image_, texture_);\n    if (ret) {\n      FML_LOG(ERROR) << \"NativeImageImageBacking attachContext failed \" << ret;\n    }\n  }\n  return texture_;\n}\n\nvoid NativeImageImageBacking::DetachGLContext() {\n  if (texture_ != 0) {\n    int ret = OH_NativeImage_DetachContext(native_image_);\n    if (ret) {\n      FML_LOG(ERROR) << \"NativeImageImageBacking detachContext failed \" << ret;\n    }\n    glDeleteTextures(1, &texture_);\n    texture_ = 0;\n  }\n}\n\nvoid NativeImageImageBacking::TriggerFrameCallback() {\n  fml::closure callback;\n  {\n    std::lock_guard<std::mutex> l(frame_callback_mutex_);\n    callback = frame_callback_;\n  }\n  if (callback) {\n    callback();\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/native_image_image_backing.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_NATIVE_IMAGE_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_NATIVE_IMAGE_IMAGE_BACKING_H_\n\n#include <GLES2/gl2.h>\n#include <native_image/native_image.h>\n\n#include <mutex>\n\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"skity/geometry/matrix.hpp\"\n\nnamespace clay {\n\nclass NativeImageImageBacking final : public SharedImageBackingUnmanaged {\n public:\n  NativeImageImageBacking(PixelFormat pixel_format, skity::Vec2 size,\n                          std::optional<GraphicsMemoryHandle> gfx_handle = {});\n  ~NativeImageImageBacking() override;\n\n  OH_NativeImage* GetNativeImage() const { return native_image_; }\n\n  const skity::Vec2 GetSize() const override;\n  BackingType GetType() const override;\n  GraphicsMemoryHandle GetGFXHandle() const override;\n  fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) override;\n  fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrDirectContext* gr_context) override;\n  const skity::Matrix GetTransformation() const override;\n  void SetTransformation(const skity::Matrix& mat) override;\n\n  /// `SharedImageBackingUnmanaged`\n  void SetFrameAvailableCallback(const fml::closure& callback) override;\n  bool UpdateFront() override;\n  void ReleaseFront() override;\n  uint32_t AcquireBack() override;\n  bool SwapBack() override;\n  uint32_t Capacity() const override;\n\n  bool SetSize(skity::Vec2 size) override;\n  GLuint EnsureAttachedToGLContext();\n  void DetachGLContext();\n  void TriggerFrameCallback();\n\n private:\n  OH_NativeImage* native_image_ = nullptr;\n  GLuint texture_ = 0;\n  std::mutex frame_callback_mutex_;\n  fml::closure frame_callback_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_NATIVE_IMAGE_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/shared_image_backing.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\n#include <utility>\n\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n\n#if OS_MACOSX || OS_IOS\n#include \"clay/gfx/shared_image/cv_pixelbuffer_image_backing.h\"\n#include \"clay/gfx/shared_image/iosurface_image_backing.h\"\n#elif OS_WIN\n#include \"clay/gfx/shared_image/d3d9_texture_image_backing.h\"\n#include \"clay/gfx/shared_image/d3d_texture_image_backing.h\"\n#elif OS_ANDROID\n#include \"clay/gfx/shared_image/android/android_hardwarebuffer_utils.h\"\n#include \"clay/gfx/shared_image/android_hardwarebuffer_image_backing.h\"\n#include \"clay/gfx/shared_image/egl_image_backing.h\"\n#elif OS_HARMONY\n#include \"clay/gfx/shared_image/native_image_image_backing.h\"\n#elif OS_LINUX\n#if defined(ENABLE_SOFTWARE_RENDERING)\n#include \"clay/gfx/shared_image/angle_software_shm_image_backing.h\"\n#endif\n#include \"clay/gfx/shared_image/epoxy_shm_image_backing.h\"\n#endif\n\nnamespace clay {\n\nSharedImageBacking::SharedImageBacking(PixelFormat pixel_format,\n                                       skity::Vec2 size)\n    : pixel_format_(pixel_format), size_(size) {\n  FML_DLOG(INFO) << \"Create SharedImageBacking with size: \" << size.x << \"x\"\n                 << size.y;\n}\n\nSharedImageBacking::~SharedImageBacking() {\n  FML_DLOG(INFO) << \"Destory SharedImageBacking with size: \" << size_.x << \"x\"\n                 << size_.y;\n}\n\nvoid SharedImageBacking::SetFenceSync(std::unique_ptr<FenceSync> fence_sync) {\n  fence_sync_ = std::move(fence_sync);\n}\n\nstd::unique_ptr<FenceSync> SharedImageBacking::GetFenceSync() {\n  return std::move(fence_sync_);\n}\n\nfml::RefPtr<SharedImageBacking> SharedImageBacking::Create(\n    BackingType type, PixelFormat pixel_format, skity::Vec2 size,\n    std::optional<GraphicsMemoryHandle> gfx_handle) {\n#if OS_MACOSX || OS_IOS\n  if (type == BackingType::kIOSurface) {\n    if (__builtin_available(macos 10.6, ios 11.0, tvos 11.0, *)) {\n      return fml::MakeRefCounted<IOSurfaceImageBacking>(pixel_format, size,\n                                                        gfx_handle);\n    }\n  } else if (type == BackingType::kCVPixelBuffer) {\n    return fml::MakeRefCounted<CVPixelBufferImageBacking>(pixel_format, size,\n                                                          gfx_handle);\n  }\n#elif OS_WIN\n  if (type == BackingType::kD3DTexture) {\n    if (D3DTextureImageBacking::IsSupported()) {\n      return fml::MakeRefCounted<D3DTextureImageBacking>(pixel_format, size,\n                                                         gfx_handle);\n    } else {\n      FML_LOG(ERROR) << \"Fallback to D3D9TextureImageBacking because D3D11 is \"\n                        \"not supported.\";\n      return fml::MakeRefCounted<D3D9TextureImageBacking>(pixel_format, size,\n                                                          gfx_handle);\n    }\n  }\n#elif OS_ANDROID\n  if (type == BackingType::kAHardwareBuffer) {\n    if (AHardwareBufferUtils::GetInstance().IsSupportAvailable()) {\n      return fml::MakeRefCounted<AHardwareBufferImageBacking>(\n          pixel_format, size, std::nullopt);\n    }\n    FML_LOG(ERROR) << \"Fallback to EGLImageBacking because AHardwareBuffer is \"\n                      \"not supported.\";\n    return fml::MakeRefCounted<EGLImageBacking>(pixel_format, size,\n                                                std::nullopt);\n  } else if (type == BackingType::kEGLImage) {\n    return fml::MakeRefCounted<EGLImageBacking>(pixel_format, size,\n                                                std::nullopt);\n  }\n#elif OS_HARMONY\n  if (type == BackingType::kNativeImage) {\n    return fml::MakeRefCounted<NativeImageImageBacking>(pixel_format, size,\n                                                        gfx_handle);\n  }\n#elif OS_LINUX\n  if (type == BackingType::kShmImage) {\n    return fml::MakeRefCounted<EpoxyShmImageBacking>(pixel_format, size,\n                                                     std::nullopt);\n  } else if (type == BackingType::kAngleShmImage) {\n#if defined(ENABLE_SOFTWARE_RENDERING)\n    return fml::MakeRefCounted<AngleSoftwareShmImageBacking>(pixel_format,\n                                                             size);\n#endif\n  }\n#endif\n\n  FML_LOG(ERROR) << \"Unable to Create SharedImageBacking with type: \"\n                 << static_cast<uint32_t>(type);\n\n  return nullptr;\n}\n\n#ifndef ENABLE_SKITY\nbool SharedImageBacking::ReadbackToMemory(const SkPixmap* pixmaps,\n                                          uint32_t planes) {\n  FML_UNIMPLEMENTED()\n  return false;\n}\n\n#ifndef NDEBUG\nvoid SharedImageBacking::DumpToPng(const std::string& file_name) {\n  SkBitmap bitmap;\n  auto image_info =\n      SkImageInfo::Make(SkISize::Make(size_.x, size_.y), kBGRA_8888_SkColorType,\n                        kPremul_SkAlphaType);\n  bitmap.allocPixels(image_info, 0);\n  bool success = ReadbackToMemory(&bitmap.pixmap(), 1);\n  FML_DCHECK(success);\n  SkDynamicMemoryWStream buf;\n  success = SkPngEncoder::Encode(&buf, bitmap.pixmap(), {});\n  FML_DCHECK(success);\n  sk_sp<SkData> data = buf.detachAsData();\n  // cspell:disable-next-line\n  success = SkFILEWStream(file_name.c_str()).write(data->data(), data->size());\n  FML_DCHECK(success);\n}\n#endif  // NDEBUG\n#endif  // ENABLE_SKITY\n\nSharedImageBackingUnmanaged::SharedImageBackingUnmanaged(\n    PixelFormat pixel_format, skity::Vec2 size)\n    : SharedImageBacking(pixel_format, size) {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/shared_image_backing.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_BACKING_H_\n#define CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_BACKING_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n#include \"clay/public/clay.h\"\n#include \"skity/geometry/matrix.hpp\"\n\nclass GrDirectContext;\n\nnamespace clay {\n/// IOSurfaceRef for kIOSurface\n/// CVPixelBufferRef for kCVPixelBuffer\n/// SharedHandle for kD3DTexture\n/// EGLImage for kEGLImage\n/// AHardwareBuffer* for kAHardwareBuffer\n/// jobject of SurfaceTexture for kSurfaceTexture\n/// ShmFd for kShmImage\nusing GraphicsMemoryHandle = void*;\n\nclass FenceSync;\n\nclass SharedImageBacking\n    : public fml::RefCountedThreadSafe<SharedImageBacking> {\n public:\n  enum class BackingType {\n    kIOSurface,\n    kCVPixelBuffer,\n    kD3DTexture,\n    kEGLImage,\n    kAHardwareBuffer,\n    kSurfaceTexture,\n    kNativeImage,\n    kShmImage,\n    kAngleShmImage,\n    kMocking,\n  };\n\n  enum class PixelFormat {\n    kNative8888,  // BGRA8888\n    kRGBA8888,\n  };\n\n  SharedImageBacking(PixelFormat pixel_format, skity::Vec2 size);\n  virtual ~SharedImageBacking();\n\n  virtual BackingType GetType() const = 0;\n  virtual GraphicsMemoryHandle GetGFXHandle() const = 0;\n\n  virtual fml::RefPtr<SharedImageRepresentation> CreateRepresentation(\n      const ClaySharedImageRepresentationConfig* config) = 0;\n#ifndef ENABLE_SKITY\n  // User MUST client wait fence before readback\n  virtual bool ReadbackToMemory(const SkPixmap* pixmaps, uint32_t planes);\n  virtual fml::RefPtr<SkiaImageRepresentation> CreateSkiaRepresentation(\n      GrContext* gr_context) = 0;\n#else\n  virtual fml::RefPtr<SkityImageRepresentation> CreateSkityRepresentation(\n      GrContext* skity_context) = 0;\n#endif  // ENABLE_SKITY\n\n  virtual PixelFormat GetPixelFormat() const { return pixel_format_; }\n  virtual const skity::Vec2 GetSize() const { return size_; }\n  virtual bool SetSize(skity::Vec2 size) { return false; }\n  // UV transform\n  virtual const skity::Matrix GetTransformation() const {\n    return transformation_;\n  }\n  virtual void SetTransformation(const skity::Matrix& mat) {\n    transformation_ = mat;\n  }\n\n  void SetFenceSync(std::unique_ptr<FenceSync> fence_sync);\n  std::unique_ptr<FenceSync> GetFenceSync();\n\n  static fml::RefPtr<SharedImageBacking> Create(\n      BackingType backing_type, PixelFormat pixel_format, skity::Vec2 size,\n      std::optional<GraphicsMemoryHandle> gfx_handle);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SharedImageBacking);\n\n#ifndef NDEBUG\n  void DumpToPng(const std::string& file_name);\n#endif\n\n protected:\n  PixelFormat pixel_format_;\n  skity::Vec2 size_;\n  skity::Matrix transformation_;\n  std::unique_ptr<FenceSync> fence_sync_;\n};\n\nclass SharedImageBackingUnmanaged : public SharedImageBacking {\n public:\n  SharedImageBackingUnmanaged(PixelFormat pixel_format, skity::Vec2 size);\n\n  virtual void SetFrameAvailableCallback(const fml::closure& callback) = 0;\n\n  /// Move the current front buffer\n  virtual bool UpdateFront() = 0;\n\n  // Releases the front buffer. This is needed in single buffered mode to\n  // allow the producer to take ownership of the buffer.\n  virtual void ReleaseFront() = 0;\n\n  /// Acquire the latest back buffer.\n  /// The return value is the buffer age, kinda like\n  /// `EGL_BUFFER_AGE_EXT`, 0 means a new buffer without swap history\n  ///\n  /// This is a blocking call, if no backing buffer available and the buffer\n  /// queue is full, it will wait until front buffer swap to back\n  virtual uint32_t AcquireBack() = 0;\n\n  /// Swap the current back buffer to pending front\n  virtual bool SwapBack() = 0;\n\n  virtual uint32_t Capacity() const = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_BACKING_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/shared_image_representation.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\n#include <cstring>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nRepresentationStorageManager::~RepresentationStorageManager() = default;\n\nSharedImageRepresentation::SharedImageRepresentation(\n    fml::RefPtr<SharedImageBacking> backing)\n    : backing_(std::move(backing)) {}\n\nSharedImageRepresentation::~SharedImageRepresentation() = default;\n\nconst skity::Vec2 SharedImageRepresentation::GetSize() const {\n  return backing_->GetSize();\n}\n\n#ifndef ENABLE_SKITY\nSkiaImageRepresentation::SkiaImageRepresentation(\n    fml::RefPtr<SharedImageBacking> backing)\n    : SharedImageRepresentation(std::move(backing)) {}\n\nSkiaImageRepresentation::~SkiaImageRepresentation() = default;\n\nImageRepresentationType SkiaImageRepresentation::GetType() const {\n  return ImageRepresentationType::kSkia;\n}\n\nbool SkiaImageRepresentation::BeginRead(ClaySharedImageReadResult* out) {\n  FML_LOG(ERROR)\n      << \"SkiaImageRepresentation doesn't support directly read/write\";\n  return false;\n}\n\nbool SkiaImageRepresentation::BeginWrite(ClaySharedImageWriteResult* out) {\n  FML_LOG(ERROR)\n      << \"SkiaImageRepresentation doesn't support directly read/write\";\n  return false;\n}\nbool SkiaImageRepresentation::EndWrite() {\n  FML_LOG(ERROR)\n      << \"SkiaImageRepresentation doesn't support directly read/write\";\n  return false;\n}\n#else\nSkityImageRepresentation::SkityImageRepresentation(\n    fml::RefPtr<SharedImageBacking> backing)\n    : SharedImageRepresentation(std::move(backing)) {}\n\nSkityImageRepresentation::~SkityImageRepresentation() = default;\n\nImageRepresentationType SkityImageRepresentation::GetType() const {\n  return ImageRepresentationType::kSkity;\n}\n\nbool SkityImageRepresentation::BeginRead(ClaySharedImageReadResult* out) {\n  FML_LOG(ERROR)\n      << \"SkityImageRepresentation doesn't support directly read/write\";\n  return false;\n}\nbool SkityImageRepresentation::BeginWrite(ClaySharedImageWriteResult* out) {\n  FML_LOG(ERROR)\n      << \"SkityImageRepresentation doesn't support directly read/write\";\n  return false;\n}\nbool SkityImageRepresentation::EndWrite() {\n  FML_LOG(ERROR)\n      << \"SkityImageRepresentation doesn't support directly read/write\";\n  return false;\n}\n#endif  // ENABLE_SKITY\n\nGLImageRepresentation::GLImageRepresentation(\n    fml::RefPtr<SharedImageBacking> backing)\n    : SharedImageRepresentation(std::move(backing)) {}\n\nbool GLImageRepresentation::BeginRead(ClaySharedImageReadResult* out) {\n  std::optional<TextureInfo> texture_info = GetTexImage();\n  if (!texture_info) {\n    return false;\n  }\n  memset(out, 0, sizeof(ClaySharedImageReadResult));\n  out->struct_size = sizeof(ClaySharedImageReadResult);\n  out->type = kClaySharedImageRepresentationTypeGL;\n  out->opengl_texture.struct_size = sizeof(ClayOpenGLTexture);\n  out->opengl_texture.format = texture_info->format;\n  out->opengl_texture.name = texture_info->name;\n  out->opengl_texture.target = texture_info->target;\n  if (texture_info->size) {\n    out->opengl_texture.size = {\n        .width = static_cast<uint32_t>(texture_info->size->x),\n        .height = static_cast<uint32_t>(texture_info->size->y)};\n  }\n  out->opengl_texture.user_data = this;\n  this->AddRef();\n  out->opengl_texture.destruction_callback = [](void* user_data) {\n    static_cast<GLImageRepresentation*>(user_data)->Release();\n  };\n  return true;\n}\n\nbool GLImageRepresentation::EndRead() {\n  // We don't delete internal texture here since it's time consuming\n  return true;\n}\n\nbool GLImageRepresentation::BeginWrite(ClaySharedImageWriteResult* out) {\n  std::optional<FramebufferInfo> fbo_info = BindFrameBuffer();\n  if (!fbo_info) {\n    return false;\n  }\n\n  memset(out, 0, sizeof(ClaySharedImageWriteResult));\n  out->struct_size = sizeof(ClaySharedImageWriteResult);\n  out->type = kClaySharedImageRepresentationTypeGL;\n  out->opengl_framebuffer.struct_size = sizeof(ClayOpenGLFramebuffer);\n  out->opengl_framebuffer.target = fbo_info->target;\n  out->opengl_framebuffer.name = fbo_info->name;\n  out->opengl_framebuffer.user_data = this;\n  this->AddRef();\n  out->opengl_framebuffer.destruction_callback = [](void* user_data) {\n    static_cast<GLImageRepresentation*>(user_data)->Release();\n  };\n  return true;\n}\n\nbool GLImageRepresentation::EndWrite() {\n  // We don't delete internal fbo here since it's time consuming\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/shared_image_representation.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_REPRESENTATION_H_\n\n#include <memory>\n#include <optional>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/public/clay.h\"\n#include \"skity/geometry/vector.hpp\"\n#ifndef ENABLE_SKITY\n#include \"third_party/skia/include/core/SkImage.h\"\n#endif\n\nnamespace clay {\n\nclass SharedImageBacking;\nclass FenceSync;\n\nenum class ImageRepresentationType {\n  kGL,\n  kCGL,\n  kMetal,\n  kD3D,\n  kSkia,\n  kSkity,\n  kEGL,\n  kANGLE,\n  kEAGL,\n  kVulkanImage,\n  kLinuxShm,\n};\n\nclass GLImageRepresentation;\nclass SkiaImageRepresentation;\n\nclass RepresentationStorageManager\n    : public fml::RefCountedThreadSafe<RepresentationStorageManager> {\n public:\n  virtual ~RepresentationStorageManager();\n};\n\nclass SharedImageRepresentation\n    : public fml::RefCountedThreadSafe<SharedImageRepresentation> {\n public:\n  explicit SharedImageRepresentation(fml::RefPtr<SharedImageBacking> backing);\n\n  virtual ~SharedImageRepresentation();\n\n  virtual ImageRepresentationType GetType() const = 0;\n\n  const skity::Vec2 GetSize() const;\n\n  SharedImageBacking* GetBacking() const { return backing_.get(); }\n\n  virtual bool BeginRead(ClaySharedImageReadResult* out) = 0;\n  virtual bool EndRead() = 0;\n  virtual bool BeginWrite(ClaySharedImageWriteResult* out) = 0;\n  virtual bool EndWrite() = 0;\n\n  virtual void ConsumeFence(std::unique_ptr<FenceSync>) = 0;\n  virtual std::unique_ptr<FenceSync> ProduceFence() = 0;\n\n  virtual fml::RefPtr<RepresentationStorageManager> GetTextureManager() const {\n    return nullptr;\n  }\n  virtual void SetTextureManager(fml::RefPtr<RepresentationStorageManager>) {}\n\n private:\n  fml::RefPtr<SharedImageBacking> backing_;\n};\n\n#ifndef ENABLE_SKITY\nclass SkiaImageRepresentation : public SharedImageRepresentation {\n public:\n  explicit SkiaImageRepresentation(fml::RefPtr<SharedImageBacking> backing);\n  ~SkiaImageRepresentation() override;\n\n  virtual sk_sp<SkImage> GetSkImage() = 0;\n#else\nclass SkityImage;\nclass SkityImageRepresentation : public SharedImageRepresentation {\n public:\n  explicit SkityImageRepresentation(fml::RefPtr<SharedImageBacking> backing);\n  ~SkityImageRepresentation() override;\n\n  virtual std::shared_ptr<SkityImage> GetSkityImage() = 0;\n#endif  // ENABLE_SKITY\n\n  ImageRepresentationType GetType() const override;\n\n  bool BeginRead(ClaySharedImageReadResult* out) override;\n  bool BeginWrite(ClaySharedImageWriteResult* out) override;\n  bool EndWrite() override;\n\n  bool EndRead() override = 0;\n};\n\nclass GLImageRepresentation : public SharedImageRepresentation {\n public:\n  struct TextureInfo {\n    /// Target texture of the active texture unit (example GL_TEXTURE_2D or\n    /// GL_TEXTURE_RECTANGLE).\n    uint32_t target;\n    /// The name of the texture.\n    uint32_t name;\n    /// The texture format (example GL_RGBA8).\n    uint32_t format;\n    /// An optional size for GL_TEXTURE_RECTANGLE\n    std::optional<skity::Vec2> size;\n  };\n  struct FramebufferInfo {\n    /// The target of the color attachment of the frame-buffer. For example,\n    /// GL_TEXTURE_2D or GL_RENDERBUFFER. In case of ambiguity when dealing\n    /// with Window bound frame-buffers, 0 may be used.\n    uint32_t target;\n\n    /// The name of the framebuffer.\n    uint32_t name;\n  };\n\n  explicit GLImageRepresentation(fml::RefPtr<SharedImageBacking> backing);\n\n  bool BeginRead(ClaySharedImageReadResult* out) override;\n  bool EndRead() override;\n  bool BeginWrite(ClaySharedImageWriteResult* out) override;\n  bool EndWrite() override;\n\n private:\n  // The returned value remains valid as long as the caller holds the\n  // representation\n  virtual std::optional<TextureInfo> GetTexImage() = 0;\n  virtual bool ReleaseTexImage() = 0;\n  // The returned value remains valid as long as the caller holds the\n  // representation\n  virtual std::optional<FramebufferInfo> BindFrameBuffer() = 0;\n  virtual bool UnbindFrameBuffer() = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/shared_image_sink.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n\n#include <string>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nSharedImageSink::SharedImageSink(UpdateFrontMode update_front_mode)\n    : update_front_mode_(update_front_mode) {}\n\nSharedImageSinkManaged::SharedImageSinkManaged(\n    BufferMode buffer_mode, const SharedImageFactory& shared_image_factory,\n    UpdateFrontMode update_front_mode)\n    : SharedImageSink(update_front_mode),\n      capacity_(buffer_mode),\n      shared_image_factory_(std::move(shared_image_factory)) {}\n\nSharedImageSinkManaged::~SharedImageSinkManaged() = default;\n\nvoid SharedImageSinkManaged::UpdateBufferMode(BufferMode mode) {\n  capacity_ = mode;\n}\n\nvoid SharedImageSinkManaged::SetFrameAvailableCallback(\n    const fml::closure& callback) {\n  std::lock_guard<std::mutex> l(mutex_);\n\n  frame_available_callback_ = callback;\n\n  backings_cond_.notify_one();\n}\n\nvoid SharedImageSinkManaged::ClearBackFences() {\n  std::lock_guard<std::mutex> l(mutex_);\n\n  for (auto& backing : back_backings_) {\n    std::unique_ptr<FenceSync> fence = backing.shared_image->GetFenceSync();\n  }\n}\n\nfml::RefPtr<SharedImageBacking> SharedImageSinkManaged::UpdateFront(\n    std::unique_ptr<FenceSync> produced_fence_sync) {\n  std::unique_lock<std::mutex> l(mutex_);\n\n  InternalReleaseCurrentFront(std::move(produced_fence_sync));\n\n  if (front_backings_.empty()) {\n    if (capacity_ == BufferMode::kSingleBuffer && !back_backings_.empty()) {\n      // Single Buffer Synchronous Mode\n      if (back_backings_.front().is_current) {\n        // Wait for back releasing\n        if (!front_cond_.wait_for(l, timeout_,\n                                  [&] { return !front_backings_.empty(); })) {\n          FML_LOG(ERROR) << \"timeout to wait front available\";\n          return nullptr;\n        }\n      } else {\n        // Steal the backing directly\n        front_backings_.swap(back_backings_);\n      }\n    } else {\n      FML_LOG(ERROR) << \"no pending front available\";\n      return nullptr;\n    }\n  }\n\n  auto& curr_front = front_backings_.front();\n\n  curr_front.is_current = true;\n\n  return curr_front.shared_image;\n}\n\nfml::RefPtr<SharedImageBacking> SharedImageSinkManaged::UpdateFrontToLatest(\n    std::unique_ptr<FenceSync> produced_fence_sync) {\n  std::unique_lock<std::mutex> l(mutex_);\n  if (front_backings_.empty()) {\n    return nullptr;\n  }\n  while (front_backings_.size() > 1) {\n    front_backings_.front().is_current = true;\n    // The fence sync is in fact only moved once for the head of front backings\n    InternalReleaseCurrentFront(std::move(produced_fence_sync));\n  }\n  auto& curr_front = front_backings_.front();\n\n  curr_front.is_current = true;\n\n  return curr_front.shared_image;\n}\n\nvoid SharedImageSinkManaged::ReleaseFront(\n    std::unique_ptr<FenceSync> produced_fence_sync) {\n  std::lock_guard<std::mutex> l(mutex_);\n\n  InternalReleaseCurrentFront(std::move(produced_fence_sync));\n}\n\nstd::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\nSharedImageSinkManaged::AcquireBack(\n    skity::Vec2 size, std::optional<GraphicsMemoryHandle> gfx_handle) {\n  std::unique_lock<std::mutex> l(mutex_);\n\n  if (capacity_ == BufferMode::kNoBuffer) {\n    // When capacity equals to ZERO, always create a new back backing.\n    SharedImageSlot slot = {.shared_image =\n                                shared_image_factory_(size, gfx_handle)};\n    if (back_backings_.empty()) {\n      back_backings_.emplace_back(slot);\n    } else {\n      back_backings_.front() = slot;\n    }\n  } else if (capacity_ == BufferMode::kMultiBuffer) {\n    if (back_backings_.empty()) {\n      SharedImageSlot slot = {.shared_image =\n                                  shared_image_factory_(size, gfx_handle)};\n      back_backings_.emplace_back(slot);\n    } else {\n      if (!frame_available_callback_) {\n        return std::make_tuple(nullptr, 0);\n      }\n      if (back_backings_.front().shared_image->GetSize() != size ||\n          (gfx_handle.has_value() && gfx_handle.value())) {\n        // maybe resized, discard it\n        back_backings_.front() = SharedImageSlot{\n            .shared_image = shared_image_factory_(size, gfx_handle)};\n      }\n    }\n  } else {\n    if (back_backings_.empty() && used_ < capacity_) {\n      SharedImageSlot slot = {.shared_image =\n                                  shared_image_factory_(size, gfx_handle)};\n      back_backings_.emplace_back(slot);\n      used_++;\n    } else {\n      if (!backings_cond_.wait_for(l, timeout_, [&] {\n            return !back_backings_.empty() || !frame_available_callback_;\n          })) {\n        FML_LOG(ERROR) << \"timeout to wait back available\";\n        return std::make_tuple(nullptr, 0);\n      }\n      // No `frame_available_callback_` means the sink front is detached,\n      // return null directly to prevent deadlock.\n      if (!frame_available_callback_) {\n        return std::make_tuple(nullptr, 0);\n      }\n\n      if (back_backings_.front().shared_image->GetSize() != size ||\n          (gfx_handle.has_value() && gfx_handle.value())) {\n        // maybe resized, discard it\n        back_backings_.front() = SharedImageSlot{\n            .shared_image = shared_image_factory_(size, gfx_handle)};\n      }\n    }\n  }\n\n  auto& curr_backing = back_backings_.front();\n  curr_backing.is_current = true;\n\n  uint32_t buffer_age = 0;\n\n  if (curr_backing.swap_id) {\n    buffer_age = swap_counter_ - curr_backing.swap_id.value();\n  }\n  return std::make_tuple(curr_backing.shared_image, buffer_age);\n}\n\nstd::tuple<fml::RefPtr<SharedImageBacking>, uint32_t,\n           SharedImageSink::AcquireBackStatus>\nSharedImageSinkManaged::TryAcquireBack(\n    skity::Vec2 size, std::optional<GraphicsMemoryHandle> gfx_handle) {\n  std::unique_lock<std::mutex> l(mutex_);\n\n  if (capacity_ == BufferMode::kNoBuffer) {\n    // When capacity equals to ZERO, always create a new back backing.\n    SharedImageSlot slot = {.shared_image =\n                                shared_image_factory_(size, gfx_handle)};\n    if (back_backings_.empty()) {\n      back_backings_.emplace_back(slot);\n    } else {\n      back_backings_.front() = slot;\n    }\n  } else {\n    if (back_backings_.empty()) {\n      if (used_ < capacity_) {\n        SharedImageSlot slot = {.shared_image =\n                                    shared_image_factory_(size, gfx_handle)};\n        back_backings_.emplace_back(slot);\n        used_++;\n      } else {\n        auto status = frame_available_callback_\n                          ? AcquireBackStatus::kFullAttach\n                          : AcquireBackStatus::kFullDetach;\n        return std::make_tuple(nullptr, 0, status);\n      }\n    } else {\n      if (back_backings_.front().shared_image->GetSize() != size ||\n          (gfx_handle.has_value() && gfx_handle.value())) {\n        // maybe resized, discard it\n        back_backings_.front() = SharedImageSlot{\n            .shared_image = shared_image_factory_(size, gfx_handle)};\n      }\n    }\n  }\n\n  auto& curr_backing = back_backings_.front();\n  curr_backing.is_current = true;\n\n  uint32_t buffer_age = 0;\n\n  if (curr_backing.swap_id) {\n    buffer_age = swap_counter_ - curr_backing.swap_id.value();\n  }\n  return std::make_tuple(curr_backing.shared_image, buffer_age,\n                         AcquireBackStatus::kSuccess);\n}\n\nstd::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\nSharedImageSinkManaged::AcquireBackForced(\n    skity::Vec2 size, std::optional<GraphicsMemoryHandle> gfx_handle) {\n  std::unique_lock<std::mutex> l(mutex_);\n\n  if (capacity_ == BufferMode::kNoBuffer) {\n    // When capacity equals to ZERO, always create a new back backing.\n    SharedImageSlot slot = {.shared_image =\n                                shared_image_factory_(size, gfx_handle)};\n    if (back_backings_.empty()) {\n      back_backings_.emplace_back(slot);\n    } else {\n      back_backings_.front() = slot;\n    }\n  } else {\n    if (back_backings_.empty() && used_ < capacity_) {\n      SharedImageSlot slot = {.shared_image =\n                                  shared_image_factory_(size, gfx_handle)};\n      back_backings_.emplace_back(slot);\n      used_++;\n    } else {\n      // No `frame_available_callback_` means the sink front is detached,\n      // return null directly to prevent deadlock.\n      if (!frame_available_callback_) {\n        return std::make_tuple(nullptr, 0);\n      }\n\n      if (back_backings_.empty()) {\n        if (front_backings_.size() == 0 ||\n            (front_backings_.size() == 1 &&\n             front_backings_.front().is_current)) {\n          return std::make_tuple(nullptr, 0);\n        }\n        FML_DCHECK(!front_backings_.back().is_current);\n        // Steal the unconsumed front backing directly\n        back_backings_.splice(back_backings_.begin(), front_backings_,\n                              --front_backings_.end());\n      }\n\n      if (back_backings_.front().shared_image->GetSize() != size ||\n          (gfx_handle.has_value() && gfx_handle.value())) {\n        // maybe resized, discard it\n        back_backings_.front() = SharedImageSlot{\n            .shared_image = shared_image_factory_(size, gfx_handle)};\n      }\n    }\n  }\n\n  auto& curr_backing = back_backings_.front();\n  curr_backing.is_current = true;\n\n  uint32_t buffer_age = 0;\n\n  if (curr_backing.swap_id) {\n    buffer_age = swap_counter_ - curr_backing.swap_id.value();\n  }\n  return std::make_tuple(curr_backing.shared_image, buffer_age);\n}\n\nbool SharedImageSinkManaged::SwapBack(std::unique_ptr<FenceSync> fence_sync) {\n  std::lock_guard<std::mutex> l(mutex_);\n  if (!frame_available_callback_) {\n    return false;\n  }\n\n  if (back_backings_.empty() || !back_backings_.front().is_current) {\n    FML_LOG(ERROR) << \"no current backing to swap\";\n    return false;\n  }\n\n  auto& curr_backing = back_backings_.front();\n\n  curr_backing.shared_image->SetFenceSync(std::move(fence_sync));\n  curr_backing.swap_id = swap_counter_++;\n  curr_backing.is_current = false;\n\n#if 0\n  {\n    std::string path =\n        \"Sink\" + std::to_string(reinterpret_cast<intptr_t>(this)) + \"_swap_\" +\n        std::to_string(*curr_backing.swap_id) + \".png\";\n    curr_backing.shared_image->DumpToPng(path);\n  }\n#endif\n\n  front_backings_.splice(front_backings_.end(), back_backings_,\n                         back_backings_.begin(), ++back_backings_.begin());\n  if (capacity_ == 1) {\n    // Single Buffer Synchronous Mode, we need to notify the front\n    front_cond_.notify_one();\n  }\n\n  if (frame_available_callback_) {\n    frame_available_callback_();\n  }\n\n  return true;\n}\n\nvoid SharedImageSinkManaged::InternalReleaseCurrentFront(\n    std::unique_ptr<FenceSync> fence_sync) {\n  if (front_backings_.empty() || !front_backings_.front().is_current) {\n    return;\n  }\n\n  if (capacity_ == BufferMode::kNoBuffer) {\n    // When capacity equals to ZERO, back would always push back new backings\n    // so there is no need to reuse backing here.\n    front_backings_.pop_front();\n  } else {\n    auto& curr_front = front_backings_.front();\n    curr_front.is_current = false;\n    curr_front.shared_image->SetFenceSync(std::move(fence_sync));\n    back_backings_.splice(back_backings_.end(), front_backings_,\n                          front_backings_.begin(), ++front_backings_.begin());\n  }\n  backings_cond_.notify_one();\n}\n\nSharedImageSinkUnmanaged::SharedImageSinkUnmanaged(\n    fml::RefPtr<SharedImageBackingUnmanaged> buffer_unmanaged,\n    UpdateFrontMode update_front_mode)\n    : SharedImageSink(update_front_mode),\n      buffer_unmanaged_(std::move(buffer_unmanaged)) {}\n\nSharedImageSinkUnmanaged::~SharedImageSinkUnmanaged() = default;\n\nvoid SharedImageSinkUnmanaged::SetFrameAvailableCallback(\n    const fml::closure& callback) {\n  buffer_unmanaged_->SetFrameAvailableCallback(callback);\n}\n\nfml::RefPtr<SharedImageBacking> SharedImageSinkUnmanaged::UpdateFront(\n    std::unique_ptr<FenceSync> produced_fence_sync) {\n  if (produced_fence_sync) {\n    FML_LOG(WARNING) << \"Unmanaged shared image sink does not support fence\";\n  }\n  if (!buffer_unmanaged_->UpdateFront()) {\n    FML_LOG(ERROR) << \"Failed to UpdateFront\";\n    return nullptr;\n  }\n  return buffer_unmanaged_;\n}\n\nfml::RefPtr<SharedImageBacking> SharedImageSinkUnmanaged::UpdateFrontToLatest(\n    std::unique_ptr<FenceSync> produced_fence_sync) {\n  return UpdateFront(std::move(produced_fence_sync));\n}\n\nvoid SharedImageSinkUnmanaged::ReleaseFront(\n    std::unique_ptr<FenceSync> produced_fence_sync) {\n  if (produced_fence_sync) {\n    FML_LOG(WARNING) << \"Unmanaged shared image sink does not support fence\";\n  }\n  buffer_unmanaged_->ReleaseFront();\n}\n\nstd::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\nSharedImageSinkUnmanaged::AcquireBack(\n    skity::Vec2 size, std::optional<GraphicsMemoryHandle> gfx_handle) {\n  if (gfx_handle.has_value() && gfx_handle.value()) {\n    FML_LOG(ERROR) << \"SharedImageSinkUnmanaged::AcquireBack can not \"\n                      \"retain gfx_handle\";\n  }\n  if (size.x > 0 & size.y > 0 && !buffer_unmanaged_->SetSize(size)) {\n    FML_LOG(ERROR)\n        << \"SharedImageSinkUnmanaged::AcquireBack can not \"\n           \"AcquireBack by size. Please set the internal buffer's size\";\n  }\n  return std::make_tuple(buffer_unmanaged_, buffer_unmanaged_->AcquireBack());\n}\n\nbool SharedImageSinkUnmanaged::SwapBack(std::unique_ptr<FenceSync> fence_sync) {\n  return buffer_unmanaged_->SwapBack();\n}\n\nuint32_t SharedImageSinkUnmanaged::Capacity() const {\n  return buffer_unmanaged_->Capacity();\n}\n\nstd::tuple<fml::RefPtr<SharedImageBacking>, uint32_t,\n           SharedImageSink::AcquireBackStatus>\nSharedImageSinkUnmanaged::TryAcquireBack(\n    skity::Vec2 size, std::optional<GraphicsMemoryHandle> gfx_handle) {\n  auto acquire_result = AcquireBack(size, gfx_handle);\n  auto status = std::make_tuple(AcquireBackStatus::kUnkown);\n  auto res = std::tuple_cat(acquire_result, status);\n  return res;\n}\n\nstd::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\nSharedImageSinkUnmanaged::AcquireBackForced(\n    skity::Vec2 size, std::optional<GraphicsMemoryHandle> gfx_handle) {\n  return AcquireBack(size, gfx_handle);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/shared_image_sink.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_SINK_H_\n#define CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_SINK_H_\n\n#include <list>\n#include <memory>\n#include <mutex>\n#include <optional>\n#include <tuple>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"skity/geometry/vector.hpp\"\n\nnamespace clay {\n\nclass SharedImageBacking;\nclass SharedImageBackingUnmanaged;\nclass FenceSync;\n\nclass SharedImageSink : public fml::RefCountedThreadSafe<SharedImageSink> {\n public:\n  enum BufferMode {\n    // No buffer is needed, which means the user would always acquire a new back\n    // backing\n    kNoBuffer = 0,\n    // In single buffer mode, the user must call ReleaseFront after read\n    kSingleBuffer = 1,\n    kDoubleBuffer = 2,\n    kTripleBuffer = 3,\n    kMultiBuffer = 4,\n  };\n\n  enum class UpdateFrontMode {\n    // Sequential update, display every frame in order\n    kSequence,\n    // Only update to the latest frame, skip intermediate frames\n    kLatest,\n  };\n\n  enum class AcquireBackStatus {\n    // Acquire succedded.\n    kSuccess,\n    // Acquire failed. Buffer is full and view is attached.\n    kFullAttach,\n    // Acquire failed. Buffer is full and view is detached.\n    kFullDetach,\n    // When this status is not needed.\n    kUnkown,\n  };\n\n  using GraphicsMemoryHandle = void*;\n\n  explicit SharedImageSink(\n      UpdateFrontMode update_front_mode = UpdateFrontMode::kSequence);\n  virtual ~SharedImageSink() = default;\n\n  virtual void SetFrameAvailableCallback(const fml::closure& callback) = 0;\n\n  /// Move the current front buffer to next.\n  ///\n  /// This is a non-blocking call and will return nullptr if no front buffer\n  /// available\n  /// The caller should receive a notification in `FrameAvailableCallback`\n  /// and determine if need to call the function before\n  [[nodiscard]] virtual fml::RefPtr<SharedImageBacking> UpdateFront(\n      std::unique_ptr<FenceSync> produced_fence_sync) = 0;\n\n  /// Move the current front buffer to latest buffer, if no.\n  ///\n  /// This is a non-blocking call and will return current back buffer if no\n  /// front buffer available.\n  [[nodiscard]] virtual fml::RefPtr<SharedImageBacking> UpdateFrontToLatest(\n      std::unique_ptr<FenceSync> produced_fence_sync) = 0;\n\n  // Releases the front buffer. This is needed in single buffered mode to\n  // allow the producer to take ownership of the buffer.\n  virtual void ReleaseFront(std::unique_ptr<FenceSync> produced_fence_sync) = 0;\n\n  /// Acquire the latest back buffer.\n  /// The second return value is the buffer age, kinda like\n  /// `EGL_BUFFER_AGE_EXT`, 0 means a new buffer without swap history\n  ///\n  /// This is a blocking call, if no backing buffer available and the buffer\n  /// queue is full, it will wait until front buffer swap to back\n  [[nodiscard]] virtual std::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\n  AcquireBack(skity::Vec2 size,\n              std::optional<GraphicsMemoryHandle> gfx_handle = {}) = 0;\n\n  /// Try acquire the latest the back buffer.\n  ///\n  /// This is a non-blocking call, when no backing buffer available, it will\n  /// return immediately when null.\n  [[nodiscard]] virtual std::tuple<fml::RefPtr<SharedImageBacking>, uint32_t,\n                                   AcquireBackStatus>\n  TryAcquireBack(skity::Vec2 size,\n                 std::optional<GraphicsMemoryHandle> gfx_handle = {}) = 0;\n\n  /// Acquire or steal the latest back buffer, only valid for non-single buffer\n  /// mode sink.\n  ///\n  /// This is a non-blocking call, it will always return the non-current oldest\n  /// back buffer.\n  /// The reader should pay attention if using frame-count buffer consuming,\n  /// since the already available frame may soon be stealed.\n  [[nodiscard]] virtual std::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\n  AcquireBackForced(skity::Vec2 size,\n                    std::optional<GraphicsMemoryHandle> gfx_handle = {}) = 0;\n\n  /// Swap the current back buffer to pending front\n  virtual bool SwapBack(std::unique_ptr<FenceSync> fence_sync) = 0;\n\n  virtual uint32_t Capacity() const = 0;\n  virtual void UpdateBufferMode(BufferMode mode) {}\n\n  UpdateFrontMode update_front_mode() const { return update_front_mode_; }\n\n  virtual void ClearBackFences() {}\n\n private:\n  UpdateFrontMode update_front_mode_;\n};\n\n/// SharedImageSinkManaged owns all internal buffers.\n/// It manages the buffer queue and controls the swap of buffers.\nclass SharedImageSinkManaged final : public SharedImageSink {\n public:\n  using SharedImageFactory = std::function<fml::RefPtr<SharedImageBacking>(\n      skity::Vec2, std::optional<GraphicsMemoryHandle>)>;\n  using UpdateFrontMode = SharedImageSink::UpdateFrontMode;\n\n  SharedImageSinkManaged(\n      BufferMode buffer_mode, const SharedImageFactory& shared_image_factory,\n      UpdateFrontMode update_front_mode = UpdateFrontMode::kSequence);\n  ~SharedImageSinkManaged();\n\n  void SetFrameAvailableCallback(const fml::closure& callback) override;\n\n  [[nodiscard]] fml::RefPtr<SharedImageBacking> UpdateFront(\n      std::unique_ptr<FenceSync> produced_fence_sync) override;\n\n  [[nodiscard]] fml::RefPtr<SharedImageBacking> UpdateFrontToLatest(\n      std::unique_ptr<FenceSync> produced_fence_sync) override;\n\n  void ReleaseFront(std::unique_ptr<FenceSync> produced_fence_sync) override;\n\n  [[nodiscard]] std::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\n  AcquireBack(skity::Vec2 size,\n              std::optional<GraphicsMemoryHandle> gfx_handle = {}) override;\n\n  [[nodiscard]] std::tuple<fml::RefPtr<SharedImageBacking>, uint32_t,\n                           AcquireBackStatus>\n  TryAcquireBack(skity::Vec2 size,\n                 std::optional<GraphicsMemoryHandle> gfx_handle = {}) override;\n\n  [[nodiscard]] std::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\n  AcquireBackForced(\n      skity::Vec2 size,\n      std::optional<GraphicsMemoryHandle> gfx_handle = {}) override;\n\n  bool SwapBack(std::unique_ptr<FenceSync> fence_sync) override;\n\n  uint32_t Capacity() const override { return capacity_; }\n  void UpdateBufferMode(BufferMode mode) override;\n\n  void ClearBackFences() override;\n\n private:\n  void InternalReleaseCurrentFront(\n      std::unique_ptr<FenceSync> produced_fence_sync);\n\n  struct SharedImageSlot {\n    std::optional<uint64_t> swap_id;\n    fml::RefPtr<SharedImageBacking> shared_image;\n    bool is_current = false;  // is current front or current back\n  };\n\n  uint32_t used_ = 0;\n  uint32_t capacity_;\n  SharedImageFactory shared_image_factory_;\n\n  fml::closure frame_available_callback_;\n\n  std::list<SharedImageSlot> front_backings_;\n  std::list<SharedImageSlot> back_backings_;\n  std::mutex mutex_;\n  std::condition_variable backings_cond_;\n  std::condition_variable front_cond_;\n  uint64_t swap_counter_ = 0;\n  const fml::Milliseconds timeout_{500};\n};\n\n/// SharedImageSinkUnmanaged doesn't own any internal buffers.\n/// The buffer queue is implemented by platforms, like SurfaceTexture on\n/// Android and NativeImage on Harmony.\nclass SharedImageSinkUnmanaged final : public SharedImageSink {\n public:\n  using UpdateFrontMode = SharedImageSink::UpdateFrontMode;\n  explicit SharedImageSinkUnmanaged(\n      fml::RefPtr<SharedImageBackingUnmanaged> buffer_unmanaged,\n      UpdateFrontMode update_front_mode = UpdateFrontMode::kSequence);\n\n  ~SharedImageSinkUnmanaged();\n\n  void SetFrameAvailableCallback(const fml::closure& callback) override;\n\n  [[nodiscard]] fml::RefPtr<SharedImageBacking> UpdateFront(\n      std::unique_ptr<FenceSync> produced_fence_sync) override;\n\n  [[nodiscard]] fml::RefPtr<SharedImageBacking> UpdateFrontToLatest(\n      std::unique_ptr<FenceSync> produced_fence_sync) override;\n\n  void ReleaseFront(std::unique_ptr<FenceSync> produced_fence_sync) override;\n\n  [[nodiscard]] std::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\n  AcquireBack(skity::Vec2 size,\n              std::optional<GraphicsMemoryHandle> gfx_handle = {}) override;\n\n  [[nodiscard]] std::tuple<fml::RefPtr<SharedImageBacking>, uint32_t,\n                           AcquireBackStatus>\n  TryAcquireBack(skity::Vec2 size,\n                 std::optional<GraphicsMemoryHandle> gfx_handle = {}) override;\n\n  [[nodiscard]] std::tuple<fml::RefPtr<SharedImageBacking>, uint32_t>\n  AcquireBackForced(\n      skity::Vec2 size,\n      std::optional<GraphicsMemoryHandle> gfx_handle = {}) override;\n\n  bool SwapBack(std::unique_ptr<FenceSync> fence_sync) override;\n\n  uint32_t Capacity() const override;\n\n private:\n  fml::RefPtr<SharedImageBackingUnmanaged> buffer_unmanaged_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_SINK_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/shared_image_sink_accessor.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/shared_image_sink_accessor.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n\nnamespace clay {\n\nSharedImageSinkAccessor::SharedImageSinkAccessor(\n    fml::RefPtr<SharedImageSink> sink,\n    const RepresentationFactory& repr_factory)\n    : sink_(std::move(sink)), repr_factory_(repr_factory) {}\n\nSharedImageSinkAccessor::~SharedImageSinkAccessor() {\n  repr_texture_manager_ = nullptr;\n  image_repr_cache_.clear();\n  if (front_repr_) {\n    ReleaseFront();\n  }\n  if (back_repr_) {\n    SwapBack();\n  }\n}\n\nfml::RefPtr<SharedImageRepresentation> SharedImageSinkAccessor::UpdateFront() {\n  switch (sink_->update_front_mode()) {\n    case SharedImageSink::UpdateFrontMode::kSequence:\n      return UpdateFrontSequence();\n    case SharedImageSink::UpdateFrontMode::kLatest:\n      return UpdateFrontToLatest();\n    default:\n      return UpdateFrontSequence();\n  }\n}\n\nfml::RefPtr<SharedImageRepresentation>\nSharedImageSinkAccessor::UpdateFrontSequence() {\n  std::unique_ptr<FenceSync> front_fence;\n  if (front_repr_) {\n    if (!front_repr_->EndRead()) {\n      FML_LOG(ERROR) << \"Failed to end read\";\n      return nullptr;\n    }\n    front_fence = front_repr_->ProduceFence();\n    front_repr_ = nullptr;\n  }\n  fml::RefPtr<SharedImageBacking> backing =\n      sink_->UpdateFront(std::move(front_fence));\n  if (!backing) {\n    return nullptr;\n  }\n  front_repr_ = GetRepresentation(backing.get());\n  front_repr_->ConsumeFence(backing->GetFenceSync());\n  return front_repr_;\n}\n\nfml::RefPtr<SharedImageRepresentation>\nSharedImageSinkAccessor::UpdateFrontToLatest() {\n  std::unique_ptr<FenceSync> front_fence;\n  if (front_repr_) {\n    front_fence = front_repr_->ProduceFence();\n  }\n  fml::RefPtr<SharedImageBacking> backing =\n      sink_->UpdateFrontToLatest(std::move(front_fence));\n  if (!backing) {\n    return nullptr;\n  }\n  if (front_repr_ && backing.get() == front_repr_->GetBacking()) {\n    return front_repr_;\n  }\n  if (front_repr_ && !front_repr_->EndRead()) {\n    FML_LOG(ERROR) << \"Failed to end read\";\n  }\n  front_repr_ = GetRepresentation(backing.get());\n  front_repr_->ConsumeFence(backing->GetFenceSync());\n  return front_repr_;\n}\n\nvoid SharedImageSinkAccessor::ReleaseFront() {\n  std::unique_ptr<FenceSync> front_fence;\n  if (front_repr_) {\n    if (!front_repr_->EndRead()) {\n      FML_LOG(ERROR) << \"Failed to end read\";\n      return;\n    }\n    front_fence = front_repr_->ProduceFence();\n    front_repr_ = nullptr;\n  }\n  sink_->ReleaseFront(std::move(front_fence));\n}\n\nstd::tuple<fml::RefPtr<SharedImageRepresentation>, uint32_t>\nSharedImageSinkAccessor::AcquireBack(const skity::Vec2& size) {\n  auto [backing, buffer_age] = sink_->AcquireBack(size);\n  if (!backing) {\n    return std::make_tuple(nullptr, 0);\n  }\n  back_repr_ = GetRepresentation(backing.get());\n  back_repr_->ConsumeFence(backing->GetFenceSync());\n  return std::make_tuple(back_repr_, buffer_age);\n}\n\nstd::tuple<fml::RefPtr<SharedImageRepresentation>, uint32_t>\nSharedImageSinkAccessor::AcquireBackForced(const skity::Vec2& size) {\n  auto [backing, buffer_age] = sink_->AcquireBackForced(size);\n  if (!backing) {\n    return std::make_tuple(nullptr, 0);\n  }\n  back_repr_ = GetRepresentation(backing.get());\n  back_repr_->ConsumeFence(backing->GetFenceSync());\n  return std::make_tuple(back_repr_, buffer_age);\n}\n\nstd::tuple<fml::RefPtr<SharedImageRepresentation>, uint32_t,\n           SharedImageSink::AcquireBackStatus>\nSharedImageSinkAccessor::TryAcquireBack(const skity::Vec2& size) {\n  auto [backing, buffer_age, status] = sink_->TryAcquireBack(size);\n  if (!backing) {\n    return std::make_tuple(nullptr, 0, status);\n  }\n  back_repr_ = GetRepresentation(backing.get());\n  back_repr_->ConsumeFence(backing->GetFenceSync());\n  return std::make_tuple(back_repr_, buffer_age, status);\n}\n\nbool SharedImageSinkAccessor::SwapBack() {\n  if (!back_repr_) {\n    return false;\n  }\n  if (!back_repr_->EndWrite()) {\n    FML_LOG(ERROR) << \"Failed to end write\";\n    return false;\n  }\n  std::unique_ptr<FenceSync> fence = back_repr_->ProduceFence();\n  if (!sink_->SwapBack(std::move(fence))) {\n    return false;\n  }\n  back_repr_ = nullptr;\n  return true;\n}\n\nfml::RefPtr<SharedImageRepresentation>\nSharedImageSinkAccessor::GetRepresentation(SharedImageBacking* backing) {\n  for (auto& [cached_backing, cached_repr] : image_repr_cache_) {\n    if (cached_backing == backing) {\n      return cached_repr;\n    }\n  }\n  if (image_repr_cache_.size() >= sink_->Capacity() && sink_->Capacity() > 0) {\n    image_repr_cache_.pop_front();\n  }\n  fml::RefPtr<SharedImageRepresentation> repr =\n      repr_factory_(fml::Ref(backing));\n\n  if (!repr_texture_manager_) {\n    repr_texture_manager_ = repr->GetTextureManager();\n  }\n  repr->SetTextureManager(repr_texture_manager_);\n\n  if (sink_->Capacity() > 0) {\n    image_repr_cache_.emplace_back(backing, repr);\n  }\n  return repr;\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/shared_image_sink_accessor.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_SINK_ACCESSOR_H_\n#define CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_SINK_ACCESSOR_H_\n\n#include <list>\n#include <tuple>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n\nnamespace clay {\n\nclass SharedImageBacking;\nclass SharedImageRepresentation;\nclass RepresentationStorageManager;\n\nclass SharedImageSinkAccessor {\n public:\n  using RepresentationFactory =\n      std::function<fml::RefPtr<SharedImageRepresentation>(\n          fml::RefPtr<SharedImageBacking>)>;\n\n  SharedImageSinkAccessor(fml::RefPtr<SharedImageSink> sink,\n                          const RepresentationFactory& repr_factory);\n\n  ~SharedImageSinkAccessor();\n\n  fml::RefPtr<SharedImageRepresentation> UpdateFront();\n\n  fml::RefPtr<SharedImageRepresentation> UpdateFrontSequence();\n\n  fml::RefPtr<SharedImageRepresentation> UpdateFrontToLatest();\n\n  void ReleaseFront();\n\n  std::tuple<fml::RefPtr<SharedImageRepresentation>, uint32_t> AcquireBack(\n      const skity::Vec2& size);\n\n  std::tuple<fml::RefPtr<SharedImageRepresentation>, uint32_t,\n             SharedImageSink::AcquireBackStatus>\n  TryAcquireBack(const skity::Vec2& size);\n\n  std::tuple<fml::RefPtr<SharedImageRepresentation>, uint32_t>\n  AcquireBackForced(const skity::Vec2& size);\n\n  bool SwapBack();\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SharedImageSinkAccessor);\n\n private:\n  fml::RefPtr<SharedImageRepresentation> GetRepresentation(\n      SharedImageBacking* backing);\n\n  fml::RefPtr<SharedImageSink> sink_;\n  RepresentationFactory repr_factory_;\n  fml::RefPtr<SharedImageRepresentation> front_repr_;\n  fml::RefPtr<SharedImageRepresentation> back_repr_;\n  std::list<\n      std::tuple<SharedImageBacking*, fml::RefPtr<SharedImageRepresentation>>>\n      image_repr_cache_;\n  fml::RefPtr<RepresentationStorageManager> repr_texture_manager_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_SHARED_IMAGE_SINK_ACCESSOR_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/skia_gl_image_representation.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/skia_gl_image_representation.h\"\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"third_party/skia/include/core/SkColorSpace.h\"\n#include \"third_party/skia/include/gpu/GrBackendSurface.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n#include \"third_party/skia/include/gpu/ganesh/SkImageGanesh.h\"\n#include \"third_party/skia/include/gpu/gl/GrGLTypes.h\"\n\nnamespace clay {\n\nSkiaGLImageRepresentation::SkiaGLImageRepresentation(\n    GrDirectContext* gr_context,\n    fml::RefPtr<GLImageRepresentation> gl_representation)\n    : SkiaImageRepresentation(fml::Ref(gl_representation->GetBacking())),\n      gr_context_(gr_context),\n      gl_representation_(gl_representation) {\n  FML_DCHECK(gr_context_->backend() == GrBackendApi::kOpenGL_GrBackend);\n}\n\nSkiaGLImageRepresentation::~SkiaGLImageRepresentation() = default;\n\nsk_sp<SkImage> SkiaGLImageRepresentation::GetSkImage() {\n  ClaySharedImageReadResult result;\n  if (!gl_representation_->BeginRead(&result)) {\n    return nullptr;\n  }\n  FML_DCHECK(result.struct_size == sizeof(result));\n  FML_DCHECK(result.type == kClaySharedImageRepresentationTypeGL &&\n             result.opengl_texture.struct_size == sizeof(ClayOpenGLTexture));\n\n  GrGLTextureInfo texture_info;\n  texture_info.fTarget = result.opengl_texture.target;\n  texture_info.fID = result.opengl_texture.name;\n  texture_info.fFormat = result.opengl_texture.format;\n\n  GrBackendTexture backend_texture(result.opengl_texture.size.width,\n                                   result.opengl_texture.size.height,\n                                   GrMipMapped::kNo, texture_info);\n\n  return SkImages::BorrowTextureFrom(\n      gr_context_, backend_texture, kBottomLeft_GrSurfaceOrigin,\n      kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr,\n      result.opengl_texture.destruction_callback,\n      result.opengl_texture.user_data);\n}\n\nbool SkiaGLImageRepresentation::EndRead() {\n  return gl_representation_->EndRead();\n}\n\nvoid SkiaGLImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  return gl_representation_->ConsumeFence(std::move(fence_sync));\n}\nstd::unique_ptr<FenceSync> SkiaGLImageRepresentation::ProduceFence() {\n  return gl_representation_->ProduceFence();\n}\n\nfml::RefPtr<RepresentationStorageManager>\nSkiaGLImageRepresentation::GetTextureManager() const {\n  return gl_representation_->GetTextureManager();\n}\nvoid SkiaGLImageRepresentation::SetTextureManager(\n    fml::RefPtr<RepresentationStorageManager> storage_manager) {\n  gl_representation_->SetTextureManager(storage_manager);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/skia_gl_image_representation.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_SKIA_GL_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_SKIA_GL_IMAGE_REPRESENTATION_H_\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass SkiaGLImageRepresentation final : public SkiaImageRepresentation {\n public:\n  SkiaGLImageRepresentation(\n      GrDirectContext* gr_context,\n      fml::RefPtr<GLImageRepresentation> gl_representation);\n  ~SkiaGLImageRepresentation() override;\n\n  sk_sp<SkImage> GetSkImage() override;\n  bool EndRead() override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  fml::RefPtr<RepresentationStorageManager> GetTextureManager() const override;\n  void SetTextureManager(\n      fml::RefPtr<RepresentationStorageManager> storage_manager) override;\n\n private:\n  GrDirectContext* gr_context_;\n  fml::RefPtr<GLImageRepresentation> gl_representation_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_SKIA_GL_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/skia_mtl_image_representation.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_SKIA_MTL_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_SKIA_MTL_IMAGE_REPRESENTATION_H_\n\n#import <Metal/Metal.h>\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass SharedImageBacking;\nclass MTLImageRepresentation;\n\nclass API_AVAILABLE(macos(10.6), ios(11.0),\n                    tvos(11.0)) SkiaMTLImageRepresentation final\n    : public SkiaImageRepresentation {\n public:\n  SkiaMTLImageRepresentation(GrDirectContext* gr_context,\n                             fml::RefPtr<SharedImageBacking> backing);\n  ~SkiaMTLImageRepresentation() override;\n\n  sk_sp<SkImage> GetSkImage() override;\n  bool EndRead() override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  fml::RefPtr<RepresentationStorageManager> GetTextureManager() const override;\n  void SetTextureManager(\n      fml::RefPtr<RepresentationStorageManager> storage_manager) override;\n\n private:\n  GrDirectContext* gr_context_;\n  fml::RefPtr<MTLImageRepresentation> mtl_image_representation_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_SKIA_MTL_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/skia_mtl_image_representation.mm",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/skia_mtl_image_representation.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/cv_pixelbuffer_image_backing.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/iosurface_image_backing.h\"\n#include \"clay/gfx/shared_image/mtl_image_representation.h\"\n#include \"third_party/skia/include/core/SkColorSpace.h\"\n#include \"third_party/skia/include/gpu/GrBackendSurface.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n#include \"third_party/skia/include/gpu/ganesh/SkImageGanesh.h\"\n#include \"third_party/skia/src/gpu/ganesh/GrDirectContextPriv.h\"  // nogncheck\n#include \"third_party/skia/src/gpu/ganesh/mtl/GrMtlGpu.h\"         // nogncheck\n\nnamespace clay {\n\nSkiaMTLImageRepresentation::SkiaMTLImageRepresentation(GrDirectContext* gr_context,\n                                                       fml::RefPtr<SharedImageBacking> backing)\n    : SkiaImageRepresentation(backing), gr_context_(gr_context) {\n  FML_DCHECK(gr_context_->backend() == GrBackendApi::kMetal);\n\n  GrMtlGpu* gpu = static_cast<GrMtlGpu*>(gr_context_->priv().getGpu());\n\n  mtl_image_representation_ =\n      fml::MakeRefCounted<MTLImageRepresentation>(gpu->device(), gpu->queue(), backing);\n}\n\nSkiaMTLImageRepresentation::~SkiaMTLImageRepresentation() = default;\n\nsk_sp<SkImage> SkiaMTLImageRepresentation::GetSkImage() {\n  skity::Vec2 size = mtl_image_representation_->GetSize();\n\n  ClaySharedImageReadResult result;\n  if (!mtl_image_representation_->BeginRead(&result)) {\n    return nullptr;\n  }\n  FML_DCHECK(result.struct_size == sizeof(result));\n  FML_DCHECK(result.type == kClaySharedImageRepresentationTypeMetal &&\n             result.metal_texture.struct_size == sizeof(ClayMetalTexture));\n  GrMtlTextureInfo skia_texture_info;\n  skia_texture_info.fTexture =\n      sk_cfp{CFBridgingRetain((__bridge id<MTLTexture>)result.metal_texture.texture)};\n\n  GrBackendTexture skia_backend_texture(size.x, size.y, GrMipMapped ::kNo, skia_texture_info);\n\n  // We always use OpenGL texture coordinate as the standard\n  return SkImages::BorrowTextureFrom(gr_context_, skia_backend_texture, kBottomLeft_GrSurfaceOrigin,\n                                     kBGRA_8888_SkColorType, kPremul_SkAlphaType, nullptr,\n                                     result.metal_texture.destruction_callback,\n                                     result.metal_texture.user_data);\n}\n\nbool SkiaMTLImageRepresentation::EndRead() { return mtl_image_representation_->EndRead(); }\n\nvoid SkiaMTLImageRepresentation::ConsumeFence(std::unique_ptr<FenceSync> fence_sync) {\n  return mtl_image_representation_->ConsumeFence(std::move(fence_sync));\n}\n\nstd::unique_ptr<FenceSync> SkiaMTLImageRepresentation::ProduceFence() {\n  return mtl_image_representation_->ProduceFence();\n}\n\nfml::RefPtr<RepresentationStorageManager> SkiaMTLImageRepresentation::GetTextureManager() const {\n  return mtl_image_representation_->GetTextureManager();\n}\nvoid SkiaMTLImageRepresentation::SetTextureManager(\n    fml::RefPtr<RepresentationStorageManager> storage_manager) {\n  mtl_image_representation_->SetTextureManager(storage_manager);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/skia_shm_image_representation.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/skia_shm_image_representation.h\"\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <unistd.h>\n\n#include <fstream>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n\nnamespace clay {\n\nSkiaShmImageRepresentation::SkiaShmImageRepresentation(\n    fml::RefPtr<SharedImageRepresentation> representation)\n    : SkiaImageRepresentation(fml::Ref(representation->GetBacking())),\n      representation_(representation) {}\n\nSkiaShmImageRepresentation::~SkiaShmImageRepresentation() = default;\n\nsk_sp<SkImage> SkiaShmImageRepresentation::GetSkImage() {\n  ClaySharedImageReadResult result;\n  if (!representation_->BeginRead(&result)) {\n    FML_LOG(ERROR)\n        << \"BeginRead failed in SkiaShmImageRepresentation::GetSkImage, \"\n           \"returning nullptr.\";\n    return nullptr;\n  }\n  FML_DCHECK(result.struct_size == sizeof(result));\n  FML_DCHECK(result.type == kClaySharedImageRepresentationTypeShm &&\n             result.shm_image.struct_size == sizeof(ClaySharedMemoryImage));\n\n  GrImageInfo info =\n      GrImageInfo::Make(GetSize().x, GetSize().y, kRGBA_8888_SkColorType,\n                        kPremul_SkAlphaType, SkColorSpace::MakeSRGB());\n\n  void* shm_buffer = mmap(nullptr, GetSize().x * GetSize().y * 4, PROT_READ,\n                          MAP_SHARED, result.shm_image.shm_fd, 0);\n\n  size_t rowBytes = GetSize().x * 4;\n  SkPixmap pixmap(info, shm_buffer, rowBytes);\n  sk_sp<SkImage> sk_image = SkImages::RasterFromPixmapCopy(pixmap);\n  auto surface = SkSurface::MakeRaster(info);\n  auto canvas = surface->getCanvas();\n  canvas->translate(0, sk_image->height());\n  canvas->scale(1, -1);\n  canvas->drawImage(sk_image, 0, 0);\n  return surface->makeImageSnapshot();\n}\n\nbool SkiaShmImageRepresentation::EndRead() {\n  return representation_->EndRead();\n}\n\nvoid SkiaShmImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  return representation_->ConsumeFence(std::move(fence_sync));\n}\nstd::unique_ptr<FenceSync> SkiaShmImageRepresentation::ProduceFence() {\n  return representation_->ProduceFence();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/skia_shm_image_representation.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_SKIA_SHM_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_SKIA_SHM_IMAGE_REPRESENTATION_H_\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass SkiaShmImageRepresentation final : public SkiaImageRepresentation {\n public:\n  explicit SkiaShmImageRepresentation(\n      fml::RefPtr<SharedImageRepresentation> representation);\n  ~SkiaShmImageRepresentation() override;\n\n  sk_sp<SkImage> GetSkImage() override;\n  bool EndRead() override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n private:\n  fml::RefPtr<SharedImageRepresentation> representation_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_SKIA_SHM_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/skity_gl_image_representation.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/skity_gl_image_representation.h\"\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/gfx/shared_image/utils/gl_texture_converter.h\"\n#include \"clay/gfx/skity/skity_image.h\"\n#include \"skity/gpu/gpu_context.hpp\"\n#include \"skity/gpu/gpu_context_gl.hpp\"\n\nnamespace clay {\n\nSkityGLImageRepresentation::SkityGLImageRepresentation(\n    skity::GPUContext* skity_context,\n    fml::RefPtr<GLImageRepresentation> gl_representation)\n    : SkityImageRepresentation(fml::Ref(gl_representation->GetBacking())),\n      skity_context_(skity_context),\n      gl_representation_(gl_representation) {\n  FML_DCHECK(skity_context_->GetBackendType() ==\n             skity::GPUBackendType::kOpenGL);\n}\n\nSkityGLImageRepresentation::~SkityGLImageRepresentation() = default;\n\nstd::shared_ptr<SkityImage> SkityGLImageRepresentation::GetSkityImage() {\n  ClaySharedImageReadResult result;\n  if (!gl_representation_->BeginRead(&result)) {\n    return nullptr;\n  }\n  FML_DCHECK(result.struct_size == sizeof(result));\n  FML_DCHECK(result.type == kClaySharedImageRepresentationTypeGL &&\n             result.opengl_texture.struct_size == sizeof(ClayOpenGLTexture));\n\n  GLuint texture_2d = 0;\n  uint32_t texture_width = result.opengl_texture.size.width;\n  uint32_t texture_height = result.opengl_texture.size.height;\n  if (result.opengl_texture.target == GL_TEXTURE_EXTERNAL_OES) {\n    texture_2d = Get2DTextureFromExternalTextureOES(\n        result.opengl_texture.name, texture_width, texture_height);\n  } else {\n    texture_2d = result.opengl_texture.name;\n  }\n  if (texture_2d == 0) {\n    return nullptr;\n  }\n\n  skity::GPUBackendTextureInfoGL texture_info = {\n      {skity::GPUBackendType::kOpenGL, texture_width, texture_height,\n       skity::TextureFormat::kRGBA, skity::AlphaType::kPremul_AlphaType},\n      texture_2d,\n      // We have created a new GL_TEXTURE_2D texture for converting\n      // GL_TEXTUER_EXTERNAL_OES texture. In that case we assume that the extra\n      // GL_TEXTURE_2D texture is owned by Skity.\n      result.opengl_texture.target == GL_TEXTURE_EXTERNAL_OES ? true : false};\n\n  auto texture = skity_context_->WrapTexture(&texture_info);\n  // TODO(yudingqian): This is a workaround that we create a `SkityImage` which\n  // wraps the `skity::Image`. Because we need the `destruction_callback` to\n  // release the texture and Skity doesn't provide this parameter for now.\n  return std::make_shared<SkityImage>(\n      skity::Image::MakeHWImage(texture),\n      result.opengl_texture.destruction_callback,\n      result.opengl_texture.user_data);\n}\n\nbool SkityGLImageRepresentation::EndRead() {\n  return gl_representation_->EndRead();\n}\n\nvoid SkityGLImageRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  return gl_representation_->ConsumeFence(std::move(fence_sync));\n}\nstd::unique_ptr<FenceSync> SkityGLImageRepresentation::ProduceFence() {\n  return gl_representation_->ProduceFence();\n}\n\nfml::RefPtr<RepresentationStorageManager>\nSkityGLImageRepresentation::GetTextureManager() const {\n  return gl_representation_->GetTextureManager();\n}\nvoid SkityGLImageRepresentation::SetTextureManager(\n    fml::RefPtr<RepresentationStorageManager> storage_manager) {\n  gl_representation_->SetTextureManager(storage_manager);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/skity_gl_image_representation.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_SKITY_GL_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_SKITY_GL_IMAGE_REPRESENTATION_H_\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n#include \"skity/graphic/image.hpp\"\n\nnamespace clay {\n\nclass SkityGLImageRepresentation final : public SkityImageRepresentation {\n public:\n  SkityGLImageRepresentation(\n      skity::GPUContext* skity_context,\n      fml::RefPtr<GLImageRepresentation> gl_representation);\n  ~SkityGLImageRepresentation() override;\n\n  std::shared_ptr<SkityImage> GetSkityImage() override;\n  bool EndRead() override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  fml::RefPtr<RepresentationStorageManager> GetTextureManager() const override;\n  void SetTextureManager(\n      fml::RefPtr<RepresentationStorageManager> storage_manager) override;\n\n private:\n  skity::GPUContext* skity_context_;\n  fml::RefPtr<GLImageRepresentation> gl_representation_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_SKITY_GL_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/skity_mtl_image_representation.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_SKITY_MTL_IMAGE_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_SKITY_MTL_IMAGE_REPRESENTATION_H_\n\n#import <Metal/Metal.h>\n\n#include <memory>\n\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/shared_image/shared_image_representation.h\"\n\nnamespace clay {\n\nclass IOSurfaceImageBacking;\nclass CVPixelBufferImageBacking;\nclass MTLImageRepresentation;\n\nclass API_AVAILABLE(macos(10.6), ios(11.0),\n                    tvos(11.0)) SkityMTLImageRepresentation final\n    : public SkityImageRepresentation {\n public:\n  SkityMTLImageRepresentation(GrContext* skity_context,\n                              fml::RefPtr<IOSurfaceImageBacking> backing);\n  SkityMTLImageRepresentation(GrContext* skity_context,\n                              fml::RefPtr<CVPixelBufferImageBacking> backing);\n  ~SkityMTLImageRepresentation() override;\n\n  std::shared_ptr<SkityImage> GetSkityImage() override;\n  bool EndRead() override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync> fence_sync) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  fml::RefPtr<RepresentationStorageManager> GetTextureManager() const override;\n  void SetTextureManager(\n      fml::RefPtr<RepresentationStorageManager> storage_manager) override;\n\n private:\n  GrContext* skity_context_;\n  fml::RefPtr<MTLImageRepresentation> mtl_image_representation_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_SKITY_MTL_IMAGE_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/skity_mtl_image_representation.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/skity_mtl_image_representation.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/cv_pixelbuffer_image_backing.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/iosurface_image_backing.h\"\n#include \"clay/gfx/shared_image/mtl_image_representation.h\"\n#include \"clay/gfx/skity/skity_image.h\"\n#include \"skity/gpu/gpu_context.hpp\"\n#include \"skity/gpu/gpu_context_mtl.h\"\n\nnamespace clay {\n\nSkityMTLImageRepresentation::SkityMTLImageRepresentation(skity::GPUContext* skity_context,\n                                                         fml::RefPtr<IOSurfaceImageBacking> backing)\n    : SkityImageRepresentation(backing), skity_context_(skity_context) {\n  id<MTLDevice> mtl_device = skity::MTLContextGetDevice(skity_context);\n  id<MTLCommandQueue> mtl_queue = skity::MTLContextGetCommandQueue(skity_context);\n\n  mtl_image_representation_ =\n      fml::MakeRefCounted<MTLImageRepresentation>(mtl_device, mtl_queue, backing);\n}\n\nSkityMTLImageRepresentation::SkityMTLImageRepresentation(\n    skity::GPUContext* skity_context, fml::RefPtr<CVPixelBufferImageBacking> backing)\n    : SkityImageRepresentation(backing), skity_context_(skity_context) {\n  id<MTLDevice> mtl_device = skity::MTLContextGetDevice(skity_context);\n  id<MTLCommandQueue> mtl_queue = skity::MTLContextGetCommandQueue(skity_context);\n\n  mtl_image_representation_ =\n      fml::MakeRefCounted<MTLImageRepresentation>(mtl_device, mtl_queue, backing);\n}\n\nSkityMTLImageRepresentation::~SkityMTLImageRepresentation() = default;\n\nstd::shared_ptr<SkityImage> SkityMTLImageRepresentation::GetSkityImage() {\n  skity::Vec2 size = mtl_image_representation_->GetSize();\n\n  ClaySharedImageReadResult result;\n  if (!mtl_image_representation_->BeginRead(&result)) {\n    return nullptr;\n  }\n  FML_DCHECK(result.struct_size == sizeof(result));\n  FML_DCHECK(result.type == kClaySharedImageRepresentationTypeMetal &&\n             result.metal_texture.struct_size == sizeof(ClayMetalTexture));\n\n  skity::GPUBackendTextureInfoMTL texture_info = {\n      {skity::GPUBackendType::kMetal, static_cast<uint32_t>(size.x), static_cast<uint32_t>(size.y),\n       skity::TextureFormat::kBGRA, skity::AlphaType::kPremul_AlphaType},\n      (__bridge id<MTLTexture>)result.metal_texture.texture};\n\n  auto texture = skity_context_->WrapTexture(&texture_info);\n  // TODO(yudingqian): This is a workaround that we create a `SkityImage` which wraps the\n  // `skity::Image`. Because we need the `destruction_callback` to release the texture and Skity\n  // doesn't provide this parameter for now.\n  return std::make_shared<SkityImage>(skity::Image::MakeHWImage(texture),\n                                      result.metal_texture.destruction_callback,\n                                      result.metal_texture.user_data);\n}\n\nbool SkityMTLImageRepresentation::EndRead() { return mtl_image_representation_->EndRead(); }\n\nvoid SkityMTLImageRepresentation::ConsumeFence(std::unique_ptr<FenceSync> fence_sync) {\n  return mtl_image_representation_->ConsumeFence(std::move(fence_sync));\n}\n\nstd::unique_ptr<FenceSync> SkityMTLImageRepresentation::ProduceFence() {\n  return mtl_image_representation_->ProduceFence();\n}\n\nfml::RefPtr<RepresentationStorageManager> SkityMTLImageRepresentation::GetTextureManager() const {\n  return mtl_image_representation_->GetTextureManager();\n}\nvoid SkityMTLImageRepresentation::SetTextureManager(\n    fml::RefPtr<RepresentationStorageManager> storage_manager) {\n  mtl_image_representation_->SetTextureManager(storage_manager);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/angle_get_proc.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// clang-format off\n// include symbols `eglGetProcAddress` from ANGLE\n#undef EGL_EGL_PROTOTYPES\n#define EGL_EGLEXT_PROTOTYPES 1 // cspell:disable-line\n#include <EGL/egl.h>\n// clang-format on\n\n#include \"clay/gfx/shared_image/utils/angle_get_proc.h\"\n\nnamespace clay {\n\nPFNEGLGETPROCADDRESSPROC GetAngleEglGetProcAddressProc() {\n  return eglGetProcAddress;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/angle_get_proc.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_UTILS_ANGLE_GET_PROC_H_\n#define CLAY_GFX_SHARED_IMAGE_UTILS_ANGLE_GET_PROC_H_\n\n#include <EGL/egl.h>\n\nnamespace clay {\n\nPFNEGLGETPROCADDRESSPROC GetAngleEglGetProcAddressProc();\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_UTILS_ANGLE_GET_PROC_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/dxgi_utils.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/utils/dxgi_utils.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nScopedDXGIKeyedMutex::ScopedDXGIKeyedMutex(IDXGIKeyedMutex* keyed_mutex)\n    : keyed_mutex_(keyed_mutex) {\n  FML_DCHECK(keyed_mutex_);\n  HRESULT result = keyed_mutex_->AcquireSync(0, INFINITE);\n  if (result != S_OK) {\n    // Maybe failed or abandoned\n    FML_LOG(ERROR) << \"Failed to acquire keyed mutex, result: \" << result;\n    keyed_mutex_.Reset();\n  }\n}\n\nScopedDXGIKeyedMutex::~ScopedDXGIKeyedMutex() {\n  if (keyed_mutex_) {\n    if (FAILED(keyed_mutex_->ReleaseSync(0))) {\n      FML_LOG(ERROR) << \"Failed to release keyed mutex\";\n    }\n  }\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/dxgi_utils.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_UTILS_DXGI_UTILS_H_\n#define CLAY_GFX_SHARED_IMAGE_UTILS_DXGI_UTILS_H_\n\n#include <dxgi.h>\n#include <wrl/client.h>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace clay {\n\nclass ScopedDXGIKeyedMutex {\n public:\n  explicit ScopedDXGIKeyedMutex(IDXGIKeyedMutex* keyed_mutex);\n\n  ~ScopedDXGIKeyedMutex();\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ScopedDXGIKeyedMutex);\n\n private:\n  Microsoft::WRL::ComPtr<IDXGIKeyedMutex> keyed_mutex_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_UTILS_DXGI_UTILS_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/functions_angle.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/utils/functions_angle.h\"\n\nnamespace clay {\n\ntemplate <typename T>\nstatic void GetEGLProcAddress(PFNEGLGETPROCADDRESSPROC eglGetProcAddressProc,\n                              const std::string& proc_name,\n                              T* out_proc_address) {\n  T proc = nullptr;\n  proc = reinterpret_cast<T>(eglGetProcAddressProc(proc_name.c_str()));\n  *out_proc_address = proc;\n}\n\nFunctionsAngle::FunctionsAngle(PFNEGLGETPROCADDRESSPROC eglGetProcAddressProc)\n    : eglGetProcAddressProc(eglGetProcAddressProc) {\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglGetCurrentDisplay\",\n                    &eglGetCurrentDisplayProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglGetCurrentContext\",\n                    &eglGetCurrentContextProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglQueryString\",\n                    &eglQueryStringProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglGetError\", &eglGetErrorProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglCreateImageKHR\",\n                    &eglCreateImageKHRProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglDestroyImageKHR\",\n                    &eglDestroyImageKHRProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglBindTexImage\",\n                    &eglBindTexImageProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglReleaseTexImage\",\n                    &eglReleaseTexImageProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglDestroySurface\",\n                    &eglDestroySurfaceProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglGetConfigs\", &eglGetConfigsProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"eglGetConfigAttrib\",\n                    &eglGetConfigAttribProc);\n\n  GetEGLProcAddress(eglGetProcAddressProc, \"glGetError\", &glGetErrorProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glGenTextures\", &glGenTexturesProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glBindTexture\", &glBindTextureProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glTexParameteri\",\n                    &glTexParameteriProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glGetIntegerv\", &glGetIntegervProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glDeleteTextures\",\n                    &glDeleteTexturesProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glGenFramebuffers\",\n                    &glGenFramebuffersProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glBindFramebuffer\",\n                    &glBindFramebufferProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glFramebufferTexture2D\",\n                    &glFramebufferTexture2DProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glCheckFramebufferStatus\",\n                    &glCheckFramebufferStatusProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glDeleteFramebuffers\",\n                    &glDeleteFramebuffersProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glReadPixels\", &glReadPixelsProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glActiveTexture\",\n                    &glActiveTextureProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glClearColor\", &glClearColorProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glClear\", &glClearProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glFinish\", &glFinishProc);\n  GetEGLProcAddress(eglGetProcAddressProc, \"glFlush\", &glFlushProc);\n\n  EGLDisplay display = eglGetCurrentDisplay();\n  const char* extensions = eglQueryString(display, EGL_EXTENSIONS);\n  if (!strstr(extensions, \"EGL_KHR_fence_sync\")) {\n    angle_fence_supported_ = false;\n  } else {\n    GetEGLProcAddress(eglGetProcAddressProc, \"eglCreateSyncKHR\",\n                      &eglCreateSyncKHRProc);\n    GetEGLProcAddress(eglGetProcAddressProc, \"eglDestroySyncKHR\",\n                      &eglDestroySyncKHRProc);\n    GetEGLProcAddress(eglGetProcAddressProc, \"eglClientWaitSyncKHR\",\n                      &eglClientWaitSyncKHRProc);\n    GetEGLProcAddress(eglGetProcAddressProc, \"eglWaitSyncKHR\",\n                      &eglWaitSyncKHRProc);\n    angle_fence_supported_ = eglCreateSyncKHRProc && eglDestroySyncKHRProc &&\n                             eglClientWaitSyncKHRProc && eglWaitSyncKHRProc;\n  }\n}\n\n#define CHECK_VOID_PROC(proc)                                             \\\n  if (proc == nullptr) {                                                  \\\n    FML_LOG(ERROR) << \"FunctionsAngle: \" << #proc << \" is unavailable !\"; \\\n    return;                                                               \\\n  }\n\n#define CHECK_PROC(proc, res)                                             \\\n  if (proc == nullptr) {                                                  \\\n    FML_LOG(ERROR) << \"FunctionsAngle: \" << #proc << \" is unavailable !\"; \\\n    return res;                                                           \\\n  }\n\nGLenum FunctionsAngle::glGetError() const {\n  CHECK_PROC(glGetErrorProc, 0);\n  return glGetErrorProc();\n}\nvoid FunctionsAngle::glGenTextures(GLsizei n, GLuint* textures) const {\n  CHECK_VOID_PROC(glGenTexturesProc)\n  glGenTexturesProc(n, textures);\n}\nvoid FunctionsAngle::glBindTexture(GLenum target, GLuint texture) const {\n  CHECK_VOID_PROC(glBindTextureProc)\n  glBindTextureProc(target, texture);\n}\nvoid FunctionsAngle::glTexParameteri(GLenum target, GLenum pname,\n                                     GLint param) const {\n  CHECK_VOID_PROC(glTexParameteriProc)\n  glTexParameteriProc(target, pname, param);\n}\nvoid FunctionsAngle::glGetIntegerv(GLenum pname, GLint* data) const {\n  CHECK_VOID_PROC(glGetIntegervProc)\n  glGetIntegervProc(pname, data);\n}\nvoid FunctionsAngle::glDeleteTextures(GLsizei n, const GLuint* textures) const {\n  CHECK_VOID_PROC(glDeleteTexturesProc)\n  glDeleteTexturesProc(n, textures);\n}\nvoid FunctionsAngle::glGenFramebuffers(GLsizei n, GLuint* framebuffers) const {\n  CHECK_VOID_PROC(glGenFramebuffersProc)\n  glGenFramebuffersProc(n, framebuffers);\n}\nvoid FunctionsAngle::glBindFramebuffer(GLenum target,\n                                       GLuint framebuffer) const {\n  CHECK_VOID_PROC(glBindFramebufferProc)\n  glBindFramebufferProc(target, framebuffer);\n}\nvoid FunctionsAngle::glFramebufferTexture2D(GLenum target, GLenum attachment,\n                                            GLenum textarget, GLuint texture,\n                                            GLint level) const {\n  CHECK_VOID_PROC(glFramebufferTexture2DProc)\n  glFramebufferTexture2DProc(target, attachment, textarget, texture, level);\n}\nGLenum FunctionsAngle::glCheckFramebufferStatus(GLenum target) const {\n#ifndef NDEBUG\n  CHECK_PROC(glCheckFramebufferStatusProc, 0)\n  return glCheckFramebufferStatusProc(target);\n#else\n  return GL_FRAMEBUFFER_COMPLETE;\n#endif\n}\nvoid FunctionsAngle::glDeleteFramebuffers(GLsizei n,\n                                          const GLuint* framebuffers) const {\n  CHECK_VOID_PROC(glDeleteFramebuffersProc)\n  glDeleteFramebuffersProc(n, framebuffers);\n}\nvoid FunctionsAngle::glReadPixels(GLint x, GLint y, GLsizei width,\n                                  GLsizei height, GLenum format, GLenum type,\n                                  void* pixels) const {\n  CHECK_VOID_PROC(glReadPixelsProc)\n  glReadPixelsProc(x, y, width, height, format, type, pixels);\n}\nvoid FunctionsAngle::glActiveTexture(GLenum texture) const {\n  CHECK_VOID_PROC(glActiveTextureProc)\n  glActiveTextureProc(texture);\n}\nvoid FunctionsAngle::glClearColor(GLfloat red, GLfloat green, GLfloat blue,\n                                  GLfloat alpha) const {\n  CHECK_VOID_PROC(glClearColorProc)\n  glClearColorProc(red, green, blue, alpha);\n}\nvoid FunctionsAngle::glClear(GLbitfield mask) const {\n  CHECK_VOID_PROC(glClearProc)\n  glClearProc(mask);\n}\nvoid FunctionsAngle::glFinish() const {\n  CHECK_VOID_PROC(glFinishProc)\n  glFinishProc();\n}\nvoid FunctionsAngle::glFlush() const {\n  CHECK_VOID_PROC(glFlushProc)\n  glFlushProc();\n}\n\nEGLDisplay FunctionsAngle::eglGetCurrentDisplay() const {\n  CHECK_PROC(eglGetCurrentDisplayProc, nullptr);\n  return eglGetCurrentDisplayProc();\n}\n\nEGLContext FunctionsAngle::eglGetCurrentContext() const {\n  CHECK_PROC(eglGetCurrentContextProc, EGL_NO_CONTEXT);\n  return eglGetCurrentContextProc();\n}\n\nconst char* FunctionsAngle::eglQueryString(EGLDisplay dpy, EGLint name) const {\n  CHECK_PROC(eglQueryStringProc, nullptr);\n  return eglQueryStringProc(dpy, name);\n}\n\nEGLint FunctionsAngle::eglGetError() const {\n  CHECK_PROC(eglGetErrorProc, 0);\n  return eglGetErrorProc();\n}\n\nEGLImageKHR FunctionsAngle::eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx,\n                                              EGLenum target,\n                                              EGLClientBuffer buffer,\n                                              const EGLint* attrib_list) const {\n  CHECK_PROC(eglCreateImageKHRProc, nullptr);\n  return eglCreateImageKHRProc(dpy, ctx, target, buffer, attrib_list);\n}\n\nEGLBoolean FunctionsAngle::eglDestroyImageKHR(EGLDisplay dpy,\n                                              EGLImageKHR image) const {\n  CHECK_PROC(eglDestroyImageKHRProc, false);\n  return eglDestroyImageKHRProc(dpy, image);\n}\n\nEGLBoolean FunctionsAngle::eglBindTexImage(EGLDisplay dpy, EGLSurface surface,\n                                           EGLint buffer) const {\n  CHECK_PROC(eglBindTexImageProc, false);\n  return eglBindTexImageProc(dpy, surface, buffer);\n}\n\nEGLBoolean FunctionsAngle::eglReleaseTexImage(EGLDisplay dpy,\n                                              EGLSurface surface,\n                                              EGLint buffer) const {\n  CHECK_PROC(eglReleaseTexImageProc, false);\n  return eglReleaseTexImageProc(dpy, surface, buffer);\n}\n\nEGLBoolean FunctionsAngle::eglDestroySurface(EGLDisplay dpy,\n                                             EGLSurface surface) const {\n  CHECK_PROC(eglDestroySurfaceProc, false);\n  return eglDestroySurfaceProc(dpy, surface);\n}\n\nEGLBoolean FunctionsAngle::eglGetConfigs(EGLDisplay dpy, EGLConfig* configs,\n                                         EGLint config_size,\n                                         EGLint* num_config) const {\n  CHECK_PROC(eglGetConfigsProc, false);\n  return eglGetConfigsProc(dpy, configs, config_size, num_config);\n}\n\nEGLBoolean FunctionsAngle::eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config,\n                                              EGLint attribute,\n                                              EGLint* value) const {\n  CHECK_PROC(eglGetConfigAttribProc, false);\n  return eglGetConfigAttribProc(dpy, config, attribute, value);\n}\n\nEGLSyncKHR FunctionsAngle::eglCreateSyncKHR(EGLDisplay dpy, EGLenum type,\n                                            const EGLint* attrib_list) const {\n  CHECK_PROC(eglCreateSyncKHRProc, nullptr)\n  return eglCreateSyncKHRProc(dpy, type, attrib_list);\n}\nEGLBoolean FunctionsAngle::eglDestroySyncKHR(EGLDisplay dpy,\n                                             EGLSyncKHR sync) const {\n  CHECK_PROC(eglDestroySyncKHRProc, false)\n  return eglDestroySyncKHRProc(dpy, sync);\n}\nEGLint FunctionsAngle::eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync,\n                                            EGLint flags,\n                                            EGLTimeKHR timeout) const {\n  CHECK_PROC(eglClientWaitSyncKHRProc, 0)\n  return eglClientWaitSyncKHRProc(dpy, sync, flags, timeout);\n}\nEGLint FunctionsAngle::eglWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync,\n                                      EGLint flags) const {\n  CHECK_PROC(eglWaitSyncKHRProc, 0)\n  return eglWaitSyncKHRProc(dpy, sync, flags);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/functions_angle.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_UTILS_FUNCTIONS_ANGLE_H_\n#define CLAY_GFX_SHARED_IMAGE_UTILS_FUNCTIONS_ANGLE_H_\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <GLES2/gl2.h>\n\n#include <fstream>\n#include <sstream>\n#include <string>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\n#define GL_RGBA8 0x8058\n\nclass FunctionsAngle {\n public:\n  explicit FunctionsAngle(PFNEGLGETPROCADDRESSPROC eglGetProcAddressProc);\n  ~FunctionsAngle() = default;\n\n  GLenum glGetError() const;\n  void glGenTextures(GLsizei n, GLuint* textures) const;\n  void glBindTexture(GLenum target, GLuint texture) const;\n  void glTexParameteri(GLenum target, GLenum pname, GLint param) const;\n  void glGetIntegerv(GLenum pname, GLint* data) const;\n  void glDeleteTextures(GLsizei n, const GLuint* textures) const;\n  void glGenFramebuffers(GLsizei n, GLuint* framebuffers) const;\n  void glBindFramebuffer(GLenum target, GLuint framebuffer) const;\n  void glFramebufferTexture2D(GLenum target, GLenum attachment,\n                              GLenum textarget, GLuint texture,\n                              GLint level) const;\n  GLenum glCheckFramebufferStatus(GLenum target) const;\n  void glDeleteFramebuffers(GLsizei n, const GLuint* framebuffers) const;\n  void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,\n                    GLenum format, GLenum type, void* pixels) const;\n  void glActiveTexture(GLenum texture) const;\n  void glClearColor(GLfloat red, GLfloat green, GLfloat blue,\n                    GLfloat alpha) const;\n  void glClear(GLbitfield mask) const;\n  void glFinish() const;\n  void glFlush() const;\n\n  EGLDisplay eglGetCurrentDisplay() const;\n  EGLContext eglGetCurrentContext() const;\n\n  const char* eglQueryString(EGLDisplay dpy, EGLint name) const;\n  EGLint eglGetError() const;\n  EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target,\n                                EGLClientBuffer buffer,\n                                const EGLint* attrib_list) const;\n  EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR image) const;\n  EGLBoolean eglBindTexImage(EGLDisplay dpy, EGLSurface surface,\n                             EGLint buffer) const;\n  EGLBoolean eglReleaseTexImage(EGLDisplay dpy, EGLSurface surface,\n                                EGLint buffer) const;\n  EGLBoolean eglDestroySurface(EGLDisplay dpy, EGLSurface surface) const;\n  EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig* configs,\n                           EGLint config_size, EGLint* num_config) const;\n  EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config,\n                                EGLint attribute, EGLint* value) const;\n\n  EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type,\n                              const EGLint* attrib_list) const;\n  EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync) const;\n  EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags,\n                              EGLTimeKHR timeout) const;\n  EGLint eglWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags) const;\n\n  PFNGLGETERRORPROC glGetErrorProc = nullptr;\n  PFNGLGENTEXTURESPROC glGenTexturesProc = nullptr;\n  PFNGLBINDTEXTUREPROC glBindTextureProc = nullptr;\n  PFNGLTEXPARAMETERIPROC glTexParameteriProc = nullptr;\n  PFNGLGETINTEGERVPROC glGetIntegervProc = nullptr;\n  PFNGLDELETETEXTURESPROC glDeleteTexturesProc = nullptr;\n  PFNGLGENFRAMEBUFFERSPROC glGenFramebuffersProc = nullptr;\n  PFNGLBINDFRAMEBUFFERPROC glBindFramebufferProc = nullptr;\n  PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2DProc = nullptr;\n  PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatusProc = nullptr;\n  PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffersProc = nullptr;\n  PFNGLREADPIXELSPROC glReadPixelsProc = nullptr;\n  PFNGLACTIVETEXTUREPROC glActiveTextureProc = nullptr;\n  PFNGLCLEARCOLORPROC glClearColorProc = nullptr;\n  PFNGLCLEARPROC glClearProc = nullptr;\n  PFNGLFINISHPROC glFinishProc = nullptr;\n  PFNGLFLUSHPROC glFlushProc = nullptr;\n\n  PFNEGLCREATESYNCKHRPROC eglCreateSyncKHRProc = nullptr;\n  PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHRProc = nullptr;\n  PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHRProc = nullptr;\n  PFNEGLWAITSYNCKHRPROC eglWaitSyncKHRProc = nullptr;\n  PFNEGLGETCURRENTDISPLAYPROC eglGetCurrentDisplayProc = nullptr;\n  PFNEGLGETCURRENTCONTEXTPROC eglGetCurrentContextProc = nullptr;\n  PFNEGLQUERYSTRINGPROC eglQueryStringProc = nullptr;\n  PFNEGLGETERRORPROC eglGetErrorProc = nullptr;\n  PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHRProc = nullptr;\n  PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHRProc = nullptr;\n  PFNEGLBINDTEXIMAGEPROC eglBindTexImageProc = nullptr;\n  PFNEGLRELEASETEXIMAGEPROC eglReleaseTexImageProc = nullptr;\n  PFNEGLDESTROYSURFACEPROC eglDestroySurfaceProc = nullptr;\n  PFNEGLGETCONFIGSPROC eglGetConfigsProc = nullptr;\n  PFNEGLGETCONFIGATTRIBPROC eglGetConfigAttribProc = nullptr;\n\n  PFNEGLGETPROCADDRESSPROC eglGetProcAddressProc = nullptr;\n\n  bool angle_fence_supported_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_UTILS_FUNCTIONS_ANGLE_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/gl_texture_converter.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/utils/gl_texture_converter.h\"\n\n#include \"base/include/closure.h\"\n#include \"clay/common/graphics/gl/gl_pipeline_helper.h\"\n#include \"clay/common/graphics/gl/gl_shader_program.h\"\n#include \"clay/common/graphics/gl/scoped_framebuffer_binder.h\"\n#include \"clay/common/graphics/gl/scoped_texture_binder.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nGLuint Get2DTextureFromExternalTextureOES(GLuint oes_texture_id,\n                                          uint32_t texture_width,\n                                          uint32_t texture_height) {\n  GLint old_framebuffer;\n  glGetIntegerv(GL_FRAMEBUFFER_BINDING, &old_framebuffer);\n\n  GLuint fbo;\n  glGenFramebuffers(1, &fbo);\n  glBindFramebuffer(GL_FRAMEBUFFER, fbo);\n  fml::ScopedCleanupClosure restore_fbo([old_framebuffer, fbo]() {\n    glBindFramebuffer(GL_FRAMEBUFFER, old_framebuffer);\n    glDeleteFramebuffers(1, &fbo);\n  });\n\n  GLuint texture_2d;\n  glGenTextures(1, &texture_2d);\n  clay::ScopedTextureBinder scoped_2d_texture_binder(GL_TEXTURE_2D, texture_2d);\n  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_width, texture_height, 0,\n               GL_RGBA, GL_UNSIGNED_BYTE, nullptr);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\n  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,\n                         texture_2d, 0);\n\n#ifndef NDEBUG\n  GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);\n#else\n  GLenum framebuffer_status = GL_FRAMEBUFFER_COMPLETE;\n#endif\n  if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {\n    FML_LOG(ERROR) << \"Framebuffer is not complete\";\n    glDeleteTextures(1, &texture_2d);\n    return 0;\n  }\n\n  clay::GlPipelineHelper* pipeline_helper =\n      clay::GlPipelineHelper::GetInstance();\n  GLint current_program;\n  glGetIntegerv(GL_CURRENT_PROGRAM, &current_program);\n  fml::ScopedCleanupClosure restore_program(\n      [program = current_program]() { glUseProgram(program); });\n  GLint current_vbo;\n  glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &current_vbo);\n  fml::ScopedCleanupClosure restore_buffer(\n      [vbo = current_vbo]() { glBindBuffer(GL_ARRAY_BUFFER, vbo); });\n\n  if (!pipeline_helper->Initialize()) {\n    FML_LOG(ERROR) << \"GL pipeline is not complete\";\n    glDeleteTextures(1, &texture_2d);\n    return 0;\n  }\n\n  pipeline_helper->Bind();\n\n  GLint active_texture;\n  glGetIntegerv(GL_ACTIVE_TEXTURE, &active_texture);\n  fml::ScopedCleanupClosure restore_texture(\n      [active_texture]() { glActiveTexture(active_texture); });\n\n  glActiveTexture(GL_TEXTURE0);\n  clay::ScopedTextureBinder scoped_external_texture_binder(\n      GL_TEXTURE_EXTERNAL_OES, oes_texture_id);\n\n  glViewport(0, 0, texture_width, texture_height);\n  glClear(GL_COLOR_BUFFER_BIT);\n  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n  pipeline_helper->UnBind();\n  return texture_2d;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/gl_texture_converter.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_UTILS_GL_TEXTURE_CONVERTER_H_\n#define CLAY_GFX_SHARED_IMAGE_UTILS_GL_TEXTURE_CONVERTER_H_\n\n#include <GLES2/gl2.h>\n#include <GLES2/gl2ext.h>\n#include <stdint.h>\n\nnamespace clay {\n\nGLuint Get2DTextureFromExternalTextureOES(GLuint oes_texture_id,\n                                          uint32_t texture_width,\n                                          uint32_t texture_height);\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_UTILS_GL_TEXTURE_CONVERTER_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/image_utils.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/utils/image_utils.h\"\n\n#include <cstring>\n\nnamespace clay {\n\nvoid CopyPlane(const uint8_t* src_y, int src_stride_y, uint8_t* dst_y,\n               int dst_stride_y, int width, int height) {\n  int y;\n  if (width <= 0 || height == 0) {\n    return;\n  }\n  // Negative height means invert the image.\n  if (height < 0) {\n    height = -height;\n    dst_y = dst_y + (height - 1) * dst_stride_y;\n    dst_stride_y = -dst_stride_y;\n  }\n  // Coalesce rows.\n  if (src_stride_y == width && dst_stride_y == width) {\n    width *= height;\n    height = 1;\n    src_stride_y = dst_stride_y = 0;\n  }\n  // Nothing to do.\n  if (src_y == dst_y && src_stride_y == dst_stride_y) {\n    return;\n  }\n\n  // Copy plane\n  for (y = 0; y < height; ++y) {\n    std::memcpy(static_cast<void*>(dst_y), static_cast<const void*>(src_y),\n                width);\n    src_y += src_stride_y;\n    dst_y += dst_stride_y;\n  }\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/utils/image_utils.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_UTILS_IMAGE_UTILS_H_\n#define CLAY_GFX_SHARED_IMAGE_UTILS_IMAGE_UTILS_H_\n\n#include <cstdint>\n\nnamespace clay {\n\nvoid CopyPlane(const uint8_t* src_y, int src_stride_y, uint8_t* dst_y,\n               int dst_stride_y, int width, int height);\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_UTILS_IMAGE_UTILS_H_\n"
  },
  {
    "path": "clay/gfx/shared_image/vulkan_image_hardwarebuffer_representation.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/shared_image/vulkan_image_hardwarebuffer_representation.h\"\n\n#include <cstring>\n#include <utility>\n\n#include \"clay/gfx/shared_image/android_egl_image_representation.h\"\n#include \"clay/gfx/vulkan/vulkan_helper.h\"\n\nnamespace clay {\n\nVkFenceSync::VkFenceSync(std::shared_ptr<VulkanFenceHelper> fence_semaphore)\n    : fence_semaphore_(fence_semaphore) {}\n\n// VkFence and VkSemaphore would be destroyed by the VulkanCommandPoolHelper.\nVkFenceSync::~VkFenceSync() {\n  if (fence_semaphore_) {\n    fence_semaphore_->MarkCanDestroy();\n  }\n}\n\nbool VkFenceSync::ClientWait() {\n  if (!fence_semaphore_) {\n    FML_LOG(ERROR) << \"VkFenceSync::ClientWait: fence_semaphore_ is null\";\n    return false;\n  }\n  return fence_semaphore_->WaitForFence();\n}\n\nvoid VkFenceSync::ServerWait() {\n  if (!fence_semaphore_) {\n    FML_LOG(ERROR) << \"VkFenceSync::ServerWait: fence_semaphore_ is null\";\n    return;\n  }\n  if (!fence_semaphore_->WaitSemaphore()) {\n    // Fallback to client wait.\n    FML_LOG(ERROR) << \"Vulkan fence sync fd is -1, fallback to client wait.\";\n    ClientWait();\n    return;\n  }\n}\n\nVulkanImageHardwareBufferRepresentation::\n    VulkanImageHardwareBufferRepresentation(\n        fml::RefPtr<SharedImageBacking> backing, void* device,\n        void* physical_device, void* queue)\n    : SharedImageRepresentation(std::move(backing)),\n      device_(reinterpret_cast<VkDevice>(device)),\n      physical_device_(reinterpret_cast<VkPhysicalDevice>(physical_device)),\n      queue_(reinterpret_cast<VkQueue>(queue)) {\n  AHardwareBufferImageBacking* buffer_backing =\n      static_cast<AHardwareBufferImageBacking*>(GetBacking());\n  CreateVulkanImage(buffer_backing->GetGFXHandle());\n}\n\nVulkanImageHardwareBufferRepresentation::\n    ~VulkanImageHardwareBufferRepresentation() = default;\n\nImageRepresentationType VulkanImageHardwareBufferRepresentation::GetType()\n    const {\n  return ImageRepresentationType::kVulkanImage;\n}\n\nbool VulkanImageHardwareBufferRepresentation::BeginRead(\n    ClaySharedImageReadResult* out) {\n  memset(out, 0, sizeof(ClaySharedImageReadResult));\n  out->struct_size = sizeof(ClaySharedImageReadResult);\n  out->type = kClaySharedImageRepresentationTypeVulkan;\n  out->vulkan_image.struct_size = sizeof(ClayVulkanImage);\n  out->vulkan_image.vulkan_image = vulkan_image_.get();\n\n  out->vulkan_image.user_data = this;\n  this->AddRef();\n  out->vulkan_image.destruction_callback = [](void* user_data) {\n    static_cast<VulkanImageHardwareBufferRepresentation*>(user_data)->Release();\n  };\n  return true;\n}\n\nvoid VulkanImageHardwareBufferRepresentation::CreateVulkanImage(void* buffer) {\n  if (vulkan_image_) {\n    // Use the existing image.\n    return;\n  }\n  vulkan_image_ = std::make_unique<VulkanImage>(device_, physical_device_);\n  AHardwareBuffer* hw_buffer = reinterpret_cast<AHardwareBuffer*>(buffer);\n  vulkan_image_->CreateFromAndroidBuffer(hw_buffer);\n}\n\nbool VulkanImageHardwareBufferRepresentation::EndRead() { return true; }\n\nbool VulkanImageHardwareBufferRepresentation::BeginWrite(\n    ClaySharedImageWriteResult* out) {\n  FML_UNIMPLEMENTED();\n  return false;\n}\n\nbool VulkanImageHardwareBufferRepresentation::EndWrite() {\n  FML_UNIMPLEMENTED();\n  return false;\n}\n\nvoid VulkanImageHardwareBufferRepresentation::ConsumeFence(\n    std::unique_ptr<FenceSync> fence_sync) {\n  if (!fence_sync) {\n    return;\n  }\n  if (fence_sync->GetType() == FenceSync::Type::kEGL) {\n    VkSemaphore semaphore;\n    VkSemaphoreCreateInfo semaphore_info = {};\n    semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;\n    semaphore_info.pNext = nullptr;\n    semaphore_info.flags = 0;\n    auto result = VulkanHelper::GetInstance().CreateSemaphore(\n        device_, &semaphore_info, nullptr, &semaphore);\n    if (!LogVkIfNotSuccess(result, \"vkCreateSemaphore\")) {\n      return;\n    }\n\n    auto sync_fd =\n        static_cast<AndroidEGLFenceSync*>(fence_sync.get())->GetSyncFD();\n    if (sync_fd == -1) {\n      VulkanHelper::GetInstance().DestroySemaphore(device_, semaphore, nullptr);\n      fence_sync->ClientWait();\n      return;\n    }\n\n    VkImportSemaphoreFdInfoKHR import_semaphore_info = {};\n    import_semaphore_info.sType =\n        VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;\n    import_semaphore_info.semaphore = semaphore;\n    import_semaphore_info.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT;\n    import_semaphore_info.handleType =\n        VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT_KHR;\n    import_semaphore_info.fd = sync_fd;\n    result = VulkanHelper::GetInstance().ImportSemaphoreFdKHR(\n        device_, &import_semaphore_info);\n    if (!LogVkIfNotSuccess(result, \"vkImportSemaphoreFdKHR\")) {\n      return;\n    }\n\n    VkFence fence;\n    VkFenceCreateInfo fence_info;\n    memset(&fence_info, 0, sizeof(VkFenceCreateInfo));\n    fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;\n    fence_info.pNext = nullptr;\n    fence_info.flags = 0;\n    result = VulkanHelper::GetInstance().CreateFence(device_, &fence_info,\n                                                     nullptr, &fence);\n    if (!LogVkIfNotSuccess(result, \"vkCreateFence\")) {\n      return;\n    }\n\n    VkPipelineStageFlags wait_stages[] = {\n        VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT};\n\n    ChangeImageLayout(old_layout_, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,\n                      VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,\n                      VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,\n                      VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 1, &semaphore,\n                      wait_stages, 0, nullptr, fence);\n    if (old_layout_ == VK_IMAGE_LAYOUT_UNDEFINED) {\n      old_layout_ = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;\n    }\n    return;\n  }\n  fence_sync->ClientWait();\n}\n\nvoid VulkanImageHardwareBufferRepresentation::ChangeImageLayout(\n    VkImageLayout old_layout, VkImageLayout new_layout,\n    VkAccessFlags src_access_mask, VkAccessFlags dst_access_mask,\n    VkPipelineStageFlagBits src_stage_mask,\n    VkPipelineStageFlagBits dst_stage_mask, uint32_t wait_semaphore_count,\n    const VkSemaphore* wait_semaphores, const VkPipelineStageFlags* wait_stages,\n    uint32_t signal_semaphore_count, const VkSemaphore* signal_semaphores,\n    VkFence fence) {\n  VkCommandBuffer command_buffer;\n  command_pool_helper_->GetCommandBuffer(&command_buffer);\n\n  VkCommandBufferBeginInfo beginInfo = {};\n  beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;\n  beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;\n\n  if (!LogVkIfNotSuccess(clay::VulkanHelper::GetInstance().BeginCommandBuffer(\n                             command_buffer, &beginInfo),\n                         \"vkBeginCommandBuffer\")) {\n    return;\n  }\n\n  vulkan_image_->ApplyImagerBarrier(command_buffer, old_layout, new_layout,\n                                    src_access_mask, dst_access_mask,\n                                    src_stage_mask, dst_stage_mask);\n\n  if (!LogVkIfNotSuccess(\n          clay::VulkanHelper::GetInstance().EndCommandBuffer(command_buffer),\n          \"vkEndCommandBuffer\")) {\n    return;\n  }\n\n  VulkanCommandPoolHelper::VkCMBStatus cmb_status;\n  cmb_status.command_buffer = command_buffer;\n  cmb_status.fence = fence;\n  for (auto i = 0u; i < wait_semaphore_count; i++) {\n    cmb_status.semaphores.push_back(wait_semaphores[i]);\n  }\n  cmb_status.has_signal_semaphore = signal_semaphore_count > 0;\n\n  command_pool_helper_->InsertUsedBuffer(cmb_status);\n\n  VkSubmitInfo submit_info = {};\n  submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;\n  submit_info.pNext = nullptr;\n  submit_info.waitSemaphoreCount = wait_semaphore_count;\n  submit_info.pWaitSemaphores = wait_semaphores;\n  submit_info.pWaitDstStageMask = wait_stages;\n  submit_info.signalSemaphoreCount = signal_semaphore_count;\n  submit_info.pSignalSemaphores = signal_semaphores;\n  submit_info.commandBufferCount = 1;\n  submit_info.pCommandBuffers = &command_buffer;\n\n  auto result =\n      VulkanHelper::GetInstance().QueueSubmit(queue_, 1, &submit_info, fence);\n  if (!LogVkIfNotSuccess(result, \"vkQueueSubmit\")) {\n    return;\n  }\n}\n\nstd::unique_ptr<FenceSync>\nVulkanImageHardwareBufferRepresentation::ProduceFence() {\n  return std::make_unique<VkFenceSync>(last_fence_semaphore_);\n}\n\nvoid VulkanImageHardwareBufferRepresentation::SetCommandPoolHelper(\n    std::shared_ptr<VulkanCommandPoolHelper> helper) {\n  command_pool_helper_ = helper;\n}\n\nvoid VulkanImageHardwareBufferRepresentation::PostDrawVk() {\n  // We need to change the VkImage's imageLayout from\n  // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL to\n  // VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL. And signal a semaphore which will\n  // be exported to OpenGL side once the barrier is applied.\n  VkExportSemaphoreCreateInfo export_create_info = {};\n  export_create_info.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;\n  export_create_info.pNext = nullptr;\n  export_create_info.handleTypes =\n      VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;  // use as export sync fd\n\n  VkSemaphore semaphore;\n  VkSemaphoreCreateInfo semaphore_create_info = {};\n  semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;\n  semaphore_create_info.pNext = &export_create_info;\n  semaphore_create_info.flags = 0;\n  auto result = VulkanHelper::GetInstance().CreateSemaphore(\n      device_, &semaphore_create_info, nullptr, &semaphore);\n  if (!LogVkIfNotSuccess(result, \"vkCreateSemaphore\")) {\n    return;\n  }\n\n  VkFence fence;\n  VkFenceCreateInfo fence_info;\n  memset(&fence_info, 0, sizeof(VkFenceCreateInfo));\n  fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;\n  fence_info.pNext = nullptr;\n  fence_info.flags = 0;\n  result = VulkanHelper::GetInstance().CreateFence(device_, &fence_info,\n                                                   nullptr, &fence);\n  if (!LogVkIfNotSuccess(result, \"vkCreateFence\")) {\n    return;\n  }\n\n  ChangeImageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,\n                    VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,\n                    VK_ACCESS_SHADER_READ_BIT,\n                    VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,\n                    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,\n                    VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, nullptr,\n                    nullptr, 1, &semaphore, fence);\n\n  // Signal semaphores and feneces will be waited in clay raster thread.\n  // So we need to trace their lifetime to avoid destroy them before they are\n  // waited.\n  std::shared_ptr<clay::VulkanFenceHelper> fence_helper =\n      std::make_shared<clay::VulkanFenceHelper>(device_, fence, semaphore);\n  command_pool_helper_->InsertFenceSemaphore(fence_helper);\n\n  last_fence_semaphore_ = fence_helper;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/shared_image/vulkan_image_hardwarebuffer_representation.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SHARED_IMAGE_VULKAN_IMAGE_HARDWAREBUFFER_REPRESENTATION_H_\n#define CLAY_GFX_SHARED_IMAGE_VULKAN_IMAGE_HARDWAREBUFFER_REPRESENTATION_H_\n\n#include <vulkan/vulkan.h>\n\n#include <memory>\n\n#include \"clay/gfx/shared_image/android_hardwarebuffer_image_backing.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/vulkan/vulkan_command_pool_helper.h\"\n#include \"clay/gfx/vulkan/vulkan_image.h\"\n\nnamespace clay {\n\nclass VkFenceSync final : public FenceSync {\n public:\n  explicit VkFenceSync(std::shared_ptr<VulkanFenceHelper> fence_semaphore);\n\n  bool ClientWait() override;\n\n  void ServerWait();\n\n  ~VkFenceSync() override;\n\n  Type GetType() const override { return Type::kVulkan; }\n\n private:\n  std::shared_ptr<VulkanFenceHelper> fence_semaphore_;\n};\n\nclass VulkanImageHardwareBufferRepresentation\n    : public SharedImageRepresentation {\n public:\n  VulkanImageHardwareBufferRepresentation(\n      fml::RefPtr<SharedImageBacking> backing, void* device,\n      void* physical_device, void* queue);\n  ~VulkanImageHardwareBufferRepresentation() override;\n\n  ImageRepresentationType GetType() const override;\n\n  bool BeginRead(ClaySharedImageReadResult* out) override;\n  bool BeginWrite(ClaySharedImageWriteResult* out) override;\n  bool EndRead() override;\n  bool EndWrite() override;\n\n  void ConsumeFence(std::unique_ptr<FenceSync>) override;\n  std::unique_ptr<FenceSync> ProduceFence() override;\n\n  void SetCommandPoolHelper(std::shared_ptr<VulkanCommandPoolHelper> helper);\n\n  void PostDrawVk();\n\n  void ChangeImageLayout(VkImageLayout old_layout, VkImageLayout new_layout,\n                         VkAccessFlags src_access_mask,\n                         VkAccessFlags dst_access_mask,\n                         VkPipelineStageFlagBits src_stage_mask,\n                         VkPipelineStageFlagBits dst_stage_mask,\n                         uint32_t wait_semaphore_count,\n                         const VkSemaphore* wait_semaphores,\n                         const VkPipelineStageFlags* wait_stages,\n                         uint32_t signal_semaphore_count,\n                         const VkSemaphore* signal_semaphores, VkFence fence);\n\n private:\n  void CreateVulkanImage(void* buffer);\n\n  VkDevice device_;\n  VkPhysicalDevice physical_device_;\n  VkQueue queue_;\n  std::unique_ptr<VulkanImage> vulkan_image_;\n\n  std::shared_ptr<VulkanFenceHelper> last_fence_semaphore_;\n\n  std::shared_ptr<VulkanCommandPoolHelper> command_pool_helper_;\n  VkImageLayout old_layout_ = VK_IMAGE_LAYOUT_UNDEFINED;\n};\n}  // namespace clay\n\n#endif  // CLAY_GFX_SHARED_IMAGE_VULKAN_IMAGE_HARDWAREBUFFER_REPRESENTATION_H_\n"
  },
  {
    "path": "clay/gfx/skia/build.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\ngfx_skia_sources = [\n  \"picture_skia.cc\",\n  \"picture_skia.h\",\n  \"skia_canvas.cc\",\n  \"skia_canvas.h\",\n]\n"
  },
  {
    "path": "clay/gfx/skia/picture_skia.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/skia/picture_skia.h\"\n\n#include \"third_party/skia/src/core/SkBigPicture.h\"\n#include \"third_party/skia/src/core/SkRecord.h\"\n#include \"third_party/skia/src/core/SkRecords.h\"\n\nnamespace clay {\n\nnamespace {  // Anonymous namespace for helpers\n\n// Overload for commands that have a direct SkPaint member.\n[[maybe_unused]] SkPaint* AsPaintPtr(SkPaint* paint) { return paint; }\n\n// Overload for commands that have an Optional<SkPaint> member (e.g.,\n// SaveLayer). It's assumed SkRecords::Optional has operator bool() and\n// operator*().\n[[maybe_unused]] SkPaint* AsPaintPtr(SkRecords::Optional<SkPaint>* paint) {\n  if (paint && *paint) {\n    return &(**paint);\n  }\n  return nullptr;\n}\n\n}  // namespace\n\n// A mutator for SkRecord to get a mutable SkPaint pointer. It uses the\n// AsPaintPtr helpers to handle both direct and optional paints.\nstruct PaintMutator {\n  template <typename T>\n  auto operator()(T* record) const -> decltype(AsPaintPtr(&record->paint)) {\n    return AsPaintPtr(&record->paint);\n  }\n\n  // Fallback for commands with no 'paint' member.\n  SkPaint* operator()(...) const { return nullptr; }\n};\n\nvoid PictureSkia::DispatchToWorklet(ClayAnimationPropertyType type,\n                                    const clay::Color& value) {\n  if (!picture_) {\n    return;\n  }\n\n  SkBigPicture* big_picture = static_cast<SkBigPicture*>(picture_.get());\n  if (!big_picture) {\n    return;\n  }\n  for (auto& op : dynamic_ops_) {\n    if ((ClayAnimationPropertyType::kColor == type &&\n         op.first == DynamicOpType::kSetTextColor) ||\n        (ClayAnimationPropertyType::kBackgroundColor == type &&\n         op.first == DynamicOpType::kSetBackgroundColor)) {\n      int offset = op.second;\n      SkRecord* record = const_cast<SkRecord*>(big_picture->record());\n      if (!record || offset < 0) {\n        return;\n      }\n      PaintMutator mutator;\n      SkPaint* paint = record->mutate(offset, mutator);\n      if (!paint) {\n        return;\n      }\n      paint->setColor(value);\n    }\n  }\n}\n\nColor PictureSkia::ObtainWorkletValue(ClayAnimationPropertyType type) const {\n  if (!picture_) {\n    return Color();\n  }\n\n  SkBigPicture* big_picture = static_cast<SkBigPicture*>(picture_.get());\n  if (!big_picture) {\n    return Color();\n  }\n\n  for (auto op : dynamic_ops_) {\n    if ((ClayAnimationPropertyType::kColor == type &&\n         op.first == DynamicOpType::kSetTextColor) ||\n        (ClayAnimationPropertyType::kBackgroundColor == type &&\n         op.first == DynamicOpType::kSetBackgroundColor)) {\n      int offset = op.second;\n      SkRecord* record = const_cast<SkRecord*>(big_picture->record());\n      if (!record || offset < 0) {\n        return Color();\n      }\n      PaintMutator mutator;\n      SkPaint* paint = record->mutate(offset, mutator);\n      if (paint) {\n        // 32-bit ARGB color value\n        return Color(paint->getColor());\n      }\n    }\n  }\n  return Color();\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/skia/picture_skia.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKIA_PICTURE_SKIA_H_\n#define CLAY_GFX_SKIA_PICTURE_SKIA_H_\n\n#include <list>\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/animation/picture_animation_type.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"third_party/skia/include/core/SkPicture.h\"\n#include \"third_party/skia/include/core/SkRefCnt.h\"\n\nnamespace clay {\n\n// The wrapper class that contains a sequence of rendering operations for skia.\n// The lifecycle of PictureSkia must be managered by the GPU thread.\nclass PictureSkia : public GPURefObject {\n public:\n  explicit PictureSkia(sk_sp<SkPicture> picture, DynamicOps&& dynamic_ops)\n      : picture_(std::move(picture)), dynamic_ops_(std::move(dynamic_ops)) {}\n\n  ~PictureSkia() override = default;\n\n  sk_sp<SkPicture> raw() const { return picture_; }\n\n  uint32_t unique_id() const { return picture_->uniqueID(); }\n\n  void DispatchToWorklet(ClayAnimationPropertyType type, const Color& value);\n\n  Color ObtainWorkletValue(ClayAnimationPropertyType type) const;\n\n  DynamicOps& GetDynamicOps() { return dynamic_ops_; }\n\n private:\n  sk_sp<SkPicture> picture_;\n  DynamicOps dynamic_ops_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKIA_PICTURE_SKIA_H_\n"
  },
  {
    "path": "clay/gfx/skia/skia_canvas.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/skia/skia_canvas.h\"\n\n#include <cstddef>\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/animation/picture_animation_type.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/image/graphics_image_skia_lazy.h\"\n#include \"clay/gfx/skia/picture_skia.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/gfx/style/sampling_options.h\"\n#include \"third_party/skia/src/core/SkRecorder.h\"\n\nnamespace clay {\n\nSkCanvas* SkiaCanvas::GetGrCanvas() { return canvas_; }\n\nGrRecordingContext* SkiaCanvas::RecordingContext() { return nullptr; }\n\nint SkiaCanvas::Save() { return canvas_->save(); }\n\nvoid SkiaCanvas::Restore() { canvas_->restore(); }\n\nint SkiaCanvas::SaveLayer(const skity::Rect& bounds, const Paint* paint) {\n  SkPaint skia_paint;\n  if (paint) {\n    skia_paint = paint->gr_object();\n  }\n  return canvas_->saveLayer(ConvertSkityRectToSkRect(bounds), &skia_paint);\n}\n\nint SkiaCanvas::GetSaveCount() { return canvas_->getSaveCount(); }\n\nvoid SkiaCanvas::RestoreToCount(int save_count) {\n  canvas_->restoreToCount(save_count);\n}\n\nvoid SkiaCanvas::Translate(float dx, float dy) { canvas_->translate(dx, dy); }\n\nvoid SkiaCanvas::Scale(float sx, float sy) { canvas_->scale(sx, sy); }\n\nvoid SkiaCanvas::Rotate(float degrees) { canvas_->rotate(degrees); }\n\nvoid SkiaCanvas::Rotate(float degrees, float px, float py) {\n  canvas_->rotate(degrees, px, py);\n}\n\nvoid SkiaCanvas::Skew(float sx, float sy) { canvas_->skew(sx, sy); }\n\nvoid SkiaCanvas::Concat(const skity::Matrix& matrix) {\n  canvas_->concat(ConvertSkityMatrixToSkMatrix(matrix));\n}\nvoid SkiaCanvas::SetMatrix(const skity::Matrix& matrix) {\n  canvas_->resetMatrix();\n  canvas_->concat(ConvertSkityMatrixToSkMatrix(matrix));\n}\n\nvoid SkiaCanvas::ClipRect(const skity::Rect& rect, SkClipOp op,\n                          bool do_anti_alias) {\n  canvas_->clipRect(ConvertSkityRectToSkRect(rect), op, do_anti_alias);\n}\n\nvoid SkiaCanvas::ClipRRect(const skity::RRect& rrect, SkClipOp op,\n                           bool do_anti_alias) {\n  canvas_->clipRRect(ConvertSkityRRectToSkia(rrect), op, do_anti_alias);\n}\n\nvoid SkiaCanvas::ClipPath(const SkPath& path, SkClipOp op, bool do_anti_alias) {\n  canvas_->clipPath(path, op, do_anti_alias);\n}\n\nbool SkiaCanvas::QuickReject(const skity::Rect& rect) {\n  return canvas_->quickReject(ConvertSkityRectToSkRect(rect));\n}\n\nbool SkiaCanvas::GetDeviceClipBounds(skity::Rect* bounds) {\n  if (!bounds) {\n    return false;\n  }\n  SkIRect clip_bounds;\n  bool result = canvas_->getDeviceClipBounds(&clip_bounds);\n  *bounds = ConvertSkIRectToSkityRect(clip_bounds);\n  return result;\n}\n\nvoid SkiaCanvas::Clear(uint32_t color) {\n  canvas_->clear(static_cast<SkColor>(color));\n}\n\nvoid SkiaCanvas::DrawPaint(const Paint& paint) {\n  SkPaint skia_paint = paint.gr_object();\n  canvas_->drawPaint(skia_paint);\n}\n\nvoid SkiaCanvas::DrawLine(float x0, float y0, float x1, float y1,\n                          const Paint& paint) {\n  canvas_->drawLine(x0, y0, x1, y1, paint.gr_object());\n}\n\nvoid SkiaCanvas::DrawRect(const skity::Rect& rect, const Paint& paint) {\n  canvas_->drawRect(ConvertSkityRectToSkRect(rect), paint.gr_object());\n  RecordDynamicOpOffset(paint);\n}\n\nvoid SkiaCanvas::DrawRRect(const skity::RRect& rrect, const Paint& paint) {\n  canvas_->drawRRect(ConvertSkityRRectToSkia(rrect), paint.gr_object());\n}\n\nvoid SkiaCanvas::DrawDRRect(const skity::RRect& outer,\n                            const skity::RRect& inner, const Paint& paint) {\n  canvas_->drawDRRect(ConvertSkityRRectToSkia(outer),\n                      ConvertSkityRRectToSkia(inner), paint.gr_object());\n}\n\nvoid SkiaCanvas::DrawCircle(float cx, float cy, float radius,\n                            const Paint& paint) {\n  canvas_->drawCircle(cx, cy, radius, paint.gr_object());\n}\n\nvoid SkiaCanvas::DrawArc(const skity::Rect& oval, float start_angle,\n                         float sweep_angle, bool use_center,\n                         const Paint& paint) {\n  canvas_->drawArc(ConvertSkityRectToSkRect(oval), start_angle, sweep_angle,\n                   use_center, paint.gr_object());\n}\n\nvoid SkiaCanvas::DrawPath(const SkPath& path, const Paint& paint) {\n  canvas_->drawPath(path, paint.gr_object());\n}\n\nvoid SkiaCanvas::DrawImage(const GraphicsImage* image, float x, float y,\n                           const SkSamplingOptions& sampling,\n                           const Paint* paint) {\n  FML_DCHECK(image);\n  FML_DCHECK(image->isTextureBacked());\n\n  SkPaint skia_paint;\n  if (paint) {\n    skia_paint = paint->gr_object();\n  }\n  auto skia_image = image->gr_image();\n  // TODO(feiyue.1998): Support lazy image.\n  canvas_->drawImage(skia_image, x, y, sampling, &skia_paint);\n}\n\nvoid SkiaCanvas::DrawImageRect(const GraphicsImage* image,\n                               const skity::Rect& src, const skity::Rect& dst,\n                               const SkSamplingOptions& sampling,\n                               const Paint* paint) {\n  FML_DCHECK(image);\n\n  SkPaint skia_paint;\n  if (paint) {\n    skia_paint = paint->gr_object();\n  }\n\n  auto skia_image = image->gr_image();\n  // TODO(feiyue.1998): Support lazy image.\n  canvas_->drawImageRect(skia_image, ConvertSkityRectToSkRect(src),\n                         ConvertSkityRectToSkRect(dst), sampling, &skia_paint,\n                         SkCanvas::kFast_SrcRectConstraint);\n}\n\nvoid SkiaCanvas::DrawImageNine(const GraphicsImage* image,\n                               const skity::Rect& center,\n                               const skity::Rect& dst, FilterMode filter,\n                               const Paint* paint) {\n  SkPaint skia_paint;\n  if (paint) {\n    skia_paint = paint->gr_object();\n  }\n  auto skia_image = image->gr_image();\n  SkFilterMode sk_filter;\n  switch (filter) {\n    case clay::FilterMode::kNearest: {\n      sk_filter = SkFilterMode::kNearest;\n      break;\n    }\n    case clay::FilterMode::kLinear: {\n      sk_filter = SkFilterMode::kLinear;\n      break;\n    }\n    default: {\n      break;\n    }\n  }\n  canvas_->drawImageNine(skia_image.get(), ConvertSkityRectToSkIRect(center),\n                         ConvertSkityRectToSkRect(dst), sk_filter, &skia_paint);\n}\nvoid SkiaCanvas::DrawTextBlob(const fml::RefPtr<TextBlob>& blob, float x,\n                              float y, const Paint& paint) {\n  auto skia_text_blob = blob->gr_text_blob();\n  if (!skia_text_blob) {\n    return;\n  }\n  canvas_->drawTextBlob(skia_text_blob, x, y, paint.gr_object());\n}\n\nvoid SkiaCanvas::DrawPicture(const Picture* picture) {\n  canvas_->drawPicture(picture->picture()->raw());\n}\n\nskity::Matrix SkiaCanvas::GetTotalMatrix() {\n  return ConvertSkMatrixToSkityMatrix(canvas_->getTotalMatrix());\n}\n\nvoid SkiaCanvas::RecordDynamicOpOffset(const Paint& paint) {\n  switch (paint.getDynamicOpType()) {\n    case clay::DynamicOpType::kSetBackgroundColor: {\n      SkRecorder* recorder = static_cast<SkRecorder*>(canvas_);\n      int op_offset = recorder->getCurrentOpCount() - 1;\n      FML_DCHECK(op_offset >= 0);\n      dynamic_ops_.emplace_back(\n          std::make_pair(clay::DynamicOpType::kSetBackgroundColor, op_offset));\n    } break;\n    case clay::DynamicOpType::kSetTextColor: {\n      if (start_draw_dynamic_text_blobs_) {\n        SkRecorder* recorder = static_cast<SkRecorder*>(canvas_);\n        int op_offset = recorder->getCurrentOpCount() - 1;\n        FML_DCHECK(op_offset >= 0);\n        dynamic_ops_.emplace_back(\n            std::make_pair(clay::DynamicOpType::kSetTextColor, op_offset));\n      }\n    } break;\n    default:\n      break;\n  }\n}\n\nvoid SkiaCanvas::OnDrawDynamicTextBlobsStart() {\n  start_draw_dynamic_text_blobs_ = true;\n}\n\nvoid SkiaCanvas::OnDrawDynamicTextBlobsEnd() {\n  start_draw_dynamic_text_blobs_ = false;\n}\n\nSkiaRecorderCanvas::SkiaRecorderCanvas(const skity::Rect& bounds,\n                                       fml::RefPtr<GPUUnrefQueue> unref_queue)\n    : picture_recorder_(std::make_unique<SkPictureRecorder>()),\n      rtree_factory_(std::make_unique<SkRTreeFactory>()),\n      unref_queue_(unref_queue) {\n  picture_recorder_->beginRecording(ConvertSkityRectToSkRect(bounds),\n                                    rtree_factory_.get());\n  canvas_ = picture_recorder_->getRecordingCanvas();\n}\n\nstd::unique_ptr<Picture> SkiaRecorderCanvas::FinishRecordingAsPicture() {\n  auto skia_picture = picture_recorder_->finishRecordingAsPicture();\n  rtree_factory_.reset();\n  auto picture_skia =\n      fml::MakeRefCounted<PictureSkia>(skia_picture, std::move(dynamic_ops_));\n  return std::make_unique<Picture>(\n      GPUObject(std::move(picture_skia), std::move(unref_queue_)),\n      has_lazy_image_);\n}\n\nSkiaBitmapCanvas::SkiaBitmapCanvas(const SkBitmap& bitmap)\n    : owned_(std::make_unique<SkCanvas>(bitmap)) {\n  canvas_ = owned_.get();\n}\n\nstd::unique_ptr<Picture> SkiaBitmapCanvas::FinishRecordingAsPicture() {\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/skia/skia_canvas.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKIA_SKIA_CANVAS_H_\n#define CLAY_GFX_SKIA_SKIA_CANVAS_H_\n\n#include <memory>\n\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/graphics_canvas.h\"\n#include \"third_party/skia/include/core/SkPictureRecorder.h\"\n\nnamespace clay {\n\nclass SkiaCanvas : public GraphicsCanvas {\n public:\n  SkiaCanvas() = default;\n  ~SkiaCanvas() = default;\n\n  SkCanvas* GetGrCanvas() override;\n\n  GrRecordingContext* RecordingContext() override;\n\n  int Save() override;\n  void Restore() override;\n  int SaveLayer(const skity::Rect& bounds, const Paint* paint) override;\n  int GetSaveCount() override;\n  void RestoreToCount(int save_count) override;\n\n  void Translate(float dx, float dy) override;\n  void Scale(float sx, float sy) override;\n  void Rotate(float degrees) override;\n  void Rotate(float degrees, float px, float py) override;\n  void Skew(float sx, float sy) override;\n  void Concat(const skity::Matrix& matrix) override;\n  void SetMatrix(const skity::Matrix& matrix) override;\n\n  void ClipRect(const skity::Rect& rect, SkClipOp op,\n                bool do_anti_alias) override;\n\n  void ClipRRect(const skity::RRect& rrect, SkClipOp op,\n                 bool do_anti_alias) override;\n\n  void ClipPath(const SkPath& path, SkClipOp op, bool do_anti_alias) override;\n\n  bool QuickReject(const skity::Rect& rect) override;\n\n  bool GetDeviceClipBounds(skity::Rect* bounds) override;\n\n  void Clear(uint32_t color) override;\n\n  void DrawPaint(const Paint& paint) override;\n  void DrawLine(float x0, float y0, float x1, float y1,\n                const Paint& paint) override;\n  void DrawRect(const skity::Rect& rect, const Paint& paint) override;\n  void DrawRRect(const skity::RRect& rrect, const Paint& paint) override;\n  void DrawDRRect(const skity::RRect& outer, const skity::RRect& inner,\n                  const Paint& paint) override;\n  void DrawCircle(float cx, float cy, float radius,\n                  const Paint& paint) override;\n  void DrawArc(const skity::Rect& oval, float start_angle, float sweep_angle,\n               bool use_center, const Paint& paint) override;\n  void DrawPath(const SkPath& path, const Paint& paint) override;\n  void DrawImage(const GraphicsImage* image, float x, float y,\n                 const SkSamplingOptions& sampling,\n                 const Paint* paint) override;\n  void DrawImageRect(const GraphicsImage* image, const skity::Rect& src,\n                     const skity::Rect& dst, const SkSamplingOptions& sampling,\n                     const Paint* paint) override;\n  void DrawImageNine(const GraphicsImage* image, const skity::Rect& center,\n                     const skity::Rect& dst, FilterMode filter,\n                     const Paint* paint) override;\n  void DrawTextBlob(const fml::RefPtr<TextBlob>& blob, float x, float y,\n                    const Paint& paint) override;\n\n  void DrawPicture(const Picture* picture) override;\n\n  skity::Matrix GetTotalMatrix() override;\n\n  void OnDrawDynamicTextBlobsStart() override;\n  void OnDrawDynamicTextBlobsEnd() override;\n\n protected:\n  SkCanvas* canvas_ = nullptr;\n  DynamicOps dynamic_ops_;\n  bool start_draw_dynamic_text_blobs_ = false;\n\n  void RecordDynamicOpOffset(const Paint& paint);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SkiaCanvas);\n};\n\nclass SkiaRecorderCanvas : public SkiaCanvas {\n public:\n  SkiaRecorderCanvas(const skity::Rect& bounds,\n                     fml::RefPtr<GPUUnrefQueue> unref_queue);\n  ~SkiaRecorderCanvas() override = default;\n\n  std::unique_ptr<Picture> FinishRecordingAsPicture() override;\n\n private:\n  std::unique_ptr<SkPictureRecorder> picture_recorder_;\n  std::unique_ptr<SkBBHFactory> rtree_factory_;\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n};\n\nclass SkiaBitmapCanvas : public SkiaCanvas {\n public:\n  explicit SkiaBitmapCanvas(const SkBitmap& bitmap);\n\n  std::unique_ptr<Picture> FinishRecordingAsPicture() override;\n\n private:\n  std::unique_ptr<SkCanvas> owned_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKIA_SKIA_CANVAS_H_\n"
  },
  {
    "path": "clay/gfx/skia/skia_concurrent_executor.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/skia/skia_concurrent_executor.h\"\n\n#include \"base/trace/native/trace_event.h\"\n\nnamespace clay {\n\nSkiaConcurrentExecutor::SkiaConcurrentExecutor(const OnWorkCallback& on_work)\n    : on_work_(on_work) {}\n\nSkiaConcurrentExecutor::~SkiaConcurrentExecutor() = default;\n\nvoid SkiaConcurrentExecutor::add(fml::closure work) {\n  if (!work) {\n    return;\n  }\n  on_work_([work]() {\n    TRACE_EVENT(\"flutter\", \"SkiaExecutor\");\n    work();\n  });\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/skia/skia_concurrent_executor.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKIA_SKIA_CONCURRENT_EXECUTOR_H_\n#define CLAY_GFX_SKIA_SKIA_CONCURRENT_EXECUTOR_H_\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/macros.h\"\n#include \"third_party/skia/include/core/SkExecutor.h\"\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// @brief      An interface used by Skia to schedule work on engine managed\n///             threads (usually workers in a concurrent message loop).\n///\n///             Skia may decide that certain workloads don't have thread\n///             affinity and may be performed on a background thread. However,\n///             Skia does not manage its own threads. So, it delegates the\n///             scheduling of this work to the engine via this interface. The\n///             engine has a dedicated pool of threads it uses for scheduling\n///             background tasks that have no thread affinity. This thread\n///             worker pool is held next to the process global Dart VM instance.\n///             The Skia executor is wired up there as well.\n///\nclass SkiaConcurrentExecutor : public SkExecutor {\n public:\n  //----------------------------------------------------------------------------\n  /// The callback invoked by the executor to schedule the given task onto an\n  /// engine managed background thread.\n  ///\n  using OnWorkCallback = std::function<void(fml::closure work)>;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Create a new instance of the executor.\n  ///\n  /// @param[in]  on_work  The work callback.\n  ///\n  explicit SkiaConcurrentExecutor(const OnWorkCallback& on_work);\n\n  // |SkExecutor|\n  ~SkiaConcurrentExecutor() override;\n\n  // |SkExecutor|\n  void add(fml::closure work) override;\n\n private:\n  OnWorkCallback on_work_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SkiaConcurrentExecutor);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKIA_SKIA_CONCURRENT_EXECUTOR_H_\n"
  },
  {
    "path": "clay/gfx/skity/BUILD.gn",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\nsource_set(\"skity\") {\n  sources = [\n    \"picture_skity.cc\",\n    \"picture_skity.h\",\n    \"rendering_material.cc\",\n    \"rendering_material.h\",\n    \"skity_auto_canvas_save.h\",\n    \"skity_canvas.cc\",\n    \"skity_canvas.h\",\n    \"skity_decoding_image.cc\",\n    \"skity_decoding_image.h\",\n    \"skity_image.cc\",\n    \"skity_image.h\",\n    \"skity_lazy_image_traveller.cc\",\n    \"skity_lazy_image_traveller.h\",\n  ]\n\n  deps = [ \"../../gfx\" ]\n\n  public_deps = [ \"../../fml:fml\" ]\n\n  public_configs = [ \"../../:config\" ]\n\n  if (!enable_skity) {\n    public_deps += [ \"//third_party/skia\" ]\n  }\n}\n"
  },
  {
    "path": "clay/gfx/skity/picture_skity.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/skity/picture_skity.h\"\n\n#include \"skity/graphic/color.hpp\"\n#include \"skity/graphic/paint.hpp\"\n\nnamespace clay {\n\nuint32_t PictureSkity::NextUniqueID() {\n  static std::atomic<uint32_t> next_id(1);\n  uint32_t id;\n  do {\n    id = next_id.fetch_add(1, std::memory_order_relaxed);\n  } while (id == 0);  // 0 is reserved for an invalid id.\n  return id;\n}\n\nvoid PictureSkity::DispatchToWorklet(ClayAnimationPropertyType type,\n                                     const clay::Color& value) {\n  if (!picture_) {\n    return;\n  }\n  for (auto op : dynamic_ops_) {\n    if ((ClayAnimationPropertyType::kColor == type &&\n         op.first == DynamicOpType::kSetTextColor) ||\n        (ClayAnimationPropertyType::kBackgroundColor == type &&\n         op.first == DynamicOpType::kSetBackgroundColor)) {\n      skity::Paint* paint = picture_->GetOpPaintByOffset(op.second);\n      if (paint) {\n        paint->SetColor(skity::ColorSetARGB(value.Alpha(), value.Red(),\n                                            value.Green(), value.Blue()));\n      }\n    }\n  }\n}\n\nclay::Color PictureSkity::ObtainWorkletValue(\n    ClayAnimationPropertyType type) const {\n  if (!picture_) {\n    return Color();\n  }\n  for (auto op : dynamic_ops_) {\n    if ((ClayAnimationPropertyType::kColor == type &&\n         op.first == DynamicOpType::kSetTextColor) ||\n        (ClayAnimationPropertyType::kBackgroundColor == type &&\n         op.first == DynamicOpType::kSetBackgroundColor)) {\n      skity::Paint* paint = picture_->GetOpPaintByOffset(op.second);\n      if (paint) {\n        // 32-bit ARGB color value\n        return Color(paint->GetColor());\n      }\n    }\n  }\n  return Color();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/skity/picture_skity.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKITY_PICTURE_SKITY_H_\n#define CLAY_GFX_SKITY_PICTURE_SKITY_H_\n\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/animation/picture_animation_type.h\"\n#include \"clay/gfx/gpu_ref_object.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"skity/recorder/display_list.hpp\"\n\nnamespace clay {\n\n// The wrapper class that contains a sequence of rendering operations for skity.\n// The lifecycle of DisplayListSkity must be managered by the GPU thread.\nclass PictureSkity : public GPURefObject {\n public:\n  PictureSkity(DynamicOps dynamic_ops,\n               std::unique_ptr<skity::DisplayList> picture)\n      : dynamic_ops_(dynamic_ops),\n        picture_(std::move(picture)),\n        unique_id_(NextUniqueID()) {}\n\n  ~PictureSkity() = default;\n\n  std::shared_ptr<skity::DisplayList> raw() const { return picture_; }\n\n  uint32_t unique_id() const { return unique_id_; }\n\n  DynamicOps& GetDynamicOps() { return dynamic_ops_; }\n\n  void DispatchToWorklet(ClayAnimationPropertyType type,\n                         const clay::Color& value);\n  clay::Color ObtainWorkletValue(ClayAnimationPropertyType type) const;\n\n private:\n  static uint32_t NextUniqueID();\n\n  DynamicOps dynamic_ops_;\n  std::shared_ptr<skity::DisplayList> picture_;\n\n  const uint32_t unique_id_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKITY_PICTURE_SKITY_H_\n"
  },
  {
    "path": "clay/gfx/skity/rendering_material.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/skity/rendering_material.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/style/color.h\"\n#include \"skity/graphic/color.hpp\"\n\nnamespace clay {\n\n#define SKITY_UNIMPLEMENTED(name) \\\n  FML_DLOG(ERROR) << \"Unimplemented with skity in \" << name;\n\nstatic skity::Color4f to_color_4f(Color const& color) {\n  return {color.RedF(), color.GreenF(), color.BlueF(), color.AlphaF()};\n}\n\nstatic skity::Color to_color(Color const& color) {\n  return skity::ColorSetARGB(color.Alpha(), color.Red(), color.Green(),\n                             color.Blue());\n}\n\nskity::TileMode RenderingMaterial::ToSkityTileMode(TileMode tile_mode) {\n  switch (tile_mode) {\n    case TileMode::kClamp:\n      return skity::TileMode::kClamp;\n    case TileMode::kRepeat:\n      return skity::TileMode::kRepeat;\n    case TileMode::kMirror:\n      return skity::TileMode::kMirror;\n    case TileMode::kDecal:\n      return skity::TileMode::kDecal;\n  }\n}\n\nstd::shared_ptr<skity::Shader> RenderingMaterial::ToSkityLinearGradient(\n    const LinearGradientColorSource* linear_gradient_source) {\n  std::vector<skity::Point> pts{\n      {linear_gradient_source->start_point().x,\n       linear_gradient_source->start_point().y, 0.f, 1.f},\n      {linear_gradient_source->end_point().x,\n       linear_gradient_source->end_point().y, 0.f, 1.f},\n  };\n  int stop_count = linear_gradient_source->stop_count();\n  std::vector<skity::Vec4> colors(stop_count);\n  std::vector<float> stops(stop_count);\n  for (size_t i = 0; i < static_cast<size_t>(stop_count); i++) {\n    colors[i] = to_color_4f(linear_gradient_source->colors()[i]);\n    stops[i] = linear_gradient_source->stops()[i];\n  }\n  auto shader = skity::Shader::MakeLinear(\n      pts.data(), colors.data(), stops.empty() ? nullptr : stops.data(),\n      colors.size(), ToSkityTileMode(linear_gradient_source->tile_mode()));\n  if (linear_gradient_source->matrix_ptr()) {\n    shader->SetLocalMatrix(linear_gradient_source->matrix());\n  }\n  return shader;\n}\n\nstd::shared_ptr<skity::Shader> RenderingMaterial::ToSkityRadialGradient(\n    const RadialGradientColorSource* radial_gradient_source) {\n  skity::Point center{\n      radial_gradient_source->center().x,\n      radial_gradient_source->center().y,\n      0.f,\n      1.f,\n  };\n  int stop_count = radial_gradient_source->stop_count();\n  std::vector<skity::Vec4> colors(stop_count);\n  std::vector<float> stops(stop_count);\n  for (size_t i = 0; i < static_cast<size_t>(stop_count); i++) {\n    colors[i] = to_color_4f(radial_gradient_source->colors()[i]);\n    stops[i] = radial_gradient_source->stops()[i];\n  }\n  auto shader = skity::Shader::MakeRadial(\n      center, radial_gradient_source->radius(), colors.data(),\n      stops.empty() ? nullptr : stops.data(), colors.size(),\n      ToSkityTileMode(radial_gradient_source->tile_mode()));\n  if (radial_gradient_source->matrix_ptr()) {\n    shader->SetLocalMatrix(radial_gradient_source->matrix());\n  }\n  return shader;\n}\n\nstd::shared_ptr<skity::PathEffect> RenderingMaterial::ToSkityPathEffect(\n    const PathEffect* effect) {\n  const DashPathEffect* dash_effect = effect->asDash();\n  FML_DCHECK(dash_effect);\n  const int interval_count = dash_effect->count_;\n  FML_DCHECK(interval_count > 0);\n  std::vector<float> pattern(interval_count);\n  for (size_t i = 0; i < static_cast<size_t>(interval_count); i++) {\n    pattern[i] = dash_effect->intervals()[i];\n  }\n  return skity::PathEffect::MakeDashPathEffect(\n      pattern.data(), dash_effect->count_, dash_effect->phase_);\n}\n\nstd::shared_ptr<skity::ColorFilter> RenderingMaterial::ToSkityColorFilter(\n    const ColorFilter* color_filter) {\n  switch (color_filter->type()) {\n    case ColorFilterType::kBlend: {\n      const BlendColorFilter* blend_color_filter = color_filter->asBlend();\n      return skity::ColorFilters::Blend(\n          to_color(blend_color_filter->color()),\n          ToSkityBlendMode(blend_color_filter->mode()));\n    }\n    case ColorFilterType::kMatrix: {\n      const MatrixColorFilter* matrix_color_filter = color_filter->asMatrix();\n      float matrix[20] = {0};\n      matrix_color_filter->get_matrix(matrix);\n      return skity::ColorFilters::Matrix(matrix);\n    }\n    case ColorFilterType::kSrgbToLinearGamma: {\n      return skity::ColorFilters::SRGBToLinearGamma();\n    }\n    case ColorFilterType::kLinearToSrgbGamma: {\n      return skity::ColorFilters::LinearToSRGBGamma();\n    }\n    default:\n      return nullptr;\n  }\n}\n\nskity::BlendMode RenderingMaterial::ToSkityBlendMode(BlendMode mode) {\n  switch (mode) {\n    case BlendMode::kClear:\n      return skity::BlendMode::kClear;\n    case BlendMode::kSrc:\n      return skity::BlendMode::kSrc;\n    case BlendMode::kDst:\n      return skity::BlendMode::kDst;\n    case BlendMode::kSrcOver:\n      return skity::BlendMode::kSrcOver;\n    case BlendMode::kDstOver:\n      return skity::BlendMode::kDstOver;\n    case BlendMode::kSrcIn:\n      return skity::BlendMode::kSrcIn;\n    case BlendMode::kDstIn:\n      return skity::BlendMode::kDstIn;\n    case BlendMode::kSrcOut:\n      return skity::BlendMode::kSrcOut;\n    case BlendMode::kDstOut:\n      return skity::BlendMode::kDstOut;\n    case BlendMode::kSrcATop:\n      return skity::BlendMode::kSrcATop;\n    case BlendMode::kDstATop:\n      return skity::BlendMode::kDstATop;\n    case BlendMode::kXor:\n      return skity::BlendMode::kXor;\n    case BlendMode::kPlus:\n      return skity::BlendMode::kPlus;\n    case BlendMode::kModulate:\n      return skity::BlendMode::kModulate;\n    case BlendMode::kScreen:\n      return skity::BlendMode::kScreen;\n    case BlendMode::kOverlay:\n      return skity::BlendMode::kOverlay;\n    case BlendMode::kDarken:\n      return skity::BlendMode::kDarken;\n    case BlendMode::kLighten:\n      return skity::BlendMode::kLighten;\n    case BlendMode::kColorDodge:\n      return skity::BlendMode::kColorDodge;\n    case BlendMode::kColorBurn:\n      return skity::BlendMode::kColorBurn;\n    default:\n      // TODO(tangruiwen) support other blend mode\n      return skity::BlendMode::kSrcOut;\n  }\n}\n\nstd::shared_ptr<skity::ImageFilter> RenderingMaterial::ToSkityImageFilter(\n    const clay::ImageFilter* image_filter) {\n  switch (image_filter->type()) {\n    case clay::ImageFilterType::kBlur: {\n      const clay::BlurImageFilter* blur_image_filter = image_filter->asBlur();\n      return skity::ImageFilters::Blur(blur_image_filter->sigma_x(),\n                                       blur_image_filter->sigma_y());\n    }\n    case clay::ImageFilterType::kDropShadow: {\n      const clay::DropShadowImageFilter* drop_shadow =\n          image_filter->asDropShadow();\n      return skity::ImageFilters::DropShadow(\n          drop_shadow->dx(), drop_shadow->dy(), drop_shadow->sigma_x(),\n          drop_shadow->sigma_y(), drop_shadow->color(), nullptr);\n    }\n    case clay::ImageFilterType::kDilate: {\n      const clay::DilateImageFilter* dilate_image_filter =\n          image_filter->asDilate();\n      return skity::ImageFilters::Dilate(dilate_image_filter->radius_x(),\n                                         dilate_image_filter->radius_y());\n    }\n    case clay::ImageFilterType::kErode: {\n      const clay::ErodeImageFilter* erode_image_filter =\n          image_filter->asErode();\n      return skity::ImageFilters::Erode(erode_image_filter->radius_x(),\n                                        erode_image_filter->radius_y());\n    }\n    case clay::ImageFilterType::kMatrix: {\n      // const clay::DlMatrixImageFilter* matrix_image_filter =\n      //     image_filter->asMatrix();\n      // return skity::ImageFilters::Matrix();\n      SKITY_UNIMPLEMENTED(\"ImageFilter-Matrix\");\n      return nullptr;\n    }\n    case clay::ImageFilterType::kComposeFilter: {\n      SKITY_UNIMPLEMENTED(\"ImageFilter-ComposeFilter\");\n      return nullptr;\n    }\n    case clay::ImageFilterType::kColorFilter: {\n      SKITY_UNIMPLEMENTED(\"ImageFilter-ColorFilter\");\n      return nullptr;\n    }\n    default:\n      return nullptr;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/skity/rendering_material.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKITY_RENDERING_MATERIAL_H_\n#define CLAY_GFX_SKITY_RENDERING_MATERIAL_H_\n\n#include <memory>\n\n#include \"clay/gfx/style/color_filter.h\"\n#include \"clay/gfx/style/color_source.h\"\n#include \"clay/gfx/style/image_filter.h\"\n#include \"clay/gfx/style/path_effect.h\"\n#include \"skity/effect/color_filter.hpp\"\n#include \"skity/effect/image_filter.hpp\"\n#include \"skity/effect/path_effect.hpp\"\n#include \"skity/effect/shader.hpp\"\n#include \"skity/geometry/matrix.hpp\"\n#include \"skity/graphic/alpha_type.hpp\"\n#include \"skity/graphic/tile_mode.hpp\"\n\nnamespace clay {\n\nclass RenderingMaterial {\n public:\n  static std::shared_ptr<skity::Shader> ToSkityLinearGradient(\n      const LinearGradientColorSource* linear_gradient_source);\n\n  static std::shared_ptr<skity::Shader> ToSkityRadialGradient(\n      const RadialGradientColorSource* radial_gradient_source);\n\n  static std::shared_ptr<skity::PathEffect> ToSkityPathEffect(\n      const PathEffect* effect);\n\n  static std::shared_ptr<skity::ColorFilter> ToSkityColorFilter(\n      const ColorFilter* color_filter);\n\n  static skity::BlendMode ToSkityBlendMode(BlendMode mode);\n\n  static skity::TileMode ToSkityTileMode(TileMode tile_mode);\n\n  static std::shared_ptr<skity::ImageFilter> ToSkityImageFilter(\n      const clay::ImageFilter* image_filter);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKITY_RENDERING_MATERIAL_H_\n"
  },
  {
    "path": "clay/gfx/skity/skity_auto_canvas_save.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKITY_SKITY_AUTO_CANVAS_SAVE_H_\n#define CLAY_GFX_SKITY_SKITY_AUTO_CANVAS_SAVE_H_\n\n#include \"skity/render/canvas.hpp\"\n\nnamespace clay {\n\nclass SkityAutoCanvasRestore {\n public:\n  SkityAutoCanvasRestore(skity::Canvas* canvas, bool do_save)\n      : canvas_(canvas), save_count_(0) {\n    if (canvas_) {\n      save_count_ = canvas->GetSaveCount();\n      if (do_save) {\n        canvas->Save();\n      }\n    }\n  }\n\n  ~SkityAutoCanvasRestore() {\n    if (canvas_) {\n      canvas_->RestoreToCount(save_count_);\n    }\n  }\n\n  void restore() {\n    if (canvas_) {\n      canvas_->RestoreToCount(save_count_);\n      canvas_ = nullptr;\n    }\n  }\n\n private:\n  skity::Canvas* canvas_;\n  int save_count_;\n  SkityAutoCanvasRestore(SkityAutoCanvasRestore&&) = delete;\n  SkityAutoCanvasRestore(const SkityAutoCanvasRestore&) = delete;\n  SkityAutoCanvasRestore& operator=(SkityAutoCanvasRestore&&) = delete;\n  SkityAutoCanvasRestore& operator=(const SkityAutoCanvasRestore&) = delete;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKITY_SKITY_AUTO_CANVAS_SAVE_H_\n"
  },
  {
    "path": "clay/gfx/skity/skity_canvas.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/skity/skity_canvas.h\"\n\n#include <utility>\n\n#include \"clay/gfx/image/graphics_image_skity_lazy.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n\nnamespace clay {\n\nint SkityCanvas::Save() { return canvas_->Save(); }\n\nvoid SkityCanvas::Restore() { canvas_->Restore(); }\n\nint SkityCanvas::SaveLayer(const skity::Rect& bounds, const Paint* paint) {\n  skity::Paint skity_paint;\n  if (paint) {\n    skity_paint = paint->gr_object();\n  }\n  return canvas_->SaveLayer(bounds, skity_paint);\n}\n\nint SkityCanvas::GetSaveCount() { return canvas_->GetSaveCount(); }\n\nvoid SkityCanvas::RestoreToCount(int save_count) {\n  canvas_->RestoreToCount(save_count);\n}\n\nvoid SkityCanvas::Translate(float dx, float dy) { canvas_->Translate(dx, dy); }\n\nvoid SkityCanvas::Scale(float sx, float sy) { canvas_->Scale(sx, sy); }\n\nvoid SkityCanvas::Rotate(float degrees) { canvas_->Rotate(degrees); }\n\nvoid SkityCanvas::Rotate(float degrees, float px, float py) {\n  canvas_->Rotate(degrees, px, py);\n}\n\nvoid SkityCanvas::Skew(float sx, float sy) { canvas_->Skew(sx, sy); }\n\nvoid SkityCanvas::Concat(const skity::Matrix& matrix) {\n  canvas_->Concat(matrix);\n}\nvoid SkityCanvas::SetMatrix(const skity::Matrix& matrix) {\n  canvas_->ResetMatrix();\n  canvas_->Concat(matrix);\n}\n\nvoid SkityCanvas::ClipRect(const skity::Rect& rect, skity::Canvas::ClipOp op,\n                           bool do_anti_alias) {\n  canvas_->ClipRect(rect, op);\n}\n\nvoid SkityCanvas::ClipRRect(const skity::RRect& rrect, skity::Canvas::ClipOp op,\n                            bool do_anti_alias) {\n  canvas_->ClipRRect(rrect, op);\n}\n\nvoid SkityCanvas::ClipPath(const skity::Path& path, skity::Canvas::ClipOp op,\n                           bool do_anti_alias) {\n  canvas_->ClipPath(path, op);\n}\n\nbool SkityCanvas::QuickReject(const skity::Rect& rect) {\n  return canvas_->QuickReject(rect);\n}\n\nbool SkityCanvas::GetDeviceClipBounds(skity::Rect* bounds) {\n  if (!bounds) {\n    return false;\n  }\n  *bounds = canvas_->GetGlobalClipBounds();\n  return true;\n}\n\nvoid SkityCanvas::Clear(uint32_t color) {\n  canvas_->Clear(static_cast<skity::Color>(color));\n}\n\nvoid SkityCanvas::DrawPaint(const Paint& paint) {\n  skity::Paint skity_paint = paint.gr_object();\n  canvas_->DrawPaint(skity_paint);\n  RecordDynamicOpOffset(paint);\n}\n\nvoid SkityCanvas::DrawLine(float x0, float y0, float x1, float y1,\n                           const Paint& paint) {\n  canvas_->DrawLine(x0, y0, x1, y1, paint.gr_object());\n  RecordDynamicOpOffset(paint);\n}\n\nvoid SkityCanvas::DrawRect(const skity::Rect& rect, const Paint& paint) {\n  canvas_->DrawRect(rect, paint.gr_object());\n  RecordDynamicOpOffset(paint);\n}\n\nvoid SkityCanvas::DrawRRect(const skity::RRect& rrect, const Paint& paint) {\n  canvas_->DrawRRect(rrect, paint.gr_object());\n  RecordDynamicOpOffset(paint);\n}\n\nvoid SkityCanvas::DrawDRRect(const skity::RRect& outer,\n                             const skity::RRect& inner, const Paint& paint) {\n  if (outer.IsEmpty()) {\n    return;\n  }\n  if (inner.IsEmpty()) {\n    this->DrawRRect(outer, paint);\n    return;\n  }\n  if (!outer.GetBounds().Contains(inner.GetBounds())) {\n    return;\n  }\n  canvas_->DrawDRRect(outer, inner, paint.gr_object());\n  RecordDynamicOpOffset(paint);\n}\n\nvoid SkityCanvas::DrawCircle(float cx, float cy, float radius,\n                             const Paint& paint) {\n  canvas_->DrawCircle(cx, cy, radius, paint.gr_object());\n  RecordDynamicOpOffset(paint);\n}\n\nvoid SkityCanvas::DrawArc(const skity::Rect& oval, float start_angle,\n                          float sweep_angle, bool use_center,\n                          const Paint& paint) {\n  canvas_->DrawArc(oval, start_angle, sweep_angle, use_center,\n                   paint.gr_object());\n  RecordDynamicOpOffset(paint);\n}\n\nvoid SkityCanvas::DrawPath(const skity::Path& path, const Paint& paint) {\n  canvas_->DrawPath(path, paint.gr_object());\n  RecordDynamicOpOffset(paint);\n}\n\nvoid SkityCanvas::DrawImage(const GraphicsImage* image, float x, float y,\n                            const GrSamplingOptions& sampling,\n                            const Paint* paint) {\n  FML_DCHECK(image);\n  FML_DCHECK(image->isTextureBacked());\n\n  skity::Rect rect =\n      skity::Rect::MakeLTRB(x, y, x + image->width(), y + image->height());\n  skity::Paint skity_paint;\n  if (paint) {\n    skity_paint = paint->gr_object();\n  }\n  auto skity_image = image->gr_image();\n  has_lazy_image_ |= image->IsLazyImage();\n  canvas_->DrawImage(\n      image->IsLazyImage()\n          ? static_cast<const GraphicsImageSkityLazy*>(image)->decoding_image()\n          : skity_image,\n      rect, sampling, &skity_paint);\n  if (paint) {\n    RecordDynamicOpOffset(*paint);\n  }\n}\n\nvoid SkityCanvas::DrawImageRect(const GraphicsImage* image,\n                                const skity::Rect& src, const skity::Rect& dst,\n                                const GrSamplingOptions& sampling,\n                                const Paint* paint) {\n  FML_DCHECK(image);\n  FML_DCHECK(image->IsLazyImage() || image->isTextureBacked());\n\n  skity::Paint skity_paint;\n  if (paint) {\n    skity_paint = paint->gr_object();\n  }\n  skity::SamplingOptions skity_sampling_options(\n      static_cast<skity::FilterMode>(sampling.filter),\n      static_cast<skity::MipmapMode>(sampling.mipmap));\n\n  auto skity_image = image->gr_image();\n  has_lazy_image_ |= image->IsLazyImage();\n  canvas_->DrawImageRect(\n      image->IsLazyImage()\n          ? static_cast<const GraphicsImageSkityLazy*>(image)->decoding_image()\n          : skity_image,\n      src, dst, sampling, &skity_paint);\n  if (paint) {\n    RecordDynamicOpOffset(*paint);\n  }\n}\n\nvoid SkityCanvas::DrawImageNine(const GraphicsImage* image,\n                                const skity::Rect& center,\n                                const skity::Rect& dst, FilterMode filter,\n                                const Paint* paint) {\n  // TODO(zhangxiao.ninja) implement later\n  FML_DCHECK(false);\n}\nvoid SkityCanvas::DrawTextBlob(const fml::RefPtr<TextBlob>& blob, float x,\n                               float y, const Paint& paint) {\n  auto skity_text_blob = blob->gr_text_blob();\n  if (!skity_text_blob) {\n    return;\n  }\n  canvas_->DrawTextBlob(skity_text_blob, x, y, paint.gr_object());\n  if (start_draw_dynamic_text_blobs_) {\n    skity::RecordedOpOffset offset = canvas_->GetLastOpOffset();\n    FML_DCHECK(offset.IsValid());\n    dynamic_ops_.emplace_back(\n        std::make_pair(clay::DynamicOpType::kSetTextColor, offset));\n  }\n}\n\nvoid SkityCanvas::DrawPicture(const Picture* picture) {\n  // TODO(zhangxiao.ninja) implement later\n  FML_DCHECK(false);\n}\n\nskity::Matrix SkityCanvas::GetTotalMatrix() {\n  return canvas_->GetTotalMatrix();\n}\n\nvoid SkityCanvas::OnDrawDynamicTextBlobsStart() {\n  start_draw_dynamic_text_blobs_ = true;\n}\n\nvoid SkityCanvas::OnDrawDynamicTextBlobsEnd() {\n  start_draw_dynamic_text_blobs_ = false;\n}\n\nvoid SkityCanvas::RecordDynamicOpOffset(const Paint& paint) {\n  switch (paint.getDynamicOpType()) {\n    case clay::DynamicOpType::kSetTextColor:\n      if (start_draw_dynamic_text_blobs_) {\n        skity::RecordedOpOffset offset = canvas_->GetLastOpOffset();\n        FML_DCHECK(offset.IsValid());\n        dynamic_ops_.emplace_back(\n            std::make_pair(clay::DynamicOpType::kSetTextColor, offset));\n      }\n      break;\n    case clay::DynamicOpType::kSetBackgroundColor: {\n      skity::RecordedOpOffset offset = canvas_->GetLastOpOffset();\n      FML_DCHECK(offset.IsValid());\n      dynamic_ops_.emplace_back(\n          std::make_pair(clay::DynamicOpType::kSetBackgroundColor, offset));\n    } break;\n    default:\n      break;\n  }\n}\n\nSkityRecorderCanvas::SkityRecorderCanvas(const skity::Rect& bounds,\n                                         fml::RefPtr<GPUUnrefQueue> unref_queue)\n    : unref_queue_(unref_queue),\n      picture_recorder_(std::make_unique<skity::PictureRecorder>()) {\n  picture_recorder_->BeginRecording(bounds);\n  canvas_ = picture_recorder_->GetRecordingCanvas();\n}\n\nstd::unique_ptr<Picture> SkityRecorderCanvas::FinishRecordingAsPicture() {\n  auto display_list = fml::MakeRefCounted<PictureSkity>(\n      std::move(dynamic_ops_), picture_recorder_->FinishRecording());\n  return std::make_unique<Picture>(\n      GPUObject(std::move(display_list), std::move(unref_queue_)),\n      has_lazy_image_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/skity/skity_canvas.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKITY_SKITY_CANVAS_H_\n#define CLAY_GFX_SKITY_SKITY_CANVAS_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/picture_animation_type.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/graphics_canvas.h\"\n#include \"skity/recorder/picture_recorder.hpp\"\n#include \"skity/recorder/recording_canvas.hpp\"\n#include \"skity/render/canvas.hpp\"\n\nnamespace clay {\n\nclass SkityCanvas : public GraphicsCanvas {\n public:\n  SkityCanvas() = default;\n  ~SkityCanvas() override = default;\n\n  int Save() override;\n  void Restore() override;\n  int SaveLayer(const skity::Rect& bounds, const Paint* paint) override;\n  int GetSaveCount() override;\n  void RestoreToCount(int save_count) override;\n\n  void Translate(float dx, float dy) override;\n  void Scale(float sx, float sy) override;\n  void Rotate(float degrees) override;\n  void Rotate(float degrees, float px, float py) override;\n  void Skew(float sx, float sy) override;\n  void Concat(const skity::Matrix& matrix) override;\n  void SetMatrix(const skity::Matrix& matrix) override;\n\n  void ClipRect(const skity::Rect& rect, skity::Canvas::ClipOp op,\n                bool do_anti_alias) override;\n\n  void ClipRRect(const skity::RRect& rrect, skity::Canvas::ClipOp op,\n                 bool do_anti_alias) override;\n\n  void ClipPath(const skity::Path& path, skity::Canvas::ClipOp op,\n                bool do_anti_alias) override;\n\n  bool QuickReject(const skity::Rect& rect) override;\n\n  bool GetDeviceClipBounds(skity::Rect* bounds) override;\n\n  void Clear(uint32_t color) override;\n\n  void DrawPaint(const Paint& paint) override;\n  void DrawLine(float x0, float y0, float x1, float y1,\n                const Paint& paint) override;\n  void DrawRect(const skity::Rect& rect, const Paint& paint) override;\n  void DrawRRect(const skity::RRect& rrect, const Paint& paint) override;\n  void DrawDRRect(const skity::RRect& outer, const skity::RRect& inner,\n                  const Paint& paint) override;\n  void DrawCircle(float cx, float cy, float radius,\n                  const Paint& paint) override;\n  void DrawArc(const skity::Rect& oval, float start_angle, float sweep_angle,\n               bool use_center, const Paint& paint) override;\n  void DrawPath(const skity::Path& path, const Paint& paint) override;\n  void DrawImage(const GraphicsImage* image, float x, float y,\n                 const GrSamplingOptions& sampling,\n                 const Paint* paint) override;\n  void DrawImageRect(const GraphicsImage* image, const skity::Rect& src,\n                     const skity::Rect& dst, const GrSamplingOptions& sampling,\n                     const Paint* paint) override;\n  void DrawImageNine(const GraphicsImage* image, const skity::Rect& center,\n                     const skity::Rect& dst, FilterMode filter,\n                     const Paint* paint) override;\n  void DrawTextBlob(const fml::RefPtr<TextBlob>& blob, float x, float y,\n                    const Paint& paint) override;\n\n  void DrawPicture(const Picture* picture) override;\n\n  skity::Matrix GetTotalMatrix() override;\n\n  skity::Canvas* GetGrCanvas() override { return canvas_; }\n\n  void OnDrawDynamicTextBlobsStart() override;\n  void OnDrawDynamicTextBlobsEnd() override;\n\n  using GraphicsCanvas::ClipPath;\n  using GraphicsCanvas::ClipRect;\n  using GraphicsCanvas::ClipRRect;\n  using GraphicsCanvas::DrawImage;\n  using GraphicsCanvas::ResetMatrix;\n\n private:\n  void RecordDynamicOpOffset(const Paint& paint);\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(SkityCanvas);\n\n protected:\n  skity::RecordingCanvas* canvas_ = nullptr;\n  DynamicOps dynamic_ops_;\n  bool start_draw_dynamic_text_blobs_ = false;\n};\n\nclass SkityRecorderCanvas : public SkityCanvas {\n public:\n  SkityRecorderCanvas(const skity::Rect& bounds,\n                      fml::RefPtr<GPUUnrefQueue> unref_queue);\n  ~SkityRecorderCanvas() override = default;\n\n  std::unique_ptr<Picture> FinishRecordingAsPicture() override;\n\n private:\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n  std::unique_ptr<skity::PictureRecorder> picture_recorder_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKITY_SKITY_CANVAS_H_\n"
  },
  {
    "path": "clay/gfx/skity/skity_decoding_image.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/skity/skity_decoding_image.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nSkityDecodingImage::SkityDecodingImage(std::shared_ptr<clay::Image> image)\n    : raw_image_(image) {\n  FML_DCHECK(raw_image_);\n}\n\nSkityDecodingImage::~SkityDecodingImage() {\n  if (image_resource_) {\n    image_resource_->RemoveImageResourceClient(this);\n  }\n}\n\nint SkityDecodingImage::GetWidth() const { return raw_image_->GetWidth(); }\nint SkityDecodingImage::GetHeight() const { return raw_image_->GetHeight(); }\n\nbool SkityDecodingImage::IsTextureBackend() const {\n  return raw_image_->UseTextureBackend();\n}\n\nconst std::shared_ptr<skity::Texture>* SkityDecodingImage::GetTexture() const {\n  return decoded_image_ ? decoded_image_->GetTexture() : nullptr;\n}\n\nconst std::shared_ptr<skity::Pixmap>* SkityDecodingImage::GetPixmap() const {\n  return decoded_image_ ? decoded_image_->GetPixmap() : nullptr;\n}\n\nsize_t SkityDecodingImage::Width() const { return raw_image_->GetWidth(); }\n\nsize_t SkityDecodingImage::Height() const { return raw_image_->GetHeight(); }\n\nskity::AlphaType SkityDecodingImage::GetAlphaType() const {\n  return decoded_image_ ? decoded_image_->GetAlphaType()\n                        : skity::AlphaType::kUnknown_AlphaType;\n}\n\nbool SkityDecodingImage::ScalePixels(\n    std::shared_ptr<skity::Pixmap> dst, skity::GPUContext* context,\n    const GrSamplingOptions& sampling_options) const {\n  return decoded_image_ &&\n         decoded_image_->ScalePixels(dst, context, sampling_options);\n}\n\nvoid SkityDecodingImage::ScheduleDecodeAndUpload(\n    const LazyImageDecodeCallback& callback) {\n  callback_ = callback;\n  if (!image_resource_) {\n    image_resource_ = raw_image_->GetAccessor(true);\n    image_resource_->AddImageResourceClient(this);\n  }\n  if (auto g_image = image_resource_->GetImage()->GetGraphicsImage()) {\n    if (auto sk_image = g_image->gr_image()) {\n      decoded_image_ = g_image->gr_image();\n    }\n  }\n}\n\nbool SkityDecodingImage::MaybeAnimated() const {\n  return raw_image_ && raw_image_->MaybeAnimated();\n}\n\nvoid SkityDecodingImage::DecodeImageFinish(bool success,\n                                           const std::string& url) {\n  // Save the latest decoded image, if decoding fails, continue to use the\n  // previous frame or empty.\n  if (auto g_image = image_resource_->GetImage()->GetGraphicsImage()) {\n    if (auto sk_image = g_image->gr_image()) {\n      decoded_image_ = g_image->gr_image();\n    }\n  }\n  // Decoding is finished, notify Rasterizer to redraw LayerTree.\n  if (callback_) {\n    callback_(success);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/skity/skity_decoding_image.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKITY_SKITY_DECODING_IMAGE_H_\n#define CLAY_GFX_SKITY_SKITY_DECODING_IMAGE_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n\n#include \"clay/gfx/image/image.h\"\n#include \"clay/gfx/image/image_resource.h\"\n#include \"clay/gfx/image/image_resource_client.h\"\n#include \"skity/gpu/texture.hpp\"\n#include \"skity/graphic/alpha_type.hpp\"\n#include \"skity/graphic/image.hpp\"\n#include \"skity/graphic/sampling_options.hpp\"\n#include \"skity/io/pixmap.hpp\"\n\nnamespace clay {\n\n// The wrapper of skity image supports deferred image decode.\nclass SkityDecodingImage final : public skity::Image,\n                                 public ImageResourceClient {\n public:\n  using LazyImageDecodeCallback = std::function<void(bool)>;\n\n  explicit SkityDecodingImage(std::shared_ptr<clay::Image> image);\n  ~SkityDecodingImage() override;\n\n  void ScheduleDecodeAndUpload(const LazyImageDecodeCallback& callback);\n\n  std::shared_ptr<skity::Image> gr_image() const { return decoded_image_; }\n\n  int GetWidth() const;\n  int GetHeight() const;\n  bool MaybeAnimated() const;\n\n  // Implement functons of skity::image.\n  bool IsTextureBackend() const override;\n  const std::shared_ptr<skity::Texture>* GetTexture() const override;\n  const std::shared_ptr<skity::Pixmap>* GetPixmap() const override;\n  size_t Width() const override;\n  size_t Height() const override;\n  skity::AlphaType GetAlphaType() const override;\n  bool ScalePixels(std::shared_ptr<skity::Pixmap> dst,\n                   skity::GPUContext* context,\n                   const GrSamplingOptions& sampling_options) const override;\n  skity::ImageType GetImageType() const override {\n    return skity::ImageType::kCustom;\n  }\n\n  // Implement functons of ImageResourceClient\n  bool WillRenderImage() override { return true; }\n  void RequestRenderImage(ImageResource* image_resource,\n                          bool success) override {}\n  void OnImageChanged() override {}\n  void DecodeImageFinish(bool success, const std::string& url) override;\n\n private:\n  LazyImageDecodeCallback callback_;\n  std::shared_ptr<clay::Image> raw_image_;\n  std::shared_ptr<skity::Image> decoded_image_;\n  std::unique_ptr<ImageResource> image_resource_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKITY_SKITY_DECODING_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/skity/skity_image.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/skity/skity_image.h\"\n\nnamespace clay {\n\nSkityImage::SkityImage(std::shared_ptr<skity::Image> image,\n                       ClayVoidCallback destruction_callback, void* user_data)\n    : image_(image),\n      destruction_callback_(destruction_callback),\n      user_data_(user_data) {}\n\nSkityImage::~SkityImage() {\n  if (destruction_callback_) {\n    destruction_callback_(user_data_);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/skity/skity_image.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKITY_SKITY_IMAGE_H_\n#define CLAY_GFX_SKITY_SKITY_IMAGE_H_\n\n#include <memory>\n\n#include \"clay/public/clay.h\"\n#include \"skity/graphic/image.hpp\"\n\nnamespace clay {\n\nclass SkityImage {\n public:\n  explicit SkityImage(std::shared_ptr<skity::Image> image,\n                      ClayVoidCallback destruction_callback = nullptr,\n                      void* user_data = nullptr);\n  ~SkityImage();\n\n  std::shared_ptr<skity::Image> gr_image() const { return image_; }\n\n private:\n  SkityImage() = default;\n\n  std::shared_ptr<skity::Image> image_;\n  ClayVoidCallback destruction_callback_;\n  void* user_data_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKITY_SKITY_IMAGE_H_\n"
  },
  {
    "path": "clay/gfx/skity/skity_lazy_image_traveller.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/skity/skity_lazy_image_traveller.h\"\n\n#include \"clay/gfx/skity/skity_decoding_image.h\"\n#include \"skity/graphic/image.hpp\"\n#include \"skity/render/canvas.hpp\"\n\nnamespace clay {\n\nnamespace {\nclass TraversalHelper : public skity::Canvas {\n public:\n  explicit TraversalHelper(const LazyImageDecodeCallback& callback)\n      : callback_(callback) {}\n\n  bool hasLazyImage() const { return has_lazy_image_; }\n\n protected:\n  using ClipOp = skity::Canvas::ClipOp;\n  void OnClipRect(skity::Rect const& rect, ClipOp op) override {}\n  void OnClipPath(skity::Path const& path, ClipOp op) override {}\n  void OnDrawGlyphs(uint32_t count, const skity::GlyphID glyphs[],\n                    const float position_x[], const float position_y[],\n                    const skity::Font& font,\n                    const skity::Paint& paint) override {}\n  void OnDrawPath(skity::Path const& path, skity::Paint const& paint) override {\n  }\n  void OnDrawPaint(skity::Paint const& paint) override {}\n  void OnSaveLayer(const skity::Rect& bounds,\n                   const skity::Paint& paint) override {}\n  void OnDrawBlob(const skity::TextBlob* blob, float x, float y,\n                  skity::Paint const& paint) override {}\n\n  void OnSave() override {}\n  void OnRestore() override {}\n  void OnRestoreToCount(int saveCount) override {}\n  void OnFlush() override {}\n  uint32_t OnGetWidth() const override { return 0; }\n  uint32_t OnGetHeight() const override { return 0; }\n\n  void OnDrawImageRect(std::shared_ptr<skity::Image> image,\n                       const skity::Rect& src, const skity::Rect& dst,\n                       const skity::SamplingOptions& sampling,\n                       skity::Paint const* paint) override {\n    DecodeAndUploadImageIfNeeded(image);\n  }\n\n private:\n  void DecodeAndUploadImageIfNeeded(const std::shared_ptr<skity::Image> image);\n\n  const LazyImageDecodeCallback& callback_;\n  bool has_lazy_image_ = false;\n};\n}  // namespace\n\nbool SkityLazyImageTraveller::Traversal(\n    skity::DisplayList* disp_list, const LazyImageDecodeCallback& callback) {\n  TraversalHelper helper = TraversalHelper(callback);\n  disp_list->Draw(&helper);\n  return helper.hasLazyImage();\n}\n\nvoid TraversalHelper::DecodeAndUploadImageIfNeeded(\n    const std::shared_ptr<skity::Image> image) {\n  if (image && image->GetImageType() != skity::ImageType::kCustom) {\n    return;\n  }\n  SkityDecodingImage* decoding_image =\n      static_cast<SkityDecodingImage*>(image.get());\n  if (decoding_image) {\n    // For multi-frame images, it is necessary to keep triggering redrawing to\n    // drive the image animation.\n    if (decoding_image->MaybeAnimated() || !decoding_image->gr_image()) {\n      decoding_image->ScheduleDecodeAndUpload(callback_);\n      has_lazy_image_ = true;\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/skity/skity_lazy_image_traveller.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SKITY_SKITY_LAZY_IMAGE_TRAVELLER_H_\n#define CLAY_GFX_SKITY_SKITY_LAZY_IMAGE_TRAVELLER_H_\n\n#include <functional>\n#include <memory>\n#include <vector>\n\n// Forward declarations to avoid heavy Skity includes in headers.\nnamespace skity {\nclass DisplayList;\n}  // namespace skity\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// Can be fed to the dispatch() method of a DisplayList to feed the resulting\n/// rendering operations to an SkCanvas instance.\n///\n/// Receives all methods on Dispatcher and sends them to an SkCanvas\n///\n\nusing LazyImageDecodeCallback = std::function<void(bool)>;\n\nclass SkityLazyImageTraveller {\n public:\n  static bool Traversal(skity::DisplayList* disp_list,\n                        const LazyImageDecodeCallback& callback);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKITY_SKITY_LAZY_IMAGE_TRAVELLER_H_\n"
  },
  {
    "path": "clay/gfx/skity_to_skia_utils.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_GFX_SKITY_TO_SKIA_UTILS_H_\n#define CLAY_GFX_SKITY_TO_SKIA_UTILS_H_\n\n#include \"clay/gfx/image/image_info.h\"\n#include \"skity/geometry/matrix.hpp\"\n#include \"skity/geometry/rect.hpp\"\n#include \"skity/geometry/rrect.hpp\"\n#ifndef ENABLE_SKITY\n#include \"third_party/skia/include/core/SkClipOp.h\"\n#include \"third_party/skia/include/core/SkM44.h\"\n#include \"third_party/skia/include/core/SkMatrix.h\"\n#include \"third_party/skia/include/core/SkRRect.h\"\n#include \"third_party/skia/include/core/SkRect.h\"\n#include \"third_party/skia/include/core/SkSamplingOptions.h\"\n#else\n#include \"skity/graphic/sampling_options.hpp\"\n#include \"skity/render/canvas.hpp\"\n#endif  // ENABLE_SKITY\n\nnamespace clay {\n\n#ifndef ENABLE_SKITY\ninline skity::Matrix ConvertSkMatrixToSkityMatrix(const SkMatrix& m) {\n  return skity::Matrix{m[0], m[3], 0, m[6],  //\n                       m[1], m[4], 0, m[7],  //\n                       0,    0,    1, 0,     //\n                       m[2], m[5], 0, m[8]};\n}\n\ninline SkMatrix ConvertSkityMatrixToSkMatrix(const skity::Matrix& m) {\n  SkMatrix matrix;\n  matrix.setAll(m[0][0], m[1][0], m[3][0],  //\n                m[0][1], m[1][1], m[3][1],  //\n                m[0][3], m[1][3], m[3][3]);\n  return matrix;\n}\n\ninline skity::Matrix ConvertSkM44ToMatrix(const SkM44& sk_matrix) {\n  float m[16];\n  sk_matrix.getColMajor(m);\n  return skity::Matrix{m[0],  m[1],  m[2],  m[3],   //\n                       m[4],  m[5],  m[6],  m[7],   //\n                       m[8],  m[9],  m[10], m[11],  //\n                       m[12], m[13], m[14], m[15]};\n}\n\ninline SkM44 ConvertSkityMatrixToSkM44(const skity::Matrix& m) {\n  return SkM44{\n      m[0][0], m[1][0], m[2][0], m[3][0],  //\n      m[0][1], m[1][1], m[2][1], m[3][1],  //\n      m[0][2], m[1][2], m[2][2], m[3][2],  //\n      m[0][3], m[1][3], m[2][3], m[3][3],  //\n  };\n}\n\ninline skity::Rect ConvertSkRectToSkityRect(const SkRect& sk_rect) {\n  return skity::Rect(sk_rect.left(), sk_rect.top(), sk_rect.right(),\n                     sk_rect.bottom());\n}\n\ninline skity::Rect ConvertSkIRectToSkityRect(const SkIRect& sk_rect) {\n  return skity::Rect(sk_rect.left(), sk_rect.top(), sk_rect.right(),\n                     sk_rect.bottom());\n}\n\ninline SkRect ConvertSkityRectToSkRect(const skity::Rect& sk_rect) {\n  return SkRect::MakeLTRB(sk_rect.Left(), sk_rect.Top(), sk_rect.Right(),\n                          sk_rect.Bottom());\n}\n\ninline SkIRect ConvertSkityRectToSkIRect(const skity::Rect& sk_rect) {\n  return SkIRect::MakeLTRB(sk_rect.Left(), sk_rect.Top(), sk_rect.Right(),\n                           sk_rect.Bottom());\n}\n\ninline skity::Vec2 ConvertSkVectorToSkity(const SkVector& vec) {\n  return skity::Vec2{vec.fX, vec.fY};\n}\n\ninline SkVector ConvertSkityVectorToSkia(const skity::Vec2& vec) {\n  return SkVector::Make(vec.x, vec.y);\n}\n\ninline skity::RRect ConvertSkRRectToSkityRRect(const SkRRect& rrect) {\n  switch (rrect.getType()) {\n    case SkRRect::kEmpty_Type:\n      return skity::RRect::MakeEmpty();\n    case SkRRect::kRect_Type:\n      return skity::RRect::MakeRect(\n          clay::ConvertSkRectToSkityRect(rrect.rect()));\n    case SkRRect::kOval_Type:\n      return skity::RRect::MakeOval(\n          clay::ConvertSkRectToSkityRect(rrect.rect()));\n    case SkRRect::kSimple_Type:\n      return skity::RRect::MakeRectXY(\n          clay::ConvertSkRectToSkityRect(rrect.rect()),\n          rrect.getSimpleRadii().x(), rrect.getSimpleRadii().y());\n    case SkRRect::kNinePatch_Type:\n    case SkRRect::kComplex_Type:\n      skity::RRect result;\n      skity::Vec2 radii[4]{\n          ConvertSkVectorToSkity(rrect.radii(SkRRect::kUpperLeft_Corner)),\n          ConvertSkVectorToSkity(rrect.radii(SkRRect::kUpperRight_Corner)),\n          ConvertSkVectorToSkity(rrect.radii(SkRRect::kLowerRight_Corner)),\n          ConvertSkVectorToSkity(rrect.radii(SkRRect::kLowerLeft_Corner)),\n      };\n      result.SetRectRadii(clay::ConvertSkRectToSkityRect(rrect.rect()), radii);\n      return result;\n  }\n}\n\ninline SkRRect ConvertSkityRRectToSkia(const skity::RRect& rrect) {\n  switch (rrect.GetType()) {\n    case skity::RRect::Type::kEmpty:\n      return SkRRect::MakeEmpty();\n    case skity::RRect::Type::kRect:\n      return SkRRect::MakeRect(clay::ConvertSkityRectToSkRect(rrect.GetRect()));\n    case skity::RRect::Type::kOval:\n      return SkRRect::MakeOval(clay::ConvertSkityRectToSkRect(rrect.GetRect()));\n    case skity::RRect::Type::kSimple:\n      return SkRRect::MakeRectXY(\n          clay::ConvertSkityRectToSkRect(rrect.GetRect()),\n          rrect.Radii(skity::RRect::Corner::kLowerLeft).x,\n          rrect.Radii(skity::RRect::Corner::kLowerLeft).y);\n    case skity::RRect::Type::kNinePatch:\n    case skity::RRect::Type::kComplex:\n      SkRRect result;\n      SkVector radii[4]{\n          ConvertSkityVectorToSkia(\n              rrect.Radii(skity::RRect::Corner::kUpperLeft)),\n          ConvertSkityVectorToSkia(\n              rrect.Radii(skity::RRect::Corner::kUpperRight)),\n          ConvertSkityVectorToSkia(\n              rrect.Radii(skity::RRect::Corner::kLowerRight)),\n          ConvertSkityVectorToSkia(\n              rrect.Radii(skity::RRect::Corner::kLowerLeft)),\n      };\n      result.setRectRadii(clay::ConvertSkityRectToSkRect(rrect.GetRect()),\n                          radii);\n      return result;\n  }\n}\n#else\ninline skity::AlphaType ConvertToSkityAlphaType(AlphaType type) {\n  switch (type) {\n    case AlphaType::kUnknown_AlphaType:\n      return skity::AlphaType::kUnknown_AlphaType;\n    case AlphaType::kOpaque_AlphaType:\n      return skity::AlphaType::kOpaque_AlphaType;\n    case AlphaType::kPremul_AlphaType:\n      return skity::AlphaType::kPremul_AlphaType;\n    case AlphaType::kUnpremul_AlphaType:\n      return skity::AlphaType::kUnpremul_AlphaType;\n  }\n}\n\ninline skity::ColorType ConvertToSkityColorType(ColorType type) {\n  switch (type) {\n    case ColorType::kRGBA_8888_ColorType:\n      return skity::ColorType::kRGBA;\n    case ColorType::kBGRA_8888_ColorType:\n      return skity::ColorType::kBGRA;\n    case ColorType::kRGB_565_ColorType:\n      return skity::ColorType::kRGB565;\n    case ColorType::kAlpha_8_ColorType:\n      return skity::ColorType::kA8;\n    default:\n      return skity::ColorType::kUnknown;\n  }\n}\ninline ColorType ConvertToClayColorType(skity::ColorType skity_type) {\n  switch (skity_type) {\n    case skity::ColorType::kRGBA:\n      return kRGBA_8888_ColorType;\n    case skity::ColorType::kBGRA:\n      return kBGRA_8888_ColorType;\n    case skity::ColorType::kRGB565:\n      return kRGB_565_ColorType;\n    case skity::ColorType::kA8:\n      return kAlpha_8_ColorType;\n    default:\n      return kUnknown_ColorType;\n  }\n}\ninline AlphaType ConvertToClayAlphaType(skity::AlphaType skity_alpha_type) {\n  switch (skity_alpha_type) {\n    case skity::AlphaType::kOpaque_AlphaType:\n      return kOpaque_AlphaType;\n    case skity::AlphaType::kPremul_AlphaType:\n      return kPremul_AlphaType;\n    case skity::AlphaType::kUnpremul_AlphaType:\n      return kUnpremul_AlphaType;\n    default:\n      return kUnknown_AlphaType;\n  }\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_SKITY_TO_SKIA_UTILS_H_\n"
  },
  {
    "path": "clay/gfx/style/blend_mode.h",
    "content": "/*\n * Copyright 2022 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n * Copyright 2016 Google Inc.\n *\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file.\n */\n\n#ifndef CLAY_GFX_STYLE_BLEND_MODE_H_\n#define CLAY_GFX_STYLE_BLEND_MODE_H_\n\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n/// A enum define the blend mode.\n/// Blends are operators that take in two colors (source, destination) and\n/// return a new color. Blends are operators that take in two colors (source,\n/// destination) and return a new color. Many of these operate the same on all 4\n/// components: red, green, blue, alpha. For these, we just document what\n/// happens to one component, rather than naming each one separately. Different\n/// SkColorTypes have different representations for color components:\n///     8-bit: 0..255\n///     6-bit: 0..63\n///     5-bit: 0..31\n///     4-bit: 0..15\n///    floats: 0...1\n/// The documentation is expressed as if the component values are always 0..1\n/// (floats). For brevity, the documentation uses the following abbreviations s\n/// : source d  : destination sa : source alpha da : destination alpha Results\n/// are abbreviated r  : if all 4 components are computed in the same manner ra\n/// : result alpha component rc : result \"color\": red, green, blue components\nenum class BlendMode {\n  kClear,     //!< r = 0\n  kSrc,       //!< r = s\n  kDst,       //!< r = d\n  kSrcOver,   //!< r = s + (1-sa)*d\n  kDstOver,   //!< r = d + (1-da)*s\n  kSrcIn,     //!< r = s * da\n  kDstIn,     //!< r = d * sa\n  kSrcOut,    //!< r = s * (1-da)\n  kDstOut,    //!< r = d * (1-sa)\n  kSrcATop,   //!< r = s*da + d*(1-sa)\n  kDstATop,   //!< r = d*sa + s*(1-da)\n  kXor,       //!< r = s*(1-da) + d*(1-sa)\n  kPlus,      //!< r = min(s + d, 1)\n  kModulate,  //!< r = s*d\n  kScreen,    //!< r = s + d - s*d\n\n  kOverlay,     //!< multiply or screen, depending on destination\n  kDarken,      //!< rc = s + d - max(s*da, d*sa), ra = kSrcOver\n  kLighten,     //!< rc = s + d - min(s*da, d*sa), ra = kSrcOver\n  kColorDodge,  //!< brighten destination to reflect source\n  kColorBurn,   //!< darken destination to reflect source\n  kHardLight,   //!< multiply or screen, depending on source\n  kSoftLight,   //!< lighten or darken, depending on source\n  kDifference,  //!< rc = s + d - 2*(min(s*da, d*sa)), ra = kSrcOver\n  kExclusion,   //!< rc = s + d - two(s*d), ra = kSrcOver\n  kMultiply,    //!< r = s*(1-da) + d*(1-sa) + s*d\n\n  kHue,         //!< hue of source with saturation and luminosity of destination\n  kSaturation,  //!< saturation of source with hue and luminosity of destination\n  kColor,       //!< hue and saturation of source with luminosity of destination\n  kLuminosity,  //!< luminosity of source with hue and saturation of destination\n\n  kLastCoeffMode = kScreen,  //!< last porter duff blend mode\n  kLastSeparableMode =\n      kMultiply,  //!< last blend mode operating separately on components\n  kLastMode = kLuminosity,  //!< last valid value\n  kDefaultMode = kSrcOver,\n};\n\n#ifndef ENABLE_SKITY\ninline BlendMode ToClay(SkBlendMode mode) {\n  return static_cast<BlendMode>(mode);\n}\n\ninline SkBlendMode ToSk(BlendMode mode) {\n  return static_cast<SkBlendMode>(mode);\n}\n#else\ninline BlendMode ToClay(skity::BlendMode mode) {\n  return static_cast<BlendMode>(mode);\n}\n\ninline skity::BlendMode ToSk(BlendMode mode) {\n  return static_cast<skity::BlendMode>(mode);\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_BLEND_MODE_H_\n"
  },
  {
    "path": "clay/gfx/style/borders_data.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/borders_data.h\"\n\n#include <sstream>\n\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n\nBordersData::BordersData()\n    : width_top_(0.0f),\n      width_right_(0.0f),\n      width_bottom_(0.0f),\n      width_left_(0.0f),\n      radius_x_top_left_(0.0f),\n      radius_x_top_right_(0.0f),\n      radius_x_bottom_right_(0.0f),\n      radius_x_bottom_left_(0.0f),\n      radius_y_top_left_(0.0f),\n      radius_y_top_right_(0.0f),\n      radius_y_bottom_right_(0.0f),\n      radius_y_bottom_left_(0.0f),\n\n      color_top_(Color::kBlack().Value()),\n      color_right_(Color::kBlack().Value()),\n      color_bottom_(Color::kBlack().Value()),\n      color_left_(Color::kBlack().Value()),\n      style_top_(BorderStyleType::kSolid),\n      style_right_(BorderStyleType::kSolid),\n      style_bottom_(BorderStyleType::kSolid),\n      style_left_(BorderStyleType::kSolid) {}\n\nvoid BordersData::Reset() {\n  width_top_ = 0.0f;\n  width_right_ = 0.0f;\n  width_bottom_ = 0.0f;\n  width_left_ = 0.0f;\n  radius_x_top_left_ = 0.0f;\n  radius_x_top_right_ = 0.0f;\n  radius_x_bottom_right_ = 0.0f;\n  radius_x_bottom_left_ = 0.0f;\n  radius_y_top_left_ = 0.0f;\n  radius_y_top_right_ = 0.0f;\n  radius_y_bottom_right_ = 0.0f;\n  radius_y_bottom_left_ = 0.0f;\n  color_top_ = Color::kBlack().Value();\n  color_right_ = Color::kBlack().Value();\n  color_bottom_ = Color::kBlack().Value();\n  color_left_ = Color::kBlack().Value();\n  style_top_ = BorderStyleType::kSolid;\n  style_right_ = BorderStyleType::kSolid;\n  style_bottom_ = BorderStyleType::kSolid;\n  style_left_ = BorderStyleType::kSolid;\n}\n\nvoid BordersData::SetRadius(size_t index, const Length& x, const Length& y) {\n  switch (index) {\n    case 0:\n      radius_x_top_left_length_ = x;\n      radius_y_top_left_length_ = y;\n      break;\n    case 1:\n      radius_x_top_right_length_ = x;\n      radius_y_top_right_length_ = y;\n      break;\n    case 2:\n      radius_x_bottom_right_length_ = x;\n      radius_y_bottom_right_length_ = y;\n      break;\n    case 3:\n      radius_x_bottom_left_length_ = x;\n      radius_y_bottom_left_length_ = y;\n      break;\n    default:\n      break;\n  }\n}\n\nstatic inline float update_value(const Length& length, float v) {\n  return length.IsPercent() ? length.GetRawValue() * v : length.GetRawValue();\n}\n\nvoid BordersData::UpdateRadius(float width, float height) {\n  radius_y_top_left_ = update_value(radius_y_top_left_length_, height);\n  radius_x_top_left_ = update_value(radius_x_top_left_length_, width);\n  radius_y_top_right_ = update_value(radius_y_top_right_length_, height);\n  radius_x_top_right_ = update_value(radius_x_top_right_length_, width);\n  radius_y_bottom_right_ = update_value(radius_y_bottom_right_length_, height);\n  radius_x_bottom_right_ = update_value(radius_x_bottom_right_length_, width);\n  radius_y_bottom_left_ = update_value(radius_y_bottom_left_length_, height);\n  radius_x_bottom_left_ = update_value(radius_x_bottom_left_length_, width);\n}\n\n#ifndef NDEBUG\nstd::string BordersData::ToString() const {\n  std::stringstream ss;\n\n  if (HasBorderRadius()) {\n    if (HasSimpleRadii() && HasSameRadii()) {\n      ss << \"border-radius=(\" << radius_x_top_left_ << \")\";\n    } else if (HasSimpleRadii()) {\n      ss << \"border-radius=(\" << radius_x_top_left_ << \" \"\n         << radius_x_top_right_ << \" \" << radius_x_bottom_right_ << \" \"\n         << radius_x_bottom_left_ << \")\";\n    } else {\n      ss << \"border-radius=(\" << radius_x_top_left_ << \" \"\n         << radius_x_top_right_ << \" \" << radius_x_bottom_right_ << \" \"\n         << radius_x_bottom_left_ << \" / \" << radius_y_top_left_ << \" \"\n         << radius_y_top_right_ << \" \" << radius_y_bottom_right_ << \" \"\n         << radius_y_bottom_left_ << \")\";\n    }\n  }\n\n  if (HasBorderWidth()) {\n    if (HasBorderRadius()) {\n      ss << \" \";\n    }\n    if (HasSameWidth() && HasSameStyle() && HasSameColor()) {\n      ss << \"border=(\" << width_top_ << \" \" << static_cast<unsigned>(style_top_)\n         << \" \" << Color(color_top_).ToString() << \")\";\n    } else {\n      if (HasSameWidth()) {\n        ss << \"border-width=(\" << width_top_ << \")\";\n      } else {\n        ss << \"border-width=(\" << width_top_ << \" \" << width_right_ << \" \"\n           << width_bottom_ << \" \" << width_left_ << \")\";\n      }\n      if (HasSameStyle()) {\n        ss << \" border-style=(\" << static_cast<unsigned>(style_top_) << \")\";\n      } else {\n        ss << \" border-style=(\" << static_cast<unsigned>(style_top_) << \" \"\n           << static_cast<unsigned>(style_right_) << \" \"\n           << static_cast<unsigned>(style_bottom_) << \" \"\n           << static_cast<unsigned>(style_left_) << \")\";\n      }\n      if (HasSameColor()) {\n        ss << \" border-color=(\" << Color(color_top_).ToString() << \")\";\n      } else {\n        ss << \" border-color=(\" << Color(color_top_).ToString() << \" \"\n           << Color(color_right_).ToString() << \" \"\n           << Color(color_bottom_).ToString() << \" \"\n           << Color(color_left_).ToString() << \")\";\n      }\n    }\n  }\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/borders_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_BORDERS_DATA_H_\n#define CLAY_GFX_STYLE_BORDERS_DATA_H_\n\n#include <limits.h>\n\n#include <array>\n#include <string>\n#include <tuple>\n\n#include \"clay/gfx/style/length.h\"\n\nnamespace clay {\n\nenum class BorderStyleType : unsigned {\n  kSolid,\n  kDashed,\n  kDotted,\n  kDouble,\n  kGroove,\n  kRidge,\n  kInset,\n  kOutset,\n  kHide,\n  kNone,\n  kUndefined = INT_MAX,  // default no border style\n};\n\nclass BordersData {\n public:\n  BordersData();\n  void Reset();\n\n  float width_top_;\n  float width_right_;\n  float width_bottom_;\n  float width_left_;\n  float radius_x_top_left_;\n  float radius_x_top_right_;\n  float radius_x_bottom_right_;\n  float radius_x_bottom_left_;\n  float radius_y_top_left_;\n  float radius_y_top_right_;\n  float radius_y_bottom_right_;\n  float radius_y_bottom_left_;\n  Length radius_x_top_left_length_;\n  Length radius_x_top_right_length_;\n  Length radius_x_bottom_right_length_;\n  Length radius_x_bottom_left_length_;\n  Length radius_y_top_left_length_;\n  Length radius_y_top_right_length_;\n  Length radius_y_bottom_right_length_;\n  Length radius_y_bottom_left_length_;\n  unsigned int color_top_;\n  unsigned int color_right_;\n  unsigned int color_bottom_;\n  unsigned int color_left_;\n  BorderStyleType style_top_;\n  BorderStyleType style_right_;\n  BorderStyleType style_bottom_;\n  BorderStyleType style_left_;\n\n  void SetRadius(size_t side, const Length& x, const Length& y);\n  void UpdateRadius(float width, float height);\n  bool HasBorderRadius() const {\n    return (radius_x_top_left_ != 0.0 && radius_y_top_left_ != 0.0) ||\n           (radius_x_top_right_ != 0.0 && radius_y_top_right_ != 0.0) ||\n           (radius_x_bottom_right_ != 0.0 && radius_y_bottom_right_ != 0.0) ||\n           (radius_x_bottom_left_ != 0.0 && radius_y_bottom_left_ != 0.0);\n  }\n\n  bool HasBorderWidth() const {\n    return width_left_ != 0.0 || width_right_ != 0.0 || width_top_ != 0.0 ||\n           width_bottom_ != 0.0;\n  }\n\n  bool HasSameWidth() const {\n    return width_top_ == width_left_ && width_top_ == width_right_ &&\n           width_top_ == width_bottom_;\n  }\n\n  bool HasSimpleRadii() const {\n    return radius_x_top_left_ == radius_y_top_left_ &&\n           radius_x_top_right_ == radius_y_top_right_ &&\n           radius_x_bottom_left_ == radius_y_bottom_left_ &&\n           radius_x_bottom_right_ == radius_y_bottom_right_;\n  }\n\n  bool HasSameRadii() const {\n    return HasSimpleRadii() && radius_x_top_left_ == radius_x_top_right_ &&\n           radius_x_top_left_ == radius_x_bottom_right_ &&\n           radius_x_top_left_ == radius_x_bottom_left_;\n  }\n\n  bool HasSameColor() const {\n    return color_top_ == color_left_ && color_top_ == color_bottom_ &&\n           color_top_ == color_right_;\n  }\n\n  bool HasSameStyle() const {\n    return style_top_ == style_left_ && style_top_ == style_bottom_ &&\n           style_top_ == style_right_;\n  }\n\n  bool operator==(const BordersData& rhs) const {\n    return std::tie(width_top_, width_right_, width_bottom_, width_left_,\n                    radius_x_top_left_, radius_x_top_right_,\n                    radius_x_bottom_right_, radius_x_bottom_left_,\n                    radius_y_top_left_, radius_y_top_right_,\n                    radius_y_bottom_right_, radius_y_bottom_left_, color_top_,\n                    color_right_, color_bottom_, color_left_, style_top_,\n                    style_right_, style_bottom_, style_left_) ==\n           std::tie(rhs.width_top_, rhs.width_right_, rhs.width_bottom_,\n                    rhs.width_left_, rhs.radius_x_top_left_,\n                    rhs.radius_x_top_right_, rhs.radius_x_bottom_right_,\n                    rhs.radius_x_bottom_left_, rhs.radius_y_top_left_,\n                    rhs.radius_y_top_right_, rhs.radius_y_bottom_right_,\n                    rhs.radius_y_bottom_left_, rhs.color_top_, rhs.color_right_,\n                    rhs.color_bottom_, rhs.color_left_, rhs.style_top_,\n                    rhs.style_right_, rhs.style_bottom_, rhs.style_left_);\n  }\n\n#ifndef NDEBUG\n  std::string ToString() const;\n#endif\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_BORDERS_DATA_H_\n"
  },
  {
    "path": "clay/gfx/style/box_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_BOX_DATA_H_\n#define CLAY_GFX_STYLE_BOX_DATA_H_\n\nnamespace clay {\n\nclass BoxData {\n public:\n  BoxData() = default;\n\n  float Left() const { return left_; }\n  float Top() const { return top_; }\n  float Width() const { return width_; }\n  float Height() const { return height_; }\n  void SetLeft(float left) { left_ = left; }\n  void SetWidth(float width) { width_ = width; }\n  void SetTop(float top) { top_ = top; }\n  void SetHeight(float height) { height_ = height; }\n\n  void SetPaddingLeft(float left) { padding_left_ = left; }\n  void SetPaddingTop(float top) { padding_top_ = top; }\n  void SetPaddingRight(float right) { padding_right_ = right; }\n  void SetPaddingBottom(float bottom) { padding_bottom_ = bottom; }\n  float PaddingLeft() const { return padding_left_; }\n  float PaddingTop() const { return padding_top_; }\n  float PaddingRight() const { return padding_right_; }\n  float PaddingBottom() const { return padding_bottom_; }\n\n  void SetMarginLeft(float left) { margin_left_ = left; }\n  void SetMarginTop(float top) { margin_top_ = top; }\n  void SetMarginRight(float right) { margin_right_ = right; }\n  void SetMarginBottom(float bottom) { margin_bottom_ = bottom; }\n  float MarginLeft() const { return margin_left_; }\n  float MarginTop() const { return margin_top_; }\n  float MarginRight() const { return margin_right_; }\n  float MarginBottom() const { return margin_bottom_; }\n\n  bool operator==(const BoxData& rhs) const {\n    return left_ == rhs.left_ && width_ == rhs.width_ && top_ == rhs.top_ &&\n           height_ == rhs.height_ && padding_top_ == rhs.padding_top_ &&\n           padding_left_ == rhs.padding_left_ &&\n           padding_right_ == rhs.padding_right_ &&\n           padding_bottom_ == rhs.padding_bottom_ &&\n           margin_top_ == rhs.margin_top_ && margin_left_ == rhs.margin_left_ &&\n           margin_right_ == rhs.margin_right_ &&\n           margin_bottom_ == rhs.margin_bottom_;\n  }\n  bool operator!=(const BoxData& rhs) const { return !(*this == rhs); }\n\n private:\n  float left_{0.f};\n  float top_{0.f};\n  float width_{0.f};\n  float height_{0.f};\n  float padding_top_{0.f};\n  float padding_left_{0.f};\n  float padding_right_{0.f};\n  float padding_bottom_{0.f};\n  float margin_top_{0.f};\n  float margin_left_{0.f};\n  float margin_right_{0.f};\n  float margin_bottom_{0.f};\n};\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_BOX_DATA_H_\n"
  },
  {
    "path": "clay/gfx/style/build.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\ngfx_style_sources = [\n  \"blend_mode.h\",\n  \"borders_data.cc\",\n  \"borders_data.h\",\n  \"box_data.h\",\n  \"color.cc\",\n  \"color.h\",\n  \"color_filter.cc\",\n  \"color_filter.h\",\n  \"color_gen.inl\",\n  \"color_source.cc\",\n  \"color_source.h\",\n  \"image_filter.cc\",\n  \"image_filter.h\",\n  \"length.h\",\n  \"mask_filter.cc\",\n  \"mask_filter.h\",\n  \"outline_data.cc\",\n  \"outline_data.h\",\n  \"path_effect.cc\",\n  \"path_effect.h\",\n  \"runtime_effect.cc\",\n  \"runtime_effect.h\",\n  \"sampling_options.h\",\n  \"shadow.h\",\n  \"tile_mode.h\",\n]\n\nif (!enable_skity) {\n  gfx_style_sources += [\n    \"vertices.cc\",\n    \"vertices.h\",\n  ]\n}\n"
  },
  {
    "path": "clay/gfx/style/color.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/color.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <vector>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/style/color_gen.inl\"\n\nnamespace clay {\n\nnamespace {\n\nstatic const uint32_t lightened_black = 0xFF545454;\nstatic const uint32_t darkened_white = 0xFFABABAB;\n\ntemplate <class T>\nT clamp(T value, T min_val, T max_val) {\n  FML_DCHECK(min_val <= max_val);\n  if (value < min_val) {\n    return min_val;\n  }\n  return value > max_val ? max_val : value;\n}\n\nuint32_t lerp(float a, float b, float t) {\n  // t with negative values and values greater than 1.0 are invalid.\n  // FML_DCHECK(t >= 0.0 && t <= 1.0);\n  // a * (1-t) + b * t\n  return a + (b - a) * t;\n}\n\n// ------- Copy from Lynx Start ------- //\ntemplate <typename T>\nuint8_t ClampCssByte(T i) {  // Clamp to integer 0 .. 255.\n  i = round(i);\n  return i < 0 ? 0 : i > 255 ? 255 : i;\n}\n\ntemplate <typename T>\nfloat ClampCssFloat(T f) {  // Clamp to float 0.0 .. 1.0.\n  return f < 0 ? 0 : f > 1 ? 1 : f;\n}\nbool ParseCssInt(const std::string& str,\n                 uint8_t* output) {  // int or percentage.\n  int64_t i = 0;\n  if (str.length() && str[str.length() - 1] == '%') {\n    if (UNLIKELY(\n            !lynx::base::StringToInt(str.substr(0, str.length() - 1), i, 10))) {\n      return false;\n    }\n    *output = ClampCssByte(i / 100.0f * 255.0f);\n    return true;\n  } else {\n    if (UNLIKELY(\n            !lynx::base::StringToInt(str.substr(0, str.length()), i, 10))) {\n      return false;\n    }\n    *output = ClampCssByte(i);\n    return true;\n  }\n}\n\nbool ParseCssFloat(const std::string& str,\n                   float* output) {  // float or percentage.\n  float d = 0;\n  if (str.length() && str[str.length() - 1] == '%') {\n    if (UNLIKELY(\n            !lynx::base::StringToFloat(str.substr(0, str.length() - 1), d))) {\n      return false;\n    }\n    *output = ClampCssFloat(d / 100.0f);\n    return true;\n  } else {\n    if (UNLIKELY(!lynx::base::StringToFloat(str.substr(0, str.length()), d))) {\n      return false;\n    }\n    *output = ClampCssFloat(d);\n    return true;\n  }\n}\n\nfloat CssHueToRgb(float m1, float m2, float h) {\n  if (h < 0.0f) {\n    h += 1.0f;\n  } else if (h > 1.0f) {\n    h -= 1.0f;\n  }\n\n  if (h * 6.0f < 1.0f) {\n    return m1 + (m2 - m1) * h * 6.0f;\n  }\n  if (h * 2.0f < 1.0f) {\n    return m2;\n  }\n  if (h * 3.0f < 2.0f) {\n    return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0f;\n  }\n  return m1;\n}\n\n// ------- Copy from Lynx End ------- //\n\n}  // namespace\n\n// static\nColor Color::Lerp(Color a, Color b, float t) {\n  return ARGBColor(clamp(lerp(a.Alpha(), b.Alpha(), t), 0u, 255u),\n                   clamp(lerp(a.Red(), b.Red(), t), 0u, 255u),\n                   clamp(lerp(a.Green(), b.Green(), t), 0u, 255u),\n                   clamp(lerp(a.Blue(), b.Blue(), t), 0u, 255u));\n}\n\n// static\nColor Color::AlphaBlend(Color foreground, Color background) {\n  int alpha = foreground.Alpha();\n  if (alpha == 0x00) {\n    return background;\n  }\n  int invAlpha = 0xFF - alpha;\n  int backAlpha = background.Alpha();\n  if (backAlpha == 0xFF) {\n    return ARGBColor(\n        0xFF, (alpha * foreground.Red() + invAlpha * background.Red()) / 0xFF,\n        (alpha * foreground.Green() + invAlpha * background.Green()) / 0xFF,\n        (alpha * foreground.Blue() + invAlpha * background.Blue()) / 0xFF);\n  } else {\n    backAlpha = (backAlpha * invAlpha) / 0xFF;\n    int outAlpha = alpha + backAlpha;\n    FML_DCHECK(outAlpha != 0x00);\n    return ARGBColor(\n        outAlpha,\n        (foreground.Red() * alpha + background.Red() * backAlpha) / outAlpha,\n        (foreground.Green() * alpha + background.Green() * backAlpha) /\n            outAlpha,\n        (foreground.Blue() * alpha + background.Blue() * backAlpha) / outAlpha);\n  }\n}\n\n// static\nbool Color::Parse(const std::string& color_str, Color* color) {\n  if (color_str.empty()) {\n    return false;\n  }\n\n  // Remove all whitespace, not compliant, but should just be more accepting.\n  std::string str = lynx::base::RemoveSpaces(color_str);\n\n  // Convert to lowercase.\n  std::transform(str.begin(), str.end(), str.begin(), ::tolower);\n\n  // #abc and #abc123 syntax.\n  if (str.length() && str[0] == '#') {\n    if (str.length() == 4) {\n      int64_t iv = 0;\n      if (UNLIKELY(!lynx::base::StringToInt(str.substr(1), iv, 16))) {\n        return false;\n      }\n      if (!(iv >= 0 && iv <= 0xfff)) {\n        return false;\n      } else {\n        *color = RGBOColor(((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8),\n                           (iv & 0xf0) | ((iv & 0xf0) >> 4),\n                           (iv & 0xf) | ((iv & 0xf) << 4), 1);\n        return true;\n      }\n    } else if (str.length() == 7) {\n      int64_t iv = 0;\n      if (UNLIKELY(!lynx::base::StringToInt(str.substr(1), iv, 16))) {\n        return false;\n      }\n      if (!(iv >= 0 && iv <= 0xffffff)) {\n        return false;  // Covers NaN.\n      } else {\n        *color =\n            RGBOColor((iv & 0xff0000) >> 16, (iv & 0xff00) >> 8, iv & 0xff, 1);\n        return true;\n      }\n    } else if (str.length() == 9) {\n      int64_t iv = 0;\n      if (UNLIKELY(!lynx::base::StringToInt(str.substr(1), iv, 16))) {\n        return false;\n      }\n      if (!(iv >= 0 && iv <= 0xffffffff)) {\n        return false;  // Covers NaN.\n      } else {\n        *color = RGBOColor((iv & 0xff000000) >> 24, (iv & 0xff0000) >> 16,\n                           (iv & 0xff00) >> 8, (iv & 0xff) / 255.0);\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  size_t op = str.find_first_of('('), ep = str.find_first_of(')');\n  if (op != std::string::npos && ep + 1 == str.length()) {\n    const std::string fname = str.substr(0, op);\n    const std::vector<std::string> params =\n        lynx::base::SplitString<std::string, std::string>(\n            str.substr(op + 1, ep - (op + 1)), \",\");\n\n    float alpha = 1.0f;\n\n    if (fname == \"rgba\" || fname == \"rgb\") {\n      if (fname == \"rgba\") {\n        if (params.size() != 4) {\n          return false;\n        }\n        if (UNLIKELY(!ParseCssFloat(params.back(), &alpha))) {\n          return false;\n        }\n      } else {\n        if (params.size() != 3) {\n          return false;\n        }\n      }\n      uint8_t r = 0;\n      uint8_t g = 0;\n      uint8_t b = 0;\n      if (UNLIKELY(!ParseCssInt(params[0], &r) || !ParseCssInt(params[1], &g) ||\n                   !ParseCssInt(params[2], &b))) {\n        return false;\n      }\n      *color = RGBOColor(r, g, b, alpha);\n      return true;\n\n    } else if (fname == \"hsla\" || fname == \"hsl\") {\n      if (fname == \"hsla\") {\n        if (params.size() != 4) {\n          return false;\n        }\n        if (UNLIKELY(!ParseCssFloat(params[3], &alpha))) {\n          return false;\n        }\n      } else {\n        if (params.size() != 3) {\n          return false;\n        }\n      }\n      float h = 0.f;\n      if (UNLIKELY(!lynx::base::StringToFloat(params[0], h))) {\n        return false;\n      }\n      h /= 360.0f;\n      while (h < 0.0f) {\n        h++;\n      }\n      while (h > 1.0f) {\n        h--;\n      }\n\n      // NOTE(deanm): According to the CSS spec s/l should only be\n      // percentages, but we don't bother and let float or percentage.\n      float s = 0.f;\n      float l = 0.f;\n      if (UNLIKELY(!ParseCssFloat(params[1], &s) ||\n                   !ParseCssFloat(params[2], &l))) {\n        return false;\n      }\n\n      float m2 = l <= 0.5f ? l * (s + 1.0f) : l + s - l * s;\n      float m1 = l * 2.0f - m2;\n\n      *color = RGBOColor(\n          ClampCssByte(CssHueToRgb(m1, m2, h + 1.0f / 3.0f) * 255.0f),\n          ClampCssByte(CssHueToRgb(m1, m2, h) * 255.0f),\n          ClampCssByte(CssHueToRgb(m1, m2, h - 1.0f / 3.0f) * 255.0f), alpha);\n      return true;\n    }\n  }\n\n  auto tk = ColorHash::find(str.c_str(), str.size());\n  if (tk) {\n    *color = tk->color;\n    return true;\n  }\n  return false;\n}\n\nvoid Color::GetARGB(float* a, float* r, float* g, float* b) const {\n  *a = Alpha() / 255.0f;\n  *r = Red() / 255.0f;\n  *g = Green() / 255.0f;\n  *b = Blue() / 255.0f;\n}\n\nColor Color::Light() const {\n  // Hardcode this common case for speed.\n  if (argb == 0xFF000000) {\n    return Color(lightened_black);\n  }\n\n  const float scaleFactor = nextafterf(256.0f, 0.0f);\n\n  float r = 0.f;\n  float g = 0.f;\n  float b = 0.f;\n  float a = 0.f;\n  GetARGB(&a, &r, &g, &b);\n\n  float v = std::max(r, std::max(g, b));\n\n  if (v == 0.0f) {\n    // Lightened black with alpha.\n    return Color::ARGBColor(Alpha(), 0x54, 0x54, 0x54);\n  }\n\n  float multiplier = std::min(1.0f, v + 0.33f) / v;\n\n  return Color::ARGBColor(Alpha(),\n                          static_cast<int>(multiplier * r * scaleFactor),\n                          static_cast<int>(multiplier * g * scaleFactor),\n                          static_cast<int>(multiplier * b * scaleFactor));\n}\n\nColor Color::Dark() const {\n  // Hardcode this common case for speed.\n  if (argb == 0xFFFFFFFF) {\n    return Color(darkened_white);\n  }\n\n  const float scaleFactor = nextafterf(256.0f, 0.0f);\n\n  float r = 0.f;\n  float g = 0.f;\n  float b = 0.f;\n  float a = 0.f;\n  GetARGB(&a, &r, &g, &b);\n\n  float v = std::max(r, std::max(g, b));\n  float multiplier = std::max(0.0f, (v - 0.33f) / v);\n\n  return Color::ARGBColor(Alpha(),\n                          static_cast<int>(multiplier * r * scaleFactor),\n                          static_cast<int>(multiplier * g * scaleFactor),\n                          static_cast<int>(multiplier * b * scaleFactor));\n}\n\n#ifndef NDEBUG\nstd::string Color::ToString() const {\n  std::stringstream ss;\n  ss << \"argb(\" << Alpha() << \",\" << Red() << \",\" << Green() << \",\" << Blue()\n     << \")\";\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/color.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_COLOR_H_\n#define CLAY_GFX_STYLE_COLOR_H_\n\n#include <cmath>\n#include <string>\n\n#include \"clay/gfx/rendering_backend.h\"\nnamespace clay {\nstruct Color {\n public:\n  constexpr Color() : argb(0xFF000000) {}\n  constexpr Color(uint32_t argb) : argb(argb) {}\n\n  static uint8_t toAlpha(float opacity) { return toC(opacity); }\n  static constexpr float toOpacity(uint8_t alpha) { return toF(alpha); }\n\n  static constexpr Color ARGBColor(uint8_t a, uint8_t r, uint8_t g, uint8_t b) {\n    return Color(((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) |\n                 ((b & 0xFF) << 0));\n  }\n  static constexpr Color RGBOColor(uint8_t r, uint8_t g, uint8_t b,\n                                   float opacity) {\n    return Color((static_cast<uint8_t>(opacity * 0xff) & 0xFF) << 24 |\n                 ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | ((b & 0xFF) << 0));\n  }\n\n  static constexpr Color ColorSetA(Color color, uint8_t a) {\n    return Color((a & 0xFF) << 24 | (color.argb & 0x00FFFFFF));\n  }\n\n  // clang-format off\n  static constexpr Color kTransparent()        {return 0x00000000;}\n  static constexpr Color kBlack()              {return 0xFF000000;}\n  static constexpr Color kWhite()              {return 0xFFFFFFFF;}\n  static constexpr Color kRed()                {return 0xFFFF0000;}\n  static constexpr Color kGreen()              {return 0xFF00FF00;}\n  static constexpr Color kBlue()               {return 0xFF0000FF;}\n  static constexpr Color kCyan()               {return 0xFF00FFFF;}\n  static constexpr Color kMagenta()            {return 0xFFFF00FF;}\n  static constexpr Color kYellow()             {return 0xFFFFFF00;}\n  static constexpr Color kDarkGrey()           {return 0xFF3F3F3F;}\n  static constexpr Color kMidGrey()            {return 0xFF808080;}\n  static constexpr Color kLightGrey()          {return 0xFFC0C0C0;}\n  // clang-format on\n\n  static Color Lerp(Color a, Color b, float t);\n  static Color AlphaBlend(Color foreground, Color background);\n\n  static bool Parse(const std::string& color_str, Color* color);\n\n  uint32_t argb;\n\n  bool Opaque() const { return Alpha() == 0xFF; }\n\n  int Alpha() const { return argb >> 24; }\n  float Opacity() const { return AlphaF(); }\n  int Red() const { return (argb >> 16) & 0xFF; }\n  int Green() const { return (argb >> 8) & 0xFF; }\n  int Blue() const { return argb & 0xFF; }\n\n  float AlphaF() const { return toF(Alpha()); }\n  float RedF() const { return toF(Red()); }\n  float GreenF() const { return toF(Green()); }\n  float BlueF() const { return toF(Blue()); }\n\n  uint32_t premultipliedArgb() const {\n    if (Opaque()) {\n      return argb;\n    }\n    float f = AlphaF();\n    return (argb & 0xFF000000) |     //\n           toC(RedF() * f) << 16 |   //\n           toC(GreenF() * f) << 8 |  //\n           toC(BlueF() * f);\n  }\n\n  Color withAlpha(uint8_t alpha) const {  //\n    return (argb & 0x00FFFFFF) | (alpha << 24);\n  }\n  Color withRed(uint8_t red) const {  //\n    return (argb & 0xFF00FFFF) | (red << 16);\n  }\n  Color withGreen(uint8_t green) const {  //\n    return (argb & 0xFFFF00FF) | (green << 8);\n  }\n  Color withBlue(uint8_t blue) const {  //\n    return (argb & 0xFFFFFF00) | (blue << 0);\n  }\n\n  void GetARGB(float* a, float* r, float* g, float* b) const;\n  Color Light() const;\n  Color Dark() const;\n\n  uint32_t Value() const { return argb; }\n\n  Color modulateOpacity(float opacity) const {\n    return opacity <= 0   ? withAlpha(0)\n           : opacity >= 1 ? *this\n                          : withAlpha(round(Alpha() * opacity));\n  }\n\n  operator uint32_t() const { return argb; }\n  bool operator==(Color const& other) const { return argb == other.argb; }\n  bool operator!=(Color const& other) const { return argb != other.argb; }\n  bool operator==(uint32_t const& other) const { return argb == other; }\n  bool operator!=(uint32_t const& other) const { return argb != other; }\n#ifndef NDEBUG\n  std::string ToString() const;\n#endif\n\n private:\n  static constexpr float toF(uint8_t comp) { return comp * (1.0 / 255); }\n  static uint8_t toC(float fComp) { return round(fComp * 255); }\n};\n\n#ifndef ENABLE_SKITY\ninline SkColor ToSk(const Color& color) {\n  return SkColorSetARGB(color.Alpha(), color.Red(), color.Green(),\n                        color.Blue());\n}\n#else\ninline skity::Color ToSk(const Color& color) {\n  return skity::ColorSetARGB(color.Alpha(), color.Red(), color.Green(),\n                             color.Blue());\n}\n#endif  // ENABLE_SKITY\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_COLOR_H_\n"
  },
  {
    "path": "clay/gfx/style/color.tmpl",
    "content": "%language=C++\n%define class-name ColorHash\n%define lookup-function-name find\n%define string-pool-name StringPool\n%struct-type\n%global-table\n%readonly-tables\n%pic\n\n%{\n%}\n\nstruct __attribute__((packed)) TokenValue {\n  short name;\n  unsigned int color;\n};\n\nstatic constexpr unsigned int RGBColor(uint8_t r, uint8_t g, uint8_t b) {\n  return 0xFF000000 | (r << 16) | (g << 8) | b;\n}\n\n%%\ntransparent, 0\naliceblue, RGBColor(240, 248, 255)\nantiquewhite, RGBColor(250, 235, 215)\naqua, RGBColor(0, 255, 255)\naquamarine, RGBColor(127, 255, 212)\nazure, RGBColor(240, 255, 255)\nbeige, RGBColor(245, 245, 220)\nbisque, RGBColor(255, 228, 196)\nblack, RGBColor(0, 0, 0)\nblanchedalmond, RGBColor(255, 235, 205)\nblue, RGBColor(0, 0, 255)\nblueviolet, RGBColor(138, 43, 226)\nbrown, RGBColor(165, 42, 42)\nburlywood, RGBColor(222, 184, 135)\ncadetblue, RGBColor(95, 158, 160)\nchartreuse, RGBColor(127, 255, 0)\nchocolate, RGBColor(210, 105, 30)\ncoral, RGBColor(255, 127, 80)\ncornflowerblue, RGBColor(100, 149, 237)\ncornsilk, RGBColor(255, 248, 220)\ncrimson, RGBColor(220, 20, 60)\ncyan, RGBColor(0, 255, 255)\ndarkblue, RGBColor(0, 0, 139)\ndarkcyan, RGBColor(0, 139, 139)\ndarkgoldenrod, RGBColor(184, 134, 11)\ndarkgray, RGBColor(169, 169, 169)\ndarkgreen, RGBColor(0, 100, 0)\ndarkgrey, RGBColor(169, 169, 169)\ndarkkhaki, RGBColor(189, 183, 107)\ndarkmagenta, RGBColor(139, 0, 139)\ndarkolivegreen, RGBColor(85, 107, 47)\ndarkorange, RGBColor(255, 140, 0)\ndarkorchid, RGBColor(153, 50, 204)\ndarkred, RGBColor(139, 0, 0)\ndarksalmon, RGBColor(233, 150, 122)\ndarkseagreen, RGBColor(143, 188, 143)\ndarkslateblue, RGBColor(72, 61, 139)\ndarkslategray, RGBColor(47, 79, 79)\ndarkslategrey, RGBColor(47, 79, 79)\ndarkturquoise, RGBColor(0, 206, 209)\ndarkviolet, RGBColor(148, 0, 211)\ndeeppink, RGBColor(255, 20, 147)\ndeepskyblue, RGBColor(0, 191, 255)\ndimgray, RGBColor(105, 105, 105)\ndimgrey, RGBColor(105, 105, 105)\ndodgerblue, RGBColor(30, 144, 255)\nfirebrick, RGBColor(178, 34, 34)\nfloralwhite, RGBColor(255, 250, 240)\nforestgreen, RGBColor(34, 139, 34)\nfuchsia, RGBColor(255, 0, 255)\ngainsboro, RGBColor(220, 220, 220)\nghostwhite, RGBColor(248, 248, 255)\ngold, RGBColor(255, 215, 0)\ngoldenrod, RGBColor(218, 165, 32)\ngray, RGBColor(128, 128, 128)\ngreen, RGBColor(0, 128, 0)\ngreenyellow, RGBColor(173, 255, 47)\ngrey, RGBColor(128, 128, 128)\nhoneydew, RGBColor(240, 255, 240)\nhotpink, RGBColor(255, 105, 180)\nindianred, RGBColor(205, 92, 92)\nindigo, RGBColor(75, 0, 130)\nivory, RGBColor(255, 255, 240)\nkhaki, RGBColor(240, 230, 140)\nlavender, RGBColor(230, 230, 250)\nlavenderblush, RGBColor(255, 240, 245)\nlawngreen, RGBColor(124, 252, 0)\nlemonchiffon, RGBColor(255, 250, 205)\nlightblue, RGBColor(173, 216, 230)\nlightcoral, RGBColor(240, 128, 128)\nlightcyan, RGBColor(224, 255, 255)\nlightgoldenrodyellow, RGBColor(250, 250, 210)\nlightgray, RGBColor(211, 211, 211)\nlightgreen, RGBColor(144, 238, 144)\nlightgrey, RGBColor(211, 211, 211)\nlightpink, RGBColor(255, 182, 193)\nlightsalmon, RGBColor(255, 160, 122)\nlightseagreen, RGBColor(32, 178, 170)\nlightskyblue, RGBColor(135, 206, 250)\nlightslategray, RGBColor(119, 136, 153)\nlightslategrey, RGBColor(119, 136, 153)\nlightsteelblue, RGBColor(176, 196, 222)\nlightyellow, RGBColor(255, 255, 224)\nlime, RGBColor(0, 255, 0)\nlimegreen, RGBColor(50, 205, 50)\nlinen, RGBColor(250, 240, 230)\nmagenta, RGBColor(255, 0, 255)\nmaroon, RGBColor(128, 0, 0)\nmediumaquamarine, RGBColor(102, 205, 170)\nmediumblue, RGBColor(0, 0, 205)\nmediumorchid, RGBColor(186, 85, 211)\nmediumpurple, RGBColor(147, 112, 219)\nmediumseagreen, RGBColor(60, 179, 113)\nmediumslateblue, RGBColor(123, 104, 238)\nmediumspringgreen, RGBColor(0, 250, 154)\nmediumturquoise, RGBColor(72, 209, 204)\nmediumvioletred, RGBColor(199, 21, 133)\nmidnightblue, RGBColor(25, 25, 112)\nmintcream, RGBColor(245, 255, 250)\nmistyrose, RGBColor(255, 228, 225)\nmoccasin, RGBColor(255, 228, 181)\nnavajowhite, RGBColor(255, 222, 173)\nnavy, RGBColor(0, 0, 128)\noldlace, RGBColor(253, 245, 230)\nolive, RGBColor(128, 128, 0)\nolivedrab, RGBColor(107, 142, 35)\norange, RGBColor(255, 165, 0)\norangered, RGBColor(255, 69, 0)\norchid, RGBColor(218, 112, 214)\npalegoldenrod, RGBColor(238, 232, 170)\npalegreen, RGBColor(152, 251, 152)\npaleturquoise, RGBColor(175, 238, 238)\npalevioletred, RGBColor(219, 112, 147)\npapayawhip, RGBColor(255, 239, 213)\npeachpuff, RGBColor(255, 218, 185)\nperu, RGBColor(205, 133, 63)\npink, RGBColor(255, 192, 203)\nplum, RGBColor(221, 160, 221)\npowderblue, RGBColor(176, 224, 230)\npurple, RGBColor(128, 0, 128)\nred, RGBColor(255, 0, 0)\nrosybrown, RGBColor(188, 143, 143)\nroyalblue, RGBColor(65, 105, 225)\nsaddlebrown, RGBColor(139, 69, 19)\nsalmon, RGBColor(250, 128, 114)\nsandybrown, RGBColor(244, 164, 96)\nseagreen, RGBColor(46, 139, 87)\nseashell, RGBColor(255, 245, 238)\nsienna, RGBColor(160, 82, 45)\nsilver, RGBColor(192, 192, 192)\nskyblue, RGBColor(135, 206, 235)\nslateblue, RGBColor(106, 90, 205)\nslategray, RGBColor(112, 128, 144)\nslategrey, RGBColor(112, 128, 144)\nsnow, RGBColor(255, 250, 250)\nspringgreen, RGBColor(0, 255, 127)\nsteelblue, RGBColor(70, 130, 180)\ntan, RGBColor(210, 180, 140)\nteal, RGBColor(0, 128, 128)\nthistle, RGBColor(216, 191, 216)\ntomato, RGBColor(255, 99, 71)\nturquoise, RGBColor(64, 224, 208)\nviolet, RGBColor(238, 130, 238)\nwheat, RGBColor(245, 222, 179)\nwhite, RGBColor(255, 255, 255)\nwhitesmoke, RGBColor(245, 245, 245)\nyellow, RGBColor(255, 255, 0)\nyellowgreen, RGBColor(154, 205, 50)\n%%\n"
  },
  {
    "path": "clay/gfx/style/color_filter.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/color_filter.h\"\n\nnamespace clay {\nstd::shared_ptr<ColorFilter> ColorFilter::MakeBlend(Color color,\n                                                    BlendMode mode) {\n  return std::make_shared<BlendColorFilter>(color, mode);\n}\n\nstd::shared_ptr<ColorFilter> ColorFilter::MakeMatrix(const float matrix[20]) {\n  return std::make_shared<MatrixColorFilter>(matrix);\n}\n\nstd::shared_ptr<ColorFilter> ColorFilter::MakeSrgbToLinearGamma() {\n  return SrgbToLinearGammaColorFilter::instance;\n}\n\nstd::shared_ptr<ColorFilter> ColorFilter::MakeLinearToSrgbGamma() {\n  return LinearToSrgbGammaColorFilter::instance;\n}\n\nconst std::shared_ptr<SrgbToLinearGammaColorFilter>\n    SrgbToLinearGammaColorFilter::instance =\n        std::make_shared<SrgbToLinearGammaColorFilter>();\nconst GrColorFilterPtr SrgbToLinearGammaColorFilter::sk_filter_ =\n    GrColorFilters::SRGBToLinearGamma();\n\nconst std::shared_ptr<LinearToSrgbGammaColorFilter>\n    LinearToSrgbGammaColorFilter::instance =\n        std::make_shared<LinearToSrgbGammaColorFilter>();\nconst GrColorFilterPtr LinearToSrgbGammaColorFilter::sk_filter_ =\n    GrColorFilters::LinearToSRGBGamma();\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/color_filter.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_COLOR_FILTER_H_\n#define CLAY_GFX_STYLE_COLOR_FILTER_H_\n\n#include <cstring>\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/attributes.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/blend_mode.h\"\n#include \"clay/gfx/style/color.h\"\nnamespace clay {\n\nclass BlendColorFilter;\nclass MatrixColorFilter;\n\nenum class ColorFilterType {\n  kBlend,\n  kMatrix,\n  kSrgbToLinearGamma,\n  kLinearToSrgbGamma,\n  kUnknown\n};\n\nclass ColorFilter\n    : public Attribute<ColorFilter, GrColorFilter, ColorFilterType> {\n public:\n  static std::shared_ptr<ColorFilter> MakeBlend(Color color, BlendMode mode);\n\n  static std::shared_ptr<ColorFilter> MakeMatrix(const float matrix[20]);\n\n  static std::shared_ptr<ColorFilter> MakeSrgbToLinearGamma();\n\n  static std::shared_ptr<ColorFilter> MakeLinearToSrgbGamma();\n\n  // Return a boolean indicating whether the color filtering operation will\n  // modify transparent black. This is typically used to determine if applying\n  // the ColorFilter to a temporary saveLayer buffer will turn the surrounding\n  // pixels non-transparent and therefore expand the bounds.\n  virtual bool modifies_transparent_black() const = 0;\n\n  // Return a boolean indicating whether the color filtering operation can\n  // be applied either before or after modulating the pixels with an opacity\n  // value without changing the operation.\n  virtual bool can_commute_with_opacity() const { return false; }\n\n  // Return a BlendColorFilter pointer to this object iff it is a Blend\n  // type of ColorFilter, otherwise return nullptr.\n  virtual const BlendColorFilter* asBlend() const { return nullptr; }\n\n  // Return a MatrixColorFilter pointer to this object iff it is a Matrix\n  // type of ColorFilter, otherwise return nullptr.\n  virtual const MatrixColorFilter* asMatrix() const { return nullptr; }\n\n  // asSrgb<->Linear and asUnknown are not needed because they\n  // have no properties to query. Their type fully specifies their\n  // operation or can be accessed via the common gr_object() method.\n};\n\n// The Blend type of ColorFilter which specifies modifying the\n// colors as if the color specified in the Blend filter is the\n// source color and the color drawn by the rendering operation\n// is the destination color. The mode parameter of the Blend\n// filter is then used to combine those colors.\nclass BlendColorFilter final : public ColorFilter {\n public:\n  BlendColorFilter(Color color, BlendMode mode) : color_(color), mode_(mode) {}\n  BlendColorFilter(const BlendColorFilter& filter)\n      : BlendColorFilter(filter.color_, filter.mode_) {}\n  explicit BlendColorFilter(const BlendColorFilter* filter)\n      : BlendColorFilter(filter->color_, filter->mode_) {}\n\n  ColorFilterType type() const override { return ColorFilterType::kBlend; }\n  size_t size() const override { return sizeof(*this); }\n  bool modifies_transparent_black() const override {\n    // Look at blend and color to make a faster determination?\n    auto filter = gr_object();\n    return filter && COLOR_FILTER_FILTER_COLOR(filter, Color::kTransparent()) !=\n                         ToSk(Color::kTransparent());\n  }\n\n  std::shared_ptr<ColorFilter> shared() const override {\n    return std::make_shared<BlendColorFilter>(this);\n  }\n\n  GrColorFilterPtr gr_object() const override {\n    return GrColorFilters::Blend(color_, ToSk(mode_));\n  }\n\n  const BlendColorFilter* asBlend() const override { return this; }\n\n  Color color() const { return color_; }\n  BlendMode mode() const { return mode_; }\n\n protected:\n  bool equals_(ColorFilter const& other) const override {\n    FML_DCHECK(other.type() == ColorFilterType::kBlend);\n    auto that = static_cast<BlendColorFilter const*>(&other);\n    return color_ == that->color_ && mode_ == that->mode_;\n  }\n\n private:\n  Color color_;\n  BlendMode mode_;\n};\n\n// The Matrix type of ColorFilter which runs every pixel drawn by\n// the rendering operation [iR,iG,iB,iA] through a vector/matrix\n// multiplication, as in:\n//\n//  [ oR ]   [ m[ 0] m[ 1] m[ 2] m[ 3] m[ 4] ]   [ iR ]\n//  [ oG ]   [ m[ 5] m[ 6] m[ 7] m[ 8] m[ 9] ]   [ iG ]\n//  [ oB ] = [ m[10] m[11] m[12] m[13] m[14] ] x [ iB ]\n//  [ oA ]   [ m[15] m[16] m[17] m[18] m[19] ]   [ iA ]\n//                                               [  1 ]\n//\n// The resulting color [oR,oG,oB,oA] is then clamped to the range of\n// valid pixel components before storing in the output.\nclass MatrixColorFilter final : public ColorFilter {\n public:\n  explicit MatrixColorFilter(const float matrix[20]) {\n    memcpy(matrix_, matrix, sizeof(matrix_));\n  }\n  MatrixColorFilter(const MatrixColorFilter& filter)\n      : MatrixColorFilter(filter.matrix_) {}\n  explicit MatrixColorFilter(const MatrixColorFilter* filter)\n      : MatrixColorFilter(filter->matrix_) {}\n\n  ColorFilterType type() const override { return ColorFilterType::kMatrix; }\n  size_t size() const override { return sizeof(*this); }\n  bool modifies_transparent_black() const override {\n    // Look at the matrix to make a faster determination?\n    // Basically, are the translation components all 0?\n    auto filter = gr_object();\n    return filter && COLOR_FILTER_FILTER_COLOR(filter, Color::kTransparent()) !=\n                         ToSk(Color::kTransparent());\n  }\n\n  bool can_commute_with_opacity() const override {\n    return matrix_[3] == 0 && matrix_[8] == 0 && matrix_[13] == 0 &&\n           matrix_[15] == 0 && matrix_[16] == 0 && matrix_[17] == 0 &&\n           (matrix_[18] >= 0.0 && matrix_[18] <= 1.0) && matrix_[19] == 0;\n  }\n\n  std::shared_ptr<ColorFilter> shared() const override {\n    return std::make_shared<MatrixColorFilter>(this);\n  }\n\n  GrColorFilterPtr gr_object() const override {\n    return GrColorFilters::Matrix(matrix_);\n  }\n\n  const MatrixColorFilter* asMatrix() const override { return this; }\n\n  const float& operator[](int index) const { return matrix_[index]; }\n  void get_matrix(float matrix[20]) const {\n    memcpy(matrix, matrix_, sizeof(matrix_));\n  }\n\n protected:\n  bool equals_(const ColorFilter& other) const override {\n    FML_DCHECK(other.type() == ColorFilterType::kMatrix);\n    auto that = static_cast<MatrixColorFilter const*>(&other);\n    return memcmp(matrix_, that->matrix_, sizeof(matrix_)) == 0;\n  }\n\n private:\n  float matrix_[20];\n};\n\n// The SrgbToLinear type of ColorFilter that applies the inverse of the sRGB\n// gamma curve to the rendered pixels.\nclass SrgbToLinearGammaColorFilter final : public ColorFilter {\n public:\n  static const std::shared_ptr<SrgbToLinearGammaColorFilter> instance;\n\n  SrgbToLinearGammaColorFilter() = default;\n  SrgbToLinearGammaColorFilter(const SrgbToLinearGammaColorFilter& filter)\n      : SrgbToLinearGammaColorFilter() {}\n  explicit SrgbToLinearGammaColorFilter(\n      const SrgbToLinearGammaColorFilter* filter)\n      : SrgbToLinearGammaColorFilter() {}\n\n  ColorFilterType type() const override {\n    return ColorFilterType::kSrgbToLinearGamma;\n  }\n  size_t size() const override { return sizeof(*this); }\n  bool modifies_transparent_black() const override { return false; }\n  bool can_commute_with_opacity() const override { return true; }\n\n  std::shared_ptr<ColorFilter> shared() const override { return instance; }\n\n  GrColorFilterPtr gr_object() const override { return sk_filter_; }\n\n protected:\n  bool equals_(const ColorFilter& other) const override {\n    FML_DCHECK(other.type() == ColorFilterType::kSrgbToLinearGamma);\n    return true;\n  }\n\n private:\n  static const GrColorFilterPtr sk_filter_;\n  friend class ColorFilter;\n};\n\n// The LinearToSrgb type of ColorFilter that applies the sRGB gamma curve\n// to the rendered pixels.\nclass LinearToSrgbGammaColorFilter final : public ColorFilter {\n public:\n  static const std::shared_ptr<LinearToSrgbGammaColorFilter> instance;\n\n  LinearToSrgbGammaColorFilter() {}\n  LinearToSrgbGammaColorFilter(const LinearToSrgbGammaColorFilter& filter)\n      : LinearToSrgbGammaColorFilter() {}\n  explicit LinearToSrgbGammaColorFilter(\n      const LinearToSrgbGammaColorFilter* filter)\n      : LinearToSrgbGammaColorFilter() {}\n\n  ColorFilterType type() const override {\n    return ColorFilterType::kLinearToSrgbGamma;\n  }\n  size_t size() const override { return sizeof(*this); }\n  bool modifies_transparent_black() const override { return false; }\n  bool can_commute_with_opacity() const override { return true; }\n\n  std::shared_ptr<ColorFilter> shared() const override { return instance; }\n\n  GrColorFilterPtr gr_object() const override { return sk_filter_; }\n\n protected:\n  bool equals_(const ColorFilter& other) const override {\n    FML_DCHECK(other.type() == ColorFilterType::kLinearToSrgbGamma);\n    return true;\n  }\n\n private:\n  static const GrColorFilterPtr sk_filter_;\n  friend class ColorFilter;\n};\n\nclass UnknownColorFilter final : public ColorFilter {\n public:\n  explicit UnknownColorFilter(GrColorFilterPtr sk_filter)\n      : sk_filter_(std::move(sk_filter)) {}\n  explicit UnknownColorFilter(const UnknownColorFilter& filter)\n      : UnknownColorFilter(filter.sk_filter_) {}\n  explicit UnknownColorFilter(const UnknownColorFilter* filter)\n      : UnknownColorFilter(filter->sk_filter_) {}\n\n  ColorFilterType type() const override { return ColorFilterType::kUnknown; }\n  size_t size() const override { return sizeof(*this); }\n  bool modifies_transparent_black() const override {\n    return sk_filter_ &&\n           COLOR_FILTER_FILTER_COLOR(sk_filter_, Color::kTransparent()) !=\n               ToSk(Color::kTransparent());\n  }\n\n  std::shared_ptr<ColorFilter> shared() const override {\n    return std::make_shared<UnknownColorFilter>(this);\n  }\n\n  GrColorFilterPtr gr_object() const override { return sk_filter_; }\n\n  virtual ~UnknownColorFilter() = default;\n\n protected:\n  bool equals_(const ColorFilter& other) const override {\n    FML_DCHECK(other.type() == ColorFilterType::kUnknown);\n    auto that = static_cast<UnknownColorFilter const*>(&other);\n    return sk_filter_ == that->sk_filter_;\n  }\n\n private:\n  GrColorFilterPtr sk_filter_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_COLOR_FILTER_H_\n"
  },
  {
    "path": "clay/gfx/style/color_filter_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstring>\n\n#include \"clay/gfx/attributes_testing.h\"\n#include \"clay/gfx/testing_utils.h\"\n\nnamespace clay {\nnamespace testing {\n\nstatic const float kMatrix[20] = {\n    1,  2,  3,  4,  5,   //\n    6,  7,  8,  9,  10,  //\n    11, 12, 13, 14, 15,  //\n    16, 17, 18, 19, 20,  //\n};\n\nTEST(DisplayListColorFilter, FromSkiaBlendFilter) {\n  std::shared_ptr<ColorFilter> filter =\n      ColorFilter::MakeBlend(Color::kRed(), BlendMode::kDstATop);\n  DlBlendColorFilter dl_filter(DlColor::kRed(), DlBlendMode::kDstATop);\n  ASSERT_EQ(filter->type(), DlColorFilterType::kBlend);\n  ASSERT_EQ(*filter->asBlend(), dl_filter);\n  ASSERT_EQ(filter->asBlend()->color(), DlColor::kRed());\n  ASSERT_EQ(filter->asBlend()->mode(), DlBlendMode::kDstATop);\n\n  ASSERT_EQ(filter->asMatrix(), nullptr);\n}\n\nTEST(DisplayListColorFilter, FromSkiaMatrixFilter) {\n  std::shared_ptr<ColorFilter> filter = ColorFilter::MakeMatrix(kMatrix);\n  DlMatrixColorFilter dl_filter(kMatrix);\n  ASSERT_EQ(filter->type(), DlColorFilterType::kMatrix);\n  ASSERT_EQ(*filter->asMatrix(), dl_filter);\n  const DlMatrixColorFilter* matrix_filter = filter->asMatrix();\n  for (int i = 0; i < 20; i++) {\n    ASSERT_EQ((*matrix_filter)[i], kMatrix[i]);\n  }\n\n  ASSERT_EQ(filter->asBlend(), nullptr);\n}\n\nTEST(DisplayListColorFilter, FromSkiaSrgbToLinearFilter) {\n  std::shared_ptr<ColorFilter> filter = ColorFilter::MakeSrgbToLinearGamma();\n  ASSERT_EQ(filter->type(), DlColorFilterType::kSrgbToLinearGamma);\n\n  ASSERT_EQ(filter->asBlend(), nullptr);\n  ASSERT_EQ(filter->asMatrix(), nullptr);\n}\n\nTEST(DisplayListColorFilter, FromSkiaLinearToSrgbFilter) {\n  std::shared_ptr<ColorFilter> filter = ColorFilter::MakeLinearToSrgbGamma();\n  ASSERT_EQ(filter->type(), DlColorFilterType::kLinearToSrgbGamma);\n\n  ASSERT_EQ(filter->asBlend(), nullptr);\n  ASSERT_EQ(filter->asMatrix(), nullptr);\n}\n\nTEST(DisplayListColorFilter, FromSkiaUnrecognizedFilter) {\n  sk_sp<SkColorFilter> sk_input_a =\n      SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kOverlay);\n  sk_sp<SkColorFilter> sk_input_b =\n      SkColorFilters::Blend(SK_ColorBLUE, SkBlendMode::kScreen);\n  sk_sp<SkColorFilter> sk_filter =\n      SkColorFilters::Compose(sk_input_a, sk_input_b);\n  std::shared_ptr<ColorFilter> filter =\n      std::make_shared<UnknownColorFilter>(sk_filter);\n  ASSERT_EQ(filter->type(), DlColorFilterType::kUnknown);\n  ASSERT_EQ(filter->gr_object(), sk_filter);\n\n  ASSERT_EQ(filter->asBlend(), nullptr);\n  ASSERT_EQ(filter->asMatrix(), nullptr);\n}\n\nTEST(DisplayListColorFilter, BlendConstructor) {\n  DlBlendColorFilter filter(DlColor::kRed(), DlBlendMode::kDstATop);\n}\n\nTEST(DisplayListColorFilter, BlendShared) {\n  DlBlendColorFilter filter(DlColor::kRed(), DlBlendMode::kDstATop);\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListColorFilter, BlendAsBlend) {\n  DlBlendColorFilter filter(DlColor::kRed(), DlBlendMode::kDstATop);\n  ASSERT_NE(filter.asBlend(), nullptr);\n  ASSERT_EQ(filter.asBlend(), &filter);\n}\n\nTEST(DisplayListColorFilter, BlendContents) {\n  DlBlendColorFilter filter(DlColor::kRed(), DlBlendMode::kDstATop);\n  ASSERT_EQ(filter.color(), DlColor::kRed());\n  ASSERT_EQ(filter.mode(), DlBlendMode::kDstATop);\n}\n\nTEST(DisplayListColorFilter, BlendEquals) {\n  DlBlendColorFilter filter1(DlColor::kRed(), DlBlendMode::kDstATop);\n  DlBlendColorFilter filter2(DlColor::kRed(), DlBlendMode::kDstATop);\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListColorFilter, BlendNotEquals) {\n  DlBlendColorFilter filter1(DlColor::kRed(), DlBlendMode::kDstATop);\n  DlBlendColorFilter filter2(DlColor::kBlue(), DlBlendMode::kDstATop);\n  DlBlendColorFilter filter3(DlColor::kRed(), DlBlendMode::kDstIn);\n  TestNotEquals(filter1, filter2, \"Color differs\");\n  TestNotEquals(filter1, filter3, \"Blend mode differs\");\n}\n\nTEST(DisplayListColorFilter, NopBlendShouldNotCrash) {\n  DlBlendColorFilter filter(DlColor::kTransparent(), DlBlendMode::kSrcOver);\n  ASSERT_FALSE(filter.modifies_transparent_black());\n}\n\nTEST(DisplayListColorFilter, MatrixConstructor) {\n  DlMatrixColorFilter filter(kMatrix);\n}\n\nTEST(DisplayListColorFilter, MatrixShared) {\n  DlMatrixColorFilter filter(kMatrix);\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListColorFilter, MatrixAsMatrix) {\n  DlMatrixColorFilter filter(kMatrix);\n  ASSERT_NE(filter.asMatrix(), nullptr);\n  ASSERT_EQ(filter.asMatrix(), &filter);\n}\n\nTEST(DisplayListColorFilter, MatrixContents) {\n  float matrix[20];\n  memcpy(matrix, kMatrix, sizeof(matrix));\n  DlMatrixColorFilter filter(matrix);\n\n  // Test deref operator []\n  for (int i = 0; i < 20; i++) {\n    ASSERT_EQ(filter[i], matrix[i]);\n  }\n\n  // Test get_matrix\n  float matrix2[20];\n  filter.get_matrix(matrix2);\n  for (int i = 0; i < 20; i++) {\n    ASSERT_EQ(matrix2[i], matrix[i]);\n  }\n\n  // Test perturbing original array does not affect filter\n  float original_value = matrix[4];\n  matrix[4] += 101;\n  ASSERT_EQ(filter[4], original_value);\n}\n\nTEST(DisplayListColorFilter, MatrixEquals) {\n  DlMatrixColorFilter filter1(kMatrix);\n  DlMatrixColorFilter filter2(kMatrix);\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListColorFilter, MatrixNotEquals) {\n  float matrix[20];\n  memcpy(matrix, kMatrix, sizeof(matrix));\n  DlMatrixColorFilter filter1(matrix);\n  matrix[4] += 101;\n  DlMatrixColorFilter filter2(matrix);\n  TestNotEquals(filter1, filter2, \"Matrix differs\");\n}\n\nTEST(DisplayListColorFilter, NopMatrixShouldNotCrash) {\n  float matrix[20] = {\n      1, 0, 0, 0, 0,  //\n      0, 1, 0, 0, 0,  //\n      0, 0, 1, 0, 0,  //\n      0, 0, 0, 1, 0,  //\n  };\n  DlMatrixColorFilter filter(matrix);\n  ASSERT_FALSE(filter.modifies_transparent_black());\n}\n\nTEST(DisplayListColorFilter, SrgbToLinearConstructor) {\n  DlSrgbToLinearGammaColorFilter filter;\n}\n\nTEST(DisplayListColorFilter, SrgbToLinearShared) {\n  DlSrgbToLinearGammaColorFilter filter;\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListColorFilter, SrgbToLinearEquals) {\n  DlSrgbToLinearGammaColorFilter filter1;\n  DlSrgbToLinearGammaColorFilter filter2;\n  TestEquals(filter1, filter2);\n  TestEquals(filter1, *DlSrgbToLinearGammaColorFilter::instance);\n}\n\nTEST(DisplayListColorFilter, LinearToSrgbConstructor) {\n  DlLinearToSrgbGammaColorFilter filter;\n}\n\nTEST(DisplayListColorFilter, LinearToSrgbShared) {\n  DlLinearToSrgbGammaColorFilter filter;\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListColorFilter, LinearToSrgbEquals) {\n  DlLinearToSrgbGammaColorFilter filter1;\n  DlLinearToSrgbGammaColorFilter filter2;\n  TestEquals(filter1, filter2);\n  TestEquals(filter1, *DlLinearToSrgbGammaColorFilter::instance);\n}\n\nTEST(DisplayListColorFilter, UnknownConstructor) {\n  DlUnknownColorFilter filter(SkColorFilters::LinearToSRGBGamma());\n}\n\nTEST(DisplayListColorFilter, UnknownShared) {\n  DlUnknownColorFilter filter(SkColorFilters::LinearToSRGBGamma());\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListColorFilter, UnknownContents) {\n  sk_sp<SkColorFilter> sk_filter = SkColorFilters::LinearToSRGBGamma();\n  DlUnknownColorFilter filter(sk_filter);\n  ASSERT_EQ(sk_filter, filter.gr_object());\n  ASSERT_EQ(sk_filter.get(), filter.gr_object().get());\n}\n\nTEST(DisplayListColorFilter, UnknownEquals) {\n  sk_sp<SkColorFilter> sk_filter = SkColorFilters::LinearToSRGBGamma();\n  DlUnknownColorFilter filter1(sk_filter);\n  DlUnknownColorFilter filter2(sk_filter);\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListColorFilter, UnknownNotEquals) {\n  // Even though the filter is the same, it is a different instance\n  // and we cannot currently tell them apart because the Skia\n  // ColorFilter objects do not implement ==\n  DlUnknownColorFilter filter1(\n      SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kDstATop));\n  DlUnknownColorFilter filter2(\n      SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kDstATop));\n  TestNotEquals(filter1, filter2, \"SkColorFilter instance differs\");\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/color_gen.inl",
    "content": "/* C++ code produced by gperf version 3.1 */\n/* Command-line: gperf -m 100 --output-file=clay/gfx/style/color_gen.inl clay/gfx/style/color.tmpl  */\n/* Computed positions: -k'1,3,6-8,12-13' */\n\n#if !((' ' == 32) && ('!' == 33) && ('\"' == 34) && ('#' == 35) \\\n      && ('%' == 37) && ('&' == 38) && ('\\'' == 39) && ('(' == 40) \\\n      && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \\\n      && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \\\n      && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \\\n      && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \\\n      && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \\\n      && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \\\n      && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \\\n      && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \\\n      && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \\\n      && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \\\n      && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \\\n      && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \\\n      && ('Z' == 90) && ('[' == 91) && ('\\\\' == 92) && (']' == 93) \\\n      && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \\\n      && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \\\n      && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \\\n      && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \\\n      && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \\\n      && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \\\n      && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \\\n      && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))\n/* The character set is not based on ISO-646.  */\n#error \"gperf generated tables don't work with this execution character set. Please report a bug to <bug-gperf@gnu.org>.\"\n#endif\n\n#line 10 \"clay/gfx/style/color.tmpl\"\n\n#line 13 \"clay/gfx/style/color.tmpl\"\nstruct __attribute__((packed)) TokenValue {\n  short name;\n  unsigned int color;\n};\n\nstatic constexpr unsigned int RGBColor(uint8_t r, uint8_t g, uint8_t b) {\n  return 0xFF000000 | (r << 16) | (g << 8) | b;\n};\n\n#define TOTAL_KEYWORDS 148\n#define MIN_WORD_LENGTH 3\n#define MAX_WORD_LENGTH 20\n#define MIN_HASH_VALUE 10\n#define MAX_HASH_VALUE 301\n/* maximum key range = 292, duplicates = 0 */\n\nclass ColorHash\n{\nprivate:\n  static inline unsigned int hash (const char *str, size_t len);\npublic:\n  static const struct __attribute__((packed)) TokenValue *find (const char *str, size_t len);\n};\n\ninline unsigned int\nColorHash::hash (const char *str, size_t len)\n{\n  static const unsigned short asso_values[] =\n    {\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302,   5,  55,   3,\n       26,   3,  51,   6,  21,   3, 302, 102,   4,  53,\n       14,  29,  52, 115,   3,   9,   4,  58,  54,  95,\n       69,  70, 302,   5, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302, 302, 302,\n      302, 302, 302, 302, 302, 302, 302, 302\n    };\n  unsigned int hval = len;\n\n  switch (hval)\n    {\n      default:\n        hval += asso_values[static_cast<unsigned char>(str[12])];\n      /*FALLTHROUGH*/\n      case 12:\n        hval += asso_values[static_cast<unsigned char>(str[11])];\n      /*FALLTHROUGH*/\n      case 11:\n      case 10:\n      case 9:\n      case 8:\n        hval += asso_values[static_cast<unsigned char>(str[7])];\n      /*FALLTHROUGH*/\n      case 7:\n        hval += asso_values[static_cast<unsigned char>(str[6])];\n      /*FALLTHROUGH*/\n      case 6:\n        hval += asso_values[static_cast<unsigned char>(str[5])];\n      /*FALLTHROUGH*/\n      case 5:\n      case 4:\n      case 3:\n        hval += asso_values[static_cast<unsigned char>(str[2]+2)];\n      /*FALLTHROUGH*/\n      case 2:\n      case 1:\n        hval += asso_values[static_cast<unsigned char>(str[0])];\n        break;\n    }\n  return hval;\n}\n\nstruct StringPool_t\n  {\n    char StringPool_str10[sizeof(\"cyan\")];\n    char StringPool_str11[sizeof(\"teal\")];\n    char StringPool_str12[sizeof(\"coral\")];\n    char StringPool_str13[sizeof(\"gray\")];\n    char StringPool_str16[sizeof(\"grey\")];\n    char StringPool_str17[sizeof(\"green\")];\n    char StringPool_str24[sizeof(\"gold\")];\n    char StringPool_str26[sizeof(\"sienna\")];\n    char StringPool_str28[sizeof(\"lightgrey\")];\n    char StringPool_str29[sizeof(\"lightgreen\")];\n    char StringPool_str30[sizeof(\"lightgray\")];\n    char StringPool_str31[sizeof(\"seashell\")];\n    char StringPool_str32[sizeof(\"silver\")];\n    char StringPool_str33[sizeof(\"slategrey\")];\n    char StringPool_str35[sizeof(\"slategray\")];\n    char StringPool_str36[sizeof(\"lightsalmon\")];\n    char StringPool_str37[sizeof(\"lime\")];\n    char StringPool_str39[sizeof(\"springgreen\")];\n    char StringPool_str40[sizeof(\"seagreen\")];\n    char StringPool_str41[sizeof(\"orange\")];\n    char StringPool_str43[sizeof(\"salmon\")];\n    char StringPool_str45[sizeof(\"lightslategrey\")];\n    char StringPool_str47[sizeof(\"lightslategray\")];\n    char StringPool_str48[sizeof(\"darkgreen\")];\n    char StringPool_str50[sizeof(\"orangered\")];\n    char StringPool_str51[sizeof(\"limegreen\")];\n    char StringPool_str52[sizeof(\"lightcoral\")];\n    char StringPool_str54[sizeof(\"lightseagreen\")];\n    char StringPool_str55[sizeof(\"darkmagenta\")];\n    char StringPool_str57[sizeof(\"red\")];\n    char StringPool_str58[sizeof(\"turquoise\")];\n    char StringPool_str59[sizeof(\"tan\")];\n    char StringPool_str60[sizeof(\"peru\")];\n    char StringPool_str61[sizeof(\"linen\")];\n    char StringPool_str62[sizeof(\"darkorange\")];\n    char StringPool_str63[sizeof(\"black\")];\n    char StringPool_str64[sizeof(\"orchid\")];\n    char StringPool_str65[sizeof(\"purple\")];\n    char StringPool_str66[sizeof(\"darkred\")];\n    char StringPool_str67[sizeof(\"darkorchid\")];\n    char StringPool_str68[sizeof(\"tomato\")];\n    char StringPool_str69[sizeof(\"fuchsia\")];\n    char StringPool_str70[sizeof(\"darkseagreen\")];\n    char StringPool_str72[sizeof(\"magenta\")];\n    char StringPool_str73[sizeof(\"firebrick\")];\n    char StringPool_str75[sizeof(\"goldenrod\")];\n    char StringPool_str76[sizeof(\"darkviolet\")];\n    char StringPool_str77[sizeof(\"maroon\")];\n    char StringPool_str78[sizeof(\"transparent\")];\n    char StringPool_str79[sizeof(\"forestgreen\")];\n    char StringPool_str80[sizeof(\"chartreuse\")];\n    char StringPool_str82[sizeof(\"skyblue\")];\n    char StringPool_str83[sizeof(\"indianred\")];\n    char StringPool_str84[sizeof(\"palegreen\")];\n    char StringPool_str85[sizeof(\"lightpink\")];\n    char StringPool_str86[sizeof(\"lemonchiffon\")];\n    char StringPool_str87[sizeof(\"navy\")];\n    char StringPool_str89[sizeof(\"indigo\")];\n    char StringPool_str90[sizeof(\"moccasin\")];\n    char StringPool_str92[sizeof(\"lawngreen\")];\n    char StringPool_str93[sizeof(\"oldlace\")];\n    char StringPool_str94[sizeof(\"lightcyan\")];\n    char StringPool_str95[sizeof(\"lightyellow\")];\n    char StringPool_str98[sizeof(\"lightgoldenrodyellow\")];\n    char StringPool_str99[sizeof(\"lightsteelblue\")];\n    char StringPool_str100[sizeof(\"greenyellow\")];\n    char StringPool_str102[sizeof(\"darksalmon\")];\n    char StringPool_str103[sizeof(\"darkblue\")];\n    char StringPool_str104[sizeof(\"aqua\")];\n    char StringPool_str105[sizeof(\"azure\")];\n    char StringPool_str106[sizeof(\"wheat\")];\n    char StringPool_str108[sizeof(\"pink\")];\n    char StringPool_str110[sizeof(\"khaki\")];\n    char StringPool_str111[sizeof(\"darkolivegreen\")];\n    char StringPool_str113[sizeof(\"lavender\")];\n    char StringPool_str114[sizeof(\"darkgrey\")];\n    char StringPool_str116[sizeof(\"darkgray\")];\n    char StringPool_str117[sizeof(\"darkslateblue\")];\n    char StringPool_str120[sizeof(\"thistle\")];\n    char StringPool_str121[sizeof(\"aquamarine\")];\n    char StringPool_str122[sizeof(\"bisque\")];\n    char StringPool_str123[sizeof(\"ivory\")];\n    char StringPool_str124[sizeof(\"cornsilk\")];\n    char StringPool_str125[sizeof(\"mintcream\")];\n    char StringPool_str127[sizeof(\"darkcyan\")];\n    char StringPool_str128[sizeof(\"snow\")];\n    char StringPool_str129[sizeof(\"darkslategrey\")];\n    char StringPool_str131[sizeof(\"darkslategray\")];\n    char StringPool_str132[sizeof(\"saddlebrown\")];\n    char StringPool_str133[sizeof(\"lightblue\")];\n    char StringPool_str134[sizeof(\"royalblue\")];\n    char StringPool_str135[sizeof(\"dimgrey\")];\n    char StringPool_str136[sizeof(\"olive\")];\n    char StringPool_str137[sizeof(\"dimgray\")];\n    char StringPool_str138[sizeof(\"slateblue\")];\n    char StringPool_str140[sizeof(\"chocolate\")];\n    char StringPool_str141[sizeof(\"steelblue\")];\n    char StringPool_str144[sizeof(\"palevioletred\")];\n    char StringPool_str148[sizeof(\"lavenderblush\")];\n    char StringPool_str149[sizeof(\"dodgerblue\")];\n    char StringPool_str150[sizeof(\"midnightblue\")];\n    char StringPool_str151[sizeof(\"plum\")];\n    char StringPool_str154[sizeof(\"blue\")];\n    char StringPool_str155[sizeof(\"crimson\")];\n    char StringPool_str157[sizeof(\"darkgoldenrod\")];\n    char StringPool_str158[sizeof(\"sandybrown\")];\n    char StringPool_str159[sizeof(\"deeppink\")];\n    char StringPool_str161[sizeof(\"mistyrose\")];\n    char StringPool_str162[sizeof(\"beige\")];\n    char StringPool_str165[sizeof(\"blanchedalmond\")];\n    char StringPool_str167[sizeof(\"darkkhaki\")];\n    char StringPool_str174[sizeof(\"olivedrab\")];\n    char StringPool_str175[sizeof(\"brown\")];\n    char StringPool_str179[sizeof(\"violet\")];\n    char StringPool_str180[sizeof(\"cadetblue\")];\n    char StringPool_str185[sizeof(\"yellow\")];\n    char StringPool_str186[sizeof(\"papayawhip\")];\n    char StringPool_str189[sizeof(\"mediumseagreen\")];\n    char StringPool_str193[sizeof(\"palegoldenrod\")];\n    char StringPool_str194[sizeof(\"powderblue\")];\n    char StringPool_str196[sizeof(\"blueviolet\")];\n    char StringPool_str197[sizeof(\"rosybrown\")];\n    char StringPool_str198[sizeof(\"hotpink\")];\n    char StringPool_str199[sizeof(\"yellowgreen\")];\n    char StringPool_str202[sizeof(\"white\")];\n    char StringPool_str203[sizeof(\"lightskyblue\")];\n    char StringPool_str204[sizeof(\"gainsboro\")];\n    char StringPool_str205[sizeof(\"honeydew\")];\n    char StringPool_str211[sizeof(\"cornflowerblue\")];\n    char StringPool_str221[sizeof(\"burlywood\")];\n    char StringPool_str225[sizeof(\"peachpuff\")];\n    char StringPool_str226[sizeof(\"mediumblue\")];\n    char StringPool_str227[sizeof(\"mediumorchid\")];\n    char StringPool_str230[sizeof(\"antiquewhite\")];\n    char StringPool_str231[sizeof(\"darkturquoise\")];\n    char StringPool_str233[sizeof(\"aliceblue\")];\n    char StringPool_str236[sizeof(\"mediumvioletred\")];\n    char StringPool_str239[sizeof(\"navajowhite\")];\n    char StringPool_str244[sizeof(\"mediumslateblue\")];\n    char StringPool_str247[sizeof(\"mediumspringgreen\")];\n    char StringPool_str250[sizeof(\"ghostwhite\")];\n    char StringPool_str266[sizeof(\"mediumturquoise\")];\n    char StringPool_str267[sizeof(\"paleturquoise\")];\n    char StringPool_str270[sizeof(\"deepskyblue\")];\n    char StringPool_str282[sizeof(\"mediumpurple\")];\n    char StringPool_str297[sizeof(\"floralwhite\")];\n    char StringPool_str298[sizeof(\"whitesmoke\")];\n    char StringPool_str301[sizeof(\"mediumaquamarine\")];\n  };\nstatic const struct StringPool_t StringPool_contents =\n  {\n    \"cyan\",\n    \"teal\",\n    \"coral\",\n    \"gray\",\n    \"grey\",\n    \"green\",\n    \"gold\",\n    \"sienna\",\n    \"lightgrey\",\n    \"lightgreen\",\n    \"lightgray\",\n    \"seashell\",\n    \"silver\",\n    \"slategrey\",\n    \"slategray\",\n    \"lightsalmon\",\n    \"lime\",\n    \"springgreen\",\n    \"seagreen\",\n    \"orange\",\n    \"salmon\",\n    \"lightslategrey\",\n    \"lightslategray\",\n    \"darkgreen\",\n    \"orangered\",\n    \"limegreen\",\n    \"lightcoral\",\n    \"lightseagreen\",\n    \"darkmagenta\",\n    \"red\",\n    \"turquoise\",\n    \"tan\",\n    \"peru\",\n    \"linen\",\n    \"darkorange\",\n    \"black\",\n    \"orchid\",\n    \"purple\",\n    \"darkred\",\n    \"darkorchid\",\n    \"tomato\",\n    \"fuchsia\",\n    \"darkseagreen\",\n    \"magenta\",\n    \"firebrick\",\n    \"goldenrod\",\n    \"darkviolet\",\n    \"maroon\",\n    \"transparent\",\n    \"forestgreen\",\n    \"chartreuse\",\n    \"skyblue\",\n    \"indianred\",\n    \"palegreen\",\n    \"lightpink\",\n    \"lemonchiffon\",\n    \"navy\",\n    \"indigo\",\n    \"moccasin\",\n    \"lawngreen\",\n    \"oldlace\",\n    \"lightcyan\",\n    \"lightyellow\",\n    \"lightgoldenrodyellow\",\n    \"lightsteelblue\",\n    \"greenyellow\",\n    \"darksalmon\",\n    \"darkblue\",\n    \"aqua\",\n    \"azure\",\n    \"wheat\",\n    \"pink\",\n    \"khaki\",\n    \"darkolivegreen\",\n    \"lavender\",\n    \"darkgrey\",\n    \"darkgray\",\n    \"darkslateblue\",\n    \"thistle\",\n    \"aquamarine\",\n    \"bisque\",\n    \"ivory\",\n    \"cornsilk\",\n    \"mintcream\",\n    \"darkcyan\",\n    \"snow\",\n    \"darkslategrey\",\n    \"darkslategray\",\n    \"saddlebrown\",\n    \"lightblue\",\n    \"royalblue\",\n    \"dimgrey\",\n    \"olive\",\n    \"dimgray\",\n    \"slateblue\",\n    \"chocolate\",\n    \"steelblue\",\n    \"palevioletred\",\n    \"lavenderblush\",\n    \"dodgerblue\",\n    \"midnightblue\",\n    \"plum\",\n    \"blue\",\n    \"crimson\",\n    \"darkgoldenrod\",\n    \"sandybrown\",\n    \"deeppink\",\n    \"mistyrose\",\n    \"beige\",\n    \"blanchedalmond\",\n    \"darkkhaki\",\n    \"olivedrab\",\n    \"brown\",\n    \"violet\",\n    \"cadetblue\",\n    \"yellow\",\n    \"papayawhip\",\n    \"mediumseagreen\",\n    \"palegoldenrod\",\n    \"powderblue\",\n    \"blueviolet\",\n    \"rosybrown\",\n    \"hotpink\",\n    \"yellowgreen\",\n    \"white\",\n    \"lightskyblue\",\n    \"gainsboro\",\n    \"honeydew\",\n    \"cornflowerblue\",\n    \"burlywood\",\n    \"peachpuff\",\n    \"mediumblue\",\n    \"mediumorchid\",\n    \"antiquewhite\",\n    \"darkturquoise\",\n    \"aliceblue\",\n    \"mediumvioletred\",\n    \"navajowhite\",\n    \"mediumslateblue\",\n    \"mediumspringgreen\",\n    \"ghostwhite\",\n    \"mediumturquoise\",\n    \"paleturquoise\",\n    \"deepskyblue\",\n    \"mediumpurple\",\n    \"floralwhite\",\n    \"whitesmoke\",\n    \"mediumaquamarine\"\n  };\n#define StringPool ((const char *) &StringPool_contents)\n\nstatic const struct __attribute__((packed)) TokenValue wordlist[] =\n  {\n    {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1},\n    {-1},\n#line 44 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str10, RGBColor(0, 255, 255)},\n#line 161 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str11, RGBColor(0, 128, 128)},\n#line 40 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str12, RGBColor(255, 127, 80)},\n#line 77 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str13, RGBColor(128, 128, 128)},\n    {-1}, {-1},\n#line 80 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str16, RGBColor(128, 128, 128)},\n#line 78 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str17, RGBColor(0, 128, 0)},\n    {-1}, {-1}, {-1}, {-1}, {-1}, {-1},\n#line 75 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str24, RGBColor(255, 215, 0)},\n    {-1},\n#line 151 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str26, RGBColor(160, 82, 45)},\n    {-1},\n#line 97 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str28, RGBColor(211, 211, 211)},\n#line 96 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str29, RGBColor(144, 238, 144)},\n#line 95 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str30, RGBColor(211, 211, 211)},\n#line 150 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str31, RGBColor(255, 245, 238)},\n#line 152 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str32, RGBColor(192, 192, 192)},\n#line 156 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str33, RGBColor(112, 128, 144)},\n    {-1},\n#line 155 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str35, RGBColor(112, 128, 144)},\n#line 99 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str36, RGBColor(255, 160, 122)},\n#line 106 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str37, RGBColor(0, 255, 0)},\n    {-1},\n#line 158 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str39, RGBColor(0, 255, 127)},\n#line 149 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str40, RGBColor(46, 139, 87)},\n#line 129 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str41, RGBColor(255, 165, 0)},\n    {-1},\n#line 147 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str43, RGBColor(250, 128, 114)},\n    {-1},\n#line 103 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str45, RGBColor(119, 136, 153)},\n    {-1},\n#line 102 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str47, RGBColor(119, 136, 153)},\n#line 49 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str48, RGBColor(0, 100, 0)},\n    {-1},\n#line 130 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str50, RGBColor(255, 69, 0)},\n#line 107 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str51, RGBColor(50, 205, 50)},\n#line 92 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str52, RGBColor(240, 128, 128)},\n    {-1},\n#line 100 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str54, RGBColor(32, 178, 170)},\n#line 52 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str55, RGBColor(139, 0, 139)},\n    {-1},\n#line 143 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str57, RGBColor(255, 0, 0)},\n#line 164 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str58, RGBColor(64, 224, 208)},\n#line 160 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str59, RGBColor(210, 180, 140)},\n#line 138 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str60, RGBColor(205, 133, 63)},\n#line 108 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str61, RGBColor(250, 240, 230)},\n#line 54 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str62, RGBColor(255, 140, 0)},\n#line 31 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str63, RGBColor(0, 0, 0)},\n#line 131 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str64, RGBColor(218, 112, 214)},\n#line 142 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str65, RGBColor(128, 0, 128)},\n#line 56 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str66, RGBColor(139, 0, 0)},\n#line 55 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str67, RGBColor(153, 50, 204)},\n#line 163 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str68, RGBColor(255, 99, 71)},\n#line 72 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str69, RGBColor(255, 0, 255)},\n#line 58 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str70, RGBColor(143, 188, 143)},\n    {-1},\n#line 109 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str72, RGBColor(255, 0, 255)},\n#line 69 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str73, RGBColor(178, 34, 34)},\n    {-1},\n#line 76 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str75, RGBColor(218, 165, 32)},\n#line 63 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str76, RGBColor(148, 0, 211)},\n#line 110 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str77, RGBColor(128, 0, 0)},\n#line 23 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str78, 0},\n#line 71 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str79, RGBColor(34, 139, 34)},\n#line 38 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str80, RGBColor(127, 255, 0)},\n    {-1},\n#line 153 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str82, RGBColor(135, 206, 235)},\n#line 83 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str83, RGBColor(205, 92, 92)},\n#line 133 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str84, RGBColor(152, 251, 152)},\n#line 98 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str85, RGBColor(255, 182, 193)},\n#line 90 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str86, RGBColor(255, 250, 205)},\n#line 125 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str87, RGBColor(0, 0, 128)},\n    {-1},\n#line 84 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str89, RGBColor(75, 0, 130)},\n#line 123 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str90, RGBColor(255, 228, 181)},\n    {-1},\n#line 89 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str92, RGBColor(124, 252, 0)},\n#line 126 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str93, RGBColor(253, 245, 230)},\n#line 93 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str94, RGBColor(224, 255, 255)},\n#line 105 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str95, RGBColor(255, 255, 224)},\n    {-1}, {-1},\n#line 94 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str98, RGBColor(250, 250, 210)},\n#line 104 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str99, RGBColor(176, 196, 222)},\n#line 79 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str100, RGBColor(173, 255, 47)},\n    {-1},\n#line 57 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str102, RGBColor(233, 150, 122)},\n#line 45 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str103, RGBColor(0, 0, 139)},\n#line 26 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str104, RGBColor(0, 255, 255)},\n#line 28 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str105, RGBColor(240, 255, 255)},\n#line 166 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str106, RGBColor(245, 222, 179)},\n    {-1},\n#line 139 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str108, RGBColor(255, 192, 203)},\n    {-1},\n#line 86 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str110, RGBColor(240, 230, 140)},\n#line 53 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str111, RGBColor(85, 107, 47)},\n    {-1},\n#line 87 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str113, RGBColor(230, 230, 250)},\n#line 50 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str114, RGBColor(169, 169, 169)},\n    {-1},\n#line 48 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str116, RGBColor(169, 169, 169)},\n#line 59 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str117, RGBColor(72, 61, 139)},\n    {-1}, {-1},\n#line 162 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str120, RGBColor(216, 191, 216)},\n#line 27 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str121, RGBColor(127, 255, 212)},\n#line 30 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str122, RGBColor(255, 228, 196)},\n#line 85 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str123, RGBColor(255, 255, 240)},\n#line 42 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str124, RGBColor(255, 248, 220)},\n#line 121 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str125, RGBColor(245, 255, 250)},\n    {-1},\n#line 46 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str127, RGBColor(0, 139, 139)},\n#line 157 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str128, RGBColor(255, 250, 250)},\n#line 61 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str129, RGBColor(47, 79, 79)},\n    {-1},\n#line 60 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str131, RGBColor(47, 79, 79)},\n#line 146 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str132, RGBColor(139, 69, 19)},\n#line 91 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str133, RGBColor(173, 216, 230)},\n#line 145 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str134, RGBColor(65, 105, 225)},\n#line 67 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str135, RGBColor(105, 105, 105)},\n#line 127 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str136, RGBColor(128, 128, 0)},\n#line 66 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str137, RGBColor(105, 105, 105)},\n#line 154 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str138, RGBColor(106, 90, 205)},\n    {-1},\n#line 39 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str140, RGBColor(210, 105, 30)},\n#line 159 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str141, RGBColor(70, 130, 180)},\n    {-1}, {-1},\n#line 135 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str144, RGBColor(219, 112, 147)},\n    {-1}, {-1}, {-1},\n#line 88 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str148, RGBColor(255, 240, 245)},\n#line 68 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str149, RGBColor(30, 144, 255)},\n#line 120 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str150, RGBColor(25, 25, 112)},\n#line 140 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str151, RGBColor(221, 160, 221)},\n    {-1}, {-1},\n#line 33 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str154, RGBColor(0, 0, 255)},\n#line 43 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str155, RGBColor(220, 20, 60)},\n    {-1},\n#line 47 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str157, RGBColor(184, 134, 11)},\n#line 148 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str158, RGBColor(244, 164, 96)},\n#line 64 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str159, RGBColor(255, 20, 147)},\n    {-1},\n#line 122 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str161, RGBColor(255, 228, 225)},\n#line 29 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str162, RGBColor(245, 245, 220)},\n    {-1}, {-1},\n#line 32 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str165, RGBColor(255, 235, 205)},\n    {-1},\n#line 51 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str167, RGBColor(189, 183, 107)},\n    {-1}, {-1}, {-1}, {-1}, {-1}, {-1},\n#line 128 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str174, RGBColor(107, 142, 35)},\n#line 35 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str175, RGBColor(165, 42, 42)},\n    {-1}, {-1}, {-1},\n#line 165 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str179, RGBColor(238, 130, 238)},\n#line 37 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str180, RGBColor(95, 158, 160)},\n    {-1}, {-1}, {-1}, {-1},\n#line 169 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str185, RGBColor(255, 255, 0)},\n#line 136 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str186, RGBColor(255, 239, 213)},\n    {-1}, {-1},\n#line 115 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str189, RGBColor(60, 179, 113)},\n    {-1}, {-1}, {-1},\n#line 132 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str193, RGBColor(238, 232, 170)},\n#line 141 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str194, RGBColor(176, 224, 230)},\n    {-1},\n#line 34 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str196, RGBColor(138, 43, 226)},\n#line 144 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str197, RGBColor(188, 143, 143)},\n#line 82 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str198, RGBColor(255, 105, 180)},\n#line 170 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str199, RGBColor(154, 205, 50)},\n    {-1}, {-1},\n#line 167 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str202, RGBColor(255, 255, 255)},\n#line 101 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str203, RGBColor(135, 206, 250)},\n#line 73 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str204, RGBColor(220, 220, 220)},\n#line 81 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str205, RGBColor(240, 255, 240)},\n    {-1}, {-1}, {-1}, {-1}, {-1},\n#line 41 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str211, RGBColor(100, 149, 237)},\n    {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1},\n#line 36 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str221, RGBColor(222, 184, 135)},\n    {-1}, {-1}, {-1},\n#line 137 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str225, RGBColor(255, 218, 185)},\n#line 112 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str226, RGBColor(0, 0, 205)},\n#line 113 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str227, RGBColor(186, 85, 211)},\n    {-1}, {-1},\n#line 25 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str230, RGBColor(250, 235, 215)},\n#line 62 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str231, RGBColor(0, 206, 209)},\n    {-1},\n#line 24 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str233, RGBColor(240, 248, 255)},\n    {-1}, {-1},\n#line 119 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str236, RGBColor(199, 21, 133)},\n    {-1}, {-1},\n#line 124 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str239, RGBColor(255, 222, 173)},\n    {-1}, {-1}, {-1}, {-1},\n#line 116 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str244, RGBColor(123, 104, 238)},\n    {-1}, {-1},\n#line 117 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str247, RGBColor(0, 250, 154)},\n    {-1}, {-1},\n#line 74 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str250, RGBColor(248, 248, 255)},\n    {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1},\n    {-1}, {-1}, {-1}, {-1}, {-1}, {-1},\n#line 118 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str266, RGBColor(72, 209, 204)},\n#line 134 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str267, RGBColor(175, 238, 238)},\n    {-1}, {-1},\n#line 65 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str270, RGBColor(0, 191, 255)},\n    {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1},\n    {-1}, {-1},\n#line 114 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str282, RGBColor(147, 112, 219)},\n    {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1},\n    {-1}, {-1}, {-1}, {-1}, {-1},\n#line 70 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str297, RGBColor(255, 250, 240)},\n#line 168 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str298, RGBColor(245, 245, 245)},\n    {-1}, {-1},\n#line 111 \"clay/gfx/style/color.tmpl\"\n    {(short)(size_t)&((struct StringPool_t *)0)->StringPool_str301, RGBColor(102, 205, 170)}\n  };\n\nconst struct __attribute__((packed)) TokenValue *\nColorHash::find (const char *str, size_t len)\n{\n  if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)\n    {\n      unsigned int key = hash (str, len);\n\n      if (key <= MAX_HASH_VALUE)\n        {\n          int o = wordlist[key].name;\n          if (o >= 0)\n            {\n              const char *s = o + StringPool;\n\n              if (*str == *s && !strcmp (str + 1, s + 1))\n                return &wordlist[key];\n            }\n        }\n    }\n  return 0;\n}\n#line 171 \"clay/gfx/style/color.tmpl\"\n\n"
  },
  {
    "path": "clay/gfx/style/color_source.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/color_source.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nstatic void DlGradientDeleter(void* p) {\n  // Some of our target environments would prefer a sized delete,\n  // but other target environments do not have that operator.\n  // Use an unsized delete until we get better agreement in the\n  // environments.\n  // See https://github.com/flutter/flutter/issues/100327\n  ::operator delete(p);\n}\n\nstd::shared_ptr<ColorSource> ColorSource::MakeLinear(\n    const skity::Vec2 start_point, const skity::Vec2 end_point,\n    uint32_t stop_count, const Color* colors, const float* stops,\n    TileMode tile_mode, const skity::Matrix* matrix) {\n  size_t needed = sizeof(LinearGradientColorSource) +\n                  (stop_count * (sizeof(uint32_t) + sizeof(float)));\n\n  void* storage = ::operator new(needed);\n\n  std::shared_ptr<LinearGradientColorSource> ret;\n  ret.reset(new (storage)\n                LinearGradientColorSource(start_point, end_point, stop_count,\n                                          colors, stops, tile_mode, matrix),\n            DlGradientDeleter);\n  return std::move(ret);\n}\n\nstd::shared_ptr<ColorSource> ColorSource::MakeRadial(\n    skity::Vec2 center, float radius, uint32_t stop_count, const Color* colors,\n    const float* stops, TileMode tile_mode, const skity::Matrix* matrix) {\n  size_t needed = sizeof(RadialGradientColorSource) +\n                  (stop_count * (sizeof(uint32_t) + sizeof(float)));\n\n  void* storage = ::operator new(needed);\n\n  std::shared_ptr<RadialGradientColorSource> ret;\n  ret.reset(new (storage) RadialGradientColorSource(\n                center, radius, stop_count, colors, stops, tile_mode, matrix),\n            DlGradientDeleter);\n  return std::move(ret);\n}\n\nstd::shared_ptr<ColorSource> ColorSource::MakeConical(\n    skity::Vec2 start_center, float start_radius, skity::Vec2 end_center,\n    float end_radius, uint32_t stop_count, const Color* colors,\n    const float* stops, TileMode tile_mode, const skity::Matrix* matrix) {\n  size_t needed = sizeof(ConicalGradientColorSource) +\n                  (stop_count * (sizeof(uint32_t) + sizeof(float)));\n\n  void* storage = ::operator new(needed);\n\n  std::shared_ptr<ConicalGradientColorSource> ret;\n  ret.reset(new (storage) ConicalGradientColorSource(\n                start_center, start_radius, end_center, end_radius, stop_count,\n                colors, stops, tile_mode, matrix),\n            DlGradientDeleter);\n  return std::move(ret);\n}\n\nstd::shared_ptr<ColorSource> ColorSource::MakeSweep(\n    skity::Vec2 center, float start, float end, uint32_t stop_count,\n    const Color* colors, const float* stops, TileMode tile_mode,\n    const skity::Matrix* matrix) {\n  size_t needed = sizeof(SweepGradientColorSource) +\n                  (stop_count * (sizeof(uint32_t) + sizeof(float)));\n\n  void* storage = ::operator new(needed);\n\n  std::shared_ptr<SweepGradientColorSource> ret;\n  ret.reset(new (storage)\n                SweepGradientColorSource(center, start, end, stop_count, colors,\n                                         stops, tile_mode, matrix),\n            DlGradientDeleter);\n  return std::move(ret);\n}\n\nstd::shared_ptr<RuntimeEffectColorSource> ColorSource::MakeRuntimeEffect(\n    fml::RefPtr<RuntimeEffect> runtime_effect,\n    std::vector<std::shared_ptr<ColorSource>> samplers,\n    std::shared_ptr<std::vector<uint8_t>> uniform_data) {\n  FML_DCHECK(uniform_data != nullptr);\n  return std::make_shared<RuntimeEffectColorSource>(\n      std::move(runtime_effect), std::move(samplers), std::move(uniform_data));\n}\n\nstd::shared_ptr<ImageColorSource> ColorSource::MakeImage(\n    fml::RefPtr<PaintImage> image, TileMode tile_mode_x, TileMode tile_mode_y,\n    ImageSampling sampling, const skity::Matrix* matrix) {\n  return std::make_shared<ImageColorSource>(image, tile_mode_x, tile_mode_y,\n                                            sampling, matrix);\n}\n\nstd::shared_ptr<ColorColorSource> ColorSource::MakeColor(Color color) {\n  return std::make_shared<ColorColorSource>(color);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/color_source.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_COLOR_SOURCE_H_\n#define CLAY_GFX_STYLE_COLOR_SOURCE_H_\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/attributes.h\"\n#ifndef ENABLE_SKITY\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#endif\n#include <cstring>\n\n#include \"clay/gfx/paint_image.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/gfx/style/runtime_effect.h\"\n#include \"clay/gfx/style/sampling_options.h\"\n#include \"clay/gfx/style/tile_mode.h\"\n#include \"skity/geometry/matrix.hpp\"\n\nnamespace clay {\n\nclass ColorColorSource;\nclass ImageColorSource;\nclass LinearGradientColorSource;\nclass RadialGradientColorSource;\nclass ConicalGradientColorSource;\nclass SweepGradientColorSource;\nclass RuntimeEffectColorSource;\nclass UnknownColorSource;\n\n// The DisplayList ColorSource class. This class implements all of the\n// facilities and adheres to the design goals of the |DlAttribute| base\n// class.\n//\n// The role of the DlColorSource is to provide color information for\n// the pixels of a rendering operation. The object is essentially the\n// origin of all color being rendered, though its output may be\n// modified or transformed by geometric coverage data, the filter\n// attributes, and the final blend with the pixels in the destination.\n\nenum class ColorSourceType {\n  kColor,\n  kImage,\n  kLinearGradient,\n  kRadialGradient,\n  kConicalGradient,\n  kSweepGradient,\n  kRuntimeEffect,\n  kUnknown\n};\n\nclass ColorSource : public Attribute<ColorSource, GrShader, ColorSourceType> {\n public:\n  static std::shared_ptr<ColorSource> MakeLinear(\n      const skity::Vec2 start_point, const skity::Vec2 end_point,\n      uint32_t stop_count, const Color* colors, const float* stops,\n      TileMode tile_mode, const skity::Matrix* matrix = nullptr);\n\n  static std::shared_ptr<ColorSource> MakeRadial(\n      skity::Vec2 center, float radius, uint32_t stop_count,\n      const Color* colors, const float* stops, TileMode tile_mode,\n      const skity::Matrix* matrix = nullptr);\n\n  static std::shared_ptr<ColorSource> MakeConical(\n      skity::Vec2 start_center, float start_radius, skity::Vec2 end_center,\n      float end_radius, uint32_t stop_count, const Color* colors,\n      const float* stops, TileMode tile_mode,\n      const skity::Matrix* matrix = nullptr);\n\n  static std::shared_ptr<ColorSource> MakeSweep(\n      skity::Vec2 center, float start, float end, uint32_t stop_count,\n      const Color* colors, const float* stops, TileMode tile_mode,\n      const skity::Matrix* matrix = nullptr);\n\n  static std::shared_ptr<RuntimeEffectColorSource> MakeRuntimeEffect(\n      fml::RefPtr<RuntimeEffect> runtime_effect,\n      std::vector<std::shared_ptr<ColorSource>> samplers,\n      std::shared_ptr<std::vector<uint8_t>> uniform_data);\n\n  static std::shared_ptr<ImageColorSource> MakeImage(\n      fml::RefPtr<PaintImage> image, TileMode tile_mode_x, TileMode tile_mode_y,\n      ImageSampling sampling, const skity::Matrix* matrix = nullptr);\n\n  static std::shared_ptr<ColorColorSource> MakeColor(Color color);\n\n  virtual bool is_opaque() const = 0;\n\n  virtual std::shared_ptr<ColorSource> with_sampling(\n      ImageSampling options) const {\n    return shared();\n  }\n\n  // Return a DlColorColorSource pointer to this object iff it is an Color\n  // type of ColorSource, otherwise return nullptr.\n  virtual const ColorColorSource* asColor() const { return nullptr; }\n\n  // Return a DlImageColorSource pointer to this object iff it is an Image\n  // type of ColorSource, otherwise return nullptr.\n  virtual const ImageColorSource* asImage() const { return nullptr; }\n\n  // Return a DlLinearGradientColorSource pointer to this object iff it is a\n  // Linear Gradient type of ColorSource, otherwise return nullptr.\n  virtual const LinearGradientColorSource* asLinearGradient() const {\n    return nullptr;\n  }\n\n  // Return a DlRadialGradientColorSource pointer to this object iff it is a\n  // Radial Gradient type of ColorSource, otherwise return nullptr.\n  virtual const RadialGradientColorSource* asRadialGradient() const {\n    return nullptr;\n  }\n\n  // Return a DlConicalGradientColorSource pointer to this object iff it is a\n  // Conical Gradient type of ColorSource, otherwise return nullptr.\n  virtual const ConicalGradientColorSource* asConicalGradient() const {\n    return nullptr;\n  }\n\n  // Return a DlSweepGradientColorSource pointer to this object iff it is a\n  // Sweep Gradient type of ColorSource, otherwise return nullptr.\n  virtual const SweepGradientColorSource* asSweepGradient() const {\n    return nullptr;\n  }\n\n  virtual const RuntimeEffectColorSource* asRuntimeEffect() const {\n    return nullptr;\n  }\n\n  // If this filter contains images, specifies the owning context for those\n  // images.\n  // Images with a DlImage::OwningContext::kRaster must only call gr_object\n  // on the raster task runner.\n  // A nullopt return means there is no image.\n  virtual std::optional<PaintImage::OwningContext> owning_context() const {\n    return std::nullopt;\n  }\n\n protected:\n  ColorSource() = default;\n\n private:\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ColorSource);\n};\n\nclass ColorColorSource final : public ColorSource {\n public:\n  ColorColorSource(Color color) : color_(color) {}\n\n  std::shared_ptr<ColorSource> shared() const override {\n    return std::make_shared<ColorColorSource>(color_);\n  }\n\n  const ColorColorSource* asColor() const override { return this; }\n\n  ColorSourceType type() const override { return ColorSourceType::kColor; }\n  size_t size() const override { return sizeof(*this); }\n\n  bool is_opaque() const override { return (color_ >> 24) == 255; }\n\n  Color color() const { return color_; }\n\n  GrShaderPtr gr_object() const override {\n#ifndef ENABLE_SKITY\n    return SkShaders::Color(color_);\n#else\n    FML_UNIMPLEMENTED();\n    return nullptr;\n#endif  // ENABLE_SKITY\n  }\n\n protected:\n  bool equals_(ColorSource const& other) const override {\n    FML_DCHECK(other.type() == ColorSourceType::kColor);\n    auto that = static_cast<ColorColorSource const*>(&other);\n    return color_ == that->color_;\n  }\n\n private:\n  Color color_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ColorColorSource);\n};\n\nclass MatrixColorSourceBase : public ColorSource {\n public:\n  const skity::Matrix& matrix() const { return matrix_; }\n  const skity::Matrix* matrix_ptr() const {\n    return matrix_.IsIdentity() ? nullptr : &matrix_;\n  }\n\n protected:\n  MatrixColorSourceBase(const skity::Matrix* matrix)\n      : matrix_(matrix ? *matrix : skity::Matrix()) {}\n\n private:\n  const skity::Matrix matrix_;\n};\n\nclass ImageColorSource final\n    : public fml::RefCountedThreadSafe<ImageColorSource>,\n      public MatrixColorSourceBase {\n public:\n  ImageColorSource(fml::RefPtr<const PaintImage> image,\n                   TileMode horizontal_tile_mode, TileMode vertical_tile_mode,\n                   ImageSampling sampling = ImageSampling::kLinear,\n                   const skity::Matrix* matrix = nullptr)\n      : MatrixColorSourceBase(matrix),\n        image_(image),\n        horizontal_tile_mode_(horizontal_tile_mode),\n        vertical_tile_mode_(vertical_tile_mode),\n        sampling_(sampling) {}\n\n  const ImageColorSource* asImage() const override { return this; }\n\n  std::shared_ptr<ColorSource> shared() const override {\n    return with_sampling(sampling_);\n  }\n\n  std::shared_ptr<ColorSource> with_sampling(\n      ImageSampling sampling) const override {\n    return std::make_shared<ImageColorSource>(image_, horizontal_tile_mode_,\n                                              vertical_tile_mode_, sampling,\n                                              matrix_ptr());\n  }\n\n  ColorSourceType type() const override { return ColorSourceType::kImage; }\n  size_t size() const override { return sizeof(*this); }\n\n  bool is_opaque() const override { return image_->isOpaque(); }\n\n  std::optional<PaintImage::OwningContext> owning_context() const override {\n    return image_->owning_context();\n  }\n\n  fml::RefPtr<const PaintImage> image() const { return image_; }\n  TileMode horizontal_tile_mode() const { return horizontal_tile_mode_; }\n  TileMode vertical_tile_mode() const { return vertical_tile_mode_; }\n  ImageSampling sampling() const { return sampling_; }\n\n  GrShaderPtr gr_object() const override {\n#ifndef ENABLE_SKITY\n    if (!image_->gr_image()) {\n      return nullptr;\n    }\n    auto sk_matrix = clay::ConvertSkityMatrixToSkMatrix(matrix());\n    return image_->gr_image()->makeShader(ToSk(horizontal_tile_mode_),\n                                          ToSk(vertical_tile_mode_),\n                                          ToSk(sampling_), &sk_matrix);\n#else\n    std::shared_ptr<skity::Image> skity_image = image_->gr_image();\n    if (skity_image) {\n      auto matrix = matrix_ptr() ? *matrix_ptr() : skity::Matrix();\n      GrSamplingOptions sampling_options;\n      switch (sampling_) {\n        case ImageSampling::kNearestNeighbor:\n          sampling_options = GrSamplingOptions(skity::FilterMode::kNearest,\n                                               skity::MipmapMode::kNone);\n          break;\n        case ImageSampling::kLinear:\n          sampling_options = GrSamplingOptions(skity::FilterMode::kLinear,\n                                               skity::MipmapMode::kNone);\n          break;\n        case ImageSampling::kMipmapLinear:\n          sampling_options = GrSamplingOptions(skity::FilterMode::kLinear,\n                                               skity::MipmapMode::kLinear);\n          break;\n        case ImageSampling::kCubic:\n          // Info: skity::FilterMode::kCubic is not supported yet.\n          sampling_options = GrSamplingOptions(skity::FilterMode::kNearest,\n                                               skity::MipmapMode::kNone);\n          break;\n      }\n      return skity::Shader::MakeShader(\n          skity_image, sampling_options,\n          static_cast<skity::TileMode>(horizontal_tile_mode_),\n          static_cast<skity::TileMode>(vertical_tile_mode_), matrix);\n    }\n    return nullptr;\n#endif  // ENABLE_SKITY\n  }\n\n protected:\n  bool equals_(ColorSource const& other) const override {\n    FML_DCHECK(other.type() == ColorSourceType::kImage);\n    auto that = static_cast<ImageColorSource const*>(&other);\n    return (image_->Equals(that->image_) && matrix() == that->matrix() &&\n            horizontal_tile_mode_ == that->horizontal_tile_mode_ &&\n            vertical_tile_mode_ == that->vertical_tile_mode_ &&\n            sampling_ == that->sampling_);\n  }\n\n private:\n  fml::RefPtr<const PaintImage> image_;\n  TileMode horizontal_tile_mode_;\n  TileMode vertical_tile_mode_;\n  ImageSampling sampling_;\n\n  friend class ColorSource;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ImageColorSource);\n};\n\nclass GradientColorSourceBase : public MatrixColorSourceBase {\n public:\n  bool is_opaque() const override {\n    if (mode_ == TileMode::kDecal) {\n      return false;\n    }\n    const Color* my_colors = colors();\n    for (uint32_t i = 0; i < stop_count_; i++) {\n      if ((my_colors[i] >> 24) < 255) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  TileMode tile_mode() const { return mode_; }\n  int stop_count() const { return stop_count_; }\n  const Color* colors() const { return reinterpret_cast<const Color*>(pod()); }\n#ifdef ENABLE_SKITY\n  std::vector<skity::Vec4> colors_vec4() const {\n    std::vector<skity::Vec4> skity_colors;\n    skity_colors.reserve(stop_count_);\n    const Color* dl_colors = colors();\n    for (auto i = 0u; i < stop_count_; i++) {\n      float r = dl_colors[i].RedF();\n      float g = dl_colors[i].GreenF();\n      float b = dl_colors[i].BlueF();\n      float a = dl_colors[i].AlphaF();\n      skity_colors.emplace_back(r, g, b, a);\n    }\n    return skity_colors;\n  }\n#endif  // ENABLE_SKITY\n  const float* stops() const {\n    return reinterpret_cast<const float*>(colors() + stop_count());\n  }\n\n protected:\n  GradientColorSourceBase(uint32_t stop_count, TileMode tile_mode,\n                          const skity::Matrix* matrix = nullptr)\n      : MatrixColorSourceBase(matrix),\n        mode_(tile_mode),\n        stop_count_(stop_count) {}\n\n  size_t vector_sizes() const {\n    return stop_count_ * (sizeof(Color) + sizeof(float));\n  }\n\n  virtual const void* pod() const = 0;\n\n  bool base_equals_(GradientColorSourceBase const* other_base) const {\n    if (mode_ != other_base->mode_ || matrix() != other_base->matrix() ||\n        stop_count_ != other_base->stop_count_) {\n      return false;\n    }\n    static_assert(sizeof(colors()[0]) == 4);\n    static_assert(sizeof(stops()[0]) == 4);\n    int num_bytes = stop_count_ * 4;\n    return (memcmp(colors(), other_base->colors(), num_bytes) == 0 &&\n            memcmp(stops(), other_base->stops(), num_bytes) == 0);\n  }\n\n  void store_color_stops(void* pod, const Color* color_data,\n                         const float* stop_data) {\n    Color* color_storage = reinterpret_cast<Color*>(pod);\n    memcpy(color_storage, color_data, stop_count_ * sizeof(*color_data));\n    float* stop_storage = reinterpret_cast<float*>(color_storage + stop_count_);\n    if (stop_data) {\n      memcpy(stop_storage, stop_data, stop_count_ * sizeof(*stop_data));\n    } else {\n      float div = stop_count_ - 1;\n      if (div <= 0) {\n        div = 1;\n      }\n      for (uint32_t i = 0; i < stop_count_; i++) {\n        stop_storage[i] = i / div;\n      }\n    }\n  }\n\n private:\n  TileMode mode_;\n  uint32_t stop_count_;\n\n  friend class ColorSource;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(GradientColorSourceBase);\n};\n\nclass LinearGradientColorSource final : public GradientColorSourceBase {\n public:\n  const LinearGradientColorSource* asLinearGradient() const override {\n    return this;\n  }\n\n  ColorSourceType type() const override {\n    return ColorSourceType::kLinearGradient;\n  }\n  size_t size() const override { return sizeof(*this) + vector_sizes(); }\n\n  std::shared_ptr<ColorSource> shared() const override {\n    return MakeLinear(start_point_, end_point_, stop_count(), colors(), stops(),\n                      tile_mode(), matrix_ptr());\n  }\n\n  const skity::Vec2& start_point() const { return start_point_; }\n  const skity::Vec2& end_point() const { return end_point_; }\n\n  GrShaderPtr gr_object() const override {\n#ifndef ENABLE_SKITY\n    SkPoint pts[] = {SkPoint::Make(start_point_.x, start_point_.y),\n                     SkPoint::Make(end_point_.x, end_point_.y)};\n    const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());\n    auto sk_matrix = clay::ConvertSkityMatrixToSkMatrix(matrix());\n    return SkGradientShader::MakeLinear(pts, sk_colors, stops(), stop_count(),\n                                        ToSk(tile_mode()), 0, &sk_matrix);\n#else\n    skity::Point start_point =\n        skity::Point(start_point_.x, start_point_.y, 0.f, 1.f);\n    skity::Point end_point = skity::Point(end_point_.x, end_point_.y, 0.f, 1.f);\n    skity::Point pts[] = {start_point, end_point};\n    std::vector<skity::Vec4> skity_colors = colors_vec4();\n    auto shader = skity::Shader::MakeLinear(\n        pts, skity_colors.data(), stops(), stop_count(),\n        static_cast<skity::TileMode>(tile_mode()));\n    skity::Matrix skity_matrix = matrix();\n    shader->SetLocalMatrix(skity_matrix);\n    return shader;\n#endif  // ENABLE_SKITY\n  }\n\n protected:\n  virtual const void* pod() const override { return this + 1; }\n\n  bool equals_(ColorSource const& other) const override {\n    FML_DCHECK(other.type() == ColorSourceType::kLinearGradient);\n    auto that = static_cast<LinearGradientColorSource const*>(&other);\n    return (start_point_ == that->start_point_ &&\n            end_point_ == that->end_point_ && base_equals_(that));\n  }\n\n private:\n  LinearGradientColorSource(const skity::Vec2 start_point,\n                            const skity::Vec2 end_point, uint32_t stop_count,\n                            const Color* colors, const float* stops,\n                            TileMode tile_mode,\n                            const skity::Matrix* matrix = nullptr)\n      : GradientColorSourceBase(stop_count, tile_mode, matrix),\n        start_point_(start_point),\n        end_point_(end_point) {\n    store_color_stops(this + 1, colors, stops);\n  }\n\n  LinearGradientColorSource(const LinearGradientColorSource* source)\n      : GradientColorSourceBase(source->stop_count(), source->tile_mode(),\n                                source->matrix_ptr()),\n        start_point_(source->start_point()),\n        end_point_(source->end_point()) {\n    store_color_stops(this + 1, source->colors(), source->stops());\n  }\n\n  skity::Vec2 start_point_;\n  skity::Vec2 end_point_;\n\n  friend class ColorSource;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(LinearGradientColorSource);\n};\n\nclass RadialGradientColorSource final : public GradientColorSourceBase {\n public:\n  const RadialGradientColorSource* asRadialGradient() const override {\n    return this;\n  }\n\n  std::shared_ptr<ColorSource> shared() const override {\n    return MakeRadial(center_, radius_, stop_count(), colors(), stops(),\n                      tile_mode(), matrix_ptr());\n  }\n\n  ColorSourceType type() const override {\n    return ColorSourceType::kRadialGradient;\n  }\n  size_t size() const override { return sizeof(*this) + vector_sizes(); }\n\n  skity::Vec2 center() const { return center_; }\n  float radius() const { return radius_; }\n\n  GrShaderPtr gr_object() const override {\n#ifndef ENABLE_SKITY\n    const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());\n    auto sk_matrix = clay::ConvertSkityMatrixToSkMatrix(matrix());\n    return SkGradientShader::MakeRadial(\n        SkPoint::Make(center_.x, center_.y), radius_, sk_colors, stops(),\n        stop_count(), ToSk(tile_mode()), 0, &sk_matrix);\n#else\n    skity::Point center = skity::Point(center_.x, center_.y, 0.f, 1.f);\n    std::vector<skity::Vec4> skity_colors = colors_vec4();\n    auto shader = skity::Shader::MakeRadial(\n        center, radius_, skity_colors.data(), stops(), stop_count(),\n        static_cast<skity::TileMode>(tile_mode()), 1);\n    if (shader) {\n      skity::Matrix skity_matrix = matrix();\n      shader->SetLocalMatrix(skity_matrix);\n      return shader;\n    }\n    return nullptr;\n#endif  // ENABLE_SKITY\n  }\n\n protected:\n  virtual const void* pod() const override { return this + 1; }\n\n  bool equals_(ColorSource const& other) const override {\n    FML_DCHECK(other.type() == ColorSourceType::kRadialGradient);\n    auto that = static_cast<RadialGradientColorSource const*>(&other);\n    return (center_ == that->center_ && radius_ == that->radius_ &&\n            base_equals_(that));\n  }\n\n private:\n  RadialGradientColorSource(skity::Vec2 center, float radius,\n                            uint32_t stop_count, const Color* colors,\n                            const float* stops, TileMode tile_mode,\n                            const skity::Matrix* matrix = nullptr)\n      : GradientColorSourceBase(stop_count, tile_mode, matrix),\n        center_(center),\n        radius_(radius) {\n    store_color_stops(this + 1, colors, stops);\n  }\n\n  RadialGradientColorSource(const RadialGradientColorSource* source)\n      : GradientColorSourceBase(source->stop_count(), source->tile_mode(),\n                                source->matrix_ptr()),\n        center_(source->center()),\n        radius_(source->radius()) {\n    store_color_stops(this + 1, source->colors(), source->stops());\n  }\n\n  skity::Vec2 center_;\n  float radius_;\n\n  friend class ColorSource;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(RadialGradientColorSource);\n};\n\nclass ConicalGradientColorSource final : public GradientColorSourceBase {\n public:\n  const ConicalGradientColorSource* asConicalGradient() const override {\n    return this;\n  }\n\n  std::shared_ptr<ColorSource> shared() const override {\n    return MakeConical(start_center_, start_radius_, end_center_, end_radius_,\n                       stop_count(), colors(), stops(), tile_mode(),\n                       matrix_ptr());\n  }\n\n  ColorSourceType type() const override {\n    return ColorSourceType::kConicalGradient;\n  }\n  size_t size() const override { return sizeof(*this) + vector_sizes(); }\n\n  skity::Vec2 start_center() const { return start_center_; }\n  float start_radius() const { return start_radius_; }\n  skity::Vec2 end_center() const { return end_center_; }\n  float end_radius() const { return end_radius_; }\n\n  GrShaderPtr gr_object() const override {\n#ifndef ENABLE_SKITY\n    const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());\n    auto sk_matrix = clay::ConvertSkityMatrixToSkMatrix(matrix());\n    return SkGradientShader::MakeTwoPointConical(\n        SkPoint::Make(start_center_.x, start_center_.y), start_radius_,\n        SkPoint::Make(end_center_.x, end_center_.y), end_radius_, sk_colors,\n        stops(), stop_count(), ToSk(tile_mode()), 0, &sk_matrix);\n#else\n    skity::Point start =\n        skity::Point(start_center_.x, start_center_.y, 0.f, 1.f);\n    skity::Point end = skity::Point(end_center_.x, end_center_.y, 0.f, 1.f);\n    std::vector<skity::Vec4> skity_colors = colors_vec4();\n    auto shader = skity::Shader::MakeTwoPointConical(\n        start, start_radius_, end, end_radius_, skity_colors.data(), stops(),\n        stop_count(), static_cast<skity::TileMode>(tile_mode()));\n    skity::Matrix skity_matrix = matrix();\n    shader->SetLocalMatrix(skity_matrix);\n    return shader;\n#endif  // ENABLE_SKITY\n  }\n\n protected:\n  virtual const void* pod() const override { return this + 1; }\n\n  bool equals_(ColorSource const& other) const override {\n    FML_DCHECK(other.type() == ColorSourceType::kConicalGradient);\n    auto that = static_cast<ConicalGradientColorSource const*>(&other);\n    return (start_center_ == that->start_center_ &&\n            start_radius_ == that->start_radius_ &&\n            end_center_ == that->end_center_ &&\n            end_radius_ == that->end_radius_ && base_equals_(that));\n  }\n\n private:\n  ConicalGradientColorSource(skity::Vec2 start_center, float start_radius,\n                             skity::Vec2 end_center, float end_radius,\n                             uint32_t stop_count, const Color* colors,\n                             const float* stops, TileMode tile_mode,\n                             const skity::Matrix* matrix = nullptr)\n      : GradientColorSourceBase(stop_count, tile_mode, matrix),\n        start_center_(start_center),\n        start_radius_(start_radius),\n        end_center_(end_center),\n        end_radius_(end_radius) {\n    store_color_stops(this + 1, colors, stops);\n  }\n\n  ConicalGradientColorSource(const ConicalGradientColorSource* source)\n      : GradientColorSourceBase(source->stop_count(), source->tile_mode(),\n                                source->matrix_ptr()),\n        start_center_(source->start_center()),\n        start_radius_(source->start_radius()),\n        end_center_(source->end_center()),\n        end_radius_(source->end_radius()) {\n    store_color_stops(this + 1, source->colors(), source->stops());\n  }\n\n  skity::Vec2 start_center_;\n  float start_radius_;\n  skity::Vec2 end_center_;\n  float end_radius_;\n\n  friend class ColorSource;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(ConicalGradientColorSource);\n};\n\nclass SweepGradientColorSource final : public GradientColorSourceBase {\n public:\n  const SweepGradientColorSource* asSweepGradient() const override {\n    return this;\n  }\n\n  std::shared_ptr<ColorSource> shared() const override {\n    return MakeSweep(center_, start_, end_, stop_count(), colors(), stops(),\n                     tile_mode(), matrix_ptr());\n  }\n\n  ColorSourceType type() const override {\n    return ColorSourceType::kSweepGradient;\n  }\n  size_t size() const override { return sizeof(*this) + vector_sizes(); }\n\n  skity::Vec2 center() const { return center_; }\n  float start() const { return start_; }\n  float end() const { return end_; }\n\n  GrShaderPtr gr_object() const override {\n#ifndef ENABLE_SKITY\n    const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());\n    auto sk_matrix = clay::ConvertSkityMatrixToSkMatrix(matrix());\n    return SkGradientShader::MakeSweep(center_.x, center_.y, sk_colors, stops(),\n                                       stop_count(), ToSk(tile_mode()), start_,\n                                       end_, 0, &sk_matrix);\n#else\n    std::vector<skity::Vec4> skity_colors = colors_vec4();\n    auto shader =\n        skity::Shader::MakeSweep(center_.x, center_.y, start_, end_,\n                                 skity_colors.data(), stops(), stop_count());\n    skity::Matrix skity_matrix = matrix();\n    shader->SetLocalMatrix(skity_matrix);\n    return shader;\n#endif  // ENABLE_SKITY\n  }\n\n protected:\n  virtual const void* pod() const override { return this + 1; }\n\n  bool equals_(ColorSource const& other) const override {\n    FML_DCHECK(other.type() == ColorSourceType::kSweepGradient);\n    auto that = static_cast<SweepGradientColorSource const*>(&other);\n    return (center_ == that->center_ && start_ == that->start_ &&\n            end_ == that->end_ && base_equals_(that));\n  }\n\n private:\n  SweepGradientColorSource(skity::Vec2 center, float start, float end,\n                           uint32_t stop_count, const Color* colors,\n                           const float* stops, TileMode tile_mode,\n                           const skity::Matrix* matrix = nullptr)\n      : GradientColorSourceBase(stop_count, tile_mode, matrix),\n        center_(center),\n        start_(start),\n        end_(end) {\n    store_color_stops(this + 1, colors, stops);\n  }\n\n  SweepGradientColorSource(const SweepGradientColorSource* source)\n      : GradientColorSourceBase(source->stop_count(), source->tile_mode(),\n                                source->matrix_ptr()),\n        center_(source->center()),\n        start_(source->start()),\n        end_(source->end()) {\n    store_color_stops(this + 1, source->colors(), source->stops());\n  }\n\n  skity::Vec2 center_;\n  float start_;\n  float end_;\n\n  friend class ColorSource;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(SweepGradientColorSource);\n};\n\nclass RuntimeEffectColorSource final : public ColorSource {\n public:\n  RuntimeEffectColorSource(fml::RefPtr<RuntimeEffect> runtime_effect,\n                           std::vector<std::shared_ptr<ColorSource>> samplers,\n                           std::shared_ptr<std::vector<uint8_t>> uniform_data)\n      : runtime_effect_(std::move(runtime_effect)),\n        samplers_(std::move(samplers)),\n        uniform_data_(std::move(uniform_data)) {}\n\n  const RuntimeEffectColorSource* asRuntimeEffect() const override {\n    return this;\n  }\n\n  std::shared_ptr<ColorSource> shared() const override {\n    return std::make_shared<RuntimeEffectColorSource>(runtime_effect_,\n                                                      samplers_, uniform_data_);\n  }\n\n  ColorSourceType type() const override {\n    return ColorSourceType::kRuntimeEffect;\n  }\n  size_t size() const override { return sizeof(*this); }\n\n  bool is_opaque() const override { return false; }\n\n  const fml::RefPtr<RuntimeEffect> runtime_effect() const {\n    return runtime_effect_;\n  }\n  const std::vector<std::shared_ptr<ColorSource>> samplers() const {\n    return samplers_;\n  }\n  const std::shared_ptr<std::vector<uint8_t>> uniform_data() const {\n    return uniform_data_;\n  }\n\n  GrShaderPtr gr_object() const override {\n#ifndef ENABLE_SKITY\n    if (!runtime_effect_) {\n      return nullptr;\n    }\n    if (!runtime_effect_->skia_runtime_effect()) {\n      return nullptr;\n    }\n    std::vector<sk_sp<SkShader>> sk_samplers(samplers_.size());\n    for (size_t i = 0; i < samplers_.size(); i++) {\n      auto sampler = samplers_[i];\n      if (sampler == nullptr) {\n        return nullptr;\n      }\n      sk_samplers[i] = sampler->gr_object();\n    }\n\n    auto ref = new std::shared_ptr<std::vector<uint8_t>>(uniform_data_);\n    auto uniform_data = SkData::MakeWithProc(\n        uniform_data_->data(), uniform_data_->size(),\n        [](const void* ptr, void* context) {\n          delete reinterpret_cast<std::shared_ptr<std::vector<uint8_t>>*>(\n              context);\n        },\n        ref);\n\n    return runtime_effect_->skia_runtime_effect()->makeShader(\n        uniform_data, sk_samplers.data(), sk_samplers.size());\n#else\n    FML_UNIMPLEMENTED();\n    return nullptr;\n#endif  // ENABLE_SKITY\n  }\n\n protected:\n  bool equals_(ColorSource const& other) const override {\n    FML_DCHECK(other.type() == ColorSourceType::kRuntimeEffect);\n    auto that = static_cast<RuntimeEffectColorSource const*>(&other);\n    if (runtime_effect_ != that->runtime_effect_) {\n      return false;\n    }\n    if (uniform_data_ != that->uniform_data_) {\n      return false;\n    }\n    if (samplers_.size() != that->samplers_.size()) {\n      return false;\n    }\n    for (size_t i = 0; i < samplers_.size(); i++) {\n      if (samplers_[i] != that->samplers_[i]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n private:\n  fml::RefPtr<RuntimeEffect> runtime_effect_;\n  std::vector<std::shared_ptr<ColorSource>> samplers_;\n  std::shared_ptr<std::vector<uint8_t>> uniform_data_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(RuntimeEffectColorSource);\n};\n\nclass UnknownColorSource final : public ColorSource {\n public:\n  UnknownColorSource(GrShaderPtr shader) : sk_shader_(std::move(shader)) {}\n\n  std::shared_ptr<ColorSource> shared() const override {\n    return std::make_shared<UnknownColorSource>(sk_shader_);\n  }\n\n  ColorSourceType type() const override { return ColorSourceType::kUnknown; }\n  size_t size() const override { return sizeof(*this); }\n\n  bool is_opaque() const override { return SHADER_IS_OPAQUE(sk_shader_); }\n\n  GrShaderPtr gr_object() const override { return sk_shader_; }\n\n protected:\n  bool equals_(ColorSource const& other) const override {\n    FML_DCHECK(other.type() == ColorSourceType::kUnknown);\n    auto that = static_cast<UnknownColorSource const*>(&other);\n    return (sk_shader_ == that->sk_shader_);\n  }\n\n private:\n  GrShaderPtr sk_shader_;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(UnknownColorSource);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_COLOR_SOURCE_H_\n"
  },
  {
    "path": "clay/gfx/style/color_source_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <vector>\n\n#include \"clay/gfx/attributes_testing.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkString.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\nnamespace clay {\nnamespace testing {\n\nstatic fml::RefPtr<DlImage> MakeTestImage(int w, int h, SkColor color) {\n  sk_sp<SkSurface> surface;\n  if (SkColorGetA(color) < 255) {\n    surface = SkSurface::MakeRasterN32Premul(w, h);\n  } else {\n    SkImageInfo info =\n        SkImageInfo::MakeN32(w, h, SkAlphaType::kOpaque_SkAlphaType);\n    surface = SkSurface::MakeRaster(info);\n  }\n  SkCanvas* canvas = surface->getCanvas();\n  canvas->drawColor(color);\n  return DlImage::Make(surface->makeImageSnapshot());\n}\n\nstatic const fml::RefPtr<DlRuntimeEffect> kTestRuntimeEffect1 =\n    DlRuntimeEffect::MakeSkia(\n        SkRuntimeEffect::MakeForShader(\n            SkString(\"vec4 main(vec2 p) { return vec4(0); }\"))\n            .effect);\nstatic const fml::RefPtr<DlRuntimeEffect> kTestRuntimeEffect2 =\n    DlRuntimeEffect::MakeSkia(\n        SkRuntimeEffect::MakeForShader(\n            SkString(\"vec4 main(vec2 p) { return vec4(1); }\"))\n            .effect);\n\nstatic const fml::RefPtr<DlImage> kTestImage1 =\n    MakeTestImage(10, 10, SK_ColorGREEN);\nstatic const fml::RefPtr<DlImage> kTestAlphaImage1 =\n    MakeTestImage(10, 10, SK_ColorTRANSPARENT);\n// clang-format off\nstatic const skity::Matrix kTestMatrix1 =\n    skity::Matrix(2, 0, 0, 0,\n                  0, 3, 0, 0,\n                  0, 0, 1, 0,\n                  10,  12, 0, 1);\nstatic const skity::Matrix kTestMatrix2 =\n// 4 0 15 0 7 17 0 0 1\n    skity::Matrix(4, 0, 0, 0,\n                  0, 7, 0, 0,\n                  0, 0, 1, 0,\n                  15, 17, 0, 1);\n// clang-format on\nstatic constexpr int kTestStopCount = 3;\nstatic constexpr DlColor kTestColors[kTestStopCount] = {\n    DlColor::kRed(),\n    DlColor::kGreen(),\n    DlColor::kBlue(),\n};\nstatic const DlColor kTestAlphaColors[kTestStopCount] = {\n    DlColor::kBlue().withAlpha(0x7F),\n    DlColor::kRed().withAlpha(0x2F),\n    DlColor::kGreen().withAlpha(0xCF),\n};\nstatic constexpr float kTestStops[kTestStopCount] = {\n    0.0f,\n    0.7f,\n    1.0f,\n};\nstatic constexpr float kTestStops2[kTestStopCount] = {\n    0.0f,\n    0.3f,\n    1.0f,\n};\nstatic constexpr skity::Vec2 kTestPoints[2] = {\n    skity::Vec2(5, 15),\n    skity::Vec2(7, 18),\n};\nstatic constexpr skity::Vec2 kTestPoints2[2] = {\n    skity::Vec2(100, 115),\n    skity::Vec2(107, 118),\n};\nstatic const sk_sp<SkShader> kShaderA = SkShaders::Color(SK_ColorRED);\nstatic const sk_sp<SkShader> kShaderB = SkShaders::Color(SK_ColorBLUE);\nstatic const sk_sp<SkShader> kTestUnknownShader =\n    SkShaders::Blend(SkBlendMode::kOverlay, kShaderA, kShaderB);\nstatic const sk_sp<SkShader> kTestAlphaUnknownShader =\n    SkShaders::Blend(SkBlendMode::kDstOut, kShaderA, kShaderB);\n\nTEST(DisplayListColorSource, FromSkiaColorShader) {\n  // We cannot read back the matrix parameter from a Skia LinearGradient\n  // so we conservatively use an UnknownColorSource wrapper so as to not\n  // lose any data. Note that the Skia Color shader end is read back from\n  // the Skia asAGradient() method so while this type of color source\n  // does not really need the matrix, we represent all of the gradient\n  // sources using an unknown source.\n  // Note that this shader should never really happen in practice as it\n  // represents a degenerate gradient that collapsed to a single color.\n  std::shared_ptr<ColorSource> source = ColorSource::MakeColor(Color::kBlue());\n  ASSERT_EQ(source->type(), DlColorSourceType::kColor);\n\n  ASSERT_EQ(source->asColor(), source.get());\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), nullptr);\n}\n\nTEST(DisplayListColorSource, FromSkiaImageShader) {\n  std::shared_ptr<DlColorSource> source =\n      ColorSource::MakeImage(kTestImage1, TileMode::kClamp, TileMode::kClamp,\n                             ImageSampling::kLinear, &kTestMatrix1);\n  DlImageColorSource dl_source(kTestImage1, DlTileMode::kClamp,\n                               DlTileMode::kClamp, DlImageSampling::kLinear,\n                               &kTestMatrix1);\n  ASSERT_EQ(source->type(), DlColorSourceType::kImage);\n  ASSERT_EQ(*source->asImage(), dl_source);\n  ASSERT_TRUE(source->asImage()->image()->Equals(kTestImage1));\n  ASSERT_TRUE(kTestImage1->Equals(source->asImage()->image()));\n  ASSERT_EQ(source->asImage()->matrix(), kTestMatrix1);\n  ASSERT_EQ(source->asImage()->horizontal_tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asImage()->vertical_tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asImage()->sampling(), DlImageSampling::kLinear);\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), nullptr);\n  ASSERT_EQ(source->asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, FromSkiaLinearGradient) {\n  // We cannot read back the matrix parameter from a Skia LinearGradient\n  // so we conservatively use an UnknownColorSource wrapper so as to not\n  // lose any data.\n  const Color* colors = reinterpret_cast<const Color*>(kTestColors);\n  std::shared_ptr<ColorSource> source = ColorSource::MakeLinear(\n      skity::Vec2(kTestPoints[0].x, kTestPoints[0].y),\n      skity::Vec2(kTestPoints[1].x, kTestPoints[1].y), kTestStopCount, colors,\n      kTestStops, TileMode::kClamp);\n  ASSERT_EQ(source->type(), DlColorSourceType::kLinearGradient);\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), source.get());\n  ASSERT_EQ(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), nullptr);\n}\n\nTEST(DisplayListColorSource, FromSkiaRadialGradient) {\n  // We cannot read back the matrix parameter from a Skia RadialGradient\n  // so we conservatively use an UnknownColorSource wrapper so as to not\n  // lose any data.\n  const Color* colors = reinterpret_cast<const Color*>(kTestColors);\n  std::shared_ptr<DlColorSource> source = ColorSource::MakeRadial(\n      skity::Vec2(kTestPoints[0].x, kTestPoints[0].y), 10.0, kTestStopCount,\n      colors, kTestStops, TileMode::kClamp);\n  ASSERT_EQ(source->type(), DlColorSourceType::kRadialGradient);\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), source.get());\n  ASSERT_EQ(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), nullptr);\n  ASSERT_EQ(source->asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, FromSkiaConicalGradient) {\n  // We cannot read back the matrix parameter from a Skia ConicalGradient\n  // so we conservatively use an UnknownColorSource wrapper so as to not\n  // lose any data.\n  const Color* colors = reinterpret_cast<const Color*>(kTestColors);\n  std::shared_ptr<DlColorSource> source = ColorSource::MakeConical(\n      skity::Vec2(kTestPoints[0].x, kTestPoints[0].y), 10.0,\n      skity::Vec2(kTestPoints[1].x, kTestPoints[1].y), 20.0, kTestStopCount,\n      colors, kTestStops, TileMode::kClamp);\n  ASSERT_EQ(source->type(), DlColorSourceType::kConicalGradient);\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), source.get());\n  ASSERT_EQ(source->asSweepGradient(), nullptr);\n  ASSERT_EQ(source->asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, FromSkiaSweepGradient) {\n  // We cannot read back the matrix parameter, nor the sweep parameters from a\n  // Skia SweepGradient so we conservatively use an UnknownColorSource wrapper\n  // so as to not lose any data.\n  const Color* colors = reinterpret_cast<const Color*>(kTestColors);\n  std::shared_ptr<DlColorSource> source = ColorSource::MakeSweep(\n      skity::Vec2{kTestPoints[0].x, kTestPoints[0].y}, 0, 360, kTestStopCount,\n      colors, kTestStops, TileMode::kClamp);\n  ASSERT_EQ(source->type(), DlColorSourceType::kSweepGradient);\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), source.get());\n  ASSERT_EQ(source->asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, FromSkiaUnrecognizedShader) {\n  std::shared_ptr<DlColorSource> source =\n      std::make_shared<UnknownColorSource>(kTestUnknownShader);\n  ASSERT_EQ(source->type(), DlColorSourceType::kUnknown);\n  ASSERT_EQ(source->gr_object(), kTestUnknownShader);\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), nullptr);\n  ASSERT_EQ(source->asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, ColorConstructor) {\n  DlColorColorSource source(SK_ColorRED);\n}\n\nTEST(DisplayListColorSource, ColorShared) {\n  DlColorColorSource source(SK_ColorRED);\n  ASSERT_NE(source.shared().get(), &source);\n  ASSERT_EQ(*source.shared(), source);\n}\n\nTEST(DisplayListColorSource, ColorAsColor) {\n  DlColorColorSource source(SK_ColorRED);\n  ASSERT_NE(source.asColor(), nullptr);\n  ASSERT_EQ(source.asColor(), &source);\n\n  ASSERT_EQ(source.asImage(), nullptr);\n  ASSERT_EQ(source.asLinearGradient(), nullptr);\n  ASSERT_EQ(source.asRadialGradient(), nullptr);\n  ASSERT_EQ(source.asConicalGradient(), nullptr);\n  ASSERT_EQ(source.asSweepGradient(), nullptr);\n  ASSERT_EQ(source.asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, ColorContents) {\n  DlColorColorSource source(SK_ColorRED);\n  ASSERT_EQ(source.color(), SK_ColorRED);\n  ASSERT_EQ(source.is_opaque(), true);\n  for (int i = 0; i < 255; i++) {\n    SkColor alpha_color = SkColorSetA(SK_ColorRED, i);\n    DlColorColorSource alpha_source(alpha_color);\n    ASSERT_EQ(alpha_source.color(), alpha_color);\n    ASSERT_EQ(alpha_source.is_opaque(), false);\n  }\n}\n\nTEST(DisplayListColorSource, ColorEquals) {\n  DlColorColorSource source1(SK_ColorRED);\n  DlColorColorSource source2(SK_ColorRED);\n  TestEquals(source1, source2);\n}\n\nTEST(DisplayListColorSource, ColorNotEquals) {\n  DlColorColorSource source1(SK_ColorRED);\n  DlColorColorSource source2(SK_ColorBLUE);\n  TestNotEquals(source1, source2, \"Color differs\");\n}\n\nTEST(DisplayListColorSource, ImageConstructor) {\n  DlImageColorSource source(kTestImage1, DlTileMode::kClamp, DlTileMode::kClamp,\n                            DlImageSampling::kLinear, &kTestMatrix1);\n}\n\nTEST(DisplayListColorSource, ImageShared) {\n  DlImageColorSource source(kTestImage1, DlTileMode::kClamp, DlTileMode::kClamp,\n                            DlImageSampling::kLinear, &kTestMatrix1);\n  ASSERT_NE(source.shared().get(), &source);\n  ASSERT_EQ(*source.shared(), source);\n}\n\nTEST(DisplayListColorSource, ImageAsImage) {\n  DlImageColorSource source(kTestImage1, DlTileMode::kClamp, DlTileMode::kClamp,\n                            DlImageSampling::kLinear, &kTestMatrix1);\n  ASSERT_NE(source.asImage(), nullptr);\n  ASSERT_EQ(source.asImage(), &source);\n\n  ASSERT_EQ(source.asColor(), nullptr);\n  ASSERT_EQ(source.asLinearGradient(), nullptr);\n  ASSERT_EQ(source.asRadialGradient(), nullptr);\n  ASSERT_EQ(source.asConicalGradient(), nullptr);\n  ASSERT_EQ(source.asSweepGradient(), nullptr);\n}\n\nTEST(DisplayListColorSource, ImageContents) {\n  DlImageColorSource source(kTestImage1, DlTileMode::kRepeat,\n                            DlTileMode::kMirror, DlImageSampling::kLinear,\n                            &kTestMatrix1);\n  ASSERT_EQ(source.image(), kTestImage1);\n  ASSERT_EQ(source.horizontal_tile_mode(), DlTileMode::kRepeat);\n  ASSERT_EQ(source.vertical_tile_mode(), DlTileMode::kMirror);\n  ASSERT_EQ(source.sampling(), DlImageSampling::kLinear);\n  ASSERT_EQ(source.matrix(), kTestMatrix1);\n  ASSERT_EQ(source.is_opaque(), true);\n}\n\nTEST(DisplayListColorSource, AlphaImageContents) {\n  DlImageColorSource source(kTestAlphaImage1, DlTileMode::kRepeat,\n                            DlTileMode::kMirror, DlImageSampling::kLinear,\n                            &kTestMatrix1);\n  ASSERT_EQ(source.image(), kTestAlphaImage1);\n  ASSERT_EQ(source.horizontal_tile_mode(), DlTileMode::kRepeat);\n  ASSERT_EQ(source.vertical_tile_mode(), DlTileMode::kMirror);\n  ASSERT_EQ(source.sampling(), DlImageSampling::kLinear);\n  ASSERT_EQ(source.matrix(), kTestMatrix1);\n  ASSERT_EQ(source.is_opaque(), false);\n}\n\nTEST(DisplayListColorSource, ImageEquals) {\n  DlImageColorSource source1(kTestImage1, DlTileMode::kClamp,\n                             DlTileMode::kMirror, DlImageSampling::kLinear,\n                             &kTestMatrix1);\n  DlImageColorSource source2(kTestImage1, DlTileMode::kClamp,\n                             DlTileMode::kMirror, DlImageSampling::kLinear,\n                             &kTestMatrix1);\n  TestEquals(source1, source2);\n}\n\nTEST(DisplayListColorSource, ImageNotEquals) {\n  DlImageColorSource source1(kTestImage1, DlTileMode::kClamp,\n                             DlTileMode::kMirror, DlImageSampling::kLinear,\n                             &kTestMatrix1);\n  {\n    DlImageColorSource source2(kTestAlphaImage1, DlTileMode::kClamp,\n                               DlTileMode::kMirror, DlImageSampling::kLinear,\n                               &kTestMatrix1);\n    TestNotEquals(source1, source2, \"Image differs\");\n  }\n  {\n    DlImageColorSource source2(kTestImage1, DlTileMode::kRepeat,\n                               DlTileMode::kMirror, DlImageSampling::kLinear,\n                               &kTestMatrix1);\n    TestNotEquals(source1, source2, \"hTileMode differs\");\n  }\n  {\n    DlImageColorSource source2(kTestImage1, DlTileMode::kClamp,\n                               DlTileMode::kRepeat, DlImageSampling::kLinear,\n                               &kTestMatrix1);\n    TestNotEquals(source1, source2, \"vTileMode differs\");\n  }\n  {\n    DlImageColorSource source2(kTestImage1, DlTileMode::kClamp,\n                               DlTileMode::kMirror, DlImageSampling::kCubic,\n                               &kTestMatrix1);\n    TestNotEquals(source1, source2, \"Sampling differs\");\n  }\n  {\n    DlImageColorSource source2(kTestImage1, DlTileMode::kClamp,\n                               DlTileMode::kMirror, DlImageSampling::kLinear,\n                               &kTestMatrix2);\n    TestNotEquals(source1, source2, \"Matrix differs\");\n  }\n}\n\nTEST(DisplayListColorSource, LinearGradientConstructor) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeLinear(\n      kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n}\n\nTEST(DisplayListColorSource, LinearGradientShared) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeLinear(\n      kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_NE(source->shared().get(), source.get());\n  ASSERT_EQ(*source->shared().get(), *source.get());\n}\n\nTEST(DisplayListColorSource, LinearGradientAsLinear) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeLinear(\n      kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_NE(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), source.get());\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), nullptr);\n  ASSERT_EQ(source->asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, LinearGradientContents) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeLinear(\n      kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_EQ(source->asLinearGradient()->start_point(), kTestPoints[0]);\n  ASSERT_EQ(source->asLinearGradient()->end_point(), kTestPoints[1]);\n  ASSERT_EQ(source->asLinearGradient()->stop_count(), kTestStopCount);\n  for (int i = 0; i < kTestStopCount; i++) {\n    ASSERT_EQ(source->asLinearGradient()->colors()[i], kTestColors[i]);\n    ASSERT_EQ(source->asLinearGradient()->stops()[i], kTestStops[i]);\n  }\n  ASSERT_EQ(source->asLinearGradient()->tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asLinearGradient()->matrix(), kTestMatrix1);\n  ASSERT_EQ(source->is_opaque(), true);\n}\n\nTEST(DisplayListColorSource, AlphaLinearGradientContents) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeLinear(\n      kTestPoints[0], kTestPoints[1], kTestStopCount, kTestAlphaColors,\n      kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_EQ(source->asLinearGradient()->start_point(), kTestPoints[0]);\n  ASSERT_EQ(source->asLinearGradient()->end_point(), kTestPoints[1]);\n  ASSERT_EQ(source->asLinearGradient()->stop_count(), kTestStopCount);\n  for (int i = 0; i < kTestStopCount; i++) {\n    ASSERT_EQ(source->asLinearGradient()->colors()[i], kTestAlphaColors[i]);\n    ASSERT_EQ(source->asLinearGradient()->stops()[i], kTestStops[i]);\n  }\n  ASSERT_EQ(source->asLinearGradient()->tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asLinearGradient()->matrix(), kTestMatrix1);\n  ASSERT_EQ(source->is_opaque(), false);\n}\n\nTEST(DisplayListColorSource, LinearGradientEquals) {\n  std::shared_ptr<DlColorSource> source1 = DlColorSource::MakeLinear(\n      kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeLinear(\n      kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  TestEquals(*source1, *source2);\n}\n\nTEST(DisplayListColorSource, LinearGradientNotEquals) {\n  std::shared_ptr<DlColorSource> source1 = DlColorSource::MakeLinear(\n      kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeLinear(\n        kTestPoints2[0], kTestPoints[1], kTestStopCount, kTestColors,\n        kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Point 0 differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeLinear(\n        kTestPoints[0], kTestPoints2[1], kTestStopCount, kTestColors,\n        kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Point 1 differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeLinear(\n        kTestPoints[0], kTestPoints[1], 2, kTestColors, kTestStops,  //\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Stop count differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeLinear(\n        kTestPoints[0], kTestPoints[1], kTestStopCount, kTestAlphaColors,\n        kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Colors differ\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeLinear(\n        kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors,\n        kTestStops2, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Stops differ\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeLinear(\n        kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kMirror, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Tile Mode differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeLinear(\n        kTestPoints[0], kTestPoints[1], kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix2);\n    TestNotEquals(*source1, *source2, \"Matrix differs\");\n  }\n}\n\nTEST(DisplayListColorSource, RadialGradientConstructor) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeRadial(\n      kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n}\n\nTEST(DisplayListColorSource, RadialGradientShared) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeRadial(\n      kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_NE(source->shared().get(), source.get());\n  ASSERT_EQ(*source->shared().get(), *source.get());\n}\n\nTEST(DisplayListColorSource, RadialGradientAsRadial) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeRadial(\n      kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_NE(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), source.get());\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), nullptr);\n  ASSERT_EQ(source->asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, RadialGradientContents) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeRadial(\n      kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_EQ(source->asRadialGradient()->center(), kTestPoints[0]);\n  ASSERT_EQ(source->asRadialGradient()->radius(), 10.0);\n  ASSERT_EQ(source->asRadialGradient()->stop_count(), kTestStopCount);\n  for (int i = 0; i < kTestStopCount; i++) {\n    ASSERT_EQ(source->asRadialGradient()->colors()[i], kTestColors[i]);\n    ASSERT_EQ(source->asRadialGradient()->stops()[i], kTestStops[i]);\n  }\n  ASSERT_EQ(source->asRadialGradient()->tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asRadialGradient()->matrix(), kTestMatrix1);\n  ASSERT_EQ(source->is_opaque(), true);\n}\n\nTEST(DisplayListColorSource, AlphaRadialGradientContents) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeRadial(\n      kTestPoints[0], 10.0, kTestStopCount, kTestAlphaColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_EQ(source->asRadialGradient()->center(), kTestPoints[0]);\n  ASSERT_EQ(source->asRadialGradient()->radius(), 10.0);\n  ASSERT_EQ(source->asRadialGradient()->stop_count(), kTestStopCount);\n  for (int i = 0; i < kTestStopCount; i++) {\n    ASSERT_EQ(source->asRadialGradient()->colors()[i], kTestAlphaColors[i]);\n    ASSERT_EQ(source->asRadialGradient()->stops()[i], kTestStops[i]);\n  }\n  ASSERT_EQ(source->asRadialGradient()->tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asRadialGradient()->matrix(), kTestMatrix1);\n  ASSERT_EQ(source->is_opaque(), false);\n}\n\nTEST(DisplayListColorSource, RadialGradientEquals) {\n  std::shared_ptr<DlColorSource> source1 = DlColorSource::MakeRadial(\n      kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeRadial(\n      kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  TestEquals(*source1, *source2);\n}\n\nTEST(DisplayListColorSource, RadialGradientNotEquals) {\n  std::shared_ptr<DlColorSource> source1 = DlColorSource::MakeRadial(\n      kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeRadial(\n        kTestPoints2[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Center differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeRadial(\n        kTestPoints[0], 20.0, kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Radius differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeRadial(\n        kTestPoints[0], 10.0, 2, kTestColors, kTestStops,  //\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Stop count differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeRadial(\n        kTestPoints[0], 10.0, kTestStopCount, kTestAlphaColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Colors differ\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeRadial(\n        kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops2,\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Stops differ\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeRadial(\n        kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kMirror, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Tile Mode differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeRadial(\n        kTestPoints[0], 10.0, kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix2);\n    TestNotEquals(*source1, *source2, \"Matrix differs\");\n  }\n}\n\nTEST(DisplayListColorSource, ConicalGradientConstructor) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeConical(\n      kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n      kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n}\n\nTEST(DisplayListColorSource, ConicalGradientShared) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeConical(\n      kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n      kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_NE(source->shared().get(), source.get());\n  ASSERT_EQ(*source->shared().get(), *source.get());\n}\n\nTEST(DisplayListColorSource, ConicalGradientAsConical) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeConical(\n      kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n      kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_NE(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), source.get());\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), nullptr);\n  ASSERT_EQ(source->asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, ConicalGradientContents) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeConical(\n      kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n      kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_EQ(source->asConicalGradient()->start_center(), kTestPoints[0]);\n  ASSERT_EQ(source->asConicalGradient()->start_radius(), 10.0);\n  ASSERT_EQ(source->asConicalGradient()->end_center(), kTestPoints[1]);\n  ASSERT_EQ(source->asConicalGradient()->end_radius(), 20.0);\n  ASSERT_EQ(source->asConicalGradient()->stop_count(), kTestStopCount);\n  for (int i = 0; i < kTestStopCount; i++) {\n    ASSERT_EQ(source->asConicalGradient()->colors()[i], kTestColors[i]);\n    ASSERT_EQ(source->asConicalGradient()->stops()[i], kTestStops[i]);\n  }\n  ASSERT_EQ(source->asConicalGradient()->tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asConicalGradient()->matrix(), kTestMatrix1);\n  ASSERT_EQ(source->is_opaque(), true);\n}\n\nTEST(DisplayListColorSource, AlphaConicalGradientContents) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeConical(\n      kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount,\n      kTestAlphaColors, kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_EQ(source->asConicalGradient()->start_center(), kTestPoints[0]);\n  ASSERT_EQ(source->asConicalGradient()->start_radius(), 10.0);\n  ASSERT_EQ(source->asConicalGradient()->end_center(), kTestPoints[1]);\n  ASSERT_EQ(source->asConicalGradient()->end_radius(), 20.0);\n  ASSERT_EQ(source->asConicalGradient()->stop_count(), kTestStopCount);\n  for (int i = 0; i < kTestStopCount; i++) {\n    ASSERT_EQ(source->asConicalGradient()->colors()[i], kTestAlphaColors[i]);\n    ASSERT_EQ(source->asConicalGradient()->stops()[i], kTestStops[i]);\n  }\n  ASSERT_EQ(source->asConicalGradient()->tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asConicalGradient()->matrix(), kTestMatrix1);\n  ASSERT_EQ(source->is_opaque(), false);\n}\n\nTEST(DisplayListColorSource, ConicalGradientEquals) {\n  std::shared_ptr<DlColorSource> source1 = DlColorSource::MakeConical(\n      kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n      kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n  std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n      kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n      kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n  TestEquals(*source1, *source2);\n}\n\nTEST(DisplayListColorSource, ConicalGradientNotEquals) {\n  std::shared_ptr<DlColorSource> source1 = DlColorSource::MakeConical(\n      kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n      kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n        kTestPoints2[0], 10.0, kTestPoints[1], 20.0, kTestStopCount,\n        kTestColors, kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Start Center differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n        kTestPoints[0], 15.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n        kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Start Radius differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n        kTestPoints[0], 10.0, kTestPoints2[1], 20.0, kTestStopCount,\n        kTestColors, kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"End Center differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n        kTestPoints[0], 10.0, kTestPoints[1], 25.0, kTestStopCount, kTestColors,\n        kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"End Radius differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n        kTestPoints[0], 10.0, kTestPoints[1], 20.0, 2, kTestColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Stop count differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n        kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount,\n        kTestAlphaColors, kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Colors differ\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n        kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n        kTestStops2, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Stops differ\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n        kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n        kTestStops, DlTileMode::kMirror, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Tile Mode differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeConical(\n        kTestPoints[0], 10.0, kTestPoints[1], 20.0, kTestStopCount, kTestColors,\n        kTestStops, DlTileMode::kClamp, &kTestMatrix2);\n    TestNotEquals(*source1, *source2, \"Matrix differs\");\n  }\n}\n\nTEST(DisplayListColorSource, SweepGradientConstructor) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeSweep(\n      kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n}\n\nTEST(DisplayListColorSource, SweepGradientShared) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeSweep(\n      kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_NE(source->shared().get(), source.get());\n  ASSERT_EQ(*source->shared().get(), *source.get());\n}\n\nTEST(DisplayListColorSource, SweepGradientAsSweep) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeSweep(\n      kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_NE(source->asSweepGradient(), nullptr);\n  ASSERT_EQ(source->asSweepGradient(), source.get());\n\n  ASSERT_EQ(source->asColor(), nullptr);\n  ASSERT_EQ(source->asImage(), nullptr);\n  ASSERT_EQ(source->asLinearGradient(), nullptr);\n  ASSERT_EQ(source->asRadialGradient(), nullptr);\n  ASSERT_EQ(source->asConicalGradient(), nullptr);\n  ASSERT_EQ(source->asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, SweepGradientContents) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeSweep(\n      kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_EQ(source->asSweepGradient()->center(), kTestPoints[0]);\n  ASSERT_EQ(source->asSweepGradient()->start(), 10.0);\n  ASSERT_EQ(source->asSweepGradient()->end(), 20.0);\n  ASSERT_EQ(source->asSweepGradient()->stop_count(), kTestStopCount);\n  for (int i = 0; i < kTestStopCount; i++) {\n    ASSERT_EQ(source->asSweepGradient()->colors()[i], kTestColors[i]);\n    ASSERT_EQ(source->asSweepGradient()->stops()[i], kTestStops[i]);\n  }\n  ASSERT_EQ(source->asSweepGradient()->tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asSweepGradient()->matrix(), kTestMatrix1);\n  ASSERT_EQ(source->is_opaque(), true);\n}\n\nTEST(DisplayListColorSource, AlphaSweepGradientContents) {\n  std::shared_ptr<DlColorSource> source = DlColorSource::MakeSweep(\n      kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestAlphaColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  ASSERT_EQ(source->asSweepGradient()->center(), kTestPoints[0]);\n  ASSERT_EQ(source->asSweepGradient()->start(), 10.0);\n  ASSERT_EQ(source->asSweepGradient()->end(), 20.0);\n  ASSERT_EQ(source->asSweepGradient()->stop_count(), kTestStopCount);\n  for (int i = 0; i < kTestStopCount; i++) {\n    ASSERT_EQ(source->asSweepGradient()->colors()[i], kTestAlphaColors[i]);\n    ASSERT_EQ(source->asSweepGradient()->stops()[i], kTestStops[i]);\n  }\n  ASSERT_EQ(source->asSweepGradient()->tile_mode(), DlTileMode::kClamp);\n  ASSERT_EQ(source->asSweepGradient()->matrix(), kTestMatrix1);\n  ASSERT_EQ(source->is_opaque(), false);\n}\n\nTEST(DisplayListColorSource, SweepGradientEquals) {\n  std::shared_ptr<DlColorSource> source1 = DlColorSource::MakeSweep(\n      kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeSweep(\n      kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  TestEquals(*source1, *source2);\n}\n\nTEST(DisplayListColorSource, SweepGradientNotEquals) {\n  std::shared_ptr<DlColorSource> source1 = DlColorSource::MakeSweep(\n      kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n      DlTileMode::kClamp, &kTestMatrix1);\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeSweep(\n        kTestPoints2[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Center differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeSweep(\n        kTestPoints[0], 15.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Start Angle differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeSweep(\n        kTestPoints[0], 10.0, 25.0, kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"End Angle differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeSweep(\n        kTestPoints[0], 10.0, 20.0, 2, kTestColors, kTestStops,  //\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Stop count differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeSweep(\n        kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestAlphaColors,\n        kTestStops, DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Colors differ\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeSweep(\n        kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops2,\n        DlTileMode::kClamp, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Stops differ\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeSweep(\n        kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kMirror, &kTestMatrix1);\n    TestNotEquals(*source1, *source2, \"Tile Mode differs\");\n  }\n  {\n    std::shared_ptr<DlColorSource> source2 = DlColorSource::MakeSweep(\n        kTestPoints[0], 10.0, 20.0, kTestStopCount, kTestColors, kTestStops,\n        DlTileMode::kClamp, &kTestMatrix2);\n    TestNotEquals(*source1, *source2, \"Matrix differs\");\n  }\n}\n\nTEST(DisplayListColorSource, UnknownConstructor) {\n  DlUnknownColorSource source(kTestUnknownShader);\n}\n\nTEST(DisplayListColorSource, UnknownShared) {\n  DlUnknownColorSource source(kTestUnknownShader);\n  ASSERT_NE(source.shared().get(), &source);\n  ASSERT_EQ(*source.shared(), source);\n}\n\nTEST(DisplayListColorSource, UnknownAsNone) {\n  DlUnknownColorSource source(kTestUnknownShader);\n  ASSERT_EQ(source.asColor(), nullptr);\n  ASSERT_EQ(source.asImage(), nullptr);\n  ASSERT_EQ(source.asLinearGradient(), nullptr);\n  ASSERT_EQ(source.asRadialGradient(), nullptr);\n  ASSERT_EQ(source.asConicalGradient(), nullptr);\n  ASSERT_EQ(source.asSweepGradient(), nullptr);\n  ASSERT_EQ(source.asRuntimeEffect(), nullptr);\n}\n\nTEST(DisplayListColorSource, UnknownContents) {\n  DlUnknownColorSource source(kTestUnknownShader);\n  ASSERT_EQ(source.gr_object(), kTestUnknownShader);\n  // Blend shaders always return false for is_opaque.\n  // See: https://bugs.chromium.org/p/skia/issues/detail?id=13046\n  ASSERT_EQ(source.is_opaque(), false);\n}\n\nTEST(DisplayListColorSource, AlphaUnknownContents) {\n  DlUnknownColorSource source(kTestAlphaUnknownShader);\n  ASSERT_EQ(source.gr_object(), kTestAlphaUnknownShader);\n  ASSERT_EQ(source.is_opaque(), false);\n}\n\nTEST(DisplayListColorSource, UnknownEquals) {\n  DlUnknownColorSource source1(kTestUnknownShader);\n  DlUnknownColorSource source2(kTestUnknownShader);\n  TestEquals(source1, source2);\n}\n\nTEST(DisplayListColorSource, UnknownNotEquals) {\n  DlUnknownColorSource source1(kTestUnknownShader);\n  DlUnknownColorSource source2(kTestAlphaUnknownShader);\n  TestNotEquals(source1, source2, \"SkShader differs\");\n}\n\nTEST(DisplayListColorSource, RuntimeEffect) {\n  std::shared_ptr<DlRuntimeEffectColorSource> source1 =\n      DlColorSource::MakeRuntimeEffect(\n          kTestRuntimeEffect1, {}, std::make_shared<std::vector<uint8_t>>());\n  std::shared_ptr<DlRuntimeEffectColorSource> source2 =\n      DlColorSource::MakeRuntimeEffect(\n          kTestRuntimeEffect2, {}, std::make_shared<std::vector<uint8_t>>());\n  std::shared_ptr<DlRuntimeEffectColorSource> source3 =\n      DlColorSource::MakeRuntimeEffect(\n          nullptr, {}, std::make_shared<std::vector<uint8_t>>());\n\n  ASSERT_EQ(source1->type(), DlColorSourceType::kRuntimeEffect);\n  ASSERT_EQ(source1->asRuntimeEffect(), source1.get());\n  ASSERT_NE(source2->asRuntimeEffect(), source1.get());\n\n  ASSERT_EQ(source1->asImage(), nullptr);\n  ASSERT_EQ(source1->asColor(), nullptr);\n  ASSERT_EQ(source1->asLinearGradient(), nullptr);\n  ASSERT_EQ(source1->asRadialGradient(), nullptr);\n  ASSERT_EQ(source1->asConicalGradient(), nullptr);\n  ASSERT_EQ(source1->asSweepGradient(), nullptr);\n\n  ASSERT_NE(source1->gr_object(), nullptr);\n  ASSERT_EQ(source3->gr_object(), nullptr);\n\n  TestEquals(source1, source1);\n  TestEquals(source3, source3);\n  TestNotEquals(source1, source2, \"SkRuntimeEffect differs\");\n  TestNotEquals(source2, source3, \"SkRuntimeEffect differs\");\n}\n\nTEST(DisplayListColorSource, RuntimeEffectWithNullSampler) {\n  std::shared_ptr<DlRuntimeEffectColorSource> source1 =\n      DlColorSource::MakeRuntimeEffect(\n          kTestRuntimeEffect1, {nullptr},\n          std::make_shared<std::vector<uint8_t>>());\n\n  ASSERT_EQ(source1->gr_object(), nullptr);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/color_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/gfx/style/color_unittests_helper.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(ColorTest, CreateColorTest) {\n  Color colorRed1(0xFFFF0000);\n  Color colorRed2 = Color::ARGBColor(255, 255, 0, 0);\n  Color colorRed3 = Color::RGBOColor(255, 0, 0, 1.0);\n\n  EXPECT_EQ(colorRed1, colorRed2);\n  EXPECT_EQ(colorRed2, colorRed3);\n  EXPECT_EQ(colorRed3, 0xFFFF0000);\n  EXPECT_EQ(colorRed3.Red(), 0xFF);\n\n  EXPECT_EQ(colorRed1, Color::Lerp(colorRed2, colorRed3, 0.2));\n\n  EXPECT_EQ(colorRed1, Color::AlphaBlend(colorRed2, colorRed3));\n}\n\nTEST(ColorTest, ParseColor) {\n  Color to_parse;\n\n  // Named color\n  EXPECT_TRUE(Color::Parse(std::string(\"red\"), &to_parse));\n  EXPECT_EQ_RGBO(to_parse, 255, 0, 0, 1.0f);\n\n  EXPECT_TRUE(Color::Parse(std::string(\"BLUE\"), &to_parse));\n  EXPECT_EQ_RGBO(to_parse, 0, 0, 255, 1.0f);\n\n  // rgb(0, 255, 0)\n  EXPECT_TRUE(Color::Parse(std::string(\"rgb(0, 255, 0)\"), &to_parse));\n  EXPECT_EQ_RGBO(to_parse, 0, 255, 0, 1.0f);\n\n  // rgba(0, 0, 255, 0.5)\n  EXPECT_TRUE(Color::Parse(std::string(\"rgba(0, 0, 255, 1.0)\"), &to_parse));\n  EXPECT_EQ_RGBO(to_parse, 0, 0, 255, 1.0f);\n\n  // rgba(50%, 50%, 50%, 0.5)\n  EXPECT_TRUE(Color::Parse(std::string(\"rgba(50%, 50%, 50%, 1.0)\"), &to_parse));\n  EXPECT_EQ_RGBO(to_parse, 128, 128, 128, 1.0f);\n\n  // Wrong Case\n  EXPECT_FALSE(Color::Parse(std::string(\"rad\"), &to_parse));\n\n  EXPECT_FALSE(Color::Parse(std::string(\"rgb(0,)\"), &to_parse));\n  EXPECT_FALSE(Color::Parse(std::string(\"rgb(0,0.0.0)\"), &to_parse));\n\n  // #0f38\n  // TODO(Xietong): doesn't support yet.\n  // EXPECT_TRUE(Color::Parse(std::string(\"#0f38\"), to_parse));\n  // EXPECT_EQ_RGBA(to_parse, 0u, (unsigned int)0xFF, (unsigned int)0x33,\n  //                (unsigned int)0x88);\n\n  // #0f3\n  EXPECT_TRUE(Color::Parse(std::string(\"#0f3\"), &to_parse));\n  EXPECT_EQ_RGB(to_parse, 0, (int)0xFF, (int)0x33);\n\n  // #00ff33\n  EXPECT_TRUE(Color::Parse(std::string(\"#00ff33\"), &to_parse));\n  EXPECT_EQ_RGB(to_parse, 0, (int)0xFF, (int)0x33);\n\n  // #00ff3388\n  EXPECT_TRUE(Color::Parse(std::string(\"#00ff3388\"), &to_parse));\n  EXPECT_EQ_RGBA(to_parse, 0, (int)0xFF, (int)0x33, (int)0x88);\n}\n\nTEST(ColorTest, CompareColor) {\n  const auto color1 = Color::ARGBColor(0x7F, 0x32, 0x41, 0xFF);\n  const auto color2 = Color::RGBOColor(0x32, 0x41, 0xFF, 0.499f);\n  const auto color3 = Color::RGBOColor(0x32, 0x41, 0xFF, 0.5f);\n\n  EXPECT_EQ(color1, color2);\n  EXPECT_EQ(color2, color3);\n}\n\nTEST(ColorTest, LerpColor) {\n  const Color red = Color(0xFFFF0000u);\n  const Color green = Color(0xFF008000u);\n  const Color yellow = Color(0xFFFFFF00u);\n  const Color blue = Color(0xFF0000FFu);\n\n  EXPECT_EQ(Color::Lerp(red, green, 0.0f), red);\n  EXPECT_EQ(Color::Lerp(red, green, 0.5f), 0xFF7F4000u);\n  EXPECT_EQ(Color::Lerp(red, green, 1.0f), green);\n\n  EXPECT_EQ(Color::Lerp(yellow, blue, 0.0f), yellow);\n  EXPECT_EQ(Color::Lerp(yellow, blue, 0.5f), 0xFF7F7F7Fu);\n  EXPECT_EQ(Color::Lerp(yellow, blue, 1.0f), blue);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/color_unittests_helper.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_COLOR_UNITTESTS_HELPER_H_\n#define CLAY_GFX_STYLE_COLOR_UNITTESTS_HELPER_H_\n\n#define EXPECT_EQ_RGBO(__color__, __r__, __g__, __b__, __o__) \\\n  EXPECT_EQ(__color__.Red(), __r__);                          \\\n  EXPECT_EQ(__color__.Green(), __g__);                        \\\n  EXPECT_EQ(__color__.Blue(), __b__);                         \\\n  EXPECT_DOUBLE_EQ(__color__.Opacity(), __o__)\n\n#define EXPECT_EQ_RGB(__color__, __r__, __g__, __b__) \\\n  EXPECT_EQ(__color__.Red(), __r__);                  \\\n  EXPECT_EQ(__color__.Green(), __g__);                \\\n  EXPECT_EQ(__color__.Blue(), __b__)\n\n#define EXPECT_EQ_RGBA(__color__, __r__, __g__, __b__, __a__) \\\n  EXPECT_EQ(__color__.Red(), __r__);                          \\\n  EXPECT_EQ(__color__.Green(), __g__);                        \\\n  EXPECT_EQ(__color__.Blue(), __b__);                         \\\n  EXPECT_DOUBLE_EQ(__color__.Alpha(), __a__)\n\n#endif  // CLAY_GFX_STYLE_COLOR_UNITTESTS_HELPER_H_\n"
  },
  {
    "path": "clay/gfx/style/enum_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/paint.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkSamplingOptions.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(DisplayListEnum, ToDlTileMode) {\n  ASSERT_EQ(clay::ToClay(SkTileMode::kClamp), DlTileMode::kClamp);\n  ASSERT_EQ(clay::ToClay(SkTileMode::kRepeat), DlTileMode::kRepeat);\n  ASSERT_EQ(clay::ToClay(SkTileMode::kMirror), DlTileMode::kMirror);\n  ASSERT_EQ(clay::ToClay(SkTileMode::kDecal), DlTileMode::kDecal);\n}\n\nTEST(DisplayListEnum, ToSkTileMode) {\n  ASSERT_EQ(ToSk(DlTileMode::kClamp), SkTileMode::kClamp);\n  ASSERT_EQ(ToSk(DlTileMode::kRepeat), SkTileMode::kRepeat);\n  ASSERT_EQ(ToSk(DlTileMode::kMirror), SkTileMode::kMirror);\n  ASSERT_EQ(ToSk(DlTileMode::kDecal), SkTileMode::kDecal);\n}\n\nTEST(DisplayListEnum, ToDlDrawStyle) {\n  ASSERT_EQ(clay::ToClay(SkPaint::Style::kFill_Style), DlDrawStyle::kFill);\n  ASSERT_EQ(clay::ToClay(SkPaint::Style::kStroke_Style), DlDrawStyle::kStroke);\n  ASSERT_EQ(clay::ToClay(SkPaint::Style::kStrokeAndFill_Style),\n            DlDrawStyle::kStrokeAndFill);\n}\n\nTEST(DisplayListEnum, ToSkDrawStyle) {\n  ASSERT_EQ(ToSk(DlDrawStyle::kFill), SkPaint::Style::kFill_Style);\n  ASSERT_EQ(ToSk(DlDrawStyle::kStroke), SkPaint::Style::kStroke_Style);\n  ASSERT_EQ(ToSk(DlDrawStyle::kStrokeAndFill),\n            SkPaint::Style::kStrokeAndFill_Style);\n}\n\nTEST(DisplayListEnum, ToDlStrokeCap) {\n  ASSERT_EQ(clay::ToClay(SkPaint::Cap::kButt_Cap), DlStrokeCap::kButt);\n  ASSERT_EQ(clay::ToClay(SkPaint::Cap::kRound_Cap), DlStrokeCap::kRound);\n  ASSERT_EQ(clay::ToClay(SkPaint::Cap::kSquare_Cap), DlStrokeCap::kSquare);\n}\n\nTEST(DisplayListEnum, ToSkStrokeCap) {\n  ASSERT_EQ(ToSk(DlStrokeCap::kButt), SkPaint::Cap::kButt_Cap);\n  ASSERT_EQ(ToSk(DlStrokeCap::kRound), SkPaint::Cap::kRound_Cap);\n  ASSERT_EQ(ToSk(DlStrokeCap::kSquare), SkPaint::Cap::kSquare_Cap);\n}\n\nTEST(DisplayListEnum, ToDlStrokeJoin) {\n  ASSERT_EQ(clay::ToClay(SkPaint::Join::kMiter_Join), DlStrokeJoin::kMiter);\n  ASSERT_EQ(clay::ToClay(SkPaint::Join::kRound_Join), DlStrokeJoin::kRound);\n  ASSERT_EQ(clay::ToClay(SkPaint::Join::kBevel_Join), DlStrokeJoin::kBevel);\n}\n\nTEST(DisplayListEnum, ToSkStrokeJoin) {\n  ASSERT_EQ(ToSk(DlStrokeJoin::kMiter), SkPaint::Join::kMiter_Join);\n  ASSERT_EQ(ToSk(DlStrokeJoin::kRound), SkPaint::Join::kRound_Join);\n  ASSERT_EQ(ToSk(DlStrokeJoin::kBevel), SkPaint::Join::kBevel_Join);\n}\n\nTEST(DisplayListEnum, ToDlVertexMode) {\n  ASSERT_EQ(clay::ToClay(SkVertices::VertexMode::kTriangles_VertexMode),\n            DlVertexMode::kTriangles);\n  ASSERT_EQ(clay::ToClay(SkVertices::VertexMode::kTriangleStrip_VertexMode),\n            DlVertexMode::kTriangleStrip);\n  ASSERT_EQ(clay::ToClay(SkVertices::VertexMode::kTriangleFan_VertexMode),\n            DlVertexMode::kTriangleFan);\n}\n\nTEST(DisplayListEnum, ToSkVertexMode) {\n  ASSERT_EQ(ToSk(DlVertexMode::kTriangles),\n            SkVertices::VertexMode::kTriangles_VertexMode);\n  ASSERT_EQ(ToSk(DlVertexMode::kTriangleStrip),\n            SkVertices::VertexMode::kTriangleStrip_VertexMode);\n  ASSERT_EQ(ToSk(DlVertexMode::kTriangleFan),\n            SkVertices::VertexMode::kTriangleFan_VertexMode);\n}\n\nTEST(DisplayListEnum, ToDlFilterMode) {\n  ASSERT_EQ(clay::ToClay(SkFilterMode::kLinear), DlFilterMode::kLinear);\n  ASSERT_EQ(clay::ToClay(SkFilterMode::kNearest), DlFilterMode::kNearest);\n  ASSERT_EQ(clay::ToClay(SkFilterMode::kLast), DlFilterMode::kLast);\n}\n\nTEST(DisplayListEnum, ToSkFilterMode) {\n  ASSERT_EQ(ToSk(DlFilterMode::kLinear), SkFilterMode::kLinear);\n  ASSERT_EQ(ToSk(DlFilterMode::kNearest), SkFilterMode::kNearest);\n  ASSERT_EQ(ToSk(DlFilterMode::kLast), SkFilterMode::kLast);\n}\n\nTEST(DisplayListEnum, ToDlImageSampling) {\n  ASSERT_EQ(clay::ToClay(\n                SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone)),\n            DlImageSampling::kLinear);\n  ASSERT_EQ(clay::ToClay(SkSamplingOptions(SkFilterMode::kLinear,\n                                           SkMipmapMode::kLinear)),\n            DlImageSampling::kMipmapLinear);\n  ASSERT_EQ(clay::ToClay(\n                SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone)),\n            DlImageSampling::kNearestNeighbor);\n  ASSERT_EQ(\n      clay::ToClay(SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f})),\n      DlImageSampling::kCubic);\n}\n\nTEST(DisplayListEnum, ToSkSamplingOptions) {\n  ASSERT_EQ(ToSk(DlImageSampling::kLinear),\n            SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));\n  ASSERT_EQ(ToSk(DlImageSampling::kMipmapLinear),\n            SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear));\n  ASSERT_EQ(ToSk(DlImageSampling::kNearestNeighbor),\n            SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone));\n  ASSERT_EQ(ToSk(DlImageSampling::kCubic),\n            SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f}));\n}\n\n#define CHECK_TO_DLENUM(V) \\\n  ASSERT_EQ(clay::ToClay(SkBlendMode::V), DlBlendMode::V);\n#define CHECK_TO_SKENUM(V) ASSERT_EQ(ToSk(DlBlendMode::V), SkBlendMode::V);\n\n#define FOR_EACH_ENUM(FUNC) \\\n  FUNC(kSrc)                \\\n  FUNC(kClear)              \\\n  FUNC(kSrc)                \\\n  FUNC(kDst)                \\\n  FUNC(kSrcOver)            \\\n  FUNC(kDstOver)            \\\n  FUNC(kSrcIn)              \\\n  FUNC(kDstIn)              \\\n  FUNC(kSrcOut)             \\\n  FUNC(kDstOut)             \\\n  FUNC(kSrcATop)            \\\n  FUNC(kDstATop)            \\\n  FUNC(kXor)                \\\n  FUNC(kPlus)               \\\n  FUNC(kModulate)           \\\n  FUNC(kScreen)             \\\n  FUNC(kOverlay)            \\\n  FUNC(kDarken)             \\\n  FUNC(kLighten)            \\\n  FUNC(kColorDodge)         \\\n  FUNC(kColorBurn)          \\\n  FUNC(kHardLight)          \\\n  FUNC(kSoftLight)          \\\n  FUNC(kDifference)         \\\n  FUNC(kExclusion)          \\\n  FUNC(kMultiply)           \\\n  FUNC(kHue)                \\\n  FUNC(kSaturation)         \\\n  FUNC(kColor)              \\\n  FUNC(kLuminosity)         \\\n  FUNC(kLastCoeffMode)      \\\n  FUNC(kLastSeparableMode)  \\\n  FUNC(kLastMode)\n\nTEST(DisplayListEnum, ToDlBlendMode){FOR_EACH_ENUM(CHECK_TO_DLENUM)}\n\nTEST(DisplayListEnum, ToSkBlendMode) {\n  FOR_EACH_ENUM(CHECK_TO_SKENUM)\n}\n\n#undef CHECK_TO_DLENUM\n#undef CHECK_TO_SKENUM\n#undef FOR_EACH_ENUM\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/image_filter.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/image_filter.h\"\n\nnamespace clay {\n\nstd::shared_ptr<ImageFilter> ImageFilter::MakeBlur(float sigma_x, float sigma_y,\n                                                   TileMode tile_mode) {\n  return std::make_shared<BlurImageFilter>(sigma_x, sigma_y, tile_mode);\n}\nstd::shared_ptr<ImageFilter> ImageFilter::MakeDilate(float radius_x,\n                                                     float radius_y) {\n  return std::make_shared<DilateImageFilter>(radius_x, radius_y);\n}\nstd::shared_ptr<ImageFilter> ImageFilter::MakeErode(float radius_x,\n                                                    float radius_y) {\n  return std::make_shared<ErodeImageFilter>(radius_x, radius_y);\n}\nstd::shared_ptr<ImageFilter> ImageFilter::MakeMatrix(\n    const skity::Matrix& matrix, ImageSampling sampling) {\n  return std::make_shared<MatrixImageFilter>(matrix, sampling);\n}\nstd::shared_ptr<ImageFilter> ImageFilter::MakeColorFilter(\n    const std::shared_ptr<ColorFilter>& filter) {\n  return std::make_shared<ColorFilterImageFilter>(filter);\n}\nstd::shared_ptr<ImageFilter> ImageFilter::MakeCompose(\n    const std::shared_ptr<ImageFilter>& outer,\n    const std::shared_ptr<ImageFilter>& inner) {\n  return std::make_shared<ComposeImageFilter>(outer, inner);\n}\n\nstd::shared_ptr<ImageFilter> ImageFilter::makeWithLocalMatrix(\n    const skity::Matrix& matrix) const {\n  if (matrix.IsIdentity()) {\n    return shared();\n  }\n  // Matrix\n  switch (this->matrix_capability()) {\n    case MatrixCapability::kTranslate: {\n      if (!matrix.OnlyTranslate()) {\n        // Nothing we can do at this point\n        return nullptr;\n      }\n      break;\n    }\n    case MatrixCapability::kScaleTranslate: {\n      if (!matrix.OnlyScaleAndTranslate()) {\n        // Nothing we can do at this point\n        return nullptr;\n      }\n      break;\n    }\n    default:\n      break;\n  }\n  return std::make_shared<LocalMatrixImageFilter>(matrix, shared());\n}\n\nskity::Rect* ComposeImageFilter::map_local_bounds(\n    const skity::Rect& input_bounds, skity::Rect& output_bounds) const {\n  skity::Rect cur_bounds = input_bounds;\n  skity::Rect* ret = &output_bounds;\n  // We set this result in case neither filter is present.\n  output_bounds = input_bounds;\n  if (inner_) {\n    if (!inner_->map_local_bounds(cur_bounds, output_bounds)) {\n      ret = nullptr;\n    }\n    cur_bounds = output_bounds;\n  }\n  if (outer_) {\n    if (!outer_->map_local_bounds(cur_bounds, output_bounds)) {\n      ret = nullptr;\n    }\n  }\n  return ret;\n}\n\nskity::Rect* ComposeImageFilter::map_device_bounds(\n    const skity::Rect& input_bounds, const skity::Matrix& ctm,\n    skity::Rect& output_bounds) const {\n  skity::Rect cur_bounds = input_bounds;\n  skity::Rect* ret = &output_bounds;\n  // We set this result in case neither filter is present.\n  output_bounds = input_bounds;\n  if (inner_) {\n    if (!inner_->map_device_bounds(cur_bounds, ctm, output_bounds)) {\n      ret = nullptr;\n    }\n    cur_bounds = output_bounds;\n  }\n  if (outer_) {\n    if (!outer_->map_device_bounds(cur_bounds, ctm, output_bounds)) {\n      ret = nullptr;\n    }\n  }\n  return ret;\n}\n\nskity::Rect* ComposeImageFilter::get_input_device_bounds(\n    const skity::Rect& output_bounds, const skity::Matrix& ctm,\n    skity::Rect& input_bounds) const {\n  skity::Rect cur_bounds = output_bounds;\n  skity::Rect* ret = &input_bounds;\n  // We set this result in case neither filter is present.\n  input_bounds = output_bounds;\n  if (outer_) {\n    if (!outer_->get_input_device_bounds(cur_bounds, ctm, input_bounds)) {\n      ret = nullptr;\n    }\n    cur_bounds = input_bounds;\n  }\n  if (inner_) {\n    if (!inner_->get_input_device_bounds(cur_bounds, ctm, input_bounds)) {\n      ret = nullptr;\n    }\n  }\n  return ret;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/image_filter.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_IMAGE_FILTER_H_\n#define CLAY_GFX_STYLE_IMAGE_FILTER_H_\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/attributes.h\"\n#include \"clay/gfx/comparable.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#ifndef ENABLE_SKITY\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#endif\n#include \"clay/gfx/style/color_filter.h\"\n#include \"clay/gfx/style/sampling_options.h\"\n#include \"clay/gfx/style/tile_mode.h\"\n#include \"skity/geometry/matrix.hpp\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n// The DisplayList ImageFilter class. This class implements all of the\n// facilities and adheres to the design goals of the |Attribute| base\n// class.\n//\n// The objects here define operations that can take a location and one or\n// more input pixels and produce a color for that output pixel\n\n// An enumerated type for the recognized ImageFilter operations.\n// If a custom ImageFilter outside of the recognized types is needed\n// then a |kUnknown| type that simply defers to an SkImageFilter is\n// provided as a fallback.\nenum class ImageFilterType {\n  kBlur,\n  kDilate,\n  kErode,\n  kMatrix,\n  kComposeFilter,\n  kColorFilter,\n  kLocalMatrixFilter,\n  kDropShadow,\n  kUnknown\n};\n\nclass BlurImageFilter;\nclass DropShadowImageFilter;\nclass DilateImageFilter;\nclass ErodeImageFilter;\nclass MatrixImageFilter;\nclass LocalMatrixImageFilter;\nclass ComposeImageFilter;\nclass ColorFilterImageFilter;\nclass ImageImageFilter;\n\nclass ImageFilter\n    : public Attribute<ImageFilter, GrImageFilter, ImageFilterType> {\n public:\n  enum class MatrixCapability {\n    kTranslate,\n    kScaleTranslate,\n    kComplex,\n  };\n\n  static std::shared_ptr<ImageFilter> MakeBlur(float sigma_x, float sigma_y,\n                                               TileMode tile_mode);\n  static std::shared_ptr<ImageFilter> MakeDilate(float radius_x,\n                                                 float radius_y);\n  static std::shared_ptr<ImageFilter> MakeErode(float radius_x, float radius_y);\n  static std::shared_ptr<ImageFilter> MakeMatrix(const skity::Matrix& matrix,\n                                                 ImageSampling sampling);\n  static std::shared_ptr<ImageFilter> MakeColorFilter(\n      const std::shared_ptr<ColorFilter>& filter);\n  static std::shared_ptr<ImageFilter> MakeCompose(\n      const std::shared_ptr<ImageFilter>& outer,\n      const std::shared_ptr<ImageFilter>& inner);\n\n  // Return a BlurImageFilter pointer to this object iff it is a Blur\n  // type of ImageFilter, otherwise return nullptr.\n  virtual const BlurImageFilter* asBlur() const { return nullptr; }\n\n  // Return a DropShadowFilter pointer to this object iff it is a DropShadow\n  // type of ImageFilter, otherwise return nullptr.\n  virtual const DropShadowImageFilter* asDropShadow() const { return nullptr; }\n\n  // Return a DilateImageFilter pointer to this object iff it is a Dilate\n  // type of ImageFilter, otherwise return nullptr.\n  virtual const DilateImageFilter* asDilate() const { return nullptr; }\n\n  // Return a ErodeImageFilter pointer to this object iff it is an Erode\n  // type of ImageFilter, otherwise return nullptr.\n  virtual const ErodeImageFilter* asErode() const { return nullptr; }\n\n  // Return a MatrixImageFilter pointer to this object iff it is a Matrix\n  // type of ImageFilter, otherwise return nullptr.\n  virtual const MatrixImageFilter* asMatrix() const { return nullptr; }\n\n  virtual const LocalMatrixImageFilter* asLocalMatrix() const {\n    return nullptr;\n  }\n\n  virtual std::shared_ptr<ImageFilter> makeWithLocalMatrix(\n      const skity::Matrix& matrix) const;\n\n  // Return a ComposeImageFilter pointer to this object iff it is a Compose\n  // type of ImageFilter, otherwise return nullptr.\n  virtual const ComposeImageFilter* asCompose() const { return nullptr; }\n\n  // Return a ColorFilterImageFilter pointer to this object iff it is a\n  // ColorFilter type of ImageFilter, otherwise return nullptr.\n  virtual const ColorFilterImageFilter* asColorFilter() const {\n    return nullptr;\n  }\n\n  virtual const ImageImageFilter* asImage() const { return nullptr; }\n\n  // Return a boolean indicating whether the image filtering operation will\n  // modify transparent black. This is typically used to determine if applying\n  // the ImageFilter to a temporary saveLayer buffer will turn the surrounding\n  // pixels non-transparent and therefore expand the bounds.\n  virtual bool modifies_transparent_black() const = 0;\n\n  // Return the bounds of the output for this image filtering operation\n  // based on the supplied input bounds where both are measured in the local\n  // (untransformed) coordinate space.\n  //\n  // The method will return a pointer to the output_bounds parameter if it\n  // can successfully compute the output bounds of the filter, otherwise the\n  // method will return a nullptr and the output_bounds will be filled with\n  // a best guess for the answer, even if just a copy of the input_bounds.\n  virtual skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                        skity::Rect& output_bounds) const = 0;\n\n  // Return the device bounds of the output for this image filtering operation\n  // based on the supplied input device bounds where both are measured in the\n  // pixel coordinate space and relative to the given rendering ctm. The\n  // transform matrix is used to adjust the filter parameters for when it\n  // is used in a rendering operation (for example, the blur radius of a\n  // Blur filter will expand based on the ctm).\n  //\n  // The method will return a pointer to the output_bounds parameter if it\n  // can successfully compute the output bounds of the filter, otherwise the\n  // method will return a nullptr and the output_bounds will be filled with\n  // a best guess for the answer, even if just a copy of the input_bounds.\n  virtual skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                         const skity::Matrix& ctm,\n                                         skity::Rect& output_bounds) const = 0;\n\n  // Return the input bounds that will be needed in order for the filter to\n  // properly fill the indicated output_bounds under the specified\n  // transformation matrix. Both output_bounds and input_bounds are taken to\n  // be relative to the transformed coordinate space of the provided |ctm|.\n  //\n  // The method will return a pointer to the input_bounds parameter if it\n  // can successfully compute the required input bounds, otherwise the\n  // method will return a nullptr and the input_bounds will be filled with\n  // a best guess for the answer, even if just a copy of the output_bounds.\n  virtual skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const = 0;\n\n  virtual MatrixCapability matrix_capability() const {\n    return MatrixCapability::kScaleTranslate;\n  }\n\n protected:\n  static skity::Vec2 map_vectors_affine(const skity::Matrix& ctm, float x,\n                                        float y) {\n    FML_DCHECK(x >= 0);\n    FML_DCHECK(y >= 0);\n    FML_DCHECK(ctm.IsFinite() && !ctm.HasPersp());\n\n    // The x and y scalars would have been used to expand a local space\n    // rectangle which is then transformed by ctm. In order to do the\n    // expansion correctly, we should look at the relevant math. The\n    // 4 corners will be moved outward by the following vectors:\n    //     (UL,UR,LR,LL) = ((-x, -y), (+x, -y), (+x, +y), (-x, +y))\n    // After applying the transform, each of these vectors could be\n    // pointing in any direction so we need to examine each transformed\n    // delta vector and how it affected the bounds.\n    // Looking at just the affine 2x3 entries of the CTM we can delta\n    // transform these corner offsets and get the following:\n    //     UL = dCTM(-x, -y) = (- x*m00 - y*m01, - x*m10 - y*m11)\n    //     UR = dCTM(+x, -y) = (  x*m00 - y*m01,   x*m10 - y*m11)\n    //     LR = dCTM(+x, +y) = (  x*m00 + y*m01,   x*m10 + y*m11)\n    //     LL = dCTM(-x, +y) = (- x*m00 + y*m01, - x*m10 + y*m11)\n    // The X vectors are all some variation of adding or subtracting\n    // the sum of x*m00 and y*m01 or their difference. Similarly the Y\n    // vectors are +/- the associated sum/difference of x*m10 and y*m11.\n    // The largest displacements, both left/right or up/down, will\n    // happen when the signs of the m00/m01/m10/m11 matrix entries\n    // coincide with the signs of the scalars, i.e. are all positive.\n    return {x * abs(ctm[0][0]) + y * abs(ctm[1][0]),\n            x * abs(ctm[0][1]) + y * abs(ctm[1][1])};\n  }\n\n  static skity::Rect* inset_device_bounds(const skity::Rect& input_bounds,\n                                          float radius_x, float radius_y,\n                                          const skity::Matrix& ctm,\n                                          skity::Rect& output_bounds) {\n    if (ctm.IsFinite()) {\n      if (ctm.HasPersp()) {\n        skity::Matrix inverse;\n        if (ctm.Invert(&inverse)) {\n          skity::Rect local_bounds;\n          inverse.MapRect(&local_bounds, input_bounds);\n          local_bounds.Inset(radius_x, radius_y);\n          ctm.MapRect(&output_bounds, local_bounds);\n          output_bounds.RoundOut();\n          return &output_bounds;\n        }\n      } else {\n        skity::Vec2 device_radius = map_vectors_affine(ctm, radius_x, radius_y);\n        output_bounds = input_bounds;\n        output_bounds.Inset(floor(device_radius.x),  //\n                            floor(device_radius.y));\n        return &output_bounds;\n      }\n    }\n    output_bounds = input_bounds;\n    return nullptr;\n  }\n  static skity::Rect* outset_device_bounds(const skity::Rect& input_bounds,\n                                           float radius_x, float radius_y,\n                                           const skity::Matrix& ctm,\n                                           skity::Rect& output_bounds) {\n    if (ctm.IsFinite()) {\n      if (ctm.HasPersp()) {\n        skity::Matrix inverse;\n        if (ctm.Invert(&inverse)) {\n          skity::Rect local_bounds;\n          inverse.MapRect(&local_bounds, input_bounds);\n          local_bounds.Outset(radius_x, radius_y);\n          ctm.MapRect(&output_bounds, local_bounds);\n          output_bounds.RoundOut();\n          return &output_bounds;\n        }\n      } else {\n        skity::Vec2 device_radius = map_vectors_affine(ctm, radius_x, radius_y);\n        output_bounds = input_bounds;\n        output_bounds.Outset(ceil(device_radius.x),  //\n                             ceil(device_radius.y));\n        return &output_bounds;\n      }\n    }\n    output_bounds = input_bounds;\n    return nullptr;\n  }\n\n  static skity::Rect* outset_device_bounds(const skity::Rect& input_bounds,\n                                           float dx, float dy, float radius_x,\n                                           float radius_y,\n                                           const skity::Matrix& ctm,\n                                           skity::Rect& output_bounds) {\n    if (ctm.IsFinite()) {\n      if (ctm.HasPersp()) {\n        skity::Matrix inverse;\n        if (ctm.Invert(&inverse)) {\n          skity::Rect local_bounds;\n          inverse.MapRect(&local_bounds, input_bounds);\n          skity::Rect shadow_bounds = local_bounds;\n          shadow_bounds.Offset(dx, dy);\n          shadow_bounds.Outset(radius_x, radius_y);\n          local_bounds.Join(shadow_bounds);\n          ctm.MapRect(&output_bounds, local_bounds);\n          output_bounds.RoundOut();\n          return &output_bounds;\n        }\n      } else {\n        skity::Vec2 device_offsets = map_vectors_affine(ctm, dx, dy);\n        skity::Vec2 device_radius = map_vectors_affine(ctm, radius_x, radius_y);\n        skity::Rect shadow_bounds = input_bounds;\n        shadow_bounds.Offset(ceil(device_offsets.x),  //\n                             ceil(device_offsets.y));\n        shadow_bounds.Offset(ceil(device_radius.x),  //\n                             ceil(device_radius.y));\n        shadow_bounds.Join(input_bounds);\n        output_bounds = shadow_bounds;\n        return &output_bounds;\n      }\n    }\n    output_bounds = input_bounds;\n    return nullptr;\n  }\n};\n\nclass BlurImageFilter final : public ImageFilter {\n public:\n  BlurImageFilter(float sigma_x, float sigma_y, TileMode tile_mode)\n      : sigma_x_(sigma_x), sigma_y_(sigma_y), tile_mode_(tile_mode) {}\n  explicit BlurImageFilter(const BlurImageFilter* filter)\n      : BlurImageFilter(filter->sigma_x_, filter->sigma_y_,\n                        filter->tile_mode_) {}\n  BlurImageFilter(const BlurImageFilter& filter) : BlurImageFilter(&filter) {}\n\n  std::shared_ptr<ImageFilter> shared() const override {\n    return std::make_shared<BlurImageFilter>(this);\n  }\n\n  ImageFilterType type() const override { return ImageFilterType::kBlur; }\n  size_t size() const override { return sizeof(*this); }\n\n  const BlurImageFilter* asBlur() const override { return this; }\n\n  bool modifies_transparent_black() const override { return false; }\n\n  skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                skity::Rect& output_bounds) const override {\n    output_bounds = input_bounds;\n    output_bounds.Outset(sigma_x_ * 3, sigma_y_ * 3);\n    return &output_bounds;\n  }\n\n  skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                 const skity::Matrix& ctm,\n                                 skity::Rect& output_bounds) const override {\n    return outset_device_bounds(input_bounds, sigma_x_ * 3.0, sigma_y_ * 3.0,\n                                ctm, output_bounds);\n  }\n\n  skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const override {\n    // Blurs are symmetric in terms of output-for-input and input-for-output\n    return map_device_bounds(output_bounds, ctm, input_bounds);\n  }\n\n  float sigma_x() const { return sigma_x_; }\n  float sigma_y() const { return sigma_y_; }\n  TileMode tile_mode() const { return tile_mode_; }\n\n  GrImageFilterPtr gr_object() const override {\n    return IMAGE_FILTERS_BLUR(sigma_x_, sigma_y_, ToSk(tile_mode_), nullptr);\n  }\n\n  bool equals_(const ImageFilter& other) const override {\n    FML_DCHECK(other.type() == ImageFilterType::kBlur);\n    auto that = static_cast<const BlurImageFilter*>(&other);\n    return (sigma_x_ == that->sigma_x_ && sigma_y_ == that->sigma_y_ &&\n            tile_mode_ == that->tile_mode_);\n  }\n\n private:\n  float sigma_x_;\n  float sigma_y_;\n  TileMode tile_mode_;\n};\n\nclass DropShadowImageFilter final : public ImageFilter {\n public:\n  DropShadowImageFilter(float dx, float dy, float sigma_x, float sigma_y,\n                        Color color)\n      : dx_(dx), dy_(dy), sigma_x_(sigma_x), sigma_y_(sigma_y), color_(color) {}\n  explicit DropShadowImageFilter(const DropShadowImageFilter* filter)\n      : DropShadowImageFilter(filter->dx_, filter->dy_, filter->sigma_x_,\n                              filter->sigma_y_, filter->color_) {}\n  DropShadowImageFilter(const DropShadowImageFilter& filter)\n      : DropShadowImageFilter(&filter) {}\n\n  std::shared_ptr<ImageFilter> shared() const override {\n    return std::make_shared<DropShadowImageFilter>(this);\n  }\n\n  ImageFilterType type() const override { return ImageFilterType::kDropShadow; }\n  size_t size() const override { return sizeof(*this); }\n\n  const DropShadowImageFilter* asDropShadow() const override { return this; }\n\n  bool modifies_transparent_black() const override { return false; }\n\n  skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                skity::Rect& output_bounds) const override {\n    skity::Rect shadow_bounds = input_bounds;\n    shadow_bounds.Offset(dx_, dy_);\n    shadow_bounds.Outset(sigma_x_ * 3.0, sigma_x_ * 3.0);\n    shadow_bounds.Join(input_bounds);\n    output_bounds = shadow_bounds;\n    return &output_bounds;\n  }\n\n  skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                 const skity::Matrix& ctm,\n                                 skity::Rect& output_bounds) const override {\n    return outset_device_bounds(input_bounds, dx_, dy_, sigma_x_ * 3.0,\n                                sigma_y_ * 3.0, ctm, output_bounds);\n  }\n\n  skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const override {\n    // TODO(zhangzhijian): Figure out how to calculate input bounds from output\n    // bounds\n    input_bounds = output_bounds;\n    return nullptr;\n  }\n  float dx() const { return dx_; }\n  float dy() const { return dy_; }\n  float sigma_x() const { return sigma_x_; }\n  float sigma_y() const { return sigma_y_; }\n  Color color() const { return color_; }\n\n  GrImageFilterPtr gr_object() const override {\n    return GrImageFilters::DropShadow(dx_, dy_, sigma_x_, sigma_y_, color_,\n                                      nullptr);\n  }\n\n protected:\n  bool equals_(const ImageFilter& other) const override {\n    FML_DCHECK(other.type() == ImageFilterType::kDropShadow);\n    auto that = static_cast<const DropShadowImageFilter*>(&other);\n    return (dx_ == that->dx_ && dy_ == that->dy_ &&\n            sigma_x_ == that->sigma_x_ && sigma_y_ == that->sigma_y_ &&\n            color_ == that->color_);\n  }\n\n private:\n  float dx_;\n  float dy_;\n  float sigma_x_;\n  float sigma_y_;\n  Color color_;\n};\n\nclass DilateImageFilter final : public ImageFilter {\n public:\n  DilateImageFilter(float radius_x, float radius_y)\n      : radius_x_(radius_x), radius_y_(radius_y) {}\n  explicit DilateImageFilter(const DilateImageFilter* filter)\n      : DilateImageFilter(filter->radius_x_, filter->radius_y_) {}\n  DilateImageFilter(const DilateImageFilter& filter)\n      : DilateImageFilter(&filter) {}\n\n  std::shared_ptr<ImageFilter> shared() const override {\n    return std::make_shared<DilateImageFilter>(this);\n  }\n\n  ImageFilterType type() const override { return ImageFilterType::kDilate; }\n  size_t size() const override { return sizeof(*this); }\n\n  const DilateImageFilter* asDilate() const override { return this; }\n\n  bool modifies_transparent_black() const override { return false; }\n\n  skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                skity::Rect& output_bounds) const override {\n    output_bounds = input_bounds;\n    output_bounds.Outset(radius_x_, radius_y_);\n    return &output_bounds;\n  }\n\n  skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                 const skity::Matrix& ctm,\n                                 skity::Rect& output_bounds) const override {\n    return outset_device_bounds(input_bounds, radius_x_, radius_y_, ctm,\n                                output_bounds);\n  }\n\n  skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const override {\n    return inset_device_bounds(output_bounds, radius_x_, radius_y_, ctm,\n                               input_bounds);\n  }\n\n  float radius_x() const { return radius_x_; }\n  float radius_y() const { return radius_y_; }\n\n  GrImageFilterPtr gr_object() const override {\n    return IMAGE_FILTERS_DILATE(radius_x_, radius_y_, nullptr);\n  }\n\n protected:\n  bool equals_(const ImageFilter& other) const override {\n    FML_DCHECK(other.type() == ImageFilterType::kDilate);\n    auto that = static_cast<const DilateImageFilter*>(&other);\n    return (radius_x_ == that->radius_x_ && radius_y_ == that->radius_y_);\n  }\n\n private:\n  float radius_x_;\n  float radius_y_;\n};\n\nclass ErodeImageFilter final : public ImageFilter {\n public:\n  ErodeImageFilter(float radius_x, float radius_y)\n      : radius_x_(radius_x), radius_y_(radius_y) {}\n  explicit ErodeImageFilter(const ErodeImageFilter* filter)\n      : ErodeImageFilter(filter->radius_x_, filter->radius_y_) {}\n  ErodeImageFilter(const ErodeImageFilter& filter)\n      : ErodeImageFilter(&filter) {}\n\n  std::shared_ptr<ImageFilter> shared() const override {\n    return std::make_shared<ErodeImageFilter>(this);\n  }\n\n  ImageFilterType type() const override { return ImageFilterType::kErode; }\n  size_t size() const override { return sizeof(*this); }\n\n  const ErodeImageFilter* asErode() const override { return this; }\n\n  bool modifies_transparent_black() const override { return false; }\n\n  skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                skity::Rect& output_bounds) const override {\n    output_bounds = input_bounds;\n    output_bounds.Inset(radius_x_, radius_y_);\n    return &output_bounds;\n  }\n\n  skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                 const skity::Matrix& ctm,\n                                 skity::Rect& output_bounds) const override {\n    return inset_device_bounds(input_bounds, radius_x_, radius_y_, ctm,\n                               output_bounds);\n  }\n\n  skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const override {\n    return outset_device_bounds(output_bounds, radius_x_, radius_y_, ctm,\n                                input_bounds);\n  }\n\n  float radius_x() const { return radius_x_; }\n  float radius_y() const { return radius_y_; }\n\n  GrImageFilterPtr gr_object() const override {\n    return IMAGE_FILTERS_ERODE(radius_x_, radius_y_, nullptr);\n  }\n\n protected:\n  bool equals_(const ImageFilter& other) const override {\n    FML_DCHECK(other.type() == ImageFilterType::kErode);\n    auto that = static_cast<const ErodeImageFilter*>(&other);\n    return (radius_x_ == that->radius_x_ && radius_y_ == that->radius_y_);\n  }\n\n private:\n  float radius_x_;\n  float radius_y_;\n};\n\nclass MatrixImageFilter final : public ImageFilter {\n public:\n  MatrixImageFilter(const skity::Matrix& matrix, ImageSampling sampling)\n      : matrix_(matrix), sampling_(sampling) {}\n  explicit MatrixImageFilter(const MatrixImageFilter* filter)\n      : MatrixImageFilter(filter->matrix_, filter->sampling_) {}\n  MatrixImageFilter(const MatrixImageFilter& filter)\n      : MatrixImageFilter(&filter) {}\n\n  std::shared_ptr<ImageFilter> shared() const override {\n    return std::make_shared<MatrixImageFilter>(this);\n  }\n\n  ImageFilterType type() const override { return ImageFilterType::kMatrix; }\n  size_t size() const override { return sizeof(*this); }\n\n  const skity::Matrix& matrix() const { return matrix_; }\n  ImageSampling sampling() const { return sampling_; }\n\n  const MatrixImageFilter* asMatrix() const override { return this; }\n\n  bool modifies_transparent_black() const override { return false; }\n\n  skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                skity::Rect& output_bounds) const override {\n    matrix_.MapRect(&output_bounds, input_bounds);\n    return &output_bounds;\n  }\n\n  skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                 const skity::Matrix& ctm,\n                                 skity::Rect& output_bounds) const override {\n    skity::Matrix matrix;\n    if (!ctm.Invert(&matrix)) {\n      output_bounds = input_bounds;\n      return nullptr;\n    }\n    matrix.PostConcat(matrix_);\n    matrix.PostConcat(ctm);\n    skity::Rect device_rect;\n    matrix.MapRect(&device_rect, input_bounds);\n    output_bounds = device_rect;\n    output_bounds.RoundOut();\n    return &output_bounds;\n  }\n\n  skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const override {\n    skity::Matrix matrix = ctm * matrix_;\n    skity::Matrix inverse;\n    if (!matrix.Invert(&inverse)) {\n      input_bounds = output_bounds;\n      return nullptr;\n    }\n    inverse.PostConcat(ctm);\n    skity::Rect bounds = output_bounds;\n    inverse.MapRect(&bounds, bounds);\n    input_bounds = bounds;\n    input_bounds.RoundOut();\n    return &input_bounds;\n  }\n\n  GrImageFilterPtr gr_object() const override {\n    return IMAGE_FILTERS_MATRIX_TRANSFORM(matrix_, ToSk(sampling_), nullptr);\n  }\n\n protected:\n  bool equals_(const ImageFilter& other) const override {\n    FML_DCHECK(other.type() == ImageFilterType::kMatrix);\n    auto that = static_cast<const MatrixImageFilter*>(&other);\n    return (matrix_ == that->matrix_ && sampling_ == that->sampling_);\n  }\n\n private:\n  skity::Matrix matrix_;\n  ImageSampling sampling_;\n};\n\nclass ComposeImageFilter final : public ImageFilter {\n public:\n  ComposeImageFilter(std::shared_ptr<ImageFilter> outer,\n                     std::shared_ptr<ImageFilter> inner)\n      : outer_(std::move(outer)), inner_(std::move(inner)) {}\n  ComposeImageFilter(const ImageFilter* outer, const ImageFilter* inner)\n      : outer_(outer->shared()), inner_(inner->shared()) {}\n  ComposeImageFilter(const ImageFilter& outer, const ImageFilter& inner)\n      : ComposeImageFilter(&outer, &inner) {}\n  explicit ComposeImageFilter(const ComposeImageFilter* filter)\n      : ComposeImageFilter(filter->outer_, filter->inner_) {}\n  ComposeImageFilter(const ComposeImageFilter& filter)\n      : ComposeImageFilter(&filter) {}\n\n  std::shared_ptr<ImageFilter> shared() const override {\n    return std::make_shared<ComposeImageFilter>(this);\n  }\n\n  ImageFilterType type() const override {\n    return ImageFilterType::kComposeFilter;\n  }\n  size_t size() const override { return sizeof(*this); }\n\n  std::shared_ptr<ImageFilter> outer() const { return outer_; }\n  std::shared_ptr<ImageFilter> inner() const { return inner_; }\n\n  const ComposeImageFilter* asCompose() const override { return this; }\n\n  bool modifies_transparent_black() const override {\n    if (inner_ && inner_->modifies_transparent_black()) {\n      return true;\n    }\n    if (outer_ && outer_->modifies_transparent_black()) {\n      return true;\n    }\n    return false;\n  }\n\n  skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                skity::Rect& output_bounds) const override;\n\n  skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                 const skity::Matrix& ctm,\n                                 skity::Rect& output_bounds) const override;\n\n  skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const override;\n\n  GrImageFilterPtr gr_object() const override {\n    return GrImageFilters::Compose(outer_->gr_object(), inner_->gr_object());\n  }\n\n  MatrixCapability matrix_capability() const override {\n    return std::min(outer_->matrix_capability(), inner_->matrix_capability());\n  }\n\n protected:\n  bool equals_(const ImageFilter& other) const override {\n    FML_DCHECK(other.type() == ImageFilterType::kComposeFilter);\n    auto that = static_cast<const ComposeImageFilter*>(&other);\n    return (Equals(outer_, that->outer_) && Equals(inner_, that->inner_));\n  }\n\n private:\n  std::shared_ptr<ImageFilter> outer_;\n  std::shared_ptr<ImageFilter> inner_;\n};\n\nclass ColorFilterImageFilter final : public ImageFilter {\n public:\n  explicit ColorFilterImageFilter(std::shared_ptr<ColorFilter> filter)\n      : color_filter_(std::move(filter)) {}\n  explicit ColorFilterImageFilter(const ColorFilter* filter)\n      : color_filter_(filter->shared()) {}\n  explicit ColorFilterImageFilter(const ColorFilter& filter)\n      : color_filter_(filter.shared()) {}\n  explicit ColorFilterImageFilter(const ColorFilterImageFilter* filter)\n      : ColorFilterImageFilter(filter->color_filter_) {}\n  ColorFilterImageFilter(const ColorFilterImageFilter& filter)\n      : ColorFilterImageFilter(&filter) {}\n\n  std::shared_ptr<ImageFilter> shared() const override {\n    return std::make_shared<ColorFilterImageFilter>(color_filter_);\n  }\n\n  ImageFilterType type() const override {\n    return ImageFilterType::kColorFilter;\n  }\n  size_t size() const override { return sizeof(*this); }\n\n  const std::shared_ptr<ColorFilter> color_filter() const {\n    return color_filter_;\n  }\n\n  const ColorFilterImageFilter* asColorFilter() const override { return this; }\n\n  bool modifies_transparent_black() const override {\n    if (color_filter_) {\n      return color_filter_->modifies_transparent_black();\n    }\n    return false;\n  }\n\n  skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                skity::Rect& output_bounds) const override {\n    output_bounds = input_bounds;\n    return modifies_transparent_black() ? nullptr : &output_bounds;\n  }\n\n  skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                 const skity::Matrix& ctm,\n                                 skity::Rect& output_bounds) const override {\n    output_bounds = input_bounds;\n    return modifies_transparent_black() ? nullptr : &output_bounds;\n  }\n\n  skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const override {\n    return map_device_bounds(output_bounds, ctm, input_bounds);\n  }\n\n  GrImageFilterPtr gr_object() const override {\n    return IMAGE_FILTERS_COLOR_FILTER(color_filter_->gr_object(), nullptr);\n  }\n\n  MatrixCapability matrix_capability() const override {\n    return MatrixCapability::kComplex;\n  }\n\n  std::shared_ptr<ImageFilter> makeWithLocalMatrix(\n      const skity::Matrix& matrix) const override {\n    return shared();\n  }\n\n protected:\n  bool equals_(const ImageFilter& other) const override {\n    FML_DCHECK(other.type() == ImageFilterType::kColorFilter);\n    auto that = static_cast<const ColorFilterImageFilter*>(&other);\n    return Equals(color_filter_, that->color_filter_);\n  }\n\n private:\n  std::shared_ptr<ColorFilter> color_filter_;\n};\n\nclass LocalMatrixImageFilter final : public ImageFilter {\n public:\n  explicit LocalMatrixImageFilter(const skity::Matrix& matrix,\n                                  std::shared_ptr<ImageFilter> filter)\n      : matrix_(matrix), image_filter_(filter) {}\n  explicit LocalMatrixImageFilter(const LocalMatrixImageFilter* filter)\n      : LocalMatrixImageFilter(filter->matrix_, filter->image_filter_) {}\n  LocalMatrixImageFilter(const LocalMatrixImageFilter& filter)\n      : LocalMatrixImageFilter(&filter) {}\n  std::shared_ptr<ImageFilter> shared() const override {\n    return std::make_shared<LocalMatrixImageFilter>(this);\n  }\n\n  ImageFilterType type() const override {\n    return ImageFilterType::kLocalMatrixFilter;\n  }\n  size_t size() const override { return sizeof(*this); }\n\n  const skity::Matrix& matrix() const { return matrix_; }\n\n  const std::shared_ptr<ImageFilter> image_filter() const {\n    return image_filter_;\n  }\n\n  const LocalMatrixImageFilter* asLocalMatrix() const override { return this; }\n\n  bool modifies_transparent_black() const override {\n    if (!image_filter_) {\n      return false;\n    }\n    return image_filter_->modifies_transparent_black();\n  }\n\n  skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                skity::Rect& output_bounds) const override {\n    if (!image_filter_) {\n      return nullptr;\n    }\n    return image_filter_->map_local_bounds(input_bounds, output_bounds);\n  }\n\n  skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                 const skity::Matrix& ctm,\n                                 skity::Rect& output_bounds) const override {\n    if (!image_filter_) {\n      return nullptr;\n    }\n    return image_filter_->map_device_bounds(input_bounds, ctm * matrix_,\n                                            output_bounds);\n  }\n\n  skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const override {\n    if (!image_filter_) {\n      return nullptr;\n    }\n    return image_filter_->get_input_device_bounds(output_bounds, ctm * matrix_,\n                                                  input_bounds);\n  }\n\n  GrImageFilterPtr gr_object() const override {\n#ifndef ENABLE_SKITY\n    if (!image_filter_) {\n      return nullptr;\n    }\n    sk_sp<SkImageFilter> gr_object = image_filter_->gr_object();\n    if (!gr_object) {\n      return nullptr;\n    }\n    return gr_object->makeWithLocalMatrix(\n        ConvertSkityMatrixToSkMatrix(matrix_));\n\n#else\n    FML_UNIMPLEMENTED();\n    return nullptr;\n#endif  // ENABLE_SKITY\n  }\n\n protected:\n  bool equals_(const ImageFilter& other) const override {\n    FML_DCHECK(other.type() == ImageFilterType::kMatrix);\n    auto that = static_cast<const LocalMatrixImageFilter*>(&other);\n    return (matrix_ == that->matrix_ &&\n            Equals(image_filter_, that->image_filter_));\n  }\n\n private:\n  skity::Matrix matrix_;\n  std::shared_ptr<ImageFilter> image_filter_;\n};\n\nclass UnknownImageFilter final : public ImageFilter {\n public:\n  explicit UnknownImageFilter(GrImageFilterPtr sk_filter)\n      : sk_filter_(std::move(sk_filter)) {}\n  explicit UnknownImageFilter(const GrImageFilter* sk_filter)\n#ifndef ENABLE_SKITY\n      : sk_filter_(sk_ref_sp(sk_filter)){}\n#else\n      : sk_filter_((const_cast<skity::ImageFilter*>(sk_filter))) {\n  }\n#endif  // ENABLE_SKITY\n        explicit UnknownImageFilter(const UnknownImageFilter* filter)\n      : UnknownImageFilter(filter->sk_filter_) {\n  }\n  UnknownImageFilter(const UnknownImageFilter& filter)\n      : UnknownImageFilter(&filter) {}\n\n  ImageFilterType type() const override { return ImageFilterType::kUnknown; }\n  size_t size() const override { return sizeof(*this); }\n\n  std::shared_ptr<ImageFilter> shared() const override {\n    return std::make_shared<UnknownImageFilter>(this);\n  }\n\n  bool modifies_transparent_black() const override {\n    if (!sk_filter_) {\n      return false;\n    }\n#ifndef ENABLE_SKITY\n    return !sk_filter_->canComputeFastBounds();\n#else\n    return false;\n#endif  // ENABLE_SKITY\n  }\n\n  skity::Rect* map_local_bounds(const skity::Rect& input_bounds,\n                                skity::Rect& output_bounds) const override {\n    if (!sk_filter_ || modifies_transparent_black()) {\n      output_bounds = input_bounds;\n      return nullptr;\n    }\n#ifndef ENABLE_SKITY\n    output_bounds =\n        clay::ConvertSkRectToSkityRect(sk_filter_->computeFastBounds(\n            clay::ConvertSkityRectToSkRect(input_bounds)));\n#endif  // ENABLE_SKITY\n    return &output_bounds;\n  }\n\n  skity::Rect* map_device_bounds(const skity::Rect& input_bounds,\n                                 const skity::Matrix& ctm,\n                                 skity::Rect& output_bounds) const override {\n    if (!sk_filter_ || modifies_transparent_black()) {\n      output_bounds = input_bounds;\n      return nullptr;\n    }\n#ifndef ENABLE_SKITY\n    output_bounds = ConvertSkIRectToSkityRect(\n        sk_filter_->filterBounds(ConvertSkityRectToSkIRect(input_bounds),\n                                 ConvertSkityMatrixToSkMatrix(ctm),\n                                 SkImageFilter::kForward_MapDirection));\n#endif  // ENABLE_SKITY\n    return &output_bounds;\n  }\n\n  skity::Rect* get_input_device_bounds(\n      const skity::Rect& output_bounds, const skity::Matrix& ctm,\n      skity::Rect& input_bounds) const override {\n    if (!sk_filter_ || modifies_transparent_black()) {\n      input_bounds = output_bounds;\n      return nullptr;\n    }\n#ifndef ENABLE_SKITY\n    input_bounds = ConvertSkIRectToSkityRect(\n        sk_filter_->filterBounds(ConvertSkityRectToSkIRect(output_bounds),\n                                 ConvertSkityMatrixToSkMatrix(ctm),\n                                 SkImageFilter::kReverse_MapDirection));\n#endif  // ENABLE_SKITY\n    return &input_bounds;\n  }\n\n  GrImageFilterPtr gr_object() const override { return sk_filter_; }\n\n  virtual ~UnknownImageFilter() = default;\n\n protected:\n  bool equals_(const ImageFilter& other) const override {\n    FML_DCHECK(other.type() == ImageFilterType::kUnknown);\n    auto that = static_cast<UnknownImageFilter const*>(&other);\n    return sk_filter_ == that->sk_filter_;\n  }\n\n private:\n  GrImageFilterPtr sk_filter_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_IMAGE_FILTER_H_\n"
  },
  {
    "path": "clay/gfx/style/image_filter_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/attributes_testing.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(DisplayListImageFilter, FromSkiaBlurImageFilter) {\n  std::shared_ptr<DlImageFilter> filter =\n      ImageFilter::MakeBlur(5.0, 5.0, TileMode::kRepeat);\n\n  ASSERT_EQ(filter->type(), DlImageFilterType::kBlur);\n\n  // We cannot recapture the blur parameters from an SkBlurImageFilter\n  ASSERT_EQ(filter->asBlur(), filter.get());\n  ASSERT_EQ(filter->asDilate(), nullptr);\n  ASSERT_EQ(filter->asErode(), nullptr);\n  ASSERT_EQ(filter->asMatrix(), nullptr);\n  ASSERT_EQ(filter->asCompose(), nullptr);\n  ASSERT_EQ(filter->asColorFilter(), nullptr);\n}\n\nTEST(DisplayListImageFilter, FromSkiaDilateImageFilter) {\n  std::shared_ptr<DlImageFilter> filter = ImageFilter::MakeDilate(5.0, 5.0);\n\n  ASSERT_EQ(filter->type(), DlImageFilterType::kDilate);\n\n  // We cannot recapture the dilate parameters from an SkDilateImageFilter\n  ASSERT_EQ(filter->asBlur(), nullptr);\n  ASSERT_EQ(filter->asDilate(), filter.get());\n  ASSERT_EQ(filter->asErode(), nullptr);\n  ASSERT_EQ(filter->asMatrix(), nullptr);\n  ASSERT_EQ(filter->asCompose(), nullptr);\n  ASSERT_EQ(filter->asColorFilter(), nullptr);\n}\n\nTEST(DisplayListImageFilter, FromSkiaErodeImageFilter) {\n  std::shared_ptr<DlImageFilter> filter = ImageFilter::MakeErode(5.0, 5.0);\n\n  ASSERT_EQ(filter->type(), DlImageFilterType::kErode);\n\n  // We cannot recapture the erode parameters from an SkErodeImageFilter\n  ASSERT_EQ(filter->asBlur(), nullptr);\n  ASSERT_EQ(filter->asDilate(), nullptr);\n  ASSERT_EQ(filter->asErode(), filter.get());\n  ASSERT_EQ(filter->asMatrix(), nullptr);\n  ASSERT_EQ(filter->asCompose(), nullptr);\n  ASSERT_EQ(filter->asColorFilter(), nullptr);\n}\n\nTEST(DisplayListImageFilter, FromSkiaMatrixImageFilter) {\n  skity::Matrix matrix = skity::Matrix::RotateDeg(45);\n  std::shared_ptr<DlImageFilter> filter =\n      ImageFilter::MakeMatrix(matrix, ImageSampling::kLinear);\n\n  ASSERT_EQ(filter->type(), DlImageFilterType::kMatrix);\n\n  // We cannot recapture the blur parameters from an SkMatrixImageFilter\n  ASSERT_EQ(filter->asBlur(), nullptr);\n  ASSERT_EQ(filter->asDilate(), nullptr);\n  ASSERT_EQ(filter->asErode(), nullptr);\n  ASSERT_EQ(filter->asMatrix(), filter.get());\n  ASSERT_EQ(filter->asCompose(), nullptr);\n  ASSERT_EQ(filter->asColorFilter(), nullptr);\n}\n\nTEST(DisplayListImageFilter, FromSkiaComposeImageFilter) {\n  std::shared_ptr<DlImageFilter> sk_blur_filter =\n      ImageFilter::MakeBlur(5.0, 5.0, TileMode::kRepeat);\n  std::shared_ptr<DlImageFilter> sk_matrix_filter = ImageFilter::MakeMatrix(\n      skity::Matrix::RotateDeg(45), ImageSampling::kLinear);\n  std::shared_ptr<DlImageFilter> filter =\n      ImageFilter::MakeCompose(sk_blur_filter, sk_matrix_filter);\n\n  ASSERT_EQ(filter->type(), DlImageFilterType::kComposeFilter);\n\n  // We cannot recapture the blur parameters from an SkComposeImageFilter\n  ASSERT_EQ(filter->asBlur(), nullptr);\n  ASSERT_EQ(filter->asDilate(), nullptr);\n  ASSERT_EQ(filter->asErode(), nullptr);\n  ASSERT_EQ(filter->asMatrix(), nullptr);\n  ASSERT_EQ(filter->asCompose(), filter.get());\n  ASSERT_EQ(filter->asColorFilter(), nullptr);\n}\n\nTEST(DisplayListImageFilter, FromSkiaColorFilterImageFilter) {\n  std::shared_ptr<ColorFilter> color_filter =\n      ColorFilter::MakeBlend(Color::kRed(), BlendMode::kSrcIn);\n  std::shared_ptr<ImageFilter> filter =\n      ImageFilter::MakeColorFilter(color_filter);\n\n  ASSERT_EQ(filter->type(), DlImageFilterType::kColorFilter);\n\n  ASSERT_EQ(filter->asColorFilter()->color_filter(), color_filter);\n\n  ASSERT_EQ(filter->asBlur(), nullptr);\n  ASSERT_EQ(filter->asDilate(), nullptr);\n  ASSERT_EQ(filter->asErode(), nullptr);\n  ASSERT_EQ(filter->asMatrix(), nullptr);\n  ASSERT_EQ(filter->asCompose(), nullptr);\n  ASSERT_NE(filter->asColorFilter(), nullptr);\n}\n\n// SkRect::contains treats the rect as a half-open interval which is\n// appropriate for so many operations. Unfortunately, we are using\n// it here to test containment of the corners of a transformed quad\n// so the corners of the quad that are measured against the right\n// and bottom edges are contained even if they are on the right or\n// bottom edge. This method does the \"all sides inclusive\" version\n// of SkRect::contains.\nstatic bool containsInclusive(const SkRect rect, const SkPoint p) {\n  // Test with a slight offset of 1E-9 to \"forgive\" IEEE bit-rounding\n  // Ending up with bounds that are off by 1E-9 (these numbers are all\n  // being tested in device space with this method) will be off by a\n  // negligible amount of a pixel that wouldn't contribute to changing\n  // the color of a pixel.\n  return (p.fX >= rect.fLeft - 1E-9 &&   //\n          p.fX <= rect.fRight + 1E-9 &&  //\n          p.fY >= rect.fTop - 1E-9 &&    //\n          p.fY <= rect.fBottom + 1E-9);\n}\n\nstatic bool containsInclusive(const SkRect rect, const SkPoint quad[4]) {\n  return (containsInclusive(rect, quad[0]) &&  //\n          containsInclusive(rect, quad[1]) &&  //\n          containsInclusive(rect, quad[2]) &&  //\n          containsInclusive(rect, quad[3]));\n}\n\nstatic bool containsInclusive(const SkIRect rect, const SkPoint quad[4]) {\n  return containsInclusive(SkRect::Make(rect), quad);\n}\n\nstatic bool containsInclusive(const SkIRect rect, const SkRect bounds) {\n  return (bounds.fLeft >= rect.fLeft - 1E-9 &&\n          bounds.fTop >= rect.fTop - 1E-9 &&\n          bounds.fRight <= rect.fRight + 1E-9 &&\n          bounds.fBottom <= rect.fBottom + 1E-9);\n}\n\n// Used to verify that the expected output bounds and reverse-engineered\n// \"input bounds for output bounds\" rectangles are included in the rectangle\n// returned from the various bounds computation methods under the specified\n// matrix.\nstatic void TestBoundsWithMatrix(const DlImageFilter& filter,\n                                 const SkMatrix& matrix,\n                                 const SkRect& sourceBounds,\n                                 const SkPoint expectedLocalOutputQuad[4]) {\n  SkRect device_input_bounds = matrix.mapRect(sourceBounds);\n  SkPoint expected_output_quad[4];\n  matrix.mapPoints(expected_output_quad, expectedLocalOutputQuad, 4);\n\n  skity::Rect device_filter_ibounds;\n  ASSERT_EQ(\n      filter.map_device_bounds(\n          clay::ConvertSkIRectToSkityRect(device_input_bounds.roundOut()),\n          clay::ConvertSkMatrixToSkityMatrix(matrix), device_filter_ibounds),\n      &device_filter_ibounds);\n\n  ASSERT_TRUE(\n      containsInclusive(clay::ConvertSkityRectToSkIRect(device_filter_ibounds),\n                        expected_output_quad));\n\n  skity::Rect reverse_input_ibounds;\n  ASSERT_EQ(\n      filter.get_input_device_bounds(device_filter_ibounds,\n                                     clay::ConvertSkMatrixToSkityMatrix(matrix),\n                                     reverse_input_ibounds),\n      &reverse_input_ibounds);\n  ASSERT_TRUE(\n      containsInclusive(clay::ConvertSkityRectToSkIRect(reverse_input_ibounds),\n                        device_input_bounds));\n}\n\nstatic void TestInvalidBounds(const DlImageFilter& filter,\n                              const SkMatrix& matrix,\n                              const SkRect& localInputBounds) {\n  SkIRect device_input_bounds = matrix.mapRect(localInputBounds).roundOut();\n\n  skity::Rect local_filter_bounds;\n  ASSERT_EQ(\n      filter.map_local_bounds(clay::ConvertSkRectToSkityRect(localInputBounds),\n                              local_filter_bounds),\n      nullptr);\n  ASSERT_EQ(local_filter_bounds,\n            clay::ConvertSkRectToSkityRect(localInputBounds));\n\n  skity::Rect device_filter_ibounds;\n  ASSERT_EQ(\n      filter.map_device_bounds(\n          clay::ConvertSkIRectToSkityRect(device_input_bounds),\n          clay::ConvertSkMatrixToSkityMatrix(matrix), device_filter_ibounds),\n      nullptr);\n  ASSERT_EQ(device_filter_ibounds,\n            clay::ConvertSkIRectToSkityRect(device_input_bounds));\n\n  skity::Rect reverse_input_ibounds;\n  ASSERT_EQ(\n      filter.get_input_device_bounds(\n          clay::ConvertSkIRectToSkityRect(device_input_bounds),\n          clay::ConvertSkMatrixToSkityMatrix(matrix), reverse_input_ibounds),\n      nullptr);\n  ASSERT_EQ(reverse_input_ibounds,\n            clay::ConvertSkIRectToSkityRect(device_input_bounds));\n}\n\n// localInputBounds is a sample bounds for testing as input to the filter.\n// localExpectOutputBounds is the theoretical output bounds for applying\n// the filter to the localInputBounds.\n// localExpectInputBounds is the theoretical input bounds required for the\n// filter to cover the localExpectOutputBounds\n// If either of the expected bounds are nullptr then the bounds methods will\n// be assumed to be unable to perform their computations for the given\n// image filter and will be returning null.\nstatic void TestBounds(const DlImageFilter& filter, const SkRect& sourceBounds,\n                       const SkPoint expectedLocalOutputQuad[4]) {\n  skity::Rect local_filter_bounds;\n  ASSERT_EQ(\n      filter.map_local_bounds(clay::ConvertSkRectToSkityRect(sourceBounds),\n                              local_filter_bounds),\n      &local_filter_bounds);\n  ASSERT_TRUE(\n      containsInclusive(clay::ConvertSkityRectToSkRect(local_filter_bounds),\n                        expectedLocalOutputQuad));\n\n  for (int scale = 1; scale <= 4; scale++) {\n    for (int skew = 0; skew < 8; skew++) {\n      for (int degrees = 0; degrees <= 360; degrees += 15) {\n        SkMatrix matrix;\n        matrix.setScale(scale, scale);\n        matrix.postSkew(skew / 8.0, skew / 8.0);\n        matrix.postRotate(degrees);\n        ASSERT_TRUE(matrix.invert(nullptr));\n        TestBoundsWithMatrix(filter, matrix, sourceBounds,\n                             expectedLocalOutputQuad);\n        matrix.setPerspX(0.001);\n        matrix.setPerspY(0.001);\n        ASSERT_TRUE(matrix.invert(nullptr));\n        TestBoundsWithMatrix(filter, matrix, sourceBounds,\n                             expectedLocalOutputQuad);\n      }\n    }\n  }\n}\n\nstatic void TestBounds(const DlImageFilter& filter, const SkRect& sourceBounds,\n                       const SkRect& expectedLocalOutputBounds) {\n  SkPoint expected_local_output_quad[4];\n  expectedLocalOutputBounds.toQuad(expected_local_output_quad);\n  TestBounds(filter, sourceBounds, expected_local_output_quad);\n}\n\nTEST(DisplayListImageFilter, BlurConstructor) {\n  DlBlurImageFilter filter(5.0, 6.0, DlTileMode::kMirror);\n}\n\nTEST(DisplayListImageFilter, BlurShared) {\n  DlBlurImageFilter filter(5.0, 6.0, DlTileMode::kMirror);\n\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListImageFilter, BlurAsBlur) {\n  DlBlurImageFilter filter(5.0, 6.0, DlTileMode::kMirror);\n\n  ASSERT_NE(filter.asBlur(), nullptr);\n  ASSERT_EQ(filter.asBlur(), &filter);\n}\n\nTEST(DisplayListImageFilter, BlurContents) {\n  DlBlurImageFilter filter(5.0, 6.0, DlTileMode::kMirror);\n\n  ASSERT_EQ(filter.sigma_x(), 5.0);\n  ASSERT_EQ(filter.sigma_y(), 6.0);\n  ASSERT_EQ(filter.tile_mode(), DlTileMode::kMirror);\n}\n\nTEST(DisplayListImageFilter, BlurEquals) {\n  DlBlurImageFilter filter1(5.0, 6.0, DlTileMode::kMirror);\n  DlBlurImageFilter filter2(5.0, 6.0, DlTileMode::kMirror);\n\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListImageFilter, BlurNotEquals) {\n  DlBlurImageFilter filter1(5.0, 6.0, DlTileMode::kMirror);\n  DlBlurImageFilter filter2(7.0, 6.0, DlTileMode::kMirror);\n  DlBlurImageFilter filter3(5.0, 8.0, DlTileMode::kMirror);\n  DlBlurImageFilter filter4(5.0, 6.0, DlTileMode::kRepeat);\n\n  TestNotEquals(filter1, filter2, \"Sigma X differs\");\n  TestNotEquals(filter1, filter3, \"Sigma Y differs\");\n  TestNotEquals(filter1, filter4, \"Tile Mode differs\");\n}\n\nTEST(DisplayListImageFilter, BlurBounds) {\n  DlBlurImageFilter filter = DlBlurImageFilter(5, 10, DlTileMode::kDecal);\n  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n  SkRect expected_output_bounds = input_bounds.makeOutset(15, 30);\n  TestBounds(filter, input_bounds, expected_output_bounds);\n}\n\nTEST(DisplayListImageFilter, DilateConstructor) {\n  DlDilateImageFilter filter(5.0, 6.0);\n}\n\nTEST(DisplayListImageFilter, DilateShared) {\n  DlDilateImageFilter filter(5.0, 6.0);\n\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListImageFilter, DilateAsDilate) {\n  DlDilateImageFilter filter(5.0, 6.0);\n\n  ASSERT_NE(filter.asDilate(), nullptr);\n  ASSERT_EQ(filter.asDilate(), &filter);\n}\n\nTEST(DisplayListImageFilter, DilateContents) {\n  DlDilateImageFilter filter(5.0, 6.0);\n\n  ASSERT_EQ(filter.radius_x(), 5.0);\n  ASSERT_EQ(filter.radius_y(), 6.0);\n}\n\nTEST(DisplayListImageFilter, DilateEquals) {\n  DlDilateImageFilter filter1(5.0, 6.0);\n  DlDilateImageFilter filter2(5.0, 6.0);\n\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListImageFilter, DilateNotEquals) {\n  DlDilateImageFilter filter1(5.0, 6.0);\n  DlDilateImageFilter filter2(7.0, 6.0);\n  DlDilateImageFilter filter3(5.0, 8.0);\n\n  TestNotEquals(filter1, filter2, \"Radius X differs\");\n  TestNotEquals(filter1, filter3, \"Radius Y differs\");\n}\n\nTEST(DisplayListImageFilter, DilateBounds) {\n  DlDilateImageFilter filter = DlDilateImageFilter(5, 10);\n  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n  SkRect expected_output_bounds = input_bounds.makeOutset(5, 10);\n  TestBounds(filter, input_bounds, expected_output_bounds);\n}\n\nTEST(DisplayListImageFilter, ErodeConstructor) {\n  DlErodeImageFilter filter(5.0, 6.0);\n}\n\nTEST(DisplayListImageFilter, ErodeShared) {\n  DlErodeImageFilter filter(5.0, 6.0);\n\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListImageFilter, ErodeAsErode) {\n  DlErodeImageFilter filter(5.0, 6.0);\n\n  ASSERT_NE(filter.asErode(), nullptr);\n  ASSERT_EQ(filter.asErode(), &filter);\n}\n\nTEST(DisplayListImageFilter, ErodeContents) {\n  DlErodeImageFilter filter(5.0, 6.0);\n\n  ASSERT_EQ(filter.radius_x(), 5.0);\n  ASSERT_EQ(filter.radius_y(), 6.0);\n}\n\nTEST(DisplayListImageFilter, ErodeEquals) {\n  DlErodeImageFilter filter1(5.0, 6.0);\n  DlErodeImageFilter filter2(5.0, 6.0);\n\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListImageFilter, ErodeNotEquals) {\n  DlErodeImageFilter filter1(5.0, 6.0);\n  DlErodeImageFilter filter2(7.0, 6.0);\n  DlErodeImageFilter filter3(5.0, 8.0);\n\n  TestNotEquals(filter1, filter2, \"Radius X differs\");\n  TestNotEquals(filter1, filter3, \"Radius Y differs\");\n}\n\nTEST(DisplayListImageFilter, ErodeBounds) {\n  DlErodeImageFilter filter = DlErodeImageFilter(5, 10);\n  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n  SkRect expected_output_bounds = input_bounds.makeInset(5, 10);\n  TestBounds(filter, input_bounds, expected_output_bounds);\n}\n\nTEST(DisplayListImageFilter, MatrixConstructor) {\n  DlMatrixImageFilter filter(skity::Matrix(2.0, 0.0, 10,  //\n                                           0.5, 3.0, 15,  //\n                                           0.0, 0.0, 1),\n                             DlImageSampling::kLinear);\n}\n\nTEST(DisplayListImageFilter, MatrixShared) {\n  DlMatrixImageFilter filter(skity::Matrix(2.0, 0.0, 10,  //\n                                           0.5, 3.0, 15,  //\n                                           0.0, 0.0, 1),\n                             DlImageSampling::kLinear);\n\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListImageFilter, MatrixAsMatrix) {\n  DlMatrixImageFilter filter(skity::Matrix(2.0, 0.0, 10,  //\n                                           0.5, 3.0, 15,  //\n                                           0.0, 0.0, 1),\n                             DlImageSampling::kLinear);\n\n  ASSERT_NE(filter.asMatrix(), nullptr);\n  ASSERT_EQ(filter.asMatrix(), &filter);\n}\n\nTEST(DisplayListImageFilter, MatrixContents) {\n  skity::Matrix matrix = skity::Matrix(2.0, 0.0, 10,  //\n                                       0.5, 3.0, 15,  //\n                                       0.0, 0.0, 1);\n  DlMatrixImageFilter filter(matrix, DlImageSampling::kLinear);\n\n  ASSERT_EQ(filter.matrix(), matrix);\n  ASSERT_EQ(filter.sampling(), DlImageSampling::kLinear);\n}\n\nTEST(DisplayListImageFilter, MatrixEquals) {\n  skity::Matrix matrix = skity::Matrix(2.0, 0.0, 10,  //\n                                       0.5, 3.0, 15,  //\n                                       0.0, 0.0, 1);\n  DlMatrixImageFilter filter1(matrix, DlImageSampling::kLinear);\n  DlMatrixImageFilter filter2(matrix, DlImageSampling::kLinear);\n\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListImageFilter, MatrixNotEquals) {\n  skity::Matrix matrix1 = skity::Matrix(2.0, 0.0, 10,  //\n                                        0.5, 3.0, 15,  //\n                                        0.0, 0.0, 1);\n  skity::Matrix matrix2 = skity::Matrix(5.0, 0.0, 10,  //\n                                        0.5, 3.0, 15,  //\n                                        0.0, 0.0, 1);\n  DlMatrixImageFilter filter1(matrix1, DlImageSampling::kLinear);\n  DlMatrixImageFilter filter2(matrix2, DlImageSampling::kLinear);\n  DlMatrixImageFilter filter3(matrix1, DlImageSampling::kNearestNeighbor);\n\n  TestNotEquals(filter1, filter2, \"Matrix differs\");\n  TestNotEquals(filter1, filter3, \"Sampling differs\");\n}\n\nTEST(DisplayListImageFilter, MatrixBounds) {\n  skity::Matrix matrix = skity::Matrix(2.0, 0.0, 10,  //\n                                       0.5, 3.0, 7,   //\n                                       0.0, 0.0, 1);\n  skity::Matrix inverse;\n  ASSERT_TRUE(matrix.Invert(&inverse));\n  DlMatrixImageFilter filter(matrix, DlImageSampling::kLinear);\n  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n  SkPoint expectedOutputQuad[4] = {\n      {50, 77},    // (20,20) => (20*2 + 10, 20/2 + 20*3 + 7) == (50, 77)\n      {50, 257},   // (20,80) => (20*2 + 10, 20/2 + 80*3 + 7) == (50, 257)\n      {170, 287},  // (80,80) => (80*2 + 10, 80/2 + 80*3 + 7) == (170, 287)\n      {170, 107},  // (80,20) => (80*2 + 10, 80/2 + 20*3 + 7) == (170, 107)\n  };\n  TestBounds(filter, input_bounds, expectedOutputQuad);\n}\n\nTEST(DisplayListImageFilter, ComposeConstructor) {\n  DlMatrixImageFilter outer(skity::Matrix(2.0, 0.0, 10,  //\n                                          0.5, 3.0, 15,  //\n                                          0.0, 0.0, 1),\n                            DlImageSampling::kLinear);\n  DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror);\n  DlComposeImageFilter filter(outer, inner);\n}\n\nTEST(DisplayListImageFilter, ComposeShared) {\n  DlMatrixImageFilter outer(skity::Matrix(2.0, 0.0, 10,  //\n                                          0.5, 3.0, 15,  //\n                                          0.0, 0.0, 1),\n                            DlImageSampling::kLinear);\n  DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror);\n  DlComposeImageFilter filter(outer, inner);\n\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListImageFilter, ComposeAsCompose) {\n  DlMatrixImageFilter outer(skity::Matrix(2.0, 0.0, 10,  //\n                                          0.5, 3.0, 15,  //\n                                          0.0, 0.0, 1),\n                            DlImageSampling::kLinear);\n  DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror);\n  DlComposeImageFilter filter(outer, inner);\n\n  ASSERT_NE(filter.asCompose(), nullptr);\n  ASSERT_EQ(filter.asCompose(), &filter);\n}\n\nTEST(DisplayListImageFilter, ComposeContents) {\n  DlMatrixImageFilter outer(skity::Matrix(2.0, 0.0, 10,  //\n                                          0.5, 3.0, 15,  //\n                                          0.0, 0.0, 1),\n                            DlImageSampling::kLinear);\n  DlBlurImageFilter inner(5.0, 6.0, DlTileMode::kMirror);\n  DlComposeImageFilter filter(outer, inner);\n\n  ASSERT_EQ(*filter.outer().get(), outer);\n  ASSERT_EQ(*filter.inner().get(), inner);\n}\n\nTEST(DisplayListImageFilter, ComposeEquals) {\n  DlMatrixImageFilter outer1(skity::Matrix(2.0, 0.0, 10,  //\n                                           0.5, 3.0, 15,  //\n                                           0.0, 0.0, 1),\n                             DlImageSampling::kLinear);\n  DlBlurImageFilter inner1(5.0, 6.0, DlTileMode::kMirror);\n  DlComposeImageFilter filter1(outer1, inner1);\n\n  DlMatrixImageFilter outer2(skity::Matrix(2.0, 0.0, 10,  //\n                                           0.5, 3.0, 15,  //\n                                           0.0, 0.0, 1),\n                             DlImageSampling::kLinear);\n  DlBlurImageFilter inner2(5.0, 6.0, DlTileMode::kMirror);\n  DlComposeImageFilter filter2(outer1, inner1);\n\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListImageFilter, ComposeNotEquals) {\n  DlMatrixImageFilter outer1(skity::Matrix(2.0, 0.0, 10,  //\n                                           0.5, 3.0, 15,  //\n                                           0.0, 0.0, 1),\n                             DlImageSampling::kLinear);\n  DlBlurImageFilter inner1(5.0, 6.0, DlTileMode::kMirror);\n\n  DlMatrixImageFilter outer2(skity::Matrix(5.0, 0.0, 10,  //\n                                           0.5, 3.0, 15,  //\n                                           0.0, 0.0, 1),\n                             DlImageSampling::kLinear);\n  DlBlurImageFilter inner2(7.0, 6.0, DlTileMode::kMirror);\n\n  DlComposeImageFilter filter1(outer1, inner1);\n  DlComposeImageFilter filter2(outer2, inner1);\n  DlComposeImageFilter filter3(outer1, inner2);\n\n  TestNotEquals(filter1, filter2, \"Outer differs\");\n  TestNotEquals(filter1, filter3, \"Inner differs\");\n}\n\n// INFO: skity::Matrix::MapRect does not consider SkApplyPerspectiveClip and\n// this test will fail. So we temporarily disable this test.\n// TEST(DisplayListImageFilter, ComposeBounds) {\n//   DlDilateImageFilter outer = DlDilateImageFilter(5, 10);\n//   DlBlurImageFilter inner = DlBlurImageFilter(12, 5, DlTileMode::kDecal);\n//   DlComposeImageFilter filter = DlComposeImageFilter(outer, inner);\n//   SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n//   SkRect expected_output_bounds =\n//       input_bounds.makeOutset(36, 15).makeOutset(5, 10);\n//   TestBounds(filter, input_bounds, expected_output_bounds);\n// }\n\nstatic void TestUnboundedBounds(DlImageFilter& filter,\n                                const SkRect& sourceBounds,\n                                const SkRect& expectedOutputBounds,\n                                const SkRect& expectedInputBounds) {\n  skity::Rect bounds;\n  EXPECT_EQ(filter.map_local_bounds(\n                clay::ConvertSkRectToSkityRect(sourceBounds), bounds),\n            nullptr);\n  EXPECT_EQ(bounds, clay::ConvertSkRectToSkityRect(expectedOutputBounds));\n\n  skity::Rect ibounds;\n  EXPECT_EQ(filter.map_device_bounds(\n                clay::ConvertSkIRectToSkityRect(sourceBounds.roundOut()),\n                skity::Matrix(), ibounds),\n            nullptr);\n  EXPECT_EQ(ibounds,\n            clay::ConvertSkIRectToSkityRect(expectedOutputBounds.roundOut()));\n\n  EXPECT_EQ(filter.get_input_device_bounds(\n                clay::ConvertSkIRectToSkityRect(sourceBounds.roundOut()),\n                skity::Matrix(), ibounds),\n            nullptr);\n  EXPECT_EQ(ibounds,\n            clay::ConvertSkIRectToSkityRect(expectedInputBounds.roundOut()));\n}\n\nTEST(DisplayListImageFilter, ComposeBoundsWithUnboundedInner) {\n  auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n  auto expected_bounds = SkRect::MakeLTRB(5, 2, 95, 98);\n\n  DlBlendColorFilter color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);\n  auto outer = DlBlurImageFilter(5.0, 6.0, DlTileMode::kRepeat);\n  auto inner = DlColorFilterImageFilter(color_filter.shared());\n  auto composed = DlComposeImageFilter(outer.shared(), inner.shared());\n\n  TestUnboundedBounds(composed, input_bounds, expected_bounds, expected_bounds);\n}\n\nTEST(DisplayListImageFilter, ComposeBoundsWithUnboundedOuter) {\n  auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n  auto expected_bounds = SkRect::MakeLTRB(5, 2, 95, 98);\n\n  DlBlendColorFilter color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);\n  auto outer = DlColorFilterImageFilter(color_filter.shared());\n  auto inner = DlBlurImageFilter(5.0, 6.0, DlTileMode::kRepeat);\n  auto composed = DlComposeImageFilter(outer.shared(), inner.shared());\n\n  TestUnboundedBounds(composed, input_bounds, expected_bounds, expected_bounds);\n}\n\nTEST(DisplayListImageFilter, ComposeBoundsWithUnboundedInnerAndOuter) {\n  auto input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n  auto expected_bounds = input_bounds;\n\n  DlBlendColorFilter color_filter1(DlColor::kRed(), DlBlendMode::kSrcOver);\n  DlBlendColorFilter color_filter2(DlColor::kBlue(), DlBlendMode::kSrcOver);\n  auto outer = DlColorFilterImageFilter(color_filter1.shared());\n  auto inner = DlColorFilterImageFilter(color_filter2.shared());\n  auto composed = DlComposeImageFilter(outer.shared(), inner.shared());\n\n  TestUnboundedBounds(composed, input_bounds, expected_bounds, expected_bounds);\n}\n\n// See https://github.com/flutter/flutter/issues/108433\nTEST(DisplayListImageFilter, Issue108433) {\n  auto input_bounds = SkIRect::MakeLTRB(20, 20, 80, 80);\n\n  auto sk_filter = SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcOver);\n  auto sk_outer = SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr);\n  auto sk_inner = SkImageFilters::ColorFilter(sk_filter, nullptr);\n  auto sk_compose = SkImageFilters::Compose(sk_outer, sk_inner);\n\n  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);\n  auto dl_outer = DlBlurImageFilter(5.0, 6.0, DlTileMode::kRepeat);\n  auto dl_inner = DlColorFilterImageFilter(dl_color_filter.shared());\n  auto dl_compose = DlComposeImageFilter(dl_outer, dl_inner);\n\n  auto sk_bounds = sk_compose->filterBounds(\n      input_bounds, SkMatrix::I(),\n      SkImageFilter::MapDirection::kForward_MapDirection);\n\n  skity::Rect dl_bounds;\n  EXPECT_EQ(dl_compose.map_device_bounds(\n                clay::ConvertSkIRectToSkityRect(input_bounds), skity::Matrix(),\n                dl_bounds),\n            nullptr);\n  ASSERT_EQ(dl_bounds, clay::ConvertSkIRectToSkityRect(sk_bounds));\n}\n\nTEST(DisplayListImageFilter, ColorFilterConstructor) {\n  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kLighten);\n  DlColorFilterImageFilter filter(dl_color_filter);\n}\n\nTEST(DisplayListImageFilter, ColorFilterShared) {\n  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kLighten);\n  DlColorFilterImageFilter filter(dl_color_filter);\n\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListImageFilter, ColorFilterAsColorFilter) {\n  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kLighten);\n  DlColorFilterImageFilter filter(dl_color_filter);\n\n  ASSERT_NE(filter.asColorFilter(), nullptr);\n  ASSERT_EQ(filter.asColorFilter(), &filter);\n}\n\nTEST(DisplayListImageFilter, ColorFilterContents) {\n  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kLighten);\n  DlColorFilterImageFilter filter(dl_color_filter);\n\n  ASSERT_EQ(*filter.color_filter().get(), dl_color_filter);\n}\n\nTEST(DisplayListImageFilter, ColorFilterEquals) {\n  DlBlendColorFilter dl_color_filter1(DlColor::kRed(), DlBlendMode::kLighten);\n  DlColorFilterImageFilter filter1(dl_color_filter1);\n\n  DlBlendColorFilter dl_color_filter2(DlColor::kRed(), DlBlendMode::kLighten);\n  DlColorFilterImageFilter filter2(dl_color_filter2);\n\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListImageFilter, ColorFilterNotEquals) {\n  DlBlendColorFilter dl_color_filter1(DlColor::kRed(), DlBlendMode::kLighten);\n  DlColorFilterImageFilter filter1(dl_color_filter1);\n\n  DlBlendColorFilter dl_color_filter2(DlColor::kBlue(), DlBlendMode::kLighten);\n  DlColorFilterImageFilter filter2(dl_color_filter2);\n\n  DlBlendColorFilter dl_color_filter3(DlColor::kRed(), DlBlendMode::kDarken);\n  DlColorFilterImageFilter filter3(dl_color_filter3);\n\n  TestNotEquals(filter1, filter2, \"Color differs\");\n  TestNotEquals(filter1, filter3, \"Blend Mode differs\");\n}\n\nTEST(DisplayListImageFilter, ColorFilterBounds) {\n  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcIn);\n  DlColorFilterImageFilter filter(dl_color_filter);\n  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n  TestBounds(filter, input_bounds, input_bounds);\n}\n\nTEST(DisplayListImageFilter, ColorFilterModifiesTransparencyBounds) {\n  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);\n  DlColorFilterImageFilter filter(dl_color_filter);\n  SkRect input_bounds = SkRect::MakeLTRB(20, 20, 80, 80);\n  TestInvalidBounds(filter, SkMatrix::I(), input_bounds);\n}\n\nTEST(DisplayListImageFilter, UnknownConstructor) {\n  DlUnknownImageFilter filter(\n      SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr));\n}\n\nTEST(DisplayListImageFilter, UnknownShared) {\n  DlUnknownImageFilter filter(\n      SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr));\n\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListImageFilter, UnknownContents) {\n  sk_sp<SkImageFilter> sk_filter =\n      SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr);\n  DlUnknownImageFilter filter(sk_filter);\n\n  ASSERT_EQ(filter.gr_object(), sk_filter);\n  ASSERT_EQ(filter.gr_object().get(), sk_filter.get());\n}\n\nTEST(DisplayListImageFilter, LocalImageFilterBounds) {\n  auto filter_matrix = SkMatrix::MakeAll(2.0, 0.0, 10,  //\n                                         0.5, 3.0, 15,  //\n                                         0.0, 0.0, 1);\n  std::vector<sk_sp<SkImageFilter>> sk_filters{\n      SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr),\n      SkImageFilters::ColorFilter(\n          SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcOver), nullptr),\n      SkImageFilters::Dilate(5.0, 10.0, nullptr),\n      SkImageFilters::MatrixTransform(filter_matrix,\n                                      ToSk(DlImageSampling::kLinear), nullptr),\n      SkImageFilters::Compose(\n          SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr),\n          SkImageFilters::ColorFilter(\n              SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcOver),\n              nullptr))};\n\n  DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver);\n  std::vector<std::shared_ptr<DlImageFilter>> dl_filters{\n      std::make_shared<DlBlurImageFilter>(5.0, 6.0, DlTileMode::kRepeat),\n      std::make_shared<DlColorFilterImageFilter>(dl_color_filter.shared()),\n      std::make_shared<DlDilateImageFilter>(5, 10),\n      std::make_shared<DlMatrixImageFilter>(\n          clay::ConvertSkMatrixToSkityMatrix(filter_matrix),\n          DlImageSampling::kLinear),\n      std::make_shared<DlComposeImageFilter>(\n          std::make_shared<DlBlurImageFilter>(5.0, 6.0, DlTileMode::kRepeat),\n          std::make_shared<DlColorFilterImageFilter>(\n              dl_color_filter.shared()))};\n\n  auto persp = SkMatrix::I();\n  persp.setPerspY(0.001);\n  std::vector<SkMatrix> matrices = {\n      SkMatrix::Translate(10.0, 10.0),\n      SkMatrix::Scale(2.0, 2.0).preTranslate(10.0, 10.0),\n      SkMatrix::RotateDeg(45).preTranslate(5.0, 5.0), persp};\n  std::vector<SkMatrix> bounds_matrices{SkMatrix::Translate(5.0, 10.0),\n                                        SkMatrix::Scale(2.0, 2.0)};\n\n  for (unsigned i = 0; i < sk_filters.size(); i++) {\n    for (unsigned j = 0; j < matrices.size(); j++) {\n      for (unsigned k = 0; k < bounds_matrices.size(); k++) {\n        auto& m = matrices[j];\n        auto& bounds_matrix = bounds_matrices[k];\n        auto sk_local_filter = sk_filters[i]->makeWithLocalMatrix(m);\n        auto dl_local_filter = dl_filters[i]->makeWithLocalMatrix(\n            clay::ConvertSkMatrixToSkityMatrix(m));\n        if (!sk_local_filter || !dl_local_filter) {\n          // Temporarily fix the equivalence testing to allow Skia to expand\n          // their behavior. Once the Skia fixes are rolled in, the\n          // DlImageFilter should adapt  to the new rules.\n          // See https://github.com/flutter/flutter/issues/114723\n          ASSERT_TRUE(sk_local_filter || !dl_local_filter);\n          continue;\n        }\n        {\n          auto input_bounds = SkIRect::MakeLTRB(20, 20, 80, 80);\n          SkIRect sk_rect;\n          skity::Rect dl_rect;\n          sk_rect = sk_local_filter->filterBounds(\n              input_bounds, bounds_matrix,\n              SkImageFilter::MapDirection::kForward_MapDirection);\n          dl_local_filter->map_device_bounds(\n              clay::ConvertSkIRectToSkityRect(input_bounds),\n              clay::ConvertSkMatrixToSkityMatrix(bounds_matrix), dl_rect);\n          ASSERT_EQ(clay::ConvertSkIRectToSkityRect(sk_rect), dl_rect);\n        }\n        {\n          // Test for: Know the outset bounds to get the inset bounds\n          // Skia have some bounds calculate error of DilateFilter and\n          // MatrixFilter\n          // Skia issue: https://bugs.chromium.org/p/skia/issues/detail?id=13444\n          // flutter issue: https://github.com/flutter/flutter/issues/108693\n          if (i == 2 || i == 3) {\n            continue;\n          }\n          auto outset_bounds = SkIRect::MakeLTRB(20, 20, 80, 80);\n          SkIRect sk_rect;\n          skity::Rect dl_rect;\n          sk_rect = sk_local_filter->filterBounds(\n              outset_bounds, bounds_matrix,\n              SkImageFilter::MapDirection::kReverse_MapDirection);\n          dl_local_filter->get_input_device_bounds(\n              clay::ConvertSkIRectToSkityRect(outset_bounds),\n              clay::ConvertSkMatrixToSkityMatrix(bounds_matrix), dl_rect);\n          ASSERT_EQ(clay::ConvertSkIRectToSkityRect(sk_rect), dl_rect);\n        }\n      }\n    }\n  }\n}\n\nTEST(DisplayListImageFilter, LocalImageSkiaNull) {\n  auto blur_filter =\n      std::make_shared<DlBlurImageFilter>(0, 0, DlTileMode::kClamp);\n  DlLocalMatrixImageFilter dl_local_matrix_filter(skity::Matrix::RotateDeg(45),\n                                                  blur_filter);\n  // With sigmas set to zero on the blur filter, Skia will return a null filter.\n  // The local matrix filter should return nullptr instead of crashing.\n  ASSERT_EQ(dl_local_matrix_filter.gr_object(), nullptr);\n}\n\nTEST(DisplayListImageFilter, UnknownEquals) {\n  sk_sp<SkImageFilter> sk_filter =\n      SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr);\n\n  DlUnknownImageFilter filter1(sk_filter);\n  DlUnknownImageFilter filter2(sk_filter);\n\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListImageFilter, UnknownNotEquals) {\n  DlUnknownImageFilter filter1(\n      SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr));\n  DlUnknownImageFilter filter2(\n      SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr));\n\n  // Even though the filter is the same, it is a different instance\n  // and we cannot currently tell them apart because the Skia\n  // ImageFilter objects do not implement ==\n  TestNotEquals(filter1, filter2, \"SkImageFilter instance differs\");\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/length.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_LENGTH_H_\n#define CLAY_GFX_STYLE_LENGTH_H_\n\nnamespace clay {\n\nenum class LengthUnit { kNum, kPercent };\n\nclass Length {\n public:\n  Length() : value_(0), unit_(LengthUnit::kNum) {}\n  explicit Length(float v) : value_(v), unit_(LengthUnit::kNum) {}\n  Length(float v, LengthUnit u) : value_(v), unit_(u) {}\n  Length(float v, int u) : value_(v), unit_(static_cast<LengthUnit>(u)) {}\n  bool IsPercent() const { return LengthUnit::kPercent == unit_; }\n  float GetValue(float v) const { return IsPercent() ? value_ * v : value_; }\n  float GetRawValue() const { return value_; }\n  void SetValue(float v) { value_ = v; }\n  void SetUnit(LengthUnit unit) { unit_ = unit; }\n  bool operator==(const Length& other) const {\n    return value_ == other.value_ && unit_ == other.unit_;\n  }\n  bool operator!=(const Length& other) const { return !(*this == other); }\n\n private:\n  float value_;\n  LengthUnit unit_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_LENGTH_H_\n"
  },
  {
    "path": "clay/gfx/style/mask_filter.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/mask_filter.h\"\n\nnamespace clay {\n\nstd::shared_ptr<MaskFilter> MaskFilter::MakeBlur(GrBlurStyle style, float sigma,\n                                                 bool respect_ctm) {\n  return std::make_shared<BlurMaskFilter>(style, sigma, respect_ctm);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/mask_filter.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_MASK_FILTER_H_\n#define CLAY_GFX_STYLE_MASK_FILTER_H_\n\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/attributes.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n#ifdef ENABLE_SKITY\n// Copy from third_party/skia/src/core/SkBlurMask.cpp.\n// Converts a sigma to blur radius in pixels.\ninline float ConvertSigmaToRadius(float sigma) {\n  return sigma > 0.5f ? (sigma - 0.5f) / 0.57735f : 0.0f;\n}\n#endif  // ENABLE_SKITY\n\nclass BlurMaskFilter;\n// An enumerated type for the recognized MaskFilter operations.\n// If a custom MaskFilter outside of the recognized types is needed\n// then a |kUnknown| type that simply defers to an SkMaskFilter is\n// provided as a fallback.\nenum class MaskFilterType { kBlur, kUnknown };\n\nclass MaskFilter : public Attribute<MaskFilter, GrMaskFilter, MaskFilterType> {\n public:\n  // Return a BlurMaskFilter pointer to this object iff it is a Blur\n  // type of MaskFilter, otherwise return nullptr.\n  virtual const BlurMaskFilter* asBlur() const { return nullptr; }\n\n  std::shared_ptr<MaskFilter> MakeBlur(GrBlurStyle style, float sigma,\n                                       bool respect_ctm = true);\n};\n\n// The Blur type of MaskFilter which specifies modifying the\n// colors as if the color specified in the Blur filter is the\n// source color and the color drawn by the rendering operation\n// is the destination color. The mode parameter of the Blur\n// filter is then used to combine those colors.\nclass BlurMaskFilter final : public MaskFilter {\n public:\n  BlurMaskFilter(GrBlurStyle style, float sigma, bool respect_ctm = true)\n      : style_(style), sigma_(sigma), respect_ctm_(respect_ctm) {}\n  BlurMaskFilter(const BlurMaskFilter& filter)\n      : BlurMaskFilter(filter.style_, filter.sigma_, filter.respect_ctm_) {}\n  BlurMaskFilter(const BlurMaskFilter* filter)\n      : BlurMaskFilter(filter->style_, filter->sigma_, filter->respect_ctm_) {}\n\n  MaskFilterType type() const override { return MaskFilterType::kBlur; }\n  size_t size() const override { return sizeof(*this); }\n\n  std::shared_ptr<MaskFilter> shared() const override {\n    return std::make_shared<BlurMaskFilter>(this);\n  }\n\n  GrMaskFilterPtr gr_object() const override {\n#ifndef ENABLE_SKITY\n    return SkMaskFilter::MakeBlur(style_, sigma_, respect_ctm_);\n#else\n    return skity::MaskFilter::MakeBlur(style_, ConvertSigmaToRadius(sigma_));\n#endif  // ENABLE_SKITY\n  }\n\n  const BlurMaskFilter* asBlur() const override { return this; }\n\n  GrBlurStyle style() const { return style_; }\n  float sigma() const { return sigma_; }\n  bool respectCTM() const { return respect_ctm_; }\n\n protected:\n  bool equals_(MaskFilter const& other) const override {\n    FML_DCHECK(other.type() == MaskFilterType::kBlur);\n    auto that = static_cast<BlurMaskFilter const*>(&other);\n    return style_ == that->style_ && sigma_ == that->sigma_ &&\n           respect_ctm_ == that->respect_ctm_;\n  }\n\n private:\n  GrBlurStyle style_;\n  float sigma_;\n  // Added for backward compatibility with Flutter text shadow rendering which\n  // uses Skia blur filters with this flag set to false.\n  bool respect_ctm_;\n};\n\n// A wrapper class for a Skia MaskFilter of unknown type. The above 4 types\n// are the only types that can be constructed by Flutter using the\n// ui.MaskFilter class so this class should be rarely used. The main use\n// would come from the |DisplayListCanvasRecorder| recording Skia rendering\n// calls. This would primarily happen in the Paragraph code that renders the\n// text using the SkCanvas interface which we capture into DisplayList data\n// structures.\nclass UnknownMaskFilter final : public MaskFilter {\n public:\n  UnknownMaskFilter(GrMaskFilterPtr sk_filter)\n      : sk_filter_(std::move(sk_filter)) {}\n\n  UnknownMaskFilter(const UnknownMaskFilter& filter)\n      : UnknownMaskFilter(filter.sk_filter_) {}\n  UnknownMaskFilter(const UnknownMaskFilter* filter)\n      : UnknownMaskFilter(filter->sk_filter_) {}\n\n  MaskFilterType type() const override { return MaskFilterType::kUnknown; }\n  size_t size() const override { return sizeof(*this); }\n\n  std::shared_ptr<MaskFilter> shared() const override {\n    return std::make_shared<UnknownMaskFilter>(this);\n  }\n\n  GrMaskFilterPtr gr_object() const override { return sk_filter_; }\n\n  virtual ~UnknownMaskFilter() = default;\n\n protected:\n  bool equals_(const MaskFilter& other) const override {\n    FML_DCHECK(other.type() == MaskFilterType::kUnknown);\n    auto that = static_cast<UnknownMaskFilter const&>(other);\n    return sk_filter_ == that.sk_filter_;\n  }\n\n private:\n  GrMaskFilterPtr sk_filter_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_MASK_FILTER_H_\n"
  },
  {
    "path": "clay/gfx/style/mask_filter_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/attributes_testing.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\nnamespace clay {\nnamespace testing {\n\nTEST(DisplayListMaskFilter, BlurConstructor) {\n  DlBlurMaskFilter filter(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n}\n\nTEST(DisplayListMaskFilter, BlurShared) {\n  DlBlurMaskFilter filter(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListMaskFilter, BlurAsBlur) {\n  DlBlurMaskFilter filter(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n  ASSERT_NE(filter.asBlur(), nullptr);\n  ASSERT_EQ(filter.asBlur(), &filter);\n}\n\nTEST(DisplayListMaskFilter, BlurContents) {\n  DlBlurMaskFilter filter(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n  ASSERT_EQ(filter.style(), SkBlurStyle::kNormal_SkBlurStyle);\n  ASSERT_EQ(filter.sigma(), 5.0);\n}\n\nTEST(DisplayListMaskFilter, BlurEquals) {\n  DlBlurMaskFilter filter1(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n  DlBlurMaskFilter filter2(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListMaskFilter, BlurNotEquals) {\n  DlBlurMaskFilter filter1(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n  DlBlurMaskFilter filter2(SkBlurStyle::kInner_SkBlurStyle, 5.0);\n  DlBlurMaskFilter filter3(SkBlurStyle::kNormal_SkBlurStyle, 6.0);\n  TestNotEquals(filter1, filter2, \"Blur style differs\");\n  TestNotEquals(filter1, filter3, \"blur radius differs\");\n}\n\nTEST(DisplayListMaskFilter, UnknownConstructor) {\n  DlUnknownMaskFilter filter(\n      SkMaskFilter::MakeBlur(SkBlurStyle::kNormal_SkBlurStyle, 5.0));\n}\n\nTEST(DisplayListMaskFilter, UnknownShared) {\n  DlUnknownMaskFilter filter(\n      SkMaskFilter::MakeBlur(SkBlurStyle::kNormal_SkBlurStyle, 5.0));\n  ASSERT_NE(filter.shared().get(), &filter);\n  ASSERT_EQ(*filter.shared(), filter);\n}\n\nTEST(DisplayListMaskFilter, UnknownContents) {\n  sk_sp<SkMaskFilter> sk_filter =\n      SkMaskFilter::MakeBlur(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n  DlUnknownMaskFilter filter(sk_filter);\n  ASSERT_EQ(filter.gr_object(), sk_filter);\n  ASSERT_EQ(filter.gr_object().get(), sk_filter.get());\n}\n\nTEST(DisplayListMaskFilter, UnknownEquals) {\n  sk_sp<SkMaskFilter> sk_filter =\n      SkMaskFilter::MakeBlur(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n  DlUnknownMaskFilter filter1(sk_filter);\n  DlUnknownMaskFilter filter2(sk_filter);\n  TestEquals(filter1, filter2);\n}\n\nTEST(DisplayListMaskFilter, UnknownNotEquals) {\n  // Even though the filter is the same, it is a different instance\n  // and we cannot currently tell them apart because the Skia\n  // MaskFilter objects do not implement ==\n  DlUnknownMaskFilter filter1(\n      SkMaskFilter::MakeBlur(SkBlurStyle::kNormal_SkBlurStyle, 5.0));\n  DlUnknownMaskFilter filter2(\n      SkMaskFilter::MakeBlur(SkBlurStyle::kNormal_SkBlurStyle, 5.0));\n  TestNotEquals(filter1, filter2, \"SkMaskFilter instance differs\");\n}\n\nvoid testEquals(DlMaskFilter* a, DlMaskFilter* b) {\n  // a and b have the same nullness or values\n  ASSERT_TRUE(Equals(a, b));\n  ASSERT_FALSE(NotEquals(a, b));\n  ASSERT_TRUE(Equals(b, a));\n  ASSERT_FALSE(NotEquals(b, a));\n}\n\nvoid testNotEquals(DlMaskFilter* a, DlMaskFilter* b) {\n  // a and b do not have the same nullness or values\n  ASSERT_FALSE(Equals(a, b));\n  ASSERT_TRUE(NotEquals(a, b));\n  ASSERT_FALSE(Equals(b, a));\n  ASSERT_TRUE(NotEquals(b, a));\n}\n\nvoid testEquals(const std::shared_ptr<const DlMaskFilter>& a, DlMaskFilter* b) {\n  // a and b have the same nullness or values\n  ASSERT_TRUE(Equals(a, b));\n  ASSERT_FALSE(NotEquals(a, b));\n  ASSERT_TRUE(Equals(b, a));\n  ASSERT_FALSE(NotEquals(b, a));\n}\n\nvoid testNotEquals(const std::shared_ptr<const DlMaskFilter>& a,\n                   DlMaskFilter* b) {\n  // a and b do not have the same nullness or values\n  ASSERT_FALSE(Equals(a, b));\n  ASSERT_TRUE(NotEquals(a, b));\n  ASSERT_FALSE(Equals(b, a));\n  ASSERT_TRUE(NotEquals(b, a));\n}\n\nvoid testEquals(const std::shared_ptr<const DlMaskFilter>& a,\n                const std::shared_ptr<const DlMaskFilter>& b) {\n  // a and b have the same nullness or values\n  ASSERT_TRUE(Equals(a, b));\n  ASSERT_FALSE(NotEquals(a, b));\n  ASSERT_TRUE(Equals(b, a));\n  ASSERT_FALSE(NotEquals(b, a));\n}\n\nvoid testNotEquals(const std::shared_ptr<const DlMaskFilter>& a,\n                   const std::shared_ptr<const DlMaskFilter>& b) {\n  // a and b do not have the same nullness or values\n  ASSERT_FALSE(Equals(a, b));\n  ASSERT_TRUE(NotEquals(a, b));\n  ASSERT_FALSE(Equals(b, a));\n  ASSERT_TRUE(NotEquals(b, a));\n}\n\nTEST(DisplayListMaskFilter, ComparableTemplates) {\n  DlBlurMaskFilter filter1a(SkBlurStyle::kNormal_SkBlurStyle, 3.0);\n  DlBlurMaskFilter filter1b(SkBlurStyle::kNormal_SkBlurStyle, 3.0);\n  DlBlurMaskFilter filter2(SkBlurStyle::kNormal_SkBlurStyle, 5.0);\n  std::shared_ptr<DlMaskFilter> shared_null;\n\n  // null to null\n  testEquals(nullptr, nullptr);\n  testEquals(shared_null, nullptr);\n  testEquals(shared_null, shared_null);\n\n  // ptr to null\n  testNotEquals(&filter1a, nullptr);\n  testNotEquals(&filter1b, nullptr);\n  testNotEquals(&filter2, nullptr);\n\n  // shared_ptr to null and shared_null to ptr\n  testNotEquals(filter1a.shared(), nullptr);\n  testNotEquals(filter1b.shared(), nullptr);\n  testNotEquals(filter2.shared(), nullptr);\n  testNotEquals(shared_null, &filter1a);\n  testNotEquals(shared_null, &filter1b);\n  testNotEquals(shared_null, &filter2);\n\n  // ptr to ptr\n  testEquals(&filter1a, &filter1a);\n  testEquals(&filter1a, &filter1b);\n  testEquals(&filter1b, &filter1b);\n  testEquals(&filter2, &filter2);\n  testNotEquals(&filter1a, &filter2);\n\n  // shared_ptr to ptr\n  testEquals(filter1a.shared(), &filter1a);\n  testEquals(filter1a.shared(), &filter1b);\n  testEquals(filter1b.shared(), &filter1b);\n  testEquals(filter2.shared(), &filter2);\n  testNotEquals(filter1a.shared(), &filter2);\n  testNotEquals(filter1b.shared(), &filter2);\n  testNotEquals(filter2.shared(), &filter1a);\n  testNotEquals(filter2.shared(), &filter1b);\n\n  // shared_ptr to shared_ptr\n  testEquals(filter1a.shared(), filter1a.shared());\n  testEquals(filter1a.shared(), filter1b.shared());\n  testEquals(filter1b.shared(), filter1b.shared());\n  testEquals(filter2.shared(), filter2.shared());\n  testNotEquals(filter1a.shared(), filter2.shared());\n  testNotEquals(filter1b.shared(), filter2.shared());\n  testNotEquals(filter2.shared(), filter1a.shared());\n  testNotEquals(filter2.shared(), filter1b.shared());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/outline_data.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/outline_data.h\"\n\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n\nOutlineData::OutlineData()\n    : width_(0.f),\n      offset_(0.f),\n      color_(Color::kBlack().Value()),\n      style_(BorderStyleType::kSolid) {}\n\nvoid OutlineData::Reset() {\n  width_ = 0.f;\n  offset_ = 0.f;\n  color_ = Color::kBlack().Value();\n  style_ = BorderStyleType::kSolid;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/outline_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_OUTLINE_DATA_H_\n#define CLAY_GFX_STYLE_OUTLINE_DATA_H_\n\n#include <limits.h>\n\n#include <array>\n#include <tuple>\n\n#include \"clay/gfx/style/borders_data.h\"\n\nnamespace clay {\n\nclass OutlineData {\n public:\n  OutlineData();\n  void Reset();\n\n  float width_;\n  float offset_;\n  unsigned int color_;\n  BorderStyleType style_;\n\n  bool operator==(const OutlineData& rhs) const {\n    return std::tie(width_, offset_, color_, style_) ==\n           std::tie(rhs.width_, rhs.offset_, rhs.color_, rhs.style_);\n  }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_OUTLINE_DATA_H_\n"
  },
  {
    "path": "clay/gfx/style/path_effect.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/path_effect.h\"\n\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nstatic void PathEffectDeleter(void* p) {\n  // Some of our target environments would prefer a sized delete,\n  // but other target environments do not have that operator.\n  // Use an unsized delete until we get better agreement in the\n  // environments.\n  // See https://github.com/flutter/flutter/issues/100327\n  ::operator delete(p);\n}\n\nstd::shared_ptr<PathEffect> PathEffect::MakeDash(const float intervals[],\n                                                 int count, float phase) {\n  return DashPathEffect::Make(intervals, count, phase);\n}\n\nstd::shared_ptr<PathEffect> DashPathEffect::Make(const float* intervals,\n                                                 int count, float phase) {\n  size_t needed = sizeof(DashPathEffect) + sizeof(float) * count;\n  void* storage = ::operator new(needed);\n\n  std::shared_ptr<DashPathEffect> ret;\n  ret.reset(new (storage) DashPathEffect(intervals, count, phase),\n            PathEffectDeleter);\n  return std::move(ret);\n}\n\nstd::optional<GrRect> DashPathEffect::effect_bounds(GrRect& rect) const {\n  // SkDashPathEffect returns the original bounds as the bounds of the effect\n  // since the dashed path will always be a subset of the original.\n  return rect;\n}\n\nstd::optional<GrRect> UnknownPathEffect::effect_bounds(GrRect& rect) const {\n  if (!RECT_IS_SORTED(rect)) {\n    return std::nullopt;\n  }\n  GrPaint p;\n  PAINT_SET_PATH_EFFECT(p, sk_path_effect_);\n  if (!PAINT_CAN_COMPUTE_FAST_BOUNDS(p)) {\n    return std::nullopt;\n  }\n  rect = PAINT_COMPUTE_FAST_BOUNDS(p, rect, rect);\n  return rect;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/path_effect.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_PATH_EFFECT_H_\n#define CLAY_GFX_STYLE_PATH_EFFECT_H_\n\n#include <cstring>\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/attributes.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass DashPathEffect;\n\nenum class PathEffectType {\n  kDash,\n  kUnknown,\n};\n\nclass PathEffect : public Attribute<PathEffect, GrPathEffect, PathEffectType> {\n public:\n  virtual ~PathEffect() = default;\n\n  std::shared_ptr<PathEffect> MakeDash(const float intervals[], int count,\n                                       float phase);\n\n  virtual const DashPathEffect* asDash() const { return nullptr; }\n\n  virtual std::optional<GrRect> effect_bounds(GrRect&) const = 0;\n\n protected:\n  PathEffect() = default;\n\n private:\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(PathEffect);\n};\n\n/// The DashPathEffect which breaks a path up into dash segments, and it\n/// only affects stroked paths.\n/// intervals: array containing an even number of entries (>=2), with\n/// the even indices specifying the length of \"on\" intervals, and the odd\n/// indices specifying the length of \"off\" intervals. This array will be\n/// copied in Make, and can be disposed of freely after.\n/// count: number of elements in the intervals array.\n/// phase: initial distance into the intervals at which to start the dashing\n/// effect for the path.\n///\n/// For example: if intervals[] = {10, 20}, count = 2, and phase = 25,\n/// this will set up a dashed path like so:\n/// 5 pixels off\n/// 10 pixels on\n/// 20 pixels off\n/// 10 pixels on\n/// 20 pixels off\n/// ...\n/// A phase of -5, 25, 55, 85, etc. would all result in the same path,\n/// because the sum of all the intervals is 30.\n///\nclass DashPathEffect final : public PathEffect {\n public:\n  static std::shared_ptr<PathEffect> Make(const float intervals[], int count,\n                                          float phase);\n\n  PathEffectType type() const override { return PathEffectType::kDash; }\n  size_t size() const override {\n    return sizeof(*this) + sizeof(float) * count_;\n  }\n\n  std::shared_ptr<PathEffect> shared() const override {\n    return Make(intervals(), count_, phase_);\n  }\n\n  const DashPathEffect* asDash() const override { return this; }\n\n  GrPathEffectPtr gr_object() const override {\n    return PATH_EFFECT_MAKE_DASH(intervals(), count_, phase_);\n  }\n\n  const float* intervals() const {\n    return reinterpret_cast<const float*>(this + 1);\n  }\n\n  std::optional<GrRect> effect_bounds(GrRect& rect) const override;\n\n protected:\n  bool equals_(PathEffect const& other) const override {\n    FML_DCHECK(other.type() == PathEffectType::kDash);\n    auto that = static_cast<DashPathEffect const*>(&other);\n    return count_ == that->count_ && phase_ == that->phase_ &&\n           memcmp(intervals(), that->intervals(), sizeof(float) * count_) == 0;\n  }\n\n private:\n  // DashPathEffect constructor assumes the caller has prealloced storage for\n  // the intervals. If the intervals is nullptr the intervals will\n  // uninitialized.\n  DashPathEffect(const float intervals[], int count, float phase)\n      : count_(count), phase_(phase) {\n    if (intervals != nullptr) {\n      float* intervals_ = reinterpret_cast<float*>(this + 1);\n      memcpy(intervals_, intervals, sizeof(float) * count);\n    }\n  }\n\n  explicit DashPathEffect(const DashPathEffect* dash_effect)\n      : DashPathEffect(dash_effect->intervals(), dash_effect->count_,\n                       dash_effect->phase_) {}\n\n  float* intervals_unsafe() { return reinterpret_cast<float*>(this + 1); }\n\n  int count_;\n  float phase_;\n\n  friend class PathEffect;\n  friend class RenderingMaterial;\n\n  BASE_DISALLOW_COPY_ASSIGN_AND_MOVE(DashPathEffect);\n};\n\nclass UnknownPathEffect final : public PathEffect {\n public:\n  UnknownPathEffect(GrPathEffectPtr effect)\n      : sk_path_effect_(std::move(effect)) {}\n  UnknownPathEffect(const UnknownPathEffect& effect)\n      : UnknownPathEffect(effect.sk_path_effect_) {}\n  UnknownPathEffect(const UnknownPathEffect* effect)\n      : UnknownPathEffect(effect->sk_path_effect_) {}\n\n  PathEffectType type() const override { return PathEffectType::kUnknown; }\n  size_t size() const override { return sizeof(*this); }\n\n  std::shared_ptr<PathEffect> shared() const override {\n    return std::make_shared<UnknownPathEffect>(this);\n  }\n\n  GrPathEffectPtr gr_object() const override { return sk_path_effect_; }\n\n  virtual ~UnknownPathEffect() = default;\n\n  std::optional<GrRect> effect_bounds(GrRect& rect) const override;\n\n protected:\n  bool equals_(const PathEffect& other) const override {\n    FML_DCHECK(other.type() == PathEffectType::kUnknown);\n    auto that = static_cast<UnknownPathEffect const*>(&other);\n    return sk_path_effect_ == that->sk_path_effect_;\n  }\n\n private:\n  GrPathEffectPtr sk_path_effect_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_PATH_EFFECT_H_\n"
  },
  {
    "path": "clay/gfx/style/path_effect_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/attributes_testing.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkPath.h\"\n#include \"third_party/skia/include/core/SkScalar.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(DisplayListPathEffect, EffectShared) {\n  const SkScalar TestDashes2[] = {1.0, 1.5};\n  auto effect = DlDashPathEffect::Make(TestDashes2, 2, 0.0);\n  ASSERT_TRUE(Equals(effect->shared(), effect));\n}\n\nTEST(DisplayListPathEffect, DashEffectAsDash) {\n  const SkScalar TestDashes2[] = {1.0, 1.5};\n  auto effect = DlDashPathEffect::Make(TestDashes2, 2, 0.0);\n  ASSERT_NE(effect->asDash(), nullptr);\n  ASSERT_EQ(effect->asDash(), effect.get());\n}\n\nTEST(DisplayListPathEffect, DashEffectEquals) {\n  const SkScalar TestDashes2[] = {1.0, 1.5};\n  auto effect1 = DlDashPathEffect::Make(TestDashes2, 2, 0.0);\n  auto effect2 = DlDashPathEffect::Make(TestDashes2, 2, 0.0);\n  TestEquals(*effect1, *effect1);\n}\n\nTEST(DisplayListPathEffect, CheckEffectProperties) {\n  const SkScalar test_dashes[] = {4.0, 2.0};\n  const SkScalar TestDashes2[] = {5.0, 2.0};\n  const SkScalar TestDashes3[] = {4.0, 3.0};\n  const SkScalar TestDashes4[] = {4.0, 2.0, 6.0};\n  auto effect1 = DlDashPathEffect::Make(test_dashes, 2, 0.0);\n  auto effect2 = DlDashPathEffect::Make(TestDashes2, 2, 0.0);\n  auto effect3 = DlDashPathEffect::Make(TestDashes3, 2, 0.0);\n  auto effect4 = DlDashPathEffect::Make(TestDashes4, 3, 0.0);\n  auto effect5 = DlDashPathEffect::Make(test_dashes, 2, 1.0);\n\n  TestNotEquals(*effect1, *effect2, \"Interval 1 differs\");\n  TestNotEquals(*effect1, *effect3, \"Interval 2 differs\");\n  TestNotEquals(*effect1, *effect4, \"Dash count differs\");\n  TestNotEquals(*effect1, *effect5, \"Dash phase differs\");\n}\n\nTEST(DisplayListPathEffect, UnknownConstructor) {\n  const SkScalar test_dashes[] = {4.0, 2.0};\n  DlUnknownPathEffect path_effect(SkDashPathEffect::Make(test_dashes, 2, 0.0));\n}\n\nTEST(DisplayListPathEffect, UnknownShared) {\n  const SkScalar test_dashes[] = {4.0, 2.0};\n  DlUnknownPathEffect path_effect(SkDashPathEffect::Make(test_dashes, 2, 0.0));\n  ASSERT_NE(path_effect.shared().get(), &path_effect);\n  ASSERT_EQ(*path_effect.shared(), path_effect);\n}\n\nTEST(DisplayListPathEffect, UnknownContents) {\n  const SkScalar test_dashes[] = {4.0, 2.0};\n  sk_sp<SkPathEffect> sk_effect = SkDashPathEffect::Make(test_dashes, 2, 0.0);\n  DlUnknownPathEffect effect(sk_effect);\n  ASSERT_EQ(effect.gr_object(), sk_effect);\n  ASSERT_EQ(effect.gr_object().get(), sk_effect.get());\n}\n\nTEST(DisplayListPathEffect, UnknownEquals) {\n  const SkScalar test_dashes[] = {4.0, 2.0};\n  sk_sp<SkPathEffect> sk_effect = SkDashPathEffect::Make(test_dashes, 2, 0.0);\n  DlUnknownPathEffect effect1(sk_effect);\n  DlUnknownPathEffect effect2(sk_effect);\n  TestEquals(effect1, effect1);\n}\n\nTEST(DisplayListPathEffect, UnknownNotEquals) {\n  const SkScalar test_dashes[] = {4.0, 2.0};\n  // Even though the effect is the same, it is a different instance\n  // and we cannot currently tell them apart because the Skia\n  // DashEffect::Make objects do not implement ==\n  DlUnknownPathEffect path_effect1(SkDashPathEffect::Make(test_dashes, 2, 0.0));\n  DlUnknownPathEffect path_effect2(SkDashPathEffect::Make(test_dashes, 2, 0.0));\n  TestNotEquals(path_effect1, path_effect2,\n                \"SkDashPathEffect instance differs\");\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/runtime_effect.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/runtime_effect.h\"\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// RuntimeEffect\n///\n\nRuntimeEffect::RuntimeEffect() = default;\nRuntimeEffect::~RuntimeEffect() = default;\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<RuntimeEffect> RuntimeEffect::MakeSkia(\n    const sk_sp<SkRuntimeEffect>& runtime_effect) {\n  return fml::MakeRefCounted<RuntimeEffectSkia>(runtime_effect);\n}\n\n//------------------------------------------------------------------------------\n/// RuntimeEffectSkia\n///\n\nRuntimeEffectSkia::~RuntimeEffectSkia() = default;\n\nRuntimeEffectSkia::RuntimeEffectSkia(\n    const sk_sp<SkRuntimeEffect>& runtime_effect)\n    : skia_runtime_effect_(runtime_effect) {}\n\nsk_sp<SkRuntimeEffect> RuntimeEffectSkia::skia_runtime_effect() const {\n  return skia_runtime_effect_;\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/runtime_effect.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_RUNTIME_EFFECT_H_\n#define CLAY_GFX_STYLE_RUNTIME_EFFECT_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nclass RuntimeEffect : public fml::RefCountedThreadSafe<RuntimeEffect> {\n public:\n#ifndef ENABLE_SKITY\n  static fml::RefPtr<RuntimeEffect> MakeSkia(\n      const sk_sp<SkRuntimeEffect>& runtime_effect);\n\n  virtual sk_sp<SkRuntimeEffect> skia_runtime_effect() const = 0;\n#endif  // ENABLE_SKITY\n\n  virtual ~RuntimeEffect();\n\n protected:\n  RuntimeEffect();\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(RuntimeEffect);\n};\n\n#ifndef ENABLE_SKITY\nclass RuntimeEffectSkia final : public RuntimeEffect {\n public:\n  explicit RuntimeEffectSkia(const sk_sp<SkRuntimeEffect>& runtime_effect);\n\n  // |DlRuntimeEffect|\n  sk_sp<SkRuntimeEffect> skia_runtime_effect() const override;\n  ~RuntimeEffectSkia() override;\n\n private:\n  RuntimeEffectSkia() = delete;\n\n  sk_sp<SkRuntimeEffect> skia_runtime_effect_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(RuntimeEffectSkia);\n\n  friend RuntimeEffectSkia;\n};\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_RUNTIME_EFFECT_H_\n"
  },
  {
    "path": "clay/gfx/style/sampling_options.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_SAMPLING_OPTIONS_H_\n#define CLAY_GFX_STYLE_SAMPLING_OPTIONS_H_\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nenum class FilterMode {\n  kNearest,  // single sample point (nearest neighbor)\n  kLinear,   // interpolate between 2x2 sample points (bilinear interpolation)\n\n  kLast = kLinear,\n};\n\nenum class ImageSampling {\n  kNearestNeighbor,\n  kLinear,\n  kMipmapLinear,\n  kCubic,\n};\n\n#ifndef ENABLE_SKITY\ninline FilterMode ToClay(const SkFilterMode filter_mode) {\n  return static_cast<FilterMode>(filter_mode);\n}\n\ninline SkFilterMode ToSk(const FilterMode filter_mode) {\n  return static_cast<SkFilterMode>(filter_mode);\n}\n\ninline ImageSampling ToClay(const SkSamplingOptions& so) {\n  if (so.useCubic) {\n    return ImageSampling::kCubic;\n  }\n  if (so.filter == SkFilterMode::kLinear) {\n    if (so.mipmap == SkMipmapMode::kLinear) {\n      return ImageSampling::kMipmapLinear;\n    }\n    return ImageSampling::kLinear;\n  }\n  return ImageSampling::kNearestNeighbor;\n}\n\ninline SkSamplingOptions ToSk(ImageSampling sampling) {\n  switch (sampling) {\n    case ImageSampling::kCubic:\n      return SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f});\n    case ImageSampling::kLinear:\n      return SkSamplingOptions(SkFilterMode::kLinear);\n    case ImageSampling::kMipmapLinear:\n      return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);\n    case ImageSampling::kNearestNeighbor:\n      return SkSamplingOptions(SkFilterMode::kNearest);\n  }\n}\n#else\ninline skity::SamplingOptions ToSk(ImageSampling sampling) {\n  switch (sampling) {\n    case ImageSampling::kCubic:\n      FML_UNIMPLEMENTED();\n      return {};\n    case ImageSampling::kLinear:\n      return skity::SamplingOptions(skity::FilterMode::kLinear,\n                                    skity::MipmapMode::kNone);\n    case ImageSampling::kMipmapLinear:\n      return skity::SamplingOptions(skity::FilterMode::kLinear,\n                                    skity::MipmapMode::kLinear);\n    case ImageSampling::kNearestNeighbor:\n      return skity::SamplingOptions(skity::FilterMode::kNearest,\n                                    skity::MipmapMode::kNone);\n  }\n}\n\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_SAMPLING_OPTIONS_H_\n"
  },
  {
    "path": "clay/gfx/style/shadow.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_SHADOW_H_\n#define CLAY_GFX_STYLE_SHADOW_H_\n\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n\nstruct Shadow {\n  // Note(Xietong): not supported by skia.\n  bool inset;\n  float offset_x;\n  float offset_y;\n  float blur_radius;\n  // Note(Xietong): not supported by skia.\n  float spread_radius;\n  Color color;\n};\n\ninline bool operator==(const Shadow& a, const Shadow& b) {\n  return a.inset == b.inset && a.color == b.color && a.offset_x == b.offset_x &&\n         a.offset_y == b.offset_y && a.blur_radius == b.blur_radius &&\n         a.spread_radius == b.spread_radius;\n}\n\ninline bool operator!=(const Shadow& a, const Shadow& b) { return !(a == b); }\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_SHADOW_H_\n"
  },
  {
    "path": "clay/gfx/style/tile_mode.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_TILE_MODE_H_\n#define CLAY_GFX_STYLE_TILE_MODE_H_\n\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n// An enum to define how to repeat, fold, or omit colors outside of the\n// typically defined range of the source of the colors (such as the\n// bounds of an image or the defining geoetry of a gradient).\nenum class TileMode {\n  // Replicate the edge color if the |DlColorSource| draws outside of the\n  // defined bounds.\n  kClamp,\n\n  // Repeat the |DlColorSource|'s defined colors both horizontally and\n  // vertically (or both along and perpendicular to a gradient's geometry).\n  kRepeat,\n\n  // Repeat the |DlColorSource|'s colors horizontally and vertically,\n  // alternating mirror images so that adjacent images always seam.\n  kMirror,\n\n  // Only draw within the original domain, return transparent-black everywhere\n  // else.\n  kDecal,\n};\n\n#ifndef ENABLE_SKITY\ninline TileMode ToClay(SkTileMode sk_mode) {\n  return static_cast<TileMode>(sk_mode);\n}\n\ninline SkTileMode ToSk(TileMode dl_mode) {\n  return static_cast<SkTileMode>(dl_mode);\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_TILE_MODE_H_\n"
  },
  {
    "path": "clay/gfx/style/vertices.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/style/vertices.h\"\n\n#include <algorithm>\n#include <cstring>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/gfx_utils.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"skity/geometry/point.hpp\"\n\nnamespace clay {\n\nusing Flags = Vertices::Builder::Flags;\n\nstatic void VerticesDeleter(void* p) {\n  // Some of our target environments would prefer a sized delete,\n  // but other target environments do not have that operator.\n  // Use an unsized delete until we get better agreement in the\n  // environments.\n  // See https://github.com/flutter/flutter/issues/100327\n  ::operator delete(p);\n}\n\nstatic size_t bytes_needed(int vertex_count, Flags flags, int index_count) {\n  int needed = sizeof(Vertices);\n  // We always have vertices\n  needed += vertex_count * sizeof(SkPoint);\n  if (flags.has_texture_coordinates) {\n    needed += vertex_count * sizeof(SkPoint);\n  }\n  if (flags.has_colors) {\n    needed += vertex_count * sizeof(clay::Color);\n  }\n  if (index_count > 0) {\n    needed += index_count * sizeof(uint16_t);\n  }\n  return needed;\n}\n\nstd::shared_ptr<Vertices> Vertices::Make(VertexMode mode, int vertex_count,\n                                         const SkPoint vertices[],\n                                         const SkPoint texture_coordinates[],\n                                         const clay::Color colors[],\n                                         int index_count,\n                                         const uint16_t indices[]) {\n  if (!vertices || vertex_count <= 0) {\n    vertex_count = 0;\n    texture_coordinates = nullptr;\n    colors = nullptr;\n  }\n  if (!indices || index_count <= 0) {\n    index_count = 0;\n    indices = nullptr;\n  }\n\n  Flags flags;\n  FML_DCHECK(!flags.has_texture_coordinates);\n  FML_DCHECK(!flags.has_colors);\n  if (texture_coordinates) {\n    flags |= Builder::kHasTextureCoordinates;\n  }\n  if (colors) {\n    flags |= Builder::kHasColors;\n  }\n  Builder builder(mode, vertex_count, flags, index_count);\n\n  builder.store_vertices(vertices);\n  if (texture_coordinates) {\n    builder.store_texture_coordinates(texture_coordinates);\n  }\n  if (colors) {\n    builder.store_colors(colors);\n  }\n  if (indices) {\n    builder.store_indices(indices);\n  }\n\n  return builder.build();\n}\n\nsize_t Vertices::size() const {\n  return bytes_needed(vertex_count_,\n                      {{texture_coordinates_offset_ > 0, colors_offset_ > 0}},\n                      index_count_);\n}\n\nstatic SkRect compute_bounds(const SkPoint* points, int count) {\n  RectBoundsAccumulator accumulator;\n  for (int i = 0; i < count; i++) {\n    accumulator.accumulate(skity::Point{points[i].fX, points[i].fY, 0, 0});\n  }\n  return ConvertSkityRectToSkRect(accumulator.bounds());\n}\n\nVertices::Vertices(VertexMode mode, int unchecked_vertex_count,\n                   const SkPoint* vertices, const SkPoint* texture_coordinates,\n                   const clay::Color* colors, int unchecked_index_count,\n                   const uint16_t* indices, const SkRect* bounds)\n    : mode_(mode),\n      vertex_count_(std::max(unchecked_vertex_count, 0)),\n      index_count_(indices ? std::max(unchecked_index_count, 0) : 0) {\n  bounds_ = bounds ? *bounds : compute_bounds(vertices, vertex_count_);\n\n  char* pod = reinterpret_cast<char*>(this);\n  size_t offset = sizeof(Vertices);\n\n  auto advance = [pod, &offset](auto* src, int count) {\n    if (src != nullptr && count > 0) {\n      size_t bytes = count * sizeof(*src);\n      memcpy(pod + offset, src, bytes);\n      size_t ret = offset;\n      offset += bytes;\n      return ret;\n    } else {\n      return static_cast<size_t>(0);\n    }\n  };\n\n  vertices_offset_ = advance(vertices, vertex_count_);\n  texture_coordinates_offset_ = advance(texture_coordinates, vertex_count_);\n  colors_offset_ = advance(colors, vertex_count_);\n  indices_offset_ = advance(indices, index_count_);\n  FML_DCHECK(offset == bytes_needed(vertex_count_,\n                                    {{!!texture_coordinates, !!colors}},\n                                    index_count_));\n}\n\nVertices::Vertices(const Vertices* other)\n    : Vertices(other->mode_, other->vertex_count_, other->vertices(),\n               other->texture_coordinates(), other->colors(),\n               other->index_count_, other->indices(), &other->bounds_) {}\n\nVertices::Vertices(VertexMode mode, int unchecked_vertex_count, Flags flags,\n                   int unchecked_index_count)\n    : mode_(mode),\n      vertex_count_(std::max(unchecked_vertex_count, 0)),\n      index_count_(std::max(unchecked_index_count, 0)) {\n  char* pod = reinterpret_cast<char*>(this);\n  size_t offset = sizeof(Vertices);\n\n  auto advance = [pod, &offset](size_t size, int count) {\n    if (count > 0) {\n      size_t bytes = count * size;\n      memset(pod + offset, 0, bytes);\n      size_t ret = offset;\n      offset += bytes;\n      return ret;\n    } else {\n      return static_cast<size_t>(0);\n    }\n  };\n\n  vertices_offset_ = advance(sizeof(SkPoint), vertex_count_);\n  texture_coordinates_offset_ = advance(\n      sizeof(SkPoint), flags.has_texture_coordinates ? vertex_count_ : 0);\n  colors_offset_ =\n      advance(sizeof(clay::Color), flags.has_colors ? vertex_count_ : 0);\n  indices_offset_ = advance(sizeof(uint16_t), index_count_);\n  FML_DCHECK(offset == bytes_needed(vertex_count_, flags, index_count_));\n  FML_DCHECK((vertex_count_ != 0) == (vertices() != nullptr));\n  FML_DCHECK((vertex_count_ != 0 && flags.has_texture_coordinates) ==\n             (texture_coordinates() != nullptr));\n  FML_DCHECK((vertex_count_ != 0 && flags.has_colors) == (colors() != nullptr));\n  FML_DCHECK((index_count_ != 0) == (indices() != nullptr));\n}\n\n#ifndef ENABLE_SKITY\nsk_sp<SkVertices> Vertices::gr_object() const {\n  const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());\n  return SkVertices::MakeCopy(ToSk(mode_), vertex_count_, vertices(),\n                              texture_coordinates(), sk_colors, index_count_,\n                              indices());\n}\n#endif  // ENABLE_SKITY\n\nbool Vertices::operator==(Vertices const& other) const {\n  auto lists_equal = [](auto* a, auto* b, int count) {\n    if (a == nullptr || b == nullptr) {\n      return a == b;\n    }\n    for (int i = 0; i < count; i++) {\n      if (a[i] != b[i]) {\n        return false;\n      }\n    }\n    return true;\n  };\n  return                                                               //\n      mode_ == other.mode_ &&                                          //\n      vertex_count_ == other.vertex_count_ &&                          //\n      lists_equal(vertices(), other.vertices(), vertex_count_) &&      //\n      lists_equal(texture_coordinates(), other.texture_coordinates(),  //\n                  vertex_count_) &&                                    //\n      lists_equal(colors(), other.colors(), vertex_count_) &&          //\n      index_count_ == other.index_count_ &&                            //\n      lists_equal(indices(), other.indices(), index_count_);\n}\n\nVertices::Builder::Builder(VertexMode mode, int vertex_count, Flags flags,\n                           int index_count)\n    : needs_vertices_(true),\n      needs_texture_coords_(flags.has_texture_coordinates),\n      needs_colors_(flags.has_colors),\n      needs_indices_(index_count > 0) {\n  vertex_count = std::max(vertex_count, 0);\n  index_count = std::max(index_count, 0);\n  void* storage =\n      ::operator new(bytes_needed(vertex_count, flags, index_count));\n  vertices_.reset(new (storage)\n                      Vertices(mode, vertex_count, flags, index_count),\n                  VerticesDeleter);\n}\n\nstatic void store_points(char* dst, int offset, const float* src, int count) {\n  SkPoint* points = reinterpret_cast<SkPoint*>(dst + offset);\n  for (int i = 0; i < count; i++) {\n    points[i] = SkPoint::Make(src[i * 2], src[i * 2 + 1]);\n  }\n}\n\nvoid Vertices::Builder::store_vertices(const SkPoint vertices[]) {\n  FML_DCHECK(is_valid());\n  FML_DCHECK(needs_vertices_);\n  char* pod = reinterpret_cast<char*>(vertices_.get());\n  size_t bytes = vertices_->vertex_count_ * sizeof(vertices[0]);\n  memcpy(pod + vertices_->vertices_offset_, vertices, bytes);\n  needs_vertices_ = false;\n}\n\nvoid Vertices::Builder::store_vertices(const float vertices[]) {\n  FML_DCHECK(is_valid());\n  FML_DCHECK(needs_vertices_);\n  char* pod = reinterpret_cast<char*>(vertices_.get());\n  store_points(pod, vertices_->vertices_offset_, vertices,\n               vertices_->vertex_count_);\n  needs_vertices_ = false;\n}\n\nvoid Vertices::Builder::store_texture_coordinates(const SkPoint coords[]) {\n  FML_DCHECK(is_valid());\n  FML_DCHECK(needs_texture_coords_);\n  char* pod = reinterpret_cast<char*>(vertices_.get());\n  size_t bytes = vertices_->vertex_count_ * sizeof(coords[0]);\n  memcpy(pod + vertices_->texture_coordinates_offset_, coords, bytes);\n  needs_texture_coords_ = false;\n}\n\nvoid Vertices::Builder::store_texture_coordinates(const float coords[]) {\n  FML_DCHECK(is_valid());\n  FML_DCHECK(needs_texture_coords_);\n  char* pod = reinterpret_cast<char*>(vertices_.get());\n  store_points(pod, vertices_->texture_coordinates_offset_, coords,\n               vertices_->vertex_count_);\n  needs_texture_coords_ = false;\n}\n\nvoid Vertices::Builder::store_colors(const clay::Color colors[]) {\n  FML_DCHECK(is_valid());\n  FML_DCHECK(needs_colors_);\n  char* pod = reinterpret_cast<char*>(vertices_.get());\n  size_t bytes = vertices_->vertex_count_ * sizeof(colors[0]);\n  memcpy(pod + vertices_->colors_offset_, colors, bytes);\n  needs_colors_ = false;\n}\n\nvoid Vertices::Builder::store_indices(const uint16_t indices[]) {\n  FML_DCHECK(is_valid());\n  FML_DCHECK(needs_indices_);\n  char* pod = reinterpret_cast<char*>(vertices_.get());\n  size_t bytes = vertices_->index_count_ * sizeof(indices[0]);\n  memcpy(pod + vertices_->indices_offset_, indices, bytes);\n  needs_indices_ = false;\n}\n\nstd::shared_ptr<Vertices> Vertices::Builder::build() {\n  FML_DCHECK(is_valid());\n  if (vertices_->vertex_count() <= 0) {\n    // We set this to true in the constructor to make sure that they\n    // call store_vertices() only once, but if there are no vertices\n    // then we will not object to them never having stored any vertices\n    needs_vertices_ = false;\n  }\n  FML_DCHECK(!needs_vertices_);\n  FML_DCHECK(!needs_texture_coords_);\n  FML_DCHECK(!needs_colors_);\n  FML_DCHECK(!needs_indices_);\n\n  vertices_->bounds_ =\n      compute_bounds(vertices_->vertices(), vertices_->vertex_count_);\n\n  return std::move(vertices_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/style/vertices.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_STYLE_VERTICES_H_\n#define CLAY_GFX_STYLE_VERTICES_H_\n\n#include <memory>\n\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n//------------------------------------------------------------------------------\n/// @brief      Defines the way in which the vertices of a Vertices object\n///             are separated into triangles into which to render.\n///\nenum class VertexMode {\n  /// The vertices are taken 3 at a time to form a triangle.\n  kTriangles,\n\n  /// The vertices are taken in overlapping triplets to form triangles, with\n  /// each triplet sharing 2 of its vertices with the preceding and following\n  /// triplets.\n  /// vertices [ABCDE] yield 3 triangles ABC,BCD,CDE\n  kTriangleStrip,\n\n  /// The vertices are taken in overlapping pairs and combined with the first\n  /// vertex to form triangles that radiate outward from the initial point.\n  /// vertices [ABCDE] yield 3 triangles ABC,ACD,ADE\n  kTriangleFan,\n};\n\ninline SkVertices::VertexMode ToSk(VertexMode dl_mode) {\n  return static_cast<SkVertices::VertexMode>(dl_mode);\n}\n\ninline VertexMode ToClay(SkVertices::VertexMode sk_mode) {\n  return static_cast<VertexMode>(sk_mode);\n}\n\n//------------------------------------------------------------------------------\n/// @brief      Holds all of the data (both required and optional) for a\n///             DisplayList drawVertices call.\n///\n/// There are 4 main pices of data:\n///   - vertices():\n///       the points on the rendering surface that define the pixels\n///       being rendered to in a series of triangles. These points\n///       can map to triangles in various ways depending on the\n///       supplied |VertexMode|.\n///   - texture_coordinates():\n///       the points in the DlColorSource to map to the coordinates\n///       of the triangles in the vertices(). If missing, the\n///       vertex coordinates themselves will be used to map the\n///       source colors to the vertices.\n///   - colors():\n///       colors to map to each triangle vertex. Note that each\n///       vertex is mapped to exactly 1 color even if the DlVertex\n///   - indices():\n///       An indirection based on indices into the array of vertices\n///       (and by extension, their associated texture_coordinates and\n///       colors). Note that the VertexMode will still apply to the\n///       indices in the same way (and instead of the way) that it\n///       would normally be applied to the vertices themselves. The\n///       indices are useful, for example, to fill the vertices with\n///       a grid of points and then use the indices to define a\n///       triangular mesh that covers that grid without having to\n///       repeat the vertex (and texture coordinate and color)\n///       information for the times when a given grid coordinate\n///       gets reused in up to 4 mesh triangles.\n///\n/// Note that each vertex is mapped to exactly 1 texture_coordinate and\n/// color even if the VertexMode or indices specify that it contributes\n/// to more than one output triangle.\n///\nclass Vertices {\n public:\n  /// @brief     A utility class to build up a |Vertices| object\n  ///            one set of data at a time.\n  class Builder {\n   public:\n    /// @brief     flags to indicate/promise which of the optional\n    ///            texture coordinates or colors will be supplied\n    ///            during the build phase.\n    union Flags {\n      struct {\n        unsigned has_texture_coordinates : 1;\n        unsigned has_colors : 1;\n      };\n      uint32_t mask = 0;\n\n      inline Flags operator|(const Flags& rhs) const {\n        return {.mask = (mask | rhs.mask)};\n      }\n\n      inline Flags& operator|=(const Flags& rhs) {\n        mask = mask | rhs.mask;\n        return *this;\n      }\n    };\n    static constexpr Flags kNone = {{false, false}};\n    static constexpr Flags kHasTextureCoordinates = {{true, false}};\n    static constexpr Flags kHasColors = {{false, true}};\n\n    //--------------------------------------------------------------------------\n    /// @brief     Constructs a Builder and prepares room for the\n    ///            required and optional data.\n    ///\n    /// Vertices are always required. Optional texture coordinates\n    /// and optional colors are reserved depending on the |Flags|.\n    /// Optional indices will be reserved if the index_count is\n    /// positive (>0).\n    ///\n    /// The caller must provide all data that is promised by the\n    /// supplied |flags| and |index_count| parameters before\n    /// calling the |build()| method.\n    Builder(VertexMode mode, int vertex_count, Flags flags, int index_count);\n\n    /// Returns true iff the underlying object was successfully allocated.\n    bool is_valid() { return vertices_ != nullptr; }\n\n    /// @brief Copies the indicated list of points as vertices.\n    ///\n    /// fails if vertices have already been supplied.\n    void store_vertices(const SkPoint points[]);\n\n    /// @brief Copies the indicated list of float pairs as vertices.\n    ///\n    /// fails if vertices have already been supplied.\n    void store_vertices(const float coordinates[]);\n\n    /// @brief Copies the indicated list of points as texture coordinates.\n    ///\n    /// fails if texture coordinates have already been supplied or if they\n    /// were not promised by the flags.has_texture_coordinates.\n    void store_texture_coordinates(const SkPoint points[]);\n\n    /// @brief Copies the indicated list of float pairs as texture coordinates.\n    ///\n    /// fails if texture coordinates have already been supplied or if they\n    /// were not promised by the flags.has_texture_coordinates.\n    void store_texture_coordinates(const float coordinates[]);\n\n    /// @brief Copies the indicated list of colors as vertex colors.\n    ///\n    /// fails if colors have already been supplied or if they were not\n    /// promised by the flags.has_colors.\n    void store_colors(const clay::Color colors[]);\n\n    /// @brief Copies the indicated list of unsigned ints as vertex colors\n    ///        in the 32-bit RGBA format.\n    ///\n    /// fails if colors have already been supplied or if they were not\n    /// promised by the flags.has_colors.\n    void store_colors(const uint32_t colors[]) {\n      store_colors(reinterpret_cast<const clay::Color*>(colors));\n    }\n\n    /// @brief Copies the indicated list of 16-bit indices as vertex indices.\n    ///\n    /// fails if indices have already been supplied or if they were not\n    /// promised by (index_count > 0).\n    void store_indices(const uint16_t indices[]);\n\n    /// @brief Finalizes and the constructed Vertices object.\n    ///\n    /// fails if any of the optional data promised in the constructor is\n    /// missing.\n    std::shared_ptr<Vertices> build();\n\n   private:\n    std::shared_ptr<Vertices> vertices_;\n    bool needs_vertices_;\n    bool needs_texture_coords_;\n    bool needs_colors_;\n    bool needs_indices_;\n  };\n\n  //--------------------------------------------------------------------------\n  /// @brief     Constructs a DlVector with compact inline storage for all\n  ///            of its required and optional lists of data.\n  ///\n  /// Vertices are always required. Optional texture coordinates\n  /// and optional colors are stored if the arguments are non-null.\n  /// Optional indices will be stored iff the array argument is\n  /// non-null and the index_count is positive (>0).\n  static std::shared_ptr<Vertices> Make(VertexMode mode, int vertex_count,\n                                        const SkPoint vertices[],\n                                        const SkPoint texture_coordinates[],\n                                        const clay::Color colors[],\n                                        int index_count = 0,\n                                        const uint16_t indices[] = nullptr);\n\n  /// Returns the size of the object including all of the inlined data.\n  size_t size() const;\n\n  /// Returns the bounds of the vertices.\n  SkRect bounds() const { return bounds_; }\n\n  /// Returns the vertex mode that defines how the vertices (or the indices)\n  /// are turned into triangles.\n  VertexMode mode() const { return mode_; }\n\n  /// Returns the number of vertices, which also applies to the number of\n  /// texture coordinate and colors if they are provided.\n  int vertex_count() const { return vertex_count_; }\n\n  /// Returns a pointer to the vertex information. Should be non-null.\n  const SkPoint* vertices() const {\n    return static_cast<const SkPoint*>(pod(vertices_offset_));\n  }\n\n  /// Returns a pointer to the vertex texture coordinate\n  /// or null if none were provided.\n  const SkPoint* texture_coordinates() const {\n    return static_cast<const SkPoint*>(pod(texture_coordinates_offset_));\n  }\n\n  /// Returns a pointer to the vertex colors\n  /// or null if none were provided.\n  const clay::Color* colors() const {\n    return static_cast<const clay::Color*>(pod(colors_offset_));\n  }\n\n  /// Returns a pointer to the count of vertex indices\n  /// or 0 if none were provided.\n  int index_count() const { return index_count_; }\n\n  /// Returns a pointer to the vertex indices\n  /// or null if none were provided.\n  const uint16_t* indices() const {\n    return static_cast<const uint16_t*>(pod(indices_offset_));\n  }\n\n  // Returns an equivalent sk_sp<SkVertices> analog to this object.\n  sk_sp<SkVertices> gr_object() const;\n\n  bool operator==(Vertices const& other) const;\n\n  bool operator!=(Vertices const& other) const { return !(*this == other); }\n\n private:\n  // Constructors are designed to encapsulate arrays sequentially in memory\n  // which means they can only be called by intantiations that use the\n  // new (ptr) paradigm which precomputes and preallocates the memory for\n  // the class body and all of its arrays, such as in Builder.\n  Vertices(VertexMode mode, int vertex_count, const SkPoint vertices[],\n           const SkPoint texture_coordinates[], const clay::Color colors[],\n           int index_count, const uint16_t indices[],\n           const SkRect* bounds = nullptr);\n\n  // This constructor is specifically used by the Vertices::Builder to\n  // establish the object before the copying of data is requested.\n  Vertices(VertexMode mode, int vertex_count, Builder::Flags flags,\n           int index_count);\n\n  // The copy constructor has the same memory pre-allocation requirements\n  // as this other constructors. This particular version is used by the\n  // DisplaylistBuilder to copy the instance into pre-allocated pod memory\n  // in the display list buffer.\n  explicit Vertices(const Vertices* other);\n\n  VertexMode mode_;\n\n  int vertex_count_;\n  size_t vertices_offset_;\n  size_t texture_coordinates_offset_;\n  size_t colors_offset_;\n\n  int index_count_;\n  size_t indices_offset_;\n\n  SkRect bounds_;\n\n  const void* pod(int offset) const {\n    if (offset <= 0) {\n      return nullptr;\n    }\n    const void* base = static_cast<const void*>(this);\n    return static_cast<const char*>(base) + offset;\n  }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_STYLE_VERTICES_H_\n"
  },
  {
    "path": "clay/gfx/style/vertices_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/attributes_testing.h\"\n#include \"clay/gfx/testing_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(DisplayListVertices, MakeWithZeroAndNegativeVerticesAndIndices) {\n  std::shared_ptr<const DlVertices> vertices1 = DlVertices::Make(\n      DlVertexMode::kTriangles, 0, nullptr, nullptr, nullptr, 0, nullptr);\n  EXPECT_NE(vertices1, nullptr);\n  EXPECT_EQ(vertices1->vertex_count(), 0);\n  EXPECT_EQ(vertices1->vertices(), nullptr);\n  EXPECT_EQ(vertices1->texture_coordinates(), nullptr);\n  EXPECT_EQ(vertices1->colors(), nullptr);\n  EXPECT_EQ(vertices1->index_count(), 0);\n  EXPECT_EQ(vertices1->indices(), nullptr);\n  EXPECT_NE(vertices1->gr_object(), nullptr);\n\n  std::shared_ptr<const DlVertices> vertices2 = DlVertices::Make(\n      DlVertexMode::kTriangles, -1, nullptr, nullptr, nullptr, -1, nullptr);\n  EXPECT_NE(vertices2, nullptr);\n  EXPECT_EQ(vertices2->vertex_count(), 0);\n  EXPECT_EQ(vertices2->vertices(), nullptr);\n  EXPECT_EQ(vertices2->texture_coordinates(), nullptr);\n  EXPECT_EQ(vertices2->colors(), nullptr);\n  EXPECT_EQ(vertices2->index_count(), 0);\n  EXPECT_EQ(vertices2->indices(), nullptr);\n  EXPECT_NE(vertices2->gr_object(), nullptr);\n\n  TestEquals(*vertices1, *vertices2);\n}\n\nTEST(DisplayListVertices, MakeWithTexAndColorAndIndices) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coords[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n  DlColor colors[3] = {\n      DlColor::kRed(),\n      DlColor::kCyan(),\n      DlColor::kGreen(),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, texture_coords, colors, 6, indices);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_NE(vertices->texture_coordinates(), nullptr);\n  ASSERT_NE(vertices->colors(), nullptr);\n  ASSERT_NE(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->texture_coordinates()[i], texture_coords[i]);\n    ASSERT_EQ(vertices->colors()[i], colors[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 6);\n  for (int i = 0; i < 6; i++) {\n    ASSERT_EQ(vertices->indices()[i], indices[i]);\n  }\n}\n\nTEST(DisplayListVertices, MakeWithTexAndColor) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coords[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n  DlColor colors[3] = {\n      DlColor::kRed(),\n      DlColor::kCyan(),\n      DlColor::kGreen(),\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, texture_coords, colors, 6, nullptr);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_NE(vertices->texture_coordinates(), nullptr);\n  ASSERT_NE(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->texture_coordinates()[i], texture_coords[i]);\n    ASSERT_EQ(vertices->colors()[i], colors[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, MakeWithTexAndIndices) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coords[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, texture_coords, nullptr, 6, indices);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_NE(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_NE(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->texture_coordinates()[i], texture_coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 6);\n  for (int i = 0; i < 6; i++) {\n    ASSERT_EQ(vertices->indices()[i], indices[i]);\n  }\n}\n\nTEST(DisplayListVertices, MakeWithColorAndIndices) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  DlColor colors[3] = {\n      DlColor::kRed(),\n      DlColor::kCyan(),\n      DlColor::kGreen(),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, nullptr, colors, 6, indices);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_NE(vertices->colors(), nullptr);\n  ASSERT_NE(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->colors()[i], colors[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 6);\n  for (int i = 0; i < 6; i++) {\n    ASSERT_EQ(vertices->indices()[i], indices[i]);\n  }\n}\n\nTEST(DisplayListVertices, MakeWithTex) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coords[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, texture_coords, nullptr, 6, nullptr);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_NE(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->texture_coordinates()[i], texture_coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, MakeWithColor) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  DlColor colors[3] = {\n      DlColor::kRed(),\n      DlColor::kCyan(),\n      DlColor::kGreen(),\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, nullptr, colors, 6, nullptr);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_NE(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->colors()[i], colors[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, MakeWithIndices) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, nullptr, nullptr, 6, indices);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_NE(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 6);\n  for (int i = 0; i < 6; i++) {\n    ASSERT_EQ(vertices->indices()[i], indices[i]);\n  }\n}\n\nTEST(DisplayListVertices, MakeWithNoOptionalData) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, nullptr, nullptr, 6, nullptr);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, MakeWithIndicesButZeroIndexCount) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, nullptr, nullptr, 0, indices);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, MakeWithIndicesButNegativeIndexCount) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  std::shared_ptr<const DlVertices> vertices = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, nullptr, nullptr, -5, indices);\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nusing Builder = DlVertices::Builder;\n\nTEST(DisplayListVertices, BuilderFlags) {\n  Builder::Flags flags;\n  EXPECT_FALSE(flags.has_texture_coordinates);\n  EXPECT_FALSE(flags.has_colors);\n\n  flags |= Builder::kHasTextureCoordinates;\n  EXPECT_TRUE(flags.has_texture_coordinates);\n  EXPECT_FALSE(flags.has_colors);\n\n  flags |= Builder::kHasColors;\n  EXPECT_TRUE(flags.has_texture_coordinates);\n  EXPECT_TRUE(flags.has_colors);\n\n  flags = Builder::Flags();\n  EXPECT_FALSE(flags.has_texture_coordinates);\n  EXPECT_FALSE(flags.has_colors);\n\n  flags |= Builder::kHasColors;\n  EXPECT_FALSE(flags.has_texture_coordinates);\n  EXPECT_TRUE(flags.has_colors);\n\n  flags |= Builder::kHasTextureCoordinates;\n  EXPECT_TRUE(flags.has_texture_coordinates);\n  EXPECT_TRUE(flags.has_colors);\n\n  EXPECT_FALSE(Builder::kNone.has_texture_coordinates);\n  EXPECT_FALSE(Builder::kNone.has_colors);\n\n  EXPECT_TRUE(Builder::kHasTextureCoordinates.has_texture_coordinates);\n  EXPECT_FALSE(Builder::kHasTextureCoordinates.has_colors);\n\n  EXPECT_FALSE(Builder::kHasColors.has_texture_coordinates);\n  EXPECT_TRUE(Builder::kHasColors.has_colors);\n\n  EXPECT_TRUE((Builder::kHasTextureCoordinates | Builder::kHasColors)  //\n                  .has_texture_coordinates);\n  EXPECT_TRUE((Builder::kHasTextureCoordinates | Builder::kHasColors)  //\n                  .has_colors);\n}\n\nTEST(DisplayListVertices, BuildWithZeroAndNegativeVerticesAndIndices) {\n  Builder builder1(DlVertexMode::kTriangles, 0, Builder::kNone, 0);\n  EXPECT_TRUE(builder1.is_valid());\n  std::shared_ptr<DlVertices> vertices1 = builder1.build();\n  EXPECT_NE(vertices1, nullptr);\n  EXPECT_EQ(vertices1->vertex_count(), 0);\n  EXPECT_EQ(vertices1->vertices(), nullptr);\n  EXPECT_EQ(vertices1->texture_coordinates(), nullptr);\n  EXPECT_EQ(vertices1->colors(), nullptr);\n  EXPECT_EQ(vertices1->index_count(), 0);\n  EXPECT_EQ(vertices1->indices(), nullptr);\n  EXPECT_NE(vertices1->gr_object(), nullptr);\n\n  Builder builder2(DlVertexMode::kTriangles, -1, Builder::kNone, -1);\n  EXPECT_TRUE(builder2.is_valid());\n  std::shared_ptr<DlVertices> vertices2 = builder2.build();\n  EXPECT_NE(vertices2, nullptr);\n  EXPECT_EQ(vertices2->vertex_count(), 0);\n  EXPECT_EQ(vertices2->vertices(), nullptr);\n  EXPECT_EQ(vertices2->texture_coordinates(), nullptr);\n  EXPECT_EQ(vertices2->colors(), nullptr);\n  EXPECT_EQ(vertices2->index_count(), 0);\n  EXPECT_EQ(vertices2->indices(), nullptr);\n  EXPECT_NE(vertices2->gr_object(), nullptr);\n\n  TestEquals(*vertices1, *vertices2);\n}\n\nTEST(DisplayListVertices, BuildWithTexAndColorAndIndices) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coords[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n  DlColor colors[3] = {\n      DlColor::kRed(),\n      DlColor::kCyan(),\n      DlColor::kGreen(),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3,  //\n                  Builder::kHasTextureCoordinates | Builder::kHasColors, 6);\n  builder.store_vertices(coords);\n  builder.store_texture_coordinates(texture_coords);\n  builder.store_colors(colors);\n  builder.store_indices(indices);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_NE(vertices->texture_coordinates(), nullptr);\n  ASSERT_NE(vertices->colors(), nullptr);\n  ASSERT_NE(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->texture_coordinates()[i], texture_coords[i]);\n    ASSERT_EQ(vertices->colors()[i], colors[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 6);\n  for (int i = 0; i < 6; i++) {\n    ASSERT_EQ(vertices->indices()[i], indices[i]);\n  }\n\n  Builder builder2(DlVertexMode::kTriangles, 3,  //\n                   Builder::kHasTextureCoordinates | Builder::kHasColors, 6);\n  builder2.store_vertices(coords);\n  builder2.store_texture_coordinates(texture_coords);\n  builder2.store_colors(colors);\n  builder2.store_indices(indices);\n  std::shared_ptr<const DlVertices> vertices2 = builder2.build();\n\n  TestEquals(*vertices, *vertices2);\n\n  std::shared_ptr<const DlVertices> vertices3 = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, texture_coords, colors, 6, indices);\n\n  TestEquals(*vertices, *vertices3);\n}\n\nTEST(DisplayListVertices, BuildWithTexAndColor) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coords[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n  DlColor colors[3] = {\n      DlColor::kRed(),\n      DlColor::kCyan(),\n      DlColor::kGreen(),\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3,  //\n                  Builder::kHasTextureCoordinates | Builder::kHasColors, 0);\n  builder.store_vertices(coords);\n  builder.store_texture_coordinates(texture_coords);\n  builder.store_colors(colors);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_NE(vertices->texture_coordinates(), nullptr);\n  ASSERT_NE(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->texture_coordinates()[i], texture_coords[i]);\n    ASSERT_EQ(vertices->colors()[i], colors[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, BuildWithTexAndIndices) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coords[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3,  //\n                  Builder::kHasTextureCoordinates, 6);\n  builder.store_vertices(coords);\n  builder.store_texture_coordinates(texture_coords);\n  builder.store_indices(indices);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_NE(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_NE(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->texture_coordinates()[i], texture_coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 6);\n  for (int i = 0; i < 6; i++) {\n    ASSERT_EQ(vertices->indices()[i], indices[i]);\n  }\n}\n\nTEST(DisplayListVertices, BuildWithColorAndIndices) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkColor colors[3] = {\n      SK_ColorRED,\n      SK_ColorCYAN,\n      SK_ColorGREEN,\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3,  //\n                  Builder::kHasColors, 6);\n  builder.store_vertices(coords);\n  builder.store_colors(colors);\n  builder.store_indices(indices);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_NE(vertices->colors(), nullptr);\n  ASSERT_NE(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->colors()[i], colors[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 6);\n  for (int i = 0; i < 6; i++) {\n    ASSERT_EQ(vertices->indices()[i], indices[i]);\n  }\n}\n\nTEST(DisplayListVertices, BuildWithTexUsingPoints) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coords[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3,  //\n                  Builder::kHasTextureCoordinates, 0);\n  builder.store_vertices(coords);\n  builder.store_texture_coordinates(texture_coords);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_NE(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->texture_coordinates()[i], texture_coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, BuildWithTexUsingFloats) {\n  float coords[6] = {\n      2,  3,  //\n      5,  6,  //\n      15, 20,\n  };\n  float texture_coords[6] = {\n      102, 103,  //\n      105, 106,  //\n      115, 120,\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3,  //\n                  Builder::kHasTextureCoordinates, 0);\n  builder.store_vertices(coords);\n  builder.store_texture_coordinates(texture_coords);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_NE(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i].fX, coords[i * 2 + 0]);\n    ASSERT_EQ(vertices->vertices()[i].fY, coords[i * 2 + 1]);\n    ASSERT_EQ(vertices->texture_coordinates()[i].fX, texture_coords[i * 2 + 0]);\n    ASSERT_EQ(vertices->texture_coordinates()[i].fY, texture_coords[i * 2 + 1]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, BuildUsingFloatsSameAsPoints) {\n  SkPoint coord_points[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coord_points[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n\n  float coord_floats[6] = {\n      2,  3,  //\n      5,  6,  //\n      15, 20,\n  };\n  float texture_coord_floats[6] = {\n      102, 103,  //\n      105, 106,  //\n      115, 120,\n  };\n\n  Builder builder_points(DlVertexMode::kTriangles, 3,  //\n                         Builder::kHasTextureCoordinates, 0);\n  builder_points.store_vertices(coord_points);\n  builder_points.store_texture_coordinates(texture_coord_points);\n  std::shared_ptr<const DlVertices> vertices_points = builder_points.build();\n\n  Builder builder_floats(DlVertexMode::kTriangles, 3,  //\n                         Builder::kHasTextureCoordinates, 0);\n  builder_floats.store_vertices(coord_floats);\n  builder_floats.store_texture_coordinates(texture_coord_floats);\n  std::shared_ptr<const DlVertices> vertices_floats = builder_floats.build();\n\n  TestEquals(*vertices_points, *vertices_floats);\n}\n\nTEST(DisplayListVertices, BuildWithColor) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkColor colors[3] = {\n      SK_ColorRED,\n      SK_ColorCYAN,\n      SK_ColorGREEN,\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3,  //\n                  Builder::kHasColors, 0);\n  builder.store_vertices(coords);\n  builder.store_colors(colors);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_NE(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n    ASSERT_EQ(vertices->colors()[i], colors[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, BuildWithIndices) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3, Builder::kNone, 6);\n  builder.store_vertices(coords);\n  builder.store_indices(indices);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_NE(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 6);\n  for (int i = 0; i < 6; i++) {\n    ASSERT_EQ(vertices->indices()[i], indices[i]);\n  }\n}\n\nTEST(DisplayListVertices, BuildWithNoOptionalData) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3, Builder::kNone, 0);\n  builder.store_vertices(coords);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, BuildWithNegativeIndexCount) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n\n  Builder builder(DlVertexMode::kTriangles, 3, Builder::kNone, -5);\n  builder.store_vertices(coords);\n  std::shared_ptr<const DlVertices> vertices = builder.build();\n\n  ASSERT_NE(vertices, nullptr);\n  ASSERT_NE(vertices->vertices(), nullptr);\n  ASSERT_EQ(vertices->texture_coordinates(), nullptr);\n  ASSERT_EQ(vertices->colors(), nullptr);\n  ASSERT_EQ(vertices->indices(), nullptr);\n\n  ASSERT_EQ(vertices->bounds(), SkRect::MakeLTRB(2, 3, 15, 20));\n  ASSERT_EQ(vertices->mode(), DlVertexMode::kTriangles);\n  ASSERT_EQ(vertices->vertex_count(), 3);\n  for (int i = 0; i < 3; i++) {\n    ASSERT_EQ(vertices->vertices()[i], coords[i]);\n  }\n  ASSERT_EQ(vertices->index_count(), 0);\n}\n\nTEST(DisplayListVertices, TestEquals) {\n  SkPoint coords[3] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n  };\n  SkPoint texture_coords[3] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n  };\n  DlColor colors[3] = {\n      DlColor::kRed(),\n      DlColor::kCyan(),\n      DlColor::kGreen(),\n  };\n  uint16_t indices[6] = {\n      2, 1, 0,  //\n      1, 2, 0,\n  };\n\n  std::shared_ptr<const DlVertices> vertices1 = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, texture_coords, colors, 6, indices);\n  std::shared_ptr<const DlVertices> vertices2 = DlVertices::Make(\n      DlVertexMode::kTriangles, 3, coords, texture_coords, colors, 6, indices);\n  TestEquals(*vertices1, *vertices2);\n}\n\nTEST(DisplayListVertices, TestNotEquals) {\n  SkPoint coords[4] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n      SkPoint::Make(53, 62),\n  };\n  SkPoint wrong_coords[4] = {\n      SkPoint::Make(2, 3),\n      SkPoint::Make(5, 6),\n      SkPoint::Make(15, 20),\n      SkPoint::Make(57, 62),\n  };\n  SkPoint texture_coords[4] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 120),\n      SkPoint::Make(153, 162),\n  };\n  SkPoint wrong_texture_coords[4] = {\n      SkPoint::Make(102, 103),\n      SkPoint::Make(105, 106),\n      SkPoint::Make(115, 121),\n      SkPoint::Make(153, 162),\n  };\n  DlColor colors[4] = {\n      DlColor::kRed(),\n      DlColor::kCyan(),\n      DlColor::kGreen(),\n      DlColor::kMagenta(),\n  };\n  DlColor wrong_colors[4] = {\n      DlColor::kRed(),\n      DlColor::kBlue(),\n      DlColor::kGreen(),\n      DlColor::kMagenta(),\n  };\n  uint16_t indices[9] = {\n      2, 1, 0,  //\n      1, 2, 0,  //\n      1, 2, 3,\n  };\n  uint16_t wrong_indices[9] = {\n      2, 1, 0,  //\n      1, 2, 0,  //\n      2, 3, 1,\n  };\n\n  std::shared_ptr<const DlVertices> vertices1 = DlVertices::Make(\n      DlVertexMode::kTriangles, 4, coords, texture_coords, colors, 9, indices);\n\n  {\n    std::shared_ptr<const DlVertices> vertices2 =\n        DlVertices::Make(DlVertexMode::kTriangleFan, 4, coords,  //\n                         texture_coords, colors, 9, indices);\n    TestNotEquals(*vertices1, *vertices2, \"vertex mode differs\");\n  }\n  {\n    std::shared_ptr<const DlVertices> vertices2 =\n        DlVertices::Make(DlVertexMode::kTriangles, 3, coords,  //\n                         texture_coords, colors, 9, indices);\n    TestNotEquals(*vertices1, *vertices2, \"vertex count differs\");\n  }\n  {\n    std::shared_ptr<const DlVertices> vertices2 =\n        DlVertices::Make(DlVertexMode::kTriangles, 4, wrong_coords,  //\n                         texture_coords, colors, 9, indices);\n    TestNotEquals(*vertices1, *vertices2, \"vertex coordinates differ\");\n  }\n  {\n    std::shared_ptr<const DlVertices> vertices2 =\n        DlVertices::Make(DlVertexMode::kTriangles, 4, coords,  //\n                         wrong_texture_coords, colors, 9, indices);\n    TestNotEquals(*vertices1, *vertices2, \"texture coordinates differ\");\n  }\n  {\n    std::shared_ptr<const DlVertices> vertices2 =\n        DlVertices::Make(DlVertexMode::kTriangles, 4, coords,  //\n                         texture_coords, wrong_colors, 9, indices);\n    TestNotEquals(*vertices1, *vertices2, \"colors differ\");\n  }\n  {\n    std::shared_ptr<const DlVertices> vertices2 =\n        DlVertices::Make(DlVertexMode::kTriangles, 4, coords,  //\n                         texture_coords, colors, 6, indices);\n    TestNotEquals(*vertices1, *vertices2, \"index count differs\");\n  }\n  {\n    std::shared_ptr<const DlVertices> vertices2 =\n        DlVertices::Make(DlVertexMode::kTriangles, 4, coords,  //\n                         texture_coords, colors, 9, wrong_indices);\n    TestNotEquals(*vertices1, *vertices2, \"indices differ\");\n  }\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/svg/svg_dom.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_SVG_SVG_DOM_H_\n#define CLAY_GFX_SVG_SVG_DOM_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/include/skity/io/data.hpp\"\n\nnamespace clay {\nclass SVGDom : public std::enable_shared_from_this<SVGDom> {\n public:\n  using ImageCallback =\n      std::function<std::shared_ptr<skity::Image>(std::string url)>;\n  virtual ~SVGDom() = default;\n  static std::shared_ptr<SVGDom> Create(std::shared_ptr<skity::Data> data,\n                                        ImageCallback callback);\n\n  virtual std::shared_ptr<skity::Image> Render(\n      int width, int height, fml::RefPtr<GPUUnrefQueue> unref_queue) = 0;\n};\n}  // namespace clay\n\n#endif  // CLAY_GFX_SVG_SVG_DOM_H_\n"
  },
  {
    "path": "clay/gfx/testing_utils.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_GFX_TESTING_UTILS_H_\n#define CLAY_GFX_TESTING_UTILS_H_\n\n#include \"clay/gfx/gfx_rendering_backend.h\"\n#include \"clay/gfx/paint.h\"\n#include \"clay/gfx/style/color_source.h\"\n#include \"clay/gfx/style/image_filter.h\"\n#include \"clay/gfx/style/mask_filter.h\"\n#include \"clay/gfx/style/path_effect.h\"\n#include \"clay/gfx/style/vertices.h\"\n#include \"clay/gfx/text_blob.h\"\n\nnamespace clay {\nusing DlColor = clay::Color;\nusing DlPathEffect = clay::PathEffect;\nusing DlBlendMode = clay::BlendMode;\nusing DlBlurImageFilter = clay::BlurImageFilter;\nusing DlTileMode = clay::TileMode;\nusing DlMatrixColorFilter = clay::MatrixColorFilter;\nusing DlUnknownImageFilter = clay::UnknownImageFilter;\nusing DlDilateImageFilter = clay::DilateImageFilter;\nusing DlErodeImageFilter = clay::ErodeImageFilter;\nusing DlBlurMaskFilter = clay::BlurMaskFilter;\nusing DlColorSource = clay::ColorSource;\nusing DlDashPathEffect = clay::DashPathEffect;\nusing DlImageColorSource = clay::ImageColorSource;\nusing DlImageSampling = clay::ImageSampling;\nusing DlFilterMode = clay::FilterMode;\nusing DlColorFilter = clay::ColorFilter;\nusing DlBlendColorFilter = clay::BlendColorFilter;\nusing DlSrgbToLinearGammaColorFilter = clay::SrgbToLinearGammaColorFilter;\nusing DlLinearToSrgbGammaColorFilter = clay::LinearToSrgbGammaColorFilter;\nusing DlMatrixImageFilter = clay::MatrixImageFilter;\nusing DlImageFilter = clay::ImageFilter;\nusing DlColorFilterType = clay::ColorFilterType;\nusing DlUnknownColorFilter = clay::UnknownColorFilter;\nusing DlRuntimeEffect = clay::RuntimeEffect;\nusing DlColorSourceType = clay::ColorSourceType;\nusing DlColorColorSource = clay::ColorColorSource;\nusing DlUnknownColorSource = clay::UnknownColorSource;\nusing DlRuntimeEffectColorSource = clay::RuntimeEffectColorSource;\nusing DlImageFilterType = clay::ImageFilterType;\nusing DlColorFilterImageFilter = clay::ColorFilterImageFilter;\nusing DlMaskFilter = clay::MaskFilter;\nusing DlComposeImageFilter = clay::ComposeImageFilter;\nusing DlLocalMatrixImageFilter = clay::LocalMatrixImageFilter;\nusing DlPathEffectType = clay::PathEffectType;\nusing DlUnknownPathEffect = clay::UnknownPathEffect;\nusing DlMaskFilterType = clay::MaskFilterType;\nusing DlUnknownMaskFilter = clay::UnknownMaskFilter;\nusing DlLinearGradientColorSource = clay::LinearGradientColorSource;\nusing DlRadialGradientColorSource = clay::RadialGradientColorSource;\nusing DlConicalGradientColorSource = clay::ConicalGradientColorSource;\nusing DlSweepGradientColorSource = clay::SweepGradientColorSource;\nusing DlPaint = clay::Paint;\nusing DlDrawStyle = clay::DrawStyle;\nusing DlStrokeCap = clay::StrokeCap;\nusing DlStrokeJoin = clay::StrokeJoin;\nusing DlVertices = clay::Vertices;\nusing DlVertexMode = clay::VertexMode;\nusing DlTextBlob = clay::TextBlob;\nusing DlImage = clay::PaintImage;\n}  // namespace clay\n\n#endif  // CLAY_GFX_TESTING_UTILS_H_\n"
  },
  {
    "path": "clay/gfx/text_blob.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/text_blob.h\"\n\n#include <utility>\n\n#include \"clay/gfx/gfx_rendering_backend.h\"\n\nnamespace clay {\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<TextBlob> TextBlob::Make(const SkTextBlob* text_blob) {\n  return Make(sk_ref_sp(text_blob));\n}\n\nfml::RefPtr<TextBlob> TextBlob::Make(sk_sp<SkTextBlob> text_blob) {\n  return fml::MakeRefCounted<TextBlobSkia>(std::move(text_blob));\n}\n#else\nfml::RefPtr<TextBlob> TextBlob::Make(\n    std::shared_ptr<skity::TextBlob> text_blob) {\n  return fml::MakeRefCounted<TextBlobSkity>(std::move(text_blob));\n}\n#endif\n\nTextBlob::TextBlob() = default;\n\nTextBlob::~TextBlob() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/text_blob.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_TEXT_BLOB_H_\n#define CLAY_GFX_TEXT_BLOB_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace skity {\nclass TextBlob;\n}  // namespace skity\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// @brief      TextBlob combines multiple text runs into an immutable\n///             container.\n///\nclass TextBlob : public fml::RefCountedThreadSafe<TextBlob> {\n public:\n#ifndef ENABLE_SKITY\n  static fml::RefPtr<TextBlob> Make(const SkTextBlob* text_blob);\n#endif  // ENABLE_SKITY\n\n  static fml::RefPtr<TextBlob> Make(GrTextBlobPtr text_blob);\n\n  virtual ~TextBlob();\n\n  virtual GrTextBlobPtr gr_text_blob() const = 0;\n\n  virtual skity::Rect bounds() const = 0;\n\n protected:\n  TextBlob();\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_TEXT_BLOB_H_\n"
  },
  {
    "path": "clay/gfx/text_blob_skia.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/text_blob_skia.h\"\n\n#include <utility>\n\n#include \"clay/gfx/skity_to_skia_utils.h\"\n\nnamespace clay {\n\nTextBlobSkia::TextBlobSkia(sk_sp<SkTextBlob> text_blob)\n    : text_blob_(std::move(text_blob)) {}\n\n// |TextBlob|\nTextBlobSkia::~TextBlobSkia() = default;\n\n// |TextBlob|\nsk_sp<SkTextBlob> TextBlobSkia::gr_text_blob() const { return text_blob_; };\n\n// |TextBlob|\nskity::Rect TextBlobSkia::bounds() const {\n  if (!text_blob_) {\n    return skity::Rect::MakeEmpty();\n  }\n  return ConvertSkRectToSkityRect(text_blob_->bounds());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/text_blob_skia.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_TEXT_BLOB_SKIA_H_\n#define CLAY_GFX_TEXT_BLOB_SKIA_H_\n\n#include <memory>\n\n#include \"clay/gfx/text_blob.h\"\n#include \"third_party/skia/include/core/SkTextBlob.h\"\n\nnamespace clay {\n\nclass TextBlobSkia final : public TextBlob {\n public:\n  explicit TextBlobSkia(sk_sp<SkTextBlob> text_blob);\n\n  // |TextBlob|\n  ~TextBlobSkia() override;\n\n  // |TextBlob|\n  sk_sp<SkTextBlob> gr_text_blob() const override;\n\n  // |TextBlob|\n  skity::Rect bounds() const override;\n\n private:\n  sk_sp<SkTextBlob> text_blob_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TextBlobSkia);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_TEXT_BLOB_SKIA_H_\n"
  },
  {
    "path": "clay/gfx/text_blob_skity.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/text_blob_skity.h\"\n\n#include <utility>\n\nnamespace clay {\n\nfml::RefPtr<TextBlobSkity> TextBlobSkity::Make(\n    std::shared_ptr<skity::TextBlob> text_blob) {\n  if (!text_blob) {\n    return nullptr;\n  }\n  return fml::MakeRefCounted<TextBlobSkity>(std::move(text_blob));\n}\n\nTextBlobSkity::TextBlobSkity(std::shared_ptr<skity::TextBlob> text_blob)\n    : text_blob_(std::move(text_blob)) {}\n\n// |TextBlob|\nTextBlobSkity::~TextBlobSkity() = default;\n\n// |TextBlob|\nstd::shared_ptr<skity::TextBlob> TextBlobSkity::gr_text_blob() const {\n  return text_blob_;\n}\n\n// |TextBlob|\nskity::Rect TextBlobSkity::bounds() const {\n  if (!text_blob_) {\n    return skity::Rect::MakeEmpty();\n  }\n\n  auto bound_size = text_blob_->GetBoundSize();\n  return skity::Rect::MakeSize({bound_size.x, bound_size.y});\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/text_blob_skity.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_TEXT_BLOB_SKITY_H_\n#define CLAY_GFX_TEXT_BLOB_SKITY_H_\n\n#include <memory>\n\n#include \"clay/gfx/text_blob.h\"\n#include \"skity/text/text_blob.hpp\"\n\nnamespace clay {\n\nclass TextBlobSkity final : public TextBlob {\n public:\n  static fml::RefPtr<TextBlobSkity> Make(\n      std::shared_ptr<skity::TextBlob> text_blob);\n\n  explicit TextBlobSkity(std::shared_ptr<skity::TextBlob> text_blob);\n\n  // |TextBlob|\n  ~TextBlobSkity() override;\n\n  // |TextBlob|\n  std::shared_ptr<skity::TextBlob> gr_text_blob() const override;\n\n  // |TextBlob|\n  skity::Rect bounds() const override;\n\n private:\n  std::shared_ptr<skity::TextBlob> text_blob_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TextBlobSkity);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_TEXT_BLOB_SKITY_H_\n"
  },
  {
    "path": "clay/gfx/vulkan/build.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\ngfx_vulkan_sources = [\n  \"vulkan_buffer.cc\",\n  \"vulkan_buffer.h\",\n  \"vulkan_command_pool_helper.cc\",\n  \"vulkan_command_pool_helper.h\",\n  \"vulkan_fence_helper.cc\",\n  \"vulkan_fence_helper.h\",\n  \"vulkan_helper.cc\",\n  \"vulkan_helper.h\",\n  \"vulkan_image.cc\",\n  \"vulkan_image.h\",\n]\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_buffer.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/vulkan/vulkan_buffer.h\"\n\n#include <cstring>\n\n#include \"base/include/closure.h\"\n#include \"clay/gfx/vulkan/vulkan_helper.h\"\n\nnamespace clay {\n\nnamespace {\nvoid createBuffer(VkDevice device, VkPhysicalDevice physical_device,\n                  VkDeviceSize size, VkBufferUsageFlags usage,\n                  VkMemoryPropertyFlags properties, VkBuffer *buffer,\n                  VkDeviceMemory *buffer_memory) {\n  VkBufferCreateInfo buffer_info;\n  memset(&buffer_info, 0, sizeof(VkBufferCreateInfo));\n  buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;\n  buffer_info.flags = 0;\n  buffer_info.size = size;\n  buffer_info.usage = usage;\n  buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;\n  buffer_info.queueFamilyIndexCount = 0;\n  buffer_info.pQueueFamilyIndices = nullptr;\n\n  auto result = VulkanHelper::GetInstance().CreateBuffer(device, &buffer_info,\n                                                         nullptr, buffer);\n  if (!LogVkIfNotSuccess(result, \"vkCreateBuffer\")) {\n    FML_LOG(ERROR) << \"vkCreateBuffer failed\";\n    return;\n  }\n\n  VkMemoryRequirements mem_requirements;\n  VulkanHelper::GetInstance().GetBufferMemoryRequirements(device, *buffer,\n                                                          &mem_requirements);\n\n  VkMemoryAllocateInfo alloc_info;\n  memset(&alloc_info, 0, sizeof(VkMemoryAllocateInfo));\n  alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;\n  alloc_info.pNext = nullptr;\n  alloc_info.allocationSize = mem_requirements.size;\n  alloc_info.memoryTypeIndex = FindMemoryType(mem_requirements.memoryTypeBits,\n                                              physical_device, properties);\n\n  result = VulkanHelper::GetInstance().AllocateMemory(device, &alloc_info,\n                                                      nullptr, buffer_memory);\n  if (!LogVkIfNotSuccess(result, \"vkAllocateMemory\")) {\n    return;\n  }\n\n  result = VulkanHelper::GetInstance().BindBufferMemory(device, *buffer,\n                                                        *buffer_memory, 0);\n  if (!LogVkIfNotSuccess(result, \"vkBindBufferMemory\")) {\n    return;\n  }\n}\n}  // namespace\n\nvoid VulkanBuffer::Reset() {\n  if (device_ == VK_NULL_HANDLE) {\n    return;\n  }\n  if (buffer_ != VK_NULL_HANDLE) {\n    VulkanHelper::GetInstance().DestroyBuffer(device_, buffer_, nullptr);\n    buffer_ = VK_NULL_HANDLE;\n  }\n  if (memory_ != VK_NULL_HANDLE) {\n    VulkanHelper::GetInstance().FreeMemory(device_, memory_, nullptr);\n    memory_ = VK_NULL_HANDLE;\n  }\n}\n\nvoid VulkanBuffer::CreateIndexBuffer(VkDevice device,\n                                     VkPhysicalDevice physical_device,\n                                     const std::vector<uint32_t> &indices) {\n  device_ = device;\n  physical_device_ = physical_device;\n\n  VkBufferCreateInfo buffer_info = {};\n  buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;\n  buffer_info.size = sizeof(indices[0]) * indices.size();\n  buffer_info.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT;\n  buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;\n\n  auto result = VulkanHelper::GetInstance().CreateBuffer(device, &buffer_info,\n                                                         nullptr, &buffer_);\n  if (!LogVkIfNotSuccess(result, \"vkCreateBuffer\")) {\n    return;\n  }\n\n  VkMemoryRequirements mem_requirements;\n  VulkanHelper::GetInstance().GetBufferMemoryRequirements(device, buffer_,\n                                                          &mem_requirements);\n\n  VkMemoryAllocateInfo alloc_info = {};\n  alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;\n  alloc_info.allocationSize = mem_requirements.size;\n  alloc_info.memoryTypeIndex =\n      FindMemoryType(mem_requirements.memoryTypeBits, physical_device,\n                     VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |\n                         VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);\n\n  VulkanHelper::GetInstance().AllocateMemory(device, &alloc_info, nullptr,\n                                             &memory_);\n  if (!LogVkIfNotSuccess(result, \"vkAllocateMemory\")) {\n    return;\n  }\n\n  result =\n      VulkanHelper::GetInstance().BindBufferMemory(device, buffer_, memory_, 0);\n  if (!LogVkIfNotSuccess(result, \"vkBindBufferMemory\")) {\n    return;\n  }\n\n  void *data;\n  result = VulkanHelper::GetInstance().MapMemory(device, memory_, 0,\n                                                 buffer_info.size, 0, &data);\n  if (!LogVkIfNotSuccess(result, \"vkMapMemory\")) {\n    return;\n  }\n\n  memcpy(data, indices.data(), buffer_info.size);\n  VulkanHelper::GetInstance().UnmapMemory(device, memory_);\n}\n\nvoid VulkanBuffer::CreateUniformBuffer(VkDevice device,\n                                       VkPhysicalDevice physical_device,\n                                       UniformBufferObject ubo) {\n  device_ = device;\n  physical_device_ = physical_device;\n\n  if (buffer_ == VK_NULL_HANDLE) {\n    VkDeviceSize buffer_size = sizeof(UniformBufferObject);\n    createBuffer(device, physical_device, buffer_size,\n                 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,\n                 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |\n                     VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,\n                 &buffer_, &memory_);\n    if (buffer_ == VK_NULL_HANDLE) {\n      return;\n    }\n  }\n\n  void *data;\n  auto result = VulkanHelper::GetInstance().MapMemory(device, memory_, 0,\n                                                      sizeof(ubo), 0, &data);\n  if (!LogVkIfNotSuccess(result, \"vkMapMemory\")) {\n    return;\n  }\n  memcpy(data, &ubo, sizeof(ubo));\n  VulkanHelper::GetInstance().UnmapMemory(device, memory_);\n}\n\nvoid VulkanBuffer::CreateVertexBuffer(VkDevice device,\n                                      VkPhysicalDevice physical_device,\n                                      const std::vector<Vertex> &vertices,\n                                      VkCommandBuffer command_buffer) {\n  device_ = device;\n  physical_device_ = physical_device;\n\n  VkDeviceSize buffer_size = sizeof(vertices[0]) * vertices.size();\n\n  if (buffer_ == VK_NULL_HANDLE) {\n    // Create vertex buffer.\n    createBuffer(device, physical_device, buffer_size,\n                 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,\n                 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |\n                     VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,\n                 &buffer_, &memory_);\n  }\n\n  void *data;\n  auto result = VulkanHelper::GetInstance().MapMemory(device, memory_, 0,\n                                                      buffer_size, 0, &data);\n  if (!LogVkIfNotSuccess(result, \"vkMapMemory\")) {\n    return;\n  }\n  memcpy(data, vertices.data(), static_cast<size_t>(buffer_size));\n  VulkanHelper::GetInstance().UnmapMemory(device, memory_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_buffer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_VULKAN_VULKAN_BUFFER_H_\n#define CLAY_GFX_VULKAN_VULKAN_BUFFER_H_\n\n#include <vulkan/vulkan.h>\n\n#include <vector>\n\nnamespace clay {\n\nstruct Vertex {\n  float position[2];\n  float tex_coord[2];\n};\n\nstruct TransformBlock {\n  float transform[16];\n};\n\nstruct UniformBufferObject {\n  float transform[16];\n  float width;\n  float height;\n  float padding[2];  // padding align to mat4\n};\n\nclass VulkanBuffer {\n public:\n  VulkanBuffer() = default;\n  ~VulkanBuffer() = default;\n\n  void CreateIndexBuffer(VkDevice device, VkPhysicalDevice physical_device,\n                         const std::vector<uint32_t>& indices);\n\n  void CreateUniformBuffer(VkDevice device, VkPhysicalDevice physical_device,\n                           UniformBufferObject ubo);\n\n  void CreateVertexBuffer(VkDevice device, VkPhysicalDevice physical_device,\n                          const std::vector<Vertex>& vertices,\n                          VkCommandBuffer command_buffer);\n\n  void Reset();\n\n  VkBuffer GetBuffer() const { return buffer_; }\n  VkDeviceMemory GetMemory() const { return memory_; }\n\n private:\n  VkBuffer buffer_ = VK_NULL_HANDLE;\n  VkDeviceMemory memory_ = VK_NULL_HANDLE;\n  VkDevice device_ = VK_NULL_HANDLE;\n  VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_VULKAN_VULKAN_BUFFER_H_\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_command_pool_helper.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/vulkan/vulkan_command_pool_helper.h\"\n\n#include <cstring>\n#include <vector>\n\n#include \"clay/gfx/vulkan/vulkan_helper.h\"\n\nnamespace clay {\n\nnamespace {\nint findGraphicsQueueFamilyIndex(VkPhysicalDevice device) {\n  uint32_t queue_family_count = 0;\n  clay::VulkanHelper::GetInstance().GetPhysicalDeviceQueueFamilyProperties(\n      device, &queue_family_count, nullptr);\n\n  std::vector<VkQueueFamilyProperties> queue_families(queue_family_count);\n  clay::VulkanHelper::GetInstance().GetPhysicalDeviceQueueFamilyProperties(\n      device, &queue_family_count, queue_families.data());\n\n  int i = 0;\n  for (const auto& queue_family : queue_families) {\n    if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) {\n      return i;  // Return the index of the queue family that supports graphics\n    }\n    i++;\n  }\n\n  FML_LOG(ERROR) << \"Failed to find a graphics-supporting queue family!\";\n  return -1;\n}\n}  // namespace\n\nVulkanCommandPoolHelper::VulkanCommandPoolHelper(\n    VkDevice device, VkPhysicalDevice physical_device)\n    : device_(device), physical_device_(physical_device) {\n  CreateCommandPool();\n}\n\nvoid VulkanCommandPoolHelper::CreateCommandPool() {\n  uint32_t graphics_queue_family_index =\n      findGraphicsQueueFamilyIndex(physical_device_);\n\n  VkCommandPoolCreateInfo pool_info;\n  memset(&pool_info, 0, sizeof(VkCommandPoolCreateInfo));\n  pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;\n  pool_info.pNext = nullptr;\n  pool_info.queueFamilyIndex = graphics_queue_family_index;\n  pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;\n\n  if (!LogVkIfNotSuccess(clay::VulkanHelper::GetInstance().CreateCommandPool(\n                             device_, &pool_info, nullptr, &command_pool_),\n                         \"vkCreateCommandPool\")) {\n    return;\n  }\n}\n\nvoid VulkanCommandPoolHelper::InsertUsedBuffer(VkCMBStatus cmb_status) {\n  used_buffers_.push_back(cmb_status);\n}\n\nvoid VulkanCommandPoolHelper::GetCommandBuffer(\n    VkCommandBuffer* command_buffer) {\n  if (!command_buffer) {\n    return;\n  }\n\n  if (!free_buffers_.empty()) {\n    *command_buffer = free_buffers_.front();\n    free_buffers_.pop_front();\n    return;\n  }\n\n  VkCommandBufferAllocateInfo alloc_info;\n  memset(&alloc_info, 0, sizeof(VkCommandBufferAllocateInfo));\n  alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;\n  alloc_info.pNext = nullptr;\n  alloc_info.commandPool = command_pool_;\n  alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;\n  alloc_info.commandBufferCount = 1;\n\n  if (!LogVkIfNotSuccess(\n          clay::VulkanHelper::GetInstance().AllocateCommandBuffers(\n              device_, &alloc_info, command_buffer),\n          \"vkAllocateCommandBuffers\")) {\n    return;\n  }\n}\n\nvoid VulkanCommandPoolHelper::Destroy() {\n  // CAUTION: We consume the command buffers are all not in use, and this needs\n  // to be assured outside.\n\n  while (!used_buffers_.empty()) {\n    // Destroy vkCommandPool will destroy all command buffers created from it,\n    // so we do not destroy any cmb here.\n    VkCMBStatus front_cmb = used_buffers_.front();\n    if (!front_cmb.has_signal_semaphore && front_cmb.fence != VK_NULL_HANDLE) {\n      VulkanHelper::GetInstance().DestroyFence(device_, front_cmb.fence,\n                                               nullptr);\n    }\n    for (auto semaphore : front_cmb.semaphores) {\n      if (semaphore != VK_NULL_HANDLE) {\n        VulkanHelper::GetInstance().DestroySemaphore(device_, semaphore,\n                                                     nullptr);\n      }\n    }\n    used_buffers_.pop_front();\n  }\n  free_buffers_.clear();\n\n  // fence_semaphores_ contains all fences that submitted with a signal\n  // semaphore.\n  for (auto fence_semaphore : fence_semaphores_) {\n    if (fence_semaphore) {\n      fence_semaphore->Destroy();\n    }\n  }\n\n  if (command_pool_ != VK_NULL_HANDLE) {\n    VulkanHelper::GetInstance().DestroyCommandPool(device_, command_pool_,\n                                                   nullptr);\n    command_pool_ = VK_NULL_HANDLE;\n  }\n}\n\nvoid VulkanCommandPoolHelper::FreeUsedBuffers() {\n  while (!used_buffers_.empty()) {\n    VkCMBStatus front_cmb = used_buffers_.front();\n    VkResult result =\n        VulkanHelper::GetInstance().GetFenceStatus(device_, front_cmb.fence);\n    if (result == VK_NOT_READY) {\n      break;\n    }\n    if (result == VK_SUCCESS) {\n      // this command buffer has already been executed. It is safe to reset.\n      VulkanHelper::GetInstance().ResetCommandBuffer(front_cmb.command_buffer,\n                                                     0);\n      free_buffers_.push_back(front_cmb.command_buffer);\n      // If there has been signal semaphores, we need to keep fence alive until\n      // the sempahore waiting is done.\n      if (!front_cmb.has_signal_semaphore &&\n          front_cmb.fence != VK_NULL_HANDLE) {\n        VulkanHelper::GetInstance().DestroyFence(device_, front_cmb.fence,\n                                                 nullptr);\n      }\n      for (auto semaphore : front_cmb.semaphores) {\n        if (semaphore != VK_NULL_HANDLE) {\n          VulkanHelper::GetInstance().DestroySemaphore(device_, semaphore,\n                                                       nullptr);\n        }\n      }\n      used_buffers_.pop_front();\n    } else {\n      LogVkIfNotSuccess(result, \"vkGetFenceStatus\");\n      return;\n    }\n  }\n}\n\nvoid VulkanCommandPoolHelper::InsertFenceSemaphore(\n    std::shared_ptr<VulkanFenceHelper> fence_semaphore) {\n  fence_semaphores_.push_back(fence_semaphore);\n}\n\nvoid VulkanCommandPoolHelper::FreeSignalSemaphoresAndFences() {\n  while (!fence_semaphores_.empty()) {\n    auto fence_semaphore = fence_semaphores_.front();\n    if (fence_semaphore == nullptr) {\n      fence_semaphores_.pop_front();\n      continue;\n    }\n    std::list<VkFence> pending_fences;\n    for (auto cmb_status : used_buffers_) {\n      pending_fences.push_back(cmb_status.fence);\n    }\n    if (fence_semaphore->CanDestroy() &&\n        !fence_semaphore->InPendingQueue(pending_fences)) {\n      fence_semaphore->Destroy();\n      fence_semaphores_.pop_front();\n    } else {\n      break;\n    }\n  }\n}\n\nVulkanCommandPoolHelper::~VulkanCommandPoolHelper() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_command_pool_helper.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_VULKAN_VULKAN_COMMAND_POOL_HELPER_H_\n#define CLAY_GFX_VULKAN_VULKAN_COMMAND_POOL_HELPER_H_\n\n#include <vulkan/vulkan.h>\n\n#include <list>\n#include <memory>\n\n#include \"clay/gfx/vulkan/vulkan_fence_helper.h\"\n\nnamespace clay {\n\nclass VulkanCommandPoolHelper {\n public:\n  struct VkCMBStatus {\n    VkCommandBuffer command_buffer;\n    VkFence fence;\n    std::list<VkSemaphore> semaphores;\n    bool has_signal_semaphore;\n  };\n\n  VulkanCommandPoolHelper(VkDevice device, VkPhysicalDevice physical_device);\n  ~VulkanCommandPoolHelper();\n\n  void GetCommandBuffer(VkCommandBuffer* command_buffer);\n\n  void InsertUsedBuffer(VkCMBStatus cmb_status);\n\n  void FreeUsedBuffers();\n\n  void InsertFenceSemaphore(\n      std::shared_ptr<clay::VulkanFenceHelper> fence_semaphore);\n  void FreeSignalSemaphoresAndFences();\n\n  void Destroy();\n\n private:\n  void CreateCommandPool();\n\n  VkDevice device_ = VK_NULL_HANDLE;\n  VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;\n\n  VkCommandPool command_pool_ = VK_NULL_HANDLE;\n  std::list<VkCMBStatus> used_buffers_;\n  std::list<VkCommandBuffer> free_buffers_;\n\n  // Signal semaphores and fences will not be destroyed when it is done in vk\n  // queue, because clay raster thread may still want to check its status.\n  std::list<std::shared_ptr<clay::VulkanFenceHelper>> fence_semaphores_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_VULKAN_VULKAN_COMMAND_POOL_HELPER_H_\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_fence_helper.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/vulkan/vulkan_fence_helper.h\"\n\n#include \"clay/gfx/shared_image/android_egl_image_representation.h\"\n#include \"clay/gfx/vulkan/vulkan_helper.h\"\nnamespace clay {\n\nVulkanFenceHelper::VulkanFenceHelper(VkDevice device, VkFence fence,\n                                     VkSemaphore semaphore)\n    : device_(device), fence_(fence), semaphore_(semaphore) {}\n\nvoid VulkanFenceHelper::MarkCanDestroy() {\n  std::unique_lock<std::mutex> lock(mutex_);\n  can_destroy_ = true;\n}\n\nbool VulkanFenceHelper::CanDestroy() {\n  std::unique_lock<std::mutex> lock(mutex_);\n  return can_destroy_;\n}\n\nvoid VulkanFenceHelper::Destroy() {\n  std::unique_lock<std::mutex> lock(valid_mutex_);\n  if (!is_valid_) {\n    return;\n  }\n  is_valid_ = false;\n  if (fence_ != VK_NULL_HANDLE) {\n    VulkanHelper::GetInstance().DestroyFence(device_, fence_, nullptr);\n    fence_ = VK_NULL_HANDLE;\n  }\n  if (semaphore_ != VK_NULL_HANDLE) {\n    VulkanHelper::GetInstance().DestroySemaphore(device_, semaphore_, nullptr);\n    semaphore_ = VK_NULL_HANDLE;\n  }\n}\n\nbool VulkanFenceHelper::WaitForFence() {\n  std::unique_lock<std::mutex> lock(valid_mutex_);\n  if (!is_valid_) {\n    return false;\n  }\n  if (fence_ == VK_NULL_HANDLE) {\n    FML_LOG(ERROR) << \"VkFenceSync::ClientWait: fence is VK_NULL_HANDLE\";\n    return false;\n  }\n  auto result = VulkanHelper::GetInstance().WaitForFences(device_, 1, &fence_,\n                                                          VK_TRUE, UINT64_MAX);\n  if (!LogVkIfNotSuccess(result, \"vkWaitForFences\")) {\n    return false;\n  }\n  return true;\n}\n\nbool VulkanFenceHelper::WaitSemaphore() {\n  std::unique_lock<std::mutex> lock(valid_mutex_);\n  if (!is_valid_) {\n    return false;\n  }\n  int32_t fd = GetSyncFd();\n  if (fd == -1) {\n    return false;\n  }\n  EGLint attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fd, EGL_NONE};\n  EGLDisplay egl_display = eglGetCurrentDisplay();\n  EGLSyncKHR sync = AndroidEGLFenceSync::eglCreateSyncKHR(\n      egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);\n  AndroidEGLFenceSync::eglWaitSyncKHR(egl_display, sync, 0);\n  AndroidEGLFenceSync::eglDestroySyncKHR(egl_display, sync);\n  return true;\n}\n\nint32_t VulkanFenceHelper::GetSyncFd() {\n  if (semaphore_ == VK_NULL_HANDLE) {\n    return -1;\n  }\n  VkSemaphoreGetFdInfoKHR fd_info = {};\n  fd_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;\n  fd_info.pNext = nullptr;\n  fd_info.semaphore = semaphore_;\n  fd_info.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;\n\n  int fd;\n  VkResult result =\n      VulkanHelper::GetInstance().GetSemaphoreFdKHR(device_, &fd_info, &fd);\n  // Some devices or drivers may fail to get the FD for VkSemaphore.\n  if (result != VK_SUCCESS) {\n    FML_LOG(ERROR) << \"GetSyncFd: failed to get FD for VkSemaphore\";\n    return -1;\n  }\n  return fd;\n}\n\nbool VulkanFenceHelper::InPendingQueue(\n    const std::list<VkFence>& pending_fences) {\n  std::unique_lock<std::mutex> lock(valid_mutex_);\n  if (!is_valid_) {\n    return false;\n  }\n  return std::find(pending_fences.begin(), pending_fences.end(), fence_) !=\n         pending_fences.end();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_fence_helper.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_VULKAN_VULKAN_FENCE_HELPER_H_\n#define CLAY_GFX_VULKAN_VULKAN_FENCE_HELPER_H_\n\n#include <vulkan/vulkan.h>\n\n#include <list>\n#include <mutex>\n#include <utility>\n\nnamespace clay {\n\n// Used to help check when we can safely destroy a fence or semaphore in vulkan.\nclass VulkanFenceHelper {\n public:\n  VulkanFenceHelper(VkDevice device, VkFence fence, VkSemaphore semaphore);\n  ~VulkanFenceHelper() = default;\n\n  void MarkCanDestroy();\n  bool CanDestroy();\n\n  void Destroy();\n\n  bool InPendingQueue(const std::list<VkFence> &pending_fences);\n\n  bool WaitForFence();\n  bool WaitSemaphore();\n\n private:\n  int32_t GetSyncFd();\n\n  VkDevice device_ = VK_NULL_HANDLE;\n  VkFence fence_ = VK_NULL_HANDLE;\n  VkSemaphore semaphore_ = VK_NULL_HANDLE;\n\n  std::mutex mutex_;\n  bool can_destroy_ = false;\n\n  std::mutex valid_mutex_;\n  bool is_valid_ = true;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_VULKAN_VULKAN_FENCE_HELPER_H_\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_helper.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/vulkan/vulkan_helper.h\"\n\n#include \"clay/fml/native_library.h\"\n\nnamespace clay {\n\nstd::string VulkanResultToString(VkResult result) {\n  switch (result) {\n    case VK_SUCCESS:\n      return \"VK_SUCCESS\";\n    case VK_NOT_READY:\n      return \"VK_NOT_READY\";\n    case VK_TIMEOUT:\n      return \"VK_TIMEOUT\";\n    case VK_EVENT_SET:\n      return \"VK_EVENT_SET\";\n    case VK_EVENT_RESET:\n      return \"VK_EVENT_RESET\";\n    case VK_INCOMPLETE:\n      return \"VK_INCOMPLETE\";\n    case VK_ERROR_OUT_OF_HOST_MEMORY:\n      return \"VK_ERROR_OUT_OF_HOST_MEMORY\";\n    case VK_ERROR_OUT_OF_DEVICE_MEMORY:\n      return \"VK_ERROR_OUT_OF_DEVICE_MEMORY\";\n    case VK_ERROR_INITIALIZATION_FAILED:\n      return \"VK_ERROR_INITIALIZATION_FAILED\";\n    case VK_ERROR_DEVICE_LOST:\n      return \"VK_ERROR_DEVICE_LOST\";\n    case VK_ERROR_MEMORY_MAP_FAILED:\n      return \"VK_ERROR_MEMORY_MAP_FAILED\";\n    case VK_ERROR_LAYER_NOT_PRESENT:\n      return \"VK_ERROR_LAYER_NOT_PRESENT\";\n    case VK_ERROR_EXTENSION_NOT_PRESENT:\n      return \"VK_ERROR_EXTENSION_NOT_PRESENT\";\n    case VK_ERROR_FEATURE_NOT_PRESENT:\n      return \"VK_ERROR_FEATURE_NOT_PRESENT\";\n    case VK_ERROR_INCOMPATIBLE_DRIVER:\n      return \"VK_ERROR_INCOMPATIBLE_DRIVER\";\n    case VK_ERROR_TOO_MANY_OBJECTS:\n      return \"VK_ERROR_TOO_MANY_OBJECTS\";\n    case VK_ERROR_FORMAT_NOT_SUPPORTED:\n      return \"VK_ERROR_FORMAT_NOT_SUPPORTED\";\n    case VK_ERROR_FRAGMENTED_POOL:\n      return \"VK_ERROR_FRAGMENTED_POOL\";\n    case VK_ERROR_SURFACE_LOST_KHR:\n      return \"VK_ERROR_SURFACE_LOST_KHR\";\n    case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:\n      return \"VK_ERROR_NATIVE_WINDOW_IN_USE_KHR\";\n    case VK_SUBOPTIMAL_KHR:\n      return \"VK_SUBOPTIMAL_KHR\";\n    case VK_ERROR_OUT_OF_DATE_KHR:\n      return \"VK_ERROR_OUT_OF_DATE_KHR\";\n    case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:\n      return \"VK_ERROR_INCOMPATIBLE_DISPLAY_KHR\";\n    case VK_ERROR_VALIDATION_FAILED_EXT:\n      return \"VK_ERROR_VALIDATION_FAILED_EXT\";\n    case VK_ERROR_INVALID_SHADER_NV:\n      return \"VK_ERROR_INVALID_SHADER_NV\";\n#if VK_HEADER_VERSION < 140\n    case VK_RESULT_RANGE_SIZE:\n      return \"VK_RESULT_RANGE_SIZE\";\n#endif\n    case VK_RESULT_MAX_ENUM:\n      return \"VK_RESULT_MAX_ENUM\";\n    case VK_ERROR_INVALID_EXTERNAL_HANDLE:\n      return \"VK_ERROR_INVALID_EXTERNAL_HANDLE\";\n    case VK_ERROR_OUT_OF_POOL_MEMORY:\n      return \"VK_ERROR_OUT_OF_POOL_MEMORY\";\n    default:\n      return \"Unknown Error\";\n  }\n  return \"\";\n}\n\nbool LogVkIfNotSuccess(VkResult result, const char* pName) {\n  if (result != VK_SUCCESS) {\n    FML_LOG(ERROR) << \"Vulkan error: \" << VulkanResultToString(result) << \" in \"\n                   << pName;\n    return false;\n  }\n  return true;\n}\n\nuint32_t FindMemoryType(uint32_t type_filter, VkPhysicalDevice physical_device,\n                        VkMemoryPropertyFlags properties) {\n  VkPhysicalDeviceMemoryProperties mem_properties;\n  VulkanHelper::GetInstance().GetPhysicalDeviceMemoryProperties(\n      physical_device, &mem_properties);\n\n  for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) {\n    if ((type_filter & (1 << i)) &&\n        (mem_properties.memoryTypes[i].propertyFlags & properties) ==\n            properties) {\n      return i;\n    }\n  }\n  return 0;\n}\n\nVulkanHelper::VulkanHelper() = default;\n\n// static\nVulkanHelper& VulkanHelper::GetInstance() {\n  static VulkanHelper instance;\n  return instance;\n}\n\nvoid VulkanHelper::Init(VkInstance instance, VkDevice device) {\n  if (instance_ == instance && device_ == device) {\n    return;  // already init, do nothing.\n  }\n\n  auto vulkan_library = fml::NativeLibrary::Create(\"libvulkan.so\");\n  if (!vulkan_library) {\n    FML_LOG(ERROR) << \"Failed to load libvulkan.so\";\n    return;\n  }\n  auto instance_proc_addr =\n      vulkan_library->ResolveFunction<PFN_vkGetInstanceProcAddr>(\n          \"vkGetInstanceProcAddr\");\n  if (!instance_proc_addr.has_value()) {\n    FML_LOG(ERROR) << \"Failed to load vkGetInstanceProcAddr\";\n    return;\n  }\n  vkGetInstanceProcAddr_ = instance_proc_addr.value();\n\n  instance_ = instance;\n  device_ = device;\n\n  vkGetDeviceProcAddr_ = reinterpret_cast<PFN_vkGetDeviceProcAddr>(\n      vkGetInstanceProcAddr_(instance_, \"vkGetDeviceProcAddr\"));\n  if (!vkGetDeviceProcAddr_) {\n    FML_LOG(ERROR) << \"Failed to load vkGetDeviceProcAddr\";\n  }\n\n  InitializeVulkanFunctions();\n}\n\nPFN_vkVoidFunction VulkanHelper::GetInstanceProcAddr(const char* pName) {\n  if (vkGetInstanceProcAddr_) {\n    return vkGetInstanceProcAddr_(instance_, pName);\n  }\n  FML_LOG(ERROR) << \"vkGetInstanceProcAddr is null\";\n  return nullptr;\n}\n\nPFN_vkVoidFunction VulkanHelper::GetDeviceProcAddr(const char* pName) {\n  if (vkGetDeviceProcAddr_) {\n    return vkGetDeviceProcAddr_(device_, pName);\n  }\n  FML_LOG(ERROR) << \"vkGetDeviceProcAddr is null\";\n  return nullptr;\n}\n\nvoid VulkanHelper::InitializeVulkanFunctions() {\n  FML_DCHECK(vkGetInstanceProcAddr_);\n  FML_DCHECK(vkGetDeviceProcAddr_);\n  FML_DCHECK(instance_ != VK_NULL_HANDLE);\n  FML_DCHECK(device_ != VK_NULL_HANDLE);\n  vkGetAndroidHardwareBufferPropertiesANDROID_ =\n      reinterpret_cast<PFN_vkGetAndroidHardwareBufferPropertiesANDROID>(\n          vkGetDeviceProcAddr_(device_,\n                               \"vkGetAndroidHardwareBufferPropertiesANDROID\"));\n  vkCreateImage_ = reinterpret_cast<PFN_vkCreateImage>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateImage\"));\n  vkDestroyImage_ = reinterpret_cast<PFN_vkDestroyImage>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyImage\"));\n  vkAllocateMemory_ = reinterpret_cast<PFN_vkAllocateMemory>(\n      vkGetDeviceProcAddr_(device_, \"vkAllocateMemory\"));\n  vkBindImageMemory_ = reinterpret_cast<PFN_vkBindImageMemory>(\n      vkGetDeviceProcAddr_(device_, \"vkBindImageMemory\"));\n  vkCreateImageView_ = reinterpret_cast<PFN_vkCreateImageView>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateImageView\"));\n  vkDestroyImageView_ = reinterpret_cast<PFN_vkDestroyImageView>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyImageView\"));\n  vkCreateFramebuffer_ = reinterpret_cast<PFN_vkCreateFramebuffer>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateFramebuffer\"));\n  vkCreateDescriptorPool_ = reinterpret_cast<PFN_vkCreateDescriptorPool>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateDescriptorPool\"));\n  vkCreateBuffer_ = reinterpret_cast<PFN_vkCreateBuffer>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateBuffer\"));\n  vkGetBufferMemoryRequirements_ =\n      reinterpret_cast<PFN_vkGetBufferMemoryRequirements>(\n          vkGetDeviceProcAddr_(device_, \"vkGetBufferMemoryRequirements\"));\n  vkBindBufferMemory_ = reinterpret_cast<PFN_vkBindBufferMemory>(\n      vkGetDeviceProcAddr_(device_, \"vkBindBufferMemory\"));\n  vkMapMemory_ = reinterpret_cast<PFN_vkMapMemory>(\n      vkGetDeviceProcAddr_(device_, \"vkMapMemory\"));\n  vkUnmapMemory_ = reinterpret_cast<PFN_vkUnmapMemory>(\n      vkGetDeviceProcAddr_(device_, \"vkUnmapMemory\"));\n  vkCreatePipelineLayout_ = reinterpret_cast<PFN_vkCreatePipelineLayout>(\n      vkGetDeviceProcAddr_(device_, \"vkCreatePipelineLayout\"));\n  vkCreateGraphicsPipelines_ = reinterpret_cast<PFN_vkCreateGraphicsPipelines>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateGraphicsPipelines\"));\n  vkCreateSampler_ = reinterpret_cast<PFN_vkCreateSampler>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateSampler\"));\n  vkCreateDescriptorSetLayout_ =\n      reinterpret_cast<PFN_vkCreateDescriptorSetLayout>(\n          vkGetDeviceProcAddr_(device_, \"vkCreateDescriptorSetLayout\"));\n  vkAllocateDescriptorSets_ = reinterpret_cast<PFN_vkAllocateDescriptorSets>(\n      vkGetDeviceProcAddr_(device_, \"vkAllocateDescriptorSets\"));\n  vkUpdateDescriptorSets_ = reinterpret_cast<PFN_vkUpdateDescriptorSets>(\n      vkGetDeviceProcAddr_(device_, \"vkUpdateDescriptorSets\"));\n  vkCmdBindDescriptorSets_ = reinterpret_cast<PFN_vkCmdBindDescriptorSets>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdBindDescriptorSets\"));\n  vkCmdBindPipeline_ = reinterpret_cast<PFN_vkCmdBindPipeline>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdBindPipeline\"));\n  vkBeginCommandBuffer_ = reinterpret_cast<PFN_vkBeginCommandBuffer>(\n      vkGetDeviceProcAddr_(device_, \"vkBeginCommandBuffer\"));\n  vkEndCommandBuffer_ = reinterpret_cast<PFN_vkEndCommandBuffer>(\n      vkGetDeviceProcAddr_(device_, \"vkEndCommandBuffer\"));\n  vkCmdBeginRenderPass_ = reinterpret_cast<PFN_vkCmdBeginRenderPass>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdBeginRenderPass\"));\n  vkCmdEndRenderPass_ = reinterpret_cast<PFN_vkCmdEndRenderPass>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdEndRenderPass\"));\n  vkCmdBindVertexBuffers_ = reinterpret_cast<PFN_vkCmdBindVertexBuffers>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdBindVertexBuffers\"));\n  vkCmdBindIndexBuffer_ = reinterpret_cast<PFN_vkCmdBindIndexBuffer>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdBindIndexBuffer\"));\n  vkCmdDrawIndexed_ = reinterpret_cast<PFN_vkCmdDrawIndexed>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdDrawIndexed\"));\n  vkCmdDraw_ = reinterpret_cast<PFN_vkCmdDraw>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdDraw\"));\n  vkQueueSubmit_ = reinterpret_cast<PFN_vkQueueSubmit>(\n      vkGetDeviceProcAddr_(device_, \"vkQueueSubmit\"));\n  vkQueueWaitIdle_ = reinterpret_cast<PFN_vkQueueWaitIdle>(\n      vkGetDeviceProcAddr_(device_, \"vkQueueWaitIdle\"));\n  vkGetPhysicalDeviceMemoryProperties_ =\n      reinterpret_cast<PFN_vkGetPhysicalDeviceMemoryProperties>(\n          vkGetInstanceProcAddr_(instance_,\n                                 \"vkGetPhysicalDeviceMemoryProperties\"));\n  vkCreateShaderModule_ = reinterpret_cast<PFN_vkCreateShaderModule>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateShaderModule\"));\n  vkDestroyShaderModule_ = reinterpret_cast<PFN_vkDestroyShaderModule>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyShaderModule\"));\n  vkDeviceWaitIdle_ = reinterpret_cast<PFN_vkDeviceWaitIdle>(\n      vkGetDeviceProcAddr_(device_, \"vkDeviceWaitIdle\"));\n  vkDestroyBuffer_ = reinterpret_cast<PFN_vkDestroyBuffer>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyBuffer\"));\n  vkFreeMemory_ = reinterpret_cast<PFN_vkFreeMemory>(\n      vkGetDeviceProcAddr_(device_, \"vkFreeMemory\"));\n  vkDestroySampler_ = reinterpret_cast<PFN_vkDestroySampler>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroySampler\"));\n  vkDestroyDescriptorPool_ = reinterpret_cast<PFN_vkDestroyDescriptorPool>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyDescriptorPool\"));\n  vkDestroyDescriptorSetLayout_ =\n      reinterpret_cast<PFN_vkDestroyDescriptorSetLayout>(\n          vkGetDeviceProcAddr_(device_, \"vkDestroyDescriptorSetLayout\"));\n  vkDestroyPipelineLayout_ = reinterpret_cast<PFN_vkDestroyPipelineLayout>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyPipelineLayout\"));\n  vkDestroyPipeline_ = reinterpret_cast<PFN_vkDestroyPipeline>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyPipeline\"));\n  vkDestroyFramebuffer_ = reinterpret_cast<PFN_vkDestroyFramebuffer>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyFramebuffer\"));\n  vkCmdSetViewport_ = reinterpret_cast<PFN_vkCmdSetViewport>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdSetViewport\"));\n  vkCmdSetScissor_ = reinterpret_cast<PFN_vkCmdSetScissor>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdSetScissor\"));\n  vkCmdCopyBuffer_ = reinterpret_cast<PFN_vkCmdCopyBuffer>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdCopyBuffer\"));\n  vkResetDescriptorPool_ = reinterpret_cast<PFN_vkResetDescriptorPool>(\n      vkGetDeviceProcAddr_(device_, \"vkResetDescriptorPool\"));\n  vkCmdPipelineBarrier_ = reinterpret_cast<PFN_vkCmdPipelineBarrier>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdPipelineBarrier\"));\n  vkCreateSemaphore_ = reinterpret_cast<PFN_vkCreateSemaphore>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateSemaphore\"));\n  vkDestroySemaphore_ = reinterpret_cast<PFN_vkDestroySemaphore>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroySemaphore\"));\n  vkImportSemaphoreFdKHR_ = reinterpret_cast<PFN_vkImportSemaphoreFdKHR>(\n      vkGetDeviceProcAddr_(device_, \"vkImportSemaphoreFdKHR\"));\n  vkGetSemaphoreFdKHR_ = reinterpret_cast<PFN_vkGetSemaphoreFdKHR>(\n      vkGetDeviceProcAddr_(device_, \"vkGetSemaphoreFdKHR\"));\n  vkCreateFence_ = reinterpret_cast<PFN_vkCreateFence>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateFence\"));\n  vkDestroyFence_ = reinterpret_cast<PFN_vkDestroyFence>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyFence\"));\n  vkWaitForFences_ = reinterpret_cast<PFN_vkWaitForFences>(\n      vkGetDeviceProcAddr_(device_, \"vkWaitForFences\"));\n  vkGetFenceStatus_ = reinterpret_cast<PFN_vkGetFenceStatus>(\n      vkGetDeviceProcAddr_(device_, \"vkGetFenceStatus\"));\n  vkCmdPushConstants_ = reinterpret_cast<PFN_vkCmdPushConstants>(\n      vkGetDeviceProcAddr_(device_, \"vkCmdPushConstants\"));\n  vkCreateCommandPool_ = reinterpret_cast<PFN_vkCreateCommandPool>(\n      vkGetDeviceProcAddr_(device_, \"vkCreateCommandPool\"));\n  vkDestroyCommandPool_ = reinterpret_cast<PFN_vkDestroyCommandPool>(\n      vkGetDeviceProcAddr_(device_, \"vkDestroyCommandPool\"));\n  vkAllocateCommandBuffers_ = reinterpret_cast<PFN_vkAllocateCommandBuffers>(\n      vkGetDeviceProcAddr_(device_, \"vkAllocateCommandBuffers\"));\n  vkFreeCommandBuffers_ = reinterpret_cast<PFN_vkFreeCommandBuffers>(\n      vkGetDeviceProcAddr_(device_, \"vkFreeCommandBuffers\"));\n  vkResetCommandBuffer_ = reinterpret_cast<PFN_vkResetCommandBuffer>(\n      vkGetDeviceProcAddr_(device_, \"vkResetCommandBuffer\"));\n  vkGetPhysicalDeviceQueueFamilyProperties_ =\n      reinterpret_cast<PFN_vkGetPhysicalDeviceQueueFamilyProperties>(\n          vkGetInstanceProcAddr_(instance_,\n                                 \"vkGetPhysicalDeviceQueueFamilyProperties\"));\n}\n\nvoid VulkanHelper::DestroyBuffer(VkDevice device, VkBuffer buffer,\n                                 const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyBuffer_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyBuffer\";\n    return;\n  }\n  vkDestroyBuffer_(device, buffer, pAllocator);\n}\n\nvoid VulkanHelper::DestroyFramebuffer(VkDevice device,\n                                      VkFramebuffer framebuffer,\n                                      const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyFramebuffer_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyFramebuffer\";\n    return;\n  }\n  vkDestroyFramebuffer_(device, framebuffer, pAllocator);\n}\n\nvoid VulkanHelper::FreeMemory(VkDevice device, VkDeviceMemory memory,\n                              const VkAllocationCallbacks* pAllocator) {\n  if (!vkFreeMemory_) {\n    FML_LOG(ERROR) << \"Failed to load vkFreeMemory\";\n    return;\n  }\n  vkFreeMemory_(device, memory, pAllocator);\n}\n\nvoid VulkanHelper::DestroySampler(VkDevice device, VkSampler sampler,\n                                  const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroySampler_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroySampler\";\n    return;\n  }\n  vkDestroySampler_(device, sampler, pAllocator);\n}\n\nvoid VulkanHelper::DestroyDescriptorPool(\n    VkDevice device, VkDescriptorPool descriptorPool,\n    const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyDescriptorPool_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyDescriptorPool\";\n    return;\n  }\n  vkDestroyDescriptorPool_(device, descriptorPool, pAllocator);\n}\n\nvoid VulkanHelper::DestroyDescriptorSetLayout(\n    VkDevice device, VkDescriptorSetLayout descriptorSetLayout,\n    const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyDescriptorSetLayout_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyDescriptorSetLayout\";\n    return;\n  }\n  vkDestroyDescriptorSetLayout_(device, descriptorSetLayout, pAllocator);\n}\n\nvoid VulkanHelper::DestroyPipelineLayout(\n    VkDevice device, VkPipelineLayout pipelineLayout,\n    const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyPipelineLayout_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyPipelineLayout\";\n    return;\n  }\n  vkDestroyPipelineLayout_(device, pipelineLayout, pAllocator);\n}\n\nvoid VulkanHelper::DestroyPipeline(VkDevice device, VkPipeline pipeline,\n                                   const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyPipeline_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyPipeline\";\n    return;\n  }\n  vkDestroyPipeline_(device, pipeline, pAllocator);\n}\n\nVkResult VulkanHelper::GetAndroidHardwareBufferPropertiesANDROID(\n    VkDevice device, const struct AHardwareBuffer* buffer,\n    VkAndroidHardwareBufferPropertiesANDROID* pProperties) {\n  if (!vkGetAndroidHardwareBufferPropertiesANDROID_) {\n    FML_LOG(ERROR)\n        << \"Failed to load vkGetAndroidHardwareBufferPropertiesANDROID\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkGetAndroidHardwareBufferPropertiesANDROID_(device, buffer,\n                                                      pProperties);\n}\n\nVkResult VulkanHelper::CreateImage(VkDevice device,\n                                   const VkImageCreateInfo* pCreateInfo,\n                                   const VkAllocationCallbacks* pAllocator,\n                                   VkImage* pImage) {\n  if (!vkCreateImage_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateImage\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateImage_(device, pCreateInfo, pAllocator, pImage);\n}\n\nvoid VulkanHelper::DestroyImage(VkDevice device, VkImage image,\n                                const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyImage_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyImage\";\n    return;\n  }\n  vkDestroyImage_(device, image, pAllocator);\n}\n\nVkResult VulkanHelper::AllocateMemory(VkDevice device,\n                                      const VkMemoryAllocateInfo* pAllocateInfo,\n                                      const VkAllocationCallbacks* pAllocator,\n                                      VkDeviceMemory* pMemory) {\n  if (!vkAllocateMemory_) {\n    FML_LOG(ERROR) << \"Failed to load vkAllocateMemory\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkAllocateMemory_(device, pAllocateInfo, pAllocator, pMemory);\n}\n\nVkResult VulkanHelper::BindImageMemory(VkDevice device, VkImage image,\n                                       VkDeviceMemory memory,\n                                       VkDeviceSize memoryOffset) {\n  if (!vkBindImageMemory_) {\n    FML_LOG(ERROR) << \"Failed to load vkBindImageMemory\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkBindImageMemory_(device, image, memory, memoryOffset);\n}\n\nVkResult VulkanHelper::CreateImageView(VkDevice device,\n                                       const VkImageViewCreateInfo* pCreateInfo,\n                                       const VkAllocationCallbacks* pAllocator,\n                                       VkImageView* pView) {\n  if (!vkCreateImageView_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateImageView\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateImageView_(device, pCreateInfo, pAllocator, pView);\n}\n\nvoid VulkanHelper::DestroyImageView(VkDevice device, VkImageView imageView,\n                                    const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyImageView_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyImageView\";\n    return;\n  }\n  vkDestroyImageView_(device, imageView, pAllocator);\n}\n\nVkResult VulkanHelper::CreateFramebuffer(\n    VkDevice device, const VkFramebufferCreateInfo* pCreateInfo,\n    const VkAllocationCallbacks* pAllocator, VkFramebuffer* pFramebuffer) {\n  if (!vkCreateFramebuffer_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateFramebuffer\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateFramebuffer_(device, pCreateInfo, pAllocator, pFramebuffer);\n}\n\nVkResult VulkanHelper::CreateDescriptorPool(\n    VkDevice device, const VkDescriptorPoolCreateInfo* pCreateInfo,\n    const VkAllocationCallbacks* pAllocator,\n    VkDescriptorPool* pDescriptorPool) {\n  if (!vkCreateDescriptorPool_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateDescriptorPool\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateDescriptorPool_(device, pCreateInfo, pAllocator,\n                                 pDescriptorPool);\n}\n\nVkResult VulkanHelper::CreateBuffer(VkDevice device,\n                                    const VkBufferCreateInfo* pCreateInfo,\n                                    const VkAllocationCallbacks* pAllocator,\n                                    VkBuffer* pBuffer) {\n  if (!vkCreateBuffer_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateBuffer\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateBuffer_(device, pCreateInfo, pAllocator, pBuffer);\n}\n\nvoid VulkanHelper::GetBufferMemoryRequirements(\n    VkDevice device, VkBuffer buffer,\n    VkMemoryRequirements* pMemoryRequirements) {\n  if (!vkGetBufferMemoryRequirements_) {\n    FML_LOG(ERROR) << \"Failed to load vkGetBufferMemoryRequirements\";\n    return;\n  }\n  vkGetBufferMemoryRequirements_(device, buffer, pMemoryRequirements);\n}\n\nVkResult VulkanHelper::BindBufferMemory(VkDevice device, VkBuffer buffer,\n                                        VkDeviceMemory memory,\n                                        VkDeviceSize memoryOffset) {\n  if (!vkBindBufferMemory_) {\n    FML_LOG(ERROR) << \"Failed to load vkBindBufferMemory\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkBindBufferMemory_(device, buffer, memory, memoryOffset);\n}\n\nVkResult VulkanHelper::MapMemory(VkDevice device, VkDeviceMemory memory,\n                                 VkDeviceSize offset, VkDeviceSize size,\n                                 VkMemoryMapFlags flags, void** ppData) {\n  if (!vkMapMemory_) {\n    FML_LOG(ERROR) << \"Failed to load vkMapMemory\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkMapMemory_(device, memory, offset, size, flags, ppData);\n}\n\nvoid VulkanHelper::UnmapMemory(VkDevice device, VkDeviceMemory memory) {\n  if (!vkUnmapMemory_) {\n    FML_LOG(ERROR) << \"Failed to load vkUnmapMemory\";\n    return;\n  }\n  vkUnmapMemory_(device, memory);\n}\n\nVkResult VulkanHelper::CreatePipelineLayout(\n    VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo,\n    const VkAllocationCallbacks* pAllocator,\n    VkPipelineLayout* pPipelineLayout) {\n  if (!vkCreatePipelineLayout_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreatePipelineLayout\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreatePipelineLayout_(device, pCreateInfo, pAllocator,\n                                 pPipelineLayout);\n}\n\nVkResult VulkanHelper::CreateGraphicsPipelines(\n    VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount,\n    const VkGraphicsPipelineCreateInfo* pCreateInfos,\n    const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) {\n  if (!vkCreateGraphicsPipelines_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateGraphicsPipelines\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateGraphicsPipelines_(device, pipelineCache, createInfoCount,\n                                    pCreateInfos, pAllocator, pPipelines);\n}\n\nVkResult VulkanHelper::CreateSampler(VkDevice device,\n                                     const VkSamplerCreateInfo* pCreateInfo,\n                                     const VkAllocationCallbacks* pAllocator,\n                                     VkSampler* pSampler) {\n  if (!vkCreateSampler_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateSampler\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateSampler_(device, pCreateInfo, pAllocator, pSampler);\n}\n\nVkResult VulkanHelper::CreateDescriptorSetLayout(\n    VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo,\n    const VkAllocationCallbacks* pAllocator,\n    VkDescriptorSetLayout* pSetLayout) {\n  if (!vkCreateDescriptorSetLayout_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateDescriptorSetLayout\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateDescriptorSetLayout_(device, pCreateInfo, pAllocator,\n                                      pSetLayout);\n}\n\nVkResult VulkanHelper::AllocateDescriptorSets(\n    VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo,\n    VkDescriptorSet* pDescriptorSets) {\n  if (!vkAllocateDescriptorSets_) {\n    FML_LOG(ERROR) << \"Failed to load vkAllocateDescriptorSets\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkAllocateDescriptorSets_(device, pAllocateInfo, pDescriptorSets);\n}\n\nvoid VulkanHelper::UpdateDescriptorSets(\n    VkDevice device, uint32_t descriptorWriteCount,\n    const VkWriteDescriptorSet* pDescriptorWrites, uint32_t descriptorCopyCount,\n    const VkCopyDescriptorSet* pDescriptorCopies) {\n  if (!vkUpdateDescriptorSets_) {\n    FML_LOG(ERROR) << \"Failed to load vkUpdateDescriptorSets\";\n    return;\n  }\n  vkUpdateDescriptorSets_(device, descriptorWriteCount, pDescriptorWrites,\n                          descriptorCopyCount, pDescriptorCopies);\n}\n\nvoid VulkanHelper::CmdBindDescriptorSets(\n    VkCommandBuffer commandBuffer, VkPipelineBindPoint pipelineBindPoint,\n    VkPipelineLayout layout, uint32_t firstSet, uint32_t descriptorSetCount,\n    const VkDescriptorSet* pDescriptorSets, uint32_t dynamicOffsetCount,\n    const uint32_t* pDynamicOffsets) {\n  if (!vkCmdBindDescriptorSets_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdBindDescriptorSets\";\n    return;\n  }\n  vkCmdBindDescriptorSets_(commandBuffer, pipelineBindPoint, layout, firstSet,\n                           descriptorSetCount, pDescriptorSets,\n                           dynamicOffsetCount, pDynamicOffsets);\n}\n\nvoid VulkanHelper::CmdBindPipeline(VkCommandBuffer commandBuffer,\n                                   VkPipelineBindPoint pipelineBindPoint,\n                                   VkPipeline pipeline) {\n  if (!vkCmdBindPipeline_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdBindPipeline\";\n    return;\n  }\n  vkCmdBindPipeline_(commandBuffer, pipelineBindPoint, pipeline);\n}\n\nVkResult VulkanHelper::BeginCommandBuffer(\n    VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo) {\n  if (!vkBeginCommandBuffer_) {\n    FML_LOG(ERROR) << \"Failed to load vkBeginCommandBuffer\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkBeginCommandBuffer_(commandBuffer, pBeginInfo);\n}\n\nVkResult VulkanHelper::EndCommandBuffer(VkCommandBuffer commandBuffer) {\n  if (!vkEndCommandBuffer_) {\n    FML_LOG(ERROR) << \"Failed to load vkEndCommandBuffer\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkEndCommandBuffer_(commandBuffer);\n}\n\nvoid VulkanHelper::CmdBeginRenderPass(\n    VkCommandBuffer commandBuffer,\n    const VkRenderPassBeginInfo* pRenderPassBegin, VkSubpassContents contents) {\n  if (!vkCmdBeginRenderPass_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdBeginRenderPass\";\n    return;\n  }\n  vkCmdBeginRenderPass_(commandBuffer, pRenderPassBegin, contents);\n}\n\nvoid VulkanHelper::CmdEndRenderPass(VkCommandBuffer commandBuffer) {\n  if (!vkCmdEndRenderPass_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdEndRenderPass\";\n    return;\n  }\n  vkCmdEndRenderPass_(commandBuffer);\n}\n\nvoid VulkanHelper::CmdBindVertexBuffers(VkCommandBuffer commandBuffer,\n                                        uint32_t firstBinding,\n                                        uint32_t bindingCount,\n                                        const VkBuffer* pBuffers,\n                                        const VkDeviceSize* pOffsets) {\n  if (!vkCmdBindVertexBuffers_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdBindVertexBuffers\";\n    return;\n  }\n  vkCmdBindVertexBuffers_(commandBuffer, firstBinding, bindingCount, pBuffers,\n                          pOffsets);\n}\n\nvoid VulkanHelper::CmdBindIndexBuffer(VkCommandBuffer commandBuffer,\n                                      VkBuffer buffer, VkDeviceSize offset,\n                                      VkIndexType indexType) {\n  if (!vkCmdBindIndexBuffer_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdBindIndexBuffer\";\n    return;\n  }\n  vkCmdBindIndexBuffer_(commandBuffer, buffer, offset, indexType);\n}\n\nvoid VulkanHelper::CmdDrawIndexed(VkCommandBuffer commandBuffer,\n                                  uint32_t indexCount, uint32_t instanceCount,\n                                  uint32_t firstIndex, int32_t vertexOffset,\n                                  uint32_t firstInstance) {\n  if (!vkCmdDrawIndexed_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdDrawIndexed\";\n    return;\n  }\n  vkCmdDrawIndexed_(commandBuffer, indexCount, instanceCount, firstIndex,\n                    vertexOffset, firstInstance);\n}\n\nvoid VulkanHelper::CmdDraw(VkCommandBuffer commandBuffer, uint32_t vertexCount,\n                           uint32_t instanceCount, uint32_t firstVertex,\n                           uint32_t firstInstance) {\n  if (!vkCmdDraw_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdDraw\";\n    return;\n  }\n  vkCmdDraw_(commandBuffer, vertexCount, instanceCount, firstVertex,\n             firstInstance);\n}\n\nVkResult VulkanHelper::QueueSubmit(VkQueue queue, uint32_t submitCount,\n                                   const VkSubmitInfo* pSubmits,\n                                   VkFence fence) {\n  if (!vkQueueSubmit_) {\n    FML_LOG(ERROR) << \"Failed to load vkQueueSubmit\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkQueueSubmit_(queue, submitCount, pSubmits, fence);\n}\n\nVkResult VulkanHelper::QueueWaitIdle(VkQueue queue) {\n  if (!vkQueueWaitIdle_) {\n    FML_LOG(ERROR) << \"Failed to load vkQueueWaitIdle\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkQueueWaitIdle_(queue);\n}\n\nvoid VulkanHelper::GetPhysicalDeviceMemoryProperties(\n    VkPhysicalDevice physicalDevice,\n    VkPhysicalDeviceMemoryProperties* pProperties) {\n  if (!vkGetPhysicalDeviceMemoryProperties_) {\n    FML_LOG(ERROR) << \"Failed to load vkGetPhysicalDeviceMemoryProperties\";\n    return;\n  }\n  vkGetPhysicalDeviceMemoryProperties_(physicalDevice, pProperties);\n}\n\nVkResult VulkanHelper::CreateShaderModule(\n    VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo,\n    const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule) {\n  if (!vkCreateShaderModule_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateShaderModule\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateShaderModule_(device, pCreateInfo, pAllocator, pShaderModule);\n}\n\nvoid VulkanHelper::DestroyShaderModule(\n    VkDevice device, VkShaderModule shaderModule,\n    const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyShaderModule_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyShaderModule\";\n    return;\n  }\n  vkDestroyShaderModule_(device, shaderModule, pAllocator);\n}\n\nVkResult VulkanHelper::DeviceWaitIdle(VkDevice device) {\n  if (!vkDeviceWaitIdle_) {\n    FML_LOG(ERROR) << \"Failed to load vkDeviceWaitIdle\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkDeviceWaitIdle_(device);\n}\n\nvoid VulkanHelper::CmdSetViewport(VkCommandBuffer commandBuffer,\n                                  uint32_t firstViewport,\n                                  uint32_t viewportCount,\n                                  const VkViewport* pViewports) {\n  if (!vkCmdSetViewport_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdSetViewport\";\n    return;\n  }\n  vkCmdSetViewport_(commandBuffer, firstViewport, viewportCount, pViewports);\n}\n\nvoid VulkanHelper::CmdSetScissor(VkCommandBuffer commandBuffer,\n                                 uint32_t firstScissor, uint32_t scissorCount,\n                                 const VkRect2D* pScissors) {\n  if (!vkCmdSetScissor_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdSetScissor\";\n    return;\n  }\n  vkCmdSetScissor_(commandBuffer, firstScissor, scissorCount, pScissors);\n}\n\nvoid VulkanHelper::CmdCopyBuffer(VkCommandBuffer commandBuffer,\n                                 VkBuffer srcBuffer, VkBuffer dstBuffer,\n                                 uint32_t regionCount,\n                                 const VkBufferCopy* pRegions) {\n  if (!vkCmdCopyBuffer_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdCopyBuffer\";\n    return;\n  }\n  vkCmdCopyBuffer_(commandBuffer, srcBuffer, dstBuffer, regionCount, pRegions);\n}\n\nVkResult VulkanHelper::ResetDescriptorPool(VkDevice device,\n                                           VkDescriptorPool descriptorPool,\n                                           uint32_t flags) {\n  if (!vkResetDescriptorPool_) {\n    FML_LOG(ERROR) << \"Failed to load vkResetDescriptorPool\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkResetDescriptorPool_(device, descriptorPool, flags);\n}\n\nvoid VulkanHelper::CmdPipelineBarrier(\n    VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask,\n    VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags,\n    uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers,\n    uint32_t bufferMemoryBarrierCount,\n    const VkBufferMemoryBarrier* pBufferMemoryBarriers,\n    uint32_t imageMemoryBarrierCount,\n    const VkImageMemoryBarrier* pImageMemoryBarriers) {\n  if (!vkCmdPipelineBarrier_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdPipelineBarrier\";\n    return;\n  }\n  vkCmdPipelineBarrier_(commandBuffer, srcStageMask, dstStageMask,\n                        dependencyFlags, memoryBarrierCount, pMemoryBarriers,\n                        bufferMemoryBarrierCount, pBufferMemoryBarriers,\n                        imageMemoryBarrierCount, pImageMemoryBarriers);\n}\n\nVkResult VulkanHelper::CreateSemaphore(VkDevice device,\n                                       const VkSemaphoreCreateInfo* pCreateInfo,\n                                       const VkAllocationCallbacks* pAllocator,\n                                       VkSemaphore* pSemaphore) {\n  if (!vkCreateSemaphore_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateSemaphore\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateSemaphore_(device, pCreateInfo, pAllocator, pSemaphore);\n}\n\nvoid VulkanHelper::DestroySemaphore(VkDevice device, VkSemaphore semaphore,\n                                    const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroySemaphore_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroySemaphore\";\n    return;\n  }\n  vkDestroySemaphore_(device, semaphore, pAllocator);\n}\n\nVkResult VulkanHelper::ImportSemaphoreFdKHR(\n    VkDevice device, const VkImportSemaphoreFdInfoKHR* pImportSemaphoreFdInfo) {\n  if (!vkImportSemaphoreFdKHR_) {\n    FML_LOG(ERROR) << \"Failed to load vkImportSemaphoreFdKHR\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkImportSemaphoreFdKHR_(device, pImportSemaphoreFdInfo);\n}\n\nVkResult VulkanHelper::GetSemaphoreFdKHR(\n    VkDevice device, const VkSemaphoreGetFdInfoKHR* pGetFdInfo, int* pFd) {\n  if (!vkGetSemaphoreFdKHR_) {\n    FML_LOG(ERROR) << \"Failed to load vkGetSemaphoreFdKHR\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkGetSemaphoreFdKHR_(device, pGetFdInfo, pFd);\n}\n\nVkResult VulkanHelper::CreateFence(VkDevice device,\n                                   const VkFenceCreateInfo* pCreateInfo,\n                                   const VkAllocationCallbacks* pAllocator,\n                                   VkFence* pFence) {\n  if (!vkCreateFence_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateFence\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateFence_(device, pCreateInfo, pAllocator, pFence);\n}\n\nvoid VulkanHelper::DestroyFence(VkDevice device, VkFence fence,\n                                const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyFence_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyFence\";\n    return;\n  }\n  vkDestroyFence_(device, fence, pAllocator);\n}\n\nVkResult VulkanHelper::WaitForFences(VkDevice device, uint32_t fenceCount,\n                                     const VkFence* pFences, VkBool32 waitAll,\n                                     uint64_t timeout) {\n  if (!vkWaitForFences_) {\n    FML_LOG(ERROR) << \"Failed to load vkWaitForFences\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkWaitForFences_(device, fenceCount, pFences, waitAll, timeout);\n}\n\nVkResult VulkanHelper::GetFenceStatus(VkDevice device, VkFence fence) {\n  if (!vkGetFenceStatus_) {\n    FML_LOG(ERROR) << \"Failed to load vkGetFenceStatus\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkGetFenceStatus_(device, fence);\n}\n\nvoid VulkanHelper::CmdPushConstants(VkCommandBuffer commandBuffer,\n                                    VkPipelineLayout layout,\n                                    VkShaderStageFlags stageFlags,\n                                    uint32_t offset, uint32_t size,\n                                    const void* pValues) {\n  if (!vkCmdPushConstants_) {\n    FML_LOG(ERROR) << \"Failed to load vkCmdPushConstants\";\n    return;\n  }\n  vkCmdPushConstants_(commandBuffer, layout, stageFlags, offset, size, pValues);\n}\n\nVkResult VulkanHelper::CreateCommandPool(\n    VkDevice device, const VkCommandPoolCreateInfo* pCreateInfo,\n    const VkAllocationCallbacks* pAllocator, VkCommandPool* pCommandPool) {\n  if (!vkCreateCommandPool_) {\n    FML_LOG(ERROR) << \"Failed to load vkCreateCommandPool\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkCreateCommandPool_(device, pCreateInfo, pAllocator, pCommandPool);\n}\n\nvoid VulkanHelper::DestroyCommandPool(VkDevice device,\n                                      VkCommandPool commandPool,\n                                      const VkAllocationCallbacks* pAllocator) {\n  if (!vkDestroyCommandPool_) {\n    FML_LOG(ERROR) << \"Failed to load vkDestroyCommandPool\";\n    return;\n  }\n  vkDestroyCommandPool_(device, commandPool, pAllocator);\n}\n\nVkResult VulkanHelper::AllocateCommandBuffers(\n    VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo,\n    VkCommandBuffer* pCommandBuffers) {\n  if (!vkAllocateCommandBuffers_) {\n    FML_LOG(ERROR) << \"Failed to load vkAllocateCommandBuffers\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkAllocateCommandBuffers_(device, pAllocateInfo, pCommandBuffers);\n}\n\nvoid VulkanHelper::FreeCommandBuffers(VkDevice device,\n                                      VkCommandPool commandPool,\n                                      uint32_t bufferCount,\n                                      const VkCommandBuffer* pBuffers) {\n  if (!vkFreeCommandBuffers_) {\n    FML_LOG(ERROR) << \"Failed to load vkFreeCommandBuffers\";\n    return;\n  }\n  vkFreeCommandBuffers_(device, commandPool, bufferCount, pBuffers);\n}\n\nVkResult VulkanHelper::ResetCommandBuffer(VkCommandBuffer commandBuffer,\n                                          VkCommandBufferResetFlags flags) {\n  if (!vkResetCommandBuffer_) {\n    FML_LOG(ERROR) << \"Failed to load vkResetCommandBuffer\";\n    return VK_ERROR_INITIALIZATION_FAILED;\n  }\n  return vkResetCommandBuffer_(commandBuffer, flags);\n}\n\nvoid VulkanHelper::GetPhysicalDeviceQueueFamilyProperties(\n    VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount,\n    VkQueueFamilyProperties* pQueueFamilies) {\n  if (!vkGetPhysicalDeviceQueueFamilyProperties_) {\n    FML_LOG(ERROR) << \"Failed to load vkGetPhysicalDeviceQueueFamilyProperties\";\n    return;\n  }\n  vkGetPhysicalDeviceQueueFamilyProperties_(\n      physicalDevice, pQueueFamilyPropertyCount, pQueueFamilies);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_helper.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_VULKAN_VULKAN_HELPER_H_\n#define CLAY_GFX_VULKAN_VULKAN_HELPER_H_\n\n#include <vulkan/vulkan.h>\n#include <vulkan/vulkan_android.h>\n\n#include <string>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nstd::string VulkanResultToString(VkResult result);\nbool LogVkIfNotSuccess(VkResult result, const char* pName);\nuint32_t FindMemoryType(uint32_t type_filter, VkPhysicalDevice physical_device,\n                        VkMemoryPropertyFlags properties);\n\nstruct InitVkParams {\n  int version;\n  VkInstance instance;\n  VkPhysicalDevice physical_device;\n  VkDevice device;\n  VkQueue queue;\n  uint32_t graphics_queue_index;\n  uint32_t api_version;\n  const char* const* enabled_instance_extension_names;\n  uint32_t enabled_instance_extension_names_length;\n  const char* const* enabled_device_extension_names;\n  uint32_t enabled_device_extension_names_length;\n\n  // Only one of device_features and device_features_2 should be non-null.\n  // If both are null then no features are enabled.\n  VkPhysicalDeviceFeatures* device_features;\n  VkPhysicalDeviceFeatures2* device_features_2;\n};\nstruct DrawVkParams {\n  // Input: current width/height of destination surface.\n  int width;\n  int height;\n\n  // Input: current transform matrix\n  float transform[16];\n\n  // Input WebView should do its main compositing draws into this. It cannot\n  // do anything that would require stopping the render pass.\n  VkCommandBuffer secondary_command_buffer;\n\n  // Input: A render pass which will be compatible to the one which the\n  // secondary_command_buffer will be submitted into.\n  VkRenderPass compatible_render_pass;\n\n  // Input: Format of the destination surface.\n  VkFormat format;\n\n  // Input: The main color attachment index where secondary_command_buffer\n  // will eventually be submitted.\n  uint32_t color_attachment_index;\n\n  // Input: current clip rect\n  int clip_left;\n  int clip_top;\n  int clip_right;\n  int clip_bottom;\n};\n\nclass VulkanHelper {\n public:\n  static VulkanHelper& GetInstance();\n\n  void Init(VkInstance instance, VkDevice device);\n\n  PFN_vkVoidFunction GetInstanceProcAddr(const char* pName);\n\n  PFN_vkVoidFunction GetDeviceProcAddr(const char* pName);\n\n  VkResult GetAndroidHardwareBufferPropertiesANDROID(\n      VkDevice device, const struct AHardwareBuffer* buffer,\n      VkAndroidHardwareBufferPropertiesANDROID* pProperties);\n\n  VkResult CreateImage(VkDevice device, const VkImageCreateInfo* pCreateInfo,\n                       const VkAllocationCallbacks* pAllocator,\n                       VkImage* pImage);\n\n  void DestroyImage(VkDevice device, VkImage image,\n                    const VkAllocationCallbacks* pAllocator);\n\n  VkResult AllocateMemory(VkDevice device,\n                          const VkMemoryAllocateInfo* pAllocateInfo,\n                          const VkAllocationCallbacks* pAllocator,\n                          VkDeviceMemory* pMemory);\n\n  VkResult BindImageMemory(VkDevice device, VkImage image,\n                           VkDeviceMemory memory, VkDeviceSize memoryOffset);\n\n  VkResult CreateImageView(VkDevice device,\n                           const VkImageViewCreateInfo* pCreateInfo,\n                           const VkAllocationCallbacks* pAllocator,\n                           VkImageView* pView);\n\n  void DestroyImageView(VkDevice device, VkImageView imageView,\n                        const VkAllocationCallbacks* pAllocator);\n\n  VkResult CreateFramebuffer(VkDevice device,\n                             const VkFramebufferCreateInfo* pCreateInfo,\n                             const VkAllocationCallbacks* pAllocator,\n                             VkFramebuffer* pFramebuffer);\n\n  VkResult CreateDescriptorPool(VkDevice device,\n                                const VkDescriptorPoolCreateInfo* pCreateInfo,\n                                const VkAllocationCallbacks* pAllocator,\n                                VkDescriptorPool* pDescriptorPool);\n\n  VkResult CreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo,\n                        const VkAllocationCallbacks* pAllocator,\n                        VkBuffer* pBuffer);\n\n  void GetBufferMemoryRequirements(VkDevice device, VkBuffer buffer,\n                                   VkMemoryRequirements* pMemoryRequirements);\n\n  VkResult BindBufferMemory(VkDevice device, VkBuffer buffer,\n                            VkDeviceMemory memory, VkDeviceSize memoryOffset);\n\n  VkResult MapMemory(VkDevice device, VkDeviceMemory memory,\n                     VkDeviceSize offset, VkDeviceSize size,\n                     VkMemoryMapFlags flags, void** ppData);\n\n  void UnmapMemory(VkDevice device, VkDeviceMemory memory);\n\n  VkResult CreatePipelineLayout(VkDevice device,\n                                const VkPipelineLayoutCreateInfo* pCreateInfo,\n                                const VkAllocationCallbacks* pAllocator,\n                                VkPipelineLayout* pPipelineLayout);\n\n  VkResult CreateGraphicsPipelines(\n      VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount,\n      const VkGraphicsPipelineCreateInfo* pCreateInfos,\n      const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines);\n\n  VkResult CreateSampler(VkDevice device,\n                         const VkSamplerCreateInfo* pCreateInfo,\n                         const VkAllocationCallbacks* pAllocator,\n                         VkSampler* pSampler);\n\n  VkResult CreateDescriptorSetLayout(\n      VkDevice device, const VkDescriptorSetLayoutCreateInfo* pCreateInfo,\n      const VkAllocationCallbacks* pAllocator,\n      VkDescriptorSetLayout* pSetLayout);\n\n  VkResult AllocateDescriptorSets(\n      VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo,\n      VkDescriptorSet* pDescriptorSets);\n\n  void UpdateDescriptorSets(VkDevice device, uint32_t descriptorWriteCount,\n                            const VkWriteDescriptorSet* pDescriptorWrites,\n                            uint32_t descriptorCopyCount,\n                            const VkCopyDescriptorSet* pDescriptorCopies);\n\n  void CmdBindDescriptorSets(VkCommandBuffer commandBuffer,\n                             VkPipelineBindPoint pipelineBindPoint,\n                             VkPipelineLayout layout, uint32_t firstSet,\n                             uint32_t descriptorSetCount,\n                             const VkDescriptorSet* pDescriptorSets,\n                             uint32_t dynamicOffsetCount,\n                             const uint32_t* pDynamicOffsets);\n\n  void CmdBindPipeline(VkCommandBuffer commandBuffer,\n                       VkPipelineBindPoint pipelineBindPoint,\n                       VkPipeline pipeline);\n\n  VkResult BeginCommandBuffer(VkCommandBuffer commandBuffer,\n                              const VkCommandBufferBeginInfo* pBeginInfo);\n\n  VkResult EndCommandBuffer(VkCommandBuffer commandBuffer);\n\n  void CmdBeginRenderPass(VkCommandBuffer commandBuffer,\n                          const VkRenderPassBeginInfo* pRenderPassBegin,\n                          VkSubpassContents contents);\n\n  void CmdEndRenderPass(VkCommandBuffer commandBuffer);\n\n  void CmdBindVertexBuffers(VkCommandBuffer commandBuffer,\n                            uint32_t firstBinding, uint32_t bindingCount,\n                            const VkBuffer* pBuffers,\n                            const VkDeviceSize* pOffsets);\n\n  void CmdBindIndexBuffer(VkCommandBuffer commandBuffer, VkBuffer buffer,\n                          VkDeviceSize offset, VkIndexType indexType);\n\n  void CmdDrawIndexed(VkCommandBuffer commandBuffer, uint32_t indexCount,\n                      uint32_t instanceCount, uint32_t firstIndex,\n                      int32_t vertexOffset, uint32_t firstInstance);\n\n  void CmdDraw(VkCommandBuffer commandBuffer, uint32_t vertexCount,\n               uint32_t instanceCount, uint32_t firstVertex,\n               uint32_t firstInstance);\n\n  VkResult QueueSubmit(VkQueue queue, uint32_t submitCount,\n                       const VkSubmitInfo* pSubmits, VkFence fence);\n\n  VkResult QueueWaitIdle(VkQueue queue);\n\n  void GetPhysicalDeviceMemoryProperties(\n      VkPhysicalDevice physicalDevice,\n      VkPhysicalDeviceMemoryProperties* pProperties);\n\n  VkResult CreateShaderModule(VkDevice device,\n                              const VkShaderModuleCreateInfo* pCreateInfo,\n                              const VkAllocationCallbacks* pAllocator,\n                              VkShaderModule* pShaderModule);\n\n  void DestroyShaderModule(VkDevice device, VkShaderModule shaderModule,\n                           const VkAllocationCallbacks* pAllocator);\n\n  VkResult DeviceWaitIdle(VkDevice device);\n\n  void DestroyBuffer(VkDevice device, VkBuffer buffer,\n                     const VkAllocationCallbacks* pAllocator);\n\n  void DestroyFramebuffer(VkDevice device, VkFramebuffer framebuffer,\n                          const VkAllocationCallbacks* pAllocator);\n\n  void FreeMemory(VkDevice device, VkDeviceMemory memory,\n                  const VkAllocationCallbacks* pAllocator);\n\n  void DestroySampler(VkDevice device, VkSampler sampler,\n                      const VkAllocationCallbacks* pAllocator);\n\n  void DestroyDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool,\n                             const VkAllocationCallbacks* pAllocator);\n\n  void DestroyDescriptorSetLayout(VkDevice device,\n                                  VkDescriptorSetLayout descriptorSetLayout,\n                                  const VkAllocationCallbacks* pAllocator);\n\n  void DestroyPipelineLayout(VkDevice device, VkPipelineLayout pipelineLayout,\n                             const VkAllocationCallbacks* pAllocator);\n\n  void DestroyPipeline(VkDevice device, VkPipeline pipeline,\n                       const VkAllocationCallbacks* pAllocator);\n\n  void CmdSetViewport(VkCommandBuffer commandBuffer, uint32_t firstViewport,\n                      uint32_t viewportCount, const VkViewport* pViewports);\n\n  void CmdSetScissor(VkCommandBuffer commandBuffer, uint32_t firstScissor,\n                     uint32_t scissorCount, const VkRect2D* pScissors);\n\n  void CmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer,\n                     VkBuffer dstBuffer, uint32_t regionCount,\n                     const VkBufferCopy* pRegions);\n\n  VkResult ResetDescriptorPool(VkDevice device, VkDescriptorPool descriptorPool,\n                               uint32_t flags);\n\n  void CmdPipelineBarrier(VkCommandBuffer commandBuffer,\n                          VkPipelineStageFlags srcStageMask,\n                          VkPipelineStageFlags dstStageMask,\n                          VkDependencyFlags dependencyFlags,\n                          uint32_t memoryBarrierCount,\n                          const VkMemoryBarrier* pMemoryBarriers,\n                          uint32_t bufferMemoryBarrierCount,\n                          const VkBufferMemoryBarrier* pBufferMemoryBarriers,\n                          uint32_t imageMemoryBarrierCount,\n                          const VkImageMemoryBarrier* pImageMemoryBarriers);\n\n  VkResult CreateSemaphore(VkDevice device,\n                           const VkSemaphoreCreateInfo* pCreateInfo,\n                           const VkAllocationCallbacks* pAllocator,\n                           VkSemaphore* pSemaphore);\n\n  void DestroySemaphore(VkDevice device, VkSemaphore semaphore,\n                        const VkAllocationCallbacks* pAllocator);\n\n  VkResult ImportSemaphoreFdKHR(\n      VkDevice device,\n      const VkImportSemaphoreFdInfoKHR* pImportSemaphoreFdInfo);\n\n  VkResult GetSemaphoreFdKHR(VkDevice device,\n                             const VkSemaphoreGetFdInfoKHR* pGetFdInfo,\n                             int* pFd);\n\n  VkResult CreateFence(VkDevice device, const VkFenceCreateInfo* pCreateInfo,\n                       const VkAllocationCallbacks* pAllocator,\n                       VkFence* pFence);\n\n  void DestroyFence(VkDevice device, VkFence fence,\n                    const VkAllocationCallbacks* pAllocator);\n\n  VkResult WaitForFences(VkDevice device, uint32_t fenceCount,\n                         const VkFence* pFences, VkBool32 waitAll,\n                         uint64_t timeout);\n\n  VkResult GetFenceStatus(VkDevice device, VkFence fence);\n\n  void CmdPushConstants(VkCommandBuffer commandBuffer, VkPipelineLayout layout,\n                        VkShaderStageFlags stageFlags, uint32_t offset,\n                        uint32_t size, const void* pValues);\n\n  VkResult CreateCommandPool(VkDevice device,\n                             const VkCommandPoolCreateInfo* pCreateInfo,\n                             const VkAllocationCallbacks* pAllocator,\n                             VkCommandPool* pCommandPool);\n  void DestroyCommandPool(VkDevice device, VkCommandPool commandPool,\n                          const VkAllocationCallbacks* pAllocator);\n\n  VkResult AllocateCommandBuffers(\n      VkDevice device, const VkCommandBufferAllocateInfo* pAllocateInfo,\n      VkCommandBuffer* pCommandBuffers);\n\n  void FreeCommandBuffers(VkDevice device, VkCommandPool commandPool,\n                          uint32_t commandBufferCount,\n                          const VkCommandBuffer* pCommandBuffers);\n\n  VkResult ResetCommandBuffer(VkCommandBuffer commandBuffer,\n                              VkCommandBufferResetFlags flags);\n\n  void GetPhysicalDeviceQueueFamilyProperties(\n      VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount,\n      VkQueueFamilyProperties* pQueueFamilyProperties);\n\n private:\n  VulkanHelper();\n  void InitializeVulkanFunctions();\n\n  PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr_ = nullptr;\n  PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr_ = nullptr;\n  VkInstance instance_ = VK_NULL_HANDLE;\n  VkDevice device_ = VK_NULL_HANDLE;\n\n  PFN_vkGetAndroidHardwareBufferPropertiesANDROID\n      vkGetAndroidHardwareBufferPropertiesANDROID_;\n  PFN_vkCreateImage vkCreateImage_;\n  PFN_vkDestroyImage vkDestroyImage_;\n  PFN_vkAllocateMemory vkAllocateMemory_;\n  PFN_vkBindImageMemory vkBindImageMemory_;\n  PFN_vkCreateImageView vkCreateImageView_;\n  PFN_vkDestroyImageView vkDestroyImageView_;\n  PFN_vkCreateFramebuffer vkCreateFramebuffer_;\n  PFN_vkCreateDescriptorPool vkCreateDescriptorPool_;\n  PFN_vkCreateBuffer vkCreateBuffer_;\n  PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements_;\n  PFN_vkBindBufferMemory vkBindBufferMemory_;\n  PFN_vkMapMemory vkMapMemory_;\n  PFN_vkUnmapMemory vkUnmapMemory_;\n  PFN_vkCreatePipelineLayout vkCreatePipelineLayout_;\n  PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines_;\n  PFN_vkCreateSampler vkCreateSampler_;\n  PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout_;\n  PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets_;\n  PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets_;\n  PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets_;\n  PFN_vkCmdBindPipeline vkCmdBindPipeline_;\n  PFN_vkBeginCommandBuffer vkBeginCommandBuffer_;\n  PFN_vkEndCommandBuffer vkEndCommandBuffer_;\n  PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass_;\n  PFN_vkCmdEndRenderPass vkCmdEndRenderPass_;\n  PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers_;\n  PFN_vkCmdBindIndexBuffer vkCmdBindIndexBuffer_;\n  PFN_vkCmdDrawIndexed vkCmdDrawIndexed_;\n  PFN_vkCmdDraw vkCmdDraw_;\n  PFN_vkQueueSubmit vkQueueSubmit_;\n  PFN_vkQueueWaitIdle vkQueueWaitIdle_;\n  PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties_;\n  PFN_vkCreateShaderModule vkCreateShaderModule_;\n  PFN_vkDestroyShaderModule vkDestroyShaderModule_;\n  PFN_vkDeviceWaitIdle vkDeviceWaitIdle_;\n  PFN_vkDestroyBuffer vkDestroyBuffer_;\n  PFN_vkFreeMemory vkFreeMemory_;\n  PFN_vkDestroySampler vkDestroySampler_;\n  PFN_vkDestroyDescriptorPool vkDestroyDescriptorPool_;\n  PFN_vkDestroyDescriptorSetLayout vkDestroyDescriptorSetLayout_;\n  PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout_;\n  PFN_vkDestroyPipeline vkDestroyPipeline_;\n  PFN_vkDestroyFramebuffer vkDestroyFramebuffer_;\n  PFN_vkCmdSetViewport vkCmdSetViewport_;\n  PFN_vkCmdSetScissor vkCmdSetScissor_;\n  PFN_vkCmdCopyBuffer vkCmdCopyBuffer_;\n  PFN_vkResetDescriptorPool vkResetDescriptorPool_;\n  PFN_vkCmdPipelineBarrier vkCmdPipelineBarrier_;\n  PFN_vkCreateSemaphore vkCreateSemaphore_;\n  PFN_vkDestroySemaphore vkDestroySemaphore_;\n  PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR_;\n  PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR_;\n  PFN_vkCreateFence vkCreateFence_;\n  PFN_vkDestroyFence vkDestroyFence_;\n  PFN_vkWaitForFences vkWaitForFences_;\n  PFN_vkGetFenceStatus vkGetFenceStatus_;\n  PFN_vkCmdPushConstants vkCmdPushConstants_;\n  PFN_vkCreateCommandPool vkCreateCommandPool_;\n  PFN_vkDestroyCommandPool vkDestroyCommandPool_;\n  PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers_;\n  PFN_vkFreeCommandBuffers vkFreeCommandBuffers_;\n  PFN_vkResetCommandBuffer vkResetCommandBuffer_;\n  PFN_vkGetPhysicalDeviceQueueFamilyProperties\n      vkGetPhysicalDeviceQueueFamilyProperties_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_VULKAN_VULKAN_HELPER_H_\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_image.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/vulkan/vulkan_image.h\"\n\n#include <cstring>\n\n#include \"clay/gfx/shared_image/android/android_hardwarebuffer_utils.h\"\n#include \"clay/gfx/vulkan/vulkan_helper.h\"\n\nnamespace clay {\n\nVulkanImage::VulkanImage(VkDevice device, VkPhysicalDevice physical_device)\n    : device_(device), physical_device_(physical_device) {}\n\nVulkanImage::~VulkanImage() {\n  if (image_view_ != VK_NULL_HANDLE) {\n    VulkanHelper::GetInstance().DestroyImageView(device_, image_view_, nullptr);\n    image_view_ = VK_NULL_HANDLE;\n  }\n  if (image_ != VK_NULL_HANDLE) {\n    VulkanHelper::GetInstance().DestroyImage(device_, image_, nullptr);\n  }\n\n  if (image_device_memory_ != VK_NULL_HANDLE) {\n    VulkanHelper::GetInstance().FreeMemory(device_, image_device_memory_,\n                                           nullptr);\n  }\n}\n\nvoid VulkanImage::CreateFromAndroidBuffer(AHardwareBuffer* buffer) {\n  CreateImage(buffer);\n  CreateImageView();\n}\n\nvoid VulkanImage::CreateImage(AHardwareBuffer* hw_buffer) {\n  VkAndroidHardwareBufferPropertiesANDROID buffer_properties = {};\n  buffer_properties.sType =\n      VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;\n  auto result =\n      VulkanHelper::GetInstance().GetAndroidHardwareBufferPropertiesANDROID(\n          device_, hw_buffer, &buffer_properties);\n  if (!LogVkIfNotSuccess(result,\n                         \"vkGetAndroidHardwareBufferPropertiesANDROID\")) {\n    return;\n  }\n\n  AHardwareBuffer_Desc desc;\n  AHardwareBufferUtils::GetInstance().Describe(hw_buffer, &desc);\n  width_ = desc.width;\n  height_ = desc.height;\n\n  // Create VkImage that is related to AHardwareBuffer.\n  VkExternalMemoryImageCreateInfo external_memory_image_create_info;\n  memset(&external_memory_image_create_info, 0,\n         sizeof(VkExternalMemoryImageCreateInfo));\n  external_memory_image_create_info.sType =\n      VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;\n  external_memory_image_create_info.pNext = nullptr;\n  external_memory_image_create_info.handleTypes =\n      VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;\n\n  VkImageCreateInfo image_create_info;\n  memset(&image_create_info, 0, sizeof(VkImageCreateInfo));\n  image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;\n  image_create_info.imageType = VK_IMAGE_TYPE_2D;\n  image_create_info.flags = 0;\n  image_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;\n  image_create_info.extent.width = width_;\n  image_create_info.extent.height = height_;\n  image_create_info.extent.depth = 1;\n  image_create_info.mipLevels = 1;\n  image_create_info.arrayLayers = 1;\n  image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;\n  image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;\n  image_create_info.usage =\n      VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;\n  image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;\n  image_create_info.initialLayout =\n      VK_IMAGE_LAYOUT_UNDEFINED;  // It has to be undefined if it is an external\n                                  // image.\n  image_create_info.queueFamilyIndexCount = 0,\n  image_create_info.pQueueFamilyIndices = nullptr,\n  image_create_info.pNext = &external_memory_image_create_info;\n\n  VkImage image;\n  result = VulkanHelper::GetInstance().CreateImage(device_, &image_create_info,\n                                                   nullptr, &image);\n  if (!LogVkIfNotSuccess(result, \"vkCreateImage\")) {\n    return;\n  }\n\n  VkMemoryDedicatedAllocateInfo dedicated_info = {};\n  dedicated_info.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;\n  dedicated_info.pNext = nullptr;\n  dedicated_info.image = image;\n\n  VkImportAndroidHardwareBufferInfoANDROID import_info = {};\n  import_info.sType =\n      VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;\n  import_info.pNext = &dedicated_info;\n  import_info.buffer = hw_buffer;\n\n  VkMemoryAllocateInfo alloc_info = {};\n  alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;\n  alloc_info.pNext = &import_info;\n  alloc_info.allocationSize = buffer_properties.allocationSize;\n  alloc_info.memoryTypeIndex =\n      FindMemoryType(buffer_properties.memoryTypeBits, physical_device_,\n                     VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);\n\n  VkDeviceMemory memory;\n  result = VulkanHelper::GetInstance().AllocateMemory(device_, &alloc_info,\n                                                      nullptr, &memory);\n  if (!LogVkIfNotSuccess(result, \"vkAllocateMemory\")) {\n    return;\n  }\n\n  result =\n      VulkanHelper::GetInstance().BindImageMemory(device_, image, memory, 0);\n  if (!LogVkIfNotSuccess(result, \"vkBindImageMemory\")) {\n    return;\n  }\n\n  image_ = image;\n  image_device_memory_ = memory;\n}\n\nvoid VulkanImage::CreateImageView() {\n  FML_DCHECK(image_ != VK_NULL_HANDLE);\n  VkImageViewCreateInfo view_info;\n  memset(&view_info, 0, sizeof(VkImageViewCreateInfo));\n  view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;\n  view_info.pNext = nullptr;\n  view_info.flags = 0;\n  view_info.image = image_;\n  view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;\n  view_info.format = VK_FORMAT_R8G8B8A8_UNORM;\n  view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n  view_info.subresourceRange.baseMipLevel = 0;\n  view_info.subresourceRange.levelCount = 1;\n  view_info.subresourceRange.baseArrayLayer = 0;\n  view_info.subresourceRange.layerCount = 1;\n\n  VkImageView image_view;\n  auto result = VulkanHelper::GetInstance().CreateImageView(\n      device_, &view_info, nullptr, &image_view);\n  if (!LogVkIfNotSuccess(result, \"vkCreateImageView\")) {\n    return;\n  }\n\n  image_view_ = image_view;\n}\n\nvoid VulkanImage::ApplyImagerBarrier(VkCommandBuffer command_buffer,\n                                     VkImageLayout old_layout,\n                                     VkImageLayout new_layout,\n                                     VkAccessFlags src_access_mask,\n                                     VkAccessFlags dst_access_mask,\n                                     VkPipelineStageFlagBits src_stage_mask,\n                                     VkPipelineStageFlagBits dst_stage_mask) {\n  VkImageMemoryBarrier barrier;\n  memset(&barrier, 0, sizeof(VkImageMemoryBarrier));\n  barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;\n  barrier.pNext = nullptr;\n  barrier.oldLayout = old_layout;\n  barrier.newLayout = new_layout;\n  barrier.srcAccessMask = src_access_mask;\n  barrier.dstAccessMask = dst_access_mask;\n  barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n  barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;\n  barrier.image = image_;\n  barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;\n  barrier.subresourceRange.baseMipLevel = 0;\n  barrier.subresourceRange.levelCount = 1;\n  barrier.subresourceRange.baseArrayLayer = 0;\n  barrier.subresourceRange.layerCount = 1;\n\n  VulkanHelper::GetInstance().CmdPipelineBarrier(command_buffer, src_stage_mask,\n                                                 dst_stage_mask, 0, 0, nullptr,\n                                                 0, nullptr, 1, &barrier);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/gfx/vulkan/vulkan_image.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_GFX_VULKAN_VULKAN_IMAGE_H_\n#define CLAY_GFX_VULKAN_VULKAN_IMAGE_H_\n\n#include <vulkan/vulkan.h>\n#include <vulkan/vulkan_android.h>\n\nnamespace clay {\n\nclass VulkanImage {\n public:\n  VulkanImage(VkDevice device, VkPhysicalDevice physical_device);\n  ~VulkanImage();\n\n  void CreateFromAndroidBuffer(AHardwareBuffer* buffer);\n\n  void ApplyImagerBarrier(VkCommandBuffer command_buffer,\n                          VkImageLayout old_layout, VkImageLayout new_layout,\n                          VkAccessFlags src_access_mask,\n                          VkAccessFlags dst_access_mask,\n                          VkPipelineStageFlagBits src_stage_mask,\n                          VkPipelineStageFlagBits dst_stage_mask);\n\n  VkImage GetImage() { return image_; }\n  VkImageView GetImageView() { return image_view_; }\n\n  int Width() const { return width_; }\n  int Height() const { return height_; }\n\n private:\n  void CreateImage(AHardwareBuffer* buffer);\n  void CreateImageView();\n\n  VkDevice device_;\n  VkPhysicalDevice physical_device_;\n\n  VkImage image_ = VK_NULL_HANDLE;\n  VkDeviceMemory image_device_memory_ = VK_NULL_HANDLE;\n  VkImageView image_view_ = VK_NULL_HANDLE;\n\n  int width_ = 0;\n  int height_ = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_GFX_VULKAN_VULKAN_IMAGE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../common/config.gni\")\nimport(\"./native_module/build.gni\")\n\nsource_set(\"lynx_adaptor\") {\n  sources = [\n    \"base_def.h\",\n    \"clay_value.cc\",\n    \"clay_value.h\",\n    \"layout_context_clay.cc\",\n    \"layout_context_clay.h\",\n    \"lynx_event_dispatcher.cc\",\n    \"lynx_event_dispatcher.h\",\n    \"painting_context_clay.cc\",\n    \"painting_context_clay.h\",\n    \"perf_controller_clay.cc\",\n    \"perf_controller_clay.h\",\n    \"platform_extra_bundle_clay.h\",\n    \"prop_bundle_impl.cc\",\n    \"prop_bundle_impl.h\",\n    \"ui_delegate_clay.cc\",\n    \"ui_delegate_clay.h\",\n    \"value_converter.cc\",\n    \"value_converter.h\",\n  ]\n  sources += rebase_path(clay_native_module_sources, \"\", \"native_module\")\n\n  # lynx_websocket_module.cc requires boringssl\n  include_dirs = [ \"//third_party/boringssl/src/include\" ]\n\n  public_configs = [\n    \"../../clay:config\",\n    \"../../core:lynx_public_config\",\n    \"../../platform/embedder:public_api\",\n  ]\n\n  public_deps = [\n    \"../fml:fml\",\n    \"../ui:ui\",\n  ]\n\n  if (is_win || is_mac) {\n    sources += [\n      \"native_view_service_desktop.cc\",\n      \"native_view_service_desktop.h\",\n    ]\n    if (enable_unittests) {\n      sources += [ \"native_platform_view_mock.cc\" ]\n    } else {\n      sources += [ \"native_platform_view.cc\" ]\n    }\n    sources += [\n      \"resource_loader_embedder.cc\",\n      \"resource_loader_embedder.h\",\n    ]\n    include_dirs += [ \"../../base/include/value\" ]\n    public_deps += [ \"../../base/src:base_values\" ]\n  }\n\n  defines = []\n  if (is_mac || is_android || is_ios) {\n    defines += [ \"TRACE_EVENT_HIDE_MACROS\" ]\n  }\n}\n"
  },
  {
    "path": "clay/lynx_adaptor/base_def.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string>\n\n#ifndef CLAY_LYNX_ADAPTOR_BASE_DEF_H_\n#define CLAY_LYNX_ADAPTOR_BASE_DEF_H_\n\nnamespace lynx {\n// Keep same as \"core/base/js_constants.h\"\nstatic constexpr const char* const BIG_INT_VAL = \"__lynx_val__\";\nstatic constexpr const int64_t kMaxJavaScriptNumber = 9007199254740991;\nstatic constexpr const int64_t kMinJavaScriptNumber = -9007199254740991;\n\n// Keep same as \"core/renderer/css/css_property.h\"\nusing PseudoState = uint32_t;\nstatic constexpr PseudoState kPseudoStateNone = 0;\nstatic constexpr PseudoState kPseudoStateHover = 1;\nstatic constexpr PseudoState kPseudoStateHoverTransition = 1 << 1;\nstatic constexpr PseudoState kPseudoStateActive = 1 << 3;\nstatic constexpr PseudoState kPseudoStateActiveTransition = 1 << 4;\nstatic constexpr PseudoState kPseudoStateFocus = 1 << 6;\nstatic constexpr PseudoState kPseudoStateFocusTransition = 1 << 7;\nstatic constexpr PseudoState kPseudoStatePlaceHolder = 1 << 8;\nstatic constexpr PseudoState kPseudoStateBefore = 1 << 9;\nstatic constexpr PseudoState kPseudoStateAfter = 1 << 10;\nstatic constexpr PseudoState kPseudoStateSelection = 1 << 11;\n\n// Keep same as \"core/style/transform_raw_data.h\"\nstruct TransformRawData {\n  static constexpr int INDEX_FUNC = 0;\n  static constexpr int INDEX_TRANSLATE_0 = 1;\n  static constexpr int INDEX_TRANSLATE_0_UNIT = 2;\n  static constexpr int INDEX_TRANSLATE_1 = 3;\n  static constexpr int INDEX_TRANSLATE_1_UNIT = 4;\n  static constexpr int INDEX_TRANSLATE_2 = 5;\n  static constexpr int INDEX_TRANSLATE_2_UNIT = 6;\n};\n}  // namespace lynx\n#endif  // CLAY_LYNX_ADAPTOR_BASE_DEF_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/clay_value.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/clay_value.h\"\n\nnamespace lynx {\n\nbool ClayValueWrapper::IsNumber() const {\n  switch (backend_value_.type()) {\n    case clay::Value::kInt:\n    case clay::Value::kLong:\n    case clay::Value::kUInt:\n    case clay::Value::kFloat:\n    case clay::Value::kDouble:\n      return true;\n    default:\n      return false;\n  }\n}\n\ndouble ClayValueWrapper::Number() const {\n  switch (backend_value_.type()) {\n    case clay::Value::kInt:\n      return backend_value_.GetInt();\n    case clay::Value::kLong:\n      return backend_value_.GetLong();\n    case clay::Value::kUInt:\n      return backend_value_.GetUint();\n    case clay::Value::kFloat:\n      return backend_value_.GetFloat();\n    case clay::Value::kDouble:\n      return backend_value_.GetDouble();\n    default:\n      return 0;\n  }\n}\n\nconst std::string& ClayValueWrapper::str() const {\n  if (backend_value_.IsString()) {\n    return backend_value_.GetString();\n  } else if (backend_value_.IsBool()) {\n    static std::string true_str(\"true\");\n    static std::string false_str(\"false\");\n    return backend_value_.GetBool() ? true_str : false_str;\n  }\n  // When backend_value_ is not a string type, the value returned by String()\n  // method will be free after leaving this function scope. Returning\n  // String()->str() directly will result in heap-use-after-free error.\n  // So we return a static string here.\n  static std::string empty(\"\");\n  return empty;\n}\nint ClayValueWrapper::Length() const {\n  if (backend_value_.IsMap()) {\n    return backend_value_.GetMap().size();\n  } else if (backend_value_.IsArray()) {\n    return backend_value_.GetArray().size();\n  } else if (backend_value_.IsString()) {\n    return backend_value_.GetString().size();\n  }\n  return 0;\n}\n\nbool ClayValueWrapper::IsEqual(const pub::Value& value) const {\n  if (value.backend_type() != pub::ValueBackendType::ValueBackendTypeCustom) {\n    return false;\n  }\n  auto that = reinterpret_cast<const ClayValueWrapper&>(value);\n  if (backend_value_.type() != that.backend_value_.type()) {\n    return false;\n  }\n  switch (backend_value_.type()) {\n    case clay::Value::kNone:\n      return true;\n    case clay::Value::kPointer:\n      return backend_value_.GetPointerType() ==\n                 that.backend_value_.GetPointerType() &&\n             backend_value_.GetPointer() == that.backend_value_.GetPointer();\n    case clay::Value::kBool:\n      return backend_value_.GetBool() == that.backend_value_.GetBool();\n    case clay::Value::kInt:\n      return backend_value_.GetInt() == that.backend_value_.GetInt();\n    case clay::Value::kLong:\n      return backend_value_.GetLong() == that.backend_value_.GetLong();\n    case clay::Value::kUInt:\n      return backend_value_.GetUint() == that.backend_value_.GetUint();\n    case clay::Value::kFloat:\n      return backend_value_.GetFloat() == that.backend_value_.GetFloat();\n    case clay::Value::kDouble:\n      return backend_value_.GetDouble() == that.backend_value_.GetDouble();\n    case clay::Value::kString:\n      return backend_value_.GetString() == that.backend_value_.GetString();\n    case clay::Value::kArray:\n      return &backend_value_.GetArray() == &that.backend_value_.GetArray();\n    case clay::Value::kArrayBuffer:\n      return &backend_value_.GetArrayBuffer() ==\n             &that.backend_value_.GetArrayBuffer();\n    case clay::Value::kMap:\n      return &backend_value_.GetMap() == &that.backend_value_.GetMap();\n    default:\n      return false;\n  }\n}\n\nvoid ClayValueWrapper::ForeachArray(pub::ForeachArrayFunc func) const {\n  if (!backend_value_.IsArray()) {\n    return;\n  }\n  for (size_t i = 0; i < backend_value_.GetArray().size(); ++i) {\n    ClayValueWrapper impl_value(backend_value_.GetArray()[i]);\n    func(i, impl_value);\n  }\n}\n\nvoid ClayValueWrapper::ForeachMap(pub::ForeachMapFunc func) const {\n  if (!backend_value_.IsMap()) {\n    return;\n  }\n  for (const auto& [key, value] : backend_value_.GetMap()) {\n    clay::Value name{key.c_str()};\n    ClayValueWrapper impl_key(name);\n    ClayValueWrapper impl_value(value);\n    func(impl_key, impl_value);\n  }\n}\n\nstd::unique_ptr<pub::Value> ClayValueWrapper::GetValueAtIndex(\n    uint32_t idx) const {\n  if (backend_value_.IsArray()) {\n    const auto& array = backend_value_.GetArray();\n    if (idx < array.size()) {\n      return wrap(array[idx]);\n    }\n  }\n  // Returns an empty Value if it's not a array to keep consistent with\n  // clay::Value\n  return std::make_unique<ClayValueWrapper>(clay::Value{});\n}\n\nbool ClayValueWrapper::Erase(uint32_t idx) const {\n  if (backend_value_.IsArray()) {\n    auto& array = const_cast<clay::Value::Array&>(backend_value_.GetArray());\n    if (idx < array.size()) {\n      array.erase(array.begin() + idx);\n      return true;\n    }\n  }\n  return false;\n}\n\nstd::unique_ptr<pub::Value> ClayValueWrapper::GetValueForKey(\n    const std::string& key) const {\n  if (backend_value_.IsMap()) {\n    auto it = backend_value_.GetMap().find(key);\n    if (it != backend_value_.GetMap().end()) {\n      return wrap(it->second);\n    }\n  }\n  // Returns an empty Value if it's not a map to keep consistent with\n  // clay::Value\n  return std::make_unique<ClayValueWrapper>(clay::Value{});\n}\n\nbool ClayValueWrapper::Erase(const std::string& key) const {\n  if (backend_value_.IsMap()) {\n    auto& map = const_cast<clay::Value::Map&>(backend_value_.GetMap());\n    auto it = map.find(key);\n    if (it != map.end()) {\n      map.erase(it);\n      return true;\n    }\n  }\n  return false;\n}\n\nbool ClayValueWrapper::Contains(const std::string& key) const {\n  if (backend_value_.IsMap()) {\n    auto it = backend_value_.GetMap().find(key);\n    if (it != backend_value_.GetMap().end()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool ClayValueWrapper::PushValueToArray(const Value& value) {\n  if (!backend_value_.IsArray()) {\n    return false;\n  }\n  auto& array = const_cast<clay::Value::Array&>(backend_value_.GetArray());\n  array.push_back(ValueConverter::CreateClayValue(value));\n  return true;\n}\n\nbool ClayValueWrapper::PushValueToArray(std::unique_ptr<Value> value) {\n  return PushValueToArray(*value);\n}\n\nbool ClayValueWrapper::PushNullToArray() {\n  if (!backend_value_.IsArray()) {\n    return false;\n  }\n  auto& array = const_cast<clay::Value::Array&>(backend_value_.GetArray());\n  array.push_back(clay::Value::Null());\n  return true;\n}\n\nbool ClayValueWrapper::PushArrayBufferToArray(std::unique_ptr<uint8_t[]> value,\n                                              size_t length) {\n  if (!backend_value_.IsArray()) {\n    return false;\n  }\n  auto& array = const_cast<clay::Value::Array&>(backend_value_.GetArray());\n  uint8_t* mem = value.get();\n  array.push_back(clay::Value(clay::Value::ArrayBuffer(mem, mem + length)));\n  return true;\n}\n\nbool ClayValueWrapper::PushBigIntToArray(const std::string& value) {\n  if (!IsArray()) {\n    return false;\n  }\n  auto& array = const_cast<clay::Value::Array&>(backend_value_.GetArray());\n  auto int_value =\n      static_cast<int64_t>(std::strtoll(value.c_str(), nullptr, 0));\n  array.push_back(clay::Value(int_value));\n  return true;\n}\n\nbool ClayValueWrapper::PushUInt64ToArray(uint64_t value) {\n  return PushInt64ToArray(static_cast<int64_t>(value));\n}\n\n#define NormalTypePushArrayImpl(name, type)                                   \\\n  bool ClayValueWrapper::Push##name##ToArray(type value) {                    \\\n    if (!backend_value_.IsArray()) {                                          \\\n      return false;                                                           \\\n    }                                                                         \\\n    auto& array = const_cast<clay::Value::Array&>(backend_value_.GetArray()); \\\n    array.push_back(clay::Value(value));                                      \\\n    return true;                                                              \\\n  }\nDeclarationTypeList(NormalTypePushArrayImpl)\n#undef NormalTypePushArrayImpl\n\n    bool ClayValueWrapper::PushValueToMap(const std::string& key,\n                                          const Value& value) {\n  if (!backend_value_.IsMap()) {\n    return false;\n  }\n  auto& map = const_cast<clay::Value::Map&>(backend_value_.GetMap());\n  map[key] = ValueConverter::CreateClayValue(value);\n  return true;\n}\n\nbool ClayValueWrapper::PushValueToMap(const std::string& key,\n                                      std::unique_ptr<Value> value) {\n  return PushValueToMap(key, *value);\n}\n\nbool ClayValueWrapper::PushNullToMap(const std::string& key) {\n  if (!backend_value_.IsMap()) {\n    return false;\n  }\n  auto& map = const_cast<clay::Value::Map&>(backend_value_.GetMap());\n  map[key] = clay::Value::Null();\n  return true;\n}\n\nbool ClayValueWrapper::PushArrayBufferToMap(const std::string& key,\n                                            std::unique_ptr<uint8_t[]> value,\n                                            size_t length) {\n  if (!backend_value_.IsMap()) {\n    return false;\n  }\n  auto& map = const_cast<clay::Value::Map&>(backend_value_.GetMap());\n  uint8_t* mem = value.get();\n  map[key] = clay::Value(clay::Value::ArrayBuffer(mem, mem + length));\n  return true;\n}\n\nbool ClayValueWrapper::PushUInt64ToMap(const std::string& key, uint64_t value) {\n  if (!backend_value_.IsMap()) {\n    return false;\n  }\n  auto& map = const_cast<clay::Value::Map&>(backend_value_.GetMap());\n  map[key] = clay::Value(static_cast<int64_t>(value));\n  return true;\n}\n\n#define NormalTypePushMapImpl(name, type)                               \\\n  bool ClayValueWrapper::Push##name##ToMap(const std::string& key,      \\\n                                           type value) {                \\\n    if (!backend_value_.IsMap()) {                                      \\\n      return false;                                                     \\\n    }                                                                   \\\n    auto& map = const_cast<clay::Value::Map&>(backend_value_.GetMap()); \\\n    map[key] = clay::Value(value);                                      \\\n    return true;                                                        \\\n  }\nDeclarationTypeList(NormalTypePushMapImpl)\n#undef NormalTypePushMapImpl\n\n    // static\n    std::unique_ptr<pub::Value> ClayValueWrapper::wrap(const clay::Value& val) {\n  switch (val.type()) {\n    case clay::Value::kNone:\n    case clay::Value::kPointer:\n      return std::make_unique<ClayValue>(clay::Value::Null());\n    case clay::Value::kBool:\n      return std::make_unique<ClayValue>(clay::Value(val.GetBool()));\n    case clay::Value::kInt:\n      return std::make_unique<ClayValue>(clay::Value(val.GetInt()));\n    case clay::Value::kLong:\n      return std::make_unique<ClayValue>(clay::Value(val.GetLong()));\n    case clay::Value::kUInt:\n      return std::make_unique<ClayValue>(clay::Value(val.GetUint()));\n    case clay::Value::kFloat:\n      return std::make_unique<ClayValue>(clay::Value(val.GetFloat()));\n    case clay::Value::kDouble:\n      return std::make_unique<ClayValue>(clay::Value(val.GetDouble()));\n    case clay::Value::kString:\n      return std::make_unique<ClayValue>(clay::Value(val.GetString()));\n    case clay::Value::kArray:\n      return std::make_unique<ClayValue>(\n          clay::Value(val.value<std::shared_ptr<clay::Value::Array>>()));\n    case clay::Value::kArrayBuffer:\n      return std::make_unique<ClayValue>(\n          clay::Value(val.value<std::shared_ptr<clay::Value::ArrayBuffer>>()));\n    case clay::Value::kMap:\n      return std::make_unique<ClayValue>(\n          clay::Value(val.value<std::shared_ptr<clay::Value::Map>>()));\n  }\n  return nullptr;\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/clay_value.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_CLAY_VALUE_H_\n#define CLAY_LYNX_ADAPTOR_CLAY_VALUE_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/lynx_adaptor/value_converter.h\"\n#include \"clay/public/value.h\"\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\n\n#define DeclarationTypeList(V) \\\n  V(Bool, bool)                \\\n  V(Double, double)            \\\n  V(Int32, int32_t)            \\\n  V(UInt32, uint32_t)          \\\n  V(Int64, int64_t)            \\\n  V(String, const std::string&)\n\nclass ClayValueWrapper : public pub::Value {\n public:\n  explicit ClayValueWrapper(const clay::Value& value)\n      : pub::Value(pub::ValueBackendType::ValueBackendTypeCustom),\n        backend_value_(value) {}\n  ~ClayValueWrapper() override {}\n\n  // TODO(chenyouhui.duke): Design common type definition for pub value.\n  int64_t Type() const override { return backend_value_.type(); }\n\n  bool IsUndefined() const override { return backend_value_.IsNone(); }\n  bool IsBool() const override { return backend_value_.IsBool(); }\n  bool IsInt32() const override { return backend_value_.IsInt(); }\n  bool IsInt64() const override { return backend_value_.IsLong(); }\n  bool IsUInt32() const override { return backend_value_.IsUint(); }\n  bool IsUInt64() const override { return backend_value_.IsLong(); }\n  bool IsDouble() const override {\n    return backend_value_.IsFloat() || backend_value_.IsDouble();\n  }\n  bool IsNumber() const override;\n  bool IsNil() const override { return backend_value_.IsNull(); }\n  bool IsString() const override { return backend_value_.IsString(); }\n  bool IsArray() const override { return backend_value_.IsArray(); }\n  bool IsArrayBuffer() const override { return backend_value_.IsArrayBuffer(); }\n  bool IsMap() const override { return backend_value_.IsMap(); }\n  bool IsFunction() const override { return false; }\n\n  bool Bool() const override {\n    FML_DCHECK(backend_value_.IsBool());\n    return backend_value_.GetBool();\n  }\n  int32_t Int32() const override {\n    FML_DCHECK(backend_value_.IsInt());\n    return backend_value_.GetInt();\n  }\n  int64_t Int64() const override {\n    FML_DCHECK(backend_value_.IsLong());\n    return backend_value_.GetLong();\n  }\n  uint32_t UInt32() const override {\n    FML_DCHECK(backend_value_.IsUint());\n    return backend_value_.GetUint();\n  }\n  uint64_t UInt64() const override {\n    FML_DCHECK(backend_value_.IsLong());\n    return backend_value_.GetLong();\n  }\n  double Double() const override {\n    FML_DCHECK(backend_value_.IsDouble());\n    return backend_value_.GetDouble();\n  }\n  double Number() const override;\n  uint8_t* ArrayBuffer() const override {\n    return const_cast<uint8_t*>(backend_value_.GetArrayBuffer().data());\n  }\n  const std::string& str() const override;\n  int Length() const override;\n  bool IsEqual(const Value& value) const override;\n\n  void ForeachArray(pub::ForeachArrayFunc func) const override;\n  void ForeachMap(pub::ForeachMapFunc func) const override;\n  std::unique_ptr<Value> GetValueAtIndex(uint32_t idx) const override;\n  bool Erase(uint32_t idx) const override;\n  std::unique_ptr<Value> GetValueForKey(const std::string& key) const override;\n  bool Erase(const std::string& key) const override;\n  bool Contains(const std::string& key) const override;\n  bool PushValueToArray(const Value& value) override;\n  bool PushValueToArray(std::unique_ptr<Value> value) override;\n  bool PushNullToArray() override;\n  bool PushArrayBufferToArray(std::unique_ptr<uint8_t[]> value,\n                              size_t length) override;\n  bool PushBigIntToArray(const std::string& value) override;\n  bool PushUInt64ToArray(uint64_t value) override;\n\n#define NormalTypePushArrayImpl(name, type) \\\n  bool Push##name##ToArray(type value) override;\n  DeclarationTypeList(NormalTypePushArrayImpl)\n#undef NormalTypePushArrayImpl\n\n      bool PushValueToMap(const std::string& key, const Value& value) override;\n  bool PushValueToMap(const std::string& key,\n                      std::unique_ptr<Value> value) override;\n  bool PushNullToMap(const std::string& key) override;\n  bool PushArrayBufferToMap(const std::string& key,\n                            std::unique_ptr<uint8_t[]> value,\n                            size_t length) override;\n\n  bool PushUInt64ToMap(const std::string& key, uint64_t value) override;\n\n#define NormalTypePushMapImpl(name, type) \\\n  bool Push##name##ToMap(const std::string& key, type value) override;\n  DeclarationTypeList(NormalTypePushMapImpl)\n#undef NormalTypePushMapImpl\n\n      const clay::Value& backend_value() const {\n    return backend_value_;\n  }\n\n private:\n  static std::unique_ptr<Value> wrap(const clay::Value& value);\n\n  const clay::Value& backend_value_;\n};\n\nclass ClayValue : public ClayValueWrapper {\n public:\n  ClayValue(clay::Value&& value)\n      : ClayValueWrapper(holder_), holder_(std::move(value)) {}\n  ~ClayValue() override {}\n\n private:\n  clay::Value holder_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_CLAY_VALUE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/layout_context_clay.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/layout_context_clay.h\"\n\n#include <string>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"base/include/string/string_utils.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/lynx_adaptor/platform_extra_bundle_clay.h\"\n#include \"clay/lynx_adaptor/prop_bundle_impl.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n\nnamespace {\n\nstd::vector<std::string> SeparateString(const std::string& src, char ch,\n                                        char end_ch) {\n  std::vector<std::string> temp_vec;\n\n  const size_t len = src.length();\n  size_t start = 0;\n  size_t end = 0;\n\n  while (start < len) {\n    end = src.find(ch, start);\n    // for url(string), string can have ch\n    // inside, so we should skip this case\n    while (end != std::string::npos && end > 1 && src[end - 1] != ')') {\n      end = src.find(ch, end + 1);\n    }\n    if (end == std::string::npos) {\n      break;\n    }\n    // skip ','\n    if (end - start != 0) {\n      temp_vec.emplace_back(src.substr(start, end - start));\n    }\n\n    start = end + 1;\n  }\n\n  if (start < len) {\n    size_t is_end_ch = src.find_last_not_of(end_ch);\n    if (is_end_ch == std::string::npos || is_end_ch < start) {\n      temp_vec.emplace_back(src.substr(start));\n    } else {\n      temp_vec.emplace_back(src.substr(start, is_end_ch - start + 1));\n    }\n  }\n\n  return temp_vec;\n}\n\nstd::vector<std::string> ParseFontFaceSrcAttr(const std::string& src) {\n  // for example:\n  // \"local(\"text1\"),url(\"test2\")\" -->\n  // 1. test1\n  // 2. test2\n  const std::string identify = \"http\";\n  const std::string url_prefix = \"url(\";\n  const std::string local_prefix = \"local(\";\n\n  // align with lynx behavior\n  const std::string asset_prefix = \"asset://lynx-fonts/\";\n\n  std::vector<std::string> src_vec;\n  src_vec = SeparateString(src, ',', ';');\n  for (auto& src_str : src_vec) {\n    std::string_view str = src_str;\n    if (str.empty()) continue;\n    str = lynx::base::TrimString(str, \" \", lynx::base::TrimPositions::TRIM_ALL);\n\n    size_t is_find_url = str.find(url_prefix);\n    if (is_find_url != std::string::npos) {\n      str = str.substr(url_prefix.length());\n      size_t pos = str.find(')');\n      if (pos != std::string::npos) {\n        str = str.substr(0, pos);\n        str = lynx::base::TrimString(str, \"\\\"\",\n                                     lynx::base::TrimPositions::TRIM_ALL);\n        str = lynx::base::TrimString(str, \"\\'\",\n                                     lynx::base::TrimPositions::TRIM_ALL);\n      }\n      src_str = str;\n    } else {\n      size_t is_find_local = str.find(local_prefix);\n      if (is_find_local != std::string::npos) {\n        str = str.substr(local_prefix.length());\n        size_t pos = str.find(')');\n        if (pos != std::string::npos) {\n          str = str.substr(0, pos);\n          str = lynx::base::TrimString(str, \"\\\"\",\n                                       lynx::base::TrimPositions::TRIM_ALL);\n          str = lynx::base::TrimString(str, \"\\'\",\n                                       lynx::base::TrimPositions::TRIM_ALL);\n        }\n        src_str = asset_prefix + std::string(str);\n      }\n    }\n  }\n  return src_vec;\n}\n\n}  // namespace\n\nnamespace lynx {\nnamespace tasm {\n\nclass MeasureFuncImpl : public MeasureFunc {\n public:\n  MeasureFuncImpl(LayoutContextClay* context, int sign)\n      : context_(context), sign_(sign) {}\n\n  LayoutResult Measure(float width, int32_t width_mode, float height,\n                       int32_t height_mode, bool final_measure) override {\n    LayoutResult result =\n        context_->MeasureImpl(sign_, width, width_mode, height, height_mode);\n    return result;\n  }\n  void Alignment() override { context_->AlignmentImpl(sign_); }\n\n private:\n  LayoutContextClay* context_ = nullptr;\n  int sign_ = -1;\n};\n\nLayoutContextClay::LayoutContextClay(clay::ViewContext* view_context)\n    : view_context_(view_context) {\n  view_context_->SetLayoutDelegate(this);\n}\n\nLayoutContextClay::~LayoutContextClay() {\n  view_context_->SetLayoutDelegate(nullptr);\n}\n\nvoid LayoutContextClay::SetLayoutNodeManager(\n    LayoutNodeManager* layout_node_manager) {\n  layout_node_manager_ = layout_node_manager;\n}\n\nint LayoutContextClay::CreateLayoutNode(int id, const std::string& tag,\n                                        PropBundle* painting_data,\n                                        bool allow_inline) {\n  auto node = view_context_->CreateShadowNode(id, tag.c_str(), allow_inline);\n  int rule = 0;\n  if (node) {\n    bool measurable = node->GetMeasurable() || node->GetCustomMeasurable();\n    rule = 0x2 | (measurable ? 0x1 : 0x0);\n  }\n\n  if (rule & 0x2) {    // is_shadow_node\n    if (rule & 0x1) {  // is_measurable\n      layout_node_manager_->SetMeasureFunc(\n          id, std::make_unique<MeasureFuncImpl>(this, id));\n    }\n    // update props\n    SetAttribute(id, painting_data);\n    if (rule & 0x1) {\n      return LayoutNodeType::CUSTOM;\n    }\n    return LayoutNodeType::VIRTUAL | LayoutNodeType::CUSTOM;\n  } else if (allow_inline) {\n    // update props\n    SetAttribute(id, painting_data);\n    return LayoutNodeType::INLINE;\n  }\n  // Set type in LayoutContext::CreateLayoutNodeSync\n  return LayoutNodeType::COMMON;\n}\n\nvoid LayoutContextClay::UpdateLayoutNode(int id, PropBundle* painting_data) {\n  SetAttribute(id, painting_data);\n}\n\nvoid LayoutContextClay::OnLayoutBefore(int id) {}\n\nvoid LayoutContextClay::InsertLayoutNode(int parent, int child, int index) {\n  view_context_->AddShadowNode(child, parent, index);\n}\n\nvoid LayoutContextClay::RemoveLayoutNode(int parent, int child, int index) {\n  view_context_->RemoveShadowNode(child);\n}\n\nvoid LayoutContextClay::OnLayout(int id, float left, float top, float width,\n                                 float height,\n                                 const std::array<float, 4>& paddings,\n                                 const std::array<float, 4>& borders) {\n  std::array<float, 4> rounded_paddings;\n  std::array<float, 4> rounded_borders;\n  rounded_paddings[0] = std::roundf(paddings[0]);\n  rounded_paddings[1] = std::roundf(paddings[1]);\n  rounded_paddings[2] = std::roundf(paddings[2]);\n  rounded_paddings[3] = std::roundf(paddings[3]);\n  rounded_borders[0] = std::roundf(borders[0]);\n  rounded_borders[1] = std::roundf(borders[1]);\n  rounded_borders[2] = std::roundf(borders[2]);\n  rounded_borders[3] = std::roundf(borders[3]);\n  view_context_->OnLayout(id, std::roundf(width), clay::MeasureMode::kDefinite,\n                          std::roundf(height), clay::MeasureMode::kDefinite,\n                          rounded_paddings, rounded_borders);\n}\n\nvoid LayoutContextClay::UpdateRootSize(float width, float height) {\n  if (width == 0 || height == 0) {\n    return;\n  }\n  view_context_->UpdateRootSize(width, height);\n}\n\nvoid LayoutContextClay::DestroyLayoutNodes(const std::unordered_set<int>& ids) {\n  for (auto& child : ids) {\n    view_context_->RemoveShadowNode(child);\n  }\n  for (auto& child : ids) {\n    view_context_->DestroyShadowNode(child);\n  }\n}\n\nvoid LayoutContextClay::Destroy() { view_context_->DestroyAllShadowNode(); }\n\nstd::unique_ptr<PlatformExtraBundle> LayoutContextClay::GetPlatformExtraBundle(\n    int32_t id) {\n  auto const& bundle = view_context_->GetTextBundle(id);\n  if (bundle == nullptr) {\n    return nullptr;\n  }\n  return std::make_unique<PlatformExtraBundleClay>(id, nullptr,\n                                                   std::move(bundle));\n}\n\nvoid LayoutContextClay::SetFontFaces(const CSSFontFaceRuleMap& fontfaces) {\n  for (auto iter = fontfaces.begin(); iter != fontfaces.end(); ++iter) {\n    std::string font_family = iter->first;\n    auto token_list = iter->second;\n    if (token_list.size() == 0) {\n      continue;\n    }\n    auto token = token_list[0];\n    auto src_map = token->second;\n    if (src_map.find(\"src\") == src_map.end()) {\n      continue;\n    }\n\n    std::string src_value = src_map[\"src\"];\n    auto src_vec = ParseFontFaceSrcAttr(src_value);\n    size_t size = src_vec.size();\n    const char* src[size];\n    int index = 0;\n    for (auto& str : src_vec) {\n      src[index++] = str.c_str();\n    }\n    view_context_->SetFontFace(font_family.c_str(), src, (int)size);\n  }\n}\n\nLayoutResult LayoutContextClay::MeasureImpl(int sign, int width, int width_mode,\n                                            int height, int height_mode) {\n  float out_width = 0.f;\n  float out_height = 0.f;\n  float out_baseline = 0.f;\n  view_context_->TextMeasure(\n      sign, width, static_cast<clay::MeasureMode>(width_mode), height,\n      static_cast<clay::MeasureMode>(height_mode), out_width, out_height);\n  out_baseline = view_context_->GetBaseline(sign);\n  return LayoutResult{out_width, out_height, out_baseline};\n}\n\nvoid LayoutContextClay::AlignmentImpl(int sign) {\n  view_context_->Alignment(sign);\n}\n\nvoid LayoutContextClay::ScheduleLayout() {\n  if (has_pending_layout_) {\n    view_context_->ScheduleLayout();\n    has_pending_layout_ = false;\n  }\n}\n\nvoid LayoutContextClay::OnTriggerLayout() {\n  layout_proxy_->TriggerLayout();\n  has_pending_layout_ = true;\n}\n\nvoid LayoutContextClay::SetAttribute(int sign, PropBundle* attributes) {\n  auto pda = static_cast<PropBundleImpl*>(attributes);\n\n  for (auto& event : pda->event_handlers()) {\n    view_context_->AddShadowNodeEventProp(sign, event.c_str());\n  }\n\n  if (!pda || pda->map().empty()) {\n    return;\n  }\n  auto& map = pda->mutable_map();\n  auto iter = map.begin();\n  for (; iter != map.end(); iter++) {\n    view_context_->SetShadowNodeAttribute(sign, iter->first.c_str(),\n                                          iter->second);\n  }\n}\n\nvoid LayoutContextClay::OnMarkDirty(int32_t id) {\n  layout_node_manager_->MarkDirtyAndRequestLayout(id);\n}\n\nvoid LayoutContextClay::OnAlignNativeNode(int32_t id, float offset_top,\n                                          float offset_left) {\n  layout_node_manager_->AlignmentByPlatform(id, offset_top, offset_left);\n}\n\nClayMeasureOutput LayoutContextClay::OnMeasureNativeNode(\n    int32_t id, float width, int width_mode, float height, int height_mode) {\n  auto size = layout_node_manager_->UpdateMeasureByPlatform(\n      id, width, width_mode, height, height_mode, true);\n  return {size.width_, size.height_, size.baseline_};\n}\n\nClayLayoutStyles LayoutContextClay::OnGetLayoutStyles(int32_t id) {\n  ClayLayoutStyles result;\n  result.padding_left =\n      static_cast<int>(layout_node_manager_->GetPaddingLeft(id));\n  result.padding_top =\n      static_cast<int>(layout_node_manager_->GetPaddingTop(id));\n  result.padding_right =\n      static_cast<int>(layout_node_manager_->GetPaddingRight(id));\n  result.padding_bottom =\n      static_cast<int>(layout_node_manager_->GetPaddingBottom(id));\n  result.margin_left =\n      static_cast<int>(layout_node_manager_->GetMarginLeft(id));\n  result.margin_top = static_cast<int>(layout_node_manager_->GetMarginTop(id));\n  result.margin_right =\n      static_cast<int>(layout_node_manager_->GetMarginRight(id));\n  result.margin_bottom =\n      static_cast<int>(layout_node_manager_->GetMarginBottom(id));\n\n  result.width = layout_node_manager_->GetWidth(id);\n  result.height = layout_node_manager_->GetHeight(id);\n\n  return result;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/layout_context_clay.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_LAYOUT_CONTEXT_CLAY_H_\n#define CLAY_LYNX_ADAPTOR_LAYOUT_CONTEXT_CLAY_H_\n\n#include <memory>\n#include <string>\n#include <unordered_set>\n\n#include \"clay/public/layout_delegate.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"core/public/layout_ctx_platform_impl.h\"\n#include \"core/public/layout_node_value.h\"\n#include \"core/public/lynx_engine_proxy.h\"\n#include \"core/public/lynx_layout_proxy.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass LayoutContextClay : public LayoutCtxPlatformImpl,\n                          public clay::LayoutDelegate {\n public:\n  explicit LayoutContextClay(clay::ViewContext* view_context);\n  ~LayoutContextClay() override;\n\n  void SetEngineProxy(\n      const std::shared_ptr<shell::LynxEngineProxy>& engine_proxy) {\n    engine_proxy_ = engine_proxy;\n  }\n\n  void SetLayoutProxy(\n      const std::shared_ptr<shell::LynxLayoutProxy>& layout_proxy) {\n    layout_proxy_ = layout_proxy;\n  }\n\n  void SetLayoutNodeManager(LayoutNodeManager* layout_node_manager) override;\n\n  int CreateLayoutNode(int id, const std::string& tag, PropBundle* props,\n                       bool allow_inline) override;\n  void UpdateLayoutNode(int id, PropBundle* painting_data) override;\n  void OnLayoutBefore(int tag) override;\n  void ScheduleLayout() override;\n  void SetFontFaces(const CSSFontFaceRuleMap& fontfaces) override;\n  void InsertLayoutNode(int parent, int child, int index) override;\n  void RemoveLayoutNode(int parent, int child, int index) override;\n  void OnLayout(int tag, float left, float top, float width, float height,\n                const std::array<float, 4>& paddings,\n                const std::array<float, 4>& borders) override;\n  void UpdateRootSize(float width, float height) override;\n  void DestroyLayoutNodes(const std::unordered_set<int>& ids) override;\n  void Destroy() override;\n  std::unique_ptr<PlatformExtraBundle> GetPlatformExtraBundle(\n      int32_t id) override;\n\n private:\n  // clay::LayoutDelegate\n  void OnTriggerLayout() override;\n  void OnMarkDirty(int32_t id) override;\n  void OnAlignNativeNode(int32_t id, float offset_top,\n                         float offset_left) override;\n  ClayMeasureOutput OnMeasureNativeNode(int32_t id, float width, int width_mode,\n                                        float height, int height_mode) override;\n  ClayLayoutStyles OnGetLayoutStyles(int32_t id) override;\n\n  friend class MeasureFuncImpl;\n  LayoutResult MeasureImpl(int sign, int width, int width_mode, int height,\n                           int height_mode);\n  void AlignmentImpl(int sign);\n\n  void SetAttribute(int sign, PropBundle* attributes);\n\n  bool has_pending_layout_ = true;\n  clay::ViewContext* view_context_ = nullptr;\n  LayoutNodeManager* layout_node_manager_ = nullptr;\n  std::shared_ptr<shell::LynxEngineProxy> engine_proxy_ = nullptr;\n  std::shared_ptr<shell::LynxLayoutProxy> layout_proxy_ = nullptr;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_LAYOUT_CONTEXT_CLAY_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/lynx_event_dispatcher.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/lynx_event_dispatcher.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/lynx_adaptor/base_def.h\"\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/public/value.h\"\n\n#if OS_WIN\n#include <Windows.h>\n#elif OS_MAC\n#include <ApplicationServices/ApplicationServices.h>\n#include <Carbon/Carbon.h>\n#endif\n\nnamespace clay {\n\nnamespace {\nstd::string AnimationPropertyTypeToString(ClayAnimationPropertyType type) {\n  std::string ret;\n  switch (type) {\n    default:\n    case ClayAnimationPropertyType::kNone:\n      ret = \"none\";\n      break;\n    case ClayAnimationPropertyType::kOpacity:\n      ret = \"opacity\";\n      break;\n    case ClayAnimationPropertyType::kScaleX:\n      ret = \"scalex\";\n      break;\n    case ClayAnimationPropertyType::kScaleY:\n      ret = \"scaley\";\n      break;\n    case ClayAnimationPropertyType::kScaleXY:\n      ret = \"scalexy\";\n      break;\n    case ClayAnimationPropertyType::kWidth:\n      ret = \"width\";\n      break;\n    case ClayAnimationPropertyType::kHeight:\n      ret = \"height\";\n      break;\n    case ClayAnimationPropertyType::kBackgroundColor:\n      ret = \"background-color\";\n      break;\n    case ClayAnimationPropertyType::kColor:\n      ret = \"color\";\n      break;\n    case ClayAnimationPropertyType::kVisibility:\n      ret = \"visibility\";\n      break;\n    case ClayAnimationPropertyType::kLeft:\n      ret = \"left\";\n      break;\n    case ClayAnimationPropertyType::kTop:\n      ret = \"top\";\n      break;\n    case ClayAnimationPropertyType::kRight:\n      ret = \"right\";\n      break;\n    case ClayAnimationPropertyType::kBottom:\n      ret = \"bottom\";\n      break;\n    case ClayAnimationPropertyType::kTransform:\n      ret = \"transform\";\n      break;\n    case ClayAnimationPropertyType::kAll:\n      ret = \"all\";\n      break;\n  }\n  ret = \"transition-\" + ret;\n  return ret;\n}\nvoid AddKeyModifierProperties(clay::Value::Map& dict) {\n  bool altKey = false;\n  bool ctrlKey = false;\n  bool shiftKey = false;\n  bool metaKey = false;\n#if OS_WIN\n  if ((GetKeyState(VK_LMENU) | GetKeyState(VK_RMENU)) & 0x8000) {\n    altKey = true;\n  }\n  if ((GetKeyState(VK_LCONTROL) | GetKeyState(VK_RCONTROL)) & 0x8000) {\n    ctrlKey = true;\n  }\n  if ((GetKeyState(VK_LSHIFT) | GetKeyState(VK_RSHIFT)) & 0x8000) {\n    shiftKey = true;\n  }\n  if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) {\n    metaKey = true;\n  }\n#elif OS_MAC\n  if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_Option) ||\n      CGEventSourceKeyState(kCGEventSourceStateHIDSystemState,\n                            kVK_RightOption)) {\n    altKey = true;\n  }\n  if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_Control) ||\n      CGEventSourceKeyState(kCGEventSourceStateHIDSystemState,\n                            kVK_RightControl)) {\n    ctrlKey = true;\n  }\n  if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_Shift) ||\n      CGEventSourceKeyState(kCGEventSourceStateHIDSystemState,\n                            kVK_RightShift)) {\n    shiftKey = true;\n  }\n  if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_Command) ||\n      CGEventSourceKeyState(kCGEventSourceStateHIDSystemState,\n                            kVK_RightCommand)) {\n    metaKey = true;\n  }\n#endif\n  dict[\"altKey\"] = clay::Value(altKey);\n  dict[\"shiftKey\"] = clay::Value(shiftKey);\n  dict[\"ctrlKey\"] = clay::Value(ctrlKey);\n  dict[\"metaKey\"] = clay::Value(metaKey);\n}\n}  // namespace\n\n// clay::EventDelegate override\nvoid LynxEventDispatcher::OnTouchEvent(const std::string& event_name, int tag,\n                                       float x, float y, float page_x,\n                                       float page_y) {\n  if (engine_proxy_) {\n    engine_proxy_->SendTouchEvent(event_name, tag, x, y, x, y, page_x, page_y);\n  }\n}\n\nvoid LynxEventDispatcher::OnMouseEvent(const std::string& event_name,\n                                       int view_id, int button, int buttons,\n                                       float scale, float x, float y,\n                                       float page_x, float page_y) {\n  if (!engine_proxy_) {\n    return;\n  }\n  clay::Value::Map rk_dict;\n\n  rk_dict[\"type\"] = clay::Value(event_name.c_str());\n  rk_dict[\"button\"] = clay::Value(button);\n  rk_dict[\"buttons\"] = clay::Value(buttons);\n  rk_dict[\"scale\"] = clay::Value(scale);\n\n  float density = engine_proxy_->GetDensity();\n  rk_dict[\"x\"] = clay::Value(x / density);\n  rk_dict[\"y\"] = clay::Value(y / density);\n  rk_dict[\"pageX\"] = clay::Value(page_x / density);\n  rk_dict[\"pageY\"] = clay::Value(page_y / density);\n  rk_dict[\"clientX\"] = clay::Value(page_x / density);\n  rk_dict[\"clientY\"] = clay::Value(page_y / density);\n\n  rk_dict[\"identifier\"] = clay::Value(reinterpret_cast<int64_t>(&rk_dict));\n\n  AddKeyModifierProperties(rk_dict);\n\n  auto params = lynx::ClayValue(clay::Value(std::move(rk_dict)));\n  engine_proxy_->SendBubbleEvent(event_name, view_id, params);\n}\n\nvoid LynxEventDispatcher::OnWheelEvent(const std::string& event_name,\n                                       int view_id, float x, float y,\n                                       float page_x, float page_y,\n                                       float delta_x, float delta_y) {\n  if (!engine_proxy_) {\n    return;\n  }\n  clay::Value::Map rk_dict;\n\n  // apply the attributes of mouse event\n  float density = engine_proxy_->GetDensity();\n  rk_dict[\"x\"] = clay::Value(x / density);\n  rk_dict[\"y\"] = clay::Value(y / density);\n  rk_dict[\"pageX\"] = clay::Value(page_x / density);\n  rk_dict[\"pageY\"] = clay::Value(page_y / density);\n  rk_dict[\"clientX\"] = clay::Value(page_x / density);\n  rk_dict[\"clientY\"] = clay::Value(page_y / density);\n\n  // apply the attributes of wheel event\n  rk_dict[\"deltaX\"] = clay::Value(delta_x);\n  rk_dict[\"deltaY\"] = clay::Value(delta_y);\n\n  auto params = lynx::ClayValue(clay::Value(std::move(rk_dict)));\n  engine_proxy_->SendBubbleEvent(event_name, view_id, params);\n}\n\nvoid LynxEventDispatcher::OnKeyEvent(const std::string& event_name, int view_id,\n                                     const char* key, bool repeat) {\n  if (!engine_proxy_) {\n    return;\n  }\n  clay::Value::Map rk_dict;\n  rk_dict[\"type\"] = clay::Value(event_name);\n  rk_dict[\"key\"] = clay::Value(key);\n  rk_dict[\"repeat\"] = clay::Value(repeat);\n\n  AddKeyModifierProperties(rk_dict);\n\n  auto params = lynx::ClayValue(clay::Value(std::move(rk_dict)));\n  engine_proxy_->SendBubbleEvent(event_name, view_id, params);\n}\n\nvoid LynxEventDispatcher::OnAnimationEvent(const std::string& event_name,\n                                           const char* animation_name,\n                                           int view_id) {\n  if (!engine_proxy_) {\n    return;\n  }\n  clay::Value::Map rk_dict;\n  rk_dict[\"animation_type\"] = clay::Value(\"keyframe-animation\");\n  rk_dict[\"animation_name\"] = clay::Value(animation_name);\n\n  auto params = lynx::ClayValue(clay::Value(std::move(rk_dict)));\n  engine_proxy_->SendCustomEvent(event_name, view_id, params, \"params\");\n}\n\nvoid LynxEventDispatcher::OnTransitionEvent(const std::string& event_name,\n                                            const char* animation_name,\n                                            int view_id,\n                                            ClayAnimationPropertyType type) {\n  if (!engine_proxy_) {\n    return;\n  }\n  auto apt = AnimationPropertyTypeToString(type);\n  clay::Value::Map rk_dict;\n  rk_dict[\"animation_type\"] = clay::Value(apt.c_str());\n  rk_dict[\"animation_name\"] = clay::Value(animation_name);\n\n  auto params = lynx::ClayValue(clay::Value(std::move(rk_dict)));\n  engine_proxy_->SendCustomEvent(event_name, view_id, params, \"params\");\n}\n\nvoid LynxEventDispatcher::OnFocusChanged(int view_id, bool focus) {\n  if (!engine_proxy_) {\n    return;\n  }\n  auto prev_state = !focus ? lynx::kPseudoStateFocus : lynx::kPseudoStateNone;\n  auto cur_state = focus ? lynx::kPseudoStateFocus : lynx::kPseudoStateNone;\n  engine_proxy_->OnPseudoStatusChanged(view_id, prev_state, cur_state);\n}\n\nvoid LynxEventDispatcher::OnHoverChanged(int view_id, bool hover) {\n  if (!engine_proxy_) {\n    return;\n  }\n  clay::Value::Map rk_dict;\n  rk_dict[\"hover\"] = clay::Value(hover);\n  auto params = lynx::ClayValue(clay::Value(std::move(rk_dict)));\n  engine_proxy_->SendCustomEvent(\"hoverchange\", view_id, params, \"detail\");\n\n  auto prev_state = !hover ? lynx::kPseudoStateHover : lynx::kPseudoStateNone;\n  auto cur_state = hover ? lynx::kPseudoStateHover : lynx::kPseudoStateNone;\n  engine_proxy_->OnPseudoStatusChanged(view_id, prev_state, cur_state);\n}\n\nvoid LynxEventDispatcher::OnDragDropEvent(const std::string& event_name,\n                                          int view_id, clay::Value::Map map) {\n  if (!engine_proxy_) {\n    return;\n  }\n  map.emplace(\"type\", clay::Value(event_name.c_str()));\n  auto params = lynx::ClayValue(clay::Value{std::move(map)});\n  engine_proxy_->SendBubbleEvent(event_name, view_id, params);\n}\n\nvoid LynxEventDispatcher::OnSendCustomEvent(int view_id,\n                                            const std::string& event_name,\n                                            clay::Value::Map args) {\n  if (!engine_proxy_) {\n    return;\n  }\n  auto params = lynx::ClayValue(clay::Value{std::move(args)});\n  engine_proxy_->SendCustomEvent(event_name, view_id, params, \"detail\");\n}\n\nvoid LynxEventDispatcher::OnSendGlobalEvent(const std::string& event_name,\n                                            clay::Value args) {\n  if (!runtime_proxy_) {\n    return;\n  }\n  clay::Value::Array array_wrapper(2);\n  array_wrapper[0] = clay::Value(event_name);\n  clay::Value::Array array_args(1);\n  array_args[0] = std::move(args);\n  array_wrapper[1] = clay::Value(std::move(array_args));\n\n  auto params = clay::Value(std::move(array_wrapper));\n  runtime_proxy_->CallJSFunction(\n      \"GlobalEventEmitter\", \"emit\",\n      std::make_unique<lynx::ClayValue>(std::move(params)));\n}\n\nvoid LynxEventDispatcher::OnDrawEndEvent() {\n  if (!perf_controller_) {\n    return;\n  }\n  perf_controller_->OnPaintEnd();\n}\n\nvoid LynxEventDispatcher::OnFirstMeaningfulPaint() {\n  if (!engine_proxy_) {\n    return;\n  }\n  engine_proxy_->OnFirstMeaningfulPaint();\n}\n\nvoid LynxEventDispatcher::OnOverlayEvent(int view_id, const char* overlay_id,\n                                         int overlay_count,\n                                         const char** overlay_ids,\n                                         const char* event_name) {\n  if (!engine_proxy_) {\n    return;\n  }\n  clay::Value::Array rk_param(1);\n  {\n    clay::Value::Map rk_dict;\n    clay::Value::Array overlays(overlay_count);\n    for (int i = 0; i < overlay_count; ++i) {\n      overlays[i] = clay::Value(overlay_ids[i]);\n    }\n    rk_dict[\"currentOverlayId\"] = clay::Value(overlay_id);\n\n    auto rk_overlays = clay::Value(std::move(overlays));\n    rk_dict[\"overlays\"] = std::move(rk_overlays);\n\n    rk_param[0] = clay::Value(std::move(rk_dict));\n  }\n  auto params = lynx::ClayValue(clay::Value(std::move(rk_param)));\n  engine_proxy_->SendCustomEvent(event_name, view_id, params, \"detail\");\n}\n\nvoid LynxEventDispatcher::OnLayoutChanged(int view_id, clay::Value::Map map) {\n  if (!engine_proxy_) {\n    return;\n  }\n  auto params = lynx::ClayValue(clay::Value(std::move(map)));\n  engine_proxy_->SendCustomEvent(\"layoutchange\", view_id, params, \"detail\");\n}\n\nvoid LynxEventDispatcher::OnIntersectionEvent(int view_id,\n                                              clay::Value::Map map) {\n  if (!engine_proxy_) {\n    return;\n  }\n  auto params = lynx::ClayValue(clay::Value(std::move(map)));\n  engine_proxy_->SendCustomEvent(\"intersection\", view_id, params, \"detail\");\n}\n\nvoid LynxEventDispatcher::OnCallJSApiCallback(int callback_id,\n                                              clay::Value value) {\n  if (!runtime_proxy_) {\n    return;\n  }\n  runtime_proxy_->CallJSApiCallbackWithValue(\n      callback_id, std::make_unique<lynx::ClayValue>(std::move(value)));\n}\n\nvoid LynxEventDispatcher::CallJSIntersectionObserver(int observer_id,\n                                                     int callback_id,\n                                                     clay::Value params) {\n  if (!runtime_proxy_) {\n    return;\n  }\n  runtime_proxy_->CallJSIntersectionObserver(\n      observer_id, callback_id,\n      std::make_unique<lynx::ClayValue>(std::move(params)));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/lynx_adaptor/lynx_event_dispatcher.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_LYNX_EVENT_DISPATCHER_H_\n#define CLAY_LYNX_ADAPTOR_LYNX_EVENT_DISPATCHER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/lynx_adaptor/perf_controller_clay.h\"\n#include \"clay/public/event_delegate.h\"\n#include \"core/public/lynx_engine_proxy.h\"\n#include \"core/public/lynx_runtime_proxy.h\"\n#include \"core/public/perf_controller_proxy.h\"\n\nnamespace clay {\n\nclass LynxEventDispatcher : public EventDelegate {\n public:\n  LynxEventDispatcher() = default;\n  ~LynxEventDispatcher() = default;\n\n  LynxEventDispatcher(const LynxEventDispatcher&) = delete;\n  LynxEventDispatcher& operator=(const LynxEventDispatcher&) = delete;\n\n  void SetEngineProxy(\n      const std::shared_ptr<lynx::shell::LynxEngineProxy>& engine_proxy) {\n    engine_proxy_ = engine_proxy;\n  }\n  void SetRuntimeProxy(\n      const std::shared_ptr<lynx::shell::LynxRuntimeProxy>& runtime_proxy) {\n    runtime_proxy_ = runtime_proxy;\n  }\n\n  void SetPerfController(\n      const std::shared_ptr<lynx::tasm::PerfControllerClay>& controller) {\n    perf_controller_ = controller;\n  }\n\n  // clay::EventDelegate override\n  void OnTouchEvent(const std::string& event_name, int tag, float x, float y,\n                    float page_x, float page_y) override;\n  void OnMouseEvent(const std::string& event_name, int view_id, int button,\n                    int buttons, float scale, float x, float y, float page_x,\n                    float page_y) override;\n  void OnWheelEvent(const std::string& event_name, int view_id, float x,\n                    float y, float page_x, float page_y, float delta_x,\n                    float delta_y) override;\n  void OnKeyEvent(const std::string& event_name, int view_id, const char* key,\n                  bool repeat) override;\n\n  void OnAnimationEvent(const std::string& event_name,\n                        const char* animation_name, int view_id) override;\n  void OnTransitionEvent(const std::string& event_name,\n                         const char* animation_name, int view_id,\n                         ClayAnimationPropertyType type) override;\n  void OnFocusChanged(int view_id, bool focus) override;\n  void OnHoverChanged(int view_id, bool hover) override;\n  void OnDragDropEvent(const std::string& event_name, int view_id,\n                       clay::Value::Map map) override;\n  void OnViewportMetricsChanged(double device_pixel_ratio,\n                                double device_density_dpi, double logical_width,\n                                double logical_height,\n                                double physical_screen_width,\n                                double physical_screen_height,\n                                double font_scale, bool night_mode) override {}\n\n  void OnSendCustomEvent(int view_id, const std::string& event_name,\n                         clay::Value::Map args) override;\n  void OnSendGlobalEvent(const std::string& event_name,\n                         clay::Value args) override;\n\n  void OnDrawEndEvent() override;\n  void OnFirstMeaningfulPaint() override;\n  void OnOverlayEvent(int view_id, const char* overlay_id, int overlay_count,\n                      const char** overlay_ids,\n                      const char* event_name) override;\n  void OnLayoutChanged(int view_id, clay::Value::Map map) override;\n  void OnIntersectionEvent(int view_id, clay::Value::Map map) override;\n  void OnCallJSApiCallback(int callback_id, clay::Value value) override;\n\n  void CallJSIntersectionObserver(int observer_id, int callback_id,\n                                  clay::Value params) override;\n\n private:\n  std::shared_ptr<lynx::shell::LynxEngineProxy> engine_proxy_ = nullptr;\n  std::shared_ptr<lynx::shell::LynxRuntimeProxy> runtime_proxy_ = nullptr;\n  std::shared_ptr<lynx::tasm::PerfControllerClay> perf_controller_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_LYNX_ADAPTOR_LYNX_EVENT_DISPATCHER_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/AGENTS.md",
    "content": "# AGENTS.md: Lynx Native Module\n\n## Directory Purpose\n\nThis directory contains the C++ implementation for Lynx Native Modules, which are exposed to the JS runtime via the JSBridge. Its primary goal is to provide a consistent, cross-platform API for JS to access underlying native capabilities, such as UI manipulation, system events, and platform-specific features.\n\n## Specification\n\nAgents are designed to automatically discover and consume specifications from the `spec/` directory within this module. All technical specs, including API contracts, behavioral notes, and platform differences, are documented here.\n\n| File | Description |\n| :--- | :--- |\n| `spec/lynx_ui_method.spec.md` | Defines the node location mechanism and cross-platform invocation chain for `invokeUIMethod`. |\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/build.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nclay_native_module_sources = [\n  \"lynx_config_module.cc\",\n  \"lynx_config_module.h\",\n  \"lynx_exposure_module.cc\",\n  \"lynx_exposure_module.h\",\n  \"lynx_focus_module.cc\",\n  \"lynx_focus_module.h\",\n  \"lynx_intersection_observer_module.cc\",\n  \"lynx_intersection_observer_module.h\",\n  \"lynx_module_base.cc\",\n  \"lynx_module_base.h\",\n  \"lynx_module_factory.cc\",\n  \"lynx_module_factory.h\",\n  \"lynx_text_info_module.cc\",\n  \"lynx_text_info_module.h\",\n  \"lynx_ui_method_module.cc\",\n  \"lynx_ui_method_module.h\",\n]\n\nif (!is_android && !is_ios && !is_tvos) {\n  clay_native_module_sources += [\n    \"lynx_websocket_module.cc\",\n    \"lynx_websocket_module.h\",\n  ]\n}\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_config_module.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_module/lynx_config_module.h\"\n\n#include <utility>\n\n#include \"clay/common/sys_info.h\"\n#include \"clay/gfx/image/image_data_cache.h\"\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n\nnamespace lynx {\n\nconst std::string LynxConfigModule::name_ = \"LynxConfigModule\";\n\nLynxConfigModule::LynxConfigModule(uint32_t view_context_id,\n                                   fml::RefPtr<fml::TaskRunner> task_runner)\n    : LynxModuleBase(view_context_id, task_runner) {\n  lynx::runtime::NativeModuleMethod set_dump_info(\"setDumpInfoToDevtoolEnabled\",\n                                                  1);\n  RegisterMethod(set_dump_info, &LynxConfigModule::setDumpInfoToDevtoolEnabled);\n\n  lynx::runtime::NativeModuleMethod set_render_options(\"setRenderOptions\", 1);\n  RegisterMethod(set_render_options, &LynxConfigModule::setRenderOptions);\n}\n\nLynxConfigModule::~LynxConfigModule() = default;\n\nstd::unique_ptr<lynx::pub::Value> LynxConfigModule::setDumpInfoToDevtoolEnabled(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(task_runner_, [weak_this = weak_from_this(),\n                                                   args_array =\n                                                       std::move(args_array),\n                                                   callback_map]() {\n    auto strong_this = weak_this.lock();\n    if (!strong_this) {\n      return;\n    }\n    auto view_context = clay::Isolate::Instance().GetViewContextById(\n        strong_this->view_context_id_);\n    if (!view_context) {\n      FML_DLOG(ERROR) << \"setDumpInfoToDevtoolEnabled failed, view context has \"\n                         \"been destroyed.\";\n      return;\n    }\n    static_cast<clay::PageView*>(view_context->GetPageView())\n        ->DumpInfoToDevtoolEnabled(args_array->GetValueAtIndex(0)->Bool());\n  });\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\nstd::unique_ptr<lynx::pub::Value> LynxConfigModule::setRenderOptions(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(task_runner_, [weak_this = weak_from_this(),\n                                                   args_array =\n                                                       std::move(args_array),\n                                                   callback_map]() {\n    auto strong_this = weak_this.lock();\n    if (!strong_this) {\n      return;\n    }\n    auto view_context = clay::Isolate::Instance().GetViewContextById(\n        strong_this->view_context_id_);\n    if (!view_context) {\n      FML_DLOG(ERROR) << \"setRenderOptions failed, view context has \"\n                         \"been destroyed.\";\n      return;\n    }\n\n    auto args = args_array->GetValueAtIndex(0);\n    if (args->Contains(\"imageRawDataCache\")) {\n      auto raw_data_cache = args->GetValueForKey(\"imageRawDataCache\")->Number();\n      FML_DLOG(WARNING) << \"setRenderOptions raw_data_cache=\" << raw_data_cache;\n      if (raw_data_cache > 0) {\n        clay::ImageDataCache::GetInstance().SetMaxCachedBytes(raw_data_cache);\n      }\n    }\n    if (args->Contains(\"isLowDevice\")) {\n      auto is_low_device = args->GetValueForKey(\"isLowDevice\")->Bool();\n      FML_DLOG(WARNING) << \"setRenderOptions is_low_device=\" << is_low_device;\n      clay::SysInfo::SetCustomIsLowEndDevice(is_low_device);\n    }\n    if (args->Contains(\"ignoreRasterCache\")) {\n      auto ignore_raster_cache =\n          args->GetValueForKey(\"ignoreRasterCache\")->Bool();\n      if (view_context) {\n        auto render_settings =\n            static_cast<clay::PageView*>(view_context->GetPageView())\n                ->GetRenderSettings();\n        if (render_settings) {\n          FML_DLOG(WARNING)\n              << \"setRenderOptions ignore_raster_cache=\" << ignore_raster_cache;\n          render_settings->SetIgnoreRasterCache(ignore_raster_cache);\n        }\n      }\n    }\n  });\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_config_module.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_CONFIG_MODULE_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_CONFIG_MODULE_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/lynx_adaptor/native_module/lynx_module_base.h\"\n\nnamespace lynx {\n\nclass LynxConfigModule : public LynxModuleBase,\n                         public std::enable_shared_from_this<LynxConfigModule> {\n public:\n  static std::shared_ptr<LynxNativeModule> Create(\n      uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner) {\n    return std::make_shared<LynxConfigModule>(view_context_id, task_runner);\n  }\n\n  static const std::string& GetName() { return name_; }\n\n  LynxConfigModule(uint32_t view_context_id,\n                   fml::RefPtr<fml::TaskRunner> task_runner);\n  ~LynxConfigModule() override;\n\n  std::unique_ptr<lynx::pub::Value> setDumpInfoToDevtoolEnabled(\n      std::unique_ptr<lynx::pub::Value> args_array,\n      const lynx::runtime::CallbackMap& callback_map);\n\n  std::unique_ptr<lynx::pub::Value> setRenderOptions(\n      std::unique_ptr<lynx::pub::Value> args_array,\n      const lynx::runtime::CallbackMap& callback_map);\n\n private:\n  static const std::string name_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_CONFIG_MODULE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_exposure_module.cc",
    "content": "\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_module/lynx_exposure_module.h\"\n\n#include <utility>\n\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n\nnamespace lynx {\n\nconst std::string LynxExposureModule::name_ = \"LynxExposureModule\";\n\nLynxExposureModule::LynxExposureModule(uint32_t view_context_id,\n                                       fml::RefPtr<fml::TaskRunner> task_runner)\n    : LynxModuleBase(view_context_id, task_runner) {\n  lynx::runtime::NativeModuleMethod stop_exposure(\"stopExposure\", 1);\n  RegisterMethod(stop_exposure, &LynxExposureModule::stopExposure);\n\n  lynx::runtime::NativeModuleMethod resume_exposure(\"resumeExposure\", 0);\n  RegisterMethod(resume_exposure, &LynxExposureModule::resumeExposure);\n}\nLynxExposureModule::~LynxExposureModule() = default;\n\nstd::unique_ptr<lynx::pub::Value> LynxExposureModule::stopExposure(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(task_runner_, [weak_this = weak_from_this(),\n                                                   args_array =\n                                                       std::move(args_array),\n                                                   callback_map]() {\n    auto strong_this = weak_this.lock();\n    if (!strong_this) {\n      return;\n    }\n    auto view_context = clay::Isolate::Instance().GetViewContextById(\n        strong_this->view_context_id_);\n    if (!view_context) {\n      FML_DLOG(ERROR) << \"stopExposure failed, view context has \"\n                         \"been destroyed.\";\n      return;\n    }\n    bool send_event = args_array->GetValueAtIndex(0)->Bool();\n    auto intersection_manager =\n        view_context->GetPageView()->intersection_observer_manager();\n    if (!intersection_manager) {\n      FML_DLOG(ERROR)\n          << \"stopExposure failed, intersection_observer_manager is nullptr\";\n      return;\n    }\n    intersection_manager->StopExposure(send_event);\n  });\n\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\nstd::unique_ptr<lynx::pub::Value> LynxExposureModule::resumeExposure(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(task_runner_, [weak_this = weak_from_this(),\n                                                   args_array =\n                                                       std::move(args_array),\n                                                   callback_map]() {\n    auto strong_this = weak_this.lock();\n    if (!strong_this) {\n      return;\n    }\n    auto view_context = clay::Isolate::Instance().GetViewContextById(\n        strong_this->view_context_id_);\n    if (!view_context) {\n      FML_DLOG(ERROR) << \"resumeExposure failed, view context has \"\n                         \"been destroyed.\";\n      return;\n    }\n    auto intersection_manager =\n        view_context->GetPageView()->intersection_observer_manager();\n    if (!intersection_manager) {\n      FML_DLOG(ERROR)\n          << \"resumeExposure failed, intersection_observer_manager is nullptr\";\n      return;\n    }\n    intersection_manager->ResumeExposure();\n  });\n\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_exposure_module.h",
    "content": "\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_EXPOSURE_MODULE_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_EXPOSURE_MODULE_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/lynx_adaptor/native_module/lynx_module_base.h\"\n\nnamespace lynx {\n\nclass LynxExposureModule\n    : public LynxModuleBase,\n      public std::enable_shared_from_this<LynxExposureModule> {\n public:\n  static std::shared_ptr<LynxNativeModule> Create(\n      uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner) {\n    return std::make_shared<LynxExposureModule>(view_context_id, task_runner);\n  }\n\n  static const std::string& GetName() { return name_; }\n\n  LynxExposureModule(uint32_t view_context_id,\n                     fml::RefPtr<fml::TaskRunner> task_runner);\n\n  ~LynxExposureModule() override;\n\n  std::unique_ptr<lynx::pub::Value> stopExposure(\n      std::unique_ptr<lynx::pub::Value>, const lynx::runtime::CallbackMap&);\n\n  std::unique_ptr<lynx::pub::Value> resumeExposure(\n      std::unique_ptr<lynx::pub::Value>, const lynx::runtime::CallbackMap&);\n\n private:\n  static const std::string name_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_EXPOSURE_MODULE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_focus_module.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_module/lynx_focus_module.h\"\n\n#include <string>\n#include <utility>\n\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n\nnamespace lynx {\n\nconst std::string LynxFocusModule::name_ = \"LynxFocusModule\";\n\nLynxFocusModule::LynxFocusModule(uint32_t view_context_id,\n                                 fml::RefPtr<fml::TaskRunner> task_runner)\n    : LynxModuleBase(view_context_id, task_runner) {\n  lynx::runtime::NativeModuleMethod set_trigger_internal(\"setTriggerInterval\",\n                                                         1);\n  RegisterMethod(set_trigger_internal, &LynxFocusModule::setTriggerInterval);\n\n  lynx::runtime::NativeModuleMethod get_curent_focus(\"getCurrentFocus\", 1);\n  RegisterMethod(get_curent_focus, &LynxFocusModule::getCurrentFocus);\n\n  lynx::runtime::NativeModuleMethod native_by_direction(\"navigateByDirection\",\n                                                        1);\n  RegisterMethod(native_by_direction, &LynxFocusModule::navigateByDirection);\n}\n\nLynxFocusModule::~LynxFocusModule() = default;\n\nstd::unique_ptr<lynx::pub::Value> LynxFocusModule::setTriggerInterval(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runner_, [weak_this = weak_from_this(),\n                     args_array = std::move(args_array), callback_map]() {\n        auto strong_this = weak_this.lock();\n        if (!strong_this) {\n          return;\n        }\n        auto view_context = clay::Isolate::Instance().GetViewContextById(\n            strong_this->view_context_id_);\n        if (!view_context) {\n          FML_DLOG(ERROR) << \"setTriggerInterval failed, view context has \"\n                             \"been destroyed.\";\n          return;\n        }\n        int32_t internal = args_array->GetValueAtIndex(0)->Number();\n        view_context->GetPageView()->GetFocusManager()->SetTriggerInterval(\n            internal);\n      });\n\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\nstd::unique_ptr<lynx::pub::Value> LynxFocusModule::getCurrentFocus(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(task_runner_, [weak_this = weak_from_this(),\n                                                   callback_map]() {\n    auto strong_this = weak_this.lock();\n    if (!strong_this) {\n      return;\n    }\n    auto view_context = clay::Isolate::Instance().GetViewContextById(\n        strong_this->view_context_id_);\n    if (!view_context) {\n      FML_DLOG(ERROR) << \"getCurrentFocus failed, view context has \"\n                         \"been destroyed.\";\n      return;\n    }\n    auto delegate = strong_this->delegate_.lock();\n    if (!delegate) {\n      return;\n    }\n\n    auto* view = static_cast<clay::BaseView*>(\n        view_context->GetPageView()->GetFocusManager()->GetLeafFocusedNode());\n    clay::Value::Map map;\n    if (view) {\n      std::string focus_index =\n          std::to_string(view->GetFocusIndex(clay::Axis::kX)) + \",\" +\n          std::to_string(view->GetFocusIndex(clay::Axis::kY));\n      std::string id_selector = view->GetIdSelector();\n      map[\"focusIndex\"] = clay::Value(std::move(focus_index));\n      map[\"id\"] = clay::Value(std::move(id_selector));\n    }\n    map[\"exist\"] = clay::Value(!!view);\n\n    clay::Value::Array array_wrapper(1);\n    array_wrapper[0] = clay::Value(std::move(map));\n    auto args = clay::Value(std::move(array_wrapper));\n    auto callback = callback_map.at(0);\n    callback->SetArgs(std::make_unique<lynx::ClayValue>(std::move(args)));\n    delegate->InvokeCallback(callback);\n  });\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\nstd::unique_ptr<lynx::pub::Value> LynxFocusModule::navigateByDirection(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callbacks) {\n  fml::TaskRunner::RunNowOrPostTask(task_runner_, [weak_this = weak_from_this(),\n                                                   args_array =\n                                                       std::move(args_array),\n                                                   callbacks]() {\n    auto strong_this = weak_this.lock();\n    if (!strong_this) {\n      return;\n    }\n    auto view_context = clay::Isolate::Instance().GetViewContextById(\n        strong_this->view_context_id_);\n    if (!view_context) {\n      FML_DLOG(ERROR) << \"navigateByDirection failed, view context has \"\n                         \"been destroyed.\";\n      return;\n    }\n\n    auto direction_str = args_array->GetValueAtIndex(0)->str();\n    clay::FocusManager::Direction direction;\n    if (direction_str == \"left\") {\n      direction = clay::FocusManager::Direction::kLeft;\n    } else if (direction_str == \"right\") {\n      direction = clay::FocusManager::Direction::kRight;\n    } else if (direction_str == \"up\") {\n      direction = clay::FocusManager::Direction::kUp;\n    } else if (direction_str == \"down\") {\n      direction = clay::FocusManager::Direction::kDown;\n    } else {\n      FML_DLOG(ERROR) << \"navigateByDirection failed, unrecognized direction: \"\n                      << direction_str;\n      return;\n    }\n    view_context->GetPageView()->GetFocusManager()->DoTraversal(direction);\n  });\n\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_focus_module.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_FOCUS_MODULE_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_FOCUS_MODULE_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/lynx_adaptor/native_module/lynx_module_base.h\"\n\nnamespace lynx {\n\nclass LynxFocusModule : public LynxModuleBase,\n                        public std::enable_shared_from_this<LynxFocusModule> {\n public:\n  static std::shared_ptr<LynxNativeModule> Create(\n      uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner) {\n    return std::make_shared<LynxFocusModule>(view_context_id, task_runner);\n  }\n\n  static const std::string& GetName() { return name_; }\n\n  LynxFocusModule(uint32_t view_context_id,\n                  fml::RefPtr<fml::TaskRunner> task_runner);\n  ~LynxFocusModule() override;\n\n  std::unique_ptr<lynx::pub::Value> setTriggerInterval(\n      std::unique_ptr<lynx::pub::Value>, const lynx::runtime::CallbackMap&);\n\n  std::unique_ptr<lynx::pub::Value> getCurrentFocus(\n      std::unique_ptr<lynx::pub::Value>, const lynx::runtime::CallbackMap&);\n\n  std::unique_ptr<lynx::pub::Value> navigateByDirection(\n      std::unique_ptr<lynx::pub::Value>, const lynx::runtime::CallbackMap&);\n\n private:\n  static const std::string name_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_FOCUS_MODULE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_intersection_observer_module.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_module/lynx_intersection_observer_module.h\"\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/lynx_adaptor/value_converter.h\"\n#include \"clay/ui/common/value_utils.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n\nnamespace lynx {\n\nconst std::string LynxIntersectionObserverModule::name_ =\n    \"IntersectionObserverModule\";\n\nLynxIntersectionObserverModule::LynxIntersectionObserverModule(\n    uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner)\n    : LynxModuleBase(view_context_id, task_runner) {\n  lynx::runtime::NativeModuleMethod create_intersection_observer(\n      \"createIntersectionObserver\", 3);\n  RegisterMethod(create_intersection_observer,\n                 &LynxIntersectionObserverModule::CreateIntersectionObserver);\n  lynx::runtime::NativeModuleMethod relative_to(\"relativeTo\", 3);\n  RegisterMethod(relative_to, &LynxIntersectionObserverModule::RelativeTo);\n  lynx::runtime::NativeModuleMethod observe(\"observe\", 3);\n  RegisterMethod(observe, &LynxIntersectionObserverModule::Observe);\n  lynx::runtime::NativeModuleMethod disconnect(\"disconnect\", 1);\n  RegisterMethod(disconnect, &LynxIntersectionObserverModule::Disconnect);\n  lynx::runtime::NativeModuleMethod relative_to_screen(\"relativeToScreen\", 2);\n  RegisterMethod(relative_to_screen,\n                 &LynxIntersectionObserverModule::RelativeToScreen);\n  lynx::runtime::NativeModuleMethod relative_to_viewport(\"relativeToViewport\",\n                                                         2);\n  RegisterMethod(relative_to_viewport,\n                 &LynxIntersectionObserverModule::RelativeToViewport);\n}\n\nLynxIntersectionObserverModule::~LynxIntersectionObserverModule() = default;\n\nstd::unique_ptr<lynx::pub::Value>\nLynxIntersectionObserverModule::CreateIntersectionObserver(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runner_, [week_this = weak_from_this(),\n                     args_array = std::move(args_array), callback_map]() {\n        auto strong_this = week_this.lock();\n        if (strong_this) {\n          auto view_context = clay::Isolate::Instance().GetViewContextById(\n              strong_this->view_context_id_);\n          if (!view_context) {\n            FML_LOG(ERROR) << \"setTriggerInterval failed, view context has \"\n                              \"been destroyed.\";\n            return;\n          }\n          int id = args_array->GetValueAtIndex(0)->Int32();\n          int component = args_array->GetValueAtIndex(1)->Int32();\n          auto options = args_array->GetValueAtIndex(2);\n          clay::Value clay_option_value =\n              ValueConverter::CreateClayValue(*options);\n          clay_option_value.GetMap()[\"componentId\"] = clay::Value(component);\n          clay_option_value.GetMap()[\"customObserverId\"] = clay::Value(id);\n          strong_this->observer_configs_[id] =\n              std::move(clay_option_value.GetMap());\n        }\n      });\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\nstd::unique_ptr<lynx::pub::Value> LynxIntersectionObserverModule::RelativeTo(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(task_runner_, [week_this = weak_from_this(),\n                                                   args_array =\n                                                       std::move(args_array),\n                                                   callback_map]() {\n    auto strong_this = week_this.lock();\n    if (strong_this) {\n      auto view_context = clay::Isolate::Instance().GetViewContextById(\n          strong_this->view_context_id_);\n      if (!view_context) {\n        FML_DLOG(ERROR) << \"setTriggerInterval failed, view context has \"\n                           \"been destroyed.\";\n        return;\n      }\n      if (args_array->Length() != 3) {\n        FML_LOG(ERROR) << \"relativeTo failed, args length is not 2.\";\n        return;\n      }\n      int id = args_array->GetValueAtIndex(0)->Int32();\n      std::string relativeToIdSelector = args_array->GetValueAtIndex(1)->str();\n      clay::Value::Map& option = strong_this->observer_configs_[id];\n      option[\"relativeToIdSelector\"] = clay::Value(relativeToIdSelector);\n      args_array->GetValueAtIndex(2)->ForeachMap(\n          [&option](const pub::Value& key, const pub::Value& value) {\n            if (!key.IsString()) {\n              return;\n            }\n            if (key.str() == \"left\") {\n              option[\"marginLeft\"] = ValueConverter::CreateClayValue(value);\n            }\n            if (key.str() == \"right\") {\n              option[\"marginRight\"] = ValueConverter::CreateClayValue(value);\n            }\n            if (key.str() == \"top\") {\n              option[\"marginTop\"] = ValueConverter::CreateClayValue(value);\n            }\n            if (key.str() == \"bottom\") {\n              option[\"marginBottom\"] = ValueConverter::CreateClayValue(value);\n            }\n          });\n    }\n  });\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\nstd::unique_ptr<lynx::pub::Value> LynxIntersectionObserverModule::Observe(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runner_, [week_this = weak_from_this(),\n                     args_array_ = std::move(args_array), callback_map]() {\n        auto strong_this = week_this.lock();\n        if (strong_this) {\n          auto view_context = clay::Isolate::Instance().GetViewContextById(\n              strong_this->view_context_id_);\n          if (!view_context) {\n            FML_DLOG(ERROR) << \"setTriggerInterval failed, view context has \"\n                               \"been destroyed.\";\n            return;\n          }\n          int id = args_array_->GetValueAtIndex(0)->Int32();\n          std::string target_id_selector =\n              args_array_->GetValueAtIndex(1)->str().substr(1);\n          int callback_id = args_array_->GetValueAtIndex(2)->Int32();\n          clay::BaseView* target = view_context->FindViewByIdSelector(\n              target_id_selector, view_context->GetPageView());\n          if (target == nullptr) {\n            FML_LOG(ERROR) << \"relativeTo failed, target is null.\";\n            return;\n          }\n          auto it = strong_this->observer_configs_.find(id);\n          if (it == strong_this->observer_configs_.end()) {\n            FML_LOG(ERROR) << \"Observe failed, no observer config for id: \"\n                           << id;\n            return;\n          }\n          clay::Value::Map& option_map = it->second;\n          clay::Value::Map option_cp;\n          for (const auto& [key, value] : option_map) {\n            option_cp.emplace(key, CloneClayValue(value));\n          }\n          option_cp.emplace(\"customCallbackId\", clay::Value(callback_id));\n          clay::Value option_value = clay::Value(std::move(option_cp));\n          clay::Value::Array wrapper_array = {};\n          wrapper_array.emplace_back(std::move(option_value));\n          clay::Value wrapper = clay::Value(std::move(wrapper_array));\n          target->SetAttribute(\"intersection-observers\", std::move(wrapper));\n          auto it2 = strong_this->observers_.find(id);\n          if (it2 == strong_this->observers_.end()) {\n            strong_this->observers_[id] = {};\n          }\n          strong_this->observers_[id].push_back(target);\n        }\n      });\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\nstd::unique_ptr<lynx::pub::Value> LynxIntersectionObserverModule::Disconnect(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runner_, [week_this = weak_from_this(),\n                     args_array = std::move(args_array), callback_map]() {\n        auto strong_this = week_this.lock();\n        if (strong_this) {\n          auto view_context = clay::Isolate::Instance().GetViewContextById(\n              strong_this->view_context_id_);\n          if (!view_context) {\n            FML_DLOG(ERROR) << \"setTriggerInterval failed, view context has \"\n                               \"been destroyed.\";\n            return;\n          }\n          if (args_array->Length() != 1) {\n            FML_LOG(ERROR) << \"relativeTo failed, args length is not 3.\";\n            return;\n          }\n          int id = args_array->GetValueAtIndex(0)->Int32();\n          auto observer_manager =\n              view_context->GetPageView()->intersection_observer_manager();\n          for (auto observer : strong_this->observers_[id]) {\n            observer_manager->RemoveIntersectionObserver(observer);\n          }\n          strong_this->observers_.erase(id);\n          strong_this->observer_configs_.erase(id);\n        }\n      });\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\n// in clay, root view will always be a sub view of screen, thus relative to\n// screen is equal to relative to viewport\n// the additional situation is x-overlay-ng, will be supported in the future.\nstd::unique_ptr<lynx::pub::Value>\nLynxIntersectionObserverModule::RelativeToScreen(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  return RelativeToViewport(std::move(args_array), callback_map);\n}\n\nstd::unique_ptr<lynx::pub::Value>\nLynxIntersectionObserverModule::RelativeToViewport(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callback_map) {\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runner_, [week_this = weak_from_this(),\n                     args_array = std::move(args_array), callback_map]() {\n        auto strong_this = week_this.lock();\n        if (strong_this) {\n          auto view_context = clay::Isolate::Instance().GetViewContextById(\n              strong_this->view_context_id_);\n          if (!view_context) {\n            FML_DLOG(ERROR) << \"setTriggerInterval failed, view context has \"\n                               \"been destroyed.\";\n            return;\n          }\n          if (args_array->Length() != 3) {\n            FML_LOG(ERROR) << \"relativeTo failed, args length is not 2.\";\n            return;\n          }\n          int id = args_array->GetValueAtIndex(0)->Int32();\n          clay::Value::Map& option = strong_this->observer_configs_[id];\n          std::string target_id_selector =\n              option[\"componentId\"].GetString().substr(1);\n          clay::BaseView* target = view_context->FindViewByIdSelector(\n              target_id_selector, view_context->GetPageView());\n          if (target == nullptr) {\n            FML_LOG(ERROR) << \"relativeTo failed, target is null.\";\n            return;\n          }\n\n          args_array->GetValueAtIndex(1)->ForeachMap(\n              [&option](const pub::Value& key, const pub::Value& value) {\n                if (!key.IsString()) {\n                  return;\n                }\n                if (key.str() == \"left\") {\n                  option[\"marginLeft\"] = ValueConverter::CreateClayValue(value);\n                }\n                if (key.str() == \"right\") {\n                  option[\"marginRight\"] =\n                      ValueConverter::CreateClayValue(value);\n                }\n                if (key.str() == \"top\") {\n                  option[\"marginTop\"] = ValueConverter::CreateClayValue(value);\n                }\n                if (key.str() == \"bottom\") {\n                  option[\"marginBottom\"] =\n                      ValueConverter::CreateClayValue(value);\n                }\n              });\n        }\n      });\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_intersection_observer_module.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_INTERSECTION_OBSERVER_MODULE_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_INTERSECTION_OBSERVER_MODULE_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/lynx_adaptor/native_module/lynx_module_base.h\"\n#include \"clay/ui/component/base_view.h\"\n\nnamespace lynx {\n\nclass LynxIntersectionObserverModule\n    : public LynxModuleBase,\n      public std::enable_shared_from_this<LynxIntersectionObserverModule> {\n public:\n  static std::shared_ptr<LynxIntersectionObserverModule> Create(\n      uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner) {\n    return std::make_shared<LynxIntersectionObserverModule>(view_context_id,\n                                                            task_runner);\n  }\n\n  static const std::string& GetName() { return name_; }\n  LynxIntersectionObserverModule(uint32_t view_context_id,\n                                 fml::RefPtr<fml::TaskRunner> task_runner);\n  ~LynxIntersectionObserverModule() override;\n\n  std::unique_ptr<lynx::pub::Value> CreateIntersectionObserver(\n      std::unique_ptr<lynx::pub::Value> args_array,\n      const lynx::runtime::CallbackMap& callback_map);\n\n  std::unique_ptr<lynx::pub::Value> RelativeTo(\n      std::unique_ptr<lynx::pub::Value> args_array,\n      const lynx::runtime::CallbackMap& callback_map);\n\n  std::unique_ptr<lynx::pub::Value> RelativeToScreen(\n      std::unique_ptr<lynx::pub::Value> args_array,\n      const lynx::runtime::CallbackMap& callback_map);\n\n  std::unique_ptr<lynx::pub::Value> RelativeToViewport(\n      std::unique_ptr<lynx::pub::Value> args_array,\n      const lynx::runtime::CallbackMap& callback_map);\n\n  std::unique_ptr<lynx::pub::Value> Observe(\n      std::unique_ptr<lynx::pub::Value> args_array,\n      const lynx::runtime::CallbackMap& callback_map);\n\n  std::unique_ptr<lynx::pub::Value> Disconnect(\n      std::unique_ptr<lynx::pub::Value> args_array,\n      const lynx::runtime::CallbackMap& callback_map);\n\n private:\n  static const std::string name_;\n\n  std::unordered_map<int, clay::Value::Map> observer_configs_;\n  std::unordered_map<int, std::vector<clay::BaseView*>> observers_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_INTERSECTION_OBSERVER_MODULE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_module_base.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_module/lynx_module_base.h\"\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace lynx {\n\nLynxModuleBase::LynxModuleBase(uint32_t view_context_id,\n                               fml::RefPtr<fml::TaskRunner> task_runner)\n    : view_context_id_(view_context_id), task_runner_(task_runner) {}\n\nLynxModuleBase::~LynxModuleBase() = default;\n\nvoid LynxModuleBase::RegisterInvocation(\n    const lynx::runtime::NativeModuleMethod& method, Invocation&& invocation) {\n  FML_DCHECK(methods_.find(method.name) == methods_.end());\n  invocations_.emplace(method.name, std::move(invocation));\n  methods_.emplace(method.name, std::move(method));\n}\n\nlynx::base::expected<std::unique_ptr<lynx::pub::Value>, std::string>\nLynxModuleBase::InvokeMethod(const std::string& method_name,\n                             std::unique_ptr<lynx::pub::Value> args,\n                             size_t count,\n                             const lynx::runtime::CallbackMap& callbacks) {\n  if (!task_runner_) {\n    return std::unique_ptr<lynx::pub::Value>(nullptr);\n  }\n  auto invocation_itr = invocations_.find(method_name);\n  if (invocation_itr != invocations_.end() &&\n      ValidateInvocation(method_name, count)) {\n    return invocation_itr->second(std::move(args), callbacks);\n  }\n  return std::unique_ptr<lynx::pub::Value>(nullptr);\n}\n\nbool LynxModuleBase::ValidateInvocation(const std::string& method_name,\n                                        size_t count) {\n  auto method = methods_.find(method_name);\n  if (method == methods_.end()) {\n    FML_DLOG(ERROR) << \"Cannot find a native method named \" << method_name;\n    return false;\n  }\n  if (method->second.args_count != count) {\n    FML_DLOG(ERROR) << \"Method \" << method_name << \"'s args count is \"\n                    << method->second.args_count\n                    << \", which is not match input count \" << count;\n    return false;\n  }\n  return true;\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_module_base.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_MODULE_BASE_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_MODULE_BASE_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"core/public/jsb/lynx_native_module.h\"\n\nnamespace lynx {\n\nclass LynxModuleBase : public runtime::LynxNativeModule {\n public:\n  LynxModuleBase(uint32_t view_context_id,\n                 fml::RefPtr<fml::TaskRunner> task_runner);\n  virtual ~LynxModuleBase();\n\n protected:\n  using Invocation = std::function<std::unique_ptr<pub::Value>(\n      std::unique_ptr<pub::Value>, const runtime::CallbackMap&)>;\n\n  template <typename T>\n  void RegisterMethod(const runtime::NativeModuleMethod& method,\n                      std::unique_ptr<pub::Value> (T::*member_fn)(\n                          std::unique_ptr<pub::Value>,\n                          const runtime::CallbackMap&)) {\n    RegisterInvocation(method, [obj = static_cast<T*>(this), member_fn](\n                                   std::unique_ptr<pub::Value> arg,\n                                   const runtime::CallbackMap& cb) {\n      return std::invoke(member_fn, obj, std::move(arg), cb);\n    });\n  }\n  void RegisterInvocation(const runtime::NativeModuleMethod& method,\n                          Invocation&& invocation);\n  bool ValidateInvocation(const std::string& method_name, size_t count);\n\n  base::expected<std::unique_ptr<lynx::pub::Value>, std::string> InvokeMethod(\n      const std::string& method_name, std::unique_ptr<lynx::pub::Value> args,\n      size_t count, const runtime::CallbackMap& callbacks) override;\n\n  uint32_t view_context_id_;\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n\n  std::unordered_map<std::string, Invocation> invocations_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_MODULE_BASE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_module_factory.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_module/lynx_module_factory.h\"\n\n#include \"clay/lynx_adaptor/native_module/lynx_config_module.h\"\n#include \"clay/lynx_adaptor/native_module/lynx_exposure_module.h\"\n#include \"clay/lynx_adaptor/native_module/lynx_focus_module.h\"\n#include \"clay/lynx_adaptor/native_module/lynx_intersection_observer_module.h\"\n#include \"clay/lynx_adaptor/native_module/lynx_text_info_module.h\"\n#include \"clay/lynx_adaptor/native_module/lynx_ui_method_module.h\"\n\n#if !defined(OS_IOS) && !defined(OS_TVOS) && !defined(OS_ANDROID)\n#include \"clay/lynx_adaptor/native_module/lynx_websocket_module.h\"\n#endif\n#include \"clay/ui/component/view_context.h\"\n\nnamespace lynx {\n\nlynx::runtime::NativeModuleFactory* LynxModuleFactory::CreateModuleFactory(\n    clay::ViewContext* view_context) {\n  auto module_factory = new LynxModuleFactory(view_context);\n  module_factory->RegisterCreator(LynxConfigModule::GetName(),\n                                  LynxConfigModule::Create);\n  module_factory->RegisterCreator(LynxExposureModule::GetName(),\n                                  LynxExposureModule::Create);\n  module_factory->RegisterCreator(LynxFocusModule::GetName(),\n                                  LynxFocusModule::Create);\n  module_factory->RegisterCreator(LynxUIMethodModule::GetName(),\n                                  LynxUIMethodModule::Create);\n  module_factory->RegisterCreator(LynxTextInfoModule::GetName(),\n                                  LynxTextInfoModule::Create);\n  module_factory->RegisterCreator(LynxIntersectionObserverModule::GetName(),\n                                  LynxIntersectionObserverModule::Create);\n#if !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(OS_TVOS)\n  module_factory->RegisterCreator(LynxWebSocketModule::GetName(),\n                                  LynxWebSocketModule::Create);\n#endif\n  return module_factory;\n}\n\nLynxModuleFactory::LynxModuleFactory(clay::ViewContext* view_context)\n    : view_context_id_(view_context->unique_id()),\n      task_runner_(view_context->GetUITaskRunner()) {}\n\nstd::shared_ptr<lynx::runtime::LynxNativeModule>\nLynxModuleFactory::CreateModule(const std::string& name) {\n  std::lock_guard<std::mutex> lock(clay_mutex_);\n  auto itr = clay_creators_.find(name);\n  if (itr == clay_creators_.end()) {\n    return nullptr;\n  }\n  return itr->second(view_context_id_, task_runner_);\n}\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_module_factory.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_MODULE_FACTORY_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_MODULE_FACTORY_H_\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"core/public/jsb/native_module_factory.h\"\n\nnamespace clay {\nclass ViewContext;\n};\nnamespace lynx {\n\nusing ClayModuleCreator =\n    std::function<std::shared_ptr<lynx::runtime::LynxNativeModule>(\n        uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner)>;\n\nclass LynxModuleFactory : public lynx::runtime::NativeModuleFactory {\n public:\n  static lynx::runtime::NativeModuleFactory* CreateModuleFactory(\n      clay::ViewContext* view_context);\n\n  explicit LynxModuleFactory(clay::ViewContext* view_context);\n\n  ~LynxModuleFactory() override = default;\n\n  std::shared_ptr<lynx::runtime::LynxNativeModule> CreateModule(\n      const std::string& name) override;\n\n  void RegisterCreator(const std::string& name, ClayModuleCreator creator) {\n    std::lock_guard<std::mutex> lock(clay_mutex_);\n    clay_creators_.emplace(name, std::move(creator));\n  }\n\n private:\n  std::mutex clay_mutex_;\n  std::unordered_map<std::string, ClayModuleCreator> clay_creators_;\n  uint64_t view_context_id_;\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_MODULE_FACTORY_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_text_info_module.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_module/lynx_text_info_module.h\"\n\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/lynx_adaptor/value_converter.h\"\n#include \"clay/ui/shadow/text_render.h\"\n\nnamespace lynx {\nconst std::string LynxTextInfoModule::name_ = \"LynxTextInfoModule\";\n\nLynxTextInfoModule::LynxTextInfoModule(uint32_t view_context_id) {\n  view_context_id_ = view_context_id;\n  lynx::runtime::NativeModuleMethod get_text_info(\"getTextInfo\", 2);\n  // RegisterMethod(get_text_info, &LynxTextInfoModule::GetTextInfo);\n  auto method = runtime::NativeModuleMethod(\"getTextInfo\", 2);\n  methods_.emplace(\"getTextInfo\", method);\n}\n\nstd::unique_ptr<pub::Value> LynxTextInfoModule::GetTextInfo(\n    const std::string& content, const pub::Value& info) {\n  auto rk_info = ValueConverter::CreateClayValue(info);\n  auto result = clay::TextRender::GetTextInfo(content.c_str(), rk_info);\n  return std::make_unique<ClayValue>(std::move(result));\n}\n\nbase::expected<std::unique_ptr<pub::Value>, std::string>\nLynxTextInfoModule::InvokeMethod(const std::string& method_name,\n                                 std::unique_ptr<pub::Value> args, size_t count,\n                                 const runtime::CallbackMap& callbacks) {\n  if (method_name == \"getTextInfo\") {\n    if (!args->IsArray() || count != 2) {\n      return base::unexpected(\"Invalid argument count\");\n    }\n    auto content = args->GetValueAtIndex(0)->str();\n    auto info = args->GetValueAtIndex(1);\n    return GetTextInfo(content, *info);\n  } else {\n    return base::unexpected(\"Unknown method\");\n  }\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_text_info_module.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_TEXT_INFO_MODULE_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_TEXT_INFO_MODULE_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"core/public/jsb/lynx_native_module.h\"\n\nnamespace lynx {\n\nclass LynxTextInfoModule : public runtime::LynxNativeModule {\n public:\n  explicit LynxTextInfoModule(uint32_t view_context_id);\n  ~LynxTextInfoModule() override = default;\n\n  static std::shared_ptr<LynxNativeModule> Create(\n      uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner) {\n    return std::make_shared<LynxTextInfoModule>(view_context_id);\n  }\n\n  static const std::string& GetName() { return name_; }\n\n  base::expected<std::unique_ptr<pub::Value>, std::string> InvokeMethod(\n      const std::string& method_name, std::unique_ptr<pub::Value> args,\n      size_t count, const runtime::CallbackMap& callbacks) override;\n\n  std::unique_ptr<pub::Value> GetTextInfo(const std::string& content,\n                                          const pub::Value& info);\n\n private:\n  static const std::string name_;\n  uint32_t view_context_id_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_TEXT_INFO_MODULE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_ui_method_module.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_module/lynx_ui_method_module.h\"\n\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/lynx_adaptor/value_converter.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n#include \"core/public/jsb/lynx_native_module.h\"\n\nnamespace lynx {\n\nnamespace {\n\nconstexpr char kStrIsCalledByRefId[] = \"_isCallByRefId\";\n\nstruct CallbackData {\n  std::shared_ptr<lynx::runtime::LynxModuleCallback> callback;\n  std::weak_ptr<lynx::runtime::LynxNativeModule::Delegate> delegate;\n  clay::LynxUIMethodCallback ui_callback;\n};\n\n}  // namespace\n\nconst std::string LynxUIMethodModule::name_ = \"LynxUIMethodModule\";\n\nLynxUIMethodModule::LynxUIMethodModule(uint32_t view_context_id,\n                                       fml::RefPtr<fml::TaskRunner> task_runner)\n    : LynxModuleBase(view_context_id, task_runner) {\n  // argmentlist: \"view_id\", \"nodes\", \"method_name\", \"args\", \"callback\"\n  lynx::runtime::NativeModuleMethod invoke_ui_method(\"invokeUIMethod\", 5);\n  RegisterMethod(invoke_ui_method,\n                 &LynxUIMethodModule::InvokeUIMethodCompatibility);\n}\n\nLynxUIMethodModule::~LynxUIMethodModule() = default;\n\nstd::unique_ptr<lynx::pub::Value>\nLynxUIMethodModule::InvokeUIMethodCompatibility(\n    std::unique_ptr<lynx::pub::Value> args_array,\n    const lynx::runtime::CallbackMap& callbacks) {\n  auto module_values = std::make_shared<clay::LynxModuleValues>();\n  CallbackData* callback_data = nullptr;\n\n  args_array->ForeachArray([&](int64_t key, const pub::Value& value) {\n    runtime::CallbackMap::const_iterator it;\n    if (value.IsInt64() && (it = callbacks.find(key)) != callbacks.end()) {\n      callback_data =\n          new CallbackData{.callback = it->second, .delegate = delegate_};\n      callback_data->ui_callback =\n          [callback_data](clay::LynxUIMethodResult code, clay::Value data) {\n            auto delegate = callback_data->delegate.lock();\n            if (!delegate || !callback_data->callback) {\n              delete callback_data;\n              return;\n            }\n\n            clay::Value::Map map;\n            map.emplace(\"code\", clay::Value(static_cast<int>(code)));\n            map.emplace(\"data\", std::move(data));\n\n            clay::Value::Array array_wrapper(1);\n            array_wrapper[0] = clay::Value(std::move(map));\n\n            callback_data->callback->SetArgs(std::make_unique<lynx::ClayValue>(\n                clay::Value(std::move(array_wrapper))));\n            delegate->InvokeCallback(callback_data->callback);\n            delete callback_data;\n          };\n    } else {\n      module_values->values.push_back(\n          lynx::ValueConverter::CreateClayValue(value));\n    }\n  });\n  if (!callback_data) {\n    FML_DLOG(ERROR) << \"callback is empty!\";\n    return std::make_unique<lynx::ClayValue>(clay::Value());\n  }\n\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runner_,\n      [weak_this = weak_from_this(), callback_data, module_values]() {\n        auto strong_this = weak_this.lock();\n        if (!strong_this) {\n          delete callback_data;\n          return;\n        }\n\n        strong_this->EnsureInvokeAfterLayout(\n            [weak_this, callback_data, module_values] {\n              auto strong_this = weak_this.lock();\n              if (!strong_this) {\n                delete callback_data;\n                return;\n              }\n              std::string view_id;\n              std::vector<std::string> nodes;\n              std::string ui_method_name;\n              clay::LynxModuleValues ui_args;\n              CastLynxModuleArgs(*module_values, view_id, nodes, ui_method_name,\n                                 ui_args);\n              strong_this->InvokeUIMethod(view_id, nodes, ui_method_name,\n                                          ui_args, callback_data->ui_callback);\n            });\n      });\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\nvoid LynxUIMethodModule::InvokeUIMethod(\n    const std::string& component_id, const std::vector<std::string>& nodes,\n    const std::string& method_name, const clay::LynxModuleValues& args,\n    const clay::LynxUIMethodCallback& callback) {\n  auto view_context =\n      clay::Isolate::Instance().GetViewContextById(view_context_id_);\n  if (!view_context) {\n    FML_DLOG(ERROR) << \"ViewContext has been released.\";\n    return;\n  }\n\n  clay::BaseView* root = view_context->FindViewByComponentId(component_id);\n  if (root == nullptr) {\n    FML_DLOG(ERROR) << \"Cannot find view with component id:\" << component_id;\n    callback(clay::LynxUIMethodResult::kNodeNotFound,\n             clay::Value(\"component not found\"));\n    return;\n  }\n  if (nodes.empty()) {\n    callback(clay::LynxUIMethodResult::kParamInvalid,\n             clay::Value(\"nodes is empty\"));\n    return;\n  }\n\n  bool is_called_by_ref =\n      clay::attribute_utils::GetBool(args.Get(kStrIsCalledByRefId), false);\n  clay::BaseView* target = root;\n  for (const auto& node_str : nodes) {\n    if (is_called_by_ref) {\n      target = clay::ViewContext::FindViewByRefIdSelector(node_str, target);\n    } else {\n      if (node_str.empty() || node_str[0] != '#') {\n        callback(\n            clay::LynxUIMethodResult::kSelectorNotSupported,\n            clay::Value(node_str +\n                        \" not support, only support id selector currently\"));\n        return;\n      }\n\n      std::string_view id_selector(node_str);\n      id_selector.remove_prefix(1);\n      target = clay::ViewContext::FindViewByIdSelector(id_selector, target);\n    }\n    if (target == nullptr) {\n      callback(clay::LynxUIMethodResult::kNodeNotFound,\n               clay::Value(\"not found \" + node_str));\n      return;\n    }\n  }\n  clay::LynxUIMethodRegistrar::Instance().Invoke(method_name, target, args,\n                                                 callback);\n}\n\nvoid LynxUIMethodModule::EnsureInvokeAfterLayout(\n    std::function<void()> invocation) {\n  auto view_context =\n      clay::Isolate::Instance().GetViewContextById(view_context_id_);\n  if (!view_context) {\n    FML_DLOG(ERROR)\n        << \"invokeUIMethod failed, view context has been destroyed.\";\n    return;\n  }\n  auto page_view = static_cast<clay::PageView*>(view_context->GetPageView());\n  if (!page_view->GetLayoutController()->HasDirtyNodes()) {\n    invocation();\n  } else {\n    // If there is a pending layout, we make the call in the next frame to\n    // ensure that the layout is updated. This is a workaround for the case\n    // of invoking UI methods on a list item in the callback of `setData`.\n    page_view->PostUIMethodTask(invocation);\n  }\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_ui_method_module.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_UI_METHOD_MODULE_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_UI_METHOD_MODULE_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/lynx_adaptor/native_module/lynx_module_base.h\"\n#include \"clay/ui/component/base_view.h\"\n\nnamespace lynx {\n\nclass LynxUIMethodModule\n    : public LynxModuleBase,\n      public std::enable_shared_from_this<LynxUIMethodModule> {\n public:\n  static std::shared_ptr<LynxNativeModule> Create(\n      uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner) {\n    return std::make_shared<LynxUIMethodModule>(view_context_id, task_runner);\n  }\n\n  static const std::string& GetName() { return name_; }\n\n  LynxUIMethodModule(uint32_t view_context_id,\n                     fml::RefPtr<fml::TaskRunner> task_runner);\n  ~LynxUIMethodModule() override;\n\n  // for compatibility with old getNodeRef\n  std::unique_ptr<lynx::pub::Value> InvokeUIMethodCompatibility(\n      std::unique_ptr<lynx::pub::Value> args,\n      const lynx::runtime::CallbackMap& callbacks);\n\n private:\n  void InvokeUIMethod(const std::string& component_id,\n                      const std::vector<std::string>& nodes,\n                      const std::string& method_name,\n                      const clay::LynxModuleValues& args,\n                      const clay::LynxUIMethodCallback& callback);\n\n  void EnsureInvokeAfterLayout(std::function<void()> invocation);\n\n  friend class LynxUIMethodRegistrar;\n\n  static const std::string name_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_UI_METHOD_MODULE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_websocket_module.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_module/lynx_websocket_module.h\"\n\n#include <random>\n#include <thread>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#if defined(_WIN32)\n#include <malloc.h>\n#include <winsock2.h>\n#include <ws2tcpip.h>\n#define CLOSESOCKET closesocket\n#define alloca _alloca\n#else\n#include <alloca.h>\n#include <netdb.h>\n#include <sys/socket.h>\n#include <sys/types.h>\n#include <unistd.h>\n#define SOCKET int\n#define CLOSESOCKET ::close\n#endif\n\n#include <openssl/ssl.h>\n\n#include <cstring>\n\n#include \"clay/fml/base64.h\"\n#include \"clay/lynx_adaptor/clay_value.h\"\n\nnamespace lynx {\n\nnamespace {\nstd::string GenerateSecWebsocketKey() {\n  // Generate 16-byte random numbers.\n  std::vector<unsigned char> random_bytes(16);\n  std::random_device rd;\n  std::mt19937 gen(rd());\n  std::uniform_int_distribution<unsigned short> dis(0, 255);\n  for (auto& byte : random_bytes) {\n    byte = static_cast<unsigned char>(dis(gen));\n  }\n  // Encode with base64.\n  size_t length =\n      ::fml::Base64::Encode(random_bytes.data(), random_bytes.size(), nullptr);\n  std::vector<char> base64_data(length);\n  ::fml::Base64::Encode(random_bytes.data(), random_bytes.size(),\n                        base64_data.data());\n  std::string key(base64_data.begin(), base64_data.end());\n  return key;\n}\n}  // namespace\n\nclass SimpleWebSocket {\n public:\n  SimpleWebSocket(std::weak_ptr<shell::LynxRuntimeProxy> runtime_proxy,\n                  const std::string& url, std::vector<std::string> protocols,\n                  std::unordered_map<std::string, std::string> headers, int id)\n      : id_(id),\n        url_(url),\n        protocols_(std::move(protocols)),\n        headers_(std::move(headers)),\n        runtime_proxy_(runtime_proxy) {\n    thread_ = std::make_unique<std::thread>([this]() { run(); });\n  }\n  ~SimpleWebSocket() { Close(0, \"\"); }\n\n  void Close(double code, const std::string& reason) {\n    code_ = code;\n    reason_ = reason;\n\n    if (socket_) {\n      CLOSESOCKET(socket_);\n    }\n    if (ssl_) {\n      SSL_shutdown(ssl_);\n    }\n    if (thread_) {\n      if (thread_->joinable()) {\n        thread_->join();\n      }\n      thread_.reset();\n    }\n    if (ssl_) {\n      SSL_free(ssl_);\n      ssl_ = nullptr;\n    }\n    socket_ = 0;\n  }\n\n  void Ping() {\n    if (!socket_) {\n      return;\n    }\n    uint8_t txb[16];\n    size_t txb_len = 2;\n\n    txb[0] = 9 /*OP_PING*/ | 0x80 /*FIN*/;\n    txb[1] = 0;\n\n    // All frames sent from client to server have this bit set to 1.\n    txb[1] |= 0x80 /*MASK*/;\n    *reinterpret_cast<uint32_t*>(txb + txb_len) = 0;\n    txb_len += 4;\n\n    do_write((char*)txb, txb_len);\n  }\n\n  void Write(const std::string& data) {\n    if (!socket_) {\n      return;\n    }\n    const char* payload = data.data();\n    size_t payload_len = data.size();\n    uint8_t* txb = (uint8_t*)alloca(16 + payload_len);\n    size_t txb_len = 2;\n\n    txb[0] = 1 /*OP_TEXT*/ | 0x80 /*FIN*/;\n\n    if (payload_len > 65535) {\n      txb[1] = 127;\n      *reinterpret_cast<uint32_t*>(txb + 2) = 0;\n      txb[6] = payload_len >> 24;\n      txb[7] = payload_len >> 16;\n      txb[8] = payload_len >> 8;\n      txb[9] = payload_len;\n      txb_len += 8;\n    } else if (payload_len > 125) {\n      txb[1] = 126;\n      txb[2] = payload_len >> 8;\n      txb[3] = payload_len;\n      txb_len += 2;\n    } else {\n      txb[1] = payload_len;\n    }\n\n    // All frames sent from client to server have this bit set to 1.\n    txb[1] |= 0x80 /*MASK*/;\n    *reinterpret_cast<uint32_t*>(txb + txb_len) = 0;\n    txb_len += 4;\n\n    memcpy(txb + txb_len, payload, payload_len);\n    txb_len += payload_len;\n\n    do_write((char*)txb, txb_len);\n  }\n\n private:\n  void emit(const std::string& event_name, clay::Value::Map map) {\n    if (auto proxy = runtime_proxy_.lock()) {\n      clay::Value::Array array_wrapper(2);\n      array_wrapper[0] = clay::Value(event_name);\n      clay::Value::Array array_args(1);\n      array_args[0] = clay::Value(std::move(map));\n      array_wrapper[1] = clay::Value(std::move(array_args));\n\n      auto params = clay::Value(std::move(array_wrapper));\n      proxy->CallJSFunction(\n          \"GlobalEventEmitter\", \"emit\",\n          std::make_unique<lynx::ClayValue>(std::move(params)));\n    }\n  }\n\n  void run() {\n    if (!Connect()) {\n      clay::Value::Map map;\n      map[\"id\"] = clay::Value(id_);\n      map[\"message\"] = clay::Value(\"\");\n      emit(\"websocketFailed\", std::move(map));\n      return;\n    }\n\n    {\n      clay::Value::Map map;\n      map[\"id\"] = clay::Value(id_);\n      map[\"protocol\"] = clay::Value(protocol_);\n      emit(\"websocketOpen\", std::move(map));\n    }\n\n    uint8_t opcode;\n    std::string payload;\n    while (Read(opcode, payload)) {\n      if (opcode == 0x1) {  // OP_TEXT\n        clay::Value::Map map;\n        map[\"id\"] = clay::Value(id_);\n        map[\"type\"] = clay::Value(\"text\");\n        map[\"data\"] = clay::Value(payload);\n        emit(\"websocketMessage\", std::move(map));\n      }\n    }\n\n    {\n      clay::Value::Map map;\n      map[\"id\"] = clay::Value(id_);\n      map[\"code\"] = clay::Value(code_);\n      map[\"reason\"] = clay::Value(reason_);\n      emit(\"websocketClosed\", std::move(map));\n    }\n  }\n\n  bool Connect() {\n    const char* purl = url_.c_str();\n    int port = 80;\n    bool wss = false;\n    if (memcmp(purl, \"wss://\", 6) == 0) {\n      port = 443;\n      wss = true;\n      purl += 6;\n    } else if (memcmp(purl, \"ws://\", 5) == 0) {\n      purl += 5;\n    } else {\n      return false;\n    }\n\n    char host[256] = {0};\n    char path[4000] = {0};\n    if (sscanf(purl, \"%[^:/]:%d/%s\", host, &port, path) == 3) {\n    } else if (sscanf(purl, \"%[^:/]/%s\", host, path) == 2) {\n    } else if (sscanf(purl, \"%[^:/]:%d\", host, &port) == 2) {\n    } else if (sscanf(purl, \"%[^:/]\", host) == 1) {\n    } else {\n      return false;\n    }\n\n    struct addrinfo ai, *sai;\n    memset(&ai, 0, sizeof ai);\n    ai.ai_family = AF_INET;\n    ai.ai_socktype = SOCK_STREAM;\n    char str_port[16];\n    snprintf(str_port, sizeof(str_port), \"%d\", port);\n    int ret = ::getaddrinfo(host, str_port, &ai, &sai);\n    if (ret != 0) {\n      return false;\n    }\n\n    for (auto p = sai; p != NULL; p = p->ai_next) {\n      auto sockfd = ::socket(p->ai_family, p->ai_socktype, p->ai_protocol);\n      if (sockfd == -1) {\n        continue;\n      }\n      if (::connect(sockfd, p->ai_addr, p->ai_addrlen) != -1) {\n        socket_ = sockfd;\n        break;\n      }\n      CLOSESOCKET(sockfd);\n    }\n    ::freeaddrinfo(sai);\n\n    if (socket_ == 0) {\n      return false;\n    }\n\n    if (wss) {\n      SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());\n      SSL* ssl = SSL_new(ctx);\n      SSL_set_fd(ssl, socket_);\n\n      if (SSL_connect(ssl) <= 0) {\n        ERR_print_errors_fp(stderr);\n        SSL_CTX_free(ctx);\n        CLOSESOCKET(socket_);\n        socket_ = 0;\n        return false;\n      }\n      SSL_CTX_free(ctx);\n      ssl_ = ssl;\n    }\n\n    std::string protocols_str;\n    for (const auto& proto : protocols_) {\n      if (!protocols_str.empty()) {\n        protocols_str += ',';\n      }\n      protocols_str += proto;\n    }\n    if (!protocols_str.empty()) {\n      headers_[\"Sec-WebSocket-Protocol\"] = protocols_str;\n    }\n\n    std::string headers_str;\n    for (const auto& [key, val] : headers_) {\n      headers_str += key;\n      headers_str += \": \";\n      headers_str += val;\n      headers_str += \"\\r\\n\";\n    }\n\n    char buf[512 + strlen(path) + strlen(host) + headers_str.size()];\n    // Generate `Sec-WebSocket-Key` using random numbers.\n    std::string key = GenerateSecWebsocketKey();\n    snprintf(buf, sizeof(buf),\n             \"GET /%s HTTP/1.1\\r\\n\"\n             \"Host: %s:%d\\r\\n\"\n             \"Upgrade: websocket\\r\\n\"\n             \"Connection: Upgrade\\r\\n\"\n             \"Sec-WebSocket-Key: %s\\r\\n\"\n             \"Sec-WebSocket-Version: 13\\r\\n\"\n             \"%s\\r\\n\",\n             path, host, port, key.c_str(), headers_str.c_str());\n    do_write(buf, strlen(buf));\n\n    int status;\n    if (read_line(buf, sizeof(buf)) < 10 ||\n        sscanf(buf, \"HTTP/1.1 %d Switching Protocols\\r\\n\", &status) != 1 ||\n        status != 101) {\n      return false;\n    }\n\n    while (read_line(buf, sizeof(buf)) > 0 && buf[0] != '\\r') {\n      auto len = strlen(buf);\n      buf[len - 2] = '\\0';  // '\\r\\n' -> '\\0\\n'\n      if (memcmp(buf, \"Sec-WebSocket-Protocol: \", 24) == 0) {\n        protocol_ = &buf[24];\n      }\n      // Prefer to validate handshake with `Sec-WebSocket-Accept` from server\n      // here.\n    }\n    return true;\n  }\n\n  bool Read(uint8_t& opcode, std::string& payload) {\n    struct {\n      uint8_t flag_opcode;\n      uint8_t mask_payload_len;\n    } head;\n\n    if (do_read((char*)&head, sizeof(head)) != sizeof(head)) {\n      return false;\n    }\n    if ((head.flag_opcode & 0x80) == 0) {  // FIN\n      return false;\n    }\n    uint8_t flags = head.flag_opcode >> 4;\n    if ((head.mask_payload_len & 0x80) != 0) {  // masked payload\n      return false;\n    }\n    size_t payload_len = head.mask_payload_len & 0x7f;\n    bool deflated = (flags & 4 /*FLAG_RSV1*/) != 0;\n    if (deflated) {\n      return false;\n    }\n\n    if (payload_len == 126) {\n      uint8_t len[2];\n      do_read((char*)&len, sizeof(len));\n      payload_len = (len[0] << 8) | len[1];\n    } else if (payload_len == 127) {\n      uint8_t len[8];\n      do_read((char*)&len, sizeof(len));\n      payload_len = (len[4] << 24) | (len[5] << 16) | (len[6] << 8) | len[7];\n    }\n\n    opcode = head.flag_opcode & 0x0F;\n    if (payload_len == 0) {\n      return true;\n    }\n\n    payload.resize(payload_len);\n\n    if (do_read(payload.data(), payload_len) != payload_len) {\n      return false;\n    }\n    return true;\n  }\n\n  int read_line(char* buf, size_t size) {\n    char* out = buf;\n    while (out - buf < size) {\n      int res = do_read(out, 1);\n      if (res == 1) {\n        if (*out++ == '\\n') {\n          break;\n        }\n      } else if (res == -1) {\n        // printf(\"recv errr: %d\\n\", errno);\n        break;\n      }\n    }\n    *out = '\\0';\n    // printf(\"RX: %s [%d bytes]\\n\", buf, int(out - buf));\n    return out - buf;\n  }\n\n  int do_read(char* buf, size_t size) {\n    if (ssl_) {\n      return SSL_read(ssl_, buf, size);\n    }\n    return ::recv(socket_, buf, size, 0);\n  }\n\n  int do_write(char* buf, size_t size) {\n    // printf(\"TX: %s [%d bytes]\\n\", buf, int(size));\n    if (ssl_) {\n      return SSL_write(ssl_, buf, size);\n    }\n    return ::send(socket_, buf, size, 0);\n  }\n\n  int id_;\n  SOCKET socket_ = 0;\n  SSL* ssl_ = nullptr;\n  std::string url_;\n  std::string protocol_;\n  std::vector<std::string> protocols_;\n  std::unordered_map<std::string, std::string> headers_;\n  std::unique_ptr<std::thread> thread_;\n  std::weak_ptr<shell::LynxRuntimeProxy> runtime_proxy_;\n  double code_;\n  std::string reason_;\n};\n\nconst std::string LynxWebSocketModule::name_ = \"LynxWebSocketModule\";\n\nLynxWebSocketModule::LynxWebSocketModule(\n    uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner)\n    : LynxModuleBase(view_context_id, task_runner) {\n  runtime::NativeModuleMethod connect_method(\"connect\", 4);\n  RegisterMethod(connect_method, &LynxWebSocketModule::connect);\n\n  runtime::NativeModuleMethod send_method(\"send\", 2);\n  RegisterMethod(send_method, &LynxWebSocketModule::send);\n\n  runtime::NativeModuleMethod ping_method(\"ping\", 1);\n  RegisterMethod(ping_method, &LynxWebSocketModule::ping);\n\n  runtime::NativeModuleMethod close_method(\"close\", 3);\n  RegisterMethod(close_method, &LynxWebSocketModule::close);\n}\n\nLynxWebSocketModule::~LynxWebSocketModule() {\n  for (const auto& [id, ws] : sockets_) {\n    delete ws;\n  }\n}\n\nstd::unique_ptr<pub::Value> LynxWebSocketModule::connect(\n    std::unique_ptr<pub::Value> args, const runtime::CallbackMap& callbacks) {\n  std::string url = args->GetValueAtIndex(0)->str();\n  auto protocols_array = args->GetValueAtIndex(1);\n  auto options_object = args->GetValueAtIndex(2);\n  int id = static_cast<int>(args->GetValueAtIndex(3)->Number());\n  std::vector<std::string> protocols;\n  std::unordered_map<std::string, std::string> headers;\n\n  if (protocols_array->IsArray()) {\n    protocols_array->ForeachArray(\n        [&protocols](int64_t idx, const pub::Value& val) {\n          protocols.push_back(val.str());\n        });\n  }\n  if (options_object->IsMap()) {\n    auto headers_object = options_object->GetValueForKey(\"headers\");\n    if (headers_object->IsMap()) {\n      headers_object->ForeachMap(\n          [&headers](const pub::Value& key, const pub::Value& val) {\n            headers[key.str()] = val.str();\n          });\n    }\n  }\n  auto ws = new SimpleWebSocket(runtime_proxy_, url, std::move(protocols),\n                                std::move(headers), id);\n  if (auto search = sockets_.find(id); search != sockets_.end()) {\n    delete search->second;\n  }\n  sockets_[id] = ws;\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n};\n\nstd::unique_ptr<pub::Value> LynxWebSocketModule::send(\n    std::unique_ptr<pub::Value> args, const runtime::CallbackMap& callbacks) {\n  std::string msg = args->GetValueAtIndex(0)->str();\n  int id = static_cast<int>(args->GetValueAtIndex(1)->Number());\n  if (auto search = sockets_.find(id); search != sockets_.end()) {\n    search->second->Write(msg);\n  }\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\nstd::unique_ptr<pub::Value> LynxWebSocketModule::ping(\n    std::unique_ptr<pub::Value> args, const runtime::CallbackMap& callbacks) {\n  int id = static_cast<int>(args->GetValueAtIndex(0)->Number());\n  if (auto search = sockets_.find(id); search != sockets_.end()) {\n    search->second->Ping();\n  }\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\nstd::unique_ptr<pub::Value> LynxWebSocketModule::close(\n    std::unique_ptr<pub::Value> args, const runtime::CallbackMap& callbacks) {\n  auto code = args->GetValueAtIndex(0)->Number();\n  auto reason = args->GetValueAtIndex(1)->str();\n  int id = static_cast<int>(args->GetValueAtIndex(2)->Number());\n  if (auto search = sockets_.find(id); search != sockets_.end()) {\n    search->second->Close(code, reason);\n    delete search->second;\n    sockets_.erase(search);\n  }\n  return std::make_unique<lynx::ClayValue>(clay::Value(true));\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/lynx_websocket_module.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_WEBSOCKET_MODULE_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_WEBSOCKET_MODULE_H_\n\n#include <memory>\n#include <mutex>\n#include <string>\n#include <unordered_map>\n\n#include \"clay/lynx_adaptor/native_module/lynx_module_base.h\"\n\nnamespace lynx {\n\nclass SimpleWebSocket;\n\nclass LynxWebSocketModule\n    : public LynxModuleBase,\n      public std::enable_shared_from_this<LynxWebSocketModule> {\n public:\n  static std::shared_ptr<LynxNativeModule> Create(\n      uint32_t view_context_id, fml::RefPtr<fml::TaskRunner> task_runner) {\n    return std::make_shared<LynxWebSocketModule>(view_context_id, task_runner);\n  }\n\n  static const std::string& GetName() { return name_; }\n\n  LynxWebSocketModule(uint32_t view_context_id,\n                      fml::RefPtr<fml::TaskRunner> task_runner);\n  ~LynxWebSocketModule() override;\n\n  std::unique_ptr<pub::Value> connect(std::unique_ptr<pub::Value> args,\n                                      const runtime::CallbackMap& callbacks);\n\n  std::unique_ptr<pub::Value> send(std::unique_ptr<pub::Value> args,\n                                   const runtime::CallbackMap& callbacks);\n\n  std::unique_ptr<pub::Value> ping(std::unique_ptr<pub::Value> args,\n                                   const runtime::CallbackMap& callbacks);\n\n  std::unique_ptr<pub::Value> close(std::unique_ptr<pub::Value> args,\n                                    const runtime::CallbackMap& callbacks);\n\n private:\n  static const std::string name_;\n  std::unordered_map<int, SimpleWebSocket*> sockets_;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_MODULE_LYNX_WEBSOCKET_MODULE_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_module/spec/lynx_ui_method.spec.md",
    "content": "# Lynx UI Method Invocation & Targeting Spec\n\n> Scope: This document describes how Lynx UI methods (`invokeUIMethod`) locate target nodes and what the cross-platform call chains look like. It covers code under `lynx/`, `platform/`, and `oss/clay/`.\n\n---\n\n## 1. Terminology and ID Types\n\nAcross layers (DOM / TASM / platform UI tree), \"finding a node\" involves different IDs:\n\n- **`component_id` (string)**: Component instance id used as the query root (component subtree).\n  - JS source: `NodeSelectToken.component_id` (`SelectorQuery`) or `NodeRef._rootComponentId` (`NodeRef`).\n- **`element_id` / `unique_id` (number / string)**: Unique element id (often seen as `uid` in event callbacks). It can be used to:\n  - Set the search root (`root_unique_id`, higher priority than `component_id`)\n  - Select by id (`IdentifierType.UNIQUE_ID` / `NodeSelectOptions::ELEMENT_ID`)\n  - References: `lynx/js_libraries/lynx-core/src/modules/selectorQuery/interface.ts`, `lynx/core/renderer/dom/vdom/radon/node_select_options.h`\n- **`ui_impl_id` / `sign` (int32)**: The \"handle/index\" of a node in the platform UI tree (UI method execution typically lands on this).\n  - `SelectorQuery/nativeApp.invokeUIMethod` selects nodes first and then converts them to `ui_impl_id`.\n  - The Lepus RenderFunction path can call directly with `ui_impl_id`.\n  - References: `lynx/core/renderer/page_proxy.cc`, `lynx/core/runtime/bindings/lepus/renderer_functions.cc`\n- **`idSelector` (string)**: Platform UI-tree attribute (JS usually writes `#id` in NodeRef id mode, while the stored value is typically `id` without `#`).\n  - Android: `LynxBaseUI.setIdSelector` (`lynx/platform/android/.../LynxBaseUI.java`)\n  - iOS: `LynxUI.idSelector` (`lynx/platform/darwin/ios/lynx/ui/...`)\n  - Harmony: `UIBase::id_selector_` (`lynx/platform/harmony/lynx_harmony/src/main/cpp/ui/ui_base.cc`)\n  - Clay: `clay::BaseView::id_selector_` (`oss/clay/ui/component/base_view.cc`)\n- **`react-ref` (string)**: ReactLynx ref identifier. When `_isCallByRefId` is true, NodeRef uses this as the selector.\n  - Android prop name: `PropsConstants.REACT_REF_ID = \"react-ref\"` (`lynx/platform/android/.../PropsConstants.java`)\n  - iOS: `LynxUI.refId`\n  - Harmony: `UIBase::react_ref_id_` (`lynx/platform/harmony/lynx_harmony/src/main/cpp/ui/ui_base.cc`)\n  - Clay: `BaseView::ref_id_selector_` (`oss/clay/ui/component/base_view.*`)\n\n---\n\n## 2. Targeting Modes (How the Target Node Is Located)\n\n`invokeUIMethod` ultimately needs to land on a single UI node. There are three major targeting modes:\n\n### 2.1 Direct `ui_impl_id/sign` (Direct UI Handle)\n\n- Typical entry: **Lepus RenderFunction `InvokeUIMethod`**\n  - Accepts element id array or a `FiberElement`, and ends up using `impl_id()` / `ui_impl_id`.\n  - References: `lynx/core/runtime/bindings/lepus/renderer_functions.cc`, `lynx/core/renderer/template_assembler.cc`\n\nUse case: internal renderer/runtime already has the target handle; shortest path.\n\n### 2.2 `SelectorQuery` (DOM Selector -> `ui_impl_id` -> UI Invoke)\n\nOn the JS side, `SelectorQuery` calls `nativeApp.invokeUIMethod(...)`:\n\n- **Identifier type** (JS: `lynx/js_libraries/lynx-core/src/modules/selectorQuery/interface.ts`)\n  - `ID_SELECTOR`: DOM CSS selector\n  - `REF_ID`: match by `react-ref` attribute\n  - `UNIQUE_ID`: match by `element_id` (`uid` in event callbacks)\n- **Root selection**\n  - `component_id`: search within the component subtree\n  - `root_unique_id`: use an element unique id as root (**higher priority** than `component_id`)\n  - References: comment on `NodeSelectToken.root_unique_id`, and `NodeSelectRoot` in `lynx/core/renderer/dom/vdom/radon/node_select_options.h`\n\nJS/C++ identifier mapping (numeric values are aligned):\n\n| JS (`IdentifierType`) | C++ (`NodeSelectOptions::IdentifierType`) | Meaning | Typical `identifier` |\n| --- | --- | --- | --- |\n| `ID_SELECTOR` (0) | `CSS_SELECTOR` (0) | DOM CSS selector | `\"#video\"` / `\".cls\"` |\n| `REF_ID` (1) | `REF_ID` (1) | React ref (`react-ref`) | `\"myRef\"` |\n| `UNIQUE_ID` (2) | `ELEMENT_ID` (2) | element unique id (`uid/element_id`) | `\"12345\"` |\n\nUse case: standard querying and event-callback targeting; best cross-platform consistency.\n\n### 2.3 `NodeRef` (Platform UI-Tree Traversal, Legacy getNodeRef Compatibility)\n\nJS `NodeRef.invoke(...)` calls `nativeLynxUIModule.invokeUIMethod(...)` (it does **not** go through DOM selector):\n\n- Signature: `(componentId, nodes: string[], method, params, callback)`\n  - `nodes` is a chained path: each segment searches inside the current node's subtree.\n    - Most platform implementations behave like **DFS-first-match per segment**: for each segment, it searches the whole subtree (not necessarily direct children), picks the first match, and then uses it as the next segment's root.\n  - `_isCallByRefId` switches the meaning of `nodes`:\n    - **`false/undefined`**: `nodes` is a `#id` path (id selector only)\n    - **`true`**: `nodes` is a `react-ref` path (refId)\n  - Reference: `lynx/js_libraries/lynx-core/src/modules/selectorQuery/nodeRef.ts`\n\nUse case: legacy getNodeRef / historical code paths; capabilities are platform-dependent (see §4.2).\n\n---\n\n## 3. Call Chains (By Entry)\n\n### 3.1 JS: `SelectorQuery` -> `nativeApp.invokeUIMethod` (Main Path)\n\n1. JS builds a token and commits a task\n   - `SelectorQuery.select/selectAll/selectReactRef/selectUniqueID` builds `NodeSelectToken`\n   - `NodesRef.invoke()` calls `nativeApp.invokeUIMethod(...)`\n   - Reference: `lynx/js_libraries/lynx-core/src/modules/selectorQuery/SelectorQuery.ts`\n\n2. JSI host parses args into `NodeSelectRoot` + `NodeSelectOptions`\n   - host function `invokeUIMethod` -> `App::InvokeUIMethod(...)`\n   - Reference: `lynx/core/runtime/bindings/jsi/js_app.cc`\n\n3. Engine selects nodes and then invokes UI method\n   - `RuntimeMediator::InvokeUIMethod` -> `LynxEngine::InvokeUIMethod`\n   - `page_proxy()->GetLynxUI(root, options)` selects the target node(s) and produces `ui_impl_id`\n   - References: `lynx/core/shell/runtime_mediator.cc`, `lynx/core/shell/lynx_engine.cc`, `lynx/core/renderer/page_proxy.cc`\n\n4. Branch: PaintingContext vs NativeFacade (controlled by `use_invoke_ui_method_func_`)\n   - `TasmMediator::InvokeUIMethod`:\n     - if `invoke_ui_method_func_` is injected: **PaintingContext branch**\n     - else: **NativeFacade branch**\n   - Injection is configured by `LynxShellBuilder::SetUseInvokeUIMethodFunction(true)` which binds `PaintingContext::InvokeUIMethod(ui_impl_id, ...)` into `TasmMediator`.\n   - References: `lynx/core/shell/tasm_mediator.cc`, `lynx/core/shell/lynx_shell_builder.cc`\n\n5. Platform sinks (illustrative)\n   - PaintingContext branch: `PaintingContext*::InvokeUIMethod(...)`\n     - Android: `lynx/core/renderer/ui_wrapper/painting/android/painting_context_android.cc`\n     - iOS: `lynx/core/renderer/ui_wrapper/painting/ios/painting_context_darwin.mm`\n     - Harmony: `lynx/core/renderer/ui_wrapper/painting/harmony/painting_context_harmony.cc`\n     - Clay (Desktop): `oss/clay/lynx_adaptor/painting_context_clay.cc`\n   - NativeFacade branch: `NativeFacade*::InvokeUIMethod(...)`\n     - Android: `lynx/core/shell/android/native_facade_android.cc`\n     - iOS: `lynx/core/shell/ios/native_facade_darwin.mm`\n     - Harmony: `lynx/core/shell/harmony/native_facade_harmony.cc`\n     - Embedder: `lynx/platform/embedder/core/native_facade_impl.cc`\n\n### 3.2 JS: `NodeRef.invoke` -> `nativeLynxUIModule.invokeUIMethod` (Compat Path)\n\n1. JS directly calls the native module\n   - Reference: `lynx/js_libraries/lynx-core/src/modules/selectorQuery/nodeRef.ts`\n\n2. Platform module: `LynxUIMethodModule.invokeUIMethod` (platform-specific)\n   - Android (Java): `lynx/platform/android/lynx_android/src/main/java/com/lynx/jsbridge/LynxUIMethodModule.java`\n   - iOS (ObjC): `lynx/platform/darwin/ios/lynx/module/LynxUIMethodModule.mm`\n   - Clay (C++): `oss/clay/lynx_adaptor/native_module/lynx_ui_method_module.cc`\n   - Harmony (C++ CAPI): `lynx/platform/harmony/lynx_harmony/src/main/cpp/module/lynx_ui_method_module.cc`\n\n3. Traverse the platform UI tree and invoke the method\n   - Android: `LynxUIOwner.invokeUIMethod(componentId, nodes, ...)`\n     - Reference: `lynx/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/LynxUIOwner.java`\n   - iOS: `LynxUIOwner invokeUIMethod:params:callback:fromRoot:toNodes:`\n     - Reference: `lynx/platform/darwin/ios/lynx/ui/LynxUIOwner.m`\n   - Harmony: `UIOwner::InvokeUIMethod(component_id, node, ...)` -> `UIBase::FindViewById(node, by_ref_id)`\n     - References: `lynx/platform/harmony/lynx_harmony/src/main/cpp/ui/ui_owner.cc`, `lynx/platform/harmony/lynx_harmony/src/main/cpp/ui/ui_base.cc`\n   - Clay: `ViewContext::FindViewByComponentId` + `FindViewByIdSelector/FindViewByRefIdSelector` + registrar invoke\n     - References: `oss/clay/ui/component/view_context.cc`, `oss/clay/ui/component/base_view.cc`, `oss/clay/lynx_adaptor/native_module/lynx_ui_method_module.cc`\n\n### 3.3 Lepus: RenderFunction `InvokeUIMethod` (Direct `ui_impl_id`)\n\n1. Lepus binding parses element ids / `FiberElement.impl_id()`\n   - Reference: `lynx/core/runtime/bindings/lepus/renderer_functions.cc`\n\n2. `TemplateAssembler::LepusInvokeUIMethod(...)` -> `Catalyzer::Invoke(ui_impl_id, ...)`\n   - References: `lynx/core/renderer/template_assembler.cc`, `lynx/core/renderer/ui_wrapper/painting/catalyzer.cc`\n\n3. `PaintingContext::Invoke(...)` -> platform `PaintingContext*::Invoke(...)`\n   - Reference: `lynx/core/renderer/ui_wrapper/painting/painting_context.h` and per-platform implementations\n\n---\n\n## 4. Platform Differences\n\n### 4.1 `use_invoke_ui_method_func_` (Affects Call Chains That Go Through `TasmMediator::InvokeUIMethod`)\n\nThis switch determines whether `SelectorQuery/nativeApp.invokeUIMethod` ends up in PaintingContext or NativeFacade:\n\n- **Embedder (macOS/Windows/Linux)**: always enabled (PaintingContext)\n  - `lynx/platform/embedder/core/lynx_template_renderer.cc`: `.SetUseInvokeUIMethodFunction(true)`\n- **Harmony**: always enabled (PaintingContext)\n  - `lynx/platform/harmony/lynx_harmony/src/main/cpp/lynx_template_renderer.cc`: `.SetUseInvokeUIMethodFunction(true)`\n- **Android**: decided by `ILynxUIRenderer.useInvokeUIMethod()`\n  - JNI builder: `lynx/core/shell/android/lynx_template_render_android.cc`\n  - Default `LynxUIRenderer` returns `false` (NativeFacade): `lynx/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/LynxUIRenderer.java`\n  - `LynxUIRendererClay` returns `true` (PaintingContext): `platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/LynxUIRendererClay.java`\n- **iOS**: decided by `LynxUIRendererProtocol.useInvokeUIMethodFunction`\n  - builder: `lynx/platform/darwin/ios/lynx/LynxTemplateRenderHelper.mm`\n  - Default `LynxUIRenderer` returns `NO` (NativeFacade): `lynx/platform/darwin/ios/lynx/LynxUIRenderer.mm`\n  - `LynxUIRendererClay` returns `YES` (PaintingContext): `platform/darwin/ios/lynx/clay/LynxUIRendererClay.mm`\n\n> Note: `NodeRef.invoke` goes through `LynxUIMethodModule` and is not affected by this switch.\n\n### 4.2 `NodeRef.invoke` Capability Differences (Compat Path)\n\nEven though the JS entry is the same, platform implementations differ:\n\n- **Android (Java UI tree)**: supports chained `nodes[]`; supports `_isCallByRefId` (match by `react-ref`); id mode requires `#...`; traversal skips `component` subtrees.\n  - Traversal: `lynx/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/LynxUIOwner.java`\n  - Storage: `lynx/platform/android/lynx_android/src/main/java/com/lynx/tasm/behavior/ui/LynxBaseUI.java` (`setRefIdSelector`)\n- **iOS (ObjC UI tree)**: supports chained `nodes[]`; supports `_isCallByRefId` (match by `refId/react-ref`); traversal skips `LynxUIComponent` subtrees.\n  - Traversal: `lynx/platform/darwin/ios/lynx/ui/LynxUIOwner.m`\n- **Clay (C++ UI tree; Desktop embedder shares this implementation)**: supports chained `nodes[]`; supports `_isCallByRefId` (match by `react-ref`).\n  - Module: `oss/clay/lynx_adaptor/native_module/lynx_ui_method_module.cc`\n  - Registration: `clay/lynx_adaptor/desktop_embedder.cc` -> `oss/clay/lynx_adaptor/native_module/lynx_module_factory.cc`\n  - Lookup: `oss/clay/ui/component/view_context.cc`\n    - Note: current lookup is full-subtree DFS and does **not** skip component subtrees, so NodeRef may \"cross component boundaries\" compared to Android/iOS/Harmony.\n  - Empty `nodes[]` returns `kParamInvalid`; unlike Android/iOS, Clay does not fall through to invoking the method on the root UI.\n  - Extra behavior: `EnsureInvokeAfterLayout` defers invocation by one frame when there are dirty layout nodes.\n- **Harmony (C++ CAPI)**: only uses `nodes[0]` (no chained `nodes[]`); supports `_isCallByRefId` (match by `react-ref`); traversal skips component subtrees.\n  - Module: `lynx/platform/harmony/lynx_harmony/src/main/cpp/module/lynx_ui_method_module.cc` (strips leading `#` if present)\n  - Lookup: `lynx/platform/harmony/lynx_harmony/src/main/cpp/ui/ui_owner.cc` -> `lynx/platform/harmony/lynx_harmony/src/main/cpp/ui/ui_base.cc`\n\n---\n\n## 5. Debugging Shortcuts\n\n- To understand **how nodes are selected** (SelectorQuery): start at `LynxEngine::InvokeUIMethod` -> `PageProxy::GetLynxUI`\n  - `lynx/core/shell/lynx_engine.cc`, `lynx/core/renderer/page_proxy.cc`\n- To determine whether **PaintingContext or NativeFacade** is used: check whether `TasmMediator::InvokeUIMethod` has `invoke_ui_method_func_` installed\n  - `lynx/core/shell/tasm_mediator.cc`, `lynx/core/shell/lynx_shell_builder.cc`\n- When **NodeRef behaves differently from SelectorQuery**: confirm you are on the `LynxUIMethodModule` compat path and whether the platform supports chaining / `_isCallByRefId`\n  - JS entry: `lynx/js_libraries/lynx-core/src/modules/selectorQuery/nodeRef.ts`\n"
  },
  {
    "path": "clay/lynx_adaptor/native_platform_view.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_platform_view.h\"\n\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n#include \"clay/lynx_adaptor/value_converter.h\"\n#include \"clay/ui/component/native_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"platform/embedder/public/lynx_value.h\"\n\nnamespace clay {\n\nnamespace {\n\nSharedImageSink::BufferMode convert_buffer_mode(\n    ClaySharedImageSinkBufferMode clay_buffer_mode) {\n  switch (clay_buffer_mode) {\n    case kClaySharedImageSinkBufferModeSingleBuffer:\n      return SharedImageSink::kSingleBuffer;\n    case kClaySharedImageSinkBufferModeDoubleBuffer:\n      return SharedImageSink::kDoubleBuffer;\n    case kClaySharedImageSinkBufferModeTripleBuffer:\n    default:\n      return SharedImageSink::kTripleBuffer;\n  }\n}\n\n}  // namespace\n\nNativePlatformView::~NativePlatformView() {\n  if (sink_ref_) {\n    ClayReleaseSharedImageSink(sink_ref_);\n  }\n}\n\nbool NativePlatformView::OnCreate() { return false; }\nvoid NativePlatformView::OnAttach() {}\nvoid NativePlatformView::OnDetach() {}\nvoid NativePlatformView::OnDestroy() {}\n\nvoid NativePlatformView::OnLayoutChanged(float left, float top, float width,\n                                         float height, float pixel_ratio) {}\n\nvoid NativePlatformView::OnPropertiesChanged(lynx_value attrs,\n                                             lynx_value events) {}\n\nvoid NativePlatformView::OnMouseClickEvent(int x, int y, int buttons,\n                                           bool mouse_up) {}\nvoid NativePlatformView::OnMouseMoveEvent(int x, int y, int modifiers,\n                                          bool mouse_leave) {}\nvoid NativePlatformView::OnMouseWheelEvent(int x, int y, int modifiers,\n                                           double delta_x, double delta_y) {}\nvoid NativePlatformView::OnFocusChanged(bool focused, bool is_leaf) {}\nvoid NativePlatformView::OnMethodInvoked(\n    const std::string& method, lynx_value attrs,\n    std::function<void(int code, lynx_value data)> callback) {\n  callback(kMethodNotFound, {.val_ptr = 0, .type = lynx_value_undefined});\n}\n\nvoid NativePlatformView::TriggerEvent(const char* name,\n                                      lynx::pub::LynxValue&& params) {\n  lynx_value_add_reference(nullptr, params.Value(), nullptr);\n  TriggerEvent(name, params.Value());\n}\n\nvoid NativePlatformView::TriggerEvent(const char* name, lynx_value params) {\n  lynx_api_env env = nullptr;\n  auto m =\n      params.type == lynx_value_map\n          ? std::move(\n                lynx::ValueConverter::CreateClayValue(env, params).GetMap())\n          : clay::Value::Map();\n  lynx_value_remove_reference(env, params, nullptr);\n  native_view_->page_view()->SendCustomEvent(native_view_->id(), name,\n                                             std::move(m));\n}\n\nClaySharedImageSinkRef NativePlatformView::GetSharedImageSink() {\n  if (!NeedSharedImageSink()) {\n    return nullptr;\n  }\n  if (!sink_ref_) {\n    sink_ref_ = ClayCreateSharedImageSink(buffer_mode(), backing_type(),\n                                          pixel_format());\n  }\n  return sink_ref_;\n}\n\nbool NativePlatformView::PresentSurface(\n    int width, int height, const ClayTransformation* transform,\n    ClaySharedImageNativeHandle gfx_handle) {\n  if (!sink_ref_) {\n    return false;\n  }\n  FML_DCHECK(gfx_handle);\n  SharedImageSink* sink = reinterpret_cast<SharedImageSink*>(sink_ref_);\n  auto [backing, buffer_age, status] =\n      sink->TryAcquireBack({width, height}, gfx_handle);\n  if (!backing) {\n    return false;\n  }\n\n  if (transform) {\n    skity::Matrix mat =\n        skity::Matrix(transform->scaleX, transform->skewX, transform->transX,\n                      transform->skewY, transform->scaleY, transform->transY,\n                      transform->pers0, transform->pers1, transform->pers2);\n    backing->SetTransformation(mat);\n  }\n\n  auto fence_sync = backing->GetFenceSync();\n  if (fence_sync) {\n    fence_sync->ClientWait();\n  }\n  if (!sink->SwapBack(nullptr)) {\n    FML_LOG(ERROR) << \"Failed to swap back shared image sink\";\n    return false;\n  }\n  return true;\n}\n\nClaySharedImageNativeHandle NativePlatformView::AcquireSurface(int width,\n                                                               int height) {\n  if (!sink_ref_) {\n    return nullptr;\n  }\n\n  SharedImageSink* sink = reinterpret_cast<SharedImageSink*>(sink_ref_);\n  SharedImageSink::BufferMode mode = convert_buffer_mode(buffer_mode());\n  if (sink->Capacity() != mode) {\n    sink->UpdateBufferMode(mode);\n  }\n\n  auto [backing, buffer_age] = sink->AcquireBack({width, height});\n  if (!backing) {\n    return nullptr;\n  }\n  auto fence_sync = backing->GetFenceSync();\n  if (fence_sync) {\n    fence_sync->ClientWait();\n  }\n  return reinterpret_cast<ClaySharedImageNativeHandle>(backing->GetGFXHandle());\n}\n\nbool NativePlatformView::SwapBack() {\n  if (sink_ref_) {\n    SharedImageSink* sink = reinterpret_cast<SharedImageSink*>(sink_ref_);\n    return sink->SwapBack(nullptr);\n  }\n  return false;\n}\n\nvoid NativePlatformView::MarkDirty() { native_view_->MarkDirty(); }\nvoid NativePlatformView::RequestFocus() { native_view_->RequestFocus(); }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/lynx_adaptor/native_platform_view.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_PLATFORM_VIEW_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_PLATFORM_VIEW_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/value/lynx_value_api.h\"\n#include \"clay/public/clay.h\"\n\nstruct native_view_motion_event;\n\nnamespace lynx::pub {\nclass LynxValue;\n}\n\nnamespace clay {\n\nclass CLAY_EXPORT NativePlatformView {\n public:\n  using Creator = std::function<NativePlatformView*()>;\n\n  // should sync with LynxUIMethodConstants.java in Lynx.\n  static constexpr int kSuccess = 0;\n  static constexpr int kUnknown = 1;\n  static constexpr int kMethodNotFound = 3;\n  static constexpr int kInvalidParameter = 4;\n  static constexpr int kInvalidStateError = 7;\n\n  virtual ~NativePlatformView();\n  virtual bool OnCreate();\n  virtual void OnAttach();\n  virtual void OnDetach();\n  virtual void OnDestroy();\n  virtual void OnLayoutChanged(float left, float top, float width, float height,\n                               float pixel_ratio);\n  virtual void OnPropertiesChanged(lynx_value attrs, lynx_value events);\n  virtual void OnMouseClickEvent(int x, int y, int buttons, bool mouse_up);\n  virtual void OnMouseMoveEvent(int x, int y, int modifiers, bool mouse_leave);\n  virtual void OnMouseWheelEvent(int x, int y, int modifiers, double delta_x,\n                                 double delta_y);\n  virtual void OnFocusChanged(bool focused, bool is_leaf);\n  virtual void OnMethodInvoked(\n      const std::string& method, lynx_value attrs,\n      std::function<void(int code, lynx_value data)> callback);\n  virtual bool SupportScrolling() const { return false; }\n  virtual bool NeedSharedImageSink() const { return false; }\n  virtual bool HandleMotionEvent(native_view_motion_event* event) {\n    return false;\n  }\n\n  virtual ClaySharedImageSinkBufferMode buffer_mode() const {\n    return kClaySharedImageSinkBufferModeDoubleBuffer;\n  }\n  virtual ClaySharedImageBackingType backing_type() const {\n#if defined(OS_WIN)\n    return kClaySharedImageBackingTypeD3DTexture;\n#else\n    return kClaySharedImageBackingTypeIOSurface;\n#endif\n  }\n  virtual ClaySharedImageBackingPixelFormat pixel_format() const {\n    return kClaySharedImageBackingPixelFormatNative8888;\n  }\n\n  bool PresentSurface(int width, int height,\n                      const ClayTransformation* transform,\n                      ClaySharedImageNativeHandle gfx_handle);\n  ClaySharedImageNativeHandle AcquireSurface(int width, int height);\n  bool SwapBack();\n  ClaySharedImageSinkRef GetSharedImageSink();\n\n  void TriggerEvent(const char* name, lynx::pub::LynxValue&& params);\n  void TriggerEvent(const char* name, lynx_value params);\n  void RequestFocus();\n  void MarkDirty();\n\n private:\n  virtual void Release() = 0;\n\n  friend class NativeViewServiceDesktop;\n  friend class NativeViewPluginDesktop;\n  class NativeView* native_view_ = nullptr;\n  ClaySharedImageSinkRef sink_ref_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_PLATFORM_VIEW_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/native_platform_view_mock.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_platform_view.h\"\n\nnamespace clay {\nNativePlatformView::~NativePlatformView() {}\nbool NativePlatformView::OnCreate() { return false; }\nvoid NativePlatformView::OnAttach() {}\nvoid NativePlatformView::OnDetach() {}\nvoid NativePlatformView::OnDestroy() {}\nvoid NativePlatformView::OnLayoutChanged(float left, float top, float width,\n                                         float height, float pixel_ratio) {}\nvoid NativePlatformView::OnPropertiesChanged(lynx_value attrs,\n                                             lynx_value events) {}\nvoid NativePlatformView::OnMouseClickEvent(int x, int y, int buttons,\n                                           bool mouse_up) {}\nvoid NativePlatformView::OnMouseMoveEvent(int x, int y, int modifiers,\n                                          bool mouse_leave) {}\nvoid NativePlatformView::OnMouseWheelEvent(int x, int y, int modifiers,\n                                           double delta_x, double delta_y) {}\nvoid NativePlatformView::OnFocusChanged(bool focused, bool is_leaf) {}\nvoid NativePlatformView::OnMethodInvoked(\n    const std::string& method, lynx_value attrs,\n    std::function<void(int code, lynx_value data)> callback) {\n  callback(kMethodNotFound, {.val_ptr = 0, .type = lynx_value_undefined});\n}\nvoid NativePlatformView::TriggerEvent(const char* name, lynx_value params) {}\nClaySharedImageSinkRef NativePlatformView::GetSharedImageSink() {\n  return nullptr;\n}\nbool NativePlatformView::PresentSurface(\n    int width, int height, const ClayTransformation* transform,\n    ClaySharedImageNativeHandle gfx_handle) {\n  return false;\n}\nClaySharedImageNativeHandle NativePlatformView::AcquireSurface(int width,\n                                                               int height) {\n  return nullptr;\n}\nbool NativePlatformView::SwapBack() { return false; }\nvoid NativePlatformView::MarkDirty() {}\nvoid NativePlatformView::RequestFocus() {}\n}  // namespace clay\n"
  },
  {
    "path": "clay/lynx_adaptor/native_view_service_desktop.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/native_view_service_desktop.h\"\n\n#include <assert.h>\n\n#include <memory>\n#include <unordered_set>\n#include <utility>\n\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n#include \"clay/lynx_adaptor/value_converter.h\"\n#include \"clay/ui/component/native_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/event/gesture_event.h\"\n\n#if OS_WIN\n#include <Windows.h>\n#elif OS_MAC\n#include <ApplicationServices/ApplicationServices.h>\n#include <Carbon/Carbon.h>\n#endif\n\nnamespace clay {\n\nclass NativeViewPluginDesktop : public NativeViewPlugin {\n public:\n  NativeViewPluginDesktop(int id, NativeView* native_view,\n                          NativePlatformView* native_platform_view)\n      : NativeViewPlugin(id),\n        native_view_(native_view),\n        native_platform_view_(native_platform_view) {}\n  ~NativeViewPluginDesktop() override { native_platform_view_->Release(); }\n\n  bool OnCreate(std::string tag) override {\n    return native_platform_view_->OnCreate();\n  }\n  void OnAttach() override { native_platform_view_->OnAttach(); }\n  void OnDetach() override { native_platform_view_->OnDetach(); }\n  void OnDestroy() override { native_platform_view_->OnDestroy(); }\n  void LayoutChanged(float left, float top, float width,\n                     float height) override {\n    float pixel_ratio = native_view_->page_view()->DevicePixelRatio();\n    native_platform_view_->OnLayoutChanged(left, top, width, height,\n                                           pixel_ratio);\n  }\n  void UpdatePlatformAttributes(const clay::Value::Map& attrs,\n                                const clay::Value::Array& events) override {\n    if (attrs.empty() && events.empty()) {\n      return;\n    }\n    lynx_api_env env = nullptr;\n    lynx_value lynx_attrs = lynx::ValueConverter::CreateLynxValue(env, attrs);\n    lynx_value lynx_events = lynx::ValueConverter::CreateLynxValue(env, events);\n    native_platform_view_->OnPropertiesChanged(lynx_attrs, lynx_events);\n    lynx_value_remove_reference(env, lynx_attrs, nullptr);\n    lynx_value_remove_reference(env, lynx_events, nullptr);\n  }\n  void InvokePlatformMethod(\n      const std::string& method, const clay::Value::Map& attrs,\n      const clay::LynxUIMethodCallback& callback) override {\n    lynx_api_env env = nullptr;\n    lynx_value lynx_attrs = lynx::ValueConverter::CreateLynxValue(env, attrs);\n    native_platform_view_->OnMethodInvoked(\n        method, lynx_attrs,\n        [env, cb = std::move(callback)](int code, lynx_value data) {\n          clay::Value result = lynx::ValueConverter::CreateClayValue(env, data);\n          lynx_value_remove_reference(env, data, nullptr);\n          cb(static_cast<LynxUIMethodResult>(code), std::move(result));\n        });\n    lynx_value_remove_reference(env, lynx_attrs, nullptr);\n  }\n\n  std::string GetName() const override { return native_view_->GetName(); }\n  bool SupportScrolling() override {\n    return native_platform_view_->SupportScrolling();\n  }\n  fml::RefPtr<SharedImageSink> GetSharedImageSink() override {\n    return fml::RefPtr<SharedImageSink>(reinterpret_cast<SharedImageSink*>(\n        native_platform_view_->GetSharedImageSink()));\n  }\n\n  static_assert(static_cast<int>(kNativeEventPanZoomEnd) ==\n                static_cast<int>(PointerEvent::EventType::kPanZoomEndEvent));\n  static_assert(static_cast<int>(kNativeDeviceTrackpad) ==\n                static_cast<int>(PointerEvent::DeviceType::kTrackpad));\n  static_assert(static_cast<int>(kNativeSignalEndScroll) ==\n                static_cast<int>(PointerEvent::SignalKind::kEndScroll));\n  static_assert(static_cast<int>(kNativeMouseButtonForward) ==\n                static_cast<int>(PointerEvent::MouseButton::kForward));\n\n  void OnTouchEvent(const clay::PointerEvent& event,\n                    const clay::FloatPoint& transformed_postion) override {\n    FloatPoint position =\n        native_view_->page_view()->ConvertTo<kPixelTypeLogical>(event.position);\n    native_view_motion_event_t motion_event;\n    memset(&motion_event, 0, sizeof(motion_event));\n    motion_event.timestamp = event.timestamp;\n    motion_event.type = static_cast<lynx_native_event_type_t>(event.type);\n    motion_event.device = static_cast<lynx_native_device_type_t>(event.device);\n    motion_event.signal_kind =\n        static_cast<lynx_native_signal_kind_t>(event.signal_kind);\n    motion_event.pointer_id = event.pointer_id;\n    motion_event.device_id = event.device_id;\n    motion_event.buttons = event.buttons;\n    motion_event.x = transformed_postion.x();\n    motion_event.y = transformed_postion.y();\n    motion_event.pageX = position.x();\n    motion_event.pageY = position.y();\n    if (event.signal_kind != PointerEvent::SignalKind::kNone) {\n      motion_event.deltaX = event.scroll_delta_x;\n      motion_event.deltaY = event.scroll_delta_y;\n    } else if (event.type == PointerEvent::EventType::kPanZoomUpdateEvent) {\n      motion_event.deltaX = event.pan_delta.width();\n      motion_event.deltaY = event.pan_delta.height();\n    } else {\n      motion_event.deltaX = event.delta.width();\n      motion_event.deltaY = event.delta.height();\n    }\n    motion_event.scale = event.scale;\n#if OS_WIN\n    if ((GetKeyState(VK_LMENU) | GetKeyState(VK_RMENU)) & 0x8000) {\n      motion_event.altKey = true;\n    }\n    if ((GetKeyState(VK_LCONTROL) | GetKeyState(VK_RCONTROL)) & 0x8000) {\n      motion_event.ctrlKey = true;\n    }\n    if ((GetKeyState(VK_LSHIFT) | GetKeyState(VK_RSHIFT)) & 0x8000) {\n      motion_event.shiftKey = true;\n    }\n    if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) {\n      motion_event.metaKey = true;\n    }\n#elif OS_MAC\n    if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_Option) ||\n        CGEventSourceKeyState(kCGEventSourceStateHIDSystemState,\n                              kVK_RightOption)) {\n      motion_event.altKey = true;\n    }\n    if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_Control) ||\n        CGEventSourceKeyState(kCGEventSourceStateHIDSystemState,\n                              kVK_RightControl)) {\n      motion_event.ctrlKey = true;\n    }\n    if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_Shift) ||\n        CGEventSourceKeyState(kCGEventSourceStateHIDSystemState,\n                              kVK_RightShift)) {\n      motion_event.shiftKey = true;\n    }\n    if (CGEventSourceKeyState(kCGEventSourceStateHIDSystemState, kVK_Command) ||\n        CGEventSourceKeyState(kCGEventSourceStateHIDSystemState,\n                              kVK_RightCommand)) {\n      motion_event.metaKey = true;\n    }\n#endif\n    if (native_platform_view_->HandleMotionEvent(&motion_event)) {\n      return;\n    }\n\n    int modifiers =\n        event.buttons & (PointerEvent::kPrimary | PointerEvent::kSecondary |\n                         PointerEvent::kMiddle);\n    if (event.signal_kind == PointerEvent::SignalKind::kScroll) {\n      native_platform_view_->OnMouseWheelEvent(\n          transformed_postion.x(), transformed_postion.y(), modifiers,\n          event.scroll_delta_x, -event.scroll_delta_y);\n      return;\n    }\n    if (event.type == PointerEvent::EventType::kPanZoomUpdateEvent) {\n      native_platform_view_->OnMouseWheelEvent(\n          transformed_postion.x(), transformed_postion.y(), modifiers,\n          event.pan_delta.width(), -event.pan_delta.height());\n      return;\n    }\n    switch (event.type) {\n      case PointerEvent::EventType::kDownEvent:\n      case PointerEvent::EventType::kUpEvent:\n        native_platform_view_->OnMouseClickEvent(\n            transformed_postion.x(), transformed_postion.y(), event.buttons,\n            event.type == PointerEvent::EventType::kUpEvent);\n        break;\n      default:\n      case PointerEvent::EventType::kHoverEvent:\n        native_platform_view_->OnMouseMoveEvent(\n            transformed_postion.x(), transformed_postion.y(), modifiers, false);\n        break;\n    }\n  }\n  void OnFocusChanged(bool focused, bool is_leaf) override {\n    native_platform_view_->OnFocusChanged(focused, is_leaf);\n  }\n  clay::MeasureResult Measure(\n      const clay::MeasureConstraint& constraint) override {\n    return {};\n  }\n\n private:\n  NativeView* native_view_;\n  NativePlatformView* native_platform_view_;\n};\n\nstd::unique_ptr<NativeViewPlugin>\nNativeViewServiceDesktop::CreateNativeViewPlugin(int id, NativeView* view_ptr) {\n  const auto& tag = view_ptr->GetName();\n  auto search = view_factories.find(tag);\n  if (search != view_factories.end()) {\n    auto native_view_ptr = search->second();\n    if (native_view_ptr) {\n      native_view_ptr->native_view_ = view_ptr;\n      return std::make_unique<NativeViewPluginDesktop>(id, view_ptr,\n                                                       native_view_ptr);\n    }\n  }\n  return nullptr;\n}\n\n// static\nvoid NativeViewServiceDesktop::SetViewFactories(\n    ViewContext* view_context,\n    std::unordered_map<std::string, std::function<NativePlatformView*()>>\n        factories) {\n  std::unordered_set<std::string> native_view_tags;\n  for (auto& pair : factories) {\n    native_view_tags.insert(pair.first);\n  }\n  view_context->SyncNativeViewTags(native_view_tags);\n\n  Puppet<Owner::kUI, NativeViewService> native_view_service =\n      view_context->GetPageView()\n          ->GetServiceManager()\n          ->GetService<NativeViewService>();\n  native_view_service.Act([&factories](auto& service) {\n    static_cast<NativeViewServiceDesktop&>(service).view_factories =\n        std::move(factories);\n  });\n}\n\n// static\nvoid NativeViewServiceDesktop::SetViewFactories(\n    ViewContext* view_context,\n    std::unordered_map<std::string, std::pair<lynx_native_view_creator, void*>>\n        creators) {\n  std::unordered_map<std::string, clay::NativePlatformView::Creator>\n      view_factories;\n  for (auto& pair : creators) {\n    view_factories[pair.first] =\n        [native_view_creator = pair.second]() -> clay::NativePlatformView* {\n      auto creator = reinterpret_cast<clay::NativePlatformView* (*)(void*)>(\n          std::get<0>(native_view_creator));\n      auto opaque = std::get<1>(native_view_creator);\n      return creator(opaque);\n    };\n  }\n  SetViewFactories(view_context, view_factories);\n}\n\n// static\nvoid NativeViewServiceDesktop::AddViewFactory(ViewContext* view_context,\n                                              const char* name,\n                                              lynx_native_view_creator creator,\n                                              void* opaque) {\n  Puppet<Owner::kUI, NativeViewService> native_view_service =\n      view_context->GetPageView()\n          ->GetServiceManager()\n          ->GetService<NativeViewService>();\n  native_view_service.Act([view_context, name, creator, opaque](auto& service) {\n    auto& factories =\n        static_cast<NativeViewServiceDesktop&>(service).view_factories;\n    std::unordered_set<std::string> native_view_tags;\n    for (auto& pair : factories) {\n      native_view_tags.insert(pair.first);\n    }\n    native_view_tags.insert(name);\n    view_context->SyncNativeViewTags(native_view_tags);\n\n    factories[name] = [creator, opaque]() -> clay::NativePlatformView* {\n      auto fx = reinterpret_cast<clay::NativePlatformView* (*)(void*)>(creator);\n      return fx(opaque);\n    };\n  });\n}\n\nstd::shared_ptr<NativeViewService> NativeViewService::Create() {\n  return std::make_shared<NativeViewServiceDesktop>();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/lynx_adaptor/native_view_service_desktop.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_NATIVE_VIEW_SERVICE_DESKTOP_H_\n#define CLAY_LYNX_ADAPTOR_NATIVE_VIEW_SERVICE_DESKTOP_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"clay/lynx_adaptor/native_platform_view.h\"\n#include \"clay/ui/platform/native_view_service.h\"\n#include \"platform/embedder/lynx_view_builder_priv.h\"\n\nnamespace clay {\nclass ViewContext;\n\nclass NativeViewServiceDesktop final : public NativeViewService {\n public:\n  std::unique_ptr<NativeViewPlugin> CreateNativeViewPlugin(\n      int id, NativeView* view_ptr) override;\n\n  std::unordered_map<std::string, NativePlatformView::Creator> view_factories;\n\n  static void SetViewFactories(\n      ViewContext* view_context,\n      std::unordered_map<std::string, std::function<NativePlatformView*()>>\n          factories);\n  static void SetViewFactories(\n      ViewContext* view_context,\n      std::unordered_map<std::string,\n                         std::pair<lynx_native_view_creator, void*>>\n          creators);\n  static void AddViewFactory(ViewContext* view_context, const char* name,\n                             lynx_native_view_creator creator, void* opaque);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_LYNX_ADAPTOR_NATIVE_VIEW_SERVICE_DESKTOP_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/painting_context_clay.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/painting_context_clay.h\"\n\n#include <unordered_map>\n#include <utility>\n\n#include \"clay/lynx_adaptor/base_def.h\"\n#include \"clay/lynx_adaptor/clay_value.h\"\n#include \"clay/lynx_adaptor/platform_extra_bundle_clay.h\"\n#include \"clay/lynx_adaptor/prop_bundle_impl.h\"\n#include \"clay/lynx_adaptor/value_converter.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/component/list/lynx_list_data.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n#include \"clay/ui/shadow/text_render.h\"\n#include \"core/renderer/css/css_property_id.h\"\n\nnamespace lynx {\n\nnamespace {\n\n[[maybe_unused]] void ConvertListData(const lynx::tasm::ListData& origin,\n                                      clay::LynxListData* res) {\n  FML_DCHECK(res);\n  for (const std::string& name : origin.GetViewTypeNames()) {\n    res->PushViewType(name.c_str());\n  }\n\n  res->SetNewArch(origin.GetNewArch());\n  res->SetDiffable(origin.GetDiffable());\n  res->SetFullSpan(origin.GetFullSpan());\n  res->SetStickyTop(origin.GetStickyTop());\n  res->SetStickyBottom(origin.GetStickyBottom());\n  res->SetInsertions(origin.GetInsertions());\n  res->SetRemovals(origin.GetRemovals());\n  res->SetUpdateFrom(origin.GetUpdateFrom());\n  res->SetUpdateTo(origin.GetUpdateTo());\n  res->SetMoveFrom(origin.GetMoveFrom());\n  res->SetMoveTo(origin.GetMoveTo());\n}\n\n}  // namespace\n\nnamespace tasm {\n\nvoid PaintingContextClayRef::InsertPaintingNode(int parent, int child,\n                                                int index) {\n  view_context_->AddView(child, parent, index);\n}\n\nvoid PaintingContextClayRef::RemovePaintingNode(int parent, int child,\n                                                int index, bool is_move) {\n  view_context_->RemoveView(child, parent, is_move);\n}\n\nvoid PaintingContextClayRef::DestroyPaintingNode(int parent, int child,\n                                                 int index) {\n  view_context_->RemoveView(child, parent);\n  view_context_->DestroyView(child);\n}\n\nvoid PaintingContextClayRef::UpdateScrollInfo(int32_t container_id, bool smooth,\n                                              float estimated_offset,\n                                              bool scrolling) {\n  view_context_->UpdateScrollInfo(container_id, smooth, estimated_offset,\n                                  scrolling);\n}\n\nvoid PaintingContextClayRef::UpdateNodeReadyPatching(\n    std::vector<int32_t> ready_ids, std::vector<int32_t> remove_ids) {\n  view_context_->UpdateNodeReadyPatching(std::move(ready_ids),\n                                         std::move(remove_ids));\n}\n\nvoid PaintingContextClayRef::InsertListItemPaintingNode(int list_sign,\n                                                        int child_sign) {\n  view_context_->InsertListItemPaintingNode(list_sign, child_sign);\n}\n\nvoid PaintingContextClayRef::RemoveListItemPaintingNode(int list_sign,\n                                                        int child_sign) {\n  view_context_->RemoveListItemPaintingNode(list_sign, child_sign);\n}\n\nvoid PaintingContextClayRef::UpdateContentOffsetForListContainer(\n    int32_t container_id, float content_size, float delta_x, float delta_y,\n    bool is_init_scroll_offset, bool from_layout) {\n  view_context_->UpdateContentOffsetForListContainer(container_id, content_size,\n                                                     delta_x, delta_y);\n}\n\nvoid PaintingContextClayRef::SetPerfController(\n    const std::shared_ptr<PerfControllerClay>& controller) {\n  perf_controller_ = controller;\n  view_context_->SetPipelineTimingDelegate(perf_controller_);\n  view_context_->SetScrollFluencyMonitorDelegate(perf_controller_);\n  if (perf_controller_) {\n    perf_controller_->SetUITaskRunner(view_context_->GetUITaskRunner());\n  }\n}\n\nvoid PaintingContextClayRef::SetNeedMarkPaintEndTiming(\n    const tasm::PipelineID& pipeline_id) {\n  if (perf_controller_) {\n    perf_controller_->SetNeedMarkPaintEndTiming(pipeline_id);\n  }\n}\n\nPaintingContextClay::PaintingContextClay(clay::ViewContext* view_context)\n    : view_context_(view_context) {\n  FML_DCHECK(view_context);\n  platform_ref_ = std::make_shared<PaintingContextClayRef>(view_context);\n  view_context_->SetUIComponentDelegate(this);\n}\n\nPaintingContextClay::~PaintingContextClay() {\n  view_context_->SetUIComponentDelegate(nullptr);\n  view_context_->ResetPageView();\n}\n\nvoid PaintingContextClay::Flush() { ui_operation_queue_ref_->Flush(); }\n\nvoid PaintingContextClay::HandleValidate(int tag) {\n  // TODO(zhangxiao): this function use to reload image if image download\n  // failed\n}\n\nvoid PaintingContextClay::FinishLayoutOperation(\n    const std::shared_ptr<PipelineOptions>& options) {\n  Enqueue([view_context = view_context_, options]() {\n    view_context->FinishLayoutOperation(options->list_comp_id_,\n                                        options->list_id_);\n  });\n}\n\nvoid PaintingContextClay::FinishTasmOperation(\n    const std::shared_ptr<PipelineOptions>& options) {}\n\n// Invoked by BTS\nvoid PaintingContextClay::InvokeUIMethod(int32_t view_id,\n                                         const std::string& method,\n                                         fml::RefPtr<tasm::PropBundle> args,\n                                         int32_t callback_id) {\n  Enqueue([view_context = view_context_, runtime_proxy = runtime_proxy_,\n           args = std::move(args), view_id, method = method, callback_id]() {\n    clay::LynxModuleValues values;\n    auto prob = static_cast<lynx::PropBundleImpl*>(args.get());\n    for (auto& it : prob->mutable_map()) {\n      values.names.push_back(it.first);\n      values.values.push_back(std::move(it.second));\n    }\n    view_context->InvokeUIMethod(\n        view_id, method, values,\n        [runtime_proxy, callback_id](clay::LynxUIMethodResult code,\n                                     clay::Value data) {\n          clay::Value::Map map;\n          map.emplace(\"code\", clay::Value(static_cast<int>(code)));\n          map.emplace(\"data\", std::move(data));\n          runtime_proxy->CallJSApiCallbackWithValue(\n              callback_id,\n              std::make_unique<ClayValue>(clay::Value(std::move(map))));\n        });\n  });\n}\n\nvoid PaintingContextClay::CreatePaintingNode(\n    int id, const std::string& tag,\n    const fml::RefPtr<PropBundle>& painting_data, bool flatten,\n    bool create_node_async, uint32_t node_index) {\n  Enqueue([view_context = view_context_, id, tag, painting_data = painting_data,\n           flatten, node_index] {\n    const char* tag_name = tag.c_str();\n    auto* pda = painting_data.get();\n    // Terminology:\n    // flatten: In lynx, the view does not have a real platform view, and its\n    //          content will be drawn on the parent node that owns the platform\n    //          view.\n    // repaint_boundary: In Clay, whether this view paints separately from its\n    //                   parent.\n    //\n    // So, if flatten is true, need to paint this view and its parent together,\n    // that means repaint_boundary is false in Clay; and when flatten is false,\n    // need to draw this view and its parent separately, that means\n    // repaint_boundary is true in Clay.\n    //\n    // In Clay, we force the following components to have a repaint boundary for\n    // better performance, regardless of the value of flatten: PageView,\n    // ScrollView, ListView, Swiper, EditableView[Input].\n    view_context->CreateView(id, tag_name);\n    // we just apply value to repaint boundary if flatten false\n    if (!flatten) {\n      view_context->SetRepaintBoundary(id, true);\n    }\n    SetAttribute(view_context, id, pda, true);\n  });\n}\n\nint32_t PaintingContextClay::GetTagInfo(const std::string& tag_name) {\n  return view_context_->GetTagInfo(tag_name);\n}\n\nvoid PaintingContextClay::SetKeyframes(fml::RefPtr<PropBundle> keyframes_data) {\n  Enqueue([view_context = view_context_, keyframes_data = keyframes_data]() {\n    auto pdr = static_cast<PropBundleImpl*>(keyframes_data.get());\n    auto keyframes_iter = pdr->map().find(\"keyframes\");\n    if (keyframes_iter == pdr->map().end()) {\n      FML_DLOG(ERROR) << \"SetKeyframes 'keyframes' not found\";\n      return;\n    }\n\n    const auto& prop_keyframes_value = keyframes_iter->second;\n    view_context->SetKeyframes(prop_keyframes_value);\n  });\n}\n\nvoid PaintingContextClay::UpdatePaintingNode(\n    int id, bool tend_to_flatten,\n    const fml::RefPtr<PropBundle>& painting_data) {\n  Enqueue([view_context = view_context_, id, tend_to_flatten,\n           painting_data = painting_data]() {\n    auto* pda = painting_data.get();\n    SetAttribute(view_context, id, pda, false);\n    view_context->SetRepaintBoundary(id, !tend_to_flatten);\n  });\n}\n\nvoid PaintingContextClay::UpdateLayout(int tag, float x, float y, float width,\n                                       float height, const float* paddings,\n                                       const float* margins,\n                                       const float* borders,\n                                       const float* bounds, const float* sticky,\n                                       float max_height, uint32_t node_index) {\n#define MAKE_UNIQUE_COPY(src, size)                      \\\n  std::unique_ptr<float[]> src##_copy{nullptr};          \\\n  if (src) {                                             \\\n    src##_copy = std::make_unique<float[]>(size);        \\\n    memcpy(src##_copy.get(), src, sizeof(float) * size); \\\n  }\n\n  MAKE_UNIQUE_COPY(paddings, 4)\n  MAKE_UNIQUE_COPY(margins, 4)\n  MAKE_UNIQUE_COPY(sticky, 4)\n#undef MAKE_UNIQUE_COPY\n  // Set margins, bounds, paddings.\n  // Margins should be earlier then bounds because of it may be used during\n  // bounds setting.\n  Enqueue([view_context = view_context_, tag, x, y, width, height,\n           paddings_ptr = std::move(paddings_copy),\n           margins_ptr = std::move(margins_copy),\n           sticky_ptr = std::move(sticky_copy)]() {\n    auto margins = margins_ptr.get();\n    auto paddings = paddings_ptr.get();\n    auto sticky = sticky_ptr.get();\n#if OS_MAC || OS_WIN\n    view_context->SetMargins(tag, std::roundf(margins[0]),\n                             std::roundf(margins[1]), std::roundf(margins[2]),\n                             std::roundf(margins[3]));\n    view_context->SetBounds(tag, std::roundf(x), std::roundf(y),\n                            std::roundf(width), std::roundf(height));\n    view_context->SetPaddings(\n        tag, std::roundf(paddings[0]), std::roundf(paddings[1]),\n        std::roundf(paddings[2]), std::roundf(paddings[3]));\n#else\n    view_context->SetMargins(tag, margins[0], margins[1], margins[2],\n                             margins[3]);\n    view_context->SetBounds(tag, x, y, width, height);\n    view_context->SetPaddings(tag, paddings[0], paddings[1], paddings[2],\n                              paddings[3]);\n#endif\n    view_context->UpdateSticky(tag, sticky);\n  });\n}\n\n// Invoked by MTS/worklet\nvoid PaintingContextClay::Invoke(\n    int64_t id, const std::string& method, const pub::Value& params,\n    const std::function<void(int32_t code, const pub::Value& data)>& callback) {\n  clay::LynxModuleValues values;\n  auto map = ValueConverter::CreateClayValue(params);\n  if (map.IsMap()) {\n    for (auto& [key, val] : map.GetMap()) {\n      values.names.push_back(key);\n      values.values.push_back(std::move(val));\n    }\n  }\n  view_context_->InvokeUIMethod(\n      id, method, values,\n      [callback](clay::LynxUIMethodResult code, clay::Value data) {\n        callback(static_cast<int>(code), ClayValue(std::move(data)));\n      });\n}\n\nvoid PaintingContextClay::getAbsolutePosition(int id, float* position) {\n  view_context_->GetAbsolutePosition(id, position[0], position[1]);\n}\n\nvoid PaintingContextClay::OnFirstMeaningfulLayout() {\n  view_context_->OnFirstMeaningfulLayout();\n}\n\nstd::vector<float> PaintingContextClay::getBoundingClientOrigin(int id) {\n  float to_root_x = 0;\n  float to_root_y = 0;\n  float position[2] = {0.0, 0.0};\n  getAbsolutePosition(id, position);\n\n  to_root_x = position[1];\n  to_root_y = position[0];\n  return std::vector<float>{to_root_x, to_root_y};\n}\n\nbool PaintingContextClay::IsFlatten(base::MoveOnlyClosure<bool, bool> func) {\n  if (func != nullptr) {\n    return func(true);\n  }\n  return false;\n}\n\nvoid PaintingContextClay::UpdatePlatformExtraBundle(\n    int32_t id, PlatformExtraBundle* bundle) {\n  Enqueue(\n      [view_context = view_context_, id,\n       platform_bundle =\n           reinterpret_cast<PlatformExtraBundleClay*>(bundle)->GetBundle()] {\n        view_context->UpdateExtraData(id, platform_bundle.get());\n      });\n}\n\n// private\nvoid PaintingContextClay::SetAttribute(clay::ViewContext* view_context,\n                                       int sign, PropBundle* attributes,\n                                       bool init) {\n  auto pda = static_cast<PropBundleImpl*>(attributes);\n\n  if (init && pda) {  // Add event props when create view\n    for (auto& event : pda->event_handlers()) {\n      view_context->AddEventProp(sign, event.c_str());\n    }\n  }\n\n  if (!pda || pda->map().empty()) {\n    view_context->DidUpdateAttributes(sign);\n    return;\n  }\n\n  auto& map = pda->mutable_map();\n\n  clay::Value trans{};\n  auto iter = map.find(kPropertyNameTransition);\n  if (iter != map.end()) {  // Has transition\n    if (init) {\n      trans = std::move(iter->second);  // Save transition value\n    } else {                            // Set transition firstly and destroy it\n      view_context->SetAttribute(sign, iter->first.c_str(), iter->second);\n    }\n    // Erase the transition prop\n    map.erase(iter);\n  }\n  // remove animation and execute in the end\n  iter = map.find(kPropertyNameAnimation);\n  clay::Value animation_property{};\n  if (iter != map.end()) {\n    animation_property = std::move(iter->second);\n    map.erase(iter);\n  }\n\n  iter = map.begin();\n  for (; iter != map.end(); iter++) {\n    view_context->SetAttribute(sign, iter->first.c_str(), iter->second);\n  }\n\n  // Update transition finally when create view\n  if (init && trans.IsArray()) {\n    view_context->SetAttribute(sign, kPropertyNameTransition, trans);\n  }\n\n  if (animation_property.IsArray()) {\n    view_context->SetAttribute(sign, kPropertyNameAnimation,\n                               animation_property);\n  }\n  view_context->DidUpdateAttributes(sign);\n}\n\nclay::LynxListData* PaintingContextClay::OnListGetData(int view_id) {\n#ifndef ENABLE_CLAY_LITE\n  lynx::tasm::ListData list_data = engine_proxy_->GetListData(view_id);\n  clay::LynxListData* res = new clay::LynxListData(\n      static_cast<int>(list_data.GetViewTypeNames().size()));\n  ConvertListData(list_data, res);\n  return res;\n#else\n  return nullptr;\n#endif\n}\n\nint PaintingContextClay::OnObtainChild(int view_id, int index,\n                                       int operation_id) {\n  return engine_proxy_->ObtainListChild(view_id, static_cast<uint32_t>(index),\n                                        operation_id, false);\n}\n\nvoid PaintingContextClay::OnRecycleChild(int view_id, int child_id) {\n  engine_proxy_->RecycleListChild(view_id, child_id);\n}\n\nvoid PaintingContextClay::OnCreateAddChild(int view_id, int index,\n                                           int operation_id) {\n  engine_proxy_->RenderListChild(view_id, static_cast<uint32_t>(index),\n                                 operation_id);\n}\n\nvoid PaintingContextClay::OnUpdateChild(int parent_id, int to_update_id,\n                                        int target_index,\n                                        int64_t operation_id) {\n  engine_proxy_->UpdateListChild(parent_id, static_cast<uint32_t>(to_update_id),\n                                 static_cast<uint32_t>(target_index),\n                                 operation_id);\n}\n\nvoid PaintingContextClay::OnScrollByListContainer(int view_id, float offset_x,\n                                                  float offset_y,\n                                                  float original_x,\n                                                  float original_y) {\n  engine_proxy_->ScrollByListContainer(view_id, offset_x, offset_y, original_x,\n                                       original_y);\n}\n\nvoid PaintingContextClay::OnScrollToPosition(int view_id, int position,\n                                             float offset, int align,\n                                             bool smooth) {\n  engine_proxy_->ScrollToPosition(view_id, position, offset, align, smooth);\n}\n\nvoid PaintingContextClay::OnScrollStopped(int view_id) {\n  engine_proxy_->ScrollStopped(view_id);\n}\n\nbool PaintingContextClay::OnEnableRasterAnimation() {\n  return engine_proxy_->EnableRasterAnimation();\n}\n\nstd::unique_ptr<lynx::pub::Value> PaintingContextClay::GetTextInfo(\n    const std::string& content, const pub::Value& info) {\n  auto rk_info = ValueConverter::CreateClayValue(info);\n  auto result = clay::TextRender::GetTextInfo(content.c_str(), rk_info);\n  return std::make_unique<ClayValue>(std::move(result));\n}\n\nvoid PaintingContextClay::StopExposure(const pub::Value& options) {}\n\nvoid PaintingContextClay::ResumeExposure() {}\n\nstd::list<int32_t> PaintingContextClay::GetAncestorElements(int32_t tag) {\n  return engine_proxy_->GetAncestorElements(tag);\n}\n\nvoid PaintingContextClay::Enqueue(base::closure&& op) {\n  if (!EnableUIOperationQueue() || !ui_operation_queue_ref_) {\n    op();\n    return;\n  }\n  ui_operation_queue_ref_->Enqueue(std::move(op));\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/painting_context_clay.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_PAINTING_CONTEXT_CLAY_H_\n#define CLAY_LYNX_ADAPTOR_PAINTING_CONTEXT_CLAY_H_\n\n#include <list>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/lynx_adaptor/perf_controller_clay.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"core/public/lynx_engine_proxy.h\"\n#include \"core/public/lynx_runtime_proxy.h\"\n#include \"core/public/painting_ctx_platform_impl.h\"\n#include \"core/public/perf_controller_proxy.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass PaintingContextClayRef : public PaintingCtxPlatformRef {\n public:\n  explicit PaintingContextClayRef(clay::ViewContext* view_context)\n      : view_context_(view_context) {}\n  ~PaintingContextClayRef() override = default;\n\n  void InsertPaintingNode(int parent, int child, int index) override;\n  void RemovePaintingNode(int parent, int child, int index,\n                          bool is_move) override;\n  void DestroyPaintingNode(int parent, int child, int index) override;\n  void UpdateScrollInfo(int32_t container_id, bool smooth,\n                        float estimated_offset, bool scrolling) override;\n  void UpdateNodeReadyPatching(std::vector<int32_t> ready_ids,\n                               std::vector<int32_t> remove_ids) override;\n  void InsertListItemPaintingNode(int list_sign, int child_sign) override;\n  void RemoveListItemPaintingNode(int list_sign, int child_sign) override;\n  void UpdateContentOffsetForListContainer(int32_t container_id,\n                                           float content_size, float delta_x,\n                                           float delta_y,\n                                           bool is_init_scroll_offset,\n                                           bool from_layout) override;\n  void SetNeedMarkPaintEndTiming(const tasm::PipelineID& pipeline_id) override;\n  void SetPerfController(const std::shared_ptr<PerfControllerClay>& collector);\n\n private:\n  clay::ViewContext* view_context_ = nullptr;\n  std::shared_ptr<PerfControllerClay> perf_controller_;\n};\n\nclass PaintingContextClay : public PaintingCtxPlatformImpl,\n                            public clay::UIComponentDelegate {\n public:\n  explicit PaintingContextClay(clay::ViewContext* view_context);\n  ~PaintingContextClay() override;\n\n  void SetUIOperationQueue(\n      const std::shared_ptr<shell::UIOperationQueueInterface>& queue) override {\n    ui_operation_queue_ref_ = queue;\n  }\n\n  void SetEngineProxy(\n      const std::shared_ptr<shell::LynxEngineProxy>& engine_proxy) {\n    engine_proxy_ = engine_proxy;\n  }\n  void SetRuntimeProxy(\n      const std::shared_ptr<shell::LynxRuntimeProxy>& runtime_proxy) {\n    runtime_proxy_ = runtime_proxy;\n  }\n  void SetInstanceId(const int32_t instance_id) override {\n    instance_id_ = instance_id;\n  }\n\n  void Flush() override;\n  void HandleValidate(int tag) override;\n\n  void FinishTasmOperation(\n      const std::shared_ptr<PipelineOptions>& options) override;\n  void FinishLayoutOperation(\n      const std::shared_ptr<PipelineOptions>& options) override;\n\n  std::vector<float> getBoundingClientOrigin(int id) override;\n  void InvokeUIMethod(int32_t view_id, const std::string& method,\n                      fml::RefPtr<tasm::PropBundle> args,\n                      int32_t callback_id) override;\n  void CreatePaintingNode(int id, const std::string& tag,\n                          const fml::RefPtr<PropBundle>& painting_data,\n                          bool flatten, bool create_node_async,\n                          uint32_t node_index) override;\n  void SetKeyframes(fml::RefPtr<PropBundle> keyframes_data) override;\n  bool DefaultOverflowAlwaysVisible() override {\n    return view_context_ != nullptr\n               ? view_context_->DefaultOverflowAlwaysVisible()\n               : true;\n  }\n  void UpdatePaintingNode(\n      int id, bool tend_to_flatten,\n      const fml::RefPtr<PropBundle>& painting_data) override;\n  void UpdateLayout(int tag, float x, float y, float width, float height,\n                    const float* paddings, const float* margins,\n                    const float* borders, const float* bounds,\n                    const float* sticky, float max_height,\n                    uint32_t node_index = 0) override;\n  void Invoke(int64_t id, const std::string& method, const pub::Value& params,\n              const std::function<void(int32_t code, const pub::Value& data)>&\n                  callback) override;\n\n  void getAbsolutePosition(int id, float* position) override;\n\n  void OnFirstMeaningfulLayout() override;\n\n  bool IsFlatten(base::MoveOnlyClosure<bool, bool> func) override;\n\n  bool NeedAnimationProps() override { return true; }\n  bool EnableParallelElement() override { return true; }\n  bool EnableUIOperationQueue() override { return true; }\n\n  // TODO(chenhyouhui): Use default implementations\n  std::vector<float> getWindowSize(int id) override { return floats_; }\n  std::vector<float> GetRectToWindow(int id) override { return floats_; }\n  std::unique_ptr<pub::Value> GetTextInfo(const std::string& content,\n                                          const pub::Value& info) override;\n  void StopExposure(const pub::Value& options) override;\n  void ResumeExposure() override;\n  std::vector<float> GetRectToLynxView(int64_t id) override { return floats_; }\n  std::vector<float> ScrollBy(int64_t id, float width, float height) override {\n    return floats_;\n  }\n  int32_t GetTagInfo(const std::string& tag_name) override;\n  void UpdatePlatformExtraBundle(int32_t id,\n                                 PlatformExtraBundle* bundle) override;\n\n  void Enqueue(base::closure&& op);\n\n private:\n  std::shared_ptr<shell::UIOperationQueueInterface> ui_operation_queue_ref_;\n  static void SetAttribute(clay::ViewContext* view_context, int sign,\n                           PropBundle* attributes, bool init);\n\n  // clay::UIComponentDelegate\n  clay::LynxListData* OnListGetData(int view_id) override;\n  int OnObtainChild(int view_id, int index, int operation_id) override;\n  void OnRecycleChild(int view_id, int child_id) override;\n  void OnCreateAddChild(int view_id, int index, int operation_id) override;\n  void OnUpdateChild(int parent_id, int to_update_id, int target_index,\n                     int64_t operation_id) override;\n  void OnScrollByListContainer(int view_id, float offset_x, float offset_y,\n                               float original_x, float original_y) override;\n  void OnScrollToPosition(int view_id, int position, float offset, int align,\n                          bool smooth) override;\n  void OnScrollStopped(int view_id) override;\n  bool OnEnableRasterAnimation() override;\n  std::list<int32_t> GetAncestorElements(int32_t tag) override;\n\n  clay::ViewContext* view_context_ = nullptr;\n  std::shared_ptr<shell::LynxEngineProxy> engine_proxy_ = nullptr;\n  std::shared_ptr<shell::LynxRuntimeProxy> runtime_proxy_ = nullptr;\n\n  int32_t instance_id_ = 0;\n\n  // Use to return an empty vector\n  std::vector<float> floats_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_PAINTING_CONTEXT_CLAY_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/perf_controller_clay.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/perf_controller_clay.h\"\n\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/services/timing_handler/timing_constants.h\"\n#include \"core/services/trace/service_trace_event_def.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nnamespace {\ninline constexpr const char* const kHostPlatformSurface = \"Clay\";\nconstexpr int32_t kMinSessionDurationInMs = 200;  // 200ms\nconstexpr int32_t kMaxSessionDurationInSec = 10;  // 10s\nconstexpr std::string_view kLynxFluencyEvent = \"lynxsdk_fluency_event\";\n\nuint64_t GenerateSessionID() {\n  static uint64_t session_id = 0;\n  ++session_id;\n  if (session_id == 0) {\n    ++session_id;\n  }\n  return session_id;\n}\n\n}  // namespace\n\nPerfControllerClay::PerfControllerClay(\n    const std::shared_ptr<shell::PerfControllerProxy>& controller,\n    int32_t instance_id)\n    : perf_controller_proxy_(controller), instance_id_(instance_id) {\n  if (perf_controller_proxy_) {\n    perf_controller_proxy_->SetHostPlatformType(\n        perf_controller_proxy_->GetPlatform() + kHostPlatformSurface);\n  }\n}\n\nvoid PerfControllerClay::SetNeedMarkPaintEndTiming(\n    const tasm::PipelineID& pipeline_id) {\n  DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n\n  if (pipeline_id.empty() || !perf_controller_proxy_) {\n    return;\n  }\n  TRACE_EVENT_INSTANT(LYNX_TRACE_CATEGORY, TIMING_SET_NEED_MARK_DRAW_END,\n                      \"pipeline_id\", pipeline_id);\n  pipeline_id_list_.push_back(pipeline_id);\n}\n\nvoid PerfControllerClay::OnPaintEnd() {\n  DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n\n  if (pipeline_id_list_.empty() || !perf_controller_proxy_) {\n    return;\n  }\n\n  auto timestamp_us = base::CurrentSystemTimeMicroseconds();\n  std::vector<tasm::PipelineID> pipeline_id_list;\n  pipeline_id_list.swap(pipeline_id_list_);\n\n  std::weak_ptr<PerfControllerClay> weak_self = shared_from_this();\n  perf_controller_proxy_->RunTaskInReportThread(\n      [weak_self, pipeline_id_list = std::move(pipeline_id_list),\n       timestamp_us]() {\n        auto strong_self = weak_self.lock();\n        if (!strong_self) {\n          return;\n        }\n\n        for (const auto& pipeline_id : pipeline_id_list) {\n          strong_self->perf_controller_proxy_->SetTiming(\n              timing::kPaintEnd, timestamp_us, pipeline_id);\n        }\n      });\n}\n\nvoid PerfControllerClay::OnPipelineEnd(\n    std::vector<std::pair<std::string, uint64_t>> timings,\n    std::vector<std::string> pipeline_id_list) {\n  if (pipeline_id_list.empty() || !perf_controller_proxy_) {\n    return;\n  }\n\n  auto timestamp = lynx::base::CurrentSystemTimeMicroseconds();\n  std::weak_ptr<PerfControllerClay> weak_self = shared_from_this();\n  perf_controller_proxy_->RunTaskInReportThread(\n      [weak_self, timings = std::move(timings),\n       pipeline_id_list = std::move(pipeline_id_list), timestamp]() {\n        auto strong_self = weak_self.lock();\n        if (!strong_self) {\n          return;\n        }\n\n        for (const auto& pipeline_id : pipeline_id_list) {\n          // mark all host-platform-timing\n          for (const auto& timing_pair : timings) {\n            strong_self->perf_controller_proxy_->SetHostPlatformTiming(\n                timing_pair.first, timing_pair.second, pipeline_id);\n          }\n          // mark pipeline-end\n          strong_self->perf_controller_proxy_->SetTiming(\n              timing::kPipelineEnd, timestamp, pipeline_id);\n        }\n      });\n}\n\nvoid PerfControllerClay::StartFluencyMonitor(\n    int id, const std::string& scene, const std::string& scroll_monitor_tag,\n    int max_refresh_rate) {\n  DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n\n  if (!enable_fluency_monitor_ || !perf_controller_proxy_) {\n    return;\n  }\n\n  if (fps_tracers_.find(id) != fps_tracers_.end()) {\n    return;\n  }\n\n  // Generate a unique session ID for this monitor\n  uint64_t session_id = GenerateSessionID();\n  fluency_monitor_session_ids_[id] = session_id;\n\n  fps_tracers_[id] = std::make_unique<clay::FpsTracer>(\n      scene, scroll_monitor_tag, max_refresh_rate, kMaxSessionDurationInSec);\n  fps_tracers_[id]->Start();\n\n  std::weak_ptr<PerfControllerClay> weak_self = shared_from_this();\n  // If one scroll event does not stop after 10 seconds, we stop it manually.\n  ui_task_runner_->PostDelayedTask(\n      [weak_self, id, session_id]() {\n        auto strong_self = weak_self.lock();\n        if (!strong_self) {\n          return;\n        }\n\n        // Check if this session is still active\n        auto it = strong_self->fluency_monitor_session_ids_.find(id);\n        if (it != strong_self->fluency_monitor_session_ids_.end() &&\n            it->second == session_id) {\n          // Only stop if the session ID matches (it's the same session)\n          strong_self->EndFluencyMonitor(id);\n        }\n      },\n      fml::TimeDelta::FromSeconds(kMaxSessionDurationInSec));\n}\n\nvoid PerfControllerClay::EndFluencyMonitor(int id) {\n  DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n\n  if (!enable_fluency_monitor_ || !perf_controller_proxy_) {\n    return;\n  }\n\n  // Remove the session ID from the map\n  auto it = fluency_monitor_session_ids_.find(id);\n  if (it != fluency_monitor_session_ids_.end()) {\n    // Remove the session ID from the map\n    fluency_monitor_session_ids_.erase(it);\n  }\n\n  if (fps_tracers_.find(id) != fps_tracers_.end()) {\n    auto fps_tracer = std::move(fps_tracers_[id]);\n    fps_tracers_.erase(id);\n    if (!fps_tracer) {\n      return;\n    }\n\n    fps_tracer->Stop();\n    std::weak_ptr<PerfControllerClay> weak_self = shared_from_this();\n    perf_controller_proxy_->RunTaskInReportThread(\n        [weak_self, fps_tracer = std::move(fps_tracer)] {\n          auto strong_self = weak_self.lock();\n          if (!strong_self) {\n            return;\n          }\n\n          clay::FpsRawMetrics metrics;\n          fps_tracer->GetFpsMetrics(metrics);\n          // just ignore scroll event with duration less than 200ms.\n          if (metrics.duration_ms < kMinSessionDurationInMs) {\n            return;\n          }\n\n          shell::ReportEvent event;\n          event.event_name = std::string(kLynxFluencyEvent);\n\n          // string props\n          event.string_props.insert_or_assign(\"lynxsdk_fluency_scene\",\n                                              fps_tracer->GetConfig().scene);\n          event.string_props.insert_or_assign(\"lynxsdk_fluency_tag\",\n                                              fps_tracer->GetConfig().tag);\n\n          // int props\n          event.int_props.insert_or_assign(\"max_refresh_rate\",\n                                           metrics.max_refresh_rate);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_frames_number\",\n                                           metrics.frames);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_fps\", metrics.fps);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_dur\",\n                                           metrics.duration_ms);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_drop1_count\",\n                                           metrics.drop1_count);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_drop1_duration\",\n                                           metrics.drop1_duration_ms);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_drop3_count\",\n                                           metrics.drop3_count);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_drop3_duration\",\n                                           metrics.drop3_duration_ms);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_drop7_count\",\n                                           metrics.drop7_count);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_drop7_duration\",\n                                           metrics.drop7_duration_ms);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_drop25_count\",\n                                           metrics.drop25_count);\n          event.int_props.insert_or_assign(\"lynxsdk_fluency_drop25_duration\",\n                                           metrics.drop25_duration_ms);\n\n          // double props\n          event.double_props.insert_or_assign(\n              \"lynxsdk_fluency_drop1_count_per_second\",\n              1000.0 * metrics.drop1_count / metrics.duration_ms);\n          event.double_props.insert_or_assign(\n              \"lynxsdk_fluency_drop3_count_per_second\",\n              1000.0 * metrics.drop3_count / metrics.duration_ms);\n          event.double_props.insert_or_assign(\n              \"lynxsdk_fluency_drop7_count_per_second\",\n              1000.0 * metrics.drop7_count / metrics.duration_ms);\n          event.double_props.insert_or_assign(\n              \"lynxsdk_fluency_drop25_count_per_second\",\n              1000.0 * metrics.drop25_count / metrics.duration_ms);\n          event.double_props.insert_or_assign(\n              \"lynxsdk_fluency_drop1_ratio\",\n              1000.0 * metrics.drop1_duration_ms / metrics.duration_ms);\n          event.double_props.insert_or_assign(\n              \"lynxsdk_fluency_drop3_ratio\",\n              1000.0 * metrics.drop3_duration_ms / metrics.duration_ms);\n          event.double_props.insert_or_assign(\n              \"lynxsdk_fluency_drop7_ratio\",\n              1000.0 * metrics.drop7_duration_ms / metrics.duration_ms);\n          event.double_props.insert_or_assign(\n              \"lynxsdk_fluency_drop25_ratio\",\n              1000.0 * metrics.drop25_duration_ms / metrics.duration_ms);\n\n          // TODO(zuojinglong.9): use real page config probability.\n          event.double_props.insert_or_assign(\n              \"lynxsdk_fluency_pageconfig_probability\", 0.0);\n          event.int_props.insert_or_assign(\n              \"lynxsdk_fluency_enabled_by_sampling\", 0);\n\n          strong_self->perf_controller_proxy_->OnEvent(\n              strong_self->instance_id_, event);\n        });\n  }\n}\n\nvoid PerfControllerClay::OnFrameTiming(int64_t frame_start_time_nanos,\n                                       int64_t frame_end_time_nanos) {\n  DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n\n  if (!enable_fluency_monitor_ || !perf_controller_proxy_) {\n    return;\n  }\n\n  for (auto& fps_tracer : fps_tracers_) {\n    fps_tracer.second->AddFrameTimeSample(frame_start_time_nanos);\n  }\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/perf_controller_clay.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_PERF_CONTROLLER_CLAY_H_\n#define CLAY_LYNX_ADAPTOR_PERF_CONTROLLER_CLAY_H_\n\n#include <map>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/concurrent_queue.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/log/logging.h\"\n#include \"clay/shell/common/pipeline_timing_delegate.h\"\n#include \"clay/shell/common/scroll_fluency_monitor_delegate.h\"\n#include \"clay/ui/common/fps_tracer.h\"\n#include \"core/public/perf_controller_proxy.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass PerfControllerClay\n    : public std::enable_shared_from_this<PerfControllerClay>,\n      public clay::PipelineTimingDelegate,\n      public clay::ScrollFluencyMonitorDelegate {\n public:\n  PerfControllerClay(\n      const std::shared_ptr<shell::PerfControllerProxy>& controller,\n      int32_t instance_id);\n  ~PerfControllerClay() override = default;\n\n  void SetUITaskRunner(fml::RefPtr<fml::TaskRunner> ui_task_runner) {\n    ui_task_runner_ = ui_task_runner;\n  }\n\n  /**\n   * @brief Mark paint-end timing event\n   * this interface can only be called in the UI thread.\n   */\n  void OnPaintEnd();\n\n  /**\n   * @brief Set the pipeline id to be processed, which will be cached inside\n   * this interface can only be called in the UI thread.\n   */\n  void SetNeedMarkPaintEndTiming(const tasm::PipelineID& pipeline_id);\n\n  /**\n   * @brief Check if there are any pipeline ids to be processed.\n   * Overrides PipelineTimingDelegate::HasPipelineIds.\n   * this interface can only be called in the UI thread.\n   */\n  bool HasPipelineIds() const override {\n    DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n    return !pipeline_id_list_.empty();\n  }\n\n  /**\n   * @brief Get cached pipeline ids to be process.\n   * Overrides PipelineTimingDelegate::GetPipelineIds.\n   * this interface can only be called in the UI thread.\n   */\n  std::vector<tasm::PipelineID> GetPipelineIds() const override {\n    DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n    return pipeline_id_list_;\n  }\n\n  /**\n   * @brief Mark pipeline-end event with additional clay timing events.\n   * Overrides PipelineTimingDelegate::OnPipelineEnd.\n   * this interface can be called in the UI or raster thread.\n   * @param timings The timing data collected by clay\n   * @param pipeline_id_list pipelineIDs associated with the timing event.\n   */\n  void OnPipelineEnd(std::vector<std::pair<std::string, uint64_t>> timings,\n                     std::vector<std::string> pipeline_id_list) override;\n\n  // Overrides ScrollFluencyMonitorDelegate\n  void StartFluencyMonitor(int id, const std::string& scene,\n                           const std::string& scroll_monitor_tag,\n                           int max_refresh_rate) override;\n  void EndFluencyMonitor(int id) override;\n  void OnFrameTiming(int64_t frame_start_time_nanos,\n                     int64_t frame_end_time_nanos) override;\n\n private:\n  const std::shared_ptr<shell::PerfControllerProxy> perf_controller_proxy_;\n\n  fml::RefPtr<fml::TaskRunner> ui_task_runner_;\n\n  std::vector<tasm::PipelineID> pipeline_id_list_;\n  std::atomic<int32_t> instance_id_ = {-1};\n\n  std::map<int, std::unique_ptr<clay::FpsTracer>> fps_tracers_;\n  // Map to track session IDs for each fluency monitor\n  std::map<int, uint64_t> fluency_monitor_session_ids_;\n  // TODO: Get this value from config.\n  bool enable_fluency_monitor_ = false;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_PERF_CONTROLLER_CLAY_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/platform_extra_bundle_clay.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_PLATFORM_EXTRA_BUNDLE_CLAY_H_\n#define CLAY_LYNX_ADAPTOR_PLATFORM_EXTRA_BUNDLE_CLAY_H_\n\n#include <memory>\n#include <utility>\n\n#include \"clay/ui/shadow/bundle.h\"\n#include \"core/public/platform_extra_bundle.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass PlatformExtraBundleClay final : public PlatformExtraBundle {\n public:\n  PlatformExtraBundleClay(int32_t sign, PlatformExtraBundleHolder* holder,\n                          clay::Bundle* bundle)\n      : PlatformExtraBundle(sign, holder), bundle_(bundle) {}\n\n  std::shared_ptr<clay::Bundle> const& GetBundle() const {\n    return this->bundle_;\n  }\n\n private:\n  std::shared_ptr<clay::Bundle> bundle_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_PLATFORM_EXTRA_BUNDLE_CLAY_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/prop_bundle_impl.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/prop_bundle_impl.h\"\n\n#include <array>\n#include <utility>\n\n#include \"clay/lynx_adaptor/value_converter.h\"\n#include \"core/renderer/css/css_property_id.h\"\n\nnamespace lynx {\n\n// Get rid of Lynx symbol exporting\nstatic const char* GetPropertyName(tasm::CSSPropertyID id) {\n  static const char* kLynxCSSPropertyNames[] = {\n      \"\",  // start\n#define DECLARE_PROPERTY_ID(name, c, value) tasm::kPropertyName##name,\n      FOREACH_ALL_PROPERTY(DECLARE_PROPERTY_ID)\n#undef DECLARE_PROPERTY_ID\n  };\n  if (id > tasm::kPropertyStart && id < tasm::kPropertyEnd) {\n    return kLynxCSSPropertyNames[id];\n  }\n  return kLynxCSSPropertyNames[0];  // Empty string\n}\n\nPropBundleImpl::~PropBundleImpl() {}\n\nvoid PropBundleImpl::SetNullProps(const char* key) {\n  map_[key] = clay::Value::Null();\n}\n\nvoid PropBundleImpl::SetProps(const char* key, unsigned int value) {\n  map_[key] = clay::Value{value};\n}\n\nvoid PropBundleImpl::SetProps(const char* key, int value) {\n  map_[key] = clay::Value{value};\n}\n\nbool PropBundleImpl::Contains(const char* key) const {\n  return map_.find(key) != map_.end();\n}\n\nvoid PropBundleImpl::SetProps(const char* key, const char* value) {\n  map_[key] = clay::Value(value);\n}\nvoid PropBundleImpl::SetProps(const char* key, bool value) {\n  map_[key] = clay::Value{value};\n}\n\nvoid PropBundleImpl::SetProps(const char* key, double value) {\n  map_[key] = clay::Value{value};\n}\n\nvoid PropBundleImpl::SetProps(const char* key, const pub::Value& value) {\n  map_[key] = ValueConverter::CreateClayValue(value);\n}\n\nvoid PropBundleImpl::SetProps(const pub::Value& value) {\n  auto prev_value_vector =\n      pub::ScopedCircleChecker::InitVectorIfNecessary(value);\n  value.ForeachMap(\n      [this, &prev_value_vector](const pub::Value& k, const pub::Value& v) {\n        map_[k.str().c_str()] =\n            ValueConverter::CreateClayValue(v, prev_value_vector.get(), 1);\n      });\n}\n\nvoid PropBundleImpl::SetNullPropsByID(tasm::CSSPropertyID id) {\n  auto property_name = GetPropertyName(id);\n  SetNullProps(property_name);\n}\n\nvoid PropBundleImpl::SetPropsByID(tasm::CSSPropertyID id, unsigned int value) {\n  auto property_name = GetPropertyName(id);\n  SetProps(property_name, value);\n}\n\nvoid PropBundleImpl::SetPropsByID(tasm::CSSPropertyID id, int value) {\n  auto property_name = GetPropertyName(id);\n  SetProps(property_name, value);\n}\n\nvoid PropBundleImpl::SetPropsByID(tasm::CSSPropertyID id, const char* value) {\n  auto property_name = GetPropertyName(id);\n  SetProps(property_name, value);\n}\n\nvoid PropBundleImpl::SetPropsByID(tasm::CSSPropertyID id, bool value) {\n  auto property_name = GetPropertyName(id);\n  SetProps(property_name, value);\n}\n\nvoid PropBundleImpl::SetPropsByID(tasm::CSSPropertyID id, double value) {\n  auto property_name = GetPropertyName(id);\n  SetProps(property_name, value);\n}\n\nvoid PropBundleImpl::SetPropsByID(tasm::CSSPropertyID id,\n                                  const pub::Value& value) {\n  auto property_name = GetPropertyName(id);\n  SetProps(property_name, value);\n}\n\nvoid PropBundleImpl::SetPropsByID(tasm::CSSPropertyID id, const uint8_t* data,\n                                  size_t size) {\n  auto property_name = GetPropertyName(id);\n  auto array = ValueConverter::CreateClayValue(data, size);\n  map_[property_name] = std::move(array);\n}\n\nvoid PropBundleImpl::SetPropsByID(tasm::CSSPropertyID id, const uint32_t* data,\n                                  size_t size) {\n  auto property_name = GetPropertyName(id);\n  auto array = ValueConverter::CreateClayValue(data, size);\n  map_[property_name] = std::move(array);\n}\n\nvoid PropBundleImpl::SetEventHandler(const pub::Value& event) {\n  auto name = event.GetValueAtIndex(0);\n  event_handlers_.emplace_back(name->str().c_str());\n}\n\nvoid PropBundleImpl::ResetEventHandler() { event_handlers_.clear(); }\n\nvoid PropBundleImpl::SetGestureDetector(const tasm::GestureDetector& detector) {\n  // TODO(luochangan.adrian): need to implement set gesture detector in Clay.\n}\n\nfml::RefPtr<tasm::PropBundle> PropBundleCreatorClay::CreatePropBundle() {\n  return fml::MakeRefCounted<PropBundleImpl>();\n}\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/prop_bundle_impl.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_PROP_BUNDLE_IMPL_H_\n#define CLAY_LYNX_ADAPTOR_PROP_BUNDLE_IMPL_H_\n\n#include <sys/types.h>\n\n#include <map>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/public/value.h\"\n#include \"core/public/prop_bundle.h\"\n\nnamespace lynx {\n\nclass PropBundleImpl : public tasm::PropBundle {\n public:\n  PropBundleImpl() = default;\n  ~PropBundleImpl();\n  void SetNullProps(const char* key) override;\n  void SetProps(const char* key, unsigned int value) override;\n  void SetProps(const char* key, int value) override;\n  void SetProps(const char* key, const char* value) override;\n  void SetProps(const char* key, bool value) override;\n  void SetProps(const char* key, double value) override;\n  void SetProps(const char* key, const pub::Value& value) override;\n  void SetProps(const pub::Value& value) override;\n  bool Contains(const char* key) const override;\n  void SetEventHandler(const pub::Value& event) override;\n  void SetGestureDetector(const tasm::GestureDetector& detector) override;\n  void ResetEventHandler() override;\n\n  void SetNullPropsByID(tasm::CSSPropertyID id) override;\n  void SetPropsByID(tasm::CSSPropertyID id, unsigned int value) override;\n  void SetPropsByID(tasm::CSSPropertyID id, int value) override;\n  void SetPropsByID(tasm::CSSPropertyID id, const char* value) override;\n  void SetPropsByID(tasm::CSSPropertyID id, bool value) override;\n  void SetPropsByID(tasm::CSSPropertyID id, double value) override;\n  void SetPropsByID(tasm::CSSPropertyID id, const pub::Value& value) override;\n  void SetPropsByID(tasm::CSSPropertyID id, const uint8_t* data,\n                    size_t size) override;\n\n  void SetPropsByID(tasm::CSSPropertyID id, const uint32_t* data,\n                    size_t size) override;\n  fml::RefPtr<PropBundle> ShallowCopy() override { return nullptr; }\n\n  const clay::Value::Map& map() const { return map_; }\n\n  clay::Value::Map& mutable_map() { return map_; }\n\n  const std::vector<std::string>& event_handlers() const {\n    return event_handlers_;\n  }\n\n private:\n  clay::Value::Map map_;\n  std::vector<std::string> event_handlers_;\n};\n\nclass PropBundleCreatorClay : public tasm::PropBundleCreator {\n public:\n  fml::RefPtr<tasm::PropBundle> CreatePropBundle() override;\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_PROP_BUNDLE_IMPL_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/resource_loader_embedder.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/resource_loader_embedder.h\"\n\n#include <future>\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/fml/paths.h\"\n#include \"clay/net/url/url_helper.h\"\n\nnamespace {\n\nstatic lynx_resource_type_e ConvertToLynxResourceType(\n    clay::ResourceType resource_type) {\n  switch (resource_type) {\n    case clay::ResourceType::kImage:\n      return kLynxResourceTypeImage;\n    case clay::ResourceType::kFont:\n      return kLynxResourceTypeFont;\n    case clay::ResourceType::kLottie:\n      return kLynxResourceTypeLottie;\n    case clay::ResourceType::kVideo:\n      return kLynxResourceTypeVideo;\n    case clay::ResourceType::kSvg:\n      return kLynxResourceTypeSVG;\n    case clay::ResourceType::kTemplate:\n      return kLynxResourceTypeTemplate;\n    case clay::ResourceType::kLynxCoreJs:\n      return kLynxResourceTypeLynxCoreJS;\n    case clay::ResourceType::kDynamicComponent:\n      return kLynxResourceTypeLazyBundle;\n    case clay::ResourceType::kI18nText:\n      return kLynxResourceTypeI18NText;\n    case clay::ResourceType::kExternalJs:\n      return kLynxResourceTypeExternalJSSource;\n    default:\n      return kLynxResourceTypeGeneric;\n  }\n}\n\n}  // namespace\n\nnamespace clay {\n\nResourceLoaderEmbedder::ResourceLoaderEmbedder(\n    fml::RefPtr<fml::TaskRunner> task_runner,\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    std::shared_ptr<lynx::embedder::LynxResourceFetcherHolder> fetcher_holder)\n    : ui_task_runner_(task_runner),\n      intercept_(intercept),\n      fetcher_holder_(fetcher_holder) {}\n\nvoid ResourceLoaderEmbedder::Load(\n    const std::string& src,\n    const std::function<void(const uint8_t*, size_t)>& callback,\n    const ResourceType resource_type, bool need_redirect) {\n  std::string url = src;\n  if (need_redirect && intercept_) {\n    url = intercept_->ShouldInterceptUrl(src, false);\n  }\n  url::UriSchemeType scheme_type = url::ParseUriScheme(url);\n  if (scheme_type == url::UriSchemeType::kNet) {\n    LoadByNet(url, callback, resource_type);\n  } else if (scheme_type == url::UriSchemeType::kLocalFile) {\n    ui_task_runner_->PostTask(\n        [url = std::move(url), callback = std::move(callback)]() {\n          std::string file_path =\n              fml::paths::AbsolutePath(fml::paths::FromURI(url));\n          auto mapping = fml::FileMapping::CreateReadOnly(file_path);\n          if (mapping && mapping->IsValid()) {\n            callback(mapping->GetMapping(), mapping->GetSize());\n          } else {\n            callback(nullptr, 0);\n          }\n        });\n  } else {\n    ui_task_runner_->PostTask(\n        [callback = std::move(callback)]() { callback(nullptr, 0); });\n  }\n}\n\nvoid ResourceLoaderEmbedder::LoadByNet(\n    const std::string& url,\n    const std::function<void(const uint8_t*, size_t)>& callback,\n    const ResourceType resource_type) {\n  lynx_resource_request_t* fetcher_request =\n      lynx_resource_request_create_internal(\n          url, ConvertToLynxResourceType(resource_type));\n  lynx_resource_response_t* fetcher_response =\n      lynx_resource_response_create_internal(fml::MakeCopyable(\n          [callback = std::move(callback), ui_task_runner = ui_task_runner_](\n              lynx_resource_response_t* fetcher_response) mutable {\n            // Swap the response to make sure the response is valid when\n            // callback is called.\n            lynx_resource_response_t* response =\n                lynx_resource_response_create_swap(fetcher_response);\n            ui_task_runner->PostTask([callback, response]() mutable {\n              if (callback) {\n                callback(response->data.content, response->data.length);\n              }\n              lynx_resource_response_release(response);\n            });\n          }));\n  lynx_generic_resource_fetcher_fetch_resource(\n      fetcher_holder_->GenericFetcher(), fetcher_request, fetcher_response);\n}\n\nRawResource ResourceLoaderEmbedder::LoadSync(const std::string& src,\n                                             const ResourceType resource_type,\n                                             bool need_redirect) {\n  std::promise<RawResource> promise;\n  std::future<RawResource> future = promise.get_future();\n  Load(\n      src,\n      [&promise](const uint8_t* data, size_t size) {\n        promise.set_value(RawResource::MakeWithCopy(data, size));\n      },\n      resource_type, need_redirect);\n  return future.get();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/lynx_adaptor/resource_loader_embedder.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_RESOURCE_LOADER_EMBEDDER_H_\n#define CLAY_LYNX_ADAPTOR_RESOURCE_LOADER_EMBEDDER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/net/loader/resource_loader.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"platform/embedder/fetcher/lynx_resource_fetcher_holder.h\"\n\nnamespace clay {\n\nclass ResourceLoaderEmbedder : public ResourceLoader {\n public:\n  ResourceLoaderEmbedder(\n      fml::RefPtr<fml::TaskRunner> task_runner,\n      std::shared_ptr<ResourceLoaderIntercept> intercept,\n      std::shared_ptr<lynx::embedder::LynxResourceFetcherHolder>\n          fetcher_holder);\n  virtual ~ResourceLoaderEmbedder() = default;\n\n  void Load(const std::string& src,\n            const std::function<void(const uint8_t*, size_t)>& callback,\n            const ResourceType resource_type = ResourceType::kOthers,\n            bool need_redirect = false) override;\n\n  RawResource LoadSync(const std::string& src,\n                       const ResourceType = ResourceType::kOthers,\n                       bool need_redirect = false) override;\n\n private:\n  void LoadByNet(const std::string& url,\n                 const std::function<void(const uint8_t*, size_t)>& callback,\n                 const ResourceType resource_type);\n  fml::RefPtr<fml::TaskRunner> ui_task_runner_;\n  std::shared_ptr<ResourceLoaderIntercept> intercept_;\n  std::shared_ptr<lynx::embedder::LynxResourceFetcherHolder> fetcher_holder_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_LYNX_ADAPTOR_RESOURCE_LOADER_EMBEDDER_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/ui_delegate_clay.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/ui_delegate_clay.h\"\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/lynx_adaptor/layout_context_clay.h\"\n#include \"clay/lynx_adaptor/painting_context_clay.h\"\n#include \"clay/lynx_adaptor/perf_controller_clay.h\"\n#include \"clay/lynx_adaptor/prop_bundle_impl.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"core/services/timing_handler/timing.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nUIDelegateClay::UIDelegateClay(\n    clay::ViewContext* view_context,\n    std::unique_ptr<lynx::runtime::NativeModuleFactory> module_factory)\n    : view_context_(view_context), module_factory_(std::move(module_factory)) {\n  event_dispatcher_ = std::make_unique<clay::LynxEventDispatcher>();\n  view_context->SetEventDelegate(event_dispatcher_.get());\n}\n\nUIDelegateClay::~UIDelegateClay() {\n  if (view_context_->GetPageView() &&\n      view_context_->GetPageView()->GetEventDelegate() ==\n          this->event_dispatcher_.get()) {\n    view_context_->SetEventDelegate(nullptr);\n  }\n}\n\nstd::unique_ptr<PaintingCtxPlatformImpl>\nUIDelegateClay::CreatePaintingContext() {\n  auto painting_context = std::make_unique<PaintingContextClay>(view_context_);\n  painting_context_ = painting_context.get();\n  return painting_context;\n}\n\nstd::unique_ptr<LayoutCtxPlatformImpl> UIDelegateClay::CreateLayoutContext() {\n  auto layout_context = std::make_unique<LayoutContextClay>(view_context_);\n  layout_context_ = layout_context.get();\n  return layout_context;\n}\n\nstd::unique_ptr<PropBundleCreator> UIDelegateClay::CreatePropBundleCreator() {\n  return std::make_unique<PropBundleCreatorClay>();\n}\n\nstd::unique_ptr<runtime::NativeModuleFactory>\nUIDelegateClay::GetCustomModuleFactory() {\n  return std::move(module_factory_);\n}\n\nbool UIDelegateClay::UsesLogicalPixels() const {\n  return view_context_ && view_context_->UsesLogicalPixels();\n}\n\ndouble UIDelegateClay::GetScreenScaleFactor() const {\n  auto page_view = view_context_->GetPageView();\n  return page_view ? page_view->DevicePixelRatio() : 1.0;\n}\n\nvoid UIDelegateClay::OnLynxCreate(\n    const std::shared_ptr<shell::ListEngineProxy>& list_engine_proxy,\n    const std::shared_ptr<shell::LynxEngineProxy>& engine_proxy,\n    const std::shared_ptr<shell::LynxRuntimeProxy>& runtime_proxy,\n    const std::shared_ptr<shell::LynxLayoutProxy>& layout_proxy,\n    const std::shared_ptr<shell::PerfControllerProxy>& perf_controller_proxy,\n    const std::shared_ptr<pub::LynxResourceLoader>& resource_loader,\n    const fml::RefPtr<fml::TaskRunner>& ui_task_runner,\n    const fml::RefPtr<fml::TaskRunner>& layout_task_runner, int32_t instance_id,\n    bool is_embedded_mode) {\n  auto perf_controller =\n      std::make_shared<PerfControllerClay>(perf_controller_proxy, instance_id);\n  if (painting_context_) {\n    painting_context_->SetListEngineProxy(list_engine_proxy);\n    painting_context_->SetEngineProxy(engine_proxy);\n    painting_context_->SetRuntimeProxy(runtime_proxy);\n    auto ref = painting_context_->GetPlatformRef();\n    if (ref) {\n      auto* painting_context_ref =\n          static_cast<PaintingContextClayRef*>(ref.get());\n      painting_context_ref->SetPerfController(perf_controller);\n    }\n  }\n  if (layout_context_) {\n    layout_context_->SetEngineProxy(engine_proxy);\n    layout_context_->SetLayoutProxy(layout_proxy);\n  }\n  if (event_dispatcher_) {\n    event_dispatcher_->SetEngineProxy(engine_proxy);\n    event_dispatcher_->SetRuntimeProxy(runtime_proxy);\n    event_dispatcher_->SetPerfController(perf_controller);\n  }\n}\n\nvoid UIDelegateClay::TakeSnapshot(\n    size_t max_width, size_t max_height, int quality, float screen_scale_factor,\n    const lynx::fml::RefPtr<lynx::fml::TaskRunner>& screenshot_runner,\n    TakeSnapshotCompletedCallback callback) {\n  auto page_view = view_context_->GetPageView();\n  if (!page_view) {\n    return;\n  }\n  clay::ScreenshotRequest request;\n  request.max_width_ = max_width;\n  request.max_height_ = max_height;\n  request.quality_ = quality;\n  request.type_ =\n      quality >= 100 ? clay::ScreenshotType::PNG : clay::ScreenshotType::JPEG;\n  request.task_runner_ = screenshot_runner;\n  request.screen_scale_factor_ = screen_scale_factor;\n  request.is_sync_ = false;\n  request.callback_ = [callback = std::move(callback), screenshot_runner](\n                          clay::GrDataPtr data,\n                          const clay::ScreenMetadata& metadata) {\n    screenshot_runner->PostTask([data = std::move(data), metadata,\n                                 callback = std::move(callback)] {\n      float timestamp =\n          std::chrono::steady_clock::now().time_since_epoch().count();\n      std::string base64_str = std::string(\n          static_cast<const char*>(DATA_GET_DATA(data)), DATA_GET_SIZE(data));\n      callback(std::move(base64_str), timestamp, metadata.device_width_,\n               metadata.device_height_, metadata.page_scale_factor_);\n    });\n  };\n  page_view->TakeScreenshotHardware(request);\n}\n\nint UIDelegateClay::GetNodeForLocation(int x, int y) {\n  return view_context_->GetViewIdForLocation(x, y);\n}\n\nstd::vector<float> UIDelegateClay::GetTransformValue(\n    int id, const std::vector<float>& pad_border_margin_layout) {\n  std::vector<float> res(32, 0);\n  // res is std::vector<float>(32 ,0) passed by lynx\n  clay::BaseView* current_view = view_context_->GetViewById(id);\n  if (current_view == nullptr) {\n    return std::vector<float>();\n  }\n\n  clay::TransOffset arr;\n\n  // Returns the coordinates of the four types of boxes\n  // - border-box:\n  // That is, the width and height of the view set by the front-end, including\n  // content, padding, border, corresponding to the real rendering view\n  // - content-box: border-box with border and padding width and height removed\n  // - padding-box: border-box without the padding width and height of the box\n  // - margin-box: border-box plus margin box\n  for (int i = 0; i < 4; i++) {\n    if (i == 0) {\n      current_view->GetTransformValue(\n          pad_border_margin_layout[BoxModelOffset::PAD_LEFT] +\n              pad_border_margin_layout[BoxModelOffset::BORDER_LEFT] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_LEFT],\n          -pad_border_margin_layout[BoxModelOffset::PAD_RIGHT] -\n              pad_border_margin_layout[BoxModelOffset::BORDER_RIGHT] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_RIGHT],\n          pad_border_margin_layout[BoxModelOffset::PAD_TOP] +\n              pad_border_margin_layout[BoxModelOffset::BORDER_TOP] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_TOP],\n          -pad_border_margin_layout[BoxModelOffset::PAD_BOTTOM] -\n              pad_border_margin_layout[BoxModelOffset::BORDER_BOTTOM] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_BOTTOM],\n          arr);\n    } else if (i == 1) {\n      current_view->GetTransformValue(\n          pad_border_margin_layout[BoxModelOffset::BORDER_LEFT] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_LEFT],\n          -pad_border_margin_layout[BoxModelOffset::BORDER_RIGHT] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_RIGHT],\n          pad_border_margin_layout[BoxModelOffset::BORDER_TOP] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_TOP],\n          -pad_border_margin_layout[BoxModelOffset::BORDER_BOTTOM] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_BOTTOM],\n          arr);\n    } else if (i == 2) {\n      current_view->GetTransformValue(\n          pad_border_margin_layout[BoxModelOffset::LAYOUT_LEFT],\n          -pad_border_margin_layout[BoxModelOffset::LAYOUT_RIGHT],\n          pad_border_margin_layout[BoxModelOffset::LAYOUT_TOP],\n          -pad_border_margin_layout[BoxModelOffset::LAYOUT_BOTTOM], arr);\n    } else {\n      current_view->GetTransformValue(\n          -pad_border_margin_layout[BoxModelOffset::MARGIN_LEFT] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_LEFT],\n          pad_border_margin_layout[BoxModelOffset::MARGIN_RIGHT] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_RIGHT],\n          -pad_border_margin_layout[BoxModelOffset::MARGIN_TOP] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_TOP],\n          pad_border_margin_layout[BoxModelOffset::MARGIN_BOTTOM] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_BOTTOM],\n          arr);\n    }\n    res[i * 8] = arr.left_top[0];\n    res[i * 8 + 1] = arr.left_top[1];\n    res[i * 8 + 2] = arr.right_top[0];\n    res[i * 8 + 3] = arr.right_top[1];\n    res[i * 8 + 4] = arr.right_bottom[0];\n    res[i * 8 + 5] = arr.right_bottom[1];\n    res[i * 8 + 6] = arr.left_bottom[0];\n    res[i * 8 + 7] = arr.left_bottom[1];\n  }\n\n  return res;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/ui_delegate_clay.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_UI_DELEGATE_CLAY_H_\n#define CLAY_LYNX_ADAPTOR_UI_DELEGATE_CLAY_H_\n\n#include <memory>\n#include <vector>\n\n#include \"clay/lynx_adaptor/lynx_event_dispatcher.h\"\n#include \"core/public/lynx_resource_loader.h\"\n#include \"core/public/ui_delegate.h\"\n\nnamespace clay {\nclass ViewContext;\n}\n\nnamespace lynx {\nnamespace tasm {\n\nclass PaintingContextClay;\nclass LayoutContextClay;\n\nclass UIDelegateClay : public UIDelegate {\n public:\n  UIDelegateClay(\n      clay::ViewContext* view_context,\n      std::unique_ptr<lynx::runtime::NativeModuleFactory> module_factory);\n  ~UIDelegateClay() override;\n\n  std::unique_ptr<PaintingCtxPlatformImpl> CreatePaintingContext() override;\n  std::unique_ptr<LayoutCtxPlatformImpl> CreateLayoutContext() override;\n  std::unique_ptr<PropBundleCreator> CreatePropBundleCreator() override;\n  std::unique_ptr<runtime::NativeModuleFactory> GetCustomModuleFactory()\n      override;\n  bool UsesLogicalPixels() const override;\n\n  double GetScreenScaleFactor() const override;\n\n  void OnLynxCreate(\n      const std::shared_ptr<shell::ListEngineProxy>& list_engine_proxy,\n      const std::shared_ptr<shell::LynxEngineProxy>& engine_proxy,\n      const std::shared_ptr<shell::LynxRuntimeProxy>& runtime_proxy,\n      const std::shared_ptr<shell::LynxLayoutProxy>& layout_proxy,\n      const std::shared_ptr<shell::PerfControllerProxy>& perf_controller_proxy,\n      const std::shared_ptr<pub::LynxResourceLoader>& resource_loader,\n      const fml::RefPtr<fml::TaskRunner>& ui_task_runner,\n      const fml::RefPtr<fml::TaskRunner>& layout_task_runner,\n      int32_t instance_id, bool is_embedded_mode = false) override;\n\n  void TakeSnapshot(\n      size_t max_width, size_t max_height, int quality,\n      float screen_scale_factor,\n      const lynx::fml::RefPtr<lynx::fml::TaskRunner>& screenshot_runner,\n      TakeSnapshotCompletedCallback callback) override;\n\n  int GetNodeForLocation(int x, int y) override;\n\n  std::vector<float> GetTransformValue(\n      int id, const std::vector<float>& pad_border_margin_layout) override;\n\n  clay::ViewContext* GetViewContext() const { return view_context_; }\n\n private:\n  clay::ViewContext* view_context_;\n  std::unique_ptr<runtime::NativeModuleFactory> module_factory_;\n  std::unique_ptr<clay::LynxEventDispatcher> event_dispatcher_;\n  // Save a PaintingContextClay raw pointer to set the LynxEngineProxy and\n  // LynxRuntimeProxy objects after the Lynx instance is created.\n  // Its lifecycle is managed by Lynx.\n  PaintingContextClay* painting_context_ = nullptr;\n  LayoutContextClay* layout_context_ = nullptr;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_UI_DELEGATE_CLAY_H_\n"
  },
  {
    "path": "clay/lynx_adaptor/value_converter.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/lynx_adaptor/value_converter.h\"\n\n#include <cstdint>\n#include <cstring>\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/lynx_adaptor/base_def.h\"\n\nnamespace lynx {\n\nclay::Value ValueConverter::CreateClayValue(\n    const pub::Value& source,\n    std::vector<std::unique_ptr<pub::Value>>* prev_value_vector, int depth) {\n  if (source.IsNil()) {\n    return clay::Value::Null();\n  } else if (source.IsString()) {\n    const auto& s = source.str();\n    return clay::Value(s);\n  } else if (source.IsInt32()) {\n    return clay::Value{source.Int32()};\n  } else if (source.IsInt64()) {\n    return clay::Value{source.Int64()};\n  } else if (source.IsUInt32()) {\n    return clay::Value{source.UInt32()};\n  } else if (source.IsUInt64()) {\n    return clay::Value{static_cast<int64_t>(source.UInt64())};\n  } else if (source.IsNumber()) {\n    return clay::Value{source.Number()};\n  } else if (source.IsArrayBuffer()) {\n    size_t length = source.Length();\n    uint8_t* buffer = source.ArrayBuffer();\n    clay::Value::ArrayBuffer ab(length);\n    memcpy(ab.data(), buffer, length);\n    return clay::Value{std::move(ab)};\n  } else if (source.IsArray()) {\n    pub::ScopedCircleChecker scoped_circle_checker;\n    if (!scoped_circle_checker.CheckCircleOrCacheValue(prev_value_vector,\n                                                       source, depth)) {\n      clay::Value::Array array;\n      source.ForeachArray([&](int64_t i, const pub::Value& v) {\n        array.push_back(CreateClayValue(v, prev_value_vector, depth + 1));\n      });\n      return clay::Value(std::move(array));\n    }\n  } else if (source.IsMap()) {\n    if (source.backend_type() == pub::ValueBackendType::ValueBackendTypePiper &&\n        source.Contains(lynx::BIG_INT_VAL)) {\n      auto val_long = source.GetValueForKey(lynx::BIG_INT_VAL);\n      if (val_long) {\n        auto val_str = val_long->str();\n        if (!val_str.empty()) {\n          auto v = std::stoll(val_str.c_str(), nullptr, 0);\n          return clay::Value{static_cast<int64_t>(v)};\n        }\n      }\n      return clay::Value{};\n    }\n    pub::ScopedCircleChecker scoped_circle_checker;\n    if (!scoped_circle_checker.CheckCircleOrCacheValue(prev_value_vector,\n                                                       source, depth)) {\n      clay::Value::Map map;\n      source.ForeachMap([&](const pub::Value& k, const pub::Value& v) {\n        auto rk_value = CreateClayValue(v, prev_value_vector, depth + 1);\n        map.emplace(k.str(), std::move(rk_value));\n      });\n      return clay::Value{std::move(map)};\n    }\n  } else if (source.IsBool()) {\n    return clay::Value{source.Bool()};\n  } else if (source.IsUndefined()) {\n    return clay::Value{};\n  } else {\n    FML_DLOG(ERROR) << \"CreateClayValue unknown type \" << source.Type();\n  }\n  return clay::Value::Null();\n}\n\nclay::Value ValueConverter::CreateClayValue(const uint8_t* data, size_t size) {\n  clay::Value::Array array;\n  for (size_t i = 0; i < size; ++i) {\n    array.push_back(clay::Value{data[i]});\n  }\n  return clay::Value(std::move(array));\n}\n\nclay::Value ValueConverter::CreateClayValue(const uint32_t* data, size_t size) {\n  clay::Value::Array array;\n  for (size_t i = 0; i < size; ++i) {\n    array.push_back(clay::Value{data[i]});\n  }\n  return clay::Value(std::move(array));\n}\n\n#if OS_WIN || OS_MAC\nnamespace {\nbool GetLynxValueString(lynx_api_env env, lynx_value val, std::string& str) {\n  size_t length = 0;\n  if (lynx_api_ok ==\n      lynx_value_get_string_utf8(env, val, nullptr, 0, &length)) {\n    str.resize(length);\n    return lynx_api_ok ==\n           lynx_value_get_string_utf8(env, val, &str[0], length + 1, &length);\n  }\n  return false;\n}\n}  // namespace\n\nclay::Value ValueConverter::CreateClayValue(lynx_api_env env, lynx_value val) {\n  switch (val.type) {\n    case lynx_value_null:\n      return clay::Value::Null();\n    case lynx_value_undefined:\n      return clay::Value();\n    case lynx_value_bool:\n      return clay::Value(val.val_bool);\n    case lynx_value_double:\n      return clay::Value(val.val_double);\n    case lynx_value_int32:\n      return clay::Value(val.val_int32);\n    case lynx_value_uint32:\n      return clay::Value(val.val_uint32);\n    case lynx_value_int64:\n      return clay::Value(val.val_int64);\n    case lynx_value_uint64:\n      return clay::Value(int64_t(val.val_uint64));\n    case lynx_value_string: {\n      std::string str;\n      if (GetLynxValueString(env, val, str)) {\n        return clay::Value(std::move(str));\n      }\n      break;\n    }\n    case lynx_value_array: {\n      clay::Value::Array array;\n      uint32_t length;\n      lynx_value_get_array_length(env, val, &length);\n      for (uint32_t i = 0; i < length; i++) {\n        lynx_value element;\n        lynx_value_get_element(env, val, i, &element);\n        clay::Value copy = lynx::ValueConverter::CreateClayValue(env, element);\n        lynx_value_remove_reference(env, element, nullptr);\n        array.push_back(std::move(copy));\n      }\n      return clay::Value(std::move(array));\n    }\n    case lynx_value_map: {\n      clay::Value::Map map;\n      lynx_value val_names;\n      lynx_value_get_property_names(env, val, &val_names);\n      uint32_t length;\n      lynx_value_get_array_length(env, val_names, &length);\n      for (uint32_t i = 0; i < length; i++) {\n        std::string name;\n        lynx_value val_name;\n        lynx_value_get_element(env, val_names, i, &val_name);\n        GetLynxValueString(env, val_name, name);\n        lynx_value_remove_reference(env, val_name, nullptr);\n\n        lynx_value property;\n        lynx_value_get_named_property(env, val, name.c_str(), &property);\n        clay::Value copy = lynx::ValueConverter::CreateClayValue(env, property);\n        lynx_value_remove_reference(env, property, nullptr);\n\n        map[name] = std::move(copy);\n      }\n      lynx_value_remove_reference(env, val_names, nullptr);\n      return clay::Value(std::move(map));\n    }\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR) << \"unsupported lynx_value type: \" << val.type;\n  return clay::Value();\n}\n\nlynx_value ValueConverter::CreateLynxValue(lynx_api_env env,\n                                           const clay::Value& val) {\n  switch (val.type()) {\n    case clay::Value::kNone:\n      return {.val_ptr = 0, .type = lynx_value_undefined};\n    case clay::Value::kPointer:\n      return {.val_ptr = 0, .type = lynx_value_null};\n    case clay::Value::kBool:\n      return {.val_bool = val.GetBool(), .type = lynx_value_bool};\n    case clay::Value::kInt:\n      return {.val_int32 = val.GetInt(), .type = lynx_value_int32};\n    case clay::Value::kUInt:\n      return {.val_uint32 = val.GetUint(), .type = lynx_value_uint32};\n    case clay::Value::kLong:\n      return {.val_int64 = val.GetLong(), .type = lynx_value_int64};\n    case clay::Value::kFloat:\n      return {.val_double = val.GetFloat(), .type = lynx_value_double};\n    case clay::Value::kDouble:\n      return {.val_double = val.GetDouble(), .type = lynx_value_double};\n    case clay::Value::kString: {\n      lynx_value result;\n      const auto& str = val.GetString();\n      lynx_value_create_string_utf8(env, str.c_str(), str.size(), &result);\n      return result;\n    }\n    case clay::Value::kArray:\n      return CreateLynxValue(env, val.GetArray());\n    case clay::Value::kMap:\n      return CreateLynxValue(env, val.GetMap());\n    default:\n      break;\n  }\n\n  FML_LOG(ERROR) << \"unsupported clay::Value type: \" << val.type();\n  return {.val_ptr = 0, .type = lynx_value_undefined};\n}\n\nlynx_value ValueConverter::CreateLynxValue(lynx_api_env env,\n                                           const clay::Value::Map& map) {\n  lynx_value val_map;\n  lynx_value_create_map(env, &val_map);\n  for (const auto& [key, val] : map) {\n    lynx_value val_property = CreateLynxValue(env, val);\n    lynx_value_set_named_property(env, val_map, key.c_str(), val_property);\n    lynx_value_remove_reference(env, val_property, nullptr);\n  }\n  return val_map;\n}\n\nlynx_value ValueConverter::CreateLynxValue(lynx_api_env env,\n                                           const clay::Value::Array& array) {\n  lynx_value val_array;\n  lynx_value_create_array(env, &val_array);\n  uint32_t i = 0;\n  for (const auto& element : array) {\n    lynx_value val_element = CreateLynxValue(env, element);\n    lynx_value_set_element(env, val_array, i++, val_element);\n    lynx_value_remove_reference(env, val_element, nullptr);\n  }\n  return val_array;\n}\n#endif\n\n}  // namespace lynx\n"
  },
  {
    "path": "clay/lynx_adaptor/value_converter.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_LYNX_ADAPTOR_VALUE_CONVERTER_H_\n#define CLAY_LYNX_ADAPTOR_VALUE_CONVERTER_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/value/lynx_value_api.h\"\n#include \"clay/public/value.h\"\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\n\nclass ValueConverter {\n public:\n  static clay::Value CreateClayValue(\n      const pub::Value& source,\n      std::vector<std::unique_ptr<pub::Value>>* prev_value_vector = nullptr,\n      int depth = 0);\n\n  static clay::Value CreateClayValue(const uint8_t* data, size_t size);\n  static clay::Value CreateClayValue(const uint32_t* data, size_t size);\n\n  static clay::Value CreateClayValue(lynx_api_env env, lynx_value val);\n  static lynx_value CreateLynxValue(lynx_api_env env, const clay::Value& val);\n  static lynx_value CreateLynxValue(lynx_api_env env,\n                                    const clay::Value::Map& map);\n  static lynx_value CreateLynxValue(lynx_api_env env,\n                                    const clay::Value::Array& array);\n\n private:\n};\n\n}  // namespace lynx\n\n#endif  // CLAY_LYNX_ADAPTOR_VALUE_CONVERTER_H_\n"
  },
  {
    "path": "clay/memory/BUILD.gn",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../common/config.gni\")\nimport(\"../testing/testing.gni\")\n\nsource_set(\"memory\") {\n  sources = [\n    \"discardable_memory.h\",\n    \"discardable_memory_allocator.h\",\n    \"discardable_memory_allocator_impl.cc\",\n    \"discardable_memory_allocator_impl.h\",\n    \"memory_pressure_listener.cc\",\n    \"memory_pressure_listener.h\",\n  ]\n  if (is_win || is_mac || is_android) {\n    sources += [ \"discardable_memory_impl.h\" ]\n  } else {\n    sources += [ \"heap_discardable_memory.h\" ]\n  }\n  if (is_win || is_mac) {\n    sources += [\n      \"memory_pressure_monitor.cc\",\n      \"memory_pressure_monitor.h\",\n      \"system_memory_pressure_evaluator.cc\",\n      \"system_memory_pressure_evaluator.h\",\n    ]\n    if (is_win) {\n      sources += [\n        \"discardable_memory_impl_win.cc\",\n        \"system_memory_pressure_evaluator_win.cc\",\n        \"system_memory_pressure_evaluator_win.h\",\n      ]\n    }\n    if (is_mac) {\n      sources += [\n        \"system_memory_pressure_evaluator_mac.cc\",\n        \"system_memory_pressure_evaluator_mac.h\",\n      ]\n    }\n  }\n  if (is_android || is_mac) {\n    sources += [ \"discardable_memory_impl_posix.cc\" ]\n  }\n  public_deps = [ \"../fml:fml\" ]\n  public_configs = [ \"../:config\" ]\n}\n\nif (enable_unittests) {\n  test_fixtures(\"memory_fixtures\") {\n    fixtures = []\n  }\n\n  executable(\"memory_unittests\") {\n    testonly = true\n    sources = [\n      \"discardable_memory_allocator_unittests.cc\",\n      \"memory_pressure_listener_unittests.cc\",\n    ]\n\n    if (is_win || is_mac || is_android) {\n      sources += [ \"discardable_memory_unittests.cc\" ]\n    } else {\n      sources += [ \"heap_discardable_memory_unittests.cc\" ]\n    }\n\n    if (is_mac) {\n      sources += [ \"system_memory_pressure_evaluator_mac_unittests.cc\" ]\n    }\n\n    if (is_win) {\n      sources += [ \"system_memory_pressure_evaluator_win_unittests.cc\" ]\n    }\n\n    deps = [\n      \":memory\",\n      \":memory_fixtures\",\n      \"../../base/src:base_static\",\n      \"../testing\",\n    ]\n  }\n}\n"
  },
  {
    "path": "clay/memory/discardable_memory.h",
    "content": "// Copyright 2014 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_MEMORY_DISCARDABLE_MEMORY_H_\n#define CLAY_MEMORY_DISCARDABLE_MEMORY_H_\n\n#include <cstddef>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace clay {\n\nclass DiscardableMemory {\n public:\n  DiscardableMemory() = default;\n  virtual ~DiscardableMemory() = default;\n\n  // Locks the memory so that it will not be purged by the system. Returns\n  // true on success. If the return value is false then this object should be\n  // destroyed and a new one should be created.\n  [[nodiscard]] virtual bool Lock() = 0;\n\n  // Unlocks the memory so that it can be purged by the system. Must be called\n  // after every successful lock call.\n  virtual void Unlock() = 0;\n\n  // Returns the memory address held by this object. The object must be locked\n  // before calling this.\n  virtual void* data() const = 0;\n\n  virtual bool isLocked() const = 0;\n\n  virtual size_t GetSize() const = 0;\n\n  // Handy method to simplify calling data() with a reinterpret_cast.\n  template <typename T>\n  T* data_as() const {\n    return reinterpret_cast<T*>(data());\n  }\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(DiscardableMemory);\n};\n\n}  // namespace clay\n#endif  // CLAY_MEMORY_DISCARDABLE_MEMORY_H_\n"
  },
  {
    "path": "clay/memory/discardable_memory_allocator.h",
    "content": "// Copyright 2015 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_H_\n#define CLAY_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_H_\n\n#include <functional>\n#include <memory>\n\n#include \"clay/memory/discardable_memory.h\"\n\nnamespace clay {\nclass DiscardableMemoryAllocator {\n public:\n  using OnNoMemCallback = std::function<void()>;\n  DiscardableMemoryAllocator() = default;\n  virtual ~DiscardableMemoryAllocator() = default;\n\n  // Creates an initially-locked instance of discardable memory.\n  // If the platform supports madvise(MADV_FREE) like Android,\n  // platform-specific techniques will be used to discard memory under pressure.\n  // Otherwise, discardable memory is emulated and manually discarded\n  // heuristically (via memory pressure notifications).\n  virtual std::unique_ptr<DiscardableMemory> AllocateLockedDiscardableMemory(\n      size_t size) = 0;\n\n  // Gets the total number of bytes allocated by this allocator which have not\n  // been discarded.\n  virtual size_t GetBytesAllocated() const = 0;\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryAllocator);\n};\n}  // namespace clay\n\n#endif  // CLAY_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_H_\n"
  },
  {
    "path": "clay/memory/discardable_memory_allocator_impl.cc",
    "content": "// Copyright 2019 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/memory/discardable_memory_allocator_impl.h\"\n\n#include \"base/include/no_destructor.h\"\n#include \"build/build_config.h\"\n\n#if OS_ANDROID || OS_MAC || OS_WIN\n#include \"clay/memory/discardable_memory_impl.h\"\n#else\n#include \"clay/memory/heap_discardable_memory.h\"\n#endif\n\nnamespace clay {\n\nDiscardableMemoryAllocatorImpl::DiscardableMemoryAllocatorImpl() = default;\nDiscardableMemoryAllocatorImpl::~DiscardableMemoryAllocatorImpl() = default;\n\nDiscardableMemoryAllocator& DiscardableMemoryAllocatorImpl::GetInstance() {\n  static fml::NoDestructor<DiscardableMemoryAllocatorImpl> instance;\n  return *instance;\n}\n\nstd::unique_ptr<DiscardableMemory>\nDiscardableMemoryAllocatorImpl::AllocateLockedDiscardableMemory(size_t size) {\n#if OS_ANDROID || OS_MAC || OS_WIN\n  return std::make_unique<DiscardableMemoryImpl>(size, &bytes_allocated_);\n#else\n  return std::make_unique<HeapDiscardableMemory>(size);\n#endif\n}\n\nsize_t DiscardableMemoryAllocatorImpl::GetBytesAllocated() const {\n  return bytes_allocated_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/discardable_memory_allocator_impl.h",
    "content": "// Copyright 2019 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_IMPL_H_\n#define CLAY_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_IMPL_H_\n\n#include <atomic>\n#include <memory>\n\n#include \"clay/memory/discardable_memory_allocator.h\"\n\nnamespace clay {\nclass DiscardableMemoryAllocatorImpl : public DiscardableMemoryAllocator {\n public:\n  DiscardableMemoryAllocatorImpl();\n  ~DiscardableMemoryAllocatorImpl() override;\n\n  static DiscardableMemoryAllocator& GetInstance();\n\n  std::unique_ptr<DiscardableMemory> AllocateLockedDiscardableMemory(\n      size_t size) override;\n\n  size_t GetBytesAllocated() const override;\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryAllocatorImpl);\n\n  std::atomic<size_t> bytes_allocated_{0};\n};\n}  // namespace clay\n\n#endif  // CLAY_MEMORY_DISCARDABLE_MEMORY_ALLOCATOR_IMPL_H_\n"
  },
  {
    "path": "clay/memory/discardable_memory_allocator_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"build/build_config.h\"\n#include \"clay/memory/discardable_memory_allocator_impl.h\"\n\n#if OS_ANDROID || OS_MAC\n#include <sys/mman.h>\n#elif OS_WIN\n#include <windows.h>\n#endif\n\n#if OS_ANDROID || OS_MAC || OS_WIN\n#include \"clay/memory/discardable_memory_impl.h\"\n#else\n#include \"clay/memory/heap_discardable_memory.h\"\n#endif\n\n#include <cstring>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\n#if OS_ANDROID || OS_MAC || OS_WIN\nnamespace {\n\nsize_t GetPageSize() {\n#if OS_ANDROID || OS_MAC\n#if defined(_SC_PAGESIZE)\n  return sysconf(_SC_PAGESIZE);\n#else\n  return getpagesize();\n#endif\n#elif OS_WIN\n  SYSTEM_INFO si;\n  GetSystemInfo(&si);\n  return si.dwPageSize;\n#else\n#error \"discardable memory is not supported on this platform.\"\n#endif\n}\n\nconst size_t kPageSize = GetPageSize();\n\n}  // namespace\n\nTEST(DiscardableMemoryAllocatorImplTest, TestAllocateAndUseMemory) {\n  DiscardableMemoryAllocatorImpl allocator;\n\n  // Allocate 4 pages.\n  std::unique_ptr<DiscardableMemory> mem1 =\n      allocator.AllocateLockedDiscardableMemory(kPageSize * 3 + 1);\n\n  EXPECT_TRUE(static_cast<DiscardableMemoryImpl*>(mem1.get())->is_locked_);\n  EXPECT_EQ(allocator.GetBytesAllocated(), kPageSize * 3 + 1);\n\n  // Allocate 3 pages.\n  std::unique_ptr<DiscardableMemory> mem2 =\n      allocator.AllocateLockedDiscardableMemory(kPageSize * 3);\n\n  EXPECT_TRUE(static_cast<DiscardableMemoryImpl*>(mem2.get())->is_locked_);\n  EXPECT_EQ(allocator.GetBytesAllocated(), kPageSize * 6 + 1);\n\n  mem1.reset();\n  EXPECT_EQ(allocator.GetBytesAllocated(), kPageSize * 3);\n\n  // Test writing and reading of discardable memory.\n  const char test_pattern[] = \"TestDiscardableMemory\";\n  char buffer[sizeof(test_pattern)];\n\n  void* data = mem2->data();\n  memcpy(data, test_pattern, sizeof(test_pattern));\n  memcpy(buffer, mem2->data_as<char>(), sizeof(test_pattern));\n\n  EXPECT_EQ(memcmp(test_pattern, buffer, sizeof(test_pattern)), 0);\n}\n#else\n// Test allocating HeapDiscardableMemory.\nconst size_t kPageSize = 1024;\nTEST(DiscardableMemoryAllocatorImplTest, TestAllocateAndUseMemory) {\n  DiscardableMemoryAllocatorImpl allocator;\n  auto mem1 = allocator.AllocateLockedDiscardableMemory(kPageSize * 3 + 1);\n  EXPECT_TRUE(static_cast<HeapDiscardableMemory*>(mem1.get()));\n  EXPECT_NE(static_cast<HeapDiscardableMemory*>(mem1.get())->memory_, nullptr);\n  EXPECT_EQ(static_cast<HeapDiscardableMemory*>(mem1.get())->size_,\n            kPageSize * 3 + 1);\n}\n#endif  // OS_ANDROID || OS_MAC || OS_WIN\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/discardable_memory_impl.h",
    "content": "// Copyright 2019 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_MEMORY_DISCARDABLE_MEMORY_IMPL_H_\n#define CLAY_MEMORY_DISCARDABLE_MEMORY_IMPL_H_\n\n#include <atomic>\n#include <vector>\n\n#include \"clay/memory/discardable_memory.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\nclass DiscardableMemoryImpl : public DiscardableMemory {\n public:\n  DiscardableMemoryImpl(size_t size_in_bytes,\n                        std::atomic<size_t>* allocator_byte_count);\n  ~DiscardableMemoryImpl() override;\n\n  bool Lock() override;\n  void Unlock() override;\n  void* data() const override;\n\n  bool IsValid() const;\n  // Gets whether this instance has been discarded (but not yet unmapped).\n  bool IsDiscarded() const;\n  // Get whether all pages in this discardable memory instance are resident.\n  bool IsResident() const;\n\n  bool isLocked() const override { return is_locked_; }\n\n  size_t GetSize() const override { return size_in_bytes_; }\n\n private:\n  FRIEND_TEST(DiscardableMemoryAllocatorImplTest, TestAllocateAndUseMemory);\n  FRIEND_TEST(DiscardableMemoryImplTest, TestAllocateAndUse);\n  FRIEND_TEST(DiscardableMemoryImplTest, TestLockAndUnlock);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(DiscardableMemoryImpl);\n\n  bool LockPage(size_t page_index);\n  void UnlockPage(size_t page_index);\n\n  bool Deallocate();\n\n  const size_t size_in_bytes_;\n  const size_t allocated_pages_;\n\n  // Data comes from mmap.\n  void* data_;\n  // data_ is locked initially and can be read and written.\n  // However after UnLock, accessing data_ will be and undefined behavior.\n  bool is_locked_ = true;\n\n  // Pointer to allocator memory usage metric for updating upon allocation and\n  // destruction.\n  std::atomic<size_t>* allocator_byte_count_;\n\n  // Stores the first word of a page for use during locking.\n  std::vector<std::atomic<intptr_t>> page_first_word_;\n};\n}  // namespace clay\n\n#endif  // CLAY_MEMORY_DISCARDABLE_MEMORY_IMPL_H_\n"
  },
  {
    "path": "clay/memory/discardable_memory_impl_posix.cc",
    "content": "// Copyright 2019 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <sys/mman.h>\n#include <unistd.h>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/memory/discardable_memory_impl.h\"\n\n#if OS_ANDROID\n#include <sys/prctl.h>\n#endif\n\nnamespace {\n\nconstexpr intptr_t kPageMagicCookie = 1;\n\nsize_t GetPageSize() {\n#if defined(_SC_PAGESIZE)\n  return sysconf(_SC_PAGESIZE);\n#else\n  return getpagesize();\n#endif\n}\n\nvoid* AllocatePages(size_t size_in_pages) {\n  const size_t length = size_in_pages * GetPageSize();\n  void* data = mmap(nullptr, length, PROT_READ | PROT_WRITE,\n                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);\n  FML_DCHECK(data != MAP_FAILED);\n\n#if OS_ANDROID\n  prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, data, length,\n        \"madv-free-discardable\");\n#endif\n  return data;\n}\n}  // namespace\n\nnamespace clay {\n\nDiscardableMemoryImpl::DiscardableMemoryImpl(\n    size_t size_in_bytes, std::atomic<size_t>* allocator_byte_count)\n    : size_in_bytes_(size_in_bytes),\n      allocated_pages_((size_in_bytes_ + GetPageSize() - 1) / GetPageSize()),\n      allocator_byte_count_(allocator_byte_count),\n      page_first_word_((size_in_bytes_ + GetPageSize() - 1) / GetPageSize()) {\n  data_ = AllocatePages(allocated_pages_);\n  (*allocator_byte_count_) += size_in_bytes_;\n}\nDiscardableMemoryImpl::~DiscardableMemoryImpl() { Deallocate(); }\n\nbool DiscardableMemoryImpl::Lock() {\n  FML_DCHECK(!is_locked_);\n  // Locking fails if the memory has been deallocated.\n  if (!data_) {\n    return false;\n  }\n\n  size_t page_index;\n  for (page_index = 0; page_index < allocated_pages_; ++page_index) {\n    if (!LockPage(page_index)) break;\n  }\n\n  if (page_index < allocated_pages_) {\n    FML_LOG(ERROR) << \"Region eviction discovered during lock with \"\n                   << allocated_pages_ << \" pages\";\n    Deallocate();\n    return false;\n  }\n  FML_DCHECK(IsResident());\n\n  is_locked_ = true;\n  return true;\n}\n\nvoid DiscardableMemoryImpl::Unlock() {\n  if (!is_locked_) {\n    return;\n  }\n  FML_DCHECK(data_ != nullptr);\n\n  for (size_t page_index = 0; page_index < allocated_pages_; ++page_index) {\n    UnlockPage(page_index);\n  }\n\n#ifdef MADV_FREE\n#define MADV_ARGUMENT MADV_FREE\n#else\n#define MADV_ARGUMENT MADV_DONTNEED\n#endif\n  int retval = madvise(data_, allocated_pages_ * GetPageSize(), MADV_ARGUMENT);\n  FML_DCHECK(!retval);\n  is_locked_ = false;\n}\n\nvoid* DiscardableMemoryImpl::data() const {\n  FML_DCHECK(is_locked_);\n  FML_DCHECK(data_ != nullptr);\n\n  return data_;\n}\n\nbool DiscardableMemoryImpl::LockPage(size_t page_index) {\n  // We require the byte-level representation of std::atomic<intptr_t> to be\n  // equivalent to that of an intptr_t. Since std::atomic<intptr_t> has standard\n  // layout, having equal size is sufficient but not necessary for them to have\n  // the same byte-level representation.\n  static_assert(sizeof(intptr_t) == sizeof(std::atomic<intptr_t>),\n                \"Incompatible layout of std::atomic.\");\n  FML_DCHECK(std::atomic<intptr_t>{}.is_lock_free());\n  std::atomic<intptr_t>* page_as_atomic =\n      reinterpret_cast<std::atomic<intptr_t>*>(static_cast<uint8_t*>(data_) +\n                                               page_index * GetPageSize());\n\n  intptr_t expected = kPageMagicCookie;\n\n  // Recall that we set the first word of the page to |kPageMagicCookie|\n  // (non-zero) during unlocking. Thus, if the value has changed, the page has\n  // been discarded. Restore the page's original first word from before\n  // unlocking only if the page has not been discarded.\n  if (!std::atomic_compare_exchange_strong_explicit(\n          page_as_atomic, &expected,\n          static_cast<intptr_t>(page_first_word_[page_index]),\n          std::memory_order_relaxed, std::memory_order_relaxed)) {\n    return false;\n  }\n\n  return true;\n}\n\nvoid DiscardableMemoryImpl::UnlockPage(size_t page_index) {\n  FML_DCHECK(std::atomic<intptr_t>{}.is_lock_free());\n\n  std::atomic<intptr_t>* page_as_atomic =\n      reinterpret_cast<std::atomic<intptr_t>*>(static_cast<uint8_t*>(data_) +\n                                               page_index * GetPageSize());\n\n  // Store the first word of the page for use during unlocking.\n  page_first_word_[page_index].store(*page_as_atomic,\n                                     std::memory_order_relaxed);\n  // Store a non-zero value into the first word of the page, so we can tell when\n  // the page is discarded during locking.\n  page_as_atomic->store(kPageMagicCookie, std::memory_order_relaxed);\n}\n\nbool DiscardableMemoryImpl::IsValid() const { return data_ != nullptr; }\n\nbool DiscardableMemoryImpl::IsResident() const {\n#if OS_MAC\n  std::vector<char> vec(allocated_pages_);\n#elif OS_ANDROID\n  std::vector<unsigned char> vec(allocated_pages_);\n#endif\n\n  int retval = mincore(data_, allocated_pages_ * GetPageSize(), vec.data());\n  FML_DCHECK(retval == 0 || errno == EAGAIN);\n\n  for (size_t i = 0; i < allocated_pages_; ++i) {\n    if (!(vec[i] & 1)) return false;\n  }\n  return true;\n}\n\nbool DiscardableMemoryImpl::IsDiscarded() const {\n  return !is_locked_ && !IsResident();\n}\n\nbool DiscardableMemoryImpl::Deallocate() {\n  if (data_) {\n    int retval = munmap(data_, allocated_pages_ * GetPageSize());\n    FML_DCHECK(!retval);\n    data_ = nullptr;\n    (*allocator_byte_count_) -= size_in_bytes_;\n    return true;\n  }\n  return false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/discardable_memory_impl_win.cc",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <windows.h>\n\n#include <cstddef>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/memory/discardable_memory_impl.h\"\n\nnamespace {\n\nconstexpr intptr_t kPageMagicCookie = 1;\n\nsize_t GetPageSize() {\n  SYSTEM_INFO si;\n  GetSystemInfo(&si);\n  return si.dwPageSize;\n}\n\nvoid* AllocatePages(size_t size_in_pages) {\n  const size_t length = size_in_pages * GetPageSize();\n  void* data =\n      VirtualAlloc(nullptr, length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);\n  FML_DCHECK(data != nullptr);\n\n  return data;\n}\n\n}  // namespace\n\nnamespace clay {\n\nDiscardableMemoryImpl::DiscardableMemoryImpl(\n    size_t size_in_bytes, std::atomic<size_t>* allocator_byte_count)\n    : size_in_bytes_(size_in_bytes),\n      allocated_pages_((size_in_bytes_ + GetPageSize() - 1) / GetPageSize()),\n      allocator_byte_count_(allocator_byte_count),\n      page_first_word_((size_in_bytes_ + GetPageSize() - 1) / GetPageSize()) {\n  data_ = AllocatePages(allocated_pages_);\n  (*allocator_byte_count_) += size_in_bytes_;\n}\n\nDiscardableMemoryImpl::~DiscardableMemoryImpl() { Deallocate(); }\n\nbool DiscardableMemoryImpl::Lock() {\n  FML_DCHECK(!is_locked_);\n  // Locking fails if the memory has been deallocated.\n  if (!data_) {\n    return false;\n  }\n\n  size_t page_index;\n  for (page_index = 0; page_index < allocated_pages_; ++page_index) {\n    if (!LockPage(page_index)) break;\n  }\n\n  if (page_index < allocated_pages_) {\n    FML_LOG(ERROR) << \"Region eviction discovered during lock with \"\n                   << allocated_pages_ << \" pages\";\n    Deallocate();\n    return false;\n  }\n  FML_DCHECK(IsResident());\n\n  is_locked_ = true;\n  return true;\n}\nvoid DiscardableMemoryImpl::Unlock() {\n  FML_DCHECK(is_locked_);\n  FML_DCHECK(data_ != nullptr);\n\n  for (size_t page_index = 0; page_index < allocated_pages_; ++page_index) {\n    UnlockPage(page_index);\n  }\n\n  // MEM_RESET is like madv_free in posix systems. This won't immediately clear\n  // the pages or unmap between the virtual and physical pages, but it just let\n  // the OS know that these pages can be unmapped if is under memory pressure.\n  VirtualAlloc(data_, allocated_pages_ * GetPageSize(), MEM_RESET,\n               PAGE_READWRITE);\n\n  is_locked_ = false;\n}\n\nvoid* DiscardableMemoryImpl::data() const {\n  FML_DCHECK(is_locked_);\n  FML_DCHECK(data_ != nullptr);\n\n  return data_;\n}\n\nbool DiscardableMemoryImpl::LockPage(size_t page_index) {\n  // We require the byte-level representation of std::atomic<intptr_t> to be\n  // equivalent to that of an intptr_t. Since std::atomic<intptr_t> has standard\n  // layout, having equal size is sufficient but not necessary for them to have\n  // the same byte-level representation.\n  static_assert(sizeof(intptr_t) == sizeof(std::atomic<intptr_t>),\n                \"Incompatible layout of std::atomic.\");\n  FML_DCHECK(std::atomic<intptr_t>{}.is_lock_free());\n  std::atomic<intptr_t>* page_as_atomic =\n      reinterpret_cast<std::atomic<intptr_t>*>(static_cast<uint8_t*>(data_) +\n                                               page_index * GetPageSize());\n\n  intptr_t expected = kPageMagicCookie;\n\n  // Recall that we set the first word of the page to |kPageMagicCookie|\n  // (non-zero) during unlocking. Thus, if the value has changed, the page has\n  // been discarded. Restore the page's original first word from before\n  // unlocking only if the page has not been discarded.\n  if (!std::atomic_compare_exchange_strong_explicit(\n          page_as_atomic, &expected,\n          static_cast<intptr_t>(page_first_word_[page_index]),\n          std::memory_order_relaxed, std::memory_order_relaxed)) {\n    return false;\n  }\n\n  return true;\n}\n\nvoid DiscardableMemoryImpl::UnlockPage(size_t page_index) {\n  FML_DCHECK(std::atomic<intptr_t>{}.is_lock_free());\n\n  std::atomic<intptr_t>* page_as_atomic =\n      reinterpret_cast<std::atomic<intptr_t>*>(static_cast<uint8_t*>(data_) +\n                                               page_index * GetPageSize());\n\n  // Store the first word of the page for use during unlocking.\n  page_first_word_[page_index].store(*page_as_atomic,\n                                     std::memory_order_relaxed);\n  // Store a non-zero value into the first word of the page, so we can tell when\n  // the page is discarded during locking.\n  page_as_atomic->store(kPageMagicCookie, std::memory_order_relaxed);\n}\n\nbool DiscardableMemoryImpl::IsValid() const { return data_ != nullptr; }\n\nbool DiscardableMemoryImpl::IsResident() const {\n  MEMORY_BASIC_INFORMATION mbi;\n  SIZE_T dwRes = VirtualQuery(data_, &mbi, sizeof(mbi));\n  if (dwRes == 0) {\n    FML_LOG(ERROR) << \"VirtualQuery failed, error: \" << GetLastError();\n  }\n  // MEM_COMMIT indicates committed pages for which physical storage has been\n  // allocated, either in memory or in the paging file on disk.\n  return (mbi.State == MEM_COMMIT);\n}\n\nbool DiscardableMemoryImpl::IsDiscarded() const {\n  return !is_locked_ && !IsResident();\n}\n\nbool DiscardableMemoryImpl::Deallocate() {\n  if (data_) {\n    VirtualFree(data_, 0, MEM_RELEASE);\n    data_ = nullptr;\n    (*allocator_byte_count_) -= size_in_bytes_;\n    return true;\n  }\n  return false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/discardable_memory_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"build/build_config.h\"\n#include \"clay/memory/discardable_memory_impl.h\"\n\n#if OS_ANDROID || OS_MAC\n#include <sys/mman.h>\n#elif OS_WIN\n#include <windows.h>\n#endif\n\n#include <cstring>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace {\n\nsize_t GetPageSize() {\n#if OS_ANDROID || OS_MAC\n#if defined(_SC_PAGESIZE)\n  return sysconf(_SC_PAGESIZE);\n#else\n  return getpagesize();\n#endif\n#elif OS_WIN\n  SYSTEM_INFO si;\n  GetSystemInfo(&si);\n  return si.dwPageSize;\n#else\n#error \"Unsupported platform for discardable_memory_unittests\";\n#endif\n}\n\nconst size_t kPageSize = GetPageSize();\nconst char test_pattern[] =\n    \"fhjasfhasjkdfhasdkgasdsajfksaljrw4i5j23krkasfk234123ntklesfasKASflkSFKLSD\";\n}  // namespace\n\nTEST(DiscardableMemoryImplTest, TestAllocateAndUse) {\n  std::atomic<size_t> allocator_byte_count{};\n  std::unique_ptr<DiscardableMemoryImpl> mem =\n      std::make_unique<DiscardableMemoryImpl>(1 * kPageSize,\n                                              &allocator_byte_count);\n  EXPECT_TRUE(mem->IsValid());\n  EXPECT_TRUE(mem->is_locked_);\n\n  char buffer[sizeof(test_pattern)];\n  void* data = mem->data();\n  memcpy(data, test_pattern, sizeof(test_pattern));\n  memcpy(buffer, mem->data_as<char>(), sizeof(test_pattern));\n\n  EXPECT_EQ(memcmp(test_pattern, buffer, sizeof(test_pattern)), 0);\n}\n\nTEST(DiscardableMemoryImplTest, TestLockAndUnlock) {\n  std::atomic<size_t> allocator_byte_count{};\n  std::unique_ptr<DiscardableMemoryImpl> mem =\n      std::make_unique<DiscardableMemoryImpl>(10 * kPageSize,\n                                              &allocator_byte_count);\n  EXPECT_TRUE(mem->IsValid());\n  EXPECT_TRUE(mem->is_locked_);\n\n  memset(mem->data(), 0xE7, 10 * kPageSize);\n\n  mem->Unlock();\n  EXPECT_FALSE(mem->is_locked_);\n  bool result = mem->Lock();\n  // If Lock() succeeded, the memory region should be valid. If Lock() failed,\n  // the memory region should be invalid.\n  EXPECT_EQ(result, mem->IsValid());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/heap_discardable_memory.h",
    "content": "// Copyright 2016 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_MEMORY_HEAP_DISCARDABLE_MEMORY_H_\n#define CLAY_MEMORY_HEAP_DISCARDABLE_MEMORY_H_\n\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/memory/discardable_memory.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass HeapDiscardableMemory : public DiscardableMemory {\n public:\n  explicit HeapDiscardableMemory(size_t size)\n      : memory_(new char[size]), size_(size) {}\n\n  ~HeapDiscardableMemory() override = default;\n\n  [[nodiscard]] bool Lock() override {\n    // Locking only succeeds when we have not yet discarded the memory (i.e. if\n    // we have never called |Unlock()|.)\n    return memory_ != nullptr;\n  }\n\n  void Unlock() override { Discard(); }\n\n  void* data() const override {\n    FML_DCHECK(memory_);\n    return static_cast<void*>(memory_.get());\n  }\n\n  bool isLocked() const override { return true; }\n\n  size_t GetSize() const override { return size_; }\n\n private:\n  FRIEND_TEST(HeapDiscardableMemoryTest, TestAllocateAndUse);\n  FRIEND_TEST(HeapDiscardableMemoryTest, TestLockAndUnlock);\n  FRIEND_TEST(DiscardableMemoryAllocatorImplTest, TestAllocateAndUseMemory);\n\n  void Discard() {\n    memory_.reset();\n    size_ = 0;\n  }\n  std::unique_ptr<char[]> memory_;\n  size_t size_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_MEMORY_HEAP_DISCARDABLE_MEMORY_H_\n"
  },
  {
    "path": "clay/memory/heap_discardable_memory_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstring>\n\n#include \"clay/memory/heap_discardable_memory.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nconst size_t kTestPageSize = 1024;\nconst char test_pattern[] =\n    \"fhjasfhasjkdfhasdkgasdsajfksaljrw4i5j23krkasfk234123ntklesfasKASflkSFKLSD\";\n\nTEST(HeapDiscardableMemoryTest, TestAllocateAndUse) {\n  auto mem = std::make_unique<HeapDiscardableMemory>(1 * kTestPageSize);\n  EXPECT_EQ(mem->size_, 1 * kTestPageSize);\n  EXPECT_NE(mem->data(), nullptr);\n\n  char buffer[sizeof(test_pattern)];\n  void* data = mem->data();\n  memcpy(data, test_pattern, sizeof(test_pattern));\n  memcpy(buffer, mem->data_as<char>(), sizeof(test_pattern));\n\n  EXPECT_EQ(memcmp(test_pattern, buffer, sizeof(test_pattern)), 0);\n}\n\nTEST(HeapDiscardableMemoryTest, TestLockAndUnlock) {\n  auto mem = std::make_unique<HeapDiscardableMemory>(10 * kTestPageSize);\n  EXPECT_EQ(mem->size_, 10 * kTestPageSize);\n  EXPECT_NE(mem->data(), nullptr);\n\n  memset(mem->data(), 0xE7, 10 * kTestPageSize);\n  EXPECT_EQ(mem->Lock(), true);\n  mem->Unlock();\n  EXPECT_EQ(mem->size_, 0u);\n  EXPECT_EQ(mem->memory_, nullptr);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/memory_pressure_listener.cc",
    "content": "// Copyright 2013 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/memory/memory_pressure_listener.h\"\n\n#include <list>\n\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/no_destructor.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nnamespace {\n// This class is thread safe and internally synchronized.\nclass MemoryPressureObserver {\n public:\n  // There is at most one MemoryPressureObserver and it is never deleted.\n  ~MemoryPressureObserver() = delete;\n\n  void AddObserver(MemoryPressureListener* listener, bool sync) {\n    std::scoped_lock<std::mutex> lock(observers_mutex_);\n    async_observers_.push_back(listener);\n    if (sync) {\n      sync_observers_.push_back(listener);\n    }\n  }\n\n  void RemoveObserver(MemoryPressureListener* listener) {\n    auto remove_func =\n        [listener](std::list<MemoryPressureListener*>& observers) {\n          auto it = std::find(observers.begin(), observers.end(), listener);\n          if (it != observers.end()) {\n            observers.erase(it);\n          }\n        };\n    std::scoped_lock<std::mutex> lock(observers_mutex_);\n    remove_func(async_observers_);\n    remove_func(sync_observers_);\n  }\n\n  void Notify(\n      MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {\n    std::scoped_lock<std::mutex> lock(observers_mutex_);\n    for (auto* listener : async_observers_) {\n      listener->Notify(memory_pressure_level);\n    }\n    for (auto* listener : sync_observers_) {\n      listener->SyncNotify(memory_pressure_level);\n    }\n  }\n\n private:\n  friend bool MemoryPressureListener::TestIfListenerExistsAsync(\n      MemoryPressureListener*);\n  friend bool MemoryPressureListener::TestIfListenerExistsSync(\n      MemoryPressureListener*);\n  std::mutex observers_mutex_;\n  std::list<MemoryPressureListener*> async_observers_;\n  std::list<MemoryPressureListener*> sync_observers_;\n};\n\n// Gets the shared MemoryPressureObserver singleton instance.\nMemoryPressureObserver& GetMemoryPressureObserver() {\n  static fml::NoDestructor<MemoryPressureObserver> observer{};\n  return *observer;\n}\n}  // namespace\n\nMemoryPressureListener::MemoryPressureListener(\n    const MemoryPressureCallback& memory_pressure_callback,\n    fml::RefPtr<fml::TaskRunner> task_runner)\n    : callback_(memory_pressure_callback) {\n  task_runner_ = task_runner;\n  GetMemoryPressureObserver().AddObserver(this, false);\n}\n\nMemoryPressureListener::MemoryPressureListener(\n    const MemoryPressureCallback& memory_pressure_callback,\n    const SyncMemoryPressureCallback& sync_memory_pressure_callback,\n    fml::RefPtr<fml::TaskRunner> task_runner)\n    : callback_(memory_pressure_callback),\n      sync_memory_pressure_callback_(sync_memory_pressure_callback) {\n  task_runner_ = task_runner;\n  GetMemoryPressureObserver().AddObserver(this, true);\n}\n\n// static\nbool MemoryPressureListener::TestIfListenerExistsAsync(\n    MemoryPressureListener* listener) {\n  std::scoped_lock<std::mutex> lock(\n      GetMemoryPressureObserver().observers_mutex_);\n  auto it =\n      std::find(GetMemoryPressureObserver().async_observers_.begin(),\n                GetMemoryPressureObserver().async_observers_.end(), listener);\n  return it != GetMemoryPressureObserver().async_observers_.end();\n}\n\n// static\nbool MemoryPressureListener::TestIfListenerExistsSync(\n    MemoryPressureListener* listener) {\n  std::scoped_lock<std::mutex> lock(\n      GetMemoryPressureObserver().observers_mutex_);\n  auto it =\n      std::find(GetMemoryPressureObserver().sync_observers_.begin(),\n                GetMemoryPressureObserver().sync_observers_.end(), listener);\n  return it != GetMemoryPressureObserver().sync_observers_.end();\n}\n\n// static\nvoid MemoryPressureListener::NotifyMemoryPressure(\n    MemoryPressureLevel memory_pressure_level) {\n  if (memory_pressure_level ==\n      MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE) {\n    return;\n  }\n  TRACE_EVENT(\"flutter\", __PRETTY_FUNCTION__);\n  DoNotifyMemoryPressure(memory_pressure_level);\n}\n\n// static\nvoid MemoryPressureListener::DoNotifyMemoryPressure(\n    MemoryPressureLevel memory_pressure_level) {\n  GetMemoryPressureObserver().Notify(memory_pressure_level);\n}\n\nvoid MemoryPressureListener::Notify(MemoryPressureLevel memory_pressure_level) {\n  TRACE_EVENT(\"flutter\", __PRETTY_FUNCTION__);\n  FML_DCHECK(task_runner_);\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runner_, [callback = callback_, memory_pressure_level]() {\n        callback(memory_pressure_level);\n      });\n}\n\nvoid MemoryPressureListener::SyncNotify(\n    MemoryPressureLevel memory_pressure_level) {\n  if (!sync_memory_pressure_callback_) {\n    return;\n  }\n  TRACE_EVENT(\"flutter\", __PRETTY_FUNCTION__);\n  fml::AutoResetWaitableEvent latch;\n  fml::TaskRunner::RunNowOrPostTask(task_runner_,\n                                    [callback = sync_memory_pressure_callback_,\n                                     memory_pressure_level, &latch]() {\n                                      callback(memory_pressure_level);\n                                      latch.Signal();\n                                    });\n  latch.Wait();\n}\n\nMemoryPressureListener::~MemoryPressureListener() {\n  GetMemoryPressureObserver().RemoveObserver(this);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/memory_pressure_listener.h",
    "content": "// Copyright 2013 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// MemoryPressure provides static APIs for handling memory pressure on\n// platforms that have such signals, such as Android and ChromeOS.\n// The app will try to discard buffers that aren't deemed essential (individual\n// modules will implement their own policy).\n\n#ifndef CLAY_MEMORY_MEMORY_PRESSURE_LISTENER_H_\n#define CLAY_MEMORY_MEMORY_PRESSURE_LISTENER_H_\n\n#include <functional>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/memory/memory_pressure_level.h\"\n\nnamespace clay {\n\n// To start listening, create a new instance, passing a callback to a\n// function that takes a MemoryPressureLevel parameter. To stop listening,\n// simply delete the listener object. The implementation guarantees\n// that the callback will always be called on the thread that created\n// the listener.\n// Note that even on the same thread, the callback is not guaranteed to be\n// called synchronously within the system memory pressure broadcast.\n// Please see notes in MemoryPressureLevel enum below: some levels are\n// absolutely critical, and if not enough memory is returned to the system,\n// it'll potentially kill the app, and then later the app will have to be\n// cold-started.\n//\n// Example:\n//\n//    void OnMemoryPressure(MemoryPressureLevel memory_pressure_level) {\n//       ...\n//    }\n//\n//    // Start listening.\n//    auto listener = std::make_unique<MemoryPressureListener>(\n//        base::BindRepeating(&OnMemoryPressure));\n//\n//    ...\n//\n//    // Stop listening.\n//    listener.reset();\n//\nclass MemoryPressureListener {\n public:\n  using MemoryPressureLevel = lynx::base::MemoryPressureLevel;\n  using MemoryPressureCallback = std::function<void(MemoryPressureLevel)>;\n  using SyncMemoryPressureCallback = std::function<void(MemoryPressureLevel)>;\n\n  MemoryPressureListener(const MemoryPressureCallback& memory_pressure_callback,\n                         fml::RefPtr<fml::TaskRunner> task_runner);\n  MemoryPressureListener(\n      const MemoryPressureCallback& memory_pressure_callback,\n      const SyncMemoryPressureCallback& sync_memory_pressure_callback,\n      fml::RefPtr<fml::TaskRunner> task_runner);\n\n  MemoryPressureListener(const MemoryPressureListener&) = delete;\n  MemoryPressureListener& operator=(const MemoryPressureListener&) = delete;\n\n  virtual ~MemoryPressureListener();\n\n  // Intended for use by the platform specific implementation.\n  static void NotifyMemoryPressure(MemoryPressureLevel memory_pressure_level);\n\n  // Mark as virtual to be utilized in UT.\n  virtual void Notify(MemoryPressureLevel memory_pressure_level);\n  void SyncNotify(MemoryPressureLevel memory_pressure_level);\n\n  // For unittests only.\n  static bool TestIfListenerExistsAsync(MemoryPressureListener* listener);\n  static bool TestIfListenerExistsSync(MemoryPressureListener* listener);\n\n private:\n  static void DoNotifyMemoryPressure(MemoryPressureLevel memory_pressure_level);\n\n  fml::RefPtr<fml::TaskRunner> task_runner_ = nullptr;\n\n  MemoryPressureCallback callback_ = nullptr;\n  SyncMemoryPressureCallback sync_memory_pressure_callback_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_MEMORY_MEMORY_PRESSURE_LISTENER_H_\n"
  },
  {
    "path": "clay/memory/memory_pressure_listener_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/memory/memory_pressure_listener.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nusing MemoryPressureLevel = MemoryPressureListener::MemoryPressureLevel;\n\nclass MockMemoryPressureListener : public MemoryPressureListener {\n public:\n  MockMemoryPressureListener(\n      const MemoryPressureCallback& memory_pressure_callback,\n      fml::RefPtr<fml::TaskRunner> task_runner)\n      : MemoryPressureListener(memory_pressure_callback, task_runner) {}\n\n  MockMemoryPressureListener(\n      const MemoryPressureCallback& memory_pressure_callback,\n      const SyncMemoryPressureCallback& sync_memory_pressure_callback,\n      fml::RefPtr<fml::TaskRunner> task_runner)\n      : MemoryPressureListener(memory_pressure_callback,\n                               sync_memory_pressure_callback, task_runner) {}\n  virtual ~MockMemoryPressureListener() = default;\n  MOCK_METHOD(void, Notify, (MemoryPressureLevel memory_pressure_level),\n              (override));\n};\n\nclass MemoryPressureListenerTest : public testing::Test {\n public:\n  MemoryPressureListenerTest() = default;\n\n  void SetUp() override {\n    listener_ = std::make_unique<MockMemoryPressureListener>(\n        [this](MemoryPressureLevel level) { OnMemoryPressure(level); },\n        nullptr);\n  }\n\n  void TearDown() override { listener_.reset(); }\n\n  MOCK_METHOD1(OnMemoryPressure, void(MemoryPressureLevel));\n\n protected:\n  std::unique_ptr<MockMemoryPressureListener> listener_;\n};\n\nTEST_F(MemoryPressureListenerTest, TestNotifyMemoryPressure) {\n  {\n    EXPECT_CALL(*listener_, Notify).Times(1);\n    MemoryPressureListener::NotifyMemoryPressure(\n        MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE);\n  }\n\n  {\n    EXPECT_CALL(*listener_, Notify).Times(1);\n    MemoryPressureListener::NotifyMemoryPressure(\n        MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL);\n  }\n\n  {\n    EXPECT_CALL(*listener_, Notify).Times(0);\n    MemoryPressureListener::NotifyMemoryPressure(\n        MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE);\n  }\n}\n\nTEST_F(MemoryPressureListenerTest, TestAddRemoveListener) {\n  MemoryPressureListener* listener_raw_ptr = listener_.get();\n  EXPECT_TRUE(\n      MemoryPressureListener::TestIfListenerExistsAsync(listener_raw_ptr));\n  EXPECT_FALSE(\n      MemoryPressureListener::TestIfListenerExistsSync(listener_raw_ptr));\n  listener_.reset();\n  EXPECT_FALSE(\n      MemoryPressureListener::TestIfListenerExistsAsync(listener_raw_ptr));\n  EXPECT_FALSE(\n      MemoryPressureListener::TestIfListenerExistsSync(listener_raw_ptr));\n\n  MemoryPressureListener::MemoryPressureCallback async_callback =\n      [](MemoryPressureLevel level) {};\n  MemoryPressureListener::MemoryPressureCallback sync_callback =\n      [](MemoryPressureLevel level) {};\n\n  auto listener2 = std::make_unique<MemoryPressureListener>(\n      async_callback, sync_callback, nullptr);\n  MemoryPressureListener* listener2_raw_ptr = listener2.get();\n  EXPECT_TRUE(\n      MemoryPressureListener::TestIfListenerExistsAsync(listener2_raw_ptr));\n  EXPECT_TRUE(\n      MemoryPressureListener::TestIfListenerExistsSync(listener2_raw_ptr));\n\n  listener2.reset();\n  EXPECT_FALSE(\n      MemoryPressureListener::TestIfListenerExistsAsync(listener2_raw_ptr));\n  EXPECT_FALSE(\n      MemoryPressureListener::TestIfListenerExistsSync(listener2_raw_ptr));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/memory_pressure_monitor.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/memory/memory_pressure_monitor.h\"\n\n#include \"base/include/no_destructor.h\"\n\nnamespace clay {\n\nMemoryPressureMonitor& MemoryPressureMonitor::GetInstance() {\n  static fml::NoDestructor<MemoryPressureMonitor> instance{};\n  return *instance;\n}\n\nbool MemoryPressureMonitor::StartMonitoring() {\n  // Creation of evaluator also starts monitoring.\n  if (!system_memory_pressure_evaluator_) {\n    system_memory_pressure_evaluator_ =\n        SystemMemoryPressureEvaluator::CreateSystemSpecificEvaluator(\n            task_runner_);\n  }\n  return system_memory_pressure_evaluator_ != nullptr;\n}\n\nMemoryPressureMonitor::~MemoryPressureMonitor() {\n  system_memory_pressure_evaluator_.reset();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/memory_pressure_monitor.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_MEMORY_MEMORY_PRESSURE_MONITOR_H_\n#define CLAY_MEMORY_MEMORY_PRESSURE_MONITOR_H_\n\n#include <memory>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/memory/memory_pressure_listener.h\"\n#include \"clay/memory/system_memory_pressure_evaluator.h\"\n\nnamespace clay {\n\n// This monitor is not for OS Android. For Android, see\n// io/flutter/embedding/engine/memory/MemoryPressureMonitor.java\nclass MemoryPressureMonitor {\n public:\n  BASE_DISALLOW_COPY_AND_ASSIGN(MemoryPressureMonitor);\n  using MemoryPressureLevel = MemoryPressureListener::MemoryPressureLevel;\n  MemoryPressureMonitor() = default;\n  ~MemoryPressureMonitor();\n  static MemoryPressureMonitor& GetInstance();\n\n  void SetTaskRunner(fml::RefPtr<fml::TaskRunner> task_runner) {\n    task_runner_ = task_runner;\n  }\n\n  // Start memory pressure monitoring.\n  bool StartMonitoring();\n\n private:\n  std::unique_ptr<SystemMemoryPressureEvaluator>\n      system_memory_pressure_evaluator_ = nullptr;\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n};\n}  // namespace clay\n\n#endif  // CLAY_MEMORY_MEMORY_PRESSURE_MONITOR_H_\n"
  },
  {
    "path": "clay/memory/system_memory_pressure_evaluator.cc",
    "content": "// Copyright 2019 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/memory/system_memory_pressure_evaluator.h\"\n\n#include \"build/build_config.h\"\n\n#if OS_WIN\n#include \"clay/memory/system_memory_pressure_evaluator_win.h\"\n#elif OS_MAC\n#include \"clay/memory/system_memory_pressure_evaluator_mac.h\"\n#endif\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\n// static\nstd::unique_ptr<SystemMemoryPressureEvaluator>\nSystemMemoryPressureEvaluator::CreateSystemSpecificEvaluator(\n    fml::RefPtr<fml::TaskRunner> task_runner) {\n#ifdef OS_WIN\n  // Windows cannot simply use fml::MessageLoop::GetCurrent().GetTaskRunner() to\n  // get current thread's task runner, so we need to pass it.\n  auto evaluator =\n      std::make_unique<SystemMemoryPressureEvaluatorWin>(task_runner);\n  return evaluator;\n#elif OS_MAC\n  auto evaluator = std::make_unique<SystemMemoryPressureEvaluatorMac>();\n  return evaluator;\n#else\n#error \"SystemMemoryPressureEvaluator only supports Windows and MacOS for now\";\n#endif\n}\n\nvoid SystemMemoryPressureEvaluator::SendCurrentPressureLevel(\n    bool notify_listeners) {\n  if (notify_listeners) {\n    MemoryPressureListener::NotifyMemoryPressure(current_pressure_level_);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/system_memory_pressure_evaluator.h",
    "content": "// Copyright 2019 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_MEMORY_SYSTEM_MEMORY_PRESSURE_EVALUATOR_H_\n#define CLAY_MEMORY_SYSTEM_MEMORY_PRESSURE_EVALUATOR_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/memory/memory_pressure_listener.h\"\n\nnamespace clay {\n\nclass SystemMemoryPressureEvaluator {\n public:\n  using MemoryPressureLevel = MemoryPressureListener::MemoryPressureLevel;\n  BASE_DISALLOW_COPY_AND_ASSIGN(SystemMemoryPressureEvaluator);\n\n  SystemMemoryPressureEvaluator() = default;\n  virtual ~SystemMemoryPressureEvaluator() = default;\n\n  // Create a system specific pressure evaluator;\n  static std::unique_ptr<SystemMemoryPressureEvaluator>\n  CreateSystemSpecificEvaluator(fml::RefPtr<fml::TaskRunner> task_runner);\n\n  void SetCurrentPressureLevel(MemoryPressureLevel level) {\n    current_pressure_level_ = level;\n  }\n\n  MemoryPressureLevel GetCurrentPressureLevel() const {\n    return current_pressure_level_;\n  }\n\n  void SendCurrentPressureLevel(bool notify_listeners);\n\n private:\n  virtual void StartObserving() {}\n  virtual void StopObserving() {}\n\n  MemoryPressureLevel current_pressure_level_ =\n      MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_MEMORY_SYSTEM_MEMORY_PRESSURE_EVALUATOR_H_\n"
  },
  {
    "path": "clay/memory/system_memory_pressure_evaluator_mac.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/memory/system_memory_pressure_evaluator_mac.h\"\n\n#include <CoreFoundation/CoreFoundation.h>\n#include <mach/mach_host.h>\n#include <mach/vm_statistics.h>\n#include <sys/sysctl.h>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nnamespace {\nMemoryPressureListener::MemoryPressureLevel MapFromMacPressureLevel(\n    dispatch_source_memorypressure_flags_t mac_pressure_level) {\n  switch (mac_pressure_level) {\n    case DISPATCH_MEMORYPRESSURE_NORMAL:\n      return MemoryPressureListener::MemoryPressureLevel::\n          MEMORY_PRESSURE_LEVEL_NONE;\n    case DISPATCH_MEMORYPRESSURE_WARN:\n      return MemoryPressureListener::MemoryPressureLevel::\n          MEMORY_PRESSURE_LEVEL_MODERATE;\n    case DISPATCH_MEMORYPRESSURE_CRITICAL:\n      return MemoryPressureListener::MemoryPressureLevel::\n          MEMORY_PRESSURE_LEVEL_CRITICAL;\n  }\n  return MemoryPressureListener::MemoryPressureLevel::\n      MEMORY_PRESSURE_LEVEL_NONE;\n}\n}  // namespace\n\nconst float SystemMemoryPressureEvaluatorMac::kCriticalPressureThreshold = 0.9;\nconst float SystemMemoryPressureEvaluatorMac::kModeratePressureThreshold = 0.75;\n\nconst fml::TimeDelta SystemMemoryPressureEvaluatorMac::kReCheckMemoryPeriod =\n    fml::TimeDelta::FromSeconds(5);\n\nSystemMemoryPressureEvaluatorMac::SystemMemoryPressureEvaluatorMac() {\n  memory_event_source_ = dispatch_source_create(\n      DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0,\n      DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL |\n          DISPATCH_MEMORYPRESSURE_NORMAL,\n      dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0));\n\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  task_runner_ = fml::MessageLoop::GetCurrent().GetTaskRunner();\n\n  StartObserving();\n}\n\nSystemMemoryPressureEvaluatorMac::~SystemMemoryPressureEvaluatorMac() {\n  StopObserving();\n}\n\nMemoryPressureListener::MemoryPressureLevel\nSystemMemoryPressureEvaluatorMac::MapPressureLevelForTest(\n    dispatch_source_memorypressure_flags_t mac_level) {\n  return MapFromMacPressureLevel(mac_level);\n}\n\nvoid SystemMemoryPressureEvaluatorMac::StartObserving() {\n  FML_DCHECK(memory_event_source_);\n  dispatch_source_set_event_handler(memory_event_source_, ^{\n    dispatch_source_memorypressure_flags_t mac_pressure_level =\n        dispatch_source_get_data(memory_event_source_);\n    MemoryPressureLevel pressure_level =\n        MapFromMacPressureLevel(mac_pressure_level);\n    task_runner_->PostTask([this, pressure_level]() {\n      SetCurrentPressureLevel(pressure_level);\n      OnMemoryPressureChanged();\n    });\n  });\n\n  dispatch_resume(memory_event_source_);\n}\n\nvoid SystemMemoryPressureEvaluatorMac::StopObserving() {\n  // Remove the memory pressure event source.\n  if (memory_event_source_) {\n    dispatch_source_cancel(memory_event_source_);\n  }\n}\n\nvoid SystemMemoryPressureEvaluatorMac::OnMemoryPressureChanged() {\n  auto current_level = GetCurrentPressureLevel();\n  bool notify =\n      current_level != MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE;\n  if (notify) {\n    MemoryPressureListener::NotifyMemoryPressure(current_level);\n  }\n  // We need to recheck to assure that the pressure level is no longer\n  // critical.\n  if (current_level == MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL) {\n    task_runner_->PostDelayedTask([this]() { CheckMemoryPressure(); },\n                                  kReCheckMemoryPeriod);\n  }\n}\n\nvoid SystemMemoryPressureEvaluatorMac::CheckMemoryPressure() {\n  int current_level;\n  size_t length = sizeof(int);\n  int error = sysctlbyname(\"kern.memorystatus_vm_pressure_level\",\n                           &current_level, &length, nullptr, 0);\n  if (error != 0) {\n    FML_LOG(ERROR) << \"sysctl kern.memorystatus_vm_pressure_level failed!\";\n    if (!CalculatePressureLevelByAvailableMemory(&current_level)) {\n      return;\n    }\n  }\n\n  SetCurrentPressureLevel(MapFromMacPressureLevel(current_level));\n\n  OnMemoryPressureChanged();\n}\n\nbool SystemMemoryPressureEvaluatorMac::CalculatePressureLevelByAvailableMemory(\n    int* pressure_level) {\n  vm_statistics64_data_t vm_stats;\n  mach_msg_type_number_t infoCount = HOST_VM_INFO64_COUNT;\n  if (host_statistics64(mach_host_self(), HOST_VM_INFO64,\n                        reinterpret_cast<host_info64_t>(&vm_stats),\n                        &infoCount) != KERN_SUCCESS) {\n    FML_LOG(ERROR) << \"Failed to get vm stats info\";\n    return false;\n  }\n  vm_size_t page_size;\n  if (host_page_size(mach_host_self(), &page_size) != KERN_SUCCESS) {\n    FML_LOG(ERROR) << \"Failed to get page size\";\n    return false;\n  }\n  uint64_t total_memory = GetTotalMemory();\n  uint64_t memory_used =\n      (vm_stats.active_count + vm_stats.inactive_count + vm_stats.wire_count +\n       vm_stats.compressor_page_count + vm_stats.purgeable_count +\n       vm_stats.speculative_count) *\n      page_size;\n  float memory_use_percentage =\n      static_cast<float>(memory_used) / static_cast<float>(total_memory);\n\n  if (memory_use_percentage > kCriticalPressureThreshold) {\n    *pressure_level = DISPATCH_MEMORYPRESSURE_CRITICAL;\n  } else if (memory_use_percentage > kModeratePressureThreshold) {\n    *pressure_level = DISPATCH_MEMORYPRESSURE_WARN;\n  } else {\n    *pressure_level = DISPATCH_MEMORYPRESSURE_NORMAL;\n  }\n  return true;\n}\n\nuint64_t SystemMemoryPressureEvaluatorMac::GetTotalMemory() {\n  static std::once_flag flag;\n  static uint64_t total_memory;\n  std::call_once(flag, []() {\n    size_t length = sizeof(uint64_t);\n    sysctlbyname(\"hw.memsize\", &total_memory, &length, nullptr, 0);\n  });\n  return total_memory;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/system_memory_pressure_evaluator_mac.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_MEMORY_SYSTEM_MEMORY_PRESSURE_EVALUATOR_MAC_H_\n#define CLAY_MEMORY_SYSTEM_MEMORY_PRESSURE_EVALUATOR_MAC_H_\n\n#include <dispatch/dispatch.h>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"clay/memory/memory_pressure_listener.h\"\n#include \"clay/memory/system_memory_pressure_evaluator.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass SystemMemoryPressureEvaluatorMac : public SystemMemoryPressureEvaluator {\n public:\n  static const float kCriticalPressureThreshold;\n  static const float kModeratePressureThreshold;\n  static const fml::TimeDelta kReCheckMemoryPeriod;\n  using MemoryPressureLevel = MemoryPressureListener::MemoryPressureLevel;\n\n  SystemMemoryPressureEvaluatorMac();\n  ~SystemMemoryPressureEvaluatorMac() override;\n  BASE_DISALLOW_COPY_AND_ASSIGN(SystemMemoryPressureEvaluatorMac);\n\n private:\n  FRIEND_TEST(SystemMemoryPressureEvaluatorMacTest, TestMapPressureLevel);\n  static MemoryPressureListener::MemoryPressureLevel MapPressureLevelForTest(\n      dispatch_source_memorypressure_flags_t mac_level);\n\n  void StartObserving() override;\n  void StopObserving() override;\n  void OnMemoryPressureChanged();\n\n  void CheckMemoryPressure();\n  bool CalculatePressureLevelByAvailableMemory(int* pressure_level);\n\n  uint64_t GetTotalMemory();\n\n  dispatch_source_t memory_event_source_;\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_MEMORY_SYSTEM_MEMORY_PRESSURE_EVALUATOR_MAC_H_\n"
  },
  {
    "path": "clay/memory/system_memory_pressure_evaluator_mac_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// #include \"system_memory_pressure_evaluator.h\"\n#include \"clay/memory/system_memory_pressure_evaluator_mac.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nusing MemoryPressureLevel = SystemMemoryPressureEvaluator::MemoryPressureLevel;\n\nTEST(SystemMemoryPressureEvaluatorMacTest, TestPressureValues) {\n  SystemMemoryPressureEvaluatorMac evaluator;\n  MemoryPressureLevel current_level = evaluator.GetCurrentPressureLevel();\n  EXPECT_TRUE(\n      current_level == MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL ||\n      current_level == MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE ||\n      current_level == MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE);\n}\n\nTEST(SystemMemoryPressureEvaluatorMacTest, TestMapPressureLevel) {\n  EXPECT_EQ(MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE,\n            SystemMemoryPressureEvaluatorMac::MapPressureLevelForTest(\n                DISPATCH_MEMORYPRESSURE_NORMAL));\n  EXPECT_EQ(MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE,\n            SystemMemoryPressureEvaluatorMac::MapPressureLevelForTest(\n                DISPATCH_MEMORYPRESSURE_WARN));\n  EXPECT_EQ(MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL,\n            SystemMemoryPressureEvaluatorMac::MapPressureLevelForTest(\n                DISPATCH_MEMORYPRESSURE_CRITICAL));\n  EXPECT_EQ(MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE,\n            SystemMemoryPressureEvaluatorMac::MapPressureLevelForTest(0));\n  EXPECT_EQ(MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE,\n            SystemMemoryPressureEvaluatorMac::MapPressureLevelForTest(3));\n  EXPECT_EQ(MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE,\n            SystemMemoryPressureEvaluatorMac::MapPressureLevelForTest(5));\n  EXPECT_EQ(MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE,\n            SystemMemoryPressureEvaluatorMac::MapPressureLevelForTest(-1));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/system_memory_pressure_evaluator_win.cc",
    "content": "// Copyright 2019 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/memory/system_memory_pressure_evaluator_win.h\"\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nnamespace {\nstatic const DWORDLONG kMBBytes = 1024 * 1024;\n\nstatic VOID CALLBACK OnLowMemoryNotification(PVOID lpParameter,\n                                             BOOLEAN TimerOrWaitFired) {\n  FML_DCHECK(!TimerOrWaitFired);\n\n  SystemMemoryPressureEvaluatorWin* self =\n      static_cast<SystemMemoryPressureEvaluatorWin*>(lpParameter);\n\n  if (!self) {\n    return;\n  }\n\n  self->UnregisterWaitInsideCallback();\n\n  // On Windows it is possible that this callback function is called\n  // but there is still enough space for the application to continue running.\n  if (self->CheckMemoryPressure()) {\n    self->SetEnablePolling(true);\n    self->PeriodicallyCheck();\n  }\n}\n}  // namespace\n\nconst fml::TimeDelta\n    SystemMemoryPressureEvaluatorWin::kModeratePressureCooldown =\n        fml::TimeDelta::FromSeconds(10);\n\n// A system is considered 'high memory' if it has more than 1.5GB of system\n// memory available for use by the memory manager (not reserved for hardware\n// and drivers). This is a fuzzy version of the ~2GB discussed below.\nconst int SystemMemoryPressureEvaluatorWin::kLargeMemoryThresholdMb = 1536;\n// These are the default thresholds used for systems with < ~2GB of physical\n// memory. Such systems have been observed to always maintain ~100MB of\n// available memory, paging until that is the case. To try to avoid paging a\n// threshold slightly above this is chosen. The moderate threshold is slightly\n// less grounded in reality and chosen as 2.5x critical.\nconst int\n    SystemMemoryPressureEvaluatorWin::kSmallMemoryDefaultModerateThresholdMb =\n        500;\nconst int\n    SystemMemoryPressureEvaluatorWin::kSmallMemoryDefaultCriticalThresholdMb =\n        200;\n// These are the default thresholds used for systems with >= ~2GB of physical\n// memory. Such systems have been observed to always maintain ~300MB of\n// available memory, paging until that is the case.\nconst int\n    SystemMemoryPressureEvaluatorWin::kLargeMemoryDefaultModerateThresholdMb =\n        1000;\nconst int\n    SystemMemoryPressureEvaluatorWin::kLargeMemoryDefaultCriticalThresholdMb =\n        400;\n\n// Check the amount of RAM left every 5 seconds.\nconst fml::TimeDelta SystemMemoryPressureEvaluatorWin::kMemorySamplingPeriod =\n    fml::TimeDelta::FromSeconds(5);\n\nSystemMemoryPressureEvaluatorWin::SystemMemoryPressureEvaluatorWin(\n    fml::RefPtr<fml::TaskRunner> task_runner)\n    : moderate_pressure_repeat_count_(0),\n      moderate_threshold_mb_(0),\n      critical_threshold_mb_(0),\n      task_runner_(task_runner),\n      weak_factory_(this) {\n  InferThresholds();\n  StartObserving();\n}\n\nSystemMemoryPressureEvaluatorWin::~SystemMemoryPressureEvaluatorWin() {\n  enable_polling_ = false;\n  if (wait_handle_ != nullptr) {\n    UnregisterWait(wait_handle_);\n  }\n  if (low_memory_handle_) {\n    CloseHandle(low_memory_handle_);\n  }\n}\n\nvoid SystemMemoryPressureEvaluatorWin::UnregisterWaitInsideCallback() {\n  UnregisterWaitEx(wait_handle_, INVALID_HANDLE_VALUE);\n  wait_handle_ = nullptr;\n}\n\nvoid SystemMemoryPressureEvaluatorWin::StartObserving() {\n  if (!TryOsSignalObserving()) {\n    // Cannot listen to OS signals, fall back to polling.\n    enable_polling_ = true;\n    PeriodicallyCheck();\n  } else {\n    enable_os_signal_ = true;\n  }\n}\n\nbool SystemMemoryPressureEvaluatorWin::TryOsSignalObserving() {\n  if (!low_memory_handle_) {\n    low_memory_handle_ =\n        CreateMemoryResourceNotification(LowMemoryResourceNotification);\n  }\n  if (!low_memory_handle_) {\n    FML_LOG(ERROR) << \"Failed to CreateMemoryResourceNotification with error \"\n                   << GetLastError()\n                   << \". Will check memory pressure by polling.\";\n    return false;\n  }\n  DWORD wait_flags = WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE;\n  bool success = RegisterWaitForSingleObject(&wait_handle_, low_memory_handle_,\n                                             OnLowMemoryNotification, this,\n                                             INFINITE, wait_flags);\n  if (!success) {\n    if (wait_handle_ != nullptr) {\n      UnregisterWait(wait_handle_);\n      wait_handle_ = nullptr;\n    }\n    if (low_memory_handle_) {\n      CloseHandle(low_memory_handle_);\n      low_memory_handle_ = nullptr;\n    }\n    FML_LOG(ERROR) << \"Failed to RegisterWaitForSingleObject with error \"\n                   << GetLastError()\n                   << \". Will check memory pressure by polling.\";\n    return false;\n  }\n  return true;\n}\n\nvoid SystemMemoryPressureEvaluatorWin::PeriodicallyCheck() {\n  if (task_runner_) {\n    auto weak_self = weak_factory_.GetWeakPtr();\n    task_runner_->PostDelayedTask(\n        [self = weak_self]() {\n          if (!self) {\n            return;\n          }\n          self->CheckMemoryPressure();\n          if (self->EnablePolling()) {\n            self->PeriodicallyCheck();\n          }\n        },\n        kMemorySamplingPeriod);\n  }\n}\n\nbool SystemMemoryPressureEvaluatorWin::CheckMemoryPressure() {\n  MemoryPressureLevel old_pressure_level = GetCurrentPressureLevel();\n  SetCurrentPressureLevel(CalculateCurrentPressureLevel());\n\n  bool notify = false;\n  switch (GetCurrentPressureLevel()) {\n    case MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE:\n      break;\n    case MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE:\n      if (old_pressure_level != GetCurrentPressureLevel()) {\n        moderate_pressure_repeat_count_ = 1;\n      } else {\n        // Already in moderate pressure, only notify if sustained over the\n        // cooldown period.\n        const int kModeratePressureCooldownCycles =\n            kModeratePressureCooldown / kMemorySamplingPeriod;\n        if (++moderate_pressure_repeat_count_ ==\n            kModeratePressureCooldownCycles) {\n          notify = true;\n          moderate_pressure_repeat_count_ = 0;\n        }\n      }\n      break;\n    case MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL:\n      notify = true;\n      break;\n  }\n  if (notify) {\n    SendCurrentPressureLevel(notify);\n  }\n  if (enable_os_signal_ && !notify) {\n    enable_polling_ = false;\n    TryOsSignalObserving();\n  }\n  return notify;\n}\n\nMemoryPressureListener::MemoryPressureLevel\nSystemMemoryPressureEvaluatorWin::CalculateCurrentPressureLevel() {\n  MEMORYSTATUSEX mem_info;\n  if (!GetSystemMemoryStatus(&mem_info)) {\n    FML_LOG(ERROR) << \"Get system memory status failed!\";\n    return MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE;\n  }\n  // How much system memory is actively available for use right now, in MBs.\n  int phys_free = static_cast<int>(mem_info.ullAvailPhys / kMBBytes);\n  // Determine if the physical memory is under critical memory pressure.\n  if (phys_free <= critical_threshold_mb_)\n    return MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL;\n\n  // Determine if the physical memory is under moderate memory pressure.\n  if (phys_free <= moderate_threshold_mb_)\n    return MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE;\n\n  // No memory pressure was detected.\n  return MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE;\n}\n\nvoid SystemMemoryPressureEvaluatorWin::InferThresholds() {\n  // Default to high memory situation for conservative considering.\n  bool high_memory = true;\n  MEMORYSTATUSEX mem_info;\n  if (GetSystemMemoryStatus(&mem_info)) {\n    static const DWORDLONG kLargeMemoryThresholdBytes =\n        static_cast<DWORDLONG>(kLargeMemoryThresholdMb) * kMBBytes;\n    high_memory = mem_info.ullTotalPhys >= kLargeMemoryThresholdBytes;\n  }\n  if (high_memory) {\n    moderate_threshold_mb_ = kLargeMemoryDefaultModerateThresholdMb;\n    critical_threshold_mb_ = kLargeMemoryDefaultCriticalThresholdMb;\n  } else {\n    moderate_threshold_mb_ = kSmallMemoryDefaultModerateThresholdMb;\n    critical_threshold_mb_ = kSmallMemoryDefaultCriticalThresholdMb;\n  }\n}\n\nbool SystemMemoryPressureEvaluatorWin::GetSystemMemoryStatus(\n    MEMORYSTATUSEX* mem_info) {\n  FML_DCHECK(mem_info);\n  mem_info->dwLength = sizeof(*mem_info);\n  return GlobalMemoryStatusEx(mem_info);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/memory/system_memory_pressure_evaluator_win.h",
    "content": "// Copyright 2019 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_MEMORY_SYSTEM_MEMORY_PRESSURE_EVALUATOR_WIN_H_\n#define CLAY_MEMORY_SYSTEM_MEMORY_PRESSURE_EVALUATOR_WIN_H_\n\n#include <windows.h>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"clay/memory/system_memory_pressure_evaluator.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\nclass SystemMemoryPressureEvaluatorWin : public SystemMemoryPressureEvaluator {\n public:\n  BASE_DISALLOW_COPY_AND_ASSIGN(SystemMemoryPressureEvaluatorWin);\n\n  explicit SystemMemoryPressureEvaluatorWin(\n      fml::RefPtr<fml::TaskRunner> task_runner);\n  SystemMemoryPressureEvaluatorWin() : weak_factory_(this) {}\n  ~SystemMemoryPressureEvaluatorWin();\n\n  bool CheckMemoryPressure();\n  // Declared as virtual for testing.\n  virtual void PeriodicallyCheck();\n\n  void SetEnablePolling(bool value) { enable_polling_ = value; }\n  bool EnablePolling() const { return enable_polling_; }\n\n  void UnregisterWaitInsideCallback();\n\n private:\n  FRIEND_TEST(SystemMemoryPressureEvaluatorWinTest, TestPolling);\n  void StartObserving() override;\n\n  // Declared as virtual for testing.\n  virtual bool TryOsSignalObserving();\n  virtual void InferThresholds();\n\n  bool GetSystemMemoryStatus(MEMORYSTATUSEX* mem_info);\n\n  MemoryPressureListener::MemoryPressureLevel CalculateCurrentPressureLevel();\n\n  // The time which should pass between 2 successive moderate memory pressure\n  // signals.\n  static const fml::TimeDelta kModeratePressureCooldown;\n\n  // Constants governing the memory pressure level detection.\n\n  // The amount of total system memory beyond which a system is considered\n  // to be a large-memory system.\n  static const int kLargeMemoryThresholdMb;\n  // Default minimum free memory thresholds for small-memory systems, in MB.\n  static const int kSmallMemoryDefaultModerateThresholdMb;\n  static const int kSmallMemoryDefaultCriticalThresholdMb;\n  // Default minimum free memory thresholds for large-memory systems, in MB.\n  static const int kLargeMemoryDefaultModerateThresholdMb;\n  static const int kLargeMemoryDefaultCriticalThresholdMb;\n\n  // The memory sampling period, currently 5s.\n  static const fml::TimeDelta kMemorySamplingPeriod;\n\n  int moderate_pressure_repeat_count_;\n\n  // Threshold amounts of available memory that trigger pressure levels.\n  int moderate_threshold_mb_;\n  int critical_threshold_mb_;\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n\n  bool enable_polling_ = false;\n  bool enable_os_signal_ = false;\n\n  HANDLE low_memory_handle_ = nullptr;\n  HANDLE wait_handle_ = nullptr;\n\n  fml::WeakPtrFactory<SystemMemoryPressureEvaluatorWin> weak_factory_;\n};\n}  // namespace clay\n#endif  // CLAY_MEMORY_SYSTEM_MEMORY_PRESSURE_EVALUATOR_WIN_H_\n"
  },
  {
    "path": "clay/memory/system_memory_pressure_evaluator_win_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/memory/system_memory_pressure_evaluator_win.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nclass MockSystemMemoryPressureEvaluatorWin\n    : public SystemMemoryPressureEvaluatorWin {\n public:\n  MockSystemMemoryPressureEvaluatorWin() : SystemMemoryPressureEvaluatorWin() {}\n  void SetSupportOsSignal(bool support) { support_os_signal_ = support; }\n  MOCK_METHOD(void, PeriodicallyCheck, (), (override));\n\n private:\n  bool TryOsSignalObserving() override { return support_os_signal_; }\n\n  bool support_os_signal_ = false;\n};\n\nTEST(SystemMemoryPressureEvaluatorWinTest, TestPolling) {\n  {\n    auto evaluator = std::make_unique<MockSystemMemoryPressureEvaluatorWin>();\n    // Both default values are set to false.\n    EXPECT_FALSE(evaluator->EnablePolling());\n    EXPECT_FALSE(evaluator->enable_os_signal_);\n\n    EXPECT_CALL(*evaluator, PeriodicallyCheck()).Times(1);\n    evaluator->StartObserving();\n    EXPECT_TRUE(evaluator->EnablePolling());\n    EXPECT_FALSE(evaluator->enable_os_signal_);\n  }\n\n  {\n    auto evaluator = std::make_unique<MockSystemMemoryPressureEvaluatorWin>();\n    evaluator->SetSupportOsSignal(true);\n\n    EXPECT_CALL(*evaluator, PeriodicallyCheck()).Times(0);\n    evaluator->StartObserving();\n    EXPECT_FALSE(evaluator->EnablePolling());\n    EXPECT_TRUE(evaluator->enable_os_signal_);\n  }\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../common/config.gni\")\nimport(\"cache/build.gni\")\nimport(\"fetcher/build.gni\")\nimport(\"loader/build.gni\")\nimport(\"url/build.gni\")\n\nsource_set(\"net\") {\n  sources = [\n    \"macros.h\",\n    \"resource_type.h\",\n  ]\n\n  sources += rebase_path(url_parse_sources, \"\", \"url\")\n  sources += rebase_path(loader_sources, \"\", \"loader\")\n\n  if (use_net_loader) {\n    sources += [\n      \"net_loader_callback.cc\",\n      \"net_loader_callback.h\",\n      \"net_loader_manager.cc\",\n      \"net_loader_manager.h\",\n    ]\n\n    sources += rebase_path(cache_sources, \"\", \"cache\")\n    sources += rebase_path(fetcher_sources, \"\", \"fetcher\")\n  }\n\n  deps = [\n    \"../common\",\n    \"../fml:fml\",\n  ]\n\n  public_deps = []\n  if (enable_skity) {\n    public_deps += [ \"../gfx/skity\" ]\n  } else {\n    public_deps += [ \"//third_party/skia\" ]\n  }\n\n  configs += [\n    \"../:config\",\n    \"../ui:ui_defines\",\n  ]\n  public_configs = [\n    \"../:config\",\n    \"../../base/src:base_public_config\",\n  ]\n}\n\nif (enable_unittests) {\n  if (use_net_loader) {\n    executable(\"net_unittests\") {\n      testonly = true\n      sources = [\n        \"fetcher/http_resource_fetcher_unittests.cc\",\n        \"net_loader_manager_unittests.cc\",\n        \"url/url_helper_test.cc\",\n      ]\n\n      public_deps = [\n        \"../../base/src:base_static\",\n        \"../shell/common\",\n        \"../testing\",\n        \"../ui:ui\",\n        \"../ui:ui_fixture_sources\",\n        \"../ui:ui_fixtures\",\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "clay/net/cache/build.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\ncache_sources = [\n  \"cache_stats.cc\",\n  \"cache_stats.h\",\n  \"net_disk_cache.cc\",\n  \"net_disk_cache.h\",\n  \"net_resource_cache_key.cc\",\n  \"net_resource_cache_key.h\",\n]\n"
  },
  {
    "path": "clay/net/cache/cache_stats.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/cache/cache_stats.h\"\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nCacheStats::CacheStats(\n    std::vector<std::pair<std::string, fml::FileInfo>>&& init_cache_items)\n    : items_mutex_(fml::SharedMutex::Create()),\n      locked_items_mutex_(fml::SharedMutex::Create()) {\n  fml::UniqueLock write_lock(*items_mutex_);\n\n  for (auto& item : init_cache_items) {\n    items_.emplace(std::move(item.first), item.second);\n    disk_size_ += item.second.size;\n  }\n}\n\nsize_t CacheStats::GetCount() const {\n  fml::SharedLock read_lock(*items_mutex_);\n  return items_.size();\n}\n\nvoid CacheStats::AddItem(const std::string& item, int64_t item_size) {\n  fml::UniqueLock write_lock(*items_mutex_);\n  fml::FileInfo file_info;\n  file_info.size = item_size;\n  file_info.last_access_time = fml::TimePoint::Now();\n  auto result = items_.emplace(item, file_info);\n  // Insert must success. otherwise we should use cached item rather insert.\n  FML_DCHECK(result.second);\n  disk_size_ += item_size;\n}\n\nvoid CacheStats::RemoveItem(const std::string& item) {\n  fml::UniqueLock write_lock(*items_mutex_);\n\n  auto iter = items_.find(item);\n  FML_DCHECK(iter != items_.end());\n  disk_size_ -= iter->second.size;\n  items_.erase(iter);\n}\n\nbool CacheStats::LockItemIfContains(const std::string& item) {\n  // Quick check if item already be locked.\n  {\n    fml::UniqueLock lock(*locked_items_mutex_);\n    auto iter = locked_items_.find(item);\n    if (iter != locked_items_.end()) {\n      iter->second++;\n      return true;\n    }\n  }\n\n  fml::SharedLock read_lock(*items_mutex_);\n  auto iter = items_.find(item);\n  if (iter != items_.end()) {\n    {\n      // Lock item if item exist to avoid delete when trim cache\n      fml::UniqueLock lock(*locked_items_mutex_);\n      locked_items_.emplace(item, 1);\n    }\n    return true;\n  }\n\n  return false;\n}\n\nbool CacheStats::ItemHasLocked(const std::string& item) {\n  fml::SharedLock lock(*locked_items_mutex_);\n  return locked_items_.find(item) != locked_items_.end();\n}\n\nvoid CacheStats::RefreshAndUnLockItem(const std::string& item) {\n  {\n    fml::UniqueLock write_lock(*items_mutex_);\n    auto iter = items_.find(item);\n    FML_DCHECK(iter != items_.end());\n    iter->second.last_access_time = fml::TimePoint::Now();\n  }\n  DecreaseItemLock(item);\n}\n\nvoid CacheStats::OnFileInvalid(const std::string& item) {\n  {\n    fml::UniqueLock write_lock(*items_mutex_);\n    auto iter = items_.find(item);\n    if (iter != items_.end()) {\n      disk_size_ -= iter->second.size;\n      items_.erase(iter);\n    }\n  }\n  DecreaseItemLock(item);\n}\n\nstd::vector<std::pair<std::string, fml::FileInfo>>\nCacheStats::GetAllDeletableCachedItem() const {\n  fml::SharedLock read_lock(*items_mutex_);\n  fml::SharedLock locked_items_lock(*locked_items_mutex_);\n\n  std::vector<std::pair<std::string, fml::FileInfo>> cache_items;\n  for (const auto& item : items_) {\n    if (locked_items_.find(item.first) != locked_items_.end()) {\n      continue;\n    }\n    cache_items.emplace_back(item.first, item.second);\n  }\n  return cache_items;\n}\n\nvoid CacheStats::DecreaseItemLock(const std::string& item) {\n  fml::UniqueLock locked_items_lock(*locked_items_mutex_);\n  auto iter = locked_items_.find(item);\n  if (iter != locked_items_.end()) {\n    if (iter->second == 1) {\n      locked_items_.erase(item);\n    } else {\n      iter->second--;\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/cache/cache_stats.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_CACHE_CACHE_STATS_H_\n#define CLAY_NET_CACHE_CACHE_STATS_H_\n\n#include <atomic>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/synchronization/shared_mutex.h\"\n#include \"clay/fml/file.h\"\n\nnamespace clay {\n\n// Keep all cache statistics info in memory, this class may accessed by\n// multi thread, so it should be thread safe.\nclass CacheStats {\n public:\n  explicit CacheStats(\n      std::vector<std::pair<std::string, fml::FileInfo>>&& init_cache_item);\n  int64_t GetSize() const { return disk_size_; }\n  size_t GetCount() const;\n  void AddItem(const std::string& item, int64_t item_size);\n  void RemoveItem(const std::string& item);\n\n  bool LockItemIfContains(const std::string& item);\n  bool ItemHasLocked(const std::string& item);\n  void RefreshAndUnLockItem(const std::string& item);\n  void OnFileInvalid(const std::string& item);\n\n  std::vector<std::pair<std::string, fml::FileInfo>> GetAllDeletableCachedItem()\n      const;\n\n private:\n  void DecreaseItemLock(const std::string& item);\n\n  std::atomic<int64_t> disk_size_ = 0;\n\n  std::unique_ptr<fml::SharedMutex> items_mutex_;\n  std::unique_ptr<fml::SharedMutex> locked_items_mutex_;\n\n  std::unordered_map<std::string, fml::FileInfo> items_;\n  // Items be visited but not used, keep those to avoid delete by trim.\n  std::unordered_map<std::string, int> locked_items_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_CACHE_CACHE_STATS_H_\n"
  },
  {
    "path": "clay/net/cache/net_disk_cache.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/cache/net_disk_cache.h\"\n\n#include <algorithm>\n#include <cstring>\n#include <vector>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/net/cache/net_resource_cache_key.h\"\n#include \"clay/net/macros.h\"\n#include \"clay/net/resource_type.h\"\n\nnamespace clay {\n\nnamespace {\n\nconstexpr char kNetDiskCacheDirectory[] = \"net-disk-cache\";\n\nusing ItemEntry = std::pair<std::string, fml::FileInfo>;\n\n}  // namespace\n\nNetDiskCache::NetDiskCache(fml::RefPtr<fml::TaskRunner> io_runner,\n                           int64_t max_cache_size)\n    : io_runner_(std::move(io_runner)) {\n  config_.max_cache_size_ = max_cache_size;\n\n  fml::TaskRunner::RunNowOrPostTask(io_runner_, [this, max_cache_size]() {\n    std::shared_ptr<fml::UniqueFD> app_cache_base_dir =\n        clay::PersistentCache::GetCacheForProcess()->cache_directory();\n\n    cache_directory_ = std::make_unique<fml::UniqueFD>(\n        fml::CreateDirectory(*app_cache_base_dir, {kNetDiskCacheDirectory},\n                             fml::FilePermission::kReadWrite));\n    if (!cache_directory_->is_valid()) {\n      FML_DLOG(ERROR)\n          << \"Directory of net cache is invalid, cache will not be used\";\n      return;\n    }\n    NET_LOG << \"Use net disk cache max cache size set to be \" << max_cache_size;\n\n    std::vector<ItemEntry> cached_items;\n    fml::VisitFiles(\n        *cache_directory_, [&cached_items](const fml::UniqueFD& directory,\n                                           const std::string& filename) {\n          auto file = fml::OpenFileReadOnly(directory, filename.c_str());\n\n          if (!file.is_valid()) {\n            FML_DLOG(WARNING) << \"Failed to open file \" << filename;\n            fml::UnlinkFile(directory, filename.c_str());\n            return true;\n          }\n\n          fml::FileInfo file_info;\n          if (!fml::GetFileInfo(file, &file_info)) {\n            FML_DLOG(WARNING) << \"Unable to get file info for \" << filename;\n          }\n          cached_items.emplace_back(filename, file_info);\n          return true;\n        });\n    // cache_stats_ is created at io thread after check cache_directory_ is\n    // valid, cache_directory_ and io_runner_ must be valid if cache_stats_\n    // exist.\n    cache_stats_ = std::make_unique<CacheStats>(std::move(cached_items));\n    alive_ = true;\n    NET_LOG << \"NetDiskCache create finished.\";\n  });\n}\n\nbool NetDiskCache::ContainsKey(NetResourceCacheKey* cache_key) {\n  FML_DCHECK(cache_key != nullptr);\n  if (!Available()) {\n    return false;\n  }\n  const std::string& res_id = cache_key->ResourceId();\n  if (res_id.empty()) {\n    return false;\n  }\n  return cache_stats_->LockItemIfContains(cache_key->ResourceId());\n}\n\nvoid NetDiskCache::GetCacheContent(\n    NetResourceCacheKey&& cache_key,\n    const std::function<void(const std::string&, RawResource)>& callback) {\n  FML_DCHECK(cache_stats_);\n  // Ensure cache item be locked\n  FML_DCHECK(cache_stats_->ItemHasLocked(cache_key.ResourceId()));\n  FML_DCHECK(cache_directory_->is_valid());\n\n  io_runner_->PostTask([callback, cache_key = std::move(cache_key),\n                        cache_stats = cache_stats_.get(),\n                        dir = cache_directory_.get()]() mutable {\n    const std::string& res_id = cache_key.ResourceId();\n    auto file = fml::OpenFileReadOnly(*dir, res_id.c_str());\n    if (!file.is_valid()) {\n      callback(cache_key.Uri().Spec(), {0, nullptr});\n      fml::UnlinkFile(*dir, res_id.c_str());\n      cache_stats->OnFileInvalid(res_id);\n      return;\n    }\n    auto mapping = std::make_unique<fml::FileMapping>(file);\n    RawResource resource = {mapping->GetSize(), nullptr};\n    if (resource.length != 0) {\n      NET_LOG << \"Cache Content of \" << cache_key.Uri().Spec() << \" hit\";\n      auto data = std::shared_ptr<uint8_t>(new uint8_t[resource.length],\n                                           std::default_delete<uint8_t[]>());\n      ::memcpy(reinterpret_cast<void*>(data.get()), mapping->GetMapping(),\n               resource.length);\n      resource.data = std::move(data);\n    }\n    callback(cache_key.Uri().Spec(), std::move(resource));\n    cache_stats->RefreshAndUnLockItem(res_id);\n  });\n}\n\nvoid NetDiskCache::WriteContent(const std::string& resource_id,\n                                RawResource resource) {\n  if (!Available() ||\n      static_cast<int64_t>(resource.length) >= config_.max_cache_size_) {\n    // `cache_stats_` has not created yet or size of resource is too big.\n    return;\n  }\n  FML_DCHECK(cache_directory_->is_valid());\n  CheckMayTrim();\n  io_runner_->PostTask([resource_id, resource = std::move(resource),\n                        cache_stats = cache_stats_.get(),\n                        dir = cache_directory_.get()]() mutable {\n    auto data = std::make_unique<fml::DataMapping>(std::vector<uint8_t>{\n        resource.data.get(), resource.data.get() + resource.length});\n\n    NET_LOG << \"Write Cache Content to \" << resource_id;\n    // Write.\n    fml::WriteAtomically(*dir, resource_id.c_str(), *data);\n    cache_stats->AddItem(resource_id, resource.length);\n  });\n}\n\nvoid NetDiskCache::ClearCaches() {\n  if (!Available()) {\n    return;\n  }\n  int64_t old_size = config_.max_cache_size_;\n  config_.max_cache_size_ = 0;\n  CheckMayTrim();\n  config_.max_cache_size_ = old_size;\n}\n\nvoid NetDiskCache::CheckMayTrim() {\n  FML_DCHECK(cache_stats_);\n  if (cache_stats_->GetSize() > config_.max_cache_size_) {\n    TrimToLimit();\n  }\n}\n\nvoid NetDiskCache::TrimToLimit() {\n  std::vector<ItemEntry> cache_items =\n      cache_stats_->GetAllDeletableCachedItem();\n  // Sort by last access time reversal\n  std::sort(cache_items.begin(), cache_items.end(),\n            [](const ItemEntry& lhs, const ItemEntry& rhs) {\n              if (lhs.second.last_access_time == rhs.second.last_access_time) {\n                return lhs.second.size > rhs.second.size;\n              }\n              return lhs.second.last_access_time > rhs.second.last_access_time;\n            });\n  for (const ItemEntry& item : cache_items) {\n    if (cache_stats_->GetSize() < config_.max_cache_size_) {\n      break;\n    }\n    RemoveItem(item.first);\n  }\n}\n\nvoid NetDiskCache::RemoveItem(const std::string& resource_id) {\n  cache_stats_->RemoveItem(resource_id);\n  io_runner_->PostTask([dir = cache_directory_.get(), resource_id]() {\n    fml::UnlinkFile(*dir, resource_id.data());\n  });\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/cache/net_disk_cache.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_CACHE_NET_DISK_CACHE_H_\n#define CLAY_NET_CACHE_NET_DISK_CACHE_H_\n\n#include <atomic>\n#include <functional>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/concurrent_message_loop.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/net/cache/cache_stats.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass NetResourceCacheKey;\nstruct RawResource;\n\n// This class handle disk cache, must access from platform thread.\nclass NetDiskCache : public fml::RefCountedThreadSafe<NetDiskCache> {\n public:\n  struct NetDiskCacheConfig {\n    int64_t max_cache_size_;\n  };\n  NetDiskCache(fml::RefPtr<fml::TaskRunner> io_runner, int64_t max_cache_size);\n\n  // Whether has `cache_key` in disk cache\n  bool ContainsKey(NetResourceCacheKey* cache_key);\n\n  // Get cached data, make sure call after `ContainsKey` returns true\n  void GetCacheContent(\n      NetResourceCacheKey&& cache_key,\n      const std::function<void(const std::string&, RawResource)>& callback);\n\n  // Trim cache and write resource into disk.\n  void WriteContent(const std::string& resource_id, RawResource resource);\n\n  void ClearCaches();\n\n  fml::RefPtr<fml::TaskRunner> GetIOTaskRunner() { return io_runner_; }\n  void WillDestory() { alive_ = false; }\n\n private:\n  FRIEND_TEST(NetLoaderManagerTest, Cache);\n  FRIEND_TEST(NetLoaderManagerTest, TrimCacheWhileAccess);\n\n  void CheckMayTrim();\n  void TrimToLimit();\n  void RemoveItem(const std::string& resource_id);\n\n  // Before cache_stats_ has initialized and after WillDestory() cache is also\n  // unavailable\n  bool Available() const { return alive_; }\n\n  std::atomic<bool> alive_ = false;\n\n  fml::RefPtr<fml::TaskRunner> io_runner_;\n  NetDiskCacheConfig config_;\n\n  std::unique_ptr<CacheStats> cache_stats_;\n  std::unique_ptr<fml::UniqueFD> cache_directory_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_CACHE_NET_DISK_CACHE_H_\n"
  },
  {
    "path": "clay/net/cache/net_resource_cache_key.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/cache/net_resource_cache_key.h\"\n\n#include <string>\n\n#include \"clay/common/sha1.h\"\n\nnamespace clay {\n\nnamespace {\nstd::string GenerateResourceId(const std::string& uri) {\n  return clay::SHA1HashString(uri);\n}\n}  // namespace\n\nNetResourceCacheKey::NetResourceCacheKey(const std::string& uri) : uri_(uri) {}\n\nbool NetResourceCacheKey::Valid() const { return uri_.Valid(); }\n\nconst std::string& NetResourceCacheKey::ResourceId() {\n  if (!Valid()) {\n    resource_id_ = \"\";\n  } else if (resource_id_.empty()) {\n    resource_id_ = GenerateResourceId(uri_.Spec());\n  }\n\n  return resource_id_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/cache/net_resource_cache_key.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_CACHE_NET_RESOURCE_CACHE_KEY_H_\n#define CLAY_NET_CACHE_NET_RESOURCE_CACHE_KEY_H_\n\n#include <string>\n\n#include \"clay/net/url/uri.h\"\n\nnamespace clay {\n\n// Contains `Uri` and generate resource_id from it, make sure call Parse before\n// use Uri\nclass NetResourceCacheKey {\n public:\n  explicit NetResourceCacheKey(const std::string& uri);\n\n  const url::Uri& Uri() const { return uri_; }\n\n  const std::string& ResourceId();\n\n  bool Valid() const;\n\n private:\n  url::Uri uri_;\n\n  // resource_id_ will be the name of cache file, we use SHA1 hash to ensure is\n  // unique.\n  std::string resource_id_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_CACHE_NET_RESOURCE_CACHE_KEY_H_\n"
  },
  {
    "path": "clay/net/fetcher/build.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nfetcher_sources = [\n  \"http_resource_fetcher.cc\",\n  \"http_resource_fetcher.h\",\n  \"http_resource_fetcher_factory.cc\",\n  \"http_resource_fetcher_factory.h\",\n  \"http_resource_fetcher_host.cc\",\n  \"http_resource_fetcher_host.h\",\n]\n"
  },
  {
    "path": "clay/net/fetcher/http_resource_fetcher.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/fetcher/http_resource_fetcher.h\"\n\n#include <memory>\n\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/net/macros.h\"\n\nnamespace clay {\n\nHttpResourceFetcher::HttpResourceFetcher(const url::Uri& uri,\n                                         size_t request_seq, int failed_time)\n    : uri_(uri), request_seq_(request_seq), failed_time_(failed_time) {}\n\nHttpResourceFetcher::~HttpResourceFetcher() = default;\n\n// static\nconst char* HttpResourceFetcher::GetPlatformCAFile() {\n#if defined(OS_LINUX)\n  static constexpr char kUbuntuCertFile[] =\n      \"/etc/ssl/certs/ca-certificates.crt\";\n  return kUbuntuCertFile;\n#elif defined(OS_MAC) || defined(OS_WIN) || defined(OS_ANDROID)\n  return nullptr;\n#else\n  NET_LOG << \"Won't set cert file on this platform (use default_verify_paths)\";\n  return nullptr;\n#endif\n}\n\n// static\nconst char* HttpResourceFetcher::GetPlatformCAPath() {\n#if defined(OS_ANDROID)\n  static constexpr char kAndroidCertPath[] = \"/system/etc/security/cacerts/\";\n  return kAndroidCertPath;\n#elif defined(OS_MAC) || defined(OS_WIN) || defined(OS_LINUX)\n  return nullptr;\n#else\n  NET_LOG << \"Load on a platform not set cert file(use default_verify_paths)\";\n  return nullptr;\n#endif\n}\n\nvoid HttpResourceFetcher::SetHeaders(\n    std::map<std::string, std::string>&& headers) {\n  headers_ = std::move(headers);\n}\n\nvoid HttpResourceFetcher::SetMethod(const std::string& method) {\n  method_ = method;\n}\n\nvoid HttpResourceFetcher::SetBody(std::string&& body) {\n  body_ = std::move(body);\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/fetcher/http_resource_fetcher.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_FETCHER_HTTP_RESOURCE_FETCHER_H_\n#define CLAY_NET_FETCHER_HTTP_RESOURCE_FETCHER_H_\n\n#include <map>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/net/resource_type.h\"\n#include \"clay/net/url/uri.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\n// This class send a http(s) request.\nclass HttpResourceFetcher {\n public:\n  HttpResourceFetcher(const url::Uri& uri, size_t request_seq, int retry_time);\n  virtual ~HttpResourceFetcher();\n\n  static const char* GetPlatformCAFile();\n  static const char* GetPlatformCAPath();\n  static constexpr int kMaxRetryTime = 3;\n\n  virtual void Load() = 0;\n  virtual RawResource LoadSync() = 0;\n  virtual void Cancel() { failed_time_ += kMaxRetryTime; }\n  virtual bool IsHostLoader() { return false; }\n\n  int FailedTime() const { return failed_time_; }\n  void SetHeaders(std::map<std::string, std::string>&& headers);\n  void SetMethod(const std::string& method);\n  void SetBody(std::string&& body);\n\n  std::string Url() const { return uri_.Spec(); }\n\n protected:\n  // seconds\n  static constexpr int kDefaultConnectionTimeout = 15;\n  static constexpr int kDefaultIOTimeout = 5;\n\n  url::Uri uri_;\n  size_t request_seq_;\n  std::map<std::string, std::string> headers_;\n  std::string method_;\n  std::string body_;\n\n private:\n  FRIEND_TEST(HttpResourceFetcherTest, Load);\n\n  int failed_time_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_FETCHER_HTTP_RESOURCE_FETCHER_H_\n"
  },
  {
    "path": "clay/net/fetcher/http_resource_fetcher_factory.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/fetcher/http_resource_fetcher_factory.h\"\n\n#include \"clay/net/fetcher/http_resource_fetcher_host.h\"\n\nnamespace clay {\n\nResourceFetcherCreator HttpResourceFetcherFactory::custom_fetcher_creator_ =\n    nullptr;\n\n// static\nstd::unique_ptr<HttpResourceFetcher> HttpResourceFetcherFactory::CreateFetcher(\n    NetLoaderManager::HostNetLoader host_net_loader, const url::Uri& uri,\n    size_t request_seq, int retry_time) {\n  if (host_net_loader != nullptr) {\n    return CreateHostFetcher(host_net_loader, uri, request_seq, retry_time);\n  } else {\n    return CreateCustomFetcher(uri, request_seq, retry_time);\n  }\n}\n\n// static\nvoid HttpResourceFetcherFactory::SetCustomFetcherCreator(\n    ResourceFetcherCreator creator) {\n  custom_fetcher_creator_ = creator;\n}\n\n// static\nstd::unique_ptr<HttpResourceFetcher>\nHttpResourceFetcherFactory::CreateCustomFetcher(const url::Uri& uri,\n                                                size_t request_seq,\n                                                int retry_time) {\n  if (custom_fetcher_creator_) {\n    return custom_fetcher_creator_(uri, request_seq, retry_time);\n  } else {\n    return nullptr;\n  }\n}\n\n// static\nstd::unique_ptr<HttpResourceFetcher>\nHttpResourceFetcherFactory::CreateHostFetcher(\n    NetLoaderManager::HostNetLoader host_net_loader, const url::Uri& uri,\n    size_t request_seq, int retry_time) {\n  return std::make_unique<HttpResourceFetcherHost>(uri, request_seq, retry_time,\n                                                   host_net_loader);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/fetcher/http_resource_fetcher_factory.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_FETCHER_HTTP_RESOURCE_FETCHER_FACTORY_H_\n#define CLAY_NET_FETCHER_HTTP_RESOURCE_FETCHER_FACTORY_H_\n\n#include <memory>\n\n#include \"clay/net/fetcher/http_resource_fetcher.h\"\n#include \"clay/net/net_loader_manager.h\"\n\nnamespace clay {\n\nusing ResourceFetcherCreator =\n    std::function<std::unique_ptr<HttpResourceFetcher>(\n        const url::Uri& uri, size_t request_seq, int retry_time)>;\n\nclass HttpResourceFetcherFactory {\n public:\n  static std::unique_ptr<HttpResourceFetcher> CreateFetcher(\n      NetLoaderManager::HostNetLoader host_net_loader, const url::Uri& uri,\n      size_t request_seq, int retry_time = 0);\n\n  static void SetCustomFetcherCreator(ResourceFetcherCreator creator);\n\n private:\n  static std::unique_ptr<HttpResourceFetcher> CreateCustomFetcher(\n      const url::Uri& uri, size_t request_seq, int retry_time);\n\n  static std::unique_ptr<HttpResourceFetcher> CreateHostFetcher(\n      NetLoaderManager::HostNetLoader host_net_loader, const url::Uri& uri,\n      size_t request_seq, int retry_time);\n\n  static ResourceFetcherCreator custom_fetcher_creator_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_FETCHER_HTTP_RESOURCE_FETCHER_FACTORY_H_\n"
  },
  {
    "path": "clay/net/fetcher/http_resource_fetcher_host.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/fetcher/http_resource_fetcher_host.h\"\n\n#include <cstring>\n#include <string>\n#include <utility>\n\nnamespace clay {\n\nHttpResourceFetcherHost::HttpResourceFetcherHost(\n    const url::Uri& uri, size_t request_seq, int failed_time,\n    NetLoaderManager::HostNetLoader host_net_loader)\n    : HttpResourceFetcher(uri, request_seq, failed_time),\n      host_net_loader_(std::move(host_net_loader)) {}\n\nHttpResourceFetcherHost::~HttpResourceFetcherHost() = default;\n\nvoid HttpResourceFetcherHost::Load() {\n  host_net_loader_(uri_.Spec(), method_.empty() ? \"GET\" : method_, body_,\n                   headers_, request_seq_);\n}\n\nRawResource HttpResourceFetcherHost::LoadSync() {\n  is_sync_ = true;\n  host_net_loader_(uri_.Spec(), method_, body_, headers_, request_seq_);\n  load_latch_.Wait();\n  return resource_;\n}\n\nvoid HttpResourceFetcherHost::Cancel() {\n  HttpResourceFetcher::Cancel();\n  load_latch_.Signal();\n}\n\nvoid HttpResourceFetcherHost::OnFinished(bool success, const uint8_t* raw_data,\n                                         size_t length) {\n  auto data = std::shared_ptr<uint8_t>(new uint8_t[length],\n                                       std::default_delete<uint8_t[]>());\n  ::memcpy(reinterpret_cast<void*>(data.get()), raw_data, length);\n  resource_.data = std::move(data);\n  resource_.length = length;\n  if (is_sync_) {\n    load_latch_.Signal();\n  } else {\n    if (success) {\n      NetLoaderManager::Instance().OnSucceeded(uri_.Spec(), request_seq_,\n                                               std::move(resource_));\n    } else {\n      std::string reason;\n      if (resource_.data) {\n        reason.assign(reinterpret_cast<const char*>(resource_.data.get()),\n                      resource_.length);\n      }\n      NetLoaderManager::Instance().OnFailed(uri_.Spec(), request_seq_,\n                                            FailedTime(), reason);\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/fetcher/http_resource_fetcher_host.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_FETCHER_HTTP_RESOURCE_FETCHER_HOST_H_\n#define CLAY_NET_FETCHER_HTTP_RESOURCE_FETCHER_HOST_H_\n\n#include <memory>\n\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"clay/net/fetcher/http_resource_fetcher.h\"\n#include \"clay/net/net_loader_manager.h\"\n\nnamespace clay {\n\nclass HttpResourceFetcherHost : public HttpResourceFetcher {\n public:\n  HttpResourceFetcherHost(const url::Uri& uri, size_t request_seq,\n                          int failed_time,\n                          NetLoaderManager::HostNetLoader host_net_loader);\n\n  virtual ~HttpResourceFetcherHost();\n\n  void Load() override;\n\n  RawResource LoadSync() override;\n\n  void Cancel() override;\n\n  void OnFinished(bool success, const uint8_t* raw_data, size_t length);\n\n  bool IsHostLoader() override { return true; }\n\n private:\n  bool is_sync_ = false;\n\n  fml::AutoResetWaitableEvent load_latch_;\n  RawResource resource_;\n\n  NetLoaderManager::HostNetLoader host_net_loader_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_FETCHER_HTTP_RESOURCE_FETCHER_HOST_H_\n"
  },
  {
    "path": "clay/net/fetcher/http_resource_fetcher_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include <string>\n#include <vector>\n\n#include \"clay/net/fetcher/http_resource_fetcher.h\"\n#include \"clay/net/fetcher/http_resource_fetcher_factory.h\"\n#include \"clay/net/net_loader_callback.h\"\n#include \"clay/net/net_loader_manager.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(HttpResourceFetcherTest, Load) {\n  std::vector<std::pair<std::string, std::string>> url_set = {\n      {\n          \"https://pics.freeicons.io\",\n          \"/uploads/icons/png/\"\n          \"4398893391579605854-512.png\",\n      },\n      {\n          \"https://secure.gravatar.com\",\n          \"/avatar/\"\n          \"e402686b4d2ec0899358e5cd311fd12f\\?s\\\\=46\\\\&d\\\\=identicon\",\n      },\n      {\n          \"https://4.bp.blogspot.com\",\n          \"/-uhjF2kC3tFc/U_r3myvwzHI/AAAAAAAACiw/tPQ2XOXFYKY/s1600/\"\n          \"Circles-3.gif\",\n      },\n  };\n\n  for (auto host_path : url_set) {\n    std::unique_ptr<HttpResourceFetcher> image_fetcher =\n        HttpResourceFetcherFactory::CreateFetcher(\n            nullptr, url::Uri(host_path.first + host_path.second), 0);\n    auto content = image_fetcher->LoadSync();\n\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n    EXPECT_NE(content.length, 0u);\n  }\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/loader/build.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nloader_sources = [\n  \"data_image_loader.cc\",\n  \"data_image_loader.h\",\n  \"resource_loader.h\",\n  \"resource_loader_creator_service.h\",\n  \"resource_loader_factory.cc\",\n  \"resource_loader_factory.h\",\n  \"resource_loader_intercept.h\",\n  \"resource_loader_platform.h\",\n]\n"
  },
  {
    "path": "clay/net/loader/data_image_loader.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/loader/data_image_loader.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/base64.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace {\nstatic constexpr char kDataURISuffixBase64[] = \";base64,\";\nstatic constexpr char kDataURIPrefixImage[] = \"data:image/\";\nstatic constexpr char kDataURIPrefixOctetStream[] =\n    \"data:application/octet-stream\";\nstatic constexpr size_t kDataURIPrefixImageLen =\n    sizeof(kDataURIPrefixImage) - 1;\nstatic constexpr size_t kDataURIPrefixOctetStreamLen =\n    sizeof(kDataURIPrefixOctetStream) - 1;\n\n// Checks if the URI starts with \"data:image/\" or\n// \"data:application/octet-stream\", and is immediately followed by \";base64,\"\n// (e.g. \"data:image/png;base64,\" or \"data:application/octet-stream;base64,\").\nstatic bool IsBase64Uri(const std::string& uri) {\n  size_t prefix_len = 0;\n  if (strncmp(uri.c_str(), kDataURIPrefixImage, kDataURIPrefixImageLen) == 0) {\n    prefix_len = kDataURIPrefixImageLen;\n  } else if (strncmp(uri.c_str(), kDataURIPrefixOctetStream,\n                     kDataURIPrefixOctetStreamLen) == 0) {\n    prefix_len = kDataURIPrefixOctetStreamLen;\n  } else {\n    return false;\n  }\n\n  const char* encoding = strstr(uri.c_str() + prefix_len, kDataURISuffixBase64);\n  if (!encoding) {\n    return false;\n  }\n  return true;\n}\n\n}  // namespace\n\nnamespace clay {\n\n// Copy from skia SkResources.cpp\nstatic RawResource DecodeBase64Image(const char* uri) {\n  const char* encoding = strstr(uri, kDataURISuffixBase64);\n  if (!encoding) {\n    return {0, nullptr};\n  }\n  const char* base64_data = encoding + std::size(kDataURISuffixBase64) - 1;\n  size_t base64_data_len = strlen(base64_data);\n  size_t data_length;\n  if (fml::Base64::Decode(base64_data, base64_data_len, nullptr,\n                          &data_length) != fml::Base64::kNoError) {\n    return {0, nullptr};\n  }\n\n  auto encode_data = std::shared_ptr<uint8_t>(new uint8_t[data_length],\n                                              std::default_delete<uint8_t[]>());\n\n  if (fml::Base64::Decode(base64_data, base64_data_len, encode_data.get(),\n                          &data_length) != fml::Base64::kNoError) {\n    return {0, nullptr};\n  }\n\n  return {data_length, encode_data};\n}\n\nDataImageLoader::DataImageLoader(fml::RefPtr<fml::TaskRunner> task_runner)\n    : task_runner_(task_runner) {}\n\nvoid DataImageLoader::Load(\n    const std::string& src,\n    const std::function<void(const uint8_t*, size_t)>& callback,\n    const ResourceType resource_type, bool need_redirect) {\n  if (!callback) {\n    return;\n  }\n\n  if (!task_runner_) {\n    callback(nullptr, 0);\n    return;\n  }\n\n  task_runner_->PostTask([uri = src, callback]() {\n    if (IsBase64Uri(uri)) {\n      auto result = DecodeBase64Image(uri.c_str());\n      if (result.data) {\n        callback(result.data.get(), result.length);\n        return;\n      }\n    }\n    callback(nullptr, 0);\n  });\n}\n\nRawResource DataImageLoader::LoadSync(const std::string& uri,\n                                      const ResourceType resource_type,\n                                      bool need_redirect) {\n  if (IsBase64Uri(uri)) {\n    auto result = DecodeBase64Image(uri.c_str());\n\n    if (result.data) {\n      return result;\n    }\n  }\n  FML_LOG(ERROR) << \"Data type uri not supported or failed decode.\";\n  return {0, nullptr};\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/loader/data_image_loader.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_LOADER_DATA_IMAGE_LOADER_H_\n#define CLAY_NET_LOADER_DATA_IMAGE_LOADER_H_\n\n#include <functional>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/net/loader/resource_loader.h\"\n#include \"clay/net/resource_type.h\"\n\nnamespace clay {\n\nclass DataImageLoader : public ResourceLoader {\n public:\n  explicit DataImageLoader(fml::RefPtr<fml::TaskRunner> task_runner);\n\n  void Load(const std::string& src,\n            const std::function<void(const uint8_t*, size_t)>& callback,\n            const ResourceType resource_type = ResourceType::kOthers,\n            bool need_redirect = false) override;\n\n  RawResource LoadSync(const std::string& src,\n                       const ResourceType resource_type = ResourceType::kOthers,\n                       bool need_redirect = false) override;\n\n  // Not support cancel\n  void CancelAll() override {}\n\n private:\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_LOADER_DATA_IMAGE_LOADER_H_\n"
  },
  {
    "path": "clay/net/loader/resource_loader.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_LOADER_RESOURCE_LOADER_H_\n#define CLAY_NET_LOADER_RESOURCE_LOADER_H_\n\n#include <functional>\n#include <string>\n\n#include \"clay/net/resource_type.h\"\n\nnamespace clay {\n\nclass ResourceLoader {\n public:\n  ResourceLoader() = default;\n  virtual ~ResourceLoader() = default;\n\n  virtual void Load(const std::string& src,\n                    const std::function<void(const uint8_t*, size_t)>& callback,\n                    const ResourceType resource_type = ResourceType::kOthers,\n                    bool need_redirect = false) = 0;\n\n  virtual RawResource LoadSync(const std::string& src,\n                               const ResourceType = ResourceType::kOthers,\n                               bool need_redirect = false) = 0;\n\n  // Some loader may not support cancel yet.\n  virtual void CancelAll() {}\n\n  virtual bool NeedInterceptUrl() { return false; }\n};\n}  // namespace clay\n\n#endif  // CLAY_NET_LOADER_RESOURCE_LOADER_H_\n"
  },
  {
    "path": "clay/net/loader/resource_loader_creator_service.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_LOADER_RESOURCE_LOADER_CREATOR_SERVICE_H_\n#define CLAY_NET_LOADER_RESOURCE_LOADER_CREATOR_SERVICE_H_\n\n#include <memory>\n\n#include \"clay/common/service/service.h\"\n#include \"clay/net/loader/resource_loader.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n\nnamespace clay {\n\nusing ResourceLoaderCreator = std::function<std::shared_ptr<ResourceLoader>(\n    fml::RefPtr<fml::TaskRunner> task_runner,\n    std::shared_ptr<ResourceLoaderIntercept> intercept)>;\n\nclass ResourceLoaderCreatorService\n    : public Service<ResourceLoaderCreatorService, Owner::kPlatform,\n                     ServiceFlags::kManualRegister |\n                         ServiceFlags::kMultiThread> {\n public:\n  explicit ResourceLoaderCreatorService(ResourceLoaderCreator creator)\n      : creator_(creator) {}\n  ~ResourceLoaderCreatorService() override = default;\n\n  std::shared_ptr<ResourceLoader> CreateResourceLoader(\n      fml::RefPtr<fml::TaskRunner> task_runner,\n      std::shared_ptr<ResourceLoaderIntercept> intercept) {\n    if (!creator_) {\n      return nullptr;\n    }\n    return creator_(task_runner, intercept);\n  }\n\n private:\n  ResourceLoaderCreator creator_;\n};\n}  // namespace clay\n\n#endif  // CLAY_NET_LOADER_RESOURCE_LOADER_CREATOR_SERVICE_H_\n"
  },
  {
    "path": "clay/net/loader/resource_loader_factory.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/loader/resource_loader_factory.h\"\n\n#include <utility>\n\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/net/loader/data_image_loader.h\"\n#include \"clay/net/loader/resource_loader_creator_service.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/net/loader/resource_loader_platform.h\"\n#include \"clay/net/url/url_helper.h\"\n\nnamespace clay {\n\n// static\nstd::shared_ptr<ResourceLoader> ResourceLoaderFactory::Create(\n    const std::string& uri, fml::RefPtr<fml::TaskRunner> task_runner,\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    std::shared_ptr<ServiceManager> service_manager) {\n  switch (url::ParseUriScheme(uri)) {\n    case url::UriSchemeType::kData:\n      return std::make_shared<DataImageLoader>(std::move(task_runner));\n    default: {\n      std::shared_ptr<ResourceLoader> resource_loader = nullptr;\n      if (service_manager) {\n        auto creator_service =\n            service_manager\n                ->GetMultiThreadService<ResourceLoaderCreatorService>();\n        // Try to create resource loader from creator service if it exists. The\n        // host application can register a creator service to create resource\n        // loader.\n        if (creator_service) {\n          resource_loader =\n              creator_service->CreateResourceLoader(task_runner, intercept);\n        }\n      }\n\n      if (!resource_loader) {\n        // Fallback to create platform resource loader.\n        resource_loader = CreatePlatformResourceLoader(\n            intercept, std::move(task_runner), service_manager);\n      }\n      return resource_loader;\n    }\n  }\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/loader/resource_loader_factory.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_LOADER_RESOURCE_LOADER_FACTORY_H_\n#define CLAY_NET_LOADER_RESOURCE_LOADER_FACTORY_H_\n\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/task_runner.h\"\n\nnamespace clay {\n\nclass ResourceLoader;\nclass ResourceLoaderIntercept;\nclass ServiceManager;\n\n// A factory use to create Platform |ResourceLoader|.\n// ResourceLoader->load(src,callback) will post task to task_runner expect\n// network task.Thus callback will execute in task_runner.\n//\n// If we making a network request, net module will use its network thread\n// to initiate network requests.task_runner is not use in Asynchronous network\n// request.After network Request completion,ResourceLoader will use task_runner\n// to execute callback.\n//\n// So we can assume that asynchronous tasks will execute in task_runner and\n// callback will be called in task_runner in any cases.\nclass ResourceLoaderFactory {\n public:\n  static std::shared_ptr<ResourceLoader> Create(\n      const std::string& uri, fml::RefPtr<fml::TaskRunner> task_runner,\n      std::shared_ptr<ResourceLoaderIntercept> intercept = nullptr,\n      std::shared_ptr<ServiceManager> service_manager = nullptr);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_LOADER_RESOURCE_LOADER_FACTORY_H_\n"
  },
  {
    "path": "clay/net/loader/resource_loader_intercept.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_LOADER_RESOURCE_LOADER_INTERCEPT_H_\n#define CLAY_NET_LOADER_RESOURCE_LOADER_INTERCEPT_H_\n\n#include <functional>\n#include <string>\n\nusing ResourceLoaderShouldInterceptUrlCallback =\n    std::function<void(const char* origin_url, bool should_decode,\n                       char* intercept_url, int max_path_length)>;\nnamespace clay {\n\nclass ResourceLoaderIntercept {\n public:\n  ResourceLoaderIntercept() = default;\n  virtual ~ResourceLoaderIntercept() = default;\n\n  void BindShouldInterceptUrlCallback(\n      ResourceLoaderShouldInterceptUrlCallback callback) {\n    should_intercept_url_callback_ = callback;\n  }\n\n  std::string ShouldInterceptUrl(const std::string& origin_url,\n                                 bool should_decode = false) {\n    if (should_intercept_url_callback_) {\n      std::size_t max_path_length =\n          origin_url.size() > 1024 ? origin_url.size() : 1024;\n      char* intercept_url = new char[max_path_length + 1];\n      std::snprintf(intercept_url, max_path_length + 1, \"%s\",\n                    origin_url.c_str());\n      should_intercept_url_callback_(origin_url.c_str(), should_decode,\n                                     intercept_url, max_path_length + 1);\n      std::string intercept_url_str = intercept_url;\n      delete[] intercept_url;\n      return intercept_url_str;\n    }\n    return origin_url;\n  }\n\n private:\n  ResourceLoaderShouldInterceptUrlCallback should_intercept_url_callback_;\n};\n}  // namespace clay\n\n#endif  // CLAY_NET_LOADER_RESOURCE_LOADER_INTERCEPT_H_\n"
  },
  {
    "path": "clay/net/loader/resource_loader_platform.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_LOADER_RESOURCE_LOADER_PLATFORM_H_\n#define CLAY_NET_LOADER_RESOURCE_LOADER_PLATFORM_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/net/loader/resource_loader.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/net/resource_type.h\"\n\nnamespace clay {\n\nstd::shared_ptr<ResourceLoader> CreatePlatformResourceLoader(\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    fml::RefPtr<fml::TaskRunner> task_runner,\n    std::shared_ptr<ServiceManager> service_manager);\n\n}\n\n#endif  // CLAY_NET_LOADER_RESOURCE_LOADER_PLATFORM_H_\n"
  },
  {
    "path": "clay/net/macros.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_MACROS_H_\n#define CLAY_NET_MACROS_H_\n\n#if (DEBUG_NET)\n#define NET_LOG FML_LOG(ERROR) << \"[CLAY]-[NET]\"\n#else\n#define NET_LOG FML_EAT_STREAM_PARAMETERS(true)\n#endif\n\n#endif  // CLAY_NET_MACROS_H_\n"
  },
  {
    "path": "clay/net/net_loader_callback.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/net_loader_callback.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/net/resource_type.h\"\n\nnamespace clay {\n\nNetLoaderCallback::NetLoaderCallback() = default;\nNetLoaderCallback::~NetLoaderCallback() = default;\n\nvoid NetLoaderCallback::OnSucceeded(RawResource result) {\n  FML_DCHECK(!callback_issued_);\n  callback_issued_ = true;\n  succeeded_func_(request_seq_, std::move(result));\n}\n\nvoid NetLoaderCallback::OnFailed(const std::string& error) {\n  FML_DCHECK(!callback_issued_);\n  callback_issued_ = true;\n  failed_func_(request_seq_, error);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/net_loader_callback.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_NET_LOADER_CALLBACK_H_\n#define CLAY_NET_NET_LOADER_CALLBACK_H_\n\n#include <functional>\n#include <string>\n#include <utility>\n\nnamespace clay {\n\nstruct RawResource;\n\nclass NetLoaderCallback {\n public:\n  using SucceededFunc = std::function<void(size_t, RawResource&&)>;\n  using FailedFunc = std::function<void(size_t, const std::string&)>;\n\n  NetLoaderCallback();\n  ~NetLoaderCallback();\n\n  void set_succeeded_func(SucceededFunc&& succeeded) {\n    succeeded_func_ = std::move(succeeded);\n  }\n  void set_succeeded_func(const SucceededFunc& succeeded) {\n    succeeded_func_ = succeeded;\n  }\n\n  void set_failed_func(FailedFunc&& failed) {\n    failed_func_ = std::move(failed);\n  }\n  void set_failed_func(const FailedFunc& failed) { failed_func_ = failed; }\n\n  void SetRequestSeq(size_t request_seq) { request_seq_ = request_seq; }\n\n  void OnSucceeded(RawResource);\n  void OnFailed(const std::string& error);\n\n private:\n  size_t request_seq_ = 0;\n  bool callback_issued_ = false;\n  SucceededFunc succeeded_func_;\n  FailedFunc failed_func_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_NET_LOADER_CALLBACK_H_\n"
  },
  {
    "path": "clay/net/net_loader_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/net_loader_manager.h\"\n\n#include <memory>\n#include <mutex>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/no_destructor.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/net/cache/net_disk_cache.h\"\n#include \"clay/net/cache/net_resource_cache_key.h\"\n#include \"clay/net/fetcher/http_resource_fetcher_factory.h\"\n#include \"clay/net/fetcher/http_resource_fetcher_host.h\"\n#include \"clay/net/macros.h\"\n#include \"clay/net/url/url_parse.h\"\n\nnamespace clay {\n\n// static\nNetLoaderManager& NetLoaderManager::Instance() {\n  static fml::NoDestructor<NetLoaderManager> instance;\n  return *(instance.get());\n}\n\nNetLoaderManager::NetLoaderManager() {}\n\nvoid NetLoaderManager::EnsureSetup(fml::RefPtr<fml::TaskRunner> task_runner,\n                                   int64_t max_cache_size) {\n  if (task_runner_) {\n    return;\n  }\n  task_runner_ = task_runner;\n  FML_CHECK(task_runner_);\n\n  {\n    std::scoped_lock lock(disk_cache_mutex_);\n    if (!disk_cache_) {\n      disk_cache_ =\n          fml::MakeRefCounted<NetDiskCache>(task_runner_, max_cache_size);\n      FML_LOG(WARNING) << \"NetLoaderManager::SetupCache!\";\n    }\n  }\n}\n\nNetLoaderManager::~NetLoaderManager() = default;\n\nstd::string NetLoaderManager::TakeLastResponse(const std::string& uri) {\n  std::scoped_lock lock(response_mutex_);\n  auto it = response_by_uri_.find(uri);\n  if (it == response_by_uri_.end()) {\n    return {};\n  }\n  std::string response = std::move(it->second);\n  response_by_uri_.erase(it);\n  return response;\n}\n\nvoid NetLoaderManager::SetResponse(const std::string& uri,\n                                   std::string&& response) {\n  std::scoped_lock lock(response_mutex_);\n  const size_t max_response_size = 128;\n  if (response_by_uri_.size() >= max_response_size &&\n      response_by_uri_.find(uri) == response_by_uri_.end()) {\n    response_by_uri_.erase(response_by_uri_.begin());\n  }\n  response_by_uri_[uri] = std::move(response);\n}\n\nvoid NetLoaderManager::UnsetCache() {\n  std::scoped_lock lock(disk_cache_mutex_);\n  if (!disk_cache_) {\n    FML_DLOG(WARNING)\n        << \"Call NetLoaderManager::UnsetCache without disk_cache_!\";\n    return;\n  }\n  disk_cache_->WillDestory();\n  auto task_runner = disk_cache_->GetIOTaskRunner();\n  auto destroy_task =\n      fml::MakeCopyable([disk_cache = std::move(disk_cache_)]() mutable {});\n  task_runner->PostTask([destroy_task]() { destroy_task(); });\n}\n\nsize_t NetLoaderManager::Request(const std::string& url,\n                                 NetLoaderCallback&& callback) {\n  return Request(url, \"\", \"\", {}, std::move(callback));\n}\n\nsize_t NetLoaderManager::Request(const std::string& uri,\n                                 const std::string& method, std::string&& body,\n                                 std::map<std::string, std::string>&& headers,\n                                 NetLoaderCallback&& callback) {\n  size_t request_seq = kInvalidRequestSeq;\n  {\n    std::scoped_lock lock(waiters_mutex_);\n    ++current_request_seq_;\n    if (current_request_seq_ == kInvalidRequestSeq) {\n      ++current_request_seq_;\n    }\n    request_seq = current_request_seq_;\n    callback.SetRequestSeq(request_seq);\n\n    auto iter = waiters_.find(uri);\n    if (iter != waiters_.end()) {\n      NET_LOG << \" add to pending requests \" << uri << \" seq \"\n              << current_request_seq_;\n      iter->second.push_back({request_seq, callback});\n      return request_seq;\n    }\n\n    NET_LOG << \" insert a new request \" << uri << \" seq \"\n            << current_request_seq_;\n    waiters_.insert({uri, {{request_seq, callback}}});\n  }\n\n  // 1. Make a cache key by uri\n  // 2. Check if cache key hit => get content by cache key\n  // 3. Else send request, update cache after request finish\n  NetResourceCacheKey cache_key(uri);\n  if (!cache_key.Valid()) {\n    callback.OnFailed(\"invalid url\");\n    return kInvalidRequestSeq;\n  }\n\n  {\n    std::unique_lock lock(disk_cache_mutex_);\n    if (disk_cache_ && disk_cache_->ContainsKey(&cache_key)) {\n      disk_cache_->GetCacheContent(\n          std::move(cache_key),\n          std::bind(&NetLoaderManager::OnSucceeded, this, std::placeholders::_1,\n                    request_seq, std::placeholders::_2, true));\n      return request_seq;\n    }\n  }\n\n  // Fetch resource\n  auto fetcher = HttpResourceFetcherFactory::CreateFetcher(\n      host_net_loader_, cache_key.Uri(), request_seq);\n  if (!fetcher) {\n    OnFailed(uri, request_seq, HttpResourceFetcher::kMaxRetryTime,\n             \"No available fetcher.\");\n    return kInvalidRequestSeq;\n  }\n  fetcher->SetMethod(method);\n  fetcher->SetHeaders(std::move(headers));\n  fetcher->SetBody(std::move(body));\n  fetcher->Load();\n  {\n    std::scoped_lock lock(waiters_mutex_);\n    running_fetchers_.emplace(request_seq, std::move(fetcher));\n  }\n\n  return request_seq;\n}\n\nsize_t NetLoaderManager::RequestWithFetcher(\n    const std::string& uri, NetLoaderCallback&& callback,\n    std::unique_ptr<HttpResourceFetcher> fetcher) {\n  size_t request_seq = kInvalidRequestSeq;\n  {\n    std::scoped_lock lock(waiters_mutex_);\n    ++current_request_seq_;\n    if (current_request_seq_ == kInvalidRequestSeq) {\n      ++current_request_seq_;\n    }\n    request_seq = current_request_seq_;\n    callback.SetRequestSeq(request_seq);\n\n    auto iter = waiters_.find(uri);\n    if (iter == waiters_.end()) {\n      waiters_.insert({uri, {{request_seq, callback}}});\n    } else {\n      return kInvalidRequestSeq;\n    }\n  }\n\n  fetcher->Load();\n  {\n    std::scoped_lock lock(waiters_mutex_);\n    running_fetchers_.emplace(request_seq, std::move(fetcher));\n  }\n  return request_seq;\n}\n\n// Synchronous request won't use cache and waiting queue.\nRawResource NetLoaderManager::RequestSync(const std::string& uri) {\n  url::Uri parsed_uri(uri);\n  if (!parsed_uri.Valid()) {\n    return {0, nullptr};\n  }\n\n  size_t request_sep = kInvalidRequestSeq;\n  {\n    std::scoped_lock lock(waiters_mutex_);\n    ++current_request_seq_;\n    if (current_request_seq_ == kInvalidRequestSeq) {\n      ++current_request_seq_;\n    }\n    request_sep = current_request_seq_;\n  }\n\n  RawResource resource;\n  std::unique_ptr<HttpResourceFetcher> fetcher =\n      HttpResourceFetcherFactory::CreateFetcher(host_net_loader_, parsed_uri,\n                                                request_sep);\n  resource = fetcher->LoadSync();\n  NET_LOG << \" request finish \" << parsed_uri.Spec()\n          << \"\\tlength:\" << resource.length;\n  return resource;\n}\n\nvoid NetLoaderManager::NotifyLowMemory() {\n  FML_CHECK(task_runner_->RunsTasksOnCurrentThread());\n  std::unique_lock lock(disk_cache_mutex_);\n  if (disk_cache_) {\n    disk_cache_->ClearCaches();\n  }\n}\n\nvoid NetLoaderManager::Clear() {\n  {\n    std::scoped_lock lock(waiters_mutex_);\n    waiters_.clear();\n  }\n  {\n    std::scoped_lock lock(response_mutex_);\n    response_by_uri_.clear();\n  }\n}\n\nvoid NetLoaderManager::CancelBySeq(size_t seq) {\n  // Iterate all pending entry.\n  std::unique_lock lock(waiters_mutex_);\n\n  auto runner_iter = running_fetchers_.find(seq);\n  std::string url;\n  if (runner_iter != running_fetchers_.end()) {\n    // Cancel should trigger OnFailed callback\n    runner_iter->second->Cancel();\n    url = runner_iter->second->Url();\n  }\n\n  auto it = waiters_.begin();\n\n  while (it != waiters_.end()) {\n    NetLoaderWaiters& waiter = it->second;\n    size_t old_size = waiter.size();\n    waiter.erase(std::remove_if(waiter.begin(), waiter.end(),\n                                [seq](const NetWaiterEntity& entity) {\n                                  return entity.request_seq == seq;\n                                }),\n                 waiter.end());\n    if (waiter.size() != old_size) {\n      if (waiter.empty()) {\n        waiters_.erase(it);\n      }\n      break;\n    } else {\n      ++it;\n    }\n  }\n\n  lock.unlock();\n  std::unique_lock response_lock(response_mutex_);\n  if (!url.empty() && response_by_uri_.find(url) != response_by_uri_.end()) {\n    response_by_uri_.erase(url);\n  }\n}\n\nNetLoaderManager::NetLoaderWaiters NetLoaderManager::TakeWaiters(\n    const std::string& uri) {\n  NetLoaderWaiters uri_waiters;\n\n  {\n    std::scoped_lock lock(waiters_mutex_);\n\n    auto iter = waiters_.find(uri);\n    if (iter != waiters_.end()) {\n      uri_waiters = std::move(iter->second);\n      waiters_.erase(iter);\n    }\n  }\n  return uri_waiters;\n}\n\nvoid NetLoaderManager::OnSucceeded(const std::string& uri, size_t request_seq,\n                                   RawResource&& resource,\n                                   bool from_disk_cache) {\n  NetLoaderWaiters uri_waiters = TakeWaiters(uri);\n  for (auto& waiter : uri_waiters) {\n    waiter.callback.OnSucceeded(resource);\n  }\n\n// TODO(zuojinglong.9) Now open on Harmony platform, gradually expanding to\n// other platforms.\n#if defined(OS_HARMONY)\n  if (!from_disk_cache) {\n    std::scoped_lock lock(disk_cache_mutex_);\n    if (disk_cache_) {\n      // Write cache if not exist.\n      NetResourceCacheKey cache_key(uri);\n\n      fml::TaskRunner::RunNowOrPostTask(\n          task_runner_, [disk_cache = disk_cache_, cache_key,\n                         resource = std::move(resource)]() mutable {\n            if (!disk_cache->ContainsKey(&cache_key)) {\n              disk_cache->WriteContent(cache_key.ResourceId(),\n                                       std::move(resource));\n            }\n          });\n    }\n  }\n#endif\n\n  size_t removed;\n  {\n    std::scoped_lock lock(waiters_mutex_);\n    removed = running_fetchers_.erase(request_seq);\n  }\n  if (removed == 0) {\n    FML_LOG(ERROR) << \"Invalid status to fetch \" << uri\n                   << \", request_seq: \" << request_seq;\n  }\n}\n\nvoid NetLoaderManager::OnFailed(const std::string& uri, size_t request_seq,\n                                int failed_time, const std::string& reason) {\n  if (failed_time < HttpResourceFetcher::kMaxRetryTime) {\n    // Fetch resource\n    FML_LOG(WARNING) << \"Failed to fetch \" << uri << \" for \" << reason\n                     << \" . Going to retry.\";\n    auto fetcher = HttpResourceFetcherFactory::CreateFetcher(\n        host_net_loader_, url::Uri(uri), request_seq, failed_time + 1);\n    auto fetcher_ptr = fetcher.get();\n    {\n      std::scoped_lock lock(waiters_mutex_);\n      running_fetchers_.erase(request_seq);\n      running_fetchers_.emplace(request_seq, std::move(fetcher));\n    }\n    fetcher_ptr->Load();\n\n    return;\n  }\n  NetLoaderWaiters uri_waiters = TakeWaiters(uri);\n  for (auto& waiter : uri_waiters) {\n    waiter.callback.OnFailed(reason);\n  }\n  {\n    std::scoped_lock lock(waiters_mutex_);\n    running_fetchers_.erase(request_seq);\n  }\n}\n\nvoid NetLoaderManager::OnFinished(size_t request_seq, bool success,\n                                  const uint8_t* raw_data, size_t length) {\n  std::unique_lock lock(waiters_mutex_);\n  auto it = running_fetchers_.find(request_seq);\n  if (it == running_fetchers_.end()) {\n    return;\n  }\n  if (!it->second->IsHostLoader()) {\n    return;\n  }\n\n  HttpResourceFetcherHost* fetcher =\n      reinterpret_cast<HttpResourceFetcherHost*>(it->second.get());\n  lock.unlock();\n\n  fetcher->OnFinished(success, raw_data, length);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/net_loader_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_NET_LOADER_MANAGER_H_\n#define CLAY_NET_NET_LOADER_MANAGER_H_\n\n#include <functional>\n#include <map>\n#include <memory>\n#include <mutex>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/concurrent_message_loop.h\"\n#include \"clay/net/fetcher/http_resource_fetcher.h\"\n#include \"clay/net/net_loader_callback.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass CacheStats;\nclass NetDiskCache;\nclass NetResourceCacheKey;\nstruct RawResource;\n\nclass NetLoaderManager {\n public:\n  static NetLoaderManager& Instance();\n\n  NetLoaderManager();\n\n  ~NetLoaderManager();\n\n  constexpr static size_t kInvalidRequestSeq = 0;\n\n  NetLoaderManager(const NetLoaderManager&) = delete;\n  NetLoaderManager& operator=(const NetLoaderManager&) = delete;\n  NetLoaderManager(NetLoaderManager&&) = delete;\n  NetLoaderManager& operator=(NetLoaderManager&&) = delete;\n\n  using HostNetLoader = std::function<void(\n      const std::string& uri, const std::string& method,\n      const std::string& body,\n      const std::map<std::string, std::string>& headers, size_t request_seq)>;\n\n  size_t Request(const std::string& uri, NetLoaderCallback&& callback);\n\n  size_t Request(const std::string& url, const std::string& method,\n                 std::string&& body,\n                 std::map<std::string, std::string>&& headers,\n                 NetLoaderCallback&& callback);\n  // This function send request by given fetcher (for testing).\n  size_t RequestWithFetcher(const std::string& uri,\n                            NetLoaderCallback&& callback,\n                            std::unique_ptr<HttpResourceFetcher> fetcher);\n  RawResource RequestSync(const std::string& uri);\n\n  void OnSucceeded(const std::string& uri, size_t request_seq,\n                   RawResource&& resource, bool from_disk_cache = false);\n  void OnFailed(const std::string& uri, size_t request_seq, int failed_time,\n                const std::string& reason);\n\n  // OnFinished is only provide to HttpResourceFetcherHost.\n  void OnFinished(size_t request_seq, bool success, const uint8_t* data,\n                  size_t length);\n\n  void NotifyLowMemory();\n\n  void Clear();\n\n  void CancelBySeq(size_t seqs);\n\n  void EnsureSetup(fml::RefPtr<fml::TaskRunner> task_runner,\n                   int64_t max_cache_size);\n  void UnsetCache();\n\n  void SetHostNetLoader(HostNetLoader&& host_net_loader) {\n    host_net_loader_ = std::move(host_net_loader);\n  }\n\n  std::string TakeLastResponse(const std::string& uri);\n  void SetResponse(const std::string& uri, std::string&& response);\n\n private:\n  FRIEND_TEST(NetLoaderManagerTest, Cache);\n  FRIEND_TEST(NetLoaderManagerTest, TrimCacheWhileAccess);\n\n  struct NetWaiterEntity {\n    size_t request_seq;\n    NetLoaderCallback callback;\n  };\n  using NetLoaderWaiters = std::vector<NetWaiterEntity>;\n\n  NetLoaderWaiters TakeWaiters(const std::string& uri);\n\n  fml::RefPtr<NetDiskCache> disk_cache_;\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n\n  size_t current_request_seq_ = 0;\n  std::unordered_map<std::string, NetLoaderWaiters> waiters_;\n  std::unordered_map<size_t, std::unique_ptr<HttpResourceFetcher>>\n      running_fetchers_;\n\n  HostNetLoader host_net_loader_ = nullptr;\n\n  std::unordered_map<std::string, std::string> response_by_uri_;\n  std::mutex response_mutex_;\n\n  std::mutex disk_cache_mutex_;\n  std::mutex waiters_mutex_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_NET_LOADER_MANAGER_H_\n"
  },
  {
    "path": "clay/net/net_loader_manager_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/net_loader_callback.h\"\n#define FML_USED_ON_EMBEDDER\n\n#include <atomic>\n#include <future>\n#include <memory>\n#include <thread>\n#include <utility>\n\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/net/cache/cache_stats.h\"\n#include \"clay/net/cache/net_disk_cache.h\"\n#include \"clay/net/cache/net_resource_cache_key.h\"\n#include \"clay/net/net_loader_manager.h\"\n#include \"clay/net/resource_type.h\"\n#include \"clay/testing/thread_test.h\"\n\nnamespace clay {\n\nclass NetLoaderManagerTest : public clay::testing::ThreadTest {\n public:\n  NetLoaderManagerTest() = default;\n\n  void SetUpCache(int64_t max_size, const std::string& cache_path) {\n    clay::PersistentCache::SetCacheDirectoryPath(cache_path);\n    clay::PersistentCache::ResetCacheForProcess();\n\n    NetLoaderManager::Instance().UnsetCache();\n    NetLoaderManager::Instance().EnsureSetup(\n        clay::testing::ThreadTest::CreateNewThread(\"io\"), max_size);\n  }\n  std::chrono::seconds sec_ = std::chrono::seconds(1);\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(NetLoaderManagerTest);\n};\n\nTEST_F(NetLoaderManagerTest, Load) {\n  std::vector<std::string> url_set = {\n      \"https://via.placeholder.com/200.png\",\n      \"https://via.placeholder.com/300.png\",\n      \"https://via.placeholder.com/400.png\",\n  };\n  fml::CountDownLatch latch(url_set.size());\n  std::set<int> pending;\n  for (auto src : url_set) {\n    NetLoaderCallback callback;\n    callback.set_failed_func(\n        [&latch](size_t request_seq, const std::string& reason) {\n          latch.CountDown();\n          EXPECT_TRUE(false);\n        });\n    callback.set_succeeded_func(\n        [&latch](size_t request_seq, RawResource&& raw_resource) {\n          latch.CountDown();\n          EXPECT_NE(raw_resource.length, 0u);\n        });\n    int seq = NetLoaderManager::Instance().Request(src, std::move(callback));\n    pending.insert(seq);\n  }\n  latch.Wait();\n}\n\nTEST_F(NetLoaderManagerTest, LoadSync) {\n  std::vector<std::string> url_set = {\n      \"https://via.placeholder.com/200.png\",\n\n      \"https://via.placeholder.com/300.png\",\n  };\n\n  for (auto src : url_set) {\n    RawResource resource = NetLoaderManager::Instance().RequestSync(src);\n    EXPECT_NE(resource.length, 0u);\n  }\n}\n\nTEST_F(NetLoaderManagerTest, Cancel) {\n  std::vector<std::string> url_set = {\n      \"https://via.placeholder.com/200.png\",\n\n      \"https://via.placeholder.com/300.png\",\n  };\n\n  std::set<int> pending;\n  for (auto src : url_set) {\n    NetLoaderCallback callback;\n    callback.set_failed_func(\n        [](size_t request_seq, const std::string& reason) {});\n    callback.set_succeeded_func(\n        [](size_t request_seq, RawResource&& raw_resource) {});\n    int seq = NetLoaderManager::Instance().Request(src, std::move(callback));\n    pending.insert(seq);\n\n    for (int seq : pending) {\n      NetLoaderManager::Instance().CancelBySeq(seq);\n    }\n  }\n}\n\nTEST_F(NetLoaderManagerTest, Cache) {\n  fml::ScopedTemporaryDirectory dir;\n  SetUpCache(10 * 1024 * 1024, dir.path());\n\n  std::vector<std::string> url_set = {\n      \"https://via.placeholder.com/200.png\",\n      \"https://via.placeholder.com/300.png\",\n      \"https://via.placeholder.com/400.png\",\n  };\n\n  fml::CountDownLatch latch(url_set.size());\n\n  std::atomic<int64_t> total_received_length = 0;\n\n  std::this_thread::sleep_for(sec_);\n  NetDiskCache* disk_cache = NetLoaderManager::Instance().disk_cache_.get();\n  // Ensure cache_stats_ successfully initialized.\n  while (!disk_cache->Available()) {\n    std::this_thread::sleep_for(sec_);\n  }\n  for (auto src : url_set) {\n    NetLoaderCallback callback;\n    callback.set_failed_func(\n        [&latch](size_t request_seq, const std::string& reason) {\n          latch.CountDown();\n          EXPECT_TRUE(false);\n        });\n    callback.set_succeeded_func(\n        [&total_received_length, &latch](size_t request_seq,\n                                         RawResource&& raw_resource) {\n          latch.CountDown();\n\n          total_received_length += raw_resource.length;\n          EXPECT_NE(raw_resource.length, 0u);\n        });\n    NetLoaderManager::Instance().Request(src, std::move(callback));\n  }\n  latch.Wait();\n\n  std::this_thread::sleep_for(sec_ * 3);\n  const NetLoaderManager& loader = NetLoaderManager::Instance();\n  EXPECT_NE(loader.disk_cache_->cache_stats_, nullptr);\n  EXPECT_EQ(loader.disk_cache_->cache_stats_->GetCount(),\n            static_cast<size_t>(3));\n  EXPECT_GE(loader.disk_cache_->cache_stats_->GetSize(), total_received_length);\n\n  // Check whether cache hit\n  fml::CountDownLatch latch2(url_set.size());\n  for (auto src : url_set) {\n    NetLoaderCallback callback;\n    callback.set_failed_func(\n        [&latch2](size_t request_seq, const std::string& reason) {\n          latch2.CountDown();\n          EXPECT_TRUE(false);\n        });\n    callback.set_succeeded_func(\n        [&latch2](size_t request_seq, RawResource&& raw_resource) {\n          latch2.CountDown();\n\n          EXPECT_NE(raw_resource.length, 0u);\n        });\n    NetLoaderManager::Instance().Request(src, std::move(callback));\n  }\n  latch2.Wait();\n\n  EXPECT_EQ(loader.disk_cache_->cache_stats_->GetCount(),\n            static_cast<size_t>(3));\n  EXPECT_GE(loader.disk_cache_->cache_stats_->GetSize(), total_received_length);\n\n  NetLoaderManager::Instance().UnsetCache();\n}\n\nTEST_F(NetLoaderManagerTest, TrimCacheWhileAccess) {\n  fml::ScopedTemporaryDirectory dir;\n  int64_t cache_size = 110;  // Less than sum of first two resources.\n  SetUpCache(cache_size, dir.path());\n\n  auto make_resource = [](size_t length) -> RawResource {\n    return {length, std::shared_ptr<uint8_t>(new uint8_t[length],\n                                             std::default_delete<uint8_t[]>())};\n  };\n  std::this_thread::sleep_for(sec_);\n  NetDiskCache* disk_cache = NetLoaderManager::Instance().disk_cache_.get();\n  // Ensure cache_stats_ successfully initialized.\n  while (!disk_cache->Available()) {\n    std::this_thread::sleep_for(sec_);\n  }\n  std::string uri1 = \"https://via.placeholder.com/200.png\";\n\n  std::string uri2 = \"https://via.placeholder.com/300.png\";\n\n  std::string uri3 = \"https://www.example.com\";\n\n  NetResourceCacheKey key1 = NetResourceCacheKey(uri1);\n  NetResourceCacheKey key2 = NetResourceCacheKey(uri2);\n\n  NetResourceCacheKey tmp_key1 = NetResourceCacheKey(uri1);\n  NetResourceCacheKey tmp_key2 = NetResourceCacheKey(uri2);\n  NetResourceCacheKey tmp_key3 = NetResourceCacheKey(uri3);\n\n  EXPECT_TRUE(key1.Valid());\n  EXPECT_TRUE(key2.Valid());\n  EXPECT_TRUE(tmp_key1.Valid());\n  EXPECT_TRUE(tmp_key2.Valid());\n  EXPECT_TRUE(tmp_key3.Valid());\n\n  // Create 2 entries.\n  disk_cache->WriteContent(tmp_key1.ResourceId(), make_resource(100));\n  disk_cache->WriteContent(tmp_key2.ResourceId(), make_resource(20));\n\n  // Wait for write done.\n  std::this_thread::sleep_for(sec_);\n  // Lock the entry 1.\n  EXPECT_TRUE(disk_cache->ContainsKey(&key1));\n\n  // Insert third entry. Entry 1 has been locked, Entry 2 should be delete.\n  disk_cache->WriteContent(tmp_key3.ResourceId(), make_resource(30));\n\n  // Wait for write done.\n  std::this_thread::sleep_for(sec_);\n\n  EXPECT_FALSE(disk_cache->ContainsKey(&key2));\n  EXPECT_EQ(disk_cache->cache_stats_->GetSize(), 130);\n  EXPECT_EQ(disk_cache->cache_stats_->GetCount(), static_cast<size_t>(2));\n\n  NetLoaderManager::Instance().UnsetCache();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/resource_type.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_RESOURCE_TYPE_H_\n#define CLAY_NET_RESOURCE_TYPE_H_\n\n#include <cstring>\n#include <memory>\n#include <utility>\n\nnamespace clay {\nstruct RawResource {\n  size_t length = 0u;\n  // TODO(hanhaoshen): use std::shared_ptr<uint8_t[]>;\n  std::shared_ptr<const uint8_t> data = nullptr;\n\n  static RawResource MakeWithCopy(const uint8_t* data, const size_t length) {\n    if (data == nullptr || length == 0) {\n      return {0, nullptr};\n    }\n    auto response = std::shared_ptr<uint8_t>(new uint8_t[length],\n                                             std::default_delete<uint8_t[]>());\n    ::memcpy(reinterpret_cast<void*>(response.get()), data, length);\n    return {length, std::move(response)};\n  }\n};\n\n// align to lynx resource type.\nenum class ResourceType : int8_t {\n  kOthers = -1,\n  kImage,\n  kFont,\n  kLottie,\n  kVideo,\n  kSvg,\n  kTemplate,\n  kLynxCoreJs,\n  kDynamicComponent,\n  kI18nText,\n  kExternalJs\n};\n\n}  // namespace clay\n\n#endif  // CLAY_NET_RESOURCE_TYPE_H_\n"
  },
  {
    "path": "clay/net/url/LICENSE.txt",
    "content": "Copyright 2007, Google Inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n    * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n    * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-------------------------------------------------------------------------------\n\nThe file url_parse.cc is based on nsURLParsers.cc from Mozilla. This file is\nlicensed separately as follows:\n\nThe contents of this file are subject to the Mozilla Public License Version\n1.1 (the \"License\"); you may not use this file except in compliance with\nthe License. You may obtain a copy of the License at\nhttp://www.mozilla.org/MPL/\n\nSoftware distributed under the License is distributed on an \"AS IS\" basis,\nWITHOUT WARRANTY OF ANY KIND, either express or implied. See the License\nfor the specific language governing rights and limitations under the\nLicense.\n\nThe Original Code is mozilla.org code.\n\nThe Initial Developer of the Original Code is\nNetscape Communications Corporation.\nPortions created by the Initial Developer are Copyright (C) 1998\nthe Initial Developer. All Rights Reserved.\n\nContributor(s):\n  Darin Fisher (original author)\n\nAlternatively, the contents of this file may be used under the terms of\neither the GNU General Public License Version 2 or later (the \"GPL\"), or\nthe GNU Lesser General Public License Version 2.1 or later (the \"LGPL\"),\nin which case the provisions of the GPL or the LGPL are applicable instead\nof those above. If you wish to allow use of your version of this file only\nunder the terms of either the GPL or the LGPL, and not to allow others to\nuse your version of this file under the terms of the MPL, indicate your\ndecision by deleting the provisions above and replace them with the notice\nand other provisions required by the GPL or the LGPL. If you do not delete\nthe provisions above, a recipient may use your version of this file under\nthe terms of any one of the MPL, the GPL or the LGPL."
  },
  {
    "path": "clay/net/url/README.chromium",
    "content": "Name: url_parse\nVersion: unknown\nUpdate Mechanism: Manual\nURL: https://hg.mozilla.org/mozilla-central/file/tip/netwerk/base/nsURLParsers.cpp\nLicense: MPL-2\nLicense File: LICENSE.txt\nUpdate Mechanism: Static (https://crbug.com/419415029)\nShipped: yes\nSecurity Critical: yes\nCPEPrefix: unknown\n\nDescription:\n\nThe file url_parse.cc is based on nsURLParsers.cc from Mozilla.\n"
  },
  {
    "path": "clay/net/url/README.md",
    "content": "# url_parse\n\n`url_parse.{h,cc}` are based on the same files in Chromium under\n`//url/third_party/mozilla` but have been slightly modified for our use case.\n`url_parse_internal.{h,cc}` contains additional functions needed by the former\nfiles but aren't provided directly.  These are also ported from Chromium's\nversion.\n\n## version and path\n\npath: //chromium/src/third_party/openscreen/src/third_party/mozilla\n\ncommit: e50a1d65fa41a9864e900e60bd7a9c59dfe4bb78"
  },
  {
    "path": "clay/net/url/build.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nurl_parse_sources = [\n  \"uri.cc\",\n  \"uri.h\",\n  \"url_helper.cc\",\n  \"url_helper.h\",\n  \"url_parse.cc\",\n  \"url_parse.h\",\n  \"url_parse_internal.cc\",\n  \"url_parse_internal.h\",\n]\n"
  },
  {
    "path": "clay/net/url/uri.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/url/uri.h\"\n\n#include <string_view>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/net/macros.h\"\n\nnamespace clay {\n\nnamespace url {\n\nUri::Uri(const std::string& uri) : spec_(uri) { DoParse(); }\n\nstd::string_view Uri::PathView() const {\n  if (!valid_) {\n    return \"\";\n  }\n  return std::string_view(spec_.c_str() + path_parsed_.begin, path_parsed_.len);\n}\n\nstd::string_view Uri::SchemeView() const {\n  if (!valid_) {\n    return {};\n  }\n  return std::string_view(spec_.c_str() + scheme_parsed_.begin,\n                          scheme_parsed_.len);\n}\n\nstd::string_view Uri::SchemeHostPortView() const {\n  if (!valid_) {\n    return \"\";\n  }\n  if (port_parsed_.is_valid()) {\n    return std::string_view(spec_.c_str() + scheme_parsed_.begin,\n                            port_parsed_.end() - scheme_parsed_.begin);\n  }\n  return std::string_view(spec_.c_str() + scheme_parsed_.begin,\n                          host_parsed_.end() - scheme_parsed_.begin);\n}\n\nstd::string Uri::SchemeHostPort() const {\n  if (!valid_) {\n    return \"\";\n  }\n  if (port_parsed_.is_valid()) {\n    return spec_.substr(scheme_parsed_.begin,\n                        port_parsed_.end() - scheme_parsed_.begin);\n  }\n  return spec_.substr(scheme_parsed_.begin,\n                      host_parsed_.end() - scheme_parsed_.begin);\n}\n\nconst std::vector<std::string_view>& Uri::QueryKeys() {\n  if (!query_valid_) {\n    DoParseQuery();\n  }\n  return query_keys_;\n}\n\nconst std::vector<std::string_view>& Uri::QueryValues() {\n  if (!query_valid_) {\n    DoParseQuery();\n  }\n  return query_values_;\n}\n\nstd::string_view Uri::GetQueryParameter(const std::string& key) {\n  FML_DCHECK(valid_);\n\n  if (!query_valid_) {\n    DoParseQuery();\n  }\n\n  FML_DCHECK(query_keys_.size() == query_values_.size());\n  size_t i = 0;\n  for (; i < query_keys_.size(); ++i) {\n    if (query_keys_[i] == key) {\n      break;\n    }\n  }\n  if (i != query_keys_.size()) {\n    return query_values_[i];\n  }\n  return {};\n}\n\nvoid Uri::DoParse() {\n  if (valid_ || spec_.empty()) {\n    return;\n  }\n\n  Parsed input_parsed;\n  ParseStandardURL(spec_.data(), spec_.size(), &input_parsed);\n  if (!input_parsed.host.is_valid() || !input_parsed.scheme.is_valid()) {\n    return;\n  }\n  // Diff between scheme and host should be \"://\"\n  if (input_parsed.scheme.is_valid() &&\n      (input_parsed.scheme.end() + 3 != input_parsed.host.begin)) {\n    return;\n  }\n  scheme_parsed_ = input_parsed.scheme;\n  host_parsed_ = input_parsed.host;\n  port_parsed_ = input_parsed.port;\n  path_parsed_ = input_parsed.path;\n  query_parsed_ = input_parsed.query;\n\n  valid_ = true;\n\n  NET_LOG << \"lgl parsed to Scheme: \" << scheme_parsed_.begin\n          << \" Host:\" << host_parsed_.begin << \" Port: \" << port_parsed_.begin\n          << \" Path:\" << path_parsed_.begin;\n}\n\nvoid Uri::DoParseQuery() {\n  FML_DCHECK(!query_valid_);\n  url::Component query = query_parsed_;\n  url::Component key;\n  url::Component value;\n  while (url::ExtractQueryKeyValue(spec_.c_str(), &query, &key, &value)) {\n    query_keys_.emplace_back(spec_.c_str() + key.begin, key.len);\n    query_values_.emplace_back(spec_.c_str() + value.begin, value.len);\n  }\n  query_valid_ = true;\n}\n\n}  // namespace url\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/url/uri.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_URL_URI_H_\n#define CLAY_NET_URL_URI_H_\n\n#include <sstream>\n#include <string>\n#include <string_view>\n#include <vector>\n\n#include \"clay/net/url/url_parse.h\"\n\nnamespace clay {\n\nnamespace url {\n\nclass Uri {\n public:\n  Uri() = default;\n  explicit Uri(const std::string& uri);\n\n  const std::string& Spec() const { return spec_; }\n  std::string_view PathView() const;\n\n  std::string_view SchemeView() const;\n\n  std::string_view SchemeHostPortView() const;\n  std::string SchemeHostPort() const;\n\n  const std::vector<std::string_view>& QueryKeys();\n  const std::vector<std::string_view>& QueryValues();\n\n  std::string_view GetQueryParameter(const std::string& key);\n\n  bool Valid() const { return valid_; }\n\n private:\n  // TODO(hanhaoshen): support port and query;\n  // parse scheme+host and path from uri\n  void DoParse();\n\n  void DoParseQuery();\n\n  bool valid_ = false;\n  bool query_valid_ = false;\n\n  // The origin data of uri.\n  std::string spec_;\n\n  Component scheme_parsed_;\n  Component host_parsed_;\n  Component port_parsed_;\n  Component path_parsed_;\n  Component query_parsed_;\n\n  std::vector<std::string_view> query_keys_;\n  std::vector<std::string_view> query_values_;\n};\n\n}  // namespace url\n}  // namespace clay\n\n#endif  // CLAY_NET_URL_URI_H_\n"
  },
  {
    "path": "clay/net/url/url_helper.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/url/url_helper.h\"\n\n#include \"base/include/string/string_utils.h\"\n\nnamespace clay {\n\nnamespace url {\n\nnamespace {\n\n// Helper function to find port colon position\nsize_t FindPortColon(std::string_view url, size_t authority_start,\n                     size_t authority_end) {\n  if (authority_start >= url.size()) {\n    return std::string::npos;\n  }\n\n  if (url[authority_start] == '[') {\n    // IPv6 host: look for \"]:port\".\n    size_t ipv6_end = url.find(']', authority_start + 1);\n    if (ipv6_end != std::string::npos && ipv6_end + 1 < authority_end &&\n        url[ipv6_end + 1] == ':') {\n      return ipv6_end + 1;\n    }\n  } else {\n    // Regular host: look for \":\" before the path or query.\n    size_t port_colon = url.find(':', authority_start);\n    if (port_colon != std::string::npos && port_colon < authority_end) {\n      return port_colon;\n    }\n  }\n\n  return std::string::npos;\n}\n\n}  // namespace\n\nUriSchemeType ParseUriScheme(std::string_view uri) {\n  Component scheme;\n\n  if (!ExtractScheme(uri.data(), uri.size(), &scheme)) {\n    return UriSchemeType::kInvalid;\n  }\n  std::string scheme_str(uri.data() + scheme.begin, scheme.len);\n\n  std::transform(scheme_str.begin(), scheme_str.end(), scheme_str.begin(),\n                 [](unsigned char c) { return std::tolower(c); });\n  if (scheme_str.compare(kHttpsScheme) == 0) {\n    return UriSchemeType::kNet;\n  } else if (scheme_str.compare(kHttpScheme) == 0) {\n    return UriSchemeType::kNet;\n  } else if (scheme_str.compare(kFtpScheme) == 0) {\n    return UriSchemeType::kNet;\n  } else if (scheme_str.compare(kDataScheme) == 0) {\n    return UriSchemeType::kData;\n  } else if (scheme_str.compare(kFileScheme) == 0) {\n    return UriSchemeType::kLocalFile;\n#if OS_ANDROID\n  } else if (scheme_str.compare(kContentProviderScheme) == 0) {\n    return UriSchemeType::kContentProvider;\n  } else if (scheme_str.compare(kAssetScheme) == 0) {\n    return UriSchemeType::kAsset;\n  } else if (scheme_str.compare(kResScheme) == 0) {\n    return UriSchemeType::kRes;\n  } else {\n#elif OS_WIN\n  } else if (scheme_str.size() == 1 && std::isalpha(scheme_str[0])) {\n    return UriSchemeType::kLocalFile;\n  } else {\n#else\n  } else {\n#endif\n    return UriSchemeType::kInvalid;\n  }\n}\n\n// Trim URL by removing unnecessary parts that don't affect loading.\nstd::string TrimUrl(std::string_view url) {\n  if (url.empty()) {\n    return \"\";\n  }\n\n  // Step 0: Remove leading and trailing whitespace using StringUtils\n  std::string_view trimmed_url = lynx::base::TrimToStringView(url);\n  if (trimmed_url.empty()) {\n    return \"\";\n  }\n\n  // Step 1: Fast scheme check - early return for non-http(s) URLs\n  std::string scheme_prefix = lynx::base::StringToLowerASCII(\n      trimmed_url.substr(0, std::min<size_t>(trimmed_url.size(), 8)));\n  bool is_http = lynx::base::BeginsWith(scheme_prefix, \"http://\");\n  bool is_https = lynx::base::BeginsWith(scheme_prefix, \"https://\");\n\n  if (!is_http && !is_https) {\n    return std::string(trimmed_url);\n  }\n\n  // Step 2: Convert scheme to lowercase for http(s) URLs\n  std::string normalized_url;\n  size_t scheme_end = trimmed_url.find(\"://\");\n  if (scheme_end != std::string::npos) {\n    std::string scheme = is_http ? \"http\" : \"https\";\n    // Combine lowercase scheme with rest of URL\n    normalized_url =\n        scheme + \"://\" + std::string(trimmed_url.substr(scheme_end + 3));\n    trimmed_url = normalized_url;\n  } else {\n    // No scheme separator found, return as-is\n    return std::string(trimmed_url);\n  }\n\n  // Step 3: Remove URL fragment (#fragment), which doesn't affect loading.\n  size_t fragment_pos = trimmed_url.find('#');\n  if (fragment_pos != std::string::npos) {\n    trimmed_url = trimmed_url.substr(0, fragment_pos);\n  }\n\n  // Step 4: Remove default ports (80 for http, 443 for https).\n  size_t authority_start = is_http ? 7 : 8;\n  size_t authority_end = trimmed_url.find_first_of(\"/?\", authority_start);\n  if (authority_end == std::string::npos) {\n    authority_end = trimmed_url.size();\n  }\n\n  size_t port_colon =\n      FindPortColon(trimmed_url, authority_start, authority_end);\n  if (port_colon != std::string::npos) {\n    std::string_view port_view =\n        trimmed_url.substr(port_colon + 1, authority_end - port_colon - 1);\n    std::string port(port_view);\n    bool is_default_port =\n        (is_http && port == \"80\") || (is_https && port == \"443\");\n    if (is_default_port) {\n      // Create result by combining parts without default port\n      std::string result;\n      result.reserve(trimmed_url.size() - (authority_end - port_colon));\n      result.append(trimmed_url.substr(0, port_colon));\n      result.append(trimmed_url.substr(authority_end));\n      return result;\n    }\n  }\n\n  // Return the processed URL as std::string\n  return std::string(trimmed_url);\n}\n\n}  // namespace url\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/url/url_helper.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_URL_URL_HELPER_H_\n#define CLAY_NET_URL_URL_HELPER_H_\n\n#include <algorithm>\n#include <string>\n#include <string_view>\n\n#include \"build/build_config.h\"\n#include \"clay/net/url/url_parse.h\"\n#include \"clay/net/url/url_parse_internal.h\"\n\nnamespace clay {\nnamespace url {\n\nstatic constexpr char kDataScheme[] = \"data\";\n\n#if OS_ANDROID\nstatic constexpr char kContentProviderScheme[] = \"content\";\nstatic constexpr char kAssetScheme[] = \"asset\";\nstatic constexpr char kResScheme[] = \"res\";\n\nenum class UriSchemeType {\n  kNet = 0,\n  kLocalFile,\n  kData,\n  kContentProvider,\n  kAsset,\n  kRes,\n  kInvalid,\n};\n#else   // OS_ANDROID\nenum class UriSchemeType {\n  kNet = 0,\n  kLocalFile,\n  kData,\n  kInvalid,\n};\n#endif  // OS_ANDROID\n\nUriSchemeType ParseUriScheme(std::string_view uri);\n\n// Trim URL by removing unnecessary parts that don't affect loading.\n// This function removes URL fragments (#fragment) and default ports (80 for\n// http, 443 for https). Returns the trimmed URL.\nstd::string TrimUrl(std::string_view url);\n\n}  // namespace url\n}  // namespace clay\n\n#endif  // CLAY_NET_URL_URL_HELPER_H_\n"
  },
  {
    "path": "clay/net/url/url_helper_test.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/url/url_helper.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace url {\n\nclass UrlHelperTest : public testing::Test {\n protected:\n  void SetUp() override {}\n\n  void TearDown() override {}\n};\n\n// Test case for empty URL\nTEST_F(UrlHelperTest, EmptyUrl) { EXPECT_EQ(TrimUrl(\"\"), \"\"); }\n\n// Test case for URL with only whitespace\nTEST_F(UrlHelperTest, WhitespaceOnlyUrl) {\n  EXPECT_EQ(TrimUrl(\"   \"), \"\");\n  EXPECT_EQ(TrimUrl(\"\\t\\n\\r\"), \"\");\n  EXPECT_EQ(TrimUrl(\" \\t \\n \"), \"\");\n}\n\n// Test case for URL with leading and trailing whitespace\nTEST_F(UrlHelperTest, UrlWithWhitespace) {\n  EXPECT_EQ(TrimUrl(\"  http://example.com  \"), \"http://example.com\");\n  EXPECT_EQ(TrimUrl(\"\\thttps://example.com\\n\"), \"https://example.com\");\n  EXPECT_EQ(TrimUrl(\" \\t http://example.com/path \\n \"),\n            \"http://example.com/path\");\n}\n\n// Test case for HTTP URL without port\nTEST_F(UrlHelperTest, HttpUrlWithoutPort) {\n  EXPECT_EQ(TrimUrl(\"http://example.com\"), \"http://example.com\");\n  EXPECT_EQ(TrimUrl(\"http://example.com/path\"), \"http://example.com/path\");\n  EXPECT_EQ(TrimUrl(\"http://example.com/path?query=value\"),\n            \"http://example.com/path?query=value\");\n}\n\n// Test case for HTTP URL with default port (80)\nTEST_F(UrlHelperTest, HttpUrlWithDefaultPort) {\n  EXPECT_EQ(TrimUrl(\"http://example.com:80\"), \"http://example.com\");\n  EXPECT_EQ(TrimUrl(\"http://example.com:80/path\"), \"http://example.com/path\");\n  EXPECT_EQ(TrimUrl(\"http://example.com:80/path?query=value\"),\n            \"http://example.com/path?query=value\");\n}\n\n// Test case for HTTP URL with non-default port\nTEST_F(UrlHelperTest, HttpUrlWithNonDefaultPort) {\n  EXPECT_EQ(TrimUrl(\"http://example.com:8080\"), \"http://example.com:8080\");\n  EXPECT_EQ(TrimUrl(\"http://example.com:3000/path\"),\n            \"http://example.com:3000/path\");\n}\n\n// Test case for HTTPS URL without port\nTEST_F(UrlHelperTest, HttpsUrlWithoutPort) {\n  EXPECT_EQ(TrimUrl(\"https://example.com\"), \"https://example.com\");\n  EXPECT_EQ(TrimUrl(\"https://example.com/path\"), \"https://example.com/path\");\n  EXPECT_EQ(TrimUrl(\"https://example.com/path?query=value\"),\n            \"https://example.com/path?query=value\");\n}\n\n// Test case for HTTPS URL with default port (443)\nTEST_F(UrlHelperTest, HttpsUrlWithDefaultPort) {\n  EXPECT_EQ(TrimUrl(\"https://example.com:443\"), \"https://example.com\");\n  EXPECT_EQ(TrimUrl(\"https://example.com:443/path\"),\n            \"https://example.com/path\");\n  EXPECT_EQ(TrimUrl(\"https://example.com:443/path?query=value\"),\n            \"https://example.com/path?query=value\");\n}\n\n// Test case for HTTPS URL with non-default port\nTEST_F(UrlHelperTest, HttpsUrlWithNonDefaultPort) {\n  EXPECT_EQ(TrimUrl(\"https://example.com:8443\"), \"https://example.com:8443\");\n  EXPECT_EQ(TrimUrl(\"https://example.com:9000/path\"),\n            \"https://example.com:9000/path\");\n}\n\n// Test case for URL with fragment\nTEST_F(UrlHelperTest, UrlWithFragment) {\n  EXPECT_EQ(TrimUrl(\"http://example.com#fragment\"), \"http://example.com\");\n  EXPECT_EQ(TrimUrl(\"http://example.com/path#fragment\"),\n            \"http://example.com/path\");\n  EXPECT_EQ(TrimUrl(\"http://example.com/path?query=value#fragment\"),\n            \"http://example.com/path?query=value\");\n  EXPECT_EQ(TrimUrl(\"https://example.com:8443/path#fragment\"),\n            \"https://example.com:8443/path\");\n}\n\n// Test case for URL with IPv4 address\nTEST_F(UrlHelperTest, UrlWithIPv4Address) {\n  EXPECT_EQ(TrimUrl(\"http://192.168.1.1\"), \"http://192.168.1.1\");\n  EXPECT_EQ(TrimUrl(\"http://192.168.1.1:8080/path\"),\n            \"http://192.168.1.1:8080/path\");\n  EXPECT_EQ(TrimUrl(\"https://127.0.0.1:443\"), \"https://127.0.0.1\");\n}\n\n// Test case for URL with IPv6 address\nTEST_F(UrlHelperTest, UrlWithIPv6Address) {\n  EXPECT_EQ(TrimUrl(\"http://[2001:db8::1]\"), \"http://[2001:db8::1]\");\n  EXPECT_EQ(TrimUrl(\"http://[2001:db8::1]:8080/path\"),\n            \"http://[2001:db8::1]:8080/path\");\n  EXPECT_EQ(TrimUrl(\"https://[2001:db8::1]:443\"), \"https://[2001:db8::1]\");\n}\n\n// Test case for non-HTTP/HTTPS URL\nTEST_F(UrlHelperTest, NonHttpUrl) {\n  // These should remain unchanged\n  EXPECT_EQ(TrimUrl(\"file:///path/to/file\"), \"file:///path/to/file\");\n  EXPECT_EQ(TrimUrl(\"data:image/\"\n                    \"png;base64,\"\n                    \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42m\"\n                    \"NkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==\"),\n            \"data:image/\"\n            \"png;base64,\"\n            \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDw\"\n            \"AChwGA60e6kgAAAABJRU5ErkJggg==\");\n  EXPECT_EQ(TrimUrl(\"ftp://ftp.example.com\"), \"ftp://ftp.example.com\");\n}\n\n// Test case for complex URL with multiple components\nTEST_F(UrlHelperTest, ComplexUrl) {\n  EXPECT_EQ(TrimUrl(\"  \"\n                    \"https://example.com:443/path/to/\"\n                    \"resource?query=value&other=param#fragment  \"),\n            \"https://example.com/path/to/resource?query=value&other=param\");\n  EXPECT_EQ(TrimUrl(\"http://example.com:80/path?query=value#fragment\"),\n            \"http://example.com/path?query=value\");\n}\n\n// Test case for edge cases\nTEST_F(UrlHelperTest, EdgeCases) {\n  // URL with just scheme\n  EXPECT_EQ(TrimUrl(\"http://\"), \"http://\");\n  EXPECT_EQ(TrimUrl(\"https://\"), \"https://\");\n\n  // URL with scheme and host only\n  EXPECT_EQ(TrimUrl(\"http://example.com\"), \"http://example.com\");\n\n  // URL with trailing slash\n  EXPECT_EQ(TrimUrl(\"http://example.com/\"), \"http://example.com/\");\n  EXPECT_EQ(TrimUrl(\"http://example.com:80/\"), \"http://example.com/\");\n}\n\n// Test case for case-insensitive HTTP/HTTPS scheme\nTEST_F(UrlHelperTest, CaseInsensitiveScheme) {\n  // HTTP variations\n  EXPECT_EQ(TrimUrl(\"HTTP://example.com\"), \"http://example.com\");\n  EXPECT_EQ(TrimUrl(\"HTTP://example.com:80\"), \"http://example.com\");\n  EXPECT_EQ(TrimUrl(\"HTTP://example.com:8080/path\"),\n            \"http://example.com:8080/path\");\n  EXPECT_EQ(TrimUrl(\"HTTP://example.com/path#fragment\"),\n            \"http://example.com/path\");\n\n  // HTTPS variations\n  EXPECT_EQ(TrimUrl(\"HTTPS://example.com\"), \"https://example.com\");\n  EXPECT_EQ(TrimUrl(\"HTTPS://example.com:443\"), \"https://example.com\");\n  EXPECT_EQ(TrimUrl(\"HTTPS://example.com:8443/path\"),\n            \"https://example.com:8443/path\");\n  EXPECT_EQ(TrimUrl(\"HTTPS://example.com/path#fragment\"),\n            \"https://example.com/path\");\n\n  // Mixed case variations\n  EXPECT_EQ(TrimUrl(\"Http://Example.Com\"), \"http://Example.Com\");\n  EXPECT_EQ(TrimUrl(\"Http://Example.Com:80\"), \"http://Example.Com\");\n  EXPECT_EQ(TrimUrl(\"Https://Example.Com:443/path\"),\n            \"https://Example.Com/path\");\n  EXPECT_EQ(TrimUrl(\"hTtP://eXaMpLe.CoM:8080/path?query=value#fragment\"),\n            \"http://eXaMpLe.CoM:8080/path?query=value\");\n}\n\n}  // namespace url\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/url/url_parse.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n/* Based on nsURLParsers.cc from Mozilla\n * -------------------------------------\n * The contents of this file are subject to the Mozilla Public License Version\n * 1.1 (the \"License\"); you may not use this file except in compliance with\n * the License. You may obtain a copy of the License at\n * http://www.mozilla.org/MPL/\n *\n * Software distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License\n * for the specific language governing rights and limitations under the\n * License.\n *\n * The Original Code is mozilla.org code.\n *\n * The Initial Developer of the Original Code is\n * Netscape Communications Corporation.\n * Portions created by the Initial Developer are Copyright (C) 1998\n * the Initial Developer. All Rights Reserved.\n *\n * Contributor(s):\n *   Darin Fisher (original author)\n *\n * Alternatively, the contents of this file may be used under the terms of\n * either the GNU General Public License Version 2 or later (the \"GPL\"), or\n * the GNU Lesser General Public License Version 2.1 or later (the \"LGPL\"),\n * in which case the provisions of the GPL or the LGPL are applicable instead\n * of those above. If you wish to allow use of your version of this file only\n * under the terms of either the GPL or the LGPL, and not to allow others to\n * use your version of this file under the terms of the MPL, indicate your\n * decision by deleting the provisions above and replace them with the notice\n * and other provisions required by the GPL or the LGPL. If you do not delete\n * the provisions above, a recipient may use your version of this file under\n * the terms of any one of the MPL, the GPL or the LGPL.\n *\n * ***** END LICENSE BLOCK ***** */\n\n#include \"clay/net/url/url_parse.h\"\n\n#include <stdlib.h>\n\n#include <cassert>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nnamespace url {\nnamespace {\n\n// Returns true if the given character is a valid digit to use in a port.\nbool IsPortDigit(char ch) { return ch >= '0' && ch <= '9'; }\n\n// Returns the offset of the next authority terminator in the input starting\n// from start_offset. If no terminator is found, the return value will be equal\n// to spec_len.\nint FindNextAuthorityTerminator(const char* spec, int start_offset,\n                                int spec_len) {\n  for (int i = start_offset; i < spec_len; i++) {\n    if (IsAuthorityTerminator(spec[i])) {\n      return i;\n    }\n  }\n  return spec_len;  // Not found.\n}\n\nvoid ParseUserInfo(const char* spec, const Component& user, Component* username,\n                   Component* password) {\n  // Find the first colon in the user section, which separates the username and\n  // password.\n  int colon_offset = 0;\n  while (colon_offset < user.len && spec[user.begin + colon_offset] != ':') {\n    colon_offset++;\n  }\n\n  if (colon_offset < user.len) {\n    // Found separator: <username>:<password>\n    *username = Component(user.begin, colon_offset);\n    *password = MakeRange(user.begin + colon_offset + 1, user.begin + user.len);\n  } else {\n    // No separator, treat everything as the username\n    *username = user;\n    *password = Component();\n  }\n}\n\nvoid ParseServerInfo(const char* spec, const Component& serverinfo,\n                     Component* hostname, Component* port_num) {\n  if (serverinfo.len == 0) {\n    // No server info, host name is empty.\n    hostname->reset();\n    port_num->reset();\n    return;\n  }\n\n  // If the host starts with a left-bracket, assume the entire host is an\n  // IPv6 literal.  Otherwise, assume none of the host is an IPv6 literal.\n  // This assumption will be overridden if we find a right-bracket.\n  //\n  // Our IPv6 address canonicalization code requires both brackets to exist,\n  // but the ability to locate an incomplete address can still be useful.\n  int ipv6_terminator = spec[serverinfo.begin] == '[' ? serverinfo.end() : -1;\n  int colon = -1;\n\n  // Find the last right-bracket, and the last colon.\n  for (int i = serverinfo.begin; i < serverinfo.end(); i++) {\n    switch (spec[i]) {\n      case ']':\n        ipv6_terminator = i;\n        break;\n      case ':':\n        colon = i;\n        break;\n    }\n  }\n\n  if (colon > ipv6_terminator) {\n    // Found a port number: <hostname>:<port>\n    *hostname = MakeRange(serverinfo.begin, colon);\n    if (hostname->len == 0) {\n      hostname->reset();\n    }\n    *port_num = MakeRange(colon + 1, serverinfo.end());\n  } else {\n    // No port: <hostname>\n    *hostname = serverinfo;\n    port_num->reset();\n  }\n}\n\n// Given an already-identified auth section, breaks it into its constituent\n// parts. The port number will be parsed and the resulting integer will be\n// filled into the given *port variable, or -1 if there is no port number or it\n// is invalid.\nvoid DoParseAuthority(const char* spec, const Component& auth,\n                      Component* username, Component* password,\n                      Component* hostname, Component* port_num) {\n  assert(auth.is_valid());\n  if (auth.len == 0) {\n    username->reset();\n    password->reset();\n    hostname->reset();\n    port_num->reset();\n    return;\n  }\n\n  // Search backwards for @, which is the separator between the user info and\n  // the server info.\n  int i = auth.begin + auth.len - 1;\n  while (i > auth.begin && spec[i] != '@') {\n    i--;\n  }\n\n  if (spec[i] == '@') {\n    // Found user info: <user-info>@<server-info>\n    ParseUserInfo(spec, Component(auth.begin, i - auth.begin), username,\n                  password);\n    ParseServerInfo(spec, MakeRange(i + 1, auth.begin + auth.len), hostname,\n                    port_num);\n  } else {\n    // No user info, everything is server info.\n    username->reset();\n    password->reset();\n    ParseServerInfo(spec, auth, hostname, port_num);\n  }\n}\n\ninline void FindQueryAndRefParts(const char* spec, const Component& path,\n                                 int* query_separator, int* ref_separator) {\n  int path_end = path.begin + path.len;\n  for (int i = path.begin; i < path_end; i++) {\n    switch (spec[i]) {\n      case '?':\n        // Only match the query string if it precedes the reference fragment\n        // and when we haven't found one already.\n        if (*query_separator < 0) {\n          *query_separator = i;\n        }\n        break;\n      case '#':\n        // Record the first # sign only.\n        if (*ref_separator < 0) {\n          *ref_separator = i;\n          return;\n        }\n        break;\n    }\n  }\n}\n\nvoid ParsePath(const char* spec, const Component& path, Component* filepath,\n               Component* query, Component* ref) {\n  // path = [/]<segment1>/<segment2>/<...>/<segmentN>;<param>?<query>#<ref>\n\n  // Special case when there is no path.\n  if (path.len == -1) {\n    filepath->reset();\n    query->reset();\n    ref->reset();\n    return;\n  }\n  assert(path.len > 0);\n\n  // Search for first occurrence of either ? or #.\n  int query_separator = -1;  // Index of the '?'\n  int ref_separator = -1;    // Index of the '#'\n  FindQueryAndRefParts(spec, path, &query_separator, &ref_separator);\n\n  // Markers pointing to the character after each of these corresponding\n  // components. The code below words from the end back to the beginning,\n  // and will update these indices as it finds components that exist.\n  int file_end, query_end;\n\n  // Ref fragment: from the # to the end of the path.\n  int path_end = path.begin + path.len;\n  if (ref_separator >= 0) {\n    file_end = query_end = ref_separator;\n    *ref = MakeRange(ref_separator + 1, path_end);\n  } else {\n    file_end = query_end = path_end;\n    ref->reset();\n  }\n\n  // Query fragment: everything from the ? to the next boundary (either the end\n  // of the path or the ref fragment).\n  if (query_separator >= 0) {\n    file_end = query_separator;\n    *query = MakeRange(query_separator + 1, query_end);\n  } else {\n    query->reset();\n  }\n\n  // File path: treat an empty file path as no file path.\n  if (file_end != path.begin) {\n    *filepath = MakeRange(path.begin, file_end);\n  } else {\n    filepath->reset();\n  }\n}\n\nbool DoExtractScheme(const char* url, int url_len, Component* scheme) {\n  // Skip leading whitespace and control characters.\n  int begin = 0;\n  while (begin < url_len && ShouldTrimFromURL(url[begin])) {\n    begin++;\n  }\n  if (begin == url_len) {\n    return false;  // Input is empty or all whitespace.\n  }\n\n  // Find the first colon character.\n  for (int i = begin; i < url_len; i++) {\n    if (url[i] == ':') {\n      *scheme = MakeRange(begin, i);\n      return true;\n    }\n  }\n  return false;  // No colon found: no scheme\n}\n\n// Fills in all members of the Parsed structure except for the scheme.\n//\n// |spec| is the full spec being parsed, of length |spec_len|.\n// |after_scheme| is the character immediately following the scheme (after the\n//   colon) where we'll begin parsing.\n//\n// Compatibility data points. I list \"host\", \"path\" extracted:\n// Input                IE6             Firefox                Us\n// -----                --------------  --------------         --------------\n// http://foo.com/      \"foo.com\", \"/\"  \"foo.com\", \"/\"         \"foo.com\", \"/\"\n// http:foo.com/        \"foo.com\", \"/\"  \"foo.com\", \"/\"         \"foo.com\", \"/\"\n// http:/foo.com/       fail(*)         \"foo.com\", \"/\"         \"foo.com\", \"/\"\n// http:\\foo.com/       fail(*)         \"\\foo.com\", \"/\"(fail)  \"foo.com\", \"/\"\n// http:////foo.com/    \"foo.com\", \"/\"  \"foo.com\", \"/\"         \"foo.com\", \"/\"\n//\n// (*) Interestingly, although IE fails to load these URLs, its history\n// canonicalizer handles them, meaning if you've been to the corresponding\n// \"http://foo.com/\" link, it will be colored.\nvoid DoParseAfterScheme(const char* spec, int spec_len, int after_scheme,\n                        Parsed* parsed) {\n  int num_slashes = CountConsecutiveSlashes(spec, after_scheme, spec_len);\n  int after_slashes = after_scheme + num_slashes;\n\n  // First split into two main parts, the authority (username, password, host,\n  // and port) and the full path (path, query, and reference).\n  Component authority;\n  Component full_path;\n\n  // Found \"//<some data>\", looks like an authority section. Treat everything\n  // from there to the next slash (or end of spec) to be the authority. Note\n  // that we ignore the number of slashes and treat it as the authority.\n  int end_auth = FindNextAuthorityTerminator(spec, after_slashes, spec_len);\n  authority = Component(after_slashes, end_auth - after_slashes);\n\n  if (end_auth == spec_len) {  // No beginning of path found.\n    full_path = Component();\n  } else {  // Everything starting from the slash to the end is the path.\n    full_path = Component(end_auth, spec_len - end_auth);\n  }\n\n  // Now parse those two sub-parts.\n  DoParseAuthority(spec, authority, &parsed->username, &parsed->password,\n                   &parsed->host, &parsed->port);\n  ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref);\n}\n\n// The main parsing function for standard URLs. Standard URLs have a scheme,\n// host, path, etc.\nvoid DoParseStandardURL(const char* spec, int spec_len, Parsed* parsed) {\n  assert(spec_len >= 0);\n\n  // Strip leading & trailing spaces and control characters.\n  int begin = 0;\n  TrimURL(spec, &begin, &spec_len);\n\n  int after_scheme;\n  if (DoExtractScheme(spec, spec_len, &parsed->scheme)) {\n    after_scheme = parsed->scheme.end() + 1;  // Skip past the colon.\n  } else {\n    // Say there's no scheme when there is no colon. We could also say that\n    // everything is the scheme. Both would produce an invalid URL, but this way\n    // seems less wrong in more cases.\n    parsed->scheme.reset();\n    after_scheme = begin;\n  }\n  DoParseAfterScheme(spec, spec_len, after_scheme, parsed);\n}\n\nvoid DoParseFileSystemURL(const char* spec, int spec_len, Parsed* parsed) {\n  assert(spec_len >= 0);\n\n  // Get the unused parts of the URL out of the way.\n  parsed->username.reset();\n  parsed->password.reset();\n  parsed->host.reset();\n  parsed->port.reset();\n  parsed->path.reset();          // May use this; reset for convenience.\n  parsed->ref.reset();           // May use this; reset for convenience.\n  parsed->query.reset();         // May use this; reset for convenience.\n  parsed->clear_inner_parsed();  // May use this; reset for convenience.\n\n  // Strip leading & trailing spaces and control characters.\n  int begin = 0;\n  TrimURL(spec, &begin, &spec_len);\n\n  // Handle empty specs or ones that contain only whitespace or control chars.\n  if (begin == spec_len) {\n    parsed->scheme.reset();\n    return;\n  }\n\n  int inner_start = -1;\n\n  // Extract the scheme.  We also handle the case where there is no scheme.\n  if (DoExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) {\n    // Offset the results since we gave ExtractScheme a substring.\n    parsed->scheme.begin += begin;\n\n    if (parsed->scheme.end() == spec_len - 1) {\n      return;\n    }\n\n    inner_start = parsed->scheme.end() + 1;\n  } else {\n    // No scheme found; that's not valid for filesystem URLs.\n    parsed->scheme.reset();\n    return;\n  }\n\n  Component inner_scheme;\n  const char* inner_spec = &spec[inner_start];\n  int inner_spec_len = spec_len - inner_start;\n\n  if (DoExtractScheme(inner_spec, inner_spec_len, &inner_scheme)) {\n    // Offset the results since we gave ExtractScheme a substring.\n    inner_scheme.begin += inner_start;\n\n    if (inner_scheme.end() == spec_len - 1) {\n      return;\n    }\n  } else {\n    // No scheme found; that's not valid for filesystem URLs.\n    // The best we can do is return \"filesystem://\".\n    return;\n  }\n\n  Parsed inner_parsed;\n\n  if (CompareSchemeComponent(spec, inner_scheme, kFileScheme)) {\n    // File URLs are special.\n    // ParseFileURL(inner_spec, inner_spec_len, &inner_parsed);\n    return;\n  } else if (CompareSchemeComponent(spec, inner_scheme, kFileSystemScheme)) {\n    // Filesystem URLs don't nest.\n    return;\n  } else if (IsStandard(spec, inner_scheme)) {\n    // All \"normal\" URLs.\n    DoParseStandardURL(inner_spec, inner_spec_len, &inner_parsed);\n  } else {\n    return;\n  }\n\n  // All members of inner_parsed need to be offset by inner_start.\n  // If we had any scheme that supported nesting more than one level deep,\n  // we'd have to recurse into the inner_parsed's inner_parsed when\n  // adjusting by inner_start.\n  inner_parsed.scheme.begin += inner_start;\n  inner_parsed.username.begin += inner_start;\n  inner_parsed.password.begin += inner_start;\n  inner_parsed.host.begin += inner_start;\n  inner_parsed.port.begin += inner_start;\n  inner_parsed.query.begin += inner_start;\n  inner_parsed.ref.begin += inner_start;\n  inner_parsed.path.begin += inner_start;\n\n  // Query and ref move from inner_parsed to parsed.\n  parsed->query = inner_parsed.query;\n  inner_parsed.query.reset();\n  parsed->ref = inner_parsed.ref;\n  inner_parsed.ref.reset();\n\n  parsed->set_inner_parsed(inner_parsed);\n  if (!inner_parsed.scheme.is_valid() || !inner_parsed.path.is_valid() ||\n      inner_parsed.inner_parsed()) {\n    return;\n  }\n\n  // The path in inner_parsed should start with a slash, then have a filesystem\n  // type followed by a slash.  From the first slash up to but excluding the\n  // second should be what it keeps; the rest goes to parsed.  If the path ends\n  // before the second slash, it's still pretty clear what the user meant, so\n  // we'll let that through.\n  if (!IsURLSlash(spec[inner_parsed.path.begin])) {\n    return;\n  }\n  int inner_path_end = inner_parsed.path.begin + 1;  // skip the leading slash\n  while (inner_path_end < spec_len && !IsURLSlash(spec[inner_path_end])) {\n    ++inner_path_end;\n  }\n  parsed->path.begin = inner_path_end;\n  int new_inner_path_length = inner_path_end - inner_parsed.path.begin;\n  parsed->path.len = inner_parsed.path.len - new_inner_path_length;\n  parsed->inner_parsed()->path.len = new_inner_path_length;\n}\n\n// Initializes a path URL which is merely a scheme followed by a path. Examples\n// include \"about:foo\" and \"javascript:alert('bar');\"\nvoid DoParsePathURL(const char* spec, int spec_len, bool trim_path_end,\n                    Parsed* parsed) {\n  // Get the non-path and non-scheme parts of the URL out of the way, we never\n  // use them.\n  parsed->username.reset();\n  parsed->password.reset();\n  parsed->host.reset();\n  parsed->port.reset();\n  parsed->path.reset();\n  parsed->query.reset();\n  parsed->ref.reset();\n\n  // Strip leading & trailing spaces and control characters.\n  int scheme_begin = 0;\n  TrimURL(spec, &scheme_begin, &spec_len, trim_path_end);\n\n  // Handle empty specs or ones that contain only whitespace or control chars.\n  if (scheme_begin == spec_len) {\n    parsed->scheme.reset();\n    parsed->path.reset();\n    return;\n  }\n\n  int path_begin;\n  // Extract the scheme, with the path being everything following. We also\n  // handle the case where there is no scheme.\n  if (ExtractScheme(&spec[scheme_begin], spec_len - scheme_begin,\n                    &parsed->scheme)) {\n    // Offset the results since we gave ExtractScheme a substring.\n    parsed->scheme.begin += scheme_begin;\n    path_begin = parsed->scheme.end() + 1;\n  } else {\n    // No scheme case.\n    parsed->scheme.reset();\n    path_begin = scheme_begin;\n  }\n\n  if (path_begin == spec_len) {\n    return;\n  }\n  assert(path_begin < spec_len);\n\n  ParsePath(spec, MakeRange(path_begin, spec_len), &parsed->path,\n            &parsed->query, &parsed->ref);\n}\n\nvoid DoParseMailtoURL(const char* spec, int spec_len, Parsed* parsed) {\n  assert(spec_len >= 0);\n\n  // Get the non-path and non-scheme parts of the URL out of the way, we never\n  // use them.\n  parsed->username.reset();\n  parsed->password.reset();\n  parsed->host.reset();\n  parsed->port.reset();\n  parsed->ref.reset();\n  parsed->query.reset();  // May use this; reset for convenience.\n\n  // Strip leading & trailing spaces and control characters.\n  int begin = 0;\n  TrimURL(spec, &begin, &spec_len);\n\n  // Handle empty specs or ones that contain only whitespace or control chars.\n  if (begin == spec_len) {\n    parsed->scheme.reset();\n    parsed->path.reset();\n    return;\n  }\n\n  int path_begin = -1;\n  int path_end = -1;\n\n  // Extract the scheme, with the path being everything following. We also\n  // handle the case where there is no scheme.\n  if (ExtractScheme(&spec[begin], spec_len - begin, &parsed->scheme)) {\n    // Offset the results since we gave ExtractScheme a substring.\n    parsed->scheme.begin += begin;\n\n    if (parsed->scheme.end() != spec_len - 1) {\n      path_begin = parsed->scheme.end() + 1;\n      path_end = spec_len;\n    }\n  } else {\n    // No scheme found, just path.\n    parsed->scheme.reset();\n    path_begin = begin;\n    path_end = spec_len;\n  }\n\n  // Split [path_begin, path_end) into a path + query.\n  for (int i = path_begin; i < path_end; ++i) {\n    if (spec[i] == '?') {\n      parsed->query = MakeRange(i + 1, path_end);\n      path_end = i;\n      break;\n    }\n  }\n\n  // For compatability with the standard URL parser, treat no path as\n  // -1, rather than having a length of 0\n  if (path_begin == path_end) {\n    parsed->path.reset();\n  } else {\n    parsed->path = MakeRange(path_begin, path_end);\n  }\n}\n\n// Converts a port number in a string to an integer. We'd like to just call\n// sscanf but our input is not NULL-terminated, which sscanf requires. Instead,\n// we copy the digits to a small stack buffer (since we know the maximum number\n// of digits in a valid port number) that we can NULL terminate.\nint DoParsePort(const char* spec, const Component& component) {\n  // Easy success case when there is no port.\n  const int kMaxDigits = 5;\n  if (!component.is_nonempty()) {\n    return PORT_UNSPECIFIED;\n  }\n\n  // Skip over any leading 0s.\n  Component digits_comp(component.end(), 0);\n  for (int i = 0; i < component.len; i++) {\n    if (spec[component.begin + i] != '0') {\n      digits_comp = MakeRange(component.begin + i, component.end());\n      break;\n    }\n  }\n  if (digits_comp.len == 0) {\n    return 0;  // All digits were 0.\n  }\n\n  // Verify we don't have too many digits (we'll be copying to our buffer so\n  // we need to double-check).\n  if (digits_comp.len > kMaxDigits) {\n    return PORT_INVALID;\n  }\n\n  // Copy valid digits to the buffer.\n  char digits[kMaxDigits + 1];  // +1 for null terminator\n  for (int i = 0; i < digits_comp.len; i++) {\n    char ch = spec[digits_comp.begin + i];\n    if (!IsPortDigit(ch)) {\n      // Invalid port digit, fail.\n      return PORT_INVALID;\n    }\n    digits[i] = static_cast<char>(ch);\n  }\n\n  // Null-terminate the string and convert to integer. Since we guarantee\n  // only digits, atoi's lack of error handling is OK.\n  digits[digits_comp.len] = 0;\n  int port = atoi(digits);\n  if (port > 65535) {\n    return PORT_INVALID;  // Out of range.\n  }\n  return port;\n}\n\nvoid DoExtractFileName(const char* spec, const Component& path,\n                       Component* file_name) {\n  // Handle empty paths: they have no file names.\n  if (!path.is_nonempty()) {\n    file_name->reset();\n    return;\n  }\n\n  // Extract the filename range from the path which is between\n  // the last slash and the following semicolon.\n  int file_end = path.end();\n  for (int i = path.end() - 1; i >= path.begin; i--) {\n    if (spec[i] == ';') {\n      file_end = i;\n    } else if (IsURLSlash(spec[i])) {\n      // File name is everything following this character to the end\n      *file_name = MakeRange(i + 1, file_end);\n      return;\n    }\n  }\n\n  // No slash found, this means the input was degenerate (generally paths\n  // will start with a slash). Let's call everything the file name.\n  *file_name = MakeRange(path.begin, file_end);\n  return;\n}\n\nbool DoExtractQueryKeyValue(const char* spec, Component* query, Component* key,\n                            Component* value) {\n  if (!query->is_nonempty()) {\n    return false;\n  }\n\n  int start = query->begin;\n  int cur = start;\n  int end = query->end();\n\n  // We assume the beginning of the input is the beginning of the \"key\" and we\n  // skip to the end of it.\n  key->begin = cur;\n  while (cur < end && spec[cur] != '&' && spec[cur] != '=') {\n    cur++;\n  }\n  key->len = cur - key->begin;\n\n  // Skip the separator after the key (if any).\n  if (cur < end && spec[cur] == '=') {\n    cur++;\n  }\n\n  // Find the value part.\n  value->begin = cur;\n  while (cur < end && spec[cur] != '&') {\n    cur++;\n  }\n  value->len = cur - value->begin;\n\n  // Finally skip the next separator if any\n  if (cur < end && spec[cur] == '&') {\n    cur++;\n  }\n\n  // Save the new query\n  *query = MakeRange(cur, end);\n  return true;\n}\n\n}  // namespace\n\nParsed::Parsed() : potentially_dangling_markup(false), inner_parsed_(nullptr) {}\n\nParsed::Parsed(const Parsed& other)\n    : scheme(other.scheme),\n      username(other.username),\n      password(other.password),\n      host(other.host),\n      port(other.port),\n      path(other.path),\n      query(other.query),\n      ref(other.ref),\n      potentially_dangling_markup(other.potentially_dangling_markup),\n      inner_parsed_(nullptr) {\n  if (other.inner_parsed_) {\n    set_inner_parsed(*other.inner_parsed_);\n  }\n}\n\nParsed& Parsed::operator=(const Parsed& other) {\n  if (this != &other) {\n    scheme = other.scheme;\n    username = other.username;\n    password = other.password;\n    host = other.host;\n    port = other.port;\n    path = other.path;\n    query = other.query;\n    ref = other.ref;\n    potentially_dangling_markup = other.potentially_dangling_markup;\n    if (other.inner_parsed_) {\n      set_inner_parsed(*other.inner_parsed_);\n    } else {\n      clear_inner_parsed();\n    }\n  }\n  return *this;\n}\n\nParsed::~Parsed() { delete inner_parsed_; }\n\nint Parsed::Length() const {\n  if (ref.is_valid()) {\n    return ref.end();\n  }\n  return CountCharactersBefore(REF, false);\n}\n\nint Parsed::CountCharactersBefore(ComponentType type,\n                                  bool include_delimiter) const {\n  if (type == SCHEME) {\n    return scheme.begin;\n  }\n\n  // There will be some characters after the scheme like \"://\" and we don't\n  // know how many. Search forwards for the next thing until we find one.\n  int cur = 0;\n  if (scheme.is_valid()) {\n    cur = scheme.end() + 1;  // Advance over the ':' at the end of the scheme.\n  }\n\n  if (username.is_valid()) {\n    if (type <= USERNAME) {\n      return username.begin;\n    }\n    cur = username.end() + 1;  // Advance over the '@' or ':' at the end.\n  }\n\n  if (password.is_valid()) {\n    if (type <= PASSWORD) {\n      return password.begin;\n    }\n    cur = password.end() + 1;  // Advance over the '@' at the end.\n  }\n\n  if (host.is_valid()) {\n    if (type <= HOST) {\n      return host.begin;\n    }\n    cur = host.end();\n  }\n\n  if (port.is_valid()) {\n    if (type < PORT || (type == PORT && include_delimiter)) {\n      return port.begin - 1;  // Back over delimiter.\n    }\n    if (type == PORT) {\n      return port.begin;  // Don't want delimiter counted.\n    }\n    cur = port.end();\n  }\n\n  if (path.is_valid()) {\n    if (type <= PATH) {\n      return path.begin;\n    }\n    cur = path.end();\n  }\n\n  if (query.is_valid()) {\n    if (type < QUERY || (type == QUERY && include_delimiter)) {\n      return query.begin - 1;  // Back over delimiter.\n    }\n    if (type == QUERY) {\n      return query.begin;  // Don't want delimiter counted.\n    }\n    cur = query.end();\n  }\n\n  if (ref.is_valid()) {\n    if (type == REF && !include_delimiter) {\n      return ref.begin;  // Back over delimiter.\n    }\n\n    // When there is a ref and we get here, the component we wanted was before\n    // this and not found, so we always know the beginning of the ref is right.\n    return ref.begin - 1;  // Don't want delimiter counted.\n  }\n\n  return cur;\n}\n\nComponent Parsed::GetContent() const {\n  const int begin = CountCharactersBefore(USERNAME, false);\n  const int len = Length() - begin;\n  // For compatability with the standard URL parser, we treat no content as\n  // -1, rather than having a length of 0 (we normally wouldn't care so\n  // much for these non-standard URLs).\n  return len ? Component(begin, len) : Component();\n}\n\nbool ExtractScheme(const char* url, int url_len, Component* scheme) {\n  return DoExtractScheme(url, url_len, scheme);\n}\n\n// This handles everything that may be an authority terminator, including\n// backslash. For special backslash handling see DoParseAfterScheme.\nbool IsAuthorityTerminator(char ch) {\n  return IsURLSlash(ch) || ch == '?' || ch == '#';\n}\n\nvoid ExtractFileName(const char* url, const Component& path,\n                     Component* file_name) {\n  DoExtractFileName(url, path, file_name);\n}\n\nbool ExtractQueryKeyValue(const char* url, Component* query, Component* key,\n                          Component* value) {\n  return DoExtractQueryKeyValue(url, query, key, value);\n}\n\nvoid ParseAuthority(const char* spec, const Component& auth,\n                    Component* username, Component* password,\n                    Component* hostname, Component* port_num) {\n  DoParseAuthority(spec, auth, username, password, hostname, port_num);\n}\n\nint ParsePort(const char* url, const Component& port) {\n  return DoParsePort(url, port);\n}\n\nvoid ParseStandardURL(const char* url, int url_len, Parsed* parsed) {\n  DoParseStandardURL(url, url_len, parsed);\n}\n\nvoid ParsePathURL(const char* url, int url_len, bool trim_path_end,\n                  Parsed* parsed) {\n  DoParsePathURL(url, url_len, trim_path_end, parsed);\n}\n\nvoid ParseFileSystemURL(const char* url, int url_len, Parsed* parsed) {\n  DoParseFileSystemURL(url, url_len, parsed);\n}\n\nvoid ParseMailtoURL(const char* url, int url_len, Parsed* parsed) {\n  DoParseMailtoURL(url, url_len, parsed);\n}\n\nvoid ParsePathInternal(const char* spec, const Component& path,\n                       Component* filepath, Component* query, Component* ref) {\n  ParsePath(spec, path, filepath, query, ref);\n}\n\nvoid ParseAfterScheme(const char* spec, int spec_len, int after_scheme,\n                      Parsed* parsed) {\n  DoParseAfterScheme(spec, spec_len, after_scheme, parsed);\n}\n\n}  // namespace url\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/url/url_parse.h",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_URL_URL_PARSE_H_\n#define CLAY_NET_URL_URL_PARSE_H_\n\n#include \"clay/net/url/url_parse_internal.h\"\n\nnamespace clay {\nnamespace url {\n// Component ------------------------------------------------------------------\n\n// Represents a substring for URL parsing.\nstruct Component {\n  Component() : begin(0), len(-1) {}\n\n  // Normal constructor: takes an offset and a length.\n  Component(int b, int l) : begin(b), len(l) {}\n\n  int end() const { return begin + len; }\n\n  // Returns true if this component is valid, meaning the length is given. Even\n  // valid components may be empty to record the fact that they exist.\n  bool is_valid() const { return (len != -1); }\n\n  // Returns true if the given component is specified on false, the component\n  // is either empty or invalid.\n  bool is_nonempty() const { return (len > 0); }\n\n  void reset() {\n    begin = 0;\n    len = -1;\n  }\n\n  bool operator==(const Component& other) const {\n    return begin == other.begin && len == other.len;\n  }\n\n  int begin;  // Byte offset in the string of this component.\n  int len;    // Will be -1 if the component is unspecified.\n};\n\n// Helper that returns a component created with the given begin and ending\n// points. The ending point is non-inclusive.\ninline Component MakeRange(int begin, int end) {\n  return Component(begin, end - begin);\n}\n\n// Parsed ---------------------------------------------------------------------\n\n// A structure that holds the identified parts of an input URL. This structure\n// does NOT store the URL itself. The caller will have to store the URL text\n// and its corresponding Parsed structure separately.\n//\n// Typical usage would be:\n//\n//    Parsed parsed;\n//    Component scheme;\n//    if (!ExtractScheme(url, url_len, &scheme))\n//      return I_CAN_NOT_FIND_THE_SCHEME_DUDE;\n//\n//    if (IsStandardScheme(url, scheme))  // Not provided by this component\n//      ParseStandardURL(url, url_len, &parsed);\n//    else if (IsFileURL(url, scheme))    // Not provided by this component\n//      ParseFileURL(url, url_len, &parsed);\n//    else\n//      ParsePathURL(url, url_len, &parsed);\n//\nstruct Parsed {\n  // Identifies different components.\n  enum ComponentType {\n    SCHEME,\n    USERNAME,\n    PASSWORD,\n    HOST,\n    PORT,\n    PATH,\n    QUERY,\n    REF,\n  };\n\n  // The default constructor is sufficient for the components, but inner_parsed_\n  // requires special handling.\n  Parsed();\n  Parsed(const Parsed&);\n  Parsed& operator=(const Parsed&);\n  ~Parsed();\n\n  // Returns the length of the URL (the end of the last component).\n  //\n  // Note that for some invalid, non-canonical URLs, this may not be the length\n  // of the string. For example \"http://\": the parsed structure will only\n  // contain an entry for the four-character scheme, and it doesn't know about\n  // the \"://\". For all other last-components, it will return the real length.\n  int Length() const;\n\n  // Returns the number of characters before the given component if it exists,\n  // or where the component would be if it did exist. This will return the\n  // string length if the component would be appended to the end.\n  //\n  // Note that this can get a little funny for the port, query, and ref\n  // components which have a delimiter that is not counted as part of the\n  // component. The |include_delimiter| flag controls if you want this counted\n  // as part of the component or not when the component exists.\n  //\n  // This example shows the difference between the two flags for two of these\n  // delimited components that is present (the port and query) and one that\n  // isn't (the reference). The components that this flag affects are marked\n  // with a *.\n  //                 0         1         2\n  //                 012345678901234567890\n  // Example input:  http://foo:80/?query\n  //              include_delim=true,  ...=false  (\"<-\" indicates different)\n  //      SCHEME: 0                    0\n  //    USERNAME: 5                    5\n  //    PASSWORD: 5                    5\n  //        HOST: 7                    7\n  //       *PORT: 10                   11 <-\n  //        PATH: 13                   13\n  //      *QUERY: 14                   15 <-\n  //        *REF: 20                   20\n  //\n  int CountCharactersBefore(ComponentType type, bool include_delimiter) const;\n\n  // Scheme without the colon: \"http://foo\"/ would have a scheme of \"http\".\n  // The length will be -1 if no scheme is specified (\"foo.com\"), or 0 if there\n  // is a colon but no scheme (\":foo\"). Note that the scheme is not guaranteed\n  // to start at the beginning of the string if there are preceding whitespace\n  // or control characters.\n  Component scheme;\n\n  // Username. Specified in URLs with an @ sign before the host. See |password|\n  Component username;\n\n  // Password. The length will be -1 if unspecified, 0 if specified but empty.\n  // Not all URLs with a username have a password, as in \"http://me@host/\".\n  // The password is separated form the username with a colon, as in\n  // \"http://me:secret@host/\"\n  Component password;\n\n  // Host name.\n  Component host;\n\n  // Port number.\n  Component port;\n\n  // Path, this is everything following the host name, stopping at the query of\n  // ref delimiter (if any). Length will be -1 if unspecified. This includes\n  // the preceding slash, so the path on http://www.google.com/asdf\" is\n  // \"/asdf\". As a result, it is impossible to have a 0 length path, it will\n  // be -1 in cases like \"http://host?foo\".\n  // Note that we treat backslashes the same as slashes.\n  Component path;\n\n  // Stuff between the ? and the # after the path. This does not include the\n  // preceding ? character. Length will be -1 if unspecified, 0 if there is\n  // a question mark but no query string.\n  Component query;\n\n  // Indicated by a #, this is everything following the hash sign (not\n  // including it). If there are multiple hash signs, we'll use the last one.\n  // Length will be -1 if there is no hash sign, or 0 if there is one but\n  // nothing follows it.\n  Component ref;\n\n  // The URL spec from the character after the scheme: until the end of the\n  // URL, regardless of the scheme. This is mostly useful for 'opaque' non-\n  // hierarchical schemes like data: and javascript: as a convenient way to get\n  // the string with the scheme stripped off.\n  Component GetContent() const;\n\n  // True if the URL's source contained a raw `<` character, and whitespace was\n  // removed from the URL during parsing\n  //\n  // TODO(mkwst): Link this to something in a spec if\n  // https://github.com/whatwg/url/pull/284 lands.\n  bool potentially_dangling_markup;\n\n  // This is used for nested URL types, currently only filesystem.  If you\n  // parse a filesystem URL, the resulting Parsed will have a nested\n  // inner_parsed_ to hold the parsed inner URL's component information.\n  // For all other url types [including the inner URL], it will be NULL.\n  Parsed* inner_parsed() const { return inner_parsed_; }\n\n  void set_inner_parsed(const Parsed& inner_parsed) {\n    if (!inner_parsed_)\n      inner_parsed_ = new Parsed(inner_parsed);\n    else\n      *inner_parsed_ = inner_parsed;\n  }\n\n  void clear_inner_parsed() {\n    if (inner_parsed_) {\n      delete inner_parsed_;\n      inner_parsed_ = nullptr;\n    }\n  }\n\n private:\n  Parsed* inner_parsed_;  // This object is owned and managed by this struct.\n};\n\n// Initialization functions ---------------------------------------------------\n//\n// These functions parse the given URL, filling in all of the structure's\n// components. These functions can not fail, they will always do their best\n// at interpreting the input given.\n//\n// The string length of the URL MUST be specified, we do not check for NULLs\n// at any point in the process, and will actually handle embedded NULLs.\n//\n// IMPORTANT: These functions do NOT hang on to the given pointer or copy it\n// in any way. See the comment above the struct.\n//\n// The 8-bit versions require UTF-8 encoding.\n\n// StandardURL is for when the scheme is known to be one that has an\n// authority (host) like \"http\". This function will not handle weird ones\n// like \"about:\" and \"javascript:\", or do the right thing for \"file:\" URLs.\nvoid ParseStandardURL(const char* url, int url_len, Parsed* parsed);\n\n// PathURL is for when the scheme is known not to have an authority (host)\n// section but that aren't file URLs either. The scheme is parsed, and\n// everything after the scheme is considered as the path. This is used for\n// things like \"about:\" and \"javascript:\"\nvoid ParsePathURL(const char* url, int url_len, bool trim_path_end,\n                  Parsed* parsed);\n\n// FileURL is for file URLs. There are some special rules for interpreting\n// these.\nvoid ParseFileURL(const char* url, int url_len, Parsed* parsed);\n\n// Filesystem URLs are structured differently than other URLs.\nvoid ParseFileSystemURL(const char* url, int url_len, Parsed* parsed);\n\n// MailtoURL is for mailto: urls. They are made up scheme,path,query\nvoid ParseMailtoURL(const char* url, int url_len, Parsed* parsed);\n\n// Helper functions -----------------------------------------------------------\n\n// Locates the scheme according to the URL  parser's rules. This function is\n// designed so the caller can find the scheme and call the correct Init*\n// function according to their known scheme types.\n//\n// It also does not perform any validation on the scheme.\n//\n// This function will return true if the scheme is found and will put the\n// scheme's range into *scheme. False means no scheme could be found. Note\n// that a URL beginning with a colon has a scheme, but it is empty, so this\n// function will return true but *scheme will = (0,0).\n//\n// The scheme is found by skipping spaces and control characters at the\n// beginning, and taking everything from there to the first colon to be the\n// scheme. The character at scheme.end() will be the colon (we may enhance\n// this to handle full width colons or something, so don't count on the\n// actual character value). The character at scheme.end()+1 will be the\n// beginning of the rest of the URL, be it the authority or the path (or the\n// end of the string).\n//\n// The 8-bit version requires UTF-8 encoding.\nbool ExtractScheme(const char* url, int url_len, Component* scheme);\n\n// Returns true if ch is a character that terminates the authority segment\n// of a URL.\nbool IsAuthorityTerminator(char ch);\n\n// Does a best effort parse of input |spec|, in range |auth|. If a particular\n// component is not found, it will be set to invalid.\nvoid ParseAuthority(const char* spec, const Component& auth,\n                    Component* username, Component* password,\n                    Component* hostname, Component* port_num);\n\n// Computes the integer port value from the given port component. The port\n// component should have been identified by one of the init functions on\n// |Parsed| for the given input url.\n//\n// The return value will be a positive integer between 0 and 64K, or one of\n// the two special values below.\nenum SpecialPort { PORT_UNSPECIFIED = -1, PORT_INVALID = -2 };\nint ParsePort(const char* url, const Component& port);\n\n// Extracts the range of the file name in the given url. The path must\n// already have been computed by the parse function, and the matching URL\n// and extracted path are provided to this function. The filename is\n// defined as being everything from the last slash/backslash of the path\n// to the end of the path.\n//\n// The file name will be empty if the path is empty or there is nothing\n// following the last slash.\n//\n// The 8-bit version requires UTF-8 encoding.\nvoid ExtractFileName(const char* url, const Component& path,\n                     Component* file_name);\n\n// Extract the first key/value from the range defined by |*query|. Updates\n// |*query| to start at the end of the extracted key/value pair. This is\n// designed for use in a loop: you can keep calling it with the same query\n// object and it will iterate over all items in the query.\n//\n// Some key/value pairs may have the key, the value, or both be empty (for\n// example, the query string \"?&\"). These will be returned. Note that an empty\n// last parameter \"foo.com?\" or foo.com?a&\" will not be returned, this case\n// is the same as \"done.\"\n//\n// The initial query component should not include the '?' (this is the default\n// for parsed URLs).\n//\n// If no key/value are found |*key| and |*value| will be unchanged and it will\n// return false.\nbool ExtractQueryKeyValue(const char* url, Component* query, Component* key,\n                          Component* value);\n}  // namespace url\n}  // namespace clay\n\n#endif  // CLAY_NET_URL_URL_PARSE_H_\n"
  },
  {
    "path": "clay/net/url/url_parse_internal.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/url/url_parse_internal.h\"\n\n#include <ctype.h>\n\n#include \"clay/net/url/url_parse.h\"\n\nnamespace clay {\nnamespace url {\n\nnamespace {\n\nstatic const char* kStandardSchemes[] = {\n    kHttpsScheme, kHttpScheme, kFileScheme, kFtpScheme, kWssScheme, kWsScheme,\n};\n\n}  // namespace\n\nbool IsURLSlash(char ch) { return ch == '/' || ch == '\\\\'; }\n\nbool ShouldTrimFromURL(char ch) { return ch <= ' '; }\n\nvoid TrimURL(const char* spec, int* begin, int* len, bool trim_path_end) {\n  // Strip leading whitespace and control characters.\n  while (*begin < *len && ShouldTrimFromURL(spec[*begin])) {\n    (*begin)++;\n  }\n\n  if (trim_path_end) {\n    // Strip trailing whitespace and control characters. We need the >i test\n    // for when the input string is all blanks; we don't want to back past the\n    // input.\n    while (*len > *begin && ShouldTrimFromURL(spec[*len - 1])) {\n      (*len)--;\n    }\n  }\n}\n\nint CountConsecutiveSlashes(const char* str, int begin_offset, int str_len) {\n  int count = 0;\n  while ((begin_offset + count) < str_len &&\n         IsURLSlash(str[begin_offset + count])) {\n    ++count;\n  }\n  return count;\n}\n\nbool CompareSchemeComponent(const char* spec, const Component& component,\n                            const char* compare_to) {\n  if (!component.is_nonempty()) {\n    return compare_to[0] == 0;  // When component is empty, match empty scheme.\n  }\n  for (int i = 0; i < component.len; ++i) {\n    if (tolower(spec[i]) != compare_to[i]) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool IsStandard(const char* spec, const Component& component) {\n  if (!component.is_nonempty()) {\n    return false;\n  }\n\n  for (auto& kStandardScheme : kStandardSchemes) {\n    if (CompareSchemeComponent(spec, component, kStandardScheme)) {\n      return true;\n    }\n  }\n  return false;\n}\n\n// NOTE: Not implemented because file URLs are currently unsupported.\n// void ParseFileURL(const char* url, int url_len, Parsed* parsed) {}\n}  // namespace url\n}  // namespace clay\n"
  },
  {
    "path": "clay/net/url/url_parse_internal.h",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_NET_URL_URL_PARSE_INTERNAL_H_\n#define CLAY_NET_URL_URL_PARSE_INTERNAL_H_\n\nnamespace clay {\nnamespace url {\nstruct Component;\n\nstatic constexpr char kHttpsScheme[] = \"https\";\nstatic constexpr char kHttpScheme[] = \"http\";\nstatic constexpr char kFileScheme[] = \"file\";\nstatic constexpr char kFtpScheme[] = \"ftp\";\nstatic constexpr char kWssScheme[] = \"wss\";\nstatic constexpr char kWsScheme[] = \"ws\";\nstatic constexpr char kFileSystemScheme[] = \"filesystem\";\nstatic constexpr char kMailtoScheme[] = \"mailto\";\n\n// Returns whether the character |ch| should be treated as a slash.\nbool IsURLSlash(char ch);\n\n// Returns whether the character |ch| can be safely removed for the URL.\nbool ShouldTrimFromURL(char ch);\n\n// Given an already-initialized begin index and length, this shrinks the range\n// to eliminate \"should-be-trimmed\" characters. Note that the length does *not*\n// indicate the length of untrimmed data from |*begin|, but rather the position\n// in the input string (so the string starts at character |*begin| in the spec,\n// and goes until |*len|).\nvoid TrimURL(const char* spec, int* begin, int* len, bool trim_path_end = true);\n\n// Returns the number of consecutive slashes in |str| starting from offset\n// |begin_offset|.\nint CountConsecutiveSlashes(const char* str, int begin_offset, int str_len);\n\n// Given a string and a range inside the string, compares it to the given\n// lower-case |compare_to| buffer.\nbool CompareSchemeComponent(const char* spec, const Component& component,\n                            const char* compare_to);\n\n// Returns whether the scheme given by (spec, component) is a standard scheme\n// (i.e. https://url.spec.whatwg.org/#special-scheme).\nbool IsStandard(const char* spec, const Component& component);\n}  // namespace url\n}  // namespace clay\n#endif  // CLAY_NET_URL_URL_PARSE_INTERNAL_H_\n"
  },
  {
    "path": "clay/public/clay.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_PUBLIC_CLAY_H_\n#define CLAY_PUBLIC_CLAY_H_\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef CLAY_LIBRARY_IMPLEMENTATION\n\n// Add visibility/export annotations when building the library.\n#ifdef _WIN32\n#define CLAY_EXPORT __declspec(dllexport)\n#else\n#define CLAY_EXPORT __attribute__((visibility(\"default\")))\n#endif\n\n#else  // CLAY_LIBRARY_IMPLEMENTATION\n\n#ifdef _WIN32\n#define CLAY_EXPORT __declspec(dllimport)\n#else\n#define CLAY_EXPORT\n#endif\n\n#endif  // CLAY_LIBRARY_IMPLEMENTATION\n\n#if defined(__cplusplus)\n#define CLAY_EXTERN_C extern \"C\"\n#define CLAY_EXTERN_C_BEGIN CLAY_EXTERN_C {\n#define CLAY_EXTERN_C_END }\n#else  // defined(__cplusplus)\n#define CLAY_EXTERN_C\n#define CLAY_EXTERN_C_BEGIN\n#define CLAY_EXTERN_C_END\n#endif  // defined(__cplusplus)\n\nCLAY_EXTERN_C_BEGIN\n\n//------------------------------------------------------------------------------\n// Handles\n//------------------------------------------------------------------------------\n\n// IOSurfaceRef on Darwin\n// D3DSharedHandle on Win\ntypedef void* ClaySharedImageNativeHandle;\n\n/// id<MTLDevice> in Metal\ntypedef void* ClayMetalDeviceHandle;\n\n/// id<MTLCommandQueue> in Metal\ntypedef void* ClayMetalCommandQueueHandle;\n\n/// ID3D11Device* in D3D\ntypedef void* ClayD3DDeviceHandle;\n\n/// id<MTLTexture> in Metal\ntypedef void* ClayMetalTextureHandle;\n\n/// ID3D11Texture2D* in D3D\ntypedef void* ClayD3DTextureHandle;\n\n//------------------------------------------------------------------------------\n// Callbacks\n//------------------------------------------------------------------------------\ntypedef void (*ClayVoidCallback)(void* /* user data */);\ntypedef bool (*ClayBoolCallback)(void* /* user data */);\ntypedef void (*ClayDestructionCallback)(const void* /* ptr */,\n                                        void* /* user_data */);\ntypedef void (*ClayKeyEventCallback)(bool /* handled */, void* /* user_data */);\ntypedef void (*ClayNetLoadResultCallback)(size_t request_seq, bool success,\n                                          const uint8_t* data, size_t length);\n\n//------------------------------------------------------------------------------\n// Enumerations\n//------------------------------------------------------------------------------\ntypedef enum {\n  kClaySharedImageBackingPixelFormatNative8888,  // BGRA8888\n  kClaySharedImageBackingPixelFormatRGBA8,\n} ClaySharedImageBackingPixelFormat;\n\ntypedef enum {\n  kClaySharedImageBackingTypeIOSurface,      /// Darwin\n  kClaySharedImageBackingTypeCVPixelBuffer,  /// Darwin\n  kClaySharedImageBackingTypeD3DTexture,     /// Windows\n  kClaySharedImageBackingTypeNativeImage,    /// Harmony\n  kClaySharedImageBackingTypeShmImage,       /// Linux\n} ClaySharedImageBackingType;\n\ntypedef enum {\n  /// CGL on OSX\n  /// ANGLE on Windows, DO NOT use unless you are using the same ANGLE library\n  /// EGL on Android\n  kClaySharedImageRepresentationTypeGL,\n  kClaySharedImageRepresentationTypeMetal,  /// OSX and iOS\n  kClaySharedImageRepresentationTypeD3D,    /// Windows\n  kClaySharedImageRepresentationTypeVulkan,\n  kClaySharedImageRepresentationTypeShm,\n} ClaySharedImageRepresentationType;\n\ntypedef enum {\n  // In single buffer mode\n  // the user must call ClaySharedImageSinkReleaseFront after read\n  kClaySharedImageSinkBufferModeSingleBuffer,\n  kClaySharedImageSinkBufferModeDoubleBuffer,\n  kClaySharedImageSinkBufferModeTripleBuffer,\n} ClaySharedImageSinkBufferMode;\n\n/// The format of image ecodeing.\ntypedef enum { kClayImageFormatPNG, kClayImageFormatJPEG } ClayImageFormat;\n\n//------------------------------------------------------------------------------\n// The headless of embebbder.\ntypedef enum {\n  kClayPointerPhaseCancel,\n  /// The pointer, which must have been down (see kDown), is now up.\n  ///\n  /// For touch, this means that the pointer is no longer in contact with the\n  /// screen. For a mouse, it means the last button was released. Note that if\n  /// any other buttons are still pressed when one button is released, that\n  /// should be sent as a kMove rather than a kUp.\n  kClayPointerPhaseUp,\n  /// The pointer, which must have been been up, is now down.\n  ///\n  /// For touch, this means that the pointer has come into contact with the\n  /// screen. For a mouse, it means a button is now pressed. Note that if any\n  /// other buttons are already pressed when a new button is pressed, that\n  /// should be sent as a kMove rather than a kDown.\n  kClayPointerPhaseDown,\n  /// The pointer moved while down.\n  ///\n  /// This is also used for changes in button state that don't cause a kDown or\n  /// kUp, such as releasing one of two pressed buttons.\n  kClayPointerPhaseMove,\n  /// The pointer is now sending input to Clay. For instance, a mouse has\n  /// entered the area where the Clay content is displayed.\n  ///\n  /// A pointer should always be added before sending any other events.\n  kClayPointerPhaseAdd,\n  /// The pointer is no longer sending input to Clay. For instance, a mouse\n  /// has left the area where the Clay content is displayed.\n  ///\n  /// A removed pointer should no longer send events until sending a new kAdd.\n  kClayPointerPhaseRemove,\n  /// The pointer moved while up.\n  kClayPointerPhaseHover,\n  /// A pan/zoom started on this pointer.\n  kClayPointerPhasePanZoomStart,\n  /// The pan/zoom updated.\n  kClayPointerPhasePanZoomUpdate,\n  /// The pan/zoom ended.\n  kClayPointerPhasePanZoomEnd,\n} ClayPointerPhase;\n\ntypedef enum {\n  kClayPointerSignalKindNone,\n  kClayPointerSignalKindScroll,\n  kClayPointerSignalKindScrollInertiaCancel,\n  kClayPointerSignalKindScale,\n} ClayPointerSignalKind;\n\ntypedef enum {\n  kClayPointerDeviceKindMouse = 1,\n  kClayPointerDeviceKindTouch,\n  kClayPointerDeviceKindStylus,\n  kClayPointerDeviceKindTrackpad,\n} ClayPointerDeviceKind;\n\n/// Flags for the `buttons` field of `ClayPointerEvent` when `device_kind`\n/// is `kClayPointerDeviceKindMouse`.\ntypedef enum {\n  kClayPointerMouseButtonsMousePrimary = 1 << 0,\n  kClayPointerMouseButtonsMouseSecondary = 1 << 1,\n  kClayPointerMouseButtonsMouseMiddle = 1 << 2,\n  kClayPointerMouseButtonsMouseBack = 1 << 3,\n  kClayPointerMouseButtonsMouseForward = 1 << 4,\n  /// If a mouse has more than five buttons, send higher bit shifted values\n  /// corresponding to the button number: 1 << 5 for the 6th, etc.\n} ClayPointerMouseButtons;\n\ntypedef enum {\n  kClayKeyEventTypeUp = 1,\n  kClayKeyEventTypeDown,\n  kClayKeyEventTypeRepeat,\n} ClayKeyEventType;\n\ntypedef enum {\n  kClayEventTypeUnknown = 0,\n\n  // touch events\n  kClayEventTypeTouchStart,\n  kClayEventTypeTouchMove,\n  kClayEventTypeTouchCancel,\n  kClayEventTypeTouchEnd,\n  kClayEventTypeTap,\n  kClayEventTypeLongPress,\n\n  // mouse events\n  kClayEventTypeMouseDown,\n  kClayEventTypeMouseUp,\n  kClayEventTypeMouseMove,\n  kClayEventTypeMouseClick,\n  kClayEventTypeMouseDoubleClick,\n  kClayEventTypeMouseLongPress,\n  kClayEventTypeMouseEnter,\n  kClayEventTypeMouseOver,\n  kClayEventTypeMouseLeave,\n  // mouse drag and drop events\n  kClayEventTypeDragEnter,\n  kClayEventTypeDragOver,\n  kClayEventTypeDragLeave,\n  kClayEventTypeDrop,\n\n  // wheel event\n  kClayEventTypeWheel,\n\n  // trackpad event\n  kClayEventTypePanZoom,\n\n  // key events\n  kClayEventTypeKeyDown,\n  kClayEventTypeKeyUp,\n  // animation events\n  kClayEventTypeAnimationStart,\n  kClayEventTypeAnimationRepeat,\n  kClayEventTypeAnimationEnd,\n  kClayEventTypeAnimationCancel,\n  kClayEventTypeTransitionStart,\n  kClayEventTypeTransitionEnd,\n} ClayEventType;\n\n//------------------------------------------------------------------------------\n// structs\n//------------------------------------------------------------------------------\ntypedef struct ClaySize {\n  uint32_t width;\n  uint32_t height;\n} ClaySize;\n\ntypedef struct ClayDataHolder {\n  size_t size;\n  const void* ptr;\n  void* user_data;\n  ClayDestructionCallback destruction_callback;\n} ClayDataHolder;\n\ntypedef struct ClayPointer {\n  typedef enum {\n    kClayPointerTypeVoidPtr = 0,  // Internal usage. Invalid pointer\n    kClayPointerTypeTransform,\n    kClayPointerTypeExternal,\n    kClayPointerTypeFilter,\n    kClayPointerTypeBoxShadow,\n  } ClayPointerType;\n\n  // Type of the pointer.\n  ClayPointerType type;\n  void* pointer;\n} ClayPointer;\n\ntypedef struct ClayTransformation {\n  /// horizontal scale factor\n  float scaleX;\n  /// horizontal skew factor\n  float skewX;\n  /// horizontal translation\n  float transX;\n  /// vertical skew factor\n  float skewY;\n  /// vertical scale factor\n  float scaleY;\n  /// vertical translation\n  float transY;\n  /// input x-axis perspective factor\n  float pers0;\n  /// input y-axis perspective factor\n  float pers1;\n  /// perspective scale factor\n  float pers2;\n} ClayTransformation;\n\n/// The container of native handle\ntypedef struct ClayCanvasView* ClayCanvasViewRef;\n\ntypedef struct ClaySharedImage* ClaySharedImageRef;\n\ntypedef struct ClayFenceSync* ClayFenceSyncRef;\n\ntypedef struct ClaySharedImageSink* ClaySharedImageSinkRef;\n\ntypedef struct ClaySharedImageSinkAccessor* ClaySharedImageSinkAccessorRef;\n\ntypedef struct ClaySharedImageGLRepresentationConfig {\n  /// The size of this struct. Must be\n  /// sizeof(ClaySharedImageGLRepresentationConfig).\n  size_t struct_size;\n  /// An optional parameter used on Windows ANGLE to support multiple ANGLE\n  /// binaries, must be `eglGetProcAddress` proc address\n  void* get_proc_address;\n} ClaySharedImageGLRepresentationConfig;\n\ntypedef struct ClaySharedImageVKRepresentationConfig {\n  /// The size of this struct. Must be\n  /// sizeof(ClaySharedImageVKRepresentationConfig).\n  size_t struct_size;\n  void* physical_device;\n  void* device;\n  void* queue;\n} ClaySharedImageVKRepresentationConfig;\n\ntypedef struct ClaySharedImageMetalRepresentationConfig {\n  /// The size of this struct. Must be\n  /// sizeof(ClaySharedImageMetalRepresentationConfig).\n  size_t struct_size;\n  ClayMetalDeviceHandle device;\n  ClayMetalCommandQueueHandle command_queue;\n} ClaySharedImageMetalRepresentationConfig;\n\ntypedef struct ClaySharedImageD3DRepresentationConfig {\n  /// The size of this struct. Must be\n  /// sizeof(ClaySharedImageD3DRepresentationConfig).\n  size_t struct_size;\n  ClayD3DDeviceHandle device;\n} ClaySharedImageD3DRepresentationConfig;\n\ntypedef struct ClaySharedImageShmRepresentationConfig {\n  /// The size of this struct. Must be\n  /// sizeof(ClaySharedImageShmRepresentationConfig).\n  size_t struct_size;\n} ClaySharedImageShmRepresentationConfig;\n\ntypedef struct ClaySharedImageRepresentationConfig {\n  /// The size of this struct. Must be\n  /// sizeof(ClaySharedImageRepresentationConfig).\n  size_t struct_size;\n  ClaySharedImageRepresentationType type;\n  union {\n    ClaySharedImageGLRepresentationConfig gl_config;\n    ClaySharedImageMetalRepresentationConfig metal_config;\n    ClaySharedImageD3DRepresentationConfig d3d_config;\n    ClaySharedImageVKRepresentationConfig vk_config;\n    ClaySharedImageShmRepresentationConfig shm_config;\n  };\n} ClaySharedImageRepresentationConfig;\n\ntypedef struct ClayMetalTexture {\n  /// The size of this struct. Must be sizeof(ClayMetalTexture).\n  size_t struct_size;\n  /// id<MTLTexture> in Metal\n  /// The value is owned by engine, user must retain to persist it.\n  ClayMetalTextureHandle texture;\n  /// User data to be returned on the invocation of the destruction callback.\n  void* user_data;\n  /// The callback invoked by the engine when it no longer needs this backing\n  /// store.\n  ClayVoidCallback destruction_callback;\n} ClayMetalTexture;\n\ntypedef struct ClayD3DTexture {\n  /// The size of this struct. Must be sizeof(ClayD3DTexture).\n  size_t struct_size;\n  /// ID3D11Texture2D* in D3D\n  /// The value is owned by engine, user must retain to persist it.\n  ClayD3DTextureHandle texture;\n  /// User data to be returned on the invocation of the destruction callback.\n  void* user_data;\n  /// The callback invoked by the engine when it no longer needs this backing\n  /// store.\n  ClayVoidCallback destruction_callback;\n} ClayD3DTexture;\n\ntypedef struct ClayOpenGLTexture {\n  /// The size of this struct. Must be sizeof(ClayOpenGLTexture).\n  size_t struct_size;\n  /// Target texture of the active texture unit (example GL_TEXTURE_2D or\n  /// GL_TEXTURE_RECTANGLE).\n  uint32_t target;\n  /// The name of the texture.\n  uint32_t name;\n  /// The texture format (example GL_RGBA8).\n  uint32_t format;\n  /// User data to be returned on the invocation of the destruction callback.\n  void* user_data;\n  /// The callback invoked by the engine when it no longer needs this backing\n  /// store.\n  ClayVoidCallback destruction_callback;\n  /// Optional parameters for texture height/width, default is 0, non-zero means\n  /// the texture has the specified width/height. Usually, when the texture type\n  /// is GL_TEXTURE_RECTANGLE, we need to specify the texture width/height to\n  /// tell the embedder to scale when rendering.\n  ClaySize size;\n} ClayOpenGLTexture;\n\ntypedef struct ClayVulkanImage {\n  /// The size of this struct. Must be sizeof(ClayVulkanImage).\n  size_t struct_size;\n  void* vulkan_image;\n  /// User data to be returned on the invocation of the destruction callback.\n  void* user_data;\n  /// The callback invoked by the engine when it no longer needs this backing\n  /// store.\n  ClayVoidCallback destruction_callback;\n} ClayVulkanImage;\n\ntypedef struct ClayOpenGLFramebuffer {\n  /// The size of this struct. Must be sizeof(ClayOpenGLFramebuffer).\n  size_t struct_size;\n\n  /// The target of the color attachment of the frame-buffer. For example,\n  /// GL_TEXTURE_2D or GL_RENDERBUFFER.\n  uint32_t target;\n\n  /// The name of the framebuffer.\n  uint32_t name;\n\n  /// User data to be returned on the invocation of the destruction callback.\n  void* user_data;\n\n  /// The callback invoked by the engine when it no longer needs this backing\n  /// store.\n  ClayVoidCallback destruction_callback;\n} ClayOpenGLFramebuffer;\n\ntypedef struct ClaySharedMemoryImage {\n  /// The size of this struct. Must be sizeof(ClaySharedMemoryImage).\n  size_t struct_size;\n\n  /// The fd of the shared memory\n  int shm_fd;\n\n  /// The size of the shared image.\n  int32_t width;\n  int32_t height;\n\n  /// User data to be returned on the invocation of the destruction callback.\n  void* user_data;\n\n  /// The callback invoked by the engine when it no longer needs this backing\n  /// store.\n  ClayVoidCallback destruction_callback;\n} ClaySharedMemoryImage;\n\ntypedef struct ClaySharedImageReadResult {\n  /// The size of this struct. Must be sizeof(ClaySharedImageReadResult).\n  size_t struct_size;\n  ClaySharedImageRepresentationType type;\n  union {\n    ClayOpenGLTexture opengl_texture;\n    ClayMetalTexture metal_texture;\n    ClayD3DTexture d3d_texture;\n    ClayVulkanImage vulkan_image;\n    ClaySharedMemoryImage shm_image;\n  };\n} ClaySharedImageReadResult;\n\ntypedef struct ClaySharedImageWriteResult {\n  /// The size of this struct. Must be sizeof(ClaySharedImageWriteResult).\n  size_t struct_size;\n  ClaySharedImageRepresentationType type;\n  union {\n    ClayOpenGLFramebuffer opengl_framebuffer;\n    ClayMetalTexture metal_texture;\n    ClayD3DTexture d3d_texture;\n    ClaySharedMemoryImage shm_image;\n  };\n} ClaySharedImageWriteResult;\n\n/// Currently only support RGBA8 bitmap with premultiplied alpha\ntypedef struct ClayBitmap {\n  size_t struct_size;\n  uint32_t width;\n  uint32_t height;\n  ClayDataHolder pixels;\n} ClayBitmap;\n\ntypedef struct ClayPointerEvent {\n  /// The size of this struct. Must be sizeof(ClayPointerEvent).\n  size_t struct_size;\n  ClayPointerPhase phase;\n  /// The timestamp at which the pointer event was generated.\n  size_t timestamp;\n  /// The x coordinate of the pointer event in physical pixels.\n  double x;\n  /// The y coordinate of the pointer event in physical pixels.\n  double y;\n  /// An optional device identifier. If this is not specified, it is assumed\n  /// that the embedder has no multi-touch capability.\n  int32_t device;\n  ClayPointerSignalKind signal_kind;\n  /// The x offset of the scroll in physical pixels.\n  double scroll_delta_x;\n  /// The y offset of the scroll in physical pixels.\n  double scroll_delta_y;\n  /// The type of the device generating this event.\n  /// Backwards compatibility note: If this is not set, the device will be\n  /// treated as a mouse, with the primary button set for `kDown` and `kMove`.\n  /// If set explicitly to `ClayPointerDeviceKind::kMouse`, you must set the\n  /// correct buttons.\n  ClayPointerDeviceKind device_kind;\n  /// The buttons currently pressed, if any.\n  int64_t buttons;\n  /// The x offset of the pan/zoom in physical pixels.\n  double pan_x;\n  /// The y offset of the pan/zoom in physical pixels.\n  double pan_y;\n  /// The scale of the pan/zoom, where 1.0 is the initial scale.\n  double scale;\n  /// The rotation of the pan/zoom in radians, where 0.0 is the initial angle.\n  double rotation;\n  /// Whether the event is triggered by the touchpad\n  size_t is_precise_scroll;\n} ClayPointerEvent;\n\n/// A structure to represent a key event.\ntypedef struct ClayKeyEvent {\n  /// The size of this struct. Must be sizeof(ClayKeyEvent).\n  size_t struct_size;\n  /// The timestamp at which the key event was generated.\n  double timestamp;\n  /// The event kind.\n  ClayKeyEventType type;\n  /// The USB HID code for the physical key of the event.\n  ///\n  /// For the full definition and list of pre-defined physical keys, see\n  /// `PhysicalKeyboardKey` from the framework.\n  ///\n  /// The only case that `physical` might be 0 is when this is an empty event.\n  uint64_t physical;\n  /// The key ID for the logical key of this event.\n  ///\n  /// For the full definition and a list of pre-defined logical keys, see\n  /// `LogicalKeyboardKey` from `clay/ui/event/keyboard_key.h`.\n  ///\n  /// The only case that `logical` might be 0 is when this is an empty event.\n  uint64_t logical;\n  /// Null-terminated character input from the event. Can be null. Ignored for\n  /// up events.\n  const char* character;\n  /// True if this event does not correspond to a native event.\n  ///\n  /// The embedder is likely to skip events and/or construct new events that do\n  /// not correspond to any native events in order to conform the regularity\n  /// of events (as documented in `ClayKeyEvent`). An example is when a key\n  /// up is missed due to loss of window focus, on a platform that provides\n  /// query to key pressing status, the embedder might realize that the key has\n  /// been released at the next key event, and should construct a synthesized up\n  /// event immediately before the actual event.\n  ///\n  /// An event being synthesized means that the `timestamp` might greatly\n  /// deviate from the actual time when the event occurs physically.\n  bool synthesized;\n} ClayKeyEvent;\n\n/// This information is passed to the headless when requesting a frame buffer\n/// object.\ntypedef struct ClayFrameInfo {\n  /// The size of this struct. Must be sizeof(ClayFrameInfo).\n  size_t struct_size;\n  /// The size of the surface that will be backed by the fbo.\n  uint32_t width;\n  uint32_t height;\n} ClayFrameInfo;\n\n/// Callback for when a frame buffer object is requested.\ntypedef uint32_t (*ClayFrameInfoCallback)(\n    void* /* user data */, const ClayFrameInfo* /* frame info */);\ntypedef void* (*ClayProcResolver)(void* /* user data */,\n                                  const char* /* name */);\n\ntypedef bool (*ClaySoftwarePresentCallback)(void* /* user data */,\n                                            const void* /* allocation */,\n                                            size_t /* row bytes */,\n                                            size_t /* height */);\n\ntypedef struct ClayOpenGLRendererConfig {\n  /// The size of this struct. Must be sizeof(ClayOpenGLRendererConfig).\n  size_t struct_size;\n  ClayBoolCallback make_current;\n  ClayBoolCallback clear_current;\n  /// Specifying `present` is required. The return value indicates success of\n  /// the present call.\n  ClayBoolCallback present;\n  /// Specifying the `fbo_callback` or is required. The return value indicates\n  /// the id of the frame buffer object that will obtain the gl\n  /// surface from.\n  ClayFrameInfoCallback fbo_callback;\n  ClayProcResolver gl_proc_resolver;\n\n  /// ATTENTION\n  /// Host GL mode is very limited. In most cases, you should use\n  /// ClayHardwareRendererConfig instead of host gl mode.\n  ///\n  /// If enable_shared_image_sink is true, host GL will be running as a bitmap\n  /// uploader, underneath will render view using a SharedImageSink\n  /// and readback GPU memory to a CPU bitmap. It may be SLOW for large views,\n  /// but all components will work well.\n  ///\n  /// Disable shared image sink will disable the CPU bitmap readback, it can\n  /// speed normal UI rendering, but components using external textures like\n  /// video or canvas can NOT work.\n  bool enable_shared_image_sink;\n  ClaySharedImageSinkBufferMode\n      shared_image_sink_buffer_mode;  // double buffer by default\n} ClayOpenGLRendererConfig;\n\ntypedef struct ClayHardwareRendererConfig {\n  /// The size of this struct. Must be sizeof(ClayHardwareRendererConfig).\n  size_t struct_size;\n  ClaySharedImageSinkRef sink_ref;\n  bool disable_partial_repaint;\n} ClayHardwareRendererConfig;\n\ntypedef struct ClaySoftwareRendererConfig {\n  /// The size of this struct. Must be sizeof(ClaySoftwareRendererConfig).\n  size_t struct_size;\n  /// The callback presented to the embedder to present a fully populated buffer\n  /// to the user. The pixel format of the buffer is the native 32-bit RGBA\n  /// format. The buffer is owned by the engine and must be copied in\n  /// this callback if needed.\n  ClaySoftwarePresentCallback present_callback;\n} ClaySoftwareRendererConfig;\n\ntypedef enum {\n  /// ClayOpenGLRendererConfig\n  /// Use Host envrionment passed OpenGL callbacks\n  kClayRendererTypeHostGL,\n\n  /// ClayHardwareRendererConfig\n  kClayRendererTypeOpenGL,  /// native GL\n  /// Metal is only supported on Darwin platforms (macOS / iOS).\n  /// iOS version >= 10.0 (device), 13.0 (simulator)\n  /// macOS version >= 10.14\n  kClayRendererTypeMetal,\n  kClayRendererTypeVulkan,\n\n  /// ClaySoftwareRendererConfig\n  kClayRendererTypeSoftware,\n} ClayRendererType;\n\ntypedef struct ClayHeadlessRendererConfig {\n  ClayRendererType type;\n  union {\n    ClayOpenGLRendererConfig opengl;\n    ClayHardwareRendererConfig hardware;\n    ClaySoftwareRendererConfig software;\n  };\n} ClayHeadlessRendererConfig;\n\ntypedef struct ClayTaskRunner_* ClayTaskRunner;\n\ntypedef struct ClayTask {\n  ClayTaskRunner runner;\n  uint64_t task;\n} ClayTask;\n\ntypedef void (*ClayTaskRunnerPostTaskCallback)(ClayTask /* task */,\n                                               uint64_t /* target time nanos */,\n                                               void* /* user data */);\n\n/// An interface used by the Headless engine to execute tasks at the target time\n/// on a specified thread. There should be a 1-1 relationship between a thread\n/// and a task runner. It is undefined behavior to run a task on a thread that\n/// is not associated with its task runner.\ntypedef struct ClayTaskRunnerDescription {\n  /// The size of this struct. Must be sizeof(ClayTaskRunnerDescription).\n  size_t struct_size;\n  void* user_data;\n  /// May be called from any thread. Should return true if tasks posted on the\n  /// calling thread will be run on that same thread.\n  ClayBoolCallback runs_task_on_current_thread_callback;\n  /// May be called from any thread.\n  ClayTaskRunnerPostTaskCallback post_task_callback;\n  /// A unique identifier for the task runner. If multiple task runners service\n  /// tasks on the same thread, their identifiers must match.\n  size_t identifier;\n} ClayTaskRunnerDescription;\n\n//------------------------------------------------------------------------------\n// SharedImage\n//------------------------------------------------------------------------------\nCLAY_EXPORT ClaySharedImageRef ClayCreateSharedImage(\n    ClaySharedImageBackingType type,\n    ClaySharedImageBackingPixelFormat pixel_format, const ClaySize* size);\nCLAY_EXPORT ClaySharedImageRef ClayCreateSharedImageFromHandle(\n    ClaySharedImageBackingType type,\n    ClaySharedImageBackingPixelFormat pixel_format, const ClaySize* size,\n    ClaySharedImageNativeHandle handle);\nCLAY_EXPORT ClaySharedImageRef\nClayRetainSharedImage(ClaySharedImageRef shared_image_ref);\nCLAY_EXPORT void ClayReleaseSharedImage(ClaySharedImageRef shared_image_ref);\n/// Get backing type, format and the native handle from ClaySharedImage\n/// The returned handle is not owned. Users must retain the value to persist it.\n/// But it's safe to use the handle whenever the shared_image_ref is valid.\nCLAY_EXPORT void ClaySharedImageGetBacking(\n    ClaySharedImageRef shared_image_ref, ClaySharedImageBackingType* out_type,\n    ClaySharedImageBackingPixelFormat* out_format,\n    ClaySharedImageNativeHandle* out_handle);\n\nCLAY_EXPORT void ClaySharedImageGetSize(ClaySharedImageRef shared_image_ref,\n                                        ClaySize* out);\n\n/// The transformation matrix maps (u, v, 1) to the final tex coord\n/// We use OpenGL texture coordinates(Bottom-Left Origin, +Y up) as the\n/// standard coordinate system.\n///\n/// By default, the transformation matrix is the identity matrix.\n///\n/// For example, to use the graphics buffer as an Skia Image\n/// ```\n/// auto image = SkImages::BorrowTextureFrom(\n///   context, tex,\n///   GrSurfaceOrigin::kBottomLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType,\n///   kPremul_SkAlphaType, nullptr)\n/// if (!transform.isIdentity()) {\n///   sk_sp<SkShader> shader = image->makeShader(SkTileMode::kRepeat,\n///     SkTileMode::kRepeat,\n///     sampling,\n///     transform);\n///\n///   SkPaint paintWithShader = paint;\n///   paintWithShader.setShader(shader);\n///   context.canvas->drawRect(SkRect::MakeWH(1, 1), paintWithShader);\n/// } else {\n///   context.canvas->drawImage(image, 0, 0, sampling, paint);\n/// }\n/// ```\nCLAY_EXPORT void ClaySharedImageGetTransformation(\n    ClaySharedImageRef shared_image_ref, ClayTransformation* out);\n/// Write the transformation to the graphics buffer\n/// usually used after a cpu or gpu write\nCLAY_EXPORT void ClaySharedImageSetTransformation(\n    ClaySharedImageRef shared_image_ref,\n    const ClayTransformation* transformation);\n\nCLAY_EXPORT ClayFenceSyncRef\nClayCreateExternalFenceSync(ClayVoidCallback wait_callback,\n                            ClayVoidCallback delete_callback, void* user_data);\n\nCLAY_EXPORT bool ClayFenceSyncClientWait(ClayFenceSyncRef fence_sync);\n\nCLAY_EXPORT void ClayDestroyFenceSync(ClayFenceSyncRef);\n\nCLAY_EXPORT void ClaySharedImageSetFenceSync(\n    ClaySharedImageRef shared_image_ref, ClayFenceSyncRef fence_sync_ref);\n\nCLAY_EXPORT ClayFenceSyncRef\nClaySharedImageGetFenceSync(ClaySharedImageRef shared_image_ref);\n\nCLAY_EXPORT ClaySharedImageSinkRef ClayCreateSharedImageSink(\n    ClaySharedImageSinkBufferMode buffer_mode, ClaySharedImageBackingType type,\n    ClaySharedImageBackingPixelFormat pixel_format);\n\nCLAY_EXPORT ClaySharedImageSinkRef\nClayRetainSharedImageSink(ClaySharedImageSinkRef sink_ref);\n\nCLAY_EXPORT void ClayReleaseSharedImageSink(ClaySharedImageSinkRef sink_ref);\n\nCLAY_EXPORT ClaySharedImageSinkBufferMode\nClaySharedImageSinkGetBufferMode(ClaySharedImageSinkRef sink_ref);\n\nCLAY_EXPORT void ClaySharedImageSinkSetFrameAvailableCallback(\n    ClaySharedImageSinkRef sink_ref, ClayVoidCallback callback,\n    void* user_data);\n\n/// It's better to use ClaySharedImageSinkAccessor instead of the raw interfaces\n/// below\n\n/// The out ClaySharedImageRef is owned by sink\n/// User must call `ClayRetainSharedImage` to retain it\nCLAY_EXPORT bool ClaySharedImageSinkUpdateFront(\n    ClaySharedImageSinkRef sink_ref, ClayFenceSyncRef produced_fence_sync,\n    ClaySharedImageRef* out);\n\n/// Explicitly release the front buffer\n/// This function is only required in single buffer mode\nCLAY_EXPORT void ClaySharedImageSinkReleaseFront(\n    ClaySharedImageRef sink_ref, ClayFenceSyncRef produced_fence_sync);\n\n/// The out ClaySharedImageRef is owned by sink\n/// User must call `ClayRetainSharedImage` to retain it\nCLAY_EXPORT bool ClaySharedImageSinkAcquireBack(ClaySharedImageSinkRef sink_ref,\n                                                const ClaySize* size,\n                                                ClaySharedImageRef* out,\n                                                uint32_t* out_buffer_age);\nCLAY_EXPORT bool ClaySharedImageSinkTryAcquireBack(\n    ClaySharedImageSinkRef sink_ref, const ClaySize* size,\n    ClaySharedImageRef* out, uint32_t* out_buffer_age,\n    ClaySharedImageNativeHandle handle = nullptr);\n\nCLAY_EXPORT bool ClaySharedImageSinkSwapBack(ClaySharedImageSinkRef sink_ref,\n                                             ClayFenceSyncRef fence_sync);\n\nCLAY_EXPORT ClaySharedImageSinkAccessorRef ClayCreateSharedImageSinkAccessor(\n    ClaySharedImageSinkRef sink_ref,\n    const ClaySharedImageRepresentationConfig* config);\n\nCLAY_EXPORT void ClayDestroySharedImageSinkAccessor(\n    ClaySharedImageSinkAccessorRef sink_ref);\n\n/// The out ClaySharedImageRef is owned by sink\n/// User must call `ClayRetainSharedImage` to retain it\nCLAY_EXPORT bool ClaySharedImageSinkRead(\n    ClaySharedImageSinkAccessorRef sink_accessor_ref,\n    ClaySharedImageRef* out_image, ClaySharedImageReadResult* out);\n\n/// Explicitly notify the read is done\n/// This function is only required in single buffer mode\nCLAY_EXPORT void ClaySharedImageSinkEndRead(\n    ClaySharedImageSinkAccessorRef sink_accessor_ref);\n\n/// The out ClaySharedImageRef is owned by sink\n/// User must call `ClayRetainSharedImage` to retain it\nCLAY_EXPORT bool ClaySharedImageSinkBeginWrite(\n    ClaySharedImageSinkAccessorRef sink_accessor_ref, const ClaySize* size,\n    ClaySharedImageRef* out_image, ClaySharedImageWriteResult* out,\n    uint32_t* out_buffer_age);\n\nCLAY_EXPORT bool ClaySharedImageSinkEndWrite(\n    ClaySharedImageSinkAccessorRef sink_accessor_ref);\n\n//------------------------------------------------------------------------------\n// Bitmap\n//------------------------------------------------------------------------------\ntypedef void (*ClayBitmapDecodeCallback)(const char* /* error message */,\n                                         const ClayBitmap* /* bitmap */,\n                                         void* /* user_data */);\nCLAY_EXPORT void ClayDecodeImage(const ClayDataHolder* data_holder,\n                                 ClayBitmapDecodeCallback decode_callback,\n                                 void* user_data);\nCLAY_EXPORT bool ClayDecodeDataUrlImage(const char* data_url, size_t length,\n                                        ClayBitmap* out);\nCLAY_EXPORT bool ClayEncodeBitmap(const ClayBitmap* bitmap,\n                                  ClayImageFormat encoding,\n                                  float compress_ratio, ClayDataHolder* out);\n\nCLAY_EXTERN_C_END\n\n#endif  // CLAY_PUBLIC_CLAY_H_\n"
  },
  {
    "path": "clay/public/event_delegate.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_PUBLIC_EVENT_DELEGATE_H_\n#define CLAY_PUBLIC_EVENT_DELEGATE_H_\n\n#include <map>\n#include <memory>\n#include <string>\n\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/public/value.h\"\n\nnamespace clay {\n\nclass EventDelegate {\n public:\n  virtual ~EventDelegate() {}\n\n  virtual void OnTouchEvent(const std::string& event_name, int tag, float x,\n                            float y, float page_x, float page_y) = 0;\n  virtual void OnMouseEvent(const std::string& event_name, int view_id,\n                            int button, int buttons, float scale, float x,\n                            float y, float page_x, float page_y) = 0;\n  virtual void OnWheelEvent(const std::string& event_name, int view_id, float x,\n                            float y, float page_x, float page_y, float delta_x,\n                            float delta_y) = 0;\n  virtual void OnKeyEvent(const std::string& event_name, int view_id,\n                          const char* key, bool repeat) = 0;\n  virtual void OnAnimationEvent(const std::string& event_name,\n                                const char* animation_name, int view_id) = 0;\n  virtual void OnTransitionEvent(const std::string& event_name,\n                                 const char* animation_name, int view_id,\n                                 ClayAnimationPropertyType type) = 0;\n  virtual void OnFocusChanged(int view_id, bool focus) = 0;\n  virtual void OnHoverChanged(int view_id, bool hover) = 0;\n  virtual void OnDragDropEvent(const std::string& event_name, int view_id,\n                               clay::Value::Map map) = 0;\n  virtual void OnViewportMetricsChanged(\n      double device_pixel_ratio, double device_density_dpi,\n      double logical_width, double logical_height, double physical_screen_width,\n      double physical_screen_height, double font_scale, bool night_mode) = 0;\n  virtual void OnDrawEndEvent() = 0;\n  virtual void OnSendCustomEvent(int view_id, const std::string& event_name,\n                                 clay::Value::Map args) = 0;\n  virtual void OnSendGlobalEvent(const std::string& event_name,\n                                 clay::Value args) = 0;\n  virtual void OnFirstMeaningfulPaint() = 0;\n  virtual void OnOverlayEvent(int view_id, const char* overlay_id,\n                              int overlay_count, const char** overlay_ids,\n                              const char* event_name) = 0;\n  virtual void OnLayoutChanged(int view_id, clay::Value::Map map) = 0;\n  virtual void OnIntersectionEvent(int view_id, clay::Value::Map map) = 0;\n  virtual void OnCallJSApiCallback(int callback_id, clay::Value value) = 0;\n  virtual void CallJSIntersectionObserver(int observer_id, int callback_id,\n                                          clay::Value params) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_PUBLIC_EVENT_DELEGATE_H_\n"
  },
  {
    "path": "clay/public/layout_delegate.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_PUBLIC_LAYOUT_DELEGATE_H_\n#define CLAY_PUBLIC_LAYOUT_DELEGATE_H_\n\n#include <cstddef>\n#include <cstdint>\n\n#include \"clay/public/style_types.h\"\n\nnamespace clay {\nclass LayoutDelegate {\n public:\n  virtual void OnTriggerLayout() = 0;\n  virtual void OnMarkDirty(int32_t id) = 0;\n  virtual void OnAlignNativeNode(int32_t id, float, float) = 0;\n  virtual ClayMeasureOutput OnMeasureNativeNode(int32_t, float, int, float,\n                                                int) = 0;\n  virtual ClayLayoutStyles OnGetLayoutStyles(int32_t id) = 0;\n};\n}  // namespace clay\n\n#endif  // CLAY_PUBLIC_LAYOUT_DELEGATE_H_\n"
  },
  {
    "path": "clay/public/style_types.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_PUBLIC_STYLE_TYPES_H_\n#define CLAY_PUBLIC_STYLE_TYPES_H_\n\n#include <limits.h>\n\n//------------------------------------------------------------------------------\n// Enumerations\n//------------------------------------------------------------------------------\nenum class ClayOverflowType {\n  kVisible = 0,\n  kHidden,\n  kScroll,\n};\n\nenum class ClayTimingFunctionType {\n  kLinear = 0,\n  kEaseIn,\n  kEaseOut,\n  kEaseInEaseOut,\n  kSquareBezier,\n  kCubicBezier,\n  kSteps,\n};\n\nenum class ClayStepsType {\n  kInvalid = 0,\n  kStart,\n  kEnd,\n  kJumpBoth,\n  kJumpNone,\n};\n\nenum class ClayAnimationPropertyType {\n  kNone = 0,\n  kOpacity = 1 << 0,\n  kScaleX = 1 << 1,\n  kScaleY = 1 << 2,\n  kScaleXY = 1 << 3,\n  kWidth = 1 << 4,\n  kHeight = 1 << 5,\n  kBackgroundColor = 1 << 6,\n  kVisibility = 1 << 7,\n  kLeft = 1 << 8,\n  kTop = 1 << 9,\n  kRight = 1 << 10,\n  kBottom = 1 << 11,\n  kTransform = 1 << 12,\n  kColor = 1 << 13,\n  kFilter = 1 << 14,\n  kBoxShadow = 1 << 15,\n  kAll = kOpacity | kWidth | kHeight | kBackgroundColor | kVisibility | kLeft |\n         kTop | kRight | kBottom | kTransform | kColor | kFilter | kBoxShadow\n};\n\nenum class ClayTextDecorationType {\n  kNone = 0,\n  kUnderLine = 1 << 0,\n  kLineThrough = 1 << 1,\n};\n\nenum class ClayShadowOption {\n  kNone = 0,\n  kInset = 1,\n  kInitial = 2,\n  kInherit = 3,\n};\n\nenum class ClayBackgroundImageType {\n  kNone,\n  kUrl,\n  kLinearGradient,\n  kRadialGradient,\n  kConicGradient,\n};\n\nenum class ClayBackgroundOriginType {\n  kPaddingBox = 0,\n  kBorderBox = 1,\n  kContentBox = 2,\n};\n\nenum class ClayBackgroundRepeatType {\n  kRepeat = 0,\n  kNoRepeat = 1,\n  kRepeatX = 2,\n  kRepeatY = 3,\n  kRound = 4,\n  kSpace = 5,\n};\n\nenum class ClayBackgroundSizeType {\n  kAuto = (1 << 5),\n  kCover,\n  kContain,\n};\n\nenum class ClayBackgroundClipType {\n  kPaddingBox = 0,\n  kBorderBox = 1,\n  kContentBox = 2,\n  kText = 3,\n  kBorderArea = 4,\n};\n\nenum class ClayMaskClipType {\n  kPaddingBox = 0,\n  kBorderBox = 1,\n  kContentBox = 2,\n};\n\nusing ClayMaskImageType = ClayBackgroundImageType;\nusing ClayMaskOriginType = ClayBackgroundOriginType;\nusing ClayMaskRepeatType = ClayBackgroundRepeatType;\nusing ClayMaskSizeType = ClayBackgroundSizeType;\n\nenum class ClayPlatformLengthUnit {\n  kNumber = 0,\n  kPercentage,\n};\n\nenum class ClayGradientPositionType {\n  kPercent,\n  kNumber,\n};\n\nenum class ClayFilterType {\n  kNone = 0,\n  kGrayScale = 1,\n  kBlur = 2,\n};\n\nenum class ClayImageRendering {\n  kAuto,\n  kCrispEdges,\n  kPixelated,\n};\n\nenum class ClayRadialGradientShapeType {\n  kEllipse = 0,\n  kCircle,\n};\n\nenum class ClayRadialGradientSizeType {\n  kFarthestCorner = 0,\n  kFarthestSide,\n  kClosestCorner,\n  kClosestSide,\n  kLength,\n};\n\nenum class ClayRadialGradientCenterType {\n  BACKGROUND_POSITION_TOP = -(1 << 5),\n  BACKGROUND_POSITION_RIGHT = -(1 << 5) - 1,\n  BACKGROUND_POSITION_BOTTOM = -(1 << 5) - 2,\n  BACKGROUND_POSITION_LEFT = -(1 << 5) - 3,\n  BACKGROUND_POSITION_CENTER = -(1 << 5) - 4,\n  RADIAL_CENTER_TYPE_PERCENTAGE = -11,  // according to lynx\n};\n\nenum class ClayBasicShapeType {\n  kUnknown = 0,\n  kCircle,\n  kEllipse,\n  kPath,\n  kSuperEllipse,\n  kInset,\n};\n\nenum class ClayAnimationDirectionType {\n  kNormal = 0,\n  kReverse,\n  kAlternate,\n  kAlternateReverse,\n};\n\nenum class ClayAnimationFillModeType {\n  kNone = 0,\n  kForwards,\n  kBackwards,\n  kBoth,\n};\n\nenum class ClayAnimationPlayStateType {\n  kPaused = 0,\n  kRunning,\n};\n\nenum class ClayVisibilityType {\n  kHidden = 0,\n  kVisible,\n  kNone,\n  kCollapse,\n};\n\nenum class ClayCursorType {\n  kUrl = 0,\n  kKeyword,\n};\n\nenum class ClayTransformType {\n  kNone = 0,\n  kTranslate = 1,\n  kTranslateX = 1 << 1,\n  kTranslateY = 1 << 2,\n  kTranslateZ = 1 << 3,\n  kTranslate3d = 1 << 4,\n  kRotate = 1 << 5,\n  kRotateX = 1 << 6,\n  kRotateY = 1 << 7,\n  kRotateZ = 1 << 8,\n  kScale = 1 << 9,\n  kScaleX = 1 << 10,\n  kScaleY = 1 << 11,\n  kSkew = 1 << 12,\n  kSkewX = 1 << 13,\n  kSkewY = 1 << 14,\n  kMatrix = 1 << 15,\n  kMatrix3d = 1 << 16,\n};\n\n//------------------------------------------------------------------------------\n// structs\n//------------------------------------------------------------------------------\ntypedef struct ClayTransformOP {\n  ClayTransformType type;\n  float value[3];\n  ClayPlatformLengthUnit unit[3];\n  double matrix[16];\n} ClayTransformOP;\n\ntypedef struct ClayTransform {\n  ClayTransformOP* op;\n  int size;\n  float transform_origin[2];\n} ClayTransform;\n\ntypedef struct ClayLayoutStyles {\n  int padding_left;\n  int padding_top;\n  int padding_right;\n  int padding_bottom;\n  int margin_left;\n  int margin_top;\n  int margin_right;\n  int margin_bottom;\n  float width;\n  float height;\n} ClayLayoutStyles;\n\ntypedef struct ClayMeasureOutput {\n  float width;\n  float height;\n  float baseline;\n} ClayMeasureOutput;\n\ntypedef struct ClayPlatformLength {\n  double value = 0.0;\n  ClayPlatformLengthUnit unit;\n} ClayPlatformLength;\n\ntypedef struct ClayLinearGradient {\n  double degree = 0.0;\n  // Owned by embedder.\n  unsigned int* colors = nullptr;\n  int colors_length = 0;\n  // Owned by embedder.\n  float* positions = nullptr;\n  // Owned by embedder.\n  ClayGradientPositionType* position_types = nullptr;\n  int positions_length = 0;\n} ClayLinearGradient;\n\ntypedef struct ClayRadialGradient {\n  ClayRadialGradientShapeType shape_type;\n  ClayRadialGradientSizeType shape_size;  // according to RadialGradientExtent\n  unsigned int* colors = nullptr;\n  int colors_length = 0;\n  float* positions = nullptr;\n  ClayGradientPositionType* position_types = nullptr;\n  int positions_length = 0;\n  ClayRadialGradientCenterType center_x;\n  float center_x_value;\n  ClayRadialGradientCenterType center_y;\n  float center_y_value;\n  ClayPlatformLengthUnit length_x_unit;\n  float length_x;\n  ClayPlatformLengthUnit length_y_unit;\n  float length_y;\n} ClayRadialGradient;\n\ntypedef struct ClayConicGradient {\n  unsigned int* colors = nullptr;\n  int colors_length = 0;\n  float* positions = nullptr;\n  ClayGradientPositionType* position_types = nullptr;\n  int positions_length = 0;\n  bool x_is_percent;\n  float center_x;\n  bool y_is_percent;\n  float center_y;\n  double start_angle = 0;\n  double end_angle = 360.0;\n} ClayConicGradient;\n\n#endif  // CLAY_PUBLIC_STYLE_TYPES_H_\n"
  },
  {
    "path": "clay/public/ui_component_delegate.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_PUBLIC_UI_COMPONENT_DELEGATE_H_\n#define CLAY_PUBLIC_UI_COMPONENT_DELEGATE_H_\n\n#include <cstddef>\n#include <cstdint>\n#include <list>\n#include <memory>\n\n#include \"core/public/list_engine_proxy.h\"\n\nnamespace clay {\nclass LynxListData;\nclass UIComponentDelegate {\n public:\n  virtual int OnObtainChild(int view_id, int index, int operation_id) = 0;\n  virtual void OnRecycleChild(int view_id, int child_id) = 0;\n  virtual void OnCreateAddChild(int view_id, int index, int operation_id) = 0;\n  virtual void OnUpdateChild(int parent_id, int to_update_id, int target_index,\n                             int64_t operation_id) = 0;\n  virtual LynxListData* OnListGetData(int view_id) = 0;\n  virtual void OnScrollByListContainer(int view_id, float offset_x,\n                                       float offset_y, float original_x,\n                                       float original_y) = 0;\n  virtual void OnScrollToPosition(int view_id, int position, float offset,\n                                  int align, bool smooth) = 0;\n  virtual void OnScrollStopped(int view_id) = 0;\n  virtual bool OnEnableRasterAnimation() = 0;\n  virtual std::list<int32_t> GetAncestorElements(int32_t tag) = 0;\n\n  void SetListEngineProxy(\n      const std::shared_ptr<lynx::shell::ListEngineProxy>& list_engine_proxy) {\n    list_engine_proxy_ = list_engine_proxy;\n  }\n\n  std::shared_ptr<lynx::shell::ListEngineProxy> GetListEngineProxy() {\n    return list_engine_proxy_;\n  }\n\n private:\n  std::shared_ptr<lynx::shell::ListEngineProxy> list_engine_proxy_ = nullptr;\n};\n}  // namespace clay\n\n#endif  // CLAY_PUBLIC_UI_COMPONENT_DELEGATE_H_\n"
  },
  {
    "path": "clay/public/value.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_PUBLIC_VALUE_H_\n#define CLAY_PUBLIC_VALUE_H_\n\n#include <cstddef>\n#include <cstdint>\n#include <functional>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <variant>\n#include <vector>\n\n#include \"clay/public/clay.h\"\n\nnamespace lynx {\nclass ClayValueWrapper;\n}\n\nnamespace clay {\nclass Value {\n public:\n  enum Type {\n    kNone = 0,  // Internal usage. Means invalid.\n    kBool,\n    kInt,\n    kUInt,\n    kLong,\n    kFloat,\n    kDouble,\n    kString,\n    kPointer,\n    kArray,\n    kArrayBuffer,\n    kMap,\n  };\n\n  using Array = std::vector<Value>;\n  using ArrayBuffer = std::vector<uint8_t>;\n  using Map = std::unordered_map<std::string, Value>;\n\n  explicit Value(bool v) : value_(v) {}\n  explicit Value(int32_t v) : value_(v) {}\n  explicit Value(uint32_t v) : value_(v) {}\n  explicit Value(int64_t v) : value_(v) {}\n  explicit Value(float v) : value_(v) {}\n  explicit Value(double v) : value_(v) {}\n  explicit Value(const char* v) : value_(std::string(v)) {}\n  explicit Value(const std::string_view& v)\n      : value_(std::string(v.data(), v.size())) {}\n  explicit Value(const std::string& v) : value_(v) {}\n  explicit Value(std::string&& v) : value_(std::move(v)) {}\n  explicit Value(const ClayPointer& v) : value_(v) {}\n  explicit Value(Array&& v) : value_(std::make_shared<Array>(std::move(v))) {}\n  explicit Value(ArrayBuffer&& v)\n      : value_(std::make_shared<ArrayBuffer>(std::move(v))) {}\n  explicit Value(Map&& v) : value_(std::make_shared<Map>(std::move(v))) {}\n  explicit Value(std::shared_ptr<Array> v) : value_(v) {}\n  explicit Value(std::shared_ptr<ArrayBuffer> v) : value_(v) {}\n  explicit Value(std::shared_ptr<Map> v) : value_(v) {}\n  explicit Value(Type type) = delete;\n  explicit Value(void* ptr, std::function<void(void*)> deleter)\n      : value_(ClayPointer{ClayPointer::kClayPointerTypeExternal, nullptr}),\n        wrapper_(ptr, std::move(deleter)) {}\n  Value(std::initializer_list<Value>&& v);\n  Value(std::initializer_list<std::pair<std::string, Value>>&& v);\n  Value() = default;\n  Value(Value&& that);\n  Value& operator=(Value&& that);\n  ~Value();\n\n  // Prevent accidental copying\n  Value& operator=(const Value&) = delete;\n#ifndef CLAY_UNIT_TESTS\n  Value(const Value&) = delete;\n#else\n  // only gmock used\n  Value(const Value& that) : value_(that.value_) {}\n#endif\n\n  static Value Null() {\n    return Value{ClayPointer{ClayPointer::kClayPointerTypeVoidPtr, nullptr}};\n  }\n\n  Type type() const { return static_cast<Type>(value_.index()); }\n  bool IsNone() const { return type() == kNone; }\n  bool IsNull() const { return type() == kPointer && GetPointer() == nullptr; }\n  bool IsBool() const { return type() == kBool; }\n  bool IsInt() const { return type() == kInt; }\n  bool IsUint() const { return type() == kUInt; }\n  bool IsLong() const { return type() == kLong; }\n  bool IsFloat() const { return type() == kFloat; }\n  bool IsDouble() const { return type() == kDouble; }\n  bool IsNumber() const {\n    return IsDouble() || IsFloat() || IsInt() || IsUint() || IsLong();\n  }\n  bool IsString() const { return type() == kString; }\n  bool IsPointer() const { return type() == kPointer; }\n  bool IsArray() const { return type() == kArray; }\n  bool IsArrayBuffer() const { return type() == kArrayBuffer; }\n  bool IsMap() const { return type() == kMap; }\n\n  bool GetBool() const { return value<bool>(); }\n  int32_t GetInt() const { return value<int32_t>(); }\n  uint32_t GetUint() const { return value<uint32_t>(); }\n  int64_t GetLong() const { return value<int64_t>(); }\n  float GetFloat() const { return value<float>(); }\n  double GetDouble() const { return value<double>(); }\n  const std::string& GetString() const { return value<std::string>(); }\n  std::string& GetString() { return value<std::string>(); }\n  const ArrayBuffer& GetArrayBuffer() const {\n    return *value<std::shared_ptr<ArrayBuffer>>();\n  }\n  ArrayBuffer& GetArrayBuffer() {\n    return *value<std::shared_ptr<ArrayBuffer>>();\n  }\n  const Array& GetArray() const { return *value<std::shared_ptr<Array>>(); }\n  Array& GetArray() { return *value<std::shared_ptr<Array>>(); }\n  const Map& GetMap() const { return *value<std::shared_ptr<Map>>(); }\n  Map& GetMap() { return *value<std::shared_ptr<Map>>(); }\n  ClayPointer::ClayPointerType GetPointerType() const {\n    return value<ClayPointer>().type;\n  }\n  void* GetPointer() const { return value<ClayPointer>().pointer; }\n\n  // Note: Changing from `std::get` to `std::get_if` to avoid compiler errors on\n  // iOS 10.\n  template <typename T>\n  const T& value() const {\n    auto* ptr = std::get_if<T>(&value_);\n    return *ptr;\n  }\n  template <typename T>\n  T& value() {\n    auto* ptr = std::get_if<T>(&value_);\n    return *ptr;\n  }\n\n  bool IsExternal() const {\n    return type() == clay::Value::kPointer &&\n           GetPointerType() == ClayPointer::kClayPointerTypeExternal;\n  }\n  std::unique_ptr<void, std::function<void(void*)>>& External() {\n    return wrapper_;\n  }\n\n  const std::unique_ptr<void, std::function<void(void*)>>& GetWrapper() const {\n    return wrapper_;\n  }\n\n  bool IsWrapperValid() const { return wrapper_ != nullptr; }\n\n  void SetWrapper(std::unique_ptr<void, std::function<void(void*)>>&& wrapper) {\n    wrapper_ = std::move(wrapper);\n  }\n\n private:\n  friend lynx::ClayValueWrapper;\n\n  std::variant<std::monostate,                // kNone\n               bool,                          // kBool\n               int32_t,                       // kInt\n               uint32_t,                      // kUInt\n               int64_t,                       // kLong\n               float,                         // kFloat\n               double,                        // kDouble\n               std::string,                   // kString\n               ClayPointer,                   // kPointer\n               std::shared_ptr<Array>,        // kArray\n               std::shared_ptr<ArrayBuffer>,  // kArrayBuffer\n               std::shared_ptr<Map>>          // kMap\n      value_;\n\n  std::unique_ptr<void, std::function<void(void*)>> wrapper_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_PUBLIC_VALUE_H_\n"
  },
  {
    "path": "clay/shell/common/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//third_party/icu/config.gni\")\nimport(\"../../common/config.gni\")\nimport(\"../../testing/testing.gni\")\n\nsource_set(\"common\") {\n  sources = [\n    \"context_options.cc\",\n    \"context_options.h\",\n    \"devtools_instrumentation.cc\",\n    \"devtools_instrumentation.h\",\n    \"display.cc\",\n    \"display.h\",\n    \"display_manager.cc\",\n    \"display_manager.h\",\n    \"engine.cc\",\n    \"engine.h\",\n    \"frame_timing_listener.h\",\n    \"one_shot_callback.h\",\n    \"output_surface.cc\",\n    \"output_surface.h\",\n    \"pipeline_timing_delegate.h\",\n    \"platform_view.cc\",\n    \"platform_view.h\",\n    \"pointer_data_dispatcher.cc\",\n    \"pointer_data_dispatcher.h\",\n    \"pointer_data_to_event.cc\",\n    \"pointer_data_to_event.h\",\n    \"rasterizer.cc\",\n    \"rasterizer.h\",\n    \"resource_cache_limit_calculator.cc\",\n    \"resource_cache_limit_calculator.h\",\n    \"scheduler/scheduler.cc\",\n    \"scheduler/scheduler.h\",\n    \"scheduler/scheduler_client.h\",\n    \"scheduler/scheduler_state_machine.cc\",\n    \"scheduler/scheduler_state_machine.h\",\n    \"screenshot_utils.cc\",\n    \"screenshot_utils.h\",\n    \"scroll_fluency_monitor_delegate.h\",\n    \"serialization_callbacks.cc\",\n    \"serialization_callbacks.h\",\n    \"services/animation_event_service_impl.cc\",\n    \"services/animation_event_service_impl.h\",\n    \"services/animator_info_service.cc\",\n    \"services/animator_info_service.h\",\n    \"services/compositor/compositor_service.cc\",\n    \"services/compositor/compositor_service.h\",\n    \"services/compositor/platform_overlay_service.h\",\n    \"services/compositor/presenter_service.cc\",\n    \"services/compositor/presenter_service.h\",\n    \"services/gesture_mediate_service.h\",\n    \"services/initialize_service.h\",\n    \"services/instrumentation_service.cc\",\n    \"services/instrumentation_service.h\",\n    \"services/platform_const_service.cc\",\n    \"services/platform_const_service.h\",\n    \"services/raster_frame_service.cc\",\n    \"services/raster_frame_service.h\",\n    \"services/rasterizer_service.cc\",\n    \"services/rasterizer_service.h\",\n    \"services/sync_compositor_service.cc\",\n    \"services/sync_compositor_service.h\",\n    \"services/ui_frame_service.cc\",\n    \"services/ui_frame_service.h\",\n    \"services/vsync_waiter_service.h\",\n    \"shell.cc\",\n    \"shell.h\",\n    \"shell_common_rendering_backend.h\",\n    \"switches.cc\",\n    \"switches.h\",\n    \"variable_refresh_rate_display.cc\",\n    \"variable_refresh_rate_display.h\",\n    \"variable_refresh_rate_reporter.h\",\n    \"vsync_waiter.cc\",\n    \"vsync_waiter.h\",\n  ]\n\n  if (enable_clay_standalone) {\n    sources += [ \"services/initialize_service.cc\" ]\n  }\n\n  if (is_win || is_mac || is_headless) {\n    sources += [\n      \"services/drag_drop_service.cc\",\n      \"services/drag_drop_service.h\",\n    ]\n  }\n\n  # Use real vsync waiter on mobile platforms, use timer-based fallback on other platforms\n  if (!(is_android || is_harmony || is_ios || is_tvos)) {\n    sources += [\n      \"vsync_waiter_fallback.cc\",\n      \"vsync_waiter_fallback.h\",\n    ]\n  }\n\n  # Note that: macOS 14.0- needs fallback\n  if (is_mac) {\n    sources += [\n      \"vsync_waiter_macos.h\",\n      \"vsync_waiter_macos.mm\",\n    ]\n    frameworks = [ \"QuartzCore.framework\" ]\n  }\n\n  if (!(is_android || is_ios)) {\n    sources += [\n      \"services/compositor/platform_overlay_service_fallback.cc\",\n      \"services/compositor/presenter_service_fallback.cc\",\n    ]\n  }\n\n  if (enable_screenshot_service) {\n    sources += [\n      \"services/screenshot_encoder.cc\",\n      \"services/screenshot_encoder.h\",\n      \"services/screenshot_service.cc\",\n      \"services/screenshot_service.h\",\n    ]\n  }\n\n  defines = []\n\n  public_configs = [ \"../../:config\" ]\n\n  public_deps = [\n    \"../../../third_party/rapidjson\",\n    \"../../third_party/txt\",\n    \"../../version\",\n  ]\n\n  deps = [\n    \"../../common\",\n    \"../../common/graphics\",\n    \"../../flow\",\n    \"../../fml:fml\",\n    \"../../gfx\",\n    \"../../gfx/shared_image\",\n    \"../../memory:memory\",\n    \"../../shell/profiling\",\n    \"../../ui\",\n  ]\n\n  if (enable_skity) {\n    deps += [ \"../../gfx/skity\" ]\n  } else {\n    if (!is_android || use_system_icu) {\n      deps += [ \"../../fml:icu_util\" ]\n    }\n\n    sources += [\n      \"skia_event_tracer_impl.cc\",\n      \"skia_event_tracer_impl.h\",\n    ]\n    public_deps += [ \"//third_party/skia\" ]\n  }\n}\n"
  },
  {
    "path": "clay/shell/common/context_options.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/context_options.h\"\n\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/common/settings.h\"\n\nnamespace clay {\n\n#ifndef ENABLE_SKITY\nGrContextOptions MakeDefaultContextOptions(ContextType type,\n                                           std::optional<GrBackendApi> api) {\n  GrContextOptions options;\n\n  if (PersistentCache::cache_sksl()) {\n    options.fShaderCacheStrategy = GrContextOptions::ShaderCacheStrategy::kSkSL;\n  }\n  PersistentCache::MarkStrategySet();\n  options.fPersistentCache =\n      PersistentCache::GetCacheForProcess()->GetResourceCache();\n\n  if (api.has_value() && api.value() == GrBackendApi::kOpenGL) {\n    options.fAvoidStencilBuffers = !Settings::ShouldEnableStencilBuffer();\n\n    // To get video playback on the widest range of devices, we limit Skia to\n    // ES2 shading language when the ES3 external image extension is missing.\n    options.fPreferExternalImagesOverES3 = true;\n  }\n\n  // TODO(goderbauer): remove option when skbug.com/7523 is fixed.\n  options.fDisableGpuYUVConversion = true;\n\n  options.fReduceOpsTaskSplitting = GrContextOptions::Enable::kNo;\n\n  // options.fReducedShaderVariations = false;\n\n  return options;\n}\n#endif  // ENABLE_SKITY\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/context_options.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_CONTEXT_OPTIONS_H_\n#define CLAY_SHELL_COMMON_CONTEXT_OPTIONS_H_\n\n#include <optional>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nenum class ContextType {\n  /// The context is used to render to a texture or renderbuffer.\n  kRender,\n  /// The context will only be used to transfer resources to and from device\n  /// memory. No rendering will be performed using this context.\n  kResource,\n};\n\n//------------------------------------------------------------------------------\n/// @brief      Initializes GrContextOptions with values suitable for Flutter.\n///             The options can be further tweaked before a GrContext is created\n///             from these options.\n///\n/// @param[in]  type  The type of context that will be created using these\n///                   options.\n/// @param[in]  type  The client rendering API that will be wrapped using a\n///                   context with these options. This argument is only required\n///                   if the context is going to be used with a particular\n///                   client rendering API.\n///\n/// @return     The default graphics context options.\n///\n#ifndef ENABLE_SKITY\nGrContextOptions MakeDefaultContextOptions(\n    ContextType type, std::optional<GrBackendApi> api = std::nullopt);\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_CONTEXT_OPTIONS_H_\n"
  },
  {
    "path": "clay/shell/common/devtools_instrumentation.cc",
    "content": "// Copyright 2022 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"clay/shell/common/devtools_instrumentation.h\"\n\n#include <cmath>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"third_party/rapidjson/document.h\"\n\n#ifndef ENABLE_SKITY\n#include \"third_party/rapidjson/rapidjson.h\"\n#include \"third_party/skia/include/core/SkImage.h\"\n#include \"third_party/skia/include/utils/SkBase64.h\"\n#endif\n\nnamespace clay {\n\nconst int kMaxImageSize = 10000;\n\nstd::string DevtoolsInstrumentation::CreateJsonStringResult() {\n  rapidjson::StringBuffer buffer;\n  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);\n  if (raster_document_.Accept(writer)) {\n    return buffer.GetString();\n  }\n  return std::string();\n}\n\n#ifndef ENABLE_SKITY\nvoid DevtoolsInstrumentation::UpdateRasterCacheInfo(\n    const int64_t& single_cache_size, const int64_t& single_cache_height,\n    const int64_t& single_cache_width,\n    const std::string& single_cache_color_type, const int64_t& layer_address,\n    const int64_t& cache_address, GrImagePtr image) {\n  auto& allocator = raster_document_.GetAllocator();\n  rapidjson::Value object(rapidjson::kObjectType);\n  object.AddMember(\"size\", single_cache_size, allocator);\n  object.AddMember(\"height\", single_cache_height, allocator);\n  object.AddMember(\"width\", single_cache_width, allocator);\n  object.AddMember(\"color_space\", single_cache_color_type, allocator);\n  object.AddMember(\"layer_address\", layer_address, allocator);\n  if (image) {\n    auto b64_data = CompressImageData(image.get());\n    rapidjson::Value b64_value(static_cast<const char*>(b64_data->data()),\n                               raster_document_.GetAllocator());\n    object.AddMember(\"base64Image\", b64_value, raster_document_.GetAllocator());\n  }\n  if (raster_document_.HasMember(\"image\")) {\n    raster_document_[\"image\"].PushBack(object, allocator);\n  } else {\n    rapidjson::Value cache_member_array(rapidjson::kArrayType);\n    cache_member_array.PushBack(object, allocator);\n    raster_document_.AddMember(\"image\", cache_member_array, allocator);\n  }\n}\n\nsk_sp<SkData> DevtoolsInstrumentation::CompressImageData(const SkImage* image) {\n  sk_sp<SkData> compressed_data;\n  int image_height = image->height();\n  int image_width = image->width();\n  if (image_height * image_width < kMaxImageSize) {\n    compressed_data = image->encodeToData();\n  } else {\n    float scale_size =\n        sqrt(static_cast<float>(image_width * image_height) / kMaxImageSize);\n    const auto scaled_image_info = image->imageInfo().makeDimensions(\n        SkISize::Make(image_width / scale_size, image_height / scale_size));\n\n    SkBitmap scaled_bitmap;\n    if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) {\n      FML_LOG(ERROR) << \"Failed to allocate memory for bitmap of size \"\n                     << scaled_image_info.computeMinByteSize() << \"B\";\n      compressed_data = image->encodeToData();\n    } else {\n      if (image->scalePixels(\n              scaled_bitmap.pixmap(),\n              SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),\n              SkImage::kDisallow_CachingHint)) {\n        auto scaled_image = SkImages::RasterFromBitmap(scaled_bitmap);\n        compressed_data = scaled_image->encodeToData();\n      } else {\n        FML_LOG(ERROR) << \"Could not scale pixels\";\n        compressed_data = image->encodeToData();\n      }\n    }\n  }\n  size_t b64_size = SkBase64::Encode(compressed_data->data(),\n                                     compressed_data->size(), nullptr);\n  sk_sp<SkData> b64_data = SkData::MakeUninitialized(b64_size + 1);\n  char* b64_char = static_cast<char*>(b64_data->writable_data());\n  SkBase64::Encode(compressed_data->data(), compressed_data->size(), b64_char);\n  b64_char[b64_size] = 0;  // make it null terminated for printing\n  return b64_data;\n}\n#else\nvoid DevtoolsInstrumentation::UpdateRasterCacheInfo(\n    const int64_t& single_cache_size, const int64_t& single_cache_height,\n    const int64_t& single_cache_width,\n    const std::string& single_cache_color_type, const int64_t& layer_address,\n    const int64_t& cache_address, std::shared_ptr<skity::Image> image) {\n  // TODO(zhangxiao.ninja) implement later\n  FML_UNIMPLEMENTED();\n}\n#endif  // ENABLE_SKITY\n\nvoid DevtoolsInstrumentation::UpdateRasterInfoToDevtool() {\n  std::string result = CreateJsonStringResult();\n  view_holder_->UpdateRasterCacheInfo(result);\n}\n\n}  //  namespace clay\n"
  },
  {
    "path": "clay/shell/common/devtools_instrumentation.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_DEVTOOLS_INSTRUMENTATION_H_\n#define CLAY_SHELL_COMMON_DEVTOOLS_INSTRUMENTATION_H_\n\n#include <cstddef>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/shell/common/platform_view.h\"\n#include \"third_party/rapidjson/document.h\"\n#include \"third_party/rapidjson/rapidjson.h\"\n#include \"third_party/rapidjson/stringbuffer.h\"\n#include \"third_party/rapidjson/writer.h\"\n\nnamespace clay {\n\nclass DevtoolsInstrumentation {\n public:\n  explicit DevtoolsInstrumentation(clay::PlatformView* view_holder)\n      : view_holder_(view_holder) {}\n\n  virtual ~DevtoolsInstrumentation() = default;\n\n  void ResetRasterDocument() { raster_document_.RemoveAllMembers(); }\n\n  std::string CreateJsonStringResult();\n\n  void UpdateRasterCacheInfo(const int64_t& single_cache_size,\n                             const int64_t& single_cache_height,\n                             const int64_t& single_cache_width,\n                             const std::string& single_cache_color_type,\n                             const int64_t& layer_address,\n                             const int64_t& cache_address, GrImagePtr image);\n\n  void UpdateRasterInfoToDevtool();\n\n protected:\n#ifndef ENABLE_SKITY\n  sk_sp<SkData> CompressImageData(const SkImage* image);\n#endif  // ENABLE_SKITY\n\n private:\n  rapidjson::Document raster_document_ =\n      rapidjson::Document(rapidjson::kObjectType);\n  clay::PlatformView* view_holder_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_DEVTOOLS_INSTRUMENTATION_H_\n"
  },
  {
    "path": "clay/shell/common/display.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/display.h\"\n\nnamespace clay {\ndouble Display::GetRefreshRate() const { return refresh_rate_; }\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/display.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_DISPLAY_H_\n#define CLAY_SHELL_COMMON_DISPLAY_H_\n\n#include <optional>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/shell/common/variable_refresh_rate_reporter.h\"\n\nnamespace clay {\n\n/// Unique ID per display that is stable until the Flutter application restarts.\n/// See also: `clay::Display`\ntypedef uint64_t DisplayId;\n\n/// To be used when the display refresh rate is unknown.\nstatic constexpr double kUnknownDisplayRefreshRate = 0;\n\n/// Display refers to a graphics hardware system consisting of a framebuffer,\n/// typically a monitor or a screen. This class holds the various display\n/// settings.\nclass Display {\n public:\n  //------------------------------------------------------------------------------\n  /// @brief Construct a new Display object in case where the display id of the\n  /// display is known. In cases where there is more than one display, every\n  /// display is expected to have a display id.\n  ///\n  Display(DisplayId display_id, double refresh_rate)\n      : display_id_(display_id), refresh_rate_(refresh_rate) {}\n\n  //------------------------------------------------------------------------------\n  /// @brief Construct a new Display object when there is only a single display.\n  /// When there are multiple displays, every display must have a display id.\n  ///\n  explicit Display(double refresh_rate)\n      : display_id_({}), refresh_rate_(refresh_rate) {}\n\n  virtual ~Display() = default;\n\n  // Get the display's maximum refresh rate in the unit of frame per second.\n  // Return `kUnknownDisplayRefreshRate` if the refresh rate is unknown.\n  virtual double GetRefreshRate() const;\n\n  /// Returns the `DisplayId` of the display.\n  std::optional<DisplayId> GetDisplayId() const { return display_id_; }\n\n private:\n  std::optional<DisplayId> display_id_;\n  double refresh_rate_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Display);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_DISPLAY_H_\n"
  },
  {
    "path": "clay/shell/common/display_manager.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/display_manager.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nDisplayManager::DisplayManager() = default;\n\nDisplayManager::~DisplayManager() = default;\n\ndouble DisplayManager::GetMainDisplayRefreshRate() const {\n  std::scoped_lock lock(displays_mutex_);\n  if (displays_.empty()) {\n    return kUnknownDisplayRefreshRate;\n  } else {\n    return displays_[0]->GetRefreshRate();\n  }\n}\n\nvoid DisplayManager::HandleDisplayUpdates(\n    DisplayUpdateType update_type,\n    std::vector<std::unique_ptr<Display>> displays) {\n  std::scoped_lock lock(displays_mutex_);\n  CheckDisplayConfiguration(displays);\n  switch (update_type) {\n    case DisplayUpdateType::kStartup:\n      FML_CHECK(displays_.empty());\n      displays_ = std::move(displays);\n      return;\n    default:\n      FML_CHECK(false) << \"Unknown DisplayUpdateType.\";\n  }\n}\n\nvoid DisplayManager::CheckDisplayConfiguration(\n    const std::vector<std::unique_ptr<Display>>& displays) const {\n  FML_CHECK(!displays.empty());\n  if (displays.size() > 1) {\n    for (auto& display : displays) {\n      FML_CHECK(display->GetDisplayId().has_value());\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/display_manager.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_DISPLAY_MANAGER_H_\n#define CLAY_SHELL_COMMON_DISPLAY_MANAGER_H_\n\n#include <memory>\n#include <mutex>\n#include <vector>\n\n#include \"clay/shell/common/display.h\"\n\nnamespace clay {\n\n/// The update type parameter that is passed to\n/// `DisplayManager::HandleDisplayUpdates`.\nenum class DisplayUpdateType {\n  /// `clay::Display`s that were active during start-up. A display is\n  /// considered active if:\n  ///    1. The frame buffer hardware is connected.\n  ///    2. The display is drawable, e.g. it isn't being mirrored from another\n  ///       connected display or sleeping.\n  kStartup\n};\n\n/// Manages lifecycle of the connected displays. This class is thread-safe.\nclass DisplayManager {\n public:\n  DisplayManager();\n\n  ~DisplayManager();\n\n  /// Returns the display refresh rate of the main display. In cases where there\n  /// is only one display connected, it will return that. We do not yet support\n  /// cases where there are multiple displays.\n  ///\n  /// When there are no registered displays, it returns\n  /// `kUnknownDisplayRefreshRate`.\n  double GetMainDisplayRefreshRate() const;\n\n  /// Handles the display updates.\n  void HandleDisplayUpdates(DisplayUpdateType update_type,\n                            std::vector<std::unique_ptr<Display>> displays);\n\n private:\n  /// Guards `displays_` vector.\n  mutable std::mutex displays_mutex_;\n  std::vector<std::unique_ptr<Display>> displays_;\n\n  /// Checks that the provided display configuration is valid. Currently this\n  /// ensures that all the displays have an id in the case there are multiple\n  /// displays. In case where there is a single display, it is valid for the\n  /// display to not have an id.\n  void CheckDisplayConfiguration(\n      const std::vector<std::unique_ptr<Display>>& displays) const;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_DISPLAY_MANAGER_H_\n"
  },
  {
    "path": "clay/shell/common/engine.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/engine.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/common/graphics/shared_image_external_texture.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/shell/common/services/initialize_service.h\"\n#include \"clay/shell/common/services/ui_frame_service.h\"\n#include \"clay/shell/common/services/vsync_waiter_service.h\"\n#if (defined(OS_MAC) || defined(OS_WIN))\n#include \"clay/shell/common/vsync_waiter_fallback.h\"\n#endif\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/ui/resource/font_collection.h\"\n\nnamespace clay {\n\nEngine::Engine(std::shared_ptr<clay::ServiceManager> service_manager,\n               TaskRunners task_runners, Settings settings,\n               fml::RefPtr<GPUUnrefQueue> unref_queue, Delegate* delegate)\n    : service_manager_(service_manager),\n      pending_layout_(false),\n      task_runners_(std::move(task_runners)),\n      settings_(std::move(settings)),\n      delegate_(delegate),\n      weak_factory_(this) {\n  Puppet<Owner::kUI, InitializeService> initialize_service =\n      service_manager->GetService<InitializeService>();\n  ui_frame_service_ = service_manager->GetService<UIFrameService>();\n  raster_frame_service_ = service_manager->GetService<RasterFrameService>();\n  page_view_ = initialize_service->CreatePageView(\n      0, service_manager, std::move(unref_queue), task_runners_);\n  shadow_node_owner_ =\n      std::make_unique<clay::ShadowNodeOwner>(page_view_->GetTaskRunner());\n  view_context_ = initialize_service->CreateViewContext(\n      page_view_.get(), shadow_node_owner_.get());\n  shadow_node_owner_->SetViewContext(view_context_.get());\n  shadow_node_owner_->SetDelegate(this);\n  page_view_->SetRenderDelegate(this);\n  page_view_->SetDefaultFocusRingEnabled(settings_.enable_default_focus_ring);\n  page_view_->SetPerformanceOverlayEnabled(\n      settings_.enable_performance_overlay);\n  page_view_->SetUseTextureBackend(!settings_.enable_software_rendering);\n  clay::Puppet<clay::Owner::kUI, VsyncWaiterService> vsync_waiter_service =\n      service_manager->GetService<VsyncWaiterService>();\n  page_view_->SetRefreshRate(vsync_waiter_service->GetRefreshRate());\n}\n\nEngine::~Engine() {\n  FML_DLOG(ERROR) << \"Dealloc clay::Engine\";\n  page_view_->Destroy();\n  // Clean up views following order: leaked views, PageView, ViewContext.\n  view_context_->CleanLeakedViews();\n  page_view_.reset();\n  shadow_node_owner_.reset();\n  view_context_.reset();\n}\n\nfml::WeakPtr<Engine> Engine::GetWeakPtr() const {\n  return weak_factory_.GetWeakPtr();\n}\n\nstd::unique_ptr<Engine> Engine::Spawn(\n    std::shared_ptr<clay::ServiceManager> service_manager, Settings settings,\n    fml::RefPtr<GPUUnrefQueue> unref_queue, Delegate* delegate) const {\n  auto engine = std::make_unique<Engine>(service_manager, task_runners_,\n                                         settings, unref_queue, delegate);\n  engine->GetPageView()->SetImageResourceFetcher(\n      page_view_->GetImageResourceFetcher());\n  return engine;\n}\n\nbool Engine::BeginFrame(std::unique_ptr<FrameTimingsRecorder> recorder) {\n  TRACE_EVENT(\"clay\", \"Engine::BeginFrame\");\n  if (view_context_ && view_context_->GetFrameObserver()) {\n    view_context_->GetFrameObserver()->OnBeginFrame();\n  }\n\n  if (pending_layout_) {\n    shadow_node_owner_->TriggerLayout();\n    pending_layout_ = false;\n  }\n  if (page_view_) {\n    return page_view_->BeginFrame(std::move(recorder));\n  }\n  return false;\n}\n\nvoid Engine::ScheduleLayout() {\n  if (!pending_layout_) {\n    pending_layout_ = true;\n    ScheduleFrame();\n  }\n}\n\nvoid Engine::OnPlatformViewCreated() { page_view_->OnPlatformViewCreated(); }\n\nvoid Engine::OnOutputSurfaceCreated() { page_view_->OnOutputSurfaceCreated(); }\n\nvoid Engine::OnOutputSurfaceCreateFailed() {\n  page_view_->OnOutputSurfaceCreateFailed();\n}\n\nvoid Engine::OnOutputSurfaceDestroyed() {\n  page_view_->OnOutputSurfaceDestroyed();\n  // It's not enough to release graphic resources of view tree under PageView,\n  // because some views may not attached when surface destroyed.\n  // Also it's not enough to release resources of views under ViewContext,\n  // because some views may created by clay inner.\n  view_context_->OnOutputSurfaceDestroyed();\n}\n\nvoid Engine::CleanForRecycle() {\n  view_context_->CleanLeakedViews();\n  page_view_->CleanForRecycle();\n  shadow_node_owner_->ClearNodes();\n  pending_layout_ = false;\n}\n\nvoid Engine::PrepareForRecycle() {}\n\nvoid Engine::SetViewportMetrics(const ViewportMetrics& metrics) {\n  page_view_->SetViewportMetrics(metrics);\n#if defined(OS_MAC) || defined(OS_WIN) || defined(OS_LINUX) || \\\n    defined(ENABLE_HEADLESS)\n  // If it is Macos, we have to call begin frame without PostTask().\n  // Otherwise, the thread will dead locked.\n  // See: FlutterThreadSynchronizer.mm - beginResize\n  ForceBeginFrame();\n#else\n  ScheduleFrame();\n#endif\n}\n\nvoid Engine::SetupDefaultFontManager() {\n  TRACE_EVENT(\"clay\", \"Engine::SetupDefaultFontManager\");\n  clay::FontCollection::Instance()->SetupDefaultFontManager(\n      settings_.font_initialization_data);\n}\n\nbool Engine::DispatchPointerEvent(std::vector<clay::PointerEvent> events) {\n  return page_view_->DispatchPointerEvent(std::move(events));\n}\n\nvoid Engine::DispatchKeyEvent(\n    std::unique_ptr<clay::KeyEvent> event,\n    std::function<void(bool /* handled */)> callback) {\n  page_view_->DispatchKeyEvent(std::move(event), std::move(callback));\n}\n\nvoid Engine::SendKeyboardEvent(std::unique_ptr<clay::KeyEvent> key_event) {\n  page_view_->OnKeyboardEvent(std::move(key_event));\n}\n\nvoid Engine::PerformEditorAction(clay::KeyboardAction action_code) {\n  page_view_->OnPerformAction(action_code);\n}\n\nvoid Engine::OnAnimationEvent(const clay::ElementId& element_id,\n                              const clay::AnimationParams& animation_params) {\n  int owner_id = element_id.view_id();\n  if (auto view = view_context_->GetViewById(owner_id);\n      view != nullptr && view->HasAnimationEvent(animation_params.event_type)) {\n    page_view_->DispatchAnimationEvent(animation_params, owner_id);\n  }\n}\n\nvoid Engine::OnTransitionEvent(const clay::ElementId& element_id,\n                               const clay::AnimationParams& animation_params,\n                               ClayAnimationPropertyType property_type) {\n  int owner_id = element_id.view_id();\n  if (auto view = view_context_->GetViewById(owner_id);\n      view != nullptr && view->HasAnimationEvent(animation_params.event_type)) {\n    page_view_->DispatchTransitionEvent(animation_params, owner_id,\n                                        property_type);\n  }\n}\n\nvoid Engine::DeleteSurroundingText(int before_length, int after_length) {\n  page_view_->OnDeleteSurroundingText(before_length, after_length);\n}\n\nvoid Engine::OnEnterForeground() { page_view_->DispatchEnterForeground(); }\n\nvoid Engine::OnEnterBackground() { page_view_->DispatchEnterBackground(); }\n\nvoid Engine::SetVisible(bool enable) { page_view_->SetVisible(enable); }\n\nvoid Engine::RequestPaint() { page_view_->RequestPaintBase(); }\n\nvoid Engine::SetDefaultFocusRingEnabled(bool enable) {\n  page_view_->SetDefaultFocusRingEnabled(enable);\n}\n\nvoid Engine::SetPerformanceOverlayEnabled(bool enable) {\n  page_view_->SetPerformanceOverlayEnabled(enable);\n}\n\nvoid Engine::SetDefaultOverflowVisible(bool visible) {\n  view_context_->SetDefaultOverflowVisible(visible);\n}\n\nvoid Engine::SetExposureProps(int freq, bool enable_exposure_ui_margin) {\n  view_context_->SetExposureProps(freq, enable_exposure_ui_margin);\n}\n\nvoid Engine::MakeRasterSnapshot(\n    std::unique_ptr<clay::LayerTree> layer_tree,\n    std::function<void(fml::RefPtr<clay::PaintImage>)> callback) {\n  if (delegate_ == nullptr) {\n    callback(nullptr);\n    return;\n  }\n  delegate_->MakeRasterSnapshot(std::move(layer_tree), std::move(callback));\n}\n\nfml::RefPtr<PaintImage> Engine::MakeRasterSnapshot(GrPicturePtr picture,\n                                                   skity::Vec2 picture_size) {\n  if (delegate_ == nullptr) {\n    return nullptr;\n  }\n\n  return delegate_->MakeRasterSnapshot(std::move(picture), picture_size);\n}\n\nclay::BaseView* Engine::FindViewById(int view_id) {\n  return view_context_->FindViewByViewId(view_id);\n}\n\nclay::ShadowNode* Engine::FindShadowNodeById(int node_id) {\n  return view_context_->FindShadowNodeByNodeId(node_id);\n}\n\nvoid Engine::SetRenderSettings(\n    fml::RefPtr<clay::RenderSettings> render_settings) {\n  page_view_->SetRenderSettings(render_settings);\n}\n\nvoid Engine::ScheduleFrame() {\n  ui_frame_service_.Act([](auto& impl) { impl.RequestFrame(); });\n}\n\nvoid Engine::ForceBeginFrame() {\n  clay::Puppet<clay::Owner::kUI, VsyncWaiterService> vsync_waiter_service =\n      service_manager_->GetService<VsyncWaiterService>();\n  const auto frame_begin_time = fml::TimePoint::Now();\n  const auto frame_end_time =\n      frame_begin_time + fml::TimeDelta::FromSecondsF(\n                             1.f / vsync_waiter_service->GetRefreshRate());\n  std::unique_ptr<FrameTimingsRecorder> recorder =\n      std::make_unique<FrameTimingsRecorder>();\n  recorder->RecordVsync(frame_begin_time, frame_end_time);\n  recorder->RecordForced(true);\n  ui_frame_service_.Act([recorder = std::move(recorder)](auto& impl) mutable {\n    impl.ForceBeginFrame(std::move(recorder));\n  });\n}\n\nvoid Engine::OnFirstMeaningfulLayout() {\n  ui_frame_service_.Act([](auto& impl) { impl.OnFirstMeaningfulLayout(); });\n}\n\nbool Engine::Raster(std::unique_ptr<clay::LayerTree> layer_tree,\n                    std::unique_ptr<clay::FrameTimingsRecorder> recorder,\n                    bool force) {\n  // Ensure frame dimensions are sane.\n  if (layer_tree &&\n      ((layer_tree->frame_size().x == 0 && layer_tree->frame_size().y == 0) ||\n       layer_tree->device_pixel_ratio() <= 0.0f)) {\n    layer_tree.reset();\n  }\n  if (!layer_tree) {\n    raster_frame_service_.Act([](auto& impl) { impl.CommitWithNoUpdates(); });\n  } else {\n    if (!recorder) {\n      recorder = std::make_unique<FrameTimingsRecorder>();\n      const fml::TimePoint placeholder_time = fml::TimePoint::Now();\n      recorder->RecordVsync(placeholder_time, placeholder_time);\n      recorder->RecordBuildStart(placeholder_time);\n    }\n    recorder->RecordBuildEnd(fml::TimePoint::Now());\n    layer_tree->SetServiceManagerForAnimation(service_manager_);\n    raster_frame_service_.Act([layer_tree = std::move(layer_tree),\n                               recorder = std::move(recorder),\n                               force = force](auto& impl) mutable {\n      if (force) {\n        // It's used for unittests, the shell_test is not able to handle the\n        // scheduler with state machine.\n        impl.ForceCommit(std::move(layer_tree), std::move(recorder));\n      } else {\n        impl.Commit(std::move(layer_tree), std::move(recorder));\n      }\n    });\n  }\n  return true;\n}\n\nvoid Engine::ShowSoftInput(int type, int action) {\n  delegate_->ShowSoftInput(type, action);\n}\n\nvoid Engine::HideSoftInput() { delegate_->HideSoftInput(); }\n\nstd::string Engine::ShouldInterceptUrl(const std::string& origin_url,\n                                       bool should_decode) {\n  return delegate_->ShouldInterceptUrl(origin_url, should_decode);\n}\n\nstd::shared_ptr<clay::ResourceLoaderIntercept>\nEngine::GetResourceLoaderIntercept() {\n  return delegate_->GetResourceLoaderIntercept();\n}\n\nstd::shared_ptr<clay::FontCollection> Engine::GetFontCollection() {\n  return clay::FontCollection::Instance();\n}\n\nvoid Engine::SetFrameTimingCollector(\n    std::shared_ptr<clay::FrameTimingCollector> frame_timing_collector) {\n  page_view_->SetFrameTimingCollector(frame_timing_collector);\n}\n\nvoid Engine::SetFontFaceCache(const char* font_family, const char* local_path) {\n  std::vector<std::string> src_vec;\n  src_vec.emplace_back(local_path);\n  clay::FontCollection::Instance()->PreLoadFontOnMem(\n      task_runners_.GetUITaskRunner(), GetResourceLoaderIntercept(),\n      page_view_->GetServiceManager(), std::string(font_family),\n      std::move(src_vec));\n}\n\nvoid Engine::UpdateMemoryCacheOptions() {\n  clay::Isolate::Instance().UpdateResourceCacheMaxMemoryLimit(\n      settings_.image_texture_cache_max_limit,\n      settings_.low_end_image_texture_cache_max_limit);\n}\n\nbool Engine::MarkDrawableImageFrameAvailable(int64_t image_id) {\n  return page_view_ && page_view_->MarkDrawableImageFrameAvailable(image_id);\n}\n\nconst std::weak_ptr<VsyncWaiter> Engine::GetVsyncWaiter() const {\n  return std::weak_ptr<VsyncWaiter>();\n}\n\nvoid Engine::ClearTextCache() {\n  clay::FontCollection::Instance()->ClearFontFamilyCache();\n}\n\nvoid Engine::UpdateRootSize(int32_t width, int32_t height) {\n  if (delegate_) {\n    delegate_->UpdateRootSize(width, height);\n  }\n}\n\nvoid Engine::RegisterDrawableImage(\n    std::shared_ptr<DrawableImage> drawable_image) {\n  if (delegate_) {\n    delegate_->RegisterDrawableImage(drawable_image);\n  }\n}\n\nvoid Engine::UnregisterDrawableImage(int64_t id) {\n  if (delegate_) {\n    delegate_->UnregisterDrawableImage(id);\n  }\n}\n\nvoid Engine::ReportTiming(\n    const std::unordered_map<std::string, int64_t>& timing,\n    const std::string& flag) {\n  delegate_->ReportTiming(timing, flag);\n}\n\n#ifdef ENABLE_ACCESSIBILITY\nvoid Engine::UpdateSemantics(const clay::SemanticsUpdateNodes& update_nodes) {\n  if (delegate_) {\n    delegate_->UpdateSemantics(update_nodes);\n  }\n}\n\nvoid Engine::SetSemanticsEnabled(bool enabled) {\n  if (page_view_) {\n    page_view_->SetSemanticsEnabled(enabled);\n  }\n}\n\nvoid Engine::SetPageEnableAccessibilityElement(bool enabled) {\n  if (page_view_) {\n    page_view_->SetPageEnableAccessibilityElement(enabled);\n  }\n}\n\nvoid Engine::DispatchSemanticsAction(int virtual_view_id, int action) {\n  if (page_view_) {\n    page_view_->DispatchSemanticsAction(virtual_view_id, action);\n  }\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/engine.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_ENGINE_H_\n#define CLAY_SHELL_COMMON_ENGINE_H_\n\n#include <future>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/common/element_id.h\"\n#include \"clay/common/recyclable.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/shell/common/services/raster_frame_service.h\"\n#include \"clay/shell/common/services/ui_frame_service.h\"\n#include \"clay/shell/common/vsync_waiter.h\"\n#include \"clay/ui/common/frame_timing_collector.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/event/key_event.h\"\n#include \"clay/ui/platform/keyboard_types.h\"\n#include \"clay/ui/render_delegate.h\"\n#include \"clay/ui/resource/font_collection.h\"\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n#include \"clay/ui/window/viewport_metrics.h\"\n\nnamespace clay {\nclass ResourceLoaderIntercept;\n}  // namespace clay\n\nnamespace clay {\n\nusing clay::GPUUnrefQueue;\n\nnamespace testing {\nclass ShellTest;\n}\n\nclass LayerTree;\n\nclass Engine : public clay::RenderDelegate, public clay::Recyclable {\n public:\n  class Delegate {\n   public:\n    virtual void ShowSoftInput(int type, int action) = 0;\n    virtual void HideSoftInput() = 0;\n    virtual void FilterInputAsync(\n        const std::string& input, const std::string& pattern,\n        std::function<void(const std::string&)> callback) = 0;\n    virtual std::string ShouldInterceptUrl(const std::string& origin_url,\n                                           bool should_decode) = 0;\n    virtual std::shared_ptr<clay::ResourceLoaderIntercept>\n    GetResourceLoaderIntercept() {\n      return nullptr;\n    }\n\n    virtual void MakeRasterSnapshot(\n        std::unique_ptr<LayerTree> layer_tree,\n        std::function<void(fml::RefPtr<PaintImage>)> callback) = 0;\n    virtual fml::RefPtr<PaintImage> MakeRasterSnapshot(\n        GrPicturePtr picture, skity::Vec2 picture_size) = 0;\n    virtual void DumpInfoToDevtoolEnabled(bool enabled) {}\n    virtual void SetClipboardData(const std::u16string& data) = 0;\n    virtual std::u16string GetClipboardData() = 0;\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n    // Text input related functions Begin.\n    virtual void SetTextInputClient(int client_id, const char* input_action,\n                                    const char* input_type) = 0;\n    virtual void ClearTextInputClient() = 0;\n    virtual void SetEditableTransform(const float transform_matrix[16]) = 0;\n    virtual void SetEditingState(uint64_t selection_base,\n                                 uint64_t composing_extent,\n                                 const std::string& selection_affinity,\n                                 const std::string& text,\n                                 bool selection_directional,\n                                 uint64_t selection_extent,\n                                 uint64_t composing_base) = 0;\n    virtual void SetCaretRect(float x, float y, float width, float height) = 0;\n    virtual void setMarkedTextRect(float x, float y, float width,\n                                   float height) = 0;\n    virtual void ShowTextInput() = 0;\n    virtual void HideTextInput() = 0;\n    // Text input related functions End.\n    virtual void WindowMove() = 0;\n    virtual void ActivateSystemCursor(int type, const std::string& path) = 0;\n#endif\n    virtual void ReportTiming(\n        const std::unordered_map<std::string, int64_t>& timing,\n        const std::string& flag) = 0;\n    virtual void UpdateRootSize(int32_t width, int32_t height) = 0;\n\n#ifdef ENABLE_ACCESSIBILITY\n    virtual void UpdateSemantics(\n        const clay::SemanticsUpdateNodes& update_nodes) = 0;\n#endif\n\n    virtual void RegisterDrawableImage(\n        std::shared_ptr<DrawableImage> drawable_image) = 0;\n    virtual void UnregisterDrawableImage(int64_t id) = 0;\n  };\n\n  Engine(std::shared_ptr<clay::ServiceManager> service_manager,\n         TaskRunners task_runners, Settings settings,\n         fml::RefPtr<GPUUnrefQueue> unref_queue, Delegate* delegate);\n  ~Engine();\n\n  fml::WeakPtr<Engine> GetWeakPtr() const;\n  std::unique_ptr<Engine> Spawn(\n      std::shared_ptr<clay::ServiceManager> service_manager, Settings settings,\n      fml::RefPtr<GPUUnrefQueue> unref_queue, Delegate* delegate) const;\n\n  bool BeginFrame(std::unique_ptr<FrameTimingsRecorder> recorder);\n\n  void SetViewportMetrics(const ViewportMetrics& metrics);\n  void SetupDefaultFontManager();\n  bool DispatchPointerEvent(std::vector<clay::PointerEvent> events);\n  void DispatchKeyEvent(std::unique_ptr<clay::KeyEvent> event,\n                        std::function<void(bool /* handled */)> callback);\n  void SendKeyboardEvent(std::unique_ptr<clay::KeyEvent> key_event);\n  void PerformEditorAction(clay::KeyboardAction action_code);\n  void DeleteSurroundingText(int before_length, int after_length);\n  void SetRenderSettings(fml::RefPtr<clay::RenderSettings> render_settings);\n\n  void OnEnterForeground();\n  void OnEnterBackground();\n\n  void SetVisible(bool enable);\n  void RequestPaint();\n\n  void SetDefaultFocusRingEnabled(bool enable);\n  void SetPerformanceOverlayEnabled(bool enable);\n  void SetDefaultOverflowVisible(bool visible);\n  void SetExposureProps(int freq, bool enable_exposure_ui_margin);\n\n  /// Schedule a frame with the default parameter of regenerating the layer\n  /// tree.\n  void ScheduleLayout() override;\n\n  // clay::RenderDelegate\n  void ScheduleFrame() override;\n  void ForceBeginFrame() override;\n  void OnFirstMeaningfulLayout() override;\n  bool Raster(std::unique_ptr<clay::LayerTree> layer_tree,\n              std::unique_ptr<clay::FrameTimingsRecorder> recorder = nullptr,\n              bool force = false) override;\n  void ShowSoftInput(int type, int action) override;\n  void HideSoftInput() override;\n  std::string ShouldInterceptUrl(const std::string& origin_url,\n                                 bool should_decode) override;\n  std::shared_ptr<clay::ResourceLoaderIntercept> GetResourceLoaderIntercept()\n      override;\n  void MakeRasterSnapshot(\n      std::unique_ptr<LayerTree> layer_tree,\n      std::function<void(fml::RefPtr<PaintImage>)> callback) override;\n  fml::RefPtr<PaintImage> MakeRasterSnapshot(GrPicturePtr picture,\n                                             skity::Vec2 picture_size) override;\n  clay::BaseView* FindViewById(int view_id) override;\n  clay::ShadowNode* FindShadowNodeById(int node_id) override;\n\n  // compositor animation.\n  void OnAnimationEvent(const clay::ElementId& element_id,\n                        const clay::AnimationParams& animation_params);\n  void OnTransitionEvent(const clay::ElementId& element_id,\n                         const clay::AnimationParams& animation_params,\n                         ClayAnimationPropertyType property_type);\n\n  void DumpInfoToDevtoolEnabled(bool enabled) override {\n    delegate_->DumpInfoToDevtoolEnabled(enabled);\n  }\n\n  std::shared_ptr<clay::FontCollection> GetFontCollection();\n  clay::ViewContext* GetViewContext() { return view_context_.get(); }\n  std::shared_ptr<clay::ViewContext> view_context() const {\n    return view_context_;\n  }\n  clay::PageView* GetPageView() { return page_view_.get(); }\n  const std::shared_ptr<clay::ServiceManager>& GetServiceManager() const {\n    return service_manager_;\n  }\n\n  void SetFrameTimingCollector(\n      std::shared_ptr<clay::FrameTimingCollector> frame_timing_collector);\n\n  void OnPlatformViewCreated();\n\n  void OnOutputSurfaceCreated();\n  void OnOutputSurfaceCreateFailed();\n  void OnOutputSurfaceDestroyed();\n\n  void SetFontFaceCache(const char* font_family, const char* local_path);\n\n  void UpdateMemoryCacheOptions();\n\n  bool MarkDrawableImageFrameAvailable(int64_t texture_id);\n\n  void SetClipboardData(const std::u16string& data) override {\n    delegate_->SetClipboardData(data);\n  }\n\n  std::u16string GetClipboardData() override {\n    return delegate_->GetClipboardData();\n  }\n\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  // Text input related functions Begin.\n  void SetTextInputClient(int client_id, const char* input_action,\n                          const char* input_type) override {\n    delegate_->SetTextInputClient(client_id, input_action, input_type);\n  }\n\n  void ClearTextInputClient() override { delegate_->ClearTextInputClient(); }\n\n  void SetEditableTransform(const float transform_matrix[16]) override {\n    delegate_->SetEditableTransform(transform_matrix);\n  }\n\n  void SetEditingState(uint64_t selection_base, uint64_t composing_extent,\n                       const std::string& selection_affinity,\n                       const std::string& text, bool selection_directional,\n                       uint64_t selection_extent,\n                       uint64_t composing_base) override {\n    delegate_->SetEditingState(selection_base, composing_extent,\n                               selection_affinity, text, selection_directional,\n                               selection_extent, composing_base);\n  }\n\n  void SetCaretRect(float x, float y, float width, float height) override {\n    delegate_->SetCaretRect(x, y, width, height);\n  }\n\n  void setMarkedTextRect(float x, float y, float width, float height) override {\n    delegate_->setMarkedTextRect(x, y, width, height);\n  }\n\n  void ShowTextInput() override { delegate_->ShowTextInput(); }\n\n  void HideTextInput() override { delegate_->HideTextInput(); }\n  // Text input related functions End.\n  void WindowMove() override { delegate_->WindowMove(); }\n  void ActivateSystemCursor(int type, const std::string& path) override {\n    delegate_->ActivateSystemCursor(type, path);\n  }\n#endif\n\n  void FilterInputAsync(\n      const std::string& input, const std::string& pattern,\n      std::function<void(const std::string&)> callback) override {\n    delegate_->FilterInputAsync(input, pattern, callback);\n  }\n  void ReportTiming(const std::unordered_map<std::string, int64_t>& timing,\n                    const std::string& flag) override;\n\n  const std::weak_ptr<VsyncWaiter> GetVsyncWaiter() const;\n\n  void ClearTextCache() override;\n\n  void UpdateRootSize(int32_t width, int32_t height) override;\n\n#ifdef ENABLE_ACCESSIBILITY\n  void UpdateSemantics(const clay::SemanticsUpdateNodes& update_nodes) override;\n  void SetSemanticsEnabled(bool enabled);\n  void SetPageEnableAccessibilityElement(bool enabled);\n  void DispatchSemanticsAction(int virtual_view_id, int action);\n#endif\n\n  void RegisterDrawableImage(\n      std::shared_ptr<DrawableImage> drawable_image) override;\n\n  void UnregisterDrawableImage(int64_t id) override;\n\n  void CleanForRecycle() override;\n  void PrepareForRecycle() override;\n\n private:\n  const std::shared_ptr<clay::ServiceManager> service_manager_;\n  bool pending_layout_;\n  TaskRunners task_runners_;\n  const Settings settings_;\n  std::shared_ptr<clay::ViewContext> view_context_;\n  clay::Puppet<clay::Owner::kUI, UIFrameService> ui_frame_service_;\n  clay::Puppet<clay::Owner::kUI, RasterFrameService> raster_frame_service_;\n  std::unique_ptr<clay::PageView> page_view_;\n  std::unique_ptr<clay::ShadowNodeOwner> shadow_node_owner_;\n  Delegate* delegate_;\n\n  fml::WeakPtrFactory<Engine> weak_factory_;\n  friend class testing::ShellTest;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_ENGINE_H_\n"
  },
  {
    "path": "clay/shell/common/expired_on_task_runner.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_EXPIRED_ON_TASK_RUNNER_H_\n#define CLAY_SHELL_COMMON_EXPIRED_ON_TASK_RUNNER_H_\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\ntemplate <typename T>\nclass ExpiredOnTaskRunner {\n public:\n  ExpiredOnTaskRunner() : ExpiredOnTaskRunner(nullptr) {}\n\n  explicit ExpiredOnTaskRunner(std::unique_ptr<T> ptr)\n      : resource_ptr_(std::move(ptr)),\n        task_runner_(nullptr),\n        release_func_(nullptr) {}\n\n  ExpiredOnTaskRunner(std::unique_ptr<T> ptr,\n                      fml::RefPtr<fml::TaskRunner> task_runner)\n      : resource_ptr_(std::move(ptr)),\n        task_runner_(task_runner),\n        release_func_(nullptr) {}\n\n  ~ExpiredOnTaskRunner() { ReleaseSourcesOnTaskRunner(); }\n\n  void SetTaskRunner(fml::RefPtr<fml::TaskRunner> task_runner) {\n    FML_DCHECK(task_runner);\n    task_runner_ = task_runner;\n  }\n\n  void SetReleaseFunc(const std::function<void(std::unique_ptr<T>)>& func) {\n    FML_DCHECK(func);\n    release_func_ = func;\n  }\n\n  ExpiredOnTaskRunner<T>& operator=(ExpiredOnTaskRunner<T>&& other) {\n    if (&other == this) {\n      return *this;\n    }\n    ReleaseSourcesOnTaskRunner();\n    resource_ptr_ = std::move(other.resource_ptr_);\n    task_runner_ = other.task_runner_;\n    release_func_ = other.release_func_;\n    other.Reset();\n    return *this;\n  }\n\n  bool IsValid() { return resource_ptr_ && task_runner_ && release_func_; }\n\n  T* operator->() { return resource_ptr_.get(); }\n\n private:\n  void Reset() {\n    resource_ptr_ = nullptr;\n    task_runner_ = nullptr;\n    release_func_ = nullptr;\n  }\n\n  void ReleaseSourcesOnTaskRunner() {\n    if (!resource_ptr_ || !release_func_ || !task_runner_) {\n      return;\n    }\n    fml::TaskRunner::RunNowOrPostTask(\n        task_runner_, fml::MakeCopyable([ptr = std::move(resource_ptr_),\n                                         func = release_func_]() mutable {\n          func(std::move(ptr));\n        }));\n  }\n\n  std::unique_ptr<T> resource_ptr_;\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n  std::function<void(std::unique_ptr<T>)> release_func_;\n};\n}  // namespace clay\n#endif  // CLAY_SHELL_COMMON_EXPIRED_ON_TASK_RUNNER_H_\n"
  },
  {
    "path": "clay/shell/common/frame_timing_listener.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_FRAME_TIMING_LISTENER_H_\n#define CLAY_SHELL_COMMON_FRAME_TIMING_LISTENER_H_\n\n#include <cstdint>\n\nnamespace clay {\n\nclass FrameTimingListener {\n public:\n  virtual ~FrameTimingListener() = default;\n\n  virtual void OnFrameTiming(int64_t frame_start_time_in_ns,\n                             int64_t frame_finish_time_in_ns) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_FRAME_TIMING_LISTENER_H_\n"
  },
  {
    "path": "clay/shell/common/one_shot_callback.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_ONE_SHOT_CALLBACK_H_\n#define CLAY_SHELL_COMMON_ONE_SHOT_CALLBACK_H_\n\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <type_traits>\n#include <utility>\n\nnamespace clay {\n\ntemplate <typename... Args>\nclass OneShotCallback {\n public:\n  OneShotCallback() = default;\n  ~OneShotCallback() = default;\n\n  template <typename F, typename = std::enable_if_t<\n                            !std::is_same_v<std::decay_t<F>, OneShotCallback>>>\n  explicit OneShotCallback(F&& f)\n      : shared_state_(std::make_shared<SharedState>(std::forward<F>(f))) {}\n\n  OneShotCallback(const OneShotCallback& other) = default;\n  OneShotCallback(OneShotCallback&& other) noexcept = default;\n  OneShotCallback& operator=(const OneShotCallback& other) = default;\n  OneShotCallback& operator=(OneShotCallback&& other) noexcept = default;\n\n  void operator()(Args&&... args) {\n    if (shared_state_) {\n      shared_state_->Execute(std::forward<Args>(args)...);\n    }\n  }\n\n  explicit operator bool() const { return UnDone(); }\n\n  bool UnDone() const { return shared_state_ && shared_state_->callback; }\n\n  void Reset() { shared_state_.reset(); }\n\n private:\n  struct SharedState {\n    std::function<void(Args...)> callback;\n    std::once_flag flag;\n\n    template <typename F>\n    explicit SharedState(F&& f) : callback(std::forward<F>(f)) {}\n\n    void Execute(Args&&... args) {\n      std::call_once(flag, [this, &args...]() {\n        if (callback) {\n          std::invoke(callback, std::forward<Args>(args)...);\n          callback = nullptr;\n        }\n      });\n    }\n  };\n\n  std::shared_ptr<SharedState> shared_state_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_ONE_SHOT_CALLBACK_H_\n"
  },
  {
    "path": "clay/shell/common/output_surface.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/output_surface.h\"\n\nnamespace clay {\n\nOutputSurface::~OutputSurface() = default;\n\nvoid OutputSurface::CreateMainGrContext() {}\n\nclay::GrContextPtr OutputSurface::GetMainGrContext() { return nullptr; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/output_surface.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_OUTPUT_SURFACE_H_\n#define CLAY_SHELL_COMMON_OUTPUT_SURFACE_H_\n\n#include <memory>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\nclass Surface;\n\n// OutputSurface is a ref counted object which is owned by platform thread and\n// raster thread. It's used to create GPU Surface for rendering, but also\n// connected to it's platform view or maybe offscreen surface in the future.\nclass OutputSurface : public fml::RefCountedThreadSafe<OutputSurface> {\n public:\n  virtual ~OutputSurface();\n\n  virtual void CreateMainGrContext();\n\n  virtual clay::GrContextPtr GetMainGrContext();\n\n  virtual std::unique_ptr<Surface> CreateGPUSurface(\n      clay::GrContext* gr_context = nullptr) = 0;\n\n  virtual bool IsValid() const = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_OUTPUT_SURFACE_H_\n"
  },
  {
    "path": "clay/shell/common/pipeline_timing_delegate.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_PIPELINE_TIMING_DELEGATE_H_\n#define CLAY_SHELL_COMMON_PIPELINE_TIMING_DELEGATE_H_\n\n#include <string>\n#include <utility>\n#include <vector>\n\nnamespace clay {\n\nclass PipelineTimingDelegate {\n public:\n  virtual ~PipelineTimingDelegate() = default;\n  virtual bool HasPipelineIds() const = 0;\n  virtual std::vector<std::string> GetPipelineIds() const = 0;\n  virtual void OnPipelineEnd(\n      std::vector<std::pair<std::string, uint64_t>> timings,\n      std::vector<std::string> pipeline_id_list) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_PIPELINE_TIMING_DELEGATE_H_\n"
  },
  {
    "path": "clay/shell/common/platform_view.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/platform_view.h\"\n\n#include <cstring>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/common/service/service_manager.h\"\n\nnamespace clay {\n\nPlatformView::PlatformView(\n    std::shared_ptr<clay::ServiceManager> service_manager, Delegate& delegate,\n    const TaskRunners& task_runners)\n    : service_manager_(service_manager),\n      delegate_(delegate),\n      task_runners_(task_runners),\n      weak_factory_(this) {}\n\nPlatformView::~PlatformView() {}\n\nbool PlatformView::DispatchPointerDataPacket(\n    std::unique_ptr<PointerDataPacket> packet) {\n  return delegate_.OnPlatformViewDispatchPointerDataPacket(\n      pointer_data_packet_converter_.Convert(std::move(packet)));\n}\n\nvoid PlatformView::DispatchKeyEvent(std::unique_ptr<clay::KeyEvent> key_event,\n                                    Delegate::KeyDataResponse callback) {\n  delegate_.OnPlatformViewDispatchKeyEvent(std::move(key_event),\n                                           std::move(callback));\n}\n\n#ifdef ENABLE_ACCESSIBILITY\nvoid PlatformView::DispatchClaySemanticsAction(int virtual_view_id,\n                                               int action) {\n  delegate_.OnPlatformViewDispatchSemanticsAction(virtual_view_id, action);\n}\n#endif\n\nvoid PlatformView::SetViewportMetrics(const ViewportMetrics& metrics) {\n  delegate_.OnPlatformViewSetViewportMetrics(metrics);\n}\n\nvoid PlatformView::NotifyCreated() { delegate_.OnPlatformViewCreated(); }\n\nvoid PlatformView::NotifyDestroyed() { delegate_.OnPlatformViewDestroyed(); }\n\nvoid PlatformView::ScheduleFrame() { delegate_.OnPlatformViewScheduleFrame(); }\n\nPointerDataDispatcherMaker PlatformView::GetDispatcherMaker() {\n  return [](DefaultPointerDataDispatcher::Delegate& delegate) {\n    return std::make_unique<DefaultPointerDataDispatcher>(delegate);\n  };\n}\n\nfml::WeakPtr<PlatformView> PlatformView::GetWeakPtr() const {\n  return weak_factory_.GetWeakPtr();\n}\n\nvoid PlatformView::OnPreEngineRestart() const {}\n\nvoid PlatformView::SetNextFrameCallback(const fml::closure& closure) {\n  if (!closure) {\n    return;\n  }\n\n  delegate_.OnPlatformViewSetNextFrameCallback(closure);\n}\n\nstd::unique_ptr<std::vector<std::string>>\nPlatformView::ComputePlatformResolvedLocales(\n    const std::vector<std::string>& supported_locale_data) {\n  std::unique_ptr<std::vector<std::string>> out =\n      std::make_unique<std::vector<std::string>>();\n  return out;\n}\n\nvoid PlatformView::DispatchKeyDataPacket(\n    std::unique_ptr<clay::KeyDataPacket> packet,\n    Delegate::KeyDataResponse callback) {\n  if (!packet) {\n    return;\n  }\n\n  // unpack clay::KeyDataPacket\n  auto& data = packet->data();\n  const uint8_t* buf = data.data();\n  uint64_t char_size = 0;\n  if (data.size() < sizeof(char_size) + sizeof(KeyData)) {\n    return;\n  }\n  memcpy(&char_size, buf, sizeof(char_size));\n  buf += sizeof(char_size);\n  if (data.size() != sizeof(char_size) + sizeof(KeyData) + char_size) {\n    return;\n  }\n  KeyData key_data;\n  memcpy(&key_data, buf, sizeof(KeyData));\n  buf += sizeof(KeyData);\n  auto character = std::string(buf, buf + char_size);\n\n  // convert to clay::KeyEvent\n  auto event = std::make_unique<clay::KeyEvent>(\n      key_data.timestamp, static_cast<clay::KeyEventType>(key_data.type),\n      static_cast<clay::PhysicalKeyboardKey>(key_data.physical),\n      static_cast<clay::LogicalKeyboardKey>(key_data.logical),\n      key_data.synthesized, character);\n\n  DispatchKeyEvent(std::move(event), std::move(callback));\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/platform_view.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_PLATFORM_VIEW_H_\n#define CLAY_SHELL_COMMON_PLATFORM_VIEW_H_\n\n#include <functional>\n#include <memory>\n#include <optional>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/shell/common/pointer_data_dispatcher.h\"\n#include \"clay/shell/common/vsync_waiter.h\"\n#include \"clay/ui/event/key_event.h\"\n#include \"clay/ui/platform/keyboard_types.h\"\n#ifdef ENABLE_ACCESSIBILITY\n#include \"clay/ui/semantics/semantics_update_node.h\"\n#endif\n#include \"clay/ui/window/key_data_packet.h\"\n#include \"clay/ui/window/pointer_data_packet.h\"\n#include \"clay/ui/window/pointer_data_packet_converter.h\"\n#include \"clay/ui/window/viewport_metrics.h\"\n\nnamespace clay {\nclass ServiceManager;\nclass ResourceLoaderIntercept;\n}  // namespace clay\n\nnamespace clay {\n\nclass OutputSurface;\n\n//------------------------------------------------------------------------------\n/// @brief      Platform views are created by the shell on the platform task\n///             runner. Unless explicitly specified, all platform view methods\n///             are called on the platform task runner as well. Platform views\n///             are usually sub-classed on a per platform basis and the bulk of\n///             the window system integration happens using that subclass. Since\n///             most platform window toolkits are usually only safe to access on\n///             a single \"main\" thread, any interaction that requires access to\n///             the underlying platform's window toolkit is routed through the\n///             platform view associated with that shell. This involves\n///             operations like settings up and tearing down the render surface,\n///             interacting with accessibility features on the platform, input\n///             events, etc.\n///\nclass PlatformView {\n public:\n  //----------------------------------------------------------------------------\n  /// @brief      Used to forward events from the platform view to interested\n  ///             subsystems. This forwarding is done by the shell which sets\n  ///             itself up as the delegate of the platform view.\n  ///\n  class Delegate {\n   public:\n    using KeyDataResponse = std::function<void(bool)>;\n\n    //--------------------------------------------------------------------------\n    /// @brief      Notifies the delegate that the platform view was created\n    ///             with the given render surface. This surface is platform\n    ///             (iOS, Android) and client-rendering API (OpenGL, Software,\n    ///             Metal, Vulkan) specific. This is usually a sign to the\n    ///             rasterizer to set up and begin rendering to that surface.\n    ///\n    ///\n    virtual void OnPlatformViewCreated() = 0;\n\n    //--------------------------------------------------------------------------\n    /// @brief      Notifies the delegate that the platform view was destroyed.\n    ///             This is usually a sign to the rasterizer to suspend\n    ///             rendering a previously configured surface and collect any\n    ///             intermediate resources.\n    ///\n    virtual void OnPlatformViewDestroyed() = 0;\n\n    //--------------------------------------------------------------------------\n    /// @brief      Notifies the delegate that the platform needs to schedule a\n    ///             frame to regenerate the layer tree and redraw the surface.\n    ///\n    virtual void OnPlatformViewScheduleFrame() = 0;\n\n    //--------------------------------------------------------------------------\n    /// @brief      Notifies the delegate that the specified callback needs to\n    ///             be invoked after the rasterizer is done rendering the next\n    ///             frame. This callback will be called on the render thread and\n    ///             it is caller responsibility to perform any re-threading as\n    ///             necessary. Due to the asynchronous nature of rendering in\n    ///             Flutter, embedders usually add a placeholder over the\n    ///             contents in which Flutter is going to render when Flutter is\n    ///             first initialized. This callback may be used as a signal to\n    ///             remove that placeholder.\n    ///\n    /// @attention  The callback will be invoked on the render thread and not\n    ///             the calling thread.\n    ///\n    /// @param[in]  closure  The callback to execute on the next frame.\n    ///\n    virtual void OnPlatformViewSetNextFrameCallback(\n        const fml::closure& closure) = 0;\n\n    //--------------------------------------------------------------------------\n    /// @brief      Notifies the delegate the viewport metrics of the platform\n    ///             view have been updated. The rasterizer will need to be\n    ///             reconfigured to render the frame in the updated viewport\n    ///             metrics.\n    ///\n    /// @param[in]  metrics  The updated viewport metrics.\n    ///\n    virtual void OnPlatformViewSetViewportMetrics(\n        const ViewportMetrics& metrics) = 0;\n\n    //--------------------------------------------------------------------------\n    /// @brief      Notifies the delegate that the platform view has encountered\n    ///             a pointer event. This pointer event needs to be forwarded to\n    ///             the running root isolate hosted by the engine on the UI\n    ///             thread.\n    ///\n    /// @param[in]  packet  The pointer data packet containing multiple pointer\n    ///                     events.\n    ///\n    virtual bool OnPlatformViewDispatchPointerDataPacket(\n        std::unique_ptr<PointerDataPacket> packet) = 0;\n\n    //--------------------------------------------------------------------------\n    /// @brief      Notifies the delegate that the platform view has encountered\n    ///             a key event. This key event and the callback needs to be\n    ///             forwarded to the running root isolate hosted by the engine\n    ///             on the UI thread.\n    ///\n    /// @param[in]  key_event    The key data containing one key event.\n    /// @param[in]  callback  Called when the framework has decided whether\n    ///                       to handle this key data.\n    ///\n    virtual void OnPlatformViewDispatchKeyEvent(\n        std::unique_ptr<clay::KeyEvent> key_event,\n        std::function<void(bool /* handled */)> callback) = 0;\n\n    virtual void OnPlatformViewSendKeyboardEvent(\n        std::unique_ptr<clay::KeyEvent> key_event) = 0;\n\n    virtual void OnPlatformViewPerformEditorAction(\n        clay::KeyboardAction action_code) = 0;\n\n    virtual void OnPlatformViewDeleteSurroundingText(int before_length,\n                                                     int after_length) = 0;\n\n    virtual void OnPlatformViewOnEnterForeground() = 0;\n    virtual void OnPlatformViewOnEnterBackground() = 0;\n\n    virtual void OnPlatformViewSetDefaultFocusRingEnabled(bool enable) = 0;\n    virtual void OnPlatformViewSetPerformanceOverlayEnabled(bool enable) = 0;\n    virtual void OnDefaultOverflowVisibleChanged(bool visible) = 0;\n    virtual void OnExposurePropsChanged(int freq,\n                                        bool enable_dump_element_tree) = 0;\n\n#ifdef ENABLE_ACCESSIBILITY\n    virtual void OnPlatformViewDispatchSemanticsAction(int virtual_view_id,\n                                                       int action) = 0;\n#endif\n\n    //--------------------------------------------------------------------------\n    /// @brief      Called by the platform view on the platform thread to get\n    ///             the settings object associated with the platform view\n    ///             instance.\n    ///\n    /// @return     The settings.\n    ///\n    virtual const Settings& OnPlatformViewGetSettings() const = 0;\n  };\n\n  //----------------------------------------------------------------------------\n  /// @brief      Creates a platform view with the specified delegate and task\n  ///             runner. The base class by itself does not do much but is\n  ///             suitable for use in test environments where full platform\n  ///             integration may not be necessary. The platform view may only\n  ///             be created, accessed and destroyed on the platform task\n  ///             runner.\n  ///\n  /// @param      delegate      The delegate. This is typically the shell.\n  /// @param[in]  task_runners  The task runners used by this platform view.\n  ///\n  PlatformView(std::shared_ptr<clay::ServiceManager> service_manager,\n               Delegate& delegate, const TaskRunners& task_runners);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Destroys the platform view. The platform view is owned by the\n  ///             shell and will be destroyed by the same on the platform tasks\n  ///             runner.\n  ///\n  virtual ~PlatformView();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Used by embedders to specify the updated viewport metrics. In\n  ///             response to this call, on the raster thread, the rasterizer\n  ///             may need to be reconfigured to the updated viewport\n  ///             dimensions. On the UI thread, the framework may need to start\n  ///             generating a new frame for the updated viewport metrics as\n  ///             well.\n  ///\n  /// @param[in]  metrics  The updated viewport metrics.\n  ///\n  void SetViewportMetrics(const ViewportMetrics& metrics);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Used by embedders to notify the shell that a platform view\n  ///             has been created. This notification is used to create a\n  ///             rendering surface and pick the client rendering API to use to\n  ///             render into this surface. No frames will be scheduled or\n  ///             rendered before this call. The surface must remain valid till\n  ///             the corresponding call to NotifyDestroyed.\n  ///\n  void NotifyCreated();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Used by embedders to notify the shell that the platform view\n  ///             has been destroyed. This notification used to collect the\n  ///             rendering surface and all associated resources. Frame\n  ///             scheduling is also suspended.\n  ///\n  /// @attention  Subclasses may choose to override this method to perform\n  ///             platform specific functions. However, they must call the base\n  ///             class method at some point in their implementation.\n  ///\n  virtual void NotifyDestroyed();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Used by embedders to schedule a frame. In response to this\n  ///             call, the framework may need to start generating a new frame.\n  ///\n  void ScheduleFrame();\n\n  //--------------------------------------------------------------------------\n  /// @brief      Returns a platform-specific PointerDataDispatcherMaker so the\n  ///             `Engine` can construct the PointerDataPacketDispatcher based\n  ///             on platforms.\n  virtual PointerDataDispatcherMaker GetDispatcherMaker();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Returns a weak pointer to the platform view. Since the\n  ///             platform view may only be created, accessed and destroyed\n  ///             on the platform thread, any access to the platform view\n  ///             from a non-platform task runner needs a weak pointer to\n  ///             the platform view along with a reference to the platform\n  ///             task runner. A task must be posted to the platform task\n  ///             runner with the weak pointer captured in the same. The\n  ///             platform view method may only be called in the posted task\n  ///             once the weak pointer validity has been checked. This\n  ///             method is used by callers to obtain that weak pointer.\n  ///\n  /// @return     The weak pointer to the platform view.\n  ///\n  fml::WeakPtr<PlatformView> GetWeakPtr() const;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Gives embedders a chance to react to a \"cold restart\" of the\n  ///             running isolate. The default implementation of this method\n  ///             does nothing.\n  ///\n  ///             While a \"hot restart\" patches a running isolate, a \"cold\n  ///             restart\" restarts the root isolate in a running shell.\n  ///\n  virtual void OnPreEngineRestart() const;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Sets a callback that gets executed when the rasterizer renders\n  ///             the next frame. Due to the asynchronous nature of\n  ///             rendering in Flutter, embedders usually add a placeholder\n  ///             over the contents in which Flutter is going to render when\n  ///             Flutter is first initialized. This callback may be used as\n  ///             a signal to remove that placeholder. The callback is\n  ///             executed on the render task runner and not the platform\n  ///             task runner. It is the embedder's responsibility to\n  ///             re-thread as necessary.\n  ///\n  /// @attention  The callback is executed on the render task runner and not the\n  ///             platform task runner. Embedders must re-thread as necessary.\n  ///\n  /// @param[in]  closure  The callback to execute on the render thread when the\n  ///                      next frame gets rendered.\n  ///\n  void SetNextFrameCallback(const fml::closure& closure);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Dispatches pointer events from the embedder to the\n  ///             framework. Each pointer data packet may contain multiple\n  ///             pointer input events. Each call to this method wakes up\n  ///             the UI thread.\n  ///\n  /// @param[in]  packet  The pointer data packet to dispatch to the framework.\n  ///\n  bool DispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet);\n  //----------------------------------------------------------------------------\n  /// @brief      Dispatches key events from the embedder to the framework. Each\n  ///             key data packet contains one physical event and multiple\n  ///             logical key events. Each call to this method wakes up the UI\n  ///             thread.\n  ///\n  /// @param[in]  key_event  The key data to dispatch to the framework.\n  ///\n  void DispatchKeyEvent(std::unique_ptr<clay::KeyEvent> key_event,\n                        Delegate::KeyDataResponse callback);\n\n  void DispatchKeyDataPacket(std::unique_ptr<clay::KeyDataPacket> packet,\n                             Delegate::KeyDataResponse callback);\n\n  //--------------------------------------------------------------------------\n  /// @brief      Directly invokes platform-specific APIs to compute the\n  ///             locale the platform would have natively resolved to.\n  ///\n  /// @param[in]  supported_locale_data  The vector of strings that represents\n  ///                                    the locales supported by the app.\n  ///                                    Each locale consists of three\n  ///                                    strings: languageCode, countryCode,\n  ///                                    and scriptCode in that order.\n  ///\n  /// @return     A vector of 3 strings languageCode, countryCode, and\n  ///             scriptCode that represents the locale selected by the\n  ///             platform. Empty strings mean the value was unassigned. Empty\n  ///             vector represents a null locale.\n  ///\n  virtual std::unique_ptr<std::vector<std::string>>\n  ComputePlatformResolvedLocales(\n      const std::vector<std::string>& supported_locale_data);\n\n  virtual void ShowSoftInput(int type, int action) {}\n  virtual void HideSoftInput() {}\n\n  virtual std::string ShouldInterceptUrl(const std::string& origin_url,\n                                         bool should_decode) {\n    return origin_url;\n  }\n\n  virtual std::shared_ptr<clay::ResourceLoaderIntercept>\n  GetResourceLoaderIntercept() {\n    return nullptr;\n  }\n\n  virtual void PostInvalidate() {}\n\n  virtual void SubmitFrameFuture(std::future<bool> future) {}\n\n  virtual void UpdateRasterCacheInfo(const std::string& result) {}\n  virtual void SetClipboardData(const std::u16string& data) {}\n  virtual std::u16string GetClipboardData() { return std::u16string(); }\n  virtual void SetTextInputClient(int client_id, const char* input_action,\n                                  const char* input_type) {}\n  virtual void ClearTextInputClient() {}\n  virtual void SetEditableTransform(const float transform_matrix[16]) {}\n  virtual void SetEditingState(uint64_t selection_base,\n                               uint64_t composing_extent,\n                               const std::string& selection_affinity,\n                               const std::string& text,\n                               bool selection_directional,\n                               uint64_t selection_extent,\n                               uint64_t composing_base) {}\n  virtual void SetCaretRect(float x, float y, float width, float height) {}\n  virtual void setMarkedTextRect(float x, float y, float width, float height) {}\n  virtual void ShowTextInput() {}\n  virtual void HideTextInput() {}\n  virtual std::string InputFilter(const std::string& input,\n                                  const std::string& pattern) {\n    return input;\n  };\n\n  virtual void WindowMove() {}\n  virtual void ActivateSystemCursor(int type, const std::string& path) {}\n\n  const TaskRunners& GetTaskRunners() const { return task_runners_; }\n\n  virtual void OnTimingSetup(\n      const std::unordered_map<std::string, int64_t>& timing) {}\n  virtual void OnTimingUpdate(\n      const std::unordered_map<std::string, int64_t>& timing,\n      const std::string& flag) {}\n\n  virtual void ReleasePlatformInstanceManager() {}\n\n  virtual void UpdateRootSize(int32_t width, int32_t height) {}\n#ifdef ENABLE_ACCESSIBILITY\n  void DispatchClaySemanticsAction(int virtual_view_id, int action);\n  virtual void UpdateSemantics(const clay::SemanticsUpdateNodes& update_nodes) {\n  }\n#endif\n\n  const std::shared_ptr<clay::ServiceManager>& GetServiceManager() const {\n    return service_manager_;\n  }\n\n  // If the output surface is thread safe, it's safe to destruct\n  // independently, otherwise the shell must wait for platform surface teardown\n  // before destructing platform view.\n  // The default implementation returns false.\n  virtual bool IsOutputSurfaceThreadSafe() const { return false; }\n\n  virtual fml::RefPtr<OutputSurface> GetOutputSurface() const = 0;\n\n protected:\n  const std::shared_ptr<clay::ServiceManager> service_manager_;\n  PlatformView::Delegate& delegate_;\n  const TaskRunners task_runners_;\n  PointerDataPacketConverter pointer_data_packet_converter_;\n  fml::WeakPtrFactory<PlatformView> weak_factory_;  // Must be the last member.\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(PlatformView);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_PLATFORM_VIEW_H_\n"
  },
  {
    "path": "clay/shell/common/pointer_data_dispatcher.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/pointer_data_dispatcher.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nPointerDataDispatcher::~PointerDataDispatcher() = default;\nDefaultPointerDataDispatcher::~DefaultPointerDataDispatcher() = default;\n\nSmoothPointerDataDispatcher::SmoothPointerDataDispatcher(\n    const Delegate& delegate)\n    : DefaultPointerDataDispatcher(delegate), weak_factory_(this) {}\nSmoothPointerDataDispatcher::~SmoothPointerDataDispatcher() = default;\n\nvoid DefaultPointerDataDispatcher::DispatchPacket(\n    std::unique_ptr<PointerDataPacket> packet, uint64_t trace_flow_id) {\n  TRACE_EVENT(\"clay\", \"DefaultPointerDataDispatcher::DispatchPacket\");\n  delegate_.DoDispatchPacket(std::move(packet), trace_flow_id);\n}\n\nvoid SmoothPointerDataDispatcher::DispatchPacket(\n    std::unique_ptr<PointerDataPacket> packet, uint64_t trace_flow_id) {\n  TRACE_EVENT(\"clay\", \"SmoothPointerDataDispatcher::DispatchPacket\");\n\n  if (is_pointer_data_in_progress_) {\n    if (pending_packet_ != nullptr) {\n      DispatchPendingPacket();\n    }\n    pending_packet_ = std::move(packet);\n    pending_trace_flow_id_ = trace_flow_id;\n  } else {\n    FML_DCHECK(pending_packet_ == nullptr);\n    DefaultPointerDataDispatcher::DispatchPacket(std::move(packet),\n                                                 trace_flow_id);\n  }\n  is_pointer_data_in_progress_ = true;\n  ScheduleSecondaryVsyncCallback();\n}\n\nvoid SmoothPointerDataDispatcher::ScheduleSecondaryVsyncCallback() {\n  delegate_.ScheduleSecondaryVsyncCallback(\n      reinterpret_cast<uintptr_t>(this),\n      [dispatcher = weak_factory_.GetWeakPtr()]() {\n        if (dispatcher && dispatcher->is_pointer_data_in_progress_) {\n          if (dispatcher->pending_packet_ != nullptr) {\n            dispatcher->DispatchPendingPacket();\n          } else {\n            dispatcher->is_pointer_data_in_progress_ = false;\n          }\n        }\n      });\n}\n\nvoid SmoothPointerDataDispatcher::DispatchPendingPacket() {\n  FML_DCHECK(pending_packet_ != nullptr);\n  FML_DCHECK(is_pointer_data_in_progress_);\n  DefaultPointerDataDispatcher::DispatchPacket(std::move(pending_packet_),\n                                               pending_trace_flow_id_);\n  pending_packet_ = nullptr;\n  pending_trace_flow_id_ = -1;\n  ScheduleSecondaryVsyncCallback();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/pointer_data_dispatcher.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_POINTER_DATA_DISPATCHER_H_\n#define CLAY_SHELL_COMMON_POINTER_DATA_DISPATCHER_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/ui/window/pointer_data_packet.h\"\n\nnamespace clay {\n\nclass PointerDataDispatcher;\n\n//------------------------------------------------------------------------------\n/// The `Engine` pointer data dispatcher that forwards the packet received from\n/// `PlatformView::DispatchPointerDataPacket` on the platform thread, to\n/// `Window::DispatchPointerDataPacket` on the UI thread.\n///\n/// This class is used to filter the packets so the Flutter framework on the UI\n/// thread will receive packets with some desired properties. See\n/// `SmoothPointerDataDispatcher` for an example which filters irregularly\n/// delivered packets, and dispatches them in sync with the VSYNC signal.\n///\n/// This object will be owned by the engine because it relies on the engine's\n/// `Animator` (which owns `VsyncWaiter`) and `RuntimeController` to do the\n/// filtering. This object is currently designed to be only called from the UI\n/// thread (no thread safety is guaranteed).\n///\n/// The `PlatformView` decides which subclass of `PointerDataDispatcher` is\n/// constructed by sending a `PointerDataDispatcherMaker` to the engine's\n/// constructor in `Shell::CreateShellOnPlatformThread`. This is needed because:\n///   (1) Different platforms (e.g., Android, iOS) have different dispatchers\n///       so the decision has to be made per `PlatformView`.\n///   (2) The `PlatformView` can only be accessed from the PlatformThread while\n///       this class (as owned by engine) can only be accessed in the UI thread.\n///       Hence `PlatformView` creates a `PointerDataDispatchMaker` on the\n///       platform thread, and sends it to the UI thread for the final\n///       construction of the `PointerDataDispatcher`.\nclass PointerDataDispatcher {\n public:\n  /// The interface for Engine to implement.\n  class Delegate {\n   public:\n    /// Actually dispatch the packet using Engine's `animator_` and\n    /// `runtime_controller_`.\n    virtual void DoDispatchPacket(std::unique_ptr<PointerDataPacket> packet,\n                                  uint64_t trace_flow_id) const = 0;\n\n    //--------------------------------------------------------------------------\n    /// @brief    Schedule a secondary callback to be executed right after the\n    ///           main `VsyncWaiter::AsyncWaitForVsync` callback (which is added\n    ///           by `Animator::RequestFrame`).\n    ///\n    ///           Like the callback in `AsyncWaitForVsync`, this callback is\n    ///           only scheduled to be called once per |id|, and it will be\n    ///           called in the UI thread. If there is no AsyncWaitForVsync\n    ///           callback (`Animator::RequestFrame` is not called), this\n    ///           secondary callback will still be executed at vsync.\n    ///\n    ///           This callback is used to provide the vsync signal needed by\n    ///           `SmoothPointerDataDispatcher`, and for `Animator` input flow\n    ///           events.\n    virtual void ScheduleSecondaryVsyncCallback(\n        uintptr_t id, const fml::closure& callback) const = 0;\n  };\n\n  //----------------------------------------------------------------------------\n  /// @brief      Signal that `PlatformView` has a packet to be dispatched.\n  ///\n  /// @param[in]  packet             The `PointerDataPacket` to be dispatched.\n  /// @param[in]  trace_flow_id      The id for `Animator::EnqueueTraceFlowId`.\n  virtual void DispatchPacket(std::unique_ptr<PointerDataPacket> packet,\n                              uint64_t trace_flow_id) = 0;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Default destructor.\n  virtual ~PointerDataDispatcher();\n};\n\n//------------------------------------------------------------------------------\n/// The default dispatcher that forwards the packet without any modification.\n///\nclass DefaultPointerDataDispatcher : public PointerDataDispatcher {\n public:\n  explicit DefaultPointerDataDispatcher(const Delegate& delegate)\n      : delegate_(delegate) {}\n\n  // |PointerDataDispatcher|\n  void DispatchPacket(std::unique_ptr<PointerDataPacket> packet,\n                      uint64_t trace_flow_id) override;\n\n  virtual ~DefaultPointerDataDispatcher();\n\n protected:\n  const Delegate& delegate_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(DefaultPointerDataDispatcher);\n};\n\n//------------------------------------------------------------------------------\n/// A dispatcher that may temporarily store and defer the last received\n/// PointerDataPacket if multiple packets are received in one VSYNC. The\n/// deferred packet will be sent in the next vsync in order to smooth out the\n/// events. This filters out irregular input events delivery to provide a smooth\n/// scroll on iPhone X/Xs.\n///\n/// It works as follows:\n///\n/// When `DispatchPacket` is called while a previous pointer data dispatch is\n/// still in progress (its frame isn't finished yet), it means that an input\n/// event is delivered to us too fast. That potentially means a later event will\n/// be too late which could cause the missing of a frame. Hence we'll cache it\n/// in `pending_packet_` for the next frame to smooth it out.\n///\n/// If the input event is sent to us regularly at the same rate of VSYNC (say\n/// at 60Hz), this would be identical to `DefaultPointerDataDispatcher` where\n/// `runtime_controller_->DispatchPointerDataPacket` is always called right\n/// away. That's because `is_pointer_data_in_progress_` will always be false\n/// when `DispatchPacket` is called since it will be cleared by the end of a\n/// frame through `ScheduleSecondaryVsyncCallback`. This is the case for all\n/// Android/iOS devices before iPhone X/XS.\n///\n/// If the input event is irregular, but with a random latency of no more than\n/// one frame, this would guarantee that we'll miss at most 1 frame. Without\n/// this, we could miss half of the frames.\n///\n/// If the input event is delivered at a higher rate than that of VSYNC, this\n/// would at most add a latency of one event delivery. For example, if the\n/// input event is delivered at 120Hz (this is only true for iPad pro, not even\n/// iPhone X), this may delay the handling of an input event by 8ms.\n///\n/// The assumption of this solution is that the sampling itself is still\n/// regular. Only the event delivery is allowed to be irregular. So far this\n/// assumption seems to hold on all devices. If it's changed in the future,\n/// we'll need a different solution.\nclass SmoothPointerDataDispatcher : public DefaultPointerDataDispatcher {\n public:\n  explicit SmoothPointerDataDispatcher(const Delegate& delegate);\n\n  // |PointerDataDispatcher|\n  void DispatchPacket(std::unique_ptr<PointerDataPacket> packet,\n                      uint64_t trace_flow_id) override;\n\n  virtual ~SmoothPointerDataDispatcher();\n\n private:\n  void DispatchPendingPacket();\n  void ScheduleSecondaryVsyncCallback();\n\n  // If non-null, this will be a pending pointer data packet for the next frame\n  // to consume. This is used to smooth out the irregular drag events delivery.\n  // See also `DispatchPointerDataPacket`.\n  std::unique_ptr<PointerDataPacket> pending_packet_;\n  int pending_trace_flow_id_ = -1;\n  bool is_pointer_data_in_progress_ = false;\n\n  // WeakPtrFactory must be the last member.\n  fml::WeakPtrFactory<SmoothPointerDataDispatcher> weak_factory_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(SmoothPointerDataDispatcher);\n};\n\n//--------------------------------------------------------------------------\n/// @brief      Signature for constructing PointerDataDispatcher.\n///\n/// @param[in]  delegate      the `Flutter::Engine`\n///\nusing PointerDataDispatcherMaker =\n    std::function<std::unique_ptr<PointerDataDispatcher>(\n        PointerDataDispatcher::Delegate&)>;\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_POINTER_DATA_DISPATCHER_H_\n"
  },
  {
    "path": "clay/shell/common/pointer_data_to_event.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/pointer_data_to_event.h\"\n\n#include <cstring>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/window/pointer_data.h\"\n\nnamespace clay {\n\nnamespace {\n\nvoid CopyToEvent(clay::PointerEvent* dest, const clay::PointerData& raw_data) {\n  if (raw_data.kind == clay::PointerData::DeviceKind::kMouse) {\n    dest->device = clay::PointerEvent::DeviceType::kMouse;\n  } else if (raw_data.kind == clay::PointerData::DeviceKind::kTrackpad) {\n    dest->device = clay::PointerEvent::DeviceType::kTrackpad;\n  } else {\n    // TODO(Chenfeng Pan): We already have differentiated *mouse* and *trackpad*\n    // in platform layer, which means raw_data.kind would be\n    // clay::PointerData::DeviceKind::kTrackpad when using trackpad. However\n    // setting PointerEvent.device to PointerEvent::DeviceType::kTrackpad need\n    // to refactor reporting events in page_view.cc and consuming events in\n    // gesture_manager.cc. Now we treat *trackpad* as *touch* temporarily to\n    // statisfy functionality and reporting.\n    dest->device = clay::PointerEvent::DeviceType::kTouch;\n  }\n  dest->embedder_id = raw_data.embedder_id;\n  dest->timestamp = raw_data.time_stamp;\n  dest->pointer_id = raw_data.pointer_identifier;\n  dest->device_id = raw_data.device;\n  dest->position = clay::FloatPoint(raw_data.physical_x, raw_data.physical_y);\n  dest->delta =\n      clay::FloatSize(raw_data.physical_delta_x, raw_data.physical_delta_y);\n  // TODO(Xietong): the value for down only valid for touch. If we need to\n  // support mouse, this should be updated.\n  dest->down = raw_data.change == clay::PointerData::Change::kDown ||\n               raw_data.change == clay::PointerData::Change::kMove;\n  dest->buttons = raw_data.buttons;\n  dest->pressure = raw_data.pressure;\n  dest->pressure_min = raw_data.pressure_min;\n  dest->pressure_max = raw_data.pressure_max;\n  dest->distance = raw_data.distance;\n  dest->distance_max = raw_data.distance_max;\n  dest->size = raw_data.size;\n  dest->radius_major = raw_data.radius_major;\n  dest->radius_minor = raw_data.radius_minor;\n  dest->radius_min = raw_data.radius_min;\n  dest->radius_max = raw_data.radius_max;\n  dest->orientation = raw_data.orientation;\n  dest->tilt = raw_data.tilt;\n  dest->platform_data = raw_data.platformData;\n  dest->synthesized = false;\n  dest->scroll_delta_x = raw_data.scroll_delta_x;\n  dest->scroll_delta_y = raw_data.scroll_delta_y;\n  dest->pan = clay::FloatPoint(raw_data.pan_x, raw_data.pan_y);\n  dest->pan_delta = clay::FloatSize(raw_data.pan_delta_x, raw_data.pan_delta_y);\n  dest->scale = raw_data.scale;\n  dest->rotation = raw_data.rotation;\n  dest->is_precise_scroll = static_cast<bool>(raw_data.is_precise_scroll);\n  dest->source = raw_data.source;\n}\n\n}  // namespace\n\nstd::vector<clay::PointerEvent> GetEventsFromPointerDataPacket(\n    const clay::PointerDataPacket* packet) {\n  constexpr size_t data_size = sizeof(clay::PointerData);\n  FML_DCHECK(packet && packet->data().size() % data_size == 0);\n\n  std::vector<clay::PointerEvent> events;\n\n  clay::PointerData raw_data;\n  for (size_t i = 0; i < packet->data().size() / data_size; i++) {\n    memcpy(&raw_data, packet->data().data() + data_size * i, data_size);\n    if (raw_data.signal_kind == PointerData::SignalKind::kNone) {\n      switch (raw_data.change) {\n        case clay::PointerData::Change::kDown: {\n          events.emplace_back(clay::PointerEvent::EventType::kDownEvent);\n          clay::PointerEvent& event = events.back();\n          CopyToEvent(&event, raw_data);\n          event.down = true;\n          event.distance = 0.0;\n        } break;\n\n        case clay::PointerData::Change::kMove: {\n          events.emplace_back(clay::PointerEvent::EventType::kMoveEvent);\n          clay::PointerEvent& event = events.back();\n\n          CopyToEvent(&event, raw_data);\n          event.down = true;\n          event.distance = 0.0;\n        } break;\n\n        case clay::PointerData::Change::kUp: {\n          events.emplace_back(clay::PointerEvent::EventType::kUpEvent);\n          clay::PointerEvent& event = events.back();\n          CopyToEvent(&event, raw_data);\n          event.down = false;\n        } break;\n\n        case clay::PointerData::Change::kCancel: {\n          events.emplace_back(clay::PointerEvent::EventType::kCancel);\n          clay::PointerEvent& event = events.back();\n\n          CopyToEvent(&event, raw_data);\n          event.down = false;\n        } break;\n\n        case clay::PointerData::Change::kAdd:\n          break;\n        case clay::PointerData::Change::kHover: {\n          // on iOS, a kAdd/kHover event could be synthesized.\n          // Ignore it for now.\n          auto& event =\n              events.emplace_back(clay::PointerEvent::EventType::kHoverEvent);\n          CopyToEvent(&event, raw_data);\n        } break;\n\n        case clay::PointerData::Change::kRemove: {\n          auto& event =\n              events.emplace_back(clay::PointerEvent::EventType::kCancel);\n          CopyToEvent(&event, raw_data);\n          event.position.SetX(0.f);\n          event.position.SetY(0.f);\n          event.down = false;\n        } break;\n        case clay::PointerData::Change::kPanZoomStart: {\n          events.emplace_back(\n              clay::PointerEvent::EventType::kPanZoomStartEvent);\n          clay::PointerEvent& event = events.back();\n\n          CopyToEvent(&event, raw_data);\n          event.down = false;\n        } break;\n        case clay::PointerData::Change::kPanZoomUpdate: {\n          events.emplace_back(\n              clay::PointerEvent::EventType::kPanZoomUpdateEvent);\n          clay::PointerEvent& event = events.back();\n\n          CopyToEvent(&event, raw_data);\n          event.down = false;\n        } break;\n        case clay::PointerData::Change::kPanZoomEnd: {\n          events.emplace_back(clay::PointerEvent::EventType::kPanZoomEndEvent);\n          clay::PointerEvent& event = events.back();\n\n          CopyToEvent(&event, raw_data);\n          event.down = false;\n        } break;\n        default:\n          FML_LOG(ERROR) << \"PointerData Change is not supported: \"\n                         << static_cast<int64_t>(raw_data.change);\n          break;\n      }\n    } else if (raw_data.signal_kind == PointerData::SignalKind::kScroll) {\n      events.emplace_back(clay::PointerEvent::EventType::kSignalEvent);\n      clay::PointerEvent& event = events.back();\n      event.signal_kind = clay::PointerEvent::SignalKind::kScroll;\n      CopyToEvent(&event, raw_data);\n    } else {\n      // Ignore unknown signal kind.\n    }\n  }\n  return events;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/pointer_data_to_event.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_POINTER_DATA_TO_EVENT_H_\n#define CLAY_SHELL_COMMON_POINTER_DATA_TO_EVENT_H_\n\n#include <vector>\n\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/window/pointer_data_packet.h\"\n\nnamespace clay {\n\nstd::vector<clay::PointerEvent> GetEventsFromPointerDataPacket(\n    const clay::PointerDataPacket* packet);\n\n}  // namespace clay\n#endif  // CLAY_SHELL_COMMON_POINTER_DATA_TO_EVENT_H_\n"
  },
  {
    "path": "clay/shell/common/rasterizer.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/rasterizer.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <memory>\n#include <mutex>\n#include <utility>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"clay/flow/compositor_context.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/fml/base64.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/shell/common/serialization_callbacks.h\"\n#include \"clay/ui/common/frame_timing_collector.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/resource/gpu_resource_cache.h\"\n\nnamespace clay {\n\n// The rasterizer will tell Skia to purge cached resources that have not been\n// used within this interval.\n#ifndef ENABLE_SKITY\nstatic constexpr std::chrono::milliseconds kSkiaCleanupExpiration(15000);\n#endif  // ENABLE_SKITY\n\nRasterizer::Rasterizer(std::shared_ptr<clay::ServiceManager> service_manager)\n    : service_manager_(service_manager),\n      platform_const_service_(\n          service_manager->GetService<PlatformConstService>()),\n      raster_frame_service_(service_manager->GetService<RasterFrameService>()),\n      instrumentation_service_(\n          service_manager->GetService<InstrumentationService>()),\n      compositor_service_(service_manager->GetService<CompositorService>()),\n      compositor_context_(std::make_unique<clay::CompositorContext>(*this)),\n      user_override_resource_cache_bytes_(false),\n      raster_time_(std::make_shared<FixedRefreshRateStopwatch>()),\n      frame_total_time_(std::make_shared<FixedRefreshRateStopwatch>()),\n      weak_factory_(this) {\n  FML_DCHECK(compositor_context_);\n}\n\nRasterizer::~Rasterizer() {\n  // Some gpu objects (like GrDirectContext lives with PageView) may be unrefed\n  // after last `Drain()` in `Rasterizer::Teardown()`. So drain again.\n  if (unref_queue_) {\n    unref_queue_->Drain();\n    // Initiate StartAutoPendingDrain after the Rasterizer destructor.\n    // This is necessary because some objects containing GPUObject types are\n    // reset after the Rasterizer is destructed. For instance, the LayerTree\n    // member of the RasterService is reset post-destruction. Additionally, this\n    // step accounts for Images that are referenced across multiple pages,\n    // ensuring that all associated resources are properly managed and released.\n    unref_queue_->StartAutoPendingDrain();\n  }\n}\n\nfml::WeakPtr<Rasterizer> Rasterizer::GetWeakPtr() const {\n  return weak_factory_.GetWeakPtr();\n}\n\nvoid Rasterizer::Setup(std::unique_ptr<Surface> surface) {\n  surface_ = std::move(surface);\n\n  if (max_cache_bytes_.has_value()) {\n    SetResourceCacheMaxBytes(max_cache_bytes_.value(),\n                             user_override_resource_cache_bytes_);\n  }\n\n  auto context_switch = surface_->MakeRenderContextCurrent();\n  if (context_switch->GetResult()) {\n    compositor_context_->OnGrContextCreated();\n  }\n}\n\nvoid Rasterizer::Teardown() {\n  if (surface_) {\n    auto context_switch = surface_->MakeRenderContextCurrent();\n    if (context_switch->GetResult()) {\n      compositor_context_->OnGrContextDestroyed();\n      if ([[maybe_unused]] auto* context = surface_->GetContext()) {\n#ifndef ENABLE_SKITY\n        context->purgeUnlockedResources(/*scratchResourcesOnly=*/false);\n#endif  // ENABLE_SKITY\n        if (unref_queue_) {\n          unref_queue_->Drain();\n        }\n      }\n    }\n    surface_.reset();\n  }\n\n  last_layer_tree_.reset();\n}\n\nvoid Rasterizer::CleanForRecycle() {\n  user_override_resource_cache_bytes_ = false;\n  last_layer_tree_.reset();\n  compositor_context_ = std::make_unique<clay::CompositorContext>(*this);\n  if (unref_queue_) {\n    unref_queue_->Drain();\n    unref_queue_->StartAutoPendingDrain();\n  }\n}\n\nvoid Rasterizer::NotifyLowMemoryWarning() const {\n  if (!surface_) {\n    FML_DLOG(INFO)\n        << \"Rasterizer::NotifyLowMemoryWarning called with no surface.\";\n    return;\n  }\n  auto context = surface_->GetContext();\n  if (!context) {\n    FML_DLOG(INFO)\n        << \"Rasterizer::NotifyLowMemoryWarning called with no GrContext.\";\n    return;\n  }\n  auto context_switch = surface_->MakeRenderContextCurrent();\n  if (!context_switch->GetResult()) {\n    return;\n  }\n#ifndef ENABLE_SKITY\n  context->performDeferredCleanup(std::chrono::milliseconds(0));\n#endif  // ENABLE_SKITY\n  compositor_context_->raster_cache().Clear();\n}\n\nstd::shared_ptr<DrawableImageRegistry> Rasterizer::GetDrawableImageRegistry() {\n  return compositor_context_->drawable_image_registry();\n}\n\nclay::LayerTree* Rasterizer::GetLastLayerTree() {\n  return last_layer_tree_.get();\n}\n\nRasterStatus Rasterizer::DrawLastLayerTree(\n    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) {\n  if (!last_layer_tree_ || !surface_) {\n    return RasterStatus::kFailed;\n  }\n  RasterStatus raster_status = RasterStatus::kFailed;\n  if (!last_layer_tree_->DoAnimations()) {\n    raster_frame_service_->RequestRasterFrame();\n  }\n  raster_status = DrawToSurface(*frame_timings_recorder, *last_layer_tree_);\n\n  if (raster_status == RasterStatus::kSuccess) {\n    frame_timings_recorder->RecordLastDrawVsyncTime(\n        frame_timings_recorder->GetVsyncStartTime());\n    frame_timings_recorder->RecordVsyncSequenceId(\n        frame_timings_recorder->GetVsyncSequenceId());\n  }\n  return raster_status;\n}\n\nRasterStatus Rasterizer::Draw(std::shared_ptr<LayerTree> layer_tree,\n                              std::unique_ptr<FrameTimingsRecorder> recorder,\n                              LayerTreeDiscardCallback discard_callback,\n                              bool report_instrumentation) {\n  TRACE_EVENT(\"clay\", \"GPURasterizer::Draw\");\n  fml::ScopedCleanupClosure instrumentation;\n  if (report_instrumentation) {\n    instrumentation.SetClosure([this] {\n      instrumentation_service_.Act([](auto& impl) {\n        impl.GetFrameTimingCollector()\n            ->InsertFocusChangedUntilFirstRasterFinish();\n        impl.GetFrameTimingCollector()->SetReceivedFocusTime(\n            0, clay::FocusManager::Direction::kUnknown);\n      });\n    });\n  }\n\n  FML_DCHECK(service_manager_->GetTaskRunners()\n                 ->SelectTaskRunner<clay::Owner::kRaster>()\n                 ->RunsTasksOnCurrentThread());\n\n  RasterStatus raster_status = RasterStatus::kFailed;\n  if (discard_callback(*layer_tree.get())) {\n    raster_status = RasterStatus::kDiscarded;\n  } else {\n    raster_status = DoDraw(std::move(recorder), std::move(layer_tree));\n  }\n\n  return raster_status;\n}\n\nfml::RefPtr<PaintImage> Rasterizer::MakeRasterSnapshot(\n    std::unique_ptr<LayerTree> layer_tree) {\n  return TakeScreenshot(std::move(layer_tree), surface_.get(),\n                        compositor_context_.get());\n}\n\nfml::RefPtr<PaintImage> Rasterizer::MakeRasterSnapshot(GrPicturePtr picture,\n                                                       skity::Vec2 size) {\n  return TakeScreenshot(picture, size);\n}\n\nfml::Milliseconds Rasterizer::GetFrameBudget() const {\n  return platform_const_service_->GetFrameBudget();\n}\n\nRasterStatus Rasterizer::DoDraw(\n    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder,\n    std::shared_ptr<clay::LayerTree> layer_tree) {\n  TRACE_EVENT_WITH_FRAME_NUMBER(frame_timings_recorder, \"clay\",\n                                \"Rasterizer::DoDraw\");\n  FML_DCHECK(service_manager_->GetTaskRunners()\n                 ->SelectTaskRunner<clay::Owner::kRaster>()\n                 ->RunsTasksOnCurrentThread());\n  if (!layer_tree || !surface_) {\n    return RasterStatus::kFailed;\n  }\n\n  PersistentCache* persistent_cache = PersistentCache::GetCacheForProcess();\n  persistent_cache->ResetStoredNewShaders();\n\n  bool need_next_animation_frame = false;\n  if (layer_tree->HasAnimations()) {\n    if (last_layer_tree_ != layer_tree) {\n      layer_tree->MergeAnimations(last_layer_tree_.get());\n    }\n    need_next_animation_frame = !layer_tree->DoAnimations();\n  }\n\n  RasterStatus raster_status =\n      DrawToSurface(*frame_timings_recorder, *layer_tree);\n\n  // In order to release the `SkiaGPUObject`s held by the `LayerTree`s in time.\n  fml::ScopedCleanupClosure unref_queue_drain([unref_queue = unref_queue_] {\n    if (unref_queue) {\n      unref_queue->Drain();\n    }\n  });\n\n  if (raster_status == RasterStatus::kSuccess) {\n    if (last_layer_tree_ != layer_tree && !!last_layer_tree_) {\n      last_layer_tree_->ResetServiceManagerForAnimation();\n    }\n    last_layer_tree_ = std::move(layer_tree);\n  } else if (raster_status == RasterStatus::kDiscarded ||\n             raster_status == RasterStatus::kFailed) {\n    return raster_status;\n  }\n#ifndef ENABLE_SKITY\n  if (persistent_cache->IsDumpingSkp() &&\n      persistent_cache->StoredNewShaders()) {\n    auto screenshot = ScreenshotLastLayerTree(\n        ScreenshotData::ScreenshotType::SkiaPicture, false);\n    persistent_cache->DumpSkp(*screenshot.data);\n  }\n#endif  // ENABLE_SKITY\n\n  // TODO(liyuqian): in Fuchsia, the rasterization doesn't finish when\n  // Rasterizer::DoDraw finishes. Future work is needed to adapt the timestamp\n  // for Fuchsia to capture SceneUpdateContext::ExecutePaintTasks.\n  instrumentation_service_.Act(\n      [time = frame_timings_recorder->GetRecordedTime()](auto& impl) {\n        impl.OnFrameRasterized(time);\n      });\n\n  if (need_next_animation_frame) {\n    raster_frame_service_->RequestRasterFrame();\n  }\n\n// SceneDisplayLag events are disabled on Fuchsia.\n// see: https://github.com/flutter/flutter/issues/56598\n#if !defined(OS_FUCHSIA)\n#if FLUTTER_TIMELINE_ENABLED\n  const fml::TimePoint raster_finish_time =\n      frame_timings_recorder->GetRasterEndTime();\n  fml::TimePoint frame_target_time =\n      frame_timings_recorder->GetVsyncTargetTime();\n  if (raster_finish_time > frame_target_time) {\n    fml::TimePoint latest_frame_target_time =\n        delegate_.GetLatestFrameTargetTime();\n    const auto frame_budget_millis = delegate_.GetFrameBudget().count();\n    if (latest_frame_target_time < raster_finish_time) {\n      latest_frame_target_time =\n          latest_frame_target_time +\n          fml::TimeDelta::FromMillisecondsF(frame_budget_millis);\n    }\n    const auto frame_lag =\n        (latest_frame_target_time - frame_target_time).ToMillisecondsF();\n    const int vsync_transitions_missed = round(frame_lag / frame_budget_millis);\n    fml::tracing::TraceEventAsyncComplete(\n        \"clay\",                       // category\n        \"SceneDisplayLag\",            // name\n        raster_finish_time,           // begin_time\n        latest_frame_target_time,     // end_time\n        \"frame_target_time\",          // arg_key_1\n        frame_target_time,            // arg_val_1\n        \"current_frame_target_time\",  // arg_key_2\n        latest_frame_target_time,     // arg_val_2\n        \"vsync_transitions_missed\",   // arg_key_3\n        vsync_transitions_missed      // arg_val_3\n    );                                // NOLINT\n  }\n#endif\n#endif\n\n  return raster_status;\n}\n\nRasterStatus Rasterizer::DrawToSurface(\n    FrameTimingsRecorder& frame_timings_recorder, clay::LayerTree& layer_tree) {\n  TRACE_EVENT(\"clay\", \"Rasterizer::DrawToSurface\");\n  FML_DCHECK(surface_);\n\n  instrumentation_service_.Act([raster_time = raster_time_,\n                                now = fml::TimePoint::Now()](auto& impl) {\n    if (impl.GetFrameTimingCollector()->IsRecordingFirstFramePerf()) {\n      impl.GetFrameTimingCollector()->BeginRecord(clay::Perf::kFirstRasterCost);\n    } else {\n      raster_time->Start(now);\n    }\n  });\n\n  RasterStatus raster_status;\n  if (surface_->AllowsDrawingWhenGpuDisabled()) {\n    raster_status = DrawToSurfaceUnsafe(frame_timings_recorder, layer_tree);\n  } else {\n    platform_const_service_->GetIsGPUDisabledSyncSwitch()->Execute(\n        fml::SyncSwitch::Handlers()\n            .SetIfTrue([&] { raster_status = RasterStatus::kDiscarded; })\n            .SetIfFalse([&] {\n              raster_status =\n                  DrawToSurfaceUnsafe(frame_timings_recorder, layer_tree);\n            }));\n  }\n\n  layer_tree.AppendFrameTimings(frame_timings_recorder.TakeFrameTimings(),\n                                true);\n  return raster_status;\n}\n\n/// Unsafe because it assumes we have access to the GPU which isn't the case\n/// when iOS is backgrounded, for example.\n/// \\see Rasterizer::DrawToSurface\nRasterStatus Rasterizer::DrawToSurfaceUnsafe(\n    FrameTimingsRecorder& frame_timings_recorder, clay::LayerTree& layer_tree) {\n  FML_DCHECK(surface_);\n\n  class ScopedDrawTiming {\n   public:\n    explicit ScopedDrawTiming(FrameTimingsRecorder& recorder)\n        : recorder_(recorder) {\n      recorder_.RecordFrameTime(FrameTimingKey::kDoDrawStart);\n    }\n    void MarkDrawEnd() { draw_end_marked_ = true; }\n    ~ScopedDrawTiming() {\n      if (draw_end_marked_) {\n        recorder_.RecordFrameTime(FrameTimingKey::kDoDrawEnd);\n      }\n    }\n\n   private:\n    FrameTimingsRecorder& recorder_;\n    bool draw_end_marked_ = false;\n  };\n\n  ScopedDrawTiming scoped_draw_timing(frame_timings_recorder);\n\n  compositor_context_->ui_time().SetLapTime(\n      frame_timings_recorder.GetBuildDuration());\n\n  frame_timings_recorder.RecordFrameTime(FrameTimingKey::kAcquireFrameStart);\n  auto frame = surface_->AcquireFrame(layer_tree.frame_size());\n  frame_timings_recorder.RecordFrameTime(FrameTimingKey::kAcquireFrameEnd);\n  if (frame == nullptr) {\n    return RasterStatus::kFailed;\n  }\n  auto compositor_state =\n      std::make_unique<CompositorState>(layer_tree.frame_size());\n\n  instrumentation_service_.Act(\n      [supports_partial_repaint =\n           frame->framebuffer_info().supports_partial_repaint](auto& impl) {\n        if (impl.GetFrameTimingCollector()->IsRecordingFirstFramePerf()) {\n          impl.GetFrameTimingCollector()->InsertRecord(\n              clay::Perf::kEnablePartialRepaint, supports_partial_repaint);\n        }\n      });\n\n  auto compositor_frame = compositor_context_->AcquireFrame(\n      surface_->GetContext(),             // skia GrContext\n      frame->GetCanvas(),                 // root surface canvas\n      compositor_state.get(),             // compositor state\n      surface_->GetRootTransformation(),  // root surface transformation\n      true,                               // instrumentation enabled\n      frame->framebuffer_info()\n          .supports_readback  // surface supports pixel reads\n  );\n\n  if (compositor_frame) {\n    compositor_context_->raster_cache().BeginFrame();\n    frame_timings_recorder.RecordRasterStart(fml::TimePoint::Now());\n\n    std::unique_ptr<FrameDamage> damage;\n    // when leaf layer tracing is enabled we wish to repaint the whole frame\n    // for accurate performance metrics.\n    if (frame->framebuffer_info().supports_partial_repaint &&\n        !layer_tree.is_leaf_layer_tracing_enabled()) {\n      // FIXME(haoyoufeng.aji) : partial repaint with platform view present\n      bool force_full_repaint = true;\n\n      damage = std::make_unique<FrameDamage>();\n      if (frame->framebuffer_info().existing_damage && !force_full_repaint) {\n        damage->SetPreviousLayerTree(last_layer_tree_.get());\n        damage->AddAdditionalDamage(*frame->framebuffer_info().existing_damage);\n        damage->SetClipAlignment(\n            frame->framebuffer_info().horizontal_clip_alignment,\n            frame->framebuffer_info().vertical_clip_alignment);\n      }\n    }\n\n    bool should_low_memory_usage = false;\n    if (render_settings_ && render_settings_->ShouldLowMemoryUsage()) {\n      should_low_memory_usage = true;\n    }\n    if (last_memory_strategy_ && !should_low_memory_usage) {\n      compositor_context_->raster_cache().set_needs_build_all_caches(true);\n    } else {\n      compositor_context_->raster_cache().set_needs_build_all_caches(false);\n    }\n    last_memory_strategy_ = should_low_memory_usage;\n    bool ignore_raster_cache =\n        should_low_memory_usage || !surface_->EnableRasterCache() ||\n        (render_settings_ && render_settings_->IgnoreRasterCache());\n\n    if (ignore_raster_cache) {\n      // ignore_raster_cache may be set dynamically by FrontEnd. So clear cache\n      // manually to avoid errors in raster_cache.EndFrame().\n      compositor_context_->raster_cache().Clear();\n    }\n    frame_timings_recorder.RecordFrameTime(FrameTimingKey::kRasterStart);\n    RasterStatus raster_status = compositor_frame->Raster(\n        layer_tree, ignore_raster_cache, damage.get(),\n        Color::kTransparent().Value(), [&] {\n          frame->Prepare(damage ? damage->GetBufferDamage() : std::nullopt);\n        });\n    frame_timings_recorder.RecordFrameTime(FrameTimingKey::kRasterEnd);\n    auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(\n                         std::chrono::system_clock::now().time_since_epoch())\n                         .count();\n    instrumentation_service_.Act([timestamp](auto& impl) {\n      if (impl.GetFrameTimingCollector()->IsRecordingFirstFramePerf()) {\n        impl.GetFrameTimingCollector()->InsertRecord(\n            clay::Perf::kFirstRasterEnd, timestamp);\n        impl.GetFrameTimingCollector()->EndRecord(clay::Perf::kFirstRasterCost);\n      }\n    });\n    if (raster_status == RasterStatus::kFailed) {\n      return raster_status;\n    }\n\n    SurfaceFrame::SubmitInfo submit_info;\n    // TODO (https://github.com/flutter/flutter/issues/105596):  // NOLINT\n    // this can be in the past and might need to get snapped to future as this\n    // frame could have been resubmitted. `presentation_time` on `submit_info`\n    // is not set in this case.\n    const auto presentation_time = frame_timings_recorder.GetVsyncTargetTime();\n    if (presentation_time > fml::TimePoint::Now()) {\n      submit_info.presentation_time = presentation_time;\n    }\n    if (damage) {\n      submit_info.frame_damage = damage->GetFrameDamage();\n      submit_info.buffer_damage = damage->GetBufferDamage();\n    }\n\n    frame->set_submit_info(submit_info);\n    frame_timings_recorder.RecordFrameTime(FrameTimingKey::kSubmitFrameStart);\n    compositor_service_->SubmitFrame(surface_->GetContext(), std::move(frame),\n                                     std::move(compositor_state));\n    frame_timings_recorder.RecordFrameTime(FrameTimingKey::kSubmitFrameEnd);\n\n    compositor_context_->raster_cache().EndFrame();\n    frame_timings_recorder.RecordRasterEnd(\n        &compositor_context_->raster_cache());\n\n    FireNextFrameCallbackIfPresent();\n\n    if (unref_queue_) {\n      unref_queue_->Drain();\n    }\n\n    fml::TimePoint now = fml::TimePoint::Now();\n    timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(\n                    std::chrono::system_clock::now().time_since_epoch())\n                    .count();\n    instrumentation_service_.Act(\n        [raster_time = raster_time_, frame_total_time = frame_total_time_, now,\n         lap_time = now - frame_timings_recorder.GetBuildStartTime(),\n         sync_compositor =\n             platform_const_service_->GetSettings().enable_sync_compositor,\n         timestamp](auto& impl) {\n          if (impl.GetFrameTimingCollector()->IsRecordingFirstFramePerf() &&\n              !sync_compositor) {\n            impl.GetFrameTimingCollector()->InsertRecord(\n                clay::Perf::kFirstPresentEnd, timestamp);\n          } else {\n            raster_time->Stop(now);\n            impl.GetFrameTimingCollector()->InsertRasterRecord(*raster_time);\n            frame_total_time->SetLapTime(lap_time);\n            impl.GetFrameTimingCollector()->InsertFrameTotalCostRecord(\n                *frame_total_time);\n          }\n        });\n    if (surface_->GetContext()) {\n#ifndef ENABLE_SKITY\n      surface_->GetContext()->performDeferredCleanup(kSkiaCleanupExpiration);\n#endif  // ENABLE_SKITY\n    }\n    if (auto* info = compositor_context_->GetRasterCacheInfo()) {\n      instrumentation_service_.Act([raster_cache_info = *info](auto& impl) {\n        impl.UpdateRasterCacheInfo(raster_cache_info);\n      });\n    }\n    scoped_draw_timing.MarkDrawEnd();\n    return raster_status;\n  }\n\n  return RasterStatus::kFailed;\n}\n\nScreenshotData Rasterizer::ScreenshotLastLayerTree(\n    ScreenshotData::ScreenshotType type, bool base64_encode,\n    uint32_t background_color) {\n  auto* layer_tree = GetLastLayerTree();\n  return TakeScreenshotWithBase64(layer_tree, type, surface_.get(),\n                                  compositor_context_.get(), base64_encode,\n                                  background_color);\n}\n\nvoid Rasterizer::SetNextFrameCallback(const fml::closure& callback) {\n  next_frame_callback_ = callback;\n}\n\nvoid Rasterizer::FireNextFrameCallbackIfPresent() {\n  if (!next_frame_callback_) {\n    return;\n  }\n  // It is safe for the callback to set a new callback.\n  auto callback = next_frame_callback_;\n  next_frame_callback_ = nullptr;\n  callback();\n}\n\nvoid Rasterizer::SetResourceCacheMaxBytes(size_t max_bytes, bool from_user) {\n  user_override_resource_cache_bytes_ |= from_user;\n\n  if (!from_user && user_override_resource_cache_bytes_) {\n    // We should not update the setting here if a user has explicitly set a\n    // value for this over the flutter/skia channel.\n    return;\n  }\n\n  max_cache_bytes_ = max_bytes;\n  if (!surface_) {\n    return;\n  }\n\n#ifndef ENABLE_SKITY\n  GrDirectContext* context = surface_->GetContext();\n  if (context) {\n    auto context_switch = surface_->MakeRenderContextCurrent();\n    if (!context_switch->GetResult()) {\n      return;\n    }\n    context->setResourceCacheLimit(max_bytes);\n  }\n#else\n// TODO(zhangzhijian.123): Since the function\n// 'skity::GPUContext::SetResourceCacheLimit()' is unstable, so do nothing\n// now. We'll fix this issue later.\n#endif  // ENABLE_SKITY\n}\n\nstd::optional<size_t> Rasterizer::GetResourceCacheMaxBytes() const {\n  if (!surface_) {\n    return std::nullopt;\n  }\n#ifndef ENABLE_SKITY\n  GrDirectContext* context = surface_->GetContext();\n  if (context) {\n    return context->getResourceCacheLimit();\n  }\n#endif  // ENABLE_SKITY\n  return std::nullopt;\n}\n\nvoid Rasterizer::SetRenderSettings(\n    fml::RefPtr<clay::RenderSettings> render_settings) {\n  render_settings_ = render_settings;\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/rasterizer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_RASTERIZER_H_\n#define CLAY_SHELL_COMMON_RASTERIZER_H_\n\n#include <deque>\n#include <memory>\n#include <optional>\n#include <queue>\n#include <vector>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/raster_thread_merger.h\"\n#include \"base/include/fml/synchronization/sync_switch.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/common/recyclable.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/common/settings.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/flow/compositor_context.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/paint_image.h\"\n#include \"clay/shell/common/screenshot_utils.h\"\n#include \"clay/shell/common/services/compositor/compositor_service.h\"\n#include \"clay/shell/common/services/instrumentation_service.h\"\n#include \"clay/shell/common/services/platform_const_service.h\"\n#include \"clay/shell/common/services/raster_frame_service.h\"\n#include \"clay/shell/common/shell_common_rendering_backend.h\"\n#include \"clay/ui/common/render_settings.h\"\n\nnamespace clay {\nclass GpuResourceCache;\nclass ServiceManager;\n}  // namespace clay\n\nnamespace clay {\n\nusing clay::GPUUnrefQueue;\n\n//------------------------------------------------------------------------------\n/// The rasterizer is a component owned by the shell that resides on the raster\n/// task runner. Each shell owns exactly one instance of a rasterizer. The\n/// rasterizer may only be created, used and collected on the raster task\n/// runner.\n///\n/// The rasterizer owns the instance of the currently active on-screen render\n/// surface. On this surface, it renders the contents of layer trees submitted\n/// to it by the `Engine` (which lives on the UI task runner).\n///\n/// The primary components owned by the rasterizer are the compositor context\n/// and the on-screen render surface. The compositor context has all the GPU\n/// state necessary to render frames to the render surface.\n///\nclass Rasterizer final : public Stopwatch::RefreshRateUpdater,\n                         public clay::Recyclable {\n public:\n  //----------------------------------------------------------------------------\n  /// @brief      Creates a new instance of a rasterizer. Rasterizers may only\n  ///             be created on the raster task runner. Rasterizers are\n  ///             currently only created by the shell (which also sets itself up\n  ///             as the rasterizer delegate).\n  ///\n  /// @param[in]  delegate                   The rasterizer delegate.\n  /// @param[in]  gpu_image_behavior         How to handle calls to\n  ///                                        MakeSkiaGpuImage.\n  ///\n  explicit Rasterizer(std::shared_ptr<clay::ServiceManager> service_manager);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Destroys the rasterizer. This must happen on the raster task\n  ///             runner. All GPU resources are collected before this call\n  ///             returns. Any context set up by the embedder to hold these\n  ///             resources can be immediately collected as well.\n  ///\n  ~Rasterizer();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Rasterizers may be created well before an on-screen surface is\n  ///             available for rendering. Shells usually create a rasterizer in\n  ///             their constructors. Once an on-screen surface is available\n  ///             however, one may be provided to the rasterizer using this\n  ///             call. No rendering may occur before this call. The surface is\n  ///             held till the balancing call to `Rasterizer::Teardown` is\n  ///             made. Calling a setup before tearing down the previous surface\n  ///             (if this is not the first time the surface has been set up) is\n  ///             user error.\n  ///\n  /// @see        `Rasterizer::Teardown`\n  ///\n  /// @param[in]  surface  The on-screen render surface.\n  ///\n  void Setup(std::unique_ptr<Surface> surface);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Releases the previously set up on-screen render surface and\n  ///             collects associated resources. No more rendering may occur\n  ///             till the next call to `Rasterizer::Setup` with a new render\n  ///             surface. Calling a teardown without a setup is user error.\n  ///\n  void Teardown();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Notifies the rasterizer that there is a low memory situation\n  ///             and it must purge as many unnecessary resources as possible.\n  ///             Currently, the Skia context associated with onscreen rendering\n  ///             is told to free GPU resources.\n  ///\n  void NotifyLowMemoryWarning() const;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Gets a weak pointer to the rasterizer. The rasterizer may only\n  ///             be accessed on the raster task runner.\n  ///\n  /// @return     The weak pointer to the rasterizer.\n  ///\n  fml::WeakPtr<Rasterizer> GetWeakPtr() const;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Sometimes, it may be necessary to render the same frame again\n  ///             without having to wait for the framework to build a whole new\n  ///             layer tree describing the same contents. One such case is when\n  ///             external textures (video or camera streams for example) are\n  ///             updated in an otherwise static layer tree. To support this use\n  ///             case, the rasterizer holds onto the last rendered layer tree.\n  ///\n  /// @bug        https://github.com/flutter/flutter/issues/33939\n  ///\n  /// @return     A pointer to the last layer or `nullptr` if this rasterizer\n  ///             has never rendered a frame.\n  ///\n  clay::LayerTree* GetLastLayerTree();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Draws a last layer tree to the render surface. This may seem\n  ///             entirely redundant at first glance. After all, on surface loss\n  ///             and re-acquisition, the framework generates a new layer tree.\n  ///             Otherwise, why render the same contents to the screen again?\n  ///             This is used as an optimization in cases where there are\n  ///             external textures (video or camera streams for example) in\n  ///             referenced in the layer tree. These textures may be updated at\n  ///             a cadence different from that of the Flutter application.\n  ///             Flutter can re-render the layer tree with just the updated\n  ///             textures instead of waiting for the framework to do the work\n  ///             to generate the layer tree describing the same contents.\n  ///\n  RasterStatus DrawLastLayerTree(\n      std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder);\n\n  std::shared_ptr<DrawableImageRegistry> GetDrawableImageRegistry();\n\n  using LayerTreeDiscardCallback = std::function<bool(clay::LayerTree&)>;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Takes the next item from the layer tree pipeline and executes\n  ///             the raster thread frame workload for that pipeline item to\n  ///             render a frame on the on-screen surface.\n  ///\n  ///             Why does the draw call take a layer tree pipeline and not the\n  ///             layer tree directly?\n  ///\n  ///             The pipeline is the way book-keeping of frame workloads\n  ///             distributed across the multiple threads is managed. The\n  ///             rasterizer deals with the pipelines directly (instead of layer\n  ///             trees which is what it actually renders) because the pipeline\n  ///             consumer's workload must be accounted for within the pipeline\n  ///             itself. If the rasterizer took the layer tree directly, it\n  ///             would have to be taken out of the pipeline. That would signal\n  ///             the end of the frame workload and the pipeline would be ready\n  ///             for new frames. But the last frame has not been rendered by\n  ///             the frame yet! On the other hand, the pipeline must own the\n  ///             layer tree it renders because it keeps a reference to the last\n  ///             layer tree around till a new frame is rendered. So a simple\n  ///             reference wont work either. The `Rasterizer::DoDraw` method\n  ///             actually performs the GPU operations within the layer tree\n  ///             pipeline.\n  ///\n  /// @see        `Rasterizer::DoDraw`\n  ///\n  /// @param[in]  pipeline  The layer tree pipeline to take the next layer tree\n  ///                       to render from.\n  /// @param[in]  discard_callback if specified and returns true, the layer tree\n  ///                             is discarded instead of being rendered\n  /// @param[in]  report_instrumentation if specified and returns true, report\n  ///                                    to perf collector after draw\n  ///\n  RasterStatus Draw(std::shared_ptr<LayerTree> layer_tree,\n                    std::unique_ptr<FrameTimingsRecorder> recorder = nullptr,\n                    LayerTreeDiscardCallback discard_callback = NoDiscard,\n                    bool report_instrumentation = false);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Screenshots the last layer tree to one of the supported\n  ///             screenshot types and optionally Base 64 encodes that data for\n  ///             easier transmission and packaging (usually over the service\n  ///             protocol for instrumentation tools running on the host).\n  ///\n  /// @param[in]  type           The type of the screenshot to gather.\n  /// @param[in]  base64_encode  Whether Base 64 encoding must be applied to the\n  ///                            data after a screenshot has been captured.\n  ///\n  /// @return     A non-empty screenshot if one could be captured. A screenshot\n  ///             capture may fail if there were no layer trees previously\n  ///             rendered by this rasterizer, or, due to an unspecified\n  ///             internal error. Internal error will be logged to the console.\n  ///\n  ScreenshotData ScreenshotLastLayerTree(\n      ScreenshotData::ScreenshotType type, bool base64_encode,\n      uint32_t background_color = Color::kTransparent().Value());\n\n  //----------------------------------------------------------------------------\n  /// @brief      Sets a callback that will be executed when the next layer tree\n  ///             in rendered to the on-screen surface. This is used by\n  ///             embedders to listen for one time operations like listening for\n  ///             when the first frame is rendered so that they may hide splash\n  ///             screens.\n  ///\n  ///             The callback is only executed once and dropped on the GPU\n  ///             thread when executed (lambda captures must be able to deal\n  ///             with the threading repercussions of this behavior).\n  ///\n  /// @param[in]  callback  The callback to execute when the next layer tree is\n  ///                       rendered on-screen.\n  ///\n  void SetNextFrameCallback(const fml::closure& callback);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Returns a pointer to the compositor context used by this\n  ///             rasterizer. This pointer will never be `nullptr`.\n  ///\n  /// @return     The compositor context used by this rasterizer.\n  ///\n  clay::CompositorContext* compositor_context() {\n    return compositor_context_.get();\n  }\n\n  //----------------------------------------------------------------------------\n  /// @brief      Skia has no notion of time. To work around the performance\n  ///             implications of this, it may cache GPU resources to reference\n  ///             them from one frame to the next. Using this call, embedders\n  ///             may set the maximum bytes cached by Skia in its caches\n  ///             dedicated to on-screen rendering.\n  ///\n  /// @attention  This cache setting will be invalidated when the surface is\n  ///             torn down via `Rasterizer::Teardown`. This call must be made\n  ///             again with new limits after surface re-acquisition.\n  ///\n  /// @attention  This cache does not describe the entirety of GPU resources\n  ///             that may be cached. The `RasterCache` also holds very large\n  ///             GPU resources.\n  ///\n  /// @see        `RasterCache`\n  ///\n  /// @param[in]  max_bytes  The maximum byte size of resource that may be\n  ///                        cached for GPU rendering.\n  /// @param[in]  from_user  Whether this request was from user code, e.g. via\n  ///                        the flutter/skia message channel, in which case\n  ///                        it should not be overridden by the platform.\n  ///\n  void SetResourceCacheMaxBytes(size_t max_bytes, bool from_user);\n\n  //----------------------------------------------------------------------------\n  /// @brief      The current value of Skia's resource cache size, if a surface\n  ///             is present.\n  ///\n  /// @attention  This cache does not describe the entirety of GPU resources\n  ///             that may be cached. The `RasterCache` also holds very large\n  ///             GPU resources.\n  ///\n  /// @see        `RasterCache`\n  ///\n  /// @return     The size of Skia's resource cache, if available.\n  ///\n  std::optional<size_t> GetResourceCacheMaxBytes() const;\n\n  void set_unref_queue(fml::RefPtr<GPUUnrefQueue> queue) {\n    unref_queue_ = queue;\n  }\n\n  void SetRenderSettings(fml::RefPtr<clay::RenderSettings> render_settings);\n\n  fml::RefPtr<PaintImage> MakeRasterSnapshot(\n      std::unique_ptr<LayerTree> layer_tree);\n\n  fml::RefPtr<PaintImage> MakeRasterSnapshot(GrPicturePtr picture,\n                                             skity::Vec2 size);\n\n  const std::shared_ptr<clay::ServiceManager>& GetServiceManager() const {\n    return service_manager_;\n  }\n\n  void CleanForRecycle() override;\n\n private:\n  // |Stopwatch::Delegate|\n  /// Time limit for a smooth frame.\n  ///\n  /// See: `DisplayManager::GetMainDisplayRefreshRate`.\n  fml::Milliseconds GetFrameBudget() const override;\n\n  RasterStatus DoDraw(\n      std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder,\n      std::shared_ptr<clay::LayerTree> layer_tree);\n\n  RasterStatus DrawToSurface(FrameTimingsRecorder& frame_timings_recorder,\n                             clay::LayerTree& layer_tree);\n\n  RasterStatus DrawToSurfaceUnsafe(FrameTimingsRecorder& frame_timings_recorder,\n                                   clay::LayerTree& layer_tree);\n\n  void FireNextFrameCallbackIfPresent();\n\n  static bool NoDiscard(const clay::LayerTree& layer_tree) { return false; }\n\n  const std::shared_ptr<clay::ServiceManager> service_manager_;\n  const clay::Puppet<clay::Owner::kRaster, PlatformConstService>\n      platform_const_service_;\n  const clay::Puppet<clay::Owner::kRaster, RasterFrameService>\n      raster_frame_service_;\n  const clay::Puppet<clay::Owner::kRaster, InstrumentationService>\n      instrumentation_service_;\n  const clay::Puppet<clay::Owner::kRaster, CompositorService>\n      compositor_service_;\n\n  std::unique_ptr<Surface> surface_;\n  std::unique_ptr<clay::CompositorContext> compositor_context_;\n  // This is the last successfully rasterized layer tree.\n  std::shared_ptr<clay::LayerTree> last_layer_tree_;\n  fml::closure next_frame_callback_;\n  bool user_override_resource_cache_bytes_;\n  std::optional<size_t> max_cache_bytes_;\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n  fml::RefPtr<clay::RenderSettings> render_settings_;\n  bool last_memory_strategy_ = false;  // true: low memory usage, false: normal\n  std::mutex frame_mutex_;\n  std::unique_ptr<FrameTimingsRecorder> last_recorder_;\n  const std::shared_ptr<FixedRefreshRateStopwatch> raster_time_;\n  const std::shared_ptr<FixedRefreshRateStopwatch> frame_total_time_;\n\n  // WeakPtrFactory must be the last member.\n  fml::WeakPtrFactory<Rasterizer> weak_factory_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(Rasterizer);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_RASTERIZER_H_\n"
  },
  {
    "path": "clay/shell/common/resource_cache_limit_calculator.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/resource_cache_limit_calculator.h\"\n\n#include <algorithm>\n#include <limits>\n#include <utility>\n\nnamespace clay {\n\nsize_t ResourceCacheLimitCalculator::GetResourceCacheMaxBytes() {\n  size_t max_bytes = 0;\n  size_t max_bytes_threshold = max_bytes_threshold_ > 0\n                                   ? max_bytes_threshold_\n                                   : std::numeric_limits<size_t>::max();\n  std::vector<fml::WeakPtr<ResourceCacheLimitItem>> live_items;\n  for (auto item : items_) {\n    if (item) {\n      live_items.push_back(item);\n      max_bytes += item->GetResourceCacheLimit();\n    }\n  }\n  items_ = std::move(live_items);\n  return std::min(max_bytes, max_bytes_threshold);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/resource_cache_limit_calculator.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_RESOURCE_CACHE_LIMIT_CALCULATOR_H_\n#define CLAY_SHELL_COMMON_RESOURCE_CACHE_LIMIT_CALCULATOR_H_\n\n#include <cstdint>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n\nnamespace clay {\nclass ResourceCacheLimitItem {\n public:\n  // The expected GPU resource cache limit in bytes. This will be called on the\n  // platform thread.\n  virtual size_t GetResourceCacheLimit() = 0;\n\n protected:\n  virtual ~ResourceCacheLimitItem() = default;\n};\n\nclass ResourceCacheLimitCalculator {\n public:\n  explicit ResourceCacheLimitCalculator(size_t max_bytes_threshold)\n      : max_bytes_threshold_(max_bytes_threshold) {}\n\n  ~ResourceCacheLimitCalculator() = default;\n\n  // This will be called on the platform thread.\n  void AddResourceCacheLimitItem(fml::WeakPtr<ResourceCacheLimitItem> item) {\n    items_.push_back(item);\n  }\n\n  // The maximum GPU resource cache limit in bytes calculated by\n  // 'ResourceCacheLimitItem's. This will be called on the platform thread.\n  size_t GetResourceCacheMaxBytes();\n\n private:\n  std::vector<fml::WeakPtr<ResourceCacheLimitItem>> items_;\n  size_t max_bytes_threshold_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(ResourceCacheLimitCalculator);\n};\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_RESOURCE_CACHE_LIMIT_CALCULATOR_H_\n"
  },
  {
    "path": "clay/shell/common/scheduler/scheduler.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/scheduler/scheduler.h\"\n\n#include \"base/include/auto_reset.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nScheduler::Scheduler(SchedulerClient* scheduler_client,\n                     bool using_sync_compositor)\n    : using_sync_compositor_(using_sync_compositor),\n      state_machine_(using_sync_compositor_),\n      scheduler_client_(scheduler_client) {\n  FML_DCHECK(scheduler_client_);\n}\n\nScheduler::~Scheduler() {}\n\nvoid Scheduler::SetVisible(bool visible) {\n  state_machine_.SetVisible(visible);\n  ProcessScheduledAction();\n}\n\nbool Scheduler::OnDraw() {\n  FML_DCHECK(using_sync_compositor_);\n  lynx::base::AutoReset<bool> resetter(&sync_compositor_result_, false);\n  // Perform the raster frame if needed, and will set the result to\n  // sync_compositor_result_ if succeed.\n  OnBeginRasterFrame();\n  // Sync the frame result.\n  return sync_compositor_result_;\n}\n\nvoid Scheduler::SetOutputSurfaceValid(bool valid) {\n  state_machine_.SetOutputSurfaceState(\n      valid ? SchedulerStateMachine::OutputSurfaceState::ACTIVE\n            : SchedulerStateMachine::OutputSurfaceState::LOST);\n  ProcessScheduledAction();\n}\n\nvoid Scheduler::SetMeaningfulLayout(bool meaningful_layout) {\n  state_machine_.SetMeaningfulLayoutState(\n      meaningful_layout ? SchedulerStateMachine::MeaningfulLayoutState::FINISH\n                        : SchedulerStateMachine::MeaningfulLayoutState::DIRTY);\n  ProcessScheduledAction();\n}\n\nvoid Scheduler::SetNeedsUIBeginFrame() {\n  state_machine_.SetNeedsUIBeginFrame();\n  ProcessScheduledAction();\n}\n\nbool Scheduler::NeedsUIBeginFrame() const {\n  return state_machine_.NeedsUIBeginFrame();\n}\n\nvoid Scheduler::SetNeedsRasterBeginFrame() {\n  state_machine_.SetNeedsRasterBeginFrame();\n  ProcessScheduledAction();\n}\n\nvoid Scheduler::NotifyReadyToSendUIBeginFrame() {\n  state_machine_.NotifyReadyToSendUIBeginFrame();\n  ProcessScheduledAction();\n}\n\nvoid Scheduler::NotifyReadyToCommit(bool commit_has_no_updates) {\n  commit_has_no_updates_ = commit_has_no_updates;\n  state_machine_.NotifyReadyToCommit();\n  // The new UI Frame is arrived, if the scheduler is still waiting for the\n  // deadline, we ignore it and perform raster frame immediately.\n  if (state_machine_.IsInsideRasterFrameDeadline()) {\n    OnReachRasterFrameDeadline();\n  }\n  ProcessScheduledAction();\n}\n\nvoid Scheduler::NotifyReadyToActivate() {\n  state_machine_.NotifyReadyToActivate();\n  ProcessScheduledAction();\n}\n\nvoid Scheduler::NotifyActiveTreeHasBeenDrawn() {\n  sync_compositor_result_ = true;\n  state_machine_.NotifyActiveTreeHasBeenDrawn();\n  ProcessScheduledAction();\n}\n\nvoid Scheduler::NotifyReadyToPerformRasterFrame() {\n  state_machine_.NotifyReadyToPerformRasterFrame();\n  ProcessScheduledAction();\n}\n\nvoid Scheduler::OnBeginRasterFrame() {\n  // Send UI begin frame at first if needed, and it will set a deadline with the\n  // SchedulerClient.\n  state_machine_.NotifyReadyToSendUIBeginFrame();\n  ProcessScheduledAction();\n  if (!state_machine_.IsInsideRasterFrameDeadline()) {\n    // The scheduler is not in frame deadline scheduling phase, try to trigger\n    // raster on the active LayerTree.\n    NotifyReadyToPerformRasterFrame();\n  }\n}\n\nbool Scheduler::BeginRasterFrameDeadline() {\n  return state_machine_.OnBeginRasterFrameDeadline();\n}\n\nvoid Scheduler::OnReachRasterFrameDeadline() {\n  if (state_machine_.IsInsideRasterFrameDeadline()) {\n    state_machine_.OnReachRasterFrameDeadline();\n    NotifyReadyToPerformRasterFrame();\n  }\n}\n\nbool Scheduler::IsFirstFrameDrawn() const {\n  return state_machine_.IsFirstFrameDrawn();\n}\n\nvoid Scheduler::NotifyUploadTaskRegistered() {\n  state_machine_.OnUploadTaskRegistered();\n  ProcessScheduledAction();\n}\n\nvoid Scheduler::OnImageUploadTaskFinished(uint32_t left_tasks) {\n  if (left_tasks == 0) {\n    state_machine_.OnUploadImageComplete();\n  }\n}\n\nvoid Scheduler::ProcessScheduledAction() {\n  if (stopped_) {\n    return;\n  }\n\n  SchedulerStateMachine::Action action;\n  do {\n    action = state_machine_.NextAction();\n    switch (action) {\n      case SchedulerStateMachine::Action::NONE:\n        break;\n      case SchedulerStateMachine::Action::BEGIN_FRAME:\n        // Schedule UI begin frame.\n        state_machine_.WillSendUIBeginFrame();\n        scheduler_client_->ScheduledActionBeginFrame();\n        break;\n      case SchedulerStateMachine::Action::COMMIT:\n        // Schedule submit UI LayerTree to Raster.\n        state_machine_.WillCommit(commit_has_no_updates_);\n        scheduler_client_->ScheduledActionCommit();\n        break;\n      case SchedulerStateMachine::Action::ACTIVE_PENDING_TREE:\n        // Schedule merge Pending LayerTree to Active LayerTree.\n        state_machine_.WillActivate();\n        scheduler_client_->ScheduledActionActivePendingTree();\n        break;\n      case SchedulerStateMachine::Action::PERFORM_RASTER_INVALIDATE:\n        // Schedule raster scroll, animation, deferred image decoding.\n        state_machine_.WillPerformRasterInvalidation();\n        scheduler_client_->ScheduledActionRasterInvalidate();\n        break;\n      case SchedulerStateMachine::Action::DRAW:\n        // Schedule draw LayerTree.\n        state_machine_.WillDraw();\n        scheduler_client_->ScheduledActionDraw();\n        state_machine_.DidDraw();\n        break;\n      case SchedulerStateMachine::Action::DRAW_ABORT:\n        // No action is actually performed, but this allows the state machine to\n        // drain the pipeline without actually drawing.\n        break;\n      case SchedulerStateMachine::Action::UPLOAD_IMAGE:\n        // Schedule upload image.\n        scheduler_client_->ScheduledActionUploadImage();\n        break;\n    }\n  } while (SchedulerStateMachine::Action::NONE != action);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/scheduler/scheduler.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SCHEDULER_SCHEDULER_H_\n#define CLAY_SHELL_COMMON_SCHEDULER_SCHEDULER_H_\n\n#include <memory>\n\n#include \"clay/shell/common/scheduler/scheduler_client.h\"\n#include \"clay/shell/common/scheduler/scheduler_state_machine.h\"\n\nnamespace clay {\n\n// The scheduler seperates \"what to do next\" from the updating of its internal\n// state to make testing cleaner.\nclass Scheduler {\n public:\n  explicit Scheduler(SchedulerClient* scheduler_client,\n                     bool using_sync_compositor = false);\n  ~Scheduler();\n\n  void Stop() { stopped_ = true; }\n  void Resume() { stopped_ = false; }\n  void SetVisible(bool visible);\n\n  // Do draw for synchronous compositor.\n  bool OnDraw();\n\n  // Indicates whether the output surface is valid, we should not do action draw\n  // if it is invalid.\n  void SetOutputSurfaceValid(bool valid);\n\n  // Indicates that current page has meaningful layout, if there is no\n  // meaningful layout, we have no need to send begin frame.\n  void SetMeaningfulLayout(bool meaningful_layout);\n\n  // Set |needs_ui_begin_frame_| to true, which will cause the BeginFrame\n  // action with appropriate timing.\n  void SetNeedsUIBeginFrame();\n\n  bool NeedsUIBeginFrame() const;\n\n  // Set |needs_raster_begin_frame_| to true, which will cause the Draw action\n  // this client so that this client can send a RasterFrame with appropriate\n  // timing.\n  void SetNeedsRasterBeginFrame();\n\n  // Set |ui_begin_frame_is_ready_to_send_| to true, which will cause the\n  // BeginFrame action with appropriate timing depends on SetNeedsUIBeginFrame.\n  void NotifyReadyToSendUIBeginFrame();\n\n  // Commit step happens after the UI thread has completed updating for a\n  // BeginFrame request from the raster. Call this method when the\n  // UI thread updates are completed to signal it is ready for the commmit.\n  // The param `commit_has_no_updates` indicates whether there is no new layer\n  // tree.\n  void NotifyReadyToCommit(bool commit_has_no_updates);\n\n  // We have 2 copies of the layer trees on the raster thread: pending_tree\n  // and active_tree. When we receive a new vsync signal, call this method to\n  // notify that this pending tree is ready to be activated, that is to be\n  // copied to the active tree.\n  void NotifyReadyToActivate();\n\n  // We should ensure that we have done the first draw successfully for the\n  // current active tree before we activate the next pending tree.\n  void NotifyActiveTreeHasBeenDrawn();\n\n  // Indicates that the raster frame is ready to be executed, which means that\n  // vsync or frame deadline reached.\n  void NotifyReadyToPerformRasterFrame();\n\n  // Indicates that the Raster thread has received a new frame signal, and it's\n  // ready to perform a new Frame draw. The scheduler will schedule the UI frame\n  // and the Raster frame with the state machine.\n  void OnBeginRasterFrame();\n  bool BeginRasterFrameDeadline();\n  // Indicates that the frame deadline arrived.\n  void OnReachRasterFrameDeadline();\n\n  // Indicates that the first frame has been drawn.\n  bool IsFirstFrameDrawn() const;\n\n  void NotifyUploadTaskRegistered();\n  void OnImageUploadTaskFinished(uint32_t left_tasks);\n\n private:\n  void ProcessScheduledAction();\n\n  bool stopped_ = false;\n  bool using_sync_compositor_ = false;\n  bool commit_has_no_updates_ = false;\n  bool sync_compositor_result_ = false;\n\n  SchedulerStateMachine state_machine_;\n  SchedulerClient* scheduler_client_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SCHEDULER_SCHEDULER_H_\n"
  },
  {
    "path": "clay/shell/common/scheduler/scheduler_client.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SCHEDULER_SCHEDULER_CLIENT_H_\n#define CLAY_SHELL_COMMON_SCHEDULER_SCHEDULER_CLIENT_H_\n\n#include <memory>\n\nnamespace clay {\n\nclass SchedulerClient {\n public:\n  virtual void ScheduledActionBeginFrame() = 0;\n  virtual void ScheduledActionCommit() = 0;\n  virtual void ScheduledActionActivePendingTree() = 0;\n  virtual void ScheduledActionRasterInvalidate() = 0;\n  virtual void ScheduledActionDraw() = 0;\n  virtual void ScheduledActionUploadImage() = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SCHEDULER_SCHEDULER_CLIENT_H_\n"
  },
  {
    "path": "clay/shell/common/scheduler/scheduler_state_machine.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/scheduler/scheduler_state_machine.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nSchedulerStateMachine::SchedulerStateMachine(bool using_sync_compositor)\n    : using_sync_compositor_(using_sync_compositor) {}\n\nSchedulerStateMachine::~SchedulerStateMachine() {}\n\nvoid SchedulerStateMachine::SetVisible(bool visible) {\n  if (visible_ == visible) {\n    return;\n  }\n\n  visible_ = visible;\n\n  did_draw_layer_tree_ = false;\n}\n\nvoid SchedulerStateMachine::SetNeedsUIBeginFrame() {\n  needs_ui_begin_frame_ = true;\n}\n\nvoid SchedulerStateMachine::SetNeedsRasterBeginFrame() {\n  needs_raster_begin_frame_ = true;\n}\n\nvoid SchedulerStateMachine::SetOutputSurfaceState(\n    OutputSurfaceState output_surface_state) {\n  output_surface_state_ = output_surface_state;\n}\n\nvoid SchedulerStateMachine::SetMeaningfulLayoutState(\n    MeaningfulLayoutState meaningful_layout_state) {\n  meaningful_layout_state_ = meaningful_layout_state;\n}\n\nSchedulerStateMachine::Action SchedulerStateMachine::NextAction() const {\n  if (ShouldSendUIBeginFrame()) {\n    return Action::BEGIN_FRAME;\n  }\n  if (ShouldCommit()) {\n    return Action::COMMIT;\n  }\n  if (ShouldActivatePendingTree()) {\n    return Action::ACTIVE_PENDING_TREE;\n  }\n  if (ShouldPerformRasterInvalidation()) {\n    return Action::PERFORM_RASTER_INVALIDATE;\n  }\n  if (ShouldDraw()) {\n    return Action::DRAW;\n  }\n  if (ShouldUploadImage()) {\n    return Action::UPLOAD_IMAGE;\n  }\n\n  return Action::NONE;\n}\n\nvoid SchedulerStateMachine::WillSendUIBeginFrame() {\n  FML_DCHECK(visible_);\n  FML_DCHECK(begin_frame_state_ == BeginFrameState::IDLE);\n  FML_DCHECK(needs_ui_begin_frame_);\n  FML_DCHECK(ui_begin_frame_is_ready_to_send_);\n\n  begin_frame_state_ = BeginFrameState::SENT;\n  needs_ui_begin_frame_ = false;\n  ui_begin_frame_is_ready_to_send_ = false;\n  image_upload_state_ = ImageUploadState::PENDING;\n}\n\nvoid SchedulerStateMachine::WillCommit(bool commit_has_no_updates) {\n  FML_DCHECK(begin_frame_state_ == BeginFrameState::READY_TO_COMMIT);\n  if (!commit_has_no_updates) {\n    // We have a new pending tree.\n    has_pending_tree_ = true;\n    pending_tree_is_ready_for_activation_ = false;\n    if (!first_frame_committed_) {\n      first_frame_committed_ = true;\n    }\n    image_upload_state_ = ImageUploadState::PENDING;\n  } else {\n    image_upload_state_ = ImageUploadState::READY;\n  }\n  begin_frame_state_ = BeginFrameState::IDLE;\n}\n\nvoid SchedulerStateMachine::WillActivate() {\n  FML_DCHECK(has_pending_tree_);\n\n  has_pending_tree_ = false;\n  pending_tree_is_ready_for_activation_ = false;\n  active_tree_has_been_drawn_ = false;\n\n  needs_raster_begin_frame_ = true;\n}\n\nvoid SchedulerStateMachine::WillDraw() {\n  FML_DCHECK(!did_draw_layer_tree_);\n  FML_DCHECK(needs_redraw_);\n  // We need to reset needs_redraw_ before we draw.\n  needs_redraw_ = false;\n  // Reset `inside_raster_frame_deadline_`\n  inside_raster_frame_deadline_ = false;\n  raster_state_ = RasterState::DRAW;\n  image_upload_state_ = ImageUploadState::PENDING;\n}\n\nvoid SchedulerStateMachine::DidDraw() {\n  raster_state_ = RasterState::IDLE;\n  image_upload_state_ = ImageUploadState::READY;\n}\n\nvoid SchedulerStateMachine::WillPerformRasterInvalidation() {}\n\nvoid SchedulerStateMachine::NotifyReadyToSendUIBeginFrame() {\n  if (needs_ui_begin_frame_) {\n    ui_begin_frame_is_ready_to_send_ = true;\n  }\n}\n\nvoid SchedulerStateMachine::NotifyReadyToCommit() {\n  FML_DCHECK(begin_frame_state_ == BeginFrameState::SENT);\n  begin_frame_state_ = BeginFrameState::READY_TO_COMMIT;\n  FML_DCHECK(has_pending_tree_ || ShouldCommit());\n  image_upload_state_ = ImageUploadState::PENDING;\n}\n\nbool SchedulerStateMachine::HasPendingTree() const { return has_pending_tree_; }\n\nbool SchedulerStateMachine::NotifyReadyToActivate() {\n  if (!has_pending_tree_ || pending_tree_is_ready_for_activation_) {\n    return false;\n  }\n  pending_tree_is_ready_for_activation_ = true;\n  return true;\n}\n\nbool SchedulerStateMachine::IsReadyToActivate() const {\n  return pending_tree_is_ready_for_activation_;\n}\n\nvoid SchedulerStateMachine::NotifyActiveTreeHasBeenDrawn() {\n  active_tree_has_been_drawn_ = true;\n  if (!first_frame_drawn_ && first_frame_committed_) {\n    // Mark the first frame has been drawn.\n    first_frame_drawn_ = true;\n  }\n}\n\nvoid SchedulerStateMachine::NotifyReadyToPerformRasterFrame() {\n  if (needs_raster_begin_frame_) {\n    raster_state_ = RasterState::BEGIN_FRAME;\n    needs_raster_begin_frame_ = false;\n    // Set the flag `needs_redraw_` which will schedule Action::DRAW.\n    needs_redraw_ = true;\n    image_upload_state_ = ImageUploadState::PENDING;\n  }\n}\n\nbool SchedulerStateMachine::OnBeginRasterFrameDeadline() {\n  if (using_sync_compositor_) {\n    // Disable the deadline mechanism if we are using sync compositor, because\n    // we prefer that the frame to be drawn immediately.\n    return false;\n  }\n  if (needs_raster_begin_frame_) {\n    // It has no need to send deadline if we have no raster frame.\n    return false;\n  }\n  if (begin_frame_state_ != BeginFrameState::SENT) {\n    // It has no need to send deadline if we have no UI frame.\n    return false;\n  }\n  if (ShouldIgnoreFrameDeadline()) {\n    // Ignore the deadline if we have a pending tree, we should prioritize\n    // active & draw the pending tree.\n    return false;\n  }\n  inside_raster_frame_deadline_ = true;\n  return true;\n}\n\nvoid SchedulerStateMachine::OnReachRasterFrameDeadline() {\n  inside_raster_frame_deadline_ = false;\n}\n\nbool SchedulerStateMachine::IsInsideRasterFrameDeadline() const {\n  return inside_raster_frame_deadline_;\n}\n\nbool SchedulerStateMachine::ShouldIgnoreFrameDeadline() const {\n  // Ignore frame deadline if we have a pending tree, we should begin raster\n  // frame immediately when the frame signal arrived in that case.\n  return has_pending_tree_ || !active_tree_has_been_drawn_;\n}\n\nbool SchedulerStateMachine::ShouldPerformRasterInvalidation() const {\n  return false;\n}\n\nbool SchedulerStateMachine::ShouldAbortCurrentFrame() const {\n  // If we're not visible, we should just abort the frame. we can simply\n  // activate on becoming invisible since we don't need to draw the active tree\n  // when we're in this state.\n  if (!visible_) {\n    return true;\n  }\n  // If our output surface is not ready, we should just abort the frame.\n  if (output_surface_state_ == OutputSurfaceState::LOST) {\n    return true;\n  }\n  return false;\n}\n\nbool SchedulerStateMachine::ShouldDraw() const {\n  // Do not draw more than once in the deadline. Aborted draws are ok because\n  // those are effectively nops.\n  if (did_draw_layer_tree_) {\n    return false;\n  }\n  // Don't draw if we are waiting for the output surface.\n  if (output_surface_state_ != OutputSurfaceState::ACTIVE) {\n    return false;\n  }\n\n  return needs_redraw_;\n}\n\nbool SchedulerStateMachine::ShouldActivatePendingTree() const {\n  // There is nothing to activate.\n  if (!has_pending_tree_) {\n    return false;\n  }\n  if (ShouldAbortCurrentFrame()) {\n    return true;\n  }\n  // We should ensure that we have done the first draw for the current active\n  // tree.\n  if (!active_tree_has_been_drawn_) {\n    return false;\n  }\n  // At this point, only activate if we are ready to activate.\n  return pending_tree_is_ready_for_activation_;\n}\n\nbool SchedulerStateMachine::ShouldSendUIBeginFrame() const {\n  if (!needs_ui_begin_frame_) {\n    return false;\n  }\n  // We can not perform commits if we are not visible.\n  if (!visible_) {\n    return false;\n  }\n  if (!ui_begin_frame_is_ready_to_send_) {\n    return false;\n  }\n  // The first meaningful layout is not complete, it has no need to begin a ui\n  // frame.\n  if (meaningful_layout_state_ == MeaningfulLayoutState::DIRTY) {\n    return false;\n  }\n  // Only send BeginUIFrame when there isn't another commit pending already.\n  if (begin_frame_state_ != BeginFrameState::IDLE) {\n    return false;\n  }\n  return true;\n}\n\nbool SchedulerStateMachine::ShouldCommit() const {\n  if (begin_frame_state_ != BeginFrameState::READY_TO_COMMIT) {\n    return false;\n  }\n  // We must not finish the commit until the pending tree is free.\n  if (has_pending_tree_) {\n    return false;\n  }\n  return true;\n}\n\nbool SchedulerStateMachine::ShouldUploadImage() const {\n  return image_upload_state_ == ImageUploadState::READY && needs_upload_image_;\n}\n\nvoid SchedulerStateMachine::OnUploadTaskRegistered() {\n  needs_upload_image_ = true;\n}\nvoid SchedulerStateMachine::OnUploadImageComplete() {\n  needs_upload_image_ = false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/scheduler/scheduler_state_machine.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SCHEDULER_SCHEDULER_STATE_MACHINE_H_\n#define CLAY_SHELL_COMMON_SCHEDULER_SCHEDULER_STATE_MACHINE_H_\n\n#include <memory>\n#include <optional>\n#include <utility>\n\nnamespace clay {\n\n// The StateMachine decides how to coordinate UI thread activite like\n// painting/running javascript with rendering and drawing activities on the\n// Raster thread.\n//\n// The state machine tracks internal state but is also influenced by external\n// state.  Internal state includes things like whether a frame has been\n// requested, while external state includes things like the current time being\n// near to the vsync time.\nclass SchedulerStateMachine {\n public:\n  explicit SchedulerStateMachine(bool using_sync_compositor = false);\n  ~SchedulerStateMachine();\n\n  SchedulerStateMachine(const SchedulerStateMachine&) = delete;\n  SchedulerStateMachine& operator=(const SchedulerStateMachine&) = delete;\n\n  enum class OutputSurfaceState {\n    LOST,\n    ACTIVE,\n  };\n\n  enum class MeaningfulLayoutState {\n    DIRTY,\n    FINISH,  // The first meaningful layout has been completed.\n  };\n\n  enum class BeginFrameState {\n    IDLE,             // A new BeginFrame can start.\n    SENT,             // A BeginFrame has already been issued.\n    READY_TO_COMMIT,  // The previously issued BeginFrame has been processed,\n                      // and is ready to commit.\n  };\n\n  enum class RasterState {\n    IDLE,         // A new Raster BeginFrame can start.\n    BEGIN_FRAME,  // A Raster BeginFrame has already been issued, to handle\n                  // scoll, animation of Layer on the Raster thread.\n    DRAW,         // Draw LayerTree.\n    SWAP_BUFFER,  // Swap buffer.\n  };\n\n  enum class ImageUploadState {\n    PENDING,  // Images are pending to upload.\n    READY,    // Images now can start uploading.\n  };\n\n  enum class Action : uint8_t {\n    NONE,\n    BEGIN_FRAME,\n    COMMIT,\n    ACTIVE_PENDING_TREE,\n    PERFORM_RASTER_INVALIDATE,\n    DRAW,\n    DRAW_ABORT,\n    UPLOAD_IMAGE,\n  };\n\n  void SetNeedsUIBeginFrame();\n  void SetNeedsRasterBeginFrame();\n  void SetOutputSurfaceState(OutputSurfaceState output_surface_state);\n  void SetMeaningfulLayoutState(MeaningfulLayoutState meaningful_layout_state);\n\n  Action NextAction() const;\n  void WillSendUIBeginFrame();\n  void WillCommit(bool commit_has_no_updates);\n  void DidCommit();\n  void WillActivate();\n  void WillDraw();\n  void DidDraw();\n  void WillDrawLayerTree();\n  void WillPerformRasterInvalidation();\n\n  // Indicates whether the LayerTree is visible.\n  void SetVisible(bool visible);\n  bool Visible() const { return visible_; }\n\n  // Indicates that the scheduler is ready to send a BeginFrame.\n  void NotifyReadyToSendUIBeginFrame();\n\n  // Call this only in response to receiving an Action::SEND_BEGIN_MAIN_FRAME\n  // from NextAction.\n  // Indicates that all painting is complete.\n  void NotifyReadyToCommit();\n\n  bool HasPendingTree() const;\n  bool NeedsUIBeginFrame() const { return needs_ui_begin_frame_; }\n\n  // Indicates that the pending tree is ready for activation. Returns whether\n  // the notification received updated the state for the current pending tree,\n  // if any.\n  bool NotifyReadyToActivate();\n  bool IsReadyToActivate() const;\n\n  // We should ensure that we have done the first draw successfully for the\n  // current active tree before we activate the next pending tree.\n  void NotifyActiveTreeHasBeenDrawn();\n\n  // Indicates the raster frame is ready to be executed, which means that the\n  // vsync or frame deadline reached.\n  void NotifyReadyToPerformRasterFrame();\n\n  bool RedrawPending() const { return needs_redraw_; }\n\n  // Indicates that the deadline task is posted, and the frame is ready to be\n  // drawn when the deadline reached or new UI Frame reached.\n  bool OnBeginRasterFrameDeadline();\n  // Indicates that the deadline is arrived.\n  void OnReachRasterFrameDeadline();\n  bool IsInsideRasterFrameDeadline() const;\n  bool ShouldIgnoreFrameDeadline() const;\n\n  // Indicates that the first frame has been drawn, excluding the force frame.\n  bool IsFirstFrameDrawn() const { return first_frame_drawn_; }\n\n  void OnUploadTaskRegistered();\n  void OnUploadImageComplete();\n\n private:\n  bool ShouldPerformRasterInvalidation() const;\n  bool ShouldAbortCurrentFrame() const;\n  bool ShouldDraw() const;\n  bool ShouldActivatePendingTree() const;\n  bool ShouldSendUIBeginFrame() const;\n  bool ShouldCommit() const;\n  bool ShouldUploadImage() const;\n\n  BeginFrameState begin_frame_state_ = BeginFrameState::IDLE;\n  OutputSurfaceState output_surface_state_ = OutputSurfaceState::LOST;\n  MeaningfulLayoutState meaningful_layout_state_ = MeaningfulLayoutState::DIRTY;\n  RasterState raster_state_ = RasterState::IDLE;\n  ImageUploadState image_upload_state_ = ImageUploadState::READY;\n\n  // The `needs_ui_begin_frame_` flag is set when a new ui frame is requested.\n  bool needs_ui_begin_frame_ = false;\n  // The `needs_raster_begin_frame_` flag is set when a new raster frame is\n  // requested.\n  bool needs_raster_begin_frame_ = false;\n\n  bool needs_upload_image_ = false;\n\n  bool visible_ = true;\n  bool ui_begin_frame_is_ready_to_send_ = false;\n  bool needs_redraw_ = false;\n  bool has_pending_tree_ = false;\n  bool pending_tree_is_ready_for_activation_ = false;\n  bool active_tree_has_been_drawn_ = true;\n  bool did_draw_layer_tree_ = false;\n  bool using_sync_compositor_ = false;\n  bool inside_raster_frame_deadline_ = false;\n  bool first_frame_committed_ = false;\n  bool first_frame_drawn_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SCHEDULER_SCHEDULER_STATE_MACHINE_H_\n"
  },
  {
    "path": "clay/shell/common/screenshot_utils.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/screenshot_utils.h\"\n\n#include <utility>\n\n#ifndef ENABLE_SKITY\n#include \"clay/flow/layers/offscreen_surface.h\"\n#endif  // ENABLE_SKITY\n#include <cstdlib>\n\n#include \"clay/fml/base64.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/paint_image_skity.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/shell/common/serialization_callbacks.h\"\n\nnamespace clay {\n\n#ifndef ENABLE_SKITY\nstatic sk_sp<SkData> ScreenshotLayerTreeAsPicture(\n    clay::LayerTree* tree, clay::CompositorContext& compositor_context,\n    uint32_t background_color) {\n  FML_DCHECK(tree != nullptr);\n  SkPictureRecorder recorder;\n  recorder.beginRecording(\n      SkRect::MakeWH(tree->frame_size().x, tree->frame_size().y));\n\n  SkMatrix root_surface_transformation;\n  root_surface_transformation.reset();\n\n  // TODO(amirh): figure out how to take a screenshot with embedded UIView.\n  // https://github.com/flutter/flutter/issues/23435\n  auto frame = compositor_context.AcquireFrame(\n      nullptr, recorder.getRecordingCanvas(), nullptr,\n      clay::ConvertSkMatrixToSkityMatrix(root_surface_transformation), false,\n      true);\n  frame->Raster(*tree, true, nullptr, background_color);\n\n#if defined(OS_FUCHSIA)\n  SkSerialProcs procs = {0};\n  procs.fImageProc = SerializeImageWithoutData;\n  procs.fTypefaceProc = SerializeTypefaceWithoutData;\n#else\n  SkSerialProcs procs = {nullptr};\n  procs.fTypefaceProc = SerializeTypefaceWithData;\n#endif  // OS_FUCHSIA\n\n  return recorder.finishRecordingAsPicture()->serialize(&procs);\n}\n\nstatic sk_sp<SkData> ScreenshotLayerTreeAsImage(\n    clay::LayerTree* tree, Surface* surface,\n    clay::CompositorContext& compositor_context, bool compressed,\n    uint32_t background_color) {\n  auto sk_image = TakeScreenshotWithOpaque(tree, surface, compositor_context,\n                                           true, background_color);\n  if (!sk_image) {\n    return nullptr;\n  }\n  // If the caller want the pixels to be compressed, there is a Skia utility to\n  // compress to PNG. Use that.\n  if (compressed) {\n    return sk_image->encodeToData();\n  }\n  // Copy it into a bitmap and return the same.\n  SkPixmap pixmap;\n  if (!sk_image->peekPixels(&pixmap)) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to obtain bitmap pixels\";\n    return nullptr;\n  }\n  return SkData::MakeWithCopy(pixmap.addr32(), pixmap.computeByteSize());\n}\n\n// static\nsk_sp<SkImage> TakeScreenshotWithOpaque(\n    clay::LayerTree* tree, Surface* surface,\n    clay::CompositorContext& compositor_context, bool opaque,\n    uint32_t background_color) {\n  if (!surface) {\n    FML_LOG(ERROR) << \"Failed to screenshot because surface is null\";\n    return nullptr;\n  }\n  auto surface_context = surface->GetContext();\n\n  // Attempt to create a snapshot surface depending on whether we have access\n  // to a valid GPU rendering context.\n  std::unique_ptr<OffscreenSurface> snapshot_surface =\n      std::make_unique<OffscreenSurface>(surface_context, tree->frame_size(),\n                                         opaque);\n\n  if (!snapshot_surface->IsValid()) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to create snapshot surface\";\n    return nullptr;\n  }\n\n  // Draw the current layer tree into the snapshot surface.\n  auto* canvas = snapshot_surface->GetCanvas();\n\n  // There is no root surface transformation for the screenshot layer. Reset\n  // the matrix to identity.\n  skity::Matrix root_surface_transformation;\n  root_surface_transformation.Reset();\n\n  // snapshot_surface->makeImageSnapshot needs the GL context to be set if the\n  // render context is GL. frame->Raster() pops the gl context in platforms\n  // that gl context switching are used. (For example, older iOS that uses GL)\n  // We reset the GL context using the context switch.\n  auto context_switch = surface->MakeRenderContextCurrent();\n  if (!context_switch->GetResult()) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to make image screenshot\";\n    return nullptr;\n  }\n\n  auto frame = compositor_context.AcquireFrame(\n      surface_context,              // skia context\n      canvas,                       // canvas\n      nullptr,                      // compositor state\n      root_surface_transformation,  // root surface transformation\n      false,                        // instrumentation enabled\n      true                          // render buffer readback supported\n  );                                // NOLINT\n  canvas->clear(SK_ColorTRANSPARENT);\n  frame->Raster(*tree, true, nullptr, background_color);\n  canvas->flush();\n\n  return snapshot_surface->GetRasterImage();\n}\n#else\nstatic std::shared_ptr<skity::Data> ScreenshotLayerTreeAsImage(\n    clay::LayerTree* tree, Surface* surface,\n    clay::CompositorContext& compositor_context, bool compressed,\n    uint32_t background_color) {\n  if (compressed) {\n    FML_LOG(ERROR) << \"Screenshot: 'compressed' not supported on skity yet. \";\n    return nullptr;\n  }\n\n  auto image = TakeScreenshotWithOpaque(tree, surface, compositor_context,\n                                        false, background_color);\n  if (!image) {\n    FML_DLOG(ERROR) << \"Screenshot: failed to snapshot a CPU-backed image\";\n    return nullptr;\n  }\n  auto pixmap = *image->GetPixmap();\n  if (!pixmap) {\n    FML_DLOG(ERROR) << \"Screenshot: failed to get pixmap from Image\";\n    return nullptr;\n  }\n\n  return skity::Data::MakeWithCopy(pixmap->Addr(),\n                                   pixmap->RowBytes() * pixmap->Height());\n}\n\nstd::shared_ptr<skity::Image> TakeScreenshotWithOpaque(\n    clay::LayerTree* tree, Surface* surface,\n    clay::CompositorContext& compositor_context, bool opaque,\n    uint32_t background_color) {\n  if (!surface) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to get surface\";\n    return nullptr;\n  }\n\n  auto* gpu_context = surface->GetContext();\n  if (!gpu_context) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to get gpu context\";\n    return nullptr;\n  }\n\n  auto context_switch = surface->MakeRenderContextCurrent();\n  if (!context_switch->GetResult()) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to make image screenshot\";\n    return nullptr;\n  }\n\n  uint32_t frame_width = tree->frame_size().x;\n  uint32_t frame_height = tree->frame_size().y;\n\n  skity::GPURenderTargetDescriptor desc;\n  desc.width = frame_width;\n  desc.height = frame_height;\n  desc.sample_count = 1;\n  auto render_target = gpu_context->CreateRenderTarget(desc);\n\n  if (!render_target) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to create offscreen RenderTarget\";\n    return nullptr;\n  }\n\n  auto skity_canvas = render_target->GetCanvas();\n\n  // There is no root surface transformation for the screenshot layer. Reset\n  // the matrix to identity.\n  skity::Matrix root_surface_transformation;\n  root_surface_transformation.Reset();\n\n  auto frame =\n      compositor_context.AcquireFrame(gpu_context, skity_canvas, nullptr,\n                                      root_surface_transformation, false, true);\n  skity_canvas->Clear(clay::Color::kTransparent());\n  frame->Raster(*tree, true, nullptr, background_color);\n\n  // Flush the canvas and snapshot a GPU-backed image.\n  auto image = gpu_context->MakeSnapshot(std::move(render_target));\n  if (!image) {\n    FML_DLOG(ERROR) << \"Screenshot: unable to snapshot offscreen RenderTarget\";\n    return nullptr;\n  }\n\n  // Make CPU-backed pixmap from the texture image.\n  auto pixmap = image->ReadPixels(gpu_context);\n  if (!pixmap) {\n    FML_DLOG(ERROR) << \"Screenshot: failed to read pixels from Image\";\n    return nullptr;\n  }\n\n  return skity::Image::MakeImage(pixmap);\n}\n#endif  // ENABLE_SKITY\n\n// static\nScreenshotData TakeScreenshotWithBase64(LayerTree* layer_tree,\n                                        ScreenshotData::ScreenshotType type,\n                                        Surface* surface,\n                                        CompositorContext* compositor_context,\n                                        bool base64_encode,\n                                        uint32_t background_color) {\n  if (layer_tree == nullptr) {\n    FML_DLOG(ERROR) << \"Last layer tree was null when screenshot.\";\n    return {};\n  }\n\n  GrDataPtr data = nullptr;\n#ifndef ENABLE_SKITY\n  switch (type) {\n    case ScreenshotData::ScreenshotType::SkiaPicture:\n      data = ScreenshotLayerTreeAsPicture(layer_tree, *compositor_context,\n                                          background_color);\n      break;\n    case ScreenshotData::ScreenshotType::UncompressedImage:\n      data = ScreenshotLayerTreeAsImage(\n          layer_tree, surface, *compositor_context, false, background_color);\n      break;\n    case ScreenshotData::ScreenshotType::CompressedImage:\n      data = ScreenshotLayerTreeAsImage(\n          layer_tree, surface, *compositor_context, true, background_color);\n      break;\n  }\n#else\n  switch (type) {\n    case ScreenshotData::ScreenshotType::SkiaPicture:\n      // Not supported.\n      break;\n    case ScreenshotData::ScreenshotType::UncompressedImage:\n      data = ScreenshotLayerTreeAsImage(\n          layer_tree, surface, *compositor_context, false, background_color);\n      break;\n    case ScreenshotData::ScreenshotType::CompressedImage:\n      FML_UNIMPLEMENTED();\n      break;\n  }\n#endif  // ENABLE_SKITY\n\n  if (data == nullptr) {\n    FML_DLOG(ERROR) << \"Screenshot data was null.\";\n    return {};\n  }\n\n  if (base64_encode) {\n    size_t b64_size =\n        fml::Base64::Encode(DATA_GET_DATA(data), DATA_GET_SIZE(data), nullptr);\n    GrDataPtr b64_data;\n#ifndef ENABLE_SKITY\n    b64_data = SkData::MakeUninitialized(b64_size);\n#else\n    if (b64_size == 0) {\n      b64_data = skity::Data::MakeEmpty();\n    } else {\n      void* pixels = malloc(b64_size);\n      b64_data = skity::Data::MakeFromMalloc(pixels, b64_size);\n    }\n#endif  // ENABLE_SKITY\n    fml::Base64::Encode(DATA_GET_DATA(data), DATA_GET_SIZE(data),\n                        DATA_GET_WRITABLE_DATA(b64_data));\n    return ScreenshotData{b64_data, layer_tree->frame_size()};\n  }\n\n  return ScreenshotData{data, layer_tree->frame_size()};\n}\n\n// static\nfml::RefPtr<PaintImage> TakeScreenshot(\n    std::unique_ptr<clay::LayerTree> layer_tree, Surface* surface,\n    CompositorContext* compositor_context) {\n  auto image = TakeScreenshotWithOpaque(layer_tree.get(), surface,\n                                        *compositor_context, false);\n  if (image) {\n#ifndef ENABLE_SKITY\n    return PaintImage::Make(image);\n#else\n    return PaintImageSkity::Make(image);\n#endif  // ENABLE_SKITY\n  }\n  return nullptr;\n}\n\n// static\nfml::RefPtr<PaintImage> TakeScreenshot(GrPicturePtr picture, skity::Vec2 size) {\n  FML_DCHECK(picture);\n  // Use 16384 as a proxy for the maximum texture size for a GPU image.\n  // This is meant to be large enough to avoid false positives in test contexts,\n  // but not so artificially large to be completely unrealistic on any platform.\n  // This limit is taken from the Metal specification. D3D, Vulkan, and GL\n  // generally have lower limits.\n  if (size.x > 16384 || size.y > 16384) {\n    return nullptr;\n  }\n#ifndef ENABLE_SKITY\n  sk_sp<SkSurface> surface =\n      SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(size.x, size.y));\n  auto canvas = surface->getCanvas();\n  canvas->clear(SK_ColorTRANSPARENT);\n  picture->playback(canvas);\n\n  sk_sp<SkImage> image = surface->makeImageSnapshot();\n  return PaintImage::Make(image);\n#else\n  auto bitmap = std::make_unique<skity::Bitmap>(\n      size.x, size.y, skity::AlphaType::kPremul_AlphaType);\n  auto canvas = skity::Canvas::MakeSoftwareCanvas(bitmap.get());\n  canvas->Clear(skity::Color_TRANSPARENT);\n  picture->Draw(canvas.get());\n  auto skity_image = skity::Image::MakeImage(bitmap->GetPixmap());\n  return PaintImageSkity::Make(skity_image);\n#endif  // ENABLE_SKITY\n}\n\nScreenshotData::ScreenshotData() = default;\n\nScreenshotData::ScreenshotData(GrDataPtr p_data, const skity::Vec2& p_size)\n    : data(std::move(p_data)), frame_size(p_size) {}\n\nScreenshotData::ScreenshotData(const ScreenshotData& other) = default;\n\nScreenshotData::~ScreenshotData() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/screenshot_utils.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_SHELL_COMMON_SCREENSHOT_UTILS_H_\n#define CLAY_SHELL_COMMON_SCREENSHOT_UTILS_H_\n\n#include <memory>\n\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/gfx/paint_image.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"skity/geometry/vector.hpp\"\n\nnamespace clay {\n\nstruct ScreenshotData {\n  enum class ScreenshotType {\n    SkiaPicture,\n    UncompressedImage,\n    CompressedImage,\n  };\n\n  GrDataPtr data;\n\n  skity::Vec2 frame_size = {0, 0};\n\n  ScreenshotData();\n  ScreenshotData(GrDataPtr p_data, const skity::Vec2& p_size);\n  ScreenshotData(const ScreenshotData& other);\n\n  ~ScreenshotData();\n};\n\nGrImagePtr TakeScreenshotWithOpaque(\n    clay::LayerTree* tree, Surface* surface,\n    clay::CompositorContext& compositor_context, bool opaque,\n    uint32_t background_color = Color::kTransparent().Value());\n\nScreenshotData TakeScreenshotWithBase64(\n    LayerTree* layer_tree, ScreenshotData::ScreenshotType type,\n    Surface* surface, CompositorContext* compositor_context, bool base64_encode,\n    uint32_t background_color = Color::kTransparent().Value());\n\nfml::RefPtr<PaintImage> TakeScreenshot(\n    std::unique_ptr<clay::LayerTree> layer_tree, Surface* surface,\n    CompositorContext* context);\nfml::RefPtr<PaintImage> TakeScreenshot(GrPicturePtr picture, skity::Vec2 size);\n};  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SCREENSHOT_UTILS_H_\n"
  },
  {
    "path": "clay/shell/common/scroll_fluency_monitor_delegate.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SCROLL_FLUENCY_MONITOR_DELEGATE_H_\n#define CLAY_SHELL_COMMON_SCROLL_FLUENCY_MONITOR_DELEGATE_H_\n\n#include <string>\n\n#include \"clay/shell/common/frame_timing_listener.h\"\n\nnamespace clay {\n\nclass ScrollFluencyMonitorDelegate : public FrameTimingListener {\n public:\n  virtual ~ScrollFluencyMonitorDelegate() = default;\n\n  virtual void StartFluencyMonitor(int id, const std::string& scene,\n                                   const std::string& scroll_monitor_tag,\n                                   int max_refresh_rate) = 0;\n  virtual void EndFluencyMonitor(int id) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SCROLL_FLUENCY_MONITOR_DELEGATE_H_\n"
  },
  {
    "path": "clay/shell/common/serialization_callbacks.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/serialization_callbacks.h\"\n\nnamespace clay {\n\n#ifndef ENABLE_SKITY\n\nsk_sp<SkData> SerializeTypefaceWithoutData(SkTypeface* typeface, void* ctx) {\n  return SkData::MakeEmpty();\n}\n\nsk_sp<SkData> SerializeTypefaceWithData(SkTypeface* typeface, void* ctx) {\n  return typeface->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);\n}\n\nsk_sp<SkTypeface> DeserializeTypefaceWithoutData(const void* data,\n                                                 size_t length, void* ctx) {\n  return SkTypeface::MakeDefault();\n}\n#endif  // ENABLE_SKITY\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/serialization_callbacks.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERIALIZATION_CALLBACKS_H_\n#define CLAY_SHELL_COMMON_SERIALIZATION_CALLBACKS_H_\n\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n#ifndef ENABLE_SKITY\nsk_sp<SkData> SerializeTypefaceWithoutData(SkTypeface* typeface, void* ctx);\nsk_sp<SkData> SerializeTypefaceWithData(SkTypeface* typeface, void* ctx);\nsk_sp<SkTypeface> DeserializeTypefaceWithoutData(const void* data,\n                                                 size_t length, void* ctx);\n#endif  // ENABLE_SKITY\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERIALIZATION_CALLBACKS_H_\n"
  },
  {
    "path": "clay/shell/common/services/animation_event_service_impl.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/animation_event_service_impl.h\"\n\n#include <string>\n\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/scroll_wrapper.h\"\n\nnamespace clay {\n\nnamespace {\n\nclay::ScrollView* FindTargetScrollView(Engine* engine, int32_t target_id) {\n  clay::BaseView* target_view =\n      engine->GetViewContext()->FindViewByViewId(target_id);\n  if (!target_view) {\n    return nullptr;\n  }\n  if (target_view->Is<clay::ScrollView>()) {\n    return static_cast<clay::ScrollView*>(target_view);\n  } else if (target_view->Is<clay::ScrollWrapper>()) {\n    return static_cast<clay::ScrollWrapper*>(target_view)->GetScrollView();\n  }\n  return nullptr;\n}\n\n}  // namespace\n\nvoid AnimationEventServiceImpl::OnAnimationEvent(\n    const clay::ElementId& element_id,\n    const clay::AnimationParams& animation_params) {\n  std::string animation_name = animation_params.animation_name;\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_->SelectTaskRunner<clay::Owner::kUI>(),\n      [engine = engine_, element_id, type = animation_params.event_type,\n       name = animation_name] {\n        if (engine) {\n          engine->OnAnimationEvent(element_id, {type, name.c_str()});\n        }\n      });\n}\n\nvoid AnimationEventServiceImpl::OnTransitionEvent(\n    const clay::ElementId& element_id,\n    const clay::AnimationParams& animation_params,\n    ClayAnimationPropertyType property_type) {\n  std::string animation_name = animation_params.animation_name;\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_->SelectTaskRunner<clay::Owner::kUI>(),\n      [engine = engine_, element_id, type = animation_params.event_type,\n       name = animation_name, property_type] {\n        if (engine) {\n          engine->OnTransitionEvent(element_id, {type, name.c_str()},\n                                    property_type);\n        }\n      });\n}\n\nvoid AnimationEventServiceImpl::OnScrolled(const clay::ElementId& element_id,\n                                           int32_t session_id,\n                                           float scroll_offset,\n                                           bool ignore_ui_repaint) {\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_->SelectTaskRunner<clay::Owner::kUI>(),\n      [engine = engine_, element_id = element_id.view_id(), session_id,\n       scroll_offset, ignore_ui_repaint] {\n        if (engine) {\n          clay::ScrollView* target_view =\n              FindTargetScrollView(engine.get(), element_id);\n          if (target_view) {\n            auto* raster_fling_manager = target_view->page_view()\n                                             ->nested_scroll_manager()\n                                             ->raster_fling_manager();\n            if (raster_fling_manager) {\n              raster_fling_manager->OnAnimationUpdate(\n                  target_view, session_id, scroll_offset, ignore_ui_repaint);\n            }\n          }\n        }\n      });\n}\n\nvoid AnimationEventServiceImpl::OnScrollEnd(const clay::ElementId& element_id,\n                                            int32_t session_id,\n                                            float scroll_offset,\n                                            float velocity) {\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_->SelectTaskRunner<clay::Owner::kUI>(),\n      [engine = engine_, element_id = element_id.view_id(), session_id,\n       scroll_offset, velocity] {\n        if (engine) {\n          clay::ScrollView* target_view =\n              FindTargetScrollView(engine.get(), element_id);\n          if (target_view) {\n            auto* raster_fling_manager = target_view->page_view()\n                                             ->nested_scroll_manager()\n                                             ->raster_fling_manager();\n            if (raster_fling_manager) {\n              raster_fling_manager->OnAnimationEnd(target_view, session_id,\n                                                   scroll_offset, velocity);\n            }\n          }\n        }\n      });\n}\n\nvoid AnimationEventServiceImpl::OnInit(clay::ServiceManager& service_manager,\n                                       const clay::UIServiceContext& ctx) {\n  task_runners_ = service_manager.GetTaskRunners();\n  engine_ = ctx.engine->GetWeakPtr();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/animation_event_service_impl.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_ANIMATION_EVENT_SERVICE_IMPL_H_\n#define CLAY_SHELL_COMMON_SERVICES_ANIMATION_EVENT_SERVICE_IMPL_H_\n\n#include <memory>\n\n#include \"clay/common/element_id.h\"\n#include \"clay/flow/services/animation_event_service.h\"\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/shell/common/engine.h\"\n\nnamespace clay {\n\nclass AnimationEventServiceImpl : public AnimationEventService {\n private:\n  void OnAnimationEvent(const clay::ElementId& element_id,\n                        const clay::AnimationParams& animation_params) override;\n\n  void OnTransitionEvent(const clay::ElementId& element_id,\n                         const clay::AnimationParams& animation_params,\n                         ClayAnimationPropertyType property_type) override;\n\n  void OnScrolled(const clay::ElementId& element_id, int32_t session_id,\n                  float scroll_offset, bool ignore_ui_repaint) override;\n\n  void OnScrollEnd(const clay::ElementId& element_id, int32_t session_id,\n                   float scroll_offset, float velocity) override;\n\n  void OnInit(clay::ServiceManager& service_manager,\n              const clay::UIServiceContext& ctx) override;\n\n  fml::WeakPtr<Engine> engine_;\n  std::shared_ptr<clay::ServiceTaskRunners> task_runners_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_ANIMATION_EVENT_SERVICE_IMPL_H_\n"
  },
  {
    "path": "clay/shell/common/services/animator_info_service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/animator_info_service.h\"\n\nnamespace clay {\n\nstd::shared_ptr<AnimatorInfoService> AnimatorInfoService::Create() {\n  return std::make_shared<AnimatorInfoService>();\n}\n\n/// Target time for the latest frame. See also `Shell::OnAnimatorBeginFrame`\n/// for when this time gets updated.\nfml::TimePoint AnimatorInfoService::GetLatestFrameTargetTime() const {\n  return latest_frame_target_time_.load();\n}\n\nvoid AnimatorInfoService::SetLatestFrameTargetTime(\n    fml::TimePoint frame_target_time) {\n  latest_frame_target_time_.store(frame_target_time);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/animator_info_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_ANIMATOR_INFO_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_ANIMATOR_INFO_SERVICE_H_\n\n#include <atomic>\n#include <memory>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/common/service/service.h\"\n\nnamespace clay {\n\nclass AnimatorInfoService\n    : public clay::Service<AnimatorInfoService, clay::Owner::kUI,\n                           clay::ServiceFlags::kMultiThread> {\n public:\n  static std::shared_ptr<AnimatorInfoService> Create();\n\n  /// Target time for the latest frame. See also `Shell::OnAnimatorBeginFrame`\n  /// for when this time gets updated.\n  fml::TimePoint GetLatestFrameTargetTime() const;\n\n  void SetLatestFrameTargetTime(fml::TimePoint frame_target_time);\n\n private:\n  std::atomic<fml::TimePoint> latest_frame_target_time_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_ANIMATOR_INFO_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/compositor/compositor_service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_COMPOSITOR_COMPOSITOR_SERVICE_IMPL_CC_\n#define CLAY_SHELL_COMMON_COMPOSITOR_COMPOSITOR_SERVICE_IMPL_CC_\n\n#include \"clay/shell/common/services/compositor/compositor_service.h\"\n\n#include <algorithm>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/flow/view_slicer.h\"\n#include \"clay/shell/common/rasterizer.h\"\n#include \"clay/shell/common/services/compositor/platform_overlay_service.h\"\n\nnamespace clay {\n\nbool CompositorService::SubmitFrame(\n    clay::GrContext* context, std::unique_ptr<SurfaceFrame> background_frame,\n    std::unique_ptr<CompositorState> compositor_state) {\n  TRACE_EVENT(\"clay\", \"FlutterPlatformViewsController::SubmitFrame\");\n\n  if (compositor_state->GetCompositionOrder().empty() &&\n      !had_hybrid_composited_) {\n    had_hybrid_composited_ = false;\n    return background_frame->Submit();\n  }\n\n  had_hybrid_composited_ = true;\n\n  // TODO(haoyoufeng.aji) support external overlay views\n  bool did_encode = true;\n  std::unordered_map<int64_t, OverlayData> platform_overlays;\n  std::vector<std::pair<SurfaceFrame::SubmitCallback, SurfaceFrame::SubmitInfo>>\n      submit_infos;\n  submit_infos.reserve(compositor_state->GetCompositionOrder().size() + 1);\n  std::unordered_map<int64_t, skity::Rect> view_rects;\n\n  for (int64_t view_id : compositor_state->GetCompositionOrder()) {\n    view_rects[view_id] =\n        compositor_state->GetViewParams()[view_id]->finalBoundingRect();\n  }\n\n  std::unordered_map<int64_t, skity::Rect> overlay_layers = SliceViews(\n      background_frame->GetCanvas(), compositor_state->GetCompositionOrder(),\n      compositor_state->GetSlices(), view_rects);\n\n  // background frame must come first since it's the \"current\" surface\n  background_frame->set_submit_info({.present_with_transaction = true});\n  did_encode &= background_frame->Encode();\n  submit_infos.push_back(background_frame->PrepareSubmit());\n\n  size_t required_overlay_layers = 0;\n  for (int64_t view_id : compositor_state->GetCompositionOrder()) {\n    std::unordered_map<int64_t, skity::Rect>::const_iterator overlay =\n        overlay_layers.find(view_id);\n    if (overlay == overlay_layers.end()) {\n      continue;\n    }\n    required_overlay_layers++;\n  }\n\n  CreateMissingSurfaces(required_overlay_layers, context);\n\n  int64_t overlay_id = 0;\n  for (int64_t view_id : compositor_state->GetCompositionOrder()) {\n    std::unordered_map<int64_t, skity::Rect>::const_iterator it =\n        overlay_layers.find(view_id);\n    if (it == overlay_layers.end()) {\n      continue;\n    }\n    auto& [_, overlay_rect] = *it;\n    CompositorSurface& compositor_surface = GetCompositorSurface();\n\n    std::unique_ptr<SurfaceFrame> frame =\n        compositor_surface.surface->AcquireFrame(\n            compositor_state->GetFrameSize());\n\n    // If frame is null, AcquireFrame already printed out an error message.\n    if (!frame) {\n      continue;\n    }\n    clay::GrCanvas* overlay_canvas = frame->GetCanvas();\n    int restore_count = CANVAS_GET_SAVE_COUNT(overlay_canvas);\n    CANVAS_SAVE(overlay_canvas);\n    CANVAS_CLIP_RECT(overlay_canvas, overlay_rect);\n    CANVAS_CLEAR(overlay_canvas, clay::Color::kTransparent());\n    CANVAS_TRANSLATE(overlay_canvas, -overlay_rect.X(), -overlay_rect.Y());\n    compositor_state->GetSlices()[view_id]->render_into(overlay_canvas);\n    CANVAS_RESTORE_TO_COUNT(overlay_canvas, restore_count);\n\n    frame->set_submit_info({.present_with_transaction = true});\n    did_encode &= frame->Encode();\n\n    platform_overlays[view_id] = OverlayData{\n        .rect = overlay_rect,                           //\n        .view_id = view_id,                             //\n        .overlay_id = overlay_id,                       //\n        .overlay = compositor_surface.platform_overlay  //\n    };\n    submit_infos.emplace_back(frame->PrepareSubmit());\n    overlay_id++;\n  }\n\n  std::vector<std::shared_ptr<PlatformOverlay>> unused_overlays =\n      RemoveUnusedSurfaces();\n  RecycleSurfaces();\n\n  PresentFrame present_frame{\n      .overlays = std::move(platform_overlays),\n      .compositor_params = std::move(compositor_state->GetViewParams()),\n      .composite_order = std::move(compositor_state->GetCompositionOrder()),\n      .unused_overlays = std::move(unused_overlays),\n      .submit_infos = std::move(submit_infos),\n  };\n\n  presenter_service_.Act([did_encode, present_frame = std::move(present_frame)](\n                             auto& impl) mutable {\n    // We do `did_encode` check here because present_frame needs to be\n    // destructed in Platform thread\n    if (did_encode) {\n      impl.Present(present_frame);\n    }\n  });\n\n  return did_encode;\n}\n\n// |clay::Service|\nvoid CompositorService::OnInit(clay::ServiceManager& service_manager,\n                               const clay::RasterServiceContext& ctx) {\n  presenter_service_ = service_manager.GetService<PresenterService>();\n  overlay_service_ = service_manager.GetService<PlatformOverlayService>();\n  raster_task_runner_ = service_manager.GetTaskRunners()\n                            ->SelectTaskRunner<clay::Owner::kRaster>();\n}\n// |clay::Service|\nvoid CompositorService::OnDestroy() {\n  presenter_service_ = nullptr;\n  overlay_service_ = nullptr;\n  raster_task_runner_ = nullptr;\n  compositor_surfaces_.clear();\n}\n\nvoid CompositorService::CreateMissingSurfaces(size_t required_surfaces,\n                                              clay::GrContext* context) {\n  if (required_surfaces <= compositor_surfaces_.size()) {\n    return;\n  }\n\n  compositor_surfaces_.reserve(required_surfaces);\n  auto created_surfaces = overlay_service_->CreatePlatformOverlay(\n      required_surfaces - compositor_surfaces_.size());\n  for (auto& surface : created_surfaces) {\n    compositor_surfaces_.emplace_back(CompositorSurface{\n        .platform_overlay = surface,\n        .surface = surface->GetOutputSurface()->CreateGPUSurface(context)});\n  }\n}\n\nCompositorSurface& CompositorService::GetCompositorSurface() {\n  CompositorSurface& result = compositor_surfaces_[available_layer_index_];\n  available_layer_index_++;\n  return result;\n}\n\nstd::vector<std::shared_ptr<PlatformOverlay>>\nCompositorService::RemoveUnusedSurfaces() {\n  std::vector<std::shared_ptr<PlatformOverlay>> results;\n  for (size_t i = available_layer_index_; i < compositor_surfaces_.size();\n       i++) {\n    results.push_back(compositor_surfaces_[i].platform_overlay);\n  }\n  // Leave at least one overlay layer, to work around cases where scrolling\n  // platform views under an app bar continually adds and removes an\n  // overlay layer. This logic could be removed if\n  // https://github.com/flutter/flutter/issues/150646 is fixed.\n  static constexpr size_t kLeakLayerCount = 1;\n  size_t erase_offset = std::max(available_layer_index_, kLeakLayerCount);\n  if (erase_offset < compositor_surfaces_.size()) {\n    compositor_surfaces_.erase(compositor_surfaces_.begin() + erase_offset,\n                               compositor_surfaces_.end());\n  }\n  return results;\n}\n\nvoid CompositorService::RecycleSurfaces() { available_layer_index_ = 0; }\n\nstd::shared_ptr<CompositorService> CompositorService::Create() {\n  return std::make_shared<CompositorService>();\n}\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_COMPOSITOR_COMPOSITOR_SERVICE_IMPL_CC_\n"
  },
  {
    "path": "clay/shell/common/services/compositor/compositor_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_COMPOSITOR_COMPOSITOR_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_COMPOSITOR_COMPOSITOR_SERVICE_H_\n\n#include <memory>\n#include <vector>\n\n#include \"clay/flow/compositor/compositor_state.h\"\n#include \"clay/flow/surface_frame.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/shell/common/services/compositor/platform_overlay_service.h\"\n#include \"clay/shell/common/services/compositor/presenter_service.h\"\n\nnamespace clay {\n\nstruct CompositorSurface {\n  std::shared_ptr<PlatformOverlay> platform_overlay;\n  std::unique_ptr<Surface> surface;\n};\n\nclass CompositorService\n    : public clay::Service<CompositorService, clay::Owner::kRaster> {\n public:\n  bool SubmitFrame(clay::GrContext* context,\n                   std::unique_ptr<SurfaceFrame> background_frame,\n                   std::unique_ptr<CompositorState> compositor_state);\n\n  static std::shared_ptr<CompositorService> Create();\n\n private:\n  // |clay::Service|\n  void OnInit(clay::ServiceManager& service_manager,\n              const clay::RasterServiceContext& ctx) override;\n  // |clay::Service|\n  void OnDestroy() override;\n\n  void CreateMissingSurfaces(size_t required_surfaces,\n                             clay::GrContext* context);\n\n  CompositorSurface& GetCompositorSurface();\n\n  std::vector<std::shared_ptr<PlatformOverlay>> RemoveUnusedSurfaces();\n\n  void RecycleSurfaces();\n\n  clay::Puppet<clay::Owner::kRaster, PresenterService> presenter_service_;\n  fml::RefPtr<fml::TaskRunner> raster_task_runner_;\n  bool had_hybrid_composited_ = false;\n\n  clay::Puppet<clay::Owner::kRaster, PlatformOverlayService> overlay_service_;\n\n  // The index of the entry in the layers_ vector that determines the beginning\n  // of the unused\n  // layers. For example, consider the following vector:\n  //  _____\n  //  | 0 |\n  /// |---|\n  /// | 1 | <-- available_layer_index_\n  /// |---|\n  /// | 2 |\n  /// |---|\n  ///\n  /// This indicates that entries starting from 1 can be reused meanwhile the\n  /// entry at position 0 cannot be reused.\n  size_t available_layer_index_ = 0;\n  std::vector<CompositorSurface> compositor_surfaces_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_COMPOSITOR_COMPOSITOR_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/compositor/platform_overlay_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_COMPOSITOR_PLATFORM_OVERLAY_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_COMPOSITOR_PLATFORM_OVERLAY_SERVICE_H_\n\n#include <memory>\n#include <vector>\n\n#include \"clay/common/service/service.h\"\n#include \"clay/flow/surface_frame.h\"\n#include \"clay/shell/common/output_surface.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nclass PlatformOverlay {\n public:\n  virtual ~PlatformOverlay() = default;\n\n  virtual fml::RefPtr<OutputSurface> GetOutputSurface() const = 0;\n\n  virtual void OnSurfaceUpdated() {}\n};\n\nstruct OverlayData {\n  skity::Rect rect;\n  int64_t view_id;\n  int64_t overlay_id;\n  std::shared_ptr<PlatformOverlay> overlay;\n};\n\n// PlatformOverlayService is a service that can create platform overlay.\n// It's called from raster thread, the implementation must be thread safe.\nclass PlatformOverlayService\n    : public clay::Service<PlatformOverlayService, clay::Owner::kPlatform,\n                           clay::ServiceFlags::kMultiThread> {\n public:\n  virtual std::vector<std::shared_ptr<PlatformOverlay>> CreatePlatformOverlay(\n      size_t num) = 0;\n\n  static std::shared_ptr<PlatformOverlayService> Create();\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_COMPOSITOR_PLATFORM_OVERLAY_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/compositor/platform_overlay_service_fallback.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/compositor/platform_overlay_service.h\"\n\nnamespace clay {\n\nstd::shared_ptr<PlatformOverlayService> PlatformOverlayService::Create() {\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/compositor/presenter_service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/compositor/presenter_service.h\"\n\n#include \"base/trace/native/trace_event.h\"\n\nnamespace clay {\n\nvoid PresenterService::Present(PresentFrame& frame) {\n  TRACE_EVENT(\"clay\", \"PresenterService::Present\");\n  OnBeforePresent();\n\n  for (const auto& [cb, info] : frame.submit_infos) {\n    cb(info);\n  }\n\n  for (const auto& [view_id, layer_data] : frame.overlays) {\n    UpdateOverlay(layer_data);\n    layer_data.overlay->OnSurfaceUpdated();\n  }\n\n  for (auto& view_id : GetPlatformViewsToDispose(frame.composite_order)) {\n    DisposePlatformView(view_id);\n  }\n\n  for (auto& [view_id, embedded_params] : frame.compositor_params) {\n    auto& current_composition_param = current_composition_params_[view_id];\n    if (current_composition_param != nullptr &&\n        *embedded_params == *current_composition_param) {\n      continue;\n    }\n    CompositePlatformView(view_id, *embedded_params);\n    current_composition_param = std::move(embedded_params);\n  }\n\n  RemoveUnusedLayers(frame.composite_order, frame.unused_overlays);\n\n  BringLayersIntoView(frame.composite_order, frame.overlays);\n\n  OnAfterPresent();\n}\n\nvoid PresenterService::MarkPlatformViewToDispose(int64_t view_id) {\n  views_to_dispose_.insert(view_id);\n}\n\nstd::vector<int64_t> PresenterService::GetPlatformViewsToDispose(\n    const std::vector<int64_t>& composition_order) {\n  std::vector<int64_t> views;\n  if (views_to_dispose_.empty()) {\n    return views;\n  }\n\n  std::unordered_set<int64_t> views_to_composite(composition_order.begin(),\n                                                 composition_order.end());\n  std::unordered_set<int64_t> views_to_delay_dispose;\n\n  for (int64_t view_id : views_to_dispose_) {\n    if (views_to_composite.count(view_id)) {\n      views_to_delay_dispose.insert(view_id);\n      continue;\n    }\n    views.push_back(view_id);\n    current_composition_params_.erase(view_id);\n  }\n  views_to_dispose_ = std::move(views_to_delay_dispose);\n  return views;\n}\n\nvoid PresenterService::RemoveUnusedLayers(\n    const std::vector<int64_t>& composite_order,\n    const std::vector<std::shared_ptr<PlatformOverlay>>& unused_overlays) {\n  for (auto& overlay : unused_overlays) {\n    DisposeOverlay(*overlay);\n  }\n\n  std::unordered_set<int64_t> composition_order_set(composite_order.begin(),\n                                                    composite_order.end());\n\n  for (const auto& [view_id, _] : current_composition_params_) {\n    if (composition_order_set.count(view_id) == 0) {\n      HidePlatformView(view_id);\n    }\n  }\n}\n\nvoid PresenterService::BringLayersIntoView(\n    const std::vector<int64_t>& composite_order,\n    const std::unordered_map<int64_t, OverlayData>& overlays) {\n  // TODO(haoyoufeng.aji) optimize unnecessary BringToFront\n  for (int64_t view_id : composite_order) {\n    BringPlatformViewToFront(view_id);\n    if (auto it = overlays.find(view_id); it != overlays.end()) {\n      BringOverlayToFront(*it->second.overlay);\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/compositor/presenter_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_COMPOSITOR_PRESENTER_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_COMPOSITOR_PRESENTER_SERVICE_H_\n\n#include <memory>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"clay/common/service/service.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/shell/common/services/compositor/platform_overlay_service.h\"\n\nnamespace clay {\n\nstruct PresentFrame {\n  std::unordered_map<int64_t, OverlayData> overlays;\n  std::unordered_map<int64_t, std::unique_ptr<EmbeddedViewParams>>\n      compositor_params;\n  std::vector<int64_t> composite_order;\n  std::vector<std::shared_ptr<PlatformOverlay>> unused_overlays;\n  std::vector<std::pair<SurfaceFrame::SubmitCallback, SurfaceFrame::SubmitInfo>>\n      submit_infos;\n};\n\nclass PresenterService\n    : public clay::Service<PresenterService, clay::Owner::kPlatform> {\n public:\n  void Present(PresentFrame &frame);\n\n  void MarkPlatformViewToDispose(int64_t id);\n\n  static std::shared_ptr<PresenterService> Create();\n\n private:\n  virtual void OnBeforePresent() {}\n  virtual void OnAfterPresent() {}\n\n  virtual void UpdateOverlay(const OverlayData &overlay_data) = 0;\n  // Organize the layers by their z indexes.\n  virtual void BringOverlayToFront(const PlatformOverlay &overlay) = 0;\n  virtual void DisposeOverlay(PlatformOverlay &overlay) = 0;\n\n  virtual void CompositePlatformView(int64_t id,\n                                     const EmbeddedViewParams &params) = 0;\n  virtual void BringPlatformViewToFront(int64_t id) = 0;\n  virtual void DisposePlatformView(int64_t id) = 0;\n  virtual void HidePlatformView(int64_t id) = 0;\n\n  std::vector<int64_t> GetPlatformViewsToDispose(\n      const std::vector<int64_t> &composition_order);\n  void RemoveUnusedLayers(\n      const std::vector<int64_t> &composite_order,\n      const std::vector<std::shared_ptr<PlatformOverlay>> &unused_overlays);\n  void BringLayersIntoView(\n      const std::vector<int64_t> &composite_order,\n      const std::unordered_map<int64_t, OverlayData> &overlays);\n\n  std::unordered_map<int64_t, std::unique_ptr<EmbeddedViewParams>>\n      current_composition_params_;\n\n  std::unordered_set<int64_t> views_to_dispose_;\n};\n\n}  // namespace clay\n   //\n#endif  // CLAY_SHELL_COMMON_SERVICES_COMPOSITOR_PRESENTER_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/compositor/presenter_service_fallback.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/compositor/presenter_service.h\"\n\nnamespace clay {\n\nstd::shared_ptr<PresenterService> PresenterService::Create() { return nullptr; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/drag_drop_service.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/drag_drop_service.h\"\n\n#include \"clay/shell/common/engine.h\"\n\nnamespace clay {\n// static\nstd::shared_ptr<DragDropService> DragDropService::Create() {\n  return std::make_shared<DragDropService>();\n}\n\nvoid DragDropService::OnInit(ServiceManager&, const UIServiceContext& ctx) {\n  drag_drop_manager_ =\n      std::make_unique<DragDropManager>(ctx.engine->GetPageView());\n}\n\nvoid DragDropService::OnPlatformDragLeave() {\n  if (drag_drop_manager_) {\n    drag_drop_manager_->DropLeave();\n  }\n}\n\nvoid DragDropService::OnPlatformDragDrop(\n    FloatPoint point, std::string drag_type, std::string content_text,\n    const std::list<std::unordered_map<std::string, std::string>>&\n        content_files) {\n  if (drag_drop_manager_) {\n    drag_drop_manager_->DragDrop(point, drag_type, content_text, content_files);\n  }\n}\n\nvoid DragDropService::OnPlatformDragEnterAndOver(FloatPoint point) {\n  if (drag_drop_manager_) {\n    drag_drop_manager_->DropEnterAndHover(point);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/drag_drop_service.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_DRAG_DROP_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_DRAG_DROP_SERVICE_H_\n\n#include <list>\n#include <memory>\n#include <string>\n#include <unordered_map>\n\n#include \"clay/common/service/service.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/ui/gesture/drag_drop_manager.h\"\n\nnamespace clay {\n\nclass DragDropService : public Service<DragDropService, clay::Owner::kUI> {\n public:\n  static std::shared_ptr<DragDropService> Create();\n\n  void OnPlatformDragLeave();\n  void OnPlatformDragDrop(\n      FloatPoint point, std::string drag_type, std::string content_text,\n      const std::list<std::unordered_map<std::string, std::string>>&\n          content_files);\n  void OnPlatformDragEnterAndOver(FloatPoint point);\n\n private:\n  void OnInit(ServiceManager&, const UIServiceContext& ctx) override;\n\n  std::unique_ptr<DragDropManager> drag_drop_manager_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_DRAG_DROP_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/gesture_mediate_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_GESTURE_MEDIATE_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_GESTURE_MEDIATE_SERVICE_H_\n\n#include \"clay/common/service/service.h\"\n#include \"clay/ui/gesture/hit_test_responsive_result.h\"\n#include \"clay/ui/gesture/scrollable_direction.h\"\n\nnamespace clay {\n\nclass GestureMediateService\n    : public clay::Service<GestureMediateService, clay::Owner::kPlatform,\n                           clay::ServiceFlags::kManualRegister> {\n public:\n  // Called by Clay\n  void UpdateResponsiveResult(HitTestResponsiveResult result) {\n    result_ = result;\n  }\n\n  // Called by Platform\n  void UpdateOuterScrollableDirection(ScrollableDirection direction) {\n    outer_scrollable_direction_ = direction;\n  }\n\n  // Called by Platform\n  HitTestResponsiveResult GetResponsiveResult() { return result_; }\n\n  // Called by Clay\n  ScrollableDirection GetOuterScrollableDirection() {\n    return outer_scrollable_direction_;\n  }\n\n private:\n  HitTestResponsiveResult result_;\n  ScrollableDirection outer_scrollable_direction_ = ScrollableDirection::kNone;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_GESTURE_MEDIATE_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/initialize_service.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/initialize_service.h\"\n\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n\nnamespace clay {\n// static\nstd::shared_ptr<InitializeService> InitializeService::Create() {\n  return std::make_shared<InitializeService>();\n}\n\nstd::unique_ptr<PageView> InitializeService::CreatePageView(\n    uint32_t id, std::shared_ptr<ServiceManager> service_manager,\n    fml::RefPtr<GPUUnrefQueue> unref_queue,\n    clay::TaskRunners task_runners) const {\n  return std::make_unique<PageView>(id, service_manager, unref_queue,\n                                    task_runners);\n}\n\nstd::shared_ptr<ViewContext> InitializeService::CreateViewContext(\n    PageView* root, ShadowNodeOwner* shadow_node_owner) {\n  return std::make_shared<ViewContext>(root, shadow_node_owner);\n}\n\nvoid InitializeService::OnInit(ServiceManager&, const UIServiceContext& ctx) {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/initialize_service.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_INITIALIZE_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_INITIALIZE_SERVICE_H_\n\n#include <memory>\n\n#include \"clay/common/service/service.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n\nnamespace clay {\n\nclass PageView;\nclass ViewContext;\n\nclass InitializeService : public Service<InitializeService, clay::Owner::kUI,\n                                         clay::ServiceFlags::kMultiThread> {\n public:\n  static std::shared_ptr<InitializeService> Create();\n\n  std::unique_ptr<PageView> CreatePageView(\n      uint32_t id, std::shared_ptr<ServiceManager> service_manager,\n      fml::RefPtr<GPUUnrefQueue> unref_queue,\n      clay::TaskRunners task_runners) const;\n\n  std::shared_ptr<ViewContext> CreateViewContext(\n      PageView* root, ShadowNodeOwner* shadow_node_owner);\n\n private:\n  void OnInit(ServiceManager&, const UIServiceContext& ctx) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_INITIALIZE_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/instrumentation_service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/instrumentation_service.h\"\n\n#include <algorithm>\n\n#include \"clay/shell/common/frame_timing_listener.h\"\n#include \"clay/shell/common/shell.h\"\n\nnamespace clay {\n\nstd::shared_ptr<InstrumentationService> InstrumentationService::Create() {\n  return std::make_shared<InstrumentationService>();\n}\n\nvoid InstrumentationService::OnInit(clay::ServiceManager& service_manager,\n                                    const clay::PlatformServiceContext& ctx) {\n  shell_ = ctx.shell;\n}\n\nvoid InstrumentationService::OnDestroy() { frame_timing_listeners_.clear(); }\n\nconst std::shared_ptr<clay::FrameTimingCollector>&\nInstrumentationService::GetFrameTimingCollector() const {\n  return shell_->GetFrameTimingCollector();\n}\n\nvoid InstrumentationService::AddFrameTimingListener(\n    std::shared_ptr<FrameTimingListener> listener) {\n  if (!listener) {\n    return;\n  }\n  frame_timing_listeners_.push_back(listener);\n}\n\nvoid InstrumentationService::RemoveFrameTimingListener(\n    std::shared_ptr<FrameTimingListener> listener) {\n  if (!listener) {\n    return;\n  }\n  frame_timing_listeners_.erase(\n      std::remove(frame_timing_listeners_.begin(),\n                  frame_timing_listeners_.end(), listener),\n      frame_timing_listeners_.end());\n}\n\nvoid InstrumentationService::UpdateRasterCacheInfo(\n    const std::vector<RasterCacheInfo>& raster_cache_info) {\n  shell_->UpdateRasterInfo(raster_cache_info);\n}\n\nvoid InstrumentationService::OnFrameRasterized(const FrameTiming& timing) {\n  if (!frame_timing_listeners_.empty()) {\n    int64_t frame_start_time_in_ns =\n        timing.Get(FrameTiming::kVsyncStart).ToEpochDelta().ToNanoseconds();\n    int64_t frame_finish_time_in_ns =\n        timing.Get(FrameTiming::kRasterFinish).ToEpochDelta().ToNanoseconds();\n    for (const auto& listener : frame_timing_listeners_) {\n      listener->OnFrameTiming(frame_start_time_in_ns, frame_finish_time_in_ns);\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/instrumentation_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_INSTRUMENTATION_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_INSTRUMENTATION_SERVICE_H_\n\n#include <memory>\n#include <vector>\n\n#include \"clay/common/service/service.h\"\n\nnamespace clay {\nclass FrameTimingCollector;\n}  // namespace clay\n\nnamespace clay {\n\nstruct RasterCacheInfo;\nclass FrameTiming;\nclass FrameTimingListener;\n\nclass InstrumentationService\n    : public clay::Service<InstrumentationService, clay::Owner::kPlatform> {\n public:\n  static std::shared_ptr<InstrumentationService> Create();\n\n  void UpdateRasterCacheInfo(\n      const std::vector<RasterCacheInfo>& raster_cache_info);\n  void OnFrameRasterized(const FrameTiming& timing);\n\n  const std::shared_ptr<clay::FrameTimingCollector>& GetFrameTimingCollector()\n      const;\n\n  void AddFrameTimingListener(std::shared_ptr<FrameTimingListener> listener);\n  void RemoveFrameTimingListener(std::shared_ptr<FrameTimingListener> listener);\n\n private:\n  void OnInit(clay::ServiceManager& service_manager,\n              const clay::PlatformServiceContext& ctx) override;\n  void OnDestroy() override;\n\n  Shell* shell_ = nullptr;\n  std::vector<std::shared_ptr<FrameTimingListener>> frame_timing_listeners_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_INSTRUMENTATION_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/platform_const_service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/platform_const_service.h\"\n\n#include <utility>\n\nnamespace clay {\n\nPlatformConstService::PlatformConstService(\n    const Settings& settings, bool is_gpu_disabled,\n    std::shared_ptr<DisplayManager> display_manager)\n    : settings_(settings),\n      is_gpu_disabled_sync_switch_(\n          std::make_shared<fml::SyncSwitch>(is_gpu_disabled)),\n      display_manager_(std::move(display_manager)) {}\n\nfml::Milliseconds PlatformConstService::GetFrameBudget() const {\n  std::scoped_lock<std::mutex> lock(display_manager_mutex_);\n  if (!display_manager_) {\n    return fml::kDefaultFrameBudget;\n  }\n\n  double display_refresh_rate = display_manager_->GetMainDisplayRefreshRate();\n  if (display_refresh_rate > 0) {\n    return fml::RefreshRateToFrameBudget(display_refresh_rate);\n  } else {\n    return fml::kDefaultFrameBudget;\n  }\n}\n\n// display_manager_ should be destroyed on platform thread.\nvoid PlatformConstService::OnDestroy() {\n  std::scoped_lock<std::mutex> lock(display_manager_mutex_);\n  display_manager_.reset();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/platform_const_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_PLATFORM_CONST_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_PLATFORM_CONST_SERVICE_H_\n\n#include <memory>\n#include <mutex>\n\n#include \"base/include/fml/synchronization/sync_switch.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/common/settings.h\"\n#include \"clay/shell/common/display_manager.h\"\n\nnamespace clay {\n\nclass PlatformConstService\n    : public clay::Service<PlatformConstService, clay::Owner::kPlatform,\n                           clay::ServiceFlags::kMultiThread |\n                               clay::ServiceFlags::kManualRegister> {\n public:\n  const std::shared_ptr<fml::SyncSwitch>& GetIsGPUDisabledSyncSwitch() const {\n    return is_gpu_disabled_sync_switch_;\n  }\n\n  const Settings& GetSettings() const { return settings_; }\n\n  fml::Milliseconds GetFrameBudget() const;\n\n  explicit PlatformConstService(\n      const Settings& settings, bool is_gpu_disabled,\n      std::shared_ptr<DisplayManager> display_manager);\n\n  void OnDestroy() override;\n\n private:\n  Settings settings_;\n  std::shared_ptr<fml::SyncSwitch> is_gpu_disabled_sync_switch_;\n\n  std::shared_ptr<DisplayManager> display_manager_;\n  mutable std::mutex display_manager_mutex_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_PLATFORM_CONST_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/raster_frame_service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/raster_frame_service.h\"\n\n#include <utility>\n\n#include \"build/build_config.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/flow/compositor_context.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/image/image_upload_manager.h\"\n#include \"clay/shell/common/rasterizer.h\"\n#include \"clay/shell/common/scheduler/scheduler.h\"\n#include \"clay/shell/common/services/platform_const_service.h\"\n#include \"clay/shell/common/services/vsync_waiter_service.h\"\n\nnamespace clay {\n\nstd::shared_ptr<RasterFrameService> RasterFrameService::Create() {\n  return std::make_shared<RasterFrameService>();\n}\n\nvoid RasterFrameService::OnInit(clay::ServiceManager& service_manager,\n                                const clay::RasterServiceContext& ctx) {\n  clay::Puppet<clay::Owner::kRaster, VsyncWaiterService> vsync_waiter_service =\n      service_manager.GetService<VsyncWaiterService>();\n  vsync_waiter_ = vsync_waiter_service->CreateVsyncWaiter(\n      service_manager.GetTaskRunners()\n          ->SelectTaskRunner<clay::Owner::kRaster>());\n  rasterizer_ = ctx.rasterizer;\n  page_unique_id_ = ctx.page_unique_id;\n  clay::Puppet<clay::Owner::kRaster, PlatformConstService>\n      platform_const_service =\n          service_manager.GetService<PlatformConstService>();\n  using_sync_compositor_ =\n      platform_const_service->GetSettings().enable_sync_compositor;\n  if (using_sync_compositor_) {\n    sync_compositor_service_ =\n        service_manager.GetService<SyncCompositorService>();\n  }\n\n  scheduler_ = std::make_unique<Scheduler>(this, using_sync_compositor_);\n  ui_frame_service_ = service_manager.GetService<UIFrameService>();\n  raster_frame_deadline_timer_ = std::make_unique<fml::OneshotTimer>(\n      service_manager.GetTaskRunners()\n          ->SelectTaskRunner<clay::Owner::kRaster>());\n}\n\nvoid RasterFrameService::OnDestroy() {\n  if (raster_frame_deadline_timer_) {\n    raster_frame_deadline_timer_->Stop();\n  }\n  StopSchedulerAndCleanLayerTree();\n}\n\nvoid RasterFrameService::SetOutputSurfaceValid(bool valid) {\n  scheduler_->SetOutputSurfaceValid(valid);\n}\n\nvoid RasterFrameService::SetMeaningfulLayout(bool meaningful_layout) {\n  scheduler_->SetMeaningfulLayout(meaningful_layout);\n}\n\nbool RasterFrameService::DemandDrawHw() { return scheduler_->OnDraw(); }\n\nvoid RasterFrameService::RequestRasterFrame() {\n  scheduler_->SetNeedsRasterBeginFrame();\n  RequestFrameSignal();\n}\n\nvoid RasterFrameService::RequestUIFrame(bool inside_ui_frame) {\n  scheduler_->SetNeedsUIBeginFrame();\n  if (!using_sync_compositor_) {\n    // Prioritize the UI frame to be scheduled if first frame has been drawn. it\n    // works fine even if the calling of `RequestUIFrame` is more frequent than\n    // vsync, because the state machine has already cover this case with pending\n    // tree.\n    // In order not to affect the first frame, we enable this logic only when\n    // the first frame has been drawn.\n    // In addition, we should also disable this logic if the `RequestUIFrame` is\n    // called during the same frame (inside_ui_frame == true).\n    if (!inside_ui_frame && scheduler_->IsFirstFrameDrawn()) {\n      scheduler_->NotifyReadyToSendUIBeginFrame();\n    }\n  }\n  RequestFrameSignal();\n}\n\nvoid RasterFrameService::ForceBeginFrame() {\n  if (pending_layer_tree_) {\n    scheduler_->NotifyReadyToActivate();\n  }\n  FML_DCHECK(!force_begin_frame_);\n  // There will be a Commit immediately after this.\n  // In force mode, we dont transfer the state machine.\n  force_begin_frame_ = true;\n}\n\nvoid RasterFrameService::Commit(\n    std::shared_ptr<LayerTree> layer_tree,\n    std::unique_ptr<FrameTimingsRecorder> recorder) {\n  // It's the response of ScheduledActionBeginFrame from UI thread if we are\n  // using async compositor. If we are using sync compositor, it's the Push\n  // request of LayerTree.\n  if (!layer_tree) {\n    CommitWithNoUpdates();\n  } else {\n    TRACE_EVENT(\"clay\", \"RasterFrameService::Commit\");\n    if (force_begin_frame_) {\n      // Should not have pending layer tree here, it has already activate in\n      // ForceBeginFrame.\n      force_begin_frame_ = false;\n\n      layer_tree->SetRequestNewFrame([weak_ptr = GetWeakPtr()] {\n        if (weak_ptr) {\n          weak_ptr->RequestRasterFrame();\n        }\n      });\n      // Force draw the layer_tree.\n      TRACE_EVENT(\"clay\", \"RasterFrameService::ForceDrawLayerTree\");\n      // Activate the layer tree and then notify scheduler to perform raster\n      // immediately for force draw.\n      active_layer_tree_ = std::move(layer_tree);\n      active_recorder_ = std::move(recorder);\n      scheduler_->SetNeedsRasterBeginFrame();\n      scheduler_->NotifyReadyToPerformRasterFrame();\n    } else {\n      committing_layer_tree_ = std::move(layer_tree);\n      committing_recorder_ = std::move(recorder);\n      if (!using_sync_compositor_) {\n        scheduler_->NotifyReadyToCommit(false);\n        RequestFrameSignal();\n      } else {\n        // Push a LayerTree by ClayView, try to trigger\n        // ScheduledActionBeginFrame and notify commit.\n        scheduler_->SetNeedsUIBeginFrame();\n      }\n    }\n  }\n}\n\nvoid RasterFrameService::CommitWithNoUpdates() {\n  TRACE_EVENT(\"clay\", \"RasterFrameService::CommitWithNoUpdates\");\n  if (force_begin_frame_) {\n    force_begin_frame_ = false;\n  } else {\n    if (!using_sync_compositor_) {\n      scheduler_->NotifyReadyToCommit(true);\n      RequestFrameSignal();\n    }\n  }\n}\n\nvoid RasterFrameService::ForceCommit(\n    std::shared_ptr<LayerTree> layer_tree,\n    std::unique_ptr<FrameTimingsRecorder> recorder) {\n  layer_tree->SetRequestNewFrame([weak_ptr = GetWeakPtr()] {\n    if (weak_ptr) {\n      weak_ptr->RequestRasterFrame();\n    }\n  });\n  // Force draw the layer_tree.\n  active_layer_tree_ = std::move(layer_tree);\n  active_recorder_ = std::move(recorder);\n  scheduler_->SetNeedsRasterBeginFrame();\n  scheduler_->NotifyReadyToPerformRasterFrame();\n}\n\nvoid RasterFrameService::RequestFrameSignal() {\n  if (using_sync_compositor_) {\n    // onDraw driven.\n    sync_compositor_service_.Act([](auto& impl) { impl.Invalidate(true); });\n  } else {\n    // vsync driven.\n    RequestVsync();\n  }\n}\n\nvoid RasterFrameService::RequestVsync() {\n  FML_DCHECK(!using_sync_compositor_);\n  if (vsync_requested_) {\n    return;\n  }\n  vsync_requested_ = true;\n  vsync_waiter_->AsyncWaitForVsync(\n      [weak_self =\n           GetWeakPtr()](std::unique_ptr<FrameTimingsRecorder> recorder) {\n        if (weak_self) {\n          weak_self->vsync_requested_ = false;\n          weak_self->OnVsync(std::move(recorder));\n        }\n      });\n}\n\nvoid RasterFrameService::OnVsync(\n    std::unique_ptr<FrameTimingsRecorder> recorder) {\n  frame_timings_recorder_ = std::move(recorder);\n  // The frame deadline time. If the UI frame is arrived, activate that new\n  // LayerTree, otherwise, draw the last active LayerTree if needed.\n  // Const the deadline time to 1/4 of the vsync period now, we may redefine\n  // it later based on historical raster-timings.\n  fml::TimePoint raster_frame_deadline =\n      frame_timings_recorder_->GetVsyncStartTime() +\n      (frame_timings_recorder_->GetVsyncTargetTime() -\n       frame_timings_recorder_->GetVsyncStartTime()) /\n          4;\n  const auto now = fml::TimePoint::Now();\n  if (now < raster_frame_deadline && scheduler_->BeginRasterFrameDeadline()) {\n    // Start the deadline timer if the scheduler has sent a UIBeginFrame.\n    ScheduledFrameDeadline(raster_frame_deadline - now);\n  }\n  scheduler_->OnBeginRasterFrame();\n  // There's bad cases that the `UIFrameService` might use an expired frame\n  // timings recorder, because the state machine might not transfer to\n  // `ScheduledActionBeginFrame` that may cause not consume the recorder, and\n  // this expired recorder might be used in the next `RequestUIFrame`. Reset\n  // it and let the `RequestUIFrame` construct a new recorder with current\n  // time.\n  frame_timings_recorder_.reset();\n}\n\nvoid RasterFrameService::OnFrameDeadlineArrived() {\n  TRACE_EVENT(\"clay\", \"RasterFrameService::FrameDeadline\");\n  scheduler_->OnReachRasterFrameDeadline();\n}\n\nvoid RasterFrameService::ScheduledActionBeginFrame() {\n  if (using_sync_compositor_) {\n    // Notify the scheduler to commit layer tree immediately if the UI thread\n    // has already push the LayerTree.\n    scheduler_->NotifyReadyToCommit(committing_layer_tree_ == nullptr);\n    return;\n  }\n  RequestFrameSignal();\n  std::unique_ptr<FrameTimingsRecorder> recorder =\n      std::move(frame_timings_recorder_);\n  if (!recorder) {\n    recorder = std::make_unique<FrameTimingsRecorder>();\n    const fml::TimePoint placeholder_time = fml::TimePoint::Now();\n    recorder->RecordVsync(placeholder_time, placeholder_time);\n  }\n  // Try to pull a new LayerTree from UI thread if it's async compositor.\n  // It will respond with RasterFrameService::Commit when it's done.\n  ui_frame_service_.Act([recorder = std::move(recorder)](auto& impl) mutable {\n    impl.BeginFrame(std::move(recorder));\n  });\n}\n\nvoid RasterFrameService::ScheduledActionCommit() {\n  FML_DCHECK(raster_frame_deadline_timer_);\n  if (!raster_frame_deadline_timer_->Stopped()) {\n    raster_frame_deadline_timer_->Stop();\n  }\n  FML_DCHECK(!pending_layer_tree_);\n  pending_layer_tree_ = std::move(committing_layer_tree_);\n  pending_recorder_ = std::move(committing_recorder_);\n  scheduler_->NotifyReadyToActivate();\n}\n\nvoid RasterFrameService::ScheduledActionActivePendingTree() {\n  FML_DCHECK(pending_layer_tree_);\n  if (pending_layer_tree_) {\n    if (!using_sync_compositor_) {\n      // Try to request next vsync in async mode.\n      RequestFrameSignal();\n    }\n    active_layer_tree_ = std::move(pending_layer_tree_);\n    active_recorder_ = std::move(pending_recorder_);\n    active_layer_tree_->SetRequestNewFrame([weak_ptr = GetWeakPtr()] {\n      if (weak_ptr) {\n        weak_ptr->RequestRasterFrame();\n      }\n    });\n  }\n}\n\nvoid RasterFrameService::ScheduledActionRasterInvalidate() {}\n\nvoid RasterFrameService::ScheduledActionDraw() {\n  FML_DCHECK(raster_frame_deadline_timer_);\n  RequestFrameSignal();\n  if (!raster_frame_deadline_timer_->Stopped()) {\n    raster_frame_deadline_timer_->Stop();\n  }\n  std::unique_ptr<FrameTimingsRecorder> recorder = std::move(active_recorder_);\n  if (!recorder) {\n    recorder = std::move(frame_timings_recorder_);\n    const fml::TimePoint placeholder_time = fml::TimePoint::Now();\n    if (!recorder) {\n      recorder = std::make_unique<FrameTimingsRecorder>();\n      recorder->RecordVsync(placeholder_time, placeholder_time);\n    }\n    recorder->RecordBuildStart(placeholder_time);\n    recorder->RecordBuildEnd(placeholder_time);\n  } else {\n    frame_timings_recorder_.reset();\n  }\n  TRACE_EVENT_WITH_FRAME_NUMBER(recorder, \"clay\",\n                                \"RasterFrameService::DrawLayerTree\");\n  rasterizer_->Draw(active_layer_tree_, std::move(recorder));\n  scheduler_->NotifyActiveTreeHasBeenDrawn();\n}\n\nvoid RasterFrameService::ScheduledFrameDeadline(fml::TimeDelta delay) {\n  FML_DCHECK(raster_frame_deadline_timer_);\n  if (!raster_frame_deadline_timer_->Stopped()) {\n    raster_frame_deadline_timer_->Stop();\n  }\n  // Start deadline timer.\n  raster_frame_deadline_timer_->Start(delay, [weak_ptr = GetWeakPtr()]() {\n    if (weak_ptr) {\n      weak_ptr->OnFrameDeadlineArrived();\n    }\n  });\n}\n\nvoid RasterFrameService::ScheduledActionUploadImage() {\n  uint64_t left_tasks =\n      ImageUploadManager::GetInstance().ProcessSingleTask(page_unique_id_);\n  scheduler_->OnImageUploadTaskFinished(left_tasks);\n}\n\nvoid RasterFrameService::PrepareForRecycle() { scheduler_->Resume(); }\n\nvoid RasterFrameService::CleanForRecycle() { StopSchedulerAndCleanLayerTree(); }\n\nvoid RasterFrameService::StopSchedulerAndCleanLayerTree() {\n  scheduler_->Stop();\n  active_layer_tree_.reset();\n  committing_layer_tree_.reset();\n  pending_layer_tree_.reset();\n#if OS_MAC\n  vsync_waiter_.reset();\n#endif\n}\n\nvoid RasterFrameService::NotifyUploadTaskRegistered() {\n  scheduler_->NotifyUploadTaskRegistered();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/raster_frame_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_RASTER_FRAME_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_RASTER_FRAME_SERVICE_H_\n\n#include <memory>\n\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/shell/common/scheduler/scheduler_client.h\"\n#include \"clay/shell/common/services/sync_compositor_service.h\"\n#include \"clay/shell/common/services/ui_frame_service.h\"\n#include \"clay/shell/common/vsync_waiter.h\"\n\nnamespace clay {\n\nclass Scheduler;\n\nclass RasterFrameService\n    : public clay::Service<RasterFrameService, clay::Owner::kRaster>,\n      public SchedulerClient {\n public:\n  static std::shared_ptr<RasterFrameService> Create();\n\n  void SetOutputSurfaceValid(bool valid);\n  void SetMeaningfulLayout(bool meaningful_layout);\n\n  // Ask the Rasterizer to submit a new Frame synchronously.\n  bool DemandDrawHw();\n\n  void RequestRasterFrame();\n  void RequestUIFrame(bool inside_ui_frame = false);\n\n  // Force begin UI frame, and Waitting the LayerTree to be commit. It's used\n  // when we need to force begin frame outside the state machine. Should call\n  // UIBeginFrame after this, otherwise the state machine may be broken.\n  // @see Engine::BeginFrameImmediately\n  void ForceBeginFrame();\n\n  // Commit the LayerTree, and then notify the scheduler to perform commit.\n  void Commit(std::shared_ptr<LayerTree> layer_tree,\n              std::unique_ptr<FrameTimingsRecorder> recorder);\n\n  // Commit with no updates, it's used when we are not able to build a new\n  // LayerTree. To avoid the state machine to break, we must do a commit action.\n  void CommitWithNoUpdates();\n\n  // Force commit & draw the LayerTree, it's only used for unittests. The\n  // shell_test is not able to handle the scheduler with state machine.\n  void ForceCommit(std::shared_ptr<LayerTree> layer_tree,\n                   std::unique_ptr<FrameTimingsRecorder> recorder);\n\n  void ScheduledActionBeginFrame() override;\n  void ScheduledActionCommit() override;\n  void ScheduledActionActivePendingTree() override;\n  void ScheduledActionRasterInvalidate() override;\n  void ScheduledActionDraw() override;\n  void ScheduledActionUploadImage() override;\n\n  void PrepareForRecycle();\n  void CleanForRecycle();\n\n  void NotifyUploadTaskRegistered();\n\n private:\n  void ScheduledFrameDeadline(fml::TimeDelta delay);\n  void RequestFrameSignal();\n  void RequestVsync();\n  void OnVsync(std::unique_ptr<FrameTimingsRecorder> recorder);\n\n  void OnFrameDeadlineArrived();\n\n  void OnInit(clay::ServiceManager& service_manager,\n              const clay::RasterServiceContext& ctx) override;\n  void OnDestroy() override;\n\n  void StopSchedulerAndCleanLayerTree();\n\n  Rasterizer* rasterizer_ = nullptr;\n  std::unique_ptr<Scheduler> scheduler_;\n\n  std::shared_ptr<VsyncWaiter> vsync_waiter_;\n  bool vsync_requested_ = false;\n  std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder_;\n  clay::Puppet<clay::Owner::kRaster, SyncCompositorService>\n      sync_compositor_service_;\n  bool using_sync_compositor_ = false;\n\n  clay::Puppet<clay::Owner::kRaster, UIFrameService> ui_frame_service_;\n  bool force_begin_frame_ = false;\n\n  std::unique_ptr<fml::OneshotTimer> raster_frame_deadline_timer_;\n\n  // We manage three layer trees: one being built, one being committed to\n  // pending, and one being active.\n  // The `committing_layer_tree_` is currently being built, will be committed to\n  // pending_layer_tree with appropriate timing.\n  std::shared_ptr<LayerTree> committing_layer_tree_;\n  std::unique_ptr<FrameTimingsRecorder> committing_recorder_;\n  // The `pending_layer_tree_` is currently being committed, will be activated\n  // with appropriate timing.\n  std::shared_ptr<LayerTree> pending_layer_tree_;\n  std::unique_ptr<FrameTimingsRecorder> pending_recorder_;\n  // The `active_layer_tree_` is currently being activated.\n  std::shared_ptr<LayerTree> active_layer_tree_;\n  std::unique_ptr<FrameTimingsRecorder> active_recorder_;\n\n  uint64_t page_unique_id_ = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_RASTER_FRAME_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/rasterizer_service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/rasterizer_service.h\"\n\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/shell/common/rasterizer.h\"\n\nnamespace clay {\n\nstd::shared_ptr<RasterizerService> RasterizerService::Create() {\n  return std::make_shared<RasterizerService>();\n}\n\nvoid RasterizerService::OnInit(clay::ServiceManager& service_manager,\n                               const clay::RasterServiceContext& ctx) {\n  rasterizer_ = ctx.rasterizer;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/rasterizer_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_RASTERIZER_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_RASTERIZER_SERVICE_H_\n\n#include <memory>\n\n#include \"clay/common/service/service.h\"\n\nnamespace clay {\n\nclass RasterizerService\n    : public clay::Service<RasterizerService, clay::Owner::kRaster> {\n public:\n  static std::shared_ptr<RasterizerService> Create();\n\n  Rasterizer* GetRasterizer() const { return rasterizer_; }\n\n private:\n  void OnInit(clay::ServiceManager&,\n              const clay::RasterServiceContext& ctx) override;\n\n  Rasterizer* rasterizer_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_RASTERIZER_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/screenshot_encoder.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/screenshot_encoder.h\"\n\n#include <algorithm>\n#include <cstdlib>\n#include <utility>\n\n#include \"clay/fml/base64.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nnamespace {\nfloat GetScale(int original_width, int original_height, int max_width,\n               int max_height) {\n  float scaling_width = 1.0;\n  float scaling_height = 1.0;\n\n  if (max_width != 0 && max_height != 0 &&\n      ((original_width > max_width) || (original_height > max_height))) {\n    scaling_width = max_width / (float)(original_width);\n    scaling_height = max_height / (float)(original_height);\n  }\n  return std::min(scaling_width, scaling_height);\n}\n}  // namespace\n\n// static\nScreenshotEncodeResult ScreenshotEncoder::ScaleAndEncode(\n    ScreenshotData screenshot, const clay::ScreenshotRequest& request) {\n  auto data = screenshot.data;\n  if (!data) {\n    FML_DLOG(ERROR) << \"Failed to take screenshot with empty data!\";\n    return {};\n  }\n  if (request.page_width_ == 0 || request.page_height_ == 0) {\n    FML_DLOG(ERROR) << \"Take screenshot but image width or size is zero!\";\n    return {};\n  }\n\n#ifndef ENABLE_SKITY\n  // Declare scaled_bitmap outside the ScaleImage function to prevent the local\n  // variable from being released when the function stack unwinds which may\n  // cause the dangling pointer exception during Encode caused by the\n  // invalidation of the pixelRef.\n  SkBitmap scaled_bitmap;\n  SkPixmap scaled_pixmap = ScaleImage(screenshot, scaled_bitmap, request);\n  return Encode(scaled_pixmap, request);\n#else\n  std::shared_ptr<skity::Pixmap> scaled_pixmap =\n      ScaleImage(screenshot, request);\n  return Encode(scaled_pixmap, request);\n#endif  // ENABLE_SKITY\n}\n\n#ifndef ENABLE_SKITY\nSkPixmap ScreenshotEncoder::ScaleImage(ScreenshotData screenshot,\n                                       SkBitmap& scaled_bitmap,\n                                       const clay::ScreenshotRequest& request) {\n  auto data = screenshot.data;\n  size_t image_width = screenshot.frame_size.x;\n  size_t image_height = screenshot.frame_size.y;\n\n  auto image_info = SkImageInfo::Make(\n      image_width, image_height, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);\n  auto image = SkImages::RasterFromData(image_info, data, image_width * 4);\n\n  if (!image) {\n    FML_DLOG(ERROR) << \"Take screenshot but image is null!\";\n    return SkPixmap();\n  }\n\n  float scale = GetScale(image_width, image_height, request.max_width_,\n                         request.max_height_);\n  if (scale == 1.f) {\n    SkPixmap pixmap;\n    if (!image->peekPixels(&pixmap)) {\n      FML_DLOG(ERROR) << \"TakeSnapshot, peekPixels failed!\";\n      return SkPixmap();\n    }\n    return pixmap;\n  }\n  int scaled_width = scale * image_width;\n  int scaled_height = scale * image_height;\n\n  SkImageInfo scaled_image_info = image->imageInfo().makeDimensions(\n      SkISize::Make(scaled_width, scaled_height));\n\n  if (!scaled_bitmap.tryAllocPixels(scaled_image_info)) {\n    FML_DLOG(ERROR) << \"TakeSnapshot, tryAllocPixels failed!\";\n    return SkPixmap();\n  }\n\n  SkPixmap scaled_pixmap;\n  if (!scaled_bitmap.peekPixels(&scaled_pixmap)) {\n    FML_DLOG(ERROR) << \"TakeSnapshot, peekPixels failed!\";\n    return SkPixmap();\n  }\n\n  if (!image->scalePixels(\n          scaled_pixmap,\n          SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),\n          SkImage::kDisallow_CachingHint)) {\n    FML_DLOG(ERROR) << \"TakeSnapshot, scalePixels failed!\";\n    return SkPixmap();\n  }\n\n  return scaled_pixmap;\n}\n\n// static\nScreenshotEncodeResult ScreenshotEncoder::Encode(\n    const SkPixmap& pixmap, const clay::ScreenshotRequest& request) {\n  if (pixmap.addr() == nullptr) {\n    FML_DLOG(ERROR) << \"Encode screenshot but pixmap is null!\";\n    return {};\n  }\n\n  clay::ScreenMetadata metadata;\n  metadata.device_width_ = request.page_width_;\n  metadata.device_height_ = request.page_height_;\n  metadata.page_scale_factor_ = request.screen_scale_factor_;\n\n  SkDynamicMemoryWStream sk_stream;\n\n  switch (request.type_) {\n    case clay::ScreenshotType::JPEG: {\n      SkJpegEncoder::Options options;\n      if (request.quality_ >= 0 && request.quality_ <= 100) {\n        options.fQuality = request.quality_;\n      }\n      if (!SkJpegEncoder::Encode(&sk_stream, pixmap, options)) {\n        FML_DLOG(ERROR) << \"TakeSnapshot, SkJpegEncoder::Encode failed!\";\n        return {};\n      }\n      break;\n    }\n    case clay::ScreenshotType::WEBP: {\n      FML_UNIMPLEMENTED();\n      FML_DCHECK(false);\n      return {};\n      break;\n    }\n    case clay::ScreenshotType::PNG: {\n      SkPngEncoder::Options options;\n      if (!SkPngEncoder::Encode(&sk_stream, pixmap, options)) {\n        FML_DLOG(ERROR) << \"TakeSnapshot, SkPngEncoder::Encode failed!\";\n        return {};\n      }\n      break;\n    }\n    case clay::ScreenshotType::BITMAP: {\n      sk_sp<SkData> data =\n          SkData::MakeWithCopy(pixmap.addr32(), pixmap.computeByteSize());\n      metadata.timestamp_ =\n          std::chrono::steady_clock::now().time_since_epoch().count();\n      return {data, metadata};\n    }\n    default:\n      FML_DCHECK(false);\n      return {};\n  }\n\n  auto image_data = sk_stream.detachAsData();\n  if (!image_data) {\n    return {};\n  }\n  size_t length =\n      fml::Base64::Encode(image_data->data(), image_data->size(), nullptr);\n  auto base64_data = SkData::MakeUninitialized(length);\n\n  fml::Base64::Encode(image_data->data(), image_data->size(),\n                      base64_data->writable_data());\n\n  metadata.timestamp_ =\n      std::chrono::steady_clock::now().time_since_epoch().count();\n  return {base64_data, metadata};\n}\n#else\nstd::shared_ptr<skity::Pixmap> ScreenshotEncoder::ScaleImage(\n    ScreenshotData screenshot, const clay::ScreenshotRequest& request) {\n  auto data = screenshot.data;\n  size_t image_width = screenshot.frame_size.x;\n  size_t image_height = screenshot.frame_size.y;\n\n  std::shared_ptr<skity::Data> skity_data =\n      skity::Data::MakeWithCopy(data->RawData(), data->Size());\n  std::shared_ptr<skity::Pixmap> pixmap = std::make_shared<skity::Pixmap>(\n      std::move(skity_data), image_width, image_height);\n\n  float scale = GetScale(image_width, image_height, request.max_width_,\n                         request.max_height_);\n  if (scale == 1.f) {\n    return pixmap;\n  }\n\n  auto image = skity::Image::MakeImage(std::move(pixmap));\n  if (!image) {\n    FML_DLOG(ERROR) << \"Take screenshot but image is null!\";\n    return nullptr;\n  }\n\n  // Scale image.\n  int scaled_width = scale * image_width;\n  int scaled_height = scale * image_height;\n  std::shared_ptr<skity::Pixmap> scaled_pixmap =\n      std::make_shared<skity::Pixmap>(scaled_width, scaled_height);\n\n  if (!image->ScalePixels(scaled_pixmap, nullptr,\n                          skity::SamplingOptions(skity::FilterMode::kLinear,\n                                                 skity::MipmapMode::kNone))) {\n    FML_DLOG(ERROR) << \"TakeSnapshot, scalePixels failed!\";\n    return nullptr;\n  }\n\n  return scaled_pixmap;\n}\n\nScreenshotEncodeResult ScreenshotEncoder::Encode(\n    std::shared_ptr<skity::Pixmap> pixmap,\n    const clay::ScreenshotRequest& request) {\n  if (!pixmap) {\n    return {};\n  }\n\n  clay::ScreenMetadata metadata;\n  metadata.device_width_ = request.page_width_;\n  metadata.device_height_ = request.page_height_;\n  metadata.page_scale_factor_ = request.screen_scale_factor_;\n\n  if (request.type_ == clay::ScreenshotType::BITMAP) {\n    std::shared_ptr<skity::Data> data = skity::Data::MakeWithCopy(\n        pixmap->Addr(), pixmap->RowBytes() * pixmap->Height());\n    metadata.timestamp_ =\n        std::chrono::steady_clock::now().time_since_epoch().count();\n    return {data, metadata};\n  }\n  if (request.type_ != clay::ScreenshotType::JPEG) {\n    FML_DLOG(ERROR) << \"Skity can only encode JPEG image for now!\";\n    return {};\n  }\n\n  // Encode image.\n  auto codec = skity::Codec::MakeJPEGCodec();\n  if (!codec) {\n    return {};\n  }\n  auto jpeg_data = codec->Encode(pixmap.get());\n  if (!jpeg_data) {\n    FML_DLOG(ERROR) << \"TakeSnapshot, Skity encode failed!\";\n    return {};\n  }\n  std::shared_ptr<skity::Data> image_data =\n      skity::Data::MakeWithCopy(jpeg_data->RawData(), jpeg_data->Size());\n  if (!image_data) {\n    return {};\n  }\n  size_t length =\n      fml::Base64::Encode(image_data->RawData(), image_data->Size(), nullptr);\n  void* pixels = malloc(length);\n  std::shared_ptr<skity::Data> base64_data =\n      skity::Data::MakeFromMalloc(pixels, length);\n\n  fml::Base64::Encode(image_data->RawData(), image_data->Size(),\n                      const_cast<void*>(base64_data->RawData()));\n\n  metadata.timestamp_ =\n      std::chrono::steady_clock::now().time_since_epoch().count();\n  return {base64_data, metadata};\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/screenshot_encoder.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_SCREENSHOT_ENCODER_H_\n#define CLAY_SHELL_COMMON_SERVICES_SCREENSHOT_ENCODER_H_\n\n#include <memory>\n\n#include \"clay/common/graphics/screenshot.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/shell/common/screenshot_utils.h\"\n\nnamespace clay {\n\nstruct ScreenshotEncodeResult {\n  GrDataPtr data = nullptr;\n  clay::ScreenMetadata metadata;\n};\n\nclass ScreenshotEncoder {\n public:\n  static ScreenshotEncodeResult ScaleAndEncode(\n      ScreenshotData screenshot, const clay::ScreenshotRequest& request);\n\n#ifndef ENABLE_SKITY\n  static ScreenshotEncodeResult Encode(const SkPixmap& pixmap,\n                                       const clay::ScreenshotRequest& request);\n#else\n  static ScreenshotEncodeResult Encode(std::shared_ptr<skity::Pixmap> pixmap,\n                                       const clay::ScreenshotRequest& request);\n#endif\n\n private:\n#ifndef ENABLE_SKITY\n  static SkPixmap ScaleImage(ScreenshotData screenshot, SkBitmap& scaled_bitmap,\n                             const clay::ScreenshotRequest& request);\n#else\n  static std::shared_ptr<skity::Pixmap> ScaleImage(\n      ScreenshotData screenshot, const clay::ScreenshotRequest& request);\n#endif\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_SCREENSHOT_ENCODER_H_\n"
  },
  {
    "path": "clay/shell/common/services/screenshot_service.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/screenshot_service.h\"\n\n#include <utility>\n\n#include \"clay/shell/common/services/screenshot_encoder.h\"\n#include \"clay/shell/common/shell.h\"\n#include \"clay/ui/common/isolate.h\"\n\nnamespace clay {\n\nstd::shared_ptr<ScreenshotService> ScreenshotService::Create() {\n  return std::make_shared<ScreenshotService>();\n}\n\nvoid ScreenshotService::OnInit(clay::ServiceManager& service_manager,\n                               const clay::PlatformServiceContext& ctx) {\n  shell_ = ctx.shell;\n}\n\nvoid ScreenshotService::SetExternalScreenshotCallback(\n    clay::ExternalScreenshotCallback callback) {\n  external_screenshot_callback_ = std::move(callback);\n}\n\nGrDataPtr ScreenshotService::TakeScreenshotHardware(\n    const clay::ScreenshotRequest& request) {\n  if (external_screenshot_callback_) {\n    return TakeExternalScreenshot(request);\n  }\n\n  if (request.is_sync_) {\n    auto screenshot = shell_->ScreenshotSync(\n        ScreenshotData::ScreenshotType::UncompressedImage,\n        request.background_color_);\n    if (request.type_ == clay::ScreenshotType::BITMAP) {\n      return screenshot.data;\n    } else {\n      auto result = ScreenshotEncoder::ScaleAndEncode(screenshot, request);\n      return result.data;\n    }\n  }\n\n  if (!request.callback_.has_value()) {\n    FML_DLOG(ERROR) << \"Has requested an asynchronous screenshot, but there \"\n                       \"is no callback.\";\n    return nullptr;\n  }\n  shell_->ScreenshotAsync(\n      ScreenshotData::ScreenshotType::UncompressedImage,\n      request.background_color_, [request](ScreenshotData screenshot) {\n        auto data = screenshot.data;\n        if (!data) {\n          return;\n        }\n        auto task_runner = request.task_runner_\n                               ? request.task_runner_\n                               : clay::Isolate::Instance().GetIOTaskRunner();\n        task_runner->PostTask([screenshot, request]() {\n          auto result = ScreenshotEncoder::ScaleAndEncode(screenshot, request);\n          if (result.data) {\n            result.metadata.timestamp_ =\n                std::chrono::steady_clock::now().time_since_epoch().count();\n            request.callback_.value()(result.data, result.metadata);\n          }\n        });\n      });\n  return nullptr;\n}\n\nGrDataPtr ScreenshotService::TakeExternalScreenshot(\n    const clay::ScreenshotRequest& request) {\n  if (!external_screenshot_callback_) {\n    return nullptr;\n  }\n\n  clay::GrImagePtr screenshot = external_screenshot_callback_();\n\n#ifndef ENABLE_SKITY\n  if (!screenshot) {\n    return nullptr;\n  }\n  SkPixmap pixmap;\n  screenshot->peekPixels(&pixmap);\n#else\n  if (!screenshot || !screenshot->GetPixmap()) {\n    return nullptr;\n  }\n  std::shared_ptr<skity::Pixmap> pixmap = *(screenshot->GetPixmap());\n#endif  // ENABLE_SKITY\n\n  if (request.is_sync_) {\n    auto result = ScreenshotEncoder::Encode(pixmap, request);\n    return result.data;\n  }\n\n  if (!request.callback_.has_value()) {\n    FML_DLOG(ERROR) << \"Has requested an asynchronous screenshot, but there \"\n                       \"is no callback.\";\n    return nullptr;\n  }\n  auto task_runner = request.task_runner_\n                         ? request.task_runner_\n                         : clay::Isolate::Instance().GetIOTaskRunner();\n  task_runner->PostTask([screenshot, pixmap, request]() {\n    ScreenshotEncodeResult result = ScreenshotEncoder::Encode(pixmap, request);\n    if (result.data) {\n      result.metadata.timestamp_ =\n          std::chrono::steady_clock::now().time_since_epoch().count();\n      request.callback_.value()(result.data, result.metadata);\n    }\n  });\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/screenshot_service.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_SCREENSHOT_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_SCREENSHOT_SERVICE_H_\n\n#include <memory>\n\n#include \"clay/common/graphics/screenshot.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/common/service/service_manager.h\"\n\nnamespace clay {\n\nclass ScreenshotService\n    : public clay::Service<ScreenshotService, clay::Owner::kPlatform> {\n public:\n  static std::shared_ptr<ScreenshotService> Create();\n\n  void SetExternalScreenshotCallback(clay::ExternalScreenshotCallback callback);\n\n  GrDataPtr TakeScreenshotHardware(\n      const clay::ScreenshotRequest& screenshot_request);\n\n private:\n  void OnInit(clay::ServiceManager&,\n              const clay::PlatformServiceContext& ctx) override;\n\n  GrDataPtr TakeExternalScreenshot(\n      const clay::ScreenshotRequest& screenshot_request);\n\n  clay::ExternalScreenshotCallback external_screenshot_callback_ = nullptr;\n  Shell* shell_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_SCREENSHOT_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/sync_compositor_service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/sync_compositor_service.h\"\n\n#include <utility>\n\n#include \"clay/shell/common/platform_view.h\"\n#include \"clay/shell/common/services/ui_frame_service.h\"\n#include \"clay/shell/common/shell.h\"\n\nnamespace clay {\n\nstd::shared_ptr<SyncCompositorService> SyncCompositorService::Create() {\n  return std::make_shared<SyncCompositorService>();\n}\n\nvoid SyncCompositorService::Invalidate(bool is_raster_frame) {\n  if (shell_ && !has_invalidated_) {\n    shell_->OnPostInvalidate(is_raster_frame);\n    has_invalidated_ = true;\n  }\n}\n\nvoid SyncCompositorService::OnFirstMeaningfulLayout() {\n  first_meaningful_layout_ = true;\n  shell_->OnPostInvalidate(false);\n}\n\nvoid SyncCompositorService::DemandDrawHw(bool force_draw) {\n  has_invalidated_ = false;\n  if (!first_meaningful_layout_) {\n    // First meaningful layout is not ready, ignore this frame.\n    return;\n  }\n\n  if (force_draw) {\n    clay::Puppet<clay::Owner::kPlatform, RasterFrameService>\n        raster_frame_service =\n            shell_->GetServiceManager()->GetService<RasterFrameService>();\n    raster_frame_service.Act(\n        [](auto& impl) mutable { impl.RequestRasterFrame(); });\n  }\n\n  clay::Puppet<clay::Owner::kPlatform, UIFrameService> ui_frame_service =\n      shell_->GetServiceManager()->GetService<UIFrameService>();\n  // Submit a frame future, and it will be consumed in Android RenderThread.\n  std::promise<bool> promise;\n  platform_view_->SubmitFrameFuture(promise.get_future());\n  // Now it's time to do BeginFrame and push LayerTree, which will be scheduled\n  // by StateMachine.\n  ui_frame_service.Act([promise = std::move(promise)](auto& impl) mutable {\n    impl.DemandDrawHw(std::move(promise));\n  });\n}\n\nvoid SyncCompositorService::OnInit(clay::ServiceManager& service_manager,\n                                   const clay::PlatformServiceContext& ctx) {\n  shell_ = ctx.shell;\n  platform_view_ = ctx.platform_view;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/sync_compositor_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_SYNC_COMPOSITOR_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_SYNC_COMPOSITOR_SERVICE_H_\n#include <memory>\n\n#include \"clay/common/service/service.h\"\n\nnamespace clay {\n\nclass SyncCompositorService\n    : public clay::Service<SyncCompositorService, clay::Owner::kPlatform> {\n public:\n  static std::shared_ptr<SyncCompositorService> Create();\n  void Invalidate(bool is_raster_frame);\n\n  void OnFirstMeaningfulLayout();\n  void DemandDrawHw(bool force_draw);\n\n private:\n  void OnInit(clay::ServiceManager& service_manager,\n              const clay::PlatformServiceContext& ctx) override;\n\n  Shell* shell_ = nullptr;\n  PlatformView* platform_view_ = nullptr;\n  bool first_meaningful_layout_ = false;\n  bool has_invalidated_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_SYNC_COMPOSITOR_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/ui_frame_service.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/services/ui_frame_service.h\"\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/common/engine.h\"\n#include \"clay/shell/common/services/platform_const_service.h\"\n#include \"clay/shell/common/services/raster_frame_service.h\"\n\nnamespace clay {\n\nstruct RasterFrameServicePuppet {\n  explicit RasterFrameServicePuppet(\n      clay::Puppet<clay::Owner::kUI, RasterFrameService> puppet)\n      : puppet_(std::move(puppet)) {}\n  clay::Puppet<clay::Owner::kUI, RasterFrameService> puppet_;\n};\n\nstd::shared_ptr<UIFrameService> UIFrameService::Create() {\n  return std::make_shared<UIFrameService>();\n}\n\nvoid UIFrameService::RequestFrame() {\n  if (frame_requested_) {\n    return;\n  }\n  frame_requested_ = true;\n  if (using_sync_compositor_) {\n    sync_compositor_service_.Act([](auto& impl) { impl.Invalidate(false); });\n  } else {\n    raster_frame_service_->puppet_.Act(\n        [inside_ui_frame = inside_ui_frame_](auto& impl) {\n          impl.RequestUIFrame(inside_ui_frame);\n        });\n  }\n}\n\nvoid UIFrameService::OnFirstMeaningfulLayout() {\n  raster_frame_service_->puppet_.Act(\n      [](auto& impl) { impl.SetMeaningfulLayout(true); });\n\n  if (using_sync_compositor_) {\n    // Do not early build LayerTree in sync mode, it may cause duplicate frame\n    // when the ClayView is wrap content which is high probability.\n    sync_compositor_service_.Act(\n        [](auto& impl) { impl.OnFirstMeaningfulLayout(); });\n  } else {\n    std::unique_ptr<FrameTimingsRecorder> recorder =\n        std::make_unique<FrameTimingsRecorder>();\n    const fml::TimePoint placeholder_time = fml::TimePoint::Now();\n    recorder->RecordVsync(placeholder_time, placeholder_time);\n    // Build and push LayerTree in async mode to improve first frame\n    // performance.\n    ForceBeginFrame(std::move(recorder));\n  }\n}\n\nvoid UIFrameService::DemandDrawHw(std::promise<bool> frame_promise) {\n  std::unique_ptr<FrameTimingsRecorder> recorder =\n      std::make_unique<FrameTimingsRecorder>();\n  const fml::TimePoint placeholder_time = fml::TimePoint::Now();\n  recorder->RecordVsync(placeholder_time, placeholder_time);\n  // Call BeginFrame synchronously first to build and push LayerTree to\n  // Scheduler, and will be scheduled with state machine in DemandDrawHw bellow.\n  BeginFrame(std::move(recorder));\n\n  raster_frame_service_->puppet_.Act(\n      [frame_promise = std::move(frame_promise)](auto& impl) mutable {\n        bool result = impl.DemandDrawHw();\n        frame_promise.set_value(result);\n      });\n}\n\nvoid UIFrameService::BeginFrame(\n    std::unique_ptr<clay::FrameTimingsRecorder> recorder) {\n  FML_DCHECK(engine_);\n  frame_requested_ = false;\n  recorder->RecordBuildStart(fml::TimePoint::Now());\n  TRACE_EVENT_WITH_FRAME_NUMBER(recorder, \"clay\", \"UIFrameService::BeginFrame\");\n  inside_ui_frame_ = true;\n  if (!engine_->BeginFrame(std::move(recorder))) {\n    // Commit with no updates if failed to build LayerTree.\n    CommitWithNoUpdates();\n  }\n  inside_ui_frame_ = false;\n}\n\nvoid UIFrameService::ForceBeginFrame(\n    std::unique_ptr<clay::FrameTimingsRecorder> recorder) {\n  raster_frame_service_->puppet_.Act(\n      [](auto& impl) { impl.ForceBeginFrame(); });\n  recorder->RecordBuildStart(fml::TimePoint::Now());\n  FML_DCHECK(engine_);\n  inside_ui_frame_ = true;\n  if (!engine_->BeginFrame(std::move(recorder))) {\n    // Commit with no updates if failed to build LayerTree.\n    CommitWithNoUpdates();\n  }\n  inside_ui_frame_ = false;\n}\n\nvoid UIFrameService::CommitWithNoUpdates() {\n  raster_frame_service_->puppet_.Act(\n      [](auto& impl) { impl.CommitWithNoUpdates(); });\n}\n\nvoid UIFrameService::OnInit(clay::ServiceManager& service_manager,\n                            const clay::UIServiceContext& ctx) {\n  engine_ = ctx.engine;\n  clay::Puppet<clay::Owner::kUI, RasterFrameService> puppet =\n      service_manager.GetService<RasterFrameService>();\n  raster_frame_service_ = std::make_unique<RasterFrameServicePuppet>(puppet);\n  clay::Puppet<clay::Owner::kUI, PlatformConstService> platform_const_service =\n      service_manager.GetService<PlatformConstService>();\n  using_sync_compositor_ =\n      platform_const_service->GetSettings().enable_sync_compositor;\n  if (using_sync_compositor_) {\n    sync_compositor_service_ =\n        service_manager.GetService<SyncCompositorService>();\n  }\n}\n\nvoid UIFrameService::OnDestroy() { raster_frame_service_.reset(); }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/services/ui_frame_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_UI_FRAME_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_UI_FRAME_SERVICE_H_\n\n#include <future>\n#include <memory>\n\n#include \"clay/common/service/service.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/shell/common/services/sync_compositor_service.h\"\n\nnamespace clay {\n\nstruct RasterFrameServicePuppet;\n\nclass UIFrameService : public clay::Service<UIFrameService, clay::Owner::kUI> {\n public:\n  static std::shared_ptr<UIFrameService> Create();\n\n  // Send a UI frame request to RasterFrameService, and it will trigger the\n  // scheduler to transfer the state machine. The scheduler will schedule to\n  // call BeginFrame with appropriate timing.\n  void RequestFrame();\n\n  // Indicates that the PageView has received meaningful layout, which means we\n  // can build first frame now.\n  void OnFirstMeaningfulLayout();\n\n  void DemandDrawHw(std::promise<bool> frame_future);\n\n  // Begin a new UI frame, which will try to build a new LayerTree, and than\n  // commit to RasterFrameService, it's called from RasterFrameService which is\n  // scheduled by the state machine.\n  void BeginFrame(std::unique_ptr<clay::FrameTimingsRecorder> recorder);\n\n  // Force begin a new UI frame, which will ignore the state machine and commit\n  // the layer tree immediately.\n  // It's used for BeginFrameImmediately in Engine.\n  void ForceBeginFrame(std::unique_ptr<clay::FrameTimingsRecorder> recorder);\n\n private:\n  void CommitWithNoUpdates();\n\n  void OnInit(clay::ServiceManager& service_manager,\n              const clay::UIServiceContext& ctx) override;\n  void OnDestroy() override;\n\n  Engine* engine_ = nullptr;\n  std::unique_ptr<RasterFrameServicePuppet> raster_frame_service_;\n  clay::Puppet<clay::Owner::kUI, SyncCompositorService>\n      sync_compositor_service_;\n  bool using_sync_compositor_ = false;\n  bool frame_requested_ = false;\n  bool inside_ui_frame_ = false;\n};\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_UI_FRAME_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/services/vsync_waiter_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SERVICES_VSYNC_WAITER_SERVICE_H_\n#define CLAY_SHELL_COMMON_SERVICES_VSYNC_WAITER_SERVICE_H_\n\n#include <memory>\n\n#include \"clay/common/service/service.h\"\n\nnamespace clay {\n\nclass VsyncWaiter;\n\n// Though vsync waiter is a platform service, it should be safe to\n// create vsync waiter in any thread\nclass VsyncWaiterService\n    : public clay::Service<VsyncWaiterService, clay::Owner::kPlatform,\n                           clay::ServiceFlags::kMultiThread> {\n public:\n  static std::shared_ptr<VsyncWaiterService> Create();\n\n  virtual std::unique_ptr<VsyncWaiter> CreateVsyncWaiter(\n      fml::RefPtr<fml::TaskRunner> task_runner) const = 0;\n\n  virtual double GetRefreshRate() const { return 60.0; }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SERVICES_VSYNC_WAITER_SERVICE_H_\n"
  },
  {
    "path": "clay/shell/common/shell.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/shell.h\"\n\n#include <future>\n#include <memory>\n#include <sstream>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/unique_fd.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/common/graphics/shared_image_external_texture.h\"\n#include \"clay/common/sys_info.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/common/thread_host.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n#include \"clay/shell/common/services/animation_event_service_impl.h\"\n#include \"clay/shell/common/services/sync_compositor_service.h\"\n#include \"clay/ui/common/frame_timing_collector.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/common/render_settings.h\"\n#ifdef ENABLE_NET_LOADER\n#include \"clay/net/net_loader_manager.h\"\n#endif\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/base32.h\"\n#include \"clay/fml/file.h\"\n#include \"clay/fml/log_settings.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/paths.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/shell/common/output_surface.h\"\n#include \"clay/shell/common/pointer_data_to_event.h\"\n#include \"clay/shell/common/services/platform_const_service.h\"\n#include \"clay/shell/common/shell_common_rendering_backend.h\"\n#ifndef ENABLE_SKITY\n#include \"clay/shell/common/skia_event_tracer_impl.h\"\n#endif  // ENABLE_SKITY\n#include \"clay/shell/common/switches.h\"\n#include \"clay/shell/common/vsync_waiter.h\"\n#include \"third_party/rapidjson/document.h\"\n#include \"third_party/rapidjson/stringbuffer.h\"\n#include \"third_party/rapidjson/writer.h\"\n#if defined(OS_WIN) || defined(OS_MAC)\n#include \"clay/memory/memory_pressure_monitor.h\"\n#endif\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/ui/platform/keyboard_types.h\"\n\nnamespace clay {\n\nnamespace {\n\nstd::unique_ptr<Engine> CreateEngine(\n    std::shared_ptr<clay::ServiceManager> service_manager,\n    TaskRunners task_runners, Settings settings,\n    fml::RefPtr<GPUUnrefQueue> unref_queue, Engine::Delegate* delegate) {\n  return std::make_unique<Engine>(service_manager, task_runners, settings,\n                                  unref_queue, delegate);\n}\n\n// Though there can be multiple shells, some settings apply to all components in\n// the process. These have to be set up before the shell or any of its\n// sub-components can be initialized. In a perfect world, this would be empty.\n// TODO(chinmaygarde): The unfortunate side effect of this call is that settings\n// that cause shell initialization failures will still lead to some of their\n// settings being applied.\nvoid PerformInitializationTasks(Settings& settings) {  // NOLINT\n  {\n    fml::LogSettings log_settings;\n#if defined(OS_WIN) || defined(OS_MAC)\n    log_settings.min_log_level = fml::LOG_INFO;\n#else\n    log_settings.min_log_level =\n        settings.verbose_logging ? fml::LOG_INFO : fml::LOG_WARNING;\n#endif\n    fml::SetLogSettings(log_settings);\n  }\n\n  static std::once_flag gShellSettingsInitialization = {};\n  std::call_once(gShellSettingsInitialization, [&settings] {\n#ifndef ENABLE_SKITY\n    if (settings.trace_skia) {\n      InitSkiaEventTracer(settings.trace_skia, settings.trace_skia_allowlist);\n    }\n\n    if (!settings.skia_deterministic_rendering_on_cpu) {\n      SkGraphics::Init();\n    } else {\n      FML_DLOG(INFO) << \"Skia deterministic rendering is enabled.\";\n    }\n\n#if !defined(USE_SYSTEM_ICU)\n    if (settings.icu_initialization_required) {\n      if (!settings.icu_data_path.empty()) {\n        fml::icu::InitializeICU(settings.icu_data_path);\n      } else if (settings.icu_mapper) {\n        fml::icu::InitializeICUFromMapping(settings.icu_mapper());\n      } else {\n        FML_DLOG(WARNING) << \"Skipping ICU initialization in the shell.\";\n      }\n    }\n#endif  // USE_SYSTEM_ICU\n#endif  // ENABLE_SKITY\n  });\n\n  PersistentCache::SetCacheSkSL(settings.cache_sksl);\n}\n\n}  // namespace\n\nstd::unique_ptr<Shell> Shell::Create(\n    std::shared_ptr<clay::ServiceManager> service_manager,\n    const TaskRunners& task_runners, Settings settings,\n    const Shell::CreateCallback<PlatformView>& on_create_platform_view,\n    const Shell::CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer,\n    bool is_gpu_disabled) {\n  clay::Isolate::Instance().SetIOTaskRunner(task_runners.GetIOTaskRunner());\n  clay::Isolate::Instance().SetPlatformTaskRunner(\n      task_runners.GetPlatformTaskRunner());\n\n  // Lynx would init tracing func early before here.\n  TRACE_EVENT(\"clay\", \"Shell::Create\");\n\n  PerformInitializationTasks(settings);\n  auto resource_cache_limit_calculator =\n      std::make_shared<ResourceCacheLimitCalculator>(\n          settings.resource_cache_max_bytes_threshold);\n  return CreateWithSnapshot(service_manager,\n                            task_runners,                     //\n                            resource_cache_limit_calculator,  //\n                            settings,                         //\n                            on_create_platform_view,          //\n                            on_create_rasterizer,             //\n                            CreateEngine, is_gpu_disabled);\n}\n\nstd::unique_ptr<Shell> Shell::CreateShellOnPlatformThread(\n    std::shared_ptr<clay::ServiceManager> service_manager,\n    const std::shared_ptr<ResourceCacheLimitCalculator>&\n        resource_cache_limit_calculator,\n    const TaskRunners& task_runners, const Settings& settings,\n    const Shell::CreateCallback<PlatformView>& on_create_platform_view,\n    const Shell::CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer,\n    const Shell::EngineCreateCallback& on_create_engine, bool is_gpu_disabled) {\n  if (!task_runners.IsValid()) {\n    FML_LOG(ERROR) << \"Task runners to run the shell were invalid.\";\n    return nullptr;\n  }\n\n  auto shell = std::unique_ptr<Shell>(new Shell(service_manager, task_runners,\n                                                resource_cache_limit_calculator,\n                                                settings, is_gpu_disabled));\n\n  // Create the platform view on the platform thread (this thread).\n  auto platform_view = on_create_platform_view(service_manager, *shell.get());\n  if (!platform_view || !platform_view->GetWeakPtr()) {\n    return nullptr;\n  }\n\n  // Send dispatcher_maker to the engine constructor because shell won't have\n  // platform_view set until Shell::Setup is called later.\n  auto dispatcher_maker = platform_view->GetDispatcherMaker();\n\n  auto unref_queue = clay::GraphicsIsolate::Instance().GetOrCreateUnrefQueue(\n      task_runners.GetRasterTaskRunner());\n  // create clay engine\n  std::promise<std::unique_ptr<Engine>> engine_promise;\n  auto engine_future = engine_promise.get_future();\n  fml::TaskRunner::RunNowOrPostTask(\n      shell->GetTaskRunners().GetUITaskRunner(),\n      fml::MakeCopyable([shell = shell.get(), service_manager, &engine_promise,\n                         unref_queue, &on_create_engine]() mutable {\n        TRACE_EVENT(\"clay\", \"ShellSetupUISubsystem\");\n        const auto& task_runners = shell->GetTaskRunners();\n\n        std::unique_ptr<Engine> engine =\n            on_create_engine(service_manager, task_runners,\n                             shell->GetSettings(), unref_queue, shell);\n\n        engine_promise.set_value(std::move(engine));\n      }));\n\n  fml::TaskRunner::RunNowOrPostTask(shell->GetTaskRunners().GetIOTaskRunner(),\n                                    []() {\n                                      // Calling IsLowEndDevice() for the first\n                                      // time may be time-consuming.\n                                      clay::SysInfo::IsLowEndDevice();\n                                    });\n  fml::RefPtr<clay::RenderSettings> render_settings =\n      fml::MakeRefCounted<clay::RenderSettings>();\n  std::unique_ptr<Engine> engine = engine_future.get();\n  engine->SetRenderSettings(render_settings);\n\n  if (!shell->Setup(std::move(platform_view), std::move(engine))) {\n    return nullptr;\n  }\n\n  // The detach order must be reversed from the attach order.\n  // Attach: Platform -> Raster -> UI\n  // Detach: UI -> Raster -> Platform\n  service_manager->Attach<clay::Owner::kPlatform>(clay::PlatformServiceContext{\n      .platform_view = shell->GetPlatformView().get(), .shell = shell.get()});\n\n  shell->GetTaskRunners().GetIOTaskRunner()->PostTask([service_manager] {\n    service_manager->Attach<clay::Owner::kIO>(clay::IOServiceContext{});\n  });\n\n  // Create the rasterizer on the raster thread.\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners.GetRasterTaskRunner(),\n      [service_manager, on_create_rasterizer, render_settings, unref_queue,\n       page_id = static_cast<uint32_t>(\n           shell->GetEngine()->GetPageView()->PageUniqueId())]() {\n        TRACE_EVENT(\"clay\", \"ShellSetupGPUSubsystem\");\n        std::unique_ptr<Rasterizer> rasterizer(\n            on_create_rasterizer(service_manager));\n        rasterizer->set_unref_queue(unref_queue);\n        rasterizer->SetRenderSettings(render_settings);\n        service_manager->Attach<clay::Owner::kRaster>(\n            clay::RasterServiceContext{.rasterizer = rasterizer.release(),\n                                       .page_unique_id = page_id});\n      });\n\n  fml::TaskRunner::RunNowOrPostTask(\n      shell->GetTaskRunners().GetUITaskRunner(),\n      fml::MakeCopyable([service_manager, engine = shell->GetEngine(),\n                         unref_queue]() mutable {\n        if (engine) {\n          service_manager->Attach<clay::Owner::kUI>(\n              clay::UIServiceContext{.engine = engine.get()});\n        }\n      }));\n\n  return shell;\n}\n\nstd::unique_ptr<Shell> Shell::CreateWithSnapshot(\n    std::shared_ptr<clay::ServiceManager> service_manager,\n    const TaskRunners& task_runners,\n    const std::shared_ptr<ResourceCacheLimitCalculator>&\n        resource_cache_limit_calculator,\n    Settings settings,\n    const Shell::CreateCallback<PlatformView>& on_create_platform_view,\n    const Shell::CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer,\n    const Shell::EngineCreateCallback& on_create_engine, bool is_gpu_disabled) {\n  // This must come first as it initializes tracing.\n  PerformInitializationTasks(settings);\n\n  TRACE_EVENT(\"clay\", \"Shell::CreateWithSnapshot\");\n\n  const bool callbacks_valid =\n      on_create_platform_view && on_create_rasterizer && on_create_engine;\n  if (!task_runners.IsValid() || !callbacks_valid) {\n    return nullptr;\n  }\n\n  fml::AutoResetWaitableEvent latch;\n  std::unique_ptr<Shell> shell;\n  auto platform_task_runner = task_runners.GetPlatformTaskRunner();\n  fml::TaskRunner::RunNowOrPostTask(\n      platform_task_runner,\n      fml::MakeCopyable([service_manager, &latch,                            //\n                         &shell,                                             //\n                         resource_cache_limit_calculator,                    //\n                         task_runners = task_runners,                        //\n                         settings = settings,                                //\n                         on_create_platform_view = on_create_platform_view,  //\n                         on_create_rasterizer = on_create_rasterizer,        //\n                         on_create_engine = on_create_engine,\n                         is_gpu_disabled]() mutable {\n        shell = CreateShellOnPlatformThread(service_manager,\n                                            resource_cache_limit_calculator,  //\n                                            task_runners,                     //\n                                            settings,                         //\n                                            on_create_platform_view,          //\n                                            on_create_rasterizer,             //\n                                            on_create_engine, is_gpu_disabled);\n        latch.Signal();\n      }));\n  latch.Wait();\n#if defined(OS_ANDROID)\n  shell->SetUpMemoryPressureListener();\n#endif\n#if defined(OS_WIN)\n  clay::MemoryPressureMonitor::GetInstance().SetTaskRunner(\n      task_runners.GetUITaskRunner());\n  shell->SetUpMemoryPressureListener();\n  clay::MemoryPressureMonitor::GetInstance().StartMonitoring();\n#endif\n#if defined(OS_MAC)\n  shell->SetUpMemoryPressureListener();\n  clay::MemoryPressureMonitor::GetInstance().StartMonitoring();\n#endif\n  return shell;\n}\n\n#if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_MAC)\nvoid Shell::SetUpMemoryPressureListener() {\n  FML_DCHECK(memory_pressure_listener_ == nullptr);\n  auto low_memory_callback =\n      [self = weak_factory_.GetWeakPtr()](\n          clay::MemoryPressureListener::MemoryPressureLevel) {\n        if (self) {\n          self->NotifyLowMemoryWarning();\n        }\n      };\n  memory_pressure_listener_ = std::make_unique<clay::MemoryPressureListener>(\n      low_memory_callback, task_runners_.GetUITaskRunner());\n}\n#endif\n\nShell::Shell(std::shared_ptr<clay::ServiceManager> service_manager,\n             const TaskRunners& task_runners,\n             const std::shared_ptr<ResourceCacheLimitCalculator>&\n                 resource_cache_limit_calculator,\n             const Settings& settings, bool is_gpu_disabled)\n    : service_manager_(service_manager),\n      task_runners_(task_runners),\n      animator_info_service_(\n          service_manager->GetService<AnimatorInfoService>()),\n      rasterizer_service_(service_manager->GetService<RasterizerService>()),\n      resource_cache_limit_calculator_(resource_cache_limit_calculator),\n      settings_(settings),\n      weak_factory_(this) {\n  FML_DCHECK(task_runners_.IsValid());\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  display_manager_ = std::make_shared<DisplayManager>();\n  auto platform_const_service = std::make_shared<PlatformConstService>(\n      settings, is_gpu_disabled, display_manager_);\n  is_gpu_disabled_sync_switch_ =\n      platform_const_service->GetIsGPUDisabledSyncSwitch();\n  service_manager->RegisterService<PlatformConstService>(\n      platform_const_service);\n  service_manager_->RegisterService<AnimationEventService>(\n      std::make_shared<AnimationEventServiceImpl>());\n\n  resource_cache_limit_calculator->AddResourceCacheLimitItem(\n      weak_factory_.GetWeakPtr());\n\n  frame_timing_collector_ = std::make_shared<clay::FrameTimingCollector>(\n      task_runners_.GetPlatformTaskRunner());\n}\n\nShell::~Shell() {\n  // If GPUUnrefQueue is not shared, we need to remove it from GraphicsIsolate.\n  if (engine_ && engine_->GetPageView()) {\n    auto unref_queue = engine_->GetPageView()->GetUnrefQueue();\n    if (unref_queue && !unref_queue->IsShared()) {\n      clay::GraphicsIsolate::Instance().RemoveUnrefQueue(\n          unref_queue->GetTaskRunner());\n    }\n  }\n\n  task_runners_.GetIOTaskRunner()->PostTask(\n      [service_manager = service_manager_] {\n        service_manager->Detach<clay::Owner::kIO>();\n      });\n\n#if defined(OS_ANDROID) || defined(OS_MAC) || defined(OS_WIN)\n  memory_pressure_listener_.reset();\n#endif\n\n  PersistentCache::GetCacheForProcess()->RemoveWorkerTaskRunner(\n      task_runners_.GetIOTaskRunner());\n\n  fml::AutoResetWaitableEvent ui_latch, platform_latch, io_latch;\n\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetPlatformTaskRunner(),\n      fml::MakeCopyable(\n          [platform_view = platform_view_.get(), &platform_latch]() mutable {\n            if (platform_view) {\n              platform_view->ReleasePlatformInstanceManager();\n            }\n            platform_latch.Signal();\n          }));\n  platform_latch.Wait();\n\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetUITaskRunner(),\n      fml::MakeCopyable([service_manager = service_manager_,\n                         engine = std::move(engine_), &ui_latch]() mutable {\n        service_manager->Detach<clay::Owner::kUI>();\n        engine.reset();\n        ui_latch.Signal();\n      }));\n  ui_latch.Wait();\n\n  std::optional<std::promise<void>> raster_thread_promise;\n  std::optional<std::future<void>> raster_thread_future;\n\n  if (platform_view_ && !platform_view_->IsOutputSurfaceThreadSafe()) {\n    raster_thread_promise.emplace();\n    raster_thread_future.emplace(raster_thread_promise->get_future());\n  }\n\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetRasterTaskRunner(),\n      [p = std::move(raster_thread_promise),\n       // OutputSurface MUST live longer than Rasterizer, since GPU surface\n       // holds the raw pointer to it as a delegate\n       output_surface = platform_view_->GetOutputSurface(),\n       service_manager = service_manager_]() mutable {\n        clay::Puppet<clay::Owner::kRaster, RasterizerService> raster_service =\n            service_manager->GetService<RasterizerService>();\n        Rasterizer* rasterizer = raster_service->GetRasterizer();\n        service_manager->Detach<clay::Owner::kRaster>();\n        delete rasterizer;\n        if (p.has_value()) {\n          p->set_value();\n        }\n      });\n\n  if (raster_thread_future.has_value()) {\n    raster_thread_future->wait();\n  }\n\n  // The platform view must go last because it may be holding onto platform side\n  // counterparts to resources owned by subsystems running on other threads. For\n  // example, the NSOpenGLContext on the Mac.\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetPlatformTaskRunner(),\n      fml::MakeCopyable([service_manager = service_manager_,\n                         platform_view = std::move(platform_view_),\n                         &platform_latch]() mutable {\n        service_manager->Detach<clay::Owner::kPlatform>();\n        platform_view.reset();\n        platform_latch.Signal();\n      }));\n  platform_latch.Wait();\n}\n\nstd::unique_ptr<Shell> Shell::Spawn(\n    std::shared_ptr<clay::ServiceManager> service_manager,\n    const std::string& initial_route,\n    const CreateCallback<PlatformView>& on_create_platform_view,\n    const CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer) const {\n  FML_DCHECK(task_runners_.IsValid());\n  // It's safe to store this value since it is set on the platform thread.\n  bool is_gpu_disabled = false;\n  GetIsGpuDisabledSyncSwitch()->Execute(\n      fml::SyncSwitch::Handlers()\n          .SetIfFalse([&is_gpu_disabled] { is_gpu_disabled = false; })\n          .SetIfTrue([&is_gpu_disabled] { is_gpu_disabled = true; }));\n\n  std::unique_ptr<Shell> result = CreateWithSnapshot(\n      service_manager, task_runners_, resource_cache_limit_calculator_,\n      GetSettings(), on_create_platform_view, on_create_rasterizer,\n      [engine = this->engine_.get()](\n          std::shared_ptr<clay::ServiceManager> service_manager,\n          TaskRunners task_runners, Settings settings,\n          fml::RefPtr<GPUUnrefQueue> unref_queue, Engine::Delegate* delegate) {\n        return engine->Spawn(service_manager, /*settings=*/settings,\n                             unref_queue, delegate);\n      },\n      is_gpu_disabled);\n  return result;\n}\n\nstd::shared_ptr<clay::ServiceManager> Shell::SpawnServiceManager() const {\n  return clay::ServiceManager::Create(\n      {task_runners_.GetPlatformTaskRunner(), task_runners_.GetUITaskRunner(),\n       task_runners_.GetRasterTaskRunner(), task_runners_.GetIOTaskRunner()});\n}\n\nvoid Shell::NotifyLowMemoryWarning() const {\n  uint64_t flow_id = TRACE_FLOW_ID();\n  TRACE_EVENT(\"clay\", \"Shell::NotifyLowMemoryWarning\",\n              [flow_id = flow_id](lynx::perfetto::EventContext ctx) {\n                ctx.event()->add_flow_ids(flow_id);\n              });\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetUITaskRunner(), [engine = weak_engine_] {\n        if (engine) {\n          // Try to clear image cache when memory is low\n          clay::Isolate::Instance().GetResourceCache()->ClearCache();\n          auto context = engine->GetViewContext();\n          if (context) {\n            context->NotifyLowMemory();\n          }\n        }\n      });\n  rasterizer_service_.Act([flow_id = flow_id](auto& impl) {\n    impl.GetRasterizer()->NotifyLowMemoryWarning();\n    TRACE_EVENT(\"clay\", \"Rasterizer::NotifyLowMemoryWarning\",\n                [flow_id = flow_id](lynx::perfetto::EventContext ctx) {\n                  ctx.event()->add_terminating_flow_ids(flow_id);\n                });\n  });\n  task_runners_.GetIOTaskRunner()->PostTask([]() {\n#ifdef ENABLE_NET_LOADER\n    clay::NetLoaderManager::Instance().NotifyLowMemory();\n#endif  // ENABLE_NET_LOADER\n  });\n}\n\nbool Shell::IsSetup() const { return is_setup_; }\n\nbool Shell::Setup(std::unique_ptr<PlatformView> platform_view,\n                  std::unique_ptr<Engine> engine) {\n  if (is_setup_) {\n    return false;\n  }\n\n  if (!platform_view) {\n    return false;\n  }\n  platform_view_ = std::move(platform_view);\n  engine_ = std::move(engine);\n\n  weak_engine_ = engine_->GetWeakPtr();\n  weak_platform_view_ = platform_view_->GetWeakPtr();\n\n  // Setup the time-consuming default font manager right after engine created.\n  if (!settings_.prefetched_default_font_manager) {\n    fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),\n                                      [engine = weak_engine_] {\n                                        if (engine) {\n                                          engine->SetupDefaultFontManager();\n                                        }\n                                      });\n  }\n\n  // Create FrameTimingCollector and set to engine\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetUITaskRunner(),\n      [frame_timing_collector = frame_timing_collector_, engine = weak_engine_,\n       ui_task_runner = task_runners_.GetUITaskRunner(),\n       raster_task_runner = task_runners_.GetRasterTaskRunner()] {\n        if (engine) {\n          engine->SetFrameTimingCollector(frame_timing_collector);\n          frame_timing_collector->SetPageView(engine->GetPageView());\n        }\n      });\n\n  // async init main skia context in rasterThread\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetRasterTaskRunner(),\n      [output_surface = platform_view_->GetOutputSurface(),\n       unref_queue = engine_->GetPageView()->GetUnrefQueue(),\n       task_runner = task_runners_.GetUITaskRunner()]() {\n        if (output_surface) {\n          output_surface->CreateMainGrContext();\n          clay::GrContextPtr main_context = output_surface->GetMainGrContext();\n          if (main_context && unref_queue) {\n            unref_queue->SetContext(main_context);\n          }\n        }\n      });\n\n  is_setup_ = true;\n\n  PersistentCache::GetCacheForProcess()->AddWorkerTaskRunner(\n      task_runners_.GetIOTaskRunner());\n\n  PersistentCache::GetCacheForProcess()->SetIsDumpingSkp(\n      settings_.dump_skp_on_shader_compilation);\n\n#ifdef ENABLE_NET_LOADER\n  clay::NetLoaderManager::Instance().EnsureSetup(\n      clay::Isolate::Instance().GetIOTaskRunner(), 10 * 1024 * 1024 /*10mB*/);\n#endif  // ENABLE_NET_LOADER\n\n  if (settings_.purge_persistent_cache) {\n    PersistentCache::GetCacheForProcess()->Purge();\n  }\n\n  return true;\n}\n\nconst Settings& Shell::GetSettings() const { return settings_; }\n\nconst TaskRunners& Shell::GetTaskRunners() const { return task_runners_; }\n\nfml::WeakPtr<Engine> Shell::GetEngine() {\n  FML_DCHECK(is_setup_);\n  return weak_engine_;\n}\n\nfml::WeakPtr<PlatformView> Shell::GetPlatformView() {\n  FML_DCHECK(is_setup_);\n  return weak_platform_view_;\n}\n\n// |PlatformView::Delegate|\nvoid Shell::OnPlatformViewCreated() {\n  TRACE_EVENT(\"clay\", \"Shell::OnPlatformViewCreated\");\n\n  fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),\n                                    [engine = engine_->GetWeakPtr()] {\n                                      if (engine) {\n                                        engine->OnPlatformViewCreated();\n                                      }\n                                    });\n\n  fml::RefPtr<OutputSurface> output_surface =\n      platform_view_->GetOutputSurface();\n  if (!output_surface) {\n    fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),\n                                      [engine = engine_->GetWeakPtr()] {\n                                        if (engine) {\n                                          engine->OnOutputSurfaceCreateFailed();\n                                        }\n                                      });\n    return;\n  }\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  rasterizer_service_.Act([waiting_for_first_frame = waiting_for_first_frame_,\n                           output_surface,\n                           ui_task_runner = task_runners_.GetUITaskRunner(),\n                           engine = engine_->GetWeakPtr()](auto& impl) mutable {\n    bool success = false;\n    if (auto surface = output_surface->CreateGPUSurface();\n        surface && surface->IsValid()) {\n      impl.GetRasterizer()->Setup(std::move(surface));\n      waiting_for_first_frame->store(true);\n      success = true;\n    }\n    fml::TaskRunner::RunNowOrPostTask(ui_task_runner, [engine, success] {\n      if (engine) {\n        if (success) {\n          engine->OnOutputSurfaceCreated();\n        } else {\n          engine->OnOutputSurfaceCreateFailed();\n        }\n      }\n    });\n  });\n  clay::Puppet<clay::Owner::kPlatform, RasterFrameService>\n      raster_frame_service =\n          GetServiceManager()->GetService<RasterFrameService>();\n  raster_frame_service.Act(\n      [](auto& impl) { impl.SetOutputSurfaceValid(true); });\n}\n\n// |PlatformView::Delegate|\nvoid Shell::OnPlatformViewDestroyed() {\n  TRACE_EVENT(\"clay\", \"Shell::OnPlatformViewDestroyed\");\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n  clay::Puppet<clay::Owner::kPlatform, RasterFrameService>\n      raster_frame_service =\n          GetServiceManager()->GetService<RasterFrameService>();\n  raster_frame_service.Act(\n      [](auto& impl) { impl.SetOutputSurfaceValid(false); });\n\n  task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() {\n    if (engine) {\n      engine->OnOutputSurfaceDestroyed();\n    }\n  });\n\n  rasterizer_service_.Act([](auto& impl) { impl.GetRasterizer()->Teardown(); });\n}\n\n// |PlatformView::Delegate|\nvoid Shell::OnPlatformViewScheduleFrame() {\n  TRACE_EVENT(\"clay\", \"Shell::OnPlatformViewScheduleFrame\");\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() {\n    if (engine) {\n      engine->ScheduleFrame();\n    }\n  });\n}\n\n// |PlatformView::Delegate|\nvoid Shell::OnPlatformViewSetViewportMetrics(const ViewportMetrics& metrics) {\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  if (metrics.device_pixel_ratio <= 0 || metrics.physical_width <= 0 ||\n      metrics.physical_height <= 0) {\n    // Ignore invalid view-port metrics.\n    if (metrics.device_pixel_ratio > 0) {\n      // NOTE(Xietong): although the physical size is still invalid, the device\n      // pixel ratio can still be useful. Pass it to clay.\n      fml::TaskRunner::RunNowOrPostTask(\n          task_runners_.GetUITaskRunner(),\n          [engine = engine_->GetWeakPtr(), metrics]() {\n            if (engine) {\n              engine->SetViewportMetrics(metrics);\n            }\n          });\n    }\n    return;\n  }\n\n#ifndef ENABLE_SKITY\n  // This is the formula Android uses.\n  // https://android.googlesource.com/platform/frameworks/base/+/39ae5bac216757bc201490f4c7b8c0f63006c6cd/libs/hwui/renderthread/CacheManager.cpp#45\n  resource_cache_limit_ = metrics.physical_width * metrics.physical_height *\n                          settings_.gpu_resource_cache_multiplier * 4;\n  size_t resource_cache_max_bytes =\n      resource_cache_limit_calculator_->GetResourceCacheMaxBytes();\n  rasterizer_service_.Act([resource_cache_max_bytes](auto& impl) {\n    impl.GetRasterizer()->SetResourceCacheMaxBytes(resource_cache_max_bytes,\n                                                   false);\n  });\n#endif  // ENABLE_SKITY\n\n  {\n    std::scoped_lock<std::mutex> lock(*resize_mutex_);\n    *expected_frame_size_ =\n        skity::Vec2(metrics.physical_width, metrics.physical_height);\n    device_pixel_ratio_ = metrics.device_pixel_ratio;\n  }\n  if (task_runners_.GetPlatformTaskRunner() ==\n      task_runners_.GetUITaskRunner()) {\n    // That mean the platform thread is the ui thread\n    // and this method will called in platform thread.\n    engine_->SetViewportMetrics(metrics);\n  } else {\n    task_runners_.GetUITaskRunner()->PostTask(\n        [engine = engine_->GetWeakPtr(), metrics]() {\n          if (engine) {\n            engine->SetViewportMetrics(metrics);\n          }\n        });\n  }\n}\n\n// |PlatformView::Delegate|\nbool Shell::OnPlatformViewDispatchPointerDataPacket(\n    std::unique_ptr<PointerDataPacket> packet) {\n  TRACE_EVENT(\"clay\", \"Shell::OnPlatformViewDispatchPointerDataPacket\");\n  std::vector<clay::PointerEvent> events =\n      GetEventsFromPointerDataPacket(packet.get());\n\n  // On some platform(ex: harmony), this action is already in the UI Looper,\n  // so we have no need to post task to next loop, just run immediatly.\n  // (On harmony, post to next loop may lose context for TouchEvent when using\n  // BuilderNode.postTouchEvent.)\n  // In Android, we need to know whether the event is consumed by the engine.\n  // i.e. whether we need to let other platform views consume it.\n  if (task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()) {\n    auto engine = engine_->GetWeakPtr();\n    return engine->DispatchPointerEvent(std::move(events));\n  }\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetUITaskRunner(),\n      [engine = engine_->GetWeakPtr(), e = std::move(events)]() {\n        if (engine) {\n          engine->DispatchPointerEvent(std::move(e));\n        }\n      });\n  return true;\n}\n\n// |PlatformView::Delegate|\nvoid Shell::OnPlatformViewDispatchKeyEvent(\n    std::unique_ptr<clay::KeyEvent> key_event,\n    std::function<void(bool /* handled */)> callback) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  task_runners_.GetUITaskRunner()->PostTask(fml::MakeCopyable(\n      [engine = engine_->GetWeakPtr(), event = std::move(key_event),\n       callback = std::move(callback)]() mutable {\n        if (engine) {\n          engine->DispatchKeyEvent(std::move(event), std::move(callback));\n        }\n      }));\n}\n\nvoid Shell::OnPlatformViewSendKeyboardEvent(\n    std::unique_ptr<clay::KeyEvent> key_event) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  task_runners_.GetUITaskRunner()->PostTask(fml::MakeCopyable(\n      [engine = engine_->GetWeakPtr(), event = std::move(key_event)]() mutable {\n        if (engine) {\n          engine->SendKeyboardEvent(std::move(event));\n        }\n      }));\n}\n\nvoid Shell::OnPlatformViewPerformEditorAction(\n    clay::KeyboardAction action_code) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n  task_runners_.GetUITaskRunner()->PostTask(\n      [engine = engine_->GetWeakPtr(), action_code = action_code]() {\n        if (engine) {\n          engine->PerformEditorAction(action_code);\n        }\n      });\n}\n\n// |PlatformView::Delegate|\nvoid Shell::OnPlatformViewDeleteSurroundingText(int before_length,\n                                                int after_length) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  task_runners_.GetUITaskRunner()->PostTask(\n      [engine = engine_->GetWeakPtr(), before_length, after_length]() {\n        if (engine) {\n          engine->DeleteSurroundingText(before_length, after_length);\n        }\n      });\n}\n\nvoid Shell::OnPlatformViewOnEnterForeground() {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() {\n    if (engine) {\n      engine->OnEnterForeground();\n    }\n  });\n}\n\nvoid Shell::OnPlatformViewOnEnterBackground() {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() {\n    if (engine) {\n      engine->OnEnterBackground();\n    }\n  });\n}\n\nvoid Shell::OnPlatformViewSetDefaultFocusRingEnabled(bool enable) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  task_runners_.GetUITaskRunner()->PostTask(\n      [engine = engine_->GetWeakPtr(), enable]() {\n        if (engine) {\n          engine->SetDefaultFocusRingEnabled(enable);\n        }\n      });\n}\n\nvoid Shell::OnPlatformViewSetPerformanceOverlayEnabled(bool enable) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetUITaskRunner(),\n      [engine = engine_->GetWeakPtr(), enable]() {\n        if (engine) {\n          engine->SetPerformanceOverlayEnabled(enable);\n        }\n      });\n}\n\nvoid Shell::OnDefaultOverflowVisibleChanged(bool visible) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetUITaskRunner(),\n      [engine = engine_->GetWeakPtr(), visible]() {\n        if (engine) {\n          engine->SetDefaultOverflowVisible(visible);\n        }\n      });\n}\n\nvoid Shell::OnExposurePropsChanged(int freq, bool enable_exposure_ui_margin) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetUITaskRunner(),\n      [engine = engine_->GetWeakPtr(), freq, enable_exposure_ui_margin]() {\n        if (engine) {\n          engine->SetExposureProps(freq, enable_exposure_ui_margin);\n        }\n      });\n}\n\n// |PlatformView::Delegate|\nvoid Shell::OnPlatformViewSetNextFrameCallback(const fml::closure& closure) {\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  rasterizer_service_.Act([closure](auto& impl) {\n    impl.GetRasterizer()->SetNextFrameCallback(closure);\n  });\n}\n\n// |PlatformView::Delegate|\nconst Settings& Shell::OnPlatformViewGetSettings() const { return settings_; }\n\nvoid Shell::ReportTimings() {\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());\n}\n\nvoid Shell::UpdateRootSize(int32_t width, int32_t height) {\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  fml::TaskRunner::RunNowOrPostTask(\n      GetTaskRunners().GetPlatformTaskRunner(),\n      [weak = weak_platform_view_, width, height]() {\n        if (weak) {\n          weak->UpdateRootSize(width, height);\n        }\n      });\n}\n\nvoid Shell::RegisterDrawableImage(\n    std::shared_ptr<DrawableImage> drawable_image) {\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  drawable_image->SetFrameAvailableCallback(\n      [ui_runner = task_runners_.GetUITaskRunner(),\n       raster_runner = task_runners_.GetRasterTaskRunner(),\n       engine = engine_->GetWeakPtr(),\n       service_manager = engine_->GetServiceManager(),\n       image_id = drawable_image->Id(),\n       weak_drawable_image =\n           std::weak_ptr<clay::DrawableImage>(drawable_image)] {\n        // Tell the rasterizer that one of its textures has a new frame\n        // available.\n        raster_runner->PostTask([weak_drawable_image, service_manager]() {\n          if (auto image = weak_drawable_image.lock()) {\n            image->MarkNewFrameAvailable();\n            // Request a new frame from Rasterizer directly.\n            clay::Puppet<clay::Owner::kRaster, RasterFrameService>\n                raster_frame_service =\n                    service_manager->GetService<RasterFrameService>();\n            raster_frame_service.Act(\n                [](auto& impl) { impl.RequestRasterFrame(); });\n          }\n        });\n\n        fml::TaskRunner::RunNowOrPostTask(ui_runner, [engine, image_id] {\n          if (engine) {\n            engine->MarkDrawableImageFrameAvailable(image_id);\n          }\n        });\n      });\n\n  rasterizer_service_.Act([drawable_image](auto& impl) {\n    impl.GetRasterizer()->GetDrawableImageRegistry()->RegisterDrawableImage(\n        drawable_image);\n  });\n}\n\nvoid Shell::UnregisterDrawableImage(int64_t image_id) {\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  rasterizer_service_.Act([image_id](auto& impl) {\n    impl.GetRasterizer()->GetDrawableImageRegistry()->UnregisterDrawableImage(\n        image_id);\n  });\n}\n\nsize_t Shell::UnreportedFramesCount() const {\n  // Check that this is running on the raster thread to avoid race conditions.\n  FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());\n  FML_DCHECK(unreported_timings_.size() % (FrameTiming::kStatisticsCount) == 0);\n  return unreported_timings_.size() / (FrameTiming::kStatisticsCount);\n}\n\nvoid Shell::OnDrawHardware(bool force_draw) {\n  TRACE_EVENT(\"clay\", \"Shell::OnDrawHardware\");\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n  clay::Puppet<clay::Owner::kPlatform, SyncCompositorService>\n      sync_compositor_service =\n          GetServiceManager()->GetService<SyncCompositorService>();\n  sync_compositor_service.Act(\n      [force_draw](auto& impl) { impl.DemandDrawHw(force_draw); });\n}\n\ndouble Shell::GetMainDisplayRefreshRate() {\n  return display_manager_->GetMainDisplayRefreshRate();\n}\n\nvoid Shell::ShowSoftInput(int type, int action) {\n  fml::TaskRunner::RunNowOrPostTask(\n      GetTaskRunners().GetPlatformTaskRunner(),\n      [type, action, weak = weak_platform_view_]() {\n        if (weak) {\n          weak->ShowSoftInput(type, action);\n        }\n      });\n}\n\nvoid Shell::HideSoftInput() {\n  fml::TaskRunner::RunNowOrPostTask(GetTaskRunners().GetPlatformTaskRunner(),\n                                    [weak = weak_platform_view_]() {\n                                      if (weak) {\n                                        weak->HideSoftInput();\n                                      }\n                                    });\n}\n\nvoid Shell::FilterInputAsync(const std::string& input,\n                             const std::string& pattern,\n                             std::function<void(const std::string&)> callback) {\n  // TODO(wangyanyi) maybe we can post this task to io runner,\n  // this will improve performance.\n  fml::TaskRunner::RunNowOrPostTask(GetTaskRunners().GetUITaskRunner(),\n                                    [weak = weak_platform_view_, input, pattern,\n                                     callback = std::move(callback)]() {\n                                      std::string result = input;  // 默认值\n                                      if (weak) {\n                                        result =\n                                            weak->InputFilter(input, pattern);\n                                      }\n                                      callback(result);\n                                    });\n}\n\nstd::string Shell::ShouldInterceptUrl(const std::string& origin_url,\n                                      bool should_decode) {\n  if (weak_platform_view_) {\n    return weak_platform_view_->ShouldInterceptUrl(origin_url, should_decode);\n  }\n  return origin_url;\n}\n\nstd::shared_ptr<clay::ResourceLoaderIntercept>\nShell::GetResourceLoaderIntercept() {\n  if (weak_platform_view_) {\n    return weak_platform_view_->GetResourceLoaderIntercept();\n  }\n  return nullptr;\n}\n\nScreenshotData Shell::ScreenshotSync(\n    ScreenshotData::ScreenshotType screenshot_type, uint32_t background_color) {\n  TRACE_EVENT(\"clay\", \"Shell::ScreenshotSync\");\n  std::future<std::optional<ScreenshotData>> screenshot_future =\n      rasterizer_service_.ActWithPromise(\n          [screenshot_type, background_color](auto& impl) {\n            return impl.GetRasterizer()->ScreenshotLastLayerTree(\n                screenshot_type, false, background_color);\n          });\n  return screenshot_future.get().value_or(ScreenshotData());\n}\n\nvoid Shell::ScreenshotAsync(ScreenshotData::ScreenshotType screenshot_type,\n                            uint32_t background_color,\n                            std::function<void(ScreenshotData)> callback) {\n  rasterizer_service_.Act(\n      [screenshot = screenshot, screenshot_type, background_color](auto& impl) {\n        if (screenshot->load()) {\n          auto result = impl.GetRasterizer()->ScreenshotLastLayerTree(\n              screenshot_type, false, background_color);\n          screenshot->store(false);\n          return std::make_optional<ScreenshotData>(result);\n        } else {\n          return std::make_optional<ScreenshotData>();\n        }\n      },\n      [callback, screenshot = screenshot](\n          std::optional<ScreenshotData> screenshot_result) {\n        if (screenshot_result.has_value()) {\n          callback(screenshot_result.value());\n          screenshot->store(true);\n        }\n      });\n}\n\nvoid Shell::MakeRasterSnapshot(\n    std::unique_ptr<clay::LayerTree> layer_tree,\n    std::function<void(fml::RefPtr<clay::PaintImage>)> callback) {\n  rasterizer_service_.Act(\n      [layer_tree = std::move(layer_tree), callback,\n       io_runner = GetTaskRunners().GetIOTaskRunner()](auto& impl) mutable {\n        auto image =\n            impl.GetRasterizer()->MakeRasterSnapshot(std::move(layer_tree));\n        // Callback to IO thread for post-processing like jpeg/png compression,\n        // do not block raster thread.\n        io_runner->PostTask(\n            [image = std::move(image), callback] { callback(image); });\n      });\n}\n\nfml::RefPtr<PaintImage> Shell::MakeRasterSnapshot(GrPicturePtr picture,\n                                                  skity::Vec2 picture_size) {\n  TRACE_EVENT(\"clay\", \"Shell::MakeRasterSnapshot\");\n  std::future<std::optional<fml::RefPtr<clay::PaintImage>>> future =\n      rasterizer_service_.ActWithPromise(\n          [picture = std::move(picture), picture_size](auto& impl) {\n            return impl.GetRasterizer()->MakeRasterSnapshot(std::move(picture),\n                                                            picture_size);\n          });\n  return future.get().value_or(nullptr);\n}\n\nfml::Status Shell::WaitForFirstFrame(fml::TimeDelta timeout) {\n  FML_DCHECK(is_setup_);\n  if (!settings_.enable_sync_compositor &&\n      (task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread() ||\n       task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread())) {\n    return fml::Status(fml::StatusCode::kFailedPrecondition,\n                       \"WaitForFirstFrame called from thread that can't wait \"\n                       \"because it is responsible for generating the frame.\");\n  }\n\n  // Check for overflow.\n  auto now = std::chrono::steady_clock::now();\n  auto max_duration = std::chrono::steady_clock::time_point::max() - now;\n  auto desired_duration = std::chrono::milliseconds(timeout.ToMilliseconds());\n  auto duration =\n      now + (desired_duration > max_duration ? max_duration : desired_duration);\n\n  std::unique_lock<std::mutex> lock(waiting_for_first_frame_mutex_);\n  bool success = waiting_for_first_frame_condition_.wait_until(\n      lock, duration, [&waiting_for_first_frame = waiting_for_first_frame_] {\n        return !waiting_for_first_frame->load();\n      });\n  if (success) {\n    return fml::Status();\n  } else {\n    return fml::Status(fml::StatusCode::kDeadlineExceeded, \"timeout\");\n  }\n}\n\nbool Shell::ReloadSystemFonts() {\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n  return true;\n}\n\nstd::shared_ptr<const fml::SyncSwitch> Shell::GetIsGpuDisabledSyncSwitch()\n    const {\n  return is_gpu_disabled_sync_switch_;\n}\n\nvoid Shell::SetGpuAvailability(GpuAvailability availability) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n  switch (availability) {\n    case GpuAvailability::kAvailable:\n      is_gpu_disabled_sync_switch_->SetSwitch(false);\n      return;\n    case GpuAvailability::kFlushAndMakeUnavailable: {\n    }\n      // FALLTHROUGH\n    case GpuAvailability::kUnavailable:\n      is_gpu_disabled_sync_switch_->SetSwitch(true);\n      return;\n    default:\n      FML_DCHECK(false);\n  }\n}\n\nvoid Shell::OnDisplayUpdates(DisplayUpdateType update_type,\n                             std::vector<std::unique_ptr<Display>> displays) {\n  display_manager_->HandleDisplayUpdates(update_type, std::move(displays));\n}\n\nvoid Shell::OnPostInvalidate(bool is_raster_frame) {\n  fml::TaskRunner::RunNowOrPostTask(\n      GetTaskRunners().GetPlatformTaskRunner(),\n      fml::MakeCopyable([weak = weak_platform_view_]() mutable {\n        if (weak) {\n          weak->PostInvalidate();\n        }\n      }));\n}\n\nconst std::weak_ptr<VsyncWaiter> Shell::GetVsyncWaiter() const {\n  return engine_->GetVsyncWaiter();\n}\n\nvoid Shell::DumpInfoToDevtoolEnabled(bool enabled) {\n  devtools_instrumentation_enabled_ = enabled;\n}\n\nvoid Shell::UpdateRasterInfo(\n    const std::vector<RasterCacheInfo>& raster_cache_info) {\n  if (devtools_instrumentation_enabled_ && devtool_instrumentation_) {\n    devtool_instrumentation_->ResetRasterDocument();\n    for (auto info : raster_cache_info) {\n      devtool_instrumentation_->UpdateRasterCacheInfo(\n          info.single_cache_size, info.single_cache_height,\n          info.single_cache_width, info.single_cache_color_type,\n          info.layer_address, info.cache_address, info.image);\n    }\n    devtool_instrumentation_->UpdateRasterInfoToDevtool();\n  }\n}\n\nvoid Shell::ReportTiming(const std::unordered_map<std::string, int64_t>& timing,\n                         const std::string& flag) {\n  // Make sure on Platform Thread.\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  if (weak_platform_view_) {\n    if (flag == kSetupFlag) {\n      weak_platform_view_->OnTimingSetup(timing);\n    } else if (flag == kUpdateFlag || flag == kForceUpdateFlag) {\n      weak_platform_view_->OnTimingUpdate(timing, flag);\n    }\n  }\n}\n\n#ifdef ENABLE_ACCESSIBILITY\nvoid Shell::UpdateSemantics(const clay::SemanticsUpdateNodes& update_nodes) {\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner());\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetPlatformTaskRunner(), [this, update_nodes]() {\n        if (platform_view_) {\n          platform_view_->UpdateSemantics(update_nodes);\n        }\n      });\n}\n\nvoid Shell::SetSemanticsEnabled(bool enabled) {\n  if (engine_) {\n    engine_->SetSemanticsEnabled(enabled);\n  }\n}\n\nvoid Shell::SetPageEnableAccessibilityElement(bool enabled) {\n  if (engine_) {\n    engine_->SetPageEnableAccessibilityElement(enabled);\n  }\n}\n\nvoid Shell::OnPlatformViewDispatchSemanticsAction(int virtual_view_id,\n                                                  int action) {\n  FML_DCHECK(is_setup_);\n  FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());\n\n  task_runners_.GetUITaskRunner()->PostTask(\n      [engine = engine_->GetWeakPtr(), virtual_view_id, action]() {\n        if (engine) {\n          engine->DispatchSemanticsAction(virtual_view_id, action);\n        }\n      });\n}\n#endif\n\nvoid Shell::CleanForRecycle() {\n  if (engine_) {\n    engine_->CleanForRecycle();\n  }\n\n  rasterizer_service_.Act(\n      [](auto& impl) mutable { impl.GetRasterizer()->CleanForRecycle(); });\n\n  clay::Puppet<clay::Owner::kPlatform, RasterFrameService>\n      raster_frame_service =\n          GetServiceManager()->GetService<RasterFrameService>();\n  raster_frame_service.Act([](auto& impl) { impl.CleanForRecycle(); });\n\n  waiting_for_first_frame_->store(true);\n  devtools_instrumentation_enabled_ = false;\n  devtool_instrumentation_.reset();\n}\n\nvoid Shell::PrepareForRecycle() {\n  if (engine_) {\n    engine_->PrepareForRecycle();\n  }\n\n  frame_timing_collector_ = std::make_shared<clay::FrameTimingCollector>(\n      task_runners_.GetPlatformTaskRunner());\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetUITaskRunner(),\n      [this, engine = weak_engine_,\n       ui_task_runner = task_runners_.GetUITaskRunner(),\n       raster_task_runner = task_runners_.GetRasterTaskRunner()] {\n        if (engine) {\n          engine->SetFrameTimingCollector(frame_timing_collector_);\n          frame_timing_collector_->SetPageView(engine->GetPageView());\n        }\n      });\n\n  clay::Puppet<clay::Owner::kPlatform, RasterFrameService>\n      raster_frame_service =\n          GetServiceManager()->GetService<RasterFrameService>();\n  raster_frame_service.Act([](auto& impl) { impl.PrepareForRecycle(); });\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/shell.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SHELL_H_\n#define CLAY_SHELL_COMMON_SHELL_H_\n\n#include <deque>\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <optional>\n#include <string>\n#include <string_view>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/common/recyclable.h\"\n#include \"clay/common/settings.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/shell/common/services/animator_info_service.h\"\n#include \"clay/shell/common/services/raster_frame_service.h\"\n#include \"clay/shell/common/services/rasterizer_service.h\"\n#if OS_ANDROID || OS_MAC || OS_WIN\n#include \"clay/memory/memory_pressure_listener.h\"\n#endif\n#include \"base/include/closure.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/synchronization/sync_switch.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/thread.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/fml/status.h\"\n#include \"clay/shell/common/devtools_instrumentation.h\"\n#include \"clay/shell/common/display_manager.h\"\n#include \"clay/shell/common/engine.h\"\n#include \"clay/shell/common/platform_view.h\"\n#include \"clay/shell/common/rasterizer.h\"\n#include \"clay/shell/common/resource_cache_limit_calculator.h\"\n\nnamespace clay {\nclass FrameTimingCollector;\nclass ResourceLoaderIntercept;\n}  // namespace clay\n\nnamespace clay {\n\n/// Values for |Shell::SetGpuAvailability|.\nenum class GpuAvailability {\n  /// Indicates that GPU operations should be permitted.\n  kAvailable = 0,\n  /// Indicates that the GPU is about to become unavailable, and to attempt to\n  /// flush any GPU related resources now.\n  kFlushAndMakeUnavailable = 1,\n  /// Indicates that the GPU is unavailable, and that no attempt should be made\n  /// to even flush GPU objects until it is available again.\n  kUnavailable = 2\n};\n\n//------------------------------------------------------------------------------\n/// Perhaps the single most important class in the Flutter engine repository.\n/// When embedders create a Flutter application, they are referring to the\n/// creation of an instance of a shell. Creation and destruction of the shell is\n/// synchronous and the embedder only holds a unique pointer to the shell. The\n/// shell does not create the threads its primary components run on. Instead, it\n/// is the embedder's responsibility to create threads and give the shell task\n/// runners for those threads. Due to deterministic destruction of the shell,\n/// the embedder can terminate all threads immediately after collecting the\n/// shell. The shell must be created and destroyed on the same thread, but,\n/// different shells (i.e. a separate instances of a Flutter application) may be\n/// run on different threads simultaneously. The task runners themselves do not\n/// have to be unique. If all task runner references given to the shell during\n/// shell creation point to the same task runner, the Flutter application is\n/// effectively single threaded.\n///\n/// The shell is the central nervous system of the Flutter application. None of\n/// the shell components are thread safe and must be created, accessed and\n/// destroyed on the same thread. To interact with one another, the various\n/// components delegate to the shell for communication. Instead of using back\n/// pointers to the shell, a delegation pattern is used by all components that\n/// want to communicate with one another. Because of this, the shell implements\n/// the delegate interface for all these components.\n///\n/// All shell methods accessed by the embedder may only be called on the\n/// platform task runner. In case the embedder wants to directly access a shell\n/// subcomponent, it is the embedder's responsibility to acquire a weak pointer\n/// to that component and post a task to the task runner used by the component\n/// to access its methods. The shell must also be destroyed on the platform\n/// task runner.\n///\nclass Shell final : public PlatformView::Delegate,\n                    public Engine::Delegate,\n                    public ResourceCacheLimitItem,\n                    public clay::Recyclable {\n public:\n  template <class T>\n  using CreateCallbackFnPtr = std::unique_ptr<T> (*)(\n      std::shared_ptr<clay::ServiceManager> service_manager);\n  template <class T>\n  using CreateCallback = std::function<std::unique_ptr<T>(\n      std::shared_ptr<clay::ServiceManager> service_manager, Shell&)>;\n  typedef std::function<std::unique_ptr<Engine>(\n      std::shared_ptr<clay::ServiceManager> service_manager,\n      TaskRunners task_runners, Settings settings,\n      fml::RefPtr<GPUUnrefQueue> unref_queue, Engine::Delegate* delegate)>\n      EngineCreateCallback;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Creates a shell instance using the provided settings. The\n  ///             callbacks to create the various shell subcomponents will be\n  ///             called on the appropriate threads before this method returns.\n  ///\n  /// @param[in]  task_runners             The task runners\n  /// @param[in]  settings                 The settings\n  /// @param[in]  on_create_platform_view  The callback that must return a\n  ///                                      platform view. This will be called on\n  ///                                      the platform task runner before this\n  ///                                      method returns.\n  /// @param[in]  on_create_rasterizer     That callback that must provide a\n  ///                                      valid rasterizer. This will be called\n  ///                                      on the render task runner before this\n  ///                                      method returns.\n  /// @param[in]  is_gpu_disabled          The default value for the switch that\n  ///                                      turns off the GPU.\n  ///\n  /// @return     A full initialized shell if the settings and callbacks are\n  ///             valid. The root isolate has been created but not yet launched.\n  ///             It may be launched by obtaining the engine weak pointer and\n  ///             posting a task onto the UI task runner with a valid run\n  ///             configuration to run the isolate. The embedder must always\n  ///             check the validity of the shell (using the IsSetup call)\n  ///             immediately after getting a pointer to it.\n  ///\n  static std::unique_ptr<Shell> Create(\n      std::shared_ptr<clay::ServiceManager> service_manager,\n      const TaskRunners& task_runners, Settings settings,\n      const CreateCallback<PlatformView>& on_create_platform_view,\n      const CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer,\n      bool is_gpu_disabled = false);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Destroys the shell. This is a synchronous operation and\n  ///             synchronous barrier blocks are introduced on the various\n  ///             threads to ensure shutdown of all shell sub-components before\n  ///             this method returns.\n  ///\n  ~Shell();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Creates one Shell from another Shell where the created Shell\n  ///             takes the opportunity to share any internal components it can.\n  ///             This results is a Shell that has a smaller startup time cost\n  ///             and a smaller memory footprint than an Shell created with the\n  ///             Create function.\n  ///\n  /// @see        http://flutter.dev/go/multiple-engines\n  std::unique_ptr<Shell> Spawn(\n      std::shared_ptr<clay::ServiceManager> service_manager,\n      const std::string& initial_route,\n      const CreateCallback<PlatformView>& on_create_platform_view,\n      const CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer) const;\n\n  std::shared_ptr<clay::ServiceManager> SpawnServiceManager() const;\n\n  //------------------------------------------------------------------------------\n  /// @return     The settings used to launch this shell.\n  ///\n  const Settings& GetSettings() const;\n\n  //------------------------------------------------------------------------------\n  /// @brief      If callers wish to interact directly with any shell\n  ///             subcomponents, they must (on the platform thread) obtain a\n  ///             task runner that the component is designed to run on and a\n  ///             weak pointer to that component. They may then post a task to\n  ///             that task runner, do the validity check on that task runner\n  ///             before performing any operation on that component. This\n  ///             accessor allows callers to access the task runners for this\n  ///             shell.\n  ///\n  /// @return     The task runners current in use by the shell.\n  ///\n  const TaskRunners& GetTaskRunners() const;\n\n  //------------------------------------------------------------------------------\n  /// @brief      Engines may only be accessed on the UI thread. This method is\n  ///             deprecated, and implementers should instead use other API\n  ///             available on the Shell or the PlatformView.\n  ///\n  /// @return     A weak pointer to the engine.\n  ///\n  fml::WeakPtr<Engine> GetEngine();\n  //----------------------------------------------------------------------------\n  /// @brief      Platform views may only be accessed on the platform task\n  ///             runner.\n  ///\n  /// @return     A weak pointer to the platform view.\n  ///\n  fml::WeakPtr<PlatformView> GetPlatformView();\n\n  //----------------------------------------------------------------------------\n  /// @brief      The IO Manager may only be accessed on the IO task runner.\n  ///\n  /// @return     A weak pointer to the IO manager.\n  ///\n\n  // Embedders should call this under low memory conditions to free up\n  // internal caches used.\n  //\n  // This method posts a task to the raster threads to signal the Rasterizer to\n  // free resources.\n\n  //----------------------------------------------------------------------------\n  /// @brief      Used by embedders to notify that there is a low memory\n  ///             warning. The shell will attempt to purge caches. Current, only\n  ///             the rasterizer cache is purged.\n  void NotifyLowMemoryWarning() const;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Used by embedders to check if all shell subcomponents are\n  ///             initialized. It is the embedder's responsibility to make this\n  ///             call before accessing any other shell method. A shell that is\n  ///             not set up must be discarded and another one created with\n  ///             updated settings.\n  ///\n  /// @return     Returns if the shell has been set up. Once set up, this does\n  ///             not change for the life-cycle of the shell.\n  ///\n  bool IsSetup() const;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Captures a screenshot and optionally Base64 encodes the data\n  ///             of the last layer tree rendered by the rasterizer in this\n  ///             shell.\n  ///\n  /// @param[in]  type           The type of screenshot to capture.\n  /// @param[in]  base64_encode  If the screenshot data should be base64\n  ///                            encoded.\n  ///\n  /// @return     The screenshot result.\n  ///\n  ScreenshotData ScreenshotSync(ScreenshotData::ScreenshotType type,\n                                uint32_t background_color);\n\n  void ScreenshotAsync(ScreenshotData::ScreenshotType type,\n                       uint32_t background_color,\n                       std::function<void(ScreenshotData)> callback);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Pauses the calling thread until the first frame is presented.\n  ///\n  /// @param[in]  timeout  The duration to wait before timing out. If this\n  ///                      duration would cause an overflow when added to\n  ///                      std::chrono::steady_clock::now(), this method will\n  ///                      wait indefinitely for the first frame.\n  ///\n  /// @return     'kOk' when the first frame has been presented before the\n  ///             timeout successfully, 'kFailedPrecondition' if called from the\n  ///             GPU or UI thread, 'kDeadlineExceeded' if there is a timeout.\n  ///\n  fml::Status WaitForFirstFrame(fml::TimeDelta timeout);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Used by embedders to reload the system fonts in\n  ///             FontCollection.\n  ///             It also clears the cached font families and send system\n  ///             channel message to framework to rebuild affected widgets.\n  ///\n  /// @return     Returns if shell reloads system fonts successfully.\n  ///\n  bool ReloadSystemFonts();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Used by embedders to get the last error if one exists.\n  ///\n  /// @return     Returns the last error code from the UI Isolate.\n  ///\n  //----------------------------------------------------------------------------\n  /// @brief     Accessor for the disable GPU SyncSwitch.\n  std::shared_ptr<const fml::SyncSwitch> GetIsGpuDisabledSyncSwitch() const;\n\n  //----------------------------------------------------------------------------\n  /// @brief     Marks the GPU as available or unavailable.\n  void SetGpuAvailability(GpuAvailability availability);\n\n  //----------------------------------------------------------------------------\n  /// @brief      Notifies the display manager of the updates.\n  ///\n  void OnDisplayUpdates(DisplayUpdateType update_type,\n                        std::vector<std::unique_ptr<Display>> displays);\n\n  //----------------------------------------------------------------------------\n  /// @brief Queries the `DisplayManager` for the main display refresh rate.\n  ///\n  double GetMainDisplayRefreshRate();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Install a new factory that can match against and decode image\n  ///             data.\n  /// @param[in]  factory   Callback that produces `ImageGenerator`s for\n  ///                       compatible input data.\n  /// @param[in]  priority  The priority used to determine the order in which\n  ///                       factories are tried. Higher values mean higher\n  ///                       priority. The built-in Skia decoders are installed\n  ///                       at priority 0, and so a priority > 0 takes precedent\n  ///                       over the builtin decoders. When multiple decoders\n  ///                       are added with the same priority, those which are\n  ///                       added earlier take precedent.\n  /// @see        `CreateCompatibleGenerator`\n  // void RegisterImageDecoder(ImageGeneratorFactory factory, int32_t priority);\n\n  // |Engine::Delegate|\n\n  const std::weak_ptr<VsyncWaiter> GetVsyncWaiter() const;\n\n  // Engine::Delegate\n  void ShowSoftInput(int type, int action) override;\n  void HideSoftInput() override;\n  void FilterInputAsync(\n      const std::string& input, const std::string& pattern,\n      std::function<void(const std::string&)> callback) override;\n\n  std::string ShouldInterceptUrl(const std::string& origin_url,\n                                 bool should_decode) override;\n  std::shared_ptr<clay::ResourceLoaderIntercept> GetResourceLoaderIntercept()\n      override;\n\n  void MakeRasterSnapshot(\n      std::unique_ptr<LayerTree> layer_tree,\n      std::function<void(fml::RefPtr<PaintImage>)> callback) override;\n  fml::RefPtr<PaintImage> MakeRasterSnapshot(GrPicturePtr picture,\n                                             skity::Vec2 picture_size) override;\n\n  void OnDrawHardware(bool force_draw);\n\n  void SetClipboardData(const std::u16string& data) override {\n    platform_view_->SetClipboardData(data);\n  }\n\n  std::u16string GetClipboardData() override {\n    return platform_view_->GetClipboardData();\n  }\n\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  // Text input related functions Begin.\n  void SetTextInputClient(int client_id, const char* input_action,\n                          const char* input_type) override {\n    platform_view_->SetTextInputClient(client_id, input_action, input_type);\n  }\n\n  void ClearTextInputClient() override {\n    platform_view_->ClearTextInputClient();\n  }\n\n  void SetEditableTransform(const float transform_matrix[16]) override {\n    platform_view_->SetEditableTransform(transform_matrix);\n  }\n\n  void SetEditingState(uint64_t selection_base, uint64_t composing_extent,\n                       const std::string& selection_affinity,\n                       const std::string& text, bool selection_directional,\n                       uint64_t selection_extent,\n                       uint64_t composing_base) override {\n    platform_view_->SetEditingState(\n        selection_base, composing_extent, selection_affinity, text,\n        selection_directional, selection_extent, composing_base);\n  }\n\n  void SetCaretRect(float x, float y, float width, float height) override {\n    platform_view_->SetCaretRect(x, y, width, height);\n  }\n\n  void setMarkedTextRect(float x, float y, float width, float height) override {\n    platform_view_->setMarkedTextRect(x, y, width, height);\n  }\n\n  void ShowTextInput() override { platform_view_->ShowTextInput(); }\n\n  void HideTextInput() override { platform_view_->HideTextInput(); }\n  // Text input related functions End.\n  void WindowMove() override { platform_view_->WindowMove(); }\n  void ActivateSystemCursor(int type, const std::string& path) override {\n    platform_view_->ActivateSystemCursor(type, path);\n  }\n#endif\n\n  std::string GetRasterCacheInfo();\n\n  void DumpInfoToDevtoolEnabled(bool enabled) override;\n\n  void UpdateRasterInfo(const std::vector<RasterCacheInfo>& raster_cache_info);\n  void SetDevtoolInstrumentation(\n      std::unique_ptr<clay::DevtoolsInstrumentation> devtool_instrumentation) {\n    devtool_instrumentation_ = std::move(devtool_instrumentation);\n  }\n  clay::DevtoolsInstrumentation* GetDevtoolInstrumentation() {\n    return devtool_instrumentation_.get();\n  }\n\n  void ReportTiming(const std::unordered_map<std::string, int64_t>& timing,\n                    const std::string& flag) override;\n\n  void UpdateRootSize(int32_t width, int32_t height) override;\n\n  void CleanForRecycle() override;\n  void PrepareForRecycle() override;\n\n  void RegisterDrawableImage(\n      std::shared_ptr<DrawableImage> drawable_image) override;\n  void UnregisterDrawableImage(int64_t image_id) override;\n\n  const std::shared_ptr<clay::ServiceManager>& GetServiceManager() const {\n    return service_manager_;\n  }\n\n  const std::shared_ptr<clay::FrameTimingCollector>& GetFrameTimingCollector()\n      const {\n    return frame_timing_collector_;\n  }\n\n  double GetDevicePixelRatio() const { return device_pixel_ratio_; }\n\n#if OS_ANDROID || OS_WIN || OS_MAC\n  void SetUpMemoryPressureListener();\n#endif\n\n#ifdef ENABLE_ACCESSIBILITY\n  void SetSemanticsEnabled(bool enabled);\n  void SetPageEnableAccessibilityElement(bool enabled);\n#endif\n\n  void OnPostInvalidate(bool is_raster_frame);\n\n private:\n  const std::shared_ptr<clay::ServiceManager> service_manager_;\n  const TaskRunners task_runners_;\n  const clay::Puppet<clay::Owner::kPlatform, AnimatorInfoService>\n      animator_info_service_;\n  const clay::Puppet<clay::Owner::kPlatform, RasterizerService>\n      rasterizer_service_;\n  std::shared_ptr<ResourceCacheLimitCalculator>\n      resource_cache_limit_calculator_;\n  size_t resource_cache_limit_;\n  const Settings settings_;\n  std::unique_ptr<Engine> engine_;\n  std::unique_ptr<PlatformView> platform_view_;  // on platform task runner\n\n  std::shared_ptr<fml::SyncSwitch> is_gpu_disabled_sync_switch_;\n\n  std::shared_ptr<clay::FrameTimingCollector> frame_timing_collector_;\n  std::unique_ptr<clay::DevtoolsInstrumentation> devtool_instrumentation_;\n\n  fml::WeakPtr<Engine> weak_engine_;  // to be shared across threads\n  fml::WeakPtr<PlatformView>\n      weak_platform_view_;  // to be shared across threads\n\n  bool is_setup_ = false;\n  bool is_added_to_service_protocol_ = false;\n  uint64_t next_pointer_flow_id_ = 0;\n\n  std::shared_ptr<std::atomic<bool>> waiting_for_first_frame_ =\n      std::make_shared<std::atomic<bool>>(true);\n  std::mutex waiting_for_first_frame_mutex_;\n  std::condition_variable waiting_for_first_frame_condition_;\n  std::shared_ptr<std::atomic<bool>> screenshot =\n      std::make_shared<std::atomic<bool>>(true);\n\n  // Whether there's a task scheduled to report the timings.\n  bool frame_timings_report_scheduled_ = false;\n\n  // Vector of FrameTiming::kCount * n timestamps for n frames whose timings\n  // have not been reported yet.\n  std::vector<int64_t> unreported_timings_;\n\n  /// Manages the displays. This class is thread safe, can be accessed from any\n  /// of the threads.\n  std::shared_ptr<DisplayManager> display_manager_;\n\n  // protects expected_frame_size_ which is set on platform thread and read on\n  // raster thread\n  std::shared_ptr<std::mutex> resize_mutex_ = std::make_shared<std::mutex>();\n\n  // used to discard wrong size layer tree produced during interactive resizing\n  std::shared_ptr<skity::Vec2> expected_frame_size_ =\n      std::make_shared<skity::Vec2>();\n\n  // Used to communicate the right frame bounds via service protocol.\n  double device_pixel_ratio_ = 0.0;\n\n  // How many frames have been timed since last report.\n  size_t UnreportedFramesCount() const;\n\n  bool has_set_gr_context_ = false;\n\n#if OS_ANDROID || OS_WIN || OS_MAC\n  std::unique_ptr<clay::MemoryPressureListener> memory_pressure_listener_ =\n      nullptr;\n#endif\n\n  bool devtools_instrumentation_enabled_ = false;\n\n  Shell(std::shared_ptr<clay::ServiceManager> service_manager,\n        const TaskRunners& task_runners,\n        const std::shared_ptr<ResourceCacheLimitCalculator>&\n            resource_cache_limit_calculator,\n        const Settings& settings, bool is_gpu_disabled);\n\n  static std::unique_ptr<Shell> CreateShellOnPlatformThread(\n      std::shared_ptr<clay::ServiceManager> service_manager,\n      const std::shared_ptr<ResourceCacheLimitCalculator>&\n          resource_cache_limit_calculator,\n      const TaskRunners& task_runners, const Settings& settings,\n      const Shell::CreateCallback<PlatformView>& on_create_platform_view,\n      const Shell::CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer,\n      const EngineCreateCallback& on_create_engine, bool is_gpu_disabled);\n\n  static std::unique_ptr<Shell> CreateWithSnapshot(\n      std::shared_ptr<clay::ServiceManager> service_manager,\n      const TaskRunners& task_runners,\n      const std::shared_ptr<ResourceCacheLimitCalculator>&\n          resource_cache_limit_calculator,\n      Settings settings,\n      const CreateCallback<PlatformView>& on_create_platform_view,\n      const CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer,\n      const EngineCreateCallback& on_create_engine, bool is_gpu_disabled);\n\n  bool Setup(std::unique_ptr<PlatformView> platform_view,\n             std::unique_ptr<Engine> engine);\n\n  void ReportTimings();\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewCreated() override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewDestroyed() override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewScheduleFrame() override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewSetViewportMetrics(\n      const ViewportMetrics& metrics) override;\n\n  // |PlatformView::Delegate|\n  bool OnPlatformViewDispatchPointerDataPacket(\n      std::unique_ptr<PointerDataPacket> packet) override;\n\n  void OnPlatformViewDispatchKeyEvent(\n      std::unique_ptr<clay::KeyEvent> key_event,\n      std::function<void(bool /* handled */)> callback) override;\n  // |PlatformView::Delegate|\n  void OnPlatformViewSendKeyboardEvent(\n      std::unique_ptr<clay::KeyEvent> key_event) override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewPerformEditorAction(\n      clay::KeyboardAction action_code) override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewDeleteSurroundingText(int before_length,\n                                           int after_length) override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewOnEnterForeground() override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewOnEnterBackground() override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewSetDefaultFocusRingEnabled(bool enable) override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewSetPerformanceOverlayEnabled(bool enable) override;\n\n  // |PlatformView::Delegate|\n  void OnDefaultOverflowVisibleChanged(bool visible) override;\n\n  // |PlatformView::Delegate|\n  void OnExposurePropsChanged(int freq,\n                              bool enable_exposure_ui_margin) override;\n\n  // |PlatformView::Delegate|\n  void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override;\n\n  // |PlatformView::Delegate|\n  const Settings& OnPlatformViewGetSettings() const override;\n\n#ifdef ENABLE_ACCESSIBILITY\n  void OnPlatformViewDispatchSemanticsAction(int virtual_view_id,\n                                             int action) override;\n  void UpdateSemantics(const clay::SemanticsUpdateNodes& update_nodes) override;\n#endif\n\n  // |ResourceCacheLimitItem|\n  size_t GetResourceCacheLimit() override { return resource_cache_limit_; }\n\n  fml::WeakPtrFactory<Shell> weak_factory_;\n\n  friend class testing::ShellTest;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Shell);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SHELL_H_\n"
  },
  {
    "path": "clay/shell/common/shell_common_rendering_backend.h",
    "content": "/*\n * Copyright 2025 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#ifndef CLAY_SHELL_COMMON_SHELL_COMMON_RENDERING_BACKEND_H_\n#define CLAY_SHELL_COMMON_SHELL_COMMON_RENDERING_BACKEND_H_\n\n#ifndef ENABLE_SKITY\n#ifndef USE_SYSTEM_ICU\n#include \"clay/fml/icu_util.h\"\n#endif  // USE_SYSTEM_ICU\n#include \"clay/flow/layers/offscreen_surface.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#else\n#include \"clay/gfx/paint_image_skity.h\"\n#endif  // ENABLE_SKITY\n\n#endif  // CLAY_SHELL_COMMON_SHELL_COMMON_RENDERING_BACKEND_H_\n"
  },
  {
    "path": "clay/shell/common/skia_event_tracer_impl.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/skia_event_tracer_impl.h\"\n\n#define TRACE_EVENT_HIDE_MACROS\n#include <map>\n#include <set>\n#include <utility>\n#include <vector>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/posix_wrappers.h\"\n#include \"third_party/skia/include/utils/SkEventTracer.h\"\n#include \"third_party/skia/include/utils/SkTraceEventPhase.h\"\n\n#if defined(OS_FUCHSIA)\n\n#include <lib/trace-engine/context.h>\n#include <lib/trace-engine/instrumentation.h>\n\n#include <algorithm>\n#include <cstring>\n\n// Skia's copy of these flags are defined in a private header, so, as is\n// commonly done with \"trace_event_common.h\" values, copy them inline here (see\n// https://cs.chromium.org/chromium/src/base/trace_event/common/trace_event_common.h?l=1102-1110&rcl=239b85aeb3a6c07b33b5f162cd0ae8128eabf44d).\n//\n// Type values for identifying types in the TraceValue union.\n#define TRACE_VALUE_TYPE_BOOL (static_cast<unsigned char>(1))\n#define TRACE_VALUE_TYPE_UINT (static_cast<unsigned char>(2))\n#define TRACE_VALUE_TYPE_INT (static_cast<unsigned char>(3))\n#define TRACE_VALUE_TYPE_DOUBLE (static_cast<unsigned char>(4))\n#define TRACE_VALUE_TYPE_POINTER (static_cast<unsigned char>(5))\n#define TRACE_VALUE_TYPE_STRING (static_cast<unsigned char>(6))\n#define TRACE_VALUE_TYPE_COPY_STRING (static_cast<unsigned char>(7))\n#define TRACE_VALUE_TYPE_CONVERTABLE (static_cast<unsigned char>(8))\n\n#endif  // defined(OS_FUCHSIA)\n\nnamespace clay {\n\nnamespace {\n\n// Skia prepends this string to the category names of its trace events.\n// Defined in Skia's src/core/SkTraceEvent.h.\nconstexpr std::string_view kTraceCategoryPrefix = \"disabled-by-default-\";\n\n// Category name used for shader compilation events.\nconstexpr std::string_view kShaderCategoryName =\n    \"disabled-by-default-skia.shaders\";\n\n#if !defined(OS_FUCHSIA)\n// Argument name of the tag used by DevTools.\nconstexpr char kDevtoolsTagArg[] = \"devtoolsTag\";\n\n// DevtoolsTag value for shader events.\nconstexpr char kShadersDevtoolsTag[] = \"shaders\";\n#endif  // !defined(OS_FUCHSIA)\n\n#if defined(OS_FUCHSIA)\ntemplate <class T, class U>\ninline T BitCast(const U& u) {\n  static_assert(sizeof(T) == sizeof(U));\n\n  T t;\n  memcpy(&t, &u, sizeof(t));\n  return t;\n}\n#endif  // defined(OS_FUCHSIA)\n\n}  // namespace\n\nclass ClayEventTracer : public SkEventTracer {\n public:\n  static constexpr const char* kSkiaTag = \"skia\";\n  static constexpr uint8_t kYes = 1;\n  static constexpr uint8_t kNo = 0;\n\n  ClayEventTracer(bool enabled,\n                  const std::optional<std::vector<std::string>>& allowlist)\n      : enabled_(enabled ? kYes : kNo) {\n    if (allowlist.has_value()) {\n      allowlist_.emplace();\n      for (const std::string& category : *allowlist) {\n        allowlist_->insert(std::string(kTraceCategoryPrefix) + category);\n      }\n    }\n  }\n\n  SkEventTracer::Handle addTraceEvent(char phase,\n                                      const uint8_t* category_enabled_flag,\n                                      const char* name, uint64_t id,\n                                      int num_args, const char** p_arg_names,\n                                      const uint8_t* p_arg_types,\n                                      const uint64_t* p_arg_values,\n                                      uint8_t flags) override {\n#if defined(OS_FUCHSIA)\n    static trace_site_t trace_site;\n    trace_string_ref_t category_ref;\n    trace_context_t* trace_context = trace_acquire_context_for_category_cached(\n        kSkiaTag, &trace_site, &category_ref);\n\n    if (likely(!trace_context)) {\n      return 0;\n    }\n\n    trace_ticks_t ticks = zx_ticks_get();\n\n    trace_thread_ref_t thread_ref;\n    trace_context_register_current_thread(trace_context, &thread_ref);\n    trace_string_ref_t name_ref;\n    trace_context_register_string_literal(trace_context, name, &name_ref);\n\n    constexpr int kMaxArgs = 2;\n    trace_arg_t trace_args[kMaxArgs] = {};\n    FML_DCHECK(num_args >= 0);\n    int num_trace_args = std::min(kMaxArgs, num_args);\n\n    for (int i = 0; i < num_trace_args; i++) {\n      const char* arg_name = p_arg_names[i];\n      const uint8_t arg_type = p_arg_types[i];\n      const uint64_t arg_value = p_arg_values[i];\n\n      trace_string_ref_t arg_name_string_ref =\n          trace_context_make_registered_string_literal(trace_context, arg_name);\n\n      trace_arg_value_t trace_arg_value;\n      switch (arg_type) {\n        case TRACE_VALUE_TYPE_BOOL: {\n          trace_arg_value = trace_make_bool_arg_value(!!arg_value);\n          break;\n        }\n        case TRACE_VALUE_TYPE_UINT:\n          trace_arg_value = trace_make_uint64_arg_value(arg_value);\n          break;\n        case TRACE_VALUE_TYPE_INT:\n          trace_arg_value =\n              trace_make_int64_arg_value(BitCast<int64_t>(arg_value));\n          break;\n        case TRACE_VALUE_TYPE_DOUBLE:\n          trace_arg_value =\n              trace_make_double_arg_value(BitCast<double>(arg_value));\n          break;\n        case TRACE_VALUE_TYPE_POINTER:\n          trace_arg_value =\n              trace_make_pointer_arg_value(BitCast<uintptr_t>(arg_value));\n          break;\n        case TRACE_VALUE_TYPE_STRING: {\n          trace_string_ref_t arg_value_string_ref =\n              trace_context_make_registered_string_literal(\n                  trace_context, reinterpret_cast<const char*>(arg_value));\n          trace_arg_value = trace_make_string_arg_value(arg_value_string_ref);\n          break;\n        }\n        case TRACE_VALUE_TYPE_COPY_STRING: {\n          const char* arg_value_as_cstring =\n              reinterpret_cast<const char*>(arg_value);\n          trace_string_ref_t arg_value_string_ref =\n              trace_context_make_registered_string_copy(\n                  trace_context, arg_value_as_cstring,\n                  strlen(arg_value_as_cstring));\n          trace_arg_value = trace_make_string_arg_value(arg_value_string_ref);\n          break;\n        }\n        case TRACE_VALUE_TYPE_CONVERTABLE:\n          trace_arg_value = trace_make_null_arg_value();\n          break;\n        default:\n          trace_arg_value = trace_make_null_arg_value();\n      }\n\n      trace_args[i] = trace_make_arg(arg_name_string_ref, trace_arg_value);\n    }\n\n    switch (phase) {\n      case TRACE_EVENT_PHASE_BEGIN:\n      case TRACE_EVENT_PHASE_COMPLETE:\n        trace_context_write_duration_begin_event_record(\n            trace_context, ticks, &thread_ref, &category_ref, &name_ref,\n            trace_args, num_trace_args);\n        break;\n      case TRACE_EVENT_PHASE_END:\n        trace_context_write_duration_end_event_record(\n            trace_context, ticks, &thread_ref, &category_ref, &name_ref,\n            trace_args, num_trace_args);\n        break;\n      case TRACE_EVENT_PHASE_INSTANT:\n        trace_context_write_instant_event_record(\n            trace_context, ticks, &thread_ref, &category_ref, &name_ref,\n            TRACE_SCOPE_THREAD, trace_args, num_trace_args);\n        break;\n      case TRACE_EVENT_PHASE_ASYNC_BEGIN:\n        trace_context_write_async_begin_event_record(\n            trace_context, ticks, &thread_ref, &category_ref, &name_ref, id,\n            trace_args, num_trace_args);\n        break;\n      case TRACE_EVENT_PHASE_ASYNC_END:\n        trace_context_write_async_end_event_record(\n            trace_context, ticks, &thread_ref, &category_ref, &name_ref, id,\n            trace_args, num_trace_args);\n        break;\n      default:\n        break;\n    }\n\n    trace_release_context(trace_context);\n\n#else   // defined(OS_FUCHSIA)\n    const char* devtoolsTag = nullptr;\n    if (shaders_category_flag_ &&\n        category_enabled_flag == shaders_category_flag_) {\n      devtoolsTag = kShadersDevtoolsTag;\n    }\n    switch (phase) {\n      case TRACE_EVENT_PHASE_BEGIN:\n      case TRACE_EVENT_PHASE_COMPLETE:\n        if (devtoolsTag) {\n          TRACE_EVENT(kSkiaTag, name, kDevtoolsTagArg, devtoolsTag);\n        } else {\n          TRACE_EVENT(kSkiaTag, name);\n        }\n        break;\n      case TRACE_EVENT_PHASE_END:\n        TRACE_EVENT_END(kSkiaTag);\n        break;\n      case TRACE_EVENT_PHASE_INSTANT:\n        if (devtoolsTag) {\n          TRACE_EVENT_INSTANT(kSkiaTag, name, kDevtoolsTagArg, devtoolsTag);\n        } else {\n          TRACE_EVENT_INSTANT(kSkiaTag, name);\n        }\n        break;\n      case TRACE_EVENT_PHASE_ASYNC_BEGIN:\n        if (devtoolsTag) {\n          TRACE_EVENT_BEGIN(kSkiaTag, name, lynx::perfetto::Track(id),\n                            kDevtoolsTagArg, devtoolsTag);\n        } else {\n          TRACE_EVENT_BEGIN(kSkiaTag, name, lynx::perfetto::Track(id));\n        }\n        break;\n      case TRACE_EVENT_PHASE_ASYNC_END:\n        if (devtoolsTag) {\n          TRACE_EVENT_END(kSkiaTag, lynx::perfetto::Track(id), kDevtoolsTagArg,\n                          devtoolsTag);\n        } else {\n          TRACE_EVENT_END(kSkiaTag, lynx::perfetto::Track(id));\n        }\n        break;\n      default:\n        break;\n    }\n#endif  // defined(OS_FUCHSIA)\n    return 0;\n  }\n\n  void updateTraceEventDuration(const uint8_t* category_enabled_flag,\n                                const char* name,\n                                SkEventTracer::Handle handle) override {\n    // This is only ever called from a scoped trace event so we will just end\n    // the section.\n#if defined(OS_FUCHSIA)\n    TRACE_DURATION_END(kSkiaTag, name);\n#else\n    TRACE_EVENT_END(kSkiaTag);\n#endif\n  }\n\n  const uint8_t* getCategoryGroupEnabled(const char* name) override {\n    // Skia will only use long-lived string literals as event names.\n    std::lock_guard<std::mutex> lock(flag_map_mutex_);\n    auto flag_it = category_flag_map_.find(name);\n    if (flag_it == category_flag_map_.end()) {\n      bool allowed;\n      if (enabled_) {\n        allowed = !allowlist_.has_value() ||\n                  allowlist_->find(name) != allowlist_->end();\n      } else {\n        allowed = false;\n      }\n      flag_it = category_flag_map_.insert(std::make_pair(name, allowed)).first;\n      const uint8_t* flag = &flag_it->second;\n      reverse_flag_map_.insert(std::make_pair(flag, name));\n      if (kShaderCategoryName == name) {\n        shaders_category_flag_ = flag;\n      }\n    }\n    return &flag_it->second;\n  }\n\n  const char* getCategoryGroupName(\n      const uint8_t* category_enabled_flag) override {\n    std::lock_guard<std::mutex> lock(flag_map_mutex_);\n    auto reverse_it = reverse_flag_map_.find(category_enabled_flag);\n    if (reverse_it != reverse_flag_map_.end()) {\n      return reverse_it->second;\n    } else {\n      return kSkiaTag;\n    }\n  }\n\n private:\n  uint8_t enabled_;\n  std::optional<std::set<std::string>> allowlist_;\n  std::mutex flag_map_mutex_;\n  std::map<const char*, uint8_t> category_flag_map_;\n  std::map<const uint8_t*, const char*> reverse_flag_map_;\n  const uint8_t* shaders_category_flag_ = nullptr;\n  BASE_DISALLOW_COPY_AND_ASSIGN(ClayEventTracer);\n};\n\nvoid InitSkiaEventTracer(\n    bool enabled, const std::optional<std::vector<std::string>>& allowlist) {\n  auto tracer = new ClayEventTracer(enabled, allowlist);\n  // Initialize the binding to Skia's tracing events. Skia will\n  // take ownership of and clean up the memory allocated here.\n  SkEventTracer::SetInstance(tracer);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/skia_event_tracer_impl.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_SKIA_EVENT_TRACER_IMPL_H_\n#define CLAY_SHELL_COMMON_SKIA_EVENT_TRACER_IMPL_H_\n\n#include <mutex>\n#include <optional>\n#include <string>\n#include <vector>\n\nnamespace clay {\n\nvoid InitSkiaEventTracer(\n    bool enabled, const std::optional<std::vector<std::string>>& allowlist);\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SKIA_EVENT_TRACER_IMPL_H_\n"
  },
  {
    "path": "clay/shell/common/switches.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <algorithm>\n#include <iomanip>\n#include <iostream>\n#include <iterator>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <vector>\n\n#include \"clay/fml/native_library.h\"\n#include \"clay/fml/paths.h\"\n#include \"clay/fml/size.h\"\n#include \"clay/version/version.h\"\n\n// Include once for the default enum definition.\n#include \"clay/shell/common/switches.h\"\n\n#undef CLAY_SHELL_COMMON_SWITCHES_H_\n\nstruct SwitchDesc {\n  clay::Switch sw;\n  const std::string_view flag;\n  const char* help;\n};\n\n#undef DEF_SWITCHES_START\n#undef DEF_SWITCH\n#undef DEF_SWITCHES_END\n\n// clang-format off\n#define DEF_SWITCHES_START static const struct SwitchDesc gSwitchDescs[] = {\n#define DEF_SWITCH(p_swtch, p_flag, p_help) \\\n  { clay::Switch::p_swtch, p_flag, \"\"/*p_help*/ },\n#define DEF_SWITCHES_END };\n// clang-format on\n\n// Include again for struct definition.\n#include \"clay/shell/common/switches.h\"\n\n// Define symbols for the ICU data that is linked into the Clay library on\n// Android.  This is a workaround for crashes seen when doing dynamic lookups\n// of the engine's own symbols on some older versions of Android.\n#ifndef USE_SYSTEM_ICU\n#if defined(ENABLE_BINARY_ICUDTL)\nextern uint8_t _binary_icudtl_dat_start[];\nextern uint8_t _binary_icudtl_dat_end[];\n\nstatic std::unique_ptr<fml::Mapping> GetICUStaticMapping() {\n  return std::make_unique<fml::NonOwnedMapping>(\n      _binary_icudtl_dat_start,\n      _binary_icudtl_dat_end - _binary_icudtl_dat_start);\n}\n#endif\n#endif\n\nnamespace clay {\nconst std::string_view FlagForSwitch(Switch swtch) {\n  for (const auto& gSwitchDesc : gSwitchDescs) {\n    if (gSwitchDesc.sw == swtch) {\n      return gSwitchDesc.flag;\n    }\n  }\n  return std::string_view();\n}\n\nstatic std::vector<std::string> ParseCommaDelimited(const std::string& input) {\n  std::istringstream ss(input);\n  std::vector<std::string> result;\n  std::string token;\n  while (std::getline(ss, token, ',')) {\n    result.push_back(token);\n  }\n  return result;\n}\n\ntemplate <typename T>\nstatic bool GetSwitchValue(const fml::CommandLine& command_line, Switch sw,\n                           T* result) {\n  std::string switch_string;\n\n  if (!command_line.GetOptionValue(FlagForSwitch(sw), &switch_string)) {\n    return false;\n  }\n\n  std::stringstream stream(switch_string);\n  T value = 0;\n  if (stream >> value) {\n    *result = value;\n    return true;\n  }\n\n  return false;\n}\n\n#ifndef USE_SYSTEM_ICU\nstd::unique_ptr<fml::Mapping> GetSymbolMapping(\n    const std::string& symbol_prefix, const std::string& native_lib_path) {\n  const uint8_t* mapping = nullptr;\n  intptr_t size;\n\n  auto lookup_symbol = [&mapping, &size, symbol_prefix](\n                           const fml::RefPtr<fml::NativeLibrary>& library) {\n    mapping = library->ResolveSymbol((symbol_prefix + \"_start\").c_str());\n    size = reinterpret_cast<intptr_t>(\n        library->ResolveSymbol((symbol_prefix + \"_size\").c_str()));\n  };\n\n  fml::RefPtr<fml::NativeLibrary> library =\n      fml::NativeLibrary::CreateForCurrentProcess();\n  lookup_symbol(library);\n\n  if (!(mapping && size)) {\n    // Symbol lookup for the current process fails on some devices.  As a\n    // fallback, try doing the lookup based on the path to the Flutter library.\n    library = fml::NativeLibrary::Create(native_lib_path.c_str());\n    lookup_symbol(library);\n  }\n\n  FML_CHECK(mapping && size) << \"Unable to resolve symbols: \" << symbol_prefix;\n  return std::make_unique<fml::NonOwnedMapping>(mapping, size);\n}\n#endif\n\nSettings SettingsFromCommandLine(const fml::CommandLine& command_line) {\n  Settings settings = {};\n\n  settings.enable_software_rendering =\n      command_line.HasOption(FlagForSwitch(Switch::EnableSoftwareRendering));\n\n#if !FLUTTER_RELEASE\n  settings.trace_skia = true;\n\n  if (command_line.HasOption(FlagForSwitch(Switch::TraceSkia))) {\n    // If --trace-skia is specified, then log all Skia events.\n    settings.trace_skia_allowlist.reset();\n  } else {\n    std::string trace_skia_allowlist;\n    command_line.GetOptionValue(FlagForSwitch(Switch::TraceSkiaAllowlist),\n                                &trace_skia_allowlist);\n    if (trace_skia_allowlist.size()) {\n      settings.trace_skia_allowlist = ParseCommaDelimited(trace_skia_allowlist);\n    } else {\n      settings.trace_skia_allowlist = {\"skia.shaders\"};\n    }\n  }\n#endif  // !FLUTTER_RELEASE\n\n  std::string trace_allowlist;\n  command_line.GetOptionValue(FlagForSwitch(Switch::TraceAllowlist),\n                              &trace_allowlist);\n  settings.trace_allowlist = ParseCommaDelimited(trace_allowlist);\n\n  settings.skia_deterministic_rendering_on_cpu =\n      command_line.HasOption(FlagForSwitch(Switch::SkiaDeterministicRendering));\n\n  settings.verbose_logging =\n      command_line.HasOption(FlagForSwitch(Switch::VerboseLogging));\n\n  command_line.GetOptionValue(FlagForSwitch(Switch::ClayAssetsDir),\n                              &settings.assets_path);\n\n  command_line.GetOptionValue(FlagForSwitch(Switch::CacheDirPath),\n                              &settings.temp_directory_path);\n\n#ifndef USE_SYSTEM_ICU\n  if (settings.icu_initialization_required) {\n    command_line.GetOptionValue(FlagForSwitch(Switch::ICUDataFilePath),\n                                &settings.icu_data_path);\n#if defined(ENABLE_BINARY_ICUDTL)\n    settings.icu_mapper = GetICUStaticMapping;\n#else\n    if (command_line.HasOption(FlagForSwitch(Switch::ICUSymbolPrefix))) {\n      std::string icu_symbol_prefix, native_lib_path;\n      command_line.GetOptionValue(FlagForSwitch(Switch::ICUSymbolPrefix),\n                                  &icu_symbol_prefix);\n      command_line.GetOptionValue(FlagForSwitch(Switch::ICUNativeLibPath),\n                                  &native_lib_path);\n      settings.icu_mapper = [icu_symbol_prefix, native_lib_path] {\n        return GetSymbolMapping(icu_symbol_prefix, native_lib_path);\n      };\n    }\n#endif\n  }\n#endif  // USE_SYSTEM_ICU\n\n  settings.use_test_fonts =\n      command_line.HasOption(FlagForSwitch(Switch::UseTestFonts));\n  settings.use_asset_fonts =\n      !command_line.HasOption(FlagForSwitch(Switch::DisableAssetFonts));\n\n  std::string enable_skparagraph = command_line.GetOptionValueWithDefault(\n      FlagForSwitch(Switch::EnableSkParagraph), \"\");\n  settings.enable_skparagraph = enable_skparagraph != \"false\";\n\n  settings.prefetched_default_font_manager = command_line.HasOption(\n      FlagForSwitch(Switch::PrefetchedDefaultFontManager));\n\n  settings.enable_default_focus_ring =\n      command_line.HasOption(FlagForSwitch(Switch::EnableDefaultFocusRing));\n\n  settings.dump_skp_on_shader_compilation =\n      command_line.HasOption(FlagForSwitch(Switch::DumpSkpOnShaderCompilation));\n\n  settings.cache_sksl =\n      command_line.HasOption(FlagForSwitch(Switch::CacheSkSL));\n\n  settings.purge_persistent_cache =\n      command_line.HasOption(FlagForSwitch(Switch::PurgePersistentCache));\n\n  if (command_line.HasOption(\n          FlagForSwitch(Switch::ResourceCacheMaxBytesThreshold))) {\n    std::string resource_cache_max_bytes_threshold;\n    command_line.GetOptionValue(\n        FlagForSwitch(Switch::ResourceCacheMaxBytesThreshold),\n        &resource_cache_max_bytes_threshold);\n    settings.resource_cache_max_bytes_threshold =\n        std::stoi(resource_cache_max_bytes_threshold);\n  }\n\n  if (command_line.HasOption(FlagForSwitch(Switch::MsaaSamples))) {\n    std::string msaa_samples;\n    command_line.GetOptionValue(FlagForSwitch(Switch::MsaaSamples),\n                                &msaa_samples);\n    settings.msaa_samples = std::stoi(msaa_samples);\n  }\n  settings.enable_sync_compositor =\n      command_line.HasOption(FlagForSwitch(Switch::EnableRendermodeSync));\n  Settings::SetStencilBuffer(\n      command_line.HasOption(FlagForSwitch(Switch::EnableStencilBuffer)) &&\n      !settings.enable_sync_compositor);\n  if (command_line.HasOption(\n          FlagForSwitch(Switch::GpuResourceCacheMultiplier))) {\n    std::string mul = \"1\";\n    command_line.GetOptionValue(\n        FlagForSwitch(Switch::GpuResourceCacheMultiplier), &mul);\n    settings.gpu_resource_cache_multiplier = std::stoi(mul);\n  }\n  return settings;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/switches.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string_view>\n\n#include \"clay/common/settings.h\"\n#include \"clay/fml/command_line.h\"\n\n#ifndef CLAY_SHELL_COMMON_SWITCHES_H_\n#define CLAY_SHELL_COMMON_SWITCHES_H_\n\nnamespace clay {\n\n// clang-format off\n#ifndef DEF_SWITCHES_START\n#define DEF_SWITCHES_START enum class Switch {\n#endif\n#ifndef DEF_SWITCH\n#define DEF_SWITCH(swtch, flag, help) swtch,\n#endif\n#ifndef DEF_SWITCHES_END\n#define DEF_SWITCHES_END Sentinel, } ;\n#endif\n// clang-format on\n\nDEF_SWITCHES_START\nDEF_SWITCH(CacheDirPath, \"cache-dir-path\",\n           \"Path to the cache directory. \"\n           \"This is different from the persistent_cache_path in embedder.h, \"\n           \"which is used for Skia shader cache.\")\nDEF_SWITCH(ICUDataFilePath, \"icu-data-file-path\", \"Path to the ICU data file.\")\nDEF_SWITCH(ICUSymbolPrefix, \"icu-symbol-prefix\",\n           \"Prefix for the symbols representing ICU data linked into the \"\n           \"Flutter library.\")\nDEF_SWITCH(ICUNativeLibPath, \"icu-native-lib-path\",\n           \"Path to the library file that exports the ICU data.\")\nDEF_SWITCH(EnableSoftwareRendering, \"enable-software-rendering\",\n           \"Enable rendering using the Skia software backend. This is useful \"\n           \"when testing Flutter on emulators. By default, Flutter will \"\n           \"attempt to either use OpenGL, Metal, or Vulkan.\")\nDEF_SWITCH(SkiaDeterministicRendering, \"skia-deterministic-rendering\",\n           \"Skips the call to SkGraphics::Init(), thus avoiding swapping out \"\n           \"some Skia function pointers based on available CPU features. This \"\n           \"is used to obtain 100% deterministic behavior in Skia rendering.\")\nDEF_SWITCH(ClayAssetsDir, \"clay-assets-dir\",\n           \"Path to the Clay assets directory.\")\nDEF_SWITCH(Help, \"help\", \"Display this help text.\")\nDEF_SWITCH(TraceSkia, \"trace-skia\",\n           \"Trace Skia calls. This is useful when debugging the GPU thread.\"\n           \"By default, Skia tracing is not enabled to reduce the number of \"\n           \"traced events\")\nDEF_SWITCH(TraceSkiaAllowlist, \"trace-skia-allowlist\",\n           \"Filters out all Skia trace event categories except those that are \"\n           \"specified in this comma separated list.\")\nDEF_SWITCH(\n    TraceAllowlist, \"trace-allowlist\",\n    \"Filters out all trace events except those that are specified in this \"\n    \"comma separated list of allowed prefixes.\")\nDEF_SWITCH(DumpSkpOnShaderCompilation, \"dump-skp-on-shader-compilation\",\n           \"Automatically dump the skp that triggers new shader compilations. \"\n           \"This is useful for writing custom ShaderWarmUp to reduce jank. \"\n           \"By default, this is not enabled to reduce the overhead. \")\nDEF_SWITCH(CacheSkSL, \"cache-sksl\",\n           \"Only cache the shader in SkSL instead of binary or GLSL. This \"\n           \"should only be used during development phases. The generated SkSLs \"\n           \"can later be used in the release build for shader precompilation \"\n           \"at launch in order to eliminate the shader-compile jank.\")\nDEF_SWITCH(PurgePersistentCache, \"purge-persistent-cache\",\n           \"Remove all existing persistent cache. This is mainly for debugging \"\n           \"purposes such as reproducing the shader compilation jank.\")\nDEF_SWITCH(UseTestFonts, \"use-test-fonts\",\n           \"Running tests that layout and measure text will not yield \"\n           \"consistent results across various platforms. Enabling this option \"\n           \"will make font resolution default to the Ahem test font on all \"\n           \"platforms (See https://www.w3.org/Style/CSS/Test/Fonts/Ahem/). \"\n           \"This option is only available on the desktop test shells.\")\nDEF_SWITCH(DisableAssetFonts, \"disable-asset-fonts\",\n           \"Prevents usage of any non-test fonts unless they were explicitly \"\n           \"This option is only available on the desktop test shells.\")\nDEF_SWITCH(PrefetchedDefaultFontManager, \"prefetched-default-font-manager\",\n           \"Indicates whether the embedding started a prefetch of the \"\n           \"default font manager before creating the engine.\")\nDEF_SWITCH(VerboseLogging, \"verbose-logging\",\n           \"By default, only errors are logged. This flag enabled logging at \"\n           \"all severity levels. This is NOT a per shell flag and affect log \"\n           \"levels for all shells in the process.\")\n\nDEF_SWITCH(ResourceCacheMaxBytesThreshold, \"resource-cache-max-bytes-threshold\",\n           \"The max bytes threshold of resource cache, or 0 for unlimited.\")\nDEF_SWITCH(EnableSkParagraph, \"enable-skparagraph\",\n           \"Selects the SkParagraph implementation of the text layout engine.\")\nDEF_SWITCH(\n    MsaaSamples, \"msaa-samples\",\n    \"The minimum number of samples to require for multisampled anti-aliasing.  \"\n    \"Setting this value to 0 or 1 disables MSAA. If it is not 0 or 1, it must \"\n    \"be one of 2, 4, 8, or 16. However, if the GPU does not support the \"\n    \"requested sampling value, MSAA will be disabled.\")\n\nDEF_SWITCH(EnableDefaultFocusRing, \"enable-default-focus-ring\",\n           \"Whether to use default focus ring. Not used by default.\")\n\nDEF_SWITCH(ImageTextureMaxMemoryLimit, \"image-texture-max-memory-limit\",\n           \"The max limit of image texture resource cached in clay.\")\n\nDEF_SWITCH(LowEndImageTextureMaxMemoryLimit,\n           \"low-end-image-texture-max-memory-limit\",\n           \"The max limit of image texture resource cached in clay for \"\n           \"low end terminal.\")\n\nDEF_SWITCH(EnableRendermodeSync, \"enable-rendermode-sync\",\n           \"Enable rendering in Android Render Thread.\")\n\nDEF_SWITCH(EnableStencilBuffer, \"enable-stencil-buffer\",\n           \"Enable stencil buffer in raster thread\")\n\nDEF_SWITCH(GpuResourceCacheMultiplier, \"gpu-cache-multiplier\",\n           \"The multiplier for the GPU resource cache size. The actual cache \"\n           \"size is mul * 4 * w * h\")\n\nDEF_SWITCHES_END\n\nconst std::string_view FlagForSwitch(Switch swtch);\n\nSettings SettingsFromCommandLine(const fml::CommandLine& command_line);\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_SWITCHES_H_\n"
  },
  {
    "path": "clay/shell/common/variable_refresh_rate_display.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/variable_refresh_rate_display.h\"\n\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n\nstatic double GetInitialRefreshRate(\n    const std::weak_ptr<clay::VariableRefreshRateReporter>&\n        refresh_rate_reporter) {\n  if (auto reporter = refresh_rate_reporter.lock()) {\n    return reporter->GetRefreshRate();\n  }\n  return 0;\n}\n\nnamespace clay {\n\nVariableRefreshRateDisplay::VariableRefreshRateDisplay(\n    DisplayId display_id,\n    const std::weak_ptr<VariableRefreshRateReporter>& refresh_rate_reporter)\n    : Display(display_id, GetInitialRefreshRate(refresh_rate_reporter)),\n      refresh_rate_reporter_(refresh_rate_reporter) {}\n\nVariableRefreshRateDisplay::VariableRefreshRateDisplay(\n    const std::weak_ptr<VariableRefreshRateReporter>& refresh_rate_reporter)\n    : Display(GetInitialRefreshRate(refresh_rate_reporter)),\n      refresh_rate_reporter_(refresh_rate_reporter) {}\n\ndouble VariableRefreshRateDisplay::GetRefreshRate() const {\n  return GetInitialRefreshRate(refresh_rate_reporter_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/variable_refresh_rate_display.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_VARIABLE_REFRESH_RATE_DISPLAY_H_\n#define CLAY_SHELL_COMMON_VARIABLE_REFRESH_RATE_DISPLAY_H_\n\n#include <memory>\n#include <optional>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/shell/common/display.h\"\n#include \"clay/shell/common/variable_refresh_rate_reporter.h\"\n\nnamespace clay {\n\n/// A Display where the refresh rate can change over time.\nclass VariableRefreshRateDisplay : public Display {\n public:\n  explicit VariableRefreshRateDisplay(\n      DisplayId display_id,\n      const std::weak_ptr<VariableRefreshRateReporter>& refresh_rate_reporter);\n  explicit VariableRefreshRateDisplay(\n      const std::weak_ptr<VariableRefreshRateReporter>& refresh_rate_reporter);\n  ~VariableRefreshRateDisplay() = default;\n\n  // |Display|\n  double GetRefreshRate() const override;\n\n private:\n  const std::weak_ptr<VariableRefreshRateReporter> refresh_rate_reporter_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(VariableRefreshRateDisplay);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_VARIABLE_REFRESH_RATE_DISPLAY_H_\n"
  },
  {
    "path": "clay/shell/common/variable_refresh_rate_reporter.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_VARIABLE_REFRESH_RATE_REPORTER_H_\n#define CLAY_SHELL_COMMON_VARIABLE_REFRESH_RATE_REPORTER_H_\n\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <unordered_map>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace clay {\n\n/// Abstract class that represents a platform specific mechanism to report\n/// current refresh rates.\nclass VariableRefreshRateReporter {\n public:\n  VariableRefreshRateReporter() = default;\n\n  virtual double GetRefreshRate() const = 0;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(VariableRefreshRateReporter);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_VARIABLE_REFRESH_RATE_REPORTER_H_\n"
  },
  {
    "path": "clay/shell/common/vsync_waiter.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/vsync_waiter.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"base/include/fml/task_queue_id.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nstatic constexpr const char* kVsyncTraceName = \"VsyncProcessCallback\";\n\nVsyncWaiter::VsyncWaiter(fml::RefPtr<fml::TaskRunner> task_runner)\n    : task_runner_(std::move(task_runner)) {}\n\nVsyncWaiter::~VsyncWaiter() = default;\n\n// Public method invoked by the animator.\nvoid VsyncWaiter::AsyncWaitForVsync(const Callback& callback) {\n  if (!callback) {\n    return;\n  }\n\n  TRACE_EVENT(\"clay\", \"AsyncWaitForVsync\");\n\n  {\n    std::scoped_lock lock(callback_mutex_);\n    if (callback_) {\n      // The animator may request a frame more than once within a frame\n      // interval. Multiple calls to request frame must result in a single\n      // callback per frame interval.\n      TRACE_EVENT_INSTANT(\"clay\", \"MultipleCallsToVsyncInFrameInterval\");\n      return;\n    }\n    callback_ = callback;\n    if (!secondary_callbacks_.empty()) {\n      // Return directly as `AwaitVSync` is already called by\n      // `ScheduleSecondaryCallback`.\n      return;\n    }\n  }\n  if (await_vsync_callback_) {\n    // Invoke the customized vsync callback.\n    await_vsync_callback_(shared_from_this(), task_runner_);\n  } else {\n    AwaitVSync();\n  }\n}\n\nvoid VsyncWaiter::ScheduleSecondaryCallback(uintptr_t id,\n                                            const fml::closure& callback) {\n  FML_DCHECK(task_runner_->RunsTasksOnCurrentThread());\n\n  if (!callback) {\n    return;\n  }\n\n  TRACE_EVENT(\"clay\", \"ScheduleSecondaryCallback\");\n\n  {\n    std::scoped_lock lock(callback_mutex_);\n    bool secondary_callbacks_originally_empty = secondary_callbacks_.empty();\n    auto [_, inserted] =  // NOLINT\n        secondary_callbacks_.emplace(id, callback);\n    if (!inserted) {\n      // Multiple schedules must result in a single callback per frame interval.\n      TRACE_EVENT_INSTANT(\"clay\",\n                          \"MultipleCallsToSecondaryVsyncInFrameInterval\");\n      return;\n    }\n    if (callback_) {\n      // Return directly as `AwaitVSync` is already called by\n      // `AsyncWaitForVsync`.\n      return;\n    }\n    if (!secondary_callbacks_originally_empty) {\n      // Return directly as `AwaitVSync` is already called by\n      // `ScheduleSecondaryCallback`.\n      return;\n    }\n  }\n  AwaitVSyncForSecondaryCallback();\n}\n\nvoid VsyncWaiter::FireCallback(fml::TimePoint frame_start_time,\n                               fml::TimePoint frame_target_time) {\n  // We assume that the FireCallback is triggered on the task_runner thread.\n  // Because on most platforms, vsync callbacks are triggered on the registered\n  // thread.\n  // If it does not behave as expected on some platforms, manual dispatch should\n  // be used.\n  FML_DCHECK(task_runner_->RunsTasksOnCurrentThread());\n\n  Callback callback;\n  callback.swap(callback_);\n  std::vector<lynx::base::closure> secondary_callbacks;\n  secondary_callbacks.reserve(secondary_callbacks_.size());\n\n  for (auto& pair : secondary_callbacks_) {\n    secondary_callbacks.push_back(std::move(pair.second));\n  }\n  secondary_callbacks_.clear();\n\n  if (!callback && secondary_callbacks.empty()) {\n    // This means that the vsync waiter implementation fired a callback for a\n    // request we did not make. This is a paranoid check but we still want to\n    // make sure we catch misbehaving vsync implementations.\n\n    // Also may because of raster animation. Just ignore it.\n    // TRACE_EVENT_INSTANT(\"clay\", \"MismatchedFrameCallback\");\n    return;\n  }\n  inside_vsync_ = true;\n\n  if (callback) {\n    TRACE_EVENT(\"clay\", kVsyncTraceName, \"StartTime\",\n                frame_start_time.ToEpochDelta().ToMilliseconds(), \"TargetTime\",\n                frame_target_time.ToEpochDelta().ToMilliseconds());\n    std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder =\n        std::make_unique<FrameTimingsRecorder>();\n    frame_timings_recorder->RecordVsync(frame_start_time, frame_target_time);\n    callback(std::move(frame_timings_recorder));\n  }\n\n  for (auto& secondary_callback : secondary_callbacks) {\n    task_runner_->PostTask(std::move(secondary_callback));\n  }\n\n  inside_vsync_ = false;\n}\n\nvoid VsyncWaiter::Hook(AWaitVSyncCallback callback) {\n  await_vsync_callback_ = callback;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/vsync_waiter.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_VSYNC_WAITER_H_\n#define CLAY_SHELL_COMMON_VSYNC_WAITER_H_\n\n#include <atomic>\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <unordered_map>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/shell/common/variable_refresh_rate_reporter.h\"\nnamespace clay {\n\n/// Abstract Base Class that represents a platform specific mechanism for\n/// getting callbacks when a vsync event happens.\nclass VsyncWaiter : public VariableRefreshRateReporter,\n                    public std::enable_shared_from_this<VsyncWaiter> {\n public:\n  using Callback = std::function<void(std::unique_ptr<FrameTimingsRecorder>)>;\n  using AWaitVSyncCallback = std::function<void(std::shared_ptr<VsyncWaiter>,\n                                                fml::RefPtr<fml::TaskRunner>)>;\n\n  virtual ~VsyncWaiter();\n\n  void AsyncWaitForVsync(const Callback& callback);\n\n  /// Add a secondary callback for key |id| for the next vsync.\n  ///\n  /// See also |PointerDataDispatcher::ScheduleSecondaryVsyncCallback| and\n  /// |Animator::ScheduleMaybeClearTraceFlowIds|.\n  void ScheduleSecondaryCallback(uintptr_t id, const fml::closure& callback);\n\n  bool HasCallback() { return !!callback_; }\n  void ResetCallback() { callback_ = nullptr; }\n\n  bool InsideVsync() const { return inside_vsync_; }\n\n  // To be removed, only use the same one in VsyncWaiterService.\n  double GetRefreshRate() const override { return 60.0; };\n\n  // Hook the AwaitVSync, it will invoke the callback when AsyncWaitForVsync is\n  // called, so we can do some customized vsync request.\n  void Hook(AWaitVSyncCallback callback);\n\n protected:\n  // On some backends, the |FireCallback| needs to be made from a static C\n  // method.\n  friend class VsyncWaiterHarmony;\n  friend class VsyncWaiterAndroid;\n  friend class VsyncWaiterEmbedder;\n\n  const fml::RefPtr<fml::TaskRunner> task_runner_;\n\n  explicit VsyncWaiter(fml::RefPtr<fml::TaskRunner> task_runner);\n\n  // There are two distinct situations where VsyncWaiter wishes to awaken at\n  // the next vsync. Although the functionality can be the same, the intent is\n  // different, therefore it makes sense to have a method for each intent.\n\n  // The intent of AwaitVSync() is that the Animator wishes to produce a frame.\n  // The underlying implementation can choose to be aware of this intent when\n  // it comes to implementing backpressure and other scheduling invariants.\n  //\n  // Implementations are meant to override this method and arm their vsync\n  // latches when in response to this invocation. On vsync, they are meant to\n  // invoke the |FireCallback| method once (and only once) with the appropriate\n  // arguments. This method should not block the current thread.\n  virtual void AwaitVSync() = 0;\n\n  // The intent of AwaitVSyncForSecondaryCallback() is simply to wake up at the\n  // next vsync.\n  //\n  // Because there is no association with frame scheduling, underlying\n  // implementations do not need to worry about maintaining invariants or\n  // backpressure. The default implementation is to simply follow the same logic\n  // as AwaitVSync().\n  virtual void AwaitVSyncForSecondaryCallback() { AwaitVSync(); }\n\n  // Schedules the callback on the UI task runner. Needs to be invoked as close\n  // to the `frame_start_time` as possible.\n  void FireCallback(fml::TimePoint frame_start_time,\n                    fml::TimePoint frame_target_time);\n\n private:\n  std::mutex callback_mutex_;\n  Callback callback_;\n  std::unordered_map<uintptr_t, fml::closure> secondary_callbacks_;\n  bool inside_vsync_ = false;\n  AWaitVSyncCallback await_vsync_callback_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(VsyncWaiter);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_VSYNC_WAITER_H_\n"
  },
  {
    "path": "clay/shell/common/vsync_waiter_fallback.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/vsync_waiter_fallback.h\"\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#ifdef _WIN32\n#include <Windows.h>\n#endif\n\n#include \"base/include/fml/message_loop.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/common/services/vsync_waiter_service.h\"\n\nnamespace clay {\nnamespace {\n\nstatic fml::TimePoint SnapToNextTick(fml::TimePoint value,\n                                     fml::TimePoint tick_phase,\n                                     fml::TimeDelta tick_interval) {\n  fml::TimeDelta offset = (tick_phase - value) % tick_interval;\n  if (offset != fml::TimeDelta::Zero()) {\n    offset = offset + tick_interval;\n  }\n  return value + offset;\n}\n\n}  // namespace\n\nclass FallbackVsyncWaiterService : public VsyncWaiterService {\n  std::unique_ptr<VsyncWaiter> CreateVsyncWaiter(\n      fml::RefPtr<fml::TaskRunner> task_runner) const override {\n    return std::make_unique<VsyncWaiterFallback>(std::move(task_runner));\n  }\n};\n\nstd::shared_ptr<VsyncWaiterService> CreateFallbackVsyncWaiterService() {\n  return std::make_shared<FallbackVsyncWaiterService>();\n}\n\n#ifndef __APPLE__\nstd::shared_ptr<VsyncWaiterService> VsyncWaiterService::Create() {\n  FML_DLOG(WARNING)\n      << \"This platform does not provide a Vsync waiter implementation. A \"\n         \"simple timer based fallback is being used.\";\n  return CreateFallbackVsyncWaiterService();\n}\n#endif\n\nVsyncWaiterFallback::VsyncWaiterFallback(\n    fml::RefPtr<fml::TaskRunner> task_runner)\n    : VsyncWaiter(std::move(task_runner)), phase_(fml::TimePoint::Now()) {}\n\nVsyncWaiterFallback::~VsyncWaiterFallback() = default;\n\n// |VsyncWaiter|\nvoid VsyncWaiterFallback::AwaitVSync() {\n  static fml::TimeDelta kSingleFrameInterval =\n      fml::TimeDelta::FromSecondsF(1.0 / GetRefreshRate());\n  auto frame_start_time =\n      SnapToNextTick(fml::TimePoint::Now(), phase_, kSingleFrameInterval);\n  auto frame_target_time = frame_start_time + kSingleFrameInterval;\n\n  std::weak_ptr<VsyncWaiterFallback> weak_this =\n      std::static_pointer_cast<VsyncWaiterFallback>(shared_from_this());\n\n  task_runner_->PostTaskForTime(\n      [frame_start_time, frame_target_time, weak_this]() {\n        if (auto vsync_waiter = weak_this.lock()) {\n          vsync_waiter->FireCallback(frame_start_time, frame_target_time);\n        }\n      },\n      frame_start_time);\n}\n\ndouble VsyncWaiterFallback::GetRefreshRate() const {\n  int rate = VsyncWaiter::GetRefreshRate();\n#ifdef _WIN32\n  HDC hdc = ::GetDC(NULL);\n  if (hdc != NULL) {\n    rate = std::max(rate, ::GetDeviceCaps(hdc, VREFRESH));\n    ::ReleaseDC(NULL, hdc);\n  }\n#endif\n  return rate;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/common/vsync_waiter_fallback.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_VSYNC_WAITER_FALLBACK_H_\n#define CLAY_SHELL_COMMON_VSYNC_WAITER_FALLBACK_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/shell/common/vsync_waiter.h\"\n\nnamespace clay {\n\n/// A |VsyncWaiter| that will fire at 60 fps irrespective of the vsync.\nclass VsyncWaiterFallback final : public VsyncWaiter {\n public:\n  explicit VsyncWaiterFallback(fml::RefPtr<fml::TaskRunner> task_runner);\n\n  ~VsyncWaiterFallback() override;\n  double GetRefreshRate() const override;\n\n private:\n  fml::TimePoint phase_;\n\n  // |VsyncWaiter|\n  void AwaitVSync() override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(VsyncWaiterFallback);\n};\n\nstd::shared_ptr<class VsyncWaiterService> CreateFallbackVsyncWaiterService();\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_VSYNC_WAITER_FALLBACK_H_\n"
  },
  {
    "path": "clay/shell/common/vsync_waiter_macos.h",
    "content": "// Copyright 2025 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_COMMON_VSYNC_WAITER_MACOS_H_\n#define CLAY_SHELL_COMMON_VSYNC_WAITER_MACOS_H_\n\n#import <Foundation/Foundation.h>\n#import <QuartzCore/CADisplayLink.h>\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/shell/common/services/vsync_waiter_service.h\"\n#include \"clay/shell/common/vsync_waiter.h\"\n\nAPI_AVAILABLE(macos(14.0))\n@interface DisplayLinkManager : NSObject\n@property(nonatomic, strong) CADisplayLink* displayLink;\n@end\n\nnamespace clay {\n\nclass API_AVAILABLE(macos(14.0)) VsyncWaiterMacOS final : public VsyncWaiter {\n public:\n  explicit VsyncWaiterMacOS(fml::RefPtr<fml::TaskRunner> task_runner);\n  ~VsyncWaiterMacOS() override;\n\n private:\n  // |VsyncWaiter|\n  void AwaitVSync() override;\n\n  DisplayLinkManager* manager_ = nil;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(VsyncWaiterMacOS);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_COMMON_VSYNC_WAITER_MACOS_H_\n"
  },
  {
    "path": "clay/shell/common/vsync_waiter_macos.mm",
    "content": "// Copyright 2025 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/common/vsync_waiter_macos.h\"\n\n#include <functional>\n\n#import <AppKit/NSScreen.h>\n\n#include \"clay/shell/common/vsync_waiter_fallback.h\"\n\n@implementation DisplayLinkManager {\n @public\n  std::function<void(fml::TimePoint, fml::TimePoint)> callback_;\n}\n\n- (instancetype)initWith:(fml::RefPtr<fml::TaskRunner>)task_runner {\n  self = [super init];\n  if (self) {\n    NSScreen *screen = [[NSScreen screens] firstObject];\n    _displayLink = [screen displayLinkWithTarget:self selector:@selector(onDisplayLink:)];\n    _displayLink.paused = YES;\n    task_runner->PostTask([self]() {\n      [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];\n    });\n  }\n  return self;\n}\n\n- (void)destroy {\n  [_displayLink invalidate];\n}\n\n- (void)onDisplayLink:(CADisplayLink *)link {\n  _displayLink.paused = YES;\n  if (!callback_) {\n    return;\n  }\n\n  CFTimeInterval delay = CACurrentMediaTime() - link.timestamp;\n  fml::TimePoint frame_start_time = fml::TimePoint::Now() - fml::TimeDelta::FromSecondsF(delay);\n  CFTimeInterval duration = link.targetTimestamp - link.timestamp;\n  fml::TimePoint frame_target_time = frame_start_time + fml::TimeDelta::FromSecondsF(duration);\n\n  callback_(frame_start_time, frame_target_time);\n}\n\n@end\n\nnamespace clay {\n\nclass API_AVAILABLE(macos(14.0)) MacOSVsyncWaiterService : public VsyncWaiterService {\n  std::unique_ptr<VsyncWaiter> CreateVsyncWaiter(\n      fml::RefPtr<fml::TaskRunner> task_runner) const override {\n    return std::make_unique<VsyncWaiterMacOS>(std::move(task_runner));\n  }\n\n  double GetRefreshRate() const override { return [NSScreen mainScreen].maximumFramesPerSecond; }\n};\n\nstd::shared_ptr<VsyncWaiterService> VsyncWaiterService::Create() {\n  if (@available(macOS 14.0, *)) {\n    return std::make_shared<MacOSVsyncWaiterService>();\n  }\n  return CreateFallbackVsyncWaiterService();\n}\n\nVsyncWaiterMacOS::VsyncWaiterMacOS(fml::RefPtr<fml::TaskRunner> task_runner)\n    : VsyncWaiter(std::move(task_runner)) {\n  manager_ = [[DisplayLinkManager alloc] initWith:task_runner_];\n}\n\nVsyncWaiterMacOS::~VsyncWaiterMacOS() {\n  if (manager_) {\n    [manager_ destroy];\n    manager_ = nil;\n  }\n}\n\nvoid VsyncWaiterMacOS::AwaitVSync() {\n  if (manager_) {\n    if (!manager_->callback_) {\n      std::weak_ptr<VsyncWaiterMacOS> weak_this =\n          std::static_pointer_cast<VsyncWaiterMacOS>(shared_from_this());\n      manager_->callback_ = [weak_this](fml::TimePoint frame_start_time,\n                                        fml::TimePoint frame_target_time) {\n        if (auto vsync_waiter = weak_this.lock()) {\n          vsync_waiter->FireCallback(frame_start_time, frame_target_time);\n        }\n      };\n    }\n    manager_.displayLink.paused = NO;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/config.gni",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../common/config.gni\")\n\ndeclare_args() {\n  shell_enable_gl = !is_fuchsia && !is_mac\n\n  # The logic for enabling Metal is in tools/gn.\n  shell_enable_metal = false\n\n  shell_enable_software = true\n\n  stripped_symbols = true\n}\n\ndeclare_args() {\n  test_enable_gl = shell_enable_gl\n  test_enable_metal = shell_enable_metal\n  test_enable_software = shell_enable_software\n}\n\n# TODO(Jinsong): Temporarily Headless only supports OpenGL, more rendering\n# backends will be supported soon.\nif (is_headless) {\n  shell_enable_gl = true\n}\n"
  },
  {
    "path": "clay/shell/gpu/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\nimport(\"../../shell/config.gni\")\n\ngpu_common_deps = [\n  \"../../common\",\n  \"../../common/graphics\",\n  \"../../flow\",\n  \"../../fml:fml\",\n  \"../../gfx\",\n  \"../../shell/common\",\n  \"../../ui:ui\",\n]\n\nsource_set(\"gpu_surface_software\") {\n  sources = [\n    \"gpu_surface_software.cc\",\n    \"gpu_surface_software.h\",\n    \"gpu_surface_software_delegate.cc\",\n    \"gpu_surface_software_delegate.h\",\n  ]\n\n  public_deps = gpu_common_deps\n  public_configs = [ \"../../:config\" ]\n}\n\nsource_set(\"gpu_surface_gl\") {\n  sources = [\n    \"gpu_surface_gl_delegate.cc\",\n    \"gpu_surface_gl_delegate.h\",\n  ]\n\n  if (!enable_skity) {\n    sources += [\n      \"gpu_surface_gl_skia.cc\",\n      \"gpu_surface_gl_skia.h\",\n      \"trace_gl_fuctions.cc\",\n      \"trace_gl_fuctions.h\",\n    ]\n  }\n\n  public_deps = gpu_common_deps\n\n  public_configs = [ \"../../:config\" ]\n\n  if (enable_skity) {\n    sources += [\n      \"gpu_surface_gl_skity.cc\",\n      \"gpu_surface_gl_skity.h\",\n    ]\n    if (is_win) {\n      deps = [ \"//third_party/angle:includes\" ]\n    }\n  }\n}\n\nsource_set(\"gpu_surface_metal\") {\n  sources = [\n    \"gpu_surface_metal_delegate.cc\",\n    \"gpu_surface_metal_delegate.h\",\n  ]\n\n  if (!enable_skity) {\n    sources += [\n      \"gpu_surface_metal_skia.h\",\n      \"gpu_surface_metal_skia.mm\",\n    ]\n  } else {\n    sources += [\n      \"gpu_surface_metal_skity.h\",\n      \"gpu_surface_metal_skity.mm\",\n    ]\n  }\n\n  public_deps = gpu_common_deps\n\n  public_configs = [ \"../../:config\" ]\n}\n"
  },
  {
    "path": "clay/shell/gpu/gpu.gni",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../shell/config.gni\")\n\nsoftware_deps = rebase_path(\":gpu_surface_software\")\ngl_deps = rebase_path(\":gpu_surface_gl\")\nmetal_deps = rebase_path(\":gpu_surface_metal\")\n\ntemplate(\"shell_gpu_configuration\") {\n  assert(defined(invoker.enable_software),\n         \"Caller must declare if the Software backend must be enabled.\")\n  assert(defined(invoker.enable_gl),\n         \"Caller must declare if the Open GL backend must be enabled.\")\n  assert(defined(invoker.enable_metal),\n         \"Caller must declare if the Metal backend must be enabled.\")\n\n  group(target_name) {\n    public_deps = []\n\n    if (invoker.enable_software) {\n      public_deps += [ software_deps ]\n    }\n\n    if (invoker.enable_gl) {\n      public_deps += [ gl_deps ]\n    }\n\n    if (invoker.enable_metal) {\n      public_deps += [ metal_deps ]\n    }\n  }\n\n  config(\"${target_name}_config\") {\n    defines = []\n\n    if (invoker.enable_software) {\n      defines += [ \"SHELL_ENABLE_SOFTWARE\" ]\n    }\n    if (invoker.enable_gl) {\n      defines += [ \"SHELL_ENABLE_GL\" ]\n    }\n    if (invoker.enable_metal) {\n      defines += [ \"SHELL_ENABLE_METAL\" ]\n    }\n  }\n}\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_gl_delegate.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/gpu/gpu_surface_gl_delegate.h\"\n\n#include <cstring>\n\n#ifndef ENABLE_SKITY\n#include \"clay/shell/gpu/trace_gl_fuctions.h\"\n#include \"third_party/skia/include/gpu/gl/GrGLAssembleInterface.h\"\n#endif\n\nnamespace clay {\n\nGPUSurfaceGLDelegate::~GPUSurfaceGLDelegate() = default;\n\nbool GPUSurfaceGLDelegate::GLContextFBOResetAfterPresent() const {\n  return false;\n}\n\nSurfaceFrame::FramebufferInfo GPUSurfaceGLDelegate::GLContextFramebufferInfo()\n    const {\n  SurfaceFrame::FramebufferInfo res;\n  res.supports_readback = true;\n  return res;\n}\n\nskity::Matrix GPUSurfaceGLDelegate::GLContextSurfaceTransformation() const {\n  return skity::Matrix();\n}\n\nGPUSurfaceGLDelegate::GLProcResolver GPUSurfaceGLDelegate::GetGLProcResolver()\n    const {\n  return nullptr;\n}\n\n#ifndef ENABLE_SKITY\nstatic bool IsProcResolverOpenGLES(\n    const GPUSurfaceGLDelegate::GLProcResolver& proc_resolver) {\n  // Version string prefix that identifies an OpenGL ES implementation.\n#define GPU_GL_VERSION 0x1F02\n  constexpr char kGLESVersionPrefix[] = \"OpenGL ES\";\n\n#ifdef WIN32\n  using GLGetStringProc = const char*(__stdcall*)(uint32_t);  // NOLINT\n#else\n  using GLGetStringProc = const char* (*)(uint32_t);\n#endif  // WIN32\n\n  GLGetStringProc gl_get_string =\n      reinterpret_cast<GLGetStringProc>(proc_resolver(\"glGetString\"));\n\n  FML_CHECK(gl_get_string)\n      << \"The GL proc resolver could not resolve glGetString\";\n\n  const char* gl_version_string = gl_get_string(GPU_GL_VERSION);\n\n  FML_CHECK(gl_version_string)\n      << \"The GL proc resolver's glGetString(GL_VERSION) failed\";\n\n  return strncmp(gl_version_string, kGLESVersionPrefix,\n                 strlen(kGLESVersionPrefix)) == 0;\n}\n\nsk_sp<const GrGLInterface> GPUSurfaceGLDelegate::CreateGLInterface(\n    const GPUSurfaceGLDelegate::GLProcResolver& proc_resolver) {\n  if (proc_resolver == nullptr) {\n    static std::once_flag flag;\n    static sk_sp<const GrGLInterface> interface;\n    static const auto create_func = []() {\n      // If there is no custom proc resolver, ask Skia to guess the native\n      // interface. This often leads to interesting results on most platforms.\n      interface = GrGLMakeNativeInterface();\n#if ENABLE_GL_FUNCTION_TRACE\n      GrGLInterface* interface_ptr =\n          const_cast<GrGLInterface*>(interface.get());\n      TraceGlFuctions::ReplaceFunctions(interface_ptr);\n#endif  // ENABLE_GL_FUNCTION_TRACE\n    };\n    std::call_once(flag, create_func);\n    return interface;\n  }\n\n  struct ProcResolverContext {\n    GPUSurfaceGLDelegate::GLProcResolver resolver;\n  };\n\n  ProcResolverContext context = {proc_resolver};\n\n  GrGLGetProc gl_get_proc = [](void* context,\n                               const char gl_proc_name[]) -> GrGLFuncPtr {\n    auto proc_resolver_context =\n        reinterpret_cast<ProcResolverContext*>(context);\n    return reinterpret_cast<GrGLFuncPtr>(\n        proc_resolver_context->resolver(gl_proc_name));\n  };\n\n  // glGetString indicates an OpenGL ES interface.\n  if (IsProcResolverOpenGLES(proc_resolver)) {\n    sk_sp<const GrGLInterface> interface =\n        GrGLMakeAssembledGLESInterface(&context, gl_get_proc);\n#if ENABLE_GL_FUNCTION_TRACE\n    GrGLInterface* interface_ptr = const_cast<GrGLInterface*>(interface.get());\n    TraceGlFuctions::ReplaceFunctions(interface_ptr);\n#endif  // ENABLE_GL_FUNCTION_TRACE\n    return interface;\n  }\n\n  // Fallback to OpenGL.\n  if (auto interface = GrGLMakeAssembledGLInterface(&context, gl_get_proc)) {\n    return interface;\n  }\n  FML_LOG(ERROR) << \"Could not create a valid GL interface.\";\n  return nullptr;\n}\n\nsk_sp<const GrGLInterface> GPUSurfaceGLDelegate::GetGLInterface() const {\n  return CreateGLInterface(GetGLProcResolver());\n}\n\nsk_sp<const GrGLInterface>\nGPUSurfaceGLDelegate::GetDefaultPlatformGLInterface() {\n  return CreateGLInterface(nullptr);\n}\n#endif  // ENABLE_SKITY\n\nbool GPUSurfaceGLDelegate::AllowsDrawingWhenGpuDisabled() const { return true; }\n\nint GPUSurfaceGLDelegate::GetSampleCount() const {\n#if defined(ENABLE_SKITY) && defined(OS_WIN)\n  return 4;\n#else\n  return 1;\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_gl_delegate.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_GPU_GPU_SURFACE_GL_DELEGATE_H_\n#define CLAY_SHELL_GPU_GPU_SURFACE_GL_DELEGATE_H_\n\n#include <memory>\n#include <optional>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/common/graphics/gl_context_switch.h\"\n#include \"clay/flow/embedded_views.h\"\n\nnamespace clay {\n\n// A structure to represent the frame information which is passed to the\n// embedder when requesting a frame buffer object.\nstruct GLFrameInfo {\n  uint32_t width;\n  uint32_t height;\n};\n\n// A structure to represent the frame buffer information which is returned to\n// the rendering backend after requesting a frame buffer object.\nstruct GLFBOInfo {\n  // The frame buffer's ID.\n  int64_t fbo_id;\n  // The frame buffer's existing damage (i.e. damage since it was last used).\n  const std::optional<skity::Rect> existing_damage;\n};\n\n// Information passed during presentation of a frame.\nstruct GLPresentInfo {\n  uint32_t fbo_id;\n\n  // The frame damage is a hint to compositor telling it which parts of front\n  // buffer need to be updated.\n  const std::optional<skity::Rect>& frame_damage;\n\n  // Time at which this frame is scheduled to be presented. This is a hint\n  // that can be passed to the platform to drop queued frames.\n  std::optional<fml::TimePoint> presentation_time = std::nullopt;\n\n  // The buffer damage refers to the region that needs to be set as damaged\n  // within the frame buffer.\n  const std::optional<skity::Rect>& buffer_damage;\n};\n\nclass GPUSurfaceGLDelegate {\n public:\n  ~GPUSurfaceGLDelegate();\n\n  // Called to make the main GL context current on the current thread.\n  virtual std::unique_ptr<GLContextResult> GLContextMakeCurrent() = 0;\n\n  // Called to clear the current GL context on the thread. This may be called on\n  // either the GPU or IO threads.\n  virtual bool GLContextClearCurrent() = 0;\n\n  // Inform the GL Context that there's going to be no writing beyond\n  // the specified region\n  virtual void GLContextSetDamageRegion(\n      const std::optional<skity::Rect>& region) {}\n\n  // Called to present the main GL surface. This is only called for the main GL\n  // context and not any of the contexts dedicated for IO.\n  virtual bool GLContextPresent(const GLPresentInfo& present_info) = 0;\n\n  // The information about the main window bound framebuffer. ID is Typically\n  // FBO0.\n  virtual GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const = 0;\n\n  // The rendering subsystem assumes that the ID of the main window bound\n  // framebuffer remains constant throughout. If this assumption in incorrect,\n  // embedders are required to return true from this method. In such cases,\n  // GLContextFBO(frame_info) will be called again to acquire the new FBO ID for\n  // rendering subsequent frames.\n  virtual bool GLContextFBOResetAfterPresent() const;\n\n  // Returns framebuffer info for current back buffer\n  virtual SurfaceFrame::FramebufferInfo GLContextFramebufferInfo() const;\n\n  // A transformation applied to the onscreen surface before the canvas is\n  // flushed.\n  virtual skity::Matrix GLContextSurfaceTransformation() const;\n\n  using GLProcResolver =\n      std::function<void* /* proc name */ (const char* /* proc address */)>;\n  // Provide a custom GL proc resolver. If no such resolver is present, Skia\n  // will attempt to do GL proc address resolution on its own. Embedders that\n  // have specific opinions on GL API selection or need to add their own\n  // instrumentation to specific GL calls can specify custom GL functions\n  // here.\n  virtual GLProcResolver GetGLProcResolver() const;\n\n  // Whether to allow drawing to the surface when the GPU is disabled\n  virtual bool AllowsDrawingWhenGpuDisabled() const;\n\n  virtual int GetSampleCount() const;\n\n#ifndef ENABLE_SKITY\n  virtual sk_sp<const GrGLInterface> GetGLInterface() const;\n\n  // TODO(chinmaygarde): The presence of this method is to work around the fact\n  // that not all platforms can accept a custom GL proc table. Migrate all\n  // platforms to move GL proc resolution to the embedder and remove this\n  // method.\n  static sk_sp<const GrGLInterface> GetDefaultPlatformGLInterface();\n\n  static sk_sp<const GrGLInterface> CreateGLInterface(\n      const GPUSurfaceGLDelegate::GLProcResolver& proc_resolver);\n#endif  // ENABLE_SKITY\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_GPU_GPU_SURFACE_GL_DELEGATE_H_\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_gl_skia.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/gpu/gpu_surface_gl_skia.h\"\n\n#include <algorithm>\n#include <cstddef>\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/common/settings.h\"\n#include \"clay/fml/base32.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/size.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/shell/common/context_options.h\"\n#include \"clay/shell/gpu/gpu_surface_gl_delegate.h\"\n#include \"third_party/skia/include/core/SkColorFilter.h\"\n#include \"third_party/skia/include/core/SkColorSpace.h\"\n#ifdef OS_ANDROID\n#include \"third_party/skia/include/core/SkGraphics.h\"\n#endif\n#include \"third_party/skia/include/core/SkSurface.h\"\n#include \"third_party/skia/include/gpu/GrBackendSurface.h\"\n#include \"third_party/skia/include/gpu/GrContextOptions.h\"\n#include \"third_party/skia/include/gpu/gl/GrGLInterface.h\"\n// These are common defines present on all OpenGL headers. However, we don't\n// want to perform GL header resolution on each platform we support. So just\n// define these upfront. It is unlikely we will need more. But, if we do, we can\n// add the same here.\n#define GPU_GL_RGBA8 0x8058\n#define GPU_GL_RGBA4 0x8056\n#define GPU_GL_RGB565 0x8D62\n\nnamespace clay {\n\n// Default maximum number of bytes of GPU memory of budgeted resources in the\n// cache.\n// The shell will dynamically increase or decrease this cache based on the\n// viewport size, unless a user has specifically requested a size on the Skia\n// system channel.\nstatic const size_t kGrCacheMaxByteSize = 24 * (1 << 20);\n\n#ifdef OS_ANDROID\nstatic inline size_t NextPowerOfTwo(size_t n) {\n  // First, check if n is already a power of 2.\n  if (n != 0 && (n & (n - 1)) == 0) {\n    return n;\n  }\n\n  int power = 0;\n  while ((1 << power) < n) {\n    ++power;\n  }\n  return (1 << power);\n}\n#endif\n\nsk_sp<GrDirectContext> GPUSurfaceGLSkia::MakeGLContext(\n    GPUSurfaceGLDelegate* delegate) {\n  auto context_switch = delegate->GLContextMakeCurrent();\n  if (!context_switch->GetResult()) {\n    FML_LOG(ERROR)\n        << \"Could not make the context current to set up the Gr context.\";\n    return nullptr;\n  }\n\n  const auto options =\n      MakeDefaultContextOptions(ContextType::kRender, GrBackendApi::kOpenGL);\n\n  auto context = GrDirectContext::MakeGL(delegate->GetGLInterface(), options);\n\n  if (!context) {\n    FML_LOG(ERROR) << \"Failed to set up Skia Gr context.\";\n    return nullptr;\n  }\n\n  context->setResourceCacheLimit(kGrCacheMaxByteSize);\n\n  PersistentCache::GetCacheForProcess()->PrecompileKnownSkSLs(context.get());\n\n  return context;\n}\n\nGPUSurfaceGLSkia::GPUSurfaceGLSkia(GPUSurfaceGLDelegate* delegate,\n                                   bool render_to_surface)\n    : GPUSurfaceGLSkia(MakeGLContext(delegate), delegate, render_to_surface) {\n  context_owner_ = true;\n}\n\nGPUSurfaceGLSkia::GPUSurfaceGLSkia(const sk_sp<GrDirectContext>& gr_context,\n                                   GPUSurfaceGLDelegate* delegate,\n                                   bool render_to_surface)\n    : delegate_(delegate),\n      context_(gr_context),\n      render_to_surface_(render_to_surface),\n      weak_factory_(this) {\n  auto context_switch = delegate_->GLContextMakeCurrent();\n  if (!context_switch->GetResult()) {\n    FML_LOG(ERROR)\n        << \"Could not make the context current to set up the Gr context.\";\n    return;\n  }\n\n  // Don't clear gl context because:\n  // 1. It'll dequeue buffer from SurfaceFlinger, which cause performance issue.\n  // 2. Image may be texturized before first frame rasterization, which needs\n  //    gl context.\n  // delegate_->GLContextClearCurrent();\n\n  valid_ = gr_context != nullptr;\n}\n\nGPUSurfaceGLSkia::~GPUSurfaceGLSkia() {\n  if (!valid_) {\n    return;\n  }\n  auto context_switch = delegate_->GLContextMakeCurrent();\n  if (!context_switch->GetResult()) {\n    FML_LOG(ERROR) << \"Could not make the context current to destroy the \"\n                      \"GrDirectContext resources.\";\n    return;\n  }\n\n  onscreen_surface_ = nullptr;\n  fbo_id_ = 0;\n  if (context_owner_) {\n    context_->releaseResourcesAndAbandonContext();\n  }\n  context_ = nullptr;\n\n  delegate_->GLContextClearCurrent();\n}\n\n// |Surface|\nbool GPUSurfaceGLSkia::IsValid() { return valid_; }\n\nstatic SkColorType FirstSupportedColorType(GrDirectContext* context,\n                                           GrGLenum* format) {\n#define RETURN_IF_RENDERABLE(x, y)                            \\\n  if (context && context->colorTypeSupportedAsSurface((x))) { \\\n    *format = (y);                                            \\\n    return (x);                                               \\\n  }\n  RETURN_IF_RENDERABLE(kRGBA_8888_SkColorType, GPU_GL_RGBA8);\n  RETURN_IF_RENDERABLE(kARGB_4444_SkColorType, GPU_GL_RGBA4);\n  RETURN_IF_RENDERABLE(kRGB_565_SkColorType, GPU_GL_RGB565);\n  return kUnknown_SkColorType;\n}\n\nstatic sk_sp<SkSurface> WrapOnscreenSurface(GrDirectContext* context,\n                                            const skity::Vec2& size,\n                                            intptr_t fbo, size_t sample_count) {\n  GrGLenum format = kUnknown_SkColorType;\n  const SkColorType color_type = FirstSupportedColorType(context, &format);\n\n  GrGLFramebufferInfo framebuffer_info = {};\n  framebuffer_info.fFBOID = static_cast<GrGLuint>(fbo);\n  framebuffer_info.fFormat = format;\n\n  GrBackendRenderTarget render_target(\n      size.x,                                         // width\n      size.y,                                         // height\n      sample_count,                                   // sample count\n      Settings::ShouldEnableStencilBuffer() ? 8 : 0,  // stencil bits\n      framebuffer_info                                // framebuffer info\n  );                                                  // NOLINT\n\n  sk_sp<SkColorSpace> colorspace = SkColorSpace::MakeSRGB();\n  uint32_t flags = Settings::ShouldUseSDFTForText()\n                       ? SkSurfaceProps::kUseDeviceIndependentFonts_Flag\n                       : 0;\n  SkSurfaceProps surface_props(flags, kUnknown_SkPixelGeometry);\n\n  return SkSurface::MakeFromBackendRenderTarget(\n      context,                                       // Gr context\n      render_target,                                 // render target\n      GrSurfaceOrigin::kBottomLeft_GrSurfaceOrigin,  // origin\n      color_type,                                    // color type\n      colorspace,                                    // colorspace\n      &surface_props                                 // surface properties\n  );                                                 // NOLINT\n}\n\nbool GPUSurfaceGLSkia::CreateOrUpdateSurfaces(const skity::Vec2& size) {\n  if (onscreen_surface_ != nullptr &&\n      size == skity::Vec2(onscreen_surface_->width(),\n                          onscreen_surface_->height())) {\n    // Surface size appears unchanged. So bail.\n    return true;\n  }\n\n  // We need to do some updates.\n  TRACE_EVENT(\"clay\", \"UpdateSurfacesSize\");\n\n  // Either way, we need to get rid of previous surface.\n  onscreen_surface_ = nullptr;\n  fbo_id_ = 0;\n\n  if (size.x == 0 && size.y == 0) {\n    FML_LOG(ERROR) << \"Cannot create surfaces of empty size.\";\n    return false;\n  }\n\n  sk_sp<SkSurface> onscreen_surface;\n\n  GLFrameInfo frame_info = {static_cast<uint32_t>(size.x),\n                            static_cast<uint32_t>(size.y)};\n\n  const GLFBOInfo fbo_info = delegate_->GLContextFBO(frame_info);\n  if (fbo_info.fbo_id < 0) {\n    // If the FBO ID is invalid, we cannot proceed.\n    FML_LOG(ERROR) << \"Invalid FBO id: \" << fbo_info.fbo_id;\n    return false;\n  }\n  onscreen_surface =\n      WrapOnscreenSurface(context_.get(),              // GL context\n                          size,                        // root surface size\n                          fbo_info.fbo_id,             // window FBO ID\n                          delegate_->GetSampleCount()  // sample count\n      );                                               // NOLINT\n\n  if (onscreen_surface == nullptr) {\n    // If the onscreen surface could not be wrapped. There is absolutely no\n    // point in moving forward.\n    FML_LOG(ERROR) << \"Could not wrap onscreen surface.\";\n    return false;\n  }\n\n  onscreen_surface_ = std::move(onscreen_surface);\n  fbo_id_ = fbo_info.fbo_id;\n  existing_damage_ = fbo_info.existing_damage;\n\n  return true;\n}\n\n// |Surface|\nskity::Matrix GPUSurfaceGLSkia::GetRootTransformation() const {\n  return delegate_->GLContextSurfaceTransformation();\n}\n\n// |Surface|\nstd::unique_ptr<SurfaceFrame> GPUSurfaceGLSkia::AcquireFrame(\n    const skity::Vec2& size) {\n  if (delegate_ == nullptr) {\n    return nullptr;\n  }\n  auto context_switch = delegate_->GLContextMakeCurrent();\n  if (!context_switch->GetResult()) {\n    FML_LOG(ERROR)\n        << \"Could not make the context current to acquire the frame.\";\n    return nullptr;\n  }\n\n  SurfaceFrame::FramebufferInfo framebuffer_info;\n\n  // TODO(38466): Refactor GPU surface APIs take into account the fact that an\n  // external view embedder may want to render to the root surface.\n  if (!render_to_surface_) {\n    framebuffer_info.supports_readback = true;\n    return std::make_unique<SurfaceFrame>(\n        nullptr, framebuffer_info,\n        [](const SurfaceFrame& surface_frame, SkCanvas* canvas) {\n          return true;\n        },\n        [](const SurfaceFrame::SubmitInfo) { return true; }, size);\n  }\n\n  const auto root_surface_transformation = GetRootTransformation();\n\n  sk_sp<SkSurface> surface =\n      AcquireRenderSurface(size, root_surface_transformation);\n\n  if (surface == nullptr) {\n    return nullptr;\n  }\n\n#ifdef OS_ANDROID\n  if (size.x * size.y > max_surface_area_) {\n    max_surface_area_ = size.x * size.y;\n    size_t font_cache_limit = std::max(SkGraphics::GetFontCacheLimit(),\n                                       NextPowerOfTwo(max_surface_area_) * 4);\n    SkGraphics::SetFontCacheLimit(font_cache_limit);\n  }\n#endif\n\n  surface->getCanvas()->setMatrix(\n      clay::ConvertSkityMatrixToSkMatrix(root_surface_transformation));\n\n  SurfaceFrame::EncodeCallback encode_callback =\n      [weak = weak_factory_.GetWeakPtr()](const SurfaceFrame& surface_frame,\n                                          SkCanvas* canvas) {\n        TRACE_EVENT(\"clay\", \"GrDirectContext::flushAndSubmit\");\n        return weak ? weak->PresentSurface(surface_frame) : false;\n      };\n  SurfaceFrame::SubmitCallback submit_callback =\n      [](const SurfaceFrame::SubmitInfo& surface_frame) { return true; };\n\n  framebuffer_info = delegate_->GLContextFramebufferInfo();\n  if (!framebuffer_info.existing_damage.has_value()) {\n    framebuffer_info.existing_damage = existing_damage_;\n  }\n  auto frame = std::make_unique<SurfaceFrame>(surface, framebuffer_info,\n                                              encode_callback, submit_callback,\n                                              size, std::move(context_switch));\n\n  frame->SetPreparedCallback(\n      [weak = weak_factory_.GetWeakPtr()](std::optional<skity::Rect> damage) {\n        if (weak) {\n          weak->delegate_->GLContextSetDamageRegion(damage);\n        }\n      });\n\n  return frame;\n}\n\nbool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame) {\n  if (delegate_ == nullptr || context_ == nullptr ||\n      onscreen_surface_ == nullptr) {\n    return false;\n  }\n\n  {\n    TRACE_EVENT(\"clay\", \"SkCanvas::Flush\");\n    onscreen_surface_->getCanvas()->flush();\n  }\n\n  GLPresentInfo present_info = {\n      .fbo_id = fbo_id_,\n      .frame_damage = frame.submit_info().frame_damage,\n      .presentation_time = frame.submit_info().presentation_time,\n      .buffer_damage = frame.submit_info().buffer_damage,\n  };\n  if (!delegate_->GLContextPresent(present_info)) {\n    return false;\n  }\n\n  if (delegate_->GLContextFBOResetAfterPresent()) {\n    onscreen_surface_ = nullptr;\n    fbo_id_ = 0;\n    existing_damage_ = {};\n  }\n\n  return true;\n}\n\nsk_sp<SkSurface> GPUSurfaceGLSkia::AcquireRenderSurface(\n    const skity::Vec2& untransformed_size,\n    const skity::Matrix& root_surface_transformation) {\n  skity::Rect transformed_rect;\n  root_surface_transformation.MapRect(\n      &transformed_rect,\n      skity::Rect::MakeWH(untransformed_size.x, untransformed_size.y));\n\n  const auto transformed_size =\n      skity::Vec2(transformed_rect.Width(), transformed_rect.Height());\n\n  if (!CreateOrUpdateSurfaces(transformed_size)) {\n    return nullptr;\n  }\n\n  return onscreen_surface_;\n}\n\n// |Surface|\nGrDirectContext* GPUSurfaceGLSkia::GetContext() { return context_.get(); }\n\n// |Surface|\nstd::unique_ptr<GLContextResult> GPUSurfaceGLSkia::MakeRenderContextCurrent() {\n  return delegate_->GLContextMakeCurrent();\n}\n\n// |Surface|\nbool GPUSurfaceGLSkia::ClearRenderContext() {\n  return delegate_->GLContextClearCurrent();\n}\n\n// |Surface|\nbool GPUSurfaceGLSkia::AllowsDrawingWhenGpuDisabled() const {\n  return delegate_->AllowsDrawingWhenGpuDisabled();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_gl_skia.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_GPU_GPU_SURFACE_GL_SKIA_H_\n#define CLAY_SHELL_GPU_GPU_SURFACE_GL_SKIA_H_\n\n#include <functional>\n#include <memory>\n#include <optional>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/common/graphics/gl_context_switch.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/shell/gpu/gpu_surface_gl_delegate.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n\nnamespace clay {\n\nclass GPUSurfaceGLSkia : public Surface {\n public:\n  static sk_sp<GrDirectContext> MakeGLContext(GPUSurfaceGLDelegate* delegate);\n\n  GPUSurfaceGLSkia(GPUSurfaceGLDelegate* delegate, bool render_to_surface);\n\n  // Creates a new GL surface reusing an existing GrDirectContext.\n  GPUSurfaceGLSkia(const sk_sp<GrDirectContext>& gr_context,\n                   GPUSurfaceGLDelegate* delegate, bool render_to_surface);\n  // |Surface|\n  ~GPUSurfaceGLSkia() override;\n\n  // |Surface|\n  bool IsValid() override;\n\n  // |Surface|\n  std::unique_ptr<SurfaceFrame> AcquireFrame(const skity::Vec2& size) override;\n\n  // |Surface|\n  skity::Matrix GetRootTransformation() const override;\n\n  // |Surface|\n  GrDirectContext* GetContext() override;\n\n  // |Surface|\n  std::unique_ptr<GLContextResult> MakeRenderContextCurrent() override;\n\n  // |Surface|\n  bool ClearRenderContext() override;\n\n  // |Surface|\n  bool AllowsDrawingWhenGpuDisabled() const override;\n\n private:\n  bool CreateOrUpdateSurfaces(const skity::Vec2& size);\n\n  sk_sp<SkSurface> AcquireRenderSurface(\n      const skity::Vec2& untransformed_size,\n      const skity::Matrix& root_surface_transformation);\n\n  bool PresentSurface(const SurfaceFrame& frame);\n\n  GPUSurfaceGLDelegate* delegate_;\n  sk_sp<GrDirectContext> context_;\n  sk_sp<SkSurface> onscreen_surface_;\n  /// FBO backing the current `onscreen_surface_`.\n  uint32_t fbo_id_ = 0;\n  // The current FBO's existing damage, as tracked by the GPU surface, delegates\n  // still have an option of overriding this damage with their own in\n  // `GLContextFrameBufferInfo`.\n  std::optional<skity::Rect> existing_damage_ = std::nullopt;\n  bool context_owner_ = false;\n  // TODO(38466): Refactor GPU surface APIs take into account the fact that an\n  // external view embedder may want to render to the root surface. This is a\n  // hack to make avoid allocating resources for the root surface when an\n  // external view embedder is present.\n  const bool render_to_surface_ = true;\n  bool valid_ = false;\n\n#ifdef OS_ANDROID\n  int max_surface_area_ = 0;\n#endif\n  // WeakPtrFactory must be the last member.\n  fml::WeakPtrFactory<GPUSurfaceGLSkia> weak_factory_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(GPUSurfaceGLSkia);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_GPU_GPU_SURFACE_GL_SKIA_H_\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_gl_skity.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/gpu/gpu_surface_gl_skity.h\"\n\n#if defined(OS_OSX)\n#else\n#include <EGL/egl.h>\n#include <GLES/gl.h>\n#endif\n\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/fml/base32.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/size.h\"\n#include \"clay/shell/common/context_options.h\"\n#include \"skity/gpu/gpu_context_gl.hpp\"\nnamespace clay {\n\nstd::shared_ptr<skity::GPUContext> GPUSurfaceGLSkity::MakeGLContext(\n    GPUSurfaceGLDelegate* delegate) {\n  auto context_switch = delegate->GLContextMakeCurrent();\n  if (!context_switch->GetResult()) {\n    FML_LOG(ERROR)\n        << \"Could not make the context current to set up the GPU context.\";\n    return nullptr;\n  }\n#if defined(OS_OSX)\n  return nullptr;\n#else\n  return skity::GLContextCreate(reinterpret_cast<void*>(eglGetProcAddress));\n#endif\n}\n\nGPUSurfaceGLSkity::GPUSurfaceGLSkity(\n    GPUSurfaceGLDelegate* delegate,\n    std::shared_ptr<skity::GPUContext> skity_context)\n    : delegate_(delegate),\n      gpu_context_(std::move(skity_context)),\n      weak_factory_(this) {\n  auto context_switch = delegate_->GLContextMakeCurrent();\n  if (!context_switch->GetResult()) {\n    FML_LOG(ERROR)\n        << \"Could not make the context current to set up the GPU context.\";\n    return;\n  }\n\n  valid_ = gpu_context_ != nullptr;\n#if defined(OS_WIN)\n  if (valid_) {\n    gpu_context_->SetEnableSimpleShapePipeline(true);\n  }\n#endif\n}\n\nGPUSurfaceGLSkity::~GPUSurfaceGLSkity() {\n  if (!valid_) {\n    return;\n  }\n  auto context_switch = delegate_->GLContextMakeCurrent();\n  if (!context_switch->GetResult()) {\n    FML_LOG(ERROR) << \"Could not make the context current to destroy the \"\n                      \"GPUContext resource.\";\n    return;\n  }\n  gpu_surface_ = nullptr;\n  fbo_id_ = 0;\n  gpu_context_ = nullptr;\n  delegate_->GLContextClearCurrent();\n}\n\n// |Surface|\nbool GPUSurfaceGLSkity::IsValid() { return valid_; }\n\n// |Surface|\nskity::Matrix GPUSurfaceGLSkity::GetRootTransformation() const {\n  return delegate_->GLContextSurfaceTransformation();\n}\n\n// |Surface|\nstd::unique_ptr<SurfaceFrame> GPUSurfaceGLSkity::AcquireFrame(\n    const skity::Vec2& size) {\n  if (!IsValid()) {\n    FML_LOG(ERROR) << \"OpenGL surface was invalid.\";\n    return nullptr;\n  }\n\n  auto swap_callback = [weak = weak_factory_.GetWeakPtr(),\n                        delegate = delegate_]() -> bool {\n    if (weak) {\n      uint32_t fbo_id = 0;\n      delegate->GLContextPresent(\n          {fbo_id, std::nullopt, std::nullopt, std::nullopt});\n    }\n    return true;\n  };\n\n  auto context_switch = delegate_->GLContextMakeCurrent();\n  if (!context_switch->GetResult()) {\n    FML_LOG(ERROR)\n        << \"Could not make the context current to acquire the frame.\";\n    return nullptr;\n  }\n\n  SurfaceFrame::FramebufferInfo framebuffer_info;\n\n  std::shared_ptr<skity::GPUSurface> gpu_surface = AcquireRenderSurface(size);\n\n  SurfaceFrame::EncodeCallback encode_callback =\n      [weak = weak_factory_.GetWeakPtr()](const SurfaceFrame& surface_frame,\n\n                                          skity::Canvas* canvas) {\n        if (!canvas) {\n          FML_LOG(ERROR) << \"Canvas is null during submit\";\n          return false;\n        }\n        {\n          TRACE_EVENT(\"clay\", \"skity::Canvas::Flush\");\n          canvas->Flush();\n        }\n        return weak ? weak->PresentSurface(surface_frame) : false;\n      };\n\n  SurfaceFrame::SubmitCallback submit_callback =\n      [](const SurfaceFrame::SubmitInfo& surface_frame) { return true; };\n\n  framebuffer_info = delegate_->GLContextFramebufferInfo();\n  if (!framebuffer_info.existing_damage.has_value()) {\n    framebuffer_info.existing_damage = existing_damage_;\n  }\n\n  auto frame = std::make_unique<SurfaceFrame>(\n      std::shared_ptr<skity::GPUSurface>(gpu_surface), framebuffer_info,\n      encode_callback, submit_callback, size, std::move(context_switch));\n\n  frame->SetPreparedCallback(\n      [weak = weak_factory_.GetWeakPtr()](std::optional<skity::Rect> damage) {\n        if (weak) {\n          weak->delegate_->GLContextSetDamageRegion(damage);\n        }\n      });\n\n  return frame;\n}\n\n// |Surface|\nstd::unique_ptr<GLContextResult> GPUSurfaceGLSkity::MakeRenderContextCurrent() {\n  return delegate_->GLContextMakeCurrent();\n}\n\n// |Surface|\nbool GPUSurfaceGLSkity::ClearRenderContext() {\n  return delegate_->GLContextClearCurrent();\n}\n\n// |Surface|\nbool GPUSurfaceGLSkity::EnableRasterCache() const { return true; }\n\n// |Surface|\nskity::GPUContext* GPUSurfaceGLSkity::GetContext() {\n  return gpu_context_.get();\n}\n\nstd::shared_ptr<skity::GPUSurface> GPUSurfaceGLSkity::AcquireRenderSurface(\n    const skity::Vec2& size) {\n  if (size != size_) {\n    size_ = size;\n    gpu_surface_map_.clear();\n  }\n\n  GLFrameInfo frame_info = {static_cast<uint32_t>(size.x),\n                            static_cast<uint32_t>(size.y)};\n  const GLFBOInfo fbo_info = delegate_->GLContextFBO(frame_info);\n\n  fbo_id_ = fbo_info.fbo_id;\n  existing_damage_ = fbo_info.existing_damage;\n\n  auto iter = gpu_surface_map_.find(fbo_info.fbo_id);\n  if (iter != gpu_surface_map_.end()) {\n    gpu_surface_ = iter->second;\n    return gpu_surface_;\n  }\n\n  skity::GPUSurfaceDescriptorGL desc{};\n  desc.backend = skity::GPUBackendType::kOpenGL;\n  desc.width = size.x;\n  desc.height = size.y;\n  desc.sample_count = delegate_->GetSampleCount();\n  desc.content_scale = 1;\n  desc.gl_id = fbo_info.fbo_id;\n  desc.has_stencil_attachment = true;\n  desc.surface_type = skity::GLSurfaceType::kFramebuffer;\n\n  gpu_surface_map_[fbo_info.fbo_id] = gpu_context_->CreateSurface(&desc);\n  gpu_surface_ = gpu_surface_map_[fbo_info.fbo_id];\n\n  return gpu_surface_;\n}\n\nbool GPUSurfaceGLSkity::PresentSurface(const SurfaceFrame& frame) {\n  if (delegate_ == nullptr || gpu_context_ == nullptr ||\n      gpu_surface_ == nullptr) {\n    return false;\n  }\n\n  {\n    TRACE_EVENT(\"clay\", \"skity::Surface::Flush\");\n    gpu_surface_->Flush();\n  }\n\n  GLPresentInfo present_info = {\n      .fbo_id = fbo_id_,\n      .frame_damage = frame.submit_info().frame_damage,\n      .presentation_time = frame.submit_info().presentation_time,\n      .buffer_damage = frame.submit_info().buffer_damage,\n  };\n  if (!delegate_->GLContextPresent(present_info)) {\n    return false;\n  }\n\n  if (delegate_->GLContextFBOResetAfterPresent()) {\n    gpu_surface_ = nullptr;\n    fbo_id_ = 0;\n    existing_damage_ = {};\n  }\n\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_gl_skity.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_GPU_GPU_SURFACE_GL_SKITY_H_\n#define CLAY_SHELL_GPU_GPU_SURFACE_GL_SKITY_H_\n\n#include <functional>\n#include <memory>\n#include <unordered_map>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/common/graphics/gl_context_switch.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/shell/gpu/gpu_surface_gl_delegate.h\"\n#include \"skity/gpu/gpu_context.hpp\"\n#include \"skity/skity.hpp\"\n\nnamespace clay {\n\nclass GPUSurfaceGLSkity : public Surface {\n public:\n  static std::shared_ptr<skity::GPUContext> MakeGLContext(\n      GPUSurfaceGLDelegate* delegate);\n\n  GPUSurfaceGLSkity(GPUSurfaceGLDelegate* delegate,\n                    std::shared_ptr<skity::GPUContext> skity_context);\n\n  // |Surface|\n  ~GPUSurfaceGLSkity() override;\n\n  // |Surface|\n  bool IsValid() override;\n\n  // |Surface|\n  std::unique_ptr<SurfaceFrame> AcquireFrame(const skity::Vec2& size) override;\n\n  // |Surface|\n  skity::Matrix GetRootTransformation() const override;\n\n  // |Surface|\n  std::unique_ptr<GLContextResult> MakeRenderContextCurrent() override;\n\n  // |Surface|\n  bool ClearRenderContext() override;\n\n  // |Surface|\n  bool EnableRasterCache() const override;\n\n  // |Surface|\n  skity::GPUContext* GetContext() override;\n\n private:\n  std::shared_ptr<skity::GPUSurface> AcquireRenderSurface(\n      const skity::Vec2& size);\n  bool PresentSurface(const SurfaceFrame& frame);\n\n  GPUSurfaceGLDelegate* delegate_;\n  bool valid_ = false;\n\n  // Contains multiple GPUSurfaces (which counts on the number of buffers).\n  // All the GPUSurfaces have the same size, but refer to different FBOs.\n  // In FunctorView mode, there might be 2 GPUSurfaces. And In SurfaceView,\n  // there will only be 1.\n  std::unordered_map<uint32_t, std::shared_ptr<skity::GPUSurface>>\n      gpu_surface_map_;\n  // Refer to the current GPUSurface in use.\n  std::shared_ptr<skity::GPUSurface> gpu_surface_;\n  std::shared_ptr<skity::GPUContext> gpu_context_;\n  uint32_t fbo_id_ = 0;\n  skity::Vec2 size_;\n\n  // The current FBO's existing damage, as tracked by the GPU surface, delegates\n  // still have an option of overriding this damage with their own in\n  // `GLContextFrameBufferInfo`.\n  std::optional<skity::Rect> existing_damage_ = std::nullopt;\n\n  // WeakPtrFactory must be the last member.\n  fml::WeakPtrFactory<GPUSurfaceGLSkity> weak_factory_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(GPUSurfaceGLSkity);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_GPU_GPU_SURFACE_GL_SKITY_H_\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_metal_delegate.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"clay/shell/gpu/gpu_surface_metal_delegate.h\"\n\nnamespace clay {\n\nGPUSurfaceMetalDelegate::GPUSurfaceMetalDelegate(\n    MTLRenderTargetType render_target_type)\n    : render_target_type_(render_target_type) {}\n\nGPUSurfaceMetalDelegate::~GPUSurfaceMetalDelegate() = default;\n\nMTLRenderTargetType GPUSurfaceMetalDelegate::GetRenderTargetType() {\n  return render_target_type_;\n}\n\nbool GPUSurfaceMetalDelegate::AllowsDrawingWhenGpuDisabled() const {\n  return true;\n}\n\nbool GPUSurfaceMetalDelegate::EnablePartialRepaint() const { return true; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_metal_delegate.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef CLAY_SHELL_GPU_GPU_SURFACE_METAL_DELEGATE_H_\n#define CLAY_SHELL_GPU_GPU_SURFACE_METAL_DELEGATE_H_\n\n#include <stdint.h>\n\n#include \"base/include/fml/macros.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\n// expected to be id<MTLDevice>\ntypedef void* GPUMTLDeviceHandle;\n\n// expected to be id<MTLCommandQueues>\ntypedef void* GPUMTLCommandQueueHandle;\n\n// expected to be CAMetalLayer*\ntypedef void* GPUCAMetalLayerHandle;\n\n// expected to be id<MTLTexture>\ntypedef const void* GPUMTLTextureHandle;\n\ntypedef void (*GPUMTLDestructionCallback)(void* /* destruction_context */);\n\nstruct GPUMTLTextureInfo {\n  int64_t texture_id;\n  GPUMTLTextureHandle texture;\n  GPUMTLDestructionCallback destruction_callback;\n  void* destruction_context;\n};\n\nenum class MTLRenderTargetType { kMTLTexture, kCAMetalLayer };\n\n//------------------------------------------------------------------------------\n/// @brief      Interface implemented by all platform surfaces that can present\n///             a metal backing store to the \"screen\". The GPU surface\n///             abstraction (which abstracts the client rendering API) uses this\n///             delegation pattern to tell the platform surface (which abstracts\n///             how backing stores fulfilled by the selected client rendering\n///             API end up on the \"screen\" on a particular platform) when the\n///             rasterizer needs to allocate and present the software backing\n///             store.\n///\n/// @see        |IOSurfaceMetal| and |EmbedderSurfaceMetal|.\n///\nclass GPUSurfaceMetalDelegate {\n public:\n  //------------------------------------------------------------------------------\n  /// @brief Construct a new GPUSurfaceMetalDelegate object with the specified\n  /// render_target type.\n  ///\n  /// @see |MTLRenderTargetType|\n  ///\n  explicit GPUSurfaceMetalDelegate(MTLRenderTargetType render_target);\n\n  virtual ~GPUSurfaceMetalDelegate();\n\n  //------------------------------------------------------------------------------\n  /// @brief Returns the handle to the CAMetalLayer to render to. This is only\n  /// called when the specified render target type is `kCAMetalLayer`.\n  ///\n  virtual GPUCAMetalLayerHandle GetCAMetalLayer(\n      const skity::Vec2& frame_info) const = 0;\n\n  virtual bool PreparePresent(const void* drawable) const { return true; }\n\n  //------------------------------------------------------------------------------\n  /// @brief Returns the handle to the MTLTexture to render to. This is only\n  /// called when the specified render target type is `kMTLTexture`.\n  ///\n  virtual GPUMTLTextureInfo GetMTLTexture(\n      const skity::Vec2& frame_info) const = 0;\n\n  //------------------------------------------------------------------------------\n  /// @brief Presents the texture with `texture_id` to the \"screen\".\n  /// `texture_id` corresponds to a texture that has been obtained by an earlier\n  /// call to `GetMTLTexture`. This is only called when the specified render\n  /// target type is `kMTLTexture`.\n  ///\n  /// @see |GPUSurfaceMetalDelegate::GetMTLTexture|\n  ///\n  virtual bool PresentTexture(GPUMTLTextureInfo texture) const = 0;\n\n  //------------------------------------------------------------------------------\n  /// @brief Whether to allow drawing to the surface when the GPU is disabled\n  ///\n  virtual bool AllowsDrawingWhenGpuDisabled() const;\n\n  //------------------------------------------------------------------------------\n  /// @brief Whether to enable partial repaint\n  ///\n  virtual bool EnablePartialRepaint() const;\n\n  MTLRenderTargetType GetRenderTargetType();\n\n private:\n  const MTLRenderTargetType render_target_type_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_GPU_GPU_SURFACE_METAL_DELEGATE_H_\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_metal_skia.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_GPU_GPU_SURFACE_METAL_SKIA_H_\n#define CLAY_SHELL_GPU_GPU_SURFACE_METAL_SKIA_H_\n\n#include <map>\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/common/graphics/msaa_sample_count.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/shell/gpu/gpu_surface_metal_delegate.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n#include \"third_party/skia/include/gpu/mtl/GrMtlTypes.h\"\n\nnamespace clay {\n\nclass SK_API_AVAILABLE_CA_METAL_LAYER GPUSurfaceMetalSkia : public Surface {\n public:\n  GPUSurfaceMetalSkia(GPUSurfaceMetalDelegate* delegate,\n                      sk_sp<GrDirectContext> context,\n                      MsaaSampleCount msaa_samples,\n                      bool render_to_surface = true);\n\n  // |Surface|\n  ~GPUSurfaceMetalSkia();\n\n  // |Surface|\n  bool IsValid() override;\n\n private:\n  const GPUSurfaceMetalDelegate* delegate_;\n  const MTLRenderTargetType render_target_type_;\n  sk_sp<GrDirectContext> context_;\n  GrDirectContext* precompiled_sksl_context_ = nullptr;\n  MsaaSampleCount msaa_samples_ = MsaaSampleCount::kNone;\n  // TODO(38466): Refactor GPU surface APIs take into account the fact that an\n  // external view embedder may want to render to the root surface. This is a\n  // hack to make avoid allocating resources for the root surface when an\n  // external view embedder is present.\n  bool render_to_surface_ = true;\n\n  // Accumulated damage for each framebuffer; Key is address of underlying\n  // MTLTexture for each drawable\n  std::map<intptr_t, skity::Rect> damage_;\n\n  // Memoize the previous frame size\n  // If changed, damage history should be reset\n  skity::Vec2 size_ = {0, 0};\n\n  // |Surface|\n  std::unique_ptr<SurfaceFrame> AcquireFrame(const skity::Vec2& size) override;\n\n  // |Surface|\n  skity::Matrix GetRootTransformation() const override;\n\n  // |Surface|\n  GrDirectContext* GetContext() override;\n\n  // |Surface|\n  std::unique_ptr<GLContextResult> MakeRenderContextCurrent() override;\n\n  // |Surface|\n  bool AllowsDrawingWhenGpuDisabled() const override;\n\n  std::unique_ptr<SurfaceFrame> AcquireFrameFromCAMetalLayer(\n      const skity::Vec2& frame_info);\n\n  std::unique_ptr<SurfaceFrame> AcquireFrameFromMTLTexture(\n      const skity::Vec2& frame_info);\n\n  void PrecompileKnownSkSLsIfNecessary();\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(GPUSurfaceMetalSkia);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_GPU_GPU_SURFACE_METAL_SKIA_H_\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_metal_skia.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/gpu/gpu_surface_metal_skia.h\"\n\n#import <Metal/Metal.h>\n#import <QuartzCore/QuartzCore.h>\n\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/flow/surface_frame.h\"\n#include \"clay/fml/platform/darwin/cf_utils.h\"\n#include \"clay/fml/platform/darwin/scoped_nsobject.h\"\n#include \"clay/shell/gpu/gpu_surface_metal_delegate.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkColorSpace.h\"\n// #include \"third_party/skia/include/core/SkColorType.h\"\n#include \"third_party/skia/include/core/SkMatrix.h\"\n#include \"third_party/skia/include/core/SkRect.h\"\n#include \"third_party/skia/include/core/SkRefCnt.h\"\n#include \"third_party/skia/include/core/SkSize.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n#include \"third_party/skia/include/core/SkSurfaceProps.h\"\n#include \"third_party/skia/include/gpu/GrBackendSurface.h\"\n#include \"third_party/skia/include/ports/SkCFObject.h\"\n\nstatic_assert(__has_feature(objc_arc), \"ARC must be enabled.\");\n\nnamespace clay {\n\nnamespace {\nsk_sp<SkSurface> CreateSurfaceFromMetalTexture(GrDirectContext* context, id<MTLTexture> texture,\n                                               GrSurfaceOrigin origin, MsaaSampleCount sample_cnt,\n                                               SkColorType color_type,\n                                               sk_sp<SkColorSpace> color_space,\n                                               const SkSurfaceProps* props,\n                                               SkSurface::TextureReleaseProc release_proc,\n                                               SkSurface::ReleaseContext release_context) {\n  GrMtlTextureInfo info;\n  info.fTexture.reset((__bridge_retained GrMTLHandle)texture);\n  GrBackendTexture backend_texture(texture.width, texture.height, GrMipmapped::kNo, info);\n  return SkSurface::MakeFromBackendTexture(\n      context, backend_texture, origin, static_cast<int>(sample_cnt), color_type,\n      std::move(color_space), props, release_proc, release_context);\n}\n}  // namespace\n\nGPUSurfaceMetalSkia::GPUSurfaceMetalSkia(GPUSurfaceMetalDelegate* delegate,\n                                         sk_sp<GrDirectContext> context,\n                                         MsaaSampleCount msaa_samples, bool render_to_surface)\n    : delegate_(delegate),\n      render_target_type_(delegate->GetRenderTargetType()),\n      context_(std::move(context)),\n      msaa_samples_(msaa_samples),\n      render_to_surface_(render_to_surface) {}\n\nGPUSurfaceMetalSkia::~GPUSurfaceMetalSkia() = default;\n\n// |Surface|\nbool GPUSurfaceMetalSkia::IsValid() { return context_ != nullptr; }\n\nvoid GPUSurfaceMetalSkia::PrecompileKnownSkSLsIfNecessary() {\n  auto* current_context = GetContext();\n  if (current_context == precompiled_sksl_context_) {\n    // Known SkSLs have already been prepared in this context.\n    return;\n  }\n  precompiled_sksl_context_ = current_context;\n  clay::PersistentCache::GetCacheForProcess()->PrecompileKnownSkSLs(precompiled_sksl_context_);\n}\n\n// |Surface|\nstd::unique_ptr<SurfaceFrame> GPUSurfaceMetalSkia::AcquireFrame(const skity::Vec2& frame_size) {\n  if (!IsValid()) {\n    FML_LOG(ERROR) << \"Metal surface was invalid.\";\n    return nullptr;\n  }\n\n  if (frame_size.x == 0 & frame_size.y == 0) {\n    FML_LOG(ERROR) << \"Metal surface was asked for an empty frame.\";\n    return nullptr;\n  }\n\n  if (!render_to_surface_) {\n    return std::make_unique<SurfaceFrame>(\n        nullptr, SurfaceFrame::FramebufferInfo(),\n        [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { return true; },\n        [](const SurfaceFrame::SubmitInfo&) { return true; },  //\n        frame_size);\n  }\n\n  PrecompileKnownSkSLsIfNecessary();\n\n  switch (render_target_type_) {\n    case MTLRenderTargetType::kCAMetalLayer:\n      return AcquireFrameFromCAMetalLayer(frame_size);\n    case MTLRenderTargetType::kMTLTexture:\n      return AcquireFrameFromMTLTexture(frame_size);\n    default:\n      FML_CHECK(false) << \"Unknown MTLRenderTargetType type.\";\n  }\n\n  return nullptr;\n}\n\nstd::unique_ptr<SurfaceFrame> GPUSurfaceMetalSkia::AcquireFrameFromCAMetalLayer(\n    const skity::Vec2& frame_info) {\n  auto layer = delegate_->GetCAMetalLayer(frame_info);\n  if (!layer) {\n    FML_LOG(ERROR) << \"Invalid CAMetalLayer given by the embedder.\";\n    return nullptr;\n  }\n\n  auto* mtl_layer = (__bridge CAMetalLayer*)layer;\n  // Get the drawable eagerly, we will need texture object to identify target framebuffer\n  fml::scoped_nsprotocol<id<CAMetalDrawable>> drawable([mtl_layer nextDrawable]);\n\n  if (!drawable.get()) {\n    FML_LOG(ERROR) << \"Could not obtain drawable from the metal layer.\";\n    return nullptr;\n  }\n\n  auto surface = CreateSurfaceFromMetalTexture(context_.get(), drawable.get().texture,\n                                               kTopLeft_GrSurfaceOrigin,  // origin\n                                               msaa_samples_,             // sample count\n                                               kBGRA_8888_SkColorType,    // color type\n                                               nullptr,                   // colorspace\n                                               nullptr,                   // surface properties\n                                               nullptr,                   // release proc\n                                               nullptr                    // release context\n  );\n\n  if (!surface) {\n    FML_LOG(ERROR) << \"Could not create the SkSurface from the CAMetalLayer.\";\n    return nullptr;\n  }\n\n  auto encode_callback = [this, drawable, mtl_layer](const SurfaceFrame& surface_frame,\n                                                     SkCanvas* canvas) -> bool {\n    if (canvas == nullptr) {\n      FML_DLOG(ERROR) << \"Canvas not available.\";\n      return false;\n    }\n\n    {\n      TRACE_EVENT(\"clay\", \"SkCanvas::Flush\");\n      canvas->flush();\n    }\n\n    if (delegate_->EnablePartialRepaint()) {\n      intptr_t texture = reinterpret_cast<intptr_t>(drawable.get().texture);\n      for (auto& entry : damage_) {\n        if (entry.first != texture) {\n          // Accumulate damage for other framebuffers\n          if (surface_frame.submit_info().frame_damage) {\n            entry.second.Join(*surface_frame.submit_info().frame_damage);\n          }\n        }\n      }\n      // Reset accumulated damage for current framebuffer\n      damage_[texture] = skity::Rect::MakeEmpty();\n    }\n\n    return delegate_->PreparePresent((__bridge GrMTLHandle)drawable.get());\n  };\n\n  // submit_callback must capture objects which is safe to use in platform thread\n  auto submit_callback = [drawable,\n                          mtl_layer](const SurfaceFrame::SubmitInfo& submit_info) -> bool {\n    TRACE_EVENT(\"clay\", \"GPUSurfaceMetal::Submit\");\n    mtl_layer.presentsWithTransaction = submit_info.present_with_transaction;\n    [drawable present];\n    return true;\n  };\n\n  SurfaceFrame::FramebufferInfo framebuffer_info;\n  framebuffer_info.supports_readback = true;\n\n  if (delegate_->EnablePartialRepaint()) {\n    // Provide accumulated damage to rasterizer (area in current framebuffer that lags behind\n    // front buffer)\n    intptr_t texture = reinterpret_cast<intptr_t>(drawable.get().texture);\n    auto i = damage_.find(texture);\n    if (i != damage_.end()) {\n      framebuffer_info.existing_damage = i->second;\n    }\n    framebuffer_info.supports_partial_repaint = true;\n  }\n\n  return std::make_unique<SurfaceFrame>(std::move(surface), framebuffer_info, encode_callback,\n                                        submit_callback, frame_info);\n}\n\nstd::unique_ptr<SurfaceFrame> GPUSurfaceMetalSkia::AcquireFrameFromMTLTexture(\n    const skity::Vec2& frame_info) {\n  GPUMTLTextureInfo texture = delegate_->GetMTLTexture(frame_info);\n  id<MTLTexture> mtl_texture = (__bridge id<MTLTexture>)(texture.texture);\n\n  if (!mtl_texture) {\n    FML_LOG(ERROR) << \"Invalid MTLTexture given by the embedder.\";\n    return nullptr;\n  }\n\n  sk_sp<SkSurface> surface = CreateSurfaceFromMetalTexture(\n      context_.get(), mtl_texture, kTopLeft_GrSurfaceOrigin, msaa_samples_, kBGRA_8888_SkColorType,\n      nullptr, nullptr, static_cast<SkSurface::TextureReleaseProc>(texture.destruction_callback),\n      texture.destruction_context);\n\n  if (!surface) {\n    FML_LOG(ERROR) << \"Could not create the SkSurface from the metal texture.\";\n    return nullptr;\n  }\n\n  SurfaceFrame::EncodeCallback encode_callback = [this, texture](const SurfaceFrame& surface_frame,\n                                                                 SkCanvas* canvas) -> bool {\n    if (canvas == nullptr) {\n      FML_DLOG(ERROR) << \"Canvas not available.\";\n      return false;\n    }\n\n    {\n      TRACE_EVENT(\"clay\", \"SkCanvas::Flush\");\n      canvas->flush();\n    }\n\n    if (delegate_->EnablePartialRepaint()) {\n      for (auto& [texture_id, damage] : damage_) {\n        if (texture_id != texture.texture_id) {\n          // Accumulate damage for other framebuffers\n          if (surface_frame.submit_info().frame_damage) {\n            damage.Join(*surface_frame.submit_info().frame_damage);\n          }\n        }\n      }\n      // Reset accumulated damage for current framebuffer\n      damage_[texture.texture_id] = skity::Rect::MakeEmpty();\n    }\n\n    return true;\n  };\n\n  // This code path is only used on Mac platform, which ensures rasterizer teardown before shell,\n  // thus safe to cauptre this\n  auto submit_callback = [this, texture](const SurfaceFrame::SubmitInfo&) -> bool {\n    TRACE_EVENT(\"clay\", \"GPUSurfaceMetal::PresentTexture\");\n    return delegate_->PresentTexture(texture);\n  };\n\n  SurfaceFrame::FramebufferInfo framebuffer_info;\n  framebuffer_info.supports_readback = true;\n\n  if (size_ != frame_info) {\n    damage_.clear();\n    size_ = frame_info;\n  }\n\n  if (delegate_->EnablePartialRepaint()) {\n    // Provide accumulated damage to rasterizer (area in current framebuffer that lags behind\n    // front buffer)\n    auto i = damage_.find(texture.texture_id);\n    if (i != damage_.end()) {\n      framebuffer_info.existing_damage = i->second;\n    }\n    framebuffer_info.supports_partial_repaint = true;\n  }\n\n  return std::make_unique<SurfaceFrame>(std::move(surface), framebuffer_info, encode_callback,\n                                        submit_callback, frame_info);\n}\n\n// |Surface|\nskity::Matrix GPUSurfaceMetalSkia::GetRootTransformation() const {\n  // This backend does not currently support root surface transformations. Just\n  // return identity.\n  return {};\n}\n\n// |Surface|\nGrDirectContext* GPUSurfaceMetalSkia::GetContext() { return context_.get(); }\n\n// |Surface|\nstd::unique_ptr<GLContextResult> GPUSurfaceMetalSkia::MakeRenderContextCurrent() {\n  // A context may either be necessary to render to the surface or to snapshot an offscreen\n  // surface. Either way, SkSL precompilation must be attempted.\n  PrecompileKnownSkSLsIfNecessary();\n\n  // This backend has no such concept.\n  return std::make_unique<GLContextDefaultResult>(true);\n}\n\nbool GPUSurfaceMetalSkia::AllowsDrawingWhenGpuDisabled() const {\n  return delegate_->AllowsDrawingWhenGpuDisabled();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_metal_skity.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_GPU_GPU_SURFACE_METAL_SKITY_H_\n#define CLAY_SHELL_GPU_GPU_SURFACE_METAL_SKITY_H_\n\n#include <map>\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/common/graphics/msaa_sample_count.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/shell/gpu/gpu_surface_metal_delegate.h\"\n\nnamespace clay {\n\nclass GPUSurfaceMetalSkity : public Surface {\n public:\n  GPUSurfaceMetalSkity(GPUSurfaceMetalDelegate* delegate,\n                       std::shared_ptr<skity::GPUContext> context,\n                       MsaaSampleCount msaa_samples,\n                       bool render_to_surface = true);\n\n  // |Surface|\n  ~GPUSurfaceMetalSkity() override;\n\n  // |Surface|\n  bool IsValid() override;\n\n private:\n  const GPUSurfaceMetalDelegate* delegate_;\n  const MTLRenderTargetType render_target_type_;\n  std::shared_ptr<skity::GPUContext> context_;\n  MsaaSampleCount msaa_samples_ = MsaaSampleCount::kNone;\n  // TODO(38466): Refactor GPU surface APIs take into account the fact that an\n  // external view embedder may want to render to the root surface. This is a\n  // hack to make avoid allocating resources for the root surface when an\n  // external view embedder is present.\n  bool render_to_surface_ = true;\n\n  // Accumulated damage for each framebuffer; Key is address of underlying\n  // MTLTexture for each drawable\n  std::map<intptr_t, skity::Rect> damage_;\n\n  // Memoize the previous frame size\n  // If changed, damage history should be reset\n  skity::Vec2 size_ = {0, 0};\n\n  // |Surface|\n  std::unique_ptr<SurfaceFrame> AcquireFrame(const skity::Vec2& size) override;\n\n  // |Surface|\n  skity::Matrix GetRootTransformation() const override;\n\n  // |Surface|\n  skity::GPUContext* GetContext() override;\n\n  // |Surface|\n  std::unique_ptr<GLContextResult> MakeRenderContextCurrent() override;\n\n  // |Surface|\n  bool AllowsDrawingWhenGpuDisabled() const override;\n\n  std::unique_ptr<SurfaceFrame> AcquireFrameFromCAMetalLayer(\n      const skity::Vec2& frame_info);\n\n  std::unique_ptr<SurfaceFrame> AcquireFrameFromMTLTexture(\n      const skity::Vec2& frame_info);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(GPUSurfaceMetalSkity);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_GPU_GPU_SURFACE_METAL_SKITY_H_\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_metal_skity.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/gpu/gpu_surface_metal_skity.h\"\n\n#import <Metal/Metal.h>\n#import <QuartzCore/QuartzCore.h>\n\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/fml/platform/darwin/cf_utils.h\"\n#include \"clay/fml/platform/darwin/scoped_nsobject.h\"\n#include \"clay/shell/gpu/gpu_surface_metal_delegate.h\"\n#include \"skity/gpu/gpu_context_mtl.h\"\n\nstatic_assert(__has_feature(objc_arc), \"ARC must be enabled.\");\n\nnamespace clay {\n\nnamespace {\nstd::shared_ptr<skity::GPUSurface> CreateSurfaceFromMetalTexture(\n    std::shared_ptr<skity::GPUContext> context, const skity::Vec2& size, id<MTLTexture> texture,\n    uint32_t sample_cnt) {\n  skity::GPUSurfaceDescriptorMTL descriptor = {\n      {skity::GPUBackendType::kMetal, static_cast<uint32_t>(size.x), static_cast<uint32_t>(size.y),\n       sample_cnt},\n      skity::MTLSurfaceType::kTexture,\n      nil,\n      texture};\n  return context->CreateSurface(&descriptor);\n}\n}  // namespace\n\nGPUSurfaceMetalSkity::GPUSurfaceMetalSkity(GPUSurfaceMetalDelegate* delegate,\n                                           std::shared_ptr<skity::GPUContext> context,\n                                           MsaaSampleCount msaa_samples, bool render_to_surface)\n    : delegate_(delegate),\n      render_target_type_(delegate->GetRenderTargetType()),\n      context_(std::move(context)),\n      msaa_samples_(msaa_samples),\n      render_to_surface_(render_to_surface) {\n#if defined(OS_OSX)\n  context_->SetEnableSimpleShapePipeline(true);\n#endif\n}\n\nGPUSurfaceMetalSkity::~GPUSurfaceMetalSkity() = default;\n\n// |Surface|\nbool GPUSurfaceMetalSkity::IsValid() { return context_ != nullptr; }\n\n// |Surface|\nstd::unique_ptr<SurfaceFrame> GPUSurfaceMetalSkity::AcquireFrame(const skity::Vec2& frame_size) {\n  if (!IsValid()) {\n    FML_LOG(ERROR) << \"Metal surface was invalid.\";\n    return nullptr;\n  }\n\n  if (frame_size.x == 0 && frame_size.y == 0) {\n    FML_LOG(ERROR) << \"Metal surface was asked for an empty frame.\";\n    return nullptr;\n  }\n\n  if (!render_to_surface_) {\n    return std::make_unique<SurfaceFrame>(\n        nullptr, SurfaceFrame::FramebufferInfo(),\n        [](const SurfaceFrame& surface_frame, skity::Canvas* canvas) { return true; },\n        [](const SurfaceFrame::SubmitInfo& surface_frame) { return true; }, frame_size);\n  }\n\n  switch (render_target_type_) {\n    case MTLRenderTargetType::kCAMetalLayer:\n      return AcquireFrameFromCAMetalLayer(frame_size);\n    case MTLRenderTargetType::kMTLTexture:\n      return AcquireFrameFromMTLTexture(frame_size);\n    default:\n      FML_CHECK(false) << \"Unknown MTLRenderTargetType type.\";\n  }\n\n  return nullptr;\n}\n\nstd::unique_ptr<SurfaceFrame> GPUSurfaceMetalSkity::AcquireFrameFromCAMetalLayer(\n    const skity::Vec2& frame_info) {\n  auto layer = delegate_->GetCAMetalLayer(frame_info);\n  if (!layer) {\n    FML_LOG(ERROR) << \"Invalid CAMetalLayer given by the embedder.\";\n    return nullptr;\n  }\n\n  auto* mtl_layer = (__bridge CAMetalLayer*)layer;\n  // Get the drawable eagerly, we will need texture object to identify target framebuffer\n  fml::scoped_nsprotocol<id<CAMetalDrawable>> drawable([mtl_layer nextDrawable]);\n\n  if (!drawable.get()) {\n    FML_LOG(ERROR) << \"Could not obtain drawable from the metal layer.\";\n    return nullptr;\n  }\n\n  auto surface = CreateSurfaceFromMetalTexture(context_, frame_info, drawable.get().texture,\n                                               static_cast<uint32_t>(msaa_samples_));\n  if (!surface) {\n    FML_LOG(ERROR) << \"Could not create gpuSurface from the CAMetalLayer.\";\n    return nullptr;\n  }\n\n  auto encode_callback = [this, drawable, surface](SurfaceFrame& surface_frame,\n                                                   skity::Canvas* canvas) -> bool {\n    if (!canvas) {\n      FML_LOG(ERROR) << \"skity Canvas is null.\";\n      return false;\n    }\n    TRACE_EVENT(\"clay\", \"skity::Canvas::Flush\");\n    canvas->Flush();\n    surface->Flush();\n\n    if (delegate_->EnablePartialRepaint()) {\n      intptr_t texture = reinterpret_cast<intptr_t>(drawable.get().texture);\n      for (auto& entry : damage_) {\n        if (entry.first != texture) {\n          // Accumulate damage for other framebuffers\n          if (surface_frame.submit_info().frame_damage) {\n            entry.second.Join(*surface_frame.submit_info().frame_damage);\n          }\n        }\n      }\n      // Reset accumulated damage for current framebuffer\n      damage_[texture] = skity::Rect::MakeEmpty();\n    }\n\n    return true;\n  };\n\n  // submit_callback must capture objects which is safe to use in platform thread\n  auto submit_callback = [drawable,\n                          mtl_layer](const SurfaceFrame::SubmitInfo& submit_info) -> bool {\n    TRACE_EVENT(\"clay\", \"GPUSurfaceMetal::Submit\");\n    mtl_layer.presentsWithTransaction = submit_info.present_with_transaction;\n    [drawable present];\n    return true;\n  };\n\n  SurfaceFrame::FramebufferInfo framebuffer_info;\n  framebuffer_info.supports_readback = true;\n\n  if (delegate_->EnablePartialRepaint()) {\n    // Provide accumulated damage to rasterizer (area in current framebuffer that lags behind\n    // front buffer)\n    intptr_t texture = reinterpret_cast<intptr_t>(drawable.get().texture);\n    auto i = damage_.find(texture);\n    if (i != damage_.end()) {\n      framebuffer_info.existing_damage = i->second;\n    }\n    framebuffer_info.supports_partial_repaint = true;\n  }\n\n  return std::make_unique<SurfaceFrame>(surface, framebuffer_info, encode_callback, submit_callback,\n                                        frame_info);\n}\n\nstd::unique_ptr<SurfaceFrame> GPUSurfaceMetalSkity::AcquireFrameFromMTLTexture(\n    const skity::Vec2& frame_info) {\n  GPUMTLTextureInfo texture = delegate_->GetMTLTexture(frame_info);\n  id<MTLTexture> mtl_texture = (__bridge id<MTLTexture>)(texture.texture);\n\n  if (!mtl_texture) {\n    FML_LOG(ERROR) << \"Invalid MTLTexture given by the embedder.\";\n    return nullptr;\n  }\n\n  auto surface = CreateSurfaceFromMetalTexture(context_, frame_info, mtl_texture,\n                                               static_cast<uint32_t>(msaa_samples_));\n  if (!surface) {\n    FML_LOG(ERROR) << \"Could not create gpuSurface from the CAMetalLayer.\";\n    return nullptr;\n  }\n\n  auto encode_callback = [this, texture, surface](SurfaceFrame& surface_frame,\n                                                  skity::Canvas* canvas) -> bool {\n    if (!canvas) {\n      FML_LOG(ERROR) << \"skity Canvas is null.\";\n      return false;\n    }\n    TRACE_EVENT(\"clay\", \"skity::Canvas::Flush\");\n    canvas->Flush();\n    surface->Flush();\n\n    if (delegate_->EnablePartialRepaint()) {\n      for (auto& entry : damage_) {\n        if (entry.first != texture.texture_id) {\n          // Accumulate damage for other framebuffers\n          if (surface_frame.submit_info().frame_damage) {\n            entry.second.Join(*surface_frame.submit_info().frame_damage);\n          }\n        }\n      }\n      // Reset accumulated damage for current framebuffer\n      damage_[texture.texture_id] = skity::Rect::MakeEmpty();\n    }\n\n    return true;\n  };\n\n  // This code path is only used on Mac platform, which ensures rasterizer teardown before shell,\n  // thus safe to cauptre this\n  auto submit_callback = [this, texture](const SurfaceFrame::SubmitInfo&) -> bool {\n    TRACE_EVENT(\"clay\", \"GPUSurfaceMetal::PresentTexture\");\n    return delegate_->PresentTexture(texture);\n  };\n\n  SurfaceFrame::FramebufferInfo framebuffer_info;\n  framebuffer_info.supports_readback = true;\n\n  if (size_ != frame_info) {\n    damage_.clear();\n    size_ = frame_info;\n  }\n\n  if (delegate_->EnablePartialRepaint()) {\n    auto i = damage_.find(texture.texture_id);\n    if (i != damage_.end()) {\n      framebuffer_info.existing_damage = i->second;\n    }\n    framebuffer_info.supports_partial_repaint = true;\n  }\n\n  return std::make_unique<SurfaceFrame>(surface, framebuffer_info, encode_callback, submit_callback,\n                                        frame_info);\n}\n\n// |Surface|\nskity::Matrix GPUSurfaceMetalSkity::GetRootTransformation() const {\n  // This backend does not currently support root surface transformations. Just\n  // return identity.\n  return {};\n}\n\n// |Surface|\nskity::GPUContext* GPUSurfaceMetalSkity::GetContext() { return context_.get(); }\n\n// |Surface|\nstd::unique_ptr<GLContextResult> GPUSurfaceMetalSkity::MakeRenderContextCurrent() {\n  // This backend has no such concept.\n  return std::make_unique<GLContextDefaultResult>(true);\n}\n\nbool GPUSurfaceMetalSkity::AllowsDrawingWhenGpuDisabled() const {\n  return delegate_->AllowsDrawingWhenGpuDisabled();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_software.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/gpu/gpu_surface_software.h\"\n\n#include <memory>\n\n#include \"clay/flow/surface_frame.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nGPUSurfaceSoftware::GPUSurfaceSoftware(GPUSurfaceSoftwareDelegate* delegate,\n                                       bool render_to_surface)\n    : delegate_(delegate),\n      render_to_surface_(render_to_surface),\n      weak_factory_(this) {}\n\nGPUSurfaceSoftware::~GPUSurfaceSoftware() = default;\n\n// |Surface|\nbool GPUSurfaceSoftware::IsValid() { return delegate_ != nullptr; }\n\n// |Surface|\nstd::unique_ptr<SurfaceFrame> GPUSurfaceSoftware::AcquireFrame(\n    const skity::Vec2& logical_size) {\n  SurfaceFrame::FramebufferInfo framebuffer_info;\n  framebuffer_info.supports_readback = true;\n\n  // TODO(38466): Refactor GPU surface APIs take into account the fact that an\n  // external view embedder may want to render to the root surface.\n  if (!render_to_surface_) {\n    return std::make_unique<SurfaceFrame>(\n        nullptr, framebuffer_info,\n        [](const SurfaceFrame& surface_frame, clay::GrCanvas* canvas) {\n          return true;\n        },\n        [](const SurfaceFrame::SubmitInfo& surface_frame) { return true; },\n        logical_size);\n  }\n\n  if (!IsValid()) {\n    return nullptr;\n  }\n  const skity::Vec2 size = {logical_size.x, logical_size.y};\n\n  clay::GrSurfacePtr backing_store = delegate_->AcquireBackingStore(size);\n\n  if (backing_store == nullptr) {\n    return nullptr;\n  }\n\n  if (size != skity::Vec2({SURFACE_GET_WIDTH(backing_store),\n                           SURFACE_GET_HEIGHT(backing_store)})) {\n    return nullptr;\n  }\n\n  // If the surface has been scaled, we need to apply the inverse scaling to the\n  // underlying canvas so that coordinates are mapped to the same spot\n  // irrespective of surface scaling.\n  clay::GrCanvas* canvas = SURFACE_GET_CANVAS(backing_store, false);\n  CANVAS_RESET_MATRIX(canvas);\n\n  SurfaceFrame::EncodeCallback encode_callback =\n      [self = weak_factory_.GetWeakPtr()](const SurfaceFrame& surface_frame,\n                                          clay::GrCanvas* canvas) -> bool {\n    // If the surface itself went away, there is nothing more to do.\n    if (!self || !self->IsValid() || canvas == nullptr) {\n      return false;\n    }\n\n    CANVAS_FLUSH(canvas);\n    return self->delegate_->PresentBackingStore(surface_frame.GetSurface());\n  };\n  SurfaceFrame::SubmitCallback submit_callback =\n      [self = weak_factory_.GetWeakPtr()](\n          const SurfaceFrame::SubmitInfo& surface_frame) { return true; };\n\n  return std::make_unique<SurfaceFrame>(backing_store, framebuffer_info,\n                                        encode_callback, submit_callback,\n                                        logical_size);\n}\n\n// |Surface|\nskity::Matrix GPUSurfaceSoftware::GetRootTransformation() const {\n  // This backend does not currently support root surface transformations. Just\n  // return identity.\n  return skity::Matrix();\n}\n\n// |Surface|\nclay::GrContext* GPUSurfaceSoftware::GetContext() {\n  // There is no GrContext associated with a software surface.\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_software.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_GPU_GPU_SURFACE_SOFTWARE_H_\n#define CLAY_SHELL_GPU_GPU_SURFACE_SOFTWARE_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/flow/surface.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/shell/gpu/gpu_surface_software_delegate.h\"\n\nnamespace clay {\n\nclass GPUSurfaceSoftware : public Surface {\n public:\n  GPUSurfaceSoftware(GPUSurfaceSoftwareDelegate* delegate,\n                     bool render_to_surface);\n\n  ~GPUSurfaceSoftware() override;\n\n  // |Surface|\n  bool IsValid() override;\n\n  // |Surface|\n  std::unique_ptr<SurfaceFrame> AcquireFrame(const skity::Vec2& size) override;\n\n  // |Surface|\n  skity::Matrix GetRootTransformation() const override;\n\n  // |Surface|\n  clay::GrContext* GetContext() override;\n\n private:\n  GPUSurfaceSoftwareDelegate* delegate_;\n  // TODO(38466): Refactor GPU surface APIs take into account the fact that an\n  // external view embedder may want to render to the root surface. This is a\n  // hack to make avoid allocating resources for the root surface when an\n  // external view embedder is present.\n  const bool render_to_surface_;\n  fml::WeakPtrFactory<GPUSurfaceSoftware> weak_factory_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(GPUSurfaceSoftware);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_GPU_GPU_SURFACE_SOFTWARE_H_\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_software_delegate.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/gpu/gpu_surface_software_delegate.h\"\n\nnamespace clay {\n\nGPUSurfaceSoftwareDelegate::~GPUSurfaceSoftwareDelegate() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/gpu/gpu_surface_software_delegate.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_GPU_GPU_SURFACE_SOFTWARE_DELEGATE_H_\n#define CLAY_SHELL_GPU_GPU_SURFACE_SOFTWARE_DELEGATE_H_\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// @brief      Interface implemented by all platform surfaces that can present\n///             a software backing store to the \"screen\". The GPU surface\n///             abstraction (which abstracts the client rendering API) uses this\n///             delegation pattern to tell the platform surface (which abstracts\n///             how backing stores fulfilled by the selected client rendering\n///             API end up on the \"screen\" on a particular platform) when the\n///             rasterizer needs to allocate and present the software backing\n///             store.\n///\n/// @see        |IOSSurfaceSoftware|, |AndroidSurfaceSoftware|,\n///             |EmbedderSurfaceSoftware|.\n///\nclass GPUSurfaceSoftwareDelegate {\n public:\n  ~GPUSurfaceSoftwareDelegate();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Called when the GPU surface needs a new buffer to render a new\n  ///             frame into.\n  ///\n  /// @param[in]  size  The size of the frame.\n  ///\n  /// @return     A raster surface returned by the platform.\n  ///\n  virtual clay::GrSurfacePtr AcquireBackingStore(const skity::Vec2& size) = 0;\n\n  //----------------------------------------------------------------------------\n  /// @brief      Called by the platform when a frame has been rendered into the\n  ///             backing store and the platform must display it on-screen.\n  ///\n  /// @param[in]  backing_store  The software backing store to present.\n  ///\n  /// @return     Returns if the platform could present the backing store onto\n  ///             the screen.\n  ///\n  virtual bool PresentBackingStore(clay::GrSurfacePtr backing_store) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_GPU_GPU_SURFACE_SOFTWARE_DELEGATE_H_\n"
  },
  {
    "path": "clay/shell/gpu/trace_gl_fuctions.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/gpu/trace_gl_fuctions.h\"\n\n#include <algorithm>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/gfx/rendering_backend.h\"\n\nnamespace clay {\n\nnamespace {\n\ninline uint64_t CalculateTexSize(GrGLenum target, GrGLenum internalformat,\n                                 GrGLsizei width, GrGLsizei height) {\n  GrBackendFormat format = GrBackendFormat::MakeGL(internalformat, target);\n  return width * height * GrBackendFormatBytesPerPixel(format);\n}\n\n}  // namespace\n\n// static\nvoid TraceGlFuctions::ReplaceFunctions(GrGLInterface* interface) {\n  TraceGlFuctions* trace_gl_fuctions = new TraceGlFuctions(interface);\n\n  interface->fFunctions.fActiveTexture = [trace_gl_fuctions](GrGLenum texture) {\n    trace_gl_fuctions->ActiveTexture(texture);\n  };\n\n  interface->fFunctions.fAttachShader = [trace_gl_fuctions](GrGLuint program,\n                                                            GrGLuint shader) {\n    trace_gl_fuctions->AttachShader(program, shader);\n  };\n\n  interface->fFunctions.fBeginQuery = [trace_gl_fuctions](GrGLenum target,\n                                                          GrGLuint id) {\n    trace_gl_fuctions->BeginQuery(target, id);\n  };\n\n  interface->fFunctions.fBindAttribLocation =\n      [trace_gl_fuctions](GrGLuint program, GrGLuint index, const char* name) {\n        trace_gl_fuctions->BindAttribLocation(program, index, name);\n      };\n\n  interface->fFunctions.fBindBuffer = [trace_gl_fuctions](GrGLenum target,\n                                                          GrGLuint buffer) {\n    trace_gl_fuctions->BindBuffer(target, buffer);\n  };\n\n  interface->fFunctions.fBindFramebuffer =\n      [trace_gl_fuctions](GrGLenum target, GrGLuint framebuffer) {\n        trace_gl_fuctions->BindFramebuffer(target, framebuffer);\n      };\n\n  interface->fFunctions.fBindRenderbuffer =\n      [trace_gl_fuctions](GrGLenum target, GrGLuint renderbuffer) {\n        trace_gl_fuctions->BindRenderbuffer(target, renderbuffer);\n      };\n\n  interface->fFunctions.fBindTexture = [trace_gl_fuctions](GrGLenum target,\n                                                           GrGLuint texture) {\n    trace_gl_fuctions->BindTexture(target, texture);\n  };\n\n  interface->fFunctions.fBindFragDataLocation =\n      [trace_gl_fuctions](GrGLuint program, GrGLuint color_number,\n                          const GrGLchar* name) {\n        trace_gl_fuctions->BindFragDataLocation(program, color_number, name);\n      };\n\n  interface->fFunctions.fBindFragDataLocationIndexed =\n      [trace_gl_fuctions](GrGLuint program, GrGLuint color_number,\n                          GrGLuint index, const GrGLchar* name) {\n        trace_gl_fuctions->BindFragDataLocationIndexed(program, color_number,\n                                                       index, name);\n      };\n\n  interface->fFunctions.fBindSampler = [trace_gl_fuctions](GrGLuint unit,\n                                                           GrGLuint sampler) {\n    trace_gl_fuctions->BindSampler(unit, sampler);\n  };\n\n  interface->fFunctions.fBindVertexArray = [trace_gl_fuctions](GrGLuint array) {\n    trace_gl_fuctions->BindVertexArray(array);\n  };\n\n  interface->fFunctions.fBlendBarrier = [trace_gl_fuctions]() {\n    trace_gl_fuctions->BlendBarrier();\n  };\n\n  interface->fFunctions.fBlendColor = [trace_gl_fuctions](\n                                          GrGLclampf red, GrGLclampf green,\n                                          GrGLclampf blue, GrGLclampf alpha) {\n    trace_gl_fuctions->BlendColor(red, green, blue, alpha);\n  };\n\n  interface->fFunctions.fBlendEquation = [trace_gl_fuctions](GrGLenum mode) {\n    trace_gl_fuctions->BlendEquation(mode);\n  };\n\n  interface->fFunctions.fBlendFunc = [trace_gl_fuctions](GrGLenum sfactor,\n                                                         GrGLenum dfactor) {\n    trace_gl_fuctions->BlendFunc(sfactor, dfactor);\n  };\n\n  interface->fFunctions.fBlitFramebuffer =\n      [trace_gl_fuctions](GrGLint srcX0, GrGLint srcY0, GrGLint srcX1,\n                          GrGLint srcY1, GrGLint dstX0, GrGLint dstY0,\n                          GrGLint dstX1, GrGLint dstY1, GrGLbitfield mask,\n                          GrGLenum filter) {\n        trace_gl_fuctions->BlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0,\n                                           dstY0, dstX1, dstY1, mask, filter);\n      };\n\n  interface->fFunctions.fBufferData =\n      [trace_gl_fuctions](GrGLenum target, GrGLsizeiptr size,\n                          const GrGLvoid* data, GrGLenum usage) {\n        trace_gl_fuctions->BufferData(target, size, data, usage);\n      };\n\n  interface->fFunctions.fBufferSubData =\n      [trace_gl_fuctions](GrGLenum target, GrGLintptr offset, GrGLsizeiptr size,\n                          const GrGLvoid* data) {\n        trace_gl_fuctions->BufferSubData(target, offset, size, data);\n      };\n\n  interface->fFunctions.fCheckFramebufferStatus =\n      [trace_gl_fuctions](GrGLenum target) {\n        return trace_gl_fuctions->CheckFramebufferStatus(target);\n      };\n\n  interface->fFunctions.fClear = [trace_gl_fuctions](GrGLbitfield mask) {\n    trace_gl_fuctions->Clear(mask);\n  };\n\n  interface->fFunctions.fClearColor = [trace_gl_fuctions](\n                                          GrGLclampf red, GrGLclampf green,\n                                          GrGLclampf blue, GrGLclampf alpha) {\n    trace_gl_fuctions->ClearColor(red, green, blue, alpha);\n  };\n\n  interface->fFunctions.fClearStencil = [trace_gl_fuctions](GrGLint s) {\n    trace_gl_fuctions->ClearStencil(s);\n  };\n\n  interface->fFunctions.fClearTexImage =\n      [trace_gl_fuctions](GrGLuint texture, GrGLint level, GrGLenum format,\n                          GrGLenum type, const GrGLvoid* data) {\n        trace_gl_fuctions->ClearTexImage(texture, level, format, type, data);\n      };\n\n  interface->fFunctions.fClearTexSubImage =\n      [trace_gl_fuctions](GrGLuint texture, GrGLint level, GrGLint xoffset,\n                          GrGLint yoffset, GrGLint zoffset, GrGLsizei width,\n                          GrGLsizei height, GrGLsizei depth, GrGLenum format,\n                          GrGLenum type, const GrGLvoid* data) {\n        trace_gl_fuctions->ClearTexSubImage(texture, level, xoffset, yoffset,\n                                            zoffset, width, height, depth,\n                                            format, type, data);\n      };\n\n  interface->fFunctions.fColorMask = [trace_gl_fuctions](\n                                         GrGLboolean red, GrGLboolean green,\n                                         GrGLboolean blue, GrGLboolean alpha) {\n    trace_gl_fuctions->ColorMask(red, green, blue, alpha);\n  };\n\n  interface->fFunctions.fCompileShader = [trace_gl_fuctions](GrGLuint shader) {\n    trace_gl_fuctions->CompileShader(shader);\n  };\n\n  interface->fFunctions.fCompressedTexImage2D =\n      [trace_gl_fuctions](GrGLenum target, GrGLint level,\n                          GrGLenum internalformat, GrGLsizei width,\n                          GrGLsizei height, GrGLint border, GrGLsizei imageSize,\n                          const GrGLvoid* data) {\n        trace_gl_fuctions->CompressedTexImage2D(target, level, internalformat,\n                                                width, height, border,\n                                                imageSize, data);\n      };\n\n  interface->fFunctions.fCompressedTexSubImage2D =\n      [trace_gl_fuctions](GrGLenum target, GrGLint level, GrGLint xoffset,\n                          GrGLint yoffset, GrGLsizei width, GrGLsizei height,\n                          GrGLenum format, GrGLsizei imageSize,\n                          const GrGLvoid* data) {\n        trace_gl_fuctions->CompressedTexSubImage2D(target, level, xoffset,\n                                                   yoffset, width, height,\n                                                   format, imageSize, data);\n      };\n\n  interface->fFunctions.fCopyTexSubImage2D =\n      [trace_gl_fuctions](GrGLenum target, GrGLint level, GrGLint xoffset,\n                          GrGLint yoffset, GrGLint x, GrGLint y,\n                          GrGLsizei width, GrGLsizei height) {\n        trace_gl_fuctions->CopyTexSubImage2D(target, level, xoffset, yoffset, x,\n                                             y, width, height);\n      };\n\n  interface->fFunctions.fCreateProgram = [trace_gl_fuctions]() {\n    return trace_gl_fuctions->CreateProgram();\n  };\n\n  interface->fFunctions.fCreateShader = [trace_gl_fuctions](GrGLenum type) {\n    return trace_gl_fuctions->CreateShader(type);\n  };\n\n  interface->fFunctions.fCullFace = [trace_gl_fuctions](GrGLenum mode) {\n    trace_gl_fuctions->CullFace(mode);\n  };\n\n  interface->fFunctions.fDeleteBuffers =\n      [trace_gl_fuctions](GrGLsizei n, const GrGLuint* buffers) {\n        trace_gl_fuctions->DeleteBuffers(n, buffers);\n      };\n\n  interface->fFunctions.fDeleteFences =\n      [trace_gl_fuctions](GrGLsizei n, const GrGLuint* fences) {\n        trace_gl_fuctions->DeleteFences(n, fences);\n      };\n\n  interface->fFunctions.fDeleteFramebuffers =\n      [trace_gl_fuctions](GrGLsizei n, const GrGLuint* framebuffers) {\n        trace_gl_fuctions->DeleteFramebuffers(n, framebuffers);\n      };\n\n  interface->fFunctions.fDeleteProgram = [trace_gl_fuctions](GrGLuint program) {\n    trace_gl_fuctions->DeleteProgram(program);\n  };\n\n  interface->fFunctions.fDeleteQueries = [trace_gl_fuctions](\n                                             GrGLsizei n, const GrGLuint* ids) {\n    trace_gl_fuctions->DeleteQueries(n, ids);\n  };\n\n  interface->fFunctions.fDeleteRenderbuffers =\n      [trace_gl_fuctions](GrGLsizei n, const GrGLuint* renderbuffers) {\n        trace_gl_fuctions->DeleteRenderbuffers(n, renderbuffers);\n      };\n\n  interface->fFunctions.fDeleteSamplers =\n      [trace_gl_fuctions](GrGLsizei count, const GrGLuint* samplers) {\n        trace_gl_fuctions->DeleteSamplers(count, samplers);\n      };\n\n  interface->fFunctions.fDeleteShader = [trace_gl_fuctions](GrGLuint shader) {\n    trace_gl_fuctions->DeleteShader(shader);\n  };\n\n  interface->fFunctions.fDeleteTextures =\n      [trace_gl_fuctions](GrGLsizei n, const GrGLuint* textures) {\n        trace_gl_fuctions->DeleteTextures(n, textures);\n      };\n\n  interface->fFunctions.fDeleteVertexArrays =\n      [trace_gl_fuctions](GrGLsizei n, const GrGLuint* arrays) {\n        trace_gl_fuctions->DeleteVertexArrays(n, arrays);\n      };\n\n  interface->fFunctions.fDepthMask = [trace_gl_fuctions](GrGLboolean flag) {\n    trace_gl_fuctions->DepthMask(flag);\n  };\n\n  interface->fFunctions.fDisable = [trace_gl_fuctions](GrGLenum cap) {\n    trace_gl_fuctions->Disable(cap);\n  };\n\n  interface->fFunctions.fDisableVertexAttribArray =\n      [trace_gl_fuctions](GrGLuint index) {\n        trace_gl_fuctions->DisableVertexAttribArray(index);\n      };\n\n  interface->fFunctions.fDrawArrays =\n      [trace_gl_fuctions](GrGLenum mode, GrGLint first, GrGLsizei count) {\n        trace_gl_fuctions->DrawArrays(mode, first, count);\n      };\n\n  interface->fFunctions.fDrawArraysInstanced =\n      [trace_gl_fuctions](GrGLenum mode, GrGLint first, GrGLsizei count,\n                          GrGLsizei primcount) {\n        trace_gl_fuctions->DrawArraysInstanced(mode, first, count, primcount);\n      };\n\n  interface->fFunctions.fDrawArraysIndirect =\n      [trace_gl_fuctions](GrGLenum mode, const GrGLvoid* indirect) {\n        trace_gl_fuctions->DrawArraysIndirect(mode, indirect);\n      };\n\n  interface->fFunctions.fDrawBuffer = [trace_gl_fuctions](GrGLenum mode) {\n    trace_gl_fuctions->DrawBuffer(mode);\n  };\n\n  interface->fFunctions.fDrawBuffers = [trace_gl_fuctions](\n                                           GrGLsizei n, const GrGLenum* bufs) {\n    trace_gl_fuctions->DrawBuffers(n, bufs);\n  };\n\n  interface->fFunctions.fDrawElements =\n      [trace_gl_fuctions](GrGLenum mode, GrGLsizei count, GrGLenum type,\n                          const GrGLvoid* indices) {\n        trace_gl_fuctions->DrawElements(mode, count, type, indices);\n      };\n\n  interface->fFunctions.fDrawElementsInstanced =\n      [trace_gl_fuctions](GrGLenum mode, GrGLsizei count, GrGLenum type,\n                          const GrGLvoid* indices, GrGLsizei primcount) {\n        trace_gl_fuctions->DrawElementsInstanced(mode, count, type, indices,\n                                                 primcount);\n      };\n\n  interface->fFunctions.fDrawElementsIndirect =\n      [trace_gl_fuctions](GrGLenum mode, GrGLenum type,\n                          const GrGLvoid* indirect) {\n        trace_gl_fuctions->DrawElementsIndirect(mode, type, indirect);\n      };\n\n  interface->fFunctions.fDrawRangeElements =\n      [trace_gl_fuctions](GrGLenum mode, GrGLuint start, GrGLuint end,\n                          GrGLsizei count, GrGLenum type,\n                          const GrGLvoid* indices) {\n        trace_gl_fuctions->DrawRangeElements(mode, start, end, count, type,\n                                             indices);\n      };\n\n  interface->fFunctions.fEnable = [trace_gl_fuctions](GrGLenum cap) {\n    trace_gl_fuctions->Enable(cap);\n  };\n\n  interface->fFunctions.fEnableVertexAttribArray =\n      [trace_gl_fuctions](GrGLuint index) {\n        trace_gl_fuctions->EnableVertexAttribArray(index);\n      };\n\n  interface->fFunctions.fEndQuery = [trace_gl_fuctions](GrGLenum target) {\n    trace_gl_fuctions->EndQuery(target);\n  };\n\n  interface->fFunctions.fFinish = [trace_gl_fuctions]() {\n    trace_gl_fuctions->Finish();\n  };\n\n  interface->fFunctions.fFinishFence = [trace_gl_fuctions](GrGLuint fence) {\n    trace_gl_fuctions->FinishFence(fence);\n  };\n\n  interface->fFunctions.fFlush = [trace_gl_fuctions]() {\n    trace_gl_fuctions->Flush();\n  };\n\n  interface->fFunctions.fFlushMappedBufferRange =\n      [trace_gl_fuctions](GrGLenum target, GrGLintptr offset,\n                          GrGLsizeiptr length) {\n        trace_gl_fuctions->FlushMappedBufferRange(target, offset, length);\n      };\n\n  interface->fFunctions.fFramebufferRenderbuffer =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum attachment,\n                          GrGLenum renderbuffertarget, GrGLuint renderbuffer) {\n        trace_gl_fuctions->FramebufferRenderbuffer(\n            target, attachment, renderbuffertarget, renderbuffer);\n      };\n\n  interface->fFunctions.fFramebufferTexture2D =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum attachment,\n                          GrGLenum textarget, GrGLuint texture, GrGLint level) {\n        trace_gl_fuctions->FramebufferTexture2D(target, attachment, textarget,\n                                                texture, level);\n      };\n\n  interface->fFunctions.fFramebufferTexture2DMultisample =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum attachment,\n                          GrGLenum textarget, GrGLuint texture, GrGLint level,\n                          GrGLsizei samples) {\n        trace_gl_fuctions->FramebufferTexture2DMultisample(\n            target, attachment, textarget, texture, level, samples);\n      };\n\n  interface->fFunctions.fFrontFace = [trace_gl_fuctions](GrGLenum mode) {\n    trace_gl_fuctions->FrontFace(mode);\n  };\n\n  interface->fFunctions.fGenBuffers = [trace_gl_fuctions](GrGLsizei n,\n                                                          GrGLuint* buffers) {\n    trace_gl_fuctions->GenBuffers(n, buffers);\n  };\n\n  interface->fFunctions.fGenFences = [trace_gl_fuctions](GrGLsizei n,\n                                                         GrGLuint* fences) {\n    trace_gl_fuctions->GenFences(n, fences);\n  };\n\n  interface->fFunctions.fGenFramebuffers =\n      [trace_gl_fuctions](GrGLsizei n, GrGLuint* framebuffers) {\n        trace_gl_fuctions->GenFramebuffers(n, framebuffers);\n      };\n\n  interface->fFunctions.fGenerateMipmap = [trace_gl_fuctions](GrGLenum target) {\n    trace_gl_fuctions->GenerateMipmap(target);\n  };\n\n  interface->fFunctions.fGenQueries = [trace_gl_fuctions](GrGLsizei n,\n                                                          GrGLuint* ids) {\n    trace_gl_fuctions->GenQueries(n, ids);\n  };\n\n  interface->fFunctions.fGenRenderbuffers =\n      [trace_gl_fuctions](GrGLsizei n, GrGLuint* renderbuffers) {\n        trace_gl_fuctions->GenRenderbuffers(n, renderbuffers);\n      };\n\n  interface->fFunctions.fGenSamplers = [trace_gl_fuctions](GrGLsizei count,\n                                                           GrGLuint* samplers) {\n    trace_gl_fuctions->GenSamplers(count, samplers);\n  };\n\n  interface->fFunctions.fGenTextures = [trace_gl_fuctions](GrGLsizei n,\n                                                           GrGLuint* textures) {\n    trace_gl_fuctions->GenTextures(n, textures);\n  };\n\n  interface->fFunctions.fGenVertexArrays = [trace_gl_fuctions](\n                                               GrGLsizei n, GrGLuint* arrays) {\n    trace_gl_fuctions->GenVertexArrays(n, arrays);\n  };\n\n  interface->fFunctions.fGetBufferParameteriv =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum pname, GrGLint* params) {\n        trace_gl_fuctions->GetBufferParameteriv(target, pname, params);\n      };\n\n  interface->fFunctions.fGetError = [trace_gl_fuctions]() {\n    return trace_gl_fuctions->GetError();\n  };\n\n  interface->fFunctions.fGetFramebufferAttachmentParameteriv =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum attachment, GrGLenum pname,\n                          GrGLint* params) {\n        trace_gl_fuctions->GetFramebufferAttachmentParameteriv(\n            target, attachment, pname, params);\n      };\n\n  interface->fFunctions.fGetIntegerv = [trace_gl_fuctions](GrGLenum pname,\n                                                           GrGLint* params) {\n    trace_gl_fuctions->GetIntegerv(pname, params);\n  };\n\n  interface->fFunctions.fGetMultisamplefv =\n      [trace_gl_fuctions](GrGLenum pname, GrGLuint index, GrGLfloat* val) {\n        trace_gl_fuctions->GetMultisamplefv(pname, index, val);\n      };\n\n  interface->fFunctions.fGetProgramBinary =\n      [trace_gl_fuctions](GrGLuint program, GrGLsizei bufsize,\n                          GrGLsizei* length, GrGLenum* binaryFormat,\n                          void* binary) {\n        trace_gl_fuctions->GetProgramBinary(program, bufsize, length,\n                                            binaryFormat, binary);\n      };\n\n  interface->fFunctions.fGetProgramInfoLog =\n      [trace_gl_fuctions](GrGLuint program, GrGLsizei bufsize,\n                          GrGLsizei* length, char* infolog) {\n        trace_gl_fuctions->GetProgramInfoLog(program, bufsize, length, infolog);\n      };\n\n  interface->fFunctions.fGetProgramiv =\n      [trace_gl_fuctions](GrGLuint program, GrGLenum pname, GrGLint* params) {\n        trace_gl_fuctions->GetProgramiv(program, pname, params);\n      };\n\n  interface->fFunctions.fGetQueryiv =\n      [trace_gl_fuctions](GrGLenum GLtarget, GrGLenum pname, GrGLint* params) {\n        trace_gl_fuctions->GetQueryiv(GLtarget, pname, params);\n      };\n\n  interface->fFunctions.fGetQueryObjecti64v =\n      [trace_gl_fuctions](GrGLuint id, GrGLenum pname, GrGLint64* params) {\n        trace_gl_fuctions->GetQueryObjecti64v(id, pname, params);\n      };\n\n  interface->fFunctions.fGetQueryObjectiv =\n      [trace_gl_fuctions](GrGLuint id, GrGLenum pname, GrGLint* params) {\n        trace_gl_fuctions->GetQueryObjectiv(id, pname, params);\n      };\n\n  interface->fFunctions.fGetQueryObjectui64v =\n      [trace_gl_fuctions](GrGLuint id, GrGLenum pname, GrGLuint64* params) {\n        trace_gl_fuctions->GetQueryObjectui64v(id, pname, params);\n      };\n\n  interface->fFunctions.fGetQueryObjectuiv =\n      [trace_gl_fuctions](GrGLuint id, GrGLenum pname, GrGLuint* params) {\n        trace_gl_fuctions->GetQueryObjectuiv(id, pname, params);\n      };\n\n  interface->fFunctions.fGetRenderbufferParameteriv =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum pname, GrGLint* params) {\n        trace_gl_fuctions->GetRenderbufferParameteriv(target, pname, params);\n      };\n\n  interface->fFunctions.fGetShaderInfoLog =\n      [trace_gl_fuctions](GrGLuint shader, GrGLsizei bufsize, GrGLsizei* length,\n                          char* infolog) {\n        trace_gl_fuctions->GetShaderInfoLog(shader, bufsize, length, infolog);\n      };\n\n  interface->fFunctions.fGetShaderiv =\n      [trace_gl_fuctions](GrGLuint shader, GrGLenum pname, GrGLint* params) {\n        trace_gl_fuctions->GetShaderiv(shader, pname, params);\n      };\n\n  interface->fFunctions.fGetShaderPrecisionFormat =\n      [trace_gl_fuctions](GrGLenum shadertype, GrGLenum precisiontype,\n                          GrGLint* range, GrGLint* precision) {\n        trace_gl_fuctions->GetShaderPrecisionFormat(shadertype, precisiontype,\n                                                    range, precision);\n      };\n\n  interface->fFunctions.fGetString = [trace_gl_fuctions](GrGLenum name) {\n    return trace_gl_fuctions->GetString(name);\n  };\n\n  interface->fFunctions.fGetStringi = [trace_gl_fuctions](GrGLenum name,\n                                                          GrGLuint index) {\n    return trace_gl_fuctions->GetStringi(name, index);\n  };\n\n  interface->fFunctions.fGetTexLevelParameteriv =\n      [trace_gl_fuctions](GrGLenum target, GrGLint level, GrGLenum pname,\n                          GrGLint* params) {\n        trace_gl_fuctions->GetTexLevelParameteriv(target, level, pname, params);\n      };\n\n  interface->fFunctions.fGetUniformLocation =\n      [trace_gl_fuctions](GrGLuint program, const char* name) {\n        return trace_gl_fuctions->GetUniformLocation(program, name);\n      };\n\n  interface->fFunctions.fInsertEventMarker =\n      [trace_gl_fuctions](GrGLsizei length, const char* marker) {\n        trace_gl_fuctions->InsertEventMarker(length, marker);\n      };\n\n  interface->fFunctions.fInvalidateBufferData =\n      [trace_gl_fuctions](GrGLuint buffer) {\n        trace_gl_fuctions->InvalidateBufferData(buffer);\n      };\n\n  interface->fFunctions.fInvalidateBufferSubData =\n      [trace_gl_fuctions](GrGLuint buffer, GrGLintptr offset,\n                          GrGLsizeiptr length) {\n        trace_gl_fuctions->InvalidateBufferSubData(buffer, offset, length);\n      };\n\n  interface->fFunctions.fInvalidateFramebuffer =\n      [trace_gl_fuctions](GrGLenum target, GrGLsizei numAttachments,\n                          const GrGLenum* attachments) {\n        trace_gl_fuctions->InvalidateFramebuffer(target, numAttachments,\n                                                 attachments);\n      };\n\n  interface->fFunctions.fInvalidateSubFramebuffer =\n      [trace_gl_fuctions](GrGLenum target, GrGLsizei numAttachments,\n                          const GrGLenum* attachments, GrGLint x, GrGLint y,\n                          GrGLsizei width, GrGLsizei height) {\n        trace_gl_fuctions->InvalidateSubFramebuffer(\n            target, numAttachments, attachments, x, y, width, height);\n      };\n\n  interface->fFunctions.fInvalidateTexImage =\n      [trace_gl_fuctions](GrGLuint texture, GrGLint level) {\n        trace_gl_fuctions->InvalidateTexImage(texture, level);\n      };\n\n  interface->fFunctions.fInvalidateTexSubImage =\n      [trace_gl_fuctions](GrGLuint texture, GrGLint level, GrGLint xoffset,\n                          GrGLint yoffset, GrGLint zoffset, GrGLsizei width,\n                          GrGLsizei height, GrGLsizei depth) {\n        trace_gl_fuctions->InvalidateTexSubImage(\n            texture, level, xoffset, yoffset, zoffset, width, height, depth);\n      };\n\n  interface->fFunctions.fIsTexture = [trace_gl_fuctions](GrGLuint texture) {\n    return trace_gl_fuctions->IsTexture(texture);\n  };\n\n  interface->fFunctions.fLineWidth = [trace_gl_fuctions](GrGLfloat width) {\n    trace_gl_fuctions->LineWidth(width);\n  };\n\n  interface->fFunctions.fLinkProgram = [trace_gl_fuctions](GrGLuint program) {\n    trace_gl_fuctions->LinkProgram(program);\n  };\n\n  interface->fFunctions.fMapBuffer = [trace_gl_fuctions](GrGLenum target,\n                                                         GrGLenum access) {\n    return trace_gl_fuctions->MapBuffer(target, access);\n  };\n\n  interface->fFunctions.fMapBufferRange =\n      [trace_gl_fuctions](GrGLenum target, GrGLintptr offset,\n                          GrGLsizeiptr length, GrGLbitfield access) {\n        return trace_gl_fuctions->MapBufferRange(target, offset, length,\n                                                 access);\n      };\n\n  interface->fFunctions.fMapBufferSubData =\n      [trace_gl_fuctions](GrGLuint target, GrGLintptr offset, GrGLsizeiptr size,\n                          GrGLenum access) {\n        return trace_gl_fuctions->MapBufferSubData(target, offset, size,\n                                                   access);\n      };\n\n  interface->fFunctions.fMapTexSubImage2D =\n      [trace_gl_fuctions](GrGLenum target, GrGLint level, GrGLint xoffset,\n                          GrGLint yoffset, GrGLsizei width, GrGLsizei height,\n                          GrGLenum format, GrGLenum type, GrGLenum access) {\n        return trace_gl_fuctions->MapTexSubImage2D(target, level, xoffset,\n                                                   yoffset, width, height,\n                                                   format, type, access);\n      };\n\n  interface->fFunctions.fMemoryBarrier =\n      [trace_gl_fuctions](GrGLbitfield barriers) {\n        return trace_gl_fuctions->MemoryBarrier(barriers);\n      };\n\n  interface->fFunctions.fPatchParameteri = [trace_gl_fuctions](GrGLenum pname,\n                                                               GrGLint value) {\n    trace_gl_fuctions->PatchParameteri(pname, value);\n  };\n\n  interface->fFunctions.fPixelStorei = [trace_gl_fuctions](GrGLenum pname,\n                                                           GrGLint param) {\n    trace_gl_fuctions->PixelStorei(pname, param);\n  };\n\n  interface->fFunctions.fPolygonMode = [trace_gl_fuctions](GrGLenum face,\n                                                           GrGLenum mode) {\n    trace_gl_fuctions->PolygonMode(face, mode);\n  };\n\n  interface->fFunctions.fPopGroupMarker = [trace_gl_fuctions]() {\n    trace_gl_fuctions->PopGroupMarker();\n  };\n\n  interface->fFunctions.fProgramBinary =\n      [trace_gl_fuctions](GrGLuint program, GrGLenum binaryFormat, void* binary,\n                          GrGLsizei length) {\n        trace_gl_fuctions->ProgramBinary(program, binaryFormat, binary, length);\n      };\n\n  interface->fFunctions.fProgramParameteri =\n      [trace_gl_fuctions](GrGLuint program, GrGLenum pname, GrGLint value) {\n        trace_gl_fuctions->ProgramParameteri(program, pname, value);\n      };\n\n  interface->fFunctions.fPushGroupMarker =\n      [trace_gl_fuctions](GrGLsizei length, const char* marker) {\n        trace_gl_fuctions->PushGroupMarker(length, marker);\n      };\n\n  interface->fFunctions.fQueryCounter = [trace_gl_fuctions](GrGLuint id,\n                                                            GrGLenum target) {\n    trace_gl_fuctions->QueryCounter(id, target);\n  };\n\n  interface->fFunctions.fReadBuffer = [trace_gl_fuctions](GrGLenum src) {\n    trace_gl_fuctions->ReadBuffer(src);\n  };\n\n  interface->fFunctions.fReadPixels = [trace_gl_fuctions](\n                                          GrGLint x, GrGLint y, GrGLsizei width,\n                                          GrGLsizei height, GrGLenum format,\n                                          GrGLenum type, GrGLvoid* pixels) {\n    trace_gl_fuctions->ReadPixels(x, y, width, height, format, type, pixels);\n  };\n\n  interface->fFunctions.fRenderbufferStorage =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum internalformat,\n                          GrGLsizei width, GrGLsizei height) {\n        trace_gl_fuctions->RenderbufferStorage(target, internalformat, width,\n                                               height);\n      };\n\n  interface->fFunctions.fRenderbufferStorageMultisample =\n      [trace_gl_fuctions](GrGLenum target, GrGLsizei samples,\n                          GrGLenum internalformat, GrGLsizei width,\n                          GrGLsizei height) {\n        trace_gl_fuctions->RenderbufferStorageMultisample(\n            target, samples, internalformat, width, height);\n      };\n\n  interface->fFunctions.fResolveMultisampleFramebuffer = [trace_gl_fuctions]() {\n    trace_gl_fuctions->ResolveMultisampleFramebuffer();\n  };\n\n  interface->fFunctions.fSamplerParameteri =\n      [trace_gl_fuctions](GrGLuint sampler, GrGLenum pname, GrGLint params) {\n        trace_gl_fuctions->SamplerParameteri(sampler, pname, params);\n      };\n\n  interface->fFunctions.fSamplerParameteriv =\n      [trace_gl_fuctions](GrGLuint sampler, GrGLenum pname,\n                          const GrGLint* params) {\n        trace_gl_fuctions->SamplerParameteriv(sampler, pname, params);\n      };\n\n  interface->fFunctions.fScissor = [trace_gl_fuctions](GrGLint x, GrGLint y,\n                                                       GrGLsizei width,\n                                                       GrGLsizei height) {\n    trace_gl_fuctions->Scissor(x, y, width, height);\n  };\n\n  // GL_CHROMIUM_bind_uniform_location\n  interface->fFunctions.fBindUniformLocation =\n      [trace_gl_fuctions](GrGLuint program, GrGLint location,\n                          const char* name) {\n        trace_gl_fuctions->BindUniformLocation(program, location, name);\n      };\n\n  interface->fFunctions.fSetFence = [trace_gl_fuctions](GrGLuint fence,\n                                                        GrGLenum condition) {\n    trace_gl_fuctions->SetFence(fence, condition);\n  };\n\n  interface->fFunctions.fShaderSource =\n      [trace_gl_fuctions](GrGLuint shader, GrGLsizei count,\n                          const char* const* str, const GrGLint* length) {\n        trace_gl_fuctions->ShaderSource(shader, count, str, length);\n      };\n\n  interface->fFunctions.fStencilFunc =\n      [trace_gl_fuctions](GrGLenum func, GrGLint ref, GrGLuint mask) {\n        trace_gl_fuctions->StencilFunc(func, ref, mask);\n      };\n\n  interface->fFunctions.fStencilFuncSeparate = [trace_gl_fuctions](\n                                                   GrGLenum face, GrGLenum func,\n                                                   GrGLint ref, GrGLuint mask) {\n    trace_gl_fuctions->StencilFuncSeparate(face, func, ref, mask);\n  };\n\n  interface->fFunctions.fStencilMask = [trace_gl_fuctions](GrGLuint mask) {\n    trace_gl_fuctions->StencilMask(mask);\n  };\n\n  interface->fFunctions.fStencilMaskSeparate =\n      [trace_gl_fuctions](GrGLenum face, GrGLuint mask) {\n        trace_gl_fuctions->StencilMaskSeparate(face, mask);\n      };\n\n  interface->fFunctions.fStencilOp =\n      [trace_gl_fuctions](GrGLenum fail, GrGLenum zfail, GrGLenum zpass) {\n        trace_gl_fuctions->StencilOp(fail, zfail, zpass);\n      };\n\n  interface->fFunctions.fStencilOpSeparate =\n      [trace_gl_fuctions](GrGLenum face, GrGLenum fail, GrGLenum zfail,\n                          GrGLenum zpass) {\n        trace_gl_fuctions->StencilOpSeparate(face, fail, zfail, zpass);\n      };\n\n  interface->fFunctions.fTexBuffer =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum internalformat,\n                          GrGLuint buffer) {\n        trace_gl_fuctions->TexBuffer(target, internalformat, buffer);\n      };\n\n  interface->fFunctions.fTexBufferRange =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum internalformat,\n                          GrGLuint buffer, GrGLintptr offset,\n                          GrGLsizeiptr size) {\n        trace_gl_fuctions->TexBufferRange(target, internalformat, buffer,\n                                          offset, size);\n      };\n\n  interface->fFunctions.fTexImage2D =\n      [trace_gl_fuctions](GrGLenum target, GrGLint level,\n                          GrGLint internalformat, GrGLsizei width,\n                          GrGLsizei height, GrGLint border, GrGLenum format,\n                          GrGLenum type, const GrGLvoid* pixels) {\n        trace_gl_fuctions->TexImage2D(target, level, internalformat, width,\n                                      height, border, format, type, pixels);\n      };\n\n  interface->fFunctions.fTexParameterf =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum pname, GrGLfloat param) {\n        trace_gl_fuctions->TexParameterf(target, pname, param);\n      };\n\n  interface->fFunctions.fTexParameterfv = [trace_gl_fuctions](\n                                              GrGLenum target, GrGLenum pname,\n                                              const GrGLfloat* params) {\n    trace_gl_fuctions->TexParameterfv(target, pname, params);\n  };\n\n  interface->fFunctions.fTexParameteri =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum pname, GrGLint param) {\n        trace_gl_fuctions->TexParameteri(target, pname, param);\n      };\n\n  interface->fFunctions.fTexParameteriv = [trace_gl_fuctions](\n                                              GrGLenum target, GrGLenum pname,\n                                              const GrGLint* params) {\n    trace_gl_fuctions->TexParameteriv(target, pname, params);\n  };\n\n  interface->fFunctions.fTexStorage2D = [trace_gl_fuctions](\n                                            GrGLenum target, GrGLsizei levels,\n                                            GrGLenum internalformat,\n                                            GrGLsizei width, GrGLsizei height) {\n    trace_gl_fuctions->TexStorage2D(target, levels, internalformat, width,\n                                    height);\n  };\n\n  interface->fFunctions.fDiscardFramebuffer = [trace_gl_fuctions](\n                                                  GrGLenum target,\n                                                  GrGLsizei numAttachments,\n                                                  const GrGLenum* attachments) {\n    trace_gl_fuctions->DiscardFramebuffer(target, numAttachments, attachments);\n  };\n\n  interface->fFunctions.fTestFence = [trace_gl_fuctions](GrGLuint fence) {\n    return trace_gl_fuctions->TestFence(fence);\n  };\n\n  interface->fFunctions.fTexSubImage2D =\n      [trace_gl_fuctions](GrGLenum target, GrGLint level, GrGLint xoffset,\n                          GrGLint yoffset, GrGLsizei width, GrGLsizei height,\n                          GrGLenum format, GrGLenum type,\n                          const GrGLvoid* pixels) {\n        trace_gl_fuctions->TexSubImage2D(target, level, xoffset, yoffset, width,\n                                         height, format, type, pixels);\n      };\n\n  interface->fFunctions.fTextureBarrier = [trace_gl_fuctions]() {\n    trace_gl_fuctions->TextureBarrier();\n  };\n\n  interface->fFunctions.fUniform1f = [trace_gl_fuctions](GrGLint location,\n                                                         GrGLfloat v0) {\n    trace_gl_fuctions->Uniform1f(location, v0);\n  };\n\n  interface->fFunctions.fUniform1i = [trace_gl_fuctions](GrGLint location,\n                                                         GrGLint v0) {\n    trace_gl_fuctions->Uniform1i(location, v0);\n  };\n\n  interface->fFunctions.fUniform1fv = [trace_gl_fuctions](GrGLint location,\n                                                          GrGLsizei count,\n                                                          const GrGLfloat* v) {\n    trace_gl_fuctions->Uniform1fv(location, count, v);\n  };\n\n  interface->fFunctions.fUniform1iv =\n      [trace_gl_fuctions](GrGLint location, GrGLsizei count, const GrGLint* v) {\n        trace_gl_fuctions->Uniform1iv(location, count, v);\n      };\n\n  interface->fFunctions.fUniform2f =\n      [trace_gl_fuctions](GrGLint location, GrGLfloat v0, GrGLfloat v1) {\n        trace_gl_fuctions->Uniform2f(location, v0, v1);\n      };\n\n  interface->fFunctions.fUniform2i =\n      [trace_gl_fuctions](GrGLint location, GrGLint v0, GrGLint v1) {\n        trace_gl_fuctions->Uniform2i(location, v0, v1);\n      };\n\n  interface->fFunctions.fUniform2fv = [trace_gl_fuctions](GrGLint location,\n                                                          GrGLsizei count,\n                                                          const GrGLfloat* v) {\n    trace_gl_fuctions->Uniform2fv(location, count, v);\n  };\n\n  interface->fFunctions.fUniform2iv =\n      [trace_gl_fuctions](GrGLint location, GrGLsizei count, const GrGLint* v) {\n        trace_gl_fuctions->Uniform2iv(location, count, v);\n      };\n\n  interface->fFunctions.fUniform3f = [trace_gl_fuctions](\n                                         GrGLint location, GrGLfloat v0,\n                                         GrGLfloat v1, GrGLfloat v2) {\n    trace_gl_fuctions->Uniform3f(location, v0, v1, v2);\n  };\n\n  interface->fFunctions.fUniform3i = [trace_gl_fuctions](GrGLint location,\n                                                         GrGLint v0, GrGLint v1,\n                                                         GrGLint v2) {\n    trace_gl_fuctions->Uniform3i(location, v0, v1, v2);\n  };\n\n  interface->fFunctions.fUniform3fv = [trace_gl_fuctions](GrGLint location,\n                                                          GrGLsizei count,\n                                                          const GrGLfloat* v) {\n    trace_gl_fuctions->Uniform3fv(location, count, v);\n  };\n\n  interface->fFunctions.fUniform3iv =\n      [trace_gl_fuctions](GrGLint location, GrGLsizei count, const GrGLint* v) {\n        trace_gl_fuctions->Uniform3iv(location, count, v);\n      };\n\n  interface->fFunctions.fUniform4f =\n      [trace_gl_fuctions](GrGLint location, GrGLfloat v0, GrGLfloat v1,\n                          GrGLfloat v2, GrGLfloat v3) {\n        trace_gl_fuctions->Uniform4f(location, v0, v1, v2, v3);\n      };\n\n  interface->fFunctions.fUniform4i = [trace_gl_fuctions](\n                                         GrGLint location, GrGLint v0,\n                                         GrGLint v1, GrGLint v2, GrGLint v3) {\n    trace_gl_fuctions->Uniform4i(location, v0, v1, v2, v3);\n  };\n\n  interface->fFunctions.fUniform4fv = [trace_gl_fuctions](GrGLint location,\n                                                          GrGLsizei count,\n                                                          const GrGLfloat* v) {\n    trace_gl_fuctions->Uniform4fv(location, count, v);\n  };\n\n  interface->fFunctions.fUniform4iv =\n      [trace_gl_fuctions](GrGLint location, GrGLsizei count, const GrGLint* v) {\n        trace_gl_fuctions->Uniform4iv(location, count, v);\n      };\n\n  interface->fFunctions.fUniformMatrix2fv =\n      [trace_gl_fuctions](GrGLint location, GrGLsizei count,\n                          GrGLboolean transpose, const GrGLfloat* value) {\n        trace_gl_fuctions->UniformMatrix2fv(location, count, transpose, value);\n      };\n\n  interface->fFunctions.fUniformMatrix3fv =\n      [trace_gl_fuctions](GrGLint location, GrGLsizei count,\n                          GrGLboolean transpose, const GrGLfloat* value) {\n        trace_gl_fuctions->UniformMatrix3fv(location, count, transpose, value);\n      };\n\n  interface->fFunctions.fUniformMatrix4fv =\n      [trace_gl_fuctions](GrGLint location, GrGLsizei count,\n                          GrGLboolean transpose, const GrGLfloat* value) {\n        trace_gl_fuctions->UniformMatrix4fv(location, count, transpose, value);\n      };\n\n  interface->fFunctions.fUnmapBuffer = [trace_gl_fuctions](GrGLenum target) {\n    return trace_gl_fuctions->UnmapBuffer(target);\n  };\n\n  interface->fFunctions.fUnmapBufferSubData =\n      [trace_gl_fuctions](const GrGLvoid* mem) {\n        trace_gl_fuctions->UnmapBufferSubData(mem);\n      };\n\n  interface->fFunctions.fUnmapTexSubImage2D =\n      [trace_gl_fuctions](const GrGLvoid* mem) {\n        trace_gl_fuctions->UnmapTexSubImage2D(mem);\n      };\n\n  interface->fFunctions.fUseProgram = [trace_gl_fuctions](GrGLuint program) {\n    trace_gl_fuctions->UseProgram(program);\n  };\n\n  interface->fFunctions.fVertexAttrib1f =\n      [trace_gl_fuctions](GrGLuint indx, const GrGLfloat value) {\n        trace_gl_fuctions->VertexAttrib1f(indx, value);\n      };\n\n  interface->fFunctions.fVertexAttrib2fv =\n      [trace_gl_fuctions](GrGLuint indx, const GrGLfloat* values) {\n        trace_gl_fuctions->VertexAttrib2fv(indx, values);\n      };\n\n  interface->fFunctions.fVertexAttrib3fv =\n      [trace_gl_fuctions](GrGLuint indx, const GrGLfloat* values) {\n        trace_gl_fuctions->VertexAttrib3fv(indx, values);\n      };\n\n  interface->fFunctions.fVertexAttrib4fv =\n      [trace_gl_fuctions](GrGLuint indx, const GrGLfloat* values) {\n        trace_gl_fuctions->VertexAttrib4fv(indx, values);\n      };\n\n  interface->fFunctions.fVertexAttribDivisor =\n      [trace_gl_fuctions](GrGLuint index, GrGLuint divisor) {\n        trace_gl_fuctions->VertexAttribDivisor(index, divisor);\n      };\n\n  interface->fFunctions.fVertexAttribIPointer =\n      [trace_gl_fuctions](GrGLuint indx, GrGLint size, GrGLenum type,\n                          GrGLsizei stride, const GrGLvoid* ptr) {\n        trace_gl_fuctions->VertexAttribIPointer(indx, size, type, stride, ptr);\n      };\n\n  interface->fFunctions.fVertexAttribPointer =\n      [trace_gl_fuctions](GrGLuint indx, GrGLint size, GrGLenum type,\n                          GrGLboolean normalized, GrGLsizei stride,\n                          const GrGLvoid* ptr) {\n        trace_gl_fuctions->VertexAttribPointer(indx, size, type, normalized,\n                                               stride, ptr);\n      };\n\n  interface->fFunctions.fViewport = [trace_gl_fuctions](GrGLint x, GrGLint y,\n                                                        GrGLsizei width,\n                                                        GrGLsizei height) {\n    trace_gl_fuctions->Viewport(x, y, width, height);\n  };\n\n  /* EXT_base_instance */\n  interface->fFunctions.fDrawArraysInstancedBaseInstance =\n      [trace_gl_fuctions](GrGLenum mode, GrGLint first, GrGLsizei count,\n                          GrGLsizei instancecount, GrGLuint baseinstance) {\n        trace_gl_fuctions->DrawArraysInstancedBaseInstance(\n            mode, first, count, instancecount, baseinstance);\n      };\n\n  interface->fFunctions.fDrawElementsInstancedBaseVertexBaseInstance =\n      [trace_gl_fuctions](GrGLenum mode, GrGLsizei count, GrGLenum type,\n                          const void* indices, GrGLsizei instancecount,\n                          GrGLint basevertex, GrGLuint baseinstance) {\n        trace_gl_fuctions->DrawElementsInstancedBaseVertexBaseInstance(\n            mode, count, type, indices, instancecount, basevertex,\n            baseinstance);\n      };\n\n  /* EXT_multi_draw_indirect */\n  interface->fFunctions.fMultiDrawArraysIndirect =\n      [trace_gl_fuctions](GrGLenum mode, const GrGLvoid* indirect,\n                          GrGLsizei drawcount, GrGLsizei stride) {\n        trace_gl_fuctions->MultiDrawArraysIndirect(mode, indirect, drawcount,\n                                                   stride);\n      };\n\n  interface->fFunctions.fMultiDrawElementsIndirect =\n      [trace_gl_fuctions](GrGLenum mode, GrGLenum type,\n                          const GrGLvoid* indirect, GrGLsizei drawcount,\n                          GrGLsizei stride) {\n        trace_gl_fuctions->MultiDrawElementsIndirect(mode, type, indirect,\n                                                     drawcount, stride);\n      };\n\n  /* ANGLE_base_vertex_base_instance */\n  interface->fFunctions.fMultiDrawArraysInstancedBaseInstance =\n      [trace_gl_fuctions](\n          GrGLenum mode, const GrGLint* firsts, const GrGLsizei* counts,\n          const GrGLsizei* instanceCounts, const GrGLuint* baseInstances,\n          const GrGLsizei drawcount) {\n        trace_gl_fuctions->MultiDrawArraysInstancedBaseInstance(\n            mode, firsts, counts, instanceCounts, baseInstances, drawcount);\n      };\n\n  interface->fFunctions.fMultiDrawElementsInstancedBaseVertexBaseInstance =\n      [trace_gl_fuctions](\n          GrGLenum mode, const GrGLint* counts, GrGLenum type,\n          const GrGLvoid* const* indices, const GrGLsizei* instanceCounts,\n          const GrGLint* baseVertices, const GrGLuint* baseInstances,\n          const GrGLsizei drawcount) {\n        trace_gl_fuctions->MultiDrawElementsInstancedBaseVertexBaseInstance(\n            mode, counts, type, indices, instanceCounts, baseVertices,\n            baseInstances, drawcount);\n      };\n\n  /* ARB_sync */\n  interface->fFunctions.fFenceSync = [trace_gl_fuctions](GrGLenum condition,\n                                                         GrGLbitfield flags) {\n    return trace_gl_fuctions->FenceSync(condition, flags);\n  };\n\n  interface->fFunctions.fIsSync = [trace_gl_fuctions](GrGLsync sync) {\n    return trace_gl_fuctions->IsSync(sync);\n  };\n\n  interface->fFunctions.fClientWaitSync = [trace_gl_fuctions](\n                                              GrGLsync sync, GrGLbitfield flags,\n                                              GrGLuint64 timeout) {\n    return trace_gl_fuctions->ClientWaitSync(sync, flags, timeout);\n  };\n\n  interface->fFunctions.fWaitSync = [trace_gl_fuctions](GrGLsync sync,\n                                                        GrGLbitfield flags,\n                                                        GrGLuint64 timeout) {\n    trace_gl_fuctions->WaitSync(sync, flags, timeout);\n  };\n\n  interface->fFunctions.fDeleteSync = [trace_gl_fuctions](GrGLsync sync) {\n    trace_gl_fuctions->DeleteSync(sync);\n  };\n\n  /* ARB_internalformat_query */\n  interface->fFunctions.fGetInternalformativ =\n      [trace_gl_fuctions](GrGLenum target, GrGLenum internalformat,\n                          GrGLenum pname, GrGLsizei bufSize, GrGLint* params) {\n        trace_gl_fuctions->GetInternalformativ(target, internalformat, pname,\n                                               bufSize, params);\n      };\n\n  /* KHR_debug */\n  interface->fFunctions.fDebugMessageControl =\n      [trace_gl_fuctions](GrGLenum source, GrGLenum type, GrGLenum severity,\n                          GrGLsizei count, const GrGLuint* ids,\n                          GrGLboolean enabled) {\n        trace_gl_fuctions->DebugMessageControl(source, type, severity, count,\n                                               ids, enabled);\n      };\n\n  interface->fFunctions.fDebugMessageInsert =\n      [trace_gl_fuctions](GrGLenum source, GrGLenum type, GrGLuint id,\n                          GrGLenum severity, GrGLsizei length,\n                          const GrGLchar* buf) {\n        trace_gl_fuctions->DebugMessageInsert(source, type, id, severity,\n                                              length, buf);\n      };\n\n  interface->fFunctions.fDebugMessageCallback =\n      [trace_gl_fuctions](GRGLDEBUGPROC callback, const GrGLvoid* userParam) {\n        trace_gl_fuctions->DebugMessageCallback(callback, userParam);\n      };\n\n  interface->fFunctions.fGetDebugMessageLog =\n      [trace_gl_fuctions](GrGLuint count, GrGLsizei bufSize, GrGLenum* sources,\n                          GrGLenum* types, GrGLuint* ids, GrGLenum* severities,\n                          GrGLsizei* lengths, GrGLchar* messageLog) {\n        return trace_gl_fuctions->GetDebugMessageLog(count, bufSize, sources,\n                                                     types, ids, severities,\n                                                     lengths, messageLog);\n      };\n\n  interface->fFunctions.fPushDebugGroup =\n      [trace_gl_fuctions](GrGLenum source, GrGLuint id, GrGLsizei length,\n                          const GrGLchar* message) {\n        trace_gl_fuctions->PushDebugGroup(source, id, length, message);\n      };\n\n  interface->fFunctions.fPopDebugGroup = [trace_gl_fuctions]() {\n    trace_gl_fuctions->PopDebugGroup();\n  };\n\n  interface->fFunctions.fObjectLabel =\n      [trace_gl_fuctions](GrGLenum identifier, GrGLuint name, GrGLsizei length,\n                          const GrGLchar* label) {\n        trace_gl_fuctions->ObjectLabel(identifier, name, length, label);\n      };\n\n  /** EXT_window_rectangles */\n  interface->fFunctions.fWindowRectangles =\n      [trace_gl_fuctions](GrGLenum mode, GrGLsizei count, const GrGLint box[]) {\n        trace_gl_fuctions->WindowRectangles(mode, count, box);\n      };\n\n  /** GL_QCOM_tiled_rendering */\n  interface->fFunctions.fStartTiling =\n      [trace_gl_fuctions](GrGLuint x, GrGLuint y, GrGLuint width,\n                          GrGLuint height, GrGLbitfield preserveMask) {\n        trace_gl_fuctions->StartTiling(x, y, width, height, preserveMask);\n      };\n\n  interface->fFunctions.fEndTiling =\n      [trace_gl_fuctions](GrGLbitfield preserveMask) {\n        trace_gl_fuctions->EndTiling(preserveMask);\n      };\n}  // NOLINT\n\nTraceGlFuctions::TraceGlFuctions(GrGLInterface* interface)\n    : real_functions_(interface->fFunctions),\n      fbo_changed_(0),\n      current_texture_size_(0),\n      total_tex_upload_size_(0) {}\n\nGrGLvoid TraceGlFuctions::ActiveTexture(GrGLenum texture) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fActiveTexture(texture);\n}\n\nGrGLvoid TraceGlFuctions::AttachShader(GrGLuint program, GrGLuint shader) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fAttachShader(program, shader);\n}\n\nGrGLvoid TraceGlFuctions::BeginQuery(GrGLenum target, GrGLuint id) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBeginQuery(target, id);\n}\n\nGrGLvoid TraceGlFuctions::BindAttribLocation(GrGLuint program, GrGLuint index,\n                                             const char* name) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBindAttribLocation(program, index, name);\n}\n\nGrGLvoid TraceGlFuctions::BindBuffer(GrGLenum target, GrGLuint buffer) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBindBuffer(target, buffer);\n}\n\nGrGLvoid TraceGlFuctions::BindFramebuffer(GrGLenum target,\n                                          GrGLuint framebuffer) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  TRACE_COUNTER(\"gpu\", __FUNCTION__, ++fbo_changed_);\n  real_functions_.fBindFramebuffer(target, framebuffer);\n}\n\nGrGLvoid TraceGlFuctions::BindRenderbuffer(GrGLenum target,\n                                           GrGLuint renderbuffer) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBindRenderbuffer(target, renderbuffer);\n}\n\nGrGLvoid TraceGlFuctions::BindTexture(GrGLenum target, GrGLuint texture) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  target_to_texture_[target] = texture;\n  real_functions_.fBindTexture(target, texture);\n}\n\nGrGLvoid TraceGlFuctions::BindFragDataLocation(GrGLuint program,\n                                               GrGLuint color_number,\n                                               const GrGLchar* name) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBindFragDataLocation(program, color_number, name);\n}\n\nGrGLvoid TraceGlFuctions::BindFragDataLocationIndexed(GrGLuint program,\n                                                      GrGLuint color_number,\n                                                      GrGLuint index,\n                                                      const GrGLchar* name) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBindFragDataLocationIndexed(program, color_number, index,\n                                               name);\n}\n\nGrGLvoid TraceGlFuctions::BindSampler(GrGLuint unit, GrGLuint sampler) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBindSampler(unit, sampler);\n}\n\nGrGLvoid TraceGlFuctions::BindVertexArray(GrGLuint array) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBindVertexArray(array);\n}\n\nGrGLvoid TraceGlFuctions::BlendBarrier() {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBlendBarrier();\n}\n\nGrGLvoid TraceGlFuctions::BlendColor(GrGLclampf red, GrGLclampf green,\n                                     GrGLclampf blue, GrGLclampf alpha) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBlendColor(red, green, blue, alpha);\n}\n\nGrGLvoid TraceGlFuctions::BlendEquation(GrGLenum mode) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBlendEquation(mode);\n}\n\nGrGLvoid TraceGlFuctions::BlendFunc(GrGLenum sfactor, GrGLenum dfactor) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBlendFunc(sfactor, dfactor);\n}\n\nGrGLvoid TraceGlFuctions::BlitFramebuffer(GrGLint srcX0, GrGLint srcY0,\n                                          GrGLint srcX1, GrGLint srcY1,\n                                          GrGLint dstX0, GrGLint dstY0,\n                                          GrGLint dstX1, GrGLint dstY1,\n                                          GrGLbitfield mask, GrGLenum filter) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0,\n                                   dstX1, dstY1, mask, filter);\n}\n\nGrGLvoid TraceGlFuctions::BufferData(GrGLenum target, GrGLsizeiptr size,\n                                     const GrGLvoid* data, GrGLenum usage) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBufferData(target, size, data, usage);\n}\n\nGrGLvoid TraceGlFuctions::BufferSubData(GrGLenum target, GrGLintptr offset,\n                                        GrGLsizeiptr size,\n                                        const GrGLvoid* data) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBufferSubData(target, offset, size, data);\n}\n\nGrGLenum TraceGlFuctions::CheckFramebufferStatus(GrGLenum target) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fCheckFramebufferStatus(target);\n}\n\nGrGLvoid TraceGlFuctions::Clear(GrGLbitfield mask) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fClear(mask);\n}\n\nGrGLvoid TraceGlFuctions::ClearColor(GrGLclampf red, GrGLclampf green,\n                                     GrGLclampf blue, GrGLclampf alpha) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fClearColor(red, green, blue, alpha);\n}\n\nGrGLvoid TraceGlFuctions::ClearStencil(GrGLint s) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fClearStencil(s);\n}\n\nGrGLvoid TraceGlFuctions::ClearTexImage(GrGLuint texture, GrGLint level,\n                                        GrGLenum format, GrGLenum type,\n                                        const GrGLvoid* data) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fClearTexImage(texture, level, format, type, data);\n}\n\nGrGLvoid TraceGlFuctions::ClearTexSubImage(GrGLuint texture, GrGLint level,\n                                           GrGLint xoffset, GrGLint yoffset,\n                                           GrGLint zoffset, GrGLsizei width,\n                                           GrGLsizei height, GrGLsizei depth,\n                                           GrGLenum format, GrGLenum type,\n                                           const GrGLvoid* data) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fClearTexSubImage(texture, level, xoffset, yoffset, zoffset,\n                                    width, height, depth, format, type, data);\n}\n\nGrGLvoid TraceGlFuctions::ColorMask(GrGLboolean red, GrGLboolean green,\n                                    GrGLboolean blue, GrGLboolean alpha) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fColorMask(red, green, blue, alpha);\n}\n\nGrGLvoid TraceGlFuctions::CompileShader(GrGLuint shader) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fCompileShader(shader);\n}\n\nGrGLvoid TraceGlFuctions::CompressedTexImage2D(GrGLenum target, GrGLint level,\n                                               GrGLenum internalformat,\n                                               GrGLsizei width,\n                                               GrGLsizei height, GrGLint border,\n                                               GrGLsizei imageSize,\n                                               const GrGLvoid* data) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fCompressedTexImage2D(target, level, internalformat, width,\n                                        height, border, imageSize, data);\n}\n\nGrGLvoid TraceGlFuctions::CompressedTexSubImage2D(\n    GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset,\n    GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLsizei imageSize,\n    const GrGLvoid* data) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fCompressedTexSubImage2D(\n      target, level, xoffset, yoffset, width, height, format, imageSize, data);\n}\n\nGrGLvoid TraceGlFuctions::CopyTexSubImage2D(GrGLenum target, GrGLint level,\n                                            GrGLint xoffset, GrGLint yoffset,\n                                            GrGLint x, GrGLint y,\n                                            GrGLsizei width, GrGLsizei height) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fCopyTexSubImage2D(target, level, xoffset, yoffset, x, y,\n                                     width, height);\n}\n\nGrGLuint TraceGlFuctions::CreateProgram() {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fCreateProgram();\n}\n\nGrGLuint TraceGlFuctions::CreateShader(GrGLenum type) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fCreateShader(type);\n}\n\nGrGLvoid TraceGlFuctions::CullFace(GrGLenum mode) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fCullFace(mode);\n}\n\nGrGLvoid TraceGlFuctions::DeleteBuffers(GrGLsizei n, const GrGLuint* buffers) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteBuffers(n, buffers);\n}\n\nGrGLvoid TraceGlFuctions::DeleteFences(GrGLsizei n, const GrGLuint* fences) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteFences(n, fences);\n}\n\nGrGLvoid TraceGlFuctions::DeleteFramebuffers(GrGLsizei n,\n                                             const GrGLuint* framebuffers) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteFramebuffers(n, framebuffers);\n}\n\nGrGLvoid TraceGlFuctions::DeleteProgram(GrGLuint program) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteProgram(program);\n}\n\nGrGLvoid TraceGlFuctions::DeleteQueries(GrGLsizei n, const GrGLuint* ids) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteQueries(n, ids);\n}\n\nGrGLvoid TraceGlFuctions::DeleteRenderbuffers(GrGLsizei n,\n                                              const GrGLuint* renderbuffers) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteRenderbuffers(n, renderbuffers);\n}\n\nGrGLvoid TraceGlFuctions::DeleteSamplers(GrGLsizei count,\n                                         const GrGLuint* samplers) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteSamplers(count, samplers);\n}\n\nGrGLvoid TraceGlFuctions::DeleteShader(GrGLuint shader) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteShader(shader);\n}\n\nGrGLvoid TraceGlFuctions::DeleteTextures(GrGLsizei n,\n                                         const GrGLuint* textures) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  for (GrGLsizei i = 0; i < n; i++) {\n    auto it = texture_infos_.find(textures[i]);\n    if (it != texture_infos_.end()) {\n      for (auto& info : it->second) {\n        if (info.inited) {\n          current_texture_size_ -= info.size;\n        }\n      }\n      texture_infos_.erase(it);\n    }\n  }\n  LogTexUsage();\n  real_functions_.fDeleteTextures(n, textures);\n}\n\nGrGLvoid TraceGlFuctions::DeleteVertexArrays(GrGLsizei n,\n                                             const GrGLuint* arrays) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteVertexArrays(n, arrays);\n}\n\nGrGLvoid TraceGlFuctions::DepthMask(GrGLboolean flag) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDepthMask(flag);\n}\n\nGrGLvoid TraceGlFuctions::Disable(GrGLenum cap) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDisable(cap);\n}\n\nGrGLvoid TraceGlFuctions::DisableVertexAttribArray(GrGLuint index) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDisableVertexAttribArray(index);\n}\n\nGrGLvoid TraceGlFuctions::DrawArrays(GrGLenum mode, GrGLint first,\n                                     GrGLsizei count) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawArrays(mode, first, count);\n}\n\nGrGLvoid TraceGlFuctions::DrawArraysInstanced(GrGLenum mode, GrGLint first,\n                                              GrGLsizei count,\n                                              GrGLsizei primcount) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawArraysInstanced(mode, first, count, primcount);\n}\n\nGrGLvoid TraceGlFuctions::DrawArraysIndirect(GrGLenum mode,\n                                             const GrGLvoid* indirect) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawArraysIndirect(mode, indirect);\n}\n\nGrGLvoid TraceGlFuctions::DrawBuffer(GrGLenum mode) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawBuffer(mode);\n}\n\nGrGLvoid TraceGlFuctions::DrawBuffers(GrGLsizei n, const GrGLenum* bufs) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawBuffers(n, bufs);\n}\n\nGrGLvoid TraceGlFuctions::DrawElements(GrGLenum mode, GrGLsizei count,\n                                       GrGLenum type, const GrGLvoid* indices) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawElements(mode, count, type, indices);\n}\n\nGrGLvoid TraceGlFuctions::DrawElementsInstanced(GrGLenum mode, GrGLsizei count,\n                                                GrGLenum type,\n                                                const GrGLvoid* indices,\n                                                GrGLsizei primcount) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawElementsInstanced(mode, count, type, indices, primcount);\n}\n\nGrGLvoid TraceGlFuctions::DrawElementsIndirect(GrGLenum mode, GrGLenum type,\n                                               const GrGLvoid* indirect) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawElementsIndirect(mode, type, indirect);\n}\n\nGrGLvoid TraceGlFuctions::DrawRangeElements(GrGLenum mode, GrGLuint start,\n                                            GrGLuint end, GrGLsizei count,\n                                            GrGLenum type,\n                                            const GrGLvoid* indices) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawRangeElements(mode, start, end, count, type, indices);\n}\n\nGrGLvoid TraceGlFuctions::Enable(GrGLenum cap) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fEnable(cap);\n}\n\nGrGLvoid TraceGlFuctions::EnableVertexAttribArray(GrGLuint index) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fEnableVertexAttribArray(index);\n}\n\nGrGLvoid TraceGlFuctions::EndQuery(GrGLenum target) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fEndQuery(target);\n}\n\nGrGLvoid TraceGlFuctions::Finish() {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fFinish();\n}\n\nGrGLvoid TraceGlFuctions::FinishFence(GrGLuint fence) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fFinishFence(fence);\n}\n\nGrGLvoid TraceGlFuctions::Flush() {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fFlush();\n}\n\nGrGLvoid TraceGlFuctions::FlushMappedBufferRange(GrGLenum target,\n                                                 GrGLintptr offset,\n                                                 GrGLsizeiptr length) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fFlushMappedBufferRange(target, offset, length);\n}\n\nGrGLvoid TraceGlFuctions::FramebufferRenderbuffer(GrGLenum target,\n                                                  GrGLenum attachment,\n                                                  GrGLenum renderbuffertarget,\n                                                  GrGLuint renderbuffer) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fFramebufferRenderbuffer(target, attachment,\n                                           renderbuffertarget, renderbuffer);\n}\n\nGrGLvoid TraceGlFuctions::FramebufferTexture2D(GrGLenum target,\n                                               GrGLenum attachment,\n                                               GrGLenum textarget,\n                                               GrGLuint texture,\n                                               GrGLint level) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fFramebufferTexture2D(target, attachment, textarget, texture,\n                                        level);\n}\n\nGrGLvoid TraceGlFuctions::FramebufferTexture2DMultisample(\n    GrGLenum target, GrGLenum attachment, GrGLenum textarget, GrGLuint texture,\n    GrGLint level, GrGLsizei samples) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fFramebufferTexture2DMultisample(\n      target, attachment, textarget, texture, level, samples);\n}\n\nGrGLvoid TraceGlFuctions::FrontFace(GrGLenum mode) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fFrontFace(mode);\n}\n\nGrGLvoid TraceGlFuctions::GenBuffers(GrGLsizei n, GrGLuint* buffers) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGenBuffers(n, buffers);\n}\n\nGrGLvoid TraceGlFuctions::GenFences(GrGLsizei n, GrGLuint* fences) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGenFences(n, fences);\n}\n\nGrGLvoid TraceGlFuctions::GenFramebuffers(GrGLsizei n, GrGLuint* framebuffers) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGenFramebuffers(n, framebuffers);\n}\n\nGrGLvoid TraceGlFuctions::GenerateMipmap(GrGLenum target) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGenerateMipmap(target);\n}\n\nGrGLvoid TraceGlFuctions::GenQueries(GrGLsizei n, GrGLuint* ids) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGenQueries(n, ids);\n}\n\nGrGLvoid TraceGlFuctions::GenRenderbuffers(GrGLsizei n,\n                                           GrGLuint* renderbuffers) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGenRenderbuffers(n, renderbuffers);\n}\n\nGrGLvoid TraceGlFuctions::GenSamplers(GrGLsizei count, GrGLuint* samplers) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGenSamplers(count, samplers);\n}\n\nGrGLvoid TraceGlFuctions::GenTextures(GrGLsizei n, GrGLuint* textures) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGenTextures(n, textures);\n}\n\nGrGLvoid TraceGlFuctions::GenVertexArrays(GrGLsizei n, GrGLuint* arrays) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGenVertexArrays(n, arrays);\n}\n\nGrGLvoid TraceGlFuctions::GetBufferParameteriv(GrGLenum target, GrGLenum pname,\n                                               GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetBufferParameteriv(target, pname, params);\n}\n\nGrGLenum TraceGlFuctions::GetError() {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fGetError();\n}\n\nGrGLvoid TraceGlFuctions::GetFramebufferAttachmentParameteriv(\n    GrGLenum target, GrGLenum attachment, GrGLenum pname, GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetFramebufferAttachmentParameteriv(target, attachment,\n                                                       pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetIntegerv(GrGLenum pname, GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetIntegerv(pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetMultisamplefv(GrGLenum pname, GrGLuint index,\n                                           GrGLfloat* val) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetMultisamplefv(pname, index, val);\n}\n\nGrGLvoid TraceGlFuctions::GetProgramBinary(GrGLuint program, GrGLsizei bufsize,\n                                           GrGLsizei* length,\n                                           GrGLenum* binaryFormat,\n                                           void* binary) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetProgramBinary(program, bufsize, length, binaryFormat,\n                                    binary);\n}\n\nGrGLvoid TraceGlFuctions::GetProgramInfoLog(GrGLuint program, GrGLsizei bufsize,\n                                            GrGLsizei* length, char* infolog) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetProgramInfoLog(program, bufsize, length, infolog);\n}\n\nGrGLvoid TraceGlFuctions::GetProgramiv(GrGLuint program, GrGLenum pname,\n                                       GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetProgramiv(program, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetQueryiv(GrGLenum GLtarget, GrGLenum pname,\n                                     GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetQueryiv(GLtarget, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetQueryObjecti64v(GrGLuint id, GrGLenum pname,\n                                             GrGLint64* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetQueryObjecti64v(id, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetQueryObjectiv(GrGLuint id, GrGLenum pname,\n                                           GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetQueryObjectiv(id, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetQueryObjectui64v(GrGLuint id, GrGLenum pname,\n                                              GrGLuint64* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetQueryObjectui64v(id, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetQueryObjectuiv(GrGLuint id, GrGLenum pname,\n                                            GrGLuint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetQueryObjectuiv(id, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetRenderbufferParameteriv(GrGLenum target,\n                                                     GrGLenum pname,\n                                                     GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetRenderbufferParameteriv(target, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetShaderInfoLog(GrGLuint shader, GrGLsizei bufsize,\n                                           GrGLsizei* length, char* infolog) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetShaderInfoLog(shader, bufsize, length, infolog);\n}\n\nGrGLvoid TraceGlFuctions::GetShaderiv(GrGLuint shader, GrGLenum pname,\n                                      GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetShaderiv(shader, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::GetShaderPrecisionFormat(GrGLenum shadertype,\n                                                   GrGLenum precisiontype,\n                                                   GrGLint* range,\n                                                   GrGLint* precision) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetShaderPrecisionFormat(shadertype, precisiontype, range,\n                                            precision);\n}\n\nconst GrGLubyte* TraceGlFuctions::GetString(GrGLenum name) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fGetString(name);\n}\n\nconst GrGLubyte* TraceGlFuctions::GetStringi(GrGLenum name, GrGLuint index) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fGetStringi(name, index);\n}\n\nGrGLvoid TraceGlFuctions::GetTexLevelParameteriv(GrGLenum target, GrGLint level,\n                                                 GrGLenum pname,\n                                                 GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetTexLevelParameteriv(target, level, pname, params);\n}\n\nGrGLint TraceGlFuctions::GetUniformLocation(GrGLuint program,\n                                            const char* name) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fGetUniformLocation(program, name);\n}\n\nGrGLvoid TraceGlFuctions::InsertEventMarker(GrGLsizei length,\n                                            const char* marker) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fInsertEventMarker(length, marker);\n}\n\nGrGLvoid TraceGlFuctions::InvalidateBufferData(GrGLuint buffer) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fInvalidateBufferData(buffer);\n}\n\nGrGLvoid TraceGlFuctions::InvalidateBufferSubData(GrGLuint buffer,\n                                                  GrGLintptr offset,\n                                                  GrGLsizeiptr length) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fInvalidateBufferSubData(buffer, offset, length);\n}\n\nGrGLvoid TraceGlFuctions::InvalidateFramebuffer(GrGLenum target,\n                                                GrGLsizei numAttachments,\n                                                const GrGLenum* attachments) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fInvalidateFramebuffer(target, numAttachments, attachments);\n}\n\nGrGLvoid TraceGlFuctions::InvalidateSubFramebuffer(\n    GrGLenum target, GrGLsizei numAttachments, const GrGLenum* attachments,\n    GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fInvalidateSubFramebuffer(target, numAttachments, attachments,\n                                            x, y, width, height);\n}\n\nGrGLvoid TraceGlFuctions::InvalidateTexImage(GrGLuint texture, GrGLint level) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fInvalidateTexImage(texture, level);\n}\n\nGrGLvoid TraceGlFuctions::InvalidateTexSubImage(\n    GrGLuint texture, GrGLint level, GrGLint xoffset, GrGLint yoffset,\n    GrGLint zoffset, GrGLsizei width, GrGLsizei height, GrGLsizei depth) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fInvalidateTexSubImage(texture, level, xoffset, yoffset,\n                                         zoffset, width, height, depth);\n}\n\nGrGLboolean TraceGlFuctions::IsTexture(GrGLuint texture) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fIsTexture(texture);\n}\n\nGrGLvoid TraceGlFuctions::LineWidth(GrGLfloat width) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fLineWidth(width);\n}\n\nGrGLvoid TraceGlFuctions::LinkProgram(GrGLuint program) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fLinkProgram(program);\n}\n\nGrGLvoid* TraceGlFuctions::MapBuffer(GrGLenum target, GrGLenum access) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fMapBuffer(target, access);\n}\n\nGrGLvoid* TraceGlFuctions::MapBufferRange(GrGLenum target, GrGLintptr offset,\n                                          GrGLsizeiptr length,\n                                          GrGLbitfield access) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fMapBufferRange(target, offset, length, access);\n}\n\nGrGLvoid* TraceGlFuctions::MapBufferSubData(GrGLuint target, GrGLintptr offset,\n                                            GrGLsizeiptr size,\n                                            GrGLenum access) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fMapBufferSubData(target, offset, size, access);\n}\n\nGrGLvoid* TraceGlFuctions::MapTexSubImage2D(GrGLenum target, GrGLint level,\n                                            GrGLint xoffset, GrGLint yoffset,\n                                            GrGLsizei width, GrGLsizei height,\n                                            GrGLenum format, GrGLenum type,\n                                            GrGLenum access) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fMapTexSubImage2D(target, level, xoffset, yoffset,\n                                           width, height, format, type, access);\n}\n\nGrGLvoid* TraceGlFuctions::MemoryBarrier(GrGLbitfield barriers) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fMemoryBarrier(barriers);\n}\n\nGrGLvoid TraceGlFuctions::PatchParameteri(GrGLenum pname, GrGLint value) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fPatchParameteri(pname, value);\n}\n\nGrGLvoid TraceGlFuctions::PixelStorei(GrGLenum pname, GrGLint param) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fPixelStorei(pname, param);\n}\n\nGrGLvoid TraceGlFuctions::PolygonMode(GrGLenum face, GrGLenum mode) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fPolygonMode(face, mode);\n}\n\nGrGLvoid TraceGlFuctions::PopGroupMarker() {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fPopGroupMarker();\n}\n\nGrGLvoid TraceGlFuctions::ProgramBinary(GrGLuint program, GrGLenum binaryFormat,\n                                        void* binary, GrGLsizei length) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fProgramBinary(program, binaryFormat, binary, length);\n}\n\nGrGLvoid TraceGlFuctions::ProgramParameteri(GrGLuint program, GrGLenum pname,\n                                            GrGLint value) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fProgramParameteri(program, pname, value);\n}\n\nGrGLvoid TraceGlFuctions::PushGroupMarker(GrGLsizei length,\n                                          const char* marker) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fPushGroupMarker(length, marker);\n}\n\nGrGLvoid TraceGlFuctions::QueryCounter(GrGLuint id, GrGLenum target) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fQueryCounter(id, target);\n}\n\nGrGLvoid TraceGlFuctions::ReadBuffer(GrGLenum src) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fReadBuffer(src);\n}\n\nGrGLvoid TraceGlFuctions::ReadPixels(GrGLint x, GrGLint y, GrGLsizei width,\n                                     GrGLsizei height, GrGLenum format,\n                                     GrGLenum type, GrGLvoid* pixels) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fReadPixels(x, y, width, height, format, type, pixels);\n}\n\nGrGLvoid TraceGlFuctions::RenderbufferStorage(GrGLenum target,\n                                              GrGLenum internalformat,\n                                              GrGLsizei width,\n                                              GrGLsizei height) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fRenderbufferStorage(target, internalformat, width, height);\n}\n\nGrGLvoid TraceGlFuctions::RenderbufferStorageMultisample(\n    GrGLenum target, GrGLsizei samples, GrGLenum internalformat,\n    GrGLsizei width, GrGLsizei height) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fRenderbufferStorageMultisample(\n      target, samples, internalformat, width, height);\n}\n\nGrGLvoid TraceGlFuctions::ResolveMultisampleFramebuffer() {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fResolveMultisampleFramebuffer();\n}\n\nGrGLvoid TraceGlFuctions::SamplerParameteri(GrGLuint sampler, GrGLenum pname,\n                                            GrGLint params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fSamplerParameteri(sampler, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::SamplerParameteriv(GrGLuint sampler, GrGLenum pname,\n                                             const GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fSamplerParameteriv(sampler, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::Scissor(GrGLint x, GrGLint y, GrGLsizei width,\n                                  GrGLsizei height) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fScissor(x, y, width, height);\n}\n\n// GL_CHROMIUM_bind_uniform_location\nGrGLvoid TraceGlFuctions::BindUniformLocation(GrGLuint program,\n                                              GrGLint location,\n                                              const char* name) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fBindUniformLocation(program, location, name);\n}\n\nGrGLvoid TraceGlFuctions::SetFence(GrGLuint fence, GrGLenum condition) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fSetFence(fence, condition);\n}\n\nGrGLvoid TraceGlFuctions::ShaderSource(GrGLuint shader, GrGLsizei count,\n                                       const char* const* str,\n                                       const GrGLint* length) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fShaderSource(shader, count, str, length);\n}\n\nGrGLvoid TraceGlFuctions::StencilFunc(GrGLenum func, GrGLint ref,\n                                      GrGLuint mask) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fStencilFunc(func, ref, mask);\n}\n\nGrGLvoid TraceGlFuctions::StencilFuncSeparate(GrGLenum face, GrGLenum func,\n                                              GrGLint ref, GrGLuint mask) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fStencilFuncSeparate(face, func, ref, mask);\n}\n\nGrGLvoid TraceGlFuctions::StencilMask(GrGLuint mask) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fStencilMask(mask);\n}\n\nGrGLvoid TraceGlFuctions::StencilMaskSeparate(GrGLenum face, GrGLuint mask) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fStencilMaskSeparate(face, mask);\n}\n\nGrGLvoid TraceGlFuctions::StencilOp(GrGLenum fail, GrGLenum zfail,\n                                    GrGLenum zpass) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fStencilOp(fail, zfail, zpass);\n}\n\nGrGLvoid TraceGlFuctions::StencilOpSeparate(GrGLenum face, GrGLenum fail,\n                                            GrGLenum zfail, GrGLenum zpass) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fStencilOpSeparate(face, fail, zfail, zpass);\n}\n\nGrGLvoid TraceGlFuctions::TexBuffer(GrGLenum target, GrGLenum internalformat,\n                                    GrGLuint buffer) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fTexBuffer(target, internalformat, buffer);\n}\n\nGrGLvoid TraceGlFuctions::TexBufferRange(GrGLenum target,\n                                         GrGLenum internalformat,\n                                         GrGLuint buffer, GrGLintptr offset,\n                                         GrGLsizeiptr size) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fTexBufferRange(target, internalformat, buffer, offset, size);\n}\n\nGrGLvoid TraceGlFuctions::TexImage2D(GrGLenum target, GrGLint level,\n                                     GrGLint internalformat, GrGLsizei width,\n                                     GrGLsizei height, GrGLint border,\n                                     GrGLenum format, GrGLenum type,\n                                     const GrGLvoid* pixels) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  if (auto texture_info =\n          SetTextureInfo(target, level, internalformat, width, height)) {\n    LogTexUsage();\n    if (pixels != nullptr) {\n      total_tex_upload_size_ += texture_info->size;\n      LogTexUploadSize();\n    }\n  }\n  real_functions_.fTexImage2D(target, level, internalformat, width, height,\n                              border, format, type, pixels);\n}\n\nGrGLvoid TraceGlFuctions::TexParameterf(GrGLenum target, GrGLenum pname,\n                                        GrGLfloat param) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fTexParameterf(target, pname, param);\n}\n\nGrGLvoid TraceGlFuctions::TexParameterfv(GrGLenum target, GrGLenum pname,\n                                         const GrGLfloat* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fTexParameterfv(target, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::TexParameteri(GrGLenum target, GrGLenum pname,\n                                        GrGLint param) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fTexParameteri(target, pname, param);\n}\n\nGrGLvoid TraceGlFuctions::TexParameteriv(GrGLenum target, GrGLenum pname,\n                                         const GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fTexParameteriv(target, pname, params);\n}\n\nGrGLvoid TraceGlFuctions::TexStorage2D(GrGLenum target, GrGLsizei levels,\n                                       GrGLenum internalformat, GrGLsizei width,\n                                       GrGLsizei height) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  for (GrGLsizei i = 0; i < levels; i++) {\n    SetTextureInfo(target, i, internalformat, std::max(1, width >> i),\n                   std::max(1, height >> i));\n  }\n  LogTexUsage();\n\n  real_functions_.fTexStorage2D(target, levels, internalformat, width, height);\n}\n\nGrGLvoid TraceGlFuctions::DiscardFramebuffer(GrGLenum target,\n                                             GrGLsizei numAttachments,\n                                             const GrGLenum* attachments) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDiscardFramebuffer(target, numAttachments, attachments);\n}\n\nGrGLboolean TraceGlFuctions::TestFence(GrGLuint fence) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fTestFence(fence);\n}\n\nGrGLvoid TraceGlFuctions::TexSubImage2D(GrGLenum target, GrGLint level,\n                                        GrGLint xoffset, GrGLint yoffset,\n                                        GrGLsizei width, GrGLsizei height,\n                                        GrGLenum format, GrGLenum type,\n                                        const GrGLvoid* pixels) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  if (auto texture_info = GetTextureInfo(target, level)) {\n    total_tex_upload_size_ +=\n        CalculateTexSize(target, texture_info->format, width, height);\n    LogTexUploadSize();\n  }\n  real_functions_.fTexSubImage2D(target, level, xoffset, yoffset, width, height,\n                                 format, type, pixels);\n}\n\nGrGLvoid TraceGlFuctions::TextureBarrier() {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fTextureBarrier();\n}\n\nGrGLvoid TraceGlFuctions::Uniform1f(GrGLint location, GrGLfloat v0) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform1f(location, v0);\n}\n\nGrGLvoid TraceGlFuctions::Uniform1i(GrGLint location, GrGLint v0) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform1i(location, v0);\n}\n\nGrGLvoid TraceGlFuctions::Uniform1fv(GrGLint location, GrGLsizei count,\n                                     const GrGLfloat* v) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform1fv(location, count, v);\n}\n\nGrGLvoid TraceGlFuctions::Uniform1iv(GrGLint location, GrGLsizei count,\n                                     const GrGLint* v) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform1iv(location, count, v);\n}\n\nGrGLvoid TraceGlFuctions::Uniform2f(GrGLint location, GrGLfloat v0,\n                                    GrGLfloat v1) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform2f(location, v0, v1);\n}\n\nGrGLvoid TraceGlFuctions::Uniform2i(GrGLint location, GrGLint v0, GrGLint v1) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform2i(location, v0, v1);\n}\n\nGrGLvoid TraceGlFuctions::Uniform2fv(GrGLint location, GrGLsizei count,\n                                     const GrGLfloat* v) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform2fv(location, count, v);\n}\n\nGrGLvoid TraceGlFuctions::Uniform2iv(GrGLint location, GrGLsizei count,\n                                     const GrGLint* v) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform2iv(location, count, v);\n}\n\nGrGLvoid TraceGlFuctions::Uniform3f(GrGLint location, GrGLfloat v0,\n                                    GrGLfloat v1, GrGLfloat v2) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform3f(location, v0, v1, v2);\n}\n\nGrGLvoid TraceGlFuctions::Uniform3i(GrGLint location, GrGLint v0, GrGLint v1,\n                                    GrGLint v2) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform3i(location, v0, v1, v2);\n}\n\nGrGLvoid TraceGlFuctions::Uniform3fv(GrGLint location, GrGLsizei count,\n                                     const GrGLfloat* v) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform3fv(location, count, v);\n}\n\nGrGLvoid TraceGlFuctions::Uniform3iv(GrGLint location, GrGLsizei count,\n                                     const GrGLint* v) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform3iv(location, count, v);\n}\n\nGrGLvoid TraceGlFuctions::Uniform4f(GrGLint location, GrGLfloat v0,\n                                    GrGLfloat v1, GrGLfloat v2, GrGLfloat v3) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform4f(location, v0, v1, v2, v3);\n}\n\nGrGLvoid TraceGlFuctions::Uniform4i(GrGLint location, GrGLint v0, GrGLint v1,\n                                    GrGLint v2, GrGLint v3) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform4i(location, v0, v1, v2, v3);\n}\n\nGrGLvoid TraceGlFuctions::Uniform4fv(GrGLint location, GrGLsizei count,\n                                     const GrGLfloat* v) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform4fv(location, count, v);\n}\n\nGrGLvoid TraceGlFuctions::Uniform4iv(GrGLint location, GrGLsizei count,\n                                     const GrGLint* v) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniform4iv(location, count, v);\n}\n\nGrGLvoid TraceGlFuctions::UniformMatrix2fv(GrGLint location, GrGLsizei count,\n                                           GrGLboolean transpose,\n                                           const GrGLfloat* value) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniformMatrix2fv(location, count, transpose, value);\n}\n\nGrGLvoid TraceGlFuctions::UniformMatrix3fv(GrGLint location, GrGLsizei count,\n                                           GrGLboolean transpose,\n                                           const GrGLfloat* value) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniformMatrix3fv(location, count, transpose, value);\n}\n\nGrGLvoid TraceGlFuctions::UniformMatrix4fv(GrGLint location, GrGLsizei count,\n                                           GrGLboolean transpose,\n                                           const GrGLfloat* value) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUniformMatrix4fv(location, count, transpose, value);\n}\n\nGrGLboolean TraceGlFuctions::UnmapBuffer(GrGLenum target) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fUnmapBuffer(target);\n}\n\nGrGLvoid TraceGlFuctions::UnmapBufferSubData(const GrGLvoid* mem) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUnmapBufferSubData(mem);\n}\n\nGrGLvoid TraceGlFuctions::UnmapTexSubImage2D(const GrGLvoid* mem) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUnmapTexSubImage2D(mem);\n}\n\nGrGLvoid TraceGlFuctions::UseProgram(GrGLuint program) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fUseProgram(program);\n}\n\nGrGLvoid TraceGlFuctions::VertexAttrib1f(GrGLuint indx, const GrGLfloat value) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fVertexAttrib1f(indx, value);\n}\n\nGrGLvoid TraceGlFuctions::VertexAttrib2fv(GrGLuint indx,\n                                          const GrGLfloat* values) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fVertexAttrib2fv(indx, values);\n}\n\nGrGLvoid TraceGlFuctions::VertexAttrib3fv(GrGLuint indx,\n                                          const GrGLfloat* values) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fVertexAttrib3fv(indx, values);\n}\n\nGrGLvoid TraceGlFuctions::VertexAttrib4fv(GrGLuint indx,\n                                          const GrGLfloat* values) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fVertexAttrib4fv(indx, values);\n}\n\nGrGLvoid TraceGlFuctions::VertexAttribDivisor(GrGLuint index,\n                                              GrGLuint divisor) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fVertexAttribDivisor(index, divisor);\n}\n\nGrGLvoid TraceGlFuctions::VertexAttribIPointer(GrGLuint indx, GrGLint size,\n                                               GrGLenum type, GrGLsizei stride,\n                                               const GrGLvoid* ptr) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fVertexAttribIPointer(indx, size, type, stride, ptr);\n}\n\nGrGLvoid TraceGlFuctions::VertexAttribPointer(GrGLuint indx, GrGLint size,\n                                              GrGLenum type,\n                                              GrGLboolean normalized,\n                                              GrGLsizei stride,\n                                              const GrGLvoid* ptr) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fVertexAttribPointer(indx, size, type, normalized, stride,\n                                       ptr);\n}\n\nGrGLvoid TraceGlFuctions::Viewport(GrGLint x, GrGLint y, GrGLsizei width,\n                                   GrGLsizei height) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fViewport(x, y, width, height);\n}\n\n/* EXT_base_instance */\nGrGLvoid TraceGlFuctions::DrawArraysInstancedBaseInstance(\n    GrGLenum mode, GrGLint first, GrGLsizei count, GrGLsizei instancecount,\n    GrGLuint baseinstance) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawArraysInstancedBaseInstance(mode, first, count,\n                                                   instancecount, baseinstance);\n}\n\nGrGLvoid TraceGlFuctions::DrawElementsInstancedBaseVertexBaseInstance(\n    GrGLenum mode, GrGLsizei count, GrGLenum type, const void* indices,\n    GrGLsizei instancecount, GrGLint basevertex, GrGLuint baseinstance) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDrawElementsInstancedBaseVertexBaseInstance(\n      mode, count, type, indices, instancecount, basevertex, baseinstance);\n}\n\n/* EXT_multi_draw_indirect */\nGrGLvoid TraceGlFuctions::MultiDrawArraysIndirect(GrGLenum mode,\n                                                  const GrGLvoid* indirect,\n                                                  GrGLsizei drawcount,\n                                                  GrGLsizei stride) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fMultiDrawArraysIndirect(mode, indirect, drawcount, stride);\n}\n\nGrGLvoid TraceGlFuctions::MultiDrawElementsIndirect(GrGLenum mode,\n                                                    GrGLenum type,\n                                                    const GrGLvoid* indirect,\n                                                    GrGLsizei drawcount,\n                                                    GrGLsizei stride) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fMultiDrawElementsIndirect(mode, type, indirect, drawcount,\n                                             stride);\n}\n\n/* ANGLE_base_vertex_base_instance */\nGrGLvoid TraceGlFuctions::MultiDrawArraysInstancedBaseInstance(\n    GrGLenum mode, const GrGLint* firsts, const GrGLsizei* counts,\n    const GrGLsizei* instanceCounts, const GrGLuint* baseInstances,\n    const GrGLsizei drawcount) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fMultiDrawArraysInstancedBaseInstance(\n      mode, firsts, counts, instanceCounts, baseInstances, drawcount);\n}\n\nGrGLvoid TraceGlFuctions::MultiDrawElementsInstancedBaseVertexBaseInstance(\n    GrGLenum mode, const GrGLint* counts, GrGLenum type,\n    const GrGLvoid* const* indices, const GrGLsizei* instanceCounts,\n    const GrGLint* baseVertices, const GrGLuint* baseInstances,\n    const GrGLsizei drawcount) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fMultiDrawElementsInstancedBaseVertexBaseInstance(\n      mode, counts, type, indices, instanceCounts, baseVertices, baseInstances,\n      drawcount);\n}\n\n/* ARB_sync */\nGrGLsync TraceGlFuctions::FenceSync(GrGLenum condition, GrGLbitfield flags) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fFenceSync(condition, flags);\n}\n\nGrGLboolean TraceGlFuctions::IsSync(GrGLsync sync) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fIsSync(sync);\n}\n\nGrGLenum TraceGlFuctions::ClientWaitSync(GrGLsync sync, GrGLbitfield flags,\n                                         GrGLuint64 timeout) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fClientWaitSync(sync, flags, timeout);\n}\n\nGrGLvoid TraceGlFuctions::WaitSync(GrGLsync sync, GrGLbitfield flags,\n                                   GrGLuint64 timeout) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fWaitSync(sync, flags, timeout);\n}\n\nGrGLvoid TraceGlFuctions::DeleteSync(GrGLsync sync) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDeleteSync(sync);\n}\n\n/* ARB_internalformat_query */\nGrGLvoid TraceGlFuctions::GetInternalformativ(GrGLenum target,\n                                              GrGLenum internalformat,\n                                              GrGLenum pname, GrGLsizei bufSize,\n                                              GrGLint* params) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fGetInternalformativ(target, internalformat, pname, bufSize,\n                                       params);\n}\n\n/* KHR_debug */\nGrGLvoid TraceGlFuctions::DebugMessageControl(GrGLenum source, GrGLenum type,\n                                              GrGLenum severity,\n                                              GrGLsizei count,\n                                              const GrGLuint* ids,\n                                              GrGLboolean enabled) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDebugMessageControl(source, type, severity, count, ids,\n                                       enabled);\n}\n\nGrGLvoid TraceGlFuctions::DebugMessageInsert(GrGLenum source, GrGLenum type,\n                                             GrGLuint id, GrGLenum severity,\n                                             GrGLsizei length,\n                                             const GrGLchar* buf) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDebugMessageInsert(source, type, id, severity, length, buf);\n}\n\nGrGLvoid TraceGlFuctions::DebugMessageCallback(GRGLDEBUGPROC callback,\n                                               const GrGLvoid* userParam) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fDebugMessageCallback(callback, userParam);\n}\n\nGrGLuint TraceGlFuctions::GetDebugMessageLog(GrGLuint count, GrGLsizei bufSize,\n                                             GrGLenum* sources, GrGLenum* types,\n                                             GrGLuint* ids,\n                                             GrGLenum* severities,\n                                             GrGLsizei* lengths,\n                                             GrGLchar* messageLog) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  return real_functions_.fGetDebugMessageLog(\n      count, bufSize, sources, types, ids, severities, lengths, messageLog);\n}\n\nGrGLvoid TraceGlFuctions::PushDebugGroup(GrGLenum source, GrGLuint id,\n                                         GrGLsizei length,\n                                         const GrGLchar* message) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fPushDebugGroup(source, id, length, message);\n}\n\nGrGLvoid TraceGlFuctions::PopDebugGroup() {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fPopDebugGroup();\n}\n\nGrGLvoid TraceGlFuctions::ObjectLabel(GrGLenum identifier, GrGLuint name,\n                                      GrGLsizei length, const GrGLchar* label) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fObjectLabel(identifier, name, length, label);\n}\n\n/** EXT_window_rectangles */\nGrGLvoid TraceGlFuctions::WindowRectangles(GrGLenum mode, GrGLsizei count,\n                                           const GrGLint box[]) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fWindowRectangles(mode, count, box);\n}\n\n/** GL_QCOM_tiled_rendering */\nGrGLvoid TraceGlFuctions::StartTiling(GrGLuint x, GrGLuint y, GrGLuint width,\n                                      GrGLuint height,\n                                      GrGLbitfield preserveMask) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fStartTiling(x, y, width, height, preserveMask);\n}\n\nGrGLvoid TraceGlFuctions::EndTiling(GrGLbitfield preserveMask) {\n  TRACE_EVENT(\"gpu\", __FUNCTION__);\n  real_functions_.fEndTiling(preserveMask);\n}\n\nTraceGlFuctions::TextureInfo* TraceGlFuctions::GetTextureInfo(GrGLenum target,\n                                                              GrGLint level) {\n  auto target_to_texture_it = target_to_texture_.find(target);\n  if (target_to_texture_it == target_to_texture_.end()) {\n    return {};\n  }\n  GrGLuint id = target_to_texture_it->second;\n  auto& texture_info_levels = texture_infos_[id];\n  if (texture_info_levels.size() <= static_cast<size_t>(level)) {\n    texture_info_levels.resize(level + 1);\n  }\n  return &texture_info_levels[level];\n}\n\nTraceGlFuctions::TextureInfo* TraceGlFuctions::SetTextureInfo(\n    GrGLenum target, GrGLint level, GrGLint internalformat, GrGLsizei width,\n    GrGLsizei height) {\n  auto* info = GetTextureInfo(target, level);\n  if (!info) {\n    return nullptr;\n  }\n\n  if (info->inited) {\n    current_texture_size_ -= info->size;\n  }\n  info->inited = true;\n  info->width = width;\n  info->height = height;\n  info->format = internalformat;\n  info->size = CalculateTexSize(target, internalformat, width, height);\n  current_texture_size_ += info->size;\n\n  return info;\n}\n\nvoid TraceGlFuctions::LogTexUsage() {\n  TRACE_COUNTER(\"gpu\", \"TextureUsage\", current_texture_size_);\n}\n\nvoid TraceGlFuctions::LogTexUploadSize() {\n  TRACE_COUNTER(\"gpu\", \"TextureUploadSize\", total_tex_upload_size_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/gpu/trace_gl_fuctions.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_GPU_TRACE_GL_FUCTIONS_H_\n#define CLAY_SHELL_GPU_TRACE_GL_FUCTIONS_H_\n\n#include <tuple>\n#include <unordered_map>\n#include <vector>\n\n#include \"third_party/skia/include/gpu/gl/GrGLInterface.h\"\n#include \"third_party/skia/include/gpu/gl/GrGLTypes.h\"\n\n#undef MemoryBarrier\n\nnamespace clay {\n\nclass TraceGlFuctions {\n public:\n  static void ReplaceFunctions(GrGLInterface* interface);\n\n  explicit TraceGlFuctions(GrGLInterface* interface);\n  ~TraceGlFuctions() = default;\n\n  GrGLvoid ActiveTexture(GrGLenum texture);\n  GrGLvoid AttachShader(GrGLuint program, GrGLuint shader);\n  GrGLvoid BeginQuery(GrGLenum target, GrGLuint id);\n  GrGLvoid BindAttribLocation(GrGLuint program, GrGLuint index,\n                              const char* name);\n  GrGLvoid BindBuffer(GrGLenum target, GrGLuint buffer);\n  GrGLvoid BindFramebuffer(GrGLenum target, GrGLuint framebuffer);\n  GrGLvoid BindRenderbuffer(GrGLenum target, GrGLuint renderbuffer);\n  GrGLvoid BindTexture(GrGLenum target, GrGLuint texture);\n  GrGLvoid BindFragDataLocation(GrGLuint program, GrGLuint colorNumber,\n                                const GrGLchar* name);\n  GrGLvoid BindFragDataLocationIndexed(GrGLuint program, GrGLuint colorNumber,\n                                       GrGLuint index, const GrGLchar* name);\n  GrGLvoid BindSampler(GrGLuint unit, GrGLuint sampler);\n  GrGLvoid BindVertexArray(GrGLuint array);\n  GrGLvoid BlendBarrier();\n  GrGLvoid BlendColor(GrGLclampf red, GrGLclampf green, GrGLclampf blue,\n                      GrGLclampf alpha);\n  GrGLvoid BlendEquation(GrGLenum mode);\n  GrGLvoid BlendFunc(GrGLenum sfactor, GrGLenum dfactor);\n  GrGLvoid BlitFramebuffer(GrGLint srcX0, GrGLint srcY0, GrGLint srcX1,\n                           GrGLint srcY1, GrGLint dstX0, GrGLint dstY0,\n                           GrGLint dstX1, GrGLint dstY1, GrGLbitfield mask,\n                           GrGLenum filter);\n  GrGLvoid BufferData(GrGLenum target, GrGLsizeiptr size, const GrGLvoid* data,\n                      GrGLenum usage);\n  GrGLvoid BufferSubData(GrGLenum target, GrGLintptr offset, GrGLsizeiptr size,\n                         const GrGLvoid* data);\n  GrGLenum CheckFramebufferStatus(GrGLenum target);\n  GrGLvoid Clear(GrGLbitfield mask);\n  GrGLvoid ClearColor(GrGLclampf red, GrGLclampf green, GrGLclampf blue,\n                      GrGLclampf alpha);\n  GrGLvoid ClearStencil(GrGLint s);\n  GrGLvoid ClearTexImage(GrGLuint texture, GrGLint level, GrGLenum format,\n                         GrGLenum type, const GrGLvoid* data);\n  GrGLvoid ClearTexSubImage(GrGLuint texture, GrGLint level, GrGLint xoffset,\n                            GrGLint yoffset, GrGLint zoffset, GrGLsizei width,\n                            GrGLsizei height, GrGLsizei depth, GrGLenum format,\n                            GrGLenum type, const GrGLvoid* data);\n  GrGLvoid ColorMask(GrGLboolean red, GrGLboolean green, GrGLboolean blue,\n                     GrGLboolean alpha);\n  GrGLvoid CompileShader(GrGLuint shader);\n  GrGLvoid CompressedTexImage2D(GrGLenum target, GrGLint level,\n                                GrGLenum internalformat, GrGLsizei width,\n                                GrGLsizei height, GrGLint border,\n                                GrGLsizei imageSize, const GrGLvoid* data);\n  GrGLvoid CompressedTexSubImage2D(GrGLenum target, GrGLint level,\n                                   GrGLint xoffset, GrGLint yoffset,\n                                   GrGLsizei width, GrGLsizei height,\n                                   GrGLenum format, GrGLsizei imageSize,\n                                   const GrGLvoid* data);\n  GrGLvoid CopyTexSubImage2D(GrGLenum target, GrGLint level, GrGLint xoffset,\n                             GrGLint yoffset, GrGLint x, GrGLint y,\n                             GrGLsizei width, GrGLsizei height);\n  GrGLuint CreateProgram();\n  GrGLuint CreateShader(GrGLenum type);\n  GrGLvoid CullFace(GrGLenum mode);\n  GrGLvoid DeleteBuffers(GrGLsizei n, const GrGLuint* buffers);\n  GrGLvoid DeleteFences(GrGLsizei n, const GrGLuint* fences);\n  GrGLvoid DeleteFramebuffers(GrGLsizei n, const GrGLuint* framebuffers);\n  GrGLvoid DeleteProgram(GrGLuint program);\n  GrGLvoid DeleteQueries(GrGLsizei n, const GrGLuint* ids);\n  GrGLvoid DeleteRenderbuffers(GrGLsizei n, const GrGLuint* renderbuffers);\n  GrGLvoid DeleteSamplers(GrGLsizei count, const GrGLuint* samplers);\n  GrGLvoid DeleteShader(GrGLuint shader);\n  GrGLvoid DeleteTextures(GrGLsizei n, const GrGLuint* textures);\n  GrGLvoid DeleteVertexArrays(GrGLsizei n, const GrGLuint* arrays);\n  GrGLvoid DepthMask(GrGLboolean flag);\n  GrGLvoid Disable(GrGLenum cap);\n  GrGLvoid DisableVertexAttribArray(GrGLuint index);\n  GrGLvoid DrawArrays(GrGLenum mode, GrGLint first, GrGLsizei count);\n  GrGLvoid DrawArraysInstanced(GrGLenum mode, GrGLint first, GrGLsizei count,\n                               GrGLsizei primcount);\n  GrGLvoid DrawArraysIndirect(GrGLenum mode, const GrGLvoid* indirect);\n  GrGLvoid DrawBuffer(GrGLenum mode);\n  GrGLvoid DrawBuffers(GrGLsizei n, const GrGLenum* bufs);\n  GrGLvoid DrawElements(GrGLenum mode, GrGLsizei count, GrGLenum type,\n                        const GrGLvoid* indices);\n  GrGLvoid DrawElementsInstanced(GrGLenum mode, GrGLsizei count, GrGLenum type,\n                                 const GrGLvoid* indices, GrGLsizei primcount);\n  GrGLvoid DrawElementsIndirect(GrGLenum mode, GrGLenum type,\n                                const GrGLvoid* indirect);\n  GrGLvoid DrawRangeElements(GrGLenum mode, GrGLuint start, GrGLuint end,\n                             GrGLsizei count, GrGLenum type,\n                             const GrGLvoid* indices);\n  GrGLvoid Enable(GrGLenum cap);\n  GrGLvoid EnableVertexAttribArray(GrGLuint index);\n  GrGLvoid EndQuery(GrGLenum target);\n  GrGLvoid Finish();\n  GrGLvoid FinishFence(GrGLuint fence);\n  GrGLvoid Flush();\n  GrGLvoid FlushMappedBufferRange(GrGLenum target, GrGLintptr offset,\n                                  GrGLsizeiptr length);\n  GrGLvoid FramebufferRenderbuffer(GrGLenum target, GrGLenum attachment,\n                                   GrGLenum renderbuffertarget,\n                                   GrGLuint renderbuffer);\n  GrGLvoid FramebufferTexture2D(GrGLenum target, GrGLenum attachment,\n                                GrGLenum textarget, GrGLuint texture,\n                                GrGLint level);\n  GrGLvoid FramebufferTexture2DMultisample(GrGLenum target, GrGLenum attachment,\n                                           GrGLenum textarget, GrGLuint texture,\n                                           GrGLint level, GrGLsizei samples);\n  GrGLvoid FrontFace(GrGLenum mode);\n  GrGLvoid GenBuffers(GrGLsizei n, GrGLuint* buffers);\n  GrGLvoid GenFences(GrGLsizei n, GrGLuint* fences);\n  GrGLvoid GenFramebuffers(GrGLsizei n, GrGLuint* framebuffers);\n  GrGLvoid GenerateMipmap(GrGLenum target);\n  GrGLvoid GenQueries(GrGLsizei n, GrGLuint* ids);\n  GrGLvoid GenRenderbuffers(GrGLsizei n, GrGLuint* renderbuffers);\n  GrGLvoid GenSamplers(GrGLsizei count, GrGLuint* samplers);\n  GrGLvoid GenTextures(GrGLsizei n, GrGLuint* textures);\n  GrGLvoid GenVertexArrays(GrGLsizei n, GrGLuint* arrays);\n  GrGLvoid GetBufferParameteriv(GrGLenum target, GrGLenum pname,\n                                GrGLint* params);\n  GrGLenum GetError();\n  GrGLvoid GetFramebufferAttachmentParameteriv(GrGLenum target,\n                                               GrGLenum attachment,\n                                               GrGLenum pname, GrGLint* params);\n  GrGLvoid GetIntegerv(GrGLenum pname, GrGLint* params);\n  GrGLvoid GetMultisamplefv(GrGLenum pname, GrGLuint index, GrGLfloat* val);\n  GrGLvoid GetProgramBinary(GrGLuint program, GrGLsizei bufsize,\n                            GrGLsizei* length, GrGLenum* binaryFormat,\n                            void* binary);\n  GrGLvoid GetProgramInfoLog(GrGLuint program, GrGLsizei bufsize,\n                             GrGLsizei* length, char* infolog);\n  GrGLvoid GetProgramiv(GrGLuint program, GrGLenum pname, GrGLint* params);\n  GrGLvoid GetQueryiv(GrGLenum GLtarget, GrGLenum pname, GrGLint* params);\n  GrGLvoid GetQueryObjecti64v(GrGLuint id, GrGLenum pname, GrGLint64* params);\n  GrGLvoid GetQueryObjectiv(GrGLuint id, GrGLenum pname, GrGLint* params);\n  GrGLvoid GetQueryObjectui64v(GrGLuint id, GrGLenum pname, GrGLuint64* params);\n  GrGLvoid GetQueryObjectuiv(GrGLuint id, GrGLenum pname, GrGLuint* params);\n  GrGLvoid GetRenderbufferParameteriv(GrGLenum target, GrGLenum pname,\n                                      GrGLint* params);\n  GrGLvoid GetShaderInfoLog(GrGLuint shader, GrGLsizei bufsize,\n                            GrGLsizei* length, char* infolog);\n  GrGLvoid GetShaderiv(GrGLuint shader, GrGLenum pname, GrGLint* params);\n  GrGLvoid GetShaderPrecisionFormat(GrGLenum shadertype, GrGLenum precisiontype,\n                                    GrGLint* range, GrGLint* precision);\n  const GrGLubyte* GetString(GrGLenum name);\n  const GrGLubyte* GetStringi(GrGLenum name, GrGLuint index);\n  GrGLvoid GetTexLevelParameteriv(GrGLenum target, GrGLint level,\n                                  GrGLenum pname, GrGLint* params);\n  GrGLint GetUniformLocation(GrGLuint program, const char* name);\n  GrGLvoid InsertEventMarker(GrGLsizei length, const char* marker);\n  GrGLvoid InvalidateBufferData(GrGLuint buffer);\n  GrGLvoid InvalidateBufferSubData(GrGLuint buffer, GrGLintptr offset,\n                                   GrGLsizeiptr length);\n  GrGLvoid InvalidateFramebuffer(GrGLenum target, GrGLsizei numAttachments,\n                                 const GrGLenum* attachments);\n  GrGLvoid InvalidateSubFramebuffer(GrGLenum target, GrGLsizei numAttachments,\n                                    const GrGLenum* attachments, GrGLint x,\n                                    GrGLint y, GrGLsizei width,\n                                    GrGLsizei height);\n  GrGLvoid InvalidateTexImage(GrGLuint texture, GrGLint level);\n  GrGLvoid InvalidateTexSubImage(GrGLuint texture, GrGLint level,\n                                 GrGLint xoffset, GrGLint yoffset,\n                                 GrGLint zoffset, GrGLsizei width,\n                                 GrGLsizei height, GrGLsizei depth);\n  GrGLboolean IsTexture(GrGLuint texture);\n  GrGLvoid LineWidth(GrGLfloat width);\n  GrGLvoid LinkProgram(GrGLuint program);\n  GrGLvoid* MapBuffer(GrGLenum target, GrGLenum access);\n  GrGLvoid* MapBufferRange(GrGLenum target, GrGLintptr offset,\n                           GrGLsizeiptr length, GrGLbitfield access);\n  GrGLvoid* MapBufferSubData(GrGLuint target, GrGLintptr offset,\n                             GrGLsizeiptr size, GrGLenum access);\n  GrGLvoid* MapTexSubImage2D(GrGLenum target, GrGLint level, GrGLint xoffset,\n                             GrGLint yoffset, GrGLsizei width, GrGLsizei height,\n                             GrGLenum format, GrGLenum type, GrGLenum access);\n  GrGLvoid* MemoryBarrier(GrGLbitfield barriers);\n  GrGLvoid PatchParameteri(GrGLenum pname, GrGLint value);\n  GrGLvoid PixelStorei(GrGLenum pname, GrGLint param);\n  GrGLvoid PolygonMode(GrGLenum face, GrGLenum mode);\n  GrGLvoid PopGroupMarker();\n  GrGLvoid ProgramBinary(GrGLuint program, GrGLenum binaryFormat, void* binary,\n                         GrGLsizei length);\n  GrGLvoid ProgramParameteri(GrGLuint program, GrGLenum pname, GrGLint value);\n  GrGLvoid PushGroupMarker(GrGLsizei length, const char* marker);\n  GrGLvoid QueryCounter(GrGLuint id, GrGLenum target);\n  GrGLvoid ReadBuffer(GrGLenum src);\n  GrGLvoid ReadPixels(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height,\n                      GrGLenum format, GrGLenum type, GrGLvoid* pixels);\n  GrGLvoid RenderbufferStorage(GrGLenum target, GrGLenum internalformat,\n                               GrGLsizei width, GrGLsizei height);\n  GrGLvoid RenderbufferStorageMultisample(GrGLenum target, GrGLsizei samples,\n                                          GrGLenum internalformat,\n                                          GrGLsizei width, GrGLsizei height);\n  GrGLvoid ResolveMultisampleFramebuffer();\n  GrGLvoid SamplerParameteri(GrGLuint sampler, GrGLenum pname, GrGLint params);\n  GrGLvoid SamplerParameteriv(GrGLuint sampler, GrGLenum pname,\n                              const GrGLint* params);\n  GrGLvoid Scissor(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height);\n  // GL_CHROMIUM_bind_uniform_location\n  GrGLvoid BindUniformLocation(GrGLuint program, GrGLint location,\n                               const char* name);\n  GrGLvoid SetFence(GrGLuint fence, GrGLenum condition);\n  GrGLvoid ShaderSource(GrGLuint shader, GrGLsizei count,\n                        const char* const* str, const GrGLint* length);\n  GrGLvoid StencilFunc(GrGLenum func, GrGLint ref, GrGLuint mask);\n  GrGLvoid StencilFuncSeparate(GrGLenum face, GrGLenum func, GrGLint ref,\n                               GrGLuint mask);\n  GrGLvoid StencilMask(GrGLuint mask);\n  GrGLvoid StencilMaskSeparate(GrGLenum face, GrGLuint mask);\n  GrGLvoid StencilOp(GrGLenum fail, GrGLenum zfail, GrGLenum zpass);\n  GrGLvoid StencilOpSeparate(GrGLenum face, GrGLenum fail, GrGLenum zfail,\n                             GrGLenum zpass);\n  GrGLvoid TexBuffer(GrGLenum target, GrGLenum internalformat, GrGLuint buffer);\n  GrGLvoid TexBufferRange(GrGLenum target, GrGLenum internalformat,\n                          GrGLuint buffer, GrGLintptr offset,\n                          GrGLsizeiptr size);\n  GrGLvoid TexImage2D(GrGLenum target, GrGLint level, GrGLint internalformat,\n                      GrGLsizei width, GrGLsizei height, GrGLint border,\n                      GrGLenum format, GrGLenum type, const GrGLvoid* pixels);\n  GrGLvoid TexParameterf(GrGLenum target, GrGLenum pname, GrGLfloat param);\n  GrGLvoid TexParameterfv(GrGLenum target, GrGLenum pname,\n                          const GrGLfloat* params);\n  GrGLvoid TexParameteri(GrGLenum target, GrGLenum pname, GrGLint param);\n  GrGLvoid TexParameteriv(GrGLenum target, GrGLenum pname,\n                          const GrGLint* params);\n  GrGLvoid TexStorage2D(GrGLenum target, GrGLsizei levels,\n                        GrGLenum internalformat, GrGLsizei width,\n                        GrGLsizei height);\n  GrGLvoid DiscardFramebuffer(GrGLenum target, GrGLsizei numAttachments,\n                              const GrGLenum* attachments);\n  GrGLboolean TestFence(GrGLuint fence);\n  GrGLvoid TexSubImage2D(GrGLenum target, GrGLint level, GrGLint xoffset,\n                         GrGLint yoffset, GrGLsizei width, GrGLsizei height,\n                         GrGLenum format, GrGLenum type,\n                         const GrGLvoid* pixels);\n  GrGLvoid TextureBarrier();\n  GrGLvoid Uniform1f(GrGLint location, GrGLfloat v0);\n  GrGLvoid Uniform1i(GrGLint location, GrGLint v0);\n  GrGLvoid Uniform1fv(GrGLint location, GrGLsizei count, const GrGLfloat* v);\n  GrGLvoid Uniform1iv(GrGLint location, GrGLsizei count, const GrGLint* v);\n  GrGLvoid Uniform2f(GrGLint location, GrGLfloat v0, GrGLfloat v1);\n  GrGLvoid Uniform2i(GrGLint location, GrGLint v0, GrGLint v1);\n  GrGLvoid Uniform2fv(GrGLint location, GrGLsizei count, const GrGLfloat* v);\n  GrGLvoid Uniform2iv(GrGLint location, GrGLsizei count, const GrGLint* v);\n  GrGLvoid Uniform3f(GrGLint location, GrGLfloat v0, GrGLfloat v1,\n                     GrGLfloat v2);\n  GrGLvoid Uniform3i(GrGLint location, GrGLint v0, GrGLint v1, GrGLint v2);\n  GrGLvoid Uniform3fv(GrGLint location, GrGLsizei count, const GrGLfloat* v);\n  GrGLvoid Uniform3iv(GrGLint location, GrGLsizei count, const GrGLint* v);\n  GrGLvoid Uniform4f(GrGLint location, GrGLfloat v0, GrGLfloat v1, GrGLfloat v2,\n                     GrGLfloat v3);\n  GrGLvoid Uniform4i(GrGLint location, GrGLint v0, GrGLint v1, GrGLint v2,\n                     GrGLint v3);\n  GrGLvoid Uniform4fv(GrGLint location, GrGLsizei count, const GrGLfloat* v);\n  GrGLvoid Uniform4iv(GrGLint location, GrGLsizei count, const GrGLint* v);\n  GrGLvoid UniformMatrix2fv(GrGLint location, GrGLsizei count,\n                            GrGLboolean transpose, const GrGLfloat* value);\n  GrGLvoid UniformMatrix3fv(GrGLint location, GrGLsizei count,\n                            GrGLboolean transpose, const GrGLfloat* value);\n  GrGLvoid UniformMatrix4fv(GrGLint location, GrGLsizei count,\n                            GrGLboolean transpose, const GrGLfloat* value);\n  GrGLboolean UnmapBuffer(GrGLenum target);\n  GrGLvoid UnmapBufferSubData(const GrGLvoid* mem);\n  GrGLvoid UnmapTexSubImage2D(const GrGLvoid* mem);\n  GrGLvoid UseProgram(GrGLuint program);\n  GrGLvoid VertexAttrib1f(GrGLuint indx, const GrGLfloat value);\n  GrGLvoid VertexAttrib2fv(GrGLuint indx, const GrGLfloat* values);\n  GrGLvoid VertexAttrib3fv(GrGLuint indx, const GrGLfloat* values);\n  GrGLvoid VertexAttrib4fv(GrGLuint indx, const GrGLfloat* values);\n  GrGLvoid VertexAttribDivisor(GrGLuint index, GrGLuint divisor);\n  GrGLvoid VertexAttribIPointer(GrGLuint indx, GrGLint size, GrGLenum type,\n                                GrGLsizei stride, const GrGLvoid* ptr);\n  GrGLvoid VertexAttribPointer(GrGLuint indx, GrGLint size, GrGLenum type,\n                               GrGLboolean normalized, GrGLsizei stride,\n                               const GrGLvoid* ptr);\n  GrGLvoid Viewport(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height);\n\n  /* GL_NV_framebuffer_mixed_samples */\n  GrGLvoid CoverageModulation(GrGLenum components);\n\n  /* EXT_base_instance */\n  GrGLvoid DrawArraysInstancedBaseInstance(GrGLenum mode, GrGLint first,\n                                           GrGLsizei count,\n                                           GrGLsizei instancecount,\n                                           GrGLuint baseinstance);\n  GrGLvoid DrawElementsInstancedBaseVertexBaseInstance(\n      GrGLenum mode, GrGLsizei count, GrGLenum type, const void* indices,\n      GrGLsizei instancecount, GrGLint basevertex, GrGLuint baseinstance);\n\n  /* EXT_multi_draw_indirect */\n  GrGLvoid MultiDrawArraysIndirect(GrGLenum mode, const GrGLvoid* indirect,\n                                   GrGLsizei drawcount, GrGLsizei stride);\n  GrGLvoid MultiDrawElementsIndirect(GrGLenum mode, GrGLenum type,\n                                     const GrGLvoid* indirect,\n                                     GrGLsizei drawcount, GrGLsizei stride);\n\n  /* ANGLE_base_vertex_base_instance */\n  GrGLvoid MultiDrawArraysInstancedBaseInstance(GrGLenum mode,\n                                                const GrGLint* firsts,\n                                                const GrGLsizei* counts,\n                                                const GrGLsizei* instanceCounts,\n                                                const GrGLuint* baseInstances,\n                                                const GrGLsizei drawcount);\n  GrGLvoid MultiDrawElementsInstancedBaseVertexBaseInstance(\n      GrGLenum mode, const GrGLint* counts, GrGLenum type,\n      const GrGLvoid* const* indices, const GrGLsizei* instanceCounts,\n      const GrGLint* baseVertices, const GrGLuint* baseInstances,\n      const GrGLsizei drawcount);\n\n  /* ARB_sync */\n  GrGLsync FenceSync(GrGLenum condition, GrGLbitfield flags);\n  GrGLboolean IsSync(GrGLsync sync);\n  GrGLenum ClientWaitSync(GrGLsync sync, GrGLbitfield flags,\n                          GrGLuint64 timeout);\n  GrGLvoid WaitSync(GrGLsync sync, GrGLbitfield flags, GrGLuint64 timeout);\n  GrGLvoid DeleteSync(GrGLsync sync);\n\n  /* ARB_internalformat_query */\n  GrGLvoid GetInternalformativ(GrGLenum target, GrGLenum internalformat,\n                               GrGLenum pname, GrGLsizei bufSize,\n                               GrGLint* params);\n\n  /* KHR_debug */\n  GrGLvoid DebugMessageControl(GrGLenum source, GrGLenum type,\n                               GrGLenum severity, GrGLsizei count,\n                               const GrGLuint* ids, GrGLboolean enabled);\n  GrGLvoid DebugMessageInsert(GrGLenum source, GrGLenum type, GrGLuint id,\n                              GrGLenum severity, GrGLsizei length,\n                              const GrGLchar* buf);\n  GrGLvoid DebugMessageCallback(GRGLDEBUGPROC callback,\n                                const GrGLvoid* userParam);\n  GrGLuint GetDebugMessageLog(GrGLuint count, GrGLsizei bufSize,\n                              GrGLenum* sources, GrGLenum* types, GrGLuint* ids,\n                              GrGLenum* severities, GrGLsizei* lengths,\n                              GrGLchar* messageLog);\n  GrGLvoid PushDebugGroup(GrGLenum source, GrGLuint id, GrGLsizei length,\n                          const GrGLchar* message);\n  GrGLvoid PopDebugGroup();\n  GrGLvoid ObjectLabel(GrGLenum identifier, GrGLuint name, GrGLsizei length,\n                       const GrGLchar* label);\n\n  /** EXT_window_rectangles */\n  GrGLvoid WindowRectangles(GrGLenum mode, GrGLsizei count,\n                            const GrGLint box[]);\n\n  /** GL_QCOM_tiled_rendering */\n  GrGLvoid StartTiling(GrGLuint x, GrGLuint y, GrGLuint width, GrGLuint height,\n                       GrGLbitfield preserveMask);\n  GrGLvoid EndTiling(GrGLbitfield preserveMask);\n\n private:\n  struct TextureInfo {\n    bool inited = false;\n    GrGLsizei width;\n    GrGLsizei height;\n    GrGLenum format;\n    uint32_t size;\n  };\n\n  // If current target has no binding texture, returns null.\n  // Otherwise returns pointer to an TextureInfo.\n  TextureInfo* GetTextureInfo(GrGLenum target, GrGLint level);\n\n  TextureInfo* SetTextureInfo(GrGLenum target, GrGLint level,\n                              GrGLint internalformat, GrGLsizei width,\n                              GrGLsizei height);\n\n  void LogTexUsage();\n  void LogTexUploadSize();\n\n  GrGLInterface::Functions real_functions_;\n  uint32_t fbo_changed_;\n  uint64_t current_texture_size_;\n  uint64_t total_tex_upload_size_;\n  std::unordered_map<GrGLenum, GrGLuint> target_to_texture_;\n  std::unordered_map<GrGLuint, std::vector<TextureInfo>>\n      texture_infos_;  // maps texture_id, level to TextureInfo\n};\n\n}  // namespace clay\n#endif  // CLAY_SHELL_GPU_TRACE_GL_FUCTIONS_H_\n"
  },
  {
    "path": "clay/shell/platform/common/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../common/config.gni\")\nimport(\"../../../testing/testing.gni\")\n\nsource_set(\"common_cpp_input\") {\n  public = [\n    \"text_editing_delta.h\",\n    \"text_input_model.h\",\n    \"text_range.h\",\n  ]\n\n  sources = [\n    \"text_editing_delta.cc\",\n    \"text_editing_delta.h\",\n    \"text_input_model.cc\",\n    \"text_input_model.h\",\n    \"text_range.h\",\n  ]\n\n  public_configs = [ \"../../../:config\" ]\n\n  deps = [ \"../../../fml:fml\" ]\n}\n\nsource_set(\"common_cpp_enums\") {\n  public = [ \"platform_provided_menu.h\" ]\n\n  public_configs = [ \"../../../:config\" ]\n}\n\nsource_set(\"common_cpp_switches\") {\n  public = [ \"engine_switches.h\" ]\n\n  sources = [\n    \"engine_switches.cc\",\n    \"engine_switches.h\",\n  ]\n\n  public_configs = [ \"../../../:config\" ]\n}\n\n# The portion of common_cpp that has no dependencies on the public/\n# headers. This division should be revisited once the Linux GTK\n# embedding is further along and it's clearer how much, if any, shared\n# API surface there will be.\nsource_set(\"common_cpp_core\") {\n  public = [ \"path_utils.h\" ]\n\n  sources = [ \"path_utils.h\" ]\n\n  if (!is_android) {\n    sources += [ \"path_utils.cc\" ]\n  }\n\n  public_configs = [ \"../../../:config\" ]\n}\n\nif (enable_unittests) {\n  test_fixtures(\"common_cpp_core_fixtures\") {\n    fixtures = []\n  }\n\n  executable(\"common_cpp_core_unittests\") {\n    testonly = true\n\n    sources = [ \"path_utils_unittests.cc\" ]\n\n    deps = [\n      \":common_cpp_core\",\n      \":common_cpp_core_fixtures\",\n      \"../../../../base/src:base_static\",\n      \"../../../testing\",\n    ]\n\n    public_configs = [ \"../../../../clay:config\" ]\n  }\n\n  test_fixtures(\"common_cpp_fixtures\") {\n    fixtures = []\n  }\n\n  executable(\"common_cpp_unittests\") {\n    testonly = true\n\n    sources = [\n      \"engine_switches_unittests.cc\",\n      \"text_editing_delta_unittests.cc\",\n      \"text_input_model_unittests.cc\",\n      \"text_range_unittests.cc\",\n    ]\n\n    deps = [\n      \":common_cpp_input\",\n      \":common_cpp_switches\",\n      \"../../../../base/src:base_static\",\n      \"../../../fml:string_conversion\",\n      \"../../../testing\",\n      \"../../../ui:ui_fixture_sources\",\n    ]\n\n    public_configs = [ \"../../../../clay:config\" ]\n  }\n}\n"
  },
  {
    "path": "clay/shell/platform/common/engine_switches.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/common/engine_switches.h\"\n\n#include <algorithm>\n#include <cstdlib>\n#include <iostream>\n#include <sstream>\n\nnamespace clay {\n\nstd::vector<std::string> GetSwitchesFromEnvironment() {\n  std::vector<std::string> switches;\n  // Read engine switches from the environment in debug/profile. If release mode\n  // support is needed in the future, it should likely use a whitelist.\n#ifndef FLUTTER_RELEASE\n  const char* switch_count_key = \"FLUTTER_ENGINE_SWITCHES\";\n  const int kMaxSwitchCount = 50;\n  const char* switch_count_string = std::getenv(switch_count_key);\n  if (!switch_count_string) {\n    return switches;\n  }\n  int switch_count = std::min(kMaxSwitchCount, atoi(switch_count_string));\n  for (int i = 1; i <= switch_count; ++i) {\n    std::ostringstream switch_key;\n    switch_key << \"FLUTTER_ENGINE_SWITCH_\" << i;\n    const char* switch_value = std::getenv(switch_key.str().c_str());\n    if (switch_value) {\n      std::ostringstream switch_value_as_flag;\n      switch_value_as_flag << \"--\" << switch_value;\n      switches.push_back(switch_value_as_flag.str());\n    } else {\n      std::cerr << switch_count << \" keys expected from \" << switch_count_key\n                << \", but \" << switch_key.str() << \" is missing.\" << std::endl;\n    }\n  }\n#endif  // !FLUTTER_RELEASE\n  return switches;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/common/engine_switches.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_COMMON_ENGINE_SWITCHES_H_\n#define CLAY_SHELL_PLATFORM_COMMON_ENGINE_SWITCHES_H_\n\n#include <string>\n#include <vector>\n\nnamespace clay {\n\n// Returns an array of engine switches suitable to pass to the embedder API\n// in Args, based on parsing variables from the environment in the form:\n//   ENGINE_SWITCHES=<count>\n//   ENGINE_SWITCH_1=...\n//   ENGINE_SWITCH_2=...\n//   ...\n// Values should match those in shell/common/switches.h\n//\n// The returned array does not include the initial dummy argument expected by\n// the embedder API, so command_line_argv should not be set directly from it.\n//\n// In release mode, not all switches from the environment will necessarily be\n// returned. See the implementation for details.\nstd::vector<std::string> GetSwitchesFromEnvironment();\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_COMMON_ENGINE_SWITCHES_H_\n"
  },
  {
    "path": "clay/shell/platform/common/engine_switches_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"clay/shell/platform/common/engine_switches.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nnamespace {\n// Sets |key=value| in the environment of this process.\nvoid SetEnvironmentVariable(const char* key, const char* value) {\n#ifdef _WIN32\n  _putenv_s(key, value);\n#else\n  setenv(key, value, 1);\n#endif\n}\n\n// Removes |key| from the environment of this process, if present.\nvoid ClearEnvironmentVariable(const char* key) {\n#ifdef _WIN32\n  _putenv_s(key, \"\");\n#else\n  unsetenv(key);\n#endif\n}\n}  // namespace\n\nTEST(FlutterProjectBundle, SwitchesEmpty) {\n  // Clear the main environment variable, since test order is not guaranteed.\n  ClearEnvironmentVariable(\"FLUTTER_ENGINE_SWITCHES\");\n\n  EXPECT_EQ(GetSwitchesFromEnvironment().size(), 0U);\n}\n\n#ifdef FLUTTER_RELEASE\nTEST(FlutterProjectBundle, SwitchesIgnoredInRelease) {\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCHES\", \"2\");\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_1\", \"abc\");\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_2\", \"foo=\\\"bar, baz\\\"\");\n\n  std::vector<std::string> switches = GetSwitchesFromEnvironment();\n  EXPECT_EQ(switches.size(), 0U);\n}\n#endif  // FLUTTER_RELEASE\n\n#ifndef FLUTTER_RELEASE\nTEST(FlutterProjectBundle, Switches) {\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCHES\", \"2\");\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_1\", \"abc\");\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_2\", \"foo=\\\"bar, baz\\\"\");\n\n  std::vector<std::string> switches = GetSwitchesFromEnvironment();\n  EXPECT_EQ(switches.size(), 2U);\n  EXPECT_EQ(switches[0], \"--abc\");\n  EXPECT_EQ(switches[1], \"--foo=\\\"bar, baz\\\"\");\n}\n\nTEST(FlutterProjectBundle, SwitchesExtraValues) {\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCHES\", \"1\");\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_1\", \"abc\");\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_2\", \"foo=\\\"bar, baz\\\"\");\n\n  std::vector<std::string> switches = GetSwitchesFromEnvironment();\n  EXPECT_EQ(switches.size(), 1U);\n  EXPECT_EQ(switches[0], \"--abc\");\n}\n\nTEST(FlutterProjectBundle, SwitchesMissingValues) {\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCHES\", \"4\");\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_1\", \"abc\");\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_2\", \"foo=\\\"bar, baz\\\"\");\n  ClearEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_3\");\n  SetEnvironmentVariable(\"FLUTTER_ENGINE_SWITCH_4\", \"oops\");\n\n  std::vector<std::string> switches = GetSwitchesFromEnvironment();\n  EXPECT_EQ(switches.size(), 3U);\n  EXPECT_EQ(switches[0], \"--abc\");\n  EXPECT_EQ(switches[1], \"--foo=\\\"bar, baz\\\"\");\n  // The missing switch should be skipped, leaving SWITCH_4 as the third\n  // switch in the array.\n  EXPECT_EQ(switches[2], \"--oops\");\n}\n#endif  // !FLUTTER_RELEASE\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/common/path_utils.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/common/path_utils.h\"\n\n#include <string>\n\n#if defined(_WIN32)\n#include <windows.h>\n#elif defined(__linux__)\n#include <linux/limits.h>\n#include <unistd.h>\n#endif\n\nnamespace clay {\n\nstd::filesystem::path GetExecutableDirectory() {\n#if defined(_WIN32)\n  wchar_t buffer[MAX_PATH];\n  if (GetModuleFileName(nullptr, buffer, MAX_PATH) == 0) {\n    return std::filesystem::path();\n  }\n  std::filesystem::path executable_path(buffer);\n  return executable_path.remove_filename();\n#elif defined(__linux__)\n  char buffer[PATH_MAX + 1];\n  ssize_t length = readlink(\"/proc/self/exe\", buffer, sizeof(buffer));\n  if (length > PATH_MAX) {\n    return std::filesystem::path();\n  }\n  std::filesystem::path executable_path(std::string(buffer, length));\n  return executable_path.remove_filename();\n#else\n  return std::filesystem::path();\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/common/path_utils.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_COMMON_PATH_UTILS_H_\n#define CLAY_SHELL_PLATFORM_COMMON_PATH_UTILS_H_\n\n#include <filesystem>\n\nnamespace clay {\n\n// Returns the path of the directory containing this executable, or an empty\n// path if the directory cannot be found.\nstd::filesystem::path GetExecutableDirectory();\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_COMMON_PATH_UTILS_H_\n"
  },
  {
    "path": "clay/shell/platform/common/path_utils_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"clay/shell/platform/common/path_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\n// Tests that GetExecutableDirectory returns a valid, absolute path.\nTEST(PathUtilsTest, ExecutableDirector) {\n#if !defined(OS_ANDROID)\n  std::filesystem::path exe_directory = GetExecutableDirectory();\n#if defined(__linux__) || defined(_WIN32)\n  EXPECT_EQ(exe_directory.empty(), false);\n  EXPECT_EQ(exe_directory.is_absolute(), true);\n#else\n  // On platforms where it's not implemented, it should indicate that\n  // by returning an empty path.\n  EXPECT_EQ(exe_directory.empty(), true);\n#endif\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/common/platform_provided_menu.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_COMMON_PLATFORM_PROVIDED_MENU_H_\n#define CLAY_SHELL_PLATFORM_COMMON_PLATFORM_PROVIDED_MENU_H_\n\nnamespace clay {\n\n// Enumerates the provided menus that a platform may support.\nenum class PlatformProvidedMenu {\n  // orderFrontStandardAboutPanel macOS provided menu\n  kAbout,\n\n  // terminate macOS provided menu\n  kQuit,\n\n  // Services macOS provided submenu.\n  kServicesSubmenu,\n\n  // hide macOS provided menu\n  kHide,\n\n  // hideOtherApplications macOS provided menu\n  kHideOtherApplications,\n\n  // unhideAllApplications macOS provided menu\n  kShowAllApplications,\n\n  // startSpeaking macOS provided menu\n  kStartSpeaking,\n\n  // stopSpeaking macOS provided menu\n  kStopSpeaking,\n\n  // toggleFullScreen macOS provided menu\n  kToggleFullScreen,\n\n  // performMiniaturize macOS provided menu\n  kMinimizeWindow,\n\n  // performZoom macOS provided menu\n  kZoomWindow,\n\n  // arrangeInFront macOS provided menu\n  kArrangeWindowsInFront,\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_COMMON_PLATFORM_PROVIDED_MENU_H_\n"
  },
  {
    "path": "clay/shell/platform/common/text_editing_delta.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/common/text_editing_delta.h\"\n\n#include \"base/include/string/string_utils.h\"\n\nnamespace clay {\n\nTextEditingDelta::TextEditingDelta(const std::u16string& text_before_change,\n                                   const TextRange& range,\n                                   const std::u16string& text)\n    : old_text_(text_before_change),\n      delta_text_(text),\n      delta_start_(range.start()),\n      delta_end_(range.start() + range.length()) {}\n\nTextEditingDelta::TextEditingDelta(const std::string& text_before_change,\n                                   const TextRange& range,\n                                   const std::string& text)\n    : old_text_(lynx::base::U8StringToU16(text_before_change)),\n      delta_text_(lynx::base::U8StringToU16(text)),\n      delta_start_(range.start()),\n      delta_end_(range.start() + range.length()) {}\n\nTextEditingDelta::TextEditingDelta(const std::u16string& text)\n    : old_text_(text), delta_text_(u\"\"), delta_start_(-1), delta_end_(-1) {}\n\nTextEditingDelta::TextEditingDelta(const std::string& text)\n    : old_text_(lynx::base::U8StringToU16(text)),\n      delta_text_(u\"\"),\n      delta_start_(-1),\n      delta_end_(-1) {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/common/text_editing_delta.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_COMMON_TEXT_EDITING_DELTA_H_\n#define CLAY_SHELL_PLATFORM_COMMON_TEXT_EDITING_DELTA_H_\n\n#include <string>\n\n#include \"base/include/string/string_utils.h\"\n#include \"clay/shell/platform/common/text_range.h\"\n\nnamespace clay {\n\n/// A change in the state of an input field.\nstruct TextEditingDelta {\n  TextEditingDelta(const std::u16string& text_before_change,\n                   const TextRange& range, const std::u16string& text);\n\n  TextEditingDelta(const std::string& text_before_change,\n                   const TextRange& range, const std::string& text);\n\n  explicit TextEditingDelta(const std::u16string& text);\n\n  explicit TextEditingDelta(const std::string& text);\n\n  virtual ~TextEditingDelta() = default;\n\n  /// Get the old_text_ value.\n  ///\n  /// All strings are stored as UTF16 but converted to UTF8 when accessed.\n  std::string old_text() const { return lynx::base::U16StringToU8(old_text_); }\n\n  /// Get the delta_text value.\n  ///\n  /// All strings are stored as UTF16 but converted to UTF8 when accessed.\n  std::string delta_text() const {\n    return lynx::base::U16StringToU8(delta_text_);\n  }\n\n  /// Get the delta_start_ value.\n  int delta_start() const { return delta_start_; }\n\n  /// Get the delta_end_ value.\n  int delta_end() const { return delta_end_; }\n\n  bool operator==(const TextEditingDelta& rhs) const {\n    return old_text_ == rhs.old_text_ && delta_text_ == rhs.delta_text_ &&\n           delta_start_ == rhs.delta_start_ && delta_end_ == rhs.delta_end_;\n  }\n\n  bool operator!=(const TextEditingDelta& rhs) const { return !(*this == rhs); }\n\n  TextEditingDelta(const TextEditingDelta& other) = default;\n\n  TextEditingDelta& operator=(const TextEditingDelta& other) = default;\n\n private:\n  std::u16string old_text_;\n  std::u16string delta_text_;\n  int delta_start_;\n  int delta_end_;\n\n  void set_old_text(const std::u16string& old_text) { old_text_ = old_text; }\n\n  void set_delta_text(const std::u16string& delta_text) {\n    delta_text_ = delta_text;\n  }\n\n  void set_delta_start(int delta_start) { delta_start_ = delta_start; }\n\n  void set_delta_end(int delta_end) { delta_end_ = delta_end; }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_COMMON_TEXT_EDITING_DELTA_H_\n"
  },
  {
    "path": "clay/shell/platform/common/text_editing_delta_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"clay/shell/platform/common/text_editing_delta.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(TextEditingDeltaTest, TestTextEditingDeltaConstructor) {\n  // Here we are simulating inserting an \"o\" at the end of \"hell\".\n  std::string old_text = \"hell\";\n  std::string replacement_text = \"hello\";\n  TextRange range(0, 4);\n\n  TextEditingDelta delta = TextEditingDelta(old_text, range, replacement_text);\n\n  EXPECT_EQ(delta.old_text(), old_text);\n  EXPECT_EQ(delta.delta_text(), \"hello\");\n  EXPECT_EQ(delta.delta_start(), 0);\n  EXPECT_EQ(delta.delta_end(), 4);\n}\n\nTEST(TextEditingDeltaTest, TestTextEditingDeltaNonTextConstructor) {\n  // Here we are simulating inserting an \"o\" at the end of \"hell\".\n  std::string old_text = \"hello\";\n\n  TextEditingDelta delta = TextEditingDelta(old_text);\n\n  EXPECT_EQ(delta.old_text(), old_text);\n  EXPECT_EQ(delta.delta_text(), \"\");\n  EXPECT_EQ(delta.delta_start(), -1);\n  EXPECT_EQ(delta.delta_end(), -1);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/common/text_input_model.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/common/text_input_model.h\"\n\n#include <algorithm>\n#include <string>\n\n#include \"base/include/string/string_utils.h\"\n\nnamespace clay {\n\nusing clay::Affinity;\n\nTextInputModel::TextInputModel() = default;\n\nTextInputModel::~TextInputModel() = default;\n\nbool TextInputModel::operator==(const TextInputModel& model) const {\n  if (model.GetText() == GetText() && model.selection() == selection() &&\n      model.composing_range() == composing_range() &&\n      model.SelectionAffinity() == SelectionAffinity()) {\n    return true;\n  }\n  return false;\n}\nTextInputModel::TextInputModel(const TextInputModel& text_editing_value) =\n    default;\n\nTextInputModel::TextInputModel(const std::string& text,\n                               const TextRange& selection_range,\n                               const TextRange& composing_range, bool composing,\n                               clay::Affinity selection_affinity)\n    : text_{lynx::base::U8StringToU16(text)},\n      selection_{selection_range},\n      composing_range_{composing_range},\n      composing_(composing),\n      selection_affinity_{selection_affinity} {}\n\nbool TextInputModel::SetText(const std::string& text,\n                             const TextRange& selection,\n                             const TextRange& composing_range) {\n  return SetText(lynx::base::U8StringToU16(text), selection, composing_range);\n}\n\nbool TextInputModel::SetText(const std::u16string& text,\n                             const TextRange& selection,\n                             const TextRange& composing_range) {\n  text_ = text;\n  TrimToLimit();\n  if (!text_range().Contains(selection) ||\n      !text_range().Contains(composing_range)) {\n    return false;\n  }\n\n  selection_ = selection;\n  composing_range_ = composing_range;\n#if !OS_WIN\n  composing_ = !composing_range.collapsed();\n#endif  // !OS_WIN\n  return true;\n}\n\nvoid TextInputModel::SetTextAndReserveSelectionState(const std::string& text) {\n  SetTextAndReserveSelectionState(lynx::base::U8StringToU16(text));\n}\nvoid TextInputModel::SetTextAndReserveSelectionState(\n    const std::u16string& text) {\n  text_ = text;\n  if (!text_range().Contains(selection_)) {\n    selection_ = TextRange{text.length()};\n  }\n}\n\nbool TextInputModel::SetSelection(const TextRange& range) {\n  if (composing_ && !range.collapsed()) {\n    return false;\n  }\n  if (!editable_range().Contains(range)) {\n    return false;\n  }\n  selection_ = range;\n  return true;\n}\n\nbool TextInputModel::SetComposingRange(const TextRange& range,\n                                       size_t cursor_offset) {\n  if (!composing_ || !text_range().Contains(range)) {\n    return false;\n  }\n  composing_range_ = range;\n  selection_ = TextRange(range.start() + cursor_offset);\n  return true;\n}\n\nvoid TextInputModel::BeginComposing() {\n  composing_ = true;\n  composing_range_ = TextRange(selection_.start());\n}\n\nvoid TextInputModel::UpdateComposingText(const std::u16string& text) {\n  // Preserve selection if we get a no-op update to the composing region.\n  if (text.length() == 0 && composing_range_.collapsed()) {\n    return;\n  }\n  DeleteSelected();\n  text_.replace(composing_range_.start(), composing_range_.length(), text);\n  composing_range_.set_end(composing_range_.start() + text.length());\n  selection_ = TextRange(composing_range_.end());\n  TrimToLimit();\n}\n\nvoid TextInputModel::UpdateComposingText(const std::string& text) {\n  UpdateComposingText(lynx::base::U8StringToU16(text));\n}\n\nvoid TextInputModel::CommitComposing() {\n  // Preserve selection if no composing text was entered.\n  if (composing_range_.collapsed()) {\n    return;\n  }\n  composing_range_ = TextRange(composing_range_.end());\n  selection_ = composing_range_;\n}\n\nvoid TextInputModel::EndComposing() {\n  composing_ = false;\n  composing_range_ = TextRange(0);\n}\n\nbool TextInputModel::DeleteSelected() {\n  if (selection_.collapsed()) {\n    return false;\n  }\n  size_t start = selection_.start();\n  text_.erase(start, selection_.length());\n  selection_ = TextRange(start);\n  if (composing_) {\n    // This occurs only immediately after composing has begun with a selection.\n    composing_range_ = selection_;\n  }\n  return true;\n}\n\nvoid TextInputModel::AddCodePoint(char32_t c) {\n  if (c <= 0xFFFF) {\n    AddText(std::u16string({static_cast<char16_t>(c)}));\n  } else {\n    char32_t to_decompose = c - 0x10000;\n    AddText(std::u16string({\n        // High surrogate.\n        static_cast<char16_t>((to_decompose >> 10) + 0xd800),\n        // Low surrogate.\n        static_cast<char16_t>((to_decompose % 0x400) + 0xdc00),\n    }));\n  }\n}\n\nvoid TextInputModel::AddText(const std::u16string& text) {\n  DeleteSelected();\n  if (composing_) {\n    // Delete the current composing text, set the cursor to composing start.\n    text_.erase(composing_range_.start(), composing_range_.length());\n    selection_ = TextRange(composing_range_.start());\n    composing_range_.set_end(composing_range_.start() + text.length());\n  }\n  size_t position = selection_.position();\n  text_.insert(position, text);\n  selection_ = TextRange(position + text.length());\n  TrimToLimit();\n}\n\nvoid TextInputModel::AddText(const std::string& text) {\n  AddText(lynx::base::U8StringToU16(text));\n}\n\nbool TextInputModel::Backspace() {\n  if (DeleteSelected()) {\n    return true;\n  }\n  // There is no selection. Delete the preceding codepoint.\n  size_t position = selection_.position();\n  if (position != editable_range().start()) {\n    int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1;\n    text_.erase(position - count, count);\n    selection_ = TextRange(position - count);\n    if (composing_) {\n      composing_range_.set_end(composing_range_.end() - count);\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid TextInputModel::TrimToLimit() {\n  if (max_length_ <= 0) {\n    return;\n  }\n  // For performance & convience, use utf16 length instead of codepoint count.\n  // `Backspace` would handle surrogate pair case.\n  while (text_.size() > max_length_) {\n    Backspace();\n  }\n}\n\nbool TextInputModel::Delete() {\n  if (DeleteSelected()) {\n    return true;\n  }\n  // There is no selection. Delete the preceding codepoint.\n  size_t position = selection_.position();\n  if (position < editable_range().end()) {\n    int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1;\n    text_.erase(position, count);\n    if (composing_) {\n      composing_range_.set_end(composing_range_.end() - count);\n    }\n    return true;\n  }\n  return false;\n}\n\nbool TextInputModel::DeleteSurrounding(int offset_from_cursor, int count) {\n  size_t max_pos = editable_range().end();\n  size_t start = selection_.extent();\n  if (offset_from_cursor < 0) {\n    for (int i = 0; i < -offset_from_cursor; i++) {\n      // If requested start is before the available text then reduce the\n      // number of characters to delete.\n      if (start == editable_range().start()) {\n        count = i;\n        break;\n      }\n      start -= IsTrailingSurrogate(text_.at(start - 1)) ? 2 : 1;\n    }\n  } else {\n    for (int i = 0; i < offset_from_cursor && start != max_pos; i++) {\n      start += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;\n    }\n  }\n\n  auto end = start;\n  for (int i = 0; i < count && end != max_pos; i++) {\n    end += IsLeadingSurrogate(text_.at(start)) ? 2 : 1;\n  }\n\n  if (start == end) {\n    return false;\n  }\n\n  auto deleted_length = end - start;\n  text_.erase(start, deleted_length);\n\n  // Cursor moves only if deleted area is before it.\n  selection_ = TextRange(offset_from_cursor <= 0 ? start : selection_.start());\n\n  // Adjust composing range.\n  if (composing_) {\n    composing_range_.set_end(composing_range_.end() - deleted_length);\n  }\n  return true;\n}\n\nbool TextInputModel::MoveCursorToBeginning() {\n  size_t min_pos = editable_range().start();\n  if (selection_.collapsed() && selection_.position() == min_pos) {\n    return false;\n  }\n  selection_ = TextRange(min_pos);\n  return true;\n}\n\nbool TextInputModel::MoveCursorToEnd() {\n  size_t max_pos = editable_range().end();\n  if (selection_.collapsed() && selection_.position() == max_pos) {\n    return false;\n  }\n  selection_ = TextRange(max_pos);\n  return true;\n}\n\nbool TextInputModel::SelectToBeginning() {\n  size_t min_pos = editable_range().start();\n  if (selection_.collapsed() && selection_.position() == min_pos) {\n    return false;\n  }\n  selection_ = TextRange(selection_.base(), min_pos);\n  return true;\n}\n\nbool TextInputModel::SelectToEnd() {\n  size_t max_pos = editable_range().end();\n  if (selection_.collapsed() && selection_.position() == max_pos) {\n    return false;\n  }\n  selection_ = TextRange(selection_.base(), max_pos);\n  return true;\n}\n\nbool TextInputModel::MoveCursorForward() {\n  // If there's a selection, move to the end of the selection.\n  if (!selection_.collapsed()) {\n    selection_ = TextRange(selection_.end());\n    return true;\n  }\n  // Otherwise, move the cursor forward.\n  size_t position = selection_.position();\n  if (position != editable_range().end()) {\n    int count = IsLeadingSurrogate(text_.at(position)) ? 2 : 1;\n    selection_ = TextRange(position + count);\n    return true;\n  }\n  return false;\n}\n\nbool TextInputModel::MoveCursorBack() {\n  // If there's a selection, move to the beginning of the selection.\n  if (!selection_.collapsed()) {\n    selection_ = TextRange(selection_.start());\n    return true;\n  }\n  // Otherwise, move the cursor backward.\n  size_t position = selection_.position();\n  if (position != editable_range().start()) {\n    int count = IsTrailingSurrogate(text_.at(position - 1)) ? 2 : 1;\n    selection_ = TextRange(position - count);\n    return true;\n  }\n  return false;\n}\n\nstd::string TextInputModel::GetText() const {\n  return lynx::base::U16StringToU8(text_);\n}\n\nint TextInputModel::GetCursorOffset() const {\n  // Measure the length of the current text up to the selection extent.\n  // There is probably a much more efficient way of doing this.\n  auto leading_text = text_.substr(0, selection_.extent());\n  return lynx::base::U16StringToU8(leading_text).size();\n}\n\nvoid TextInputModel::SetSelectionAffinity(clay::Affinity selection_affinity) {\n  selection_affinity_ = selection_affinity;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/common/text_input_model.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_COMMON_TEXT_INPUT_MODEL_H_\n#define CLAY_SHELL_PLATFORM_COMMON_TEXT_INPUT_MODEL_H_\n\n#include <limits>\n#include <memory>\n#include <string>\n\n#include \"clay/shell/platform/common/text_range.h\"\n#include \"clay/ui/common/editing_misc.h\"\n\nnamespace clay {\n\n// Handles underlying text input state, using a simple ASCII model.\n//\n// Ignores special states like \"insert mode\" for now.\nclass TextInputModel {\n public:\n  TextInputModel();\n  virtual ~TextInputModel();\n  TextInputModel(const TextInputModel& text_editing_value);\n  TextInputModel& operator=(const TextInputModel& text_editing_value) = default;\n  TextInputModel(const std::string& text, const TextRange& selection_range,\n                 const TextRange& composing_range, bool composing,\n                 clay::Affinity affinity);\n  bool operator==(const TextInputModel&) const;\n\n  // Sets the text, as well as the selection and the composing region.\n  //\n  // This method is typically used to update the TextInputModel's editing state\n  // when the Flutter framework sends its latest text editing state.\n  bool SetText(const std::string& text,\n               const TextRange& selection = TextRange(0),\n               const TextRange& composing_range = TextRange(0));\n  bool SetText(const std::u16string& text,\n               const TextRange& selection = TextRange(0),\n               const TextRange& composing_range = TextRange(0));\n\n  void SetTextAndReserveSelectionState(const std::string& text);\n  void SetTextAndReserveSelectionState(const std::u16string& text);\n\n  // Attempts to set the text selection.\n  //\n  // Returns false if the selection is not within the bounds of the text.\n  // While in composing mode, the selection is restricted to the composing\n  // range; otherwise, it is restricted to the length of the text.\n  //\n  // To update both the text and the selection/composing range within the text\n  // (for instance, when the framework sends its latest text editing state),\n  // call |SetText| instead.\n  bool SetSelection(const TextRange& range);\n\n  // Attempts to set the composing range.\n  //\n  // Returns false if the range or offset are out of range for the text, or if\n  // the offset is outside the composing range.\n  //\n  // To update both the text and the selection/composing range within the text\n  // (for instance, when the framework sends its latest text editing state),\n  // call |SetText| instead.\n  bool SetComposingRange(const TextRange& range, size_t cursor_offset);\n\n  void SetSelectionAffinity(clay::Affinity selection_affinity);\n  clay::Affinity SelectionAffinity() const { return selection_affinity_; }\n  // Begins IME composing mode.\n  //\n  // Resets the composing base and extent to the selection start. The existing\n  // selection is preserved in case composing is aborted with no changes. Until\n  // |EndComposing| is called, any further changes to selection base and extent\n  // are restricted to the composing range.\n  void BeginComposing();\n\n  // Replaces the composing range with new UTF-16 text.\n  //\n  // If a selection of non-zero length exists, it is deleted if the composing\n  // text is non-empty. The composing range is adjusted to the length of\n  // |text| and the selection base and offset are set to the end of the\n  // composing range.\n  void UpdateComposingText(const std::u16string& text);\n\n  // Replaces the composing range with new UTF-8 text.\n  //\n  // If a selection of non-zero length exists, it is deleted if the composing\n  // text is non-empty. The composing range is adjusted to the length of\n  // |text| and the selection base and offset are set to the end of the\n  // composing range.\n  void UpdateComposingText(const std::string& text);\n\n  // Commits composing range to the string.\n  //\n  // Causes the composing base and extent to be collapsed to the end of the\n  // range.\n  void CommitComposing();\n\n  // Ends IME composing mode.\n  //\n  // Collapses the composing base and offset to 0.\n  void EndComposing();\n\n  // Adds a Unicode code point.\n  //\n  // Either appends after the cursor (when selection base and extent are the\n  // same), or deletes the selected text, replacing it with the given\n  // code point.\n  void AddCodePoint(char32_t c);\n\n  // Adds UTF-16 text.\n  //\n  // Either appends after the cursor (when selection base and extent are the\n  // same), or deletes the selected text, replacing it with the given text.\n  void AddText(const std::u16string& text);\n\n  // Adds UTF-8 text.\n  //\n  // Either appends after the cursor (when selection base and extent are the\n  // same), or deletes the selected text, replacing it with the given text.\n  void AddText(const std::string& text);\n\n  // Trim content begin from current cursor position until length is restricted\n  // to |max_length_|.\n  void TrimToLimit();\n\n  // Deletes either the selection, or one character ahead of the cursor.\n  //\n  // Deleting one character ahead of the cursor occurs when the selection base\n  // and extent are the same. When composing is active, deletions are\n  // restricted to text between the composing base and extent.\n  //\n  // Returns true if any deletion actually occurred.\n  bool Delete();\n\n  // Deletes text near the cursor.\n  //\n  // A section is made starting at |offset_from_cursor| code points past the\n  // cursor (negative values go before the cursor). |count| code points are\n  // removed. The selection may go outside the bounds of the available text and\n  // will result in only the part selection that covers the available text\n  // being deleted. The existing selection is ignored and removed after this\n  // operation. When composing is active, deletions are restricted to the\n  // composing range.\n  //\n  // Returns true if any deletion actually occurred.\n  bool DeleteSurrounding(int offset_from_cursor, int count);\n\n  // Deletes either the selection, or one character behind the cursor.\n  //\n  // Deleting one character behind the cursor occurs when the selection base\n  // and extent are the same. When composing is active, deletions are\n  // restricted to the text between the composing base and extent.\n  //\n  // Returns true if any deletion actually occurred.\n  bool Backspace();\n\n  // Attempts to move the cursor backward.\n  //\n  // Returns true if the cursor could be moved. If a selection is active, moves\n  // to the start of the selection. If composing is active, motion is\n  // restricted to the composing range.\n  bool MoveCursorBack();\n\n  // Attempts to move the cursor forward.\n  //\n  // Returns true if the cursor could be moved. If a selection is active, moves\n  // to the end of the selection. If composing is active, motion is restricted\n  // to the composing range.\n  bool MoveCursorForward();\n\n  // Attempts to move the cursor to the beginning.\n  //\n  // If composing is active, the cursor is moved to the beginning of the\n  // composing range; otherwise, it is moved to the beginning of the text. If\n  // composing is active, motion is restricted to the composing range.\n  //\n  // Returns true if the cursor could be moved.\n  bool MoveCursorToBeginning();\n\n  // Attempts to move the cursor to the end.\n  //\n  // If composing is active, the cursor is moved to the end of the composing\n  // range; otherwise, it is moved to the end of the text. If composing is\n  // active, motion is restricted to the composing range.\n  //\n  // Returns true if the cursor could be moved.\n  bool MoveCursorToEnd();\n\n  // Attempts to select text from the cursor position to the beginning.\n  //\n  // If composing is active, the selection is applied to the beginning of the\n  // composing range; otherwise, it is applied to the beginning of the text.\n  //\n  // Returns true if the selection could be applied.\n  bool SelectToBeginning();\n\n  // Attempts to select text from the cursor position to the end.\n  //\n  // If composing is active, the selection is applied to the end of the\n  // composing range; otherwise, it is moved to the end of the text.\n  //\n  // Returns true if the selection could be applied.\n  bool SelectToEnd();\n\n  // Gets the current text as UTF-8.\n  std::string GetText() const;\n\n  const std::u16string& GetU16Text() const { return text_; }\n  size_t GetU16Length() const { return text_.length(); }\n\n  bool empty() const { return text_.empty(); }\n\n  // Gets the cursor position as a byte offset in UTF-8 string returned from\n  // GetText().\n  int GetCursorOffset() const;\n\n  // Returns a range covering the entire text.\n  TextRange text_range() const { return TextRange(0, text_.length()); }\n\n  // The current selection.\n  const TextRange& selection() const { return selection_; }\n\n  // The composing range.\n  //\n  // If not in composing mode, returns a collapsed range at position 0.\n  const TextRange& composing_range() const { return composing_range_; }\n\n  // Whether multi-step input composing mode is active.\n  bool composing() const { return composing_; }\n\n  // Stop Composing when there is no composing text\n  void StopComposingIfNecessary() {\n    if (composing_ && composing_range_.collapsed()) {\n      EndComposing();\n    }\n  }\n\n protected:\n  // Deletes the current selection, if any.\n  //\n  // Returns true if any text is deleted. The selection base and extent are\n  // reset to the start of the selected range.\n  bool DeleteSelected();\n\n  // Returns the currently editable text range.\n  //\n  // In composing mode, returns the composing range; otherwise, returns a range\n  // covering the entire text.\n  TextRange editable_range() const {\n    return composing_ ? composing_range_ : text_range();\n  }\n\n  std::u16string text_;\n  TextRange selection_ = TextRange(0);\n  TextRange composing_range_ = TextRange(0);\n  bool composing_ = false;\n  uint32_t max_length_ = std::numeric_limits<uint32_t>::max();\n  clay::Affinity selection_affinity_ = clay::Affinity::kDownstream;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_COMMON_TEXT_INPUT_MODEL_H_\n"
  },
  {
    "path": "clay/shell/platform/common/text_input_model_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include <limits>\n#include <map>\n#include <vector>\n\n#include \"clay/shell/platform/common/text_input_model.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(TextInputModel, SetText) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetTextWideCharacters) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"😄🙃🤪🧐\");\n  EXPECT_STREQ(model->GetText().c_str(), \"😄🙃🤪🧐\");\n}\n\nTEST(TextInputModel, SetTextEmpty) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"\");\n  EXPECT_STREQ(model->GetText().c_str(), \"\");\n}\n\nTEST(TextInputModel, SetTextReplaceText) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n  model->SetText(\"\");\n  EXPECT_STREQ(model->GetText().c_str(), \"\");\n}\n\nTEST(TextInputModel, SetTextResetsSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(3)));\n  EXPECT_EQ(model->selection(), TextRange(3));\n  model->SetText(\"FGHJI\");\n  EXPECT_EQ(model->selection(), TextRange(0));\n}\n\nTEST(TextInputModel, SetSelectionStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionComposingStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->SetSelection(TextRange(1)));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionComposingMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(5)));\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionComposingEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->SetSelection(TextRange(4)));\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionWthExtent) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  EXPECT_EQ(model->selection(), TextRange(1, 4));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionWthExtentComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_FALSE(model->SetSelection(TextRange(1, 4)));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionReverseExtent) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  EXPECT_EQ(model->selection(), TextRange(4, 1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionReverseExtentComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_FALSE(model->SetSelection(TextRange(4, 1)));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetSelectionOutsideString) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_FALSE(model->SetSelection(TextRange(4, 6)));\n  EXPECT_FALSE(model->SetSelection(TextRange(5, 6)));\n  EXPECT_FALSE(model->SetSelection(TextRange(6)));\n}\n\nTEST(TextInputModel, SetSelectionOutsideComposingRange) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_FALSE(model->SetSelection(TextRange(0)));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_FALSE(model->SetSelection(TextRange(5)));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n}\n\nTEST(TextInputModel, SetComposingRangeStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(0, 0), 0));\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetComposingRangeMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(2, 2), 0));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(2));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetComposingRangeEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(5, 5), 0));\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(5));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetComposingRangeWithExtent) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3));\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetComposingRangeReverseExtent) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3));\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SetComposingRangeOutsideString) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_FALSE(model->SetComposingRange(TextRange(4, 6), 0));\n  EXPECT_FALSE(model->SetComposingRange(TextRange(5, 6), 0));\n  EXPECT_FALSE(model->SetComposingRange(TextRange(6, 6), 0));\n}\n\n// Composing sequence with no initial selection and no text input.\nTEST(TextInputModel, CommitComposingNoTextWithNoSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->SetSelection(TextRange(0));\n\n  // Verify no changes on BeginComposing.\n  model->BeginComposing();\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n\n  // Verify no changes on CommitComposing.\n  model->CommitComposing();\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n\n  // Verify no changes on CommitComposing.\n  model->EndComposing();\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\n// Composing sequence with an initial selection and no text input.\nTEST(TextInputModel, CommitComposingNoTextWithSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->SetSelection(TextRange(1, 3));\n\n  // Verify no changes on BeginComposing.\n  model->BeginComposing();\n  EXPECT_EQ(model->selection(), TextRange(1, 3));\n  EXPECT_EQ(model->composing_range(), TextRange(1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n\n  // Verify no changes on CommitComposing.\n  model->CommitComposing();\n  EXPECT_EQ(model->selection(), TextRange(1, 3));\n  EXPECT_EQ(model->composing_range(), TextRange(1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n\n  // Verify no changes on CommitComposing.\n  model->EndComposing();\n  EXPECT_EQ(model->selection(), TextRange(1, 3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\n// Composing sequence with no initial selection.\nTEST(TextInputModel, CommitComposingTextWithNoSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->SetSelection(TextRange(1));\n\n  // Verify no changes on BeginComposing.\n  model->BeginComposing();\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n\n  // Verify selection base, extent and composing extent increment as text is\n  // entered. Verify composing base does not change.\n  model->UpdateComposingText(\"つ\");\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"AつBCDE\");\n  model->UpdateComposingText(\"つる\");\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"AつるBCDE\");\n\n  // Verify that cursor position is set to correct offset from composing base.\n  model->UpdateComposingText(\"-\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1)));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-BCDE\");\n\n  // Verify composing base is set to composing extent on commit.\n  model->CommitComposing();\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(2));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-BCDE\");\n\n  // Verify that further text entry increments the selection base, extent and\n  // the composing extent. Verify that composing base does not change.\n  model->UpdateComposingText(\"が\");\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(2, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-がBCDE\");\n\n  // Verify composing base is set to composing extent on commit.\n  model->CommitComposing();\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(3));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-がBCDE\");\n\n  // Verify no changes on EndComposing.\n  model->EndComposing();\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-がBCDE\");\n}\n\n// Composing sequence with an initial selection.\nTEST(TextInputModel, CommitComposingTextWithSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->SetSelection(TextRange(1, 3));\n\n  // Verify no changes on BeginComposing.\n  model->BeginComposing();\n  EXPECT_EQ(model->selection(), TextRange(1, 3));\n  EXPECT_EQ(model->composing_range(), TextRange(1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n\n  // Verify selection is replaced and selection base, extent and composing\n  // extent increment to the position immediately after the composing text.\n  // Verify composing base does not change.\n  model->UpdateComposingText(\"つ\");\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"AつDE\");\n\n  // Verify that further text entry increments the selection base, extent and\n  // the composing extent. Verify that composing base does not change.\n  model->UpdateComposingText(\"つる\");\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"AつるDE\");\n\n  // Verify that cursor position is set to correct offset from composing base.\n  model->UpdateComposingText(\"-\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1)));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-DE\");\n\n  // Verify composing base is set to composing extent on commit.\n  model->CommitComposing();\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(2));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-DE\");\n\n  // Verify that further text entry increments the selection base, extent and\n  // the composing extent. Verify that composing base does not change.\n  model->UpdateComposingText(\"が\");\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(2, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-がDE\");\n\n  // Verify composing base is set to composing extent on commit.\n  model->CommitComposing();\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(3));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-がDE\");\n\n  // Verify no changes on EndComposing.\n  model->EndComposing();\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"A-がDE\");\n}\n\nTEST(TextInputModel, UpdateComposingRemovesLastComposingCharacter) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  model->SetComposingRange(TextRange(1, 2), 1);\n  model->UpdateComposingText(\"\");\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1));\n  model->SetText(\"ACDE\");\n}\n\nTEST(TextInputModel, AddCodePoint) {\n  auto model = std::make_unique<TextInputModel>();\n  model->AddCodePoint('A');\n  model->AddCodePoint('B');\n  model->AddCodePoint(0x1f604);\n  model->AddCodePoint('D');\n  model->AddCodePoint('E');\n  EXPECT_EQ(model->selection(), TextRange(6));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AB😄DE\");\n}\n\nTEST(TextInputModel, AddCodePointSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  model->AddCodePoint('x');\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AxE\");\n}\n\nTEST(TextInputModel, AddCodePointReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  model->AddCodePoint('x');\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AxE\");\n}\n\nTEST(TextInputModel, AddCodePointSelectionWideCharacter) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  model->AddCodePoint(0x1f604);\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"A😄E\");\n}\n\nTEST(TextInputModel, AddCodePointReverseSelectionWideCharacter) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  model->AddCodePoint(0x1f604);\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"A😄E\");\n}\n\nTEST(TextInputModel, AddText) {\n  auto model = std::make_unique<TextInputModel>();\n  model->AddText(u\"ABCDE\");\n  model->AddText(\"😄\");\n  model->AddText(\"FGHIJ\");\n  EXPECT_EQ(model->selection(), TextRange(12));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE😄FGHIJ\");\n}\n\nTEST(TextInputModel, AddTextSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  model->AddText(\"xy\");\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AxyE\");\n}\n\nTEST(TextInputModel, AddTextReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  model->AddText(\"xy\");\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AxyE\");\n}\n\nTEST(TextInputModel, AddTextSelectionWideCharacter) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  model->AddText(u\"😄🙃\");\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"A😄🙃E\");\n}\n\nTEST(TextInputModel, AddTextReverseSelectionWideCharacter) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  model->AddText(u\"😄🙃\");\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"A😄🙃E\");\n}\n\nTEST(TextInputModel, DeleteStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"BCDE\");\n}\n\nTEST(TextInputModel, DeleteMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABDE\");\n}\n\nTEST(TextInputModel, DeleteEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(5)));\n  ASSERT_FALSE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, DeleteWideCharacters) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"😄🙃🤪🧐\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4)));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"😄🙃🧐\");\n}\n\nTEST(TextInputModel, DeleteSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AE\");\n}\n\nTEST(TextInputModel, DeleteReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AE\");\n}\n\nTEST(TextInputModel, DeleteStartComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"ACDE\");\n}\n\nTEST(TextInputModel, DeleteStartReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(3, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ACDE\");\n}\n\nTEST(TextInputModel, DeleteMiddleComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABDE\");\n}\n\nTEST(TextInputModel, DeleteMiddleReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(3, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABDE\");\n}\n\nTEST(TextInputModel, DeleteEndComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3));\n  ASSERT_FALSE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, DeleteEndReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3));\n  ASSERT_FALSE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAtCursor) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->DeleteSurrounding(0, 1));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABDE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAtCursorComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  EXPECT_TRUE(model->DeleteSurrounding(0, 1));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABDE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAtCursorAll) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->DeleteSurrounding(0, 3));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AB\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAtCursorAllComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  EXPECT_TRUE(model->DeleteSurrounding(0, 2));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAtCursorGreedy) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->DeleteSurrounding(0, 4));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AB\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAtCursorGreedyComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  EXPECT_TRUE(model->DeleteSurrounding(0, 4));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingBeforeCursor) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->DeleteSurrounding(-1, 1));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ACDE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingBeforeCursorComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 2));\n  EXPECT_TRUE(model->DeleteSurrounding(-1, 1));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABDE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingBeforeCursorAll) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->DeleteSurrounding(-2, 2));\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"CDE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingBeforeCursorAllComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 2));\n  EXPECT_TRUE(model->DeleteSurrounding(-2, 2));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"ADE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingBeforeCursorGreedy) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->DeleteSurrounding(-3, 3));\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"CDE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingBeforeCursorGreedyComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 2));\n  EXPECT_TRUE(model->DeleteSurrounding(-3, 3));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"ADE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAfterCursor) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->DeleteSurrounding(1, 1));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAfterCursorComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->DeleteSurrounding(1, 1));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABDE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAfterCursorAll) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->DeleteSurrounding(1, 2));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABC\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAfterCursorAllComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->DeleteSurrounding(1, 2));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAfterCursorGreedy) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->DeleteSurrounding(1, 3));\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABC\");\n}\n\nTEST(TextInputModel, DeleteSurroundingAfterCursorGreedyComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->DeleteSurrounding(1, 3));\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 2));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2, 3)));\n  EXPECT_TRUE(model->DeleteSurrounding(0, 1));\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCE\");\n}\n\nTEST(TextInputModel, DeleteSurroundingReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 3)));\n  EXPECT_TRUE(model->DeleteSurrounding(0, 1));\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCE\");\n}\n\nTEST(TextInputModel, BackspaceStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  ASSERT_FALSE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, BackspaceMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  ASSERT_TRUE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ACDE\");\n}\n\nTEST(TextInputModel, BackspaceEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(5)));\n  ASSERT_TRUE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCD\");\n}\n\nTEST(TextInputModel, BackspaceWideCharacters) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"😄🙃🤪🧐\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4)));\n  ASSERT_TRUE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"😄🤪🧐\");\n}\n\nTEST(TextInputModel, BackspaceSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AE\");\n}\n\nTEST(TextInputModel, BackspaceReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  ASSERT_TRUE(model->Delete());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"AE\");\n}\n\nTEST(TextInputModel, BackspaceStartComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  ASSERT_FALSE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, BackspaceStartReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0));\n  ASSERT_FALSE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, BackspaceMiddleComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  ASSERT_TRUE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"ACDE\");\n}\n\nTEST(TextInputModel, BackspaceMiddleReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1));\n  ASSERT_TRUE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(3, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ACDE\");\n}\n\nTEST(TextInputModel, BackspaceEndComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3));\n  ASSERT_TRUE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 3));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCE\");\n}\n\nTEST(TextInputModel, BackspaceEndReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3));\n  ASSERT_TRUE(model->Backspace());\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(3, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(5)));\n  EXPECT_FALSE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardWideCharacters) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"😄🙃🤪🧐\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4)));\n  ASSERT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(6));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"😄🙃🤪🧐\");\n}\n\nTEST(TextInputModel, MoveCursorForwardSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardStartComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardStartReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0));\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardMiddleComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardMiddleReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1));\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardEndComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3));\n  EXPECT_FALSE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorForwardEndReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3));\n  EXPECT_FALSE(model->MoveCursorForward());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  EXPECT_FALSE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(5)));\n  EXPECT_TRUE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackWideCharacters) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"😄🙃🤪🧐\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4)));\n  ASSERT_TRUE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(2));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"😄🙃🤪🧐\");\n}\n\nTEST(TextInputModel, MoveCursorBackSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  EXPECT_TRUE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  EXPECT_TRUE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackStartComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->SetSelection(TextRange(1)));\n  EXPECT_FALSE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackStartReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0));\n  EXPECT_TRUE(model->SetSelection(TextRange(1)));\n  EXPECT_FALSE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackMiddleComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  EXPECT_TRUE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackMiddleReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1));\n  EXPECT_TRUE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackEndComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3));\n  EXPECT_TRUE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorBackEndReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3));\n  EXPECT_TRUE(model->MoveCursorBack());\n  EXPECT_EQ(model->selection(), TextRange(3));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  EXPECT_FALSE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  EXPECT_FALSE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(2, 0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(5)));\n  EXPECT_TRUE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(5)));\n  EXPECT_TRUE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(5, 0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  EXPECT_TRUE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  EXPECT_TRUE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1, 0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  EXPECT_TRUE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  EXPECT_TRUE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(4, 0));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningStartComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_FALSE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningStartComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_FALSE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningStartReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0));\n  EXPECT_FALSE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningStartReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 0));\n  EXPECT_FALSE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningMiddleComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  EXPECT_TRUE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningMiddleComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  EXPECT_TRUE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(2, 1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningMiddleReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1));\n  EXPECT_TRUE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningMiddleReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1));\n  EXPECT_TRUE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(2, 1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningEndComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3));\n  EXPECT_TRUE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningEndComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3));\n  EXPECT_TRUE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToBeginningEndReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3));\n  EXPECT_TRUE(model->MoveCursorToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToBeginningEndReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3));\n  EXPECT_TRUE(model->SelectToBeginning());\n  EXPECT_EQ(model->selection(), TextRange(4, 1));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  EXPECT_TRUE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndStart) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  EXPECT_TRUE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(0, 5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndMiddle) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(2)));\n  EXPECT_TRUE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(2, 5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(5)));\n  EXPECT_FALSE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndEnd) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(5)));\n  EXPECT_FALSE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  EXPECT_TRUE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  EXPECT_TRUE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(1, 5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  EXPECT_TRUE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  EXPECT_TRUE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(4, 5));\n  EXPECT_EQ(model->composing_range(), TextRange(0));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndStartComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndStartComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(1, 4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndStartReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndStartReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 0));\n  EXPECT_TRUE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(1, 4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndMiddleComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  EXPECT_TRUE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndMiddleComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 1));\n  EXPECT_TRUE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(2, 4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndMiddleReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1));\n  EXPECT_TRUE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndMiddleReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 1));\n  EXPECT_TRUE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(2, 4));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndEndComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3));\n  EXPECT_FALSE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndEndComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(1, 4), 3));\n  EXPECT_FALSE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(1, 4));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, MoveCursorToEndEndReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3));\n  EXPECT_FALSE(model->MoveCursorToEnd());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, SelectToEndEndReverseComposing) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  model->BeginComposing();\n  EXPECT_TRUE(model->SetComposingRange(TextRange(4, 1), 3));\n  EXPECT_FALSE(model->SelectToEnd());\n  EXPECT_EQ(model->selection(), TextRange(4));\n  EXPECT_EQ(model->composing_range(), TextRange(4, 1));\n  EXPECT_STREQ(model->GetText().c_str(), \"ABCDE\");\n}\n\nTEST(TextInputModel, GetCursorOffset) {\n  auto model = std::make_unique<TextInputModel>();\n  // These characters take 1, 2, 3 and 4 bytes in UTF-8.\n  model->SetText(\"$¢€𐍈\");\n  EXPECT_TRUE(model->SetSelection(TextRange(0)));\n  EXPECT_EQ(model->GetCursorOffset(), 0);\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->GetCursorOffset(), 1);\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->GetCursorOffset(), 3);\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->GetCursorOffset(), 6);\n  EXPECT_TRUE(model->MoveCursorForward());\n  EXPECT_EQ(model->GetCursorOffset(), 10);\n}\n\nTEST(TextInputModel, GetCursorOffsetSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(1, 4)));\n  EXPECT_EQ(model->GetCursorOffset(), 4);\n}\n\nTEST(TextInputModel, GetCursorOffsetReverseSelection) {\n  auto model = std::make_unique<TextInputModel>();\n  model->SetText(\"ABCDE\");\n  EXPECT_TRUE(model->SetSelection(TextRange(4, 1)));\n  EXPECT_EQ(model->GetCursorOffset(), 1);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/common/text_range.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_COMMON_TEXT_RANGE_H_\n#define CLAY_SHELL_PLATFORM_COMMON_TEXT_RANGE_H_\n\n#include <algorithm>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\n// A directional range of text.\n//\n// A |TextRange| describes a range of text with |base| and |extent| positions.\n// In the case where |base| == |extent|, the range is said to be collapsed, and\n// when |base| > |extent|, the range is said to be reversed.\nclass TextRange {\n public:\n  TextRange() : TextRange(0) {}\n  explicit TextRange(size_t position) : base_(position), extent_(position) {}\n  explicit TextRange(size_t base, size_t extent)\n      : base_(base), extent_(extent) {}\n  TextRange(const TextRange&) = default;\n  TextRange& operator=(const TextRange&) = default;\n\n  virtual ~TextRange() = default;\n\n  // The base position of the range.\n  size_t base() const { return base_; }\n\n  // Sets the base position of the range.\n  void set_base(size_t pos) { base_ = pos; }\n\n  // The extent position of the range.\n  size_t extent() const { return extent_; }\n\n  // Sets the extent position of the range.\n  void set_extent(size_t pos) { extent_ = pos; }\n\n  // The lesser of the base and extent positions.\n  size_t start() const { return std::min(base_, extent_); }\n\n  // Sets the start position of the range.\n  void set_start(size_t pos) {\n    if (base_ <= extent_) {\n      base_ = pos;\n    } else {\n      extent_ = pos;\n    }\n  }\n\n  // The greater of the base and extent positions.\n  size_t end() const { return std::max(base_, extent_); }\n\n  // Sets the end position of the range.\n  void set_end(size_t pos) {\n    if (base_ <= extent_) {\n      extent_ = pos;\n    } else {\n      base_ = pos;\n    }\n  }\n\n  // The position of a collapsed range.\n  //\n  // Asserts that the range is of length 0.\n  size_t position() const {\n    FML_DCHECK(base_ == extent_);\n    return extent_;\n  }\n\n  // The length of the range.\n  size_t length() const { return end() - start(); }\n\n  // Returns true if the range is of length 0.\n  bool collapsed() const { return base_ == extent_; }\n\n  // Returns true if the base is greater than the extent.\n  bool reversed() const { return base_ > extent_; }\n\n  // Returns true if |position| is contained within the range.\n  bool Contains(size_t position) const {\n    return position >= start() && position <= end();\n  }\n\n  // Returns true if |range| is contained within the range.\n  bool Contains(const TextRange& range) const {\n    return range.start() >= start() && range.end() <= end();\n  }\n\n  bool operator==(const TextRange& other) const {\n    return base_ == other.base_ && extent_ == other.extent_;\n  }\n\n private:\n  size_t base_;\n  size_t extent_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_COMMON_TEXT_RANGE_H_\n"
  },
  {
    "path": "clay/shell/platform/common/text_range_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"clay/shell/platform/common/text_range.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(TextRange, TextRangeFromPositionZero) {\n  TextRange range(0);\n  EXPECT_EQ(range.base(), size_t(0));\n  EXPECT_EQ(range.extent(), size_t(0));\n  EXPECT_EQ(range.start(), size_t(0));\n  EXPECT_EQ(range.end(), size_t(0));\n  EXPECT_EQ(range.length(), size_t(0));\n  EXPECT_EQ(range.position(), size_t(0));\n  EXPECT_TRUE(range.collapsed());\n}\n\nTEST(TextRange, TextRangeFromPositionNonZero) {\n  TextRange range(3);\n  EXPECT_EQ(range.base(), size_t(3));\n  EXPECT_EQ(range.extent(), size_t(3));\n  EXPECT_EQ(range.start(), size_t(3));\n  EXPECT_EQ(range.end(), size_t(3));\n  EXPECT_EQ(range.length(), size_t(0));\n  EXPECT_EQ(range.position(), size_t(3));\n  EXPECT_TRUE(range.collapsed());\n}\n\nTEST(TextRange, TextRangeFromRange) {\n  TextRange range(3, 7);\n  EXPECT_EQ(range.base(), size_t(3));\n  EXPECT_EQ(range.extent(), size_t(7));\n  EXPECT_EQ(range.start(), size_t(3));\n  EXPECT_EQ(range.end(), size_t(7));\n  EXPECT_EQ(range.length(), size_t(4));\n  EXPECT_FALSE(range.collapsed());\n}\n\nTEST(TextRange, TextRangeFromReversedRange) {\n  TextRange range(7, 3);\n  EXPECT_EQ(range.base(), size_t(7));\n  EXPECT_EQ(range.extent(), size_t(3));\n  EXPECT_EQ(range.start(), size_t(3));\n  EXPECT_EQ(range.end(), size_t(7));\n  EXPECT_EQ(range.length(), size_t(4));\n  EXPECT_FALSE(range.collapsed());\n}\n\nTEST(TextRange, SetBase) {\n  TextRange range(3, 7);\n  range.set_base(4);\n  EXPECT_EQ(range.base(), size_t(4));\n  EXPECT_EQ(range.extent(), size_t(7));\n}\n\nTEST(TextRange, SetBaseReversed) {\n  TextRange range(7, 3);\n  range.set_base(5);\n  EXPECT_EQ(range.base(), size_t(5));\n  EXPECT_EQ(range.extent(), size_t(3));\n}\n\nTEST(TextRange, SetExtent) {\n  TextRange range(3, 7);\n  range.set_extent(6);\n  EXPECT_EQ(range.base(), size_t(3));\n  EXPECT_EQ(range.extent(), size_t(6));\n}\n\nTEST(TextRange, SetExtentReversed) {\n  TextRange range(7, 3);\n  range.set_extent(4);\n  EXPECT_EQ(range.base(), size_t(7));\n  EXPECT_EQ(range.extent(), size_t(4));\n}\n\nTEST(TextRange, SetStart) {\n  TextRange range(3, 7);\n  range.set_start(5);\n  EXPECT_EQ(range.base(), size_t(5));\n  EXPECT_EQ(range.extent(), size_t(7));\n}\n\nTEST(TextRange, SetStartReversed) {\n  TextRange range(7, 3);\n  range.set_start(5);\n  EXPECT_EQ(range.base(), size_t(7));\n  EXPECT_EQ(range.extent(), size_t(5));\n}\n\nTEST(TextRange, SetEnd) {\n  TextRange range(3, 7);\n  range.set_end(6);\n  EXPECT_EQ(range.base(), size_t(3));\n  EXPECT_EQ(range.extent(), size_t(6));\n}\n\nTEST(TextRange, SetEndReversed) {\n  TextRange range(7, 3);\n  range.set_end(5);\n  EXPECT_EQ(range.base(), size_t(5));\n  EXPECT_EQ(range.extent(), size_t(3));\n}\n\nTEST(TextRange, ContainsPreStartPosition) {\n  TextRange range(2, 6);\n  EXPECT_FALSE(range.Contains(1));\n}\n\nTEST(TextRange, ContainsStartPosition) {\n  TextRange range(2, 6);\n  EXPECT_TRUE(range.Contains(2));\n}\n\nTEST(TextRange, ContainsMiddlePosition) {\n  TextRange range(2, 6);\n  EXPECT_TRUE(range.Contains(3));\n  EXPECT_TRUE(range.Contains(4));\n}\n\nTEST(TextRange, ContainsEndPosition) {\n  TextRange range(2, 6);\n  EXPECT_TRUE(range.Contains(6));\n}\n\nTEST(TextRange, ContainsPostEndPosition) {\n  TextRange range(2, 6);\n  EXPECT_FALSE(range.Contains(7));\n}\n\nTEST(TextRange, ContainsPreStartPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_FALSE(range.Contains(1));\n}\n\nTEST(TextRange, ContainsStartPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_TRUE(range.Contains(2));\n}\n\nTEST(TextRange, ContainsMiddlePositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_TRUE(range.Contains(3));\n  EXPECT_TRUE(range.Contains(4));\n}\n\nTEST(TextRange, ContainsEndPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_TRUE(range.Contains(6));\n}\n\nTEST(TextRange, ContainsPostEndPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_FALSE(range.Contains(7));\n}\n\nTEST(TextRange, ContainsRangePreStartPosition) {\n  TextRange range(2, 6);\n  EXPECT_FALSE(range.Contains(TextRange(0, 1)));\n}\n\nTEST(TextRange, ContainsRangeSpanningStartPosition) {\n  TextRange range(2, 6);\n  EXPECT_FALSE(range.Contains(TextRange(1, 3)));\n}\n\nTEST(TextRange, ContainsRangeStartPosition) {\n  TextRange range(2, 6);\n  EXPECT_TRUE(range.Contains(TextRange(2)));\n}\n\nTEST(TextRange, ContainsRangeMiddlePosition) {\n  TextRange range(2, 6);\n  EXPECT_TRUE(range.Contains(TextRange(3, 4)));\n  EXPECT_TRUE(range.Contains(TextRange(4, 5)));\n}\n\nTEST(TextRange, ContainsRangeEndPosition) {\n  TextRange range(2, 6);\n  EXPECT_TRUE(range.Contains(TextRange(6)));\n}\n\nTEST(TextRange, ContainsRangeSpanningEndPosition) {\n  TextRange range(2, 6);\n  EXPECT_FALSE(range.Contains(TextRange(5, 7)));\n}\n\nTEST(TextRange, ContainsRangePostEndPosition) {\n  TextRange range(2, 6);\n  EXPECT_FALSE(range.Contains(TextRange(6, 7)));\n}\n\nTEST(TextRange, ContainsRangePreStartPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_FALSE(range.Contains(TextRange(0, 1)));\n}\n\nTEST(TextRange, ContainsRangeSpanningStartPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_FALSE(range.Contains(TextRange(1, 3)));\n}\n\nTEST(TextRange, ContainsRangeStartPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_TRUE(range.Contains(TextRange(2)));\n}\n\nTEST(TextRange, ContainsRangeMiddlePositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_TRUE(range.Contains(TextRange(3, 4)));\n  EXPECT_TRUE(range.Contains(TextRange(4, 5)));\n}\n\nTEST(TextRange, ContainsRangeSpanningEndPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_FALSE(range.Contains(TextRange(5, 7)));\n}\n\nTEST(TextRange, ContainsRangeEndPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_TRUE(range.Contains(TextRange(5)));\n}\n\nTEST(TextRange, ContainsRangePostEndPositionReversed) {\n  TextRange range(6, 2);\n  EXPECT_FALSE(range.Contains(TextRange(6, 7)));\n}\n\nTEST(TextRange, ReversedForwardRange) {\n  TextRange range(2, 6);\n  EXPECT_FALSE(range.reversed());\n}\n\nTEST(TextRange, ReversedCollapsedRange) {\n  TextRange range(2, 2);\n  EXPECT_FALSE(range.reversed());\n}\n\nTEST(TextRange, ReversedReversedRange) {\n  TextRange range(6, 2);\n  EXPECT_TRUE(range.reversed());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/darwin/CPPLINT.cfg",
    "content": "set noparent\nfilter=-,+lynx_custom/new_java_ref,+build/namespaces,+build/c++14,+build/class,+build/c++tr1,+build/deprecated,+build/endif_comment,+build/explicit_make_pair\nexclude_files=.*\\.java\nexclude_files=.*\\.mm\nexclude_files=.*\\.m\nexclude_files=.*unittest\\.cc\nexclude_files=.*mock_element\\.cc\n"
  },
  {
    "path": "clay/shell/platform/darwin/common/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../../common/config.gni\")\nimport(\"framework_shared.gni\")\n\nsource_set(\"common\") {\n  sources = [\n    \"clay_service_manager_service_darwin.h\",\n    \"clay_service_manager_service_darwin.mm\",\n  ]\n\n  deps = [ \"../../../../fml:fml\" ]\n\n  public_configs = [\n    \"../../../../../clay:config\",\n    \":framework_relative_headers\",\n  ]\n\n  if (enable_trace != \"none\") {\n    if (enable_skity) {\n      include_dirs = [ \"../../../../../base/trace\" ]\n    } else {\n      deps += [ \"../../../../../base/trace/darwin:LynxTrace\" ]\n    }\n  }\n}\n\n# Shared framework headers end up in the same folder as platform-specific\n# framework headers when consumed by clients, so the include paths assume they\n# are next to each other.\nconfig(\"framework_relative_headers\") {\n  include_dirs = [ \"framework/Headers\" ]\n}\n\nsource_set(\"framework_shared\") {\n  sources = [ \"framework/Source/ClayServiceManager.mm\" ]\n\n  public = framework_shared_headers\n\n  sources += framework_shared_headers\n\n  defines = [ \"FLUTTER_FRAMEWORK\" ]\n\n  public_configs = [\n    \"../../../../../clay:config\",\n    \":framework_relative_headers\",\n  ]\n\n  deps = [ \"../../../../fml:fml\" ]\n}\n"
  },
  {
    "path": "clay/shell/platform/darwin/common/clay_service_manager_service_darwin.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_DARWIN_COMMON_CLAY_SERVICE_MANAGER_SERVICE_DARWIN_H_\n#define CLAY_SHELL_PLATFORM_DARWIN_COMMON_CLAY_SERVICE_MANAGER_SERVICE_DARWIN_H_\n\n#import \"ClayServiceManager.h\"\n#include \"clay/common/service/service.h\"\n\nnamespace clay {\nclass ClayServiceManagerServiceDarwin final\n    : public Service<ClayServiceManagerServiceDarwin, Owner::kPlatform,\n                     clay::ServiceFlags::kManualRegister |\n                         ServiceFlags::kMultiThread> {\n public:\n  explicit ClayServiceManagerServiceDarwin(ClayServiceManager* manager);\n\n  ClayServiceManager* GetClayServiceManager();\n\n private:\n  ClayServiceManager* service_manager_;\n};\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_DARWIN_COMMON_CLAY_SERVICE_MANAGER_SERVICE_DARWIN_H_\n"
  },
  {
    "path": "clay/shell/platform/darwin/common/clay_service_manager_service_darwin.mm",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/common/clay_service_manager_service_darwin.h\"\n\nnamespace clay {\n\nClayServiceManagerServiceDarwin::ClayServiceManagerServiceDarwin(ClayServiceManager* manager)\n    : service_manager_(manager) {}\n\nClayServiceManager* ClayServiceManagerServiceDarwin::GetClayServiceManager() {\n  return service_manager_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/darwin/common/framework/Headers/ClayServiceManager.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Foundation/Foundation.h>\n\n#import \"FlutterMacros.h\"\n\nFLUTTER_DARWIN_EXPORT\n@interface ClayServiceManager : NSObject\n\n+ (void)registerGlobalService:(Protocol *)protocol service:(id)service;\n- (void)registerService:(Protocol *)protocol service:(id)service;\n- (id)getService:(Protocol *)protocol;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/common/framework/Headers/FlutterMacros.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_DARWIN_COMMON_FRAMEWORK_HEADERS_FLUTTERMACROS_H_\n#define CLAY_SHELL_PLATFORM_DARWIN_COMMON_FRAMEWORK_HEADERS_FLUTTERMACROS_H_\n\n#if defined(FLUTTER_FRAMEWORK)\n\n#define FLUTTER_DARWIN_EXPORT __attribute__((visibility(\"default\")))\n\n#else  // defined(FLUTTER_SDK)\n\n#define FLUTTER_DARWIN_EXPORT\n\n#endif  // defined(FLUTTER_SDK)\n\n#ifndef NS_ASSUME_NONNULL_BEGIN\n#define NS_ASSUME_NONNULL_BEGIN _Pragma(\"clang assume_nonnull begin\")\n#define NS_ASSUME_NONNULL_END _Pragma(\"clang assume_nonnull end\")\n#endif  // defined(NS_ASSUME_NONNULL_BEGIN)\n\n/**\n * Indicates that the API has been deprecated for the specified reason. Code\n * that uses the deprecated API will continue to work as before. However, the\n * API will soon become unavailable and users are encouraged to immediately take\n * the appropriate action mentioned in the deprecation message and the BREAKING\n * CHANGES section present in the Flutter.h umbrella header.\n */\n#define FLUTTER_DEPRECATED(msg) __attribute__((__deprecated__(msg)))\n\n/**\n * Indicates that the previously deprecated API is now unavailable. Code that\n * uses the API will not work and the declaration of the API is only a stub\n * meant to display the given message detailing the actions for the user to take\n * immediately.\n */\n#define FLUTTER_UNAVAILABLE(msg) __attribute__((__unavailable__(msg)))\n\n#if __has_feature(objc_arc)\n#define FLUTTER_ASSERT_ARC\n#define FLUTTER_ASSERT_NOT_ARC #error ARC must be disabled !\n#else\n#define FLUTTER_ASSERT_ARC #error ARC must be enabled !\n#define FLUTTER_ASSERT_NOT_ARC\n#endif\n\n#endif  // CLAY_SHELL_PLATFORM_DARWIN_COMMON_FRAMEWORK_HEADERS_FLUTTERMACROS_H_\n"
  },
  {
    "path": "clay/shell/platform/darwin/common/framework/Source/ClayServiceManager.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"ClayServiceManager.h\"\n#include \"clay/fml/logging.h\"\n\n#import <Foundation/Foundation.h>\n\n@implementation ClayServiceManager {\n  NSMutableDictionary<NSString *, id> *_serviceMap;\n}\n\n- (instancetype)init {\n  if (self = [super init]) {\n    _serviceMap = [[NSMutableDictionary alloc] init];\n  }\n  return self;\n}\n\n+ (instancetype)sharedInstance {\n  static ClayServiceManager *sharedInstance = nil;\n  static dispatch_once_t onceToken;\n  dispatch_once(&onceToken, ^{\n    sharedInstance = [[self alloc] init];\n  });\n  return sharedInstance;\n}\n\n+ (void)registerGlobalService:(Protocol *)protocol service:(id)service {\n  if (!protocol || !service || ![service conformsToProtocol:protocol]) {\n    FML_LOG(ERROR) << \"registerService failed: param error\";\n    return;\n  }\n  [[ClayServiceManager sharedInstance] registerService:protocol service:service];\n}\n- (void)registerService:(Protocol *)protocol service:(id)service {\n  if (!protocol || !service || ![service conformsToProtocol:protocol]) {\n    FML_LOG(ERROR) << \"registerService failed: param error\";\n    return;\n  }\n  @synchronized(self) {\n    [_serviceMap setObject:service forKey:NSStringFromProtocol(protocol)];\n  }\n}\n- (id)getService:(Protocol *)protocol {\n  id service = nil;\n  @synchronized(self) {\n    service = [_serviceMap objectForKey:NSStringFromProtocol(protocol)];\n  }\n  if (service) {\n    return service;\n  } else {\n    if (self != [ClayServiceManager sharedInstance]) {\n      return [[ClayServiceManager sharedInstance] getService:protocol];\n    }\n    return nil;\n  }\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/common/framework_shared.gni",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../../common/config.gni\")\n\nframework_shared_headers =\n    get_path_info([\n                    \"framework/Headers/FlutterMacros.h\",\n                    \"framework/Headers/ClayServiceManager.h\",\n                  ],\n                  \"abspath\")\n"
  },
  {
    "path": "clay/shell/platform/darwin/graphics/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nassert(is_apple)\n\nimport(\"../../../../common/config.gni\")\n\nsource_set(\"graphics\") {\n  if (!enable_skity) {\n    sources = [\n      \"FlutterDarwinContextMetalSkia.h\",\n      \"FlutterDarwinContextMetalSkia.mm\",\n    ]\n  } else {\n    sources = [\n      \"FlutterDarwinContextMetalSkity.h\",\n      \"FlutterDarwinContextMetalSkity.mm\",\n    ]\n  }\n\n  deps = [\n    \"../../../../common:common\",\n    \"../../../../common/graphics\",\n    \"../../../../fml:fml\",\n  ]\n\n  frameworks = [ \"CoreVideo.framework\" ]\n\n  public_configs = [\n    \"../../../../../clay:config\",\n    \"../common:framework_relative_headers\",\n  ]\n\n  if (!enable_skity) {\n    public_deps = [ \"//third_party/skia\" ]\n  }\n}\n"
  },
  {
    "path": "clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef SHELL_PLATFORM_DARWIN_GRAPHICS_DARWIN_CONTEXT_METAL_H_\n#define SHELL_PLATFORM_DARWIN_GRAPHICS_DARWIN_CONTEXT_METAL_H_\n\n#import <Foundation/Foundation.h>\n#import <Metal/Metal.h>\n\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n/**\n * Provides skia GrContexts that are shared between iOS and macOS embeddings.\n */\n@interface FlutterDarwinContextMetalSkia : NSObject\n\n/**\n * Initializes a FlutterDarwinContextMetalSkia with the system default MTLDevice and a new\n * MTLCommandQueue.\n */\n- (instancetype)initWithDefaultMTLDevice;\n\n/**\n * Initializes a FlutterDarwinContextMetalSkia with provided MTLDevice and MTLCommandQueue.\n */\n- (instancetype)initWithMTLDevice:(id<MTLDevice>)device\n                     commandQueue:(id<MTLCommandQueue>)commandQueue;\n\n/**\n * Creates a GrDirectContext with the provided `MTLDevice` and `MTLCommandQueue`.\n */\n+ (sk_sp<GrDirectContext>)createGrContext:(id<MTLDevice>)device\n                             commandQueue:(id<MTLCommandQueue>)commandQueue;\n\n/**\n * MTLDevice that is backing this context.s\n */\n@property(nonatomic, readonly) id<MTLDevice> device;\n\n/**\n * MTLCommandQueue that is acquired from the `device`. This queue is used both for rendering and\n * resource related commands.\n */\n@property(nonatomic, readonly) id<MTLCommandQueue> commandQueue;\n\n/**\n * Skia GrContext that is used for rendering.\n */\n@property(nonatomic, readonly) sk_sp<GrDirectContext> mainContext;\n\n/**\n * Skia GrContext that is used for resources (uploading textures etc).\n */\n@property(nonatomic, readonly) sk_sp<GrDirectContext> resourceContext;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n#endif  // SHELL_PLATFORM_DARWIN_GRAPHICS_DARWIN_CONTEXT_METAL_H_\n"
  },
  {
    "path": "clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.h\"\n\n#import \"FlutterMacros.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/common/context_options.h\"\n\nFLUTTER_ASSERT_ARC\n\n@implementation FlutterDarwinContextMetalSkia\n\n- (instancetype)initWithDefaultMTLDevice {\n  id<MTLDevice> device = MTLCreateSystemDefaultDevice();\n  return [self initWithMTLDevice:device commandQueue:[device newCommandQueue]];\n}\n\n- (instancetype)initWithMTLDevice:(id<MTLDevice>)device\n                     commandQueue:(id<MTLCommandQueue>)commandQueue {\n  self = [super init];\n  if (self != nil) {\n    _device = device;\n\n    if (!_device) {\n      FML_DLOG(ERROR) << \"Could not acquire Metal device.\";\n      return nil;\n    }\n\n    _commandQueue = commandQueue;\n\n    if (!_commandQueue) {\n      FML_DLOG(ERROR) << \"Could not create Metal command queue.\";\n      return nil;\n    }\n\n    [_commandQueue setLabel:@\"Flutter Main Queue\"];\n\n    // The devices are in the same \"sharegroup\" because they share the same device and command\n    // queues for now. When the resource context gets its own transfer queue, this will have to be\n    // refactored.\n    _mainContext = [self createGrContext];\n    _resourceContext = [self createGrContext];\n\n    if (!_mainContext || !_resourceContext) {\n      FML_DLOG(ERROR) << \"Could not create Skia Metal contexts.\";\n      return nil;\n    }\n\n    _resourceContext->setResourceCacheLimit(0u);\n  }\n  return self;\n}\n\n- (sk_sp<GrDirectContext>)createGrContext {\n  const auto contextOptions =\n      clay::MakeDefaultContextOptions(clay::ContextType::kRender, GrBackendApi::kMetal);\n  id<MTLDevice> device = _device;\n  id<MTLCommandQueue> commandQueue = _commandQueue;\n  return [FlutterDarwinContextMetalSkia createGrContext:device commandQueue:commandQueue];\n}\n\n+ (sk_sp<GrDirectContext>)createGrContext:(id<MTLDevice>)device\n                             commandQueue:(id<MTLCommandQueue>)commandQueue {\n  const auto contextOptions =\n      clay::MakeDefaultContextOptions(clay::ContextType::kRender, GrBackendApi::kMetal);\n  // Skia expect arguments to `MakeMetal` transfer ownership of the reference in for release later\n  // when the GrDirectContext is collected.\n  return GrDirectContext::MakeMetal((__bridge_retained void*)device,\n                                    (__bridge_retained void*)commandQueue, contextOptions);\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkity.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_DARWIN_GRAPHICS_FLUTTER_DARWIN_CONTEXT_METAL_SKITY_H_\n#define CLAY_SHELL_PLATFORM_DARWIN_GRAPHICS_FLUTTER_DARWIN_CONTEXT_METAL_SKITY_H_\n\n#import <Foundation/Foundation.h>\n#import <Metal/Metal.h>\n\n#include <memory>\n#include \"skity/gpu/gpu_context_mtl.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n/**\n * Provides skity GPUContext that are shared between iOS and macOS embeddings.\n */\n@interface FlutterDarwinContextMetalSkity : NSObject\n\n/**\n * Initializes a FlutterDarwinContextMetalSkity with the system default MTLDevice and a new\n * MTLCommandQueue.\n */\n- (instancetype)initWithDefaultMTLDevice;\n\n/**\n * Initializes a FlutterDarwinContextMetalSkity with provided MTLDevice and MTLCommandQueue.\n */\n- (instancetype)initWithMTLDevice:(id<MTLDevice>)device\n                     commandQueue:(id<MTLCommandQueue>)commandQueue;\n\n/**\n * Creates a skity::GPUContext with the provided `MTLDevice` and `MTLCommandQueue`.\n */\n+ (std::shared_ptr<skity::GPUContext>)createGPUContext:(id<MTLDevice>)device\n                                          commandQueue:(id<MTLCommandQueue>)commandQueue;\n\n/**\n * MTLDevice that is backing this context.s\n */\n@property(nonatomic, readonly) id<MTLDevice> device;\n\n/**\n * MTLCommandQueue that is acquired from the `device`. This queue is used both for rendering and\n * resource related commands.\n */\n@property(nonatomic, readonly) id<MTLCommandQueue> commandQueue;\n\n/**\n * Skity GPUContext that is used for rendering.\n */\n@property(nonatomic, readonly) std::shared_ptr<skity::GPUContext> skityContext;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n#endif  // CLAY_SHELL_PLATFORM_DARWIN_GRAPHICS_FLUTTER_DARWIN_CONTEXT_METAL_SKITY_H_\n"
  },
  {
    "path": "clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkity.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkity.h\"\n\n#import \"FlutterMacros.h\"\n#include \"clay/common/graphics/persistent_cache.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/common/context_options.h\"\n\nFLUTTER_ASSERT_ARC\n\n@implementation FlutterDarwinContextMetalSkity\n\n- (instancetype)initWithDefaultMTLDevice {\n  id<MTLDevice> device = MTLCreateSystemDefaultDevice();\n  return [self initWithMTLDevice:device commandQueue:[device newCommandQueue]];\n}\n\n- (instancetype)initWithMTLDevice:(id<MTLDevice>)device\n                     commandQueue:(id<MTLCommandQueue>)commandQueue {\n  self = [super init];\n  if (self != nil) {\n    _device = device;\n\n    if (!_device) {\n      FML_DLOG(ERROR) << \"Could not acquire Metal device.\";\n      return nil;\n    }\n\n    _commandQueue = commandQueue;\n\n    if (!_commandQueue) {\n      FML_DLOG(ERROR) << \"Could not create Metal command queue.\";\n      return nil;\n    }\n\n    [_commandQueue setLabel:@\"Flutter Main Queue\"];\n\n    // The devices are in the same \"sharegroup\" because they share the same device and command\n    // queues for now. When the resource context gets its own transfer queue, this will have to be\n    // refactored.\n    _skityContext = [self createGPUContext];\n\n    if (!_skityContext) {\n      FML_DLOG(ERROR) << \"Could not create Skity Metal contexts.\";\n      return nil;\n    }\n  }\n  return self;\n}\n\n- (std::shared_ptr<skity::GPUContext>)createGPUContext {\n  id<MTLDevice> device = _device;\n  id<MTLCommandQueue> commandQueue = _commandQueue;\n  return [FlutterDarwinContextMetalSkity createGPUContext:device commandQueue:commandQueue];\n}\n\n+ (std::shared_ptr<skity::GPUContext>)createGPUContext:(id<MTLDevice>)device\n                                          commandQueue:(id<MTLCommandQueue>)commandQueue {\n  return skity::MTLContextCreate(device, commandQueue);\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nassert(is_mac)\n\nimport(\"../../../../common/config.gni\")\nimport(\"../../../config.gni\")\nimport(\"../../../gpu/gpu.gni\")\n\nshell_gpu_configuration(\"macos_gpu_configuration\") {\n  enable_software = true\n  enable_gl = true\n  enable_metal = shell_enable_metal\n}\n\n_flutter_framework_headers = [\n  \"framework/Headers/FlutterAppDelegate.h\",\n  \"framework/Headers/FlutterEngine.h\",\n  \"framework/Headers/ClayMacOS.h\",\n  \"framework/Headers/FlutterPlatformViews.h\",\n  \"framework/Headers/FlutterPluginMacOS.h\",\n  \"framework/Headers/FlutterPluginRegistrarMacOS.h\",\n  \"framework/Headers/FlutterView.h\",\n  \"framework/Headers/FlutterViewController.h\",\n  \"framework/Headers/ClayViewProvider.h\",\n]\n\nsource_set(\"flutter_framework_source\") {\n  sources = [\n    \"framework/Source/ClayMouseCursorPlugin.h\",\n    \"framework/Source/ClayMouseCursorPlugin.mm\",\n    \"framework/Source/ClayViewProvider.mm\",\n    \"framework/Source/ClayViewProvider_Internal.h\",\n    \"framework/Source/FlutterAppDelegate.mm\",\n    \"framework/Source/FlutterEmbedderKeyResponder.h\",\n    \"framework/Source/FlutterEmbedderKeyResponder.mm\",\n    \"framework/Source/FlutterEngine.mm\",\n    \"framework/Source/FlutterEngine_Internal.h\",\n    \"framework/Source/FlutterKeyPrimaryResponder.h\",\n    \"framework/Source/FlutterKeyboardManager.h\",\n    \"framework/Source/FlutterKeyboardManager.mm\",\n    \"framework/Source/FlutterKeyboardViewDelegate.h\",\n    \"framework/Source/FlutterPlatformViewController.h\",\n    \"framework/Source/FlutterPlatformViewController.mm\",\n    \"framework/Source/FlutterRenderer.h\",\n    \"framework/Source/FlutterRenderer.mm\",\n    \"framework/Source/FlutterSurface.h\",\n    \"framework/Source/FlutterSurface.mm\",\n    \"framework/Source/FlutterSurfaceManager.h\",\n    \"framework/Source/FlutterSurfaceManager.mm\",\n    \"framework/Source/FlutterTextInputPlugin.h\",\n    \"framework/Source/FlutterTextInputPlugin.mm\",\n    \"framework/Source/FlutterThreadSynchronizer.h\",\n    \"framework/Source/FlutterThreadSynchronizer.mm\",\n    \"framework/Source/FlutterView.h\",\n    \"framework/Source/FlutterView.mm\",\n    \"framework/Source/FlutterViewController.mm\",\n    \"framework/Source/FlutterViewController_Internal.h\",\n    \"framework/Source/FlutterViewEngineProvider.h\",\n    \"framework/Source/FlutterViewEngineProvider.mm\",\n    \"framework/Source/FlutterViewProvider.h\",\n    \"framework/Source/KeyCodeMap.g.mm\",\n  ]\n\n  sources += _flutter_framework_headers\n\n  deps = [\n    \":macos_gpu_configuration\",\n    \"../../../../flow:flow\",\n    \"../../../../fml:fml\",\n    \"../../../../fml:icu_util\",\n    \"../../../../ui:ui\",\n    \"../../common:common_cpp_enums\",\n    \"../../common:common_cpp_input\",\n    \"../../common:common_cpp_switches\",\n    \"../../embedder:embedder\",\n    \"../common:common\",\n    \"../common:framework_shared\",\n    \"../graphics:graphics\",\n    \"//third_party/skia\",\n  ]\n\n  public_configs = [ \"../../../../../clay:config\" ]\n\n  defines = [\n    \"FLUTTER_FRAMEWORK\",\n    \"FLUTTER_ENGINE_NO_PROTOTYPES\",\n  ]\n\n  frameworks = [\n    \"Carbon.framework\",\n    \"Cocoa.framework\",\n    \"CoreVideo.framework\",\n    \"IOSurface.framework\",\n    \"Metal.framework\",\n    \"QuartzCore.framework\",\n  ]\n}\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Headers/ClayMacOS.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <ClayMacOS/ClayViewProvider.h>\n#import <ClayMacOS/FlutterAppDelegate.h>\n#import <ClayMacOS/FlutterEngine.h>\n#import <ClayMacOS/FlutterMacros.h>\n#import <ClayMacOS/FlutterPluginMacOS.h>\n#import <ClayMacOS/FlutterPluginRegistrarMacOS.h>\n#import <ClayMacOS/FlutterView.h>\n#import <ClayMacOS/FlutterViewController.h>\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Headers/ClayViewProvider.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef FLUTTER_CLAYVIEWPROVIDER_H_\n#define FLUTTER_CLAYVIEWPROVIDER_H_\n\n#import <Cocoa/Cocoa.h>\n#include <objc/objc.h>\n\n#import \"ClayServiceManager.h\"\n#import \"FlutterEngine.h\"\n#import \"FlutterMacros.h\"\n#import \"FlutterPluginRegistrarMacOS.h\"\n#import \"FlutterView.h\"\n#import \"FlutterViewController.h\"\n\n@class FlutterTextInputPlugin;\n@class ClayMouseCursorPlugin;\n\ntypedef void (*HostLogCallback)(const char* _Nullable log_level, const char* _Nullable file,\n                                const int line, const char* _Nullable log_info);\n\nNS_ASSUME_NONNULL_BEGIN\n\nFLUTTER_DARWIN_EXPORT\n@interface ClayViewProvider : NSResponder <FlutterPluginRegistry>\n\n/**\n * The Flutter engine associated with this view controller.\n */\n@property(nonatomic, nonnull, readonly) FlutterEngine* engine;\n\n// The FlutterView for this view controller.\n@property(nonatomic, readonly, nullable) FlutterView* flutterView;\n\n/**\n * The style of mouse tracking to use for the view. Defaults to\n * FlutterMouseTrackingModeInKeyWindow.\n */\n@property(nonatomic) FlutterMouseTrackingMode mouseTrackingMode;\n\n@property(nonatomic, readonly, nullable) FlutterTextInputPlugin* textInputPlugin;\n\n@property(nonatomic, strong, nonnull) ClayMouseCursorPlugin* mouseCursorPlugin;\n\n- (instancetype)init;\n\n- (void)viewDidLoad;\n- (void)viewWillAppear;\n- (void)viewWillDisappear;\n- (nullable void*)clayViewContext;\n- (void)onEnterForeground;\n- (void)onEnterBackground;\n- (void)setVisible:(BOOL)visible;\n/**\n * Returns YES if provided event is being currently redispatched by keyboard manager.\n */\n- (BOOL)isDispatchingKeyEvent:(nonnull NSEvent*)event;\n\n- (BOOL)viewLoaded;\n- (BOOL)isLaunchEngineSuccess;\n- (void)setInterceptUrlCallback:(FlutterViewShouldInterceptUrlCallback _Nullable)callback;\n- (void)requestIME:(nullable void*)callback arg:(nullable void*)arg;\n\n- (void)performMouseDragLeave;\n- (void)performMouseDragEnterAndOverAtPoint:(NSPoint)point;\n- (void)performMouseDragDropAtPoint:(NSPoint)point\n                               type:(NSString* _Nonnull)type\n                        dropContent:(id _Nonnull)content;\n\n- (ClayServiceManager*)GetClayServiceManager;\n\n@end\n\nNS_ASSUME_NONNULL_END\n\n#endif  // FLUTTER_CLAYVIEWPROVIDER_H_\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef FLUTTER_FLUTTERAPPDELEGATE_H_\n#define FLUTTER_FLUTTERAPPDELEGATE_H_\n\n#import <Cocoa/Cocoa.h>\n\n#import \"FlutterMacros.h\"\n\n/**\n * `NSApplicationDelegate` subclass for simple apps that want default behavior.\n *\n * This class implements the following behaviors:\n *   * Updates the application name of items in the application menu to match the name in\n *     the app's Info.plist, assuming it is set to APP_NAME initially. |applicationMenu| must be\n *     set before the application finishes launching for this to take effect.\n *   * Updates the main Flutter window's title to match the name in the app's Info.plist.\n *     |mainFlutterWindow| must be set before the application finishes launching for this to take\n *     effect.\n *\n * App delegates for Flutter applications are *not* required to inherit from\n * this class. Developers of custom app delegate classes should copy and paste\n * code as necessary from FlutterAppDelegate.mm.\n */\nFLUTTER_DARWIN_EXPORT\n@interface FlutterAppDelegate : NSObject <NSApplicationDelegate>\n\n/**\n * The application menu in the menu bar.\n */\n@property(weak, nonatomic) IBOutlet NSMenu* applicationMenu;\n\n/**\n * The primary application window containing a FlutterViewController. This is primarily intended\n * for use in single-window applications.\n */\n@property(weak, nonatomic) IBOutlet NSWindow* mainFlutterWindow;\n\n@end\n\n#endif  // FLUTTER_FLUTTERAPPDELEGATE_H_\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef FLUTTER_FLUTTERENGINE_H_\n#define FLUTTER_FLUTTERENGINE_H_\n\n#import <Foundation/Foundation.h>\n\n#import \"FlutterMacros.h\"\n#import \"FlutterPluginRegistrarMacOS.h\"\n\ntypedef NSString* _Nullable (^FlutterViewShouldInterceptUrlCallback)(NSString* _Nonnull originUrl,\n                                                                     BOOL shouldDecode,\n                                                                     NSInteger maxPathLength);\n\n// TODO: Merge this file with the iOS FlutterEngine.h.\n\n@class FlutterView;\n@class FlutterViewController;\n@class ClayViewProvider;\n\nextern NSString* _Nonnull const kDragTextType;\nextern NSString* _Nonnull const kDragFileType;\nextern NSString* _Nonnull const kDragDropPathKey;\nextern NSString* _Nonnull const kDragDropNameKey;\nextern NSString* _Nonnull const kDragDropTypeKey;\nextern NSString* _Nonnull const kDragDropSizeKey;\nextern NSString* _Nonnull const kDragDropLastModifiedKey;\n\n/**\n * Coordinates a single instance of execution of a Flutter engine.\n */\nFLUTTER_DARWIN_EXPORT\n@interface FlutterEngine : NSObject <FlutterPluginRegistry>\n\n/**\n * Initializes an engine with the given project.\n *\n * @param labelPrefix Currently unused; in the future, may be used for labelling threads\n *                    as with the iOS FlutterEngine.\n */\n- (nonnull instancetype)initWithName:(nonnull NSString*)labelPrefix;\n\n/**\n * Initializes an engine that can run headlessly with the given project.\n *\n * @param labelPrefix Currently unused; in the future, may be used for labelling threads\n *                    as with the iOS FlutterEngine.\n */\n- (nonnull instancetype)initWithName:(nonnull NSString*)labelPrefix\n              allowHeadlessExecution:(BOOL)allowHeadlessExecution NS_DESIGNATED_INITIALIZER;\n\n- (nonnull instancetype)init NS_UNAVAILABLE;\n\n/**\n * Runs a Dart program on an Isolate from the main Dart library (i.e. the library that\n * contains `main()`).\n *\n * The first call to this method will create a new Isolate. Subsequent calls will return\n * immediately.\n *\n * @param entrypoint The name of a top-level function from the same Dart\n *   library that contains the app's main() function.  If this is nil, it will\n *   default to `main()`.  If it is not the app's main() function, that function\n *   must be decorated with `@pragma(vm:entry-point)` to ensure the method is not\n *   tree-shaken by the Dart compiler.\n * @return YES if the call succeeds in creating and running a Flutter Engine instance; NO otherwise.\n */\n- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint;\n\n/**\n * The default `FlutterViewController` associated with this engine, if any.\n *\n * The default view always has ID kFlutterDefaultViewId, and is the view\n * operated by the APIs that do not have a view ID specified.\n */\n@property(nonatomic, nullable, weak) FlutterViewController* viewController;\n\n/**\n * The `FlutterView` associated with this engine, if any.\n */\n@property(nonatomic, nullable, weak) FlutterView* view;\n\n/**\n * Shuts the Flutter engine if it is running. The FlutterEngine instance must always be shutdown\n * before it may be collected. Not shutting down the FlutterEngine instance before releasing it will\n * result in the leak of that engine instance.\n */\n- (void)shutDownEngine;\n\n- (nullable void*)clayViewContext;\n\n- (void)onEnterForeground;\n- (void)onEnterBackground;\n- (void)setVisible:(BOOL)visible;\n\n/**\n * set intercept image url callback\n */\n- (void)setInterceptUrlCallback:(FlutterViewShouldInterceptUrlCallback _Nullable)callback;\n\n- (void)updateEditState:(int)client_id\n          selectionBase:(uint64_t)selection_base\n        composingExtent:(uint64_t)composing_extent\n      selectionAffinity:(const char* _Nullable)selection_affinity\n                   text:(const char* _Nullable)text\n        selectionExtent:(uint64_t)selection_extent\n          composingBase:(uint64_t)composing_base;\n\n- (void)performInputAction:(int)client_id;\n\n- (void)performMouseDragLeave;\n- (void)performMouseDragEnterAndOverAtPoint:(NSPoint)point;\n- (void)performMouseDragDropAtPoint:(NSPoint)point\n                               type:(NSString* _Nonnull)type\n                        dropContent:(id _Nonnull)content;\n\n@end\n\n#endif  // FLUTTER_FLUTTERENGINE_H_\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef FLUTTER_FLUTTERPLATFORMVIEWS_H_\n#define FLUTTER_FLUTTERPLATFORMVIEWS_H_\n\n#import <AppKit/AppKit.h>\n\n#import \"FlutterMacros.h\"\n\n@protocol FlutterPlatformViewFactory <NSObject>\n\n/**\n * Create a Platform View which is an `NSView`.\n *\n * A MacOS plugin should implement this method and return an `NSView`, which can be embedded in a\n * Flutter App.\n *\n * The implementation of this method should create a new `NSView`.\n *\n * @param viewId A unique identifier for this view.\n * @param args Parameters for creating the view sent from the Dart side of the\n * Flutter app. If `createArgsCodec` is not implemented, or if no creation arguments were sent from\n * the Dart code, this will be null. Otherwise this will be the value sent from the Dart code as\n * decoded by `createArgsCodec`.\n */\n- (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable id)args;\n\n@end\n\n#endif  // FLUTTER_FLUTTERPLATFORMVIEWS_H_\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Headers/FlutterPluginMacOS.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Foundation/Foundation.h>\n\n#import \"FlutterMacros.h\"\n\n// TODO: Merge this file and FlutterPluginRegistrarMacOS.h with the iOS FlutterPlugin.h, sharing\n// all but the platform-specific methods.\n\n@protocol FlutterPluginRegistrar;\n\n/**\n * Implemented by the platform side of a Flutter plugin.\n *\n * Defines a set of optional callback methods and a method to set up the plugin\n * and register it to be called by other application components.\n *\n * Currently the macOS version of FlutterPlugin has very limited functionality, but is expected to\n * expand over time to more closely match the functionality of the iOS FlutterPlugin.\n */\nFLUTTER_DARWIN_EXPORT\n@protocol FlutterPlugin <NSObject>\n\n/**\n * Creates an instance of the plugin to register with |registrar| using the desired\n * FlutterPluginRegistrar methods.\n */\n+ (void)registerWithRegistrar:(nonnull id<FlutterPluginRegistrar>)registrar;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"FlutterMacros.h\"\n#import \"FlutterPlatformViews.h\"\n#import \"FlutterPluginMacOS.h\"\n\n// TODO: Merge this file and FlutterPluginMacOS.h with the iOS FlutterPlugin.h, sharing all but\n// the platform-specific methods.\n\n/**\n * The protocol for an object managing registration for a plugin. It provides access to application\n * context, as allowing registering for callbacks for handling various conditions.\n *\n * Currently, the macOS PluginRegistrar has very limited functionality, but is expected to expand\n * over time to more closely match the functionality of FlutterPluginRegistrar.\n */\nFLUTTER_DARWIN_EXPORT\n@protocol FlutterPluginRegistrar <NSObject>\n\n/**\n * The view displaying Flutter content. May return |nil|, for instance in a headless environment.\n *\n * WARNING: If/when multiple Flutter views within the same application are supported (#30701), this\n * API will change.\n */\n@property(nullable, readonly) NSView* view;\n\n/**\n * Registers a `FlutterPlatformViewFactory` for creation of platform views.\n *\n * Plugins expose `NSView` for embedding in Flutter apps by registering a view factory.\n *\n * @param factory The view factory that will be registered.\n * @param factoryId A unique identifier for the factory, the Dart code of the Flutter app can use\n *   this identifier to request creation of a `NSView` by the registered factory.\n */\n- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)factory\n                     withId:(nonnull NSString*)factoryId;\n\n@end\n\n/**\n * A registry of Flutter macOS plugins.\n *\n * Plugins are identified by unique string keys, typically the name of the\n * plugin's main class.\n *\n * Plugins typically need contextual information and the ability to register\n * callbacks for various application events. To keep the API of the registry\n * focused, these facilities are not provided directly by the registry, but by\n * a `FlutterPluginRegistrar`, created by the registry in exchange for the unique\n * key of the plugin.\n *\n * There is no implied connection between the registry and the registrar.\n * Specifically, callbacks registered by the plugin via the registrar may be\n * relayed directly to the underlying iOS application objects.\n */\n@protocol FlutterPluginRegistry <NSObject>\n\n/**\n * Returns a registrar for registering a plugin.\n *\n * @param pluginKey The unique key identifying the plugin.\n */\n- (nonnull id<FlutterPluginRegistrar>)registrarForPlugin:(nonnull NSString*)pluginKey;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Headers/FlutterView.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef FLUTTER_VIEW_H_\n#define FLUTTER_VIEW_H_\n\n#import <Cocoa/Cocoa.h>\n\n/**\n * View capable of acting as a rendering target and input source for the Flutter\n * engine.\n */\n@interface FlutterView : NSView\n\n@end\n\n#endif\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"FlutterEngine.h\"\n#import \"FlutterMacros.h\"\n#import \"FlutterPlatformViews.h\"\n#import \"FlutterPluginRegistrarMacOS.h\"\n\n/**\n * Values for the `mouseTrackingMode` property.\n */\ntypedef NS_ENUM(NSInteger, FlutterMouseTrackingMode) {\n  // Hover events will never be sent to Flutter.\n  FlutterMouseTrackingModeNone = 0,\n  // Hover events will be sent to Flutter when the view is in the key window.\n  FlutterMouseTrackingModeInKeyWindow,\n  // Hover events will be sent to Flutter when the view is in the active app.\n  FlutterMouseTrackingModeInActiveApp,\n  // Hover events will be sent to Flutter regardless of window and app focus.\n  FlutterMouseTrackingModeAlways,\n};\n\n/**\n * Controls a view that displays Flutter content and manages input.\n */\nFLUTTER_DARWIN_EXPORT\n@interface FlutterViewController : NSViewController <FlutterPluginRegistry>\n\n/**\n * The Flutter engine associated with this view controller.\n */\n@property(nonatomic, nonnull, readonly) FlutterEngine* engine;\n\n/**\n * The style of mouse tracking to use for the view. Defaults to\n * FlutterMouseTrackingModeInKeyWindow.\n */\n@property(nonatomic) FlutterMouseTrackingMode mouseTrackingMode;\n\n/**\n * Initializes a controller.\n */\n- (nonnull instancetype)init NS_DESIGNATED_INITIALIZER;\n\n- (nonnull instancetype)initWithNibName:(nullable NSString*)nibNameOrNil\n                                 bundle:(nullable NSBundle*)nibBundleOrNil\n    NS_DESIGNATED_INITIALIZER;\n- (nonnull instancetype)initWithCoder:(nonnull NSCoder*)nibNameOrNil NS_DESIGNATED_INITIALIZER;\n/**\n * Initializes this FlutterViewController with the specified `FlutterEngine`.\n *\n * The initialized viewcontroller will attach itself to the engine as part of this process.\n *\n * @param engine The `FlutterEngine` instance to attach to. Cannot be nil.\n * @param nibName The NIB name to initialize this controller with.\n * @param nibBundle The NIB bundle.\n */\n- (nonnull instancetype)initWithEngine:(nonnull FlutterEngine*)engine\n                               nibName:(nullable NSString*)nibName\n                                bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER;\n/**\n * Invoked by the engine right before the engine is restarted.\n *\n * This should reset states to as if the application has just started.  It\n * usually indicates a hot restart (Shift-R in Flutter CLI.)\n */\n- (void)onPreEngineRestart;\n\n/**\n * The contentView (FlutterView)'s background color is set to black during\n * its instantiation.\n *\n * The containing layer's color can be set to the NSColor provided to this method.\n *\n * For example, the background may be set after the FlutterViewController\n * is instantiated in MainFlutterWindow.swift in the Flutter project.\n * ```swift\n * import Cocoa\n * import FlutterMacOS\n *\n * class MainFlutterWindow: NSWindow {\n *   override func awakeFromNib() {\n *     let flutterViewController = FlutterViewController.init()\n *\n *     // The background color of the window and `FlutterViewController`\n *     // are retained separately.\n *     //\n *     // In this example, both the MainFlutterWindow and FlutterViewController's\n *     // FlutterView's backgroundColor are set to clear to achieve a fully\n *     // transparent effect.\n *     //\n *     // If the window's background color is not set, it will use the system\n *     // default.\n *     //\n *     // If the `FlutterView`'s color is not set via `FlutterViewController.setBackgroundColor`\n *     // it's default will be black.\n *     self.backgroundColor = NSColor.clear\n *     flutterViewController.backgroundColor = NSColor.clear\n *\n *     let windowFrame = self.frame\n *     self.contentViewController = flutterViewController\n *     self.setFrame(windowFrame, display: true)\n *\n *     RegisterGeneratedPlugins(registry: flutterViewController)\n *\n *     super.awakeFromNib()\n *   }\n * }\n * ```\n */\n@property(readwrite, nonatomic, nullable, copy) NSColor* backgroundColor;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/ClayMouseCursorPlugin.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h\"\n\n/**\n * A plugin to handle mouse cursor.\n *\n * Responsible for bridging the native macOS mouse cursor system with the\n * Flutter engine , via system channels.\n */\n@interface ClayMouseCursorPlugin : NSObject\n\n- (void)activateSystemCursor:(int)type path:(const char*)path;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/ClayMouseCursorPlugin.mm",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// Copyright 2012 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#import <objc/message.h>\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/ClayMouseCursorPlugin.h\"\n\n#include \"clay/ui/platform/cursor_types.h\"\n\nusing clay::CursorTypes;\n\nstatic NSString* const kTypeKey = @\"type\";\nstatic NSString* const kPathKey = @\"path\";\n\nstatic NSDictionary* systemCursors;\n\n// code from chromium/src/ui/base/cocoa/cursor_utils.mm\n// Private interface to CoreCursor. See\n// https://github.com/WebKit/WebKit/blob/main/Source/WebCore/PAL/pal/spi/mac/HIServicesSPI.h\n//\n// Note that the column/row resize cursors have a bar in the middle (e.g. <-|->)\n// whereas the frame resize cursors have no bar in the middle (e.g. <-->).\nenum class CrCoreCursorType : int32_t {\n  kArrow = 0,                           // NSCursor.arrowCursor\n  kIBeam = 1,                           // NSCursor.IBeamCursor\n  kMakeAlias = 2,                       // NSCursor.dragLinkCursor\n  kOperationNotAllowed = 3,             // NSCursor.operationNotAllowedCursor\n  kBusyButClickable = 4,                // NSCursor.busyButClickableCursor (private)\n  kCopy = 5,                            // NSCursor.dragCopyCursor\n  kScreenShotSelection = 7,             // -\n  kScreenShotSelectionToClip = 8,       // -\n  kScreenShotWindow = 9,                // -\n  kScreenShotWindowToClip = 10,         // -\n  kClosedHand = 11,                     // NSCursor.closedHandCursor\n  kOpenHand = 12,                       // NSCursor.openHandCursor\n  kPointingHand = 13,                   // NSCursor.pointingHandCursor\n  kCountingUpHand = 14,                 // -\n  kCountingDownHand = 15,               // -\n  kCountingUpAndDownHand = 16,          // -\n  kColumnResizeLeft = 17,               // [NSCursor columnResizeCursorInDirections:]\n  kColumnResizeRight = 18,              // [NSCursor columnResizeCursorInDirections:]\n  kColumnResizeLeftRight = 19,          // NSCursor.columnResizeCursor\n  kCrosshair = 20,                      // NSCursor.crosshairCursor\n  kRowResizeUp = 21,                    // [NSCursor rowResizeCursorInDirections:]\n  kRowResizeDown = 22,                  // [NSCursor rowResizeCursorInDirections:]\n  kRowResizeUpDown = 23,                // NSCursor.rowResizeCursor\n  kContextualMenu = 24,                 // NSCursor.contextualMenuCursor\n  kDisappearingItem = 25,               // NSCursor.disappearingItemCursor\n  kVerticalIBeam = 26,                  // NSCursor.IBeamCursorForVerticalLayout\n  kFrameResizeEast = 27,                // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeEastWest = 28,            // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeNortheast = 29,           // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeNortheastSouthwest = 30,  // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeNorth = 31,               // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeNorthSouth = 32,          // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeNorthwest = 33,           // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeNorthwestSoutheast = 34,  // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeSoutheast = 35,           // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeSouth = 36,               // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeSouthwest = 37,           // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kFrameResizeWest = 38,                // [NSCursor frameResizeCursorFromPosition:inDirections:]\n  kMove = 39,                           // oddly, not NSCursor._moveCursor (private)\n  kHelp = 40,                           // NSCursor._helpCursor (private)\n  kCell = 41,                           // -\n  kZoomIn = 42,                         // NSCursor.zoomInCursor\n  kZoomOut = 43,                        // NSCursor.zoomOutCursor\n};\n\n@interface CrCoreCursor : NSCursor\n\n+ (id)cursorWithType:(CrCoreCursorType)type;\n@property(readonly, nonatomic) CrCoreCursorType _coreCursorType;\n\n@end\n\n@implementation CrCoreCursor\n\n@synthesize _coreCursorType = _type;\n\n+ (id)cursorWithType:(CrCoreCursorType)type {\n  return [[CrCoreCursor alloc] initWithType:type];\n}\n\n- (id)initWithType:(CrCoreCursorType)type {\n  if ((self = [super init])) {\n    _type = type;\n  }\n  return self;\n}\n\n@end\n\n/**\n * Maps a Flutter's constant to a platform's cursor object.\n *\n * Returns the arrow cursor for unknown constants, including kSystemShapeNone.\n */\nstatic NSCursor* GetCursorByType(CursorTypes type) {\n  NSCursor* result = nil;\n\n  switch (type) {\n    case CursorTypes::kAlias:\n      result = [NSCursor dragLinkCursor];\n      break;\n    case CursorTypes::kBasic:\n      result = [NSCursor arrowCursor];\n      break;\n    case CursorTypes::kClick:\n      result = [NSCursor pointingHandCursor];\n      break;\n    case CursorTypes::kContextmenu:\n      result = [NSCursor contextualMenuCursor];\n      break;\n    case CursorTypes::kSystemmousecursor:\n      result = [NSCursor dragCopyCursor];\n      break;\n    case CursorTypes::kDisappearing:\n      result = [NSCursor disappearingItemCursor];\n      break;\n    case CursorTypes::kForbidden:\n      result = [NSCursor operationNotAllowedCursor];\n      break;\n    case CursorTypes::kGrab:\n      result = [NSCursor openHandCursor];\n      break;\n    case CursorTypes::kGrabbing:\n      result = [NSCursor closedHandCursor];\n      break;\n    case CursorTypes::kNodrop:\n      result = [NSCursor operationNotAllowedCursor];\n      break;\n    case CursorTypes::kPrecise:\n      result = [NSCursor crosshairCursor];\n      break;\n    case CursorTypes::kText:\n      result = [NSCursor IBeamCursor];\n      break;\n    case CursorTypes::kResizecolumn:\n#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000\n      if (@available(macOS 15.0, *))\n        result = [NSCursor columnResizeCursor];\n      else\n#endif\n        result = [NSCursor resizeLeftRightCursor];\n      break;\n    case CursorTypes::kResizedown:\n      result = [NSCursor resizeDownCursor];\n      break;\n    case CursorTypes::kResizeleft:\n      result = [NSCursor resizeLeftCursor];\n      break;\n    case CursorTypes::kResizeleftright:\n#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000\n      if (@available(macOS 15.0, *)) {\n        result = [NSCursor frameResizeCursorFromPosition:NSCursorFrameResizePositionLeft\n                                            inDirections:NSCursorFrameResizeDirectionsAll];\n      } else\n#endif\n        result = [CrCoreCursor cursorWithType:CrCoreCursorType::kFrameResizeEastWest];\n      break;\n    case CursorTypes::kResizeright:\n      result = [NSCursor resizeRightCursor];\n      break;\n    case CursorTypes::kResizerow:\n#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000\n      if (@available(macOS 15.0, *))\n        result = NSCursor.rowResizeCursor;\n      else\n#endif\n        result = NSCursor.resizeUpDownCursor;\n      break;\n    case CursorTypes::kResizeup:\n      result = [NSCursor resizeUpCursor];\n      break;\n    case CursorTypes::kResizeupdown:\n      result = [NSCursor resizeUpDownCursor];\n      break;\n    // cspell:disable-next-line\n    case CursorTypes::kResizedownleft:\n#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000\n      if (@available(macOS 15.0, *)) {\n        result = [NSCursor frameResizeCursorFromPosition:NSCursorFrameResizePositionBottomLeft\n                                            inDirections:NSCursorFrameResizeDirectionsOutward];\n      } else\n#endif\n        result = [CrCoreCursor cursorWithType:CrCoreCursorType::kFrameResizeSouthwest];\n      break;\n    // cspell:disable-next-line\n    case CursorTypes::kResizedownright:\n#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000\n      if (@available(macOS 15.0, *)) {\n        result = [NSCursor frameResizeCursorFromPosition:NSCursorFrameResizePositionBottomRight\n                                            inDirections:NSCursorFrameResizeDirectionsOutward];\n      } else\n#endif\n        result = [CrCoreCursor cursorWithType:CrCoreCursorType::kFrameResizeSoutheast];\n      break;\n    case CursorTypes::kResizeupleft:\n#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000\n      if (@available(macOS 15.0, *)) {\n        result = [NSCursor frameResizeCursorFromPosition:NSCursorFrameResizePositionTopLeft\n                                            inDirections:NSCursorFrameResizeDirectionsOutward];\n      } else\n#endif\n        result = [CrCoreCursor cursorWithType:CrCoreCursorType::kFrameResizeNorthwest];\n      break;\n    case CursorTypes::kResizeupright:\n#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150000\n      if (@available(macOS 15.0, *)) {\n        result = [NSCursor frameResizeCursorFromPosition:NSCursorFrameResizePositionTopRight\n                                            inDirections:NSCursorFrameResizeDirectionsOutward];\n      } else\n#endif\n        result = [CrCoreCursor cursorWithType:CrCoreCursorType::kFrameResizeNortheast];\n      break;\n    case CursorTypes::kVerticaltext:\n      result = [NSCursor IBeamCursorForVerticalLayout];\n      break;\n    default:\n      break;\n  }\n\n  if (result == nil) {\n    // TODO(jiangwenlong): support all css cursor if need\n    return [NSCursor arrowCursor];\n  }\n\n  return result;\n}\n\n@interface ClayMouseCursorPlugin ()\n/**\n * Whether the cursor is currently hidden.\n */\n@property(nonatomic) BOOL hidden;\n\n/**\n * Handles the method call that activates a system cursor.\n *\n */\n- (void)activateSystemCursor:(nonnull NSDictionary*)arguments;\n\n/**\n * Displays the specified cursor.\n *\n * Unhides the cursor before displaying the cursor, and updates\n * internal states.\n */\n- (void)displayCursorObject:(nonnull NSCursor*)cursorObject;\n\n/**\n * Hides the cursor.\n */\n- (void)hide;\n\n@end\n\n@implementation ClayMouseCursorPlugin\n\n#pragma mark - Private\n\nNSMutableDictionary* cachedSystemCursors;\n\n- (instancetype)init {\n  self = [super init];\n  if (self) {\n    cachedSystemCursors = [NSMutableDictionary dictionary];\n  }\n  return self;\n}\n\n- (void)dealloc {\n  if (_hidden) {\n    [NSCursor unhide];\n  }\n}\n\n- (void)activateSystemCursor:(nonnull NSDictionary*)arguments {\n  NSNumber* num = arguments[kTypeKey];\n  NSString* path = arguments[kPathKey];\n  if (num == NULL) {\n    return;\n  }\n  CursorTypes type = (CursorTypes)[num intValue];\n\n  if (type == clay::CursorTypes::kNone) {\n    [self hide];\n    return;\n  }\n\n  NSCursor* cursorObject = [ClayMouseCursorPlugin cursorFromType:type path:path];\n  [self displayCursorObject:cursorObject];\n}\n\n- (void)activateSystemCursor:(int)type path:(const char*)path {\n  NSDictionary* args =\n      @{@\"type\" : @(type), @\"path\" : path ? [NSString stringWithUTF8String:path] : @\"\"};\n  [self activateSystemCursor:args];\n}\n\n- (void)displayCursorObject:(nonnull NSCursor*)cursorObject {\n  [cursorObject set];\n  if (_hidden) {\n    [NSCursor unhide];\n  }\n  _hidden = NO;\n}\n\n- (void)hide {\n  if (!_hidden) {\n    [NSCursor hide];\n  }\n  _hidden = YES;\n}\n\n+ (NSCursor*)cursorFromType:(CursorTypes)type path:(NSString*)path {\n  NSCursor* result = nil;\n\n  switch (type) {\n    case CursorTypes::kNet:\n    case CursorTypes::kFile:\n      // TODO(jiangwenlong) : support network and local file\n      result = [NSCursor arrowCursor];\n      break;\n    default:\n      result = GetCursorByType(type);\n      break;\n  }\n  return result;\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/ClayViewProvider.mm",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#import \"clay/shell/platform/darwin/macos/framework/Headers/ClayViewProvider.h\"\n#include <Carbon/Carbon.h>\n#include <objc/objc.h>\n#include <cctype>\n#include \"base/include/fml/memory/weak_ptr.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/ClayMouseCursorPlugin.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/ClayViewProvider_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterView.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace {\n\n// Use different device ID for mouse and pan/zoom events, since we can't differentiate the actual\n// device (mouse v.s. trackpad).\nstatic constexpr int32_t kMousePointerDeviceId = 0;\nstatic constexpr int32_t kPointerPanZoomDeviceId = 1;\n\nusing clay::KeyboardLayoutNotifier;\nusing clay::LayoutClue;\n\n/// Clipboard plain text format.\nconstexpr char kTextPlainFormat[] = \"text/plain\";\n\n/// The private notification for voice over.\nstatic NSString* const EnhancedUserInterfaceNotification =\n    @\"NSApplicationDidChangeAccessibilityEnhancedUserInterfaceNotification\";\nstatic NSString* const EnhancedUserInterfaceKey = @\"AXEnhancedUserInterface\";\n\n/**\n * State tracking for mouse events, to adapt between the events coming from the system and the\n * events that the embedding API expects.\n */\nstruct MouseState {\n  /**\n   * The currently pressed buttons, as represented in FlutterPointerEvent.\n   */\n  int64_t buttons = 0;\n\n  /**\n   * The accumulated gesture pan.\n   */\n  CGFloat delta_x = 0;\n  CGFloat delta_y = 0;\n\n  /**\n   * The accumulated gesture zoom scale.\n   */\n  CGFloat scale = 0;\n\n  /**\n   * The accumulated gesture rotation.\n   */\n  CGFloat rotation = 0;\n\n  /**\n   * Whether or not a kAdd event has been sent (or sent again since the last kRemove if tracking is\n   * enabled). Used to determine whether to send a kAdd event before sending an incoming mouse\n   * event, since Flutter expects pointers to be added before events are sent for them.\n   */\n  bool clay_state_is_added = false;\n\n  /**\n   * Whether or not a kDown has been sent since the last kAdd/kUp.\n   */\n  bool clay_state_is_down = false;\n\n  /**\n   * Whether or not mouseExited: was received while a button was down. Cocoa's behavior when\n   * dragging out of a tracked area is to send an exit, then keep sending drag events until the last\n   * button is released. Flutter doesn't expect to receive events after a kRemove, so the kRemove\n   * for the exit needs to be delayed until after the last mouse button is released. If cursor\n   * returns back to the window while still dragging, the flag is cleared in mouseEntered:.\n   */\n  bool has_pending_exit = false;\n\n  /*\n   * Whether or not a kPanZoomStart has been sent since the last kAdd/kPanZoomEnd.\n   */\n  bool flutter_state_is_pan_zoom_started = false;\n\n  /**\n   * State of pan gesture.\n   */\n  NSEventPhase pan_gesture_phase = NSEventPhaseNone;\n\n  /**\n   * State of scale gesture.\n   */\n  NSEventPhase scale_gesture_phase = NSEventPhaseNone;\n\n  /**\n   * State of rotate gesture.\n   */\n  NSEventPhase rotate_gesture_phase = NSEventPhaseNone;\n\n  /**\n   * Time of last scroll momentum event.\n   */\n  NSTimeInterval last_scroll_momentum_changed_time = 0;\n\n  /**\n   * Resets all gesture state to default values.\n   */\n  void GestureReset() {\n    delta_x = 0;\n    delta_y = 0;\n    scale = 0;\n    rotation = 0;\n    flutter_state_is_pan_zoom_started = false;\n    pan_gesture_phase = NSEventPhaseNone;\n    scale_gesture_phase = NSEventPhaseNone;\n    rotate_gesture_phase = NSEventPhaseNone;\n  }\n\n  /**\n   * Resets all state to default values.\n   */\n  void Reset() {\n    clay_state_is_added = false;\n    clay_state_is_down = false;\n    has_pending_exit = false;\n    buttons = 0;\n  }\n};\n\n/**\n * Returns the current Unicode layout data (kTISPropertyUnicodeKeyLayoutData).\n *\n * To use the returned data, convert it to CFDataRef first, finds its bytes\n * with CFDataGetBytePtr, then reinterpret it into const UCKeyboardLayout*.\n * It's returned in NSData* to enable auto reference count.\n */\nNSData* currentKeyboardLayoutData() {\n  TISInputSourceRef source = TISCopyCurrentKeyboardInputSource();\n  CFTypeRef layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);\n  if (layout_data == nil) {\n    CFRelease(source);\n    // TISGetInputSourceProperty returns null with Japanese keyboard layout.\n    // Using TISCopyCurrentKeyboardLayoutInputSource to fix NULL return.\n    // https://github.com/microsoft/node-native-keymap/blob/5f0699ded00179410a14c0e1b0e089fe4df8e130/src/keyboard_mac.mm#L91\n    source = TISCopyCurrentKeyboardLayoutInputSource();\n    layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);\n  }\n  return (__bridge_transfer NSData*)CFRetain(layout_data);\n}\n\n}  // namespace\n\n#pragma mark - Private interface declaration.\n\n/**\n * Private interface declaration for ClayViewProvider.\n */\n@interface ClayViewProvider () <FlutterViewReshapeListener>\n\n/**\n * The tracking area used to generate hover events, if enabled.\n */\n@property(nonatomic) NSTrackingArea* trackingArea;\n\n/**\n * The current state of the mouse and the sent mouse events.\n */\n@property(nonatomic) MouseState mouseState;\n\n/**\n * Event monitor for keyUp events.\n */\n@property(nonatomic) id keyUpMonitor;\n\n/**\n * TODO\n */\n@property(nonatomic) FlutterKeyboardManager* keyboardManager;\n\n@property(nonatomic) FlutterTextInputPlugin* textInputPlugin;\n\n@property(nonatomic, nullable) FlutterView* view;\n\n@property(nonatomic) KeyboardLayoutNotifier keyboardLayoutNotifier;\n\n@property(nonatomic) NSData* keyboardLayoutData;\n\n/**\n * Starts running |engine|, including any initial setup.\n */\n- (BOOL)launchEngine;\n\n/**\n * Updates |trackingArea| for the current tracking settings, creating it with\n * the correct mode if tracking is enabled, or removing it if not.\n */\n- (void)configureTrackingArea;\n\n/**\n * Creates and registers plugins used by this view controller.\n */\n- (void)addInternalPlugins;\n\n/**\n * Calls dispatchMouseEvent:phase: with a phase determined by self.mouseState.\n *\n * mouseState.buttons should be updated before calling this method.\n */\n- (void)dispatchMouseEvent:(nonnull NSEvent*)event;\n\n/**\n * Calls dispatchMouseEvent:phase: with a phase determined by event.phase.\n */\n- (void)dispatchGestureEvent:(nonnull NSEvent*)event;\n\n/**\n * Converts |event| to a ClayPointerEvent with the given phase, and sends it to the engine.\n */\n- (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(ClayPointerPhase)phase;\n\n/**\n * Initializes the KVO for user settings and passes the initial user settings to the engine.\n */\n- (void)sendInitialSettings;\n\n/**\n * Responds to updates in the user settings and passes this data to the engine.\n */\n- (void)onSettingsChanged:(NSNotification*)notification;\n\n/**\n * Plays a system sound. |soundType| specifies which system sound to play. Valid\n * values can be found in the SystemSoundType enum in the services SDK package.\n */\n- (void)playSystemSound:(NSString*)soundType;\n\n/**\n * Reads the data from the clipboard. |format| specifies the media type of the\n * data to obtain.\n */\n- (NSDictionary*)getClipboardData:(NSString*)format;\n\n/**\n * Clears contents and writes new data into clipboard. |data| is a dictionary where\n * the keys are the type of data, and tervalue the data to be stored.\n */\n- (void)setClipboardData:(NSDictionary*)data;\n\n/**\n * Returns true iff the clipboard contains nonempty string data.\n */\n- (BOOL)clipboardHasStrings;\n\n@end\n\n#pragma mark - ClayViewProvider implementation.\n\n@implementation ClayViewProvider {\n  ClayServiceManager* _serviceManager;\n\n  void (*_ime_callback)(NSEvent*, void*);\n  void* _ime_callback_arg;\n\n  BOOL _viewDidLoad;\n  BOOL _isLaunchEngineSuccess;\n}\n\n/**\n * Performs initialization that's common between the different init paths.\n */\nstatic void CommonInit(ClayViewProvider* controller) {\n  if (!controller->_engine) {\n    controller->_engine = [[FlutterEngine alloc] initWithName:@\"io.flutter\"\n                                       allowHeadlessExecution:NO];\n    [controller->_engine setViewProvider:controller];\n  }\n  controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow;\n}\n\n- (instancetype)init {\n  self = [super init];\n  NSAssert(self, @\"Super init cannot be nil\");\n  _ime_callback = nullptr;\n  _viewDidLoad = NO;\n  _serviceManager = [[ClayServiceManager alloc] init];\n  CommonInit(self);\n  _isLaunchEngineSuccess = [self launchEngine];\n  return self;\n}\n\n- (FlutterView*)view {\n  if (!_view) {\n    [self loadView];\n  }\n  return _view;\n}\n\n- (void)loadView {\n  FlutterView* flutterView;\n  id<MTLDevice> device = _engine.renderer.device;\n  id<MTLCommandQueue> commandQueue = _engine.renderer.commandQueue;\n  if (!device || !commandQueue) {\n    FML_LOG(ERROR) << \"Unable to create FlutterView; no MTLDevice or MTLCommandQueue available.\";\n    return;\n  }\n  flutterView = [[FlutterView alloc] initWithMTLDevice:device\n                                          commandQueue:commandQueue\n                                       reshapeListener:self];\n\n  flutterView.provider = self;\n  self.view = flutterView;\n  _viewDidLoad = YES;\n  // As viewWillAppear not be called, put it here.\n  [self listenForMetaModifiedKeyUpEvents];\n\n  // track mouse hover events\n  [self configureTrackingArea];\n}\n\n- (void)viewDidLoad {\n  [self configureTrackingArea];\n}\n\n- (void)viewWillAppear {\n  if (!_engine.running) {\n    [self launchEngine];\n  }\n  [self listenForMetaModifiedKeyUpEvents];\n}\n\n- (void)viewWillDisappear {\n  // Per Apple's documentation, it is discouraged to call removeMonitor: in dealloc, and it's\n  // recommended to be called earlier in the lifecycle.\n  [NSEvent removeMonitor:_keyUpMonitor];\n  _keyUpMonitor = nil;\n}\n\n- (void)onEnterForeground {\n  if (!_engine) {\n    return;\n  }\n  [_engine onEnterForeground];\n}\n\n- (void)onEnterBackground {\n  if (!_engine) {\n    return;\n  }\n  [_engine onEnterBackground];\n}\n\n- (void)setVisible:(BOOL)visible {\n  if (!_engine) {\n    return;\n  }\n  [_engine setVisible:visible];\n  if (_view) {\n    // Set the synchronizer's visibility to avoid blocking when resizing.\n    [[_view threadSynchronizer] setVisible:visible];\n  }\n}\n\n- (void)setInterceptUrlCallback:(FlutterViewShouldInterceptUrlCallback _Nullable)callback {\n  if (!_engine) {\n    return;\n  }\n\n  [_engine setInterceptUrlCallback:callback];\n}\n\n- (void)dealloc {\n  _engine.view = nil;\n  self.view.provider = nil;\n}\n\n- (nullable void*)clayViewContext {\n  if (!_engine) {\n    return nullptr;\n  }\n\n  void* result = [_engine clayViewContext];\n  return result;\n}\n\n#pragma mark - Public methods\n\n- (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {\n  if (_mouseTrackingMode == mode) {\n    return;\n  }\n  _mouseTrackingMode = mode;\n  [self configureTrackingArea];\n}\n\n#pragma mark - Framework-internal methods\n\n- (FlutterView*)flutterView {\n  return self.view;\n}\n\n#pragma mark - Private methods\n\n- (BOOL)launchEngine {\n  // Register internal plugins before starting the engine.\n  [self addInternalPlugins];\n  _engine.view = self.view;\n  if (![_engine runWithEntrypoint:nil]) {\n    return NO;\n  }\n  // Send the initial user settings such as brightness and text scale factor\n  // to the engine.\n  // TODO(stuartmorgan): Move this logic to FlutterEngine.\n  [self sendInitialSettings];\n  return YES;\n}\n\n// macOS does not call keyUp: on a key while the command key is pressed. This results in a loss\n// of a key event once the modified key is released. This method registers the\n// ViewController as a listener for a keyUp event before it's handled by NSApplication, and should\n// NOT modify the event to avoid any unexpected behavior.\n- (void)listenForMetaModifiedKeyUpEvents {\n  if (_keyUpMonitor != nil) {\n    // It is possible for [NSViewController viewWillAppear] to be invoked multiple times\n    // in a row. https://github.com/flutter/flutter/issues/105963\n    return;\n  }\n  ClayViewProvider* __weak weakSelf = self;\n  _keyUpMonitor = [NSEvent\n      addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp\n                                   handler:^NSEvent*(NSEvent* event) {\n                                     // Intercept keyUp only for events triggered on the current\n                                     // view.\n                                     if (weakSelf.view && weakSelf.view.superview &&\n                                         ([[event window] firstResponder] == weakSelf.view) &&\n                                         ([event modifierFlags] & NSEventModifierFlagCommand) &&\n                                         ([event type] == NSEventTypeKeyUp))\n                                       [weakSelf keyUp:event];\n                                     return event;\n                                   }];\n}\n\n- (void)configureTrackingArea {\n  if (_mouseTrackingMode != FlutterMouseTrackingModeNone && self.view) {\n    NSTrackingAreaOptions options =\n        NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingInVisibleRect;\n    switch (_mouseTrackingMode) {\n      case FlutterMouseTrackingModeInKeyWindow:\n        options |= NSTrackingActiveInKeyWindow;\n        break;\n      case FlutterMouseTrackingModeInActiveApp:\n        options |= NSTrackingActiveInActiveApp;\n        break;\n      case FlutterMouseTrackingModeAlways:\n        options |= NSTrackingActiveAlways;\n        break;\n      default:\n        FML_LOG(ERROR) << \"Error: Unrecognized mouse tracking mode: \" << _mouseTrackingMode;\n        return;\n    }\n    _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect\n                                                 options:options\n                                                   owner:self\n                                                userInfo:nil];\n    [self.view addTrackingArea:_trackingArea];\n  } else if (_trackingArea) {\n    [self.view removeTrackingArea:_trackingArea];\n    _trackingArea = nil;\n  }\n}\n\n- (void)addInternalPlugins {\n  __weak ClayViewProvider* weakSelf = self;\n  _keyboardManager = [[FlutterKeyboardManager alloc] initWithViewDelegate:weakSelf];\n  _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithClayProvider:self];\n  _mouseCursorPlugin = [[ClayMouseCursorPlugin alloc] init];\n}\n\n- (void)dispatchMouseEvent:(nonnull NSEvent*)event {\n  ClayPointerPhase phase =\n      _mouseState.buttons == 0\n          ? (_mouseState.clay_state_is_down ? kClayPointerPhaseUp : kClayPointerPhaseHover)\n          : (_mouseState.clay_state_is_down ? kClayPointerPhaseMove : kClayPointerPhaseDown);\n  [self dispatchMouseEvent:event phase:phase];\n}\n\n- (void)dispatchGestureEvent:(nonnull NSEvent*)event {\n  if (event.phase == NSEventPhaseMayBegin) {\n    return;\n  }\n  if (event.phase == NSEventPhaseBegan) {\n    [self dispatchMouseEvent:event phase:kClayPointerPhasePanZoomStart];\n  } else if (event.phase == NSEventPhaseChanged) {\n    [self dispatchMouseEvent:event phase:kClayPointerPhasePanZoomUpdate];\n  } else if (event.phase == NSEventPhaseEnded || event.phase == NSEventPhaseCancelled) {\n    [self dispatchMouseEvent:event phase:kClayPointerPhasePanZoomEnd];\n  } else if (event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone) {\n    [self dispatchMouseEvent:event phase:kClayPointerPhaseHover];\n  } else {\n    // Waiting until the first momentum change event is a workaround for an issue where\n    // touchesBegan: is called unexpectedly while in low power mode within the interval between\n    // momentum start and the first momentum change.\n    if (event.momentumPhase == NSEventPhaseChanged) {\n      _mouseState.last_scroll_momentum_changed_time = event.timestamp;\n    }\n    // Skip momentum update events, the framework will generate scroll momentum.\n    NSAssert(event.momentumPhase != NSEventPhaseNone,\n             @\"Received gesture event with unexpected phase\");\n  }\n}\n\n- (void)dispatchMouseEvent:(NSEvent*)event phase:(ClayPointerPhase)phase {\n  // There are edge cases where the system will deliver enter out of order relative to other\n  // events (e.g., drag out and back in, release, then click; mouseDown: will be called before\n  // mouseEntered:). Discard those events, since the add will already have been synthesized.\n  if (_mouseState.clay_state_is_added && phase == kClayPointerPhaseAdd) {\n    return;\n  }\n\n  // Multiple gesture recognizers could be active at once, we can't send multiple kPanZoomStart.\n  // For example: rotation and magnification.\n  if (phase == kClayPointerPhasePanZoomStart || phase == kClayPointerPhasePanZoomEnd) {\n    if (event.type == NSEventTypeScrollWheel) {\n      _mouseState.pan_gesture_phase = event.phase;\n    } else if (event.type == NSEventTypeMagnify) {\n      _mouseState.scale_gesture_phase = event.phase;\n    } else if (event.type == NSEventTypeRotate) {\n      _mouseState.rotate_gesture_phase = event.phase;\n    }\n  }\n  if (phase == kClayPointerPhasePanZoomStart) {\n    if (event.type == NSEventTypeScrollWheel) {\n      // Ensure scroll inertia cancel event is not sent afterwards.\n      _mouseState.last_scroll_momentum_changed_time = 0;\n    }\n    if (_mouseState.flutter_state_is_pan_zoom_started) {\n      // Already started on a previous gesture type\n      return;\n    }\n    _mouseState.flutter_state_is_pan_zoom_started = true;\n  }\n  if (phase == kClayPointerPhasePanZoomEnd) {\n    if (!_mouseState.flutter_state_is_pan_zoom_started) {\n      // NSEventPhaseCancelled is sometimes received at incorrect times in the state\n      // machine, just ignore it here if it doesn't make sense\n      // (we have no active gesture to cancel).\n      NSAssert(event.phase == NSEventPhaseCancelled,\n               @\"Received gesture event with unexpected phase\");\n      return;\n    }\n    // NSEventPhase values are powers of two, we can use this to inspect merged phases.\n    NSEventPhase all_gestures_fields = _mouseState.pan_gesture_phase |\n                                       _mouseState.scale_gesture_phase |\n                                       _mouseState.rotate_gesture_phase;\n    NSEventPhase active_mask = NSEventPhaseBegan | NSEventPhaseChanged;\n    if ((all_gestures_fields & active_mask) != 0) {\n      // Even though this gesture type ended, a different type is still active.\n      return;\n    }\n  }\n\n  // If a pointer added event hasn't been sent, synthesize one using this event for the basic\n  // information.\n  if (!_mouseState.clay_state_is_added && phase != kClayPointerPhaseAdd) {\n    // Only the values extracted for use in clayEvent below matter, the rest are dummy values.\n    NSEvent* addEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseEntered\n                                               location:event.locationInWindow\n                                          modifierFlags:0\n                                              timestamp:event.timestamp\n                                           windowNumber:event.windowNumber\n                                                context:nil\n                                            eventNumber:0\n                                         trackingNumber:0\n                                               userData:NULL];\n    [self dispatchMouseEvent:addEvent phase:kClayPointerPhaseAdd];\n  }\n\n  NSPoint locationInView = [self.view convertPoint:event.locationInWindow fromView:nil];\n  NSPoint locationInBackingCoordinates = [self.view convertPointToBacking:locationInView];\n  int32_t device = kMousePointerDeviceId;\n  ClayPointerDeviceKind deviceKind = kClayPointerDeviceKindMouse;\n  if (phase == kClayPointerPhasePanZoomStart || phase == kClayPointerPhasePanZoomUpdate ||\n      phase == kClayPointerPhasePanZoomEnd) {\n    device = kPointerPanZoomDeviceId;\n    deviceKind = kClayPointerDeviceKindTrackpad;\n  }\n  ClayPointerEvent clayEvent = {\n      .struct_size = sizeof(clayEvent),\n      .phase = phase,\n      .timestamp = static_cast<size_t>(event.timestamp * USEC_PER_SEC),\n      .x = locationInBackingCoordinates.x,\n      .y = -locationInBackingCoordinates.y,  // convertPointToBacking makes this negative.\n      .device = device,\n      .device_kind = deviceKind,\n      // If a click triggered a synthesized kAdd, don't pass the buttons in that event.\n      .buttons = phase == kClayPointerPhaseAdd ? 0 : _mouseState.buttons,\n      .is_precise_scroll = 1,\n  };\n\n  if (phase == kClayPointerPhasePanZoomUpdate) {\n    if (event.type == NSEventTypeScrollWheel) {\n      _mouseState.delta_x += event.scrollingDeltaX * self.view.layer.contentsScale;\n      _mouseState.delta_y += event.scrollingDeltaY * self.view.layer.contentsScale;\n    } else if (event.type == NSEventTypeMagnify) {\n      _mouseState.scale += event.magnification;\n    } else if (event.type == NSEventTypeRotate) {\n      _mouseState.rotation += event.rotation * (-M_PI / 180.0);\n    }\n    clayEvent.pan_x = _mouseState.delta_x;\n    clayEvent.pan_y = _mouseState.delta_y;\n    // Scale value needs to be normalized to range 0->infinity.\n    clayEvent.scale = pow(2.0, _mouseState.scale);\n    clayEvent.rotation = _mouseState.rotation;\n  } else if (phase == kClayPointerPhasePanZoomEnd) {\n    _mouseState.GestureReset();\n  } else if (phase != kClayPointerPhasePanZoomStart && event.type == NSEventTypeScrollWheel) {\n    clayEvent.signal_kind = kClayPointerSignalKindScroll;\n\n    double pixelsPerLine = 1.0;\n    if (!event.hasPreciseScrollingDeltas) {\n      CGEventSourceRef source = CGEventCreateSourceFromEvent(event.CGEvent);\n      pixelsPerLine = CGEventSourceGetPixelsPerLine(source);\n      if (source) {\n        CFRelease(source);\n      }\n      clayEvent.is_precise_scroll = 0;\n    }\n    double scaleFactor = self.view.layer.contentsScale;\n    clayEvent.scroll_delta_x = -event.scrollingDeltaX * pixelsPerLine * scaleFactor;\n    clayEvent.scroll_delta_y = -event.scrollingDeltaY * pixelsPerLine * scaleFactor;\n  }\n  [_engine sendPointerEvent:clayEvent];\n\n  // Update tracking of state as reported to Flutter.\n  if (phase == kClayPointerPhaseDown) {\n    _mouseState.clay_state_is_down = true;\n  } else if (phase == kClayPointerPhaseUp) {\n    _mouseState.clay_state_is_down = false;\n    if (_mouseState.has_pending_exit) {\n      [self dispatchMouseEvent:event phase:kClayPointerPhaseRemove];\n      _mouseState.has_pending_exit = false;\n    }\n  } else if (phase == kClayPointerPhaseAdd) {\n    _mouseState.clay_state_is_added = true;\n  } else if (phase == kClayPointerPhaseRemove) {\n    _mouseState.Reset();\n  }\n}\n\n- (void)onSettingsChanged:(NSNotification*)notification {\n  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.\n}\n\n- (void)sendInitialSettings {\n  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.\n  [[NSDistributedNotificationCenter defaultCenter]\n      addObserver:self\n         selector:@selector(onSettingsChanged:)\n             name:@\"AppleInterfaceThemeChangedNotification\"\n           object:nil];\n  [self onSettingsChanged:nil];\n}\n\n- (void)playSystemSound:(NSString*)soundType {\n  if ([soundType isEqualToString:@\"SystemSoundType.alert\"]) {\n    NSBeep();\n  }\n}\n\n- (NSDictionary*)getClipboardData:(NSString*)format {\n  NSPasteboard* pasteboard = self.pasteboard;\n  if ([format isEqualToString:@(kTextPlainFormat)]) {\n    NSString* stringInPasteboard = [pasteboard stringForType:NSPasteboardTypeString];\n    return stringInPasteboard == nil ? nil : @{@\"text\" : stringInPasteboard};\n  }\n  return nil;\n}\n\n- (void)setClipboardData:(NSDictionary*)data {\n  NSPasteboard* pasteboard = self.pasteboard;\n  NSString* text = data[@\"text\"];\n  [pasteboard clearContents];\n  if (text && ![text isEqual:[NSNull null]]) {\n    [pasteboard setString:text forType:NSPasteboardTypeString];\n  }\n}\n\n- (BOOL)clipboardHasStrings {\n  return [self.pasteboard stringForType:NSPasteboardTypeString].length > 0;\n}\n\n- (NSPasteboard*)pasteboard {\n  return [NSPasteboard generalPasteboard];\n}\n\n- (BOOL)viewLoaded {\n  return _viewDidLoad;\n}\n\n- (BOOL)isLaunchEngineSuccess {\n  return _isLaunchEngineSuccess;\n}\n\n#pragma mark - FlutterViewReshapeListener\n\n/**\n * Responds to view reshape by notifying the engine of the change in dimensions.\n */\n- (void)viewDidReshape:(NSView*)view {\n  [_engine updateWindowMetrics];\n}\n\n#pragma mark - FlutterPluginRegistry\n\n- (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {\n  return [_engine registrarForPlugin:pluginName];\n}\n\n#pragma mark - Use Clay Service\n\n- (ClayServiceManager*)GetClayServiceManager {\n  return _serviceManager;\n}\n\n#pragma mark - NSResponder\n\n- (BOOL)acceptsFirstResponder {\n  return NO;\n}\n\n- (void)keyDown:(NSEvent*)event {\n  if (_ime_callback) {\n    _ime_callback(event, _ime_callback_arg);\n    return;\n  }\n  if ([_textInputPlugin isComposing] && [_textInputPlugin handleKeyEvent:event]) {\n    return;\n  }\n  [_keyboardManager handleEvent:event];\n}\n\n- (void)keyUp:(NSEvent*)event {\n  if (_ime_callback) {\n    _ime_callback(event, _ime_callback_arg);\n    return;\n  }\n  if ([_textInputPlugin isComposing] && [_textInputPlugin handleKeyEvent:event]) {\n    return;\n  }\n  [_keyboardManager handleEvent:event];\n}\n\n- (void)requestIME:(nullable void*)callback arg:(nullable void*)arg {\n  _ime_callback = (void (*)(NSEvent*, void*))callback;\n  _ime_callback_arg = arg;\n}\n\n/**\n * Returns YES if provided event is being currently redispatched by keyboard manager.\n */\n- (BOOL)isDispatchingKeyEvent:(nonnull NSEvent*)event {\n  return [_keyboardManager isDispatchingKeyEvent:event];\n}\n\n- (void)flagsChanged:(NSEvent*)event {\n  if (_ime_callback) {\n    _ime_callback(event, _ime_callback_arg);\n    return;\n  }\n  if ([_textInputPlugin isComposing] && [_textInputPlugin handleKeyEvent:event]) {\n    return;\n  }\n  [_keyboardManager handleEvent:event];\n}\n\n- (void)mouseEntered:(NSEvent*)event {\n  [self dispatchMouseEvent:event phase:kClayPointerPhaseAdd];\n}\n\n- (void)mouseExited:(NSEvent*)event {\n  if (_mouseState.buttons != 0) {\n    _mouseState.has_pending_exit = true;\n    return;\n  }\n  [self dispatchMouseEvent:event phase:kClayPointerPhaseRemove];\n}\n\n- (void)mouseDown:(NSEvent*)event {\n  _mouseState.buttons |= kClayPointerMouseButtonsMousePrimary;\n  [self dispatchMouseEvent:event];\n}\n\n- (void)mouseUp:(NSEvent*)event {\n  _mouseState.buttons &= ~static_cast<uint64_t>(kClayPointerMouseButtonsMousePrimary);\n  [self dispatchMouseEvent:event];\n}\n\n- (void)mouseDragged:(NSEvent*)event {\n  [self dispatchMouseEvent:event];\n}\n\n- (void)rightMouseDown:(NSEvent*)event {\n  _mouseState.buttons |= kClayPointerMouseButtonsMouseSecondary;\n  [self dispatchMouseEvent:event];\n}\n\n- (void)rightMouseUp:(NSEvent*)event {\n  _mouseState.buttons &= ~static_cast<uint64_t>(kClayPointerMouseButtonsMouseSecondary);\n  [self dispatchMouseEvent:event];\n}\n\n- (void)rightMouseDragged:(NSEvent*)event {\n  [self dispatchMouseEvent:event];\n}\n\n- (void)otherMouseDown:(NSEvent*)event {\n  _mouseState.buttons |= (1 << event.buttonNumber);\n  [self dispatchMouseEvent:event];\n}\n\n- (void)otherMouseUp:(NSEvent*)event {\n  _mouseState.buttons &= ~static_cast<uint64_t>(1 << event.buttonNumber);\n  [self dispatchMouseEvent:event];\n}\n\n- (void)otherMouseDragged:(NSEvent*)event {\n  [self dispatchMouseEvent:event];\n}\n\n- (void)mouseMoved:(NSEvent*)event {\n  [self dispatchMouseEvent:event];\n}\n\n- (void)scrollWheel:(NSEvent*)event {\n  [self dispatchGestureEvent:event];\n}\n\n- (void)magnifyWithEvent:(NSEvent*)event {\n  [self dispatchGestureEvent:event];\n}\n\n#pragma mark - FlutterKeyboardViewDelegate\n- (void)sendKeyEvent:(const ClayKeyEvent&)event\n            callback:(nullable ClayKeyEventCallback)callback\n            userData:(nullable void*)userData {\n  [_engine sendKeyEvent:event callback:callback userData:userData];\n}\n\n- (BOOL)onTextInputKeyEvent:(nonnull NSEvent*)event {\n  return [_textInputPlugin handleKeyEvent:event];\n}\n\n- (void)subscribeToKeyboardLayoutChange:(nullable KeyboardLayoutNotifier)callback {\n  _keyboardLayoutNotifier = callback;\n}\n\n- (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {\n  if (_keyboardLayoutData == nil) {\n    _keyboardLayoutData = currentKeyboardLayoutData();\n  }\n  const UCKeyboardLayout* layout = reinterpret_cast<const UCKeyboardLayout*>(\n      CFDataGetBytePtr((__bridge CFDataRef)_keyboardLayoutData));\n\n  UInt32 deadKeyState = 0;\n  UniCharCount stringLength = 0;\n  UniChar resultChar;\n\n  UInt32 modifierState = ((shift ? shiftKey : 0) >> 8) & 0xFF;\n  UInt32 keyboardType = LMGetKbdLast();\n\n  bool isDeadKey = false;\n  OSStatus status =\n      UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,\n                     kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);\n  // For dead keys, press the same key again to get the printable representation of the key.\n  if (status == noErr && stringLength == 0 && deadKeyState != 0) {\n    isDeadKey = true;\n    status =\n        UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,\n                       kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);\n  }\n\n  if (status == noErr && stringLength == 1 && !std::iscntrl(resultChar)) {\n    return LayoutClue{resultChar, isDeadKey};\n  }\n  return LayoutClue{0, false};\n}\n\n- (void)performMouseDragLeave {\n  if (_engine) {\n    [_engine performMouseDragLeave];\n  }\n}\n\n- (void)performMouseDragEnterAndOverAtPoint:(NSPoint)point {\n  if (_engine) {\n    [_engine performMouseDragEnterAndOverAtPoint:point];\n  }\n}\n\n- (void)performMouseDragDropAtPoint:(NSPoint)point\n                               type:(NSString* _Nonnull)type\n                        dropContent:(id _Nonnull)content {\n  if (_engine) {\n    [_engine performMouseDragDropAtPoint:point type:type dropContent:content];\n  }\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/ClayViewProvider_Internal.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/fml/memory/weak_ptr.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Headers/ClayViewProvider.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterView.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h\"\n\n@interface ClayViewProvider () <FlutterKeyboardViewDelegate>\n\n/**\n * This just returns the NSPasteboard so that it can be mocked in the tests.\n */\n@property(nonatomic, readonly, nonnull) NSPasteboard* pasteboard;\n\n#pragma mark - Private interface declaration.\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h\"\n\n@interface FlutterAppDelegate ()\n\n/**\n * Returns the display name of the application as set in the Info.plist.\n */\n- (NSString*)applicationName;\n\n@end\n\n@implementation FlutterAppDelegate\n\n// TODO(stuartmorgan): Implement application lifecycle forwarding to plugins here, as is done\n// on iOS. Currently macOS plugins don't have access to lifecycle messages.\n\n- (void)applicationWillFinishLaunching:(NSNotification*)notification {\n  // Update UI elements to match the application name.\n  NSString* applicationName = [self applicationName];\n  _mainFlutterWindow.title = applicationName;\n  for (NSMenuItem* menuItem in _applicationMenu.itemArray) {\n    menuItem.title = [menuItem.title stringByReplacingOccurrencesOfString:@\"APP_NAME\"\n                                                               withString:applicationName];\n  }\n}\n\n#pragma mark Private Methods\n\n- (NSString*)applicationName {\n  NSString* applicationName =\n      [NSBundle.mainBundle objectForInfoDictionaryKey:@\"CFBundleDisplayName\"];\n  if (!applicationName) {\n    applicationName = [NSBundle.mainBundle objectForInfoDictionaryKey:@\"CFBundleName\"];\n  }\n  return applicationName;\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#include \"clay/public/clay.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h\"\n\ntypedef void* _VoidPtr;\n\ntypedef void (^FlutterSendEmbedderKeyEvent)(const ClayKeyEvent& /* event */,\n                                            _Nullable ClayKeyEventCallback /* callback */,\n                                            _Nullable _VoidPtr /* user_data */);\n\n/**\n * A primary responder of |FlutterKeyboardManager| that handles events by\n * sending the converted events through the embedder API.\n *\n * This class communicates with the HardwareKeyboard API in the framework.\n */\n@interface FlutterEmbedderKeyResponder : NSObject <FlutterKeyPrimaryResponder>\n\n/**\n * Create an instance by specifying the function to send converted events to.\n *\n * The |sendEvent| is typically |FlutterEngine|'s |sendKeyEvent|.\n */\n- (nonnull instancetype)initWithSendEvent:(_Nonnull FlutterSendEmbedderKeyEvent)sendEvent;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <objc/message.h>\n#include <memory>\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/KeyCodeMap_Internal.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace {\n\n/**\n * Isolate the least significant 1-bit.\n *\n * For example,\n *\n *  * lowestSetBit(0x1010) returns 0x10.\n *  * lowestSetBit(0) returns 0.\n */\nstatic NSUInteger lowestSetBit(NSUInteger bitmask) {\n  // This utilizes property of two's complement (negation), which propagates a\n  // carry bit from LSB to the lowest set bit.\n  return bitmask & -bitmask;\n}\n\n/**\n * Whether a string represents a control character.\n */\nstatic bool IsControlCharacter(uint64_t character) {\n  return (character <= 0x1f && character >= 0x00) || (character >= 0x7f && character <= 0x9f);\n}\n\n/**\n * Whether a string represents an unprintable key.\n */\nstatic bool IsUnprintableKey(uint64_t character) {\n  return character >= 0xF700 && character <= 0xF8FF;\n}\n\n/**\n * Returns a key code composed with a base key and a plane.\n *\n * Examples of unprintable keys are \"NSUpArrowFunctionKey = 0xF700\" or\n * \"NSHomeFunctionKey = 0xF729\".\n *\n * See\n * https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc\n * for more information.\n */\nstatic uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane) {\n  return plane | (baseKey & clay::kValueMask);\n}\n\n/**\n * Returns the physical key for a key code.\n */\nstatic uint64_t GetPhysicalKeyForKeyCode(unsigned short keyCode) {\n  NSNumber* physicalKey = [clay::keyCodeToPhysicalKey objectForKey:@(keyCode)];\n  if (physicalKey == nil) {\n    return KeyOfPlane(keyCode, clay::kMacosPlane);\n  }\n  return physicalKey.unsignedLongLongValue;\n}\n\n/**\n * Returns the logical key for a modifier physical key.\n */\nstatic uint64_t GetLogicalKeyForModifier(unsigned short keyCode, uint64_t hidCode) {\n  NSNumber* fromKeyCode = [clay::keyCodeToLogicalKey objectForKey:@(keyCode)];\n  if (fromKeyCode != nil) {\n    return fromKeyCode.unsignedLongLongValue;\n  }\n  return KeyOfPlane(hidCode, clay::kMacosPlane);\n}\n\n/**\n * Converts upper letters to lower letters in ASCII, and returns as-is\n * otherwise.\n *\n * Independent of locale.\n */\nstatic uint64_t toLower(uint64_t n) {\n  constexpr uint64_t lowerA = 0x61;\n  constexpr uint64_t upperA = 0x41;\n  constexpr uint64_t upperZ = 0x5a;\n\n  constexpr uint64_t lowerAGrave = 0xe0;\n  constexpr uint64_t upperAGrave = 0xc0;\n  constexpr uint64_t upperThorn = 0xde;\n  constexpr uint64_t division = 0xf7;\n\n  // ASCII range.\n  if (n >= upperA && n <= upperZ) {\n    return n - upperA + lowerA;\n  }\n\n  // EASCII range.\n  if (n >= upperAGrave && n <= upperThorn && n != division) {\n    return n - upperAGrave + lowerAGrave;\n  }\n\n  return n;\n}\n\n// Decode a UTF-16 sequence to an array of char32 (UTF-32).\n//\n// See https://en.wikipedia.org/wiki/UTF-16#Description for the algorithm.\n//\n// The returned character array must be deallocated with delete[]. The length of\n// the result is stored in `out_length`.\n//\n// Although NSString has a dataUsingEncoding method, we implement our own\n// because dataUsingEncoding outputs redundant characters for unknown reasons.\nstatic uint32_t* DecodeUtf16(NSString* target, size_t* out_length) {\n  // The result always has a length less or equal to target.\n  size_t result_pos = 0;\n  uint32_t* result = new uint32_t[target.length];\n  uint16_t high_surrogate = 0;\n  for (NSUInteger target_pos = 0; target_pos < target.length; target_pos += 1) {\n    uint16_t codeUnit = [target characterAtIndex:target_pos];\n    // BMP\n    if (codeUnit <= 0xD7FF || codeUnit >= 0xE000) {\n      result[result_pos] = codeUnit;\n      result_pos += 1;\n      // High surrogates\n    } else if (codeUnit <= 0xDBFF) {\n      high_surrogate = codeUnit - 0xD800;\n      // Low surrogates\n    } else {\n      uint16_t low_surrogate = codeUnit - 0xDC00;\n      result[result_pos] = (high_surrogate << 10) + low_surrogate + 0x10000;\n      result_pos += 1;\n    }\n  }\n  *out_length = result_pos;\n  return result;\n}\n\n/**\n * Returns the logical key of a KeyUp or KeyDown event.\n *\n * For FlagsChanged event, use GetLogicalKeyForModifier.\n */\nstatic uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) {\n  // Look to see if the keyCode can be mapped from keycode.\n  NSNumber* fromKeyCode = [clay::keyCodeToLogicalKey objectForKey:@(event.keyCode)];\n  if (fromKeyCode != nil) {\n    return fromKeyCode.unsignedLongLongValue;\n  }\n\n  // Convert `charactersIgnoringModifiers` to UTF32.\n  NSString* keyLabelUtf16 = event.charactersIgnoringModifiers;\n\n  // Check if this key is a single character, which will be used to generate the\n  // logical key from its Unicode value.\n  //\n  // Multi-char keys will be minted onto the macOS plane because there are no\n  // meaningful values for them. Control keys and unprintable keys have been\n  // converted by `keyCodeToLogicalKey` earlier.\n  uint32_t character = 0;\n  if (keyLabelUtf16.length != 0) {\n    size_t keyLabelLength;\n    uint32_t* keyLabel = DecodeUtf16(keyLabelUtf16, &keyLabelLength);\n    if (keyLabelLength == 1) {\n      uint32_t keyLabelChar = *keyLabel;\n      NSCAssert(!IsControlCharacter(keyLabelChar) && !IsUnprintableKey(keyLabelChar),\n                @\"Unexpected control or unprintable keylabel 0x%x\", keyLabelChar);\n      NSCAssert(keyLabelChar <= 0x10FFFF, @\"Out of range keylabel 0x%x\", keyLabelChar);\n      character = keyLabelChar;\n    }\n    delete[] keyLabel;\n  }\n  if (character != 0) {\n    return KeyOfPlane(toLower(character), clay::kUnicodePlane);\n  }\n\n  // We can't represent this key with a single printable unicode, so a new code\n  // is minted to the macOS plane.\n  return KeyOfPlane(event.keyCode, clay::kMacosPlane);\n}\n\n/**\n * Converts NSEvent.timestamp to the timestamp for Flutter.\n */\nstatic double GetFlutterTimestampFrom(NSTimeInterval timestamp) {\n  // Timestamp in microseconds. The event.timestamp is in seconds with sub-ms precision.\n  return timestamp * 1000000.0;\n}\n\n/**\n * Compute |modifierFlagOfInterestMask| out of |keyCodeToModifierFlag|.\n *\n * This is equal to the bitwise-or of all values of |keyCodeToModifierFlag| as\n * well as NSEventModifierFlagCapsLock.\n */\nstatic NSUInteger computeModifierFlagOfInterestMask() {\n  __block NSUInteger modifierFlagOfInterestMask = NSEventModifierFlagCapsLock;\n  [clay::keyCodeToModifierFlag\n      enumerateKeysAndObjectsUsingBlock:^(NSNumber* keyCode, NSNumber* flag, BOOL* stop) {\n        modifierFlagOfInterestMask = modifierFlagOfInterestMask | [flag unsignedLongValue];\n      }];\n  return modifierFlagOfInterestMask;\n}\n\n/**\n * The C-function sent to the embedder's |SendKeyEvent|, wrapping\n * |FlutterEmbedderKeyResponder.handleResponse|.\n *\n * For the reason of this wrap, see |FlutterKeyPendingResponse|.\n */\nvoid HandleResponse(bool handled, void* user_data);\n\n/**\n * Converts NSEvent.characters to a C-string for ClayKeyEvent.\n */\nconst char* getEventString(NSString* characters) {\n  if ([characters length] == 0) {\n    return nullptr;\n  }\n  unichar utf16Code = [characters characterAtIndex:0];\n  if (utf16Code >= 0xf700 && utf16Code <= 0xf7ff) {\n    // Some function keys are assigned characters with codepoints from the\n    // private use area. These characters are filtered out since they're\n    // unprintable.\n    //\n    // The official documentation reserves 0xF700-0xF8FF as private use area\n    // (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc).\n    // But macOS seems to only use a reduced range of it. The official doc\n    // defines a few constants, all of which are within 0xF700-0xF747.\n    // (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc).\n    // This mostly aligns with the experimentation result, except for 0xF8FF,\n    // which is used for the \"Apple logo\" character (Option-Shift-K on a US\n    // keyboard.)\n    //\n    // Assume that non-printable function keys are defined from\n    // 0xF700 upwards, and printable private keys are defined from 0xF8FF\n    // downwards. This function filters out 0xF700-0xF7FF in order to keep\n    // the printable private keys.\n    return nullptr;\n  }\n  return [characters UTF8String];\n}\n}  // namespace\n\n/**\n * The invocation context for |HandleResponse|, wrapping\n * |FlutterEmbedderKeyResponder.handleResponse|.\n */\nstruct FlutterKeyPendingResponse {\n  FlutterEmbedderKeyResponder* responder;\n  uint64_t responseId;\n};\n\n/**\n * Guards a |FlutterAsyncKeyCallback| to make sure it's handled exactly once\n * throughout |FlutterEmbedderKeyResponder.handleEvent|.\n *\n * A callback can either be handled with |pendTo:withId:|, or with |resolveTo:|.\n * Either way, the callback cannot be handled again, or an assertion will be\n * thrown.\n */\n@interface FlutterKeyCallbackGuard : NSObject\n- (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback;\n\n/**\n * Handle the callback by storing it to pending responses.\n */\n- (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses\n        withId:(uint64_t)responseId;\n\n/**\n * Handle the callback by calling it with a result.\n */\n- (void)resolveTo:(BOOL)handled;\n\n@property(nonatomic) BOOL handled;\n@property(nonatomic) BOOL sentAnyEvents;\n/**\n * A string indicating how the callback is handled.\n *\n * Only set in debug mode. Nil in release mode, or if the callback has not been\n * handled.\n */\n@property(nonatomic, copy) NSString* debugHandleSource;\n@end\n\n@implementation FlutterKeyCallbackGuard {\n  // The callback is declared in the implemnetation block to avoid being\n  // accessed directly.\n  FlutterAsyncKeyCallback _callback;\n}\n- (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback {\n  self = [super init];\n  if (self != nil) {\n    _callback = callback;\n    _handled = FALSE;\n    _sentAnyEvents = FALSE;\n  }\n  return self;\n}\n\n- (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses\n        withId:(uint64_t)responseId {\n  NSAssert(!_handled, @\"This callback has been handled by %@.\", _debugHandleSource);\n  if (_handled) {\n    return;\n  }\n  pendingResponses[@(responseId)] = _callback;\n  _handled = TRUE;\n  NSAssert(\n      ((_debugHandleSource = [NSString stringWithFormat:@\"pending event %llu\", responseId]), TRUE),\n      @\"\");\n}\n\n- (void)resolveTo:(BOOL)handled {\n  NSAssert(!_handled, @\"This callback has been handled by %@.\", _debugHandleSource);\n  if (_handled) {\n    return;\n  }\n  _callback(handled);\n  _handled = TRUE;\n  NSAssert(((_debugHandleSource = [NSString stringWithFormat:@\"resolved with %d\", _handled]), TRUE),\n           @\"\");\n}\n@end\n\n@interface FlutterEmbedderKeyResponder ()\n\n/**\n * The function to send converted events to.\n *\n * Set by the initializer.\n */\n@property(nonatomic, copy) FlutterSendEmbedderKeyEvent sendEvent;\n\n/**\n * A map of presessd keys.\n *\n * The keys of the dictionary are physical keys, while the values are the logical keys\n * of the key down event.\n */\n@property(nonatomic) NSMutableDictionary<NSNumber*, NSNumber*>* pressingRecords;\n\n/**\n * A constant mask for NSEvent.modifierFlags that Flutter synchronizes with.\n *\n * Flutter keeps track of the last |modifierFlags| and compares it with the\n * incoming one. Any bit within |modifierFlagOfInterestMask| that is different\n * (except for the one that corresponds to the event key) indicates that an\n * event for this modifier was missed, and Flutter synthesizes an event to make\n * up for the state difference.\n *\n * It is computed by computeModifierFlagOfInterestMask.\n */\n@property(nonatomic) NSUInteger modifierFlagOfInterestMask;\n\n/**\n * The modifier flags of the last received key event, excluding uninterested\n * bits.\n *\n * This should be kept synchronized with the last |NSEvent.modifierFlags|\n * after masking with |modifierFlagOfInterestMask|. This should also be kept\n * synchronized with the corresponding keys of |pressingRecords|.\n *\n * This is used by |synchronizeModifiers| to quickly find\n * out modifier keys that are desynchronized.\n */\n@property(nonatomic) NSUInteger lastModifierFlagsOfInterest;\n\n/**\n * A self-incrementing ID used to label key events sent to the framework.\n */\n@property(nonatomic) uint64_t responseId;\n\n/**\n * A map of unresponded key events sent to the framework.\n *\n * Its values are |responseId|s, and keys are the callback that was received\n * along with the event.\n */\n@property(nonatomic) NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>* pendingResponses;\n\n/**\n * Compare the last modifier flags and the current, and dispatch synthesized\n * key events for each different modifier flag bit.\n *\n * The flags compared are all flags after masking with\n * |modifierFlagOfInterestMask| and excluding |ignoringFlags|.\n *\n * The |guard| is basically a regular guarded callback, but instead of being\n * called, it is only used to record whether an event is sent.\n */\n- (void)synchronizeModifiers:(NSUInteger)currentFlags\n               ignoringFlags:(NSUInteger)ignoringFlags\n                   timestamp:(NSTimeInterval)timestamp\n                       guard:(nonnull FlutterKeyCallbackGuard*)guard;\n\n/**\n * Update the pressing state.\n *\n * If `logicalKey` is not 0, `physicalKey` is pressed as `logicalKey`.\n * Otherwise, `physicalKey` is released.\n */\n- (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey;\n\n/**\n * Send an event to the framework, expecting its response.\n */\n- (void)sendPrimaryFlutterEvent:(const ClayKeyEvent&)event\n                       callback:(nonnull FlutterKeyCallbackGuard*)callback;\n\n/**\n * Send a synthesized key event, never expecting its event result.\n *\n * The |guard| is basically a regular guarded callback, but instead of being\n * called, it is only used to record whether an event is sent.\n */\n- (void)sendSynthesizedFlutterEvent:(const ClayKeyEvent&)event\n                              guard:(FlutterKeyCallbackGuard*)guard;\n\n/**\n * Send a CapsLock down event, then a CapsLock up event.\n *\n * If synthesizeDown is TRUE, then both events will be synthesized. Otherwise,\n * the callback will be used as the callback for the down event, which is not\n * synthesized, while the up event will always be synthesized.\n */\n- (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp\n                      synthesizeDown:(bool)synthesizeDown\n                            callback:(nonnull FlutterKeyCallbackGuard*)callback;\n\n/**\n * Send a key event for a modifier key.\n */\n- (void)sendModifierEventOfType:(BOOL)isDownEvent\n                      timestamp:(NSTimeInterval)timestamp\n                        keyCode:(unsigned short)keyCode\n                    synthesized:(bool)synthesized\n                       callback:(nonnull FlutterKeyCallbackGuard*)callback;\n\n/**\n * Processes a down event from the system.\n */\n- (void)handleDownEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;\n\n/**\n * Processes an up event from the system.\n */\n- (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;\n\n/**\n * Processes an event from the system for the CapsLock key.\n */\n- (void)handleCapsLockEvent:(nonnull NSEvent*)event\n                   callback:(nonnull FlutterKeyCallbackGuard*)callback;\n\n/**\n * Processes a flags changed event from the system, where modifier keys are pressed or released.\n */\n- (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;\n\n/**\n * Processes the response from the framework.\n */\n- (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId;\n\n@end\n\n@implementation FlutterEmbedderKeyResponder\n\n@synthesize layoutMap;\n\n- (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent {\n  self = [super init];\n  if (self != nil) {\n    _sendEvent = sendEvent;\n    _pressingRecords = [NSMutableDictionary dictionary];\n    _pendingResponses = [NSMutableDictionary dictionary];\n    _responseId = 1;\n    _lastModifierFlagsOfInterest = 0;\n    _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask();\n  }\n  return self;\n}\n\n- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {\n  // The conversion algorithm relies on a non-nil callback to properly compute\n  // `synthesized`.\n  NSAssert(callback != nil, @\"The callback must not be nil.\");\n  FlutterKeyCallbackGuard* guardedCallback =\n      [[FlutterKeyCallbackGuard alloc] initWithCallback:callback];\n  switch (event.type) {\n    case NSEventTypeKeyDown:\n      [self handleDownEvent:event callback:guardedCallback];\n      break;\n    case NSEventTypeKeyUp:\n      [self handleUpEvent:event callback:guardedCallback];\n      break;\n    case NSEventTypeFlagsChanged:\n      [self handleFlagEvent:event callback:guardedCallback];\n      break;\n    default:\n      NSAssert(false, @\"Unexpected key event type: |%@|.\", @(event.type));\n  }\n  NSAssert(guardedCallback.handled, @\"The callback is returned without being handled.\");\n  if (!guardedCallback.sentAnyEvents) {\n    ClayKeyEvent clayEvent = {\n        .struct_size = sizeof(ClayKeyEvent),\n        .timestamp = 0,\n        .type = kClayKeyEventTypeDown,\n        .physical = 0,\n        .logical = 0,\n        .character = nil,\n        .synthesized = false,\n    };\n    _sendEvent(clayEvent, nullptr, nullptr);\n  }\n  NSAssert(_lastModifierFlagsOfInterest == (event.modifierFlags & _modifierFlagOfInterestMask),\n           @\"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx\",\n           _lastModifierFlagsOfInterest, event.modifierFlags & _modifierFlagOfInterestMask);\n}\n\n#pragma mark - Private\n\n- (void)synchronizeModifiers:(NSUInteger)currentFlags\n               ignoringFlags:(NSUInteger)ignoringFlags\n                   timestamp:(NSTimeInterval)timestamp\n                       guard:(FlutterKeyCallbackGuard*)guard {\n  const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags;\n  const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask;\n  const NSUInteger lastFlagsOfInterest = _lastModifierFlagsOfInterest & updatingMask;\n  NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest;\n  if (flagDifference & NSEventModifierFlagCapsLock) {\n    [self sendCapsLockTapWithTimestamp:timestamp synthesizeDown:true callback:guard];\n    flagDifference = flagDifference & ~NSEventModifierFlagCapsLock;\n  }\n  while (true) {\n    const NSUInteger currentFlag = lowestSetBit(flagDifference);\n    if (currentFlag == 0) {\n      break;\n    }\n    flagDifference = flagDifference & ~currentFlag;\n    NSNumber* keyCode = [clay::modifierFlagToKeyCode objectForKey:@(currentFlag)];\n    NSAssert(keyCode != nil, @\"Invalid modifier flag 0x%lx\", currentFlag);\n    if (keyCode == nil) {\n      continue;\n    }\n    BOOL isDownEvent = (currentFlagsOfInterest & currentFlag) != 0;\n    [self sendModifierEventOfType:isDownEvent\n                        timestamp:timestamp\n                          keyCode:[keyCode unsignedShortValue]\n                      synthesized:true\n                         callback:guard];\n  }\n  _lastModifierFlagsOfInterest =\n      (_lastModifierFlagsOfInterest & ~updatingMask) | currentFlagsOfInterest;\n}\n\n- (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey {\n  if (logicalKey == 0) {\n    [_pressingRecords removeObjectForKey:@(physicalKey)];\n  } else {\n    _pressingRecords[@(physicalKey)] = @(logicalKey);\n  }\n}\n\n- (void)sendPrimaryFlutterEvent:(const ClayKeyEvent&)event\n                       callback:(FlutterKeyCallbackGuard*)callback {\n  _responseId += 1;\n  uint64_t responseId = _responseId;\n  // The `pending` is released in `HandleResponse`.\n  FlutterKeyPendingResponse* pending = new FlutterKeyPendingResponse{self, responseId};\n  [callback pendTo:_pendingResponses withId:responseId];\n  _sendEvent(event, HandleResponse, pending);\n  callback.sentAnyEvents = TRUE;\n}\n\n- (void)sendSynthesizedFlutterEvent:(const ClayKeyEvent&)event\n                              guard:(FlutterKeyCallbackGuard*)guard {\n  _sendEvent(event, nullptr, nullptr);\n  guard.sentAnyEvents = TRUE;\n}\n\n- (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp\n                      synthesizeDown:(bool)synthesizeDown\n                            callback:(FlutterKeyCallbackGuard*)callback {\n  // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on\n  // even taps and odd taps. A CapsLock down or CapsLock up should always be\n  // converted to a down *and* an up, and the up should always be a synthesized\n  // event, since the FlutterEmbedderKeyResponder will never know when the\n  // button is released.\n  ClayKeyEvent clayEvent = {\n      .struct_size = sizeof(ClayKeyEvent),\n      .timestamp = GetFlutterTimestampFrom(timestamp),\n      .type = kClayKeyEventTypeDown,\n      .physical = clay::kCapsLockPhysicalKey,\n      .logical = clay::kCapsLockLogicalKey,\n      .character = nil,\n      .synthesized = synthesizeDown,\n  };\n  if (!synthesizeDown) {\n    [self sendPrimaryFlutterEvent:clayEvent callback:callback];\n  } else {\n    [self sendSynthesizedFlutterEvent:clayEvent guard:callback];\n  }\n\n  clayEvent.type = kClayKeyEventTypeUp;\n  clayEvent.synthesized = true;\n  [self sendSynthesizedFlutterEvent:clayEvent guard:callback];\n}\n\n- (void)sendModifierEventOfType:(BOOL)isDownEvent\n                      timestamp:(NSTimeInterval)timestamp\n                        keyCode:(unsigned short)keyCode\n                    synthesized:(bool)synthesized\n                       callback:(FlutterKeyCallbackGuard*)callback {\n  uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode);\n  uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey);\n  if (physicalKey == 0 || logicalKey == 0) {\n    FML_LOG(ERROR) << \"Unrecognized modifier key: keyCode \" << keyCode << \", physical key \"\n                   << physicalKey;\n    [callback resolveTo:TRUE];\n    return;\n  }\n  ClayKeyEvent clayEvent = {\n      .struct_size = sizeof(ClayKeyEvent),\n      .timestamp = GetFlutterTimestampFrom(timestamp),\n      .type = isDownEvent ? kClayKeyEventTypeDown : kClayKeyEventTypeUp,\n      .physical = physicalKey,\n      .logical = logicalKey,\n      .character = nil,\n      .synthesized = synthesized,\n  };\n  [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0];\n  if (!synthesized) {\n    [self sendPrimaryFlutterEvent:clayEvent callback:callback];\n  } else {\n    [self sendSynthesizedFlutterEvent:clayEvent guard:callback];\n  }\n}\n\n- (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {\n  uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);\n  NSNumber* logicalKeyFromMap = self.layoutMap[@(event.keyCode)];\n  uint64_t logicalKey = logicalKeyFromMap != nil ? [logicalKeyFromMap unsignedLongLongValue]\n                                                 : GetLogicalKeyForEvent(event, physicalKey);\n  [self synchronizeModifiers:event.modifierFlags\n               ignoringFlags:0\n                   timestamp:event.timestamp\n                       guard:callback];\n\n  bool isARepeat = event.isARepeat;\n  NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];\n  if (pressedLogicalKey != nil && !isARepeat) {\n    // This might happen in add-to-app scenarios if the focus is changed\n    // from the native view to the Flutter view amid the key tap.\n    //\n    // This might also happen when a key event is forged (such as by an\n    // IME) using the same keyCode as an unreleased key. See\n    // https://github.com/flutter/flutter/issues/82673#issuecomment-988661079\n    ClayKeyEvent clayEvent = {\n        .struct_size = sizeof(ClayKeyEvent),\n        .timestamp = GetFlutterTimestampFrom(event.timestamp),\n        .type = kClayKeyEventTypeUp,\n        .physical = physicalKey,\n        .logical = [pressedLogicalKey unsignedLongLongValue],\n        .character = nil,\n        .synthesized = true,\n    };\n    [self sendSynthesizedFlutterEvent:clayEvent guard:callback];\n    pressedLogicalKey = nil;\n  }\n\n  if (pressedLogicalKey == nil) {\n    [self updateKey:physicalKey asPressed:logicalKey];\n  }\n\n  ClayKeyEvent clayEvent = {\n      .struct_size = sizeof(ClayKeyEvent),\n      .timestamp = GetFlutterTimestampFrom(event.timestamp),\n      .type = pressedLogicalKey == nil ? kClayKeyEventTypeDown : kClayKeyEventTypeRepeat,\n      .physical = physicalKey,\n      .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue],\n      .character = getEventString(event.characters),\n      .synthesized = false,\n  };\n  [self sendPrimaryFlutterEvent:clayEvent callback:callback];\n}\n\n- (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {\n  NSAssert(!event.isARepeat, @\"Unexpected repeated Up event: keyCode %d, char %@, charIM %@\",\n           event.keyCode, event.characters, event.charactersIgnoringModifiers);\n  [self synchronizeModifiers:event.modifierFlags\n               ignoringFlags:0\n                   timestamp:event.timestamp\n                       guard:callback];\n\n  uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);\n  NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];\n  if (pressedLogicalKey == nil) {\n    // Normally the key up events won't be missed since macOS always sends the\n    // key up event to the window where the corresponding key down occurred.\n    // However this might happen in add-to-app scenarios if the focus is changed\n    // from the native view to the Flutter view amid the key tap.\n    [callback resolveTo:TRUE];\n    return;\n  }\n  [self updateKey:physicalKey asPressed:0];\n\n  ClayKeyEvent clayEvent = {\n      .struct_size = sizeof(ClayKeyEvent),\n      .timestamp = GetFlutterTimestampFrom(event.timestamp),\n      .type = kClayKeyEventTypeUp,\n      .physical = physicalKey,\n      .logical = [pressedLogicalKey unsignedLongLongValue],\n      .character = nil,\n      .synthesized = false,\n  };\n  [self sendPrimaryFlutterEvent:clayEvent callback:callback];\n}\n\n- (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {\n  [self synchronizeModifiers:event.modifierFlags\n               ignoringFlags:NSEventModifierFlagCapsLock\n                   timestamp:event.timestamp\n                       guard:callback];\n  if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) !=\n      (event.modifierFlags & NSEventModifierFlagCapsLock)) {\n    [self sendCapsLockTapWithTimestamp:event.timestamp synthesizeDown:false callback:callback];\n    _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ NSEventModifierFlagCapsLock;\n  } else {\n    [callback resolveTo:TRUE];\n  }\n}\n\n- (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {\n  NSNumber* targetModifierFlagObj = clay::keyCodeToModifierFlag[@(event.keyCode)];\n  NSUInteger targetModifierFlag =\n      targetModifierFlagObj == nil ? 0 : [targetModifierFlagObj unsignedLongValue];\n  uint64_t targetKey = GetPhysicalKeyForKeyCode(event.keyCode);\n  if (targetKey == clay::kCapsLockPhysicalKey) {\n    return [self handleCapsLockEvent:event callback:callback];\n  }\n\n  [self synchronizeModifiers:event.modifierFlags\n               ignoringFlags:targetModifierFlag\n                   timestamp:event.timestamp\n                       guard:callback];\n\n  NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)];\n  BOOL lastTargetPressed = pressedLogicalKey != nil;\n  NSAssert(targetModifierFlagObj == nil ||\n               (_lastModifierFlagsOfInterest & targetModifierFlag) != 0 == lastTargetPressed,\n           @\"Desynchronized state between lastModifierFlagsOfInterest (0x%lx) on bit 0x%lx \"\n           @\"for keyCode 0x%hx, whose pressing state is %@.\",\n           _lastModifierFlagsOfInterest, targetModifierFlag, event.keyCode,\n           lastTargetPressed\n               ? [NSString stringWithFormat:@\"0x%llx\", [pressedLogicalKey unsignedLongLongValue]]\n               : @\"empty\");\n\n  BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0;\n  if (lastTargetPressed == shouldBePressed) {\n    [callback resolveTo:TRUE];\n    return;\n  }\n  _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ targetModifierFlag;\n  [self sendModifierEventOfType:shouldBePressed\n                      timestamp:event.timestamp\n                        keyCode:event.keyCode\n                    synthesized:false\n                       callback:callback];\n}\n\n- (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId {\n  FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)];\n  callback(handled);\n  [_pendingResponses removeObjectForKey:@(responseId)];\n}\n\n@end\n\nnamespace {\nvoid HandleResponse(bool handled, void* user_data) {\n  // Use unique_ptr to release on leaving.\n  auto pending = std::unique_ptr<FlutterKeyPendingResponse>(\n      reinterpret_cast<FlutterKeyPendingResponse*>(user_data));\n  [pending->responder handleResponse:handled forId:pending->responseId];\n}\n}  // namespace\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h\"\n\n#include <algorithm>\n#include <iostream>\n#include <vector>\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/ClayViewProvider_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h\"\n\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/common/services/drag_drop_service.h\"\n#include \"clay/shell/common/switches.h\"\n#include \"clay/shell/platform/common/engine_switches.h\"\n#include \"clay/shell/platform/darwin/common/clay_service_manager_service_darwin.h\"\n#include \"clay/shell/platform/embedder/embedder_engine.h\"\n#include \"clay/shell/platform/embedder/platform_view_embedder.h\"\n#include \"clay/shell/platform/embedder/platform_view_embedder_delegate.h\"\n\n/// The private notification for voice over.\nstatic NSString* const kEnhancedUserInterfaceNotification =\n    @\"NSApplicationDidChangeAccessibilityEnhancedUserInterfaceNotification\";\nstatic NSString* const kEnhancedUserInterfaceKey = @\"AXEnhancedUserInterface\";\n\n/// Clipboard plain text format.\nconstexpr char kTextPlainFormat[] = \"text/plain\";\n\nNSString* const kDragTextType = @\"drag_text\";\nNSString* const kDragFileType = @\"drag_file\";\nNSString* const kDragDropPathKey = @\"path\";\nNSString* const kDragDropNameKey = @\"name\";\nNSString* const kDragDropTypeKey = @\"type\";\nNSString* const kDragDropSizeKey = @\"size\";\nNSString* const kDragDropLastModifiedKey = @\"lastModified\";\n\n#pragma mark -\n\n/**\n * Private interface declaration for FlutterEngine.\n */\n@interface FlutterEngine ()\n\n/**\n * A mutable array that holds one bool value that determines if responses to platform messages are\n * clear to execute. This value should be read or written only inside of a synchronized block and\n * will return `NO` after the FlutterEngine has been dealloc'd.\n */\n@property(nonatomic, strong) NSMutableArray<NSNumber*>* isResponseValid;\n\n/**\n * Sends the list of user-preferred locales to the Flutter engine.\n */\n- (void)sendUserLocales;\n\n/**\n * Invoked right before the engine is restarted.\n *\n * This should reset states to as if the application has just started.  It\n * usually indicates a hot restart (Shift-R in Flutter CLI.)\n */\n- (void)engineCallbackOnPreEngineRestart;\n\n/**\n * Requests that the task be posted back the to the Flutter engine at the target time. The target\n * time is in the clock used by the Flutter engine.\n */\n- (void)postMainThreadTask:(ClayTask)task targetTimeInNanoseconds:(uint64_t)targetTime;\n\n@end\n\n#pragma mark -\n\n/**\n * `FlutterPluginRegistrar` implementation handling a single plugin.\n */\n@interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>\n- (instancetype)initWithPlugin:(nonnull NSString*)pluginKey\n                 flutterEngine:(nonnull FlutterEngine*)flutterEngine;\n@end\n\n@implementation FlutterEngineRegistrar {\n  NSString* _pluginKey;\n  FlutterEngine* _flutterEngine;\n}\n\n- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine {\n  self = [super init];\n  if (self) {\n    _pluginKey = [pluginKey copy];\n    _flutterEngine = flutterEngine;\n  }\n  return self;\n}\n\n- (NSView*)view {\n  if (!_flutterEngine.viewController.viewLoaded) {\n    [_flutterEngine.viewController loadView];\n  }\n  return _flutterEngine.viewController.flutterView;\n}\n\n- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)factory\n                     withId:(nonnull NSString*)factoryId {\n  [[_flutterEngine platformViewController] registerViewFactory:factory withId:factoryId];\n}\n\n@end\n\n#pragma mark - Static methods provided to engine configuration\n\nclass ClayPlatformViewMacDelegate : public clay::PlatformViewEmbedderDelegate {\n public:\n  ClayPlatformViewMacDelegate() = default;\n  virtual ~ClayPlatformViewMacDelegate() = default;\n  void SetInterceptUrlCallback(FlutterViewShouldInterceptUrlCallback callback) {\n    auto intercept_callback = [callback](const char* origin_url, bool should_decode,\n                                         char* intercept_url, int max_path_length) {\n      if (callback) {\n        NSString* origin_url_str = [NSString stringWithUTF8String:origin_url];\n        NSString* intercept_url_str = nil;\n        intercept_url_str = callback(origin_url_str, should_decode, max_path_length);\n        if (intercept_url_str) {\n          std::string temp_url = [intercept_url_str UTF8String];\n          strcpy(intercept_url, temp_url.c_str());\n        }\n      }\n    };\n    this->BindShouldInterceptUrlCallback(intercept_callback);\n  }\n};\n\n@implementation FlutterEngine {\n  // The embedder engine object.\n  std::unique_ptr<clay::EmbedderEngine> _engine;\n\n  std::shared_ptr<clay::ServiceManager> _service_manager;\n\n  // Whether the engine can continue running after the view controller is removed.\n  BOOL _allowHeadlessExecution;\n\n  FlutterViewEngineProvider* _viewProvider;\n\n  // Used to support creation and deletion of platform views and registering platform view\n  // factories. Lifecycle is tied to the engine.\n  FlutterPlatformViewController* _platformViewController;\n\n  ClayPlatformViewMacDelegate _platformViewDelegate;\n\n  ClayPointerEvent _lastScrollEvent;\n}\n\n- (instancetype)initWithName:(NSString*)labelPrefix {\n  return [self initWithName:labelPrefix allowHeadlessExecution:YES];\n}\n\n- (instancetype)initWithName:(NSString*)labelPrefix\n      allowHeadlessExecution:(BOOL)allowHeadlessExecution {\n  self = [super init];\n  NSAssert(self, @\"Super init cannot be nil\");\n\n  _lastScrollEvent.signal_kind = kClayPointerSignalKindNone;\n  _allowHeadlessExecution = allowHeadlessExecution;\n  _viewProvider = [[FlutterViewEngineProvider alloc] initWithEngine:self];\n  _isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];\n  [_isResponseValid addObject:@YES];\n\n  _renderer = [[FlutterRenderer alloc] initWithFlutterEngine:self];\n\n  NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];\n  [notificationCenter addObserver:self\n                         selector:@selector(sendUserLocales)\n                             name:NSCurrentLocaleDidChangeNotification\n                           object:nil];\n\n  _platformViewController = [[FlutterPlatformViewController alloc] init];\n  [self setUpNotificationCenterListeners];\n\n  return self;\n}\n\n- (void)dealloc {\n  @synchronized(_isResponseValid) {\n    [_isResponseValid removeAllObjects];\n    [_isResponseValid addObject:@NO];\n  }\n  [self shutDownEngine];\n}\n\n- (void)setViewProvider:(ClayViewProvider*)viewProvider {\n  _clayViewProvider = viewProvider;\n}\n\n- (BOOL)runWithEntrypoint:(NSString*)entrypoint {\n  if (self.running) {\n    return NO;\n  }\n\n  if (!_allowHeadlessExecution && !_view) {\n    FML_LOG(ERROR)\n        << \"Attempted to run an engine with no view controller without headless mode enabled.\";\n    return NO;\n  }\n\n  // The first argument of argv is required to be the executable name.\n  std::vector<const char*> argv = {[self.executableName UTF8String]};\n\n  fml::CommandLine command_line;\n  if (!argv.empty()) {\n    command_line = fml::CommandLineFromArgcArgv(argv.size(), argv.data());\n  }\n  clay::Settings settings = clay::SettingsFromCommandLine(command_line);\n  const char* icu_data_path =\n      [[NSBundle mainBundle] pathForResource:@\"icudtl.dat\" ofType:nil].UTF8String;\n  if (icu_data_path) {\n    settings.icu_data_path = icu_data_path;\n  }\n  clay::PlatformViewEmbedder::PlatformDispatchTable platform_dispatch_table = {};\n  platform_dispatch_table.clipboard.set_clipboard_data_callback =\n      [self](const std::u16string& data) {\n        std::string u8string = lynx::base::U16StringToU8(data);\n        NSString* dataString = [NSString stringWithUTF8String:u8string.c_str()];\n        NSDictionary* dataDict = @{@\"text\" : dataString};\n        [self setClipboardData:dataDict];\n      };\n  platform_dispatch_table.clipboard.get_clipboard_data_callback = [self]() {\n    NSDictionary* clipboardData = [self getClipboardData:@(kTextPlainFormat)];\n    NSString* text = clipboardData[@\"text\"];\n    if (text) {\n      return lynx::base::U8StringToU16([text UTF8String]);\n    }\n    return std::u16string();\n  };\n\n  platform_dispatch_table.textinput.set_text_input_client_callback =\n      [self](int client_id, const char* input_action, const char* input_type) {\n        if (self.clayViewProvider && self.clayViewProvider.textInputPlugin) {\n          [self.clayViewProvider.textInputPlugin setTextInputClient:client_id\n                                                        inputAction:@(input_action)\n                                                          inputType:@(input_type)];\n        }\n      };\n  platform_dispatch_table.textinput.clear_text_input_client_callback = [self]() {\n    if (self.clayViewProvider && self.clayViewProvider.textInputPlugin) {\n      [self.clayViewProvider.textInputPlugin clearTextInputClient];\n    }\n  };\n  platform_dispatch_table.textinput.set_editable_transform_callback =\n      [self](const float transform[16]) {\n        if (self.clayViewProvider && self.clayViewProvider.textInputPlugin) {\n          NSMutableArray* transformArray = [NSMutableArray arrayWithCapacity:16];\n          for (int i = 0; i < 16; i++) {\n            [transformArray addObject:@(transform[i])];\n          }\n          [self.clayViewProvider.textInputPlugin setEditableTransform:transformArray];\n        }\n      };\n  platform_dispatch_table.textinput.set_editing_state_callback =\n      [self](uint64_t selection_base, uint64_t composing_extent,\n             const std::string& selection_affinity, const std::string& text,\n             bool selection_directional, uint64_t selection_extent, uint64_t composing_base) {\n        if (self.clayViewProvider && self.clayViewProvider.textInputPlugin) {\n          NSMutableDictionary* state = [NSMutableDictionary dictionary];\n          state[@\"text\"] = @(text.c_str());\n          state[@\"selectionBase\"] = @(selection_base);\n          state[@\"selectionExtent\"] = @(selection_extent);\n          state[@\"selectionAffinity\"] = @(selection_affinity.c_str());\n          state[@\"selectionIsDirectional\"] = @(selection_directional);\n          state[@\"composingBase\"] = @(composing_base);\n          state[@\"composingExtent\"] = @(composing_extent);\n          [self.clayViewProvider.textInputPlugin setEditingState:state];\n        }\n      };\n  platform_dispatch_table.textinput.set_caret_rect_callback = [self](float x, float y, float width,\n                                                                     float height) {\n    if (self.clayViewProvider && self.clayViewProvider.textInputPlugin) {\n      NSMutableDictionary* rect = [NSMutableDictionary dictionary];\n      rect[@\"x\"] = @(x);\n      rect[@\"y\"] = @(y);\n      rect[@\"width\"] = @(width);\n      rect[@\"height\"] = @(height);\n      [self.clayViewProvider.textInputPlugin updateCaretRect:rect];\n    }\n  };\n  platform_dispatch_table.textinput.set_marked_text_rect_callback = [](float x, float y,\n                                                                       float width, float height) {\n    // INFO: this method is not implemented in textInputPlugin yet.\n  };\n  platform_dispatch_table.textinput.show_text_input_callback = [self]() {\n    if (self.clayViewProvider && self.clayViewProvider.textInputPlugin) {\n      [self.clayViewProvider.textInputPlugin showTextInput];\n    }\n  };\n  platform_dispatch_table.textinput.hide_text_input_callback = [self]() {\n    if (self.clayViewProvider && self.clayViewProvider.textInputPlugin) {\n      [self.clayViewProvider.textInputPlugin hideTextInput];\n    }\n  };\n  platform_dispatch_table.textinput.input_filter_callback =\n      [self](const std::string& input, const std::string pattern) -> std::string {\n    if (self.clayViewProvider && self.clayViewProvider.textInputPlugin) {\n      NSString* inputString = [NSString stringWithUTF8String:input.c_str()];\n      NSString* patternString = [NSString stringWithUTF8String:pattern.c_str()];\n      NSString* filteredString = [self.clayViewProvider.textInputPlugin filterInput:inputString\n                                                                        withPattern:patternString];\n      if (filteredString != nullptr) {\n        return std::string([filteredString UTF8String]);\n      }\n    }\n    return input;\n  };\n\n  platform_dispatch_table.window_move_callback = []() {\n    NSWindow* window = [[NSApplication sharedApplication] mainWindow];\n    dispatch_async(dispatch_get_main_queue(), ^{\n      [window performWindowDragWithEvent:[window currentEvent]];\n    });\n  };\n  platform_dispatch_table.activate_system_cursor_callback = [self](int type,\n                                                                   const std::string& path) {\n    if (self.clayViewProvider && self.clayViewProvider.mouseCursorPlugin) {\n      [self.clayViewProvider.mouseCursorPlugin activateSystemCursor:type path:path.c_str()];\n    }\n  };\n  platform_dispatch_table.on_pre_engine_restart_callback = [self]() {\n    [self engineCallbackOnPreEngineRestart];\n  };\n\n  static size_t sTaskRunnerIdentifiers = 0;\n  const ClayTaskRunnerDescription cocoa_task_runner_description = {\n      .struct_size = sizeof(ClayTaskRunnerDescription),\n      .user_data = (void*)CFBridgingRetain(self),\n      .runs_task_on_current_thread_callback = [](void* user_data) -> bool {\n        return [[NSThread currentThread] isMainThread];\n      },\n      .post_task_callback = [](ClayTask task, uint64_t target_time_nanos, void* user_data) -> void {\n        [((__bridge FlutterEngine*)(user_data)) postMainThreadTask:task\n                                           targetTimeInNanoseconds:target_time_nanos];\n      },\n      .identifier = ++sTaskRunnerIdentifiers,\n  };\n  const clay::ClayCustomTaskRunners custom_task_runners = {\n      .struct_size = sizeof(clay::ClayCustomTaskRunners),\n      .platform_task_runner = &cocoa_task_runner_description,\n  };\n\n  fml::RefPtr<clay::EmbedderSurfaceMetal> embedderSurfaceMetal =\n      [_renderer createEmbedderSurfaceMetal];\n  _engine = clay::EmbedderEngine::CreateEngine(settings, &custom_task_runners, embedderSurfaceMetal,\n                                               platform_dispatch_table, &_platformViewDelegate,\n                                               (__bridge void*)(self));\n  if (!_engine) {\n    FML_LOG(ERROR) << \"Failed to initialize Flutter engine\";\n    return NO;\n  }\n  // The engine must not already be running. Initialize may only be called\n  // once on an engine instance.\n  if (_engine->IsValid()) {\n    FML_LOG(ERROR) << \"Engine handle was invalid.\";\n    return NO;\n  }\n\n  // Step 1: Launch the shell.\n  if (!_engine->LaunchShell()) {\n    FML_LOG(ERROR) << \"Could not launch the engine using supplied initialization arguments.\";\n    return NO;\n  }\n\n  // Step 2: Tell the platform view to initialize itself.\n  if (!_engine->NotifyCreated()) {\n    FML_LOG(ERROR) << \"Could not create platform view components.\";\n    return NO;\n  }\n  _service_manager = _engine->GetServiceManager();\n  if (!_service_manager) {\n    FML_LOG(ERROR) << \"Failed to get clay service manager\";\n    return NO;\n  }\n\n  _service_manager->RegisterService<clay::ClayServiceManagerServiceDarwin>(\n      std::make_shared<clay::ClayServiceManagerServiceDarwin>(\n          [_clayViewProvider GetClayServiceManager]));\n\n  [self sendUserLocales];\n  [self updateWindowMetrics];\n  [self updateDisplayConfig];\n  // Send the initial user settings such as brightness and text scale factor\n  // to the engine.\n  [self sendInitialSettings];\n  return YES;\n}\n\n- (void)setViewController:(FlutterViewController*)controller {\n  if (_viewController != controller) {\n    _viewController = controller;\n    _view = controller.flutterView;\n\n    if (!controller && !_allowHeadlessExecution) {\n      [self shutDownEngine];\n    }\n  }\n}\n\n- (void)setView:(FlutterView*)view {\n  if (_view) {\n    [_view shutdown];\n  }\n  _viewController = nil;\n  _view = view;\n\n  if (!view && !_allowHeadlessExecution) {\n    [self shutDownEngine];\n  }\n}\n\n#pragma mark - Framework-internal methods\n\n- (BOOL)running {\n  return _engine != nullptr;\n}\n\n- (void)updateDisplayConfig {\n  if (!_engine) {\n    return;\n  }\n\n  CVDisplayLinkRef displayLinkRef;\n  CGDirectDisplayID mainDisplayID = CGMainDisplayID();\n  CVDisplayLinkCreateWithCGDisplay(mainDisplayID, &displayLinkRef);\n  CVTime nominal = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLinkRef);\n  if (!(nominal.flags & kCVTimeIsIndefinite)) {\n    double refreshRate = static_cast<double>(nominal.timeScale) / nominal.timeValue;\n\n    std::vector<std::unique_ptr<clay::Display>> displays;\n    displays.push_back(std::make_unique<clay::Display>(mainDisplayID, round(refreshRate)));\n    _engine->GetShell().OnDisplayUpdates(clay::DisplayUpdateType::kStartup, std::move(displays));\n  }\n\n  CVDisplayLinkRelease(displayLinkRef);\n}\n\n- (void)onSettingsChanged:(NSNotification*)notification {\n  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.\n  // NSString* brightness =\n  //  [[NSUserDefaults standardUserDefaults] stringForKey:@\"AppleInterfaceStyle\"];\n}\n\n- (void)sendInitialSettings {\n  // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015.\n  [[NSDistributedNotificationCenter defaultCenter]\n      addObserver:self\n         selector:@selector(onSettingsChanged:)\n             name:@\"AppleInterfaceThemeChangedNotification\"\n           object:nil];\n  [self onSettingsChanged:nil];\n}\n\n- (nonnull NSString*)executableName {\n  return [[[NSProcessInfo processInfo] arguments] firstObject] ?: @\"Flutter\";\n}\n\n- (void)updateWindowMetrics {\n  if (!_engine) {\n    return;\n  }\n  NSView* view = _view;\n  CGRect scaledBounds = [view convertRectToBacking:view.bounds];\n  CGSize scaledSize = scaledBounds.size;\n  double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width;\n\n  clay::ViewportMetrics metrics;\n\n  metrics.physical_width = static_cast<size_t>(scaledSize.width);\n  metrics.physical_height = static_cast<size_t>(scaledSize.height);\n  metrics.device_pixel_ratio = pixelRatio;\n  // Default logical pixel is 96\n  metrics.device_density_dpi = pixelRatio * 96.f;\n  metrics.physical_view_inset_top = 0.0;\n  metrics.physical_view_inset_right = 0.0;\n  metrics.physical_view_inset_bottom = 0.0;\n  metrics.physical_view_inset_left = 0.0;\n  _engine->SetViewportMetrics(metrics);\n}\n\n- (void)sendPointerEvent:(const ClayPointerEvent&)event {\n  if (event.signal_kind == kClayPointerSignalKindScroll) {\n    [self sendScrollEvent:event];\n    return;\n  }\n  _engine->SendPointerEvent(&event, 1);\n}\n\n- (void)sendScrollEvent:(const ClayPointerEvent&)event {\n  if (_lastScrollEvent.signal_kind == kClayPointerSignalKindScroll) {\n    _lastScrollEvent.scroll_delta_x += event.scroll_delta_x;\n    _lastScrollEvent.scroll_delta_y += event.scroll_delta_y;\n    return;\n  }\n  _lastScrollEvent = event;\n\n  const int kVSyncInterval = 16 * 1000 * 1000;  // 16ms\n  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kVSyncInterval), dispatch_get_main_queue(), ^{\n    if (_lastScrollEvent.signal_kind == kClayPointerSignalKindNone) {\n      return;\n    }\n    _engine->SendPointerEvent(&_lastScrollEvent, 1);\n    _lastScrollEvent.signal_kind = kClayPointerSignalKindNone;\n  });\n}\n\n- (void)sendKeyEvent:(const ClayKeyEvent&)event\n            callback:(ClayKeyEventCallback)callback\n            userData:(void*)userData {\n  _engine->SendKeyEvent(&event, callback, userData);\n}\n\n- (FlutterPlatformViewController*)platformViewController {\n  return _platformViewController;\n}\n\n#pragma mark - Private methods\n\n- (void)sendUserLocales {\n}\n\n- (void)engineCallbackOnPreEngineRestart {\n  if (_viewController) {\n    [_viewController onPreEngineRestart];\n  }\n}\n\n/**\n * Note: Called from dealloc. Should not use accessors or other methods.\n */\n- (void)shutDownEngine {\n  if (_engine == nullptr) {\n    return;\n  }\n\n  if (_view) {\n    [_view shutdown];\n  }\n  _engine->NotifyDestroyed();\n  _engine->CollectShell();\n\n  // Balancing release for the retain in the task runner dispatch table.\n  CFRelease((CFTypeRef)self);\n  _engine = nullptr;\n}\n\n- (void)setUpNotificationCenterListeners {\n  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];\n  // macOS fires this private message when VoiceOver turns on or off.\n  [center addObserver:self\n             selector:@selector(applicationWillTerminate:)\n                 name:NSApplicationWillTerminateNotification\n               object:nil];\n}\n\n- (void)applicationWillTerminate:(NSNotification*)notification {\n  [self shutDownEngine];\n}\n\n- (void)playSystemSound:(NSString*)soundType {\n  if ([soundType isEqualToString:@\"SystemSoundType.alert\"]) {\n    NSBeep();\n  }\n}\n\n- (NSDictionary*)getClipboardData:(NSString*)format {\n  NSPasteboard* pasteboard = self.pasteboard;\n  if ([format isEqualToString:@(kTextPlainFormat)]) {\n    NSString* stringInPasteboard = [pasteboard stringForType:NSPasteboardTypeString];\n    return stringInPasteboard == nil ? nil : @{@\"text\" : stringInPasteboard};\n  }\n  return nil;\n}\n\n- (void)setClipboardData:(NSDictionary*)data {\n  NSPasteboard* pasteboard = self.pasteboard;\n  NSString* text = data[@\"text\"];\n  [pasteboard clearContents];\n  if (text && ![text isEqual:[NSNull null]]) {\n    [pasteboard setString:text forType:NSPasteboardTypeString];\n  }\n}\n\n- (BOOL)clipboardHasStrings {\n  return [self.pasteboard stringForType:NSPasteboardTypeString].length > 0;\n}\n\n- (NSPasteboard*)pasteboard {\n  return [NSPasteboard generalPasteboard];\n}\n\n- (nullable void*)clayViewContext {\n  if (_engine == nullptr) {\n    return nullptr;\n  }\n\n  return _engine->GetShell().GetEngine()->GetViewContext();\n}\n\n- (void)onEnterForeground {\n  if (_engine == nullptr) {\n    return;\n  }\n  _engine->GetShell().GetEngine()->OnEnterForeground();\n}\n\n- (void)onEnterBackground {\n  if (_engine == nullptr) {\n    return;\n  }\n  _engine->GetShell().GetEngine()->OnEnterBackground();\n}\n\n- (void)setVisible:(BOOL)visible {\n  if (!_engine) {\n    return;\n  }\n  _engine->GetShell().GetEngine()->SetVisible(visible);\n}\n\n- (void)setInterceptUrlCallback:(FlutterViewShouldInterceptUrlCallback _Nullable)callback {\n  _platformViewDelegate.SetInterceptUrlCallback(callback);\n}\n\n- (void)updateEditState:(int)client_id\n          selectionBase:(uint64_t)selection_base\n        composingExtent:(uint64_t)composing_extent\n      selectionAffinity:(const char*)selection_affinity\n                   text:(const char*)text\n        selectionExtent:(uint64_t)selection_extent\n          composingBase:(uint64_t)composing_base {\n  _engine->GetShell().GetEngine()->GetPageView()->OnPlatformUpdateEditState(\n      client_id, selection_base, composing_extent, selection_affinity, text, selection_extent,\n      composing_base);\n}\n\n- (void)performInputAction:(int)client_id {\n  _engine->GetShell().GetEngine()->GetPageView()->OnPlatformPerformInputAction(client_id);\n}\n\n- (void)performMouseDragLeave {\n  clay::Puppet<clay::Owner::kPlatform, clay::DragDropService> drag_drop_service =\n      _service_manager->GetService<clay::DragDropService>();\n  drag_drop_service.Act([](auto& impl) { impl.OnPlatformDragLeave(); });\n}\n\n- (void)performMouseDragEnterAndOverAtPoint:(NSPoint)point {\n  clay::Puppet<clay::Owner::kPlatform, clay::DragDropService> drag_drop_service =\n      _service_manager->GetService<clay::DragDropService>();\n  drag_drop_service.Act(\n      [point](auto& impl) { impl.OnPlatformDragEnterAndOver(clay::FloatPoint(point.x, point.y)); });\n}\n\n- (void)performMouseDragDropAtPoint:(NSPoint)point type:(NSString*)type dropContent:(id)content {\n  std::string typeStr = \"\";\n  // content maybe a string or an array.\n  std::string contentStr = \"\";\n  std::list<std::unordered_map<std::string, std::string>> contentList;\n  if ([content isKindOfClass:[NSString class]]) {\n    typeStr = clay::kDragTextType;\n    contentStr = [(NSString*)content UTF8String];\n  } else if ([content isKindOfClass:[NSArray class]]) {\n    typeStr = clay::kDragFileType;\n    NSArray* contentArrayObj = (NSArray*)content;\n    for (id item in contentArrayObj) {\n      if ([item isKindOfClass:[NSDictionary class]]) {\n        NSDictionary* dict = (NSDictionary*)item;\n        std::unordered_map<std::string, std::string> mapItem;\n        mapItem[clay::kDragDropPathKey] =\n            [(NSString*)[dict objectForKey:kDragDropPathKey] UTF8String];\n        mapItem[clay::kDragDropNameKey] =\n            [(NSString*)[dict objectForKey:kDragDropNameKey] UTF8String];\n        mapItem[clay::kDragDropTypeKey] =\n            [(NSString*)[dict objectForKey:kDragDropTypeKey] UTF8String];\n        mapItem[clay::kDragDropSizeKey] =\n            [[(NSNumber*)[dict objectForKey:kDragDropSizeKey] stringValue] UTF8String];\n        mapItem[clay::kDragDropLastModifiedKey] =\n            [(NSString*)[dict objectForKey:kDragDropLastModifiedKey] UTF8String];\n        contentList.push_back(mapItem);\n      }\n    }\n  }\n  clay::Puppet<clay::Owner::kPlatform, clay::DragDropService> drag_drop_service =\n      _service_manager->GetService<clay::DragDropService>();\n  drag_drop_service.Act([&](auto& impl) {\n    impl.OnPlatformDragDrop(clay::FloatPoint(point.x, point.y), typeStr, contentStr, contentList);\n  });\n}\n\n#pragma mark - FlutterPluginRegistry\n\n- (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {\n  return [[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self];\n}\n\n#pragma mark - Task runner integration\n\n- (void)runTaskOnEmbedder:(ClayTask)task {\n  if (!_engine) {\n    return;\n  }\n  if (!_engine->RunTask(&task)) {\n    FML_LOG(ERROR) << \"Could not post a task to the Flutter engine.\";\n  }\n}\n\n- (void)postMainThreadTask:(ClayTask)task targetTimeInNanoseconds:(uint64_t)targetTime {\n  __weak FlutterEngine* weakSelf = self;\n  auto worker = ^{\n    [weakSelf runTaskOnEmbedder:task];\n  };\n\n  const auto engine_time = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();\n  if (targetTime <= engine_time) {\n    dispatch_async(dispatch_get_main_queue(), worker);\n\n  } else {\n    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, targetTime - engine_time),\n                   dispatch_get_main_queue(), worker);\n  }\n}\n\n- (BOOL)postUIThreadTask:(const fml::closure&)callback {\n  struct Captures {\n    fml::closure callback;\n  };\n  auto captures = new Captures();\n  captures->callback = std::move(callback);\n  FlutterEngine* strong_self = self;\n  if (strong_self && strong_self->_engine) {\n    auto task = [captures]() {\n      captures->callback();\n      delete captures;\n    };\n    if (_engine->PostUIThreadTask(std::move(task))) {\n      return true;\n    }\n  }\n  delete captures;\n  return false;\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/darwin/macos/framework/Headers/ClayViewProvider.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h\"\n\n#import <Cocoa/Cocoa.h>\n\n#include <memory>\n\n#import \"base/include/closure.h\"\n#import \"clay/public/clay.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/ClayMouseCursorPlugin.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h\"\n\n@interface FlutterEngine ()\n\n/**\n * True if the engine is currently running.\n */\n@property(nonatomic, readonly) BOOL running;\n\n/**\n * Provides the renderer config needed to initialize the engine.\n */\n@property(nonatomic, readonly, nullable) FlutterRenderer* renderer;\n\n/**\n * The executable name for the current process.\n */\n@property(nonatomic, readonly, nonnull) NSString* executableName;\n\n/**\n * This just returns the NSPasteboard so that it can be mocked in the tests.\n */\n@property(nonatomic, readonly, nonnull) NSPasteboard* pasteboard;\n\n@property(nonatomic, weak) ClayViewProvider* clayViewProvider;\n\n/**\n * Informs the engine that the associated view controller's view size has changed.\n */\n- (void)updateWindowMetrics;\n\n/**\n * Dispatches the given pointer event data to engine.\n */\n- (void)sendPointerEvent:(const ClayPointerEvent&)event;\n\n/**\n * Dispatches the given pointer event data to engine.\n */\n- (void)sendKeyEvent:(const ClayKeyEvent&)event\n            callback:(nullable ClayKeyEventCallback)callback\n            userData:(nullable void*)userData;\n\n- (nonnull FlutterPlatformViewController*)platformViewController;\n\n- (BOOL)postUIThreadTask:(const fml::closure&)callback;\n\n- (void)setViewProvider:(nonnull ClayViewProvider*)viewProvider;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\ntypedef void (^FlutterAsyncKeyCallback)(BOOL handled);\n\n/**\n * An interface for a responder that can process a key event and decides whether\n * to handle an event asynchronously.\n *\n * To use this class, add it to a |FlutterKeyboardManager| with |addPrimaryResponder|.\n */\n@protocol FlutterKeyPrimaryResponder\n\n/**\n * Process the event.\n *\n * The |callback| should be called with a value that indicates whether the\n * responder has handled the given event. The |callback| must be called exactly\n * once, and can be called before the return of this method, or after.\n */\n@required\n- (void)handleEvent:(nonnull NSEvent*)event callback:(nonnull FlutterAsyncKeyCallback)callback;\n\n/* A map from macOS key code to logical keyboard.\n *\n * The map is assigned on initialization, and updated when the user changes\n * keyboard type or layout. The responder should prioritize this map when\n * deriving logical keys.\n */\n@required\n@property(nonatomic, nullable, strong) NSMutableDictionary<NSNumber*, NSNumber*>* layoutMap;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h\"\n\n/**\n * Processes keyboard events and cooperate with |TextInputPlugin|.\n *\n * A keyboard event goes through a few sections, each can choose to handled the\n * event, and only unhandled events can move to the next section:\n *\n * - Pre-filtering: Events during IME are sent to the system immediately.\n * - Keyboard: Dispatch to the embedder responder and the channel responder\n *   simultaneously. After both responders have responded (asynchronously), the\n *   event is considered handled if either responder handles.\n * - Text input: Events are sent to |TextInputPlugin| and are handled\n *   synchronously.\n * - Next responder: Events are sent to the next responder as specified by\n *   |viewDelegate|.\n */\n@interface FlutterKeyboardManager : NSObject\n\n/**\n * Create a keyboard manager.\n *\n * The |viewDelegate| is a weak reference, typically implemented by\n * |FlutterViewController|.\n */\n- (nonnull instancetype)initWithViewDelegate:(nonnull id<FlutterKeyboardViewDelegate>)viewDelegate;\n\n/**\n * Processes a key event.\n *\n * Unhandled events will be dispatched to the text input system, and possibly\n * the next responder afterwards.\n */\n- (void)handleEvent:(nonnull NSEvent*)event;\n\n/**\n * Returns yes if is event currently being redispatched.\n *\n * In some instances (i.e. emoji shortcut) the event may be redelivered by cocoa\n * as key equivalent to FlutterTextInput, in which case it shouldn't be\n * processed again.\n */\n- (BOOL)isDispatchingKeyEvent:(nonnull NSEvent*)event;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h\"\n\n#include <cctype>\n#include <map>\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/KeyCodeMap_Internal.h\"\n\n// Turn on this flag to print complete layout data when switching IMEs. The data\n// is used in unit tests.\n// #define DEBUG_PRINT_LAYOUT\n\nnamespace {\nusing clay::LayoutClue;\nusing clay::LayoutGoal;\n\n#ifdef DEBUG_PRINT_LAYOUT\n// Prints layout entries that will be parsed by `MockLayoutData`.\nNSString* debugFormatLayoutData(NSString* debugLayoutData, uint16_t keyCode, LayoutClue clue1,\n                                LayoutClue clue2) {\n  return [NSString\n      stringWithFormat:@\"    %@%@0x%d%04x, 0x%d%04x,\", debugLayoutData,\n                       keyCode % 4 == 0 ? [NSString stringWithFormat:@\"\\n/* 0x%02x */ \", keyCode]\n                                        : @\" \",\n                       clue1.isDeadKey, clue1.character, clue2.isDeadKey, clue2.character];\n}\n#endif\n\n// Someohow this pointer type must be defined as a single type for the compiler\n// to compile the function pointer type (due to _Nullable).\ntypedef NSResponder* _NSResponderPtr;\ntypedef _Nullable _NSResponderPtr (^NextResponderProvider)();\n\nbool isEascii(const LayoutClue& clue) { return clue.character < 256 && !clue.isDeadKey; }\n\ntypedef void (^VoidBlock)();\n\n// Someohow this pointer type must be defined as a single type for the compiler\n// to compile the function pointer type (due to _Nullable).\ntypedef NSResponder* _NSResponderPtr;\ntypedef _Nullable _NSResponderPtr (^NextResponderProvider)();\n}  // namespace\n\n@interface FlutterKeyboardManager ()\n\n/**\n * The text input plugin set by initialization.\n */\n@property(weak, nonatomic) id<FlutterKeyboardViewDelegate> viewDelegate;\n\n/**\n * The primary responders added by addPrimaryResponder.\n */\n@property(nonatomic) NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;\n\n@property(nonatomic) NSMutableArray<NSEvent*>* pendingEvents;\n\n@property(nonatomic) BOOL processingEvent;\n\n@property(nonatomic) NSMutableDictionary<NSNumber*, NSNumber*>* layoutMap;\n\n@property(nonatomic, nullable) NSEvent* eventBeingDispatched;\n\n/**\n * Add a primary responder, which asynchronously decides whether to handle an\n * event.\n */\n- (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder;\n\n/**\n * Start processing the next event if not started already.\n *\n * This function might initiate an async process, whose callback calls this\n * function again.\n */\n- (void)processNextEvent;\n\n/**\n * Implement how to process an event.\n *\n * The `onFinish` must be called eventually, either during this function or\n * asynchronously later, otherwise the event queue will be stuck.\n *\n * This function is called by processNextEvent.\n */\n- (void)performProcessEvent:(NSEvent*)event onFinish:(nonnull VoidBlock)onFinish;\n\n/**\n * Dispatch an event that's not hadled by the responders to text input plugin,\n * and potentially to the next responder.\n */\n- (void)dispatchTextEvent:(nonnull NSEvent*)pendingEvent;\n\n/**\n * Clears the current layout and build a new one based on the current layout.\n */\n- (void)buildLayout;\n\n@end\n\n@implementation FlutterKeyboardManager {\n  NextResponderProvider _getNextResponder;\n}\n\n- (nonnull instancetype)initWithViewDelegate:(nonnull id<FlutterKeyboardViewDelegate>)viewDelegate {\n  self = [super init];\n  if (self != nil) {\n    _processingEvent = FALSE;\n    _viewDelegate = viewDelegate;\n\n    _primaryResponders = [[NSMutableArray alloc] init];\n    [self\n        addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc]\n                                initWithSendEvent:^(const ClayKeyEvent& event,\n                                                    ClayKeyEventCallback callback, void* userData) {\n                                  [_viewDelegate sendKeyEvent:event\n                                                     callback:callback\n                                                     userData:userData];\n                                }]];\n    _pendingEvents = [[NSMutableArray alloc] init];\n    _layoutMap = [NSMutableDictionary<NSNumber*, NSNumber*> dictionary];\n    [self buildLayout];\n    for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {\n      responder.layoutMap = _layoutMap;\n    }\n\n    __weak __typeof__(self) weakSelf = self;\n    [_viewDelegate subscribeToKeyboardLayoutChange:^() {\n      [weakSelf buildLayout];\n    }];\n  }\n  return self;\n}\n\n- (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder {\n  [_primaryResponders addObject:responder];\n}\n\n- (void)handleEvent:(nonnull NSEvent*)event {\n  // The `handleEvent` does not process the event immediately, but instead put\n  // events into a queue. Events are processed one by one by `processNextEvent`.\n\n  // Be sure to add a handling method in propagateKeyEvent when allowing more\n  // event types here.\n  if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp &&\n      event.type != NSEventTypeFlagsChanged) {\n    return;\n  }\n\n  [_pendingEvents addObject:event];\n  [self processNextEvent];\n}\n\n- (BOOL)isDispatchingKeyEvent:(NSEvent*)event {\n  return _eventBeingDispatched == event;\n}\n\n#pragma mark - Private\n\n- (void)processNextEvent {\n  @synchronized(self) {\n    if (_processingEvent || [_pendingEvents count] == 0) {\n      return;\n    }\n    _processingEvent = TRUE;\n  }\n\n  NSEvent* pendingEvent = [_pendingEvents firstObject];\n  [_pendingEvents removeObjectAtIndex:0];\n\n  __weak __typeof__(self) weakSelf = self;\n  VoidBlock onFinish = ^() {\n    weakSelf.processingEvent = FALSE;\n    [weakSelf processNextEvent];\n  };\n  [self performProcessEvent:pendingEvent onFinish:onFinish];\n}\n\n- (void)performProcessEvent:(NSEvent*)event onFinish:(VoidBlock)onFinish {\n  // Having no primary responders require extra logic, but Flutter hard-codes\n  // all primary responders, so this is a situation that Flutter will never\n  // encounter.\n  NSAssert([_primaryResponders count] >= 0, @\"At least one primary responder must be added.\");\n\n  __weak __typeof__(self) weakSelf = self;\n  __block int unreplied = [_primaryResponders count];\n  __block BOOL anyHandled = false;\n\n  FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {\n    unreplied -= 1;\n    NSAssert(unreplied >= 0, @\"More primary responders replied than possible.\");\n    anyHandled = anyHandled || handled;\n    if (unreplied == 0) {\n      if (!anyHandled) {\n        [weakSelf dispatchTextEvent:event];\n      }\n      onFinish();\n    }\n  };\n\n  for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {\n    [responder handleEvent:event callback:replyCallback];\n  }\n}\n\n- (void)dispatchTextEvent:(NSEvent*)event {\n  if ([_viewDelegate onTextInputKeyEvent:event]) {\n    return;\n  }\n  NSResponder* nextResponder = _viewDelegate.nextResponder;\n  if (nextResponder == nil) {\n    return;\n  }\n  NSAssert(_eventBeingDispatched == nil, @\"An event is already being dispached.\");\n  _eventBeingDispatched = event;\n  switch (event.type) {\n    case NSEventTypeKeyDown:\n      if ([nextResponder respondsToSelector:@selector(keyDown:)]) {\n        [nextResponder keyDown:event];\n      }\n      break;\n    case NSEventTypeKeyUp:\n      if ([nextResponder respondsToSelector:@selector(keyUp:)]) {\n        [nextResponder keyUp:event];\n      }\n      break;\n    case NSEventTypeFlagsChanged:\n      if ([nextResponder respondsToSelector:@selector(flagsChanged:)]) {\n        [nextResponder flagsChanged:event];\n      }\n      break;\n    default:\n      NSAssert(false, @\"Unexpected key event type (got %lu).\", event.type);\n  }\n  NSAssert(_eventBeingDispatched != nil, @\"_eventBeingDispatched was cleared unexpectedly.\");\n  _eventBeingDispatched = nil;\n}\n\n- (void)buildLayout {\n  [_layoutMap removeAllObjects];\n\n  std::map<uint32_t, LayoutGoal> mandatoryGoalsByChar;\n  std::map<uint32_t, LayoutGoal> usLayoutGoalsByKeyCode;\n  for (const LayoutGoal& goal : clay::layoutGoals) {\n    if (goal.mandatory) {\n      mandatoryGoalsByChar[goal.keyChar] = goal;\n    } else {\n      usLayoutGoalsByKeyCode[goal.keyCode] = goal;\n    }\n  }\n\n  // Derive key mapping for each key code based on their layout clues.\n  // Key code 0x00 - 0x32 are typewriter keys (letters, digits, and symbols.)\n  // See keyCodeToPhysicalKey.\n  const uint16_t kMaxKeyCode = 0x32;\n#ifdef DEBUG_PRINT_LAYOUT\n  NSString* debugLayoutData = @\"\";\n#endif\n  for (uint16_t keyCode = 0; keyCode <= kMaxKeyCode; keyCode += 1) {\n    std::vector<LayoutClue> thisKeyClues = {\n        [_viewDelegate lookUpLayoutForKeyCode:keyCode shift:false],\n        [_viewDelegate lookUpLayoutForKeyCode:keyCode shift:true]};\n#ifdef DEBUG_PRINT_LAYOUT\n    debugLayoutData =\n        debugFormatLayoutData(debugLayoutData, keyCode, thisKeyClues[0], thisKeyClues[1]);\n#endif\n    // The logical key should be the first available clue from below:\n    //\n    //  - Mandatory goal, if it matches any clue. This ensures that all alnum\n    //    keys can be found somewhere.\n    //  - US layout, if neither clue of the key is EASCII. This ensures that\n    //    there are no non-latin logical keys.\n    //  - Derived on the fly from keyCode & characters.\n    for (const LayoutClue& clue : thisKeyClues) {\n      uint32_t keyChar = clue.isDeadKey ? 0 : clue.character;\n      auto matchingGoal = mandatoryGoalsByChar.find(keyChar);\n      if (matchingGoal != mandatoryGoalsByChar.end()) {\n        // Found a key that produces a mandatory char. Use it.\n        NSAssert(_layoutMap[@(keyCode)] == nil, @\"Attempting to assign an assigned key code.\");\n        _layoutMap[@(keyCode)] = @(keyChar);\n        mandatoryGoalsByChar.erase(matchingGoal);\n        break;\n      }\n    }\n    bool hasAnyEascii = isEascii(thisKeyClues[0]) || isEascii(thisKeyClues[1]);\n    // See if any produced char meets the requirement as a logical key.\n    auto foundUsLayoutGoal = usLayoutGoalsByKeyCode.find(keyCode);\n    if (foundUsLayoutGoal != usLayoutGoalsByKeyCode.end() && _layoutMap[@(keyCode)] == nil &&\n        !hasAnyEascii) {\n      _layoutMap[@(keyCode)] = @(foundUsLayoutGoal->second.keyChar);\n    }\n  }\n#ifdef DEBUG_PRINT_LAYOUT\n  FML_LOG(INFO) << debugLayoutData;\n#endif\n\n  // Ensure all mandatory goals are assigned.\n  for (auto mandatoryGoalIter : mandatoryGoalsByChar) {\n    const LayoutGoal& goal = mandatoryGoalIter.second;\n    _layoutMap[@(goal.keyCode)] = @(goal.keyChar);\n  }\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"clay/public/clay.h\"\n\nnamespace clay {\n\n// Signature used to notify that a keyboard layout has changed.\ntypedef void (^KeyboardLayoutNotifier)();\n\n// The printable result of a key under certain modifiers, used to derive key\n// mapping.\ntypedef struct {\n  // The printable character.\n  //\n  // If `isDeadKey` is true, then this is the character when pressing the same\n  // dead key twice.\n  uint32_t character;\n\n  // Whether this character is a dead key.\n  //\n  // A dead key is a key that is not counted as text per se, but holds a\n  // diacritics to be added to the next key.\n  bool isDeadKey;\n} LayoutClue;\n\n}  // namespace clay\n\n/**\n * An interface for a class that can provides |FlutterKeyboardManager| with\n * platform-related features.\n *\n * This protocol is typically implemented by |FlutterViewController|.\n */\n@protocol FlutterKeyboardViewDelegate\n\n@required\n/**\n * Get the next responder to dispatch events that the keyboard system\n * (including text input) do not handle.\n *\n * If the |nextResponder| is null, then those events will be discarded.\n */\n@property(nonatomic, readonly, nullable) NSResponder* nextResponder;\n\n/**\n * Dispatch events to the framework to be processed by |HardwareKeyboard|.\n *\n * This method typically forwards events to\n * |FlutterEngine.sendKeyEvent:callback:userData:|.\n */\n- (void)sendKeyEvent:(const ClayKeyEvent&)event\n            callback:(nullable ClayKeyEventCallback)callback\n            userData:(nullable void*)userData;\n\n/**\n * Dispatch events that are not handled by the keyboard event handlers\n * to the text input handler.\n *\n * This method typically forwards events to |TextInputPlugin.handleKeyEvent|.\n */\n- (BOOL)onTextInputKeyEvent:(nonnull NSEvent*)event;\n\n/**\n * Add a listener that is called whenever the user changes keyboard layout.\n *\n * Only one listeners is supported. Adding new ones overwrites the current one.\n * Assigning nil unsubscribes.\n */\n- (void)subscribeToKeyboardLayoutChange:(nullable clay::KeyboardLayoutNotifier)callback;\n\n/**\n * Querying the printable result of a key under the given modifier state.\n */\n- (clay::LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h\"\n\n#include <map>\n#include <unordered_set>\n\n@interface FlutterPlatformViewController : NSViewController\n@end\n\n@interface FlutterPlatformViewController ()\n\n/**\n * Returns the platform view associated with the viewId.\n */\n- (nullable NSView*)platformViewWithID:(int64_t)viewId;\n\n/**\n * Register a view factory by adding an entry into the platformViewFactories map with key factoryId\n * and value factory.\n */\n- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)factory\n                     withId:(nonnull NSString*)factoryId;\n\n/**\n * Removes platform views slated to be disposed via method handler calls.\n */\n- (void)disposePlatformViews;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/logging.h\"\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h\"\n\n@implementation FlutterPlatformViewController {\n  // NSDictionary maps platform view type identifiers to FlutterPlatformViewFactories.\n  NSMutableDictionary<NSString*, NSObject<FlutterPlatformViewFactory>*>* _platformViewFactories;\n\n  // Map from platform view id to the underlying NSView.\n  std::map<int, NSView*> _platformViews;\n\n  // View ids that are going to be disposed on the next present call.\n  std::unordered_set<int64_t> _platformViewsToDispose;\n}\n\n- (instancetype)init {\n  self = [super init];\n  if (self) {\n    _platformViewFactories = [[NSMutableDictionary alloc] init];\n  }\n  return self;\n}\n\n- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)factory\n                     withId:(nonnull NSString*)factoryId {\n  _platformViewFactories[factoryId] = factory;\n}\n\n- (nullable NSView*)platformViewWithID:(int64_t)viewId {\n  if (_platformViews.count(viewId)) {\n    return _platformViews[viewId];\n  } else {\n    return nil;\n  }\n}\n\n- (void)disposePlatformViews {\n  if (_platformViewsToDispose.empty()) {\n    return;\n  }\n\n  FML_DCHECK([[NSThread currentThread] isMainThread])\n      << \"Must be on the main thread to handle disposing platform views\";\n  for (int64_t viewId : _platformViewsToDispose) {\n    NSView* view = _platformViews[viewId];\n    [view removeFromSuperview];\n    _platformViews.erase(viewId);\n  }\n  _platformViewsToDispose.clear();\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterView.h\"\n#import \"clay/shell/platform/embedder/embedder_surface_metal.h\"\n\n/**\n * Rendering backend agnostic FlutterRendererConfig provider to be used by the embedder API.\n */\n@interface FlutterRenderer : NSObject\n\n/**\n * Interface to the system GPU. Used to issue all the rendering commands.\n */\n@property(nonatomic, readonly, nonnull) id<MTLDevice> device;\n\n/**\n * Used to get the command buffers for the MTLDevice to render to.\n */\n@property(nonatomic, readonly, nonnull) id<MTLCommandQueue> commandQueue;\n\n/**\n * Intializes the renderer with the given FlutterEngine.\n */\n- (nullable instancetype)initWithFlutterEngine:(nonnull FlutterEngine*)flutterEngine;\n\n/**\n * Creates a Metal embedder surface.\n */\n- (fml::RefPtr<clay::EmbedderSurfaceMetal>)createEmbedderSurfaceMetal;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterRenderer.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h\"\n\n#include \"clay/fml/logging.h\"\n\n#ifndef ENABLE_SKITY\n#include \"clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.h\"\n#else\n#include \"clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkity.h\"\n#endif\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h\"\n\nnamespace clay {\nclass EmbedderSurfaceMetalDelegateImpl : public EmbedderSurfaceMetalDelegate {\n public:\n  explicit EmbedderSurfaceMetalDelegateImpl(void* flutter_renderer);\n  GPUMTLDeviceHandle GetMTLDevice() const override;\n  GPUMTLCommandQueueHandle GetMTLCommandQueue() const override;\n  GPUMTLTextureInfo GetMTLTexture(const skity::Vec2& frame_size) const override;\n  bool PresentTexture(GPUMTLTextureInfo texture) const override;\n  bool EnablePartialRepaint() const override;\n\n private:\n  void* flutter_renderer_;\n};\n}  // namespace clay\n\n#pragma mark - FlutterRenderer implementation\n\n@implementation FlutterRenderer {\n  FlutterViewEngineProvider* _viewProvider;\n#ifndef ENABLE_SKITY\n  FlutterDarwinContextMetalSkia* _darwinMetalContext;\n#else\n  FlutterDarwinContextMetalSkity* _darwinMetalContext;\n#endif\n  clay::EmbedderSurfaceMetalDelegateImpl* _embedderSurfaceMetalDelegate;\n}\n\n- (instancetype)initWithFlutterEngine:(nonnull FlutterEngine*)flutterEngine {\n  self = [super init];\n  if (self) {\n    _viewProvider = [[FlutterViewEngineProvider alloc] initWithEngine:flutterEngine];\n    _device = MTLCreateSystemDefaultDevice();\n    if (!_device) {\n      FML_LOG(ERROR) << \"Could not acquire Metal device.\";\n      return nil;\n    }\n\n    _commandQueue = [_device newCommandQueue];\n    if (!_commandQueue) {\n      FML_LOG(ERROR) << \"Could not create Metal command queue.\";\n      return nil;\n    }\n\n#ifndef ENABLE_SKITY\n    _darwinMetalContext = [[FlutterDarwinContextMetalSkia alloc] initWithMTLDevice:_device\n                                                                      commandQueue:_commandQueue];\n#else\n    _darwinMetalContext = [[FlutterDarwinContextMetalSkity alloc] initWithMTLDevice:_device\n                                                                       commandQueue:_commandQueue];\n#endif\n\n    _embedderSurfaceMetalDelegate =\n        new clay::EmbedderSurfaceMetalDelegateImpl((__bridge void*)self);\n  }\n  return self;\n}\n\n- (void)dealloc {\n  if (_embedderSurfaceMetalDelegate) {\n    delete _embedderSurfaceMetalDelegate;\n  }\n}\n\n- (FlutterViewEngineProvider*)viewProvider {\n  return _viewProvider;\n}\n\n- (id<MTLDevice>)mtlDevice {\n  return _device;\n}\n\n- (id<MTLCommandQueue>)mtlCommandQueue {\n  return _commandQueue;\n}\n\n- (fml::RefPtr<clay::EmbedderSurfaceMetal>)createEmbedderSurfaceMetal {\n  return fml::MakeRefCounted<clay::EmbedderSurfaceMetal>(_embedderSurfaceMetalDelegate);\n}\n\n@end\n\nnamespace clay {\n\nEmbedderSurfaceMetalDelegateImpl::EmbedderSurfaceMetalDelegateImpl(void* flutter_renderer)\n    : flutter_renderer_(flutter_renderer) {}\n\nGPUMTLDeviceHandle EmbedderSurfaceMetalDelegateImpl::GetMTLDevice() const {\n  FlutterRenderer* flutter_renderer = (__bridge FlutterRenderer*)flutter_renderer_;\n  return (__bridge GPUMTLDeviceHandle)[flutter_renderer mtlDevice];\n}\n\nGPUMTLCommandQueueHandle EmbedderSurfaceMetalDelegateImpl::GetMTLCommandQueue() const {\n  FlutterRenderer* flutter_renderer = (__bridge FlutterRenderer*)flutter_renderer_;\n  return (__bridge GPUMTLCommandQueueHandle)[flutter_renderer mtlCommandQueue];\n}\n\nGPUMTLTextureInfo EmbedderSurfaceMetalDelegateImpl::GetMTLTexture(\n    const skity::Vec2& frame_size) const {\n  FlutterRenderer* flutter_renderer = (__bridge FlutterRenderer*)flutter_renderer_;\n  CGSize size = CGSizeMake(frame_size.x, frame_size.y);\n  FlutterView* view = [[flutter_renderer viewProvider] getView:kFlutterDefaultViewId];\n  if (view == nil) {\n    return {};\n  }\n  return [view.surfaceManager surfaceForSize:size].asGPUMTLTextureInfo;\n}\n\nbool EmbedderSurfaceMetalDelegateImpl::PresentTexture(GPUMTLTextureInfo texture) const {\n  FlutterRenderer* flutter_renderer = (__bridge FlutterRenderer*)flutter_renderer_;\n  FlutterView* view = [[flutter_renderer viewProvider] getView:kFlutterDefaultViewId];\n  if (view == nil) {\n    return NO;\n  }\n  FlutterSurface* surface = [FlutterSurface fromGPUMTLTextureInfo:&texture];\n  if (surface == nil) {\n    return NO;\n  }\n  FlutterSurfacePresentInfo* info = [[FlutterSurfacePresentInfo alloc] init];\n  info.surface = surface;\n  [view.surfaceManager present:@[ info ] notify:nil];\n  return YES;\n}\n\nbool EmbedderSurfaceMetalDelegateImpl::EnablePartialRepaint() const { return YES; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterSurface.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"clay/shell/platform/embedder/embedder_surface_metal.h\"\n\n/**\n * Opaque surface type.\n * Can be represented as FlutterMetalTexture to cross the embedder API boundary.\n */\n@interface FlutterSurface : NSObject\n\n- (clay::GPUMTLTextureInfo)asGPUMTLTextureInfo;\n\n+ (nullable FlutterSurface*)fromGPUMTLTextureInfo:\n    (nonnull const clay::GPUMTLTextureInfo*)textureInfo;\n\n@end\n\n/**\n * Internal FlutterSurface interface used by FlutterSurfaceManager.\n * Wraps an IOSurface framebuffer and metadata related to the surface.\n */\n@interface FlutterSurface (Private)\n\n- (nonnull instancetype)initWithSize:(CGSize)size device:(nonnull id<MTLDevice>)device;\n\n@property(readonly, nonatomic, nonnull) IOSurfaceRef ioSurface;\n@property(readonly, nonatomic) CGSize size;\n@property(readonly, nonatomic) int64_t textureId;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterSurface.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterSurface.h\"\n\n#import <Metal/Metal.h>\n\n@interface FlutterSurface () {\n  CGSize _size;\n  IOSurfaceRef _ioSurface;\n  id<MTLTexture> _texture;\n}\n@end\n\n@implementation FlutterSurface\n\n- (IOSurfaceRef)ioSurface {\n  return _ioSurface;\n}\n\n- (CGSize)size {\n  return _size;\n}\n\n- (int64_t)textureId {\n  return reinterpret_cast<int64_t>(_texture);\n}\n\n- (instancetype)initWithSize:(CGSize)size device:(id<MTLDevice>)device {\n  if (self = [super init]) {\n    self->_size = size;\n    self->_ioSurface = [FlutterSurface createIOSurfaceWithSize:size];\n    self->_texture = [FlutterSurface createTextureForIOSurface:_ioSurface size:size device:device];\n  }\n  return self;\n}\n\nstatic void ReleaseSurface(void* surface) {\n  if (surface != nullptr) {\n    CFBridgingRelease(surface);\n  }\n}\n\n- (clay::GPUMTLTextureInfo)asGPUMTLTextureInfo {\n  clay::GPUMTLTextureInfo res;\n  memset(&res, 0, sizeof(clay::GPUMTLTextureInfo));\n  res.texture = (__bridge void*)_texture;\n  res.texture_id = self.textureId;\n  res.destruction_callback = ReleaseSurface;\n  res.destruction_context = (void*)CFBridgingRetain(self);\n  return res;\n}\n\n+ (FlutterSurface*)fromGPUMTLTextureInfo:(const clay::GPUMTLTextureInfo*)textureInfo {\n  return (__bridge FlutterSurface*)textureInfo->destruction_context;\n}\n\n- (void)dealloc {\n  CFRelease(_ioSurface);\n}\n\n+ (IOSurfaceRef)createIOSurfaceWithSize:(CGSize)size {\n  unsigned pixelFormat = 'BGRA';\n  unsigned bytesPerElement = 4;\n\n  size_t bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width * bytesPerElement);\n  size_t totalBytes = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height * bytesPerRow);\n  NSDictionary* options = @{\n    (id)kIOSurfaceWidth : @(size.width),\n    (id)kIOSurfaceHeight : @(size.height),\n    (id)kIOSurfacePixelFormat : @(pixelFormat),\n    (id)kIOSurfaceBytesPerElement : @(bytesPerElement),\n    (id)kIOSurfaceBytesPerRow : @(bytesPerRow),\n    (id)kIOSurfaceAllocSize : @(totalBytes),\n  };\n\n  IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options);\n  IOSurfaceSetValue(res, CFSTR(\"IOSurfaceColorSpace\"), kCGColorSpaceSRGB);\n  return res;\n}\n\n+ (id<MTLTexture>)createTextureForIOSurface:(IOSurfaceRef)surface\n                                       size:(CGSize)size\n                                     device:(id<MTLDevice>)device {\n  MTLTextureDescriptor* textureDescriptor =\n      [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm\n                                                         width:size.width\n                                                        height:size.height\n                                                     mipmapped:NO];\n  textureDescriptor.usage =\n      MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget | MTLTextureUsageShaderWrite;\n  // plane = 0 for BGRA.\n  return [device newTextureWithDescriptor:textureDescriptor iosurface:surface plane:0];\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n#import <QuartzCore/QuartzCore.h>\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterSurface.h\"\n\n/**\n * Surface with additional properties needed for presenting.\n */\n@interface FlutterSurfacePresentInfo : NSObject\n\n@property(readwrite, strong, nonatomic, nonnull) FlutterSurface* surface;\n@property(readwrite, nonatomic) CGPoint offset;\n@property(readwrite, nonatomic) size_t zIndex;\n\n@end\n\n@protocol FlutterSurfaceManagerDelegate <NSObject>\n\n/*\n * Schedules the block on the platform thread and blocks until the block is executed.\n * Provided `frameSize` is used to unblock the platform thread if it waits for\n * a certain frame size during resizing.\n */\n- (void)onPresent:(CGSize)frameSize withBlock:(nonnull dispatch_block_t)block;\n\n@end\n\n/**\n * FlutterSurfaceManager is responsible for providing and presenting Core Animation render\n * surfaces and managing sublayers.\n *\n * Owned by `FlutterView`.\n */\n@interface FlutterSurfaceManager : NSObject\n\n/**\n * Initializes and returns a surface manager that renders to a child layer (referred to as the\n * content layer) of the containing layer.\n */\n- (nullable instancetype)initWithDevice:(nonnull id<MTLDevice>)device\n                           commandQueue:(nonnull id<MTLCommandQueue>)commandQueue\n                                  layer:(nonnull CALayer*)containingLayer\n                               delegate:(nonnull id<FlutterSurfaceManagerDelegate>)delegate;\n\n/**\n * Returns a back buffer surface of the given size to which Flutter can render content.\n * A cached surface will be returned if available; otherwise a new one will be created.\n *\n * Must be called on raster thread.\n */\n- (nonnull FlutterSurface*)surfaceForSize:(CGSize)size;\n\n/**\n * Sets the provided surfaces as contents of FlutterView. Will create, update and\n * remove sublayers as needed.\n *\n * Must be called on raster thread. This will schedule a commit on the platform thread and block the\n * raster thread until the commit is done. The `notify` block will be invoked on the platform thread\n * and can be used to perform additional work, such as mutating platform views. It is guaranteed be\n * called in the same CATransaction.\n */\n- (void)present:(nonnull NSArray<FlutterSurfacePresentInfo*>*)surfaces\n         notify:(nullable dispatch_block_t)notify;\n\n@end\n\n/**\n * Cache of back buffers to prevent unnecessary IOSurface allocations.\n */\n@interface FlutterBackBufferCache : NSObject\n\n/**\n * Removes surface with given size from cache (if available) and returns it.\n */\n- (nullable FlutterSurface*)removeSurfaceForSize:(CGSize)size;\n\n/**\n * Removes all cached surfaces replacing them with new ones.\n */\n- (void)replaceSurfaces:(nonnull NSArray<FlutterSurface*>*)surfaces;\n\n/**\n * Returns number of surfaces currently in cache. Used for tests.\n */\n- (NSUInteger)count;\n\n@end\n\n/**\n * Interface to internal properties used for testing.\n */\n@interface FlutterSurfaceManager (Private)\n\n@property(readonly, nonatomic, nonnull) FlutterBackBufferCache* backBufferCache;\n@property(readonly, nonatomic, nonnull) NSArray<FlutterSurface*>* frontSurfaces;\n@property(readonly, nonatomic, nonnull) NSArray<CALayer*>* layers;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h\"\n\n#import <Metal/Metal.h>\n\n#include <algorithm>\n\n#include \"clay/fml/logging.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterSurface.h\"\n\n@implementation FlutterSurfacePresentInfo\n@end\n\n@interface FlutterSurfaceManager () {\n  id<MTLDevice> _device;\n  id<MTLCommandQueue> _commandQueue;\n  CALayer* _containingLayer;\n  __weak id<FlutterSurfaceManagerDelegate> _delegate;\n\n  // Available (cached) back buffer surfaces. These will be cleared during\n  // present and replaced by current frong surfaces.\n  FlutterBackBufferCache* _backBufferCache;\n\n  // Surfaces currently used to back visible layers.\n  NSMutableArray<FlutterSurface*>* _frontSurfaces;\n\n  // Currently visible layers.\n  NSMutableArray<CALayer*>* _layers;\n}\n\n/**\n * Updates underlying CALayers with the contents of the surfaces to present.\n */\n- (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces;\n\n@end\n\n@implementation FlutterSurfaceManager\n\n- (instancetype)initWithDevice:(id<MTLDevice>)device\n                  commandQueue:(id<MTLCommandQueue>)commandQueue\n                         layer:(CALayer*)containingLayer\n                      delegate:(__weak id<FlutterSurfaceManagerDelegate>)delegate {\n  if (self = [super init]) {\n    _device = device;\n    _commandQueue = commandQueue;\n    _containingLayer = containingLayer;\n    _delegate = delegate;\n\n    _backBufferCache = [[FlutterBackBufferCache alloc] init];\n    _frontSurfaces = [NSMutableArray array];\n    _layers = [NSMutableArray array];\n  }\n  return self;\n}\n\n- (FlutterBackBufferCache*)backBufferCache {\n  return _backBufferCache;\n}\n\n- (NSArray*)frontSurfaces {\n  return _frontSurfaces;\n}\n\n- (NSArray*)layers {\n  return _layers;\n}\n\n- (FlutterSurface*)surfaceForSize:(CGSize)size {\n  FlutterSurface* surface = [_backBufferCache removeSurfaceForSize:size];\n  if (surface == nil) {\n    surface = [[FlutterSurface alloc] initWithSize:size device:_device];\n  }\n  return surface;\n}\n\n- (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces {\n  FML_DCHECK([NSThread isMainThread]);\n\n  // Release all unused back buffer surfaces and replace them with front surfaces.\n  [_backBufferCache replaceSurfaces:_frontSurfaces];\n\n  // Front surfaces will be replaced by currently presented surfaces.\n  [_frontSurfaces removeAllObjects];\n  for (FlutterSurfacePresentInfo* info in surfaces) {\n    [_frontSurfaces addObject:info.surface];\n  }\n\n  // Add or remove layers to match the count of surfaces to present.\n  while (_layers.count > _frontSurfaces.count) {\n    [_layers.lastObject removeFromSuperlayer];\n    [_layers removeLastObject];\n  }\n  while (_layers.count < _frontSurfaces.count) {\n    CALayer* layer = [CALayer layer];\n    [_containingLayer addSublayer:layer];\n    [_layers addObject:layer];\n  }\n\n  // Update contents of surfaces.\n  for (size_t i = 0; i < surfaces.count; ++i) {\n    FlutterSurfacePresentInfo* info = surfaces[i];\n    CALayer* layer = _layers[i];\n    CGFloat scale = _containingLayer.contentsScale;\n    layer.frame = CGRectMake(info.offset.x / scale, info.offset.y / scale,\n                             info.surface.size.width / scale, info.surface.size.height / scale);\n    layer.contents = (__bridge id)info.surface.ioSurface;\n    layer.zPosition = info.zIndex;\n  }\n}\n\nstatic CGSize GetRequiredFrameSize(NSArray<FlutterSurfacePresentInfo*>* surfaces) {\n  CGSize size = CGSizeZero;\n  for (FlutterSurfacePresentInfo* info in surfaces) {\n    size = CGSizeMake(std::max(size.width, info.offset.x + info.surface.size.width),\n                      std::max(size.height, info.offset.y + info.surface.size.height));\n  }\n  return size;\n}\n\n- (void)present:(NSArray<FlutterSurfacePresentInfo*>*)surfaces notify:(dispatch_block_t)notify {\n  id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];\n  [commandBuffer commit];\n  [commandBuffer waitUntilScheduled];\n\n  // Get the actual dimensions of the frame (relevant for thread synchronizer).\n  CGSize size = GetRequiredFrameSize(surfaces);\n\n  [_delegate onPresent:size\n             withBlock:^{\n               [self commit:surfaces];\n               if (notify != nil) {\n                 notify();\n               }\n             }];\n}\n\n@end\n\n// Cached back buffers will be released after kIdleDelay if there is no activity.\nstatic const double kIdleDelay = 1.0;\n\n@interface FlutterBackBufferCache () {\n  NSMutableArray<FlutterSurface*>* _surfaces;\n}\n\n@end\n\n@implementation FlutterBackBufferCache\n\n- (instancetype)init {\n  if (self = [super init]) {\n    self->_surfaces = [[NSMutableArray alloc] init];\n  }\n  return self;\n}\n\n- (nullable FlutterSurface*)removeSurfaceForSize:(CGSize)size {\n  @synchronized(self) {\n    for (FlutterSurface* surface in _surfaces) {\n      if (CGSizeEqualToSize(surface.size, size)) {\n        // By default ARC doesn't retain enumeration iteration variables.\n        FlutterSurface* res = surface;\n        [_surfaces removeObject:surface];\n        return res;\n      }\n    }\n    return nil;\n  }\n}\n\n- (void)replaceSurfaces:(nonnull NSArray<FlutterSurface*>*)surfaces {\n  @synchronized(self) {\n    [_surfaces removeAllObjects];\n    [_surfaces addObjectsFromArray:surfaces];\n  }\n\n  // performSelector:withObject:afterDelay needs to be performed on RunLoop thread\n  [self performSelectorOnMainThread:@selector(reschedule) withObject:nil waitUntilDone:NO];\n}\n\n- (NSUInteger)count {\n  @synchronized(self) {\n    return _surfaces.count;\n  }\n}\n\n- (void)onIdle {\n  @synchronized(self) {\n    [_surfaces removeAllObjects];\n  }\n}\n\n- (void)reschedule {\n  [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(onIdle) object:nil];\n  [self performSelector:@selector(onIdle) withObject:nil afterDelay:kIdleDelay];\n}\n\n- (void)dealloc {\n  [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(onIdle) object:nil];\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/ClayViewProvider.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h\"\n\n/**\n * A plugin to handle text input.\n *\n * Responsible for bridging the native macOS text input system with the Flutter framework text\n * editing classes.\n *\n * This is not an FlutterPlugin since it needs access to FlutterViewController internals, so needs\n * to be managed differently.\n *\n * When accessibility is on, accessibility bridge creates a NSTextField, i.e. FlutterTextField,\n * for every text field in the Flutter. This plugin acts as a field editor for those NSTextField[s].\n */\n@interface FlutterTextInputPlugin : NSTextView\n\n/**\n * Initializes a text input plugin that coordinates key event handling with |viewController|.\n */\n- (instancetype)initWithViewController:(FlutterViewController *)viewController;\n\n/**\n * Whether this plugin is the first responder of this NSWindow.\n *\n * When accessibility is on, this plugin is set as the first responder to act as the field\n * editor for FlutterTextFields.\n *\n * Returns false if accessibility is off.\n */\n- (BOOL)isFirstResponder;\n\n/**\n * Handles key down events received from the view controller, responding YES if\n * the event was handled.\n *\n * Note, the Apple docs suggest that clients should override essentially all the\n * mouse and keyboard event-handling methods of NSResponder. However, experimentation\n * indicates that only key events are processed by the native layer; Flutter processes\n * mouse events. Additionally, processing both keyUp and keyDown results in duplicate\n * processing of the same keys.\n */\n- (BOOL)handleKeyEvent:(NSEvent *)event;\n\n/**\n * Whether this plugin has composing text.\n *\n * This is only true when the text input plugin is actively taking user input with composing text.\n */\n// TODO (LongCatIsLooong): remove this method and implement a long-term fix for\n// https://github.com/flutter/flutter/issues/85328.\n- (BOOL)isComposing;\n\n- (instancetype)initWithClayProvider:(ClayViewProvider *)clayProvider;\n\n/**\n * Sets the text input client for the text input plugin.\n */\n- (void)setTextInputClient:(int)clientId\n               inputAction:(NSString *)inputAction\n                 inputType:(NSString *)inputType;\n\n/**\n * Clears the text input client for the text input plugin.\n */\n- (void)clearTextInputClient;\n\n/**\n * Sets the transform for the editable text area.\n */\n- (void)setEditableTransform:(NSArray *)matrix;\n/**\n * Sets the editing state for the text input plugin.\n */\n- (void)setEditingState:(NSDictionary *)state;\n/**\n * Updates the caret rect for the text input plugin.\n */\n- (void)updateCaretRect:(NSDictionary *)dictionary;\n\n- (void)showTextInput;\n- (void)hideTextInput;\n- (NSString *)filterInput:(NSString *)inputString withPattern:(NSString *)pattern;\n\n@end\n\n// Private methods made visible for testing\n@interface FlutterTextInputPlugin (TestMethods)\n- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange;\n- (NSDictionary *)editingState;\n@property(readwrite, nonatomic) NSString *customRunLoopMode;\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h\"\n#include <cstddef>\n#include \"clay/shell/platform/darwin/macos/framework/Headers/ClayViewProvider.h\"\n#include \"clay/shell/platform/darwin/macos/framework/Headers/FlutterView.h\"\n\n#import <Foundation/Foundation.h>\n#import <objc/message.h>\n\n#include <algorithm>\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/platform/darwin/string_range_sanitization.h\"\n#include \"clay/shell/platform/common/text_editing_delta.h\"\n#include \"clay/shell/platform/common/text_input_model.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h\"\n\n#include \"clay/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h\"\n\nstatic NSString* const kMultilineInputType = @\"TextInputType.multiline\";\nstatic NSString* const kTextAffinityDownstream = @\"TextAffinity.downstream\";\nstatic NSString* const kTextAffinityUpstream = @\"TextAffinity.upstream\";\nstatic NSString* const kSelectionBaseKey = @\"selectionBase\";\nstatic NSString* const kSelectionExtentKey = @\"selectionExtent\";\nstatic NSString* const kSelectionAffinityKey = @\"selectionAffinity\";\nstatic NSString* const kSelectionIsDirectionalKey = @\"selectionIsDirectional\";\nstatic NSString* const kComposingBaseKey = @\"composingBase\";\nstatic NSString* const kComposingExtentKey = @\"composingExtent\";\nstatic NSString* const kTextKey = @\"text\";\n\n/**\n * The affinity of the current cursor position. If the cursor is at a position representing\n * a line break, the cursor may be drawn either at the end of the current line (upstream)\n * or at the beginning of the next (downstream).\n */\ntypedef NS_ENUM(NSUInteger, FlutterTextAffinity) {\n  kFlutterTextAffinityUpstream,\n  kFlutterTextAffinityDownstream\n};\n\n/*\n * Updates a range given base and extent fields.\n */\nstatic clay::TextRange RangeFromBaseExtent(NSNumber* base, NSNumber* extent,\n                                           const clay::TextRange& range) {\n  if (base == nil || extent == nil) {\n    return range;\n  }\n  if (base.intValue == -1 && extent.intValue == -1) {\n    return clay::TextRange(0, 0);\n  }\n  return clay::TextRange([base unsignedLongValue], [extent unsignedLongValue]);\n}\n\n@interface NSEvent (KeyEquivalentMarker)\n\n// Internally marks that the event was received through performKeyEquivalent:.\n// When text editing is active, keyboard events that have modifier keys pressed\n// are received through performKeyEquivalent: instead of keyDown:. If such event\n// is passed to TextInputContext but doesn't result in a text editing action it\n// needs to be forwarded by FlutterKeyboardManager to the next responder.\n- (void)markAsKeyEquivalent;\n\n// Returns YES if the event is marked as a key equivalent.\n- (BOOL)isKeyEquivalent;\n\n@end\n\n@implementation NSEvent (KeyEquivalentMarker)\n\n// This field doesn't need a value because only its address is used as a unique identifier.\nstatic char markerKey;\n\n- (void)markAsKeyEquivalent {\n  objc_setAssociatedObject(self, &markerKey, @true, OBJC_ASSOCIATION_RETAIN);\n}\n\n- (BOOL)isKeyEquivalent {\n  return [objc_getAssociatedObject(self, &markerKey) boolValue] == YES;\n}\n\n@end\n\n/**\n * Private properties of FlutterTextInputPlugin.\n */\n@interface FlutterTextInputPlugin ()\n\n/**\n * A text input context, representing a connection to the Cocoa text input system.\n */\n@property(nonatomic) NSTextInputContext* textInputContext;\n\n/**\n * The FlutterViewController to manage input for.\n */\n@property(nonatomic, weak) FlutterViewController* flutterViewController;\n\n/**\n * Whether the text input is shown in the view.\n *\n * Defaults to TRUE on startup.\n */\n@property(nonatomic) BOOL shown;\n\n/**\n * The current state of the keyboard and pressed keys.\n */\n@property(nonatomic) uint64_t previouslyPressedFlags;\n\n/**\n * The affinity for the current cursor position.\n */\n@property FlutterTextAffinity textAffinity;\n\n/**\n * ID of the text input client.\n */\n@property(nonatomic, nonnull) NSNumber* clientID;\n\n/**\n * Keyboard type of the client. See available options:\n * https://api.flutter.dev/flutter/services/TextInputType-class.html\n */\n@property(nonatomic, nonnull) NSString* inputType;\n\n/**\n * An action requested by the user on the input client. See available options:\n * https://api.flutter.dev/flutter/services/TextInputAction-class.html\n */\n@property(nonatomic, nonnull) NSString* inputAction;\n\n/**\n * Set to true if the last event fed to the input context produced a text editing command\n * or text output. It is reset to false at the beginning of every key event, and is only\n * used while processing this event.\n */\n@property(nonatomic) BOOL eventProducedOutput;\n\n/**\n * Whether to enable the sending of text input updates from the engine to the\n * framework as TextEditingDeltas rather than as one TextEditingValue.\n * For more information on the delta model, see:\n * https://master-api.flutter.dev/flutter/services/TextInputConfiguration/enableDeltaModel.html\n */\n@property(nonatomic) BOOL enableDeltaModel;\n\n/**\n * Used to gather multiple selectors performed in one run loop turn. These\n * will be all sent in one platform channel call so that the framework can process\n * them in single microtask.\n */\n@property(nonatomic) NSMutableArray* pendingSelectors;\n\n/**\n * Updates the text input model with state received from the framework via the\n * TextInput.setEditingState message.\n */\n- (void)setEditingState:(NSDictionary*)state;\n\n/**\n * Informs the Flutter framework of changes to the text input model's state by\n * sending the entire new state.\n */\n- (void)updateEditState;\n\n/**\n * Informs the Flutter framework of changes to the text input model's state by\n * sending only the difference.\n */\n- (void)updateEditStateWithDelta:(const clay::TextEditingDelta)delta;\n\n/**\n * Updates the stringValue and selectedRange that stored in the NSTextView interface\n * that this plugin inherits from.\n *\n * If there is a FlutterTextField uses this plugin as its field editor, this method\n * will update the stringValue and selectedRange through the API of the FlutterTextField.\n */\n- (void)updateTextAndSelection;\n\n/**\n * Return the string representation of the current textAffinity.\n */\n- (NSString*)textAffinityString;\n\n@property(nonatomic, weak) FlutterEngine* flutterEngine;\n@property(nonatomic, weak) ClayViewProvider* clayProvider;\n\n/**\n * Allow overriding run loop mode for test.\n */\n@property(readwrite, nonatomic) NSString* customRunLoopMode;\n\n@end\n\n@implementation FlutterTextInputPlugin {\n  /**\n   * The currently active text input model.\n   */\n  std::unique_ptr<clay::TextInputModel> _activeModel;\n\n  /**\n   * Transform for current the editable. Used to determine position of accent selection menu.\n   */\n  CATransform3D _editableTransform;\n\n  /**\n   * Current position of caret in local (editable) coordinates.\n   */\n  CGRect _caretRect;\n}\n\n- (instancetype)initWithViewController:(FlutterViewController*)viewController {\n  // The view needs an empty frame otherwise it is visible on dark background.\n  // https://github.com/flutter/flutter/issues/118504\n  self = [super initWithFrame:NSZeroRect];\n  if (self != nil) {\n    _flutterViewController = viewController;\n    _flutterEngine = viewController.engine;\n    _shown = FALSE;\n    // NSTextView does not support _weak reference, so this class has to\n    // use __unsafe_unretained and manage the reference by itself.\n    //\n    // Since the dealloc removes the handler, the pointer should\n    // be valid if the handler is ever called.\n    __unsafe_unretained FlutterTextInputPlugin* unsafeSelf = self;\n    _textInputContext = [[NSTextInputContext alloc] initWithClient:unsafeSelf];\n    _previouslyPressedFlags = 0;\n\n    // Initialize with the zero matrix which is not\n    // an affine transform.\n    _editableTransform = CATransform3D();\n    _caretRect = CGRectNull;\n  }\n  return self;\n}\n\n- (BOOL)isFirstResponder {\n  if (!self.clayProvider.viewLoaded) {\n    return false;\n  }\n  return [self.clayProvider.flutterView.window firstResponder] == self;\n}\n\n- (void)dealloc {\n}\n\n- (instancetype)initWithClayProvider:(ClayViewProvider*)clayProvider {\n  // The view needs an empty frame otherwise it is visible on dark background.\n  // https://github.com/flutter/flutter/issues/118504\n  self = [super initWithFrame:NSZeroRect];\n  if (self != nil) {\n    _clayProvider = clayProvider;\n    _flutterEngine = clayProvider.engine;\n    _shown = FALSE;\n    // NSTextView does not support _weak reference, so this class has to\n    // use __unsafe_unretained and manage the reference by itself.\n    //\n    // Since the dealloc removes the handler, the pointer should\n    // be valid if the handler is ever called.\n    __unsafe_unretained FlutterTextInputPlugin* unsafeSelf = self;\n    _textInputContext = [[NSTextInputContext alloc] initWithClient:unsafeSelf];\n    _previouslyPressedFlags = 0;\n\n    // Initialize with the zero matrix which is not\n    // an affine transform.\n    _editableTransform = CATransform3D();\n    _caretRect = CGRectNull;\n  }\n  return self;\n}\n\n#pragma mark - Private\n\n- (void)resignAndRemoveFromSuperview {\n  if (self.superview != nil) {\n    // With accessiblity enabled TextInputPlugin is inside _client, so take the\n    // nextResponder from the _client.\n    NSResponder* nextResponder = self.nextResponder;\n    [self.window makeFirstResponder:nextResponder];\n    [self removeFromSuperview];\n  }\n}\n- (void)showTextInput {\n  // Ensure the plugin is in hierarchy. Only do this with accessibility disabled.\n  // When accessibility is enabled cocoa will reparent the plugin inside\n  // FlutterTextField in [FlutterTextField startEditing].\n  if (_flutterEngine && _flutterEngine.view != nil) {\n    [_flutterEngine.view addSubview:self];\n  }\n  [self.window makeFirstResponder:self];\n  _shown = TRUE;\n}\n\n- (void)hideTextInput {\n  [self resignAndRemoveFromSuperview];\n  _shown = FALSE;\n}\n\n- (NSString*)filterInput:(NSString*)inputString withPattern:(NSString*)pattern {\n  if (!inputString || !pattern) {\n    return inputString;\n  }\n  NSError* error = nil;\n  NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:pattern\n                                                                         options:0\n                                                                           error:&error];\n  if (error) {\n    const char* errorDescription = [error.localizedDescription UTF8String];\n    FML_LOG(ERROR) << \"FlutterTextInputPlugin error: \" << errorDescription;\n    return inputString;\n  }\n  NSString* filteredString =\n      [regex stringByReplacingMatchesInString:inputString\n                                      options:0\n                                        range:NSMakeRange(0, inputString.length)\n                                 withTemplate:@\"\"];\n  return filteredString;\n}\n\n- (void)setTextInputClient:(int)clientId\n               inputAction:(NSString*)inputAction\n                 inputType:(NSString*)inputType {\n  NSNumber* clientID = @(clientId);\n  if (clientID != nil) {\n    _clientID = clientID;\n    _inputAction = inputAction;\n    _enableDeltaModel = false;\n    _inputType = inputType;\n    self.textAffinity = kFlutterTextAffinityUpstream;\n\n    _activeModel = std::make_unique<clay::TextInputModel>();\n  }\n}\n\n- (void)clearTextInputClient {\n  [self resignAndRemoveFromSuperview];\n  // If there's an active mark region, commit it, end composing, and clear the IME's mark text.\n  if (_activeModel && _activeModel->composing()) {\n    _activeModel->CommitComposing();\n    _activeModel->EndComposing();\n  }\n  [_textInputContext discardMarkedText];\n\n  _clientID = nil;\n  _inputAction = nil;\n  _enableDeltaModel = NO;\n  _inputType = nil;\n  _activeModel = nullptr;\n}\n\n- (void)setEditableTransform:(NSArray*)matrix {\n  CATransform3D* transform = &_editableTransform;\n\n  transform->m11 = [matrix[0] doubleValue];\n  transform->m12 = [matrix[1] doubleValue];\n  transform->m13 = [matrix[2] doubleValue];\n  transform->m14 = [matrix[3] doubleValue];\n\n  transform->m21 = [matrix[4] doubleValue];\n  transform->m22 = [matrix[5] doubleValue];\n  transform->m23 = [matrix[6] doubleValue];\n  transform->m24 = [matrix[7] doubleValue];\n\n  transform->m31 = [matrix[8] doubleValue];\n  transform->m32 = [matrix[9] doubleValue];\n  transform->m33 = [matrix[10] doubleValue];\n  transform->m34 = [matrix[11] doubleValue];\n\n  transform->m41 = [matrix[12] doubleValue];\n  transform->m42 = [matrix[13] doubleValue];\n  transform->m43 = [matrix[14] doubleValue];\n  transform->m44 = [matrix[15] doubleValue];\n}\n\n- (void)updateCaretRect:(NSDictionary*)dictionary {\n  NSAssert(dictionary[@\"x\"] != nil && dictionary[@\"y\"] != nil && dictionary[@\"width\"] != nil &&\n               dictionary[@\"height\"] != nil,\n           @\"Expected a dictionary representing a CGRect, got %@\", dictionary);\n  _caretRect = CGRectMake([dictionary[@\"x\"] doubleValue], [dictionary[@\"y\"] doubleValue],\n                          [dictionary[@\"width\"] doubleValue], [dictionary[@\"height\"] doubleValue]);\n}\n\n- (void)setEditingState:(NSDictionary*)state {\n  NSString* selectionAffinity = state[kSelectionAffinityKey];\n  if (selectionAffinity != nil) {\n    _textAffinity = [selectionAffinity isEqualToString:kTextAffinityUpstream]\n                        ? kFlutterTextAffinityUpstream\n                        : kFlutterTextAffinityDownstream;\n  }\n\n  NSString* text = state[kTextKey];\n\n  clay::TextRange selected_range = RangeFromBaseExtent(\n      state[kSelectionBaseKey], state[kSelectionExtentKey], _activeModel->selection());\n\n  clay::TextRange composing_range = RangeFromBaseExtent(\n      state[kComposingBaseKey], state[kComposingExtentKey], _activeModel->composing_range());\n  if (composing_range.collapsed()) {\n    _activeModel->EndComposing();\n  }\n\n  const bool wasComposing = _activeModel->composing();\n  _activeModel->SetText([text UTF8String], selected_range, composing_range);\n  if (composing_range.collapsed() && wasComposing) {\n    [_textInputContext discardMarkedText];\n  }\n\n  [self updateTextAndSelection];\n}\n\n- (NSDictionary*)editingState {\n  if (_activeModel == nullptr) {\n    return nil;\n  }\n\n  NSString* const textAffinity = [self textAffinityString];\n\n  int composingBase = _activeModel->composing() ? _activeModel->composing_range().base() : -1;\n  int composingExtent = _activeModel->composing() ? _activeModel->composing_range().extent() : -1;\n\n  return @{\n    kSelectionBaseKey : @(_activeModel->selection().base()),\n    kSelectionExtentKey : @(_activeModel->selection().extent()),\n    kSelectionAffinityKey : textAffinity,\n    kSelectionIsDirectionalKey : @NO,\n    kComposingBaseKey : @(composingBase),\n    kComposingExtentKey : @(composingExtent),\n    kTextKey : [NSString stringWithUTF8String:_activeModel->GetText().c_str()]\n  };\n}\n\n- (void)updateEditState {\n  if (_activeModel == nullptr) {\n    return;\n  }\n\n  NSDictionary* state = [self editingState];\n  NSString* text = state[kTextKey];\n  NSNumber* selectionBase = state[kSelectionBaseKey];\n  NSNumber* selectionExtent = state[kSelectionExtentKey];\n  NSString* selectionAffinity = state[kSelectionAffinityKey];\n  NSNumber* composingBase = state[kComposingBaseKey];\n  NSNumber* composingExtent = state[kComposingExtentKey];\n\n  [_flutterEngine updateEditState:_clientID.intValue\n                    selectionBase:selectionBase.unsignedLongLongValue\n                  composingExtent:composingExtent.unsignedLongLongValue\n                selectionAffinity:selectionAffinity ? selectionAffinity.UTF8String : \"\"\n                             text:text ? text.UTF8String : \"\"\n                  selectionExtent:selectionExtent.unsignedLongLongValue\n                    composingBase:composingBase.unsignedLongLongValue];\n\n  [self updateTextAndSelection];\n}\n\n- (void)updateEditStateWithDelta:(const clay::TextEditingDelta)delta {\n  [self updateTextAndSelection];\n}\n\n- (void)updateTextAndSelection {\n  NSAssert(_activeModel != nullptr, @\"Flutter text model must not be null.\");\n  NSString* text = @(_activeModel->GetText().data());\n  int start = _activeModel->selection().base();\n  int extend = _activeModel->selection().extent();\n  NSRange selection = NSMakeRange(MIN(start, extend), ABS(start - extend));\n  // There may be a native text field client if VoiceOver is on.\n  // In this case, this plugin has to update text and selection through\n  // the client in order for VoiceOver to announce the text editing\n  // properly.\n  self.string = text;\n  [self setSelectedRange:selection];\n}\n\n- (NSString*)textAffinityString {\n  return (self.textAffinity == kFlutterTextAffinityUpstream) ? kTextAffinityUpstream\n                                                             : kTextAffinityDownstream;\n}\n\n- (BOOL)handleKeyEvent:(NSEvent*)event {\n  if (event.type == NSEventTypeKeyUp ||\n      (event.type == NSEventTypeFlagsChanged && event.modifierFlags < _previouslyPressedFlags)) {\n    return NO;\n  }\n  _previouslyPressedFlags = event.modifierFlags;\n  if (!_shown) {\n    return NO;\n  }\n\n  _eventProducedOutput = NO;\n  BOOL res = [_textInputContext handleEvent:event];\n  // NSTextInputContext#handleEvent returns YES if the context handles the event. One of the reasons\n  // the event is handled is because it's a key equivalent. But a key equivalent might produce a\n  // text command (indicated by calling doCommandBySelector) or might not (for example, Cmd+Q). In\n  // the latter case, this command somehow has not been executed yet and Flutter must dispatch it to\n  // the next responder. See https://github.com/flutter/flutter/issues/106354 .\n  if (event.isKeyEquivalent && !_eventProducedOutput) {\n    return NO;\n  }\n  return res;\n}\n\n- (BOOL)isComposing {\n  return _activeModel && !_activeModel->composing_range().collapsed();\n}\n\n#pragma mark -\n#pragma mark NSResponder\n\n- (void)keyDown:(NSEvent*)event {\n  [self.clayProvider keyDown:event];\n}\n\n- (void)keyUp:(NSEvent*)event {\n  [self.clayProvider keyUp:event];\n}\n\n- (BOOL)performKeyEquivalent:(NSEvent*)event {\n  if ([_clayProvider isDispatchingKeyEvent:event]) {\n    // When NSWindow is nextResponder, keyboard manager will send to it\n    // unhandled events (through [NSWindow keyDown:]). If event has has both\n    // control and cmd modifiers set (i.e. cmd+control+space - emoji picker)\n    // NSWindow will then send this event as performKeyEquivalent: to first\n    // responder, which is FlutterTextInputPlugin. If that's the case, the\n    // plugin must not handle the event, otherwise the emoji picker would not\n    // work (due to first responder returning YES from performKeyEquivalent:)\n    // and there would be endless loop, because FlutterViewController will\n    // send the event back to [keyboardManager handleEvent:].\n    return NO;\n  }\n  [event markAsKeyEquivalent];\n  [self.clayProvider keyDown:event];\n  return YES;\n}\n\n- (void)flagsChanged:(NSEvent*)event {\n  [self.clayProvider flagsChanged:event];\n}\n\n- (void)mouseDown:(NSEvent*)event {\n  [self.clayProvider mouseDown:event];\n}\n\n- (void)mouseUp:(NSEvent*)event {\n  [self.clayProvider mouseUp:event];\n}\n\n- (void)mouseDragged:(NSEvent*)event {\n  [self.clayProvider mouseDragged:event];\n}\n\n- (void)rightMouseDown:(NSEvent*)event {\n  [self.clayProvider rightMouseDown:event];\n}\n\n- (void)rightMouseUp:(NSEvent*)event {\n  [self.clayProvider rightMouseUp:event];\n}\n\n- (void)rightMouseDragged:(NSEvent*)event {\n  [self.clayProvider rightMouseDragged:event];\n}\n\n- (void)otherMouseDown:(NSEvent*)event {\n  [self.clayProvider otherMouseDown:event];\n}\n\n- (void)otherMouseUp:(NSEvent*)event {\n  [self.clayProvider otherMouseUp:event];\n}\n\n- (void)otherMouseDragged:(NSEvent*)event {\n  [self.clayProvider otherMouseDragged:event];\n}\n\n- (void)mouseMoved:(NSEvent*)event {\n  [self.clayProvider mouseMoved:event];\n}\n\n- (void)scrollWheel:(NSEvent*)event {\n  [self.clayProvider scrollWheel:event];\n}\n\n- (NSTextInputContext*)inputContext {\n  return _textInputContext;\n}\n\n#pragma mark -\n#pragma mark NSTextInputClient\n\n- (void)insertTab:(id)sender {\n  // Implementing insertTab: makes AppKit send tab as command, instead of\n  // insertText with '\\t'.\n}\n\n- (void)insertText:(id)string replacementRange:(NSRange)range {\n  if (_activeModel == nullptr) {\n    return;\n  }\n\n  _eventProducedOutput |= true;\n\n  if (range.location != NSNotFound) {\n    // The selected range can actually have negative numbers, since it can start\n    // at the end of the range if the user selected the text going backwards.\n    // Cast to a signed type to determine whether or not the selection is reversed.\n    long signedLength = static_cast<long>(range.length);\n    long location = range.location;\n    long textLength = _activeModel->text_range().end();\n\n    size_t base = std::clamp(location, 0L, textLength);\n    size_t extent = std::clamp(location + signedLength, 0L, textLength);\n\n    _activeModel->SetSelection(clay::TextRange(base, extent));\n  }\n\n  clay::TextRange oldSelection = _activeModel->selection();\n  clay::TextRange composingBeforeChange = _activeModel->composing_range();\n  clay::TextRange replacedRange(-1, -1);\n\n  std::string textBeforeChange = _activeModel->GetText().c_str();\n  std::string utf8String = [string UTF8String];\n  _activeModel->AddText(utf8String);\n  if (_activeModel->composing()) {\n    replacedRange = composingBeforeChange;\n    _activeModel->CommitComposing();\n    _activeModel->EndComposing();\n  } else {\n    replacedRange = range.location == NSNotFound\n                        ? clay::TextRange(oldSelection.base(), oldSelection.extent())\n                        : clay::TextRange(range.location, range.location + range.length);\n  }\n  if (_enableDeltaModel) {\n    [self updateEditStateWithDelta:clay::TextEditingDelta(textBeforeChange, replacedRange,\n                                                          utf8String)];\n  } else {\n    [self updateEditState];\n  }\n}\n\n- (void)doCommandBySelector:(SEL)selector {\n  _eventProducedOutput |= selector != NSSelectorFromString(@\"noop:\");\n  if ([self respondsToSelector:selector]) {\n    // Note: The more obvious [self performSelector...] doesn't give ARC enough information to\n    // handle retain semantics properly. See https://stackoverflow.com/questions/7017281/ for more\n    // information.\n    IMP imp = [self methodForSelector:selector];\n    void (*func)(id, SEL, id) = reinterpret_cast<void (*)(id, SEL, id)>(imp);\n    func(self, selector, nil);\n  }\n\n  if (selector == @selector(insertNewline:)) {\n    // Already handled through text insertion (multiline) or action.\n    return;\n  }\n\n  // Group multiple selectors received within a single run loop turn so that\n  // the framework can process them in single microtask.\n  NSString* name = NSStringFromSelector(selector);\n  if (_pendingSelectors == nil) {\n    _pendingSelectors = [NSMutableArray array];\n  }\n  [_pendingSelectors addObject:name];\n\n  if (_pendingSelectors.count == 1) {\n    __weak NSMutableArray* selectors = _pendingSelectors;\n\n    CFStringRef runLoopMode = self.customRunLoopMode != nil\n                                  ? (__bridge CFStringRef)self.customRunLoopMode\n                                  : kCFRunLoopCommonModes;\n\n    CFRunLoopPerformBlock(CFRunLoopGetMain(), runLoopMode, ^{\n      if (selectors.count > 0) {\n        [selectors removeAllObjects];\n      }\n    });\n  }\n}\n\n- (void)insertNewline:(id)sender {\n  if (_activeModel == nullptr) {\n    return;\n  }\n  if (_activeModel->composing()) {\n    _activeModel->CommitComposing();\n    _activeModel->EndComposing();\n  }\n  if ([self.inputType isEqualToString:kMultilineInputType]) {\n    [self insertText:@\"\\n\" replacementRange:self.selectedRange];\n  }\n  [_flutterEngine performInputAction:(self.clientID.intValue)];\n}\n\n- (void)setMarkedText:(id)string\n        selectedRange:(NSRange)selectedRange\n     replacementRange:(NSRange)replacementRange {\n  if (_activeModel == nullptr) {\n    return;\n  }\n  std::string textBeforeChange = _activeModel->GetText().c_str();\n  if (!_activeModel->composing()) {\n    _activeModel->BeginComposing();\n  }\n\n  if (replacementRange.location != NSNotFound) {\n    // According to the NSTextInputClient documentation replacementRange is\n    // computed from the beginning of the marked text. That doesn't seem to be\n    // the case, because in situations where the replacementRange is actually\n    // specified (i.e. when switching between characters equivalent after long\n    // key press) the replacementRange is provided while there is no composition.\n    _activeModel->SetComposingRange(\n        clay::TextRange(replacementRange.location,\n                        replacementRange.location + replacementRange.length),\n        0);\n  }\n\n  clay::TextRange composingBeforeChange = _activeModel->composing_range();\n  clay::TextRange selectionBeforeChange = _activeModel->selection();\n\n  // Input string may be NSString or NSAttributedString.\n  BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];\n  std::string marked_text = isAttributedString ? [[string string] UTF8String] : [string UTF8String];\n  _activeModel->UpdateComposingText(marked_text);\n\n  // Update the selection within the marked text.\n  long signedLength = static_cast<long>(selectedRange.length);\n  long location = selectedRange.location + _activeModel->composing_range().base();\n  long textLength = _activeModel->text_range().end();\n\n  size_t base = std::clamp(location, 0L, textLength);\n  size_t extent = std::clamp(location + signedLength, 0L, textLength);\n  _activeModel->SetSelection(clay::TextRange(base, extent));\n\n  if (_enableDeltaModel) {\n    [self updateEditStateWithDelta:clay::TextEditingDelta(textBeforeChange,\n                                                          selectionBeforeChange.collapsed()\n                                                              ? composingBeforeChange\n                                                              : selectionBeforeChange,\n                                                          marked_text)];\n  } else {\n    [self updateEditState];\n  }\n}\n\n- (void)unmarkText {\n  if (_activeModel == nullptr) {\n    return;\n  }\n  _activeModel->CommitComposing();\n  _activeModel->EndComposing();\n  if (_enableDeltaModel) {\n    [self updateEditStateWithDelta:clay::TextEditingDelta(_activeModel->GetText().c_str())];\n  } else {\n    [self updateEditState];\n  }\n}\n\n- (NSRange)markedRange {\n  if (_activeModel == nullptr) {\n    return NSMakeRange(NSNotFound, 0);\n  }\n  return NSMakeRange(\n      _activeModel->composing_range().base(),\n      _activeModel->composing_range().extent() - _activeModel->composing_range().base());\n}\n\n- (BOOL)hasMarkedText {\n  return _activeModel != nullptr && _activeModel->composing_range().length() > 0;\n}\n\n- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range\n                                               actualRange:(NSRangePointer)actualRange {\n  if (_activeModel == nullptr) {\n    return nil;\n  }\n  if (actualRange != nil) {\n    *actualRange = range;\n  }\n  NSString* text = [NSString stringWithUTF8String:_activeModel->GetText().c_str()];\n  NSString* substring = [text substringWithRange:range];\n  return [[NSAttributedString alloc] initWithString:substring attributes:nil];\n}\n\n- (NSArray<NSString*>*)validAttributesForMarkedText {\n  return @[];\n}\n\n// Returns the bounding CGRect of the transformed incomingRect, in screen\n// coordinates.\n- (CGRect)screenRectFromFrameworkTransform:(CGRect)incomingRect {\n  CGPoint points[] = {\n      incomingRect.origin,\n      CGPointMake(incomingRect.origin.x, incomingRect.origin.y + incomingRect.size.height),\n      CGPointMake(incomingRect.origin.x + incomingRect.size.width, incomingRect.origin.y),\n      CGPointMake(incomingRect.origin.x + incomingRect.size.width,\n                  incomingRect.origin.y + incomingRect.size.height)};\n\n  CGPoint origin = CGPointMake(CGFLOAT_MAX, CGFLOAT_MAX);\n  CGPoint farthest = CGPointMake(-CGFLOAT_MAX, -CGFLOAT_MAX);\n\n  for (int i = 0; i < 4; i++) {\n    const CGPoint point = points[i];\n\n    CGFloat x = _editableTransform.m11 * point.x + _editableTransform.m21 * point.y +\n                _editableTransform.m41;\n    CGFloat y = _editableTransform.m12 * point.x + _editableTransform.m22 * point.y +\n                _editableTransform.m42;\n\n    const CGFloat w = _editableTransform.m14 * point.x + _editableTransform.m24 * point.y +\n                      _editableTransform.m44;\n\n    if (w == 0.0) {\n      return CGRectZero;\n    } else if (w != 1.0) {\n      x /= w;\n      y /= w;\n    }\n\n    origin.x = MIN(origin.x, x);\n    origin.y = MIN(origin.y, y);\n    farthest.x = MAX(farthest.x, x);\n    farthest.y = MAX(farthest.y, y);\n  }\n  const NSView* fromView = _clayProvider.flutterView;\n  const CGRect rectInWindow = [fromView\n      convertRect:CGRectMake(origin.x, origin.y, farthest.x - origin.x, farthest.y - origin.y)\n           toView:nil];\n  NSWindow* window = fromView.window;\n  return window ? [window convertRectToScreen:rectInWindow] : rectInWindow;\n}\n\n- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange {\n  // This only determines position of caret instead of any arbitrary range, but it's enough\n  // to properly position accent selection popup\n  // if (!self.clayProvider.viewLoaded || CGRectEqualToRect(_caretRect, CGRectNull)) {\n  //   return CGRectZero;\n  // }\n  // CGRect rect = [self screenRectFromFrameworkTransform:_caretRect];\n  // double viewHeight = self.clayProvider.flutterView.frame.size.height;\n  // rect.origin.y = viewHeight - rect.origin.y - rect.size.height;\n  // return [self.clayProvider.flutterView.window convertRectToScreen:rect];\n  return [self screenRectFromFrameworkTransform:_caretRect];\n}\n\n- (NSUInteger)characterIndexForPoint:(NSPoint)point {\n  // TODO(cbracken): Implement.\n  // Note: This function can't easily be implemented under the system-message architecture.\n  return 0;\n}\n\n// Override method to avoid drawing cursor\n- (void)drawInsertionPointInRect:(NSRect)rect color:(NSColor*)color turnedOn:(BOOL)flag {\n}\n\n// This method needs to be overridden to ensure that the cursor is not redrawn when the text\n// changes\n- (void)setNeedsDisplayInRect:(NSRect)invalidRect {\n}\n\n// Set isEditable to NO to disable editing and cursor drawing.\n- (BOOL)isEditable {\n  return NO;\n}\n\n// Setting isSelectable to NO disables selection and cursor display.\n- (BOOL)isSelectable {\n  return NO;\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#import <Cocoa/Cocoa.h>\n\n/**\n * Takes care of synchronization between raster and platform thread.\n */\n@interface FlutterThreadSynchronizer : NSObject\n\n/**\n * Set whether the synchronizer is visible.\n * If not visible, the synchronizer will not block.\n */\n- (void)setVisible:(BOOL)visible;\n\n/**\n * Blocks current thread until there is frame available.\n * Used in FlutterEngineTest.\n */\n- (void)blockUntilFrameAvailable;\n\n/**\n * Called from platform thread. Blocks until commit with given size (or empty)\n * is requested.\n */\n- (void)beginResize:(CGSize)size notify:(nonnull dispatch_block_t)notify;\n\n/**\n * Called from raster thread. Schedules the given block on platform thread\n * and blocks until it is performed.\n *\n * If platform thread is blocked in `beginResize:` for given size (or size is empty),\n * unblocks platform thread.\n *\n * The notify block is guaranteed to be called within a core animation transaction.\n */\n- (void)performCommit:(CGSize)size notify:(nonnull dispatch_block_t)notify;\n\n/**\n * Called when shutting down. Unblocks everything and prevents any further synchronization.\n */\n- (void)shutdown;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h\"\n\n#import <QuartzCore/QuartzCore.h>\n\n#include <mutex>\n#include <vector>\n\n#import \"base/include/fml/synchronization/waitable_event.h\"\n#import \"clay/fml/logging.h\"\n\n@interface FlutterThreadSynchronizer () {\n  std::mutex _mutex;\n  BOOL _shuttingDown;\n  CGSize _contentSize;\n  std::vector<dispatch_block_t> _scheduledBlocks;\n\n  BOOL _beginResizeWaiting;\n\n  // Used to block [beginResize:].\n  std::condition_variable _condBlockBeginResize;\n\n  BOOL _visible;\n}\n\n@end\n\n@implementation FlutterThreadSynchronizer\n\n- (instancetype)init {\n  self = [super init];\n  if (self) {\n    _visible = YES;\n  }\n  return self;\n}\n\n- (void)drain {\n  FML_DCHECK([NSThread isMainThread]);\n\n  [CATransaction begin];\n  [CATransaction setDisableActions:YES];\n  for (dispatch_block_t block : _scheduledBlocks) {\n    block();\n  }\n  [CATransaction commit];\n  _scheduledBlocks.clear();\n}\n\n- (void)setVisible:(BOOL)visible {\n  _visible = visible;\n}\n\n- (void)blockUntilFrameAvailable {\n  std::unique_lock<std::mutex> lock(_mutex);\n\n  _beginResizeWaiting = YES;\n\n  while (CGSizeEqualToSize(_contentSize, CGSizeZero) && !_shuttingDown) {\n    _condBlockBeginResize.wait(lock);\n    [self drain];\n  }\n\n  _beginResizeWaiting = NO;\n}\n\n- (void)beginResize:(CGSize)size notify:(nonnull dispatch_block_t)notify {\n  std::unique_lock<std::mutex> lock(_mutex);\n\n  if (CGSizeEqualToSize(_contentSize, CGSizeZero) || _shuttingDown) {\n    // No blocking until framework produces at least one frame\n    notify();\n    return;\n  }\n  if (!_visible) {\n    // Synchronizer is not visible, no need to block.\n    notify();\n    return;\n  }\n\n  [self drain];\n\n  notify();\n\n  _contentSize = CGSizeMake(-1, -1);\n\n  _beginResizeWaiting = YES;\n\n  while (!CGSizeEqualToSize(_contentSize, size) &&  //\n         !CGSizeEqualToSize(_contentSize, CGSizeZero) && !_shuttingDown) {\n    _condBlockBeginResize.wait(lock);\n    [self drain];\n  }\n\n  _beginResizeWaiting = NO;\n}\n\n- (void)performCommit:(CGSize)size notify:(nonnull dispatch_block_t)notify {\n  fml::AutoResetWaitableEvent event;\n  {\n    std::unique_lock<std::mutex> lock(_mutex);\n    if (_shuttingDown) {\n      // Engine is shutting down, main thread may be blocked by the engine\n      // waiting for raster thread to finish.\n      return;\n    }\n    fml::AutoResetWaitableEvent& e = event;\n    _scheduledBlocks.push_back(^{\n      notify();\n      _contentSize = size;\n      e.Signal();\n    });\n    if (_beginResizeWaiting) {\n      _condBlockBeginResize.notify_all();\n    } else {\n      dispatch_async(dispatch_get_main_queue(), ^{\n        std::unique_lock<std::mutex> lock(_mutex);\n        [self drain];\n      });\n    }\n  }\n  event.Wait();\n}\n\n- (void)shutdown {\n  std::unique_lock<std::mutex> lock(_mutex);\n  _shuttingDown = YES;\n  _condBlockBeginResize.notify_all();\n  [self drain];\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterView.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterView.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h\"\n\n#include <stdint.h>\n\n@class ClayViewProvider;\n\n/**\n * The view ID for APIs that don't support multi-view.\n *\n * Some single-view APIs will eventually be replaced by their multi-view\n * variant. During the deprecation period, the single-view APIs will coexist with\n * and work with the multi-view APIs as if the other views don't exist.  For\n * backward compatibility, single-view APIs will always operate the view with\n * this ID. Also, the first view assigned to the engine will also have this ID.\n */\nconstexpr uint64_t kFlutterDefaultViewId = 0;\n\n/**\n * Listener for view resizing.\n */\n@protocol FlutterViewReshapeListener <NSObject>\n/**\n * Called when the view's backing store changes size.\n */\n- (void)viewDidReshape:(nonnull NSView*)view;\n@end\n\n/**\n * View capable of acting as a rendering target and input source for the Flutter\n * engine.\n */\n@interface FlutterView ()\n\n/**\n * Initialize a FlutterView that will be rendered to using Metal rendering apis.\n */\n- (nullable instancetype)initWithMTLDevice:(nonnull id<MTLDevice>)device\n                              commandQueue:(nonnull id<MTLCommandQueue>)commandQueue\n                           reshapeListener:(nonnull id<FlutterViewReshapeListener>)reshapeListener\n    NS_DESIGNATED_INITIALIZER;\n\n- (nullable instancetype)initWithFrame:(NSRect)frameRect\n                           pixelFormat:(nullable NSOpenGLPixelFormat*)format NS_UNAVAILABLE;\n- (nonnull instancetype)initWithFrame:(NSRect)frameRect NS_UNAVAILABLE;\n- (nullable instancetype)initWithCoder:(nonnull NSCoder*)coder NS_UNAVAILABLE;\n- (nonnull instancetype)init NS_UNAVAILABLE;\n\n/**\n * Returns SurfaceManager for this view. SurfaceManager is responsible for\n * providing and presenting render surfaces.\n */\n@property(readonly, nonatomic, nonnull) FlutterSurfaceManager* surfaceManager;\n\n@property(nonatomic, nullable, weak) ClayViewProvider* provider;\n\n/**\n * Must be called when shutting down. Unblocks raster thread and prevents any further\n * synchronization.\n */\n- (void)shutdown;\n\n/**\n * By default, the `FlutterSurfaceManager` creates two layers to manage Flutter\n * content, the content layer and containing layer. To set the native background\n * color, onto which the Flutter content is drawn, call this method with the\n * NSColor which you would like to override the default, black background color\n * with.\n */\n- (void)setBackgroundColor:(nonnull NSColor*)color;\n\n@end\n\n@interface FlutterView (FlutterViewPrivate)\n\n/**\n * Returns FlutterThreadSynchronizer for this view.\n * Used for FlutterEngineTest.\n */\n- (nonnull FlutterThreadSynchronizer*)threadSynchronizer;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterView.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterView.h\"\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/ClayViewProvider_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h\"\n\n#import <QuartzCore/QuartzCore.h>\n\n@interface FlutterView () <FlutterSurfaceManagerDelegate> {\n  __weak id<FlutterViewReshapeListener> _reshapeListener;\n  FlutterThreadSynchronizer* _threadSynchronizer;\n  FlutterSurfaceManager* _surfaceManager;\n}\n\n@end\n\n@implementation FlutterView\n\n- (instancetype)initWithMTLDevice:(id<MTLDevice>)device\n                     commandQueue:(id<MTLCommandQueue>)commandQueue\n                  reshapeListener:(id<FlutterViewReshapeListener>)reshapeListener {\n  self = [super initWithFrame:NSZeroRect];\n  if (self) {\n    [self setWantsLayer:YES];\n    [self setBackgroundColor:[NSColor clearColor]];\n    [self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawDuringViewResize];\n    _reshapeListener = reshapeListener;\n    _threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];\n    _surfaceManager = [[FlutterSurfaceManager alloc] initWithDevice:device\n                                                       commandQueue:commandQueue\n                                                              layer:self.layer\n                                                           delegate:self];\n    NSArray* allowedTypes = @[ NSFilenamesPboardType, NSPasteboardTypeString ];\n    [self registerForDraggedTypes:allowedTypes];\n  }\n  return self;\n}\n\n- (void)onPresent:(CGSize)frameSize withBlock:(dispatch_block_t)block {\n  [_threadSynchronizer performCommit:frameSize notify:block];\n}\n\n- (FlutterSurfaceManager*)surfaceManager {\n  return _surfaceManager;\n}\n\n- (FlutterThreadSynchronizer*)threadSynchronizer {\n  return _threadSynchronizer;\n}\n\n- (void)reshaped {\n  if ([self isHidden]) {\n    return;\n  }\n  CGSize scaledSize = [self convertSizeToBacking:self.bounds.size];\n  [_threadSynchronizer beginResize:scaledSize\n                            notify:^{\n                              [_reshapeListener viewDidReshape:self];\n                            }];\n}\n\n- (void)setBackgroundColor:(NSColor*)color {\n  self.layer.backgroundColor = color.CGColor;\n}\n\n#pragma mark - NSView overrides\n\n- (void)setFrameSize:(NSSize)newSize {\n  [super setFrameSize:newSize];\n  [self reshaped];\n}\n\n/**\n * Declares that the view uses a flipped coordinate system, consistent with Flutter conventions.\n */\n- (BOOL)isFlipped {\n  return YES;\n}\n\n- (BOOL)isOpaque {\n  return YES;\n}\n\n/**\n * Declares that the initial mouse-down when the view is not in focus will send an event to the\n * view.\n */\n- (BOOL)acceptsFirstMouse:(NSEvent*)event {\n  return YES;\n}\n\n- (BOOL)acceptsFirstResponder {\n  return YES;\n}\n\n- (void)cursorUpdate:(NSEvent*)event {\n  // When adding/removing views AppKit will schedule call to current hit-test view\n  // cursorUpdate: at the end of frame to determine possible cursor change. If\n  // the view doesn't implement cursorUpdate: AppKit will set the default (arrow) cursor\n  // instead. This would replace the cursor set by FlutterMouseCursorPlugin.\n  // Empty cursorUpdate: implementation prevents this behavior.\n  // https://github.com/flutter/flutter/issues/111425\n}\n\n- (void)viewDidChangeBackingProperties {\n  [super viewDidChangeBackingProperties];\n  // Force redraw\n  [_reshapeListener viewDidReshape:self];\n}\n\n- (BOOL)layer:(CALayer*)layer\n    shouldInheritContentsScale:(CGFloat)newScale\n                    fromWindow:(NSWindow*)window {\n  return YES;\n}\n\n- (void)shutdown {\n  [_threadSynchronizer shutdown];\n}\n#pragma mark - NSAccessibility overrides\n\n- (BOOL)isAccessibilityElement {\n  return YES;\n}\n\n- (NSAccessibilityRole)accessibilityRole {\n  return NSAccessibilityGroupRole;\n}\n\n- (NSString*)accessibilityLabel {\n  // TODO(chunhtai): Provides a way to let developer customize the accessibility\n  // label.\n  // https://github.com/flutter/flutter/issues/75446\n  NSString* applicationName =\n      [NSBundle.mainBundle objectForInfoDictionaryKey:@\"CFBundleDisplayName\"];\n  if (!applicationName) {\n    applicationName = [NSBundle.mainBundle objectForInfoDictionaryKey:@\"CFBundleName\"];\n  }\n  return applicationName;\n}\n\n#pragma mark - NSResponder\n\n#define DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(METHOD) \\\n  self.provider ? [self.provider METHOD:event] : [super METHOD:event]\n\n// Forward NSResponder events to provider.\n- (void)mouseDown:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(mouseDown);\n}\n\n- (void)rightMouseDown:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(rightMouseDown);\n}\n\n- (void)otherMouseDown:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(otherMouseDown);\n}\n\n- (void)mouseUp:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(mouseUp);\n}\n\n- (void)rightMouseUp:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(rightMouseUp);\n}\n\n- (void)otherMouseUp:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(otherMouseUp);\n}\n\n- (void)mouseMoved:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(mouseMoved);\n}\n\n- (void)mouseDragged:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(mouseDragged);\n}\n\n- (void)scrollWheel:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(scrollWheel);\n}\n\n- (void)magnifyWithEvent:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(magnifyWithEvent);\n}\n\n- (void)rightMouseDragged:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(rightMouseDragged);\n}\n\n- (void)otherMouseDragged:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(otherMouseDragged);\n}\n\n- (void)mouseEntered:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(mouseEntered);\n}\n\n- (void)mouseExited:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(mouseExited);\n}\n\n- (void)keyDown:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(keyDown);\n}\n\n- (void)keyUp:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(keyUp);\n}\n\n- (void)flagsChanged:(NSEvent*)event {\n  DELEGATE_EVENT_TO_PROVIDER_OR_SUPER(flagsChanged);\n}\n\n#undef DELEGATE_EVENT_TO_PROVIDER_OR_SUPER\n\n- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {\n  if (self.provider) {\n    NSPoint locationInWindow = [self convertPoint:[sender draggingLocation] fromView:nil];\n    CGFloat scale = self.window.backingScaleFactor;\n    NSPoint dropPoint = NSMakePoint(locationInWindow.x * scale, locationInWindow.y * scale);\n    [self.provider performMouseDragEnterAndOverAtPoint:dropPoint];\n  }\n  if ([sender draggingSourceOperationMask] & NSDragOperationCopy) {\n    if ([sender draggingPasteboard] == [NSPasteboard pasteboardWithName:NSDragPboard]) {\n      return NSDragOperationCopy;\n    } else {\n      return NSDragOperationGeneric;\n    }\n  }\n  return NSDragOperationNone;\n}\n\n- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender {\n  if (self.provider) {\n    NSPoint locationInWindow = [self convertPoint:[sender draggingLocation] fromView:nil];\n    CGFloat scale = self.window.backingScaleFactor;\n    NSPoint dropPoint = NSMakePoint(locationInWindow.x * scale, locationInWindow.y * scale);\n    [self.provider performMouseDragEnterAndOverAtPoint:dropPoint];\n  }\n  if ([sender draggingSourceOperationMask] & NSDragOperationCopy) {\n    if ([sender draggingPasteboard] == [NSPasteboard pasteboardWithName:NSDragPboard]) {\n      return NSDragOperationCopy;\n    } else {\n      return NSDragOperationGeneric;\n    }\n  }\n  return NSDragOperationNone;\n}\n\n- (void)draggingExited:(id<NSDraggingInfo>)sender {\n  if (self.provider) {\n    [self.provider performMouseDragLeave];\n  }\n}\n\n- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {\n  if (!self.provider) {\n    return NO;\n  }\n  NSPoint locationInWindow = [self convertPoint:[sender draggingLocation] fromView:nil];\n  CGFloat scale = self.window.backingScaleFactor;\n  NSPoint dropPoint = NSMakePoint(locationInWindow.x * scale, locationInWindow.y * scale);\n  NSPasteboard* pasteboard = [sender draggingPasteboard];\n  NSArray* urls = [pasteboard readObjectsForClasses:@[ [NSURL class] ] options:nil];\n  NSString* text = [pasteboard stringForType:NSPasteboardTypeString];\n  if ([urls count] > 0) {\n    NSMutableArray* pathArray = [NSMutableArray array];\n    for (NSURL* url in urls) {\n      NSString* filePath = url.path;\n      NSDictionary* fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath\n                                                                                      error:nil];\n      if (fileAttributes) {\n        NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];\n        dateFormatter.dateStyle = NSDateFormatterMediumStyle;\n        dateFormatter.timeStyle = NSDateFormatterMediumStyle;\n\n        NSString* fileName = [filePath lastPathComponent];\n        NSString* fileType = [fileAttributes objectForKey:NSFileType];\n        NSNumber* fileSize = [fileAttributes objectForKey:NSFileSize];\n        NSDate* fileModificationDate = [fileAttributes objectForKey:NSFileModificationDate];\n        NSString* fileModificationDateString = [dateFormatter stringFromDate:fileModificationDate];\n        NSMutableDictionary* file_dictionary = [NSMutableDictionary dictionary];\n        [file_dictionary setObject:filePath forKey:kDragDropPathKey];\n        [file_dictionary setObject:fileName forKey:kDragDropNameKey];\n        [file_dictionary setObject:fileType forKey:kDragDropTypeKey];\n        [file_dictionary setObject:fileSize forKey:kDragDropSizeKey];\n        [file_dictionary setObject:fileModificationDateString forKey:kDragDropLastModifiedKey];\n        [pathArray addObject:file_dictionary];\n      }\n    }\n    [self.provider performMouseDragDropAtPoint:dropPoint type:kDragFileType dropContent:pathArray];\n    return YES;\n  } else if (text != nil) {\n    [self.provider performMouseDragDropAtPoint:dropPoint type:kDragTextType dropContent:text];\n    return YES;\n  } else {\n    return NO;\n  }\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h\"\n\n#include \"clay/fml/logging.h\"\n\n#include <cctype>\n\n#include <Carbon/Carbon.h>\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterView.h\"\n\nnamespace {\nusing clay::KeyboardLayoutNotifier;\nusing clay::LayoutClue;\n\n// Use different device ID for mouse and pan/zoom events, since we can't differentiate the actual\n// device (mouse v.s. trackpad).\nstatic constexpr int32_t kMousePointerDeviceId = 0;\nstatic constexpr int32_t kPointerPanZoomDeviceId = 1;\n\n// A trackpad touch following inertial scrolling should cause an inertia cancel\n// event to be issued. Use a window of 50 milliseconds after the scroll to account\n// for delays in event propagation observed in macOS Ventura.\nstatic constexpr double kTrackpadTouchInertiaCancelWindowMs = 0.050;\n\n/**\n * State tracking for mouse events, to adapt between the events coming from the system and the\n * events that the embedding API expects.\n */\nstruct MouseState {\n  /**\n   * The currently pressed buttons, as represented in FlutterPointerEvent.\n   */\n  int64_t buttons = 0;\n\n  /**\n   * The accumulated gesture pan.\n   */\n  CGFloat delta_x = 0;\n  CGFloat delta_y = 0;\n\n  /**\n   * The accumulated gesture zoom scale.\n   */\n  CGFloat scale = 0;\n\n  /**\n   * The accumulated gesture rotation.\n   */\n  CGFloat rotation = 0;\n\n  /**\n   * Whether or not a kAdd event has been sent (or sent again since the last kRemove if tracking is\n   * enabled). Used to determine whether to send a kAdd event before sending an incoming mouse\n   * event, since Flutter expects pointers to be added before events are sent for them.\n   */\n  bool clay_state_is_added = false;\n\n  /**\n   * Whether or not a kDown has been sent since the last kAdd/kUp.\n   */\n  bool clay_state_is_down = false;\n\n  /**\n   * Whether or not mouseExited: was received while a button was down. Cocoa's behavior when\n   * dragging out of a tracked area is to send an exit, then keep sending drag events until the last\n   * button is released. Flutter doesn't expect to receive events after a kRemove, so the kRemove\n   * for the exit needs to be delayed until after the last mouse button is released. If cursor\n   * returns back to the window while still dragging, the flag is cleared in mouseEntered:.\n   */\n  bool has_pending_exit = false;\n\n  /**\n   * Pan gesture is currently sending us events.\n   */\n  bool pan_gesture_active = false;\n\n  /**\n   * Scale gesture is currently sending us events.\n   */\n  bool scale_gesture_active = false;\n\n  /**\n   * Rotate gesture is currently sending use events.\n   */\n  bool rotate_gesture_active = false;\n\n  /**\n   * Time of last scroll momentum event.\n   */\n  NSTimeInterval last_scroll_momentum_changed_time = 0;\n\n  /**\n   * Resets all gesture state to default values.\n   */\n  void GestureReset() {\n    delta_x = 0;\n    delta_y = 0;\n    scale = 0;\n    rotation = 0;\n  }\n\n  /**\n   * Resets all state to default values.\n   */\n  void Reset() {\n    clay_state_is_added = false;\n    clay_state_is_down = false;\n    has_pending_exit = false;\n    buttons = 0;\n    GestureReset();\n  }\n};\n\n/**\n * Returns the current Unicode layout data (kTISPropertyUnicodeKeyLayoutData).\n *\n * To use the returned data, convert it to CFDataRef first, finds its bytes\n * with CFDataGetBytePtr, then reinterpret it into const UCKeyboardLayout*.\n * It's returned in NSData* to enable auto reference count.\n */\nNSData* currentKeyboardLayoutData() {\n  TISInputSourceRef source = TISCopyCurrentKeyboardInputSource();\n  CFTypeRef layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);\n  if (layout_data == nil) {\n    CFRelease(source);\n    // TISGetInputSourceProperty returns null with Japanese keyboard layout.\n    // Using TISCopyCurrentKeyboardLayoutInputSource to fix NULL return.\n    // https://github.com/microsoft/node-native-keymap/blob/5f0699ded00179410a14c0e1b0e089fe4df8e130/src/keyboard_mac.mm#L91\n    source = TISCopyCurrentKeyboardLayoutInputSource();\n    layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);\n  }\n  return (__bridge_transfer NSData*)CFRetain(layout_data);\n}\n\n}  // namespace\n\n#pragma mark - Private interface declaration.\n\n/**\n * FlutterViewWrapper is a convenience class that wraps a FlutterView and provides\n * a mechanism to attach AppKit views such as FlutterTextField without affecting\n * the accessibility subtree of the wrapped FlutterView itself.\n *\n * The FlutterViewController uses this class to create its content view. When\n * any of the accessibility services (e.g. VoiceOver) is turned on, the accessibility\n * bridge creates FlutterTextFields that interact with the service. The bridge has to\n * attach the FlutterTextField somewhere in the view hierarchy in order for the\n * FlutterTextField to interact correctly with VoiceOver. Those FlutterTextFields\n * will be attached to this view so that they won't affect the accessibility subtree\n * of FlutterView.\n */\n@interface FlutterViewWrapper : NSView\n\n- (void)setBackgroundColor:(NSColor*)color;\n\n@end\n\n/**\n * Private interface declaration for FlutterViewController.\n */\n@interface FlutterViewController () <FlutterViewReshapeListener>\n\n/**\n * The tracking area used to generate hover events, if enabled.\n */\n@property(nonatomic) NSTrackingArea* trackingArea;\n\n/**\n * The current state of the mouse and the sent mouse events.\n */\n@property(nonatomic) MouseState mouseState;\n\n/**\n * Event monitor for keyUp events.\n */\n@property(nonatomic) id keyUpMonitor;\n\n/**\n * Pointer to a keyboard manager, a hub that manages how key events are\n * dispatched to various Flutter key responders, and whether the event is\n * propagated to the next NSResponder.\n */\n@property(nonatomic, readonly, nonnull) FlutterKeyboardManager* keyboardManager;\n\n@property(nonatomic) KeyboardLayoutNotifier keyboardLayoutNotifier;\n\n@property(nonatomic) NSData* keyboardLayoutData;\n\n/**\n * Starts running |engine|, including any initial setup.\n */\n- (BOOL)launchEngine;\n\n/**\n * Updates |trackingArea| for the current tracking settings, creating it with\n * the correct mode if tracking is enabled, or removing it if not.\n */\n- (void)configureTrackingArea;\n\n/**\n * Creates and registers keyboard related components.\n */\n- (void)initializeKeyboard;\n\n/**\n * Calls dispatchMouseEvent:phase: with a phase determined by self.mouseState.\n *\n * mouseState.buttons should be updated before calling this method.\n */\n- (void)dispatchMouseEvent:(nonnull NSEvent*)event;\n\n/**\n * Calls dispatchMouseEvent:phase: with a phase determined by event.phase.\n */\n- (void)dispatchGestureEvent:(nonnull NSEvent*)event;\n\n/**\n * Converts |event| to a FlutterPointerEvent with the given phase, and sends it to the engine.\n */\n- (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(ClayPointerPhase)phase;\n\n/**\n * Called when the active keyboard input source changes.\n *\n * Input sources may be simple keyboard layouts, or more complex input methods involving an IME,\n * such as Chinese, Japanese, and Korean.\n */\n- (void)onKeyboardLayoutChanged;\n\n@end\n\n#pragma mark - Private dependant functions\n\nnamespace {\nvoid OnKeyboardLayoutChanged(CFNotificationCenterRef center, void* observer, CFStringRef name,\n                             const void* object, CFDictionaryRef userInfo) {\n  FlutterViewController* controller = (__bridge FlutterViewController*)observer;\n  if (controller != nil) {\n    [controller onKeyboardLayoutChanged];\n  }\n}\n}  // namespace\n\n#pragma mark - FlutterViewWrapper implementation.\n\n@implementation FlutterViewWrapper {\n  FlutterView* _flutterView;\n}\n\n- (instancetype)initWithFlutterView:(FlutterView*)view {\n  self = [super initWithFrame:NSZeroRect];\n  if (self) {\n    _flutterView = view;\n    view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;\n    [self addSubview:view];\n  }\n  return self;\n}\n\n- (void)setBackgroundColor:(NSColor*)color {\n  [_flutterView setBackgroundColor:color];\n}\n\n- (NSArray*)accessibilityChildren {\n  return @[ _flutterView ];\n}\n\n@end\n\n#pragma mark - FlutterViewController implementation.\n\n@implementation FlutterViewController {\n}\n\n@dynamic view;\n\n/**\n * Performs initialization that's common between the different init paths.\n */\nstatic void CommonInit(FlutterViewController* controller) {\n  if (!controller->_engine) {\n    controller->_engine = [[FlutterEngine alloc] initWithName:@\"io.flutter\"\n                                       allowHeadlessExecution:NO];\n  }\n  controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow;\n  controller->_textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:controller];\n  // macOS fires this message when changing IMEs.\n  CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();\n  __weak FlutterViewController* weakSelf = controller;\n  CFNotificationCenterAddObserver(cfCenter, (__bridge void*)weakSelf, OnKeyboardLayoutChanged,\n                                  kTISNotifySelectedKeyboardInputSourceChanged, NULL,\n                                  CFNotificationSuspensionBehaviorDeliverImmediately);\n}\n\n- (instancetype)initWithCoder:(NSCoder*)coder {\n  self = [super initWithCoder:coder];\n  NSAssert(self, @\"Super init cannot be nil\");\n\n  CommonInit(self);\n  return self;\n}\n\n- (instancetype)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {\n  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];\n  NSAssert(self, @\"Super init cannot be nil\");\n\n  CommonInit(self);\n  return self;\n}\n\n- (instancetype)init {\n  self = [super initWithNibName:nil bundle:nil];\n  NSAssert(self, @\"Super init cannot be nil\");\n\n  CommonInit(self);\n  return self;\n}\n\n- (instancetype)initWithEngine:(nonnull FlutterEngine*)engine\n                       nibName:(nullable NSString*)nibName\n                        bundle:(nullable NSBundle*)nibBundle {\n  NSAssert(engine != nil, @\"Engine is required\");\n  NSAssert(engine.viewController == nil,\n           @\"The supplied FlutterEngine is already used with FlutterViewController \"\n            \"instance. One instance of the FlutterEngine can only be attached to one \"\n            \"FlutterViewController at a time. Set FlutterEngine.viewController \"\n            \"to nil before attaching it to another FlutterViewController.\");\n\n  self = [super initWithNibName:nibName bundle:nibBundle];\n  if (self) {\n    _engine = engine;\n    CommonInit(self);\n    if (engine.running) {\n      [self loadView];\n      engine.viewController = self;\n      [self initializeKeyboard];\n    }\n  }\n\n  return self;\n}\n\n- (BOOL)isDispatchingKeyEvent:(NSEvent*)event {\n  return [_keyboardManager isDispatchingKeyEvent:event];\n}\n\n- (void)loadView {\n  FlutterView* flutterView;\n  id<MTLDevice> device = _engine.renderer.device;\n  id<MTLCommandQueue> commandQueue = _engine.renderer.commandQueue;\n  if (!device || !commandQueue) {\n    FML_LOG(ERROR) << \"Unable to create FlutterView; no MTLDevice or MTLCommandQueue available.\";\n    return;\n  }\n  flutterView = [[FlutterView alloc] initWithMTLDevice:device\n                                          commandQueue:commandQueue\n                                       reshapeListener:self];\n  if (_backgroundColor != nil) {\n    [flutterView setBackgroundColor:_backgroundColor];\n  }\n  FlutterViewWrapper* wrapperView = [[FlutterViewWrapper alloc] initWithFlutterView:flutterView];\n  self.view = wrapperView;\n  _flutterView = flutterView;\n}\n\n- (void)viewDidLoad {\n  [self configureTrackingArea];\n  [self.view setAllowedTouchTypes:NSTouchTypeMaskIndirect];\n  [self.view setWantsRestingTouches:YES];\n}\n\n- (void)viewWillAppear {\n  [super viewWillAppear];\n  if (!_engine.running) {\n    [self launchEngine];\n  }\n  [self listenForMetaModifiedKeyUpEvents];\n}\n\n- (void)viewWillDisappear {\n  // Per Apple's documentation, it is discouraged to call removeMonitor: in dealloc, and it's\n  // recommended to be called earlier in the lifecycle.\n  [NSEvent removeMonitor:_keyUpMonitor];\n  _keyUpMonitor = nil;\n}\n\n- (void)dealloc {\n  _engine.viewController = nil;\n  CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();\n  CFNotificationCenterRemoveEveryObserver(cfCenter, (__bridge void*)self);\n}\n\n#pragma mark - Public methods\n\n- (void)setMouseTrackingMode:(FlutterMouseTrackingMode)mode {\n  if (_mouseTrackingMode == mode) {\n    return;\n  }\n  _mouseTrackingMode = mode;\n  [self configureTrackingArea];\n}\n\n- (void)setBackgroundColor:(NSColor*)color {\n  _backgroundColor = color;\n  [_flutterView setBackgroundColor:_backgroundColor];\n}\n\n- (void)onPreEngineRestart {\n  [self initializeKeyboard];\n}\n\n#pragma mark - Private methods\n\n- (BOOL)launchEngine {\n  [self initializeKeyboard];\n\n  _engine.viewController = self;\n  if (![_engine runWithEntrypoint:nil]) {\n    return NO;\n  }\n  return YES;\n}\n\n// macOS does not call keyUp: on a key while the command key is pressed. This results in a loss\n// of a key event once the modified key is released. This method registers the\n// ViewController as a listener for a keyUp event before it's handled by NSApplication, and should\n// NOT modify the event to avoid any unexpected behavior.\n- (void)listenForMetaModifiedKeyUpEvents {\n  if (_keyUpMonitor != nil) {\n    // It is possible for [NSViewController viewWillAppear] to be invoked multiple times\n    // in a row. https://github.com/flutter/flutter/issues/105963\n    return;\n  }\n  FlutterViewController* __weak weakSelf = self;\n  _keyUpMonitor = [NSEvent\n      addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp\n                                   handler:^NSEvent*(NSEvent* event) {\n                                     // Intercept keyUp only for events triggered on the current\n                                     // view or textInputPlugin.\n                                     NSResponder* firstResponder = [[event window] firstResponder];\n                                     if (weakSelf.viewLoaded && weakSelf.flutterView &&\n                                         (firstResponder == weakSelf.flutterView ||\n                                          firstResponder == weakSelf.textInputPlugin) &&\n                                         ([event modifierFlags] & NSEventModifierFlagCommand) &&\n                                         ([event type] == NSEventTypeKeyUp)) {\n                                       [weakSelf keyUp:event];\n                                     }\n                                     return event;\n                                   }];\n}\n\n- (void)configureTrackingArea {\n  if (!self.viewLoaded) {\n    // The viewDidLoad will call configureTrackingArea again when\n    // the view is actually loaded.\n    return;\n  }\n  if (_mouseTrackingMode != FlutterMouseTrackingModeNone && self.flutterView) {\n    NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |\n                                    NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag;\n    switch (_mouseTrackingMode) {\n      case FlutterMouseTrackingModeInKeyWindow:\n        options |= NSTrackingActiveInKeyWindow;\n        break;\n      case FlutterMouseTrackingModeInActiveApp:\n        options |= NSTrackingActiveInActiveApp;\n        break;\n      case FlutterMouseTrackingModeAlways:\n        options |= NSTrackingActiveAlways;\n        break;\n      default:\n        FML_LOG(ERROR) << \"Error: Unrecognized mouse tracking mode: \" << _mouseTrackingMode;\n        return;\n    }\n    _trackingArea = [[NSTrackingArea alloc] initWithRect:NSZeroRect\n                                                 options:options\n                                                   owner:self\n                                                userInfo:nil];\n    [self.flutterView addTrackingArea:_trackingArea];\n  } else if (_trackingArea) {\n    [self.flutterView removeTrackingArea:_trackingArea];\n    _trackingArea = nil;\n  }\n}\n\n- (void)initializeKeyboard {\n  // TODO(goderbauer): Seperate keyboard/textinput stuff into ViewController specific and Engine\n  // global parts. Move the global parts to FlutterEngine.\n  __weak FlutterViewController* weakSelf = self;\n  _keyboardManager = [[FlutterKeyboardManager alloc] initWithViewDelegate:weakSelf];\n}\n\n- (void)dispatchMouseEvent:(nonnull NSEvent*)event {\n  ClayPointerPhase phase =\n      _mouseState.buttons == 0\n          ? (_mouseState.clay_state_is_down ? kClayPointerPhaseUp : kClayPointerPhaseHover)\n          : (_mouseState.clay_state_is_down ? kClayPointerPhaseMove : kClayPointerPhaseDown);\n  [self dispatchMouseEvent:event phase:phase];\n}\n\n- (void)dispatchGestureEvent:(nonnull NSEvent*)event {\n  if (event.phase == NSEventPhaseBegan || event.phase == NSEventPhaseMayBegin) {\n    [self dispatchMouseEvent:event phase:kClayPointerPhasePanZoomStart];\n  } else if (event.phase == NSEventPhaseChanged) {\n    [self dispatchMouseEvent:event phase:kClayPointerPhasePanZoomUpdate];\n  } else if (event.phase == NSEventPhaseEnded || event.phase == NSEventPhaseCancelled) {\n    [self dispatchMouseEvent:event phase:kClayPointerPhasePanZoomEnd];\n  } else if (event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone) {\n    [self dispatchMouseEvent:event phase:kClayPointerPhaseHover];\n  } else {\n    // Waiting until the first momentum change event is a workaround for an issue where\n    // touchesBegan: is called unexpectedly while in low power mode within the interval between\n    // momentum start and the first momentum change.\n    if (event.momentumPhase == NSEventPhaseChanged) {\n      _mouseState.last_scroll_momentum_changed_time = event.timestamp;\n    }\n    // Skip momentum update events, the framework will generate scroll momentum.\n    NSAssert(event.momentumPhase != NSEventPhaseNone,\n             @\"Received gesture event with unexpected phase\");\n  }\n}\n\n- (void)dispatchMouseEvent:(NSEvent*)event phase:(ClayPointerPhase)phase {\n  NSAssert(self.viewLoaded, @\"View must be loaded before it handles the mouse event\");\n  // There are edge cases where the system will deliver enter out of order relative to other\n  // events (e.g., drag out and back in, release, then click; mouseDown: will be called before\n  // mouseEntered:). Discard those events, since the add will already have been synthesized.\n  if (_mouseState.clay_state_is_added && phase == kClayPointerPhaseAdd) {\n    return;\n  }\n\n  // Multiple gesture recognizers could be active at once, we can't send multiple kPanZoomStart.\n  // For example: rotation and magnification.\n  if (phase == kClayPointerPhasePanZoomStart) {\n    bool gestureAlreadyDown = _mouseState.pan_gesture_active || _mouseState.scale_gesture_active ||\n                              _mouseState.rotate_gesture_active;\n    if (event.type == NSEventTypeScrollWheel) {\n      _mouseState.pan_gesture_active = true;\n      // Ensure scroll inertia cancel event is not sent afterwards.\n      _mouseState.last_scroll_momentum_changed_time = 0;\n    } else if (event.type == NSEventTypeMagnify) {\n      _mouseState.scale_gesture_active = true;\n    } else if (event.type == NSEventTypeRotate) {\n      _mouseState.rotate_gesture_active = true;\n    }\n    if (gestureAlreadyDown) {\n      return;\n    }\n  }\n  if (phase == kClayPointerPhasePanZoomEnd) {\n    if (event.type == NSEventTypeScrollWheel) {\n      _mouseState.pan_gesture_active = false;\n    } else if (event.type == NSEventTypeMagnify) {\n      _mouseState.scale_gesture_active = false;\n    } else if (event.type == NSEventTypeRotate) {\n      _mouseState.rotate_gesture_active = false;\n    }\n    if (_mouseState.pan_gesture_active || _mouseState.scale_gesture_active ||\n        _mouseState.rotate_gesture_active) {\n      return;\n    }\n  }\n\n  // If a pointer added event hasn't been sent, synthesize one using this event for the basic\n  // information.\n  if (!_mouseState.clay_state_is_added && phase != kClayPointerPhaseAdd) {\n    // Only the values extracted for use in flutterEvent below matter, the rest are dummy values.\n    NSEvent* addEvent = [NSEvent enterExitEventWithType:NSEventTypeMouseEntered\n                                               location:event.locationInWindow\n                                          modifierFlags:0\n                                              timestamp:event.timestamp\n                                           windowNumber:event.windowNumber\n                                                context:nil\n                                            eventNumber:0\n                                         trackingNumber:0\n                                               userData:NULL];\n    [self dispatchMouseEvent:addEvent phase:kClayPointerPhaseAdd];\n  }\n\n  NSPoint locationInView = [self.flutterView convertPoint:event.locationInWindow fromView:nil];\n  NSPoint locationInBackingCoordinates = [self.flutterView convertPointToBacking:locationInView];\n  int32_t device = kMousePointerDeviceId;\n  ClayPointerDeviceKind deviceKind = kClayPointerDeviceKindMouse;\n  if (phase == kClayPointerPhasePanZoomStart || phase == kClayPointerPhasePanZoomUpdate ||\n      phase == kClayPointerPhasePanZoomEnd) {\n    device = kPointerPanZoomDeviceId;\n    deviceKind = kClayPointerDeviceKindTrackpad;\n  }\n  ClayPointerEvent clayEvent = {\n      .struct_size = sizeof(clayEvent),\n      .phase = phase,\n      .timestamp = static_cast<size_t>(event.timestamp * USEC_PER_SEC),\n      .x = locationInBackingCoordinates.x,\n      .y = -locationInBackingCoordinates.y,  // convertPointToBacking makes this negative.\n      .device = device,\n      .device_kind = deviceKind,\n      // If a click triggered a synthesized kAdd, don't pass the buttons in that event.\n      .buttons = phase == kClayPointerPhaseAdd ? 0 : _mouseState.buttons,\n  };\n\n  if (phase == kClayPointerPhasePanZoomUpdate) {\n    if (event.type == NSEventTypeScrollWheel) {\n      _mouseState.delta_x += event.scrollingDeltaX * self.flutterView.layer.contentsScale;\n      _mouseState.delta_y += event.scrollingDeltaY * self.flutterView.layer.contentsScale;\n    } else if (event.type == NSEventTypeMagnify) {\n      _mouseState.scale += event.magnification;\n    } else if (event.type == NSEventTypeRotate) {\n      _mouseState.rotation += event.rotation * (-M_PI / 180.0);\n    }\n    clayEvent.pan_x = _mouseState.delta_x;\n    clayEvent.pan_y = _mouseState.delta_y;\n    // Scale value needs to be normalized to range 0->infinity.\n    clayEvent.scale = pow(2.0, _mouseState.scale);\n    clayEvent.rotation = _mouseState.rotation;\n  } else if (phase == kClayPointerPhasePanZoomEnd) {\n    _mouseState.GestureReset();\n  } else if (phase != kClayPointerPhasePanZoomStart && event.type == NSEventTypeScrollWheel) {\n    clayEvent.signal_kind = kClayPointerSignalKindScroll;\n\n    double pixelsPerLine = 1.0;\n    if (!event.hasPreciseScrollingDeltas) {\n      // The scrollingDelta needs to be multiplied by the line height.\n      // CGEventSourceGetPixelsPerLine() will return 10, which will result in\n      // scrolling that is noticeably slower than in other applications.\n      // Using 40.0 as the multiplier to match Chromium.\n      // See https://source.chromium.org/chromium/chromium/src/+/main:ui/events/cocoa/events_mac.mm\n      pixelsPerLine = 40.0;\n    }\n    double scaleFactor = self.flutterView.layer.contentsScale;\n    clayEvent.scroll_delta_x = -event.scrollingDeltaX * pixelsPerLine * scaleFactor;\n    clayEvent.scroll_delta_y = -event.scrollingDeltaY * pixelsPerLine * scaleFactor;\n  }\n  [_engine sendPointerEvent:clayEvent];\n\n  // Update tracking of state as reported to Flutter.\n  if (phase == kClayPointerPhaseAdd) {\n    _mouseState.clay_state_is_down = true;\n  } else if (phase == kClayPointerPhaseUp) {\n    _mouseState.clay_state_is_down = false;\n    if (_mouseState.has_pending_exit) {\n      [self dispatchMouseEvent:event phase:kClayPointerPhaseRemove];\n      _mouseState.has_pending_exit = false;\n    }\n  } else if (phase == kClayPointerPhaseAdd) {\n    _mouseState.clay_state_is_added = true;\n  } else if (phase == kClayPointerPhaseRemove) {\n    _mouseState.Reset();\n  }\n}\n\n- (void)onKeyboardLayoutChanged {\n  _keyboardLayoutData = nil;\n  if (_keyboardLayoutNotifier != nil) {\n    _keyboardLayoutNotifier();\n  }\n}\n\n#pragma mark - FlutterViewReshapeListener\n\n/**\n * Responds to view reshape by notifying the engine of the change in dimensions.\n */\n- (void)viewDidReshape:(NSView*)view {\n  [_engine updateWindowMetrics];\n}\n\n#pragma mark - FlutterPluginRegistry\n\n- (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {\n  return [_engine registrarForPlugin:pluginName];\n}\n\n#pragma mark - FlutterKeyboardViewDelegate\n\n- (void)sendKeyEvent:(const ClayKeyEvent&)event\n            callback:(nullable ClayKeyEventCallback)callback\n            userData:(nullable void*)userData {\n  [_engine sendKeyEvent:event callback:callback userData:userData];\n}\n\n- (BOOL)onTextInputKeyEvent:(nonnull NSEvent*)event {\n  return [_textInputPlugin handleKeyEvent:event];\n}\n\n- (void)subscribeToKeyboardLayoutChange:(nullable KeyboardLayoutNotifier)callback {\n  _keyboardLayoutNotifier = callback;\n}\n\n- (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {\n  if (_keyboardLayoutData == nil) {\n    _keyboardLayoutData = currentKeyboardLayoutData();\n  }\n  const UCKeyboardLayout* layout = reinterpret_cast<const UCKeyboardLayout*>(\n      CFDataGetBytePtr((__bridge CFDataRef)_keyboardLayoutData));\n\n  UInt32 deadKeyState = 0;\n  UniCharCount stringLength = 0;\n  UniChar resultChar;\n\n  UInt32 modifierState = ((shift ? shiftKey : 0) >> 8) & 0xFF;\n  UInt32 keyboardType = LMGetKbdLast();\n\n  bool isDeadKey = false;\n  OSStatus status =\n      UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,\n                     kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);\n  // For dead keys, press the same key again to get the printable representation of the key.\n  if (status == noErr && stringLength == 0 && deadKeyState != 0) {\n    isDeadKey = true;\n    status =\n        UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,\n                       kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);\n  }\n\n  if (status == noErr && stringLength == 1 && !std::iscntrl(resultChar)) {\n    return LayoutClue{resultChar, isDeadKey};\n  }\n  return LayoutClue{0, false};\n}\n\n#pragma mark - NSResponder\n\n- (BOOL)acceptsFirstResponder {\n  return YES;\n}\n\n- (void)keyDown:(NSEvent*)event {\n  [_keyboardManager handleEvent:event];\n}\n\n- (void)keyUp:(NSEvent*)event {\n  [_keyboardManager handleEvent:event];\n}\n\n- (void)flagsChanged:(NSEvent*)event {\n  [_keyboardManager handleEvent:event];\n}\n\n- (void)mouseEntered:(NSEvent*)event {\n  if (_mouseState.has_pending_exit) {\n    _mouseState.has_pending_exit = false;\n  } else {\n    [self dispatchMouseEvent:event phase:kClayPointerPhaseAdd];\n  }\n}\n\n- (void)mouseExited:(NSEvent*)event {\n  if (_mouseState.buttons != 0) {\n    _mouseState.has_pending_exit = true;\n    return;\n  }\n  [self dispatchMouseEvent:event phase:kClayPointerPhaseRemove];\n}\n\n- (void)mouseDown:(NSEvent*)event {\n  _mouseState.buttons |= kClayPointerMouseButtonsMousePrimary;\n  [self dispatchMouseEvent:event];\n}\n\n- (void)mouseUp:(NSEvent*)event {\n  _mouseState.buttons &= ~static_cast<uint64_t>(kClayPointerMouseButtonsMousePrimary);\n  [self dispatchMouseEvent:event];\n}\n\n- (void)mouseDragged:(NSEvent*)event {\n  [self dispatchMouseEvent:event];\n}\n\n- (void)rightMouseDown:(NSEvent*)event {\n  _mouseState.buttons |= kClayPointerMouseButtonsMouseSecondary;\n  [self dispatchMouseEvent:event];\n}\n\n- (void)rightMouseUp:(NSEvent*)event {\n  _mouseState.buttons &= ~static_cast<uint64_t>(kClayPointerMouseButtonsMouseSecondary);\n  [self dispatchMouseEvent:event];\n}\n\n- (void)rightMouseDragged:(NSEvent*)event {\n  [self dispatchMouseEvent:event];\n}\n\n- (void)otherMouseDown:(NSEvent*)event {\n  _mouseState.buttons |= (1 << event.buttonNumber);\n  [self dispatchMouseEvent:event];\n}\n\n- (void)otherMouseUp:(NSEvent*)event {\n  _mouseState.buttons &= ~static_cast<uint64_t>(1 << event.buttonNumber);\n  [self dispatchMouseEvent:event];\n}\n\n- (void)otherMouseDragged:(NSEvent*)event {\n  [self dispatchMouseEvent:event];\n}\n\n- (void)mouseMoved:(NSEvent*)event {\n  [self dispatchMouseEvent:event];\n}\n\n- (void)scrollWheel:(NSEvent*)event {\n  [self dispatchGestureEvent:event];\n}\n\n- (void)magnifyWithEvent:(NSEvent*)event {\n  [self dispatchGestureEvent:event];\n}\n\n- (void)rotateWithEvent:(NSEvent*)event {\n  [self dispatchGestureEvent:event];\n}\n\n- (void)swipeWithEvent:(NSEvent*)event {\n  // Not needed, it's handled by scrollWheel.\n}\n\n- (void)touchesBeganWithEvent:(NSEvent*)event {\n  NSTouch* touch = event.allTouches.anyObject;\n  if (touch != nil) {\n    if ((event.timestamp - _mouseState.last_scroll_momentum_changed_time) <\n        kTrackpadTouchInertiaCancelWindowMs) {\n      // The trackpad has been touched following a scroll momentum event.\n      // A scroll inertia cancel message should be sent to the framework.\n      NSPoint locationInView = [self.flutterView convertPoint:event.locationInWindow fromView:nil];\n      NSPoint locationInBackingCoordinates =\n          [self.flutterView convertPointToBacking:locationInView];\n      ClayPointerEvent clayEvent = {\n          .struct_size = sizeof(clayEvent),\n          .timestamp = static_cast<size_t>(event.timestamp * USEC_PER_SEC),\n          .x = locationInBackingCoordinates.x,\n          .y = -locationInBackingCoordinates.y,  // convertPointToBacking makes this negative.\n          .device = kPointerPanZoomDeviceId,\n          .signal_kind = kClayPointerSignalKindScrollInertiaCancel,\n          .device_kind = kClayPointerDeviceKindTrackpad,\n      };\n\n      [_engine sendPointerEvent:clayEvent];\n      // Ensure no further scroll inertia cancel event will be sent.\n      _mouseState.last_scroll_momentum_changed_time = 0;\n    }\n  }\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h\"\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterView.h\"\n\n@interface FlutterViewController () <FlutterKeyboardViewDelegate>\n\n// The FlutterView for this view controller.\n@property(nonatomic, readonly, nullable) FlutterView* flutterView;\n\n/**\n * The text input plugin that handles text editing state for text fields.\n */\n@property(nonatomic, readonly, nonnull) FlutterTextInputPlugin* textInputPlugin;\n\n/**\n * Returns YES if provided event is being currently redispatched by keyboard manager.\n */\n- (BOOL)isDispatchingKeyEvent:(nonnull NSEvent*)event;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewProvider.h\"\n\n@class FlutterEngine;\n\n/**\n * A facade over FlutterEngine that allows FlutterEngine's children components\n * to query FlutterView.\n *\n * FlutterViewProvider only holds a weak reference to FlutterEngine.\n */\n@interface FlutterViewEngineProvider : NSObject <FlutterViewProvider>\n\n/**\n * Create a FlutterViewProvider with the underlying engine.\n */\n- (nonnull instancetype)initWithEngine:(nonnull __weak FlutterEngine*)engine;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h\"\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h\"\n\n@interface FlutterViewEngineProvider () {\n  __weak FlutterEngine* _engine;\n}\n\n@end\n\n@implementation FlutterViewEngineProvider\n\n- (instancetype)initWithEngine:(FlutterEngine*)engine {\n  self = [super init];\n  if (self != nil) {\n    _engine = engine;\n  }\n  return self;\n}\n\n- (nullable FlutterView*)getView:(uint64_t)viewId {\n  // TODO(dkwingsmt): This class only supports the first view for now. After\n  // FlutterEngine supports multi-view, it should get the view associated to the\n  // ID.\n  if (viewId == kFlutterDefaultViewId) {\n    // Clay may be doesn't init viewController, so we will get engine's view is VC is null\n    FlutterView* view = _engine.viewController.flutterView;\n    if (!view) {\n      return _engine.view;\n    }\n  }\n  return nil;\n}\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/FlutterViewProvider.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"clay/shell/platform/darwin/macos/framework/Source/FlutterView.h\"\n\n/**\n * An interface to query FlutterView.\n *\n * See also:\n *\n *  * FlutterViewEngineProvider, a typical implementation.\n */\n@protocol FlutterViewProvider\n\n/**\n * Get the FlutterView with the given view ID.\n *\n * Returns nil if the ID is invalid.\n */\n- (nullable FlutterView*)getView:(uint64_t)id;\n\n@end\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/KeyCodeMap.g.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n#import <Foundation/Foundation.h>\n#include \"./KeyCodeMap_Internal.h\"\n\n// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT\n// This file is generated by\n// flutter/flutter:dev/tools/gen_keycodes/bin/gen_keycodes.dart and should not\n// be edited directly.\n//\n// Edit the template\n// flutter/flutter:dev/tools/gen_keycodes/data/keyboard_map_macos_cc.tmpl\n// instead.\n//\n// See flutter/flutter:dev/tools/gen_keycodes/README.md for more information.\n\nnamespace clay {\n\nconst uint64_t kValueMask = 0x000ffffffff;\nconst uint64_t kUnicodePlane = 0x00000000000;\nconst uint64_t kMacosPlane = 0x01400000000;\n\nconst NSDictionary* keyCodeToPhysicalKey = @{\n  @0x00000000 : @0x00070004,  // keyA\n  @0x00000001 : @0x00070016,  // keyS\n  @0x00000002 : @0x00070007,  // keyD\n  @0x00000003 : @0x00070009,  // keyF\n  @0x00000004 : @0x0007000b,  // keyH\n  @0x00000005 : @0x0007000a,  // keyG\n  @0x00000006 : @0x0007001d,  // keyZ\n  @0x00000007 : @0x0007001b,  // keyX\n  @0x00000008 : @0x00070006,  // keyC\n  @0x00000009 : @0x00070019,  // keyV\n  @0x0000000a : @0x00070064,  // intlBackslash\n  @0x0000000b : @0x00070005,  // keyB\n  @0x0000000c : @0x00070014,  // keyQ\n  @0x0000000d : @0x0007001a,  // keyW\n  @0x0000000e : @0x00070008,  // keyE\n  @0x0000000f : @0x00070015,  // keyR\n  @0x00000010 : @0x0007001c,  // keyY\n  @0x00000011 : @0x00070017,  // keyT\n  @0x00000012 : @0x0007001e,  // digit1\n  @0x00000013 : @0x0007001f,  // digit2\n  @0x00000014 : @0x00070020,  // digit3\n  @0x00000015 : @0x00070021,  // digit4\n  @0x00000016 : @0x00070023,  // digit6\n  @0x00000017 : @0x00070022,  // digit5\n  @0x00000018 : @0x0007002e,  // equal\n  @0x00000019 : @0x00070026,  // digit9\n  @0x0000001a : @0x00070024,  // digit7\n  @0x0000001b : @0x0007002d,  // minus\n  @0x0000001c : @0x00070025,  // digit8\n  @0x0000001d : @0x00070027,  // digit0\n  @0x0000001e : @0x00070030,  // bracketRight\n  @0x0000001f : @0x00070012,  // keyO\n  @0x00000020 : @0x00070018,  // keyU\n  @0x00000021 : @0x0007002f,  // bracketLeft\n  @0x00000022 : @0x0007000c,  // keyI\n  @0x00000023 : @0x00070013,  // keyP\n  @0x00000024 : @0x00070028,  // enter\n  @0x00000025 : @0x0007000f,  // keyL\n  @0x00000026 : @0x0007000d,  // keyJ\n  @0x00000027 : @0x00070034,  // quote\n  @0x00000028 : @0x0007000e,  // keyK\n  @0x00000029 : @0x00070033,  // semicolon\n  @0x0000002a : @0x00070031,  // backslash\n  @0x0000002b : @0x00070036,  // comma\n  @0x0000002c : @0x00070038,  // slash\n  @0x0000002d : @0x00070011,  // keyN\n  @0x0000002e : @0x00070010,  // keyM\n  @0x0000002f : @0x00070037,  // period\n  @0x00000030 : @0x0007002b,  // tab\n  @0x00000031 : @0x0007002c,  // space\n  @0x00000032 : @0x00070035,  // backquote\n  @0x00000033 : @0x0007002a,  // backspace\n  @0x00000035 : @0x00070029,  // escape\n  @0x00000036 : @0x000700e7,  // metaRight\n  @0x00000037 : @0x000700e3,  // metaLeft\n  @0x00000038 : @0x000700e1,  // shiftLeft\n  @0x00000039 : @0x00070039,  // capsLock\n  @0x0000003a : @0x000700e2,  // altLeft\n  @0x0000003b : @0x000700e0,  // controlLeft\n  @0x0000003c : @0x000700e5,  // shiftRight\n  @0x0000003d : @0x000700e6,  // altRight\n  @0x0000003e : @0x000700e4,  // controlRight\n  @0x0000003f : @0x00000012,  // fn\n  @0x00000040 : @0x0007006c,  // f17\n  @0x00000041 : @0x00070063,  // numpadDecimal\n  @0x00000043 : @0x00070055,  // numpadMultiply\n  @0x00000045 : @0x00070057,  // numpadAdd\n  @0x00000047 : @0x00070053,  // numLock\n  @0x00000048 : @0x00070080,  // audioVolumeUp\n  @0x00000049 : @0x00070081,  // audioVolumeDown\n  @0x0000004a : @0x0007007f,  // audioVolumeMute\n  @0x0000004b : @0x00070054,  // numpadDivide\n  @0x0000004c : @0x00070058,  // numpadEnter\n  @0x0000004e : @0x00070056,  // numpadSubtract\n  @0x0000004f : @0x0007006d,  // f18\n  @0x00000050 : @0x0007006e,  // f19\n  @0x00000051 : @0x00070067,  // numpadEqual\n  @0x00000052 : @0x00070062,  // numpad0\n  @0x00000053 : @0x00070059,  // numpad1\n  @0x00000054 : @0x0007005a,  // numpad2\n  @0x00000055 : @0x0007005b,  // numpad3\n  @0x00000056 : @0x0007005c,  // numpad4\n  @0x00000057 : @0x0007005d,  // numpad5\n  @0x00000058 : @0x0007005e,  // numpad6\n  @0x00000059 : @0x0007005f,  // numpad7\n  @0x0000005a : @0x0007006f,  // f20\n  @0x0000005b : @0x00070060,  // numpad8\n  @0x0000005c : @0x00070061,  // numpad9\n  @0x0000005d : @0x00070089,  // intlYen\n  @0x0000005e : @0x00070087,  // intlRo\n  @0x0000005f : @0x00070085,  // numpadComma\n  @0x00000060 : @0x0007003e,  // f5\n  @0x00000061 : @0x0007003f,  // f6\n  @0x00000062 : @0x00070040,  // f7\n  @0x00000063 : @0x0007003c,  // f3\n  @0x00000064 : @0x00070041,  // f8\n  @0x00000065 : @0x00070042,  // f9\n  @0x00000066 : @0x00070091,  // lang2\n  @0x00000067 : @0x00070044,  // f11\n  @0x00000068 : @0x00070090,  // lang1\n  @0x00000069 : @0x00070068,  // f13\n  @0x0000006a : @0x0007006b,  // f16\n  @0x0000006b : @0x00070069,  // f14\n  @0x0000006d : @0x00070043,  // f10\n  @0x0000006e : @0x00070065,  // contextMenu\n  @0x0000006f : @0x00070045,  // f12\n  @0x00000071 : @0x0007006a,  // f15\n  @0x00000072 : @0x00070049,  // insert\n  @0x00000073 : @0x0007004a,  // home\n  @0x00000074 : @0x0007004b,  // pageUp\n  @0x00000075 : @0x0007004c,  // delete\n  @0x00000076 : @0x0007003d,  // f4\n  @0x00000077 : @0x0007004d,  // end\n  @0x00000078 : @0x0007003b,  // f2\n  @0x00000079 : @0x0007004e,  // pageDown\n  @0x0000007a : @0x0007003a,  // f1\n  @0x0000007b : @0x00070050,  // arrowLeft\n  @0x0000007c : @0x0007004f,  // arrowRight\n  @0x0000007d : @0x00070051,  // arrowDown\n  @0x0000007e : @0x00070052,  // arrowUp\n};\n\nconst NSDictionary* keyCodeToLogicalKey = @{\n  @0x00000024 : @0x0010000000d,  // Enter -> enter\n  @0x00000030 : @0x00100000009,  // Tab -> tab\n  @0x00000033 : @0x00100000008,  // Backspace -> backspace\n  @0x00000035 : @0x0010000001b,  // Escape -> escape\n  @0x00000036 : @0x00200000107,  // MetaRight -> metaRight\n  @0x00000037 : @0x00200000106,  // MetaLeft -> metaLeft\n  @0x00000038 : @0x00200000102,  // ShiftLeft -> shiftLeft\n  @0x00000039 : @0x00100000104,  // CapsLock -> capsLock\n  @0x0000003a : @0x00200000104,  // AltLeft -> altLeft\n  @0x0000003b : @0x00200000100,  // ControlLeft -> controlLeft\n  @0x0000003c : @0x00200000103,  // ShiftRight -> shiftRight\n  @0x0000003d : @0x00200000105,  // AltRight -> altRight\n  @0x0000003e : @0x00200000101,  // ControlRight -> controlRight\n  @0x0000003f : @0x00100000106,  // Fn -> fn\n  @0x00000040 : @0x00100000811,  // F17 -> f17\n  @0x00000041 : @0x0020000022e,  // NumpadDecimal -> numpadDecimal\n  @0x00000043 : @0x0020000022a,  // NumpadMultiply -> numpadMultiply\n  @0x00000045 : @0x0020000022b,  // NumpadAdd -> numpadAdd\n  @0x00000047 : @0x0010000010a,  // NumLock -> numLock\n  @0x00000048 : @0x00100000a10,  // AudioVolumeUp -> audioVolumeUp\n  @0x00000049 : @0x00100000a0f,  // AudioVolumeDown -> audioVolumeDown\n  @0x0000004a : @0x00100000a11,  // AudioVolumeMute -> audioVolumeMute\n  @0x0000004b : @0x0020000022f,  // NumpadDivide -> numpadDivide\n  @0x0000004c : @0x0020000020d,  // NumpadEnter -> numpadEnter\n  @0x0000004e : @0x0020000022d,  // NumpadSubtract -> numpadSubtract\n  @0x0000004f : @0x00100000812,  // F18 -> f18\n  @0x00000050 : @0x00100000813,  // F19 -> f19\n  @0x00000051 : @0x0020000023d,  // NumpadEqual -> numpadEqual\n  @0x00000052 : @0x00200000230,  // Numpad0 -> numpad0\n  @0x00000053 : @0x00200000231,  // Numpad1 -> numpad1\n  @0x00000054 : @0x00200000232,  // Numpad2 -> numpad2\n  @0x00000055 : @0x00200000233,  // Numpad3 -> numpad3\n  @0x00000056 : @0x00200000234,  // Numpad4 -> numpad4\n  @0x00000057 : @0x00200000235,  // Numpad5 -> numpad5\n  @0x00000058 : @0x00200000236,  // Numpad6 -> numpad6\n  @0x00000059 : @0x00200000237,  // Numpad7 -> numpad7\n  @0x0000005a : @0x00100000814,  // F20 -> f20\n  @0x0000005b : @0x00200000238,  // Numpad8 -> numpad8\n  @0x0000005c : @0x00200000239,  // Numpad9 -> numpad9\n  @0x0000005d : @0x00200000022,  // IntlYen -> intlYen\n  @0x0000005e : @0x00200000021,  // IntlRo -> intlRo\n  @0x0000005f : @0x0020000022c,  // NumpadComma -> numpadComma\n  @0x00000060 : @0x00100000805,  // F5 -> f5\n  @0x00000061 : @0x00100000806,  // F6 -> f6\n  @0x00000062 : @0x00100000807,  // F7 -> f7\n  @0x00000063 : @0x00100000803,  // F3 -> f3\n  @0x00000064 : @0x00100000808,  // F8 -> f8\n  @0x00000065 : @0x00100000809,  // F9 -> f9\n  @0x00000066 : @0x00200000011,  // Lang2 -> lang2\n  @0x00000067 : @0x0010000080b,  // F11 -> f11\n  @0x00000068 : @0x00200000010,  // Lang1 -> lang1\n  @0x00000069 : @0x0010000080d,  // F13 -> f13\n  @0x0000006a : @0x00100000810,  // F16 -> f16\n  @0x0000006b : @0x0010000080e,  // F14 -> f14\n  @0x0000006d : @0x0010000080a,  // F10 -> f10\n  @0x0000006e : @0x00100000505,  // ContextMenu -> contextMenu\n  @0x0000006f : @0x0010000080c,  // F12 -> f12\n  @0x00000071 : @0x0010000080f,  // F15 -> f15\n  @0x00000072 : @0x00100000407,  // Insert -> insert\n  @0x00000073 : @0x00100000306,  // Home -> home\n  @0x00000074 : @0x00100000308,  // PageUp -> pageUp\n  @0x00000075 : @0x0010000007f,  // Delete -> delete\n  @0x00000076 : @0x00100000804,  // F4 -> f4\n  @0x00000077 : @0x00100000305,  // End -> end\n  @0x00000078 : @0x00100000802,  // F2 -> f2\n  @0x00000079 : @0x00100000307,  // PageDown -> pageDown\n  @0x0000007a : @0x00100000801,  // F1 -> f1\n  @0x0000007b : @0x00100000302,  // ArrowLeft -> arrowLeft\n  @0x0000007c : @0x00100000303,  // ArrowRight -> arrowRight\n  @0x0000007d : @0x00100000301,  // ArrowDown -> arrowDown\n  @0x0000007e : @0x00100000304,  // ArrowUp -> arrowUp\n};\n\nconst NSDictionary* keyCodeToModifierFlag = @{\n  @0x00000038 : @(kModifierFlagShiftLeft),\n  @0x0000003c : @(kModifierFlagShiftRight),\n  @0x0000003b : @(kModifierFlagControlLeft),\n  @0x0000003e : @(kModifierFlagControlRight),\n  @0x0000003a : @(kModifierFlagAltLeft),\n  @0x0000003d : @(kModifierFlagAltRight),\n  @0x00000037 : @(kModifierFlagMetaLeft),\n  @0x00000036 : @(kModifierFlagMetaRight),\n};\n\nconst NSDictionary* modifierFlagToKeyCode = @{\n  @(kModifierFlagShiftLeft) : @0x00000038,\n  @(kModifierFlagShiftRight) : @0x0000003c,\n  @(kModifierFlagControlLeft) : @0x0000003b,\n  @(kModifierFlagControlRight) : @0x0000003e,\n  @(kModifierFlagAltLeft) : @0x0000003a,\n  @(kModifierFlagAltRight) : @0x0000003d,\n  @(kModifierFlagMetaLeft) : @0x00000037,\n  @(kModifierFlagMetaRight) : @0x00000036,\n};\n\nconst uint64_t kCapsLockPhysicalKey = 0x00070039;\nconst uint64_t kCapsLockLogicalKey = 0x100000104;\n\nconst std::vector<LayoutGoal> layoutGoals = {\n    LayoutGoal{0x31, 0x20, false},  // Space\n    LayoutGoal{0x27, 0x22, false},  // Quote\n    LayoutGoal{0x2b, 0x2c, false},  // Comma\n    LayoutGoal{0x1b, 0x2d, false},  // Minus\n    LayoutGoal{0x2f, 0x2e, false},  // Period\n    LayoutGoal{0x2c, 0x2f, false},  // Slash\n    LayoutGoal{0x1d, 0x30, true},   // Digit0\n    LayoutGoal{0x12, 0x31, true},   // Digit1\n    LayoutGoal{0x13, 0x32, true},   // Digit2\n    LayoutGoal{0x14, 0x33, true},   // Digit3\n    LayoutGoal{0x15, 0x34, true},   // Digit4\n    LayoutGoal{0x17, 0x35, true},   // Digit5\n    LayoutGoal{0x16, 0x36, true},   // Digit6\n    LayoutGoal{0x1a, 0x37, true},   // Digit7\n    LayoutGoal{0x1c, 0x38, true},   // Digit8\n    LayoutGoal{0x19, 0x39, true},   // Digit9\n    LayoutGoal{0x29, 0x3b, false},  // Semicolon\n    LayoutGoal{0x18, 0x3d, false},  // Equal\n    LayoutGoal{0x21, 0x5b, false},  // BracketLeft\n    LayoutGoal{0x2a, 0x5c, false},  // Backslash\n    LayoutGoal{0x1e, 0x5d, false},  // BracketRight\n    LayoutGoal{0x32, 0x60, false},  // Backquote\n    LayoutGoal{0x00, 0x61, true},   // KeyA\n    LayoutGoal{0x0b, 0x62, true},   // KeyB\n    LayoutGoal{0x08, 0x63, true},   // KeyC\n    LayoutGoal{0x02, 0x64, true},   // KeyD\n    LayoutGoal{0x0e, 0x65, true},   // KeyE\n    LayoutGoal{0x03, 0x66, true},   // KeyF\n    LayoutGoal{0x05, 0x67, true},   // KeyG\n    LayoutGoal{0x04, 0x68, true},   // KeyH\n    LayoutGoal{0x22, 0x69, true},   // KeyI\n    LayoutGoal{0x26, 0x6a, true},   // KeyJ\n    LayoutGoal{0x28, 0x6b, true},   // KeyK\n    LayoutGoal{0x25, 0x6c, true},   // KeyL\n    LayoutGoal{0x2e, 0x6d, true},   // KeyM\n    LayoutGoal{0x2d, 0x6e, true},   // KeyN\n    LayoutGoal{0x1f, 0x6f, true},   // KeyO\n    LayoutGoal{0x23, 0x70, true},   // KeyP\n    LayoutGoal{0x0c, 0x71, true},   // KeyQ\n    LayoutGoal{0x0f, 0x72, true},   // KeyR\n    LayoutGoal{0x01, 0x73, true},   // KeyS\n    LayoutGoal{0x11, 0x74, true},   // KeyT\n    LayoutGoal{0x20, 0x75, true},   // KeyU\n    LayoutGoal{0x09, 0x76, true},   // KeyV\n    LayoutGoal{0x0d, 0x77, true},   // KeyW\n    LayoutGoal{0x07, 0x78, true},   // KeyX\n    LayoutGoal{0x10, 0x79, true},   // KeyY\n    LayoutGoal{0x06, 0x7a, true},   // KeyZ\n};\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/darwin/macos/framework/Source/KeyCodeMap_Internal.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import <Cocoa/Cocoa.h>\n#include <cinttypes>\n#include <vector>\n\nnamespace clay {\n\n/**\n * Maps macOS-specific key code values representing |PhysicalKeyboardKey|.\n *\n * MacOS doesn't provide a scan code, but a virtual keycode to represent a physical key.\n */\nextern const NSDictionary* keyCodeToPhysicalKey;\n\n/**\n * A map from macOS key codes to Flutter's logical key values.\n *\n * This is used to derive logical keys that can't or shouldn't be derived from\n * |charactersIgnoringModifiers|.\n */\nextern const NSDictionary* keyCodeToLogicalKey;\n\n// Several mask constants. See KeyCodeMap.g.mm for their descriptions.\n\n/**\n * Mask for the 32-bit value portion of the key code.\n */\nextern const uint64_t kValueMask;\n\n/**\n * The plane value for keys which have a Unicode representation.\n */\nextern const uint64_t kUnicodePlane;\n\n/**\n * The plane value for the private keys defined by the macOS embedding.\n */\nextern const uint64_t kMacosPlane;\n\n/**\n * Map |NSEvent.keyCode| to its corresponding bitmask of NSEventModifierFlags.\n *\n * This does not include CapsLock, for it is handled specially.\n */\nextern const NSDictionary* keyCodeToModifierFlag;\n\n/**\n * Map a bit of bitmask of NSEventModifierFlags to its corresponding\n * |NSEvent.keyCode|.\n *\n * This does not include CapsLock, for it is handled specially.\n */\nextern const NSDictionary* modifierFlagToKeyCode;\n\n/**\n * The physical key for CapsLock, which needs special handling.\n */\nextern const uint64_t kCapsLockPhysicalKey;\n\n/**\n * The logical key for CapsLock, which needs special handling.\n */\nextern const uint64_t kCapsLockLogicalKey;\n\n/**\n * Bits in |NSEvent.modifierFlags| indicating whether a modifier key is pressed.\n *\n * These constants are not written in the official documentation, but derived\n * from experiments. This is currently the only way to know whether a one-side\n * modifier key (such as ShiftLeft) is pressed, instead of the general combined\n * modifier state (such as Shift).\n */\ntypedef enum {\n  kModifierFlagControlLeft = 0x1,\n  kModifierFlagShiftLeft = 0x2,\n  kModifierFlagShiftRight = 0x4,\n  kModifierFlagMetaLeft = 0x8,\n  kModifierFlagMetaRight = 0x10,\n  kModifierFlagAltLeft = 0x20,\n  kModifierFlagAltRight = 0x40,\n  kModifierFlagControlRight = 0x200,\n} ModifierFlag;\n\n/**\n * A character that Flutter wants to derive layout for, and guides on how to\n * derive it.\n */\ntypedef struct {\n  // The key code for a key that prints `keyChar` in the US keyboard layout.\n  uint16_t keyCode;\n\n  // The printable string to derive logical key for.\n  uint8_t keyChar;\n\n  // If the goal is mandatory, the keyboard manager will make sure to find a\n  // logical key for this character, falling back to the US keyboard layout.\n  bool mandatory;\n} LayoutGoal;\n\n/**\n * All keys that Flutter wants to derive layout for, and guides on how to derive\n * them.\n */\nextern const std::vector<LayoutGoal> layoutGoals;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//build/toolchain/clang.gni\")\nimport(\"../../../common/config.gni\")\nimport(\"../../config.gni\")\nimport(\"../../gpu/gpu.gni\")\n\ndeclare_args() {\n  embedder_enable_software = shell_enable_software\n  embedder_enable_gl = shell_enable_gl\n  embedder_enable_metal = shell_enable_metal\n}\n\nshell_gpu_configuration(\"embedder_gpu_configuration\") {\n  enable_software = embedder_enable_software\n  enable_gl = embedder_enable_gl\n  enable_metal = embedder_enable_metal\n}\n\nconfig(\"embedder_header_config\") {\n  include_dirs = [ \".\" ]\n}\n\n# Template for the embedder build. Used to allow building it multiple times with\n# different flags.\ntemplate(\"embedder_source_set\") {\n  forward_variables_from(invoker, \"*\")\n\n  source_set(target_name) {\n    sources = [\n      \"embedder_engine.cc\",\n      \"embedder_engine.h\",\n      \"embedder_struct_macros.h\",\n      \"embedder_surface.cc\",\n      \"embedder_surface.h\",\n      \"embedder_surface_software.cc\",\n      \"embedder_surface_software.h\",\n      \"embedder_task_runner.cc\",\n      \"embedder_task_runner.h\",\n      \"embedder_thread_host.cc\",\n      \"embedder_thread_host.h\",\n      \"platform_view_embedder.cc\",\n      \"platform_view_embedder.h\",\n      \"platform_view_embedder_delegate.cc\",\n      \"platform_view_embedder_delegate.h\",\n      \"vsync_waiter_embedder.cc\",\n      \"vsync_waiter_embedder.h\",\n    ]\n\n    include_dirs = [ \"//third_party/skity\" ]\n\n    if (embedder_enable_gl) {\n      sources += [\n        \"embedder_surface_gl.cc\",\n        \"embedder_surface_gl.h\",\n      ]\n    }\n\n    deps = [\n      \":embedder_gpu_configuration\",\n      \"../../../common\",\n      \"../../../common/graphics\",\n      \"../../../flow\",\n      \"../../../fml:fml\",\n      \"../../../ui:ui\",\n      \"../../common:common\",\n    ]\n\n    if (!enable_skity) {\n      deps += [ \"//third_party/skia\" ]\n    }\n\n    if (embedder_enable_metal) {\n      sources += [\n        \"embedder_surface_metal.h\",\n        \"embedder_surface_metal.mm\",\n      ]\n\n      deps += [ \"../darwin/graphics\" ]\n    }\n\n    public_configs += [\n      \":embedder_gpu_configuration_config\",\n      \":embedder_header_config\",\n      \"../../../../clay:config\",\n      \"../darwin/common:framework_relative_headers\",\n    ]\n  }\n}\n\nembedder_source_set(\"embedder\") {\n  public_configs = []\n}\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_engine.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/embedder/embedder_engine.h\"\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"clay/shell/platform/embedder/embedder_struct_macros.h\"\n#include \"clay/shell/platform/embedder/vsync_waiter_embedder.h\"\n#include \"clay/ui/window/key_data_helper.h\"\n#include \"clay/ui/window/pointer_data_helper.h\"\n\nnamespace clay {\n\nnamespace {\n\nstd::unique_ptr<EmbedderThreadHost> CreateThreadHost(\n    const ClayCustomTaskRunners* custom_task_runners) {\n  void (*thread_priority_setter)(fml::Thread::ThreadPriority);\n  thread_priority_setter = custom_task_runners\n                               ? custom_task_runners->thread_priority_setter\n                               : nullptr;\n  auto thread_config_callback =\n      [thread_priority_setter](const fml::Thread::ThreadConfig& config) {\n        fml::Thread::SetCurrentThreadName(config);\n        if (!thread_priority_setter) {\n          return;\n        }\n        thread_priority_setter(config.priority);\n      };\n  auto thread_host =\n      clay::EmbedderThreadHost::CreateEmbedderOrEngineManagedThreadHost(\n          custom_task_runners, thread_config_callback);\n\n  if (!thread_host || !thread_host->IsValid()) {\n    FML_LOG(ERROR) << \"Could not set up or infer thread configuration to run \"\n                      \"the Flutter engine on.\";\n    return nullptr;\n  }\n  auto task_runners = thread_host->GetTaskRunners();\n\n  if (!task_runners.IsValid()) {\n    FML_LOG(ERROR) << \"Task runner configuration was invalid.\";\n    return nullptr;\n  }\n  return thread_host;\n}\n\nstatic std::unique_ptr<clay::Rasterizer> CreateRasterizer(\n    std::shared_ptr<clay::ServiceManager> service_manager) {\n  return std::make_unique<clay::Rasterizer>(service_manager);\n};\n}  // namespace\n\nstruct ShellArgs {\n  Settings settings;\n  Shell::CreateCallback<PlatformView> on_create_platform_view;\n  Shell::CreateCallbackFnPtr<Rasterizer> on_create_rasterizer;\n  ShellArgs(const Settings& p_settings,\n            Shell::CreateCallback<PlatformView> p_on_create_platform_view,\n            Shell::CreateCallbackFnPtr<Rasterizer> p_on_create_rasterizer)\n      : settings(p_settings),\n        on_create_platform_view(std::move(p_on_create_platform_view)),\n        on_create_rasterizer(std::move(p_on_create_rasterizer)) {}\n};\n\n#ifdef SHELL_ENABLE_METAL\nstd::unique_ptr<EmbedderEngine> EmbedderEngine::CreateEngine(\n    Settings& settings, const ClayCustomTaskRunners* custom_task_runners,\n    fml::RefPtr<clay::EmbedderSurfaceMetal> embedder_surface,\n    const PlatformViewEmbedder::PlatformDispatchTable& platform_dispatch_table,\n    PlatformViewEmbedderDelegate* platform_view_delegate, void* user_data) {\n  auto thread_host = CreateThreadHost(custom_task_runners);\n  if (!thread_host) {\n    return nullptr;\n  }\n  auto task_runners = thread_host->GetTaskRunners();\n\n  std::shared_ptr<clay::ServiceManager> clay_service_manager =\n      clay::ServiceManager::Create(\n          {task_runners.GetPlatformTaskRunner(), task_runners.GetUITaskRunner(),\n           task_runners.GetRasterTaskRunner(), task_runners.GetIOTaskRunner()});\n\n  auto on_create_platform_view = fml::MakeCopyable(\n      [=, embedder_surface = embedder_surface](\n          std::shared_ptr<clay::ServiceManager> service_manager,\n          clay::Shell& shell) mutable {\n        return std::make_unique<clay::PlatformViewEmbedder>(\n            service_manager,              // service manager\n            shell,                        // delegate\n            shell.GetTaskRunners(),       // task runners\n            std::move(embedder_surface),  // embedder surface\n            platform_dispatch_table,      // platform dispatch table\n            user_data,                    // engine\n            platform_view_delegate        // PlatformViewEmbedderDelegate\n        );                                // NOLINT(whitespace/parens)\n      });\n\n  clay::Shell::CreateCallbackFnPtr<clay::Rasterizer> on_create_rasterizer =\n      &CreateRasterizer;\n\n  // Create the engine but don't launch the shell or run the root isolate.\n  auto embedder_engine =\n      std::make_unique<clay::EmbedderEngine>(clay_service_manager,     //\n                                             std::move(thread_host),   //\n                                             std::move(task_runners),  //\n                                             std::move(settings),      //\n                                             on_create_platform_view,  //\n                                             on_create_rasterizer      //\n      );  // NOLINT(whitespace/parens)\n  return embedder_engine;\n}\n#endif\n\n#ifdef SHELL_ENABLE_GL\nstd::unique_ptr<EmbedderEngine> EmbedderEngine::CreateEngine(\n    Settings& settings, const ClayCustomTaskRunners* custom_task_runners,\n    GPUSurfaceGLDelegate* gl_delegate,\n    const PlatformViewEmbedder::PlatformDispatchTable& platform_dispatch_table,\n    PlatformViewEmbedderDelegate* platform_view_delegate, void* user_data) {\n  auto thread_host = CreateThreadHost(custom_task_runners);\n  if (!thread_host) {\n    return nullptr;\n  }\n  auto task_runners = thread_host->GetTaskRunners();\n\n  std::shared_ptr<clay::ServiceManager> clay_service_manager =\n      clay::ServiceManager::Create(\n          {task_runners.GetPlatformTaskRunner(), task_runners.GetUITaskRunner(),\n           task_runners.GetRasterTaskRunner(), task_runners.GetIOTaskRunner()});\n\n  auto on_create_platform_view = fml::MakeCopyable(\n      [=, gl_delegate = gl_delegate](\n          std::shared_ptr<clay::ServiceManager> service_manager,\n          clay::Shell& shell) mutable {\n        return std::make_unique<clay::PlatformViewEmbedder>(\n            service_manager,          // service manager\n            shell,                    // delegate\n            shell.GetTaskRunners(),   // task runners\n            gl_delegate,              // GPUSurfaceGLDelegate\n            platform_dispatch_table,  // platform dispatch table\n            user_data,                // engine\n            platform_view_delegate    // PlatformViewEmbedderDelegate\n        );                            // NOLINT(whitespace/parens)\n      });\n\n  clay::Shell::CreateCallbackFnPtr<clay::Rasterizer> on_create_rasterizer =\n      &CreateRasterizer;\n\n  // Create the engine but don't launch the shell or run the root isolate.\n  auto embedder_engine =\n      std::make_unique<clay::EmbedderEngine>(clay_service_manager,     //\n                                             std::move(thread_host),   //\n                                             std::move(task_runners),  //\n                                             std::move(settings),      //\n                                             on_create_platform_view,  //\n                                             on_create_rasterizer      //\n      );  // NOLINT(whitespace/parens)\n  return embedder_engine;\n}\n#endif\n\nstd::unique_ptr<EmbedderEngine> EmbedderEngine::CreateEngine(\n    Settings& settings, const ClayCustomTaskRunners* custom_task_runners,\n    EmbedderSurfaceSoftwareDelegate* software_delegate,\n    const clay::PlatformViewEmbedder::PlatformDispatchTable&\n        platform_dispatch_table,\n    PlatformViewEmbedderDelegate* platform_view_delegate, void* user_data) {\n  auto thread_host = CreateThreadHost(custom_task_runners);\n  if (!thread_host) {\n    return nullptr;\n  }\n  auto task_runners = thread_host->GetTaskRunners();\n\n  std::shared_ptr<clay::ServiceManager> clay_service_manager =\n      clay::ServiceManager::Create(\n          {task_runners.GetPlatformTaskRunner(), task_runners.GetUITaskRunner(),\n           task_runners.GetRasterTaskRunner(), task_runners.GetIOTaskRunner()});\n\n  auto on_create_platform_view = fml::MakeCopyable(\n      [=, software_delegate = software_delegate](\n          std::shared_ptr<clay::ServiceManager> service_manager,\n          clay::Shell& shell) mutable {\n        return std::make_unique<clay::PlatformViewEmbedder>(\n            service_manager,          // service manager\n            shell,                    // delegate\n            shell.GetTaskRunners(),   // task runners\n            software_delegate,        // EmbedderSurfaceSoftwareDelegate\n            platform_dispatch_table,  // platform dispatch table\n            user_data,                // engine\n            platform_view_delegate    // PlatformViewEmbedderDelegate\n        );                            // NOLINT(whitespace/parens)\n      });\n\n  clay::Shell::CreateCallbackFnPtr<clay::Rasterizer> on_create_rasterizer =\n      &CreateRasterizer;\n\n  // Create the engine but don't launch the shell or run the root isolate.\n  auto embedder_engine =\n      std::make_unique<clay::EmbedderEngine>(clay_service_manager,     //\n                                             std::move(thread_host),   //\n                                             std::move(task_runners),  //\n                                             std::move(settings),      //\n                                             on_create_platform_view,  //\n                                             on_create_rasterizer      //\n      );  // NOLINT(whitespace/parens)\n  return embedder_engine;\n}\n\nEmbedderEngine::EmbedderEngine(\n    std::shared_ptr<clay::ServiceManager> service_manager,\n    std::unique_ptr<EmbedderThreadHost> thread_host,\n    const clay::TaskRunners& task_runners, const clay::Settings& settings,\n    const Shell::CreateCallback<PlatformView>& on_create_platform_view,\n    const Shell::CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer)\n    : service_manager_(service_manager),\n      thread_host_(std::move(thread_host)),\n      task_runners_(task_runners),\n      shell_args_(std::make_unique<ShellArgs>(settings, on_create_platform_view,\n                                              on_create_rasterizer)) {}\n\nEmbedderEngine::~EmbedderEngine() {}\n\nbool EmbedderEngine::LaunchShell() {\n  if (!shell_args_) {\n    FML_DLOG(ERROR) << \"Invalid shell arguments.\";\n    return false;\n  }\n\n  if (shell_) {\n    FML_DLOG(ERROR) << \"Shell already initialized\";\n  }\n\n  shell_ = Shell::Create(service_manager_, task_runners_, shell_args_->settings,\n                         shell_args_->on_create_platform_view,\n                         shell_args_->on_create_rasterizer);\n\n  // Reset the args no matter what. They will never be used to initialize a\n  // shell again.\n  shell_args_.reset();\n\n  return IsValid();\n}\n\nbool EmbedderEngine::CollectShell() {\n  shell_.reset();\n  return IsValid();\n}\n\nbool EmbedderEngine::IsValid() const { return static_cast<bool>(shell_); }\n\nconst TaskRunners& EmbedderEngine::GetTaskRunners() const {\n  return task_runners_;\n}\n\nbool EmbedderEngine::NotifyCreated() {\n  if (!IsValid()) {\n    return false;\n  }\n\n  shell_->GetPlatformView()->NotifyCreated();\n  return true;\n}\n\nbool EmbedderEngine::NotifyDestroyed() {\n  if (!IsValid()) {\n    return false;\n  }\n\n  shell_->GetPlatformView()->NotifyDestroyed();\n  return true;\n}\n\nbool EmbedderEngine::SetViewportMetrics(const clay::ViewportMetrics& metrics) {\n  if (!IsValid()) {\n    return false;\n  }\n\n  auto platform_view = shell_->GetPlatformView();\n  if (!platform_view) {\n    return false;\n  }\n  platform_view->SetViewportMetrics(metrics);\n  return true;\n}\n\nbool EmbedderEngine::SendPointerEvent(const ClayPointerEvent* pointers,\n                                      size_t events_count) {\n  if (pointers == nullptr || events_count == 0) {\n    return false;\n  }\n\n  auto packet = std::make_unique<clay::PointerDataPacket>(events_count);\n\n  const ClayPointerEvent* current = pointers;\n\n  for (size_t i = 0; i < events_count; ++i) {\n    clay::PointerData pointer_data;\n    pointer_data.Clear();\n    // this is current in use only on android embedding.\n    pointer_data.embedder_id = 0;\n    pointer_data.time_stamp = SAFE_ACCESS(current, timestamp, 0);\n    pointer_data.change = PointerDataHelper::ToPointerDataChange(\n        SAFE_ACCESS(current, phase, ClayPointerPhase::kClayPointerPhaseCancel));\n    pointer_data.physical_x = SAFE_ACCESS(current, x, 0.0);\n    pointer_data.physical_y = SAFE_ACCESS(current, y, 0.0);\n    // Delta will be generated in pointer_data_packet_converter.cc.\n    pointer_data.physical_delta_x = 0.0;\n    pointer_data.physical_delta_y = 0.0;\n    pointer_data.device = SAFE_ACCESS(current, device, 0);\n    // Pointer identifier will be generated in\n    // pointer_data_packet_converter.cc.\n    pointer_data.pointer_identifier = 0;\n    pointer_data.signal_kind = PointerDataHelper::ToPointerDataSignalKind(\n        SAFE_ACCESS(current, signal_kind, kClayPointerSignalKindNone));\n    pointer_data.scroll_delta_x = SAFE_ACCESS(current, scroll_delta_x, 0.0);\n    pointer_data.scroll_delta_y = SAFE_ACCESS(current, scroll_delta_y, 0.0);\n    ClayPointerDeviceKind device_kind = SAFE_ACCESS(current, device_kind, 0);\n    // For backwards compatibility with embedders written before the device\n    // kind and buttons were exposed, if the device kind is not set treat it\n    // as a mouse, with a synthesized primary button state based on the phase.\n    if (device_kind == 0) {\n      pointer_data.kind = clay::PointerData::DeviceKind::kMouse;\n      pointer_data.buttons =\n          PointerDataHelper::PointerDataButtonsForLegacyEvent(\n              pointer_data.change);\n\n    } else {\n      pointer_data.kind = PointerDataHelper::ToPointerDataKind(device_kind);\n      if (pointer_data.kind == clay::PointerData::DeviceKind::kTouch) {\n        // For touch events, set the button internally rather than requiring\n        // it at the API level, since it's a confusing construction to expose.\n        if (pointer_data.change == clay::PointerData::Change::kDown ||\n            pointer_data.change == clay::PointerData::Change::kMove) {\n          pointer_data.buttons = clay::kPointerButtonTouchContact;\n        }\n      } else {\n        // Buttons use the same mask values, so pass them through directly.\n        pointer_data.buttons = SAFE_ACCESS(current, buttons, 0);\n      }\n    }\n    pointer_data.pan_x = SAFE_ACCESS(current, pan_x, 0.0);\n    pointer_data.pan_y = SAFE_ACCESS(current, pan_y, 0.0);\n    // Delta will be generated in pointer_data_packet_converter.cc.\n    pointer_data.pan_delta_x = 0.0;\n    pointer_data.pan_delta_y = 0.0;\n    pointer_data.scale = SAFE_ACCESS(current, scale, 0.0);\n    pointer_data.rotation = SAFE_ACCESS(current, rotation, 0.0);\n    pointer_data.is_precise_scroll =\n        SAFE_ACCESS(current, is_precise_scroll, true);\n    packet->SetPointerData(i, pointer_data);\n    current = reinterpret_cast<const ClayPointerEvent*>(\n        reinterpret_cast<const uint8_t*>(current) + current->struct_size);\n  }\n  return DispatchPointerDataPacket(std::move(packet));\n}\n\nbool EmbedderEngine::SendKeyEvent(const ClayKeyEvent* event,\n                                  ClayKeyEventCallback callback,\n                                  void* user_data) {\n  if (event == nullptr) {\n    return false;\n  }\n\n  const char* character = SAFE_ACCESS(event, character, nullptr);\n\n  clay::KeyData key_data;\n  key_data.Clear();\n  key_data.timestamp = static_cast<uint64_t>(SAFE_ACCESS(event, timestamp, 0));\n  key_data.type = KeyDataHelper::MapKeyEventType(\n      SAFE_ACCESS(event, type, ClayKeyEventType::kClayKeyEventTypeUp));\n  key_data.physical = SAFE_ACCESS(event, physical, 0);\n  key_data.logical = SAFE_ACCESS(event, logical, 0);\n  key_data.synthesized = SAFE_ACCESS(event, synthesized, false);\n\n  auto packet = std::make_unique<clay::KeyDataPacket>(key_data, character);\n  auto response = [callback, user_data](bool handled) {\n    if (callback != nullptr) {\n      callback(handled, user_data);\n    }\n  };\n\n  // Clay direct dispatch a key data packet\n  return DispatchKeyDataPacket(std::move(packet), response);\n}\n\nbool EmbedderEngine::DispatchPointerDataPacket(\n    std::unique_ptr<clay::PointerDataPacket> packet) {\n  if (!IsValid() || !packet) {\n    return false;\n  }\n\n  auto platform_view = shell_->GetPlatformView();\n  if (!platform_view) {\n    return false;\n  }\n\n  platform_view->DispatchPointerDataPacket(std::move(packet));\n  return true;\n}\n\nbool EmbedderEngine::DispatchKeyDataPacket(\n    std::unique_ptr<clay::KeyDataPacket> packet,\n    PlatformView::Delegate::KeyDataResponse callback) {\n  if (!IsValid() || !packet) {\n    return false;\n  }\n\n  auto platform_view = shell_->GetPlatformView();\n  if (!platform_view) {\n    return false;\n  }\n\n  platform_view->DispatchKeyDataPacket(std::move(packet), std::move(callback));\n  return true;\n}\n\nbool EmbedderEngine::OnVsyncEvent(intptr_t baton,\n                                  fml::TimePoint frame_start_time,\n                                  fml::TimePoint frame_target_time) {\n  if (!IsValid()) {\n    return false;\n  }\n\n  return VsyncWaiterEmbedder::OnEmbedderVsync(\n      task_runners_, baton, frame_start_time, frame_target_time);\n}\n\nbool EmbedderEngine::ReloadSystemFonts() {\n  if (!IsValid()) {\n    return false;\n  }\n\n  return shell_->ReloadSystemFonts();\n}\n\nbool EmbedderEngine::PostRenderThreadTask(lynx::base::closure task) {\n  if (!IsValid()) {\n    return false;\n  }\n\n  shell_->GetTaskRunners().GetRasterTaskRunner()->PostTask(std::move(task));\n  return true;\n}\n\nbool EmbedderEngine::PostPlatformThreadTask(lynx::base::closure task) {\n  if (!IsValid()) {\n    return false;\n  }\n\n  shell_->GetTaskRunners().GetPlatformTaskRunner()->PostTask(std::move(task));\n  return true;\n}\n\nbool EmbedderEngine::PostUIThreadTask(lynx::base::closure task) {\n  if (!IsValid()) {\n    return false;\n  }\n  shell_->GetTaskRunners().GetUITaskRunner()->PostTask(std::move(task));\n  return true;\n}\n\nbool EmbedderEngine::RunTask(const ClayTask* task) {\n  // The shell doesn't need to be running or valid for access to the thread\n  // host. This is why there is no `IsValid` check here. This allows embedders\n  // to perform custom task runner interop before the shell is running.\n  if (task == nullptr) {\n    return false;\n  }\n  return thread_host_->PostTask(reinterpret_cast<int64_t>(task->runner),\n                                task->task);\n}\n\nbool EmbedderEngine::ScheduleFrame() {\n  if (!IsValid()) {\n    return false;\n  }\n\n  auto platform_view = shell_->GetPlatformView();\n  if (!platform_view) {\n    return false;\n  }\n  platform_view->ScheduleFrame();\n  return true;\n}\n\nShell& EmbedderEngine::GetShell() {\n  FML_DCHECK(shell_);\n  return *shell_.get();\n}\n\nconst std::shared_ptr<clay::ServiceManager>& EmbedderEngine::GetServiceManager()\n    const {\n  FML_DCHECK(service_manager_);\n  return service_manager_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_engine.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_ENGINE_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_ENGINE_H_\n\n#include <memory>\n#include <unordered_map>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/common/settings.h\"\n#include \"clay/common/thread_host.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/common/shell.h\"\n#include \"clay/shell/platform/embedder/embedder_thread_host.h\"\n#include \"clay/shell/platform/embedder/platform_view_embedder.h\"\nnamespace clay {\n\nstruct ShellArgs;\n\n// The object that is returned to the embedder as an opaque pointer to the\n// instance of the Flutter engine.\nclass EmbedderEngine {\n public:\n#ifdef SHELL_ENABLE_METAL\n  static std::unique_ptr<EmbedderEngine> CreateEngine(\n      Settings& settings, const ClayCustomTaskRunners* custom_task_runners,\n      fml::RefPtr<clay::EmbedderSurfaceMetal> embedder_surface,\n      const clay::PlatformViewEmbedder::PlatformDispatchTable&\n          platform_dispatch_table,\n      PlatformViewEmbedderDelegate* platform_view_delegate, void* user_data);\n#endif\n#ifdef SHELL_ENABLE_GL\n  static std::unique_ptr<EmbedderEngine> CreateEngine(\n      Settings& settings, const ClayCustomTaskRunners* custom_task_runners,\n      GPUSurfaceGLDelegate* gl_delegate,\n      const clay::PlatformViewEmbedder::PlatformDispatchTable&\n          platform_dispatch_table,\n      PlatformViewEmbedderDelegate* platform_view_delegate, void* user_data);\n#endif\n\n  static std::unique_ptr<EmbedderEngine> CreateEngine(\n      Settings& settings, const ClayCustomTaskRunners* custom_task_runners,\n      EmbedderSurfaceSoftwareDelegate* software_delegate,\n      const clay::PlatformViewEmbedder::PlatformDispatchTable&\n          platform_dispatch_table,\n      PlatformViewEmbedderDelegate* platform_view_delegate, void* user_data);\n\n  EmbedderEngine(\n      std::shared_ptr<clay::ServiceManager> service_manager,\n      std::unique_ptr<EmbedderThreadHost> thread_host,\n      const TaskRunners& task_runners, const Settings& settings,\n      const Shell::CreateCallback<PlatformView>& on_create_platform_view,\n      const Shell::CreateCallbackFnPtr<Rasterizer>& on_create_rasterizer);\n\n  ~EmbedderEngine();\n\n  bool LaunchShell();\n\n  bool CollectShell();\n\n  const TaskRunners& GetTaskRunners() const;\n\n  bool NotifyCreated();\n\n  bool NotifyDestroyed();\n\n  bool IsValid() const;\n\n  bool SetViewportMetrics(const clay::ViewportMetrics& metrics);\n\n  bool SendPointerEvent(const ClayPointerEvent* pointers, size_t events_count);\n  bool SendKeyEvent(const ClayKeyEvent* event, ClayKeyEventCallback callback,\n                    void* user_data);\n\n  bool DispatchPointerDataPacket(\n      std::unique_ptr<clay::PointerDataPacket> packet);\n  bool DispatchKeyDataPacket(std::unique_ptr<clay::KeyDataPacket> packet,\n                             PlatformView::Delegate::KeyDataResponse callback);\n\n  bool OnVsyncEvent(intptr_t baton, fml::TimePoint frame_start_time,\n                    fml::TimePoint frame_target_time);\n\n  bool ReloadSystemFonts();\n\n  bool PostRenderThreadTask(lynx::base::closure task);\n\n  bool PostPlatformThreadTask(lynx::base::closure task);\n\n  bool PostUIThreadTask(lynx::base::closure task);\n\n  bool RunTask(const ClayTask* task);\n\n  bool ScheduleFrame();\n\n  Shell& GetShell();\n\n  const std::shared_ptr<clay::ServiceManager>& GetServiceManager() const;\n\n private:\n  const std::shared_ptr<clay::ServiceManager> service_manager_;\n  const std::unique_ptr<EmbedderThreadHost> thread_host_;\n  TaskRunners task_runners_;\n  std::unique_ptr<ShellArgs> shell_args_;\n  std::unique_ptr<Shell> shell_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(EmbedderEngine);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_ENGINE_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_struct_macros.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_STRUCT_MACROS_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_STRUCT_MACROS_H_\n\n#include <type_traits>\n\n// Checks if the given struct contains a member, whether set or not.\n#define STRUCT_HAS_MEMBER(pointer, member)                           \\\n  ((offsetof(std::remove_pointer<decltype(pointer)>::type, member) + \\\n        sizeof(pointer->member) <=                                   \\\n    pointer->struct_size))\n\n#define SAFE_ACCESS(pointer, member, default_value)                 \\\n  ([=]() {                                                          \\\n    if (STRUCT_HAS_MEMBER(pointer, member)) {                       \\\n      return pointer->member;                                       \\\n    }                                                               \\\n    return static_cast<decltype(pointer->member)>((default_value)); \\\n  })()\n\n/// Checks if the member exists and is non-null.\n#define SAFE_EXISTS(pointer, member) \\\n  (SAFE_ACCESS(pointer, member, nullptr) != nullptr)\n\n/// Checks if exactly one of member1 or member2 exists and is non-null.\n#define SAFE_EXISTS_ONE_OF(pointer, member1, member2) \\\n  (SAFE_EXISTS(pointer, member1) != SAFE_EXISTS(pointer, member2))\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_STRUCT_MACROS_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_surface.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/embedder/embedder_surface.h\"\n\nnamespace clay {\n\nEmbedderSurface::EmbedderSurface() = default;\n\nEmbedderSurface::~EmbedderSurface() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_surface.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_H_\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/shell/common/output_surface.h\"\n\nnamespace clay {\n\nclass EmbedderSurface : public OutputSurface {\n public:\n  EmbedderSurface();\n\n  virtual ~EmbedderSurface();\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(EmbedderSurface);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_surface_gl.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/embedder/embedder_surface_gl.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/shell/platform/embedder/shell_platform_embedder_rendering_backend.h\"\n\nnamespace clay {\n\nEmbedderSurfaceGL::EmbedderSurfaceGL(GPUSurfaceGLDelegate* delegate)\n    : delegate_(delegate) {\n  valid_ = true;\n}\n\nEmbedderSurfaceGL::~EmbedderSurfaceGL() = default;\n\n// |OutputSurface|\nbool EmbedderSurfaceGL::IsValid() const { return valid_; }\n\n// |GPUSurfaceGLDelegate|\nstd::unique_ptr<GLContextResult> EmbedderSurfaceGL::GLContextMakeCurrent() {\n  return delegate_->GLContextMakeCurrent();\n}\n\n// |GPUSurfaceGLDelegate|\nbool EmbedderSurfaceGL::GLContextClearCurrent() {\n  return delegate_->GLContextClearCurrent();\n}\n\nvoid EmbedderSurfaceGL::GLContextSetDamageRegion(\n    const std::optional<skity::Rect>& region) {\n  delegate_->GLContextSetDamageRegion(region);\n}\n\n// |GPUSurfaceGLDelegate|\nbool EmbedderSurfaceGL::GLContextPresent(const GLPresentInfo& present_info) {\n  return delegate_->GLContextPresent(present_info);\n}\n\n// |GPUSurfaceGLDelegate|\nGLFBOInfo EmbedderSurfaceGL::GLContextFBO(GLFrameInfo frame_info) const {\n  return delegate_->GLContextFBO(frame_info);\n}\n\n// |GPUSurfaceGLDelegate|\nbool EmbedderSurfaceGL::GLContextFBOResetAfterPresent() const {\n  return delegate_->GLContextFBOResetAfterPresent();\n}\n\n// |GPUSurfaceGLDelegate|\nskity::Matrix EmbedderSurfaceGL::GLContextSurfaceTransformation() const {\n  return delegate_->GLContextSurfaceTransformation();\n}\n\n// |GPUSurfaceGLDelegate|\nEmbedderSurfaceGL::GLProcResolver EmbedderSurfaceGL::GetGLProcResolver() const {\n  return delegate_->GetGLProcResolver();\n}\n\n// |GPUSurfaceGLDelegate|\nSurfaceFrame::FramebufferInfo EmbedderSurfaceGL::GLContextFramebufferInfo()\n    const {\n  return delegate_->GLContextFramebufferInfo();\n}\n\n// |OutputSurface|\nstd::unique_ptr<Surface> EmbedderSurfaceGL::CreateGPUSurface(\n    clay::GrContext* context) {\n#ifndef ENABLE_SKITY\n  const bool render_to_surface = true;\n  return std::make_unique<GPUSurfaceGLSkia>(\n      context ? sk_ref_sp(context) : GetMainGrContext(),\n      this,              // GPU surface GL delegate\n      render_to_surface  // render to surface\n  );\n#else\n  return std::make_unique<GPUSurfaceGLSkity>(\n      this,\n      context ? std::shared_ptr<clay::GrContext>(context) : GetMainGrContext());\n#endif  // ENABLE_SKITY\n}\n\n// |OutputSurface|\nclay::GrContextPtr EmbedderSurfaceGL::GetMainGrContext() {\n  if (!main_context_) {\n#ifndef ENABLE_SKITY\n    main_context_ = GPUSurfaceGLSkia::MakeGLContext(this);\n#else\n    main_context_ = GPUSurfaceGLSkity::MakeGLContext(this);\n#endif  // ENABLE_SKITY\n  }\n  return main_context_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_surface_gl.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_GL_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_GL_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/shell/gpu/gpu_surface_gl_delegate.h\"\n#include \"clay/shell/platform/embedder/embedder_surface.h\"\n\nnamespace clay {\n\nclass EmbedderSurfaceGL final : public EmbedderSurface,\n                                public GPUSurfaceGLDelegate {\n public:\n  explicit EmbedderSurfaceGL(GPUSurfaceGLDelegate* delegate);\n\n  ~EmbedderSurfaceGL() override;\n\n private:\n  bool valid_ = false;\n  GPUSurfaceGLDelegate* delegate_;\n\n  clay::GrContextPtr main_context_;\n\n  // |OutputSurface|\n  bool IsValid() const override;\n\n  // |OutputSurface|\n  std::unique_ptr<Surface> CreateGPUSurface(clay::GrContext*) override;\n\n  // |OutputSurface|\n  clay::GrContextPtr GetMainGrContext() override;\n\n  // |GPUSurfaceGLDelegate|\n  std::unique_ptr<GLContextResult> GLContextMakeCurrent() override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextClearCurrent() override;\n\n  // |GPUSurfaceGLDelegate|\n  void GLContextSetDamageRegion(\n      const std::optional<skity::Rect>& region) override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextPresent(const GLPresentInfo& present_info) override;\n\n  // |GPUSurfaceGLDelegate|\n  GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextFBOResetAfterPresent() const override;\n\n  // |GPUSurfaceGLDelegate|\n  skity::Matrix GLContextSurfaceTransformation() const override;\n\n  // |GPUSurfaceGLDelegate|\n  GLProcResolver GetGLProcResolver() const override;\n\n  // |GPUSurfaceGLDelegate|\n  SurfaceFrame::FramebufferInfo GLContextFramebufferInfo() const override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(EmbedderSurfaceGL);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_GL_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_surface_metal.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_METAL_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_METAL_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/shell/gpu/gpu_surface_metal_delegate.h\"\n#include \"clay/shell/platform/embedder/embedder_surface.h\"\n\nnamespace clay {\n\nclass EmbedderSurfaceMetalDelegate {\n public:\n  virtual ~EmbedderSurfaceMetalDelegate() = default;\n\n  virtual GPUMTLDeviceHandle GetMTLDevice() const = 0;\n\n  virtual GPUMTLCommandQueueHandle GetMTLCommandQueue() const = 0;\n\n  virtual GPUMTLTextureInfo GetMTLTexture(\n      const skity::Vec2& frame_size) const = 0;\n\n  virtual bool PresentTexture(GPUMTLTextureInfo texture) const = 0;\n\n  virtual bool EnablePartialRepaint() const = 0;\n};\n\nclass EmbedderSurfaceMetal final : public EmbedderSurface,\n                                   public GPUSurfaceMetalDelegate {\n public:\n  explicit EmbedderSurfaceMetal(EmbedderSurfaceMetalDelegate* delegate);\n\n  ~EmbedderSurfaceMetal() override;\n\n private:\n  EmbedderSurfaceMetalDelegate* delegate_;\n  bool valid_ = false;\n  clay::GrContextPtr main_context_;\n\n  // |OutputSurface|\n  bool IsValid() const override;\n\n  // |OutputSurface|\n  std::unique_ptr<Surface> CreateGPUSurface(clay::GrContext* context) override;\n\n  // |OutputSurface|\n  clay::GrContextPtr GetMainGrContext() override;\n\n  // |GPUSurfaceMetalDelegate|\n  GPUCAMetalLayerHandle GetCAMetalLayer(\n      const skity::Vec2& frame_size) const override;\n\n  // |GPUSurfaceMetalDelegate|\n  GPUMTLTextureInfo GetMTLTexture(const skity::Vec2& frame_size) const override;\n\n  // |GPUSurfaceMetalDelegate|\n  bool PresentTexture(GPUMTLTextureInfo texture) const override;\n\n  // |GPUSurfaceMetalDelegate|\n  bool EnablePartialRepaint() const override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(EmbedderSurfaceMetal);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_METAL_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_surface_metal.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <utility>\n\n#include \"clay/shell/platform/embedder/embedder_surface_metal.h\"\n\n#import \"FlutterMacros.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/gpu/gpu_surface_metal_delegate.h\"\n\n#ifndef ENABLE_SKITY\n#include \"clay/shell/gpu/gpu_surface_metal_skia.h\"\n#import \"clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkia.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n#else\n#include \"clay/shell/gpu/gpu_surface_metal_skity.h\"\n#include \"clay/shell/platform/darwin/graphics/FlutterDarwinContextMetalSkity.h\"\n#endif  // ENABLE_SKITY\n\nFLUTTER_ASSERT_ARC\nnamespace clay {\n\nEmbedderSurfaceMetal::EmbedderSurfaceMetal(EmbedderSurfaceMetalDelegate* delegate)\n    : GPUSurfaceMetalDelegate(MTLRenderTargetType::kMTLTexture), delegate_(delegate) {\n#ifndef ENABLE_SKITY\n  main_context_ = [FlutterDarwinContextMetalSkia\n      createGrContext:(__bridge id<MTLDevice>)delegate_->GetMTLDevice()\n         commandQueue:(__bridge id<MTLCommandQueue>)delegate_->GetMTLCommandQueue()];\n#else\n  main_context_ = [FlutterDarwinContextMetalSkity\n      createGPUContext:(__bridge id<MTLDevice>)delegate_->GetMTLDevice()\n          commandQueue:(__bridge id<MTLCommandQueue>)delegate_->GetMTLCommandQueue()];\n#endif\n  valid_ = main_context_ != nullptr;\n}\n\nGPUCAMetalLayerHandle EmbedderSurfaceMetal::GetCAMetalLayer(const skity::Vec2& frame_size) const {\n  return nullptr;\n}\n\nclay::GrContextPtr EmbedderSurfaceMetal::GetMainGrContext() { return main_context_; }\n\nEmbedderSurfaceMetal::~EmbedderSurfaceMetal() = default;\n\nbool EmbedderSurfaceMetal::IsValid() const { return valid_; }\n\nstd::unique_ptr<Surface> EmbedderSurfaceMetal::CreateGPUSurface(clay::GrContext* context)\n    API_AVAILABLE(ios(13.0)) {\n#ifndef ENABLE_SKITY\n  if (@available(iOS 13.0, *)) {\n  } else {\n    return nullptr;\n  }\n  if (!IsValid()) {\n    return nullptr;\n  }\n\n  auto surface = std::make_unique<GPUSurfaceMetalSkia>(\n      this, context ? sk_ref_sp(context) : main_context_, MsaaSampleCount::kNone, true);\n\n  if (!surface->IsValid()) {\n    return nullptr;\n  }\n\n  return surface;\n#else\n  if (!IsValid()) {\n    return nullptr;\n  }\n  auto surface = std::make_unique<GPUSurfaceMetalSkity>(\n      this, context ? std::shared_ptr<clay::GrContext>(context) : main_context_,\n#if defined(OS_OSX)\n      MsaaSampleCount::kFour,\n#else\n      MsaaSampleCount::kNone,\n#endif\n      true);\n\n  if (!surface->IsValid()) {\n    return nullptr;\n  }\n\n  return surface;\n#endif  // ENABLE_SKITY\n}\n\nGPUMTLTextureInfo EmbedderSurfaceMetal::GetMTLTexture(const skity::Vec2& frame_info) const {\n  return delegate_->GetMTLTexture(frame_info);\n}\n\nbool EmbedderSurfaceMetal::PresentTexture(GPUMTLTextureInfo texture) const {\n  return delegate_->PresentTexture(texture);\n}\n\nbool EmbedderSurfaceMetal::EnablePartialRepaint() const {\n  return delegate_->EnablePartialRepaint();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_surface_software.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/embedder/embedder_surface_software.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#ifndef ENABLE_SKITY\n#include \"third_party/skia/include/core/SkColorSpace.h\"\n#include \"third_party/skia/include/core/SkImageInfo.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n#endif  // ENABLE_SKITY\n\nnamespace clay {\n\nEmbedderSurfaceSoftware::EmbedderSurfaceSoftware(\n    EmbedderSurfaceSoftwareDelegate* delegate)\n    : delegate_(delegate) {\n  if (!delegate_) {\n    return;\n  }\n  valid_ = true;\n}\n\nEmbedderSurfaceSoftware::~EmbedderSurfaceSoftware() = default;\n\n// |OutputSurface|\nbool EmbedderSurfaceSoftware::IsValid() const { return valid_; }\n\n// |OutputSurface|\nstd::unique_ptr<Surface> EmbedderSurfaceSoftware::CreateGPUSurface(\n    clay::GrContext*) {\n  if (!IsValid()) {\n    return nullptr;\n  }\n  auto surface = std::make_unique<GPUSurfaceSoftware>(this, true);\n\n  if (!surface->IsValid()) {\n    return nullptr;\n  }\n\n  return surface;\n}\n\n// |GPUSurfaceSoftwareDelegate|\nclay::GrSurfacePtr EmbedderSurfaceSoftware::AcquireBackingStore(\n    const skity::Vec2& skity_size) {\n  TRACE_EVENT(\"flutter\", \"EmbedderSurfaceSoftware::AcquireBackingStore\");\n  if (!IsValid()) {\n    FML_LOG(ERROR)\n        << \"Could not acquire backing store for the software surface.\";\n    return nullptr;\n  }\n#ifndef ENABLE_SKITY\n  SkISize size = SkISize::Make(skity_size.x, skity_size.y);\n  if (sk_surface_ != nullptr &&\n      SkISize::Make(sk_surface_->width(), sk_surface_->height()) == size) {\n    // The old and new surface sizes are the same. Nothing to do here.\n    return sk_surface_;\n  }\n\n  SkImageInfo info = SkImageInfo::MakeN32(\n      size.fWidth, size.fHeight, kPremul_SkAlphaType, SkColorSpace::MakeSRGB());\n  sk_surface_ = SkSurface::MakeRaster(info, nullptr);\n\n  if (sk_surface_ == nullptr) {\n    FML_LOG(ERROR) << \"Could not create backing store for software rendering.\";\n    return nullptr;\n  }\n#endif\n  return sk_surface_;\n}\n\n// |GPUSurfaceSoftwareDelegate|\nbool EmbedderSurfaceSoftware::PresentBackingStore(\n    clay::GrSurfacePtr backing_store) {\n#ifdef ENABLE_SKITY\n  FML_UNIMPLEMENTED();\n  return false;\n#else\n  if (!IsValid()) {\n    FML_LOG(ERROR) << \"Tried to present an invalid software surface.\";\n    return false;\n  }\n\n  SkPixmap pixmap;\n  if (!backing_store->peekPixels(&pixmap)) {\n    FML_LOG(ERROR) << \"Could not peek the pixels of the backing store.\";\n    return false;\n  }\n\n  // Some basic sanity checking.\n  uint64_t expected_pixmap_data_size = pixmap.width() * pixmap.height() * 4;\n\n  const size_t pixmap_size = pixmap.computeByteSize();\n\n  if (expected_pixmap_data_size != pixmap_size) {\n    FML_LOG(ERROR) << \"Software backing store had unexpected size.\";\n    return false;\n  }\n\n  return delegate_->OnPresentBackingStore(pixmap.addr(),      //\n                                          pixmap.rowBytes(),  //\n                                          pixmap.height());   //\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_surface_software.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_SOFTWARE_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_SOFTWARE_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/shell/gpu/gpu_surface_software.h\"\n#include \"clay/shell/platform/embedder/embedder_surface.h\"\n\nnamespace clay {\n\nclass EmbedderSurfaceSoftwareDelegate {\n public:\n  virtual ~EmbedderSurfaceSoftwareDelegate() = default;\n  virtual bool OnPresentBackingStore(const void* allocation, size_t row_bytes,\n                                     size_t height) = 0;\n};\n\nclass EmbedderSurfaceSoftware final : public EmbedderSurface,\n                                      public GPUSurfaceSoftwareDelegate {\n public:\n  explicit EmbedderSurfaceSoftware(EmbedderSurfaceSoftwareDelegate* delegate);\n\n  ~EmbedderSurfaceSoftware() override;\n\n private:\n  bool valid_ = false;\n  EmbedderSurfaceSoftwareDelegate* delegate_;\n  clay::GrSurfacePtr sk_surface_;\n\n  // |OutputSurface|\n  bool IsValid() const override;\n\n  // |OutputSurface|\n  std::unique_ptr<Surface> CreateGPUSurface(clay::GrContext*) override;\n\n  // |GPUSurfaceSoftwareDelegate|\n  clay::GrSurfacePtr AcquireBackingStore(const skity::Vec2& size) override;\n\n  // |GPUSurfaceSoftwareDelegate|\n  bool PresentBackingStore(clay::GrSurfacePtr backing_store) override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(EmbedderSurfaceSoftware);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_SOFTWARE_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_task_runner.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/embedder/embedder_task_runner.h\"\n\n#include <utility>\n\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nEmbedderTaskRunner::EmbedderTaskRunner(DispatchTable table,\n                                       size_t embedder_identifier)\n    : TaskRunner(nullptr /* loop implementation*/),\n      embedder_identifier_(embedder_identifier),\n      dispatch_table_(std::move(table)),\n      placeholder_id_(\n          fml::MessageLoopTaskQueues::GetInstance()->CreateTaskQueue()) {\n  FML_DCHECK(dispatch_table_.post_task_callback);\n  FML_DCHECK(dispatch_table_.runs_task_on_current_thread_callback);\n}\n\nEmbedderTaskRunner::~EmbedderTaskRunner() = default;\n\nsize_t EmbedderTaskRunner::GetEmbedderIdentifier() const {\n  return embedder_identifier_;\n}\n\nvoid EmbedderTaskRunner::PostTask(lynx::base::closure task) {\n  PostTaskForTime(std::move(task), fml::TimePoint::Now());\n}\n\nvoid EmbedderTaskRunner::PostTaskForTime(lynx::base::closure task,\n                                         fml::TimePoint target_time) {\n  if (!task) {\n    return;\n  }\n\n  uint64_t baton = 0;\n\n  {\n    // Release the lock before the jump via the dispatch table.\n    std::scoped_lock lock(tasks_mutex_);\n    if (thread_host_destroyed_) {\n      FML_LOG(ERROR) << \"Embedder attempted to post a task to a destroyed \"\n                        \"thread host. Maybe leaked.\";\n      return;\n    }\n    baton = ++last_baton_;\n    pending_tasks_[baton] = std::make_pair(target_time, std::move(task));\n    dispatch_table_.post_task_callback(this, baton, target_time);\n  }\n}\n\nvoid EmbedderTaskRunner::PostDelayedTask(lynx::base::closure task,\n                                         fml::TimeDelta delay) {\n  PostTaskForTime(std::move(task), fml::TimePoint::Now() + delay);\n}\n\nbool EmbedderTaskRunner::RunsTasksOnCurrentThread() {\n  std::scoped_lock lock(tasks_mutex_);\n  if (thread_host_destroyed_) {\n    FML_LOG(ERROR) << \"Embedder attempted to call RunsTasksOnCurrentThread\"\n                      \"to a destroyed thread host.\";\n    return false;\n  }\n  return dispatch_table_.runs_task_on_current_thread_callback();\n}\n\nvoid EmbedderTaskRunner::OnThreadHostDestroyed() {\n  std::unordered_map<uint64_t, std::pair<fml::TimePoint, lynx::base::closure>>\n      tasks;\n\n  {\n    std::scoped_lock lock(tasks_mutex_);\n    thread_host_destroyed_ = true;\n    tasks = std::move(pending_tasks_);\n  }\n\n  fml::TimePoint now = fml::TimePoint::Now();\n\n  for (auto& it : tasks) {\n    auto& [time, cb] = it.second;\n    if (time <= now) {\n      cb();\n    }\n  }\n}\n\nbool EmbedderTaskRunner::PostTask(uint64_t baton) {\n  lynx::base::closure task;\n\n  {\n    std::scoped_lock lock(tasks_mutex_);\n    if (thread_host_destroyed_) {\n      FML_LOG(ERROR) << \"Embedder attempted to post a task to a destroyed \"\n                        \"thread host. Maybe leaked.\";\n      return false;\n    }\n    const auto& found = pending_tasks_.find(baton);\n    if (found == pending_tasks_.end()) {\n      FML_LOG(ERROR) << \"Embedder attempted to post an unknown task.\";\n      return false;\n    }\n    task = std::move(found->second.second);\n    pending_tasks_.erase(found);\n\n    // Let go of the tasks mutex befor executing the task.\n  }\n\n  FML_DCHECK(task);\n  task();\n  return true;\n}\n\n// |fml::TaskRunner|\nfml::TaskQueueId EmbedderTaskRunner::GetTaskQueueId() {\n  return placeholder_id_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_task_runner.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_TASK_RUNNER_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_TASK_RUNNER_H_\n\n#include <mutex>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/task_runner.h\"\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// A task runner which delegates responsibility of task execution to an\n/// embedder. This is done by managing a dispatch table to the embedder.\n///\nclass EmbedderTaskRunner final : public fml::TaskRunner {\n public:\n  //----------------------------------------------------------------------------\n  /// @brief      A\n  ///\n  struct DispatchTable {\n    //--------------------------------------------------------------------------\n    /// Delegates responsibility of deferred task execution to the embedder.\n    /// Once the embedder gets the task, it must call\n    /// `EmbedderTaskRunner::PostTask` with the supplied `task_baton` on the\n    /// correct thread after the tasks `target_time` point expires.\n    ///\n    std::function<void(EmbedderTaskRunner* task_runner, uint64_t task_baton,\n                       fml::TimePoint target_time)>\n        post_task_callback;\n    //--------------------------------------------------------------------------\n    /// Asks the embedder if tasks posted to it on this task task runner via the\n    /// `post_task_callback` will be executed (after task expiry) on the calling\n    /// thread.\n    ///\n    std::function<bool(void)> runs_task_on_current_thread_callback;\n  };\n\n  //----------------------------------------------------------------------------\n  /// @brief      Create a task runner with a dispatch table for delegation of\n  ///             task runner responsibility to the embedder. When embedders\n  ///             specify task runner dispatch tables that service tasks on the\n  ///             same thread, they also must ensure that their\n  ///             `embedder_idetifier`s match. This allows the engine to\n  ///             determine task runner equality without actually posting tasks\n  ///             to the task runner.\n  ///\n  /// @param[in]  table                The task runner dispatch table.\n  /// @param[in]  embedder_identifier  The embedder identifier\n  ///\n  EmbedderTaskRunner(DispatchTable table, size_t embedder_identifier);\n\n  // |fml::TaskRunner|\n  ~EmbedderTaskRunner() override;\n\n  //----------------------------------------------------------------------------\n  /// @brief      The unique identifier provided by the embedder for the task\n  ///             runner. Embedders whose dispatch tables service tasks on the\n  ///             same underlying OS thread must ensure that their identifiers\n  ///             match. This allows the engine to determine task runner\n  ///             equality without posting tasks on the thread.\n  ///\n  /// @return     The embedder identifier.\n  ///\n  size_t GetEmbedderIdentifier() const;\n\n  bool PostTask(uint64_t baton);\n\n  void OnThreadHostDestroyed();\n\n  // |fml::TaskRunner|\n  void PostTask(lynx::base::closure task) override;\n\n private:\n  const size_t embedder_identifier_;\n  DispatchTable dispatch_table_;\n  std::recursive_mutex tasks_mutex_;\n  uint64_t last_baton_ = 0;\n  std::unordered_map<uint64_t, std::pair<fml::TimePoint, lynx::base::closure>>\n      pending_tasks_;\n  fml::TaskQueueId placeholder_id_;\n  bool thread_host_destroyed_ = false;\n\n  // |fml::TaskRunner|\n  void PostTaskForTime(lynx::base::closure task,\n                       fml::TimePoint target_time) override;\n\n  // |fml::TaskRunner|\n  void PostDelayedTask(lynx::base::closure task, fml::TimeDelta delay) override;\n\n  // |fml::TaskRunner|\n  bool RunsTasksOnCurrentThread() override;\n\n  // |fml::TaskRunner|\n  fml::TaskQueueId GetTaskQueueId() override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(EmbedderTaskRunner);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_TASK_RUNNER_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_thread_host.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/common/thread_host.h\"\n#define FML_USED_ON_EMBEDDER\n\n#include <algorithm>\n#include <utility>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/platform/embedder/embedder_struct_macros.h\"\n#include \"clay/shell/platform/embedder/embedder_thread_host.h\"\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// @brief      Attempts to create a task runner from an embedder task runner\n///             description. The first boolean in the pair indicate whether the\n///             embedder specified an invalid task runner description. In this\n///             case, engine launch must be aborted. If the embedder did not\n///             specify any task runner, an engine managed task runner and\n///             thread must be selected instead.\n///\n/// @param[in]  description  The description\n///\n/// @return     A pair that returns if the embedder has specified a task runner\n///             (null otherwise) and whether to terminate further engine launch.\n///\nstatic std::pair<bool, fml::RefPtr<EmbedderTaskRunner>>\nCreateEmbedderTaskRunner(const ClayTaskRunnerDescription* description) {\n  if (description == nullptr) {\n    // This is not embedder error. The embedder API will just have to create a\n    // plain old task runner (and create a thread for it) instead of using a\n    // task runner provided to us by the embedder.\n    return {true, {}};\n  }\n\n  if (SAFE_ACCESS(description, runs_task_on_current_thread_callback, nullptr) ==\n      nullptr) {\n    FML_LOG(ERROR) << \"ClayTaskRunnerDescription.runs_task_on_current_\"\n                      \"thread_callback was nullptr.\";\n    return {false, {}};\n  }\n\n  if (SAFE_ACCESS(description, post_task_callback, nullptr) == nullptr) {\n    FML_LOG(ERROR)\n        << \"ClayTaskRunnerDescription.post_task_callback was nullptr.\";\n    return {false, {}};\n  }\n\n  auto user_data = SAFE_ACCESS(description, user_data, nullptr);\n\n  // ABI safety checks have been completed.\n  auto post_task_callback_c = description->post_task_callback;\n  auto runs_task_on_current_thread_callback_c =\n      description->runs_task_on_current_thread_callback;\n\n  EmbedderTaskRunner::DispatchTable task_runner_dispatch_table = {\n      // .post_task_callback\n      [post_task_callback_c, user_data](EmbedderTaskRunner* task_runner,\n                                        uint64_t task_baton,\n                                        fml::TimePoint target_time) -> void {\n        ClayTask task = {\n            // runner\n            reinterpret_cast<ClayTaskRunner>(task_runner),\n            // task\n            task_baton,\n        };\n        post_task_callback_c(task, target_time.ToEpochDelta().ToNanoseconds(),\n                             user_data);\n      },\n      // runs_task_on_current_thread_callback\n      [runs_task_on_current_thread_callback_c, user_data]() -> bool {\n        return runs_task_on_current_thread_callback_c(user_data);\n      }};\n\n  return {true, fml::MakeRefCounted<EmbedderTaskRunner>(\n                    task_runner_dispatch_table,\n                    SAFE_ACCESS(description, identifier, 0u))};\n}\n\nstd::unique_ptr<EmbedderThreadHost>\nEmbedderThreadHost::CreateEmbedderOrEngineManagedThreadHost(\n    const ClayCustomTaskRunners* custom_task_runners,\n    const clay::ThreadConfigSetter& config_setter) {\n  {\n    auto host =\n        CreateEmbedderManagedThreadHost(custom_task_runners, config_setter);\n    if (host && host->IsValid()) {\n      return host;\n    }\n  }\n\n  // Only attempt to create the engine managed host if the embedder did not\n  // specify a custom configuration. Don't fallback to the engine managed\n  // configuration if the embedder attempted to specify a configuration but\n  // messed up with an incorrect configuration.\n  if (custom_task_runners == nullptr) {\n    auto host = CreateEngineManagedThreadHost(config_setter);\n    if (host && host->IsValid()) {\n      return host;\n    }\n  }\n\n  return nullptr;\n}\n\nstatic fml::RefPtr<fml::TaskRunner> GetCurrentThreadTaskRunner() {\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  return fml::MessageLoop::GetCurrent().GetTaskRunner();\n}\n\nconstexpr const char* kFlutterThreadName = \"io.flutter\";\n\nfml::Thread::ThreadConfig MakeThreadConfig(\n    clay::ThreadHost::Type type, fml::Thread::ThreadPriority priority) {\n  return fml::Thread::ThreadConfig(\n      clay::ThreadHost::ThreadHostConfig::MakeThreadName(type,\n                                                         kFlutterThreadName),\n      priority);\n}\n\n// static\nstd::unique_ptr<EmbedderThreadHost>\nEmbedderThreadHost::CreateEmbedderManagedThreadHost(\n    const ClayCustomTaskRunners* custom_task_runners,\n    const clay::ThreadConfigSetter& config_setter) {\n  if (custom_task_runners == nullptr) {\n    return nullptr;\n  }\n\n  auto thread_host_config = ThreadHost::ThreadHostConfig(config_setter);\n\n  thread_host_config.SetIOConfig(MakeThreadConfig(\n      ThreadHost::Type::IO, fml::Thread::ThreadPriority::BACKGROUND));\n\n  auto platform_task_runner_pair = CreateEmbedderTaskRunner(\n      SAFE_ACCESS(custom_task_runners, platform_task_runner, nullptr));\n  auto render_task_runner_pair = CreateEmbedderTaskRunner(\n      SAFE_ACCESS(custom_task_runners, render_task_runner, nullptr));\n\n  if (!platform_task_runner_pair.first || !render_task_runner_pair.first) {\n    // User error while supplying a custom task runner. Return an invalid thread\n    // host. This will abort engine initialization. Don't fallback to defaults\n    // if the user wanted to specify a task runner but just messed up instead.\n    return nullptr;\n  }\n\n  // If the embedder has not supplied a raster task runner, one needs to be\n  // created.\n  if (!render_task_runner_pair.second) {\n    thread_host_config.SetRasterConfig(MakeThreadConfig(\n        ThreadHost::Type::RASTER, fml::Thread::ThreadPriority::HIGH));\n  }\n\n  // If both the platform task runner and the raster task runner are specified\n  // and have the same identifier, store only one.\n  if (platform_task_runner_pair.second && render_task_runner_pair.second) {\n    if (platform_task_runner_pair.second->GetEmbedderIdentifier() ==\n        render_task_runner_pair.second->GetEmbedderIdentifier()) {\n      render_task_runner_pair.second = platform_task_runner_pair.second;\n    }\n  }\n\n  // Create a thread host with just the threads that need to be managed by the\n  // engine. The embedder has provided the rest.\n  ThreadHost thread_host(thread_host_config);\n\n  // If the embedder has supplied a platform task runner, use that. If not, use\n  // the current thread task runner.\n  auto platform_task_runner = platform_task_runner_pair.second\n                                  ? static_cast<fml::RefPtr<fml::TaskRunner>>(\n                                        platform_task_runner_pair.second)\n                                  : GetCurrentThreadTaskRunner();\n\n  auto ui_task_runner = platform_task_runner;\n\n  // If the embedder has supplied a raster task runner, use that. If not, use\n  // the one from our thread host.\n  auto render_task_runner = render_task_runner_pair.second\n                                ? static_cast<fml::RefPtr<fml::TaskRunner>>(\n                                      render_task_runner_pair.second)\n                                : thread_host.raster_thread->GetTaskRunner();\n\n  clay::TaskRunners task_runners(\n      kFlutterThreadName,\n      platform_task_runner,  // platform\n      render_task_runner,    // raster\n      ui_task_runner,\n      thread_host.io_thread->GetTaskRunner());  // io (always engine managed)\n\n  if (!task_runners.IsValid()) {\n    return nullptr;\n  }\n\n  auto embedder_host = std::make_unique<EmbedderThreadHost>(\n      std::move(thread_host), std::move(task_runners),\n      platform_task_runner_pair.second, render_task_runner_pair.second);\n\n  if (embedder_host->IsValid()) {\n    return embedder_host;\n  }\n\n  return nullptr;\n}\n\n// static\nstd::unique_ptr<EmbedderThreadHost>\nEmbedderThreadHost::CreateEngineManagedThreadHost(\n    const clay::ThreadConfigSetter& config_setter) {\n  // Crate a thraed host config, and specified the thread name and priority.\n  auto thread_host_config = ThreadHost::ThreadHostConfig(config_setter);\n  thread_host_config.SetRasterConfig(MakeThreadConfig(\n      clay::ThreadHost::RASTER, fml::Thread::ThreadPriority::HIGH));\n  thread_host_config.SetIOConfig(MakeThreadConfig(\n      clay::ThreadHost::IO, fml::Thread::ThreadPriority::BACKGROUND));\n\n  // Create a thread host with the current thread as the platform thread and all\n  // other threads managed.\n  ThreadHost thread_host(thread_host_config);\n\n  // For embedder platforms that don't have native message loop interop, this\n  // will reference a task runner that points to a null message loop\n  // implementation.\n  auto platform_task_runner = GetCurrentThreadTaskRunner();\n  auto ui_task_runner = platform_task_runner;\n\n  clay::TaskRunners task_runners(\n      kFlutterThreadName,\n      platform_task_runner,                        // platform\n      thread_host.raster_thread->GetTaskRunner(),  // raster\n      ui_task_runner,                              // ui\n      thread_host.io_thread->GetTaskRunner());     // io\n\n  if (!task_runners.IsValid()) {\n    return nullptr;\n  }\n\n  auto embedder_host = std::make_unique<EmbedderThreadHost>(\n      std::move(thread_host), std::move(task_runners), nullptr, nullptr);\n\n  if (embedder_host->IsValid()) {\n    return embedder_host;\n  }\n\n  return nullptr;\n}\n\nEmbedderThreadHost::EmbedderThreadHost(\n    ThreadHost thread_host, const clay::TaskRunners& runners,\n    fml::RefPtr<EmbedderTaskRunner> platform_task_runner,\n    fml::RefPtr<EmbedderTaskRunner> render_task_runner)\n    : thread_host_(std::move(thread_host)),\n      runners_(runners),\n      platform_task_runner_(platform_task_runner),\n      render_task_runner_(render_task_runner) {\n  if (platform_task_runner) {\n    runners_map_[reinterpret_cast<int64_t>(platform_task_runner.get())] =\n        platform_task_runner;\n  }\n  if (render_task_runner) {\n    runners_map_[reinterpret_cast<int64_t>(render_task_runner.get())] =\n        render_task_runner;\n  }\n}\n\nEmbedderThreadHost::~EmbedderThreadHost() {\n  // Make sure these threads's pending tasks are executed before the thread host\n  // is destroyed.\n  thread_host_.ui_thread.reset();\n  if (render_task_runner_) {\n    render_task_runner_->PostTask(\n        [render_task_runner = render_task_runner_.get()]() {\n          render_task_runner->OnThreadHostDestroyed();\n        });\n  }\n  thread_host_.raster_thread.reset();\n  if (platform_task_runner_) {\n    platform_task_runner_->PostSyncTask(\n        [platform_task_runner = platform_task_runner_.get()]() {\n          platform_task_runner->OnThreadHostDestroyed();\n        });\n  }\n}\n\nbool EmbedderThreadHost::IsValid() const { return runners_.IsValid(); }\n\nconst clay::TaskRunners& EmbedderThreadHost::GetTaskRunners() const {\n  return runners_;\n}\n\nbool EmbedderThreadHost::PostTask(int64_t runner, uint64_t task) const {\n  auto found = runners_map_.find(runner);\n  if (found == runners_map_.end()) {\n    return false;\n  }\n  return found->second->PostTask(task);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/embedder_thread_host.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_THREAD_HOST_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_THREAD_HOST_H_\n\n#include <map>\n#include <memory>\n#include <set>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/common/thread_host.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/platform/embedder/embedder_task_runner.h\"\n\nnamespace clay {\n\ntypedef struct {\n  /// The size of this struct. Must be sizeof(ClayCustomTaskRunners).\n  size_t struct_size;\n  /// Specify the task runner for the thread on which the\n  /// `EmbedderEngine::RunTask` call is made. The same task runner description\n  /// can be specified for both the render and platform task runners. This makes\n  /// the Flutter engine use the same thread for both task runners.\n  const ClayTaskRunnerDescription* platform_task_runner;\n  /// Specify the task runner for the thread on which the render tasks will be\n  /// run. The same task runner description can be specified for both the render\n  /// and platform task runners. This makes the Flutter engine use the same\n  /// thread for both task runners.\n  const ClayTaskRunnerDescription* render_task_runner;\n  /// Specify a callback that is used to set the thread priority for embedder\n  /// task runners.\n  void (*thread_priority_setter)(fml::Thread::ThreadPriority);\n} ClayCustomTaskRunners;\n\nclass EmbedderThreadHost {\n public:\n  static std::unique_ptr<EmbedderThreadHost>\n  CreateEmbedderOrEngineManagedThreadHost(\n      const ClayCustomTaskRunners* custom_task_runners,\n      const clay::ThreadConfigSetter& config_setter =\n          fml::Thread::SetCurrentThreadName);\n\n  EmbedderThreadHost(ThreadHost thread_host, const clay::TaskRunners& runners,\n                     fml::RefPtr<EmbedderTaskRunner> platform_task_runner,\n                     fml::RefPtr<EmbedderTaskRunner> render_task_runner);\n\n  ~EmbedderThreadHost();\n\n  bool IsValid() const;\n\n  const clay::TaskRunners& GetTaskRunners() const;\n\n  bool PostTask(int64_t runner, uint64_t task) const;\n\n private:\n  ThreadHost thread_host_;\n  clay::TaskRunners runners_;\n  std::map<int64_t, fml::RefPtr<EmbedderTaskRunner>> runners_map_;\n  fml::RefPtr<EmbedderTaskRunner> platform_task_runner_;\n  fml::RefPtr<EmbedderTaskRunner> render_task_runner_;\n\n  static std::unique_ptr<EmbedderThreadHost> CreateEmbedderManagedThreadHost(\n      const ClayCustomTaskRunners* custom_task_runners,\n      const clay::ThreadConfigSetter& config_setter =\n          fml::Thread::SetCurrentThreadName);\n\n  static std::unique_ptr<EmbedderThreadHost> CreateEngineManagedThreadHost(\n      const clay::ThreadConfigSetter& config_setter =\n          fml::Thread::SetCurrentThreadName);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(EmbedderThreadHost);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_EMBEDDER_THREAD_HOST_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/platform_view_embedder.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/embedder/platform_view_embedder.h\"\n\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n\nnamespace clay {\n\nPlatformViewEmbedder::PlatformViewEmbedder(\n    std::shared_ptr<clay::ServiceManager> service_manager,\n    PlatformView::Delegate& delegate, const clay::TaskRunners& task_runners,\n    EmbedderSurfaceSoftwareDelegate* software_delegate,\n    PlatformDispatchTable platform_dispatch_table, void* user_data,\n    PlatformViewEmbedderDelegate* embedder_delegate)\n    : PlatformView(service_manager, delegate, task_runners),\n      embedder_surface_(\n          fml::MakeRefCounted<EmbedderSurfaceSoftware>(software_delegate)),\n      platform_dispatch_table_(std::move(platform_dispatch_table)),\n      embedder_delegate_(embedder_delegate) {}\n\n#ifdef SHELL_ENABLE_GL\nPlatformViewEmbedder::PlatformViewEmbedder(\n    std::shared_ptr<clay::ServiceManager> service_manager,\n    PlatformView::Delegate& delegate, const clay::TaskRunners& task_runners,\n    GPUSurfaceGLDelegate* gl_delegate,\n    PlatformDispatchTable platform_dispatch_table, void* user_data,\n    PlatformViewEmbedderDelegate* embedder_delegate)\n    : PlatformView(service_manager, delegate, task_runners),\n      embedder_surface_(fml::MakeRefCounted<EmbedderSurfaceGL>(gl_delegate)),\n      platform_dispatch_table_(std::move(platform_dispatch_table)),\n      embedder_delegate_(embedder_delegate) {}\n#endif\n\n#ifdef SHELL_ENABLE_METAL\nPlatformViewEmbedder::PlatformViewEmbedder(\n    std::shared_ptr<clay::ServiceManager> service_manager,\n    PlatformView::Delegate& delegate, const clay::TaskRunners& task_runners,\n    fml::RefPtr<EmbedderSurfaceMetal> embedder_surface,\n    PlatformDispatchTable platform_dispatch_table, void* user_data,\n    PlatformViewEmbedderDelegate* embedder_delegate)\n    : PlatformView(service_manager, delegate, task_runners),\n      embedder_surface_(std::move(embedder_surface)),\n      platform_dispatch_table_(std::move(platform_dispatch_table)),\n      embedder_delegate_(embedder_delegate) {}\n#endif\n\nPlatformViewEmbedder::~PlatformViewEmbedder() = default;\n\nvoid PlatformViewEmbedder::NotifyDestroyed() {\n  PlatformView::NotifyDestroyed();\n  // This context needs to be deallocated from the raster thread in order to\n  // keep a coherent usage of egl from a single thread.\n  fml::AutoResetWaitableEvent latch;\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetRasterTaskRunner(),\n      [&latch, surface = embedder_surface_.get()]() {\n        auto gr_context = surface->GetMainGrContext();\n        if (gr_context) {\n#ifndef ENABLE_SKITY\n          bool has_released = false;\n#ifdef SHELL_ENABLE_GL\n          if (gr_context->backend() == GrBackendApi::kOpenGL) {\n            auto surface_gl = static_cast<EmbedderSurfaceGL*>(surface);\n            auto status = static_cast<GPUSurfaceGLDelegate*>(surface_gl)\n                              ->GLContextMakeCurrent();\n            if (status->GetResult()) {\n              gr_context->releaseResourcesAndAbandonContext();\n              static_cast<GPUSurfaceGLDelegate*>(surface_gl)\n                  ->GLContextClearCurrent();\n              has_released = true;\n            }\n          }\n#endif\n          if (!has_released) {\n            gr_context->releaseResourcesAndAbandonContext();\n          }\n#endif\n        }\n        latch.Signal();\n      });\n  latch.Wait();\n}\n\n// |PlatformView|\nstd::unique_ptr<std::vector<std::string>>\nPlatformViewEmbedder::ComputePlatformResolvedLocales(\n    const std::vector<std::string>& supported_locale_data) {\n  if (platform_dispatch_table_.compute_platform_resolved_locale_callback !=\n      nullptr) {\n    return platform_dispatch_table_.compute_platform_resolved_locale_callback(\n        supported_locale_data);\n  }\n  std::unique_ptr<std::vector<std::string>> out =\n      std::make_unique<std::vector<std::string>>();\n  return out;\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::OnPreEngineRestart() const {\n  if (platform_dispatch_table_.on_pre_engine_restart_callback != nullptr) {\n    platform_dispatch_table_.on_pre_engine_restart_callback();\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::SetClipboardData(const std::u16string& data) {\n  if (platform_dispatch_table_.clipboard.set_clipboard_data_callback !=\n      nullptr) {\n    platform_dispatch_table_.clipboard.set_clipboard_data_callback(data);\n  }\n}\n\n// |PlatformView|\nstd::u16string PlatformViewEmbedder::GetClipboardData() {\n  if (platform_dispatch_table_.clipboard.get_clipboard_data_callback !=\n      nullptr) {\n    return platform_dispatch_table_.clipboard.get_clipboard_data_callback();\n  }\n  return std::u16string();\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::SetTextInputClient(int client_id,\n                                              const char* input_action,\n                                              const char* input_type) {\n  if (platform_dispatch_table_.textinput.set_text_input_client_callback !=\n      nullptr) {\n    platform_dispatch_table_.textinput.set_text_input_client_callback(\n        client_id, input_action, input_type);\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::ClearTextInputClient() {\n  if (platform_dispatch_table_.textinput.clear_text_input_client_callback !=\n      nullptr) {\n    platform_dispatch_table_.textinput.clear_text_input_client_callback();\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::SetEditableTransform(\n    const float transform_matrix[16]) {\n  if (platform_dispatch_table_.textinput.set_editable_transform_callback !=\n      nullptr) {\n    platform_dispatch_table_.textinput.set_editable_transform_callback(\n        transform_matrix);\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::SetEditingState(\n    uint64_t selection_base, uint64_t composing_extent,\n    const std::string& selection_affinity, const std::string& text,\n    bool selection_directional, uint64_t selection_extent,\n    uint64_t composing_base) {\n  if (platform_dispatch_table_.textinput.set_editing_state_callback !=\n      nullptr) {\n    platform_dispatch_table_.textinput.set_editing_state_callback(\n        selection_base, composing_extent, selection_affinity, text,\n        selection_directional, selection_extent, composing_base);\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::SetCaretRect(float x, float y, float width,\n                                        float height) {\n  if (platform_dispatch_table_.textinput.set_caret_rect_callback != nullptr) {\n    platform_dispatch_table_.textinput.set_caret_rect_callback(x, y, width,\n                                                               height);\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::setMarkedTextRect(float x, float y, float width,\n                                             float height) {\n  if (platform_dispatch_table_.textinput.set_marked_text_rect_callback !=\n      nullptr) {\n    platform_dispatch_table_.textinput.set_marked_text_rect_callback(\n        x, y, width, height);\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::ShowTextInput() {\n  if (platform_dispatch_table_.textinput.show_text_input_callback != nullptr) {\n    platform_dispatch_table_.textinput.show_text_input_callback();\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::HideTextInput() {\n  if (platform_dispatch_table_.textinput.hide_text_input_callback != nullptr) {\n    platform_dispatch_table_.textinput.hide_text_input_callback();\n  }\n}\n\n// |PlatformView|\nstd::string PlatformViewEmbedder::InputFilter(const std::string& input,\n                                              const std::string& pattern) {\n  if (platform_dispatch_table_.textinput.input_filter_callback != nullptr) {\n    return platform_dispatch_table_.textinput.input_filter_callback(input,\n                                                                    pattern);\n  } else {\n    return input;\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::WindowMove() {\n  if (platform_dispatch_table_.window_move_callback != nullptr) {\n    platform_dispatch_table_.window_move_callback();\n  }\n}\n\n// |PlatformView|\nvoid PlatformViewEmbedder::ActivateSystemCursor(int type,\n                                                const std::string& path) {\n  if (platform_dispatch_table_.activate_system_cursor_callback != nullptr) {\n    platform_dispatch_table_.activate_system_cursor_callback(type, path);\n  }\n}\n\nstd::string PlatformViewEmbedder::ShouldInterceptUrl(\n    const std::string& origin_url, bool should_decode) {\n  if (embedder_delegate_) {\n    return embedder_delegate_->ShouldInterceptUrl(origin_url, should_decode);\n  }\n  return origin_url;\n}\n\nstd::shared_ptr<clay::ResourceLoaderIntercept>\nPlatformViewEmbedder::GetResourceLoaderIntercept() {\n  if (embedder_delegate_) {\n    return embedder_delegate_->GetResourceLoaderIntercept();\n  }\n  return nullptr;\n}\n\nfml::RefPtr<OutputSurface> PlatformViewEmbedder::GetOutputSurface() const {\n  return embedder_surface_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/platform_view_embedder.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_PLATFORM_VIEW_EMBEDDER_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_PLATFORM_VIEW_EMBEDDER_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/flow/embedded_views.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/common/platform_view.h\"\n#include \"clay/shell/platform/embedder/embedder_surface.h\"\n#include \"clay/shell/platform/embedder/embedder_surface_software.h\"\n#include \"clay/shell/platform/embedder/platform_view_embedder_delegate.h\"\n#include \"clay/shell/platform/embedder/vsync_waiter_embedder.h\"\n\n#ifdef SHELL_ENABLE_GL\n#include \"clay/shell/platform/embedder/embedder_surface_gl.h\"\n#endif\n\n#ifdef SHELL_ENABLE_METAL\n#include \"clay/shell/platform/embedder/embedder_surface_metal.h\"\n#endif\n\nnamespace clay {\n\nclass PlatformViewEmbedder final : public PlatformView {\n public:\n  using ComputePlatformResolvedLocaleCallback =\n      std::function<std::unique_ptr<std::vector<std::string>>(\n          const std::vector<std::string>& supported_locale_data)>;\n  using OnPreEngineRestartCallback = std::function<void()>;\n  using SetClipboardDataCallback =\n      std::function<void(const std::u16string& data)>;\n  using GetClipboardDataCallback = std::function<std::u16string()>;\n  using SetTextInputClientCallback = std::function<void(\n      int client_id, const char* input_action, const char* input_type)>;\n  using ClearTextInputClientCallback = std::function<void()>;\n  using SetEditableTransformCallback =\n      std::function<void(const float transform_matrix[16])>;\n  using SetEditingStateCallback =\n      std::function<void(uint64_t selection_base, uint64_t composing_extent,\n                         const std::string& selection_affinity,\n                         const std::string& text, bool selection_directional,\n                         uint64_t selection_extent, uint64_t composing_base)>;\n  using SetCaretRectCallback =\n      std::function<void(float x, float y, float width, float height)>;\n  using SetMarkedTextRectCallback =\n      std::function<void(float x, float y, float width, float height)>;\n  using ShowTextInputCallback = std::function<void()>;\n  using HideTextInputCallback = std::function<void()>;\n  using FilterInputCallback =\n      std::function<std::string(const std::string&, const std::string&)>;\n  using WindowMoveCallback = std::function<void()>;\n  using ActivateSystemCursorCallback =\n      std::function<void(int type, const std::string& path)>;\n\n  struct PlatformDispatchTable {\n    ComputePlatformResolvedLocaleCallback\n        compute_platform_resolved_locale_callback;\n    OnPreEngineRestartCallback on_pre_engine_restart_callback;  // optional\n\n    struct ClipboardDispatchTable {\n      SetClipboardDataCallback set_clipboard_data_callback;\n      GetClipboardDataCallback get_clipboard_data_callback;\n    } clipboard;\n\n    struct TextInputDispatchTable {\n      SetTextInputClientCallback set_text_input_client_callback;\n      ClearTextInputClientCallback clear_text_input_client_callback;\n      SetEditableTransformCallback set_editable_transform_callback;\n      SetEditingStateCallback set_editing_state_callback;\n      SetCaretRectCallback set_caret_rect_callback;\n      SetMarkedTextRectCallback set_marked_text_rect_callback;\n      ShowTextInputCallback show_text_input_callback;\n      HideTextInputCallback hide_text_input_callback;\n      FilterInputCallback input_filter_callback;\n    } textinput;\n    WindowMoveCallback window_move_callback;\n    ActivateSystemCursorCallback activate_system_cursor_callback;\n  };\n\n  // Create a platform view that sets up a software rasterizer.\n  PlatformViewEmbedder(std::shared_ptr<clay::ServiceManager> service_manager,\n                       PlatformView::Delegate& delegate,\n                       const clay::TaskRunners& task_runners,\n                       EmbedderSurfaceSoftwareDelegate* software_delegate,\n                       PlatformDispatchTable platform_dispatch_table,\n                       void* user_data,\n                       PlatformViewEmbedderDelegate* embedder_delegate);\n\n#ifdef SHELL_ENABLE_GL\n  // Creates a platform view that sets up an OpenGL rasterizer.\n  PlatformViewEmbedder(std::shared_ptr<clay::ServiceManager> service_manager,\n                       PlatformView::Delegate& delegate,\n                       const clay::TaskRunners& task_runners,\n                       GPUSurfaceGLDelegate* gl_delegate,\n                       PlatformDispatchTable platform_dispatch_table,\n                       void* user_data,\n                       PlatformViewEmbedderDelegate* embedder_delegate);\n#endif\n\n#ifdef SHELL_ENABLE_METAL\n  // Creates a platform view that sets up an metal rasterizer.\n  PlatformViewEmbedder(std::shared_ptr<clay::ServiceManager> service_manager,\n                       PlatformView::Delegate& delegate,\n                       const clay::TaskRunners& task_runners,\n                       fml::RefPtr<EmbedderSurfaceMetal> embedder_surface,\n                       PlatformDispatchTable platform_dispatch_table,\n                       void* user_data,\n                       PlatformViewEmbedderDelegate* embedder_delegate);\n#endif\n\n  ~PlatformViewEmbedder() override;\n\n  // |PlatformView|\n  void NotifyDestroyed() override;\n\n  std::string ShouldInterceptUrl(const std::string& origin_url,\n                                 bool should_decode) override;\n\n  std::shared_ptr<clay::ResourceLoaderIntercept> GetResourceLoaderIntercept()\n      override;\n\n  fml::RefPtr<OutputSurface> GetOutputSurface() const override;\n\n private:\n  fml::RefPtr<EmbedderSurface> embedder_surface_;\n  PlatformDispatchTable platform_dispatch_table_;\n\n  PlatformViewEmbedderDelegate* embedder_delegate_ = nullptr;\n\n  // |PlatformView|\n  void OnPreEngineRestart() const override;\n  // |PlatformView|\n  void SetClipboardData(const std::u16string& data) override;\n  // |PlatformView|\n  std::u16string GetClipboardData() override;\n  // |PlatformView|\n  void SetTextInputClient(int client_id, const char* input_action,\n                          const char* input_type) override;\n  // |PlatformView|\n  void ClearTextInputClient() override;\n  // |PlatformView|\n  void SetEditableTransform(const float transform_matrix[16]) override;\n  // |PlatformView|\n  void SetEditingState(uint64_t selection_base, uint64_t composing_extent,\n                       const std::string& selection_affinity,\n                       const std::string& text, bool selection_directional,\n                       uint64_t selection_extent,\n                       uint64_t composing_base) override;\n  // |PlatformView|\n  void SetCaretRect(float x, float y, float width, float height) override;\n  // |PlatformView|\n  void setMarkedTextRect(float x, float y, float width, float height) override;\n  // |PlatformView|\n  void ShowTextInput() override;\n  // |PlatformView|\n  void HideTextInput() override;\n  // |PlatformView|\n  std::string InputFilter(const std::string& input,\n                          const std::string& pattern) override;\n  // |PlatformView|\n  void WindowMove() override;\n  // |PlatformView|\n  void ActivateSystemCursor(int type, const std::string& path) override;\n\n  // |PlatformView|\n  std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocales(\n      const std::vector<std::string>& supported_locale_data) override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PlatformViewEmbedder);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_PLATFORM_VIEW_EMBEDDER_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/platform_view_embedder_delegate.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/embedder/platform_view_embedder_delegate.h\"\n\nnamespace clay {\n\nstd::string PlatformViewEmbedderDelegate::ShouldInterceptUrl(\n    const std::string& origin_url, bool should_decode) {\n  if (resource_loader_intercept_) {\n    return resource_loader_intercept_->ShouldInterceptUrl(origin_url,\n                                                          should_decode);\n  }\n  return origin_url;\n}\n\nvoid PlatformViewEmbedderDelegate::BindShouldInterceptUrlCallback(\n    ResourceLoaderShouldInterceptUrlCallback callback) {\n  GetResourceLoaderIntercept()->BindShouldInterceptUrlCallback(callback);\n}\n\nstd::shared_ptr<clay::ResourceLoaderIntercept>\nPlatformViewEmbedderDelegate::GetResourceLoaderIntercept() {\n  if (!resource_loader_intercept_) {\n    resource_loader_intercept_ =\n        std::make_shared<clay::ResourceLoaderIntercept>();\n  }\n  return resource_loader_intercept_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/platform_view_embedder_delegate.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_PLATFORM_VIEW_EMBEDDER_DELEGATE_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_PLATFORM_VIEW_EMBEDDER_DELEGATE_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n\nnamespace clay {\n\nclass PlatformViewEmbedderDelegate {\n public:\n  PlatformViewEmbedderDelegate() = default;\n  virtual ~PlatformViewEmbedderDelegate() = default;\n\n  virtual std::string ShouldInterceptUrl(const std::string& origin_url,\n                                         bool should_decode);\n\n  virtual std::shared_ptr<clay::ResourceLoaderIntercept>\n  GetResourceLoaderIntercept();\n\n  virtual void BindShouldInterceptUrlCallback(\n      ResourceLoaderShouldInterceptUrlCallback callback);\n\n  virtual bool OffscreenRenderingEnabled() const { return false; }\n\n private:\n  std::shared_ptr<clay::ResourceLoaderIntercept> resource_loader_intercept_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PlatformViewEmbedderDelegate);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_PLATFORM_VIEW_EMBEDDER_DELEGATE_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/shell_platform_embedder_rendering_backend.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_SHELL_PLATFORM_EMBEDDER_RENDERING_BACKEND_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_SHELL_PLATFORM_EMBEDDER_RENDERING_BACKEND_H_\n\n#ifndef ENABLE_SKITY\n#include \"clay/shell/gpu/gpu_surface_gl_skia.h\"\n#else\n#include \"clay/shell/gpu/gpu_surface_gl_skity.h\"\n#endif  // ENABLE_SKITY\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_SHELL_PLATFORM_EMBEDDER_RENDERING_BACKEND_H_\n"
  },
  {
    "path": "clay/shell/platform/embedder/vsync_waiter_embedder.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/embedder/vsync_waiter_embedder.h\"\n\n#include <memory>\n#include <utility>\n\nnamespace clay {\n\nVsyncWaiterEmbedder::VsyncWaiterEmbedder(\n    const VsyncCallback& vsync_callback,\n    fml::RefPtr<fml::TaskRunner> task_runner)\n    : VsyncWaiter(std::move(task_runner)), vsync_callback_(vsync_callback) {\n  FML_DCHECK(vsync_callback_);\n}\n\nVsyncWaiterEmbedder::~VsyncWaiterEmbedder() = default;\n\n// |VsyncWaiter|\nvoid VsyncWaiterEmbedder::AwaitVSync() {\n  auto* weak_waiter = new std::weak_ptr<VsyncWaiter>(shared_from_this());\n  intptr_t baton = reinterpret_cast<intptr_t>(weak_waiter);\n  vsync_callback_(baton);\n}\n\n// static\nbool VsyncWaiterEmbedder::OnEmbedderVsync(const clay::TaskRunners& task_runners,\n                                          intptr_t baton,\n                                          fml::TimePoint frame_start_time,\n                                          fml::TimePoint frame_target_time) {\n  if (baton == 0) {\n    return false;\n  }\n\n  auto* weak_waiter = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(baton);\n  auto strong_waiter = weak_waiter->lock();\n  delete weak_waiter;\n\n  if (!strong_waiter) {\n    return false;\n  }\n\n  fml::TaskRunner::RunNowOrPostTask(\n      strong_waiter->task_runner_,\n      [strong_waiter, frame_start_time, frame_target_time] {\n        strong_waiter->FireCallback(frame_start_time, frame_target_time);\n      });\n\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/embedder/vsync_waiter_embedder.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_EMBEDDER_VSYNC_WAITER_EMBEDDER_H_\n#define CLAY_SHELL_PLATFORM_EMBEDDER_VSYNC_WAITER_EMBEDDER_H_\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/shell/common/vsync_waiter.h\"\n\nnamespace clay {\n\nclass VsyncWaiterEmbedder final : public VsyncWaiter {\n public:\n  using VsyncCallback = std::function<void(intptr_t)>;\n\n  VsyncWaiterEmbedder(const VsyncCallback& callback,\n                      fml::RefPtr<fml::TaskRunner> task_runner);\n\n  ~VsyncWaiterEmbedder() override;\n\n  static bool OnEmbedderVsync(const clay::TaskRunners& task_runners,\n                              intptr_t baton, fml::TimePoint frame_start_time,\n                              fml::TimePoint frame_target_time);\n\n private:\n  const VsyncCallback vsync_callback_;\n\n  // |VsyncWaiter|\n  void AwaitVSync() override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(VsyncWaiterEmbedder);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_EMBEDDER_VSYNC_WAITER_EMBEDDER_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//build/toolchain/clang.gni\")\nimport(\"../../../../clay/shell/config.gni\")\nimport(\"../../../../clay/shell/gpu/gpu.gni\")\nimport(\"../../../../core/Lynx.gni\")\n\nsource_set(\"headless\") {\n  sources = [\n    \"clay_event_loop.cc\",\n    \"clay_event_loop.h\",\n    \"clay_headless_engine.cc\",\n    \"clay_headless_engine.h\",\n    \"clay_headless_renderer.cc\",\n    \"clay_headless_renderer.h\",\n  ]\n\n  deps = [\n    \"../../../common\",\n    \"../../../common/graphics\",\n    \"../../../flow\",\n    \"../../../fml:fml\",\n    \"../../../fml:icu_util\",\n    \"../../../gfx/shared_image\",\n    \"../../../ui:ui\",\n    \"../../common:common\",\n    \"../embedder:embedder\",\n  ]\n\n  if (shell_enable_gl) {\n    sources += [\n      \"gl/clay_headless_renderer_gl.cc\",\n      \"gl/clay_headless_renderer_gl.h\",\n      \"gl/clay_headless_renderer_host_gl.cc\",\n      \"gl/clay_headless_renderer_host_gl.h\",\n    ]\n\n    deps += [ \"../../gpu:gpu_surface_gl\" ]\n\n    if (is_mac) {\n      sources += [\n        \"gl/clay_headless_renderer_cgl.cc\",\n        \"gl/clay_headless_renderer_cgl.h\",\n      ]\n    }\n\n    if (is_win) {\n      sources += [\n        \"gl/clay_headless_renderer_angle.cc\",\n        \"gl/clay_headless_renderer_angle.h\",\n      ]\n\n      configs += [ \"//third_party/angle:gl_prototypes\" ]\n\n      if (clay_force_d3d9) {\n        defines += [ \"CLAY_FORCE_D3D9\" ]\n      }\n\n      deps += [\n        \"//third_party/angle:libEGL_static\",  # the order of libEGL_static and\n                                              # libGLESv2_static is\n                                              # important.. if\n                                              # reversed, will cause a linker\n                                              # error\n                                              # DllMain already defined in\n                                              # LIBCMTD.lib\n        \"//third_party/angle:libGLESv2_static\",\n      ]\n    }\n\n    if (is_linux) {\n      sources += [\n        \"gl/clay_headless_renderer_epoxy.cc\",\n        \"gl/clay_headless_renderer_epoxy.h\",\n      ]\n    }\n\n    if (is_harmony) {\n      sources += [\n        \"gl/clay_headless_renderer_gles.cc\",\n        \"gl/clay_headless_renderer_gles.h\",\n      ]\n    }\n  }\n\n  if (shell_enable_metal) {\n    sources += [\n      \"metal/clay_headless_renderer_metal.h\",\n      \"metal/clay_headless_renderer_metal.mm\",\n    ]\n  }\n\n  public_configs = [\n    \"../../../../clay:config\",\n    \":headless_config\",\n  ]\n}\n\nconfig(\"headless_config\") {\n  defines = []\n\n  defines += [ \"CLAY_LIBRARY_IMPLEMENTATION\" ]\n\n  if (is_win) {\n    defines += [ \"lynx_EXPORTS\" ]\n    cflags = [ \"-Wno-c99-designator\" ]\n  }\n}\n"
  },
  {
    "path": "clay/shell/platform/headless/clay_event_loop.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/clay_event_loop.h\"\n\n#include <algorithm>\n#include <atomic>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nClayEventLoop::ClayEventLoop(std::thread::id main_thread_id,\n                             const TaskExpiredCallback& on_task_expired)\n    : main_thread_id_(main_thread_id), on_task_expired_(on_task_expired) {}\n\nClayEventLoop::~ClayEventLoop() = default;\n\nbool ClayEventLoop::RunsTasksOnCurrentThread() const {\n  return std::this_thread::get_id() == main_thread_id_;\n}\n\nvoid ClayEventLoop::WaitForEvents(std::chrono::nanoseconds max_wait) {\n  const auto now = TaskTimePoint::clock::now();\n  std::vector<ClayTask> expired_tasks;\n\n  // Process expired tasks.\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    while (!task_queue_.empty()) {\n      const auto& top = task_queue_.top();\n      // If this task (and all tasks after this) has not yet expired, there is\n      // nothing more to do. Quit iterating.\n      if (top.fire_time > now) {\n        break;\n      }\n\n      // Make a record of the expired task. Do NOT service the task here\n      // because we are still holding onto the task queue mutex. We don't want\n      // other threads to block on posting tasks onto this thread till we are\n      // done processing expired tasks.\n      expired_tasks.push_back(task_queue_.top().task);\n\n      // Remove the tasks from the delayed tasks queue.\n      task_queue_.pop();\n    }\n  }\n\n  // Fire expired tasks.\n  {\n    // Flushing tasks here without holing onto the task queue mutex.\n    for (const auto& task : expired_tasks) {\n      on_task_expired_(&task);\n    }\n  }\n\n  // Sleep till the next task needs to be processed. If a new task comes\n  // along, the wait will be resolved early because PostTask calls Wake().\n  {\n    TaskTimePoint next_wake;\n    {\n      std::lock_guard<std::mutex> lock(task_queue_mutex_);\n      TaskTimePoint max_wake_timepoint =\n          max_wait == std::chrono::nanoseconds::max() ? TaskTimePoint::max()\n                                                      : now + max_wait;\n      TaskTimePoint next_event_timepoint = task_queue_.empty()\n                                               ? TaskTimePoint::max()\n                                               : task_queue_.top().fire_time;\n      next_wake = std::min(max_wake_timepoint, next_event_timepoint);\n    }\n    WaitUntil(next_wake);\n  }\n}\n\nClayEventLoop::TaskTimePoint ClayEventLoop::TimePointFromFlutterTime(\n    uint64_t flutter_target_time_nanos) {\n  const auto now = TaskTimePoint::clock::now();\n  const int64_t flutter_duration =\n      flutter_target_time_nanos -\n      fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();\n  return now + std::chrono::nanoseconds(flutter_duration);\n}\n\nvoid ClayEventLoop::PostTask(ClayTask clay_task,\n                             uint64_t clay_target_time_nanos) {\n  static std::atomic_uint64_t sGlobalTaskOrder(0);\n\n  Task task;\n  task.order = ++sGlobalTaskOrder;\n  task.fire_time = TimePointFromFlutterTime(clay_target_time_nanos);\n  task.task = clay_task;\n\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    task_queue_.push(task);\n\n    // Make sure the queue mutex is unlocked before waking up the loop. In case\n    // the wake causes this thread to be unscheduled for the primary thread to\n    // process tasks, the acquisition of the lock on that thread while holding\n    // the lock here momentarily till the end of the scope is a pessimization.\n  }\n  Wake();\n}\nvoid ClayEventLoop::WaitUntil(const TaskTimePoint& time) {\n  std::mutex& mutex = GetTaskQueueMutex();\n  std::unique_lock<std::mutex> lock(mutex);\n  task_queue_condition_.wait_until(lock, time);\n}\n\nvoid ClayEventLoop::Wake() { task_queue_condition_.notify_one(); }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/clay_event_loop.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_CLAY_EVENT_LOOP_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_CLAY_EVENT_LOOP_H_\n\n#include <chrono>\n#include <condition_variable>\n#include <deque>\n#include <functional>\n#include <mutex>\n#include <queue>\n#include <thread>\n\n#include \"clay/public/clay.h\"\n\nnamespace clay {\n\n// An event loop implementation that only handles headless Engine task\n// scheduling.\nclass ClayEventLoop {\n public:\n  using TaskExpiredCallback = std::function<void(const ClayTask*)>;\n\n  // Creates an event loop running on the given thread, calling\n  // |on_task_expired| to run tasks.\n  ClayEventLoop(std::thread::id main_thread_id,\n                const TaskExpiredCallback& on_task_expired);\n\n  ~ClayEventLoop();\n\n  // Disallow copy.\n  ClayEventLoop(const ClayEventLoop&) = delete;\n  ClayEventLoop& operator=(const ClayEventLoop&) = delete;\n\n  // Returns if the current thread is the thread used by this event loop.\n  bool RunsTasksOnCurrentThread() const;\n\n  // Waits for the next event, processes it, and returns.\n  //\n  // Expired engine events, if any, are processed as well. The optional\n  // timeout should only be used when events not managed by this loop need to be\n  // processed in a polling manner.\n  void WaitForEvents(\n      std::chrono::nanoseconds max_wait = std::chrono::nanoseconds::max());\n\n  // Posts a Headless engine task to the event loop for delayed execution.\n  void PostTask(ClayTask clay_task, uint64_t clay_target_time_nanos);\n\n private:\n  using TaskTimePoint = std::chrono::steady_clock::time_point;\n\n  // Returns the timepoint corresponding to a Flutter task time.\n  static TaskTimePoint TimePointFromFlutterTime(\n      uint64_t flutter_target_time_nanos);\n\n  // Returns the mutex used to control the task queue. Subclasses may safely\n  // lock this mutex in the abstract methods below.\n  std::mutex& GetTaskQueueMutex() { return task_queue_mutex_; }\n\n  // Waits until the given time, or a Wake() call.\n  void WaitUntil(const TaskTimePoint& time);\n\n  // Wakes the main thread from a WaitUntil call.\n  void Wake();\n\n  struct Task {\n    uint64_t order;\n    TaskTimePoint fire_time;\n    ClayTask task;\n\n    struct Comparer {\n      bool operator()(const Task& a, const Task& b) {\n        if (a.fire_time == b.fire_time) {\n          return a.order > b.order;\n        }\n        return a.fire_time > b.fire_time;\n      }\n    };\n  };\n  std::thread::id main_thread_id_;\n  TaskExpiredCallback on_task_expired_;\n  std::mutex task_queue_mutex_;\n  std::priority_queue<Task, std::deque<Task>, Task::Comparer> task_queue_;\n  std::condition_variable task_queue_condition_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_CLAY_EVENT_LOOP_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/clay_headless_engine.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/clay_headless_engine.h\"\n\n#include <assert.h>\n\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/common/switches.h\"\n#include \"clay/shell/platform/embedder/embedder_engine.h\"\n#include \"clay/shell/platform/headless/clay_headless_renderer.h\"\n\nnamespace clay {\n\nClayHeadlessEngine::ClayHeadlessEngine(\n    const char* icu_data_path,\n    const ClayHeadlessRendererConfig* renderer_config,\n    const ClayTaskRunnerDescription* platform_task_runner_description,\n    void* user_data) {\n  RunEngine(icu_data_path, renderer_config, platform_task_runner_description,\n            user_data);\n}\n\nClayHeadlessEngine::~ClayHeadlessEngine() {\n  // Engine will reset rasterizer synchronously during `Shutdown`,\n  // so there is no need to wait a latch here.\n  fml::TaskRunner::RunNowOrPostTask(GetTaskRunners().GetRasterTaskRunner(),\n                                    [&] { renderer_->CleanupGPUResources(); });\n\n  StopEngine();\n}\n\nbool ClayHeadlessEngine::IsValid() const {\n  return static_cast<bool>(!!engine_);\n}\n\nClayHeadlessRenderer* ClayHeadlessEngine::Renderer() const {\n  return renderer_->GetEngineRenderer();\n}\n\nbool ClayHeadlessEngine::RunEngine(\n    const char* icu_data_path,\n    const ClayHeadlessRendererConfig* renderer_config,\n    const ClayTaskRunnerDescription* platform_task_runner_description,\n    void* user_data) {\n  user_data_ = user_data;\n  // FlutterProjectArgs is expecting a full argv, so when processing it for\n  // flags the first item is treated as the executable and ignored. Add a dummy\n  // value so that all provided arguments are used.\n  std::vector<const char*> argv = {\"placeholder\"};\n\n  fml::CommandLine command_line;\n  if (!argv.empty()) {\n    command_line = fml::CommandLineFromArgcArgv(argv.size(), argv.data());\n  }\n  clay::Settings settings = clay::SettingsFromCommandLine(command_line);\n  if (icu_data_path) {\n    settings.icu_data_path = icu_data_path;\n  }\n\n  ClayTaskRunnerDescription platform_task_runner = {};\n  platform_task_runner.struct_size = sizeof(ClayTaskRunnerDescription);\n  // Configure task runners.\n  if (platform_task_runner_description) {\n    platform_task_runner.user_data = const_cast<ClayTaskRunnerDescription*>(\n        platform_task_runner_description);\n    platform_task_runner.runs_task_on_current_thread_callback =\n        [](void* user_data) -> bool {\n      ClayTaskRunnerDescription* desc =\n          reinterpret_cast<ClayTaskRunnerDescription*>(user_data);\n      return desc->runs_task_on_current_thread_callback(desc->user_data);\n    };\n    platform_task_runner.post_task_callback =\n        [](ClayTask task, uint64_t target_time_nanos, void* user_data) -> void {\n      ClayTaskRunnerDescription* desc =\n          reinterpret_cast<ClayTaskRunnerDescription*>(user_data);\n      ClayTask clay_task{reinterpret_cast<ClayTaskRunner>(task.runner),\n                         task.task};\n      desc->post_task_callback(clay_task, target_time_nanos, desc->user_data);\n    };\n  } else {\n    // Create an event loop for the Headless. It is not running yet.\n    event_loop_ = std::make_unique<clay::ClayEventLoop>(\n        std::this_thread::get_id(),  // main thread\n        [this](const auto* task) {\n          if (!engine_->RunTask(task)) {\n            FML_LOG(ERROR) << \"Could not post an engine task.\";\n          }\n        });\n\n    platform_task_runner.user_data = this;\n    platform_task_runner.runs_task_on_current_thread_callback =\n        [](void* user_data) -> bool {\n      return reinterpret_cast<ClayHeadlessEngine*>(user_data)\n          ->event_loop_->RunsTasksOnCurrentThread();\n    };\n    platform_task_runner.post_task_callback =\n        [](ClayTask task, uint64_t target_time_nanos, void* user_data) -> void {\n      reinterpret_cast<ClayHeadlessEngine*>(user_data)->event_loop_->PostTask(\n          task, target_time_nanos);\n    };\n  }\n  ClayCustomTaskRunners custom_task_runners = {};\n  custom_task_runners.struct_size = sizeof(ClayCustomTaskRunners);\n  custom_task_runners.platform_task_runner = &platform_task_runner;\n\n  clay::PlatformViewEmbedder::PlatformDispatchTable platform_dispatch_table =\n      {};\n  platform_dispatch_table.clipboard.set_clipboard_data_callback =\n      [this](const std::u16string& data) {\n        std::string u8string = lynx::base::U16StringToU8(data);\n        SetClipboardData(u8string.c_str());\n      };\n  platform_dispatch_table.clipboard.get_clipboard_data_callback = [this]() {\n    const char* clipboard_data = GetClipboardData();\n    return lynx::base::U8StringToU16(clipboard_data ? clipboard_data : \"\");\n  };\n\n  platform_dispatch_table.textinput.set_text_input_client_callback =\n      [this](int client_id, const char* input_action, const char* input_type) {\n        SetTextInputClient(client_id, input_action, input_type);\n      };\n  platform_dispatch_table.textinput.clear_text_input_client_callback =\n      [this]() { ClearTextInputClient(); };\n  platform_dispatch_table.textinput.set_editable_transform_callback =\n      [this](const float transform[16]) { SetEditableTransform(transform); };\n  platform_dispatch_table.textinput.set_marked_text_rect_callback =\n      [this](float x, float y, float width, float height) {\n        SetMarkedTextRect(x, y, width, height);\n      };\n\n  platform_dispatch_table.textinput.show_text_input_callback = [this]() {\n    ShowTextInput();\n  };\n  platform_dispatch_table.textinput.hide_text_input_callback = [this]() {\n    HideTextInput();\n  };\n  platform_dispatch_table.activate_system_cursor_callback =\n      [this](int type, const std::string& path) {\n        ActivateSystemCursor(type, path.c_str());\n      };\n\n  renderer_ = ClayHeadlessRenderer::Create(this, *renderer_config);\n  if (!renderer_) {\n    return false;\n  }\n#ifdef SHELL_ENABLE_GL\n  auto* gl_renderer_delegate = renderer_->GetGLRendererDelegate();\n  if (gl_renderer_delegate) {\n    engine_ = EmbedderEngine::CreateEngine(settings, &custom_task_runners,\n                                           gl_renderer_delegate,\n                                           platform_dispatch_table, this, this);\n  }\n#endif\n#ifdef SHELL_ENABLE_METAL\n  if (!engine_) {\n    auto* metal_renderer_delegate = renderer_->GetMetalRendererDelegate();\n    if (metal_renderer_delegate) {\n      engine_ = EmbedderEngine::CreateEngine(\n          settings, &custom_task_runners,\n          fml::MakeRefCounted<EmbedderSurfaceMetal>(metal_renderer_delegate),\n          platform_dispatch_table, this, this);\n    }\n  }\n#endif\n  if (!engine_) {\n    auto* software_renderer_delegate = renderer_->GetSoftwareRendererDelegate();\n    if (software_renderer_delegate) {\n      engine_ = EmbedderEngine::CreateEngine(\n          settings, &custom_task_runners, software_renderer_delegate,\n          platform_dispatch_table, this, this);\n    }\n  }\n  if (engine_ != nullptr) {\n    FML_LOG(ERROR) << \"HeadlessEngineRun Success!\";\n  } else {\n    FML_LOG(ERROR) << \"HeadlessEngineRun Failure!\";\n    return false;\n  }\n\n  // Step 1: Launch the shell.\n  if (!engine_->LaunchShell()) {\n    FML_LOG(ERROR) << \"Could not launch the engine using supplied \"\n                      \"initialization arguments.\";\n    return false;\n  }\n\n  // Step 2: Tell the platform view to initialize itself.\n  if (!engine_->NotifyCreated()) {\n    FML_LOG(ERROR) << \"Could not create platform view components.\";\n    return false;\n  }\n  service_manager_ = engine_->GetServiceManager();\n  if (!service_manager_) {\n    FML_LOG(ERROR) << \"Failed to get clay service manager\";\n    return false;\n  }\n\n  return true;\n}\n\nconst char* ClayHeadlessEngine::GetClipboardData() const {\n  if (!delegate_) {\n    return \"\";\n  }\n  return delegate_->GetClipboardData();\n}\n\nvoid ClayHeadlessEngine::SetClipboardData(const char* data) {\n  if (!delegate_) {\n    return;\n  }\n  delegate_->SetClipboardData(data);\n}\n\nvoid ClayHeadlessEngine::ActivateSystemCursor(int type, const char* path) {\n  if (!delegate_) {\n    return;\n  }\n  delegate_->ActivateSystemCursor(type, path);\n}\n\nvoid ClayHeadlessEngine::ShowTextInput() {\n  if (!delegate_) {\n    return;\n  }\n  delegate_->ShowTextInput();\n}\n\nvoid ClayHeadlessEngine::HideTextInput() {\n  if (!delegate_) {\n    return;\n  }\n  delegate_->HideTextInput();\n}\n\nvoid ClayHeadlessEngine::SetMarkedTextRect(float x, float y, float width,\n                                           float height) {\n  if (!delegate_) {\n    return;\n  }\n  delegate_->SetMarkedTextRect(x, y, width, height);\n}\n\nvoid ClayHeadlessEngine::SetEditableTransform(\n    const float transform_matrix[16]) {\n  if (!delegate_) {\n    return;\n  }\n  delegate_->SetEditableTransform(transform_matrix);\n}\n\nvoid ClayHeadlessEngine::SetTextInputClient(int client_id,\n                                            const char* input_action,\n                                            const char* input_type) {\n  client_id_ = client_id;\n}\n\nvoid ClayHeadlessEngine::ClearTextInputClient() { client_id_ = -1; }\n\nbool ClayHeadlessEngine::StopEngine() {\n  if (IsValid()) {\n    engine_->NotifyDestroyed();\n    engine_->CollectShell();\n    engine_ = nullptr;\n    return true;\n  }\n  return false;\n}\n\nvoid ClayHeadlessEngine::EnableDefaultFocusRing() {\n  if (!IsValid()) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->SetDefaultFocusRingEnabled(true);\n}\n\nvoid ClayHeadlessEngine::EnablePerformanceOverlay() {\n  if (!IsValid()) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->SetPerformanceOverlayEnabled(true);\n}\n\nvoid ClayHeadlessEngine::OnEnterForeground() {\n  if (!IsValid()) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->OnEnterForeground();\n}\n\nvoid ClayHeadlessEngine::OnEnterBackground() {\n  if (!IsValid()) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->OnEnterBackground();\n}\n\nvoid ClayHeadlessEngine::NotifyLowMemoryWarning() {\n  if (!IsValid()) {\n    return;\n  }\n  engine_->GetShell().NotifyLowMemoryWarning();\n}\n\nvoid ClayHeadlessEngine::NotifyLocaleChange() {\n  if (!IsValid()) {\n    return;\n  }\n  // todo.\n}\n\nvoid ClayHeadlessEngine::RequestPaint() {\n  if (!IsValid()) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->RequestPaint();\n}\n\nvoid ClayHeadlessEngine::SetVisible(bool enable) {\n  if (!IsValid()) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->SetVisible(enable);\n}\n\nvoid ClayHeadlessEngine::SetFontFaceCache(const char* font_family,\n                                          const char* local_path) {\n  if (!IsValid()) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->SetFontFaceCache(font_family, local_path);\n}\n\nvoid ClayHeadlessEngine::SendViewportMetrics(int32_t width, int32_t height,\n                                             double pixel_ratio) {\n  if (IsValid()) {\n    clay::ViewportMetrics metrics;\n    metrics.physical_width = width;\n    metrics.physical_height = height;\n    metrics.device_pixel_ratio = pixel_ratio;\n    // Default logical pixel is 96\n    metrics.device_density_dpi = pixel_ratio * 96.f;\n    metrics.physical_view_inset_top = 0.0;\n    metrics.physical_view_inset_right = 0.0;\n    metrics.physical_view_inset_bottom = 0.0;\n    metrics.physical_view_inset_left = 0.0;\n    engine_->SetViewportMetrics(metrics);\n  }\n}\n\nvoid ClayHeadlessEngine::SendPointerEvents(const ClayPointerEvent* events,\n                                           size_t events_count) {\n  if (IsValid()) {\n    engine_->SendPointerEvent(events, events_count);\n  }\n}\n\nvoid ClayHeadlessEngine::SendKeyEvent(const ClayKeyEvent* event,\n                                      ClayKeyEventCallback callback,\n                                      void* user_data) {\n  if (IsValid()) {\n    bool enter_key_up = event->type == kClayKeyEventTypeUp &&\n                        event->physical == keycodes::kPhysicalEnter;\n    uintptr_t params = reinterpret_cast<uintptr_t>(this) | (enter_key_up & 1);\n    engine_->SendKeyEvent(\n        event,\n        [](bool handled, void* user_data) {\n          auto w = reinterpret_cast<uintptr_t>(user_data);\n          bool enter_key_up = w & 1;\n          if (!handled && enter_key_up) {\n            auto* self = reinterpret_cast<ClayHeadlessEngine*>(w & ~1);\n            self->engine_->GetShell()\n                .GetEngine()\n                ->GetPageView()\n                ->OnPlatformPerformInputAction(self->client_id_);\n          }\n        },\n        reinterpret_cast<void*>(params));\n  }\n}\n\nvoid ClayHeadlessEngine::PostPlatformThreadTask(ClayVoidCallback callback,\n                                                void* callback_data) {\n  if (IsValid()) {\n    engine_->PostPlatformThreadTask(\n        [callback, callback_data]() { callback(callback_data); });\n  }\n}\n\nvoid* ClayHeadlessEngine::GetViewContext() {\n  void* clay_view_context = nullptr;\n  if (IsValid()) {\n    clay_view_context = engine_->GetShell().GetEngine()->GetViewContext();\n  }\n  return clay_view_context;\n}\n\nvoid ClayHeadlessEngine::RunEventLoopWithTimeout(size_t timeout) {\n  if (!event_loop_) {\n    return;\n  }\n  uint32_t timeout_milliseconds = static_cast<uint32_t>(timeout);\n  std::chrono::nanoseconds wait_duration =\n      timeout_milliseconds == 0\n          ? std::chrono::nanoseconds::max()\n          : std::chrono::milliseconds(timeout_milliseconds);\n  event_loop_->WaitForEvents(wait_duration);\n}\n\nbool ClayHeadlessEngine::RunTask(const ClayTask* task) {\n  if (IsValid()) {\n    return engine_->RunTask(task);\n  }\n  return false;\n}\n\nconst TaskRunners& ClayHeadlessEngine::GetTaskRunners() const {\n  FML_DCHECK(IsValid());\n  return engine_->GetTaskRunners();\n}\n\nuint64_t ClayHeadlessEngine::GetEngineCurrentTime() {\n  return fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/clay_headless_engine.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_CLAY_HEADLESS_ENGINE_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_CLAY_HEADLESS_ENGINE_H_\n\n#include <chrono>\n#include <map>\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/platform/embedder/embedder_engine.h\"\n#include \"clay/shell/platform/embedder/platform_view_embedder_delegate.h\"\n#include \"clay/shell/platform/headless/clay_event_loop.h\"\n#include \"clay/shell/platform/headless/public/embdder_headless_delegate.h\"\n\nnamespace lynx {\nclass EmbedderLynxView;\n}  // namespace lynx\n\nnamespace clay {\n\nclass ClayHeadlessRenderer;\n\nclass ClayHeadlessEngine : public clay::PlatformViewEmbedderDelegate {\n public:\n  ClayHeadlessEngine(\n      const char* icu_data_path,\n      const ClayHeadlessRendererConfig* renderer_config,\n      const ClayTaskRunnerDescription* platform_task_runner_description,\n      void* user_data);\n  ~ClayHeadlessEngine() override;\n\n  bool IsValid() const;\n  void* UserData() const { return user_data_; }\n  ClayHeadlessRenderer* Renderer() const;\n\n  void EnableDefaultFocusRing();\n  void EnablePerformanceOverlay();\n  void OnEnterForeground();\n  void OnEnterBackground();\n  void NotifyLowMemoryWarning();\n  void NotifyLocaleChange();\n  void RequestPaint();\n\n  void SetVisible(bool enable);\n  void SetFontFaceCache(const char* font_family, const char* local_path);\n\n  void SendViewportMetrics(int32_t width, int32_t height, double pixel_ratio);\n  void SendPointerEvents(const ClayPointerEvent* events, size_t events_count);\n  void SendKeyEvent(const ClayKeyEvent* event, ClayKeyEventCallback callback,\n                    void* user_data);\n  void PostPlatformThreadTask(ClayVoidCallback callback, void* callback_data);\n  void* GetViewContext();\n\n  // Processes the next event for the engine, or returns early if |timeout| is\n  // reached before the next event.\n  void RunEventLoopWithTimeout(size_t timeout_milliseconds = 0);\n  bool RunTask(const ClayTask* task);\n  const TaskRunners& GetTaskRunners() const;\n\n  static uint64_t GetEngineCurrentTime();\n\n  void SetHeadlessDelegate(lynx::HeadlessDelegate* delegate) {\n    delegate_ = delegate;\n  }\n\n  const std::shared_ptr<clay::ServiceManager>& GetServiceManager() const {\n    return service_manager_;\n  }\n\n private:\n  bool RunEngine(\n      const char* icu_data_path,\n      const ClayHeadlessRendererConfig* renderer_config,\n      const ClayTaskRunnerDescription* platform_task_runner_description,\n      void* user_data);\n  bool StopEngine();\n\n  const char* GetClipboardData() const;\n  void SetClipboardData(const char* data);\n  void ActivateSystemCursor(int type, const char* path);\n  void ShowTextInput();\n  void HideTextInput();\n  void SetMarkedTextRect(float x, float y, float width, float height);\n  void SetEditableTransform(const float transform_matrix[16]);\n  void SetTextInputClient(int client_id, const char* input_action,\n                          const char* input_type);\n  void ClearTextInputClient();\n\n  void* user_data_ = nullptr;\n  int client_id_ = -1;\n\n  // The event loop for the main thread that allows for delayed task execution.\n  std::unique_ptr<ClayEventLoop> event_loop_;\n\n  // The interface between the Flutter rasterizer and the platform.\n  std::unique_ptr<ClayHeadlessRenderer> renderer_;\n  // The Flutter engine instance.\n  std::unique_ptr<EmbedderEngine> engine_;\n\n  std::shared_ptr<clay::ServiceManager> service_manager_;\n\n  lynx::HeadlessDelegate* delegate_ = nullptr;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ClayHeadlessEngine);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_CLAY_HEADLESS_ENGINE_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/clay_headless_renderer.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/clay_headless_renderer.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/platform/headless/clay_headless_engine.h\"\n\nnamespace clay {\n\nClayHeadlessRenderer::ClayHeadlessRenderer(ClayHeadlessEngine* engine)\n    : engine_(engine) {}\n\nClayHeadlessRenderer::~ClayHeadlessRenderer() = default;\n\nclass ClayHeadlessRendererSoftware final\n    : public ClayHeadlessRenderer,\n      public EmbedderSurfaceSoftwareDelegate {\n public:\n  explicit ClayHeadlessRendererSoftware(\n      ClayHeadlessEngine* engine, const ClaySoftwareRendererConfig& config)\n      : ClayHeadlessRenderer(engine), config_(config) {}\n\n  EmbedderSurfaceSoftwareDelegate* GetSoftwareRendererDelegate() override {\n    return this;\n  }\n\n  bool OnPresentBackingStore(const void* allocation, size_t row_bytes,\n                             size_t height) override {\n    return config_.present_callback(engine_->UserData(), allocation, row_bytes,\n                                    height);\n  }\n\n  void CleanupGPUResources() override {}\n\n private:\n  ClaySoftwareRendererConfig config_;\n};\n\nstd::unique_ptr<ClayHeadlessRenderer> ClayHeadlessRenderer::Create(\n    ClayHeadlessEngine* engine, const ClayHeadlessRendererConfig& config) {\n#ifdef SHELL_ENABLE_GL\n  if (config.type == kClayRendererTypeHostGL) {\n    return CreateHostGL(engine, config.opengl);\n  }\n\n  if (config.type == kClayRendererTypeOpenGL) {\n    return CreateGL(engine, config.hardware);\n  }\n#endif\n#ifdef SHELL_ENABLE_METAL\n  if (config.type == kClayRendererTypeMetal) {\n    return CreateMetal(engine, config.hardware);\n  }\n#endif\n  if (config.type == kClayRendererTypeSoftware) {\n    return std::make_unique<ClayHeadlessRendererSoftware>(engine,\n                                                          config.software);\n  }\n\n  FML_LOG(ERROR) << \"Unknown renderer type: \" << config.type;\n\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/clay_headless_renderer.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_CLAY_HEADLESS_RENDERER_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_CLAY_HEADLESS_RENDERER_H_\n\n#include <memory>\n\n#include \"clay/public/clay.h\"\n#include \"clay/shell/platform/embedder/embedder_surface_software.h\"\n#ifdef SHELL_ENABLE_GL\n#include \"clay/shell/platform/embedder/embedder_surface_gl.h\"\n#endif\n\n#ifdef SHELL_ENABLE_METAL\n#include \"clay/shell/platform/embedder/embedder_surface_metal.h\"\n#endif\n\nnamespace clay {\n\nclass ClayHeadlessEngine;\n\n/**\n * Provides the renderer config needed to initialize the embedder engine and\n * also handles external texture management.\n */\nclass ClayHeadlessRenderer {\n public:\n  explicit ClayHeadlessRenderer(ClayHeadlessEngine* engine);\n\n  /**\n   * Called on UI thread\n   */\n  virtual ~ClayHeadlessRenderer();\n\n  /**\n   * Cleanup GPU resources, called in raster thread before destruction\n   */\n  virtual void CleanupGPUResources() = 0;\n\n  virtual EmbedderSurfaceSoftwareDelegate* GetSoftwareRendererDelegate() {\n    return nullptr;\n  }\n#ifdef SHELL_ENABLE_GL\n  virtual GPUSurfaceGLDelegate* GetGLRendererDelegate() { return nullptr; }\n#endif\n#ifdef SHELL_ENABLE_METAL\n  virtual EmbedderSurfaceMetalDelegate* GetMetalRendererDelegate() {\n    return nullptr;\n  }\n#endif\n\n  /**\n   * Return the renderer used in FlutterRendererConfig callbacks\n   * It's only overriden in ClayHeadlessRendererSharedImageHostGL,\n   * since it's a \"proxy\" renderer\n   */\n  virtual ClayHeadlessRenderer* GetEngineRenderer() { return this; }\n\n  static std::unique_ptr<ClayHeadlessRenderer> Create(\n      ClayHeadlessEngine* engine, const ClayHeadlessRendererConfig& config);\n\n#ifdef SHELL_ENABLE_GL\n  static std::unique_ptr<ClayHeadlessRenderer> CreateHostGL(\n      ClayHeadlessEngine* engine, const ClayOpenGLRendererConfig& config);\n\n  static std::unique_ptr<ClayHeadlessRenderer> CreateGL(\n      ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config);\n#endif\n\n#ifdef SHELL_ENABLE_METAL\n  static std::unique_ptr<ClayHeadlessRenderer> CreateMetal(\n      ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config);\n#endif\n\n protected:\n  ClayHeadlessEngine* engine_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_CLAY_HEADLESS_RENDERER_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_angle.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_angle.h\"\n\n#include <Windows.h>\n\n#include <string>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/platform/headless/clay_headless_engine.h\"\n\nnamespace clay {\n\n// Logs an EGL error to stderr. This automatically calls eglGetError()\n// and logs the error code.\nstatic void LogEglError(std::string message) {\n  EGLint error = eglGetError();\n  FML_LOG(ERROR) << \"EGL: \" << message;\n  FML_LOG(ERROR) << \"EGL: eglGetError returned \" << error;\n}\n\nstd::unique_ptr<HeadlessAngleSurfaceManager>\nHeadlessAngleSurfaceManager::Create() {\n  return std::unique_ptr<HeadlessAngleSurfaceManager>(\n      new HeadlessAngleSurfaceManager());\n}\n\nHeadlessAngleSurfaceManager::HeadlessAngleSurfaceManager()\n    : egl_config_(nullptr),\n      egl_display_(EGL_NO_DISPLAY),\n      egl_context_(EGL_NO_CONTEXT),\n      egl_device_(nullptr) {\n  initialize_succeeded_ = Initialize();\n}\n\nHeadlessAngleSurfaceManager::~HeadlessAngleSurfaceManager() { CleanUp(); }\n\nbool HeadlessAngleSurfaceManager::MakeCurrent() {\n  return (eglMakeCurrent(egl_display_, egl_surface_, egl_surface_,\n                         egl_context_) == EGL_TRUE);\n}\n\nbool HeadlessAngleSurfaceManager::ClearContext() {\n  return (eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,\n                         EGL_NO_CONTEXT) == EGL_TRUE);\n}\n\nMicrosoft::WRL::ComPtr<ID3D11Device> HeadlessAngleSurfaceManager::GetDevice() {\n  if (resolved_device_) {\n    return resolved_device_;\n  }\n  Microsoft::WRL::ComPtr<ID3D11Device> result;\n  PFNEGLQUERYDISPLAYATTRIBEXTPROC egl_query_display_attrib_EXT =\n      reinterpret_cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>(\n          eglGetProcAddress(\"eglQueryDisplayAttribEXT\"));\n\n  PFNEGLQUERYDEVICEATTRIBEXTPROC egl_query_device_attrib_EXT =\n      reinterpret_cast<PFNEGLQUERYDEVICEATTRIBEXTPROC>(\n          eglGetProcAddress(\"eglQueryDeviceAttribEXT\"));\n\n  if (!egl_query_display_attrib_EXT || !egl_query_device_attrib_EXT) {\n    return result;\n  }\n\n  EGLAttrib egl_device = 0;\n  EGLAttrib angle_device = 0;\n  if (egl_query_display_attrib_EXT(egl_display_, EGL_DEVICE_EXT, &egl_device) ==\n      EGL_TRUE) {\n    if (egl_query_device_attrib_EXT(reinterpret_cast<EGLDeviceEXT>(egl_device),\n                                    EGL_D3D11_DEVICE_ANGLE,\n                                    &angle_device) == EGL_TRUE) {\n      result = reinterpret_cast<ID3D11Device*>(angle_device);\n    }\n  }\n\n  resolved_device_ = result;\n  return result;\n}\n\nbool HeadlessAngleSurfaceManager::TryInitializeD3D11Device() {\n  d3d11_ = fml::NativeLibrary::Create(\"d3d11.dll\");\n\n  if (!d3d11_) {\n    FML_LOG(WARNING) << \"Could not load D3D11 library.\";\n    return false;\n  }\n\n  std::optional<PFN_D3D11_CREATE_DEVICE> D3D11CreateDevice =\n      d3d11_->ResolveFunction<PFN_D3D11_CREATE_DEVICE>(\"D3D11CreateDevice\");\n\n  if (!D3D11CreateDevice.has_value()) {\n    FML_LOG(WARNING) << \"Could not retrieve D3D11CreateDevice address.\";\n    return false;\n  }\n  D3D_FEATURE_LEVEL feature_levels[] = {D3D_FEATURE_LEVEL_11_1,\n                                        D3D_FEATURE_LEVEL_11_0};\n\n  HRESULT hr = D3D11CreateDevice.value()(\n      nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, feature_levels,\n      ARRAYSIZE(feature_levels), D3D11_SDK_VERSION, &resolved_device_, nullptr,\n      nullptr);\n  if (FAILED(hr)) {\n    FML_LOG(WARNING) << \"Could not create D3D11 Device.\";\n    return false;\n  }\n\n  egl_device_ = eglCreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE,\n                                     resolved_device_.Get(), nullptr);\n  if (!egl_device_) {\n    FML_LOG(WARNING) << \"Could not create EGL device.\";\n    return false;\n  }\n\n  return true;\n}\n\nbool HeadlessAngleSurfaceManager::Initialize() {\n#ifndef CLAY_FORCE_D3D9\n  TryInitializeD3D11Device();\n#endif\n\n  // TODO(dnfield): Enable MSAA here, see similar code in android_context_gl.cc\n  // Will need to plumb in argument from project bundle for sampling rate.\n  // https://github.com/flutter/flutter/issues/100392\n  const EGLint config_attributes[] = {EGL_RED_SIZE,   8, EGL_GREEN_SIZE,   8,\n                                      EGL_BLUE_SIZE,  8, EGL_ALPHA_SIZE,   8,\n                                      EGL_DEPTH_SIZE, 8, EGL_STENCIL_SIZE, 8,\n                                      EGL_NONE};\n\n  const EGLint display_context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2,\n                                               EGL_NONE};\n\n  // These are preferred display attributes and request ANGLE's D3D11\n  // renderer. eglInitialize will only succeed with these attributes if the\n  // hardware supports D3D11 Feature Level 10_0+.\n  const EGLint d3d11_display_attributes[] = {\n      EGL_PLATFORM_ANGLE_TYPE_ANGLE,\n      EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,\n\n      // EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that will\n      // enable ANGLE to automatically call the IDXGIDevice3::Trim method on\n      // behalf of the application when it gets suspended.\n      EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,\n      EGL_TRUE,\n\n      // This extension allows angle to render directly on a D3D swapchain\n      // in the correct orientation on D3D11.\n      EGL_EXPERIMENTAL_PRESENT_PATH_ANGLE,\n      EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE,\n\n      EGL_NONE,\n  };\n\n  // These are used to request ANGLE's D3D11 renderer, with D3D11 Feature\n  // Level 9_3.\n  const EGLint d3d11_fl_9_3_display_attributes[] = {\n      EGL_PLATFORM_ANGLE_TYPE_ANGLE,\n      EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,\n      EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE,\n      9,\n      EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE,\n      3,\n      EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,\n      EGL_TRUE,\n      EGL_NONE,\n  };\n\n  // These attributes request D3D11 WARP (software rendering fallback) in case\n  // hardware-backed D3D11 is unavailable.\n  const EGLint d3d11_warp_display_attributes[] = {\n      EGL_PLATFORM_ANGLE_TYPE_ANGLE,\n      EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,\n      EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,\n      EGL_TRUE,\n      EGL_NONE,\n  };\n\n  // These are used to request ANGLE's D3D9 renderer as a fallback if D3D11\n  // is not available.\n  const EGLint d3d9_display_attributes[] = {\n      EGL_PLATFORM_ANGLE_TYPE_ANGLE,\n      EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,\n      EGL_NONE,\n  };\n\n  std::vector<const EGLint*> display_attributes_configs = {\n#ifndef CLAY_FORCE_D3D9\n      d3d11_display_attributes,\n      d3d11_fl_9_3_display_attributes,\n      d3d11_warp_display_attributes,\n#endif\n      d3d9_display_attributes,\n  };\n\n  PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_EXT =\n      reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(\n          eglGetProcAddress(\"eglGetPlatformDisplayEXT\"));\n  if (!egl_get_platform_display_EXT) {\n    LogEglError(\"eglGetPlatformDisplayEXT not available\");\n    return false;\n  }\n\n  // Attempt to initialize ANGLE's renderer in order of: D3D11, D3D11 Feature\n  // Level 9_3 and finally D3D11 WARP.\n  for (auto config : display_attributes_configs) {\n    bool should_log = (config == display_attributes_configs.back());\n    if (InitializeEGL(egl_get_platform_display_EXT, config, should_log)) {\n      break;\n    }\n  }\n\n  EGLint numConfigs = 0;\n  if ((eglChooseConfig(egl_display_, config_attributes, &egl_config_, 1,\n                       &numConfigs) == EGL_FALSE) ||\n      (numConfigs == 0)) {\n    LogEglError(\"Failed to choose first context\");\n    return false;\n  }\n\n  egl_context_ = eglCreateContext(egl_display_, egl_config_, EGL_NO_CONTEXT,\n                                  display_context_attributes);\n  if (egl_context_ == EGL_NO_CONTEXT) {\n    LogEglError(\"Failed to create EGL context\");\n    return false;\n  }\n\n  // We only ever create pbuffer surfaces for background resource loading\n  // contexts. We never bind the pbuffer to anything.\n  const EGLint attribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};\n\n  egl_surface_ = eglCreatePbufferSurface(egl_display_, egl_config_, attribs);\n\n  if (egl_surface_ == EGL_NO_SURFACE) {\n    LogEglError(\"Failed to create EGL surface\");\n    return false;\n  }\n\n  return true;\n}\n\nvoid HeadlessAngleSurfaceManager::CleanUp() {\n  EGLBoolean result = EGL_FALSE;\n\n  // Needs to be reset before destroying the EGLContext.\n  resolved_device_.Reset();\n\n  if (egl_display_ != EGL_NO_DISPLAY && egl_surface_ != EGL_NO_SURFACE) {\n    result = eglDestroySurface(egl_display_, egl_surface_);\n    egl_surface_ = EGL_NO_SURFACE;\n    if (result == EGL_FALSE) {\n      LogEglError(\"Failed to destroy context\");\n    }\n  }\n\n  if (egl_display_ != EGL_NO_DISPLAY && egl_context_ != EGL_NO_CONTEXT) {\n    result = eglDestroyContext(egl_display_, egl_context_);\n    egl_context_ = EGL_NO_CONTEXT;\n\n    if (result == EGL_FALSE) {\n      LogEglError(\"Failed to destroy context\");\n    }\n  }\n\n  if (egl_display_ != EGL_NO_DISPLAY) {\n    if (egl_device_ != nullptr) {\n      // If the display is created from an owned device,\n      // it needs to be released.\n      result = eglTerminate(egl_display_);\n      if (result == EGL_FALSE) {\n        LogEglError(\"Failed to destroy display\");\n      }\n    }\n    egl_display_ = EGL_NO_DISPLAY;\n  }\n\n  if (egl_device_ != nullptr) {\n    result = eglReleaseDeviceANGLE(egl_device_);\n    egl_device_ = nullptr;\n    if (result == EGL_FALSE) {\n      LogEglError(\"Failed to destroy device\");\n    }\n  }\n}\n\nbool HeadlessAngleSurfaceManager::InitializeEGL(\n    PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_EXT,\n    const EGLint* config, bool should_log) {\n  if (egl_device_ != nullptr) {\n    egl_display_ = egl_get_platform_display_EXT(EGL_PLATFORM_DEVICE_EXT,\n                                                egl_device_, config);\n  } else {\n    egl_display_ = egl_get_platform_display_EXT(EGL_PLATFORM_ANGLE_ANGLE,\n                                                EGL_DEFAULT_DISPLAY, config);\n  }\n\n  if (egl_display_ == EGL_NO_DISPLAY) {\n    if (should_log) {\n      LogEglError(\"Failed to get a compatible EGLdisplay\");\n    }\n    return false;\n  }\n\n  if (eglInitialize(egl_display_, nullptr, nullptr) == EGL_FALSE) {\n    if (should_log) {\n      LogEglError(\"Failed to initialize EGL via ANGLE\");\n    }\n    return false;\n  }\n\n  return true;\n}\n\nClayHeadlessRendererAngle::ClayHeadlessRendererAngle(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config)\n    : ClayHeadlessRendererSharedImageGL(engine, config) {\n  surface_manager_ = HeadlessAngleSurfaceManager::Create();\n}\n\nClayHeadlessRendererAngle::~ClayHeadlessRendererAngle() = default;\n\nGPUSurfaceGLDelegate::GLProcResolver\nClayHeadlessRendererAngle::GetGLProcResolver() const {\n  return [](const char* name) -> void* {\n    return reinterpret_cast<void*>(eglGetProcAddress(name));\n  };\n}\n\nbool ClayHeadlessRendererAngle::MakeCurrent() {\n  return surface_manager_->MakeCurrent();\n}\n\nbool ClayHeadlessRendererAngle::ClearCurrent() {\n  return surface_manager_->ClearContext();\n}\n\nstd::unique_ptr<ClayHeadlessRenderer> ClayHeadlessRenderer::CreateGL(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config) {\n  return std::make_unique<ClayHeadlessRendererAngle>(engine, config);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_angle.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_ANGLE_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_ANGLE_H_\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <d3d11.h>\n#include <wrl/client.h>\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/native_library.h\"\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_gl.h\"\n\nnamespace clay {\n\nclass HeadlessAngleSurfaceManager {\n public:\n  static std::unique_ptr<HeadlessAngleSurfaceManager> Create();\n\n  ~HeadlessAngleSurfaceManager();\n\n  bool MakeCurrent();\n\n  bool ClearContext();\n\n  // The current D3D device.\n  Microsoft::WRL::ComPtr<ID3D11Device> GetDevice();\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(HeadlessAngleSurfaceManager);\n\n private:\n  // Creates a new surface manager retaining reference to the passed-in target\n  // for the lifetime of the manager.\n  HeadlessAngleSurfaceManager();\n\n  bool TryInitializeD3D11Device();\n  bool Initialize();\n  void CleanUp();\n\n  bool InitializeEGL(\n      PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_EXT,\n      const EGLint* config, bool should_log);\n\n  EGLDeviceEXT egl_device_;\n\n  EGLDisplay egl_display_;\n\n  EGLContext egl_context_;\n\n  EGLConfig egl_config_;\n\n  // Pbuffer surface to make current\n  EGLSurface egl_surface_ = EGL_NO_SURFACE;\n\n  bool initialize_succeeded_;\n\n  Microsoft::WRL::ComPtr<ID3D11Device> resolved_device_;\n\n  fml::RefPtr<fml::NativeLibrary> d3d11_;\n};\n\nclass ClayHeadlessRendererAngle final\n    : public ClayHeadlessRendererSharedImageGL {\n public:\n  explicit ClayHeadlessRendererAngle(ClayHeadlessEngine* engine,\n                                     const ClayHardwareRendererConfig& config);\n  ~ClayHeadlessRendererAngle() override;\n\n  // |GPUSurfaceGLDelegate|\n  GLProcResolver GetGLProcResolver() const override;\n\n  bool MakeCurrent() override;\n  bool ClearCurrent() override;\n\n private:\n  std::unique_ptr<HeadlessAngleSurfaceManager> surface_manager_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_ANGLE_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_cgl.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_cgl.h\"\n\n#include <memory>\n\n#include \"clay/shell/platform/headless/clay_headless_engine.h\"\n\nnamespace clay {\n\nClayHeadlessRendererCGL::ClayHeadlessRendererCGL(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config)\n    : ClayHeadlessRendererSharedImageGL(engine, config) {}\n\nClayHeadlessRendererCGL ::~ClayHeadlessRendererCGL() {\n  if (gl_context_ != nullptr) {\n    CGLDestroyContext(gl_context_);\n  }\n}\n\nbool ClayHeadlessRendererCGL::MakeCurrent() {\n  if (gl_context_ == nullptr) {\n    // https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_pixelformats/opengl_pixelformats.html\n    CGLPixelFormatAttribute attrib_list[] = {\n        kCGLPFAAllowOfflineRenderers, kCGLPFAAccelerated, kCGLPFAOpenGLProfile,\n        static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_3_2_Core),\n        static_cast<CGLPixelFormatAttribute>(0)};\n\n    CGLPixelFormatObj pixel_format;\n    GLint num_formats = 0;\n    if (CGLChoosePixelFormat(attrib_list, &pixel_format, &num_formats) !=\n        kCGLNoError) {\n      return false;\n    }\n\n    CGLError error = CGLCreateContext(pixel_format, nullptr, &gl_context_);\n\n    CGLDestroyPixelFormat(pixel_format);\n\n    if (error != kCGLNoError) {\n      return false;\n    }\n  }\n\n  return CGLSetCurrentContext(gl_context_) == kCGLNoError;\n}\n\nbool ClayHeadlessRendererCGL::ClearCurrent() {\n  return CGLSetCurrentContext(nullptr) == kCGLNoError;\n}\n\nstd::unique_ptr<ClayHeadlessRenderer> ClayHeadlessRenderer::CreateGL(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config) {\n  return std::make_unique<ClayHeadlessRendererCGL>(engine, config);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_cgl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_CGL_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_CGL_H_\n\n#import <OpenGL/OpenGL.h>\n\n#include <optional>\n\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_gl.h\"\n\nnamespace clay {\n\nclass ClayHeadlessRendererCGL : public ClayHeadlessRendererSharedImageGL {\n public:\n  ClayHeadlessRendererCGL(ClayHeadlessEngine* engine,\n                          const ClayHardwareRendererConfig& config);\n\n  ~ClayHeadlessRendererCGL() override;\n\n  bool MakeCurrent() override;\n  bool ClearCurrent() override;\n\n private:\n  CGLContextObj gl_context_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_CGL_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_epoxy.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_epoxy.h\"\n\n#include <epoxy/egl.h>\n#include <epoxy/gl.h>\n\n#include <memory>\n#include <string>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nnamespace {\n\nstatic void LogEglError(std::string message) {\n  EGLint error = eglGetError();\n  FML_LOG(ERROR) << \"EGL: \" << message;\n  FML_LOG(ERROR) << \"EGL: eglGetError returned \" << error;\n}\n\n}  // namespace\n\nclass ClayHeadlessEpoxyManager {\n public:\n  ClayHeadlessEpoxyManager() = default;\n\n  ~ClayHeadlessEpoxyManager() {\n    if (egl_context_ != EGL_NO_CONTEXT && egl_display_ != EGL_NO_DISPLAY) {\n      eglDestroyContext(egl_display_, egl_context_);\n      eglTerminate(egl_display_);\n    }\n  }\n\n  bool MakeCurrent() {\n    if (egl_context_ == EGL_NO_CONTEXT) {\n      egl_display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n      if (egl_display_ == EGL_NO_DISPLAY) {\n        LogEglError(\"Failed to get EGL display.\");\n        return false;\n      }\n\n      EGLint major, minor;\n      if (!eglInitialize(egl_display_, &major, &minor)) {\n        LogEglError(\"Failed to initialize EGL.\");\n        return false;\n      }\n\n      if (!epoxy_has_egl_extension(egl_display_, \"EGL_KHR_image_base\")) {\n        LogEglError(\"EGL_KHR_image_base not supported.\");\n        return false;\n      }\n\n      const EGLint attribs[] = {EGL_RED_SIZE,   8,  EGL_GREEN_SIZE,   8,\n                                EGL_BLUE_SIZE,  8,  EGL_ALPHA_SIZE,   8,\n                                EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8,\n                                EGL_NONE};\n      EGLint num_config;\n      if (!eglChooseConfig(egl_display_, attribs, &egl_config_, 1,\n                           &num_config)) {\n        LogEglError(\"Failed to choose EGL config.\");\n        return false;\n      }\n\n      EGLint attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};\n      egl_context_ = eglCreateContext(egl_display_, egl_config_, EGL_NO_CONTEXT,\n                                      attributes);\n      if (egl_context_ == EGL_NO_CONTEXT) {\n        LogEglError(\"Failed to create EGL context.\");\n        return false;\n      }\n    }\n\n    return eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,\n                          egl_context_) == EGL_TRUE;\n  }\n  bool ClearCurrent() {\n    return eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,\n                          EGL_NO_CONTEXT) == EGL_TRUE;\n  }\n\n private:\n  EGLDisplay egl_display_ = EGL_NO_DISPLAY;\n  EGLContext egl_context_ = EGL_NO_CONTEXT;\n  EGLConfig egl_config_ = nullptr;\n};\n\nClayHeadlessRendererEpoxy::ClayHeadlessRendererEpoxy(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config)\n    : ClayHeadlessRendererSharedImageGL(engine, config),\n      epoxy_manager_(std::make_unique<ClayHeadlessEpoxyManager>()) {}\n\nClayHeadlessRendererEpoxy::~ClayHeadlessRendererEpoxy() = default;\n\nGPUSurfaceGLDelegate::GLProcResolver\nClayHeadlessRendererEpoxy::GetGLProcResolver() const {\n  return [](const char* name) -> void* {\n    return reinterpret_cast<void*>(eglGetProcAddress(name));\n  };\n}\n\nbool ClayHeadlessRendererEpoxy::MakeCurrent() {\n  return epoxy_manager_->MakeCurrent();\n}\n\nbool ClayHeadlessRendererEpoxy::ClearCurrent() {\n  return epoxy_manager_->ClearCurrent();\n}\n\nstd::unique_ptr<ClayHeadlessRenderer> ClayHeadlessRenderer::CreateGL(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config) {\n  return std::make_unique<ClayHeadlessRendererEpoxy>(engine, config);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_epoxy.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_EPOXY_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_EPOXY_H_\n\n#include <memory>\n#include <optional>\n\n#include \"clay/shell/platform/headless/clay_headless_engine.h\"\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_gl.h\"\n\nnamespace clay {\n\nclass ClayHeadlessEpoxyManager;\n\nclass ClayHeadlessRendererEpoxy : public ClayHeadlessRendererSharedImageGL {\n public:\n  ClayHeadlessRendererEpoxy(ClayHeadlessEngine* engine,\n                            const ClayHardwareRendererConfig& config);\n\n  ~ClayHeadlessRendererEpoxy() override;\n\n  // |GPUSurfaceGLDelegate|\n  GLProcResolver GetGLProcResolver() const override;\n\n  bool MakeCurrent() override;\n  bool ClearCurrent() override;\n\n private:\n  std::unique_ptr<ClayHeadlessEpoxyManager> epoxy_manager_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_EPOXY_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_gl.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_gl.h\"\n\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/platform/embedder/embedder_struct_macros.h\"\n#include \"clay/shell/platform/headless/clay_headless_engine.h\"\n\nnamespace clay {\n\nClayHeadlessRendererGL::ClayHeadlessRendererGL(ClayHeadlessEngine* engine)\n    : ClayHeadlessRenderer(engine) {}\n\nGPUSurfaceGLDelegate* ClayHeadlessRendererGL::GetGLRendererDelegate() {\n  return this;\n}\n\nstd::unique_ptr<GLContextResult>\nClayHeadlessRendererGL::GLContextMakeCurrent() {\n  return std::make_unique<GLContextDefaultResult>(MakeCurrent());\n}\n\nbool ClayHeadlessRendererGL::GLContextClearCurrent() { return ClearCurrent(); }\n\nvoid ClayHeadlessRendererGL::GLContextSetDamageRegion(\n    const std::optional<skity::Rect>& region) {}\n\nbool ClayHeadlessRendererGL::GLContextPresent(\n    const GLPresentInfo& present_info) {\n  return Present();\n}\n\nGLFBOInfo ClayHeadlessRendererGL::GLContextFBO(GLFrameInfo frame_info) const {\n  ClayFrameInfo clay_frame_info{};\n  clay_frame_info.struct_size = sizeof(clay_frame_info);\n  clay_frame_info.width = frame_info.width;\n  clay_frame_info.height = frame_info.height;\n  return {\n      .fbo_id = const_cast<ClayHeadlessRendererGL*>(this)->FBO(clay_frame_info),\n      .existing_damage = std::nullopt};\n}\n\nbool ClayHeadlessRendererGL::GLContextFBOResetAfterPresent() const {\n  return true;\n}\n\nGPUSurfaceGLDelegate::GLProcResolver ClayHeadlessRendererGL::GetGLProcResolver()\n    const {\n  return nullptr;\n}\n\nSurfaceFrame::FramebufferInfo ClayHeadlessRendererGL::GLContextFramebufferInfo()\n    const {\n  auto info = SurfaceFrame::FramebufferInfo{};\n  info.supports_readback = true;\n  info.supports_partial_repaint = false;\n  return info;\n}\n\nClayHeadlessRendererSharedImageGL::ClayHeadlessRendererSharedImageGL(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config)\n    : ClayHeadlessRendererGL(engine),\n      disable_partial_repaint_(config.disable_partial_repaint) {\n  ClaySharedImageRepresentationConfig image_repr{};\n  image_repr.struct_size = sizeof(image_repr);\n  image_repr.type = kClaySharedImageRepresentationTypeGL;\n  surface_accessor_ =\n      ClayCreateSharedImageSinkAccessor(config.sink_ref, &image_repr);\n}\n\nClayHeadlessRendererSharedImageGL::~ClayHeadlessRendererSharedImageGL() =\n    default;\n\nvoid ClayHeadlessRendererSharedImageGL::CleanupGPUResources() {\n  if (fbo_storage_) {\n    if (fbo_storage_->framebuffer.destruction_callback) {\n      fbo_storage_->framebuffer.destruction_callback(\n          fbo_storage_->framebuffer.user_data);\n    }\n\n    fbo_storage_.reset();\n  }\n\n  if (surface_accessor_) {\n    ClayDestroySharedImageSinkAccessor(surface_accessor_);\n    surface_accessor_ = nullptr;\n  }\n}\n\nint64_t ClayHeadlessRendererSharedImageGL::FBO(\n    const ClayFrameInfo& frame_info) {\n  if (fbo_storage_) {\n    return fbo_storage_->framebuffer.name;\n  }\n\n  if (size_.width != frame_info.width || size_.height != frame_info.height) {\n    damage_history_.clear();\n  }\n\n  size_.width = frame_info.width;\n  size_.height = frame_info.height;\n\n  ClaySharedImageWriteResult result;\n  uint32_t buffer_age = 0;\n\n  if (!surface_accessor_ ||\n      !ClaySharedImageSinkBeginWrite(surface_accessor_, &size_, nullptr,\n                                     &result, &buffer_age)) {\n    FML_LOG(ERROR) << \"failed to get fbo\";\n    return -1;\n  }\n\n  FML_DCHECK(result.type == kClaySharedImageRepresentationTypeGL);\n\n  uint32_t fbo_name = result.opengl_framebuffer.name;\n\n  fbo_storage_ = FBOSlot{\n      .framebuffer = result.opengl_framebuffer,\n      .buffer_age = buffer_age,\n  };\n\n  return fbo_name;\n}\n\n// |GPUSurfaceGLDelegate|\nbool ClayHeadlessRendererSharedImageGL::GLContextPresent(\n    const GLPresentInfo& present_info) {\n  if (!fbo_storage_) {\n    return false;\n  }\n  if (disable_partial_repaint_) {\n    return Present();\n  }\n\n  FML_DCHECK(present_info.fbo_id == fbo_storage_->framebuffer.name);\n\n  if (!Present()) {\n    return false;\n  }\n\n  if (present_info.frame_damage) {\n    damage_history_.push_back(*present_info.frame_damage);\n  } else {\n    damage_history_.push_back(skity::Rect(0, 0, size_.width, size_.height));\n  }\n\n  if (damage_history_.size() > kMaxHistorySize) {\n    damage_history_.pop_front();\n  }\n\n  return true;\n}\n\n// |GPUSurfaceGLDelegate|\nGLFBOInfo ClayHeadlessRendererSharedImageGL::GLContextFBO(\n    GLFrameInfo frame_info) const {\n  ClayFrameInfo clay_frame_info{};\n  clay_frame_info.struct_size = sizeof(clay_frame_info);\n  clay_frame_info.width = frame_info.width;\n  clay_frame_info.height = frame_info.height;\n  int64_t fbo_id = const_cast<ClayHeadlessRendererSharedImageGL*>(this)->FBO(\n      clay_frame_info);\n  return {\n      .fbo_id = fbo_id,\n      .existing_damage = const_cast<ClayHeadlessRendererSharedImageGL*>(this)\n                             ->PopulateExistingDamage(fbo_id)};\n}\n\n// |GPUSurfaceGLDelegate|\nSurfaceFrame::FramebufferInfo\nClayHeadlessRendererSharedImageGL::GLContextFramebufferInfo() const {\n  auto info = SurfaceFrame::FramebufferInfo{};\n  info.supports_readback = true;\n  info.supports_partial_repaint = true;\n  return info;\n}\n\nstd::optional<skity::Rect>\nClayHeadlessRendererSharedImageGL::PopulateExistingDamage(int64_t fbo_id) {\n  if (disable_partial_repaint_) {\n    return std::nullopt;\n  }\n  if (!fbo_storage_) {\n    return std::nullopt;\n  }\n  FML_DCHECK(fbo_id == fbo_storage_->framebuffer.name);\n  uint32_t buffer_age = fbo_storage_->buffer_age;\n  if (buffer_age == 0 || damage_history_.size() < buffer_age) {\n    return std::nullopt;\n  }\n  skity::Rect damage = skity::Rect::MakeEmpty();\n  // join up to (age - 1) last rects from damage history\n  --buffer_age;\n  for (auto i = damage_history_.rbegin(); buffer_age > 0; ++i, --buffer_age) {\n    damage.Join(*i);\n  }\n  fbo_storage_->damage_rect = damage;\n\n  return fbo_storage_->damage_rect;\n}\n\nbool ClayHeadlessRendererSharedImageGL::Present() {\n  if (!fbo_storage_ || !surface_accessor_) {\n    return false;\n  }\n\n  if (!ClaySharedImageSinkEndWrite(surface_accessor_)) {\n    return false;\n  }\n\n  if (fbo_storage_->framebuffer.destruction_callback) {\n    fbo_storage_->framebuffer.destruction_callback(\n        fbo_storage_->framebuffer.user_data);\n  }\n\n  fbo_storage_.reset();\n\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_gl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_GL_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_GL_H_\n\n#include <list>\n#include <map>\n#include <memory>\n#include <optional>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/shell/platform/headless/clay_headless_renderer.h\"\n\nnamespace clay {\n\nclass ClayHeadlessRendererGL : public ClayHeadlessRenderer,\n                               public GPUSurfaceGLDelegate {\n public:\n  explicit ClayHeadlessRendererGL(ClayHeadlessEngine* engine);\n\n  GPUSurfaceGLDelegate* GetGLRendererDelegate() override;\n\n  // |GPUSurfaceGLDelegate|\n  std::unique_ptr<GLContextResult> GLContextMakeCurrent() override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextClearCurrent() override;\n\n  // |GPUSurfaceGLDelegate|\n  void GLContextSetDamageRegion(\n      const std::optional<skity::Rect>& region) override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextPresent(const GLPresentInfo& present_info) override;\n\n  // |GPUSurfaceGLDelegate|\n  GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextFBOResetAfterPresent() const override;\n\n  // |GPUSurfaceGLDelegate|\n  GLProcResolver GetGLProcResolver() const override;\n\n  // |GPUSurfaceGLDelegate|\n  SurfaceFrame::FramebufferInfo GLContextFramebufferInfo() const override;\n\n protected:\n  virtual bool MakeCurrent() = 0;\n  virtual bool ClearCurrent() = 0;\n  virtual int64_t FBO(const ClayFrameInfo& frame_info) = 0;\n  virtual bool Present() = 0;\n};\n\nclass ClayHeadlessRendererSharedImageGL : public ClayHeadlessRendererGL {\n public:\n  ClayHeadlessRendererSharedImageGL(ClayHeadlessEngine* engine,\n                                    const ClayHardwareRendererConfig& config);\n\n  ~ClayHeadlessRendererSharedImageGL() override;\n\n protected:\n  void CleanupGPUResources() override;\n\n  int64_t FBO(const ClayFrameInfo& frame_info) override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextPresent(const GLPresentInfo& present_info) override;\n\n  // |GPUSurfaceGLDelegate|\n  GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;\n\n  // |GPUSurfaceGLDelegate|\n  SurfaceFrame::FramebufferInfo GLContextFramebufferInfo() const override;\n\n  std::optional<skity::Rect> PopulateExistingDamage(int64_t fbo_id);\n\n  bool Present() override;\n\n  struct FBOSlot {\n    ClayOpenGLFramebuffer framebuffer;\n    uint32_t buffer_age;\n    skity::Rect damage_rect;\n  };\n  bool disable_partial_repaint_ = false;\n  std::optional<FBOSlot> fbo_storage_;\n  ClaySize size_ = {0, 0};\n  std::list<skity::Rect> damage_history_;\n  ClaySharedImageSinkAccessorRef surface_accessor_ = nullptr;\n\n  static constexpr uint32_t kMaxHistorySize = 10;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_GL_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_gles.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_gles.h\"\n\n#include <string>\n#include <vector>\n\n#include \"clay/shell/platform/headless/clay_headless_engine.h\"\n\nnamespace clay {\n\nnamespace {\n// Logs an EGL error to stderr. This automatically calls eglGetError()\n// and logs the error code.\nstatic void LogEglError(std::string message) {\n  EGLint error = eglGetError();\n  FML_LOG(ERROR) << \"EGL: \" << message;\n  FML_LOG(ERROR) << \"EGL: eglGetError returned \" << error;\n}\n\nstatic bool ChooseEGLConfiguration(EGLDisplay display, EGLConfig* egl_config) {\n  EGLint attributes[] = {\n      // clang-format off\n      EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,\n      EGL_SURFACE_TYPE,    EGL_WINDOW_BIT,\n      EGL_RED_SIZE,        8,\n      EGL_GREEN_SIZE,      8,\n      EGL_BLUE_SIZE,       8,\n      EGL_ALPHA_SIZE,      8,\n      EGL_DEPTH_SIZE,      0,\n      EGL_STENCIL_SIZE,    0,\n      EGL_NONE,            // termination sentinel\n      // clang-format on\n  };\n  EGLint config_count = 0;\n  if (eglChooseConfig(display, attributes, egl_config, 1, &config_count) !=\n      EGL_TRUE) {\n    return false;\n  }\n  return config_count > 0 && egl_config != nullptr;\n}\n\n}  // namespace\n\nClayHeadlessRendererGLES::ClayHeadlessRendererGLES(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config)\n    : ClayHeadlessRendererSharedImageGL(engine, config),\n      egl_display_(EGL_NO_DISPLAY),\n      egl_context_(EGL_NO_CONTEXT),\n      egl_config_(nullptr),\n      egl_surface_(EGL_NO_SURFACE) {\n  // Get the display.\n  egl_display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n  if (egl_display_ == EGL_NO_DISPLAY) {\n    LogEglError(\"Failed to get EGL display\");\n    return;\n  }\n  // Initialize the display connection.\n  if (eglInitialize(egl_display_, nullptr, nullptr) != EGL_TRUE) {\n    LogEglError(\"Failed to initialize EGL\");\n    egl_display_ = EGL_NO_DISPLAY;\n    return;\n  }\n  // Choose an EGL configuration.\n  if (!ChooseEGLConfiguration(egl_display_, &egl_config_)) {\n    return;\n  }\n  const EGLint display_context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2,\n                                               EGL_NONE};\n  egl_context_ = eglCreateContext(egl_display_, egl_config_, EGL_NO_CONTEXT,\n                                  display_context_attributes);\n  if (egl_context_ == EGL_NO_CONTEXT) {\n    LogEglError(\"Failed to create EGL context\");\n    return;\n  }\n  // Create a 1x1 PbufferSurface to initialize the EGL context.\n  const EGLint attribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};\n  egl_surface_ = eglCreatePbufferSurface(egl_display_, egl_config_, attribs);\n  if (egl_surface_ == EGL_NO_SURFACE) {\n    LogEglError(\"Failed to create EGL surface\");\n    return;\n  }\n}\n\nClayHeadlessRendererGLES::~ClayHeadlessRendererGLES() {\n  EGLBoolean result = EGL_FALSE;\n  if (egl_display_ != EGL_NO_DISPLAY && egl_surface_ != EGL_NO_SURFACE) {\n    result = eglDestroySurface(egl_display_, egl_surface_);\n    egl_surface_ = EGL_NO_SURFACE;\n    if (result == EGL_FALSE) {\n      LogEglError(\"Failed to destroy context\");\n    }\n  }\n\n  if (egl_display_ != EGL_NO_DISPLAY && egl_context_ != EGL_NO_CONTEXT) {\n    result = eglDestroyContext(egl_display_, egl_context_);\n    egl_context_ = EGL_NO_CONTEXT;\n\n    if (result == EGL_FALSE) {\n      LogEglError(\"Failed to destroy context\");\n    }\n  }\n\n  if (egl_display_ != EGL_NO_DISPLAY) {\n    result = eglTerminate(egl_display_);\n    if (result == EGL_FALSE) {\n      LogEglError(\"Failed to destroy display\");\n    }\n    egl_display_ = EGL_NO_DISPLAY;\n  }\n}\n\nGPUSurfaceGLDelegate::GLProcResolver\nClayHeadlessRendererGLES::GetGLProcResolver() const {\n  return [](const char* name) -> void* {\n    return reinterpret_cast<void*>(eglGetProcAddress(name));\n  };\n}\n\nbool ClayHeadlessRendererGLES::MakeCurrent() {\n  return (eglMakeCurrent(egl_display_, egl_surface_, egl_surface_,\n                         egl_context_) == EGL_TRUE);\n}\n\nbool ClayHeadlessRendererGLES::ClearCurrent() {\n  return (eglMakeCurrent(egl_display_, EGL_NO_SURFACE, EGL_NO_SURFACE,\n                         EGL_NO_CONTEXT) == EGL_TRUE);\n}\n\nstd::unique_ptr<ClayHeadlessRenderer> ClayHeadlessRenderer::CreateGL(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config) {\n  return std::make_unique<ClayHeadlessRendererGLES>(engine, config);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_gles.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_GLES_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_GLES_H_\n\n#include <EGL/egl.h>\n\n#include <memory>\n\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_gl.h\"\n\nnamespace clay {\n\nclass ClayHeadlessRendererGLES final\n    : public ClayHeadlessRendererSharedImageGL {\n public:\n  ClayHeadlessRendererGLES(ClayHeadlessEngine* engine,\n                           const ClayHardwareRendererConfig& config);\n  ~ClayHeadlessRendererGLES() override;\n\n  // |GPUSurfaceGLDelegate|\n  GLProcResolver GetGLProcResolver() const override;\n\n  bool MakeCurrent() override;\n  bool ClearCurrent() override;\n\n private:\n  EGLDisplay egl_display_;\n\n  EGLContext egl_context_;\n\n  EGLConfig egl_config_;\n\n  // Pbuffer surface to make current\n  EGLSurface egl_surface_ = EGL_NO_SURFACE;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_GLES_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_host_gl.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_host_gl.h\"\n\n#include <string>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"build/build_config.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n#include \"clay/gfx/skity_to_skia_utils.h\"\n#include \"clay/shell/platform/embedder/embedder_struct_macros.h\"\n#include \"clay/shell/platform/headless/clay_headless_engine.h\"\n\nnamespace clay {\n\nstd::unique_ptr<ClayHeadlessRenderer> ClayHeadlessRenderer::CreateHostGL(\n    ClayHeadlessEngine* engine, const ClayOpenGLRendererConfig& config) {\n  const ClayOpenGLRendererConfig* config_ptr = &config;\n  if (SAFE_ACCESS(config_ptr, enable_shared_image_sink, false)) {\n    ClaySharedImageSinkBufferMode buffer_mode =\n        SAFE_ACCESS(config_ptr, shared_image_sink_buffer_mode,\n                    kClaySharedImageSinkBufferModeDoubleBuffer);\n    return std::make_unique<ClayHeadlessRendererSharedImageHostGL>(\n        engine, config, buffer_mode);\n\n  } else {\n    return std::make_unique<ClayHeadlessRendererHostGL>(engine, config);\n  }\n}\n\nClayHeadlessRendererHostGL::ClayHeadlessRendererHostGL(\n    ClayHeadlessEngine* engine, const ClayOpenGLRendererConfig& renderer_config)\n    : ClayHeadlessRendererGL(engine), config_(renderer_config) {\n  FML_LOG(ERROR) << \"Starting Clay in [Host GL] mode. \"\n                    \"Components using external textures will NOT work\";\n}\n\nGPUSurfaceGLDelegate::GLProcResolver\nClayHeadlessRendererHostGL::GetGLProcResolver() const {\n  return [this](const char* name) -> void* {\n    return const_cast<ClayHeadlessRendererHostGL*>(this)->ResolveProc(name);\n  };\n}\n\nbool ClayHeadlessRendererHostGL::MakeCurrent() {\n  return config_.make_current(engine_->UserData());\n}\n\nbool ClayHeadlessRendererHostGL::ClearCurrent() {\n  return config_.clear_current(engine_->UserData());\n}\n\nbool ClayHeadlessRendererHostGL::Present() {\n  return config_.present(engine_->UserData());\n}\n\nint64_t ClayHeadlessRendererHostGL::FBO(const ClayFrameInfo& frame_info) {\n  return config_.fbo_callback(engine_->UserData(), &frame_info);\n}\n\nvoid* ClayHeadlessRendererHostGL::ResolveProc(const char* name) {\n  return config_.gl_proc_resolver(engine_->UserData(), name);\n}\n\nvoid ClayHeadlessRendererHostGL::CleanupGPUResources() {}\n\nClayHeadlessRendererSharedImageHostGL::ClayHeadlessRendererSharedImageHostGL(\n    ClayHeadlessEngine* engine, const ClayOpenGLRendererConfig& renderer_config,\n    ClaySharedImageSinkBufferMode buffer_mode)\n    : ClayHeadlessRenderer(engine),\n      host_gl_thread_(\"clay.headless.host-gl\"),\n      config_(renderer_config) {\n  FML_LOG(ERROR) << \"Starting Clay in [Host GL+SharedImage] mode. \"\n                    \"Maybe slow in large views\";\n\n  host_gl_thread_.GetTaskRunner()->PostTask([&] {\n#ifdef ENABLE_SKITY\n    host_gl_surface_ = std::make_unique<GPUSurfaceGLSkity>(this, nullptr);\n#else\n    host_gl_surface_ = std::make_unique<GPUSurfaceGLSkia>(this, true);\n#endif\n  });\n\n  ClayHeadlessRendererConfig hardware_config;\n\n  ClaySharedImageBackingType image_backing_type;\n\n#if OS_MACOSX\n  image_backing_type = kClaySharedImageBackingTypeIOSurface;\n  hardware_config.type = kClayRendererTypeMetal;\n#elif OS_WIN\n  image_backing_type = kClaySharedImageBackingTypeD3DTexture;\n  hardware_config.type = kClayRendererTypeOpenGL;\n#elif OS_LINUX\n  image_backing_type = kClaySharedImageBackingTypeShmImage;\n  hardware_config.type = kClayRendererTypeOpenGL;\n#elif OS_HARMONY\n  image_backing_type = kClaySharedImageBackingTypeNativeImage;\n  hardware_config.type = kClayRendererTypeOpenGL;\n#else\n  FML_DCHECK(false) << \"Shared Image Renderer not supported on this platform\";\n  return;\n#endif\n\n  ClaySharedImageSinkRef sink_ref =\n      ClayCreateSharedImageSink(buffer_mode, image_backing_type,\n                                kClaySharedImageBackingPixelFormatNative8888);\n  shared_image_sink_ = fml::RefPtr<clay::SharedImageSink>(\n      reinterpret_cast<clay::SharedImageSink*>(sink_ref));\n\n  shared_image_sink_->SetFrameAvailableCallback([this] {\n    // The callback is triggered in Clay Raster thread\n    host_gl_thread_.GetTaskRunner()->PostTask([this] { Draw(); });\n  });\n\n  hardware_config.hardware.struct_size = sizeof(hardware_config.hardware);\n  hardware_config.hardware.sink_ref = sink_ref;\n  renderer_ = ClayHeadlessRenderer::Create(engine, hardware_config);\n\n  FML_CHECK(renderer_);\n\n  // sink_ref is owned by shared_image_sink_\n  ClayReleaseSharedImageSink(sink_ref);\n}\n\n// |ClayHeadlessRenderer|\nEmbedderSurfaceSoftwareDelegate*\nClayHeadlessRendererSharedImageHostGL::GetSoftwareRendererDelegate() {\n  return renderer_->GetSoftwareRendererDelegate();\n}\n#ifdef SHELL_ENABLE_GL\n// |ClayHeadlessRenderer|\nGPUSurfaceGLDelegate*\nClayHeadlessRendererSharedImageHostGL::GetGLRendererDelegate() {\n  return renderer_->GetGLRendererDelegate();\n}\n#endif\n#ifdef SHELL_ENABLE_METAL\n// |ClayHeadlessRenderer|\nEmbedderSurfaceMetalDelegate*\nClayHeadlessRendererSharedImageHostGL::GetMetalRendererDelegate() {\n  return renderer_->GetMetalRendererDelegate();\n}\n#endif\n\nClayHeadlessRenderer*\nClayHeadlessRendererSharedImageHostGL::GetEngineRenderer() {\n  return renderer_.get();\n}\n\nClayHeadlessRendererSharedImageHostGL::\n    ~ClayHeadlessRendererSharedImageHostGL() {\n  {\n    std::lock_guard<std::mutex> lock(shared_image_sink_mutex_);\n    // shared_image_sink internally keeps ref to D3D device and mutex,\n    // which means it should be reset before destroy renderer\n    shared_image_sink_->SetFrameAvailableCallback(nullptr);\n    shared_image_sink_ = nullptr;\n  }\n  renderer_.reset();\n  fml::AutoResetWaitableEvent latch;\n  host_gl_thread_.GetTaskRunner()->PostTask([&] {\n    host_gl_surface_.reset();\n    latch.Signal();\n  });\n  latch.Wait();\n}\n\nvoid ClayHeadlessRendererSharedImageHostGL::CleanupGPUResources() {\n  renderer_->CleanupGPUResources();\n}\n\n// |GPUSurfaceGLDelegate|\nstd::unique_ptr<GLContextResult>\nClayHeadlessRendererSharedImageHostGL::GLContextMakeCurrent() {\n  return std::make_unique<GLContextDefaultResult>(\n      config_.make_current(engine_->UserData()));\n}\n\n// |GPUSurfaceGLDelegate|\nbool ClayHeadlessRendererSharedImageHostGL::GLContextClearCurrent() {\n  return config_.clear_current(engine_->UserData());\n}\n\n// |GPUSurfaceGLDelegate|\nbool ClayHeadlessRendererSharedImageHostGL::GLContextPresent(\n    const GLPresentInfo& present_info) {\n  return config_.present(engine_->UserData());\n}\n\n// |GPUSurfaceGLDelegate|\nbool ClayHeadlessRendererSharedImageHostGL::GLContextFBOResetAfterPresent()\n    const {\n  return true;\n}\n\n// |GPUSurfaceGLDelegate|\nGLFBOInfo ClayHeadlessRendererSharedImageHostGL::GLContextFBO(\n    GLFrameInfo frame_info) const {\n  ClayFrameInfo clay_frame_info{};\n  clay_frame_info.struct_size = sizeof(clay_frame_info);\n  clay_frame_info.width = frame_info.width;\n  clay_frame_info.height = frame_info.height;\n  return {.fbo_id = config_.fbo_callback(engine_->UserData(), &clay_frame_info),\n          .existing_damage = {}};\n}\n\n// |GPUSurfaceGLDelegate|\nGPUSurfaceGLDelegate::GLProcResolver\nClayHeadlessRendererSharedImageHostGL::GetGLProcResolver() const {\n  return [gl_proc_resolver = config_.gl_proc_resolver,\n          user_data = engine_->UserData()](const char* name) -> void* {\n    return gl_proc_resolver(user_data, name);\n  };\n}\n\nvoid ClayHeadlessRendererSharedImageHostGL::Draw() {\n#ifdef ENABLE_SKITY\n  FML_UNIMPLEMENTED();\n#else\n  TRACE_EVENT(\"flutter\", __FUNCTION__);\n  std::lock_guard<std::mutex> lock(shared_image_sink_mutex_);\n  if (!host_gl_surface_ || !shared_image_sink_) {\n    return;\n  }\n\n  SkMatrix transformation;\n  SkBitmap bitmap;\n  {\n    fml::RefPtr<clay::SharedImageBacking> backing =\n        shared_image_sink_->UpdateFront(nullptr);\n    if (!backing) {\n      FML_LOG(ERROR) << \"No front buffer\";\n      return;\n    }\n\n    // We don't need to hold the front, so we always release it\n    struct AutoReleaseSink {\n      explicit AutoReleaseSink(clay::SharedImageSink& sink) : sink_(sink) {}\n\n      ~AutoReleaseSink() { sink_.ReleaseFront(nullptr); }\n\n      clay::SharedImageSink& sink_;\n    };\n\n    AutoReleaseSink auto_release_sink(*shared_image_sink_);\n\n    if (backing->GetPixelFormat() !=\n        clay::SharedImageBacking::PixelFormat::kNative8888) {\n      FML_LOG(ERROR) << \"PixelFormat not supported: \"\n                     << static_cast<uint32_t>(backing->GetPixelFormat());\n      return;\n    }\n\n    // Currently, kNative8888 equals BGRA8888\n    auto image_info = SkImageInfo::Make(\n        SkISize::Make(backing->GetSize().x, backing->GetSize().y),\n        kBGRA_8888_SkColorType, kPremul_SkAlphaType);\n    bitmap.allocPixels(image_info, 0);\n\n    if (std::unique_ptr<clay::FenceSync> fence_sync = backing->GetFenceSync()) {\n      if (!fence_sync->ClientWait()) {\n        FML_LOG(ERROR) << \"Failed to wait sync\";\n        return;\n      }\n    }\n\n    {\n      TRACE_EVENT(\"flutter\", \"SharedImageBacking::ReadbackToMemory\");\n\n      // temp trace\n      fml::TimePoint start = fml::TimePoint::Now();\n\n      if (!backing->ReadbackToMemory(&bitmap.pixmap(), 1)) {\n        FML_LOG(ERROR) << \"Failed to ReadbackToMemory\";\n        return;\n      }\n\n      bitmap.setImmutable();\n\n      transformation =\n          clay::ConvertSkityMatrixToSkMatrix(backing->GetTransformation());\n\n      [[maybe_unused]] fml::TimeDelta duration = fml::TimePoint::Now() - start;\n\n      // Remove these lines after tracing working\n      // FML_LOG(ERROR) << \"!!! ReadbackToMemory from [\"\n      //                << backing->GetSize().width() << \"x\"\n      //                << backing->GetSize().height()\n      //                << \"], cost: \" << duration.ToMilliseconds() << \"ms\";\n    }\n  }\n\n  std::unique_ptr<SurfaceFrame> frame = host_gl_surface_->AcquireFrame(\n      {bitmap.dimensions().fWidth, bitmap.dimensions().fHeight});\n\n  if (!frame) {\n    FML_LOG(ERROR) << \"Failed to AcquireFrame\";\n    return;\n  }\n\n  SkCanvas* canvas = frame->GetCanvas();\n  canvas->clear(SK_ColorTRANSPARENT);\n\n  SkAutoCanvasRestore autoRestore(canvas, true);\n\n  sk_sp<SkImage> sk_image = bitmap.asImage();\n\n  SkIRect bounds = sk_image->bounds();\n\n  // The incoming texture is vertically flipped, so we flip it\n  // back.\n  // Maybe it's better to use SurfaceOrigin in AdoptTexture,\n  // but on Electron this method doesn't work.\n  SkMatrix flip_y_mat =\n      SkMatrix::MakeAll(1, 0, 0, 0, -1, bounds.height(), 0, 0, 1);\n\n  canvas->concat(flip_y_mat);\n\n  if (!transformation.isIdentity()) {\n    sk_sp<SkShader> shader =\n        sk_image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,\n                             SkSamplingOptions(), transformation);\n\n    SkPaint paintWithShader;\n    paintWithShader.setShader(shader);\n    canvas->drawRect(SkRect::Make(sk_image->bounds()), paintWithShader);\n  } else {\n    canvas->drawImage(sk_image, 0, 0, SkSamplingOptions());\n  }\n  frame->Submit();\n\n  host_gl_surface_->GetContext()->performDeferredCleanup(\n      std::chrono::milliseconds(0));\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/gl/clay_headless_renderer_host_gl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_HOST_GL_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_HOST_GL_H_\n\n#include <memory>\n\n#include \"base/include/fml/thread.h\"\n#ifdef ENABLE_SKITY\n#include \"clay/shell/gpu/gpu_surface_gl_skity.h\"\n#else\n#include \"clay/shell/gpu/gpu_surface_gl_skia.h\"\n#endif\n#include \"clay/shell/platform/headless/gl/clay_headless_renderer_gl.h\"\n\nnamespace clay {\nclass SharedImageSink;\n}\n\nnamespace clay {\n\nclass ClayHeadlessRendererHostGL final : public ClayHeadlessRendererGL {\n public:\n  ClayHeadlessRendererHostGL(ClayHeadlessEngine* engine,\n                             const ClayOpenGLRendererConfig& renderer_config);\n\n  GPUSurfaceGLDelegate::GLProcResolver GetGLProcResolver() const override;\n\n  bool MakeCurrent() override;\n  bool ClearCurrent() override;\n  bool Present() override;\n  int64_t FBO(const ClayFrameInfo& frame_info) override;\n\n  void* ResolveProc(const char* name);\n\n  void CleanupGPUResources() override;\n\n private:\n  ClayOpenGLRendererConfig config_;\n};\n\n// In this mode, we create a \"fake\" render thread\n// which create skia environment from host gl.\n// The render thread takes the front buffer of shared_image_sink_,\n// calling `ReadbackToMemory` to get the pixel data,\n// and draw the pixel data to SkSurface in host gl.\nclass ClayHeadlessRendererSharedImageHostGL final\n    : public ClayHeadlessRenderer,\n      public GPUSurfaceGLDelegate {\n public:\n  ClayHeadlessRendererSharedImageHostGL(\n      ClayHeadlessEngine* engine,\n      const ClayOpenGLRendererConfig& renderer_config,\n      ClaySharedImageSinkBufferMode buffer_mode);\n\n  ~ClayHeadlessRendererSharedImageHostGL() override;\n\n  void CleanupGPUResources() override;\n\n  // |ClayHeadlessRenderer|\n  EmbedderSurfaceSoftwareDelegate* GetSoftwareRendererDelegate() override;\n#ifdef SHELL_ENABLE_GL\n  // |ClayHeadlessRenderer|\n  GPUSurfaceGLDelegate* GetGLRendererDelegate() override;\n#endif\n#ifdef SHELL_ENABLE_METAL\n  // |ClayHeadlessRenderer|\n  EmbedderSurfaceMetalDelegate* GetMetalRendererDelegate() override;\n#endif\n\n  // |ClayHeadlessRenderer|\n  ClayHeadlessRenderer* GetEngineRenderer() override;\n\n  // |GPUSurfaceGLDelegate|\n  std::unique_ptr<GLContextResult> GLContextMakeCurrent() override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextClearCurrent() override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextPresent(const GLPresentInfo& present_info) override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextFBOResetAfterPresent() const override;\n\n  // |GPUSurfaceGLDelegate|\n  GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;\n\n  // |GPUSurfaceGLDelegate|\n  GLProcResolver GetGLProcResolver() const override;\n\n private:\n  void Draw();\n\n  fml::Thread host_gl_thread_;\n#ifdef ENABLE_SKITY\n  std::unique_ptr<GPUSurfaceGLSkity> host_gl_surface_;\n#else\n  std::unique_ptr<GPUSurfaceGLSkia> host_gl_surface_;\n#endif\n  fml::RefPtr<clay::SharedImageSink> shared_image_sink_;\n  std::mutex shared_image_sink_mutex_;\n  std::unique_ptr<ClayHeadlessRenderer> renderer_;\n  ClayOpenGLRendererConfig config_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_GL_CLAY_HEADLESS_RENDERER_HOST_GL_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/metal/clay_headless_renderer_metal.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_METAL_CLAY_HEADLESS_RENDERER_METAL_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_METAL_CLAY_HEADLESS_RENDERER_METAL_H_\n\n#import <Cocoa/Cocoa.h>\n#import <Metal/Metal.h>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/fml/platform/darwin/scoped_nsobject.h\"\n#include \"clay/shell/platform/headless/clay_headless_renderer.h\"\n\nnamespace clay {\nclass IOSurfaceImageBacking;\n}\n\nnamespace clay {\n\nclass ClayHeadlessRendererMetal : public ClayHeadlessRenderer,\n                                  public EmbedderSurfaceMetalDelegate {\n public:\n  ClayHeadlessRendererMetal(ClayHeadlessEngine* engine,\n                            const ClayHardwareRendererConfig& config);\n  ~ClayHeadlessRendererMetal() override;\n\n  EmbedderSurfaceMetalDelegate* GetMetalRendererDelegate() override;\n\n  GPUMTLDeviceHandle GetMTLDevice() const override;\n  GPUMTLCommandQueueHandle GetMTLCommandQueue() const override;\n  GPUMTLTextureInfo GetMTLTexture(const skity::Vec2& frame_size) const override;\n  bool PresentTexture(GPUMTLTextureInfo texture) const override;\n  bool EnablePartialRepaint() const override;\n\n  void CleanupGPUResources() override;\n\n private:\n  ClaySharedImageSinkAccessorRef surface_accessor_ = nullptr;\n  bool enable_partial_repaint_ = false;\n  fml::scoped_nsprotocol<id<MTLDevice>> device_;\n  fml::scoped_nsprotocol<id<MTLCommandQueue>> command_queue_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_METAL_CLAY_HEADLESS_RENDERER_METAL_H_\n"
  },
  {
    "path": "clay/shell/platform/headless/metal/clay_headless_renderer_metal.mm",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/headless/metal/clay_headless_renderer_metal.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/shared_image/iosurface_image_backing.h\"\n#include \"clay/gfx/shared_image/mtl_image_representation.h\"\n#include \"clay/shell/platform/headless/clay_headless_engine.h\"\n\nstatic_assert(__has_feature(objc_arc), \"ARC must be enabled.\");\n\nnamespace clay {\nClayHeadlessRendererMetal::ClayHeadlessRendererMetal(ClayHeadlessEngine* engine,\n                                                     const ClayHardwareRendererConfig& config)\n    : ClayHeadlessRenderer(engine), enable_partial_repaint_(!config.disable_partial_repaint) {\n  device_.reset(MTLCreateSystemDefaultDevice());\n  FML_CHECK(device_ != nil);\n  command_queue_.reset([device_ newCommandQueue]);\n  FML_CHECK(command_queue_ != nil);\n\n  ClaySharedImageRepresentationConfig image_config{};\n  image_config.struct_size = sizeof(ClaySharedImageRepresentationConfig);\n  image_config.type = kClaySharedImageRepresentationTypeMetal;\n  image_config.metal_config.struct_size = sizeof(ClaySharedImageMetalRepresentationConfig);\n  image_config.metal_config.device = (__bridge ClayMetalDeviceHandle)device_.get();\n  image_config.metal_config.command_queue =\n      (__bridge ClayMetalCommandQueueHandle)command_queue_.get();\n  surface_accessor_ = ClayCreateSharedImageSinkAccessor(config.sink_ref, &image_config);\n}\n\nClayHeadlessRendererMetal::~ClayHeadlessRendererMetal() = default;\n\nEmbedderSurfaceMetalDelegate* ClayHeadlessRendererMetal::GetMetalRendererDelegate() { return this; }\n\nGPUMTLDeviceHandle ClayHeadlessRendererMetal::GetMTLDevice() const {\n  return (__bridge GPUMTLDeviceHandle)device_.get();\n}\nGPUMTLCommandQueueHandle ClayHeadlessRendererMetal::GetMTLCommandQueue() const {\n  return (__bridge GPUMTLCommandQueueHandle)command_queue_.get();\n}\nGPUMTLTextureInfo ClayHeadlessRendererMetal::GetMTLTexture(const skity::Vec2& frame_size) const {\n  ClaySize size{.width = static_cast<uint32_t>(frame_size.x),\n                .height = static_cast<uint32_t>(frame_size.y)};\n  ClaySharedImageWriteResult result;\n  ClaySharedImageRef shared_image;\n  uint32_t buffer_age = 0;  // partial repaint is handled in gpu_surface_metal_skia.mm\n\n  GPUMTLTextureInfo res;\n  memset(&res, 0, sizeof(clay::GPUMTLTextureInfo));\n  if (!surface_accessor_ || !ClaySharedImageSinkBeginWrite(surface_accessor_, &size, &shared_image,\n                                                           &result, &buffer_age)) {\n    FML_LOG(ERROR) << \"failed to get fbo\";\n    return res;\n  }\n  // Metal coordinate is Y-flipped\n  ClayTransformation transformation{1, 0, 0, 0, -1, 1, 0, 0, 1};\n  ClaySharedImageSetTransformation(shared_image, &transformation);\n  FML_DCHECK(result.type == kClaySharedImageRepresentationTypeMetal);\n\n  res.texture = result.metal_texture.texture;\n  res.texture_id = reinterpret_cast<int64_t>(result.metal_texture.texture);\n  res.destruction_callback = result.metal_texture.destruction_callback;\n  res.destruction_context = result.metal_texture.user_data;\n  return res;\n}\nbool ClayHeadlessRendererMetal::PresentTexture(GPUMTLTextureInfo texture) const {\n  if (!surface_accessor_) {\n    return false;\n  }\n  return ClaySharedImageSinkEndWrite(surface_accessor_);\n}\nbool ClayHeadlessRendererMetal::EnablePartialRepaint() const { return enable_partial_repaint_; }\n\nvoid ClayHeadlessRendererMetal::CleanupGPUResources() {\n  if (surface_accessor_) {\n    ClayDestroySharedImageSinkAccessor(surface_accessor_);\n    surface_accessor_ = nullptr;\n  }\n}\n\nstd::unique_ptr<ClayHeadlessRenderer> ClayHeadlessRenderer::CreateMetal(\n    ClayHeadlessEngine* engine, const ClayHardwareRendererConfig& config) {\n  return std::make_unique<ClayHeadlessRendererMetal>(engine, config);\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/headless/public/embdder_headless_delegate.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_HEADLESS_PUBLIC_EMBDDER_HEADLESS_DELEGATE_H_\n#define CLAY_SHELL_PLATFORM_HEADLESS_PUBLIC_EMBDDER_HEADLESS_DELEGATE_H_\n\nnamespace lynx {\nclass HeadlessDelegate {\n public:\n  virtual const char* GetClipboardData() const = 0;\n  virtual void SetClipboardData(const char* data) = 0;\n  virtual void ActivateSystemCursor(int type, const char* path) = 0;\n  virtual void ShowTextInput() = 0;\n  virtual void HideTextInput() = 0;\n  virtual void SetMarkedTextRect(float x, float y, float width,\n                                 float height) = 0;\n  virtual void SetEditableTransform(const float transform_matrix[16]) = 0;\n};\n}  // namespace lynx\n\n#endif  // CLAY_SHELL_PLATFORM_HEADLESS_PUBLIC_EMBDDER_HEADLESS_DELEGATE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nassert(is_win)\n\nimport(\"../../../common/config.gni\")\n\nconfig(\"relative_angle_headers\") {\n  include_dirs = [ \"//third_party/angle/include\" ]\n}\n\nsource_set(\"flutter_windows_utils\") {\n  sources = [\n    \"dpi_utils.cc\",\n    \"dpi_utils.h\",\n  ]\n  configs += [ \"../../../../clay:config\" ]\n}\n\nsource_set(\"flutter_windows_source\") {\n  # Common Windows sources.\n  sources = [\n    \"cursor_handler.cc\",\n    \"cursor_handler.h\",\n    \"direct_manipulation.cc\",\n    \"direct_manipulation.h\",\n    \"egl/child_window_win.cc\",\n    \"egl/child_window_win.h\",\n    \"egl/context.cc\",\n    \"egl/context.h\",\n    \"egl/direct_composition_surface.cc\",\n    \"egl/direct_composition_surface.h\",\n    \"egl/egl.cc\",\n    \"egl/egl.h\",\n    \"egl/manager.cc\",\n    \"egl/manager.h\",\n    \"egl/surface.cc\",\n    \"egl/surface.h\",\n    \"egl/window_surface.cc\",\n    \"egl/window_surface.h\",\n    \"flutter_key_map.g.cc\",\n    \"flutter_project_bundle.cc\",\n    \"flutter_project_bundle.h\",\n    \"flutter_window.cc\",\n    \"flutter_window.h\",\n    \"flutter_windows_engine.cc\",\n    \"flutter_windows_engine.h\",\n    \"flutter_windows_view.cc\",\n    \"flutter_windows_view.h\",\n    \"keyboard_handler_base.h\",\n    \"keyboard_key_embedder_handler.cc\",\n    \"keyboard_key_embedder_handler.h\",\n    \"keyboard_key_handler.cc\",\n    \"keyboard_key_handler.h\",\n    \"keyboard_manager.cc\",\n    \"keyboard_manager.h\",\n    \"keyboard_utils.cc\",\n    \"keyboard_utils.h\",\n    \"platform_handler.cc\",\n    \"platform_handler.h\",\n    \"sequential_id_generator.cc\",\n    \"sequential_id_generator.h\",\n    \"task_runner.cc\",\n    \"task_runner.h\",\n    \"task_runner_window.cc\",\n    \"task_runner_window.h\",\n    \"text_input_manager.cc\",\n    \"text_input_manager.h\",\n    \"text_input_plugin.cc\",\n    \"text_input_plugin.h\",\n    \"window.cc\",\n    \"window.h\",\n    \"window_binding_handler.h\",\n    \"window_binding_handler_delegate.h\",\n    \"window_mouse_drop_handler.cc\",\n    \"window_mouse_drop_handler.h\",\n    \"window_move_handler.cc\",\n    \"window_move_handler.h\",\n    \"window_proc_delegate_manager.cc\",\n    \"window_proc_delegate_manager.h\",\n    \"windows_proc_table.cc\",\n    \"windows_proc_table.h\",\n    \"windowsx_shim.h\",\n  ]\n\n  libs = [\n    \"dwmapi.lib\",\n    \"imm32.lib\",\n  ]\n\n  configs += [ \"//third_party/angle:gl_prototypes\" ]\n\n  public_configs = [\n    \":relative_angle_headers\",\n    \"../../../../clay:config\",\n  ]\n\n  defines = [\n    \"FLUTTER_ENGINE_NO_PROTOTYPES\",\n    \"_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING\",\n  ]\n\n  public_deps = [ \"../../../fml:string_conversion\" ]\n\n  defines += [ \"CLAY_LIBRARY_IMPLEMENTATION\" ]\n\n  if (clay_force_d3d9) {\n    defines += [ \"CLAY_FORCE_D3D9\" ]\n  }\n\n  deps = [\n    \":flutter_windows_utils\",\n    \"../../../../third_party/rapidjson\",\n    \"../../../common/graphics:gl_scoped_binder\",\n    \"../../../fml:fml\",\n    \"../../../fml:icu_util\",\n    \"../../../gfx/shared_image\",\n    \"../../../ui:ui\",\n    \"../../common:common\",\n    \"../common:common_cpp_core\",\n    \"../common:common_cpp_input\",\n    \"../common:common_cpp_switches\",\n    \"../embedder:embedder\",\n    \"//third_party/angle:libEGL_static\",  # the order of libEGL_static and\n                                          # libGLESv2_static is important.. if\n                                          # reversed, will cause a linker error\n                                          # DllMain already defined in\n                                          # LIBCMTD.lib\n    \"//third_party/angle:libGLESv2_static\",\n  ]\n\n  if (enable_trace != \"none\") {\n    deps += [ \"../../../../base/trace/native:trace\" ]\n  }\n\n  configs += [ \"../../../../clay:config\" ]\n}\n"
  },
  {
    "path": "clay/shell/platform/windows/cursor_handler.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/cursor_handler.h\"\n\n#include <windows.h>\n\n#include <cstring>\n#include <vector>\n\n#include \"clay/shell/platform/windows/flutter_windows_engine.h\"\n#include \"clay/shell/platform/windows/flutter_windows_view.h\"\n#include \"clay/ui/platform/cursor_types.h\"\n\nnamespace clay {\n\nCursorHandler::CursorHandler(FlutterWindowsEngine* engine)\n    : engine_(engine), weak_factory_(this) {}\n\nvoid CursorHandler::ActivateSystemCursor(int type, const char* path) {\n  auto kind = static_cast<clay::CursorTypes>(type);\n  FlutterWindowsView* view = engine_->view();\n  if (view == nullptr) {\n    FML_LOG(ERROR) << \"Cursor is not available in Windows headless mode\";\n    return;\n  }\n  view->UpdateFlutterCursor(kind);\n}\n\nHCURSOR GetCursorFromBuffer(const std::vector<uint8_t>& buffer, double hot_x,\n                            double hot_y, int width, int height) {\n  HCURSOR cursor = nullptr;\n  HDC display_dc = GetDC(NULL);\n  // Flutter should returns rawBGRA, which has 8bits * 4channels.\n  BITMAPINFO bmi;\n  memset(&bmi, 0, sizeof(bmi));\n  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);\n  bmi.bmiHeader.biWidth = width;\n  bmi.bmiHeader.biHeight = -height;\n  bmi.bmiHeader.biPlanes = 1;\n  bmi.bmiHeader.biBitCount = 32;\n  bmi.bmiHeader.biCompression = BI_RGB;\n  bmi.bmiHeader.biSizeImage = width * height * 4;\n  // Create the pixmap DIB section\n  uint8_t* pixels = 0;\n  HBITMAP bitmap =\n      CreateDIBSection(display_dc, &bmi, DIB_RGB_COLORS, (void**)&pixels, 0, 0);\n  ReleaseDC(0, display_dc);\n  if (!bitmap || !pixels) {\n    return nullptr;\n  }\n  int bytes_per_line = width * 4;\n  for (int y = 0; y < height; ++y) {\n    memcpy(pixels + y * bytes_per_line, &buffer[bytes_per_line * y],\n           bytes_per_line);\n  }\n  HBITMAP mask;\n  GetMaskBitmaps(bitmap, mask);\n  ICONINFO icon_info;\n  icon_info.fIcon = 0;\n  icon_info.xHotspot = hot_x;\n  icon_info.yHotspot = hot_y;\n  icon_info.hbmMask = mask;\n  icon_info.hbmColor = bitmap;\n  cursor = CreateIconIndirect(&icon_info);\n  DeleteObject(mask);\n  DeleteObject(bitmap);\n  return cursor;\n}\n\nvoid GetMaskBitmaps(HBITMAP bitmap, HBITMAP& mask_bitmap) {\n  HDC h_dc = ::GetDC(NULL);\n  HDC h_main_dc = ::CreateCompatibleDC(h_dc);\n  HDC h_and_mask_dc = ::CreateCompatibleDC(h_dc);\n\n  // Get the dimensions of the source bitmap\n  BITMAP bm;\n  ::GetObject(bitmap, sizeof(BITMAP), &bm);\n  mask_bitmap = ::CreateCompatibleBitmap(h_dc, bm.bmWidth, bm.bmHeight);\n\n  // Select the bitmaps to DC\n  HBITMAP h_old_main_bitmap = (HBITMAP)::SelectObject(h_main_dc, bitmap);\n  HBITMAP h_old_and_mask_bitmap =\n      (HBITMAP)::SelectObject(h_and_mask_dc, mask_bitmap);\n\n  // Scan each pixel of the souce bitmap and create the masks\n  COLORREF main_bit_pixel;\n  for (int x = 0; x < bm.bmWidth; ++x) {\n    for (int y = 0; y < bm.bmHeight; ++y) {\n      main_bit_pixel = ::GetPixel(h_main_dc, x, y);\n      if (main_bit_pixel == RGB(0, 0, 0)) {\n        ::SetPixel(h_and_mask_dc, x, y, RGB(255, 255, 255));\n      } else {\n        ::SetPixel(h_and_mask_dc, x, y, RGB(0, 0, 0));\n      }\n    }\n  }\n  ::SelectObject(h_main_dc, h_old_main_bitmap);\n  ::SelectObject(h_and_mask_dc, h_old_and_mask_bitmap);\n\n  ::DeleteDC(h_and_mask_dc);\n  ::DeleteDC(h_main_dc);\n\n  ::ReleaseDC(NULL, h_dc);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/cursor_handler.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_CURSOR_HANDLER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_CURSOR_HANDLER_H_\n\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/shell/platform/windows/window_binding_handler.h\"\n\nnamespace clay {\n\nclass FlutterWindowsEngine;\n\n// Handler for the cursor system.\nclass CursorHandler {\n public:\n  explicit CursorHandler(clay::FlutterWindowsEngine* engine);\n\n  void ActivateSystemCursor(int type, const char* path);\n\n private:\n  // The Flutter engine that will be notified for cursor updates.\n  FlutterWindowsEngine* engine_;\n\n  // The cache map for custom cursors.\n  std::unordered_map<std::string, HCURSOR> custom_cursors_;\n\n  fml::WeakPtrFactory<CursorHandler> weak_factory_;\n};\n\n// Create a cursor from a rawBGRA buffer and the cursor info.\nHCURSOR GetCursorFromBuffer(const std::vector<uint8_t>& buffer, double hot_x,\n                            double hot_y, int width, int height);\n\n// Get the corresponding mask bitmap from the source bitmap.\nvoid GetMaskBitmaps(HBITMAP bitmap, HBITMAP& mask_bitmap);\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_CURSOR_HANDLER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/direct_manipulation.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/direct_manipulation.h\"\n\n#include <algorithm>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/platform/windows/window.h\"\n#include \"clay/shell/platform/windows/window_binding_handler_delegate.h\"\n\n#define RETURN_IF_FAILED(operation)            \\\n  if (FAILED(operation)) {                     \\\n    FML_LOG(ERROR) << #operation << \" failed\"; \\\n    manager_ = nullptr;                        \\\n    updateManager_ = nullptr;                  \\\n    viewport_ = nullptr;                       \\\n    return -1;                                 \\\n  }\n\n#define WARN_IF_FAILED(operation)              \\\n  if (FAILED(operation)) {                     \\\n    FML_LOG(ERROR) << #operation << \" failed\"; \\\n  }\n\nnamespace clay {\n\nint32_t DirectManipulationEventHandler::GetDeviceId() {\n  return (int32_t) reinterpret_cast<int64_t>(this);\n}\n\nSTDMETHODIMP DirectManipulationEventHandler::QueryInterface(REFIID iid,\n                                                            void** ppv) {\n  if ((iid == IID_IUnknown) ||\n      (iid == IID_IDirectManipulationViewportEventHandler)) {\n    *ppv = static_cast<IDirectManipulationViewportEventHandler*>(this);\n    AddRef();\n    return S_OK;\n  } else if (iid == IID_IDirectManipulationInteractionEventHandler) {\n    *ppv = static_cast<IDirectManipulationInteractionEventHandler*>(this);\n    AddRef();\n    return S_OK;\n  }\n  return E_NOINTERFACE;\n}\n\nDirectManipulationEventHandler::GestureData\nDirectManipulationEventHandler::ConvertToGestureData(float transform[6]) {\n  // DirectManipulation provides updates with very high precision. If the user\n  // holds their fingers steady on a trackpad, DirectManipulation sends\n  // jittery updates. This calculation will reduce the precision of the scale\n  // value of the event to avoid jitter.\n  const int mantissa_bits_chop = 2;\n  const float factor = (1 << mantissa_bits_chop) + 1;\n  float c = factor * transform[0];\n  return GestureData{\n      c - (c - transform[0]),  // scale\n      transform[4],            // pan_x\n      transform[5],            // pan_y\n  };\n}\n\nHRESULT DirectManipulationEventHandler::OnViewportStatusChanged(\n    IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,\n    DIRECTMANIPULATION_STATUS previous) {\n  if (during_synthesized_reset_) {\n    during_synthesized_reset_ = current != DIRECTMANIPULATION_READY;\n    return S_OK;\n  }\n  during_inertia_ = current == DIRECTMANIPULATION_INERTIA;\n  if (current == DIRECTMANIPULATION_RUNNING) {\n    IDirectManipulationContent* content;\n    HRESULT hr = viewport->GetPrimaryContent(IID_PPV_ARGS(&content));\n    if (SUCCEEDED(hr)) {\n      float transform[6];\n      hr = content->GetContentTransform(transform, ARRAYSIZE(transform));\n      if (SUCCEEDED(hr)) {\n        initial_gesture_data_ = ConvertToGestureData(transform);\n      } else {\n        FML_LOG(ERROR) << \"GetContentTransform failed\";\n      }\n    } else {\n      FML_LOG(ERROR) << \"GetPrimaryContent failed\";\n    }\n    if (owner_->binding_handler_delegate) {\n      owner_->binding_handler_delegate->OnPointerPanZoomStart(GetDeviceId());\n    }\n  } else if (previous == DIRECTMANIPULATION_RUNNING) {\n    // Reset deltas to ensure only inertia values will be compared later.\n    last_pan_delta_x_ = 0.0;\n    last_pan_delta_y_ = 0.0;\n    if (owner_->binding_handler_delegate) {\n      owner_->binding_handler_delegate->OnPointerPanZoomEnd(GetDeviceId());\n    }\n  } else if (previous == DIRECTMANIPULATION_INERTIA) {\n    if (owner_->binding_handler_delegate &&\n        (std::max)(std::abs(last_pan_delta_x_), std::abs(last_pan_delta_y_)) >\n            0.01) {\n      owner_->binding_handler_delegate->OnScrollInertiaCancel(GetDeviceId());\n    }\n    // Need to reset the content transform to its original position\n    // so that we are ready for the next gesture.\n    // Use during_synthesized_reset_ flag to prevent sending reset also to the\n    // framework.\n    during_synthesized_reset_ = true;\n    last_pan_x_ = 0.0;\n    last_pan_y_ = 0.0;\n    last_pan_delta_x_ = 0.0;\n    last_pan_delta_y_ = 0.0;\n    RECT rect;\n    HRESULT hr = viewport->GetViewportRect(&rect);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to get the current viewport rect\";\n      return E_FAIL;\n    }\n    hr = viewport->ZoomToRect(rect.left, rect.top, rect.right, rect.bottom,\n                              false);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to reset the gesture using ZoomToRect\";\n      return E_FAIL;\n    }\n  }\n  return S_OK;\n}\n\nHRESULT DirectManipulationEventHandler::OnViewportUpdated(\n    IDirectManipulationViewport* viewport) {\n  return S_OK;\n}\n\nHRESULT DirectManipulationEventHandler::OnContentUpdated(\n    IDirectManipulationViewport* viewport,\n    IDirectManipulationContent* content) {\n  float transform[6];\n  HRESULT hr = content->GetContentTransform(transform, ARRAYSIZE(transform));\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"GetContentTransform failed\";\n    return S_OK;\n  }\n  if (!during_synthesized_reset_) {\n    GestureData data = ConvertToGestureData(transform);\n    float scale = data.scale / initial_gesture_data_.scale;\n    float pan_x = data.pan_x - initial_gesture_data_.pan_x;\n    float pan_y = data.pan_y - initial_gesture_data_.pan_y;\n    last_pan_delta_x_ = pan_x - last_pan_x_;\n    last_pan_delta_y_ = pan_y - last_pan_y_;\n    last_pan_x_ = pan_x;\n    last_pan_y_ = pan_y;\n    if (owner_->binding_handler_delegate && !during_inertia_) {\n      owner_->binding_handler_delegate->OnPointerPanZoomUpdate(\n          GetDeviceId(), pan_x, pan_y, scale, 0);\n    }\n  }\n  return S_OK;\n}\n\nHRESULT DirectManipulationEventHandler::OnInteraction(\n    IDirectManipulationViewport2* viewport,\n    DIRECTMANIPULATION_INTERACTION_TYPE interaction) {\n  return S_OK;\n}\n\nULONG STDMETHODCALLTYPE DirectManipulationEventHandler::AddRef() {\n  RefCountedThreadSafe::AddRef();\n  return 0;\n}\n\nULONG STDMETHODCALLTYPE DirectManipulationEventHandler::Release() {\n  RefCountedThreadSafe::Release();\n  return 0;\n}\n\nDirectManipulationOwner::DirectManipulationOwner(Window* window)\n    : window_(window) {}\n\nint DirectManipulationOwner::Init(unsigned int width, unsigned int height) {\n  RETURN_IF_FAILED(CoCreateInstance(CLSID_DirectManipulationManager, nullptr,\n                                    CLSCTX_INPROC_SERVER,\n                                    IID_IDirectManipulationManager, &manager_));\n  RETURN_IF_FAILED(manager_->GetUpdateManager(\n      IID_IDirectManipulationUpdateManager, &updateManager_));\n  RETURN_IF_FAILED(manager_->CreateViewport(nullptr, window_->GetWindowHandle(),\n                                            IID_IDirectManipulationViewport,\n                                            &viewport_));\n  DIRECTMANIPULATION_CONFIGURATION configuration =\n      DIRECTMANIPULATION_CONFIGURATION_INTERACTION |\n      DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X |\n      DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y |\n      DIRECTMANIPULATION_CONFIGURATION_SCALING |\n      DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA;\n  RETURN_IF_FAILED(viewport_->ActivateConfiguration(configuration));\n  RETURN_IF_FAILED(viewport_->SetViewportOptions(\n      DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE));\n  handler_ = fml::MakeRefCounted<DirectManipulationEventHandler>(this);\n  RETURN_IF_FAILED(viewport_->AddEventHandler(\n      window_->GetWindowHandle(), handler_.get(), &viewportHandlerCookie_));\n  RECT rect = {0, 0, (LONG)width, (LONG)height};\n  RETURN_IF_FAILED(viewport_->SetViewportRect(&rect));\n  RETURN_IF_FAILED(manager_->Activate(window_->GetWindowHandle()));\n  RETURN_IF_FAILED(viewport_->Enable());\n  RETURN_IF_FAILED(updateManager_->Update(nullptr));\n  return 0;\n}\n\nvoid DirectManipulationOwner::ResizeViewport(unsigned int width,\n                                             unsigned int height) {\n  if (viewport_) {\n    RECT rect = {0, 0, (LONG)width, (LONG)height};\n    WARN_IF_FAILED(viewport_->SetViewportRect(&rect));\n  }\n}\n\nvoid DirectManipulationOwner::Destroy() {\n  if (handler_) {\n    handler_->owner_ = nullptr;\n  }\n\n  if (viewport_) {\n    WARN_IF_FAILED(viewport_->Disable());\n    WARN_IF_FAILED(viewport_->Disable());\n    WARN_IF_FAILED(viewport_->RemoveEventHandler(viewportHandlerCookie_));\n    WARN_IF_FAILED(viewport_->Abandon());\n  }\n\n  if (window_ && manager_) {\n    WARN_IF_FAILED(manager_->Deactivate(window_->GetWindowHandle()));\n  }\n\n  handler_ = nullptr;\n  viewport_ = nullptr;\n  updateManager_ = nullptr;\n  manager_ = nullptr;\n  window_ = nullptr;\n}\n\nvoid DirectManipulationOwner::SetContact(UINT contactId) {\n  if (viewport_) {\n    viewport_->SetContact(contactId);\n  }\n}\n\nvoid DirectManipulationOwner::SetBindingHandlerDelegate(\n    WindowBindingHandlerDelegate* delegate) {\n  binding_handler_delegate = delegate;\n}\n\nvoid DirectManipulationOwner::Update() {\n  if (updateManager_) {\n    HRESULT hr = updateManager_->Update(nullptr);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"updateManager_->Update failed\";\n      auto error = GetLastError();\n      FML_LOG(ERROR) << error;\n      LPWSTR message = nullptr;\n      size_t size = FormatMessageW(\n          FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |\n              FORMAT_MESSAGE_IGNORE_INSERTS,\n          NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n          reinterpret_cast<LPWSTR>(&message), 0, NULL);\n      FML_LOG(ERROR) << message;\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/direct_manipulation.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_DIRECT_MANIPULATION_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_DIRECT_MANIPULATION_H_\n\n#include <wrl/client.h>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"directmanipulation.h\"\n\nnamespace clay {\n\nclass Window;\nclass WindowBindingHandlerDelegate;\n\nclass DirectManipulationEventHandler;\n\n// Owner for a DirectManipulation event handler, contains the link between\n// DirectManipulation and WindowBindingHandlerDelegate.\nclass DirectManipulationOwner {\n public:\n  explicit DirectManipulationOwner(Window* window);\n  virtual ~DirectManipulationOwner() = default;\n  // Initialize a DirectManipulation viewport with specified width and height.\n  // These should match the width and height of the application window.\n  int Init(unsigned int width, unsigned int height);\n  // Resize the DirectManipulation viewport. Should be called when the\n  // application window is resized.\n  void ResizeViewport(unsigned int width, unsigned int height);\n  // Set the WindowBindingHandlerDelegate which will receive callbacks based on\n  // DirectManipulation updates.\n  void SetBindingHandlerDelegate(\n      WindowBindingHandlerDelegate* binding_handler_delegate);\n  // Called when DM_POINTERHITTEST occurs with an acceptable pointer type. Will\n  // start DirectManipulation for that interaction.\n  virtual void SetContact(UINT contactId);\n  // Called to get updates from DirectManipulation. Should be called frequently\n  // to provide smooth updates.\n  void Update();\n  // Release child event handler and OS resources.\n  void Destroy();\n  // The target that should be updated when DirectManipulation provides a new\n  // pan/zoom transformation.\n  WindowBindingHandlerDelegate* binding_handler_delegate;\n\n private:\n  // The window gesture input is occuring on.\n  Window* window_;\n  // Cookie needed to register child event handler with viewport.\n  DWORD viewportHandlerCookie_;\n  // Object needed for operation of the DirectManipulation API.\n  Microsoft::WRL::ComPtr<IDirectManipulationManager> manager_;\n  // Object needed for operation of the DirectManipulation API.\n  Microsoft::WRL::ComPtr<IDirectManipulationUpdateManager> updateManager_;\n  // Object needed for operation of the DirectManipulation API.\n  Microsoft::WRL::ComPtr<IDirectManipulationViewport> viewport_;\n  // Child needed for operation of the DirectManipulation API.\n  fml::RefPtr<DirectManipulationEventHandler> handler_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(DirectManipulationOwner);\n};\n\n// Implements DirectManipulation event handling interfaces, receives calls from\n// system when gesture events occur.\nclass DirectManipulationEventHandler\n    : public fml::RefCountedThreadSafe<DirectManipulationEventHandler>,\n      public IDirectManipulationViewportEventHandler,\n      public IDirectManipulationInteractionEventHandler {\n  friend class DirectManipulationOwner;\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(DirectManipulationEventHandler);\n  FML_FRIEND_MAKE_REF_COUNTED(DirectManipulationEventHandler);\n\n public:\n  explicit DirectManipulationEventHandler(DirectManipulationOwner* owner)\n      : owner_(owner) {}\n\n  // |IUnknown|\n  STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override;\n\n  // |IUnknown|\n  ULONG STDMETHODCALLTYPE AddRef() override;\n\n  // |IUnknown|\n  ULONG STDMETHODCALLTYPE Release() override;\n\n  // |IDirectManipulationViewportEventHandler|\n  HRESULT STDMETHODCALLTYPE OnViewportStatusChanged(\n      IDirectManipulationViewport* viewport, DIRECTMANIPULATION_STATUS current,\n      DIRECTMANIPULATION_STATUS previous) override;\n\n  // |IDirectManipulationViewportEventHandler|\n  HRESULT STDMETHODCALLTYPE\n  OnViewportUpdated(IDirectManipulationViewport* viewport) override;\n\n  // |IDirectManipulationViewportEventHandler|\n  HRESULT STDMETHODCALLTYPE\n  OnContentUpdated(IDirectManipulationViewport* viewport,\n                   IDirectManipulationContent* content) override;\n\n  // |IDirectManipulationInteractionEventHandler|\n  HRESULT STDMETHODCALLTYPE\n  OnInteraction(IDirectManipulationViewport2* viewport,\n                DIRECTMANIPULATION_INTERACTION_TYPE interaction) override;\n\n private:\n  struct GestureData {\n    float scale;\n    float pan_x;\n    float pan_y;\n  };\n  // Convert transform array to Flutter-usable values.\n  GestureData ConvertToGestureData(float transform[6]);\n  // Unique identifier to associate with all gesture event updates.\n  int32_t GetDeviceId();\n  // Parent object, used to store the target for gesture event updates.\n  DirectManipulationOwner* owner_;\n  // We need to reset some parts of DirectManipulation after each gesture\n  // A flag is needed to ensure that false events created as the reset occurs\n  // are not sent to the flutter framework.\n  bool during_synthesized_reset_ = false;\n  // Store whether current events are from synthetic inertia rather than user\n  // input.\n  bool during_inertia_ = false;\n  // The transform might not be able to be reset before the next gesture, so\n  // the initial state needs to be stored for reference.\n  GestureData initial_gesture_data_ = {\n      1,  // scale\n      0,  // pan_x\n      0,  // pan_y\n  };\n  // Store the difference between the last pan offsets to determine if inertia\n  // has been cancelled in the middle of an animation.\n  float last_pan_x_ = 0.0;\n  float last_pan_y_ = 0.0;\n  float last_pan_delta_x_ = 0.0;\n  float last_pan_delta_y_ = 0.0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_DIRECT_MANIPULATION_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/dpi_utils.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/dpi_utils.h\"\n\nnamespace clay {\n\nnamespace {\n\nconstexpr UINT kDefaultDpi = 96;\n\n// This is the MDT_EFFECTIVE_DPI value from MONITOR_DPI_TYPE, an enum declared\n// in ShellScalingApi.h. Replicating here to avoid importing the library\n// directly.\nconstexpr UINT kEffectiveDpiMonitorType = 0;\n\ntemplate <typename T>\n\n/// Retrieves a function |name| from a given |comBaseModule| into |outProc|.\n/// Returns a bool indicating whether the function was found.\nbool AssignProcAddress(HMODULE comBaseModule, const char* name, T*& outProc) {\n  outProc = reinterpret_cast<T*>(GetProcAddress(comBaseModule, name));\n  return *outProc != nullptr;\n}\n\n/// A helper class for abstracting various Windows DPI related functions across\n/// Windows OS versions.\nclass DpiHelper {\n public:\n  DpiHelper();\n\n  ~DpiHelper();\n\n  /// Returns the DPI for |hwnd|. Supports all DPI awareness modes, and is\n  /// backward compatible down to Windows Vista. If |hwnd| is nullptr, returns\n  /// the DPI for the primary monitor. If Per-Monitor DPI awareness is not\n  /// available, returns the system's DPI.\n  UINT GetDpiForWindow(HWND);\n\n  /// Returns the DPI of a given monitor. Defaults to 96 if the API is not\n  /// available.\n  UINT GetDpiForMonitor(HMONITOR);\n\n private:\n  using GetDpiForWindow_ = UINT __stdcall(HWND);\n  using GetDpiForMonitor_ = HRESULT __stdcall(HMONITOR hmonitor, UINT dpiType,\n                                              UINT* dpiX, UINT* dpiY);\n  using EnableNonClientDpiScaling_ = BOOL __stdcall(HWND hwnd);\n\n  GetDpiForWindow_* get_dpi_for_window_ = nullptr;\n  GetDpiForMonitor_* get_dpi_for_monitor_ = nullptr;\n  EnableNonClientDpiScaling_* enable_non_client_dpi_scaling_ = nullptr;\n\n  HMODULE user32_module_ = nullptr;\n  HMODULE shlib_module_ = nullptr;\n  bool dpi_for_window_supported_ = false;\n  bool dpi_for_monitor_supported_ = false;\n};\n\nDpiHelper::DpiHelper() {\n  if ((user32_module_ = LoadLibraryA(\"User32.dll\")) != nullptr) {\n    dpi_for_window_supported_ = (AssignProcAddress(\n        user32_module_, \"GetDpiForWindow\", get_dpi_for_window_));\n  }\n  if ((shlib_module_ = LoadLibraryA(\"Shcore.dll\")) != nullptr) {\n    dpi_for_monitor_supported_ = AssignProcAddress(\n        shlib_module_, \"GetDpiForMonitor\", get_dpi_for_monitor_);\n  }\n}\n\nDpiHelper::~DpiHelper() {\n  if (user32_module_ != nullptr) {\n    FreeLibrary(user32_module_);\n  }\n  if (shlib_module_ != nullptr) {\n    FreeLibrary(shlib_module_);\n  }\n}\n\nUINT DpiHelper::GetDpiForWindow(HWND hwnd) {\n  // GetDpiForWindow returns the DPI for any awareness mode. If not available,\n  // or no |hwnd| is provided, fallback to a per monitor, system, or default\n  // DPI.\n  if (dpi_for_window_supported_ && hwnd != nullptr) {\n    return get_dpi_for_window_(hwnd);\n  }\n\n  if (dpi_for_monitor_supported_) {\n    HMONITOR monitor = nullptr;\n    if (hwnd != nullptr) {\n      monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);\n    }\n    return GetDpiForMonitor(monitor);\n  }\n  HDC hdc = GetDC(hwnd);\n  UINT dpi = GetDeviceCaps(hdc, LOGPIXELSX);\n  ReleaseDC(hwnd, hdc);\n  return dpi;\n}\n\nUINT DpiHelper::GetDpiForMonitor(HMONITOR monitor) {\n  if (dpi_for_monitor_supported_) {\n    if (monitor == nullptr) {\n      const POINT target_point = {0, 0};\n      monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTOPRIMARY);\n    }\n    UINT dpi_x = 0, dpi_y = 0;\n    HRESULT result =\n        get_dpi_for_monitor_(monitor, kEffectiveDpiMonitorType, &dpi_x, &dpi_y);\n    if (SUCCEEDED(result)) {\n      return dpi_x;\n    }\n  }\n  return kDefaultDpi;\n}  // namespace\n\nDpiHelper* GetHelper() {\n  static DpiHelper* dpi_helper = new DpiHelper();\n  return dpi_helper;\n}\n}  // namespace\n\nUINT GetDpiForHWND(HWND hwnd) { return GetHelper()->GetDpiForWindow(hwnd); }\n\nUINT GetDpiForMonitor(HMONITOR monitor) {\n  return GetHelper()->GetDpiForMonitor(monitor);\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/dpi_utils.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"Windows.h\"\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_DPI_UTILS_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_DPI_UTILS_H_\n\nnamespace clay {\n\n/// Returns the DPI for |hwnd|. Supports all DPI awareness modes, and is\n/// backward compatible down to Windows Vista. If |hwnd| is nullptr, returns the\n/// DPI for the primary monitor. If Per-Monitor DPI awareness is not available,\n/// returns the system's DPI.\nUINT GetDpiForHWND(HWND hwnd);\n\n/// Returns the DPI of a given monitor. Defaults to 96 if the API is not\n/// available.\nUINT GetDpiForMonitor(HMONITOR monitor);\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_DPI_UTILS_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/child_window_win.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/egl/child_window_win.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/isolate.h\"\n\nnamespace clay {\nnamespace egl {\n\nnamespace {\n\nATOM g_window_class;\n\nvoid InitializeWindowClass() {\n  if (g_window_class) {\n    return;\n  }\n  WNDCLASSEX window_class = {0};\n  window_class.cbSize = sizeof(WNDCLASSEX);\n  window_class.style = CS_OWNDC;\n  window_class.lpfnWndProc = DefWindowProc;\n  window_class.cbClsExtra = 0;\n  window_class.cbWndExtra = 0;\n  window_class.hInstance = GetModuleHandle(nullptr);\n  window_class.hIcon = nullptr;\n  window_class.hCursor = nullptr;\n  window_class.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);\n  window_class.lpszMenuName = nullptr;\n  window_class.lpszClassName = L\"Intermediate D3D Window\";\n  window_class.hIconSm = nullptr;\n  g_window_class = RegisterClassEx(&window_class);\n  if (!g_window_class) {\n    FML_LOG(ERROR) << \"RegisterClassEx failed: \" << GetLastError();\n  }\n}\n\n}  // namespace\n\nChildWindowWin::ChildWindowWin(HWND parent_window)\n    : parent_window_(parent_window) {\n  Initialize();\n}\n\nvoid ChildWindowWin::Initialize() {\n  if (window_) {\n    return;\n  }\n  RECT window_rect;\n  GetClientRect(parent_window_, &window_rect);\n  SIZE size = {window_rect.right - window_rect.left,\n               window_rect.bottom - window_rect.top};\n  InitializeWindowClass();\n\n  HWND window =\n      CreateWindowEx(WS_EX_NOPARENTNOTIFY | WS_EX_LAYERED | WS_EX_LAYERED |\n                         WS_EX_TRANSPARENT | WS_EX_NOREDIRECTIONBITMAP,\n                     L\"Intermediate D3D Window\", L\"\",\n                     WS_CHILDWINDOW | WS_DISABLED | WS_VISIBLE, 0, 0, size.cx,\n                     size.cy, parent_window_, nullptr, nullptr, nullptr);\n\n  if (!window) {\n    std::cerr << \"CreateWindowEx failed: \" << GetLastError() << std::endl;\n    return;\n  }\n  window_ = window;\n}\nChildWindowWin::~ChildWindowWin() { DestroyWindow(window_); }\n\nvoid ChildWindowWin::Resize(int width, int height) {\n  // Force a resize and redraw (but not a move, activate, etc.).\n  if (!task_runner_) {\n    task_runner_ = clay::Isolate::Instance().GetPlatformTaskRunner();\n  }\n  task_runner_->PostTask([this, width, height]() {\n    if (window_) {\n      SetWindowPos(window_, nullptr, 0, 0, width, height,\n                   SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS |\n                       SWP_NOOWNERZORDER | SWP_NOZORDER);\n    }\n  });\n}\n\n}  // namespace egl\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/child_window_win.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_EGL_CHILD_WINDOW_WIN_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_EGL_CHILD_WINDOW_WIN_H_\n\n#include <windows.h>\n\n#include <memory>\n\n#include \"base/include/fml/thread.h\"\n\nnamespace clay {\nnamespace egl {\n\nclass ChildWindowWin {\n public:\n  explicit ChildWindowWin(HWND parent_window);\n\n  ChildWindowWin(const ChildWindowWin&) = delete;\n  ChildWindowWin& operator=(const ChildWindowWin&) = delete;\n\n  ~ChildWindowWin();\n\n  void Initialize();\n  HWND window() const { return window_; }\n\n  void Resize(int width, int height);\n\n private:\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n\n  HWND parent_window_ = nullptr;\n  HWND window_ = nullptr;\n};\n\n}  // namespace egl\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_EGL_CHILD_WINDOW_WIN_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/context.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/egl/context.h\"\n\n#include \"clay/shell/platform/windows/egl/egl.h\"\n\nnamespace clay {\nnamespace egl {\n\nContext::Context(EGLDisplay display, EGLContext context)\n    : display_(display), context_(context) {}\n\nContext::~Context() {\n  if (display_ == EGL_NO_DISPLAY && context_ == EGL_NO_CONTEXT) {\n    return;\n  }\n\n  if (::eglDestroyContext(display_, context_) != EGL_TRUE) {\n    WINDOWS_LOG_EGL_ERROR;\n  }\n}\n\nbool Context::IsCurrent() const { return ::eglGetCurrentContext() == context_; }\n\nbool Context::MakeCurrent() const {\n  const auto result =\n      ::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, context_);\n  if (result != EGL_TRUE) {\n    WINDOWS_LOG_EGL_ERROR;\n    return false;\n  }\n\n  return true;\n}\n\nbool Context::ClearCurrent() const {\n  const auto result = ::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE,\n                                       EGL_NO_CONTEXT);\n  if (result != EGL_TRUE) {\n    WINDOWS_LOG_EGL_ERROR;\n    return false;\n  }\n\n  return true;\n}\n\nconst EGLContext& Context::GetHandle() const { return context_; }\n\n}  // namespace egl\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/context.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_EGL_CONTEXT_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_EGL_CONTEXT_H_\n\n#include <EGL/egl.h>\n\n#include \"base/include/fml/macros.h\"\n\nnamespace clay {\nnamespace egl {\n\n// An EGL context to interact with OpenGL.\n//\n// This enables automatic error logging and mocking.\n//\n// Flutter Windows uses this to create render and resource contexts.\nclass Context {\n public:\n  Context(EGLDisplay display, EGLContext context);\n  ~Context();\n\n  // Check if this context is currently bound to the thread.\n  virtual bool IsCurrent() const;\n\n  // Bind the context to the thread without any read or draw surfaces.\n  //\n  // Returns true on success.\n  virtual bool MakeCurrent() const;\n\n  // Unbind any context and surfaces from the thread.\n  //\n  // Returns true on success.\n  virtual bool ClearCurrent() const;\n\n  // Get the raw EGL context.\n  virtual const EGLContext& GetHandle() const;\n\n private:\n  EGLDisplay display_ = EGL_NO_DISPLAY;\n  EGLContext context_ = EGL_NO_CONTEXT;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Context);\n};\n\n}  // namespace egl\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_EGL_CONTEXT_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/direct_composition_surface.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/egl/direct_composition_surface.h\"\n\n#include \"clay/gfx/geometry/rect.h\"\n#include \"clay/shell/platform/windows/egl/egl.h\"\n\nnamespace {\n\nRECT ToRECT(const clay::Rect& rect) {\n  RECT r;\n  r.left = rect.x();\n  r.right = rect.MaxX();\n  r.top = rect.y();\n  r.bottom = rect.MaxY();\n  return r;\n}\n\n}  // namespace\n\nnamespace clay {\nnamespace egl {\n\n// If damage_rect / full_chrome_rect >= kForceFullDamageThreshold, present\n// the swap chain with full damage.\nstatic constexpr float kForceFullDamageThreshold = 0.6f;\n\nDirectCompositionSurface::DirectCompositionSurface(\n    EGLDisplay display, EGLContext context, EGLConfig config,\n    EGLNativeWindowType window,\n    Microsoft::WRL::ComPtr<IDCompositionDevice2> device, int width, int height)\n    : WindowSurface(display, context, config, window, width, height),\n      dcomp_device_(device) {\n  d3d11_device_ = QueryD3D11DeviceObjectFromANGLE(display);\n}\n\nDirectCompositionSurface::~DirectCompositionSurface() { Destroy(); }\n\nbool DirectCompositionSurface::Initialize() {\n  if (display_ == EGL_NO_DISPLAY) {\n    FML_LOG(ERROR) << \"Trying to create surface with invalid display.\";\n    return false;\n  }\n\n  child_window_ = std::make_unique<ChildWindowWin>(window_);\n\n  if (!child_window_->window()) {\n    FML_LOG(ERROR) << \"Trying to Initial child window failed.\";\n    return false;\n  }\n\n  FML_DCHECK(dcomp_device_);\n\n  HRESULT hr;\n  Microsoft::WRL::ComPtr<IDCompositionDesktopDevice> desktop_device;\n  dcomp_device_.As(&desktop_device);\n  FML_DCHECK(desktop_device);\n\n  hr = desktop_device->CreateTargetForHwnd(\n      static_cast<HWND>(child_window_->window()), TRUE, &dcomp_target_);\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"CreateTargetForHwnd failed with error 0x\" << std::hex\n                   << hr;\n    return false;\n  }\n  dcomp_device_->CreateVisual(&dcomp_root_visual_);\n  FML_DCHECK(dcomp_root_visual_);\n  dcomp_target_->SetRoot(dcomp_root_visual_.Get());\n  // A visual inherits the interpolation mode of the parent visual by default.\n  // If no visuals set the interpolation mode, the default for the entire visual\n  // tree is nearest neighbor interpolation.\n  // Set the interpolation mode to Linear to get a better upscaling quality.\n  dcomp_root_visual_->SetBitmapInterpolationMode(\n      DCOMPOSITION_BITMAP_INTERPOLATION_MODE_LINEAR);\n  EGLint pbuffer_attribs[] = {\n      EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE,\n  };\n\n  default_surface_ =\n      eglCreatePbufferSurface(display_, config_, pbuffer_attribs);\n  if (!default_surface_) {\n    FML_LOG(ERROR) << \"eglCreatePbufferSurface failed with error \";\n    return false;\n  }\n\n  is_valid_ = true;\n  return true;\n}\n\nbool DirectCompositionSurface::Destroy() {\n  // Ensure the surface is not current before destroying it.\n  if (::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE,\n                       EGL_NO_CONTEXT) != EGL_TRUE) {\n    WINDOWS_LOG_EGL_ERROR;\n    return false;\n  }\n  if (default_surface_) {\n    if (!eglDestroySurface(display_, default_surface_)) {\n      FML_LOG(ERROR) << \"eglDestroySurface failed with error\";\n    }\n    default_surface_ = nullptr;\n  }\n  if (real_surface_) {\n    if (!eglDestroySurface(display_, real_surface_)) {\n      FML_LOG(ERROR) << \"eglDestroySurface failed with error\";\n    }\n    real_surface_ = nullptr;\n  }\n  if (dcomp_target_) {\n    dcomp_target_->SetRoot(nullptr);\n    dcomp_target_.Reset();\n  }\n  if (dcomp_root_visual_) {\n    dcomp_root_visual_->RemoveAllVisuals();\n    dcomp_root_visual_.Reset();\n  }\n  draw_texture_.Reset();\n\n  is_valid_ = false;\n  return true;\n}\n\nbool DirectCompositionSurface::MakeCurrent() {\n  if (::eglMakeCurrent(display_, GetHandle(), GetHandle(), context_) !=\n      EGL_TRUE) {\n    WINDOWS_LOG_EGL_ERROR;\n    return false;\n  }\n  return true;\n}\n\nbool DirectCompositionSurface::SwapBuffers() {\n  bool swap_result = ReleaseDrawTexture(false);\n  AddDamageRegion(swap_rect_);\n  swap_rect_.Clear();\n  return swap_result;\n}\n\nbool DirectCompositionSurface::Resize(int width, int height) {\n  if (size_.width() != width || size_.height() != height) {\n    Surface::Resize(width, height);\n    size_ = {width, height};\n  }\n  child_window_->Resize(width, height);\n  if (!Surface::Destroy()) {\n    FML_LOG(ERROR) << \"Surface resize failed to destroy surface\";\n    return false;\n  }\n  is_valid_ = true;\n  draw_texture_.Reset();\n  // This will release indirect references to swap chain (|real_surface_|) by\n  // binding |default_surface_| as the default framebuffer.\n  if (!ReleaseDrawTexture(true /* will_discard */)) return false;\n\n  // ResizeBuffers can't change alpha blending mode.\n  if (swap_chain_) {\n    UINT count = buffer_count();\n    DXGI_FORMAT format = DXGI_FORMAT_B8G8R8A8_UNORM;\n    UINT flags =\n        IsSwapChainTearingSupported() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;\n    Microsoft::WRL::ComPtr<ID3D11DeviceContext> context;\n    d3d11_device_->GetImmediateContext(&context);\n    HRESULT hr =\n        swap_chain_->ResizeBuffers(count, width, height, format, flags);\n    if (!SUCCEEDED(hr)) {\n      FML_DLOG(ERROR) << \"ResizeBuffers failed with error 0x\" << std::hex << hr;\n      return false;\n    }\n    if (!MakeCurrent() || !SetVSyncEnabled(vsync_enabled())) {\n      // Surfaces block until the v-blank by default.\n      // Failing to update the vsync might result in unnecessary blocking.\n      // This regresses performance but not correctness.\n      FML_LOG(ERROR) << \"Surface resize failed to set vsync\";\n    }\n    return true;\n  }\n  // Next SetDrawRectangle call will recreate the swap chain or surface.\n  swap_chain_.Reset();\n  return false;\n}\n\nconst EGLSurface& DirectCompositionSurface::GetHandle() const {\n  return real_surface_ ? real_surface_ : default_surface_;\n}\n\nvoid DirectCompositionSurface::SetDamageRegion(const clay::Rect& region) {\n  SetDrawRectangle(region);\n  if (::eglMakeCurrent(display_, GetHandle(), GetHandle(), context_) !=\n      EGL_TRUE) {\n    WINDOWS_LOG_EGL_ERROR;\n  }\n}\n\nbool DirectCompositionSurface::ReleaseDrawTexture(bool will_discard) {\n  EGLSurface egl_surface = real_surface_;\n  real_surface_ = nullptr;\n  if (egl_surface) {\n    eglDestroySurface(display_, egl_surface);\n    egl_surface = nullptr;\n  }\n\n  // If MakeCurrent fails (probably lost device), we'll want to return failure,\n  // but we still want to reset the rest of the state for consistency.\n  if (::eglMakeCurrent(display_, GetHandle(), GetHandle(), context_) !=\n      EGL_TRUE) {\n    LogEGLError(\"Failed to make current in ReleaseDrawTexture\");\n    return false;\n  }\n\n  HRESULT hr, device_removed_reason;\n  if (draw_texture_) {\n    draw_texture_.Reset();\n\n    if (!will_discard) {\n      const bool use_swap_chain_tearing = IsSwapChainTearingSupported();\n      UINT interval =\n          first_swap_ || !vsync_enabled() || use_swap_chain_tearing ? 0 : 1;\n      UINT flags = use_swap_chain_tearing ? DXGI_PRESENT_ALLOW_TEARING : 0;\n\n      DXGI_PRESENT_PARAMETERS params = {};\n      RECT dirty_rect = ToRECT(swap_rect_);\n      params.DirtyRectsCount = 1;\n      params.pDirtyRects = &dirty_rect;\n\n      hr = swap_chain_->Present1(interval, flags, &params);\n      if (FAILED(hr) && hr != DXGI_STATUS_OCCLUDED) {\n        FML_LOG(ERROR) << \"swap_chain_->Present failed. \"\n                       << \"hr=0x\" << std::hex << hr << \", interval=\" << interval\n                       << \", flags=0x\" << std::hex << flags;\n        return false;\n      }\n      // Wait for the GPU to finish executing its commands before\n      // committing the DirectComposition tree, or else the swapchain\n      // may flicker black when it's first presented.\n      if (first_swap_) {\n        first_swap_ = false;\n        Microsoft::WRL::ComPtr<IDXGIDevice2> dxgi_device2;\n        hr = d3d11_device_.As(&dxgi_device2);\n        if (SUCCEEDED(hr) && dxgi_device2) {\n          HANDLE event_handle = CreateEvent(nullptr, FALSE, FALSE, nullptr);\n          if (event_handle) {\n            hr = dxgi_device2->EnqueueSetEvent(event_handle);\n            if (SUCCEEDED(hr)) {\n              WaitForSingleObject(event_handle, INFINITE);\n            } else {\n              HRESULT device_removed_reason =\n                  d3d11_device_->GetDeviceRemovedReason();\n              FML_LOG(ERROR) << \"EnqueueSetEvent failed. hr=0x\" << std::hex\n                             << hr << \", device_removed_reason=0x\" << std::hex\n                             << device_removed_reason;\n            }\n            CloseHandle(event_handle);\n          }\n        }\n      }\n      hr = dcomp_device_->Commit();\n      if (FAILED(hr)) {\n        FML_LOG(ERROR) << \"dcomp_device_->Commit() failed. hr=0x\" << std::hex\n                       << hr;\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\nbool DirectCompositionSurface::SetDrawRectangle(const clay::Rect& rectangle) {\n  auto rect = clay::Rect(size_);\n  if (!rect.Contains(rectangle)) {\n    FML_DLOG(ERROR)\n        << \"Draw rectangle must be contained within size of surface\";\n    return false;\n  }\n\n  if (draw_texture_) {\n    FML_DLOG(ERROR)\n        << \"SetDrawRectangle must be called only once per swap buffers\";\n    return false;\n  }\n\n  if (rect != rectangle && !swap_chain_) {\n    FML_DLOG(ERROR) << \"First draw to surface must draw to everything\";\n    return false;\n  }\n\n  DXGI_FORMAT dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM;\n\n  if (!swap_chain_ &&\n      ((!false || dxgi_format == DXGI_FORMAT_R10G10B10A2_UNORM))) {\n    Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;\n    d3d11_device_.As(&dxgi_device);\n    Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;\n    dxgi_device->GetAdapter(&dxgi_adapter);\n    Microsoft::WRL::ComPtr<IDXGIFactory2> dxgi_factory;\n    dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory));\n\n    DXGI_SWAP_CHAIN_DESC1 desc = {};\n    desc.Width = width();\n    desc.Height = height();\n    desc.Format = dxgi_format;\n    desc.SampleDesc.Count = 1;\n    desc.BufferCount = buffer_count();\n    desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;\n    desc.Scaling = DXGI_SCALING_STRETCH;\n    desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;\n    desc.AlphaMode =\n        has_alpha_ ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE;\n    desc.Flags =\n        IsSwapChainTearingSupported() ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;\n\n    HRESULT hr = dxgi_factory->CreateSwapChainForComposition(\n        d3d11_device_.Get(), &desc, nullptr, &swap_chain_);\n    first_swap_ = true;\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"CreateSwapChainForComposition failed. \"\n                     << \"hr=0x\" << std::hex << hr\n                     << \", d3d11_device_=\" << d3d11_device_.Get()\n                     << \", swap_chain_=\" << swap_chain_.Get();\n      return false;\n    }\n    dcomp_root_visual_->SetContent(swap_chain_.Get());\n  }\n  swap_rect_ = rectangle;\n\n  swap_chain_->GetBuffer(0, IID_PPV_ARGS(&draw_texture_));\n\n  FML_DCHECK(draw_texture_);\n\n  std::vector<EGLint> pbuffer_attribs = {\n      EGL_WIDTH, static_cast<EGLint>(width()), EGL_HEIGHT,\n      static_cast<EGLint>(height()), EGL_NONE};\n\n  EGLClientBuffer buffer =\n      reinterpret_cast<EGLClientBuffer>(draw_texture_.Get());\n  real_surface_ = eglCreatePbufferFromClientBuffer(\n      display_, EGL_D3D_TEXTURE_ANGLE, buffer, config_, pbuffer_attribs.data());\n\n  if (!real_surface_) {\n    WINDOWS_LOG_EGL_ERROR;\n    return false;\n  }\n\n  return true;\n}\n\nbool DirectCompositionSurface::IsSwapChainTearingSupported() {\n  static const bool supported = [=] {\n    Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device = d3d11_device_;\n    if (!d3d11_device) {\n      return false;\n    }\n\n    Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;\n    HRESULT hr = d3d11_device.As(&dxgi_device);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to query IDXGIDevice from d3d11_device. hr=0x\"\n                     << std::hex << hr;\n      return false;\n    }\n\n    Microsoft::WRL::ComPtr<IDXGIAdapter> dxgi_adapter;\n    hr = dxgi_device->GetAdapter(&dxgi_adapter);\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to get IDXGIAdapter from IDXGIDevice. hr=0x\"\n                     << std::hex << hr;\n      return false;\n    }\n\n    Microsoft::WRL::ComPtr<IDXGIFactory5> dxgi_factory;\n    hr = dxgi_adapter->GetParent(IID_PPV_ARGS(&dxgi_factory));\n    if (FAILED(hr)) {\n      FML_LOG(ERROR) << \"Failed to get IDXGIFactory5 from IDXGIAdapter. hr=0x\"\n                     << std::hex << hr;\n      return false;\n    }\n\n    BOOL present_allow_tearing = FALSE;\n    hr = dxgi_factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING,\n                                           &present_allow_tearing,\n                                           sizeof(present_allow_tearing));\n    if (FAILED(hr)) {\n      FML_LOG(ERROR)\n          << \"Failed to check DXGI_FEATURE_PRESENT_ALLOW_TEARING. hr=0x\"\n          << std::hex << hr;\n      return false;\n    }\n\n    return present_allow_tearing == TRUE;\n  }();\n\n  return supported;\n}\n\nMicrosoft::WRL::ComPtr<ID3D11Device>\nDirectCompositionSurface::QueryD3D11DeviceObjectFromANGLE(\n    EGLDisplay egl_display) {\n  if (egl_display == EGL_NO_DISPLAY) {\n    LogEGLError(\"Failed to retrieve EGLDisplay\");\n    return nullptr;\n  }\n\n  intptr_t egl_device = 0;\n  if (!eglQueryDisplayAttribEXT(egl_display, EGL_DEVICE_EXT, &egl_device)) {\n    LogEGLError(\"eglQueryDisplayAttribEXT failed\");\n    return nullptr;\n  }\n\n  if (!egl_device) {\n    LogEGLError(\"Failed to retrieve EGLDeviceEXT\");\n    return nullptr;\n  }\n  intptr_t device = 0;\n  if (!eglQueryDeviceAttribEXT(reinterpret_cast<EGLDeviceEXT>(egl_device),\n                               EGL_D3D11_DEVICE_ANGLE, &device)) {\n    LogEGLError(\"eglQueryDeviceAttribEXT failed\");\n    return nullptr;\n  }\n  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device;\n  d3d11_device = reinterpret_cast<ID3D11Device*>(device);\n  return d3d11_device;\n}\n\n}  // namespace egl\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/direct_composition_surface.h",
    "content": "\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_EGL_DIRECT_COMPOSITION_SURFACE_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_EGL_DIRECT_COMPOSITION_SURFACE_H_\n\n// OpenGL ES and EGL includes\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <EGL/eglplatform.h>\n#include <GLES2/gl2.h>\n#include <GLES2/gl2ext.h>\n#include <d3d11.h>\n#include <dcomp.h>\n#include <windows.h>\n#include <wrl/client.h>\n\n#include <memory>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/rect.h\"\n#include \"clay/gfx/geometry/size.h\"\n#include \"clay/shell/platform/windows/egl/child_window_win.h\"\n#include \"clay/shell/platform/windows/egl/window_surface.h\"\n\nnamespace clay {\nnamespace egl {\n\nclass DirectCompositionSurface : public WindowSurface {\n public:\n  DirectCompositionSurface(EGLDisplay display, EGLContext context,\n                           EGLConfig config, EGLNativeWindowType window,\n                           Microsoft::WRL::ComPtr<IDCompositionDevice2> device,\n                           int width, int height);\n\n  ~DirectCompositionSurface() override;\n\n  bool Initialize() override;\n\n  bool Destroy() override;\n\n  bool MakeCurrent() override;\n\n  bool SwapBuffers() override;\n\n  bool Resize(int width, int height) override;\n\n  const EGLSurface& GetHandle() const override;\n\n  void SetDamageRegion(const clay::Rect& region) override;\n\n  uint32_t buffer_count() const override { return 2; }\n\n private:\n  bool SetDrawRectangle(const clay::Rect& rectangle);\n  bool ReleaseDrawTexture(bool will_discard);\n  Microsoft::WRL::ComPtr<ID3D11Device> QueryD3D11DeviceObjectFromANGLE(\n      EGLDisplay egl_display);\n  bool IsSwapChainTearingSupported();\n\n  // This is a placeholder surface used when not rendering to the\n  // DirectComposition surface.\n  EGLSurface default_surface_ = 0;\n\n  // This is the real surface representing the backbuffer. It may be null\n  // outside of a BeginDraw/EndDraw pair.\n  EGLSurface real_surface_ = 0;\n\n  clay::Rect swap_rect_;\n  bool has_alpha_ = true;\n  bool first_swap_ = true;\n\n  Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;\n  Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device_;\n  Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain_;\n  Microsoft::WRL::ComPtr<ID3D11Texture2D> draw_texture_;\n  Microsoft::WRL::ComPtr<IDCompositionVisual2> dcomp_root_visual_;\n  Microsoft::WRL::ComPtr<IDCompositionTarget> dcomp_target_;\n\n  std::unique_ptr<ChildWindowWin> child_window_;\n};\n\n}  // namespace egl\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_EGL_DIRECT_COMPOSITION_SURFACE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/egl.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/egl/egl.h\"\n\n#include <EGL/egl.h>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nnamespace egl {\n\nnamespace {\n\nconst char* EGLErrorToString(EGLint error) {\n  switch (error) {\n    case EGL_SUCCESS:\n      return \"Success\";\n    case EGL_NOT_INITIALIZED:\n      return \"Not Initialized\";\n    case EGL_BAD_ACCESS:\n      return \"Bad Access\";\n    case EGL_BAD_ALLOC:\n      return \"Bad Alloc\";\n    case EGL_BAD_ATTRIBUTE:\n      return \"Bad Attribute\";\n    case EGL_BAD_CONTEXT:\n      return \"Bad Context\";\n    case EGL_BAD_CONFIG:\n      return \"Bad Config\";\n    case EGL_BAD_CURRENT_SURFACE:\n      return \"Bad Current Surface\";\n    case EGL_BAD_DISPLAY:\n      return \"Bad Display\";\n    case EGL_BAD_SURFACE:\n      return \"Bad Surface\";\n    case EGL_BAD_MATCH:\n      return \"Bad Match\";\n    case EGL_BAD_PARAMETER:\n      return \"Bad Parameter\";\n    case EGL_BAD_NATIVE_PIXMAP:\n      return \"Bad Native Pixmap\";\n    case EGL_BAD_NATIVE_WINDOW:\n      return \"Bad Native Window\";\n    case EGL_CONTEXT_LOST:\n      return \"Context Lost\";\n  }\n  FML_UNREACHABLE();\n  return \"Unknown\";\n}\n\n}  // namespace\n\nvoid LogEGLError(std::string_view message) {\n  const EGLint error = ::eglGetError();\n  FML_LOG(ERROR) << \"EGL Error: \" << EGLErrorToString(error) << \" (\" << error\n                 << \") \" << message;\n}\n\nvoid LogEGLError(std::string_view file, int line) {\n  std::stringstream stream;\n  stream << \"in \" << file << \":\" << line;\n  LogEGLError(stream.str());\n}\n\n}  // namespace egl\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/egl.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_EGL_EGL_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_EGL_EGL_H_\n\n#include <string_view>\n\nnamespace clay {\nnamespace egl {\n\n/// Log the last EGL error with an error message.\nvoid LogEGLError(std::string_view message);\n\n/// Log the last EGL error.\nvoid LogEGLError(std::string_view file, int line);\n\n#define WINDOWS_LOG_EGL_ERROR LogEGLError(__FILE__, __LINE__);\n\n}  // namespace egl\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_EGL_EGL_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/manager.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/egl/manager.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/platform/windows/egl/direct_composition_surface.h\"\n#include \"clay/shell/platform/windows/egl/egl.h\"\n\nnamespace clay {\nnamespace egl {\n\nint Manager::instance_count_ = 0;\n\nstd::unique_ptr<Manager> Manager::Create() {\n  std::unique_ptr<Manager> manager;\n  manager.reset(new Manager());\n  if (!manager->IsValid()) {\n    return nullptr;\n  }\n  return std::move(manager);\n}\n\nManager::Manager() {\n  ++instance_count_;\n\n#ifndef CLAY_FORCE_D3D9\n  if (!TryInitializeD3D11Device()) {\n    return;\n  }\n#endif\n\n  if (!InitializeDisplay()) {\n    return;\n  }\n\n  if (!InitializeConfig()) {\n    return;\n  }\n\n  if (!InitializeContexts()) {\n    return;\n  }\n\n  is_valid_ = true;\n}\n\nManager::~Manager() {\n  CleanUp();\n  --instance_count_;\n}\n\nbool Manager::InitializeDisplay() {\n  // These are preferred display attributes and request ANGLE's D3D11\n  // renderer. eglInitialize will only succeed with these attributes if the\n  // hardware supports D3D11 Feature Level 10_0+.\n  const EGLint d3d11_display_attributes[] = {\n      EGL_PLATFORM_ANGLE_TYPE_ANGLE,\n      EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,\n\n      // EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE is an option that will\n      // enable ANGLE to automatically call the IDXGIDevice3::Trim method on\n      // behalf of the application when it gets suspended.\n      EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,\n      EGL_TRUE,\n\n      // This extension allows angle to render directly on a D3D swapchain\n      // in the correct orientation on D3D11.\n      EGL_EXPERIMENTAL_PRESENT_PATH_ANGLE,\n      EGL_EXPERIMENTAL_PRESENT_PATH_FAST_ANGLE,\n\n      EGL_NONE,\n  };\n\n  // These are used to request ANGLE's D3D11 renderer, with D3D11 Feature\n  // Level 9_3.\n  const EGLint d3d11_fl_9_3_display_attributes[] = {\n      EGL_PLATFORM_ANGLE_TYPE_ANGLE,\n      EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,\n      EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE,\n      9,\n      EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE,\n      3,\n      EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,\n      EGL_TRUE,\n      EGL_NONE,\n  };\n\n  // These attributes request D3D11 WARP (software rendering fallback) in case\n  // hardware-backed D3D11 is unavailable.\n  const EGLint d3d11_warp_display_attributes[] = {\n      EGL_PLATFORM_ANGLE_TYPE_ANGLE,\n      EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,\n      EGL_PLATFORM_ANGLE_ENABLE_AUTOMATIC_TRIM_ANGLE,\n      EGL_TRUE,\n      EGL_NONE,\n  };\n\n  // These are used to request ANGLE's D3D9 renderer as a fallback if D3D11\n  // is not available.\n  const EGLint d3d9_display_attributes[] = {\n      EGL_PLATFORM_ANGLE_TYPE_ANGLE,\n      EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,\n      EGL_NONE,\n  };\n\n  std::vector<const EGLint*> display_attributes_configs = {\n#ifndef CLAY_FORCE_D3D9\n      d3d11_display_attributes,\n      d3d11_fl_9_3_display_attributes,\n      d3d11_warp_display_attributes,\n#endif\n      d3d9_display_attributes,\n  };\n\n  PFNEGLGETPLATFORMDISPLAYEXTPROC egl_get_platform_display_EXT =\n      reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(\n          ::eglGetProcAddress(\"eglGetPlatformDisplayEXT\"));\n  if (!egl_get_platform_display_EXT) {\n    LogEGLError(\"eglGetPlatformDisplayEXT not available\");\n    return false;\n  }\n\n  // Attempt to initialize ANGLE's renderer in order of: D3D11, D3D11 Feature\n  // Level 9_3 and finally D3D11 WARP.\n  for (auto config : display_attributes_configs) {\n    bool is_last = (config == display_attributes_configs.back());\n    if (egl_device_ != nullptr) {\n      display_ = egl_get_platform_display_EXT(EGL_PLATFORM_DEVICE_EXT,\n                                              egl_device_, config);\n    } else {\n      display_ = egl_get_platform_display_EXT(EGL_PLATFORM_ANGLE_ANGLE,\n                                              EGL_DEFAULT_DISPLAY, config);\n    }\n\n    if (display_ == EGL_NO_DISPLAY) {\n      if (is_last) {\n        LogEGLError(\"Failed to get a compatible EGLdisplay\");\n        return false;\n      }\n\n      // Try the next config.\n      continue;\n    }\n\n    if (::eglInitialize(display_, nullptr, nullptr) == EGL_FALSE) {\n      if (is_last) {\n        LogEGLError(\"Failed to initialize EGL via ANGLE\");\n        return false;\n      }\n\n      // Try the next config.\n      continue;\n    }\n\n    return true;\n  }\n\n  FML_UNREACHABLE();\n}\n\nbool Manager::InitializeConfig() {\n  const EGLint config_attributes[] = {EGL_RED_SIZE,   8, EGL_GREEN_SIZE,   8,\n                                      EGL_BLUE_SIZE,  8, EGL_ALPHA_SIZE,   8,\n                                      EGL_DEPTH_SIZE, 8, EGL_STENCIL_SIZE, 8,\n                                      EGL_NONE};\n\n  EGLint num_config = 0;\n\n  EGLBoolean result =\n      ::eglChooseConfig(display_, config_attributes, &config_, 1, &num_config);\n\n  if (result == EGL_TRUE && num_config > 0) {\n    return true;\n  }\n\n  LogEGLError(\"Failed to choose EGL config\");\n  return false;\n}\n\nbool Manager::InitializeContexts() {\n  const EGLint context_attributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};\n\n  auto const render_context =\n      ::eglCreateContext(display_, config_, EGL_NO_CONTEXT, context_attributes);\n  if (render_context == EGL_NO_CONTEXT) {\n    LogEGLError(\"Failed to create EGL render context\");\n    return false;\n  }\n\n  auto const resource_context =\n      ::eglCreateContext(display_, config_, render_context, context_attributes);\n  if (resource_context == EGL_NO_CONTEXT) {\n    LogEGLError(\"Failed to create EGL resource context\");\n    return false;\n  }\n\n  render_context_ = std::make_unique<Context>(display_, render_context);\n  resource_context_ = std::make_unique<Context>(display_, resource_context);\n  return true;\n}\n\nbool Manager::InitializeDevice() {\n  const auto query_display_attrib_EXT =\n      reinterpret_cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>(\n          ::eglGetProcAddress(\"eglQueryDisplayAttribEXT\"));\n  const auto query_device_attrib_EXT =\n      reinterpret_cast<PFNEGLQUERYDEVICEATTRIBEXTPROC>(\n          ::eglGetProcAddress(\"eglQueryDeviceAttribEXT\"));\n\n  if (query_display_attrib_EXT == nullptr ||\n      query_device_attrib_EXT == nullptr) {\n    return false;\n  }\n\n  EGLAttrib egl_device = 0;\n  EGLAttrib angle_device = 0;\n\n  auto result = query_display_attrib_EXT(display_, EGL_DEVICE_EXT, &egl_device);\n  if (result != EGL_TRUE) {\n    return false;\n  }\n\n  result = query_device_attrib_EXT(reinterpret_cast<EGLDeviceEXT>(egl_device),\n                                   EGL_D3D11_DEVICE_ANGLE, &angle_device);\n  if (result != EGL_TRUE) {\n    return false;\n  }\n\n  resolved_device_ = reinterpret_cast<ID3D11Device*>(angle_device);\n  return true;\n}\n\nbool Manager::TryInitializeD3D11Device() {\n  // Note: Different raster threads cannot use the same egldisplay.\n  d3d11_ = fml::NativeLibrary::Create(\"d3d11.dll\");\n\n  if (!d3d11_) {\n    FML_LOG(WARNING) << \"Could not load D3D11 library.\";\n    return false;\n  }\n\n  std::optional<PFN_D3D11_CREATE_DEVICE> D3D11CreateDevice =\n      d3d11_->ResolveFunction<PFN_D3D11_CREATE_DEVICE>(\"D3D11CreateDevice\");\n\n  if (!D3D11CreateDevice.has_value()) {\n    FML_LOG(WARNING) << \"Could not retrieve D3D11CreateDevice address.\";\n    return false;\n  }\n  D3D_FEATURE_LEVEL feature_levels[] = {D3D_FEATURE_LEVEL_11_1,\n                                        D3D_FEATURE_LEVEL_11_0};\n  HRESULT hr = D3D11CreateDevice.value()(\n      nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, feature_levels,\n      ARRAYSIZE(feature_levels), D3D11_SDK_VERSION, &resolved_device_, nullptr,\n      nullptr);\n  if (FAILED(hr)) {\n    FML_LOG(WARNING) << \"Could not create D3D11 Device.\";\n    return false;\n  }\n\n  egl_device_ = eglCreateDeviceANGLE(EGL_D3D11_DEVICE_ANGLE,\n                                     resolved_device_.Get(), nullptr);\n  if (!egl_device_) {\n    FML_LOG(WARNING) << \"Could not create EGL device.\";\n    return false;\n  }\n\n  return true;\n}\n\nbool Manager::TryInitializeDirectCompositionDevice() {\n  if (dcomp_device_) {\n    return true;\n  }\n  // Load DLL at runtime since older Windows versions don't have dcomp.\n  if (!dcomp_) {\n    dcomp_ = fml::NativeLibrary::Create(\"dcomp.dll\");\n  }\n\n  if (!dcomp_) {\n    FML_LOG(WARNING) << \"Could not load DCOMP library.\";\n    return false;\n  }\n  using PFN_DCOMPOSITION_CREATE_DEVICE2 = HRESULT(WINAPI*)(\n      IUnknown * renderingDevice, REFIID iid, void** dcompositionDevice);\n  std::optional<PFN_DCOMPOSITION_CREATE_DEVICE2> create_device_function =\n      dcomp_->ResolveFunction<PFN_DCOMPOSITION_CREATE_DEVICE2>(\n          \"DCompositionCreateDevice2\");\n\n  if (!create_device_function.has_value()) {\n    FML_LOG(ERROR) << \"GetProcAddress failed for DCompositionCreateDevice2\";\n    return false;\n  }\n\n  HRESULT hr;\n  Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device;\n  resolved_device_.As(&dxgi_device);\n\n  Microsoft::WRL::ComPtr<IDCompositionDesktopDevice> desktop_device;\n  hr = create_device_function.value()(dxgi_device.Get(),\n                                      IID_PPV_ARGS(&desktop_device));\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"DCompositionCreateDevice2 failed with error 0x\"\n                   << std::hex << hr;\n    return false;\n  }\n\n  hr = desktop_device.As(&dcomp_device_);\n\n  if (FAILED(hr)) {\n    FML_LOG(ERROR) << \"IDCompositionDevice2 create failed with error 0x\"\n                   << std::hex << hr;\n    return false;\n  }\n  return true;\n}\n\nvoid Manager::CleanUp() {\n  EGLBoolean result = EGL_FALSE;\n\n  // Needs to be reset before destroying the contexts.\n  resolved_device_.Reset();\n\n  // Needs to be reset before destroying the EGLDisplay.\n  render_context_.reset();\n  resource_context_.reset();\n\n  if (display_ != EGL_NO_DISPLAY) {\n    if (egl_device_ != nullptr) {\n      // If the display is created from an owned device,\n      // it needs to be released.\n      eglTerminate(display_);\n    }\n    display_ = EGL_NO_DISPLAY;\n  }\n\n  if (egl_device_ != nullptr) {\n    eglReleaseDeviceANGLE(egl_device_);\n    egl_device_ = nullptr;\n  }\n}\n\nbool Manager::IsValid() const { return is_valid_; }\n\nstd::unique_ptr<WindowSurface> Manager::CreateWindowSurface(\n    GLImplementationType type, HWND hwnd, size_t width, size_t height) {\n  if (!hwnd || !is_valid_) {\n    return nullptr;\n  }\n  std::unique_ptr<WindowSurface> surface = nullptr;\n  if (type == GLImplementationType::kAngleEGL) {\n    surface = std::make_unique<WindowSurface>(\n        display_, render_context_->GetHandle(), config_,\n        static_cast<EGLNativeWindowType>(hwnd), width, height);\n  } else if (type == GLImplementationType::kAngleEGLDirectComposition) {\n    if (TryInitializeDirectCompositionDevice()) {\n      surface = std::make_unique<DirectCompositionSurface>(\n          display_, render_context_->GetHandle(), config_,\n          static_cast<EGLNativeWindowType>(hwnd), dcomp_device_, width, height);\n    } else {\n      surface = std::make_unique<WindowSurface>(\n          display_, render_context_->GetHandle(), config_,\n          static_cast<EGLNativeWindowType>(hwnd), width, height);\n    }\n  } else {\n    return nullptr;\n  }\n  if (!surface->Initialize()) {\n    return nullptr;\n  }\n  return surface;\n}\n\nbool Manager::HasContextCurrent() {\n  return ::eglGetCurrentContext() != EGL_NO_CONTEXT;\n}\n\nEGLSurface Manager::CreateSurfaceFromHandle(EGLenum handle_type,\n                                            EGLClientBuffer handle,\n                                            const EGLint* attributes) const {\n  return ::eglCreatePbufferFromClientBuffer(display_, handle_type, handle,\n                                            config_, attributes);\n}\n\nbool Manager::GetDevice(ID3D11Device** device) {\n  if (!resolved_device_) {\n    if (!InitializeDevice()) {\n      return false;\n    }\n  }\n\n  resolved_device_.CopyTo(device);\n  return (resolved_device_ != nullptr);\n}\n\nContext* Manager::render_context() const { return render_context_.get(); }\n\nContext* Manager::resource_context() const { return resource_context_.get(); }\n\n}  // namespace egl\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/manager.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_EGL_MANAGER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_EGL_MANAGER_H_\n\n// OpenGL ES and EGL includes\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <EGL/eglplatform.h>\n#include <GLES2/gl2.h>\n#include <GLES2/gl2ext.h>\n\n// Windows platform specific includes\n#include <d3d11.h>\n#include <dcomp.h>\n#include <windows.h>\n#include <wrl/client.h>\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/fml/native_library.h\"\n#include \"clay/shell/platform/windows/egl/context.h\"\n#include \"clay/shell/platform/windows/egl/surface.h\"\n#include \"clay/shell/platform/windows/egl/window_surface.h\"\n\nnamespace clay {\nnamespace egl {\n\n// A manager for initializing ANGLE correctly and using it to create and\n// destroy surfaces\nclass Manager {\n public:\n  static std::unique_ptr<Manager> Create();\n\n  virtual ~Manager();\n\n  // Whether the manager is currently valid.\n  bool IsValid() const;\n\n  // Creates an EGL surface that can be used to render a Flutter view into a\n  // win32 HWND.\n  //\n  // After the surface is created, |WindowSurface::SetVSyncEnabled| should be\n  // called on a thread that can make the surface current.\n  //\n  // HWND is the window backing the surface. Width and height are the surface's\n  // physical pixel dimensions.\n  //\n  // Returns nullptr on failure.\n  virtual std::unique_ptr<WindowSurface> CreateWindowSurface(\n      GLImplementationType type, HWND hwnd, size_t width, size_t height);\n  // Check if the current thread has a context bound.\n  bool HasContextCurrent();\n\n  // Creates a |EGLSurface| from the provided handle.\n  EGLSurface CreateSurfaceFromHandle(EGLenum handle_type,\n                                     EGLClientBuffer handle,\n                                     const EGLint* attributes) const;\n\n  // Gets the |EGLDisplay|.\n  EGLDisplay egl_display() const { return display_; };\n\n  // Gets the |ID3D11Device| chosen by ANGLE.\n  bool GetDevice(ID3D11Device** device);\n\n  // Get the EGL context used to render Flutter views.\n  virtual Context* render_context() const;\n\n  // Get the EGL context used for async texture uploads.\n  virtual Context* resource_context() const;\n\n protected:\n  // Creates a new surface manager retaining reference to the passed-in target\n  // for the lifetime of the manager.\n  explicit Manager();\n\n private:\n  // Number of active instances of Manager\n  static int instance_count_;\n\n  // Initialize the EGL display.\n  bool InitializeDisplay();\n\n  // Initialize the EGL configs.\n  bool InitializeConfig();\n\n  // Initialize the EGL render and resource contexts.\n  bool InitializeContexts();\n\n  // Initialize the D3D11 device.\n  bool InitializeDevice();\n\n  // Try initialize the D3D11 device.\n  bool TryInitializeD3D11Device();\n\n  bool TryInitializeDirectCompositionDevice();\n\n  void CleanUp();\n\n  // Whether the manager was initialized successfully.\n  bool is_valid_ = false;\n\n  // EGL representation of native display.\n  EGLDisplay display_ = EGL_NO_DISPLAY;\n\n  // EGL framebuffer configuration.\n  EGLConfig config_ = nullptr;\n\n  // The EGL context used to render Flutter views.\n  std::unique_ptr<Context> render_context_;\n\n  // The EGL context used for async texture uploads.\n  std::unique_ptr<Context> resource_context_;\n\n  EGLDeviceEXT egl_device_ = nullptr;\n  fml::RefPtr<fml::NativeLibrary> d3d11_;\n  fml::RefPtr<fml::NativeLibrary> dcomp_;\n\n  // The current D3D device.\n  Microsoft::WRL::ComPtr<ID3D11Device> resolved_device_ = nullptr;\n\n  Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device_ = nullptr;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Manager);\n};\n\n}  // namespace egl\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_EGL_MANAGER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/surface.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/egl/surface.h\"\n\n#include \"clay/shell/platform/windows/egl/egl.h\"\n\nnamespace clay {\nnamespace egl {\n\n// The number of buffers in the swap chain can range from 2 to 16. see DXGI Flip\n// Model.\nstatic constexpr int kMaxBufferCount = 16;\n\nSurface::Surface(EGLDisplay display, EGLContext context, EGLSurface surface)\n    : display_(display), context_(context), surface_(surface) {\n  buffers_.resize(kMaxBufferCount);\n}\n\nSurface::~Surface() { Destroy(); }\n\nbool Surface::IsValid() const { return is_valid_; }\n\nbool Surface::Destroy() {\n  if (surface_ != EGL_NO_SURFACE) {\n    // Ensure the surface is not current before destroying it.\n    if (::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE,\n                         EGL_NO_CONTEXT) != EGL_TRUE) {\n      WINDOWS_LOG_EGL_ERROR;\n      return false;\n    }\n\n    if (::eglDestroySurface(display_, surface_) != EGL_TRUE) {\n      WINDOWS_LOG_EGL_ERROR;\n      return false;\n    }\n  }\n\n  is_valid_ = false;\n  surface_ = EGL_NO_SURFACE;\n  return true;\n}\n\nbool Surface::IsCurrent() const {\n  return display_ == ::eglGetCurrentDisplay() &&\n         GetHandle() == ::eglGetCurrentSurface(EGL_DRAW) &&\n         GetHandle() == ::eglGetCurrentSurface(EGL_READ) &&\n         context_ == ::eglGetCurrentContext();\n}\n\nbool Surface::MakeCurrent() {\n  if (::eglMakeCurrent(display_, GetHandle(), GetHandle(), context_) !=\n      EGL_TRUE) {\n    WINDOWS_LOG_EGL_ERROR;\n    return false;\n  }\n\n  return true;\n}\n\nbool Surface::SwapBuffers() {\n  if (::eglSwapBuffers(display_, GetHandle()) != EGL_TRUE) {\n    WINDOWS_LOG_EGL_ERROR;\n    return false;\n  }\n  return true;\n}\n\nconst EGLSurface& Surface::GetHandle() const { return surface_; }\n\nvoid Surface::SetDamageRegion(const clay::Rect& region) {}\n\nstd::optional<clay::Rect> Surface::GetDamageRegion() const {\n  int age = buffers_[current_buffer_index_].age;\n  if (age == 0) {\n    return std::nullopt;\n  } else {\n    --age;\n    clay::Rect res = {};\n    for (size_t i = 0; i < buffer_count(); i++) {\n      if (buffers_[i].age <= age) {\n        res.Unite(buffers_[i].damage);\n      }\n    }\n    return res;\n  }\n}\n\nvoid Surface::AddDamageRegion(const clay::Rect& region) {\n  buffers_[current_buffer_index_].damage = region;\n  for (size_t i = 0; i < buffer_count(); ++i) {\n    if (i == current_buffer_index_) {\n      buffers_[i].age = 1;\n    } else if (buffers_[i].age > 0) {\n      ++buffers_[i].age;\n    }\n  }\n  current_buffer_index_ = (current_buffer_index_ + 1) % buffer_count();\n}\n\nbool Surface::Resize(int width, int height) {\n  buffers_.clear();\n  buffers_.resize(buffer_count());\n  current_buffer_index_ = 0;\n  return true;\n}\n\n}  // namespace egl\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/surface.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_EGL_SURFACE_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_EGL_SURFACE_H_\n\n#include <EGL/egl.h>\n\n#include <optional>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/geometry/rect.h\"\n\nnamespace clay {\nnamespace egl {\n\nenum class GLImplementationType {\n  kAngleEGL,\n  kAngleEGLDirectComposition,\n};\n\nstruct BufferInfo {\n  uint32_t age = 0;\n  clay::Rect damage;\n};\n\n// An EGL surface. This can be window surface or an off-screen buffer.\n//\n// This enables automatic error logging and mocking.\nclass Surface {\n public:\n  Surface(EGLDisplay display, EGLContext context, EGLSurface surface);\n\n  virtual ~Surface();\n\n  // Destroy the EGL surface and invalidate this object.\n  //\n  // This also unbinds the current context from the thread.\n  virtual bool Destroy();\n\n  // Check whether the EGL surface is valid.\n  virtual bool IsValid() const;\n\n  // Check whether the EGL display, context, and surface are bound\n  // to the current thread.\n  virtual bool IsCurrent() const;\n\n  // Bind the EGL surface's context and read and draw surfaces to the\n  // current thread. Returns true on success.\n  virtual bool MakeCurrent();\n\n  // Swap the surface's front the and back buffers. Used to present content.\n  // Returns true on success.\n  virtual bool SwapBuffers();\n\n  // Resizes the underlying surface or resource to the specified width and\n  // height.\n  virtual bool Resize(int width, int height);\n\n  virtual uint32_t buffer_count() const { return 1; }\n\n  // Get the raw EGL surface.\n  virtual const EGLSurface& GetHandle() const;\n\n  virtual void SetDamageRegion(const clay::Rect& region);\n\n  std::optional<clay::Rect> GetDamageRegion() const;\n\n protected:\n  void AddDamageRegion(const clay::Rect& rect);\n\n  bool is_valid_ = true;\n\n  EGLDisplay display_ = EGL_NO_DISPLAY;\n  EGLContext context_ = EGL_NO_CONTEXT;\n  EGLSurface surface_ = EGL_NO_SURFACE;\n\n  std::vector<BufferInfo> buffers_;\n  int current_buffer_index_ = 0;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(Surface);\n};\n\n}  // namespace egl\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_EGL_SURFACE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/window_surface.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/egl/window_surface.h\"\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/platform/windows/egl/egl.h\"\n\nnamespace clay {\nnamespace egl {\n\nWindowSurface::WindowSurface(EGLDisplay display, EGLContext context,\n                             EGLSurface surface, int width, int height)\n    : Surface(display, context, surface), size_({width, height}) {}\n\nWindowSurface::WindowSurface(EGLDisplay display, EGLContext context,\n                             EGLConfig config, EGLNativeWindowType window,\n                             int width, int height)\n    : Surface(display, context, EGL_NO_SURFACE),\n      size_({width, height}),\n      config_(config),\n      window_(window) {}\n\nbool WindowSurface::Initialize() {\n  if (display_ == EGL_NO_DISPLAY) {\n    FML_LOG(ERROR) << \"Trying to create surface with invalid display.\";\n    return false;\n  }\n  // Disable ANGLE's automatic surface resizing and provide an explicit size.\n  // The surface will need to be destroyed and re-created if the HWND is\n  // resized.\n  const EGLint surface_attributes[] = {EGL_FIXED_SIZE_ANGLE,\n                                       EGL_TRUE,\n                                       EGL_WIDTH,\n                                       static_cast<EGLint>(size_.width()),\n                                       EGL_HEIGHT,\n                                       static_cast<EGLint>(size_.height()),\n                                       EGL_NONE};\n  auto const surface =\n      ::eglCreateWindowSurface(display_, config_, window_, surface_attributes);\n  if (surface == EGL_NO_SURFACE) {\n    FML_LOG(ERROR) << \"Surface creation failed.\";\n    return false;\n  }\n  surface_ = std::move(surface);\n  is_valid_ = true;\n  return true;\n}\n\nbool WindowSurface::SetVSyncEnabled(bool enabled) {\n  FML_DCHECK(IsCurrent());\n\n  if (::eglSwapInterval(display_, enabled ? 1 : 0) != EGL_TRUE) {\n    WINDOWS_LOG_EGL_ERROR;\n    return false;\n  }\n\n  vsync_enabled_ = enabled;\n  return true;\n}\n\nint WindowSurface::width() const { return size_.width(); }\n\nint WindowSurface::height() const { return size_.height(); }\n\nbool WindowSurface::vsync_enabled() const { return vsync_enabled_; }\n\nbool WindowSurface::Resize(int width, int height) {\n  if (!Surface::Destroy()) {\n    FML_LOG(ERROR) << \"Surface resize failed to destroy surface\";\n    return false;\n  }\n  size_ = {width, height};\n  if (!Initialize()) {\n    FML_LOG(ERROR) << \"Surface resize failed to create surface\";\n    return false;\n  }\n  if (!MakeCurrent() || !SetVSyncEnabled(vsync_enabled())) {\n    // Surfaces block until the v-blank by default.\n    // Failing to update the vsync might result in unnecessary blocking.\n    // This regresses performance but not correctness.\n    FML_LOG(ERROR) << \"Surface resize failed to set vsync\";\n  }\n  return true;\n}\n\n}  // namespace egl\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/egl/window_surface.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_EGL_WINDOW_SURFACE_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_EGL_WINDOW_SURFACE_H_\n\n#include <EGL/egl.h>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/gfx/geometry/rect.h\"\n#include \"clay/gfx/geometry/size.h\"\n#include \"clay/shell/platform/windows/egl/surface.h\"\n\nnamespace clay {\nnamespace egl {\n\n// An EGL surface used to render a Flutter view to a win32 HWND.\n//\n// This enables automatic error logging and mocking.\nclass WindowSurface : public Surface {\n public:\n  WindowSurface(EGLDisplay display, EGLContext context, EGLSurface surface,\n                int width, int height);\n  WindowSurface(EGLDisplay display, EGLContext context, EGLConfig config,\n                EGLNativeWindowType window, int width, int height);\n\n  virtual bool Initialize();\n  // If enabled, makes the surface's buffer swaps block until the v-blank.\n  //\n  // If disabled, allows one thread to swap multiple buffers per v-blank\n  // but can result in screen tearing if the system compositor is disabled.\n  //\n  // The surface must be current before calling this.\n  virtual bool SetVSyncEnabled(bool enabled);\n\n  // Get the surface's width in physical pixels.\n  virtual int width() const;\n\n  // Get the surface's height in physical pixels.\n  virtual int height() const;\n\n  // Get whether the surface's buffer swap blocks until the v-blank.\n  virtual bool vsync_enabled() const;\n\n  bool Resize(int width, int height) override;\n\n protected:\n  clay::Size size_ = {0, 0};\n  EGLNativeWindowType window_ = 0;\n  EGLConfig config_;\n\n private:\n  bool vsync_enabled_ = true;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(WindowSurface);\n};\n\n}  // namespace egl\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_EGL_WINDOW_SURFACE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/flutter_key_map.g.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_KEY_MAP_H_\n#define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_KEY_MAP_H_\n\n#include <map>\n\n#include \"clay/shell/platform/windows/keyboard_key_embedder_handler.h\"\n\n// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT\n// This file is generated by\n// flutter/flutter:dev/tools/gen_keycodes/bin/gen_keycodes.dart and should not\n// be edited directly.\n//\n// Edit the template\n// flutter/flutter:dev/tools/gen_keycodes/data/windows_flutter_key_map_cc.tmpl\n// instead.\n//\n// See flutter/flutter:dev/tools/gen_keycodes/README.md for more information.\n\nnamespace clay {\n\nstd::map<uint64_t, uint64_t> KeyboardKeyEmbedderHandler::windowsToPhysicalMap_ =\n    {\n        {0x00000001, 0x00070029},  // escape\n        {0x00000002, 0x0007001e},  // digit1\n        {0x00000003, 0x0007001f},  // digit2\n        {0x00000004, 0x00070020},  // digit3\n        {0x00000005, 0x00070021},  // digit4\n        {0x00000006, 0x00070022},  // digit5\n        {0x00000007, 0x00070023},  // digit6\n        {0x00000008, 0x00070024},  // digit7\n        {0x00000009, 0x00070025},  // digit8\n        {0x0000000a, 0x00070026},  // digit9\n        {0x0000000b, 0x00070027},  // digit0\n        {0x0000000c, 0x0007002d},  // minus\n        {0x0000000d, 0x0007002e},  // equal\n        {0x0000000e, 0x0007002a},  // backspace\n        {0x0000000f, 0x0007002b},  // tab\n        {0x00000010, 0x00070014},  // keyQ\n        {0x00000011, 0x0007001a},  // keyW\n        {0x00000012, 0x00070008},  // keyE\n        {0x00000013, 0x00070015},  // keyR\n        {0x00000014, 0x00070017},  // keyT\n        {0x00000015, 0x0007001c},  // keyY\n        {0x00000016, 0x00070018},  // keyU\n        {0x00000017, 0x0007000c},  // keyI\n        {0x00000018, 0x00070012},  // keyO\n        {0x00000019, 0x00070013},  // keyP\n        {0x0000001a, 0x0007002f},  // bracketLeft\n        {0x0000001b, 0x00070030},  // bracketRight\n        {0x0000001c, 0x00070028},  // enter\n        {0x0000001d, 0x000700e0},  // controlLeft\n        {0x0000001e, 0x00070004},  // keyA\n        {0x0000001f, 0x00070016},  // keyS\n        {0x00000020, 0x00070007},  // keyD\n        {0x00000021, 0x00070009},  // keyF\n        {0x00000022, 0x0007000a},  // keyG\n        {0x00000023, 0x0007000b},  // keyH\n        {0x00000024, 0x0007000d},  // keyJ\n        {0x00000025, 0x0007000e},  // keyK\n        {0x00000026, 0x0007000f},  // keyL\n        {0x00000027, 0x00070033},  // semicolon\n        {0x00000028, 0x00070034},  // quote\n        {0x00000029, 0x00070035},  // backquote\n        {0x0000002a, 0x000700e1},  // shiftLeft\n        {0x0000002b, 0x00070031},  // backslash\n        {0x0000002c, 0x0007001d},  // keyZ\n        {0x0000002d, 0x0007001b},  // keyX\n        {0x0000002e, 0x00070006},  // keyC\n        {0x0000002f, 0x00070019},  // keyV\n        {0x00000030, 0x00070005},  // keyB\n        {0x00000031, 0x00070011},  // keyN\n        {0x00000032, 0x00070010},  // keyM\n        {0x00000033, 0x00070036},  // comma\n        {0x00000034, 0x00070037},  // period\n        {0x00000035, 0x00070038},  // slash\n        {0x00000036, 0x000700e5},  // shiftRight\n        {0x00000037, 0x00070055},  // numpadMultiply\n        {0x00000038, 0x000700e2},  // altLeft\n        {0x00000039, 0x0007002c},  // space\n        {0x0000003a, 0x00070039},  // capsLock\n        {0x0000003b, 0x0007003a},  // f1\n        {0x0000003c, 0x0007003b},  // f2\n        {0x0000003d, 0x0007003c},  // f3\n        {0x0000003e, 0x0007003d},  // f4\n        {0x0000003f, 0x0007003e},  // f5\n        {0x00000040, 0x0007003f},  // f6\n        {0x00000041, 0x00070040},  // f7\n        {0x00000042, 0x00070041},  // f8\n        {0x00000043, 0x00070042},  // f9\n        {0x00000044, 0x00070043},  // f10\n        {0x00000045, 0x00070048},  // pause\n        {0x00000046, 0x00070047},  // scrollLock\n        {0x00000047, 0x0007005f},  // numpad7\n        {0x00000048, 0x00070060},  // numpad8\n        {0x00000049, 0x00070061},  // numpad9\n        {0x0000004a, 0x00070056},  // numpadSubtract\n        {0x0000004b, 0x0007005c},  // numpad4\n        {0x0000004c, 0x0007005d},  // numpad5\n        {0x0000004d, 0x0007005e},  // numpad6\n        {0x0000004e, 0x00070057},  // numpadAdd\n        {0x0000004f, 0x00070059},  // numpad1\n        {0x00000050, 0x0007005a},  // numpad2\n        {0x00000051, 0x0007005b},  // numpad3\n        {0x00000052, 0x00070062},  // numpad0\n        {0x00000053, 0x00070063},  // numpadDecimal\n        {0x00000056, 0x00070064},  // intlBackslash\n        {0x00000057, 0x00070044},  // f11\n        {0x00000058, 0x00070045},  // f12\n        {0x00000059, 0x00070067},  // numpadEqual\n        {0x00000064, 0x00070068},  // f13\n        {0x00000065, 0x00070069},  // f14\n        {0x00000066, 0x0007006a},  // f15\n        {0x00000067, 0x0007006b},  // f16\n        {0x00000068, 0x0007006c},  // f17\n        {0x00000069, 0x0007006d},  // f18\n        {0x0000006a, 0x0007006e},  // f19\n        {0x0000006b, 0x0007006f},  // f20\n        {0x0000006c, 0x00070070},  // f21\n        {0x0000006d, 0x00070071},  // f22\n        {0x0000006e, 0x00070072},  // f23\n        {0x00000070, 0x00070088},  // kanaMode\n        {0x00000071, 0x00070091},  // lang2\n        {0x00000072, 0x00070090},  // lang1\n        {0x00000073, 0x00070087},  // intlRo\n        {0x00000076, 0x00070073},  // f24\n        {0x00000077, 0x00070093},  // lang4\n        {0x00000078, 0x00070092},  // lang3\n        {0x00000079, 0x0007008a},  // convert\n        {0x0000007b, 0x0007008b},  // nonConvert\n        {0x0000007d, 0x00070089},  // intlYen\n        {0x0000007e, 0x00070085},  // numpadComma\n        {0x000000fc, 0x00070002},  // usbPostFail\n        {0x000000ff, 0x00070001},  // usbErrorRollOver\n        {0x0000e008, 0x0007007a},  // undo\n        {0x0000e00a, 0x0007007d},  // paste\n        {0x0000e010, 0x000c00b6},  // mediaTrackPrevious\n        {0x0000e017, 0x0007007b},  // cut\n        {0x0000e018, 0x0007007c},  // copy\n        {0x0000e019, 0x000c00b5},  // mediaTrackNext\n        {0x0000e01c, 0x00070058},  // numpadEnter\n        {0x0000e01d, 0x000700e4},  // controlRight\n        {0x0000e020, 0x0007007f},  // audioVolumeMute\n        {0x0000e021, 0x000c0192},  // launchApp2\n        {0x0000e022, 0x000c00cd},  // mediaPlayPause\n        {0x0000e024, 0x000c00b7},  // mediaStop\n        {0x0000e02c, 0x000c00b8},  // eject\n        {0x0000e02e, 0x00070081},  // audioVolumeDown\n        {0x0000e030, 0x00070080},  // audioVolumeUp\n        {0x0000e032, 0x000c0223},  // browserHome\n        {0x0000e035, 0x00070054},  // numpadDivide\n        {0x0000e037, 0x00070046},  // printScreen\n        {0x0000e038, 0x000700e6},  // altRight\n        {0x0000e03b, 0x00070075},  // help\n        {0x0000e045, 0x00070053},  // numLock\n        {0x0000e047, 0x0007004a},  // home\n        {0x0000e048, 0x00070052},  // arrowUp\n        {0x0000e049, 0x0007004b},  // pageUp\n        {0x0000e04b, 0x00070050},  // arrowLeft\n        {0x0000e04d, 0x0007004f},  // arrowRight\n        {0x0000e04f, 0x0007004d},  // end\n        {0x0000e050, 0x00070051},  // arrowDown\n        {0x0000e051, 0x0007004e},  // pageDown\n        {0x0000e052, 0x00070049},  // insert\n        {0x0000e053, 0x0007004c},  // delete\n        {0x0000e05b, 0x000700e3},  // metaLeft\n        {0x0000e05c, 0x000700e7},  // metaRight\n        {0x0000e05d, 0x00070065},  // contextMenu\n        {0x0000e05e, 0x00070066},  // power\n        {0x0000e05f, 0x00010082},  // sleep\n        {0x0000e063, 0x00010083},  // wakeUp\n        {0x0000e065, 0x000c0221},  // browserSearch\n        {0x0000e066, 0x000c022a},  // browserFavorites\n        {0x0000e067, 0x000c0227},  // browserRefresh\n        {0x0000e068, 0x000c0226},  // browserStop\n        {0x0000e069, 0x000c0225},  // browserForward\n        {0x0000e06a, 0x000c0224},  // browserBack\n        {0x0000e06b, 0x000c0194},  // launchApp1\n        {0x0000e06c, 0x000c018a},  // launchMail\n        {0x0000e06d, 0x000c0183},  // mediaSelect\n};\n\nstd::map<uint64_t, uint64_t> KeyboardKeyEmbedderHandler::windowsToLogicalMap_ =\n    {\n        {0x00000003, 0x00100000504},  // CANCEL -> cancel\n        {0x00000008, 0x00100000008},  // BACK -> backspace\n        {0x00000009, 0x00100000009},  // TAB -> tab\n        {0x0000000c, 0x00100000401},  // CLEAR -> clear\n        {0x0000000d, 0x0010000000d},  // RETURN -> enter\n        {0x00000010, 0x00200000102},  // SHIFT -> shiftLeft\n        {0x00000011, 0x00200000100},  // CONTROL -> controlLeft\n        {0x00000013, 0x00100000509},  // PAUSE -> pause\n        {0x00000014, 0x00100000104},  // CAPITAL -> capsLock\n        {0x00000015, 0x00200000010},  // KANA, HANGEUL, HANGUL -> lang1\n        {0x00000017, 0x00100000713},  // JUNJA -> junjaMode\n        {0x00000018, 0x00100000706},  // FINAL -> finalMode\n        {0x00000019, 0x00100000719},  // HANJA, KANJI -> kanjiMode\n        {0x0000001b, 0x0010000001b},  // ESCAPE -> escape\n        {0x0000001c, 0x00100000705},  // CONVERT -> convert\n        {0x0000001e, 0x00100000501},  // ACCEPT -> accept\n        {0x0000001f, 0x0010000070b},  // MODECHANGE -> modeChange\n        {0x00000020, 0x00000000020},  // SPACE -> space\n        {0x00000021, 0x00100000308},  // PRIOR -> pageUp\n        {0x00000022, 0x00100000307},  // NEXT -> pageDown\n        {0x00000023, 0x00100000305},  // END -> end\n        {0x00000024, 0x00100000306},  // HOME -> home\n        {0x00000025, 0x00100000302},  // LEFT -> arrowLeft\n        {0x00000026, 0x00100000304},  // UP -> arrowUp\n        {0x00000027, 0x00100000303},  // RIGHT -> arrowRight\n        {0x00000028, 0x00100000301},  // DOWN -> arrowDown\n        {0x00000029, 0x0010000050c},  // SELECT -> select\n        {0x0000002a, 0x00100000a0c},  // PRINT -> print\n        {0x0000002b, 0x00100000506},  // EXECUTE -> execute\n        {0x0000002c, 0x00100000608},  // SNAPSHOT -> printScreen\n        {0x0000002d, 0x00100000407},  // INSERT -> insert\n        {0x0000002e, 0x0010000007f},  // DELETE -> delete\n        {0x0000002f, 0x00100000508},  // HELP -> help\n        {0x0000005b, 0x00200000106},  // LWIN -> metaLeft\n        {0x0000005c, 0x00200000107},  // RWIN -> metaRight\n        {0x0000005d, 0x00100000505},  // APPS -> contextMenu\n        {0x0000005f, 0x00200000002},  // SLEEP -> sleep\n        {0x00000060, 0x00200000230},  // NUMPAD0 -> numpad0\n        {0x00000061, 0x00200000231},  // NUMPAD1 -> numpad1\n        {0x00000062, 0x00200000232},  // NUMPAD2 -> numpad2\n        {0x00000063, 0x00200000233},  // NUMPAD3 -> numpad3\n        {0x00000064, 0x00200000234},  // NUMPAD4 -> numpad4\n        {0x00000065, 0x00200000235},  // NUMPAD5 -> numpad5\n        {0x00000066, 0x00200000236},  // NUMPAD6 -> numpad6\n        {0x00000067, 0x00200000237},  // NUMPAD7 -> numpad7\n        {0x00000068, 0x00200000238},  // NUMPAD8 -> numpad8\n        {0x00000069, 0x00200000239},  // NUMPAD9 -> numpad9\n        {0x0000006a, 0x0020000022a},  // MULTIPLY -> numpadMultiply\n        {0x0000006b, 0x0020000022b},  // ADD -> numpadAdd\n        {0x0000006c, 0x0020000022c},  // SEPARATOR -> numpadComma\n        {0x0000006d, 0x0020000022d},  // SUBTRACT -> numpadSubtract\n        {0x0000006e, 0x0020000022e},  // DECIMAL -> numpadDecimal\n        {0x0000006f, 0x0020000022f},  // DIVIDE -> numpadDivide\n        {0x00000070, 0x00100000801},  // F1 -> f1\n        {0x00000071, 0x00100000802},  // F2 -> f2\n        {0x00000072, 0x00100000803},  // F3 -> f3\n        {0x00000073, 0x00100000804},  // F4 -> f4\n        {0x00000074, 0x00100000805},  // F5 -> f5\n        {0x00000075, 0x00100000806},  // F6 -> f6\n        {0x00000076, 0x00100000807},  // F7 -> f7\n        {0x00000077, 0x00100000808},  // F8 -> f8\n        {0x00000078, 0x00100000809},  // F9 -> f9\n        {0x00000079, 0x0010000080a},  // F10 -> f10\n        {0x0000007a, 0x0010000080b},  // F11 -> f11\n        {0x0000007b, 0x0010000080c},  // F12 -> f12\n        {0x0000007c, 0x0010000080d},  // F13 -> f13\n        {0x0000007d, 0x0010000080e},  // F14 -> f14\n        {0x0000007e, 0x0010000080f},  // F15 -> f15\n        {0x0000007f, 0x00100000810},  // F16 -> f16\n        {0x00000080, 0x00100000811},  // F17 -> f17\n        {0x00000081, 0x00100000812},  // F18 -> f18\n        {0x00000082, 0x00100000813},  // F19 -> f19\n        {0x00000083, 0x00100000814},  // F20 -> f20\n        {0x00000084, 0x00100000815},  // F21 -> f21\n        {0x00000085, 0x00100000816},  // F22 -> f22\n        {0x00000086, 0x00100000817},  // F23 -> f23\n        {0x00000087, 0x00100000818},  // F24 -> f24\n        {0x00000090, 0x0010000010a},  // NUMLOCK -> numLock\n        {0x00000091, 0x0010000010c},  // SCROLL -> scrollLock\n        {0x00000092, 0x0020000023d},  // OEM_NEC_EQUAL -> numpadEqual\n        {0x000000a0, 0x00200000102},  // LSHIFT -> shiftLeft\n        {0x000000a1, 0x00200000103},  // RSHIFT -> shiftRight\n        {0x000000a2, 0x00200000100},  // LCONTROL -> controlLeft\n        {0x000000a3, 0x00200000101},  // RCONTROL -> controlRight\n        {0x000000a4, 0x00200000104},  // LMENU -> altLeft\n        {0x000000a5, 0x00200000105},  // RMENU -> altRight\n        {0x000000a6, 0x00100000c01},  // BROWSER_BACK -> browserBack\n        {0x000000a7, 0x00100000c03},  // BROWSER_FORWARD -> browserForward\n        {0x000000a8, 0x00100000c05},  // BROWSER_REFRESH -> browserRefresh\n        {0x000000a9, 0x00100000c07},  // BROWSER_STOP -> browserStop\n        {0x000000aa, 0x00100000c06},  // BROWSER_SEARCH -> browserSearch\n        {0x000000ab, 0x00100000c02},  // BROWSER_FAVORITES -> browserFavorites\n        {0x000000ac, 0x00100000c04},  // BROWSER_HOME -> browserHome\n        {0x000000ad, 0x00100000a11},  // VOLUME_MUTE -> audioVolumeMute\n        {0x000000ae, 0x00100000a0f},  // VOLUME_DOWN -> audioVolumeDown\n        {0x000000af, 0x00100000a10},  // VOLUME_UP -> audioVolumeUp\n        {0x000000b2, 0x00100000a07},  // MEDIA_STOP -> mediaStop\n        {0x000000b3, 0x00100000a05},  // MEDIA_PLAY_PAUSE -> mediaPlayPause\n        {0x000000b4, 0x00100000b03},  // LAUNCH_MAIL -> launchMail\n        {0x000000ba, 0x0000000003b},  // OEM_1 -> semicolon\n        {0x000000bb, 0x0000000003d},  // OEM_PLUS -> equal\n        {0x000000bc, 0x0000000002c},  // OEM_COMMA -> comma\n        {0x000000bd, 0x0000000002d},  // OEM_MINUS -> minus\n        {0x000000be, 0x0000000002e},  // OEM_PERIOD -> period\n        {0x000000bf, 0x0000000002f},  // OEM_2 -> slash\n        {0x000000c0, 0x00000000060},  // OEM_3 -> backquote\n        {0x000000c3, 0x00200000308},  // GAMEPAD_A -> gameButton8\n        {0x000000c4, 0x00200000309},  // GAMEPAD_B -> gameButton9\n        {0x000000c5, 0x0020000030a},  // GAMEPAD_X -> gameButton10\n        {0x000000c6, 0x0020000030b},  // GAMEPAD_Y -> gameButton11\n        {0x000000c7, 0x0020000030c},  // GAMEPAD_RIGHT_SHOULDER -> gameButton12\n        {0x000000c8, 0x0020000030d},  // GAMEPAD_LEFT_SHOULDER -> gameButton13\n        {0x000000c9, 0x0020000030e},  // GAMEPAD_LEFT_TRIGGER -> gameButton14\n        {0x000000ca, 0x0020000030f},  // GAMEPAD_RIGHT_TRIGGER -> gameButton15\n        {0x000000cb, 0x00200000310},  // GAMEPAD_DPAD_UP -> gameButton16\n        {0x000000db, 0x0000000005b},  // OEM_4 -> bracketLeft\n        {0x000000dc, 0x0000000005c},  // OEM_5 -> backslash\n        {0x000000dd, 0x0000000005d},  // OEM_6 -> bracketRight\n        {0x000000de, 0x00000000022},  // OEM_7 -> quote\n        {0x000000f6, 0x00100000503},  // ATTN -> attn\n        {0x000000fa, 0x0010000050a},  // PLAY -> play\n};\n\nstd::map<uint64_t, uint64_t> KeyboardKeyEmbedderHandler::scanCodeToLogicalMap_ =\n    {\n        {0x00000037, 0x0020000022a},  // numpadMultiply -> numpadMultiply\n        {0x00000047, 0x00200000237},  // numpad7 -> numpad7\n        {0x00000048, 0x00200000238},  // numpad8 -> numpad8\n        {0x00000049, 0x00200000239},  // numpad9 -> numpad9\n        {0x0000004a, 0x0020000022d},  // numpadSubtract -> numpadSubtract\n        {0x0000004b, 0x00200000234},  // numpad4 -> numpad4\n        {0x0000004c, 0x00200000235},  // numpad5 -> numpad5\n        {0x0000004d, 0x00200000236},  // numpad6 -> numpad6\n        {0x0000004e, 0x0020000022b},  // numpadAdd -> numpadAdd\n        {0x0000004f, 0x00200000231},  // numpad1 -> numpad1\n        {0x00000050, 0x00200000232},  // numpad2 -> numpad2\n        {0x00000051, 0x00200000233},  // numpad3 -> numpad3\n        {0x00000052, 0x00200000230},  // numpad0 -> numpad0\n        {0x00000053, 0x0020000022e},  // numpadDecimal -> numpadDecimal\n        {0x00000059, 0x0020000023d},  // numpadEqual -> numpadEqual\n        {0x0000e01d, 0x00200000101},  // controlRight -> controlRight\n        {0x0000e035, 0x0020000022f},  // numpadDivide -> numpadDivide\n        {0x0000e038, 0x00200000105},  // altRight -> altRight\n};\n\nconst uint64_t KeyboardKeyEmbedderHandler::valueMask = 0x000ffffffff;\nconst uint64_t KeyboardKeyEmbedderHandler::unicodePlane = 0x00000000000;\nconst uint64_t KeyboardKeyEmbedderHandler::windowsPlane = 0x01600000000;\n\n}  // namespace clay\n\n#endif\n"
  },
  {
    "path": "clay/shell/platform/windows/flutter_project_bundle.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/flutter_project_bundle.h\"\n\n#include <filesystem>\n#include <sstream>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/common/switches.h\"\n#include \"clay/shell/platform/common/engine_switches.h\"  // nogncheck\n#include \"clay/shell/platform/common/path_utils.h\"\n\nnamespace clay {\n\nFlutterProjectBundle::FlutterProjectBundle(\n    const FlutterDesktopEngineProperties& properties)\n    : icu_path_(properties.icu_data_path),\n      enable_software_rendering_(properties.enable_software_rendering) {\n  // Resolve any relative paths.\n  if (icu_path_.is_relative()) {\n    std::filesystem::path executable_location = GetExecutableDirectory();\n    if (executable_location.empty()) {\n      FML_LOG(ERROR)\n          << \"Unable to find executable location to resolve resource paths.\";\n      icu_path_ = std::filesystem::path();\n    } else {\n      icu_path_ = std::filesystem::path(executable_location) / icu_path_;\n    }\n  }\n}\n\nbool FlutterProjectBundle::HasValidPaths() { return !icu_path_.empty(); }\n\nFlutterProjectBundle::~FlutterProjectBundle() {}\n\nvoid FlutterProjectBundle::SetSwitches(\n    const std::vector<std::string>& switches) {\n  engine_switches_ = switches;\n}\n\nconst std::vector<std::string> FlutterProjectBundle::GetSwitches() {\n  std::vector<std::string> switches = GetSwitchesFromEnvironment();\n  if (enable_software_rendering_) {\n    std::ostringstream switch_value_as_flag;\n    switch_value_as_flag << \"--\"\n                         << FlagForSwitch(Switch::EnableSoftwareRendering);\n    std::string flag = switch_value_as_flag.str();\n    if (std::find(switches.begin(), switches.end(), flag) == switches.end()) {\n      switches.push_back(flag);\n    }\n  }\n  return switches;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/flutter_project_bundle.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_PROJECT_BUNDLE_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_PROJECT_BUNDLE_H_\n\n#include <filesystem>\n#include <string>\n#include <vector>\n\nnamespace clay {\n\n// Properties for configuring a Flutter engine instance.\ntypedef struct {\n  // The path to the icudtl.dat file for the version of Flutter you are using.\n  // This can either be an absolute path or a path relative to the directory\n  // containing the executable.\n  const wchar_t* icu_data_path;\n\n  bool enable_software_rendering;\n} FlutterDesktopEngineProperties;\n\n// The data associated with a Flutter project needed to run it in an engine.\nclass FlutterProjectBundle {\n public:\n  // Creates a new project bundle from the given properties.\n  //\n  // Treats any relative paths as relative to the directory of this executable.\n  explicit FlutterProjectBundle(\n      const FlutterDesktopEngineProperties& properties);\n\n  ~FlutterProjectBundle();\n\n  // Whether or not the bundle is valid. This does not check that the paths\n  // exist, or contain valid data, just that paths were able to be constructed.\n  bool HasValidPaths();\n\n  // Returns the path to the ICU data file.\n  const std::filesystem::path& icu_path() { return icu_path_; }\n\n  // Returns any switches that should be passed to the engine.\n  const std::vector<std::string> GetSwitches();\n\n  // Sets engine switches.\n  void SetSwitches(const std::vector<std::string>& switches);\n\n  bool use_software_rendering() { return enable_software_rendering_; }\n\n private:\n  std::filesystem::path icu_path_;\n\n  // Engine switches.\n  std::vector<std::string> engine_switches_;\n\n  bool enable_software_rendering_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_PROJECT_BUNDLE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/flutter_window.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/flutter_window.h\"\n\n#include <WinUser.h>\n#include <dwmapi.h>\n\n#include <chrono>\n#include <map>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/shell/platform/windows/flutter_windows_engine.h\"\n#include \"clay/shell/platform/windows/flutter_windows_view.h\"\n#include \"clay/ui/platform/cursor_types.h\"\n\n// cspell:disable\nnamespace clay {\n\nnamespace {\n\n// The Windows DPI system is based on this\n// constant for machines running at 100% scaling.\nconstexpr int base_dpi = 96;\n\n// Maps a Flutter cursor name to an HCURSOR.\n//\n// Returns the arrow cursor for unknown constants.\n//\n// This map must be kept in sync with Flutter framework's\n// services/mouse_cursor.dart.\n\nstatic HCURSOR GetCursorByType(clay::CursorTypes cursor_type) {\n  static auto* cursors = new std::map<clay::CursorTypes, const wchar_t*>{\n      {clay::CursorTypes::kClick, IDC_HAND},\n      {clay::CursorTypes::kForbidden, IDC_NO},\n      {clay::CursorTypes::kHelp, IDC_HELP},\n      {clay::CursorTypes::kMove, IDC_SIZEALL},\n      {clay::CursorTypes::kNone, nullptr},\n      {clay::CursorTypes::kNodrop, IDC_NO},\n      {clay::CursorTypes::kPrecise, IDC_CROSS},\n      {clay::CursorTypes::kProgress, IDC_APPSTARTING},\n      {clay::CursorTypes::kText, IDC_IBEAM},\n      {clay::CursorTypes::kResizecolumn, IDC_SIZEWE},\n      {clay::CursorTypes::kResizedown, IDC_SIZENS},\n      {clay::CursorTypes::kResizedownleft, IDC_SIZENESW},\n      {clay::CursorTypes::kResizedownright, IDC_SIZENWSE},\n      {clay::CursorTypes::kResizeleft, IDC_SIZEWE},\n      {clay::CursorTypes::kResizeleftright, IDC_SIZEWE},\n      {clay::CursorTypes::kResizeright, IDC_SIZEWE},\n      {clay::CursorTypes::kResizerow, IDC_SIZENS},\n      {clay::CursorTypes::kResizeup, IDC_SIZENS},\n      {clay::CursorTypes::kResizeupdown, IDC_SIZENS},\n      {clay::CursorTypes::kResizeupleft, IDC_SIZENWSE},\n      {clay::CursorTypes::kResizeupright, IDC_SIZENESW},\n      {clay::CursorTypes::kResizeupleftdownright, IDC_SIZENWSE},\n      {clay::CursorTypes::kResizeuprightdownleft, IDC_SIZENESW},\n      {clay::CursorTypes::kWait, IDC_WAIT},\n  };\n\n  const wchar_t* idc_name = IDC_ARROW;\n  auto it = cursors->find(cursor_type);\n  if (it != cursors->end()) {\n    idc_name = it->second;\n  }\n  return ::LoadCursor(nullptr, idc_name);\n}\n\n}  // namespace\n\nFlutterWindow::FlutterWindow(HWND parent_hwnd, int x, int y, int width,\n                             int height)\n    : binding_handler_delegate_(nullptr) {\n  Window::InitializeChild(\"FLUTTERVIEW\", parent_hwnd, x, y, width, height);\n  auto cursor = ::LoadCursor(nullptr, IDC_ARROW);\n  SetClassLongPtr(GetWindowHandle(), GCLP_HCURSOR,\n                  static_cast<LONG>(reinterpret_cast<LONG_PTR>(cursor)));\n}\n\nFlutterWindow::~FlutterWindow() {}\n\nvoid FlutterWindow::SetView(WindowBindingHandlerDelegate* window) {\n  binding_handler_delegate_ = window;\n  direct_manipulation_owner_->SetBindingHandlerDelegate(window);\n}\n\nWindowsRenderTarget FlutterWindow::GetRenderTarget() {\n  return WindowsRenderTarget(GetWindowHandle());\n}\n\nPlatformWindow FlutterWindow::GetPlatformWindow() { return GetWindowHandle(); }\n\nfloat FlutterWindow::GetDpiScale() {\n  return static_cast<float>(GetCurrentDPI()) / static_cast<float>(base_dpi);\n}\n\nbool FlutterWindow::IsVisible() { return IsWindowVisible(GetWindowHandle()); }\n\nPhysicalWindowBounds FlutterWindow::GetPhysicalWindowBounds() {\n  return {GetCurrentWidth(), GetCurrentHeight()};\n}\n\nvoid FlutterWindow::UpdateFlutterCursor(clay::CursorTypes cursor_type) {\n  auto cursor = GetCursorByType(cursor_type);\n  SetClassLongPtr(GetWindowHandle(), GCLP_HCURSOR,\n                  static_cast<LONG>(reinterpret_cast<LONG_PTR>(cursor)));\n}\n\nvoid FlutterWindow::SetFlutterCursor(HCURSOR cursor) {\n  SetClassLongPtr(GetWindowHandle(), GCLP_HCURSOR,\n                  static_cast<LONG>(reinterpret_cast<LONG_PTR>(cursor)));\n  ::SetCursor(cursor);\n}\n\nvoid FlutterWindow::OnWindowResized() {\n  // Blocking the raster thread until DWM flushes alleviates glitches where\n  // previous size surface is stretched over current size view.\n  DwmFlush();\n}\n\n// Translates button codes from Win32 API to ClayPointerMouseButtons.\nstatic uint64_t ConvertWinButtonToFlutterButton(UINT button) {\n  switch (button) {\n    case WM_LBUTTONDOWN:\n    case WM_LBUTTONUP:\n      return kClayPointerMouseButtonsMousePrimary;\n    case WM_RBUTTONDOWN:\n    case WM_RBUTTONUP:\n      return kClayPointerMouseButtonsMouseSecondary;\n    case WM_MBUTTONDOWN:\n    case WM_MBUTTONUP:\n      return kClayPointerMouseButtonsMouseMiddle;\n    case XBUTTON1:\n      return kClayPointerMouseButtonsMouseBack;\n    case XBUTTON2:\n      return kClayPointerMouseButtonsMouseForward;\n  }\n  FML_LOG(WARNING) << \"Mouse button not recognized: \" << button;\n  return 0;\n}\n\nvoid FlutterWindow::OnDpiScale(unsigned int dpi) {}\n\n// When DesktopWindow notifies that a WM_Size message has come in\n// lets FlutterEngine know about the new size.\nvoid FlutterWindow::OnResize(unsigned int width, unsigned int height) {\n  if (binding_handler_delegate_ != nullptr) {\n    binding_handler_delegate_->OnWindowSizeChanged(width, height);\n  }\n}\n\nvoid FlutterWindow::OnPaint() {\n  if (binding_handler_delegate_ != nullptr) {\n    binding_handler_delegate_->OnWindowRepaint();\n  }\n}\n\nvoid FlutterWindow::OnPointerMove(double x, double y,\n                                  ClayPointerDeviceKind device_kind,\n                                  int32_t device_id, int modifiers_state) {\n  binding_handler_delegate_->OnPointerMove(x, y, device_kind, device_id,\n                                           modifiers_state);\n}\n\nvoid FlutterWindow::OnPointerDown(double x, double y,\n                                  ClayPointerDeviceKind device_kind,\n                                  int32_t device_id, UINT button) {\n  uint64_t flutter_button = ConvertWinButtonToFlutterButton(button);\n  if (flutter_button != 0) {\n    binding_handler_delegate_->OnPointerDown(\n        x, y, device_kind, device_id,\n        static_cast<ClayPointerMouseButtons>(flutter_button));\n  }\n}\n\nvoid FlutterWindow::OnPointerUp(double x, double y,\n                                ClayPointerDeviceKind device_kind,\n                                int32_t device_id, UINT button) {\n  uint64_t flutter_button = ConvertWinButtonToFlutterButton(button);\n  if (flutter_button != 0) {\n    binding_handler_delegate_->OnPointerUp(\n        x, y, device_kind, device_id,\n        static_cast<ClayPointerMouseButtons>(flutter_button));\n  }\n}\n\nvoid FlutterWindow::OnPointerLeave(double x, double y,\n                                   ClayPointerDeviceKind device_kind,\n                                   int32_t device_id) {\n  binding_handler_delegate_->OnPointerLeave(x, y, device_kind, device_id);\n}\n\nvoid FlutterWindow::OnSetCursor() {\n  auto cursor = GetClassLongPtr(GetWindowHandle(), GCLP_HCURSOR);\n  ::SetCursor(reinterpret_cast<HCURSOR>(cursor));\n}\n\nvoid FlutterWindow::OnText(const std::u16string& text) {\n  binding_handler_delegate_->OnText(text);\n}\n\nvoid FlutterWindow::OnKey(int key, int scancode, int action, char32_t character,\n                          bool extended, bool was_down,\n                          KeyEventCallback callback) {\n  binding_handler_delegate_->OnKey(key, scancode, action, character, extended,\n                                   was_down, std::move(callback));\n}\n\nvoid FlutterWindow::OnComposeBegin() {\n  binding_handler_delegate_->OnComposeBegin();\n}\n\nvoid FlutterWindow::OnComposeCommit() {\n  binding_handler_delegate_->OnComposeCommit();\n}\n\nvoid FlutterWindow::OnComposeEnd() {\n  binding_handler_delegate_->OnComposeEnd();\n}\n\nvoid FlutterWindow::OnComposeChange(const std::u16string& text,\n                                    int cursor_pos) {\n  binding_handler_delegate_->OnComposeChange(text, cursor_pos);\n}\n\nvoid FlutterWindow::OnScroll(double delta_x, double delta_y,\n                             ClayPointerDeviceKind device_kind,\n                             int32_t device_id) {\n  POINT point;\n  GetCursorPos(&point);\n\n  ScreenToClient(GetWindowHandle(), &point);\n  binding_handler_delegate_->OnScroll(point.x, point.y, delta_x, delta_y,\n                                      GetScrollOffsetMultiplier(), device_kind,\n                                      device_id);\n}\n\nvoid FlutterWindow::OnCursorRectUpdated(const FloatRect& rect) {\n  // Convert the rect from Flutter logical coordinates to device coordinates.\n  auto scale = GetDpiScale();\n  FloatPoint origin(rect.left() * scale, rect.top() * scale);\n  FloatSize size(rect.width() * scale, rect.height() * scale);\n  UpdateCursorRect(FloatRect(origin, size));\n}\n\nvoid FlutterWindow::OnResetImeComposing() { AbortImeComposing(); }\n\nvoid FlutterWindow::OnTextInputClientChange(int client_id) {\n  UpdateTextInputClientFocused(client_id);\n}\n\nbool FlutterWindow::OnBitmapSurfaceUpdated(const void* allocation,\n                                           size_t row_bytes, size_t height) {\n  HDC dc = ::GetDC(GetWindowHandle());\n  BITMAPINFO bmi = {};\n  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);\n  bmi.bmiHeader.biWidth = row_bytes / 4;\n  bmi.bmiHeader.biHeight = -height;\n  bmi.bmiHeader.biPlanes = 1;\n  bmi.bmiHeader.biBitCount = 32;\n  bmi.bmiHeader.biCompression = BI_RGB;\n  bmi.bmiHeader.biSizeImage = 0;\n  int ret = SetDIBitsToDevice(dc, 0, 0, row_bytes / 4, height, 0, 0, 0, height,\n                              allocation, &bmi, DIB_RGB_COLORS);\n  ::ReleaseDC(GetWindowHandle(), dc);\n  return ret != 0;\n}\n\nPointerLocation FlutterWindow::GetPrimaryPointerLocation() {\n  POINT point;\n  GetCursorPos(&point);\n  ScreenToClient(GetWindowHandle(), &point);\n  return {(size_t)point.x, (size_t)point.y};\n}\n\nvoid FlutterWindow::OnThemeChange() {\n  binding_handler_delegate_->UpdateHighContrastEnabled(\n      GetHighContrastEnabled());\n}\n\nbool FlutterWindow::NeedsVSync() {\n  // If the Desktop Window Manager composition is enabled,\n  // the system itself synchronizes with v-sync.\n  // See: https://learn.microsoft.com/windows/win32/dwm/composition-ovw\n  BOOL composition_enabled;\n  if (SUCCEEDED(::DwmIsCompositionEnabled(&composition_enabled))) {\n    return !composition_enabled;\n  }\n\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/flutter_window.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOW_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOW_H_\n\n#include <iostream>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/shell/platform/windows/flutter_windows_view.h\"\n#include \"clay/shell/platform/windows/window.h\"\n#include \"clay/shell/platform/windows/window_binding_handler.h\"\n\nnamespace clay {\n\n// A win32 flutter child window used as implementations for flutter view.  In\n// the future, there will likely be a CoreWindow-based FlutterWindow as well.\n// At the point may make sense to dependency inject the native window rather\n// than inherit.\nclass FlutterWindow : public Window, public WindowBindingHandler {\n public:\n  // Create flutter Window for use as child window\n  FlutterWindow(HWND parent_hwnd, int x, int y, int width, int height);\n\n  virtual ~FlutterWindow();\n\n  // |Window|\n  void OnDpiScale(unsigned int dpi) override;\n\n  // |Window|\n  void OnResize(unsigned int width, unsigned int height) override;\n\n  // |Window|\n  void OnPaint() override;\n\n  // |Window|\n  void OnPointerMove(double x, double y, ClayPointerDeviceKind device_kind,\n                     int32_t device_id, int modifiers_state) override;\n\n  // |Window|\n  void OnPointerDown(double x, double y, ClayPointerDeviceKind device_kind,\n                     int32_t device_id, UINT button) override;\n\n  // |Window|\n  void OnPointerUp(double x, double y, ClayPointerDeviceKind device_kind,\n                   int32_t device_id, UINT button) override;\n\n  // |Window|\n  void OnPointerLeave(double x, double y, ClayPointerDeviceKind device_kind,\n                      int32_t device_id) override;\n\n  // |Window|\n  void OnSetCursor() override;\n\n  // |Window|\n  void OnText(const std::u16string& text) override;\n\n  // |Window|\n  void OnKey(int key, int scancode, int action, char32_t character,\n             bool extended, bool was_down, KeyEventCallback callback) override;\n\n  // |Window|\n  void OnComposeBegin() override;\n\n  // |Window|\n  void OnComposeCommit() override;\n\n  // |Window|\n  void OnComposeEnd() override;\n\n  // |Window|\n  void OnComposeChange(const std::u16string& text, int cursor_pos) override;\n\n  // |FlutterWindowBindingHandler|\n  void OnCursorRectUpdated(const FloatRect& rect) override;\n\n  // |FlutterWindowBindingHandler|\n  void OnResetImeComposing() override;\n\n  // |FlutterWindowBindingHandler|\n  void OnTextInputClientChange(int client_id) override;\n\n  // |Window|\n  void OnScroll(double delta_x, double delta_y,\n                ClayPointerDeviceKind device_kind, int32_t device_id) override;\n\n  // |FlutterWindowBindingHandler|\n  void SetView(WindowBindingHandlerDelegate* view) override;\n\n  // |FlutterWindowBindingHandler|\n  WindowsRenderTarget GetRenderTarget() override;\n\n  // |FlutterWindowBindingHandler|\n  PlatformWindow GetPlatformWindow() override;\n\n  // |FlutterWindowBindingHandler|\n  float GetDpiScale() override;\n\n  // |FlutterWindowBindingHandler|\n  bool IsVisible() override;\n\n  // |FlutterWindowBindingHandler|\n  PhysicalWindowBounds GetPhysicalWindowBounds() override;\n\n  // |FlutterWindowBindingHandler|\n  void UpdateFlutterCursor(clay::CursorTypes cursor_name) override;\n\n  // |FlutterWindowBindingHandler|\n  void SetFlutterCursor(HCURSOR cursor) override;\n\n  // |FlutterWindowBindingHandler|\n  void OnWindowResized() override;\n\n  // |FlutterWindowBindingHandler|\n  bool OnBitmapSurfaceUpdated(const void* allocation, size_t row_bytes,\n                              size_t height) override;\n\n  // |FlutterWindowBindingHandler|\n  PointerLocation GetPrimaryPointerLocation() override;\n\n  // |Window|\n  void OnThemeChange() override;\n\n  // |WindowBindingHandler|\n  bool NeedsVSync() override;\n\n private:\n  // A pointer to a FlutterWindowsView that can be used to update engine\n  // windowing and input state.\n  WindowBindingHandlerDelegate* binding_handler_delegate_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOW_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/flutter_windows_engine.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/flutter_windows_engine.h\"\n\n#include <dwmapi.h>\n\n#include <algorithm>\n#include <cmath>\n#include <filesystem>\n#include <iostream>\n#include <list>\n#include <memory>\n#include <sstream>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/paths.h\"\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n#include \"clay/shell/common/services/drag_drop_service.h\"\n#include \"clay/shell/common/switches.h\"\n#include \"clay/shell/platform/common/path_utils.h\"\n#include \"clay/shell/platform/windows/flutter_windows_view.h\"\n#include \"clay/shell/platform/windows/task_runner.h\"\n#include \"clay/shell/platform/windows/text_input_plugin.h\"\n#include \"clay/ui/common/isolate.h\"\n\n// winbase.h defines GetCurrentTime as a macro.\n#undef GetCurrentTime\n\nnamespace clay {\n\nnamespace {\n\n// Lifted from vsync_waiter_fallback.cc\nstatic int64_t SnapToNextTick(int64_t value, int64_t tick_phase,\n                              int64_t tick_interval) {\n  int64_t offset = (tick_phase - value) % tick_interval;\n  if (offset != 0) offset = offset + tick_interval;\n  return value + offset;\n}\n\n}  // namespace\n\nFlutterWindowsEngine::FlutterWindowsEngine(const FlutterProjectBundle& project)\n    : PlatformViewEmbedderDelegate(),\n      project_(std::make_unique<FlutterProjectBundle>(project)) {\n  task_runner_ =\n      std::make_unique<TaskRunner>(\n          []() -> uint64_t {\n            return fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();\n          },\n          [this](const auto* task) {\n            if (!engine_) {\n              FML_LOG(ERROR)\n                  << \"Cannot post an engine task when engine is not running.\";\n              return;\n            }\n            if (!engine_->RunTask(task)) {\n              FML_LOG(ERROR) << \"Failed to post an engine task.\";\n            }\n          });\n\n  if (!project_->use_software_rendering()) {\n    egl_manager_ = egl::Manager::Create();\n  }\n  window_proc_delegate_manager_ = std::make_unique<WindowProcDelegateManager>();\n  window_proc_delegate_manager_->RegisterTopLevelWindowProcDelegate(\n      [](HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar, void* user_data,\n         LRESULT* result) -> bool {\n        FML_DCHECK(user_data);\n        FlutterWindowsEngine* that =\n            static_cast<FlutterWindowsEngine*>(user_data);\n        if (msg == WM_DWMCOMPOSITIONCHANGED) {\n          // DWM composition can be disabled on Windows 7.\n          // Notify the engine as this can result in screen tearing.\n          that->OnDwmCompositionChanged();\n          return true;\n        }\n        return false;\n      },\n      static_cast<void*>(this));\n\n  cursor_handler_ = std::make_unique<CursorHandler>(this);\n  platform_handler_ = std::make_unique<PlatformHandler>(this);\n}\n\nFlutterWindowsEngine::~FlutterWindowsEngine() { Stop(); }\n\nvoid FlutterWindowsEngine::SetSwitches(\n    const std::vector<std::string>& switches) {\n  project_->SetSwitches(switches);\n}\n\nbool FlutterWindowsEngine::Run() { return Run(\"\"); }\n\nbool FlutterWindowsEngine::Run(std::string_view entrypoint) {\n  if (!project_->HasValidPaths()) {\n    FML_LOG(ERROR) << \"Missing or unresolvable paths to assets.\";\n    return false;\n  }\n  std::string icu_path_string = project_->icu_path().u8string();\n\n  // FlutterProjectArgs is expecting a full argv, so when processing it for\n  // flags the first item is treated as the executable and ignored. Add a dummy\n  // value so that all provided arguments are used.\n  std::string executable_name = GetExecutableName();\n  std::vector<const char*> argv = {executable_name.c_str()};\n  std::vector<std::string> switches = project_->GetSwitches();\n  std::transform(\n      switches.begin(), switches.end(), std::back_inserter(argv),\n      [](const std::string& arg) -> const char* { return arg.c_str(); });\n\n  fml::CommandLine command_line;\n  if (!argv.empty()) {\n    command_line = fml::CommandLineFromArgcArgv(argv.size(), argv.data());\n  }\n  clay::Settings settings = clay::SettingsFromCommandLine(command_line);\n  settings.icu_data_path = icu_path_string;\n  settings.enable_default_focus_ring = false;\n  settings.enable_performance_overlay = false;\n\n  // Configure task runners.\n  ClayTaskRunnerDescription platform_task_runner = {};\n  platform_task_runner.struct_size = sizeof(ClayTaskRunnerDescription);\n  platform_task_runner.user_data = task_runner_.get();\n  platform_task_runner.runs_task_on_current_thread_callback =\n      [](void* user_data) -> bool {\n    return static_cast<TaskRunner*>(user_data)->RunsTasksOnCurrentThread();\n  };\n  platform_task_runner.post_task_callback =\n      [](ClayTask task, uint64_t target_time_nanos, void* user_data) -> void {\n    static_cast<TaskRunner*>(user_data)->PostClayTask(task, target_time_nanos);\n  };\n  ClayCustomTaskRunners custom_task_runners = {};\n  custom_task_runners.struct_size = sizeof(ClayCustomTaskRunners);\n  custom_task_runners.platform_task_runner = &platform_task_runner;\n  custom_task_runners.thread_priority_setter =\n      &WindowsPlatformThreadPrioritySetter;\n\n  clay::PlatformViewEmbedder::PlatformDispatchTable platform_dispatch_table =\n      {};\n  platform_dispatch_table.on_pre_engine_restart_callback = [this]() {\n    OnPreEngineRestart();\n  };\n\n  platform_dispatch_table.clipboard.set_clipboard_data_callback =\n      [this](const std::u16string& data) {\n        std::string u8string = lynx::base::U16StringToU8(data);\n        platform_handler_->SetPlainText(u8string);\n      };\n  platform_dispatch_table.clipboard.get_clipboard_data_callback = [this]() {\n    return lynx::base::U8StringToU16(platform_handler_->GetPlainText());\n  };\n  platform_dispatch_table.textinput.set_text_input_client_callback =\n      [this](int client_id, const char* input_action, const char* input_type) {\n        if (text_input_plugin_) {\n          text_input_plugin_->SetTextInputClient(client_id, input_action,\n                                                 input_type);\n        }\n      };\n  platform_dispatch_table.textinput.clear_text_input_client_callback =\n      [this]() {\n        if (text_input_plugin_) {\n          text_input_plugin_->ClearTextInputClient();\n        }\n      };\n  platform_dispatch_table.textinput.set_editable_transform_callback =\n      [this](const float transform[16]) {\n        if (text_input_plugin_) {\n          text_input_plugin_->SetEditableTransform(transform);\n        }\n      };\n  platform_dispatch_table.textinput.set_editing_state_callback =\n      [this](uint64_t selection_base, uint64_t composing_extent,\n             const std::string& selection_affinity, const std::string& text,\n             bool selection_directional, uint64_t selection_extent,\n             uint64_t composing_base) {\n        if (text_input_plugin_) {\n          text_input_plugin_->SetEditingState(\n              selection_base, composing_extent, selection_affinity.c_str(),\n              text.c_str(), selection_directional, selection_extent,\n              composing_base);\n        }\n      };\n  platform_dispatch_table.textinput.set_caret_rect_callback =\n      [this](float x, float y, float width, float height) {\n        // INFO: this method is not implemented in textInputPlugin yet.\n      };\n  platform_dispatch_table.textinput.set_marked_text_rect_callback =\n      [this](float x, float y, float width, float height) {\n        if (text_input_plugin_) {\n          text_input_plugin_->SetMarkedTextRect(x, y, width, height);\n        }\n      };\n  platform_dispatch_table.textinput.show_text_input_callback = [this]() {\n    if (text_input_plugin_) {\n      text_input_plugin_->ShowTextInput();\n    }\n  };\n  platform_dispatch_table.textinput.hide_text_input_callback = [this]() {\n    if (text_input_plugin_) {\n      text_input_plugin_->HideTextInput();\n    }\n  };\n  platform_dispatch_table.textinput.input_filter_callback =\n      [this](const std::string& input,\n             const std::string& pattern) -> std::string {\n    if (text_input_plugin_) {\n      return text_input_plugin_->FilterInput(input, pattern);\n    } else {\n      return input;\n    }\n  };\n  platform_dispatch_table.window_move_callback = [this]() {\n    if (view_) {\n      view_->MoveWindow();\n    }\n  };\n  platform_dispatch_table.activate_system_cursor_callback =\n      [this](int type, const std::string& path) {\n        if (cursor_handler_) {\n          cursor_handler_->ActivateSystemCursor(type, path.c_str());\n        }\n      };\n\n  if (egl_manager()) {\n    engine_ = clay::EmbedderEngine::CreateEngine(\n        settings, &custom_task_runners, (GPUSurfaceGLDelegate*)this,\n        platform_dispatch_table, this, this);\n  } else {\n    engine_ = clay::EmbedderEngine::CreateEngine(\n        settings, &custom_task_runners, (EmbedderSurfaceSoftwareDelegate*)this,\n        platform_dispatch_table, this, this);\n  }\n\n  if (!engine_) {\n    FML_LOG(ERROR) << \"Failed to start Flutter engine\";\n    return false;\n  }\n  // Step 1: Launch the shell.\n  if (!engine_->LaunchShell()) {\n    FML_LOG(ERROR) << \"Could not launch the engine using supplied \"\n                      \"initialization arguments.\";\n    return false;\n  }\n\n  // Step 2: Tell the platform view to initialize itself.\n  if (!engine_->NotifyCreated()) {\n    FML_LOG(ERROR) << \"Could not create platform view components.\";\n    return false;\n  }\n  service_manager_ = engine_->GetServiceManager();\n  if (!service_manager_) {\n    FML_LOG(ERROR) << \"Failed to get clay service manager\";\n    return false;\n  }\n\n  // Configure device frame rate displayed via devtools.\n  std::vector<std::unique_ptr<clay::Display>> displays;\n  displays.push_back(std::make_unique<clay::Display>(\n      0, round(1.0 / (static_cast<double>(FrameInterval()) / 1000000000.0))));\n  engine_->GetShell().OnDisplayUpdates(clay::DisplayUpdateType::kStartup,\n                                       std::move(displays));\n\n  SendSystemLocales();\n\n  // settings_plugin_->StartWatching();\n  // settings_plugin_->SendSettings();\n\n  return true;\n}\n\nbool FlutterWindowsEngine::Stop() {\n  if (engine_) {\n    engine_->NotifyDestroyed();\n    engine_->CollectShell();\n    engine_ = nullptr;\n    return true;\n  }\n  return false;\n}\n\nvoid FlutterWindowsEngine::SetView(FlutterWindowsView* view) {\n  view_ = view;\n  InitializeKeyboard();\n}\n\nvoid FlutterWindowsEngine::OnVsync(intptr_t baton) {\n  int64_t current_time = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds();\n  int64_t frame_interval = FrameInterval();\n  auto next = SnapToNextTick(current_time, start_time_, frame_interval);\n  auto start_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(next));\n  auto target_time = fml::TimePoint::FromEpochDelta(\n      fml::TimeDelta::FromNanoseconds(next + frame_interval));\n  engine_->OnVsyncEvent(baton, start_time, target_time);\n}\n\nvoid FlutterWindowsEngine::InitializeKeyboard() {\n  if (view_ == nullptr) {\n    FML_LOG(ERROR) << \"Cannot initialize keyboard on Windows headless mode.\";\n  }\n\n  KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = GetKeyState;\n  KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan =\n      [](UINT virtual_key, bool extended) {\n        return MapVirtualKey(virtual_key,\n                             extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC);\n      };\n  keyboard_key_handler_ =\n      std::move(CreateKeyboardKeyHandler(get_key_state, map_vk_to_scan));\n  text_input_plugin_ = std::move(CreateTextInputPlugin());\n}\n\nstd::unique_ptr<KeyboardHandlerBase>\nFlutterWindowsEngine::CreateKeyboardKeyHandler(\n    KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state,\n    KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) {\n  auto send_event = [this](const ClayKeyEvent& event,\n                           ClayKeyEventCallback callback, void* user_data) {\n    return SendKeyEvent(event, callback, user_data);\n  };\n  auto keyboard_key_handler = std::make_unique<KeyboardKeyHandler>();\n  keyboard_key_handler->AddDelegate(\n      std::make_unique<KeyboardKeyEmbedderHandler>(send_event, get_key_state,\n                                                   map_vk_to_scan));\n  return keyboard_key_handler;\n}\n\nstd::unique_ptr<TextInputPlugin> FlutterWindowsEngine::CreateTextInputPlugin() {\n  return std::make_unique<TextInputPlugin>(view_, this);\n}\n\nint64_t FlutterWindowsEngine::FrameInterval() {\n  int64_t interval = 16600000;\n\n  DWM_TIMING_INFO timing_info = {};\n  timing_info.cbSize = sizeof(timing_info);\n  HRESULT result = DwmGetCompositionTimingInfo(NULL, &timing_info);\n  if (result == S_OK && timing_info.rateRefresh.uiDenominator > 0 &&\n      timing_info.rateRefresh.uiNumerator > 0) {\n    interval = static_cast<double>(timing_info.rateRefresh.uiDenominator *\n                                   1000000000.0) /\n               static_cast<double>(timing_info.rateRefresh.uiNumerator);\n  }\n\n  return interval;\n}\n\nvoid FlutterWindowsEngine::SendWindowMetricsEvent(\n    const clay::ViewportMetrics& metrics) {\n  if (engine_) {\n    engine_->SetViewportMetrics(metrics);\n  }\n}\n\nvoid FlutterWindowsEngine::SendPointerEvent(const ClayPointerEvent& event) {\n  if (engine_) {\n    engine_->SendPointerEvent(&event, 1);\n  }\n}\n\nvoid FlutterWindowsEngine::SendKeyEvent(const ClayKeyEvent& event,\n                                        ClayKeyEventCallback callback,\n                                        void* user_data) {\n  if (engine_) {\n    engine_->SendKeyEvent(&event, callback, user_data);\n  }\n}\n\nvoid FlutterWindowsEngine::ReloadSystemFonts() {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->ReloadSystemFonts();\n}\n\nvoid* FlutterWindowsEngine::GetViewContext() {\n  if (engine_ == nullptr) {\n    return nullptr;\n  }\n  return engine_->GetShell().GetEngine()->GetViewContext();\n}\n\nvoid FlutterWindowsEngine::ScheduleFrame() {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->RequestPaint();\n}\n\nvoid FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) {\n  if (engine_ == nullptr) {\n    return;\n  }\n  fml::WeakPtr<clay::PlatformView> weak_platform_view =\n      engine_->GetShell().GetPlatformView();\n  if (!weak_platform_view) {\n    return;\n  }\n  weak_platform_view->SetNextFrameCallback(\n      [this, callback = std::move(callback)]() {\n        task_runner_->PostTask(std::move(callback));\n      });\n}\n\nvoid FlutterWindowsEngine::SendSystemLocales() {}\n\nbool FlutterWindowsEngine::PostRasterThreadTask(fml::closure callback) const {\n  struct Captures {\n    fml::closure callback;\n  };\n  auto captures = new Captures();\n  captures->callback = std::move(callback);\n  if (engine_) {\n    auto task = [captures]() {\n      captures->callback();\n      delete captures;\n    };\n    if (engine_->PostRenderThreadTask(std::move(task))) {\n      return true;\n    }\n  }\n  delete captures;\n  return false;\n}\n\nvoid FlutterWindowsEngine::OnPreEngineRestart() {\n  // Reset the keyboard's state on hot restart.\n  if (view_) {\n    InitializeKeyboard();\n  }\n}\n\nstd::string FlutterWindowsEngine::GetExecutableName() const {\n  std::pair<bool, std::string> result = fml::paths::GetExecutablePath();\n  if (result.first) {\n    const std::string& executable_path = result.second;\n    size_t last_separator = executable_path.find_last_of(\"/\\\\\");\n    if (last_separator == std::string::npos ||\n        last_separator == executable_path.size() - 1) {\n      return executable_path;\n    }\n    return executable_path.substr(last_separator + 1);\n  }\n  return \"Flutter\";\n}\n\nvoid FlutterWindowsEngine::UpdateHighContrastEnabled(bool enabled) {\n  // high_contrast_enabled_ = enabled;\n  // int flags = EnabledAccessibilityFeatures();\n  // if (enabled) {\n  //   flags |=\n  //       FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast;\n  // } else {\n  //   flags &=\n  //       ~FlutterAccessibilityFeature::kFlutterAccessibilityFeatureHighContrast;\n  // }\n  // UpdateAccessibilityFeatures(static_cast<FlutterAccessibilityFeature>(flags));\n}\n\nvoid FlutterWindowsEngine::EnableDefaultFocusRing() {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->SetDefaultFocusRingEnabled(true);\n}\nvoid FlutterWindowsEngine::EnablePerformanceOverlay() {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->SetPerformanceOverlayEnabled(true);\n}\n\nvoid FlutterWindowsEngine::OnEnterForeground() {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->OnEnterForeground();\n}\n\nvoid FlutterWindowsEngine::OnEnterBackground() {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->OnEnterBackground();\n}\n\nvoid FlutterWindowsEngine::RequestPaint() {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->RequestPaint();\n}\n\nvoid FlutterWindowsEngine::SetVisible(bool enable) {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->SetVisible(enable);\n}\n\nbool FlutterWindowsEngine::PostUIThreadTask(\n    const fml::closure& callback) const {\n  struct Captures {\n    fml::closure callback;\n  };\n  auto captures = new Captures();\n  captures->callback = std::move(callback);\n  if (engine_) {\n    auto task = [captures]() {\n      captures->callback();\n      delete captures;\n    };\n    if (engine_->PostUIThreadTask(std::move(task))) {\n      return true;\n    }\n  }\n  delete captures;\n  return false;\n}\n\nvoid FlutterWindowsEngine::SetFontFaceCache(const char* font_family,\n                                            const char* local_path) {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->SetFontFaceCache(font_family, local_path);\n}\n\nstd::string FlutterWindowsEngine::ShouldInterceptUrl(\n    const std::string& origin_url, bool should_decode) {\n  if (platform_view_delegate_) {\n    return platform_view_delegate_->ShouldInterceptUrl(origin_url,\n                                                       should_decode);\n  }\n  return origin_url;\n}\n\nstd::shared_ptr<clay::ResourceLoaderIntercept>\nFlutterWindowsEngine::GetResourceLoaderIntercept() {\n  if (platform_view_delegate_) {\n    return platform_view_delegate_->GetResourceLoaderIntercept();\n  }\n  return nullptr;\n}\n\nvoid FlutterWindowsEngine::OnDwmCompositionChanged() {\n  view_->OnDwmCompositionChanged();\n}\n\nvoid FlutterWindowsEngine::UpdateEditState(\n    int client_id, uint64_t selection_base, uint64_t composing_extent,\n    const char* selection_affinity, const char* text, uint64_t selection_extent,\n    uint64_t composing_base) {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->GetPageView()->OnPlatformUpdateEditState(\n      client_id, selection_base, composing_extent, selection_affinity, text,\n      selection_extent, composing_base);\n}\n\nvoid FlutterWindowsEngine::PerformInputAction(int client_id) {\n  if (engine_ == nullptr) {\n    return;\n  }\n  engine_->GetShell().GetEngine()->GetPageView()->OnPlatformPerformInputAction(\n      client_id);\n}\n\nvoid FlutterWindowsEngine::PerformDragEnterAndOver(FloatPoint point) {\n  if (engine_ == nullptr) {\n    return;\n  }\n  clay::Puppet<clay::Owner::kPlatform, clay::DragDropService>\n      drag_drop_service = service_manager_->GetService<DragDropService>();\n  drag_drop_service.Act(\n      [point](auto& impl) { impl.OnPlatformDragEnterAndOver(point); });\n}\n\nvoid FlutterWindowsEngine::PerformDragLeave() {\n  if (engine_ == nullptr) {\n    return;\n  }\n  clay::Puppet<clay::Owner::kPlatform, clay::DragDropService>\n      drag_drop_service = service_manager_->GetService<DragDropService>();\n  drag_drop_service.Act([](auto& impl) { impl.OnPlatformDragLeave(); });\n}\n\nvoid FlutterWindowsEngine::PerformDragDrop(\n    FloatPoint point, std::string drag_type, std::string content_text,\n    const std::list<std::unordered_map<std::string, std::string>>&\n        content_files) {\n  if (engine_ == nullptr) {\n    return;\n  }\n  clay::Puppet<clay::Owner::kPlatform, clay::DragDropService>\n      drag_drop_service = service_manager_->GetService<DragDropService>();\n  drag_drop_service.Act(\n      [point, drag_type, content_text, content_files](auto& impl) {\n        impl.OnPlatformDragDrop(point, drag_type, content_text, content_files);\n      });\n}\n\nstd::unique_ptr<GLContextResult> FlutterWindowsEngine::GLContextMakeCurrent() {\n  if (!view_) {\n    return std::make_unique<GLContextDefaultResult>(false);\n  }\n  return std::make_unique<GLContextDefaultResult>(view_->MakeCurrent());\n}\n\nbool FlutterWindowsEngine::GLContextClearCurrent() {\n  if (!egl_manager()) {\n    return false;\n  }\n  return egl_manager()->render_context()->ClearCurrent();\n}\n\nvoid FlutterWindowsEngine::GLContextSetDamageRegion(\n    const std::optional<skity::Rect>& region) {\n  if (!region.has_value() || !view_) {\n    return;\n  }\n  skity::Rect damage_rect = region.value();\n  int left = static_cast<int>(std::floor(damage_rect.Left()));\n  int top = static_cast<int>(std::floor(damage_rect.Top()));\n  int right = static_cast<int>(std::ceil(damage_rect.Right()));\n  int bottom = static_cast<int>(std::ceil(damage_rect.Bottom()));\n  view_->SetDamageRegion({left, top, right - left, bottom - top});\n}\n\nbool FlutterWindowsEngine::GLContextPresent(const GLPresentInfo& present_info) {\n  if (!view_) {\n    return false;\n  }\n  return view_->SwapBuffers();\n}\n\nGLFBOInfo FlutterWindowsEngine::GLContextFBO(GLFrameInfo frame_info) const {\n  if (!view_) {\n    return {.fbo_id = kWindowFrameBufferID, .existing_damage = std::nullopt};\n  }\n  auto damage_rect = view_->GetDamageRegion();\n  return {\n      .fbo_id = view_->GetFrameBufferId(frame_info.width, frame_info.height),\n      .existing_damage = damage_rect};\n}\n\nbool FlutterWindowsEngine::GLContextFBOResetAfterPresent() const {\n  return true;\n}\n\nGPUSurfaceGLDelegate::GLProcResolver FlutterWindowsEngine::GetGLProcResolver()\n    const {\n  return [](const char* name) -> void* {\n    return reinterpret_cast<void*>(eglGetProcAddress(name));\n  };\n}\n\nSurfaceFrame::FramebufferInfo FlutterWindowsEngine::GLContextFramebufferInfo()\n    const {\n  auto info = SurfaceFrame::FramebufferInfo{};\n  info.supports_readback = true;\n  info.supports_partial_repaint = true;\n  // Unspecified damage (nullopt) invokes full-frame rasterization (no partial\n  // redraw); use an empty skity::Rect to indicate no existing damage.\n  info.existing_damage = skity::Rect();\n  return info;\n}\n\nbool FlutterWindowsEngine::OnPresentBackingStore(const void* allocation,\n                                                 size_t row_bytes,\n                                                 size_t height) {\n  if (view_) {\n    return view_->PresentSoftwareBitmap(allocation, row_bytes, height);\n  }\n  return false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/flutter_windows_engine.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOWS_ENGINE_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOWS_ENGINE_H_\n\n#include <chrono>\n#include <list>\n#include <map>\n#include <memory>\n#include <optional>\n#include <string>\n#include <string_view>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/closure.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/shell/platform/embedder/embedder_engine.h\"\n#include \"clay/shell/platform/embedder/embedder_surface_gl.h\"\n#include \"clay/shell/platform/embedder/embedder_surface_software.h\"\n#include \"clay/shell/platform/embedder/platform_view_embedder_delegate.h\"\n#include \"clay/shell/platform/windows/cursor_handler.h\"\n#include \"clay/shell/platform/windows/egl/manager.h\"\n#include \"clay/shell/platform/windows/flutter_project_bundle.h\"\n#include \"clay/shell/platform/windows/keyboard_handler_base.h\"\n#include \"clay/shell/platform/windows/keyboard_key_embedder_handler.h\"\n#include \"clay/shell/platform/windows/platform_handler.h\"\n#include \"clay/shell/platform/windows/task_runner.h\"\n#include \"clay/shell/platform/windows/text_input_plugin.h\"\n#include \"clay/shell/platform/windows/window_proc_delegate_manager.h\"\n#include \"third_party/rapidjson/document.h\"\n\nnamespace clay {\n\nclass FlutterWindowsView;\n\n// Update the thread priority for the Windows engine.\nstatic void WindowsPlatformThreadPrioritySetter(\n    fml::Thread::ThreadPriority priority) {\n  // TODO(99502): Add support for tracing to the windows embedding so we can\n  // mark thread priorities and success/failure.\n  switch (priority) {\n    case fml::Thread::ThreadPriority::LOW:\n    case fml::Thread::ThreadPriority::BACKGROUND: {\n      SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);\n      break;\n    }\n    case fml::Thread::ThreadPriority::HIGH: {\n      SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);\n      break;\n    }\n    case fml::Thread::ThreadPriority::NORMAL: {\n      // For normal or default priority we do not need to set the priority\n      // class.\n      break;\n    }\n  }\n}\n\n// Manages state associated with the underlying FlutterEngine that isn't\n// related to its display.\n//\n// In most cases this will be associated with a FlutterView, but if not will\n// run in headless mode.\nclass FlutterWindowsEngine : public PlatformViewEmbedderDelegate,\n                             public GPUSurfaceGLDelegate,\n                             public EmbedderSurfaceSoftwareDelegate {\n public:\n  // Creates a new Flutter engine object configured to run |project|.\n  explicit FlutterWindowsEngine(const FlutterProjectBundle& project);\n\n  virtual ~FlutterWindowsEngine();\n\n  // Prevent copying.\n  FlutterWindowsEngine(FlutterWindowsEngine const&) = delete;\n  FlutterWindowsEngine& operator=(FlutterWindowsEngine const&) = delete;\n\n  // Starts running the entrypoint function specifed in the project bundle. If\n  // unspecified, defaults to main().\n  //\n  // Returns false if the engine couldn't be started.\n  bool Run();\n\n  // Starts running the engine with the given entrypoint. If the empty string\n  // is specified, defaults to the entrypoint function specified in the project\n  // bundle, or main() if both are unspecified.\n  //\n  // Returns false if the engine couldn't be started or if conflicting,\n  // non-default values are passed here and in the project bundle..\n  //\n  // DEPRECATED: Prefer setting the entrypoint in the FlutterProjectBundle\n  // passed to the constructor and calling the no-parameter overload.\n  bool Run(std::string_view entrypoint);\n\n  // Returns true if the engine is currently running.\n  bool running() const { return engine_ != nullptr; }\n\n  // Stops the engine. This invalidates the pointer returned by engine().\n  //\n  // Returns false if stopping the engine fails, or if it was not running.\n  bool Stop();\n\n  // Sets the view that is displaying this engine's content.\n  void SetView(FlutterWindowsView* view);\n\n  // The view displaying this engine's content, if any. This will be null for\n  // headless engines.\n  FlutterWindowsView* view() { return view_; }\n\n  // Sets switches member to the given switches.\n  void SetSwitches(const std::vector<std::string>& switches);\n\n  TaskRunner* task_runner() { return task_runner_.get(); }\n\n  // The EGL manager object. If this is nullptr, then we are\n  // rendering using software instead of OpenGL.\n  egl::Manager* egl_manager() const { return egl_manager_.get(); }\n\n  WindowProcDelegateManager* window_proc_delegate_manager() {\n    return window_proc_delegate_manager_.get();\n  }\n\n  // Informs the engine that the window metrics have changed.\n  void SendWindowMetricsEvent(const clay::ViewportMetrics& metrics);\n\n  // Informs the engine of an incoming pointer event.\n  void SendPointerEvent(const ClayPointerEvent& event);\n\n  // Informs the engine of an incoming key event.\n  void SendKeyEvent(const ClayKeyEvent& event, ClayKeyEventCallback callback,\n                    void* user_data);\n\n  KeyboardHandlerBase* keyboard_key_handler() {\n    return keyboard_key_handler_.get();\n  }\n  TextInputPlugin* text_input_plugin() { return text_input_plugin_.get(); }\n\n  // Informs the engine that the system font list has changed.\n  void ReloadSystemFonts();\n\n  // Informs the engine that a new frame is needed to redraw the content.\n  void ScheduleFrame();\n  void* GetViewContext();\n  void EnableDefaultFocusRing();\n  void EnablePerformanceOverlay();\n  // Set the callback that is called when the next frame is drawn.\n  void SetNextFrameCallback(fml::closure callback);\n\n  // Posts the given callback onto the raster thread.\n  bool PostRasterThreadTask(fml::closure callback) const;\n  // Posts the given callback onto the ui thread.\n  bool PostUIThreadTask(const fml::closure& callback) const;\n\n  // Invoke on the embedder's vsync callback to schedule a frame.\n  void OnVsync(intptr_t baton);\n\n  // Update the high contrast feature state.\n  void UpdateHighContrastEnabled(bool enabled);\n\n  // Returns true if the high contrast feature is enabled.\n  bool high_contrast_enabled() const { return high_contrast_enabled_; }\n\n  // Returns the executable name for this process or \"Flutter\" if unknown.\n  std::string GetExecutableName() const;\n\n  void OnEnterForeground();\n  void OnEnterBackground();\n  void RequestPaint();\n\n  void SetVisible(bool enable);\n  void SetFontFaceCache(const char* font_family, const char* local_path);\n  std::string ShouldInterceptUrl(const std::string& origin_url,\n                                 bool should_decode) override;\n  std::shared_ptr<clay::ResourceLoaderIntercept> GetResourceLoaderIntercept()\n      override;\n  void SetPlatformViewEmbedderDelegate(PlatformViewEmbedderDelegate* delegate) {\n    platform_view_delegate_ = delegate;\n  }\n\n  const std::shared_ptr<clay::ServiceManager>& GetServiceManager() const {\n    return service_manager_;\n  }\n\n  // Called when a WM_DWMCOMPOSITIONCHANGED message is received.\n  void OnDwmCompositionChanged();\n\n  void UpdateEditState(int client_id, uint64_t selection_base,\n                       uint64_t composing_extent,\n                       const char* selection_affinity, const char* text,\n                       uint64_t selection_extent, uint64_t composing_base);\n  void PerformInputAction(int client_id);\n\n  void PerformDragEnterAndOver(FloatPoint point);\n  void PerformDragLeave();\n  void PerformDragDrop(\n      FloatPoint point, std::string drag_type, std::string content_text,\n      const std::list<std::unordered_map<std::string, std::string>>&\n          content_files);\n\n protected:\n  // Creates the keyboard key handler.\n  //\n  // Exposing this method allows unit tests to override in order to\n  // capture information.\n  virtual std::unique_ptr<KeyboardHandlerBase> CreateKeyboardKeyHandler(\n      KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state,\n      KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan);\n\n  // Creates the text input plugin.\n  //\n  // Exposing this method allows unit tests to override in order to\n  // capture information.\n  virtual std::unique_ptr<TextInputPlugin> CreateTextInputPlugin();\n\n  // Invoked by the engine right before the engine is restarted.\n  //\n  // This should reset necessary states to as if the engine has just been\n  // created. This is typically caused by a hot restart (Shift-R in CLI.)\n  void OnPreEngineRestart();\n\n  // |GPUSurfaceGLDelegate|\n  std::unique_ptr<GLContextResult> GLContextMakeCurrent() override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextClearCurrent() override;\n\n  // |GPUSurfaceGLDelegate|\n  void GLContextSetDamageRegion(\n      const std::optional<skity::Rect>& region) override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextPresent(const GLPresentInfo& present_info) override;\n\n  // |GPUSurfaceGLDelegate|\n  GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override;\n\n  // |GPUSurfaceGLDelegate|\n  bool GLContextFBOResetAfterPresent() const override;\n\n  // |GPUSurfaceGLDelegate|\n  GLProcResolver GetGLProcResolver() const override;\n\n  // |GPUSurfaceGLDelegate|\n  SurfaceFrame::FramebufferInfo GLContextFramebufferInfo() const override;\n\n  // |EmbedderSurfaceSoftwareDelegate|\n  bool OnPresentBackingStore(const void* allocation, size_t row_bytes,\n                             size_t height) override;\n\n private:\n  // Sends system locales to the engine.\n  //\n  // Should be called just after the engine is run, and after any relevant\n  // system changes.\n  void SendSystemLocales();\n\n  // Create the keyboard & text input sub-systems.\n  //\n  // This requires that a view is attached to the engine.\n  // Calling this method again resets the keyboard state.\n  void InitializeKeyboard();\n\n  std::unique_ptr<EmbedderEngine> engine_;\n\n  std::unique_ptr<FlutterProjectBundle> project_;\n\n  // The view displaying the content running in this engine, if any.\n  FlutterWindowsView* view_ = nullptr;\n\n  // Task runner for tasks posted from the engine.\n  std::unique_ptr<TaskRunner> task_runner_;\n\n  // An object used for intializing Angle and creating / destroying render\n  // surfaces. Surface creation functionality requires a valid render_target.\n  // May be nullptr if ANGLE failed to initialize.\n  std::unique_ptr<egl::Manager> egl_manager_;\n\n  // Handler for cursor events.\n  std::unique_ptr<CursorHandler> cursor_handler_;\n\n  // Handler for the flutter/platform.\n  std::unique_ptr<PlatformHandler> platform_handler_;\n\n  // Handlers for keyboard events from Windows.\n  std::unique_ptr<KeyboardHandlerBase> keyboard_key_handler_;\n\n  // Handlers for text events from Windows.\n  std::unique_ptr<TextInputPlugin> text_input_plugin_;\n\n  // The approximate time between vblank events.\n  int64_t FrameInterval();\n\n  // The start time used to align frames.\n  int64_t start_time_ = 0;\n\n  bool high_contrast_enabled_ = false;\n\n  // The manager for WindowProc delegate registration and callbacks.\n  std::unique_ptr<WindowProcDelegateManager> window_proc_delegate_manager_;\n\n  // The on frame drawn callback.\n  fml::closure next_frame_callback_;\n\n  PlatformViewEmbedderDelegate* platform_view_delegate_ = nullptr;\n\n  std::shared_ptr<clay::ServiceManager> service_manager_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOWS_ENGINE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/flutter_windows_view.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/flutter_windows_view.h\"\n\n#include <chrono>\n\n#include \"clay/ui/platform/cursor_types.h\"\nnamespace clay {\n\nnamespace {\n// The maximum duration to block the platform thread for while waiting\n// for a window resize operation to complete.\nconstexpr std::chrono::milliseconds kWindowResizeTimeout{100};\n\n/// Returns true if the surface will be updated as part of the resize process.\n///\n/// This is called on window resize to determine if the platform thread needs\n/// to be blocked until the frame with the right size has been rendered. It\n/// should be kept in-sync with how the engine deals with a new surface request\n/// as seen in `CreateOrUpdateSurface` in `GPUSurfaceGL`.\nbool SurfaceWillUpdate(size_t cur_width, size_t cur_height, size_t target_width,\n                       size_t target_height) {\n  // TODO (https://github.com/flutter/flutter/issues/65061) : Avoid special\n  // handling for zero dimensions.\n  bool non_zero_target_dims = target_height > 0 && target_width > 0;\n  bool not_same_size =\n      (cur_height != target_height) || (cur_width != target_width);\n  return non_zero_target_dims && not_same_size;\n}\n\n/// Update the surface's swap interval to block until the v-blank iff\n/// the system compositor is disabled.\nvoid UpdateVsync(FlutterWindowsEngine* engine, egl::WindowSurface* surface,\n                 bool needs_vsync) {\n  egl::Manager* egl_manager = engine->egl_manager();\n  if (!egl_manager) {\n    return;\n  }\n\n  auto update_vsync = [egl_manager, surface, needs_vsync]() {\n    if (!surface || !surface->IsValid()) {\n      return;\n    }\n\n    if (!surface->MakeCurrent()) {\n      FML_LOG(ERROR) << \"Unable to make the render surface current to update \"\n                        \"the swap interval\";\n      return;\n    }\n\n    if (!surface->SetVSyncEnabled(needs_vsync)) {\n      FML_LOG(ERROR) << \"Unable to update the render surface's swap interval\";\n    }\n\n    if (!egl_manager->render_context()->ClearCurrent()) {\n      FML_LOG(ERROR) << \"Unable to clear current surface after updating \"\n                        \"the swap interval\";\n    }\n  };\n\n  // Updating the vsync makes the EGL context and render surface current.\n  // If the engine is running, the render surface should only be made current on\n  // the raster thread. If the engine is initializing, the raster thread doesn't\n  // exist yet and the render surface can be made current on the platform\n  // thread.\n  if (engine->running()) {\n    engine->PostRasterThreadTask(update_vsync);\n  } else {\n    update_vsync();\n  }\n}\n\n/// Destroys a rendering surface that backs a Flutter view.\nvoid DestroyWindowSurface(const FlutterWindowsEngine& engine,\n                          std::unique_ptr<egl::WindowSurface> surface) {\n  // EGL surfaces are used on the raster thread if the engine is running.\n  // There may be pending raster tasks that use this surface. Destroy the\n  // surface on the raster thread to avoid concurrent uses.\n  if (engine.running()) {\n    engine.PostRasterThreadTask(fml::MakeCopyable(\n        [surface = std::move(surface)] { surface->Destroy(); }));\n  } else {\n    // There's no raster thread if engine isn't running. The surface can be\n    // destroyed on the platform thread.\n    surface->Destroy();\n  }\n}\n\n}  // namespace\n\nFlutterWindowsView::FlutterWindowsView(\n    std::unique_ptr<WindowBindingHandler> window_binding) {\n  // Take the binding handler, and give it a pointer back to self.\n  binding_handler_ = std::move(window_binding);\n  binding_handler_->SetView(this);\n\n  render_target_ = std::make_unique<WindowsRenderTarget>(\n      binding_handler_->GetRenderTarget());\n}\n\nFlutterWindowsView::~FlutterWindowsView() {\n  // stop draw texture in raster thread before destroy window view avoid crash\n  engine_->Stop();\n  engine_->SetView(nullptr);\n  binding_handler_.reset();\n  if (surface_) {\n    DestroyWindowSurface(*engine_, std::move(surface_));\n  }\n}\n\nvoid FlutterWindowsView::SetEngine(FlutterWindowsEngine* engine) {\n  engine_ = engine;\n\n  engine_->SetView(this);\n\n  move_handle_ =\n      std::make_unique<clay::WindowMoveHandle>(binding_handler_.get());\n\n  mouse_drop_handle_ = std::make_unique<clay::WindowMouseDropHandle>(\n      binding_handler_.get(), engine_);\n\n  PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds();\n\n  SendWindowMetrics(bounds.width, bounds.height,\n                    binding_handler_->GetDpiScale());\n}\n\nvoid FlutterWindowsView::MoveWindow() { move_handle_->MoveWindow(); }\n\nuint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) {\n  // Called on an engine-controlled (non-platform) thread.\n  std::unique_lock<std::mutex> lock(resize_mutex_);\n\n  if (resize_status_ != ResizeState::kResizeStarted) {\n    return kWindowFrameBufferID;\n  }\n\n  if (resize_target_width_ == width && resize_target_height_ == height) {\n    if (!ResizeRenderSurface(resize_target_width_, resize_target_height_)) {\n      return kWindowFrameBufferID;\n    }\n    resize_status_ = ResizeState::kFrameGenerated;\n  }\n\n  return kWindowFrameBufferID;\n}\n\nvoid FlutterWindowsView::SetDamageRegion(const clay::Rect& region) {\n  surface_->SetDamageRegion(region);\n}\n\nstd::optional<clay::Rect> FlutterWindowsView::GetDamageRegion() {\n  return surface_->GetDamageRegion();\n}\n\nvoid FlutterWindowsView::UpdateFlutterCursor(clay::CursorTypes cursor_type) {\n  binding_handler_->UpdateFlutterCursor(cursor_type);\n}\n\nvoid FlutterWindowsView::SetFlutterCursor(HCURSOR cursor) {\n  binding_handler_->SetFlutterCursor(cursor);\n}\n\nvoid FlutterWindowsView::ForceRedraw() {\n  if (engine_ && resize_status_ == ResizeState::kDone) {\n    // Request new frame.\n    engine_->ScheduleFrame();\n  }\n}\n\nvoid FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) {\n  // Called on the platform thread.\n  std::unique_lock<std::mutex> lock(resize_mutex_);\n\n  if (!engine_) {\n    return;\n  }\n\n  if (!engine_->egl_manager()) {\n    SendWindowMetrics(width, height, binding_handler_->GetDpiScale());\n    return;\n  }\n\n  if (!surface_ || !surface_->IsValid()) {\n    SendWindowMetrics(width, height, binding_handler_->GetDpiScale());\n    return;\n  }\n\n  bool surface_will_update =\n      SurfaceWillUpdate(surface_->width(), surface_->height(), width, height) ||\n      SurfaceWillUpdate(resize_target_width_, resize_target_height_, width,\n                        height);\n  if (surface_will_update) {\n    resize_status_ = ResizeState::kResizeStarted;\n    resize_target_width_ = width;\n    resize_target_height_ = height;\n  }\n\n  SendWindowMetrics(width, height, binding_handler_->GetDpiScale());\n\n  if (surface_will_update) {\n    // Block the platform thread until:\n    //   1. GetFrameBufferId is called with the right frame size.\n    //   2. Any pending SwapBuffers calls have been invoked.\n    resize_cv_.wait_for(lock, kWindowResizeTimeout,\n                        [&resize_status = resize_status_] {\n                          return resize_status == ResizeState::kDone;\n                        });\n  }\n}\n\nvoid FlutterWindowsView::OnWindowRepaint() { ForceRedraw(); }\n\nvoid FlutterWindowsView::OnPointerMove(double x, double y,\n                                       ClayPointerDeviceKind device_kind,\n                                       int32_t device_id, int modifiers_state) {\n  engine_->keyboard_key_handler()->SyncModifiersIfNeeded(modifiers_state);\n  SendPointerMove(x, y, GetOrCreatePointerState(device_kind, device_id));\n}\n\nvoid FlutterWindowsView::OnPointerDown(double x, double y,\n                                       ClayPointerDeviceKind device_kind,\n                                       int32_t device_id,\n                                       ClayPointerMouseButtons flutter_button) {\n  if (flutter_button != 0) {\n    auto state = GetOrCreatePointerState(device_kind, device_id);\n    state->buttons |= flutter_button;\n    SendPointerDown(x, y, state);\n  }\n}\n\nvoid FlutterWindowsView::OnPointerUp(double x, double y,\n                                     ClayPointerDeviceKind device_kind,\n                                     int32_t device_id,\n                                     ClayPointerMouseButtons flutter_button) {\n  if (flutter_button != 0) {\n    auto state = GetOrCreatePointerState(device_kind, device_id);\n    state->buttons &= ~flutter_button;\n    SendPointerUp(x, y, state);\n  }\n}\n\nvoid FlutterWindowsView::OnPointerLeave(double x, double y,\n                                        ClayPointerDeviceKind device_kind,\n                                        int32_t device_id) {\n  SendPointerLeave(x, y, GetOrCreatePointerState(device_kind, device_id));\n}\n\nvoid FlutterWindowsView::OnPointerPanZoomStart(int32_t device_id) {\n  PointerLocation point = binding_handler_->GetPrimaryPointerLocation();\n  SendPointerPanZoomStart(device_id, point.x, point.y);\n}\n\nvoid FlutterWindowsView::OnPointerPanZoomUpdate(int32_t device_id, double pan_x,\n                                                double pan_y, double scale,\n                                                double rotation) {\n  SendPointerPanZoomUpdate(device_id, pan_x, pan_y, scale, rotation);\n}\n\nvoid FlutterWindowsView::OnPointerPanZoomEnd(int32_t device_id) {\n  SendPointerPanZoomEnd(device_id);\n}\n\nvoid FlutterWindowsView::OnText(const std::u16string& text) { SendText(text); }\n\nvoid FlutterWindowsView::OnKey(int key, int scancode, int action,\n                               char32_t character, bool extended, bool was_down,\n                               KeyEventCallback callback) {\n  SendKey(key, scancode, action, character, extended, was_down, callback);\n}\n\nvoid FlutterWindowsView::OnComposeBegin() { SendComposeBegin(); }\n\nvoid FlutterWindowsView::OnComposeCommit() { SendComposeCommit(); }\n\nvoid FlutterWindowsView::OnComposeEnd() { SendComposeEnd(); }\n\nvoid FlutterWindowsView::OnComposeChange(const std::u16string& text,\n                                         int cursor_pos) {\n  SendComposeChange(text, cursor_pos);\n}\n\nvoid FlutterWindowsView::OnScroll(double x, double y, double delta_x,\n                                  double delta_y, int scroll_offset_multiplier,\n                                  ClayPointerDeviceKind device_kind,\n                                  int32_t device_id) {\n  SendScroll(x, y, delta_x, delta_y, scroll_offset_multiplier, device_kind,\n             device_id);\n}\n\nvoid FlutterWindowsView::OnScrollInertiaCancel(int32_t device_id) {\n  PointerLocation point = binding_handler_->GetPrimaryPointerLocation();\n  SendScrollInertiaCancel(device_id, point.x, point.y);\n}\n\nvoid FlutterWindowsView::OnCursorRectUpdated(const FloatRect& rect) {\n  binding_handler_->OnCursorRectUpdated(rect);\n}\n\nvoid FlutterWindowsView::OnResetImeComposing() {\n  binding_handler_->OnResetImeComposing();\n}\n\nvoid FlutterWindowsView::OnTextInputClientChange(int client_id) {\n  binding_handler_->OnTextInputClientChange(client_id);\n}\n\n// Sends new size  information to FlutterEngine.\nvoid FlutterWindowsView::SendWindowMetrics(size_t width, size_t height,\n                                           double dpi_scale) const {\n  clay::ViewportMetrics metrics;\n  metrics.physical_width = width;\n  metrics.physical_height = height;\n  metrics.device_pixel_ratio = dpi_scale;\n  // Default logical pixel is 96\n  metrics.device_density_dpi = dpi_scale * 96.f;\n  metrics.physical_view_inset_top = 0.0;\n  metrics.physical_view_inset_right = 0.0;\n  metrics.physical_view_inset_bottom = 0.0;\n  metrics.physical_view_inset_left = 0.0;\n  engine_->SendWindowMetricsEvent(metrics);\n}\n\nvoid FlutterWindowsView::SendInitialBounds() {\n  PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds();\n\n  SendWindowMetrics(bounds.width, bounds.height,\n                    binding_handler_->GetDpiScale());\n}\n\nFlutterWindowsView::PointerState* FlutterWindowsView::GetOrCreatePointerState(\n    ClayPointerDeviceKind device_kind, int32_t device_id) {\n  // Create a virtual pointer ID that is unique across all device types\n  // to prevent pointers from clashing in the engine's converter\n  // (ui/window/pointer_data_packet_converter.cc)\n  int32_t pointer_id = (static_cast<int32_t>(device_kind) << 28) | device_id;\n\n  auto [it, added] = pointer_states_.try_emplace(pointer_id, nullptr);\n  if (added) {\n    auto state = std::make_unique<PointerState>();\n    state->device_kind = device_kind;\n    state->pointer_id = pointer_id;\n    it->second = std::move(state);\n  }\n\n  return it->second.get();\n}\n\n// Set's |event_data|'s phase to either kMove or kHover depending on the current\n// primary mouse button state.\nvoid FlutterWindowsView::SetEventPhaseFromCursorButtonState(\n    ClayPointerEvent* event_data, const PointerState* state) const {\n  // For details about this logic, see ClayPointerPhase in the clay.h\n  // file.\n  if (state->buttons == 0) {\n    event_data->phase = state->clay_state_is_down\n                            ? ClayPointerPhase::kClayPointerPhaseUp\n                            : ClayPointerPhase::kClayPointerPhaseHover;\n  } else {\n    event_data->phase = state->clay_state_is_down\n                            ? ClayPointerPhase::kClayPointerPhaseMove\n                            : ClayPointerPhase::kClayPointerPhaseDown;\n  }\n}\n\nvoid FlutterWindowsView::SendPointerMove(double x, double y,\n                                         PointerState* state) {\n  ClayPointerEvent event = {};\n  event.x = x;\n  event.y = y;\n\n  SetEventPhaseFromCursorButtonState(&event, state);\n  SendPointerEventWithData(event, state);\n}\n\nvoid FlutterWindowsView::SendPointerDown(double x, double y,\n                                         PointerState* state) {\n  ClayPointerEvent event = {};\n  event.x = x;\n  event.y = y;\n\n  SetEventPhaseFromCursorButtonState(&event, state);\n  SendPointerEventWithData(event, state);\n\n  state->clay_state_is_down = true;\n}\n\nvoid FlutterWindowsView::SendPointerUp(double x, double y,\n                                       PointerState* state) {\n  ClayPointerEvent event = {};\n  event.x = x;\n  event.y = y;\n\n  SetEventPhaseFromCursorButtonState(&event, state);\n  SendPointerEventWithData(event, state);\n  if (event.phase == ClayPointerPhase::kClayPointerPhaseUp) {\n    state->clay_state_is_down = false;\n  }\n}\n\nvoid FlutterWindowsView::SendPointerLeave(double x, double y,\n                                          PointerState* state) {\n  ClayPointerEvent event = {};\n  event.x = x;\n  event.y = y;\n  event.phase = ClayPointerPhase::kClayPointerPhaseRemove;\n  SendPointerEventWithData(event, state);\n}\n\nvoid FlutterWindowsView::SendPointerPanZoomStart(int32_t device_id, double x,\n                                                 double y) {\n  auto state =\n      GetOrCreatePointerState(kClayPointerDeviceKindTrackpad, device_id);\n  state->pan_zoom_start_x = x;\n  state->pan_zoom_start_y = y;\n  ClayPointerEvent event = {};\n  event.x = x;\n  event.y = y;\n  event.phase = ClayPointerPhase::kClayPointerPhasePanZoomStart;\n  SendPointerEventWithData(event, state);\n}\n\nvoid FlutterWindowsView::SendPointerPanZoomUpdate(int32_t device_id,\n                                                  double pan_x, double pan_y,\n                                                  double scale,\n                                                  double rotation) {\n  auto state =\n      GetOrCreatePointerState(kClayPointerDeviceKindTrackpad, device_id);\n  ClayPointerEvent event = {};\n  event.x = state->pan_zoom_start_x;\n  event.y = state->pan_zoom_start_y;\n  event.pan_x = pan_x;\n  event.pan_y = pan_y;\n  event.scale = scale;\n  event.rotation = rotation;\n  event.phase = ClayPointerPhase::kClayPointerPhasePanZoomUpdate;\n  SendPointerEventWithData(event, state);\n}\n\nvoid FlutterWindowsView::SendPointerPanZoomEnd(int32_t device_id) {\n  auto state =\n      GetOrCreatePointerState(kClayPointerDeviceKindTrackpad, device_id);\n  ClayPointerEvent event = {};\n  event.x = state->pan_zoom_start_x;\n  event.y = state->pan_zoom_start_y;\n  event.phase = ClayPointerPhase::kClayPointerPhasePanZoomEnd;\n  SendPointerEventWithData(event, state);\n}\n\nvoid FlutterWindowsView::SendText(const std::u16string& text) {\n  engine_->text_input_plugin()->TextHook(text);\n}\n\nvoid FlutterWindowsView::SendKey(int key, int scancode, int action,\n                                 char32_t character, bool extended,\n                                 bool was_down, KeyEventCallback callback) {\n  engine_->keyboard_key_handler()->KeyboardHook(\n      key, scancode, action, character, extended, was_down,\n      [=, callback = std::move(callback)](bool handled) {\n        if (!handled) {\n          engine_->text_input_plugin()->KeyboardHook(\n              key, scancode, action, character, extended, was_down);\n        }\n        callback(handled);\n      });\n}\n\nvoid FlutterWindowsView::SendComposeBegin() {\n  engine_->text_input_plugin()->ComposeBeginHook();\n}\n\nvoid FlutterWindowsView::SendComposeCommit() {\n  engine_->text_input_plugin()->ComposeCommitHook();\n}\n\nvoid FlutterWindowsView::SendComposeEnd() {\n  engine_->text_input_plugin()->ComposeEndHook();\n}\n\nvoid FlutterWindowsView::SendComposeChange(const std::u16string& text,\n                                           int cursor_pos) {\n  engine_->text_input_plugin()->ComposeChangeHook(text, cursor_pos);\n}\n\nvoid FlutterWindowsView::SendScroll(double x, double y, double delta_x,\n                                    double delta_y,\n                                    int scroll_offset_multiplier,\n                                    ClayPointerDeviceKind device_kind,\n                                    int32_t device_id) {\n  auto state = GetOrCreatePointerState(device_kind, device_id);\n\n  ClayPointerEvent event = {};\n  event.x = x;\n  event.y = y;\n  event.signal_kind = ClayPointerSignalKind::kClayPointerSignalKindScroll;\n  event.scroll_delta_x = delta_x * scroll_offset_multiplier;\n  event.scroll_delta_y = delta_y * scroll_offset_multiplier;\n  event.is_precise_scroll = 0;\n  SetEventPhaseFromCursorButtonState(&event, state);\n  SendPointerEventWithData(event, state);\n}\n\nvoid FlutterWindowsView::SendScrollInertiaCancel(int32_t device_id, double x,\n                                                 double y) {\n  auto state =\n      GetOrCreatePointerState(kClayPointerDeviceKindTrackpad, device_id);\n\n  ClayPointerEvent event = {};\n  event.x = x;\n  event.y = y;\n  event.signal_kind =\n      ClayPointerSignalKind::kClayPointerSignalKindScrollInertiaCancel;\n  SetEventPhaseFromCursorButtonState(&event, state);\n  SendPointerEventWithData(event, state);\n}\n\nvoid FlutterWindowsView::SendPointerEventWithData(\n    const ClayPointerEvent& event_data, PointerState* state) {\n  // If sending anything other than an add, and the pointer isn't already added,\n  // synthesize an add to satisfy Flutter's expectations about events.\n  if (!state->clay_state_is_added &&\n      event_data.phase != ClayPointerPhase::kClayPointerPhaseAdd) {\n    ClayPointerEvent event = {};\n    event.phase = ClayPointerPhase::kClayPointerPhaseAdd;\n    event.x = event_data.x;\n    event.y = event_data.y;\n    event.buttons = 0;\n    SendPointerEventWithData(event, state);\n  }\n  // Don't double-add (e.g., if events are delivered out of order, so an add has\n  // already been synthesized).\n  if (state->clay_state_is_added &&\n      event_data.phase == ClayPointerPhase::kClayPointerPhaseAdd) {\n    return;\n  }\n\n  ClayPointerEvent event = event_data;\n  event.device_kind = state->device_kind;\n  event.device = state->pointer_id;\n  event.buttons = state->buttons;\n\n  // Set metadata that's always the same regardless of the event.\n  event.struct_size = sizeof(event);\n  event.timestamp =\n      std::chrono::duration_cast<std::chrono::microseconds>(\n          std::chrono::high_resolution_clock::now().time_since_epoch())\n          .count();\n  event.is_precise_scroll = event_data.is_precise_scroll;\n\n  engine_->SendPointerEvent(event);\n\n  if (event.phase == ClayPointerPhase::kClayPointerPhaseAdd) {\n    state->clay_state_is_added = true;\n  } else if (event.phase == ClayPointerPhase::kClayPointerPhaseRemove) {\n    auto it = pointer_states_.find(state->pointer_id);\n    if (it != pointer_states_.end()) {\n      pointer_states_.erase(it);\n    }\n  }\n}\n\nbool FlutterWindowsView::MakeCurrent() {\n  if (!surface_ || !surface_->IsValid()) {\n    return false;\n  }\n  return surface_->MakeCurrent();\n}\n\nbool FlutterWindowsView::SwapBuffers() {\n  if (!surface_ || !surface_->IsValid()) {\n    return false;\n  }\n  // Called on an engine-controlled (non-platform) thread.\n  std::unique_lock<std::mutex> lock(resize_mutex_);\n\n  switch (resize_status_) {\n    // SwapBuffer requests during resize are ignored until the frame with the\n    // right dimensions has been generated. This is marked with\n    // kFrameGenerated resize status.\n    case ResizeState::kResizeStarted:\n      return false;\n    case ResizeState::kFrameGenerated: {\n      bool visible = binding_handler_->IsVisible();\n      bool swap_buffers_result;\n      // For visible windows swap the buffers while resize handler is waiting.\n      // For invisible windows unblock the handler first and then swap buffers.\n      // SwapBuffers waits for vsync and there's no point doing that for\n      // invisible windows.\n      if (visible) {\n        swap_buffers_result = surface_->SwapBuffers();\n      }\n      resize_status_ = ResizeState::kDone;\n      lock.unlock();\n      resize_cv_.notify_all();\n      binding_handler_->OnWindowResized();\n      if (!visible) {\n        swap_buffers_result = surface_->SwapBuffers();\n      }\n      return swap_buffers_result;\n    }\n    case ResizeState::kDone:\n    default:\n      return surface_->SwapBuffers();\n  }\n}\n\nbool FlutterWindowsView::PresentSoftwareBitmap(const void* allocation,\n                                               size_t row_bytes,\n                                               size_t height) {\n  return binding_handler_->OnBitmapSurfaceUpdated(allocation, row_bytes,\n                                                  height);\n}\n\nvoid FlutterWindowsView::CreateRenderSurface() {\n  FML_DCHECK(surface_ == nullptr);\n\n  if (engine_ && engine_->egl_manager()) {\n    PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds();\n    bool enable_vsync = binding_handler_->NeedsVSync();\n    surface_ = engine_->egl_manager()->CreateWindowSurface(\n        egl::GLImplementationType::kAngleEGL,\n        std::get<HWND>(*GetRenderTarget()), bounds.width, bounds.height);\n    UpdateVsync(engine_, surface_.get(), enable_vsync);\n    resize_target_width_ = bounds.width;\n    resize_target_height_ = bounds.height;\n  }\n}\n\nbool FlutterWindowsView::ResizeRenderSurface(size_t width, size_t height) {\n  FML_DCHECK(surface_ != nullptr);\n\n  // No-op if the surface is already the desired size.\n  if (width == surface_->width() && height == surface_->height()) {\n    return true;\n  }\n\n  return surface_->Resize(width, height);\n  ;\n}\n\nvoid FlutterWindowsView::UpdateHighContrastEnabled(bool enabled) {\n  engine_->UpdateHighContrastEnabled(enabled);\n}\n\nWindowsRenderTarget* FlutterWindowsView::GetRenderTarget() const {\n  return render_target_.get();\n}\n\nPlatformWindow FlutterWindowsView::GetPlatformWindow() const {\n  return binding_handler_->GetPlatformWindow();\n}\n\nFlutterWindowsEngine* FlutterWindowsView::GetEngine() { return engine_; }\n\nvoid FlutterWindowsView::NotifyWinEventWrapper(DWORD event, HWND hwnd,\n                                               LONG idObject, LONG idChild) {\n  // if (hwnd) {\n  //   NotifyWinEvent(EVENT_SYSTEM_ALERT, hwnd, OBJID_CLIENT,\n  //                  AccessibilityRootNode::kAlertChildId);\n  // }\n}\n\nvoid FlutterWindowsView::OnDwmCompositionChanged() {\n  if (!surface_ || !surface_->IsValid()) {\n    return;\n  }\n\n  if (!surface_->MakeCurrent()) {\n    FML_LOG(ERROR) << \"Unable to make the render surface current to update \"\n                      \"the swap interval\";\n    return;\n  }\n  surface_->SetVSyncEnabled(binding_handler_->NeedsVSync());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/flutter_windows_view.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOWS_VIEW_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOWS_VIEW_H_\n\n#include <memory>\n#include <mutex>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/shell/platform/windows/egl/manager.h\"\n#include \"clay/shell/platform/windows/flutter_windows_engine.h\"\n#include \"clay/shell/platform/windows/text_input_plugin_delegate.h\"\n#include \"clay/shell/platform/windows/window_binding_handler.h\"\n#include \"clay/shell/platform/windows/window_binding_handler_delegate.h\"\n#include \"clay/shell/platform/windows/window_mouse_drop_handler.h\"\n#include \"clay/shell/platform/windows/window_move_handler.h\"\n\nnamespace clay {\n// ID for the window frame buffer.\ninline constexpr uint32_t kWindowFrameBufferID = 0;\n\n// An OS-windowing neutral abstration for flutter\n// view that works with win32 hwnds and Windows::UI::Composition visuals.\nclass FlutterWindowsView : public WindowBindingHandlerDelegate,\n                           public TextInputPluginDelegate {\n public:\n  // Creates a FlutterWindowsView with the given implementor of\n  // WindowBindingHandler.\n  //\n  // In order for object to render Flutter content the SetEngine method must\n  // be called with a valid FlutterWindowsEngine instance.\n  FlutterWindowsView(std::unique_ptr<WindowBindingHandler> window_binding);\n\n  virtual ~FlutterWindowsView();\n\n  // Configures the window instance with an instance of a running Flutter\n  // engine.\n  void SetEngine(FlutterWindowsEngine* engine);\n\n  // Creates rendering surface for Flutter engine to draw into.\n  // Should be called before calling FlutterEngineRun using this view.\n  void CreateRenderSurface();\n\n  // Return the currently configured WindowsRenderTarget.\n  WindowsRenderTarget* GetRenderTarget() const;\n\n  // Return the currently configured PlatformWindow.\n  PlatformWindow GetPlatformWindow() const;\n\n  // Returns the engine backing this view.\n  FlutterWindowsEngine* GetEngine();\n\n  // Tells the engine to generate a new frame\n  void ForceRedraw();\n\n  bool MakeCurrent();\n\n  bool SwapBuffers();\n\n  // Callback for presenting a software bitmap.\n  bool PresentSoftwareBitmap(const void* allocation, size_t row_bytes,\n                             size_t height);\n\n  // Send initial bounds to embedder.  Must occur after engine has\n  // initialized.\n  void SendInitialBounds();\n\n  // |WindowBindingHandlerDelegate|\n  void UpdateHighContrastEnabled(bool enabled) override;\n\n  // Returns the frame buffer id for the engine to render to.\n  uint32_t GetFrameBufferId(size_t width, size_t height);\n\n  void SetDamageRegion(const clay::Rect& region);\n\n  std::optional<clay::Rect> GetDamageRegion();\n\n  // Sets the cursor that should be used when the mouse is over the Flutter\n  // content. See mouse_cursor.dart for the values and meanings of\n  // cursor_name.\n  void UpdateFlutterCursor(clay::CursorTypes cursor_name);\n\n  // Sets the cursor directly from a cursor handle.\n  void SetFlutterCursor(HCURSOR cursor);\n\n  // |WindowBindingHandlerDelegate|\n  void OnWindowSizeChanged(size_t width, size_t height) override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnWindowRepaint() override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnPointerMove(double x, double y, ClayPointerDeviceKind device_kind,\n                     int32_t device_id, int modifiers_state) override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnPointerDown(double x, double y, ClayPointerDeviceKind device_kind,\n                     int32_t device_id,\n                     ClayPointerMouseButtons button) override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnPointerUp(double x, double y, ClayPointerDeviceKind device_kind,\n                   int32_t device_id, ClayPointerMouseButtons button) override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnPointerLeave(double x, double y, ClayPointerDeviceKind device_kind,\n                      int32_t device_id = 0) override;\n\n  // |WindowBindingHandlerDelegate|\n  virtual void OnPointerPanZoomStart(int32_t device_id) override;\n\n  // |WindowBindingHandlerDelegate|\n  virtual void OnPointerPanZoomUpdate(int32_t device_id, double pan_x,\n                                      double pan_y, double scale,\n                                      double rotation) override;\n\n  // |WindowBindingHandlerDelegate|\n  virtual void OnPointerPanZoomEnd(int32_t device_id) override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnText(const std::u16string&) override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnKey(int key, int scancode, int action, char32_t character,\n             bool extended, bool was_down, KeyEventCallback callback) override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnComposeBegin() override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnComposeCommit() override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnComposeEnd() override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnComposeChange(const std::u16string& text, int cursor_pos) override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnScroll(double x, double y, double delta_x, double delta_y,\n                int scroll_offset_multiplier, ClayPointerDeviceKind device_kind,\n                int32_t device_id) override;\n\n  // |WindowBindingHandlerDelegate|\n  void OnScrollInertiaCancel(int32_t device_id) override;\n\n  // |TextInputPluginDelegate|\n  void OnCursorRectUpdated(const FloatRect& rect) override;\n\n  // |TextInputPluginDelegate|\n  void OnResetImeComposing() override;\n\n  // |TextInputPluginDelegate|\n  void OnTextInputClientChange(int client_id) override;\n\n  // Called when a WM_ONCOMPOSITIONCHANGED message is received.\n  void OnDwmCompositionChanged();\n\n  void MoveWindow();\n\n protected:\n  virtual void NotifyWinEventWrapper(DWORD event, HWND hwnd, LONG idObject,\n                                     LONG idChild);\n\n private:\n  // Struct holding the state of an individual pointer. The engine doesn't\n  // keep track of which buttons have been pressed, so it's the embedding's\n  // responsibility.\n  struct PointerState {\n    // The device kind.\n    ClayPointerDeviceKind device_kind = kClayPointerDeviceKindMouse;\n\n    // A virtual pointer ID that is unique across all device kinds.\n    int32_t pointer_id = 0;\n\n    // True if the last event sent to Flutter had at least one button pressed.\n    bool clay_state_is_down = false;\n\n    // True if kAdd has been sent to Flutter. Used to determine whether\n    // to send a kAdd event before sending an incoming pointer event, since\n    // Flutter expects pointers to be added before events are sent for them.\n    bool clay_state_is_added = false;\n\n    // The currently pressed buttons, as represented in ClayPointerEvent.\n    uint64_t buttons = 0;\n\n    // The x position where the last pan/zoom started.\n    double pan_zoom_start_x = 0;\n\n    // The y position where the last pan/zoom started.\n    double pan_zoom_start_y = 0;\n  };\n\n  // States a resize event can be in.\n  enum class ResizeState {\n    // When a resize event has started but is in progress.\n    kResizeStarted,\n    // After a resize event starts and the framework has been notified to\n    // generate a frame for the right size.\n    kFrameGenerated,\n    // Default state for when no resize is in progress. Also used to indicate\n    // that during a resize event, a frame with the right size has been\n    // rendered\n    // and the buffers have been swapped.\n    kDone,\n  };\n\n  // Resize the surface to the desired size.\n  //\n  // If the dimensions have changed, this destroys the original surface and\n  // creates a new one.\n  //\n  // This must be run on the raster thread. This binds the surface to the\n  // current thread.\n  //\n  // Width and height are the surface's desired physical pixel dimensions.\n  bool ResizeRenderSurface(size_t width, size_t height);\n\n  // Sends a window metrics update to the Flutter engine using current window\n  // dimensions in physical\n  void SendWindowMetrics(size_t width, size_t height, double dpi_scale) const;\n\n  // Reports a mouse movement to Flutter engine.\n  void SendPointerMove(double x, double y, PointerState* state);\n\n  // Reports mouse press to Flutter engine.\n  void SendPointerDown(double x, double y, PointerState* state);\n\n  // Reports mouse release to Flutter engine.\n  void SendPointerUp(double x, double y, PointerState* state);\n\n  // Reports mouse left the window client area.\n  //\n  // Win32 api doesn't have \"mouse enter\" event. Therefore, there is no\n  // SendPointerEnter method. A mouse enter event is tracked then the \"move\"\n  // event is called.\n  void SendPointerLeave(double x, double y, PointerState* state);\n\n  void SendPointerPanZoomStart(int32_t device_id, double x, double y);\n\n  void SendPointerPanZoomUpdate(int32_t device_id, double pan_x, double pan_y,\n                                double scale, double rotation);\n\n  void SendPointerPanZoomEnd(int32_t device_id);\n\n  // Reports a keyboard character to Flutter engine.\n  void SendText(const std::u16string&);\n\n  // Reports a raw keyboard message to Flutter engine.\n  void SendKey(int key, int scancode, int action, char32_t character,\n               bool extended, bool was_down, KeyEventCallback callback);\n\n  // Reports an IME compose begin event.\n  //\n  // Triggered when the user begins editing composing text using a multi-step\n  // input method such as in CJK text input.\n  void SendComposeBegin();\n\n  // Reports an IME compose commit event.\n  //\n  // Triggered when the user commits the current composing text while using a\n  // multi-step input method such as in CJK text input. Composing continues\n  // with the next keypress.\n  void SendComposeCommit();\n\n  // Reports an IME compose end event.\n  //\n  // Triggered when the user commits the composing text while using a\n  // multi-step input method such as in CJK text input.\n  void SendComposeEnd();\n\n  // Reports an IME composing region change event.\n  //\n  // Triggered when the user edits the composing text while using a multi-step\n  // input method such as in CJK text input.\n  void SendComposeChange(const std::u16string& text, int cursor_pos);\n\n  // Reports scroll wheel events to Flutter engine.\n  void SendScroll(double x, double y, double delta_x, double delta_y,\n                  int scroll_offset_multiplier,\n                  ClayPointerDeviceKind device_kind, int32_t device_id);\n\n  // Reports scroll inertia cancel events to Flutter engine.\n  void SendScrollInertiaCancel(int32_t device_id, double x, double y);\n\n  // Creates a PointerState object unless it already exists.\n  PointerState* GetOrCreatePointerState(ClayPointerDeviceKind device_kind,\n                                        int32_t device_id);\n\n  // Sets |event_data|'s phase to either kMove or kHover depending on the\n  // current primary mouse button state.\n  void SetEventPhaseFromCursorButtonState(ClayPointerEvent* event_data,\n                                          const PointerState* state) const;\n\n  // Sends a pointer event to the Flutter engine based on given data.  Since\n  // all input messages are passed in physical pixel values, no translation is\n  // needed before passing on to engine.\n  void SendPointerEventWithData(const ClayPointerEvent& event_data,\n                                PointerState* state);\n\n  // Currently configured WindowsRenderTarget for this view used by\n  // surface_manager for creation of render surfaces and bound to the physical\n  // os window.\n  std::unique_ptr<WindowsRenderTarget> render_target_;\n\n  // The engine associated with this view.\n  FlutterWindowsEngine* engine_;\n\n  // Keeps track of pointer states in relation to the window.\n  std::unordered_map<int32_t, std::unique_ptr<PointerState>> pointer_states_;\n\n  // Currently configured WindowBindingHandler for view.\n  std::unique_ptr<WindowBindingHandler> binding_handler_;\n\n  // The EGL surface backing the view.\n  //\n  // Null if using software rasterization, the surface hasn't been created\n  // yet, or if surface creation failed.\n  std::unique_ptr<egl::WindowSurface> surface_ = nullptr;\n\n  // Resize events are synchronized using this mutex and the corresponding\n  // condition variable.\n  std::mutex resize_mutex_;\n  std::condition_variable resize_cv_;\n\n  // Indicates the state of a window resize event. Platform thread will be\n  // blocked while this is not done. Guarded by resize_mutex_.\n  ResizeState resize_status_ = ResizeState::kDone;\n\n  // Target for the window width. Valid when resize_pending_ is set. Guarded\n  // by resize_mutex_.\n  size_t resize_target_width_ = 0;\n\n  // Target for the window width. Valid when resize_pending_ is set. Guarded\n  // by resize_mutex_.\n  size_t resize_target_height_ = 0;\n\n  // Handler for move events.\n  std::unique_ptr<clay::WindowMoveHandle> move_handle_;\n\n  // Handler for mouse drop events.\n  std::unique_ptr<clay::WindowMouseDropHandle> mouse_drop_handle_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOWS_VIEW_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/keyboard_handler_base.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_HANDLER_BASE_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_HANDLER_BASE_H_\n\n#include <functional>\n#include <string>\n\nnamespace clay {\n\n// Interface for classes that handles keyboard input events.\n//\n// Keyboard handlers are added to |FlutterWindowsView| in a chain.\n// When a key event arrives, |KeyboardHook| is called on each handler\n// until the first one that returns true. Then the proper text hooks\n// are called on each handler.\nclass KeyboardHandlerBase {\n public:\n  using KeyEventCallback = std::function<void(bool)>;\n\n  virtual ~KeyboardHandlerBase() = default;\n\n  // A function for hooking into keyboard input.\n  virtual void KeyboardHook(int key, int scancode, int action,\n                            char32_t character, bool extended, bool was_down,\n                            KeyEventCallback callback) = 0;\n\n  // If needed, synthesize modifier keys events by comparing the\n  // given modifiers state to the known pressing state..\n  virtual void SyncModifiersIfNeeded(int modifiers_state) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_HANDLER_BASE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/keyboard_key_embedder_handler.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/keyboard_key_embedder_handler.h\"\n\n#include <assert.h>\n#include <windows.h>\n\n#include <chrono>\n#include <iostream>\n#include <string>\n#include <utility>\n\n#include \"clay/shell/platform/windows/keyboard_utils.h\"\n\nnamespace clay {\n\nnamespace {\n// An arbitrary size for the character cache in bytes.\n//\n// It should hold a UTF-32 character encoded in UTF-8 as well as the trailing\n// '\\0'.\nconstexpr size_t kCharacterCacheSize = 8;\n\nconstexpr SHORT kStateMaskToggled = 0x01;\nconstexpr SHORT kStateMaskPressed = 0x80;\n\nconst char* empty_character = \"\";\n\n// Get some bits of the char, from the start'th bit from the right (excluded)\n// to the end'th bit from the right (included).\n//\n// For example, _GetBit(0x1234, 8, 4) => 0x3.\nchar _GetBit(char32_t ch, size_t start, size_t end) {\n  return (ch >> end) & ((1 << (start - end)) - 1);\n}\n}  // namespace\n\nstd::string ConvertChar32ToUtf8(char32_t ch) {\n  std::string result;\n  assert(0 <= ch && ch <= 0x10FFFF);\n  if (ch <= 0x007F) {\n    result.push_back(ch);\n  } else if (ch <= 0x07FF) {\n    result.push_back(0b11000000 + _GetBit(ch, 11, 6));\n    result.push_back(0b10000000 + _GetBit(ch, 6, 0));\n  } else if (ch <= 0xFFFF) {\n    result.push_back(0b11100000 + _GetBit(ch, 16, 12));\n    result.push_back(0b10000000 + _GetBit(ch, 12, 6));\n    result.push_back(0b10000000 + _GetBit(ch, 6, 0));\n  } else {\n    result.push_back(0b11110000 + _GetBit(ch, 21, 18));\n    result.push_back(0b10000000 + _GetBit(ch, 18, 12));\n    result.push_back(0b10000000 + _GetBit(ch, 12, 6));\n    result.push_back(0b10000000 + _GetBit(ch, 6, 0));\n  }\n  return result;\n}\n\nKeyboardKeyEmbedderHandler::KeyboardKeyEmbedderHandler(\n    SendEventHandler send_event, GetKeyStateHandler get_key_state,\n    MapVirtualKeyToScanCode map_virtual_key_to_scan_code)\n    : perform_send_event_(send_event),\n      get_key_state_(get_key_state),\n      response_id_(1) {\n  InitCriticalKeys(map_virtual_key_to_scan_code);\n}\n\nKeyboardKeyEmbedderHandler::~KeyboardKeyEmbedderHandler() = default;\n\nstatic bool isEasciiPrintable(int codeUnit) {\n  return (codeUnit <= 0x7f && codeUnit >= 0x20) ||\n         (codeUnit <= 0xff && codeUnit >= 0x80);\n}\n\n// Converts upper letters to lower letters in ASCII and extended ASCII, and\n// returns as-is otherwise.\n//\n// Independent of locale.\nstatic uint64_t toLower(uint64_t n) {\n  constexpr uint64_t lower_a = 0x61;\n  constexpr uint64_t upper_a = 0x41;\n  constexpr uint64_t upper_z = 0x5a;\n\n  constexpr uint64_t lower_a_grave = 0xe0;\n  constexpr uint64_t upper_a_grave = 0xc0;\n  constexpr uint64_t upper_thorn = 0xde;\n  constexpr uint64_t division = 0xf7;\n\n  // ASCII range.\n  if (n >= upper_a && n <= upper_z) {\n    return n - upper_a + lower_a;\n  }\n\n  // EASCII range.\n  if (n >= upper_a_grave && n <= upper_thorn && n != division) {\n    return n - upper_a_grave + lower_a_grave;\n  }\n\n  return n;\n}\n\n// Transform scancodes sent by windows to scancodes written in Chromium spec.\nstatic uint16_t normalizeScancode(int windowsScanCode, bool extended) {\n  // In Chromium spec the extended bit is shown as 0xe000 bit,\n  // e.g. PageUp is represented as 0xe049.\n  return (windowsScanCode & 0xff) | (extended ? 0xe000 : 0);\n}\n\nuint64_t KeyboardKeyEmbedderHandler::ApplyPlaneToId(uint64_t id,\n                                                    uint64_t plane) {\n  return (id & valueMask) | plane;\n}\n\nuint64_t KeyboardKeyEmbedderHandler::GetPhysicalKey(int scancode,\n                                                    bool extended) {\n  int chromiumScancode = normalizeScancode(scancode, extended);\n  auto resultIt = windowsToPhysicalMap_.find(chromiumScancode);\n  if (resultIt != windowsToPhysicalMap_.end()) return resultIt->second;\n  return ApplyPlaneToId(scancode, windowsPlane);\n}\n\nuint64_t KeyboardKeyEmbedderHandler::GetLogicalKey(int key, bool extended,\n                                                   int scancode) {\n  if (key == VK_PROCESSKEY) {\n    return VK_PROCESSKEY;\n  }\n\n  // Normally logical keys should only be derived from key codes, but since some\n  // key codes are either 0 or ambiguous (multiple keys using the same key\n  // code), these keys are resolved by scan codes.\n  auto numpadIter =\n      scanCodeToLogicalMap_.find(normalizeScancode(scancode, extended));\n  if (numpadIter != scanCodeToLogicalMap_.cend()) return numpadIter->second;\n\n  // Check if the keyCode is one we know about and have a mapping for.\n  auto logicalIt = windowsToLogicalMap_.find(key);\n  if (logicalIt != windowsToLogicalMap_.cend()) return logicalIt->second;\n\n  // Upper case letters should be normalized into lower case letters.\n  if (isEasciiPrintable(key)) {\n    return ApplyPlaneToId(toLower(key), unicodePlane);\n  }\n\n  return ApplyPlaneToId(toLower(key), windowsPlane);\n}\n\nvoid KeyboardKeyEmbedderHandler::KeyboardHookImpl(\n    int key, int scancode, int action, char32_t character, bool extended,\n    bool was_down, std::function<void(bool)> callback) {\n  const uint64_t physical_key = GetPhysicalKey(scancode, extended);\n  const uint64_t logical_key = GetLogicalKey(key, extended, scancode);\n  assert(action == WM_KEYDOWN || action == WM_KEYUP ||\n         action == WM_SYSKEYDOWN || action == WM_SYSKEYUP);\n\n  auto last_logical_record_iter = pressingRecords_.find(physical_key);\n  bool had_record = last_logical_record_iter != pressingRecords_.end();\n  uint64_t last_logical_record =\n      had_record ? last_logical_record_iter->second : 0;\n\n  // The logical key for the current \"tap sequence\".\n  //\n  // Key events are formed in tap sequences: down, repeats, up. The logical key\n  // stays consistent throughout a tap sequence, which is this value.\n  uint64_t sequence_logical_key =\n      had_record ? last_logical_record : logical_key;\n\n  if (sequence_logical_key == VK_PROCESSKEY) {\n    // VK_PROCESSKEY means that the key press is used by an IME. These key\n    // presses are considered handled and not sent to Flutter. These events must\n    // be filtered by result_logical_key because the key up event of such\n    // presses uses the \"original\" logical key.\n    callback(true);\n    return;\n  }\n\n  const bool is_event_down = action == WM_KEYDOWN || action == WM_SYSKEYDOWN;\n\n  bool event_key_can_be_repeat = true;\n  UpdateLastSeenCriticalKey(key, physical_key, sequence_logical_key);\n  // Synchronize the toggled states of critical keys (such as whether CapsLocks\n  // is enabled). Toggled states can only be changed upon a down event, so if\n  // the recorded toggled state does not match the true state, this function\n  // will synthesize (an up event if the key is recorded pressed, then) a down\n  // event.\n  //\n  // After this function, all critical keys will have their toggled state\n  // updated to the true state, while the critical keys whose toggled state have\n  // been changed will be pressed regardless of their true pressed state.\n  // Updating the pressed state will be done by\n  // SynchronizeCriticalPressedStates.\n  SynchronizeCriticalToggledStates(key, is_event_down,\n                                   &event_key_can_be_repeat);\n  // Synchronize the pressed states of critical keys (such as whether CapsLocks\n  // is pressed).\n  //\n  // After this function, all critical keys except for the target key will have\n  // their toggled state and pressed state matched with their true states. The\n  // target key's pressed state will be updated immediately after this.\n  SynchronizeCriticalPressedStates(key, physical_key, is_event_down,\n                                   event_key_can_be_repeat);\n\n  // Reassess the last logical record in case pressingRecords_ was modified\n  // by the above synchronization methods.\n  last_logical_record_iter = pressingRecords_.find(physical_key);\n  had_record = last_logical_record_iter != pressingRecords_.end();\n  last_logical_record = had_record ? last_logical_record_iter->second : 0;\n\n  // The resulting event's `type`.\n  ClayKeyEventType type;\n  character = UndeadChar(character);\n  char character_bytes[kCharacterCacheSize];\n  // What pressingRecords_[physical_key] should be after the KeyboardHookImpl\n  // returns (0 if the entry should be removed).\n  uint64_t eventual_logical_record;\n  uint64_t result_logical_key;\n\n  if (is_event_down) {\n    if (had_record) {\n      if (was_down) {\n        // A normal repeated key.\n        type = kClayKeyEventTypeRepeat;\n        assert(had_record);\n        ConvertUtf32ToUtf8_(character_bytes, character);\n        eventual_logical_record = last_logical_record;\n        result_logical_key = last_logical_record;\n      } else {\n        // A non-repeated key has been pressed that has the exact physical key\n        // as a currently pressed one, usually indicating multiple keyboards are\n        // pressing keys with the same physical key, or the up event was lost\n        // during a loss of focus. The down event is ignored.\n        callback(true);\n        return;\n      }\n    } else {\n      // A normal down event (whether the system event is a repeat or not).\n      type = kClayKeyEventTypeDown;\n      assert(!had_record);\n      ConvertUtf32ToUtf8_(character_bytes, character);\n      eventual_logical_record = logical_key;\n      result_logical_key = logical_key;\n    }\n  } else {  // isPhysicalDown is false\n    if (last_logical_record == 0) {\n      // The physical key has been released before. It might indicate a missed\n      // event due to loss of focus, or multiple keyboards pressed keys with the\n      // same physical key. Ignore the up event.\n      callback(true);\n      return;\n    } else {\n      // A normal up event.\n      type = kClayKeyEventTypeUp;\n      assert(had_record);\n      // Up events never have character.\n      character_bytes[0] = '\\0';\n      eventual_logical_record = 0;\n      result_logical_key = last_logical_record;\n    }\n  }\n\n  if (eventual_logical_record != 0) {\n    pressingRecords_[physical_key] = eventual_logical_record;\n  } else {\n    auto record_iter = pressingRecords_.find(physical_key);\n    // Assert this in debug mode. But in cases that it doesn't satisfy\n    // (such as due to a bug), make sure the `erase` is only called\n    // with a valid value to avoid crashing.\n    if (record_iter != pressingRecords_.end()) {\n      pressingRecords_.erase(record_iter);\n    } else {\n      assert(false);\n    }\n  }\n\n  ClayKeyEvent key_data{\n      .struct_size = sizeof(ClayKeyEvent),\n      .timestamp = static_cast<double>(\n          std::chrono::duration_cast<std::chrono::microseconds>(\n              std::chrono::high_resolution_clock::now().time_since_epoch())\n              .count()),\n      .type = type,\n      .physical = physical_key,\n      .logical = result_logical_key,\n      .character = character_bytes,\n      .synthesized = false,\n  };\n\n  response_id_ += 1;\n  uint64_t response_id = response_id_;\n  PendingResponse pending{\n      .callback =\n          [this, callback = std::move(callback)](bool handled,\n                                                 uint64_t response_id) {\n            auto found = pending_responses_.find(response_id);\n            if (found != pending_responses_.end()) {\n              pending_responses_.erase(found);\n            }\n            callback(handled);\n          },\n      .response_id = response_id,\n  };\n  auto pending_ptr = std::make_unique<PendingResponse>(std::move(pending));\n  pending_responses_[response_id] = std::move(pending_ptr);\n  SendEvent(key_data, KeyboardKeyEmbedderHandler::HandleResponse,\n            reinterpret_cast<void*>(pending_responses_[response_id].get()));\n\n  // Post-event synchronization. It is useful in cases where the true pressing\n  // state does not match the event type. For example, a CapsLock down event is\n  // received despite that GetKeyState says that CapsLock is not pressed. In\n  // such case, post-event synchronization will synthesize a CapsLock up event\n  // after the main event.\n  SynchronizeCriticalPressedStates(key, physical_key, is_event_down,\n                                   event_key_can_be_repeat);\n}\n\nvoid KeyboardKeyEmbedderHandler::KeyboardHook(\n    int key, int scancode, int action, char32_t character, bool extended,\n    bool was_down, std::function<void(bool)> callback) {\n  sent_any_events = false;\n  KeyboardHookImpl(key, scancode, action, character, extended, was_down,\n                   std::move(callback));\n  if (!sent_any_events) {\n    ClayKeyEvent empty_event{\n        .struct_size = sizeof(ClayKeyEvent),\n        .timestamp = static_cast<double>(\n            std::chrono::duration_cast<std::chrono::microseconds>(\n                std::chrono::high_resolution_clock::now().time_since_epoch())\n                .count()),\n        .type = kClayKeyEventTypeDown,\n        .physical = 0,\n        .logical = 0,\n        .character = empty_character,\n        .synthesized = false,\n    };\n    SendEvent(empty_event, nullptr, nullptr);\n  }\n}\n\nvoid KeyboardKeyEmbedderHandler::UpdateLastSeenCriticalKey(\n    int virtual_key, uint64_t physical_key, uint64_t logical_key) {\n  auto found = critical_keys_.find(virtual_key);\n  if (found != critical_keys_.end()) {\n    found->second.physical_key = physical_key;\n    found->second.logical_key = logical_key;\n  }\n}\n\nvoid KeyboardKeyEmbedderHandler::SynchronizeCriticalToggledStates(\n    int event_virtual_key, bool is_event_down, bool* event_key_can_be_repeat) {\n  //    NowState ---------------->  PreEventState --------------> TrueState\n  //              Synchronization                      Event\n  for (auto& kv : critical_keys_) {\n    UINT virtual_key = kv.first;\n    CriticalKey& key_info = kv.second;\n    if (key_info.physical_key == 0) {\n      // Never seen this key.\n      continue;\n    }\n    assert(key_info.logical_key != 0);\n\n    // Check toggling state first, because it might alter pressing state.\n    if (key_info.check_toggled) {\n      const bool target_is_pressed =\n          pressingRecords_.find(key_info.physical_key) !=\n          pressingRecords_.end();\n      // The togglable keys observe a 4-phase cycle:\n      //\n      //  Phase#   0          1          2         3\n      //   Event       Down        Up        Down      Up\n      // Pressed   0          1          0         1\n      // Toggled   0          1          1         0\n      const bool true_toggled = get_key_state_(virtual_key) & kStateMaskToggled;\n      bool pre_event_toggled = true_toggled;\n      // Check if the main event's key is the key being checked. If it's the\n      // non-repeat down event, toggle the state.\n      if (virtual_key == event_virtual_key && !target_is_pressed &&\n          is_event_down) {\n        pre_event_toggled = !pre_event_toggled;\n      }\n      if (key_info.toggled_on != pre_event_toggled) {\n        // If the key is pressed, release it first.\n        if (target_is_pressed) {\n          SendEvent(\n              SynthesizeSimpleEvent(kClayKeyEventTypeUp, key_info.physical_key,\n                                    key_info.logical_key, empty_character),\n              nullptr, nullptr);\n        }\n        // Synchronizing toggle state always ends with the key being pressed.\n        pressingRecords_[key_info.physical_key] = key_info.logical_key;\n        SendEvent(\n            SynthesizeSimpleEvent(kClayKeyEventTypeDown, key_info.physical_key,\n                                  key_info.logical_key, empty_character),\n            nullptr, nullptr);\n        *event_key_can_be_repeat = false;\n      }\n      key_info.toggled_on = true_toggled;\n    }\n  }\n}\n\nvoid KeyboardKeyEmbedderHandler::SynchronizeCriticalPressedStates(\n    int event_virtual_key, int event_physical_key, bool is_event_down,\n    bool event_key_can_be_repeat) {\n  // During an incoming event, there might be a synthesized Flutter event for\n  // each key of each pressing goal, followed by an eventual main Flutter\n  // event.\n  //\n  //    NowState ---------------->  PreEventState --------------> TrueState\n  //              Synchronization                      Event\n  //\n  // The goal of the synchronization algorithm is to derive a pre-event state\n  // that can satisfy the true state (`true_pressed`) after the event, and that\n  // requires as few synthesized events based on the current state\n  // (`now_pressed`) as possible.\n  for (auto& kv : critical_keys_) {\n    UINT virtual_key = kv.first;\n    CriticalKey& key_info = kv.second;\n    if (key_info.physical_key == 0) {\n      // Never seen this key.\n      continue;\n    }\n    assert(key_info.logical_key != 0);\n    if (key_info.check_pressed) {\n      SHORT true_pressed = get_key_state_(virtual_key) & kStateMaskPressed;\n      auto pressing_record_iter = pressingRecords_.find(key_info.physical_key);\n      bool now_pressed = pressing_record_iter != pressingRecords_.end();\n      bool pre_event_pressed = true_pressed;\n      // Check if the main event is the key being checked to get the correct\n      // target state.\n      if (is_event_down) {\n        // For down events, this key is the event key if they have the same\n        // virtual key, because virtual key represents \"functionality.\"\n        //\n        // In that case, normally Flutter should synthesize nothing since the\n        // resulting event can adapt to the current state by dispatching either\n        // a down or a repeat event. However, in certain cases (when Flutter has\n        // just synchronized the key's toggling state) the event must not be a\n        // repeat event.\n        if (virtual_key == event_virtual_key) {\n          if (event_key_can_be_repeat) {\n            continue;\n          } else {\n            pre_event_pressed = false;\n          }\n        }\n      } else {\n        // For up events, this key is the event key if they have the same\n        // physical key, because it is necessary to ensure that the physical\n        // key is correctly released.\n        //\n        // In that case, although the previous state should be pressed, don't\n        // synthesize a down event even if it's not. The later code will handle\n        // such cases by skipping abrupt up events. Obviously don't synthesize\n        // up events either.\n        if (event_physical_key == key_info.physical_key) {\n          continue;\n        }\n      }\n      if (now_pressed != pre_event_pressed) {\n        if (now_pressed) {\n          pressingRecords_.erase(pressing_record_iter);\n        } else {\n          pressingRecords_[key_info.physical_key] = key_info.logical_key;\n        }\n        const char* empty_character = \"\";\n        SendEvent(\n            SynthesizeSimpleEvent(\n                now_pressed ? kClayKeyEventTypeUp : kClayKeyEventTypeDown,\n                key_info.physical_key, key_info.logical_key, empty_character),\n            nullptr, nullptr);\n      }\n    }\n  }\n}\n\nvoid KeyboardKeyEmbedderHandler::SyncModifiersIfNeeded(int modifiers_state) {\n  // TODO(bleroux): consider exposing these constants in flutter_key_map.g.cc?\n  const uint64_t physical_shift_left =\n      windowsToPhysicalMap_.at(kScanCodeShiftLeft);\n  const uint64_t physical_shift_right =\n      windowsToPhysicalMap_.at(kScanCodeShiftRight);\n  const uint64_t logical_shift_left =\n      windowsToLogicalMap_.at(kKeyCodeShiftLeft);\n  const uint64_t physical_control_left =\n      windowsToPhysicalMap_.at(kScanCodeControlLeft);\n  const uint64_t physical_control_right =\n      windowsToPhysicalMap_.at(kScanCodeControlRight);\n  const uint64_t logical_control_left =\n      windowsToLogicalMap_.at(kKeyCodeControlLeft);\n\n  bool shift_pressed = (modifiers_state & kShift) != 0;\n  SynthesizeIfNeeded(physical_shift_left, physical_shift_right,\n                     logical_shift_left, shift_pressed);\n  bool control_pressed = (modifiers_state & kControl) != 0;\n  SynthesizeIfNeeded(physical_control_left, physical_control_right,\n                     logical_control_left, control_pressed);\n}\n\nvoid KeyboardKeyEmbedderHandler::SynthesizeIfNeeded(uint64_t physical_left,\n                                                    uint64_t physical_right,\n                                                    uint64_t logical_left,\n                                                    bool is_pressed) {\n  auto pressing_record_iter_left = pressingRecords_.find(physical_left);\n  bool left_pressed = pressing_record_iter_left != pressingRecords_.end();\n  auto pressing_record_iter_right = pressingRecords_.find(physical_right);\n  bool right_pressed = pressing_record_iter_right != pressingRecords_.end();\n  bool already_pressed = left_pressed || right_pressed;\n  bool synthesize_down = is_pressed && !already_pressed;\n  bool synthesize_up = !is_pressed && already_pressed;\n\n  if (synthesize_down) {\n    SendSynthesizeDownEvent(physical_left, logical_left);\n  }\n\n  if (synthesize_up && left_pressed) {\n    uint64_t known_logical = pressing_record_iter_left->second;\n    SendSynthesizeUpEvent(physical_left, known_logical);\n  }\n\n  if (synthesize_up && right_pressed) {\n    uint64_t known_logical = pressing_record_iter_right->second;\n    SendSynthesizeUpEvent(physical_right, known_logical);\n  }\n}\n\nvoid KeyboardKeyEmbedderHandler::SendSynthesizeDownEvent(uint64_t physical,\n                                                         uint64_t logical) {\n  SendEvent(SynthesizeSimpleEvent(kClayKeyEventTypeDown, physical, logical, \"\"),\n            nullptr, nullptr);\n  pressingRecords_[physical] = logical;\n};\n\nvoid KeyboardKeyEmbedderHandler::SendSynthesizeUpEvent(uint64_t physical,\n                                                       uint64_t logical) {\n  SendEvent(SynthesizeSimpleEvent(kClayKeyEventTypeUp, physical, logical, \"\"),\n            nullptr, nullptr);\n  pressingRecords_.erase(physical);\n};\n\nvoid KeyboardKeyEmbedderHandler::HandleResponse(bool handled, void* user_data) {\n  PendingResponse* pending = reinterpret_cast<PendingResponse*>(user_data);\n  auto callback = std::move(pending->callback);\n  callback(handled, pending->response_id);\n}\n\nvoid KeyboardKeyEmbedderHandler::InitCriticalKeys(\n    MapVirtualKeyToScanCode map_virtual_key_to_scan_code) {\n  auto createCheckedKey = [this, &map_virtual_key_to_scan_code](\n                              UINT virtual_key, bool extended,\n                              bool check_pressed,\n                              bool check_toggled) -> CriticalKey {\n    UINT scan_code = map_virtual_key_to_scan_code(virtual_key, extended);\n    return CriticalKey{\n        .physical_key = GetPhysicalKey(scan_code, extended),\n        .logical_key = GetLogicalKey(virtual_key, extended, scan_code),\n        .check_pressed = check_pressed || check_toggled,\n        .check_toggled = check_toggled,\n        .toggled_on = check_toggled\n                          ? !!(get_key_state_(virtual_key) & kStateMaskToggled)\n                          : false,\n    };\n  };\n\n  critical_keys_.emplace(VK_LSHIFT,\n                         createCheckedKey(VK_LSHIFT, false, true, false));\n  critical_keys_.emplace(VK_RSHIFT,\n                         createCheckedKey(VK_RSHIFT, false, true, false));\n  critical_keys_.emplace(VK_LCONTROL,\n                         createCheckedKey(VK_LCONTROL, false, true, false));\n  critical_keys_.emplace(VK_RCONTROL,\n                         createCheckedKey(VK_RCONTROL, true, true, false));\n  critical_keys_.emplace(VK_LMENU,\n                         createCheckedKey(VK_LMENU, false, true, false));\n  critical_keys_.emplace(VK_RMENU,\n                         createCheckedKey(VK_RMENU, true, true, false));\n  critical_keys_.emplace(VK_LWIN, createCheckedKey(VK_LWIN, true, true, false));\n  critical_keys_.emplace(VK_RWIN, createCheckedKey(VK_RWIN, true, true, false));\n  critical_keys_.emplace(VK_CAPITAL,\n                         createCheckedKey(VK_CAPITAL, false, true, true));\n  critical_keys_.emplace(VK_SCROLL,\n                         createCheckedKey(VK_SCROLL, false, true, true));\n  critical_keys_.emplace(VK_NUMLOCK,\n                         createCheckedKey(VK_NUMLOCK, true, true, true));\n}\n\nvoid KeyboardKeyEmbedderHandler::ConvertUtf32ToUtf8_(char* out, char32_t ch) {\n  if (ch == 0) {\n    out[0] = '\\0';\n    return;\n  }\n  std::string result = ConvertChar32ToUtf8(ch);\n  strcpy_s(out, kCharacterCacheSize, result.c_str());\n}\n\nClayKeyEvent KeyboardKeyEmbedderHandler::SynthesizeSimpleEvent(\n    ClayKeyEventType type, uint64_t physical, uint64_t logical,\n    const char* character) {\n  return ClayKeyEvent{\n      .struct_size = sizeof(ClayKeyEvent),\n      .timestamp = static_cast<double>(\n          std::chrono::duration_cast<std::chrono::microseconds>(\n              std::chrono::high_resolution_clock::now().time_since_epoch())\n              .count()),\n      .type = type,\n      .physical = physical,\n      .logical = logical,\n      .character = character,\n      .synthesized = true,\n  };\n}\n\nvoid KeyboardKeyEmbedderHandler::SendEvent(const ClayKeyEvent& event,\n                                           ClayKeyEventCallback callback,\n                                           void* user_data) {\n  sent_any_events = true;\n  perform_send_event_(event, callback, user_data);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/keyboard_key_embedder_handler.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_EMBEDDER_HANDLER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_EMBEDDER_HANDLER_H_\n\n#include <functional>\n#include <map>\n#include <memory>\n#include <string>\n\n#include \"clay/public/clay.h\"\n#include \"clay/shell/platform/windows/keyboard_key_handler.h\"\n\nnamespace clay {\n\n// Encode a 32-bit unicode code point into a UTF-8 byte array.\n//\n// See https://en.wikipedia.org/wiki/UTF-8#Encoding for the algorithm.\nstd::string ConvertChar32ToUtf8(char32_t ch);\n\n// A delegate of |KeyboardKeyHandler| that handles events by sending\n// converted |FlutterKeyEvent|s through the embedder API.\n//\n// This class communicates with the HardwareKeyboard API in the framework.\n//\n// Every key event must result in at least one FlutterKeyEvent, even an empty\n// one (both logical and physical IDs are 0). This ensures that raw key\n// messages are always preceded by key data so that the transit mode is\n// correctly inferred. (Technically only the first key event needs so, but for\n// simplicity.)\nclass KeyboardKeyEmbedderHandler\n    : public KeyboardKeyHandler::KeyboardKeyHandlerDelegate {\n public:\n  using SendEventHandler = std::function<void(\n      const ClayKeyEvent& /* event */, ClayKeyEventCallback /* callback */,\n      void* /* user_data */)>;\n  using GetKeyStateHandler = std::function<SHORT(int /* nVirtKey */)>;\n  using MapVirtualKeyToScanCode =\n      std::function<SHORT(UINT /* nVirtKey */, bool /* extended */)>;\n\n  // Build a KeyboardKeyEmbedderHandler.\n  //\n  // Use `send_event` to define how the class should dispatch converted\n  // flutter events, as well as how to receive the response, to the engine. It's\n  // typically FlutterWindowsEngine::SendKeyEvent. The 2nd and 3rd parameter\n  // of the SendEventHandler call might be nullptr.\n  //\n  // Use `get_key_state` to define how the class should get a reliable result of\n  // the state for a virtual key. It's typically Win32's GetKeyState, but can\n  // also be nullptr (for UWP).\n  //\n  // Use `map_vk_to_scan` to define how the class should get map a virtual key\n  // to a scan code. It's typically Win32's MapVirtualKey, but can also be\n  // nullptr (for UWP).\n  explicit KeyboardKeyEmbedderHandler(SendEventHandler send_event,\n                                      GetKeyStateHandler get_key_state,\n                                      MapVirtualKeyToScanCode map_vk_to_scan);\n\n  virtual ~KeyboardKeyEmbedderHandler();\n\n  // |KeyboardHandlerBase|\n  void KeyboardHook(int key, int scancode, int action, char32_t character,\n                    bool extended, bool was_down,\n                    std::function<void(bool)> callback) override;\n\n  void SyncModifiersIfNeeded(int modifiers_state) override;\n\n private:\n  struct PendingResponse {\n    std::function<void(bool, uint64_t)> callback;\n    uint64_t response_id;\n  };\n\n  // The information for a virtual key that's important enough that its\n  // state is checked after every event.\n  struct CriticalKey {\n    // Last seen value of physical key and logical key for the virtual key.\n    //\n    // Used to synthesize down events.\n    uint64_t physical_key;\n    uint64_t logical_key;\n\n    // Whether to ensure the pressing state of the key (usually for modifier\n    // keys).\n    bool check_pressed;\n    // Whether to ensure the toggled state of the key (usually for lock keys).\n    //\n    // If this is true, `check_pressed` must be true.\n    bool check_toggled;\n    // Whether the lock key is currently toggled on.\n    bool toggled_on;\n  };\n\n  // Implements the core logic of |KeyboardHook|, leaving out some state\n  // guards.\n  void KeyboardHookImpl(int key, int scancode, int action, char32_t character,\n                        bool extended, bool was_down,\n                        std::function<void(bool)> callback);\n\n  // Assign |critical_keys_| with basic information.\n  void InitCriticalKeys(MapVirtualKeyToScanCode map_virtual_key_to_scan_code);\n  // Update |critical_keys_| with last seen logical and physical key.\n  void UpdateLastSeenCriticalKey(int virtual_key, uint64_t physical_key,\n                                 uint64_t logical_key);\n  // Check each key's state from |get_key_state_| and synthesize events\n  // if their toggling states have been desynchronized.\n  void SynchronizeCriticalToggledStates(int event_virtual_key,\n                                        bool is_event_down,\n                                        bool* event_key_can_be_repeat);\n  // Check each key's state from |get_key_state_| and synthesize events\n  // if their pressing states have been desynchronized.\n  void SynchronizeCriticalPressedStates(int event_virtual_key,\n                                        int event_physical_key,\n                                        bool is_event_down,\n                                        bool event_key_can_be_repeat);\n\n  // Wraps perform_send_event_ with state tracking. Use this instead of\n  // |perform_send_event_| to send events to the framework.\n  void SendEvent(const ClayKeyEvent& event, ClayKeyEventCallback callback,\n                 void* user_data);\n\n  // Send a synthesized down event and update pressing records.\n  void SendSynthesizeDownEvent(uint64_t physical, uint64_t logical);\n\n  // Send a synthesized up event and update pressing records.\n  void SendSynthesizeUpEvent(uint64_t physical, uint64_t logical);\n\n  // Send a synthesized up or down event depending on the current pressing\n  // state.\n  void SynthesizeIfNeeded(uint64_t physical_left, uint64_t physical_right,\n                          uint64_t logical_left, bool is_pressed);\n\n  std::function<void(const ClayKeyEvent&, ClayKeyEventCallback, void*)>\n      perform_send_event_;\n  GetKeyStateHandler get_key_state_;\n\n  // A map from physical keys to logical keys, each entry indicating a pressed\n  // key.\n  std::map<uint64_t, uint64_t> pressingRecords_;\n  // Information for key events that have been sent to the framework but yet\n  // to receive the response. Indexed by response IDs.\n  std::map<uint64_t, std::unique_ptr<PendingResponse>> pending_responses_;\n  // A self-incrementing integer, used as the ID for the next entry for\n  // |pending_responses_|.\n  uint64_t response_id_;\n  // Whether any events has been sent with |PerformSendEvent| during a\n  // |KeyboardHook|.\n  bool sent_any_events;\n\n  // Important keys whose states are checked and guaranteed synchronized\n  // on every key event.\n  //\n  // The following maps map Win32 virtual key to the physical key and logical\n  // key they're last seen.\n  std::map<UINT, CriticalKey> critical_keys_;\n\n  static uint64_t GetPhysicalKey(int scancode, bool extended);\n  static uint64_t GetLogicalKey(int key, bool extended, int scancode);\n  static void HandleResponse(bool handled, void* user_data);\n  static void ConvertUtf32ToUtf8_(char* out, char32_t ch);\n  static ClayKeyEvent SynthesizeSimpleEvent(ClayKeyEventType type,\n                                            uint64_t physical, uint64_t logical,\n                                            const char* character);\n  static uint64_t ApplyPlaneToId(uint64_t id, uint64_t plane);\n\n  static std::map<uint64_t, uint64_t> windowsToPhysicalMap_;\n  static std::map<uint64_t, uint64_t> windowsToLogicalMap_;\n  static std::map<uint64_t, uint64_t> scanCodeToLogicalMap_;\n\n  // Mask for the 32-bit value portion of the key code.\n  static const uint64_t valueMask;\n\n  // The plane value for keys which have a Unicode representation.\n  static const uint64_t unicodePlane;\n\n  // The plane value for the private keys defined by the GTK embedding.\n  static const uint64_t windowsPlane;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_EMBEDDER_HANDLER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/keyboard_key_handler.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/keyboard_key_handler.h\"\n\n#include <windows.h>\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/platform/windows/keyboard_utils.h\"\n\nnamespace clay {\n\nnamespace {\n\n// The maximum number of pending events to keep before\n// emitting a warning on the console about unhandled events.\nstatic constexpr int kMaxPendingEvents = 1000;\n\n}  // namespace\n\nKeyboardKeyHandler::KeyboardKeyHandlerDelegate::~KeyboardKeyHandlerDelegate() =\n    default;\n\nKeyboardKeyHandler::KeyboardKeyHandler() : last_sequence_id_(1) {}\n\nKeyboardKeyHandler::~KeyboardKeyHandler() = default;\n\nvoid KeyboardKeyHandler::AddDelegate(\n    std::unique_ptr<KeyboardKeyHandlerDelegate> delegate) {\n  delegates_.push_back(std::move(delegate));\n}\n\nvoid KeyboardKeyHandler::SyncModifiersIfNeeded(int modifiers_state) {\n  // Only call SyncModifierIfNeeded on the key embedder handler.\n  auto& key_embedder_handler = delegates_.front();\n  key_embedder_handler->SyncModifiersIfNeeded(modifiers_state);\n}\n\nvoid KeyboardKeyHandler::KeyboardHook(int key, int scancode, int action,\n                                      char32_t character, bool extended,\n                                      bool was_down,\n                                      KeyEventCallback callback) {\n  std::unique_ptr<PendingEvent> incoming = std::make_unique<PendingEvent>();\n\n  uint64_t sequence_id = ++last_sequence_id_;\n  incoming->sequence_id = sequence_id;\n  incoming->unreplied = delegates_.size();\n  incoming->any_handled = false;\n  incoming->callback = std::move(callback);\n\n  if (pending_responds_.size() > kMaxPendingEvents) {\n    FML_LOG(ERROR)\n        << \"There are \" << pending_responds_.size()\n        << \" keyboard events that have not yet received a response from the \"\n        << \"framework. Are responses being sent?\";\n  }\n  pending_responds_.push_back(std::move(incoming));\n\n  for (const auto& delegate : delegates_) {\n    delegate->KeyboardHook(key, scancode, action, character, extended, was_down,\n                           [sequence_id, this](bool handled) {\n                             ResolvePendingEvent(sequence_id, handled);\n                           });\n  }\n\n  // |ResolvePendingEvent| might trigger redispatching synchronously,\n  // which might occur before |KeyboardHook| is returned. This won't\n  // make events out of order though, because |KeyboardHook| will always\n  // return true at this time, preventing this event from affecting\n  // others.\n}\n\nvoid KeyboardKeyHandler::ResolvePendingEvent(uint64_t sequence_id,\n                                             bool handled) {\n  // Find the pending event\n  for (auto iter = pending_responds_.begin(); iter != pending_responds_.end();\n       ++iter) {\n    if ((*iter)->sequence_id == sequence_id) {\n      PendingEvent& event = **iter;\n      event.any_handled = event.any_handled || handled;\n      event.unreplied -= 1;\n      assert(event.unreplied >= 0);\n      // If all delegates have replied, report if any of them handled the event.\n      if (event.unreplied == 0) {\n        std::unique_ptr<PendingEvent> event_ptr = std::move(*iter);\n        pending_responds_.erase(iter);\n        event.callback(event.any_handled);\n      }\n      // Return here; |iter| can't do ++ after erase.\n      return;\n    }\n  }\n  // The pending event should always be found.\n  assert(false);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/keyboard_key_handler.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_HANDLER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_HANDLER_H_\n\n#include <windows.h>\n\n#include <deque>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/shell/platform/windows/keyboard_handler_base.h\"\n\nnamespace clay {\n\n// Handles key events.\n//\n// This class detects whether an incoming event is a redispatched one,\n// dispatches native events to delegates and collect their responses,\n// and redispatches events unhandled by Flutter back to the system.\n// See |KeyboardHook| for more information about dispatching.\n//\n// This class owns multiple |KeyboardKeyHandlerDelegate|s, which\n// implements the exact behavior to asynchronously handle events.\nclass KeyboardKeyHandler : public KeyboardHandlerBase {\n public:\n  // An interface for concrete definition of how to asynchronously handle key\n  // events.\n  class KeyboardKeyHandlerDelegate {\n   public:\n    // Defines how to how to asynchronously handle key events.\n    //\n    // |KeyboardHook| should invoke |callback| with the response (whether the\n    // event is handled) later for exactly once.\n    virtual void KeyboardHook(int key, int scancode, int action,\n                              char32_t character, bool extended, bool was_down,\n                              KeyEventCallback callback) = 0;\n\n    virtual void SyncModifiersIfNeeded(int modifiers_state) = 0;\n\n    virtual ~KeyboardKeyHandlerDelegate();\n  };\n\n  // Create a KeyboardKeyHandler.\n  explicit KeyboardKeyHandler();\n\n  ~KeyboardKeyHandler();\n\n  // Add a delegate that handles events received by |KeyboardHook|.\n  void AddDelegate(std::unique_ptr<KeyboardKeyHandlerDelegate> delegate);\n\n  // Synthesize modifier keys events if needed.\n  void SyncModifiersIfNeeded(int modifiers_state) override;\n\n  // Handles a key event.\n  //\n  // Returns whether this handler claims to handle the event, which is true if\n  // and only if the event is a non-synthesized event.\n  //\n  // Windows requires a synchronous response of whether a key event should be\n  // handled, while the query to Flutter is always asynchronous. This is\n  // resolved by the \"redispatching\" algorithm: by default, the response to a\n  // fresh event is always always true. The event is then sent to the framework.\n  // If the framework later decides not to handle the event, this class will\n  // create an identical event and dispatch it to the system, and remember all\n  // synthesized events. The fist time an exact event (by |ComputeEventHash|) is\n  // received in the future, the new event is considered a synthesized one,\n  // causing |KeyboardHook| to return false to fall back to other keyboard\n  // handlers.\n  //\n  // Whether a non-synthesized event is considered handled by the framework is\n  // decided by dispatching the event to all delegates, simultaneously,\n  // unconditionally, in insertion order, and collecting their responses later.\n  // It's not supported to prevent any delegates to process the events, because\n  // in reality this will only support 2 hardcoded delegates, and only to\n  // continue supporting the legacy API (channel) during the deprecation window,\n  // after which the channel delegate should be removed.\n  //\n  // Inherited from |KeyboardHandlerBase|.\n  void KeyboardHook(int key, int scancode, int action, char32_t character,\n                    bool extended, bool was_down,\n                    KeyEventCallback callback) override;\n\n private:\n  struct PendingEvent {\n    // Self-incrementing ID attached to an event sent to the framework.\n    uint64_t sequence_id;\n    // The number of delegates that haven't replied.\n    size_t unreplied;\n    // Whether any replied delegates reported true (handled).\n    bool any_handled;\n\n    // Where to report the delegates' result to.\n    //\n    // Typically a callback provided by KeyboardManager32.\n    KeyEventCallback callback;\n  };\n\n  void ResolvePendingEvent(uint64_t sequence_id, bool handled);\n\n  std::vector<std::unique_ptr<KeyboardKeyHandlerDelegate>> delegates_;\n\n  // The queue of key events that have been sent to the framework but have not\n  // yet received a response.\n  std::deque<std::unique_ptr<PendingEvent>> pending_responds_;\n\n  // The sequence_id attached to the last event sent to the framework.\n  uint64_t last_sequence_id_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_KEY_HANDLER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/keyboard_manager.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/keyboard_manager.h\"\n\n#include <assert.h>\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/shell/platform/windows/keyboard_utils.h\"\n\nnamespace clay {\n\nnamespace {\n\n// The maximum number of pending events to keep before\n// emitting a warning on the console about unhandled events.\nstatic constexpr int kMaxPendingEvents = 1000;\n\n// Returns true if this key is an AltRight key down event.\n//\n// This is used to resolve an issue where an AltGr press causes CtrlLeft to hang\n// when pressed, as reported in https://github.com/flutter/flutter/issues/78005.\n//\n// When AltGr is pressed (in a supporting layout such as Spanish), Win32 first\n// fires a fake CtrlLeft down event, then an AltRight down event.\n// This is significant because this fake CtrlLeft down event will not be paired\n// with a up event, which is fine until Flutter redispatches the CtrlDown\n// event, which Win32 then interprets as a real event, leaving both Win32 and\n// the Flutter framework thinking that CtrlLeft is still pressed.\n//\n// To resolve this, Flutter recognizes this fake CtrlLeft down event using the\n// following AltRight down event. Flutter then forges a CtrlLeft key up event\n// immediately after the corresponding AltRight key up event.\n//\n// One catch is that it is impossible to distinguish the fake CtrlLeft down\n// from a normal CtrlLeft down (followed by a AltRight down), since they\n// contain the exactly same information, including the GetKeyState result.\n// Fortunately, this will require the two events to occur *really* close, which\n// would be rare, and a misrecognition would only cause a minor consequence\n// where the CtrlLeft is released early; the later, real, CtrlLeft up event will\n// be ignored.\nstatic bool IsKeyDownAltRight(int action, int virtual_key, bool extended) {\n  return virtual_key == VK_RMENU && extended &&\n         (action == WM_KEYDOWN || action == WM_SYSKEYDOWN);\n}\n\n// Returns true if this key is a key up event of AltRight.\n//\n// This is used to assist a corner case described in |IsKeyDownAltRight|.\nstatic bool IsKeyUpAltRight(int action, int virtual_key, bool extended) {\n  return virtual_key == VK_RMENU && extended &&\n         (action == WM_KEYUP || action == WM_SYSKEYUP);\n}\n\n// Returns true if this key is a key down event of CtrlLeft.\n//\n// This is used to assist a corner case described in |IsKeyDownAltRight|.\nstatic bool IsKeyDownCtrlLeft(int action, int virtual_key) {\n  return virtual_key == VK_LCONTROL &&\n         (action == WM_KEYDOWN || action == WM_SYSKEYDOWN);\n}\n\n// Returns if a character sent by Win32 is a dead key.\nstatic bool IsDeadKey(uint32_t ch) { return (ch & kDeadKeyCharMask) != 0; }\n\nstatic char32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) {\n  return 0x10000 + ((static_cast<char32_t>(high) & 0x000003FF) << 10) +\n         (low & 0x3FF);\n}\n\nstatic uint16_t ResolveKeyCode(uint16_t original, bool extended,\n                               uint8_t scancode) {\n  switch (original) {\n    case VK_SHIFT:\n    case VK_LSHIFT:\n      return MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX);\n    case VK_MENU:\n    case VK_LMENU:\n      return extended ? VK_RMENU : VK_LMENU;\n    case VK_CONTROL:\n    case VK_LCONTROL:\n      return extended ? VK_RCONTROL : VK_LCONTROL;\n    default:\n      return original;\n  }\n}\n\nstatic bool IsPrintable(uint32_t c) {\n  constexpr char32_t kMinPrintable = ' ';\n  constexpr char32_t kDelete = 0x7F;\n  return c >= kMinPrintable && c != kDelete;\n}\n\nstatic bool IsSysAction(UINT action) {\n  return action == WM_SYSKEYDOWN || action == WM_SYSKEYUP ||\n         action == WM_SYSCHAR || action == WM_SYSDEADCHAR;\n}\n\n}  // namespace\n\nKeyboardManager::KeyboardManager(WindowDelegate* delegate)\n    : window_delegate_(delegate),\n      last_key_is_ctrl_left_down(false),\n      should_synthesize_ctrl_left_up(false),\n      processing_event_(false) {}\n\nvoid KeyboardManager::RedispatchEvent(std::unique_ptr<PendingEvent> event) {\n  for (const Win32Message& message : event->session) {\n    // Never redispatch sys keys, because their original messages have been\n    // passed to the system default processor.\n    if (IsSysAction(message.action)) {\n      continue;\n    }\n    pending_redispatches_.push_back(message);\n    UINT result = window_delegate_->Win32DispatchMessage(\n        message.action, message.wparam, message.lparam);\n    if (result != 0) {\n      FML_LOG(ERROR) << \"Unable to synthesize event for keyboard event.\";\n    }\n  }\n  if (pending_redispatches_.size() > kMaxPendingEvents) {\n    FML_LOG(ERROR)\n        << \"There are \" << pending_redispatches_.size()\n        << \" keyboard events that have not yet received a response from the \"\n        << \"framework. Are responses being sent?\";\n  }\n}\n\nbool KeyboardManager::RemoveRedispatchedMessage(UINT const action,\n                                                WPARAM const wparam,\n                                                LPARAM const lparam) {\n  for (auto iter = pending_redispatches_.begin();\n       iter != pending_redispatches_.end(); ++iter) {\n    if (action == iter->action && wparam == iter->wparam) {\n      pending_redispatches_.erase(iter);\n      return true;\n    }\n  }\n  return false;\n}\n\nbool KeyboardManager::HandleMessage(UINT const action, WPARAM const wparam,\n                                    LPARAM const lparam) {\n  if (RemoveRedispatchedMessage(action, wparam, lparam)) {\n    return false;\n  }\n  switch (action) {\n    case WM_DEADCHAR:\n    case WM_SYSDEADCHAR:\n    case WM_CHAR:\n    case WM_SYSCHAR: {\n      const Win32Message message =\n          Win32Message{.action = action, .wparam = wparam, .lparam = lparam};\n      current_session_.push_back(message);\n\n      char32_t code_point;\n      if (message.IsHighSurrogate()) {\n        // A high surrogate is always followed by a low surrogate.  Process the\n        // session later and consider this message as handled.\n        return true;\n      } else if (message.IsLowSurrogate()) {\n        const Win32Message* last_message =\n            current_session_.size() <= 1\n                ? nullptr\n                : &current_session_[current_session_.size() - 2];\n        if (last_message == nullptr || !last_message->IsHighSurrogate()) {\n          return false;\n        }\n        // A low surrogate always follows a high surrogate, marking the end of\n        // a char session. Process the session after the if clause.\n        code_point =\n            CodePointFromSurrogatePair(last_message->wparam, message.wparam);\n      } else {\n        // A non-surrogate character always appears alone. Process the session\n        // after the if clause.\n        code_point = static_cast<wchar_t>(message.wparam);\n      }\n\n      // If this char message is preceded by a key down message, then dispatch\n      // the key down message as a key down event first, and only dispatch the\n      // OnText if the key down event is not handled.\n      if (current_session_.front().IsGeneralKeyDown()) {\n        const Win32Message first_message = current_session_.front();\n        const uint8_t scancode = (lparam >> 16) & 0xff;\n        const uint16_t key_code = first_message.wparam;\n        const bool extended = ((lparam >> 24) & 0x01) == 0x01;\n        const bool was_down = lparam & 0x40000000;\n        // Certain key combinations yield control characters as WM_CHAR's\n        // lParam. For example, 0x01 for Ctrl-A. Filter these characters. See\n        // https://docs.microsoft.com/en-us/windows/win32/learnwin32/accelerator-tables\n        char32_t character;\n        if (action == WM_DEADCHAR || action == WM_SYSDEADCHAR) {\n          // Mask the resulting char with kDeadKeyCharMask anyway, because in\n          // rare cases the bit is *not* set (US INTL Shift-6 circumflex, see\n          // https://github.com/flutter/flutter/issues/92654 .)\n          character =\n              window_delegate_->Win32MapVkToChar(key_code) | kDeadKeyCharMask;\n        } else {\n          character = IsPrintable(code_point) ? code_point : 0;\n        }\n        auto event = std::make_unique<PendingEvent>(PendingEvent{\n            .key = key_code,\n            .scancode = scancode,\n            .action = static_cast<UINT>(action == WM_SYSCHAR ? WM_SYSKEYDOWN\n                                                             : WM_KEYDOWN),\n            .character = character,\n            .extended = extended,\n            .was_down = was_down,\n            .session = std::move(current_session_),\n        });\n\n        pending_events_.push_back(std::move(event));\n        ProcessNextEvent();\n\n        // SYS messages must not be consumed by `HandleMessage` so that they are\n        // forwarded to the system.\n        return !IsSysAction(action);\n      }\n\n      // If the charcter session is not preceded by a key down message,\n      // mark PendingEvent::action as WM_CHAR, informing |PerformProcessEvent|\n      // to dispatch the text content immediately.\n      //\n      // Only WM_CHAR should be treated as characters. WM_SYS*CHAR are not part\n      // of text input, and WM_DEADCHAR will be incorporated into a later\n      // WM_CHAR with the full character.\n      if (action == WM_CHAR) {\n        auto event = std::make_unique<PendingEvent>(PendingEvent{\n            .action = WM_CHAR,\n            .character = code_point,\n            .session = std::move(current_session_),\n        });\n        pending_events_.push_back(std::move(event));\n        ProcessNextEvent();\n      }\n      return true;\n    }\n\n    case WM_KEYDOWN:\n    case WM_SYSKEYDOWN:\n    case WM_KEYUP:\n    case WM_SYSKEYUP: {\n      if (wparam == VK_PACKET) {\n        return false;\n      }\n\n      const uint8_t scancode = (lparam >> 16) & 0xff;\n      const bool extended = ((lparam >> 24) & 0x01) == 0x01;\n      // If the key is a modifier, get its side.\n      const uint16_t key_code = ResolveKeyCode(wparam, extended, scancode);\n      const bool was_down = lparam & 0x40000000;\n\n      // Detect a pattern of key events in order to forge a CtrlLeft up event.\n      // See |IsKeyDownAltRight| for explanation.\n      if (IsKeyDownAltRight(action, key_code, extended)) {\n        if (last_key_is_ctrl_left_down) {\n          should_synthesize_ctrl_left_up = true;\n        }\n      }\n      if (IsKeyDownCtrlLeft(action, key_code)) {\n        last_key_is_ctrl_left_down = true;\n        ctrl_left_scancode = scancode;\n        should_synthesize_ctrl_left_up = false;\n      } else {\n        last_key_is_ctrl_left_down = false;\n      }\n      if (IsKeyUpAltRight(action, key_code, extended)) {\n        if (should_synthesize_ctrl_left_up) {\n          should_synthesize_ctrl_left_up = false;\n          const LPARAM lParam =\n              (1 /* repeat_count */ << 0) | (ctrl_left_scancode << 16) |\n              (0 /* extended */ << 24) | (1 /* prev_state */ << 30) |\n              (1 /* transition */ << 31);\n          window_delegate_->Win32DispatchMessage(WM_KEYUP, VK_CONTROL, lParam);\n        }\n      }\n\n      current_session_.clear();\n      current_session_.push_back(\n          Win32Message{.action = action, .wparam = wparam, .lparam = lparam});\n      const bool is_keydown_message =\n          (action == WM_KEYDOWN || action == WM_SYSKEYDOWN);\n      // Check if this key produces a character by peeking if this key down\n      // message has a following char message. Certain key messages are not\n      // followed by char messages even though `MapVirtualKey` returns a valid\n      // character (such as Ctrl + Digit, see\n      // https://github.com/flutter/flutter/issues/85587 ).\n      unsigned int character = window_delegate_->Win32MapVkToChar(wparam);\n      UINT next_key_action = PeekNextMessageType(WM_KEYFIRST, WM_KEYLAST);\n      bool has_char_action =\n          (next_key_action == WM_DEADCHAR ||\n           next_key_action == WM_SYSDEADCHAR || next_key_action == WM_CHAR ||\n           next_key_action == WM_SYSCHAR);\n      if (character > 0 && is_keydown_message && has_char_action) {\n        // This key down message has a following char message. Process this\n        // session in the char message, because the character for the key call\n        // should be decided by the char events. Consider this message as\n        // handled.\n        return true;\n      }\n\n      // This key down message is not followed by a char message. Conclude this\n      // session.\n      auto event = std::make_unique<PendingEvent>(PendingEvent{\n          .key = key_code,\n          .scancode = scancode,\n          .action = action,\n          .character = 0,\n          .extended = extended,\n          .was_down = was_down,\n          .session = std::move(current_session_),\n      });\n      pending_events_.push_back(std::move(event));\n      ProcessNextEvent();\n      // SYS messages must not be consumed by `HandleMessage` so that they are\n      // forwarded to the system.\n      return !IsSysAction(action);\n    }\n    default:\n      assert(false);\n  }\n  return false;\n}\n\nvoid KeyboardManager::ProcessNextEvent() {\n  if (processing_event_ || pending_events_.empty()) {\n    return;\n  }\n  processing_event_ = true;\n  auto pending_event = std::move(pending_events_.front());\n  pending_events_.pop_front();\n  PerformProcessEvent(std::move(pending_event), [this] {\n    assert(processing_event_);\n    processing_event_ = false;\n    ProcessNextEvent();\n  });\n}\n\nvoid KeyboardManager::PerformProcessEvent(std::unique_ptr<PendingEvent> event,\n                                          std::function<void()> callback) {\n  // PendingEvent::action being WM_CHAR means this is a char message without\n  // a preceding key message, and should be dispatched immediately.\n  if (event->action == WM_CHAR) {\n    DispatchText(*event);\n    callback();\n    return;\n  }\n\n  // A unique_ptr can't be sent into a lambda without C++23's\n  // move_only_function. Until then, `event` is sent as a raw pointer, hoping\n  // WindowDelegate::OnKey to correctly call it once and only once.\n  PendingEvent* event_p = event.release();\n  window_delegate_->OnKey(\n      event_p->key, event_p->scancode, event_p->action, event_p->character,\n      event_p->extended, event_p->was_down,\n      [this, event_p, callback = std::move(callback)](bool handled) {\n        HandleOnKeyResult(std::unique_ptr<PendingEvent>(event_p), handled);\n        callback();\n      });\n}\n\nvoid KeyboardManager::HandleOnKeyResult(std::unique_ptr<PendingEvent> event,\n                                        bool framework_handled) {\n  const UINT last_action = event->session.back().action;\n  // SYS messages must not be redispached, and their text content is not\n  // dispatched either.\n  bool handled = framework_handled || IsSysAction(last_action);\n\n  if (handled) {\n    return;\n  }\n\n  // Only WM_CHAR should be treated as characters. WM_SYS*CHAR are not part of\n  // text input, and WM_DEADCHAR will be incorporated into a later WM_CHAR with\n  // the full character.\n  if (last_action == WM_CHAR) {\n    DispatchText(*event);\n  }\n\n  RedispatchEvent(std::move(event));\n}\n\nvoid KeyboardManager::DispatchText(const PendingEvent& event) {\n  // Check if the character is printable based on the last wparam, which works\n  // even if the last wparam is a low surrogate, because the only unprintable\n  // keys defined by `IsPrintable` are certain characters at lower ASCII range.\n  // These ASCII control characters are sent as WM_CHAR events for all control\n  // key shortcuts.\n  assert(!event.session.empty());\n  bool is_printable = IsPrintable(event.session.back().wparam);\n  bool valid = event.character != 0 && is_printable;\n  if (valid) {\n    auto text = EncodeUtf16(event.character);\n    window_delegate_->OnText(text);\n  }\n}\n\nUINT KeyboardManager::PeekNextMessageType(UINT wMsgFilterMin,\n                                          UINT wMsgFilterMax) {\n  MSG next_message;\n  BOOL has_msg = window_delegate_->Win32PeekMessage(\n      &next_message, wMsgFilterMin, wMsgFilterMax, PM_NOREMOVE);\n  if (!has_msg) {\n    return 0;\n  }\n  return next_message.message;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/keyboard_manager.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_\n\n#include <windows.h>\n\n#include <atomic>\n#include <deque>\n#include <functional>\n#include <map>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace clay {\n\n// Handles keyboard and text messages on Win32.\n//\n// |KeyboardManager| consumes raw Win32 messages related to key and chars, and\n// converts them to key calls (|WindowDelegate::OnKey|) and possibly text calls\n// (|WindowDelegate::OnText|).\n//\n// |KeyboardManager| requires a |WindowDelegate| to define how to access Win32\n// system calls (to allow mocking) and where to send the results of key calls\n// and text calls to.\n//\n// Typically, |KeyboardManager| is owned by a |Window|, which also implements\n// the window delegate. The key calls and text calls are forwarded to those of\n// |Window|'s, and consequently, those of |FlutterWindowsView|'s.\n//\n// ## Terminology\n//\n// The keyboard system follows the following terminology instead of the\n// inconsistent/incomplete one used by Win32:\n//\n//  * Message: An invocation of |WndProc|, which consists of an\n//    action, an lparam, and a wparam.\n//  * Action: The type of a message.\n//  * Session: One to three messages that should be processed together, such\n//    as a key down message followed by char messages.\n//  * Event: A FlutterKeyEvent/ui.KeyData sent to the framework.\n//  * Call: A call to |WindowDelegate::OnKey| or |WindowDelegate::OnText|,\n//    which contains semi-processed messages.\nclass KeyboardManager {\n public:\n  // Define how the keyboard manager accesses Win32 system calls (to allow\n  // mocking) and sends key calls and and text calls.\n  //\n  // Typically implemented by |Window|.\n  class WindowDelegate {\n   public:\n    using KeyEventCallback = std::function<void(bool)>;\n\n    virtual ~WindowDelegate() = default;\n\n    // Called when text input occurs.\n    virtual void OnText(const std::u16string& text) = 0;\n\n    // Called when raw keyboard input occurs.\n    //\n    // The `callback` must be called exactly once.\n    virtual void OnKey(int key, int scancode, int action, char32_t character,\n                       bool extended, bool was_down,\n                       KeyEventCallback callback) = 0;\n\n    // Win32's PeekMessage.\n    //\n    // Used to process key messages.\n    virtual BOOL Win32PeekMessage(LPMSG lpMsg, UINT wMsgFilterMin,\n                                  UINT wMsgFilterMax, UINT wRemoveMsg) = 0;\n\n    // Win32's MapVirtualKey(*, MAPVK_VK_TO_CHAR).\n    //\n    // Used to process key messages.\n    virtual uint32_t Win32MapVkToChar(uint32_t virtual_key) = 0;\n\n    // Win32's |SendMessage|.\n    //\n    // Used to synthesize key messages.\n    virtual UINT Win32DispatchMessage(UINT Msg, WPARAM wParam,\n                                      LPARAM lParam) = 0;\n  };\n\n  using KeyEventCallback = WindowDelegate::KeyEventCallback;\n\n  explicit KeyboardManager(WindowDelegate* delegate);\n\n  // Processes Win32 messages related to keyboard and text.\n  //\n  // All messages related to keyboard and text should be sent here without\n  // pre-processing, including WM_{SYS,}KEY{DOWN,UP} and WM_{SYS,}{DEAD,}CHAR.\n  // Other message types will trigger assertion error.\n  //\n  // |HandleMessage| returns true if Flutter keyboard system decides to handle\n  // this message synchronously. It doesn't mean that the Flutter framework\n  // handles it, which is reported asynchronously later. Not handling this\n  // message here usually means that this message is a redispatched message,\n  // but there are other rare cases too. |Window| should forward unhandled\n  // messages to |DefWindowProc|.\n  bool HandleMessage(UINT const message, WPARAM const wparam,\n                     LPARAM const lparam);\n\n protected:\n  struct Win32Message {\n    UINT action;\n    WPARAM wparam;\n    LPARAM lparam;\n\n    bool IsHighSurrogate() const { return IS_HIGH_SURROGATE(wparam); }\n\n    bool IsLowSurrogate() const { return IS_LOW_SURROGATE(wparam); }\n\n    bool IsGeneralKeyDown() const {\n      return action == WM_KEYDOWN || action == WM_SYSKEYDOWN;\n    }\n  };\n\n  struct PendingEvent {\n    WPARAM key;\n    uint8_t scancode;\n    UINT action;\n    char32_t character;\n    bool extended;\n    bool was_down;\n\n    std::vector<Win32Message> session;\n  };\n\n  virtual void RedispatchEvent(std::unique_ptr<PendingEvent> event);\n\n private:\n  using OnKeyCallback =\n      std::function<void(std::unique_ptr<PendingEvent>, bool)>;\n\n  struct PendingText {\n    bool ready;\n    std::u16string content;\n    bool placeholder = false;\n  };\n\n  // Resume processing the pending events.\n  //\n  // If there is at least one pending event and no event is being processed,\n  // the oldest pending event will be handed over to |PerformProcessEvent|.\n  // After the event is processed, the next pending event will be automatically\n  // started, until there are no pending events left.\n  //\n  // Otherwise, this call is a no-op.\n  void ProcessNextEvent();\n\n  // Process an event and call `callback` when it's completed.\n  //\n  // The `callback` is constructed by |ProcessNextEvent| to start the next\n  // event, and must be called exactly once.\n  void PerformProcessEvent(std::unique_ptr<PendingEvent> event,\n                           std::function<void()> callback);\n\n  // Handle the result of |WindowDelegate::OnKey|, possibly dispatching the text\n  // result to |WindowDelegate::OnText| and then redispatching.\n  //\n  // The `pending_text` is either a valid iterator of `pending_texts`, or its\n  // end(). In the latter case, this OnKey message does not contain a text.\n  void HandleOnKeyResult(std::unique_ptr<PendingEvent> event,\n                         bool framework_handled);\n\n  // Dispatch the text content of a |PendingEvent| to |WindowDelegate::OnText|.\n  //\n  // If the content is empty of invalid, |WindowDelegate::OnText| will not be\n  // called.\n  void DispatchText(const PendingEvent& event);\n\n  // Returns the type of the next WM message.\n  //\n  // The parameters limits the range of interested messages. See Win32's\n  // |PeekMessage| for information.\n  //\n  // If there's no message, returns 0.\n  UINT PeekNextMessageType(UINT wMsgFilterMin, UINT wMsgFilterMax);\n\n  // Find an event in the redispatch list that matches the given one.\n  //\n  // If an matching event is found, removes the matching event from the\n  // redispatch list, and returns true. Otherwise, returns false;\n  bool RemoveRedispatchedMessage(UINT action, WPARAM wparam, LPARAM lparam);\n\n  WindowDelegate* window_delegate_;\n\n  // Keeps track of all messages during the current session.\n  //\n  // At the end of a session, it is moved to the `PendingEvent`, which is\n  // passed to `WindowDelegate::OnKey`.\n  std::vector<Win32Message> current_session_;\n\n  // Whether the last event is a CtrlLeft key down.\n  //\n  // This is used to resolve a corner case described in |IsKeyDownAltRight|.\n  bool last_key_is_ctrl_left_down;\n\n  // The scancode of the last met CtrlLeft down.\n  //\n  // This is used to resolve a corner case described in |IsKeyDownAltRight|.\n  uint8_t ctrl_left_scancode;\n\n  // Whether a CtrlLeft up should be synthesized upon the next AltRight up.\n  //\n  // This is used to resolve a corner case described in |IsKeyDownAltRight|.\n  bool should_synthesize_ctrl_left_up;\n\n  // Store the messages coming from |HandleMessage|.\n  //\n  // Only one message is processed at a time. The next one will not start\n  // until the framework has responded to the previous message.\n  std::deque<std::unique_ptr<PendingEvent>> pending_events_;\n\n  // Whether a message is being processed.\n  std::atomic<bool> processing_event_;\n\n  // The queue of messages that have been redispatched to the system but have\n  // not yet been received for a second time.\n  std::deque<Win32Message> pending_redispatches_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/keyboard_utils.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/keyboard_utils.h\"\n\nnamespace clay {\n\nstd::u16string EncodeUtf16(char32_t character) {\n  // Algorithm: https://en.wikipedia.org/wiki/UTF-16#Description\n  std::u16string result;\n  // Invalid value.\n  assert(!(character >= 0xD800 && character <= 0xDFFF) &&\n         !(character > 0x10FFFF));\n  if ((character >= 0xD800 && character <= 0xDFFF) || (character > 0x10FFFF)) {\n    return result;\n  }\n  if (character <= 0xD7FF || (character >= 0xE000 && character <= 0xFFFF)) {\n    result.push_back((char16_t)character);\n    return result;\n  }\n  uint32_t remnant = character - 0x10000;\n  result.push_back((remnant >> 10) + 0xD800);\n  result.push_back((remnant & 0x3FF) + 0xDC00);\n  return result;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/keyboard_utils.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_UTILS_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_UTILS_H_\n\n#include <assert.h>\n#include <stdint.h>\n\n#include <string>\n\nnamespace clay {\n\nconstexpr int kShift = 1 << 0;\nconstexpr int kControl = 1 << 3;\nconstexpr int kScanCodeShiftLeft = 0x2a;\nconstexpr int kScanCodeShiftRight = 0x36;\nconstexpr int kKeyCodeShiftLeft = 0xa0;\nconstexpr int kScanCodeControlLeft = 0x1d;\nconstexpr int kScanCodeControlRight = 0xe01d;\nconstexpr int kKeyCodeControlLeft = 0xa2;\n\n// The bit of a mapped character in a WM_KEYDOWN message that indicates the\n// character is a dead key.\n//\n// When a dead key is pressed, the WM_KEYDOWN's lParam is mapped to a special\n// value: the \"normal character\" | 0x80000000.  For example, when pressing\n// \"dead key caret\" (one that makes the following e into ê), its mapped\n// character is 0x8000005E. \"Reverting\" it gives 0x5E, which is character '^'.\nconstexpr int kDeadKeyCharMask = 0x80000000;\n\n// Revert the \"character\" for a dead key to its normal value, or the argument\n// unchanged otherwise.\ninline uint32_t UndeadChar(uint32_t ch) { return ch & ~kDeadKeyCharMask; }\n\n// Encode a Unicode codepoint into a UTF-16 string.\n//\n// If the codepoint is invalid, this function throws an assertion error, and\n// returns an empty string.\nstd::u16string EncodeUtf16(char32_t character);\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_KEYBOARD_UTILS_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/platform_handler.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/platform_handler.h\"\n\n#include <windows.h>\n\n#include <cstring>\n#include <string>\n\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n#include \"clay/shell/platform/windows/flutter_windows_view.h\"\n\nstatic constexpr int kErrorSuccess = 0;\n\nnamespace clay {\n\nnamespace {\n// A Clipboard wrapper that automatically closes the clipboard when it goes out\n// of scope.\nclass ScopedClipboard : public ScopedClipboardInterface {\n public:\n  ScopedClipboard();\n  virtual ~ScopedClipboard();\n\n  // Prevent copying.\n  ScopedClipboard(ScopedClipboard const&) = delete;\n  ScopedClipboard& operator=(ScopedClipboard const&) = delete;\n\n  int Open(HWND window) override;\n\n  bool HasString() override;\n\n  std::variant<std::wstring, int> GetString() override;\n\n  int SetString(const std::wstring string) override;\n\n private:\n  bool opened_ = false;\n};\n\nScopedClipboard::ScopedClipboard() {}\n\nScopedClipboard::~ScopedClipboard() {\n  if (opened_) {\n    ::CloseClipboard();\n  }\n}\n\nint ScopedClipboard::Open(HWND window) {\n  opened_ = ::OpenClipboard(window);\n\n  if (!opened_) {\n    return ::GetLastError();\n  }\n\n  return kErrorSuccess;\n}\n\nbool ScopedClipboard::HasString() {\n  // Allow either plain text format, since getting data will auto-interpolate.\n  return ::IsClipboardFormatAvailable(CF_UNICODETEXT) ||\n         ::IsClipboardFormatAvailable(CF_TEXT);\n}\n\nstd::variant<std::wstring, int> ScopedClipboard::GetString() {\n  assert(opened_);\n\n  HANDLE data = ::GetClipboardData(CF_UNICODETEXT);\n  if (data == nullptr) {\n    return static_cast<int>(::GetLastError());\n  }\n  ScopedGlobalLock locked_data(data);\n\n  if (!locked_data.get()) {\n    return static_cast<int>(::GetLastError());\n  }\n  return static_cast<wchar_t*>(locked_data.get());\n}\n\nint ScopedClipboard::SetString(const std::wstring string) {\n  assert(opened_);\n  if (!::EmptyClipboard()) {\n    return ::GetLastError();\n  }\n  size_t null_terminated_byte_count =\n      sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1);\n  ScopedGlobalMemory destination_memory(GMEM_MOVEABLE,\n                                        null_terminated_byte_count);\n  ScopedGlobalLock locked_memory(destination_memory.get());\n  if (!locked_memory.get()) {\n    return ::GetLastError();\n  }\n  memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count);\n  if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) {\n    return ::GetLastError();\n  }\n  // The clipboard now owns the global memory.\n  destination_memory.release();\n  return kErrorSuccess;\n}\n\n}  // namespace\n\nPlatformHandler::PlatformHandler(\n    FlutterWindowsEngine* engine,\n    std::optional<std::function<std::unique_ptr<ScopedClipboardInterface>()>>\n        scoped_clipboard_provider)\n    : engine_(engine) {\n  if (scoped_clipboard_provider.has_value()) {\n    scoped_clipboard_provider_ = scoped_clipboard_provider.value();\n  } else {\n    scoped_clipboard_provider_ = []() {\n      return std::make_unique<ScopedClipboard>();\n    };\n  }\n}\n\nPlatformHandler::~PlatformHandler() = default;\n\nvoid PlatformHandler::SetPlainText(const std::string& text) {\n  const FlutterWindowsView* view = engine_->view();\n  if (view == nullptr) {\n    FML_LOG(ERROR) << \"Clipboard is not available in Windows headless mode\";\n    return;\n  }\n  std::unique_ptr<ScopedClipboardInterface> clipboard =\n      scoped_clipboard_provider_();\n  int open_result = clipboard->Open(std::get<HWND>(*view->GetRenderTarget()));\n  if (open_result != kErrorSuccess) {\n    FML_LOG(ERROR) << \"Unable to open clipboard, error_code = \" << open_result;\n    return;\n  }\n  int set_result = clipboard->SetString(fml::Utf8ToWideString(text));\n  if (set_result != kErrorSuccess) {\n    FML_LOG(ERROR) << \"Unable to set clipboard data, error_code = \"\n                   << set_result;\n  }\n}\nconst std::string& PlatformHandler::GetPlainText() const {\n  last_clipboard_text_.clear();\n  const FlutterWindowsView* view = engine_->view();\n  if (view == nullptr) {\n    FML_LOG(ERROR) << \"Clipboard is not available in Windows headless mode\";\n    return std::string();\n  }\n  std::unique_ptr<ScopedClipboardInterface> clipboard =\n      scoped_clipboard_provider_();\n  int open_result = clipboard->Open(std::get<HWND>(*view->GetRenderTarget()));\n  if (open_result != kErrorSuccess) {\n    FML_LOG(ERROR) << \"Unable to open clipboard, error_code = \" << open_result;\n    return std::string();\n  }\n  if (!clipboard->HasString()) {\n    return std::string();\n  }\n  std::variant<std::wstring, int> get_string_result = clipboard->GetString();\n  if (std::holds_alternative<int>(get_string_result)) {\n    FML_LOG(ERROR) << \"Unable to get clipboard data, error_code = \"\n                   << std::get<int>(get_string_result);\n    return std::string();\n  }\n  last_clipboard_text_ =\n      fml::WideStringToUtf8(std::get<std::wstring>(get_string_result));\n  return last_clipboard_text_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/platform_handler.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_H_\n\n#include <Windows.h>\n\n#include <functional>\n#include <memory>\n#include <optional>\n#include <string>\n#include <variant>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\n// A scoped wrapper for GlobalAlloc/GlobalFree.\nclass ScopedGlobalMemory {\n public:\n  // Allocates |bytes| bytes of global memory with the given flags.\n  ScopedGlobalMemory(unsigned int flags, size_t bytes) {\n    memory_ = ::GlobalAlloc(flags, bytes);\n    if (!memory_) {\n      FML_LOG(ERROR) << \"Unable to allocate global memory: \"\n                     << ::GetLastError();\n    }\n  }\n\n  ~ScopedGlobalMemory() {\n    if (memory_) {\n      if (::GlobalFree(memory_) != nullptr) {\n        FML_LOG(ERROR) << \"Failed to free global allocation: \"\n                       << ::GetLastError();\n      }\n    }\n  }\n\n  // Prevent copying.\n  ScopedGlobalMemory(ScopedGlobalMemory const&) = delete;\n  ScopedGlobalMemory& operator=(ScopedGlobalMemory const&) = delete;\n\n  // Returns the memory pointer, which will be nullptr if allocation failed.\n  void* get() { return memory_; }\n\n  void* release() {\n    void* memory = memory_;\n    memory_ = nullptr;\n    return memory;\n  }\n\n private:\n  HGLOBAL memory_;\n};\n\n// A scoped wrapper for GlobalLock/GlobalUnlock.\nclass ScopedGlobalLock {\n public:\n  // Attempts to acquire a global lock on |memory| for the life of this object.\n  ScopedGlobalLock(HGLOBAL memory) {\n    source_ = memory;\n    if (memory) {\n      locked_memory_ = ::GlobalLock(memory);\n      if (!locked_memory_) {\n        FML_LOG(ERROR) << \"Unable to acquire global lock: \" << ::GetLastError();\n      }\n    }\n  }\n\n  ~ScopedGlobalLock() {\n    if (locked_memory_) {\n      if (!::GlobalUnlock(source_)) {\n        DWORD error = ::GetLastError();\n        if (error != NO_ERROR) {\n          FML_LOG(ERROR) << \"Unable to release global lock: \"\n                         << ::GetLastError();\n        }\n      }\n    }\n  }\n\n  // Prevent copying.\n  ScopedGlobalLock(ScopedGlobalLock const&) = delete;\n  ScopedGlobalLock& operator=(ScopedGlobalLock const&) = delete;\n\n  // Returns the locked memory pointer, which will be nullptr if acquiring the\n  // lock failed.\n  void* get() { return locked_memory_; }\n\n private:\n  HGLOBAL source_;\n  void* locked_memory_;\n};\n\nclass FlutterWindowsEngine;\nclass ScopedClipboardInterface;\n\nclass PlatformHandler {\n public:\n  explicit PlatformHandler(\n      FlutterWindowsEngine* engine,\n      std::optional<std::function<std::unique_ptr<ScopedClipboardInterface>()>>\n          scoped_clipboard_provider = std::nullopt);\n\n  virtual ~PlatformHandler();\n\n  // Sets the clipboard's plain text to |text|.\n  void SetPlainText(const std::string& text);\n  // Gets plain text from the clipboard.\n  const std::string& GetPlainText() const;\n\n private:\n  // A reference to the Flutter engine.\n  FlutterWindowsEngine* engine_;\n\n  // A scoped clipboard provider that can be passed in for mocking in tests.\n  // Use this to acquire clipboard in each operation to avoid blocking clipboard\n  // unnecessarily. See flutter/flutter#103205.\n  std::function<std::unique_ptr<ScopedClipboardInterface>()>\n      scoped_clipboard_provider_;\n\n  mutable std::string last_clipboard_text_;\n};\n\n// A public interface for ScopedClipboard, so that it can be injected into\n// PlatformHandler.\nclass ScopedClipboardInterface {\n public:\n  virtual ~ScopedClipboardInterface(){};\n\n  // Attempts to open the clipboard for the given window, returning the error\n  // code in the case of failure and 0 otherwise.\n  virtual int Open(HWND window) = 0;\n\n  // Returns true if there is string data available to get.\n  virtual bool HasString() = 0;\n\n  // Returns string data from the clipboard.\n  //\n  // If getting a string fails, returns the error code.\n  //\n  // Open(...) must have succeeded to call this method.\n  virtual std::variant<std::wstring, int> GetString() = 0;\n\n  // Sets the string content of the clipboard, returning the error code on\n  // failure and 0 otherwise.\n  //\n  // Open(...) must have succeeded to call this method.\n  virtual int SetString(const std::wstring string) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/sequential_id_generator.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/sequential_id_generator.h\"\n\nnamespace clay {\n\nnamespace {\n\n// Removes |key| from |first|, and |first[key]| from |second|.\ntemplate <typename T>\nvoid Remove(uint32_t key, T* first, T* second) {\n  auto iter = first->find(key);\n  if (iter == first->end()) return;\n\n  uint32_t second_key = iter->second;\n  first->erase(iter);\n\n  iter = second->find(second_key);\n  second->erase(iter);\n}\n\n}  // namespace\n\nSequentialIdGenerator::SequentialIdGenerator(uint32_t min_id, uint32_t max_id)\n    : min_id_(min_id), min_available_id_(min_id), max_id_(max_id) {}\n\nSequentialIdGenerator::~SequentialIdGenerator() {}\n\nuint32_t SequentialIdGenerator::GetGeneratedId(uint32_t number) {\n  auto it = number_to_id_.find(number);\n  if (it != number_to_id_.end()) return it->second;\n\n  auto id = GetNextAvailableId();\n  number_to_id_.emplace(number, id);\n  id_to_number_.emplace(id, number);\n  return id;\n}\n\nbool SequentialIdGenerator::HasGeneratedIdFor(uint32_t number) const {\n  return number_to_id_.find(number) != number_to_id_.end();\n}\n\nvoid SequentialIdGenerator::ReleaseNumber(uint32_t number) {\n  if (number_to_id_.count(number) > 0U) {\n    UpdateNextAvailableIdAfterRelease(number_to_id_[number]);\n    Remove(number, &number_to_id_, &id_to_number_);\n  }\n}\n\nvoid SequentialIdGenerator::ReleaseId(uint32_t id) {\n  if (id_to_number_.count(id) > 0U) {\n    UpdateNextAvailableIdAfterRelease(id);\n    Remove(id_to_number_[id], &number_to_id_, &id_to_number_);\n  }\n}\n\nuint32_t SequentialIdGenerator::GetNextAvailableId() {\n  while (id_to_number_.count(min_available_id_) > 0 &&\n         min_available_id_ < max_id_) {\n    ++min_available_id_;\n  }\n  if (min_available_id_ >= max_id_) min_available_id_ = min_id_;\n  return min_available_id_;\n}\n\nvoid SequentialIdGenerator::UpdateNextAvailableIdAfterRelease(uint32_t id) {\n  if (id < min_available_id_) {\n    min_available_id_ = id;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/sequential_id_generator.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_SEQUENTIAL_ID_GENERATOR_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_SEQUENTIAL_ID_GENERATOR_H_\n\n#include <cstdint>\n#include <unordered_map>\n\nnamespace clay {\n\n// This is used to generate a series of sequential ID numbers in a way that a\n// new ID is always the lowest possible ID in the sequence.\n//\n// based on\n// https://source.chromium.org/chromium/chromium/src/+/main:ui/gfx/sequential_id_generator.h\nclass SequentialIdGenerator {\n public:\n  // Creates a new generator with the specified lower bound and uppoer bound for\n  // the IDs.\n  explicit SequentialIdGenerator(uint32_t min_id, uint32_t max_id);\n  ~SequentialIdGenerator();\n\n  // Generates a unique ID to represent |number|. The generated ID is the\n  // smallest available ID greater than or equal to the |min_id| specified\n  // during creation of the generator.\n  uint32_t GetGeneratedId(uint32_t number);\n\n  // Checks to see if the generator currently has a unique ID generated for\n  // |number|.\n  bool HasGeneratedIdFor(uint32_t number) const;\n\n  // Removes the ID previously generated for |number| by calling\n  // |GetGeneratedID()| - does nothing if the number is not mapped.\n  void ReleaseNumber(uint32_t number);\n\n  // Releases ID previously generated by calling |GetGeneratedID()|. Does\n  // nothing if the ID is not mapped.\n  void ReleaseId(uint32_t id);\n\n private:\n  typedef std::unordered_map<uint32_t, uint32_t> IdMap;\n\n  uint32_t GetNextAvailableId();\n\n  void UpdateNextAvailableIdAfterRelease(uint32_t id);\n\n  IdMap number_to_id_;\n  IdMap id_to_number_;\n\n  const uint32_t min_id_;\n  const uint32_t max_id_;\n  uint32_t min_available_id_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_SEQUENTIAL_ID_GENERATOR_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/task_runner.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/task_runner.h\"\n\n#include <algorithm>\n#include <atomic>\n#include <utility>\n#include <vector>\n\nnamespace clay {\n\nTaskRunner::TaskRunner(CurrentTimeProc get_current_time,\n                       const TaskExpiredCallback& on_task_expired)\n    : get_current_time_(get_current_time),\n      on_task_expired_(std::move(on_task_expired)) {\n  main_thread_id_ = GetCurrentThreadId();\n  task_runner_window_ = TaskRunnerWindow::GetSharedInstance();\n  task_runner_window_->AddDelegate(this);\n}\n\nTaskRunner::~TaskRunner() { task_runner_window_->RemoveDelegate(this); }\n\nstd::chrono::nanoseconds TaskRunner::ProcessTasks() {\n  const TaskTimePoint now = GetCurrentTimeForTask();\n\n  std::vector<Task> expired_tasks;\n\n  // Process expired tasks.\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    while (!task_queue_.empty()) {\n      const auto& top = task_queue_.top();\n      // If this task (and all tasks after this) has not yet expired, there is\n      // nothing more to do. Quit iterating.\n      if (top.fire_time > now) {\n        break;\n      }\n\n      // Make a record of the expired task. Do NOT service the task here\n      // because we are still holding onto the task queue mutex. We don't want\n      // other threads to block on posting tasks onto this thread till we are\n      // done processing expired tasks.\n      expired_tasks.push_back(task_queue_.top());\n\n      // Remove the tasks from the delayed tasks queue.\n      task_queue_.pop();\n    }\n  }\n\n  // Fire expired tasks.\n  {\n    // Flushing tasks here without holing onto the task queue mutex.\n    for (const auto& task : expired_tasks) {\n      if (auto clay_task = std::get_if<ClayTask>(&task.variant)) {\n        on_task_expired_(clay_task);\n      } else if (auto closure = std::get_if<TaskClosure>(&task.variant))\n        (*closure)();\n    }\n  }\n\n  // Calculate duration to sleep for on next iteration.\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    const auto next_wake = task_queue_.empty() ? TaskTimePoint::max()\n                                               : task_queue_.top().fire_time;\n\n    return std::min(next_wake - now, std::chrono::nanoseconds::max());\n  }\n}\n\nTaskRunner::TaskTimePoint TaskRunner::TimePointFromFlutterTime(\n    uint64_t flutter_target_time_nanos) const {\n  const auto now = GetCurrentTimeForTask();\n  const auto flutter_duration = flutter_target_time_nanos - get_current_time_();\n  return now + std::chrono::nanoseconds(flutter_duration);\n}\n\nvoid TaskRunner::PostClayTask(ClayTask clay_task, uint64_t target_time_nanos) {\n  Task task;\n  task.fire_time = TimePointFromFlutterTime(target_time_nanos);\n  task.variant = clay_task;\n  EnqueueTask(std::move(task));\n}\n\nvoid TaskRunner::PostTask(TaskClosure closure) {\n  Task task;\n  task.fire_time = GetCurrentTimeForTask();\n  task.variant = std::move(closure);\n  EnqueueTask(std::move(task));\n}\n\nvoid TaskRunner::EnqueueTask(Task task) {\n  static std::atomic_uint64_t sGlobalTaskOrder(0);\n\n  task.order = ++sGlobalTaskOrder;\n  {\n    std::lock_guard<std::mutex> lock(task_queue_mutex_);\n    task_queue_.push(task);\n\n    // Make sure the queue mutex is unlocked before waking up the loop. In case\n    // the wake causes this thread to be descheduled for the primary thread to\n    // process tasks, the acquisition of the lock on that thread while holding\n    // the lock here momentarily till the end of the scope is a pessimization.\n  }\n\n  WakeUp();\n}\n\nbool TaskRunner::RunsTasksOnCurrentThread() const {\n  return GetCurrentThreadId() == main_thread_id_;\n}\n\nvoid TaskRunner::WakeUp() { task_runner_window_->WakeUp(); }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/task_runner.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_H_\n\n#include <chrono>\n#include <deque>\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <queue>\n#include <utility>\n#include <variant>\n\n#include \"clay/public/clay.h\"\n#include \"clay/shell/platform/windows/task_runner_window.h\"\n\nnamespace clay {\n\ntypedef uint64_t (*CurrentTimeProc)();\n\n// A custom task runner that integrates with user32 GetMessage semantics so\n// that host app can own its own message loop and flutter still gets to process\n// tasks on a timely basis.\nclass TaskRunner : public TaskRunnerWindow::Delegate {\n public:\n  using TaskTimePoint = std::chrono::steady_clock::time_point;\n  using TaskExpiredCallback = std::function<void(const ClayTask*)>;\n  using TaskClosure = std::function<void()>;\n\n  // Creates a new task runner with the current thread, current time\n  // provider, and callback for tasks that are ready to be run.\n  TaskRunner(CurrentTimeProc get_current_time,\n             const TaskExpiredCallback& on_task_expired);\n\n  virtual ~TaskRunner();\n\n  // Returns `true` if the current thread is this runner's thread.\n  virtual bool RunsTasksOnCurrentThread() const;\n\n  // Post a Flutter engine task to the event loop for delayed execution.\n  void PostClayTask(ClayTask task, uint64_t target_time_nanos);\n\n  // Post a task to the event loop.\n  void PostTask(TaskClosure task);\n\n  // Post a task to the event loop or run it immediately if this is being called\n  // from the runner's thread.\n  void RunNowOrPostTask(TaskClosure task) {\n    if (RunsTasksOnCurrentThread()) {\n      task();\n    } else {\n      PostTask(std::move(task));\n    }\n  }\n\n  // |TaskRunnerWindow::Delegate|\n  std::chrono::nanoseconds ProcessTasks();\n\n private:\n  typedef std::variant<ClayTask, TaskClosure> TaskVariant;\n\n  struct Task {\n    uint64_t order;\n    TaskTimePoint fire_time;\n    TaskVariant variant;\n\n    struct Comparer {\n      bool operator()(const Task& a, const Task& b) {\n        if (a.fire_time == b.fire_time) {\n          return a.order > b.order;\n        }\n        return a.fire_time > b.fire_time;\n      }\n    };\n  };\n\n  // Enqueues the given task.\n  void EnqueueTask(Task task);\n\n  // Schedules timers to call `ProcessTasks()` at the runner's thread.\n  virtual void WakeUp();\n\n  // Returns the current TaskTimePoint that can be used to determine whether a\n  // task is expired.\n  //\n  // Tests can override this to mock up the time.\n  virtual TaskTimePoint GetCurrentTimeForTask() const {\n    return TaskTimePoint::clock::now();\n  }\n\n  // Returns a TaskTimePoint computed from the given target time from Flutter.\n  TaskTimePoint TimePointFromFlutterTime(\n      uint64_t flutter_target_time_nanos) const;\n\n  CurrentTimeProc get_current_time_;\n  TaskExpiredCallback on_task_expired_;\n  std::mutex task_queue_mutex_;\n  std::priority_queue<Task, std::deque<Task>, Task::Comparer> task_queue_;\n  DWORD main_thread_id_;\n  std::shared_ptr<TaskRunnerWindow> task_runner_window_;\n\n  TaskRunner(const TaskRunner&) = delete;\n  TaskRunner& operator=(const TaskRunner&) = delete;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/task_runner_window.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/task_runner_window.h\"\n\n#include <algorithm>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nTaskRunnerWindow::TaskRunnerWindow() {\n  WNDCLASS window_class = RegisterWindowClass();\n  window_handle_ =\n      CreateWindowEx(0, window_class.lpszClassName, L\"\", 0, 0, 0, 0, 0,\n                     HWND_MESSAGE, nullptr, window_class.hInstance, nullptr);\n\n  if (window_handle_) {\n    SetWindowLongPtr(window_handle_, GWLP_USERDATA,\n                     reinterpret_cast<LONG_PTR>(this));\n  } else {\n    auto error = GetLastError();\n    LPWSTR message = nullptr;\n    size_t size = FormatMessageW(\n        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |\n            FORMAT_MESSAGE_IGNORE_INSERTS,\n        NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n        reinterpret_cast<LPWSTR>(&message), 0, NULL);\n    OutputDebugString(message);\n    LocalFree(message);\n  }\n}\n\nTaskRunnerWindow::~TaskRunnerWindow() {\n  if (window_handle_) {\n    DestroyWindow(window_handle_);\n    window_handle_ = nullptr;\n  }\n  UnregisterClass(window_class_name_.c_str(), nullptr);\n}\n\nstd::shared_ptr<TaskRunnerWindow> TaskRunnerWindow::GetSharedInstance() {\n  static std::weak_ptr<TaskRunnerWindow> instance;\n  auto res = instance.lock();\n  if (!res) {\n    // can't use make_shared with private contructor\n    res.reset(new TaskRunnerWindow());\n    instance = res;\n  }\n  return res;\n}\n\nvoid TaskRunnerWindow::WakeUp() {\n  if (!PostMessage(window_handle_, WM_NULL, 0, 0)) {\n    FML_LOG(ERROR) << \"Failed to post message to main thread.\";\n  }\n}\n\nvoid TaskRunnerWindow::AddDelegate(Delegate* delegate) {\n  delegates_.push_back(delegate);\n  SetTimer(std::chrono::nanoseconds::zero());\n}\n\nvoid TaskRunnerWindow::RemoveDelegate(Delegate* delegate) {\n  auto i = std::find(delegates_.begin(), delegates_.end(), delegate);\n  if (i != delegates_.end()) {\n    delegates_.erase(i);\n  }\n}\n\nvoid TaskRunnerWindow::ProcessTasks() {\n  auto next = std::chrono::nanoseconds::max();\n  auto delegates_copy(delegates_);\n  for (auto delegate : delegates_copy) {\n    // if not removed in the meanwhile\n    if (std::find(delegates_.begin(), delegates_.end(), delegate) !=\n        delegates_.end()) {\n      next = std::min(next, delegate->ProcessTasks());\n    }\n  }\n  SetTimer(next);\n}\n\nvoid TaskRunnerWindow::SetTimer(std::chrono::nanoseconds when) {\n  if (when == std::chrono::nanoseconds::max()) {\n    KillTimer(window_handle_, 0);\n  } else {\n    auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(when);\n    ::SetTimer(window_handle_, 0, millis.count() + 1, nullptr);\n  }\n}\n\nWNDCLASS TaskRunnerWindow::RegisterWindowClass() {\n  window_class_name_ = L\"ClayTaskRunnerWindow\";\n\n  WNDCLASS window_class{};\n  window_class.hCursor = nullptr;\n  window_class.lpszClassName = window_class_name_.c_str();\n  window_class.style = 0;\n  window_class.cbClsExtra = 0;\n  window_class.cbWndExtra = 0;\n  window_class.hInstance = GetModuleHandle(nullptr);\n  window_class.hIcon = nullptr;\n  window_class.hbrBackground = 0;\n  window_class.lpszMenuName = nullptr;\n  window_class.lpfnWndProc = WndProc;\n  RegisterClass(&window_class);\n  return window_class;\n}\n\nLRESULT\nTaskRunnerWindow::HandleMessage(UINT const message, WPARAM const wparam,\n                                LPARAM const lparam) noexcept {\n  switch (message) {\n    case WM_TIMER:\n    case WM_NULL:\n      ProcessTasks();\n      return 0;\n  }\n  return DefWindowProcW(window_handle_, message, wparam, lparam);\n}\n\nLRESULT TaskRunnerWindow::WndProc(HWND const window, UINT const message,\n                                  WPARAM const wparam,\n                                  LPARAM const lparam) noexcept {\n  if (auto* that = reinterpret_cast<TaskRunnerWindow*>(\n          GetWindowLongPtr(window, GWLP_USERDATA))) {\n    return that->HandleMessage(message, wparam, lparam);\n  } else {\n    return DefWindowProc(window, message, wparam, lparam);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/task_runner_window.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_WINDOW_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_WINDOW_H_\n\n#include <windows.h>\n\n#include <chrono>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace clay {\n\n// Hidden HWND responsible for processing flutter tasks on main thread\nclass TaskRunnerWindow {\n public:\n  class Delegate {\n   public:\n    // Executes expired task, and returns the duration until the next task\n    // deadline if exists, otherwise returns `std::chrono::nanoseconds::max()`.\n    //\n    // Each platform implementation must call this to schedule the tasks.\n    virtual std::chrono::nanoseconds ProcessTasks() = 0;\n  };\n\n  static std::shared_ptr<TaskRunnerWindow> GetSharedInstance();\n\n  // Triggers processing delegate tasks on main thread\n  void WakeUp();\n\n  void AddDelegate(Delegate* delegate);\n  void RemoveDelegate(Delegate* delegate);\n\n  ~TaskRunnerWindow();\n\n private:\n  TaskRunnerWindow();\n\n  void ProcessTasks();\n\n  void SetTimer(std::chrono::nanoseconds when);\n\n  WNDCLASS RegisterWindowClass();\n\n  LRESULT\n  HandleMessage(UINT const message, WPARAM const wparam,\n                LPARAM const lparam) noexcept;\n\n  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,\n                                  WPARAM const wparam,\n                                  LPARAM const lparam) noexcept;\n\n  HWND window_handle_;\n  std::wstring window_class_name_;\n  std::vector<Delegate*> delegates_;\n};\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_WINDOW_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/text_input_manager.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/text_input_manager.h\"\n\n#include <imm.h>\n\n#include <cassert>\n#include <memory>\n\nnamespace clay {\n\n// RAII wrapper for the Win32 Input Method Manager context.\nclass ImmContext {\n public:\n  ImmContext(HWND window_handle)\n      : context_(::ImmGetContext(window_handle)),\n        window_handle_(window_handle) {\n    assert(window_handle);\n  }\n\n  ~ImmContext() {\n    if (context_ != nullptr) {\n      ::ImmReleaseContext(window_handle_, context_);\n    }\n  }\n\n  // Prevent copying.\n  ImmContext(const ImmContext& other) = delete;\n  ImmContext& operator=(const ImmContext& other) = delete;\n\n  // Returns true if a valid IMM context has been obtained.\n  bool IsValid() const { return context_ != nullptr; }\n\n  // Returns the IMM context.\n  HIMC get() {\n    assert(context_);\n    return context_;\n  }\n\n private:\n  HWND window_handle_;\n  HIMC context_;\n};\n\nvoid TextInputManager::SetWindowHandle(HWND window_handle) {\n  window_handle_ = window_handle;\n  UpdateIMEState();\n}\n\nvoid TextInputManager::CreateImeWindow() {\n  if (window_handle_ == nullptr) {\n    return;\n  }\n\n  // Some IMEs ignore calls to ::ImmSetCandidateWindow() and use the position of\n  // the current system caret instead via ::GetCaretPos(). In order to behave\n  // as expected with these IMEs, we create a temporary system caret.\n  if (!ime_active_) {\n    ::CreateCaret(window_handle_, nullptr, 1, 1);\n  }\n  ime_active_ = true;\n\n  // Set the position of the IME windows.\n  UpdateImeWindow();\n}\n\nvoid TextInputManager::DestroyImeWindow() {\n  if (window_handle_ == nullptr) {\n    return;\n  }\n\n  // Destroy the system caret created in CreateImeWindow().\n  if (ime_active_) {\n    ::DestroyCaret();\n  }\n  ime_active_ = false;\n}\n\nvoid TextInputManager::UpdateImeWindow() {\n  if (window_handle_ == nullptr) {\n    return;\n  }\n\n  ImmContext imm_context(window_handle_);\n  if (imm_context.IsValid()) {\n    MoveImeWindow(imm_context.get());\n  }\n}\n\nvoid TextInputManager::UpdateCaretRect(const FloatRect& rect) {\n  caret_rect_ = rect;\n\n  if (window_handle_ == nullptr) {\n    return;\n  }\n\n  ImmContext imm_context(window_handle_);\n  if (imm_context.IsValid()) {\n    MoveImeWindow(imm_context.get());\n  }\n}\n\nlong TextInputManager::GetComposingCursorPosition() const {\n  if (window_handle_ == nullptr) {\n    return false;\n  }\n\n  ImmContext imm_context(window_handle_);\n  if (imm_context.IsValid()) {\n    // Read the cursor position within the composing string.\n    return ImmGetCompositionString(imm_context.get(), GCS_CURSORPOS, nullptr,\n                                   0);\n  }\n  return -1;\n}\n\nstd::optional<std::u16string> TextInputManager::GetComposingString() const {\n  return GetString(GCS_COMPSTR);\n}\n\nstd::optional<std::u16string> TextInputManager::GetResultString() const {\n  return GetString(GCS_RESULTSTR);\n}\n\nvoid TextInputManager::AbortComposing() {\n  if (window_handle_ == nullptr || !ime_active_) {\n    return;\n  }\n\n  ImmContext imm_context(window_handle_);\n  if (imm_context.IsValid()) {\n    // Cancel composing and close the candidates window.\n    ::ImmNotifyIME(imm_context.get(), NI_COMPOSITIONSTR, CPS_CANCEL, 0);\n    ::ImmNotifyIME(imm_context.get(), NI_CLOSECANDIDATE, 0, 0);\n\n    // Clear the composing string.\n    wchar_t composition_str[] = L\"\";\n    wchar_t reading_str[] = L\"\";\n    ::ImmSetCompositionStringW(imm_context.get(), SCS_SETSTR, composition_str,\n                               sizeof(wchar_t), reading_str, sizeof(wchar_t));\n  }\n}\n\nvoid TextInputManager::EnableIME() {\n  // Load the default IME context.\n  // IMM ignores this call if the IME context is loaded. Therefore, we do\n  // not have to check whether or not the IME context is loaded.\n  ::ImmAssociateContextEx(window_handle_, NULL, IACE_DEFAULT);\n}\n\nvoid TextInputManager::DisableIME() {\n  AbortComposing();\n  ::ImmAssociateContextEx(window_handle_, NULL, 0);\n}\n\nvoid TextInputManager::UpdateIMEState() {\n  // TODO(wangchen): Resolve KeyboardInputType\n  if (client_id_ != -1) {\n    // Set the focus to the window in order to receive IME events.\n    SetFocus(window_handle_);\n    EnableIME();\n  } else {\n    DisableIME();\n  }\n}\n\nvoid TextInputManager::UpdateTextInputClientFocused(int client_id) {\n  client_id_ = client_id;\n  UpdateIMEState();\n}\n\nstd::optional<std::u16string> TextInputManager::GetString(int type) const {\n  if (window_handle_ == nullptr || !ime_active_) {\n    return std::nullopt;\n  }\n  ImmContext imm_context(window_handle_);\n  if (imm_context.IsValid()) {\n    // Read the composing string length.\n    const long compose_bytes =\n        ::ImmGetCompositionString(imm_context.get(), type, nullptr, 0);\n    const long compose_length = compose_bytes / sizeof(wchar_t);\n    if (compose_length < 0) {\n      return std::nullopt;\n    }\n\n    std::u16string text(compose_length, '\\0');\n    ::ImmGetCompositionString(imm_context.get(), type, &text[0], compose_bytes);\n    return text;\n  }\n  return std::nullopt;\n}\n\nvoid TextInputManager::MoveImeWindow(HIMC imm_context) {\n  if (GetFocus() != window_handle_) {\n    return;\n  }\n  LONG left = caret_rect_.left();\n  LONG top = caret_rect_.top();\n  LONG right = caret_rect_.right();\n  LONG bottom = caret_rect_.bottom();\n  ::SetCaretPos(left, bottom);\n\n  // Set the position of composition text.\n  COMPOSITIONFORM composition_form = {CFS_POINT, {left, top}};\n  ::ImmSetCompositionWindow(imm_context, &composition_form);\n\n  // Set the position of candidate window.\n  CANDIDATEFORM candidate_form = {\n      0, CFS_EXCLUDE, {left, bottom}, {left, top, right, bottom}};\n  ::ImmSetCandidateWindow(imm_context, &candidate_form);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/text_input_manager.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_MANAGER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_MANAGER_H_\n\n#include <Windows.h>\n#include <Windowsx.h>\n\n#include <optional>\n#include <string>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n\nnamespace clay {\n\n// Management interface for IME-based text input on Windows.\n//\n// When inputting text in CJK languages, text is entered via a multi-step\n// process, where direct keyboard input is buffered into a composing string,\n// which is then converted into the desired characters by selecting from a\n// candidates list and committing the change to the string.\n//\n// This implementation wraps the Win32 IMM32 APIs and provides a mechanism for\n// creating and positioning the IME window, a system caret, and the candidates\n// list as well as for accessing composing and results string contents.\nclass TextInputManager {\n public:\n  TextInputManager() noexcept = default;\n  virtual ~TextInputManager() = default;\n\n  TextInputManager(const TextInputManager&) = delete;\n  TextInputManager& operator=(const TextInputManager&) = delete;\n\n  // Sets the window handle with which the IME is associated.\n  void SetWindowHandle(HWND window_handle);\n\n  // Creates a new IME window and system caret.\n  //\n  // This method should be invoked in response to the WM_IME_SETCONTEXT and\n  // WM_IME_STARTCOMPOSITION events.\n  void CreateImeWindow();\n\n  // Destroys the current IME window and system caret.\n  //\n  // This method should be invoked in response to the WM_IME_ENDCOMPOSITION\n  // event.\n  void DestroyImeWindow();\n\n  // Updates the current IME window and system caret position.\n  //\n  // This method should be invoked when handling user input via\n  // WM_IME_COMPOSITION events.\n  void UpdateImeWindow();\n\n  // Updates the current IME window and system caret position.\n  //\n  // This method should be invoked when handling cursor position/size updates.\n  void UpdateCaretRect(const FloatRect& rect);\n\n  // Returns the cursor position relative to the start of the composing range.\n  virtual long GetComposingCursorPosition() const;\n\n  // Returns the contents of the composing string.\n  //\n  // This may be called in response to WM_IME_COMPOSITION events where the\n  // GCS_COMPSTR flag is set in the lparam. In some IMEs, this string may also\n  // be set in events where the GCS_RESULTSTR flag is set. This contains the\n  // in-progress composing string.\n  virtual std::optional<std::u16string> GetComposingString() const;\n\n  // Returns the contents of the result string.\n  //\n  // This may be called in response to WM_IME_COMPOSITION events where the\n  // GCS_RESULTSTR flag is set in the lparam. This contains the final string to\n  // be committed in the composing region when composition is ended.\n  virtual std::optional<std::u16string> GetResultString() const;\n\n  /// Aborts IME composing.\n  ///\n  /// Aborts composing, closes the candidates window, and clears the contents\n  /// of the composing string.\n  void AbortComposing();\n\n  // Enables the IME attached to the given window, i.e. allows user-input\n  // events to be dispatched to the IME.\n  void EnableIME();\n\n  // Disables the IME attached to the given window, i.e. prohibits any\n  // user-input events from being dispatched to the IME.\n  void DisableIME();\n\n  // Enables or disables the IME according to the current text input type.\n  void UpdateIMEState();\n\n  // Update focused text input client id.\n  void UpdateTextInputClientFocused(int client_id);\n\n private:\n  // Returns either the composing string or result string based on the value of\n  // the |type| parameter.\n  std::optional<std::u16string> GetString(int type) const;\n\n  // Moves the IME composing and candidates windows to the current caret\n  // position.\n  void MoveImeWindow(HIMC imm_context);\n\n  // The window with which the IME windows are associated.\n  HWND window_handle_ = nullptr;\n\n  // True if IME-based composing is active.\n  bool ime_active_ = false;\n\n  // The system caret rect.\n  FloatRect caret_rect_ = {{0, 0}, {0, 0}};\n\n  int client_id_ = -1;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_MANAGER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/text_input_plugin.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/text_input_plugin.h\"\n\n#include <windows.h>\n\n#include <algorithm>\n#include <codecvt>\n#include <cstdint>\n#include <regex>\n#include <string>\n#include <utility>\n\n#include \"base/include/string/string_utils.h\"\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/shell/platform/common/text_editing_delta.h\"\n#include \"clay/shell/platform/windows/flutter_windows_engine.h\"\n\nstatic constexpr char kAffinityDownstream[] = \"TextAffinity.downstream\";\n\nnamespace clay {\n\nvoid TextInputPlugin::TextHook(const std::u16string& text) {\n  if (active_model_ == nullptr) {\n    return;\n  }\n  std::u16string text_before_change =\n      lynx::base::U8StringToU16(active_model_->GetText());\n  TextRange selection_before_change = active_model_->selection();\n  active_model_->AddText(text);\n\n  if (enable_delta_model) {\n    TextEditingDelta delta =\n        TextEditingDelta(text_before_change, selection_before_change, text);\n    SendStateUpdateWithDelta(*active_model_, &delta);\n  } else {\n    SendStateUpdate(*active_model_);\n  }\n}\n\nvoid TextInputPlugin::KeyboardHook(int key, int scancode, int action,\n                                   char32_t character, bool extended,\n                                   bool was_down) {\n  if (active_model_ == nullptr) {\n    return;\n  }\n  if (action == WM_KEYDOWN || action == WM_SYSKEYDOWN) {\n    // Most editing keys (arrow keys, backspace, delete, etc.) are handled in\n    // the framework, so don't need to be handled at this layer.\n    switch (key) {\n      case VK_RETURN:\n        EnterPressed(active_model_.get());\n        break;\n      default:\n        break;\n    }\n  }\n}\n\nTextInputPlugin::TextInputPlugin(TextInputPluginDelegate* delegate,\n                                 FlutterWindowsEngine* engine,\n                                 SendEvent send_event)\n    : delegate_(delegate),\n      engine_(engine),\n      active_model_(nullptr),\n      send_event_(send_event),\n      weak_factory_(this) {}\n\nTextInputPlugin::~TextInputPlugin() {}\n\nvoid TextInputPlugin::ComposeBeginHook() {\n  if (active_model_ == nullptr) {\n    return;\n  }\n  active_model_->BeginComposing();\n  if (enable_delta_model) {\n    std::string text = active_model_->GetText();\n    TextRange selection = active_model_->selection();\n    TextEditingDelta delta = TextEditingDelta(text);\n    SendStateUpdateWithDelta(*active_model_, &delta);\n  } else {\n    SendStateUpdate(*active_model_);\n  }\n}\n\nvoid TextInputPlugin::ComposeCommitHook() {\n  if (active_model_ == nullptr) {\n    return;\n  }\n  std::string text_before_change = active_model_->GetText();\n  TextRange selection_before_change = active_model_->selection();\n  TextRange composing_before_change = active_model_->composing_range();\n  std::string composing_text_before_change = text_before_change.substr(\n      composing_before_change.start(), composing_before_change.length());\n  active_model_->CommitComposing();\n\n  // We do not trigger SendStateUpdate here.\n  //\n  // Until a WM_IME_ENDCOMPOSING event, the user is still composing from the OS\n  // point of view. Commit events are always immediately followed by another\n  // composing event or an end composing event. However, in the brief window\n  // between the commit event and the following event, the composing region is\n  // collapsed. Notifying the framework of this intermediate state will trigger\n  // any framework code designed to execute at the end of composing, such as\n  // input formatters, which may try to update the text and send a message back\n  // to the engine with changes.\n  //\n  // This is a particular problem with Korean IMEs, which build up one\n  // character at a time in their composing region until a keypress that makes\n  // no sense for the in-progress character. At that point, the result\n  // character is committed and a compose event is immedidately received with\n  // the new composing region.\n  //\n  // In the case where this event is immediately followed by a composing event,\n  // the state will be sent in ComposeChangeHook.\n  //\n  // In the case where this event is immediately followed by an end composing\n  // event, the state will be sent in ComposeEndHook.\n}\n\nvoid TextInputPlugin::ComposeEndHook() {\n  if (active_model_ == nullptr) {\n    return;\n  }\n  std::string text_before_change = active_model_->GetText();\n  TextRange selection_before_change = active_model_->selection();\n  active_model_->CommitComposing();\n  active_model_->EndComposing();\n  if (enable_delta_model) {\n    std::string text = active_model_->GetText();\n    TextEditingDelta delta = TextEditingDelta(text);\n    SendStateUpdateWithDelta(*active_model_, &delta);\n  } else {\n    SendStateUpdate(*active_model_);\n  }\n}\n\nvoid TextInputPlugin::ComposeChangeHook(const std::u16string& text,\n                                        int cursor_pos) {\n  if (!!send_event_ && cursor_pos == 0) {\n    std::string utf8 = lynx::base::U16StringToU8(text);\n    ClayKeyEvent event{\n        .struct_size = sizeof(ClayKeyEvent),\n        .timestamp = static_cast<double>(\n            std::chrono::duration_cast<std::chrono::microseconds>(\n                std::chrono::high_resolution_clock::now().time_since_epoch())\n                .count()),\n        .type = kClayKeyEventTypeDown,\n        .physical = 0,  // unknown\n        .logical = 0,   // unknown\n        .character = utf8.data(),\n        .synthesized = false,\n    };\n    auto callback = [](bool handled, void* user_data) -> void {\n      // do nothing\n    };\n    send_event_(event, callback, nullptr);\n  }\n\n  if (active_model_ == nullptr) {\n    return;\n  }\n  std::string text_before_change = active_model_->GetText();\n  TextRange composing_before_change = active_model_->composing_range();\n  active_model_->AddText(text);\n  cursor_pos += active_model_->composing_range().extent();\n  active_model_->UpdateComposingText(text);\n  active_model_->SetSelection(TextRange(cursor_pos, cursor_pos));\n  std::string text_after_change = active_model_->GetText();\n  if (enable_delta_model) {\n    TextEditingDelta delta =\n        TextEditingDelta(lynx::base::U8StringToU16(text_before_change),\n                         composing_before_change, text);\n    SendStateUpdateWithDelta(*active_model_, &delta);\n  } else {\n    SendStateUpdate(*active_model_);\n  }\n}\n\nFloatRect TextInputPlugin::GetCursorRect() const {\n  FloatPoint transformed_point = {\n      composing_rect_.left() * editable_text_transform_[0][0] +\n          composing_rect_.top() * editable_text_transform_[1][0] +\n          editable_text_transform_[3][0],\n      composing_rect_.left() * editable_text_transform_[0][1] +\n          composing_rect_.top() * editable_text_transform_[1][1] +\n          editable_text_transform_[3][1]};\n  return {transformed_point, composing_rect_.size()};\n}\n\nvoid TextInputPlugin::SendStateUpdate(const TextInputModel& model) {\n  TextRange selection = model.selection();\n  engine_->UpdateEditState(client_id_, selection.base(),\n                           model.composing_range().extent(),\n                           kAffinityDownstream, model.GetText().c_str(),\n                           selection.extent(), model.composing_range().base());\n}\n\nvoid TextInputPlugin::SendStateUpdateWithDelta(const TextInputModel& model,\n                                               const TextEditingDelta* delta) {}\n\nvoid TextInputPlugin::EnterPressed(TextInputModel* model) {\n  engine_->PerformInputAction(client_id_);\n}\n\nvoid TextInputPlugin::SetTextInputClient(int client_id,\n                                         const char* input_action,\n                                         const char* input_type) {\n  client_id_ = client_id;\n  enable_delta_model = false;\n  input_action_ = std::string(input_action);\n  input_type_ = std::string(input_type);\n  active_model_ = std::make_unique<TextInputModel>();\n  delegate_->OnTextInputClientChange(client_id_);\n}\n\nvoid TextInputPlugin::ClearTextInputClient() {\n  if (active_model_ != nullptr && active_model_->composing()) {\n    active_model_->CommitComposing();\n    active_model_->EndComposing();\n    SendStateUpdate(*active_model_);\n  }\n  delegate_->OnResetImeComposing();\n  active_model_ = nullptr;\n  client_id_ = -1;\n  delegate_->OnTextInputClientChange(client_id_);\n}\n\nvoid TextInputPlugin::SetEditableTransform(const float transform[16]) {\n  for (int i = 0; i < 16; ++i) {\n    editable_text_transform_[i / 4][i % 4] = transform[i];\n  }\n  FloatRect transformed_rect = GetCursorRect();\n  delegate_->OnCursorRectUpdated(transformed_rect);\n}\n\nvoid TextInputPlugin::SetEditingState(uint64_t selection_base,\n                                      uint64_t composing_extent,\n                                      const char* selection_affinity,\n                                      const char* text,\n                                      bool selection_directional,\n                                      uint64_t selection_extent,\n                                      uint64_t composing_base) {\n  if (active_model_ == nullptr) {\n    FML_LOG(ERROR)\n        << \"Set editing state has been invoked, but no client is set.\";\n    return;\n  }\n  if (!text) {\n    FML_LOG(ERROR) << \"Set editing state has been invoked, but without text.\";\n    return;\n  }\n  auto text_str = std::string(text);\n  // Flutter uses -1/-1 for invalid; translate that to 0/0 for the model.\n  if ((int)selection_base == -1 && (int)selection_extent == -1) {\n    selection_base = selection_extent = 0;\n  }\n  active_model_->SetText(text_str);\n  active_model_->SetSelection(\n      TextRange((int)selection_base, (int)selection_extent));\n\n  if ((int)composing_base == -1 && (int)composing_extent == -1) {\n    active_model_->EndComposing();\n  } else {\n    int composing_start = std::min((int)composing_base, (int)composing_extent);\n    int cursor_offset = (int)selection_base - composing_start;\n    active_model_->SetComposingRange(\n        TextRange((int)composing_base, (int)composing_extent), cursor_offset);\n  }\n  // Add send event to handle text input model modified by clay.\n  SendStateUpdate(*active_model_);\n}\n\nvoid TextInputPlugin::SetMarkedTextRect(float x, float y, float width,\n                                        float height) {\n  composing_rect_ = {{x, y}, {width, height}};\n  FloatRect transformed_rect = GetCursorRect();\n  delegate_->OnCursorRectUpdated(transformed_rect);\n}\n\nvoid TextInputPlugin::ShowTextInput() {\n  // This method is no-ops.\n}\n\nvoid TextInputPlugin::HideTextInput() {\n  // This method is no-ops.\n}\n\nstd::string TextInputPlugin::FilterInput(const std::string& input,\n                                         const std::string& pattern) {\n  auto w_input = fml::Utf8ToWideString(input);\n  auto w_pattern = fml::Utf8ToWideString(pattern);\n  std::wregex re(w_pattern);\n  auto result = std::regex_replace(w_input, re, L\"\");\n  return fml::WideStringToUtf8(result);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/text_input_plugin.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_PLUGIN_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_PLUGIN_H_\n\n#include <array>\n#include <map>\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/platform/common/text_editing_delta.h\"\n#include \"clay/shell/platform/common/text_input_model.h\"\n#include \"clay/shell/platform/windows/keyboard_handler_base.h\"\n#include \"clay/shell/platform/windows/text_input_plugin_delegate.h\"\n\nnamespace clay {\nclass FlutterWindowsEngine;\n\n// Implements a text input plugin.\n//\n// Specifically handles window events within windows.\nclass TextInputPlugin {\n public:\n  using SendEvent = std::function<void(const ClayKeyEvent& /* event */,\n                                       ClayKeyEventCallback /* callback */,\n                                       void* /* user_data */)>;\n\n  explicit TextInputPlugin(TextInputPluginDelegate* delegate,\n                           FlutterWindowsEngine* engine,\n                           SendEvent send_event = nullptr);\n\n  virtual ~TextInputPlugin();\n\n  virtual void KeyboardHook(int key, int scancode, int action,\n                            char32_t character, bool extended, bool was_down);\n\n  virtual void TextHook(const std::u16string& text);\n\n  virtual void ComposeBeginHook();\n\n  virtual void ComposeCommitHook();\n\n  virtual void ComposeEndHook();\n\n  virtual void ComposeChangeHook(const std::u16string& text, int cursor_pos);\n\n  void SetTextInputClient(int client_id, const char* input_action,\n                          const char* input_type);\n  void ClearTextInputClient();\n  void SetEditableTransform(const float transform[16]);\n  void SetEditingState(uint64_t selection_base, uint64_t composing_extent,\n                       const char* selection_affinity, const char* text,\n                       bool selection_directional, uint64_t selection_extent,\n                       uint64_t composing_base);\n  void SetMarkedTextRect(float x, float y, float width, float height);\n  void ShowTextInput();\n  void HideTextInput();\n  std::string FilterInput(const std::string& input, const std::string& pattern);\n\n private:\n  // Sends the current state of the given model to the Flutter engine.\n  void SendStateUpdate(const TextInputModel& model);\n\n  // Sends the current state of the given model to the Flutter engine.\n  void SendStateUpdateWithDelta(const TextInputModel& model,\n                                const TextEditingDelta*);\n\n  // Sends an action triggered by the Enter key to the Flutter engine.\n  void EnterPressed(TextInputModel* model);\n\n  // Returns the composing rect, or if IME composing mode is not active, the\n  // cursor rect in the PipelineOwner root coordinate system.\n  FloatRect GetCursorRect() const;\n\n  // The associated |TextInputPluginDelegate|.\n  TextInputPluginDelegate* delegate_;\n\n  // The associated |FlutterWindowsEngine|.\n  FlutterWindowsEngine* engine_;\n\n  // The active client id.\n  int client_id_;\n\n  // The active model. nullptr if not set.\n  std::unique_ptr<TextInputModel> active_model_;\n\n  // Whether to enable that the engine sends text input updates to the framework\n  // as TextEditingDeltas or as one TextEditingValue.\n  // For more information on the delta model, see:\n  // https://master-api.flutter.dev/flutter/services/TextInputConfiguration/enableDeltaModel.html\n  bool enable_delta_model = false;\n\n  // Keyboard type of the client. See available options:\n  // https://api.flutter.dev/flutter/services/TextInputType-class.html\n  std::string input_type_;\n\n  // An action requested by the user on the input client. See available options:\n  // https://api.flutter.dev/flutter/services/TextInputAction-class.html\n  std::string input_action_;\n\n  // The smallest rect, in local coordinates, of the text in the composing\n  // range, or of the caret in the case where there is no current composing\n  // range.\n  FloatRect composing_rect_;\n\n  // A 4x4 matrix that maps from `EditableText` local coordinates to the\n  // coordinate system of `PipelineOwner.rootNode`.\n  std::array<std::array<float, 4>, 4> editable_text_transform_ = {\n      0.0, 0.0, 0.0, 0.0,  //\n      0.0, 0.0, 0.0, 0.0,  //\n      0.0, 0.0, 0.0, 0.0,  //\n      0.0, 0.0, 0.0, 0.0};\n\n  SendEvent send_event_;\n\n  fml::WeakPtrFactory<TextInputPlugin> weak_factory_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_PLUGIN_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/text_input_plugin_delegate.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_PLUGIN_DELEGATE_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_PLUGIN_DELEGATE_H_\n\n#include \"clay/gfx/geometry/float_rect.h\"\n\nnamespace clay {\n\nclass TextInputPluginDelegate {\n public:\n  // Notifies the delegate of the updated the cursor rect in Flutter root view\n  // coordinates.\n  virtual void OnCursorRectUpdated(const FloatRect& rect) = 0;\n\n  // Notifies the delegate that the system IME composing state should be reset.\n  virtual void OnResetImeComposing() = 0;\n\n  virtual void OnTextInputClientChange(int client_id) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_TEXT_INPUT_PLUGIN_DELEGATE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/window.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/window.h\"\n\n#include <imm.h>\n#include <oleacc.h>\n#include <uiautomationcore.h>\n#include <uiautomationcoreapi.h>\n#include <wrl/client.h>\n\n#include <cstring>\n#include <utility>\n\n#include \"clay/shell/platform/windows/dpi_utils.h\"\n#include \"clay/shell/platform/windows/keyboard_utils.h\"\n\n// cspell:disable\nnamespace clay {\n\nnamespace {\n\nstatic constexpr int32_t kDefaultPointerDeviceId = 0;\n\n// This method is only valid during a window message related to mouse/touch\n// input.\n// See\n// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages?redirectedfrom=MSDN#distinguishing-pen-input-from-mouse-and-touch.\nstatic ClayPointerDeviceKind GetClayPointerDeviceKind() {\n  constexpr LPARAM kTouchOrPenSignature = 0xFF515700;\n  constexpr LPARAM kTouchSignature = kTouchOrPenSignature | 0x80;\n  constexpr LPARAM kSignatureMask = 0xFFFFFF00;\n  LPARAM info = GetMessageExtraInfo();\n  if ((info & kSignatureMask) == kTouchOrPenSignature) {\n    if ((info & kTouchSignature) == kTouchSignature) {\n      return kClayPointerDeviceKindTouch;\n    }\n    return kClayPointerDeviceKindStylus;\n  }\n  return kClayPointerDeviceKindMouse;\n}\n\nchar32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) {\n  return 0x10000 + ((static_cast<char32_t>(high) & 0x000003FF) << 10) +\n         (low & 0x3FF);\n}\n\nstatic const int kMinTouchDeviceId = 0;\nstatic const int kMaxTouchDeviceId = 128;\n\nstatic const int kLinesPerScrollWindowsDefault = 3;\n\n}  // namespace\n\nWindow::Window() : Window(nullptr, nullptr) {}\n\nWindow::Window(std::unique_ptr<WindowsProcTable> windows_proc_table,\n               std::unique_ptr<TextInputManager> text_input_manager)\n    : touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId),\n      windows_proc_table_(std::move(windows_proc_table)),\n      text_input_manager_(std::move(text_input_manager)) {\n  // Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is\n  // supported, |current_dpi_| should be updated in the\n  // kWmDpiChangedBeforeParent message.\n  current_dpi_ = GetDpiForHWND(nullptr);\n\n  // Get initial value for wheel scroll lines\n  // TODO: Listen to changes for this value\n  // https://github.com/flutter/flutter/issues/107248\n  UpdateScrollOffsetMultiplier();\n\n  if (windows_proc_table_ == nullptr) {\n    windows_proc_table_ = std::make_unique<WindowsProcTable>();\n  }\n  if (text_input_manager_ == nullptr) {\n    text_input_manager_ = std::make_unique<TextInputManager>();\n  }\n  keyboard_manager_ = std::make_unique<KeyboardManager>(this);\n}\n\nWindow::~Window() {\n  Destroy();\n  OleUninitialize();\n}\n\nvoid Window::InitializeChild(const char* title, HWND parent_hwnd,\n                             unsigned int x, unsigned int y, unsigned int width,\n                             unsigned int height) {\n  Destroy();\n  std::wstring converted_title = NarrowToWide(title);\n\n  WNDCLASS window_class = RegisterWindowClass(converted_title);\n\n  auto* result =\n      CreateWindowEx(0, window_class.lpszClassName, converted_title.c_str(),\n                     WS_CHILD | WS_VISIBLE, x, y, width, height,\n                     parent_hwnd ? parent_hwnd : HWND_MESSAGE, nullptr,\n                     window_class.hInstance, this);\n\n  if (result == nullptr) {\n    auto error = GetLastError();\n    LPWSTR message = nullptr;\n    size_t size = FormatMessageW(\n        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |\n            FORMAT_MESSAGE_IGNORE_INSERTS,\n        NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),\n        reinterpret_cast<LPWSTR>(&message), 0, NULL);\n    OutputDebugString(message);\n    LocalFree(message);\n  }\n  DEVMODE dmi;\n  ZeroMemory(&dmi, sizeof(dmi));\n  dmi.dmSize = sizeof(dmi);\n  if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dmi)) {\n    directManipulationPollingRate_ = dmi.dmDisplayFrequency;\n  } else {\n    OutputDebugString(\n        L\"Failed to get framerate, will use default of 60 Hz for gesture \"\n        L\"polling.\");\n  }\n  SetUserObjectInformationA(GetCurrentProcess(),\n                            UOI_TIMERPROC_EXCEPTION_SUPPRESSION, FALSE, 1);\n  SetTimer(result, kDirectManipulationTimer,\n           1000 / directManipulationPollingRate_, nullptr);\n  direct_manipulation_owner_ = std::make_unique<DirectManipulationOwner>(this);\n  direct_manipulation_owner_->Init(width, height);\n  auto value = OleInitialize(nullptr);\n  if (value != S_OK) {\n    FML_DLOG(WARNING) << \"OleInitialize failed: \" << value;\n  }\n}\n\nstd::wstring Window::NarrowToWide(const char* source) {\n  size_t length = strlen(source);\n  size_t outlen = 0;\n  std::wstring wideTitle(length, L'#');\n  mbstowcs_s(&outlen, &wideTitle[0], length + 1, source, length);\n  return wideTitle;\n}\n\nWNDCLASS Window::RegisterWindowClass(std::wstring& title) {\n  window_class_name_ = title;\n\n  WNDCLASS window_class{};\n  window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);\n  window_class.lpszClassName = title.c_str();\n  window_class.style = CS_HREDRAW | CS_VREDRAW;\n  window_class.cbClsExtra = 0;\n  window_class.cbWndExtra = 0;\n  window_class.hInstance = GetModuleHandle(nullptr);\n  window_class.hIcon = nullptr;\n  window_class.hbrBackground = 0;\n  window_class.lpszMenuName = nullptr;\n  window_class.lpfnWndProc = WndProc;\n  RegisterClass(&window_class);\n  return window_class;\n}\n\nLRESULT CALLBACK Window::WndProc(HWND const window, UINT const message,\n                                 WPARAM const wparam,\n                                 LPARAM const lparam) noexcept {\n  if (message == WM_NCCREATE) {\n    auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);\n    SetWindowLongPtr(window, GWLP_USERDATA,\n                     reinterpret_cast<LONG_PTR>(cs->lpCreateParams));\n\n    auto that = static_cast<Window*>(cs->lpCreateParams);\n    that->window_handle_ = window;\n    that->text_input_manager_->SetWindowHandle(window);\n    that->current_dpi_ = GetDpiForHWND(window);\n    that->OnDpiScale(that->current_dpi_);\n    RegisterTouchWindow(window, 0);\n  } else if (Window* that = GetThisFromHandle(window)) {\n    return that->HandleMessage(message, wparam, lparam);\n  }\n\n  return DefWindowProc(window, message, wparam, lparam);\n}\n\nvoid Window::TrackMouseLeaveEvent(HWND hwnd) {\n  if (!tracking_mouse_leave_) {\n    TRACKMOUSEEVENT tme;\n    tme.cbSize = sizeof(tme);\n    tme.hwndTrack = hwnd;\n    tme.dwFlags = TME_LEAVE;\n    TrackMouseEvent(&tme);\n    tracking_mouse_leave_ = true;\n  }\n}\n\nvoid Window::OnImeSetContext(UINT const message, WPARAM const wparam,\n                             LPARAM const lparam) {\n  if (wparam != 0) {\n    text_input_manager_->CreateImeWindow();\n  }\n}\n\nvoid Window::OnImeStartComposition(UINT const message, WPARAM const wparam,\n                                   LPARAM const lparam) {\n  text_input_manager_->CreateImeWindow();\n  OnComposeBegin();\n}\n\nvoid Window::OnImeComposition(UINT const message, WPARAM const wparam,\n                              LPARAM const lparam) {\n  // Update the IME window position.\n  text_input_manager_->UpdateImeWindow();\n\n  if (lparam == 0) {\n    OnComposeChange(u\"\", 0);\n    OnComposeCommit();\n  }\n\n  // Process GCS_RESULTSTR at fisrt, because Google Japanese Input and ATOK send\n  // both GCS_RESULTSTR and GCS_COMPSTR to commit composed text and send new\n  // composing text.\n  if (lparam & GCS_RESULTSTR) {\n    // Commit but don't end composing.\n    // Read the committed composing string.\n    long pos = text_input_manager_->GetComposingCursorPosition();\n    std::optional<std::u16string> text = text_input_manager_->GetResultString();\n    if (text) {\n      OnComposeChange(text.value(), pos);\n      OnComposeCommit();\n    }\n  }\n  if (lparam & GCS_COMPSTR) {\n    // Read the in-progress composing string.\n    long pos = text_input_manager_->GetComposingCursorPosition();\n    std::optional<std::u16string> text =\n        text_input_manager_->GetComposingString();\n    if (text) {\n      OnComposeChange(text.value(), pos);\n    }\n  }\n}\n\nvoid Window::OnImeEndComposition(UINT const message, WPARAM const wparam,\n                                 LPARAM const lparam) {\n  text_input_manager_->DestroyImeWindow();\n  OnComposeEnd();\n}\n\nvoid Window::OnImeRequest(UINT const message, WPARAM const wparam,\n                          LPARAM const lparam) {\n  // TODO(cbracken): Handle IMR_RECONVERTSTRING, IMR_DOCUMENTFEED,\n  // and IMR_QUERYCHARPOSITION messages.\n  // https://github.com/flutter/flutter/issues/74547\n}\n\nvoid Window::AbortImeComposing() { text_input_manager_->AbortComposing(); }\n\nvoid Window::UpdateCursorRect(const FloatRect& rect) {\n  text_input_manager_->UpdateCaretRect(rect);\n}\n\nvoid Window::UpdateTextInputClientFocused(int client_id) {\n  text_input_manager_->UpdateTextInputClientFocused(client_id);\n}\n\nstatic uint16_t ResolveKeyCode(uint16_t original, bool extended,\n                               uint8_t scancode) {\n  switch (original) {\n    case VK_SHIFT:\n    case VK_LSHIFT:\n      return MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX);\n    case VK_MENU:\n    case VK_LMENU:\n      return extended ? VK_RMENU : VK_LMENU;\n    case VK_CONTROL:\n    case VK_LCONTROL:\n      return extended ? VK_RCONTROL : VK_LCONTROL;\n    default:\n      return original;\n  }\n}\n\nstatic bool IsPrintable(uint32_t c) {\n  constexpr char32_t kMinPrintable = ' ';\n  constexpr char32_t kDelete = 0x7F;\n  return c >= kMinPrintable && c != kDelete;\n}\n\nconstexpr UINT kRequestIME = WM_USER + 9;\n\nLRESULT\nWindow::HandleMessage(UINT const message, WPARAM const wparam,\n                      LPARAM const lparam) noexcept {\n  LPARAM result_lparam = lparam;\n  int xPos = 0, yPos = 0;\n  UINT width = 0, height = 0;\n  UINT button_pressed = 0;\n  ClayPointerDeviceKind device_kind;\n\n  switch (message) {\n    case kWmDpiChangedBeforeParent:\n      current_dpi_ = GetDpiForHWND(window_handle_);\n      OnDpiScale(current_dpi_);\n      return 0;\n    case WM_SIZE:\n      width = LOWORD(lparam);\n      height = HIWORD(lparam);\n\n      current_width_ = width;\n      current_height_ = height;\n      HandleResize(width, height);\n      break;\n    case WM_PAINT:\n      OnPaint();\n      break;\n    case WM_TOUCH: {\n      UINT num_points = LOWORD(wparam);\n      touch_points_.resize(num_points);\n      auto touch_input_handle = reinterpret_cast<HTOUCHINPUT>(lparam);\n      if (GetTouchInputInfo(touch_input_handle, num_points,\n                            touch_points_.data(), sizeof(TOUCHINPUT))) {\n        for (const auto& touch : touch_points_) {\n          // Generate a mapped ID for the Windows-provided touch ID\n          auto touch_id = touch_id_generator_.GetGeneratedId(touch.dwID);\n\n          POINT pt = {TOUCH_COORD_TO_PIXEL(touch.x),\n                      TOUCH_COORD_TO_PIXEL(touch.y)};\n          ScreenToClient(window_handle_, &pt);\n          auto x = static_cast<double>(pt.x);\n          auto y = static_cast<double>(pt.y);\n\n          if (touch.dwFlags & TOUCHEVENTF_DOWN) {\n            OnPointerDown(x, y, kClayPointerDeviceKindTouch, touch_id,\n                          WM_LBUTTONDOWN);\n          } else if (touch.dwFlags & TOUCHEVENTF_MOVE) {\n            OnPointerMove(x, y, kClayPointerDeviceKindTouch, touch_id, 0);\n          } else if (touch.dwFlags & TOUCHEVENTF_UP) {\n            OnPointerUp(x, y, kClayPointerDeviceKindTouch, touch_id,\n                        WM_LBUTTONDOWN);\n            OnPointerLeave(x, y, kClayPointerDeviceKindTouch, touch_id);\n            touch_id_generator_.ReleaseNumber(touch.dwID);\n          }\n        }\n        CloseTouchInputHandle(touch_input_handle);\n      }\n      return 0;\n    }\n    case WM_MOUSEMOVE:\n      device_kind = GetClayPointerDeviceKind();\n      if (device_kind == kClayPointerDeviceKindMouse) {\n        TrackMouseLeaveEvent(window_handle_);\n\n        xPos = GET_X_LPARAM(lparam);\n        yPos = GET_Y_LPARAM(lparam);\n        mouse_x_ = static_cast<double>(xPos);\n        mouse_y_ = static_cast<double>(yPos);\n\n        int mods = 0;\n        if (wparam & MK_CONTROL) {\n          mods |= kControl;\n        }\n        if (wparam & MK_SHIFT) {\n          mods |= kShift;\n        }\n        OnPointerMove(mouse_x_, mouse_y_, device_kind, kDefaultPointerDeviceId,\n                      mods);\n      }\n      break;\n    case WM_MOUSELEAVE:\n      if (tracking_mouse_leave_) {\n        // Stop tracking mouse leave.\n        TRACKMOUSEEVENT tme;\n        tme.cbSize = sizeof(TRACKMOUSEEVENT);\n        tme.dwFlags = TME_LEAVE & TME_CANCEL;\n        tme.hwndTrack = window_handle_;\n        TrackMouseEvent(&tme);\n        tracking_mouse_leave_ = false;\n      }\n      device_kind = GetClayPointerDeviceKind();\n      if (device_kind == kClayPointerDeviceKindMouse) {\n        OnPointerLeave(mouse_x_, mouse_y_, device_kind,\n                       kDefaultPointerDeviceId);\n      }\n      break;\n    case WM_SETCURSOR: {\n      UINT hit_test_result = LOWORD(lparam);\n      if (hit_test_result == HTCLIENT) {\n        OnSetCursor();\n        return TRUE;\n      }\n      break;\n    }\n    case WM_SETFOCUS:\n      ::CreateCaret(window_handle_, nullptr, 1, 1);\n      break;\n    case WM_KILLFOCUS:\n      ::DestroyCaret();\n      break;\n    case WM_LBUTTONDOWN:\n    case WM_RBUTTONDOWN:\n    case WM_MBUTTONDOWN:\n    case WM_XBUTTONDOWN:\n      device_kind = GetClayPointerDeviceKind();\n      if (device_kind != kClayPointerDeviceKindMouse) {\n        break;\n      }\n\n      if (message == WM_LBUTTONDOWN) {\n        // Capture the pointer in case the user drags outside the client area.\n        // In this case, the \"mouse leave\" event is delayed until the user\n        // releases the button. It's only activated on left click given that\n        // it's more common for apps to handle dragging with only the left\n        // button.\n        SetCapture(window_handle_);\n      }\n      button_pressed = message;\n      if (message == WM_XBUTTONDOWN) {\n        button_pressed = GET_XBUTTON_WPARAM(wparam);\n      }\n      xPos = GET_X_LPARAM(lparam);\n      yPos = GET_Y_LPARAM(lparam);\n      OnPointerDown(static_cast<double>(xPos), static_cast<double>(yPos),\n                    device_kind, kDefaultPointerDeviceId, button_pressed);\n      break;\n    case WM_LBUTTONUP:\n    case WM_RBUTTONUP:\n    case WM_MBUTTONUP:\n    case WM_XBUTTONUP:\n      device_kind = GetClayPointerDeviceKind();\n      if (device_kind != kClayPointerDeviceKindMouse) {\n        break;\n      }\n\n      if (message == WM_LBUTTONUP) {\n        ReleaseCapture();\n      }\n      button_pressed = message;\n      if (message == WM_XBUTTONUP) {\n        button_pressed = GET_XBUTTON_WPARAM(wparam);\n      }\n      xPos = GET_X_LPARAM(lparam);\n      yPos = GET_Y_LPARAM(lparam);\n      OnPointerUp(static_cast<double>(xPos), static_cast<double>(yPos),\n                  device_kind, kDefaultPointerDeviceId, button_pressed);\n      break;\n    case WM_MOUSEWHEEL: {\n      double x = 0.0, y = 0.0;\n      if (LOWORD(wparam) & MK_SHIFT) {\n        x = -(static_cast<short>(HIWORD(wparam)) /\n              static_cast<double>(WHEEL_DELTA));\n      } else {\n        y = -(static_cast<short>(HIWORD(wparam)) /\n              static_cast<double>(WHEEL_DELTA));\n      }\n      OnScroll(x, y, kClayPointerDeviceKindMouse, kDefaultPointerDeviceId);\n    } break;\n    case WM_MOUSEHWHEEL:\n      OnScroll((static_cast<short>(HIWORD(wparam)) /\n                static_cast<double>(WHEEL_DELTA)),\n               0.0, kClayPointerDeviceKindMouse, kDefaultPointerDeviceId);\n      break;\n    case WM_GETOBJECT: {\n      // In order to improve the initialization speed, we will give parent\n      // hwnd when create window, that may receive more GetObject events before\n      // engine initialization completed, and caused crash here,\n      // so we remove the code.\n      break;\n    }\n    case WM_TIMER:\n      if (wparam == kDirectManipulationTimer) {\n        direct_manipulation_owner_->Update();\n        return 0;\n      }\n      break;\n    case DM_POINTERHITTEST: {\n      if (direct_manipulation_owner_) {\n        UINT contact_id = GET_POINTERID_WPARAM(wparam);\n        POINTER_INPUT_TYPE pointer_type;\n        if (windows_proc_table_->GetPointerType(contact_id, &pointer_type) &&\n            pointer_type == PT_TOUCHPAD) {\n          direct_manipulation_owner_->SetContact(contact_id);\n        }\n      }\n      break;\n    }\n    case WM_INPUTLANGCHANGE:\n      // TODO(cbracken): pass this to TextInputManager to aid with\n      // language-specific issues.\n      break;\n    case WM_IME_SETCONTEXT:\n      if (ime_callback_) {\n        return ime_callback_(window_handle_, message, wparam, lparam,\n                             ime_callback_arg_);\n      }\n      OnImeSetContext(message, wparam, lparam);\n      // Strip the ISC_SHOWUICOMPOSITIONWINDOW bit from lparam before passing it\n      // to DefWindowProc() so that the composition window is hidden since\n      // Flutter renders the composing string itself.\n      result_lparam &= ~ISC_SHOWUICOMPOSITIONWINDOW;\n      break;\n    case WM_IME_STARTCOMPOSITION:\n      if (ime_callback_) {\n        return ime_callback_(window_handle_, message, wparam, lparam,\n                             ime_callback_arg_);\n      }\n      OnImeStartComposition(message, wparam, lparam);\n      // Suppress further processing by DefWindowProc() so that the default\n      // system IME style isn't used, but rather the one set in the\n      // WM_IME_SETCONTEXT handler.\n      return TRUE;\n    case WM_IME_COMPOSITION:\n      if (ime_callback_) {\n        return ime_callback_(window_handle_, message, wparam, lparam,\n                             ime_callback_arg_);\n      }\n      OnImeComposition(message, wparam, lparam);\n      if (lparam & GCS_RESULTSTR || lparam & GCS_COMPSTR) {\n        // Suppress further processing by DefWindowProc() since otherwise it\n        // will emit the result string as WM_CHAR messages on commit. Instead,\n        // committing the composing text to the EditableText string is handled\n        // in TextInputModel::CommitComposing, triggered by\n        // OnImeEndComposition().\n        return TRUE;\n      }\n      break;\n    case WM_IME_ENDCOMPOSITION:\n      if (ime_callback_) {\n        return ime_callback_(window_handle_, message, wparam, lparam,\n                             ime_callback_arg_);\n      }\n      OnImeEndComposition(message, wparam, lparam);\n      return TRUE;\n    case WM_IME_REQUEST:\n      if (ime_callback_) {\n        return ime_callback_(window_handle_, message, wparam, lparam,\n                             ime_callback_arg_);\n      }\n      OnImeRequest(message, wparam, lparam);\n      break;\n    case WM_UNICHAR: {\n      // Tell third-pary app, we can support Unicode.\n      if (wparam == UNICODE_NOCHAR) return TRUE;\n      // DefWindowProc will send WM_CHAR for this WM_UNICHAR.\n      break;\n    }\n    case WM_THEMECHANGED:\n      OnThemeChange();\n      break;\n    case WM_DEADCHAR:\n    case WM_SYSDEADCHAR:\n    case WM_CHAR:\n    case WM_SYSCHAR:\n    case WM_KEYDOWN:\n    case WM_SYSKEYDOWN:\n    case WM_KEYUP:\n    case WM_SYSKEYUP:\n      if (ime_callback_) {\n        return ime_callback_(window_handle_, message, wparam, lparam,\n                             ime_callback_arg_);\n      }\n      if (keyboard_manager_->HandleMessage(message, wparam, lparam)) {\n        return 0;\n      }\n      break;\n    case kRequestIME:\n      (WPARAM&)ime_callback_ = wparam;\n      (LPARAM&)ime_callback_arg_ = lparam;\n      if (wparam) {\n        SetFocus(window_handle_);\n        ::ImmAssociateContextEx(window_handle_, NULL, IACE_DEFAULT);\n      } else {\n        ::ImmAssociateContextEx(window_handle_, NULL, 0);\n      }\n      break;\n  }\n\n  return Win32DefWindowProc(window_handle_, message, wparam, result_lparam);\n}\n\nUINT Window::GetCurrentDPI() { return current_dpi_; }\n\nUINT Window::GetCurrentWidth() { return current_width_; }\n\nUINT Window::GetCurrentHeight() { return current_height_; }\n\nHWND Window::GetWindowHandle() { return window_handle_; }\n\nfloat Window::GetScrollOffsetMultiplier() {\n  return scroll_offset_multiplier_ * current_dpi_ / 96.0;\n}\n\nvoid Window::UpdateScrollOffsetMultiplier() {\n  UINT lines_per_scroll = kLinesPerScrollWindowsDefault;\n\n  // Get lines per scroll wheel value from Windows\n  SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &lines_per_scroll, 0);\n\n  // This logic is based off Chromium's implementation\n  // https://source.chromium.org/chromium/chromium/src/+/main:ui/events/blink/web_input_event_builders_win.cc;l=319-331\n  scroll_offset_multiplier_ =\n      static_cast<float>(lines_per_scroll) * 100.0 / 3.0;\n}\n\nvoid Window::Destroy() {\n  if (window_handle_) {\n    text_input_manager_->SetWindowHandle(nullptr);\n    SetWindowLongPtr(window_handle_, GWLP_USERDATA, 0);\n    DestroyWindow(window_handle_);\n    window_handle_ = nullptr;\n  }\n\n  UnregisterClass(window_class_name_.c_str(), nullptr);\n}\n\nvoid Window::HandleResize(UINT width, UINT height) {\n  current_width_ = width;\n  current_height_ = height;\n  if (direct_manipulation_owner_) {\n    direct_manipulation_owner_->ResizeViewport(width, height);\n  }\n  OnResize(width, height);\n}\n\nWindow* Window::GetThisFromHandle(HWND const window) noexcept {\n  return reinterpret_cast<Window*>(GetWindowLongPtr(window, GWLP_USERDATA));\n}\n\nLRESULT Window::Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam,\n                                   LPARAM lParam) {\n  return ::DefWindowProc(hWnd, Msg, wParam, lParam);\n}\n\nBOOL Window::Win32PeekMessage(LPMSG lpMsg, UINT wMsgFilterMin,\n                              UINT wMsgFilterMax, UINT wRemoveMsg) {\n  return ::PeekMessage(lpMsg, window_handle_, wMsgFilterMin, wMsgFilterMax,\n                       wRemoveMsg);\n}\n\nuint32_t Window::Win32MapVkToChar(uint32_t virtual_key) {\n  return ::MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR);\n}\n\nUINT Window::Win32DispatchMessage(UINT Msg, WPARAM wParam, LPARAM lParam) {\n  return ::SendMessage(window_handle_, Msg, wParam, lParam);\n}\n\nbool Window::GetHighContrastEnabled() {\n  HIGHCONTRAST high_contrast = {.cbSize = sizeof(HIGHCONTRAST)};\n  // API call is only supported on Windows 8+\n  if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRAST),\n                            &high_contrast, 0)) {\n    return high_contrast.dwFlags & HCF_HIGHCONTRASTON;\n  } else {\n    FML_LOG(INFO) << \"Failed to get status of high contrast feature,\"\n                  << \"support only for Windows 8 + \";\n    return false;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/window.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_H_\n\n#include <Windows.h>\n\n#include <map>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/shell/platform/windows/direct_manipulation.h\"\n#include \"clay/shell/platform/windows/keyboard_manager.h\"\n#include \"clay/shell/platform/windows/sequential_id_generator.h\"\n#include \"clay/shell/platform/windows/text_input_manager.h\"\n#include \"clay/shell/platform/windows/windows_proc_table.h\"\n#include \"clay/shell/platform/windows/windowsx_shim.h\"\n\nnamespace clay {\n// A class abstraction for a high DPI aware Win32 Window.  Intended to be\n// inherited from by classes that wish to specialize with custom\n// rendering and input handling.\nclass Window : public KeyboardManager::WindowDelegate {\n public:\n  Window();\n  Window(std::unique_ptr<WindowsProcTable> windows_proc_table,\n         std::unique_ptr<TextInputManager> text_input_manager);\n  virtual ~Window();\n\n  // Initializes as a child window with size using |width| and |height| and\n  // |title| to identify the windowclass.  Does not show window, window must be\n  // parented into window hierarchy by caller.\n  void InitializeChild(const char* title, HWND parent_hwnd, unsigned int x,\n                       unsigned int y, unsigned int width, unsigned int height);\n\n  HWND GetWindowHandle();\n\n  // |KeyboardManager::WindowDelegate|\n  virtual BOOL Win32PeekMessage(LPMSG lpMsg, UINT wMsgFilterMin,\n                                UINT wMsgFilterMax, UINT wRemoveMsg) override;\n\n  // |KeyboardManager::WindowDelegate|\n  virtual uint32_t Win32MapVkToChar(uint32_t virtual_key) override;\n\n  // |KeyboardManager::WindowDelegate|\n  virtual UINT Win32DispatchMessage(UINT Msg, WPARAM wParam,\n                                    LPARAM lParam) override;\n\n protected:\n  // Converts a c string to a wide unicode string.\n  std::wstring NarrowToWide(const char* source);\n\n  // Registers a window class with default style attributes, cursor and\n  // icon.\n  WNDCLASS RegisterWindowClass(std::wstring& title);\n\n  // OS callback called by message pump.  Handles the WM_NCCREATE message which\n  // is passed when the non-client area is being created and enables automatic\n  // non-client DPI scaling so that the non-client area automatically\n  // responsponds to changes in DPI.  All other messages are handled by\n  // MessageHandler.\n  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,\n                                  WPARAM const wparam,\n                                  LPARAM const lparam) noexcept;\n\n  // Processes and route salient window messages for mouse handling,\n  // size change and DPI.  Delegates handling of these to member overloads that\n  // inheriting classes can handle.\n  LRESULT HandleMessage(UINT const message, WPARAM const wparam,\n                        LPARAM const lparam) noexcept;\n\n  // When WM_DPICHANGE process it using |hWnd|, |wParam|.  If\n  // |top_level| is set, extract the suggested new size from |lParam| and resize\n  // the window to the new suggested size.  If |top_level| is not set, the\n  // |lParam| will not contain a suggested size hence ignore it.\n  LRESULT HandleDpiChange(HWND hWnd, WPARAM wParam, LPARAM lParam,\n                          bool top_level);\n\n  // Called when the DPI changes either when a\n  // user drags the window between monitors of differing DPI or when the user\n  // manually changes the scale factor.\n  virtual void OnDpiScale(UINT dpi) = 0;\n\n  // Called when a resize occurs.\n  virtual void OnResize(UINT width, UINT height) = 0;\n\n  // Called when a paint is requested.\n  virtual void OnPaint() = 0;\n\n  // Called when the pointer moves within the\n  // window bounds.\n  virtual void OnPointerMove(double x, double y,\n                             ClayPointerDeviceKind device_kind,\n                             int32_t device_id, int modifiers_state) = 0;\n\n  // Called when the a mouse button, determined by |button|, goes down.\n  virtual void OnPointerDown(double x, double y,\n                             ClayPointerDeviceKind device_kind,\n                             int32_t device_id, UINT button) = 0;\n\n  // Called when the a mouse button, determined by |button|, goes from\n  // down to up\n  virtual void OnPointerUp(double x, double y,\n                           ClayPointerDeviceKind device_kind, int32_t device_id,\n                           UINT button) = 0;\n\n  // Called when the mouse leaves the window.\n  virtual void OnPointerLeave(double x, double y,\n                              ClayPointerDeviceKind device_kind,\n                              int32_t device_id) = 0;\n\n  // Called when the cursor should be set for the client area.\n  virtual void OnSetCursor() = 0;\n\n  // Called when IME composing begins.\n  virtual void OnComposeBegin() = 0;\n\n  // Called when IME composing text is committed.\n  virtual void OnComposeCommit() = 0;\n\n  // Called when IME composing ends.\n  virtual void OnComposeEnd() = 0;\n\n  // Called when IME composing text or cursor position changes.\n  virtual void OnComposeChange(const std::u16string& text, int cursor_pos) = 0;\n\n  // Called when a window is activated in order to configure IME support for\n  // multi-step text input.\n  virtual void OnImeSetContext(UINT const message, WPARAM const wparam,\n                               LPARAM const lparam);\n\n  // Called when multi-step text input begins when using an IME.\n  virtual void OnImeStartComposition(UINT const message, WPARAM const wparam,\n                                     LPARAM const lparam);\n\n  // Called when edits/commit of multi-step text input occurs when using an IME.\n  virtual void OnImeComposition(UINT const message, WPARAM const wparam,\n                                LPARAM const lparam);\n\n  // Called when multi-step text input ends when using an IME.\n  virtual void OnImeEndComposition(UINT const message, WPARAM const wparam,\n                                   LPARAM const lparam);\n\n  // Called when the user triggers an IME-specific request such as input\n  // reconversion, where an existing input sequence is returned to composing\n  // mode to select an alternative candidate conversion.\n  virtual void OnImeRequest(UINT const message, WPARAM const wparam,\n                            LPARAM const lparam);\n\n  // Called when the app ends IME composing, such as when the text input client\n  // is cleared or changed.\n  virtual void AbortImeComposing();\n\n  // Called when the cursor rect has been updated.\n  //\n  // |rect| is in Win32 window coordinates.\n  virtual void UpdateCursorRect(const FloatRect& rect);\n\n  virtual void UpdateTextInputClientFocused(int client_id);\n\n  // Called when mouse scrollwheel input occurs.\n  virtual void OnScroll(double delta_x, double delta_y,\n                        ClayPointerDeviceKind device_kind,\n                        int32_t device_id) = 0;\n\n  UINT GetCurrentDPI();\n\n  UINT GetCurrentWidth();\n\n  UINT GetCurrentHeight();\n\n  // Returns the current pixel per scroll tick value.\n  virtual float GetScrollOffsetMultiplier();\n\n  // Check if the high contrast feature is enabled on the OS\n  virtual bool GetHighContrastEnabled();\n\n protected:\n  // Win32's DefWindowProc.\n  //\n  // Used as the fallback behavior of HandleMessage. Exposed for dependency\n  // injection.\n  virtual LRESULT Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam,\n                                     LPARAM lParam);\n\n  // Handles running DirectManipulation on the window to receive trackpad\n  // gestures.\n  std::unique_ptr<DirectManipulationOwner> direct_manipulation_owner_;\n\n  // Called when a theme change message is issued\n  virtual void OnThemeChange() = 0;\n\n private:\n  // Release OS resources associated with window.\n  void Destroy();\n\n  // Activates tracking for a \"mouse leave\" event.\n  void TrackMouseLeaveEvent(HWND hwnd);\n\n  // Stores new width and height and calls |OnResize| to notify inheritors\n  void HandleResize(UINT width, UINT height);\n\n  // Retrieves a class instance pointer for |window|\n  static Window* GetThisFromHandle(HWND const window) noexcept;\n\n  // Updates the cached scroll_offset_multiplier_ value based off OS settings.\n  void UpdateScrollOffsetMultiplier();\n\n  int current_dpi_ = 0;\n  int current_width_ = 0;\n  int current_height_ = 0;\n\n  // Holds the conversion factor from lines scrolled to pixels scrolled.\n  float scroll_offset_multiplier_;\n\n  // WM_DPICHANGED_BEFOREPARENT defined in more recent Windows\n  // SDK\n  const static long kWmDpiChangedBeforeParent = 0x02E2;\n\n  // Member variable to hold window handle.\n  HWND window_handle_ = nullptr;\n\n  // Member variable to hold the window title.\n  std::wstring window_class_name_;\n\n  // Set to true to be notified when the mouse leaves the window.\n  bool tracking_mouse_leave_ = false;\n\n  // Keeps track of the last key code produced by a WM_KEYDOWN or WM_SYSKEYDOWN\n  // message.\n  int keycode_for_char_message_ = 0;\n\n  // Keeps track of the last mouse coordinates by a WM_MOUSEMOVE message.\n  double mouse_x_ = 0;\n  double mouse_y_ = 0;\n\n  // Abstracts Windows APIs that may not be available on all supported versions\n  // of Windows.\n  std::unique_ptr<WindowsProcTable> windows_proc_table_;\n\n  // Manages IME state.\n  std::unique_ptr<TextInputManager> text_input_manager_;\n\n  // Manages IME state.\n  std::unique_ptr<KeyboardManager> keyboard_manager_;\n\n  // Used for temporarily storing the WM_TOUCH-provided touch points.\n  std::vector<TOUCHINPUT> touch_points_;\n\n  // Generates touch point IDs for touch events.\n  SequentialIdGenerator touch_id_generator_;\n\n  // Timer identifier for DirectManipulation gesture polling.\n  const static int kDirectManipulationTimer = 1;\n\n  // Frequency (Hz) to poll for DirectManipulation updates.\n  int directManipulationPollingRate_ = 60;\n\n  LRESULT (*ime_callback_)(HWND, UINT, WPARAM, LPARAM, void* arg) = nullptr;\n  void* ime_callback_arg_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/window_binding_handler.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_BINDING_HANDLER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_BINDING_HANDLER_H_\n\n#include <windows.h>\n\n#include <string>\n#include <variant>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/shell/platform/windows/window_binding_handler_delegate.h\"\n#include \"clay/ui/platform/cursor_types.h\"\n\nnamespace clay {\n\nclass FlutterWindowsView;\n\n// Structure containing physical bounds of a Window\nstruct PhysicalWindowBounds {\n  size_t width;\n  size_t height;\n};\n\n// Structure containing the position of a mouse pointer in the coordinate system\n// specified by the function where it's used.\nstruct PointerLocation {\n  size_t x;\n  size_t y;\n};\n\n// Type representing an underlying platform window.\nusing PlatformWindow = HWND;\n\n// Type representing a platform object that can be accepted by the Angle\n// rendering layer to bind to and render pixels into.\nusing WindowsRenderTarget = std::variant<HWND>;\n\n// Abstract class for binding Windows platform windows to Flutter views.\nclass WindowBindingHandler {\n public:\n  virtual ~WindowBindingHandler() = default;\n\n  // Sets the delegate used to communicate state changes from window to view\n  // such as key presses, mouse position updates etc.\n  virtual void SetView(WindowBindingHandlerDelegate* view) = 0;\n\n  // Returns a valid WindowsRenderTarget representing the platform object that\n  // rendering can be bound to by ANGLE rendering backend.\n  virtual WindowsRenderTarget GetRenderTarget() = 0;\n\n  // Returns a valid PlatformWindow representing the backing\n  // window.\n  virtual PlatformWindow GetPlatformWindow() = 0;\n\n  // Returns the scale factor for the backing window.\n  virtual float GetDpiScale() = 0;\n\n  // Returns whether the PlatformWindow is currently visible.\n  virtual bool IsVisible() = 0;\n\n  // Returns the bounds of the backing window in physical pixels.\n  virtual PhysicalWindowBounds GetPhysicalWindowBounds() = 0;\n\n  // Invoked after the window has been resized.\n  virtual void OnWindowResized() = 0;\n\n  // Sets the cursor that should be used when the mouse is over the Flutter\n  // content. See mouse_cursor.dart for the values and meanings of cursor_name.\n  virtual void UpdateFlutterCursor(clay::CursorTypes cursor_type) = 0;\n\n  // Sets the cursor directly from a cursor handle.\n  virtual void SetFlutterCursor(HCURSOR cursor) = 0;\n\n  // Invoked when the cursor/composing rect has been updated in the framework.\n  virtual void OnCursorRectUpdated(const FloatRect& rect) = 0;\n\n  // Invoked when the Embedder provides us with new bitmap data for the contents\n  // of this Flutter view.\n  //\n  // Returns whether the surface was successfully updated or not.\n  virtual bool OnBitmapSurfaceUpdated(const void* allocation, size_t row_bytes,\n                                      size_t height) = 0;\n\n  // Invoked when the app ends IME composing, such when the active text input\n  // client is cleared.\n  virtual void OnResetImeComposing() = 0;\n\n  virtual void OnTextInputClientChange(int client_id) = 0;\n\n  // Returns the last known position of the primary pointer in window\n  // coordinates.\n  virtual PointerLocation GetPrimaryPointerLocation() = 0;\n\n  // If true, rendering to the window should synchronize with the vsync\n  // to prevent screen tearing.\n  virtual bool NeedsVSync() = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_BINDING_HANDLER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/window_binding_handler_delegate.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_BINDING_HANDLER_DELEGATE_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_BINDING_HANDLER_DELEGATE_H_\n\n#include <functional>\n\n#include \"clay/public/clay.h\"\n\nnamespace clay {\n\nclass WindowBindingHandlerDelegate {\n public:\n  using KeyEventCallback = std::function<void(bool)>;\n\n  // Notifies delegate that backing window size has changed.\n  // Typically called by currently configured WindowBindingHandler, this is\n  // called on the platform thread.\n  virtual void OnWindowSizeChanged(size_t width, size_t height) = 0;\n\n  // Notifies delegate that backing window needs to be repainted.\n  // Typically called by currently configured WindowBindingHandler.\n  virtual void OnWindowRepaint() = 0;\n\n  // Notifies delegate that backing window mouse has moved.\n  // Typically called by currently configured WindowBindingHandler.\n  virtual void OnPointerMove(double x, double y,\n                             ClayPointerDeviceKind device_kind,\n                             int32_t device_id, int modifiers_state) = 0;\n\n  // Notifies delegate that backing window mouse pointer button has been\n  // pressed. Typically called by currently configured WindowBindingHandler.\n  virtual void OnPointerDown(double x, double y,\n                             ClayPointerDeviceKind device_kind,\n                             int32_t device_id,\n                             ClayPointerMouseButtons button) = 0;\n\n  // Notifies delegate that backing window mouse pointer button has been\n  // released. Typically called by currently configured WindowBindingHandler.\n  virtual void OnPointerUp(double x, double y,\n                           ClayPointerDeviceKind device_kind, int32_t device_id,\n                           ClayPointerMouseButtons button) = 0;\n\n  // Notifies delegate that backing window mouse pointer has left the window.\n  // Typically called by currently configured WindowBindingHandler.\n  virtual void OnPointerLeave(double x, double y,\n                              ClayPointerDeviceKind device_kind,\n                              int32_t device_id) = 0;\n\n  // Notifies delegate that a pan/zoom gesture has started.\n  // Typically called by DirectManipulationEventHandler.\n  virtual void OnPointerPanZoomStart(int32_t device_id) = 0;\n\n  // Notifies delegate that a pan/zoom gesture has updated.\n  // Typically called by DirectManipulationEventHandler.\n  virtual void OnPointerPanZoomUpdate(int32_t device_id, double pan_x,\n                                      double pan_y, double scale,\n                                      double rotation) = 0;\n\n  // Notifies delegate that a pan/zoom gesture has ended.\n  // Typically called by DirectManipulationEventHandler.\n  virtual void OnPointerPanZoomEnd(int32_t device_id) = 0;\n\n  // Notifies delegate that backing window has received text.\n  // Typically called by currently configured WindowBindingHandler.\n  virtual void OnText(const std::u16string&) = 0;\n\n  // Notifies delegate that backing window size has received key press. Should\n  // return true if the event was handled and should not be propagated.\n  // Typically called by currently configured WindowBindingHandler.\n  virtual void OnKey(int key, int scancode, int action, char32_t character,\n                     bool extended, bool was_down,\n                     KeyEventCallback callback) = 0;\n\n  // Notifies the delegate that IME composing mode has begun.\n  //\n  // Triggered when the user begins editing composing text using a multi-step\n  // input method such as in CJK text input.\n  virtual void OnComposeBegin() = 0;\n\n  // Notifies the delegate that IME composing region have been committed.\n  //\n  // Triggered when the user commits the current composing text while using a\n  // multi-step input method such as in CJK text input. Composing continues with\n  // the next keypress.\n  virtual void OnComposeCommit() = 0;\n\n  // Notifies the delegate that IME composing mode has ended.\n  //\n  // Triggered when the user commits the composing text while using a multi-step\n  // input method such as in CJK text input.\n  virtual void OnComposeEnd() = 0;\n\n  // Notifies the delegate that IME composing region contents have changed.\n  //\n  // Triggered when the user edits the composing text while using a multi-step\n  // input method such as in CJK text input.\n  virtual void OnComposeChange(const std::u16string& text, int cursor_pos) = 0;\n\n  // Notifies delegate that backing window size has recevied scroll.\n  // Typically called by currently configured WindowBindingHandler.\n  virtual void OnScroll(double x, double y, double delta_x, double delta_y,\n                        int scroll_offset_multiplier,\n                        ClayPointerDeviceKind device_kind,\n                        int32_t device_id) = 0;\n\n  // Notifies delegate that scroll inertia should be cancelled.\n  // Typically called by DirectManipulationEventHandler\n  virtual void OnScrollInertiaCancel(int32_t device_id) = 0;\n\n  // Update the status of the high contrast feature\n  virtual void UpdateHighContrastEnabled(bool enabled) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_BINDING_HANDLER_DELEGATE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/window_mouse_drop_handler.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"clay/shell/platform/windows/window_mouse_drop_handler.h\"\n\n#include <Shlobj.h>\n#include <shellapi.h>\n\n#include <cstdio>\n#include <list>\n#include <string>\n#include <unordered_map>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/platform/win/wstring_conversion.h\"\n#include \"clay/shell/platform/windows/platform_handler.h\"\n#include \"clay/ui/gesture/drag_drop_manager.h\"\n\nnamespace clay {\n\nWindowMouseDropHandle::WindowMouseDropHandle(WindowBindingHandler* delegate,\n                                             FlutterWindowsEngine* engine)\n    : engine_(engine), window_dwEffect_(0) {\n  auto target = delegate->GetRenderTarget();\n  window_handle_ = std::get<HWND>(target);\n  auto ret = RegisterDragDrop(window_handle_, this);\n  if (ret != 0) {\n    FML_DLOG(WARNING) << \"RegisterDragDrop failed: \" << ret;\n  }\n}\n\nWindowMouseDropHandle::~WindowMouseDropHandle() {\n  RevokeDragDrop(window_handle_);\n}\n\nHRESULT STDMETHODCALLTYPE WindowMouseDropHandle::DragEnter(\n    IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) {\n  window_dwEffect_ = DROPEFFECT_NONE;\n  std::string drag_type;\n  // Check the data object for supported formats\n  FORMATETC formatEtc = {CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1,\n                         TYMED_HGLOBAL};\n  if (pDataObj->QueryGetData(&formatEtc) == S_OK) {\n    // Set the drop effect to \"copy\"\n    window_dwEffect_ = DROPEFFECT_COPY;\n    drag_type = kDragTextType;\n  }\n\n  // Check the data object for dropped files\n  formatEtc = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};\n  if (pDataObj->QueryGetData(&formatEtc) == S_OK) {\n    // Set the drop effect to drop file path\n    window_dwEffect_ = DROPEFFECT_LINK;\n    drag_type = kDragFileType;\n  }\n\n  // Return the drop effect\n  *pdwEffect = window_dwEffect_;\n\n  POINT point = {pt.x, pt.y};\n  ScreenToClient(window_handle_, &point);\n\n  if (engine_) {\n    engine_->PerformDragEnterAndOver(FloatPoint(point.x, point.y));\n  }\n  return 0;\n}\n\nHRESULT STDMETHODCALLTYPE WindowMouseDropHandle::DragOver(DWORD grfKeyState,\n                                                          POINTL pt,\n                                                          DWORD* pdwEffect) {\n  // Return the drop effect\n  *pdwEffect = window_dwEffect_;\n  std::string drag_type;\n  if (window_dwEffect_ == DROPEFFECT_COPY) {\n    drag_type = kDragTextType;\n  } else if (window_dwEffect_ == DROPEFFECT_LINK) {\n    drag_type = kDragFileType;\n  }\n  POINT point = {pt.x, pt.y};\n  ScreenToClient(window_handle_, &point);\n\n  if (engine_) {\n    engine_->PerformDragEnterAndOver(FloatPoint(point.x, point.y));\n  }\n  return 0;\n}\n\nHRESULT WindowMouseDropHandle::DragLeave() {\n  if (engine_) {\n    engine_->PerformDragLeave();\n  }\n  return 0;\n}\n\nHRESULT STDMETHODCALLTYPE WindowMouseDropHandle::Drop(IDataObject* pDataObj,\n                                                      DWORD grfKeyState,\n                                                      POINTL pt,\n                                                      DWORD* pdwEffect) {\n  std::string drag_type;\n  // construct a FORMATETC object\n  FORMATETC fmtetc;\n  STGMEDIUM stgmed = {TYMED_HGLOBAL};\n  if (window_dwEffect_ == DROPEFFECT_COPY) {\n    drag_type = kDragTextType;\n    fmtetc = {CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};\n  } else if (window_dwEffect_ == DROPEFFECT_LINK) {\n    drag_type = kDragFileType;\n    fmtetc = {CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};\n  } else {\n    return 0;\n  }\n\n  POINT point = {pt.x, pt.y};\n  ScreenToClient(window_handle_, &point);\n  bool get_data_success = false;\n\n  std::string drag_content_text = \"\";\n  std::list<std::unordered_map<std::string, std::string>> drag_content_files;\n  // See if the dataobject contains any TEXT stored as a HGLOBAL\n  if (pDataObj->QueryGetData(&fmtetc) == S_OK) {\n    // Yippie! the data is there, so go get it!\n    if (pDataObj->GetData(&fmtetc, &stgmed) == S_OK) {\n      ScopedGlobalLock locked_data(stgmed.hGlobal);\n      auto data = locked_data.get();\n      if (data != nullptr) {\n        get_data_success = true;\n        if (window_dwEffect_ == DROPEFFECT_COPY) {\n          LPWSTR pszText = (LPWSTR)data;\n          std::wstring wide(pszText);\n          std::string drop_text = fml::WideStringToUtf8(wide);\n          FML_DLOG(WARNING)\n              << \"WindowMouseDropHandle::Drop text: \" << drop_text;\n          drag_content_text = drop_text;\n        } else if (window_dwEffect_ == DROPEFFECT_LINK) {\n          std::unordered_map<std::string, std::string> file_args;\n          auto files = DragQueryFile(reinterpret_cast<HDROP>(data), 0xFFFFFFFF,\n                                     nullptr, 0);\n          UINT file_name_length = 0;\n          TCHAR* file_name = nullptr;\n          for (unsigned int i = 0; i < files; ++i) {\n            // Get length of file names.\n            file_name_length =\n                DragQueryFile(reinterpret_cast<HDROP>(data), i, nullptr, 0);\n            if (file_name_length <= 0) {\n              FML_DLOG(WARNING)\n                  << \"WindowMouseDropHandle::Drop file name length <=0\";\n              continue;\n            }\n            file_name = new TCHAR[file_name_length + 1];\n            if (!file_name) {\n              FML_DLOG(WARNING)\n                  << \"WindowMouseDropHandle::Drop file name init fail\";\n              continue;\n            }\n            DragQueryFile(reinterpret_cast<HDROP>(data), i, file_name,\n                          file_name_length + 1);\n            SHFILEINFO fileInfo = {0};\n            if (SHGetFileInfo(\n                    file_name, 0, &fileInfo, sizeof(fileInfo),\n                    SHGFI_DISPLAYNAME | SHGFI_TYPENAME | SHGFI_ATTRIBUTES)) {\n              int64_t file_size = 0;\n              std::wstring wide(file_name);\n              std::string path = fml::WideStringToUtf8(wide);\n              FML_DLOG(WARNING) << \"WindowMouseDropHandle::Drop file: \" << path;\n              std::wstring wide_name(fileInfo.szDisplayName);\n              std::string file_display_name = fml::WideStringToUtf8(wide_name);\n              std::wstring wide_type(fileInfo.szTypeName);\n              std::string file_type = fml::WideStringToUtf8(wide_type);\n              std::string file_last_modify_time;\n\n              WIN32_FIND_DATAW findData;\n              HANDLE hFind = ::FindFirstFile(file_name, &findData);\n              if (hFind != INVALID_HANDLE_VALUE) {\n                ULARGE_INTEGER fileSize;\n                fileSize.LowPart = findData.nFileSizeLow;\n                fileSize.HighPart = findData.nFileSizeHigh;\n                file_size = fileSize.QuadPart;\n\n                FILETIME ftLastWrite = findData.ftLastWriteTime;\n                FILETIME ftLocal;\n                if (FileTimeToLocalFileTime(&ftLastWrite, &ftLocal)) {\n                  SYSTEMTIME st;\n                  if (FileTimeToSystemTime(&ftLocal, &st)) {\n                    TCHAR time_str[20];\n                    wsprintf(time_str, TEXT(\"%04d/%02d/%02d %02d:%02d:%02d\"),\n                             st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute,\n                             st.wSecond);\n                    std::wstring wide_date(time_str);\n                    file_last_modify_time = fml::WideStringToUtf8(wide_date);\n                  }\n                }\n              }\n              ::FindClose(hFind);\n\n              file_args[kDragDropPathKey] = path;\n              file_args[kDragDropNameKey] = file_display_name;\n              file_args[kDragDropTypeKey] = file_type;\n              file_args[kDragDropSizeKey] = std::to_string(file_size);\n              file_args[kDragDropLastModifiedKey] = file_last_modify_time;\n              drag_content_files.push_back(file_args);\n            }\n            delete[] file_name;\n            file_name = nullptr;\n          }\n        }\n      }\n      // release the data using the COM API\n      ReleaseStgMedium(&stgmed);\n    }\n  }\n  if (get_data_success) {\n    if (engine_) {\n      engine_->PerformDragDrop(FloatPoint(point.x, point.y), drag_type,\n                               drag_content_text, drag_content_files);\n    }\n  }\n\n  return 0;\n}\n\nHRESULT STDMETHODCALLTYPE\nWindowMouseDropHandle::QueryInterface(const IID& riid, void** ppvObject) {\n  if (riid == IID_IDropTarget) {\n    *ppvObject = this;  // or static_cast<IUnknown*> if preferred\n    // AddRef() if doing things properly\n    // but then you should probably handle IID_IUnknown as well;\n    return S_OK;\n  }\n\n  *ppvObject = NULL;\n  return E_NOINTERFACE;\n}\nULONG STDMETHODCALLTYPE WindowMouseDropHandle::AddRef() { return 1; }\nULONG STDMETHODCALLTYPE WindowMouseDropHandle::Release() { return 0; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/window_mouse_drop_handler.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_MOUSE_DROP_HANDLER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_MOUSE_DROP_HANDLER_H_\n\n#include <windows.h>\n\n#include \"clay/shell/platform/windows/flutter_windows_engine.h\"\n#include \"clay/shell/platform/windows/window_binding_handler.h\"\n\nnamespace clay {\nclass WindowMouseDropHandle : public IDropTarget {\n public:\n  WindowMouseDropHandle(WindowBindingHandler* delegate,\n                        FlutterWindowsEngine* engine);\n\n  ~WindowMouseDropHandle();\n\n  HRESULT STDMETHODCALLTYPE DragEnter(IDataObject* pDataObj, DWORD grfKeyState,\n                                      POINTL pt, DWORD* pdwEffect) override;\n  HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt,\n                                     DWORD* pdwEffect) override;\n  HRESULT STDMETHODCALLTYPE DragLeave() override;\n  HRESULT STDMETHODCALLTYPE Drop(IDataObject* pDataObj, DWORD grfKeyState,\n                                 POINTL pt, DWORD* pdwEffect) override;\n\n  HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid,\n                                           void** ppvObject) override;\n  ULONG STDMETHODCALLTYPE AddRef() override;\n  ULONG STDMETHODCALLTYPE Release() override;\n\n private:\n  FlutterWindowsEngine* engine_;\n\n  HWND window_handle_;\n  DWORD window_dwEffect_;\n};\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_MOUSE_DROP_HANDLER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/window_move_handler.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"clay/shell/platform/windows/window_move_handler.h\"\n\n#include <windows.h>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nWindowMoveHandle::WindowMoveHandle(WindowBindingHandler* delegate)\n    : delegate_(delegate), weak_factory_(this) {}\n\nvoid WindowMoveHandle::MoveWindow() {\n  HWND top_hwnd = GetTopWindowHwnd();\n  if (!top_hwnd) {\n    FML_LOG(ERROR) << \"GetTopWindowHwnd error; Error getting root window! \"\n                      \"Check the hierarchy of Windows!\";\n    return;\n  }\n  // If you use SetCapture to set the focus on the form HWND before\n  // SendMessage, SendMessage(hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0) will be\n  // invalid. The solution is to add ::ReleaseCapture() before SendMessage()\n  // to release the focus;\n  ReleaseCapture();\n  PostMessage(top_hwnd, WM_SYSCOMMAND, SC_MOVE | HTCAPTION, 0);\n}\n\nHWND WindowMoveHandle::GetTopWindowHwnd() {\n  auto target = delegate_->GetRenderTarget();\n  auto hwnd = std::get<HWND>(target);\n  auto top_hwnd = hwnd;\n  while (hwnd) {\n    hwnd = GetParent(top_hwnd);\n    if (hwnd) {\n      top_hwnd = hwnd;\n    }\n  }\n  return top_hwnd;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/window_move_handler.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_MOVE_HANDLER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_MOVE_HANDLER_H_\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/shell/platform/windows/window_binding_handler.h\"\n\nnamespace clay {\nclass WindowMoveHandle {\n public:\n  explicit WindowMoveHandle(WindowBindingHandler* delegate);\n\n  HWND GetTopWindowHwnd();\n\n  void MoveWindow();\n\n private:\n  // The delegate for cursor updates.\n  WindowBindingHandler* delegate_;\n\n  fml::WeakPtrFactory<WindowMoveHandle> weak_factory_;\n};\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_MOVE_HANDLER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/window_proc_delegate_manager.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/window_proc_delegate_manager.h\"\n\nnamespace clay {\n\nWindowProcDelegateManager::WindowProcDelegateManager() = default;\nWindowProcDelegateManager::~WindowProcDelegateManager() = default;\n\nvoid WindowProcDelegateManager::RegisterTopLevelWindowProcDelegate(\n    FlutterDesktopWindowProcCallback delegate, void* user_data) {\n  top_level_window_proc_handlers_[delegate] = user_data;\n}\n\nvoid WindowProcDelegateManager::UnregisterTopLevelWindowProcDelegate(\n    FlutterDesktopWindowProcCallback delegate) {\n  top_level_window_proc_handlers_.erase(delegate);\n}\n\nstd::optional<LRESULT> WindowProcDelegateManager::OnTopLevelWindowProc(\n    HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {\n  std::optional<LRESULT> result;\n  for (const auto& [handler, user_data] : top_level_window_proc_handlers_) {\n    LPARAM handler_result;\n    // Stop as soon as any delegate indicates that it has handled the message.\n    if (handler(hwnd, message, wparam, lparam, user_data, &handler_result)) {\n      result = handler_result;\n      break;\n    }\n  }\n  return result;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/window_proc_delegate_manager.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_PROC_DELEGATE_MANAGER_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_PROC_DELEGATE_MANAGER_H_\n\n#include <dxgi.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <windows.h>\n\n#include <map>\n#include <optional>\n\n// Function pointer type for top level WindowProc delegate registration.\n//\n// The user data will be whatever was passed to\n// FlutterDesktopRegisterTopLevelWindowProcHandler.\n//\n// Implementations should populate |result| and return true if the WindowProc\n// was handled and further handling should stop. |result| is ignored if the\n// function returns false.\ntypedef bool (*FlutterDesktopWindowProcCallback)(\n    HWND /* hwnd */, UINT /* uMsg */, WPARAM /*wParam*/, LPARAM /* lParam*/,\n    void* /* user data */, LRESULT* result);\n\nnamespace clay {\n\n// Handles registration, unregistration, and dispatching for WindowProc\n// delegation.\nclass WindowProcDelegateManager {\n public:\n  explicit WindowProcDelegateManager();\n  ~WindowProcDelegateManager();\n\n  // Prevent copying.\n  WindowProcDelegateManager(WindowProcDelegateManager const&) = delete;\n  WindowProcDelegateManager& operator=(WindowProcDelegateManager const&) =\n      delete;\n\n  // Adds |delegate| as a delegate to be called for |OnTopLevelWindowProc|.\n  //\n  // Multiple calls with the same |delegate| will replace the previous\n  // registration, even if |user_data| is different.\n  void RegisterTopLevelWindowProcDelegate(\n      FlutterDesktopWindowProcCallback delegate, void* user_data);\n\n  // Unregisters |delegate| as a delate for |OnTopLevelWindowProc|.\n  void UnregisterTopLevelWindowProcDelegate(\n      FlutterDesktopWindowProcCallback delegate);\n\n  // Calls any registered WindowProc delegates.\n  //\n  // If a result is returned, then the message was handled in such a way that\n  // further handling should not be done.\n  std::optional<LRESULT> OnTopLevelWindowProc(HWND hwnd, UINT message,\n                                              WPARAM wparam, LPARAM lparam);\n\n private:\n  std::map<FlutterDesktopWindowProcCallback, void*>\n      top_level_window_proc_handlers_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_WINDOW_PROC_DELEGATE_MANAGER_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/windows_proc_table.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/platform/windows/windows_proc_table.h\"\n\nnamespace clay {\n\nWindowsProcTable::WindowsProcTable() {\n  user32_ = fml::NativeLibrary::Create(\"user32.dll\");\n  get_pointer_type_ =\n      user32_->ResolveFunction<GetPointerType_*>(\"GetPointerType\");\n}\n\nWindowsProcTable::~WindowsProcTable() { user32_ = nullptr; }\n\nBOOL WindowsProcTable::GetPointerType(UINT32 pointer_id,\n                                      POINTER_INPUT_TYPE* pointer_type) {\n  if (!get_pointer_type_.has_value()) {\n    return FALSE;\n  }\n\n  return get_pointer_type_.value()(pointer_id, pointer_type);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/platform/windows/windows_proc_table.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_\n\n#include <optional>\n\n#include \"clay/fml/native_library.h\"\n\nnamespace clay {\n\n// Lookup table for Windows APIs that aren't available on all versions of\n// Windows.\nclass WindowsProcTable {\n public:\n  WindowsProcTable();\n  virtual ~WindowsProcTable();\n\n  // Retrieves the pointer type for a specified pointer.\n  //\n  // Used to react differently to touch or pen inputs. Returns false on failure.\n  // Available in Windows 8 and newer, otherwise returns false.\n  virtual BOOL GetPointerType(UINT32 pointer_id,\n                              POINTER_INPUT_TYPE* pointer_type);\n\n private:\n  using GetPointerType_ = BOOL __stdcall(UINT32 pointerId,\n                                         POINTER_INPUT_TYPE* pointerType);\n\n  // The User32.dll library, used to resolve functions at runtime.\n  fml::RefPtr<fml::NativeLibrary> user32_;\n\n  std::optional<GetPointerType_*> get_pointer_type_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(WindowsProcTable);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_\n"
  },
  {
    "path": "clay/shell/platform/windows/windowsx_shim.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PLATFORM_WINDOWS_WINDOWSX_SHIM_H_\n#define CLAY_SHELL_PLATFORM_WINDOWS_WINDOWSX_SHIM_H_\n\n// The Win32 platform header <windowsx.h> contains some macros for\n// common function names. To work around that, windowsx.h is not to be\n// included directly, and instead this file should be included. If one\n// of the removed Win32 macros is wanted, use the expanded form\n// manually instead.\n\n#ifdef _INC_WINDOWS_X\n#error \"There is an include of windowsx.h in the code. Use windowsx_shim.h\"\n#endif  // _INC_WINDOWS_X\n\n#include <windowsx.h>\n\n#undef GetNextSibling  // Same as GetWindow(hwnd, GW_HWNDNEXT)\n#undef GetFirstChild   // Same as GetTopWindow(hwnd)\n#undef IsMaximized     // Defined to IsZoomed, use IsZoomed directly instead\n#undef IsMinimized     // Defined to IsIconic, use IsIconic directly instead\n#undef IsRestored      // Macro to check that neither WS_MINIMIZE, nor\n                       // WS_MAXIMIZE is set in the GetWindowStyle return\n                       // value.\n\n#endif  // CLAY_SHELL_PLATFORM_WINDOWS_WINDOWSX_SHIM_H_\n"
  },
  {
    "path": "clay/shell/profiling/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../shell/config.gni\")\n\n_profiler_deps = [\n  \"../../common\",\n  \"../../fml:fml\",\n]\n\nsource_set(\"profiling\") {\n  sources = [\n    \"sampling_profiler.cc\",\n    \"sampling_profiler.h\",\n  ]\n\n  deps = _profiler_deps\n}\n\nsource_set(\"profiling_unittests\") {\n  testonly = true\n  sources = [ \"sampling_profiler_unittest.cc\" ]\n  deps = [\n    \":profiling\",\n    \"../../testing\",\n  ]\n}\n"
  },
  {
    "path": "clay/shell/profiling/sampling_profiler.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/profiling/sampling_profiler.h\"\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nSamplingProfiler::SamplingProfiler(\n    const char* thread_label, fml::RefPtr<fml::TaskRunner> profiler_task_runner,\n    Sampler sampler, int num_samples_per_sec)\n    : thread_label_(thread_label),\n      profiler_task_runner_(std::move(profiler_task_runner)),\n      sampler_(std::move(sampler)),\n      num_samples_per_sec_(num_samples_per_sec) {}\n\nSamplingProfiler::~SamplingProfiler() {\n  if (is_running_) {\n    Stop();\n  }\n}\n\nvoid SamplingProfiler::Start() {\n  if (!profiler_task_runner_) {\n    return;\n  }\n  FML_CHECK(num_samples_per_sec_ > 0)\n      << \"number of samples must be a positive integer, got: \"\n      << num_samples_per_sec_;\n  double delay_between_samples = 1.0 / num_samples_per_sec_;\n  auto task_delay = fml::TimeDelta::FromSecondsF(delay_between_samples);\n  UpdateObservatoryThreadName();\n  is_running_ = true;\n  SampleRepeatedly(task_delay);\n}\n\nvoid SamplingProfiler::Stop() {\n  FML_DCHECK(is_running_);\n  auto latch = std::make_unique<fml::AutoResetWaitableEvent>();\n  shutdown_latch_.store(latch.get());\n  latch->Wait();\n  shutdown_latch_.store(nullptr);\n  is_running_ = false;\n}\n\nvoid SamplingProfiler::SampleRepeatedly(fml::TimeDelta task_delay) const {\n  profiler_task_runner_->PostDelayedTask(\n      [profiler = this, task_delay = task_delay, sampler = sampler_,\n       &shutdown_latch = shutdown_latch_]() {\n        // TODO(kaushikiska): consider buffering these every n seconds to\n        // avoid spamming the trace buffer.\n        const ProfileSample usage = sampler();\n        if (usage.cpu_usage) {\n          const auto& cpu_usage = usage.cpu_usage;\n          std::string total_cpu_usage =\n              std::to_string(cpu_usage->total_cpu_usage);\n          std::string num_threads = std::to_string(cpu_usage->num_threads);\n          TRACE_EVENT_INSTANT(\"clay::profiling\", \"CpuUsage\", \"total_cpu_usage\",\n                              total_cpu_usage.c_str(), \"num_threads\",\n                              num_threads.c_str());\n        }\n        if (usage.memory_usage) {\n          std::string dirty_memory_usage =\n              std::to_string(usage.memory_usage->dirty_memory_usage);\n          std::string owned_shared_memory_usage =\n              std::to_string(usage.memory_usage->owned_shared_memory_usage);\n          TRACE_EVENT_INSTANT(\"clay::profiling\", \"MemoryUsage\",\n                              \"dirty_memory_usage\", dirty_memory_usage.c_str(),\n                              \"owned_shared_memory_usage\",\n                              owned_shared_memory_usage.c_str());\n        }\n        if (usage.gpu_usage) {\n          std::string gpu_usage =\n              std::to_string(usage.gpu_usage->percent_usage);\n          TRACE_EVENT_INSTANT(\"clay::profiling\", \"GpuUsage\", \"gpu_usage\",\n                              gpu_usage.c_str());\n        }\n        if (shutdown_latch.load()) {\n          shutdown_latch.load()->Signal();\n        } else {\n          profiler->SampleRepeatedly(task_delay);\n        }\n      },\n      task_delay);\n}\n\nvoid SamplingProfiler::UpdateObservatoryThreadName() const {\n  FML_CHECK(profiler_task_runner_);\n\n  profiler_task_runner_->PostTask(\n      [label = thread_label_ + std::string{\".profiler\"}]() {});\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/shell/profiling/sampling_profiler.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_SHELL_PROFILING_SAMPLING_PROFILER_H_\n#define CLAY_SHELL_PROFILING_SAMPLING_PROFILER_H_\n\n#include <functional>\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/trace/native/trace_event.h\"\n\nnamespace clay {\n\n/**\n * @brief CPU usage stats. `num_threads` is the number of threads owned by the\n * process. It is to be noted that this is not per shell, there can be multiple\n * shells within the process. `total_cpu_usage` is the percentage (between [0,\n * 100]) cpu usage of the application. This is across all the cores, for example\n * an application using 100% of all the core will report `total_cpu_usage` as\n * `100`, if it has 100% across 2 cores and 0% across the other cores, embedder\n * must report `total_cpu_usage` as `50`.\n */\nstruct CpuUsageInfo {\n  uint32_t num_threads;\n  double total_cpu_usage;\n};\n\n/**\n * @brief Memory usage stats. `dirty_memory_usage` is the memory usage (in\n * MB) such that the app uses its physical memory for dirty memory. Dirty memory\n * is the memory data that cannot be paged to disk. `owned_shared_memory_usage`\n * is the memory usage (in MB) such that the app uses its physical memory for\n * shared memory, including loaded frameworks and executables. On iOS, it's\n * `physical memory - dirty memory`.\n */\nstruct MemoryUsageInfo {\n  double dirty_memory_usage;\n  double owned_shared_memory_usage;\n};\n\n/**\n * @brief Polled information related to the usage of the GPU.\n */\nstruct GpuUsageInfo {\n  double percent_usage;\n};\n\n/**\n * @brief Container for the metrics we collect during each run of `Sampler`.\n * This currently holds `CpuUsageInfo` and `MemoryUsageInfo` but the intent\n * is to expand it to other metrics.\n *\n * @see clay::Sampler\n */\nstruct ProfileSample {\n  std::optional<CpuUsageInfo> cpu_usage;\n  std::optional<MemoryUsageInfo> memory_usage;\n  std::optional<GpuUsageInfo> gpu_usage;\n};\n\n/**\n * @brief Sampler is run during `SamplingProfiler::SampleRepeatedly`. Each\n * platform should implement its version of a `Sampler` if they decide to\n * participate in gathering profiling metrics.\n *\n * @see clay::SamplingProfiler::SampleRepeatedly\n */\nusing Sampler = std::function<ProfileSample(void)>;\n\n/**\n * @brief a Sampling Profiler that runs periodically and calls the `Sampler`\n * which servers as a value function to gather various profiling metrics as\n * represented by `ProfileSample`. These profiling metrics are then posted to\n * the observatory timeline.\n *\n */\nclass SamplingProfiler {\n public:\n  /**\n   * @brief Construct a new Sampling Profiler object\n   *\n   * @param thread_label observatory prefix to be set for the profiling task\n   * runner.\n   * @param profiler_task_runner the task runner to service sampling requests.\n   * @param sampler the value function to collect the profiling metrics.\n   * @param num_samples_per_sec number of times you wish to run the sampler per\n   * second.\n   *\n   * @see fml::TaskRunner\n   */\n  SamplingProfiler(const char* thread_label,\n                   fml::RefPtr<fml::TaskRunner> profiler_task_runner,\n                   Sampler sampler, int num_samples_per_sec);\n\n  ~SamplingProfiler();\n\n  /**\n   * @brief Starts the SamplingProfiler by triggering `SampleRepeatedly`.\n   *\n   */\n  void Start();\n\n  void Stop();\n\n private:\n  const std::string thread_label_;\n  const fml::RefPtr<fml::TaskRunner> profiler_task_runner_;\n  const Sampler sampler_;\n  const uint32_t num_samples_per_sec_;\n  bool is_running_ = false;\n  std::atomic<fml::AutoResetWaitableEvent*> shutdown_latch_ = nullptr;\n\n  void SampleRepeatedly(fml::TimeDelta task_delay) const;\n\n  /**\n   * @brief This doesn't update the underlying OS thread name for the thread\n   * backing `profiler_task_runner_`. Instead, this is just additional metadata\n   * for the Observatory to show the thread name of the isolate.\n   *\n   */\n  void UpdateObservatoryThreadName() const;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(SamplingProfiler);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_SHELL_PROFILING_SAMPLING_PROFILER_H_\n"
  },
  {
    "path": "clay/shell/profiling/sampling_profiler_unittest.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/shell/profiling/sampling_profiler.h\"\n\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/thread.h\"\n#include \"clay/testing/testing.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n\nusing testing::_;\nusing testing::Invoke;\n\nnamespace fml {\nnamespace {\nclass MockTaskRunner : public fml::TaskRunner {\n public:\n  inline static RefPtr<MockTaskRunner> Create() {\n    return AdoptRef(new MockTaskRunner());\n  }\n  MOCK_METHOD1(PostTask, void(lynx::base::closure task));\n  MOCK_METHOD2(PostTaskForTime,\n               void(lynx::base::closure task, fml::TimePoint target_time));\n  MOCK_METHOD2(PostDelayedTask,\n               void(lynx::base::closure task, fml::TimeDelta delay));\n  MOCK_METHOD0(RunsTasksOnCurrentThread, bool());\n  MOCK_METHOD0(GetTaskQueueId, TaskQueueId());\n\n private:\n  MockTaskRunner() : TaskRunner(fml::RefPtr<MessageLoopImpl>()) {}\n};\n}  // namespace\n}  // namespace fml\n\nnamespace clay {\n\nTEST(SamplingProfilerTest, DeleteAfterStart) {\n  auto thread =\n      std::make_unique<fml::Thread>(clay::testing::GetCurrentTestName());\n  auto task_runner = fml::MockTaskRunner::Create();\n  std::atomic<int> invoke_count = 0;\n\n  EXPECT_CALL(*task_runner, PostDelayedTask(_, _))\n      .WillRepeatedly(\n          Invoke([&](lynx::base::closure task, fml::TimeDelta delay) {\n            invoke_count.fetch_add(1);\n            thread->GetTaskRunner()->PostTask(std::move(task));\n          }));\n\n  {\n    auto profiler = SamplingProfiler(\n        \"profiler\",\n        /*profiler_task_runner=*/task_runner, [] { return ProfileSample(); },\n        /*num_samples_per_sec=*/1000);\n    profiler.Start();\n  }\n  int invoke_count_at_delete = invoke_count.load();\n  std::this_thread::sleep_for(std::chrono::milliseconds(2));  // nyquist\n  ASSERT_EQ(invoke_count_at_delete, invoke_count.load());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/.vpython",
    "content": "python_version: \"2.7\"\n\n# Used by:\n#   auth.py\n#   gerrit_util.py\n#   git_cl.py\n#   my_activity.py\n#   TODO(crbug.com/1002153): Add ninjalog_uploader.py\nwheel: <\n  name: \"infra/python/wheels/httplib2-py2_py3\"\n  version: \"version:0.10.3\"\n>\n\n# Used by:\n#   my_activity.py\nwheel: <\n  name: \"infra/python/wheels/python-dateutil-py2_py3\"\n  version: \"version:2.7.3\"\n>\nwheel: <\n  name: \"infra/python/wheels/six-py2_py3\"\n  version: \"version:1.10.0\"\n>\n\n# Used by:\n#   tests/auth_test.py\n#   tests/detect_host_arch_test.py\n#   tests/gclient_scm_test.py\n#   tests/gclient_test.py\n#   tests/gclient_utils_test.py\n#   tests/gerrit_util_test.py\n#   tests/git_cl_test.py\n#   tests/git_drover_test.py\n#   tests/git_footers_test.py\n#   tests/metrics_test.py\n#   tests/presubmit_unittest.py\n#   tests/scm_unittest.py\n#   tests/subprocess2_test.py\n#   tests/watchlists_unittest.py\nwheel: <\n  name: \"infra/python/wheels/mock-py2_py3\"\n  version: \"version:2.0.0\"\n>\nwheel <\n  name: \"infra/python/wheels/funcsigs-py2_py3\"\n  version: \"version:1.0.2\"\n>\nwheel: <\n  name: \"infra/python/wheels/pbr-py2_py3\"\n  version: \"version:3.0.0\"\n>\nwheel: <\n  name: \"infra/python/wheels/six-py2_py3\"\n  version: \"version:1.10.0\"\n>\n"
  },
  {
    "path": "clay/testing/.vpython3",
    "content": "python_version: \"3.8\"\n\n# Used by:\n#   auth.py\n#   gerrit_util.py\n#   git_cl.py\n#   my_activity.py\n#   TODO(crbug.com/1002153): Add ninjalog_uploader.py\nwheel: <\n  name: \"infra/python/wheels/httplib2-py3\"\n  version: \"version:0.13.1\"\n>\n\n# Used by:\n#   my_activity.py\nwheel: <\n  name: \"infra/python/wheels/python-dateutil-py2_py3\"\n  version: \"version:2.7.3\"\n>\nwheel: <\n  name: \"infra/python/wheels/six-py2_py3\"\n  version: \"version:1.10.0\"\n>\n"
  },
  {
    "path": "clay/testing/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../common/config.gni\")\nimport(\"../shell/config.gni\")\nimport(\"testing.gni\")\n\nconfig(\"dynamic_symbols\") {\n  if (is_clang && is_linux) {\n    ldflags = [ \"-rdynamic\" ]\n  }\n}\n\nsource_set(\"testing_lib\") {\n  testonly = true\n\n  sources = [\n    \"assertions.h\",\n    \"fixture_test.cc\",\n    \"fixture_test.h\",\n    \"post_task_sync.cc\",\n    \"post_task_sync.h\",\n    \"testing.cc\",\n    \"testing.h\",\n    \"thread_test.cc\",\n    \"thread_test.h\",\n  ]\n\n  public_deps = [\n    \"../common:common\",\n    \"../fml:fml\",\n    \"//third_party/googletest:gmock\",\n    \"//third_party/googletest:gtest\",\n  ]\n  public_configs = [ \"../:config\" ]\n}\n\nsource_set(\"testing\") {\n  testonly = true\n\n  sources = [\n    \"debugger_detection.cc\",\n    \"debugger_detection.h\",\n    \"run_all_unittests.cc\",\n    \"test_timeout_listener.cc\",\n    \"test_timeout_listener.h\",\n  ]\n\n  public_deps = [ \":testing_lib\" ]\n  public_configs = [ \":dynamic_symbols\" ]\n}\n\nsource_set(\"skia\") {\n  testonly = true\n\n  sources = [\n    \"assertions_skia.cc\",\n    \"assertions_skia.h\",\n    \"canvas_test.h\",\n    \"mock_canvas.cc\",\n    \"mock_canvas.h\",\n  ]\n\n  public_deps = [ \":testing_lib\" ]\n\n  if (!enable_skity) {\n    public_deps += [ \"//third_party/skia\" ]\n  }\n}\n\nif (enable_unittests) {\n  test_fixtures(\"testing_fixtures\") {\n    fixtures = []\n  }\n\n  # The //clay/testing library provides utility methods to other test targets.\n  # This test target tests the testing utilities.\n  executable(\"testing_unittests\") {\n    testonly = true\n\n    sources = [ \"mock_canvas_unittests.cc\" ]\n\n    deps = [\n      \":skia\",\n      \":testing\",\n      \":testing_fixtures\",\n      \"../../base/src:base_static\",\n    ]\n\n    if (shell_enable_metal) {\n      sources += [ \"test_metal_surface_unittests.cc\" ]\n\n      deps += [ \":metal\" ]\n    }\n  }\n}\n\n# All targets on all platforms should be able to use the Metal utilities. On\n# platforms where Metal is not available, the tests must be skipped or\n# implemented to use another available client rendering API. This is usually\n# either OpenGL which is portably implemented via SwiftShader or the software\n# backend. This way, all tests compile on all platforms but the Metal backend\n# is exercised on platforms where Metal itself is available.\n#\n# On iOS, this is enabled to allow for Metal tests to run within a test app\nif (is_mac || is_ios) {\n  source_set(\"metal\") {\n    if (shell_enable_metal) {\n      sources = [\n        \"test_metal_context.h\",\n        \"test_metal_context.mm\",\n        \"test_metal_surface.cc\",\n        \"test_metal_surface.h\",\n        \"test_metal_surface_impl.h\",\n        \"test_metal_surface_impl.mm\",\n      ]\n      deps = [\n        \":skia\",\n        \"../fml:fml\",\n      ]\n      configs += [ \"../:config\" ]\n    }\n\n    testonly = true\n  }\n}\n\nsource_set(\"opengl\") {\n  testonly = true\n\n  sources = [\n    \"test_gl_surface.cc\",\n    \"test_gl_surface.h\",\n  ]\n\n  deps = [\n    \":skia\",\n    \"../fml:fml\",\n  ]\n  configs += [ \"../:config\" ]\n}\n"
  },
  {
    "path": "clay/testing/CPPLINT.cfg",
    "content": "exclude_files=.*"
  },
  {
    "path": "clay/testing/analysis_options.yaml",
    "content": "include: ../analysis_options.yaml\n\nlinter:\n  rules:\n    avoid_print: false\n    only_throw_errors: false\n"
  },
  {
    "path": "clay/testing/analyze_core_dump.sh",
    "content": "#!/bin/bash\n\n# Gather information from a core dump.\n#\n# This script can be invoked by the run_tests.py script after an\n# engine test crashes.\n\nBUILDROOT=$1\nEXE=$2\nCORE=$3\nOUTPUT=$4\n\nUNAME=$(uname)\nif [ \"$UNAME\" == \"Linux\" ]; then\n  if [ -x \"$(command -v gdb)\" ]; then\n    GDB=gdb\n  else\n    GDB=$BUILDROOT/third_party/android_tools/ndk/prebuilt/linux-x86_64/bin/gdb\n  fi\n  echo \"GDB=$GDB\"\n  $GDB $EXE $CORE --batch -ex \"thread apply all bt\" > $OUTPUT\nfi\n"
  },
  {
    "path": "clay/testing/android_systrace_test.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport argparse\nimport os\nimport subprocess\nimport sys\n\nBUILDROOT_DIR = os.path.abspath(\n    os.path.join(os.path.realpath(__file__), '..', '..', '..')\n)\n\nPERFETTO_SESSION_KEY = 'session1'\nPERFETTO_TRACE_FILE = '/data/misc/perfetto-traces/trace'\nPERFETTO_CONFIG = \"\"\"\nwrite_into_file: true\nfile_write_period_ms: 1000000000\nflush_period_ms: 1000\n\nbuffers: {\n    size_kb: 129024\n}\ndata_sources: {\n    config {\n        name: \"linux.ftrace\"\n        ftrace_config {\n            ftrace_events: \"ftrace/print\"\n            atrace_apps: \"%s\"\n        }\n    }\n}\n\"\"\"\n\n\ndef install_apk(apk_path, package_name, adb_path='adb'):\n  print('Installing APK')\n  subprocess.check_output([adb_path, 'shell', 'am', 'force-stop', package_name])\n  # Allowed to fail if APK was never installed.\n  subprocess.call([adb_path, 'uninstall', package_name],\n                  stdout=subprocess.DEVNULL)\n  subprocess.check_output([adb_path, 'install', apk_path])\n\n\ndef start_perfetto(package_name, adb_path='adb'):\n  print('Starting trace')\n  cmd = [\n      adb_path, 'shell', 'echo', \"'\" + PERFETTO_CONFIG % package_name + \"'\",\n      '|', 'perfetto', '-c', '-', '--txt', '-o', PERFETTO_TRACE_FILE,\n      '--detach', PERFETTO_SESSION_KEY\n  ]\n\n  subprocess.check_output(cmd, stderr=subprocess.STDOUT)\n\n\ndef launch_package(package_name, activity_name, adb_path='adb'):\n  print('Scanning logcat')\n  subprocess.check_output([adb_path, 'logcat', '-c'], stderr=subprocess.STDOUT)\n  logcat = subprocess.Popen([adb_path, 'logcat'],\n                            stdout=subprocess.PIPE,\n                            stderr=subprocess.STDOUT,\n                            universal_newlines=True)\n\n  print('Launching %s (%s)' % (package_name, activity_name))\n  subprocess.check_output([\n      adb_path, 'shell', 'am ', 'start', '-n',\n      '%s/%s' % (package_name, activity_name)\n  ],\n                          stderr=subprocess.STDOUT)\n  for line in logcat.stdout:\n    print('>>>>>>>> ' + line.strip())\n    if ('Observatory listening' in line) or ('Dart VM service is listening'\n                                             in line):\n      logcat.kill()\n      break\n\n\ndef collect_and_validate_trace(adb_path='adb'):\n  print('Fetching trace')\n  subprocess.check_output([\n      adb_path, 'shell', 'perfetto', '--attach', PERFETTO_SESSION_KEY, '--stop'\n  ],\n                          stderr=subprocess.STDOUT)\n  subprocess.check_output([adb_path, 'pull', PERFETTO_TRACE_FILE, 'trace.pb'],\n                          stderr=subprocess.STDOUT)\n\n  print('Validating trace')\n  traceconv = os.path.join(\n      BUILDROOT_DIR, 'third_party', 'android_tools', 'trace_to_text',\n      'trace_to_text'\n  )\n  traceconv_output = subprocess.check_output([\n      traceconv, 'systrace', 'trace.pb'\n  ],\n                                             stderr=subprocess.STDOUT,\n                                             universal_newlines=True)\n\n  print('Trace output:')\n  print(traceconv_output)\n\n  if 'ShellSetupUISubsystem' in traceconv_output:\n    return 0\n\n  print('Trace did not contain ShellSetupUISubsystem, failing.')\n  return 1\n\n\ndef main():\n  parser = argparse.ArgumentParser()\n\n  parser.add_argument(\n      '--apk-path',\n      dest='apk_path',\n      action='store',\n      help='Provide the path to the APK to install'\n  )\n  parser.add_argument(\n      '--package-name',\n      dest='package_name',\n      action='store',\n      help='The package name of the APK, e.g. dev.flutter.scenarios'\n  )\n  parser.add_argument(\n      '--activity-name',\n      dest='activity_name',\n      action='store',\n      help='The activity to launch as it appears in AndroidManifest.xml, '\n      'e.g. .PlatformViewsActivity'\n  )\n  parser.add_argument(\n      '--adb-path',\n      dest='adb_path',\n      action='store',\n      default='adb',\n      help='Provide the path of adb used for android tests. '\n      'By default it looks on $PATH.'\n  )\n\n  args = parser.parse_args()\n\n  install_apk(args.apk_path, args.package_name, args.adb_path)\n  start_perfetto(args.package_name, args.adb_path)\n  launch_package(args.package_name, args.activity_name, args.adb_path)\n  return collect_and_validate_trace(args.adb_path)\n\n\nif __name__ == '__main__':\n  sys.exit(main())\n"
  },
  {
    "path": "clay/testing/assertions.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_ASSERTIONS_H_\n#define TESTING_ASSERTIONS_H_\n\n#include <type_traits>\n\nnamespace clay {\nnamespace testing {\n\ninline bool NumberNear(double a, double b) {\n  static const double epsilon = 1e-3;\n  return (a > (b - epsilon)) && (a < (b + epsilon));\n}\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_ASSERTIONS_H_\n"
  },
  {
    "path": "clay/testing/assertions_skia.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/assertions_skia.h\"\n\nnamespace clay {\nnamespace testing {\n\nstd::ostream& operator<<(std::ostream& os, const SkClipOp& o) {\n  switch (o) {\n    case SkClipOp::kDifference:\n      os << \"ClipOpDifference\";\n      break;\n    case SkClipOp::kIntersect:\n      os << \"ClipOpIntersect\";\n      break;\n    default:\n      os << \"ClipOpUnknown\" << static_cast<int>(o);\n      break;\n  }\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkMatrix& m) {\n  os << std::endl;\n  os << \"Scale X: \" << m[SkMatrix::kMScaleX] << \", \";\n  os << \"Skew  X: \" << m[SkMatrix::kMSkewX] << \", \";\n  os << \"Trans X: \" << m[SkMatrix::kMTransX] << std::endl;\n  os << \"Skew  Y: \" << m[SkMatrix::kMSkewY] << \", \";\n  os << \"Scale Y: \" << m[SkMatrix::kMScaleY] << \", \";\n  os << \"Trans Y: \" << m[SkMatrix::kMTransY] << std::endl;\n  os << \"Persp X: \" << m[SkMatrix::kMPersp0] << \", \";\n  os << \"Persp Y: \" << m[SkMatrix::kMPersp1] << \", \";\n  os << \"Persp Z: \" << m[SkMatrix::kMPersp2];\n  os << std::endl;\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkM44& m) {\n  os << m.rc(0, 0) << \", \" << m.rc(0, 1) << \", \" << m.rc(0, 2) << \", \"\n     << m.rc(0, 3) << std::endl;\n  os << m.rc(1, 0) << \", \" << m.rc(1, 1) << \", \" << m.rc(1, 2) << \", \"\n     << m.rc(1, 3) << std::endl;\n  os << m.rc(2, 0) << \", \" << m.rc(2, 1) << \", \" << m.rc(2, 2) << \", \"\n     << m.rc(2, 3) << std::endl;\n  os << m.rc(3, 0) << \", \" << m.rc(3, 1) << \", \" << m.rc(3, 2) << \", \"\n     << m.rc(3, 3);\n  return os;\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkVector3& v) {\n  return os << v.x() << \", \" << v.y() << \", \" << v.z();\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkIRect& r) {\n  return os << \"LTRB: \" << r.fLeft << \", \" << r.fTop << \", \" << r.fRight << \", \"\n            << r.fBottom;\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkRect& r) {\n  return os << \"LTRB: \" << r.fLeft << \", \" << r.fTop << \", \" << r.fRight << \", \"\n            << r.fBottom;\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkRRect& r) {\n  return os << \"LTRB: \" << r.rect().fLeft << \", \" << r.rect().fTop << \", \"\n            << r.rect().fRight << \", \" << r.rect().fBottom;\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkPath& r) {\n  return os << \"Valid: \" << r.isValid()\n            << \", FillType: \" << static_cast<int>(r.getFillType())\n            << \", Bounds: \" << r.getBounds();\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkPoint& r) {\n  return os << \"XY: \" << r.fX << \", \" << r.fY;\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkISize& size) {\n  return os << size.width() << \", \" << size.height();\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkColor4f& r) {\n  return os << r.fR << \", \" << r.fG << \", \" << r.fB << \", \" << r.fA;\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkPaint& r) {\n  return os << \"Color: \" << r.getColor4f() << \", Style: \" << r.getStyle()\n            << \", AA: \" << r.isAntiAlias() << \", Shader: \" << r.getShader();\n}\n\nstd::ostream& operator<<(std::ostream& os, const SkSamplingOptions& s) {\n  if (s.useCubic) {\n    return os << \"CubicResampler: \" << s.cubic.B << \", \" << s.cubic.C;\n  } else {\n    return os << \"Filter: \" << static_cast<int>(s.filter)\n              << \", Mipmap: \" << static_cast<int>(s.mipmap);\n  }\n}\n\nstd::ostream& operator<<(std::ostream& os, const skity::Rect& r) {\n  return os << \"LTRB: \" << r.Left() << \", \" << r.Top() << \", \" << r.Right()\n            << \", \" << r.Bottom();\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/assertions_skia.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_ASSERTIONS_SKIA_H_\n#define TESTING_ASSERTIONS_SKIA_H_\n\n#include <ostream>\n\n#include \"skity/geometry/rect.hpp\"\n#include \"third_party/skia/include/core/SkClipOp.h\"\n#include \"third_party/skia/include/core/SkM44.h\"\n#include \"third_party/skia/include/core/SkMatrix.h\"\n#include \"third_party/skia/include/core/SkPaint.h\"\n#include \"third_party/skia/include/core/SkPath.h\"\n#include \"third_party/skia/include/core/SkPoint3.h\"\n#include \"third_party/skia/include/core/SkRRect.h\"\n#include \"third_party/skia/include/core/SkSamplingOptions.h\"\n\nnamespace clay {\nnamespace testing {\n\nextern std::ostream& operator<<(std::ostream& os, const SkClipOp& o);\nextern std::ostream& operator<<(std::ostream& os, const SkMatrix& m);\nextern std::ostream& operator<<(std::ostream& os, const SkM44& m);\nextern std::ostream& operator<<(std::ostream& os, const SkVector3& v);\nextern std::ostream& operator<<(std::ostream& os, const SkIRect& r);\nextern std::ostream& operator<<(std::ostream& os, const SkRect& r);\nextern std::ostream& operator<<(std::ostream& os, const SkRRect& r);\nextern std::ostream& operator<<(std::ostream& os, const SkPath& r);\nextern std::ostream& operator<<(std::ostream& os, const SkPoint& r);\nextern std::ostream& operator<<(std::ostream& os, const SkISize& size);\nextern std::ostream& operator<<(std::ostream& os, const SkColor4f& r);\nextern std::ostream& operator<<(std::ostream& os, const SkPaint& r);\nextern std::ostream& operator<<(std::ostream& os, const SkSamplingOptions& s);\n\nextern std::ostream& operator<<(std::ostream& os, const skity::Rect& r);\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_ASSERTIONS_SKIA_H_\n"
  },
  {
    "path": "clay/testing/canvas_test.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_CANVAS_TEST_H_\n#define TESTING_CANVAS_TEST_H_\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\n// This fixture allows creating tests that make use of a mock |SkCanvas|.\ntemplate <typename BaseT>\nclass CanvasTestBase : public BaseT {\n public:\n  CanvasTestBase() = default;\n\n  MockCanvas& mock_canvas() { return canvas_; }\n  SkColorSpace* mock_color_space() { return canvas_.imageInfo().colorSpace(); }\n\n private:\n  MockCanvas canvas_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(CanvasTestBase);\n};\nusing CanvasTest = CanvasTestBase<::testing::Test>;\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_CANVAS_TEST_H_\n"
  },
  {
    "path": "clay/testing/debugger_detection.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/debugger_detection.h\"\n\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n\n#if OS_MACOSX\n#include <assert.h>\n#include <stdbool.h>\n#include <sys/sysctl.h>\n#include <sys/types.h>\n#include <unistd.h>\n#endif  // OS_MACOSX\n\n#if OS_WIN\n#include <windows.h>\n#endif  // OS_WIN\n\nnamespace clay {\nnamespace testing {\n\nDebuggerStatus GetDebuggerStatus() {\n#if OS_MACOSX\n  // From Technical Q&A QA1361 Detecting the Debugger\n  // https://developer.apple.com/library/archive/qa/qa1361/_index.html\n  int management_info_base[4];\n  struct kinfo_proc info;\n  size_t size;\n\n  // Initialize the flags so that, if sysctl fails for some bizarre\n  // reason, we get a predictable result.\n  info.kp_proc.p_flag = 0;\n\n  // Initialize management_info_base, which tells sysctl the info we want, in\n  // this case we're looking for information about a specific process ID.\n  management_info_base[0] = CTL_KERN;\n  management_info_base[1] = KERN_PROC;\n  management_info_base[2] = KERN_PROC_PID;\n  management_info_base[3] = getpid();\n\n  // Call sysctl.\n\n  size = sizeof(info);\n  auto status =\n      ::sysctl(management_info_base,\n               sizeof(management_info_base) / sizeof(*management_info_base),\n               &info, &size, NULL, 0);\n  FML_CHECK(status == 0);\n\n  // We're being debugged if the P_TRACED flag is set.\n  return ((info.kp_proc.p_flag & P_TRACED) != 0) ? DebuggerStatus::kAttached\n                                                 : DebuggerStatus::kDontKnow;\n\n#elif OS_WIN\n  return ::IsDebuggerPresent() ? DebuggerStatus::kAttached\n                               : DebuggerStatus::kDontKnow;\n\n#else\n  return DebuggerStatus::kDontKnow;\n#endif\n}  // namespace testing\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/debugger_detection.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_DEBUGGER_DETECTION_H_\n#define TESTING_DEBUGGER_DETECTION_H_\n\n#include \"base/include/fml/macros.h\"\n\nnamespace clay {\nnamespace testing {\n\nenum class DebuggerStatus {\n  kDontKnow,\n  kAttached,\n};\n\nDebuggerStatus GetDebuggerStatus();\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_DEBUGGER_DETECTION_H_\n"
  },
  {
    "path": "clay/testing/fixture_test.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/fixture_test.h\"\n\n#include <utility>\n\nnamespace clay {\nnamespace testing {\n\nFixtureTest::FixtureTest() {}\n\nSettings FixtureTest::CreateSettingsForFixture() {\n  Settings settings;\n  return settings;\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/fixture_test.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_FIXTURE_TEST_H_\n#define TESTING_FIXTURE_TEST_H_\n\n#include <string>\n\n#include \"clay/common/settings.h\"\n#include \"clay/testing/thread_test.h\"\n\nnamespace clay {\nnamespace testing {\n\nclass FixtureTest : public ThreadTest {\n public:\n  // Uses the default filenames from the fixtures generator.\n  FixtureTest();\n\n  virtual Settings CreateSettingsForFixture();\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(FixtureTest);\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_FIXTURE_TEST_H_\n"
  },
  {
    "path": "clay/testing/lsan_suppressions.txt",
    "content": "# For more information on how to add additional suppressions here go to\n# https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#suppressions\n\n# The Dart VM has incorrectly annotated false positives that we don't care\n# about.\nleak:dart::*\n\n# Objective-C class definitions are not leaks.\nleak:class_createInstance\n\n# SkSurfaces from Embedder backing stores may not be collected on engine\n# shutdown.\n# https://github.com/flutter/flutter/issues/42172\nleak:MakeSkSurfaceFromBackingStore\n\n# dispatch_once blocks don't cleanup their contents.\nleak:_dispatch_once_callout\n\n# Message loops in TLS slots may not be collected.\n# https://github.com/flutter/flutter/issues/42174\nleak:fml::MessageLoop::EnsureInitializedForCurrentThread\n\n# CFRunLoopSource in MessageLoopDarwin not being collected\n# https://github.com/flutter/flutter/issues/42175\nleak:wrap_dispatch_source_set_event_handler\n\n\n# Dispatched platform message that are not handled due to isolate death are\n# leaked.\n# https://github.com/flutter/flutter/issues/42176\nleak:clay::PlatformViewEmbedder::HandlePlatformMessage\n\n# Font families on are leaked when launching the shell.\n# https://github.com/flutter/flutter/issues/42179\nleak:txt::FontCollection::CreateMinikinFontFamily\nleak:CGFontCreateFontsWithURL\nleak:txt::GetDefaultFontFamily\n\n# The RefCounted DebugChecks tests intentionally fail to wrap and manage heap\n# allocated objects.\nleak:RefCountedTest_DebugChecks_Test::TestBody\n\n# Objects allocated by this mock API are intentionally not collected and are\n# instead marked as released in order to explicitly assert double free attempts\n# and allow some tests to inspect contents.\nleak:*flutter/shell/platform/linux/testing/mock_engine.cc\n\n# TODO(bdero): Fix FL leaks: https://github.com/flutter/flutter/issues/90155\nleak:*flutter/shell/platform/linux/fl_keyboard_manager_test.cc*\nleak:*flutter/shell/platform/linux/fl_key_channel_responder_test.cc*\nleak:*flutter/shell/platform/linux/fl_basic_message_channel_test.cc*\nleak:fl_message_codec_decode_message\n\n# TODO(bdero): https://github.com/flutter/flutter/issues/90156\n# Unfortunately, realloc calls originating from g_realloc effectively obscure\n# the trace of the original allocation. Deeper investigation is required to\n# identify the original allocation source for these.\n# At the time of writing, this only amounts to 4 allocations totaling 512 bytes\n# worth of leaks.\nleak:g_realloc\n\n\n# ANGLE's egl impelementation uses annotations that our LSAN setup does\n# not respect. Ignore leaks from egl::*\nleak:egl::*\n"
  },
  {
    "path": "clay/testing/mock_canvas.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/mock_canvas.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"third_party/skia/include/core/SkImageInfo.h\"\n#include \"third_party/skia/include/core/SkPicture.h\"\n#include \"third_party/skia/include/core/SkSerialProcs.h\"\n#include \"third_party/skia/include/core/SkSize.h\"\n#include \"third_party/skia/include/core/SkTextBlob.h\"\n\nnamespace clay {\nnamespace testing {\n\nconstexpr SkISize kSize = SkISize::Make(64, 64);\n\nMockCanvas::MockCanvas()\n    : SkCanvasVirtualEnforcer<SkCanvas>(kSize.fWidth, kSize.fHeight),\n      internal_canvas_(imageInfo().width(), imageInfo().height()),\n      current_layer_(0) {\n  internal_canvas_.addCanvas(this);\n}\n\nMockCanvas::~MockCanvas() { EXPECT_EQ(current_layer_, 0); }\n\nvoid MockCanvas::willSave() {\n  draw_calls_.emplace_back(\n      DrawCall{current_layer_, SaveData{current_layer_ + 1}});\n  current_layer_++;  // Must go here; func params order of eval is undefined\n}\n\nSkCanvas::SaveLayerStrategy MockCanvas::getSaveLayerStrategy(\n    const SaveLayerRec& rec) {\n  // saveLayer calls this prior to running, so we use it to track saveLayer\n  // calls\n  draw_calls_.emplace_back(DrawCall{\n      current_layer_,\n      SaveLayerData{rec.fBounds ? *rec.fBounds : SkRect(),\n                    rec.fPaint ? *rec.fPaint : SkPaint(),\n                    rec.fBackdrop ? sk_ref_sp<SkImageFilter>(rec.fBackdrop)\n                                  : sk_sp<SkImageFilter>(),\n                    current_layer_ + 1}});\n  current_layer_++;  // Must go here; func params order of eval is undefined\n  return kNoLayer_SaveLayerStrategy;\n}\n\nvoid MockCanvas::willRestore() {\n  FML_DCHECK(current_layer_ > 0);\n\n  draw_calls_.emplace_back(\n      DrawCall{current_layer_, RestoreData{current_layer_ - 1}});\n  current_layer_--;  // Must go here; func params order of eval is undefined\n}\n\nvoid MockCanvas::didConcat44(const SkM44& matrix) {\n  draw_calls_.emplace_back(DrawCall{current_layer_, ConcatMatrixData{matrix}});\n}\n\nvoid MockCanvas::didSetM44(const SkM44& matrix) {\n  draw_calls_.emplace_back(DrawCall{current_layer_, SetMatrixData{matrix}});\n}\n\nvoid MockCanvas::didScale(SkScalar x, SkScalar y) {\n  this->didConcat44(SkM44::Scale(x, y));\n}\n\nvoid MockCanvas::didTranslate(SkScalar x, SkScalar y) {\n  this->didConcat44(SkM44::Translate(x, y));\n}\n\nvoid MockCanvas::onDrawTextBlob(const SkTextBlob* text, SkScalar x, SkScalar y,\n                                const SkPaint& paint) {\n  // This duplicates existing logic in SkCanvas::onDrawPicture\n  // that should probably be split out so it doesn't need to be here as well.\n  SkRect storage;\n  if (paint.canComputeFastBounds()) {\n    storage = text->bounds().makeOffset(x, y);\n    SkRect tmp;\n    if (this->quickReject(paint.computeFastBounds(storage, &tmp))) {\n      return;\n    }\n  }\n\n  draw_calls_.emplace_back(DrawCall{\n      current_layer_, DrawTextData{text ? text->serialize(SkSerialProcs{})\n                                        : SkData::MakeUninitialized(0),\n                                   paint, SkPoint::Make(x, y)}});\n}\n\nvoid MockCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {\n  draw_calls_.emplace_back(DrawCall{current_layer_, DrawRectData{rect, paint}});\n}\n\nvoid MockCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) {\n  draw_calls_.emplace_back(DrawCall{current_layer_, DrawPathData{path, paint}});\n}\n\nvoid MockCanvas::onDrawShadowRec(const SkPath& path,\n                                 const SkDrawShadowRec& rec) {\n  // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125\n  (void)rec;  // Can't use b/c Skia keeps this type anonymous.\n  draw_calls_.emplace_back(DrawCall{current_layer_, DrawShadowData{path}});\n}\n\nvoid MockCanvas::onDrawImage2(const SkImage* image, SkScalar x, SkScalar y,\n                              const SkSamplingOptions& options,\n                              const SkPaint* paint) {\n  if (paint) {\n    draw_calls_.emplace_back(\n        DrawCall{current_layer_,\n                 DrawImageData{sk_ref_sp(image), x, y, options, *paint}});\n  } else {\n    draw_calls_.emplace_back(DrawCall{\n        current_layer_, DrawImageDataNoPaint{sk_ref_sp(image), x, y, options}});\n  }\n}\n\nvoid MockCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,\n                               const SkPaint* paint) {\n  // This duplicates existing logic in SkCanvas::onDrawPicture\n  // that should probably be split out so it doesn't need to be here as well.\n  if (!paint || paint->canComputeFastBounds()) {\n    SkRect bounds = picture->cullRect();\n    if (paint) {\n      paint->computeFastBounds(bounds, &bounds);\n    }\n    if (matrix) {\n      matrix->mapRect(&bounds);\n    }\n    if (this->quickReject(bounds)) {\n      return;\n    }\n  }\n\n  draw_calls_.emplace_back(DrawCall{\n      current_layer_,\n      DrawPictureData{\n          picture ? picture->serialize() : SkData::MakeUninitialized(0),\n          paint ? *paint : SkPaint(), matrix ? *matrix : SkMatrix()}});\n}\n\nvoid MockCanvas::onClipRect(const SkRect& rect, SkClipOp op,\n                            ClipEdgeStyle style) {\n  draw_calls_.emplace_back(\n      DrawCall{current_layer_, ClipRectData{rect, op, style}});\n  // quickReject() is handled by base class and needs accurate clip information\n  SkCanvas::onClipRect(rect, op, style);\n}\n\nvoid MockCanvas::onClipRRect(const SkRRect& rrect, SkClipOp op,\n                             ClipEdgeStyle style) {\n  draw_calls_.emplace_back(\n      DrawCall{current_layer_, ClipRRectData{rrect, op, style}});\n  // quickReject() is handled by base class and needs accurate clip information\n  SkCanvas::onClipRRect(rrect, op, style);\n}\n\nvoid MockCanvas::onClipPath(const SkPath& path, SkClipOp op,\n                            ClipEdgeStyle style) {\n  draw_calls_.emplace_back(\n      DrawCall{current_layer_, ClipPathData{path, op, style}});\n  // quickReject() is handled by base class and needs accurate clip information\n  SkCanvas::onClipPath(path, op, style);\n}\n\nbool MockCanvas::onDoSaveBehind(const SkRect*) {\n  FML_DCHECK(false);\n  return false;\n}\n\nvoid MockCanvas::onDrawAnnotation(const SkRect&, const char[], SkData*) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawDrawable(SkDrawable*, const SkMatrix*) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawPatch(const SkPoint[12], const SkColor[4],\n                             const SkPoint[4], SkBlendMode, const SkPaint&) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawPaint(const SkPaint& skPaint) {\n  draw_calls_.emplace_back(DrawCall{current_layer_, DrawPaint{skPaint}});\n}\n\nvoid MockCanvas::onDrawBehind(const SkPaint&) { FML_DCHECK(false); }\n\nvoid MockCanvas::onDrawPoints(PointMode mode, size_t count, const SkPoint pts[],\n                              const SkPaint& paint) {\n  draw_calls_.emplace_back(DrawCall{\n      current_layer_,\n      DrawPointsData{mode, paint, std::vector<SkPoint>{pts, pts + count}}});\n}\n\nvoid MockCanvas::onDrawRegion(const SkRegion&, const SkPaint&) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawOval(const SkRect&, const SkPaint&) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawArc(const SkRect&, SkScalar, SkScalar, bool,\n                           const SkPaint&) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawRRect(const SkRRect& rect, const SkPaint& paint) {\n  draw_calls_.emplace_back(\n      DrawCall{current_layer_, DrawRRectData{rect, paint}});\n}\n\n// void MockCanvas::onDrawImage2(const SkImage*,\n//                               SkScalar,\n//                               SkScalar,\n//                               const SkSamplingOptions&,\n//                               const SkPaint*) {\n//   FML_DCHECK(false);\n// }\n\nvoid MockCanvas::onDrawImageRect2(const SkImage* image, const SkRect& src,\n                                  const SkRect& dst,\n                                  const SkSamplingOptions& sampling,\n                                  const SkPaint* paint,\n                                  SrcRectConstraint constraint) {\n  draw_calls_.emplace_back(DrawCall{\n      current_layer_,\n      DrawImageRectData{image ? image->uniqueID() : 0, src, dst, sampling,\n                        constraint, paint ? *paint : SkPaint{}}});\n}\n\nvoid MockCanvas::onDrawImageLattice2(const SkImage*, const Lattice&,\n                                     const SkRect&, SkFilterMode,\n                                     const SkPaint*) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawVerticesObject(const SkVertices*, SkBlendMode,\n                                      const SkPaint&) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawAtlas2(const SkImage*, const SkRSXform[], const SkRect[],\n                              const SkColor[], int, SkBlendMode,\n                              const SkSamplingOptions&, const SkRect*,\n                              const SkPaint*) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags,\n                                  const SkColor4f&, SkBlendMode) {\n  FML_DCHECK(false);\n}\n\nvoid MockCanvas::onClipRegion(const SkRegion&, SkClipOp) { FML_DCHECK(false); }\n\nbool operator==(const MockCanvas::SaveData& a, const MockCanvas::SaveData& b) {\n  return a.save_to_layer == b.save_to_layer;\n}\n\nstd::ostream& operator<<(std::ostream& os, const MockCanvas::SaveData& data) {\n  return os << data.save_to_layer;\n}\n\nbool operator==(const MockCanvas::SaveLayerData& a,\n                const MockCanvas::SaveLayerData& b) {\n  return a.save_bounds == b.save_bounds && a.restore_paint == b.restore_paint &&\n         a.backdrop_filter == b.backdrop_filter &&\n         a.save_to_layer == b.save_to_layer;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::SaveLayerData& data) {\n  return os << data.save_bounds << \" \" << data.restore_paint << \" \"\n            << data.backdrop_filter << \" \" << data.save_to_layer;\n}\n\nbool operator==(const MockCanvas::RestoreData& a,\n                const MockCanvas::RestoreData& b) {\n  return a.restore_to_layer == b.restore_to_layer;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::RestoreData& data) {\n  return os << data.restore_to_layer;\n}\n\nbool operator==(const MockCanvas::ConcatMatrixData& a,\n                const MockCanvas::ConcatMatrixData& b) {\n  return a.matrix == b.matrix;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::ConcatMatrixData& data) {\n  return os << data.matrix;\n}\n\nbool operator==(const MockCanvas::SetMatrixData& a,\n                const MockCanvas::SetMatrixData& b) {\n  return a.matrix == b.matrix;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::SetMatrixData& data) {\n  return os << data.matrix;\n}\n\nbool operator==(const MockCanvas::DrawRectData& a,\n                const MockCanvas::DrawRectData& b) {\n  return a.rect == b.rect && a.paint == b.paint;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawRectData& data) {\n  return os << data.rect << \" \" << data.paint;\n}\n\nbool operator==(const MockCanvas::DrawRRectData& a,\n                const MockCanvas::DrawRRectData& b) {\n  return a.rect == b.rect && a.paint == b.paint;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawRRectData& data) {\n  return os << data.rect << \" \" << data.paint;\n}\n\nbool operator==(const MockCanvas::DrawPathData& a,\n                const MockCanvas::DrawPathData& b) {\n  return a.path == b.path && a.paint == b.paint;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawPathData& data) {\n  return os << data.path << \" \" << data.paint;\n}\n\nbool operator==(const MockCanvas::DrawTextData& a,\n                const MockCanvas::DrawTextData& b) {\n  return a.serialized_text->equals(b.serialized_text.get()) &&\n         a.paint == b.paint && a.offset == b.offset;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawTextData& data) {\n  return os << data.serialized_text << \" \" << data.paint << \" \" << data.offset;\n}\n\nbool operator==(const MockCanvas::DrawImageData& a,\n                const MockCanvas::DrawImageData& b) {\n  return a.image == b.image && a.x == b.x && a.y == b.y &&\n         a.options == b.options && a.paint == b.paint;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawImageData& data) {\n  return os << data.image << \" \" << data.x << \" \" << data.y << \" \"\n            << data.options << \" \" << data.paint;\n}\n\nbool operator==(const MockCanvas::DrawImageDataNoPaint& a,\n                const MockCanvas::DrawImageDataNoPaint& b) {\n  return a.image == b.image && a.x == b.x && a.y == b.y &&\n         a.options == b.options;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawImageDataNoPaint& data) {\n  return os << data.image << \" \" << data.x << \" \" << data.y << \" \"\n            << data.options;\n}\n\nbool operator==(const MockCanvas::DrawPictureData& a,\n                const MockCanvas::DrawPictureData& b) {\n  return a.serialized_picture->equals(b.serialized_picture.get()) &&\n         a.paint == b.paint && a.matrix == b.matrix;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawPictureData& data) {\n  return os << data.serialized_picture << \" \" << data.paint << \" \"\n            << data.matrix;\n}\n\nbool operator==(const MockCanvas::DrawImageRectData& a,\n                const MockCanvas::DrawImageRectData& b) {\n  return a.image_unique_id == b.image_unique_id && a.src == b.src &&\n         a.dst == b.dst && a.sampling == b.sampling &&\n         a.constraint == b.constraint && a.paint == b.paint;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawImageRectData& data) {\n  return os << data.image_unique_id << \" \" << data.src << \" \" << data.dst << \" \"\n            << data.sampling << \" \" << data.constraint << \" \" << data.paint;\n}\n\nbool operator==(const MockCanvas::DrawPointsData& a,\n                const MockCanvas::DrawPointsData& b) {\n  return a.mode == b.mode && a.paint == b.paint && a.points == b.points;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawPointsData& data) {\n  os << data.mode << \" \" << data.paint;\n  for (auto& point : data.points) {\n    os << \" \" << point;\n  }\n  return os;\n}\n\nbool operator==(const MockCanvas::DrawShadowData& a,\n                const MockCanvas::DrawShadowData& b) {\n  return a.path == b.path;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawShadowData& data) {\n  return os << data.path;\n}\n\nbool operator==(const MockCanvas::ClipRectData& a,\n                const MockCanvas::ClipRectData& b) {\n  return a.rect == b.rect && a.clip_op == b.clip_op && a.style == b.style;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::ClipRectData& data) {\n  return os << data.rect << \" \" << data.clip_op << \" \" << data.style;\n}\n\nbool operator==(const MockCanvas::ClipRRectData& a,\n                const MockCanvas::ClipRRectData& b) {\n  return a.rrect == b.rrect && a.clip_op == b.clip_op && a.style == b.style;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::ClipRRectData& data) {\n  return os << data.rrect << \" \" << data.clip_op << \" \" << data.style;\n}\n\nbool operator==(const MockCanvas::ClipPathData& a,\n                const MockCanvas::ClipPathData& b) {\n  return a.path == b.path && a.clip_op == b.clip_op && a.style == b.style;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::ClipPathData& data) {\n  return os << data.path << \" \" << data.clip_op << \" \" << data.style;\n}\n\nstd::ostream& operator<<(std::ostream& os,\n                         const MockCanvas::DrawCallData& data) {\n  std::visit(\n      [&os](auto&& arg) {\n        using T = std::decay_t<decltype(arg)>;\n        if constexpr (std::is_same_v<T, MockCanvas::DrawImageRectData>) {\n          os << \"DrawImageRectData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::DrawPointsData>) {\n          os << \"DrawPointsData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::SaveData>) {\n          os << \"SaveData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::SaveLayerData>) {\n          os << \"SaveLayerData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::RestoreData>) {\n          os << \"RestoreData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::ConcatMatrixData>) {\n          os << \"ConcatMatrixData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::SetMatrixData>) {\n          os << \"SetMatrixData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::DrawRectData>) {\n          os << \"DrawRectData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::DrawRRectData>) {\n          os << \"DrawRRectData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::DrawPathData>) {\n          os << \"DrawPathData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::DrawTextData>) {\n          os << \"DrawTextData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::DrawPictureData>) {\n          os << \"DrawPictureData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::DrawShadowData>) {\n          os << \"DrawShadowData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::ClipRectData>) {\n          os << \"ClipRectData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::ClipRRectData>) {\n          os << \"ClipRRectData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::ClipPathData>) {\n          os << \"ClipPathData: \" << arg;\n        } else if constexpr (std::is_same_v<T, MockCanvas::DrawPaint>) {\n          os << \"DrawPaint: \" << arg;\n        } else {\n          os << \"other type: \" << arg;\n        }\n        os << \", \";\n      },\n      data);\n  return os;\n}\n\nbool operator==(const MockCanvas::DrawCall& a, const MockCanvas::DrawCall& b) {\n  return a.layer == b.layer && a.data == b.data;\n}\n\nstd::ostream& operator<<(std::ostream& os, const MockCanvas::DrawCall& draw) {\n  return os << \"[Layer: \" << draw.layer << \", Data: \" << draw.data << \"]\";\n}\n\nbool operator==(const MockCanvas::DrawPaint& a,\n                const MockCanvas::DrawPaint& b) {\n  return a.paint == b.paint;\n}\n\nstd::ostream& operator<<(std::ostream& os, const MockCanvas::DrawPaint& data) {\n  return os << data.paint;\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/mock_canvas.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_MOCK_CANVAS_H_\n#define TESTING_MOCK_CANVAS_H_\n\n#include <ostream>\n#include <variant>\n#include <vector>\n\n#include \"clay/testing/assertions_skia.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkCanvasVirtualEnforcer.h\"\n#include \"third_party/skia/include/core/SkClipOp.h\"\n#include \"third_party/skia/include/core/SkData.h\"\n#include \"third_party/skia/include/core/SkImage.h\"\n#include \"third_party/skia/include/core/SkImageFilter.h\"\n#include \"third_party/skia/include/core/SkM44.h\"\n#include \"third_party/skia/include/core/SkPath.h\"\n#include \"third_party/skia/include/core/SkRRect.h\"\n#include \"third_party/skia/include/core/SkRect.h\"\n#include \"third_party/skia/include/utils/SkNWayCanvas.h\"\n\nnamespace clay {\nnamespace testing {\n\nstatic constexpr skity::Rect kEmptyRect = skity::Rect::MakeEmpty();\n\n// Mock |SkCanvas|, useful for writing tests that use Skia but do not interact\n// with the GPU.\n//\n// The |MockCanvas| stores a list of |DrawCall| that the test can later verify\n// against the expected list of primitives to be drawn.\nclass MockCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {\n public:\n  using SkCanvas::kHard_ClipEdgeStyle;\n  using SkCanvas::kSoft_ClipEdgeStyle;\n\n  struct SaveData {\n    int save_to_layer;\n  };\n\n  struct SaveLayerData {\n    SkRect save_bounds;\n    SkPaint restore_paint;\n    sk_sp<SkImageFilter> backdrop_filter;\n    int save_to_layer;\n  };\n\n  struct RestoreData {\n    int restore_to_layer;\n  };\n\n  struct ConcatMatrixData {\n    SkM44 matrix;\n  };\n\n  struct SetMatrixData {\n    SkM44 matrix;\n  };\n\n  struct DrawRectData {\n    SkRect rect;\n    SkPaint paint;\n  };\n\n  struct DrawRRectData {\n    SkRRect rect;\n    SkPaint paint;\n  };\n\n  struct DrawPathData {\n    SkPath path;\n    SkPaint paint;\n  };\n\n  struct DrawTextData {\n    sk_sp<SkData> serialized_text;\n    SkPaint paint;\n    SkPoint offset;\n  };\n\n  struct DrawImageDataNoPaint {\n    sk_sp<SkImage> image;\n    SkScalar x;\n    SkScalar y;\n    SkSamplingOptions options;\n  };\n\n  struct DrawImageData {\n    sk_sp<SkImage> image;\n    SkScalar x;\n    SkScalar y;\n    SkSamplingOptions options;\n    SkPaint paint;\n  };\n\n  struct DrawPictureData {\n    sk_sp<SkData> serialized_picture;\n    SkPaint paint;\n    SkMatrix matrix;\n  };\n\n  struct DrawImageRectData {\n    uint32_t image_unique_id;\n    SkRect src;\n    SkRect dst;\n    SkSamplingOptions sampling;\n    SrcRectConstraint constraint;\n    SkPaint paint;\n  };\n\n  struct DrawPointsData {\n    PointMode mode;\n    SkPaint paint;\n    std::vector<SkPoint> points;\n  };\n\n  struct DrawShadowData {\n    SkPath path;\n  };\n\n  struct ClipRectData {\n    SkRect rect;\n    SkClipOp clip_op;\n    ClipEdgeStyle style;\n  };\n\n  struct ClipRRectData {\n    SkRRect rrect;\n    SkClipOp clip_op;\n    ClipEdgeStyle style;\n  };\n\n  struct ClipPathData {\n    SkPath path;\n    SkClipOp clip_op;\n    ClipEdgeStyle style;\n  };\n\n  struct DrawPaint {\n    SkPaint paint;\n  };\n\n  // Discriminated union of all the different |DrawCall| types.  It is roughly\n  // equivalent to the different methods in |SkCanvas|' public API.\n  using DrawCallData =\n      std::variant<SaveData, SaveLayerData, RestoreData, ConcatMatrixData,\n                   SetMatrixData, DrawRectData, DrawRRectData, DrawPathData,\n                   DrawTextData, DrawImageDataNoPaint, DrawImageData,\n                   DrawPictureData, DrawImageRectData, DrawPointsData,\n                   DrawShadowData, ClipRectData, ClipRRectData, ClipPathData,\n                   DrawPaint>;\n\n  // A single call made against this canvas.\n  struct DrawCall {\n    int layer;\n    DrawCallData data;\n  };\n\n  MockCanvas();\n  ~MockCanvas() override;\n\n  SkNWayCanvas* internal_canvas() { return &internal_canvas_; }\n\n  const std::vector<DrawCall>& draw_calls() const { return draw_calls_; }\n  void reset_draw_calls() { draw_calls_.clear(); }\n\n protected:\n  // Save/restore/set operations that we track.\n  void willSave() override;\n  SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override;\n  void willRestore() override;\n  void didRestore() override {}\n  void didConcat44(const SkM44&) override;\n  void didSetM44(const SkM44&) override;\n  void didScale(SkScalar x, SkScalar y) override;\n  void didTranslate(SkScalar x, SkScalar y) override;\n\n  // Draw and clip operations that we track.\n  void onDrawRect(const SkRect& rect, const SkPaint& paint) override;\n  void onDrawPath(const SkPath& path, const SkPaint& paint) override;\n  void onDrawTextBlob(const SkTextBlob* text, SkScalar x, SkScalar y,\n                      const SkPaint& paint) override;\n  void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override;\n  void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix,\n                     const SkPaint* paint) override;\n  void onClipRect(const SkRect& rect, SkClipOp op,\n                  ClipEdgeStyle style) override;\n  void onClipRRect(const SkRRect& rrect, SkClipOp op,\n                   ClipEdgeStyle style) override;\n  void onClipPath(const SkPath& path, SkClipOp op,\n                  ClipEdgeStyle style) override;\n\n  // Operations that we don't track.\n  bool onDoSaveBehind(const SkRect*) override;\n  void onDrawAnnotation(const SkRect&, const char[], SkData*) override;\n  void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;\n  void onDrawDrawable(SkDrawable*, const SkMatrix*) override;\n  void onDrawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4],\n                   SkBlendMode, const SkPaint&) override;\n  void onDrawPaint(const SkPaint&) override;\n  void onDrawBehind(const SkPaint&) override;\n  void onDrawPoints(PointMode, size_t, const SkPoint[],\n                    const SkPaint&) override;\n  void onDrawRegion(const SkRegion&, const SkPaint&) override;\n  void onDrawOval(const SkRect&, const SkPaint&) override;\n  void onDrawArc(const SkRect&, SkScalar, SkScalar, bool,\n                 const SkPaint&) override;\n  void onDrawRRect(const SkRRect&, const SkPaint&) override;\n  void onDrawImage2(const SkImage* image, SkScalar x, SkScalar y,\n                    const SkSamplingOptions&, const SkPaint* paint) override;\n  void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,\n                        const SkSamplingOptions&, const SkPaint*,\n                        SrcRectConstraint) override;\n  void onDrawImageLattice2(const SkImage*, const Lattice&, const SkRect&,\n                           SkFilterMode, const SkPaint*) override;\n  void onDrawVerticesObject(const SkVertices*, SkBlendMode,\n                            const SkPaint&) override;\n  void onDrawAtlas2(const SkImage*, const SkRSXform[], const SkRect[],\n                    const SkColor[], int, SkBlendMode, const SkSamplingOptions&,\n                    const SkRect*, const SkPaint*) override;\n  void onDrawEdgeAAQuad(const SkRect&, const SkPoint[4], QuadAAFlags,\n                        const SkColor4f&, SkBlendMode) override;\n  void onClipRegion(const SkRegion&, SkClipOp) override;\n\n private:\n  SkNWayCanvas internal_canvas_;\n\n  std::vector<DrawCall> draw_calls_;\n  int current_layer_;\n};\n\nextern bool operator==(const MockCanvas::SaveData& a,\n                       const MockCanvas::SaveData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::SaveData& data);\nextern bool operator==(const MockCanvas::SaveLayerData& a,\n                       const MockCanvas::SaveLayerData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::SaveLayerData& data);\nextern bool operator==(const MockCanvas::RestoreData& a,\n                       const MockCanvas::RestoreData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::RestoreData& data);\nextern bool operator==(const MockCanvas::ConcatMatrixData& a,\n                       const MockCanvas::ConcatMatrixData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::ConcatMatrixData& data);\nextern bool operator==(const MockCanvas::SetMatrixData& a,\n                       const MockCanvas::SetMatrixData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::SetMatrixData& data);\nextern bool operator==(const MockCanvas::DrawRectData& a,\n                       const MockCanvas::DrawRectData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawRectData& data);\nextern bool operator==(const MockCanvas::DrawRRectData& a,\n                       const MockCanvas::DrawRRectData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawRRectData& data);\nextern bool operator==(const MockCanvas::DrawPathData& a,\n                       const MockCanvas::DrawPathData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawPathData& data);\nextern bool operator==(const MockCanvas::DrawTextData& a,\n                       const MockCanvas::DrawTextData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawTextData& data);\nextern bool operator==(const MockCanvas::DrawImageData& a,\n                       const MockCanvas::DrawImageData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawImageData& data);\nextern bool operator==(const MockCanvas::DrawImageDataNoPaint& a,\n                       const MockCanvas::DrawImageDataNoPaint& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawImageDataNoPaint& data);\nextern bool operator==(const MockCanvas::DrawPictureData& a,\n                       const MockCanvas::DrawPictureData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawPictureData& data);\nextern bool operator==(const MockCanvas::DrawImageRectData& a,\n                       const MockCanvas::DrawImageRectData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawImageRectData& data);\nextern bool operator==(const MockCanvas::DrawPointsData& a,\n                       const MockCanvas::DrawPointsData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawPointsData& data);\nextern bool operator==(const MockCanvas::DrawShadowData& a,\n                       const MockCanvas::DrawShadowData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawShadowData& data);\nextern bool operator==(const MockCanvas::ClipRectData& a,\n                       const MockCanvas::ClipRectData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::ClipRectData& data);\nextern bool operator==(const MockCanvas::ClipRRectData& a,\n                       const MockCanvas::ClipRRectData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::ClipRRectData& data);\nextern bool operator==(const MockCanvas::ClipPathData& a,\n                       const MockCanvas::ClipPathData& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::ClipPathData& data);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawCallData& data);\nextern bool operator==(const MockCanvas::DrawCall& a,\n                       const MockCanvas::DrawCall& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawCall& draw);\nextern bool operator==(const MockCanvas::DrawPaint& a,\n                       const MockCanvas::DrawPaint& b);\nextern std::ostream& operator<<(std::ostream& os,\n                                const MockCanvas::DrawPaint& data);\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_MOCK_CANVAS_H_\n"
  },
  {
    "path": "clay/testing/mock_canvas_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/canvas_test.h\"\n#include \"clay/testing/mock_canvas.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing MockCanvasTest = CanvasTest;\n\nTEST_F(MockCanvasTest, DrawCalls) {\n  const SkRect rect = SkRect::MakeWH(5.0f, 5.0f);\n  const SkPaint paint = SkPaint(SkColors::kGreen);\n  const auto expected_draw_calls = std::vector{\n      MockCanvas::DrawCall{0, MockCanvas::DrawRectData{rect, paint}}};\n\n  mock_canvas().drawRect(rect, paint);\n  EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/post_task_sync.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/post_task_sync.h\"\n\n#include \"base/include/fml/synchronization/waitable_event.h\"\n\nnamespace clay {\nnamespace testing {\n\nvoid PostTaskSync(const fml::RefPtr<fml::TaskRunner>& task_runner,\n                  const std::function<void()>& function) {\n  fml::AutoResetWaitableEvent latch;\n  task_runner->PostTask([&] {\n    function();\n    latch.Signal();\n  });\n  latch.Wait();\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/post_task_sync.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_POST_TASK_SYNC_H_\n#define TESTING_POST_TASK_SYNC_H_\n\n#include \"base/include/fml/task_runner.h\"\n\nnamespace clay {\nnamespace testing {\n\nvoid PostTaskSync(const fml::RefPtr<fml::TaskRunner>& task_runner,\n                  const std::function<void()>& function);\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_POST_TASK_SYNC_H_\n"
  },
  {
    "path": "clay/testing/run_all_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <iostream>\n#include <optional>\n#include <string>\n\n#include \"build/build_config.h\"\n#include \"clay/fml/backtrace.h\"\n#include \"clay/fml/command_line.h\"\n#include \"clay/testing/debugger_detection.h\"\n#include \"clay/testing/test_timeout_listener.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#ifdef OS_IOS\n#include <asl.h>\n#endif  // OS_IOS\n\nstd::optional<fml::TimeDelta> GetTestTimeoutFromArgs(int argc, char** argv) {\n  const auto command_line = fml::CommandLineFromPlatformOrArgcArgv(argc, argv);\n\n  std::string timeout_seconds;\n  if (!command_line.GetOptionValue(\"timeout\", &timeout_seconds)) {\n    // No timeout specified. Default to 120s.\n    return fml::TimeDelta::FromSeconds(120u);\n  }\n\n  const auto seconds = std::stoi(timeout_seconds);\n\n  if (seconds < 1) {\n    return std::nullopt;\n  }\n\n  return fml::TimeDelta::FromSeconds(seconds);\n}\n\nint main(int argc, char** argv) {\n  fml::InstallCrashHandler();\n#ifdef OS_IOS\n  asl_log_descriptor(NULL, NULL, ASL_LEVEL_NOTICE, STDOUT_FILENO,\n                     ASL_LOG_DESCRIPTOR_WRITE);\n  asl_log_descriptor(NULL, NULL, ASL_LEVEL_ERR, STDERR_FILENO,\n                     ASL_LOG_DESCRIPTOR_WRITE);\n#endif  // OS_IOS\n\n  ::testing::InitGoogleTest(&argc, argv);\n\n  // Check if the user has specified a timeout.\n  const auto timeout = GetTestTimeoutFromArgs(argc, argv);\n  if (!timeout.has_value()) {\n    FML_LOG(INFO) << \"Timeouts disabled via a command line flag.\";\n    return RUN_ALL_TESTS();\n  }\n\n  // Check if the user is debugging the process.\n  if (clay::testing::GetDebuggerStatus() ==\n      clay::testing::DebuggerStatus::kAttached) {\n    FML_LOG(INFO) << \"Debugger is attached. Suspending test timeouts.\";\n    return RUN_ALL_TESTS();\n  }\n\n  auto timeout_listener =\n      new clay::testing::TestTimeoutListener(timeout.value());\n  auto& listeners = ::testing::UnitTest::GetInstance()->listeners();\n  listeners.Append(timeout_listener);\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  auto result = RUN_ALL_TESTS();\n  delete listeners.Release(timeout_listener);\n  return result;\n}\n"
  },
  {
    "path": "clay/testing/run_tests.py",
    "content": "#!/usr/bin/env python3\n#\n# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\n\"\"\"\nA top level harness to run all unit-tests in a specific engine build.\n\"\"\"\n\nimport argparse\nimport glob\nimport errno\nimport multiprocessing\nimport os\nimport re\nimport subprocess\nimport sys\nimport time\nimport csv\nimport xvfb\n\nSCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))\nBUILDROOT_DIR = os.path.abspath(\n    os.path.join(os.path.realpath(__file__), '..', '..', '..', '..')\n)\nOUT_DIR = os.path.join(BUILDROOT_DIR, 'out')\nGOLDEN_DIR = os.path.join(BUILDROOT_DIR, 'lynx', 'clay', 'testing', 'resources')\nFONTS_DIR = os.path.join(\n    BUILDROOT_DIR, 'lynx', 'clay', 'third_party', 'txt', 'third_party', 'fonts'\n)\nROBOTO_FONT_PATH = os.path.join(FONTS_DIR, 'Roboto-Regular.ttf')\n\nCOVERAGE_SCRIPT = os.path.join(\n    BUILDROOT_DIR, 'lynx', 'clay', 'build', 'generate_coverage.py'\n)\n\nFML_UNITTESTS_FILTER = '--gtest_filter=-*TimeSensitiveTest*'\n\ng_executables = []\n\n\ndef print_divider(char='='):\n  print('\\n')\n  for _ in range(4):\n    print(''.join([char for _ in range(80)]))\n  print('\\n')\n\ndef run_cmd(\n    cmd, forbidden_output=None, expect_failure=False, env=None, **kwargs\n):\n  if forbidden_output is None:\n    forbidden_output = []\n\n  command_string = ' '.join(cmd)\n\n  print_divider('>')\n  print('Running command \"%s\"' % command_string)\n\n  start_time = time.time()\n  stdout_pipe = sys.stdout if not forbidden_output else subprocess.PIPE\n  stderr_pipe = sys.stderr if not forbidden_output else subprocess.PIPE\n  process = subprocess.Popen(\n      cmd,\n      stdout=stdout_pipe,\n      stderr=stderr_pipe,\n      env=env,\n      universal_newlines=True,\n      **kwargs\n  )\n  stdout, stderr = process.communicate()\n  end_time = time.time()\n\n  if process.returncode != 0 and not expect_failure:\n    print_divider('!')\n\n    print(\n        'Failed Command:\\n\\n%s\\n\\nExit Code: %d\\n' %\n        (command_string, process.returncode)\n    )\n\n    if stdout:\n      print('STDOUT: \\n%s' % stdout)\n\n    if stderr:\n      print('STDERR: \\n%s' % stderr)\n\n    print_divider('!')\n\n    raise Exception(\n        'Command \"%s\" exited with code %d.' %\n        (command_string, process.returncode)\n    )\n\n  if stdout or stderr:\n    print(stdout)\n    print(stderr)\n\n  for forbidden_string in forbidden_output:\n    if (stdout and forbidden_string in stdout) or (stderr and\n                                                   forbidden_string in stderr):\n      raise Exception(\n          'command \"%s\" contained forbidden string %s' %\n          (command_string, forbidden_string)\n      )\n\n  print_divider('<')\n  print(\n      'Command run successfully in %.2f seconds: %s' %\n      (end_time - start_time, command_string)\n  )\n\n\ndef is_mac():\n  return sys.platform == 'darwin'\n\n\ndef is_linux():\n  return sys.platform.startswith('linux')\n\n\ndef is_windows():\n  return sys.platform.startswith(('cygwin', 'win'))\n\n\ndef executable_suffix():\n  return '.exe' if is_windows() else ''\n\n\ndef find_executable_path(path):\n  if os.path.exists(path):\n    return path\n\n  if is_windows():\n    exe_path = path + '.exe'\n    if os.path.exists(exe_path):\n      return exe_path\n\n    bat_path = path + '.bat'\n    if os.path.exists(bat_path):\n      return bat_path\n\n  raise Exception('Executable %s does not exist!' % path)\n\n\ndef build_engine_executable_command(\n    build_dir, executable_name, flags=None, coverage=False, gtest=False\n):\n  if flags is None:\n    flags = []\n\n  unstripped_exe = os.path.join(build_dir, 'exe.unstripped', executable_name)\n  # We cannot run the unstripped binaries directly when coverage is enabled.\n  if is_linux() and os.path.exists(unstripped_exe) and not coverage:\n    # Use unstripped executables in order to get better symbolized crash\n    # stack traces on Linux.\n    executable = unstripped_exe\n  else:\n    executable = find_executable_path(os.path.join(build_dir, executable_name))\n\n  if coverage:\n    global g_executables\n    g_executables.append(executable)\n    coverage_flags = [\n        '-t', executable, '-o',\n        os.path.join(build_dir, 'coverage', executable_name), '-f', 'html'\n    ]\n    updated_flags = ['--args=%s' % ' '.join(flags)]\n    test_command = [COVERAGE_SCRIPT] + coverage_flags + updated_flags\n  else:\n    test_command = [executable] + flags\n    if gtest:\n      gtest_parallel = os.path.join(\n          BUILDROOT_DIR, 'third_party', 'gtest-parallel', 'gtest-parallel'\n      )\n      test_command = ['python3', gtest_parallel] + test_command\n\n  return test_command\n\n\ndef run_engine_executable( # pylint: disable=too-many-arguments\n    build_dir,\n    executable_name,\n    executable_filter,\n    flags=None,\n    cwd=BUILDROOT_DIR,\n    forbidden_output=None,\n    expect_failure=False,\n    coverage=False,\n    extra_env=None,\n    gtest=False\n):\n  if executable_filter is not None and executable_name not in executable_filter:\n    print('Skipping %s due to filter.' % executable_name)\n    return\n\n  if flags is None:\n    flags = []\n  if forbidden_output is None:\n    forbidden_output = []\n  if extra_env is None:\n    extra_env = {}\n\n  unstripped_exe = os.path.join(build_dir, 'exe.unstripped', executable_name)\n  env = os.environ.copy()\n  if is_linux():\n    env['LD_LIBRARY_PATH'] = build_dir\n    env['VK_DRIVER_FILES'] = os.path.join(build_dir, 'vk_swiftshader_icd.json')\n    if os.path.exists(unstripped_exe):\n      try:\n        os.symlink(\n            os.path.join(build_dir, 'lib.unstripped', 'libvulkan.so.1'),\n            os.path.join(build_dir, 'exe.unstripped', 'libvulkan.so.1')\n        )\n      except OSError as err:\n        if err.errno == errno.EEXIST:\n          pass\n        else:\n          raise\n  elif is_mac():\n    env['DYLD_LIBRARY_PATH'] = build_dir\n  else:\n    env['PATH'] = build_dir + ':' + env['PATH']\n\n  print('Running %s in %s' % (executable_name, cwd))\n\n  test_command = build_engine_executable_command(\n      build_dir,\n      executable_name,\n      flags=flags,\n      coverage=coverage,\n      gtest=gtest,\n  )\n\n  env['FLUTTER_BUILD_DIRECTORY'] = build_dir\n  for key, value in extra_env.items():\n    env[key] = value\n\n  try:\n    run_cmd(\n        test_command,\n        cwd=cwd,\n        forbidden_output=forbidden_output,\n        expect_failure=expect_failure,\n        env=env\n    )\n  except:\n    # The LUCI environment may provide a variable containing a directory path\n    # for additional output files that will be uploaded to cloud storage.\n    # If the command generated a core dump, then run a script to analyze\n    # the dump and output a report that will be uploaded.\n    luci_test_outputs_path = os.environ.get('FLUTTER_TEST_OUTPUTS_DIR')\n    core_path = os.path.join(cwd, 'core')\n    if luci_test_outputs_path and os.path.exists(core_path) and os.path.exists(\n        unstripped_exe):\n      dump_path = os.path.join(\n          luci_test_outputs_path, '%s_%s.txt' % (executable_name, sys.platform)\n      )\n      print('Writing core dump analysis to %s' % dump_path)\n      subprocess.call([\n          os.path.join(\n              BUILDROOT_DIR, 'clay', 'testing', 'analyze_core_dump.sh'\n          ),\n          BUILDROOT_DIR,\n          unstripped_exe,\n          core_path,\n          dump_path,\n      ])\n      os.unlink(core_path)\n    raise\n\nshuffle_flags = [\n    '--gtest_repeat=2',\n    '--gtest_shuffle',\n]\n\n\ndef run_cc_tests(build_dir, executable_filter, coverage, capture_core_dump):\n  print('Running Engine Unit-tests.')\n\n  if capture_core_dump and is_linux():\n    import resource  # pylint: disable=import-outside-toplevel\n    resource.setrlimit(\n        resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)\n    )\n\n  repeat_flags = [\n      '--repeat=2',\n  ]\n\n  def make_test(name, flags=None, extra_env=None):\n    if flags is None:\n      flags = repeat_flags\n    if extra_env is None:\n      extra_env = {}\n    return (name, flags, extra_env)\n\n  unittests = [\n      make_test('common_cpp_core_unittests'),\n      make_test('common_cpp_unittests'),\n      make_test('memory_unittests'),\n      make_test('testing_unittests'),\n      make_test('clay_unittests'),\n      make_test('gfx_unittests'),\n  ]\n\n  if is_linux():\n    flow_flags = [\n        '--golden-dir=%s' % GOLDEN_DIR,\n        '--font-file=%s' % ROBOTO_FONT_PATH,\n    ]\n    icu_flags = [\n        '--icu-data-file-path=%s' % os.path.join(build_dir, 'icudtl.dat')\n    ]\n    unittests += [\n        make_test('flow_unittests', flags=repeat_flags + ['--'] + flow_flags),\n        # https://github.com/flutter/flutter/issues/36296\n        make_test('txt_unittests', flags=repeat_flags + ['--'] + icu_flags),\n    ]\n  else:\n    flow_flags = ['--gtest_filter=-PerformanceOverlayLayer*.Gold']\n    unittests += [\n        make_test('flow_unittests', flags=repeat_flags + flow_flags),\n    ]\n\n  for test, flags, extra_env in unittests:\n    run_engine_executable(\n        build_dir,\n        test,\n        executable_filter,\n        flags,\n        coverage=coverage,\n        extra_env=extra_env,\n        cwd = build_dir,\n        gtest=True\n    )\n\ndef ensure_debug_unopt_sky_packages():\n  variant_out_dir = os.path.join(OUT_DIR, 'host_debug_unopt')\n  message = []\n  message.append('gn --runtime-mode debug --unopt --no-lto')\n  message.append('ninja -C %s clay/sky/packages' % variant_out_dir)\n  final_message = \"%s doesn't exist. Please run the following commands: \\n%s\" % (\n      variant_out_dir, '\\n'.join(message)\n  )\n  assert os.path.exists(variant_out_dir), final_message\n\n\ndef ensure_ios_tests_are_built(ios_out_dir):\n  \"\"\"Builds the engine variant and the test dylib containing the XCTests\"\"\"\n  tmp_out_dir = os.path.join(OUT_DIR, ios_out_dir)\n  ios_test_lib = os.path.join(tmp_out_dir, 'libios_test_flutter.dylib')\n  message = []\n  message.append(\n      'gn --ios --is-debug --no-lto --simulator'\n  )\n  message.append('autoninja -C %s ios_test_flutter' % ios_out_dir)\n  final_message = \"%s or %s doesn't exist. Please run the following commands: \\n%s\" % (\n      ios_out_dir, ios_test_lib, '\\n'.join(message)\n  )\n  assert os.path.exists(tmp_out_dir\n                       ) and os.path.exists(ios_test_lib), final_message\n\n\ndef assert_expected_xcode_version():\n  \"\"\"Checks that the user has a version of Xcode installed\"\"\"\n  version_output = subprocess.check_output(['xcodebuild', '-version'])\n  match = re.match(r'Xcode (\\d+)', version_output)\n  message = 'Xcode must be installed to run the iOS embedding unit tests'\n  assert match, message\n\ndef make_coverage_summary(build_dir):\n  if len(g_executables) == 0:\n    return\n\n  print('Merge all coverage into summary.json')\n  coverage_flags = ['-t']\n  coverage_flags.extend(g_executables)\n  coverage_flags.extend(['-o', os.path.join(build_dir, 'coverage'), '-f', 'all', '-m'])\n  test_command = [COVERAGE_SCRIPT] + coverage_flags\n  run_cmd(test_command)\n\ndef main():\n  parser = argparse.ArgumentParser()\n  all_types = [ 'engine', 'benchmarks' ]\n\n  parser.add_argument(\n      '--variant',\n      dest='variant',\n      action='store',\n      default='host_debug_unopt',\n      help='The engine build variant to run the tests for.'\n  )\n  parser.add_argument(\n      '--type',\n      type=str,\n      default='all',\n      help='A list of test types, default is \"all\" (equivalent to \"%s\")' %\n      (','.join(all_types))\n  )\n  parser.add_argument(\n      '--engine-filter',\n      type=str,\n      default='',\n      help='A list of engine test executables to run.'\n  )\n  parser.add_argument(\n      '--coverage',\n      action='store_true',\n      default=None,\n      help='Generate coverage reports for each unit test framework run.'\n  )\n  parser.add_argument(\n      '--engine-capture-core-dump',\n      dest='engine_capture_core_dump',\n      action='store_true',\n      default=False,\n      help='Capture core dumps from crashes of engine tests.'\n  )\n  parser.add_argument(\n      '--use-sanitizer-suppressions',\n      dest='sanitizer_suppressions',\n      action='store_true',\n      default=False,\n      help='Provide the sanitizer suppressions lists to the via environment to the tests.'\n  )\n\n  args = parser.parse_args()\n\n  if args.type == 'all':\n    types = all_types\n  else:\n    types = args.type.split(',')\n\n  build_dir = os.path.join(OUT_DIR, args.variant)\n  assert os.path.exists(\n      build_dir\n  ), 'Build variant directory %s does not exist!' % build_dir\n\n  if args.sanitizer_suppressions:\n    assert is_linux() or is_mac(\n    ), 'The sanitizer suppressions flag is only supported on Linux and Mac.'\n    file_dir = os.path.dirname(os.path.abspath(__file__))\n    command = [\n        'env', '-i', 'bash', '-c',\n        'source {}/sanitizer_suppressions.sh >/dev/null && env'\n        .format(file_dir)\n    ]\n    process = subprocess.Popen(command, stdout=subprocess.PIPE)\n    for line in process.stdout:\n      key, _, value = line.decode('utf8').strip().partition('=')\n      os.environ[key] = value\n    process.communicate()  # Avoid pipe deadlock while waiting for termination.\n\n  engine_filter = args.engine_filter.split(',') if args.engine_filter else None\n  if 'engine' in types:\n    run_cc_tests(\n        build_dir, engine_filter, args.coverage, args.engine_capture_core_dump\n    )\n\n  if args.coverage:\n    make_coverage_summary(build_dir)\n\n\nif __name__ == '__main__':\n  sys.exit(main())\n"
  },
  {
    "path": "clay/testing/run_tests.sh",
    "content": "#!/bin/bash\n\nset -o pipefail -e;\n\nBUILD_VARIANT=\"${1:-host_debug_unopt}\"\nCURRENT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\n\npython \"${CURRENT_DIR}/run_tests.py\" --variant=\"${BUILD_VARIANT}\" --type=engine,dart,benchmarks\n"
  },
  {
    "path": "clay/testing/sanitizer_suppressions.sh",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\nTESTING_DIRECTORY=$(cd $(dirname \"${BASH_SOURCE[0]}\"); pwd -P)\nENGINE_BUILDROOT=$(cd $TESTING_DIRECTORY/../..; pwd -P)\n\ncase \"$(uname -s)\" in\n  Linux)\n    BUILDTOOLS_DIRECTORY=\"${ENGINE_BUILDROOT}/buildtools/linux-x64\"\n    ;;\n  Darwin)\n    BUILDTOOLS_DIRECTORY=\"${ENGINE_BUILDROOT}/buildtools/mac-x64\"\n    ;;\nesac\n\nTSAN_SUPPRESSIONS_FILE=\"${TESTING_DIRECTORY}/tsan_suppressions.txt\"\nexport TSAN_OPTIONS=\"suppressions=${TSAN_SUPPRESSIONS_FILE}\"\necho \"Using Thread Sanitizer suppressions in ${TSAN_SUPPRESSIONS_FILE}\"\n\nLSAN_SUPPRESSIONS_FILE=\"${TESTING_DIRECTORY}/lsan_suppressions.txt\"\nexport LSAN_OPTIONS=\"suppressions=${LSAN_SUPPRESSIONS_FILE}\"\necho \"Using Leak Sanitizer suppressions in ${LSAN_SUPPRESSIONS_FILE}\"\n\nUBSAN_SUPPRESSIONS_FILE=\"${TESTING_DIRECTORY}/ubsan_suppressions.txt\"\nexport UBSAN_OPTIONS=\"suppressions=${UBSAN_SUPPRESSIONS_FILE}\"\necho \"Using Undefined Behavior suppressions in ${UBSAN_SUPPRESSIONS_FILE}\"\n\nexport ASAN_OPTIONS=\"symbolize=1:detect_leaks=1:intercept_tls_get_addr=0\"\nexport ASAN_SYMBOLIZER_PATH=\"${BUILDTOOLS_DIRECTORY}/clang/bin/llvm-symbolizer\"\n"
  },
  {
    "path": "clay/testing/test_gl_surface.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/test_gl_surface.h\"\n\n#include <EGL/egl.h>\n#include <EGL/eglext.h>\n#include <EGL/eglplatform.h>\n#include <GLES2/gl2.h>\n\n#include <sstream>\n#include <string>\n\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"third_party/skia/include/core/SkColorSpace.h\"\n// #include \"third_party/skia/include/core/SkColorType.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n#include \"third_party/skia/include/gpu/gl/GrGLAssembleInterface.h\"\n\nnamespace clay {\nnamespace testing {\n\nstatic std::string GetEGLError() {\n  std::stringstream stream;\n\n  auto error = ::eglGetError();\n\n  stream << \"EGL Result: '\";\n\n  switch (error) {\n    case EGL_SUCCESS:\n      stream << \"EGL_SUCCESS\";\n      break;\n    case EGL_NOT_INITIALIZED:\n      stream << \"EGL_NOT_INITIALIZED\";\n      break;\n    case EGL_BAD_ACCESS:\n      stream << \"EGL_BAD_ACCESS\";\n      break;\n    case EGL_BAD_ALLOC:\n      stream << \"EGL_BAD_ALLOC\";\n      break;\n    case EGL_BAD_ATTRIBUTE:\n      stream << \"EGL_BAD_ATTRIBUTE\";\n      break;\n    case EGL_BAD_CONTEXT:\n      stream << \"EGL_BAD_CONTEXT\";\n      break;\n    case EGL_BAD_CONFIG:\n      stream << \"EGL_BAD_CONFIG\";\n      break;\n    case EGL_BAD_CURRENT_SURFACE:\n      stream << \"EGL_BAD_CURRENT_SURFACE\";\n      break;\n    case EGL_BAD_DISPLAY:\n      stream << \"EGL_BAD_DISPLAY\";\n      break;\n    case EGL_BAD_SURFACE:\n      stream << \"EGL_BAD_SURFACE\";\n      break;\n    case EGL_BAD_MATCH:\n      stream << \"EGL_BAD_MATCH\";\n      break;\n    case EGL_BAD_PARAMETER:\n      stream << \"EGL_BAD_PARAMETER\";\n      break;\n    case EGL_BAD_NATIVE_PIXMAP:\n      stream << \"EGL_BAD_NATIVE_PIXMAP\";\n      break;\n    case EGL_BAD_NATIVE_WINDOW:\n      stream << \"EGL_BAD_NATIVE_WINDOW\";\n      break;\n    case EGL_CONTEXT_LOST:\n      stream << \"EGL_CONTEXT_LOST\";\n      break;\n    default:\n      stream << \"Unknown\";\n  }\n\n  stream << \"' (0x\" << std::hex << error << std::dec << \").\";\n  return stream.str();\n}\nTestGLSurface::TestGLSurface(SkISize surface_size)\n    : surface_size_(surface_size) {\n  display_ = ::eglGetDisplay(EGL_DEFAULT_DISPLAY);\n  FML_CHECK(display_ != EGL_NO_DISPLAY);\n\n  auto result = ::eglInitialize(display_, nullptr, nullptr);\n  FML_CHECK(result == EGL_TRUE) << GetEGLError();\n\n  EGLConfig config = {0};\n\n  EGLint num_config = 0;\n  const EGLint attribute_list[] = {EGL_RED_SIZE,\n                                   8,\n                                   EGL_GREEN_SIZE,\n                                   8,\n                                   EGL_BLUE_SIZE,\n                                   8,\n                                   EGL_ALPHA_SIZE,\n                                   8,\n                                   EGL_SURFACE_TYPE,\n                                   EGL_PBUFFER_BIT,\n                                   EGL_CONFORMANT,\n                                   EGL_OPENGL_ES2_BIT,\n                                   EGL_RENDERABLE_TYPE,\n                                   EGL_OPENGL_ES2_BIT,\n                                   EGL_NONE};\n\n  result = ::eglChooseConfig(display_, attribute_list, &config, 1, &num_config);\n  FML_CHECK(result == EGL_TRUE) << GetEGLError();\n  FML_CHECK(num_config == 1) << GetEGLError();\n\n  {\n    const EGLint onscreen_surface_attributes[] = {\n        EGL_WIDTH,  surface_size_.width(),   //\n        EGL_HEIGHT, surface_size_.height(),  //\n        EGL_NONE,\n    };\n\n    onscreen_surface_ = ::eglCreatePbufferSurface(\n        display_,                    // display connection\n        config,                      // config\n        onscreen_surface_attributes  // surface attributes\n    );\n    FML_CHECK(onscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();\n  }\n\n  {\n    const EGLint offscreen_surface_attributes[] = {\n        EGL_WIDTH,  1,  //\n        EGL_HEIGHT, 1,  //\n        EGL_NONE,\n    };\n    offscreen_surface_ = ::eglCreatePbufferSurface(\n        display_,                     // display connection\n        config,                       // config\n        offscreen_surface_attributes  // surface attributes\n    );\n    FML_CHECK(offscreen_surface_ != EGL_NO_SURFACE) << GetEGLError();\n  }\n\n  {\n    const EGLint context_attributes[] = {\n        EGL_CONTEXT_CLIENT_VERSION,  //\n        2,                           //\n        EGL_NONE                     //\n    };\n\n    onscreen_context_ =\n        ::eglCreateContext(display_,           // display connection\n                           config,             // config\n                           EGL_NO_CONTEXT,     // sharegroup\n                           context_attributes  // context attributes\n        );\n    FML_CHECK(onscreen_context_ != EGL_NO_CONTEXT) << GetEGLError();\n\n    offscreen_context_ =\n        ::eglCreateContext(display_,           // display connection\n                           config,             // config\n                           onscreen_context_,  // sharegroup\n                           context_attributes  // context attributes\n        );\n    FML_CHECK(offscreen_context_ != EGL_NO_CONTEXT) << GetEGLError();\n  }\n}\n\nTestGLSurface::~TestGLSurface() {\n  context_ = nullptr;\n\n  auto result = ::eglDestroyContext(display_, onscreen_context_);\n  FML_CHECK(result == EGL_TRUE) << GetEGLError();\n\n  result = ::eglDestroyContext(display_, offscreen_context_);\n  FML_CHECK(result == EGL_TRUE) << GetEGLError();\n\n  result = ::eglDestroySurface(display_, onscreen_surface_);\n  FML_CHECK(result == EGL_TRUE) << GetEGLError();\n\n  result = ::eglDestroySurface(display_, offscreen_surface_);\n  FML_CHECK(result == EGL_TRUE) << GetEGLError();\n\n  result = ::eglTerminate(display_);\n  FML_CHECK(result == EGL_TRUE);\n}\n\nconst SkISize& TestGLSurface::GetSurfaceSize() const { return surface_size_; }\n\nbool TestGLSurface::MakeCurrent() {\n  auto result = ::eglMakeCurrent(display_, onscreen_surface_, onscreen_surface_,\n                                 onscreen_context_);\n\n  if (result == EGL_FALSE) {\n    FML_LOG(ERROR) << \"Could not make the context current. \" << GetEGLError();\n  }\n\n  return result == EGL_TRUE;\n}\n\nbool TestGLSurface::ClearCurrent() {\n  auto result = ::eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE,\n                                 EGL_NO_CONTEXT);\n\n  if (result == EGL_FALSE) {\n    FML_LOG(ERROR) << \"Could not clear the current context. \" << GetEGLError();\n  }\n\n  return result == EGL_TRUE;\n}\n\nbool TestGLSurface::Present() {\n  auto result = ::eglSwapBuffers(display_, onscreen_surface_);\n\n  if (result == EGL_FALSE) {\n    FML_LOG(ERROR) << \"Could not swap buffers. \" << GetEGLError();\n  }\n\n  return result == EGL_TRUE;\n}\n\nuint32_t TestGLSurface::GetFramebuffer(uint32_t width, uint32_t height) const {\n  return GetWindowFBOId();\n}\n\nbool TestGLSurface::MakeResourceCurrent() {\n  auto result = ::eglMakeCurrent(display_, offscreen_surface_,\n                                 offscreen_surface_, offscreen_context_);\n\n  if (result == EGL_FALSE) {\n    FML_LOG(ERROR) << \"Could not make the resource context current. \"\n                   << GetEGLError();\n  }\n\n  return result == EGL_TRUE;\n}\n\nvoid* TestGLSurface::GetProcAddress(const char* name) const {\n  if (name == nullptr) {\n    return nullptr;\n  }\n  auto symbol = ::eglGetProcAddress(name);\n  if (symbol == NULL) {\n    FML_LOG(ERROR) << \"Could not fetch symbol for name: \" << name;\n  }\n  return reinterpret_cast<void*>(symbol);\n}\n\nsk_sp<GrDirectContext> TestGLSurface::GetGrContext() {\n  if (context_) {\n    return context_;\n  }\n\n  return CreateGrContext();\n}\n\nsk_sp<GrDirectContext> TestGLSurface::CreateGrContext() {\n  if (!MakeCurrent()) {\n    return nullptr;\n  }\n\n  auto get_string =\n      reinterpret_cast<PFNGLGETSTRINGPROC>(GetProcAddress(\"glGetString\"));\n\n  if (!get_string) {\n    return nullptr;\n  }\n\n  auto c_version = reinterpret_cast<const char*>(get_string(GL_VERSION));\n\n  if (c_version == NULL) {\n    return nullptr;\n  }\n\n  GrGLGetProc get_proc = [](void* context, const char name[]) -> GrGLFuncPtr {\n    return reinterpret_cast<GrGLFuncPtr>(\n        reinterpret_cast<TestGLSurface*>(context)->GetProcAddress(name));\n  };\n\n  std::string version(c_version);\n  auto interface = version.find(\"OpenGL ES\") == std::string::npos\n                       ? GrGLMakeAssembledGLInterface(this, get_proc)\n                       : GrGLMakeAssembledGLESInterface(this, get_proc);\n\n  if (!interface) {\n    return nullptr;\n  }\n\n  context_ = GrDirectContext::MakeGL(interface);\n  return context_;\n}\n\nsk_sp<SkSurface> TestGLSurface::GetOnscreenSurface() {\n  FML_CHECK(::eglGetCurrentContext() != EGL_NO_CONTEXT);\n\n  GrGLFramebufferInfo framebuffer_info = {};\n  const uint32_t width = surface_size_.width();\n  const uint32_t height = surface_size_.height();\n  framebuffer_info.fFBOID = GetFramebuffer(width, height);\n#if OS_MACOSX\n  framebuffer_info.fFormat = 0x8058;  // GL_RGBA8\n#else\n  framebuffer_info.fFormat = 0x93A1;  // GL_BGRA8;\n#endif\n\n  GrBackendRenderTarget backend_render_target(\n      width,            // width\n      height,           // height\n      1,                // sample count\n      8,                // stencil bits\n      framebuffer_info  // framebuffer info\n  );\n\n  SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry);\n\n  auto surface = SkSurface::MakeFromBackendRenderTarget(\n      GetGrContext().get(),         // context\n      backend_render_target,        // backend render target\n      kBottomLeft_GrSurfaceOrigin,  // surface origin\n      kN32_SkColorType,             // color type\n      SkColorSpace::MakeSRGB(),     // color space\n      &surface_properties,          // surface properties\n      nullptr,                      // release proc\n      nullptr                       // release context\n  );\n\n  if (!surface) {\n    FML_LOG(ERROR) << \"Could not wrap the surface while attempting to \"\n                      \"snapshot the GL surface.\";\n    return nullptr;\n  }\n\n  return surface;\n}\n\nsk_sp<SkImage> TestGLSurface::GetRasterSurfaceSnapshot() {\n  auto surface = GetOnscreenSurface();\n\n  if (!surface) {\n    FML_LOG(ERROR) << \"Aborting snapshot because of on-screen surface \"\n                      \"acquisition failure.\";\n    return nullptr;\n  }\n\n  auto device_snapshot = surface->makeImageSnapshot();\n\n  if (!device_snapshot) {\n    FML_LOG(ERROR) << \"Could not create the device snapshot while attempting \"\n                      \"to snapshot the GL surface.\";\n    return nullptr;\n  }\n\n  auto host_snapshot = device_snapshot->makeRasterImage();\n\n  if (!host_snapshot) {\n    FML_LOG(ERROR) << \"Could not create the host snapshot while attempting to \"\n                      \"snapshot the GL surface.\";\n    return nullptr;\n  }\n\n  return host_snapshot;\n}\n\nuint32_t TestGLSurface::GetWindowFBOId() const { return 0u; }\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/test_gl_surface.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_TEST_GL_SURFACE_H_\n#define TESTING_TEST_GL_SURFACE_H_\n\n#include <cstdint>\n\n#include \"base/include/fml/macros.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n\nnamespace clay {\nnamespace testing {\n\nclass TestGLSurface {\n public:\n  explicit TestGLSurface(SkISize surface_size);\n\n  ~TestGLSurface();\n\n  const SkISize& GetSurfaceSize() const;\n\n  bool MakeCurrent();\n\n  bool ClearCurrent();\n\n  bool Present();\n\n  uint32_t GetFramebuffer(uint32_t width, uint32_t height) const;\n\n  bool MakeResourceCurrent();\n\n  void* GetProcAddress(const char* name) const;\n\n  sk_sp<SkSurface> GetOnscreenSurface();\n\n  sk_sp<GrDirectContext> GetGrContext();\n\n  sk_sp<GrDirectContext> CreateGrContext();\n\n  sk_sp<SkImage> GetRasterSurfaceSnapshot();\n\n  uint32_t GetWindowFBOId() const;\n\n private:\n  // Importing the EGL.h pulls in platform headers which are problematic\n  // (especially X11 which #defineds types like Bool). Any TUs importing\n  // this header then become susceptible to failures because of platform\n  // specific craziness. Don't expose EGL internals via this header.\n  using EGLDisplay = void*;\n  using EGLContext = void*;\n  using EGLSurface = void*;\n\n  const SkISize surface_size_;\n  EGLDisplay display_;\n  EGLContext onscreen_context_;\n  EGLContext offscreen_context_;\n  EGLSurface onscreen_surface_;\n  EGLSurface offscreen_surface_;\n  sk_sp<GrDirectContext> context_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TestGLSurface);\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_TEST_GL_SURFACE_H_\n"
  },
  {
    "path": "clay/testing/test_metal_context.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_TEST_METAL_CONTEXT_H_\n#define TESTING_TEST_METAL_CONTEXT_H_\n\n#include <map>\n#include <mutex>\n\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n#include \"third_party/skia/include/gpu/mtl/GrMtlTypes.h\"\n\nnamespace clay {\n\nclass TestMetalContext {\n public:\n  struct TextureInfo {\n    int64_t texture_id;\n    void* texture;\n  };\n\n  TestMetalContext();\n\n  ~TestMetalContext();\n\n  void* GetMetalDevice() const;\n\n  void* GetMetalCommandQueue() const;\n\n  sk_sp<GrDirectContext> GetSkiaContext() const;\n\n  /// Returns texture_id = -1 when texture creation fails.\n  TextureInfo CreateMetalTexture(const SkISize& size);\n\n  bool Present(int64_t texture_id);\n\n  TextureInfo GetTextureInfo(int64_t texture_id);\n\n private:\n  void* device_;\n  void* command_queue_;\n  sk_sp<GrDirectContext> skia_context_;\n  std::mutex textures_mutex;\n  int64_t texture_id_ctr_ = 1;                 // guarded by textures_mutex\n  std::map<int64_t, sk_cfp<void*>> textures_;  // guarded by textures_mutex\n};\n\n}  // namespace clay\n\n#endif  // TESTING_TEST_METAL_CONTEXT_H_\n"
  },
  {
    "path": "clay/testing/test_metal_context.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/test_metal_context.h\"\n\n#include <Metal/Metal.h>\n#include <iostream>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/platform/darwin/scoped_nsobject.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n\nnamespace clay {\n\nTestMetalContext::TestMetalContext() {\n  auto device = fml::scoped_nsprotocol{MTLCreateSystemDefaultDevice()};\n  if (!device) {\n    FML_LOG(ERROR) << \"Could not acquire Metal device.\";\n    return;\n  }\n\n  auto command_queue = fml::scoped_nsobject{[device.get() newCommandQueue]};\n  if (!command_queue) {\n    FML_LOG(ERROR) << \"Could not create the default command queue.\";\n    return;\n  }\n\n  [command_queue.get() setLabel:@\"Flutter Test Queue\"];\n\n  // Skia expect arguments to `MakeMetal` transfer ownership of the reference in for release later\n  // when the GrDirectContext is collected.\n  skia_context_ = GrDirectContext::MakeMetal((__bridge_retained void*)device.get(),\n                                             (__bridge_retained void*)command_queue.get());\n  if (!skia_context_) {\n    FML_LOG(ERROR) << \"Could not create the GrDirectContext from the Metal Device \"\n                      \"and command queue.\";\n  }\n\n  device_ = (__bridge_retained void*)device.get();\n  command_queue_ = (__bridge_retained void*)command_queue.get();\n}\n\nTestMetalContext::~TestMetalContext() {\n  std::scoped_lock lock(textures_mutex);\n  textures_.clear();\n  if (device_) {\n    (void)(__bridge_transfer id)device_;\n  }\n  if (command_queue_) {\n    (void)(__bridge_transfer id)command_queue_;\n  }\n}\n\nvoid* TestMetalContext::GetMetalDevice() const { return device_; }\n\nvoid* TestMetalContext::GetMetalCommandQueue() const { return command_queue_; }\n\nsk_sp<GrDirectContext> TestMetalContext::GetSkiaContext() const { return skia_context_; }\n\nTestMetalContext::TextureInfo TestMetalContext::CreateMetalTexture(const SkISize& size) {\n  std::scoped_lock lock(textures_mutex);\n  auto texture_descriptor = fml::scoped_nsobject{[MTLTextureDescriptor\n      texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm\n                                   width:size.width()\n                                  height:size.height()\n                               mipmapped:NO]};\n\n  // The most pessimistic option and disables all optimizations but allows tests\n  // the most flexible access to the surface. They may read and write to the\n  // surface from shaders or use as a pixel view.\n  texture_descriptor.get().usage = MTLTextureUsageUnknown;\n\n  if (!texture_descriptor) {\n    FML_CHECK(false) << \"Invalid texture descriptor.\";\n    return {.texture_id = -1, .texture = nullptr};\n  }\n\n  id<MTLDevice> device = (__bridge id<MTLDevice>)GetMetalDevice();\n  sk_cfp<void*> texture =\n      sk_cfp{(__bridge_retained void*)([device newTextureWithDescriptor:texture_descriptor.get()])};\n\n  if (!texture) {\n    FML_CHECK(false) << \"Could not create texture from texture descriptor.\";\n    return {.texture_id = -1, .texture = nullptr};\n  }\n\n  const int64_t texture_id = texture_id_ctr_++;\n  textures_[texture_id] = texture;\n\n  return {\n      .texture_id = texture_id,\n      .texture = texture.get(),\n  };\n}\n\n// Don't remove the texture from the map here.\nbool TestMetalContext::Present(int64_t texture_id) {\n  std::scoped_lock lock(textures_mutex);\n  if (textures_.find(texture_id) == textures_.end()) {\n    return false;\n  } else {\n    return true;\n  }\n}\n\nTestMetalContext::TextureInfo TestMetalContext::GetTextureInfo(int64_t texture_id) {\n  std::scoped_lock lock(textures_mutex);\n  if (textures_.find(texture_id) == textures_.end()) {\n    FML_CHECK(false) << \"Invalid texture id: \" << texture_id;\n    return {.texture_id = -1, .texture = nullptr};\n  } else {\n    return {\n        .texture_id = texture_id,\n        .texture = textures_[texture_id].get(),\n    };\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/test_metal_surface.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/test_metal_surface.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/testing/test_metal_surface_impl.h\"\n\nnamespace clay {\n\nbool TestMetalSurface::PlatformSupportsMetal() { return true; }\n\nstd::unique_ptr<TestMetalSurface> TestMetalSurface::Create(\n    const TestMetalContext& test_metal_context, SkISize surface_size) {\n  return std::make_unique<TestMetalSurfaceImpl>(test_metal_context,\n                                                surface_size);\n}\n\nstd::unique_ptr<TestMetalSurface> TestMetalSurface::Create(\n    const TestMetalContext& test_metal_context, int64_t texture_id,\n    SkISize surface_size) {\n  return std::make_unique<TestMetalSurfaceImpl>(test_metal_context, texture_id,\n                                                surface_size);\n}\n\nTestMetalSurface::TestMetalSurface() = default;\n\nTestMetalSurface::~TestMetalSurface() = default;\n\nbool TestMetalSurface::IsValid() const {\n  return impl_ ? impl_->IsValid() : false;\n}\n\nsk_sp<GrDirectContext> TestMetalSurface::GetGrContext() const {\n  return impl_ ? impl_->GetGrContext() : nullptr;\n}\n\nsk_sp<SkSurface> TestMetalSurface::GetSurface() const {\n  return impl_ ? impl_->GetSurface() : nullptr;\n}\n\nsk_sp<SkImage> TestMetalSurface::GetRasterSurfaceSnapshot() {\n  return impl_ ? impl_->GetRasterSurfaceSnapshot() : nullptr;\n}\n\nTestMetalContext::TextureInfo TestMetalSurface::GetTextureInfo() {\n  return impl_ ? impl_->GetTextureInfo() : TestMetalContext::TextureInfo();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/test_metal_surface.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_TEST_METAL_SURFACE_H_\n#define TESTING_TEST_METAL_SURFACE_H_\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/testing/test_metal_context.h\"\n#include \"third_party/skia/include/core/SkSize.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// @brief      Creates a MTLTexture backed SkSurface and context that can be\n///             used to render to in unit-tests.\n///\nclass TestMetalSurface {\n public:\n  static bool PlatformSupportsMetal();\n\n  static std::unique_ptr<TestMetalSurface> Create(\n      const TestMetalContext& test_metal_context,\n      SkISize surface_size = SkISize::MakeEmpty());\n\n  static std::unique_ptr<TestMetalSurface> Create(\n      const TestMetalContext& test_metal_context, int64_t texture_id,\n      SkISize surface_size = SkISize::MakeEmpty());\n\n  virtual ~TestMetalSurface();\n\n  virtual bool IsValid() const;\n\n  virtual sk_sp<GrDirectContext> GetGrContext() const;\n\n  virtual sk_sp<SkSurface> GetSurface() const;\n\n  virtual sk_sp<SkImage> GetRasterSurfaceSnapshot();\n\n  virtual TestMetalContext::TextureInfo GetTextureInfo();\n\n protected:\n  TestMetalSurface();\n\n private:\n  std::unique_ptr<TestMetalSurface> impl_;\n\n  explicit TestMetalSurface(std::unique_ptr<TestMetalSurface> impl);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TestMetalSurface);\n};\n\n}  // namespace clay\n\n#endif  // TESTING_TEST_METAL_SURFACE_H_\n"
  },
  {
    "path": "clay/testing/test_metal_surface_impl.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_TEST_METAL_SURFACE_IMPL_H_\n#define TESTING_TEST_METAL_SURFACE_IMPL_H_\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/testing/test_metal_context.h\"\n#include \"clay/testing/test_metal_surface.h\"\n\nnamespace clay {\n\nclass TestMetalSurfaceImpl : public TestMetalSurface {\n public:\n  TestMetalSurfaceImpl(const TestMetalContext& test_metal_context,\n                       const SkISize& surface_size);\n\n  TestMetalSurfaceImpl(const TestMetalContext& test_metal_context,\n                       int64_t texture_id, const SkISize& surface_size);\n\n  // |TestMetalSurface|\n  ~TestMetalSurfaceImpl() override;\n\n private:\n  void Init(const TestMetalContext::TextureInfo& texture_info,\n            const SkISize& surface_size);\n\n  const TestMetalContext& test_metal_context_;\n  bool is_valid_ = false;\n  sk_sp<SkSurface> surface_;\n  TestMetalContext::TextureInfo texture_info_;\n\n  // |TestMetalSurface|\n  bool IsValid() const override;\n\n  // |TestMetalSurface|\n  sk_sp<GrDirectContext> GetGrContext() const override;\n\n  // |TestMetalSurface|\n  sk_sp<SkSurface> GetSurface() const override;\n\n  // |TestMetalSurface|\n  sk_sp<SkImage> GetRasterSurfaceSnapshot() override;\n\n  // |TestMetalSurface|\n  TestMetalContext::TextureInfo GetTextureInfo() override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TestMetalSurfaceImpl);\n};\n\n}  // namespace clay\n\n#endif  // TESTING_TEST_METAL_SURFACE_IMPL_H_\n"
  },
  {
    "path": "clay/testing/test_metal_surface_impl.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/test_metal_surface_impl.h\"\n\n#include <Metal/Metal.h>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/platform/darwin/scoped_nsobject.h\"\n#include \"clay/testing/test_metal_context.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkColorSpace.h\"\n#include \"third_party/skia/include/core/SkImage.h\"\n#include \"third_party/skia/include/core/SkRefCnt.h\"\n#include \"third_party/skia/include/core/SkSize.h\"\n#include \"third_party/skia/include/core/SkSurface.h\"\n\nnamespace clay {\n\nvoid TestMetalSurfaceImpl::Init(const TestMetalContext::TextureInfo& texture_info,\n                                const SkISize& surface_size) {\n  id<MTLTexture> texture = (__bridge id<MTLTexture>)texture_info.texture;\n\n  GrMtlTextureInfo skia_texture_info;\n  skia_texture_info.fTexture.reset((__bridge GrMTLHandle)texture);\n  GrBackendTexture backend_texture(surface_size.width(), surface_size.height(), GrMipmapped::kNo,\n                                   skia_texture_info);\n\n  sk_sp<SkSurface> surface = SkSurface::MakeFromBackendTexture(\n      test_metal_context_.GetSkiaContext().get(), backend_texture, kTopLeft_GrSurfaceOrigin, 1,\n      kBGRA_8888_SkColorType, nullptr, nullptr);\n\n  if (!surface) {\n    FML_LOG(ERROR) << \"Could not create Skia surface from a Metal texture.\";\n    return;\n  }\n\n  surface_ = std::move(surface);\n  texture_info_ = texture_info;\n  is_valid_ = true;\n}\n\nTestMetalSurfaceImpl::TestMetalSurfaceImpl(const TestMetalContext& test_metal_context,\n                                           int64_t texture_id, const SkISize& surface_size)\n    : test_metal_context_(test_metal_context) {\n  TestMetalContext::TextureInfo texture_info =\n      const_cast<TestMetalContext&>(test_metal_context_).GetTextureInfo(texture_id);\n  Init(texture_info, surface_size);\n}\n\nTestMetalSurfaceImpl::TestMetalSurfaceImpl(const TestMetalContext& test_metal_context,\n                                           const SkISize& surface_size)\n    : test_metal_context_(test_metal_context) {\n  if (surface_size.isEmpty()) {\n    FML_LOG(ERROR) << \"Size of test Metal surface was empty.\";\n    return;\n  }\n  TestMetalContext::TextureInfo texture_info =\n      const_cast<TestMetalContext&>(test_metal_context_).CreateMetalTexture(surface_size);\n  Init(texture_info, surface_size);\n}\n\nsk_sp<SkImage> TestMetalSurfaceImpl::GetRasterSurfaceSnapshot() {\n  if (!IsValid()) {\n    return nullptr;\n  }\n\n  if (!surface_) {\n    FML_LOG(ERROR) << \"Aborting snapshot because of on-screen surface \"\n                      \"acquisition failure.\";\n    return nullptr;\n  }\n\n  auto device_snapshot = surface_->makeImageSnapshot();\n\n  if (!device_snapshot) {\n    FML_LOG(ERROR) << \"Could not create the device snapshot while attempting \"\n                      \"to snapshot the Metal surface.\";\n    return nullptr;\n  }\n\n  auto host_snapshot = device_snapshot->makeRasterImage();\n\n  if (!host_snapshot) {\n    FML_LOG(ERROR) << \"Could not create the host snapshot while attempting to \"\n                      \"snapshot the Metal surface.\";\n    return nullptr;\n  }\n\n  return host_snapshot;\n}\n\n// |TestMetalSurface|\nTestMetalSurfaceImpl::~TestMetalSurfaceImpl() = default;\n\n// |TestMetalSurface|\nbool TestMetalSurfaceImpl::IsValid() const { return is_valid_; }\n\n// |TestMetalSurface|\nsk_sp<GrDirectContext> TestMetalSurfaceImpl::GetGrContext() const {\n  return IsValid() ? test_metal_context_.GetSkiaContext() : nullptr;\n}\n\n// |TestMetalSurface|\nsk_sp<SkSurface> TestMetalSurfaceImpl::GetSurface() const { return IsValid() ? surface_ : nullptr; }\n\n// |TestMetalSurface|\nTestMetalContext::TextureInfo TestMetalSurfaceImpl::GetTextureInfo() {\n  return IsValid() ? texture_info_ : TestMetalContext::TextureInfo();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/test_metal_surface_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/test_metal_context.h\"\n#include \"clay/testing/test_metal_surface.h\"\n#include \"clay/testing/testing.h\"\n\nnamespace clay {\nnamespace testing {\n\n#ifdef SHELL_ENABLE_METAL\n\nTEST(TestMetalSurface, EmptySurfaceIsInvalid) {\n  if (!TestMetalSurface::PlatformSupportsMetal()) {\n    GTEST_SKIP();\n  }\n\n  TestMetalContext metal_context = TestMetalContext();\n  auto surface = TestMetalSurface::Create(metal_context);\n  ASSERT_NE(surface, nullptr);\n  ASSERT_FALSE(surface->IsValid());\n}\n\nTEST(TestMetalSurface, CanCreateValidTestMetalSurface) {\n  if (!TestMetalSurface::PlatformSupportsMetal()) {\n    GTEST_SKIP();\n  }\n\n  TestMetalContext metal_context = TestMetalContext();\n  auto surface =\n      TestMetalSurface::Create(metal_context, SkISize::Make(100, 100));\n  ASSERT_NE(surface, nullptr);\n  ASSERT_TRUE(surface->IsValid());\n  ASSERT_NE(surface->GetSurface(), nullptr);\n  ASSERT_NE(surface->GetGrContext(), nullptr);\n}\n\n#endif\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/test_timeout_listener.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/test_timeout_listener.h\"\n\n#include <map>\n#include <sstream>\n\nnamespace clay {\nnamespace testing {\n\nclass PendingTests : public std::enable_shared_from_this<PendingTests> {\n public:\n  static std::shared_ptr<PendingTests> Create(\n      fml::RefPtr<fml::TaskRunner> host_task_runner, fml::TimeDelta timeout) {\n    return std::shared_ptr<PendingTests>(\n        new PendingTests(std::move(host_task_runner), timeout));\n  }\n\n  ~PendingTests() = default;\n\n  void OnTestBegin(const std::string& test_name, fml::TimePoint test_time) {\n    FML_CHECK(tests_.find(test_name) == tests_.end())\n        << \"Attempting to start a test that is already pending.\";\n    tests_[test_name] = test_time;\n\n    host_task_runner_->PostDelayedTask(\n        [weak = weak_from_this()] {\n          if (auto strong = weak.lock()) {\n            strong->CheckTimedOutTests();\n          }\n        },\n        timeout_);\n  }\n\n  void OnTestEnd(const std::string& test_name) { tests_.erase(test_name); }\n\n  void CheckTimedOutTests() const {\n    const auto now = fml::TimePoint::Now();\n\n    for (const auto& test : tests_) {\n      auto delay = now - test.second;\n      FML_CHECK(delay < timeout_)\n          << \"Test \" << test.first << \" did not complete in \"\n          << timeout_.ToSeconds()\n          << \" seconds and is assumed to be hung. Killing the test harness.\";\n    }\n  }\n\n private:\n  using TestData = std::map<std::string, fml::TimePoint>;\n\n  fml::RefPtr<fml::TaskRunner> host_task_runner_;\n  TestData tests_;\n  const fml::TimeDelta timeout_;\n\n  PendingTests(fml::RefPtr<fml::TaskRunner> host_task_runner,\n               fml::TimeDelta timeout)\n      : host_task_runner_(std::move(host_task_runner)), timeout_(timeout) {}\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PendingTests);\n};\n\ntemplate <class T>\nauto WeakPtr(std::shared_ptr<T> pointer) {\n  return std::weak_ptr<T>{pointer};\n}\n\nTestTimeoutListener::TestTimeoutListener(fml::TimeDelta timeout)\n    : timeout_(timeout),\n      listener_thread_(\"test_timeout_listener\"),\n      listener_thread_runner_(listener_thread_.GetTaskRunner()),\n      pending_tests_(PendingTests::Create(listener_thread_runner_, timeout_)) {\n  FML_LOG(INFO) << \"Test timeout of \" << timeout_.ToSeconds()\n                << \" seconds per test case will be enforced.\";\n}\n\nTestTimeoutListener::~TestTimeoutListener() {\n  listener_thread_runner_->PostTask(\n      [tests = std::move(pending_tests_)]() mutable { tests.reset(); });\n  FML_CHECK(pending_tests_ == nullptr);\n}\n\nstatic std::string GetTestNameFromTestInfo(\n    const ::testing::TestInfo& test_info) {\n  std::stringstream stream;\n  stream << test_info.test_suite_name();\n  stream << \".\";\n  stream << test_info.name();\n  if (auto type_param = test_info.type_param()) {\n    stream << \"/\" << type_param;\n  }\n  if (auto value_param = test_info.value_param()) {\n    stream << \"/\" << value_param;\n  }\n  return stream.str();\n}\n\n// |testing::EmptyTestEventListener|\nvoid TestTimeoutListener::OnTestStart(const ::testing::TestInfo& test_info) {\n  listener_thread_runner_->PostTask([weak_tests = WeakPtr(pending_tests_),\n                                     name = GetTestNameFromTestInfo(test_info),\n                                     now = fml::TimePoint::Now()]() {\n    if (auto tests = weak_tests.lock()) {\n      tests->OnTestBegin(name, now);\n    }\n  });\n}\n\n// |testing::EmptyTestEventListener|\nvoid TestTimeoutListener::OnTestEnd(const ::testing::TestInfo& test_info) {\n  listener_thread_runner_->PostTask(\n      [weak_tests = WeakPtr(pending_tests_),\n       name = GetTestNameFromTestInfo(test_info)]() {\n        if (auto tests = weak_tests.lock()) {\n          tests->OnTestEnd(name);\n        }\n      });\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/test_timeout_listener.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef TESTING_TEST_TIMEOUT_LISTENER_H_\n#define TESTING_TEST_TIMEOUT_LISTENER_H_\n\n#include <memory>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/thread.h\"\n#include \"clay/testing/testing.h\"\n\nnamespace clay {\nnamespace testing {\n\nclass PendingTests;\n\nclass TestTimeoutListener : public ::testing::EmptyTestEventListener {\n public:\n  explicit TestTimeoutListener(fml::TimeDelta timeout);\n\n  ~TestTimeoutListener();\n\n private:\n  const fml::TimeDelta timeout_;\n  fml::Thread listener_thread_;\n  fml::RefPtr<fml::TaskRunner> listener_thread_runner_;\n  std::shared_ptr<PendingTests> pending_tests_;\n\n  // |testing::EmptyTestEventListener|\n  void OnTestStart(const ::testing::TestInfo& test_info) override;\n\n  // |testing::EmptyTestEventListener|\n  void OnTestEnd(const ::testing::TestInfo& test_info) override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TestTimeoutListener);\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_TEST_TIMEOUT_LISTENER_H_\n"
  },
  {
    "path": "clay/testing/testing.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/testing/testing.h\"\n\n#include <utility>\n\n#include \"clay/fml/file.h\"\n#include \"clay/fml/paths.h\"\n\nnamespace clay {\nnamespace testing {\n\nstd::string GetCurrentTestName() {\n  return ::testing::UnitTest::GetInstance()->current_test_info()->name();\n}\n\nstd::string GetDefaultKernelFilePath() {\n  return fml::paths::JoinPaths({GetFixturesPath(), \"kernel_blob.bin\"});\n}\n\nfml::UniqueFD OpenFixturesDirectory() {\n  auto fixtures_directory =\n      OpenDirectory(GetFixturesPath(),          // path\n                    false,                      // create\n                    fml::FilePermission::kRead  // permission\n      );\n\n  if (!fixtures_directory.is_valid()) {\n    FML_LOG(ERROR) << \"Could not open fixtures directory.\";\n    return {};\n  }\n  return fixtures_directory;\n}\n\nfml::UniqueFD OpenFixture(const std::string& fixture_name) {\n  if (fixture_name.size() == 0) {\n    FML_LOG(ERROR) << \"Invalid fixture name.\";\n    return {};\n  }\n\n  auto fixtures_directory = OpenFixturesDirectory();\n\n  auto fixture_fd = fml::OpenFile(fixtures_directory,         // base directory\n                                  fixture_name.c_str(),       // path\n                                  false,                      // create\n                                  fml::FilePermission::kRead  // permission\n  );\n  if (!fixture_fd.is_valid()) {\n    FML_LOG(ERROR) << \"Could not open fixture for path: \" << GetFixturesPath()\n                   << \"/\" << fixture_name << \".\";\n    return {};\n  }\n\n  return fixture_fd;\n}\n\nstd::unique_ptr<fml::Mapping> OpenFixtureAsMapping(\n    const std::string& fixture_name) {\n  return fml::FileMapping::CreateReadOnly(OpenFixture(fixture_name));\n}\n\nbool MemsetPatternSetOrCheck(uint8_t* buffer, size_t size, MemsetPatternOp op) {\n  if (buffer == nullptr) {\n    return false;\n  }\n\n  auto pattern = reinterpret_cast<const uint8_t*>(\"dErP\");\n  constexpr auto pattern_length = 4;\n\n  uint8_t* start = buffer;\n  uint8_t* p = buffer;\n\n  while ((start + size) - p >= pattern_length) {\n    switch (op) {\n      case MemsetPatternOp::kMemsetPatternOpSetBuffer:\n        memmove(p, pattern, pattern_length);\n        break;\n      case MemsetPatternOp::kMemsetPatternOpCheckBuffer:\n        if (memcmp(pattern, p, pattern_length) != 0) {\n          return false;\n        }\n        break;\n    };\n    p += pattern_length;\n  }\n\n  if ((start + size) - p != 0) {\n    switch (op) {\n      case MemsetPatternOp::kMemsetPatternOpSetBuffer:\n        memmove(p, pattern, (start + size) - p);\n        break;\n      case MemsetPatternOp::kMemsetPatternOpCheckBuffer:\n        if (memcmp(pattern, p, (start + size) - p) != 0) {\n          return false;\n        }\n        break;\n    }\n  }\n\n  return true;\n}\n\nbool MemsetPatternSetOrCheck(std::vector<uint8_t>& buffer, MemsetPatternOp op) {\n  return MemsetPatternSetOrCheck(buffer.data(), buffer.size(), op);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/testing.gni",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//build/compiled_action.gni\")\nimport(\"../common/config.gni\")\n\n# Creates a translation unit that defines the clay::testing::GetFixturesPath\n# method that tests can use to locate their fixtures.\n#\n# Arguments\n#     assets_dir (required): The assets directory\ntemplate(\"fixtures_location\") {\n  testonly = true\n\n  assert(defined(invoker.assets_dir), \"The assets directory.\")\n\n  location_path = rebase_path(invoker.assets_dir)\n  testing_assets_path = rebase_path(\"$root_out_dir/gen/lynx/clay/testing/assets\")\n  source_path = rebase_path(\"//\")\n\n  # Array of source lines. We use a list to ensure a trailing newline is\n  # emitted by write_file() to comply with -Wnewline-eof.\n  location_source = [\n    \"namespace clay { namespace testing { \",\n    \"const char* GetSourcePath() {return \\\"$source_path\\\";} \",\n    \"const char* GetFixturesPath() {return \\\"$location_path\\\";} \",\n    \"const char* GetTestingAssetsPath() {return \\\"$testing_assets_path\\\";} \",\n    \"}}\",\n  ]\n  location_source_path = \"$target_gen_dir/_fl_$target_name.cc\"\n\n  write_file(location_source_path, location_source)\n\n  source_set(target_name) {\n    public = []\n    sources = [ location_source_path ]\n  }\n}\n\n# Copies a (potentially empty) list of fixtures to the fixtures directory for\n# the unit test.\n#\n# Arguments:\n#\n#     fixtures (required): The list of fixtures to copy.\n#\n#     dest (optional): When specified, the fixtures are placed under an\n#                      'assets' subdirectory of this path rather than an\n#                      'assets' subdirectory of target_gen_dir.\ntemplate(\"copy_fixtures\") {\n  testonly = true\n\n  assert(defined(invoker.fixtures), \"The test fixtures must be specified.\")\n\n  dest = target_gen_dir\n  if (defined(invoker.dest)) {\n    dest = invoker.dest\n  }\n\n  has_fixtures = false\n  foreach(fixture, invoker.fixtures) {\n    has_fixtures = true\n  }\n\n  if (has_fixtures) {\n    copy(target_name) {\n      sources = invoker.fixtures\n      outputs = [ \"$dest/assets/{{source_file_part}}\" ]\n\n      # forward_variables_from(invoker, [ \"deps\" ])\n    }\n  } else {\n    group(target_name) {\n      # The copy target cannot accept an empty list.\n    }\n  }\n}\n\n# Specifies the fixtures to copy to a location known by the specific unit test.\n# Test executable can only depend on one such target. You can use either one of\n# both arguments to expand this template. If you have none, then you'll see the\n# unused invoker scope error. In such cases specify the fixtures using an empty\n# array.\n#\n# The targets which generate the outputs from these test fixtures (e.g. the\n# Dart kernel snapshot) are exposed as public dependencies of the test fixture\n# target. This is so that users can depend on the test fixture target directly\n# and be able to access the generated outputs without needing to know about the\n# internal dependency structure generated by this template.\n#\n# Arguments:\n#\n#     fixtures (optional): The list of test fixtures. An empty list may be\n#                          specified.\n#\n#     use_target_as_artifact_prefix(optional): If true, adds the target name as\n#         prefix of the kernel and AOT ELF snapshot filename.\n#\n#     dest (optional): When specified, the fixtures are placed under an\n#                      'assets' subdirectory of this path rather than an\n#                      'assets' subdirectory of target_gen_dir.\ntemplate(\"test_fixtures\") {\n  dest = target_gen_dir\n  if (defined(invoker.dest)) {\n    dest = invoker.dest\n  }\n\n  # Not all paths use 'dest'\n  not_needed([ \"dest\" ])\n\n  # Even if no fixtures are present, the location of the fixtures directory\n  # must always be known to tests.\n  fixtures_location_target_name = \"_fl_$target_name\"\n  fixtures_location(fixtures_location_target_name) {\n    if (is_fuchsia) {\n      assets_dir = \"/pkg/data/assets\"\n    } else {\n      assets_dir = \"$dest/assets\"\n    }\n  }\n  test_deps = [ \":$fixtures_location_target_name\" ]\n  test_public_deps = []\n\n  # If the fixtures are specified, copy them to the assets directory.\n  if (defined(invoker.fixtures)) {\n    copy_fixtures_target_name = \"_cf_$target_name\"\n    copy_fixtures(copy_fixtures_target_name) {\n      fixtures = invoker.fixtures\n      dest = dest\n\n      # forward_variables_from(invoker, [ \"deps\" ])\n    }\n    test_public_deps += [ \":$copy_fixtures_target_name\" ]\n  }\n\n  group(target_name) {\n    testonly = true\n    deps = test_deps\n    public_deps = test_public_deps\n  }\n}\n"
  },
  {
    "path": "clay/testing/testing.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_TESTING_H_\n#define TESTING_TESTING_H_\n\n#include <string>\n#include <vector>\n\n#include \"clay/fml/file.h\"\n#include \"clay/fml/mapping.h\"\n#include \"clay/testing/assertions.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nconst char* GetSourcePath();\n\n//------------------------------------------------------------------------------\n/// @brief      Returns the directory containing the test fixture for the target\n///             if this target has fixtures configured. If there are no\n///             fixtures, this is a link error. If you see a linker error on\n///             this symbol, the unit-test target needs to depend on a\n///             `test_fixtures` target.\n///\n/// @return     The fixtures path.\n///\nconst char* GetFixturesPath();\n\n//------------------------------------------------------------------------------\n/// @brief      Returns the directory containing assets shared across all tests.\n///\n/// @return     The testing assets path.\n///\nconst char* GetTestingAssetsPath();\n\n//------------------------------------------------------------------------------\n/// @brief      Returns the default path to kernel_blob.bin. This file is within\n///             the directory returned by `GetFixturesPath()`.\n///\n/// @return     The kernel file path.\n///\nstd::string GetDefaultKernelFilePath();\n\n//------------------------------------------------------------------------------\n/// @brief      Opens the fixtures directory for the unit-test harness.\n///\n/// @return     The file descriptor of the fixtures directory.\n///\nfml::UniqueFD OpenFixturesDirectory();\n\n//------------------------------------------------------------------------------\n/// @brief      Opens a fixture of the given file name.\n///\n/// @param[in]  fixture_name  The fixture name\n///\n/// @return     The file descriptor of the given fixture. An invalid file\n///             descriptor is returned in case the fixture is not found.\n///\nfml::UniqueFD OpenFixture(const std::string& fixture_name);\n\n//------------------------------------------------------------------------------\n/// @brief      Opens a fixture of the given file name and returns a mapping to\n///             its contents.\n///\n/// @param[in]  fixture_name  The fixture name\n///\n/// @return     A mapping to the contents of fixture or null if the fixture does\n///             not exist or its contents cannot be mapped in.\n///\nstd::unique_ptr<fml::Mapping> OpenFixtureAsMapping(\n    const std::string& fixture_name);\n\n//------------------------------------------------------------------------------\n/// @brief      Gets the name of the currently running test. This is useful in\n///             generating logs or assets based on test name.\n///\n/// @return     The current test name.\n///\nstd::string GetCurrentTestName();\n\nenum class MemsetPatternOp {\n  kMemsetPatternOpSetBuffer,\n  kMemsetPatternOpCheckBuffer,\n};\n\n//------------------------------------------------------------------------------\n/// @brief      Depending on the operation, either scribbles a known pattern\n///             into the buffer or checks if that pattern is present in an\n///             existing buffer. This is a portable variant of the\n///             memset_pattern class of methods that also happen to do assert\n///             that the same pattern exists.\n///\n/// @param      buffer  The buffer\n/// @param[in]  size    The size\n/// @param[in]  op      The operation\n///\n/// @return     If the result of the operation was a success.\n///\nbool MemsetPatternSetOrCheck(uint8_t* buffer, size_t size, MemsetPatternOp op);\n\nbool MemsetPatternSetOrCheck(std::vector<uint8_t>& buffer, MemsetPatternOp op);\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_TESTING_H_\n"
  },
  {
    "path": "clay/testing/thread_test.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define FML_USED_ON_EMBEDDER\n\n#include \"clay/testing/thread_test.h\"\n\nnamespace clay {\nnamespace testing {\nnamespace {\n\nfml::RefPtr<fml::TaskRunner> GetDefaultTaskRunner() {\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  return fml::MessageLoop::GetCurrent().GetTaskRunner();\n}\n\n}  // namespace\n\nThreadTest::ThreadTest() : current_task_runner_(GetDefaultTaskRunner()) {}\n\nfml::RefPtr<fml::TaskRunner> ThreadTest::GetCurrentTaskRunner() {\n  return current_task_runner_;\n}\n\nfml::RefPtr<fml::TaskRunner> ThreadTest::CreateNewThread(\n    const std::string& name) {\n  auto thread = std::make_unique<fml::Thread>(name);\n  auto runner = thread->GetTaskRunner();\n  extra_threads_.emplace_back(std::move(thread));\n  return runner;\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/testing/thread_test.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef TESTING_THREAD_TEST_H_\n#define TESTING_THREAD_TEST_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/thread.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\n//------------------------------------------------------------------------------\n/// @brief      A fixture that creates threads with running message loops that\n///             are terminated when the test is done (the threads are joined\n///             then as well). While this fixture may be used on it's own, it is\n///             often sub-classed by other fixtures whose functioning requires\n///             threads to be created as necessary.\n///\nclass ThreadTest : public ::testing::Test {\n public:\n  ThreadTest();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Get the task runner for the thread that the current unit-test\n  ///             is running on. This creates a message loop as necessary.\n  ///\n  /// @attention  Unlike all other threads and task runners, this task runner is\n  ///             shared by all tests running in the process. Tests must ensure\n  ///             that all tasks posted to this task runner are executed before\n  ///             the test ends to prevent the task from one test being executed\n  ///             while another test is running. When in doubt, just create a\n  ///             bespoke thread and task running. These cannot be seen by other\n  ///             tests in the process.\n  ///\n  /// @see        `GetThreadTaskRunner`, `CreateNewThread`.\n  ///\n  /// @return     The task runner for the thread the test is running on.\n  ///\n  fml::RefPtr<fml::TaskRunner> GetCurrentTaskRunner();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Creates a new thread, initializes a message loop on it, and,\n  ///             returns its task runner to the unit-test. The message loop is\n  ///             terminated (and its thread joined) when the test ends. This\n  ///             allows tests to create multiple named threads as necessary.\n  ///\n  /// @param[in]  name  The name of the OS thread created.\n  ///\n  /// @return     The task runner for the newly created thread.\n  ///\n  fml::RefPtr<fml::TaskRunner> CreateNewThread(const std::string& name = \"\");\n\n private:\n  fml::RefPtr<fml::TaskRunner> current_task_runner_;\n  std::vector<std::unique_ptr<fml::Thread>> extra_threads_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ThreadTest);\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // TESTING_THREAD_TEST_H_\n"
  },
  {
    "path": "clay/testing/tsan_suppressions.txt",
    "content": "# For more information on how to add additional suppressions here go to\n# https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions\n\n# We don't care about errors from the Dart VM.\nrace:dart::*\nthread:dart::*\n\n# Data race on animator begin frame when the engine is shutting down.\n# https://github.com/flutter/flutter/issues/42190\nrace:clay::Shell::OnAnimatorBeginFrame\nrace:clay::Shell::OnAnimatorNotifyIdle\n\n# Data race in MessageLoopTaskQueues disposal.\n# https://github.com/flutter/flutter/issues/42192\nrace:fml::MessageLoopTaskQueues::Dispose\n"
  },
  {
    "path": "clay/testing/ubsan_suppressions.txt",
    "content": "# For more information on how to add additional suppressions here go to\n# https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#id13\n\n# Ignore errors is Dart VM.\nalignment:third_party/dart\nnull:third_party/dart\n\n# Ignore errors in SwiftShader\nundefined:third_party/swiftshader\n\n"
  },
  {
    "path": "clay/testing/xvfb.py",
    "content": "# Copyright (c) 2012 The Chromium Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n\"\"\"Functions to setup xvfb, which is used by the linux machines.\n\"\"\"\n\nimport os\nimport signal\nimport subprocess\nimport tempfile\nimport time\n\n\ndef xvfb_display_index(_child_build_name):\n  return '9'\n\n\ndef xvfb_pid_filename(child_build_name):\n  \"\"\"Returns the filename to the Xvfb pid file.  This name is unique for each\n  builder. This is used by the linux builders.\"\"\"\n  return os.path.join(\n      tempfile.gettempdir(),\n      'xvfb-' + xvfb_display_index(child_build_name) + '.pid'\n  )\n\n\ndef start_virtual_x(child_build_name, build_dir):\n  \"\"\"Start a virtual X server and set the DISPLAY environment variable so sub\n  processes will use the virtual X server.  Also start openbox. This only works\n  on Linux and assumes that xvfb and openbox are installed.\n\n  Args:\n    child_build_name: The name of the build that we use for the pid file.\n        E.g., webkit-rel-linux.\n    build_dir: The directory where binaries are produced.  If this is non-empty,\n        we try running xdisplaycheck from |build_dir| to verify our X\n        connection.\n  \"\"\"\n  # We use a pid file to make sure we don't have any xvfb processes running\n  # from a previous test run.\n  stop_virtual_x(child_build_name)\n\n  xdisplaycheck_path = None\n  if build_dir:\n    xdisplaycheck_path = os.path.join(build_dir, 'xdisplaycheck')\n\n  display = ':%s' % xvfb_display_index(child_build_name)\n  # Note we don't add the optional screen here (+ '.0')\n  os.environ['DISPLAY'] = display\n\n  # Parts of Xvfb use a hard-coded \"/tmp\" for its temporary directory.\n  # This can cause a failure when those parts expect to hardlink against\n  # files that were created in \"TEMPDIR\" / \"TMPDIR\".\n  #\n  # See: https://crbug.com/715848\n  env = os.environ.copy()\n  if env.get('TMPDIR') and env['TMPDIR'] != '/tmp':\n    print('Overriding TMPDIR to \"/tmp\" for Xvfb, was: %s' % (env['TMPDIR'],))\n    env['TMPDIR'] = '/tmp'\n\n  if xdisplaycheck_path and os.path.exists(xdisplaycheck_path):\n    print('Verifying Xvfb is not running ...')\n    checkstarttime = time.time()\n    xdisplayproc = subprocess.Popen([xdisplaycheck_path, '--noserver'],\n                                    stdout=subprocess.PIPE,\n                                    stderr=subprocess.STDOUT,\n                                    env=env)\n    # Wait for xdisplaycheck to exit.\n    logs = xdisplayproc.communicate()[0]\n    if xdisplayproc.returncode == 0:\n      print('xdisplaycheck says there is a display still running, exiting...')\n      raise Exception('Display already present.')\n\n    xvfb_lock_filename = '/tmp/.X%s-lock' % xvfb_display_index(child_build_name)\n    if os.path.exists(xvfb_lock_filename):\n      print('Removing stale xvfb lock file %r' % xvfb_lock_filename)\n      try:\n        os.unlink(xvfb_lock_filename)\n      except OSError as err:\n        print('Removing xvfb lock file failed: %s' % err)\n\n  # Figure out which X server to try.\n  cmd = 'Xvfb'\n\n  # Start a virtual X server that we run the tests in.  This makes it so we can\n  # run the tests even if we didn't start the tests from an X session.\n  proc = subprocess.Popen([\n      cmd, display, '-screen', '0', '1280x800x24', '-ac', '-dpi', '96',\n      '-maxclients', '512', '-extension', 'MIT-SHM'\n  ],\n                          stdout=subprocess.PIPE,\n                          stderr=subprocess.STDOUT,\n                          env=env)\n  pid_filename = xvfb_pid_filename(child_build_name)\n  open(pid_filename, 'w').write(str(proc.pid))\n\n  # Wait for Xvfb to start up.\n  time.sleep(10)\n\n  # Verify that Xvfb has started by using xdisplaycheck.\n  if xdisplaycheck_path and os.path.exists(xdisplaycheck_path):\n    print('Verifying Xvfb has started...')\n    checkstarttime = time.time()\n    xdisplayproc = subprocess.Popen([xdisplaycheck_path],\n                                    stdout=subprocess.PIPE,\n                                    stderr=subprocess.STDOUT)\n    # Wait for xdisplaycheck to exit.\n    logs = xdisplayproc.communicate()[0]\n    checktime = time.time() - checkstarttime\n    if xdisplayproc.returncode != 0:\n      print('xdisplaycheck failed after %d seconds.' % checktime)\n      print('xdisplaycheck output:')\n      for line in logs.splitlines():\n        print('> %s' % line)\n      return_code = proc.poll()\n      if return_code is None:\n        print('Xvfb still running, stopping.')\n        proc.terminate()\n      else:\n        print('Xvfb exited, code %d' % return_code)\n\n      print('Xvfb output:')\n      for line in proc.communicate()[0].splitlines():\n        print('> %s' % line)\n      raise Exception(logs)\n\n    print('xdisplaycheck succeeded after %d seconds.' % checktime)\n    print('xdisplaycheck output:')\n    for line in logs.splitlines():\n      print('> %s' % line)\n    print('...OK')\n\n  # Some ChromeOS tests need a window manager.\n  subprocess.Popen('openbox', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\n  print('Window manager (openbox) started.')\n\n\ndef stop_virtual_x(child_build_name):\n  \"\"\"Try and stop the virtual X server if one was started with StartVirtualX.\n  When the X server dies, it takes down the window manager with it.\n  If a virtual x server is not running, this method does nothing.\"\"\"\n  pid_filename = xvfb_pid_filename(child_build_name)\n  if os.path.exists(pid_filename):\n    xvfb_pid = int(open(pid_filename).read())\n    print('Stopping Xvfb with pid %d ...' % xvfb_pid)\n    # If the process doesn't exist, we raise an exception that we can ignore.\n    try:\n      os.kill(xvfb_pid, signal.SIGKILL)\n    except OSError:\n      print('... killing failed, presuming unnecessary.')\n    os.remove(pid_filename)\n    print('Xvfb pid file removed')\n"
  },
  {
    "path": "clay/third_party/CPPLINT.cfg",
    "content": "set noparent\nexclude_files=.*"
  },
  {
    "path": "clay/third_party/skity_geometry/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\nsource_set(\"skity_geometry\") {\n  sources = [\n    \"src/geometry/glm_helper.hpp\",\n    \"src/geometry/math.hpp\",\n    \"src/geometry/matrix.cc\",\n    \"src/geometry/rect.cc\",\n    \"src/geometry/rrect.cc\",\n  ]\n\n  include_dirs = [\n    \".\",\n    \"include\",\n    \"../glm\",\n  ]\n\n  defines = []\n  cflags_objcc = [ \"-std=c++17\" ]\n  cflags_cc = []\n}\n"
  },
  {
    "path": "clay/third_party/skity_geometry/include/skity/geometry/matrix.hpp",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef INCLUDE_SKITY_GEOMETRY_MATRIX_HPP\n#define INCLUDE_SKITY_GEOMETRY_MATRIX_HPP\n\n#include <skity/geometry/point.hpp>\n#include <skity/macros.hpp>\n\nnamespace skity {\nnamespace clay {\n\nclass Rect;\n\nstruct SKITY_API Matrix {\n public:\n  constexpr static Matrix Translate(float dx, float dy) {\n    return Matrix(1.0f, 0.0f, 0.0f, 0.0f,  //\n                  0.0f, 1.0f, 0.0f, 0.0f,  //\n                  0.0f, 0.0f, 1.0f, 0.0f,  //\n                  dx, dy, 0.0f, 1.0f);\n  }\n\n  constexpr static Matrix Scale(float sx, float sy) {\n    return Matrix(sx, 0.0f, 0.0f, 0.0f,    //\n                  0.0f, sy, 0.0f, 0.0f,    //\n                  0.0f, 0.0f, 1.0f, 0.0f,  //\n                  0.0f, 0.0f, 0.0f, 1.0f);\n  }\n\n  constexpr static Matrix Skew(float sx, float sy) {\n    return Matrix(1.0f, sy, 0.0f, 0.0f,    //\n                  sx, 1.0f, 0.0f, 0.0f,    //\n                  0.0f, 0.0f, 1.0f, 0.0f,  //\n                  0.0f, 0.0f, 0.0f, 1.0f);\n  }\n\n  static Matrix RotateDeg(float deg);\n\n  static Matrix RotateDeg(float deg, Vec2 pt);\n\n  static Matrix RotateDeg(float deg, Vec3 axis);\n\n  static Matrix RotateRad(float rad);\n\n  static Matrix RotateRad(float deg, Vec2 pt);\n\n  ~Matrix() = default;\n\n  constexpr Matrix()\n      : Matrix(1.0f, 0.0f, 0.0f, 0.0f,  //\n               0.0f, 1.0f, 0.0f, 0.0f,  //\n               0.0f, 0.0f, 1.0f, 0.0f,  //\n               0.0f, 0.0f, 0.0f, 1.0f) {}\n\n  constexpr Matrix(float s)\n      : Matrix(s, 0.0f, 0.0f, 0.0f,  //\n               0.0f, s, 0.0f, 0.0f,  //\n               0.0f, 0.0f, s, 0.0f,  //\n               0.0f, 0.0f, 0.0f, s) {}\n\n  constexpr Matrix(float mxx, float myx, float mzx, float mwx, float mxy,\n                   float myy, float mzy, float mwy, float mxz, float myz,\n                   float mzz, float mwz, float mxt, float myt, float mzt,\n                   float mwt)\n      : m{mxx, myx, mzx, mwx,  //\n          mxy, myy, mzy, mwy,  //\n          mxz, myz, mzz, mwz,  //\n          mxt, myt, mzt, mwt} {}\n\n  Matrix(float scale_x, float skew_x, float trans_x, float skew_y,\n         float scale_y, float trans_y, float pers_0, float pers_1, float pers_2)\n      : Matrix{scale_x, skew_y,  0, pers_0,  //\n               skew_x,  scale_y, 0, pers_1,  //\n               0,       0,       1, 0,       //\n               trans_x, trans_y, 0, pers_2} {}\n\n  Matrix(const Matrix&) = default;\n\n  Matrix& operator=(const Matrix&) = default;\n\n  constexpr bool operator==(const Matrix& other) const {\n    return vec[0] == other.vec[0] && vec[1] == other.vec[1] &&\n           vec[2] == other.vec[2] && vec[3] == other.vec[3];\n  }\n  constexpr bool operator!=(const Matrix& other) const {\n    return !(*this == other);\n  }\n\n  Matrix& Reset() {\n    *this = Matrix();\n    return *this;\n  }\n\n  bool IsIdentity() const;\n\n  bool IsFinite() const;\n\n  static constexpr float kNearZeroFloat = 1.0f / (1 << 12);\n  bool IsSimilarity(float tol = kNearZeroFloat) const;\n\n  Matrix& Set9(const float buffer[9]);\n\n  void Get9(float buffer[9]) const;\n\n  Matrix& Set(int row, int column, float value);\n\n  float Get(int row, int column) const;\n\n  constexpr float GetScaleX() const { return e[0][0]; }\n\n  constexpr float GetScaleY() const { return e[1][1]; }\n\n  constexpr float GetSkewX() const { return e[1][0]; }\n\n  constexpr float GetSkewY() const { return e[0][1]; }\n\n  constexpr float GetTranslateX() const { return e[3][0]; }\n\n  constexpr float GetTranslateY() const { return e[3][1]; }\n\n  constexpr float GetPersp0() const { return e[0][3]; }\n\n  constexpr float GetPersp1() const { return e[1][3]; }\n\n  constexpr float GetPersp2() const { return e[3][3]; }\n\n  constexpr void SetScaleX(float s) { e[0][0] = s; }\n\n  constexpr void SetScaleY(float s) { e[1][1] = s; }\n\n  constexpr void SetSkewX(float s) { e[1][0] = s; }\n\n  constexpr void SetSkewY(float s) { e[0][1] = s; }\n\n  constexpr void SetTranslateX(float t) { e[3][0] = t; }\n\n  constexpr void SetTranslateY(float t) { e[3][1] = t; }\n\n  constexpr void SetPersp0(float p) { e[0][3] = p; }\n\n  constexpr void SetPersp1(float p) { e[1][3] = p; }\n\n  constexpr void SetPersp2(float p) { e[3][3] = p; }\n\n  bool Invert(Matrix* inverse) const;\n\n  float Determinant() const;\n\n  void Transpose();\n\n  void MapPoints(Vec2 dst[], const Vec2 src[], int count) const;\n\n  void MapPoints(Point dst[], const Point src[], int count) const;\n\n  bool MapRect(Rect* dst, const Rect& src) const;\n\n  Rect MapRect(const Rect& src) const;\n\n  bool RectStaysRect() const;\n\n  Matrix& PreConcat(const Matrix& other);\n  Matrix& PreTranslate(float dx, float dy);\n  Matrix& PreScale(float sx, float sy);\n  Matrix& PreScale(float sx, float sy, float px, float py);\n  Matrix& PreRotate(float degrees);\n  Matrix& PreRotate(float degrees, float px, float py);\n\n  Matrix& PostConcat(const Matrix& other);\n  Matrix& PostTranslate(float dx, float dy);\n  Matrix& PostScale(float sx, float sy);\n  Matrix& PostRotate(float degrees);\n  Matrix& PostRotate(float degrees, float px, float py);\n  Matrix& PostSkew(float kx, float ky);\n\n  bool OnlyScaleAndTranslate() const;\n\n  bool OnlyTranslate() const;\n\n  bool OnlyScale() const;\n\n  bool HasPersp() const;\n\n  friend SKITY_API Matrix operator*(const Matrix& a, const Matrix& b);\n\n  friend SKITY_API Vec4 operator*(const Matrix& m, const Vec4& v);\n\n  constexpr const Vec4& operator[](int i) const { return vec[i]; }\n\n  constexpr Vec4& operator[](int i) { return vec[i]; }\n\n private:\n  bool InvertNonIdentity(Matrix* inverse) const;\n\n  Matrix& SetConcat(const Matrix& left, const Matrix& right);\n\n  union {\n    float m[16];\n    float e[4][4];\n    Vec4 vec[4];\n  };\n};\n}  // namespace clay\n\nusing Matrix = clay::Matrix;\n}  // namespace skity\n\n#endif  // INCLUDE_SKITY_GEOMETRY_MATRIX_HPP\n"
  },
  {
    "path": "clay/third_party/skity_geometry/include/skity/geometry/point.hpp",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef INCLUDE_SKITY_GEOMETRY_POINT_HPP\n#define INCLUDE_SKITY_GEOMETRY_POINT_HPP\n\n#include <skity/geometry/vector.hpp>\nnamespace skity {\n\nusing Point = Vec4;\n\n}  // namespace skity\n\n#endif  // INCLUDE_SKITY_GEOMETRY_POINT_HPP\n"
  },
  {
    "path": "clay/third_party/skity_geometry/include/skity/geometry/rect.hpp",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef INCLUDE_SKITY_GEOMETRY_RECT_HPP\n#define INCLUDE_SKITY_GEOMETRY_RECT_HPP\n\n#include <skity/geometry/point.hpp>\n#include <skity/macros.hpp>\n\nnamespace skity {\nnamespace clay {\n\nclass SKITY_API Rect {\n public:\n  constexpr Rect() : Rect{0, 0, 0, 0} {}\n  constexpr Rect(float left, float top, float right, float bottom)\n      : left_{left}, top_{top}, right_{right}, bottom_{bottom} {}\n  constexpr Rect(const Rect&) = default;\n  constexpr Rect(Rect&&) = default;\n  Rect& operator=(const Rect&) = default;\n\n  bool operator==(const Rect& other) const {\n    return this->left_ == other.left_ && this->top_ == other.top_ &&\n           this->right_ == other.right_ && this->bottom_ == other.bottom_;\n  }\n\n  bool operator!=(const Rect& other) const { return !(*this == other); }\n  /**\n   * Returns left edge of Rect, if sorted.\n   * @return left\n   */\n  constexpr float X() const { return left_; }\n  constexpr float Left() const { return left_; }\n  /**\n   * Returns top edge of Rect.\n   * @return top\n   */\n  constexpr float Y() const { return top_; }\n  constexpr float Top() const { return top_; }\n  /**\n   * Returns right edge of Rect.\n   * @return right\n   */\n  constexpr float Right() const { return right_; }\n  /**\n   * Returns bottom edge of rect.\n   * @return bottom\n   */\n  constexpr float Bottom() const { return bottom_; }\n  /**\n   * Returns span on the x-axis. This dose not check if Rect is sorted.\n   * Result may be negative or infinity.\n   */\n  constexpr float Width() const { return right_ - left_; }\n  /**\n   * Returns span on the y-axis.\n   */\n  constexpr float Height() const { return bottom_ - top_; }\n  float CenterX() const;\n  float CenterY() const;\n\n  /**\n   * Returns true if left is equal to or greater than right, or if top is equal\n   * to or greater than bottom. Call sort() to reverse rectangles with negative\n   * width() or height().\n   */\n  constexpr bool IsEmpty() const { return !(left_ < right_ && top_ < bottom_); }\n  void SetEmpty() { *this = MakeEmpty(); }\n  /**\n   * Returns for points in quad that enclose Rect ordered as: top-left,\n   * top-right, bottom-right, bottom-left\n   */\n  void ToQuad(Point quad[4]) const;\n  /**\n   * Set Rect to (left, top, right, bottom)\n   * @note left,right and top,bottom need to be sorted.\n   * @param left    sorted in left\n   * @param top     sorted in top\n   * @param right   sorted in right\n   * @param bottom  sorted in bottom\n   */\n  void SetLTRB(float left, float top, float right, float bottom) {\n    left_ = left;\n    top_ = top;\n    right_ = right;\n    bottom_ = bottom;\n  }\n  /**\n   * Sets to bounds of Point array with count entries.\n   * If count is zero or smaller, or if Point array contains an infinity or NaN,\n   * sets to (0, 0, 0, 0).\n   *\n   * Result is either empty or sorted.\n   *\n   * @param pts     Point array\n   * @param count   entries in array\n   */\n  void SetBounds(const Point pts[], int count) {\n    this->SetBoundsCheck(pts, count);\n  }\n  bool SetBoundsCheck(const Point pts[], int count);\n\n  void Set(const Point& p0, const Point& p1) {\n    left_ = std::min(p0.x, p1.x);\n    right_ = std::max(p0.x, p1.x);\n    top_ = std::min(p0.y, p1.y);\n    bottom_ = std::max(p0.y, p1.y);\n  }\n\n  /**\n   * Sets Rect to (x, y, x + width, y + height)\n   * Dose not validate input; with or height may be nagtive.\n   *\n   * @param x       stored in left\n   * @param y       stored in top\n   * @param width   added to x and stored in right\n   * @param height  added to y and stored in bottom\n   */\n  void SetXYWH(float x, float y, float width, float height) {\n    left_ = x;\n    top_ = y;\n    right_ = x + width;\n    bottom_ = y + height;\n  }\n\n  void SetX(float x);\n\n  void SetY(float y);\n\n  void SetLeft(float left);\n\n  void SetTop(float top);\n\n  void SetRight(float right);\n\n  void SetBottom(float bottom);\n\n  void SetWH(float width, float height);\n\n  void Offset(float dx, float dy);\n\n  void Inset(float inset);\n\n  void Inset(float dx, float dy);\n\n  void Outset(float outset);\n\n  void Outset(float dx, float dy);\n\n  void RoundOut();\n\n  void RoundIn();\n\n  void Round();\n\n  bool IsSorted() const { return left_ <= right_ && top_ <= bottom_; }\n\n  bool IsFinite() const;\n\n  void Sort() {\n    if (left_ > right_) {\n      std::swap(left_, right_);\n    }\n    if (top_ > bottom_) {\n      std::swap(top_, bottom_);\n    }\n  }\n\n  Rect MakeSorted() const;\n\n  Rect MakeOffset(float dx, float dy) const;\n\n  Rect MakeInset(float dx, float dy) const;\n\n  Rect MakeOutset(float dx, float dy) const;\n\n  /**\n   * Sets Rect to the union of itself and r.\n   * @param r expansion Rect\n   */\n  void Join(Rect const& r);\n\n  /**\n   * Returns true if Rect intersects r, and sets Rect to intersection.\n   * Returns false if Rect does not intersect r, and leaves Rect unchanged.\n   * @param r expansion Rect\n   */\n  bool Intersect(Rect const& r);\n\n  bool Contains(float x, float y) const {\n    return x >= left_ && x < right_ && y >= top_ && y < bottom_;\n  }\n\n  bool Contains(const Rect& r) const {\n    return !r.IsEmpty() && !this->IsEmpty() && left_ <= r.left_ &&\n           top_ <= r.top_ && right_ >= r.right_ && bottom_ >= r.bottom_;\n  }\n\n  static constexpr Rect MakeEmpty() { return Rect{0, 0, 0, 0}; }\n\n  static constexpr Rect MakeWH(float width, float height) {\n    return Rect{0, 0, width, height};\n  }\n\n  static constexpr Rect MakeLTRB(float l, float t, float r, float b) {\n    return Rect{l, t, r, b};\n  }\n\n  static constexpr Rect MakeXYWH(float x, float y, float w, float h) {\n    return Rect{x, y, x + w, y + h};\n  }\n\n  static constexpr Rect MakeSize(const Vec2& size) {\n    return Rect{0, 0, size.x, size.y};\n  }\n\n  static float HalfWidth(Rect const& rect);\n\n  static float HalfHeight(Rect const& rect);\n\n  /**\n   * @return true   If a intersects b\n   * @return false  If a does not intersect b\n   */\n  static bool Intersect(Rect const& a, Rect const& b);\n\n private:\n  float left_;\n  float top_;\n  float right_;\n  float bottom_;\n\n  friend class RRect;\n};\n\n}  // namespace clay\nusing Rect = clay::Rect;\n}  // namespace skity\n\n#endif  // INCLUDE_SKITY_GEOMETRY_RECT_HPP\n"
  },
  {
    "path": "clay/third_party/skity_geometry/include/skity/geometry/rrect.hpp",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef INCLUDE_SKITY_GEOMETRY_RRECT_HPP\n#define INCLUDE_SKITY_GEOMETRY_RRECT_HPP\n\n#include <array>\n#include <skity/geometry/point.hpp>\n#include <skity/geometry/rect.hpp>\n#include <skity/macros.hpp>\n\nnamespace skity {\nnamespace clay {\n\nclass SKITY_API RRect {\n public:\n  RRect() = default;\n  RRect(RRect const& rrect) = default;\n  RRect& operator=(RRect const& rrect) = default;\n\n  bool operator==(const RRect& other) const {\n    return type_ == other.type_ && rect_ == other.rect_ &&\n           radii_ == other.radii_;\n  }\n\n  bool operator!=(const RRect& other) const { return !(*this == other); }\n\n  /**\n   * @enum RRect::Type\n   *  Type describes possible specializations of SkRRect. Each Type is\n   * \texclusive; a RRect may only have one type.\n   */\n  enum Type {\n    // zero width or height\n    kEmpty,\n    // non-zero width and height, and zeroed radii\n    kRect,\n    // non-zero width and height filled with radii\n    kOval,\n    // non-zero width and height with equal radii\n    kSimple,\n    // non-zero width and height with axis-aligned radii\n    kNinePatch,\n    // non-zero width and height with arbitrary radii\n    kComplex,\n    kLastType = kComplex,\n  };\n\n  /**\n   * @enum RRect::Corner\n   *\tThe radii are stored: top-left, top-right, bottom-right, bottom-left.\n   */\n  enum Corner {\n    // index of top-left corner radii\n    kUpperLeft,\n    // index of top-right corner radii\n    kUpperRight,\n    // index of bottom-right corner radii\n    kLowerRight,\n    // index of bottom-left corner radii\n    kLowerLeft,\n  };\n\n  Type GetType() const;\n\n  bool IsEmpty() const { return Type::kEmpty == this->GetType(); }\n  bool IsRect() const { return Type::kRect == this->GetType(); }\n  bool IsOval() const { return Type::kOval == this->GetType(); }\n  bool IsSimple() const { return Type::kSimple == this->GetType(); }\n  bool IsNinePatch() const { return Type::kNinePatch == this->GetType(); }\n  bool IsComplex() const { return Type::kComplex == this->GetType(); }\n\n  float Width() const { return rect_.Width(); }\n  float Height() const { return rect_.Height(); }\n  Vec2 GetSimpleRadii() const { return radii_[0]; }\n\n  void SetEmpty() { *this = RRect(); }\n\n  /**\n   * @brief Set the Rect object\n   *\n   * @param rect bounds to set\n   */\n  void SetRect(Rect const& rect);\n\n  /**\n   * Sets bounds to oval, x-axis radii to half oval.width(), and all y-axis\n   * radii to half oval.height(). If oval bounds is empty, sets to kEmpty.\n   * Otherwise, sets to kOval.\n   *\n   * @param oval  bounds of oval\n   */\n  void SetOval(Rect const& oval);\n\n  /**\n   * @brief Sets to rounded rectangle with the same radii for all four corners.\n   *\n   * @param rect  bounds of rounded rectangle\n   * @param xRad  x-axis radius of corners\n   * @param yRad  y-axis radius of corners\n   */\n  void SetRectXY(Rect const& rect, float xRad, float yRad);\n\n  Rect const& GetRect() const { return rect_; }\n\n  /**\n   * @brief Sets bounds to rect. Sets radii array for individual control of all\n   * for corners.\n   *\n   * @param rect   bounds of rounded rectangle\n   * @param radii  corner x-axis and y-axis radii\n   */\n  void SetRectRadii(Rect const& rect, const Vec2 radii[4]);\n\n  /**\n   * Check if bounds and radii match type\n   *\n   * @return true\n   * @return false\n   */\n  bool IsValid() const;\n\n  Vec2 Radii(Corner corner) const { return radii_[corner]; }\n\n  const Vec2* Radii() const { return radii_.data(); }\n\n  /**\n   * Translates RRect by (dx, dy)\n   *\n   * @param dx  offset added to rect().left() and rect().right()\n   * @param dy  offset added to rect().top() and rect().bottom()\n   */\n  void Offset(float dx, float dy) { rect_.Offset(dx, dy); }\n\n  void Inset(float dx, float dy, RRect* dst) const;\n\n  void Outset(float dx, float dy, RRect* dst) const {\n    this->Inset(-dx, -dy, dst);\n  }\n\n  /**\n   * Returns bounds. Bounds may have zero width or zero height\n   *\n   * @return bounding box\n   */\n  Rect const& GetBounds() const { return rect_; }\n\n  bool Contains(const Rect& rect) const;\n\n  RRect MakeOffset(float dx, float dy) const;\n\n  static RRect MakeEmpty();\n  static RRect MakeRect(Rect const& r);\n  static RRect MakeRectXY(Rect const& rect, float xRad, float yRad);\n  static RRect MakeOval(Rect const& oval);\n\n private:\n  RRect(Rect const& rect, std::array<Vec2, 4> const& radii, Type type)\n      : rect_(rect), radii_(radii), type_(type) {}\n\n  static bool AreRectAndRadiiValid(Rect const& rect,\n                                   std::array<Vec2, 4> const& radii);\n\n  bool InitializeRect(Rect const& rect);\n  void ComputeType();\n  bool CheckCornerContainment(float x, float y) const;\n  // return true if the radii had to be scaled to fit rect\n  bool ScaleRadii();\n\n private:\n  Rect rect_ = Rect::MakeEmpty();\n  std::array<Vec2, 4> radii_ = {};\n  Type type_ = Type::kEmpty;\n};\n\n}  // namespace clay\nusing RRect = clay::RRect;\n}  // namespace skity\n\n#endif  // INCLUDE_SKITY_GEOMETRY_RRECT_HPP\n"
  },
  {
    "path": "clay/third_party/skity_geometry/include/skity/geometry/scalar.hpp",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cmath>\n\n#ifndef INCLUDE_SKITY_GEOMETRY_SCALAR_HPP\n#define INCLUDE_SKITY_GEOMETRY_SCALAR_HPP\n\nnamespace skity {\n\n#define Float1 1.0f\n#define FloatHalf 0.5f\n#define FloatNaN std::numeric_limits<float>::quiet_NaN()\n#define FloatInfinity std::numeric_limits<float>::infinity()\n#define FloatRoot2Over2 0.707106781f\n#define FloatSqrt2 1.41421356f\n\nconstexpr static float kNearlyZero = (Float1 / (1 << 12));\n\n/**\n *  Returns -1 || 0 || 1 depending on the sign of value:\n *  -1 if x < 0\n *   0 if x == 0\n *   1 if x > 0\n */\nstatic inline int FloatSignAsInt(float x) { return x < 0 ? -1 : (x > 0); }\n\nstatic inline int RoundToInt(float x) {\n  return static_cast<int>(std::round(x));\n}\n\nstatic inline bool FloatNearlyZero(float x, float tolerance = kNearlyZero) {\n  return std::abs(x) <= tolerance;\n}\n\nstatic inline float FloatFract(float x) { return x - std::floor(x); }\n\nstatic inline float FloatInterp(float A, float B, float t) {\n  return A + (B - A) * t;\n}\n\nstatic inline float FloatInterpFunc(float search_key, const float keys[],\n                                    const float values[], int length) {\n  int right = 0;\n  while (right < length && keys[right] < search_key) {\n    ++right;\n  }\n  if (right == length) {\n    return values[length - 1];\n  }\n  if (right == 0) {\n    return values[0];\n  }\n  // Otherwise, interpolate between right - 1 and right.\n  float range_left = keys[right - 1];\n  float range_right = keys[right];\n  float t = (search_key - range_left) / (range_right - range_left);\n  return FloatInterp(values[right - 1], values[right], t);\n}\n\nstatic inline float SkityFloatHalf(float v) { return v * FloatHalf; }\n\nstatic inline bool FloatIsNan(float x) { return x != x; }\n\n[[clang::no_sanitize(\"float-divide-by-zero\")]] static inline float\nSkityIEEEFloatDivided(float number, float denom) {\n  return number / denom;\n}\n\n#define FloatInvert(x) SkityIEEEFloatDivided(Float1, (x))\n\nstatic inline bool FloatIsFinite(float x) { return !std::isinf(x); }\n\nstatic inline float FloatSinSnapToZero(float radians) {\n  float v = std::sin(radians);\n  return FloatNearlyZero(v) ? 0.f : v;\n}\n\nstatic inline float FloatCosSnapToZero(float radians) {\n  float v = std::cos(radians);\n  return FloatNearlyZero(v) ? 0.f : v;\n}\n\nstatic inline float FloatTanSnapToZero(float radians) {\n  float v = std::tan(radians);\n  return FloatNearlyZero(v) ? 0.f : v;\n}\n\nstatic inline float FloatCopySign(float v1, float v2) {\n  return std::copysignf(v1, v2);\n}\n\nstatic inline float FloatRadiansToDegrees(float radians) {\n  return radians * static_cast<float>(57.295779513082320876798154814105);\n}\n\nstatic inline float FloatDegreesToRadians(float degrees) {\n  return degrees * static_cast<float>(0.01745329251994329576923690768489);\n}\n\n}  // namespace skity\n\n#endif  // INCLUDE_SKITY_GEOMETRY_SCALAR_HPP\n"
  },
  {
    "path": "clay/third_party/skity_geometry/include/skity/geometry/vector.hpp",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef INCLUDE_SKITY_GEOMETRY_VECTOR_HPP\n#define INCLUDE_SKITY_GEOMETRY_VECTOR_HPP\n\n#include <algorithm>\n#include <skity/geometry/scalar.hpp>\n#include <skity/macros.hpp>\n\nnamespace skity {\nnamespace clay {\nstruct Vec2;\nstruct Vec3;\nstruct Vec4;\n\nstruct SKITY_API Vec2 {\n  union {\n    struct {\n      float x;\n      float y;\n    };\n    float e[2];\n  };\n\n  constexpr Vec2() : x(0.f), y(0.f) {}\n\n  template <typename T1, typename T2,\n            typename = std::enable_if_t<std::is_arithmetic_v<T1> &&\n                                        std::is_arithmetic_v<T2>>>\n  constexpr Vec2(T1 x, T2 y)\n      : x(static_cast<float>(x)), y(static_cast<float>(y)) {}\n\n  constexpr explicit Vec2(float v) : x(v), y(v) {}\n\n  constexpr explicit Vec2(const Vec4& v);\n\n  constexpr bool operator==(const Vec2 v) const { return x == v.x && y == v.y; }\n  constexpr bool operator!=(const Vec2 v) const { return !(*this == v); }\n\n  constexpr static float Dot(Vec2 a, Vec2 b) { return a.x * b.x + a.y * b.y; }\n  constexpr static float Cross(Vec2 a, Vec2 b) { return a.x * b.y - a.y * b.x; }\n\n  constexpr static Vec2 Min(Vec2 a, Vec2 b) {\n    return Vec2{std::min(a.x, b.x), std::min(a.y, b.y)};\n  }\n\n  constexpr static Vec2 Max(Vec2 a, Vec2 b) {\n    return Vec2{std::max(a.x, b.x), std::max(a.y, b.y)};\n  }\n\n  static Vec2 Normalize(Vec2 v) {\n    auto length_squared = v.LengthSquared();\n    if (FloatNearlyZero(length_squared)) {\n      return Vec2{};\n    }\n    return v * (1.0f / std::sqrt(length_squared));\n  }\n\n  static Vec2 Sqrt(Vec2 v) { return {std::sqrt(v.x), std::sqrt(v.y)}; }\n\n  static Vec2 Round(Vec2 v) { return {std::round(v.x), std::round(v.y)}; }\n\n  static Vec2 Abs(Vec2 v) { return {std::abs(v.x), std::abs(v.y)}; }\n\n  constexpr Vec2 operator-() const { return {-x, -y}; }\n  constexpr Vec2 operator+(Vec2 v) const { return {x + v.x, y + v.y}; }\n  constexpr Vec2 operator-(Vec2 v) const { return {x - v.x, y - v.y}; }\n  constexpr Vec2 operator*(Vec2 v) const { return {x * v.x, y * v.y}; }\n  constexpr friend Vec2 operator*(Vec2 v, float s) {\n    return {v.x * s, v.y * s};\n  }\n  constexpr friend Vec2 operator*(float s, Vec2 v) {\n    return {v.x * s, v.y * s};\n  }\n  constexpr friend Vec2 operator/(Vec2 v, float s) {\n    return {v.x / s, v.y / s};\n  }\n  constexpr friend Vec2 operator/(float s, Vec2 v) {\n    return {s / v.x, s / v.y};\n  }\n  constexpr friend Vec2 operator/(Vec2 a, Vec2 b) {\n    return {a.x / b.x, a.y / b.y};\n  }\n\n  constexpr void operator+=(Vec2 v) { *this = *this + v; }\n  constexpr void operator-=(Vec2 v) { *this = *this - v; }\n  constexpr void operator*=(Vec2 v) { *this = *this * v; }\n  constexpr void operator*=(float s) { *this = *this * s; }\n  constexpr void operator/=(float s) { *this = *this / s; }\n\n  constexpr float LengthSquared() const { return Dot(*this, *this); }\n  float Length() const { return std::sqrt(this->LengthSquared()); }\n\n  constexpr float Dot(Vec2 v) const { return Dot(*this, v); }\n  constexpr float Cross(Vec2 v) const { return Cross(*this, v); }\n  Vec2 Normalize() const { return Normalize(*this); }\n\n  constexpr float operator[](int i) const { return e[i]; }\n  constexpr float& operator[](int i) { return e[i]; }\n};\n\nstruct SKITY_API Vec3 {\n  union {\n    struct {\n      float x, y, z;\n    };\n    float e[3];\n  };\n\n  constexpr Vec3() : x(0.f), y(0.f), z(0.f) {}\n\n  template <typename T1, typename T2, typename T3,\n            typename = std::enable_if_t<std::is_arithmetic_v<T1> &&\n                                        std::is_arithmetic_v<T2> &&\n                                        std::is_arithmetic_v<T3>>>\n  constexpr Vec3(T1 x, T2 y, T3 z)\n      : x(static_cast<float>(x)),\n        y(static_cast<float>(y)),\n        z(static_cast<float>(z)) {}\n\n  constexpr explicit Vec3(float v) : x(v), y(v), z(v) {}\n\n  constexpr bool operator==(const Vec3& v) const {\n    return x == v.x && y == v.y && z == v.z;\n  }\n  constexpr bool operator!=(const Vec3& v) const { return !(*this == v); }\n\n  constexpr static float Dot(const Vec3& a, const Vec3& b) {\n    return a.x * b.x + a.y * b.y + a.z * b.z;\n  }\n  constexpr static Vec3 Cross(const Vec3& a, const Vec3& b) {\n    return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z,\n            a.x * b.y - a.y * b.x};\n  }\n  static Vec3 Normalize(const Vec3& v) {\n    auto length_squared = v.LengthSquared();\n    if (FloatNearlyZero(length_squared)) {\n      return Vec3{};\n    }\n    return v * (1.0f / std::sqrt(length_squared));\n  }\n\n  constexpr Vec3 operator-() const { return {-x, -y, -z}; }\n  constexpr Vec3 operator+(const Vec3& v) const {\n    return {x + v.x, y + v.y, z + v.z};\n  }\n  constexpr Vec3 operator-(const Vec3& v) const {\n    return {x - v.x, y - v.y, z - v.z};\n  }\n\n  constexpr Vec3 operator*(const Vec3& v) const {\n    return {x * v.x, y * v.y, z * v.z};\n  }\n  constexpr friend Vec3 operator*(const Vec3& v, float s) {\n    return {v.x * s, v.y * s, v.z * s};\n  }\n  constexpr friend Vec3 operator*(float s, const Vec3& v) { return v * s; }\n\n  constexpr Vec3 operator/(const Vec3& v) const {\n    return {x / v.x, y / v.y, z / v.z};\n  }\n  constexpr friend Vec3 operator/(const Vec3& v, float s) {\n    return {v.x / s, v.y / s, v.z / s};\n  }\n  constexpr friend Vec3 operator/(float s, const Vec3& v) {\n    return {s / v.x, s / v.y, s / v.z};\n  }\n\n  constexpr void operator+=(Vec3 v) { *this = *this + v; }\n  constexpr void operator-=(Vec3 v) { *this = *this - v; }\n  constexpr void operator*=(Vec3 v) { *this = *this * v; }\n  constexpr void operator*=(float s) { *this = *this * s; }\n  constexpr void operator/=(float s) { *this = *this / s; }\n\n  constexpr float LengthSquared() const { return Dot(*this, *this); }\n  float Length() const { return std::sqrt(Dot(*this, *this)); }\n\n  constexpr float Dot(const Vec3& v) const { return Dot(*this, v); }\n  constexpr Vec3 Cross(const Vec3& v) const { return Cross(*this, v); }\n  Vec3 Normalize() const { return Normalize(*this); }\n\n  constexpr static Vec3 Min(Vec3 a, Vec3 b) {\n    return Vec3{std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)};\n  }\n  constexpr static Vec3 Max(Vec3 a, Vec3 b) {\n    return Vec3{std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)};\n  }\n\n  constexpr float operator[](int i) const { return e[i]; }\n  constexpr float& operator[](int i) { return e[i]; }\n};\n\nstruct SKITY_API Vec4 {\n  union {\n    struct {\n      float x, y, z, w;\n    };\n    struct {\n      float r, g, b, a;\n    };\n    float e[4];\n  };\n\n  constexpr Vec4() : x(0), y(0), z(0), w(0) {}\n\n  template <typename T1, typename T2, typename T3, typename T4,\n            typename = std::enable_if_t<\n                std::is_arithmetic_v<T1> && std::is_arithmetic_v<T2> &&\n                std::is_arithmetic_v<T3> && std::is_arithmetic_v<T4>>>\n  constexpr Vec4(T1 x, T2 y, T3 z, T4 w)\n      : x(static_cast<float>(x)),\n        y(static_cast<float>(y)),\n        z(static_cast<float>(z)),\n        w(static_cast<float>(w)) {}\n\n  constexpr explicit Vec4(float v) : x(v), y(v), z(v), w(v) {}\n\n  constexpr Vec4(const Vec2& xy, float z, float w)\n      : x(xy.x), y(xy.y), z(z), w(w) {}\n\n  constexpr Vec4(const Vec2& xy, const Vec2& zw)\n      : x(xy.x), y(xy.y), z(zw.x), w(zw.y) {}\n\n  constexpr Vec4(const Vec3& xyz, float w)\n      : x(xyz.x), y(xyz.y), z(xyz.z), w(w) {}\n\n  constexpr bool operator==(const Vec4& v) const {\n    return x == v.x && y == v.y && z == v.z && w == v.w;\n  }\n  constexpr bool operator!=(const Vec4& v) const { return !(*this == v); }\n\n  constexpr static float Dot(const Vec4& a, const Vec4& b) {\n    return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;\n  }\n  static Vec4 Normalize(const Vec4& v) {\n    auto length_squared = v.LengthSquared();\n    if (FloatNearlyZero(length_squared)) {\n      return Vec4{};\n    }\n    return v * (1.0f / std::sqrt(length_squared));\n  }\n\n  constexpr Vec4 operator-() const { return {-x, -y, -z, -w}; }\n  constexpr Vec4 operator+(const Vec4& v) const {\n    return {x + v.x, y + v.y, z + v.z, w + v.w};\n  }\n  constexpr Vec4 operator-(const Vec4& v) const {\n    return {x - v.x, y - v.y, z - v.z, w - v.w};\n  }\n\n  constexpr Vec4 operator*(const Vec4& v) const {\n    return {x * v.x, y * v.y, z * v.z, w * v.w};\n  }\n  constexpr friend Vec4 operator*(const Vec4& v, float s) {\n    return {v.x * s, v.y * s, v.z * s, v.w * s};\n  }\n  constexpr friend Vec4 operator*(float s, const Vec4& v) { return v * s; }\n\n  constexpr friend Vec4 operator/(const Vec4& v, float s) {\n    return {v.x / s, v.y / s, v.z / s, v.w / s};\n  }\n  constexpr friend Vec4 operator/(float s, const Vec4& v) {\n    return {s / v.x, s / v.y, s / v.z, s / v.w};\n  }\n\n  constexpr friend Vec4 operator/(const Vec4& a, const Vec4& b) {\n    return {a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w};\n  }\n\n  constexpr static Vec4 Min(Vec4 a, Vec4 b) {\n    return Vec4{std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z),\n                std::min(a.w, b.w)};\n  }\n  constexpr static Vec4 Max(Vec4 a, Vec4 b) {\n    return Vec4{std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z),\n                std::max(a.w, b.w)};\n  }\n\n  constexpr float LengthSquared() const { return Dot(*this, *this); }\n  float Length() const { return std::sqrt(Dot(*this, *this)); }\n\n  constexpr float Dot(const Vec4& v) const { return Dot(*this, v); }\n  Vec4 Normalize() const { return Normalize(*this); }\n\n  constexpr void operator+=(Vec4 v) { *this = *this + v; }\n  constexpr void operator-=(Vec4 v) { *this = *this - v; }\n  constexpr void operator*=(Vec4 v) { *this = *this * v; }\n  constexpr void operator*=(float s) { *this = *this * s; }\n  constexpr void operator/=(float s) { *this = *this / s; }\n\n  constexpr float operator[](int i) const { return e[i]; }\n  constexpr float& operator[](int i) { return e[i]; }\n};\n\nusing Vector = Vec4;\n\nconstexpr Vec2::Vec2(const Vec4& v) : x(v.x), y(v.y) {}\n}  // namespace clay\n\nusing Vec2 = clay::Vec2;\nusing Vec3 = clay::Vec3;\nusing Vec4 = clay::Vec4;\nusing Vector = clay::Vector;\n\n}  // namespace skity\n#endif  // INCLUDE_SKITY_GEOMETRY_VECTOR_HPP\n"
  },
  {
    "path": "clay/third_party/skity_geometry/include/skity/macros.hpp",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef INCLUDE_SKITY_MACROS_HPP\n#define INCLUDE_SKITY_MACROS_HPP\n\n#if defined(_WIN32) || defined(_WIN64)\n#define SKITY_WIN\n#elif defined(__APPLE__) || defined(__MACH__)\n#include <TargetConditionals.h>\n#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1\n#ifndef SKITY_IOS\n#define SKITY_IOS 1\n#endif\n#elif TARGET_OS_MAC == 1\n#ifndef SKITY_MACOS\n#define SKITY_MACOS 1\n#endif\n#endif\n#elif defined(__ANDROID__)\n#define SKITY_ANDROID\n#elif defined(__linux__)\n#define SKITY_LINUX\n#elif defined(__EMSCRIPTEN__) || defined(__wasm__) || defined(__wasm32__) || \\\n    defined(__wasm64__)\n#define SKITY_WASM\n#endif\n\n// wasm do not support shared library\n// iOS use cocoapods also not use shared library\n// all other platform has been changed to use shared library\n#if !defined(SKITY_WASM) && !defined(SKITY_IOS)\n#ifndef SKITY_DLL\n#define SKITY_DLL\n#endif\n#endif\n\n#ifdef SKITY_DLL\n\n#if defined(SKITY_WIN)\n#define SKITY_API\n#elif defined(_MSC_VER)\n#define SKITY_API __declspec(dllexport)\n#else\n#define SKITY_API __attribute__((visibility(\"default\")))\n#endif\n\n#else\n#define SKITY_API\n#endif\n\n#if defined(__ARM_NEON__) || defined(__arm__) || defined(__aarch64__)\n#define SKITY_ARM_NEON\n#endif\n\n#endif  // INCLUDE_SKITY_MACROS_HPP\n"
  },
  {
    "path": "clay/third_party/skity_geometry/src/geometry/glm_helper.hpp",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef SRC_GEOMETRY_GLM_HELPER_HPP\n#define SRC_GEOMETRY_GLM_HELPER_HPP\n\n#include <glm/glm.hpp>\n#include <skity/geometry/matrix.hpp>\n#include <skity/geometry/vector.hpp>\n\nnamespace skity {\ntemplate <typename O, typename I>\ninline const O& To(const I& input) {\n  static_assert(sizeof(I) == sizeof(O),\n                \"Input and output types must have the same size.\");\n  return reinterpret_cast<const O&>(input);\n}\n\ntemplate <typename O, typename I>\ninline O& To(I& input) {\n  static_assert(sizeof(I) == sizeof(O),\n                \"Input and output types must have the same size.\");\n  return reinterpret_cast<O&>(input);\n}\n\ninline const glm::vec2& ToGLM(const Vec2& v) { return To<glm::vec2>(v); }\n\ninline const glm::vec3& ToGLM(const Vec3& v) { return To<glm::vec3>(v); }\n\ninline const glm::vec4& ToGLM(const Vec4& v) { return To<glm::vec4>(v); }\n\ninline const glm::mat4& ToGLM(const Matrix& m) { return To<glm::mat4>(m); }\n\ninline glm::mat4& ToGLM(Matrix& m) { return To<glm::mat4>(m); }\n\ninline const Matrix& FromGLM(const glm::mat4& m) { return To<Matrix>(m); }\n\n}  // namespace skity\n\n#endif  // SRC_GEOMETRY_GLM_HELPER_HPP\n"
  },
  {
    "path": "clay/third_party/skity_geometry/src/geometry/math.hpp",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef SRC_GEOMETRY_MATH_HPP\n#define SRC_GEOMETRY_MATH_HPP\n\n#include <algorithm>\n#include <cmath>\n#include <glm/glm.hpp>\n#include <limits>\n#include <skity/geometry/scalar.hpp>\n#include <skity/geometry/vector.hpp>\n\nnamespace skity {\n\n#ifdef __GNUC__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunused-function\"\n#endif\n\nstatic inline Vec2 Times2(Vec2 const& value) { return value + value; }\n\ntemplate <class T>\nT Interp(T const& v0, T const& v1, T const& t) {\n  return v0 + (v1 - v0) * t;\n}\n\ntemplate <class T>\nfloat CrossProduct(T const& a, T const& b) {\n  return a.x * b.y - a.y * b.x;\n}\n\nenum class Orientation {\n  kLinear,\n  kClockWise,\n  kAntiClockWise,\n};\n\ntemplate <class T>\nOrientation CalculateOrientation(T const& p, T const& q, T const& r) {\n  float val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);\n\n  if (FloatNearlyZero(val, 0.001f)) {\n    return Orientation::kLinear;\n  }\n\n  return (val > 0) ? Orientation::kClockWise : Orientation::kAntiClockWise;\n}\n\ntemplate <class T>\nint32_t CrossProductResult(T const& p, T const& q, T const& r) {\n  return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);\n}\n\n//! Returns the number of leading zero bits (0...32)\n// From Hacker's Delight 2nd Edition\nconstexpr int CLZ(uint32_t x) {\n  int n = 32;\n\n  uint32_t y = x >> 16;\n  if (y != 0) {\n    n -= 16;\n    x = y;\n  }\n  y = x >> 8;\n  if (y != 0) {\n    n -= 8;\n    x = y;\n  }\n  y = x >> 4;\n  if (y != 0) {\n    n -= 4;\n    x = y;\n  }\n  y = x >> 2;\n  if (y != 0) {\n    n -= 2;\n    x = y;\n  }\n  y = x >> 1;\n  if (y != 0) {\n    return n - 2;\n  }\n\n  return n - static_cast<int>(x);\n}\n\ntemplate <typename T>\nconstexpr inline bool IsPow2(T value) {\n  return (value & (value - 1)) == 0;\n}\n\nconstexpr int NextLog2(uint32_t value) { return 32 - CLZ(value - 1); }\n\nconstexpr int NextPow2(int value) {\n  return 1 << NextLog2(static_cast<uint32_t>(value));\n}\n\n// Map 'value' to a larger multiple of 2. Values <= 'kMagicTol' will pop up to\n// the next power of 2. Those above 'kMagicTol' will only go up half the floor\n// power of 2.\ninline glm::uvec2 MakeApprox(glm::uvec2 dimensions) {\n  auto adjust = [](int value) {\n    static const int kMagicTol = 1024;\n\n    value = std::max(16, value);\n\n    if (IsPow2(value)) {\n      return value;\n    }\n\n    int ceil_pow2 = NextPow2(value);\n    if (value <= kMagicTol) {\n      return ceil_pow2;\n    }\n\n    int floor_pow2 = ceil_pow2 >> 1;\n    int mid = floor_pow2 + (floor_pow2 >> 1);\n\n    if (value <= mid) {\n      return mid;\n    }\n    return ceil_pow2;\n  };\n\n  return {adjust(dimensions.x), adjust(dimensions.y)};\n}\n\n#ifdef __GNUC__\n#pragma clang diagnostic pop\n#endif\n\n}  // namespace skity\n\n#endif  // SRC_GEOMETRY_MATH_HPP\n"
  },
  {
    "path": "clay/third_party/skity_geometry/src/geometry/matrix.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef GLM_ENABLE_EXPERIMENTAL\n#define GLM_ENABLE_EXPERIMENTAL\n#include \"glm/ext/matrix_float4x4.hpp\"\n#include \"skity/geometry/vector.hpp\"\n#endif\n\n#include <glm/ext/matrix_transform.hpp>\n#include <glm/ext/scalar_constants.hpp>\n#include <glm/gtx/matrix_query.hpp>\n#include <memory>\n#include <skity/geometry/matrix.hpp>\n#include <skity/geometry/rect.hpp>\n\n#include \"src/geometry/glm_helper.hpp\"\n#include \"src/geometry/math.hpp\"\n\nnamespace skity {\nnamespace clay {\n\n// static\nMatrix Matrix::RotateDeg(float deg) {\n  return Matrix::RotateRad(FloatDegreesToRadians(deg), {0, 0});\n}\n\n// static\nMatrix Matrix::RotateDeg(float deg, Vec2 pt) {\n  return RotateRad(FloatDegreesToRadians(deg), pt);\n}\n\n// static\nMatrix Matrix::RotateDeg(float deg, Vec3 axis) {\n  return FromGLM(\n      glm::rotate(glm::mat4(1.f), FloatDegreesToRadians(deg), ToGLM(axis)));\n}\n\n// static\nMatrix Matrix::RotateRad(float rad) {\n  return Matrix::RotateRad(rad, Vec2{0, 0});\n}\n\n// static\nMatrix Matrix::RotateRad(float rad, Vec2 pt) {\n  glm::mat4 d1(1.f);\n  d1[3][0] = -pt.x;\n  d1[3][1] = -pt.y;\n  glm::mat4 d2(1.f);\n  d2[3][0] = pt.x;\n  d2[3][1] = pt.y;\n  return FromGLM(glm::rotate(d2, rad, glm::vec3(0, 0, 1)) * d1);\n}\n\nbool Matrix::IsIdentity() const {\n  return glm::isIdentity(ToGLM(*this), glm::epsilon<float>());\n}\n\nbool Matrix::IsFinite() const {\n  float accum = 0;\n  for (int i = 0; i < 4; i++) {\n    for (int j = 0; j < 4; j++) {\n      accum *= e[i][j];\n    }\n  }\n\n  return !FloatIsNan(accum);\n}\n\nbool Matrix::IsSimilarity(float tol) const {\n  if (HasPersp()) {\n    return false;\n  }\n\n  float mx = GetScaleX();\n  float my = GetScaleY();\n  // if no skew, can just compare scale factors\n  if (GetSkewX() == 0 && GetSkewY() == 0) {\n    return !FloatNearlyZero(mx) && FloatNearlyZero(mx - my);\n  }\n  float sx = GetSkewX();\n  float sy = GetSkewY();\n\n  float perp_dot = mx * my - sx * sy;\n  if (FloatNearlyZero(perp_dot, kNearlyZero * kNearlyZero)) {\n    return false;\n  }\n\n  // upper 2x2 is rotation/reflection + uniform scale if basis vectors\n  // are 90 degree rotations of each other\n  return (FloatNearlyZero(mx - my, tol) && FloatNearlyZero(sx + sy, tol)) ||\n         (FloatNearlyZero(mx + my, tol) && FloatNearlyZero(sx - sy, tol));\n}\n\nMatrix& Matrix::Set9(const float buffer[9]) {\n  // buffer is in row major, Matrix is in column major\n  *this = Matrix{\n      buffer[0], buffer[3], 0, buffer[6],  //\n      buffer[1], buffer[4], 0, buffer[7],  //\n      0,         0,         1, 0,          //\n      buffer[2], buffer[5], 0, buffer[8],  //\n  };\n  return *this;\n}\n\nvoid Matrix::Get9(float buffer[9]) const {\n  buffer[0] = GetScaleX();\n  buffer[1] = GetSkewX();\n  buffer[2] = GetTranslateX();\n  buffer[3] = GetSkewY();\n  buffer[4] = GetScaleY();\n  buffer[5] = GetTranslateY();\n  buffer[6] = GetPersp0();\n  buffer[7] = GetPersp1();\n  buffer[8] = GetPersp2();\n}\n\nMatrix& Matrix::Set(int row, int column, float value) {\n  if (row < 0 || row > 3 || column < 0 || column > 3) {\n    return *this;\n  }\n  Matrix& m = *this;\n  m[column][row] = value;\n  return *this;\n}\n\nfloat Matrix::Get(int row, int column) const {\n  if (row < 0 || row > 3 || column < 0 || column > 3) {\n    return 0.f;\n  }\n  const Matrix& m = *this;\n  return m[column][row];\n}\n\nbool Matrix::Invert(Matrix* inverse) const {\n  if (IsIdentity()) {\n    if (inverse != nullptr) {\n      *inverse = Matrix(1.0);\n    }\n    return true;\n  }\n  return this->InvertNonIdentity(inverse);\n}\n\nfloat Matrix::Determinant() const {\n  if (IsIdentity()) {\n    return 1.f;\n  }\n  const Matrix& m = *this;\n  if (OnlyScaleAndTranslate()) {\n    return m[0][0] * m[1][1] * m[2][2] * m[3][3];\n  }\n  return glm::determinant(ToGLM(*this));\n}\n\nvoid Matrix::Transpose() { *this = FromGLM(glm::transpose(ToGLM(*this))); }\n\nbool Matrix::OnlyScaleAndTranslate() const {\n  auto& m = *this;\n  // No z\n  if (m[0][2] != 0 || m[1][2] != 0 || m[2][2] != 1 || m[3][2] != 0 ||\n      m[2][0] != 0 || m[2][1] != 0) {\n    return false;\n  }\n  // no persp\n  if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0 || m[3][3] != 1) {\n    return false;\n  }\n  // no skew\n  if (m[0][1] != 0 || m[1][0] != 0) {\n    return false;\n  }\n  return true;\n}\n\nbool Matrix::OnlyTranslate() const {\n  auto& m = *this;\n  // No Scale\n  if (m[0][0] != 1.f || m[1][1] != 1.f) {\n    return false;\n  }\n  // No z\n  if (m[0][2] != 0 || m[1][2] != 0 || m[2][2] != 1 || m[3][2] != 0 ||\n      m[2][0] != 0 || m[2][1] != 0) {\n    return false;\n  }\n  // no persp\n  if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0 || m[3][3] != 1) {\n    return false;\n  }\n  // no skew\n  if (m[0][1] != 0 || m[1][0] != 0) {\n    return false;\n  }\n  return true;\n}\n\nbool Matrix::OnlyScale() const {\n  auto& m = *this;\n  // No translate\n  if (m[3][0] != 0 || m[3][1] != 0) {\n    return false;\n  }\n  // No z\n  if (m[0][2] != 0 || m[1][2] != 0 || m[2][2] != 1 || m[3][2] != 0 ||\n      m[2][0] != 0 || m[2][1] != 0) {\n    return false;\n  }\n  // no persp\n  if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0 || m[3][3] != 1) {\n    return false;\n  }\n  // no skew\n  if (m[0][1] != 0 || m[1][0] != 0) {\n    return false;\n  }\n  return true;\n}\n\nbool Matrix::HasPersp() const {\n  const Matrix& m = *this;\n  if (m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0 || m[3][3] != 1) {\n    return true;\n  }\n  return false;\n}\n\nbool Matrix::InvertNonIdentity(Matrix* inverse) const {\n  Matrix temp_inverse;\n  Matrix* p_inverse =\n      (inverse == nullptr || inverse == this) ? &temp_inverse : inverse;\n\n  // short path\n  if (OnlyScaleAndTranslate()) {\n    static_cast<Matrix*>(p_inverse)->Reset();\n    if (GetScaleX() != 1.f || GetScaleY() != 1.f) {\n      float inverse_x = SkityIEEEFloatDivided(1.f, GetScaleX());\n      float inverse_y = SkityIEEEFloatDivided(1.f, GetScaleY());\n      if (FloatInfinity == inverse_x || FloatInfinity == inverse_y) {\n        return false;\n      }\n      p_inverse->SetScaleX(inverse_x);\n      p_inverse->SetScaleY(inverse_y);\n      p_inverse->SetTranslateX(-GetTranslateX() * inverse_x);\n      p_inverse->SetTranslateY(-GetTranslateY() * inverse_y);\n    } else {\n      p_inverse->SetTranslateX(-GetTranslateX());\n      p_inverse->SetTranslateY(-GetTranslateY());\n    }\n    if (inverse == this) {\n      *inverse = *p_inverse;\n    }\n    return true;\n  }\n\n  if (FloatNearlyZero(glm::determinant(ToGLM(*this)))) {\n    return false;\n  }\n\n  *p_inverse = FromGLM(glm::inverse(ToGLM(*this)));\n  if (inverse == this) {\n    *inverse = *p_inverse;\n  }\n  return true;\n}\n\nvoid Matrix::MapPoints(Vec2 dst[], const Vec2 src[], int count) const {\n  if (dst == nullptr || src == nullptr || count <= 0) {\n    return;\n  }\n  for (int i = 0; i < count; ++i) {\n    auto v = Vec4(src[i].x, src[i].y, 0.f, 1.f);\n    auto r = (*this) * v;\n    float w = r.w;\n    if (w != 0) {\n      w = 1.f / w;\n    }\n    dst[i].x = r.x * w;\n    dst[i].y = r.y * w;\n  }\n}\n\nvoid Matrix::MapPoints(Point dst[], const Point src[], int count) const {\n  if (dst == nullptr || src == nullptr || count <= 0) {\n    return;\n  }\n  for (int i = 0; i < count; ++i) {\n    dst[i] = (*this) * src[i];\n  }\n}\n\nbool Matrix::MapRect(Rect* dst, const Rect& src) const {\n  if (dst == nullptr) {\n    return false;\n  }\n\n  Vec4 src_quad[4] = {{src.Left(), src.Top(), 0.f, 1.f},\n                      {src.Right(), src.Top(), 0.f, 1.f},\n                      {src.Right(), src.Bottom(), 0.f, 1.f},\n                      {src.Left(), src.Bottom(), 0.f, 1.f}};\n  Vec4 dst_quad[4];\n  for (size_t i = 0; i < 4; ++i) {\n    dst_quad[i] = (*this) * src_quad[i];\n    float w = dst_quad[i].w;\n    if (w != 0) {\n      w = 1.0f / w;\n    }\n    dst_quad[i] = dst_quad[i] * w;\n  }\n  float left = dst_quad[0].x;\n  float right = dst_quad[0].x;\n  float top = dst_quad[0].y;\n  float bottom = dst_quad[0].y;\n  for (size_t i = 1; i < 4; i++) {\n    left = std::min(dst_quad[i].x, left);\n    right = std::max(dst_quad[i].x, right);\n    top = std::min(dst_quad[i].y, top);\n    bottom = std::max(dst_quad[i].y, bottom);\n  }\n  dst->SetLTRB(left, top, right, bottom);\n\n  return RectStaysRect();\n}\n\nRect Matrix::MapRect(const Rect& src) const {\n  Rect dst;\n  MapRect(&dst, src);\n  return dst;\n}\n\nbool Matrix::RectStaysRect() const {\n  auto& m = *this;\n  if (m[0][2] != 0 || m[1][2] != 0 || m[2][2] != 1 || m[3][2] != 0 ||\n      m[0][3] != 0 || m[1][3] != 0 || m[2][3] != 0 || m[3][3] != 1) {\n    return false;\n  }\n\n  if (m[0][1] != 0 || m[1][0] != 0) {\n    return m[0][0] == 0 && m[1][1] == 0 && m[1][0] != 0 && m[0][1] != 0;\n  } else {\n    return m[0][0] != 0 && m[1][1] != 0;\n  }\n}\n\nMatrix& Matrix::PreConcat(const Matrix& other) {\n  if (!other.IsIdentity()) {\n    SetConcat(*this, other);\n  }\n  return *this;\n}\n\nMatrix& Matrix::PostConcat(const Matrix& other) {\n  if (!other.IsIdentity()) {\n    SetConcat(other, *this);\n  }\n  return *this;\n}\n\nMatrix& Matrix::PreTranslate(float dx, float dy) {\n  Matrix m = Matrix::Translate(dx, dy);\n  SetConcat(*this, m);\n  return *this;\n}\n\nMatrix& Matrix::PostTranslate(float dx, float dy) {\n  Matrix m = Matrix::Translate(dx, dy);\n  SetConcat(m, *this);\n  return *this;\n}\n\nMatrix& Matrix::PreScale(float sx, float sy) {\n  Matrix m = Matrix::Scale(sx, sy);\n  SetConcat(*this, m);\n  return *this;\n}\n\nMatrix& Matrix::PostScale(float sx, float sy) {\n  Matrix m = Matrix::Scale(sx, sy);\n  SetConcat(m, *this);\n  return *this;\n}\n\nMatrix& Matrix::PreScale(float sx, float sy, float px, float py) {\n  Matrix d1 = Matrix::Translate(-px, -py);\n  Matrix s = Matrix::Scale(sx, sy);\n  Matrix d2 = Matrix::Translate(px, py);\n  auto result = *this * d2 * s * d1;\n  *this = result;\n  return *this;\n}\n\nMatrix& Matrix::PreRotate(float degrees) {\n  return this->PreRotate(degrees, 0, 0);\n}\n\nMatrix& Matrix::PostRotate(float degrees) {\n  return this->PostRotate(degrees, 0, 0);\n}\n\nMatrix& Matrix::PreRotate(float degrees, float px, float py) {\n  auto r = Matrix::RotateDeg(degrees, Vec2(px, py));\n  return this->PreConcat(r);\n}\n\nMatrix& Matrix::PostRotate(float degrees, float px, float py) {\n  auto r = Matrix::RotateDeg(degrees, Vec2(px, py));\n  return this->PostConcat(r);\n}\n\nMatrix& Matrix::PostSkew(float kx, float ky) {\n  Matrix m = Matrix::Skew(kx, ky);\n  return this->PostConcat(m);\n}\n\n// Notice: we ignore element related z.\nMatrix& Matrix::SetConcat(const Matrix& left, const Matrix& right) {\n  if (left.IsIdentity()) {\n    *this = right;\n  } else if (right.IsIdentity()) {\n    *this = left;\n  } else if (left.OnlyScaleAndTranslate() && right.OnlyScaleAndTranslate()) {\n    float scale_x = left.GetScaleX() * right.GetScaleX();\n    float scale_y = left.GetScaleY() * right.GetScaleY();\n    float tranlate_x =\n        left.GetScaleX() * right.GetTranslateX() + left.GetTranslateX();\n    float tranlate_y =\n        left.GetScaleY() * right.GetTranslateY() + left.GetTranslateY();\n    this->SetScaleX(scale_x);\n    this->SetScaleY(scale_y);\n    this->SetTranslateX(tranlate_x);\n    this->SetTranslateY(tranlate_y);\n  } else {\n    *this = left * right;\n  }\n  return *this;\n}\n\nSKITY_API Matrix operator*(const Matrix& a, const Matrix& b) {\n  if (a.IsIdentity()) {\n    return b;\n  }\n  if (b.IsIdentity()) {\n    return a;\n  }\n  return FromGLM(ToGLM(a) * ToGLM(b));\n}\n\nVec4 operator*(const Matrix& m, const Vec4& v) {\n  glm::vec4 r = ToGLM(m) * ToGLM(v);\n  return Vec4{r.x, r.y, r.z, r.w};\n}\n\n}  // namespace clay\n}  // namespace skity\n"
  },
  {
    "path": "clay/third_party/skity_geometry/src/geometry/rect.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <algorithm>\n#include <skity/geometry/rect.hpp>\n\n#include \"src/geometry/math.hpp\"\n\nnamespace skity {\nnamespace clay {\n\nbool Rect::SetBoundsCheck(const Point* pts, int count) {\n  if (count <= 0) {\n    SetEmpty();\n    return true;\n  }\n\n  Vec2 min, max;\n  if (count & 1) {\n    min = max = {pts->x, pts->y};\n    pts += 1;\n    count -= 1;\n  } else {\n    min = Vec2{Vec4::Min(pts[0], pts[1])};\n    max = Vec2{Vec4::Max(pts[0], pts[1])};\n    pts += 2;\n    count -= 2;\n  }\n\n  Vec2 accum = min * 0.f;\n  while (count) {\n    Vec2 x = Vec2{pts[0]};\n    Vec2 y = Vec2{pts[1]};\n    accum *= x;\n    accum *= y;\n    min = Vec2::Min(min, Vec2::Min(x, y));\n    max = Vec2::Max(max, Vec2::Max(x, y));\n    pts += 2;\n    count -= 2;\n  }\n\n  accum *= 0.f;\n  bool all_finite = !glm::isinf(accum.x) && !glm::isinf(accum.y);\n  if (all_finite) {\n    this->SetLTRB(min.x, min.y, max.x, max.y);\n  } else {\n    this->SetEmpty();\n  }\n  return all_finite;\n}\n\nfloat Rect::CenterX() const { return FloatHalf * (right_ + left_); }\n\nfloat Rect::CenterY() const { return FloatHalf * (top_ + bottom_); }\n\nvoid Rect::ToQuad(Point quad[4]) const {\n  quad[0] = {left_, top_, 0, 0};\n  quad[1] = {right_, top_, 0, 0};\n  quad[2] = {right_, bottom_, 0, 0};\n  quad[3] = {left_, bottom_, 0, 0};\n}\n\nvoid Rect::SetX(float x) {\n  auto width = Width();\n  left_ = x;\n  right_ = x + width;\n}\n\nvoid Rect::SetY(float y) {\n  auto height = Height();\n  top_ = y;\n  bottom_ = y + height;\n}\n\nvoid Rect::SetLeft(float left) { left_ = left; }\n\nvoid Rect::SetTop(float top) { top_ = top; }\n\nvoid Rect::SetRight(float right) { right_ = right; }\n\nvoid Rect::SetBottom(float bottom) { bottom_ = bottom; }\n\nvoid Rect::SetWH(float width, float height) {\n  this->SetXYWH(0, 0, width, height);\n}\n\nvoid Rect::Offset(float dx, float dy) {\n  left_ += dx;\n  top_ += dy;\n  right_ += dx;\n  bottom_ += dy;\n}\n\nvoid Rect::Inset(float inset) { Inset(inset, inset); }\n\nvoid Rect::Inset(float dx, float dy) {\n  left_ += dx;\n  top_ += dy;\n  right_ -= dx;\n  bottom_ -= dy;\n}\n\nvoid Rect::Outset(float outset) { Outset(outset, outset); }\n\nvoid Rect::Outset(float dx, float dy) { Inset(-dx, -dy); }\n\nvoid Rect::RoundOut() {\n  left_ = floorf(left_);\n  top_ = floorf(top_);\n  right_ = ceilf(right_);\n  bottom_ = ceilf(bottom_);\n}\n\nvoid Rect::RoundIn() {\n  left_ = ceilf(left_);\n  top_ = ceilf(top_);\n  right_ = floorf(right_);\n  bottom_ = floorf(bottom_);\n}\n\nvoid Rect::Round() {\n  left_ = roundf(left_);\n  top_ = roundf(top_);\n  right_ = roundf(right_);\n  bottom_ = roundf(bottom_);\n}\n\nRect Rect::MakeSorted() const {\n  return MakeLTRB(std::min(left_, right_), std::min(top_, bottom_),\n                  std::max(left_, right_), std::max(top_, bottom_));\n}\n\nRect Rect::MakeOffset(float dx, float dy) const {\n  return MakeLTRB(left_ + dx, top_ + dy, right_ + dx, bottom_ + dy);\n}\n\nRect Rect::MakeInset(float dx, float dy) const {\n  return MakeLTRB(left_ + dx, top_ + dy, right_ - dx, bottom_ - dy);\n}\n\nRect Rect::MakeOutset(float dx, float dy) const {\n  return MakeLTRB(left_ - dx, top_ - dy, right_ + dx, bottom_ + dy);\n}\n\nvoid Rect::Join(const Rect& r) {\n  if (r.IsEmpty()) {\n    return;\n  }\n\n  if (this->IsEmpty()) {\n    *this = r;\n  } else {\n    left_ = std::min(left_, r.left_);\n    top_ = std::min(top_, r.top_);\n    right_ = std::max(right_, r.right_);\n    bottom_ = std::max(bottom_, r.bottom_);\n  }\n}\n\nbool Rect::Intersect(Rect const& rect) {\n  float l = std::max(left_, rect.left_);\n  float r = std::min(right_, rect.right_);\n  float t = std::max(top_, rect.top_);\n  float b = std::min(bottom_, rect.bottom_);\n\n  if (!(l < r && t < b)) {\n    return false;\n  }\n\n  SetLTRB(l, t, r, b);\n  return true;\n}\n\nbool Rect::IsFinite() const {\n  float accum = 0;\n  accum *= left_;\n  accum *= top_;\n  accum *= right_;\n  accum *= bottom_;\n\n  return !FloatIsNan(accum);\n}\n\nfloat Rect::HalfWidth(Rect const& rect) {\n  return rect.right_ * FloatHalf - rect.left_ * FloatHalf;\n}\n\nfloat Rect::HalfHeight(Rect const& rect) {\n  return rect.bottom_ * FloatHalf - rect.top_ * FloatHalf;\n}\n\nbool Rect::Intersect(const Rect& a, const Rect& b) {\n  float left = std::max(a.Left(), b.Left());\n  float top = std::max(a.Top(), b.Top());\n  float right = std::min(a.Right(), b.Right());\n  float bottom = std::min(a.Bottom(), b.Bottom());\n\n  auto tmp = Rect::MakeLTRB(left, top, right, bottom);\n\n  return !tmp.IsEmpty();\n}\n\n}  // namespace clay\n}  // namespace skity\n"
  },
  {
    "path": "clay/third_party/skity_geometry/src/geometry/rrect.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cassert>\n#include <skity/geometry/rrect.hpp>\n\n#include \"src/geometry/math.hpp\"\n\nnamespace skity {\nnamespace clay {\n\nstatic bool are_radius_check_predicates_valid(float rad, float min, float max) {\n  return (min <= max) && (rad <= max - min) && (min + rad <= max) &&\n         (max - rad >= min) && rad >= 0;\n}\n\nRRect::Type RRect::GetType() const { return type_; }\n\nvoid RRect::SetRect(Rect const& rect) {\n  if (!this->InitializeRect(rect)) {\n    return;\n  }\n\n  this->radii_[0] = {0, 0};\n  this->radii_[1] = {0, 0};\n  this->radii_[2] = {0, 0};\n  this->radii_[3] = {0, 0};\n\n  this->type_ = Type::kRect;\n\n  assert(this->IsValid());\n}\nnamespace {\nfloat CalculateMinScale(float r1, float r2, float limit, float curr_scale) {\n  if (r1 + r2 > limit) {\n    return std::min(curr_scale, SkityIEEEFloatDivided(limit, (r1 + r2)));\n  }\n  return curr_scale;\n}\n}  // namespace\n\nbool RRect::ScaleRadii() {\n  float scale = 1.0f;\n  scale =\n      CalculateMinScale(radii_[0].x, radii_[1].x, rect_.Width(), scale);  // Top\n  scale = CalculateMinScale(radii_[1].y, radii_[2].y, rect_.Height(),\n                            scale);  // Right\n  scale = CalculateMinScale(radii_[2].x, radii_[3].x, rect_.Width(),\n                            scale);  // Bottom\n  scale = CalculateMinScale(radii_[3].y, radii_[0].y, rect_.Height(),\n                            scale);  // Left\n  if (scale < 1.0f) {\n    radii_[0] *= scale;\n    radii_[1] *= scale;\n    radii_[2] *= scale;\n    radii_[3] *= scale;\n  }\n\n  return scale < 1.0f;\n}\n\nvoid RRect::SetRectRadii(Rect const& rect, const Vec2 radii[4]) {\n  if (!this->InitializeRect(rect)) {\n    return;\n  }\n  this->radii_[0] = radii[0];\n  this->radii_[1] = radii[1];\n  this->radii_[2] = radii[2];\n  this->radii_[3] = radii[3];\n\n  ScaleRadii();\n\n  this->type_ = Type::kComplex;\n}\n\nvoid RRect::SetRectXY(Rect const& rect, float xRad, float yRad) {\n  if (!this->InitializeRect(rect)) {\n    return;\n  }\n\n  if (!FloatIsFinite(xRad) && !FloatIsFinite(yRad)) {\n    xRad = yRad = 0;\n  }\n\n  if (rect_.Width() < xRad + xRad || rect_.Height() < yRad + yRad) {\n    float scale = std::min(SkityIEEEFloatDivided(rect_.Width(), xRad + xRad),\n                           SkityIEEEFloatDivided(rect_.Height(), yRad + yRad));\n    assert(scale < Float1);\n    xRad *= scale;\n    yRad *= scale;\n  }\n\n  if (xRad <= 0 || yRad <= 0) {\n    this->SetRect(rect);\n    return;\n  }\n\n  for (int32_t i = 0; i < 4; i++) {\n    radii_[i].x = xRad;\n    radii_[i].y = yRad;\n  }\n\n  type_ = Type::kSimple;\n\n  if (xRad >= FloatHalf * rect_.Width() && yRad >= FloatHalf * rect_.Height()) {\n    type_ = Type::kOval;\n  }\n\n  assert(this->IsValid());\n}\n\nvoid RRect::SetOval(Rect const& oval) {\n  if (!this->InitializeRect(oval)) {\n    return;\n  }\n\n  float x_rad = Rect::HalfWidth(rect_);\n  float y_rad = Rect::HalfHeight(rect_);\n\n  if (x_rad == 0.f || y_rad == 0.f) {\n    type_ = kRect;\n  } else {\n    for (int32_t i = 0; i < 4; i++) {\n      radii_[i].x = x_rad;\n      radii_[i].y = y_rad;\n    }\n    type_ = kOval;\n  }\n\n  assert(this->IsValid());\n}\n\nbool RRect::IsValid() const {\n  if (!AreRectAndRadiiValid(rect_, radii_)) {\n    return false;\n  }\n\n  bool all_radii_zero = (0 == radii_[0].x && 0 == radii_[0].y);\n  bool all_corners_square = (0 == radii_[0].x && 0 == radii_[0].y);\n  bool all_radii_same = true;\n\n  for (int32_t i = 1; i < 4; i++) {\n    if (0 != radii_[i].x || 0 != radii_[i].y) {\n      all_radii_zero = false;\n    }\n\n    if (radii_[i].x != radii_[i - 1].x || radii_[i].y != radii_[i - 1].y) {\n      all_radii_same = false;\n    }\n\n    if (0 != radii_[i].x && 0 != radii_[i].y) {\n      all_corners_square = false;\n    }\n  }\n\n  // TODO(tangruiwen) support nine patch\n\n  if (static_cast<int32_t>(type_) < 0 ||\n      static_cast<int32_t>(type_) > kLastType) {\n    return false;\n  }\n\n  switch (type_) {\n    case Type::kEmpty:\n      if (!rect_.IsEmpty() || !all_radii_zero || !all_radii_same ||\n          !all_corners_square) {\n        return false;\n      }\n      break;\n    case Type::kRect:\n      if (rect_.IsEmpty() || !all_radii_zero || !all_radii_same ||\n          !all_corners_square) {\n        return false;\n      }\n      break;\n    case Type::kOval:\n      if (rect_.IsEmpty() || all_radii_zero || !all_radii_same ||\n          all_corners_square) {\n        return false;\n      }\n      for (int32_t i = 0; i < 4; i++) {\n        if (!FloatNearlyZero(radii_[i].x, Rect::HalfWidth(rect_)) ||\n            !FloatNearlyZero(radii_[i].y, Rect::HalfHeight(rect_))) {\n          return false;\n        }\n      }\n      break;\n    case Type::kSimple:\n      if (rect_.IsEmpty() || all_radii_zero || !all_radii_same ||\n          all_corners_square) {\n        return false;\n      }\n      break;\n    case Type::kNinePatch:\n      return false;\n    case Type::kComplex:\n      if (rect_.IsEmpty() || all_radii_zero || all_radii_same ||\n          all_corners_square) {\n        return false;\n      }\n      break;\n  }\n\n  return true;\n}\n\nbool RRect::AreRectAndRadiiValid(Rect const& rect,\n                                 std::array<Vec2, 4> const& radii) {\n  if (!rect.IsFinite() || !rect.IsSorted()) {\n    return false;\n  }\n\n  for (int32_t i = 0; i < 4; i++) {\n    if (!are_radius_check_predicates_valid(radii[i].x, rect.Left(),\n                                           rect.Right()) ||\n        !are_radius_check_predicates_valid(radii[i].y, rect.Top(),\n                                           rect.Bottom())) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nRRect RRect::MakeOffset(float dx, float dy) const {\n  RRect rr;\n  rr.SetRectRadii(rect_.MakeOffset(dx, dy), radii_.data());\n  return rr;\n}\n\nRRect RRect::MakeEmpty() { return RRect(); }\n\nRRect RRect::MakeRect(Rect const& rect) {\n  RRect rr;\n  rr.SetRect(rect);\n  return rr;\n}\n\nRRect RRect::MakeRectXY(Rect const& rect, float xRad, float yRad) {\n  RRect rr;\n  rr.SetRectXY(rect, xRad, yRad);\n  return rr;\n}\n\nRRect RRect::MakeOval(Rect const& oval) {\n  RRect rr;\n  rr.SetOval(oval);\n  return rr;\n}\n\nbool RRect::InitializeRect(Rect const& rect) {\n  // Check this before sorting because sorting can hide nans.\n  if (!rect.IsFinite()) {\n    *this = RRect();\n    return false;\n  }\n\n  rect_ = rect.MakeSorted();\n  if (rect_.IsEmpty()) {\n    type_ = Type::kEmpty;\n    return false;\n  }\n\n  return true;\n}\n\nvoid RRect::Inset(float dx, float dy, RRect* dst) const {\n  Rect r = rect_;\n  r.Inset(dx, dy);\n\n  bool degenerate = false;\n  if (r.Right() <= r.Left()) {\n    degenerate = true;\n    r.left_ = r.right_ = (r.Left() + r.Right()) * 0.5f;\n  }\n  if (r.Bottom() <= r.Top()) {\n    degenerate = true;\n    r.top_ = r.bottom_ = (r.Top() + r.Bottom()) * 0.5f;\n  }\n  if (degenerate) {\n    dst->rect_ = r;\n    dst->radii_ = {};\n    dst->type_ = Type::kEmpty;\n    return;\n  }\n  if (!r.IsFinite()) {\n    *dst = RRect();\n    return;\n  }\n\n  std::array<Vec2, 4> radii = radii_;\n  for (int i = 0; i < 4; ++i) {\n    if (radii[i].x) {\n      radii[i].x -= dx;\n    }\n    if (radii[i].y) {\n      radii[i].y -= dy;\n    }\n  }\n  dst->rect_ = r;\n  dst->radii_ = radii;\n  dst->type_ = Type::kComplex;\n}\n\nbool RRect::Contains(const Rect& rect) const {\n  if (!rect_.Contains(rect)) {\n    return false;\n  }\n\n  if (IsRect()) {\n    return true;\n  }\n\n  return CheckCornerContainment(rect.Left(), rect.Top()) &&\n         CheckCornerContainment(rect.Right(), rect.Top()) &&\n         CheckCornerContainment(rect.Right(), rect.Bottom()) &&\n         CheckCornerContainment(rect.Left(), rect.Bottom());\n}\n\nbool RRect::CheckCornerContainment(float x, float y) const {\n  Vec2 canonical_pt;\n  int index;\n\n  if (kOval == GetType()) {\n    canonical_pt = {x - rect_.CenterX(), y - rect_.CenterY()};\n    index = kUpperLeft;\n  } else {\n    if (x < rect_.Left() + radii_[kUpperLeft].x &&\n        y < rect_.Top() + radii_[kUpperLeft].y) {\n      // UL corner\n      index = kUpperLeft;\n      canonical_pt = {x - (rect_.Left() + radii_[kUpperLeft].x),\n                      y - (rect_.Top() + radii_[kUpperLeft].y)};\n    } else if (x < rect_.Left() + radii_[kLowerLeft].x &&\n               y > rect_.Bottom() - radii_[kLowerLeft].y) {\n      // LL corner\n      index = kLowerLeft;\n      canonical_pt = {x - (rect_.Left() + radii_[kLowerLeft].x),\n                      y - (rect_.Bottom() - radii_[kLowerLeft].y)};\n    } else if (x > rect_.Right() - radii_[kUpperRight].x &&\n               y < rect_.Top() + radii_[kUpperRight].y) {\n      // UR corner\n      index = kUpperRight;\n      canonical_pt = {x - (rect_.Right() - radii_[kUpperRight].x),\n                      y - (rect_.Top() + radii_[kUpperRight].y)};\n    } else if (x > rect_.Right() - radii_[kLowerRight].x &&\n               y > rect_.Bottom() - radii_[kLowerRight].y) {\n      // LR corner\n      index = kLowerRight;\n      canonical_pt = {x - (rect_.Right() - radii_[kLowerRight].x),\n                      y - (rect_.Bottom() - radii_[kLowerRight].y)};\n    } else {\n      // not in any of the corners\n      return true;\n    }\n  }\n\n  // A point is in an ellipse (in standard position) if:\n  //      x^2     y^2\n  //     ----- + ----- <= 1\n  //      a^2     b^2\n  // or :\n  //     b^2*x^2 + a^2*y^2 <= (ab)^2\n  float dist = std::sqrtf(canonical_pt.x) * std::sqrtf(radii_[index].y) +\n               std::sqrtf(canonical_pt.y) * std::sqrtf(radii_[index].x);\n  return dist <= std::sqrtf(radii_[index].x * radii_[index].y);\n}\n\n}  // namespace clay\n}  // namespace skity\n"
  },
  {
    "path": "clay/third_party/txt/.clang-format",
    "content": "# Defines the Chromium style for automatic reformatting.\n# http://clang.llvm.org/docs/ClangFormatStyleOptions.html\nBasedOnStyle: Chromium\n# This defaults to 'Auto'. Explicitly set it for a while, so that\n# 'vector<vector<int> >' in existing files gets formatted to\n# 'vector<vector<int>>'. ('Auto' means that clang-format will only use\n# 'int>>' if the file already contains at least one such instance.)\nStandard: Cpp11\n"
  },
  {
    "path": "clay/third_party/txt/.gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n*      text=auto\n\n# Always perform LF normalization on these files\n*.c    text\n*.cc   text\n*.cpp  text\n*.h    text\n*.gn   text\n*.md   text\n"
  },
  {
    "path": "clay/third_party/txt/.gitignore",
    "content": "*.pyc\n*~\n.*.sw?\n.DS_Store\n.classpath\n.cproject\n.gdb_history\n.gdbinit\n.landmines\n.project\n.pydevproject\n.checkstyle\n.vscode\ncscope.*\nSession.vim\ntags\nThumbs.db\n"
  },
  {
    "path": "clay/third_party/txt/BUILD.gn",
    "content": "# Copyright 2017 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport(\"../../common/config.gni\")\nimport(\"../../testing/testing.gni\")\n\ndeclare_args() {\n  flutter_use_fontconfig = false\n}\n\nif (flutter_use_fontconfig) {\n  assert(is_linux)\n}\n\nconfig(\"txt_config\") {\n  visibility = [ \":*\" ]\n  include_dirs = [\n    \"src\",\n  ]\n}\n\nconfig(\"allow_posix_names\") {\n  if (is_win && is_clang) {\n    # POSIX names of many functions are marked deprecated by default;\n    # disable that since they are used for cross-platform compatibility.\n    defines = [ \"_CRT_NONSTDC_NO_DEPRECATE\" ]\n  }\n}\n\nconfig(\"define_skshaper\") {\n  if (clay_enable_skshaper) {\n    defines = [ \"CLAY_ENABLE_SKSHAPER\" ]\n  }\n}\n\nconfig(\"define_minikin\") {\n  if (clay_enable_minikin) {\n    defines = [ \"CLAY_ENABLE_MINIKIN\" ]\n  }\n}\n\nsource_set(\"txt\") {\n  defines = []\n  if (flutter_use_fontconfig) {\n    defines += [ \"FLUTTER_USE_FONTCONFIG\" ]\n  }\n\n  sources = [\n    \"src/log/log.cc\",\n    \"src/log/log.h\",\n    \"src/minikin/CmapCoverage.cpp\",\n    \"src/minikin/CmapCoverage.h\",\n    \"src/minikin/Emoji.cpp\",\n    \"src/minikin/Emoji.h\",\n    \"src/minikin/FontCollection.cpp\",\n    \"src/minikin/FontCollection.h\",\n    \"src/minikin/FontFamily.cpp\",\n    \"src/minikin/FontFamily.h\",\n    \"src/minikin/FontLanguage.cpp\",\n    \"src/minikin/FontLanguage.h\",\n    \"src/minikin/FontLanguageListCache.cpp\",\n    \"src/minikin/FontLanguageListCache.h\",\n    \"src/minikin/FontUtils.cpp\",\n    \"src/minikin/FontUtils.h\",\n    \"src/minikin/GraphemeBreak.cpp\",\n    \"src/minikin/GraphemeBreak.h\",\n    \"src/minikin/HbFontCache.cpp\",\n    \"src/minikin/HbFontCache.h\",\n    \"src/minikin/Hyphenator.cpp\",\n    \"src/minikin/Hyphenator.h\",\n    \"src/minikin/Layout.cpp\",\n    \"src/minikin/Layout.h\",\n    \"src/minikin/LayoutUtils.cpp\",\n    \"src/minikin/LayoutUtils.h\",\n    \"src/minikin/LineBreaker.cpp\",\n    \"src/minikin/LineBreaker.h\",\n    \"src/minikin/Measurement.cpp\",\n    \"src/minikin/Measurement.h\",\n    \"src/minikin/MinikinFont.cpp\",\n    \"src/minikin/MinikinFont.h\",\n    \"src/minikin/MinikinInternal.cpp\",\n    \"src/minikin/MinikinInternal.h\",\n    \"src/minikin/SparseBitSet.cpp\",\n    \"src/minikin/SparseBitSet.h\",\n    \"src/minikin/WordBreaker.cpp\",\n    \"src/minikin/WordBreaker.h\",\n    \"src/txt/font_asset_provider.cc\",\n    \"src/txt/font_asset_provider.h\",\n    \"src/txt/font_features.cc\",\n    \"src/txt/font_features.h\",\n    \"src/txt/font_skia.cc\",\n    \"src/txt/font_skia.h\",\n    \"src/txt/font_style.h\",\n    \"src/txt/font_weight.h\",\n    \"src/txt/line_metrics.h\",\n    \"src/txt/paragraph.h\",\n    \"src/txt/paragraph_builder.cc\",\n    \"src/txt/paragraph_builder.h\",\n    \"src/txt/paragraph_builder_txt.cc\",\n    \"src/txt/paragraph_builder_txt.h\",\n    \"src/txt/paragraph_style.cc\",\n    \"src/txt/paragraph_style.h\",\n    \"src/txt/placeholder_run.cc\",\n    \"src/txt/placeholder_run.h\",\n    \"src/txt/platform.h\",\n    \"src/txt/styled_runs.cc\",\n    \"src/txt/styled_runs.h\",\n    \"src/txt/text_baseline.h\",\n    \"src/txt/text_decoration.cc\",\n    \"src/txt/text_decoration.h\",\n    \"src/txt/text_shadow.cc\",\n    \"src/txt/text_shadow.h\",\n    \"src/txt/text_style.cc\",\n    \"src/txt/text_style.h\",\n    \"src/utils/JenkinsHash.cpp\",\n    \"src/utils/JenkinsHash.h\",\n    \"src/utils/LinuxUtils.h\",\n    \"src/utils/LruCache.h\",\n    \"src/utils/MacUtils.h\",\n    \"src/utils/TypeHelpers.h\",\n    \"src/utils/WindowsUtils.h\",\n  ]\n\n  include_dirs = [\n    \"src\",\n  ]\n  deps = []\n\n  if (enable_skity) {\n    sources += [\n      \"src/txt/asset_font_manager_skity.cc\",\n      \"src/txt/asset_font_manager_skity.h\",\n      \"src/txt/font_collection_skity.cc\",\n      \"src/txt/font_collection_skity.h\",\n      \"src/txt/typeface_font_asset_provider_skity.cc\",\n      \"src/txt/typeface_font_asset_provider_skity.h\",\n    ]\n  } else {\n    sources += [\n      \"src/txt/run_metrics.h\",\n      \"src/txt/paint_record.cc\",\n      \"src/txt/paint_record.h\",\n      \"src/txt/font_collection_skia.cc\",\n      \"src/txt/font_collection_skia.h\",\n      \"src/txt/asset_font_manager_skia.cc\",\n      \"src/txt/asset_font_manager_skia.h\",\n      \"src/txt/test_font_manager.cc\",\n      \"src/txt/test_font_manager.h\",\n      \"src/txt/typeface_font_asset_provider_skia.cc\",\n      \"src/txt/typeface_font_asset_provider_skia.h\",\n    ]\n\n    deps += [ \"//third_party/skia\" ]\n  }\n\n  public_configs = [ \":txt_config\" ]\n\n  public_configs += [\n    \":define_skshaper\",\n    \"../../:config\",\n  ]\n\n  public_deps = [\n    \"../../fml:fml\",\n    \"//third_party/harfbuzz\",\n    \"//third_party/icu\",\n  ]\n\n  if (flutter_use_fontconfig) {\n    deps += [ \"//third_party/fontconfig\" ]\n  }\n\n  if (clay_enable_skshaper) {\n    sources += [\n      \"src/skia/paragraph_builder_skia.cc\",\n      \"src/skia/paragraph_builder_skia.h\",\n      \"src/skia/paragraph_skia.cc\",\n      \"src/skia/paragraph_skia.h\",\n    ]\n\n    deps += [ \"//third_party/skia/modules/skparagraph\" ]\n  }\n\n  if (is_mac || is_ios) {\n    sources += [ \"src/txt/platform_mac.mm\" ]\n    deps += [\n      \"../../fml:fml\",\n    ]\n  } else if (is_android) {\n    sources += [ \"src/txt/platform_android.cc\" ]\n  } else if (is_linux) {\n    sources += [ \"src/txt/platform_linux.cc\" ]\n  } else if (is_harmony) {\n    sources += [ \"src/txt/platform_harmony.cc\" ]\n  } else if (is_fuchsia) {\n    sources += [ \"src/txt/platform_fuchsia.cc\" ]\n    deps += [ \"$fuchsia_sdk_root/fidl:fuchsia.fonts\" ]\n  } else if (is_win) {\n    sources += [ \"src/txt/platform_windows.cc\" ]\n  } else {\n    sources += [ \"src/txt/platform.cc\" ]\n  }\n\n  public_configs += [ \":define_minikin\" ]\n\n  if (!clay_enable_minikin) {\n    sources -= [\n      \"src/minikin/CmapCoverage.cpp\",\n      \"src/minikin/CmapCoverage.h\",\n      \"src/minikin/Emoji.cpp\",\n      \"src/minikin/Emoji.h\",\n      \"src/minikin/FontCollection.cpp\",\n      \"src/minikin/FontCollection.h\",\n      \"src/minikin/FontFamily.cpp\",\n      \"src/minikin/FontFamily.h\",\n      \"src/minikin/FontLanguage.cpp\",\n      \"src/minikin/FontLanguage.h\",\n      \"src/minikin/FontLanguageListCache.cpp\",\n      \"src/minikin/FontLanguageListCache.h\",\n      \"src/minikin/FontUtils.cpp\",\n      \"src/minikin/FontUtils.h\",\n      \"src/minikin/GraphemeBreak.cpp\",\n      \"src/minikin/GraphemeBreak.h\",\n      \"src/minikin/HbFontCache.cpp\",\n      \"src/minikin/HbFontCache.h\",\n      \"src/minikin/Hyphenator.cpp\",\n      \"src/minikin/Hyphenator.h\",\n      \"src/minikin/Layout.cpp\",\n      \"src/minikin/Layout.h\",\n      \"src/minikin/LayoutUtils.cpp\",\n      \"src/minikin/LayoutUtils.h\",\n      \"src/minikin/LineBreaker.cpp\",\n      \"src/minikin/LineBreaker.h\",\n      \"src/minikin/Measurement.cpp\",\n      \"src/minikin/Measurement.h\",\n      \"src/minikin/MinikinFont.cpp\",\n      \"src/minikin/MinikinFont.h\",\n      \"src/minikin/MinikinInternal.cpp\",\n      \"src/minikin/MinikinInternal.h\",\n      \"src/minikin/SparseBitSet.cpp\",\n      \"src/minikin/SparseBitSet.h\",\n      \"src/minikin/WordBreaker.cpp\",\n      \"src/minikin/WordBreaker.h\",\n      \"src/txt/font_skia.cc\",\n      \"src/txt/font_skia.h\",\n      \"src/txt/paragraph_builder_txt.cc\",\n      \"src/txt/paragraph_builder_txt.h\",\n    ]\n  }\n\n  if (clay_enable_tttext) {\n    sources += [\n      \"src/tttext/paragraph_builder_tt_text.cc\",\n      \"src/tttext/paragraph_builder_tt_text.h\",\n      \"src/tttext/paragraph_tt_text.cc\",\n      \"src/tttext/paragraph_tt_text.h\",\n      \"src/tttext/tttext_headers.h\",\n    ]\n\n    include_dirs += [\n      \"src/tttext/\",\n    ]\n\n    if (!(is_android && enable_skity)) {\n      public_deps += [ \"//third_party/textlayout/src:textlayout\" ]\n      public_configs += [ \"//third_party/textlayout/src:textlayout_public\" ]\n    }\n\n    public_deps -= [\n      \"//third_party/harfbuzz\",\n    ]\n    if (!is_mac && !is_win) {\n      public_deps -= [\n        \"//third_party/icu\",\n      ]\n    }\n  }\n}\n\nif (enable_unittests) {\n  txt_common_executable_deps = [\n    \"../../fml:fml\",  # For ICU initialization.\n    \"../../fml:icu_util\",\n    \"../../../base/src:base_static\",\n  ]\n\n  test_fixtures(\"txt_fixtures\") {\n    fixtures = [ \"third_party/fonts/Roboto-Bold.ttf\" ]\n  }\n\n  source_set(\"txt_test_utils\") {\n    sources = [\n      \"tests/txt_test_utils.cc\",\n      \"tests/txt_test_utils.h\",\n    ]\n\n    configs += [\n      \":define_skshaper\",\n      \"../../:config\",\n    ]\n\n    deps = [\n      \":txt\",\n      \"../../fml:fml\",\n      \"//third_party/skia\",\n    ]\n  }\n\n  executable(\"txt_benchmarks\") {\n    testonly = true\n\n    sources = [\n      \"benchmarks/paint_record_benchmarks.cc\",\n      \"benchmarks/paragraph_benchmarks.cc\",\n      \"benchmarks/paragraph_builder_benchmarks.cc\",\n      \"benchmarks/txt_run_all_benchmarks.cc\",\n    ]\n\n    configs += [ \":define_skshaper\" ]\n\n    deps = [\n             \":txt\",\n             \":txt_fixtures\",\n             \":txt_test_utils\",\n             \"../../testing:testing_lib\",\n             \"//third_party/benchmark\",\n           ] + txt_common_executable_deps\n\n    if (clay_enable_skshaper) {\n      sources += [ \"benchmarks/skparagraph_benchmarks.cc\" ]\n\n      deps += [ \"//third_party/skia/modules/skparagraph\" ]\n    }\n\n    configs += [ \":define_minikin\" ]\n    if (!clay_enable_minikin) {\n      sources -= [\n        \"benchmarks/paragraph_benchmarks.cc\",\n        \"benchmarks/paragraph_builder_benchmarks.cc\",\n      ]\n    }\n  }\n\n  executable(\"txt_unittests\") {\n    testonly = true\n\n    sources = [\n      \"tests/CmapCoverageTest.cpp\",\n      \"tests/EmojiTest.cpp\",\n      \"tests/FileUtils.cpp\",\n      \"tests/FileUtils.h\",\n      \"tests/FontTestUtils.h\",\n      \"tests/GraphemeBreakTests.cpp\",\n      \"tests/ICUTestBase.h\",\n      \"tests/LayoutUtilsTest.cpp\",\n      \"tests/MeasurementTests.cpp\",\n      \"tests/SparseBitSetTest.cpp\",\n      \"tests/UnicodeUtils.cpp\",\n      \"tests/UnicodeUtils.h\",\n      \"tests/UnicodeUtilsTest.cpp\",\n      \"tests/font_collection_unittests.cc\",\n      \"tests/paragraph_unittests.cc\",\n      \"tests/render_test.cc\",\n      \"tests/render_test.h\",\n      \"tests/txt_run_all_unittests.cc\",\n\n      # These tests require static fixtures.\n      # \"tests/FontCollectionItemizeTest.cpp\",\n      # \"tests/FontCollectionTest.cpp\",\n      # \"tests/FontFamilyTest.cpp\",\n      # \"tests/FontLanguageListCacheTest.cpp\",\n      # \"tests/FontTestUtils.cpp\",\n      # \"tests/HbFontCacheTest.cpp\",\n      # \"tests/HyphenatorTest.cpp\",\n      # \"tests/LayoutTest.cpp\",\n    ]\n\n    if (!is_win) {\n      sources += [\n        \"tests/MinikinFontForTest.cpp\",\n        \"tests/MinikinFontForTest.h\",\n      ]\n    }\n\n    configs += [\n      \":allow_posix_names\",\n      \":define_skshaper\",\n    ]\n\n    deps = [\n             \":txt\",\n             \":txt_fixtures\",\n             \":txt_test_utils\",\n             \"../../testing:testing_lib\",\n             \"//third_party/skia\",\n           ] + txt_common_executable_deps\n\n    if (is_fuchsia) {\n      sources += [ \"tests/platform_fuchsia_unittests.cc\" ]\n      deps += [\n        \"$fuchsia_sdk_root/fidl:fuchsia.fonts\",\n        \"$fuchsia_sdk_root/pkg:async-testing\",\n      ]\n    }\n\n    configs += [ \":define_minikin\" ]\n\n    if (!clay_enable_minikin) {\n      sources -= [\n        \"tests/CmapCoverageTest.cpp\",\n        \"tests/EmojiTest.cpp\",\n        \"tests/GraphemeBreakTests.cpp\",\n        \"tests/LayoutUtilsTest.cpp\",\n        \"tests/MeasurementTests.cpp\",\n        \"tests/SparseBitSetTest.cpp\",\n        \"tests/paragraph_unittests.cc\",\n      ]\n\n      if (!is_win) {\n        sources -= [\n          \"tests/MinikinFontForTest.cpp\",\n          \"tests/MinikinFontForTest.h\",\n        ]\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "clay/third_party/txt/LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "clay/third_party/txt/PATENTS",
    "content": "Additional IP Rights Grant (Patents)\n\n\"This implementation\" means the copyrightable works distributed by\nGoogle as part of the Fuchsia project.\n\nGoogle hereby grants to you a perpetual, worldwide, non-exclusive,\nno-charge, royalty-free, irrevocable (except as stated in this\nsection) patent license to make, have made, use, offer to sell, sell,\nimport, transfer, and otherwise run, modify and propagate the contents\nof this implementation of Fuchsia, where such license applies only to\nthose patent claims, both currently owned by Google and acquired in\nthe future, licensable by Google that are necessarily infringed by\nthis implementation. This grant does not include claims that would be\ninfringed only as a consequence of further modification of this\nimplementation. If you or your agent or exclusive licensee institute\nor order or agree to the institution of patent litigation or any other\npatent enforcement activity against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that this\nimplementation of Fuchsia constitutes direct or contributory patent\ninfringement, or inducement of patent infringement, then any patent\nrights granted to you under this License for this implementation of\nFuchsia shall terminate as of the date such litigation is filed.\n"
  },
  {
    "path": "clay/third_party/txt/benchmarks/paint_record_benchmarks.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"clay/fml/command_line.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/third_party/txt/tests/txt_test_utils.h\"\n#include \"third_party/benchmark/include/benchmark/benchmark.h\"\n#include \"txt/paint_record.h\"\n#include \"txt/text_style.h\"\n\nnamespace txt {\n\nstatic void BM_PaintRecordInit(benchmark::State& state) {\n  TextStyle style;\n  style.font_families = std::vector<std::string>(1, \"Roboto\");\n\n  SkFont font;\n  font.setEdging(SkFont::Edging::kAntiAlias);\n  font.setSize(14);\n  font.setEmbolden(false);\n\n  SkTextBlobBuilder builder;\n  builder.allocRunPos(font, 100);\n  auto text_blob = builder.make();\n\n  while (state.KeepRunning()) {\n    PaintRecord PaintRecord(style, text_blob, SkFontMetrics(), 0, 0, 0, false);\n  }\n}\nBENCHMARK(BM_PaintRecordInit);\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/benchmarks/paragraph_benchmarks.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <minikin/Layout.h>\n\n#include <cstring>\n\n#include \"clay/fml/command_line.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/third_party/txt/tests/txt_test_utils.h\"\n#include \"minikin/LayoutUtils.h\"\n#include \"third_party/benchmark/include/benchmark/benchmark.h\"\n#include \"third_party/icu/source/common/unicode/unistr.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n#include \"txt/font_collection.h\"\n#include \"txt/font_skia.h\"\n#include \"txt/font_style.h\"\n#include \"txt/font_weight.h\"\n#include \"txt/paragraph.h\"\n#include \"txt/paragraph_builder_txt.h\"\n\nnamespace txt {\n\nclass ParagraphFixture : public benchmark::Fixture {\n public:\n  void SetUp(const benchmark::State& state) {\n    font_collection_ = GetTestFontCollection();\n\n    bitmap_ = std::make_unique<SkBitmap>();\n    bitmap_->allocN32Pixels(1000, 1000);\n    canvas_ = std::make_unique<SkCanvas>(*bitmap_);\n    canvas_->clear(SK_ColorWHITE);\n  }\n\n  void TearDown(const benchmark::State& state) { font_collection_.reset(); }\n\n protected:\n  std::shared_ptr<FontCollection> font_collection_;\n  std::unique_ptr<SkCanvas> canvas_;\n  std::unique_ptr<SkBitmap> bitmap_;\n};\n\nBENCHMARK_F(ParagraphFixture, ShortLayout)(benchmark::State& state) {\n  const char* text = \"Hello World\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  txt::ParagraphBuilderTxt builder(paragraph_style, font_collection_);\n\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  builder.Pop();\n  auto paragraph = BuildParagraph(builder);\n  while (state.KeepRunning()) {\n    paragraph->SetDirty();\n    paragraph->Layout(300);\n  }\n}\n\nBENCHMARK_F(ParagraphFixture, LongLayout)(benchmark::State& state) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, font_collection_);\n\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  builder.Pop();\n  auto paragraph = BuildParagraph(builder);\n  while (state.KeepRunning()) {\n    paragraph->SetDirty();\n    paragraph->Layout(300);\n  }\n}\n\nBENCHMARK_F(ParagraphFixture, JustifyLayout)(benchmark::State& state) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.text_align = TextAlign::justify;\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, font_collection_);\n\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  builder.Pop();\n  auto paragraph = BuildParagraph(builder);\n  while (state.KeepRunning()) {\n    paragraph->SetDirty();\n    paragraph->Layout(300);\n  }\n}\n\nBENCHMARK_F(ParagraphFixture, ManyStylesLayout)(benchmark::State& state) {\n  const char* text = \"-\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  txt::ParagraphBuilderTxt builder(paragraph_style, font_collection_);\n  for (int i = 0; i < 1000; ++i) {\n    builder.PushStyle(text_style);\n    builder.AddText(u16_text);\n  }\n  auto paragraph = BuildParagraph(builder);\n  while (state.KeepRunning()) {\n    paragraph->SetDirty();\n    paragraph->Layout(300);\n  }\n}\n\nBENCHMARK_DEFINE_F(ParagraphFixture, TextBigO)(benchmark::State& state) {\n  std::vector<uint16_t> text;\n  for (uint16_t i = 0; i < state.range(0); ++i) {\n    text.push_back(i % 5 == 0 ? ' ' : i);\n  }\n  std::u16string u16_text(text.data(), text.data() + text.size());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.font_family = \"Roboto\";\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, font_collection_);\n\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  builder.Pop();\n  auto paragraph = BuildParagraph(builder);\n  while (state.KeepRunning()) {\n    paragraph->SetDirty();\n    paragraph->Layout(300);\n  }\n  state.SetComplexityN(state.range(0));\n}\nBENCHMARK_REGISTER_F(ParagraphFixture, TextBigO)\n    ->RangeMultiplier(4)\n    ->Range(1 << 6, 1 << 14)\n    ->Complexity(benchmark::oN);\n\nBENCHMARK_DEFINE_F(ParagraphFixture, StylesBigO)(benchmark::State& state) {\n  const char* text = \"vry shrt \";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, font_collection_);\n\n  for (int i = 0; i < state.range(0); ++i) {\n    builder.PushStyle(text_style);\n    builder.AddText(u16_text);\n  }\n  auto paragraph = BuildParagraph(builder);\n  while (state.KeepRunning()) {\n    paragraph->SetDirty();\n    paragraph->Layout(300);\n  }\n  state.SetComplexityN(state.range(0));\n}\nBENCHMARK_REGISTER_F(ParagraphFixture, StylesBigO)\n    ->RangeMultiplier(4)\n    ->Range(1 << 3, 1 << 12)\n    ->Complexity(benchmark::oN);\n\nBENCHMARK_F(ParagraphFixture, PaintSimple)(benchmark::State& state) {\n  const char* text = \"Hello world! This is a simple sentence to test drawing.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  txt::ParagraphBuilderTxt builder(paragraph_style, font_collection_);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(300);\n\n  int offset = 0;\n  while (state.KeepRunning()) {\n    paragraph->Paint(canvas_.get(), offset % 700, 10);\n    offset++;\n  }\n}\n\nBENCHMARK_F(ParagraphFixture, PaintLarge)(benchmark::State& state) {\n  const char* text =\n      \"Hello world! This is a simple sentence to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  txt::ParagraphBuilderTxt builder(paragraph_style, font_collection_);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(300);\n\n  int offset = 0;\n  while (state.KeepRunning()) {\n    paragraph->Paint(canvas_.get(), offset % 700, 10);\n    offset++;\n  }\n}\n\nBENCHMARK_F(ParagraphFixture, PaintDecoration)(benchmark::State& state) {\n  const char* text =\n      \"Hello world! This is a simple sentence to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.decoration = TextDecoration::kUnderline |\n                          TextDecoration::kOverline |\n                          TextDecoration::kLineThrough;\n  text_style.decoration_style = TextDecorationStyle(kSolid);\n  text_style.color = SK_ColorBLACK;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, font_collection_);\n\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  text_style.decoration_style = TextDecorationStyle(kDotted);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  text_style.decoration_style = TextDecorationStyle(kWavy);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(300);\n\n  int offset = 0;\n  while (state.KeepRunning()) {\n    paragraph->Paint(canvas_.get(), offset % 700, 10);\n    offset++;\n  }\n}\n\n// -----------------------------------------------------------------------------\n//\n// The following benchmarks break down the layout function and attempts to time\n// each of the components to more finely attribute latency.\n//\n// -----------------------------------------------------------------------------\n\nBENCHMARK_DEFINE_F(ParagraphFixture, MinikinDoLayout)(benchmark::State& state) {\n  std::vector<uint16_t> text;\n  for (uint16_t i = 0; i < 16000 * 2; ++i) {\n    text.push_back(i % 5 == 0 ? ' ' : i);\n  }\n  minikin::FontStyle font;\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  minikin::MinikinPaint paint;\n\n  font = minikin::FontStyle(4, false);\n  paint.size = text_style.font_size;\n  paint.letterSpacing = text_style.letter_spacing;\n  paint.wordSpacing = text_style.word_spacing;\n\n  auto collection = font_collection_->GetMinikinFontCollectionForFamilies(\n      text_style.font_families, \"en-US\");\n\n  while (state.KeepRunning()) {\n    minikin::Layout layout;\n    layout.doLayout(text.data(), 0, state.range(0), state.range(0), 0, font,\n                    paint, collection);\n  }\n  state.SetComplexityN(state.range(0));\n}\nBENCHMARK_REGISTER_F(ParagraphFixture, MinikinDoLayout)\n    ->RangeMultiplier(4)\n    ->Range(1 << 7, 1 << 14)\n    ->Complexity(benchmark::oN);\n\nBENCHMARK_DEFINE_F(ParagraphFixture, AddStyleRun)(benchmark::State& state) {\n  std::vector<uint16_t> text;\n  for (uint16_t i = 0; i < 16000 * 2; ++i) {\n    text.push_back(i % 5 == 0 ? ' ' : i);\n  }\n  minikin::FontStyle font;\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  minikin::MinikinPaint paint;\n\n  font = minikin::FontStyle(4, false);\n  paint.size = text_style.font_size;\n  paint.letterSpacing = text_style.letter_spacing;\n  paint.wordSpacing = text_style.word_spacing;\n\n  minikin::LineBreaker breaker;\n  breaker.setLocale();\n  breaker.resize(text.size());\n  memcpy(breaker.buffer(), text.data(), text.size() * sizeof(text[0]));\n  breaker.setText();\n\n  while (state.KeepRunning()) {\n    for (int i = 0; i < 20; ++i) {\n      breaker.addStyleRun(&paint,\n                          font_collection_->GetMinikinFontCollectionForFamilies(\n                              std::vector<std::string>(1, \"Roboto\"), \"en-US\"),\n                          font, state.range(0) / 20 * i,\n                          state.range(0) / 20 * (i + 1), false);\n    }\n  }\n  state.SetComplexityN(state.range(0));\n}\nBENCHMARK_REGISTER_F(ParagraphFixture, AddStyleRun)\n    ->RangeMultiplier(4)\n    ->Range(1 << 7, 1 << 14)\n    ->Complexity(benchmark::oN);\n\nBENCHMARK_DEFINE_F(ParagraphFixture, SkTextBlobAlloc)(benchmark::State& state) {\n  SkFont font;\n  font.setEdging(SkFont::Edging::kAntiAlias);\n  font.setSize(14);\n  font.setEmbolden(false);\n\n  while (state.KeepRunning()) {\n    SkTextBlobBuilder builder;\n    builder.allocRunPos(font, state.range(0));\n  }\n  state.SetComplexityN(state.range(0));\n}\nBENCHMARK_REGISTER_F(ParagraphFixture, SkTextBlobAlloc)\n    ->RangeMultiplier(4)\n    ->Range(1 << 7, 1 << 14)\n    ->Complexity(benchmark::oN);\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/benchmarks/paragraph_builder_benchmarks.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"clay/fml/logging.h\"\n#include \"clay/third_party/txt/tests/txt_test_utils.h\"\n#include \"third_party/benchmark/include/benchmark/benchmark.h\"\n#include \"third_party/icu/source/common/unicode/unistr.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n#include \"txt/font_collection.h\"\n#include \"txt/font_style.h\"\n#include \"txt/font_weight.h\"\n#include \"txt/paragraph.h\"\n#include \"txt/paragraph_builder_txt.h\"\n\nnamespace txt {\n\nstatic void BM_ParagraphBuilderConstruction(benchmark::State& state) {\n  txt::ParagraphStyle paragraph_style;\n  auto font_collection = GetTestFontCollection();\n  while (state.KeepRunning()) {\n    txt::ParagraphBuilderTxt builder(paragraph_style, font_collection);\n  }\n}\nBENCHMARK(BM_ParagraphBuilderConstruction);\n\nstatic void BM_ParagraphBuilderPushStyle(benchmark::State& state) {\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  auto font_collection = GetTestFontCollection();\n  while (state.KeepRunning()) {\n    txt::ParagraphBuilderTxt builder(paragraph_style, font_collection);\n    builder.PushStyle(text_style);\n  }\n}\nBENCHMARK(BM_ParagraphBuilderPushStyle);\n\nstatic void BM_ParagraphBuilderPushPop(benchmark::State& state) {\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  while (state.KeepRunning()) {\n    builder.PushStyle(text_style);\n    builder.Pop();\n  }\n}\nBENCHMARK(BM_ParagraphBuilderPushPop);\n\nstatic void BM_ParagraphBuilderAddTextString(benchmark::State& state) {\n  std::u16string text = u\"Hello World\";\n\n  auto font_collection = GetTestFontCollection();\n\n  txt::ParagraphStyle paragraph_style;\n\n  while (state.KeepRunning()) {\n    txt::ParagraphBuilderTxt builder(paragraph_style, font_collection);\n    builder.AddText(text);\n  }\n}\nBENCHMARK(BM_ParagraphBuilderAddTextString);\n\nstatic void BM_ParagraphBuilderAddTextChar(benchmark::State& state) {\n  std::u16string text = u\"Hello World\";\n\n  txt::ParagraphStyle paragraph_style;\n  auto font_collection = GetTestFontCollection();\n  while (state.KeepRunning()) {\n    txt::ParagraphBuilderTxt builder(paragraph_style, font_collection);\n    builder.AddText(text);\n  }\n}\nBENCHMARK(BM_ParagraphBuilderAddTextChar);\n\nstatic void BM_ParagraphBuilderAddTextU16stringShort(benchmark::State& state) {\n  const char* text = \"H\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  auto font_collection = GetTestFontCollection();\n  while (state.KeepRunning()) {\n    txt::ParagraphBuilderTxt builder(paragraph_style, font_collection);\n    builder.AddText(u16_text);\n  }\n}\nBENCHMARK(BM_ParagraphBuilderAddTextU16stringShort);\n\nstatic void BM_ParagraphBuilderAddTextU16stringLong(benchmark::State& state) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  auto font_collection = GetTestFontCollection();\n\n  txt::ParagraphStyle paragraph_style;\n\n  while (state.KeepRunning()) {\n    txt::ParagraphBuilderTxt builder(paragraph_style, font_collection);\n    builder.AddText(u16_text);\n  }\n}\nBENCHMARK(BM_ParagraphBuilderAddTextU16stringLong);\n\nstatic void BM_ParagraphBuilderShortParagraphConstruct(\n    benchmark::State& state) {\n  const char* text = \"Hello World\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  auto font_collection = GetTestFontCollection();\n  while (state.KeepRunning()) {\n    txt::ParagraphBuilderTxt builder(paragraph_style, font_collection);\n    builder.PushStyle(text_style);\n    builder.AddText(u16_text);\n    builder.Pop();\n    auto paragraph = builder.Build();\n  }\n}\nBENCHMARK(BM_ParagraphBuilderShortParagraphConstruct);\n\nstatic void BM_ParagraphBuilderLongParagraphConstruct(benchmark::State& state) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  auto font_collection = GetTestFontCollection();\n  while (state.KeepRunning()) {\n    txt::ParagraphBuilderTxt builder(paragraph_style, font_collection);\n    builder.PushStyle(text_style);\n    builder.AddText(u16_text);\n    builder.Pop();\n    auto paragraph = builder.Build();\n  }\n}\nBENCHMARK(BM_ParagraphBuilderLongParagraphConstruct);\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/benchmarks/skparagraph_benchmarks.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <sstream>\n\n#include \"clay/fml/command_line.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/third_party/txt/tests/txt_test_utils.h\"\n#include \"third_party/benchmark/include/benchmark/benchmark.h\"\n#include \"third_party/icu/source/common/unicode/unistr.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n#include \"third_party/skia/modules/skparagraph/include/Paragraph.h\"\n#include \"third_party/skia/modules/skparagraph/include/ParagraphBuilder.h\"\n#include \"third_party/skia/modules/skparagraph/include/TypefaceFontProvider.h\"\n#include \"third_party/skia/modules/skparagraph/utils/TestFontCollection.h\"\n\nnamespace sktxt = skia::textlayout;\n\nclass SkParagraphFixture : public benchmark::Fixture {\n public:\n  void SetUp(const ::benchmark::State& state) {\n    font_collection_ = sk_make_sp<sktxt::TestFontCollection>(txt::GetFontDir());\n\n    bitmap_ = std::make_unique<SkBitmap>();\n    bitmap_->allocN32Pixels(1000, 1000);\n    canvas_ = std::make_unique<SkCanvas>(*bitmap_);\n    canvas_->clear(SK_ColorWHITE);\n  }\n\n protected:\n  sk_sp<sktxt::TestFontCollection> font_collection_;\n  std::unique_ptr<SkCanvas> canvas_;\n  std::unique_ptr<SkBitmap> bitmap_;\n};\n\nBENCHMARK_F(SkParagraphFixture, ShortLayout)(benchmark::State& state) {\n  const char* text = \"Hello World\";\n  sktxt::ParagraphStyle paragraph_style;\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  auto builder =\n      sktxt::ParagraphBuilder::make(paragraph_style, font_collection_);\n  builder->pushStyle(text_style);\n  builder->addText(text);\n  builder->pop();\n  auto paragraph = builder->Build();\n  while (state.KeepRunning()) {\n    paragraph->markDirty();\n    paragraph->layout(300);\n  }\n}\n\nBENCHMARK_F(SkParagraphFixture, LongLayout)(benchmark::State& state) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum.\";\n  sktxt::ParagraphStyle paragraph_style;\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  auto builder =\n      sktxt::ParagraphBuilder::make(paragraph_style, font_collection_);\n  builder->pushStyle(text_style);\n  builder->addText(text);\n  builder->pop();\n  auto paragraph = builder->Build();\n  while (state.KeepRunning()) {\n    paragraph->markDirty();\n    paragraph->layout(300);\n  }\n}\n\nBENCHMARK_F(SkParagraphFixture, JustifyLayout)(benchmark::State& state) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum.\";\n  sktxt::ParagraphStyle paragraph_style;\n  paragraph_style.setTextAlign(sktxt::TextAlign::kJustify);\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  auto builder =\n      sktxt::ParagraphBuilder::make(paragraph_style, font_collection_);\n  builder->pushStyle(text_style);\n  builder->addText(text);\n  builder->pop();\n  auto paragraph = builder->Build();\n  while (state.KeepRunning()) {\n    paragraph->markDirty();\n    paragraph->layout(300);\n  }\n}\n\nBENCHMARK_F(SkParagraphFixture, ManyStylesLayout)(benchmark::State& state) {\n  const char* text = \"-\";\n  sktxt::ParagraphStyle paragraph_style;\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  auto builder =\n      sktxt::ParagraphBuilder::make(paragraph_style, font_collection_);\n  for (int i = 0; i < 1000; ++i) {\n    builder->pushStyle(text_style);\n    builder->addText(text);\n  }\n  auto paragraph = builder->Build();\n  while (state.KeepRunning()) {\n    paragraph->markDirty();\n    paragraph->layout(300);\n  }\n}\n\nBENCHMARK_DEFINE_F(SkParagraphFixture, TextBigO)(benchmark::State& state) {\n  std::vector<uint16_t> text;\n  for (uint16_t i = 0; i < state.range(0); ++i) {\n    text.push_back(i % 5 == 0 ? ' ' : i);\n  }\n  std::u16string u16_text(text.data(), text.data() + text.size());\n  sktxt::ParagraphStyle paragraph_style;\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  auto builder =\n      sktxt::ParagraphBuilder::make(paragraph_style, font_collection_);\n  builder->pushStyle(text_style);\n  builder->addText(u16_text);\n  builder->pop();\n  auto paragraph = builder->Build();\n  while (state.KeepRunning()) {\n    paragraph->markDirty();\n    paragraph->layout(300);\n  }\n  state.SetComplexityN(state.range(0));\n}\nBENCHMARK_REGISTER_F(SkParagraphFixture, TextBigO)\n    ->RangeMultiplier(4)\n    ->Range(1 << 6, 1 << 14)\n    ->Complexity(benchmark::oN);\n\nBENCHMARK_DEFINE_F(SkParagraphFixture, StylesBigO)(benchmark::State& state) {\n  const char* text = \"vry shrt \";\n  sktxt::ParagraphStyle paragraph_style;\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  auto builder = sktxt::ParagraphBuilder::make(\n      paragraph_style,\n      sk_make_sp<sktxt::TestFontCollection>(txt::GetFontDir()));\n  for (int i = 0; i < 1000; ++i) {\n    builder->pushStyle(text_style);\n    builder->addText(text);\n  }\n  auto paragraph = builder->Build();\n  while (state.KeepRunning()) {\n    paragraph->markDirty();\n    paragraph->layout(300);\n  }\n  state.SetComplexityN(state.range(0));\n}\nBENCHMARK_REGISTER_F(SkParagraphFixture, StylesBigO)\n    ->RangeMultiplier(4)\n    ->Range(1 << 3, 1 << 12)\n    ->Complexity(benchmark::oN);\n\nBENCHMARK_F(SkParagraphFixture, PaintSimple)(benchmark::State& state) {\n  const char* text = \"This is a simple sentence to test drawing.\";\n  sktxt::ParagraphStyle paragraph_style;\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  auto builder =\n      sktxt::ParagraphBuilder::make(paragraph_style, font_collection_);\n  builder->pushStyle(text_style);\n  builder->addText(text);\n  builder->pop();\n  auto paragraph = builder->Build();\n  paragraph->layout(300);\n  int offset = 0;\n  while (state.KeepRunning()) {\n    paragraph->paint(canvas_.get(), offset % 700, 10);\n    offset++;\n  }\n}\n\nBENCHMARK_F(SkParagraphFixture, PaintLarge)(benchmark::State& state) {\n  const char* text =\n      \"Hello world! This is a simple sentence to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.Hello world! This is a simple sentence \"\n      \"to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing. Hello world! This is a \"\n      \"simple sentence to test drawing.\";\n  sktxt::ParagraphStyle paragraph_style;\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  auto builder =\n      sktxt::ParagraphBuilder::make(paragraph_style, font_collection_);\n  builder->pushStyle(text_style);\n  builder->addText(text);\n  builder->pop();\n  auto paragraph = builder->Build();\n  paragraph->layout(300);\n  int offset = 0;\n  while (state.KeepRunning()) {\n    paragraph->paint(canvas_.get(), offset % 700, 10);\n    offset++;\n  }\n}\n\nBENCHMARK_F(SkParagraphFixture, PaintDecoration)(benchmark::State& state) {\n  const char* text =\n      \"Hello world! This is a simple sentence to test drawing. Hello world! \"\n      \"This is a simple sentence to test drawing.\";\n  sktxt::ParagraphStyle paragraph_style;\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  text_style.setDecoration(static_cast<sktxt::TextDecoration>(\n      sktxt::TextDecoration::kLineThrough | sktxt::TextDecoration::kOverline |\n      sktxt::TextDecoration::kUnderline));\n  auto builder =\n      sktxt::ParagraphBuilder::make(paragraph_style, font_collection_);\n  text_style.setDecorationStyle(sktxt::TextDecorationStyle::kSolid);\n  builder->pushStyle(text_style);\n  builder->addText(text);\n\n  text_style.setDecorationStyle(sktxt::TextDecorationStyle::kDotted);\n  builder->pushStyle(text_style);\n  builder->addText(text);\n\n  text_style.setDecorationStyle(sktxt::TextDecorationStyle::kWavy);\n  builder->pushStyle(text_style);\n  builder->addText(text);\n\n  auto paragraph = builder->Build();\n  paragraph->layout(300);\n  int offset = 0;\n  while (state.KeepRunning()) {\n    paragraph->paint(canvas_.get(), offset % 700, 10);\n    offset++;\n  }\n}\n\nBENCHMARK_F(SkParagraphFixture, SimpleBuilder)(benchmark::State& state) {\n  const char* text = \"Hello World\";\n  sktxt::ParagraphStyle paragraph_style;\n  sktxt::TextStyle text_style;\n  text_style.setFontFamilies({SkString(\"Roboto\")});\n  text_style.setColor(SK_ColorBLACK);\n  while (state.KeepRunning()) {\n    auto builder =\n        sktxt::ParagraphBuilder::make(paragraph_style, font_collection_);\n    builder->pushStyle(text_style);\n    builder->addText(text);\n    builder->pop();\n    auto paragraph = builder->Build();\n  }\n}\n"
  },
  {
    "path": "clay/third_party/txt/benchmarks/txt_run_all_benchmarks.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"clay/fml/icu_util.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/testing/testing.h\"\n#include \"clay/third_party/txt/tests/txt_test_utils.h\"\n#include \"third_party/benchmark/include/benchmark/benchmark.h\"\n\n// We will use a custom main to allow custom font directories for consistency.\nint main(int argc, char** argv) {\n  ::benchmark::Initialize(&argc, argv);\n  fml::CommandLine cmd = fml::CommandLineFromPlatformOrArgcArgv(argc, argv);\n  txt::SetCommandLine(cmd);\n  txt::SetFontDir(flutter::testing::GetFixturesPath());\n  if (txt::GetFontDir().length() <= 0) {\n    FML_LOG(ERROR) << \"Font directory not set via txt::SetFontDir.\";\n    return EXIT_FAILURE;\n  }\n  FML_DCHECK(txt::GetFontDir().length() > 0);\n\n  std::string icudtl_path =\n      cmd.GetOptionValueWithDefault(\"icu-data-file-path\", \"icudtl.dat\");\n  fml::icu::InitializeICU(icudtl_path);\n\n  ::benchmark::RunSpecifiedBenchmarks();\n}\n"
  },
  {
    "path": "clay/third_party/txt/src/log/log.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <log/log.h>\n\nint __android_log_error_write(int tag,\n                              const char* subTag,\n                              int32_t uid,\n                              const char* data,\n                              uint32_t dataLen) {\n  return 0;\n}\n"
  },
  {
    "path": "clay/third_party/txt/src/log/log.h",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_LOG_LOG_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_LOG_LOG_H_\n#pragma once\n\n#include <stdint.h>\n\n#include \"clay/fml/logging.h\"\n\n#if defined(_WIN32)\n// Conflicts with macro on Windows.\n#undef ERROR\n#endif\n\n#ifndef LOG_ALWAYS_FATAL_IF\n#define LOG_ALWAYS_FATAL_IF(cond, ...) \\\n  ((cond) ? (FML_LOG(FATAL) << #cond) : (void)0)\n#endif\n\n#ifndef LOG_ALWAYS_FATAL\n#define LOG_ALWAYS_FATAL(...) FML_LOG(FATAL)\n#endif\n\n#ifndef LOG_ASSERT\n#define LOG_ASSERT(cond, ...) FML_CHECK(cond)\n#define ALOG_ASSERT LOG_ASSERT\n#endif\n\n#ifndef ALOGD\n#define ALOGD(message, ...) FML_DLOG(INFO) << (message)\n#endif\n\n#ifndef ALOGW\n#define ALOGW(message, ...) FML_LOG(WARNING) << (message)\n#endif\n\n#ifndef ALOGE\n#define ALOGE(message, ...) FML_LOG(ERROR) << (message)\n#endif\n\n#define android_errorWriteLog(tag, subTag) \\\n  __android_log_error_write(tag, subTag, -1, NULL, 0)\n#define android_errorWriteWithInfoLog(tag, subTag, uid, data, dataLen) \\\n  __android_log_error_write(tag, subTag, uid, data, dataLen)\nint __android_log_error_write(int tag,\n                              const char* subTag,\n                              int32_t uid,\n                              const char* data,\n                              uint32_t dataLen);\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_LOG_LOG_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/CmapCoverage.cpp",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Determine coverage of font given its raw \"cmap\" OpenType table\n\n#define LOG_TAG \"Minikin\"\n\n#include <vector>\nusing std::vector;\n\n#include <log/log.h>\n\n#include <minikin/CmapCoverage.h>\n#include <minikin/SparseBitSet.h>\n#include \"MinikinInternal.h\"\n\nnamespace minikin {\n\n// These could perhaps be optimized to use __builtin_bswap16 and friends.\nstatic uint32_t readU16(const uint8_t* data, size_t offset) {\n  return ((uint32_t)data[offset]) << 8 | ((uint32_t)data[offset + 1]);\n}\n\nstatic uint32_t readU32(const uint8_t* data, size_t offset) {\n  return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |\n         ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);\n}\n\nstatic void addRange(vector<uint32_t>& coverage, uint32_t start, uint32_t end) {\n#ifdef VERBOSE_DEBUG\n  ALOGD(\"adding range %d-%d\\n\", start, end);\n#endif\n  if (coverage.empty() || coverage.back() < start) {\n    coverage.push_back(start);\n    coverage.push_back(end);\n  } else {\n    coverage.back() = end;\n  }\n}\n\n// Get the coverage information out of a Format 4 subtable, storing it in the\n// coverage vector\nstatic bool getCoverageFormat4(vector<uint32_t>& coverage,\n                               const uint8_t* data,\n                               size_t size) {\n  const size_t kSegCountOffset = 6;\n  const size_t kEndCountOffset = 14;\n  const size_t kHeaderSize = 16;\n  const size_t kSegmentSize =\n      8;  // total size of array elements for one segment\n  if (kEndCountOffset > size) {\n    return false;\n  }\n  size_t segCount = readU16(data, kSegCountOffset) >> 1;\n  if (kHeaderSize + segCount * kSegmentSize > size) {\n    return false;\n  }\n  for (size_t i = 0; i < segCount; i++) {\n    uint32_t end = readU16(data, kEndCountOffset + 2 * i);\n    uint32_t start = readU16(data, kHeaderSize + 2 * (segCount + i));\n    if (end < start) {\n      // invalid segment range: size must be positive\n      android_errorWriteLog(0x534e4554, \"26413177\");\n      return false;\n    }\n    uint32_t rangeOffset = readU16(data, kHeaderSize + 2 * (3 * segCount + i));\n    if (rangeOffset == 0) {\n      uint32_t delta = readU16(data, kHeaderSize + 2 * (2 * segCount + i));\n      if (((end + delta) & 0xffff) > end - start) {\n        addRange(coverage, start, end + 1);\n      } else {\n        for (uint32_t j = start; j < end + 1; j++) {\n          if (((j + delta) & 0xffff) != 0) {\n            addRange(coverage, j, j + 1);\n          }\n        }\n      }\n    } else {\n      for (uint32_t j = start; j < end + 1; j++) {\n        uint32_t actualRangeOffset =\n            kHeaderSize + 6 * segCount + rangeOffset + (i + j - start) * 2;\n        if (actualRangeOffset + 2 > size) {\n          // invalid rangeOffset is considered a \"warning\" by OpenType Sanitizer\n          continue;\n        }\n        uint32_t glyphId = readU16(data, actualRangeOffset);\n        if (glyphId != 0) {\n          addRange(coverage, j, j + 1);\n        }\n      }\n    }\n  }\n  return true;\n}\n\n// Get the coverage information out of a Format 12 subtable, storing it in the\n// coverage vector\nstatic bool getCoverageFormat12(vector<uint32_t>& coverage,\n                                const uint8_t* data,\n                                size_t size) {\n  const size_t kNGroupsOffset = 12;\n  const size_t kFirstGroupOffset = 16;\n  const size_t kGroupSize = 12;\n  const size_t kStartCharCodeOffset = 0;\n  const size_t kEndCharCodeOffset = 4;\n  const size_t kMaxNGroups =\n      0xfffffff0 / kGroupSize;  // protection against overflow\n  // For all values < kMaxNGroups, kFirstGroupOffset + nGroups * kGroupSize fits\n  // in 32 bits.\n  if (kFirstGroupOffset > size) {\n    return false;\n  }\n  uint32_t nGroups = readU32(data, kNGroupsOffset);\n  if (nGroups >= kMaxNGroups ||\n      kFirstGroupOffset + nGroups * kGroupSize > size) {\n    android_errorWriteLog(0x534e4554, \"25645298\");\n    return false;\n  }\n  for (uint32_t i = 0; i < nGroups; i++) {\n    uint32_t groupOffset = kFirstGroupOffset + i * kGroupSize;\n    uint32_t start = readU32(data, groupOffset + kStartCharCodeOffset);\n    uint32_t end = readU32(data, groupOffset + kEndCharCodeOffset);\n    if (end < start) {\n      // invalid group range: size must be positive\n      android_errorWriteLog(0x534e4554, \"26413177\");\n      return false;\n    }\n\n    // No need to read outside of Unicode code point range.\n    if (start > MAX_UNICODE_CODE_POINT) {\n      return true;\n    }\n    if (end > MAX_UNICODE_CODE_POINT) {\n      // file is inclusive, vector is exclusive\n      addRange(coverage, start, MAX_UNICODE_CODE_POINT + 1);\n      return true;\n    }\n    addRange(coverage, start,\n             end + 1);  // file is inclusive, vector is exclusive\n  }\n  return true;\n}\n\n// Lower value has higher priority. 0 for the highest priority table.\n// kLowestPriority for unsupported tables.\n// This order comes from HarfBuzz's hb-ot-font.cc and needs to be kept in sync\n// with it.\nconstexpr uint8_t kLowestPriority = 255;\nuint8_t getTablePriority(uint16_t platformId, uint16_t encodingId) {\n  if (platformId == 3 && encodingId == 10) {\n    return 0;\n  }\n  if (platformId == 0 && encodingId == 6) {\n    return 1;\n  }\n  if (platformId == 0 && encodingId == 4) {\n    return 2;\n  }\n  if (platformId == 3 && encodingId == 1) {\n    return 3;\n  }\n  if (platformId == 0 && encodingId == 3) {\n    return 4;\n  }\n  if (platformId == 0 && encodingId == 2) {\n    return 5;\n  }\n  if (platformId == 0 && encodingId == 1) {\n    return 6;\n  }\n  if (platformId == 0 && encodingId == 0) {\n    return 7;\n  }\n  // Tables other than above are not supported.\n  return kLowestPriority;\n}\n\nSparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data,\n                                       size_t cmap_size,\n                                       bool* has_cmap_format14_subtable) {\n  constexpr size_t kHeaderSize = 4;\n  constexpr size_t kNumTablesOffset = 2;\n  constexpr size_t kTableSize = 8;\n  constexpr size_t kPlatformIdOffset = 0;\n  constexpr size_t kEncodingIdOffset = 2;\n  constexpr size_t kOffsetOffset = 4;\n  constexpr size_t kFormatOffset = 0;\n  constexpr uint32_t kInvalidOffset = UINT32_MAX;\n\n  if (kHeaderSize > cmap_size) {\n    return SparseBitSet();\n  }\n  uint32_t numTables = readU16(cmap_data, kNumTablesOffset);\n  if (kHeaderSize + numTables * kTableSize > cmap_size) {\n    return SparseBitSet();\n  }\n\n  uint32_t bestTableOffset = kInvalidOffset;\n  uint16_t bestTableFormat = 0;\n  uint8_t bestTablePriority = kLowestPriority;\n  *has_cmap_format14_subtable = false;\n  for (uint32_t i = 0; i < numTables; ++i) {\n    const uint32_t tableHeadOffset = kHeaderSize + i * kTableSize;\n    const uint16_t platformId =\n        readU16(cmap_data, tableHeadOffset + kPlatformIdOffset);\n    const uint16_t encodingId =\n        readU16(cmap_data, tableHeadOffset + kEncodingIdOffset);\n    const uint32_t offset = readU32(cmap_data, tableHeadOffset + kOffsetOffset);\n\n    if (offset > cmap_size - 2) {\n      continue;  // Invalid table: not enough space to read.\n    }\n    const uint16_t format = readU16(cmap_data, offset + kFormatOffset);\n\n    if (platformId == 0 /* Unicode */ &&\n        encodingId == 5 /* Variation Sequences */) {\n      if (!(*has_cmap_format14_subtable) && format == 14) {\n        *has_cmap_format14_subtable = true;\n      } else {\n        // Ignore the (0, 5) table if we have already seen another valid one or\n        // it's in a format we don't understand.\n      }\n    } else {\n      uint32_t length;\n      uint32_t language;\n\n      if (format == 4) {\n        constexpr size_t lengthOffset = 2;\n        constexpr size_t languageOffset = 4;\n        constexpr size_t minTableSize = languageOffset + 2;\n        if (offset > cmap_size - minTableSize) {\n          continue;  // Invalid table: not enough space to read.\n        }\n        length = readU16(cmap_data, offset + lengthOffset);\n        language = readU16(cmap_data, offset + languageOffset);\n      } else if (format == 12) {\n        constexpr size_t lengthOffset = 4;\n        constexpr size_t languageOffset = 8;\n        constexpr size_t minTableSize = languageOffset + 4;\n        if (offset > cmap_size - minTableSize) {\n          continue;  // Invalid table: not enough space to read.\n        }\n        length = readU32(cmap_data, offset + lengthOffset);\n        language = readU32(cmap_data, offset + languageOffset);\n      } else {\n        continue;\n      }\n\n      if (length > cmap_size - offset) {\n        continue;  // Invalid table: table length is larger than whole cmap data\n                   // size.\n      }\n      if (language != 0) {\n        // Unsupported or invalid table: this is either a subtable for the\n        // Macintosh platform (which we don't support), or an invalid subtable\n        // since language field should be zero for non-Macintosh subtables.\n        continue;\n      }\n      const uint8_t priority = getTablePriority(platformId, encodingId);\n      if (priority < bestTablePriority) {\n        bestTableOffset = offset;\n        bestTablePriority = priority;\n        bestTableFormat = format;\n      }\n    }\n    if (*has_cmap_format14_subtable &&\n        bestTablePriority == 0 /* highest priority */) {\n      // Already found the highest priority table and variation sequences table.\n      // No need to look at remaining tables.\n      break;\n    }\n  }\n  if (bestTableOffset == kInvalidOffset) {\n    return SparseBitSet();\n  }\n  const uint8_t* tableData = cmap_data + bestTableOffset;\n  const size_t tableSize = cmap_size - bestTableOffset;\n  vector<uint32_t> coverageVec;\n  bool success;\n  if (bestTableFormat == 4) {\n    success = getCoverageFormat4(coverageVec, tableData, tableSize);\n  } else {\n    success = getCoverageFormat12(coverageVec, tableData, tableSize);\n  }\n  if (success && (coverageVec.size() != 0)) {\n    return SparseBitSet(&coverageVec.front(), coverageVec.size() >> 1);\n  } else {\n    return SparseBitSet();\n  }\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/CmapCoverage.h",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_CMAPCOVERAGE_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_CMAPCOVERAGE_H_\n\n#include <minikin/SparseBitSet.h>\n\nnamespace minikin {\n\nclass CmapCoverage {\n public:\n  static SparseBitSet getCoverage(const uint8_t* cmap_data,\n                                  size_t cmap_size,\n                                  bool* has_cmap_format14_subtable);\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_CMAPCOVERAGE_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/Emoji.cpp",
    "content": "/*\n * Copyright (C) 2014 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <minikin/Emoji.h>\n\nnamespace minikin {\n\nbool isEmoji(uint32_t c) {\n  return u_hasBinaryProperty(c, UCHAR_EMOJI);\n}\n\nbool isEmojiModifier(uint32_t c) {\n  return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER);\n}\n\nbool isEmojiBase(uint32_t c) {\n  // These two characters were removed from Emoji_Modifier_Base in Emoji 4.0,\n  // but we need to keep them as emoji modifier bases since there are fonts and\n  // user-generated text out there that treats these as potential emoji bases.\n  if (c == 0x1F91D || c == 0x1F93C) {\n    return true;\n  }\n  return u_hasBinaryProperty(c, UCHAR_EMOJI_MODIFIER_BASE);\n}\n\nUCharDirection emojiBidiOverride(const void* /* context */, UChar32 c) {\n  return u_charDirection(c);\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/Emoji.h",
    "content": "/*\n * Copyright (C) 2014 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_EMOJI_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_EMOJI_H_\n#include <unicode/uchar.h>\n\nnamespace minikin {\n\n// Returns true if c is emoji.\nbool isEmoji(uint32_t c);\n\n// Returns true if c is emoji modifier base.\nbool isEmojiBase(uint32_t c);\n\n// Returns true if c is emoji modifier.\nbool isEmojiModifier(uint32_t c);\n\n// Bidi override for ICU that knows about new emoji.\nUCharDirection emojiBidiOverride(const void* context, UChar32 c);\n\n}  // namespace minikin\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_EMOJI_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontCollection.cpp",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// #define VERBOSE_DEBUG\n\n#define LOG_TAG \"Minikin\"\n\n#include <algorithm>\n\n#include <log/log.h>\n#include \"unicode/unistr.h\"\n#include \"unicode/unorm2.h\"\n#include \"unicode/utf16.h\"\n\n#include <minikin/Emoji.h>\n#include <minikin/FontCollection.h>\n#include \"FontLanguage.h\"\n#include \"FontLanguageListCache.h\"\n#include \"MinikinInternal.h\"\n\nusing std::vector;\n\nnamespace minikin {\n\ntemplate <typename T>\nstatic inline T max(T a, T b) {\n  return a > b ? a : b;\n}\n\nconst uint32_t EMOJI_STYLE_VS = 0xFE0F;\nconst uint32_t TEXT_STYLE_VS = 0xFE0E;\n\nuint32_t FontCollection::sNextId = 0;\n\n// libtxt: return a locale string for a language list ID\nstd::string GetFontLocale(uint32_t langListId) {\n  const FontLanguages& langs = FontLanguageListCache::getById(langListId);\n  return langs.size() ? langs[0].getString() : \"\";\n}\n\nstd::shared_ptr<minikin::FontCollection> FontCollection::Create(\n    const std::vector<std::shared_ptr<FontFamily>>& typefaces) {\n  std::shared_ptr<minikin::FontCollection> font_collection(\n      new minikin::FontCollection());\n  if (!font_collection || !font_collection->init(typefaces)) {\n    return nullptr;\n  }\n  return font_collection;\n}\n\nFontCollection::FontCollection() : mMaxChar(0) {}\n\nbool FontCollection::init(\n    const std::vector<std::shared_ptr<FontFamily>>& typefaces) {\n  std::scoped_lock _l(gMinikinLock);\n  mId = sNextId++;\n  vector<uint32_t> lastChar;\n  size_t nTypefaces = typefaces.size();\n#ifdef VERBOSE_DEBUG\n  ALOGD(\"nTypefaces = %zd\\n\", nTypefaces);\n#endif\n  const FontStyle defaultStyle;\n  for (size_t i = 0; i < nTypefaces; i++) {\n    const std::shared_ptr<FontFamily>& family = typefaces[i];\n    if (family->getClosestMatch(defaultStyle).font == nullptr) {\n      continue;\n    }\n    const SparseBitSet& coverage = family->getCoverage();\n    mFamilies.push_back(family);  // emplace_back would be better\n    if (family->hasVSTable()) {\n      mVSFamilyVec.push_back(family);\n    }\n    mMaxChar = max(mMaxChar, coverage.length());\n    lastChar.push_back(coverage.nextSetBit(0));\n\n    const std::unordered_set<AxisTag>& supportedAxes = family->supportedAxes();\n    mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());\n  }\n  nTypefaces = mFamilies.size();\n  if (nTypefaces == 0) {\n    ALOGE(\"Font collection must have at least one valid typeface.\");\n    return false;\n  }\n  if (nTypefaces > 254) {\n    ALOGE(\"Font collection may only have up to 254 font families.\");\n    return false;\n  }\n  size_t nPages = (mMaxChar + kPageMask) >> kLogCharsPerPage;\n  // TODO: Use variation selector map for mRanges construction.\n  // A font can have a glyph for a base code point and variation selector pair\n  // but no glyph for the base code point without variation selector. The family\n  // won't be listed in the range in this case.\n  for (size_t i = 0; i < nPages; i++) {\n    Range dummy;\n    mRanges.push_back(dummy);\n    Range* range = &mRanges.back();\n#ifdef VERBOSE_DEBUG\n    ALOGD(\"i=%zd: range start = %zd\\n\", i, offset);\n#endif\n    range->start = mFamilyVec.size();\n    for (size_t j = 0; j < nTypefaces; j++) {\n      if (lastChar[j] < (i + 1) << kLogCharsPerPage) {\n        const std::shared_ptr<FontFamily>& family = mFamilies[j];\n        mFamilyVec.push_back(static_cast<uint8_t>(j));\n        uint32_t nextChar =\n            family->getCoverage().nextSetBit((i + 1) << kLogCharsPerPage);\n#ifdef VERBOSE_DEBUG\n        ALOGD(\"nextChar = %d (j = %zd)\\n\", nextChar, j);\n#endif\n        lastChar[j] = nextChar;\n      }\n    }\n    range->end = mFamilyVec.size();\n  }\n\n  if (mFamilyVec.size() >= 0xFFFF) {\n    ALOGE(\"Exceeded the maximum indexable cmap coverage.\");\n    return false;\n  }\n  return true;\n}\n\n// Special scores for the font fallback.\nconst uint32_t kUnsupportedFontScore = 0;\nconst uint32_t kFirstFontScore = UINT32_MAX;\n\n// Calculates a font score.\n// The score of the font family is based on three subscores.\n//  - Coverage Score: How well the font family covers the given character or\n//  variation sequence.\n//  - Language Score: How well the font family is appropriate for the language.\n//  - Variant Score: Whether the font family matches the variant. Note that this\n//  variant is not the\n//    one in BCP47. This is our own font variant (e.g., elegant, compact).\n//\n// Then, there is a priority for these three subscores as follow:\n//   Coverage Score > Language Score > Variant Score\n// The returned score reflects this priority order.\n//\n// Note that there are two special scores.\n//  - kUnsupportedFontScore: When the font family doesn't support the variation\n//  sequence or even its\n//    base character.\n//  - kFirstFontScore: When the font is the first font family in the collection\n//  and it supports the\n//    given character or variation sequence.\nuint32_t FontCollection::calcFamilyScore(\n    uint32_t ch,\n    uint32_t vs,\n    int variant,\n    uint32_t langListId,\n    const std::shared_ptr<FontFamily>& fontFamily) const {\n  const uint32_t coverageScore = calcCoverageScore(ch, vs, fontFamily);\n  if (coverageScore == kFirstFontScore ||\n      coverageScore == kUnsupportedFontScore) {\n    // No need to calculate other scores.\n    return coverageScore;\n  }\n\n  const uint32_t languageScore =\n      calcLanguageMatchingScore(langListId, *fontFamily);\n  const uint32_t variantScore = calcVariantMatchingScore(variant, *fontFamily);\n\n  // Subscores are encoded into 31 bits representation to meet the subscore\n  // priority. The highest 2 bits are for coverage score, then following 28 bits\n  // are for language score, then the last 1 bit is for variant score.\n  return coverageScore << 29 | languageScore << 1 | variantScore;\n}\n\n// Calculates a font score based on variation sequence coverage.\n// - Returns kUnsupportedFontScore if the font doesn't support the variation\n// sequence or its base\n//   character.\n// - Returns kFirstFontScore if the font family is the first font family in the\n// collection and it\n//   supports the given character or variation sequence.\n// - Returns 3 if the font family supports the variation sequence.\n// - Returns 2 if the vs is a color variation selector (U+FE0F) and if the font\n// is an emoji font.\n// - Returns 2 if the vs is a text variation selector (U+FE0E) and if the font\n// is not an emoji font.\n// - Returns 1 if the variation selector is not specified or if the font family\n// only supports the\n//   variation sequence's base character.\nuint32_t FontCollection::calcCoverageScore(\n    uint32_t ch,\n    uint32_t vs,\n    const std::shared_ptr<FontFamily>& fontFamily) const {\n  const bool hasVSGlyph = (vs != 0) && fontFamily->hasGlyph(ch, vs);\n  if (!hasVSGlyph && !fontFamily->getCoverage().get(ch)) {\n    // The font doesn't support either variation sequence or even the base\n    // character.\n    return kUnsupportedFontScore;\n  }\n\n  if ((vs == 0 || hasVSGlyph) && mFamilies[0] == fontFamily) {\n    // If the first font family supports the given character or variation\n    // sequence, always use it.\n    return kFirstFontScore;\n  }\n\n  if (vs == 0) {\n    return 1;\n  }\n\n  if (hasVSGlyph) {\n    return 3;\n  }\n\n  if (vs == EMOJI_STYLE_VS || vs == TEXT_STYLE_VS) {\n    const FontLanguages& langs =\n        FontLanguageListCache::getById(fontFamily->langId());\n    bool hasEmojiFlag = false;\n    for (size_t i = 0; i < langs.size(); ++i) {\n      if (langs[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {\n        hasEmojiFlag = true;\n        break;\n      }\n    }\n\n    if (vs == EMOJI_STYLE_VS) {\n      return hasEmojiFlag ? 2 : 1;\n    } else {  // vs == TEXT_STYLE_VS\n      return hasEmojiFlag ? 1 : 2;\n    }\n  }\n  return 1;\n}\n\n// Calculate font scores based on the script matching, subtag matching and\n// primary language matching.\n//\n// 1. If only the font's language matches or there is no matches between\n// requested font and\n//    supported font, then the font obtains a score of 0.\n// 2. Without a match in language, considering subtag may change font's\n// EmojiStyle over script,\n//    a match in subtag gets a score of 2 and a match in scripts gains a score\n//    of 1.\n// 3. Regarding to two elements matchings, language-and-subtag matching has a\n// score of 4, while\n//    language-and-script obtains a socre of 3 with the same reason above.\n//\n// If two languages in the requested list have the same language score, the font\n// matching with higher priority language gets a higher score. For example, in\n// the case the user requested language list is \"ja-Jpan,en-Latn\". The score of\n// for the font of \"ja-Jpan\" gets a higher score than the font of \"en-Latn\".\n//\n// To achieve score calculation with priorities, the language score is\n// determined as follows:\n//   LanguageScore = s(0) * 5^(m - 1) + s(1) * 5^(m - 2) + ... + s(m - 2) * 5 +\n//   s(m - 1)\n// Here, m is the maximum number of languages to be compared, and s(i) is the\n// i-th language's matching score. The possible values of s(i) are 0, 1, 2, 3\n// and 4.\nuint32_t FontCollection::calcLanguageMatchingScore(\n    uint32_t userLangListId,\n    const FontFamily& fontFamily) {\n  const FontLanguages& langList =\n      FontLanguageListCache::getById(userLangListId);\n  const FontLanguages& fontLanguages =\n      FontLanguageListCache::getById(fontFamily.langId());\n\n  const size_t maxCompareNum = std::min(langList.size(), FONT_LANGUAGES_LIMIT);\n  uint32_t score = 0;\n  for (size_t i = 0; i < maxCompareNum; ++i) {\n    score = score * 5u + langList[i].calcScoreFor(fontLanguages);\n  }\n  return score;\n}\n\n// Calculates a font score based on variant (\"compact\" or \"elegant\") matching.\n//  - Returns 1 if the font doesn't have variant or the variant matches with the\n//  text style.\n//  - No score if the font has a variant but it doesn't match with the text\n//  style.\nuint32_t FontCollection::calcVariantMatchingScore(\n    int variant,\n    const FontFamily& fontFamily) {\n  return (fontFamily.variant() == 0 || fontFamily.variant() == variant) ? 1 : 0;\n}\n\n// Implement heuristic for choosing best-match font. Here are the rules:\n// 1. If first font in the collection has the character, it wins.\n// 2. Calculate a score for the font family. See comments in calcFamilyScore for\n// the detail.\n// 3. Highest score wins, with ties resolved to the first font.\n// This method never returns nullptr.\nconst std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(\n    uint32_t ch,\n    uint32_t vs,\n    uint32_t langListId,\n    int variant) const {\n  if (ch >= mMaxChar) {\n    // libtxt: check if the fallback font provider can match this character\n    if (mFallbackFontProvider) {\n      const std::shared_ptr<FontFamily>& fallback =\n          findFallbackFont(ch, vs, langListId);\n      if (fallback) {\n        return fallback;\n      }\n    }\n    return mFamilies[0];\n  }\n\n  Range range = mRanges[ch >> kLogCharsPerPage];\n\n  if (vs != 0) {\n    range = {0, static_cast<uint16_t>(mFamilies.size())};\n  }\n\n#ifdef VERBOSE_DEBUG\n  ALOGD(\"querying range %zd:%zd\\n\", range.start, range.end);\n#endif\n  int bestFamilyIndex = -1;\n  uint32_t bestScore = kUnsupportedFontScore;\n  for (size_t i = range.start; i < range.end; i++) {\n    const std::shared_ptr<FontFamily>& family =\n        vs == 0 ? mFamilies[mFamilyVec[i]] : mFamilies[i];\n    const uint32_t score = calcFamilyScore(ch, vs, variant, langListId, family);\n    if (score == kFirstFontScore) {\n      // If the first font family supports the given character or variation\n      // sequence, always use it.\n      return family;\n    }\n    if (score > bestScore) {\n      bestScore = score;\n      bestFamilyIndex = i;\n    }\n  }\n  if (bestFamilyIndex == -1) {\n    // libtxt: check if the fallback font provider can match this character\n    if (mFallbackFontProvider) {\n      const std::shared_ptr<FontFamily>& fallback =\n          findFallbackFont(ch, vs, langListId);\n      if (fallback) {\n        return fallback;\n      }\n    }\n\n    UErrorCode errorCode = U_ZERO_ERROR;\n    const UNormalizer2* normalizer = unorm2_getNFDInstance(&errorCode);\n    if (U_SUCCESS(errorCode)) {\n      UChar decomposed[4];\n      int len =\n          unorm2_getRawDecomposition(normalizer, ch, decomposed, 4, &errorCode);\n      if (U_SUCCESS(errorCode) && len > 0) {\n        int off = 0;\n        U16_NEXT_UNSAFE(decomposed, off, ch);\n        return getFamilyForChar(ch, vs, langListId, variant);\n      }\n    }\n    return mFamilies[0];\n  }\n  return vs == 0 ? mFamilies[mFamilyVec[bestFamilyIndex]]\n                 : mFamilies[bestFamilyIndex];\n}\n\nconst std::shared_ptr<FontFamily>& FontCollection::findFallbackFont(\n    uint32_t ch,\n    uint32_t vs,\n    uint32_t langListId) const {\n  std::string locale = GetFontLocale(langListId);\n\n  const auto it = mCachedFallbackFamilies.find(locale);\n  if (it != mCachedFallbackFamilies.end()) {\n    for (const auto& fallbackFamily : it->second) {\n      if (calcCoverageScore(ch, vs, fallbackFamily)) {\n        return fallbackFamily;\n      }\n    }\n  }\n\n  const std::shared_ptr<FontFamily>& fallback =\n      mFallbackFontProvider->matchFallbackFont(ch, GetFontLocale(langListId));\n\n  if (fallback) {\n    mCachedFallbackFamilies[locale].push_back(fallback);\n  }\n  return fallback;\n}\n\nconst uint32_t NBSP = 0x00A0;\nconst uint32_t SOFT_HYPHEN = 0x00AD;\nconst uint32_t ZWJ = 0x200C;\nconst uint32_t ZWNJ = 0x200D;\nconst uint32_t HYPHEN = 0x2010;\nconst uint32_t NB_HYPHEN = 0x2011;\nconst uint32_t NNBSP = 0x202F;\nconst uint32_t FEMALE_SIGN = 0x2640;\nconst uint32_t MALE_SIGN = 0x2642;\nconst uint32_t STAFF_OF_AESCULAPIUS = 0x2695;\n\n// Characters where we want to continue using existing font run instead of\n// recomputing the best match in the fallback list.\nstatic const uint32_t stickyAllowlist[] = {\n    '!',   ',',         '-',       '.',\n    ':',   ';',         '?',       NBSP,\n    ZWJ,   ZWNJ,        HYPHEN,    NB_HYPHEN,\n    NNBSP, FEMALE_SIGN, MALE_SIGN, STAFF_OF_AESCULAPIUS};\n\nstatic bool isStickyAllowed(uint32_t c) {\n  for (size_t i = 0; i < sizeof(stickyAllowlist) / sizeof(stickyAllowlist[0]);\n       i++) {\n    if (stickyAllowlist[i] == c)\n      return true;\n  }\n  return false;\n}\n\nstatic bool isVariationSelector(uint32_t c) {\n  return (0xFE00 <= c && c <= 0xFE0F) || (0xE0100 <= c && c <= 0xE01EF);\n}\n\nbool FontCollection::hasVariationSelector(uint32_t baseCodepoint,\n                                          uint32_t variationSelector) const {\n  if (!isVariationSelector(variationSelector)) {\n    return false;\n  }\n  if (baseCodepoint >= mMaxChar) {\n    return false;\n  }\n\n  std::scoped_lock _l(gMinikinLock);\n\n  // Currently mRanges can not be used here since it isn't aware of the\n  // variation sequence.\n  for (size_t i = 0; i < mVSFamilyVec.size(); i++) {\n    if (mVSFamilyVec[i]->hasGlyph(baseCodepoint, variationSelector)) {\n      return true;\n    }\n  }\n\n  // Even if there is no cmap format 14 subtable entry for the given sequence,\n  // should return true for <char, text presentation selector> case since we\n  // have special fallback rule for the sequence. Note that we don't need to\n  // restrict this to already standardized variation sequences, since Unicode is\n  // adding variation sequences more frequently now and may even move towards\n  // allowing text and emoji variation selectors on any character.\n  if (variationSelector == TEXT_STYLE_VS) {\n    for (size_t i = 0; i < mFamilies.size(); ++i) {\n      if (!mFamilies[i]->isColorEmojiFamily() &&\n          mFamilies[i]->hasGlyph(baseCodepoint, 0)) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\nvoid FontCollection::itemize(const uint16_t* string,\n                             size_t string_size,\n                             FontStyle style,\n                             vector<Run>* result) const {\n  const uint32_t langListId = style.getLanguageListId();\n  int variant = style.getVariant();\n  const FontFamily* lastFamily = nullptr;\n  Run* run = NULL;\n\n  if (string_size == 0) {\n    return;\n  }\n\n  const uint32_t kEndOfString = 0xFFFFFFFF;\n\n  uint32_t nextCh = 0;\n  uint32_t prevCh = 0;\n  size_t nextUtf16Pos = 0;\n  size_t readLength = 0;\n  U16_NEXT(string, readLength, string_size, nextCh);\n\n  do {\n    const uint32_t ch = nextCh;\n    const size_t utf16Pos = nextUtf16Pos;\n    nextUtf16Pos = readLength;\n    if (readLength < string_size) {\n      U16_NEXT(string, readLength, string_size, nextCh);\n    } else {\n      nextCh = kEndOfString;\n    }\n\n    bool shouldContinueRun = false;\n    if (lastFamily != nullptr) {\n      if (isStickyAllowed(ch)) {\n        // Continue using existing font as long as it has coverage and is\n        // allowed.\n        shouldContinueRun = lastFamily->getCoverage().get(ch);\n      } else if (ch == SOFT_HYPHEN || isVariationSelector(ch)) {\n        // Always continue if the character is the soft hyphen or a variation\n        // selector.\n        shouldContinueRun = true;\n      }\n    }\n\n    if (!shouldContinueRun) {\n      const std::shared_ptr<FontFamily>& family = getFamilyForChar(\n          ch, isVariationSelector(nextCh) ? nextCh : 0, langListId, variant);\n      if (utf16Pos == 0 || family.get() != lastFamily) {\n        size_t start = utf16Pos;\n        // Workaround for combining marks and emoji modifiers until we implement\n        // per-cluster font selection: if a combining mark or an emoji modifier\n        // is found in a different font that also supports the previous\n        // character, attach previous character to the new run. U+20E3 COMBINING\n        // ENCLOSING KEYCAP, used in emoji, is handled properly by this since\n        // it's a combining mark too.\n        if (utf16Pos != 0 &&\n            ((U_GET_GC_MASK(ch) & U_GC_M_MASK) != 0 ||\n             (isEmojiModifier(ch) && isEmojiBase(prevCh))) &&\n            family != nullptr && family->getCoverage().get(prevCh)) {\n          const size_t prevChLength = U16_LENGTH(prevCh);\n          run->end -= prevChLength;\n          if (run->start == run->end) {\n            result->pop_back();\n          }\n          start -= prevChLength;\n        }\n        result->push_back(\n            {family->getClosestMatch(style), static_cast<int>(start), 0});\n        run = &result->back();\n        lastFamily = family.get();\n      }\n    }\n    prevCh = ch;\n    run->end = nextUtf16Pos;  // exclusive\n  } while (nextCh != kEndOfString);\n}\n\nFakedFont FontCollection::baseFontFaked(FontStyle style) {\n  return mFamilies[0]->getClosestMatch(style);\n}\n\nstd::shared_ptr<FontCollection> FontCollection::createCollectionWithVariation(\n    const std::vector<FontVariation>& variations) {\n  if (variations.empty() || mSupportedAxes.empty()) {\n    return nullptr;\n  }\n\n  bool hasSupportedAxis = false;\n  for (const FontVariation& variation : variations) {\n    if (mSupportedAxes.find(variation.axisTag) != mSupportedAxes.end()) {\n      hasSupportedAxis = true;\n      break;\n    }\n  }\n  if (!hasSupportedAxis) {\n    // None of variation axes are supported by this font collection.\n    return nullptr;\n  }\n\n  std::vector<std::shared_ptr<FontFamily>> families;\n  for (const std::shared_ptr<FontFamily>& family : mFamilies) {\n    std::shared_ptr<FontFamily> newFamily =\n        family->createFamilyWithVariation(variations);\n    if (newFamily) {\n      families.push_back(newFamily);\n    } else {\n      families.push_back(family);\n    }\n  }\n\n  return FontCollection::Create(std::move(families));\n}\n\nuint32_t FontCollection::getId() const {\n  return mId;\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontCollection.h",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTCOLLECTION_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTCOLLECTION_H_\n\n#include <map>\n#include <utility>\n#include <string>\n#include <memory>\n#include <unordered_set>\n#include <vector>\n\n#include <minikin/FontFamily.h>\n#include <minikin/MinikinFont.h>\n\nnamespace minikin {\n\nclass FontCollection {\n private:\n  explicit FontCollection();\n\n public:\n  static std::shared_ptr<minikin::FontCollection> Create(\n      const std::vector<std::shared_ptr<FontFamily>>& typefaces);\n\n  // libtxt extension: an interface for looking up fallback fonts for characters\n  // that do not match this collection's font families.\n  class FallbackFontProvider {\n   public:\n    virtual ~FallbackFontProvider() = default;\n    virtual const std::shared_ptr<FontFamily>& matchFallbackFont(\n        uint32_t ch,\n        std::string locale) = 0;\n  };\n\n  struct Run {\n    FakedFont fakedFont;\n    int start;\n    int end;\n  };\n\n  void itemize(const uint16_t* string,\n               size_t string_length,\n               FontStyle style,\n               std::vector<Run>* result) const;\n\n  // Returns true if there is a glyph for the code point and variation selector\n  // pair. Returns false if no fonts have a glyph for the code point and\n  // variation selector pair, or invalid variation selector is passed.\n  bool hasVariationSelector(uint32_t baseCodepoint,\n                            uint32_t variationSelector) const;\n\n  // Get base font with fakery information (fake bold could affect metrics)\n  FakedFont baseFontFaked(FontStyle style);\n\n  // Creates new FontCollection based on this collection while applying font\n  // variations. Returns nullptr if none of variations apply to this collection.\n  std::shared_ptr<FontCollection> createCollectionWithVariation(\n      const std::vector<FontVariation>& variations);\n\n  const std::unordered_set<AxisTag>& getSupportedTags() const {\n    return mSupportedAxes;\n  }\n\n  uint32_t getId() const;\n\n  void set_fallback_font_provider(std::unique_ptr<FallbackFontProvider> ffp) {\n    mFallbackFontProvider = std::move(ffp);\n  }\n\n private:\n  static const int kLogCharsPerPage = 8;\n  static const int kPageMask = (1 << kLogCharsPerPage) - 1;\n\n  // mFamilyVec holds the indices of the mFamilies and mRanges holds the range\n  // of indices of mFamilyVec. The maximum number of pages is 0x10FF (U+10FFFF\n  // >> 8). The maximum number of the fonts is 0xFF. Thus, technically the\n  // maximum length of mFamilyVec is 0x10EE01 (0x10FF * 0xFF). However, in\n  // practice, 16-bit integers are enough since most fonts supports only limited\n  // range of code points.\n  struct Range {\n    uint16_t start;\n    uint16_t end;\n  };\n\n  // Initialize the FontCollection.\n  bool init(const std::vector<std::shared_ptr<FontFamily>>& typefaces);\n\n  const std::shared_ptr<FontFamily>& getFamilyForChar(uint32_t ch,\n                                                      uint32_t vs,\n                                                      uint32_t langListId,\n                                                      int variant) const;\n\n  const std::shared_ptr<FontFamily>&\n  findFallbackFont(uint32_t ch, uint32_t vs, uint32_t langListId) const;\n\n  uint32_t calcFamilyScore(uint32_t ch,\n                           uint32_t vs,\n                           int variant,\n                           uint32_t langListId,\n                           const std::shared_ptr<FontFamily>& fontFamily) const;\n\n  uint32_t calcCoverageScore(\n      uint32_t ch,\n      uint32_t vs,\n      const std::shared_ptr<FontFamily>& fontFamily) const;\n\n  static uint32_t calcLanguageMatchingScore(uint32_t userLangListId,\n                                            const FontFamily& fontFamily);\n\n  static uint32_t calcVariantMatchingScore(int variant,\n                                           const FontFamily& fontFamily);\n\n  // static for allocating unique id's\n  static uint32_t sNextId;\n\n  // unique id for this font collection (suitable for cache key)\n  uint32_t mId;\n\n  // Highest UTF-32 code point that can be mapped\n  uint32_t mMaxChar;\n\n  // This vector has pointers to the all font family instances in this\n  // collection. This vector can't be empty.\n  std::vector<std::shared_ptr<FontFamily>> mFamilies;\n\n  // Following two vectors are pre-calculated tables for resolving coverage\n  // faster. For example, to iterate over all fonts which support Unicode code\n  // point U+XXYYZZ, iterate font families index from\n  // mFamilyVec[mRanges[0xXXYY].start] to mFamilyVec[mRange[0xXXYY].end] instead\n  // of whole mFamilies. This vector contains indices into mFamilies. This\n  // vector can't be empty.\n  std::vector<Range> mRanges;\n  std::vector<uint8_t> mFamilyVec;\n\n  // This vector has pointers to the font family instances which have cmap 14\n  // subtables.\n  std::vector<std::shared_ptr<FontFamily>> mVSFamilyVec;\n\n  // Set of supported axes in this collection.\n  std::unordered_set<AxisTag> mSupportedAxes;\n\n  // libtxt extension: Fallback font provider.\n  std::unique_ptr<FallbackFontProvider> mFallbackFontProvider;\n\n  // libtxt extension: Fallback fonts discovered after this font collection\n  // was constructed.\n  mutable std::map<std::string, std::vector<std::shared_ptr<FontFamily>>>\n      mCachedFallbackFamilies;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTCOLLECTION_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontFamily.cpp",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <log/log.h>\n#include <utils/JenkinsHash.h>\n\n#include <hb-ot.h>\n#include <hb.h>\n\n#include <minikin/CmapCoverage.h>\n#include <minikin/FontFamily.h>\n#include <minikin/MinikinFont.h>\n#include \"FontLanguage.h\"\n#include \"FontLanguageListCache.h\"\n#include \"FontUtils.h\"\n#include \"HbFontCache.h\"\n#include \"MinikinInternal.h\"\n\nusing std::vector;\n\nnamespace minikin {\n\nFontStyle::FontStyle(int variant, int weight, bool italic)\n    : FontStyle(FontLanguageListCache::kEmptyListId, variant, weight, italic) {}\n\nFontStyle::FontStyle(uint32_t languageListId,\n                     int variant,\n                     int weight,\n                     bool italic)\n    : bits(pack(variant, weight, italic)), mLanguageListId(languageListId) {}\n\nandroid::hash_t FontStyle::hash() const {\n  uint32_t hash = android::JenkinsHashMix(0, bits);\n  hash = android::JenkinsHashMix(hash, mLanguageListId);\n  return android::JenkinsHashWhiten(hash);\n}\n\n// static\nuint32_t FontStyle::registerLanguageList(const std::string& languages) {\n  std::scoped_lock _l(gMinikinLock);\n  return FontLanguageListCache::getId(languages);\n}\n\n// static\nuint32_t FontStyle::pack(int variant, int weight, bool italic) {\n  return (weight & kWeightMask) | (italic ? kItalicMask : 0) |\n         (variant << kVariantShift);\n}\n\nFont::Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style)\n    : typeface(typeface), style(style) {}\n\nFont::Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style)\n    : typeface(typeface), style(style) {}\n\nstd::unordered_set<AxisTag> Font::getSupportedAxesLocked() const {\n  const uint32_t fvarTag = MinikinFont::MakeTag('f', 'v', 'a', 'r');\n  HbBlob fvarTable(getFontTable(typeface.get(), fvarTag));\n  if (fvarTable.size() == 0) {\n    return std::unordered_set<AxisTag>();\n  }\n\n  std::unordered_set<AxisTag> supportedAxes;\n  analyzeAxes(fvarTable.get(), fvarTable.size(), &supportedAxes);\n  return supportedAxes;\n}\n\nFont::Font(Font&& o) {\n  typeface = std::move(o.typeface);\n  style = o.style;\n  o.typeface = nullptr;\n}\n\nFont::Font(const Font& o) {\n  typeface = o.typeface;\n  style = o.style;\n}\n\n// static\nFontFamily::FontFamily(std::vector<Font>&& fonts)\n    : FontFamily(0 /* variant */, std::move(fonts)) {}\n\nFontFamily::FontFamily(int variant, std::vector<Font>&& fonts)\n    : FontFamily(FontLanguageListCache::kEmptyListId,\n                 variant,\n                 std::move(fonts)) {}\n\nFontFamily::FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts)\n    : mLangId(langId),\n      mVariant(variant),\n      mFonts(std::move(fonts)),\n      mHasVSTable(false) {\n  computeCoverage();\n}\n\nbool FontFamily::analyzeStyle(const std::shared_ptr<MinikinFont>& typeface,\n                              int* weight,\n                              bool* italic) {\n  std::scoped_lock _l(gMinikinLock);\n  const uint32_t os2Tag = MinikinFont::MakeTag('O', 'S', '/', '2');\n  HbBlob os2Table(getFontTable(typeface.get(), os2Tag));\n  if (os2Table.get() == nullptr)\n    return false;\n  return ::minikin::analyzeStyle(os2Table.get(), os2Table.size(), weight,\n                                 italic);\n}\n\n// Compute a matching metric between two styles - 0 is an exact match\nstatic int computeMatch(FontStyle style1, FontStyle style2) {\n  if (style1 == style2)\n    return 0;\n  int score = abs(style1.getWeight() - style2.getWeight());\n  if (style1.getItalic() != style2.getItalic()) {\n    score += 2;\n  }\n  return score;\n}\n\nstatic FontFakery computeFakery(FontStyle wanted, FontStyle actual) {\n  // If desired weight is semibold or darker, and 2 or more grades\n  // higher than actual (for example, medium 500 -> bold 700), then\n  // select fake bold.\n  int wantedWeight = wanted.getWeight();\n  bool isFakeBold =\n      wantedWeight >= 6 && (wantedWeight - actual.getWeight()) >= 2;\n  bool isFakeItalic = wanted.getItalic() && !actual.getItalic();\n  return FontFakery(isFakeBold, isFakeItalic);\n}\n\nFakedFont FontFamily::getClosestMatch(FontStyle style) const {\n  const Font* bestFont = nullptr;\n  int bestMatch = 0;\n  for (size_t i = 0; i < mFonts.size(); i++) {\n    const Font& font = mFonts[i];\n    int match = computeMatch(font.style, style);\n    if (i == 0 || match < bestMatch) {\n      bestFont = &font;\n      bestMatch = match;\n    }\n  }\n  if (bestFont != nullptr) {\n    return FakedFont{bestFont->typeface.get(),\n                     computeFakery(style, bestFont->style)};\n  }\n  return FakedFont{nullptr, FontFakery()};\n}\n\nbool FontFamily::isColorEmojiFamily() const {\n  const FontLanguages& languageList = FontLanguageListCache::getById(mLangId);\n  for (size_t i = 0; i < languageList.size(); ++i) {\n    if (languageList[i].getEmojiStyle() == FontLanguage::EMSTYLE_EMOJI) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid FontFamily::computeCoverage() {\n  std::scoped_lock _l(gMinikinLock);\n  const FontStyle defaultStyle;\n  const MinikinFont* typeface = getClosestMatch(defaultStyle).font;\n  const uint32_t cmapTag = MinikinFont::MakeTag('c', 'm', 'a', 'p');\n  HbBlob cmapTable(getFontTable(typeface, cmapTag));\n  if (cmapTable.get() == nullptr) {\n    // Missing or corrupt font cmap table; bail out.\n    // The cmap table maps charcodes to glyph indices in a font.\n    return;\n  }\n  mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(),\n                                        &mHasVSTable);\n\n  for (size_t i = 0; i < mFonts.size(); ++i) {\n    std::unordered_set<AxisTag> supportedAxes =\n        mFonts[i].getSupportedAxesLocked();\n    mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end());\n  }\n}\n\nbool FontFamily::hasGlyph(uint32_t codepoint,\n                          uint32_t variationSelector) const {\n  assertMinikinLocked();\n  if (variationSelector != 0 && !mHasVSTable) {\n    // Early exit if the variation selector is specified but the font doesn't\n    // have a cmap format 14 subtable.\n    return false;\n  }\n\n  const FontStyle defaultStyle;\n  hb_font_t* font = getHbFontLocked(getClosestMatch(defaultStyle).font);\n  uint32_t unusedGlyph;\n  bool result =\n      hb_font_get_glyph(font, codepoint, variationSelector, &unusedGlyph);\n  hb_font_destroy(font);\n  return result;\n}\n\nstd::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation(\n    const std::vector<FontVariation>& variations) const {\n  if (variations.empty() || mSupportedAxes.empty()) {\n    return nullptr;\n  }\n\n  bool hasSupportedAxis = false;\n  for (const FontVariation& variation : variations) {\n    if (mSupportedAxes.find(variation.axisTag) != mSupportedAxes.end()) {\n      hasSupportedAxis = true;\n      break;\n    }\n  }\n  if (!hasSupportedAxis) {\n    // None of variation axes are suppored by this family.\n    return nullptr;\n  }\n\n  std::vector<Font> fonts;\n  for (const Font& font : mFonts) {\n    bool supportedVariations = false;\n    std::scoped_lock _l(gMinikinLock);\n    std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxesLocked();\n    if (!supportedAxes.empty()) {\n      for (const FontVariation& variation : variations) {\n        if (supportedAxes.find(variation.axisTag) != supportedAxes.end()) {\n          supportedVariations = true;\n          break;\n        }\n      }\n    }\n    std::shared_ptr<MinikinFont> minikinFont;\n    if (supportedVariations) {\n      minikinFont = font.typeface->createFontWithVariation(variations);\n    }\n    if (minikinFont == nullptr) {\n      minikinFont = font.typeface;\n    }\n    fonts.push_back(Font(std::move(minikinFont), font.style));\n  }\n\n  return std::shared_ptr<FontFamily>(\n      new FontFamily(mLangId, mVariant, std::move(fonts)));\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontFamily.h",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTFAMILY_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTFAMILY_H_\n\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <vector>\n\n#include <hb.h>\n\n#include <utils/TypeHelpers.h>\n\n#include <minikin/SparseBitSet.h>\n\nnamespace minikin {\n\nclass MinikinFont;\n\n// FontStyle represents all style information needed to select an actual font\n// from a collection. The implementation is packed into two 32-bit words\n// so it can be efficiently copied, embedded in other objects, etc.\nclass FontStyle {\n public:\n  FontStyle()\n      : FontStyle(0 /* variant */, 4 /* weight */, false /* italic */) {}\n  FontStyle(int weight, bool italic)\n      : FontStyle(0 /* variant */, weight, italic) {}\n  FontStyle(uint32_t langListId)  // NOLINT(google-explicit-constructor)\n      : FontStyle(langListId,\n                  0 /* variant */,\n                  4 /* weight */,\n                  false /* italic */) {}\n\n  FontStyle(int variant, int weight, bool italic);\n  FontStyle(uint32_t langListId, int variant, int weight, bool italic);\n\n  int getWeight() const { return bits & kWeightMask; }\n  bool getItalic() const { return (bits & kItalicMask) != 0; }\n  int getVariant() const { return (bits >> kVariantShift) & kVariantMask; }\n  uint32_t getLanguageListId() const { return mLanguageListId; }\n\n  bool operator==(const FontStyle other) const {\n    return bits == other.bits && mLanguageListId == other.mLanguageListId;\n  }\n\n  android::hash_t hash() const;\n\n  // Looks up a language list from an internal cache and returns its ID.\n  // If the passed language list is not in the cache, registers it and returns\n  // newly assigned ID.\n  static uint32_t registerLanguageList(const std::string& languages);\n\n private:\n  static const uint32_t kWeightMask = (1 << 4) - 1;\n  static const uint32_t kItalicMask = 1 << 4;\n  static const int kVariantShift = 5;\n  static const uint32_t kVariantMask = (1 << 2) - 1;\n\n  static uint32_t pack(int variant, int weight, bool italic);\n\n  uint32_t bits;\n  uint32_t mLanguageListId;\n};\n\nenum FontVariant {\n  VARIANT_DEFAULT = 0,\n  VARIANT_COMPACT = 1,\n  VARIANT_ELEGANT = 2,\n};\n\ninline android::hash_t hash_type(const FontStyle& style) {\n  return style.hash();\n}\n\n// attributes representing transforms (fake bold, fake italic) to match styles\nclass FontFakery {\n public:\n  FontFakery() : mFakeBold(false), mFakeItalic(false) {}\n  FontFakery(bool fakeBold, bool fakeItalic)\n      : mFakeBold(fakeBold), mFakeItalic(fakeItalic) {}\n  // TODO: want to support graded fake bolding\n  bool isFakeBold() { return mFakeBold; }\n  bool isFakeItalic() { return mFakeItalic; }\n\n private:\n  bool mFakeBold;\n  bool mFakeItalic;\n};\n\nstruct FakedFont {\n  // ownership is the enclosing FontCollection\n  MinikinFont* font;\n  FontFakery fakery;\n};\n\ntypedef uint32_t AxisTag;\n\nstruct Font {\n  Font(const std::shared_ptr<MinikinFont>& typeface, FontStyle style);\n  Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style);\n  Font(Font&& o);\n  Font(const Font& o);\n\n  std::shared_ptr<MinikinFont> typeface;\n  FontStyle style;\n\n  std::unordered_set<AxisTag> getSupportedAxesLocked() const;\n};\n\nstruct FontVariation {\n  FontVariation(AxisTag axisTag, float value)\n      : axisTag(axisTag), value(value) {}\n  AxisTag axisTag;\n  float value;\n};\n\nclass FontFamily {\n public:\n  explicit FontFamily(std::vector<Font>&& fonts);\n  FontFamily(int variant, std::vector<Font>&& fonts);\n  FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts);\n\n  // TODO: Good to expose FontUtil.h.\n  static bool analyzeStyle(const std::shared_ptr<MinikinFont>& typeface,\n                           int* weight,\n                           bool* italic);\n  FakedFont getClosestMatch(FontStyle style) const;\n\n  uint32_t langId() const { return mLangId; }\n  int variant() const { return mVariant; }\n\n  // API's for enumerating the fonts in a family. These don't guarantee any\n  // particular order\n  size_t getNumFonts() const { return mFonts.size(); }\n  const std::shared_ptr<MinikinFont>& getFont(size_t index) const {\n    return mFonts[index].typeface;\n  }\n  FontStyle getStyle(size_t index) const { return mFonts[index].style; }\n  bool isColorEmojiFamily() const;\n  const std::unordered_set<AxisTag>& supportedAxes() const {\n    return mSupportedAxes;\n  }\n\n  // Get Unicode coverage.\n  const SparseBitSet& getCoverage() const { return mCoverage; }\n\n  // Returns true if the font has a glyph for the code point and variation\n  // selector pair. Caller should acquire a lock before calling the method.\n  bool hasGlyph(uint32_t codepoint, uint32_t variationSelector) const;\n\n  // Returns true if this font family has a variaion sequence table (cmap format\n  // 14 subtable).\n  bool hasVSTable() const { return mHasVSTable; }\n\n  // Creates new FontFamily based on this family while applying font variations.\n  // Returns nullptr if none of variations apply to this family.\n  std::shared_ptr<FontFamily> createFamilyWithVariation(\n      const std::vector<FontVariation>& variations) const;\n\n private:\n  void computeCoverage();\n\n  uint32_t mLangId;\n  int mVariant;\n  std::vector<Font> mFonts;\n  std::unordered_set<AxisTag> mSupportedAxes;\n\n  SparseBitSet mCoverage;\n  bool mHasVSTable;\n\n  // Forbid copying and assignment.\n  FontFamily(const FontFamily&) = delete;\n  void operator=(const FontFamily&) = delete;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTFAMILY_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontLanguage.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include \"FontLanguage.h\"\n\n#include <hb.h>\n#include <string.h>\n#include <unicode/uloc.h>\n#include <algorithm>\n\nnamespace minikin {\n\n#define SCRIPT_TAG(c1, c2, c3, c4)                                           \\\n  (((uint32_t)(c1)) << 24 | ((uint32_t)(c2)) << 16 | ((uint32_t)(c3)) << 8 | \\\n   ((uint32_t)(c4)))\n\n// Check if a language code supports emoji according to its subtag\nstatic bool isEmojiSubtag(const char* buf,\n                          size_t bufLen,\n                          const char* subtag,\n                          size_t subtagLen) {\n  if (bufLen < subtagLen) {\n    return false;\n  }\n  if (strncmp(buf, subtag, subtagLen) != 0) {\n    return false;  // no match between two strings\n  }\n  return (bufLen == subtagLen || buf[subtagLen] == '\\0' ||\n          buf[subtagLen] == '-' || buf[subtagLen] == '_');\n}\n\n// Pack the three letter code into 15 bits and stored to 16 bit integer. The\n// highest bit is 0. For the region code, the letters must be all digits in\n// three letter case, so the number of possible values are 10. For the language\n// code, the letters must be all small alphabets, so the number of possible\n// values are 26. Thus, 5 bits are sufficient for each case and we can pack the\n// three letter language code or region code to 15 bits.\n//\n// In case of two letter code, use fullbit(0x1f) for the first letter instead.\nstatic uint16_t packLanguageOrRegion(const char* c,\n                                     size_t length,\n                                     uint8_t twoLetterBase,\n                                     uint8_t threeLetterBase) {\n  if (length == 2) {\n    return 0x7c00u |  // 0x1fu << 10\n           (uint16_t)(c[0] - twoLetterBase) << 5 |\n           (uint16_t)(c[1] - twoLetterBase);\n  } else {\n    return ((uint16_t)(c[0] - threeLetterBase) << 10) |\n           (uint16_t)(c[1] - threeLetterBase) << 5 |\n           (uint16_t)(c[2] - threeLetterBase);\n  }\n}\n\nstatic size_t unpackLanguageOrRegion(uint16_t in,\n                                     char* out,\n                                     uint8_t twoLetterBase,\n                                     uint8_t threeLetterBase) {\n  uint8_t first = (in >> 10) & 0x1f;\n  uint8_t second = (in >> 5) & 0x1f;\n  uint8_t third = in & 0x1f;\n\n  if (first == 0x1f) {\n    out[0] = second + twoLetterBase;\n    out[1] = third + twoLetterBase;\n    return 2;\n  } else {\n    out[0] = first + threeLetterBase;\n    out[1] = second + threeLetterBase;\n    out[2] = third + threeLetterBase;\n    return 3;\n  }\n}\n\n// Find the next '-' or '_' index from startOffset position. If not found,\n// returns bufferLength.\nstatic size_t nextDelimiterIndex(const char* buffer,\n                                 size_t bufferLength,\n                                 size_t startOffset) {\n  for (size_t i = startOffset; i < bufferLength; ++i) {\n    if (buffer[i] == '-' || buffer[i] == '_') {\n      return i;\n    }\n  }\n  return bufferLength;\n}\n\nstatic inline bool isLowercase(char c) {\n  return 'a' <= c && c <= 'z';\n}\n\nstatic inline bool isUppercase(char c) {\n  return 'A' <= c && c <= 'Z';\n}\n\nstatic inline bool isDigit(char c) {\n  return '0' <= c && c <= '9';\n}\n\n// Returns true if the buffer is valid for language code.\nstatic inline bool isValidLanguageCode(const char* buffer, size_t length) {\n  if (length != 2 && length != 3)\n    return false;\n  if (!isLowercase(buffer[0]))\n    return false;\n  if (!isLowercase(buffer[1]))\n    return false;\n  if (length == 3 && !isLowercase(buffer[2]))\n    return false;\n  return true;\n}\n\n// Returns true if buffer is valid for script code. The length of buffer must\n// be 4.\nstatic inline bool isValidScriptCode(const char* buffer) {\n  return isUppercase(buffer[0]) && isLowercase(buffer[1]) &&\n         isLowercase(buffer[2]) && isLowercase(buffer[3]);\n}\n\n// Returns true if the buffer is valid for region code.\nstatic inline bool isValidRegionCode(const char* buffer, size_t length) {\n  return (length == 2 && isUppercase(buffer[0]) && isUppercase(buffer[1])) ||\n         (length == 3 && isDigit(buffer[0]) && isDigit(buffer[1]) &&\n          isDigit(buffer[2]));\n}\n\n// Parse BCP 47 language identifier into internal structure\nFontLanguage::FontLanguage(const char* buf, size_t length) : FontLanguage() {\n  size_t firstDelimiterPos = nextDelimiterIndex(buf, length, 0);\n  if (isValidLanguageCode(buf, firstDelimiterPos)) {\n    mLanguage = packLanguageOrRegion(buf, firstDelimiterPos, 'a', 'a');\n  } else {\n    // We don't understand anything other than two-letter or three-letter\n    // language codes, so we skip parsing the rest of the string.\n    return;\n  }\n\n  if (firstDelimiterPos == length) {\n    mHbLanguage = hb_language_from_string(getString().c_str(), -1);\n    return;  // Language code only.\n  }\n\n  size_t nextComponentStartPos = firstDelimiterPos + 1;\n  size_t nextDelimiterPos =\n      nextDelimiterIndex(buf, length, nextComponentStartPos);\n  size_t componentLength = nextDelimiterPos - nextComponentStartPos;\n\n  if (componentLength == 4) {\n    // Possibly script code.\n    const char* p = buf + nextComponentStartPos;\n    if (isValidScriptCode(p)) {\n      mScript = SCRIPT_TAG(p[0], p[1], p[2], p[3]);\n      mSubScriptBits = scriptToSubScriptBits(mScript);\n    }\n\n    if (nextDelimiterPos == length) {\n      mHbLanguage = hb_language_from_string(getString().c_str(), -1);\n      mEmojiStyle = resolveEmojiStyle(buf, length, mScript);\n      return;  // No region code.\n    }\n\n    nextComponentStartPos = nextDelimiterPos + 1;\n    nextDelimiterPos = nextDelimiterIndex(buf, length, nextComponentStartPos);\n    componentLength = nextDelimiterPos - nextComponentStartPos;\n  }\n\n  if (componentLength == 2 || componentLength == 3) {\n    // Possibly region code.\n    const char* p = buf + nextComponentStartPos;\n    if (isValidRegionCode(p, componentLength)) {\n      mRegion = packLanguageOrRegion(p, componentLength, 'A', '0');\n    }\n  }\n\n  mHbLanguage = hb_language_from_string(getString().c_str(), -1);\n  mEmojiStyle = resolveEmojiStyle(buf, length, mScript);\n}\n\n// static\nFontLanguage::EmojiStyle FontLanguage::resolveEmojiStyle(const char* buf,\n                                                         size_t length,\n                                                         uint32_t script) {\n  // First, lookup emoji subtag.\n  // 10 is the length of \"-u-em-text\", which is the shortest emoji subtag,\n  // unnecessary comparison can be avoided if total length is smaller than 10.\n  const size_t kMinSubtagLength = 10;\n  if (length >= kMinSubtagLength) {\n    static const char kPrefix[] = \"-u-em-\";\n    const char* pos =\n        std::search(buf, buf + length, kPrefix, kPrefix + strlen(kPrefix));\n    if (pos != buf + length) {  // found\n      pos += strlen(kPrefix);\n      const size_t remainingLength = length - (pos - buf);\n      if (isEmojiSubtag(pos, remainingLength, \"emoji\", 5)) {\n        return EMSTYLE_EMOJI;\n      } else if (isEmojiSubtag(pos, remainingLength, \"text\", 4)) {\n        return EMSTYLE_TEXT;\n      } else if (isEmojiSubtag(pos, remainingLength, \"default\", 7)) {\n        return EMSTYLE_DEFAULT;\n      }\n    }\n  }\n\n  // If no emoji subtag was provided, resolve the emoji style from script code.\n  if (script == SCRIPT_TAG('Z', 's', 'y', 'e')) {\n    return EMSTYLE_EMOJI;\n  } else if (script == SCRIPT_TAG('Z', 's', 'y', 'm')) {\n    return EMSTYLE_TEXT;\n  }\n\n  return EMSTYLE_EMPTY;\n}\n\n// static\nuint8_t FontLanguage::scriptToSubScriptBits(uint32_t script) {\n  uint8_t subScriptBits = 0u;\n  switch (script) {\n    case SCRIPT_TAG('B', 'o', 'p', 'o'):\n      subScriptBits = kBopomofoFlag;\n      break;\n    case SCRIPT_TAG('H', 'a', 'n', 'g'):\n      subScriptBits = kHangulFlag;\n      break;\n    case SCRIPT_TAG('H', 'a', 'n', 'b'):\n      // Bopomofo is almost exclusively used in Taiwan.\n      subScriptBits = kHanFlag | kBopomofoFlag;\n      break;\n    case SCRIPT_TAG('H', 'a', 'n', 'i'):\n      subScriptBits = kHanFlag;\n      break;\n    case SCRIPT_TAG('H', 'a', 'n', 's'):\n      subScriptBits = kHanFlag | kSimplifiedChineseFlag;\n      break;\n    case SCRIPT_TAG('H', 'a', 'n', 't'):\n      subScriptBits = kHanFlag | kTraditionalChineseFlag;\n      break;\n    case SCRIPT_TAG('H', 'i', 'r', 'a'):\n      subScriptBits = kHiraganaFlag;\n      break;\n    case SCRIPT_TAG('H', 'r', 'k', 't'):\n      subScriptBits = kKatakanaFlag | kHiraganaFlag;\n      break;\n    case SCRIPT_TAG('J', 'p', 'a', 'n'):\n      subScriptBits = kHanFlag | kKatakanaFlag | kHiraganaFlag;\n      break;\n    case SCRIPT_TAG('K', 'a', 'n', 'a'):\n      subScriptBits = kKatakanaFlag;\n      break;\n    case SCRIPT_TAG('K', 'o', 'r', 'e'):\n      subScriptBits = kHanFlag | kHangulFlag;\n      break;\n  }\n  return subScriptBits;\n}\n\nstd::string FontLanguage::getString() const {\n  if (isUnsupported()) {\n    return \"und\";\n  }\n  char buf[16];\n  size_t i = unpackLanguageOrRegion(mLanguage, buf, 'a', 'a');\n  if (mScript != 0) {\n    buf[i++] = '-';\n    buf[i++] = (mScript >> 24) & 0xFFu;\n    buf[i++] = (mScript >> 16) & 0xFFu;\n    buf[i++] = (mScript >> 8) & 0xFFu;\n    buf[i++] = mScript & 0xFFu;\n  }\n  if (mRegion != INVALID_CODE) {\n    buf[i++] = '-';\n    i += unpackLanguageOrRegion(mRegion, buf + i, 'A', '0');\n  }\n  return std::string(buf, i);\n}\n\nbool FontLanguage::isEqualScript(const FontLanguage& other) const {\n  return other.mScript == mScript;\n}\n\n// static\nbool FontLanguage::supportsScript(uint8_t providedBits, uint8_t requestedBits) {\n  return requestedBits != 0 && (providedBits & requestedBits) == requestedBits;\n}\n\nbool FontLanguage::supportsHbScript(hb_script_t script) const {\n  static_assert(\n      SCRIPT_TAG('J', 'p', 'a', 'n') == HB_TAG('J', 'p', 'a', 'n'),\n      \"The Minikin script and HarfBuzz hb_script_t have different encodings.\");\n  if (script == mScript)\n    return true;\n  return supportsScript(mSubScriptBits, scriptToSubScriptBits(script));\n}\n\nint FontLanguage::calcScoreFor(const FontLanguages& supported) const {\n  bool languageScriptMatch = false;\n  bool subtagMatch = false;\n  bool scriptMatch = false;\n\n  for (size_t i = 0; i < supported.size(); ++i) {\n    if (mEmojiStyle != EMSTYLE_EMPTY &&\n        mEmojiStyle == supported[i].mEmojiStyle) {\n      subtagMatch = true;\n      if (mLanguage == supported[i].mLanguage) {\n        return 4;\n      }\n    }\n    if (isEqualScript(supported[i]) ||\n        supportsScript(supported[i].mSubScriptBits, mSubScriptBits)) {\n      scriptMatch = true;\n      if (mLanguage == supported[i].mLanguage) {\n        languageScriptMatch = true;\n      }\n    }\n  }\n\n  if (supportsScript(supported.getUnionOfSubScriptBits(), mSubScriptBits)) {\n    scriptMatch = true;\n    if (mLanguage == supported[0].mLanguage &&\n        supported.isAllTheSameLanguage()) {\n      return 3;\n    }\n  }\n\n  if (languageScriptMatch) {\n    return 3;\n  } else if (subtagMatch) {\n    return 2;\n  } else if (scriptMatch) {\n    return 1;\n  }\n  return 0;\n}\n\nFontLanguages::FontLanguages(std::vector<FontLanguage>&& languages)\n    : mLanguages(std::move(languages)) {\n  if (mLanguages.empty()) {\n    return;\n  }\n\n  const FontLanguage& lang = mLanguages[0];\n\n  mIsAllTheSameLanguage = true;\n  mUnionOfSubScriptBits = lang.mSubScriptBits;\n  for (size_t i = 1; i < mLanguages.size(); ++i) {\n    mUnionOfSubScriptBits |= mLanguages[i].mSubScriptBits;\n    if (mIsAllTheSameLanguage && lang.mLanguage != mLanguages[i].mLanguage) {\n      mIsAllTheSameLanguage = false;\n    }\n  }\n}\n\n#undef SCRIPT_TAG\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontLanguage.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTLANGUAGE_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTLANGUAGE_H_\n\n#include <string>\n#include <vector>\n\n#include <hb.h>\n\nnamespace minikin {\n\n// Due to the limits in font fallback score calculation, we can't use anything\n// more than 12 languages.\nconst size_t FONT_LANGUAGES_LIMIT = 12;\n\n// The language or region code is encoded to 15 bits.\nconst uint16_t INVALID_CODE = 0x7fff;\n\nclass FontLanguages;\n\n// FontLanguage is a compact representation of a BCP 47 language tag. It\n// does not capture all possible information, only what directly affects\n// font rendering.\nstruct FontLanguage {\n public:\n  enum EmojiStyle : uint8_t {\n    EMSTYLE_EMPTY = 0,\n    EMSTYLE_DEFAULT = 1,\n    EMSTYLE_EMOJI = 2,\n    EMSTYLE_TEXT = 3,\n  };\n  // Default constructor creates the unsupported language.\n  FontLanguage()\n      : mScript(0ul),\n        mLanguage(INVALID_CODE),\n        mRegion(INVALID_CODE),\n        mHbLanguage(HB_LANGUAGE_INVALID),\n        mSubScriptBits(0ul),\n        mEmojiStyle(EMSTYLE_EMPTY) {}\n\n  // Parse from string\n  FontLanguage(const char* buf, size_t length);\n\n  bool operator==(const FontLanguage other) const {\n    return !isUnsupported() && isEqualScript(other) &&\n           mLanguage == other.mLanguage && mRegion == other.mRegion &&\n           mEmojiStyle == other.mEmojiStyle;\n  }\n\n  bool operator!=(const FontLanguage other) const { return !(*this == other); }\n\n  bool isUnsupported() const { return mLanguage == INVALID_CODE; }\n  EmojiStyle getEmojiStyle() const { return mEmojiStyle; }\n  hb_language_t getHbLanguage() const { return mHbLanguage; }\n\n  bool isEqualScript(const FontLanguage& other) const;\n\n  // Returns true if this script supports the given script. For example, ja-Jpan\n  // supports Hira, ja-Hira doesn't support Jpan.\n  bool supportsHbScript(hb_script_t script) const;\n\n  std::string getString() const;\n\n  // Calculates a matching score. This score represents how well the input\n  // languages cover this language. The maximum score in the language list is\n  // returned. 0 = no match, 1 = script match, 2 = script and primary language\n  // match.\n  int calcScoreFor(const FontLanguages& supported) const;\n\n  uint64_t getIdentifier() const {\n    return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 17) |\n           ((uint64_t)mRegion << 2) | mEmojiStyle;\n  }\n\n private:\n  friend class FontLanguages;  // for FontLanguages constructor\n\n  // ISO 15924 compliant script code. The 4 chars script code are packed into a\n  // 32 bit integer.\n  uint32_t mScript;\n\n  // ISO 639-1 or ISO 639-2 compliant language code.\n  // The two- or three-letter language code is packed into a 15 bit integer.\n  // mLanguage = 0 means the FontLanguage is unsupported.\n  uint16_t mLanguage;\n\n  // ISO 3166-1 or UN M.49 compliant region code. The two-letter or three-digit\n  // region code is packed into a 15 bit integer.\n  uint16_t mRegion;\n\n  // The language to be passed HarfBuzz shaper.\n  hb_language_t mHbLanguage;\n\n  // For faster comparing, use 7 bits for specific scripts.\n  static const uint8_t kBopomofoFlag = 1u;\n  static const uint8_t kHanFlag = 1u << 1;\n  static const uint8_t kHangulFlag = 1u << 2;\n  static const uint8_t kHiraganaFlag = 1u << 3;\n  static const uint8_t kKatakanaFlag = 1u << 4;\n  static const uint8_t kSimplifiedChineseFlag = 1u << 5;\n  static const uint8_t kTraditionalChineseFlag = 1u << 6;\n  uint8_t mSubScriptBits;\n\n  EmojiStyle mEmojiStyle;\n\n  static uint8_t scriptToSubScriptBits(uint32_t script);\n\n  static EmojiStyle resolveEmojiStyle(const char* buf,\n                                      size_t length,\n                                      uint32_t script);\n\n  // Returns true if the provide subscript bits has the requested subscript\n  // bits. Note that this function returns false if the requested subscript bits\n  // are empty.\n  static bool supportsScript(uint8_t providedBits, uint8_t requestedBits);\n};\n\n// An immutable list of languages.\nclass FontLanguages {\n public:\n  explicit FontLanguages(std::vector<FontLanguage>&& languages);\n  FontLanguages() : mUnionOfSubScriptBits(0), mIsAllTheSameLanguage(false) {}\n  FontLanguages(FontLanguages&&) = default;\n\n  size_t size() const { return mLanguages.size(); }\n  bool empty() const { return mLanguages.empty(); }\n  const FontLanguage& operator[](size_t n) const { return mLanguages[n]; }\n\n private:\n  friend struct FontLanguage;  // for calcScoreFor\n\n  std::vector<FontLanguage> mLanguages;\n  uint8_t mUnionOfSubScriptBits;\n  bool mIsAllTheSameLanguage;\n\n  uint8_t getUnionOfSubScriptBits() const { return mUnionOfSubScriptBits; }\n  bool isAllTheSameLanguage() const { return mIsAllTheSameLanguage; }\n\n  // Do not copy and assign.\n  FontLanguages(const FontLanguages&) = delete;\n  void operator=(const FontLanguages&) = delete;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTLANGUAGE_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontLanguageListCache.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include \"FontLanguageListCache.h\"\n\n#include <unicode/uloc.h>\n#include <unicode/umachine.h>\n#include <unordered_set>\n\n#include <log/log.h>\n\n#include \"FontLanguage.h\"\n#include \"MinikinInternal.h\"\n\nnamespace minikin {\n\nconst uint32_t FontLanguageListCache::kEmptyListId;\n\n// Returns the text length of output.\nstatic size_t toLanguageTag(char* output,\n                            size_t outSize,\n                            const std::string& locale) {\n  output[0] = '\\0';\n  if (locale.empty()) {\n    return 0;\n  }\n\n  size_t outLength = 0;\n  UErrorCode uErr = U_ZERO_ERROR;\n  outLength = uloc_canonicalize(locale.c_str(), output, outSize, &uErr);\n  if (U_FAILURE(uErr)) {\n    // unable to build a proper language identifier\n    ALOGD(\"uloc_canonicalize(\\\"%s\\\") failed: %s\", locale.c_str(),\n          u_errorName(uErr));\n    output[0] = '\\0';\n    return 0;\n  }\n\n  // Preserve \"und\" and \"und-****\" since uloc_addLikelySubtags changes \"und\" to\n  // \"en-Latn-US\".\n  if (strncmp(output, \"und\", 3) == 0 &&\n      (outLength == 3 || (outLength == 8 && output[3] == '_'))) {\n    return outLength;\n  }\n\n  char likelyChars[ULOC_FULLNAME_CAPACITY];\n  uErr = U_ZERO_ERROR;\n  uloc_addLikelySubtags(output, likelyChars, ULOC_FULLNAME_CAPACITY, &uErr);\n  if (U_FAILURE(uErr)) {\n    // unable to build a proper language identifier\n    ALOGD(\"uloc_addLikelySubtags(\\\"%s\\\") failed: %s\", output,\n          u_errorName(uErr));\n    output[0] = '\\0';\n    return 0;\n  }\n\n  uErr = U_ZERO_ERROR;\n  outLength =\n      uloc_toLanguageTag(likelyChars, output, outSize, /*false*/ 0, &uErr);\n  if (U_FAILURE(uErr)) {\n    // unable to build a proper language identifier\n    ALOGD(\"uloc_toLanguageTag(\\\"%s\\\") failed: %s\", likelyChars,\n          u_errorName(uErr));\n    output[0] = '\\0';\n    return 0;\n  }\n#ifdef VERBOSE_DEBUG\n  ALOGD(\"ICU normalized '%s' to '%s'\", locale.c_str(), output);\n#endif\n  return outLength;\n}\n\nstatic std::vector<FontLanguage> parseLanguageList(const std::string& input) {\n  std::vector<FontLanguage> result;\n  size_t currentIdx = 0;\n  size_t commaLoc = 0;\n  char langTag[ULOC_FULLNAME_CAPACITY];\n  std::unordered_set<uint64_t> seen;\n  std::string locale(input.size(), 0);\n\n  while ((commaLoc = input.find_first_of(',', currentIdx)) !=\n         std::string::npos) {\n    locale.assign(input, currentIdx, commaLoc - currentIdx);\n    currentIdx = commaLoc + 1;\n    size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);\n    FontLanguage lang(langTag, length);\n    uint64_t identifier = lang.getIdentifier();\n    if (!lang.isUnsupported() && seen.count(identifier) == 0) {\n      result.push_back(lang);\n      if (result.size() == FONT_LANGUAGES_LIMIT) {\n        break;\n      }\n      seen.insert(identifier);\n    }\n  }\n  if (result.size() < FONT_LANGUAGES_LIMIT) {\n    locale.assign(input, currentIdx, input.size() - currentIdx);\n    size_t length = toLanguageTag(langTag, ULOC_FULLNAME_CAPACITY, locale);\n    FontLanguage lang(langTag, length);\n    uint64_t identifier = lang.getIdentifier();\n    if (!lang.isUnsupported() && seen.count(identifier) == 0) {\n      result.push_back(lang);\n    }\n  }\n  return result;\n}\n\n// static\nuint32_t FontLanguageListCache::getId(const std::string& languages) {\n  FontLanguageListCache* inst = FontLanguageListCache::getInstance();\n  std::unordered_map<std::string, uint32_t>::const_iterator it =\n      inst->mLanguageListLookupTable.find(languages);\n  if (it != inst->mLanguageListLookupTable.end()) {\n    return it->second;\n  }\n\n  // Given language list is not in cache. Insert it and return newly assigned\n  // ID.\n  const uint32_t nextId = inst->mLanguageLists.size();\n  FontLanguages fontLanguages(parseLanguageList(languages));\n  if (fontLanguages.empty()) {\n    return kEmptyListId;\n  }\n  inst->mLanguageLists.push_back(std::move(fontLanguages));\n  inst->mLanguageListLookupTable.insert(std::make_pair(languages, nextId));\n  return nextId;\n}\n\n// static\nconst FontLanguages& FontLanguageListCache::getById(uint32_t id) {\n  FontLanguageListCache* inst = FontLanguageListCache::getInstance();\n  LOG_ALWAYS_FATAL_IF(id >= inst->mLanguageLists.size(),\n                      \"Lookup by unknown language list ID.\");\n  return inst->mLanguageLists[id];\n}\n\n// static\nFontLanguageListCache* FontLanguageListCache::getInstance() {\n  assertMinikinLocked();\n  static FontLanguageListCache* instance = nullptr;\n  if (instance == nullptr) {\n    instance = new FontLanguageListCache();\n\n    // Insert an empty language list for mapping default language list to\n    // kEmptyListId. The default language list has only one FontLanguage and it\n    // is the unsupported language.\n    instance->mLanguageLists.push_back(FontLanguages());\n    instance->mLanguageListLookupTable.insert(std::make_pair(\"\", kEmptyListId));\n  }\n  return instance;\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontLanguageListCache.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTLANGUAGELISTCACHE_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTLANGUAGELISTCACHE_H_\n\n#include <unordered_map>\n#include <vector>\n#include <string>\n\n#include <minikin/FontFamily.h>\n#include \"FontLanguage.h\"\n\nnamespace minikin {\n\nclass FontLanguageListCache {\n public:\n  // A special ID for the empty language list.\n  // This value must be 0 since the empty language list is inserted into\n  // mLanguageLists by default.\n  const static uint32_t kEmptyListId = 0;\n\n  // Returns language list ID for the given string representation of\n  // FontLanguages. Caller should acquire a lock before calling the method.\n  static uint32_t getId(const std::string& languages);\n\n  // Caller should acquire a lock before calling the method.\n  static const FontLanguages& getById(uint32_t id);\n\n private:\n  FontLanguageListCache() {}  // Singleton\n  ~FontLanguageListCache() {}\n\n  // Caller should acquire a lock before calling the method.\n  static FontLanguageListCache* getInstance();\n\n  std::vector<FontLanguages> mLanguageLists;\n\n  // A map from string representation of the font language list to the ID.\n  std::unordered_map<std::string, uint32_t> mLanguageListLookupTable;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTLANGUAGELISTCACHE_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontUtils.cpp",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdint.h>\n#include <stdlib.h>\n\n#include \"FontUtils.h\"\n\nnamespace minikin {\n\nstatic uint16_t readU16(const uint8_t* data, size_t offset) {\n  return data[offset] << 8 | data[offset + 1];\n}\n\nstatic uint32_t readU32(const uint8_t* data, size_t offset) {\n  return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 |\n         ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]);\n}\n\nbool analyzeStyle(const uint8_t* os2_data,\n                  size_t os2_size,\n                  int* weight,\n                  bool* italic) {\n  const size_t kUsWeightClassOffset = 4;\n  const size_t kFsSelectionOffset = 62;\n  const uint16_t kItalicFlag = (1 << 0);\n  if (os2_size < kFsSelectionOffset + 2) {\n    return false;\n  }\n  uint16_t weightClass = readU16(os2_data, kUsWeightClassOffset);\n  *weight = weightClass / 100;\n  uint16_t fsSelection = readU16(os2_data, kFsSelectionOffset);\n  *italic = (fsSelection & kItalicFlag) != 0;\n  return true;\n}\n\nvoid analyzeAxes(const uint8_t* fvar_data,\n                 size_t fvar_size,\n                 std::unordered_set<uint32_t>* axes) {\n  const size_t kMajorVersionOffset = 0;\n  const size_t kMinorVersionOffset = 2;\n  const size_t kOffsetToAxesArrayOffset = 4;\n  const size_t kAxisCountOffset = 8;\n  const size_t kAxisSizeOffset = 10;\n\n  axes->clear();\n\n  if (fvar_size < kAxisSizeOffset + 2) {\n    return;\n  }\n  const uint16_t majorVersion = readU16(fvar_data, kMajorVersionOffset);\n  const uint16_t minorVersion = readU16(fvar_data, kMinorVersionOffset);\n  const uint32_t axisOffset = readU16(fvar_data, kOffsetToAxesArrayOffset);\n  const uint32_t axisCount = readU16(fvar_data, kAxisCountOffset);\n  const uint32_t axisSize = readU16(fvar_data, kAxisSizeOffset);\n\n  if (majorVersion != 1 || minorVersion != 0 || axisOffset != 0x10 ||\n      axisSize != 0x14) {\n    return;  // Unsupported version.\n  }\n  if (fvar_size < axisOffset + axisOffset * axisCount) {\n    return;  // Invalid table size.\n  }\n  for (uint32_t i = 0; i < axisCount; ++i) {\n    size_t axisRecordOffset = axisOffset + i * axisSize;\n    uint32_t tag = readU32(fvar_data, axisRecordOffset);\n    axes->insert(tag);\n  }\n}\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/FontUtils.h",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTUTILS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTUTILS_H_\n\n#include <unordered_set>\n\nnamespace minikin {\n\nbool analyzeStyle(const uint8_t* os2_data,\n                  size_t os2_size,\n                  int* weight,\n                  bool* italic);\nvoid analyzeAxes(const uint8_t* fvar_data,\n                 size_t fvar_size,\n                 std::unordered_set<uint32_t>* axes);\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_FONTUTILS_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/GraphemeBreak.cpp",
    "content": "/*\n * Copyright (C) 2014 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <stdint.h>\n#include <unicode/uchar.h>\n#include <unicode/utf16.h>\n#include <algorithm>\n\n#include <minikin/Emoji.h>\n#include <minikin/GraphemeBreak.h>\n#include \"MinikinInternal.h\"\n#include \"utils/WindowsUtils.h\"\n\nnamespace minikin {\n\nint32_t tailoredGraphemeClusterBreak(uint32_t c) {\n  // Characters defined as Control that we want to treat them as Extend.\n  // These are curated manually.\n  if (c == 0x00AD                      // SHY\n      || c == 0x061C                   // ALM\n      || c == 0x180E                   // MONGOLIAN VOWEL SEPARATOR\n      || c == 0x200B                   // ZWSP\n      || c == 0x200E                   // LRM\n      || c == 0x200F                   // RLM\n      || (0x202A <= c && c <= 0x202E)  // LRE, RLE, PDF, LRO, RLO\n      || ((c | 0xF) ==\n          0x206F)     // WJ, invisible math operators, LRI, RLI, FSI, PDI,\n                      // and the deprecated invisible format controls\n      || c == 0xFEFF  // BOM\n      || ((c | 0x7F) ==\n          0xE007F))  // recently undeprecated tag characters in Plane 14\n    return U_GCB_EXTEND;\n  // THAI CHARACTER SARA AM is treated as a normal letter by most other\n  // implementations: they allow a grapheme break before it.\n  else if (c == 0x0E33)\n    return U_GCB_OTHER;\n  else\n    return u_getIntPropertyValue(c, UCHAR_GRAPHEME_CLUSTER_BREAK);\n}\n\n// Returns true for all characters whose IndicSyllabicCategory is Pure_Killer.\n// From http://www.unicode.org/Public/9.0.0/ucd/IndicSyllabicCategory.txt\nbool isPureKiller(uint32_t c) {\n  return (c == 0x0E3A || c == 0x0E4E || c == 0x0F84 || c == 0x103A ||\n          c == 0x1714 || c == 0x1734 || c == 0x17D1 || c == 0x1BAA ||\n          c == 0x1BF2 || c == 0x1BF3 || c == 0xA806 || c == 0xA953 ||\n          c == 0xABED || c == 0x11134 || c == 0x112EA || c == 0x1172B);\n}\n\nbool GraphemeBreak::isGraphemeBreak(const float* advances,\n                                    const uint16_t* buf,\n                                    size_t start,\n                                    size_t count,\n                                    const size_t offset) {\n  // This implementation closely follows Unicode Standard Annex #29 on\n  // Unicode Text Segmentation (http://www.unicode.org/reports/tr29/),\n  // implementing a tailored version of extended grapheme clusters.\n  // The GB rules refer to section 3.1.1, Grapheme Cluster Boundary Rules.\n\n  // Rule GB1, sot ÷; Rule GB2, ÷ eot\n  if (offset <= start || offset >= start + count) {\n    return true;\n  }\n  if (U16_IS_TRAIL(buf[offset])) {\n    // Don't break a surrogate pair, but a lonely trailing surrogate pair is a\n    // break\n    return !U16_IS_LEAD(buf[offset - 1]);\n  }\n  uint32_t c1 = 0;\n  uint32_t c2 = 0;\n  size_t offset_back = offset;\n  size_t offset_forward = offset;\n  U16_PREV(buf, start, offset_back, c1);\n  U16_NEXT(buf, offset_forward, start + count, c2);\n  int32_t p1 = tailoredGraphemeClusterBreak(c1);\n  int32_t p2 = tailoredGraphemeClusterBreak(c2);\n  // Rule GB3, CR x LF\n  if (p1 == U_GCB_CR && p2 == U_GCB_LF) {\n    return false;\n  }\n  // Rule GB4, (Control | CR | LF) ÷\n  if (p1 == U_GCB_CONTROL || p1 == U_GCB_CR || p1 == U_GCB_LF) {\n    return true;\n  }\n  // Rule GB5, ÷ (Control | CR | LF)\n  if (p2 == U_GCB_CONTROL || p2 == U_GCB_CR || p2 == U_GCB_LF) {\n    return true;\n  }\n  // Rule GB6, L x ( L | V | LV | LVT )\n  if (p1 == U_GCB_L &&\n      (p2 == U_GCB_L || p2 == U_GCB_V || p2 == U_GCB_LV || p2 == U_GCB_LVT)) {\n    return false;\n  }\n  // Rule GB7, ( LV | V ) x ( V | T )\n  if ((p1 == U_GCB_LV || p1 == U_GCB_V) && (p2 == U_GCB_V || p2 == U_GCB_T)) {\n    return false;\n  }\n  // Rule GB8, ( LVT | T ) x T\n  if ((p1 == U_GCB_LVT || p1 == U_GCB_T) && p2 == U_GCB_T) {\n    return false;\n  }\n  // Rule GB9, x (Extend | ZWJ); Rule GB9a, x SpacingMark; Rule GB9b, Prepend x\n  if (p2 == U_GCB_EXTEND || p2 == U_GCB_ZWJ || p2 == U_GCB_SPACING_MARK ||\n      p1 == U_GCB_PREPEND) {\n    return false;\n  }\n\n  // This is used to decide font-dependent grapheme clusters. If we don't have\n  // the advance information, we become conservative in grapheme breaking and\n  // assume that it has no advance.\n  const bool c2_has_advance =\n      (advances != nullptr && advances[offset - start] != 0.0);\n\n  // All the following rules are font-dependent, in the way that if we know c2\n  // has an advance, we definitely know that it cannot form a grapheme with the\n  // character(s) before it. So we make the decision in favor a grapheme break\n  // early.\n  if (c2_has_advance) {\n    return true;\n  }\n\n  // Note: For Rule GB10 and GB11 below, we do not use the Unicode line breaking\n  // properties for determining emoji-ness and carry our own data, because our\n  // data could be more fresh than what ICU provides.\n  //\n  // Tailored version of Rule GB10, (E_Base | EBG) Extend* × E_Modifier.\n  // The rule itself says do not break between emoji base and emoji modifiers,\n  // skipping all Extend characters. Variation selectors are considered Extend,\n  // so they are handled fine.\n  //\n  // We tailor this by requiring that an actual ligature is formed. If the font\n  // doesn't form a ligature, we allow a break before the modifier.\n  if (isEmojiModifier(c2)) {\n    uint32_t c0 = c1;\n    size_t offset_backback = offset_back;\n    int32_t p0 = p1;\n    if (p0 == U_GCB_EXTEND && offset_backback > start) {\n      // skip over emoji variation selector\n      U16_PREV(buf, start, offset_backback, c0);\n    }\n    if (isEmojiBase(c0)) {\n      return false;\n    }\n  }\n\n  // Tailored version of Rule GB11, ZWJ × (Glue_After_Zwj | EBG)\n  // We try to make emoji sequences with ZWJ a single grapheme cluster, but only\n  // if they actually merge to one cluster. So we are more relaxed than the UAX\n  // #29 rules in accepting any emoji character after the ZWJ, but are tighter\n  // in that we only treat it as one cluster if a ligature is actually formed\n  // and we also require the character before the ZWJ to also be an emoji.\n  if (p1 == U_GCB_ZWJ && isEmoji(c2) && offset_back > start) {\n    // look at character before ZWJ to see that both can participate in an\n    // emoji zwj sequence\n    uint32_t c0 = 0;\n    size_t offset_backback = offset_back;\n    U16_PREV(buf, start, offset_backback, c0);\n    if (c0 == 0xFE0F && offset_backback > start) {\n      // skip over emoji variation selector\n      U16_PREV(buf, start, offset_backback, c0);\n    }\n    if (isEmoji(c0)) {\n      return false;\n    }\n  }\n\n  // Tailored version of Rule GB12 and Rule GB13 that look at even-odd cases.\n  // sot   (RI RI)*  RI x RI\n  // [^RI] (RI RI)*  RI x RI\n  //\n  // If we have font information, we have already broken the cluster if and only\n  // if the second character had no advance, which means a ligature was formed.\n  // If we don't, we look back like UAX #29 recommends, but only up to 1000 code\n  // units.\n  if (p1 == U_GCB_REGIONAL_INDICATOR && p2 == U_GCB_REGIONAL_INDICATOR) {\n    if (advances != nullptr) {\n      // We have advances information. But if we are here, we already know c2\n      // has no advance. So we should definitely disallow a break.\n      return false;\n    } else {\n      // Look at up to 1000 code units.\n      const size_t lookback_barrier =\n          std::max((ssize_t)start, (ssize_t)offset_back - 1000);\n      size_t offset_backback = offset_back;\n      while (offset_backback > lookback_barrier) {\n        uint32_t c0 = 0;\n        U16_PREV(buf, lookback_barrier, offset_backback, c0);\n        if (tailoredGraphemeClusterBreak(c0) != U_GCB_REGIONAL_INDICATOR) {\n          offset_backback += U16_LENGTH(c0);\n          break;\n        }\n      }\n      // The number 4 comes from the number of code units in a whole flag.\n      return (offset - offset_backback) % 4 == 0;\n    }\n  }\n  // Cluster Indic syllables together (tailoring of UAX #29).\n  // Immediately after each virama (that is not just a pure killer) followed by\n  // a letter, we disallow grapheme breaks (if we are here, we don't know about\n  // advances, or we already know that c2 has no advance).\n  if (u_getIntPropertyValue(c1, UCHAR_CANONICAL_COMBINING_CLASS) == 9  // virama\n      && !isPureKiller(c1) &&\n      u_getIntPropertyValue(c2, UCHAR_GENERAL_CATEGORY) == U_OTHER_LETTER) {\n    return false;\n  }\n  // Rule GB999, Any ÷ Any\n  return true;\n}\n\nsize_t GraphemeBreak::getTextRunCursor(const float* advances,\n                                       const uint16_t* buf,\n                                       size_t start,\n                                       size_t count,\n                                       size_t offset,\n                                       MoveOpt opt) {\n  switch (opt) {\n    case AFTER:\n      if (offset < start + count) {\n        offset++;\n      }\n      // fall through\n    case AT_OR_AFTER:\n      while (!isGraphemeBreak(advances, buf, start, count, offset)) {\n        offset++;\n      }\n      break;\n    case BEFORE:\n      if (offset > start) {\n        offset--;\n      }\n      // fall through\n    case AT_OR_BEFORE:\n      while (!isGraphemeBreak(advances, buf, start, count, offset)) {\n        offset--;\n      }\n      break;\n    case AT:\n      if (!isGraphemeBreak(advances, buf, start, count, offset)) {\n        offset = (size_t)-1;\n      }\n      break;\n  }\n  return offset;\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/GraphemeBreak.h",
    "content": "/*\n * Copyright (C) 2014 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_GRAPHEMEBREAK_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_GRAPHEMEBREAK_H_\n\n#include <stddef.h>\n#include <unicode/utf16.h>\n\nnamespace minikin {\n\nclass GraphemeBreak {\n public:\n  // These values must be kept in sync with CURSOR_AFTER etc in Paint.java\n  enum MoveOpt {\n    AFTER = 0,\n    AT_OR_AFTER = 1,\n    BEFORE = 2,\n    AT_OR_BEFORE = 3,\n    AT = 4\n  };\n\n  // Determine whether the given offset is a grapheme break.\n  // This implementation generally follows Unicode's UTR #29 extended\n  // grapheme break, with various tweaks.\n  static bool isGraphemeBreak(const float* advances,\n                              const uint16_t* buf,\n                              size_t start,\n                              size_t count,\n                              size_t offset);\n\n  // Matches Android's Java API. Note, return (size_t)-1 for AT to\n  // signal non-break because unsigned return type.\n  static size_t getTextRunCursor(const float* advances,\n                                 const uint16_t* buf,\n                                 size_t start,\n                                 size_t count,\n                                 size_t offset,\n                                 MoveOpt opt);\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_GRAPHEMEBREAK_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/HbFontCache.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include \"HbFontCache.h\"\n\n#include <log/log.h>\n#include <utils/LruCache.h>\n\n#include <hb-ot.h>\n#include <hb.h>\n\n#include <minikin/MinikinFont.h>\n#include \"MinikinInternal.h\"\n\nnamespace minikin {\n\nclass HbFontCache : private android::OnEntryRemoved<int32_t, hb_font_t*> {\n public:\n  HbFontCache() : mCache(kMaxEntries) {\n    mCache.setOnEntryRemovedListener(this);\n  }\n\n  // callback for OnEntryRemoved\n  void operator()(int32_t& /* key */, hb_font_t*& value) {\n    hb_font_destroy(value);\n  }\n\n  hb_font_t* get(int32_t fontId) { return mCache.get(fontId); }\n\n  void put(int32_t fontId, hb_font_t* font) { mCache.put(fontId, font); }\n\n  void clear() { mCache.clear(); }\n\n  void remove(int32_t fontId) { mCache.remove(fontId); }\n\n private:\n  static const size_t kMaxEntries = 100;\n\n  android::LruCache<int32_t, hb_font_t*> mCache;\n};\n\nHbFontCache* getFontCacheLocked() {\n  assertMinikinLocked();\n  static HbFontCache* cache = nullptr;\n  if (cache == nullptr) {\n    cache = new HbFontCache();\n  }\n  return cache;\n}\n\nvoid purgeHbFontCacheLocked() {\n  assertMinikinLocked();\n  getFontCacheLocked()->clear();\n}\n\nvoid purgeHbFontLocked(const MinikinFont* minikinFont) {\n  assertMinikinLocked();\n  const int32_t fontId = minikinFont->GetUniqueId();\n  getFontCacheLocked()->remove(fontId);\n}\n\n// Returns a new reference to a hb_font_t object, caller is\n// responsible for calling hb_font_destroy() on it.\nhb_font_t* getHbFontLocked(const MinikinFont* minikinFont) {\n  assertMinikinLocked();\n  // TODO: get rid of nullFaceFont\n  static hb_font_t* nullFaceFont = nullptr;\n  if (minikinFont == nullptr) {\n    if (nullFaceFont == nullptr) {\n      nullFaceFont = hb_font_create(nullptr);\n    }\n    return hb_font_reference(nullFaceFont);\n  }\n\n  HbFontCache* fontCache = getFontCacheLocked();\n  const int32_t fontId = minikinFont->GetUniqueId();\n  hb_font_t* font = fontCache->get(fontId);\n  if (font != nullptr) {\n    return hb_font_reference(font);\n  }\n\n  hb_face_t* face = minikinFont->CreateHarfBuzzFace();\n\n  hb_font_t* parent_font = hb_font_create(face);\n  hb_ot_font_set_funcs(parent_font);\n\n  unsigned int upem = hb_face_get_upem(face);\n  hb_font_set_scale(parent_font, upem, upem);\n\n  font = hb_font_create_sub_font(parent_font);\n  std::vector<hb_variation_t> variations;\n  for (const FontVariation& variation : minikinFont->GetAxes()) {\n    variations.push_back({variation.axisTag, variation.value});\n  }\n  hb_font_set_variations(font, variations.data(), variations.size());\n  hb_font_destroy(parent_font);\n  hb_face_destroy(face);\n  fontCache->put(fontId, font);\n  return hb_font_reference(font);\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/HbFontCache.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_HBFONTCACHE_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_HBFONTCACHE_H_\n\nstruct hb_font_t;\n\nnamespace minikin {\nclass MinikinFont;\n\nvoid purgeHbFontCacheLocked();\nvoid purgeHbFontLocked(const MinikinFont* minikinFont);\nhb_font_t* getHbFontLocked(const MinikinFont* minikinFont);\n\n}  // namespace minikin\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_HBFONTCACHE_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/Hyphenator.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <unicode/uchar.h>\n#include <unicode/uscript.h>\n#include <algorithm>\n#include <memory>\n#include <string>\n#include <vector>\n\n// HACK: for reading pattern file\n#include <fcntl.h>\n\n#define LOG_TAG \"Minikin\"\n\n#include \"minikin/Hyphenator.h\"\n#include \"utils/WindowsUtils.h\"\n\nusing std::vector;\n\nnamespace minikin {\n\nstatic const uint16_t CHAR_HYPHEN_MINUS = 0x002D;\nstatic const uint16_t CHAR_SOFT_HYPHEN = 0x00AD;\nstatic const uint16_t CHAR_MIDDLE_DOT = 0x00B7;\nstatic const uint16_t CHAR_HYPHEN = 0x2010;\n\n// The following are structs that correspond to tables inside the hyb file\n// format\n\nstruct AlphabetTable0 {\n  uint32_t version;\n  uint32_t min_codepoint;\n  uint32_t max_codepoint;\n  uint8_t data[1];  // actually flexible array, size is known at runtime\n};\n\nstruct AlphabetTable1 {\n  uint32_t version;\n  uint32_t n_entries;\n  uint32_t data[1];  // actually flexible array, size is known at runtime\n\n  static uint32_t codepoint(uint32_t entry) { return entry >> 11; }\n  static uint32_t value(uint32_t entry) { return entry & 0x7ff; }\n};\n\nstruct Trie {\n  uint32_t version;\n  uint32_t char_mask;\n  uint32_t link_shift;\n  uint32_t link_mask;\n  uint32_t pattern_shift;\n  uint32_t n_entries;\n  uint32_t data[1];  // actually flexible array, size is known at runtime\n};\n\nstruct Pattern {\n  uint32_t version;\n  uint32_t n_entries;\n  uint32_t pattern_offset;\n  uint32_t pattern_size;\n  uint32_t data[1];  // actually flexible array, size is known at runtime\n\n  // accessors\n  static uint32_t len(uint32_t entry) { return entry >> 26; }\n  static uint32_t shift(uint32_t entry) { return (entry >> 20) & 0x3f; }\n  const uint8_t* buf(uint32_t entry) const {\n    return reinterpret_cast<const uint8_t*>(this) + pattern_offset +\n           (entry & 0xfffff);\n  }\n};\n\nstruct Header {\n  uint32_t magic;\n  uint32_t version;\n  uint32_t alphabet_offset;\n  uint32_t trie_offset;\n  uint32_t pattern_offset;\n  uint32_t file_size;\n\n  // accessors\n  const uint8_t* bytes() const {\n    return reinterpret_cast<const uint8_t*>(this);\n  }\n  uint32_t alphabetVersion() const {\n    return *reinterpret_cast<const uint32_t*>(bytes() + alphabet_offset);\n  }\n  const AlphabetTable0* alphabetTable0() const {\n    return reinterpret_cast<const AlphabetTable0*>(bytes() + alphabet_offset);\n  }\n  const AlphabetTable1* alphabetTable1() const {\n    return reinterpret_cast<const AlphabetTable1*>(bytes() + alphabet_offset);\n  }\n  const Trie* trieTable() const {\n    return reinterpret_cast<const Trie*>(bytes() + trie_offset);\n  }\n  const Pattern* patternTable() const {\n    return reinterpret_cast<const Pattern*>(bytes() + pattern_offset);\n  }\n};\n\nHyphenator* Hyphenator::loadBinary(const uint8_t* patternData,\n                                   size_t minPrefix,\n                                   size_t minSuffix) {\n  Hyphenator* result = new Hyphenator;\n  result->patternData = patternData;\n  result->minPrefix = minPrefix;\n  result->minSuffix = minSuffix;\n  return result;\n}\n\nvoid Hyphenator::hyphenate(vector<HyphenationType>* result,\n                           const uint16_t* word,\n                           size_t len,\n                           const icu::Locale& locale) {\n  result->clear();\n  result->resize(len);\n  const size_t paddedLen = len + 2;  // start and stop code each count for 1\n  if (patternData != nullptr && len >= minPrefix + minSuffix &&\n      paddedLen <= MAX_HYPHENATED_SIZE) {\n    uint16_t alpha_codes[MAX_HYPHENATED_SIZE];\n    const HyphenationType hyphenValue = alphabetLookup(alpha_codes, word, len);\n    if (hyphenValue != HyphenationType::DONT_BREAK) {\n      hyphenateFromCodes(result->data(), alpha_codes, paddedLen, hyphenValue);\n      return;\n    }\n    // TODO: try NFC normalization\n    // TODO: handle non-BMP Unicode (requires remapping of offsets)\n  }\n  // Note that we will always get here if the word contains a hyphen or a soft\n  // hyphen, because the alphabet is not expected to contain a hyphen or a soft\n  // hyphen character, so alphabetLookup would return DONT_BREAK.\n  hyphenateWithNoPatterns(result->data(), word, len, locale);\n}\n\n// This function determines whether a character is like U+2010 HYPHEN in\n// line breaking and usage: a character immediately after which line breaks\n// are allowed, but words containing it should not be automatically\n// hyphenated using patterns. This is a curated set, created by manually\n// inspecting all the characters that have the Unicode line breaking\n// property of BA or HY and seeing which ones are hyphens.\nbool Hyphenator::isLineBreakingHyphen(uint32_t c) {\n  return (c == 0x002D ||  // HYPHEN-MINUS\n          c == 0x058A ||  // ARMENIAN HYPHEN\n          c == 0x05BE ||  // HEBREW PUNCTUATION MAQAF\n          c == 0x1400 ||  // CANADIAN SYLLABICS HYPHEN\n          c == 0x2010 ||  // HYPHEN\n          c == 0x2013 ||  // EN DASH\n          c == 0x2027 ||  // HYPHENATION POINT\n          c == 0x2E17 ||  // DOUBLE OBLIQUE HYPHEN\n          c == 0x2E40);   // DOUBLE HYPHEN\n}\n\nconst static uint32_t HYPHEN_STR[] = {0x2010, 0};\nconst static uint32_t ARMENIAN_HYPHEN_STR[] = {0x058A, 0};\nconst static uint32_t MAQAF_STR[] = {0x05BE, 0};\nconst static uint32_t UCAS_HYPHEN_STR[] = {0x1400, 0};\nconst static uint32_t ZWJ_STR[] = {0x200D, 0};\nconst static uint32_t ZWJ_AND_HYPHEN_STR[] = {0x200D, 0x2010, 0};\n\nconst uint32_t* HyphenEdit::getHyphenString(uint32_t hyph) {\n  switch (hyph) {\n    case INSERT_HYPHEN_AT_END:\n    case REPLACE_WITH_HYPHEN_AT_END:\n    case INSERT_HYPHEN_AT_START:\n      return HYPHEN_STR;\n    case INSERT_ARMENIAN_HYPHEN_AT_END:\n      return ARMENIAN_HYPHEN_STR;\n    case INSERT_MAQAF_AT_END:\n      return MAQAF_STR;\n    case INSERT_UCAS_HYPHEN_AT_END:\n      return UCAS_HYPHEN_STR;\n    case INSERT_ZWJ_AND_HYPHEN_AT_END:\n      return ZWJ_AND_HYPHEN_STR;\n    case INSERT_ZWJ_AT_START:\n      return ZWJ_STR;\n    default:\n      return nullptr;\n  }\n}\n\nuint32_t HyphenEdit::editForThisLine(HyphenationType type) {\n  switch (type) {\n    case HyphenationType::DONT_BREAK:\n      return NO_EDIT;\n    case HyphenationType::BREAK_AND_INSERT_HYPHEN:\n      return INSERT_HYPHEN_AT_END;\n    case HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN:\n      return INSERT_ARMENIAN_HYPHEN_AT_END;\n    case HyphenationType::BREAK_AND_INSERT_MAQAF:\n      return INSERT_MAQAF_AT_END;\n    case HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN:\n      return INSERT_UCAS_HYPHEN_AT_END;\n    case HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN:\n      return REPLACE_WITH_HYPHEN_AT_END;\n    case HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ:\n      return INSERT_ZWJ_AND_HYPHEN_AT_END;\n    default:\n      return BREAK_AT_END;\n  }\n}\n\nuint32_t HyphenEdit::editForNextLine(HyphenationType type) {\n  switch (type) {\n    case HyphenationType::DONT_BREAK:\n      return NO_EDIT;\n    case HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE:\n      return INSERT_HYPHEN_AT_START;\n    case HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ:\n      return INSERT_ZWJ_AT_START;\n    default:\n      return BREAK_AT_START;\n  }\n}\n\nstatic UScriptCode getScript(uint32_t codePoint) {\n  UErrorCode errorCode = U_ZERO_ERROR;\n  const UScriptCode script =\n      uscript_getScript(static_cast<UChar32>(codePoint), &errorCode);\n  if (U_SUCCESS(errorCode)) {\n    return script;\n  } else {\n    return USCRIPT_INVALID_CODE;\n  }\n}\n\nstatic HyphenationType hyphenationTypeBasedOnScript(uint32_t codePoint) {\n  // Note: It's not clear what the best hyphen for Hebrew is. While maqaf is the\n  // \"correct\" hyphen for Hebrew, modern practice may have shifted towards\n  // Western hyphens. We use normal hyphens for now to be safe.\n  // BREAK_AND_INSERT_MAQAF is already implemented, so if we want to switch to\n  // maqaf for Hebrew, we can simply add a condition here.\n  const UScriptCode script = getScript(codePoint);\n  if (script == USCRIPT_KANNADA || script == USCRIPT_MALAYALAM ||\n      script == USCRIPT_TAMIL || script == USCRIPT_TELUGU) {\n    // Grantha is not included, since we don't support non-BMP hyphenation yet.\n    return HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;\n  } else if (script == USCRIPT_ARMENIAN) {\n    return HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN;\n  } else if (script == USCRIPT_CANADIAN_ABORIGINAL) {\n    return HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN;\n  } else {\n    return HyphenationType::BREAK_AND_INSERT_HYPHEN;\n  }\n}\n\nstatic inline int32_t getJoiningType(UChar32 codepoint) {\n  return u_getIntPropertyValue(codepoint, UCHAR_JOINING_TYPE);\n}\n\n// Assumption for caller: location must be >= 2 and word[location] ==\n// CHAR_SOFT_HYPHEN. This function decides if the letters before and after the\n// hyphen should appear as joining.\nstatic inline HyphenationType getHyphTypeForArabic(const uint16_t* word,\n                                                   size_t len,\n                                                   size_t location) {\n  ssize_t i = location;\n  int32_t type = U_JT_NON_JOINING;\n  while (static_cast<size_t>(i) < len &&\n         (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {\n    i++;\n  }\n  if (type == U_JT_DUAL_JOINING || type == U_JT_RIGHT_JOINING ||\n      type == U_JT_JOIN_CAUSING) {\n    // The next character is of the type that may join the last character. See\n    // if the last character is also of the right type.\n    i = location - 2;  // Skip the soft hyphen\n    type = U_JT_NON_JOINING;\n    while (i >= 0 && (type = getJoiningType(word[i])) == U_JT_TRANSPARENT) {\n      i--;\n    }\n    if (type == U_JT_DUAL_JOINING || type == U_JT_LEFT_JOINING ||\n        type == U_JT_JOIN_CAUSING) {\n      return HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ;\n    }\n  }\n  return HyphenationType::BREAK_AND_INSERT_HYPHEN;\n}\n\n// Use various recommendations of UAX #14 Unicode Line Breaking Algorithm for\n// hyphenating words that didn't match patterns, especially words that contain\n// hyphens or soft hyphens (See sections 5.3, Use of Hyphen, and 5.4, Use of\n// Soft Hyphen).\nvoid Hyphenator::hyphenateWithNoPatterns(HyphenationType* result,\n                                         const uint16_t* word,\n                                         size_t len,\n                                         const icu::Locale& locale) {\n  result[0] = HyphenationType::DONT_BREAK;\n  for (size_t i = 1; i < len; i++) {\n    const uint16_t prevChar = word[i - 1];\n    if (i > 1 && isLineBreakingHyphen(prevChar)) {\n      // Break after hyphens, but only if they don't start the word.\n\n      if ((prevChar == CHAR_HYPHEN_MINUS || prevChar == CHAR_HYPHEN) &&\n          strcmp(locale.getLanguage(), \"pl\") == 0 &&\n          getScript(word[i]) == USCRIPT_LATIN) {\n        // In Polish, hyphens get repeated at the next line. To be safe,\n        // we will do this only if the next character is Latin.\n        result[i] = HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE;\n      } else {\n        result[i] = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;\n      }\n    } else if (i > 1 && prevChar == CHAR_SOFT_HYPHEN) {\n      // Break after soft hyphens, but only if they don't start the word (a soft\n      // hyphen starting the word doesn't give any useful break opportunities).\n      // The type of the break is based on the script of the character we break\n      // on.\n      if (getScript(word[i]) == USCRIPT_ARABIC) {\n        // For Arabic, we need to look and see if the characters around the soft\n        // hyphen actually join. If they don't, we'll just insert a normal\n        // hyphen.\n        result[i] = getHyphTypeForArabic(word, len, i);\n      } else {\n        result[i] = hyphenationTypeBasedOnScript(word[i]);\n      }\n    } else if (prevChar == CHAR_MIDDLE_DOT && minPrefix < i &&\n               i <= len - minSuffix &&\n               ((word[i - 2] == 'l' && word[i] == 'l') ||\n                (word[i - 2] == 'L' && word[i] == 'L')) &&\n               strcmp(locale.getLanguage(), \"ca\") == 0) {\n      // In Catalan, \"l·l\" should break as \"l-\" on the first line\n      // and \"l\" on the next line.\n      result[i] = HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN;\n    } else {\n      result[i] = HyphenationType::DONT_BREAK;\n    }\n  }\n}\n\nHyphenationType Hyphenator::alphabetLookup(uint16_t* alpha_codes,\n                                           const uint16_t* word,\n                                           size_t len) {\n  const Header* header = getHeader();\n  HyphenationType result = HyphenationType::BREAK_AND_INSERT_HYPHEN;\n  // TODO: check header magic\n  uint32_t alphabetVersion = header->alphabetVersion();\n  if (alphabetVersion == 0) {\n    const AlphabetTable0* alphabet = header->alphabetTable0();\n    uint32_t min_codepoint = alphabet->min_codepoint;\n    uint32_t max_codepoint = alphabet->max_codepoint;\n    alpha_codes[0] = 0;  // word start\n    for (size_t i = 0; i < len; i++) {\n      uint16_t c = word[i];\n      if (c < min_codepoint || c >= max_codepoint) {\n        return HyphenationType::DONT_BREAK;\n      }\n      uint8_t code = alphabet->data[c - min_codepoint];\n      if (code == 0) {\n        return HyphenationType::DONT_BREAK;\n      }\n      if (result == HyphenationType::BREAK_AND_INSERT_HYPHEN) {\n        result = hyphenationTypeBasedOnScript(c);\n      }\n      alpha_codes[i + 1] = code;\n    }\n    alpha_codes[len + 1] = 0;  // word termination\n    return result;\n  } else if (alphabetVersion == 1) {\n    const AlphabetTable1* alphabet = header->alphabetTable1();\n    size_t n_entries = alphabet->n_entries;\n    const uint32_t* begin = alphabet->data;\n    const uint32_t* end = begin + n_entries;\n    alpha_codes[0] = 0;\n    for (size_t i = 0; i < len; i++) {\n      uint16_t c = word[i];\n      auto p = std::lower_bound<const uint32_t*, uint32_t>(begin, end, c << 11);\n      if (p == end) {\n        return HyphenationType::DONT_BREAK;\n      }\n      uint32_t entry = *p;\n      if (AlphabetTable1::codepoint(entry) != c) {\n        return HyphenationType::DONT_BREAK;\n      }\n      if (result == HyphenationType::BREAK_AND_INSERT_HYPHEN) {\n        result = hyphenationTypeBasedOnScript(c);\n      }\n      alpha_codes[i + 1] = AlphabetTable1::value(entry);\n    }\n    alpha_codes[len + 1] = 0;\n    return result;\n  }\n  return HyphenationType::DONT_BREAK;\n}\n\n/**\n * Internal implementation, after conversion to codes. All case folding and\n *normalization has been done by now, and all characters have been found in the\n *alphabet. Note: len here is the padded length including 0 codes at start and\n *end.\n **/\nvoid Hyphenator::hyphenateFromCodes(HyphenationType* result,\n                                    const uint16_t* codes,\n                                    size_t len,\n                                    HyphenationType hyphenValue) {\n  static_assert(sizeof(HyphenationType) == sizeof(uint8_t),\n                \"HyphnationType must be uint8_t.\");\n  // Reuse the result array as a buffer for calculating intermediate hyphenation\n  // numbers.\n  uint8_t* buffer = reinterpret_cast<uint8_t*>(result);\n\n  const Header* header = getHeader();\n  const Trie* trie = header->trieTable();\n  const Pattern* pattern = header->patternTable();\n  uint32_t char_mask = trie->char_mask;\n  uint32_t link_shift = trie->link_shift;\n  uint32_t link_mask = trie->link_mask;\n  uint32_t pattern_shift = trie->pattern_shift;\n  size_t maxOffset = len - minSuffix - 1;\n  for (size_t i = 0; i < len - 1; i++) {\n    uint32_t node = 0;  // index into Trie table\n    for (size_t j = i; j < len; j++) {\n      uint16_t c = codes[j];\n      uint32_t entry = trie->data[node + c];\n      if ((entry & char_mask) == c) {\n        node = (entry & link_mask) >> link_shift;\n      } else {\n        break;\n      }\n      uint32_t pat_ix = trie->data[node] >> pattern_shift;\n      // pat_ix contains a 3-tuple of length, shift (number of trailing zeros),\n      // and an offset into the buf pool. This is the pattern for the substring\n      // (i..j) we just matched, which we combine (via point-wise max) into the\n      // buffer vector.\n      if (pat_ix != 0) {\n        uint32_t pat_entry = pattern->data[pat_ix];\n        int pat_len = Pattern::len(pat_entry);\n        int pat_shift = Pattern::shift(pat_entry);\n        const uint8_t* pat_buf = pattern->buf(pat_entry);\n        int offset = j + 1 - (pat_len + pat_shift);\n        // offset is the index within buffer that lines up with the start of\n        // pat_buf\n        int start = std::max((int)minPrefix - offset, 0);\n        int end = std::min(pat_len, (int)maxOffset - offset);\n        for (int k = start; k < end; k++) {\n          buffer[offset + k] = std::max(buffer[offset + k], pat_buf[k]);\n        }\n      }\n    }\n  }\n  // Since the above calculation does not modify values outside\n  // [minPrefix, len - minSuffix], they are left as 0 = DONT_BREAK.\n  for (size_t i = minPrefix; i < maxOffset; i++) {\n    // Hyphenation opportunities happen when the hyphenation numbers are odd.\n    result[i] = (buffer[i] & 1u) ? hyphenValue : HyphenationType::DONT_BREAK;\n  }\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/Hyphenator.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * An implementation of Liang's hyphenation algorithm.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_HYPHENATOR_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_HYPHENATOR_H_\n\n#ifndef U_USING_ICU_NAMESPACE\n#define U_USING_ICU_NAMESPACE 0\n#endif\n\n#include <memory>\n#include <unordered_map>\n#include <vector>\n#include \"unicode/locid.h\"\n\nnamespace minikin {\n\nenum class HyphenationType : uint8_t {\n  // Note: There are implicit assumptions scattered in the code that DONT_BREAK\n  // is 0.\n\n  // Do not break.\n  DONT_BREAK = 0,\n  // Break the line and insert a normal hyphen.\n  BREAK_AND_INSERT_HYPHEN = 1,\n  // Break the line and insert an Armenian hyphen (U+058A).\n  BREAK_AND_INSERT_ARMENIAN_HYPHEN = 2,\n  // Break the line and insert a maqaf (Hebrew hyphen, U+05BE).\n  BREAK_AND_INSERT_MAQAF = 3,\n  // Break the line and insert a Canadian Syllabics hyphen (U+1400).\n  BREAK_AND_INSERT_UCAS_HYPHEN = 4,\n  // Break the line, but don't insert a hyphen. Used for cases when there is\n  // already a hyphen\n  // present or the script does not use a hyphen (e.g. in Malayalam).\n  BREAK_AND_DONT_INSERT_HYPHEN = 5,\n  // Break and replace the last code unit with hyphen. Used for Catalan \"l·l\"\n  // which hyphenates\n  // as \"l-/l\".\n  BREAK_AND_REPLACE_WITH_HYPHEN = 6,\n  // Break the line, and repeat the hyphen (which is the last character) at the\n  // beginning of the\n  // next line. Used in Polish, where \"czerwono-niebieska\" should hyphenate as\n  // \"czerwono-/-niebieska\".\n  BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE = 7,\n  // Break the line, insert a ZWJ and hyphen at the first line, and a ZWJ at the\n  // second line.\n  // This is used in Arabic script, mostly for writing systems of Central Asia.\n  // It's our default\n  // behavior when a soft hyphen is used in Arabic script.\n  BREAK_AND_INSERT_HYPHEN_AND_ZWJ = 8\n};\n\n// The hyphen edit represents an edit to the string when a word is\n// hyphenated. The most common hyphen edit is adding a \"-\" at the end\n// of a syllable, but nonstandard hyphenation allows for more choices.\n// Note that a HyphenEdit can hold two types of edits at the same time,\n// One at the beginning of the string/line and one at the end.\nclass HyphenEdit {\n public:\n  static const uint32_t NO_EDIT = 0x00;\n\n  static const uint32_t INSERT_HYPHEN_AT_END = 0x01;\n  static const uint32_t INSERT_ARMENIAN_HYPHEN_AT_END = 0x02;\n  static const uint32_t INSERT_MAQAF_AT_END = 0x03;\n  static const uint32_t INSERT_UCAS_HYPHEN_AT_END = 0x04;\n  static const uint32_t INSERT_ZWJ_AND_HYPHEN_AT_END = 0x05;\n  static const uint32_t REPLACE_WITH_HYPHEN_AT_END = 0x06;\n  static const uint32_t BREAK_AT_END = 0x07;\n\n  static const uint32_t INSERT_HYPHEN_AT_START = 0x01 << 3;\n  static const uint32_t INSERT_ZWJ_AT_START = 0x02 << 3;\n  static const uint32_t BREAK_AT_START = 0x03 << 3;\n\n  // Keep in sync with the definitions in the Java code at:\n  // frameworks/base/graphics/java/android/graphics/Paint.java\n  static const uint32_t MASK_END_OF_LINE = 0x07;\n  static const uint32_t MASK_START_OF_LINE = 0x03 << 3;\n\n  inline static bool isReplacement(uint32_t hyph) {\n    return hyph == REPLACE_WITH_HYPHEN_AT_END;\n  }\n\n  inline static bool isInsertion(uint32_t hyph) {\n    return (hyph == INSERT_HYPHEN_AT_END ||\n            hyph == INSERT_ARMENIAN_HYPHEN_AT_END ||\n            hyph == INSERT_MAQAF_AT_END || hyph == INSERT_UCAS_HYPHEN_AT_END ||\n            hyph == INSERT_ZWJ_AND_HYPHEN_AT_END ||\n            hyph == INSERT_HYPHEN_AT_START || hyph == INSERT_ZWJ_AT_START);\n  }\n\n  const static uint32_t* getHyphenString(uint32_t hyph);\n  static uint32_t editForThisLine(HyphenationType type);\n  static uint32_t editForNextLine(HyphenationType type);\n\n  HyphenEdit() : hyphen(NO_EDIT) {}\n  HyphenEdit(uint32_t hyphenInt)  // NOLINT(google-explicit-constructor)\n      : hyphen(hyphenInt) {}\n  uint32_t getHyphen() const { return hyphen; }\n  bool operator==(const HyphenEdit& other) const {\n    return hyphen == other.hyphen;\n  }\n\n  uint32_t getEnd() const { return hyphen & MASK_END_OF_LINE; }\n  uint32_t getStart() const { return hyphen & MASK_START_OF_LINE; }\n\n private:\n  uint32_t hyphen;\n};\n\n// hyb file header; implementation details are in the .cpp file\nstruct Header;\n\nclass Hyphenator {\n public:\n  // Compute the hyphenation of a word, storing the hyphenation in result\n  // vector. Each entry in the vector is a \"hyphenation type\" for a potential\n  // hyphenation that can be applied at the corresponding code unit offset in\n  // the word.\n  //\n  // Example: word is \"hyphen\", result is the following, corresponding to\n  // \"hy-phen\": [DONT_BREAK, DONT_BREAK, BREAK_AND_INSERT_HYPHEN, DONT_BREAK,\n  // DONT_BREAK, DONT_BREAK]\n  void hyphenate(std::vector<HyphenationType>* result,\n                 const uint16_t* word,\n                 size_t len,\n                 const icu::Locale& locale);\n\n  // Returns true if the codepoint is like U+2010 HYPHEN in line breaking and\n  // usage: a character immediately after which line breaks are allowed, but\n  // words containing it should not be automatically hyphenated.\n  static bool isLineBreakingHyphen(uint32_t cp);\n\n  // pattern data is in binary format, as described in doc/hyb_file_format.md.\n  // Note: the caller is responsible for ensuring that the lifetime of the\n  // pattern data is at least as long as the Hyphenator object.\n\n  // Note: nullptr is valid input, in which case the hyphenator only processes\n  // soft hyphens.\n  static Hyphenator* loadBinary(const uint8_t* patternData,\n                                size_t minPrefix,\n                                size_t minSuffix);\n\n private:\n  // apply various hyphenation rules including hard and soft hyphens, ignoring\n  // patterns\n  void hyphenateWithNoPatterns(HyphenationType* result,\n                               const uint16_t* word,\n                               size_t len,\n                               const icu::Locale& locale);\n\n  // Try looking up word in alphabet table, return DONT_BREAK if any code units\n  // fail to map. Otherwise, returns BREAK_AND_INSERT_HYPHEN,\n  // BREAK_AND_INSERT_ARMENIAN_HYPHEN, or BREAK_AND_DONT_INSERT_HYPHEN based on\n  // the script of the characters seen. Note that this method writes len+2\n  // entries into alpha_codes (including start and stop)\n  HyphenationType alphabetLookup(uint16_t* alpha_codes,\n                                 const uint16_t* word,\n                                 size_t len);\n\n  // calculate hyphenation from patterns, assuming alphabet lookup has already\n  // been done\n  void hyphenateFromCodes(HyphenationType* result,\n                          const uint16_t* codes,\n                          size_t len,\n                          HyphenationType hyphenValue);\n\n  // See also LONGEST_HYPHENATED_WORD in LineBreaker.cpp. Here the constant is\n  // used so that temporary buffers can be stack-allocated without waste, which\n  // is a slightly different use case. It measures UTF-16 code units.\n  static const size_t MAX_HYPHENATED_SIZE = 64;\n\n  const uint8_t* patternData;\n  size_t minPrefix, minSuffix;\n\n  // accessors for binary data\n  const Header* getHeader() const {\n    return reinterpret_cast<const Header*>(patternData);\n  }\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_HYPHENATOR_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/Layout.cpp",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include <math.h>\n#include <unicode/ubidi.h>\n#include <unicode/utf16.h>\n#include <algorithm>\n#include <fstream>\n#include <iostream>  // for debugging\n#include <string>\n#include <vector>\n\n#include <log/log.h>\n#include <utils/JenkinsHash.h>\n#include <utils/LruCache.h>\n#include <utils/WindowsUtils.h>\n\n#include <hb-icu.h>\n#include <hb-ot.h>\n\n#include <minikin/Emoji.h>\n#include <minikin/Layout.h>\n#include \"FontLanguage.h\"\n#include \"FontLanguageListCache.h\"\n#include \"HbFontCache.h\"\n#include \"LayoutUtils.h\"\n#include \"MinikinInternal.h\"\n\nnamespace minikin {\n\nconst int kDirection_Mask = 0x1;\n\nstruct LayoutContext {\n  MinikinPaint paint;\n  FontStyle style;\n  std::vector<hb_font_t*> hbFonts;  // parallel to mFaces\n\n  void clearHbFonts() {\n    for (size_t i = 0; i < hbFonts.size(); i++) {\n      hb_font_set_funcs(hbFonts[i], nullptr, nullptr, nullptr);\n      hb_font_destroy(hbFonts[i]);\n    }\n    hbFonts.clear();\n  }\n};\n\n// Layout cache datatypes\n\nclass LayoutCacheKey {\n public:\n  LayoutCacheKey(const std::shared_ptr<FontCollection>& collection,\n                 const MinikinPaint& paint,\n                 FontStyle style,\n                 const uint16_t* chars,\n                 size_t start,\n                 size_t count,\n                 size_t nchars,\n                 bool dir)\n      : mChars(chars),\n        mNchars(nchars),\n        mStart(start),\n        mCount(count),\n        mId(collection->getId()),\n        mStyle(style),\n        mSize(paint.size),\n        mScaleX(paint.scaleX),\n        mSkewX(paint.skewX),\n        mLetterSpacing(paint.letterSpacing),\n        mPaintFlags(paint.paintFlags),\n        mHyphenEdit(paint.hyphenEdit),\n        mIsRtl(dir),\n        mHash(computeHash()) {}\n  bool operator==(const LayoutCacheKey& other) const;\n\n  android::hash_t hash() const { return mHash; }\n\n  void copyText() {\n    uint16_t* charsCopy = new uint16_t[mNchars];\n    memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t));\n    mChars = charsCopy;\n  }\n  void freeText() {\n    delete[] mChars;\n    mChars = NULL;\n  }\n\n  void doLayout(Layout* layout,\n                LayoutContext* ctx,\n                const std::shared_ptr<FontCollection>& collection) const {\n    layout->mAdvances.resize(mCount, 0);\n    ctx->clearHbFonts();\n    layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, ctx,\n                        collection);\n  }\n\n private:\n  const uint16_t* mChars;\n  size_t mNchars;\n  size_t mStart;\n  size_t mCount;\n  uint32_t mId;  // for the font collection\n  FontStyle mStyle;\n  float mSize;\n  float mScaleX;\n  float mSkewX;\n  float mLetterSpacing;\n  int32_t mPaintFlags;\n  HyphenEdit mHyphenEdit;\n  bool mIsRtl;\n  // Note: any fields added to MinikinPaint must also be reflected here.\n  // TODO: language matching (possibly integrate into style)\n  android::hash_t mHash;\n\n  android::hash_t computeHash() const;\n};\n\nclass LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> {\n public:\n  LayoutCache() : mCache(kMaxEntries) {\n    mCache.setOnEntryRemovedListener(this);\n  }\n\n  void clear() { mCache.clear(); }\n\n  Layout* get(LayoutCacheKey& key,\n              LayoutContext* ctx,\n              const std::shared_ptr<FontCollection>& collection) {\n    Layout* layout = mCache.get(key);\n    if (layout == NULL) {\n      key.copyText();\n      layout = new Layout();\n      key.doLayout(layout, ctx, collection);\n      mCache.put(key, layout);\n    }\n    return layout;\n  }\n\n private:\n  // callback for OnEntryRemoved\n  void operator()(LayoutCacheKey& key, Layout*& value) {\n    key.freeText();\n    delete value;\n  }\n\n  android::LruCache<LayoutCacheKey, Layout*> mCache;\n\n  // static const size_t kMaxEntries = LruCache<LayoutCacheKey,\n  // Layout*>::kUnlimitedCapacity;\n\n  // TODO: eviction based on memory footprint; for now, we just use a constant\n  // number of strings\n  static const size_t kMaxEntries = 5000;\n};\n\nclass LayoutEngine {\n public:\n  LayoutEngine() {\n    unicodeFunctions = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());\n    hbBuffer = hb_buffer_create();\n    hb_buffer_set_unicode_funcs(hbBuffer, unicodeFunctions);\n  }\n\n  hb_buffer_t* hbBuffer;\n  hb_unicode_funcs_t* unicodeFunctions;\n  LayoutCache layoutCache;\n\n  static LayoutEngine& getInstance() {\n    static LayoutEngine* instance = new LayoutEngine();\n    return *instance;\n  }\n};\n\nbool LayoutCacheKey::operator==(const LayoutCacheKey& other) const {\n  return mId == other.mId && mStart == other.mStart && mCount == other.mCount &&\n         mStyle == other.mStyle && mSize == other.mSize &&\n         mScaleX == other.mScaleX && mSkewX == other.mSkewX &&\n         mLetterSpacing == other.mLetterSpacing &&\n         mPaintFlags == other.mPaintFlags && mHyphenEdit == other.mHyphenEdit &&\n         mIsRtl == other.mIsRtl && mNchars == other.mNchars &&\n         !memcmp(mChars, other.mChars, mNchars * sizeof(uint16_t));\n}\n\nandroid::hash_t LayoutCacheKey::computeHash() const {\n  uint32_t hash = android::JenkinsHashMix(0, mId);\n  hash = android::JenkinsHashMix(hash, mStart);\n  hash = android::JenkinsHashMix(hash, mCount);\n  hash = android::JenkinsHashMix(hash, hash_type(mStyle));\n  hash = android::JenkinsHashMix(hash, hash_type(mSize));\n  hash = android::JenkinsHashMix(hash, hash_type(mScaleX));\n  hash = android::JenkinsHashMix(hash, hash_type(mSkewX));\n  hash = android::JenkinsHashMix(hash, hash_type(mLetterSpacing));\n  hash = android::JenkinsHashMix(hash, hash_type(mPaintFlags));\n  hash = android::JenkinsHashMix(hash, hash_type(mHyphenEdit.getHyphen()));\n  hash = android::JenkinsHashMix(hash, hash_type(mIsRtl));\n  hash = android::JenkinsHashMixShorts(hash, mChars, mNchars);\n  return android::JenkinsHashWhiten(hash);\n}\n\nandroid::hash_t hash_type(const LayoutCacheKey& key) {\n  return key.hash();\n}\n\nvoid MinikinRect::join(const MinikinRect& r) {\n  if (isEmpty()) {\n    set(r);\n  } else if (!r.isEmpty()) {\n    mLeft = std::min(mLeft, r.mLeft);\n    mTop = std::min(mTop, r.mTop);\n    mRight = std::max(mRight, r.mRight);\n    mBottom = std::max(mBottom, r.mBottom);\n  }\n}\n\nvoid Layout::reset() {\n  mGlyphs.clear();\n  mFaces.clear();\n  mBounds.setEmpty();\n  mAdvances.clear();\n  mAdvance = 0;\n}\n\nstatic hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */,\n                                                       void* fontData,\n                                                       hb_codepoint_t glyph,\n                                                       void* /* userData */) {\n  MinikinPaint* paint = reinterpret_cast<MinikinPaint*>(fontData);\n  float advance = paint->font->GetHorizontalAdvance(glyph, *paint);\n  return 256 * advance + 0.5;\n}\n\nstatic hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */,\n                                                  void* /* fontData */,\n                                                  hb_codepoint_t /* glyph */,\n                                                  hb_position_t* /* x */,\n                                                  hb_position_t* /* y */,\n                                                  void* /* userData */) {\n  // Just return true, following the way that Harfbuzz-FreeType\n  // implementation does.\n  return true;\n}\n\nhb_font_funcs_t* getHbFontFuncs(bool forColorBitmapFont) {\n  assertMinikinLocked();\n\n  static hb_font_funcs_t* hbFuncs = nullptr;\n  static hb_font_funcs_t* hbFuncsForColorBitmap = nullptr;\n\n  hb_font_funcs_t** funcs =\n      forColorBitmapFont ? &hbFuncs : &hbFuncsForColorBitmap;\n  if (*funcs == nullptr) {\n    *funcs = hb_font_funcs_create();\n    if (forColorBitmapFont) {\n      // Don't override the h_advance function since we use HarfBuzz's\n      // implementation for emoji for performance reasons. Note that it is\n      // technically possible for a TrueType font to have outline and embedded\n      // bitmap at the same time. We ignore modified advances of hinted outline\n      // glyphs in that case.\n    } else {\n      // Override the h_advance function since we can't use HarfBuzz's\n      // implemenation. It may return the wrong value if the font uses hinting\n      // aggressively.\n      hb_font_funcs_set_glyph_h_advance_func(\n          *funcs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);\n    }\n    hb_font_funcs_set_glyph_h_origin_func(\n        *funcs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);\n    hb_font_funcs_make_immutable(*funcs);\n  }\n  return *funcs;\n}\n\nstatic bool isColorBitmapFont(hb_font_t* font) {\n  hb_face_t* face = hb_font_get_face(font);\n  return hb_ot_color_has_png(face);\n}\n\nstatic float HBFixedToFloat(hb_position_t v) {\n  return scalbnf(v, -8);\n}\n\nstatic hb_position_t HBFloatToFixed(float v) {\n  return scalbnf(v, +8);\n}\n\nvoid Layout::dump() const {\n  for (size_t i = 0; i < mGlyphs.size(); i++) {\n    const LayoutGlyph& glyph = mGlyphs[i];\n    std::cout << glyph.glyph_id << \": \" << glyph.x << \", \" << glyph.y\n              << std::endl;\n  }\n}\n\nint Layout::findFace(const FakedFont& face, LayoutContext* ctx) {\n  unsigned int ix;\n  for (ix = 0; ix < mFaces.size(); ix++) {\n    if (mFaces[ix].font == face.font) {\n      return ix;\n    }\n  }\n  mFaces.push_back(face);\n  // Note: ctx == NULL means we're copying from the cache, no need to create\n  // corresponding hb_font object.\n  if (ctx != NULL) {\n    hb_font_t* font = getHbFontLocked(face.font);\n    hb_font_set_funcs(font, getHbFontFuncs(isColorBitmapFont(font)),\n                      &ctx->paint, 0);\n    ctx->hbFonts.push_back(font);\n  }\n  return ix;\n}\n\nstatic hb_script_t codePointToScript(hb_codepoint_t codepoint) {\n  static hb_unicode_funcs_t* u = 0;\n  if (!u) {\n    u = LayoutEngine::getInstance().unicodeFunctions;\n  }\n  return hb_unicode_script(u, codepoint);\n}\n\nstatic hb_codepoint_t decodeUtf16(const uint16_t* chars,\n                                  size_t len,\n                                  ssize_t* iter) {\n  const uint16_t v = chars[(*iter)++];\n  // test whether v in (0xd800..0xdfff), lead or trail surrogate\n  if ((v & 0xf800) == 0xd800) {\n    // test whether v in (0xd800..0xdbff), lead surrogate\n    if (size_t(*iter) < len && (v & 0xfc00) == 0xd800) {\n      const uint16_t v2 = chars[(*iter)++];\n      // test whether v2 in (0xdc00..0xdfff), trail surrogate\n      if ((v2 & 0xfc00) == 0xdc00) {\n        // (0xd800 0xdc00) in utf-16 maps to 0x10000 in ucs-32\n        const hb_codepoint_t delta = (0xd800 << 10) + 0xdc00 - 0x10000;\n        return (((hb_codepoint_t)v) << 10) + v2 - delta;\n      }\n      (*iter) -= 1;\n      return 0xFFFDu;\n    } else {\n      return 0xFFFDu;\n    }\n  } else {\n    return v;\n  }\n}\n\nstatic hb_script_t getScriptRun(const uint16_t* chars,\n                                size_t len,\n                                ssize_t* iter) {\n  if (size_t(*iter) == len) {\n    return HB_SCRIPT_UNKNOWN;\n  }\n  uint32_t cp = decodeUtf16(chars, len, iter);\n  hb_script_t current_script = codePointToScript(cp);\n  for (;;) {\n    if (size_t(*iter) == len)\n      break;\n    const ssize_t prev_iter = *iter;\n    cp = decodeUtf16(chars, len, iter);\n    const hb_script_t script = codePointToScript(cp);\n    if (script != current_script) {\n      if (current_script == HB_SCRIPT_INHERITED ||\n          current_script == HB_SCRIPT_COMMON) {\n        current_script = script;\n      } else if (script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON) {\n        continue;\n      } else {\n        *iter = prev_iter;\n        break;\n      }\n    }\n  }\n  if (current_script == HB_SCRIPT_INHERITED) {\n    current_script = HB_SCRIPT_COMMON;\n  }\n\n  return current_script;\n}\n\n/**\n * Disable certain scripts (mostly those with cursive connection) from having\n * letterspacing applied. See https://github.com/behdad/harfbuzz/issues/64 for\n * more details.\n */\nstatic bool isScriptOkForLetterspacing(hb_script_t script) {\n  return !(script == HB_SCRIPT_ARABIC || script == HB_SCRIPT_NKO ||\n           script == HB_SCRIPT_PSALTER_PAHLAVI || script == HB_SCRIPT_MANDAIC ||\n           script == HB_SCRIPT_MONGOLIAN || script == HB_SCRIPT_PHAGS_PA ||\n           script == HB_SCRIPT_DEVANAGARI || script == HB_SCRIPT_BENGALI ||\n           script == HB_SCRIPT_GURMUKHI || script == HB_SCRIPT_MODI ||\n           script == HB_SCRIPT_SHARADA || script == HB_SCRIPT_SYLOTI_NAGRI ||\n           script == HB_SCRIPT_TIRHUTA || script == HB_SCRIPT_OGHAM);\n}\n\nclass BidiText {\n public:\n  class Iter {\n   public:\n    struct RunInfo {\n      int32_t mRunStart;\n      int32_t mRunLength;\n      bool mIsRtl;\n    };\n\n    Iter(UBiDi* bidi,\n         size_t start,\n         size_t end,\n         size_t runIndex,\n         size_t runCount,\n         bool isRtl);\n\n    bool operator!=(const Iter& other) const {\n      return mIsEnd != other.mIsEnd || mNextRunIndex != other.mNextRunIndex ||\n             mBidi != other.mBidi;\n    }\n\n    const RunInfo& operator*() const { return mRunInfo; }\n\n    const Iter& operator++() {\n      updateRunInfo();\n      return *this;\n    }\n\n   private:\n    UBiDi* const mBidi;\n    bool mIsEnd;\n    size_t mNextRunIndex;\n    const size_t mRunCount;\n    const int32_t mStart;\n    const int32_t mEnd;\n    RunInfo mRunInfo;\n\n    void updateRunInfo();\n  };\n\n  BidiText(const uint16_t* buf,\n           size_t start,\n           size_t count,\n           size_t bufSize,\n           int bidiFlags);\n\n  ~BidiText() {\n    if (mBidi) {\n      ubidi_close(mBidi);\n    }\n  }\n\n  Iter begin() const { return Iter(mBidi, mStart, mEnd, 0, mRunCount, mIsRtl); }\n\n  Iter end() const {\n    return Iter(mBidi, mStart, mEnd, mRunCount, mRunCount, mIsRtl);\n  }\n\n private:\n  const size_t mStart;\n  const size_t mEnd;\n  const size_t mBufSize;\n  UBiDi* mBidi;\n  size_t mRunCount;\n  bool mIsRtl;\n\n  BidiText(const BidiText&) = delete;\n  void operator=(const BidiText&) = delete;\n};\n\nBidiText::Iter::Iter(UBiDi* bidi,\n                     size_t start,\n                     size_t end,\n                     size_t runIndex,\n                     size_t runCount,\n                     bool isRtl)\n    : mBidi(bidi),\n      mIsEnd(runIndex == runCount),\n      mNextRunIndex(runIndex),\n      mRunCount(runCount),\n      mStart(start),\n      mEnd(end),\n      mRunInfo() {\n  if (mRunCount == 1) {\n    mRunInfo.mRunStart = start;\n    mRunInfo.mRunLength = end - start;\n    mRunInfo.mIsRtl = isRtl;\n    mNextRunIndex = mRunCount;\n    return;\n  }\n  updateRunInfo();\n}\n\nvoid BidiText::Iter::updateRunInfo() {\n  if (mNextRunIndex == mRunCount) {\n    // All runs have been iterated.\n    mIsEnd = true;\n    return;\n  }\n  int32_t startRun = -1;\n  int32_t lengthRun = -1;\n  const UBiDiDirection runDir =\n      ubidi_getVisualRun(mBidi, mNextRunIndex, &startRun, &lengthRun);\n  mNextRunIndex++;\n  if (startRun == -1 || lengthRun == -1) {\n    ALOGE(\"invalid visual run\");\n    // skip the invalid run.\n    updateRunInfo();\n    return;\n  }\n  const int32_t runEnd = std::min(startRun + lengthRun, mEnd);\n  mRunInfo.mRunStart = std::max(startRun, mStart);\n  mRunInfo.mRunLength = runEnd - mRunInfo.mRunStart;\n  if (mRunInfo.mRunLength <= 0) {\n    // skip the empty run.\n    updateRunInfo();\n    return;\n  }\n  mRunInfo.mIsRtl = (runDir == UBIDI_RTL);\n}\n\nBidiText::BidiText(const uint16_t* buf,\n                   size_t start,\n                   size_t count,\n                   size_t bufSize,\n                   int bidiFlags)\n    : mStart(start),\n      mEnd(start + count),\n      mBufSize(bufSize),\n      mBidi(NULL),\n      mRunCount(1),\n      mIsRtl((bidiFlags & kDirection_Mask) != 0) {\n  if (bidiFlags == kBidi_Force_LTR || bidiFlags == kBidi_Force_RTL) {\n    // force single run.\n    return;\n  }\n  mBidi = ubidi_open();\n  if (!mBidi) {\n    ALOGE(\"error creating bidi object\");\n    return;\n  }\n  UErrorCode status = U_ZERO_ERROR;\n  // Set callbacks to override bidi classes of new emoji\n  ubidi_setClassCallback(mBidi, emojiBidiOverride, nullptr, nullptr, nullptr,\n                         &status);\n  if (!U_SUCCESS(status)) {\n    ALOGE(\"error setting bidi callback function, status = %d\", status);\n    return;\n  }\n\n  UBiDiLevel bidiReq = bidiFlags;\n  if (bidiFlags == kBidi_Default_LTR) {\n    bidiReq = UBIDI_DEFAULT_LTR;\n  } else if (bidiFlags == kBidi_Default_RTL) {\n    bidiReq = UBIDI_DEFAULT_RTL;\n  }\n  ubidi_setPara(mBidi, reinterpret_cast<const UChar*>(buf), mBufSize, bidiReq,\n                NULL, &status);\n  if (!U_SUCCESS(status)) {\n    ALOGE(\"error calling ubidi_setPara, status = %d\", status);\n    return;\n  }\n  const int paraDir = ubidi_getParaLevel(mBidi) & kDirection_Mask;\n  const ssize_t rc = ubidi_countRuns(mBidi, &status);\n  if (!U_SUCCESS(status) || rc < 0) {\n    ALOGW(\"error counting bidi runs, status = %d\", status);\n  }\n  if (!U_SUCCESS(status) || rc <= 1) {\n    mIsRtl = (paraDir == kBidi_RTL);\n    return;\n  }\n  mRunCount = rc;\n}\n\nvoid Layout::doLayout(const uint16_t* buf,\n                      size_t start,\n                      size_t count,\n                      size_t bufSize,\n                      bool isRtl,\n                      const FontStyle& style,\n                      const MinikinPaint& paint,\n                      const std::shared_ptr<FontCollection>& collection) {\n  std::scoped_lock _l(gMinikinLock);\n\n  LayoutContext ctx;\n  ctx.style = style;\n  ctx.paint = paint;\n\n  reset();\n  mAdvances.resize(count, 0);\n\n  doLayoutRunCached(buf, start, count, bufSize, isRtl, &ctx, start, collection,\n                    this, NULL);\n\n  ctx.clearHbFonts();\n}\n\nfloat Layout::measureText(const uint16_t* buf,\n                          size_t start,\n                          size_t count,\n                          size_t bufSize,\n                          bool isRtl,\n                          const FontStyle& style,\n                          const MinikinPaint& paint,\n                          const std::shared_ptr<FontCollection>& collection,\n                          float* advances) {\n  std::scoped_lock _l(gMinikinLock);\n\n  LayoutContext ctx;\n  ctx.style = style;\n  ctx.paint = paint;\n\n  float advance = doLayoutRunCached(buf, start, count, bufSize, isRtl, &ctx, 0,\n                                    collection, NULL, advances);\n\n  ctx.clearHbFonts();\n  return advance;\n}\n\nfloat Layout::doLayoutRunCached(\n    const uint16_t* buf,\n    size_t start,\n    size_t count,\n    size_t bufSize,\n    bool isRtl,\n    LayoutContext* ctx,\n    size_t dstStart,\n    const std::shared_ptr<FontCollection>& collection,\n    Layout* layout,\n    float* advances) {\n  const uint32_t originalHyphen = ctx->paint.hyphenEdit.getHyphen();\n  float advance = 0;\n  if (!isRtl) {\n    // left to right\n    size_t wordstart = start == bufSize\n                           ? start\n                           : getPrevWordBreakForCache(buf, start + 1, bufSize);\n    size_t wordend;\n    for (size_t iter = start; iter < start + count; iter = wordend) {\n      wordend = getNextWordBreakForCache(buf, iter, bufSize);\n      // Only apply hyphen to the first or last word in the string.\n      uint32_t hyphen = originalHyphen;\n      if (iter != start) {  // Not the first word\n        hyphen &= ~HyphenEdit::MASK_START_OF_LINE;\n      }\n      if (wordend < start + count) {  // Not the last word\n        hyphen &= ~HyphenEdit::MASK_END_OF_LINE;\n      }\n      ctx->paint.hyphenEdit = hyphen;\n      size_t wordcount = std::min(start + count, wordend) - iter;\n      advance += doLayoutWord(buf + wordstart, iter - wordstart, wordcount,\n                              wordend - wordstart, isRtl, ctx, iter - dstStart,\n                              collection, layout,\n                              advances ? advances + (iter - start) : advances);\n      wordstart = wordend;\n    }\n  } else {\n    // right to left\n    size_t wordstart;\n    size_t end = start + count;\n    size_t wordend =\n        end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize);\n    for (size_t iter = end; iter > start; iter = wordstart) {\n      wordstart = getPrevWordBreakForCache(buf, iter, bufSize);\n      // Only apply hyphen to the first (rightmost) or last (leftmost) word in\n      // the string.\n      uint32_t hyphen = originalHyphen;\n      if (wordstart > start) {  // Not the first word\n        hyphen &= ~HyphenEdit::MASK_START_OF_LINE;\n      }\n      if (iter != end) {  // Not the last word\n        hyphen &= ~HyphenEdit::MASK_END_OF_LINE;\n      }\n      ctx->paint.hyphenEdit = hyphen;\n      size_t bufStart = std::max(start, wordstart);\n      advance += doLayoutWord(\n          buf + wordstart, bufStart - wordstart, iter - bufStart,\n          wordend - wordstart, isRtl, ctx, bufStart - dstStart, collection,\n          layout, advances ? advances + (bufStart - start) : advances);\n      wordend = wordstart;\n    }\n  }\n  return advance;\n}\n\nfloat Layout::doLayoutWord(const uint16_t* buf,\n                           size_t start,\n                           size_t count,\n                           size_t bufSize,\n                           bool isRtl,\n                           LayoutContext* ctx,\n                           size_t bufStart,\n                           const std::shared_ptr<FontCollection>& collection,\n                           Layout* layout,\n                           float* advances) {\n  LayoutCache& cache = LayoutEngine::getInstance().layoutCache;\n  LayoutCacheKey key(collection, ctx->paint, ctx->style, buf, start, count,\n                     bufSize, isRtl);\n\n  float wordSpacing =\n      count == 1 && isWordSpace(buf[start]) ? ctx->paint.wordSpacing : 0;\n\n  float advance;\n  if (ctx->paint.skipCache()) {\n    Layout layoutForWord;\n    key.doLayout(&layoutForWord, ctx, collection);\n    if (layout) {\n      layout->appendLayout(&layoutForWord, bufStart, wordSpacing);\n    }\n    if (advances) {\n      layoutForWord.getAdvances(advances);\n    }\n    advance = layoutForWord.getAdvance();\n  } else {\n    Layout* layoutForWord = cache.get(key, ctx, collection);\n    if (layout) {\n      layout->appendLayout(layoutForWord, bufStart, wordSpacing);\n    }\n    if (advances) {\n      layoutForWord->getAdvances(advances);\n    }\n    advance = layoutForWord->getAdvance();\n  }\n\n  if (wordSpacing != 0) {\n    advance += wordSpacing;\n    if (advances) {\n      advances[0] += wordSpacing;\n    }\n  }\n  return advance;\n}\n\nstatic void addFeatures(const std::string& str,\n                        std::vector<hb_feature_t>* features) {\n  if (!str.size())\n    return;\n\n  const char* start = str.c_str();\n  const char* end = start + str.size();\n\n  while (start < end) {\n    static hb_feature_t feature;\n    const char* p = strchr(start, ',');\n    if (!p)\n      p = end;\n    /* We do not allow setting features on ranges.  As such, reject any\n     * setting that has non-universal range. */\n    if (hb_feature_from_string(start, p - start, &feature) &&\n        feature.start == 0 && feature.end == (unsigned int)-1)\n      features->push_back(feature);\n    start = p + 1;\n  }\n}\n\nstatic const hb_codepoint_t CHAR_HYPHEN = 0x2010; /* HYPHEN */\n\nstatic inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen,\n                                                 hb_font_t* font) {\n  hb_codepoint_t glyph;\n  if (preferredHyphen == 0x058A    /* ARMENIAN_HYPHEN */\n      || preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */\n      || preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {\n    if (hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {\n      return preferredHyphen;\n    } else {\n      // The original hyphen requested was not supported. Let's try and see if\n      // the Unicode hyphen is supported.\n      preferredHyphen = CHAR_HYPHEN;\n    }\n  }\n  if (preferredHyphen == CHAR_HYPHEN) { /* HYPHEN */\n    // Fallback to ASCII HYPHEN-MINUS if the font didn't have a glyph for the\n    // preferred hyphen. Note that we intentionally don't do anything special if\n    // the font doesn't have a HYPHEN-MINUS either, so a tofu could be shown,\n    // hinting towards something missing.\n    if (!hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {\n      return 0x002D;  // HYPHEN-MINUS\n    }\n  }\n  return preferredHyphen;\n}\n\nstatic inline void addHyphenToHbBuffer(hb_buffer_t* buffer,\n                                       hb_font_t* font,\n                                       uint32_t hyphen,\n                                       uint32_t cluster) {\n  const uint32_t* hyphenStr = HyphenEdit::getHyphenString(hyphen);\n  while (*hyphenStr != 0) {\n    hb_codepoint_t hyphenChar = determineHyphenChar(*hyphenStr, font);\n    hb_buffer_add(buffer, hyphenChar, cluster);\n    hyphenStr++;\n  }\n}\n\n// Returns the cluster value assigned to the first codepoint added to the\n// buffer, which can be used to translate cluster values returned by HarfBuzz to\n// input indices.\nstatic inline uint32_t addToHbBuffer(hb_buffer_t* buffer,\n                                     const uint16_t* buf,\n                                     size_t start,\n                                     size_t count,\n                                     size_t bufSize,\n                                     ssize_t scriptRunStart,\n                                     ssize_t scriptRunEnd,\n                                     HyphenEdit hyphenEdit,\n                                     hb_font_t* hbFont) {\n  // Only hyphenate the very first script run for starting hyphens.\n  const uint32_t startHyphen =\n      (scriptRunStart == 0) ? hyphenEdit.getStart() : HyphenEdit::NO_EDIT;\n  // Only hyphenate the very last script run for ending hyphens.\n  const uint32_t endHyphen = (static_cast<size_t>(scriptRunEnd) == count)\n                                 ? hyphenEdit.getEnd()\n                                 : HyphenEdit::NO_EDIT;\n\n  // In the following code, we drop the pre-context and/or post-context if there\n  // is a hyphen edit at that end. This is not absolutely necessary, since\n  // HarfBuzz uses contexts only for joining scripts at the moment, e.g. to\n  // determine if the first or last letter of a text range to shape should take\n  // a joining form based on an adjacent letter or joiner (that comes from the\n  // context).\n  //\n  // TODO: Revisit this for:\n  // 1. Desperate breaks for joining scripts like Arabic (where it may be better\n  // to keep\n  //    the context);\n  // 2. Special features like start-of-word font features (not implemented in\n  // HarfBuzz\n  //    yet).\n\n  // We don't have any start-of-line replacement edit yet, so we don't need to\n  // check for those.\n  if (HyphenEdit::isInsertion(startHyphen)) {\n    // A cluster value of zero guarantees that the inserted hyphen will be in\n    // the same cluster with the next codepoint, since there is no pre-context.\n    addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster value */);\n  }\n\n  const uint16_t* hbText;\n  int hbTextLength;\n  unsigned int hbItemOffset;\n  unsigned int hbItemLength = scriptRunEnd - scriptRunStart;  // This is >= 1.\n\n  const bool hasEndInsertion = HyphenEdit::isInsertion(endHyphen);\n  const bool hasEndReplacement = HyphenEdit::isReplacement(endHyphen);\n  if (hasEndReplacement) {\n    // Skip the last code unit while copying the buffer for HarfBuzz if it's a\n    // replacement. We don't need to worry about non-BMP characters yet since\n    // replacements are only done for code units at the moment.\n    hbItemLength -= 1;\n  }\n\n  if (startHyphen == HyphenEdit::NO_EDIT) {\n    // No edit at the beginning. Use the whole pre-context.\n    hbText = buf;\n    hbItemOffset = start + scriptRunStart;\n  } else {\n    // There's an edit at the beginning. Drop the pre-context and start the\n    // buffer at where we want to start shaping.\n    hbText = buf + start + scriptRunStart;\n    hbItemOffset = 0;\n  }\n\n  if (endHyphen == HyphenEdit::NO_EDIT) {\n    // No edit at the end, use the whole post-context.\n    hbTextLength = (buf + bufSize) - hbText;\n  } else {\n    // There is an edit at the end. Drop the post-context.\n    hbTextLength = hbItemOffset + hbItemLength;\n  }\n\n  hb_buffer_add_utf16(buffer, hbText, hbTextLength, hbItemOffset, hbItemLength);\n\n  unsigned int numCodepoints;\n  hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer, &numCodepoints);\n\n  // Add the hyphen at the end, if there's any.\n  if (hasEndInsertion || hasEndReplacement) {\n    // When a hyphen is inserted, by assigning the added hyphen and the last\n    // codepoint added to the HarfBuzz buffer to the same cluster, we can make\n    // sure that they always remain in the same cluster, even if the last\n    // codepoint gets merged into another cluster (for example when it's a\n    // combining mark).\n    //\n    // When a replacement happens instead, we want it to get the cluster value\n    // of the character it's replacing, which is one \"codepoint length\" larger\n    // than the last cluster. But since the character replaced is always just\n    // one code unit, we can just add 1.\n    uint32_t hyphenCluster;\n    if (numCodepoints == 0) {\n      // Nothing was added to the HarfBuzz buffer. This can only happen if\n      // we have a replacement that is replacing a one-code unit script run.\n      hyphenCluster = 0;\n    } else {\n      hyphenCluster =\n          cpInfo[numCodepoints - 1].cluster + (uint32_t)hasEndReplacement;\n    }\n    addHyphenToHbBuffer(buffer, hbFont, endHyphen, hyphenCluster);\n    // Since we have just added to the buffer, cpInfo no longer necessarily\n    // points to the right place. Refresh it.\n    cpInfo =\n        hb_buffer_get_glyph_infos(buffer, nullptr /* we don't need the size */);\n  }\n  return cpInfo[0].cluster;\n}\n\nvoid Layout::doLayoutRun(const uint16_t* buf,\n                         size_t start,\n                         size_t count,\n                         size_t bufSize,\n                         bool isRtl,\n                         LayoutContext* ctx,\n                         const std::shared_ptr<FontCollection>& collection) {\n  hb_buffer_t* buffer = LayoutEngine::getInstance().hbBuffer;\n  std::vector<FontCollection::Run> items;\n  collection->itemize(buf + start, count, ctx->style, &items);\n\n  std::vector<hb_feature_t> features;\n  // Disable default-on non-required ligature features if letter-spacing\n  // See http://dev.w3.org/csswg/css-text-3/#letter-spacing-property\n  // \"When the effective spacing between two characters is not zero (due to\n  // either justification or a non-zero value of letter-spacing), user agents\n  // should not apply optional ligatures.\"\n  if (fabs(ctx->paint.letterSpacing) > 0.03) {\n    static const hb_feature_t no_liga = {HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u};\n    static const hb_feature_t no_clig = {HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u};\n    features.push_back(no_liga);\n    features.push_back(no_clig);\n  }\n  addFeatures(ctx->paint.fontFeatureSettings, &features);\n\n  double size = ctx->paint.size;\n  double scaleX = ctx->paint.scaleX;\n\n  float x = mAdvance;\n  float y = 0;\n  for (int run_ix = isRtl ? items.size() - 1 : 0;\n       isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());\n       isRtl ? --run_ix : ++run_ix) {\n    FontCollection::Run& run = items[run_ix];\n    if (run.fakedFont.font == NULL) {\n      ALOGE(\"no font for run starting u+%04x length %d\", buf[run.start],\n            run.end - run.start);\n      continue;\n    }\n    int font_ix = findFace(run.fakedFont, ctx);\n    ctx->paint.font = mFaces[font_ix].font;\n    ctx->paint.fakery = mFaces[font_ix].fakery;\n    hb_font_t* hbFont = ctx->hbFonts[font_ix];\n#ifdef VERBOSE_DEBUG\n    ALOGD(\"Run %zu, font %d [%d:%d]\", run_ix, font_ix, run.start, run.end);\n#endif\n\n    hb_font_set_ppem(hbFont, size * scaleX, size);\n    hb_font_set_scale(hbFont, HBFloatToFixed(size * scaleX),\n                      HBFloatToFixed(size));\n\n    const bool is_color_bitmap_font = isColorBitmapFont(hbFont);\n\n    // TODO: if there are multiple scripts within a font in an RTL run,\n    // we need to reorder those runs. This is unlikely with our current\n    // font stack, but should be done for correctness.\n\n    // Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end,\n    // run between 0 and count.\n    ssize_t scriptRunEnd;\n    for (ssize_t scriptRunStart = run.start; scriptRunStart < run.end;\n         scriptRunStart = scriptRunEnd) {\n      scriptRunEnd = scriptRunStart;\n      hb_script_t script =\n          getScriptRun(buf + start, run.end, &scriptRunEnd /* iterator */);\n      // After the last line, scriptRunEnd is guaranteed to have increased,\n      // since the only time getScriptRun does not increase its iterator is when\n      // it has already reached the end of the buffer. But that can't happen,\n      // since if we have already reached the end of the buffer, we should have\n      // had (scriptRunEnd == run.end), which means (scriptRunStart == run.end)\n      // which is impossible due to the exit condition of the for loop. So we\n      // can be sure that scriptRunEnd > scriptRunStart.\n\n      double letterSpace = 0.0;\n      double letterSpaceHalfLeft = 0.0;\n      double letterSpaceHalfRight = 0.0;\n\n      if (ctx->paint.letterSpacing != 0.0 &&\n          isScriptOkForLetterspacing(script)) {\n        letterSpace = ctx->paint.letterSpacing * size * scaleX;\n        if ((ctx->paint.paintFlags & LinearTextFlag) == 0) {\n          letterSpace = round(letterSpace);\n          letterSpaceHalfLeft = floor(letterSpace * 0.5);\n        } else {\n          letterSpaceHalfLeft = letterSpace * 0.5;\n        }\n        letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;\n      }\n\n      hb_buffer_clear_contents(buffer);\n      hb_buffer_set_script(buffer, script);\n      hb_buffer_set_direction(buffer,\n                              isRtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);\n      const FontLanguages& langList =\n          FontLanguageListCache::getById(ctx->style.getLanguageListId());\n      if (langList.size() != 0) {\n        const FontLanguage* hbLanguage = &langList[0];\n        for (size_t i = 0; i < langList.size(); ++i) {\n          if (langList[i].supportsHbScript(script)) {\n            hbLanguage = &langList[i];\n            break;\n          }\n        }\n        hb_buffer_set_language(buffer, hbLanguage->getHbLanguage());\n      }\n\n      const uint32_t clusterStart =\n          addToHbBuffer(buffer, buf, start, count, bufSize, scriptRunStart,\n                        scriptRunEnd, ctx->paint.hyphenEdit, hbFont);\n\n      hb_shape(hbFont, buffer, features.empty() ? NULL : &features[0],\n               features.size());\n      unsigned int numGlyphs;\n      hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);\n      hb_glyph_position_t* positions =\n          hb_buffer_get_glyph_positions(buffer, NULL);\n\n      // At this point in the code, the cluster values in the info buffer\n      // correspond to the input characters with some shift. The cluster value\n      // clusterStart corresponds to the first character passed to HarfBuzz,\n      // which is at buf[start + scriptRunStart] whose advance needs to be saved\n      // into mAdvances[scriptRunStart]. So cluster values need to be reduced by\n      // (clusterStart - scriptRunStart) to get converted to indices of\n      // mAdvances.\n      const ssize_t clusterOffset = clusterStart - scriptRunStart;\n\n      if (numGlyphs) {\n        mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;\n        x += letterSpaceHalfLeft;\n      }\n      for (unsigned int i = 0; i < numGlyphs; i++) {\n#ifdef VERBOSE_DEBUG\n        ALOGD(\"%d %d %d %d\", positions[i].x_advance, positions[i].y_advance,\n              positions[i].x_offset, positions[i].y_offset);\n        ALOGD(\"DoLayout %u: %f; %d, %d\", info[i].codepoint,\n              HBFixedToFloat(positions[i].x_advance), positions[i].x_offset,\n              positions[i].y_offset);\n#endif\n        if (i > 0 && info[i - 1].cluster != info[i].cluster) {\n          mAdvances[info[i - 1].cluster - clusterOffset] +=\n              letterSpaceHalfRight;\n          mAdvances[info[i].cluster - clusterOffset] += letterSpaceHalfLeft;\n          x += letterSpace;\n        }\n\n        hb_codepoint_t glyph_ix = info[i].codepoint;\n        float xoff = HBFixedToFloat(positions[i].x_offset);\n        float yoff = -HBFixedToFloat(positions[i].y_offset);\n        xoff += yoff * ctx->paint.skewX;\n        LayoutGlyph glyph = {\n            font_ix, glyph_ix, x + xoff, y + yoff,\n            static_cast<uint32_t>(info[i].cluster - clusterOffset)};\n        mGlyphs.push_back(glyph);\n        float xAdvance = HBFixedToFloat(positions[i].x_advance);\n        if ((ctx->paint.paintFlags & LinearTextFlag) == 0) {\n          xAdvance = roundf(xAdvance);\n        }\n        MinikinRect glyphBounds;\n        hb_glyph_extents_t extents = {};\n        if (is_color_bitmap_font &&\n            hb_font_get_glyph_extents(hbFont, glyph_ix, &extents)) {\n          // Note that it is technically possible for a TrueType font to have\n          // outline and embedded bitmap at the same time. We ignore modified\n          // bbox of hinted outline glyphs in that case.\n          glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing));\n          glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing));\n          glyphBounds.mRight =\n              roundf(HBFixedToFloat(extents.x_bearing + extents.width));\n          glyphBounds.mBottom =\n              roundf(HBFixedToFloat(-extents.y_bearing - extents.height));\n        } else {\n          ctx->paint.font->GetBounds(&glyphBounds, glyph_ix, ctx->paint);\n        }\n        glyphBounds.offset(x + xoff, y + yoff);\n        mBounds.join(glyphBounds);\n        if (static_cast<size_t>(info[i].cluster - clusterOffset) < count) {\n          mAdvances[info[i].cluster - clusterOffset] += xAdvance;\n        } else {\n          ALOGE(\"cluster %zu (start %zu) out of bounds of count %zu\",\n                info[i].cluster - clusterOffset, start, count);\n        }\n        x += xAdvance;\n      }\n      if (numGlyphs) {\n        mAdvances[info[numGlyphs - 1].cluster - clusterOffset] +=\n            letterSpaceHalfRight;\n        x += letterSpaceHalfRight;\n      }\n    }\n  }\n  mAdvance = x;\n}\n\nvoid Layout::appendLayout(Layout* src, size_t start, float extraAdvance) {\n  int fontMapStack[16];\n  int* fontMap;\n  if (src->mFaces.size() < sizeof(fontMapStack) / sizeof(fontMapStack[0])) {\n    fontMap = fontMapStack;\n  } else {\n    fontMap = new int[src->mFaces.size()];\n  }\n  for (size_t i = 0; i < src->mFaces.size(); i++) {\n    int font_ix = findFace(src->mFaces[i], NULL);\n    fontMap[i] = font_ix;\n  }\n  // LibTxt: Changed x0 from int to float to prevent rounding that causes text\n  // jitter.\n  float x0 = mAdvance;\n  for (size_t i = 0; i < src->mGlyphs.size(); i++) {\n    LayoutGlyph& srcGlyph = src->mGlyphs[i];\n    int font_ix = fontMap[srcGlyph.font_ix];\n    unsigned int glyph_id = srcGlyph.glyph_id;\n    float x = x0 + srcGlyph.x;\n    float y = srcGlyph.y;\n    LayoutGlyph glyph = {font_ix, glyph_id, x, y,\n                         static_cast<uint32_t>(srcGlyph.cluster + start)};\n    mGlyphs.push_back(glyph);\n  }\n  for (size_t i = 0; i < src->mAdvances.size(); i++) {\n    mAdvances[i + start] = src->mAdvances[i];\n    if (i == 0)\n      mAdvances[i + start] += extraAdvance;\n  }\n  MinikinRect srcBounds(src->mBounds);\n  srcBounds.offset(x0, 0);\n  mBounds.join(srcBounds);\n  mAdvance += src->mAdvance + extraAdvance;\n\n  if (fontMap != fontMapStack) {\n    delete[] fontMap;\n  }\n}\n\nsize_t Layout::nGlyphs() const {\n  return mGlyphs.size();\n}\n\nconst MinikinFont* Layout::getFont(int i) const {\n  const LayoutGlyph& glyph = mGlyphs[i];\n  return mFaces[glyph.font_ix].font;\n}\n\nFontFakery Layout::getFakery(int i) const {\n  const LayoutGlyph& glyph = mGlyphs[i];\n  return mFaces[glyph.font_ix].fakery;\n}\n\nunsigned int Layout::getGlyphId(int i) const {\n  const LayoutGlyph& glyph = mGlyphs[i];\n  return glyph.glyph_id;\n}\n\n// libtxt extension\nunsigned int Layout::getGlyphCluster(int i) const {\n  const LayoutGlyph& glyph = mGlyphs[i];\n  return glyph.cluster;\n}\n\nfloat Layout::getX(int i) const {\n  const LayoutGlyph& glyph = mGlyphs[i];\n  return glyph.x;\n}\n\nfloat Layout::getY(int i) const {\n  const LayoutGlyph& glyph = mGlyphs[i];\n  return glyph.y;\n}\n\nfloat Layout::getAdvance() const {\n  return mAdvance;\n}\n\nvoid Layout::getAdvances(float* advances) {\n  memcpy(advances, &mAdvances[0], mAdvances.size() * sizeof(float));\n}\n\nvoid Layout::getBounds(MinikinRect* bounds) const {\n  bounds->set(mBounds);\n}\n\nvoid Layout::purgeCaches() {\n  std::scoped_lock _l(gMinikinLock);\n  LayoutCache& layoutCache = LayoutEngine::getInstance().layoutCache;\n  layoutCache.clear();\n  purgeHbFontCacheLocked();\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/Layout.h",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_LAYOUT_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_LAYOUT_H_\n\n#include <hb.h>\n\n#include <memory>\n#include <vector>\n\n#include <minikin/FontCollection.h>\n\nnamespace minikin {\n\nstruct LayoutGlyph {\n  // index into mFaces and mHbFonts vectors. We could imagine\n  // moving this into a run length representation, because it's\n  // more efficient for long strings, and we'll probably need\n  // something like that for paint attributes (color, underline,\n  // fake b/i, etc), as having those per-glyph is bloated.\n  int font_ix;\n\n  unsigned int glyph_id;\n  float x;\n  float y;\n\n  // libtxt extension: record the cluster (character index) that corresponds\n  // to this glyph\n  uint32_t cluster;\n};\n\n// Internal state used during layout operation\nstruct LayoutContext;\n\nenum {\n  kBidi_LTR = 0,\n  kBidi_RTL = 1,\n  kBidi_Default_LTR = 2,\n  kBidi_Default_RTL = 3,\n  kBidi_Force_LTR = 4,\n  kBidi_Force_RTL = 5,\n\n  kBidi_Mask = 0x7\n};\n\n// Lifecycle and threading assumptions for Layout:\n// The object is assumed to be owned by a single thread; multiple threads\n// may not mutate it at the same time.\nclass Layout {\n public:\n  Layout() : mGlyphs(), mAdvances(), mFaces(), mAdvance(0), mBounds() {\n    mBounds.setEmpty();\n  }\n\n  Layout(Layout&& layout) = default;\n\n  // Forbid copying and assignment.\n  Layout(const Layout&) = delete;\n  void operator=(const Layout&) = delete;\n\n  void dump() const;\n\n  void doLayout(const uint16_t* buf,\n                size_t start,\n                size_t count,\n                size_t bufSize,\n                bool isRtl,\n                const FontStyle& style,\n                const MinikinPaint& paint,\n                const std::shared_ptr<FontCollection>& collection);\n\n  static float measureText(const uint16_t* buf,\n                           size_t start,\n                           size_t count,\n                           size_t bufSize,\n                           bool isRtl,\n                           const FontStyle& style,\n                           const MinikinPaint& paint,\n                           const std::shared_ptr<FontCollection>& collection,\n                           float* advances);\n\n  // public accessors\n  size_t nGlyphs() const;\n  const MinikinFont* getFont(int i) const;\n  FontFakery getFakery(int i) const;\n  unsigned int getGlyphId(int i) const;\n  uint32_t getGlyphCluster(int i) const;  // libtxt extension\n  float getX(int i) const;\n  float getY(int i) const;\n\n  float getAdvance() const;\n\n  // Get advances, copying into caller-provided buffer. The size of this\n  // buffer must match the length of the string (count arg to doLayout).\n  void getAdvances(float* advances);\n\n  // The i parameter is an offset within the buf relative to start, it is <\n  // count, where start and count are the parameters to doLayout\n  float getCharAdvance(size_t i) const { return mAdvances[i]; }\n\n  void getBounds(MinikinRect* rect) const;\n\n  // Purge all caches, useful in low memory conditions\n  static void purgeCaches();\n\n private:\n  friend class LayoutCacheKey;\n\n  // Find a face in the mFaces vector, or create a new entry\n  int findFace(const FakedFont& face, LayoutContext* ctx);\n\n  // Clears layout, ready to be used again\n  void reset();\n\n  // Lay out a single bidi run\n  // When layout is not null, layout info will be stored in the object.\n  // When advances is not null, measurement results will be stored in the array.\n  static float doLayoutRunCached(\n      const uint16_t* buf,\n      size_t runStart,\n      size_t runLength,\n      size_t bufSize,\n      bool isRtl,\n      LayoutContext* ctx,\n      size_t dstStart,\n      const std::shared_ptr<FontCollection>& collection,\n      Layout* layout,\n      float* advances);\n\n  // Lay out a single word\n  static float doLayoutWord(const uint16_t* buf,\n                            size_t start,\n                            size_t count,\n                            size_t bufSize,\n                            bool isRtl,\n                            LayoutContext* ctx,\n                            size_t bufStart,\n                            const std::shared_ptr<FontCollection>& collection,\n                            Layout* layout,\n                            float* advances);\n\n  // Lay out a single bidi run\n  void doLayoutRun(const uint16_t* buf,\n                   size_t start,\n                   size_t count,\n                   size_t bufSize,\n                   bool isRtl,\n                   LayoutContext* ctx,\n                   const std::shared_ptr<FontCollection>& collection);\n\n  // Append another layout (for example, cached value) into this one\n  void appendLayout(Layout* src, size_t start, float extraAdvance);\n\n  std::vector<LayoutGlyph> mGlyphs;\n  std::vector<float> mAdvances;\n\n  std::vector<FakedFont> mFaces;\n  float mAdvance;\n  MinikinRect mBounds;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_LAYOUT_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/LayoutUtils.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include \"LayoutUtils.h\"\n\nnamespace minikin {\n\nconst uint16_t CHAR_NBSP = 0x00A0;\n\n/*\n * Determine whether the code unit is a word space for the purposes of\n * justification.\n */\nbool isWordSpace(uint16_t code_unit) {\n  return code_unit == ' ' || code_unit == CHAR_NBSP;\n}\n\n/**\n * For the purpose of layout, a word break is a boundary with no\n * kerning or complex script processing. This is necessarily a\n * heuristic, but should be accurate most of the time.\n */\nstatic bool isWordBreakAfter(uint16_t c) {\n  if (isWordSpace(c) || (c >= 0x2000 && c <= 0x200a) || c == 0x3000) {\n    // spaces\n    return true;\n  }\n  // Note: kana is not included, as sophisticated fonts may kern kana\n  return false;\n}\n\nstatic bool isWordBreakBefore(uint16_t c) {\n  // CJK ideographs (and yijing hexagram symbols)\n  return isWordBreakAfter(c) || (c >= 0x3400 && c <= 0x9fff);\n}\n\n/**\n * Return offset of previous word break. It is either < offset or == 0.\n */\nsize_t getPrevWordBreakForCache(const uint16_t* chars,\n                                size_t offset,\n                                size_t len) {\n  if (offset == 0)\n    return 0;\n  if (offset > len)\n    offset = len;\n  if (isWordBreakBefore(chars[offset - 1])) {\n    return offset - 1;\n  }\n  for (size_t i = offset - 1; i > 0; i--) {\n    if (isWordBreakBefore(chars[i]) || isWordBreakAfter(chars[i - 1])) {\n      return i;\n    }\n  }\n  return 0;\n}\n\n/**\n * Return offset of next word break. It is either > offset or == len.\n */\nsize_t getNextWordBreakForCache(const uint16_t* chars,\n                                size_t offset,\n                                size_t len) {\n  if (offset >= len)\n    return len;\n  if (isWordBreakAfter(chars[offset])) {\n    return offset + 1;\n  }\n  for (size_t i = offset + 1; i < len; i++) {\n    // No need to check isWordBreakAfter(chars[i - 1]) since it is checked\n    // in previous iteration.  Note that isWordBreakBefore returns true\n    // whenever isWordBreakAfter returns true.\n    if (isWordBreakBefore(chars[i])) {\n      return i;\n    }\n  }\n  return len;\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/LayoutUtils.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_LAYOUTUTILS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_LAYOUTUTILS_H_\n\n#include <stddef.h>\n#include <stdint.h>\n\nnamespace minikin {\n\n/*\n * Determine whether the code unit is a word space for the purposes of\n * justification.\n */\nbool isWordSpace(uint16_t code_unit);\n\n/**\n * Return offset of previous word break. It is either < offset or == 0.\n *\n * For the purpose of layout, a word break is a boundary with no\n * kerning or complex script processing. This is necessarily a\n * heuristic, but should be accurate most of the time.\n */\nsize_t getPrevWordBreakForCache(const uint16_t* chars,\n                                size_t offset,\n                                size_t len);\n\n/**\n * Return offset of next word break. It is either > offset or == len.\n *\n * For the purpose of layout, a word break is a boundary with no\n * kerning or complex script processing. This is necessarily a\n * heuristic, but should be accurate most of the time.\n */\nsize_t getNextWordBreakForCache(const uint16_t* chars,\n                                size_t offset,\n                                size_t len);\n\n}  // namespace minikin\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_LAYOUTUTILS_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/LineBreaker.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define VERBOSE_DEBUG 0\n\n#define LOG_TAG \"Minikin\"\n\n#include <algorithm>\n#include <limits>\n\n#include <log/log.h>\n\n#include <minikin/Layout.h>\n#include <minikin/LineBreaker.h>\n#include <utils/WindowsUtils.h>\n#include \"LayoutUtils.h\"\n\nusing std::vector;\n\nnamespace minikin {\n\n// Large scores in a hierarchy; we prefer desperate breaks to an overfull line.\n// All these constants are larger than any reasonable actual width score.\nconst float SCORE_INFTY = std::numeric_limits<float>::max();\nconst float SCORE_OVERFULL = 1e12f;\nconst float SCORE_DESPERATE = 1e10f;\n\n// Multiplier for hyphen penalty on last line.\nconst float LAST_LINE_PENALTY_MULTIPLIER = 4.0f;\n// Penalty assigned to each line break (to try to minimize number of lines)\n// TODO: when we implement full justification (so spaces can shrink and\n// stretch), this is probably not the most appropriate method.\nconst float LINE_PENALTY_MULTIPLIER = 2.0f;\n\n// Penalty assigned to shrinking the whitepsace.\nconst float SHRINK_PENALTY_MULTIPLIER = 4.0f;\n\n// Very long words trigger O(n^2) behavior in hyphenation, so we disable\n// hyphenation for unreasonably long words. This is somewhat of a heuristic\n// because extremely long words are possible in some languages. This does mean\n// that very long real words can get broken by desperate breaks, with no\n// hyphens.\nconst size_t LONGEST_HYPHENATED_WORD = 45;\n\n// When the text buffer is within this limit, capacity of vectors is retained at\n// finish(), to avoid allocation.\nconst size_t MAX_TEXT_BUF_RETAIN = 32678;\n\n// Maximum amount that spaces can shrink, in justified text.\nconst float SHRINKABILITY = 1.0 / 3.0;\n\n// libtxt: Add a fudge factor to comparisons between currentLineWidth and\n// postBreak width.  The currentLineWidth passed by the Flutter framework\n// is based on maxIntrinsicWidth/Layout::measureText calculations that may\n// not precisely match the postBreak width.\nconst float LIBTXT_WIDTH_ADJUST = 0.00001;\n\nvoid LineBreaker::setLocale() {\n  mWordBreaker.setLocale();\n  mLocale = icu::Locale();\n  mHyphenator = nullptr;\n}\n\nvoid LineBreaker::setText() {\n  mWordBreaker.setText(mTextBuf.data(), mTextBuf.size());\n\n  // handle initial break here because addStyleRun may never be called\n  mWordBreaker.next();\n  mCandidates.clear();\n  Candidate cand = {0,   0, 0.0, 0.0, 0.0,\n                    0.0, 0, 0,   0,   HyphenationType::DONT_BREAK};\n  mCandidates.push_back(cand);\n\n  // reset greedy breaker state\n  mBreaks.clear();\n  mWidths.clear();\n  mFlags.clear();\n  mLastBreak = 0;\n  mBestBreak = 0;\n  mBestScore = SCORE_INFTY;\n  mPreBreak = 0;\n  mLastHyphenation = HyphenEdit::NO_EDIT;\n  mFirstTabIndex = INT_MAX;\n  mSpaceCount = 0;\n}\n\nvoid LineBreaker::setLineWidths(float firstWidth,\n                                int firstWidthLineCount,\n                                float restWidth) {\n  mLineWidths.setWidths(firstWidth, firstWidthLineCount, restWidth);\n}\n\nvoid LineBreaker::setIndents(const std::vector<float>& indents) {\n  mLineWidths.setIndents(indents);\n}\n\n// This function determines whether a character is a space that disappears at\n// end of line. It is the Unicode set:\n// [[:General_Category=Space_Separator:]-[:Line_Break=Glue:]], plus '\\n'. Note:\n// all such characters are in the BMP, so it's ok to use code units for this.\nbool isLineEndSpace(uint16_t c) {\n  return c == '\\n' || c == ' ' || c == 0x1680 ||\n         (0x2000 <= c && c <= 0x200A && c != 0x2007) || c == 0x205F ||\n         c == 0x3000;\n}\n\n// Ordinarily, this method measures the text in the range given. However, when\n// paint is nullptr, it assumes the widths have already been calculated and\n// stored in the width buffer. This method finds the candidate word breaks\n// (using the ICU break iterator) and sends them to addCandidate.\nfloat LineBreaker::addStyleRun(MinikinPaint* paint,\n                               const std::shared_ptr<FontCollection>& typeface,\n                               FontStyle style,\n                               size_t start,\n                               size_t end,\n                               bool isRtl) {\n  float width = 0.0f;\n\n  float hyphenPenalty = 0.0;\n  if (paint != nullptr) {\n    width = Layout::measureText(mTextBuf.data(), start, end - start,\n                                mTextBuf.size(), isRtl, style, *paint, typeface,\n                                mCharWidths.data() + start);\n\n    // a heuristic that seems to perform well\n    hyphenPenalty =\n        0.5 * paint->size * paint->scaleX * mLineWidths.getLineWidth(0);\n    if (mHyphenationFrequency == kHyphenationFrequency_Normal) {\n      hyphenPenalty *=\n          4.0;  // TODO: Replace with a better value after some testing\n    }\n\n    if (mJustified) {\n      // Make hyphenation more aggressive for fully justified text (so that\n      // \"normal\" in justified mode is the same as \"full\" in ragged-right).\n      hyphenPenalty *= 0.25;\n    } else {\n      // Line penalty is zero for justified text.\n      mLinePenalty =\n          std::max(mLinePenalty, hyphenPenalty * LINE_PENALTY_MULTIPLIER);\n    }\n  }\n\n  size_t current = (size_t)mWordBreaker.current();\n  size_t afterWord = start;\n  size_t lastBreak = start;\n  ParaWidth lastBreakWidth = mWidth;\n  ParaWidth postBreak = mWidth;\n  size_t postSpaceCount = mSpaceCount;\n  for (size_t i = start; i < end; i++) {\n    uint16_t c = mTextBuf[i];\n    // libtxt: Tab handling was removed here.\n    if (isWordSpace(c))\n      mSpaceCount += 1;\n    mWidth += mCharWidths[i];\n    if (!isLineEndSpace(c)) {\n      postBreak = mWidth;\n      postSpaceCount = mSpaceCount;\n      afterWord = i + 1;\n    }\n    if (i + 1 == current) {\n      size_t wordStart = mWordBreaker.wordStart();\n      size_t wordEnd = mWordBreaker.wordEnd();\n      if (paint != nullptr && mHyphenator != nullptr &&\n          mHyphenationFrequency != kHyphenationFrequency_None &&\n          wordStart >= start && wordEnd > wordStart &&\n          wordEnd - wordStart <= LONGEST_HYPHENATED_WORD) {\n        mHyphenator->hyphenate(&mHyphBuf, &mTextBuf[wordStart],\n                               wordEnd - wordStart, mLocale);\n#if VERBOSE_DEBUG\n        std::string hyphenatedString;\n        for (size_t j = wordStart; j < wordEnd; j++) {\n          if (mHyphBuf[j - wordStart] ==\n              HyphenationType::BREAK_AND_INSERT_HYPHEN) {\n            hyphenatedString.push_back('-');\n          }\n          // Note: only works with ASCII, should do UTF-8 conversion here\n          hyphenatedString.push_back(buffer()[j]);\n        }\n        ALOGD(\"hyphenated string: %s\", hyphenatedString.c_str());\n#endif\n\n        // measure hyphenated substrings\n        for (size_t j = wordStart; j < wordEnd; j++) {\n          HyphenationType hyph = mHyphBuf[j - wordStart];\n          if (hyph != HyphenationType::DONT_BREAK) {\n            paint->hyphenEdit = HyphenEdit::editForThisLine(hyph);\n            const float firstPartWidth = Layout::measureText(\n                mTextBuf.data(), lastBreak, j - lastBreak, mTextBuf.size(),\n                isRtl, style, *paint, typeface, nullptr);\n            ParaWidth hyphPostBreak = lastBreakWidth + firstPartWidth;\n\n            paint->hyphenEdit = HyphenEdit::editForNextLine(hyph);\n            const float secondPartWidth = Layout::measureText(\n                mTextBuf.data(), j, afterWord - j, mTextBuf.size(), isRtl,\n                style, *paint, typeface, nullptr);\n            ParaWidth hyphPreBreak = postBreak - secondPartWidth;\n\n            addWordBreak(j, hyphPreBreak, hyphPostBreak, postSpaceCount,\n                         postSpaceCount, hyphenPenalty, hyph);\n\n            paint->hyphenEdit = HyphenEdit::NO_EDIT;\n          }\n        }\n      }\n\n      // Skip break for zero-width characters inside replacement span\n      if (paint != nullptr || current == end || mCharWidths[current] > 0) {\n        float penalty = hyphenPenalty * mWordBreaker.breakBadness();\n        addWordBreak(current, mWidth, postBreak, mSpaceCount, postSpaceCount,\n                     penalty, HyphenationType::DONT_BREAK);\n      }\n      lastBreak = current;\n      lastBreakWidth = mWidth;\n      current = (size_t)mWordBreaker.next();\n    }\n  }\n\n  return width;\n}\n\n// add a word break (possibly for a hyphenated fragment), and add desperate\n// breaks if needed (ie when word exceeds current line width)\nvoid LineBreaker::addWordBreak(size_t offset,\n                               ParaWidth preBreak,\n                               ParaWidth postBreak,\n                               size_t preSpaceCount,\n                               size_t postSpaceCount,\n                               float penalty,\n                               HyphenationType hyph) {\n  Candidate cand;\n  ParaWidth width = mCandidates.back().preBreak;\n  // libtxt: add a fudge factor to this comparison.  The currentLineWidth passed\n  // by the framework is based on maxIntrinsicWidth/Layout::measureText\n  // calculations that may not precisely match the postBreak width.\n  if (postBreak - width > currentLineWidth() + LIBTXT_WIDTH_ADJUST) {\n    // Add desperate breaks.\n    // Note: these breaks are based on the shaping of the (non-broken) original\n    // text; they are imprecise especially in the presence of kerning,\n    // ligatures, and Arabic shaping.\n    size_t i = mCandidates.back().offset;\n    width += mCharWidths[i++];\n    for (; i < offset; i++) {\n      float w = mCharWidths[i];\n      if (w > 0) {\n        cand.offset = i;\n        cand.preBreak = width;\n        cand.postBreak = width;\n        // postSpaceCount doesn't include trailing spaces\n        cand.preSpaceCount = postSpaceCount;\n        cand.postSpaceCount = postSpaceCount;\n        cand.penalty = SCORE_DESPERATE;\n        cand.hyphenType = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN;\n#if VERBOSE_DEBUG\n        ALOGD(\"desperate cand: %zd %g:%g\", mCandidates.size(), cand.postBreak,\n              cand.preBreak);\n#endif\n        addCandidate(cand);\n        width += w;\n      }\n    }\n  }\n\n  cand.offset = offset;\n  cand.preBreak = preBreak;\n  cand.postBreak = postBreak;\n  cand.penalty = penalty;\n  cand.preSpaceCount = preSpaceCount;\n  cand.postSpaceCount = postSpaceCount;\n  cand.hyphenType = hyph;\n#if VERBOSE_DEBUG\n  ALOGD(\"cand: %zd %g:%g\", mCandidates.size(), cand.postBreak, cand.preBreak);\n#endif\n  addCandidate(cand);\n}\n\n// Helper method for addCandidate()\nvoid LineBreaker::pushGreedyBreak() {\n  const Candidate& bestCandidate = mCandidates[mBestBreak];\n  pushBreak(\n      bestCandidate.offset, bestCandidate.postBreak - mPreBreak,\n      mLastHyphenation | HyphenEdit::editForThisLine(bestCandidate.hyphenType));\n  mBestScore = SCORE_INFTY;\n#if VERBOSE_DEBUG\n  ALOGD(\"break: %d %g\", mBreaks.back(), mWidths.back());\n#endif\n  mLastBreak = mBestBreak;\n  mPreBreak = bestCandidate.preBreak;\n  mLastHyphenation = HyphenEdit::editForNextLine(bestCandidate.hyphenType);\n}\n\n// TODO performance: could avoid populating mCandidates if greedy only\nvoid LineBreaker::addCandidate(Candidate cand) {\n  const size_t candIndex = mCandidates.size();\n  mCandidates.push_back(cand);\n\n  // mLastBreak is the index of the last line break we decided to do in\n  // mCandidates, and mPreBreak is its preBreak value. mBestBreak is the index\n  // of the best line breaking candidate we have found since then, and\n  // mBestScore is its penalty.\n  if (cand.postBreak - mPreBreak > currentLineWidth() + LIBTXT_WIDTH_ADJUST) {\n    // This break would create an overfull line, pick the best break and break\n    // there (greedy)\n    if (mBestBreak == mLastBreak) {\n      // No good break has been found since last break. Break here.\n      mBestBreak = candIndex;\n    }\n    pushGreedyBreak();\n  }\n\n  while (mLastBreak != candIndex &&\n         cand.postBreak - mPreBreak >\n             currentLineWidth() + LIBTXT_WIDTH_ADJUST) {\n    // We should rarely come here. But if we are here, we have broken the line,\n    // but the remaining part still doesn't fit. We now need to break at the\n    // second best place after the last break, but we have not kept that\n    // information, so we need to go back and find it.\n    //\n    // In some really rare cases, postBreak - preBreak of a candidate itself may\n    // be over the current line width. We protect ourselves against an infinite\n    // loop in that case by checking that we have not broken the line at this\n    // candidate already.\n    for (size_t i = mLastBreak + 1; i < candIndex; i++) {\n      const float penalty = mCandidates[i].penalty;\n      if (penalty <= mBestScore) {\n        mBestBreak = i;\n        mBestScore = penalty;\n      }\n    }\n    if (mBestBreak == mLastBreak) {\n      // We didn't find anything good. Break here.\n      mBestBreak = candIndex;\n    }\n    pushGreedyBreak();\n  }\n\n  if (cand.penalty <= mBestScore) {\n    mBestBreak = candIndex;\n    mBestScore = cand.penalty;\n  }\n}\n\nvoid LineBreaker::pushBreak(int offset, float width, uint8_t hyphenEdit) {\n  mBreaks.push_back(offset);\n  mWidths.push_back(width);\n  int flags = (mFirstTabIndex < mBreaks.back()) << kTab_Shift;\n  flags |= hyphenEdit;\n  mFlags.push_back(flags);\n  mFirstTabIndex = INT_MAX;\n}\n\n// libtxt: Add ability to set custom char widths. This allows manual definition\n// of the widths of arbitrary glyphs. To linebreak properly, call addStyleRun\n// with nullptr as the paint property, which will lead it to assume the width\n// has already been calculated. Used for properly breaking inline widgets.\nvoid LineBreaker::setCustomCharWidth(size_t offset, float width) {\n  mCharWidths[offset] = (width);\n}\n\nvoid LineBreaker::addReplacement(size_t start, size_t end, float width) {\n  mCharWidths[start] = width;\n  std::fill(&mCharWidths[start + 1], &mCharWidths[end], 0.0f);\n  addStyleRun(nullptr, nullptr, FontStyle(), start, end, false);\n}\n\n// Get the width of a space. May return 0 if there are no spaces.\n// Note: if there are multiple different widths for spaces (for example, because\n// of mixing of fonts), it's only guaranteed to pick one.\nfloat LineBreaker::getSpaceWidth() const {\n  for (size_t i = 0; i < mTextBuf.size(); i++) {\n    if (isWordSpace(mTextBuf[i])) {\n      return mCharWidths[i];\n    }\n  }\n  return 0.0f;\n}\n\nfloat LineBreaker::currentLineWidth() const {\n  return mLineWidths.getLineWidth(mBreaks.size());\n}\n\nvoid LineBreaker::computeBreaksGreedy() {\n  // All breaks but the last have been added in addCandidate already.\n  size_t nCand = mCandidates.size();\n  if (nCand > 0 && (nCand == 1 || mLastBreak != nCand - 1)) {\n    pushBreak(mCandidates[nCand - 1].offset,\n              mCandidates[nCand - 1].postBreak - mPreBreak, mLastHyphenation);\n    // don't need to update mBestScore, because we're done\n#if VERBOSE_DEBUG\n    ALOGD(\"final break: %d %g\", mBreaks.back(), mWidths.back());\n#endif\n  }\n}\n\n// Follow \"prev\" links in mCandidates array, and copy to result arrays.\nvoid LineBreaker::finishBreaksOptimal() {\n  // clear existing greedy break result\n  mBreaks.clear();\n  mWidths.clear();\n  mFlags.clear();\n  size_t nCand = mCandidates.size();\n  size_t prev;\n  for (size_t i = nCand - 1; i > 0; i = prev) {\n    prev = mCandidates[i].prev;\n    mBreaks.push_back(mCandidates[i].offset);\n    mWidths.push_back(mCandidates[i].postBreak - mCandidates[prev].preBreak);\n    int flags = HyphenEdit::editForThisLine(mCandidates[i].hyphenType);\n    if (prev > 0) {\n      flags |= HyphenEdit::editForNextLine(mCandidates[prev].hyphenType);\n    }\n    mFlags.push_back(flags);\n  }\n  std::reverse(mBreaks.begin(), mBreaks.end());\n  std::reverse(mWidths.begin(), mWidths.end());\n  std::reverse(mFlags.begin(), mFlags.end());\n}\n\nvoid LineBreaker::computeBreaksOptimal(bool isRectangle) {\n  size_t active = 0;\n  size_t nCand = mCandidates.size();\n  float width = mLineWidths.getLineWidth(0);\n  float shortLineFactor = mJustified ? 0.75f : 0.5f;\n  float maxShrink = mJustified ? SHRINKABILITY * getSpaceWidth() : 0.0f;\n\n  // \"i\" iterates through candidates for the end of the line.\n  for (size_t i = 1; i < nCand; i++) {\n    bool atEnd = i == nCand - 1;\n    float best = SCORE_INFTY;\n    size_t bestPrev = 0;\n    size_t lineNumberLast = 0;\n\n    if (!isRectangle) {\n      size_t lineNumberLast = mCandidates[active].lineNumber;\n      width = mLineWidths.getLineWidth(lineNumberLast);\n    }\n    ParaWidth leftEdge = mCandidates[i].postBreak - width;\n    float bestHope = 0;\n\n    // \"j\" iterates through candidates for the beginning of the line.\n    for (size_t j = active; j < i; j++) {\n      if (!isRectangle) {\n        size_t lineNumber = mCandidates[j].lineNumber;\n        if (lineNumber != lineNumberLast) {\n          float widthNew = mLineWidths.getLineWidth(lineNumber);\n          if (widthNew != width) {\n            leftEdge = mCandidates[i].postBreak - width;\n            bestHope = 0;\n            width = widthNew;\n          }\n          lineNumberLast = lineNumber;\n        }\n      }\n      float jScore = mCandidates[j].score;\n      if (jScore + bestHope >= best)\n        continue;\n      float delta = mCandidates[j].preBreak - leftEdge;\n\n      // compute width score for line\n\n      // Note: the \"bestHope\" optimization makes the assumption that, when delta\n      // is non-negative, widthScore will increase monotonically as successive\n      // candidate breaks are considered.\n      float widthScore = 0.0f;\n      float additionalPenalty = 0.0f;\n      if ((atEnd || !mJustified) && delta < 0) {\n        widthScore = SCORE_OVERFULL;\n      } else if (atEnd && mStrategy != kBreakStrategy_Balanced) {\n        // increase penalty for hyphen on last line\n        additionalPenalty =\n            LAST_LINE_PENALTY_MULTIPLIER * mCandidates[j].penalty;\n        // Penalize very short (< 1 - shortLineFactor of total width) lines.\n        float underfill = delta - shortLineFactor * width;\n        widthScore = underfill > 0 ? underfill * underfill : 0;\n      } else {\n        widthScore = delta * delta;\n        if (delta < 0) {\n          if (-delta < maxShrink * (mCandidates[i].postSpaceCount -\n                                    mCandidates[j].preSpaceCount)) {\n            widthScore *= SHRINK_PENALTY_MULTIPLIER;\n          } else {\n            widthScore = SCORE_OVERFULL;\n          }\n        }\n      }\n\n      if (delta < 0) {\n        active = j + 1;\n      } else {\n        bestHope = widthScore;\n      }\n\n      float score = jScore + widthScore + additionalPenalty;\n      if (score <= best) {\n        best = score;\n        bestPrev = j;\n      }\n    }\n    mCandidates[i].score = best + mCandidates[i].penalty + mLinePenalty;\n    mCandidates[i].prev = bestPrev;\n    mCandidates[i].lineNumber = mCandidates[bestPrev].lineNumber + 1;\n#if VERBOSE_DEBUG\n    ALOGD(\"break %zd: score=%g, prev=%zd\", i, mCandidates[i].score,\n          mCandidates[i].prev);\n#endif\n  }\n  finishBreaksOptimal();\n}\n\nsize_t LineBreaker::computeBreaks() {\n  if (mStrategy == kBreakStrategy_Greedy) {\n    computeBreaksGreedy();\n  } else {\n    computeBreaksOptimal(mLineWidths.isConstant());\n  }\n  return mBreaks.size();\n}\n\nvoid LineBreaker::finish() {\n  mWordBreaker.finish();\n  mWidth = 0;\n  mLineWidths.clear();\n  mCandidates.clear();\n  mBreaks.clear();\n  mWidths.clear();\n  mFlags.clear();\n  if (mTextBuf.size() > MAX_TEXT_BUF_RETAIN) {\n    mTextBuf.clear();\n    mTextBuf.shrink_to_fit();\n    mCharWidths.clear();\n    mCharWidths.shrink_to_fit();\n    mHyphBuf.clear();\n    mHyphBuf.shrink_to_fit();\n    mCandidates.shrink_to_fit();\n    mBreaks.shrink_to_fit();\n    mWidths.shrink_to_fit();\n    mFlags.shrink_to_fit();\n  }\n  mStrategy = kBreakStrategy_Greedy;\n  mHyphenationFrequency = kHyphenationFrequency_Normal;\n  mLinePenalty = 0.0f;\n  mJustified = false;\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/LineBreaker.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A module for breaking paragraphs into lines, supporting high quality\n * hyphenation and justification.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_LINEBREAKER_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_LINEBREAKER_H_\n\n#ifndef U_USING_ICU_NAMESPACE\n#define U_USING_ICU_NAMESPACE 0\n#endif  // U_USING_ICU_NAMESPACE\n\n#include <cmath>\n#include <memory>\n#include <vector>\n#include \"minikin/FontCollection.h\"\n#include \"minikin/Hyphenator.h\"\n#include \"minikin/MinikinFont.h\"\n#include \"minikin/WordBreaker.h\"\n#include \"unicode/brkiter.h\"\n#include \"unicode/locid.h\"\n\nnamespace minikin {\n\nenum BreakStrategy {\n  kBreakStrategy_Greedy = 0,\n  kBreakStrategy_HighQuality = 1,\n  kBreakStrategy_Balanced = 2\n};\n\nenum HyphenationFrequency {\n  kHyphenationFrequency_None = 0,\n  kHyphenationFrequency_Normal = 1,\n  kHyphenationFrequency_Full = 2\n};\n\nbool isLineEndSpace(uint16_t c);\n\n// TODO: want to generalize to be able to handle array of line widths\nclass LineWidths {\n public:\n  void setWidths(float firstWidth, int firstWidthLineCount, float restWidth) {\n    mFirstWidth = firstWidth;\n    mFirstWidthLineCount = firstWidthLineCount;\n    mRestWidth = restWidth;\n  }\n  void setIndents(const std::vector<float>& indents) { mIndents = indents; }\n  bool isConstant() const {\n    // technically mFirstWidthLineCount == 0 would count too, but doesn't\n    // actually happen\n    return mRestWidth == mFirstWidth && mIndents.empty();\n  }\n  float getLineWidth(int line) const {\n    float width = (line < mFirstWidthLineCount) ? mFirstWidth : mRestWidth;\n    if (!mIndents.empty()) {\n      if ((size_t)line < mIndents.size()) {\n        width -= mIndents[line];\n      } else {\n        width -= mIndents.back();\n      }\n    }\n    return width;\n  }\n  void clear() { mIndents.clear(); }\n\n private:\n  float mFirstWidth;\n  int mFirstWidthLineCount;\n  float mRestWidth;\n  std::vector<float> mIndents;\n};\n\nclass LineBreaker {\n public:\n  const static int kTab_Shift =\n      29;  // keep synchronized with TAB_MASK in StaticLayout.java\n\n  // Note: Locale persists across multiple invocations (it is not cleaned up by\n  // finish()), explicitly to avoid the cost of creating ICU BreakIterator\n  // objects. It should always be set on the first invocation, but callers are\n  // encouraged not to call again unless locale has actually changed. That logic\n  // could be here but it's better for performance that it's upstream because of\n  // the cost of constructing and comparing the ICU Locale object.\n  // Note: caller is responsible for managing lifetime of hyphenator\n  //\n  // libtxt extension: always use the default locale so that a cached instance\n  // of the ICU break iterator can be reused.\n  void setLocale();\n\n  void resize(size_t size) {\n    mTextBuf.resize(size);\n    mCharWidths.resize(size);\n  }\n\n  size_t size() const { return mTextBuf.size(); }\n\n  uint16_t* buffer() { return mTextBuf.data(); }\n\n  float* charWidths() { return mCharWidths.data(); }\n\n  // set text to current contents of buffer\n  void setText();\n\n  void setLineWidths(float firstWidth,\n                     int firstWidthLineCount,\n                     float restWidth);\n\n  void setIndents(const std::vector<float>& indents);\n\n  BreakStrategy getStrategy() const { return mStrategy; }\n\n  void setStrategy(BreakStrategy strategy) { mStrategy = strategy; }\n\n  void setJustified(bool justified) { mJustified = justified; }\n\n  HyphenationFrequency getHyphenationFrequency() const {\n    return mHyphenationFrequency;\n  }\n\n  void setHyphenationFrequency(HyphenationFrequency frequency) {\n    mHyphenationFrequency = frequency;\n  }\n\n  // TODO: this class is actually fairly close to being general and not tied to\n  // using Minikin to do the shaping of the strings. The main thing that would\n  // need to be changed is having some kind of callback (or virtual class, or\n  // maybe even template), which could easily be instantiated with Minikin's\n  // Layout. Future work for when needed.\n  float addStyleRun(MinikinPaint* paint,\n                    const std::shared_ptr<FontCollection>& typeface,\n                    FontStyle style,\n                    size_t start,\n                    size_t end,\n                    bool isRtl);\n\n  void addReplacement(size_t start, size_t end, float width);\n\n  size_t computeBreaks();\n\n  // libtxt: Add ability to set custom char widths. This allows manual\n  // definition of the widths of arbitrary glyphs. To linebreak properly, call\n  // addStyleRun with nullptr as the paint property, which will lead it to\n  // assume the width has already been calculated. Used for properly breaking\n  // inline placeholders.\n  void setCustomCharWidth(size_t offset, float width);\n\n  const int* getBreaks() const { return mBreaks.data(); }\n\n  const float* getWidths() const { return mWidths.data(); }\n\n  const int* getFlags() const { return mFlags.data(); }\n\n  void finish();\n\n private:\n  // ParaWidth is used to hold cumulative width from beginning of paragraph.\n  // Note that for very large paragraphs, accuracy could degrade using only\n  // 32-bit float. Note however that float is used extensively on the Java side\n  // for this. This is a typedef so that we can easily change it based on\n  // performance/accuracy tradeoff.\n  typedef double ParaWidth;\n\n  // A single candidate break\n  struct Candidate {\n    size_t offset;        // offset to text buffer, in code units\n    size_t prev;          // index to previous break\n    ParaWidth preBreak;   // width of text until this point, if we decide to not\n                          // break here\n    ParaWidth postBreak;  // width of text until this point, if we decide to\n                          // break here\n    float penalty;        // penalty of this break (for example, hyphen penalty)\n    float score;          // best score found for this break\n    size_t lineNumber;    // only updated for non-constant line widths\n    size_t preSpaceCount;   // preceding space count before breaking\n    size_t postSpaceCount;  // preceding space count after breaking\n    HyphenationType hyphenType;\n  };\n\n  float currentLineWidth() const;\n\n  void addWordBreak(size_t offset,\n                    ParaWidth preBreak,\n                    ParaWidth postBreak,\n                    size_t preSpaceCount,\n                    size_t postSpaceCount,\n                    float penalty,\n                    HyphenationType hyph);\n\n  void addCandidate(Candidate cand);\n  void pushGreedyBreak();\n\n  // push an actual break to the output. Takes care of setting flags for tab\n  void pushBreak(int offset, float width, uint8_t hyphenEdit);\n\n  float getSpaceWidth() const;\n\n  void computeBreaksGreedy();\n\n  void computeBreaksOptimal(bool isRectangular);\n\n  void finishBreaksOptimal();\n\n  WordBreaker mWordBreaker;\n  icu::Locale mLocale;\n  std::vector<uint16_t> mTextBuf;\n  std::vector<float> mCharWidths;\n\n  Hyphenator* mHyphenator;\n  std::vector<HyphenationType> mHyphBuf;\n\n  // layout parameters\n  BreakStrategy mStrategy = kBreakStrategy_Greedy;\n  HyphenationFrequency mHyphenationFrequency = kHyphenationFrequency_Normal;\n  bool mJustified;\n  LineWidths mLineWidths;\n\n  // result of line breaking\n  std::vector<int> mBreaks;\n  std::vector<float> mWidths;\n  std::vector<int> mFlags;\n\n  ParaWidth mWidth = 0;\n  std::vector<Candidate> mCandidates;\n  float mLinePenalty = 0.0f;\n\n  // the following are state for greedy breaker (updated while adding style\n  // runs)\n  size_t mLastBreak;\n  size_t mBestBreak;\n  float mBestScore;\n  ParaWidth mPreBreak;        // prebreak of last break\n  uint32_t mLastHyphenation;  // hyphen edit of last break kept for next line\n  int mFirstTabIndex;\n  size_t mSpaceCount;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_LINEBREAKER_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/Measurement.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include <unicode/uchar.h>\n#include <cmath>\n\n#include <log/log.h>\n\n#include <minikin/GraphemeBreak.h>\n#include <minikin/Measurement.h>\n\nnamespace minikin {\n\n// These could be considered helper methods of layout, but need only be loosely\n// coupled, so are separate.\n\nstatic float getRunAdvance(const float* advances,\n                           const uint16_t* buf,\n                           size_t layoutStart,\n                           size_t start,\n                           size_t count,\n                           size_t offset) {\n  float advance = 0.0f;\n  size_t lastCluster = start;\n  float clusterWidth = 0.0f;\n  for (size_t i = start; i < offset; i++) {\n    float charAdvance = advances[i - layoutStart];\n    if (charAdvance != 0.0f) {\n      advance += charAdvance;\n      lastCluster = i;\n      clusterWidth = charAdvance;\n    }\n  }\n  if (offset < start + count && advances[offset - layoutStart] == 0.0f) {\n    // In the middle of a cluster, distribute width of cluster so that each\n    // grapheme cluster gets an equal share.\n    // TODO: get caret information out of font when that's available\n    size_t nextCluster;\n    for (nextCluster = offset + 1; nextCluster < start + count; nextCluster++) {\n      if (advances[nextCluster - layoutStart] != 0.0f)\n        break;\n    }\n    int numGraphemeClusters = 0;\n    int numGraphemeClustersAfter = 0;\n    for (size_t i = lastCluster; i < nextCluster; i++) {\n      bool isAfter = i >= offset;\n      if (GraphemeBreak::isGraphemeBreak(advances + (start - layoutStart), buf,\n                                         start, count, i)) {\n        numGraphemeClusters++;\n        if (isAfter) {\n          numGraphemeClustersAfter++;\n        }\n      }\n    }\n    if (numGraphemeClusters > 0) {\n      advance -= clusterWidth * numGraphemeClustersAfter / numGraphemeClusters;\n    }\n  }\n  return advance;\n}\n\nfloat getRunAdvance(const float* advances,\n                    const uint16_t* buf,\n                    size_t start,\n                    size_t count,\n                    size_t offset) {\n  return getRunAdvance(advances, buf, start, start, count, offset);\n}\n\n/**\n * Essentially the inverse of getRunAdvance. Compute the value of offset for\n * which the measured caret comes closest to the provided advance param, and\n * which is on a grapheme cluster boundary.\n *\n * The actual implementation fast-forwards through clusters to get \"close\", then\n * does a finer-grain search within the cluster and grapheme breaks.\n */\nsize_t getOffsetForAdvance(const float* advances,\n                           const uint16_t* buf,\n                           size_t start,\n                           size_t count,\n                           float advance) {\n  float x = 0.0f, xLastClusterStart = 0.0f, xSearchStart = 0.0f;\n  size_t lastClusterStart = start, searchStart = start;\n  for (size_t i = start; i < start + count; i++) {\n    if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {\n      searchStart = lastClusterStart;\n      xSearchStart = xLastClusterStart;\n    }\n    float width = advances[i - start];\n    if (width != 0.0f) {\n      lastClusterStart = i;\n      xLastClusterStart = x;\n      x += width;\n      if (x > advance) {\n        break;\n      }\n    }\n  }\n  size_t best = searchStart;\n  float bestDist = FLT_MAX;\n  for (size_t i = searchStart; i <= start + count; i++) {\n    if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) {\n      // \"getRunAdvance(layout, buf, start, count, i) - advance\" but more\n      // efficient\n      float delta = getRunAdvance(advances, buf, start, searchStart,\n                                  count - searchStart, i)\n\n                    + xSearchStart - advance;\n      if (std::abs(delta) < bestDist) {\n        bestDist = std::abs(delta);\n        best = i;\n      }\n      if (delta >= 0.0f) {\n        break;\n      }\n    }\n  }\n  return best;\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/Measurement.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_MEASUREMENT_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_MEASUREMENT_H_\n\n#include <minikin/Layout.h>\n\nnamespace minikin {\n\nfloat getRunAdvance(const float* advances,\n                    const uint16_t* buf,\n                    size_t start,\n                    size_t count,\n                    size_t offset);\n\nsize_t getOffsetForAdvance(const float* advances,\n                           const uint16_t* buf,\n                           size_t start,\n                           size_t count,\n                           float advance);\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_MEASUREMENT_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/MinikinFont.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <minikin/MinikinFont.h>\n#include \"HbFontCache.h\"\n#include \"MinikinInternal.h\"\n\nnamespace minikin {\n\nMinikinFont::~MinikinFont() {\n  std::scoped_lock _l(gMinikinLock);\n  purgeHbFontLocked(this);\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/MinikinFont.h",
    "content": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_MINIKINFONT_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_MINIKINFONT_H_\n\n#include <memory>\n#include <vector>\n#include <string>\n\n#include <minikin/FontFamily.h>\n#include <minikin/Hyphenator.h>\n\n// An abstraction for platform fonts, allowing Minikin to be used with\n// multiple actual implementations of fonts.\n\nnamespace minikin {\n\nclass MinikinFont;\n\n// Possibly move into own .h file?\n// Note: if you add a field here, either add it to LayoutCacheKey or to\n// skipCache()\nstruct MinikinPaint {\n  MinikinPaint()\n      : font(nullptr),\n        size(0),\n        scaleX(0),\n        skewX(0),\n        letterSpacing(0),\n        wordSpacing(0),\n        paintFlags(0),\n        fakery(),\n        hyphenEdit(),\n        fontFeatureSettings() {}\n\n  bool skipCache() const { return !fontFeatureSettings.empty(); }\n\n  MinikinFont* font;\n  float size;\n  float scaleX;\n  float skewX;\n  float letterSpacing;\n  float wordSpacing;\n  uint32_t paintFlags;\n  FontFakery fakery;\n  HyphenEdit hyphenEdit;\n  std::string fontFeatureSettings;\n};\n\n// Only a few flags affect layout, but those that do should have values\n// consistent with Android's paint flags.\nenum MinikinPaintFlags {\n  LinearTextFlag = 0x40,\n};\n\nstruct MinikinRect {\n  float mLeft, mTop, mRight, mBottom;\n  bool isEmpty() const { return mLeft == mRight || mTop == mBottom; }\n  void set(const MinikinRect& r) {\n    mLeft = r.mLeft;\n    mTop = r.mTop;\n    mRight = r.mRight;\n    mBottom = r.mBottom;\n  }\n  void offset(float dx, float dy) {\n    mLeft += dx;\n    mTop += dy;\n    mRight += dx;\n    mBottom += dy;\n  }\n  void setEmpty() { mLeft = mTop = mRight = mBottom = 0; }\n  void join(const MinikinRect& r);\n};\n\n// Callback for freeing data\ntypedef void (*MinikinDestroyFunc)(void* data);\n\nclass MinikinFont {\n public:\n  explicit MinikinFont(int32_t uniqueId) : mUniqueId(uniqueId) {}\n\n  virtual ~MinikinFont();\n\n  virtual float GetHorizontalAdvance(uint32_t glyph_id,\n                                     const MinikinPaint& paint) const = 0;\n\n  virtual void GetBounds(MinikinRect* bounds,\n                         uint32_t glyph_id,\n                         const MinikinPaint& paint) const = 0;\n\n  virtual hb_face_t* CreateHarfBuzzFace() const { return nullptr; }\n\n  virtual const std::vector<minikin::FontVariation>& GetAxes() const = 0;\n\n  virtual std::shared_ptr<MinikinFont> createFontWithVariation(\n      const std::vector<FontVariation>&) const {\n    return nullptr;\n  }\n\n  static uint32_t MakeTag(char c1, char c2, char c3, char c4) {\n    return ((uint32_t)c1 << 24) | ((uint32_t)c2 << 16) | ((uint32_t)c3 << 8) |\n           (uint32_t)c4;\n  }\n\n  int32_t GetUniqueId() const { return mUniqueId; }\n\n private:\n  const int32_t mUniqueId;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_MINIKINFONT_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/MinikinInternal.cpp",
    "content": "/*\n * Copyright (C) 2014 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Definitions internal to Minikin\n#define LOG_TAG \"Minikin\"\n\n#include \"MinikinInternal.h\"\n#include \"HbFontCache.h\"\n\n#include <log/log.h>\n\nnamespace minikin {\n\n#ifdef __clang__\n[[clang::no_destroy]]\n#endif\nstd::recursive_mutex gMinikinLock;\n\nvoid assertMinikinLocked() {\n#ifdef ENABLE_RACE_DETECTION\n  LOG_ALWAYS_FATAL_IF(gMinikinLock.tryLock() == 0);\n#endif\n}\n\nhb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag) {\n  assertMinikinLocked();\n  hb_font_t* font = getHbFontLocked(minikinFont);\n  hb_face_t* face = hb_font_get_face(font);\n  hb_blob_t* blob = hb_face_reference_table(face, tag);\n  hb_font_destroy(font);\n  return blob;\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/MinikinInternal.h",
    "content": "/*\n * Copyright (C) 2014 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Definitions internal to Minikin\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_MINIKININTERNAL_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_MINIKININTERNAL_H_\n\n#include <mutex>\n\n#include <hb.h>\n\n#include <minikin/MinikinFont.h>\n\nnamespace minikin {\n\n// All external Minikin interfaces are designed to be thread-safe.\n// Presently, that's implemented by through a global lock, and having\n// all external interfaces take that lock.\n\nextern std::recursive_mutex gMinikinLock;\n\n// Aborts if gMinikinLock is not acquired. Do nothing on the release build.\nvoid assertMinikinLocked();\n\nhb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag);\n\nconstexpr uint32_t MAX_UNICODE_CODE_POINT = 0x10FFFF;\n\n// An RAII wrapper for hb_blob_t\nclass HbBlob {\n public:\n  // Takes ownership of hb_blob_t object, caller is no longer\n  // responsible for calling hb_blob_destroy().\n  explicit HbBlob(hb_blob_t* blob) : mBlob(blob) {}\n\n  ~HbBlob() { hb_blob_destroy(mBlob); }\n\n  const uint8_t* get() const {\n    const char* data = hb_blob_get_data(mBlob, nullptr);\n    return reinterpret_cast<const uint8_t*>(data);\n  }\n\n  size_t size() const { return (size_t)hb_blob_get_length(mBlob); }\n\n private:\n  hb_blob_t* mBlob;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_MINIKININTERNAL_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/SparseBitSet.cpp",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"SparseBitSet\"\n\n#include <stddef.h>\n#include <string.h>\n\n#include <log/log.h>\n\n#include <minikin/SparseBitSet.h>\n#include <utils/WindowsUtils.h>\n\nnamespace minikin {\n\nconst uint32_t SparseBitSet::kNotFound;\n\nuint32_t SparseBitSet::calcNumPages(const uint32_t* ranges, size_t nRanges) {\n  bool haveZeroPage = false;\n  uint32_t nonzeroPageEnd = 0;\n  uint32_t nPages = 0;\n  for (size_t i = 0; i < nRanges; i++) {\n    uint32_t start = ranges[i * 2];\n    uint32_t end = ranges[i * 2 + 1];\n    uint32_t startPage = start >> kLogValuesPerPage;\n    uint32_t endPage = (end - 1) >> kLogValuesPerPage;\n    if (startPage >= nonzeroPageEnd) {\n      if (startPage > nonzeroPageEnd) {\n        if (!haveZeroPage) {\n          haveZeroPage = true;\n          nPages++;\n        }\n      }\n      nPages++;\n    }\n    nPages += endPage - startPage;\n    nonzeroPageEnd = endPage + 1;\n  }\n  return nPages;\n}\n\nvoid SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) {\n  if (nRanges == 0) {\n    return;\n  }\n  const uint32_t maxVal = ranges[nRanges * 2 - 1];\n  if (maxVal >= kMaximumCapacity) {\n    return;\n  }\n  mMaxVal = maxVal;\n  mIndices.reset(new uint16_t[(mMaxVal + kPageMask) >> kLogValuesPerPage]);\n  uint32_t nPages = calcNumPages(ranges, nRanges);\n  mBitmaps.reset(new element[nPages << (kLogValuesPerPage - kLogBitsPerEl)]());\n  mZeroPageIndex = noZeroPage;\n  uint32_t nonzeroPageEnd = 0;\n  uint32_t currentPage = 0;\n  for (size_t i = 0; i < nRanges; i++) {\n    uint32_t start = ranges[i * 2];\n    uint32_t end = ranges[i * 2 + 1];\n    LOG_ALWAYS_FATAL_IF(end < start);  // make sure range size is nonnegative\n    uint32_t startPage = start >> kLogValuesPerPage;\n    uint32_t endPage = (end - 1) >> kLogValuesPerPage;\n    if (startPage >= nonzeroPageEnd) {\n      if (startPage > nonzeroPageEnd) {\n        if (mZeroPageIndex == noZeroPage) {\n          mZeroPageIndex = (currentPage++)\n                           << (kLogValuesPerPage - kLogBitsPerEl);\n        }\n        for (uint32_t j = nonzeroPageEnd; j < startPage; j++) {\n          mIndices[j] = mZeroPageIndex;\n        }\n      }\n      mIndices[startPage] = (currentPage++)\n                            << (kLogValuesPerPage - kLogBitsPerEl);\n    }\n\n    size_t index = ((currentPage - 1) << (kLogValuesPerPage - kLogBitsPerEl)) +\n                   ((start & kPageMask) >> kLogBitsPerEl);\n    size_t nElements = (end - (start & ~kElMask) + kElMask) >> kLogBitsPerEl;\n    if (nElements == 1) {\n      mBitmaps[index] |= (kElAllOnes >> (start & kElMask)) &\n                         (kElAllOnes << ((~end + 1) & kElMask));\n    } else {\n      mBitmaps[index] |= kElAllOnes >> (start & kElMask);\n      for (size_t j = 1; j < nElements - 1; j++) {\n        mBitmaps[index + j] = kElAllOnes;\n      }\n      mBitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask);\n    }\n    for (size_t j = startPage + 1; j < endPage + 1; j++) {\n      mIndices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);\n    }\n    nonzeroPageEnd = endPage + 1;\n  }\n}\n\n#if defined(_WIN32)\nint SparseBitSet::CountLeadingZeros(element x) {\n  return sizeof(element) <= sizeof(int) ? clz_win(x) : clzl_win(x);\n}\n#else\nint SparseBitSet::CountLeadingZeros(element x) {\n  // Note: GCC / clang builtin\n  return sizeof(element) <= sizeof(int) ? __builtin_clz(x) : __builtin_clzl(x);\n}\n#endif\n\nuint32_t SparseBitSet::nextSetBit(uint32_t fromIndex) const {\n  if (fromIndex >= mMaxVal) {\n    return kNotFound;\n  }\n  uint32_t fromPage = fromIndex >> kLogValuesPerPage;\n  const element* bitmap = &mBitmaps[mIndices[fromPage]];\n  uint32_t offset = (fromIndex & kPageMask) >> kLogBitsPerEl;\n  element e = bitmap[offset] & (kElAllOnes >> (fromIndex & kElMask));\n  if (e != 0) {\n    return (fromIndex & ~kElMask) + CountLeadingZeros(e);\n  }\n  for (uint32_t j = offset + 1; j < (1 << (kLogValuesPerPage - kLogBitsPerEl));\n       j++) {\n    e = bitmap[j];\n    if (e != 0) {\n      return (fromIndex & ~kPageMask) + (j << kLogBitsPerEl) +\n             CountLeadingZeros(e);\n    }\n  }\n  uint32_t maxPage = (mMaxVal + kPageMask) >> kLogValuesPerPage;\n  for (uint32_t page = fromPage + 1; page < maxPage; page++) {\n    uint16_t index = mIndices[page];\n    if (index == mZeroPageIndex) {\n      continue;\n    }\n    bitmap = &mBitmaps[index];\n    for (uint32_t j = 0; j < (1 << (kLogValuesPerPage - kLogBitsPerEl)); j++) {\n      e = bitmap[j];\n      if (e != 0) {\n        return (page << kLogValuesPerPage) + (j << kLogBitsPerEl) +\n               CountLeadingZeros(e);\n      }\n    }\n  }\n  return kNotFound;\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/SparseBitSet.h",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_SPARSEBITSET_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_SPARSEBITSET_H_\n\n#include <stdint.h>\n#include <sys/types.h>\n\n#include <memory>\n\n// ---------------------------------------------------------------------------\n\nnamespace minikin {\n\n// This is an implementation of a set of integers. It is optimized for\n// values that are somewhat sparse, in the ballpark of a maximum value\n// of thousands to millions. It is particularly efficient when there are\n// large gaps. The motivating example is Unicode coverage of a font, but\n// the abstraction itself is fully general.\nclass SparseBitSet {\n public:\n  // Create an empty bit set.\n  SparseBitSet() : mMaxVal(0) {}\n\n  // Initialize the set to a new value, represented by ranges. For\n  // simplicity, these ranges are arranged as pairs of values,\n  // inclusive of start, exclusive of end, laid out in a uint32 array.\n  SparseBitSet(const uint32_t* ranges, size_t nRanges) : SparseBitSet() {\n    initFromRanges(ranges, nRanges);\n  }\n\n  SparseBitSet(SparseBitSet&&) = default;\n  SparseBitSet& operator=(SparseBitSet&&) = default;\n\n  // Determine whether the value is included in the set\n  bool get(uint32_t ch) const {\n    if (ch >= mMaxVal)\n      return false;\n    const uint32_t* bitmap = &mBitmaps[mIndices[ch >> kLogValuesPerPage]];\n    uint32_t index = ch & kPageMask;\n    return (bitmap[index >> kLogBitsPerEl] & (kElFirst >> (index & kElMask))) !=\n           0;\n  }\n\n  // One more than the maximum value in the set, or zero if empty\n  uint32_t length() const { return mMaxVal; }\n\n  // The next set bit starting at fromIndex, inclusive, or kNotFound\n  // if none exists.\n  uint32_t nextSetBit(uint32_t fromIndex) const;\n\n  static const uint32_t kNotFound = ~0u;\n\n private:\n  void initFromRanges(const uint32_t* ranges, size_t nRanges);\n\n  static const uint32_t kMaximumCapacity = 0xFFFFFF;\n  static const int kLogValuesPerPage = 8;\n  static const int kPageMask = (1 << kLogValuesPerPage) - 1;\n  static const int kLogBytesPerEl = 2;\n  static const int kLogBitsPerEl = kLogBytesPerEl + 3;\n  static const int kElMask = (1 << kLogBitsPerEl) - 1;\n  // invariant: sizeof(element) == (1 << kLogBytesPerEl)\n  typedef uint32_t element;\n  static const element kElAllOnes = ~((element)0);\n  static const element kElFirst = ((element)1) << kElMask;\n  static const uint16_t noZeroPage = 0xFFFF;\n\n  static uint32_t calcNumPages(const uint32_t* ranges, size_t nRanges);\n  static int CountLeadingZeros(element x);\n\n  uint32_t mMaxVal;\n\n  std::unique_ptr<uint16_t[]> mIndices;\n  std::unique_ptr<element[]> mBitmaps;\n  uint16_t mZeroPageIndex;\n\n  // Forbid copy and assign.\n  SparseBitSet(const SparseBitSet&) = delete;\n  void operator=(const SparseBitSet&) = delete;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_SPARSEBITSET_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/WordBreaker.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include <log/log.h>\n\n#include <minikin/Emoji.h>\n#include <minikin/Hyphenator.h>\n#include <minikin/WordBreaker.h>\n#include \"MinikinInternal.h\"\n\n#include <unicode/uchar.h>\n#include <unicode/utf16.h>\n\nnamespace minikin {\n\nconst uint32_t CHAR_SOFT_HYPHEN = 0x00AD;\nconst uint32_t CHAR_ZWJ = 0x200D;\n\n// libtxt extension: avoid the cost of initializing new ICU break iterators\n// by constructing a global iterator using the default locale and then\n// creating a clone for each WordBreaker instance.\nstatic std::once_flag gLibtxtBreakIteratorInitFlag;\nstatic icu::BreakIterator* gLibtxtDefaultBreakIterator = nullptr;\n\nvoid WordBreaker::setLocale() {\n  UErrorCode status = U_ZERO_ERROR;\n  std::call_once(gLibtxtBreakIteratorInitFlag, [&status] {\n    gLibtxtDefaultBreakIterator =\n        icu::BreakIterator::createLineInstance(icu::Locale(), status);\n  });\n  mBreakIterator.reset(gLibtxtDefaultBreakIterator->clone());\n  // TODO: handle failure status\n  if (mText != nullptr) {\n    mBreakIterator->setText(&mUText, status);\n  }\n  mIteratorWasReset = true;\n}\n\nvoid WordBreaker::setText(const uint16_t* data, size_t size) {\n  mText = data;\n  mTextSize = size;\n  mIteratorWasReset = false;\n  mLast = 0;\n  mCurrent = 0;\n  mScanOffset = 0;\n  mInEmailOrUrl = false;\n  UErrorCode status = U_ZERO_ERROR;\n  utext_openUChars(&mUText, reinterpret_cast<const UChar*>(data), size,\n                   &status);\n  mBreakIterator->setText(&mUText, status);\n  mBreakIterator->first();\n}\n\nssize_t WordBreaker::current() const {\n  return mCurrent;\n}\n\n/**\n * Determine whether a line break at position i within the buffer buf is valid.\n *This represents customization beyond the ICU behavior, because plain ICU\n *provides some line break opportunities that we don't want.\n **/\nstatic bool isBreakValid(const uint16_t* buf, size_t bufEnd, size_t i) {\n  uint32_t codePoint;\n  size_t prev_offset = i;\n  U16_PREV(buf, 0, prev_offset, codePoint);\n  // Do not break on hard or soft hyphens. These are handled by automatic\n  // hyphenation.\n  if (Hyphenator::isLineBreakingHyphen(codePoint) ||\n      codePoint == CHAR_SOFT_HYPHEN) {\n    // txt addition: Temporarily always break on hyphen. Changed from false to\n    // true.\n    return true;\n  }\n  // For Myanmar kinzi sequences, created by <consonant, ASAT, VIRAMA,\n  // consonant>. This is to go around a bug in ICU line breaking:\n  // http://bugs.icu-project.org/trac/ticket/12561. To avoid too much looking\n  // around in the strings, we simply avoid breaking after any Myanmar virama,\n  // where no line break could be imagined, since the Myanmar virama is a pure\n  // stacker.\n  if (codePoint == 0x1039) {  // MYANMAR SIGN VIRAMA\n    return false;\n  }\n\n  uint32_t next_codepoint;\n  size_t next_offset = i;\n  U16_NEXT(buf, next_offset, bufEnd, next_codepoint);\n\n  // Rule LB8 for Emoji ZWJ sequences. We need to do this ourselves since we may\n  // have fresher emoji data than ICU does.\n  if (codePoint == CHAR_ZWJ && isEmoji(next_codepoint)) {\n    return false;\n  }\n\n  // Rule LB30b. We need to this ourselves since we may have fresher emoji data\n  // than ICU does.\n  if (isEmojiModifier(next_codepoint)) {\n    if (codePoint == 0xFE0F && prev_offset > 0) {\n      // skip over emoji variation selector\n      U16_PREV(buf, 0, prev_offset, codePoint);\n    }\n    if (isEmojiBase(codePoint)) {\n      return false;\n    }\n  }\n  return true;\n}\n\n// Customized iteratorNext that takes care of both resets and our modifications\n// to ICU's behavior.\nint32_t WordBreaker::iteratorNext() {\n  int32_t result;\n  do {\n    if (mIteratorWasReset) {\n      result = mBreakIterator->following(mCurrent);\n      mIteratorWasReset = false;\n    } else {\n      result = mBreakIterator->next();\n    }\n  } while (!(result == icu::BreakIterator::DONE ||\n             (size_t)result == mTextSize ||\n             isBreakValid(mText, mTextSize, result)));\n  return result;\n}\n\n// Chicago Manual of Style recommends breaking after these characters in URLs\n// and email addresses\nstatic bool breakAfter(uint16_t c) {\n  return c == ':' || c == '=' || c == '&';\n}\n\n// Chicago Manual of Style recommends breaking before these characters in URLs\n// and email addresses\nstatic bool breakBefore(uint16_t c) {\n  return c == '~' || c == '.' || c == ',' || c == '-' || c == '_' || c == '?' ||\n         c == '#' || c == '%' || c == '=' || c == '&';\n}\n\nenum ScanState {\n  START,\n  SAW_AT,\n  SAW_COLON,\n  SAW_COLON_SLASH,\n  SAW_COLON_SLASH_SLASH,\n};\n\nvoid WordBreaker::detectEmailOrUrl() {\n  // scan forward from current ICU position for email address or URL\n  if (mLast >= mScanOffset) {\n    ScanState state = START;\n    size_t i;\n    for (i = mLast; i < mTextSize; i++) {\n      uint16_t c = mText[i];\n      // scan only ASCII characters, stop at space\n      if (!(' ' < c && c <= 0x007E)) {\n        break;\n      }\n      if (state == START && c == '@') {\n        state = SAW_AT;\n      } else if (state == START && c == ':') {\n        state = SAW_COLON;\n      } else if (state == SAW_COLON || state == SAW_COLON_SLASH) {\n        if (c == '/') {\n          state = static_cast<ScanState>((int)state +\n                                         1);  // next state adds a slash\n        } else {\n          state = START;\n        }\n      }\n    }\n    if (state == SAW_AT || state == SAW_COLON_SLASH_SLASH) {\n      if (!mBreakIterator->isBoundary(i)) {\n        // If there are combining marks or such at the end of the URL or the\n        // email address, consider them a part of the URL or the email, and skip\n        // to the next actual boundary.\n        i = mBreakIterator->following(i);\n      }\n      mInEmailOrUrl = true;\n      mIteratorWasReset = true;\n    } else {\n      mInEmailOrUrl = false;\n    }\n    mScanOffset = i;\n  }\n}\n\nssize_t WordBreaker::findNextBreakInEmailOrUrl() {\n  // special rules for email addresses and URL's as per Chicago Manual of Style\n  // (16th ed.)\n  uint16_t lastChar = mText[mLast];\n  ssize_t i;\n  for (i = mLast + 1; i < mScanOffset; i++) {\n    if (breakAfter(lastChar)) {\n      break;\n    }\n    // break after double slash\n    if (lastChar == '/' && i >= mLast + 2 && mText[i - 2] == '/') {\n      break;\n    }\n    const uint16_t thisChar = mText[i];\n    // never break after hyphen\n    if (lastChar != '-') {\n      if (breakBefore(thisChar)) {\n        break;\n      }\n      // break before single slash\n      if (thisChar == '/' && lastChar != '/' &&\n          !(i + 1 < mScanOffset && mText[i + 1] == '/')) {\n        break;\n      }\n    }\n    lastChar = thisChar;\n  }\n  return i;\n}\n\nssize_t WordBreaker::next() {\n  mLast = mCurrent;\n\n  detectEmailOrUrl();\n  if (mInEmailOrUrl) {\n    mCurrent = findNextBreakInEmailOrUrl();\n  } else {  // Business as usual\n    mCurrent = (ssize_t)iteratorNext();\n  }\n  return mCurrent;\n}\n\nssize_t WordBreaker::wordStart() const {\n  if (mInEmailOrUrl) {\n    return mLast;\n  }\n  ssize_t result = mLast;\n  while (result < mCurrent) {\n    UChar32 c;\n    ssize_t ix = result;\n    U16_NEXT(mText, ix, mCurrent, c);\n    const int32_t lb = u_getIntPropertyValue(c, UCHAR_LINE_BREAK);\n    // strip leading punctuation, defined as OP and QU line breaking classes,\n    // see UAX #14\n    if (!(lb == U_LB_OPEN_PUNCTUATION || lb == U_LB_QUOTATION)) {\n      break;\n    }\n    result = ix;\n  }\n  return result;\n}\n\nssize_t WordBreaker::wordEnd() const {\n  if (mInEmailOrUrl) {\n    return mLast;\n  }\n  ssize_t result = mCurrent;\n  while (result > mLast) {\n    UChar32 c;\n    ssize_t ix = result;\n    U16_PREV(mText, mLast, ix, c);\n    const int32_t gc_mask = U_GET_GC_MASK(c);\n    // strip trailing space and punctuation\n    if ((gc_mask & (U_GC_ZS_MASK | U_GC_P_MASK)) == 0) {\n      break;\n    }\n    result = ix;\n  }\n  return result;\n}\n\nint WordBreaker::breakBadness() const {\n  return (mInEmailOrUrl && mCurrent < mScanOffset) ? 1 : 0;\n}\n\nvoid WordBreaker::finish() {\n  mText = nullptr;\n  // Note: calling utext_close multiply is safe\n  utext_close(&mUText);\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/src/minikin/WordBreaker.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A wrapper around ICU's line break iterator, that gives customized line\n * break opportunities, as well as identifying words for the purpose of\n * hyphenation.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_WORDBREAKER_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_WORDBREAKER_H_\n\n#include <memory>\n#include \"unicode/brkiter.h\"\n#include \"utils/WindowsUtils.h\"\n\nnamespace minikin {\n\nclass WordBreaker {\n public:\n  ~WordBreaker() { finish(); }\n\n  // libtxt extension: always use the default locale so that a cached instance\n  // of the ICU break iterator can be reused.\n  void setLocale();\n\n  void setText(const uint16_t* data, size_t size);\n\n  // Advance iterator to next word break. Return offset, or -1 if EOT\n  ssize_t next();\n\n  // Current offset of iterator, equal to 0 at BOT or last return from next()\n  ssize_t current() const;\n\n  // After calling next(), wordStart() and wordEnd() are offsets defining the\n  // previous word. If wordEnd <= wordStart, it's not a word for the purpose of\n  // hyphenation.\n  ssize_t wordStart() const;\n\n  ssize_t wordEnd() const;\n\n  int breakBadness() const;\n\n  void finish();\n\n private:\n  int32_t iteratorNext();\n  void detectEmailOrUrl();\n  ssize_t findNextBreakInEmailOrUrl();\n\n  std::unique_ptr<icu::BreakIterator> mBreakIterator;\n  UText mUText = UTEXT_INITIALIZER;\n  const uint16_t* mText = nullptr;\n  size_t mTextSize;\n  ssize_t mLast;\n  ssize_t mCurrent;\n  bool mIteratorWasReset;\n\n  // state for the email address / url detector\n  ssize_t mScanOffset;\n  bool mInEmailOrUrl;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_MINIKIN_WORDBREAKER_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/skia/paragraph_builder_skia.cc",
    "content": "/*\n * Copyright 2019 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"paragraph_builder_skia.h\"\n#include \"paragraph_skia.h\"\n\n#include \"third_party/skia/modules/skparagraph/include/ParagraphStyle.h\"\n#include \"third_party/skia/modules/skparagraph/include/TextStyle.h\"\n#include \"txt/paragraph_style.h\"\n\nnamespace skt = skia::textlayout;\n\nnamespace txt {\n\nnamespace {\n\n// Convert txt::FontWeight values (ranging from 0-8) to SkFontStyle::Weight\n// values (ranging from 100-900).\nSkFontStyle::Weight GetSkFontStyleWeight(txt::FontWeight font_weight) {\n  return static_cast<SkFontStyle::Weight>(static_cast<int>(font_weight) * 100 +\n                                          100);\n}\n\nSkFontStyle MakeSkFontStyle(txt::FontWeight font_weight,\n                            txt::FontStyle font_style) {\n  return SkFontStyle(\n      GetSkFontStyleWeight(font_weight), SkFontStyle::Width::kNormal_Width,\n      font_style == txt::FontStyle::normal ? SkFontStyle::Slant::kUpright_Slant\n                                           : SkFontStyle::Slant::kItalic_Slant);\n}\n\n}  // anonymous namespace\n\nParagraphBuilderSkia::ParagraphBuilderSkia(\n    const ParagraphStyle& style,\n    std::shared_ptr<FontCollection> font_collection)\n    : base_style_(style.GetTextStyle()) {\n  builder_ = skt::ParagraphBuilder::make(\n      TxtToSkia(style), font_collection->CreateSktFontCollection());\n}\n\nParagraphBuilderSkia::~ParagraphBuilderSkia() = default;\n\nvoid ParagraphBuilderSkia::PushStyle(const TextStyle& style) {\n  builder_->pushStyle(TxtToSkia(style));\n  txt_style_stack_.push(style);\n}\n\nvoid ParagraphBuilderSkia::Pop() {\n  builder_->pop();\n  txt_style_stack_.pop();\n}\n\nconst TextStyle& ParagraphBuilderSkia::PeekStyle() {\n  return txt_style_stack_.empty() ? base_style_ : txt_style_stack_.top();\n}\n\nvoid ParagraphBuilderSkia::AddText(const std::u16string& text) {\n  builder_->addText(text);\n}\n\nvoid ParagraphBuilderSkia::AddText(const char* text, size_t len) {\n  builder_->addText(text, len);\n}\n\nvoid ParagraphBuilderSkia::AddPlaceholder(PlaceholderRun& span) {\n  skt::PlaceholderStyle placeholder_style;\n  placeholder_style.fHeight = span.height;\n  placeholder_style.fWidth = span.width;\n  placeholder_style.fBaseline = static_cast<skt::TextBaseline>(span.baseline);\n  placeholder_style.fBaselineOffset = span.baseline_offset;\n  placeholder_style.fAlignment =\n      static_cast<skt::PlaceholderAlignment>(span.alignment);\n\n  builder_->addPlaceholder(placeholder_style);\n}\n\nstd::unique_ptr<Paragraph> ParagraphBuilderSkia::Build() {\n  return std::make_unique<ParagraphSkia>(builder_->Build());\n}\n\nskt::ParagraphStyle ParagraphBuilderSkia::TxtToSkia(const ParagraphStyle& txt) {\n  skt::ParagraphStyle skia;\n  skt::TextStyle text_style;\n\n  text_style.setFontStyle(MakeSkFontStyle(txt.font_weight, txt.font_style));\n  text_style.setFontSize(SkDoubleToScalar(txt.font_size));\n  text_style.setHeight(SkDoubleToScalar(txt.height));\n  text_style.setHeightOverride(txt.has_height_override);\n  text_style.setFontFamilies({SkString(txt.font_family.c_str())});\n  text_style.setLocale(SkString(txt.locale.c_str()));\n  skia.setTextStyle(text_style);\n\n  skt::StrutStyle strut_style;\n  strut_style.setFontStyle(\n      MakeSkFontStyle(txt.strut_font_weight, txt.strut_font_style));\n  strut_style.setFontSize(SkDoubleToScalar(txt.strut_font_size));\n  strut_style.setHeight(SkDoubleToScalar(txt.strut_height));\n  strut_style.setHeightOverride(txt.strut_has_height_override);\n  strut_style.setHalfLeading(txt.strut_half_leading);\n\n  std::vector<SkString> strut_fonts;\n  std::transform(txt.strut_font_families.begin(), txt.strut_font_families.end(),\n                 std::back_inserter(strut_fonts),\n                 [](const std::string& f) { return SkString(f.c_str()); });\n  strut_style.setFontFamilies(strut_fonts);\n  strut_style.setLeading(txt.strut_leading);\n  strut_style.setForceStrutHeight(txt.force_strut_height);\n  strut_style.setStrutEnabled(txt.strut_enabled);\n  skia.setStrutStyle(strut_style);\n\n  skia.setTextAlign(static_cast<skt::TextAlign>(txt.text_align));\n  skia.setTextDirection(static_cast<skt::TextDirection>(txt.text_direction));\n  skia.setMaxLines(txt.max_lines);\n  skia.setEllipsis(txt.ellipsis);\n  skia.setTextHeightBehavior(\n      static_cast<skt::TextHeightBehavior>(txt.text_height_behavior));\n\n  skia.turnHintingOff();\n  skia.setKeepingTrailingSpaces(txt.keep_trailing_spaces);\n\n  return skia;\n}\n\nskt::TextStyle ParagraphBuilderSkia::TxtToSkia(const TextStyle& txt) {\n  skt::TextStyle skia;\n\n  skia.setColor(txt.color);\n  skia.setDecoration(static_cast<skt::TextDecoration>(txt.decoration));\n  skia.setDecorationColor(txt.decoration_color);\n  skia.setDecorationStyle(\n      static_cast<skt::TextDecorationStyle>(txt.decoration_style));\n  skia.setDecorationThicknessMultiplier(\n      SkDoubleToScalar(txt.decoration_thickness_multiplier));\n  skia.setFontStyle(MakeSkFontStyle(txt.font_weight, txt.font_style));\n  skia.setTextBaseline(static_cast<skt::TextBaseline>(txt.text_baseline));\n  skia.setBaselineShift(txt.text_baseline_shift);\n  skia.setHalfLeading(txt.half_leading);\n  skia.setLineSpacing(txt.line_spacing);\n\n  std::vector<SkString> skia_fonts;\n  std::transform(txt.font_families.begin(), txt.font_families.end(),\n                 std::back_inserter(skia_fonts),\n                 [](const std::string& f) { return SkString(f.c_str()); });\n  skia.setFontFamilies(skia_fonts);\n\n  skia.setFontSize(SkDoubleToScalar(txt.font_size));\n  skia.setLetterSpacing(SkDoubleToScalar(txt.letter_spacing));\n  skia.setWordSpacing(SkDoubleToScalar(txt.word_spacing));\n  skia.setHeight(SkDoubleToScalar(txt.height));\n  skia.setHeightOverride(txt.has_height_override);\n\n  skia.setLocale(SkString(txt.locale.c_str()));\n  if (txt.has_background) {\n    skia.setBackgroundColor(txt.background);\n  }\n  if (txt.has_foreground) {\n    skia.setForegroundColor(txt.foreground);\n  }\n  if (txt.has_foreground_id) {\n    skia.setForegroundPaintID(txt.foreground_id);\n  }\n\n  skia.resetFontFeatures();\n  for (const auto& ff : txt.font_features.GetFontFeatures()) {\n    skia.addFontFeature(SkString(ff.first.c_str()), ff.second);\n  }\n\n  if (!txt.font_variations.GetAxisValues().empty()) {\n    std::vector<SkFontArguments::VariationPosition::Coordinate> coordinates;\n    for (const auto& it : txt.font_variations.GetAxisValues()) {\n      const std::string& axis = it.first;\n      if (axis.length() != 4) {\n        continue;\n      }\n      coordinates.push_back({\n          SkSetFourByteTag(axis[0], axis[1], axis[2], axis[3]),\n          it.second,\n      });\n    }\n    SkFontArguments::VariationPosition position = {\n        coordinates.data(), static_cast<int>(coordinates.size())};\n    skia.setFontArguments(\n        SkFontArguments().setVariationDesignPosition(position));\n  }\n\n  skia.resetShadows();\n  for (const txt::TextShadow& txt_shadow : txt.text_shadows) {\n    skt::TextShadow shadow;\n    shadow.fOffset = SkPoint::Make(txt_shadow.offset.x, txt_shadow.offset.y);\n    shadow.fBlurSigma = txt_shadow.blur_sigma;\n    shadow.fColor = txt_shadow.color;\n    skia.addShadow(shadow);\n  }\n\n  return skia;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/skia/paragraph_builder_skia.h",
    "content": "/*\n * Copyright 2019 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_SKIA_PARAGRAPH_BUILDER_SKIA_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_SKIA_PARAGRAPH_BUILDER_SKIA_H_\n\n#include <memory>\n#include <stack>\n#include <vector>\n#include \"txt/paragraph_builder.h\"\n\n#include \"third_party/skia/modules/skparagraph/include/ParagraphBuilder.h\"\n\nnamespace txt {\n\n// Implementation of ParagraphBuilder based on Skia's text layout module.\nclass ParagraphBuilderSkia : public ParagraphBuilder {\n public:\n  ParagraphBuilderSkia(const ParagraphStyle& style,\n                       std::shared_ptr<FontCollection> font_collection);\n\n  virtual ~ParagraphBuilderSkia();\n\n  virtual void PushStyle(const TextStyle& style) override;\n  virtual void Pop() override;\n  virtual const TextStyle& PeekStyle() override;\n  virtual void AddText(const std::u16string& text) override;\n  virtual void AddPlaceholder(PlaceholderRun& span) override;\n  virtual std::unique_ptr<Paragraph> Build() override;\n\n  void AddText(const char* text, size_t len);\n\n private:\n\n  skia::textlayout::ParagraphStyle TxtToSkia(const ParagraphStyle& txt);\n  skia::textlayout::TextStyle TxtToSkia(const TextStyle& txt);\n\n  std::shared_ptr<skia::textlayout::ParagraphBuilder> builder_;\n  TextStyle base_style_;\n  std::stack<TextStyle> txt_style_stack_;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_SKIA_PARAGRAPH_BUILDER_SKIA_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/skia/paragraph_skia.cc",
    "content": "/*\n * Copyright 2019 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"paragraph_skia.h\"\n\n#include <algorithm>\n#include <numeric>\n#include \"include/core/SkPaint.h\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace txt {\n\nnamespace skt = skia::textlayout;\n// using PaintID = skt::ParagraphPainter::PaintID;\n\nnamespace {\n\n// Convert SkFontStyle::Weight values (ranging from 100-900) to txt::FontWeight\n// values (ranging from 0-8).\ntxt::FontWeight GetTxtFontWeight(int font_weight) {\n  int txt_weight = (font_weight - 100) / 100;\n  txt_weight = std::clamp(txt_weight, static_cast<int>(txt::FontWeight::w100),\n                          static_cast<int>(txt::FontWeight::w900));\n  return static_cast<txt::FontWeight>(txt_weight);\n}\n\ntxt::FontStyle GetTxtFontStyle(SkFontStyle::Slant font_slant) {\n  return font_slant == SkFontStyle::Slant::kUpright_Slant\n             ? txt::FontStyle::normal\n             : txt::FontStyle::italic;\n}\n\n}  // anonymous namespace\n\nParagraphSkia::ParagraphSkia(std::unique_ptr<skt::Paragraph> paragraph)\n    : paragraph_(std::move(paragraph)) {}\n\ndouble ParagraphSkia::GetMaxWidth() {\n  return SkScalarToDouble(paragraph_->getMaxWidth());\n}\n\ndouble ParagraphSkia::GetHeight() {\n  return SkScalarToDouble(paragraph_->getHeight());\n}\n\ndouble ParagraphSkia::GetLongestLine() {\n  return SkScalarToDouble(paragraph_->getLongestLine());\n}\n\nstd::vector<LineMetrics>& ParagraphSkia::GetLineMetrics() {\n  if (!line_metrics_) {\n    std::vector<skt::LineMetrics> metrics;\n    paragraph_->getLineMetrics(metrics);\n\n    line_metrics_.emplace();\n    line_metrics_styles_.reserve(\n        std::accumulate(metrics.begin(), metrics.end(), 0,\n                        [](const int a, const skt::LineMetrics& b) {\n                          return a + b.fLineMetrics.size();\n                        }));\n\n    for (const skt::LineMetrics& skm : metrics) {\n      LineMetrics& txtm = line_metrics_->emplace_back(\n          skm.fStartIndex, skm.fEndIndex, skm.fEndExcludingWhitespaces,\n          skm.fEndIncludingNewline, skm.fHardBreak);\n      txtm.ascent = skm.fAscent;\n      txtm.descent = skm.fDescent;\n      txtm.unscaled_ascent = skm.fUnscaledAscent;\n      txtm.height = skm.fHeight;\n      txtm.width = skm.fWidth;\n      txtm.left = skm.fLeft;\n      txtm.baseline = skm.fBaseline;\n      txtm.line_number = skm.fLineNumber;\n\n      for (const auto& sk_iter : skm.fLineMetrics) {\n        const skt::StyleMetrics& sk_style_metrics = sk_iter.second;\n        line_metrics_styles_.push_back(SkiaToTxt(*sk_style_metrics.text_style));\n        txtm.run_metrics.emplace(\n            std::piecewise_construct, std::forward_as_tuple(sk_iter.first),\n            std::forward_as_tuple(&line_metrics_styles_.back(),\n                                  sk_style_metrics.font_metrics));\n      }\n    }\n  }\n\n  return line_metrics_.value();\n}\n\ndouble ParagraphSkia::GetMinIntrinsicWidth() {\n  return SkScalarToDouble(paragraph_->getMinIntrinsicWidth());\n}\n\ndouble ParagraphSkia::GetMaxIntrinsicWidth() {\n  return SkScalarToDouble(paragraph_->getMaxIntrinsicWidth());\n}\n\ndouble ParagraphSkia::GetAlphabeticBaseline() {\n  return SkScalarToDouble(paragraph_->getAlphabeticBaseline());\n}\n\ndouble ParagraphSkia::GetIdeographicBaseline() {\n  return SkScalarToDouble(paragraph_->getIdeographicBaseline());\n}\n\nbool ParagraphSkia::DidExceedMaxLines() {\n  return paragraph_->didExceedMaxLines();\n}\n\nvoid ParagraphSkia::Layout(double width) {\n  line_metrics_.reset();\n  line_metrics_styles_.clear();\n  paragraph_->layout(width);\n}\n\nvoid ParagraphSkia::Paint(SkCanvas* canvas, double x, double y) {\n  paragraph_->paint(canvas, x, y);\n}\n\nstd::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForRange(\n    size_t start,\n    size_t end,\n    RectHeightStyle rect_height_style,\n    RectWidthStyle rect_width_style) {\n  std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForRange(\n      start, end, static_cast<skt::RectHeightStyle>(rect_height_style),\n      static_cast<skt::RectWidthStyle>(rect_width_style));\n\n  std::vector<Paragraph::TextBox> boxes;\n  for (const skt::TextBox& skia_box : skia_boxes) {\n    boxes.emplace_back(\n        skity::Rect::MakeLTRB(skia_box.rect.left(), skia_box.rect.top(),\n                              skia_box.rect.right(), skia_box.rect.bottom()),\n        static_cast<TextDirection>(skia_box.direction));\n  }\n\n  return boxes;\n}\n\nstd::vector<Paragraph::TextBox> ParagraphSkia::GetRectsForPlaceholders() {\n  std::vector<skt::TextBox> skia_boxes = paragraph_->getRectsForPlaceholders();\n\n  std::vector<Paragraph::TextBox> boxes;\n  for (const skt::TextBox& skia_box : skia_boxes) {\n    boxes.emplace_back(\n        skity::Rect::MakeLTRB(skia_box.rect.left(), skia_box.rect.top(),\n                              skia_box.rect.right(), skia_box.rect.bottom()),\n        static_cast<TextDirection>(skia_box.direction), skia_box.placeholderId);\n  }\n\n  return boxes;\n}\n\nParagraph::PositionWithAffinity ParagraphSkia::GetGlyphPositionAtCoordinate(\n    double dx,\n    double dy) {\n  skt::PositionWithAffinity skia_pos =\n      paragraph_->getGlyphPositionAtCoordinate(dx, dy);\n\n  return ParagraphSkia::PositionWithAffinity(\n      skia_pos.position, static_cast<Affinity>(skia_pos.affinity));\n}\n\nParagraph::Range<size_t> ParagraphSkia::GetWordBoundary(size_t offset) {\n  skt::SkRange<size_t> range = paragraph_->getWordBoundary(offset);\n  return Paragraph::Range<size_t>(range.start, range.end);\n}\n\nTextStyle ParagraphSkia::SkiaToTxt(const skt::TextStyle& skia) {\n  TextStyle txt;\n\n  txt.color = skia.getColor();\n  txt.decoration = static_cast<TextDecoration>(skia.getDecorationType());\n  txt.decoration_color = skia.getDecorationColor();\n  txt.decoration_style =\n      static_cast<TextDecorationStyle>(skia.getDecorationStyle());\n  txt.decoration_thickness_multiplier =\n      SkScalarToDouble(skia.getDecorationThicknessMultiplier());\n  txt.font_weight = GetTxtFontWeight(skia.getFontStyle().weight());\n  txt.font_style = GetTxtFontStyle(skia.getFontStyle().slant());\n\n  txt.text_baseline = static_cast<TextBaseline>(skia.getTextBaseline());\n\n  for (const SkString& font_family : skia.getFontFamilies()) {\n    txt.font_families.emplace_back(font_family.c_str());\n  }\n\n  txt.font_size = SkScalarToDouble(skia.getFontSize());\n  txt.letter_spacing = SkScalarToDouble(skia.getLetterSpacing());\n  txt.word_spacing = SkScalarToDouble(skia.getWordSpacing());\n  txt.height = SkScalarToDouble(skia.getHeight());\n\n  txt.locale = skia.getLocale().c_str();\n  if (skia.hasBackground()) {\n    txt.background = skia.getBackground();\n  }\n  if (skia.hasForeground()) {\n    txt.foreground = skia.getForeground();\n  }\n\n  txt.text_shadows.clear();\n  for (const skt::TextShadow& skia_shadow : skia.getShadows()) {\n    txt::TextShadow shadow;\n    shadow.offset = {skia_shadow.fOffset.fX, skia_shadow.fOffset.fY};\n    shadow.blur_sigma = skia_shadow.fBlurSigma;\n    shadow.color = skia_shadow.fColor;\n    txt.text_shadows.emplace_back(shadow);\n  }\n\n  return txt;\n}\n\nvoid ParagraphSkia::UpdateForegroundPaint(size_t text_size, SkPaint paint) {\n  paragraph_->updateForegroundPaint(0, text_size, paint);\n}\n\nvoid ParagraphSkia::UpdateForegroundPaint(\n    std::unordered_map<int, SkPaint> paint_map) {\n  paragraph_->updateForegroundPaint(paint_map);\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/skia/paragraph_skia.h",
    "content": "/*\n * Copyright 2019 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_SKIA_PARAGRAPH_SKIA_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_SKIA_PARAGRAPH_SKIA_H_\n\n#include <memory>\n#include <optional>\n#include <unordered_map>\n#include <vector>\n\n#include \"txt/paragraph.h\"\n\n#include \"third_party/skia/modules/skparagraph/include/Paragraph.h\"\n\nnamespace clay {\nclass TextPainter;\n}  // namespace clay\n\nnamespace txt {\n\n// Implementation of Paragraph based on Skia's text layout module.\nclass ParagraphSkia : public Paragraph {\n public:\n  explicit ParagraphSkia(std::unique_ptr<skia::textlayout::Paragraph> paragraph);\n\n  virtual ~ParagraphSkia() = default;\n\n  double GetMaxWidth() override;\n\n  double GetHeight() override;\n\n  double GetLongestLine() override;\n\n  double GetMinIntrinsicWidth() override;\n\n  double GetMaxIntrinsicWidth() override;\n\n  double GetAlphabeticBaseline() override;\n\n  double GetIdeographicBaseline() override;\n\n  std::vector<LineMetrics>& GetLineMetrics() override;\n\n  bool DidExceedMaxLines() override;\n\n  void Layout(double width) override;\n\n  void Paint(SkCanvas* canvas, double x, double y) override;\n\n\n  std::vector<TextBox> GetRectsForRange(\n      size_t start,\n      size_t end,\n      RectHeightStyle rect_height_style,\n      RectWidthStyle rect_width_style) override;\n\n  std::vector<TextBox> GetRectsForPlaceholders() override;\n\n  PositionWithAffinity GetGlyphPositionAtCoordinate(double dx,\n                                                    double dy) override;\n\n  Range<size_t> GetWordBoundary(size_t offset) override;\n\n  void UpdateForegroundPaint(size_t text_size, SkPaint paint);\n\n  void UpdateForegroundPaint(std::unordered_map<int, SkPaint> paint_map);\n\n private:\n  friend class clay::TextPainter;\n  TextStyle SkiaToTxt(const skia::textlayout::TextStyle& skia);\n\n  std::unique_ptr<skia::textlayout::Paragraph> paragraph_;\n  std::optional<std::vector<LineMetrics>> line_metrics_;\n  std::vector<TextStyle> line_metrics_styles_;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_SKIA_PARAGRAPH_SKIA_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/tttext/paragraph_builder_tt_text.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"paragraph_builder_tt_text.h\"\n\n#include <textra/layout_region.h>\n#include \"clay/fml/logging.h\"\n#include \"textra/layout_definition.h\"\n#include \"tttext/tttext_headers.h\"\n#include \"txt/text_decoration.h\"\n\nnamespace txt {\nParagraphBuilderTTText::ParagraphBuilderTTText(\n    const ParagraphStyle& style,\n    const std::shared_ptr<FontCollection>& font_collection)\n    : font_collection_(font_collection) {\n  ToTTParaStyle(style);\n  ParagraphBuilderTTText::PushStyle(style.GetTextStyle());\n}\nParagraphBuilderTTText::~ParagraphBuilderTTText() = default;\nvoid ParagraphBuilderTTText::PushStyle(const TextStyle& style) {\n  text_style_stack_.push_back(style);\n  run_style_stack_.push_back(ToTTStyle(style));\n}\nvoid ParagraphBuilderTTText::Pop() {\n  text_style_stack_.pop_back();\n  run_style_stack_.pop_back();\n}\nconst TextStyle& ParagraphBuilderTTText::PeekStyle() {\n  return text_style_stack_.back();\n}\nvoid ParagraphBuilderTTText::AddText(const std::u16string& text) {\n  CreateParagraph();\n  paragraph_->AddTextRun(run_style_stack_.back(), text);\n}\nvoid ParagraphBuilderTTText::AddPlaceholder(PlaceholderRun& span) {\n  CreateParagraph();\n  paragraph_->AddPlaceholder(run_style_stack_.back(), span, false);\n}\nstd::unique_ptr<Paragraph> ParagraphBuilderTTText::Build() {\n  CreateParagraph();\n  return std::move(paragraph_);\n}\nvoid ParagraphBuilderTTText::AddText(const char* text, size_t len) {\n  CreateParagraph();\n  paragraph_->AddTextRun(run_style_stack_.back(), std::string(text, len));\n}\n\ntttext::LineType ParagraphBuilderTTText::ToTTLineType(\n    TextDecorationStyle decoration_style) {\n  if (decoration_style == TextDecorationStyle::kDashed) {\n    return tttext::LineType::kDashed;\n  } else if (decoration_style == TextDecorationStyle::kDotted) {\n    return tttext::LineType::kDotted;\n  } else if (decoration_style == TextDecorationStyle::kDouble) {\n    return tttext::LineType::kDouble;\n  } else if (decoration_style == TextDecorationStyle::kSolid) {\n    return tttext::LineType::kSolid;\n  } else if (decoration_style == TextDecorationStyle::kWavy) {\n    return tttext::LineType::kWavy;\n  } else {\n    return tttext::LineType::kNone;\n  }\n}\n\ntttext::Style ParagraphBuilderTTText::ToTTStyle(const TextStyle& text_style) {\n  tttext::Style style;\n  tttext::Weight weight;\n  switch (text_style.font_weight) {\n    case FontWeight::w100:\n      weight = tttext::FontStyle::kThin_Weight;\n      break;\n    case FontWeight::w200:\n      weight = tttext::FontStyle::kExtraLight_Weight;\n      break;\n    case FontWeight::w300:\n      weight = tttext::FontStyle::kLight_Weight;\n      break;\n    case FontWeight::w400:\n      weight = tttext::FontStyle::kNormal_Weight;\n      break;\n    case FontWeight::w500:\n      weight = tttext::FontStyle::kMedium_Weight;\n      break;\n    case FontWeight::w600:\n      weight = tttext::FontStyle::kSemiBold_Weight;\n      break;\n    case FontWeight::w700:\n      weight = tttext::FontStyle::kBold_Weight;\n      break;\n    case FontWeight::w800:\n      weight = tttext::FontStyle::kExtraBold_Weight;\n      break;\n    case FontWeight::w900:\n      weight = tttext::FontStyle::kBlack_Weight;\n      break;\n  }\n  tttext::FontDescriptor fd{{text_style.font_families},\n                            {weight, tttext::FontStyle::kNormal_Width,\n                             tttext::FontStyle::kUpright_Slant},\n                            0};\n  style.SetTextSize(text_style.font_size);\n  style.SetFontDescriptor(fd);\n  style.SetForegroundColor(tttext::TTColor(text_style.color));\n#if defined(ENABLE_SKITY)\n  if (text_style.has_foreground) {\n    style.SetForegroundColor(tttext::TTColor(text_style.foreground.GetColor()));\n  }\n  if (text_style.has_background) {\n    style.SetBackgroundColor(tttext::TTColor(text_style.background.GetColor()));\n  }\n#else\n  if (text_style.has_foreground) {\n    style.SetForegroundColor(tttext::TTColor(text_style.foreground.getColor()));\n  }\n  if (text_style.has_background) {\n    style.SetBackgroundColor(tttext::TTColor(text_style.background.getColor()));\n  }\n#endif  // ENABLE_SKITY\n  if (text_style.font_style != FontStyle::normal) {\n    style.SetItalic(true);\n  }\n  style.SetDecorationType(\n      static_cast<tttext::DecorationType>(text_style.decoration));\n  style.SetDecorationColor(tttext::TTColor(text_style.decoration_color\n                                               ? text_style.decoration_color\n                                               : text_style.color));\n  style.SetDecorationStyle(ToTTLineType(text_style.decoration_style));\n  style.SetDecorationThicknessMultiplier(\n      text_style.decoration_thickness_multiplier);\n  style.SetWordSpacing(text_style.word_spacing);\n  style.SetLetterSpacing(text_style.letter_spacing);\n  style.SetBaselineOffset(text_style.text_baseline_shift);\n  switch (text_style.word_break) {\n    case kNormal:\n      style.SetWordBreak(tttext::WordBreakType::kNormal);\n      break;\n    case kBreakAll:\n      style.SetWordBreak(tttext::WordBreakType::kBreakAll);\n      break;\n    case kKeepAll:\n      style.SetWordBreak(tttext::WordBreakType::kKeepAll);\n      break;\n  }\n\n  std::vector<tttext::TextShadow> shadow_list;\n  for (const txt::TextShadow& txt_shadow : text_style.text_shadows) {\n    tttext::TextShadow shadow;\n    shadow.offset_[0] = txt_shadow.offset.x;\n    shadow.offset_[1] = txt_shadow.offset.y;\n    shadow.blur_radius_ = txt_shadow.blur_sigma;\n    shadow.color_ = txt_shadow.color;\n    shadow_list.emplace_back(shadow);\n  }\n  style.SetTextShadowList(shadow_list);\n  style.SetTextStrokeStyle(tttext::TTColor(text_style.stroke_color),\n                           text_style.stroke_width);\n#if defined(CLAY_ENABLE_TTTEXT)\n  style.SetVerticalAlignment(text_style.align_type);\n#endif  // CLAY_ENABLE_TTTEXT\n  return style;\n}\nvoid ParagraphBuilderTTText::ToTTParaStyle(const ParagraphStyle& para_style) {\n  auto& tt_para_style = paragraph_style_;\n  auto text_style = para_style.GetTextStyle();\n  switch (para_style.text_align) {\n    case TextAlign::left:\n      tt_para_style.SetHorizontalAlign(\n          tttext::ParagraphHorizontalAlignment::kLeft);\n      break;\n    case TextAlign::right:\n      tt_para_style.SetHorizontalAlign(\n          tttext::ParagraphHorizontalAlignment::kRight);\n      break;\n    case TextAlign::center:\n      tt_para_style.SetHorizontalAlign(\n          tttext::ParagraphHorizontalAlignment::kCenter);\n      break;\n    case TextAlign::justify:\n      tt_para_style.SetHorizontalAlign(\n          tttext::ParagraphHorizontalAlignment::kJustify);\n      break;\n    case TextAlign::start:\n      if (para_style.text_direction == TextDirection::ltr) {\n        tt_para_style.SetHorizontalAlign(\n            tttext::ParagraphHorizontalAlignment::kLeft);\n      } else {\n        tt_para_style.SetHorizontalAlign(\n            tttext::ParagraphHorizontalAlignment::kRight);\n      }\n      break;\n    case TextAlign::end:\n      if (para_style.text_direction == TextDirection::rtl) {\n        tt_para_style.SetHorizontalAlign(\n            tttext::ParagraphHorizontalAlignment::kLeft);\n      } else {\n        tt_para_style.SetHorizontalAlign(\n            tttext::ParagraphHorizontalAlignment::kRight);\n      }\n      break;\n  }\n  tt_para_style.SetMaxLines(para_style.max_lines);\n  tt_para_style.SetEllipsis(para_style.ellipsis);\n  tt_para_style.SetWriteDirection(para_style.text_direction ==\n                                          TextDirection::ltr\n                                      ? tttext::WriteDirection::kLTR\n                                      : tttext::WriteDirection::kRTL);\n  tt_para_style.SetLineHeightOverride(para_style.has_height_override);\n  tt_para_style.SetLineHeightInPx(\n      para_style.height,\n      static_cast<ttoffice::tttext::RulerType>(para_style.height_type));\n  tt_para_style.EnableTextBounds(para_style.enable_text_bounds);\n  tt_para_style.SetDefaultStyle(ToTTStyle(text_style));\n  tt_para_style.SetLineSpaceAfterPx(para_style.line_spacing);\n  tt_para_style.SetHalfLeading(para_style.half_leading);\n}\nvoid ParagraphBuilderTTText::CreateParagraph() {\n  if (paragraph_ == nullptr) {\n    paragraph_ =\n        std::make_unique<ParagraphTTText>(font_collection_, paragraph_style_);\n  }\n}\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/tttext/paragraph_builder_tt_text.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TTTEXT_PARAGRAPH_BUILDER_TT_TEXT_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TTTEXT_PARAGRAPH_BUILDER_TT_TEXT_H_\n\n#include <memory>\n#include <vector>\n#include \"paragraph_tt_text.h\"\n#include \"textra/layout_definition.h\"\n#ifdef ENABLE_SKITY\n#include \"txt/font_collection_skity.h\"\n#else\n#include \"txt/font_collection_skia.h\"\n#endif\n#include <textra/paragraph_style.h>\n#include \"txt/paragraph_builder.h\"\n\nnamespace ttoffice {\nnamespace textlayout {\nclass Paragraph;\nclass Style;\n}  // namespace textlayout\n}  // namespace ttoffice\nnamespace txt {\nclass ParagraphBuilderTTText : public ParagraphBuilder {\n public:\n  ParagraphBuilderTTText(\n      const ParagraphStyle& style,\n      const std::shared_ptr<FontCollection>& font_collection);\n\n  ~ParagraphBuilderTTText() override;\n\n  void PushStyle(const TextStyle& style) override;\n  void Pop() override;\n  const TextStyle& PeekStyle() override;\n  void AddText(const std::u16string& text) override;\n  void AddPlaceholder(PlaceholderRun& span) override;\n  std::unique_ptr<Paragraph> Build() override;\n\n  void AddText(const char* text, size_t len);\n  tttext::ParagraphStyle GetTTParagraphStyle() { return paragraph_style_; }\n\n private:\n  tttext::LineType ToTTLineType(TextDecorationStyle decoration_style);\n  tttext::Style ToTTStyle(const TextStyle& text_style);\n  void ToTTParaStyle(const ParagraphStyle& para_style);\n  void CreateParagraph();\n\n private:\n  std::shared_ptr<FontCollection> font_collection_;\n  std::vector<TextStyle> text_style_stack_;\n  std::vector<tttext::Style> run_style_stack_;\n  std::unique_ptr<ParagraphTTText> paragraph_;\n  tttext::ParagraphStyle paragraph_style_;\n};\n}  // namespace txt\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TTTEXT_PARAGRAPH_BUILDER_TT_TEXT_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/tttext/paragraph_tt_text.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"paragraph_tt_text.h\"\n\n#include <textra/layout_drawer.h>\n#include <textra/layout_region.h>\n#include <textra/text_layout.h>\n#include <textra/text_line.h>\n#include \"base/include/string/string_utils.h\"\n#include \"txt/placeholder_run.h\"\n#ifdef ENABLE_SKITY\n#include \"txt/font_collection_skity.h\"\n#else\n#include \"third_party/textlayout/textra/public/textra/platform/skia/skia_canvas_helper.h\"\n#include \"third_party/textlayout/textra/public/textra/run_delegate.h\"\n#include \"txt/font_collection_skia.h\"\n#endif\n\nnamespace txt {\n\nclass TTShapeRun : public tttext::RunDelegate {\n public:\n  TTShapeRun(const PlaceholderRun& span) {\n    FML_DCHECK(span.baseline == TextBaseline::kAlphabetic);\n    FML_DCHECK(span.alignment == PlaceholderAlignment::kBaseline);\n    ascent_ = -span.height;\n    descent_ = 0;\n    advance_ = span.width;\n  }\n  float GetAscent() const override { return ascent_; }\n  float GetDescent() const override { return descent_; }\n  float GetAdvance() const override { return advance_; }\n\n private:\n  float ascent_;\n  float descent_;\n  float advance_;\n};\n\nParagraphTTText::ParagraphTTText(\n    std::shared_ptr<FontCollection> font_collection,\n    const tttext::ParagraphStyle& paragraph_style)\n    : font_collection_(font_collection) {\n  paragraph_ = tttext::Paragraph::Create();\n  paragraph_->SetParagraphStyle(&paragraph_style);\n}\ndouble ParagraphTTText::GetMaxWidth() {\n  if (region_ == nullptr)\n    return 0;\n  return region_->GetLayoutedWidth();\n}\ndouble ParagraphTTText::GetHeight() {\n  if (region_ == nullptr)\n    return 0;\n  return region_->GetLayoutedHeight();\n}\ndouble ParagraphTTText::GetLongestLine() {\n  return GetMaxWidth();\n}\ndouble ParagraphTTText::GetMinIntrinsicWidth() {\n  return GetMaxWidth();\n}\ndouble ParagraphTTText::GetMaxIntrinsicWidth() {\n  return GetMaxWidth();\n}\ndouble ParagraphTTText::GetAlphabeticBaseline() {\n  if (region_ == nullptr || region_->GetLineCount() == 0) {\n    return 0;\n  }\n  return region_->GetLine(0)->GetMaxAscent();\n}\ndouble ParagraphTTText::GetIdeographicBaseline() {\n  if (region_ == nullptr || region_->GetLineCount() == 0) {\n    return 0;\n  }\n  return region_->GetLine(0)->GetMaxAscent() +\n         region_->GetLine(0)->GetMaxDescent();\n}\nstd::vector<LineMetrics>& ParagraphTTText::GetLineMetrics() {\n  return line_metrics_;\n}\n\nbool ParagraphTTText::DidExceedMaxLines() {\n  if (region_ == nullptr) {\n    return false;\n  }\n  return region_->DidExceedMaxLines();\n}\n\nvoid ParagraphTTText::Layout(double width) {\n#if defined(ENABLE_SKITY)\n  auto i_font_collection = font_collection_->GetIFontCollection();\n  tttext::TextLayout layout(&i_font_collection, tttext::kSelfRendering);\n#else\n  auto i_font_collection = font_collection_->CreateTTFontCollection();\n  tttext::TextLayout layout(i_font_collection.get(), tttext::kSelfRendering);\n#endif\n  auto halign = paragraph_->GetParagraphStyle().GetHorizontalAlign();\n  auto width_mode =\n      std::isinf(width) || halign == tttext::ParagraphHorizontalAlignment::kLeft\n          ? tttext::LayoutMode::kAtMost\n          : tttext::LayoutMode::kDefinite;\n  region_ = std::make_unique<tttext::LayoutRegion>(\n      width, std::numeric_limits<float>::max(), width_mode,\n      tttext::LayoutMode::kAtMost);\n  tttext::TTTextContext context;\n  context.SetEnableSystemFontAdjust(false);\n  if (need_trim_space_) {\n    context.EnableFeature(ttoffice::tttext::FeatureOption::kTrimLineTailSpace,\n                          false);\n  }\n  tttext::LayoutResult result =\n      layout.Layout(paragraph_.get(), region_.get(), context);\n  if (result != tttext::LayoutResult::kNormal &&\n      result != tttext::LayoutResult::kBreakPage) {\n    FML_DCHECK(false) << \"TTText layout result is not normal!\";\n  }\n  line_metrics_.resize(region_->GetLineCount());\n\n  for (uint32_t k = 0; k < region_->GetLineCount(); k++) {\n    auto* text_line = region_->GetLine(k);\n    auto& metrics = line_metrics_[k];\n    metrics.ascent = text_line->GetMaxAscent();\n    metrics.descent = text_line->GetMaxDescent();\n    float rect_ltwh[4];\n    text_line->GetBoundingRectForLine(rect_ltwh);\n    metrics.width = rect_ltwh[2];\n    metrics.height = rect_ltwh[3];\n    metrics.baseline = text_line->GetLineTop() + text_line->GetMaxAscent();\n    metrics.line_number = k;\n    metrics.start_index = text_line->GetStartCharPos();\n    metrics.end_index = text_line->GetEndCharPos();\n  }\n}\n\nvoid ParagraphTTText::Paint(SkCanvas* canvas, double x, double y) {\n#ifdef ENABLE_SKITY\n  FML_DCHECK(false);\n#else\n  canvas->save();\n  canvas->translate(x, y);\n  SkiaCanvasHelper helper(canvas);\n  tttext::LayoutDrawer drawer(&helper);\n  drawer.DrawLayoutPage(region_.get());\n  canvas->restore();\n#endif\n}\n\n#ifdef ENABLE_SKITY\nvoid ParagraphTTText::Paint(clay::GraphicsCanvas* canvas, double x, double y) {\n  canvas->Save();\n  canvas->Translate(x, y);\n  tttext::SkityCanvasHelper helper(canvas->GetGrCanvas());\n  tttext::LayoutDrawer drawer(&helper);\n  drawer.DrawLayoutPage(region_.get());\n  canvas->Restore();\n}\n#endif\n\nstd::vector<Paragraph::TextBox> ParagraphTTText::GetRectsForRange(\n    size_t start,\n    size_t end,\n    Paragraph::RectHeightStyle rect_height_style,\n    Paragraph::RectWidthStyle rect_width_style) {\n  std::vector<TextBox> result;\n  for (uint32_t k = 0; k < region_->GetLineCount(); k++) {\n    auto* text_line = region_->GetLine(k);\n    size_t start_index = text_line->GetStartCharPos();\n    size_t end_index = text_line->GetEndCharPos();\n\n    // Check to see if we are finished.\n    if (start_index >= end)\n      break;\n\n    if (end_index <= start)\n      continue;\n\n    float rect[4] = {0};\n    text_line->GetBoundingRectByCharRange(rect, std::max(start, start_index),\n                                          std::min(end, end_index));\n    result.push_back(\n        TextBox(skity::Rect::MakeXYWH(rect[0], rect[1], rect[2], rect[3]),\n                TextDirection::ltr));\n  }\n  return result;\n}\nstd::vector<Paragraph::TextBox> ParagraphTTText::GetRectsForPlaceholders() {\n  std::vector<TextBox> result;\n  for (uint32_t k = 0; k < region_->GetLineCount(); k++) {\n    auto* text_line = region_->GetLine(k);\n    size_t start_index = text_line->GetStartCharPos();\n    size_t end_index = text_line->GetEndCharPos();\n    for (size_t i = 0; i < placeholder_pos_.size(); i++) {\n      if (placeholder_pos_[i] >= start_index &&\n          placeholder_pos_[i] < end_index) {\n        float rect[4] = {0};\n        text_line->GetCharBoundingRect(rect, placeholder_pos_[i]);\n        if (rect[2] != 0 && rect[3] != 0) {\n          result.push_back(\n              TextBox(skity::Rect::MakeXYWH(rect[0], rect[1], rect[2], rect[3]),\n                      TextDirection::ltr, i));\n        }\n      }\n    }\n  }\n  return result;\n}\nParagraph::PositionWithAffinity ParagraphTTText::GetGlyphPositionAtCoordinate(\n    double dx,\n    double dy) {\n  ttoffice::tttext::CharPos result = 0;\n  for (uint32_t k = 0; k < region_->GetLineCount(); k++) {\n    auto* text_line = region_->GetLine(k);\n    float rect[4] = {0};\n    text_line->GetBoundingRectForLine(rect);\n    if (rect[2] != 0 && rect[3] != 0 && dy <= rect[1] + rect[3]) {\n      if ((k != 0 && dy < rect[1])) {\n        break;\n      }\n      auto char_pos = text_line->GetCharPosByCoordinateX(dx);\n      if (char_pos > 0) {\n        auto end_char = paragraph_->GetContentString(char_pos - 1, 1);\n        if (end_char == \"\\n\") {\n          char_pos -= 1;\n        }\n      }\n      result += char_pos;\n      break;\n    } else {\n      result += text_line->GetCharCount();\n    }\n  }\n  return Paragraph::PositionWithAffinity(result, Paragraph::DOWNSTREAM);\n}\nParagraph::Range<size_t> ParagraphTTText::GetWordBoundary(size_t offset) {\n  if (region_ == nullptr)\n    return Range<size_t>(0, 0);\n  auto word = paragraph_->GetWordBoundary(offset);\n  return Paragraph::Range<size_t>(word.first, word.second);\n}\n\nvoid ParagraphTTText::UpdateForegroundPaint(size_t text_size,\n#ifdef ENABLE_SKITY\n                                            skity::Paint paint) {\n#else\n                                            SkPaint paint) {\n#endif\n  sk_paint_ = paint;\n\n#ifdef ENABLE_SKITY\n  tt_painter_.platform_painter_ = std::make_unique<skity::Paint>(sk_paint_);\n#else\n  tt_painter_.sk_paint_ = std::make_unique<SkPaint>(sk_paint_);\n#endif\n  tttext::Style style;\n  style.SetForegroundPainter(&tt_painter_);\n  paragraph_->ApplyStyleInRange(style, 0, text_size);\n}\n\n#ifdef ENABLE_SKITY\nvoid ParagraphTTText::UpdateForegroundPaint(size_t start,\n                                            size_t end,\n                                            skity::Paint paint) {\n  // Make sure start and end are valid positions.\n  tttext::Style style;\n  auto skity_painter = std::make_unique<tttext::SkityPainter>();\n  tttext::SkityPainter* tt_painter = skity_painter.get();\n  tt_painters_.push_back(std::move(skity_painter));\n  tt_painter->platform_painter_ = std::make_unique<skity::Paint>(paint);\n  style.SetForegroundPainter(tt_painter);\n  paragraph_->ApplyStyleInRange(style, start, end - start);\n}\n#endif\n\nvoid ParagraphTTText::AddPlaceholder(tttext::Style& style,\n                                     PlaceholderRun& span,\n                                     bool is_float) {\n  auto delegate = std::make_unique<TTShapeRun>(span);\n  placeholder_pos_.push_back(paragraph_->GetCharCount());\n  paragraph_->AddShapeRun(&style, std::move(delegate), is_float);\n}\n\nvoid ParagraphTTText::AddTextRun(tttext::Style& style,\n                                 const std::u16string& content) {\n  paragraph_->AddTextRun(&style, lynx::base::U16StringToU8(content).c_str());\n}\nvoid ParagraphTTText::AddTextRun(tttext::Style& style,\n                                 const std::string& content) {\n  paragraph_->AddTextRun(&style, content.c_str());\n}\nuint32_t ParagraphTTText::GetTextSize() const {\n  return paragraph_->GetCharCount();\n}\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/tttext/paragraph_tt_text.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TTTEXT_PARAGRAPH_TT_TEXT_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TTTEXT_PARAGRAPH_TT_TEXT_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include <textra/paragraph_style.h>\n#include \"clay/gfx/graphics_canvas.h\"\n#ifdef ENABLE_SKITY\n#include \"tttext/tttext_headers.h\"\n#else\n#include \"third_party/textlayout/textra/public/textra/platform/skia/skia_painter.h\"\n#endif\n#include \"txt/paragraph.h\"\n\nnamespace ttoffice {\nnamespace tttext {\nclass Paragraph;\nclass Style;\nclass LayoutRegion;\n}  // namespace tttext\n}  // namespace ttoffice\nnamespace txt {\nclass FontCollection;\nclass PlaceholderRun;\n\n// Implementation of Paragraph based on Skia's text layout module.\nclass ParagraphTTText : public Paragraph {\n public:\n  ParagraphTTText(std::shared_ptr<FontCollection> font_collection,\n                  const tttext::ParagraphStyle& paragraph_style);\n\n  ~ParagraphTTText() override = default;\n\n  double GetMaxWidth() override;\n\n  double GetHeight() override;\n\n  double GetLongestLine() override;\n\n  double GetMinIntrinsicWidth() override;\n\n  double GetMaxIntrinsicWidth() override;\n\n  double GetAlphabeticBaseline() override;\n\n  double GetIdeographicBaseline() override;\n\n  std::vector<LineMetrics>& GetLineMetrics() override;\n\n  bool DidExceedMaxLines() override;\n\n  void Layout(double width) override;\n\n  void Paint(SkCanvas* canvas, double x, double y) override;\n\n#ifdef ENABLE_SKITY\n  void Paint(clay::GraphicsCanvas* canvas, double x, double y);\n#endif\n\n  std::vector<TextBox> GetRectsForRange(\n      size_t start,\n      size_t end,\n      RectHeightStyle rect_height_style,\n      RectWidthStyle rect_width_style) override;\n\n  std::vector<TextBox> GetRectsForPlaceholders() override;\n\n  PositionWithAffinity GetGlyphPositionAtCoordinate(double dx,\n                                                    double dy) override;\n\n  Range<size_t> GetWordBoundary(size_t offset) override;\n\n#ifdef ENABLE_SKITY\n  void UpdateForegroundPaint(size_t text_size, skity::Paint paint);\n  void UpdateForegroundPaint(size_t start, size_t end, skity::Paint paint);\n#else\n  void UpdateForegroundPaint(size_t text_size, SkPaint paint);\n#endif\n\n  void SetNeedTrimSpace(bool need_trim_space) {\n    need_trim_space_ = need_trim_space;\n  }\n  void AddPlaceholder(tttext::Style& style,\n                      PlaceholderRun& span,\n                      bool is_float);\n  void AddTextRun(tttext::Style& style, const std::u16string& content);\n  void AddTextRun(tttext::Style& style, const std::string& content);\n  uint32_t GetTextSize() const;\n\n private:\n  std::shared_ptr<FontCollection> font_collection_;\n  std::unique_ptr<tttext::Paragraph> paragraph_;\n  std::unique_ptr<tttext::LayoutRegion> region_;\n  std::vector<LineMetrics> line_metrics_;\n  std::vector<size_t> placeholder_pos_;\n  bool need_trim_space_ = false;\n#ifdef ENABLE_SKITY\n  skity::Paint sk_paint_;\n  tttext::SkityPainter tt_painter_;\n  std::vector<std::unique_ptr<tttext::SkityPainter>> tt_painters_;\n#else\n  SkPaint sk_paint_;\n  tttext::SkiaPainter tt_painter_;\n#endif\n};\n}  // namespace txt\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TTTEXT_PARAGRAPH_TT_TEXT_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/tttext/tttext_headers.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TTTEXT_TTTEXT_HEADERS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TTTEXT_TTTEXT_HEADERS_H_\n\n#if defined(OS_IOS)\n#import <textra/font_info.h>\n#import <textra/fontmgr_collection.h>\n#import <textra/i_canvas_helper.h>\n#import <textra/i_font_manager.h>\n#import <textra/i_typeface_helper.h>\n#import <textra/macro.h>\n#import <textra/painter.h>\n#ifdef ENABLE_SKITY\n#import <textra/platform/skity/skity_canvas_helper.h>\n#import <textra/platform/skity/skity_font_manager.h>\n#import <textra/platform/skity/skity_font_manager_coretext.h>\n#endif\n#import <textra/run_delegate.h>\n#import <textra/style.h>\n#import <textra/text_layout.h>\n#else\n#include <textra/font_info.h>\n#include <textra/fontmgr_collection.h>\n#include <textra/i_canvas_helper.h>\n#include <textra/i_font_manager.h>\n#include <textra/i_typeface_helper.h>\n#if !defined(OS_WIN)\n#include <textra/icu_wrapper.h>\n#endif\n#include <textra/macro.h>\n#include <textra/painter.h>\n#ifdef ENABLE_SKITY\n#include <textra/platform/skity/skity_canvas_helper.h>\n#include <textra/platform/skity/skity_font_manager.h>\n#if defined(OS_IOS) || defined(OS_OSX)\n#include <textra/platform/skity/skity_font_manager_coretext.h>\n#endif\n#endif\n#include <textra/run_delegate.h>\n#include <textra/style.h>\n#include <textra/text_layout.h>\n#endif\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TTTEXT_TTTEXT_HEADERS_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/asset_font_manager_skia.cc",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"txt/asset_font_manager_skia.h\"\n\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n#include \"third_party/skia/include/core/SkString.h\"\n#include \"third_party/skia/include/core/SkTypeface.h\"\n\nnamespace txt {\n\nAssetFontManager::AssetFontManager(\n    std::unique_ptr<FontAssetProvider> font_provider)\n    : font_provider_(std::move(font_provider)) {\n  FML_DCHECK(font_provider_ != nullptr);\n}\n\nAssetFontManager::~AssetFontManager() = default;\n\nint AssetFontManager::onCountFamilies() const {\n  return font_provider_->GetFamilyCount();\n}\n\nvoid AssetFontManager::onGetFamilyName(int index, SkString* familyName) const {\n  familyName->set(font_provider_->GetFamilyName(index).c_str());\n}\n\nsk_sp<SkFontStyleSet> AssetFontManager::onCreateStyleSet(int index) const {\n  FML_DCHECK(false);\n  return nullptr;\n}\n\nsk_sp<SkFontStyleSet> AssetFontManager::onMatchFamily(\n    const char family_name_string[]) const {\n  std::string family_name(family_name_string);\n  return font_provider_->MatchFamily(family_name);\n}\n\nsk_sp<SkTypeface> AssetFontManager::onMatchFamilyStyle(\n    const char familyName[],\n    const SkFontStyle& style) const {\n  sk_sp<SkFontStyleSet> font_style_set =\n      font_provider_->MatchFamily(std::string(familyName));\n  if (font_style_set == nullptr)\n    return nullptr;\n  return font_style_set->matchStyle(style);\n}\n\nsk_sp<SkTypeface> AssetFontManager::onMatchFamilyStyleCharacter(\n    const char familyName[],\n    const SkFontStyle&,\n    const char* bcp47[],\n    int bcp47Count,\n    SkUnichar character) const {\n  return nullptr;\n}\n\nsk_sp<SkTypeface> AssetFontManager::onMakeFromData(sk_sp<SkData>,\n                                                   int ttcIndex) const {\n  FML_DCHECK(false);\n  return nullptr;\n}\n\nsk_sp<SkTypeface> AssetFontManager::onMakeFromStreamIndex(\n    std::unique_ptr<SkStreamAsset>,\n    int ttcIndex) const {\n  FML_DCHECK(false);\n  return nullptr;\n}\n\nsk_sp<SkTypeface> AssetFontManager::onMakeFromStreamArgs(\n    std::unique_ptr<SkStreamAsset>,\n    const SkFontArguments&) const {\n  FML_DCHECK(false);\n  return nullptr;\n}\n\nsk_sp<SkTypeface> AssetFontManager::onMakeFromFile(const char path[],\n                                                   int ttcIndex) const {\n  FML_DCHECK(false);\n  return nullptr;\n}\n\nsk_sp<SkTypeface> AssetFontManager::onLegacyMakeTypeface(\n    const char familyName[],\n    SkFontStyle) const {\n  return nullptr;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/asset_font_manager_skia.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_ASSET_FONT_MANAGER_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_ASSET_FONT_MANAGER_H_\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"third_party/skia/include/core/SkFontMgr.h\"\n#include \"third_party/skia/include/core/SkStream.h\"\n#include \"txt/font_asset_provider.h\"\n#include \"txt/typeface_font_asset_provider_skia.h\"\n\nnamespace txt {\n\nclass AssetFontManager : public SkFontMgr {\n public:\n  explicit AssetFontManager(std::unique_ptr<FontAssetProvider> font_provider);\n\n  ~AssetFontManager() override;\n\n protected:\n  // |SkFontMgr|\n  sk_sp<SkFontStyleSet> onMatchFamily(const char familyName[]) const override;\n\n  std::unique_ptr<FontAssetProvider> font_provider_;\n\n private:\n  // |SkFontMgr|\n  int onCountFamilies() const override;\n\n  // |SkFontMgr|\n  void onGetFamilyName(int index, SkString* familyName) const override;\n\n  // |SkFontMgr|\n  sk_sp<SkFontStyleSet> onCreateStyleSet(int index) const override;\n\n  // |SkFontMgr|\n  sk_sp<SkTypeface> onMatchFamilyStyle(const char familyName[],\n                                       const SkFontStyle&) const override;\n\n  // |SkFontMgr|\n  sk_sp<SkTypeface> onMatchFamilyStyleCharacter(\n      const char familyName[],\n      const SkFontStyle&,\n      const char* bcp47[],\n      int bcp47Count,\n      SkUnichar character) const override;\n\n  // |SkFontMgr|\n  sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override;\n\n  // |SkFontMgr|\n  sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,\n                                          int ttcIndex) const override;\n\n  // |SkFontMgr|\n  sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,\n                                         const SkFontArguments&) const override;\n\n  // |SkFontMgr|\n  sk_sp<SkTypeface> onMakeFromFile(const char path[],\n                                   int ttcIndex) const override;\n\n  // |SkFontMgr|\n  sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[],\n                                         SkFontStyle) const override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(AssetFontManager);\n};\n\nclass DynamicFontManager : public AssetFontManager {\n public:\n  DynamicFontManager()\n      : AssetFontManager(std::make_unique<TypefaceFontAssetProvider>()) {}\n\n  TypefaceFontAssetProvider& font_provider() {\n    return static_cast<TypefaceFontAssetProvider&>(*font_provider_);\n  }\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_ASSET_FONT_MANAGER_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/asset_font_manager_skity.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"txt/asset_font_manager_skity.h\"\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"skity/text/typeface.hpp\"\n\nnamespace txt {\n\nAssetFontManager::AssetFontManager(\n    std::unique_ptr<FontAssetProvider> font_provider)\n    : font_provider_(std::move(font_provider)) {\n  FML_DCHECK(font_provider_ != nullptr);\n}\n\nAssetFontManager::~AssetFontManager() = default;\n\nint AssetFontManager::OnCountFamilies() const {\n  return font_provider_->GetFamilyCount();\n}\n\nstd::string AssetFontManager::OnGetFamilyName(int index) const {\n  return font_provider_->GetFamilyName(index);\n}\n\nstd::shared_ptr<skity::FontStyleSet> AssetFontManager::OnCreateStyleSet(int index) const {\n  FML_DCHECK(false);\n  return nullptr;\n}\n\nstd::shared_ptr<skity::FontStyleSet> AssetFontManager::OnMatchFamily(\n    const char familyName[]) const {\n  std::string family_name(familyName);\n  return font_provider_->MatchFamily(family_name);\n}\n\nstd::shared_ptr<skity::Typeface> AssetFontManager::OnMatchFamilyStyle(\n    const char familyName[],\n    const skity::FontStyle& style) const {\n  auto font_style_set =\n      font_provider_->MatchFamily(std::string(familyName));\n  if (font_style_set == nullptr) {\n    return nullptr;\n  }\n  return font_style_set->MatchStyle(style);\n}\n\nstd::shared_ptr<skity::Typeface> AssetFontManager::OnMatchFamilyStyleCharacter(\n    const char familyName[],\n    const skity::FontStyle&,\n    const char* bcp47[],\n    int bcp47Count,\n    skity::Unichar character) const {\n  return nullptr;\n}\n\nstd::shared_ptr<skity::Typeface> AssetFontManager::OnMakeFromData(\n    std::shared_ptr<skity::Data> const&,\n    int ttcIndex) const {\n  FML_DCHECK(false);\n  return nullptr;\n}\n\nstd::shared_ptr<skity::Typeface> AssetFontManager::OnMakeFromFile(\n    const char path[],\n    int ttcIndex) const {\n  FML_DCHECK(false);\n  return nullptr;\n}\n\nstd::shared_ptr<skity::Typeface> AssetFontManager::OnGetDefaultTypeface(\n    skity::FontStyle const& font_style) const {\n  return nullptr;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/asset_font_manager_skity.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_ASSET_FONT_MANAGER_SKITY_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_ASSET_FONT_MANAGER_SKITY_H_\n\n#include <memory>\n#include <string>\n\n#include \"skity/text/font_manager.hpp\"\n#include \"txt/typeface_font_asset_provider_skity.h\"\n\nnamespace txt {\n\nclass AssetFontManager : public skity::FontManager {\n public:\n  explicit AssetFontManager(std::unique_ptr<FontAssetProvider> font_provider);\n\n  ~AssetFontManager() override;\n\n protected:\n  std::shared_ptr<skity::FontStyleSet> OnMatchFamily(const char familyName[]) const override;\n\n  std::unique_ptr<FontAssetProvider> font_provider_;\n\n private:\n  int OnCountFamilies() const override;\n\n  std::string OnGetFamilyName(int index) const override;\n\n  std::shared_ptr<skity::FontStyleSet> OnCreateStyleSet(int index) const override;\n\n  std::shared_ptr<skity::Typeface> OnMatchFamilyStyle(const char familyName[],\n                                      const skity::FontStyle&) const override;\n\n  std::shared_ptr<skity::Typeface> OnMatchFamilyStyleCharacter(\n      const char familyName[],\n      const skity::FontStyle&,\n      const char* bcp47[],\n      int bcp47Count,\n      skity::Unichar character) const override;\n\n  std::shared_ptr<skity::Typeface> OnMakeFromData(\n      std::shared_ptr<skity::Data> const&,\n      int ttcIndex) const override;\n\n  std::shared_ptr<skity::Typeface> OnMakeFromFile(const char path[],\n                                                  int ttcIndex) const override;\n\n  std::shared_ptr<skity::Typeface> OnGetDefaultTypeface(\n      skity::FontStyle const& font_style) const override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(AssetFontManager);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_ASSET_FONT_MANAGER_SKITY_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_asset_provider.cc",
    "content": "/*\n * Copyright 2018 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <algorithm>\n#include <string>\n\n#include \"txt/font_asset_provider.h\"\n\nnamespace txt {\n\n// Return a canonicalized version of a family name that is suitable for\n// matching.\nstd::string FontAssetProvider::CanonicalFamilyName(std::string family_name) {\n  std::string result(family_name.length(), 0);\n\n  // Convert ASCII characters to lower case.\n  std::transform(family_name.begin(), family_name.end(), result.begin(),\n                 [](char c) { return (c & 0x80) ? c : ::tolower(c); });\n\n  return result;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_asset_provider.h",
    "content": "/*\n * Copyright 2018 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_ASSET_PROVIDER_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_ASSET_PROVIDER_H_\n\n#include <string>\n\n#ifdef ENABLE_SKITY\n#include \"skity/text/font_manager.hpp\"\n#else\n#include \"third_party/skia/include/core/SkFontMgr.h\"\n#endif\n\nnamespace txt {\n\nclass FontAssetProvider {\n public:\n  virtual ~FontAssetProvider() = default;\n\n  virtual size_t GetFamilyCount() const = 0;\n  virtual std::string GetFamilyName(int index) const = 0;\n#ifdef ENABLE_SKITY\n  virtual std::shared_ptr<skity::FontStyleSet> MatchFamily(const std::string& family_name) = 0;\n#else\n  virtual sk_sp<SkFontStyleSet> MatchFamily(const std::string& family_name) = 0;\n#endif\n\n protected:\n  static std::string CanonicalFamilyName(std::string family_name);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_ASSET_PROVIDER_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_asset_provider_skity.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_ASSET_PROVIDER_SKITY_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_ASSET_PROVIDER_SKITY_H_\n\n#include <string>\n#include \"skity/text/font_manager.hpp\"\n\nnamespace txt {\n\nclass FontAssetProvider {\n public:\n  virtual ~FontAssetProvider() = default;\n\n  virtual size_t GetFamilyCount() const = 0;\n  virtual std::string GetFamilyName(int index) const = 0;\n  virtual std::shared_ptr<skity::FontStyleSet> MatchFamily(const std::string& family_name) = 0;\n\n protected:\n  static std::string CanonicalFamilyName(std::string family_name);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_ASSET_PROVIDER_SKITY_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_collection_skia.cc",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"font_collection_skia.h\"\n\n#include <algorithm>\n#include <list>\n#include <memory>\n#include <mutex>\n#include <set>\n#include <string>\n#include <unordered_map>\n#include <vector>\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#if defined(CLAY_ENABLE_MINIKIN)\n#include \"font_skia.h\"\n#endif\n#include \"txt/platform.h\"\n#include \"txt/text_style.h\"\n\nnamespace txt {\n\nnamespace {\n#if defined(CLAY_ENABLE_MINIKIN)\nconst std::shared_ptr<minikin::FontFamily> g_null_family;\n#endif  // CLAY_ENABLE_SKSHAPER\n\n}  // anonymous namespace\n\nFontCollection::FamilyKey::FamilyKey(const std::vector<std::string>& families,\n                                     const std::string& loc) {\n  locale = loc;\n\n  std::stringstream stream;\n  for_each(families.begin(), families.end(),\n           [&stream](const std::string& str) { stream << str << ','; });\n  font_families = stream.str();\n}\n\nbool FontCollection::FamilyKey::operator==(\n    const FontCollection::FamilyKey& other) const {\n  return font_families == other.font_families && locale == other.locale;\n}\n\nsize_t FontCollection::FamilyKey::Hasher::operator()(\n    const FontCollection::FamilyKey& key) const {\n  return std::hash<std::string>()(key.font_families) ^\n         std::hash<std::string>()(key.locale);\n}\n\nFontCollection::FontCollection() : enable_font_fallback_(true) {}\n\nFontCollection::~FontCollection() {\n#if CLAY_ENABLE_MINIKIN\n  minikin::Layout::purgeCaches();\n#endif  // CLAY_ENABLE_SKSHAPER\n\n#if CLAY_ENABLE_SKSHAPER\n  if (skt_collection_) {\n    skt_collection_->clearCaches();\n  }\n#endif  // CLAY_ENABLE_SKSHAPER\n}\n\nsize_t FontCollection::GetFontManagersCount() const {\n  return GetFontManagerOrder().size();\n}\n\nvoid FontCollection::SetupDefaultFontManager(\n    uint32_t font_initialization_data) {\n  default_font_manager_ = GetDefaultFontManager(font_initialization_data);\n}\n\nvoid FontCollection::SetDefaultFontManager(sk_sp<SkFontMgr> font_manager) {\n  default_font_manager_ = font_manager;\n\n#if CLAY_ENABLE_SKSHAPER\n  skt_collection_.reset();\n#endif\n}\n\nvoid FontCollection::SetAssetFontManager(sk_sp<SkFontMgr> font_manager) {\n  asset_font_manager_ = font_manager;\n\n#if CLAY_ENABLE_SKSHAPER\n  skt_collection_.reset();\n#endif\n}\n\nvoid FontCollection::SetDynamicFontManager(sk_sp<SkFontMgr> font_manager) {\n  dynamic_font_manager_ = font_manager;\n\n#if CLAY_ENABLE_SKSHAPER\n  skt_collection_.reset();\n#endif\n}\n\nvoid FontCollection::SetTestFontManager(sk_sp<SkFontMgr> font_manager) {\n  test_font_manager_ = font_manager;\n\n#if CLAY_ENABLE_SKSHAPER\n  skt_collection_.reset();\n#endif\n}\n\n// Return the available font managers in the order they should be queried.\nstd::vector<sk_sp<SkFontMgr>> FontCollection::GetFontManagerOrder() const {\n  std::vector<sk_sp<SkFontMgr>> order;\n  if (dynamic_font_manager_)\n    order.push_back(dynamic_font_manager_);\n  if (asset_font_manager_)\n    order.push_back(asset_font_manager_);\n  if (test_font_manager_)\n    order.push_back(test_font_manager_);\n  if (default_font_manager_)\n    order.push_back(default_font_manager_);\n  return order;\n}\n\nvoid FontCollection::DisableFontFallback() {\n  enable_font_fallback_ = false;\n\n#if CLAY_ENABLE_SKSHAPER\n  if (skt_collection_) {\n    skt_collection_->disableFontFallback();\n  }\n#endif\n}\n\nvoid FontCollection::SortSkTypefaces(\n    std::vector<sk_sp<SkTypeface>>& sk_typefaces) {\n  std::sort(\n      sk_typefaces.begin(), sk_typefaces.end(),\n      [](const sk_sp<SkTypeface>& a, const sk_sp<SkTypeface>& b) {\n        SkFontStyle a_style = a->fontStyle();\n        SkFontStyle b_style = b->fontStyle();\n\n        int a_delta = std::abs(a_style.width() - SkFontStyle::kNormal_Width);\n        int b_delta = std::abs(b_style.width() - SkFontStyle::kNormal_Width);\n\n        if (a_delta != b_delta) {\n          // If a family name query is so generic it ends up bringing in fonts\n          // of multiple widths (e.g. condensed, expanded), opt to be\n          // conservative and select the most standard width.\n          //\n          // If a specific width is desired, it should be be narrowed down via\n          // the family name.\n          //\n          // The font weights are also sorted lightest to heaviest but Flutter\n          // APIs have the weight specified to narrow it down later. The width\n          // ordering here is more consequential since TextStyle doesn't have\n          // letter width APIs.\n          return a_delta < b_delta;\n        } else if (a_style.width() != b_style.width()) {\n          // However, if the 2 fonts are equidistant from the \"normal\" width,\n          // just arbitrarily but consistently return the more condensed font.\n          return a_style.width() < b_style.width();\n        } else if (a_style.weight() != b_style.weight()) {\n          return a_style.weight() < b_style.weight();\n        } else {\n          return a_style.slant() < b_style.slant();\n        }\n        // Use a cascade of conditions so results are consistent each time.\n      });\n}\n\n#if defined(CLAY_ENABLE_MINIKIN)\nclass TxtFallbackFontProvider\n    : public minikin::FontCollection::FallbackFontProvider {\n public:\n  TxtFallbackFontProvider(std::shared_ptr<FontCollection> font_collection)\n      : font_collection_(font_collection) {}\n\n  virtual const std::shared_ptr<minikin::FontFamily>& matchFallbackFont(\n      uint32_t ch,\n      std::string locale) {\n    std::shared_ptr<FontCollection> fc = font_collection_.lock();\n    if (fc) {\n      return fc->MatchFallbackFont(ch, locale);\n    } else {\n      return g_null_family;\n    }\n  }\n\n private:\n  std::weak_ptr<FontCollection> font_collection_;\n};\n\nstd::shared_ptr<minikin::FontCollection>\nFontCollection::GetMinikinFontCollectionForFamilies(\n    const std::vector<std::string>& font_families,\n    const std::string& locale) {\n  // Look inside the font collections cache first.\n  FamilyKey family_key(font_families, locale);\n  auto cached = font_collections_cache_.find(family_key);\n  if (cached != font_collections_cache_.end()) {\n    return cached->second;\n  }\n\n  std::vector<std::shared_ptr<minikin::FontFamily>> minikin_families;\n\n  // Search for all user provided font families.\n  for (size_t fallback_index = 0; fallback_index < font_families.size();\n       fallback_index++) {\n    std::shared_ptr<minikin::FontFamily> minikin_family =\n        FindFontFamilyInManagers(font_families[fallback_index]);\n    if (minikin_family != nullptr) {\n      minikin_families.push_back(minikin_family);\n    }\n  }\n  // Search for default font family if no user font families were found.\n  if (minikin_families.empty()) {\n    const auto default_font_families = GetDefaultFontFamilies();\n    for (const auto& family : default_font_families) {\n      std::shared_ptr<minikin::FontFamily> minikin_family =\n          FindFontFamilyInManagers(family);\n      if (minikin_family != nullptr) {\n        minikin_families.push_back(minikin_family);\n        break;\n      }\n    }\n  }\n  // Default font family also not found. We fail to get a FontCollection.\n  if (minikin_families.empty()) {\n    font_collections_cache_[family_key] = nullptr;\n    return nullptr;\n  }\n  if (enable_font_fallback_) {\n    for (const std::string& fallback_family :\n         fallback_fonts_for_locale_[locale]) {\n      auto it = fallback_fonts_.find(fallback_family);\n      if (it != fallback_fonts_.end()) {\n        minikin_families.push_back(it->second);\n      }\n    }\n  }\n  // Create the minikin font collection.\n  auto font_collection =\n      minikin::FontCollection::Create(std::move(minikin_families));\n  if (!font_collection) {\n    font_collections_cache_[family_key] = nullptr;\n    return nullptr;\n  }\n  if (enable_font_fallback_) {\n    font_collection->set_fallback_font_provider(\n        std::make_unique<TxtFallbackFontProvider>(shared_from_this()));\n  }\n\n  // Cache the font collection for future queries.\n  font_collections_cache_[family_key] = font_collection;\n\n  return font_collection;\n}\n\nstd::shared_ptr<minikin::FontFamily> FontCollection::FindFontFamilyInManagers(\n    const std::string& family_name) {\n  TRACE_EVENT(\"flutter\", \"FontCollection::FindFontFamilyInManagers\");\n  // Search for the font family in each font manager.\n  for (sk_sp<SkFontMgr>& manager : GetFontManagerOrder()) {\n    std::shared_ptr<minikin::FontFamily> minikin_family =\n        CreateMinikinFontFamily(manager, family_name);\n    if (!minikin_family)\n      continue;\n    return minikin_family;\n  }\n  return nullptr;\n}\n\nstd::shared_ptr<minikin::FontFamily> FontCollection::CreateMinikinFontFamily(\n    const sk_sp<SkFontMgr>& manager,\n    const std::string& family_name) {\n  TRACE_EVENT(\"flutter\", \"FontCollection::CreateMinikinFontFamily\",\n              \"family_name\", family_name.c_str());\n  sk_sp<SkFontStyleSet> font_style_set(\n      manager->matchFamily(family_name.c_str()));\n  if (font_style_set == nullptr || font_style_set->count() == 0) {\n    return nullptr;\n  }\n\n  std::vector<sk_sp<SkTypeface>> skia_typefaces;\n  for (int i = 0; i < font_style_set->count(); ++i) {\n    TRACE_EVENT(\"flutter\", \"CreateSkiaTypeface\");\n    sk_sp<SkTypeface> skia_typeface(\n        sk_sp<SkTypeface>(font_style_set->createTypeface(i)));\n    if (skia_typeface != nullptr) {\n      skia_typefaces.emplace_back(std::move(skia_typeface));\n    }\n  }\n\n  if (skia_typefaces.empty()) {\n    return nullptr;\n  }\n\n  SortSkTypefaces(skia_typefaces);\n\n  std::vector<minikin::Font> minikin_fonts;\n  for (const sk_sp<SkTypeface>& skia_typeface : skia_typefaces) {\n    // Create the minikin font from the skia typeface.\n    // Divide by 100 because the weights are given as \"100\", \"200\", etc.\n    minikin_fonts.emplace_back(\n        std::make_shared<FontSkia>(skia_typeface),\n        minikin::FontStyle{skia_typeface->fontStyle().weight() / 100,\n                           skia_typeface->isItalic()});\n  }\n\n  return std::make_shared<minikin::FontFamily>(std::move(minikin_fonts));\n}\n\nconst std::shared_ptr<minikin::FontFamily>& FontCollection::MatchFallbackFont(\n    uint32_t ch,\n    std::string locale) {\n  // Check if the ch's matched font has been cached. We cache the results of\n  // this method as repeated matchFamilyStyleCharacter calls can become\n  // extremely laggy when typing a large number of complex emojis.\n  auto lookup = fallback_match_cache_.find(ch);\n  if (lookup != fallback_match_cache_.end()) {\n    return *lookup->second;\n  }\n  const std::shared_ptr<minikin::FontFamily>* match =\n      &DoMatchFallbackFont(ch, locale);\n  fallback_match_cache_.insert(std::make_pair(ch, match));\n  return *match;\n}\n\nconst std::shared_ptr<minikin::FontFamily>& FontCollection::DoMatchFallbackFont(\n    uint32_t ch,\n    std::string locale) {\n  for (const sk_sp<SkFontMgr>& manager : GetFontManagerOrder()) {\n    std::vector<const char*> bcp47;\n    if (!locale.empty())\n      bcp47.push_back(locale.c_str());\n    sk_sp<SkTypeface> typeface(manager->matchFamilyStyleCharacter(\n        0, SkFontStyle(), bcp47.data(), bcp47.size(), ch));\n    if (!typeface)\n      continue;\n\n    SkString sk_family_name;\n    typeface->getFamilyName(&sk_family_name);\n    std::string family_name(sk_family_name.c_str());\n\n    if (std::find(fallback_fonts_for_locale_[locale].begin(),\n                  fallback_fonts_for_locale_[locale].end(),\n                  family_name) == fallback_fonts_for_locale_[locale].end())\n      fallback_fonts_for_locale_[locale].push_back(family_name);\n\n    return GetFallbackFontFamily(manager, family_name);\n  }\n  return g_null_family;\n}\n\nconst std::shared_ptr<minikin::FontFamily>&\nFontCollection::GetFallbackFontFamily(const sk_sp<SkFontMgr>& manager,\n                                      const std::string& family_name) {\n  TRACE_EVENT(\"flutter\", \"FontCollection::GetFallbackFontFamily\");\n  auto fallback_it = fallback_fonts_.find(family_name);\n  if (fallback_it != fallback_fonts_.end()) {\n    return fallback_it->second;\n  }\n\n  std::shared_ptr<minikin::FontFamily> minikin_family =\n      CreateMinikinFontFamily(manager, family_name);\n  if (!minikin_family)\n    return g_null_family;\n\n  auto insert_it =\n      fallback_fonts_.insert(std::make_pair(family_name, minikin_family));\n\n  // Clear the cache to force creation of new font collections that will\n  // include this fallback font.\n  font_collections_cache_.clear();\n\n  return insert_it.first->second;\n}\n\n#endif  // CLAY_ENABLE_SKSHAPER\n\nvoid FontCollection::ClearFontFamilyCache() {\n#if defined(CLAY_ENABLE_MINIKIN)\n  font_collections_cache_.clear();\n#endif  // CLAY_ENABLE_MINIKIN\n\n#if defined(CLAY_ENABLE_SKSHAPER) || defined(CLAY_ENABLE_TTTEXT)\n  if (skt_collection_) {\n    skt_collection_->clearCaches();\n  }\n#endif  // CLAY_ENABLE_SKSHAPER\n}\n\n#if defined(CLAY_ENABLE_TTTEXT)\n\nstd::shared_ptr<ttoffice::tttext::FontmgrCollection>\nFontCollection::CreateTTFontCollection() {\n  if (!skt_collection_) {\n    skt_collection_ = std::make_shared<ttoffice::tttext::FontmgrCollection>();\n\n    if (default_font_manager_ != nullptr) {\n      skt_collection_->SetDefaultFontManager(\n          std::make_shared<ttoffice::tttext::SkiaFontManager>(\n              default_font_manager_));\n    }\n    if (asset_font_manager_ != nullptr) {\n      skt_collection_->SetAssetFontManager(\n          std::make_shared<ttoffice::tttext::SkiaFontManager>(\n              asset_font_manager_));\n    }\n    if (dynamic_font_manager_ != nullptr) {\n      skt_collection_->SetDynamicFontManager(\n          std::make_shared<ttoffice::tttext::SkiaFontManager>(\n              dynamic_font_manager_));\n    }\n    if (test_font_manager_ != nullptr) {\n      skt_collection_->SetTestFontManager(\n          std::make_shared<ttoffice::tttext::SkiaFontManager>(\n              test_font_manager_));\n    }\n    if (!enable_font_fallback_) {\n      skt_collection_->disableFontFallback();\n    }\n  }\n\n  return skt_collection_;\n}\n\n#endif  // CLAY_ENABLE_SKSHAPER\n\n#if defined(CLAY_ENABLE_SKSHAPER)\n\nsk_sp<skia::textlayout::FontCollection>\nFontCollection::CreateSktFontCollection() {\n  if (!skt_collection_) {\n    skt_collection_ = sk_make_sp<skia::textlayout::FontCollection>();\n\n    std::vector<SkString> default_font_families;\n    for (const std::string& family : GetDefaultFontFamilies()) {\n      default_font_families.emplace_back(family);\n    }\n    skt_collection_->setDefaultFontManager(default_font_manager_,\n                                           default_font_families);\n    skt_collection_->setAssetFontManager(asset_font_manager_);\n    skt_collection_->setDynamicFontManager(dynamic_font_manager_);\n    skt_collection_->setTestFontManager(test_font_manager_);\n    if (!enable_font_fallback_) {\n      skt_collection_->disableFontFallback();\n    }\n  }\n\n  return skt_collection_;\n}\n\n#endif  // CLAY_ENABLE_SKSHAPER\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_collection_skia.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_COLLECTION_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_COLLECTION_H_\n\n#include <memory>\n#include <set>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n#include \"third_party/skia/include/core/SkFontMgr.h\"\n#include \"third_party/skia/include/core/SkRefCnt.h\"\n#include \"txt/asset_font_manager_skia.h\"\n#include \"txt/text_style.h\"\n\n#if CLAY_ENABLE_MINIKIN\n#include \"minikin/FontCollection.h\"\n#include \"minikin/FontFamily.h\"\n#include \"minikin/Layout.h\"\n#endif\n\n#if CLAY_ENABLE_SKSHAPER\n#include \"third_party/skia/modules/skparagraph/include/FontCollection.h\"  // nogncheck\n#elif defined(CLAY_ENABLE_TTTEXT)\n#include \"third_party/textlayout/textra/public/textra/fontmgr_collection.h\"  // nogncheck\n#include \"third_party/textlayout/textra/public/textra/platform/skia/skia_font_manager.h\"\n#endif\n\nnamespace txt {\n\nclass FontCollection : public std::enable_shared_from_this<FontCollection> {\n public:\n  FontCollection();\n\n  ~FontCollection();\n\n  size_t GetFontManagersCount() const;\n\n  void SetupDefaultFontManager(uint32_t font_initialization_data);\n  void SetDefaultFontManager(sk_sp<SkFontMgr> font_manager);\n  void SetAssetFontManager(sk_sp<SkFontMgr> font_manager);\n  void SetDynamicFontManager(sk_sp<SkFontMgr> font_manager);\n  void SetTestFontManager(sk_sp<SkFontMgr> font_manager);\n\n#if defined(CLAY_ENABLE_MINIKIN)\n  std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForFamilies(\n      const std::vector<std::string>& font_families,\n      const std::string& locale);\n\n  // Provides a FontFamily that contains glyphs for ch. This caches previously\n  // matched fonts. Also see FontCollection::DoMatchFallbackFont.\n  const std::shared_ptr<minikin::FontFamily>& MatchFallbackFont(\n      uint32_t ch,\n      std::string locale);\n#endif\n\n  // Do not provide alternative fonts that can match characters which are\n  // missing from the requested font family.\n  void DisableFontFallback();\n\n  // Remove all entries in the font family cache.\n  void ClearFontFamilyCache();\n\n#if defined(CLAY_ENABLE_TTTEXT)\n\n  // Construct a Skia text layout FontCollection based on this collection.\n  std::shared_ptr<ttoffice::tttext::FontmgrCollection> CreateTTFontCollection();\n\n#endif  // CLAY_ENABLE_SKSHAPER\n\n#if defined(CLAY_ENABLE_SKSHAPER)\n\n  // Construct a Skia text layout FontCollection based on this collection.\n  sk_sp<skia::textlayout::FontCollection> CreateSktFontCollection();\n\n#endif  // CLAY_ENABLE_SKSHAPER\n\n private:\n  struct FamilyKey {\n    FamilyKey(const std::vector<std::string>& families, const std::string& loc);\n\n    // Concatenated string with all font families.\n    std::string font_families;\n    std::string locale;\n\n    bool operator==(const FamilyKey& other) const;\n\n    struct Hasher {\n      size_t operator()(const FamilyKey& key) const;\n    };\n  };\n\n  sk_sp<SkFontMgr> default_font_manager_;\n  sk_sp<SkFontMgr> asset_font_manager_;\n  sk_sp<SkFontMgr> dynamic_font_manager_;\n  sk_sp<SkFontMgr> test_font_manager_;\n\n#if defined(CLAY_ENABLE_MINIKIN)\n  std::unordered_map<FamilyKey,\n                     std::shared_ptr<minikin::FontCollection>,\n                     FamilyKey::Hasher>\n      font_collections_cache_;\n  // Cache that stores the results of MatchFallbackFont to ensure lag-free emoji\n  // font fallback matching.\n  std::unordered_map<uint32_t, const std::shared_ptr<minikin::FontFamily>*>\n      fallback_match_cache_;\n  std::unordered_map<std::string, std::shared_ptr<minikin::FontFamily>>\n      fallback_fonts_;\n#endif\n\n  std::unordered_map<std::string, std::vector<std::string>>\n      fallback_fonts_for_locale_;\n  bool enable_font_fallback_;\n\n#if defined(CLAY_ENABLE_TTTEXT)\n  // An equivalent font collection usable by the Skia text shaper library.\n  std::shared_ptr<ttoffice::tttext::FontmgrCollection> skt_collection_;\n#endif\n\n#if defined(CLAY_ENABLE_SKSHAPER)\n  // An equivalent font collection usable by the Skia text shaper library.\n  sk_sp<skia::textlayout::FontCollection> skt_collection_;\n#endif\n\n#if defined(CLAY_ENABLE_MINIKIN)\n  // Performs the actual work of MatchFallbackFont. The result is cached in\n  // fallback_match_cache_.\n  const std::shared_ptr<minikin::FontFamily>& DoMatchFallbackFont(\n      uint32_t ch,\n      std::string locale);\n\n  std::shared_ptr<minikin::FontFamily> FindFontFamilyInManagers(\n      const std::string& family_name);\n\n  std::shared_ptr<minikin::FontFamily> CreateMinikinFontFamily(\n      const sk_sp<SkFontMgr>& manager,\n      const std::string& family_name);\n\n  const std::shared_ptr<minikin::FontFamily>& GetFallbackFontFamily(\n      const sk_sp<SkFontMgr>& manager,\n      const std::string& family_name);\n#endif\n\n  std::vector<sk_sp<SkFontMgr>> GetFontManagerOrder() const;\n  // Sorts in-place a group of SkTypeface from an SkTypefaceSet into a\n  // reasonable order for future queries.\n  FRIEND_TEST(FontCollectionTest, CheckSkTypefacesSorting);\n  static void SortSkTypefaces(std::vector<sk_sp<SkTypeface>>& sk_typefaces);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(FontCollection);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_COLLECTION_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_collection_skity.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"font_collection_skity.h\"\n\n#include <algorithm>\n#include <cassert>\n#include <list>\n#include <memory>\n#include <set>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"txt/platform.h\"\n#include \"txt/text_style.h\"\n\nnamespace txt {\n\nFontCollection::FamilyKey::FamilyKey(const std::vector<std::string>& families,\n                                     const std::string& loc) {\n  locale = loc;\n\n  std::stringstream stream;\n  for_each(families.begin(), families.end(),\n           [&stream](const std::string& str) { stream << str << ','; });\n  font_families = stream.str();\n}\n\nbool FontCollection::FamilyKey::operator==(\n    const FontCollection::FamilyKey& other) const {\n  return font_families == other.font_families && locale == other.locale;\n}\n\nsize_t FontCollection::FamilyKey::Hasher::operator()(\n    const FontCollection::FamilyKey& key) const {\n  return std::hash<std::string>()(key.font_families) ^\n         std::hash<std::string>()(key.locale);\n}\n\nFontCollection::FontCollection() : enable_font_fallback_(true) {}\n\nFontCollection::~FontCollection() {}\n\nsize_t FontCollection::GetFontManagersCount() const {\n  return GetFontManagerOrder().size();\n}\n\nvoid FontCollection::SetupDefaultFontManager(\n    uint32_t font_initialization_data) {\n  default_font_manager_ = GetDefaultFontManager(font_initialization_data);\n}\n\nvoid FontCollection::SetDefaultFontManager(\n    std::shared_ptr<skity::FontManager> font_manager) {\n  default_font_manager_ = std::move(font_manager);\n}\n\nvoid FontCollection::SetAssetFontManager(\n    std::shared_ptr<skity::FontManager> font_manager) {\n  asset_font_manager_ = std::move(font_manager);\n}\n\n// Return the available font managers in the order they should be queried.\nstd::vector<std::shared_ptr<skity::FontManager>>\nFontCollection::GetFontManagerOrder() const {\n  std::vector<std::shared_ptr<skity::FontManager>> order;\n  if (default_font_manager_) {\n    order.push_back(default_font_manager_);\n  }\n  if (asset_font_manager_) {\n    order.push_back(asset_font_manager_);\n  }\n  return order;\n}\n\nvoid FontCollection::DisableFontFallback() {\n  enable_font_fallback_ = false;\n}\n\nvoid FontCollection::ClearFontFamilyCache() {}\n\ntttext::FontmgrCollection FontCollection::GetIFontCollection() {\n  tttext::FontmgrCollection collection(nullptr);\n\n  assert(default_font_manager_ != nullptr);\n  if (default_font_manager_ != nullptr) {\n#if OS_IOS\n    collection.SetDefaultFontManager(\n        std::make_shared<tttext::SkityFontManagerCoreText>());\n#else\n    collection.SetDefaultFontManager(\n        std::make_shared<tttext::SkityFontManager>());\n#endif\n  }\n  if (asset_font_manager_ != nullptr) {\n#if OS_IOS\n    collection.SetAssetFontManager(\n        std::make_shared<tttext::SkityFontManagerCoreText>(\n            asset_font_manager_));\n#else\n    collection.SetAssetFontManager(\n        std::make_shared<tttext::SkityFontManager>(asset_font_manager_));\n#endif\n  }\n  return collection;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_collection_skity.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_COLLECTION_SKITY_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_COLLECTION_SKITY_H_\n\n#include <memory>\n#include <set>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n#include \"tttext/tttext_headers.h\"\n#include \"txt/asset_font_manager_skity.h\"\n#include \"txt/text_style.h\"\n\nnamespace txt {\n\n// Macro Notice: enable_skity implies enable enable_tttext\nclass FontCollection : public std::enable_shared_from_this<FontCollection> {\n public:\n  FontCollection();\n\n  ~FontCollection();\n\n  size_t GetFontManagersCount() const;\n\n  void SetupDefaultFontManager(uint32_t font_initialization_data);\n  void SetDefaultFontManager(std::shared_ptr<skity::FontManager> font_manager);\n  void SetAssetFontManager(std::shared_ptr<skity::FontManager> font_manager);\n  // void SetDynamicFontManager(sk_sp<SkFontMgr> font_manager);\n  // void SetTestFontManager(sk_sp<SkFontMgr> font_manager);\n\n  // Do not provide alternative fonts that can match characters which are\n  // missing from the requested font family.\n  void DisableFontFallback();\n\n  // Remove all entries in the font family cache.\n  void ClearFontFamilyCache();\n\n  // Construct a Skia text layout FontCollection based on this collection.\n  tttext::FontmgrCollection GetIFontCollection();\n\n private:\n  struct FamilyKey {\n    FamilyKey(const std::vector<std::string>& families, const std::string& loc);\n\n    // Concatenated string with all font families.\n    std::string font_families;\n    std::string locale;\n\n    bool operator==(const FamilyKey& other) const;\n\n    struct Hasher {\n      size_t operator()(const FamilyKey& key) const;\n    };\n  };\n\n  std::shared_ptr<skity::FontManager> default_font_manager_;\n  std::shared_ptr<skity::FontManager> asset_font_manager_;\n  // sk_sp<SkFontMgr> dynamic_font_manager_;\n  // sk_sp<SkFontMgr> test_font_manager_;\n\n  std::unordered_map<std::string, std::vector<std::string>>\n      fallback_fonts_for_locale_;\n  bool enable_font_fallback_;\n\n  std::vector<std::shared_ptr<skity::FontManager>> GetFontManagerOrder() const;\n  // Sorts in-place a group of SkTypeface from an SkTypefaceSet into a\n  // reasonable order for future queries.\n  FRIEND_TEST(FontCollectionTest, CheckSkTypefacesSorting);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(FontCollection);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_COLLECTION_SKITY_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_features.cc",
    "content": "/*\n * Copyright 2019 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"font_features.h\"\n\n#include <sstream>\n\nnamespace txt {\n\nvoid FontFeatures::SetFeature(std::string tag, int value) {\n  feature_map_[tag] = value;\n}\n\nstd::string FontFeatures::GetFeatureSettings() const {\n  if (feature_map_.empty())\n    return \"\";\n\n  std::ostringstream stream;\n\n  for (const auto& kv : feature_map_) {\n    if (stream.tellp()) {\n      stream << ',';\n    }\n    stream << kv.first << '=' << kv.second;\n  }\n\n  return stream.str();\n}\n\nconst std::map<std::string, int>& FontFeatures::GetFontFeatures() const {\n  return feature_map_;\n}\n\nvoid FontVariations::SetAxisValue(std::string tag, float value) {\n  axis_map_[tag] = value;\n}\n\nconst std::map<std::string, float>& FontVariations::GetAxisValues() const {\n  return axis_map_;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_features.h",
    "content": "/*\n * Copyright 2019 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_FEATURES_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_FEATURES_H_\n\n#include <map>\n#include <string>\n#include <vector>\n\nnamespace txt {\n\n// Feature tags that can be applied in a text style to control how a font\n// selects glyphs.\nclass FontFeatures {\n public:\n  void SetFeature(std::string tag, int value);\n\n  std::string GetFeatureSettings() const;\n\n  const std::map<std::string, int>& GetFontFeatures() const;\n\n private:\n  std::map<std::string, int> feature_map_;\n};\n\n// Axis tags and values that can be applied in a text style to control the\n// attributes of variable fonts.\nclass FontVariations {\n public:\n  void SetAxisValue(std::string tag, float value);\n\n  const std::map<std::string, float>& GetAxisValues() const;\n\n private:\n  std::map<std::string, float> axis_map_;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_FEATURES_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_skia.cc",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"font_skia.h\"\n\n#include <minikin/MinikinFont.h>\n\n#include \"third_party/skia/include/core/SkFont.h\"\n\nnamespace txt {\nnamespace {\n\nhb_blob_t* GetTable(hb_face_t* face, hb_tag_t tag, void* context) {\n  SkTypeface* typeface = reinterpret_cast<SkTypeface*>(context);\n\n  const size_t table_size = typeface->getTableSize(tag);\n  if (table_size == 0)\n    return nullptr;\n  void* buffer = malloc(table_size);\n  if (buffer == nullptr)\n    return nullptr;\n\n  size_t actual_size = typeface->getTableData(tag, 0, table_size, buffer);\n  if (table_size != actual_size) {\n    free(buffer);\n    return nullptr;\n  }\n  return hb_blob_create(reinterpret_cast<char*>(buffer), table_size,\n                        HB_MEMORY_MODE_WRITABLE, buffer, free);\n}\n\n}  // namespace\n\nFontSkia::FontSkia(sk_sp<SkTypeface> typeface)\n    : MinikinFont(typeface->uniqueID()), typeface_(std::move(typeface)) {}\n\nFontSkia::~FontSkia() = default;\n\nstatic void FontSkia_SetSkiaFont(sk_sp<SkTypeface> typeface,\n                                 SkFont* skFont,\n                                 const minikin::MinikinPaint& paint) {\n  skFont->setTypeface(std::move(typeface));\n  skFont->setLinearMetrics((paint.paintFlags & minikin::LinearTextFlag) != 0);\n  // TODO: set more paint parameters from Minikin\n  skFont->setSize(paint.size);\n}\n\nfloat FontSkia::GetHorizontalAdvance(uint32_t glyph_id,\n                                     const minikin::MinikinPaint& paint) const {\n  SkFont skFont;\n  uint16_t glyph16 = glyph_id;\n  SkScalar skWidth;\n  FontSkia_SetSkiaFont(typeface_, &skFont, paint);\n  skFont.getWidths(&glyph16, 1, &skWidth);\n  return skWidth;\n}\n\nvoid FontSkia::GetBounds(minikin::MinikinRect* bounds,\n                         uint32_t glyph_id,\n                         const minikin::MinikinPaint& paint) const {\n  SkFont skFont;\n  uint16_t glyph16 = glyph_id;\n  SkRect skBounds;\n  FontSkia_SetSkiaFont(typeface_, &skFont, paint);\n  skFont.getWidths(&glyph16, 1, NULL, &skBounds);\n  bounds->mLeft = skBounds.fLeft;\n  bounds->mTop = skBounds.fTop;\n  bounds->mRight = skBounds.fRight;\n  bounds->mBottom = skBounds.fBottom;\n}\n\nhb_face_t* FontSkia::CreateHarfBuzzFace() const {\n  return hb_face_create_for_tables(GetTable, typeface_.get(), 0);\n}\n\nconst std::vector<minikin::FontVariation>& FontSkia::GetAxes() const {\n  return variations_;\n}\n\nconst sk_sp<SkTypeface>& FontSkia::GetSkTypeface() const {\n  return typeface_;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_skia.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_SKIA_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_SKIA_H_\n#include <minikin/MinikinFont.h>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"third_party/skia/include/core/SkPaint.h\"\n#include \"third_party/skia/include/core/SkTypeface.h\"\n\nnamespace txt {\n\nclass FontSkia : public minikin::MinikinFont {\n public:\n  explicit FontSkia(sk_sp<SkTypeface> typeface);\n\n  ~FontSkia();\n\n  float GetHorizontalAdvance(uint32_t glyph_id,\n                             const minikin::MinikinPaint& paint) const override;\n\n  void GetBounds(minikin::MinikinRect* bounds,\n                 uint32_t glyph_id,\n                 const minikin::MinikinPaint& paint) const override;\n\n  hb_face_t* CreateHarfBuzzFace() const override;\n\n  const std::vector<minikin::FontVariation>& GetAxes() const override;\n\n  const sk_sp<SkTypeface>& GetSkTypeface() const;\n\n private:\n  sk_sp<SkTypeface> typeface_;\n  std::vector<minikin::FontVariation> variations_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(FontSkia);\n};\n\n}  // namespace txt\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_SKIA_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_style.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_STYLE_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_STYLE_H_\n\nnamespace txt {\n\nenum class FontStyle {\n  normal,\n  italic,\n  oblique,\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_STYLE_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/font_weight.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_WEIGHT_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_WEIGHT_H_\n\nnamespace txt {\n\nenum class FontWeight {\n  w100,  // Thin\n  w200,  // Extra-Light\n  w300,  // Light\n  w400,  // Normal/Regular\n  w500,  // Medium\n  w600,  // Semi-bold\n  w700,  // Bold\n  w800,  // Extra-Bold\n  w900,  // Black\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_FONT_WEIGHT_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/line_metrics.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_LINE_METRICS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_LINE_METRICS_H_\n\n#include <map>\n#include <vector>\n\n#ifndef ENABLE_SKITY\n#include \"run_metrics.h\"\n#endif\n\nnamespace txt {\n\nclass LineMetrics {\n public:\n  // The following fields are used in the layout process itself.\n\n  // The indexes in the text buffer the line begins and ends.\n  size_t start_index = 0;\n  size_t end_index = 0;\n  size_t end_excluding_whitespace = 0;\n  size_t end_including_newline = 0;\n  bool hard_break = false;\n\n  // The following fields are tracked after or during layout to provide to\n  // the user as well as for computing bounding boxes.\n\n  // The final computed ascent and descent for the line. This can be impacted by\n  // the strut, height, scaling, as well as outlying runs that are very tall.\n  //\n  // The top edge is `baseline - ascent` and the bottom edge is `baseline +\n  // descent`. Ascent and descent are provided as positive numbers. Raw numbers\n  // for specific runs of text can be obtained in run_metrics_map. These values\n  // are the cumulative metrics for the entire line.\n  double ascent = 0.0;\n  double descent = 0.0;\n  double unscaled_ascent = 0.0;\n  // Total height of the paragraph including the current line.\n  //\n  // The height of the current line is `round(ascent + descent)`.\n  double height = 0.0;\n  // Width of the line.\n  double width = 0.0;\n  // The left edge of the line. The right edge can be obtained with `left +\n  // width`\n  double left = 0.0;\n  // The y position of the baseline for this line from the top of the paragraph.\n  double baseline = 0.0;\n  // Zero indexed line number.\n  size_t line_number = 0;\n\n#ifndef ENABLE_SKITY\n  // Mapping between text index ranges and the FontMetrics associated with\n  // them. The first run will be keyed under start_index. The metrics here\n  // are before layout and are the base values we calculate from.\n  std::map<size_t, RunMetrics> run_metrics;\n#endif\n\n  LineMetrics() = default;\n\n  LineMetrics(size_t start,\n              size_t end,\n              size_t end_excluding_whitespace,\n              size_t end_including_newline,\n              bool hard_break)\n      : start_index(start),\n        end_index(end),\n        end_excluding_whitespace(end_excluding_whitespace),\n        end_including_newline(end_including_newline),\n        hard_break(hard_break) {}\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_LINE_METRICS_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paint_record.cc",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"paint_record.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace txt {\n\nPaintRecord::~PaintRecord() = default;\n\n#ifndef ENABLE_SKITY\nPaintRecord::PaintRecord(TextStyle style,\n                         SkPoint offset,\n                         sk_sp<SkTextBlob> text,\n                         SkFontMetrics metrics,\n                         size_t line,\n                         double x_start,\n                         double x_end,\n                         bool is_ghost)\n    : style_(style),\n      offset_(offset),\n      text_(std::move(text)),\n      metrics_(metrics),\n      line_(line),\n      x_start_(x_start),\n      x_end_(x_end),\n      is_ghost_(is_ghost) {}\n\nPaintRecord::PaintRecord(TextStyle style,\n                         SkPoint offset,\n                         sk_sp<SkTextBlob> text,\n                         SkFontMetrics metrics,\n                         size_t line,\n                         double x_start,\n                         double x_end,\n                         bool is_ghost,\n                         PlaceholderRun* placeholder_run)\n    : style_(style),\n      offset_(offset),\n      text_(std::move(text)),\n      metrics_(metrics),\n      line_(line),\n      x_start_(x_start),\n      x_end_(x_end),\n      is_ghost_(is_ghost),\n      placeholder_run_(placeholder_run) {}\n\nPaintRecord::PaintRecord(TextStyle style,\n                         sk_sp<SkTextBlob> text,\n                         SkFontMetrics metrics,\n                         size_t line,\n                         double x_start,\n                         double x_end,\n                         bool is_ghost)\n    : style_(style),\n      text_(std::move(text)),\n      metrics_(metrics),\n      line_(line),\n      x_start_(x_start),\n      x_end_(x_end),\n      is_ghost_(is_ghost) {}\n\nPaintRecord::PaintRecord(PaintRecord&& other) {\n  style_ = other.style_;\n  offset_ = other.offset_;\n  text_ = std::move(other.text_);\n  metrics_ = other.metrics_;\n  line_ = other.line_;\n  placeholder_run_ = other.placeholder_run_;\n  x_start_ = other.x_start_;\n  x_end_ = other.x_end_;\n  is_ghost_ = other.is_ghost_;\n}\n\nPaintRecord& PaintRecord::operator=(PaintRecord&& other) {\n  style_ = other.style_;\n  offset_ = other.offset_;\n  text_ = std::move(other.text_);\n  metrics_ = other.metrics_;\n  line_ = other.line_;\n  x_start_ = other.x_start_;\n  x_end_ = other.x_end_;\n  is_ghost_ = other.is_ghost_;\n  placeholder_run_ = other.placeholder_run_;\n  return *this;\n}\n\nvoid PaintRecord::SetOffset(SkPoint pt) {\n  offset_ = pt;\n}\n#endif\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paint_record.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_PAINT_RECORD_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_PAINT_RECORD_H_\n\n#include \"clay/fml/logging.h\"\n#include \"base/include/fml/macros.h\"\n#include \"placeholder_run.h\"\n#include \"text_style.h\"\n#include \"third_party/skia/include/core/SkFontMetrics.h\"\n#include \"third_party/skia/include/core/SkTextBlob.h\"\n\nnamespace txt {\n\n// PaintRecord holds the layout data after Paragraph::Layout() is called. This\n// stores all necessary offsets, blobs, metrics, and more for Skia to draw the\n// text.\nclass PaintRecord {\n public:\n  PaintRecord() = delete;\n\n  ~PaintRecord();\n#ifndef ENABLE_SKITY\n  PaintRecord(TextStyle style,\n              SkPoint offset,\n              sk_sp<SkTextBlob> text,\n              SkFontMetrics metrics,\n              size_t line,\n              double x_start,\n              double x_end,\n              bool is_ghost);\n\n  PaintRecord(TextStyle style,\n              SkPoint offset,\n              sk_sp<SkTextBlob> text,\n              SkFontMetrics metrics,\n              size_t line,\n              double x_start,\n              double x_end,\n              bool is_ghost,\n              PlaceholderRun* placeholder_run);\n\n  PaintRecord(TextStyle style,\n              sk_sp<SkTextBlob> text,\n              SkFontMetrics metrics,\n              size_t line,\n              double x_start,\n              double x_end,\n              bool is_ghost);\n#endif\n\n  PaintRecord(PaintRecord&& other);\n\n  PaintRecord& operator=(PaintRecord&& other);\n\n  SkPoint offset() const { return offset_; }\n\n  void SetOffset(SkPoint pt);\n\n#ifndef ENABLE_SKITY\n  SkTextBlob* text() const { return text_.get(); }\n#endif\n\n  const SkFontMetrics& metrics() const { return metrics_; }\n\n  const TextStyle& style() const { return style_; }\n\n  size_t line() const { return line_; }\n\n  double x_start() const { return x_start_; }\n  double x_end() const { return x_end_; }\n  double GetRunWidth() const { return x_end_ - x_start_; }\n\n  PlaceholderRun* GetPlaceholderRun() const { return placeholder_run_; }\n\n  bool isGhost() const { return is_ghost_; }\n\n  bool isPlaceholder() const { return placeholder_run_ == nullptr; }\n\n private:\n  TextStyle style_;\n  // offset_ is the overall offset of the origin of the SkTextBlob.\n  SkPoint offset_;\n#ifndef ENABLE_SKITY\n  // SkTextBlob stores the glyphs and coordinates to draw them.\n  sk_sp<SkTextBlob> text_;\n#endif\n  // FontMetrics stores the measurements of the font used.\n  SkFontMetrics metrics_;\n  size_t line_;\n  double x_start_ = 0.0f;\n  double x_end_ = 0.0f;\n  // 'Ghost' runs represent trailing whitespace. 'Ghost' runs should not have\n  // decorations painted on them and do not impact layout of visible glyphs.\n  bool is_ghost_ = false;\n  // Stores the corresponding PlaceholderRun that the record corresponds to.\n  // When this is nullptr, then the record is of normal text and does not\n  // represent an inline placeholder.\n  PlaceholderRun* placeholder_run_ = nullptr;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PaintRecord);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_PAINT_RECORD_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paragraph.h",
    "content": "\n/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_H_\n\n#include <vector>\n\n#include \"line_metrics.h\"\n#include \"paragraph_style.h\"\n#include \"skity/geometry/rect.hpp\"\n\nclass SkCanvas;\n\nnamespace txt {\n\n// Interface for text layout engines.  The original implementation was based on\n// the Minikin text layout library used by Android.  Another implementation is\n// available based on Skia's SkShaper/SkParagraph text layout module.\nclass Paragraph {\n public:\n  enum Affinity { UPSTREAM, DOWNSTREAM };\n\n  // Options for various types of bounding boxes provided by\n  // GetRectsForRange(...).\n  enum class RectHeightStyle {\n    // Provide tight bounding boxes that fit heights per run.\n    kTight,\n\n    // The height of the boxes will be the maximum height of all runs in the\n    // line. All rects in the same line will be the same height.\n    kMax,\n\n    // Extends the top and/or bottom edge of the bounds to fully cover any line\n    // spacing. The top edge of each line should be the same as the bottom edge\n    // of the line above. There should be no gaps in vertical coverage given any\n    // ParagraphStyle line_height.\n    //\n    // The top and bottom of each rect will cover half of the\n    // space above and half of the space below the line.\n    kIncludeLineSpacingMiddle,\n    // The line spacing will be added to the top of the rect.\n    kIncludeLineSpacingTop,\n    // The line spacing will be added to the bottom of the rect.\n    kIncludeLineSpacingBottom,\n\n    // Calculate boxes based on the strut's metrics.\n    kStrut\n  };\n\n  enum class RectWidthStyle {\n    // Provide tight bounding boxes that fit widths to the runs of each line\n    // independently.\n    kTight,\n\n    // Extends the width of the last rect of each line to match the position of\n    // the widest rect over all the lines.\n    kMax\n  };\n\n  struct PositionWithAffinity {\n    const size_t position;\n    const Affinity affinity;\n\n    PositionWithAffinity(size_t p, Affinity a) : position(p), affinity(a) {}\n  };\n\n  struct TextBox {\n    skity::Rect rect;\n    TextDirection direction;\n    size_t placeholder_id;\n\n    TextBox(skity::Rect r, TextDirection d) : rect(r), direction(d) {}\n    TextBox(skity::Rect r, TextDirection d, size_t placeholder_id)\n        : rect(r), direction(d), placeholder_id(placeholder_id) {}\n  };\n\n  template <typename T>\n  struct Range {\n    Range() : start(), end() {}\n    Range(T s, T e) : start(s), end(e) {}\n\n    T start, end;\n\n    bool operator==(const Range<T>& other) const {\n      return start == other.start && end == other.end;\n    }\n\n    T width() const { return end - start; }\n\n    void Shift(T delta) {\n      start += delta;\n      end += delta;\n    }\n  };\n\n  virtual ~Paragraph() = default;\n\n  // Returns the width provided in the Layout() method. This is the maximum\n  // width any line in the laid out paragraph can occupy. We expect that\n  // GetMaxWidth() >= GetLayoutWidth().\n  virtual double GetMaxWidth() = 0;\n\n  // Returns the height of the laid out paragraph. NOTE this is not a tight\n  // bounding height of the glyphs, as some glyphs do not reach as low as they\n  // can.\n  virtual double GetHeight() = 0;\n\n  // Returns the width of the longest line as found in Layout(), which is\n  // defined as the horizontal distance from the left edge of the leftmost glyph\n  // to the right edge of the rightmost glyph. We expect that\n  // GetLongestLine() <= GetMaxWidth().\n  virtual double GetLongestLine() = 0;\n\n  // Returns the actual max width of the longest line after Layout().\n  virtual double GetMinIntrinsicWidth() = 0;\n\n  // Returns the total width covered by the paragraph without linebreaking.\n  virtual double GetMaxIntrinsicWidth() = 0;\n\n  // Distance from top of paragraph to the Alphabetic baseline of the first\n  // line. Used for alphabetic fonts (A-Z, a-z, greek, etc.)\n  virtual double GetAlphabeticBaseline() = 0;\n\n  // Distance from top of paragraph to the Ideographic baseline of the first\n  // line. Used for ideographic fonts (Chinese, Japanese, Korean, etc.)\n  virtual double GetIdeographicBaseline() = 0;\n\n  // Checks if the layout extends past the maximum lines and had to be\n  // truncated.\n  virtual bool DidExceedMaxLines() = 0;\n\n  // Layout calculates the positioning of all the glyphs. Must call this method\n  // before Painting and getting any statistics from this class.\n  virtual void Layout(double width) = 0;\n\n  // Paints the laid out text onto the supplied SkCanvas at (x, y) offset from\n  // the origin. Only valid after Layout() is called.\n  virtual void Paint(SkCanvas* canvas, double x, double y) = 0;\n\n  // Returns a vector of bounding boxes that enclose all text between start and\n  // end glyph indexes, including start and excluding end.\n  virtual std::vector<TextBox> GetRectsForRange(\n      size_t start,\n      size_t end,\n      RectHeightStyle rect_height_style,\n      RectWidthStyle rect_width_style) = 0;\n\n  // Returns a vector of bounding boxes that bound all inline placeholders in\n  // the paragraph.\n  //\n  // There will be one box for each inline placeholder. The boxes will be in the\n  // same order as they were added to the paragraph. The bounds will always be\n  // tight and should fully enclose the area where the placeholder should be.\n  //\n  // More granular boxes may be obtained through GetRectsForRange, which will\n  // return bounds on both text as well as inline placeholders.\n  virtual std::vector<TextBox> GetRectsForPlaceholders() = 0;\n\n  // Returns the index of the glyph that corresponds to the provided coordinate,\n  // with the top left corner as the origin, and +y direction as down.\n  virtual PositionWithAffinity GetGlyphPositionAtCoordinate(double dx,\n                                                            double dy) = 0;\n\n  // Finds the first and last glyphs that define a word containing the glyph at\n  // index offset.\n  virtual Range<size_t> GetWordBoundary(size_t offset) = 0;\n\n  virtual std::vector<LineMetrics>& GetLineMetrics() = 0;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paragraph_builder.cc",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"paragraph_builder.h\"\n#include \"paragraph_style.h\"\n// #include \"third_party/icu/source/common/unicode/unistr.h\"\n\n#if CLAY_ENABLE_MINIKIN\n#include \"paragraph_builder_txt.h\"\n#endif\n\n#if CLAY_ENABLE_SKSHAPER\n#include \"clay/third_party/txt/src/skia/paragraph_builder_skia.h\"\n#endif\n\n#if CLAY_ENABLE_TTTEXT\n#include \"clay/third_party/txt/src/tttext/paragraph_builder_tt_text.h\"\n#endif\n\nnamespace txt {\n\n#if CLAY_ENABLE_MINIKIN\nstd::unique_ptr<ParagraphBuilder> ParagraphBuilder::CreateTxtBuilder(\n    const ParagraphStyle& style,\n    std::shared_ptr<FontCollection> font_collection) {\n  return std::make_unique<ParagraphBuilderTxt>(style, font_collection);\n}\n#endif // CLAY_ENABLE_MINIKIN\n\n#if CLAY_ENABLE_SKSHAPER\n\nstd::unique_ptr<ParagraphBuilder> ParagraphBuilder::CreateSkiaBuilder(\n    const ParagraphStyle& style,\n    std::shared_ptr<FontCollection> font_collection) {\n  return std::make_unique<ParagraphBuilderSkia>(style, font_collection);\n}\n\n#endif  // CLAY_ENABLE_SKSHAPER\n\n#if CLAY_ENABLE_TTTEXT\nstd::unique_ptr<ParagraphBuilder> ParagraphBuilder::CreateTTTextBuilder(\n    const ParagraphStyle& style,\n    std::shared_ptr<FontCollection> font_collection) {\n  return std::make_unique<ParagraphBuilderTTText>(style, font_collection);\n}\n#endif // CLAY_ENABLE_MINIKIN\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paragraph_builder.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_BUILDER_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_BUILDER_H_\n\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#ifdef ENABLE_SKITY\n#include \"font_collection_skity.h\"\n#else\n#include \"font_collection_skia.h\"\n#endif\n#include \"paragraph.h\"\n#include \"paragraph_style.h\"\n#include \"placeholder_run.h\"\n#include \"text_style.h\"\n\nnamespace txt {\n\nclass ParagraphBuilder {\n public:\n#if CLAY_ENABLE_MINIKIN\n  static std::unique_ptr<ParagraphBuilder> CreateTxtBuilder(\n      const ParagraphStyle& style,\n      std::shared_ptr<FontCollection> font_collection);\n#endif\n\n#if CLAY_ENABLE_SKSHAPER\n  static std::unique_ptr<ParagraphBuilder> CreateSkiaBuilder(\n      const ParagraphStyle& style,\n      std::shared_ptr<FontCollection> font_collection);\n#endif\n\n#if CLAY_ENABLE_TTTEXT\n  static std::unique_ptr<ParagraphBuilder> CreateTTTextBuilder(\n      const ParagraphStyle& style,\n      std::shared_ptr<FontCollection> font_collection);\n#endif\n\n  virtual ~ParagraphBuilder() = default;\n\n  // Push a style to the stack. The corresponding text added with AddText will\n  // use the top-most style.\n  virtual void PushStyle(const TextStyle& style) = 0;\n\n  // Remove a style from the stack. Useful to apply different styles to chunks\n  // of text such as bolding.\n  // Example:\n  //   builder.PushStyle(normal_style);\n  //   builder.AddText(\"Hello this is normal. \");\n  //\n  //   builder.PushStyle(bold_style);\n  //   builder.AddText(\"And this is BOLD. \");\n  //\n  //   builder.Pop();\n  //   builder.AddText(\" Back to normal again.\");\n  virtual void Pop() = 0;\n\n  // Returns the last TextStyle on the stack.\n  virtual const TextStyle& PeekStyle() = 0;\n\n  // Adds text to the builder. Forms the proper runs to use the upper-most style\n  // on the style_stack_;\n  virtual void AddText(const std::u16string& text) = 0;\n\n  // Pushes the information required to leave an open space, where Flutter may\n  // draw a custom placeholder into.\n  //\n  // Internally, this method adds a single object replacement character (0xFFFC)\n  // and emplaces a new PlaceholderRun instance to the vector of inline\n  // placeholders.\n  virtual void AddPlaceholder(PlaceholderRun& span) = 0;\n\n  // Constructs a Paragraph object that can be used to layout and paint the text\n  // to a SkCanvas.\n  virtual std::unique_ptr<Paragraph> Build() = 0;\n\n protected:\n  ParagraphBuilder() = default;\n\n private:\n  BASE_DISALLOW_COPY_AND_ASSIGN(ParagraphBuilder);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_BUILDER_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paragraph_builder_txt.cc",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"paragraph_builder_txt.h\"\n\n#include \"paragraph_txt.h\"\n\nnamespace txt {\n\nParagraphBuilderTxt::ParagraphBuilderTxt(\n    const ParagraphStyle& style,\n    std::shared_ptr<FontCollection> font_collection)\n    : font_collection_(std::move(font_collection)) {\n  SetParagraphStyle(style);\n}\n\nParagraphBuilderTxt::~ParagraphBuilderTxt() = default;\n\nvoid ParagraphBuilderTxt::SetParagraphStyle(const ParagraphStyle& style) {\n  paragraph_style_ = style;\n  paragraph_style_index_ = runs_.AddStyle(style.GetTextStyle());\n  runs_.StartRun(paragraph_style_index_, text_.size());\n}\n\nvoid ParagraphBuilderTxt::PushStyle(const TextStyle& style) {\n  size_t style_index = runs_.AddStyle(style);\n  style_stack_.push_back(style_index);\n  runs_.StartRun(style_index, text_.size());\n}\n\nvoid ParagraphBuilderTxt::Pop() {\n  if (style_stack_.empty()) {\n    return;\n  }\n  style_stack_.pop_back();\n  runs_.StartRun(PeekStyleIndex(), text_.size());\n}\n\nsize_t ParagraphBuilderTxt::PeekStyleIndex() const {\n  return style_stack_.size() ? style_stack_.back() : paragraph_style_index_;\n}\n\nconst TextStyle& ParagraphBuilderTxt::PeekStyle() {\n  return runs_.GetStyle(PeekStyleIndex());\n}\n\nvoid ParagraphBuilderTxt::AddText(const std::u16string& text) {\n  text_.insert(text_.end(), text.begin(), text.end());\n}\n\nvoid ParagraphBuilderTxt::AddPlaceholder(PlaceholderRun& span) {\n  obj_replacement_char_indexes_.insert(text_.size());\n  runs_.StartRun(PeekStyleIndex(), text_.size());\n  AddText(std::u16string(1ull, objReplacementChar));\n  runs_.StartRun(PeekStyleIndex(), text_.size());\n  inline_placeholders_.push_back(span);\n}\n\nstd::unique_ptr<Paragraph> ParagraphBuilderTxt::Build() {\n  runs_.EndRunIfNeeded(text_.size());\n\n  std::unique_ptr<ParagraphTxt> paragraph = std::make_unique<ParagraphTxt>();\n  paragraph->SetText(std::move(text_), std::move(runs_));\n  paragraph->SetInlinePlaceholders(std::move(inline_placeholders_),\n                                   std::move(obj_replacement_char_indexes_));\n  paragraph->SetParagraphStyle(paragraph_style_);\n  paragraph->SetFontCollection(font_collection_);\n  SetParagraphStyle(paragraph_style_);\n  return paragraph;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paragraph_builder_txt.h",
    "content": "/*\n * Copyright 2019 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_BUILDER_TXT_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_BUILDER_TXT_H_\n\n#include <memory>\n#include <unordered_set>\n#include <vector>\n#include \"paragraph_builder.h\"\n\n#include \"styled_runs.h\"\n\nnamespace txt {\n\n// Implementation of ParagraphBuilder that produces paragraphs backed by the\n// Minikin text layout library.\nclass ParagraphBuilderTxt : public ParagraphBuilder {\n public:\n  ParagraphBuilderTxt(const ParagraphStyle& style,\n                      std::shared_ptr<FontCollection> font_collection);\n\n  virtual ~ParagraphBuilderTxt();\n\n  virtual void PushStyle(const TextStyle& style) override;\n  virtual void Pop() override;\n  virtual const TextStyle& PeekStyle() override;\n  virtual void AddText(const std::u16string& text) override;\n  virtual void AddPlaceholder(PlaceholderRun& span) override;\n  virtual std::unique_ptr<Paragraph> Build() override;\n\n private:\n  std::vector<uint16_t> text_;\n  // A vector of PlaceholderRuns, which detail the sizes, positioning and break\n  // behavior of the empty spaces to leave. Each placeholder span corresponds to\n  // a 0xFFFC (object replacement character) in text_, which indicates the\n  // position in the text where the placeholder will occur. There should be an\n  // equal number of 0xFFFC characters and elements in this vector.\n  std::vector<PlaceholderRun> inline_placeholders_;\n  // The indexes of the obj replacement characters added through\n  // ParagraphBuilder::addPlaceholder().\n  std::unordered_set<size_t> obj_replacement_char_indexes_;\n  std::vector<size_t> style_stack_;\n  std::shared_ptr<FontCollection> font_collection_;\n  StyledRuns runs_;\n  ParagraphStyle paragraph_style_;\n  size_t paragraph_style_index_;\n\n  void SetParagraphStyle(const ParagraphStyle& style);\n\n  size_t PeekStyleIndex() const;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_BUILDER_TXT_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paragraph_style.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"paragraph_style.h\"\n\n#include <vector>\n\nnamespace txt {\n\nTextStyle ParagraphStyle::GetTextStyle() const {\n  TextStyle result;\n  result.font_weight = font_weight;\n  result.font_style = font_style;\n  if (!font_family.empty()) {\n    result.font_families = std::vector<std::string>({font_family});\n  }\n  if (font_size >= 0) {\n    result.font_size = font_size;\n  }\n  result.locale = locale;\n  result.height = height;\n  result.has_height_override = has_height_override;\n  return result;\n}\n\nbool ParagraphStyle::unlimited_lines() const {\n  return max_lines == std::numeric_limits<size_t>::max();\n};\n\nbool ParagraphStyle::ellipsized() const {\n  return !ellipsis.empty();\n}\n\nTextAlign ParagraphStyle::effective_align() const {\n  if (text_align == TextAlign::start) {\n    return (text_direction == TextDirection::ltr) ? TextAlign::left\n                                                  : TextAlign::right;\n  } else if (text_align == TextAlign::end) {\n    return (text_direction == TextDirection::ltr) ? TextAlign::right\n                                                  : TextAlign::left;\n  } else {\n    return text_align;\n  }\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paragraph_style.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_STYLE_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_STYLE_H_\n\n#include <climits>\n#include <limits>\n#include <string>\n#include <vector>\n\n#include \"font_style.h\"\n#include \"font_weight.h\"\n#include \"text_style.h\"\n\nnamespace txt {\n\nenum class TextAlign {\n  left,\n  right,\n  center,\n  justify,\n  start,\n  end,\n};\n\nenum class TextDirection {\n  rtl,\n  ltr,\n};\n\nenum RulerType { kAtLeast, kAuto, kExact };\n\n// Adjusts the leading over and under text.\n//\n// kDisableFirstAscent and kDisableLastDescent allow disabling height\n// adjustments to first line's ascent and the last line's descent. If disabled,\n// the line will use the default font metric provided ascent/descent and\n// ParagraphStyle.height or TextStyle.height will not take effect.\n//\n// The default behavior is kAll where height adjustments are enabled for all\n// lines.\n//\n// Multiple behaviors can be applied at once with a bitwise | operator. For\n// example, disabling first ascent and last descent can achieved with:\n//\n//   (kDisableFirstAscent | kDisableLastDescent).\nenum TextHeightBehavior {\n  kAll = 0x0,\n  kDisableFirstAscent = 0x1,\n  kDisableLastDescent = 0x2,\n  kDisableAll = 0x1 | 0x2,\n};\n\nclass ParagraphStyle {\n public:\n  // Default TextStyle. Used in GetTextStyle() to obtain the base TextStyle to\n  // inherit off of.\n  FontWeight font_weight = FontWeight::w400;\n  FontStyle font_style = FontStyle::normal;\n  std::string font_family = \"\";\n  double font_size = 14;\n  double height = 1;\n  RulerType height_type = RulerType::kAuto;\n  bool has_height_override = false;\n  size_t text_height_behavior = TextHeightBehavior::kAll;\n\n  // Strut properties. strut_enabled must be set to true for the rest of the\n  // properties to take effect.\n  // TODO(garyq): Break the strut properties into a separate class.\n  bool strut_enabled = false;\n  FontWeight strut_font_weight = FontWeight::w400;\n  FontStyle strut_font_style = FontStyle::normal;\n  std::vector<std::string> strut_font_families;\n  double strut_font_size = 14;\n  double strut_height = 1;\n  double line_spacing = 0.0;\n  bool strut_has_height_override = false;\n  bool strut_half_leading = false;\n  // This is for tttext\n  bool half_leading = true;\n  double strut_leading = -1;  // Negative to use font's default leading. [0,inf)\n                              // to use custom leading as a ratio of font size.\n  bool force_strut_height = false;\n  bool keep_trailing_spaces = true;\n  bool enable_text_bounds = false;  // this varient is for tttext vertical align\n\n  // General paragraph properties.\n  TextAlign text_align = TextAlign::start;\n  TextDirection text_direction = TextDirection::ltr;\n  size_t max_lines = std::numeric_limits<size_t>::max();\n  std::u16string ellipsis;\n  std::string locale;\n\n  TextStyle GetTextStyle() const;\n\n  bool unlimited_lines() const;\n  bool ellipsized() const;\n\n  // Return a text alignment value that is not dependent on the text direction.\n  TextAlign effective_align() const;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_STYLE_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paragraph_txt.cc",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"paragraph_txt.h\"\n\n#include <hb.h>\n#include <minikin/Layout.h>\n\n#include <algorithm>\n#include <cstring>\n#include <limits>\n#include <map>\n#include <numeric>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"font_collection_skia.h\"\n#include \"font_skia.h\"\n#include \"minikin/FontLanguageListCache.h\"\n#include \"minikin/GraphemeBreak.h\"\n#include \"minikin/HbFontCache.h\"\n#include \"minikin/LayoutUtils.h\"\n#include \"minikin/LineBreaker.h\"\n#include \"minikin/MinikinFont.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkFont.h\"\n#include \"third_party/skia/include/core/SkFontMetrics.h\"\n#include \"third_party/skia/include/core/SkMaskFilter.h\"\n#include \"third_party/skia/include/core/SkPaint.h\"\n#include \"third_party/skia/include/core/SkTextBlob.h\"\n#include \"third_party/skia/include/core/SkTypeface.h\"\n#include \"third_party/skia/include/effects/SkDashPathEffect.h\"\n#include \"third_party/skia/include/effects/SkDiscretePathEffect.h\"\n#include \"unicode/ubidi.h\"\n#include \"unicode/utf16.h\"\n\nnamespace txt {\nnamespace {\n\nclass GlyphTypeface {\n public:\n  GlyphTypeface(sk_sp<SkTypeface> typeface, minikin::FontFakery fakery)\n      : typeface_(std::move(typeface)),\n        fake_bold_(fakery.isFakeBold()),\n        fake_italic_(fakery.isFakeItalic()) {}\n\n  bool operator==(GlyphTypeface& other) {\n    return other.typeface_.get() == typeface_.get() &&\n           other.fake_bold_ == fake_bold_ && other.fake_italic_ == fake_italic_;\n  }\n\n  bool operator!=(GlyphTypeface& other) { return !(*this == other); }\n\n  void apply(SkFont& font) {\n    font.setTypeface(typeface_);\n    font.setEmbolden(fake_bold_);\n    font.setSkewX(fake_italic_ ? -SK_Scalar1 / 4 : 0);\n  }\n\n private:\n  sk_sp<SkTypeface> typeface_;\n  bool fake_bold_;\n  bool fake_italic_;\n};\n\nGlyphTypeface GetGlyphTypeface(const minikin::Layout& layout, size_t index) {\n  const FontSkia* font = static_cast<const FontSkia*>(layout.getFont(index));\n  return GlyphTypeface(font->GetSkTypeface(), layout.getFakery(index));\n}\n\n// Return ranges of text that have the same typeface in the layout.\nstd::vector<Paragraph::Range<size_t>> GetLayoutTypefaceRuns(\n    const minikin::Layout& layout) {\n  std::vector<Paragraph::Range<size_t>> result;\n  if (layout.nGlyphs() == 0)\n    return result;\n  size_t run_start = 0;\n  GlyphTypeface run_typeface = GetGlyphTypeface(layout, run_start);\n  for (size_t i = 1; i < layout.nGlyphs(); ++i) {\n    GlyphTypeface typeface = GetGlyphTypeface(layout, i);\n    if (typeface != run_typeface) {\n      result.emplace_back(run_start, i);\n      run_start = i;\n      run_typeface = typeface;\n    }\n  }\n  result.emplace_back(run_start, layout.nGlyphs());\n  return result;\n}\n\nint GetWeight(const FontWeight weight) {\n  switch (weight) {\n    case FontWeight::w100:\n      return 1;\n    case FontWeight::w200:\n      return 2;\n    case FontWeight::w300:\n      return 3;\n    case FontWeight::w400:  // Normal.\n      return 4;\n    case FontWeight::w500:\n      return 5;\n    case FontWeight::w600:\n      return 6;\n    case FontWeight::w700:  // Bold.\n      return 7;\n    case FontWeight::w800:\n      return 8;\n    case FontWeight::w900:\n      return 9;\n    default:\n      return -1;\n  }\n}\n\nint GetWeight(const TextStyle& style) {\n  return GetWeight(style.font_weight);\n}\n\nbool GetItalic(const TextStyle& style) {\n  switch (style.font_style) {\n    case FontStyle::italic:\n      return true;\n    case FontStyle::normal:\n    default:\n      return false;\n  }\n}\n\nminikin::FontStyle GetMinikinFontStyle(const TextStyle& style) {\n  uint32_t language_list_id =\n      style.locale.empty()\n          ? minikin::FontLanguageListCache::kEmptyListId\n          : minikin::FontStyle::registerLanguageList(style.locale);\n  return minikin::FontStyle(language_list_id, 0, GetWeight(style),\n                            GetItalic(style));\n}\n\nvoid GetFontAndMinikinPaint(const TextStyle& style,\n                            minikin::FontStyle* font,\n                            minikin::MinikinPaint* paint) {\n  *font = GetMinikinFontStyle(style);\n  paint->size = style.font_size;\n  // Divide by font size so letter spacing is pixels, not proportional to font\n  // size.\n  paint->letterSpacing = style.letter_spacing / style.font_size;\n  paint->wordSpacing = style.word_spacing;\n  paint->scaleX = 1.0f;\n  // Prevent spacing rounding in Minikin. This causes jitter when switching\n  // between same text content with different runs composing it, however, it\n  // also produces more accurate layouts.\n  paint->paintFlags |= minikin::LinearTextFlag;\n  paint->fontFeatureSettings = style.font_features.GetFeatureSettings();\n}\n\nvoid FindWords(const std::vector<uint16_t>& text,\n               size_t start,\n               size_t end,\n               std::vector<Paragraph::Range<size_t>>* words) {\n  bool in_word = false;\n  size_t word_start;\n  for (size_t i = start; i < end; ++i) {\n    bool is_space = minikin::isWordSpace(text[i]);\n    if (!in_word && !is_space) {\n      word_start = i;\n      in_word = true;\n    } else if (in_word && is_space) {\n      words->emplace_back(word_start, i);\n      in_word = false;\n    }\n  }\n  if (in_word)\n    words->emplace_back(word_start, end);\n}\n\n}  // namespace\n\nstatic const float kDoubleDecorationSpacing = 3.0f;\n\nParagraphTxt::GlyphPosition::GlyphPosition(double x_start,\n                                           double x_advance,\n                                           size_t code_unit_index,\n                                           size_t code_unit_width)\n    : code_units(code_unit_index, code_unit_index + code_unit_width),\n      x_pos(x_start, x_start + x_advance) {}\n\nvoid ParagraphTxt::GlyphPosition::Shift(double delta) {\n  x_pos.Shift(delta);\n}\n\nParagraphTxt::GlyphLine::GlyphLine(std::vector<GlyphPosition>&& p, size_t tcu)\n    : positions(std::move(p)), total_code_units(tcu) {}\n\nParagraphTxt::CodeUnitRun::CodeUnitRun(std::vector<GlyphPosition>&& p,\n                                       Range<size_t> cu,\n                                       Range<double> x,\n                                       size_t line,\n                                       const SkFontMetrics& metrics,\n                                       const TextStyle& st,\n                                       TextDirection dir,\n                                       const PlaceholderRun* placeholder)\n    : positions(std::move(p)),\n      code_units(cu),\n      x_pos(x),\n      line_number(line),\n      font_metrics(metrics),\n      style(&st),\n      direction(dir),\n      placeholder_run(placeholder) {}\n\nvoid ParagraphTxt::CodeUnitRun::Shift(double delta) {\n  x_pos.Shift(delta);\n  for (GlyphPosition& position : positions)\n    position.Shift(delta);\n}\n\nParagraphTxt::ParagraphTxt() {\n  breaker_.setLocale();\n}\n\nParagraphTxt::~ParagraphTxt() = default;\n\nvoid ParagraphTxt::SetText(std::vector<uint16_t> text, StyledRuns runs) {\n  SetDirty(true);\n  if (text.size() == 0)\n    return;\n  text_ = std::move(text);\n  runs_ = std::move(runs);\n}\n\nvoid ParagraphTxt::SetInlinePlaceholders(\n    std::vector<PlaceholderRun> inline_placeholders,\n    std::unordered_set<size_t> obj_replacement_char_indexes) {\n  needs_layout_ = true;\n  inline_placeholders_ = std::move(inline_placeholders);\n  obj_replacement_char_indexes_ = std::move(obj_replacement_char_indexes);\n}\n\nbool ParagraphTxt::ComputeLineBreaks() {\n  line_metrics_.clear();\n  line_widths_.clear();\n  max_intrinsic_width_ = 0;\n\n  std::vector<size_t> newline_positions;\n  // Discover and add all hard breaks.\n  for (size_t i = 0; i < text_.size(); ++i) {\n    ULineBreak ulb = static_cast<ULineBreak>(\n        u_getIntPropertyValue(text_[i], UCHAR_LINE_BREAK));\n    if (ulb == U_LB_LINE_FEED || ulb == U_LB_MANDATORY_BREAK)\n      newline_positions.push_back(i);\n  }\n  // Break at the end of the paragraph.\n  newline_positions.push_back(text_.size());\n\n  // Calculate and add any breaks due to a line being too long.\n  size_t run_index = 0;\n  size_t inline_placeholder_index = 0;\n  for (size_t newline_index = 0; newline_index < newline_positions.size();\n       ++newline_index) {\n    size_t block_start =\n        (newline_index > 0) ? newline_positions[newline_index - 1] + 1 : 0;\n    size_t block_end = newline_positions[newline_index];\n    size_t block_size = block_end - block_start;\n\n    if (block_size == 0) {\n      line_metrics_.emplace_back(block_start, block_end, block_end,\n                                 block_end + 1, true);\n      line_widths_.push_back(0);\n      continue;\n    }\n\n    // Setup breaker. We wait to set the line width in order to account for the\n    // widths of the inline placeholders, which are calculated in the loop over\n    // the runs.\n    breaker_.setLineWidths(0.0f, 0, width_);\n    breaker_.setJustified(paragraph_style_.text_align == TextAlign::justify);\n    breaker_.setStrategy(paragraph_style_.break_strategy);\n    breaker_.resize(block_size);\n    memcpy(breaker_.buffer(), text_.data() + block_start,\n           block_size * sizeof(text_[0]));\n    breaker_.setText();\n\n    // Add the runs that include this line to the LineBreaker.\n    double block_total_width = 0;\n    while (run_index < runs_.size()) {\n      StyledRuns::Run run = runs_.GetRun(run_index);\n      if (run.start >= block_end)\n        break;\n      if (run.end < block_start) {\n        run_index++;\n        continue;\n      }\n\n      minikin::FontStyle font;\n      minikin::MinikinPaint paint;\n      GetFontAndMinikinPaint(run.style, &font, &paint);\n      std::shared_ptr<minikin::FontCollection> collection =\n          GetMinikinFontCollectionForStyle(run.style);\n      if (collection == nullptr) {\n        FML_LOG(INFO) << \"Could not find font collection for families \\\"\"\n                      << (run.style.font_families.empty()\n                              ? \"\"\n                              : run.style.font_families[0])\n                      << \"\\\".\";\n        return false;\n      }\n      size_t run_start = std::max(run.start, block_start) - block_start;\n      size_t run_end = std::min(run.end, block_end) - block_start;\n      bool isRtl = (paragraph_style_.text_direction == TextDirection::rtl);\n\n      // Check if the run is an object replacement character-only run. We should\n      // leave space for inline placeholder and break around it if appropriate.\n      if (run.end - run.start == 1 &&\n          obj_replacement_char_indexes_.count(run.start) != 0 &&\n          text_[run.start] == objReplacementChar &&\n          inline_placeholder_index < inline_placeholders_.size()) {\n        // Is a inline placeholder run.\n        PlaceholderRun placeholder_run =\n            inline_placeholders_[inline_placeholder_index];\n        block_total_width += placeholder_run.width;\n\n        // Inject custom width into minikin breaker. (Uses LibTxt-minikin\n        // patch).\n        breaker_.setCustomCharWidth(run_start, placeholder_run.width);\n\n        // Called with nullptr as paint in order to use the custom widths passed\n        // above.\n        breaker_.addStyleRun(nullptr, collection, font, run_start, run_end,\n                             isRtl);\n        inline_placeholder_index++;\n      } else {\n        // Is a regular text run.\n        double run_width = breaker_.addStyleRun(&paint, collection, font,\n                                                run_start, run_end, isRtl);\n        block_total_width += run_width;\n      }\n\n      if (run.end > block_end)\n        break;\n      run_index++;\n    }\n    max_intrinsic_width_ = std::max(max_intrinsic_width_, block_total_width);\n\n    size_t breaks_count = breaker_.computeBreaks();\n    const int* breaks = breaker_.getBreaks();\n    for (size_t i = 0; i < breaks_count; ++i) {\n      size_t break_start = (i > 0) ? breaks[i - 1] : 0;\n      size_t line_start = break_start + block_start;\n      size_t line_end = breaks[i] + block_start;\n      bool hard_break = i == breaks_count - 1;\n      size_t line_end_including_newline =\n          (hard_break && line_end < text_.size()) ? line_end + 1 : line_end;\n      size_t line_end_excluding_whitespace = line_end;\n      while (\n          line_end_excluding_whitespace > line_start &&\n          minikin::isLineEndSpace(text_[line_end_excluding_whitespace - 1])) {\n        line_end_excluding_whitespace--;\n      }\n      line_metrics_.emplace_back(line_start, line_end,\n                                 line_end_excluding_whitespace,\n                                 line_end_including_newline, hard_break);\n      line_widths_.push_back(breaker_.getWidths()[i]);\n    }\n\n    breaker_.finish();\n  }\n\n  return true;\n}\n\nbool ParagraphTxt::ComputeBidiRuns(std::vector<BidiRun>* result) {\n  if (text_.empty())\n    return true;\n\n  auto ubidi_closer = [](UBiDi* b) { ubidi_close(b); };\n  std::unique_ptr<UBiDi, decltype(ubidi_closer)> bidi(ubidi_open(),\n                                                      ubidi_closer);\n  if (!bidi)\n    return false;\n\n  UBiDiLevel paraLevel = (paragraph_style_.text_direction == TextDirection::rtl)\n                             ? UBIDI_RTL\n                             : UBIDI_LTR;\n  UErrorCode status = U_ZERO_ERROR;\n  ubidi_setPara(bidi.get(), reinterpret_cast<const UChar*>(text_.data()),\n                text_.size(), paraLevel, nullptr, &status);\n  if (!U_SUCCESS(status))\n    return false;\n\n  int32_t bidi_run_count = ubidi_countRuns(bidi.get(), &status);\n  if (!U_SUCCESS(status))\n    return false;\n\n  // Detect if final trailing run is a single ambiguous whitespace.\n  // We want to bundle the final ambiguous whitespace with the preceding\n  // run in order to maintain logical typing behavior when mixing RTL and LTR\n  // text. We do not want this to be a true ghost run since the contrasting\n  // directionality causes the trailing space to not render at the visual end of\n  // the paragraph.\n  //\n  // This only applies to the final whitespace at the end as other whitespace is\n  // no longer ambiguous when surrounded by additional text.\n\n  // TODO(garyq): Handle this in the text editor caret code instead at layout\n  // level.\n  bool has_trailing_whitespace = false;\n  int32_t bidi_run_start, bidi_run_length;\n  if (bidi_run_count > 1) {\n    ubidi_getVisualRun(bidi.get(), bidi_run_count - 1, &bidi_run_start,\n                       &bidi_run_length);\n    if (!U_SUCCESS(status))\n      return false;\n    if (bidi_run_length == 1) {\n      UChar32 last_char;\n      U16_GET(text_.data(), 0, bidi_run_start + bidi_run_length - 1,\n              static_cast<int>(text_.size()), last_char);\n      if (u_hasBinaryProperty(last_char, UCHAR_WHITE_SPACE)) {\n        // Check if the trailing whitespace occurs before the previous run or\n        // not. If so, this trailing whitespace was a leading whitespace.\n        int32_t second_last_bidi_run_start, second_last_bidi_run_length;\n        ubidi_getVisualRun(bidi.get(), bidi_run_count - 2,\n                           &second_last_bidi_run_start,\n                           &second_last_bidi_run_length);\n        if (bidi_run_start ==\n            second_last_bidi_run_start + second_last_bidi_run_length) {\n          has_trailing_whitespace = true;\n          bidi_run_count--;\n        }\n      }\n    }\n  }\n\n  // Build a map of styled runs indexed by start position.\n  std::map<size_t, StyledRuns::Run> styled_run_map;\n  for (size_t i = 0; i < runs_.size(); ++i) {\n    StyledRuns::Run run = runs_.GetRun(i);\n    styled_run_map.emplace(std::make_pair(run.start, run));\n  }\n\n  for (int32_t bidi_run_index = 0; bidi_run_index < bidi_run_count;\n       ++bidi_run_index) {\n    UBiDiDirection direction = ubidi_getVisualRun(\n        bidi.get(), bidi_run_index, &bidi_run_start, &bidi_run_length);\n    if (!U_SUCCESS(status))\n      return false;\n\n    // Exclude the leading bidi control character if present.\n    UChar32 first_char;\n    U16_GET(text_.data(), 0, bidi_run_start, static_cast<int>(text_.size()),\n            first_char);\n    if (u_hasBinaryProperty(first_char, UCHAR_BIDI_CONTROL)) {\n      bidi_run_start++;\n      bidi_run_length--;\n    }\n    if (bidi_run_length == 0)\n      continue;\n\n    // Exclude the trailing bidi control character if present.\n    UChar32 last_char;\n    U16_GET(text_.data(), 0, bidi_run_start + bidi_run_length - 1,\n            static_cast<int>(text_.size()), last_char);\n    if (u_hasBinaryProperty(last_char, UCHAR_BIDI_CONTROL)) {\n      bidi_run_length--;\n    }\n    if (bidi_run_length == 0)\n      continue;\n\n    // Attach the final trailing whitespace as part of this run.\n    if (has_trailing_whitespace && bidi_run_index == bidi_run_count - 1) {\n      bidi_run_length++;\n    }\n\n    size_t bidi_run_end = bidi_run_start + bidi_run_length;\n    TextDirection text_direction =\n        direction == UBIDI_RTL ? TextDirection::rtl : TextDirection::ltr;\n\n    // Break this bidi run into chunks based on text style.\n    std::vector<BidiRun> chunks;\n    size_t chunk_start = bidi_run_start;\n    while (chunk_start < bidi_run_end) {\n      auto styled_run_iter = styled_run_map.upper_bound(chunk_start);\n      styled_run_iter--;\n      const StyledRuns::Run& styled_run = styled_run_iter->second;\n      size_t chunk_end = std::min(bidi_run_end, styled_run.end);\n      chunks.emplace_back(chunk_start, chunk_end, text_direction,\n                          styled_run.style);\n      chunk_start = chunk_end;\n    }\n\n    if (text_direction == TextDirection::ltr) {\n      result->insert(result->end(), chunks.begin(), chunks.end());\n    } else {\n      result->insert(result->end(), chunks.rbegin(), chunks.rend());\n    }\n  }\n\n  return true;\n}\n\nbool ParagraphTxt::IsStrutValid() const {\n  // Font size must be positive.\n  return (paragraph_style_.strut_enabled &&\n          paragraph_style_.strut_font_size >= 0);\n}\n\nvoid ParagraphTxt::ComputeStrut(StrutMetrics* strut, SkFont& font) {\n  strut->ascent = 0;\n  strut->descent = 0;\n  strut->leading = 0;\n  strut->half_leading = 0;\n  strut->line_height = 0;\n  strut->force_strut = false;\n\n  if (!IsStrutValid())\n    return;\n\n  // force_strut makes all lines have exactly the strut metrics, and ignores all\n  // actual metrics. We only force the strut if the strut is non-zero and valid.\n  strut->force_strut = paragraph_style_.force_strut_height;\n  minikin::FontStyle minikin_font_style(\n      0, GetWeight(paragraph_style_.strut_font_weight),\n      paragraph_style_.strut_font_style == FontStyle::italic);\n\n  std::shared_ptr<minikin::FontCollection> collection =\n      font_collection_->GetMinikinFontCollectionForFamilies(\n          paragraph_style_.strut_font_families, \"\");\n  if (!collection) {\n    return;\n  }\n  minikin::FakedFont faked_font = collection->baseFontFaked(minikin_font_style);\n\n  if (faked_font.font != nullptr) {\n    SkString str;\n    static_cast<FontSkia*>(faked_font.font)\n        ->GetSkTypeface()\n        ->getFamilyName(&str);\n    font.setTypeface(static_cast<FontSkia*>(faked_font.font)->GetSkTypeface());\n    font.setSize(paragraph_style_.strut_font_size);\n    SkFontMetrics strut_metrics;\n    font.getMetrics(&strut_metrics);\n\n    const double metrics_height =\n        -strut_metrics.fAscent + strut_metrics.fDescent;\n\n    if (paragraph_style_.strut_has_height_override) {\n      const double strut_height =\n          paragraph_style_.strut_height * paragraph_style_.strut_font_size;\n      const double metrics_leading =\n          // Zero extra leading if there is no user specified strut leading.\n          paragraph_style_.strut_leading < 0\n              ? 0\n              : (paragraph_style_.strut_leading *\n                 paragraph_style_.strut_font_size);\n\n      const double available_height =\n          paragraph_style_.strut_half_leading ? metrics_height : strut_height;\n\n      strut->ascent =\n          (-strut_metrics.fAscent / metrics_height) * available_height;\n      strut->descent =\n          (strut_metrics.fDescent / metrics_height) * available_height;\n\n      strut->leading = metrics_leading + strut_height - available_height;\n    } else {\n      strut->ascent = -strut_metrics.fAscent;\n      strut->descent = strut_metrics.fDescent;\n      strut->leading =\n          // Use font's leading if there is no user specified strut leading.\n          paragraph_style_.strut_leading < 0\n              ? strut_metrics.fLeading\n              : (paragraph_style_.strut_leading *\n                 paragraph_style_.strut_font_size);\n    }\n    strut->half_leading = strut->leading / 2;\n    strut->line_height = strut->ascent + strut->descent + strut->leading;\n  }\n}\n\nvoid ParagraphTxt::ComputePlaceholder(PlaceholderRun* placeholder_run,\n                                      double& ascent,\n                                      double& descent) {\n  if (placeholder_run != nullptr) {\n    // Calculate how much to shift the ascent and descent to account\n    // for the baseline choice.\n    //\n    // TODO(garyq): implement for various baselines. Currently only\n    // supports for alphabetic and ideographic\n    double baseline_adjustment = 0;\n    switch (placeholder_run->baseline) {\n      case TextBaseline::kAlphabetic: {\n        baseline_adjustment = 0;\n        break;\n      }\n      case TextBaseline::kIdeographic: {\n        baseline_adjustment = -descent / 2;\n        break;\n      }\n    }\n    // Convert the ascent and descent from the font's to the placeholder\n    // rect's.\n    switch (placeholder_run->alignment) {\n      case PlaceholderAlignment::kBaseline: {\n        ascent = baseline_adjustment + placeholder_run->baseline_offset;\n        descent = -baseline_adjustment + placeholder_run->height -\n                  placeholder_run->baseline_offset;\n        break;\n      }\n      case PlaceholderAlignment::kAboveBaseline: {\n        ascent = baseline_adjustment + placeholder_run->height;\n        descent = -baseline_adjustment;\n        break;\n      }\n      case PlaceholderAlignment::kBelowBaseline: {\n        descent = baseline_adjustment + placeholder_run->height;\n        ascent = -baseline_adjustment;\n        break;\n      }\n      case PlaceholderAlignment::kTop: {\n        descent = placeholder_run->height - ascent;\n        break;\n      }\n      case PlaceholderAlignment::kBottom: {\n        ascent = placeholder_run->height - descent;\n        break;\n      }\n      case PlaceholderAlignment::kMiddle: {\n        double mid = (ascent - descent) / 2;\n        ascent = mid + placeholder_run->height / 2;\n        descent = -mid + placeholder_run->height / 2;\n        break;\n      }\n    }\n    placeholder_run->baseline_offset = ascent;\n  }\n}\n\n// Implementation outline:\n//\n// -For each line:\n//   -Compute Bidi runs, convert into line_runs (keeps in-line-range runs, adds\n//   special runs)\n//   -For each line_run (runs in the line):\n//     -Calculate ellipsis\n//     -Obtain font\n//     -layout.doLayout(...), genereates glyph blobs\n//     -For each glyph blob:\n//       -Convert glyph blobs into pixel metrics/advances\n//     -Store as paint records (for painting) and code unit runs (for metrics\n//     and boxes).\n//   -Apply letter spacing, alignment, justification, etc\n//   -Calculate line vertical layout (ascent, descent, etc)\n//   -Store per-line metrics\nvoid ParagraphTxt::Layout(double width) {\n  double rounded_width = floor(width);\n  // Do not allow calling layout multiple times without changing anything.\n  if (!needs_layout_ && rounded_width == width_) {\n    return;\n  }\n\n  width_ = rounded_width;\n\n  needs_layout_ = false;\n\n  records_.clear();\n  glyph_lines_.clear();\n  code_unit_runs_.clear();\n  inline_placeholder_code_unit_runs_.clear();\n  max_right_ = std::numeric_limits<double>::lowest();\n  min_left_ = std::numeric_limits<double>::max();\n  final_line_count_ = 0;\n\n  if (!ComputeLineBreaks())\n    return;\n\n  std::vector<BidiRun> bidi_runs;\n  if (!ComputeBidiRuns(&bidi_runs))\n    return;\n\n  SkFont font;\n  font.setEdging(SkFont::Edging::kAntiAlias);\n  font.setSubpixel(true);\n  font.setHinting(SkFontHinting::kSlight);\n\n  minikin::Layout layout;\n  SkTextBlobBuilder builder;\n  double y_offset = 0;\n  double prev_max_descent = 0;\n  double max_word_width = 0;\n\n  // Compute strut minimums according to paragraph_style_.\n  ComputeStrut(&strut_, font);\n\n  // Paragraph bounds tracking.\n  size_t line_limit =\n      std::min(paragraph_style_.max_lines, line_metrics_.size());\n  did_exceed_max_lines_ = (line_metrics_.size() > paragraph_style_.max_lines);\n\n  size_t placeholder_run_index = 0;\n  for (size_t line_number = 0; line_number < line_limit; ++line_number) {\n    LineMetrics& line_metrics = line_metrics_[line_number];\n\n    // Break the line into words if justification should be applied.\n    std::vector<Range<size_t>> words;\n    double word_gap_width = 0;\n    size_t word_index = 0;\n    bool justify_line =\n        (paragraph_style_.text_align == TextAlign::justify &&\n         line_number != line_limit - 1 && !line_metrics.hard_break);\n    FindWords(text_, line_metrics.start_index, line_metrics.end_index, &words);\n    if (justify_line) {\n      if (words.size() > 1) {\n        word_gap_width =\n            (width_ - line_widths_[line_number]) / (words.size() - 1);\n      }\n    }\n\n    // Exclude trailing whitespace from justified lines so the last visible\n    // character in the line will be flush with the right margin.\n    size_t line_end_index =\n        (paragraph_style_.effective_align() == TextAlign::right ||\n         paragraph_style_.effective_align() == TextAlign::center ||\n         paragraph_style_.effective_align() == TextAlign::justify)\n            ? line_metrics.end_excluding_whitespace\n            : line_metrics.end_index;\n\n    // Find the runs comprising this line.\n    std::vector<BidiRun> line_runs;\n    for (const BidiRun& bidi_run : bidi_runs) {\n      // A \"ghost\" run is a run that does not impact the layout, breaking,\n      // alignment, width, etc but is still \"visible\" through getRectsForRange.\n      // For example, trailing whitespace on centered text can be scrolled\n      // through with the caret but will not wrap the line.\n      //\n      // Here, we add an additional run for the whitespace, but dont\n      // let it impact metrics. After layout of the whitespace run, we do not\n      // add its width into the x-offset adjustment, effectively nullifying its\n      // impact on the layout.\n      std::unique_ptr<BidiRun> ghost_run = nullptr;\n      if (paragraph_style_.ellipsis.empty() &&\n          line_metrics.end_excluding_whitespace < line_metrics.end_index &&\n          bidi_run.start() <= line_metrics.end_index &&\n          bidi_run.end() > line_end_index) {\n        ghost_run = std::make_unique<BidiRun>(\n            std::max(bidi_run.start(), line_end_index),\n            std::min(bidi_run.end(), line_metrics.end_index),\n            bidi_run.direction(), bidi_run.style(), true);\n      }\n      // Include the ghost run before normal run if RTL\n      if (bidi_run.direction() == TextDirection::rtl && ghost_run != nullptr) {\n        line_runs.push_back(*ghost_run);\n      }\n      // Emplace a normal line run.\n      if (bidi_run.start() < line_end_index &&\n          bidi_run.end() > line_metrics.start_index) {\n        // The run is a placeholder run.\n        if (bidi_run.size() == 1 &&\n            text_[bidi_run.start()] == objReplacementChar &&\n            obj_replacement_char_indexes_.count(bidi_run.start()) != 0 &&\n            placeholder_run_index < inline_placeholders_.size()) {\n          line_runs.emplace_back(\n              std::max(bidi_run.start(), line_metrics.start_index),\n              std::min(bidi_run.end(), line_end_index), bidi_run.direction(),\n              bidi_run.style(), inline_placeholders_[placeholder_run_index]);\n          placeholder_run_index++;\n        } else {\n          line_runs.emplace_back(\n              std::max(bidi_run.start(), line_metrics.start_index),\n              std::min(bidi_run.end(), line_end_index), bidi_run.direction(),\n              bidi_run.style());\n        }\n      }\n      // Include the ghost run after normal run if LTR\n      if (bidi_run.direction() == TextDirection::ltr && ghost_run != nullptr) {\n        line_runs.push_back(*ghost_run);\n      }\n    }\n    bool line_runs_all_rtl =\n        line_runs.size() &&\n        std::accumulate(\n            line_runs.begin(), line_runs.end(), true,\n            [](const bool a, const BidiRun& b) { return a && b.is_rtl(); });\n    if (line_runs_all_rtl) {\n      std::reverse(words.begin(), words.end());\n    }\n\n    std::vector<GlyphPosition> line_glyph_positions;\n    std::vector<CodeUnitRun> line_code_unit_runs;\n    std::vector<CodeUnitRun> line_inline_placeholder_code_unit_runs;\n\n    double run_x_offset = 0;\n    double justify_x_offset = 0;\n    std::vector<PaintRecord> paint_records;\n\n    for (auto line_run_it = line_runs.begin(); line_run_it != line_runs.end();\n         ++line_run_it) {\n      const BidiRun& run = *line_run_it;\n      minikin::FontStyle minikin_font;\n      minikin::MinikinPaint minikin_paint;\n      GetFontAndMinikinPaint(run.style(), &minikin_font, &minikin_paint);\n      font.setSize(run.style().font_size);\n\n      std::shared_ptr<minikin::FontCollection> minikin_font_collection =\n          GetMinikinFontCollectionForStyle(run.style());\n      if (!minikin_font_collection) {\n        return;\n      }\n\n      // Lay out this run.\n      uint16_t* text_ptr = text_.data();\n      size_t text_start = run.start();\n      size_t text_count = run.end() - run.start();\n      size_t text_size = text_.size();\n\n      // Apply ellipsizing if the run was not completely laid out and this\n      // is the last line (or lines are unlimited).\n      const std::u16string& ellipsis = paragraph_style_.ellipsis;\n      std::vector<uint16_t> ellipsized_text;\n      if (ellipsis.length() && !isinf(width_) && !line_metrics.hard_break &&\n          line_run_it == line_runs.end() - 1 &&\n          (line_number == line_limit - 1 ||\n           paragraph_style_.unlimited_lines())) {\n        float ellipsis_width = layout.measureText(\n            reinterpret_cast<const uint16_t*>(ellipsis.data()), 0,\n            ellipsis.length(), ellipsis.length(), run.is_rtl(), minikin_font,\n            minikin_paint, minikin_font_collection, nullptr);\n\n        std::vector<float> text_advances(text_count);\n        float text_width =\n            layout.measureText(text_ptr, text_start, text_count, text_.size(),\n                               run.is_rtl(), minikin_font, minikin_paint,\n                               minikin_font_collection, text_advances.data());\n\n        // Truncate characters from the text until the ellipsis fits.\n        size_t truncate_count = 0;\n        while (truncate_count < text_count &&\n               run_x_offset + text_width + ellipsis_width > width_) {\n          text_width -= text_advances[text_count - truncate_count - 1];\n          truncate_count++;\n        }\n\n        ellipsized_text.reserve(text_count - truncate_count +\n                                ellipsis.length());\n        ellipsized_text.insert(ellipsized_text.begin(),\n                               text_.begin() + run.start(),\n                               text_.begin() + run.end() - truncate_count);\n        ellipsized_text.insert(ellipsized_text.end(), ellipsis.begin(),\n                               ellipsis.end());\n        text_ptr = ellipsized_text.data();\n        text_start = 0;\n        text_count = ellipsized_text.size();\n        text_size = text_count;\n\n        // If there is no line limit, then skip all lines after the ellipsized\n        // line.\n        if (paragraph_style_.unlimited_lines()) {\n          line_limit = line_number + 1;\n          did_exceed_max_lines_ = true;\n        }\n      }\n\n      layout.doLayout(text_ptr, text_start, text_count, text_size, run.is_rtl(),\n                      minikin_font, minikin_paint, minikin_font_collection);\n\n      if (layout.nGlyphs() == 0)\n        continue;\n\n      // When laying out RTL ghost runs, shift the run_x_offset here by the\n      // advance so that the ghost run is positioned to the left of the first\n      // real run of text in the line. However, since we do not want it to\n      // impact the layout of real text, this advance is subsequently added\n      // back into the run_x_offset after the ghost run positions have been\n      // calcuated and before the next real run of text is laid out, ensuring\n      // later runs are laid out in the same position as if there were no ghost\n      // run.\n      if (run.is_ghost() && run.is_rtl())\n        run_x_offset -= layout.getAdvance();\n\n      std::vector<float> layout_advances(text_count);\n      layout.getAdvances(layout_advances.data());\n\n      // Break the layout into blobs that share the same SkPaint parameters.\n      std::vector<Range<size_t>> glyph_blobs = GetLayoutTypefaceRuns(layout);\n\n      double word_start_position = std::numeric_limits<double>::quiet_NaN();\n\n      // Build a Skia text blob from each group of glyphs.\n      for (const Range<size_t>& glyph_blob : glyph_blobs) {\n        std::vector<GlyphPosition> glyph_positions;\n\n        GetGlyphTypeface(layout, glyph_blob.start).apply(font);\n        const SkTextBlobBuilder::RunBuffer& blob_buffer =\n            builder.allocRunPos(font, glyph_blob.end - glyph_blob.start);\n\n        double justify_x_offset_delta = 0;\n        for (size_t glyph_index = glyph_blob.start;\n             glyph_index < glyph_blob.end;) {\n          size_t cluster_start_glyph_index = glyph_index;\n          uint32_t cluster = layout.getGlyphCluster(cluster_start_glyph_index);\n          double glyph_x_offset;\n          // Add all the glyphs in this cluster to the text blob.\n          do {\n            size_t blob_index = glyph_index - glyph_blob.start;\n            blob_buffer.glyphs[blob_index] = layout.getGlyphId(glyph_index);\n\n            size_t pos_index = blob_index * 2;\n            blob_buffer.pos[pos_index] = layout.getX(glyph_index) +\n                                         justify_x_offset +\n                                         justify_x_offset_delta;\n            blob_buffer.pos[pos_index + 1] = layout.getY(glyph_index);\n\n            if (glyph_index == cluster_start_glyph_index)\n              glyph_x_offset = blob_buffer.pos[pos_index];\n\n            glyph_index++;\n          } while (glyph_index < glyph_blob.end &&\n                   layout.getGlyphCluster(glyph_index) == cluster);\n\n          Range<int32_t> glyph_code_units(cluster, 0);\n          std::vector<size_t> grapheme_code_unit_counts;\n          if (run.is_rtl()) {\n            if (cluster_start_glyph_index > 0) {\n              glyph_code_units.end =\n                  layout.getGlyphCluster(cluster_start_glyph_index - 1);\n            } else {\n              glyph_code_units.end = text_count;\n            }\n            grapheme_code_unit_counts.push_back(glyph_code_units.width());\n          } else {\n            if (glyph_index < layout.nGlyphs()) {\n              glyph_code_units.end = layout.getGlyphCluster(glyph_index);\n            } else {\n              glyph_code_units.end = text_count;\n            }\n\n            // The glyph may be a ligature.  Determine how many graphemes are\n            // joined into this glyph and how many input code units map to\n            // each grapheme.\n            size_t code_unit_count = 1;\n            for (int32_t offset = glyph_code_units.start + 1;\n                 offset < glyph_code_units.end; ++offset) {\n              if (minikin::GraphemeBreak::isGraphemeBreak(\n                      layout_advances.data(), text_ptr, text_start, text_count,\n                      text_start + offset)) {\n                grapheme_code_unit_counts.push_back(code_unit_count);\n                code_unit_count = 1;\n              } else {\n                code_unit_count++;\n              }\n            }\n            grapheme_code_unit_counts.push_back(code_unit_count);\n          }\n          float glyph_advance;\n          if (run.is_placeholder_run()) {\n            // The placeholder run's layout should yield one glyph representing\n            // the object replacement character.  Replace its width with the\n            // placeholder's width.\n            FML_DCHECK(layout.nGlyphs() == 1);\n            glyph_advance = run.placeholder_run()->width;\n          } else {\n            glyph_advance = layout.getCharAdvance(glyph_code_units.start);\n          }\n          float grapheme_advance =\n              glyph_advance / grapheme_code_unit_counts.size();\n\n          glyph_positions.emplace_back(run_x_offset + glyph_x_offset,\n                                       grapheme_advance,\n                                       run.start() + glyph_code_units.start,\n                                       grapheme_code_unit_counts[0]);\n\n          // Compute positions for the additional graphemes in the ligature.\n          for (size_t i = 1; i < grapheme_code_unit_counts.size(); ++i) {\n            glyph_positions.emplace_back(\n                glyph_positions.back().x_pos.end, grapheme_advance,\n                glyph_positions.back().code_units.start +\n                    grapheme_code_unit_counts[i - 1],\n                grapheme_code_unit_counts[i]);\n          }\n\n          bool at_word_start = false;\n          bool at_word_end = false;\n          if (word_index < words.size()) {\n            at_word_start =\n                words[word_index].start == run.start() + glyph_code_units.start;\n            at_word_end =\n                words[word_index].end == run.start() + glyph_code_units.end;\n            if (line_runs_all_rtl) {\n              std::swap(at_word_start, at_word_end);\n            }\n          }\n\n          if (at_word_start) {\n            word_start_position = run_x_offset + glyph_x_offset;\n          }\n\n          if (at_word_end) {\n            if (justify_line) {\n              justify_x_offset_delta += word_gap_width;\n            }\n            word_index++;\n\n            if (!isnan(word_start_position)) {\n              double word_width =\n                  glyph_positions.back().x_pos.end - word_start_position;\n              max_word_width = std::max(word_width, max_word_width);\n              word_start_position = std::numeric_limits<double>::quiet_NaN();\n            }\n          }\n        }  // for each in glyph_blob\n\n        if (glyph_positions.empty())\n          continue;\n\n        // Store the font metrics and TextStyle in the LineMetrics for this line\n        // to provide metrics upon user request. We index this RunMetrics\n        // instance at `run.end() - 1` to allow map::lower_bound to access the\n        // correct RunMetrics at any text index.\n        size_t run_key = run.end() - 1;\n        line_metrics.run_metrics.emplace(run_key, &run.style());\n        SkFontMetrics* metrics =\n            &line_metrics.run_metrics.at(run_key).font_metrics;\n        font.getMetrics(metrics);\n\n        Range<double> record_x_pos(\n            glyph_positions.front().x_pos.start - run_x_offset,\n            glyph_positions.back().x_pos.end - run_x_offset);\n        paint_records.emplace_back(run.style(), SkPoint::Make(run_x_offset, 0),\n                                   builder.make(), *metrics, line_number,\n                                   record_x_pos.start, record_x_pos.end,\n                                   run.is_ghost(), run.placeholder_run());\n\n        justify_x_offset += justify_x_offset_delta;\n\n        line_glyph_positions.insert(line_glyph_positions.end(),\n                                    glyph_positions.begin(),\n                                    glyph_positions.end());\n\n        // Add a record of glyph positions sorted by code unit index.\n        std::vector<GlyphPosition> code_unit_positions(glyph_positions);\n        std::sort(code_unit_positions.begin(), code_unit_positions.end(),\n                  [](const GlyphPosition& a, const GlyphPosition& b) {\n                    return a.code_units.start < b.code_units.start;\n                  });\n\n        double blob_x_pos_start = glyph_positions.front().x_pos.start;\n        double blob_x_pos_end = glyph_positions.back().x_pos.end;\n        line_code_unit_runs.emplace_back(\n            std::move(code_unit_positions),\n            Range<size_t>(run.start(), run.end()),\n            Range<double>(blob_x_pos_start, blob_x_pos_end), line_number,\n            *metrics, run.style(), run.direction(), run.placeholder_run());\n\n        if (run.is_placeholder_run()) {\n          line_inline_placeholder_code_unit_runs.push_back(\n              line_code_unit_runs.back());\n        }\n\n        if (!run.is_ghost()) {\n          min_left_ = std::min(min_left_, blob_x_pos_start);\n          max_right_ = std::max(max_right_, blob_x_pos_end);\n        }\n      }  // for each in glyph_blobs\n\n      if (run.is_placeholder_run()) {\n        run_x_offset += run.placeholder_run()->width;\n      } else {\n        // Do not increase x offset for LTR trailing ghost runs as it should not\n        // impact the layout of visible glyphs. RTL tailing ghost runs have the\n        // advance subtracted, so we do add the advance here to reset the\n        // run_x_offset. We do keep the record though so GetRectsForRange() can\n        // find metrics for trailing spaces.\n        if (!run.is_ghost() || run.is_rtl()) {\n          run_x_offset += layout.getAdvance();\n        }\n      }\n    }  // for each in line_runs\n\n    // Adjust the glyph positions based on the alignment of the line.\n    double line_x_offset = GetLineXOffset(run_x_offset, justify_line);\n    if (line_x_offset) {\n      for (CodeUnitRun& code_unit_run : line_code_unit_runs) {\n        code_unit_run.Shift(line_x_offset);\n      }\n      for (CodeUnitRun& code_unit_run :\n           line_inline_placeholder_code_unit_runs) {\n        code_unit_run.Shift(line_x_offset);\n      }\n      for (GlyphPosition& position : line_glyph_positions) {\n        position.Shift(line_x_offset);\n      }\n    }\n\n    size_t next_line_start = (line_number < line_metrics_.size() - 1)\n                                 ? line_metrics_[line_number + 1].start_index\n                                 : text_.size();\n    glyph_lines_.emplace_back(std::move(line_glyph_positions),\n                              next_line_start - line_metrics.start_index);\n    code_unit_runs_.insert(code_unit_runs_.end(), line_code_unit_runs.begin(),\n                           line_code_unit_runs.end());\n    inline_placeholder_code_unit_runs_.insert(\n        inline_placeholder_code_unit_runs_.end(),\n        line_inline_placeholder_code_unit_runs.begin(),\n        line_inline_placeholder_code_unit_runs.end());\n\n    // Calculate the amount to advance in the y direction. This is done by\n    // computing the maximum ascent and descent with respect to the strut.\n    double max_ascent = IsStrutValid() ? strut_.ascent + strut_.half_leading\n                                       : std::numeric_limits<double>::lowest();\n    double max_descent = IsStrutValid() ? strut_.descent + strut_.half_leading\n                                        : std::numeric_limits<double>::lowest();\n    double max_unscaled_ascent = 0;\n    for (const PaintRecord& paint_record : paint_records) {\n      UpdateLineMetrics(paint_record.metrics(), paint_record.style(),\n                        max_ascent, max_descent, max_unscaled_ascent,\n                        paint_record.GetPlaceholderRun(), line_number,\n                        line_limit);\n    }\n\n    // If no fonts were actually rendered, then compute a baseline based on the\n    // font of the paragraph style.\n    if (paint_records.empty()) {\n      SkFontMetrics metrics;\n      TextStyle style(paragraph_style_.GetTextStyle());\n      font.setTypeface(GetDefaultSkiaTypeface(style));\n      font.setSize(style.font_size);\n      font.getMetrics(&metrics);\n      UpdateLineMetrics(metrics, style, max_ascent, max_descent,\n                        max_unscaled_ascent, nullptr, line_number, line_limit);\n    }\n\n    // Calculate the baselines. This is only done on the first line.\n    if (line_number == 0) {\n      alphabetic_baseline_ = max_ascent;\n      // TODO(garyq): Ideographic baseline is currently bottom of EM\n      // box, which is not correct. This should be obtained from metrics.\n      // Skia currently does not support various baselines.\n      ideographic_baseline_ = (max_ascent + max_descent);\n    }\n\n    line_metrics.height =\n        (line_number == 0 ? 0 : line_metrics_[line_number - 1].height) +\n        round(max_ascent + max_descent);\n    line_metrics.baseline = line_metrics.height - max_descent;\n\n    y_offset += round(max_ascent + prev_max_descent);\n    prev_max_descent = max_descent;\n\n    line_metrics.line_number = line_number;\n    line_metrics.ascent = max_ascent;\n    line_metrics.descent = max_descent;\n    line_metrics.unscaled_ascent = max_unscaled_ascent;\n    line_metrics.width = line_widths_[line_number];\n    line_metrics.left = line_x_offset;\n\n    final_line_count_++;\n\n    for (PaintRecord& paint_record : paint_records) {\n      paint_record.SetOffset(\n          SkPoint::Make(paint_record.offset().x() + line_x_offset, y_offset));\n      records_.emplace_back(std::move(paint_record));\n    }\n  }  // for each line_number\n\n  if (paragraph_style_.max_lines == 1 ||\n      (paragraph_style_.unlimited_lines() && paragraph_style_.ellipsized())) {\n    min_intrinsic_width_ = max_intrinsic_width_;\n  } else {\n    min_intrinsic_width_ = std::min(max_word_width, max_intrinsic_width_);\n  }\n\n  std::sort(code_unit_runs_.begin(), code_unit_runs_.end(),\n            [](const CodeUnitRun& a, const CodeUnitRun& b) {\n              return a.code_units.start < b.code_units.start;\n            });\n\n  longest_line_ = max_right_ - min_left_;\n}\n\nvoid ParagraphTxt::UpdateLineMetrics(const SkFontMetrics& metrics,\n                                     const TextStyle& style,\n                                     double& max_ascent,\n                                     double& max_descent,\n                                     double& max_unscaled_ascent,\n                                     PlaceholderRun* placeholder_run,\n                                     size_t line_number,\n                                     size_t line_limit) {\n  if (!strut_.force_strut) {\n    const double metrics_font_height = metrics.fDescent - metrics.fAscent;\n    // The overall height of the glyph blob. If neither the ascent or the\n    // descent is disabled, we have block_height = ascent + descent, where\n    // \"ascent\" is the extent from the top of the blob to its baseline, and\n    // \"descent\" is the extent from the text blob's baseline to its bottom. Not\n    // to be mistaken with the font's ascent and descent.\n    const double blob_height = style.has_height_override\n                                   ? style.height * style.font_size\n                                   : metrics_font_height + metrics.fLeading;\n\n    // Scale the ascent and descent such that the sum of ascent and\n    // descent is `style.height * style.font_size`.\n    //\n    // The raw metrics do not add up to fontSize. The state of font\n    // metrics is a mess:\n    //\n    // Each font has 4 sets of vertical metrics:\n    //\n    // * hhea: hheaAscender, hheaDescender, hheaLineGap.\n    //     Used by Apple.\n    // * OS/2 typo: typoAscender, typoDescender, typoLineGap.\n    //     Used sometimes by Windows for layout.\n    // * OS/2 win: winAscent, winDescent.\n    //     Also used by Windows, generally will be cut if extends past\n    //     these metrics.\n    // * EM Square: ascent, descent\n    //     Not actively used, but this defines the 'scale' of the\n    //     units used.\n    //\n    // `Use Typo Metrics` is a boolean that, when enabled, prefers\n    // typo metrics over win metrics. Default is off. Enabled by most\n    // modern fonts.\n    //\n    // In addition to these different sets of metrics, there are also\n    // multiple strategies for using these metrics:\n    //\n    // * Adobe: Set hhea values to typo equivalents.\n    // * Microsoft: Set hhea values to win equivalents.\n    // * Web: Use hhea values for text, regardless of `Use Typo Metrics`\n    //     The hheaLineGap is distributed half across the top and half\n    //     across the bottom of the line.\n    //   Exceptions:\n    //     Windows: All browsers respect `Use Typo Metrics`\n    //     Firefox respects `Use Typo Metrics`.\n    //\n    // This pertains to this code in that it is ambiguous which set of\n    // metrics we are actually using via SkFontMetrics. This in turn\n    // means that if we use the raw metrics, we will see differences\n    // between platforms as well as unpredictable line heights.\n    //\n    // A more thorough explanation is available at\n    // https://glyphsapp.com/tutorials/vertical-metrics\n    //\n    // Doing this ascent/descent normalization to the EM Square allows\n    // a sane, consistent, and reasonable \"blob_height\" to be specified,\n    // though it breaks with what is done by any of the platforms above.\n    const bool shouldNormalizeFont =\n        style.has_height_override && !style.half_leading;\n    const double font_height =\n        shouldNormalizeFont ? style.font_size : metrics_font_height;\n\n    // Reserve the outermost vertical space we want to distribute evenly over\n    // and under the text (\"half-leading\").\n    double leading;\n    if (style.half_leading) {\n      leading = blob_height - font_height;\n    } else {\n      leading = style.has_height_override ? 0.0 : metrics.fLeading;\n    }\n    const double half_leading = leading / 2;\n\n    // Proportionally distribute the remaining vertical space above and below\n    // the glyph blob's baseline, per the font's ascent/discent ratio.\n    const double available_vspace = blob_height - leading;\n    const double modifiedAscent =\n        -metrics.fAscent / metrics_font_height * available_vspace +\n        half_leading;\n    const double modifiedDescent =\n        metrics.fDescent / metrics_font_height * available_vspace +\n        half_leading;\n\n    const bool disableAscent =\n        line_number == 0 && paragraph_style_.text_height_behavior &\n                                TextHeightBehavior::kDisableFirstAscent;\n    const bool disableDescent = line_number == line_limit - 1 &&\n                                paragraph_style_.text_height_behavior &\n                                    TextHeightBehavior::kDisableLastDescent;\n\n    double ascent = disableAscent ? -metrics.fAscent : modifiedAscent;\n    double descent = disableDescent ? metrics.fDescent : modifiedDescent;\n\n    ComputePlaceholder(placeholder_run, ascent, descent);\n\n    max_ascent = std::max(ascent, max_ascent);\n    max_descent = std::max(descent, max_descent);\n  }\n\n  max_unscaled_ascent =\n      std::max(placeholder_run == nullptr ? -metrics.fAscent\n                                          : placeholder_run->baseline_offset,\n               max_unscaled_ascent);\n};\n\ndouble ParagraphTxt::GetLineXOffset(double line_total_advance,\n                                    bool justify_line) {\n  if (isinf(width_))\n    return 0;\n\n  TextAlign align = paragraph_style_.effective_align();\n\n  if (align == TextAlign::right ||\n      (align == TextAlign::justify &&\n       paragraph_style_.text_direction == TextDirection::rtl &&\n       !justify_line)) {\n    return width_ - line_total_advance;\n  } else if (align == TextAlign::center) {\n    return (width_ - line_total_advance) / 2;\n  } else {\n    return 0;\n  }\n}\n\nconst ParagraphStyle& ParagraphTxt::GetParagraphStyle() const {\n  return paragraph_style_;\n}\n\ndouble ParagraphTxt::GetAlphabeticBaseline() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  // Currently -fAscent\n  return alphabetic_baseline_;\n}\n\ndouble ParagraphTxt::GetIdeographicBaseline() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  // TODO(garyq): Currently -fAscent + fUnderlinePosition. Verify this.\n  return ideographic_baseline_;\n}\n\ndouble ParagraphTxt::GetMaxIntrinsicWidth() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  return max_intrinsic_width_;\n}\n\ndouble ParagraphTxt::GetMinIntrinsicWidth() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  return min_intrinsic_width_;\n}\n\nsize_t ParagraphTxt::TextSize() const {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  return text_.size();\n}\n\ndouble ParagraphTxt::GetHeight() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  return final_line_count_ == 0 ? 0\n                                : line_metrics_[final_line_count_ - 1].height;\n}\n\ndouble ParagraphTxt::GetMaxWidth() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  return width_;\n}\n\ndouble ParagraphTxt::GetLongestLine() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  return longest_line_;\n}\n\nvoid ParagraphTxt::SetParagraphStyle(const ParagraphStyle& style) {\n  needs_layout_ = true;\n  paragraph_style_ = style;\n}\n\nvoid ParagraphTxt::SetFontCollection(\n    std::shared_ptr<FontCollection> font_collection) {\n  font_collection_ = std::move(font_collection);\n}\n\nstd::shared_ptr<minikin::FontCollection>\nParagraphTxt::GetMinikinFontCollectionForStyle(const TextStyle& style) {\n  std::string locale;\n  if (!style.locale.empty()) {\n    uint32_t language_list_id =\n        minikin::FontStyle::registerLanguageList(style.locale);\n    const minikin::FontLanguages& langs =\n        minikin::FontLanguageListCache::getById(language_list_id);\n    if (langs.size()) {\n      locale = langs[0].getString();\n    }\n  }\n\n  return font_collection_->GetMinikinFontCollectionForFamilies(\n      style.font_families, locale);\n}\n\nsk_sp<SkTypeface> ParagraphTxt::GetDefaultSkiaTypeface(const TextStyle& style) {\n  std::shared_ptr<minikin::FontCollection> collection =\n      GetMinikinFontCollectionForStyle(style);\n  if (!collection) {\n    return nullptr;\n  }\n  minikin::FakedFont faked_font =\n      collection->baseFontFaked(GetMinikinFontStyle(style));\n  return static_cast<FontSkia*>(faked_font.font)->GetSkTypeface();\n}\n\n// The x,y coordinates will be the very top left corner of the rendered\n// paragraph.\nvoid ParagraphTxt::Paint(SkCanvas* canvas, double x, double y) {\n  SkPoint base_offset = SkPoint::Make(x, y);\n  SkPaint paint;\n  // Paint the background first before painting any text to prevent\n  // potential overlap.\n  for (const PaintRecord& record : records_) {\n    PaintBackground(canvas, record, base_offset);\n  }\n  for (const PaintRecord& record : records_) {\n    if (record.style().has_foreground) {\n      paint = record.style().foreground;\n    } else {\n      paint.reset();\n      paint.setColor(record.style().color);\n    }\n    SkPoint offset = base_offset + record.offset();\n    if (record.GetPlaceholderRun() == nullptr) {\n      PaintShadow(canvas, record, offset);\n      canvas->drawTextBlob(record.text(), offset.x(), offset.y(), paint);\n    }\n    PaintDecorations(canvas, record, base_offset);\n  }\n}\n\nvoid ParagraphTxt::PaintDecorations(SkCanvas* canvas,\n                                    const PaintRecord& record,\n                                    SkPoint base_offset) {\n  if (record.style().decoration == TextDecoration::kNone)\n    return;\n\n  if (record.isGhost())\n    return;\n\n  const SkFontMetrics& metrics = record.metrics();\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  if (record.style().decoration_color == SK_ColorTRANSPARENT) {\n    paint.setColor(record.style().color);\n  } else {\n    paint.setColor(record.style().decoration_color);\n  }\n  paint.setAntiAlias(true);\n\n  // This is set to 2 for the double line style\n  int decoration_count = 1;\n\n  // Filled when drawing wavy decorations.\n  SkPath path;\n\n  double width = record.GetRunWidth();\n\n  SkScalar underline_thickness;\n  if ((metrics.fFlags &\n       SkFontMetrics::FontMetricsFlags::kUnderlineThicknessIsValid_Flag) &&\n      metrics.fUnderlineThickness > 0) {\n    underline_thickness = metrics.fUnderlineThickness;\n  } else {\n    // Backup value if the fUnderlineThickness metric is not available:\n    // Divide by 14pt as it is the default size.\n    underline_thickness = record.style().font_size / 14.0f;\n  }\n  paint.setStrokeWidth(underline_thickness *\n                       record.style().decoration_thickness_multiplier);\n\n  SkPoint record_offset = base_offset + record.offset();\n  SkScalar x = record_offset.x() + record.x_start();\n  SkScalar y = record_offset.y();\n\n  // Setup the decorations.\n  switch (record.style().decoration_style) {\n    case TextDecorationStyle::kSolid: {\n      break;\n    }\n    case TextDecorationStyle::kDouble: {\n      decoration_count = 2;\n      break;\n    }\n    // Note: the intervals are scaled by the thickness of the line, so it is\n    // possible to change spacing by changing the decoration_thickness\n    // property of TextStyle.\n    case TextDecorationStyle::kDotted: {\n      // Divide by 14pt as it is the default size.\n      const float scale = record.style().font_size / 14.0f;\n      const SkScalar intervals[] = {1.0f * scale, 1.5f * scale, 1.0f * scale,\n                                    1.5f * scale};\n      size_t count = sizeof(intervals) / sizeof(intervals[0]);\n      paint.setPathEffect(SkPathEffect::MakeCompose(\n          SkDashPathEffect::Make(intervals, count, 0.0f),\n          SkDiscretePathEffect::Make(0, 0)));\n      break;\n    }\n    // Note: the intervals are scaled by the thickness of the line, so it is\n    // possible to change spacing by changing the decoration_thickness\n    // property of TextStyle.\n    case TextDecorationStyle::kDashed: {\n      // Divide by 14pt as it is the default size.\n      const float scale = record.style().font_size / 14.0f;\n      const SkScalar intervals[] = {4.0f * scale, 2.0f * scale, 4.0f * scale,\n                                    2.0f * scale};\n      size_t count = sizeof(intervals) / sizeof(intervals[0]);\n      paint.setPathEffect(SkPathEffect::MakeCompose(\n          SkDashPathEffect::Make(intervals, count, 0.0f),\n          SkDiscretePathEffect::Make(0, 0)));\n      break;\n    }\n    case TextDecorationStyle::kWavy: {\n      ComputeWavyDecoration(\n          path, x, y, width,\n          underline_thickness * record.style().decoration_thickness_multiplier);\n      break;\n    }\n  }\n\n  // Draw the decorations.\n  // Use a for loop for \"kDouble\" decoration style\n  for (int i = 0; i < decoration_count; i++) {\n    double y_offset = i * underline_thickness * kDoubleDecorationSpacing;\n    double y_offset_original = y_offset;\n    // Underline\n    if (record.style().decoration & TextDecoration::kUnderline) {\n      y_offset +=\n          (metrics.fFlags &\n           SkFontMetrics::FontMetricsFlags::kUnderlinePositionIsValid_Flag)\n              ? metrics.fUnderlinePosition\n              : underline_thickness;\n      if (record.style().decoration_style != TextDecorationStyle::kWavy) {\n        canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint);\n      } else {\n        SkPath offsetPath = path;\n        offsetPath.offset(0, y_offset);\n        canvas->drawPath(offsetPath, paint);\n      }\n      y_offset = y_offset_original;\n    }\n    // Overline\n    if (record.style().decoration & TextDecoration::kOverline) {\n      // We subtract fAscent here because for double overlines, we want the\n      // second line to be above, not below the first.\n      y_offset -= metrics.fAscent;\n      if (record.style().decoration_style != TextDecorationStyle::kWavy) {\n        canvas->drawLine(x, y - y_offset, x + width, y - y_offset, paint);\n      } else {\n        SkPath offsetPath = path;\n        offsetPath.offset(0, -y_offset);\n        canvas->drawPath(offsetPath, paint);\n      }\n      y_offset = y_offset_original;\n    }\n    // Strikethrough\n    if (record.style().decoration & TextDecoration::kLineThrough) {\n      if (metrics.fFlags &\n          SkFontMetrics::FontMetricsFlags::kStrikeoutThicknessIsValid_Flag)\n        paint.setStrokeWidth(metrics.fStrikeoutThickness *\n                             record.style().decoration_thickness_multiplier);\n      // Make sure the double line is \"centered\" vertically.\n      y_offset += (decoration_count - 1.0) * underline_thickness *\n                  kDoubleDecorationSpacing / -2.0;\n      y_offset +=\n          (metrics.fFlags &\n           SkFontMetrics::FontMetricsFlags::kStrikeoutPositionIsValid_Flag)\n              ? metrics.fStrikeoutPosition\n              // Backup value if the strikeoutposition metric is not\n              // available:\n              : metrics.fXHeight / -2.0;\n      if (record.style().decoration_style != TextDecorationStyle::kWavy) {\n        canvas->drawLine(x, y + y_offset, x + width, y + y_offset, paint);\n      } else {\n        SkPath offsetPath = path;\n        offsetPath.offset(0, y_offset);\n        canvas->drawPath(offsetPath, paint);\n      }\n      y_offset = y_offset_original;\n    }\n  }\n}\n\nvoid ParagraphTxt::ComputeWavyDecoration(SkPath& path,\n                                         double x,\n                                         double y,\n                                         double width,\n                                         double thickness) {\n  int wave_count = 0;\n  double x_start = 0;\n  // One full wavelength is 4 * thickness.\n  double quarter = thickness;\n  path.moveTo(x, y);\n  double remaining = width;\n  while (x_start + (quarter * 2) < width) {\n    path.rQuadTo(quarter, wave_count % 2 == 0 ? -quarter : quarter, quarter * 2,\n                 0);\n    x_start += quarter * 2;\n    remaining = width - x_start;\n    ++wave_count;\n  }\n  // Manually add a final partial quad for the remaining width that do\n  // not fit nicely into a half-wavelength.\n  // The following math is based off of quadratic bezier equations:\n  //\n  //  * Let P(x) be the equation for the curve.\n  //  * Let P0 = start, P1 = control point, P2 = end\n  //  * P(x) = -2x^2 - 2x\n  //  * P0 = (0, 0)\n  //  * P1 = 2P(0.5) - 0.5 * P0 - 0.5 * P2\n  //  * P2 = P(remaining / (wavelength / 2))\n  //\n  // Simplified implementation coursesy of @jim-flar at\n  // https://github.com/flutter/engine/pull/9468#discussion_r297872739\n  // Unsimplified original version at\n  // https://github.com/flutter/engine/pull/9468#discussion_r297879129\n\n  double x1 = remaining / 2;\n  double y1 = remaining / 2 * (wave_count % 2 == 0 ? -1 : 1);\n  double x2 = remaining;\n  double y2 = (remaining - remaining * remaining / (quarter * 2)) *\n              (wave_count % 2 == 0 ? -1 : 1);\n  path.rQuadTo(x1, y1, x2, y2);\n}\n\nvoid ParagraphTxt::PaintBackground(SkCanvas* canvas,\n                                   const PaintRecord& record,\n                                   SkPoint base_offset) {\n  if (!record.style().has_background)\n    return;\n\n  const SkFontMetrics& metrics = record.metrics();\n  SkRect rect(SkRect::MakeLTRB(record.x_start(), metrics.fAscent,\n                               record.x_end(), metrics.fDescent));\n  rect.offset(base_offset + record.offset());\n  canvas->drawRect(rect, record.style().background);\n}\n\nvoid ParagraphTxt::PaintShadow(SkCanvas* canvas,\n                               const PaintRecord& record,\n                               SkPoint offset) {\n  if (record.style().text_shadows.size() == 0)\n    return;\n  for (TextShadow text_shadow : record.style().text_shadows) {\n    if (!text_shadow.hasShadow()) {\n      continue;\n    }\n\n    SkPaint paint;\n    paint.setColor(text_shadow.color);\n    if (text_shadow.blur_sigma > 0.5) {\n      paint.setMaskFilter(SkMaskFilter::MakeBlur(\n          kNormal_SkBlurStyle, text_shadow.blur_sigma, false));\n    }\n    canvas->drawTextBlob(record.text(), offset.x() + text_shadow.offset.x,\n                         offset.y() + text_shadow.offset.y, paint);\n  }\n}\n\nstd::vector<Paragraph::TextBox> ParagraphTxt::GetRectsForRange(\n    size_t start,\n    size_t end,\n    RectHeightStyle rect_height_style,\n    RectWidthStyle rect_width_style) {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  // Struct that holds calculated metrics for each line.\n  struct LineBoxMetrics {\n    std::vector<Paragraph::TextBox> boxes;\n    // Per-line metrics for max and min coordinates for left and right boxes.\n    // These metrics cannot be calculated in layout generically because of\n    // selections that do not cover the whole line.\n    SkScalar max_right = std::numeric_limits<SkScalar>::lowest();\n    SkScalar min_left = std::numeric_limits<SkScalar>::max();\n  };\n\n  std::map<size_t, LineBoxMetrics> line_box_metrics;\n  // Text direction of the first line so we can extend the correct side for\n  // RectWidthStyle::kMax.\n  TextDirection first_line_dir = TextDirection::ltr;\n  std::map<size_t, size_t> newline_x_positions;\n\n  // Lines that are actually in the requested range.\n  size_t max_line = 0;\n  size_t min_line = INT_MAX;\n  size_t glyph_length = 0;\n\n  // Generate initial boxes and calculate metrics.\n  for (const CodeUnitRun& run : code_unit_runs_) {\n    // Check to see if we are finished.\n    if (run.code_units.start >= end)\n      break;\n\n    // Update new line x position with the ending of last bidi run on the line\n    newline_x_positions[run.line_number] =\n        run.direction == TextDirection::ltr ? run.x_pos.end : run.x_pos.start;\n\n    if (run.code_units.end <= start)\n      continue;\n\n    double baseline = line_metrics_[run.line_number].baseline;\n    SkScalar top = baseline + run.font_metrics.fAscent;\n    SkScalar bottom = baseline + run.font_metrics.fDescent;\n\n    if (run.placeholder_run !=\n        nullptr) {  // Use inline placeholder size as height.\n      top = baseline - run.placeholder_run->baseline_offset;\n      bottom = baseline + run.placeholder_run->height -\n               run.placeholder_run->baseline_offset;\n    }\n\n    max_line = std::max(run.line_number, max_line);\n    min_line = std::min(run.line_number, min_line);\n\n    // Calculate left and right.\n    SkScalar left, right;\n    if (run.code_units.start >= start && run.code_units.end <= end) {\n      left = run.x_pos.start;\n      right = run.x_pos.end;\n    } else {\n      left = SK_ScalarMax;\n      right = SK_ScalarMin;\n      for (const GlyphPosition& gp : run.positions) {\n        if (gp.code_units.start >= start && gp.code_units.end <= end) {\n          left = std::min(left, static_cast<SkScalar>(gp.x_pos.start));\n          right = std::max(right, static_cast<SkScalar>(gp.x_pos.end));\n        } else if (gp.code_units.end == end) {\n          // Calculate left and right when we are at\n          // the last position of a combining character.\n          glyph_length = (gp.code_units.end - gp.code_units.start) - 1;\n          if (gp.code_units.start ==\n              std::max<size_t>(0, (start - glyph_length))) {\n            left = std::min(left, static_cast<SkScalar>(gp.x_pos.start));\n            right = std::max(right, static_cast<SkScalar>(gp.x_pos.end));\n          }\n        }\n      }\n      if (left == SK_ScalarMax || right == SK_ScalarMin)\n        continue;\n    }\n    // Keep track of the min and max horizontal coordinates over all lines. Not\n    // needed for kTight.\n    if (rect_width_style == RectWidthStyle::kMax) {\n      line_box_metrics[run.line_number].max_right =\n          std::max(line_box_metrics[run.line_number].max_right, right);\n      line_box_metrics[run.line_number].min_left =\n          std::min(line_box_metrics[run.line_number].min_left, left);\n      if (min_line == run.line_number) {\n        first_line_dir = run.direction;\n      }\n    }\n    line_box_metrics[run.line_number].boxes.emplace_back(\n        skity::Rect::MakeLTRB(left, top, right, bottom), run.direction);\n  }\n\n  // Add empty rectangles representing any newline characters within the\n  // range.\n  for (size_t line_number = 0; line_number < line_metrics_.size();\n       ++line_number) {\n    LineMetrics& line = line_metrics_[line_number];\n    if (line.start_index >= end)\n      break;\n    if (line.end_including_newline <= start)\n      continue;\n    if (line_box_metrics.find(line_number) == line_box_metrics.end()) {\n      if (line.end_index != line.end_including_newline &&\n          line.end_index >= start && line.end_including_newline <= end) {\n        SkScalar x;\n        auto it = newline_x_positions.find(line_number);\n        if (it != newline_x_positions.end()) {\n          x = it->second;\n        } else {\n          x = GetLineXOffset(0, false);\n        }\n        SkScalar top =\n            (line_number > 0) ? line_metrics_[line_number - 1].height : 0;\n        SkScalar bottom = line_metrics_[line_number].height;\n        line_box_metrics[line_number].boxes.emplace_back(\n            skity::Rect::MakeLTRB(x, top, x, bottom), TextDirection::ltr);\n      }\n    }\n  }\n\n  // \"Post-process\" metrics and aggregate final rects to return.\n  std::vector<Paragraph::TextBox> boxes;\n  for (const auto& kv : line_box_metrics) {\n    // Handle rect_width_styles. We skip the last line because not everything is\n    // selected.\n\n    LineMetrics& line =\n        line_metrics_[fmin(line_metrics_.size() - 1, fmax(0, kv.first))];\n    if (rect_width_style == RectWidthStyle::kMax && kv.first != max_line) {\n      if (line_box_metrics[kv.first].min_left > min_left_ &&\n          (kv.first != min_line || first_line_dir == TextDirection::rtl)) {\n        line_box_metrics[kv.first].boxes.emplace_back(\n            skity::Rect::MakeLTRB(min_left_,\n                                  line.baseline - line.unscaled_ascent,\n                                  line_box_metrics[kv.first].min_left,\n                                  line.baseline + line.descent),\n            TextDirection::rtl);\n      }\n      if (line_box_metrics[kv.first].max_right < max_right_ &&\n          (kv.first != min_line || first_line_dir == TextDirection::ltr)) {\n        line_box_metrics[kv.first].boxes.emplace_back(\n            skity::Rect::MakeLTRB(line_box_metrics[kv.first].max_right,\n                                  line.baseline - line.unscaled_ascent,\n                                  max_right_, line.baseline + line.descent),\n            TextDirection::ltr);\n      }\n    }\n\n    // Handle rect_height_styles. The height metrics used are all positive to\n    // make the signage clear here.\n    if (rect_height_style == RectHeightStyle::kTight) {\n      // Ignore line max height and width and generate tight bounds.\n      boxes.insert(boxes.end(), kv.second.boxes.begin(), kv.second.boxes.end());\n    } else if (rect_height_style == RectHeightStyle::kMax) {\n      for (const Paragraph::TextBox& box : kv.second.boxes) {\n        boxes.emplace_back(skity::Rect::MakeLTRB(\n                               box.rect.Left(), line.baseline - line.ascent,\n                               box.rect.Right(), line.baseline + line.descent),\n                           box.direction);\n      }\n    } else if (rect_height_style ==\n               RectHeightStyle::kIncludeLineSpacingMiddle) {\n      SkScalar adjusted_bottom = line.baseline + line.descent;\n      if (kv.first < line_metrics_.size() - 1) {\n        adjusted_bottom += (line_metrics_[kv.first + 1].ascent -\n                            line_metrics_[kv.first + 1].unscaled_ascent) /\n                           2;\n      }\n      SkScalar adjusted_top = line.baseline - line.unscaled_ascent;\n      if (kv.first != 0) {\n        adjusted_top -= (line.ascent - line.unscaled_ascent) / 2;\n      }\n      for (const Paragraph::TextBox& box : kv.second.boxes) {\n        boxes.emplace_back(\n            skity::Rect::MakeLTRB(box.rect.Left(), adjusted_top,\n                                  box.rect.Right(), adjusted_bottom),\n            box.direction);\n      }\n    } else if (rect_height_style == RectHeightStyle::kIncludeLineSpacingTop) {\n      for (const Paragraph::TextBox& box : kv.second.boxes) {\n        SkScalar adjusted_top = kv.first == 0\n                                    ? line.baseline - line.unscaled_ascent\n                                    : line.baseline - line.ascent;\n        boxes.emplace_back(skity::Rect::MakeLTRB(box.rect.Left(), adjusted_top,\n                                                 box.rect.Right(),\n                                                 line.baseline + line.descent),\n                           box.direction);\n      }\n    } else if (rect_height_style ==\n               RectHeightStyle::kIncludeLineSpacingBottom) {\n      for (const Paragraph::TextBox& box : kv.second.boxes) {\n        SkScalar adjusted_bottom = line.baseline + line.descent;\n        if (kv.first < line_metrics_.size() - 1) {\n          adjusted_bottom += -line.unscaled_ascent + line.ascent;\n        }\n        boxes.emplace_back(\n            skity::Rect::MakeLTRB(box.rect.Left(),\n                                  line.baseline - line.unscaled_ascent,\n                                  box.rect.Right(), adjusted_bottom),\n            box.direction);\n      }\n    } else if (rect_height_style == RectHeightStyle::kStrut) {\n      if (IsStrutValid()) {\n        for (const Paragraph::TextBox& box : kv.second.boxes) {\n          boxes.emplace_back(\n              skity::Rect::MakeLTRB(\n                  box.rect.Left(), line.baseline - strut_.ascent,\n                  box.rect.Right(), line.baseline + strut_.descent),\n              box.direction);\n        }\n      } else {\n        // Fall back to tight bounds if the strut is invalid.\n        boxes.insert(boxes.end(), kv.second.boxes.begin(),\n                     kv.second.boxes.end());\n      }\n    }\n  }\n  return boxes;\n}\n\nParagraph::PositionWithAffinity ParagraphTxt::GetGlyphPositionAtCoordinate(\n    double dx,\n    double dy) {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  if (final_line_count_ <= 0)\n    return PositionWithAffinity(0, DOWNSTREAM);\n\n  size_t y_index;\n  for (y_index = 0; y_index < final_line_count_ - 1; ++y_index) {\n    if (dy < line_metrics_[y_index].height)\n      break;\n  }\n\n  const std::vector<GlyphPosition>& line_glyph_position =\n      glyph_lines_[y_index].positions;\n  if (line_glyph_position.empty()) {\n    int line_start_index =\n        std::accumulate(glyph_lines_.begin(), glyph_lines_.begin() + y_index, 0,\n                        [](const int a, const GlyphLine& b) {\n                          return a + static_cast<int>(b.total_code_units);\n                        });\n    return PositionWithAffinity(line_start_index, DOWNSTREAM);\n  }\n\n  size_t x_index;\n  const GlyphPosition* gp = nullptr;\n  for (x_index = 0; x_index < line_glyph_position.size(); ++x_index) {\n    double glyph_end = (x_index < line_glyph_position.size() - 1)\n                           ? line_glyph_position[x_index + 1].x_pos.start\n                           : line_glyph_position[x_index].x_pos.end;\n    if (dx < glyph_end) {\n      gp = &line_glyph_position[x_index];\n      break;\n    }\n  }\n\n  if (gp == nullptr) {\n    gp = &line_glyph_position.back();\n  }\n\n  // Find the direction of the run that contains this glyph.\n  TextDirection direction = TextDirection::ltr;\n  for (const CodeUnitRun& run : code_unit_runs_) {\n    if (gp->code_units.start >= run.code_units.start &&\n        gp->code_units.end <= run.code_units.end) {\n      direction = run.direction;\n      break;\n    }\n  }\n\n  double glyph_center = (gp->x_pos.start + gp->x_pos.end) / 2;\n  if ((direction == TextDirection::ltr && dx < glyph_center) ||\n      (direction == TextDirection::rtl && dx >= glyph_center)) {\n    return PositionWithAffinity(gp->code_units.start, DOWNSTREAM);\n  } else {\n    return PositionWithAffinity(gp->code_units.end, UPSTREAM);\n  }\n}\n\n// We don't cache this because since this returns all boxes, it is usually\n// unnecessary to call this multiple times in succession.\nstd::vector<Paragraph::TextBox> ParagraphTxt::GetRectsForPlaceholders() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  // Struct that holds calculated metrics for each line.\n  struct LineBoxMetrics {\n    std::vector<Paragraph::TextBox> boxes;\n    // Per-line metrics for max and min coordinates for left and right boxes.\n    // These metrics cannot be calculated in layout generically because of\n    // selections that do not cover the whole line.\n    SkScalar max_right = std::numeric_limits<SkScalar>::lowest();\n    SkScalar min_left = std::numeric_limits<SkScalar>::max();\n  };\n\n  std::vector<Paragraph::TextBox> boxes;\n\n  // Generate initial boxes and calculate metrics.\n  for (const CodeUnitRun& run : inline_placeholder_code_unit_runs_) {\n    // Check to see if we are finished.\n    double baseline = line_metrics_[run.line_number].baseline;\n    SkScalar top = baseline + run.font_metrics.fAscent;\n    SkScalar bottom = baseline + run.font_metrics.fDescent;\n\n    if (run.placeholder_run !=\n        nullptr) {  // Use inline placeholder size as height.\n      top = baseline - run.placeholder_run->baseline_offset;\n      bottom = baseline + run.placeholder_run->height -\n               run.placeholder_run->baseline_offset;\n    }\n\n    // Calculate left and right.\n    SkScalar left, right;\n    left = run.x_pos.start;\n    right = run.x_pos.end;\n\n    boxes.emplace_back(skity::Rect::MakeLTRB(left, top, right, bottom),\n                       run.direction);\n  }\n  return boxes;\n}\n\nParagraph::Range<size_t> ParagraphTxt::GetWordBoundary(size_t offset) {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  if (text_.size() == 0)\n    return Range<size_t>(0, 0);\n\n  if (!word_breaker_) {\n    UErrorCode status = U_ZERO_ERROR;\n    word_breaker_.reset(\n        icu::BreakIterator::createWordInstance(icu::Locale(), status));\n    if (!U_SUCCESS(status))\n      return Range<size_t>(0, 0);\n  }\n\n  icu::UnicodeString icu_text(false, text_.data(), text_.size());\n  word_breaker_->setText(icu_text);\n\n  int32_t prev_boundary = word_breaker_->preceding(offset + 1);\n  int32_t next_boundary = word_breaker_->next();\n  if (prev_boundary == icu::BreakIterator::DONE)\n    prev_boundary = offset;\n  if (next_boundary == icu::BreakIterator::DONE)\n    next_boundary = offset;\n  return Range<size_t>(prev_boundary, next_boundary);\n}\n\nsize_t ParagraphTxt::GetLineCount() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  return final_line_count_;\n}\n\nbool ParagraphTxt::DidExceedMaxLines() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  return did_exceed_max_lines_;\n}\n\nvoid ParagraphTxt::SetDirty(bool dirty) {\n  needs_layout_ = dirty;\n}\n\nstd::vector<LineMetrics>& ParagraphTxt::GetLineMetrics() {\n  FML_DCHECK(!needs_layout_) << \"only valid after layout\";\n  return line_metrics_;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/paragraph_txt.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_TXT_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_TXT_H_\n\n#include <limits>\n#include <memory>\n#include <set>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/fml/macros.h\"\n#include \"font_collection_skia.h\"\n#include \"line_metrics.h\"\n#include \"minikin/LineBreaker.h\"\n#include \"paint_record.h\"\n#include \"paragraph.h\"\n#include \"paragraph_style.h\"\n#include \"placeholder_run.h\"\n#include \"run_metrics.h\"\n#include \"styled_runs.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n#include \"third_party/skia/include/core/SkFontMetrics.h\"\n#include \"third_party/skia/include/core/SkRect.h\"\n#include \"utils/LinuxUtils.h\"\n#include \"utils/MacUtils.h\"\n#include \"utils/WindowsUtils.h\"\n\nnamespace txt {\n\nusing GlyphID = uint32_t;\n\n// Constant with the unicode codepoint for the \"Object replacement character\".\n// Used as a stand-in character for Placeholder boxes.\nconst int objReplacementChar = 0xFFFC;\n// Constant with the unicode codepoint for the \"Replacement character\". This is\n// the character that commonly renders as a black diamond with a white question\n// mark. Used to replace non-placeholder instances of 0xFFFC in the text buffer.\nconst int replacementChar = 0xFFFD;\n\n// Paragraph provides Layout, metrics, and painting capabilities for text. Once\n// a Paragraph is constructed with ParagraphBuilder::Build(), an example basic\n// workflow can be this:\n//\n//   std::unique_ptr<Paragraph> paragraph = paragraph_builder.Build();\n//   paragraph->Layout(<somewidthgoeshere>);\n//   paragraph->Paint(<someSkCanvas>, <xpos>, <ypos>);\nclass ParagraphTxt : public Paragraph {\n public:\n  // Constructor. It is highly recommended to construct a paragraph with a\n  // ParagraphBuilder.\n  ParagraphTxt();\n\n  virtual ~ParagraphTxt();\n\n  // Minikin Layout doLayout() and LineBreaker addStyleRun() has an\n  // O(N^2) (according to benchmarks) time complexity where N is the total\n  // number of characters. However, this is not significant for reasonably sized\n  // paragraphs. It is currently recommended to break up very long paragraphs\n  // (10k+ characters) to ensure speedy layout.\n  virtual void Layout(double width) override;\n\n  virtual void Paint(SkCanvas* canvas, double x, double y) override;\n\n  // Getter for paragraph_style_.\n  const ParagraphStyle& GetParagraphStyle() const;\n\n  // Returns the number of characters/unicode characters. AKA text_.size()\n  size_t TextSize() const;\n\n  double GetHeight() override;\n\n  double GetMaxWidth() override;\n\n  double GetLongestLine() override;\n\n  double GetAlphabeticBaseline() override;\n\n  double GetIdeographicBaseline() override;\n\n  double GetMaxIntrinsicWidth() override;\n\n  // Currently, calculated similarly to as GetLayoutWidth(), however this is not\n  // necessarily 100% correct in all cases.\n  double GetMinIntrinsicWidth() override;\n\n  std::vector<TextBox> GetRectsForRange(\n      size_t start,\n      size_t end,\n      RectHeightStyle rect_height_style,\n      RectWidthStyle rect_width_style) override;\n\n  PositionWithAffinity GetGlyphPositionAtCoordinate(double dx,\n                                                    double dy) override;\n\n  std::vector<Paragraph::TextBox> GetRectsForPlaceholders() override;\n\n  Range<size_t> GetWordBoundary(size_t offset) override;\n\n  // Returns the number of lines the paragraph takes up. If the text exceeds the\n  // amount width and maxlines provides, Layout() truncates the extra text from\n  // the layout and this will return the max lines allowed.\n  size_t GetLineCount();\n\n  bool DidExceedMaxLines() override;\n\n  // Gets the full vector of LineMetrics which includes detailed data on each\n  // line in the final layout.\n  std::vector<LineMetrics>& GetLineMetrics() override;\n\n  // Sets the needs_layout_ to dirty. When Layout() is called, a new Layout will\n  // be performed when this is set to true. Can also be used to prevent a new\n  // Layout from being calculated by setting to false.\n  void SetDirty(bool dirty = true);\n\n private:\n  friend class ParagraphBuilderTxt;\n  FRIEND_TEST(ParagraphTest, SimpleParagraph);\n  FRIEND_TEST(ParagraphTest, SimpleParagraphSmall);\n  FRIEND_TEST(ParagraphTest, SimpleRedParagraph);\n  FRIEND_TEST(ParagraphTest, RainbowParagraph);\n  FRIEND_TEST(ParagraphTest, DefaultStyleParagraph);\n  FRIEND_TEST(ParagraphTest, BoldParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, LeftAlignParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, RightAlignParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, CenterAlignParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, JustifyAlignParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, JustifyRTL);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, InlinePlaceholderLongestLine);\n  FRIEND_TEST_LINUX_ONLY(ParagraphTest, JustifyRTLNewLine);\n  FRIEND_TEST(ParagraphTest, DecorationsParagraph);\n  FRIEND_TEST(ParagraphTest, ItalicsParagraph);\n  FRIEND_TEST(ParagraphTest, ChineseParagraph);\n  FRIEND_TEST(ParagraphTest, DISABLED_ArabicParagraph);\n  FRIEND_TEST(ParagraphTest, SpacingParagraph);\n  FRIEND_TEST(ParagraphTest, LongWordParagraph);\n  FRIEND_TEST_LINUX_ONLY(ParagraphTest, KernScaleParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, NewlineParagraph);\n  FRIEND_TEST_LINUX_ONLY(ParagraphTest, EmojiParagraph);\n  FRIEND_TEST_LINUX_ONLY(ParagraphTest, EmojiMultiLineRectsParagraph);\n  FRIEND_TEST(ParagraphTest, HyphenBreakParagraph);\n  FRIEND_TEST(ParagraphTest, RepeatLayoutParagraph);\n  FRIEND_TEST(ParagraphTest, Ellipsize);\n  FRIEND_TEST(ParagraphTest, UnderlineShiftParagraph);\n  FRIEND_TEST(ParagraphTest, WavyDecorationParagraph);\n  FRIEND_TEST(ParagraphTest, SimpleShadow);\n  FRIEND_TEST(ParagraphTest, ComplexShadow);\n  FRIEND_TEST(ParagraphTest, FontFallbackParagraph);\n  FRIEND_TEST(ParagraphTest, InlinePlaceholder0xFFFCParagraph);\n  FRIEND_TEST(ParagraphTest, FontFeaturesParagraph);\n  FRIEND_TEST(ParagraphTest, GetGlyphPositionAtCoordinateSegfault);\n  FRIEND_TEST(ParagraphTest, KhmerLineBreaker);\n  FRIEND_TEST(ParagraphTest, TextHeightBehaviorRectsParagraph);\n\n  // Starting data to layout.\n  std::vector<uint16_t> text_;\n  // A vector of PlaceholderRuns, which detail the sizes, positioning and break\n  // behavior of the empty spaces to leave. Each placeholder span corresponds to\n  // a 0xFFFC (object replacement character) in text_, which indicates the\n  // position in the text where the placeholder will occur. There should be an\n  // equal number of 0xFFFC characters and elements in this vector.\n  std::vector<PlaceholderRun> inline_placeholders_;\n  // The indexes of the boxes that correspond to an inline placeholder.\n  std::vector<size_t> inline_placeholder_boxes_;\n  // The indexes of instances of 0xFFFC that correspond to placeholders. This is\n  // necessary since the user may pass in manually entered 0xFFFC values using\n  // AddText().\n  std::unordered_set<size_t> obj_replacement_char_indexes_;\n  StyledRuns runs_;\n  ParagraphStyle paragraph_style_;\n  std::shared_ptr<FontCollection> font_collection_;\n\n  minikin::LineBreaker breaker_;\n  mutable std::unique_ptr<icu::BreakIterator> word_breaker_;\n\n  std::vector<LineMetrics> line_metrics_;\n  size_t final_line_count_ = 0;\n  std::vector<double> line_widths_;\n\n  // Stores the result of Layout().\n  std::vector<PaintRecord> records_;\n\n  bool did_exceed_max_lines_;\n\n  // Strut metrics of zero will have no effect on the layout.\n  struct StrutMetrics {\n    double ascent = 0;  // Positive value to keep signs clear.\n    double descent = 0;\n    double leading = 0;\n    double half_leading = 0;\n    double line_height = 0;\n    bool force_strut = false;\n  };\n\n  StrutMetrics strut_;\n\n  // Overall left and right extremes over all lines.\n  double max_right_;\n  double min_left_;\n\n  class BidiRun {\n   public:\n    // Constructs a BidiRun with is_ghost defaulted to false.\n    BidiRun(size_t s, size_t e, TextDirection d, const TextStyle& st)\n        : start_(s), end_(e), direction_(d), style_(&st), is_ghost_(false) {}\n\n    // Constructs a BidiRun with a custom is_ghost flag.\n    BidiRun(size_t s,\n            size_t e,\n            TextDirection d,\n            const TextStyle& st,\n            bool is_ghost)\n        : start_(s), end_(e), direction_(d), style_(&st), is_ghost_(is_ghost) {}\n\n    // Constructs a placeholder bidi run.\n    BidiRun(size_t s,\n            size_t e,\n            TextDirection d,\n            const TextStyle& st,\n            PlaceholderRun& placeholder)\n        : start_(s),\n          end_(e),\n          direction_(d),\n          style_(&st),\n          is_ghost_(false),\n          placeholder_run_(&placeholder) {}\n\n    size_t start() const { return start_; }\n    size_t end() const { return end_; }\n    size_t size() const { return end_ - start_; }\n    TextDirection direction() const { return direction_; }\n    const TextStyle& style() const { return *style_; }\n    PlaceholderRun* placeholder_run() const { return placeholder_run_; }\n    bool is_rtl() const { return direction_ == TextDirection::rtl; }\n    // Tracks if the run represents trailing whitespace.\n    bool is_ghost() const { return is_ghost_; }\n    bool is_placeholder_run() const { return placeholder_run_ != nullptr; }\n\n   private:\n    size_t start_, end_;\n    TextDirection direction_;\n    const TextStyle* style_;\n    bool is_ghost_;\n    PlaceholderRun* placeholder_run_ = nullptr;\n  };\n\n  struct GlyphPosition {\n    Range<size_t> code_units;\n    Range<double> x_pos;\n\n    GlyphPosition(double x_start,\n                  double x_advance,\n                  size_t code_unit_index,\n                  size_t code_unit_width);\n\n    void Shift(double delta);\n  };\n\n  struct GlyphLine {\n    // Glyph positions sorted by x coordinate.\n    const std::vector<GlyphPosition> positions;\n    const size_t total_code_units;\n\n    GlyphLine(std::vector<GlyphPosition>&& p, size_t tcu);\n  };\n\n  struct CodeUnitRun {\n    // Glyph positions sorted by code unit index.\n    std::vector<GlyphPosition> positions;\n    Range<size_t> code_units;\n    Range<double> x_pos;\n    size_t line_number;\n    SkFontMetrics font_metrics;\n    const TextStyle* style;\n    TextDirection direction;\n    const PlaceholderRun* placeholder_run;\n\n    CodeUnitRun(std::vector<GlyphPosition>&& p,\n                Range<size_t> cu,\n                Range<double> x,\n                size_t line,\n                const SkFontMetrics& metrics,\n                const TextStyle& st,\n                TextDirection dir,\n                const PlaceholderRun* placeholder);\n\n    void Shift(double delta);\n  };\n\n  // Holds the laid out x positions of each glyph.\n  std::vector<GlyphLine> glyph_lines_;\n\n  // Holds the positions of each range of code units in the text.\n  // Sorted in code unit index order.\n  std::vector<CodeUnitRun> code_unit_runs_;\n  // Holds the positions of the inline placeholders.\n  std::vector<CodeUnitRun> inline_placeholder_code_unit_runs_;\n\n  // The max width of the paragraph as provided in the most recent Layout()\n  // call.\n  double width_ = -1.0f;\n  double longest_line_ = -1.0f;\n  double max_intrinsic_width_ = 0;\n  double min_intrinsic_width_ = 0;\n  double alphabetic_baseline_ = std::numeric_limits<double>::max();\n  double ideographic_baseline_ = std::numeric_limits<double>::max();\n\n  bool needs_layout_ = true;\n\n  struct WaveCoordinates {\n    double x_start;\n    double y_start;\n    double x_end;\n    double y_end;\n\n    WaveCoordinates(double x_s, double y_s, double x_e, double y_e)\n        : x_start(x_s), y_start(y_s), x_end(x_e), y_end(y_e) {}\n  };\n\n  // Passes in the text and Styled Runs. text_ and runs_ will later be passed\n  // into breaker_ in InitBreaker(), which is called in Layout().\n  void SetText(std::vector<uint16_t> text, StyledRuns runs);\n\n  void SetParagraphStyle(const ParagraphStyle& style);\n\n  void SetFontCollection(std::shared_ptr<FontCollection> font_collection);\n\n  void SetInlinePlaceholders(\n      std::vector<PlaceholderRun> inline_placeholders,\n      std::unordered_set<size_t> obj_replacement_char_indexes);\n\n  // Break the text into lines.\n  bool ComputeLineBreaks();\n\n  // Break the text into runs based on LTR/RTL text direction.\n  bool ComputeBidiRuns(std::vector<BidiRun>* result);\n\n  // Calculates and populates strut based on paragraph_style_ strut info.\n  void ComputeStrut(StrutMetrics* strut, SkFont& font);\n\n  // Adjusts the ascent and descent based on the existence and type of\n  // placeholder. This method sets the proper metrics to achieve the different\n  // PlaceholderAlignment options.\n  void ComputePlaceholder(PlaceholderRun* placeholder_run,\n                          double& ascent,\n                          double& descent);\n\n  bool IsStrutValid() const;\n\n  void UpdateLineMetrics(const SkFontMetrics& metrics,\n                         const TextStyle& style,\n                         double& max_ascent,\n                         double& max_descent,\n                         double& max_unscaled_ascent,\n                         PlaceholderRun* placeholder_run,\n                         size_t line_number,\n                         size_t line_limit);\n\n  // Calculate the starting X offset of a line based on the line's width and\n  // alignment.\n  double GetLineXOffset(double line_total_advance, bool justify_line);\n\n  // Creates and draws the decorations onto the canvas.\n  void PaintDecorations(SkCanvas* canvas,\n                        const PaintRecord& record,\n                        SkPoint base_offset);\n\n  // Computes the beziers for a wavy decoration. The results will be\n  // applied to path.\n  void ComputeWavyDecoration(SkPath& path,\n                             double x,\n                             double y,\n                             double width,\n                             double thickness);\n\n  // Draws the background onto the canvas.\n  void PaintBackground(SkCanvas* canvas,\n                       const PaintRecord& record,\n                       SkPoint base_offset);\n\n  // Draws the shadows onto the canvas.\n  void PaintShadow(SkCanvas* canvas, const PaintRecord& record, SkPoint offset);\n\n  // Obtain a Minikin font collection matching this text style.\n  std::shared_ptr<minikin::FontCollection> GetMinikinFontCollectionForStyle(\n      const TextStyle& style);\n\n  // Get a default SkTypeface for a text style.\n  sk_sp<SkTypeface> GetDefaultSkiaTypeface(const TextStyle& style);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ParagraphTxt);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_PARAGRAPH_TXT_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/placeholder_run.cc",
    "content": "/*\n * Copyright 2019 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"placeholder_run.h\"\n\nnamespace txt {\n\nPlaceholderRun::PlaceholderRun() {}\n\nPlaceholderRun::PlaceholderRun(double width,\n                               double height,\n                               PlaceholderAlignment alignment,\n                               TextBaseline baseline,\n                               double baseline_offset)\n    : width(width),\n      height(height),\n      alignment(alignment),\n      baseline(baseline),\n      baseline_offset(baseline_offset) {}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/placeholder_run.h",
    "content": "/*\n * Copyright 2019 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_PLACEHOLDER_RUN_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_PLACEHOLDER_RUN_H_\n\n#include \"text_baseline.h\"\n\nnamespace txt {\n\n/// Where to vertically align the placeholder relative to the surrounding text.\nenum class PlaceholderAlignment {\n  /// Match the baseline of the placeholder with the baseline.\n  kBaseline,\n\n  /// Align the bottom edge of the placeholder with the baseline such that the\n  /// placeholder sits on top of the baseline.\n  kAboveBaseline,\n\n  /// Align the top edge of the placeholder with the baseline specified in\n  /// such that the placeholder hangs below the baseline.\n  kBelowBaseline,\n\n  /// Align the top edge of the placeholder with the top edge of the font.\n  /// When the placeholder is very tall, the extra space will hang from\n  /// the top and extend through the bottom of the line.\n  kTop,\n\n  /// Align the bottom edge of the placeholder with the top edge of the font.\n  /// When the placeholder is very tall, the extra space will rise from\n  /// the bottom and extend through the top of the line.\n  kBottom,\n\n  /// Align the middle of the placeholder with the middle of the text. When the\n  /// placeholder is very tall, the extra space will grow equally from\n  /// the top and bottom of the line.\n  kMiddle,\n};\n\n// Represents the metrics required to fully define a rect that will fit a\n// placeholder.\n//\n// LibTxt will leave an empty space in the layout of the text of the size\n// defined by this class. After layout, the framework will draw placeholders\n// into the reserved space.\nclass PlaceholderRun {\n public:\n  double width = 0;\n  double height = 0;\n\n  PlaceholderAlignment alignment;\n\n  TextBaseline baseline;\n\n  // Distance from the top edge of the rect to the baseline position. This\n  // baseline will be aligned against the alphabetic baseline of the surrounding\n  // text.\n  //\n  // Positive values drop the baseline lower (positions the rect higher) and\n  // small or negative values will cause the rect to be positioned underneath\n  // the line. When baseline == height, the bottom edge of the rect will rest on\n  // the alphabetic baseline.\n  double baseline_offset = 0;\n\n  PlaceholderRun();\n\n  PlaceholderRun(double width,\n                 double height,\n                 PlaceholderAlignment alignment,\n                 TextBaseline baseline,\n                 double baseline_offset);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_PLACEHOLDER_RUN_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/platform.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"txt/platform.h\"\n\nnamespace txt {\n\nstd::vector<std::string> GetDefaultFontFamilies() {\n  return {\"Arial\"};\n}\n\n#ifdef ENABLE_SKITY\nstd::shared_ptr<skity::FontManager> GetDefaultFontManager(\n    uint32_t font_initialization_data) {\n  return skity::FontManager::RefDefault();\n}\n#else\nsk_sp<SkFontMgr> GetDefaultFontManager(uint32_t font_initialization_data) {\n  return SkFontMgr::RefDefault();\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/platform.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_PLATFORM_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_PLATFORM_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#ifdef ENABLE_SKITY\n#include \"skity/text/font_manager.hpp\"\n#else\n#include \"third_party/skia/include/core/SkFontMgr.h\"\n#endif\n\nnamespace txt {\n\nstd::vector<std::string> GetDefaultFontFamilies();\n\n#ifdef ENABLE_SKITY\nstd::shared_ptr<skity::FontManager> GetDefaultFontManager(\n    uint32_t font_initialization_data);\n#else\nsk_sp<SkFontMgr> GetDefaultFontManager(uint32_t font_initialization_data);\n#endif  // ENABLE_SKITY\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_PLATFORM_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/platform_android.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"txt/platform.h\"\n\nnamespace txt {\n\nstd::vector<std::string> GetDefaultFontFamilies() {\n  return {\"sans-serif\"};\n}\n\n#ifdef ENABLE_SKITY\nstd::shared_ptr<skity::FontManager> GetDefaultFontManager(\n    uint32_t font_initialization_data) {\n  return skity::FontManager::RefDefault();\n}\n#else\nsk_sp<SkFontMgr> GetDefaultFontManager(uint32_t font_initialization_data) {\n  return SkFontMgr::RefDefault();\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/platform_fuchsia.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <lib/zx/channel.h>\n\n#include \"third_party/skia/include/ports/SkFontMgr_fuchsia.h\"\n#include \"txt/platform.h\"\n\nnamespace txt {\n\nstd::vector<std::string> GetDefaultFontFamilies() {\n  return {\"Roboto\"};\n}\n\n#ifdef ENABLE_SKITY\nstd::shared_ptr<skity::FontManager> GetDefaultFontManager(\n    uint32_t font_initialization_data) {\n  return nullptr;\n}\n#else\nsk_sp<SkFontMgr> GetDefaultFontManager(uint32_t font_initialization_data) {\n  if (font_initialization_data) {\n    fuchsia::fonts::ProviderSyncPtr sync_font_provider;\n    sync_font_provider.Bind(zx::channel(font_initialization_data));\n    return SkFontMgr_New_Fuchsia(std::move(sync_font_provider));\n  } else {\n    return SkFontMgr::RefDefault();\n  }\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/platform_harmony.cc",
    "content": "/*\n * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * Copyright 2021 The Lynx Authors. All rights reserved.\n * Licensed under the Apache License Version 2.0 that can be found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#include \"txt/platform.h\"\n\nnamespace txt {\n\nstd::vector<std::string> GetDefaultFontFamilies() {\n  return {\"sans-serif\"};\n}\n\nsk_sp<SkFontMgr> GetDefaultFontManager(uint32_t font_initialization_data) {\n  return SkFontMgr::RefDefault();\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/platform_linux.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"txt/platform.h\"\n\n#ifdef FLUTTER_USE_FONTCONFIG\n#include \"third_party/skia/include/ports/SkFontMgr_fontconfig.h\"\n#else\n#include \"third_party/skia/include/ports/SkFontMgr_directory.h\"\n#endif\n\nnamespace txt {\n\nstd::vector<std::string> GetDefaultFontFamilies() {\n  return {\"Ubuntu\", \"Cantarell\", \"DejaVu Sans\", \"Liberation Sans\", \"Arial\"};\n}\n\n#ifdef ENABLE_SKITY\nstd::shared_ptr<skity::FontManager> GetDefaultFontManager(\n    uint32_t font_initialization_data) {\n  return nullptr;\n}\n#else\nsk_sp<SkFontMgr> GetDefaultFontManager(uint32_t font_initialization_data) {\n#ifdef FLUTTER_USE_FONTCONFIG\n  return SkFontMgr_New_FontConfig(nullptr);\n#else\n  return SkFontMgr_New_Custom_Directory(\"/usr/share/fonts/\");\n#endif\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/platform_mac.mm",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <TargetConditionals.h>\n\n#include \"clay/fml/platform/darwin/platform_version.h\"\n#include \"txt/platform.h\"\n\n#if TARGET_OS_EMBEDDED || TARGET_OS_SIMULATOR\n#include <UIKit/UIKit.h>\n#define FONT_CLASS UIFont\n#else  // TARGET_OS_EMBEDDED\n#include <AppKit/AppKit.h>\n#define FONT_CLASS NSFont\n#endif  // TARGET_OS_EMBEDDED\n\nnamespace txt {\n\nstd::vector<std::string> GetDefaultFontFamilies() {\n  if (fml::IsPlatformVersionAtLeast(9)) {\n    return {[FONT_CLASS systemFontOfSize:14].familyName.UTF8String};\n  } else {\n    return {\"Helvetica\"};\n  }\n}\n\n#ifdef ENABLE_SKITY\nstd::shared_ptr<skity::FontManager> GetDefaultFontManager(\n    uint32_t font_initialization_data) {\n  return skity::FontManager::RefDefault();\n}\n#else\nsk_sp<SkFontMgr> GetDefaultFontManager(uint32_t font_initialization_data) {\n  return SkFontMgr::RefDefault();\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/platform_windows.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef ENABLE_SKITY\n#include \"third_party/skia/include/ports/SkTypeface_win.h\"\n#endif\n#include \"txt/platform.h\"\n\nnamespace txt {\n\nstd::vector<std::string> GetDefaultFontFamilies() {\n  return {\"Segoe UI\", \"Arial\"};\n}\n\n#ifdef ENABLE_SKITY\nstd::shared_ptr<skity::FontManager> GetDefaultFontManager(\n    uint32_t font_initialization_data) {\n  return skity::FontManager::RefDefault();\n}\n#else\nsk_sp<SkFontMgr> GetDefaultFontManager(uint32_t font_initialization_data) {\n  return SkFontMgr_New_DirectWrite();\n}\n#endif  // ENABLE_SKITY\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/run_metrics.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_RUN_METRICS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_RUN_METRICS_H_\n\n#include \"text_style.h\"\n#include \"third_party/skia/include/core/SkFontMetrics.h\"\n\nnamespace txt {\n\n// Contains the font metrics and TextStyle of a unique run.\nclass RunMetrics {\n public:\n  explicit RunMetrics(const TextStyle* style) : text_style(style) {}\n\n  RunMetrics(const TextStyle* style, const SkFontMetrics& metrics)\n      : text_style(style), font_metrics(metrics) {}\n\n  const TextStyle* text_style;\n\n  // SkFontMetrics contains the following metrics:\n  //\n  // * Top                 distance to reserve above baseline\n  // * Ascent              distance to reserve below baseline\n  // * Descent             extent below baseline\n  // * Bottom              extent below baseline\n  // * Leading             distance to add between lines\n  // * AvgCharWidth        average character width\n  // * MaxCharWidth        maximum character width\n  // * XMin                minimum x\n  // * XMax                maximum x\n  // * XHeight             height of lower-case 'x'\n  // * CapHeight           height of an upper-case letter\n  // * UnderlineThickness  underline thickness\n  // * UnderlinePosition   underline position relative to baseline\n  // * StrikeoutThickness  strikeout thickness\n  // * StrikeoutPosition   strikeout position relative to baseline\n  SkFontMetrics font_metrics;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_RUN_METRICS_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/styled_runs.cc",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"styled_runs.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"utils/WindowsUtils.h\"\n\nnamespace txt {\n\nStyledRuns::StyledRuns() = default;\n\nStyledRuns::~StyledRuns() = default;\n\nStyledRuns::StyledRuns(StyledRuns&& other) {\n  styles_.swap(other.styles_);\n  runs_.swap(other.runs_);\n}\n\nconst StyledRuns& StyledRuns::operator=(StyledRuns&& other) {\n  styles_.swap(other.styles_);\n  runs_.swap(other.runs_);\n  return *this;\n}\n\nvoid StyledRuns::swap(StyledRuns& other) {\n  styles_.swap(other.styles_);\n  runs_.swap(other.runs_);\n}\n\nsize_t StyledRuns::AddStyle(const TextStyle& style) {\n  const size_t style_index = styles_.size();\n  styles_.push_back(style);\n  return style_index;\n}\n\nconst TextStyle& StyledRuns::GetStyle(size_t style_index) const {\n  return styles_[style_index];\n}\n\nvoid StyledRuns::StartRun(size_t style_index, size_t start) {\n  EndRunIfNeeded(start);\n  runs_.push_back(IndexedRun{style_index, start, start});\n}\n\nvoid StyledRuns::EndRunIfNeeded(size_t end) {\n  if (runs_.empty())\n    return;\n  IndexedRun& run = runs_.back();\n  if (run.start == end) {\n    // The run is empty. We can skip it.\n    runs_.pop_back();\n  } else {\n    run.end = end;\n  }\n}\n\nStyledRuns::Run StyledRuns::GetRun(size_t index) const {\n  const IndexedRun& run = runs_[index];\n  return Run{styles_[run.style_index], run.start, run.end};\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/styled_runs.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_STYLED_RUNS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_STYLED_RUNS_H_\n\n#include <list>\n#include <utility>\n#include <vector>\n\n#include \"text_style.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n#include \"utils/WindowsUtils.h\"\n\nnamespace txt {\n\n// This holds and handles the start/end positions of discrete chunks of text\n// that use different styles (a 'run').\nclass StyledRuns {\n public:\n  struct Run {\n    const TextStyle& style;\n    size_t start;\n    size_t end;\n  };\n\n  StyledRuns();\n\n  ~StyledRuns();\n\n  StyledRuns(const StyledRuns& other) = delete;\n\n  StyledRuns(StyledRuns&& other);\n\n  const StyledRuns& operator=(StyledRuns&& other);\n\n  void swap(StyledRuns& other);\n\n  size_t AddStyle(const TextStyle& style);\n\n  const TextStyle& GetStyle(size_t style_index) const;\n\n  void StartRun(size_t style_index, size_t start);\n\n  void EndRunIfNeeded(size_t end);\n\n  size_t size() const { return runs_.size(); }\n\n  Run GetRun(size_t index) const;\n\n private:\n  FRIEND_TEST(ParagraphTest, SimpleParagraph);\n  FRIEND_TEST(ParagraphTest, SimpleParagraphSmall);\n  FRIEND_TEST(ParagraphTest, SimpleRedParagraph);\n  FRIEND_TEST(ParagraphTest, RainbowParagraph);\n  FRIEND_TEST(ParagraphTest, DefaultStyleParagraph);\n  FRIEND_TEST(ParagraphTest, BoldParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, LeftAlignParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, RightAlignParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, CenterAlignParagraph);\n  FRIEND_TEST_WINDOWS_DISABLED(ParagraphTest, JustifyAlignParagraph);\n  FRIEND_TEST(ParagraphTest, DecorationsParagraph);\n  FRIEND_TEST(ParagraphTest, ItalicsParagraph);\n  FRIEND_TEST(ParagraphTest, ChineseParagraph);\n  FRIEND_TEST(ParagraphTest, DISABLED_ArabicParagraph);\n  FRIEND_TEST(ParagraphTest, LongWordParagraph);\n  FRIEND_TEST(ParagraphTest, KernParagraph);\n  FRIEND_TEST(ParagraphTest, HyphenBreakParagraph);\n  FRIEND_TEST(ParagraphTest, RepeatLayoutParagraph);\n  FRIEND_TEST(ParagraphTest, Ellipsize);\n  FRIEND_TEST(ParagraphTest, SimpleShadow);\n  FRIEND_TEST(ParagraphTest, ComplexShadow);\n  FRIEND_TEST(ParagraphTest, FontFallbackParagraph);\n\n  struct IndexedRun {\n    size_t style_index = 0;\n    size_t start = 0;\n    size_t end = 0;\n\n    explicit IndexedRun(size_t style_index, size_t start, size_t end)\n        : style_index(style_index), start(start), end(end) {}\n  };\n\n  std::vector<TextStyle> styles_;\n  std::vector<IndexedRun> runs_;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_STYLED_RUNS_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/test_font_manager.cc",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"txt/test_font_manager.h\"\n#include \"clay/fml/logging.h\"\n\nnamespace txt {\n\nTestFontManager::TestFontManager(\n    std::unique_ptr<FontAssetProvider> font_provider,\n    std::vector<std::string> test_font_family_names)\n    : AssetFontManager(std::move(font_provider)),\n      test_font_family_names_(test_font_family_names) {}\n\nTestFontManager::~TestFontManager() = default;\n\nsk_sp<SkFontStyleSet> TestFontManager::onMatchFamily(\n    const char family_name[]) const {\n  // Find the requested name in the list, if not found, default to the first\n  // font family in the test font family list.\n  std::string requested_name(family_name);\n  std::string sanitized_name = test_font_family_names_[0];\n  for (const std::string& test_family : test_font_family_names_) {\n    if (requested_name == test_family) {\n      sanitized_name = test_family;\n    }\n  }\n  return AssetFontManager::onMatchFamily(sanitized_name.c_str());\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/test_font_manager.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_TEST_FONT_MANAGER_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_TEST_FONT_MANAGER_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"third_party/skia/include/core/SkFontMgr.h\"\n#include \"txt/asset_font_manager_skia.h\"\n#include \"txt/font_asset_provider.h\"\n\nnamespace txt {\n\n// A font manager intended for tests that matches all requested fonts using\n// one family.\nclass TestFontManager : public AssetFontManager {\n public:\n  TestFontManager(std::unique_ptr<FontAssetProvider> font_provider,\n                  std::vector<std::string> test_font_family_names);\n\n  ~TestFontManager() override;\n\n private:\n  std::vector<std::string> test_font_family_names_;\n\n  sk_sp<SkFontStyleSet> onMatchFamily(const char family_name[]) const override;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TestFontManager);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_TEST_FONT_MANAGER_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/text_baseline.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_BASELINE_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_BASELINE_H_\n\nnamespace txt {\n\nenum TextBaseline {\n  kAlphabetic,\n  kIdeographic,\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_BASELINE_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/text_decoration.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <algorithm>\n#include <vector>\n\n#include \"text_decoration.h\"\n\nnamespace txt {\n\n//\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/text_decoration.h",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_DECORATION_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_DECORATION_H_\n\nnamespace txt {\n\n// Multiple decorations can be applied at once. Ex: Underline and overline is\n// (0x1 | 0x2)\nenum TextDecoration {\n  kNone = 0x0,\n  kUnderline = 0x1,\n  kOverline = 0x2,\n  kLineThrough = 0x4,\n};\n\nenum TextDecorationStyle { kSolid, kDouble, kDotted, kDashed, kWavy };\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_DECORATION_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/text_shadow.cc",
    "content": "/*\n * Copyright 2018 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"text_shadow.h\"\n\nnamespace txt {\n\nTextShadow::TextShadow() {}\nTextShadow::TextShadow(clay::Color color, skity::Vec2 offset, double blur_sigma)\n    : color(color), offset(offset), blur_sigma(blur_sigma) {}\n\nbool TextShadow::operator==(const TextShadow& other) const {\n  if (color != other.color)\n    return false;\n  if (offset != other.offset)\n    return false;\n  if (blur_sigma != other.blur_sigma)\n    return false;\n\n  return true;\n}\n\nbool TextShadow::operator!=(const TextShadow& other) const {\n  return !(*this == other);\n}\n\nbool TextShadow::hasShadow() const {\n  if (offset.x != 0 && offset.y != 0)\n    return true;\n  if (blur_sigma > 0.5)\n    return true;\n\n  return false;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/text_shadow.h",
    "content": "/*\n * Copyright 2018 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_SHADOW_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_SHADOW_H_\n\n#include \"clay/gfx/style/color.h\"\n#include \"skity/geometry/vector.hpp\"\n\nnamespace txt {\n\nclass TextShadow {\n public:\n  clay::Color color = clay::Color::kBlack();\n  skity::Vec2 offset;\n  double blur_sigma = 0.0;\n\n  TextShadow();\n\n  TextShadow(clay::Color color, skity::Vec2 offset, double blur_sigma);\n\n  bool operator==(const TextShadow& other) const;\n\n  bool operator!=(const TextShadow& other) const;\n\n  bool hasShadow() const;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_SHADOW_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/text_style.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"text_style.h\"\n\n#include \"font_style.h\"\n#include \"font_weight.h\"\n#include \"txt/platform.h\"\n\nnamespace txt {\n\nTextStyle::TextStyle() : font_families(GetDefaultFontFamilies()) {}\n\nbool TextStyle::equals(const TextStyle& other) const {\n  if (color != other.color)\n    return false;\n  if (decoration != other.decoration)\n    return false;\n  if (decoration_color != other.decoration_color)\n    return false;\n  if (decoration_style != other.decoration_style)\n    return false;\n  if (decoration_thickness_multiplier != other.decoration_thickness_multiplier)\n    return false;\n  if (font_weight != other.font_weight)\n    return false;\n  if (font_style != other.font_style)\n    return false;\n  if (letter_spacing != other.letter_spacing)\n    return false;\n  if (word_spacing != other.word_spacing)\n    return false;\n  if (height != other.height)\n    return false;\n  if (has_height_override != other.has_height_override)\n    return false;\n  if (half_leading != other.half_leading)\n    return false;\n  if (locale != other.locale)\n    return false;\n#if defined(ENABLE_SKITY)\n    // TODO(zhangxiao.ninja) should skity implement operator !=?\n#else\n  if (foreground != other.foreground)\n    return false;\n#endif  // ENABLE_SKITY\n  if (word_break != other.word_break)\n    return false;\n  if (font_families.size() != other.font_families.size())\n    return false;\n  if (text_shadows.size() != other.text_shadows.size())\n    return false;\n  for (size_t font_index = 0; font_index < font_families.size(); ++font_index) {\n    if (font_families[font_index] != other.font_families[font_index])\n      return false;\n  }\n  for (size_t shadow_index = 0; shadow_index < text_shadows.size();\n       ++shadow_index) {\n    if (text_shadows[shadow_index] != other.text_shadows[shadow_index])\n      return false;\n  }\n\n  return true;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/text_style.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_STYLE_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_STYLE_H_\n\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/rendering_backend.h\"\n#include \"font_features.h\"\n#include \"font_style.h\"\n#include \"font_weight.h\"\n#include \"text_baseline.h\"\n#include \"text_decoration.h\"\n#include \"text_shadow.h\"\n#if defined(CLAY_ENABLE_TTTEXT)\n#include <textra/layout_definition.h>\n#endif  // ENABLE_TTTEXT\n\nnamespace txt {\nenum WordBreak : uint8_t {\n  kNormal = 0,\n  kBreakAll = 1,\n  kKeepAll = 2,\n};\nclass TextStyle {\n public:\n  clay::Color color = clay::Color::kBlack();\n  int decoration = TextDecoration::kNone;\n  // Does not make sense to draw a transparent object, so we use it as a default\n  // value to indicate no decoration color was set.\n  clay::Color decoration_color = clay::Color::kTransparent();\n  TextDecorationStyle decoration_style = TextDecorationStyle::kSolid;\n  // Thickness is applied as a multiplier to the default thickness of the font.\n  double decoration_thickness_multiplier = 1.0;\n  FontWeight font_weight = FontWeight::w400;\n  FontStyle font_style = FontStyle::normal;\n  TextBaseline text_baseline = TextBaseline::kAlphabetic;\n  float text_baseline_shift = 0.0;\n  bool half_leading = true;\n  // An ordered list of fonts in order of priority. The first font is more\n  // highly preferred than the last font.\n  std::vector<std::string> font_families;\n  double font_size = 14.0;\n  double letter_spacing = 0.0;\n  double word_spacing = 0.0;\n  double height = 1.0;\n  double line_spacing = 0.0;\n  bool has_height_override = false;\n  std::string locale;\n  bool has_background = false;\n  clay::GrPaint background;\n  bool has_foreground = false;\n  clay::GrPaint foreground;\n  bool has_foreground_id = false;\n  int foreground_id;\n  // An ordered list of shadows where the first shadow will be drawn first (at\n  // the bottom).\n  std::vector<TextShadow> text_shadows;\n  FontFeatures font_features;\n  FontVariations font_variations;\n  WordBreak word_break = kNormal;\n#if defined(CLAY_ENABLE_TTTEXT)\n  ttoffice::tttext::CharacterVerticalAlignment align_type =\n      ttoffice::tttext::CharacterVerticalAlignment::kBaseLine;\n  bool enable_text_bounds_ = false;\n  float stroke_width = 0;\n  clay::Color stroke_color = clay::Color::kTransparent();\n#endif  // CLAY_ENABLE_TTTEXT\n\n  TextStyle();\n\n  bool equals(const TextStyle& other) const;\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_TEXT_STYLE_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/typeface_font_asset_provider_skia.cc",
    "content": "/*\n * Copyright 2018 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"txt/typeface_font_asset_provider_skia.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"third_party/skia/include/core/SkString.h\"\n#include \"third_party/skia/include/core/SkTypeface.h\"\n\nnamespace txt {\n\nTypefaceFontAssetProvider::TypefaceFontAssetProvider() = default;\n\nTypefaceFontAssetProvider::~TypefaceFontAssetProvider() = default;\n\n// |FontAssetProvider|\nsize_t TypefaceFontAssetProvider::GetFamilyCount() const {\n  return family_names_.size();\n}\n\n// |FontAssetProvider|\nstd::string TypefaceFontAssetProvider::GetFamilyName(int index) const {\n  return family_names_[index];\n}\n\n// |FontAssetProvider|\nsk_sp<SkFontStyleSet> TypefaceFontAssetProvider::MatchFamily(\n    const std::string& family_name) {\n  auto found = registered_families_.find(CanonicalFamilyName(family_name));\n  if (found == registered_families_.end()) {\n    return nullptr;\n  }\n  return found->second;\n}\n\nvoid TypefaceFontAssetProvider::RegisterTypeface(sk_sp<SkTypeface> typeface) {\n  if (typeface == nullptr) {\n    return;\n  }\n\n  SkString sk_family_name;\n  typeface->getFamilyName(&sk_family_name);\n\n  std::string family_name(sk_family_name.c_str(), sk_family_name.size());\n  RegisterTypeface(std::move(typeface), std::move(family_name));\n}\n\nvoid TypefaceFontAssetProvider::RegisterTypeface(\n    sk_sp<SkTypeface> typeface,\n    std::string family_name_alias) {\n  if (family_name_alias.empty()) {\n    return;\n  }\n\n  std::string canonical_name = CanonicalFamilyName(family_name_alias);\n  auto family_it = registered_families_.find(canonical_name);\n  if (family_it == registered_families_.end()) {\n    family_names_.push_back(family_name_alias);\n    auto value =\n        std::make_pair(canonical_name, sk_make_sp<TypefaceFontStyleSet>());\n    family_it = registered_families_.emplace(value).first;\n  }\n  family_it->second->registerTypeface(std::move(typeface));\n}\n\nTypefaceFontStyleSet::TypefaceFontStyleSet() = default;\n\nTypefaceFontStyleSet::~TypefaceFontStyleSet() = default;\n\nvoid TypefaceFontStyleSet::registerTypeface(sk_sp<SkTypeface> typeface) {\n  if (typeface == nullptr) {\n    return;\n  }\n  typefaces_.emplace_back(std::move(typeface));\n}\n\nint TypefaceFontStyleSet::count() {\n  return typefaces_.size();\n}\n\nvoid TypefaceFontStyleSet::getStyle(int index,\n                                    SkFontStyle* style,\n                                    SkString* name) {\n  FML_DCHECK(static_cast<size_t>(index) < typefaces_.size());\n  if (style) {\n    *style = typefaces_[index]->fontStyle();\n  }\n  if (name) {\n    name->reset();\n  }\n}\n\nsk_sp<SkTypeface> TypefaceFontStyleSet::createTypeface(int i) {\n  size_t index = i;\n  if (index >= typefaces_.size()) {\n    return nullptr;\n  }\n  return typefaces_[index];\n}\n\nsk_sp<SkTypeface> TypefaceFontStyleSet::matchStyle(const SkFontStyle& pattern) {\n  return matchStyleCSS3(pattern);\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/typeface_font_asset_provider_skia.h",
    "content": "/*\n * Copyright 2018 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_TYPEFACE_FONT_ASSET_PROVIDER_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_TYPEFACE_FONT_ASSET_PROVIDER_H_\n\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"third_party/skia/include/core/SkFontMgr.h\"\n#include \"txt/font_asset_provider.h\"\n\nnamespace txt {\n\nclass TypefaceFontStyleSet : public SkFontStyleSet {\n public:\n  TypefaceFontStyleSet();\n\n  ~TypefaceFontStyleSet() override;\n\n  void registerTypeface(sk_sp<SkTypeface> typeface);\n\n  // |SkFontStyleSet|\n  int count() override;\n\n  // |SkFontStyleSet|\n  void getStyle(int index, SkFontStyle* style, SkString* name) override;\n\n  // |SkFontStyleSet|\n  sk_sp<SkTypeface> createTypeface(int index) override;\n\n  // |SkFontStyleSet|\n  sk_sp<SkTypeface> matchStyle(const SkFontStyle& pattern) override;\n\n private:\n  std::vector<sk_sp<SkTypeface>> typefaces_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TypefaceFontStyleSet);\n};\n\nclass TypefaceFontAssetProvider : public FontAssetProvider {\n public:\n  TypefaceFontAssetProvider();\n  ~TypefaceFontAssetProvider() override;\n\n  void RegisterTypeface(sk_sp<SkTypeface> typeface);\n\n  void RegisterTypeface(sk_sp<SkTypeface> typeface,\n                        std::string family_name_alias);\n\n  // |FontAssetProvider|\n  size_t GetFamilyCount() const override;\n\n  // |FontAssetProvider|\n  std::string GetFamilyName(int index) const override;\n\n  // |FontAssetProvider|\n  sk_sp<SkFontStyleSet> MatchFamily(const std::string& family_name) override;\n\n private:\n  std::unordered_map<std::string, sk_sp<TypefaceFontStyleSet>>\n      registered_families_;\n  std::vector<std::string> family_names_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TypefaceFontAssetProvider);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_TYPEFACE_FONT_ASSET_PROVIDER_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/typeface_font_asset_provider_skity.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"txt/typeface_font_asset_provider_skity.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"skity/text/typeface.hpp\"\n\nnamespace txt {\n\nTypefaceFontAssetProvider::TypefaceFontAssetProvider() = default;\n\nTypefaceFontAssetProvider::~TypefaceFontAssetProvider() = default;\n\n// |FontAssetProvider|\nsize_t TypefaceFontAssetProvider::GetFamilyCount() const {\n  return family_names_.size();\n}\n\n// |FontAssetProvider|\nstd::string TypefaceFontAssetProvider::GetFamilyName(int index) const {\n  return family_names_[index];\n}\n\n// |FontAssetProvider|\nstd::shared_ptr<skity::FontStyleSet> TypefaceFontAssetProvider::MatchFamily(\n    const std::string& family_name) {\n  auto found = registered_families_.find(CanonicalFamilyName(family_name));\n  if (found == registered_families_.end()) {\n    return nullptr;\n  }\n  return found->second;\n}\n\nvoid TypefaceFontAssetProvider::RegisterTypeface(std::shared_ptr<skity::Typeface> typeface) {\n  if (typeface == nullptr) {\n    return;\n  }\n\n  // TODO(jingle) There is no need for now\n  std::string sk_family_name = \"\";\n  // typeface->GetFamilyName(&sk_family_name);\n\n  std::string family_name(sk_family_name.c_str(), sk_family_name.size());\n  RegisterTypeface(std::move(typeface), std::move(family_name));\n}\n\nvoid TypefaceFontAssetProvider::RegisterTypeface(\n    std::shared_ptr<skity::Typeface> typeface,\n    std::string family_name_alias) {\n  if (family_name_alias.empty()) {\n    return;\n  }\n\n  std::string canonical_name = CanonicalFamilyName(family_name_alias);\n  auto family_it = registered_families_.find(canonical_name);\n  if (family_it == registered_families_.end()) {\n    family_names_.push_back(family_name_alias);\n    auto value = std::make_pair(canonical_name,\n                                std::make_shared<TypefaceFontStyleSet>());\n    family_it = registered_families_.emplace(std::move(value)).first;\n  }\n  family_it->second->registerTypeface(std::move(typeface));\n}\n\nTypefaceFontStyleSet::TypefaceFontStyleSet() = default;\n\nTypefaceFontStyleSet::~TypefaceFontStyleSet() = default;\n\nvoid TypefaceFontStyleSet::registerTypeface(std::shared_ptr<skity::Typeface> typeface) {\n  if (typeface == nullptr) {\n    return;\n  }\n  typefaces_.emplace_back(std::move(typeface));\n}\n\nint TypefaceFontStyleSet::Count() {\n  return typefaces_.size();\n}\n\nvoid TypefaceFontStyleSet::GetStyle(int index,\n                                    skity::FontStyle* style,\n                                    std::string* name) {\n  FML_DCHECK(static_cast<size_t>(index) < typefaces_.size());\n  if (style) {\n    *style = typefaces_[index]->GetFontStyle();\n  }\n  if (name) {\n    name->clear();\n  }\n}\n\nstd::shared_ptr<skity::Typeface> TypefaceFontStyleSet::CreateTypeface(int i) {\n  size_t index = i;\n  if (index >= typefaces_.size()) {\n    return nullptr;\n  }\n  return typefaces_[index];\n}\n\nstd::shared_ptr<skity::Typeface> TypefaceFontStyleSet::MatchStyle(\n    const skity::FontStyle& pattern) {\n  return MatchStyleCSS3(pattern);\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/src/txt/typeface_font_asset_provider_skity.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_TXT_TYPEFACE_FONT_ASSET_PROVIDER_SKITY_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_TXT_TYPEFACE_FONT_ASSET_PROVIDER_SKITY_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"skity/text/font_manager.hpp\"\n#include \"base/include/fml/macros.h\"\n#include \"txt/font_asset_provider.h\"\n\nnamespace txt {\n\nclass TypefaceFontStyleSet : public skity::FontStyleSet {\n public:\n  TypefaceFontStyleSet();\n\n  ~TypefaceFontStyleSet() override;\n\n  void registerTypeface(std::shared_ptr<skity::Typeface> typeface);\n\n  // |SkFontStyleSet|\n  int Count() override;\n\n  // |SkFontStyleSet|\n  void GetStyle(int index, skity::FontStyle* style, std::string* name) override;\n\n  // |SkFontStyleSet|\n  std::shared_ptr<skity::Typeface> CreateTypeface(int index) override;\n\n  // |SkFontStyleSet|\n  std::shared_ptr<skity::Typeface> MatchStyle(const skity::FontStyle& pattern) override;\n\n private:\n  std::vector<std::shared_ptr<skity::Typeface>> typefaces_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TypefaceFontStyleSet);\n};\n\nclass TypefaceFontAssetProvider : public FontAssetProvider {\n public:\n  TypefaceFontAssetProvider();\n  ~TypefaceFontAssetProvider() override;\n\n  void RegisterTypeface(std::shared_ptr<skity::Typeface> typeface);\n\n  void RegisterTypeface(std::shared_ptr<skity::Typeface> typeface,\n                        std::string family_name_alias);\n\n  // |FontAssetProvider|\n  size_t GetFamilyCount() const override;\n\n  // |FontAssetProvider|\n  std::string GetFamilyName(int index) const override;\n\n  // |FontAssetProvider|\n  std::shared_ptr<skity::FontStyleSet> MatchFamily(const std::string& family_name) override;\n\n private:\n  std::unordered_map<std::string, std::shared_ptr<TypefaceFontStyleSet>>\n      registered_families_;\n  std::vector<std::string> family_names_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(TypefaceFontAssetProvider);\n};\n\n}  // namespace txt\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_TXT_TYPEFACE_FONT_ASSET_PROVIDER_SKITY_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/utils/JenkinsHash.cpp",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* Implementation of Jenkins one-at-a-time hash function. These choices are\n * optimized for code size and portability, rather than raw speed. But speed\n * should still be quite good.\n **/\n\n#include <stdlib.h>\n#include <utils/JenkinsHash.h>\n\nnamespace android {\n\n#ifdef __clang__\n__attribute__((no_sanitize(\"integer\")))\n#endif\nhash_t\nJenkinsHashWhiten(uint32_t hash) {\n  hash += (hash << 3);\n  hash ^= (hash >> 11);\n  hash += (hash << 15);\n  return hash;\n}\n\nuint32_t JenkinsHashMixBytes(uint32_t hash, const uint8_t* bytes, size_t size) {\n  if (size > UINT32_MAX) {\n    abort();\n  }\n  hash = JenkinsHashMix(hash, (uint32_t)size);\n  size_t i;\n  for (i = 0; i < (size & -4); i += 4) {\n    uint32_t data = bytes[i] | (bytes[i + 1] << 8) | (bytes[i + 2] << 16) |\n                    (bytes[i + 3] << 24);\n    hash = JenkinsHashMix(hash, data);\n  }\n  if (size & 3) {\n    uint32_t data = bytes[i];\n    data |= ((size & 3) > 1) ? (bytes[i + 1] << 8) : 0;\n    data |= ((size & 3) > 2) ? (bytes[i + 2] << 16) : 0;\n    hash = JenkinsHashMix(hash, data);\n  }\n  return hash;\n}\n\nuint32_t JenkinsHashMixShorts(uint32_t hash,\n                              const uint16_t* shorts,\n                              size_t size) {\n  if (size > UINT32_MAX) {\n    abort();\n  }\n  hash = JenkinsHashMix(hash, (uint32_t)size);\n  size_t i;\n  for (i = 0; i < (size & -2); i += 2) {\n    uint32_t data = shorts[i] | (shorts[i + 1] << 16);\n    hash = JenkinsHashMix(hash, data);\n  }\n  if (size & 1) {\n    uint32_t data = shorts[i];\n    hash = JenkinsHashMix(hash, data);\n  }\n  return hash;\n}\n\n}  // namespace android\n"
  },
  {
    "path": "clay/third_party/txt/src/utils/JenkinsHash.h",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* Implementation of Jenkins one-at-a-time hash function. These choices are\n * optimized for code size and portability, rather than raw speed. But speed\n * should still be quite good.\n **/\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_UTILS_JENKINSHASH_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_UTILS_JENKINSHASH_H_\n\n#include <utils/TypeHelpers.h>\n\nnamespace android {\n\n/* The Jenkins hash of a sequence of 32 bit words A, B, C is:\n * Whiten(Mix(Mix(Mix(0, A), B), C)) */\n\n#ifdef __clang__\n__attribute__((no_sanitize(\"integer\")))\n#endif\ninline uint32_t\nJenkinsHashMix(uint32_t hash, uint32_t data) {\n  hash += data;\n  hash += (hash << 10);\n  hash ^= (hash >> 6);\n  return hash;\n}\n\nhash_t JenkinsHashWhiten(uint32_t hash);\n\n/* Helpful utility functions for hashing data in 32 bit chunks */\nuint32_t JenkinsHashMixBytes(uint32_t hash, const uint8_t* bytes, size_t size);\n\nuint32_t JenkinsHashMixShorts(uint32_t hash,\n                              const uint16_t* shorts,\n                              size_t size);\n\n}  // namespace android\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_UTILS_JENKINSHASH_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/utils/LinuxUtils.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_UTILS_LINUXUTILS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_UTILS_LINUXUTILS_H_\n\n#if defined(__linux__)\n#define DISABLE_TEST_LINUX(TEST_NAME) TEST_NAME\n#define FRIEND_TEST_LINUX_DISABLED_EXPANDED(SUITE, TEST_NAME) \\\n  FRIEND_TEST(SUITE, TEST_NAME)\n#define FRIEND_TEST_LINUX_DISABLED(SUITE, TEST_NAME) \\\n  FRIEND_TEST_LINUX_DISABLED_EXPANDED(SUITE, DISABLE_TEST_LINUX(TEST_NAME))\n\n#define FRIEND_TEST_LINUX_ONLY(SUITE, TEST_NAME) FRIEND_TEST(SUITE, TEST_NAME)\n#define LINUX_ONLY(TEST_NAME) TEST_NAME\n\n#else\n#define DISABLE_TEST_LINUX(TEST_NAME) DISABLED_##TEST_NAME\n#define FRIEND_TEST_LINUX_DISABLED(SUITE, TEST_NAME) \\\n  FRIEND_TEST(SUITE, TEST_NAME)\n\n#define LINUX_ONLY(TEST_NAME) DISABLED_##TEST_NAME\n#define FRIEND_TEST_LINUX_ONLY_EXPANDED(SUITE, TEST_NAME) \\\n  FRIEND_TEST(SUITE, TEST_NAME)\n#define FRIEND_TEST_LINUX_ONLY(SUITE, TEST_NAME) \\\n  FRIEND_TEST_LINUX_ONLY_EXPANDED(SUITE, DISABLE_TEST_LINUX(TEST_NAME))\n#endif  // defined(__linux__)\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_UTILS_LINUXUTILS_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/utils/LruCache.h",
    "content": "/*\n * Copyright (C) 2012 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_UTILS_LRUCACHE_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_UTILS_LRUCACHE_H_\n\n#include <memory>\n#include <unordered_set>\n\n#include \"utils/TypeHelpers.h\"  // hash_t\n\nnamespace android {\n\n/**\n * GenerationCache callback used when an item is removed\n */\ntemplate <typename EntryKey, typename EntryValue>\nclass OnEntryRemoved {\n public:\n  virtual ~OnEntryRemoved(){};\n  virtual void operator()(EntryKey& key, EntryValue& value) = 0;\n};  // class OnEntryRemoved\n\ntemplate <typename TKey, typename TValue>\nclass LruCache {\n public:\n  explicit LruCache(uint32_t maxCapacity);\n  virtual ~LruCache();\n\n  enum Capacity {\n    kUnlimitedCapacity,\n  };\n\n  void setOnEntryRemovedListener(OnEntryRemoved<TKey, TValue>* listener);\n  size_t size() const;\n  const TValue& get(const TKey& key);\n  bool put(const TKey& key, const TValue& value);\n  bool remove(const TKey& key);\n  bool removeOldest();\n  void clear();\n  const TValue& peekOldestValue();\n\n private:\n  LruCache(const LruCache& that);  // disallow copy constructor\n\n  // Super class so that we can have entries having only a key reference, for\n  // searches.\n  class KeyedEntry {\n   public:\n    virtual const TKey& getKey() const = 0;\n    // Make sure the right destructor is executed so that keys and values are\n    // deleted.\n    virtual ~KeyedEntry() {}\n  };\n\n  class Entry final : public KeyedEntry {\n   public:\n    TKey key;\n    TValue value;\n    Entry* parent;\n    Entry* child;\n\n    Entry(TKey _key, TValue _value)\n        : key(_key), value(_value), parent(NULL), child(NULL) {}\n    const TKey& getKey() const final { return key; }\n  };\n\n  class EntryForSearch : public KeyedEntry {\n   public:\n    const TKey& key;\n    EntryForSearch(const TKey& key_) : key(key_) {}\n    const TKey& getKey() const final { return key; }\n  };\n\n  struct HashForEntry {\n    size_t operator()(const KeyedEntry* entry) const {\n      return hash_type(entry->getKey());\n    };\n  };\n\n  struct EqualityForHashedEntries {\n    bool operator()(const KeyedEntry* lhs, const KeyedEntry* rhs) const {\n      return lhs->getKey() == rhs->getKey();\n    };\n  };\n\n  // All entries in the set will be Entry*. Using the weaker KeyedEntry as to\n  // allow entries that have only a key reference, for searching.\n  typedef std::\n      unordered_set<KeyedEntry*, HashForEntry, EqualityForHashedEntries>\n          LruCacheSet;\n\n  void attachToCache(Entry& entry);\n  void detachFromCache(Entry& entry);\n\n  typename LruCacheSet::iterator findByKey(const TKey& key) {\n    EntryForSearch entryForSearch(key);\n    typename LruCacheSet::iterator result = mSet->find(&entryForSearch);\n    return result;\n  }\n\n  std::unique_ptr<LruCacheSet> mSet;\n  OnEntryRemoved<TKey, TValue>* mListener;\n  Entry* mOldest;\n  Entry* mYoungest;\n  uint32_t mMaxCapacity;\n  TValue mNullValue;\n\n public:\n  // To be used like:\n  // while (it.next()) {\n  //   it.value(); it.key();\n  // }\n  class Iterator {\n   public:\n    Iterator(const LruCache<TKey, TValue>& cache)\n        : mCache(cache),\n          mIterator(mCache.mSet->begin()),\n          mBeginReturned(false) {}\n\n    bool next() {\n      if (mIterator == mCache.mSet->end()) {\n        return false;\n      }\n      if (!mBeginReturned) {\n        // mIterator has been initialized to the beginning and\n        // hasn't been returned. Do not advance:\n        mBeginReturned = true;\n      } else {\n        std::advance(mIterator, 1);\n      }\n      bool ret = (mIterator != mCache.mSet->end());\n      return ret;\n    }\n\n    const TValue& value() const {\n      // All the elements in the set are of type Entry. See comment in the\n      // definition of LruCacheSet above.\n      return reinterpret_cast<Entry*>(*mIterator)->value;\n    }\n\n    const TKey& key() const { return (*mIterator)->getKey(); }\n\n   private:\n    const LruCache<TKey, TValue>& mCache;\n    typename LruCacheSet::iterator mIterator;\n    bool mBeginReturned;\n  };\n};\n\n// Implementation is here, because it's fully templated\ntemplate <typename TKey, typename TValue>\nLruCache<TKey, TValue>::LruCache(uint32_t maxCapacity)\n    : mSet(new LruCacheSet()),\n      mListener(NULL),\n      mOldest(NULL),\n      mYoungest(NULL),\n      mMaxCapacity(maxCapacity),\n      mNullValue(0) {\n  mSet->max_load_factor(1.0);\n};\n\ntemplate <typename TKey, typename TValue>\nLruCache<TKey, TValue>::~LruCache() {\n  // Need to delete created entries.\n  clear();\n};\n\ntemplate <typename K, typename V>\nvoid LruCache<K, V>::setOnEntryRemovedListener(OnEntryRemoved<K, V>* listener) {\n  mListener = listener;\n}\n\ntemplate <typename TKey, typename TValue>\nsize_t LruCache<TKey, TValue>::size() const {\n  return mSet->size();\n}\n\ntemplate <typename TKey, typename TValue>\nconst TValue& LruCache<TKey, TValue>::get(const TKey& key) {\n  typename LruCacheSet::const_iterator find_result = findByKey(key);\n  if (find_result == mSet->end()) {\n    return mNullValue;\n  }\n  // All the elements in the set are of type Entry. See comment in the\n  // definition of LruCacheSet above.\n  Entry* entry = reinterpret_cast<Entry*>(*find_result);\n  detachFromCache(*entry);\n  attachToCache(*entry);\n  return entry->value;\n}\n\ntemplate <typename TKey, typename TValue>\nbool LruCache<TKey, TValue>::put(const TKey& key, const TValue& value) {\n  if (mMaxCapacity != kUnlimitedCapacity && size() >= mMaxCapacity) {\n    removeOldest();\n  }\n\n  if (findByKey(key) != mSet->end()) {\n    return false;\n  }\n\n  Entry* newEntry = new Entry(key, value);\n  mSet->insert(newEntry);\n  attachToCache(*newEntry);\n  return true;\n}\n\ntemplate <typename TKey, typename TValue>\nbool LruCache<TKey, TValue>::remove(const TKey& key) {\n  typename LruCacheSet::const_iterator find_result = findByKey(key);\n  if (find_result == mSet->end()) {\n    return false;\n  }\n  // All the elements in the set are of type Entry. See comment in the\n  // definition of LruCacheSet above.\n  Entry* entry = reinterpret_cast<Entry*>(*find_result);\n  mSet->erase(entry);\n  if (mListener) {\n    (*mListener)(entry->key, entry->value);\n  }\n  detachFromCache(*entry);\n  delete entry;\n  return true;\n}\n\ntemplate <typename TKey, typename TValue>\nbool LruCache<TKey, TValue>::removeOldest() {\n  if (mOldest != NULL) {\n    return remove(mOldest->key);\n    // TODO: should probably abort if false\n  }\n  return false;\n}\n\ntemplate <typename TKey, typename TValue>\nconst TValue& LruCache<TKey, TValue>::peekOldestValue() {\n  if (mOldest) {\n    return mOldest->value;\n  }\n  return mNullValue;\n}\n\ntemplate <typename TKey, typename TValue>\nvoid LruCache<TKey, TValue>::clear() {\n  if (mListener) {\n    for (Entry* p = mOldest; p != NULL; p = p->child) {\n      (*mListener)(p->key, p->value);\n    }\n  }\n  mYoungest = NULL;\n  mOldest = NULL;\n  for (auto entry : *mSet.get()) {\n    delete entry;\n  }\n  mSet->clear();\n}\n\ntemplate <typename TKey, typename TValue>\nvoid LruCache<TKey, TValue>::attachToCache(Entry& entry) {\n  if (mYoungest == NULL) {\n    mYoungest = mOldest = &entry;\n  } else {\n    entry.parent = mYoungest;\n    mYoungest->child = &entry;\n    mYoungest = &entry;\n  }\n}\n\ntemplate <typename TKey, typename TValue>\nvoid LruCache<TKey, TValue>::detachFromCache(Entry& entry) {\n  if (entry.parent != NULL) {\n    entry.parent->child = entry.child;\n  } else {\n    mOldest = entry.child;\n  }\n  if (entry.child != NULL) {\n    entry.child->parent = entry.parent;\n  } else {\n    mYoungest = entry.parent;\n  }\n\n  entry.parent = NULL;\n  entry.child = NULL;\n}\n\n}  // namespace android\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_UTILS_LRUCACHE_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/utils/MacUtils.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_UTILS_MACUTILS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_UTILS_MACUTILS_H_\n\n#if defined(__APPLE__)\n#define DISABLE_TEST_MAC(TEST_NAME) DISABLED_##TEST_NAME\n#define FRIEND_TEST_MAC_DISABLED_EXPANDED(SUITE, TEST_NAME) \\\n  FRIEND_TEST(SUITE, TEST_NAME)\n#define FRIEND_TEST_MAC_DISABLED(SUITE, TEST_NAME) \\\n  FRIEND_TEST_MAC_DISABLED_EXPANDED(SUITE, DISABLE_TEST_MAC(TEST_NAME))\n\n#define FRIEND_TEST_MAC_ONLY(SUITE, TEST_NAME) FRIEND_TEST(SUITE, TEST_NAME)\n#define MAC_ONLY(TEST_NAME) TEST_NAME\n\n#else\n#define DISABLE_TEST_MAC(TEST_NAME) TEST_NAME\n#define FRIEND_TEST_MAC_DISABLED(SUITE, TEST_NAME) FRIEND_TEST(SUITE, TEST_NAME)\n\n#define MAC_ONLY(TEST_NAME) DISABLED_##TEST_NAME\n#define FRIEND_TEST_MAC_ONLY_EXPANDED(SUITE, TEST_NAME) \\\n  FRIEND_TEST(SUITE, TEST_NAME)\n#define FRIEND_TEST_MAC_ONLY(SUITE, TEST_NAME) \\\n  FRIEND_TEST_MAC_ONLY_EXPANDED(SUITE, DISABLE_TEST_MAC(TEST_NAME))\n#endif  // defined(__APPLE__)\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_UTILS_MACUTILS_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/utils/TypeHelpers.h",
    "content": "/*\n * Copyright (C) 2005 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_UTILS_TYPEHELPERS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_UTILS_TYPEHELPERS_H_\n\n#include <sys/types.h>\n\n#include <cstdint>\n#include <cstring>\n#include <new>\n#include <type_traits>\n\n// ---------------------------------------------------------------------------\n\nnamespace android {\n\n/*\n * Types traits\n */\n\ntemplate <typename T>\nstruct trait_trivial_ctor {\n  enum { value = false };\n};\ntemplate <typename T>\nstruct trait_trivial_dtor {\n  enum { value = false };\n};\ntemplate <typename T>\nstruct trait_trivial_copy {\n  enum { value = false };\n};\ntemplate <typename T>\nstruct trait_trivial_move {\n  enum { value = false };\n};\ntemplate <typename T>\nstruct trait_pointer {\n  enum { value = false };\n};\ntemplate <typename T>\nstruct trait_pointer<T*> {\n  enum { value = true };\n};\n\ntemplate <typename TYPE>\nstruct traits {\n  enum {\n    // whether this type is a pointer\n    is_pointer = trait_pointer<TYPE>::value,\n    // whether this type's constructor is a no-op\n    has_trivial_ctor = is_pointer || trait_trivial_ctor<TYPE>::value,\n    // whether this type's destructor is a no-op\n    has_trivial_dtor = is_pointer || trait_trivial_dtor<TYPE>::value,\n    // whether this type type can be copy-constructed with memcpy\n    has_trivial_copy = is_pointer || trait_trivial_copy<TYPE>::value,\n    // whether this type can be moved with memmove\n    has_trivial_move = is_pointer || trait_trivial_move<TYPE>::value\n  };\n};\n\ntemplate <typename T, typename U>\nstruct aggregate_traits {\n  enum {\n    is_pointer = false,\n    has_trivial_ctor =\n        traits<T>::has_trivial_ctor && traits<U>::has_trivial_ctor,\n    has_trivial_dtor =\n        traits<T>::has_trivial_dtor && traits<U>::has_trivial_dtor,\n    has_trivial_copy =\n        traits<T>::has_trivial_copy && traits<U>::has_trivial_copy,\n    has_trivial_move =\n        traits<T>::has_trivial_move && traits<U>::has_trivial_move\n  };\n};\n\n#define ANDROID_TRIVIAL_CTOR_TRAIT(T) \\\n  template <>                         \\\n  struct trait_trivial_ctor<T> {      \\\n    enum { value = true };            \\\n  };\n\n#define ANDROID_TRIVIAL_DTOR_TRAIT(T) \\\n  template <>                         \\\n  struct trait_trivial_dtor<T> {      \\\n    enum { value = true };            \\\n  };\n\n#define ANDROID_TRIVIAL_COPY_TRAIT(T) \\\n  template <>                         \\\n  struct trait_trivial_copy<T> {      \\\n    enum { value = true };            \\\n  };\n\n#define ANDROID_TRIVIAL_MOVE_TRAIT(T) \\\n  template <>                         \\\n  struct trait_trivial_move<T> {      \\\n    enum { value = true };            \\\n  };\n\n#define ANDROID_BASIC_TYPES_TRAITS(T) \\\n  ANDROID_TRIVIAL_CTOR_TRAIT(T)       \\\n  ANDROID_TRIVIAL_DTOR_TRAIT(T)       \\\n  ANDROID_TRIVIAL_COPY_TRAIT(T)       \\\n  ANDROID_TRIVIAL_MOVE_TRAIT(T)\n\n// ---------------------------------------------------------------------------\n\n/*\n * basic types traits\n */\n\nANDROID_BASIC_TYPES_TRAITS(void)\nANDROID_BASIC_TYPES_TRAITS(bool)\nANDROID_BASIC_TYPES_TRAITS(char)\nANDROID_BASIC_TYPES_TRAITS(unsigned char)\nANDROID_BASIC_TYPES_TRAITS(short)\nANDROID_BASIC_TYPES_TRAITS(unsigned short)\nANDROID_BASIC_TYPES_TRAITS(int)\nANDROID_BASIC_TYPES_TRAITS(unsigned int)\nANDROID_BASIC_TYPES_TRAITS(long)\nANDROID_BASIC_TYPES_TRAITS(unsigned long)\nANDROID_BASIC_TYPES_TRAITS(long long)\nANDROID_BASIC_TYPES_TRAITS(unsigned long long)\nANDROID_BASIC_TYPES_TRAITS(float)\nANDROID_BASIC_TYPES_TRAITS(double)\n\n// ---------------------------------------------------------------------------\n\n/*\n * compare and order types\n */\n\ntemplate <typename TYPE>\ninline int strictly_order_type(const TYPE& lhs, const TYPE& rhs) {\n  return (lhs < rhs) ? 1 : 0;\n}\n\ntemplate <typename TYPE>\ninline int compare_type(const TYPE& lhs, const TYPE& rhs) {\n  return strictly_order_type(rhs, lhs) - strictly_order_type(lhs, rhs);\n}\n\n/*\n * create, destroy, copy and move types...\n */\n\ntemplate <typename TYPE>\ninline void construct_type(TYPE* p, size_t n) {\n  if (!traits<TYPE>::has_trivial_ctor) {\n    while (n > 0) {\n      n--;\n      new (p++) TYPE;\n    }\n  }\n}\n\ntemplate <typename TYPE>\ninline void destroy_type(TYPE* p, size_t n) {\n  if (!traits<TYPE>::has_trivial_dtor) {\n    while (n > 0) {\n      n--;\n      p->~TYPE();\n      p++;\n    }\n  }\n}\n\ntemplate <typename TYPE>\ntypename std::enable_if<traits<TYPE>::has_trivial_copy>::type inline copy_type(\n    TYPE* d,\n    const TYPE* s,\n    size_t n) {\n  memcpy(d, s, n * sizeof(TYPE));\n}\n\ntemplate <typename TYPE>\ntypename std::enable_if<!traits<TYPE>::has_trivial_copy>::type inline copy_type(\n    TYPE* d,\n    const TYPE* s,\n    size_t n) {\n  while (n > 0) {\n    n--;\n    new (d) TYPE(*s);\n    d++, s++;\n  }\n}\n\ntemplate <typename TYPE>\ninline void splat_type(TYPE* where, const TYPE* what, size_t n) {\n  if (!traits<TYPE>::has_trivial_copy) {\n    while (n > 0) {\n      n--;\n      new (where) TYPE(*what);\n      where++;\n    }\n  } else {\n    while (n > 0) {\n      n--;\n      *where++ = *what;\n    }\n  }\n}\n\ntemplate <typename TYPE>\nstruct use_trivial_move\n    : public std::integral_constant<bool,\n                                    (traits<TYPE>::has_trivial_dtor &&\n                                     traits<TYPE>::has_trivial_copy) ||\n                                        traits<TYPE>::has_trivial_move> {};\n\ntemplate <typename TYPE>\ntypename std::enable_if<use_trivial_move<TYPE>::value>::\n    type inline move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {\n  memmove(d, s, n * sizeof(TYPE));\n}\n\ntemplate <typename TYPE>\ntypename std::enable_if<!use_trivial_move<TYPE>::value>::\n    type inline move_forward_type(TYPE* d, const TYPE* s, size_t n = 1) {\n  d += n;\n  s += n;\n  while (n > 0) {\n    n--;\n    --d, --s;\n    if (!traits<TYPE>::has_trivial_copy) {\n      new (d) TYPE(*s);\n    } else {\n      *d = *s;\n    }\n    if (!traits<TYPE>::has_trivial_dtor) {\n      s->~TYPE();\n    }\n  }\n}\n\ntemplate <typename TYPE>\ntypename std::enable_if<use_trivial_move<TYPE>::value>::\n    type inline move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {\n  memmove(d, s, n * sizeof(TYPE));\n}\n\ntemplate <typename TYPE>\ntypename std::enable_if<!use_trivial_move<TYPE>::value>::\n    type inline move_backward_type(TYPE* d, const TYPE* s, size_t n = 1) {\n  while (n > 0) {\n    n--;\n    if (!traits<TYPE>::has_trivial_copy) {\n      new (d) TYPE(*s);\n    } else {\n      *d = *s;\n    }\n    if (!traits<TYPE>::has_trivial_dtor) {\n      s->~TYPE();\n    }\n    d++, s++;\n  }\n}\n\n// ---------------------------------------------------------------------------\n\n/*\n * a key/value pair\n */\n\ntemplate <typename KEY, typename VALUE>\nstruct key_value_pair_t {\n  typedef KEY key_t;\n  typedef VALUE value_t;\n\n  KEY key;\n  VALUE value;\n  key_value_pair_t() {}\n  key_value_pair_t(const key_value_pair_t& o) : key(o.key), value(o.value) {}\n  key_value_pair_t& operator=(const key_value_pair_t& o) {\n    key = o.key;\n    value = o.value;\n    return *this;\n  }\n  key_value_pair_t(const KEY& k, const VALUE& v) : key(k), value(v) {}\n  explicit key_value_pair_t(const KEY& k) : key(k) {}\n  inline bool operator<(const key_value_pair_t& o) const {\n    return strictly_order_type(key, o.key);\n  }\n  inline const KEY& getKey() const { return key; }\n  inline const VALUE& getValue() const { return value; }\n};\n\ntemplate <typename K, typename V>\nstruct trait_trivial_ctor<key_value_pair_t<K, V>> {\n  enum { value = aggregate_traits<K, V>::has_trivial_ctor };\n};\ntemplate <typename K, typename V>\nstruct trait_trivial_dtor<key_value_pair_t<K, V>> {\n  enum { value = aggregate_traits<K, V>::has_trivial_dtor };\n};\ntemplate <typename K, typename V>\nstruct trait_trivial_copy<key_value_pair_t<K, V>> {\n  enum { value = aggregate_traits<K, V>::has_trivial_copy };\n};\ntemplate <typename K, typename V>\nstruct trait_trivial_move<key_value_pair_t<K, V>> {\n  enum { value = aggregate_traits<K, V>::has_trivial_move };\n};\n\n// ---------------------------------------------------------------------------\n\n/*\n * Hash codes.\n */\ntypedef uint32_t hash_t;\n\ntemplate <typename TKey>\nhash_t hash_type(const TKey& key);\n\n/* Built-in hash code specializations */\n#define ANDROID_INT32_HASH(T)               \\\n  template <>                               \\\n  inline hash_t hash_type(const T& value) { \\\n    return hash_t(value);                   \\\n  }\n#define ANDROID_INT64_HASH(T)               \\\n  template <>                               \\\n  inline hash_t hash_type(const T& value) { \\\n    return hash_t((value >> 32) ^ value);   \\\n  }\n#define ANDROID_REINTERPRET_HASH(T, R)                                 \\\n  template <>                                                          \\\n  inline hash_t hash_type(const T& value) {                            \\\n    R newValue;                                                        \\\n    static_assert(sizeof(newValue) == sizeof(value), \"size mismatch\"); \\\n    memcpy(&newValue, &value, sizeof(newValue));                       \\\n    return hash_type(newValue);                                        \\\n  }\n\nANDROID_INT32_HASH(bool)\nANDROID_INT32_HASH(int8_t)\nANDROID_INT32_HASH(uint8_t)\nANDROID_INT32_HASH(int16_t)\nANDROID_INT32_HASH(uint16_t)\nANDROID_INT32_HASH(int32_t)\nANDROID_INT32_HASH(uint32_t)\nANDROID_INT64_HASH(int64_t)\nANDROID_INT64_HASH(uint64_t)\nANDROID_REINTERPRET_HASH(float, uint32_t)\nANDROID_REINTERPRET_HASH(double, uint64_t)\n\ntemplate <typename T>\ninline hash_t hash_type(T* const& value) {\n  return hash_type(uintptr_t(value));\n}\n\n};  // namespace android\n\n// ---------------------------------------------------------------------------\n\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_UTILS_TYPEHELPERS_H_\n"
  },
  {
    "path": "clay/third_party/txt/src/utils/WindowsUtils.h",
    "content": "/*\n * Copyright 2017 Google Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_SRC_UTILS_WINDOWSUTILS_H_\n#define CLAY_THIRD_PARTY_TXT_SRC_UTILS_WINDOWSUTILS_H_\n\n#if defined(_WIN32)\n#define DISABLE_TEST_WINDOWS(TEST_NAME) DISABLED_##TEST_NAME\n#define FRIEND_TEST_WINDOWS_DISABLED_EXPANDED(SUITE, TEST_NAME) \\\n  FRIEND_TEST(SUITE, TEST_NAME)\n#define FRIEND_TEST_WINDOWS_DISABLED(SUITE, TEST_NAME) \\\n  FRIEND_TEST_WINDOWS_DISABLED_EXPANDED(SUITE, DISABLE_TEST_WINDOWS(TEST_NAME))\n\n#define FRIEND_TEST_WINDOWS_ONLY(SUITE, TEST_NAME) FRIEND_TEST(SUITE, TEST_NAME)\n#define WINDOWS_ONLY(TEST_NAME) TEST_NAME\n\n#define NOMINMAX\n#include <BaseTsd.h>\n#include <intrin.h>\n#include <windows.h>\n\n#undef ERROR\n\ninline unsigned int clz_win(unsigned int num) {\n  unsigned long r = 0;\n  _BitScanReverse(&r, num);\n  return r;\n}\n\ninline unsigned int clzl_win(unsigned long num) {\n  unsigned long r = 0;\n#if defined(_WIN64)\n  _BitScanReverse64(&r, num);\n#else\n  _BitScanReverse(&r, num);\n#endif\n  return r;\n}\n\ninline unsigned int ctz_win(unsigned int num) {\n  unsigned long r = 0;\n  _BitScanForward(&r, num);\n  return r;\n}\n\ntypedef SSIZE_T ssize_t;\n\n#else\n#define DISABLE_TEST_WINDOWS(TEST_NAME) TEST_NAME\n#define FRIEND_TEST_WINDOWS_DISABLED(SUITE, TEST_NAME) \\\n  FRIEND_TEST(SUITE, TEST_NAME)\n\n#define WINDOWS_ONLY(TEST_NAME) DISABLED_##TEST_NAME\n#define FRIEND_TEST_WINDOWS_ONLY_EXPANDED(SUITE, TEST_NAME) \\\n  FRIEND_TEST(SUITE, TEST_NAME)\n#define FRIEND_TEST_WINDOWS_ONLY(SUITE, TEST_NAME) \\\n  FRIEND_TEST_WINDOWS_ONLY_EXPANDED(SUITE, DISABLE_TEST_WINDOWS(TEST_NAME))\n#endif  // defined(_WIN32)\n#endif  // CLAY_THIRD_PARTY_TXT_SRC_UTILS_WINDOWSUTILS_H_\n"
  },
  {
    "path": "clay/third_party/txt/tests/CmapCoverageTest.cpp",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <random>\n\n#include <gtest/gtest.h>\n#include <log/log.h>\n#include <minikin/CmapCoverage.h>\n#include <minikin/SparseBitSet.h>\n#include <utils/WindowsUtils.h>\n\nnamespace minikin {\n\nsize_t writeU16(uint16_t x, uint8_t* out, size_t offset) {\n  out[offset] = x >> 8;\n  out[offset + 1] = x;\n  return offset + 2;\n}\n\nsize_t writeI16(int16_t sx, uint8_t* out, size_t offset) {\n  return writeU16(static_cast<uint16_t>(sx), out, offset);\n}\n\nsize_t writeU32(uint32_t x, uint8_t* out, size_t offset) {\n  out[offset] = x >> 24;\n  out[offset + 1] = x >> 16;\n  out[offset + 2] = x >> 8;\n  out[offset + 3] = x;\n  return offset + 4;\n}\n\n// Returns valid cmap format 4 table contents. All glyph ID is same value as\n// code point. (e.g. 'a' (U+0061) is mapped to Glyph ID = 0x0061). 'range'\n// should be specified with inclusive-inclusive values.\nstatic std::vector<uint8_t> buildCmapFormat4Table(\n    const std::vector<uint16_t>& ranges) {\n  uint16_t segmentCount = ranges.size() / 2 + 1 /* +1 for end marker */;\n\n  const size_t numOfUint16 =\n      8 /* format, length, languages, segCountX2, searchRange, entrySelector,\n           rangeShift, pad */\n      + segmentCount * 4 /* endCount, startCount, idRange, idRangeOffset */;\n  const size_t finalLength = sizeof(uint16_t) * numOfUint16;\n\n  std::vector<uint8_t> out(finalLength);\n  size_t head = 0;\n  head = writeU16(4, out.data(), head);            // format\n  head = writeU16(finalLength, out.data(), head);  // length\n  head = writeU16(0, out.data(), head);            // language\n\n  const uint16_t searchRange =\n      2 * (1 << static_cast<int>(floor(log2(segmentCount))));\n\n  head = writeU16(segmentCount * 2, out.data(), head);  // segCountX2\n  head = writeU16(searchRange, out.data(), head);       // searchRange\n#if defined(_WIN32)\n  head = writeU16(ctz_win(searchRange) - 1, out.data(), head);\n#else\n  head = writeU16(__builtin_ctz(searchRange) - 1, out.data(),\n                  head);  // entrySelector\n#endif\n  head =\n      writeU16(segmentCount * 2 - searchRange, out.data(), head);  // rangeShift\n\n  size_t endCountHead = head;\n  size_t startCountHead =\n      head + segmentCount * sizeof(uint16_t) + 2 /* padding */;\n  size_t idDeltaHead = startCountHead + segmentCount * sizeof(uint16_t);\n  size_t idRangeOffsetHead = idDeltaHead + segmentCount * sizeof(uint16_t);\n\n  for (size_t i = 0; i < ranges.size() / 2; ++i) {\n    const uint16_t begin = ranges[i * 2];\n    const uint16_t end = ranges[i * 2 + 1];\n    startCountHead = writeU16(begin, out.data(), startCountHead);\n    endCountHead = writeU16(end, out.data(), endCountHead);\n    // map glyph ID as the same value of the code point.\n    idDeltaHead = writeU16(0, out.data(), idDeltaHead);\n    idRangeOffsetHead =\n        writeU16(0 /* we don't use this */, out.data(), idRangeOffsetHead);\n  }\n\n  // fill end marker\n  endCountHead = writeU16(0xFFFF, out.data(), endCountHead);\n  startCountHead = writeU16(0xFFFF, out.data(), startCountHead);\n  idDeltaHead = writeU16(1, out.data(), idDeltaHead);\n  idRangeOffsetHead = writeU16(0, out.data(), idRangeOffsetHead);\n  LOG_ALWAYS_FATAL_IF(endCountHead > finalLength);\n  LOG_ALWAYS_FATAL_IF(startCountHead > finalLength);\n  LOG_ALWAYS_FATAL_IF(idDeltaHead > finalLength);\n  LOG_ALWAYS_FATAL_IF(idRangeOffsetHead != finalLength);\n  return out;\n}\n\n// Returns valid cmap format 4 table contents. All glyph ID is same value as\n// code point. (e.g. 'a' (U+0061) is mapped to Glyph ID = 0x0061). 'range'\n// should be specified with inclusive-inclusive values.\nstatic std::vector<uint8_t> buildCmapFormat12Table(\n    const std::vector<uint32_t>& ranges) {\n  uint32_t numGroups = ranges.size() / 2;\n\n  const size_t finalLength =\n      2 /* format */ + 2 /* reserved */ + 4 /* length */ + 4 /* languages */ +\n      4 /* numGroups */ + 12 /* size of a group */ * numGroups;\n\n  std::vector<uint8_t> out(finalLength);\n  size_t head = 0;\n  head = writeU16(12, out.data(), head);           // format\n  head = writeU16(0, out.data(), head);            // reserved\n  head = writeU32(finalLength, out.data(), head);  // length\n  head = writeU32(0, out.data(), head);            // language\n  head = writeU32(numGroups, out.data(), head);    // numGroups\n\n  for (uint32_t i = 0; i < numGroups; ++i) {\n    const uint32_t start = ranges[2 * i];\n    const uint32_t end = ranges[2 * i + 1];\n    head = writeU32(start, out.data(), head);\n    head = writeU32(end, out.data(), head);\n    // map glyph ID as the same value of the code point.\n    // TODO: Use glyph IDs lower than 65535.\n    // Cmap can store 32 bit glyph ID but due to the size of numGlyph, a font\n    // file can contain up to 65535 glyphs in a file.\n    head = writeU32(start, out.data(), head);\n  }\n\n  LOG_ALWAYS_FATAL_IF(head != finalLength);\n  return out;\n}\n\nclass CmapBuilder {\n public:\n  static constexpr size_t kEncodingTableHead = 4;\n  static constexpr size_t kEncodingTableSize = 8;\n\n  CmapBuilder(int numTables) : mNumTables(numTables), mCurrentTableIndex(0) {\n    const size_t headerSize =\n        2 /* version */ + 2 /* numTables */ + kEncodingTableSize * numTables;\n    out.resize(headerSize);\n    writeU16(0, out.data(), 0);\n    writeU16(numTables, out.data(), 2);\n  }\n\n  void appendTable(uint16_t platformId,\n                   uint16_t encodingId,\n                   const std::vector<uint8_t>& table) {\n    appendEncodingTable(platformId, encodingId, out.size());\n    out.insert(out.end(), table.begin(), table.end());\n  }\n\n  // TODO: Introduce Format 14 table builder.\n\n  std::vector<uint8_t> build() {\n    LOG_ALWAYS_FATAL_IF(mCurrentTableIndex != mNumTables);\n    return out;\n  }\n\n  // Helper functions.\n  static std::vector<uint8_t> buildSingleFormat4Cmap(\n      uint16_t platformId,\n      uint16_t encodingId,\n      const std::vector<uint16_t>& ranges) {\n    CmapBuilder builder(1);\n    builder.appendTable(platformId, encodingId, buildCmapFormat4Table(ranges));\n    return builder.build();\n  }\n\n  static std::vector<uint8_t> buildSingleFormat12Cmap(\n      uint16_t platformId,\n      uint16_t encodingId,\n      const std::vector<uint32_t>& ranges) {\n    CmapBuilder builder(1);\n    builder.appendTable(platformId, encodingId, buildCmapFormat12Table(ranges));\n    return builder.build();\n  }\n\n private:\n  void appendEncodingTable(uint16_t platformId,\n                           uint16_t encodingId,\n                           uint32_t offset) {\n    LOG_ALWAYS_FATAL_IF(mCurrentTableIndex == mNumTables);\n\n    const size_t currentEncodingTableHead =\n        kEncodingTableHead + mCurrentTableIndex * kEncodingTableSize;\n    size_t head = writeU16(platformId, out.data(), currentEncodingTableHead);\n    head = writeU16(encodingId, out.data(), head);\n    head = writeU32(offset, out.data(), head);\n    LOG_ALWAYS_FATAL_IF((head - currentEncodingTableHead) !=\n                        kEncodingTableSize);\n    mCurrentTableIndex++;\n  }\n\n  int mNumTables;\n  int mCurrentTableIndex;\n  std::vector<uint8_t> out;\n};\n\nTEST(CmapCoverageTest, SingleFormat4_brokenCmap) {\n  bool has_cmap_format_14_subtable = false;\n  {\n    SCOPED_TRACE(\"Reading beyond buffer size - Too small cmap size\");\n    std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(\n        0, 0, std::vector<uint16_t>({'a', 'a'}));\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), 3 /* too small */, &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\n        \"Reading beyond buffer size - space needed for tables goes beyond cmap \"\n        \"size\");\n    std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(\n        0, 0, std::vector<uint16_t>({'a', 'a'}));\n\n    writeU16(1000, cmap.data(), 2 /* offset of num tables in cmap header */);\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\n        \"Reading beyond buffer size - Invalid offset in encoding table\");\n    std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(\n        0, 0, std::vector<uint16_t>({'a', 'a'}));\n\n    writeU16(1000, cmap.data(),\n             8 /* offset of the offset in the first encoding record */);\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n}\n\nTEST(CmapCoverageTest, SingleFormat4) {\n  bool has_cmap_format_14_subtable = false;\n  struct TestCast {\n    std::string testTitle;\n    uint16_t platformId;\n    uint16_t encodingId;\n  } TEST_CASES[] = {\n      {\"Platform 0, Encoding 0\", 0, 0}, {\"Platform 0, Encoding 1\", 0, 1},\n      {\"Platform 0, Encoding 2\", 0, 2}, {\"Platform 0, Encoding 3\", 0, 3},\n      {\"Platform 3, Encoding 1\", 3, 1},\n  };\n\n  for (const auto& testCase : TEST_CASES) {\n    SCOPED_TRACE(testCase.testTitle.c_str());\n    std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(\n        testCase.platformId, testCase.encodingId,\n        std::vector<uint16_t>({'a', 'a'}));\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));\n    EXPECT_FALSE(coverage.get('b'));\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n}\n\nTEST(CmapCoverageTest, SingleFormat12) {\n  bool has_cmap_format_14_subtable = false;\n\n  struct TestCast {\n    std::string testTitle;\n    uint16_t platformId;\n    uint16_t encodingId;\n  } TEST_CASES[] = {\n      {\"Platform 0, Encoding 4\", 0, 4},\n      {\"Platform 0, Encoding 6\", 0, 6},\n      {\"Platform 3, Encoding 10\", 3, 10},\n  };\n\n  for (const auto& testCase : TEST_CASES) {\n    SCOPED_TRACE(testCase.testTitle.c_str());\n    std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap(\n        testCase.platformId, testCase.encodingId,\n        std::vector<uint32_t>({'a', 'a'}));\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));\n    EXPECT_FALSE(coverage.get('b'));\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n}\n\nTEST(CmapCoverageTest, Format12_beyondTheUnicodeLimit) {\n  bool has_cmap_format_14_subtable = false;\n  {\n    SCOPED_TRACE(\n        \"Starting range is out of Unicode code point. Should be ignored.\");\n    std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap(\n        0, 0, std::vector<uint32_t>({'a', 'a', 0x110000, 0x110000}));\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));\n    EXPECT_FALSE(coverage.get(0x110000));\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\n        \"Ending range is out of Unicode code point. Should be ignored.\");\n    std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap(\n        0, 0, std::vector<uint32_t>({'a', 'a', 0x10FF00, 0x110000}));\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));\n    EXPECT_TRUE(coverage.get(0x10FF00));\n    EXPECT_TRUE(coverage.get(0x10FFFF));\n    EXPECT_FALSE(coverage.get(0x110000));\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n}\n\nTEST(CmapCoverageTest, notSupportedEncodings) {\n  bool has_cmap_format_14_subtable = false;\n\n  struct TestCast {\n    std::string testTitle;\n    uint16_t platformId;\n    uint16_t encodingId;\n  } TEST_CASES[] = {\n      // Any encodings with platform 2 is not supported.\n      {\"Platform 2, Encoding 0\", 2, 0},\n      {\"Platform 2, Encoding 1\", 2, 1},\n      {\"Platform 2, Encoding 2\", 2, 2},\n      {\"Platform 2, Encoding 3\", 2, 3},\n      // UCS-2 or UCS-4 are supported on Platform == 3. Others are not\n      // supported.\n      {\"Platform 3, Encoding 0\", 3, 0},  // Symbol\n      {\"Platform 3, Encoding 2\", 3, 2},  // ShiftJIS\n      {\"Platform 3, Encoding 3\", 3, 3},  // RPC\n      {\"Platform 3, Encoding 4\", 3, 4},  // Big5\n      {\"Platform 3, Encoding 5\", 3, 5},  // Wansung\n      {\"Platform 3, Encoding 6\", 3, 6},  // Johab\n      {\"Platform 3, Encoding 7\", 3, 7},  // Reserved\n      {\"Platform 3, Encoding 8\", 3, 8},  // Reserved\n      {\"Platform 3, Encoding 9\", 3, 9},  // Reserved\n      // Uknown platforms\n      {\"Platform 4, Encoding 0\", 4, 0},\n      {\"Platform 5, Encoding 1\", 5, 1},\n      {\"Platform 6, Encoding 0\", 6, 0},\n      {\"Platform 7, Encoding 1\", 7, 1},\n  };\n\n  for (const auto& testCase : TEST_CASES) {\n    SCOPED_TRACE(testCase.testTitle.c_str());\n    CmapBuilder builder(1);\n    std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(\n        testCase.platformId, testCase.encodingId,\n        std::vector<uint16_t>({'a', 'a'}));\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n}\n\nTEST(CmapCoverageTest, brokenFormat4Table) {\n  bool has_cmap_format_14_subtable = false;\n  {\n    SCOPED_TRACE(\"Too small table cmap size\");\n    std::vector<uint8_t> table =\n        buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));\n    table.resize(2);  // Remove trailing data.\n\n    CmapBuilder builder(1);\n    builder.appendTable(0, 0, table);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\"Too many segments\");\n    std::vector<uint8_t> table =\n        buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));\n    writeU16(5000, table.data(),\n             6 /* segment count offset */);  // 5000 segments.\n    CmapBuilder builder(1);\n    builder.appendTable(0, 0, table);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\"Inversed range\");\n    std::vector<uint8_t> table =\n        buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));\n    // Put smaller end code point to inverse the range.\n    writeU16('a', table.data(), 14 /* the first element of endCount offset */);\n    CmapBuilder builder(1);\n    builder.appendTable(0, 0, table);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n}\n\nTEST(CmapCoverageTest, brokenFormat12Table) {\n  bool has_cmap_format_14_subtable = false;\n  {\n    SCOPED_TRACE(\"Too small cmap size\");\n    std::vector<uint8_t> table =\n        buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));\n    table.resize(2);  // Remove trailing data.\n\n    CmapBuilder builder(1);\n    builder.appendTable(0, 0, table);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\"Too many groups\");\n    std::vector<uint8_t> table =\n        buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));\n    writeU32(5000, table.data(), 12 /* num group offset */);  // 5000 groups.\n\n    CmapBuilder builder(1);\n    builder.appendTable(0, 0, table);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\"Inversed range.\");\n    std::vector<uint8_t> table =\n        buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));\n    // Put larger start code point to inverse the range.\n    writeU32('b', table.data(),\n             16 /* start code point offset in the first  group */);\n\n    CmapBuilder builder(1);\n    builder.appendTable(0, 0, table);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\"Too large code point\");\n    std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap(\n        0, 0, std::vector<uint32_t>({0x110000, 0x110000}));\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_EQ(0U, coverage.length());\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n}\n\nTEST(CmapCoverageTest, TableSelection_Priority) {\n  bool has_cmap_format_14_subtable = false;\n  std::vector<uint8_t> highestFormat12Table =\n      buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));\n  std::vector<uint8_t> highestFormat4Table =\n      buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));\n  std::vector<uint8_t> format4 =\n      buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));\n  std::vector<uint8_t> format12 =\n      buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'}));\n\n  {\n    SCOPED_TRACE(\"(platform, encoding) = (3, 10) is the highest priority.\");\n\n    struct LowerPriorityTable {\n      uint16_t platformId;\n      uint16_t encodingId;\n      const std::vector<uint8_t>& table;\n    } LOWER_PRIORITY_TABLES[] = {\n        {0, 0, format4},  {0, 1, format4},  {0, 2, format4}, {0, 3, format4},\n        {0, 4, format12}, {0, 6, format12}, {3, 1, format4},\n    };\n\n    for (const auto& table : LOWER_PRIORITY_TABLES) {\n      CmapBuilder builder(2);\n      builder.appendTable(table.platformId, table.encodingId, table.table);\n      builder.appendTable(3, 10, highestFormat12Table);\n      std::vector<uint8_t> cmap = builder.build();\n\n      SparseBitSet coverage = CmapCoverage::getCoverage(\n          cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n      EXPECT_TRUE(coverage.get('a'));   // comes from highest table\n      EXPECT_FALSE(coverage.get('b'));  // should not use other table.\n      EXPECT_FALSE(has_cmap_format_14_subtable);\n    }\n  }\n  {\n    SCOPED_TRACE(\"(platform, encoding) = (3, 1) case\");\n\n    struct LowerPriorityTable {\n      uint16_t platformId;\n      uint16_t encodingId;\n      const std::vector<uint8_t>& table;\n    } LOWER_PRIORITY_TABLES[] = {\n        {0, 0, format4},\n        {0, 1, format4},\n        {0, 2, format4},\n        {0, 3, format4},\n    };\n\n    for (const auto& table : LOWER_PRIORITY_TABLES) {\n      CmapBuilder builder(2);\n      builder.appendTable(table.platformId, table.encodingId, table.table);\n      builder.appendTable(3, 1, highestFormat4Table);\n      std::vector<uint8_t> cmap = builder.build();\n\n      SparseBitSet coverage = CmapCoverage::getCoverage(\n          cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n      EXPECT_TRUE(coverage.get('a'));   // comes from highest table\n      EXPECT_FALSE(coverage.get('b'));  // should not use other table.\n      EXPECT_FALSE(has_cmap_format_14_subtable);\n    }\n  }\n}\n\nTEST(CmapCoverageTest, TableSelection_SkipBrokenFormat4Table) {\n  SparseBitSet coverage;\n  bool has_cmap_format_14_subtable = false;\n  std::vector<uint8_t> validTable =\n      buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));\n  {\n    SCOPED_TRACE(\"Unsupported format\");\n    CmapBuilder builder(2);\n    std::vector<uint8_t> table =\n        buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));\n    writeU16(0, table.data(), 0 /* format offset */);\n    builder.appendTable(3, 1, table);\n    builder.appendTable(0, 0, validTable);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));   // comes from valid table\n    EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\"Invalid language\");\n    CmapBuilder builder(2);\n    std::vector<uint8_t> table =\n        buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));\n    writeU16(1, table.data(), 4 /* language offset */);\n    builder.appendTable(3, 1, table);\n    builder.appendTable(0, 0, validTable);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));   // comes from valid table\n    EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\"Invalid length\");\n    CmapBuilder builder(2);\n    std::vector<uint8_t> table =\n        buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));\n    writeU16(5000, table.data(), 2 /* length offset */);\n    builder.appendTable(3, 1, table);\n    builder.appendTable(0, 0, validTable);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));   // comes from valid table\n    EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n}\n\nTEST(CmapCoverageTest, TableSelection_SkipBrokenFormat12Table) {\n  SparseBitSet coverage;\n  bool has_cmap_format_14_subtable = false;\n  std::vector<uint8_t> validTable =\n      buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));\n  {\n    SCOPED_TRACE(\"Unsupported format\");\n    CmapBuilder builder(2);\n    std::vector<uint8_t> table =\n        buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'}));\n    writeU16(0, table.data(), 0 /* format offset */);\n    builder.appendTable(3, 1, table);\n    builder.appendTable(0, 0, validTable);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));   // comes from valid table\n    EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\"Invalid language\");\n    CmapBuilder builder(2);\n    std::vector<uint8_t> table =\n        buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'}));\n    writeU32(1, table.data(), 8 /* language offset */);\n    builder.appendTable(3, 1, table);\n    builder.appendTable(0, 0, validTable);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));   // comes from valid table\n    EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n  {\n    SCOPED_TRACE(\"Invalid length\");\n    CmapBuilder builder(2);\n    std::vector<uint8_t> table =\n        buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'}));\n    writeU32(5000, table.data(), 4 /* length offset */);\n    builder.appendTable(3, 1, table);\n    builder.appendTable(0, 0, validTable);\n    std::vector<uint8_t> cmap = builder.build();\n\n    SparseBitSet coverage = CmapCoverage::getCoverage(\n        cmap.data(), cmap.size(), &has_cmap_format_14_subtable);\n    EXPECT_TRUE(coverage.get('a'));   // comes from valid table\n    EXPECT_FALSE(coverage.get('b'));  // should not use invalid table.\n    EXPECT_FALSE(has_cmap_format_14_subtable);\n  }\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/EmojiTest.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\n#include <unicode/uchar.h>\n\n#include <minikin/Emoji.h>\n\nnamespace minikin {\n\nTEST(EmojiTest, isEmojiTest) {\n  EXPECT_TRUE(isEmoji(0x0023));  // NUMBER SIGN\n  EXPECT_TRUE(isEmoji(0x0035));  // DIGIT FIVE\n  // EXPECT_TRUE(isEmoji(0x2640));  // FEMALE SIGN\n  // EXPECT_TRUE(isEmoji(0x2642));  // MALE SIGN\n  // EXPECT_TRUE(isEmoji(0x2695));  // STAFF OF AESCULAPIUS\n  EXPECT_TRUE(isEmoji(0x1F0CF));  // PLAYING CARD BLACK JOKER\n  EXPECT_TRUE(isEmoji(0x1F1E9));  // REGIONAL INDICATOR SYMBOL LETTER D\n  EXPECT_TRUE(isEmoji(0x1F6F7));  // SLED\n  EXPECT_TRUE(isEmoji(0x1F9E6));  // SOCKS\n\n  EXPECT_FALSE(isEmoji(0x0000));   // <control>\n  EXPECT_FALSE(isEmoji(0x0061));   // LATIN SMALL LETTER A\n  EXPECT_FALSE(isEmoji(0x1F93B));  // MODERN PENTATHLON\n  EXPECT_FALSE(isEmoji(0x1F946));  // RIFLE\n  EXPECT_FALSE(isEmoji(0x29E3D));  // A han character.\n}\n\nTEST(EmojiTest, isEmojiModifierTest) {\n  EXPECT_TRUE(isEmojiModifier(0x1F3FB));  // EMOJI MODIFIER FITZPATRICK TYPE-1-2\n  EXPECT_TRUE(isEmojiModifier(0x1F3FC));  // EMOJI MODIFIER FITZPATRICK TYPE-3\n  EXPECT_TRUE(isEmojiModifier(0x1F3FD));  // EMOJI MODIFIER FITZPATRICK TYPE-4\n  EXPECT_TRUE(isEmojiModifier(0x1F3FE));  // EMOJI MODIFIER FITZPATRICK TYPE-5\n  EXPECT_TRUE(isEmojiModifier(0x1F3FF));  // EMOJI MODIFIER FITZPATRICK TYPE-6\n\n  EXPECT_FALSE(isEmojiModifier(0x0000));   // <control>\n  EXPECT_FALSE(isEmojiModifier(0x1F3FA));  // AMPHORA\n  EXPECT_FALSE(isEmojiModifier(0x1F400));  // RAT\n  EXPECT_FALSE(isEmojiModifier(0x29E3D));  // A han character.\n}\n\nTEST(EmojiTest, isEmojiBaseTest) {\n  EXPECT_TRUE(isEmojiBase(0x261D));   // WHITE UP POINTING INDEX\n  EXPECT_TRUE(isEmojiBase(0x270D));   // WRITING HAND\n  EXPECT_TRUE(isEmojiBase(0x1F385));  // FATHER CHRISTMAS\n  // EXPECT_TRUE(isEmojiBase(0x1F3C2));  // SNOWBOARDER\n  // EXPECT_TRUE(isEmojiBase(0x1F3C7));  // HORSE RACING\n  // EXPECT_TRUE(isEmojiBase(0x1F3CC));  // GOLFER\n  // EXPECT_TRUE(isEmojiBase(0x1F574));  // MAN IN BUSINESS SUIT LEVITATING\n  // EXPECT_TRUE(isEmojiBase(0x1F6CC));  // SLEEPING ACCOMMODATION\n  EXPECT_TRUE(isEmojiBase(\n      0x1F91D));  // HANDSHAKE (removed from Emoji 4.0, but we need it)\n  EXPECT_TRUE(isEmojiBase(0x1F91F));  // I LOVE YOU HAND SIGN\n  EXPECT_TRUE(isEmojiBase(0x1F931));  // BREAST-FEEDING\n  EXPECT_TRUE(isEmojiBase(0x1F932));  // PALMS UP TOGETHER\n  EXPECT_TRUE(isEmojiBase(\n      0x1F93C));  // WRESTLERS (removed from Emoji 4.0, but we need it)\n  EXPECT_TRUE(isEmojiBase(0x1F9D1));  // ADULT\n  EXPECT_TRUE(isEmojiBase(0x1F9DD));  // ELF\n\n  EXPECT_FALSE(isEmojiBase(0x0000));   // <control>\n  EXPECT_FALSE(isEmojiBase(0x261C));   // WHITE LEFT POINTING INDEX\n  EXPECT_FALSE(isEmojiBase(0x1F384));  // CHRISTMAS TREE\n  EXPECT_FALSE(isEmojiBase(0x1F9DE));  // GENIE\n  EXPECT_FALSE(isEmojiBase(0x29E3D));  // A han character.\n}\n\nTEST(EmojiTest, emojiBidiOverrideTest) {\n  EXPECT_EQ(U_RIGHT_TO_LEFT,\n            emojiBidiOverride(nullptr, 0x05D0));  // HEBREW LETTER ALEF\n  EXPECT_EQ(U_LEFT_TO_RIGHT,\n            emojiBidiOverride(\n                nullptr, 0x1F170));  // NEGATIVE SQUARED LATIN CAPITAL LETTER A\n  EXPECT_EQ(U_OTHER_NEUTRAL, emojiBidiOverride(nullptr, 0x1F6F7));  // SLED\n  EXPECT_EQ(U_OTHER_NEUTRAL, emojiBidiOverride(nullptr, 0x1F9E6));  // SOCKS\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/FileUtils.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <log/log.h>\n\n#include <stdio.h>\n#include <sys/stat.h>\n\n#include <string>\n#include <vector>\n\nstd::vector<uint8_t> readWholeFile(const std::string& filePath) {\n  FILE* fp = fopen(filePath.c_str(), \"r\");\n  LOG_ALWAYS_FATAL_IF(fp == nullptr);\n  struct stat st;\n  LOG_ALWAYS_FATAL_IF(fstat(fileno(fp), &st) != 0);\n\n  std::vector<uint8_t> result(st.st_size);\n  LOG_ALWAYS_FATAL_IF(fread(result.data(), 1, st.st_size, fp) !=\n                      static_cast<size_t>(st.st_size));\n  fclose(fp);\n  return result;\n}\n"
  },
  {
    "path": "clay/third_party/txt/tests/FileUtils.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_TESTS_FILEUTILS_H_\n#define CLAY_THIRD_PARTY_TXT_TESTS_FILEUTILS_H_\n#include <string>\n#include <vector>\n\nstd::vector<uint8_t> readWholeFile(const std::string& filePath);\n#endif  // CLAY_THIRD_PARTY_TXT_TESTS_FILEUTILS_H_\n"
  },
  {
    "path": "clay/third_party/txt/tests/FontCollectionItemizeTest.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\n#include <memory>\n\n#include \"FontTestUtils.h\"\n#include \"ICUTestBase.h\"\n#include \"MinikinFontForTest.h\"\n#include \"UnicodeUtils.h\"\n#include \"minikin/FontFamily.h\"\n#include \"minikin/FontLanguage.h\"\n#include \"minikin/FontLanguageListCache.h\"\n#include \"minikin/MinikinInternal.h\"\n\nnamespace minikin {\n\nconst char kItemizeFontXml[] = kTestFontDir \"itemize.xml\";\nconst char kEmojiFont[] = kTestFontDir \"Emoji.ttf\";\nconst char kJAFont[] = kTestFontDir \"Ja.ttf\";\nconst char kKOFont[] = kTestFontDir \"Ko.ttf\";\nconst char kLatinBoldFont[] = kTestFontDir \"Bold.ttf\";\nconst char kLatinBoldItalicFont[] = kTestFontDir \"BoldItalic.ttf\";\nconst char kLatinFont[] = kTestFontDir \"Regular.ttf\";\nconst char kLatinItalicFont[] = kTestFontDir \"Italic.ttf\";\nconst char kZH_HansFont[] = kTestFontDir \"ZhHans.ttf\";\nconst char kZH_HantFont[] = kTestFontDir \"ZhHant.ttf\";\n\nconst char kEmojiXmlFile[] = kTestFontDir \"emoji.xml\";\nconst char kNoGlyphFont[] = kTestFontDir \"NoGlyphFont.ttf\";\nconst char kColorEmojiFont[] = kTestFontDir \"ColorEmojiFont.ttf\";\nconst char kTextEmojiFont[] = kTestFontDir \"TextEmojiFont.ttf\";\nconst char kMixedEmojiFont[] = kTestFontDir \"ColorTextMixedEmojiFont.ttf\";\n\nconst char kHasCmapFormat14Font[] = kTestFontDir \"NoCmapFormat14.ttf\";\nconst char kNoCmapFormat14Font[] =\n    kTestFontDir \"VariationSelectorTest-Regular.ttf\";\n\ntypedef ICUTestBase FontCollectionItemizeTest;\n\n// Utility function for calling itemize function.\nvoid itemize(const std::shared_ptr<FontCollection>& collection,\n             const char* str,\n             FontStyle style,\n             std::vector<FontCollection::Run>* result) {\n  const size_t BUF_SIZE = 256;\n  uint16_t buf[BUF_SIZE];\n  size_t len;\n\n  result->clear();\n  ParseUnicode(buf, BUF_SIZE, str, &len, NULL);\n  std::scoped_lock _l(gMinikinLock);\n  collection->itemize(buf, len, style, result);\n}\n\n// Utility function to obtain font path associated with run.\nconst std::string& getFontPath(const FontCollection::Run& run) {\n  EXPECT_NE(nullptr, run.fakedFont.font);\n  return ((MinikinFontForTest*)run.fakedFont.font)->fontPath();\n}\n\n// Utility function to obtain FontLanguages from string.\nconst FontLanguages& registerAndGetFontLanguages(\n    const std::string& lang_string) {\n  std::scoped_lock _l(gMinikinLock);\n  return FontLanguageListCache::getById(\n      FontLanguageListCache::getId(lang_string));\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_latin) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kItemizeFontXml));\n  std::vector<FontCollection::Run> runs;\n\n  const FontStyle kRegularStyle = FontStyle();\n  const FontStyle kItalicStyle = FontStyle(4, true);\n  const FontStyle kBoldStyle = FontStyle(7, false);\n  const FontStyle kBoldItalicStyle = FontStyle(7, true);\n\n  itemize(collection, \"'a' 'b' 'c' 'd' 'e'\", kRegularStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kLatinFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  itemize(collection, \"'a' 'b' 'c' 'd' 'e'\", kItalicStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kLatinItalicFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  itemize(collection, \"'a' 'b' 'c' 'd' 'e'\", kBoldStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kLatinBoldFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  itemize(collection, \"'a' 'b' 'c' 'd' 'e'\", kBoldItalicStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kLatinBoldItalicFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // Continue if the specific characters (e.g. hyphen, comma, etc.) is\n  // followed.\n  itemize(collection, \"'a' ',' '-' 'd' '!'\", kRegularStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kLatinFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  itemize(collection, \"'a' ',' '-' 'd' '!'\", kRegularStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kLatinFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // U+0301(COMBINING ACUTE ACCENT) must be in the same run with preceding\n  // chars if the font supports it.\n  itemize(collection, \"'a' U+0301\", kRegularStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kLatinFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_emoji) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kItemizeFontXml));\n  std::vector<FontCollection::Run> runs;\n\n  itemize(collection, \"U+1F469 U+1F467\", FontStyle(), &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(4, runs[0].end);\n  EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // U+20E3(COMBINING ENCLOSING KEYCAP) must be in the same run with preceding\n  // character if the font supports.\n  itemize(collection, \"'0' U+20E3\", FontStyle(), &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  itemize(collection, \"U+1F470 U+20E3\", FontStyle(), &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(3, runs[0].end);\n  EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  itemize(collection, \"U+242EE U+1F470 U+20E3\", FontStyle(), &runs);\n  ASSERT_EQ(2U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  EXPECT_EQ(2, runs[1].start);\n  EXPECT_EQ(5, runs[1].end);\n  EXPECT_EQ(kEmojiFont, getFontPath(runs[1]));\n  EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());\n\n  // Currently there is no fonts which has a glyph for 'a' + U+20E3, so they\n  // are splitted into two.\n  itemize(collection, \"'a' U+20E3\", FontStyle(), &runs);\n  ASSERT_EQ(2U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kLatinFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  EXPECT_EQ(1, runs[1].start);\n  EXPECT_EQ(2, runs[1].end);\n  EXPECT_EQ(kEmojiFont, getFontPath(runs[1]));\n  EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_non_latin) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kItemizeFontXml));\n  std::vector<FontCollection::Run> runs;\n\n  FontStyle kJAStyle = FontStyle(FontStyle::registerLanguageList(\"ja_JP\"));\n  FontStyle kUSStyle = FontStyle(FontStyle::registerLanguageList(\"en_US\"));\n  FontStyle kZH_HansStyle =\n      FontStyle(FontStyle::registerLanguageList(\"zh_Hans\"));\n\n  // All Japanese Hiragana characters.\n  itemize(collection, \"U+3042 U+3044 U+3046 U+3048 U+304A\", kUSStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // All Korean Hangul characters.\n  itemize(collection, \"U+B300 U+D55C U+BBFC U+AD6D\", kUSStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(4, runs[0].end);\n  EXPECT_EQ(kKOFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // All Han characters ja, zh-Hans font having.\n  // Japanese font should be selected if the specified language is Japanese.\n  itemize(collection, \"U+81ED U+82B1 U+5FCD\", kJAStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(3, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // Simplified Chinese font should be selected if the specified language is\n  // Simplified Chinese.\n  itemize(collection, \"U+81ED U+82B1 U+5FCD\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(3, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // Fallbacks to other fonts if there is no glyph in the specified language's\n  // font. There is no character U+4F60 in Japanese.\n  itemize(collection, \"U+81ED U+4F60 U+5FCD\", kJAStyle, &runs);\n  ASSERT_EQ(3U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  EXPECT_EQ(1, runs[1].start);\n  EXPECT_EQ(2, runs[1].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[1]));\n  EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());\n\n  EXPECT_EQ(2, runs[2].start);\n  EXPECT_EQ(3, runs[2].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[2]));\n  EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeItalic());\n\n  // Tone mark.\n  itemize(collection, \"U+4444 U+302D\", FontStyle(), &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // Both zh-Hant and ja fonts support U+242EE, but zh-Hans doesn't.\n  // Here, ja and zh-Hant font should have the same score but ja should be\n  // selected since it is listed before zh-Hant.\n  itemize(collection, \"U+242EE\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_mixed) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kItemizeFontXml));\n  std::vector<FontCollection::Run> runs;\n\n  FontStyle kUSStyle = FontStyle(FontStyle::registerLanguageList(\"en_US\"));\n\n  itemize(collection, \"'a' U+4F60 'b' U+4F60 'c'\", kUSStyle, &runs);\n  ASSERT_EQ(5U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kLatinFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  EXPECT_EQ(1, runs[1].start);\n  EXPECT_EQ(2, runs[1].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[1]));\n  EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[1].fakedFont.fakery.isFakeItalic());\n\n  EXPECT_EQ(2, runs[2].start);\n  EXPECT_EQ(3, runs[2].end);\n  EXPECT_EQ(kLatinFont, getFontPath(runs[2]));\n  EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[2].fakedFont.fakery.isFakeItalic());\n\n  EXPECT_EQ(3, runs[3].start);\n  EXPECT_EQ(4, runs[3].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[3]));\n  EXPECT_FALSE(runs[3].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[3].fakedFont.fakery.isFakeItalic());\n\n  EXPECT_EQ(4, runs[4].start);\n  EXPECT_EQ(5, runs[4].end);\n  EXPECT_EQ(kLatinFont, getFontPath(runs[4]));\n  EXPECT_FALSE(runs[4].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[4].fakedFont.fakery.isFakeItalic());\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_variationSelector) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kItemizeFontXml));\n  std::vector<FontCollection::Run> runs;\n\n  // A glyph for U+4FAE is provided by both Japanese font and Simplified\n  // Chinese font. Also a glyph for U+242EE is provided by both Japanese and\n  // Traditional Chinese font.  To avoid effects of device default locale,\n  // explicitly specify the locale.\n  FontStyle kZH_HansStyle =\n      FontStyle(FontStyle::registerLanguageList(\"zh_Hans\"));\n  FontStyle kZH_HantStyle =\n      FontStyle(FontStyle::registerLanguageList(\"zh_Hant\"));\n\n  // U+4FAE is available in both zh_Hans and ja font, but U+4FAE,U+FE00 is\n  // only available in ja font.\n  itemize(collection, \"U+4FAE\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+4FAE U+FE00\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+4FAE U+4FAE U+FE00\", kZH_HansStyle, &runs);\n  ASSERT_EQ(2U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n  EXPECT_EQ(1, runs[1].start);\n  EXPECT_EQ(3, runs[1].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[1]));\n\n  itemize(collection, \"U+4FAE U+4FAE U+FE00 U+4FAE\", kZH_HansStyle, &runs);\n  ASSERT_EQ(3U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n  EXPECT_EQ(1, runs[1].start);\n  EXPECT_EQ(3, runs[1].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[1]));\n  EXPECT_EQ(3, runs[2].start);\n  EXPECT_EQ(4, runs[2].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[2]));\n\n  // Validation selector after validation selector.\n  itemize(collection, \"U+4FAE U+FE00 U+FE00\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(3, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[1]));\n\n  // No font supports U+242EE U+FE0E.\n  itemize(collection, \"U+4FAE U+FE0E\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n\n  // Surrogate pairs handling.\n  // U+242EE is available in ja font and zh_Hant font.\n  // U+242EE U+FE00 is available only in ja font.\n  itemize(collection, \"U+242EE\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+242EE U+FE00\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(3, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+242EE U+242EE U+FE00\", kZH_HantStyle, &runs);\n  ASSERT_EQ(2U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));\n  EXPECT_EQ(2, runs[1].start);\n  EXPECT_EQ(5, runs[1].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[1]));\n\n  itemize(collection, \"U+242EE U+242EE U+FE00 U+242EE\", kZH_HantStyle, &runs);\n  ASSERT_EQ(3U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));\n  EXPECT_EQ(2, runs[1].start);\n  EXPECT_EQ(5, runs[1].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[1]));\n  EXPECT_EQ(5, runs[2].start);\n  EXPECT_EQ(7, runs[2].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[2]));\n\n  // Validation selector after validation selector.\n  itemize(collection, \"U+242EE U+FE00 U+FE00\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(4, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n\n  // No font supports U+242EE U+FE0E\n  itemize(collection, \"U+242EE U+FE0E\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(3, runs[0].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));\n\n  // Isolated variation selector supplement.\n  itemize(collection, \"U+FE00\", FontStyle(), &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_TRUE(runs[0].fakedFont.font == nullptr ||\n              kLatinFont == getFontPath(runs[0]));\n\n  itemize(collection, \"U+FE00\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_TRUE(runs[0].fakedFont.font == nullptr ||\n              kLatinFont == getFontPath(runs[0]));\n\n  // First font family (Regular.ttf) supports U+203C but doesn't support U+203C\n  // U+FE0F. Emoji.ttf font supports U+203C U+FE0F.  Emoji.ttf should be\n  // selected.\n  itemize(collection, \"U+203C U+FE0F\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kEmojiFont, getFontPath(runs[0]));\n\n  // First font family (Regular.ttf) supports U+203C U+FE0E.\n  itemize(collection, \"U+203C U+FE0E\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kLatinFont, getFontPath(runs[0]));\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_variationSelectorSupplement) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kItemizeFontXml));\n  std::vector<FontCollection::Run> runs;\n\n  // A glyph for U+845B is provided by both Japanese font and Simplified\n  // Chinese font. Also a glyph for U+242EE is provided by both Japanese and\n  // Traditional Chinese font.  To avoid effects of device default locale,\n  // explicitly specify the locale.\n  FontStyle kZH_HansStyle =\n      FontStyle(FontStyle::registerLanguageList(\"zh_Hans\"));\n  FontStyle kZH_HantStyle =\n      FontStyle(FontStyle::registerLanguageList(\"zh_Hant\"));\n\n  // U+845B is available in both zh_Hans and ja font, but U+845B,U+E0100 is\n  // only available in ja font.\n  itemize(collection, \"U+845B\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+845B U+E0100\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(3, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+845B U+845B U+E0100\", kZH_HansStyle, &runs);\n  ASSERT_EQ(2U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n  EXPECT_EQ(1, runs[1].start);\n  EXPECT_EQ(4, runs[1].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[1]));\n\n  itemize(collection, \"U+845B U+845B U+E0100 U+845B\", kZH_HansStyle, &runs);\n  ASSERT_EQ(3U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n  EXPECT_EQ(1, runs[1].start);\n  EXPECT_EQ(4, runs[1].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[1]));\n  EXPECT_EQ(4, runs[2].start);\n  EXPECT_EQ(5, runs[2].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[2]));\n\n  // Validation selector after validation selector.\n  itemize(collection, \"U+845B U+E0100 U+E0100\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n\n  // No font supports U+845B U+E01E0.\n  itemize(collection, \"U+845B U+E01E0\", kZH_HansStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(3, runs[0].end);\n  EXPECT_EQ(kZH_HansFont, getFontPath(runs[0]));\n\n  // Isolated variation selector supplement\n  // Surrogate pairs handling.\n  // U+242EE is available in ja font and zh_Hant font.\n  // U+242EE U+E0100 is available only in ja font.\n  itemize(collection, \"U+242EE\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+242EE U+E0101\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(4, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+242EE U+242EE U+E0101\", kZH_HantStyle, &runs);\n  ASSERT_EQ(2U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));\n  EXPECT_EQ(2, runs[1].start);\n  EXPECT_EQ(6, runs[1].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[1]));\n\n  itemize(collection, \"U+242EE U+242EE U+E0101 U+242EE\", kZH_HantStyle, &runs);\n  ASSERT_EQ(3U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));\n  EXPECT_EQ(2, runs[1].start);\n  EXPECT_EQ(6, runs[1].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[1]));\n  EXPECT_EQ(6, runs[2].start);\n  EXPECT_EQ(8, runs[2].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[2]));\n\n  // Validation selector after validation selector.\n  itemize(collection, \"U+242EE U+E0100 U+E0100\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(6, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n\n  // No font supports U+242EE U+E01E0.\n  itemize(collection, \"U+242EE U+E01E0\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(4, runs[0].end);\n  EXPECT_EQ(kZH_HantFont, getFontPath(runs[0]));\n\n  // Isolated variation selector supplement.\n  itemize(collection, \"U+E0100\", FontStyle(), &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_TRUE(runs[0].fakedFont.font == nullptr ||\n              kLatinFont == getFontPath(runs[0]));\n\n  itemize(collection, \"U+E0100\", kZH_HantStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_TRUE(runs[0].fakedFont.font == nullptr ||\n              kLatinFont == getFontPath(runs[0]));\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_no_crash) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kItemizeFontXml));\n  std::vector<FontCollection::Run> runs;\n\n  // Broken Surrogate pairs. Check only not crashing.\n  itemize(collection, \"'a' U+D83D 'a'\", FontStyle(), &runs);\n  itemize(collection, \"'a' U+DC69 'a'\", FontStyle(), &runs);\n  itemize(collection, \"'a' U+D83D U+D83D 'a'\", FontStyle(), &runs);\n  itemize(collection, \"'a' U+DC69 U+DC69 'a'\", FontStyle(), &runs);\n\n  // Isolated variation selector. Check only not crashing.\n  itemize(collection, \"U+FE00 U+FE00\", FontStyle(), &runs);\n  itemize(collection, \"U+E0100 U+E0100\", FontStyle(), &runs);\n  itemize(collection, \"U+FE00 U+E0100\", FontStyle(), &runs);\n  itemize(collection, \"U+E0100 U+FE00\", FontStyle(), &runs);\n\n  // Tone mark only. Check only not crashing.\n  itemize(collection, \"U+302D\", FontStyle(), &runs);\n  itemize(collection, \"U+302D U+302D\", FontStyle(), &runs);\n\n  // Tone mark and variation selector mixed. Check only not crashing.\n  itemize(collection, \"U+FE00 U+302D U+E0100\", FontStyle(), &runs);\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_fakery) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kItemizeFontXml));\n  std::vector<FontCollection::Run> runs;\n\n  FontStyle kJABoldStyle =\n      FontStyle(FontStyle::registerLanguageList(\"ja_JP\"), 0, 7, false);\n  FontStyle kJAItalicStyle =\n      FontStyle(FontStyle::registerLanguageList(\"ja_JP\"), 0, 5, true);\n  FontStyle kJABoldItalicStyle =\n      FontStyle(FontStyle::registerLanguageList(\"ja_JP\"), 0, 7, true);\n\n  // Currently there is no italic or bold font for Japanese. FontFakery has\n  // the differences between desired and actual font style.\n\n  // All Japanese Hiragana characters.\n  itemize(collection, \"U+3042 U+3044 U+3046 U+3048 U+304A\", kJABoldStyle,\n          &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n  EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // All Japanese Hiragana characters.\n  itemize(collection, \"U+3042 U+3044 U+3046 U+3048 U+304A\", kJAItalicStyle,\n          &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n  EXPECT_FALSE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeItalic());\n\n  // All Japanese Hiragana characters.\n  itemize(collection, \"U+3042 U+3044 U+3046 U+3048 U+304A\", kJABoldItalicStyle,\n          &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kJAFont, getFontPath(runs[0]));\n  EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeBold());\n  EXPECT_TRUE(runs[0].fakedFont.fakery.isFakeItalic());\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) {\n  // kVSTestFont supports U+717D U+FE02 but doesn't support U+717D.\n  // kVSTestFont should be selected for U+717D U+FE02 even if it does not\n  // support the base code point.\n  const std::string kVSTestFont =\n      kTestFontDir \"VariationSelectorTest-Regular.ttf\";\n\n  std::vector<std::shared_ptr<FontFamily>> families;\n  std::shared_ptr<MinikinFont> font(new MinikinFontForTest(kLatinFont));\n  std::shared_ptr<FontFamily> family1(new FontFamily(\n      VARIANT_DEFAULT, std::vector<Font>{Font(font, FontStyle())}));\n  families.push_back(family1);\n\n  std::shared_ptr<MinikinFont> font2(new MinikinFontForTest(kVSTestFont));\n  std::shared_ptr<FontFamily> family2(new FontFamily(\n      VARIANT_DEFAULT, std::vector<Font>{Font(font2, FontStyle())}));\n  families.push_back(family2);\n\n  std::shared_ptr<FontCollection> collection(new FontCollection(families));\n\n  std::vector<FontCollection::Run> runs;\n\n  itemize(collection, \"U+717D U+FE02\", FontStyle(), &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kVSTestFont, getFontPath(runs[0]));\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_LanguageScore) {\n  struct TestCase {\n    std::string userPreferredLanguages;\n    std::vector<std::string> fontLanguages;\n    int selectedFontIndex;\n  } testCases[] = {\n      // Font can specify empty language.\n      {\"und\", {\"\", \"\"}, 0},\n      {\"und\", {\"\", \"en-Latn\"}, 0},\n      {\"en-Latn\", {\"\", \"\"}, 0},\n      {\"en-Latn\", {\"\", \"en-Latn\"}, 1},\n\n      // Single user preferred language.\n      // Exact match case\n      {\"en-Latn\", {\"en-Latn\", \"ja-Jpan\"}, 0},\n      {\"ja-Jpan\", {\"en-Latn\", \"ja-Jpan\"}, 1},\n      {\"en-Latn\", {\"en-Latn\", \"nl-Latn\", \"es-Latn\"}, 0},\n      {\"nl-Latn\", {\"en-Latn\", \"nl-Latn\", \"es-Latn\"}, 1},\n      {\"es-Latn\", {\"en-Latn\", \"nl-Latn\", \"es-Latn\"}, 2},\n      {\"es-Latn\", {\"en-Latn\", \"en-Latn\", \"nl-Latn\"}, 0},\n\n      // Exact script match case\n      {\"en-Latn\", {\"nl-Latn\", \"e-Latn\"}, 0},\n      {\"en-Arab\", {\"nl-Latn\", \"ar-Arab\"}, 1},\n      {\"en-Latn\", {\"be-Latn\", \"ar-Arab\", \"d-Beng\"}, 0},\n      {\"en-Arab\", {\"be-Latn\", \"ar-Arab\", \"d-Beng\"}, 1},\n      {\"en-Beng\", {\"be-Latn\", \"ar-Arab\", \"d-Beng\"}, 2},\n      {\"en-Beng\", {\"be-Latn\", \"ar-Beng\", \"d-Beng\"}, 1},\n      {\"zh-Hant\", {\"zh-Hant\", \"zh-Hans\"}, 0},\n      {\"zh-Hans\", {\"zh-Hant\", \"zh-Hans\"}, 1},\n\n      // Subscript match case, e.g. Jpan supports Hira.\n      {\"en-Hira\", {\"ja-Jpan\"}, 0},\n      {\"zh-Hani\", {\"zh-Hans\", \"zh-Hant\"}, 0},\n      {\"zh-Hani\", {\"zh-Hant\", \"zh-Hans\"}, 0},\n      {\"en-Hira\", {\"zh-Hant\", \"ja-Jpan\", \"ja-Jpan\"}, 1},\n\n      // Language match case\n      {\"ja-Latn\", {\"zh-Latn\", \"ja-Latn\"}, 1},\n      {\"zh-Latn\", {\"zh-Latn\", \"ja-Latn\"}, 0},\n      {\"ja-Latn\", {\"zh-Latn\", \"ja-Latn\"}, 1},\n      {\"ja-Latn\", {\"zh-Latn\", \"ja-Latn\", \"ja-Latn\"}, 1},\n\n      // Mixed case\n      // Script/subscript match is strongest.\n      {\"ja-Jpan\", {\"en-Latn\", \"ja-Latn\", \"en-Jpan\"}, 2},\n      {\"ja-Hira\", {\"en-Latn\", \"ja-Latn\", \"en-Jpan\"}, 2},\n      {\"ja-Hira\", {\"en-Latn\", \"ja-Latn\", \"en-Jpan\", \"en-Jpan\"}, 2},\n\n      // Language match only happens if the script matches.\n      {\"ja-Hira\", {\"en-Latn\", \"ja-Latn\"}, 0},\n      {\"ja-Hira\", {\"en-Jpan\", \"ja-Jpan\"}, 1},\n\n      // Multiple languages.\n      // Even if all fonts have the same score, use the 2nd language for better\n      // selection.\n      {\"en-Latn,ja-Jpan\", {\"zh-Hant\", \"zh-Hans\", \"ja-Jpan\"}, 2},\n      {\"en-Latn,nl-Latn\", {\"es-Latn\", \"be-Latn\", \"nl-Latn\"}, 2},\n      {\"en-Latn,br-Latn,nl-Latn\", {\"es-Latn\", \"be-Latn\", \"nl-Latn\"}, 2},\n      {\"en-Latn,br-Latn,nl-Latn\",\n       {\"es-Latn\", \"be-Latn\", \"nl-Latn\", \"nl-Latn\"},\n       2},\n\n      // Script score.\n      {\"en-Latn,ja-Jpan\", {\"en-Arab\", \"en-Jpan\"}, 1},\n      {\"en-Latn,ja-Jpan\", {\"en-Arab\", \"en-Jpan\", \"en-Jpan\"}, 1},\n\n      // Language match case\n      {\"en-Latn,ja-Latn\", {\"bd-Latn\", \"ja-Latn\"}, 1},\n      {\"en-Latn,ja-Latn\", {\"bd-Latn\", \"ja-Latn\", \"ja-Latn\"}, 1},\n\n      // Language match only happens if the script matches.\n      {\"en-Latn,ar-Arab\", {\"en-Beng\", \"ar-Arab\"}, 1},\n\n      // Multiple languages in the font settings.\n      {\"ko-Jamo\", {\"ja-Jpan\", \"ko-Kore\", \"ko-Kore,ko-Jamo\"}, 2},\n      {\"en-Latn\", {\"ja-Jpan\", \"en-Latn,ja-Jpan\"}, 1},\n      {\"en-Latn\", {\"ja-Jpan\", \"ja-Jpan,en-Latn\"}, 1},\n      {\"en-Latn\", {\"ja-Jpan,zh-Hant\", \"en-Latn,ja-Jpan\", \"en-Latn\"}, 1},\n      {\"en-Latn\", {\"zh-Hant,ja-Jpan\", \"ja-Jpan,en-Latn\", \"en-Latn\"}, 1},\n\n      // Kore = Hang + Hani, etc.\n      {\"ko-Kore\", {\"ko-Hang\", \"ko-Jamo,ko-Hani\", \"ko-Hang,ko-Hani\"}, 2},\n      {\"ja-Hrkt\", {\"ja-Hira\", \"ja-Kana\", \"ja-Hira,ja-Kana\"}, 2},\n      {\"ja-Jpan\",\n       {\"ja-Hira\", \"ja-Kana\", \"ja-Hani\", \"ja-Hira,ja-Kana,ja-Hani\"},\n       3},\n      {\"zh-Hanb\", {\"zh-Hant\", \"zh-Bopo\", \"zh-Hant,zh-Bopo\"}, 2},\n      {\"zh-Hanb\", {\"ja-Hanb\", \"zh-Hant,zh-Bopo\"}, 1},\n\n      // Language match with unified subscript bits.\n      {\"zh-Hanb\",\n       {\"zh-Hant\", \"zh-Bopo\", \"ja-Hant,ja-Bopo\", \"zh-Hant,zh-Bopo\"},\n       3},\n      {\"zh-Hanb\",\n       {\"zh-Hant\", \"zh-Bopo\", \"ja-Hant,zh-Bopo\", \"zh-Hant,zh-Bopo\"},\n       3},\n\n      // Two elements subtag matching: language and subtag or language or\n      // script.\n      {\"ja-Kana-u-em-emoji\", {\"zh-Hant\", \"ja-Kana\"}, 1},\n      {\"ja-Kana-u-em-emoji\", {\"zh-Hant\", \"ja-Kana\", \"ja-Zsye\"}, 2},\n      {\"ja-Zsym-u-em-emoji\", {\"ja-Kana\", \"ja-Zsym\", \"ja-Zsye\"}, 2},\n\n      // One element subtag matching: subtag only or script only.\n      {\"en-Latn-u-em-emoji\", {\"ja-Latn\", \"ja-Zsye\"}, 1},\n      {\"en-Zsym-u-em-emoji\", {\"ja-Zsym\", \"ja-Zsye\"}, 1},\n      {\"en-Zsye-u-em-text\", {\"ja-Zsym\", \"ja-Zsye\"}, 0},\n\n      // Multiple languages list with subtags.\n      {\"en-Latn,ja-Jpan-u-em-text\", {\"en-Latn\", \"en-Zsye\", \"en-Zsym\"}, 0},\n      {\"en-Latn,en-Zsye,ja-Jpan-u-em-text\", {\"zh\", \"en-Zsye\", \"en-Zsym\"}, 1},\n  };\n\n  for (auto testCase : testCases) {\n    std::string fontLanguagesStr = \"{\";\n    for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {\n      if (i != 0) {\n        fontLanguagesStr += \", \";\n      }\n      fontLanguagesStr += \"\\\"\" + testCase.fontLanguages[i] + \"\\\"\";\n    }\n    fontLanguagesStr += \"}\";\n    SCOPED_TRACE(\"Test of user preferred languages: \\\"\" +\n                 testCase.userPreferredLanguages +\n                 \"\\\" with font languages: \" + fontLanguagesStr);\n\n    std::vector<std::shared_ptr<FontFamily>> families;\n\n    // Prepare first font which doesn't supports U+9AA8\n    std::shared_ptr<MinikinFont> firstFamilyMinikinFont(\n        new MinikinFontForTest(kNoGlyphFont));\n    std::shared_ptr<FontFamily> firstFamily(new FontFamily(\n        FontStyle::registerLanguageList(\"und\"), 0 /* variant */,\n        std::vector<Font>({Font(firstFamilyMinikinFont, FontStyle())})));\n    families.push_back(firstFamily);\n\n    // Prepare font families\n    // Each font family is associated with a specified language. All font\n    // families except for the first font support U+9AA8.\n    std::unordered_map<MinikinFont*, int> fontLangIdxMap;\n\n    for (size_t i = 0; i < testCase.fontLanguages.size(); ++i) {\n      std::shared_ptr<MinikinFont> minikin_font(\n          new MinikinFontForTest(kJAFont));\n      std::shared_ptr<FontFamily> family(new FontFamily(\n          FontStyle::registerLanguageList(testCase.fontLanguages[i]),\n          0 /* variant */,\n          std::vector<Font>({Font(minikin_font, FontStyle())})));\n      families.push_back(family);\n      fontLangIdxMap.insert(std::make_pair(minikin_font.get(), i));\n    }\n    std::shared_ptr<FontCollection> collection(new FontCollection(families));\n    // Do itemize\n    const FontStyle style = FontStyle(\n        FontStyle::registerLanguageList(testCase.userPreferredLanguages));\n    std::vector<FontCollection::Run> runs;\n    itemize(collection, \"U+9AA8\", style, &runs);\n    ASSERT_EQ(1U, runs.size());\n    ASSERT_NE(nullptr, runs[0].fakedFont.font);\n\n    // First family doesn't support U+9AA8 and others support it, so the first\n    // font should not be selected.\n    EXPECT_NE(firstFamilyMinikinFont.get(), runs[0].fakedFont.font);\n\n    // Lookup used font family by MinikinFont*.\n    const int usedLangIndex = fontLangIdxMap[runs[0].fakedFont.font];\n    EXPECT_EQ(testCase.selectedFontIndex, usedLangIndex);\n  }\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_LanguageAndCoverage) {\n  struct TestCase {\n    std::string testString;\n    std::string requestedLanguages;\n    std::string expectedFont;\n  } testCases[] = {\n      // Following test cases verify that following rules in font fallback\n      // chain.\n      // - If the first font in the collection supports the given character or\n      // variation sequence,\n      //   it should be selected.\n      // - If the font doesn't support the given character, variation sequence\n      // or its base\n      //   character, it should not be selected.\n      // - If two or more fonts match the requested languages, the font matches\n      // with the highest\n      //   priority language should be selected.\n      // - If two or more fonts get the same score, the font listed earlier in\n      // the XML file\n      //   (here, kItemizeFontXml) should be selected.\n\n      // Regardless of language, the first font is always selected if it covers\n      // the code point.\n      {\"'a'\", \"\", kLatinFont},\n      {\"'a'\", \"en-Latn\", kLatinFont},\n      {\"'a'\", \"ja-Jpan\", kLatinFont},\n      {\"'a'\", \"ja-Jpan,en-Latn\", kLatinFont},\n      {\"'a'\", \"zh-Hans,zh-Hant,en-Latn,ja-Jpan,fr-Latn\", kLatinFont},\n\n      // U+81ED is supported by both the ja font and zh-Hans font.\n      {\"U+81ED\", \"\", kZH_HansFont},  // zh-Hans font is listed before ja font.\n      {\"U+81ED\", \"en-Latn\",\n       kZH_HansFont},  // zh-Hans font is listed before ja font.\n      {\"U+81ED\", \"ja-Jpan\", kJAFont},\n      {\"U+81ED\", \"zh-Hans\", kZH_HansFont},\n\n      {\"U+81ED\", \"ja-Jpan,en-Latn\", kJAFont},\n      {\"U+81ED\", \"en-Latn,ja-Jpan\", kJAFont},\n      {\"U+81ED\", \"en-Latn,zh-Hans\", kZH_HansFont},\n      {\"U+81ED\", \"zh-Hans,en-Latn\", kZH_HansFont},\n      {\"U+81ED\", \"ja-Jpan,zh-Hans\", kJAFont},\n      {\"U+81ED\", \"zh-Hans,ja-Jpan\", kZH_HansFont},\n\n      {\"U+81ED\", \"en-Latn,zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+81ED\", \"en-Latn,ja-Jpan,zh-Hans\", kJAFont},\n      {\"U+81ED\", \"en-Latn,zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+81ED\", \"ja-Jpan,en-Latn,zh-Hans\", kJAFont},\n      {\"U+81ED\", \"ja-Jpan,zh-Hans,en-Latn\", kJAFont},\n      {\"U+81ED\", \"zh-Hans,en-Latn,ja-Jpan\", kZH_HansFont},\n      {\"U+81ED\", \"zh-Hans,ja-Jpan,en-Latn\", kZH_HansFont},\n\n      // U+304A is only supported by ja font.\n      {\"U+304A\", \"\", kJAFont},\n      {\"U+304A\", \"ja-Jpan\", kJAFont},\n      {\"U+304A\", \"zh-Hant\", kJAFont},\n      {\"U+304A\", \"zh-Hans\", kJAFont},\n\n      {\"U+304A\", \"ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+304A\", \"zh-Hant,ja-Jpan\", kJAFont},\n      {\"U+304A\", \"zh-Hans,zh-Hant\", kJAFont},\n      {\"U+304A\", \"zh-Hant,zh-Hans\", kJAFont},\n      {\"U+304A\", \"zh-Hans,ja-Jpan\", kJAFont},\n      {\"U+304A\", \"ja-Jpan,zh-Hans\", kJAFont},\n\n      {\"U+304A\", \"zh-Hans,ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+304A\", \"zh-Hans,zh-Hant,ja-Jpan\", kJAFont},\n      {\"U+304A\", \"ja-Jpan,zh-Hans,zh-Hant\", kJAFont},\n      {\"U+304A\", \"ja-Jpan,zh-Hant,zh-Hans\", kJAFont},\n      {\"U+304A\", \"zh-Hant,zh-Hans,ja-Jpan\", kJAFont},\n      {\"U+304A\", \"zh-Hant,ja-Jpan,zh-Hans\", kJAFont},\n\n      // U+242EE is supported by both ja font and zh-Hant fonts but not by\n      // zh-Hans font.\n      {\"U+242EE\", \"\", kJAFont},  // ja font is listed before zh-Hant font.\n      {\"U+242EE\", \"ja-Jpan\", kJAFont},\n      {\"U+242EE\", \"zh-Hans\", kJAFont},\n      {\"U+242EE\", \"zh-Hant\", kZH_HantFont},\n\n      {\"U+242EE\", \"ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+242EE\", \"zh-Hant,ja-Jpan\", kZH_HantFont},\n      {\"U+242EE\", \"zh-Hans,zh-Hant\", kZH_HantFont},\n      {\"U+242EE\", \"zh-Hant,zh-Hans\", kZH_HantFont},\n      {\"U+242EE\", \"zh-Hans,ja-Jpan\", kJAFont},\n      {\"U+242EE\", \"ja-Jpan,zh-Hans\", kJAFont},\n\n      {\"U+242EE\", \"zh-Hans,ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+242EE\", \"zh-Hans,zh-Hant,ja-Jpan\", kZH_HantFont},\n      {\"U+242EE\", \"ja-Jpan,zh-Hans,zh-Hant\", kJAFont},\n      {\"U+242EE\", \"ja-Jpan,zh-Hant,zh-Hans\", kJAFont},\n      {\"U+242EE\", \"zh-Hant,zh-Hans,ja-Jpan\", kZH_HantFont},\n      {\"U+242EE\", \"zh-Hant,ja-Jpan,zh-Hans\", kZH_HantFont},\n\n      // U+9AA8 is supported by all ja-Jpan, zh-Hans, zh-Hant fonts.\n      {\"U+9AA8\", \"\",\n       kZH_HansFont},  // zh-Hans font is listed before ja and zh-Hant fonts.\n      {\"U+9AA8\", \"ja-Jpan\", kJAFont},\n      {\"U+9AA8\", \"zh-Hans\", kZH_HansFont},\n      {\"U+9AA8\", \"zh-Hant\", kZH_HantFont},\n\n      {\"U+9AA8\", \"ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+9AA8\", \"zh-Hant,ja-Jpan\", kZH_HantFont},\n      {\"U+9AA8\", \"zh-Hans,zh-Hant\", kZH_HansFont},\n      {\"U+9AA8\", \"zh-Hant,zh-Hans\", kZH_HantFont},\n      {\"U+9AA8\", \"zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+9AA8\", \"ja-Jpan,zh-Hans\", kJAFont},\n\n      {\"U+9AA8\", \"zh-Hans,ja-Jpan,zh-Hant\", kZH_HansFont},\n      {\"U+9AA8\", \"zh-Hans,zh-Hant,ja-Jpan\", kZH_HansFont},\n      {\"U+9AA8\", \"ja-Jpan,zh-Hans,zh-Hant\", kJAFont},\n      {\"U+9AA8\", \"ja-Jpan,zh-Hant,zh-Hans\", kJAFont},\n      {\"U+9AA8\", \"zh-Hant,zh-Hans,ja-Jpan\", kZH_HantFont},\n      {\"U+9AA8\", \"zh-Hant,ja-Jpan,zh-Hans\", kZH_HantFont},\n\n      // U+242EE U+FE00 is supported by ja font but not by zh-Hans or zh-Hant\n      // fonts.\n      {\"U+242EE U+FE00\", \"\", kJAFont},\n      {\"U+242EE U+FE00\", \"ja-Jpan\", kJAFont},\n      {\"U+242EE U+FE00\", \"zh-Hant\", kJAFont},\n      {\"U+242EE U+FE00\", \"zh-Hans\", kJAFont},\n\n      {\"U+242EE U+FE00\", \"ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+242EE U+FE00\", \"zh-Hant,ja-Jpan\", kJAFont},\n      {\"U+242EE U+FE00\", \"zh-Hans,zh-Hant\", kJAFont},\n      {\"U+242EE U+FE00\", \"zh-Hant,zh-Hans\", kJAFont},\n      {\"U+242EE U+FE00\", \"zh-Hans,ja-Jpan\", kJAFont},\n      {\"U+242EE U+FE00\", \"ja-Jpan,zh-Hans\", kJAFont},\n\n      {\"U+242EE U+FE00\", \"zh-Hans,ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+242EE U+FE00\", \"zh-Hans,zh-Hant,ja-Jpan\", kJAFont},\n      {\"U+242EE U+FE00\", \"ja-Jpan,zh-Hans,zh-Hant\", kJAFont},\n      {\"U+242EE U+FE00\", \"ja-Jpan,zh-Hant,zh-Hans\", kJAFont},\n      {\"U+242EE U+FE00\", \"zh-Hant,zh-Hans,ja-Jpan\", kJAFont},\n      {\"U+242EE U+FE00\", \"zh-Hant,ja-Jpan,zh-Hans\", kJAFont},\n\n      // U+3402 U+E0100 is supported by both zh-Hans and zh-Hant but not by ja\n      // font.\n      {\"U+3402 U+E0100\", \"\",\n       kZH_HansFont},  // zh-Hans font is listed before zh-Hant font.\n      {\"U+3402 U+E0100\", \"ja-Jpan\",\n       kZH_HansFont},  // zh-Hans font is listed before zh-Hant font.\n      {\"U+3402 U+E0100\", \"zh-Hant\", kZH_HantFont},\n      {\"U+3402 U+E0100\", \"zh-Hans\", kZH_HansFont},\n\n      {\"U+3402 U+E0100\", \"ja-Jpan,zh-Hant\", kZH_HantFont},\n      {\"U+3402 U+E0100\", \"zh-Hant,ja-Jpan\", kZH_HantFont},\n      {\"U+3402 U+E0100\", \"zh-Hans,zh-Hant\", kZH_HansFont},\n      {\"U+3402 U+E0100\", \"zh-Hant,zh-Hans\", kZH_HantFont},\n      {\"U+3402 U+E0100\", \"zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+3402 U+E0100\", \"ja-Jpan,zh-Hans\", kZH_HansFont},\n\n      {\"U+3402 U+E0100\", \"zh-Hans,ja-Jpan,zh-Hant\", kZH_HansFont},\n      {\"U+3402 U+E0100\", \"zh-Hans,zh-Hant,ja-Jpan\", kZH_HansFont},\n      {\"U+3402 U+E0100\", \"ja-Jpan,zh-Hans,zh-Hant\", kZH_HansFont},\n      {\"U+3402 U+E0100\", \"ja-Jpan,zh-Hant,zh-Hans\", kZH_HantFont},\n      {\"U+3402 U+E0100\", \"zh-Hant,zh-Hans,ja-Jpan\", kZH_HantFont},\n      {\"U+3402 U+E0100\", \"zh-Hant,ja-Jpan,zh-Hans\", kZH_HantFont},\n\n      // No font supports U+4444 U+FE00 but only zh-Hans supports its base\n      // character U+4444.\n      {\"U+4444 U+FE00\", \"\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"ja-Jpan\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"zh-Hant\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"zh-Hans\", kZH_HansFont},\n\n      {\"U+4444 U+FE00\", \"ja-Jpan,zh-Hant\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"zh-Hant,ja-Jpan\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"zh-Hans,zh-Hant\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"zh-Hant,zh-Hans\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"ja-Jpan,zh-Hans\", kZH_HansFont},\n\n      {\"U+4444 U+FE00\", \"zh-Hans,ja-Jpan,zh-Hant\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"zh-Hans,zh-Hant,ja-Jpan\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"ja-Jpan,zh-Hans,zh-Hant\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"ja-Jpan,zh-Hant,zh-Hans\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"zh-Hant,zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+4444 U+FE00\", \"zh-Hant,ja-Jpan,zh-Hans\", kZH_HansFont},\n\n      // No font supports U+81ED U+E0100 but ja and zh-Hans support its base\n      // character U+81ED.\n      // zh-Hans font is listed before ja font.\n      {\"U+81ED U+E0100\", \"\", kZH_HansFont},\n      {\"U+81ED U+E0100\", \"ja-Jpan\", kJAFont},\n      {\"U+81ED U+E0100\", \"zh-Hant\", kZH_HansFont},\n      {\"U+81ED U+E0100\", \"zh-Hans\", kZH_HansFont},\n\n      {\"U+81ED U+E0100\", \"ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+81ED U+E0100\", \"zh-Hant,ja-Jpan\", kJAFont},\n      {\"U+81ED U+E0100\", \"zh-Hans,zh-Hant\", kZH_HansFont},\n      {\"U+81ED U+E0100\", \"zh-Hant,zh-Hans\", kZH_HansFont},\n      {\"U+81ED U+E0100\", \"zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+81ED U+E0100\", \"ja-Jpan,zh-Hans\", kJAFont},\n\n      {\"U+81ED U+E0100\", \"zh-Hans,ja-Jpan,zh-Hant\", kZH_HansFont},\n      {\"U+81ED U+E0100\", \"zh-Hans,zh-Hant,ja-Jpan\", kZH_HansFont},\n      {\"U+81ED U+E0100\", \"ja-Jpan,zh-Hans,zh-Hant\", kJAFont},\n      {\"U+81ED U+E0100\", \"ja-Jpan,zh-Hant,zh-Hans\", kJAFont},\n      {\"U+81ED U+E0100\", \"zh-Hant,zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+81ED U+E0100\", \"zh-Hant,ja-Jpan,zh-Hans\", kJAFont},\n\n      // No font supports U+9AA8 U+E0100 but all zh-Hans zh-hant ja fonts\n      // support its base\n      // character U+9AA8.\n      // zh-Hans font is listed before ja and zh-Hant fonts.\n      {\"U+9AA8 U+E0100\", \"\", kZH_HansFont},\n      {\"U+9AA8 U+E0100\", \"ja-Jpan\", kJAFont},\n      {\"U+9AA8 U+E0100\", \"zh-Hans\", kZH_HansFont},\n      {\"U+9AA8 U+E0100\", \"zh-Hant\", kZH_HantFont},\n\n      {\"U+9AA8 U+E0100\", \"ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+9AA8 U+E0100\", \"zh-Hant,ja-Jpan\", kZH_HantFont},\n      {\"U+9AA8 U+E0100\", \"zh-Hans,zh-Hant\", kZH_HansFont},\n      {\"U+9AA8 U+E0100\", \"zh-Hant,zh-Hans\", kZH_HantFont},\n      {\"U+9AA8 U+E0100\", \"zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+9AA8 U+E0100\", \"ja-Jpan,zh-Hans\", kJAFont},\n\n      {\"U+9AA8 U+E0100\", \"zh-Hans,ja-Jpan,zh-Hant\", kZH_HansFont},\n      {\"U+9AA8 U+E0100\", \"zh-Hans,zh-Hant,ja-Jpan\", kZH_HansFont},\n      {\"U+9AA8 U+E0100\", \"ja-Jpan,zh-Hans,zh-Hant\", kJAFont},\n      {\"U+9AA8 U+E0100\", \"ja-Jpan,zh-Hant,zh-Hans\", kJAFont},\n      {\"U+9AA8 U+E0100\", \"zh-Hant,zh-Hans,ja-Jpan\", kZH_HantFont},\n      {\"U+9AA8 U+E0100\", \"zh-Hant,ja-Jpan,zh-Hans\", kZH_HantFont},\n\n      // All zh-Hans,zh-Hant,ja fonts support U+35A8 U+E0100 and its base\n      // character U+35A8.\n      // zh-Hans font is listed before ja and zh-Hant fonts.\n      {\"U+35A8\", \"\", kZH_HansFont},\n      {\"U+35A8\", \"ja-Jpan\", kJAFont},\n      {\"U+35A8\", \"zh-Hans\", kZH_HansFont},\n      {\"U+35A8\", \"zh-Hant\", kZH_HantFont},\n\n      {\"U+35A8\", \"ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+35A8\", \"zh-Hant,ja-Jpan\", kZH_HantFont},\n      {\"U+35A8\", \"zh-Hans,zh-Hant\", kZH_HansFont},\n      {\"U+35A8\", \"zh-Hant,zh-Hans\", kZH_HantFont},\n      {\"U+35A8\", \"zh-Hans,ja-Jpan\", kZH_HansFont},\n      {\"U+35A8\", \"ja-Jpan,zh-Hans\", kJAFont},\n\n      {\"U+35A8\", \"zh-Hans,ja-Jpan,zh-Hant\", kZH_HansFont},\n      {\"U+35A8\", \"zh-Hans,zh-Hant,ja-Jpan\", kZH_HansFont},\n      {\"U+35A8\", \"ja-Jpan,zh-Hans,zh-Hant\", kJAFont},\n      {\"U+35A8\", \"ja-Jpan,zh-Hant,zh-Hans\", kJAFont},\n      {\"U+35A8\", \"zh-Hant,zh-Hans,ja-Jpan\", kZH_HantFont},\n      {\"U+35A8\", \"zh-Hant,ja-Jpan,zh-Hans\", kZH_HantFont},\n\n      // All zh-Hans,zh-Hant,ja fonts support U+35B6 U+E0100, but zh-Hant and ja\n      // fonts support its\n      // base character U+35B6.\n      // ja font is listed before zh-Hant font.\n      {\"U+35B6\", \"\", kJAFont},\n      {\"U+35B6\", \"ja-Jpan\", kJAFont},\n      {\"U+35B6\", \"zh-Hant\", kZH_HantFont},\n      {\"U+35B6\", \"zh-Hans\", kJAFont},\n\n      {\"U+35B6\", \"ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+35B6\", \"zh-Hant,ja-Jpan\", kZH_HantFont},\n      {\"U+35B6\", \"zh-Hans,zh-Hant\", kZH_HantFont},\n      {\"U+35B6\", \"zh-Hant,zh-Hans\", kZH_HantFont},\n      {\"U+35B6\", \"zh-Hans,ja-Jpan\", kJAFont},\n      {\"U+35B6\", \"ja-Jpan,zh-Hans\", kJAFont},\n\n      {\"U+35B6\", \"zh-Hans,ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+35B6\", \"zh-Hans,zh-Hant,ja-Jpan\", kZH_HantFont},\n      {\"U+35B6\", \"ja-Jpan,zh-Hans,zh-Hant\", kJAFont},\n      {\"U+35B6\", \"ja-Jpan,zh-Hant,zh-Hans\", kJAFont},\n      {\"U+35B6\", \"zh-Hant,zh-Hans,ja-Jpan\", kZH_HantFont},\n      {\"U+35B6\", \"zh-Hant,ja-Jpan,zh-Hans\", kZH_HantFont},\n\n      // All zh-Hans,zh-Hant,ja fonts support U+35C5 U+E0100, but only ja font\n      // supports its base\n      // character U+35C5.\n      {\"U+35C5\", \"\", kJAFont},\n      {\"U+35C5\", \"ja-Jpan\", kJAFont},\n      {\"U+35C5\", \"zh-Hant\", kJAFont},\n      {\"U+35C5\", \"zh-Hans\", kJAFont},\n\n      {\"U+35C5\", \"ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+35C5\", \"zh-Hant,ja-Jpan\", kJAFont},\n      {\"U+35C5\", \"zh-Hans,zh-Hant\", kJAFont},\n      {\"U+35C5\", \"zh-Hant,zh-Hans\", kJAFont},\n      {\"U+35C5\", \"zh-Hans,ja-Jpan\", kJAFont},\n      {\"U+35C5\", \"ja-Jpan,zh-Hans\", kJAFont},\n\n      {\"U+35C5\", \"zh-Hans,ja-Jpan,zh-Hant\", kJAFont},\n      {\"U+35C5\", \"zh-Hans,zh-Hant,ja-Jpan\", kJAFont},\n      {\"U+35C5\", \"ja-Jpan,zh-Hans,zh-Hant\", kJAFont},\n      {\"U+35C5\", \"ja-Jpan,zh-Hant,zh-Hans\", kJAFont},\n      {\"U+35C5\", \"zh-Hant,zh-Hans,ja-Jpan\", kJAFont},\n      {\"U+35C5\", \"zh-Hant,ja-Jpan,zh-Hans\", kJAFont},\n\n      // None of ja-Jpan, zh-Hant, zh-Hans font supports U+1F469. Emoji font\n      // supports it.\n      {\"U+1F469\", \"\", kEmojiFont},\n      {\"U+1F469\", \"ja-Jpan\", kEmojiFont},\n      {\"U+1F469\", \"zh-Hant\", kEmojiFont},\n      {\"U+1F469\", \"zh-Hans\", kEmojiFont},\n\n      {\"U+1F469\", \"ja-Jpan,zh-Hant\", kEmojiFont},\n      {\"U+1F469\", \"zh-Hant,ja-Jpan\", kEmojiFont},\n      {\"U+1F469\", \"zh-Hans,zh-Hant\", kEmojiFont},\n      {\"U+1F469\", \"zh-Hant,zh-Hans\", kEmojiFont},\n      {\"U+1F469\", \"zh-Hans,ja-Jpan\", kEmojiFont},\n      {\"U+1F469\", \"ja-Jpan,zh-Hans\", kEmojiFont},\n\n      {\"U+1F469\", \"zh-Hans,ja-Jpan,zh-Hant\", kEmojiFont},\n      {\"U+1F469\", \"zh-Hans,zh-Hant,ja-Jpan\", kEmojiFont},\n      {\"U+1F469\", \"ja-Jpan,zh-Hans,zh-Hant\", kEmojiFont},\n      {\"U+1F469\", \"ja-Jpan,zh-Hant,zh-Hans\", kEmojiFont},\n      {\"U+1F469\", \"zh-Hant,zh-Hans,ja-Jpan\", kEmojiFont},\n      {\"U+1F469\", \"zh-Hant,ja-Jpan,zh-Hans\", kEmojiFont},\n  };\n\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kItemizeFontXml));\n\n  for (auto testCase : testCases) {\n    SCOPED_TRACE(\"Test for \\\"\" + testCase.testString + \"\\\" with languages \" +\n                 testCase.requestedLanguages);\n\n    std::vector<FontCollection::Run> runs;\n    const FontStyle style =\n        FontStyle(FontStyle::registerLanguageList(testCase.requestedLanguages));\n    itemize(collection, testCase.testString.c_str(), style, &runs);\n    ASSERT_EQ(1U, runs.size());\n    EXPECT_EQ(testCase.expectedFont, getFontPath(runs[0]));\n  }\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_emojiSelection_withFE0E) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kEmojiXmlFile));\n  std::vector<FontCollection::Run> runs;\n\n  const FontStyle kDefaultFontStyle;\n\n  // U+00A9 is a text default emoji which is only available in\n  // TextEmojiFont.ttf. TextEmojiFont.ttf should be selected.\n  itemize(collection, \"U+00A9 U+FE0E\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));\n\n  // U+00A9 is a text default emoji which is only available in\n  // ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected.\n  itemize(collection, \"U+00AE U+FE0E\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  // Text emoji is specified but it is not available. Use color emoji instead.\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  // U+203C is a text default emoji which is available in both TextEmojiFont.ttf\n  // and ColorEmojiFont.ttf. TextEmojiFont.ttf should be selected.\n  itemize(collection, \"U+203C U+FE0E\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));\n\n  // U+2049 is a text default emoji which is not available either\n  // TextEmojiFont.ttf or ColorEmojiFont.ttf. No font should be selected.\n  itemize(collection, \"U+2049 U+FE0E\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));\n\n  // U+231A is a emoji default emoji which is available only in TextEmojifFont.\n  // TextEmojiFont.ttf sohuld be selected.\n  itemize(collection, \"U+231A U+FE0E\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));\n\n  // U+231B is a emoji default emoji which is available only in\n  // ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected.\n  itemize(collection, \"U+231B U+FE0E\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  // Text emoji is specified but it is not available. Use color emoji instead.\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  // U+23E9 is a emoji default emoji which is available in both\n  // TextEmojiFont.ttf and ColorEmojiFont.ttf. TextEmojiFont.ttf should be\n  // selected even if U+23E9 is emoji default emoji since U+FE0E is appended.\n  itemize(collection, \"U+23E9 U+FE0E\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));\n\n  // U+23EA is a emoji default emoji but which is not available in either\n  // TextEmojiFont.ttf or ColorEmojiFont.ttf. No font should be selected.\n  itemize(collection, \"U+23EA U+FE0E\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));\n\n  // U+26FA U+FE0E is specified but ColorTextMixedEmojiFont has a variation\n  // sequence U+26F9 U+FE0F in its cmap, so ColorTextMixedEmojiFont should be\n  // selected instaed of ColorEmojiFont.\n  itemize(collection, \"U+26FA U+FE0E\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kMixedEmojiFont, getFontPath(runs[0]));\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_emojiSelection_withFE0F) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kEmojiXmlFile));\n  std::vector<FontCollection::Run> runs;\n\n  const FontStyle kDefaultFontStyle;\n\n  // U+00A9 is a text default emoji which is available only in\n  // TextEmojiFont.ttf. TextEmojiFont.ttf should be selected.\n  itemize(collection, \"U+00A9 U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  // Color emoji is specified but it is not available. Use text representation\n  // instead.\n  EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));\n\n  // U+00AE is a text default emoji which is available only in\n  // ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected.\n  itemize(collection, \"U+00AE U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  // U+203C is a text default emoji which is available in both TextEmojiFont.ttf\n  // and ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected even if\n  // U+203C is a text default emoji since U+FF0F is appended.\n  itemize(collection, \"U+203C U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  // U+2049 is a text default emoji which is not available in either\n  // TextEmojiFont.ttf or ColorEmojiFont.ttf. No font should be selected.\n  itemize(collection, \"U+2049 U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));\n\n  // U+231A is a emoji default emoji which is available only in\n  // TextEmojiFont.ttf. TextEmojiFont.ttf should be selected.\n  itemize(collection, \"U+231A U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  // Color emoji is specified but it is not available. Use text representation\n  // instead.\n  EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));\n\n  // U+231B is a emoji default emoji which is available only in\n  // ColorEmojiFont.ttf. ColorEmojiFont.ttf should be selected.\n  itemize(collection, \"U+231B U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  // U+23E9 is a emoji default emoji which is available in both\n  // TextEmojiFont.ttf and ColorEmojiFont.ttf. ColorEmojiFont.ttf should be\n  // selected.\n  itemize(collection, \"U+23E9 U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  // U+23EA is a emoji default emoji which is not available in either\n  // TextEmojiFont.ttf or ColorEmojiFont.ttf. No font should be selected.\n  itemize(collection, \"U+23EA U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));\n\n  // U+26F9 U+FE0F is specified but ColorTextMixedEmojiFont has a variation\n  // sequence U+26F9 U+FE0F in its cmap, so ColorTextMixedEmojiFont should be\n  // selected instaed of ColorEmojiFont.\n  itemize(collection, \"U+26F9 U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kMixedEmojiFont, getFontPath(runs[0]));\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_emojiSelection_with_skinTone) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kEmojiXmlFile));\n  std::vector<FontCollection::Run> runs;\n\n  const FontStyle kDefaultFontStyle;\n\n  // TextEmoji font is selected since it is listed before ColorEmoji font.\n  itemize(collection, \"U+261D\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(1, runs[0].end);\n  EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));\n\n  // If skin tone is specified, it should be colored.\n  itemize(collection, \"U+261D U+1F3FD\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(3, runs[0].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  // Still color font is selected if an emoji variation selector is specified.\n  itemize(collection, \"U+261D U+FE0F U+1F3FD\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(4, runs[0].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  // Text font should be selected if a text variation selector is specified and\n  // skin tone is rendered by itself.\n  itemize(collection, \"U+261D U+FE0E U+1F3FD\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(2U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kTextEmojiFont, getFontPath(runs[0]));\n  EXPECT_EQ(2, runs[1].start);\n  EXPECT_EQ(4, runs[1].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[1]));\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_PrivateUseArea) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kEmojiXmlFile));\n  std::vector<FontCollection::Run> runs;\n\n  const FontStyle kDefaultFontStyle;\n\n  // Should not set nullptr to the result run. (Issue 26808815)\n  itemize(collection, \"U+FEE10\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(2, runs[0].end);\n  EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+FEE40 U+FE4C5\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(4, runs[0].end);\n  EXPECT_EQ(kNoGlyphFont, getFontPath(runs[0]));\n}\n\nTEST_F(FontCollectionItemizeTest, itemize_genderBalancedEmoji) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kEmojiXmlFile));\n  std::vector<FontCollection::Run> runs;\n\n  const FontStyle kDefaultFontStyle;\n\n  itemize(collection, \"U+1F469 U+200D U+1F373\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+1F469 U+200D U+2695 U+FE0F\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(5, runs[0].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n\n  itemize(collection, \"U+1F469 U+200D U+2695\", kDefaultFontStyle, &runs);\n  ASSERT_EQ(1U, runs.size());\n  EXPECT_EQ(0, runs[0].start);\n  EXPECT_EQ(4, runs[0].end);\n  EXPECT_EQ(kColorEmojiFont, getFontPath(runs[0]));\n}\n\n// For b/29585939\nTEST_F(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS) {\n  const FontStyle kDefaultFontStyle;\n\n  std::shared_ptr<MinikinFont> dummyFont(new MinikinFontForTest(kNoGlyphFont));\n  std::shared_ptr<MinikinFont> fontA(new MinikinFontForTest(kZH_HansFont));\n  std::shared_ptr<MinikinFont> fontB(new MinikinFontForTest(kZH_HansFont));\n\n  std::shared_ptr<FontFamily> dummyFamily(\n      new FontFamily(std::vector<Font>({Font(dummyFont, FontStyle())})));\n  std::shared_ptr<FontFamily> familyA(\n      new FontFamily(std::vector<Font>({Font(fontA, FontStyle())})));\n  std::shared_ptr<FontFamily> familyB(\n      new FontFamily(std::vector<Font>({Font(fontB, FontStyle())})));\n\n  std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, familyA,\n                                                       familyB};\n  std::vector<std::shared_ptr<FontFamily>> reversedFamilies = {\n      dummyFamily, familyB, familyA};\n\n  std::shared_ptr<FontCollection> collection(new FontCollection(families));\n  std::shared_ptr<FontCollection> reversedCollection(\n      new FontCollection(reversedFamilies));\n\n  // Both fontA/fontB support U+35A8 but don't support U+35A8 U+E0100. The first\n  // font should be selected.\n  std::vector<FontCollection::Run> runs;\n  itemize(collection, \"U+35A8 U+E0100\", kDefaultFontStyle, &runs);\n  EXPECT_EQ(fontA.get(), runs[0].fakedFont.font);\n\n  itemize(reversedCollection, \"U+35A8 U+E0100\", kDefaultFontStyle, &runs);\n  EXPECT_EQ(fontB.get(), runs[0].fakedFont.font);\n}\n\n// For b/29585939\nTEST_F(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS2) {\n  const FontStyle kDefaultFontStyle;\n\n  std::shared_ptr<MinikinFont> dummyFont(new MinikinFontForTest(kNoGlyphFont));\n  std::shared_ptr<MinikinFont> hasCmapFormat14Font(\n      new MinikinFontForTest(kHasCmapFormat14Font));\n  std::shared_ptr<MinikinFont> noCmapFormat14Font(\n      new MinikinFontForTest(kNoCmapFormat14Font));\n\n  std::shared_ptr<FontFamily> dummyFamily(\n      new FontFamily(std::vector<Font>({Font(dummyFont, FontStyle())})));\n  std::shared_ptr<FontFamily> hasCmapFormat14Family(new FontFamily(\n      std::vector<Font>({Font(hasCmapFormat14Font, FontStyle())})));\n  std::shared_ptr<FontFamily> noCmapFormat14Family(new FontFamily(\n      std::vector<Font>({Font(noCmapFormat14Font, FontStyle())})));\n\n  std::vector<std::shared_ptr<FontFamily>> families = {\n      dummyFamily, hasCmapFormat14Family, noCmapFormat14Family};\n  std::vector<std::shared_ptr<FontFamily>> reversedFamilies = {\n      dummyFamily, noCmapFormat14Family, hasCmapFormat14Family};\n\n  std::shared_ptr<FontCollection> collection(new FontCollection(families));\n  std::shared_ptr<FontCollection> reversedCollection(\n      new FontCollection(reversedFamilies));\n\n  // Both hasCmapFormat14Font/noCmapFormat14Font support U+5380 but don't\n  // support U+5380 U+E0100. The first font should be selected.\n  std::vector<FontCollection::Run> runs;\n  itemize(collection, \"U+5380 U+E0100\", kDefaultFontStyle, &runs);\n  EXPECT_EQ(hasCmapFormat14Font.get(), runs[0].fakedFont.font);\n\n  itemize(reversedCollection, \"U+5380 U+E0100\", kDefaultFontStyle, &runs);\n  EXPECT_EQ(noCmapFormat14Font.get(), runs[0].fakedFont.font);\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/FontCollectionTest.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\n#include <minikin/FontCollection.h>\n#include \"FontTestUtils.h\"\n#include \"MinikinFontForTest.h\"\n#include \"minikin/MinikinInternal.h\"\n\nnamespace minikin {\n\n// The test font has following glyphs.\n// U+82A6\n// U+82A6 U+FE00 (VS1)\n// U+82A6 U+E0100 (VS17)\n// U+82A6 U+E0101 (VS18)\n// U+82A6 U+E0102 (VS19)\n// U+845B\n// U+845B U+FE01 (VS2)\n// U+845B U+E0101 (VS18)\n// U+845B U+E0102 (VS19)\n// U+845B U+E0103 (VS20)\n// U+537F\n// U+717D U+FE02 (VS3)\n// U+717D U+E0102 (VS19)\n// U+717D U+E0103 (VS20)\nconst char kVsTestFont[] = kTestFontDir \"/VariationSelectorTest-Regular.ttf\";\n\nvoid expectVSGlyphs(const FontCollection* fc,\n                    uint32_t codepoint,\n                    const std::set<uint32_t>& vsSet) {\n  for (uint32_t vs = 0xFE00; vs <= 0xE01EF; ++vs) {\n    // Move to variation selectors supplements after variation selectors.\n    if (vs == 0xFF00) {\n      vs = 0xE0100;\n    }\n    if (vsSet.find(vs) == vsSet.end()) {\n      EXPECT_FALSE(fc->hasVariationSelector(codepoint, vs))\n          << \"Glyph for U+\" << std::hex << codepoint << \" U+\" << vs;\n    } else {\n      EXPECT_TRUE(fc->hasVariationSelector(codepoint, vs))\n          << \"Glyph for U+\" << std::hex << codepoint << \" U+\" << vs;\n    }\n  }\n}\n\nTEST(FontCollectionTest, hasVariationSelectorTest) {\n  std::shared_ptr<MinikinFont> font(new MinikinFontForTest(kVsTestFont));\n  std::shared_ptr<FontFamily> family(\n      new FontFamily(std::vector<Font>({Font(font, FontStyle())})));\n  std::vector<std::shared_ptr<FontFamily>> families({family});\n  std::shared_ptr<FontCollection> fc(new FontCollection(families));\n\n  EXPECT_FALSE(fc->hasVariationSelector(0x82A6, 0));\n  expectVSGlyphs(\n      fc.get(), 0x82A6,\n      std::set<uint32_t>({0xFE00, 0xFE0E, 0xE0100, 0xE0101, 0xE0102}));\n\n  EXPECT_FALSE(fc->hasVariationSelector(0x845B, 0));\n  expectVSGlyphs(\n      fc.get(), 0x845B,\n      std::set<uint32_t>({0xFE01, 0xFE0E, 0xE0101, 0xE0102, 0xE0103}));\n\n  EXPECT_FALSE(fc->hasVariationSelector(0x537F, 0));\n  expectVSGlyphs(fc.get(), 0x537F, std::set<uint32_t>({0xFE0E}));\n\n  EXPECT_FALSE(fc->hasVariationSelector(0x717D, 0));\n  expectVSGlyphs(fc.get(), 0x717D,\n                 std::set<uint32_t>({0xFE02, 0xE0102, 0xE0103}));\n}\n\nconst char kEmojiXmlFile[] = kTestFontDir \"emoji.xml\";\n\nTEST(FontCollectionTest, hasVariationSelectorTest_emoji) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kEmojiXmlFile));\n\n  // Both text/color font have cmap format 14 subtable entry for VS15/VS16\n  // respectively.\n  EXPECT_TRUE(collection->hasVariationSelector(0x2623, 0xFE0E));\n  EXPECT_TRUE(collection->hasVariationSelector(0x2623, 0xFE0F));\n\n  // The text font has cmap format 14 subtable entry for VS15 but the color font\n  // doesn't have for VS16\n  EXPECT_TRUE(collection->hasVariationSelector(0x2626, 0xFE0E));\n  EXPECT_FALSE(collection->hasVariationSelector(0x2626, 0xFE0F));\n\n  // The color font has cmap format 14 subtable entry for VS16 but the text font\n  // doesn't have for VS15.\n  EXPECT_TRUE(collection->hasVariationSelector(0x262A, 0xFE0E));\n  EXPECT_TRUE(collection->hasVariationSelector(0x262A, 0xFE0F));\n\n  // Neither text/color font have cmap format 14 subtable entry for VS15/VS16.\n  EXPECT_TRUE(collection->hasVariationSelector(0x262E, 0xFE0E));\n  EXPECT_FALSE(collection->hasVariationSelector(0x262E, 0xFE0F));\n\n  // Text font doesn't support U+1F3FD. Only the color emoji fonts has. So VS15\n  // is not supported.\n  EXPECT_FALSE(collection->hasVariationSelector(0x1F3FD, 0xFE0E));\n\n  // Text font doesn't have U+262F U+FE0E or even its base code point U+262F.\n  EXPECT_FALSE(collection->hasVariationSelector(0x262F, 0xFE0E));\n\n  // None of the fonts support U+2229.\n  EXPECT_FALSE(collection->hasVariationSelector(0x2229, 0xFE0E));\n  EXPECT_FALSE(collection->hasVariationSelector(0x2229, 0xFE0F));\n}\n\nTEST(FontCollectionTest, newEmojiTest) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(kTestFontDir, kEmojiXmlFile));\n\n  // U+2695, U+2640, U+2642 are not in emoji catrgory in Unicode 9 but they are\n  // now in emoji category. Should return true even if U+FE0E was appended.\n  // These three emojis are only avalilable in TextEmoji.ttf but U+2695 is\n  // excluded here since it is used in other tests.\n  EXPECT_TRUE(collection->hasVariationSelector(0x2640, 0xFE0E));\n  EXPECT_FALSE(collection->hasVariationSelector(0x2640, 0xFE0F));\n  EXPECT_TRUE(collection->hasVariationSelector(0x2642, 0xFE0E));\n  EXPECT_FALSE(collection->hasVariationSelector(0x2642, 0xFE0F));\n}\n\nTEST(FontCollectionTest, createWithVariations) {\n  // This font has 'wdth' and 'wght' axes.\n  const char kMultiAxisFont[] = kTestFontDir \"/MultiAxis.ttf\";\n  const char kNoAxisFont[] = kTestFontDir \"/Regular.ttf\";\n\n  std::shared_ptr<MinikinFont> multiAxisFont(\n      new MinikinFontForTest(kMultiAxisFont));\n  std::shared_ptr<FontFamily> multiAxisFamily(\n      new FontFamily(std::vector<Font>({Font(multiAxisFont, FontStyle())})));\n  std::vector<std::shared_ptr<FontFamily>> multiAxisFamilies({multiAxisFamily});\n  std::shared_ptr<FontCollection> multiAxisFc(\n      new FontCollection(multiAxisFamilies));\n\n  std::shared_ptr<MinikinFont> noAxisFont(new MinikinFontForTest(kNoAxisFont));\n  std::shared_ptr<FontFamily> noAxisFamily(\n      new FontFamily(std::vector<Font>({Font(noAxisFont, FontStyle())})));\n  std::vector<std::shared_ptr<FontFamily>> noAxisFamilies({noAxisFamily});\n  std::shared_ptr<FontCollection> noAxisFc(new FontCollection(noAxisFamilies));\n\n  {\n    // Do not ceate new instance if none of variations are specified.\n    EXPECT_EQ(nullptr, multiAxisFc->createCollectionWithVariation(\n                           std::vector<FontVariation>()));\n    EXPECT_EQ(nullptr, noAxisFc->createCollectionWithVariation(\n                           std::vector<FontVariation>()));\n  }\n  {\n    // New instance should be used for supported variation.\n    std::vector<FontVariation> variations = {\n        {MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f}};\n    std::shared_ptr<FontCollection> newFc(\n        multiAxisFc->createCollectionWithVariation(variations));\n    EXPECT_NE(nullptr, newFc.get());\n    EXPECT_NE(multiAxisFc.get(), newFc.get());\n\n    EXPECT_EQ(nullptr, noAxisFc->createCollectionWithVariation(variations));\n  }\n  {\n    // New instance should be used for supported variation (multiple variations\n    // case).\n    std::vector<FontVariation> variations = {\n        {MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},\n        {MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f}};\n    std::shared_ptr<FontCollection> newFc(\n        multiAxisFc->createCollectionWithVariation(variations));\n    EXPECT_NE(nullptr, newFc.get());\n    EXPECT_NE(multiAxisFc.get(), newFc.get());\n\n    EXPECT_EQ(nullptr, noAxisFc->createCollectionWithVariation(variations));\n  }\n  {\n    // Do not ceate new instance if none of variations are supported.\n    std::vector<FontVariation> variations = {\n        {MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};\n    EXPECT_EQ(nullptr, multiAxisFc->createCollectionWithVariation(variations));\n    EXPECT_EQ(nullptr, noAxisFc->createCollectionWithVariation(variations));\n  }\n  {\n    // At least one axis is supported, should create new instance.\n    std::vector<FontVariation> variations = {\n        {MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},\n        {MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};\n    std::shared_ptr<FontCollection> newFc(\n        multiAxisFc->createCollectionWithVariation(variations));\n    EXPECT_NE(nullptr, newFc.get());\n    EXPECT_NE(multiAxisFc.get(), newFc.get());\n\n    EXPECT_EQ(nullptr, noAxisFc->createCollectionWithVariation(variations));\n  }\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/FontFamilyTest.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <minikin/FontFamily.h>\n\n#include <gtest/gtest.h>\n#include <log/log.h>\n\n#include \"ICUTestBase.h\"\n#include \"MinikinFontForTest.h\"\n#include \"minikin/FontLanguageListCache.h\"\n#include \"minikin/MinikinInternal.h\"\n\nnamespace minikin {\n\ntypedef ICUTestBase FontLanguagesTest;\ntypedef ICUTestBase FontLanguageTest;\n\nstatic const FontLanguages& createFontLanguages(const std::string& input) {\n  std::scoped_lock _l(gMinikinLock);\n  uint32_t langId = FontLanguageListCache::getId(input);\n  return FontLanguageListCache::getById(langId);\n}\n\nstatic FontLanguage createFontLanguage(const std::string& input) {\n  std::scoped_lock _l(gMinikinLock);\n  uint32_t langId = FontLanguageListCache::getId(input);\n  return FontLanguageListCache::getById(langId)[0];\n}\n\nstatic FontLanguage createFontLanguageWithoutICUSanitization(\n    const std::string& input) {\n  return FontLanguage(input.c_str(), input.size());\n}\n\nstd::shared_ptr<FontFamily> makeFamily(const std::string& fontPath) {\n  std::shared_ptr<MinikinFont> font(new MinikinFontForTest(fontPath));\n  return std::make_shared<FontFamily>(\n      std::vector<Font>({Font(font, FontStyle())}));\n}\n\nTEST_F(FontLanguageTest, basicTests) {\n  FontLanguage defaultLang;\n  FontLanguage emptyLang(\"\", 0);\n  FontLanguage english = createFontLanguage(\"en\");\n  FontLanguage french = createFontLanguage(\"fr\");\n  FontLanguage und = createFontLanguage(\"und\");\n  FontLanguage undZsye = createFontLanguage(\"und-Zsye\");\n\n  EXPECT_EQ(english, english);\n  EXPECT_EQ(french, french);\n\n  EXPECT_TRUE(defaultLang != defaultLang);\n  EXPECT_TRUE(emptyLang != emptyLang);\n  EXPECT_TRUE(defaultLang != emptyLang);\n  EXPECT_TRUE(defaultLang != und);\n  EXPECT_TRUE(emptyLang != und);\n  EXPECT_TRUE(english != defaultLang);\n  EXPECT_TRUE(english != emptyLang);\n  EXPECT_TRUE(english != french);\n  EXPECT_TRUE(english != undZsye);\n  EXPECT_TRUE(und != undZsye);\n  EXPECT_TRUE(english != und);\n\n  EXPECT_TRUE(defaultLang.isUnsupported());\n  EXPECT_TRUE(emptyLang.isUnsupported());\n\n  EXPECT_FALSE(english.isUnsupported());\n  EXPECT_FALSE(french.isUnsupported());\n  EXPECT_FALSE(und.isUnsupported());\n  EXPECT_FALSE(undZsye.isUnsupported());\n}\n\nTEST_F(FontLanguageTest, getStringTest) {\n  EXPECT_EQ(\"en-Latn-US\", createFontLanguage(\"en\").getString());\n  EXPECT_EQ(\"en-Latn-US\", createFontLanguage(\"en-Latn\").getString());\n\n  // Capitalized language code or lowercased script should be normalized.\n  EXPECT_EQ(\"en-Latn-US\", createFontLanguage(\"EN-LATN\").getString());\n  EXPECT_EQ(\"en-Latn-US\", createFontLanguage(\"EN-latn\").getString());\n  EXPECT_EQ(\"en-Latn-US\", createFontLanguage(\"en-latn\").getString());\n\n  // Invalid script should be kept.\n  EXPECT_EQ(\"en-Xyzt-US\", createFontLanguage(\"en-xyzt\").getString());\n\n  EXPECT_EQ(\"en-Latn-US\", createFontLanguage(\"en-Latn-US\").getString());\n  EXPECT_EQ(\"ja-Jpan-JP\", createFontLanguage(\"ja\").getString());\n  EXPECT_EQ(\"zh-Hant-TW\", createFontLanguage(\"zh-TW\").getString());\n  EXPECT_EQ(\"zh-Hant-HK\", createFontLanguage(\"zh-HK\").getString());\n  EXPECT_EQ(\"zh-Hant-MO\", createFontLanguage(\"zh-MO\").getString());\n  EXPECT_EQ(\"zh-Hans-CN\", createFontLanguage(\"zh\").getString());\n  EXPECT_EQ(\"zh-Hans-CN\", createFontLanguage(\"zh-CN\").getString());\n  EXPECT_EQ(\"zh-Hans-SG\", createFontLanguage(\"zh-SG\").getString());\n  EXPECT_EQ(\"und\", createFontLanguage(\"und\").getString());\n  EXPECT_EQ(\"und\", createFontLanguage(\"UND\").getString());\n  EXPECT_EQ(\"und\", createFontLanguage(\"Und\").getString());\n  EXPECT_EQ(\"und-Zsye\", createFontLanguage(\"und-Zsye\").getString());\n  EXPECT_EQ(\"und-Zsye\", createFontLanguage(\"Und-ZSYE\").getString());\n  EXPECT_EQ(\"und-Zsye\", createFontLanguage(\"Und-zsye\").getString());\n\n  EXPECT_EQ(\"de-Latn-DE\", createFontLanguage(\"de-1901\").getString());\n\n  EXPECT_EQ(\"es-Latn-419\", createFontLanguage(\"es-Latn-419\").getString());\n\n  // Emoji subtag is dropped from getString().\n  EXPECT_EQ(\"es-Latn-419\", createFontLanguage(\"es-419-u-em-emoji\").getString());\n  EXPECT_EQ(\"es-Latn-419\",\n            createFontLanguage(\"es-Latn-419-u-em-emoji\").getString());\n\n  // This is not a necessary desired behavior, just known behavior.\n  EXPECT_EQ(\"en-Latn-US\", createFontLanguage(\"und-Abcdefgh\").getString());\n}\n\nTEST_F(FontLanguageTest, testReconstruction) {\n  EXPECT_EQ(\"en\", createFontLanguageWithoutICUSanitization(\"en\").getString());\n  EXPECT_EQ(\"fil\", createFontLanguageWithoutICUSanitization(\"fil\").getString());\n  EXPECT_EQ(\"und\", createFontLanguageWithoutICUSanitization(\"und\").getString());\n\n  EXPECT_EQ(\"en-Latn\",\n            createFontLanguageWithoutICUSanitization(\"en-Latn\").getString());\n  EXPECT_EQ(\"fil-Taga\",\n            createFontLanguageWithoutICUSanitization(\"fil-Taga\").getString());\n  EXPECT_EQ(\"und-Zsye\",\n            createFontLanguageWithoutICUSanitization(\"und-Zsye\").getString());\n\n  EXPECT_EQ(\"en-US\",\n            createFontLanguageWithoutICUSanitization(\"en-US\").getString());\n  EXPECT_EQ(\"fil-PH\",\n            createFontLanguageWithoutICUSanitization(\"fil-PH\").getString());\n  EXPECT_EQ(\"es-419\",\n            createFontLanguageWithoutICUSanitization(\"es-419\").getString());\n\n  EXPECT_EQ(\"en-Latn-US\",\n            createFontLanguageWithoutICUSanitization(\"en-Latn-US\").getString());\n  EXPECT_EQ(\n      \"fil-Taga-PH\",\n      createFontLanguageWithoutICUSanitization(\"fil-Taga-PH\").getString());\n  EXPECT_EQ(\n      \"es-Latn-419\",\n      createFontLanguageWithoutICUSanitization(\"es-Latn-419\").getString());\n\n  // Possible minimum/maximum values.\n  EXPECT_EQ(\"aa\", createFontLanguageWithoutICUSanitization(\"aa\").getString());\n  EXPECT_EQ(\"zz\", createFontLanguageWithoutICUSanitization(\"zz\").getString());\n  EXPECT_EQ(\"aa-Aaaa\",\n            createFontLanguageWithoutICUSanitization(\"aa-Aaaa\").getString());\n  EXPECT_EQ(\"zz-Zzzz\",\n            createFontLanguageWithoutICUSanitization(\"zz-Zzzz\").getString());\n  EXPECT_EQ(\n      \"aaa-Aaaa-AA\",\n      createFontLanguageWithoutICUSanitization(\"aaa-Aaaa-AA\").getString());\n  EXPECT_EQ(\n      \"zzz-Zzzz-ZZ\",\n      createFontLanguageWithoutICUSanitization(\"zzz-Zzzz-ZZ\").getString());\n  EXPECT_EQ(\n      \"aaa-Aaaa-000\",\n      createFontLanguageWithoutICUSanitization(\"aaa-Aaaa-000\").getString());\n  EXPECT_EQ(\n      \"zzz-Zzzz-999\",\n      createFontLanguageWithoutICUSanitization(\"zzz-Zzzz-999\").getString());\n}\n\nTEST_F(FontLanguageTest, ScriptEqualTest) {\n  EXPECT_TRUE(createFontLanguage(\"en\").isEqualScript(createFontLanguage(\"en\")));\n  EXPECT_TRUE(\n      createFontLanguage(\"en-Latn\").isEqualScript(createFontLanguage(\"en\")));\n  EXPECT_TRUE(createFontLanguage(\"jp-Latn\").isEqualScript(\n      createFontLanguage(\"en-Latn\")));\n  EXPECT_TRUE(createFontLanguage(\"en-Jpan\").isEqualScript(\n      createFontLanguage(\"en-Jpan\")));\n\n  EXPECT_FALSE(createFontLanguage(\"en-Jpan\").isEqualScript(\n      createFontLanguage(\"en-Hira\")));\n  EXPECT_FALSE(createFontLanguage(\"en-Jpan\").isEqualScript(\n      createFontLanguage(\"en-Hani\")));\n}\n\nTEST_F(FontLanguageTest, ScriptMatchTest) {\n  const bool SUPPORTED = true;\n  const bool NOT_SUPPORTED = false;\n\n  struct TestCase {\n    const std::string baseScript;\n    const std::string requestedScript;\n    bool isSupported;\n  } testCases[] = {\n      // Same scripts\n      {\"en-Latn\", \"Latn\", SUPPORTED},\n      {\"ja-Jpan\", \"Jpan\", SUPPORTED},\n      {\"ja-Hira\", \"Hira\", SUPPORTED},\n      {\"ja-Kana\", \"Kana\", SUPPORTED},\n      {\"ja-Hrkt\", \"Hrkt\", SUPPORTED},\n      {\"zh-Hans\", \"Hans\", SUPPORTED},\n      {\"zh-Hant\", \"Hant\", SUPPORTED},\n      {\"zh-Hani\", \"Hani\", SUPPORTED},\n      {\"ko-Kore\", \"Kore\", SUPPORTED},\n      {\"ko-Hang\", \"Hang\", SUPPORTED},\n      {\"zh-Hanb\", \"Hanb\", SUPPORTED},\n\n      // Japanese supports Hiragana, Katakanara, etc.\n      {\"ja-Jpan\", \"Hira\", SUPPORTED},\n      {\"ja-Jpan\", \"Kana\", SUPPORTED},\n      {\"ja-Jpan\", \"Hrkt\", SUPPORTED},\n      {\"ja-Hrkt\", \"Hira\", SUPPORTED},\n      {\"ja-Hrkt\", \"Kana\", SUPPORTED},\n\n      // Chinese supports Han.\n      {\"zh-Hans\", \"Hani\", SUPPORTED},\n      {\"zh-Hant\", \"Hani\", SUPPORTED},\n      {\"zh-Hanb\", \"Hani\", SUPPORTED},\n\n      // Hanb supports Bopomofo.\n      {\"zh-Hanb\", \"Bopo\", SUPPORTED},\n\n      // Korean supports Hangul.\n      {\"ko-Kore\", \"Hang\", SUPPORTED},\n\n      // Different scripts\n      {\"ja-Jpan\", \"Latn\", NOT_SUPPORTED},\n      {\"en-Latn\", \"Jpan\", NOT_SUPPORTED},\n      {\"ja-Jpan\", \"Hant\", NOT_SUPPORTED},\n      {\"zh-Hant\", \"Jpan\", NOT_SUPPORTED},\n      {\"ja-Jpan\", \"Hans\", NOT_SUPPORTED},\n      {\"zh-Hans\", \"Jpan\", NOT_SUPPORTED},\n      {\"ja-Jpan\", \"Kore\", NOT_SUPPORTED},\n      {\"ko-Kore\", \"Jpan\", NOT_SUPPORTED},\n      {\"zh-Hans\", \"Hant\", NOT_SUPPORTED},\n      {\"zh-Hant\", \"Hans\", NOT_SUPPORTED},\n      {\"zh-Hans\", \"Kore\", NOT_SUPPORTED},\n      {\"ko-Kore\", \"Hans\", NOT_SUPPORTED},\n      {\"zh-Hant\", \"Kore\", NOT_SUPPORTED},\n      {\"ko-Kore\", \"Hant\", NOT_SUPPORTED},\n\n      // Hiragana doesn't support Japanese, etc.\n      {\"ja-Hira\", \"Jpan\", NOT_SUPPORTED},\n      {\"ja-Kana\", \"Jpan\", NOT_SUPPORTED},\n      {\"ja-Hrkt\", \"Jpan\", NOT_SUPPORTED},\n      {\"ja-Hani\", \"Jpan\", NOT_SUPPORTED},\n      {\"ja-Hira\", \"Hrkt\", NOT_SUPPORTED},\n      {\"ja-Kana\", \"Hrkt\", NOT_SUPPORTED},\n      {\"ja-Hani\", \"Hrkt\", NOT_SUPPORTED},\n      {\"ja-Hani\", \"Hira\", NOT_SUPPORTED},\n      {\"ja-Hani\", \"Kana\", NOT_SUPPORTED},\n\n      // Kanji doesn't support Chinese, etc.\n      {\"zh-Hani\", \"Hant\", NOT_SUPPORTED},\n      {\"zh-Hani\", \"Hans\", NOT_SUPPORTED},\n      {\"zh-Hani\", \"Hanb\", NOT_SUPPORTED},\n\n      // Hangul doesn't support Korean, etc.\n      {\"ko-Hang\", \"Kore\", NOT_SUPPORTED},\n      {\"ko-Hani\", \"Kore\", NOT_SUPPORTED},\n      {\"ko-Hani\", \"Hang\", NOT_SUPPORTED},\n      {\"ko-Hang\", \"Hani\", NOT_SUPPORTED},\n\n      // Han with botomofo doesn't support simplified Chinese, etc.\n      {\"zh-Hanb\", \"Hant\", NOT_SUPPORTED},\n      {\"zh-Hanb\", \"Hans\", NOT_SUPPORTED},\n      {\"zh-Hanb\", \"Jpan\", NOT_SUPPORTED},\n      {\"zh-Hanb\", \"Kore\", NOT_SUPPORTED},\n  };\n\n  for (auto testCase : testCases) {\n    hb_script_t script = hb_script_from_iso15924_tag(\n        HB_TAG(testCase.requestedScript[0], testCase.requestedScript[1],\n               testCase.requestedScript[2], testCase.requestedScript[3]));\n    if (testCase.isSupported) {\n      EXPECT_TRUE(\n          createFontLanguage(testCase.baseScript).supportsHbScript(script))\n          << testCase.baseScript << \" should support \"\n          << testCase.requestedScript;\n    } else {\n      EXPECT_FALSE(\n          createFontLanguage(testCase.baseScript).supportsHbScript(script))\n          << testCase.baseScript << \" shouldn't support \"\n          << testCase.requestedScript;\n    }\n  }\n}\n\nTEST_F(FontLanguagesTest, basicTests) {\n  FontLanguages emptyLangs;\n  EXPECT_EQ(0u, emptyLangs.size());\n\n  FontLanguage english = createFontLanguage(\"en\");\n  const FontLanguages& singletonLangs = createFontLanguages(\"en\");\n  EXPECT_EQ(1u, singletonLangs.size());\n  EXPECT_EQ(english, singletonLangs[0]);\n\n  FontLanguage french = createFontLanguage(\"fr\");\n  const FontLanguages& twoLangs = createFontLanguages(\"en,fr\");\n  EXPECT_EQ(2u, twoLangs.size());\n  EXPECT_EQ(english, twoLangs[0]);\n  EXPECT_EQ(french, twoLangs[1]);\n}\n\nTEST_F(FontLanguagesTest, unsupportedLanguageTests) {\n  const FontLanguages& oneUnsupported = createFontLanguages(\"abcd-example\");\n  EXPECT_TRUE(oneUnsupported.empty());\n\n  const FontLanguages& twoUnsupporteds =\n      createFontLanguages(\"abcd-example,abcd-example\");\n  EXPECT_TRUE(twoUnsupporteds.empty());\n\n  FontLanguage english = createFontLanguage(\"en\");\n  const FontLanguages& firstUnsupported =\n      createFontLanguages(\"abcd-example,en\");\n  EXPECT_EQ(1u, firstUnsupported.size());\n  EXPECT_EQ(english, firstUnsupported[0]);\n\n  const FontLanguages& lastUnsupported = createFontLanguages(\"en,abcd-example\");\n  EXPECT_EQ(1u, lastUnsupported.size());\n  EXPECT_EQ(english, lastUnsupported[0]);\n}\n\nTEST_F(FontLanguagesTest, repeatedLanguageTests) {\n  FontLanguage english = createFontLanguage(\"en\");\n  FontLanguage french = createFontLanguage(\"fr\");\n  FontLanguage canadianFrench = createFontLanguage(\"fr-CA\");\n  FontLanguage englishInLatn = createFontLanguage(\"en-Latn\");\n  ASSERT_TRUE(english == englishInLatn);\n\n  const FontLanguages& langs = createFontLanguages(\"en,en-Latn\");\n  EXPECT_EQ(1u, langs.size());\n  EXPECT_EQ(english, langs[0]);\n\n  const FontLanguages& fr = createFontLanguages(\"fr,fr-FR,fr-Latn-FR\");\n  EXPECT_EQ(1u, fr.size());\n  EXPECT_EQ(french, fr[0]);\n\n  // ICU appends FR to fr. The third language is dropped which is same as the\n  // first language.\n  const FontLanguages& fr2 = createFontLanguages(\"fr,fr-CA,fr-FR\");\n  EXPECT_EQ(2u, fr2.size());\n  EXPECT_EQ(french, fr2[0]);\n  EXPECT_EQ(canadianFrench, fr2[1]);\n\n  // The order should be kept.\n  const FontLanguages& langs2 = createFontLanguages(\"en,fr,en-Latn\");\n  EXPECT_EQ(2u, langs2.size());\n  EXPECT_EQ(english, langs2[0]);\n  EXPECT_EQ(french, langs2[1]);\n}\n\nTEST_F(FontLanguagesTest, identifierTest) {\n  EXPECT_EQ(createFontLanguage(\"en-Latn-US\"), createFontLanguage(\"en-Latn-US\"));\n  EXPECT_EQ(createFontLanguage(\"zh-Hans-CN\"), createFontLanguage(\"zh-Hans-CN\"));\n  EXPECT_EQ(createFontLanguage(\"en-Zsye-US\"), createFontLanguage(\"en-Zsye-US\"));\n\n  EXPECT_NE(createFontLanguage(\"en-Latn-US\"), createFontLanguage(\"en-Latn-GB\"));\n  EXPECT_NE(createFontLanguage(\"en-Latn-US\"), createFontLanguage(\"en-Zsye-US\"));\n  EXPECT_NE(createFontLanguage(\"es-Latn-US\"), createFontLanguage(\"en-Latn-US\"));\n  EXPECT_NE(createFontLanguage(\"zh-Hant-HK\"), createFontLanguage(\"zh-Hant-TW\"));\n}\n\nTEST_F(FontLanguagesTest, undEmojiTests) {\n  FontLanguage emoji = createFontLanguage(\"und-Zsye\");\n  EXPECT_EQ(FontLanguage::EMSTYLE_EMOJI, emoji.getEmojiStyle());\n\n  FontLanguage und = createFontLanguage(\"und\");\n  EXPECT_EQ(FontLanguage::EMSTYLE_EMPTY, und.getEmojiStyle());\n  EXPECT_FALSE(emoji == und);\n\n  FontLanguage undExample = createFontLanguage(\"und-example\");\n  EXPECT_EQ(FontLanguage::EMSTYLE_EMPTY, undExample.getEmojiStyle());\n  EXPECT_FALSE(emoji == undExample);\n}\n\nTEST_F(FontLanguagesTest, subtagEmojiTest) {\n  std::string subtagEmojiStrings[] = {\n      // Duplicate subtag case.\n      \"und-Latn-u-em-emoji-u-em-text\",\n\n      // Strings that contain language.\n      \"und-u-em-emoji\",\n      \"en-u-em-emoji\",\n\n      // Strings that contain the script.\n      \"und-Jpan-u-em-emoji\",\n      \"en-Latn-u-em-emoji\",\n      \"und-Zsym-u-em-emoji\",\n      \"und-Zsye-u-em-emoji\",\n      \"en-Zsym-u-em-emoji\",\n      \"en-Zsye-u-em-emoji\",\n\n      // Strings that contain the county.\n      \"und-US-u-em-emoji\",\n      \"en-US-u-em-emoji\",\n      \"es-419-u-em-emoji\",\n      \"und-Latn-US-u-em-emoji\",\n      \"en-Zsym-US-u-em-emoji\",\n      \"en-Zsye-US-u-em-emoji\",\n      \"es-Zsye-419-u-em-emoji\",\n  };\n\n  for (auto subtagEmojiString : subtagEmojiStrings) {\n    SCOPED_TRACE(\"Test for \\\"\" + subtagEmojiString + \"\\\"\");\n    FontLanguage subtagEmoji = createFontLanguage(subtagEmojiString);\n    EXPECT_EQ(FontLanguage::EMSTYLE_EMOJI, subtagEmoji.getEmojiStyle());\n  }\n}\n\nTEST_F(FontLanguagesTest, subtagTextTest) {\n  std::string subtagTextStrings[] = {\n      // Duplicate subtag case.\n      \"und-Latn-u-em-text-u-em-emoji\",\n\n      // Strings that contain language.\n      \"und-u-em-text\",\n      \"en-u-em-text\",\n\n      // Strings that contain the script.\n      \"und-Latn-u-em-text\",\n      \"en-Jpan-u-em-text\",\n      \"und-Zsym-u-em-text\",\n      \"und-Zsye-u-em-text\",\n      \"en-Zsym-u-em-text\",\n      \"en-Zsye-u-em-text\",\n\n      // Strings that contain the county.\n      \"und-US-u-em-text\",\n      \"en-US-u-em-text\",\n      \"es-419-u-em-text\",\n      \"und-Latn-US-u-em-text\",\n      \"en-Zsym-US-u-em-text\",\n      \"en-Zsye-US-u-em-text\",\n      \"es-Zsye-419-u-em-text\",\n  };\n\n  for (auto subtagTextString : subtagTextStrings) {\n    SCOPED_TRACE(\"Test for \\\"\" + subtagTextString + \"\\\"\");\n    FontLanguage subtagText = createFontLanguage(subtagTextString);\n    EXPECT_EQ(FontLanguage::EMSTYLE_TEXT, subtagText.getEmojiStyle());\n  }\n}\n\n// TODO: add more \"und\" language cases whose language and script are\n//       unexpectedly translated to en-Latn by ICU.\nTEST_F(FontLanguagesTest, subtagDefaultTest) {\n  std::string subtagDefaultStrings[] = {\n      // Duplicate subtag case.\n      \"en-Latn-u-em-default-u-em-emoji\",\n      \"en-Latn-u-em-default-u-em-text\",\n\n      // Strings that contain language.\n      \"und-u-em-default\",\n      \"en-u-em-default\",\n\n      // Strings that contain the script.\n      \"en-Latn-u-em-default\",\n      \"en-Zsym-u-em-default\",\n      \"en-Zsye-u-em-default\",\n\n      // Strings that contain the county.\n      \"en-US-u-em-default\",\n      \"en-Latn-US-u-em-default\",\n      \"es-Latn-419-u-em-default\",\n      \"en-Zsym-US-u-em-default\",\n      \"en-Zsye-US-u-em-default\",\n      \"es-Zsye-419-u-em-default\",\n  };\n\n  for (auto subtagDefaultString : subtagDefaultStrings) {\n    SCOPED_TRACE(\"Test for \\\"\" + subtagDefaultString + \"\\\"\");\n    FontLanguage subtagDefault = createFontLanguage(subtagDefaultString);\n    EXPECT_EQ(FontLanguage::EMSTYLE_DEFAULT, subtagDefault.getEmojiStyle());\n  }\n}\n\nTEST_F(FontLanguagesTest, subtagEmptyTest) {\n  std::string subtagEmptyStrings[] = {\n      \"und\",\n      \"jp\",\n      \"en-US\",\n      \"en-Latn\",\n      \"en-Latn-US\",\n      \"en-Latn-US-u-em\",\n      \"en-Latn-US-u-em-defaultemoji\",\n  };\n\n  for (auto subtagEmptyString : subtagEmptyStrings) {\n    SCOPED_TRACE(\"Test for \\\"\" + subtagEmptyString + \"\\\"\");\n    FontLanguage subtagEmpty = createFontLanguage(subtagEmptyString);\n    EXPECT_EQ(FontLanguage::EMSTYLE_EMPTY, subtagEmpty.getEmojiStyle());\n  }\n}\n\nTEST_F(FontLanguagesTest, registerLanguageListTest) {\n  EXPECT_EQ(0UL, FontStyle::registerLanguageList(\"\"));\n  EXPECT_NE(0UL, FontStyle::registerLanguageList(\"en\"));\n  EXPECT_NE(0UL, FontStyle::registerLanguageList(\"jp\"));\n  EXPECT_NE(0UL, FontStyle::registerLanguageList(\"en,zh-Hans\"));\n\n  EXPECT_EQ(FontStyle::registerLanguageList(\"en\"),\n            FontStyle::registerLanguageList(\"en\"));\n  EXPECT_NE(FontStyle::registerLanguageList(\"en\"),\n            FontStyle::registerLanguageList(\"jp\"));\n\n  EXPECT_EQ(FontStyle::registerLanguageList(\"en,zh-Hans\"),\n            FontStyle::registerLanguageList(\"en,zh-Hans\"));\n  EXPECT_NE(FontStyle::registerLanguageList(\"en,zh-Hans\"),\n            FontStyle::registerLanguageList(\"zh-Hans,en\"));\n  EXPECT_NE(FontStyle::registerLanguageList(\"en,zh-Hans\"),\n            FontStyle::registerLanguageList(\"jp\"));\n  EXPECT_NE(FontStyle::registerLanguageList(\"en,zh-Hans\"),\n            FontStyle::registerLanguageList(\"en\"));\n  EXPECT_NE(FontStyle::registerLanguageList(\"en,zh-Hans\"),\n            FontStyle::registerLanguageList(\"en,zh-Hant\"));\n}\n\n// The test font has following glyphs.\n// U+82A6\n// U+82A6 U+FE00 (VS1)\n// U+82A6 U+E0100 (VS17)\n// U+82A6 U+E0101 (VS18)\n// U+82A6 U+E0102 (VS19)\n// U+845B\n// U+845B U+FE00 (VS2)\n// U+845B U+E0101 (VS18)\n// U+845B U+E0102 (VS19)\n// U+845B U+E0103 (VS20)\n// U+537F\n// U+717D U+FE02 (VS3)\n// U+717D U+E0102 (VS19)\n// U+717D U+E0103 (VS20)\nconst char kVsTestFont[] = kTestFontDir \"VariationSelectorTest-Regular.ttf\";\n\nclass FontFamilyTest : public ICUTestBase {\n public:\n  virtual void SetUp() override {\n    ICUTestBase::SetUp();\n    if (access(kVsTestFont, R_OK) != 0) {\n      FAIL() << \"Unable to read \" << kVsTestFont << \". \"\n             << \"Please prepare the test data directory. \"\n             << \"For more details, please see how_to_run.txt.\";\n    }\n  }\n};\n\n// Asserts that the font family has glyphs for and only for specified codepoint\n// and variationSelector pairs.\nvoid expectVSGlyphs(FontFamily* family,\n                    uint32_t codepoint,\n                    const std::set<uint32_t>& vs) {\n  for (uint32_t i = 0xFE00; i <= 0xE01EF; ++i) {\n    // Move to variation selectors supplements after variation selectors.\n    if (i == 0xFF00) {\n      i = 0xE0100;\n    }\n    if (vs.find(i) == vs.end()) {\n      EXPECT_FALSE(family->hasGlyph(codepoint, i))\n          << \"Glyph for U+\" << std::hex << codepoint << \" U+\" << i;\n    } else {\n      EXPECT_TRUE(family->hasGlyph(codepoint, i))\n          << \"Glyph for U+\" << std::hex << codepoint << \" U+\" << i;\n    }\n  }\n}\n\nTEST_F(FontFamilyTest, hasVariationSelectorTest) {\n  std::shared_ptr<MinikinFont> minikinFont(new MinikinFontForTest(kVsTestFont));\n  std::shared_ptr<FontFamily> family(\n      new FontFamily(std::vector<Font>{Font(minikinFont, FontStyle())}));\n\n  std::scoped_lock _l(gMinikinLock);\n\n  const uint32_t kVS1 = 0xFE00;\n  const uint32_t kVS2 = 0xFE01;\n  const uint32_t kVS3 = 0xFE02;\n  const uint32_t kVS17 = 0xE0100;\n  const uint32_t kVS18 = 0xE0101;\n  const uint32_t kVS19 = 0xE0102;\n  const uint32_t kVS20 = 0xE0103;\n\n  const uint32_t kSupportedChar1 = 0x82A6;\n  EXPECT_TRUE(family->getCoverage().get(kSupportedChar1));\n  expectVSGlyphs(family.get(), kSupportedChar1,\n                 std::set<uint32_t>({kVS1, kVS17, kVS18, kVS19}));\n\n  const uint32_t kSupportedChar2 = 0x845B;\n  EXPECT_TRUE(family->getCoverage().get(kSupportedChar2));\n  expectVSGlyphs(family.get(), kSupportedChar2,\n                 std::set<uint32_t>({kVS2, kVS18, kVS19, kVS20}));\n\n  const uint32_t kNoVsSupportedChar = 0x537F;\n  EXPECT_TRUE(family->getCoverage().get(kNoVsSupportedChar));\n  expectVSGlyphs(family.get(), kNoVsSupportedChar, std::set<uint32_t>());\n\n  const uint32_t kVsOnlySupportedChar = 0x717D;\n  EXPECT_FALSE(family->getCoverage().get(kVsOnlySupportedChar));\n  expectVSGlyphs(family.get(), kVsOnlySupportedChar,\n                 std::set<uint32_t>({kVS3, kVS19, kVS20}));\n\n  const uint32_t kNotSupportedChar = 0x845C;\n  EXPECT_FALSE(family->getCoverage().get(kNotSupportedChar));\n  expectVSGlyphs(family.get(), kNotSupportedChar, std::set<uint32_t>());\n}\n\nTEST_F(FontFamilyTest, hasVSTableTest) {\n  struct TestCase {\n    const std::string fontPath;\n    bool hasVSTable;\n  } testCases[] = {\n      {kTestFontDir \"Ja.ttf\", true},     {kTestFontDir \"ZhHant.ttf\", true},\n      {kTestFontDir \"ZhHans.ttf\", true}, {kTestFontDir \"Italic.ttf\", false},\n      {kTestFontDir \"Bold.ttf\", false},  {kTestFontDir \"BoldItalic.ttf\", false},\n  };\n\n  for (auto testCase : testCases) {\n    SCOPED_TRACE(testCase.hasVSTable\n                     ? \"Font \" + testCase.fontPath +\n                           \" should have a variation sequence table.\"\n                     : \"Font \" + testCase.fontPath +\n                           \" shouldn't have a variation sequence table.\");\n\n    std::shared_ptr<MinikinFont> minikinFont(\n        new MinikinFontForTest(testCase.fontPath));\n    std::shared_ptr<FontFamily> family(\n        new FontFamily(std::vector<Font>{Font(minikinFont, FontStyle())}));\n    std::scoped_lock _l(gMinikinLock);\n    EXPECT_EQ(testCase.hasVSTable, family->hasVSTable());\n  }\n}\n\nTEST_F(FontFamilyTest, createFamilyWithVariationTest) {\n  // This font has 'wdth' and 'wght' axes.\n  const char kMultiAxisFont[] = kTestFontDir \"/MultiAxis.ttf\";\n  const char kNoAxisFont[] = kTestFontDir \"/Regular.ttf\";\n\n  std::shared_ptr<FontFamily> multiAxisFamily = makeFamily(kMultiAxisFont);\n  std::shared_ptr<FontFamily> noAxisFamily = makeFamily(kNoAxisFont);\n\n  {\n    // Do not ceate new instance if none of variations are specified.\n    EXPECT_EQ(nullptr, multiAxisFamily->createFamilyWithVariation(\n                           std::vector<FontVariation>()));\n    EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(\n                           std::vector<FontVariation>()));\n  }\n  {\n    // New instance should be used for supported variation.\n    std::vector<FontVariation> variations = {\n        {MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f}};\n    std::shared_ptr<FontFamily> newFamily(\n        multiAxisFamily->createFamilyWithVariation(variations));\n    EXPECT_NE(nullptr, newFamily.get());\n    EXPECT_NE(multiAxisFamily.get(), newFamily.get());\n    EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));\n  }\n  {\n    // New instance should be used for supported variation. (multiple variations\n    // case)\n    std::vector<FontVariation> variations = {\n        {MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},\n        {MinikinFont::MakeTag('w', 'g', 'h', 't'), 1.0f}};\n    std::shared_ptr<FontFamily> newFamily(\n        multiAxisFamily->createFamilyWithVariation(variations));\n    EXPECT_NE(nullptr, newFamily.get());\n    EXPECT_NE(multiAxisFamily.get(), newFamily.get());\n    EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));\n  }\n  {\n    // Do not ceate new instance if none of variations are supported.\n    std::vector<FontVariation> variations = {\n        {MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};\n    EXPECT_EQ(nullptr, multiAxisFamily->createFamilyWithVariation(variations));\n    EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));\n  }\n  {\n    // At least one axis is supported, should create new instance.\n    std::vector<FontVariation> variations = {\n        {MinikinFont::MakeTag('w', 'd', 't', 'h'), 1.0f},\n        {MinikinFont::MakeTag('Z', 'Z', 'Z', 'Z'), 1.0f}};\n    std::shared_ptr<FontFamily> newFamily(\n        multiAxisFamily->createFamilyWithVariation(variations));\n    EXPECT_NE(nullptr, newFamily.get());\n    EXPECT_NE(multiAxisFamily.get(), newFamily.get());\n    EXPECT_EQ(nullptr, noAxisFamily->createFamilyWithVariation(variations));\n  }\n}\n\nTEST_F(FontFamilyTest, coverageTableSelectionTest) {\n  // This font supports U+0061. The cmap subtable is format 4 and its platform\n  // ID is 0 and encoding ID is 1.\n  const char kUnicodeEncoding1Font[] = kTestFontDir \"UnicodeBMPOnly.ttf\";\n\n  // This font supports U+0061. The cmap subtable is format 4 and its platform\n  // ID is 0 and encoding ID is 3.\n  const char kUnicodeEncoding3Font[] = kTestFontDir \"UnicodeBMPOnly2.ttf\";\n\n  // This font has both cmap format 4 subtable which platform ID is 0 and\n  // encoding ID is 1 and cmap format 14 subtable which platform ID is 0 and\n  // encoding ID is 10. U+0061 is listed in both subtable but U+1F926 is only\n  // listed in latter.\n  const char kUnicodeEncoding4Font[] = kTestFontDir \"UnicodeUCS4.ttf\";\n\n  std::shared_ptr<FontFamily> unicodeEnc1Font =\n      makeFamily(kUnicodeEncoding1Font);\n  std::shared_ptr<FontFamily> unicodeEnc3Font =\n      makeFamily(kUnicodeEncoding3Font);\n  std::shared_ptr<FontFamily> unicodeEnc4Font =\n      makeFamily(kUnicodeEncoding4Font);\n\n  std::scoped_lock _l(gMinikinLock);\n\n  EXPECT_TRUE(unicodeEnc1Font->hasGlyph(0x0061, 0));\n  EXPECT_TRUE(unicodeEnc3Font->hasGlyph(0x0061, 0));\n  EXPECT_TRUE(unicodeEnc4Font->hasGlyph(0x0061, 0));\n\n  EXPECT_TRUE(unicodeEnc4Font->hasGlyph(0x1F926, 0));\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/FontLanguageListCacheTest.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\n#include <minikin/FontFamily.h>\n\n#include \"ICUTestBase.h\"\n#include \"minikin/FontLanguageListCache.h\"\n#include \"minikin/MinikinInternal.h\"\n\nnamespace minikin {\n\ntypedef ICUTestBase FontLanguageListCacheTest;\n\nTEST_F(FontLanguageListCacheTest, getId) {\n  EXPECT_NE(0UL, FontStyle::registerLanguageList(\"en\"));\n  EXPECT_NE(0UL, FontStyle::registerLanguageList(\"jp\"));\n  EXPECT_NE(0UL, FontStyle::registerLanguageList(\"en,zh-Hans\"));\n\n  std::scoped_lock _l(gMinikinLock);\n  EXPECT_EQ(0UL, FontLanguageListCache::getId(\"\"));\n\n  EXPECT_EQ(FontLanguageListCache::getId(\"en\"),\n            FontLanguageListCache::getId(\"en\"));\n  EXPECT_NE(FontLanguageListCache::getId(\"en\"),\n            FontLanguageListCache::getId(\"jp\"));\n\n  EXPECT_EQ(FontLanguageListCache::getId(\"en,zh-Hans\"),\n            FontLanguageListCache::getId(\"en,zh-Hans\"));\n  EXPECT_NE(FontLanguageListCache::getId(\"en,zh-Hans\"),\n            FontLanguageListCache::getId(\"zh-Hans,en\"));\n  EXPECT_NE(FontLanguageListCache::getId(\"en,zh-Hans\"),\n            FontLanguageListCache::getId(\"jp\"));\n  EXPECT_NE(FontLanguageListCache::getId(\"en,zh-Hans\"),\n            FontLanguageListCache::getId(\"en\"));\n  EXPECT_NE(FontLanguageListCache::getId(\"en,zh-Hans\"),\n            FontLanguageListCache::getId(\"en,zh-Hant\"));\n}\n\nTEST_F(FontLanguageListCacheTest, getById) {\n  std::scoped_lock _l(gMinikinLock);\n  uint32_t enLangId = FontLanguageListCache::getId(\"en\");\n  uint32_t jpLangId = FontLanguageListCache::getId(\"jp\");\n  FontLanguage english = FontLanguageListCache::getById(enLangId)[0];\n  FontLanguage japanese = FontLanguageListCache::getById(jpLangId)[0];\n\n  const FontLanguages& defLangs = FontLanguageListCache::getById(0);\n  EXPECT_TRUE(defLangs.empty());\n\n  const FontLanguages& langs =\n      FontLanguageListCache::getById(FontLanguageListCache::getId(\"en\"));\n  ASSERT_EQ(1UL, langs.size());\n  EXPECT_EQ(english, langs[0]);\n\n  const FontLanguages& langs2 =\n      FontLanguageListCache::getById(FontLanguageListCache::getId(\"en,jp\"));\n  ASSERT_EQ(2UL, langs2.size());\n  EXPECT_EQ(english, langs2[0]);\n  EXPECT_EQ(japanese, langs2[1]);\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/FontTestUtils.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include <libxml/tree.h>\n#include <unistd.h>\n\n#include <log/log.h>\n\n#include <minikin/FontCollection.h>\n#include <minikin/FontFamily.h>\n#include \"MinikinFontForTest.h\"\n#include \"minikin/FontLanguage.h\"\n\nnamespace minikin {\n\nstd::vector<std::shared_ptr<FontFamily>> getFontFamilies(const char* fontDir,\n                                                         const char* fontXml) {\n  xmlDoc* doc = xmlReadFile(fontXml, NULL, 0);\n  xmlNode* familySet = xmlDocGetRootElement(doc);\n\n  std::vector<std::shared_ptr<FontFamily>> families;\n  for (xmlNode* familyNode = familySet->children; familyNode;\n       familyNode = familyNode->next) {\n    if (xmlStrcmp(familyNode->name, (const xmlChar*)\"family\") != 0) {\n      continue;\n    }\n\n    xmlChar* variantXmlch = xmlGetProp(familyNode, (const xmlChar*)\"variant\");\n    int variant = VARIANT_DEFAULT;\n    if (variantXmlch) {\n      if (xmlStrcmp(variantXmlch, (const xmlChar*)\"elegant\") == 0) {\n        variant = VARIANT_ELEGANT;\n      } else if (xmlStrcmp(variantXmlch, (const xmlChar*)\"compact\") == 0) {\n        variant = VARIANT_COMPACT;\n      }\n    }\n\n    std::vector<Font> fonts;\n    for (xmlNode* fontNode = familyNode->children; fontNode;\n         fontNode = fontNode->next) {\n      if (xmlStrcmp(fontNode->name, (const xmlChar*)\"font\") != 0) {\n        continue;\n      }\n\n      int weight =\n          atoi((const char*)(xmlGetProp(fontNode, (const xmlChar*)\"weight\"))) /\n          100;\n      bool italic = xmlStrcmp(xmlGetProp(fontNode, (const xmlChar*)\"style\"),\n                              (const xmlChar*)\"italic\") == 0;\n      xmlChar* index = xmlGetProp(familyNode, (const xmlChar*)\"index\");\n\n      xmlChar* fontFileName =\n          xmlNodeListGetString(doc, fontNode->xmlChildrenNode, 1);\n      std::string fontPath = fontDir + std::string((const char*)fontFileName);\n      xmlFree(fontFileName);\n\n      if (access(fontPath.c_str(), R_OK) != 0) {\n        ALOGW(\"%s is not found.\", fontPath.c_str());\n        continue;\n      }\n\n      if (index == nullptr) {\n        std::shared_ptr<MinikinFont> minikinFont =\n            std::make_shared<MinikinFontForTest>(fontPath);\n        fonts.push_back(Font(minikinFont, FontStyle(weight, italic)));\n      } else {\n        std::shared_ptr<MinikinFont> minikinFont =\n            std::make_shared<MinikinFontForTest>(fontPath,\n                                                 atoi((const char*)index));\n        fonts.push_back(Font(minikinFont, FontStyle(weight, italic)));\n      }\n    }\n\n    xmlChar* lang = xmlGetProp(familyNode, (const xmlChar*)\"lang\");\n    std::shared_ptr<FontFamily> family;\n    if (lang == nullptr) {\n      family = std::make_shared<FontFamily>(variant, std::move(fonts));\n    } else {\n      uint32_t langId = FontStyle::registerLanguageList(\n          std::string((const char*)lang, xmlStrlen(lang)));\n      family = std::make_shared<FontFamily>(langId, variant, std::move(fonts));\n    }\n    families.push_back(family);\n  }\n  xmlFreeDoc(doc);\n  return families;\n}\nstd::shared_ptr<FontCollection> getFontCollection(const char* fontDir,\n                                                  const char* fontXml) {\n  return std::make_shared<FontCollection>(getFontFamilies(fontDir, fontXml));\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/FontTestUtils.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_TESTS_FONTTESTUTILS_H_\n#define CLAY_THIRD_PARTY_TXT_TESTS_FONTTESTUTILS_H_\n\n#include <minikin/FontCollection.h>\n#include <vector>\n\n#include <memory>\n\nnamespace minikin {\n\n/**\n * Returns list of FontFamily from installed fonts.\n *\n * This function reads an XML file and makes font families.\n *\n * Caller must unref the returned pointer.\n */\nstd::vector<std::shared_ptr<FontFamily>> getFontFamilies(const char* fontDir,\n                                                         const char* fontXml);\n\n/**\n * Returns FontCollection from installed fonts.\n *\n * This function reads an XML file and makes font families and collections of\n * them. MinikinFontForTest is used for FontFamily creation.\n *\n * Caller must unref the returned pointer.\n */\nstd::shared_ptr<FontCollection> getFontCollection(const char* fontDir,\n                                                  const char* fontXml);\n\n}  // namespace minikin\n#endif  // CLAY_THIRD_PARTY_TXT_TESTS_FONTTESTUTILS_H_\n"
  },
  {
    "path": "clay/third_party/txt/tests/GraphemeBreakTests.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include <minikin/GraphemeBreak.h>\n#include \"UnicodeUtils.h\"\n\nnamespace minikin {\n\nbool IsBreak(const char* src) {\n  const size_t BUF_SIZE = 256;\n  uint16_t buf[BUF_SIZE];\n  size_t offset;\n  size_t size;\n  ParseUnicode(buf, BUF_SIZE, src, &size, &offset);\n  return GraphemeBreak::isGraphemeBreak(nullptr, buf, 0, size, offset);\n}\n\nbool IsBreakWithAdvances(const float* advances, const char* src) {\n  const size_t BUF_SIZE = 256;\n  uint16_t buf[BUF_SIZE];\n  size_t offset;\n  size_t size;\n  ParseUnicode(buf, BUF_SIZE, src, &size, &offset);\n  return GraphemeBreak::isGraphemeBreak(advances, buf, 0, size, offset);\n}\n\nTEST(GraphemeBreak, utf16) {\n  EXPECT_FALSE(IsBreak(\"U+D83C | U+DC31\"));  // emoji, U+1F431\n\n  // tests for invalid UTF-16\n  EXPECT_TRUE(IsBreak(\"U+D800 | U+D800\"));  // two leading surrogates\n  EXPECT_TRUE(IsBreak(\"U+DC00 | U+DC00\"));  // two trailing surrogates\n  EXPECT_TRUE(IsBreak(\"'a' | U+D800\"));     // lonely leading surrogate\n  EXPECT_TRUE(IsBreak(\"U+DC00 | 'a'\"));     // lonely trailing surrogate\n  EXPECT_TRUE(\n      IsBreak(\"U+D800 | 'a'\"));  // leading surrogate followed by non-surrogate\n  EXPECT_TRUE(\n      IsBreak(\"'a' | U+DC00\"));  // non-surrogate followed by trailing surrogate\n}\n\nTEST(GraphemeBreak, rules) {\n  // Rule GB1, sot ÷; Rule GB2, ÷ eot\n  EXPECT_TRUE(IsBreak(\"| 'a'\"));\n  EXPECT_TRUE(IsBreak(\"'a' |\"));\n\n  // Rule GB3, CR x LF\n  EXPECT_FALSE(IsBreak(\"U+000D | U+000A\"));  // CR x LF\n\n  // Rule GB4, (Control | CR | LF) ÷\n  EXPECT_TRUE(IsBreak(\"'a' | U+2028\"));  // Line separator\n  EXPECT_TRUE(IsBreak(\"'a' | U+000D\"));  // LF\n  EXPECT_TRUE(IsBreak(\"'a' | U+000A\"));  // CR\n\n  // Rule GB5, ÷ (Control | CR | LF)\n  EXPECT_TRUE(IsBreak(\"U+2028 | 'a'\"));  // Line separator\n  EXPECT_TRUE(IsBreak(\"U+000D | 'a'\"));  // LF\n  EXPECT_TRUE(IsBreak(\"U+000A | 'a'\"));  // CR\n\n  // Rule GB6, L x ( L | V | LV | LVT )\n  EXPECT_FALSE(IsBreak(\"U+1100 | U+1100\"));  // L x L\n  EXPECT_FALSE(IsBreak(\"U+1100 | U+1161\"));  // L x V\n  EXPECT_FALSE(IsBreak(\"U+1100 | U+AC00\"));  // L x LV\n  EXPECT_FALSE(IsBreak(\"U+1100 | U+AC01\"));  // L x LVT\n\n  // Rule GB7, ( LV | V ) x ( V | T )\n  EXPECT_FALSE(IsBreak(\"U+AC00 | U+1161\"));  // LV x V\n  EXPECT_FALSE(IsBreak(\"U+1161 | U+1161\"));  // V x V\n  EXPECT_FALSE(IsBreak(\"U+AC00 | U+11A8\"));  // LV x T\n  EXPECT_FALSE(IsBreak(\"U+1161 | U+11A8\"));  // V x T\n\n  // Rule GB8, ( LVT | T ) x T\n  EXPECT_FALSE(IsBreak(\"U+AC01 | U+11A8\"));  // LVT x T\n  EXPECT_FALSE(IsBreak(\"U+11A8 | U+11A8\"));  // T x T\n\n  // Other hangul pairs not counted above _are_ breaks (GB10)\n  EXPECT_TRUE(IsBreak(\"U+AC00 | U+1100\"));  // LV x L\n  EXPECT_TRUE(IsBreak(\"U+AC01 | U+1100\"));  // LVT x L\n  EXPECT_TRUE(IsBreak(\"U+11A8 | U+1100\"));  // T x L\n  EXPECT_TRUE(IsBreak(\"U+11A8 | U+AC00\"));  // T x LV\n  EXPECT_TRUE(IsBreak(\"U+11A8 | U+AC01\"));  // T x LVT\n\n  // Rule GB12 and Rule GB13, Regional_Indicator x Regional_Indicator\n  EXPECT_FALSE(IsBreak(\"U+1F1FA | U+1F1F8\"));\n  EXPECT_TRUE(IsBreak(\n      \"U+1F1FA U+1F1F8 | U+1F1FA U+1F1F8\"));  // Regional indicator pair (flag)\n  EXPECT_FALSE(IsBreak(\n      \"U+1F1FA | U+1F1F8 U+1F1FA U+1F1F8\"));  // Regional indicator pair (flag)\n  EXPECT_FALSE(IsBreak(\n      \"U+1F1FA U+1F1F8 U+1F1FA | U+1F1F8\"));  // Regional indicator pair (flag)\n\n  EXPECT_TRUE(\n      IsBreak(\"U+1F1FA U+1F1F8 | U+1F1FA\"));  // Regional indicator pair (flag)\n  EXPECT_FALSE(\n      IsBreak(\"U+1F1FA | U+1F1F8 U+1F1FA\"));  // Regional indicator pair (flag)\n  // Same case as the two above, knowing that the first two characters ligate,\n  // which is what would typically happen.\n  const float firstPairLigated[] = {1.0, 0.0, 0.0, 0.0,\n                                    1.0, 0.0};  // Two entries per codepoint\n  EXPECT_TRUE(\n      IsBreakWithAdvances(firstPairLigated, \"U+1F1FA U+1F1F8 | U+1F1FA\"));\n  EXPECT_FALSE(\n      IsBreakWithAdvances(firstPairLigated, \"U+1F1FA | U+1F1F8 U+1F1FA\"));\n  // Repeat the tests, But now the font doesn't have a ligature for the first\n  // two characters, while it does have a ligature for the last two. This could\n  // happen for fonts that do not support some (potentially encoded later than\n  // they were developed) flags.\n  const float secondPairLigated[] = {1.0, 0.0, 1.0, 0.0, 0.0, 0.0};\n  EXPECT_FALSE(\n      IsBreakWithAdvances(secondPairLigated, \"U+1F1FA U+1F1F8 | U+1F1FA\"));\n  EXPECT_TRUE(\n      IsBreakWithAdvances(secondPairLigated, \"U+1F1FA | U+1F1F8 U+1F1FA\"));\n\n  EXPECT_TRUE(IsBreak(\n      \"'a' U+1F1FA U+1F1F8 | U+1F1FA\"));  // Regional indicator pair (flag)\n  EXPECT_FALSE(IsBreak(\n      \"'a' U+1F1FA | U+1F1F8 U+1F1FA\"));  // Regional indicator pair (flag)\n\n  EXPECT_TRUE(IsBreak(\"'a' U+1F1FA U+1F1F8 | U+1F1FA U+1F1F8\"));  // Regional\n                                                                  // indicator\n                                                                  // pair (flag)\n  EXPECT_FALSE(IsBreak(\"'a' U+1F1FA | U+1F1F8 U+1F1FA U+1F1F8\"));  // Regional\n                                                                   // indicator\n                                                                   // pair\n                                                                   // (flag)\n  EXPECT_FALSE(IsBreak(\"'a' U+1F1FA U+1F1F8 U+1F1FA | U+1F1F8\"));  // Regional\n                                                                   // indicator\n                                                                   // pair\n                                                                   // (flag)\n\n  // Rule GB9, x (Extend | ZWJ)\n  EXPECT_FALSE(IsBreak(\"'a' | U+0301\"));  // combining accent\n  // TODO(jsimmons): re-enable this test when ICU has been updated in all\n  // Flutter platforms.\n  // EXPECT_FALSE(IsBreak(\"'a' | U+200D\"));  // ZWJ\n  // Rule GB9a, x SpacingMark\n  EXPECT_FALSE(IsBreak(\"U+0915 | U+093E\"));  // KA, AA (spacing mark)\n  // Rule GB9b, Prepend x\n  // see tailoring test for prepend, as current ICU doesn't have any characters\n  // in the class\n\n  // Rule GB999, Any ÷ Any\n  EXPECT_TRUE(IsBreak(\"'a' | 'b'\"));\n  EXPECT_TRUE(IsBreak(\"'f' | 'i'\"));        // probable ligature\n  EXPECT_TRUE(IsBreak(\"U+0644 | U+0627\"));  // probable ligature, lam + alef\n  EXPECT_TRUE(IsBreak(\"U+4E00 | U+4E00\"));  // CJK ideographs\n  EXPECT_TRUE(\n      IsBreak(\"'a' | U+1F1FA U+1F1F8\"));  // Regional indicator pair (flag)\n  EXPECT_TRUE(\n      IsBreak(\"U+1F1FA U+1F1F8 | 'a'\"));  // Regional indicator pair (flag)\n\n  // Extended rule for emoji tag sequence.\n  EXPECT_TRUE(IsBreak(\"'a' | U+1F3F4 'a'\"));\n  EXPECT_TRUE(IsBreak(\"'a' U+1F3F4 | 'a'\"));\n\n  // Immediate tag_term after tag_base.\n  EXPECT_TRUE(IsBreak(\"'a' | U+1F3F4 U+E007F 'a'\"));\n  EXPECT_FALSE(IsBreak(\"U+1F3F4 | U+E007F\"));\n  EXPECT_TRUE(IsBreak(\"'a' U+1F3F4 U+E007F | 'a'\"));\n\n  // Flag sequence\n  // U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F is emoji tag\n  // sequence for the flag of Scotland. U+1F3F4 is WAVING BLACK FLAG. This can\n  // be a tag_base character. U+E0067 is TAG LATIN SMALL LETTER G. This can be a\n  // part of tag_spec. U+E0062 is TAG LATIN SMALL LETTER B. This can be a part\n  // of tag_spec. U+E0073 is TAG LATIN SMALL LETTER S. This can be a part of\n  // tag_spec. U+E0063 is TAG LATIN SMALL LETTER C. This can be a part of\n  // tag_spec. U+E0074 is TAG LATIN SMALL LETTER T. This can be a part of\n  // tag_spec. U+E007F is CANCEL TAG. This is a tag_term character.\n  EXPECT_TRUE(\n      IsBreak(\"'a' | U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F3F4 | U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F3F4 U+E0067 | U+E0062 U+E0073 U+E0063 U+E0074 U+E007F\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F3F4 U+E0067 U+E0062 | U+E0073 U+E0063 U+E0074 U+E007F\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F3F4 U+E0067 U+E0062 U+E0073 | U+E0063 U+E0074 U+E007F\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 | U+E0074 U+E007F\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 | U+E007F\"));\n  EXPECT_TRUE(\n      IsBreak(\"U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F | 'a'\"));\n}\n\nTEST(GraphemeBreak, DISABLED_tailoring) {\n  // control characters that we interpret as \"extend\"\n  EXPECT_FALSE(IsBreak(\"'a' | U+00AD\"));   // soft hyphen\n  EXPECT_FALSE(IsBreak(\"'a' | U+200B\"));   // zwsp\n  EXPECT_FALSE(IsBreak(\"'a' | U+200E\"));   // lrm\n  EXPECT_FALSE(IsBreak(\"'a' | U+202A\"));   // lre\n  EXPECT_FALSE(IsBreak(\"'a' | U+E0041\"));  // tag character\n\n  // UTC-approved characters for the Prepend class\n  EXPECT_FALSE(\n      IsBreak(\"U+06DD | U+0661\"));  // arabic subtending mark + digit one\n\n  EXPECT_TRUE(IsBreak(\"U+0E01 | U+0E33\"));  // Thai sara am\n\n  // virama is not a grapheme break, but \"pure killer\" is\n  EXPECT_FALSE(IsBreak(\"U+0915 | U+094D U+0915\"));  // Devanagari ka+virama+ka\n  EXPECT_FALSE(IsBreak(\"U+0915 U+094D | U+0915\"));  // Devanagari ka+virama+ka\n  EXPECT_FALSE(\n      IsBreak(\"U+0E01 | U+0E3A U+0E01\"));          // thai phinthu = pure killer\n  EXPECT_TRUE(IsBreak(\"U+0E01 U+0E3A | U+0E01\"));  // thai phinthu = pure killer\n\n  // Repetition of above tests, but with a given advances array that implies\n  // everything became just one cluster.\n  const float conjoined[] = {1.0, 0.0, 0.0};\n  EXPECT_FALSE(IsBreakWithAdvances(\n      conjoined,\n      \"U+0915 | U+094D U+0915\"));  // Devanagari ka+virama+ka\n  EXPECT_FALSE(IsBreakWithAdvances(\n      conjoined,\n      \"U+0915 U+094D | U+0915\"));  // Devanagari ka+virama+ka\n  EXPECT_FALSE(IsBreakWithAdvances(\n      conjoined,\n      \"U+0E01 | U+0E3A U+0E01\"));  // thai phinthu = pure killer\n  EXPECT_TRUE(IsBreakWithAdvances(\n      conjoined,\n      \"U+0E01 U+0E3A | U+0E01\"));  // thai phinthu = pure killer\n\n  // Repetition of above tests, but with a given advances array that the virama\n  // did not form a cluster with the following consonant. The difference is that\n  // there is now a grapheme break after the virama in ka+virama+ka.\n  const float separate[] = {1.0, 0.0, 1.0};\n  EXPECT_FALSE(IsBreakWithAdvances(\n      separate,\n      \"U+0915 | U+094D U+0915\"));  // Devanagari ka+virama+ka\n  EXPECT_TRUE(IsBreakWithAdvances(\n      separate,\n      \"U+0915 U+094D | U+0915\"));  // Devanagari ka+virama+ka\n  EXPECT_FALSE(IsBreakWithAdvances(\n      separate,\n      \"U+0E01 | U+0E3A U+0E01\"));  // thai phinthu = pure killer\n  EXPECT_TRUE(IsBreakWithAdvances(\n      separate,\n      \"U+0E01 U+0E3A | U+0E01\"));  // thai phinthu = pure killer\n\n  // suppress grapheme breaks in zwj emoji sequences\n  EXPECT_FALSE(\n      IsBreak(\"U+1F469 U+200D | U+2764 U+FE0F U+200D U+1F48B U+200D U+1F468\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F469 U+200D U+2764 U+FE0F U+200D | U+1F48B U+200D U+1F468\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F469 U+200D U+2764 U+FE0F U+200D U+1F48B U+200D | U+1F468\"));\n  EXPECT_FALSE(IsBreak(\"U+1F468 U+200D | U+1F469 U+200D U+1F466\"));\n  EXPECT_FALSE(IsBreak(\"U+1F468 U+200D U+1F469 U+200D | U+1F466\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F469 U+200D | U+1F469 U+200D U+1F467 U+200D U+1F466\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F469 U+200D U+1F469 U+200D | U+1F467 U+200D U+1F466\"));\n  EXPECT_FALSE(\n      IsBreak(\"U+1F469 U+200D U+1F469 U+200D U+1F467 U+200D | U+1F466\"));\n  EXPECT_FALSE(IsBreak(\"U+1F441 U+200D | U+1F5E8\"));\n\n  // Do not break before and after zwj with all kind of emoji characters.\n  EXPECT_FALSE(IsBreak(\"U+1F431 | U+200D U+1F464\"));\n  EXPECT_FALSE(IsBreak(\"U+1F431 U+200D | U+1F464\"));\n\n  // ARABIC LETTER BEH + ZWJ + heart, not a zwj emoji sequence, so we preserve\n  // the break\n  EXPECT_TRUE(IsBreak(\"U+0628 U+200D | U+2764\"));\n}\n\nTEST(GraphemeBreak, DISABLED_emojiModifiers) {\n  EXPECT_FALSE(\n      IsBreak(\"U+261D | U+1F3FB\"));  // white up pointing index + modifier\n  EXPECT_FALSE(IsBreak(\"U+270C | U+1F3FB\"));   // victory hand + modifier\n  EXPECT_FALSE(IsBreak(\"U+1F466 | U+1F3FB\"));  // boy + modifier\n  EXPECT_FALSE(IsBreak(\"U+1F466 | U+1F3FC\"));  // boy + modifier\n  EXPECT_FALSE(IsBreak(\"U+1F466 | U+1F3FD\"));  // boy + modifier\n  EXPECT_FALSE(IsBreak(\"U+1F466 | U+1F3FE\"));  // boy + modifier\n  EXPECT_FALSE(IsBreak(\"U+1F466 | U+1F3FF\"));  // boy + modifier\n  EXPECT_FALSE(IsBreak(\"U+1F918 | U+1F3FF\"));  // sign of the horns + modifier\n  EXPECT_FALSE(IsBreak(\"U+1F933 | U+1F3FF\"));  // selfie (Unicode 9) + modifier\n  // Repetition of the tests above, with the knowledge that they are ligated.\n  const float ligated1_2[] = {1.0, 0.0, 0.0};\n  const float ligated2_2[] = {1.0, 0.0, 0.0, 0.0};\n  EXPECT_FALSE(IsBreakWithAdvances(ligated1_2, \"U+261D | U+1F3FB\"));\n  EXPECT_FALSE(IsBreakWithAdvances(ligated1_2, \"U+270C | U+1F3FB\"));\n  EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, \"U+1F466 | U+1F3FB\"));\n  EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, \"U+1F466 | U+1F3FC\"));\n  EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, \"U+1F466 | U+1F3FD\"));\n  EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, \"U+1F466 | U+1F3FE\"));\n  EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, \"U+1F466 | U+1F3FF\"));\n  EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, \"U+1F918 | U+1F3FF\"));\n  EXPECT_FALSE(IsBreakWithAdvances(ligated2_2, \"U+1F933 | U+1F3FF\"));\n  // Repetition of the tests above, with the knowledge that they are not\n  // ligated.\n  const float unligated1_2[] = {1.0, 1.0, 0.0};\n  const float unligated2_2[] = {1.0, 0.0, 1.0, 0.0};\n  EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, \"U+261D | U+1F3FB\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated1_2, \"U+270C | U+1F3FB\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, \"U+1F466 | U+1F3FB\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, \"U+1F466 | U+1F3FC\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, \"U+1F466 | U+1F3FD\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, \"U+1F466 | U+1F3FE\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, \"U+1F466 | U+1F3FF\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, \"U+1F918 | U+1F3FF\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated2_2, \"U+1F933 | U+1F3FF\"));\n\n  // adding extend characters between emoji base and modifier doesn't affect\n  // grapheme cluster\n  EXPECT_FALSE(IsBreak(\n      \"U+270C U+FE0E | U+1F3FB\"));  // victory hand + text style + modifier\n  EXPECT_FALSE(\n      IsBreak(\"U+270C U+FE0F | U+1F3FB\"));  // heart + emoji style + modifier\n  // Repetition of the two tests above, with the knowledge that they are\n  // ligated.\n  const float ligated1_1_2[] = {1.0, 0.0, 0.0, 0.0};\n  EXPECT_FALSE(IsBreakWithAdvances(ligated1_1_2, \"U+270C U+FE0E | U+1F3FB\"));\n  EXPECT_FALSE(IsBreakWithAdvances(ligated1_1_2, \"U+270C U+FE0F | U+1F3FB\"));\n  // Repetition of the first two tests, with the knowledge that they are not\n  // ligated.\n  const float unligated1_1_2[] = {1.0, 0.0, 1.0, 0.0};\n  EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, \"U+270C U+FE0E | U+1F3FB\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated1_1_2, \"U+270C U+FE0F | U+1F3FB\"));\n\n  // heart is not an emoji base\n  EXPECT_TRUE(IsBreak(\"U+2764 | U+1F3FB\"));  // heart + modifier\n  EXPECT_TRUE(\n      IsBreak(\"U+2764 U+FE0E | U+1F3FB\"));  // heart + emoji style + modifier\n  EXPECT_TRUE(\n      IsBreak(\"U+2764 U+FE0F | U+1F3FB\"));    // heart + emoji style + modifier\n  EXPECT_TRUE(IsBreak(\"U+1F3FB | U+1F3FB\"));  // modifier + modifier\n\n  // rat is not an emoji modifer\n  EXPECT_TRUE(IsBreak(\"U+1F466 | U+1F400\"));  // boy + rat\n}\n\nTEST(GraphemeBreak, DISABLED_genderBalancedEmoji) {\n  // U+1F469 is WOMAN, U+200D is ZWJ, U+1F4BC is BRIEFCASE.\n  EXPECT_FALSE(IsBreak(\"U+1F469 | U+200D U+1F4BC\"));\n  EXPECT_FALSE(IsBreak(\"U+1F469 U+200D | U+1F4BC\"));\n  // The above two cases, when the ligature is not supported in the font. We now\n  // expect a break between them.\n  const float unligated2_1_2[] = {1.0, 0.0, 0.0, 1.0, 0.0};\n  EXPECT_FALSE(IsBreakWithAdvances(unligated2_1_2, \"U+1F469 | U+200D U+1F4BC\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated2_1_2, \"U+1F469 U+200D | U+1F4BC\"));\n\n  // U+2695 has now emoji property, so should be part of ZWJ sequence.\n  EXPECT_FALSE(IsBreak(\"U+1F469 | U+200D U+2695\"));\n  EXPECT_FALSE(IsBreak(\"U+1F469 U+200D | U+2695\"));\n  // The above two cases, when the ligature is not supported in the font. We now\n  // expect a break between them.\n  const float unligated2_1_1[] = {1.0, 0.0, 0.0, 1.0};\n  EXPECT_FALSE(IsBreakWithAdvances(unligated2_1_1, \"U+1F469 | U+200D U+2695\"));\n  EXPECT_TRUE(IsBreakWithAdvances(unligated2_1_1, \"U+1F469 U+200D | U+2695\"));\n}\n\nTEST(GraphemeBreak, offsets) {\n  uint16_t string[] = {0x0041, 0x06DD, 0x0045, 0x0301, 0x0049, 0x0301};\n  EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 2));\n  EXPECT_FALSE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 3));\n  EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 4));\n  EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(nullptr, string, 2, 3, 5));\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/HbFontCacheTest.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"minikin/HbFontCache.h\"\n\n#include <gtest/gtest.h>\n#include <log/log.h>\n\n#include <memory>\n#include <mutex>\n\n#include <hb.h>\n\n#include <minikin/MinikinFont.h>\n#include \"MinikinFontForTest.h\"\n#include \"minikin/MinikinInternal.h\"\n\nnamespace minikin {\n\nclass HbFontCacheTest : public testing::Test {\n public:\n  virtual void TearDown() {\n    std::scoped_lock _l(gMinikinLock);\n    purgeHbFontCacheLocked();\n  }\n};\n\nTEST_F(HbFontCacheTest, getHbFontLockedTest) {\n  std::shared_ptr<MinikinFontForTest> fontA(\n      new MinikinFontForTest(kTestFontDir \"Regular.ttf\"));\n\n  std::shared_ptr<MinikinFontForTest> fontB(\n      new MinikinFontForTest(kTestFontDir \"Bold.ttf\"));\n\n  std::shared_ptr<MinikinFontForTest> fontC(\n      new MinikinFontForTest(kTestFontDir \"BoldItalic.ttf\"));\n\n  std::scoped_lock _l(gMinikinLock);\n  // Never return NULL.\n  EXPECT_NE(nullptr, getHbFontLocked(fontA.get()));\n  EXPECT_NE(nullptr, getHbFontLocked(fontB.get()));\n  EXPECT_NE(nullptr, getHbFontLocked(fontC.get()));\n\n  EXPECT_NE(nullptr, getHbFontLocked(nullptr));\n\n  // Must return same object if same font object is passed.\n  EXPECT_EQ(getHbFontLocked(fontA.get()), getHbFontLocked(fontA.get()));\n  EXPECT_EQ(getHbFontLocked(fontB.get()), getHbFontLocked(fontB.get()));\n  EXPECT_EQ(getHbFontLocked(fontC.get()), getHbFontLocked(fontC.get()));\n\n  // Different object must be returned if the passed minikinFont has different\n  // ID.\n  EXPECT_NE(getHbFontLocked(fontA.get()), getHbFontLocked(fontB.get()));\n  EXPECT_NE(getHbFontLocked(fontA.get()), getHbFontLocked(fontC.get()));\n}\n\nTEST_F(HbFontCacheTest, purgeCacheTest) {\n  std::shared_ptr<MinikinFontForTest> minikinFont(\n      new MinikinFontForTest(kTestFontDir \"Regular.ttf\"));\n\n  std::scoped_lock _l(gMinikinLock);\n  hb_font_t* font = getHbFontLocked(minikinFont.get());\n  ASSERT_NE(nullptr, font);\n\n  // Set user data to identify the font object.\n  hb_user_data_key_t key;\n  void* data = (void*)0xdeadbeef;\n  hb_font_set_user_data(font, &key, data, NULL, false);\n  ASSERT_EQ(data, hb_font_get_user_data(font, &key));\n\n  purgeHbFontCacheLocked();\n\n  // By checking user data, confirm that the object after purge is different\n  // from previously created one. Do not compare the returned pointer here since\n  // memory allocator may assign same region for new object.\n  font = getHbFontLocked(minikinFont.get());\n  EXPECT_EQ(nullptr, hb_font_get_user_data(font, &key));\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/HyphenatorTest.cpp",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\n#include <minikin/Hyphenator.h>\n#include \"FileUtils.h\"\n#include \"ICUTestBase.h\"\n\n#ifndef NELEM\n#define NELEM(x) ((sizeof(x) / sizeof((x)[0])))\n#endif\n\nnamespace minikin {\n\nconst char* usHyph = \"/system/usr/hyphen-data/hyph-en-us.hyb\";\nconst char* malayalamHyph = \"/system/usr/hyphen-data/hyph-ml.hyb\";\n\ntypedef ICUTestBase HyphenatorTest;\n\nconst icu::Locale catalanLocale(\"ca\", \"ES\", nullptr, nullptr);\nconst icu::Locale polishLocale(\"pl\", \"PL\", nullptr, nullptr);\nconst icu::Locale& usLocale = icu::Locale::getUS();\n\nconst uint16_t HYPHEN_MINUS = 0x002D;\nconst uint16_t SOFT_HYPHEN = 0x00AD;\nconst uint16_t MIDDLE_DOT = 0x00B7;\nconst uint16_t GREEK_LOWER_ALPHA = 0x03B1;\nconst uint16_t ARMENIAN_AYB = 0x0531;\nconst uint16_t HEBREW_ALEF = 0x05D0;\nconst uint16_t ARABIC_ALEF = 0x0627;\nconst uint16_t ARABIC_BEH = 0x0628;\nconst uint16_t ARABIC_ZWARAKAY = 0x0659;\nconst uint16_t MALAYALAM_KA = 0x0D15;\nconst uint16_t UCAS_E = 0x1401;\nconst uint16_t HYPHEN = 0x2010;\nconst uint16_t EN_DASH = 0x2013;\n\n// Simple test for US English. This tests \"table\", which happens to be the in\n// the exceptions list.\nTEST_F(HyphenatorTest, usEnglishAutomaticHyphenation) {\n  Hyphenator* hyphenator =\n      Hyphenator::loadBinary(readWholeFile(usHyph).data(), 2, 3);\n  const uint16_t word[] = {'t', 'a', 'b', 'l', 'e'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)5, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[3]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[4]);\n}\n\n// Catalan l·l should break as l-/l\nTEST_F(HyphenatorTest, catalanMiddleDot) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {'l', 'l', MIDDLE_DOT, 'l', 'l'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), catalanLocale);\n  EXPECT_EQ((size_t)5, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN, result[3]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[4]);\n}\n\n// Catalan l·l should not break if the word is too short.\nTEST_F(HyphenatorTest, catalanMiddleDotShortWord) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {'l', MIDDLE_DOT, 'l'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), catalanLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);\n}\n\n// If we break on a hyphen in Polish, the hyphen should be repeated on the next\n// line.\nTEST_F(HyphenatorTest, polishHyphen) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {'x', HYPHEN, 'y'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), polishLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE, result[2]);\n}\n\n// If the language is Polish but the script is not Latin, don't use Polish rules\n// for hyphenation.\nTEST_F(HyphenatorTest, polishHyphenButNonLatinWord) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {GREEK_LOWER_ALPHA, HYPHEN, GREEK_LOWER_ALPHA};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), polishLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);\n}\n\n// Polish en dash doesn't repeat on next line (as far as we know), but just\n// provides a break opportunity.\nTEST_F(HyphenatorTest, polishEnDash) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {'x', EN_DASH, 'y'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), polishLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);\n}\n\n// In Latin script text, soft hyphens should insert a visible hyphen if broken\n// at.\nTEST_F(HyphenatorTest, latinSoftHyphen) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {'x', SOFT_HYPHEN, 'y'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);\n}\n\n// Soft hyphens at the beginning of a word are not useful in linebreaking.\nTEST_F(HyphenatorTest, latinSoftHyphenStartingTheWord) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {SOFT_HYPHEN, 'y'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)2, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n}\n\n// In Malayalam script text, soft hyphens should not insert a visible hyphen if\n// broken at.\nTEST_F(HyphenatorTest, malayalamSoftHyphen) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {MALAYALAM_KA, SOFT_HYPHEN, MALAYALAM_KA};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);\n}\n\n// In automatically hyphenated Malayalam script text, we should not insert a\n// visible hyphen.\nTEST_F(HyphenatorTest, malayalamAutomaticHyphenation) {\n  Hyphenator* hyphenator =\n      Hyphenator::loadBinary(readWholeFile(malayalamHyph).data(), 2, 2);\n  const uint16_t word[] = {MALAYALAM_KA, MALAYALAM_KA, MALAYALAM_KA,\n                           MALAYALAM_KA, MALAYALAM_KA};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)5, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[3]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[4]);\n}\n\n// In Armenian script text, soft hyphens should insert an Armenian hyphen if\n// broken at.\nTEST_F(HyphenatorTest, aremenianSoftHyphen) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {ARMENIAN_AYB, SOFT_HYPHEN, ARMENIAN_AYB};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN, result[2]);\n}\n\n// In Hebrew script text, soft hyphens should insert a normal hyphen if broken\n// at, for now. We may need to change this to maqaf later.\nTEST_F(HyphenatorTest, hebrewSoftHyphen) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {HEBREW_ALEF, SOFT_HYPHEN, HEBREW_ALEF};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);\n}\n\n// Soft hyphen between two Arabic letters that join should keep the joining\n// behavior when broken across lines.\nTEST_F(HyphenatorTest, arabicSoftHyphenConnecting) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {ARABIC_BEH, SOFT_HYPHEN, ARABIC_BEH};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ, result[2]);\n}\n\n// Arabic letters may be joining on one side, but if it's the wrong side, we\n// should use the normal hyphen.\nTEST_F(HyphenatorTest, arabicSoftHyphenNonConnecting) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {ARABIC_ALEF, SOFT_HYPHEN, ARABIC_BEH};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);\n}\n\n// Skip transparent characters until you find a non-transparent one.\nTEST_F(HyphenatorTest, arabicSoftHyphenSkipTransparents) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {ARABIC_BEH, ARABIC_ZWARAKAY, SOFT_HYPHEN,\n                           ARABIC_ZWARAKAY, ARABIC_BEH};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)5, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ, result[3]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[4]);\n}\n\n// Skip transparent characters until you find a non-transparent one. If we get\n// to one end without finding anything, we are still non-joining.\nTEST_F(HyphenatorTest, arabicSoftHyphenTransparentsAtEnd) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {ARABIC_BEH, ARABIC_ZWARAKAY, SOFT_HYPHEN,\n                           ARABIC_ZWARAKAY};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)4, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[2]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[3]);\n}\n\n// Skip transparent characters until you find a non-transparent one. If we get\n// to one end without finding anything, we are still non-joining.\nTEST_F(HyphenatorTest, arabicSoftHyphenTransparentsAtStart) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {ARABIC_ZWARAKAY, SOFT_HYPHEN, ARABIC_ZWARAKAY,\n                           ARABIC_BEH};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)4, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_HYPHEN, result[2]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[3]);\n}\n\n// In Unified Canadian Aboriginal script (UCAS) text, soft hyphens should insert\n// a UCAS hyphen.\nTEST_F(HyphenatorTest, ucasSoftHyphen) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {UCAS_E, SOFT_HYPHEN, UCAS_E};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN, result[2]);\n}\n\n// Presently, soft hyphen looks at the character after it to determine\n// hyphenation type. This is a little arbitrary, but let's test it anyway.\nTEST_F(HyphenatorTest, mixedScriptSoftHyphen) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {'a', SOFT_HYPHEN, UCAS_E};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN, result[2]);\n}\n\n// Hard hyphens provide a breaking opportunity with nothing extra inserted.\nTEST_F(HyphenatorTest, hardHyphen) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {'x', HYPHEN, 'y'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);\n}\n\n// Hyphen-minuses also provide a breaking opportunity with nothing extra\n// inserted.\nTEST_F(HyphenatorTest, hyphenMinus) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {'x', HYPHEN_MINUS, 'y'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)3, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n  EXPECT_EQ(HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, result[2]);\n}\n\n// If the word starts with a hard hyphen or hyphen-minus, it doesn't make sense\n// to break it at that point.\nTEST_F(HyphenatorTest, startingHyphenMinus) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(nullptr, 2, 2);\n  const uint16_t word[] = {HYPHEN_MINUS, 'y'};\n  std::vector<HyphenationType> result;\n  hyphenator->hyphenate(&result, word, NELEM(word), usLocale);\n  EXPECT_EQ((size_t)2, result.size());\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[0]);\n  EXPECT_EQ(HyphenationType::DONT_BREAK, result[1]);\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/ICUTestBase.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_TESTS_ICUTESTBASE_H_\n#define CLAY_THIRD_PARTY_TXT_TESTS_ICUTESTBASE_H_\n\n#include <gtest/gtest.h>\n#include <unicode/uclean.h>\n#include <unicode/udata.h>\n\n// low level file access for mapping ICU data\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n\nnamespace minikin {\n\nclass ICUTestBase : public testing::Test {\n protected:\n  virtual void SetUp() override {\n    const char* fn = \"/system/usr/icu/\" U_ICUDATA_NAME \".dat\";\n    int fd = open(fn, O_RDONLY);\n    ASSERT_NE(-1, fd);\n    struct stat sb;\n    ASSERT_EQ(0, fstat(fd, &sb));\n    void* data = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);\n\n    UErrorCode errorCode = U_ZERO_ERROR;\n    udata_setCommonData(data, &errorCode);\n    ASSERT_TRUE(U_SUCCESS(errorCode));\n    u_init(&errorCode);\n    ASSERT_TRUE(U_SUCCESS(errorCode));\n  }\n\n  virtual void TearDown() override { u_cleanup(); }\n};\n\n}  // namespace minikin\n#endif  // CLAY_THIRD_PARTY_TXT_TESTS_ICUTESTBASE_H_\n"
  },
  {
    "path": "clay/third_party/txt/tests/LayoutTest.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\n#include \"../util/FontTestUtils.h\"\n#include \"../util/UnicodeUtils.h\"\n#include \"ICUTestBase.h\"\n#include \"minikin/FontCollection.h\"\n#include \"minikin/Layout.h\"\n\nconst char* SYSTEM_FONT_PATH = \"/system/fonts/\";\nconst char* SYSTEM_FONT_XML = \"/system/etc/fonts.xml\";\n\nnamespace minikin {\n\nconst float UNTOUCHED_MARKER = 1e+38;\n\nstatic void expectAdvances(std::vector<float> expected,\n                           float* advances,\n                           size_t length) {\n  EXPECT_LE(expected.size(), length);\n  for (size_t i = 0; i < expected.size(); ++i) {\n    EXPECT_EQ(expected[i], advances[i])\n        << i << \"th element is different. Expected: \" << expected[i]\n        << \", Actual: \" << advances[i];\n  }\n  EXPECT_EQ(UNTOUCHED_MARKER, advances[expected.size()]);\n}\n\nstatic void resetAdvances(float* advances, size_t length) {\n  for (size_t i = 0; i < length; ++i) {\n    advances[i] = UNTOUCHED_MARKER;\n  }\n}\n\nclass LayoutTest : public ICUTestBase {\n protected:\n  LayoutTest() : mCollection(nullptr) {}\n\n  virtual ~LayoutTest() {}\n\n  virtual void SetUp() override {\n    mCollection = std::shared_ptr<FontCollection>(\n        getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));\n  }\n\n  virtual void TearDown() override {}\n\n  std::shared_ptr<FontCollection> mCollection;\n};\n\nTEST_F(LayoutTest, doLayoutTest) {\n  MinikinPaint paint;\n  MinikinRect rect;\n  const size_t kMaxAdvanceLength = 32;\n  float advances[kMaxAdvanceLength];\n  std::vector<float> expectedValues;\n\n  Layout layout;\n  std::vector<uint16_t> text;\n\n  // The mock implementation returns 10.0f advance and 0,0-10x10 bounds for all\n  // glyph.\n  {\n    SCOPED_TRACE(\"one word\");\n    text = utf8ToUtf16(\"oneword\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(70.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(70.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n  {\n    SCOPED_TRACE(\"two words\");\n    text = utf8ToUtf16(\"two words\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(90.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(90.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n  {\n    SCOPED_TRACE(\"three words\");\n    text = utf8ToUtf16(\"three words test\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(160.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(160.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n  {\n    SCOPED_TRACE(\"two spaces\");\n    text = utf8ToUtf16(\"two  spaces\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(110.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(110.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n}\n\nTEST_F(LayoutTest, doLayoutTest_wordSpacing) {\n  MinikinPaint paint;\n  MinikinRect rect;\n  const size_t kMaxAdvanceLength = 32;\n  float advances[kMaxAdvanceLength];\n  std::vector<float> expectedValues;\n  std::vector<uint16_t> text;\n\n  Layout layout;\n\n  paint.wordSpacing = 5.0f;\n\n  // The mock implementation returns 10.0f advance and 0,0-10x10 bounds for all\n  // glyph.\n  {\n    SCOPED_TRACE(\"one word\");\n    text = utf8ToUtf16(\"oneword\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(70.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(70.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n  {\n    SCOPED_TRACE(\"two words\");\n    text = utf8ToUtf16(\"two words\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(95.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(95.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    EXPECT_EQ(UNTOUCHED_MARKER, advances[text.size()]);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectedValues[3] = 15.0f;\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n  {\n    SCOPED_TRACE(\"three words test\");\n    text = utf8ToUtf16(\"three words test\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(170.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(170.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectedValues[5] = 15.0f;\n    expectedValues[11] = 15.0f;\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n  {\n    SCOPED_TRACE(\"two spaces\");\n    text = utf8ToUtf16(\"two  spaces\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(120.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(120.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectedValues[3] = 15.0f;\n    expectedValues[4] = 15.0f;\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n}\n\nTEST_F(LayoutTest, doLayoutTest_negativeWordSpacing) {\n  MinikinPaint paint;\n  MinikinRect rect;\n  const size_t kMaxAdvanceLength = 32;\n  float advances[kMaxAdvanceLength];\n  std::vector<float> expectedValues;\n\n  Layout layout;\n  std::vector<uint16_t> text;\n\n  // Negative word spacing also should work.\n  paint.wordSpacing = -5.0f;\n\n  {\n    SCOPED_TRACE(\"one word\");\n    text = utf8ToUtf16(\"oneword\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(70.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(70.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n  {\n    SCOPED_TRACE(\"two words\");\n    text = utf8ToUtf16(\"two words\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(85.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(85.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectedValues[3] = 5.0f;\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n  {\n    SCOPED_TRACE(\"three words\");\n    text = utf8ToUtf16(\"three word test\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(140.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(140.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectedValues[5] = 5.0f;\n    expectedValues[10] = 5.0f;\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n  {\n    SCOPED_TRACE(\"two spaces\");\n    text = utf8ToUtf16(\"two  spaces\");\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(100.0f, layout.getAdvance());\n    layout.getBounds(&rect);\n    EXPECT_EQ(0.0f, rect.mLeft);\n    EXPECT_EQ(0.0f, rect.mTop);\n    EXPECT_EQ(100.0f, rect.mRight);\n    EXPECT_EQ(10.0f, rect.mBottom);\n    resetAdvances(advances, kMaxAdvanceLength);\n    layout.getAdvances(advances);\n    expectedValues.resize(text.size());\n    for (size_t i = 0; i < expectedValues.size(); ++i) {\n      expectedValues[i] = 10.0f;\n    }\n    expectedValues[3] = 5.0f;\n    expectedValues[4] = 5.0f;\n    expectAdvances(expectedValues, advances, kMaxAdvanceLength);\n  }\n}\n\nTEST_F(LayoutTest, doLayoutTest_rtlTest) {\n  MinikinPaint paint;\n\n  std::vector<uint16_t> text =\n      parseUnicodeString(\"'a' 'b' U+3042 U+3043 'c' 'd'\");\n\n  Layout ltrLayout;\n  ltrLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                     FontStyle(), paint, mCollection);\n\n  Layout rtlLayout;\n  rtlLayout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_RTL,\n                     FontStyle(), paint, mCollection);\n\n  ASSERT_EQ(ltrLayout.nGlyphs(), rtlLayout.nGlyphs());\n  ASSERT_EQ(6u, ltrLayout.nGlyphs());\n\n  size_t nGlyphs = ltrLayout.nGlyphs();\n  for (size_t i = 0; i < nGlyphs; ++i) {\n    EXPECT_EQ(ltrLayout.getFont(i), rtlLayout.getFont(nGlyphs - i - 1));\n    EXPECT_EQ(ltrLayout.getGlyphId(i), rtlLayout.getGlyphId(nGlyphs - i - 1));\n  }\n}\n\nTEST_F(LayoutTest, hyphenationTest) {\n  Layout layout;\n  std::vector<uint16_t> text;\n\n  // The mock implementation returns 10.0f advance for all glyphs.\n  {\n    SCOPED_TRACE(\"one word with no hyphen edit\");\n    text = utf8ToUtf16(\"oneword\");\n    MinikinPaint paint;\n    paint.hyphenEdit = HyphenEdit::NO_EDIT;\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(70.0f, layout.getAdvance());\n  }\n  {\n    SCOPED_TRACE(\"one word with hyphen insertion at the end\");\n    text = utf8ToUtf16(\"oneword\");\n    MinikinPaint paint;\n    paint.hyphenEdit = HyphenEdit::INSERT_HYPHEN_AT_END;\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(80.0f, layout.getAdvance());\n  }\n  {\n    SCOPED_TRACE(\"one word with hyphen replacement at the end\");\n    text = utf8ToUtf16(\"oneword\");\n    MinikinPaint paint;\n    paint.hyphenEdit = HyphenEdit::REPLACE_WITH_HYPHEN_AT_END;\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(70.0f, layout.getAdvance());\n  }\n  {\n    SCOPED_TRACE(\"one word with hyphen insertion at the start\");\n    text = utf8ToUtf16(\"oneword\");\n    MinikinPaint paint;\n    paint.hyphenEdit = HyphenEdit::INSERT_HYPHEN_AT_START;\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(80.0f, layout.getAdvance());\n  }\n  {\n    SCOPED_TRACE(\"one word with hyphen insertion at the both ends\");\n    text = utf8ToUtf16(\"oneword\");\n    MinikinPaint paint;\n    paint.hyphenEdit =\n        HyphenEdit::INSERT_HYPHEN_AT_START | HyphenEdit::INSERT_HYPHEN_AT_END;\n    layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                    FontStyle(), paint, mCollection);\n    EXPECT_EQ(90.0f, layout.getAdvance());\n  }\n}\n\n// TODO: Add more test cases, e.g. measure text, letter spacing.\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/LayoutUtilsTest.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include \"UnicodeUtils.h\"\n\n#include \"minikin/LayoutUtils.h\"\n\nnamespace minikin {\n\nvoid ExpectNextWordBreakForCache(size_t offset_in, const char* query_str) {\n  const size_t BUF_SIZE = 256U;\n  uint16_t buf[BUF_SIZE];\n  size_t expected_breakpoint = 0U;\n  size_t size = 0U;\n\n  ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);\n  EXPECT_EQ(expected_breakpoint, getNextWordBreakForCache(buf, offset_in, size))\n      << \"Expected position is [\" << query_str << \"] from offset \" << offset_in;\n}\n\nvoid ExpectPrevWordBreakForCache(size_t offset_in, const char* query_str) {\n  const size_t BUF_SIZE = 256U;\n  uint16_t buf[BUF_SIZE];\n  size_t expected_breakpoint = 0U;\n  size_t size = 0U;\n\n  ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint);\n  EXPECT_EQ(expected_breakpoint, getPrevWordBreakForCache(buf, offset_in, size))\n      << \"Expected position is [\" << query_str << \"] from offset \" << offset_in;\n}\n\nTEST(WordBreakTest, goNextWordBreakTest) {\n  ExpectNextWordBreakForCache(0, \"|\");\n\n  // Continue for spaces.\n  ExpectNextWordBreakForCache(0, \"'a' 'b' 'c' 'd' |\");\n  ExpectNextWordBreakForCache(1, \"'a' 'b' 'c' 'd' |\");\n  ExpectNextWordBreakForCache(2, \"'a' 'b' 'c' 'd' |\");\n  ExpectNextWordBreakForCache(3, \"'a' 'b' 'c' 'd' |\");\n  ExpectNextWordBreakForCache(4, \"'a' 'b' 'c' 'd' |\");\n  ExpectNextWordBreakForCache(1000, \"'a' 'b' 'c' 'd' |\");\n\n  // Space makes word break.\n  ExpectNextWordBreakForCache(0, \"'a' 'b' | U+0020 'c' 'd'\");\n  ExpectNextWordBreakForCache(1, \"'a' 'b' | U+0020 'c' 'd'\");\n  ExpectNextWordBreakForCache(2, \"'a' 'b' U+0020 | 'c' 'd'\");\n  ExpectNextWordBreakForCache(3, \"'a' 'b' U+0020 'c' 'd' |\");\n  ExpectNextWordBreakForCache(4, \"'a' 'b' U+0020 'c' 'd' |\");\n  ExpectNextWordBreakForCache(5, \"'a' 'b' U+0020 'c' 'd' |\");\n  ExpectNextWordBreakForCache(1000, \"'a' 'b' U+0020 'c' 'd' |\");\n\n  ExpectNextWordBreakForCache(0, \"'a' 'b' | U+2000 'c' 'd'\");\n  ExpectNextWordBreakForCache(1, \"'a' 'b' | U+2000 'c' 'd'\");\n  ExpectNextWordBreakForCache(2, \"'a' 'b' U+2000 | 'c' 'd'\");\n  ExpectNextWordBreakForCache(3, \"'a' 'b' U+2000 'c' 'd' |\");\n  ExpectNextWordBreakForCache(4, \"'a' 'b' U+2000 'c' 'd' |\");\n  ExpectNextWordBreakForCache(5, \"'a' 'b' U+2000 'c' 'd' |\");\n  ExpectNextWordBreakForCache(1000, \"'a' 'b' U+2000 'c' 'd' |\");\n\n  ExpectNextWordBreakForCache(0, \"'a' 'b' | U+2000 U+2000 'c' 'd'\");\n  ExpectNextWordBreakForCache(1, \"'a' 'b' | U+2000 U+2000 'c' 'd'\");\n  ExpectNextWordBreakForCache(2, \"'a' 'b' U+2000 | U+2000 'c' 'd'\");\n  ExpectNextWordBreakForCache(3, \"'a' 'b' U+2000 U+2000 | 'c' 'd'\");\n  ExpectNextWordBreakForCache(4, \"'a' 'b' U+2000 U+2000 'c' 'd' |\");\n  ExpectNextWordBreakForCache(5, \"'a' 'b' U+2000 U+2000 'c' 'd' |\");\n  ExpectNextWordBreakForCache(6, \"'a' 'b' U+2000 U+2000 'c' 'd' |\");\n  ExpectNextWordBreakForCache(1000, \"'a' 'b' U+2000 U+2000 'c' 'd' |\");\n\n  // CJK ideographs makes word break.\n  ExpectNextWordBreakForCache(0, \"U+4E00 | U+4E00   U+4E00   U+4E00   U+4E00\");\n  ExpectNextWordBreakForCache(1, \"U+4E00   U+4E00 | U+4E00   U+4E00   U+4E00\");\n  ExpectNextWordBreakForCache(2, \"U+4E00   U+4E00   U+4E00 | U+4E00   U+4E00\");\n  ExpectNextWordBreakForCache(3, \"U+4E00   U+4E00   U+4E00   U+4E00 | U+4E00\");\n  ExpectNextWordBreakForCache(4,\n                              \"U+4E00   U+4E00   U+4E00   U+4E00   U+4E00 |\");\n  ExpectNextWordBreakForCache(5,\n                              \"U+4E00   U+4E00   U+4E00   U+4E00   U+4E00 |\");\n  ExpectNextWordBreakForCache(1000,\n                              \"U+4E00   U+4E00   U+4E00   U+4E00   U+4E00 |\");\n\n  ExpectNextWordBreakForCache(0, \"U+4E00 | U+4E8C   U+4E09   U+56DB   U+4E94\");\n  ExpectNextWordBreakForCache(1, \"U+4E00   U+4E8C | U+4E09   U+56DB   U+4E94\");\n  ExpectNextWordBreakForCache(2, \"U+4E00   U+4E8C   U+4E09 | U+56DB   U+4E94\");\n  ExpectNextWordBreakForCache(3, \"U+4E00   U+4E8C   U+4E09   U+56DB | U+4E94\");\n  ExpectNextWordBreakForCache(4,\n                              \"U+4E00   U+4E8C   U+4E09   U+56DB   U+4E94 |\");\n  ExpectNextWordBreakForCache(5,\n                              \"U+4E00   U+4E8C   U+4E09   U+56DB   U+4E94 |\");\n  ExpectNextWordBreakForCache(1000,\n                              \"U+4E00   U+4E8C   U+4E09   U+56DB   U+4E94 |\");\n\n  ExpectNextWordBreakForCache(0, \"U+4E00 'a' 'b' | U+2000 'c' U+4E00\");\n  ExpectNextWordBreakForCache(1, \"U+4E00 'a' 'b' | U+2000 'c' U+4E00\");\n  ExpectNextWordBreakForCache(2, \"U+4E00 'a' 'b' | U+2000 'c' U+4E00\");\n  ExpectNextWordBreakForCache(3, \"U+4E00 'a' 'b' U+2000 | 'c' U+4E00\");\n  ExpectNextWordBreakForCache(4, \"U+4E00 'a' 'b' U+2000 'c' | U+4E00\");\n  ExpectNextWordBreakForCache(5, \"U+4E00 'a' 'b' U+2000 'c' U+4E00 |\");\n  ExpectNextWordBreakForCache(1000, \"U+4E00 'a' 'b' U+2000 'c' U+4E00 |\");\n\n  // Continue if trailing characters is Unicode combining characters.\n  ExpectNextWordBreakForCache(0, \"U+4E00 U+0332 | U+4E00\");\n  ExpectNextWordBreakForCache(1, \"U+4E00 U+0332 | U+4E00\");\n  ExpectNextWordBreakForCache(2, \"U+4E00 U+0332 U+4E00 |\");\n  ExpectNextWordBreakForCache(3, \"U+4E00 U+0332 U+4E00 |\");\n  ExpectNextWordBreakForCache(1000, \"U+4E00 U+0332 U+4E00 |\");\n\n  // Surrogate pairs.\n  ExpectNextWordBreakForCache(0, \"U+1F60D U+1F618 |\");\n  ExpectNextWordBreakForCache(1, \"U+1F60D U+1F618 |\");\n  ExpectNextWordBreakForCache(2, \"U+1F60D U+1F618 |\");\n  ExpectNextWordBreakForCache(3, \"U+1F60D U+1F618 |\");\n  ExpectNextWordBreakForCache(4, \"U+1F60D U+1F618 |\");\n  ExpectNextWordBreakForCache(1000, \"U+1F60D U+1F618 |\");\n\n  // Broken surrogate pairs.\n  // U+D84D is leading surrogate but there is no trailing surrogate for it.\n  ExpectNextWordBreakForCache(0, \"U+D84D U+1F618 |\");\n  ExpectNextWordBreakForCache(1, \"U+D84D U+1F618 |\");\n  ExpectNextWordBreakForCache(2, \"U+D84D U+1F618 |\");\n  ExpectNextWordBreakForCache(3, \"U+D84D U+1F618 |\");\n  ExpectNextWordBreakForCache(1000, \"U+D84D U+1F618 |\");\n\n  ExpectNextWordBreakForCache(0, \"U+1F618 U+D84D |\");\n  ExpectNextWordBreakForCache(1, \"U+1F618 U+D84D |\");\n  ExpectNextWordBreakForCache(2, \"U+1F618 U+D84D |\");\n  ExpectNextWordBreakForCache(3, \"U+1F618 U+D84D |\");\n  ExpectNextWordBreakForCache(1000, \"U+1F618 U+D84D |\");\n\n  // U+DE0D is trailing surrogate but there is no leading surrogate for it.\n  ExpectNextWordBreakForCache(0, \"U+DE0D U+1F618 |\");\n  ExpectNextWordBreakForCache(1, \"U+DE0D U+1F618 |\");\n  ExpectNextWordBreakForCache(2, \"U+DE0D U+1F618 |\");\n  ExpectNextWordBreakForCache(3, \"U+DE0D U+1F618 |\");\n  ExpectNextWordBreakForCache(1000, \"U+DE0D U+1F618 |\");\n\n  ExpectNextWordBreakForCache(0, \"U+1F618 U+DE0D |\");\n  ExpectNextWordBreakForCache(1, \"U+1F618 U+DE0D |\");\n  ExpectNextWordBreakForCache(2, \"U+1F618 U+DE0D |\");\n  ExpectNextWordBreakForCache(3, \"U+1F618 U+DE0D |\");\n  ExpectNextWordBreakForCache(1000, \"U+1F618 U+DE0D |\");\n\n  // Regional indicator pair. U+1F1FA U+1F1F8 is US national flag.\n  ExpectNextWordBreakForCache(0, \"U+1F1FA U+1F1F8 |\");\n  ExpectNextWordBreakForCache(1, \"U+1F1FA U+1F1F8 |\");\n  ExpectNextWordBreakForCache(2, \"U+1F1FA U+1F1F8 |\");\n  ExpectNextWordBreakForCache(1000, \"U+1F1FA U+1F1F8 |\");\n\n  // Tone marks.\n  // CJK ideographic char + Tone mark + CJK ideographic char\n  ExpectNextWordBreakForCache(0, \"U+4444 U+302D | U+4444\");\n  ExpectNextWordBreakForCache(1, \"U+4444 U+302D | U+4444\");\n  ExpectNextWordBreakForCache(2, \"U+4444 U+302D U+4444 |\");\n  ExpectNextWordBreakForCache(3, \"U+4444 U+302D U+4444 |\");\n  ExpectNextWordBreakForCache(1000, \"U+4444 U+302D U+4444 |\");\n\n  // Variation Selectors.\n  // CJK Ideographic char + Variation Selector(VS1) + CJK Ideographic char\n  ExpectNextWordBreakForCache(0, \"U+845B U+FE00 | U+845B\");\n  ExpectNextWordBreakForCache(1, \"U+845B U+FE00 | U+845B\");\n  ExpectNextWordBreakForCache(2, \"U+845B U+FE00 U+845B |\");\n  ExpectNextWordBreakForCache(3, \"U+845B U+FE00 U+845B |\");\n  ExpectNextWordBreakForCache(1000, \"U+845B U+FE00 U+845B |\");\n\n  // CJK Ideographic char + Variation Selector(VS17) + CJK Ideographic char\n  ExpectNextWordBreakForCache(0, \"U+845B U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(1, \"U+845B U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(2, \"U+845B U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(3, \"U+845B U+E0100 U+845B |\");\n  ExpectNextWordBreakForCache(4, \"U+845B U+E0100 U+845B |\");\n  ExpectNextWordBreakForCache(5, \"U+845B U+E0100 U+845B |\");\n  ExpectNextWordBreakForCache(1000, \"U+845B U+E0100 U+845B |\");\n\n  // CJK ideographic char + Tone mark + Variation Character(VS1)\n  ExpectNextWordBreakForCache(0, \"U+4444 U+302D U+FE00 | U+4444\");\n  ExpectNextWordBreakForCache(1, \"U+4444 U+302D U+FE00 | U+4444\");\n  ExpectNextWordBreakForCache(2, \"U+4444 U+302D U+FE00 | U+4444\");\n  ExpectNextWordBreakForCache(3, \"U+4444 U+302D U+FE00 U+4444 |\");\n  ExpectNextWordBreakForCache(4, \"U+4444 U+302D U+FE00 U+4444 |\");\n  ExpectNextWordBreakForCache(1000, \"U+4444 U+302D U+FE00 U+4444 |\");\n\n  // CJK ideographic char + Tone mark + Variation Character(VS17)\n  ExpectNextWordBreakForCache(0, \"U+4444 U+302D U+E0100 | U+4444\");\n  ExpectNextWordBreakForCache(1, \"U+4444 U+302D U+E0100 | U+4444\");\n  ExpectNextWordBreakForCache(2, \"U+4444 U+302D U+E0100 | U+4444\");\n  ExpectNextWordBreakForCache(3, \"U+4444 U+302D U+E0100 | U+4444\");\n  ExpectNextWordBreakForCache(4, \"U+4444 U+302D U+E0100 U+4444 |\");\n  ExpectNextWordBreakForCache(5, \"U+4444 U+302D U+E0100 U+4444 |\");\n  ExpectNextWordBreakForCache(1000, \"U+4444 U+302D U+E0100 U+4444 |\");\n\n  // CJK ideographic char + Variation Character(VS1) + Tone mark\n  ExpectNextWordBreakForCache(0, \"U+4444 U+FE00 U+302D | U+4444\");\n  ExpectNextWordBreakForCache(1, \"U+4444 U+FE00 U+302D | U+4444\");\n  ExpectNextWordBreakForCache(2, \"U+4444 U+FE00 U+302D | U+4444\");\n  ExpectNextWordBreakForCache(3, \"U+4444 U+FE00 U+302D U+4444 |\");\n  ExpectNextWordBreakForCache(4, \"U+4444 U+FE00 U+302D U+4444 |\");\n  ExpectNextWordBreakForCache(1000, \"U+4444 U+FE00 U+302D U+4444 |\");\n\n  // CJK ideographic char + Variation Character(VS17) + Tone mark\n  ExpectNextWordBreakForCache(0, \"U+4444 U+E0100 U+302D | U+4444\");\n  ExpectNextWordBreakForCache(1, \"U+4444 U+E0100 U+302D | U+4444\");\n  ExpectNextWordBreakForCache(2, \"U+4444 U+E0100 U+302D | U+4444\");\n  ExpectNextWordBreakForCache(3, \"U+4444 U+E0100 U+302D | U+4444\");\n  ExpectNextWordBreakForCache(4, \"U+4444 U+E0100 U+302D U+4444 |\");\n  ExpectNextWordBreakForCache(5, \"U+4444 U+E0100 U+302D U+4444 |\");\n  ExpectNextWordBreakForCache(1000, \"U+4444 U+E0100 U+302D U+4444 |\");\n\n  // Following test cases are unusual usage of variation selectors and tone\n  // marks for caching up the further behavior changes, e.g. index of bounds\n  // or crashes. Please feel free to update the test expectations if the\n  // behavior change makes sense to you.\n\n  // Isolated Tone marks and Variation Selectors\n  ExpectNextWordBreakForCache(0, \"U+FE00 |\");\n  ExpectNextWordBreakForCache(1, \"U+FE00 |\");\n  ExpectNextWordBreakForCache(1000, \"U+FE00 |\");\n  ExpectNextWordBreakForCache(0, \"U+E0100 |\");\n  ExpectNextWordBreakForCache(1000, \"U+E0100 |\");\n  ExpectNextWordBreakForCache(0, \"U+302D |\");\n  ExpectNextWordBreakForCache(1000, \"U+302D |\");\n\n  // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS1)\n  ExpectNextWordBreakForCache(0, \"U+845B U+FE00 U+FE00 | U+845B\");\n  ExpectNextWordBreakForCache(1, \"U+845B U+FE00 U+FE00 | U+845B\");\n  ExpectNextWordBreakForCache(2, \"U+845B U+FE00 U+FE00 | U+845B\");\n  ExpectNextWordBreakForCache(3, \"U+845B U+FE00 U+FE00 U+845B |\");\n  ExpectNextWordBreakForCache(4, \"U+845B U+FE00 U+FE00 U+845B |\");\n  ExpectNextWordBreakForCache(1000, \"U+845B U+FE00 U+FE00 U+845B |\");\n\n  // CJK Ideographic char + Variation Selector(VS17) + Variation Selector(VS17)\n  ExpectNextWordBreakForCache(0, \"U+845B U+E0100 U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(1, \"U+845B U+E0100 U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(2, \"U+845B U+E0100 U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(3, \"U+845B U+E0100 U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(4, \"U+845B U+E0100 U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(5, \"U+845B U+E0100 U+E0100 U+845B |\");\n  ExpectNextWordBreakForCache(6, \"U+845B U+E0100 U+E0100 U+845B |\");\n  ExpectNextWordBreakForCache(1000, \"U+845B U+E0100 U+E0100 U+845B |\");\n\n  // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS17)\n  ExpectNextWordBreakForCache(0, \"U+845B U+FE00 U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(1, \"U+845B U+FE00 U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(2, \"U+845B U+FE00 U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(3, \"U+845B U+FE00 U+E0100 | U+845B\");\n  ExpectNextWordBreakForCache(4, \"U+845B U+FE00 U+E0100 U+845B |\");\n  ExpectNextWordBreakForCache(5, \"U+845B U+FE00 U+E0100 U+845B |\");\n  ExpectNextWordBreakForCache(1000, \"U+845B U+FE00 U+E0100 U+845B |\");\n\n  // CJK Ideographic char + Variation Selector(VS17) + Variation Selector(VS1)\n  ExpectNextWordBreakForCache(0, \"U+845B U+E0100 U+FE00 | U+845B\");\n  ExpectNextWordBreakForCache(1, \"U+845B U+E0100 U+FE00 | U+845B\");\n  ExpectNextWordBreakForCache(2, \"U+845B U+E0100 U+FE00 | U+845B\");\n  ExpectNextWordBreakForCache(3, \"U+845B U+E0100 U+FE00 | U+845B\");\n  ExpectNextWordBreakForCache(4, \"U+845B U+E0100 U+FE00 U+845B |\");\n  ExpectNextWordBreakForCache(5, \"U+845B U+E0100 U+FE00 U+845B |\");\n  ExpectNextWordBreakForCache(1000, \"U+845B U+E0100 U+FE00 U+845B |\");\n\n  // Tone mark. + Tone mark\n  ExpectNextWordBreakForCache(0, \"U+4444 U+302D U+302D | U+4444\");\n  ExpectNextWordBreakForCache(1, \"U+4444 U+302D U+302D | U+4444\");\n  ExpectNextWordBreakForCache(2, \"U+4444 U+302D U+302D | U+4444\");\n  ExpectNextWordBreakForCache(3, \"U+4444 U+302D U+302D U+4444 |\");\n  ExpectNextWordBreakForCache(4, \"U+4444 U+302D U+302D U+4444 |\");\n  ExpectNextWordBreakForCache(1000, \"U+4444 U+302D U+302D U+4444 |\");\n}\n\nTEST(WordBreakTest, goPrevWordBreakTest) {\n  ExpectPrevWordBreakForCache(0, \"|\");\n\n  // Continue for spaces.\n  ExpectPrevWordBreakForCache(0, \"| 'a' 'b' 'c' 'd'\");\n  ExpectPrevWordBreakForCache(1, \"| 'a' 'b' 'c' 'd'\");\n  ExpectPrevWordBreakForCache(2, \"| 'a' 'b' 'c' 'd'\");\n  ExpectPrevWordBreakForCache(3, \"| 'a' 'b' 'c' 'd'\");\n  ExpectPrevWordBreakForCache(4, \"| 'a' 'b' 'c' 'd'\");\n  ExpectPrevWordBreakForCache(1000, \"| 'a' 'b' 'c' 'd'\");\n\n  // Space makes word break.\n  ExpectPrevWordBreakForCache(0, \"| 'a' 'b' U+0020 'c' 'd'\");\n  ExpectPrevWordBreakForCache(1, \"| 'a' 'b' U+0020 'c' 'd'\");\n  ExpectPrevWordBreakForCache(2, \"| 'a' 'b' U+0020 'c' 'd'\");\n  ExpectPrevWordBreakForCache(3, \"'a' 'b' | U+0020 'c' 'd'\");\n  ExpectPrevWordBreakForCache(4, \"'a' 'b' U+0020 | 'c' 'd'\");\n  ExpectPrevWordBreakForCache(5, \"'a' 'b' U+0020 | 'c' 'd'\");\n  ExpectPrevWordBreakForCache(1000, \"'a' 'b' U+0020 | 'c' 'd'\");\n\n  ExpectPrevWordBreakForCache(0, \"| 'a' 'b' U+2000 'c' 'd'\");\n  ExpectPrevWordBreakForCache(1, \"| 'a' 'b' U+2000 'c' 'd'\");\n  ExpectPrevWordBreakForCache(2, \"| 'a' 'b' U+2000 'c' 'd'\");\n  ExpectPrevWordBreakForCache(3, \"'a' 'b' | U+2000 'c' 'd'\");\n  ExpectPrevWordBreakForCache(4, \"'a' 'b' U+2000 | 'c' 'd'\");\n  ExpectPrevWordBreakForCache(5, \"'a' 'b' U+2000 | 'c' 'd'\");\n  ExpectPrevWordBreakForCache(1000, \"'a' 'b' U+2000 | 'c' 'd'\");\n\n  ExpectPrevWordBreakForCache(0, \"| 'a' 'b' U+2000 U+2000 'c' 'd'\");\n  ExpectPrevWordBreakForCache(1, \"| 'a' 'b' U+2000 U+2000 'c' 'd'\");\n  ExpectPrevWordBreakForCache(2, \"| 'a' 'b' U+2000 U+2000 'c' 'd'\");\n  ExpectPrevWordBreakForCache(3, \"'a' 'b' | U+2000 U+2000 'c' 'd'\");\n  ExpectPrevWordBreakForCache(4, \"'a' 'b' U+2000 | U+2000 'c' 'd'\");\n  ExpectPrevWordBreakForCache(5, \"'a' 'b' U+2000 U+2000 | 'c' 'd'\");\n  ExpectPrevWordBreakForCache(6, \"'a' 'b' U+2000 U+2000 | 'c' 'd'\");\n  ExpectPrevWordBreakForCache(1000, \"'a' 'b' U+2000 U+2000 | 'c' 'd'\");\n\n  // CJK ideographs makes word break.\n  ExpectPrevWordBreakForCache(0, \"| U+4E00 U+4E00 U+4E00 U+4E00 U+4E00\");\n  ExpectPrevWordBreakForCache(1, \"| U+4E00 U+4E00 U+4E00 U+4E00 U+4E00\");\n  ExpectPrevWordBreakForCache(2, \"U+4E00 | U+4E00 U+4E00 U+4E00 U+4E00\");\n  ExpectPrevWordBreakForCache(3, \"U+4E00 U+4E00 | U+4E00 U+4E00 U+4E00\");\n  ExpectPrevWordBreakForCache(4, \"U+4E00 U+4E00 U+4E00 | U+4E00 U+4E00\");\n  ExpectPrevWordBreakForCache(5, \"U+4E00 U+4E00 U+4E00 U+4E00 | U+4E00\");\n  ExpectPrevWordBreakForCache(1000, \"U+4E00 U+4E00 U+4E00 U+4E00 | U+4E00\");\n\n  ExpectPrevWordBreakForCache(0, \"| U+4E00 U+4E8C U+4E09 U+56DB U+4E94\");\n  ExpectPrevWordBreakForCache(1, \"| U+4E00 U+4E8C U+4E09 U+56DB U+4E94\");\n  ExpectPrevWordBreakForCache(2, \"U+4E00 | U+4E8C U+4E09 U+56DB U+4E94\");\n  ExpectPrevWordBreakForCache(3, \"U+4E00 U+4E8C | U+4E09 U+56DB U+4E94\");\n  ExpectPrevWordBreakForCache(4, \"U+4E00 U+4E8C U+4E09 | U+56DB U+4E94\");\n  ExpectPrevWordBreakForCache(5, \"U+4E00 U+4E8C U+4E09 U+56DB | U+4E94\");\n  ExpectPrevWordBreakForCache(1000, \"U+4E00 U+4E8C U+4E09 U+56DB | U+4E94\");\n\n  // Mixed case.\n  ExpectPrevWordBreakForCache(0, \"| U+4E00 'a' 'b' U+2000 'c' U+4E00\");\n  ExpectPrevWordBreakForCache(1, \"| U+4E00 'a' 'b' U+2000 'c' U+4E00\");\n  ExpectPrevWordBreakForCache(2, \"| U+4E00 'a' 'b' U+2000 'c' U+4E00\");\n  ExpectPrevWordBreakForCache(3, \"| U+4E00 'a' 'b' U+2000 'c' U+4E00\");\n  ExpectPrevWordBreakForCache(4, \"U+4E00 'a' 'b' | U+2000 'c' U+4E00\");\n  ExpectPrevWordBreakForCache(5, \"U+4E00 'a' 'b' U+2000 | 'c' U+4E00\");\n  ExpectPrevWordBreakForCache(6, \"U+4E00 'a' 'b' U+2000 'c' | U+4E00\");\n  ExpectPrevWordBreakForCache(1000, \"U+4E00 'a' 'b' U+2000 'c' | U+4E00\");\n\n  // Continue if trailing characters is Unicode combining characters.\n  ExpectPrevWordBreakForCache(0, \"| U+4E00 U+0332 U+4E00\");\n  ExpectPrevWordBreakForCache(1, \"| U+4E00 U+0332 U+4E00\");\n  ExpectPrevWordBreakForCache(2, \"| U+4E00 U+0332 U+4E00\");\n  ExpectPrevWordBreakForCache(3, \"U+4E00 U+0332 | U+4E00\");\n  ExpectPrevWordBreakForCache(1000, \"U+4E00 U+0332 | U+4E00\");\n\n  // Surrogate pairs.\n  ExpectPrevWordBreakForCache(0, \"| U+1F60D U+1F618\");\n  ExpectPrevWordBreakForCache(1, \"| U+1F60D U+1F618\");\n  ExpectPrevWordBreakForCache(2, \"| U+1F60D U+1F618\");\n  ExpectPrevWordBreakForCache(3, \"| U+1F60D U+1F618\");\n  ExpectPrevWordBreakForCache(4, \"| U+1F60D U+1F618\");\n  ExpectPrevWordBreakForCache(1000, \"| U+1F60D U+1F618\");\n\n  // Broken surrogate pairs.\n  // U+D84D is leading surrogate but there is no trailing surrogate for it.\n  ExpectPrevWordBreakForCache(0, \"| U+D84D U+1F618\");\n  ExpectPrevWordBreakForCache(1, \"| U+D84D U+1F618\");\n  ExpectPrevWordBreakForCache(2, \"| U+D84D U+1F618\");\n  ExpectPrevWordBreakForCache(3, \"| U+D84D U+1F618\");\n  ExpectPrevWordBreakForCache(1000, \"| U+D84D U+1F618\");\n\n  ExpectPrevWordBreakForCache(0, \"| U+1F618 U+D84D\");\n  ExpectPrevWordBreakForCache(1, \"| U+1F618 U+D84D\");\n  ExpectPrevWordBreakForCache(2, \"| U+1F618 U+D84D\");\n  ExpectPrevWordBreakForCache(3, \"| U+1F618 U+D84D\");\n  ExpectPrevWordBreakForCache(1000, \"| U+1F618 U+D84D\");\n\n  // U+DE0D is trailing surrogate but there is no leading surrogate for it.\n  ExpectPrevWordBreakForCache(0, \"| U+DE0D U+1F618\");\n  ExpectPrevWordBreakForCache(1, \"| U+DE0D U+1F618\");\n  ExpectPrevWordBreakForCache(2, \"| U+DE0D U+1F618\");\n  ExpectPrevWordBreakForCache(3, \"| U+DE0D U+1F618\");\n  ExpectPrevWordBreakForCache(1000, \"| U+DE0D U+1F618\");\n\n  ExpectPrevWordBreakForCache(0, \"| U+1F618 U+DE0D\");\n  ExpectPrevWordBreakForCache(1, \"| U+1F618 U+DE0D\");\n  ExpectPrevWordBreakForCache(2, \"| U+1F618 U+DE0D\");\n  ExpectPrevWordBreakForCache(3, \"| U+1F618 U+DE0D\");\n  ExpectPrevWordBreakForCache(1000, \"| U+1F618 U+DE0D\");\n\n  // Regional indicator pair. U+1F1FA U+1F1F8 is US national flag.\n  ExpectPrevWordBreakForCache(0, \"| U+1F1FA U+1F1F8\");\n  ExpectPrevWordBreakForCache(1, \"| U+1F1FA U+1F1F8\");\n  ExpectPrevWordBreakForCache(2, \"| U+1F1FA U+1F1F8\");\n  ExpectPrevWordBreakForCache(1000, \"| U+1F1FA U+1F1F8\");\n\n  // Tone marks.\n  // CJK ideographic char + Tone mark + CJK ideographic char\n  ExpectPrevWordBreakForCache(0, \"| U+4444 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(1, \"| U+4444 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(2, \"| U+4444 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(3, \"U+4444 U+302D | U+4444\");\n  ExpectPrevWordBreakForCache(1000, \"U+4444 U+302D | U+4444\");\n\n  // Variation Selectors.\n  // CJK Ideographic char + Variation Selector(VS1) + CJK Ideographic char\n  ExpectPrevWordBreakForCache(0, \"| U+845B U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(1, \"| U+845B U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(2, \"| U+845B U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(3, \"U+845B U+FE00 | U+845B\");\n  ExpectPrevWordBreakForCache(1000, \"U+845B U+FE00 | U+845B\");\n\n  // CJK Ideographic char + Variation Selector(VS17) + CJK Ideographic char\n  ExpectPrevWordBreakForCache(0, \"| U+845B U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(1, \"| U+845B U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(2, \"| U+845B U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(3, \"| U+845B U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(4, \"U+845B U+E0100 | U+845B\");\n  ExpectPrevWordBreakForCache(5, \"U+845B U+E0100 | U+845B\");\n  ExpectPrevWordBreakForCache(1000, \"U+845B U+E0100 | U+845B\");\n\n  // CJK ideographic char + Tone mark + Variation Character(VS1)\n  ExpectPrevWordBreakForCache(0, \"| U+4444 U+302D U+FE00 U+4444\");\n  ExpectPrevWordBreakForCache(1, \"| U+4444 U+302D U+FE00 U+4444\");\n  ExpectPrevWordBreakForCache(2, \"| U+4444 U+302D U+FE00 U+4444\");\n  ExpectPrevWordBreakForCache(3, \"| U+4444 U+302D U+FE00 U+4444\");\n  ExpectPrevWordBreakForCache(4, \"U+4444 U+302D U+FE00 | U+4444\");\n  ExpectPrevWordBreakForCache(1000, \"U+4444 U+302D U+FE00 | U+4444\");\n\n  // CJK ideographic char + Tone mark + Variation Character(VS17)\n  ExpectPrevWordBreakForCache(0, \"| U+4444 U+302D U+E0100 U+4444\");\n  ExpectPrevWordBreakForCache(1, \"| U+4444 U+302D U+E0100 U+4444\");\n  ExpectPrevWordBreakForCache(2, \"| U+4444 U+302D U+E0100 U+4444\");\n  ExpectPrevWordBreakForCache(3, \"| U+4444 U+302D U+E0100 U+4444\");\n  ExpectPrevWordBreakForCache(4, \"| U+4444 U+302D U+E0100 U+4444\");\n  ExpectPrevWordBreakForCache(5, \"U+4444 U+302D U+E0100 | U+4444\");\n  ExpectPrevWordBreakForCache(1000, \"U+4444 U+302D U+E0100 | U+4444\");\n\n  // CJK ideographic char + Variation Character(VS1) + Tone mark\n  ExpectPrevWordBreakForCache(0, \"| U+4444 U+FE00 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(1, \"| U+4444 U+FE00 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(2, \"| U+4444 U+FE00 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(3, \"| U+4444 U+FE00 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(4, \"U+4444 U+FE00 U+302D | U+4444\");\n  ExpectPrevWordBreakForCache(1000, \"U+4444 U+FE00 U+302D | U+4444\");\n\n  // CJK ideographic char + Variation Character(VS17) + Tone mark\n  ExpectPrevWordBreakForCache(0, \"| U+4444 U+E0100 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(1, \"| U+4444 U+E0100 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(2, \"| U+4444 U+E0100 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(3, \"| U+4444 U+E0100 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(4, \"| U+4444 U+E0100 U+302D U+4444\");\n  ExpectPrevWordBreakForCache(5, \"U+4444 U+E0100 U+302D | U+4444\");\n  ExpectPrevWordBreakForCache(1000, \"U+4444 U+E0100 U+302D | U+4444\");\n\n  // Following test cases are unusual usage of variation selectors and tone\n  // marks for caching up the further behavior changes, e.g. index of bounds\n  // or crashes. Please feel free to update the test expectations if the\n  // behavior change makes sense to you.\n\n  // Isolated Tone marks and Variation Selectors\n  ExpectPrevWordBreakForCache(0, \"| U+FE00\");\n  ExpectPrevWordBreakForCache(1, \"| U+FE00\");\n  ExpectPrevWordBreakForCache(1000, \"| U+FE00\");\n  ExpectPrevWordBreakForCache(0, \"| U+E0100\");\n  ExpectPrevWordBreakForCache(1000, \"| U+E0100\");\n  ExpectPrevWordBreakForCache(0, \"| U+302D\");\n  ExpectPrevWordBreakForCache(1000, \"| U+302D\");\n\n  // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS1)\n  ExpectPrevWordBreakForCache(0, \"| U+845B U+FE00 U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(1, \"| U+845B U+FE00 U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(2, \"| U+845B U+FE00 U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(3, \"| U+845B U+FE00 U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(4, \"U+845B U+FE00 U+FE00 | U+845B\");\n  ExpectPrevWordBreakForCache(1000, \"U+845B U+FE00 U+FE00 | U+845B\");\n\n  // CJK Ideographic char + Variation Selector(VS17) + Variation Selector(VS17)\n  ExpectPrevWordBreakForCache(0, \"| U+845B U+E0100 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(1, \"| U+845B U+E0100 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(2, \"| U+845B U+E0100 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(3, \"| U+845B U+E0100 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(4, \"| U+845B U+E0100 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(5, \"| U+845B U+E0100 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(6, \"U+845B U+E0100 U+E0100 | U+845B\");\n  ExpectPrevWordBreakForCache(1000, \"U+845B U+E0100 U+E0100 | U+845B\");\n\n  // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS17)\n  ExpectPrevWordBreakForCache(0, \"| U+845B U+FE00 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(1, \"| U+845B U+FE00 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(2, \"| U+845B U+FE00 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(3, \"| U+845B U+FE00 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(4, \"| U+845B U+FE00 U+E0100 U+845B\");\n  ExpectPrevWordBreakForCache(5, \"U+845B U+FE00 U+E0100 | U+845B\");\n  ExpectPrevWordBreakForCache(1000, \"U+845B U+FE00 U+E0100 | U+845B\");\n\n  // CJK Ideographic char + Variation Selector(VS17) + Variation Selector(VS1)\n  ExpectPrevWordBreakForCache(0, \"| U+845B U+E0100 U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(1, \"| U+845B U+E0100 U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(2, \"| U+845B U+E0100 U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(3, \"| U+845B U+E0100 U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(4, \"| U+845B U+E0100 U+FE00 U+845B\");\n  ExpectPrevWordBreakForCache(5, \"U+845B U+E0100 U+FE00 | U+845B\");\n  ExpectPrevWordBreakForCache(1000, \"U+845B U+E0100 U+FE00 | U+845B\");\n\n  // Tone mark. + Tone mark\n  ExpectPrevWordBreakForCache(0, \"| U+4444 U+302D U+302D U+4444\");\n  ExpectPrevWordBreakForCache(1, \"| U+4444 U+302D U+302D U+4444\");\n  ExpectPrevWordBreakForCache(2, \"| U+4444 U+302D U+302D U+4444\");\n  ExpectPrevWordBreakForCache(3, \"| U+4444 U+302D U+302D U+4444\");\n  ExpectPrevWordBreakForCache(4, \"U+4444 U+302D U+302D | U+4444\");\n  ExpectPrevWordBreakForCache(1000, \"U+4444 U+302D U+302D | U+4444\");\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/MeasurementTests.cpp",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n#include <minikin/Measurement.h>\n#include \"UnicodeUtils.h\"\n\nnamespace minikin {\n\nfloat getAdvance(const float* advances, const char* src) {\n  const size_t BUF_SIZE = 256;\n  uint16_t buf[BUF_SIZE];\n  size_t offset;\n  size_t size;\n  ParseUnicode(buf, BUF_SIZE, src, &size, &offset);\n  return getRunAdvance(advances, buf, 0, size, offset);\n}\n\n// Latin fi\nTEST(Measurement, getRunAdvance_fi) {\n  const float unligated[] = {30.0, 20.0};\n  EXPECT_EQ(0.0, getAdvance(unligated, \"| 'f' 'i'\"));\n  EXPECT_EQ(30.0, getAdvance(unligated, \"'f' | 'i'\"));\n  EXPECT_EQ(50.0, getAdvance(unligated, \"'f' 'i' |\"));\n\n  const float ligated[] = {40.0, 0.0};\n  EXPECT_EQ(0.0, getAdvance(ligated, \"| 'f' 'i'\"));\n  EXPECT_EQ(20.0, getAdvance(ligated, \"'f' | 'i'\"));\n  EXPECT_EQ(40.0, getAdvance(ligated, \"'f' 'i' |\"));\n}\n\n// Devanagari ka+virama+ka\nTEST(Measurement, getRunAdvance_kka) {\n  const float unligated[] = {30.0, 0.0, 30.0};\n  EXPECT_EQ(0.0, getAdvance(unligated, \"| U+0915 U+094D U+0915\"));\n  EXPECT_EQ(30.0, getAdvance(unligated, \"U+0915 | U+094D U+0915\"));\n  EXPECT_EQ(30.0, getAdvance(unligated, \"U+0915 U+094D | U+0915\"));\n  EXPECT_EQ(60.0, getAdvance(unligated, \"U+0915 U+094D U+0915 |\"));\n\n  const float ligated[] = {30.0, 0.0, 0.0};\n  EXPECT_EQ(0.0, getAdvance(ligated, \"| U+0915 U+094D U+0915\"));\n  EXPECT_EQ(30.0, getAdvance(ligated, \"U+0915 | U+094D U+0915\"));\n  EXPECT_EQ(30.0, getAdvance(ligated, \"U+0915 U+094D | U+0915\"));\n  EXPECT_EQ(30.0, getAdvance(ligated, \"U+0915 U+094D U+0915 |\"));\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/MinikinFontForTest.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include \"MinikinFontForTest.h\"\n\n#include <minikin/MinikinFont.h>\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n#include <string>\n\n#include <log/log.h>\n\nnamespace minikin {\n\nstatic int uniqueId = 0;  // TODO: make thread safe if necessary.\n\nMinikinFontForTest::MinikinFontForTest(\n    const std::string& font_path,\n    int index,\n    const std::vector<FontVariation>& variations)\n    : MinikinFont(uniqueId++),\n      mFontPath(font_path),\n      mVariations(variations),\n      mFontIndex(index) {\n  int fd = open(font_path.c_str(), O_RDONLY);\n  LOG_ALWAYS_FATAL_IF(fd == -1);\n  struct stat st = {};\n  LOG_ALWAYS_FATAL_IF(fstat(fd, &st) != 0);\n  mFontSize = st.st_size;\n  mFontData = mmap(NULL, mFontSize, PROT_READ, MAP_SHARED, fd, 0);\n  LOG_ALWAYS_FATAL_IF(mFontData == nullptr);\n  close(fd);\n}\n\nMinikinFontForTest::~MinikinFontForTest() {\n  munmap(mFontData, mFontSize);\n}\n\nfloat MinikinFontForTest::GetHorizontalAdvance(\n    uint32_t /* glyph_id */,\n    const MinikinPaint& /* paint */) const {\n  // TODO: Make mock value configurable if necessary.\n  return 10.0f;\n}\n\nvoid MinikinFontForTest::GetBounds(MinikinRect* bounds,\n                                   uint32_t /* glyph_id */,\n                                   const MinikinPaint& /* paint */) const {\n  // TODO: Make mock values configurable if necessary.\n  bounds->mLeft = 0.0f;\n  bounds->mTop = 0.0f;\n  bounds->mRight = 10.0f;\n  bounds->mBottom = 10.0f;\n}\n\nstd::shared_ptr<MinikinFont> MinikinFontForTest::createFontWithVariation(\n    const std::vector<FontVariation>& variations) const {\n  return std::shared_ptr<MinikinFont>(\n      new MinikinFontForTest(mFontPath, mFontIndex, variations));\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/MinikinFontForTest.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_TESTS_MINIKINFONTFORTEST_H_\n#define CLAY_THIRD_PARTY_TXT_TESTS_MINIKINFONTFORTEST_H_\n\n#include <minikin/MinikinFont.h>\n#include <memory>\n#include <vector>\n#include <string>\n\nclass SkTypeface;\n\nnamespace minikin {\n\nclass MinikinFontForTest : public MinikinFont {\n public:\n  MinikinFontForTest(const std::string& font_path,\n                     int index,\n                     const std::vector<FontVariation>& variations);\n  MinikinFontForTest(const std::string& font_path, int index)\n      : MinikinFontForTest(font_path, index, std::vector<FontVariation>()) {}\n  MinikinFontForTest(const std::string& font_path)\n      : MinikinFontForTest(font_path, 0) {}\n  virtual ~MinikinFontForTest();\n\n  // MinikinFont overrides.\n  float GetHorizontalAdvance(uint32_t glyph_id,\n                             const MinikinPaint& paint) const;\n  void GetBounds(MinikinRect* bounds,\n                 uint32_t glyph_id,\n                 const MinikinPaint& paint) const;\n\n  const std::string& fontPath() const { return mFontPath; }\n\n  const void* GetFontData() const { return mFontData; }\n  size_t GetFontSize() const { return mFontSize; }\n  int GetFontIndex() const { return mFontIndex; }\n  const std::vector<minikin::FontVariation>& GetAxes() const {\n    return mVariations;\n  }\n  std::shared_ptr<MinikinFont> createFontWithVariation(\n      const std::vector<FontVariation>& variations) const;\n\n private:\n  MinikinFontForTest() = delete;\n  MinikinFontForTest(const MinikinFontForTest&) = delete;\n  MinikinFontForTest& operator=(MinikinFontForTest&) = delete;\n\n  const std::string mFontPath;\n  const std::vector<FontVariation> mVariations;\n  const int mFontIndex;\n  void* mFontData;\n  size_t mFontSize;\n};\n\n}  // namespace minikin\n\n#endif  // CLAY_THIRD_PARTY_TXT_TESTS_MINIKINFONTFORTEST_H_\n"
  },
  {
    "path": "clay/third_party/txt/tests/SparseBitSetTest.cpp",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <random>\n\n#include <gtest/gtest.h>\n#include <minikin/SparseBitSet.h>\n\nnamespace minikin {\n\nTEST(SparseBitSetTest, randomTest) {\n  const uint32_t kTestRangeNum = 4096;\n\n  std::mt19937 mt;  // Fix seeds to be able to reproduce the result.\n  std::uniform_int_distribution<uint16_t> distribution(1, 512);\n\n  std::vector<uint32_t> range{distribution(mt)};\n  for (size_t i = 1; i < kTestRangeNum * 2; ++i) {\n    range.push_back((range.back() - 1) + distribution(mt));\n  }\n\n  SparseBitSet bitset(range.data(), range.size() / 2);\n\n  uint32_t ch = 0;\n  for (size_t i = 0; i < range.size() / 2; ++i) {\n    uint32_t start = range[i * 2];\n    uint32_t end = range[i * 2 + 1];\n\n    for (; ch < start; ch++) {\n      ASSERT_FALSE(bitset.get(ch)) << std::hex << ch;\n    }\n    for (; ch < end; ch++) {\n      ASSERT_TRUE(bitset.get(ch)) << std::hex << ch;\n    }\n  }\n  for (; ch < 0x1FFFFFF; ++ch) {\n    ASSERT_FALSE(bitset.get(ch)) << std::hex << ch;\n  }\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/UnicodeUtils.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <log/log.h>\n#include <unicode/utf.h>\n#include <unicode/utf8.h>\n#include <cstdlib>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace minikin {\n\n// src is of the form \"U+1F431 | 'h' 'i'\". Position of \"|\" gets saved to offset\n// if non-null. Size is returned in an out parameter because gtest needs a void\n// return for ASSERT to work.\nvoid ParseUnicode(uint16_t* buf,\n                  size_t buf_size,\n                  const char* src,\n                  size_t* result_size,\n                  size_t* offset) {\n  size_t input_ix = 0;\n  size_t output_ix = 0;\n  bool seen_offset = false;\n\n  while (src[input_ix] != 0) {\n    switch (src[input_ix]) {\n      case '\\'':\n        // single ASCII char\n        LOG_ALWAYS_FATAL_IF(static_cast<uint8_t>(src[input_ix]) >= 0x80);\n        input_ix++;\n        LOG_ALWAYS_FATAL_IF(src[input_ix] == 0);\n        LOG_ALWAYS_FATAL_IF(output_ix >= buf_size);\n        buf[output_ix++] = (uint16_t)src[input_ix++];\n        LOG_ALWAYS_FATAL_IF(src[input_ix] != '\\'');\n        input_ix++;\n        break;\n      case 'u':\n      case 'U': {\n        // Unicode codepoint in hex syntax\n        input_ix++;\n        LOG_ALWAYS_FATAL_IF(src[input_ix] != '+');\n        input_ix++;\n        char* endptr = (char*)src + input_ix;\n        unsigned long int codepoint = strtoul(src + input_ix, &endptr, 16);\n        size_t num_hex_digits = endptr - (src + input_ix);\n\n        // also triggers on invalid number syntax, digits = 0\n        LOG_ALWAYS_FATAL_IF(num_hex_digits < 4u);\n        LOG_ALWAYS_FATAL_IF(num_hex_digits > 6u);\n        LOG_ALWAYS_FATAL_IF(codepoint > 0x10FFFFu);\n        input_ix += num_hex_digits;\n        if (U16_LENGTH(codepoint) == 1) {\n          LOG_ALWAYS_FATAL_IF(output_ix + 1 > buf_size);\n          buf[output_ix++] = codepoint;\n        } else {\n          // UTF-16 encoding\n          LOG_ALWAYS_FATAL_IF(output_ix + 2 > buf_size);\n          buf[output_ix++] = U16_LEAD(codepoint);\n          buf[output_ix++] = U16_TRAIL(codepoint);\n        }\n        break;\n      }\n      case ' ':\n        input_ix++;\n        break;\n      case '|':\n        LOG_ALWAYS_FATAL_IF(seen_offset);\n        LOG_ALWAYS_FATAL_IF(offset == nullptr);\n        *offset = output_ix;\n        seen_offset = true;\n        input_ix++;\n        break;\n      default:\n        LOG_ALWAYS_FATAL(\"Unexpected Character\");\n    }\n  }\n  LOG_ALWAYS_FATAL_IF(result_size == nullptr);\n  *result_size = output_ix;\n  LOG_ALWAYS_FATAL_IF(!seen_offset && offset != nullptr);\n}\n\nstd::vector<uint16_t> parseUnicodeStringWithOffset(const std::string& in,\n                                                   size_t* offset) {\n  std::unique_ptr<uint16_t[]> buffer = std::make_unique<uint16_t[]>(in.size());\n  size_t result_size = 0;\n  ParseUnicode(buffer.get(), in.size(), in.c_str(), &result_size, offset);\n  return std::vector<uint16_t>(buffer.get(), buffer.get() + result_size);\n}\n\nstd::vector<uint16_t> parseUnicodeString(const std::string& in) {\n  return parseUnicodeStringWithOffset(in, nullptr);\n}\n\nstd::vector<uint16_t> utf8ToUtf16(const std::string& text) {\n  std::vector<uint16_t> result;\n  int32_t i = 0;\n  const int32_t textLength = static_cast<int32_t>(text.size());\n  uint32_t c = 0;\n  while (i < textLength) {\n    U8_NEXT(text.c_str(), i, textLength, c);\n    if (U16_LENGTH(c) == 1) {\n      result.push_back(c);\n    } else {\n      result.push_back(U16_LEAD(c));\n      result.push_back(U16_TRAIL(c));\n    }\n  }\n  return result;\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/UnicodeUtils.h",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_TESTS_UNICODEUTILS_H_\n#define CLAY_THIRD_PARTY_TXT_TESTS_UNICODEUTILS_H_\n#include <cstdint>\n#include <string>\n#include <vector>\n\nnamespace minikin {\n\nvoid ParseUnicode(uint16_t* buf,\n                  size_t buf_size,\n                  const char* src,\n                  size_t* result_size,\n                  size_t* offset);\n\nstd::vector<uint16_t> parseUnicodeStringWithOffset(const std::string& in,\n                                                   size_t* offset);\nstd::vector<uint16_t> parseUnicodeString(const std::string& in);\n\n// Converts UTF-8 to UTF-16.\nstd::vector<uint16_t> utf8ToUtf16(const std::string& text);\n\n}  // namespace minikin\n#endif  // CLAY_THIRD_PARTY_TXT_TESTS_UNICODEUTILS_H_\n"
  },
  {
    "path": "clay/third_party/txt/tests/UnicodeUtilsTest.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\n#include \"UnicodeUtils.h\"\n\nnamespace minikin {\n\nTEST(UnicodeUtils, parse) {\n  const size_t BUF_SIZE = 256;\n  uint16_t buf[BUF_SIZE];\n  size_t offset;\n  size_t size;\n  ParseUnicode(buf, BUF_SIZE, \"U+000D U+1F431 | 'a'\", &size, &offset);\n  EXPECT_EQ(size, 4u);\n  EXPECT_EQ(offset, 3u);\n  EXPECT_EQ(buf[0], 0x000D);\n  EXPECT_EQ(buf[1], 0xD83D);\n  EXPECT_EQ(buf[2], 0xDC31);\n  EXPECT_EQ(buf[3], 'a');\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/WordBreakerTests.cpp",
    "content": "/*\n * Copyright (C) 2015 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#define LOG_TAG \"Minikin\"\n\n#include <gtest/gtest.h>\n#include <log/log.h>\n\n#include <minikin/WordBreaker.h>\n#include <unicode/locid.h>\n#include <unicode/uclean.h>\n#include <unicode/udata.h>\n#include \"ICUTestBase.h\"\n#include \"UnicodeUtils.h\"\n\n#ifndef NELEM\n#define NELEM(x) ((sizeof(x) / sizeof((x)[0])))\n#endif\n\n#define UTF16(codepoint) U16_LEAD(codepoint), U16_TRAIL(codepoint)\n\nnamespace minikin {\n\ntypedef ICUTestBase WordBreakerTest;\n\nTEST_F(WordBreakerTest, basic) {\n  uint16_t buf[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(6, breaker.next());       // after \"hello \"\n  EXPECT_EQ(0, breaker.wordStart());  // \"hello\"\n  EXPECT_EQ(5, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ(6, breaker.current());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(6, breaker.wordStart());               // \"world\"\n  EXPECT_EQ(11, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ(11, breaker.current());\n}\n\nTEST_F(WordBreakerTest, softHyphen) {\n  uint16_t buf[] = {'h', 'e', 'l', 0x00AD, 'l', 'o',\n                    ' ', 'w', 'o', 'r',    'l', 'd'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(7, breaker.next());       // after \"hel{SOFT HYPHEN}lo \"\n  EXPECT_EQ(0, breaker.wordStart());  // \"hel{SOFT HYPHEN}lo\"\n  EXPECT_EQ(6, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(7, breaker.wordStart());               // \"world\"\n  EXPECT_EQ(12, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\nTEST_F(WordBreakerTest, hardHyphen) {\n  // Hyphens should not allow breaks anymore.\n  uint16_t buf[] = {'s', 'u', 'g', 'a', 'r', '-', 'f', 'r', 'e', 'e'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());\n  EXPECT_EQ(0, breaker.wordStart());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\nTEST_F(WordBreakerTest, postfixAndPrefix) {\n  uint16_t buf[] = {'U', 'S', 0x00A2, ' ', 'J', 'P', 0x00A5};  // US¢ JP¥\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n\n  EXPECT_EQ(4, breaker.next());       // after CENT SIGN\n  EXPECT_EQ(0, breaker.wordStart());  // \"US¢\"\n  EXPECT_EQ(3, breaker.wordEnd());\n\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end of string\n  EXPECT_EQ(4, breaker.wordStart());               // \"JP¥\"\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd());\n}\n\nTEST_F(WordBreakerTest, myanmarKinzi) {\n  uint16_t buf[] = {0x1004, 0x103A, 0x1039, 0x1000,\n                    0x102C};  // NGA, ASAT, VIRAMA, KA, UU\n  WordBreaker breaker;\n  icu::Locale burmese(\"my\");\n  breaker.setLocale(burmese);\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end of string\n  EXPECT_EQ(0, breaker.wordStart());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.wordEnd());\n}\n\nTEST_F(WordBreakerTest, zwjEmojiSequences) {\n  uint16_t buf[] = {\n      // man + zwj + heart + zwj + man\n      UTF16(0x1F468),\n      0x200D,\n      0x2764,\n      0x200D,\n      UTF16(0x1F468),\n      // woman + zwj + heart + zwj + kiss mark + zwj + woman\n      UTF16(0x1F469),\n      0x200D,\n      0x2764,\n      0x200D,\n      UTF16(0x1F48B),\n      0x200D,\n      UTF16(0x1F469),\n      // eye + zwj + left speech bubble\n      UTF16(0x1F441),\n      0x200D,\n      UTF16(0x1F5E8),\n      // CAT FACE + zwj + BUST IN SILHOUETTE\n      UTF16(0x1F431),\n      0x200D,\n      UTF16(0x1F464),\n  };\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(7, breaker.next());  // after man + zwj + heart + zwj + man\n  EXPECT_EQ(0, breaker.wordStart());\n  EXPECT_EQ(7, breaker.wordEnd());\n  EXPECT_EQ(17, breaker.next());  // after woman + zwj + heart + zwj + woman\n  EXPECT_EQ(7, breaker.wordStart());\n  EXPECT_EQ(17, breaker.wordEnd());\n  EXPECT_EQ(22, breaker.next());  // after eye + zwj + left speech bubble\n  EXPECT_EQ(17, breaker.wordStart());\n  EXPECT_EQ(22, breaker.wordEnd());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(22, breaker.wordStart());\n  EXPECT_EQ(27, breaker.wordEnd());\n}\n\nTEST_F(WordBreakerTest, emojiWithModifier) {\n  uint16_t buf[] = {\n      UTF16(0x1F466), UTF16(0x1F3FB),  // boy + type 1-2 fitzpatrick modifier\n      0x270C, 0xFE0F,\n      UTF16(\n          0x1F3FF)  // victory hand + emoji style + type 6 fitzpatrick modifier\n  };\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(4, breaker.next());  // after boy + type 1-2 fitzpatrick modifier\n  EXPECT_EQ(0, breaker.wordStart());\n  EXPECT_EQ(4, breaker.wordEnd());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(4, breaker.wordStart());\n  EXPECT_EQ(8, breaker.wordEnd());\n}\n\nTEST_F(WordBreakerTest, unicode10Emoji) {\n  // Should break between emojis.\n  uint16_t buf[] = {\n      // SLED + SLED\n      UTF16(0x1F6F7),\n      UTF16(0x1F6F7),\n      // SLED + VS15 + SLED\n      UTF16(0x1F6F7),\n      0xFE0E,\n      UTF16(0x1F6F7),\n      // WHITE SMILING FACE + SLED\n      0x263A,\n      UTF16(0x1F6F7),\n      // WHITE SMILING FACE + VS16 + SLED\n      0x263A,\n      0xFE0F,\n      UTF16(0x1F6F7),\n  };\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getEnglish());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(2, breaker.next());\n  EXPECT_EQ(0, breaker.wordStart());\n  EXPECT_EQ(2, breaker.wordEnd());\n\n  EXPECT_EQ(4, breaker.next());\n  EXPECT_EQ(2, breaker.wordStart());\n  EXPECT_EQ(4, breaker.wordEnd());\n\n  EXPECT_EQ(7, breaker.next());\n  EXPECT_EQ(4, breaker.wordStart());\n  EXPECT_EQ(7, breaker.wordEnd());\n\n  EXPECT_EQ(9, breaker.next());\n  EXPECT_EQ(7, breaker.wordStart());\n  EXPECT_EQ(9, breaker.wordEnd());\n\n  EXPECT_EQ(10, breaker.next());\n  EXPECT_EQ(9, breaker.wordStart());\n  EXPECT_EQ(10, breaker.wordEnd());\n\n  EXPECT_EQ(12, breaker.next());\n  EXPECT_EQ(10, breaker.wordStart());\n  EXPECT_EQ(12, breaker.wordEnd());\n\n  EXPECT_EQ(14, breaker.next());\n  EXPECT_EQ(12, breaker.wordStart());\n  EXPECT_EQ(14, breaker.wordEnd());\n\n  EXPECT_EQ(16, breaker.next());\n  EXPECT_EQ(14, breaker.wordStart());\n  EXPECT_EQ(16, breaker.wordEnd());\n}\n\nTEST_F(WordBreakerTest, flagsSequenceSingleFlag) {\n  const std::string kFlag = \"U+1F3F4\";\n  const std::string flags = kFlag + \" \" + kFlag;\n\n  const int kFlagLength = 2;\n  const size_t BUF_SIZE = kFlagLength * 2;\n\n  uint16_t buf[BUF_SIZE];\n  size_t size;\n  ParseUnicode(buf, BUF_SIZE, flags.c_str(), &size, nullptr);\n\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, size);\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(kFlagLength, breaker.next());  // end of the first flag\n  EXPECT_EQ(0, breaker.wordStart());\n  EXPECT_EQ(kFlagLength, breaker.wordEnd());\n  EXPECT_EQ(static_cast<ssize_t>(size), breaker.next());\n  EXPECT_EQ(kFlagLength, breaker.wordStart());\n  EXPECT_EQ(kFlagLength * 2, breaker.wordEnd());\n}\n\nTEST_F(WordBreakerTest, flagsSequence) {\n  // U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F is emoji tag\n  // sequence for the flag of Scotland.\n  const std::string kFlagSequence =\n      \"U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F\";\n  const std::string flagSequence = kFlagSequence + \" \" + kFlagSequence;\n\n  const int kFlagLength = 14;\n  const size_t BUF_SIZE = kFlagLength * 2;\n\n  uint16_t buf[BUF_SIZE];\n  size_t size;\n  ParseUnicode(buf, BUF_SIZE, flagSequence.c_str(), &size, nullptr);\n\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, size);\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(kFlagLength, breaker.next());  // end of the first flag sequence\n  EXPECT_EQ(0, breaker.wordStart());\n  EXPECT_EQ(kFlagLength, breaker.wordEnd());\n  EXPECT_EQ(static_cast<ssize_t>(size), breaker.next());\n  EXPECT_EQ(kFlagLength, breaker.wordStart());\n  EXPECT_EQ(kFlagLength * 2, breaker.wordEnd());\n}\n\nTEST_F(WordBreakerTest, punct) {\n  uint16_t buf[] = {0x00A1, 0x00A1, 'h', 'e', 'l', 'l', 'o', ',',\n                    ' ',    'w',    'o', 'r', 'l', 'd', '!', '!'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(9, breaker.next());       // after \"¡¡hello, \"\n  EXPECT_EQ(2, breaker.wordStart());  // \"hello\"\n  EXPECT_EQ(7, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(9, breaker.wordStart());               // \"world\"\n  EXPECT_EQ(14, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\nTEST_F(WordBreakerTest, email) {\n  uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm', 'p',\n                    'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(11, breaker.next());  // after \"foo@example\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(16, breaker.next());  // after \".com \"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(16, breaker.wordStart());              // \"x\"\n  EXPECT_EQ(17, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\nTEST_F(WordBreakerTest, mailto) {\n  uint16_t buf[] = {'m', 'a', 'i', 'l', 't', 'o', ':', 'f', 'o', 'o', '@', 'e',\n                    'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(7, breaker.next());  // after \"mailto:\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(18, breaker.next());  // after \"foo@example\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(23, breaker.next());  // after \".com \"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(23, breaker.wordStart());              // \"x\"\n  EXPECT_EQ(24, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\n// The current logic always places a line break after a detected email address\n// or URL and an immediately following non-ASCII character.\nTEST_F(WordBreakerTest, emailNonAscii) {\n  uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a', 'm',\n                    'p', 'l', 'e', '.', 'c', 'o', 'm', 0x4E00};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(11, breaker.next());  // after \"foo@example\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(15, breaker.next());  // after \".com\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(15, breaker.wordStart());              // \"一\"\n  EXPECT_EQ(16, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\nTEST_F(WordBreakerTest, emailCombining) {\n  uint16_t buf[] = {'f', 'o', 'o', '@', 'e', 'x', 'a',    'm', 'p',\n                    'l', 'e', '.', 'c', 'o', 'm', 0x0303, ' ', 'x'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(11, breaker.next());  // after \"foo@example\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(17, breaker.next());  // after \".com̃ \"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(17, breaker.wordStart());              // \"x\"\n  EXPECT_EQ(18, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\nTEST_F(WordBreakerTest, lonelyAt) {\n  uint16_t buf[] = {'a', ' ', '@', ' ', 'b'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(2, breaker.next());       // after \"a \"\n  EXPECT_EQ(0, breaker.wordStart());  // \"a\"\n  EXPECT_EQ(1, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ(4, breaker.next());  // after \"@ \"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(4, breaker.wordStart());               // \"b\"\n  EXPECT_EQ(5, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\nTEST_F(WordBreakerTest, url) {\n  uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'e', 'x', 'a',\n                    'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', ' ', 'x'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(5, breaker.next());  // after \"http:\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(7, breaker.next());  // after \"//\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(14, breaker.next());  // after \"example\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(19, breaker.next());  // after \".com \"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_EQ(19, breaker.wordStart());              // \"x\"\n  EXPECT_EQ(20, breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\n// Breaks according to section 14.12 of Chicago Manual of Style, *URLs or DOIs\n// and line breaks*\nTEST_F(WordBreakerTest, urlBreakChars) {\n  uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '.', 'b', '/',\n                    '~', 'c', ',', 'd', '-', 'e', '?', 'f', '=', 'g', '&',\n                    'h', '#', 'i', '%', 'j', '_', 'k', '/', 'l'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(5, breaker.next());  // after \"http:\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(7, breaker.next());  // after \"//\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(8, breaker.next());  // after \"a\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(10, breaker.next());  // after \".b\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(11, breaker.next());  // after \"/\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(13, breaker.next());  // after \"~c\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(15, breaker.next());  // after \",d\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(17, breaker.next());  // after \"-e\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(19, breaker.next());  // after \"?f\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(20, breaker.next());  // after \"=\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(21, breaker.next());  // after \"g\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(22, breaker.next());  // after \"&\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(23, breaker.next());  // after \"h\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(25, breaker.next());  // after \"#i\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(27, breaker.next());  // after \"%j\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ(29, breaker.next());  // after \"_k\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(1, breaker.breakBadness());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(0, breaker.breakBadness());\n}\n\nTEST_F(WordBreakerTest, urlNoHyphenBreak) {\n  uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '-', '/', 'b'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(5, breaker.next());  // after \"http:\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(7, breaker.next());  // after \"//\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(8, breaker.next());  // after \"a\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n}\n\nTEST_F(WordBreakerTest, urlEndsWithSlash) {\n  uint16_t buf[] = {'h', 't', 't', 'p', ':', '/', '/', 'a', '/'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ(5, breaker.next());  // after \"http:\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(7, breaker.next());  // after \"//\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ(8, breaker.next());  // after \"a\"\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n}\n\nTEST_F(WordBreakerTest, emailStartsWithSlash) {\n  uint16_t buf[] = {'/', 'a', '@', 'b'};\n  WordBreaker breaker;\n  breaker.setLocale(icu::Locale::getUS());\n  breaker.setText(buf, NELEM(buf));\n  EXPECT_EQ(0, breaker.current());\n  EXPECT_EQ((ssize_t)NELEM(buf), breaker.next());  // end\n  EXPECT_TRUE(breaker.wordStart() >= breaker.wordEnd());\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/fake_provider.h",
    "content": "/*\n * Copyright 2021 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_TESTS_FAKE_PROVIDER_H_\n#define CLAY_THIRD_PARTY_TXT_TESTS_FAKE_PROVIDER_H_\n#include <fuchsia/fonts/cpp/fidl_test_base.h>\n#include <lib/fidl/cpp/binding.h>\n#include <lib/fidl/cpp/interface_handle.h>\n#include <string>\n\n#include \"clay/fml/logging.h\"\n\nnamespace txt {\n\nclass FakeProvider : public fuchsia::fonts::testing::Provider_TestBase {\n public:\n  FakeProvider() : binding_(this) {}\n\n  fidl::InterfaceHandle<fuchsia::fonts::Provider> Bind(\n      async_dispatcher_t* dispatcher) {\n    FML_CHECK(!binding_.is_bound());\n\n    fidl::InterfaceHandle<fuchsia::fonts::Provider> provider;\n    binding_.Bind(provider.NewRequest(), dispatcher);\n\n    return provider;\n  }\n\n  virtual void NotImplemented_(const std::string& name) override {\n    FML_LOG(ERROR) << \"A fidl call for \" << name\n                   << \" on fake_provider is not implemented! This likely means\"\n                      \"that your test will hang.\";\n  }\n\n  void GetFontFamilyInfo(fuchsia::fonts::FamilyName family,\n                         GetFontFamilyInfoCallback callback) override {\n    was_invoked_ = true;\n    callback(fuchsia::fonts::FontFamilyInfo());\n  }\n\n  bool WasInvoked() { return was_invoked_; }\n\n private:\n  fidl::Binding<fuchsia::fonts::Provider> binding_;\n  bool was_invoked_ = false;\n};\n\n}  // namespace txt\n#endif  // CLAY_THIRD_PARTY_TXT_TESTS_FAKE_PROVIDER_H_\n"
  },
  {
    "path": "clay/third_party/txt/tests/font_collection_unittests.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"clay/fml/logging.h\"\n#include \"gtest/gtest.h\"\n#include \"third_party/skia/include/utils/SkCustomTypeface.h\"\n#include \"txt/font_collection_skia.h\"\n#include \"txt_test_utils.h\"\n\nnamespace txt {\n\n// We don't really need a fixture but a class in a namespace is needed for\n// the FRIEND_TEST macro.\nclass FontCollectionTest : public ::testing::Test {};\n\nnamespace {\n// This function does some boilerplate to fill a builder with enough real\n// font-like data. Otherwise, detach won't actually build an SkTypeface.\nvoid PopulateUserTypefaceBoilerplate(SkCustomTypefaceBuilder* builder) {\n  constexpr float upem = 200;\n\n  {\n    SkFontMetrics metrics;\n    metrics.fFlags = 0;\n    metrics.fTop = -200;\n    metrics.fAscent = -150;\n    metrics.fDescent = 50;\n    metrics.fBottom = -75;\n    metrics.fLeading = 10;\n    metrics.fAvgCharWidth = 150;\n    metrics.fMaxCharWidth = 300;\n    metrics.fXMin = -20;\n    metrics.fXMax = 290;\n    metrics.fXHeight = -100;\n    metrics.fCapHeight = 0;\n    metrics.fUnderlineThickness = 5;\n    metrics.fUnderlinePosition = 2;\n    metrics.fStrikeoutThickness = 5;\n    metrics.fStrikeoutPosition = -50;\n    builder->setMetrics(metrics, 1.0f / upem);\n  }\n\n  const SkMatrix scale = SkMatrix::Scale(1.0f / upem, 1.0f / upem);\n  for (SkGlyphID index = 0; index <= 67; ++index) {\n    SkScalar width;\n    width = 100;\n    SkPath path;\n    path.addCircle(50, -50, 75);\n\n    builder->setGlyph(index, width / upem, path.makeTransform(scale));\n  }\n}\n}  // namespace\n\nTEST(FontCollectionTest, CheckSkTypefacesSorting) {\n  // We have to make a real SkTypeface here. Not all the structs from the\n  // SkTypeface headers are fully declared to be able to gmock.\n  // SkCustomTypefaceBuilder is the simplest way to get a simple SkTypeface.\n  SkCustomTypefaceBuilder typefaceBuilder1;\n  typefaceBuilder1.setFontStyle(SkFontStyle(SkFontStyle::kThin_Weight,\n                                            SkFontStyle::kExpanded_Width,\n                                            SkFontStyle::kItalic_Slant));\n  // For the purpose of this test, we need to fill this to make the SkTypeface\n  // build but it doesn't matter. We only care about the SkFontStyle.\n  PopulateUserTypefaceBoilerplate(&typefaceBuilder1);\n  sk_sp<SkTypeface> typeface1{typefaceBuilder1.detach()};\n\n  SkCustomTypefaceBuilder typefaceBuilder2;\n  typefaceBuilder2.setFontStyle(SkFontStyle(SkFontStyle::kLight_Weight,\n                                            SkFontStyle::kNormal_Width,\n                                            SkFontStyle::kUpright_Slant));\n  PopulateUserTypefaceBoilerplate(&typefaceBuilder2);\n  sk_sp<SkTypeface> typeface2{typefaceBuilder2.detach()};\n\n  SkCustomTypefaceBuilder typefaceBuilder3;\n  typefaceBuilder3.setFontStyle(SkFontStyle(SkFontStyle::kNormal_Weight,\n                                            SkFontStyle::kNormal_Width,\n                                            SkFontStyle::kUpright_Slant));\n  PopulateUserTypefaceBoilerplate(&typefaceBuilder3);\n  sk_sp<SkTypeface> typeface3{typefaceBuilder3.detach()};\n\n  SkCustomTypefaceBuilder typefaceBuilder4;\n  typefaceBuilder4.setFontStyle(SkFontStyle(SkFontStyle::kThin_Weight,\n                                            SkFontStyle::kCondensed_Width,\n                                            SkFontStyle::kUpright_Slant));\n  PopulateUserTypefaceBoilerplate(&typefaceBuilder4);\n  sk_sp<SkTypeface> typeface4{typefaceBuilder4.detach()};\n\n  std::vector<sk_sp<SkTypeface>> candidateTypefaces = {typeface1, typeface2,\n                                                       typeface3, typeface4};\n\n  // This sorts the vector in-place.\n  txt::FontCollection::SortSkTypefaces(candidateTypefaces);\n\n  // The second one is first because it's both the most normal width font\n  // with the lightest weight.\n  ASSERT_EQ(candidateTypefaces[0].get(), typeface2.get());\n  // Then the most normal width font with normal weight.\n  ASSERT_EQ(candidateTypefaces[1].get(), typeface3.get());\n  // Then a less normal (condensed) width font.\n  ASSERT_EQ(candidateTypefaces[2].get(), typeface4.get());\n  // All things equal, 4 came before 1 because we arbitrarily chose to make the\n  // narrower font come first.\n  ASSERT_EQ(candidateTypefaces[3].get(), typeface1.get());\n\n  // Double check.\n  ASSERT_EQ(candidateTypefaces[0]->fontStyle().weight(),\n            SkFontStyle::kLight_Weight);\n  ASSERT_EQ(candidateTypefaces[0]->fontStyle().width(),\n            SkFontStyle::kNormal_Width);\n\n  ASSERT_EQ(candidateTypefaces[1]->fontStyle().weight(),\n            SkFontStyle::kNormal_Weight);\n  ASSERT_EQ(candidateTypefaces[1]->fontStyle().width(),\n            SkFontStyle::kNormal_Width);\n\n  ASSERT_EQ(candidateTypefaces[2]->fontStyle().weight(),\n            SkFontStyle::kThin_Weight);\n  ASSERT_EQ(candidateTypefaces[2]->fontStyle().width(),\n            SkFontStyle::kCondensed_Width);\n\n  ASSERT_EQ(candidateTypefaces[3]->fontStyle().weight(),\n            SkFontStyle::kThin_Weight);\n  ASSERT_EQ(candidateTypefaces[3]->fontStyle().width(),\n            SkFontStyle::kExpanded_Width);\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/tests/old/perftests/FontCollection.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include <benchmark/benchmark.h>\n\n#include <memory>\n\n#include <FontTestUtils.h>\n#include <MinikinInternal.h>\n#include <UnicodeUtils.h>\n#include <minikin/FontCollection.h>\n\nnamespace minikin {\n\nconst char* SYSTEM_FONT_PATH = \"/system/fonts/\";\nconst char* SYSTEM_FONT_XML = \"/system/etc/fonts.xml\";\n\nstatic void BM_FontCollection_construct(benchmark::State& state) {\n  std::vector<std::shared_ptr<FontFamily>> families =\n      getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML);\n  while (state.KeepRunning()) {\n    std::make_shared<FontCollection>(families);\n  }\n}\n\nBENCHMARK(BM_FontCollection_construct);\n\nstatic void BM_FontCollection_hasVariationSelector(benchmark::State& state) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));\n\n  uint32_t baseCp = state.range(0);\n  uint32_t vsCp = state.range(1);\n\n  char titleBuffer[64];\n  snprintf(titleBuffer, 64, \"hasVariationSelector U+%04X,U+%04X\", baseCp, vsCp);\n  state.SetLabel(titleBuffer);\n\n  while (state.KeepRunning()) {\n    collection->hasVariationSelector(baseCp, vsCp);\n  }\n}\n\n// TODO: Rewrite with BENCHMARK_CAPTURE for better test name.\nBENCHMARK(BM_FontCollection_hasVariationSelector)\n    ->ArgPair(0x2708, 0xFE0F)\n    ->ArgPair(0x2708, 0xFE0E)\n    ->ArgPair(0x3402, 0xE0100);\n\nstruct ItemizeTestCases {\n  std::string itemizeText;\n  std::string languageTag;\n  std::string labelText;\n} ITEMIZE_TEST_CASES[] = {\n    {\"'A' 'n' 'd' 'r' 'o' 'i' 'd'\", \"en\", \"English\"},\n    {\"U+4E16\", \"zh-Hans\", \"CJK Ideograph\"},\n    {\"U+4E16\", \"zh-Hans,zh-Hant,ja,en,es,pt,fr,de\",\n     \"CJK Ideograph with many language fallback\"},\n    {\"U+3402 U+E0100\", \"ja\", \"CJK Ideograph with variation selector\"},\n    {\"'A' 'n' U+0E1A U+0E31 U+0645 U+062D U+0648\", \"en\",\n     \"Mixture of English, Thai and Arabic\"},\n    {\"U+2708 U+FE0E\", \"en\", \"Emoji with variation selector\"},\n    {\"U+0031 U+FE0F U+20E3\", \"en\", \"KEYCAP\"},\n};\n\nstatic void BM_FontCollection_itemize(benchmark::State& state) {\n  std::shared_ptr<FontCollection> collection(\n      getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));\n\n  size_t testIndex = state.range(0);\n  state.SetLabel(\"Itemize: \" + ITEMIZE_TEST_CASES[testIndex].labelText);\n\n  uint16_t buffer[64];\n  size_t utf16_length = 0;\n  ParseUnicode(buffer, 64, ITEMIZE_TEST_CASES[testIndex].itemizeText.c_str(),\n               &utf16_length, nullptr);\n  std::vector<FontCollection::Run> result;\n  FontStyle style(FontStyle::registerLanguageList(\n      ITEMIZE_TEST_CASES[testIndex].languageTag));\n\n  std::scoped_lock _l(gMinikinLock);\n  while (state.KeepRunning()) {\n    result.clear();\n    collection->itemize(buffer, utf16_length, style, &result);\n  }\n}\n\n// TODO: Rewrite with BENCHMARK_CAPTURE once it is available in Android.\nBENCHMARK(BM_FontCollection_itemize)\n    ->Arg(0)\n    ->Arg(1)\n    ->Arg(2)\n    ->Arg(3)\n    ->Arg(4)\n    ->Arg(5)\n    ->Arg(6);\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/old/perftests/FontFamily.cpp",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include <benchmark/benchmark.h>\n\n#include <minikin/FontFamily.h>\n#include \"../util/MinikinFontForTest.h\"\n\nnamespace minikin {\n\nstatic void BM_FontFamily_create(benchmark::State& state) {\n  std::shared_ptr<MinikinFontForTest> minikinFont =\n      std::make_shared<MinikinFontForTest>(\n          \"/system/fonts/NotoSansCJK-Regular.ttc\", 0);\n\n  while (state.KeepRunning()) {\n    std::shared_ptr<FontFamily> family = std::make_shared<FontFamily>(\n        std::vector<Font>({Font(minikinFont, FontStyle())}));\n  }\n}\n\nBENCHMARK(BM_FontFamily_create);\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/old/perftests/FontLanguage.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include <benchmark/benchmark.h>\n\n#include \"FontLanguage.h\"\n\nnamespace minikin {\n\nstatic void BM_FontLanguage_en_US(benchmark::State& state) {\n  while (state.KeepRunning()) {\n    FontLanguage language(\"en-US\", 5);\n  }\n}\nBENCHMARK(BM_FontLanguage_en_US);\n\nstatic void BM_FontLanguage_en_Latn_US(benchmark::State& state) {\n  while (state.KeepRunning()) {\n    FontLanguage language(\"en-Latn-US\", 10);\n  }\n}\nBENCHMARK(BM_FontLanguage_en_Latn_US);\n\nstatic void BM_FontLanguage_en_Latn_US_u_em_emoji(benchmark::State& state) {\n  while (state.KeepRunning()) {\n    FontLanguage language(\"en-Latn-US-u-em-emoji\", 21);\n  }\n}\nBENCHMARK(BM_FontLanguage_en_Latn_US_u_em_emoji);\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/old/perftests/GraphemeBreak.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include <benchmark/benchmark.h>\n\n#include <cutils/log.h>\n\n#include \"UnicodeUtils.h\"\n#include \"minikin/GraphemeBreak.h\"\n\nnamespace minikin {\n\nconst char* ASCII_TEST_STR = \"'L' 'o' 'r' 'e' 'm' ' ' 'i' 'p' 's' 'u' 'm' '.'\";\n// U+261D: WHITE UP POINTING INDEX\n// U+1F3FD: EMOJI MODIFIER FITZPATRICK TYPE-4\nconst char* EMOJI_TEST_STR =\n    \"U+261D U+1F3FD U+261D U+1F3FD U+261D U+1F3FD U+261D U+1F3FD\";\n// U+1F1FA: REGIONAL INDICATOR SYMBOL LETTER U\n// U+1F1F8: REGIONAL INDICATOR SYMBOL LETTER S\nconst char* FLAGS_TEST_STR = \"U+1F1FA U+1F1F8 U+1F1FA U+1F1F8 U+1F1FA U+1F1F8\";\n\n// TODO: Migrate BENCHMARK_CAPTURE for parameterizing.\nstatic void BM_GraphemeBreak_Ascii(benchmark::State& state) {\n  size_t result_size;\n  uint16_t buffer[12];\n  ParseUnicode(buffer, 12, ASCII_TEST_STR, &result_size, nullptr);\n  LOG_ALWAYS_FATAL_IF(result_size != 12);\n  const size_t testIndex = state.range(0);\n  while (state.KeepRunning()) {\n    GraphemeBreak::isGraphemeBreak(nullptr, buffer, 0, result_size, testIndex);\n  }\n}\nBENCHMARK(BM_GraphemeBreak_Ascii)\n    ->Arg(0)    // Beginning of the text.\n    ->Arg(1)    // Middle of the text.\n    ->Arg(12);  // End of the text.\n\nstatic void BM_GraphemeBreak_Emoji(benchmark::State& state) {\n  size_t result_size;\n  uint16_t buffer[12];\n  ParseUnicode(buffer, 12, EMOJI_TEST_STR, &result_size, nullptr);\n  LOG_ALWAYS_FATAL_IF(result_size != 12);\n  const size_t testIndex = state.range(0);\n  while (state.KeepRunning()) {\n    GraphemeBreak::isGraphemeBreak(nullptr, buffer, 0, result_size, testIndex);\n  }\n}\nBENCHMARK(BM_GraphemeBreak_Emoji)\n    ->Arg(1)   // Middle of emoji modifier sequence.\n    ->Arg(2)   // Middle of the surrogate pairs.\n    ->Arg(3);  // After emoji modifier sequence. Here is boundary of grapheme\n               // cluster.\n\nstatic void BM_GraphemeBreak_Emoji_Flags(benchmark::State& state) {\n  size_t result_size;\n  uint16_t buffer[12];\n  ParseUnicode(buffer, 12, FLAGS_TEST_STR, &result_size, nullptr);\n  LOG_ALWAYS_FATAL_IF(result_size != 12);\n  const size_t testIndex = state.range(0);\n  while (state.KeepRunning()) {\n    GraphemeBreak::isGraphemeBreak(nullptr, buffer, 0, result_size, testIndex);\n  }\n}\nBENCHMARK(BM_GraphemeBreak_Emoji_Flags)\n    ->Arg(2)    // Middle of flag sequence.\n    ->Arg(4)    // After flag sequence. Here is boundary of grapheme cluster.\n    ->Arg(10);  // Middle of 3rd flag sequence.\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/old/perftests/Hyphenator.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include <benchmark/benchmark.h>\n\n#include <FileUtils.h>\n#include <UnicodeUtils.h>\n#include <minikin/Hyphenator.h>\n\nnamespace minikin {\n\nconst char* enUsHyph = \"/system/usr/hyphen-data/hyph-en-us.hyb\";\nconst int enUsMinPrefix = 2;\nconst int enUsMinSuffix = 3;\nconst icu::Locale& usLocale = icu::Locale::getUS();\n\nstatic void BM_Hyphenator_short_word(benchmark::State& state) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(\n      readWholeFile(enUsHyph).data(), enUsMinPrefix, enUsMinSuffix);\n  std::vector<uint16_t> word = utf8ToUtf16(\"hyphen\");\n  std::vector<HyphenationType> result;\n  while (state.KeepRunning()) {\n    hyphenator->hyphenate(&result, word.data(), word.size(), usLocale);\n  }\n  Hyphenator::loadBinary(nullptr, 2, 2);\n}\n\n// TODO: Use BENCHMARK_CAPTURE for parametrise.\nBENCHMARK(BM_Hyphenator_short_word);\n\nstatic void BM_Hyphenator_long_word(benchmark::State& state) {\n  Hyphenator* hyphenator = Hyphenator::loadBinary(\n      readWholeFile(enUsHyph).data(), enUsMinPrefix, enUsMinSuffix);\n  std::vector<uint16_t> word =\n      utf8ToUtf16(\"Pneumonoultramicroscopicsilicovolcanoconiosis\");\n  std::vector<HyphenationType> result;\n  while (state.KeepRunning()) {\n    hyphenator->hyphenate(&result, word.data(), word.size(), usLocale);\n  }\n  Hyphenator::loadBinary(nullptr, 2, 2);\n}\n\n// TODO: Use BENCHMARK_CAPTURE for parametrise.\nBENCHMARK(BM_Hyphenator_long_word);\n\n// TODO: Add more tests for other languages.\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/old/perftests/WordBreaker.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include <benchmark/benchmark.h>\n\n#include \"UnicodeUtils.h\"\n#include \"minikin/WordBreaker.h\"\n\nnamespace minikin {\n\nstatic void BM_WordBreaker_English(benchmark::State& state) {\n  const char* kLoremIpsum =\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do \"\n      \"eiusmod tempor incididunt ut labore et dolore magna aliqua.\";\n\n  WordBreaker wb;\n  wb.setLocale(icu::Locale::getEnglish());\n  std::vector<uint16_t> text = utf8ToUtf16(kLoremIpsum);\n  while (state.KeepRunning()) {\n    wb.setText(text.data(), text.size());\n    while (wb.next() != -1) {\n    }\n  }\n}\nBENCHMARK(BM_WordBreaker_English);\n\n// TODO: Add more tests for other languages.\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/old/perftests/main.cpp",
    "content": "/*\n * Copyright (C) 2016 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#include <benchmark/benchmark.h>\n\n#include <cutils/log.h>\n\n#include <unicode/uclean.h>\n#include <unicode/udata.h>\n\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <sys/stat.h>\n\nint main(int argc, char** argv) {\n  const char* fn = \"/system/usr/icu/\" U_ICUDATA_NAME \".dat\";\n  int fd = open(fn, O_RDONLY);\n  LOG_ALWAYS_FATAL_IF(fd == -1);\n  struct stat st;\n  LOG_ALWAYS_FATAL_IF(fstat(fd, &st) != 0);\n  void* data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);\n\n  UErrorCode errorCode = U_ZERO_ERROR;\n  udata_setCommonData(data, &errorCode);\n  LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode));\n  u_init(&errorCode);\n  LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode));\n\n  benchmark::Initialize(&argc, argv);\n  benchmark::RunSpecifiedBenchmarks();\n\n  u_cleanup();\n  return 0;\n}\n"
  },
  {
    "path": "clay/third_party/txt/tests/old/stresstest/MultithreadTest.cpp",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <gtest/gtest.h>\n\n#include <condition_variable>\n#include <mutex>\n#include <random>\n#include <thread>\n\n#include <cutils/log.h>\n\n#include \"../util/FontTestUtils.h\"\n#include \"MinikinInternal.h\"\n#include \"minikin/FontCollection.h\"\n#include \"minikin/Layout.h\"\n\nnamespace minikin {\n\nconst char* SYSTEM_FONT_PATH = \"/system/fonts/\";\nconst char* SYSTEM_FONT_XML = \"/system/etc/fonts.xml\";\n\nconstexpr int LAYOUT_COUNT_PER_COLLECTION = 500;\nconstexpr int COLLECTION_COUNT_PER_THREAD = 15;\nconstexpr int NUM_THREADS = 10;\n\nstd::mutex gMutex;\nstd::condition_variable gCv;\nbool gReady = false;\n\nstatic std::vector<uint16_t> generateTestText(std::mt19937* mt,\n                                              int lettersInWord,\n                                              int wordsInText) {\n  std::uniform_int_distribution<uint16_t> dist('A', 'Z');\n\n  std::vector<uint16_t> text;\n  text.reserve((lettersInWord + 1) * wordsInText - 1);\n  for (int i = 0; i < wordsInText; ++i) {\n    if (i != 0) {\n      text.emplace_back(' ');\n    }\n    for (int j = 0; j < lettersInWord; ++j) {\n      text.emplace_back(dist(*mt));\n    }\n  }\n  return text;\n}\n\nstatic void thread_main(int tid) {\n  {\n    // Wait until all threads are created.\n    std::unique_lock<std::mutex> lock(gMutex);\n    gCv.wait(lock, [] { return gReady; });\n  }\n\n  std::mt19937 mt(tid);\n  MinikinPaint paint;\n\n  for (int i = 0; i < COLLECTION_COUNT_PER_THREAD; ++i) {\n    std::shared_ptr<FontCollection> collection(\n        getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML));\n\n    for (int j = 0; j < LAYOUT_COUNT_PER_COLLECTION; ++j) {\n      // Generates 10 of 3-letter words so that the word sometimes hit the\n      // cache.\n      Layout layout;\n      std::vector<uint16_t> text = generateTestText(&mt, 3, 10);\n      layout.doLayout(text.data(), 0, text.size(), text.size(), kBidi_LTR,\n                      FontStyle(), paint, collection);\n      std::vector<float> advances(text.size());\n      layout.getAdvances(advances.data());\n      for (size_t k = 0; k < advances.size(); ++k) {\n        // MinikinFontForTest always returns 10.0f for horizontal advance.\n        LOG_ALWAYS_FATAL_IF(advances[k] != 10.0f,\n                            \"Memory corruption detected.\");\n      }\n    }\n  }\n}\n\nTEST(MultithreadTest, ThreadSafeStressTest) {\n  std::vector<std::thread> threads;\n\n  {\n    std::unique_lock<std::mutex> lock(gMutex);\n    threads.reserve(NUM_THREADS);\n    for (int i = 0; i < NUM_THREADS; ++i) {\n      threads.emplace_back(&thread_main, i);\n    }\n    gReady = true;\n  }\n  gCv.notify_all();\n\n  for (auto& thread : threads) {\n    thread.join();\n  }\n}\n\n}  // namespace minikin\n"
  },
  {
    "path": "clay/third_party/txt/tests/paragraph_unittests.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cstring>\n#include <iostream>\n\n#include \"clay/fml/logging.h\"\n#include \"render_test.h\"\n#include \"third_party/icu/source/common/unicode/unistr.h\"\n#include \"third_party/skia/include/core/SkColor.h\"\n#include \"third_party/skia/include/core/SkPath.h\"\n#include \"txt/font_style.h\"\n#include \"txt/font_weight.h\"\n#include \"txt/paragraph_builder_txt.h\"\n#include \"txt/paragraph_txt.h\"\n#include \"txt/placeholder_run.h\"\n#include \"txt_test_utils.h\"\n\n#define DISABLE_ON_WINDOWS(TEST) DISABLE_TEST_WINDOWS(TEST)\n#define DISABLE_ON_MAC(TEST) DISABLE_TEST_MAC(TEST)\n\nnamespace txt {\n\nusing ParagraphTest = RenderTest;\n\nTEST_F(ParagraphTest, SimpleParagraph) {\n  const char* text = \"Hello World Text Dialog\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  // We must supply a font here, as the default is Arial, and we do not\n  // include Arial in our test fonts as it is proprietary. We want it to\n  // be Arial default though as it is one of the most common fonts on host\n  // platforms. On real devices/apps, Arial should be able to be resolved.\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, SimpleParagraphSmall) {\n  const char* text =\n      \"Hello World Text Dialog. This is a very small text in order to check \"\n      \"for constant advance additions that are only visible when the advance \"\n      \"of the glyphs are small.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_size = 6;\n  // We must supply a font here, as the default is Arial, and we do not\n  // include Arial in our test fonts as it is proprietary. We want it to\n  // be Arial default though as it is one of the most common fonts on host\n  // platforms. On real devices/apps, Arial should be able to be resolved.\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_TRUE(Snapshot());\n}\n\n// It is possible for the line_metrics_ vector in paragraph to have an empty\n// line at the end as a result of the line breaking algorithm. This causes\n// the final_line_count_ to be one less than line metrics. This tests that we\n// properly handle this case and do not segfault.\nTEST_F(ParagraphTest, GetGlyphPositionAtCoordinateSegfault) {\n  const char* text = \"Hello World\\nText Dialog\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  // We must supply a font here, as the default is Arial, and we do not\n  // include Arial in our test fonts as it is proprietary. We want it to\n  // be Arial default though as it is one of the most common fonts on host\n  // platforms. On real devices/apps, Arial should be able to be resolved.\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_EQ(paragraph->final_line_count_, paragraph->line_metrics_.size());\n  ASSERT_EQ(paragraph->final_line_count_, 2ull);\n  ASSERT_EQ(paragraph->GetLineCount(), 2ull);\n\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0.2, 0.2).position, 0ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(20.2, 0.2).position, 3ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0.2, 20.2).position, 12ull);\n\n  // We artificially reproduce the conditions that cause segfaults in very\n  // specific circumstances in the wild. By adding this empty un-laid-out\n  // LineMetrics at the end, we force the case where final_line_count_\n  // represents the true number of lines whereas line_metrics_ has one\n  // extra empty one.\n  paragraph->line_metrics_.emplace_back(23, 24, 24, 24, true);\n\n  ASSERT_EQ(paragraph->final_line_count_, paragraph->line_metrics_.size() - 1);\n  ASSERT_EQ(paragraph->final_line_count_, 2ull);\n  ASSERT_EQ(paragraph->GetLineCount(), 2ull);\n\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0.2, 20.2).position, 12ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0.2, 0.2).position, 0ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(20.2, 0.2).position, 3ull);\n\n  paragraph->line_metrics_.emplace_back(24, 25, 25, 25, true);\n\n  ASSERT_EQ(paragraph->final_line_count_, paragraph->line_metrics_.size() - 2);\n  ASSERT_EQ(paragraph->final_line_count_, 2ull);\n  ASSERT_EQ(paragraph->GetLineCount(), 2ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\n// Check that GetGlyphPositionAtCoordinate computes correct text positions for\n// a paragraph containing multiple styled runs.\nTEST_F(ParagraphTest, GetGlyphPositionAtCoordinateMultiRun) {\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Ahem\");\n  text_style.color = SK_ColorBLACK;\n  text_style.font_size = 10;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"A\");\n  text_style.font_size = 20;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"B\");\n  text_style.font_size = 30;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"C\");\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(2.0, 5.0).position, 0ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(12.0, 5.0).position, 1ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(32.0, 5.0).position, 2ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, LineMetricsParagraph1) {\n  const char* text = \"Hello! What is going on?\\nSecond line \\nthirdline\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  // We must supply a font here, as the default is Arial, and we do not\n  // include Arial in our test fonts as it is proprietary. We want it to\n  // be Arial default though as it is one of the most common fonts on host\n  // platforms. On real devices/apps, Arial should be able to be resolved.\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n\n  ASSERT_EQ(paragraph->GetLineMetrics().size(), 3ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].start_index, 0ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].end_index, 24ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].end_including_newline, 25ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].end_excluding_whitespace, 24ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].hard_break, true);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].ascent, 12.988281);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].descent, 3.4179688);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].width, 149.72266);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].left, 0.0);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].baseline, 12.582031);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].line_number, 0ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].run_metrics.size(), 1ull);\n  ASSERT_EQ(\n      paragraph->GetLineMetrics()[0]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index)\n          ->second.text_style->color,\n      SK_ColorBLACK);\n  ASSERT_EQ(\n      paragraph->GetLineMetrics()[0]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index)\n          ->second.text_style->font_families,\n      std::vector<std::string>(1, \"Roboto\"));\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[0]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index)\n          ->second.font_metrics.fAscent,\n      -12.988281);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[0]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index)\n          ->second.font_metrics.fDescent,\n      3.4179688);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[0]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index)\n          ->second.font_metrics.fXHeight,\n      7.3964844);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[0]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index)\n          ->second.font_metrics.fLeading,\n      0);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[0]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index)\n          ->second.font_metrics.fTop,\n      -14.786133);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[0]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[0].start_index)\n          ->second.font_metrics.fUnderlinePosition,\n      1.0253906);\n\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].start_index, 25ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].end_index, 37ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].end_including_newline, 38ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].end_excluding_whitespace, 36ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].hard_break, true);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].ascent, 12.988281);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].descent, 3.4179688);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].width, 72.0625);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].left, 0.0);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].baseline, 28.582031);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].line_number, 1ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].run_metrics.size(), 1ull);\n  ASSERT_EQ(\n      paragraph->GetLineMetrics()[1]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index)\n          ->second.text_style->color,\n      SK_ColorBLACK);\n  ASSERT_EQ(\n      paragraph->GetLineMetrics()[1]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index)\n          ->second.text_style->font_families,\n      std::vector<std::string>(1, \"Roboto\"));\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[1]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index)\n          ->second.font_metrics.fAscent,\n      -12.988281);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[1]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index)\n          ->second.font_metrics.fDescent,\n      3.4179688);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[1]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index)\n          ->second.font_metrics.fXHeight,\n      7.3964844);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[1]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index)\n          ->second.font_metrics.fLeading,\n      0);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[1]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index)\n          ->second.font_metrics.fTop,\n      -14.786133);\n  ASSERT_FLOAT_EQ(\n      paragraph->GetLineMetrics()[1]\n          .run_metrics.lower_bound(paragraph->GetLineMetrics()[1].start_index)\n          ->second.font_metrics.fUnderlinePosition,\n      1.0253906);\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_MAC(LineMetricsParagraph2)) {\n  const char* text = \"test string alphabetic\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string alphabetic(icu_text.getBuffer(),\n                            icu_text.getBuffer() + icu_text.length());\n\n  const char* text2 = \"测试中文日本語한국어\";\n  auto icu_text2 = icu::UnicodeString::fromUTF8(text2);\n  std::u16string cjk(icu_text2.getBuffer(),\n                     icu_text2.getBuffer() + icu_text2.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_families.push_back(\"Noto Sans CJK JP\");\n  text_style.font_size = 27;\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(alphabetic);\n\n  text_style.font_size = 24;\n  builder.PushStyle(text_style);\n  builder.AddText(cjk);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(350);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n\n  ASSERT_EQ(paragraph->GetLineMetrics().size(), 2ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].start_index, 0ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].end_index, 26ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].end_including_newline, 26ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].end_excluding_whitespace, 26ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].hard_break, false);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].ascent, 27.84);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].descent, 7.6799998);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].width, 348.61328);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].left, 0.0);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0].baseline, 28.32);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].line_number, 0ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0].run_metrics.size(), 2ull);\n  // First run\n  ASSERT_EQ(paragraph->GetLineMetrics()[0]\n                .run_metrics.lower_bound(2)\n                ->second.text_style->font_size,\n            27);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0]\n                .run_metrics.lower_bound(2)\n                ->second.text_style->font_families,\n            text_style.font_families);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0]\n                      .run_metrics.lower_bound(2)\n                      ->second.font_metrics.fAscent,\n                  -25.048828);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0]\n                      .run_metrics.lower_bound(2)\n                      ->second.font_metrics.fDescent,\n                  6.5917969);\n\n  ASSERT_EQ(paragraph->GetLineMetrics()[0]\n                .run_metrics.lower_bound(21)\n                ->second.text_style->font_size,\n            27);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0]\n                .run_metrics.lower_bound(21)\n                ->second.text_style->font_families,\n            text_style.font_families);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0]\n                      .run_metrics.lower_bound(21)\n                      ->second.font_metrics.fAscent,\n                  -25.048828);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0]\n                      .run_metrics.lower_bound(21)\n                      ->second.font_metrics.fDescent,\n                  6.5917969);\n\n  // Second run\n  ASSERT_EQ(paragraph->GetLineMetrics()[0]\n                .run_metrics.lower_bound(22)\n                ->second.text_style->font_size,\n            24);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0]\n                .run_metrics.lower_bound(22)\n                ->second.text_style->font_families,\n            text_style.font_families);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0]\n                      .run_metrics.lower_bound(22)\n                      ->second.font_metrics.fAscent,\n                  -27.84);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0]\n                      .run_metrics.lower_bound(22)\n                      ->second.font_metrics.fDescent,\n                  7.6799998);\n\n  ASSERT_EQ(paragraph->GetLineMetrics()[0]\n                .run_metrics.lower_bound(24)\n                ->second.text_style->font_size,\n            24);\n  ASSERT_EQ(paragraph->GetLineMetrics()[0]\n                .run_metrics.lower_bound(24)\n                ->second.text_style->font_families,\n            text_style.font_families);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0]\n                      .run_metrics.lower_bound(24)\n                      ->second.font_metrics.fAscent,\n                  -27.84);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[0]\n                      .run_metrics.lower_bound(24)\n                      ->second.font_metrics.fDescent,\n                  7.6799998);\n\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].start_index, 26ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].end_index, 32ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].end_including_newline, 32ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].end_excluding_whitespace, 32ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].hard_break, true);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].ascent, 27.84);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].descent, 7.6799998);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].width, 138.23438);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].left, 0.0);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1].baseline, 64.32);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].line_number, 1ull);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1].run_metrics.size(), 1ull);\n  // Indexing below the line will just resolve to the first run in the line.\n  ASSERT_EQ(paragraph->GetLineMetrics()[1]\n                .run_metrics.lower_bound(3)\n                ->second.text_style->font_size,\n            24);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1]\n                .run_metrics.lower_bound(3)\n                ->second.text_style->font_families,\n            text_style.font_families);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1]\n                      .run_metrics.lower_bound(3)\n                      ->second.font_metrics.fAscent,\n                  -27.84);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1]\n                      .run_metrics.lower_bound(3)\n                      ->second.font_metrics.fDescent,\n                  7.6799998);\n\n  // Indexing within the line\n  ASSERT_EQ(paragraph->GetLineMetrics()[1]\n                .run_metrics.lower_bound(31)\n                ->second.text_style->font_size,\n            24);\n  ASSERT_EQ(paragraph->GetLineMetrics()[1]\n                .run_metrics.lower_bound(31)\n                ->second.text_style->font_families,\n            text_style.font_families);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1]\n                      .run_metrics.lower_bound(31)\n                      ->second.font_metrics.fAscent,\n                  -27.84);\n  ASSERT_FLOAT_EQ(paragraph->GetLineMetrics()[1]\n                      .run_metrics.lower_bound(31)\n                      ->second.font_metrics.fDescent,\n                  7.6799998);\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderParagraph)) {\n  const char* text = \"012 34\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(50, 50, PlaceholderAlignment::kBaseline,\n                                      TextBaseline::kAlphabetic, 0);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.AddPlaceholder(placeholder_run);\n  txt::PlaceholderRun placeholder_run2(5, 50, PlaceholderAlignment::kBaseline,\n                                       TextBaseline::kAlphabetic, 50);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddText(u16_text);\n  builder.AddPlaceholder(placeholder_run2);\n\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 3, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  // ASSERT_TRUE(Snapshot());\n  EXPECT_EQ(boxes.size(), 1ull);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n\n  paint.setColor(SK_ColorRED);\n  boxes = paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(4, 17, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 7ull);\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[1].rect.top(), 50);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 140.94531);\n  EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 100);\n\n  EXPECT_FLOAT_EQ(boxes[3].rect.left(), 231.39062);\n  EXPECT_FLOAT_EQ(boxes[3].rect.top(), 50);\n  EXPECT_FLOAT_EQ(boxes[3].rect.right(), 231.39062 + 50);\n  EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 100);\n\n  EXPECT_FLOAT_EQ(boxes[4].rect.left(), 281.39062);\n  EXPECT_FLOAT_EQ(boxes[4].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[4].rect.right(), 281.39062 + 5);\n  EXPECT_FLOAT_EQ(boxes[4].rect.bottom(), 50);\n\n  EXPECT_FLOAT_EQ(boxes[6].rect.left(), 336.39062);\n  EXPECT_FLOAT_EQ(boxes[6].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[6].rect.right(), 336.39062 + 5);\n  EXPECT_FLOAT_EQ(boxes[6].rect.bottom(), 50);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderBaselineParagraph)) {\n  const char* text = \"012 34\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(55, 50, PlaceholderAlignment::kBaseline,\n                                      TextBaseline::kAlphabetic, 38.34734);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the box is in the right place\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 145.94531);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 50);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(5, 6, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the other text didn't just shift to accommodate it.\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 75.34375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 14.226246);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 44.694996);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       DISABLE_ON_WINDOWS(InlinePlaceholderAboveBaselineParagraph)) {\n  const char* text = \"012 34\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(55, 50,\n                                      PlaceholderAlignment::kAboveBaseline,\n                                      TextBaseline::kAlphabetic, 903129.129308);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the box is in the right place\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), -0.34765625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 145.94531);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 49.652344);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(5, 6, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the other text didn't just shift to accommodate it.\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 75.34375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 25.53125);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 56);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       DISABLE_ON_WINDOWS(InlinePlaceholderBelowBaselineParagraph)) {\n  const char* text = \"012 34\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(55, 50,\n                                      PlaceholderAlignment::kBelowBaseline,\n                                      TextBaseline::kAlphabetic, 903129.129308);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the box is in the right place\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 24);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 145.94531);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 74);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(5, 6, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the other text didn't just shift to accommodate it.\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 75.34375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), -0.12109375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 30.347656);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderBottomParagraph)) {\n  const char* text = \"012 34\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(55, 50, PlaceholderAlignment::kBottom,\n                                      TextBaseline::kAlphabetic, 0);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the box is in the right place\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 145.94531);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 50);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the other text didn't just shift to accommodate it.\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0.5);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 19.53125);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 16.101562);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 50);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderTopParagraph)) {\n  const char* text = \"012 34\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(55, 50, PlaceholderAlignment::kTop,\n                                      TextBaseline::kAlphabetic, 0);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the box is in the right place\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 145.94531);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 50);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the other text didn't just shift to accommodate it.\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0.5);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 16.101562);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 30.46875);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderMiddleParagraph)) {\n  const char* text = \"012 34\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(55, 50, PlaceholderAlignment::kMiddle,\n                                      TextBaseline::kAlphabetic, 0);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the box is in the right place\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 145.94531);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 50);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(5, 6, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the other text didn't just shift to accommodate it.\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 75.34375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 9.765625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 40.234375);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       DISABLE_ON_MAC(\n           DISABLE_ON_WINDOWS(InlinePlaceholderIdeographicBaselineParagraph))) {\n  const char* text = \"給能上目秘使\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Source Han Serif CN\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(55, 50, PlaceholderAlignment::kBaseline,\n                                      TextBaseline::kIdeographic, 38.34734);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the box is in the right place\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 162.5);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 217.5);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 50);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(5, 6, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  // Verify the other text didn't just shift to accommodate it.\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 135.5);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 4.7033391);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 162.5);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 42.065342);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderBreakParagraph)) {\n  const char* text = \"012 34\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(50, 50, PlaceholderAlignment::kBaseline,\n                                      TextBaseline::kAlphabetic, 50);\n  txt::PlaceholderRun placeholder_run2(25, 25, PlaceholderAlignment::kBaseline,\n                                       TextBaseline::kAlphabetic, 12.5);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddText(u16_text);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddText(u16_text);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 3, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes = paragraph->GetRectsForRange(175, 176, rect_height_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 31.703125);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 218.53125);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 47.304688);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 249);\n\n  paint.setColor(SK_ColorRED);\n  boxes = paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(4, 45, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 30ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 59.742188);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 26.378906);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 56.847656);\n\n  EXPECT_FLOAT_EQ(boxes[11].rect.left(), 606.39062);\n  EXPECT_FLOAT_EQ(boxes[11].rect.top(), 38);\n  EXPECT_FLOAT_EQ(boxes[11].rect.right(), 631.39062);\n  EXPECT_FLOAT_EQ(boxes[11].rect.bottom(), 63);\n\n  EXPECT_FLOAT_EQ(boxes[17].rect.left(), 0.5);\n  EXPECT_FLOAT_EQ(boxes[17].rect.top(), 63.5);\n  EXPECT_FLOAT_EQ(boxes[17].rect.right(), 50.5);\n  EXPECT_FLOAT_EQ(boxes[17].rect.bottom(), 113.5);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderGetRectsParagraph)) {\n  const char* text = \"012 34\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  txt::PlaceholderRun placeholder_run(50, 50, PlaceholderAlignment::kBaseline,\n                                      TextBaseline::kAlphabetic, 50);\n  txt::PlaceholderRun placeholder_run2(5, 20, PlaceholderAlignment::kBaseline,\n                                       TextBaseline::kAlphabetic, 10);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.AddText(u16_text);\n\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run2);\n\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddText(u16_text);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddPlaceholder(placeholder_run2);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 34ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 90.945312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 140.94531);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 50);\n\n  EXPECT_FLOAT_EQ(boxes[16].rect.left(), 800.94531);\n  EXPECT_FLOAT_EQ(boxes[16].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[16].rect.right(), 850.94531);\n  EXPECT_FLOAT_EQ(boxes[16].rect.bottom(), 50);\n\n  EXPECT_FLOAT_EQ(boxes[33].rect.left(), 503.48438);\n  EXPECT_FLOAT_EQ(boxes[33].rect.top(), 160);\n  EXPECT_FLOAT_EQ(boxes[33].rect.right(), 508.48438);\n  EXPECT_FLOAT_EQ(boxes[33].rect.bottom(), 180);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(30, 50, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 8ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 216.10156);\n  // Top should be taller than \"tight\"\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 60);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 290.94531);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 120);\n\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 290.94531);\n  EXPECT_FLOAT_EQ(boxes[1].rect.top(), 60);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 340.94531);\n  EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 120);\n\n  EXPECT_FLOAT_EQ(boxes[2].rect.left(), 340.94531);\n  EXPECT_FLOAT_EQ(boxes[2].rect.top(), 60);\n  EXPECT_FLOAT_EQ(boxes[2].rect.right(), 345.94531);\n  EXPECT_FLOAT_EQ(boxes[2].rect.bottom(), 120);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderLongestLine)) {\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 1;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  txt::PlaceholderRun placeholder_run(50, 50, PlaceholderAlignment::kBaseline,\n                                      TextBaseline::kAlphabetic, 0);\n  builder.AddPlaceholder(placeholder_run);\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  ASSERT_DOUBLE_EQ(paragraph->width_, GetTestCanvasWidth());\n  ASSERT_TRUE(paragraph->longest_line_ < GetTestCanvasWidth());\n  ASSERT_TRUE(paragraph->longest_line_ >= 50);\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholderIntrinsicWidth)) {\n  const char* text = \"A \";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::PlaceholderRun placeholder_run(50, 50, PlaceholderAlignment::kBaseline,\n                                      TextBaseline::kAlphabetic, 0);\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 20;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n  builder.AddPlaceholder(placeholder_run);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  ASSERT_DOUBLE_EQ(paragraph->GetMinIntrinsicWidth(), 50);\n  ASSERT_DOUBLE_EQ(paragraph->GetMaxIntrinsicWidth(), 68);\n}\n\n#if OS_LINUX\n// Tests if manually inserted 0xFFFC characters are replaced to 0xFFFD in order\n// to not interfere with the placeholder box layout.\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(InlinePlaceholder0xFFFCParagraph)) {\n  const char* text = \"ab\\uFFFCcd\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  // Used to generate the replaced version.\n  const char* text2 = \"ab\\uFFFDcd\";\n  auto icu_text2 = icu::UnicodeString::fromUTF8(text2);\n  std::u16string u16_text2(icu_text2.getBuffer(),\n                           icu_text2.getBuffer() + icu_text2.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  std::vector<uint16_t> truth_text;\n\n  builder.AddText(u16_text);\n  truth_text.insert(truth_text.end(), u16_text2.begin(), u16_text2.end());\n  builder.AddText(u16_text);\n  truth_text.insert(truth_text.end(), u16_text2.begin(), u16_text2.end());\n\n  txt::PlaceholderRun placeholder_run(50, 50, PlaceholderAlignment::kBaseline,\n                                      TextBaseline::kAlphabetic, 25);\n  builder.AddPlaceholder(placeholder_run);\n  truth_text.push_back(0xFFFC);\n\n  builder.AddText(u16_text);\n  truth_text.insert(truth_text.end(), u16_text2.begin(), u16_text2.end());\n  builder.AddText(u16_text);\n  truth_text.insert(truth_text.end(), u16_text2.begin(), u16_text2.end());\n\n  builder.AddPlaceholder(placeholder_run);\n  truth_text.push_back(0xFFFC);\n  builder.AddPlaceholder(placeholder_run);\n  truth_text.push_back(0xFFFC);\n  builder.AddText(u16_text);\n  truth_text.insert(truth_text.end(), u16_text2.begin(), u16_text2.end());\n  builder.AddText(u16_text);\n  truth_text.insert(truth_text.end(), u16_text2.begin(), u16_text2.end());\n  builder.AddPlaceholder(placeholder_run);\n  truth_text.push_back(0xFFFC);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  for (size_t i = 0; i < truth_text.size(); ++i) {\n    EXPECT_EQ(paragraph->text_[i], truth_text[i]);\n  }\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  paint.setColor(SK_ColorRED);\n\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForPlaceholders();\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 4ull);\n\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 177.83594);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 227.83594);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 50);\n\n  EXPECT_FLOAT_EQ(boxes[3].rect.left(), 682.50781);\n  EXPECT_FLOAT_EQ(boxes[3].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[3].rect.right(), 732.50781);\n  EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 50);\n\n  ASSERT_TRUE(Snapshot());\n}\n#endif\n\nTEST_F(ParagraphTest, SimpleRedParagraph) {\n  const char* text = \"I am RED\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorRED;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, RainbowParagraph) {\n  const char* text1 = \"Red Roboto\";\n  auto icu_text1 = icu::UnicodeString::fromUTF8(text1);\n  std::u16string u16_text1(icu_text1.getBuffer(),\n                           icu_text1.getBuffer() + icu_text1.length());\n  const char* text2 = \"big Greeen Default\";\n  auto icu_text2 = icu::UnicodeString::fromUTF8(text2);\n  std::u16string u16_text2(icu_text2.getBuffer(),\n                           icu_text2.getBuffer() + icu_text2.length());\n  const char* text3 = \"Defcolor Homemade Apple\";\n  auto icu_text3 = icu::UnicodeString::fromUTF8(text3);\n  std::u16string u16_text3(icu_text3.getBuffer(),\n                           icu_text3.getBuffer() + icu_text3.length());\n  const char* text4 = \"Small Blue Roboto\";\n  auto icu_text4 = icu::UnicodeString::fromUTF8(text4);\n  std::u16string u16_text4(icu_text4.getBuffer(),\n                           icu_text4.getBuffer() + icu_text4.length());\n  const char* text5 =\n      \"Continue Last Style With lots of words to check if it overlaps \"\n      \"properly or not\";\n  auto icu_text5 = icu::UnicodeString::fromUTF8(text5);\n  std::u16string u16_text5(icu_text5.getBuffer(),\n                           icu_text5.getBuffer() + icu_text5.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 2;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style1;\n  text_style1.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style1.color = SK_ColorRED;\n\n  builder.PushStyle(text_style1);\n\n  builder.AddText(u16_text1);\n\n  txt::TextStyle text_style2;\n  text_style2.font_size = 50;\n  text_style2.letter_spacing = 10;\n  text_style2.word_spacing = 30;\n  text_style2.font_weight = txt::FontWeight::w600;\n  text_style2.color = SK_ColorGREEN;\n  text_style2.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style2.decoration = TextDecoration::kUnderline |\n                           TextDecoration::kOverline |\n                           TextDecoration::kLineThrough;\n  text_style2.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style2);\n\n  builder.AddText(u16_text2);\n\n  txt::TextStyle text_style3;\n  text_style3.font_families = std::vector<std::string>(1, \"Homemade Apple\");\n  builder.PushStyle(text_style3);\n\n  builder.AddText(u16_text3);\n\n  txt::TextStyle text_style4;\n  text_style4.font_size = 14;\n  text_style4.color = SK_ColorBLUE;\n  text_style4.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style4.decoration = TextDecoration::kUnderline |\n                           TextDecoration::kOverline |\n                           TextDecoration::kLineThrough;\n  text_style4.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style4);\n\n  builder.AddText(u16_text4);\n\n  // Extra text to see if it goes to default when there is more text chunks than\n  // styles.\n  builder.AddText(u16_text5);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  u16_text1 += u16_text2 + u16_text3 + u16_text4;\n  for (size_t i = 0; i < u16_text1.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text1[i]);\n  }\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 4ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 5ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style1));\n  ASSERT_TRUE(paragraph->runs_.styles_[2].equals(text_style2));\n  ASSERT_TRUE(paragraph->runs_.styles_[3].equals(text_style3));\n  ASSERT_TRUE(paragraph->runs_.styles_[4].equals(text_style4));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style1.color);\n  ASSERT_EQ(paragraph->records_[1].style().color, text_style2.color);\n  ASSERT_EQ(paragraph->records_[2].style().color, text_style3.color);\n  ASSERT_EQ(paragraph->records_[3].style().color, text_style4.color);\n}\n\n// Currently, this should render nothing without a supplied TextStyle.\nTEST_F(ParagraphTest, DefaultStyleParagraph) {\n  const char* text = \"No TextStyle! Uh Oh!\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.font_family = \"Roboto\";\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 1ull);\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, BoldParagraph) {\n  const char* text = \"This is Red max bold text!\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 60;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = txt::FontWeight::w900;\n  text_style.color = SK_ColorRED;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 10.0, 60.0);\n\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_TRUE(Snapshot());\n\n  // width_ takes the full available space, but longest_line_ is only the width\n  // of the text, which is less than one line.\n  ASSERT_DOUBLE_EQ(paragraph->width_, GetTestCanvasWidth());\n  ASSERT_TRUE(paragraph->longest_line_ < paragraph->width_);\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  std::vector<txt::Paragraph::TextBox> boxes = paragraph->GetRectsForRange(\n      0, strlen(text), rect_height_style, rect_width_style);\n  ASSERT_DOUBLE_EQ(paragraph->longest_line_,\n                   boxes[boxes.size() - 1].rect.right() - boxes[0].rect.left());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(HeightOverrideParagraph)) {\n  const char* text = \"01234満毎冠行来昼本可\\nabcd\\n満毎冠行来昼本可\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 20;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 3.6345;\n  text_style.has_height_override = true;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kIncludeLineSpacingMiddle;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 40, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 3ull);\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);\n  EXPECT_NEAR(boxes[1].rect.top(), 92.805778503417969, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 43.851562);\n  EXPECT_NEAR(boxes[1].rect.bottom(), 165.49578857421875, 0.0001);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(HeightOverrideHalfLeadingTextStyle)) {\n  // All 3 lines will have the same typeface.\n  const char* text = \"01234満毎冠行来昼本可\\nabcd\\n満毎冠行来昼本可\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_height_behavior = TextHeightBehavior::kAll;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 20;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 3.6345;\n  text_style.has_height_override = true;\n  // Override paragraph_style.text_height_behavior:\n  text_style.half_leading = true;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_style_max =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 40, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n\n  std::vector<txt::Paragraph::TextBox> line_boxes = paragraph->GetRectsForRange(\n      0, 40, rect_height_style_max, rect_width_style);\n  EXPECT_EQ(boxes.size(), 3ull);\n  EXPECT_EQ(line_boxes.size(), 3ull);\n\n  const double line_spacing1 = boxes[1].rect.top() - boxes[0].rect.bottom();\n  const double line_spacing2 = boxes[2].rect.top() - boxes[1].rect.bottom();\n\n  EXPECT_EQ(line_spacing1, line_spacing2);\n\n  // half leading.\n  EXPECT_EQ(line_boxes[0].rect.top() - boxes[0].rect.top(),\n            boxes[0].rect.bottom() - line_boxes[0].rect.bottom());\n  EXPECT_EQ(line_boxes[1].rect.top() - boxes[1].rect.top(),\n            boxes[1].rect.bottom() - line_boxes[1].rect.bottom());\n  EXPECT_EQ(line_boxes[2].rect.top() - boxes[2].rect.top(),\n            boxes[2].rect.bottom() - line_boxes[2].rect.bottom());\n  // With half-leadding, the x coordinates should remain the same.\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 43.851562);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(MixedTextHeightBehaviorSameLine)) {\n  // Both runs will still have the same typeface, but with different text height\n  // behaviors.\n  const char* text = \"01234満毎冠行来昼本可abcd\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  std::u16string u16_text2(icu_text.getBuffer(),\n                           icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_height_behavior = TextHeightBehavior::kAll;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 20;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 3.6345;\n  text_style.has_height_override = true;\n  // First run, with half-leading.\n  text_style.half_leading = true;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  // Second run with AD-scaling.\n  text_style.half_leading = false;\n\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text2);\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_style_max =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n\n  std::vector<txt::Paragraph::TextBox> boxes = paragraph->GetRectsForRange(\n      0, icu_text.length(), rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n\n  std::vector<txt::Paragraph::TextBox> line_boxes = paragraph->GetRectsForRange(\n      0, icu_text.length(), rect_height_style_max, rect_width_style);\n  // The runs has the same typeface so they should be grouped together.\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_EQ(line_boxes.size(), 1ull);\n\n  const double glyphHeight = boxes[0].rect.height();\n  const double metricsAscent = 18.5546875;\n  const double metricsDescent = 4.8828125;\n  EXPECT_DOUBLE_EQ(glyphHeight, metricsAscent + metricsDescent);\n\n  const double line_height = 3.6345 * 20;\n  const double leading = line_height - glyphHeight;\n\n  // Overall descent is from half-leading and overall ascent is from AD-scaling.\n  EXPECT_NEAR(boxes[0].rect.top() - line_boxes[0].rect.top(),\n              leading * metricsAscent / (metricsAscent + metricsDescent),\n              0.001);\n\n  EXPECT_NEAR(line_boxes[0].rect.bottom() - boxes[0].rect.bottom(),\n              leading * 0.5, 0.001);\n}\n\nTEST_F(ParagraphTest,\n       DISABLE_ON_WINDOWS(MixedTextHeightBehaviorSameLineWithZeroHeight)) {\n  // Both runs will still have the same typeface, but with different text height\n  // behaviors.\n  const char* text = \"01234満毎冠行来昼本可abcd\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_height_behavior = TextHeightBehavior::kAll;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 20;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  // Set height to 0\n  text_style.height = 0;\n  text_style.has_height_override = true;\n  // First run, with half-leading.\n  text_style.half_leading = true;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  // Second run with AD-scaling.\n  text_style.half_leading = false;\n\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_style_max =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n\n  std::vector<txt::Paragraph::TextBox> boxes = paragraph->GetRectsForRange(\n      0, icu_text.length(), rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n\n  std::vector<txt::Paragraph::TextBox> line_boxes = paragraph->GetRectsForRange(\n      0, icu_text.length(), rect_height_style_max, rect_width_style);\n  // The runs has the same typeface so they should be grouped together.\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_EQ(line_boxes.size(), 1ull);\n\n  const double glyphHeight = boxes[0].rect.height();\n  const double metricsAscent = 18.5546875;\n  const double metricsDescent = 4.8828125;\n  EXPECT_DOUBLE_EQ(glyphHeight, metricsAscent + metricsDescent);\n\n  // line_height for both styled runs is 0, but the overall line height is not\n  // 0.\n  EXPECT_DOUBLE_EQ(line_boxes[0].rect.height(),\n                   metricsAscent - (metricsAscent + metricsDescent) / 2);\n  EXPECT_LT(boxes[0].rect.top(), 0.0);\n  EXPECT_GT(boxes[0].rect.bottom(), 0.0);\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(HeightOverrideHalfLeadingStrut)) {\n  // All 3 lines will have the same typeface.\n  const char* text = \"01234満毎冠行来昼本可\\nabcd\\n満毎冠行来昼本可\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.strut_enabled = true;\n  paragraph_style.strut_has_height_override = true;\n  paragraph_style.strut_height = 3.6345;\n  paragraph_style.strut_font_size = 20;\n  paragraph_style.strut_font_families.push_back(\"Roboto\");\n  paragraph_style.strut_half_leading = true;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 20;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 3.6345;\n  text_style.has_height_override = true;\n  text_style.half_leading = true;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_style_max =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 40, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n\n  std::vector<txt::Paragraph::TextBox> line_boxes = paragraph->GetRectsForRange(\n      0, 40, rect_height_style_max, rect_width_style);\n  EXPECT_EQ(boxes.size(), 3ull);\n  EXPECT_EQ(line_boxes.size(), 3ull);\n\n  const double line_spacing1 = boxes[1].rect.top() - boxes[0].rect.bottom();\n  const double line_spacing2 = boxes[2].rect.top() - boxes[1].rect.bottom();\n\n  EXPECT_EQ(line_spacing1, line_spacing2);\n\n  // Strut half leading.\n  EXPECT_EQ(line_boxes[0].rect.top() - boxes[0].rect.top(),\n            boxes[0].rect.bottom() - line_boxes[0].rect.bottom());\n  EXPECT_EQ(line_boxes[1].rect.top() - boxes[1].rect.top(),\n            boxes[1].rect.bottom() - line_boxes[1].rect.bottom());\n  EXPECT_EQ(line_boxes[2].rect.top() - boxes[2].rect.top(),\n            boxes[2].rect.bottom() - line_boxes[2].rect.bottom());\n\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 43.851562);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       DISABLE_ON_WINDOWS(ZeroHeightHalfLeadingStrutForceHeight)) {\n  // All 3 lines will have the same typeface.\n  const char* text = \"01234満毎冠行来昼本可abcdn満毎冠行来昼本可\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.strut_enabled = true;\n  paragraph_style.strut_has_height_override = true;\n  paragraph_style.strut_height = 0;\n  // Force strut height.\n  paragraph_style.force_strut_height = true;\n  paragraph_style.strut_font_size = 20;\n  paragraph_style.strut_font_families.push_back(\"Roboto\");\n  paragraph_style.strut_half_leading = true;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 20;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 0;\n  text_style.has_height_override = true;\n\n  // First run, with half-leading.\n  text_style.half_leading = true;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  // Second run with AD-scaling.\n  text_style.half_leading = false;\n\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_style_max =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n\n  std::vector<txt::Paragraph::TextBox> boxes = paragraph->GetRectsForRange(\n      0, icu_text.length(), rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n\n  std::vector<txt::Paragraph::TextBox> line_boxes = paragraph->GetRectsForRange(\n      0, icu_text.length(), rect_height_style_max, rect_width_style);\n  // The runs has the same typeface so they should be grouped together.\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_EQ(line_boxes.size(), 1ull);\n\n  const double glyphHeight = boxes[0].rect.height();\n  const double metricsAscent = 18.5546875;\n  const double metricsDescent = 4.8828125;\n  EXPECT_DOUBLE_EQ(glyphHeight, metricsAscent + metricsDescent);\n\n  EXPECT_DOUBLE_EQ(line_boxes[0].rect.height(), 0.0);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(LeftAlignParagraph)) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_.size(), paragraph_style.max_lines);\n  double expected_y = 24;\n\n  ASSERT_TRUE(paragraph->records_[0].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[0].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_DOUBLE_EQ(paragraph->records_[0].offset().x(), 0);\n\n  ASSERT_TRUE(paragraph->records_[1].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[1].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_DOUBLE_EQ(paragraph->records_[1].offset().x(), 0);\n\n  ASSERT_TRUE(paragraph->records_[2].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[2].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_DOUBLE_EQ(paragraph->records_[2].offset().x(), 0);\n\n  ASSERT_TRUE(paragraph->records_[3].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[3].offset().y(), expected_y);\n  expected_y += 30 * 10;\n  ASSERT_DOUBLE_EQ(paragraph->records_[3].offset().x(), 0);\n\n  ASSERT_TRUE(paragraph->records_[13].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[13].offset().y(), expected_y);\n  ASSERT_DOUBLE_EQ(paragraph->records_[13].offset().x(), 0);\n\n  ASSERT_EQ(paragraph_style.text_align,\n            paragraph->GetParagraphStyle().text_align);\n\n  // Tests for GetGlyphPositionAtCoordinate()\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0, 0).position, 0ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 1).position, 0ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 35).position, 68ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 70).position, 134ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(2000, 35).position, 134ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(LeftAlignRTLParagraphHitTest)) {\n  // Regression test for https://github.com/flutter/flutter/issues/54969.\n  const char* text = \"بمباركة التقليدية قام عن. تصفح\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 1;\n  paragraph_style.text_align = TextAlign::left;\n  paragraph_style.text_direction = TextDirection::rtl;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n\n  // Tests for GetGlyphPositionAtCoordinate()\n  ASSERT_EQ(\n      paragraph->GetGlyphPositionAtCoordinate(GetTestCanvasWidth() - 0.5, 0.5)\n          .position,\n      0ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(RightAlignParagraph)) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::right;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  int available_width = GetTestCanvasWidth() - 100;\n  paragraph->Layout(available_width);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  // Two records for each due to 'ghost' trailing whitespace run.\n  ASSERT_EQ(paragraph->records_.size(), paragraph_style.max_lines * 2);\n  double expected_y = 24;\n\n  ASSERT_TRUE(paragraph->records_[0].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[0].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_NEAR(paragraph->records_[0].offset().x(),\n              paragraph->width_ -\n                  paragraph->line_widths_[paragraph->records_[0].line()],\n              2.0);\n\n  // width_ takes the full available space, while longest_line_ wraps the glyphs\n  // as tightly as possible. Even though this text is more than one line long,\n  // no line perfectly spans the width of the full line, so longest_line_ is\n  // less than width_.\n  ASSERT_DOUBLE_EQ(paragraph->width_, available_width);\n  ASSERT_TRUE(paragraph->longest_line_ < available_width);\n  ASSERT_DOUBLE_EQ(paragraph->longest_line_, 880.87109375);\n\n  ASSERT_TRUE(paragraph->records_[2].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[2].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_NEAR(paragraph->records_[2].offset().x(),\n              paragraph->width_ -\n                  paragraph->line_widths_[paragraph->records_[2].line()],\n              2.0);\n\n  ASSERT_TRUE(paragraph->records_[4].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[4].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_NEAR(paragraph->records_[4].offset().x(),\n              paragraph->width_ -\n                  paragraph->line_widths_[paragraph->records_[4].line()],\n              2.0);\n\n  ASSERT_TRUE(paragraph->records_[6].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[6].offset().y(), expected_y);\n  expected_y += 30 * 10;\n  ASSERT_NEAR(paragraph->records_[6].offset().x(),\n              paragraph->width_ -\n                  paragraph->line_widths_[paragraph->records_[6].line()],\n              2.0);\n\n  ASSERT_TRUE(paragraph->records_[26].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[26].offset().y(), expected_y);\n  ASSERT_NEAR(paragraph->records_[26].offset().x(),\n              paragraph->width_ -\n                  paragraph->line_widths_[paragraph->records_[26].line()],\n              2.0);\n\n  ASSERT_EQ(paragraph_style.text_align,\n            paragraph->GetParagraphStyle().text_align);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(CenterAlignParagraph)) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::center;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  // Two records for each due to 'ghost' trailing whitespace run.\n  ASSERT_EQ(paragraph->records_.size(), paragraph_style.max_lines * 2);\n  double expected_y = 24;\n\n  ASSERT_TRUE(paragraph->records_[0].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[0].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_NEAR(paragraph->records_[0].offset().x(),\n              (paragraph->width_ -\n               paragraph->line_widths_[paragraph->records_[0].line()]) /\n                  2,\n              2.0);\n\n  ASSERT_TRUE(paragraph->records_[2].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[2].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_NEAR(paragraph->records_[2].offset().x(),\n              (paragraph->width_ -\n               paragraph->line_widths_[paragraph->records_[2].line()]) /\n                  2,\n              2.0);\n\n  ASSERT_TRUE(paragraph->records_[4].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[4].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_NEAR(paragraph->records_[4].offset().x(),\n              (paragraph->width_ -\n               paragraph->line_widths_[paragraph->records_[4].line()]) /\n                  2,\n              2.0);\n\n  ASSERT_TRUE(paragraph->records_[6].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[6].offset().y(), expected_y);\n  expected_y += 30 * 10;\n  ASSERT_NEAR(paragraph->records_[6].offset().x(),\n              (paragraph->width_ -\n               paragraph->line_widths_[paragraph->records_[6].line()]) /\n                  2,\n              2.0);\n\n  ASSERT_TRUE(paragraph->records_[26].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[26].offset().y(), expected_y);\n  ASSERT_NEAR(paragraph->records_[26].offset().x(),\n              (paragraph->width_ -\n               paragraph->line_widths_[paragraph->records_[26].line()]) /\n                  2,\n              2.0);\n\n  ASSERT_EQ(paragraph_style.text_align,\n            paragraph->GetParagraphStyle().text_align);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(JustifyAlignParagraph)) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint \"\n      \"occaecat cupidatat non proident, sunt in culpa qui officia deserunt \"\n      \"mollit anim id est laborum. \"\n      \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod \"\n      \"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \"\n      \"veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \"\n      \"commodo consequat. Duis aute irure dolor in reprehenderit in voluptate \"\n      \"velit esse cillum dolore eu fugiat.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::justify;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_.size(), 27ull);\n  double expected_y = 24;\n\n  ASSERT_TRUE(paragraph->records_[0].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[0].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_DOUBLE_EQ(paragraph->records_[0].offset().x(), 0);\n\n  ASSERT_TRUE(paragraph->records_[2].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[2].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_DOUBLE_EQ(paragraph->records_[2].offset().x(), 0);\n\n  ASSERT_TRUE(paragraph->records_[4].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[4].offset().y(), expected_y);\n  expected_y += 30;\n  ASSERT_DOUBLE_EQ(paragraph->records_[4].offset().x(), 0);\n\n  ASSERT_TRUE(paragraph->records_[6].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[6].offset().y(), expected_y);\n  expected_y += 30 * 10;\n  ASSERT_DOUBLE_EQ(paragraph->records_[6].offset().x(), 0);\n\n  ASSERT_TRUE(paragraph->records_[26].style().equals(text_style));\n  ASSERT_DOUBLE_EQ(paragraph->records_[26].offset().y(), expected_y);\n  ASSERT_DOUBLE_EQ(paragraph->records_[26].offset().x(), 0);\n\n  ASSERT_EQ(paragraph_style.text_align,\n            paragraph->GetParagraphStyle().text_align);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(JustifyRTL)) {\n  const char* text =\n      \"אאא בּבּבּבּ אאאא בּבּ אאא בּבּבּ אאאאא בּבּבּבּ אאאא בּבּבּבּבּ \"\n      \"אאאאא בּבּבּבּבּ אאאבּבּבּבּבּבּאאאאא בּבּבּבּבּבּאאאאאבּבּבּבּבּבּ אאאאא בּבּבּבּבּ \"\n      \"אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ\";\n\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::justify;\n  paragraph_style.text_direction = TextDirection::rtl;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Ahem\");\n  text_style.font_size = 26;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  size_t paragraph_width = GetTestCanvasWidth() - 100;\n  paragraph->Layout(paragraph_width);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  auto glyph_line_width = [&paragraph](int index) {\n    size_t second_to_last_position_index =\n        paragraph->glyph_lines_[index].positions.size() - 1;\n    return paragraph->glyph_lines_[index]\n        .positions[second_to_last_position_index]\n        .x_pos.end;\n  };\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 100, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  ASSERT_EQ(boxes.size(), 5ull);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes = paragraph->GetRectsForRange(240, 250, rect_height_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  ASSERT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 588);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 130);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 640);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 156);\n  ASSERT_TRUE(Snapshot());\n\n  // All lines should be justified to the width of the\n  // paragraph.\n  for (size_t i = 0; i < paragraph->glyph_lines_.size(); ++i) {\n    ASSERT_EQ(glyph_line_width(i), paragraph_width);\n  }\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(JustifyRTLNewLine)) {\n  const char* text =\n      \"אאא בּבּבּבּ אאאא\\nבּבּ אאא בּבּבּ אאאאא בּבּבּבּ אאאא בּבּבּבּבּ \"\n      \"אאאאא בּבּבּבּבּ אאאבּבּבּבּבּבּאאאאא בּבּבּבּבּבּאאאאאבּבּבּבּבּבּ אאאאא בּבּבּבּבּ \"\n      \"אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ אאאאא בּבּבּבּבּבּ\";\n\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::justify;\n  paragraph_style.text_direction = TextDirection::rtl;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Ahem\");\n  text_style.font_size = 26;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  size_t paragraph_width = GetTestCanvasWidth() - 100;\n  paragraph->Layout(paragraph_width);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  auto glyph_line_width = [&paragraph](int index) {\n    size_t second_to_last_position_index =\n        paragraph->glyph_lines_[index].positions.size() - 1;\n    return paragraph->glyph_lines_[index]\n        .positions[second_to_last_position_index]\n        .x_pos.end;\n  };\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  ASSERT_TRUE(Snapshot());\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 30, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  ASSERT_EQ(boxes.size(), 2ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 562);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), -1.4305115e-06);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 900);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 26);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes = paragraph->GetRectsForRange(240, 250, rect_height_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  ASSERT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 68);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 130);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 120);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 156);\n  ASSERT_TRUE(Snapshot());\n\n  // All lines should be justified to the width of the\n  // paragraph.\n  for (size_t i = 0; i < paragraph->glyph_lines_.size(); ++i) {\n    ASSERT_EQ(glyph_line_width(i), paragraph_width);\n  }\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(JustifyPlaceholder)) {\n  const char* text1 = \"A \";\n  auto icu_text1 = icu::UnicodeString::fromUTF8(text1);\n  std::u16string u16_text1(icu_text1.getBuffer(),\n                           icu_text1.getBuffer() + icu_text1.length());\n\n  txt::PlaceholderRun placeholder_run(60, 60, PlaceholderAlignment::kBaseline,\n                                      TextBaseline::kAlphabetic, 0);\n\n  const char* text2 = \" B CCCCC\";\n  auto icu_text2 = icu::UnicodeString::fromUTF8(text2);\n  std::u16string u16_text2(icu_text2.getBuffer(),\n                           icu_text2.getBuffer() + icu_text2.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.text_align = TextAlign::justify;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Ahem\");\n  text_style.font_size = 20;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text1);\n  builder.AddPlaceholder(placeholder_run);\n  builder.AddText(u16_text2);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(200);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n\n  // Check location of placeholder at the center of the line.\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(2, 3, rect_height_style, rect_width_style);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 70);\n\n  // Check location of character B at the end of the line.\n  boxes =\n      paragraph->GetRectsForRange(4, 5, rect_height_style, rect_width_style);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 180);\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(LeadingSpaceRTL)) {\n  const char* text = \" leading space\";\n\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::justify;\n  paragraph_style.text_direction = TextDirection::rtl;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Ahem\");\n  text_style.font_size = 26;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  size_t paragraph_width = GetTestCanvasWidth() - 100;\n  paragraph->Layout(paragraph_width);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 100, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  ASSERT_EQ(boxes.size(), 2ull);\n\n  // This test should crash if behavior regresses.\n}\n\nTEST_F(ParagraphTest, DecorationsParagraph) {\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 2;\n  text_style.decoration = TextDecoration::kUnderline |\n                          TextDecoration::kOverline |\n                          TextDecoration::kLineThrough;\n  text_style.decoration_style = txt::TextDecorationStyle::kSolid;\n  text_style.decoration_color = SK_ColorBLACK;\n  text_style.decoration_thickness_multiplier = 2.0;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"This text should be\");\n\n  text_style.decoration_style = txt::TextDecorationStyle::kDouble;\n  text_style.decoration_color = SK_ColorBLUE;\n  text_style.decoration_thickness_multiplier = 1.0;\n  builder.PushStyle(text_style);\n  builder.AddText(u\" decorated even when\");\n\n  text_style.decoration_style = txt::TextDecorationStyle::kDotted;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u\" wrapped around to\");\n\n  text_style.decoration_style = txt::TextDecorationStyle::kDashed;\n  text_style.decoration_color = SK_ColorBLACK;\n  text_style.decoration_thickness_multiplier = 3.0;\n  builder.PushStyle(text_style);\n  builder.AddText(u\" the next line.\");\n\n  text_style.decoration_style = txt::TextDecorationStyle::kWavy;\n  text_style.decoration_color = SK_ColorRED;\n  text_style.decoration_thickness_multiplier = 1.0;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u\" Otherwise, bad things happen.\");\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->runs_.size(), 5ull);\n  ASSERT_EQ(paragraph->records_.size(), 6ull);\n\n  for (size_t i = 0; i < 6; ++i) {\n    ASSERT_EQ(paragraph->records_[i].style().decoration,\n              TextDecoration::kUnderline | TextDecoration::kOverline |\n                  TextDecoration::kLineThrough);\n  }\n\n  ASSERT_EQ(paragraph->records_[0].style().decoration_style,\n            txt::TextDecorationStyle::kSolid);\n  ASSERT_EQ(paragraph->records_[1].style().decoration_style,\n            txt::TextDecorationStyle::kDouble);\n  ASSERT_EQ(paragraph->records_[2].style().decoration_style,\n            txt::TextDecorationStyle::kDotted);\n  ASSERT_EQ(paragraph->records_[3].style().decoration_style,\n            txt::TextDecorationStyle::kDashed);\n  ASSERT_EQ(paragraph->records_[4].style().decoration_style,\n            txt::TextDecorationStyle::kDashed);\n  ASSERT_EQ(paragraph->records_[5].style().decoration_style,\n            txt::TextDecorationStyle::kWavy);\n\n  ASSERT_EQ(paragraph->records_[0].style().decoration_color, SK_ColorBLACK);\n  ASSERT_EQ(paragraph->records_[1].style().decoration_color, SK_ColorBLUE);\n  ASSERT_EQ(paragraph->records_[2].style().decoration_color, SK_ColorBLACK);\n  ASSERT_EQ(paragraph->records_[3].style().decoration_color, SK_ColorBLACK);\n  ASSERT_EQ(paragraph->records_[4].style().decoration_color, SK_ColorBLACK);\n  ASSERT_EQ(paragraph->records_[5].style().decoration_color, SK_ColorRED);\n\n  ASSERT_EQ(paragraph->records_[0].style().decoration_thickness_multiplier,\n            2.0);\n  ASSERT_EQ(paragraph->records_[1].style().decoration_thickness_multiplier,\n            1.0);\n  ASSERT_EQ(paragraph->records_[2].style().decoration_thickness_multiplier,\n            1.0);\n  ASSERT_EQ(paragraph->records_[3].style().decoration_thickness_multiplier,\n            3.0);\n  ASSERT_EQ(paragraph->records_[4].style().decoration_thickness_multiplier,\n            3.0);\n  ASSERT_EQ(paragraph->records_[5].style().decoration_thickness_multiplier,\n            1.0);\n}\n\nTEST_F(ParagraphTest, WavyDecorationParagraph) {\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 2;\n  text_style.decoration = TextDecoration::kUnderline |\n                          TextDecoration::kOverline |\n                          TextDecoration::kLineThrough;\n\n  text_style.decoration_style = txt::TextDecorationStyle::kWavy;\n  text_style.decoration_color = SK_ColorRED;\n  text_style.decoration_thickness_multiplier = 1.0;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u\" Otherwise, bad things happen.\");\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->records_.size(), 1ull);\n\n  for (size_t i = 0; i < 1; ++i) {\n    ASSERT_EQ(paragraph->records_[i].style().decoration,\n              TextDecoration::kUnderline | TextDecoration::kOverline |\n                  TextDecoration::kLineThrough);\n  }\n\n  ASSERT_EQ(paragraph->records_[0].style().decoration_style,\n            txt::TextDecorationStyle::kWavy);\n\n  ASSERT_EQ(paragraph->records_[0].style().decoration_color, SK_ColorRED);\n\n  ASSERT_EQ(paragraph->records_[0].style().decoration_thickness_multiplier,\n            1.0);\n\n  SkPath path0;\n  SkPath canonical_path0;\n  paragraph->ComputeWavyDecoration(path0, 1, 1, 9.56, 1);\n\n  canonical_path0.moveTo(1, 1);\n  canonical_path0.rQuadTo(1, -1, 2, 0);\n  canonical_path0.rQuadTo(1, 1, 2, 0);\n  canonical_path0.rQuadTo(1, -1, 2, 0);\n  canonical_path0.rQuadTo(1, 1, 2, 0);\n  canonical_path0.rQuadTo(0.78, -0.78, 1.56, -0.3432);\n\n  ASSERT_EQ(path0.countPoints(), canonical_path0.countPoints());\n  for (int i = 0; i < canonical_path0.countPoints(); ++i) {\n    ASSERT_EQ(path0.getPoint(i).x(), canonical_path0.getPoint(i).x());\n    ASSERT_EQ(path0.getPoint(i).y(), canonical_path0.getPoint(i).y());\n  }\n\n  SkPath path1;\n  SkPath canonical_path1;\n  paragraph->ComputeWavyDecoration(path1, 1, 1, 8.35, 1);\n\n  canonical_path1.moveTo(1, 1);\n  canonical_path1.rQuadTo(1, -1, 2, 0);\n  canonical_path1.rQuadTo(1, 1, 2, 0);\n  canonical_path1.rQuadTo(1, -1, 2, 0);\n  canonical_path1.rQuadTo(1, 1, 2, 0);\n  canonical_path1.rQuadTo(0.175, -0.175, 0.35, -0.28875);\n\n  ASSERT_EQ(path1.countPoints(), canonical_path1.countPoints());\n  for (int i = 0; i < canonical_path1.countPoints(); ++i) {\n    ASSERT_EQ(path1.getPoint(i).x(), canonical_path1.getPoint(i).x());\n    ASSERT_EQ(path1.getPoint(i).y(), canonical_path1.getPoint(i).y());\n  }\n\n  SkPath path2;\n  SkPath canonical_path2;\n  paragraph->ComputeWavyDecoration(path2, 1, 1, 10.59, 1);\n\n  canonical_path2.moveTo(1, 1);\n  canonical_path2.rQuadTo(1, -1, 2, 0);\n  canonical_path2.rQuadTo(1, 1, 2, 0);\n  canonical_path2.rQuadTo(1, -1, 2, 0);\n  canonical_path2.rQuadTo(1, 1, 2, 0);\n  canonical_path2.rQuadTo(1, -1, 2, 0);\n  canonical_path2.rQuadTo(0.295, 0.295, 0.59, 0.41595);\n\n  ASSERT_EQ(path2.countPoints(), canonical_path2.countPoints());\n  for (int i = 0; i < canonical_path2.countPoints(); ++i) {\n    ASSERT_EQ(path2.getPoint(i).x(), canonical_path2.getPoint(i).x());\n    ASSERT_EQ(path2.getPoint(i).y(), canonical_path2.getPoint(i).y());\n  }\n\n  SkPath path3;\n  SkPath canonical_path3;\n  paragraph->ComputeWavyDecoration(path3, 1, 1, 11.2, 1);\n\n  canonical_path3.moveTo(1, 1);\n  canonical_path3.rQuadTo(1, -1, 2, 0);\n  canonical_path3.rQuadTo(1, 1, 2, 0);\n  canonical_path3.rQuadTo(1, -1, 2, 0);\n  canonical_path3.rQuadTo(1, 1, 2, 0);\n  canonical_path3.rQuadTo(1, -1, 2, 0);\n  canonical_path3.rQuadTo(0.6, 0.6, 1.2, 0.48);\n\n  ASSERT_EQ(path3.countPoints(), canonical_path3.countPoints());\n  for (int i = 0; i < canonical_path3.countPoints(); ++i) {\n    ASSERT_EQ(path3.getPoint(i).x(), canonical_path3.getPoint(i).x());\n    ASSERT_EQ(path3.getPoint(i).y(), canonical_path3.getPoint(i).y());\n  }\n\n  SkPath path4;\n  SkPath canonical_path4;\n  paragraph->ComputeWavyDecoration(path4, 1, 1, 12, 1);\n\n  canonical_path4.moveTo(1, 1);\n  canonical_path4.rQuadTo(1, -1, 2, 0);\n  canonical_path4.rQuadTo(1, 1, 2, 0);\n  canonical_path4.rQuadTo(1, -1, 2, 0);\n  canonical_path4.rQuadTo(1, 1, 2, 0);\n  canonical_path4.rQuadTo(1, -1, 2, 0);\n  canonical_path4.rQuadTo(1, 1, 2, 0);\n\n  ASSERT_EQ(path4.countPoints(), canonical_path4.countPoints());\n  for (int i = 0; i < canonical_path4.countPoints(); ++i) {\n    ASSERT_EQ(path4.getPoint(i).x(), canonical_path4.getPoint(i).x());\n    ASSERT_EQ(path4.getPoint(i).y(), canonical_path4.getPoint(i).y());\n  }\n}\n\nTEST_F(ParagraphTest, ItalicsParagraph) {\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorRED;\n  text_style.font_size = 10;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"No italic \");\n\n  text_style.font_style = txt::FontStyle::italic;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"Yes Italic \");\n\n  builder.Pop();\n  builder.AddText(u\"No Italic again.\");\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 3ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 3ull);\n  ASSERT_EQ(paragraph->records_[1].style().color, text_style.color);\n  ASSERT_EQ(paragraph->records_[1].style().font_style, txt::FontStyle::italic);\n  ASSERT_EQ(paragraph->records_[2].style().font_style, txt::FontStyle::normal);\n  ASSERT_EQ(paragraph->records_[0].style().font_style, txt::FontStyle::normal);\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, ChineseParagraph) {\n  const char* text =\n      \"左線読設重説切後碁給能上目秘使約。満毎冠行来昼本可必図将発確年。今属場育\"\n      \"図情闘陰野高備込制詩西校客。審対江置講今固残必託地集済決維駆年策。立得庭\"\n      \"際輝求佐抗蒼提夜合逃表。注統天言件自謙雅載報紙喪。作画稿愛器灯女書利変探\"\n      \"訃第金線朝開化建。子戦年帝励害表月幕株漠新期刊人秘。図的海力生禁挙保天戦\"\n      \"聞条年所在口。\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::justify;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  text_style.font_size = 35;\n  text_style.letter_spacing = 2;\n  text_style.font_families = std::vector<std::string>(1, \"Source Han Serif CN\");\n  text_style.decoration = TextDecoration::kUnderline |\n                          TextDecoration::kOverline |\n                          TextDecoration::kLineThrough;\n  text_style.decoration_style = txt::TextDecorationStyle::kSolid;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_EQ(paragraph->records_.size(), 7ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\n// TODO(garyq): Support RTL languages.\nTEST_F(ParagraphTest, DISABLED_ArabicParagraph) {\n  const char* text =\n      \"من أسر وإعلان الخاصّة وهولندا،, عل قائمة الضغوط بالمطالبة تلك. الصفحة \"\n      \"بمباركة التقليدية قام عن. تصفح\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::right;\n  paragraph_style.text_direction = TextDirection::rtl;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  text_style.font_size = 35;\n  text_style.letter_spacing = 2;\n  text_style.font_families = std::vector<std::string>(1, \"Katibeh\");\n  text_style.decoration = TextDecoration::kUnderline |\n                          TextDecoration::kOverline |\n                          TextDecoration::kLineThrough;\n  text_style.decoration_style = txt::TextDecorationStyle::kSolid;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_EQ(paragraph->records_.size(), 2ull);\n  ASSERT_EQ(paragraph->paragraph_style_.text_direction, TextDirection::rtl);\n\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[u16_text.length() - i]);\n  }\n\n  ASSERT_TRUE(Snapshot());\n}\n\n// Checks if the rects are in the correct positions after typing spaces in\n// Arabic.\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(ArabicRectsParagraph)) {\n  const char* text = \"بمباركة التقليدية قام عن. تصفح يد    \";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::right;\n  paragraph_style.text_direction = TextDirection::rtl;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Noto Naskh Arabic\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 100, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 2ull);\n\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 556.48438);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), -0.26855469);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 900);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 44);\n\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 510.03125);\n  EXPECT_FLOAT_EQ(boxes[1].rect.top(), -0.26855469);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 556.98438);\n  EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 44);\n\n  ASSERT_EQ(paragraph_style.text_align,\n            paragraph->GetParagraphStyle().text_align);\n\n  ASSERT_TRUE(Snapshot());\n}\n\n// Trailing space at the end of the arabic rtl run should be at the left end of\n// the arabic run.\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(ArabicRectsLTRLeftAlignParagraph)) {\n  const char* text = \"Helloبمباركة التقليدية قام عن. تصفح يد \";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::left;\n  paragraph_style.text_direction = TextDirection::ltr;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Noto Naskh Arabic\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(36, 40, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 89.425781);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), -0.26855469);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 121.90625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 44);\n\n  ASSERT_EQ(paragraph_style.text_align,\n            paragraph->GetParagraphStyle().text_align);\n\n  ASSERT_TRUE(Snapshot());\n}\n\n// Trailing space at the end of the arabic rtl run should be at the left end of\n// the arabic run and be a ghost space.\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(ArabicRectsLTRRightAlignParagraph)) {\n  const char* text = \"Helloبمباركة التقليدية قام عن. تصفح يد \";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::right;\n  paragraph_style.text_direction = TextDirection::ltr;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Noto Naskh Arabic\");\n  text_style.font_size = 26;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  text_style.decoration = TextDecoration::kUnderline;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(36, 40, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 2ull);\n\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 556.48438);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), -0.26855469);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 577.72656);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 44);\n\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 545.24609);\n  EXPECT_FLOAT_EQ(boxes[1].rect.top(), -0.26855469);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 556.98438);\n  EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 44);\n\n  ASSERT_EQ(paragraph_style.text_align,\n            paragraph->GetParagraphStyle().text_align);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, GetGlyphPositionAtCoordinateParagraph) {\n  const char* text =\n      \"12345 67890 12345 67890 12345 67890 12345 67890 12345 67890 12345 \"\n      \"67890 12345\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n\n  // Tests for GetGlyphPositionAtCoordinate()\n  // NOTE: resulting values can be a few off from their respective positions in\n  // the original text because the final trailing whitespaces are sometimes not\n  // drawn (namely, when using \"justify\" alignment) and therefore are not active\n  // glyphs.\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(-10000, -10000).position,\n            0ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(-1, -1).position, 0ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0, 0).position, 0ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(3, 3).position, 0ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 1).position, 1ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(300, 2).position, 11ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(301, 2.2).position, 11ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(302, 2.6).position, 11ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(301, 2.1).position, 11ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 20).position,\n            18ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(450, 20).position, 16ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 90).position,\n            36ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(-100000, 90).position,\n            18ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(20, -80).position, 1ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 90).position, 18ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 170).position, 36ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 180).position,\n            72ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(70, 180).position, 56ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 270).position, 72ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 90).position, 19ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 10000).position,\n            77ull);\n  ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(85, 10000).position, 75ull);\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) {\n  const char* text =\n      \"12345,  \\\"67890\\\" 12345 67890 12345 67890 12345 67890 12345 67890 12345 \"\n      \"67890 12345\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  // NOTE: The base truth values may still need adjustment as the specifics\n  // are adjusted.\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 28.417969);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 56.835938);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 177.98438);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 177.98438);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 507.03906);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(30, 100, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 4ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 211.37891);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 59.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 463.62891);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 118);\n\n  // TODO(garyq): The following set of vals are definitely wrong and\n  // end of paragraph handling needs to be fixed in a later patch.\n  EXPECT_FLOAT_EQ(boxes[3].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[3].rect.top(), 236.40625);\n  EXPECT_FLOAT_EQ(boxes[3].rect.right(), 142.08984);\n  EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 295);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(19, 22, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 450.20312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 519.49219);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeTight)) {\n  const char* text =\n      \"(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(\"\n      \"　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(\"\n      \"　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Noto Sans CJK JP\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  // NOTE: The base truth values may still need adjustment as the specifics\n  // are adjusted.\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 16.898438);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 74);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 264.09766);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 74);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 2ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 264.09766);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 595.09375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 74);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       DISABLE_ON_WINDOWS(GetRectsForRangeIncludeLineSpacingMiddle)) {\n  const char* text =\n      \"(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(\"\n      \"　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(\"\n      \"　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1.6;\n  text_style.has_height_override = true;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  // NOTE: The base truth values may still need adjustment as the specifics\n  // are adjusted.\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kIncludeLineSpacingMiddle;\n  Paragraph::RectWidthStyle rect_width_style = Paragraph::RectWidthStyle::kMax;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 17.433594);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 88.473305);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 67.433594);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 190.01953);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 88.473305);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.01953);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 508.09375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 88.473312);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(30, 150, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 8ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.01953);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 88.473312);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 525.72266);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 168.47331);\n\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 525.72266);\n  EXPECT_FLOAT_EQ(boxes[1].rect.top(), 88.473312);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 168.4733);\n\n  EXPECT_FLOAT_EQ(boxes[2].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[2].rect.top(), 168.4733);\n  EXPECT_FLOAT_EQ(boxes[2].rect.right(), 531.60547);\n  EXPECT_FLOAT_EQ(boxes[2].rect.bottom(), 248.47331);\n\n  EXPECT_FLOAT_EQ(boxes[3].rect.left(), 531.60547);\n  EXPECT_FLOAT_EQ(boxes[3].rect.top(), 168.4733);\n  EXPECT_FLOAT_EQ(boxes[3].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 248.47331);\n\n  EXPECT_FLOAT_EQ(boxes[4].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[4].rect.top(), 248.47331);\n  EXPECT_FLOAT_EQ(boxes[4].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[4].rect.bottom(), 328.4733);\n\n  EXPECT_FLOAT_EQ(boxes[5].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[5].rect.top(), 328.47333);\n  EXPECT_FLOAT_EQ(boxes[5].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[5].rect.bottom(), 408.4733);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(19, 22, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 463.75781);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 530.26172);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 88.473305);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       DISABLE_ON_WINDOWS(GetRectsForRangeIncludeLineSpacingTop)) {\n  const char* text =\n      \"(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(\"\n      \"　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(\"\n      \"　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1.6;\n  text_style.has_height_override = true;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  // NOTE: The base truth values may still need adjustment as the specifics\n  // are adjusted.\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kIncludeLineSpacingTop;\n  Paragraph::RectWidthStyle rect_width_style = Paragraph::RectWidthStyle::kMax;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 17.433594);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 80);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 67.433594);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 190.01953);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 80);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.01953);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 508.09375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 80);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(30, 150, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 8ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.01953);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 80);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 525.72266);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160);\n\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 525.72266);\n  EXPECT_FLOAT_EQ(boxes[1].rect.top(), 80);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 160);\n\n  EXPECT_FLOAT_EQ(boxes[2].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[2].rect.top(), 160);\n  EXPECT_FLOAT_EQ(boxes[2].rect.right(), 531.60547);\n  EXPECT_FLOAT_EQ(boxes[2].rect.bottom(), 240);\n\n  EXPECT_FLOAT_EQ(boxes[3].rect.left(), 531.60547);\n  EXPECT_FLOAT_EQ(boxes[3].rect.top(), 160);\n  EXPECT_FLOAT_EQ(boxes[3].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 240);\n\n  EXPECT_FLOAT_EQ(boxes[4].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[4].rect.top(), 240);\n  EXPECT_FLOAT_EQ(boxes[4].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[4].rect.bottom(), 320);\n\n  EXPECT_FLOAT_EQ(boxes[5].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[5].rect.top(), 320);\n  EXPECT_FLOAT_EQ(boxes[5].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[5].rect.bottom(), 400);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(19, 22, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 463.75781);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 530.26172);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 80);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       DISABLE_ON_WINDOWS(GetRectsForRangeIncludeLineSpacingBottom)) {\n  const char* text =\n      \"(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(\"\n      \"　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(\"\n      \"　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)(　´･‿･｀)\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1.6;\n  text_style.has_height_override = true;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  // NOTE: The base truth values may still need adjustment as the specifics\n  // are adjusted.\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kIncludeLineSpacingBottom;\n  Paragraph::RectWidthStyle rect_width_style = Paragraph::RectWidthStyle::kMax;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 17.433594);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 96.946609);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(2, 8, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 67.433594);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 190.01953);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 96.946609);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(8, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.01953);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 508.09375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 96.946609);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(30, 150, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 8ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 190.01953);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 96.946617);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 525.72266);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 176.94661);\n\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 525.72266);\n  EXPECT_FLOAT_EQ(boxes[1].rect.top(), 96.946617);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[1].rect.bottom(), 176.94661);\n\n  EXPECT_FLOAT_EQ(boxes[2].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[2].rect.top(), 176.94661);\n  EXPECT_FLOAT_EQ(boxes[2].rect.right(), 531.60547);\n  EXPECT_FLOAT_EQ(boxes[2].rect.bottom(), 256.94662);\n\n  EXPECT_FLOAT_EQ(boxes[3].rect.left(), 531.60547);\n  EXPECT_FLOAT_EQ(boxes[3].rect.top(), 176.94661);\n  EXPECT_FLOAT_EQ(boxes[3].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 256.94662);\n\n  EXPECT_FLOAT_EQ(boxes[4].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[4].rect.top(), 256.94662);\n  EXPECT_FLOAT_EQ(boxes[4].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[4].rect.bottom(), 336.94662);\n\n  EXPECT_FLOAT_EQ(boxes[5].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[5].rect.top(), 336.94662);\n  EXPECT_FLOAT_EQ(boxes[5].rect.right(), 570.05859);\n  EXPECT_FLOAT_EQ(boxes[5].rect.bottom(), 416.94662);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(19, 22, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 463.75781);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 16.946615);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 530.26172);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 96.946609);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, GetRectsForRangeIncludeCombiningCharacter) {\n  const char* text = \"ดีสวัสดีชาวโลกที่น่ารัก\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 1;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  // Case when the sentence starts with a combining character\n  // We should get 0 box for ด because it's already been combined to ดี\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(1, 2, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 1ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 2, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 1ull);\n\n  // Case when the sentence contains a combining character\n  // We should get 0 box for ว because it's already been combined to วั\n  boxes =\n      paragraph->GetRectsForRange(3, 4, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(4, 5, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 1ull);\n\n  boxes =\n      paragraph->GetRectsForRange(3, 5, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 1ull);\n\n  // Case when the sentence contains a combining character that contain 3\n  // characters We should get 0 box for ท and ที because it's already been\n  // combined to ที่\n  boxes =\n      paragraph->GetRectsForRange(14, 15, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(16, 17, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 1ull);\n\n  boxes =\n      paragraph->GetRectsForRange(14, 17, rect_height_style, rect_width_style);\n  EXPECT_EQ(boxes.size(), 1ull);\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeCenterParagraph)) {\n  const char* text = \"01234  　 \";  // includes ideographic space\n                                    // and english space.\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::center;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 203.95508);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 232.37305);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(2, 4, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 260.79102);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 317.62695);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(4, 6, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 2ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 317.62695);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 346.04492);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorBLACK);\n  boxes =\n      paragraph->GetRectsForRange(5, 6, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 346.04492);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 358.49805);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeParagraphNewlineLeftAlign)) {\n  const char* text = \"01234\\n\\nعab\\naعلی\\n\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.font_family = \"Roboto\";\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_direction = TextDirection::ltr;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = {\"Roboto\", \"Noto Naskh Arabic\"};\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 28.417969);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(6, 7, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(),\n                  75);  // TODO(garyq): This value can be improved... Should be\n                        // taller, but we need a good way to obtain a height\n                        // without any glyphs on the line.\n\n  boxes =\n      paragraph->GetRectsForRange(10, 11, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 85);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 85);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160);\n\n  boxes =\n      paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 27);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 27);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeParagraphNewlineRightAlign)) {\n  const char* text = \"01234\\n\\nعab\\naعلی\\n\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.font_family = \"Roboto\";\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_direction = TextDirection::ltr;\n  paragraph_style.text_align = TextAlign::right;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = {\"Roboto\", \"Noto Naskh Arabic\"};\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 407.91016);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 436.32812);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(6, 7, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 550);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 550);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(),\n                  75);  // TODO(garyq): This value can be improved... Should be\n                        // taller, but we need a good way to obtain a height\n                        // without any glyphs on the line.\n\n  boxes =\n      paragraph->GetRectsForRange(10, 11, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 550);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 550);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160);\n\n  boxes =\n      paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 478);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 478);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       LINUX_ONLY(GetRectsForRangeCenterParagraphNewlineCentered)) {\n  const char* text = \"01234\\n\\nعab\\naعلی\\n\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.font_family = \"Roboto\";\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_direction = TextDirection::ltr;\n  paragraph_style.text_align = TextAlign::center;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = {\"Roboto\", \"Noto Naskh Arabic\"};\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 203.95508);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 232.37305);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(6, 7, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 275);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 275);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(),\n                  75);  // TODO(garyq): This value can be improved... Should be\n                        // taller, but we need a good way to obtain a height\n                        // without any glyphs on the line.\n\n  boxes =\n      paragraph->GetRectsForRange(10, 11, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 317);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 317);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160);\n\n  boxes =\n      paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 252);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 252);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       LINUX_ONLY(GetRectsForRangeParagraphNewlineRTLLeftAlign)) {\n  const char* text = \"01234\\n\\nعab\\naعلی\\n\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.font_family = \"Roboto\";\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_direction = TextDirection::rtl;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = {\"Roboto\", \"Noto Naskh Arabic\"};\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 28.417969);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(6, 7, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(),\n                  75);  // TODO(garyq): This value can be improved... Should be\n                        // taller, but we need a good way to obtain a height\n                        // without any glyphs on the line.\n\n  boxes =\n      paragraph->GetRectsForRange(10, 11, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 55);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 55);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160);\n\n  boxes =\n      paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       LINUX_ONLY(GetRectsForRangeParagraphNewlineRTLRightAlign)) {\n  const char* text = \"01234\\n\\nعab\\naعلی\\n\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.font_family = \"Roboto\";\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_direction = TextDirection::rtl;\n  paragraph_style.text_align = TextAlign::right;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = {\"Roboto\", \"Noto Naskh Arabic\"};\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 407.91016);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 436.32812);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(6, 7, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 550);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 550);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(),\n                  75);  // TODO(garyq): This value can be improved... Should be\n                        // taller, but we need a good way to obtain a height\n                        // without any glyphs on the line.\n\n  boxes =\n      paragraph->GetRectsForRange(10, 11, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 519);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 519);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160);\n\n  boxes =\n      paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 451);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 451);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       LINUX_ONLY(GetRectsForRangeCenterParagraphNewlineRTLCentered)) {\n  const char* text = \"01234\\n\\nعab\\naعلی\\n\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.font_family = \"Roboto\";\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_direction = TextDirection::rtl;\n  paragraph_style.text_align = TextAlign::center;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = {\"Roboto\", \"Noto Naskh Arabic\"};\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 203.95508);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 232.37305);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(6, 7, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 275);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 275);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(),\n                  75);  // TODO(garyq): This value can be improved... Should be\n                        // taller, but we need a good way to obtain a height\n                        // without any glyphs on the line.\n\n  boxes =\n      paragraph->GetRectsForRange(10, 11, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 287);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 287);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 160);\n\n  boxes =\n      paragraph->GetRectsForRange(15, 16, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 225);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 225);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 245);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest,\n       DISABLE_ON_WINDOWS(GetRectsForRangeCenterMultiLineParagraph)) {\n  const char* text = \"01234  　 \\n0123　        \";  // includes ideographic\n                                                    // space and english space.\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::center;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 203.95508);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 232.37305);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorBLUE);\n  boxes =\n      paragraph->GetRectsForRange(2, 4, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 260.79102);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 317.62695);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorGREEN);\n  boxes =\n      paragraph->GetRectsForRange(4, 6, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 2ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 317.62695);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 346.04492);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorBLACK);\n  boxes =\n      paragraph->GetRectsForRange(5, 6, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 346.04492);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 358.49805);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n\n  paint.setColor(SK_ColorBLACK);\n  boxes =\n      paragraph->GetRectsForRange(10, 12, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 218.16406);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 59.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 275);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 118);\n\n  paint.setColor(SK_ColorBLACK);\n  boxes =\n      paragraph->GetRectsForRange(14, 18, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 331.83594);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 59.40625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 419.19531);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 118);\n\n  paint.setColor(SK_ColorRED);\n  boxes =\n      paragraph->GetRectsForRange(21, 21, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(GetRectsForRangeStrut)) {\n  const char* text = \"Chinese 字典\";\n\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.strut_enabled = true;\n  paragraph_style.strut_font_families.push_back(\"Roboto\");\n  paragraph_style.strut_font_size = 14;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families.push_back(\"Noto Sans CJK JP\");\n  text_style.font_size = 20;\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  std::vector<txt::Paragraph::TextBox> strut_boxes =\n      paragraph->GetRectsForRange(0, 10, Paragraph::RectHeightStyle::kStrut,\n                                  Paragraph::RectWidthStyle::kMax);\n  ASSERT_EQ(strut_boxes.size(), 1ull);\n  const SkRect& strut_rect = strut_boxes.front().rect;\n  paint.setColor(SK_ColorRED);\n  GetCanvas()->drawRect(strut_rect, paint);\n\n  std::vector<txt::Paragraph::TextBox> tight_boxes =\n      paragraph->GetRectsForRange(0, 10, Paragraph::RectHeightStyle::kTight,\n                                  Paragraph::RectWidthStyle::kMax);\n  ASSERT_EQ(tight_boxes.size(), 1ull);\n  const SkRect& tight_rect = tight_boxes.front().rect;\n  paint.setColor(SK_ColorGREEN);\n  GetCanvas()->drawRect(tight_rect, paint);\n\n  EXPECT_FLOAT_EQ(strut_rect.left(), 0);\n  EXPECT_FLOAT_EQ(strut_rect.top(), 10.611719);\n  EXPECT_FLOAT_EQ(strut_rect.right(), 118.61719);\n  EXPECT_FLOAT_EQ(strut_rect.bottom(), 27.017969);\n\n  ASSERT_TRUE(tight_rect.contains(strut_rect));\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeStrutFallback)) {\n  const char* text = \"Chinese 字典\";\n\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.strut_enabled = false;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families.push_back(\"Noto Sans CJK JP\");\n  text_style.font_size = 20;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  std::vector<txt::Paragraph::TextBox> strut_boxes =\n      paragraph->GetRectsForRange(0, 10, Paragraph::RectHeightStyle::kStrut,\n                                  Paragraph::RectWidthStyle::kMax);\n  std::vector<txt::Paragraph::TextBox> tight_boxes =\n      paragraph->GetRectsForRange(0, 10, Paragraph::RectHeightStyle::kTight,\n                                  Paragraph::RectWidthStyle::kMax);\n\n  ASSERT_EQ(strut_boxes.size(), 1ull);\n  ASSERT_EQ(tight_boxes.size(), 1ull);\n  ASSERT_EQ(strut_boxes.front().rect, tight_boxes.front().rect);\n}\n\nSkRect GetCoordinatesForGlyphPosition(txt::Paragraph& paragraph, size_t pos) {\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph.GetRectsForRange(pos, pos + 1, Paragraph::RectHeightStyle::kMax,\n                                 Paragraph::RectWidthStyle::kTight);\n  return !boxes.empty() ? boxes.front().rect : SkRect::MakeEmpty();\n}\n\nTEST_F(ParagraphTest, GetWordBoundaryParagraph) {\n  const char* text =\n      \"12345  67890 12345 67890 12345 67890 12345 67890 12345 67890 12345 \"\n      \"67890 12345\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 52;\n  text_style.letter_spacing = 1.19039;\n  text_style.word_spacing = 5;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1.5;\n  text_style.has_height_override = true;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n  paint.setColor(SK_ColorRED);\n\n  SkRect rect = GetCoordinatesForGlyphPosition(*paragraph, 0);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  EXPECT_EQ(paragraph->GetWordBoundary(0), txt::Paragraph::Range<size_t>(0, 5));\n  EXPECT_EQ(paragraph->GetWordBoundary(1), txt::Paragraph::Range<size_t>(0, 5));\n  EXPECT_EQ(paragraph->GetWordBoundary(2), txt::Paragraph::Range<size_t>(0, 5));\n  EXPECT_EQ(paragraph->GetWordBoundary(3), txt::Paragraph::Range<size_t>(0, 5));\n  EXPECT_EQ(paragraph->GetWordBoundary(4), txt::Paragraph::Range<size_t>(0, 5));\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 5);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  EXPECT_EQ(paragraph->GetWordBoundary(5), txt::Paragraph::Range<size_t>(5, 7));\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 6);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  EXPECT_EQ(paragraph->GetWordBoundary(6), txt::Paragraph::Range<size_t>(5, 7));\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 7);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  EXPECT_EQ(paragraph->GetWordBoundary(7),\n            txt::Paragraph::Range<size_t>(7, 12));\n  EXPECT_EQ(paragraph->GetWordBoundary(8),\n            txt::Paragraph::Range<size_t>(7, 12));\n  EXPECT_EQ(paragraph->GetWordBoundary(9),\n            txt::Paragraph::Range<size_t>(7, 12));\n  EXPECT_EQ(paragraph->GetWordBoundary(10),\n            txt::Paragraph::Range<size_t>(7, 12));\n  EXPECT_EQ(paragraph->GetWordBoundary(11),\n            txt::Paragraph::Range<size_t>(7, 12));\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 12);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  EXPECT_EQ(paragraph->GetWordBoundary(12),\n            txt::Paragraph::Range<size_t>(12, 13));\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 13);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  EXPECT_EQ(paragraph->GetWordBoundary(13),\n            txt::Paragraph::Range<size_t>(13, 18));\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 18);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 19);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 24);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 25);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 30);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  EXPECT_EQ(paragraph->GetWordBoundary(30),\n            txt::Paragraph::Range<size_t>(30, 31));\n  rect = GetCoordinatesForGlyphPosition(*paragraph, 31);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  rect = GetCoordinatesForGlyphPosition(*paragraph, icu_text.length() - 5);\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  EXPECT_EQ(\n      paragraph->GetWordBoundary(icu_text.length() - 1),\n      txt::Paragraph::Range<size_t>(icu_text.length() - 5, icu_text.length()));\n  rect = GetCoordinatesForGlyphPosition(*paragraph, icu_text.length());\n  GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, SpacingParagraph) {\n  const char* text = \"H\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 20;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  builder.Pop();\n\n  text_style.font_size = 50;\n  text_style.letter_spacing = 10;\n  text_style.word_spacing = 0;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  builder.Pop();\n\n  text_style.font_size = 50;\n  text_style.letter_spacing = 20;\n  text_style.word_spacing = 0;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n  builder.Pop();\n\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"|\");\n  builder.Pop();\n\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 20;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"H \");\n  builder.Pop();\n\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"H \");\n  builder.Pop();\n\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 20;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"H \");\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n  paint.setColor(SK_ColorRED);\n\n  ASSERT_TRUE(Snapshot());\n\n  ASSERT_EQ(paragraph->records_.size(), 7ull);\n  ASSERT_EQ(paragraph->records_[0].style().letter_spacing, 20);\n  ASSERT_EQ(paragraph->records_[1].style().letter_spacing, 10);\n  ASSERT_EQ(paragraph->records_[2].style().letter_spacing, 20);\n\n  ASSERT_EQ(paragraph->records_[4].style().word_spacing, 20);\n  ASSERT_EQ(paragraph->records_[5].style().word_spacing, 0);\n  ASSERT_EQ(paragraph->records_[6].style().word_spacing, 20);\n}\n\nTEST_F(ParagraphTest, LongWordParagraph) {\n  const char* text =\n      \"A \"\n      \"veryverylongwordtoseewherethiswillwraporifitwillatallandifitdoesthenthat\"\n      \"wouldbeagoodthingbecausethebreakingisworking.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.break_strategy = minikin::kBreakStrategy_HighQuality;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 31;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() / 2);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_EQ(paragraph->GetLineCount(), 4ull);\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(KernScaleParagraph)) {\n  float scale = 3.0f;\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.break_strategy = minikin::kBreakStrategy_HighQuality;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Droid Serif\");\n  text_style.font_size = 100 / scale;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n  builder.AddText(u\"AVAVAWAH A0 V0 VA To The Lo\");\n  builder.PushStyle(text_style);\n  builder.AddText(u\"A\");\n  builder.PushStyle(text_style);\n  builder.AddText(u\"V\");\n  text_style.font_size = 14 / scale;\n  builder.PushStyle(text_style);\n  builder.AddText(\n      u\" Dialog Text List lots of words to see if kerning works on a bigger \"\n      u\"set of characters AVAVAW\");\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() / scale);\n  GetCanvas()->scale(scale, scale);\n  paragraph->Paint(GetCanvas(), 0, 0);\n  GetCanvas()->scale(1.0, 1.0);\n  ASSERT_TRUE(Snapshot());\n\n  EXPECT_DOUBLE_EQ(paragraph->records_[0].offset().x(), 0);\n  EXPECT_DOUBLE_EQ(paragraph->records_[1].offset().x(), 0);\n  EXPECT_DOUBLE_EQ(paragraph->records_[2].offset().x(), 207.3671875f);\n  EXPECT_DOUBLE_EQ(paragraph->records_[3].offset().x(), 230.8671875f);\n  EXPECT_DOUBLE_EQ(paragraph->records_[4].offset().x(), 253.35546875f);\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(NewlineParagraph)) {\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.font_family = \"Roboto\";\n  paragraph_style.break_strategy = minikin::kBreakStrategy_HighQuality;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 60;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n  builder.AddText(\n      u\"line1\\nline2 test1 test2 test3 test4 test5 test6 test7\\nline3\\n\\nline4 \"\n      \"test1 test2 test3 test4\");\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 300);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n\n  ASSERT_EQ(paragraph->records_.size(), 6ull);\n  EXPECT_DOUBLE_EQ(paragraph->records_[0].offset().x(), 0);\n  EXPECT_DOUBLE_EQ(paragraph->records_[1].offset().x(), 0);\n  EXPECT_DOUBLE_EQ(paragraph->records_[1].offset().y(), 126);\n  EXPECT_DOUBLE_EQ(paragraph->records_[2].offset().x(), 0);\n  EXPECT_DOUBLE_EQ(paragraph->records_[3].offset().x(), 0);\n  EXPECT_DOUBLE_EQ(paragraph->records_[4].offset().x(), 0);\n  EXPECT_DOUBLE_EQ(paragraph->records_[3].offset().y(), 266);\n  EXPECT_DOUBLE_EQ(paragraph->records_[5].offset().x(), 0);\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(EmojiParagraph)) {\n  const char* text =\n      \"😀😃😄😁😆😅😂🤣☺😇🙂😍😡😟😢😻👽💩👍👎🙏👌👋👄👁👦👼👨‍🚀👨‍🚒🙋‍♂️👳👨‍👨‍👧‍👧\\\n      💼👡👠☂🐶🐰🐻🐼🐷🐒🐵🐔🐧🐦🐋🐟🐡🕸🐌🐴🐊🐄🐪🐘🌸🌏🔥🌟🌚🌝💦💧\\\n      ❄🍕🍔🍟🥝🍱🕶🎩🏈⚽🚴‍♀️🎻🎼🎹🚨🚎🚐⚓🛳🚀🚁🏪🏢🖱⏰📱💾💉📉🛏🔑🔓\\\n      📁🗓📊❤💯🚫🔻♠♣🕓❗🏳🏁🏳️‍🌈🇮🇹🇱🇷🇺🇸🇬🇧🇨🇳🇧🇴\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  text_style.font_families = std::vector<std::string>(1, \"Noto Color Emoji\");\n  text_style.font_size = 50;\n  text_style.decoration = TextDecoration::kUnderline;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n\n  ASSERT_EQ(paragraph->records_.size(), 8ull);\n\n  EXPECT_EQ(paragraph->records_[0].line(), 0ull);\n  EXPECT_EQ(paragraph->records_[1].line(), 1ull);\n  EXPECT_EQ(paragraph->records_[2].line(), 2ull);\n  EXPECT_EQ(paragraph->records_[3].line(), 3ull);\n  EXPECT_EQ(paragraph->records_[7].line(), 7ull);\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(EmojiMultiLineRectsParagraph)) {\n  // clang-format off\n  const char* text =\n      \"👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧i🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸\"\n      \"👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸\"\n      \"👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸\"\n      \"👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸👩‍👩‍👦👩‍👩‍👧‍👧🇺🇸\"\n      \"❄🍕🍔🍟🥝🍱🕶🎩🏈⚽🚴‍♀️🎻🎼🎹🚨🚎🚐⚓🛳🚀🚁🏪🏢🖱⏰📱💾💉📉🛏🔑🔓\"\n      \"📁🗓📊❤💯🚫🔻♠♣🕓❗🏳🏁🏳️‍🌈🇮🇹🇱🇷🇺🇸🇬🇧🇨🇳🇧🇴\";\n  // clang-format on\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  text_style.font_families = std::vector<std::string>(1, \"Noto Color Emoji\");\n  text_style.font_size = 50;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 300);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n\n  ASSERT_EQ(paragraph->records_.size(), 10ull);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  // GetPositionForCoordinates should not return inter-emoji positions.\n  boxes = paragraph->GetRectsForRange(\n      0, paragraph->GetGlyphPositionAtCoordinate(610, 100).position,\n      rect_height_style, rect_width_style);\n  paint.setColor(SK_ColorGREEN);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 2ull);\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 622.53906);\n\n  boxes = paragraph->GetRectsForRange(\n      0, paragraph->GetGlyphPositionAtCoordinate(580, 100).position,\n      rect_height_style, rect_width_style);\n  paint.setColor(SK_ColorGREEN);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 2ull);\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 560.28516);\n\n  boxes = paragraph->GetRectsForRange(\n      0, paragraph->GetGlyphPositionAtCoordinate(560, 100).position,\n      rect_height_style, rect_width_style);\n  paint.setColor(SK_ColorGREEN);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 2ull);\n  EXPECT_FLOAT_EQ(boxes[1].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[1].rect.right(), 560.28516);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(LigatureCharacters)) {\n  const char* text = \"Office\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  // The \"ffi\" characters will be combined into one glyph in the Roboto font.\n  // Verify that the graphemes within the glyph have distinct boxes.\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(1, 2, Paragraph::RectHeightStyle::kTight,\n                                  Paragraph::RectWidthStyle::kTight);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 9.625);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 13.608073);\n\n  boxes = paragraph->GetRectsForRange(2, 4, Paragraph::RectHeightStyle::kTight,\n                                      Paragraph::RectWidthStyle::kTight);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 13.608073);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 21.574219);\n}\n\nTEST_F(ParagraphTest, HyphenBreakParagraph) {\n  const char* text =\n      \"A \"\n      \"very-very-long-Hyphen-word-to-see-where-this-will-wrap-or-if-it-will-at-\"\n      \"all-and-if-it-does-thent-hat-\"\n      \"would-be-a-good-thing-because-the-breaking.\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.break_strategy = minikin::kBreakStrategy_HighQuality;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 31;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() / 2);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_EQ(paragraph->GetLineCount(), 5ull);\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, RepeatLayoutParagraph) {\n  const char* text =\n      \"Sentence to layout at diff widths to get diff line counts. short words \"\n      \"short words short words short words short words short words short words \"\n      \"short words short words short words short words short words short words \"\n      \"end\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.break_strategy = minikin::kBreakStrategy_HighQuality;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 31;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  // First Layout.\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(300);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_EQ(paragraph->GetLineCount(), 12ull);\n\n  // Second Layout.\n  SetUp();\n  paragraph->Layout(600);\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n  ASSERT_EQ(paragraph->GetLineCount(), 6ull);\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, Ellipsize) {\n  const char* text =\n      \"This is a very long sentence to test if the text will properly wrap \"\n      \"around and go to the next line. Sometimes, short sentence. Longer \"\n      \"sentences are okay too because they are necessary. Very short. \";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.ellipsis = u\"\\u2026\";\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_TRUE(Snapshot());\n\n  // Check that the ellipsizer limited the text to one line and did not wrap\n  // to a second line.\n  ASSERT_EQ(paragraph->records_.size(), 1ull);\n}\n\n// Test for shifting when identical runs of text are built as multiple runs.\nTEST_F(ParagraphTest, UnderlineShiftParagraph) {\n  const char* text1 = \"fluttser \";\n  auto icu_text1 = icu::UnicodeString::fromUTF8(text1);\n  std::u16string u16_text1(icu_text1.getBuffer(),\n                           icu_text1.getBuffer() + icu_text1.length());\n  const char* text2 = \"mdje\";\n  auto icu_text2 = icu::UnicodeString::fromUTF8(text2);\n  std::u16string u16_text2(icu_text2.getBuffer(),\n                           icu_text2.getBuffer() + icu_text2.length());\n  const char* text3 = \"fluttser mdje\";\n  auto icu_text3 = icu::UnicodeString::fromUTF8(text3);\n  std::u16string u16_text3(icu_text3.getBuffer(),\n                           icu_text3.getBuffer() + icu_text3.length());\n\n  // Construct multi-run paragraph.\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 2;\n  paragraph_style.text_align = TextAlign::left;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style1;\n  text_style1.color = SK_ColorBLACK;\n  text_style1.font_families = std::vector<std::string>(1, \"Roboto\");\n  builder.PushStyle(text_style1);\n\n  builder.AddText(u16_text1);\n\n  txt::TextStyle text_style2;\n  text_style2.color = SK_ColorBLACK;\n  text_style2.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style2.decoration = TextDecoration::kUnderline;\n  text_style2.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style2);\n\n  builder.AddText(u16_text2);\n\n  builder.Pop();\n\n  // Construct single run paragraph.\n  txt::ParagraphBuilderTxt builder2(paragraph_style, GetTestFontCollection());\n\n  builder2.PushStyle(text_style1);\n\n  builder2.AddText(u16_text3);\n\n  builder2.Pop();\n\n  // Build multi-run paragraph\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  // Build single-run paragraph\n  auto paragraph2 = BuildParagraph(builder2);\n  paragraph2->Layout(GetTestCanvasWidth());\n\n  paragraph2->Paint(GetCanvas(), 0, 25);\n\n  ASSERT_TRUE(Snapshot());\n\n  ASSERT_EQ(paragraph->records_[0].GetRunWidth() +\n                paragraph->records_[1].GetRunWidth(),\n            paragraph2->records_[0].GetRunWidth());\n\n  auto rects1 =\n      paragraph->GetRectsForRange(0, 12, Paragraph::RectHeightStyle::kMax,\n                                  Paragraph::RectWidthStyle::kTight);\n  auto rects2 =\n      paragraph2->GetRectsForRange(0, 12, Paragraph::RectHeightStyle::kMax,\n                                   Paragraph::RectWidthStyle::kTight);\n\n  for (size_t i = 0; i < 12; ++i) {\n    auto r1 = GetCoordinatesForGlyphPosition(*paragraph, i);\n    auto r2 = GetCoordinatesForGlyphPosition(*paragraph2, i);\n\n    ASSERT_EQ(r1.fLeft, r2.fLeft);\n    ASSERT_EQ(r1.fRight, r2.fRight);\n  }\n}\n\nTEST_F(ParagraphTest, SimpleShadow) {\n  const char* text = \"Hello World Text Dialog\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  text_style.text_shadows.emplace_back(SK_ColorBLACK, SkPoint::Make(2.0, 2.0),\n                                       1.0);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n  ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull);\n  ASSERT_EQ(paragraph->runs_.styles_.size(), 2ull);\n  ASSERT_TRUE(paragraph->runs_.styles_[1].equals(text_style));\n  ASSERT_EQ(paragraph->records_[0].style().color, text_style.color);\n\n  ASSERT_EQ(paragraph->records_[0].style().text_shadows.size(), 1ull);\n  ASSERT_EQ(paragraph->records_[0].style().text_shadows[0],\n            text_style.text_shadows[0]);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, ComplexShadow) {\n  const char* text = \"Text Chunk \";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  text_style.text_shadows.emplace_back(SK_ColorBLACK, SkPoint::Make(2.0, 2.0),\n                                       1.0);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  text_style.text_shadows.emplace_back(SK_ColorRED, SkPoint::Make(2.0, 2.0),\n                                       5.0);\n  text_style.text_shadows.emplace_back(SK_ColorGREEN, SkPoint::Make(10.0, -5.0),\n                                       3.0);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n  builder.AddText(u16_text);\n\n  text_style.text_shadows.emplace_back(SK_ColorGREEN, SkPoint::Make(0.0, -1.0),\n                                       0.0);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_EQ(paragraph->text_.size(), std::string{text}.length() * 5);\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n\n  ASSERT_EQ(paragraph->records_[0].style().text_shadows.size(), 1ull);\n  ASSERT_EQ(paragraph->records_[1].style().text_shadows.size(), 3ull);\n  ASSERT_EQ(paragraph->records_[2].style().text_shadows.size(), 1ull);\n  ASSERT_EQ(paragraph->records_[3].style().text_shadows.size(), 4ull);\n  ASSERT_EQ(paragraph->records_[4].style().text_shadows.size(), 1ull);\n  for (size_t i = 0; i < 1; ++i)\n    ASSERT_EQ(paragraph->records_[0].style().text_shadows[i],\n              text_style.text_shadows[i]);\n  for (size_t i = 0; i < 3; ++i)\n    ASSERT_EQ(paragraph->records_[1].style().text_shadows[i],\n              text_style.text_shadows[i]);\n  for (size_t i = 0; i < 1; ++i)\n    ASSERT_EQ(paragraph->records_[2].style().text_shadows[i],\n              text_style.text_shadows[i]);\n  for (size_t i = 0; i < 4; ++i)\n    ASSERT_EQ(paragraph->records_[3].style().text_shadows[i],\n              text_style.text_shadows[i]);\n  for (size_t i = 0; i < 1; ++i)\n    ASSERT_EQ(paragraph->records_[4].style().text_shadows[i],\n              text_style.text_shadows[i]);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_MAC(BaselineParagraph)) {\n  const char* text =\n      \"左線読設Byg後碁給能上目秘使約。満毎冠行来昼本可必図将発確年。今属場育\"\n      \"図情闘陰野高備込制詩西校客。審対江置講今固残必託地集済決維駆年策。立得\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 14;\n  paragraph_style.text_align = TextAlign::justify;\n  paragraph_style.height = 1.5;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  text_style.font_size = 55;\n  text_style.letter_spacing = 2;\n  text_style.font_families = std::vector<std::string>(1, \"Source Han Serif CN\");\n  text_style.decoration_style = txt::TextDecorationStyle::kSolid;\n  text_style.decoration_color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 100);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n  paint.setColor(SK_ColorRED);\n  GetCanvas()->drawLine(0, paragraph->GetIdeographicBaseline(),\n                        paragraph->GetMaxWidth(),\n                        paragraph->GetIdeographicBaseline(), paint);\n\n  paint.setColor(SK_ColorGREEN);\n\n  GetCanvas()->drawLine(0, paragraph->GetAlphabeticBaseline(),\n                        paragraph->GetMaxWidth(),\n                        paragraph->GetAlphabeticBaseline(), paint);\n  ASSERT_DOUBLE_EQ(paragraph->GetIdeographicBaseline(), 79.035000801086426);\n  ASSERT_DOUBLE_EQ(paragraph->GetAlphabeticBaseline(), 63.305000305175781);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, FontFallbackParagraph) {\n  const char* text = \"Roboto 字典 \";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n  const char* text2 = \"Homemade Apple 字典\";\n  icu_text = icu::UnicodeString::fromUTF8(text2);\n  std::u16string u16_text2(icu_text.getBuffer(),\n                           icu_text.getBuffer() + icu_text.length());\n  const char* text3 = \"Chinese 字典\";\n  icu_text = icu::UnicodeString::fromUTF8(text3);\n  std::u16string u16_text3(icu_text.getBuffer(),\n                           icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  // No chinese fallback provided, should not be able to render the chinese.\n  text_style.font_families = std::vector<std::string>(1, \"Not a real font\");\n  text_style.font_families.push_back(\"Also a fake font\");\n  text_style.font_families.push_back(\"So fake it is obvious\");\n  text_style.font_families.push_back(\"Next one should be a real font...\");\n  text_style.font_families.push_back(\"Roboto\");\n  text_style.font_families.push_back(\"another fake one in between\");\n  text_style.font_families.push_back(\"Homemade Apple\");\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  // Japanese version of the chinese should be rendered.\n  text_style.font_families = std::vector<std::string>(1, \"Not a real font\");\n  text_style.font_families.push_back(\"Also a fake font\");\n  text_style.font_families.push_back(\"So fake it is obvious\");\n  text_style.font_families.push_back(\"Homemade Apple\");\n  text_style.font_families.push_back(\"Next one should be a real font...\");\n  text_style.font_families.push_back(\"Roboto\");\n  text_style.font_families.push_back(\"another fake one in between\");\n  text_style.font_families.push_back(\"Noto Sans CJK JP\");\n  text_style.font_families.push_back(\"Source Han Serif CN\");\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text2);\n\n  // Chinese font defiend first\n  text_style.font_families = std::vector<std::string>(1, \"Not a real font\");\n  text_style.font_families.push_back(\"Also a fake font\");\n  text_style.font_families.push_back(\"So fake it is obvious\");\n  text_style.font_families.push_back(\"Homemade Apple\");\n  text_style.font_families.push_back(\"Next one should be a real font...\");\n  text_style.font_families.push_back(\"Roboto\");\n  text_style.font_families.push_back(\"another fake one in between\");\n  text_style.font_families.push_back(\"Source Han Serif CN\");\n  text_style.font_families.push_back(\"Noto Sans CJK JP\");\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text3);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_TRUE(Snapshot());\n\n  ASSERT_EQ(paragraph->records_.size(), 5ull);\n  ASSERT_DOUBLE_EQ(paragraph->records_[0].GetRunWidth(), 64.2109375);\n  ASSERT_DOUBLE_EQ(paragraph->records_[1].GetRunWidth(), 139.1328125);\n  ASSERT_DOUBLE_EQ(paragraph->records_[2].GetRunWidth(), 28);\n  ASSERT_DOUBLE_EQ(paragraph->records_[3].GetRunWidth(), 62.25);\n  ASSERT_DOUBLE_EQ(paragraph->records_[4].GetRunWidth(), 28);\n  // When a different font is resolved, then the metrics are different.\n  ASSERT_TRUE(paragraph->records_[2].metrics().fTop -\n                  paragraph->records_[4].metrics().fTop !=\n              0);\n  ASSERT_TRUE(paragraph->records_[2].metrics().fAscent -\n                  paragraph->records_[4].metrics().fAscent !=\n              0);\n  ASSERT_TRUE(paragraph->records_[2].metrics().fDescent -\n                  paragraph->records_[4].metrics().fDescent !=\n              0);\n  ASSERT_TRUE(paragraph->records_[2].metrics().fBottom -\n                  paragraph->records_[4].metrics().fBottom !=\n              0);\n  ASSERT_TRUE(paragraph->records_[2].metrics().fAvgCharWidth -\n                  paragraph->records_[4].metrics().fAvgCharWidth !=\n              0);\n}\n\nTEST_F(ParagraphTest, LINUX_ONLY(StrutParagraph1)) {\n  // The chinese extra height should be absorbed by the strut.\n  const char* text = \"01234満毎冠p来É本可\\nabcd\\n満毎É行p昼本可\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.strut_font_families = std::vector<std::string>(1, \"BlahFake\");\n  paragraph_style.strut_font_families.push_back(\"ahem\");\n  paragraph_style.strut_font_size = 50;\n  paragraph_style.strut_height = 1.8;\n  paragraph_style.strut_has_height_override = true;\n  paragraph_style.strut_leading = 0.1;\n  paragraph_style.strut_enabled = true;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"ahem\");\n  text_style.font_families.push_back(\"ahem\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = .5;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_max_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 34.5, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 50);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 84.5, 0.0001);\n\n  boxes = paragraph->GetRectsForRange(0, 1, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 0, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 50);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 95);\n\n  boxes =\n      paragraph->GetRectsForRange(6, 10, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 300);\n  EXPECT_NEAR(boxes[0].rect.top(), 34.5, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 500);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 84.5, 0.0001);\n  ;\n\n  boxes = paragraph->GetRectsForRange(6, 10, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 300);\n  EXPECT_NEAR(boxes[0].rect.top(), 0, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 500);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 95);\n\n  boxes = paragraph->GetRectsForRange(14, 16, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 190, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 100);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 285);\n\n  boxes = paragraph->GetRectsForRange(20, 25, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 50);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 285);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 300);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 380);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(StrutParagraph2)) {\n  // This string is all one size and smaller than the strut metrics.\n  const char* text = \"01234ABCDEFGH\\nabcd\\nABCDEFGH\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.strut_font_families = std::vector<std::string>(1, \"ahem\");\n  paragraph_style.strut_font_size = 50;\n  paragraph_style.strut_height = 1.6;\n  paragraph_style.strut_has_height_override = true;\n  paragraph_style.strut_enabled = true;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"ahem\");\n  text_style.font_families.push_back(\"ahem\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_max_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 24, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 50);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 74, 0.0001);\n\n  boxes = paragraph->GetRectsForRange(0, 1, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 0, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 50);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 80);\n\n  boxes =\n      paragraph->GetRectsForRange(6, 10, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 300);\n  EXPECT_NEAR(boxes[0].rect.top(), 24, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 500);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 74, 0.0001);\n\n  boxes = paragraph->GetRectsForRange(6, 10, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 300);\n  EXPECT_NEAR(boxes[0].rect.top(), 0, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 500);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 80);\n\n  boxes = paragraph->GetRectsForRange(14, 16, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 160, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 100);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 240);\n\n  boxes = paragraph->GetRectsForRange(20, 25, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 50);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 240);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 300);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 320);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(StrutParagraph3)) {\n  // The strut is too small to absorb the extra chinese height, but the english\n  // second line height is increased due to strut.\n  const char* text = \"01234満毎p行来昼本可\\nabcd\\n満毎冠行来昼本可\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.strut_font_families = std::vector<std::string>(1, \"ahem\");\n  paragraph_style.strut_font_size = 50;\n  paragraph_style.strut_height = 1.2;\n  paragraph_style.strut_has_height_override = true;\n  paragraph_style.strut_enabled = true;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"ahem\");\n  text_style.font_families.push_back(\"ahem\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.font_weight = FontWeight::w500;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_max_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 8, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 50);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 58, 0.0001);\n\n  boxes = paragraph->GetRectsForRange(0, 1, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 0, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 50);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 60);\n\n  boxes =\n      paragraph->GetRectsForRange(6, 10, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 300);\n  EXPECT_NEAR(boxes[0].rect.top(), 8, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 500);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 58, 0.0001);\n\n  boxes = paragraph->GetRectsForRange(6, 10, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 300);\n  EXPECT_NEAR(boxes[0].rect.top(), 0, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 500);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 60);\n\n  boxes = paragraph->GetRectsForRange(14, 16, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 120);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 100);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 180);\n\n  boxes = paragraph->GetRectsForRange(20, 25, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 50);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 180);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 300);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 240);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(StrutForceParagraph)) {\n  // The strut is too small to absorb the extra chinese height, but the english\n  // second line height is increased due to strut.\n  const char* text = \"01234満毎冠行来昼本可\\nabcd\\n満毎冠行来昼本可\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.strut_font_families = std::vector<std::string>(1, \"ahem\");\n  paragraph_style.strut_font_size = 50;\n  paragraph_style.strut_height = 1.5;\n  paragraph_style.strut_has_height_override = true;\n  paragraph_style.strut_leading = 0.1;\n  paragraph_style.force_strut_height = true;\n  paragraph_style.strut_enabled = true;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"ahem\");\n  text_style.font_families.push_back(\"ahem\");\n  text_style.font_size = 50;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_max_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 22.5, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 50);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 72.5, 0.0001);\n\n  boxes = paragraph->GetRectsForRange(0, 1, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 0, 0.0001);\n  ;\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 50);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 80);\n\n  boxes =\n      paragraph->GetRectsForRange(6, 10, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 300);\n  EXPECT_NEAR(boxes[0].rect.top(), 22.5, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 500);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 72.5, 0.0001);\n\n  boxes = paragraph->GetRectsForRange(6, 10, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 300);\n  EXPECT_NEAR(boxes[0].rect.top(), 0, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 500);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 80);\n\n  boxes = paragraph->GetRectsForRange(14, 16, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 160, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 100);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 240);\n\n  boxes = paragraph->GetRectsForRange(20, 25, rect_height_max_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 50);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 240);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 300);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 320);\n\n  ASSERT_TRUE(Snapshot());\n}\n\n// The height override is disabled for this test. Direct metrics from the font.\nTEST_F(ParagraphTest, DISABLE_ON_WINDOWS(StrutDefaultParagraph)) {\n  const char* text = \"01234満毎冠行来昼本可\\nabcd\\n満毎冠行来昼本可\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.max_lines = 10;\n  paragraph_style.strut_font_families = std::vector<std::string>(1, \"ahem\");\n  paragraph_style.strut_font_size = 50;\n  paragraph_style.strut_height = 1.5;\n  paragraph_style.strut_leading = 0.1;\n  paragraph_style.force_strut_height = false;\n  paragraph_style.strut_enabled = true;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"ahem\");\n  text_style.font_families.push_back(\"ahem\");\n  text_style.font_size = 20;\n  text_style.letter_spacing = 0;\n  text_style.word_spacing = 0;\n  text_style.color = SK_ColorBLACK;\n  text_style.height = 1;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(550);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kTight;\n  Paragraph::RectHeightStyle rect_height_strut_style =\n      Paragraph::RectHeightStyle::kStrut;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  boxes =\n      paragraph->GetRectsForRange(0, 1, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 26.5, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 20);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 46.5, 0.0001);\n\n  boxes = paragraph->GetRectsForRange(0, 2, rect_height_strut_style,\n                                      rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_NEAR(boxes[0].rect.top(), 2.5, 0.0001);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 40);\n  EXPECT_NEAR(boxes[0].rect.bottom(), 52.5, 0.0001);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, FontFeaturesParagraph) {\n  const char* text = \"12ab\\n\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.color = SK_ColorBLACK;\n  text_style.font_features.SetFeature(\"tnum\", 1);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  text_style.font_features.SetFeature(\"tnum\", 0);\n  text_style.font_features.SetFeature(\"pnum\", 1);\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth());\n\n  paragraph->Paint(GetCanvas(), 10.0, 15.0);\n\n  ASSERT_EQ(paragraph->glyph_lines_.size(), 3ull);\n\n  // Tabular numbers should have equal widths.\n  const txt::ParagraphTxt::GlyphLine& tnum_line = paragraph->glyph_lines_[0];\n  ASSERT_EQ(tnum_line.positions.size(), 4ull);\n  EXPECT_FLOAT_EQ(tnum_line.positions[0].x_pos.width(),\n                  tnum_line.positions[1].x_pos.width());\n\n  // Proportional numbers should have variable widths.\n  const txt::ParagraphTxt::GlyphLine& pnum_line = paragraph->glyph_lines_[1];\n  ASSERT_EQ(pnum_line.positions.size(), 4ull);\n  EXPECT_NE(pnum_line.positions[0].x_pos.width(),\n            pnum_line.positions[1].x_pos.width());\n\n  // Alphabetic characters should be unaffected.\n  EXPECT_FLOAT_EQ(tnum_line.positions[2].x_pos.width(),\n                  pnum_line.positions[2].x_pos.width());\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, KhmerLineBreaker) {\n  const char* text = \"និងក្មេងចង់ផ្ទៃសមុទ្រសែនខៀវស្រងាត់\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.font_families = std::vector<std::string>(1, \"Noto Sans Khmer\");\n  text_style.font_size = 24;\n  text_style.color = SK_ColorBLACK;\n  builder.PushStyle(text_style);\n\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(200);\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  ASSERT_EQ(paragraph->glyph_lines_.size(), 3ull);\n  EXPECT_EQ(paragraph->glyph_lines_[0].positions.size(), 7ul);\n  EXPECT_EQ(paragraph->glyph_lines_[1].positions.size(), 7ul);\n  EXPECT_EQ(paragraph->glyph_lines_[2].positions.size(), 3ul);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, TextHeightBehaviorRectsParagraph) {\n  // clang-format off\n  const char* text =\n      \"line1\\nline2\\nline3\";\n  // clang-format on\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  paragraph_style.text_height_behavior =\n      txt::TextHeightBehavior::kDisableFirstAscent |\n      txt::TextHeightBehavior::kDisableLastDescent;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 30;\n  text_style.height = 5;\n  text_style.has_height_override = true;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 300);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  for (size_t i = 0; i < u16_text.length(); i++) {\n    ASSERT_EQ(paragraph->text_[i], u16_text[i]);\n  }\n\n  ASSERT_EQ(paragraph->records_.size(), 3ull);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 0, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 0ull);\n\n  // First line. Shorter due to disabled height modifications on first ascent.\n  boxes =\n      paragraph->GetRectsForRange(0, 3, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 31.117188);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), -0.08203125);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom() - boxes[0].rect.top(), 59.082031);\n\n  // Second line. Normal.\n  boxes =\n      paragraph->GetRectsForRange(6, 10, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 47.011719);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 59);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 209);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom() - boxes[0].rect.top(), 150);\n\n  // Third line. Shorter due to disabled height modifications on last descent\n  boxes =\n      paragraph->GetRectsForRange(12, 17, rect_height_style, rect_width_style);\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  EXPECT_EQ(boxes.size(), 1ull);\n  EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);\n  EXPECT_FLOAT_EQ(boxes[0].rect.right(), 63.859375);\n  EXPECT_FLOAT_EQ(boxes[0].rect.top(), 208.92578);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 335);\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom() - boxes[0].rect.top(), 126.07422);\n\n  ASSERT_TRUE(Snapshot());\n}\n\nTEST_F(ParagraphTest, MixedTextHeightBehaviorRectsParagraph) {\n  const char* text = \"0123456789\";\n  auto icu_text = icu::UnicodeString::fromUTF8(text);\n  std::u16string u16_text(icu_text.getBuffer(),\n                          icu_text.getBuffer() + icu_text.length());\n\n  txt::ParagraphStyle paragraph_style;\n  // The paragraph's first line and the last line use the font's ascent/descent.\n  paragraph_style.text_height_behavior =\n      txt::TextHeightBehavior::kDisableFirstAscent |\n      txt::TextHeightBehavior::kDisableLastDescent;\n\n  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());\n\n  txt::TextStyle text_style;\n  text_style.color = SK_ColorBLACK;\n  text_style.font_families = std::vector<std::string>(1, \"Roboto\");\n  text_style.font_size = 30;\n  text_style.height = 5;\n  text_style.has_height_override = true;\n  text_style.half_leading = true;\n\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  text_style.half_leading = false;\n  builder.PushStyle(text_style);\n  builder.AddText(u16_text);\n\n  // 2 identical runs except the first run has half-leading enabled.\n  builder.Pop();\n\n  auto paragraph = BuildParagraph(builder);\n  paragraph->Layout(GetTestCanvasWidth() - 300);\n\n  paragraph->Paint(GetCanvas(), 0, 0);\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setAntiAlias(true);\n  paint.setStrokeWidth(1);\n\n  // Tests for GetRectsForRange()\n  Paragraph::RectHeightStyle rect_height_style =\n      Paragraph::RectHeightStyle::kMax;\n  Paragraph::RectWidthStyle rect_width_style =\n      Paragraph::RectWidthStyle::kTight;\n  paint.setColor(SK_ColorRED);\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph->GetRectsForRange(0, 20, rect_height_style, rect_width_style);\n\n  for (size_t i = 0; i < boxes.size(); ++i) {\n    GetCanvas()->drawRect(boxes[i].rect, paint);\n  }\n  // The kDisableAll flag is applied.\n  EXPECT_GT(boxes.size(), 1ull);\n  // The height of the line equals to the metrics height of the font\n  // (ascent + descent).\n  EXPECT_FLOAT_EQ(boxes[0].rect.bottom() - boxes[0].rect.top(),\n                  27.8320312 + 7.32421875);\n\n  ASSERT_TRUE(Snapshot());\n}\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/tests/platform_fuchsia_unittests.cc",
    "content": "/*\n * Copyright 2021 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <lib/async-loop/cpp/loop.h>\n#include <lib/async-loop/default.h>\n#include <lib/async-testing/test_loop.h>\n\n#include \"fake_provider.h\"\n#include \"gtest/gtest.h\"\n#include \"txt/platform.h\"\n\nnamespace txt {\n\nclass PlatformFuchsiaTest : public ::testing::Test {\n protected:\n  PlatformFuchsiaTest() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {\n    loop_.StartThread();\n  }\n\n  async::Loop& loop() { return loop_; }\n\n  FakeProvider& fake_provider() { return fake_provider_; }\n\n  fidl::InterfaceHandle<fuchsia::fonts::Provider> GetProvider() {\n    return fake_provider_.Bind(loop_.dispatcher());\n  }\n\n  void TearDown() override {\n    loop_.Quit();\n    loop_.JoinThreads();\n  }\n\n private:\n  async::Loop loop_;  // Must come before FIDL bindings.\n  FakeProvider fake_provider_;\n};\n\nTEST_F(PlatformFuchsiaTest, GetDefaultFontManager) {\n  zx_handle_t handle = GetProvider().TakeChannel().release();\n  auto font_manager = GetDefaultFontManager(handle);\n\n  // Nonnull font initialization data should not create SkFontMgr::RefDefault().\n  EXPECT_NE(font_manager, SkFontMgr::RefDefault());\n\n  // Check to see that our font provider was called.\n  EXPECT_FALSE(fake_provider().WasInvoked());\n  font_manager->matchFamily(\"Invalid font.\");\n  EXPECT_TRUE(fake_provider().WasInvoked());\n}\n\nTEST_F(PlatformFuchsiaTest, GetDefaultFontManagerFail) {\n  // Null font initialization data should create SkFontMgr::RefDefault().\n  EXPECT_EQ(GetDefaultFontManager(0), SkFontMgr::RefDefault());\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/tests/render_test.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"render_test.h\"\n\n#include <string>\n\n#include \"clay/fml/logging.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"third_party/skia/include/core/SkEncodedImageFormat.h\"\n#include \"third_party/skia/include/core/SkImageEncoder.h\"\n#include \"third_party/skia/include/core/SkStream.h\"\n#include \"txt/asset_font_manager_skia.h\"\n#include \"txt/font_collection_skia.h\"\n#include \"txt_test_utils.h\"\n\nnamespace txt {\n\nRenderTest::RenderTest()\n    : snapshots_(0), font_collection_(txt::GetTestFontCollection()) {}\n\nRenderTest::~RenderTest() = default;\n\nSkCanvas* RenderTest::GetCanvas() {\n  return canvas_ == nullptr ? nullptr : canvas_.get();\n}\n\nstd::string RenderTest::GetNextSnapshotName() {\n  const auto& test_info =\n      ::testing::UnitTest::GetInstance()->current_test_info();\n\n  std::stringstream stream;\n  stream << \"snapshots/\" << test_info->test_case_name() << \"_\"\n         << test_info->name();\n  stream << \"_\" << ++snapshots_ << \".png\";\n\n  return stream.str();\n}\n\nbool RenderTest::Snapshot() {\n  if (!canvas_ || !bitmap_) {\n    return false;\n  }\n  std::string snapshot_dir = \"snapshots\";\n  int error = 0;\n// _WIN32 defined by Windows Visual compiler.\n#if defined(_WIN32)\n  // Handle windows path creation.\n  error = _mkdir(snapshot_dir.c_str());\n#else\n  // Handle non-windows path creation with Unix permissions.\n  mode_t permissions = 0733;\n  error = mkdir(snapshot_dir.c_str(), permissions);\n#endif\n  if (error > 0) {\n    FML_LOG(ERROR) << \"'snapshot/' Directory not available and could not be \"\n                      \"created. Please create manually to save snapshot.\";\n    return false;\n  }\n  auto file_name = GetNextSnapshotName();\n  SkFILEWStream file(file_name.c_str());\n  return SkEncodeImage(&file, *bitmap_, SkEncodedImageFormat::kPNG, 100);\n}\n\nsize_t RenderTest::GetTestCanvasWidth() const {\n  return 1000;\n}\n\nsize_t RenderTest::GetTestCanvasHeight() const {\n  return 600;\n}\n\nvoid RenderTest::SetUp() {\n  bitmap_ = std::make_unique<SkBitmap>();\n  bitmap_->allocN32Pixels(GetTestCanvasWidth(), GetTestCanvasHeight());\n  canvas_ = std::make_unique<SkCanvas>(*bitmap_);\n  canvas_->clear(SK_ColorWHITE);\n}\n\nstd::shared_ptr<FontCollection> RenderTest::GetTestFontCollection() const {\n  return font_collection_;\n}\n\nvoid RenderTest::TearDown() {\n  canvas_ = nullptr;\n  bitmap_ = nullptr;\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/tests/render_test.h",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_TESTS_RENDER_TEST_H_\n#define CLAY_THIRD_PARTY_TXT_TESTS_RENDER_TEST_H_\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkBitmap.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n#include \"txt/font_collection_skia.h\"\n\nnamespace txt {\n\nclass RenderTest : public ::testing::Test {\n public:\n  RenderTest();\n\n  ~RenderTest();\n\n  SkCanvas* GetCanvas();\n\n  bool Snapshot();\n\n  size_t GetTestCanvasWidth() const;\n\n  size_t GetTestCanvasHeight() const;\n\n  std::shared_ptr<txt::FontCollection> GetTestFontCollection() const;\n\n protected:\n  void SetUp() override;\n\n  void TearDown() override;\n\n private:\n  size_t snapshots_;\n  std::unique_ptr<SkBitmap> bitmap_;\n  std::unique_ptr<SkCanvas> canvas_;\n  std::shared_ptr<txt::FontCollection> font_collection_;\n\n  std::string GetNextSnapshotName();\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(RenderTest);\n};\n\n}  // namespace txt\n#endif  // CLAY_THIRD_PARTY_TXT_TESTS_RENDER_TEST_H_\n"
  },
  {
    "path": "clay/third_party/txt/tests/txt_run_all_unittests.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include <cassert>\n\n#include \"clay/fml/backtrace.h\"\n#include \"clay/fml/command_line.h\"\n#include \"clay/fml/icu_util.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/testing/testing.h\"\n#include \"third_party/skia/include/core/SkGraphics.h\"\n#include \"txt_test_utils.h\"\n\nint main(int argc, char** argv) {\n  fml::InstallCrashHandler();\n  fml::CommandLine cmd = fml::CommandLineFromPlatformOrArgcArgv(argc, argv);\n  txt::SetCommandLine(cmd);\n  txt::SetFontDir(clay::testing::GetFixturesPath());\n  if (txt::GetFontDir().length() <= 0) {\n    FML_LOG(ERROR) << \"Font directory not set via txt::SetFontDir.\";\n    return EXIT_FAILURE;\n  }\n  FML_DCHECK(txt::GetFontDir().length() > 0);\n#if defined(OS_FUCHSIA)\n  fml::icu::InitializeICU(\"/pkg/data/icudtl.dat\");\n#else\n  std::string icudtl_path =\n      cmd.GetOptionValueWithDefault(\"icu-data-file-path\", \"icudtl.dat\");\n  fml::icu::InitializeICU(icudtl_path);\n#endif\n  SkGraphics::Init();\n  testing::InitGoogleTest(&argc, argv);\n  return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "clay/third_party/txt/tests/txt_test_utils.cc",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#include \"txt_test_utils.h\"\n\n#include <sstream>\n\n#include \"third_party/skia/include/core/SkTypeface.h\"\n#include \"txt/asset_font_manager_skia.h\"\n#include \"txt/typeface_font_asset_provider_skia.h\"\n#include \"utils/MacUtils.h\"\n#include \"utils/WindowsUtils.h\"\n\n#if !defined(_WIN32)\n#include <dirent.h>\n#endif\n\nnamespace txt {\n\nstatic std::string gFontDir;\nstatic fml::CommandLine gCommandLine;\n\nconst std::string& GetFontDir() {\n  return gFontDir;\n}\n\nvoid SetFontDir(const std::string& dir) {\n  gFontDir = dir;\n}\n\nconst fml::CommandLine& GetCommandLineForProcess() {\n  return gCommandLine;\n}\n\nvoid SetCommandLine(fml::CommandLine cmd) {\n  gCommandLine = std::move(cmd);\n}\n\nvoid RegisterFontsFromPath(TypefaceFontAssetProvider& font_provider,\n                           std::string directory_path) {\n#if defined(_WIN32)\n  std::string path = directory_path + \"\\\\*\";\n  WIN32_FIND_DATAA ffd;\n  HANDLE directory = FindFirstFileA(path.c_str(), &ffd);\n  if (directory == INVALID_HANDLE_VALUE) {\n    return;\n  }\n\n  do {\n    if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {\n      continue;\n    }\n\n    std::string file_name(ffd.cFileName);\n\n    std::stringstream file_path;\n    file_path << directory_path << \"/\" << file_name;\n\n    font_provider.RegisterTypeface(\n        SkTypeface::MakeFromFile(file_path.str().c_str()));\n  } while (FindNextFileA(directory, &ffd) != 0);\n\n  // TODO(bkonyi): check for error here?\n  FindClose(directory);\n#else\n  auto directory_closer = [](DIR* directory) {\n    if (directory != nullptr) {\n      ::closedir(directory);\n    }\n  };\n\n  std::unique_ptr<DIR, decltype(directory_closer)> directory(\n      ::opendir(directory_path.c_str()), directory_closer);\n\n  if (directory == nullptr) {\n    return;\n  }\n\n  for (struct dirent* entry = ::readdir(directory.get()); entry != nullptr;\n       entry = ::readdir(directory.get())) {\n    if (entry->d_type != DT_REG) {\n      continue;\n    }\n\n    std::string file_name(entry->d_name);\n\n    std::stringstream file_path;\n    file_path << directory_path << \"/\" << file_name;\n\n    font_provider.RegisterTypeface(\n        SkTypeface::MakeFromFile(file_path.str().c_str()));\n  }\n#endif\n}\n\nstd::shared_ptr<FontCollection> GetTestFontCollection() {\n  std::unique_ptr<TypefaceFontAssetProvider> font_provider =\n      std::make_unique<TypefaceFontAssetProvider>();\n  RegisterFontsFromPath(*font_provider, GetFontDir());\n\n  std::shared_ptr<FontCollection> collection =\n      std::make_shared<FontCollection>();\n  // collection->SetAssetFontManager(\n  //     sk_make_sp<AssetFontManager>(std::move(font_provider)));\n\n  return collection;\n}\n\n// Build a paragraph and return it as a ParagraphTxt usable by tests that need\n// access to ParagraphTxt internals.\nstd::unique_ptr<ParagraphTxt> BuildParagraph(\n    txt::ParagraphBuilderTxt& builder) {\n  return std::unique_ptr<txt::ParagraphTxt>(\n      static_cast<txt::ParagraphTxt*>(builder.Build().release()));\n}\n\n}  // namespace txt\n"
  },
  {
    "path": "clay/third_party/txt/tests/txt_test_utils.h",
    "content": "/*\n * Copyright 2017 Google, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CLAY_THIRD_PARTY_TXT_TESTS_TXT_TEST_UTILS_H_\n#define CLAY_THIRD_PARTY_TXT_TESTS_TXT_TEST_UTILS_H_\n#include <memory>\n#include <string>\n\n#include \"clay/fml/command_line.h\"\n#include \"txt/font_collection_skia.h\"\n#include \"txt/paragraph_builder_txt.h\"\n#include \"txt/paragraph_txt.h\"\n\nnamespace txt {\n\nconst std::string& GetFontDir();\n\nvoid SetFontDir(const std::string& dir);\n\nconst fml::CommandLine& GetCommandLineForProcess();\n\nvoid SetCommandLine(fml::CommandLine cmd);\n\nstd::shared_ptr<FontCollection> GetTestFontCollection();\n\nstd::unique_ptr<ParagraphTxt> BuildParagraph(ParagraphBuilderTxt& builder);\n\n}  // namespace txt\n#endif  // CLAY_THIRD_PARTY_TXT_TESTS_TXT_TEST_UTILS_H_\n"
  },
  {
    "path": "clay/tools/make_keywords.py",
    "content": "#!/usr/bin/env python3\n# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport io\nimport os\nimport subprocess\nimport sys\nimport tempfile\n\nLICENSE = \"\"\"// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n//\n// DO NOT MODIFY THIS FILE! AUTO GENERATED BY clay/tools/make_keywords.py\n//\n\"\"\"\n\nH_TEMPLATE = \"\"\"%(license)s\n#ifndef CLAY_UI_COMPONENT_KEYWORDS_H_\n#define CLAY_UI_COMPONENT_KEYWORDS_H_\n\n#include <string>\n\nnamespace clay {\n\nenum class KeywordID {\n  kInvalid = -1,\n  %(id_list)s\n};\n\nKeywordID GetKeywordID(const std::string &str);\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_KEYWORDS_H_\n\"\"\"\n\nGPERF_TEMPLATE = \"\"\"%%language=C++\n%%define class-name KeywordHash\n%%define lookup-function-name find\n%%define string-pool-name StringPool\n%%struct-type\n%%global-table\n%%readonly-tables\n%%pic\n\n%%{\n%(license)s\n#include \"clay/ui/component/keywords.h\"\n\n#include <string>\n\nnamespace clay {\n%%}\n\nstruct TokenValue {\n  short name;\n  short id;\n};\n\n%%%%\n%(str2id_list)s\n%%%%\n\nstatic_assert(sizeof(StringPool_contents) < 32767);\n\nKeywordID GetKeywordID(const std::string& str) {\n  auto *tk = KeywordHash::find(str.c_str(), str.size());\n  return tk ? static_cast<KeywordID>(tk->id) : KeywordID::kInvalid;\n}\n\n}  // namespace clay\n\"\"\"\n\ndef camelcase_name(name):\n    return ''.join(w.capitalize() for w in name.split('-'))\n\ndef main():\n    base_dir = os.path.join('lynx','clay', 'ui', 'component')\n    keywords_in = os.path.join(base_dir, 'keywords.in')\n    keywords_h = os.path.join(base_dir, 'keywords.h')\n    keywords_cc = os.path.join(base_dir, 'keywords.cc')\n    kw_list = []\n    id_list = []\n    str2id_list = []\n    f = io.open(keywords_in, 'r+', encoding='utf-8')\n    for line in f.readlines():\n        token = line.strip()\n        if token.startswith('//'):\n            continue\n        enum = 'k' + camelcase_name(token)\n        kw_list.append(token)\n        id_list.append(enum)\n        str2id_list.append(token + ', (short)KeywordID::' + enum)\n    f.close()\n\n    # generate keywords.h\n    f = io.open(keywords_h, 'w', encoding='utf-8')\n    f.write(H_TEMPLATE % { 'license': LICENSE, 'id_list': ',\\n  '.join(id_list)})\n    f.close()\n\n    # generate keywords.gperf\n    tmp = tempfile.NamedTemporaryFile(delete=False, mode=\"wt\", encoding='utf-8')\n    keywords_gperf = tmp.name\n    tmp.write(GPERF_TEMPLATE % { 'license': LICENSE, 'str2id_list': '\\n'.join(str2id_list)})\n    tmp.close()\n\n    # generate keywords.cc\n    subprocess.check_call(['gperf', keywords_gperf, '-m', '100', '--output-file=' + keywords_cc])\n\n    ## post processes\n    lines = []\n    f = io.open(keywords_cc, 'r+', encoding='utf-8')\n    for line in f.readlines():\n        if line.startswith('#line') or line.startswith('/* Command-line:'):\n            continue\n        line = line.replace('(int)', '(short)').replace('register ', '')\n        lines.append(line)\n    f.close()\n\n    ## write back\n    f = io.open(keywords_cc, 'w', encoding='utf-8')\n    f.write(''.join(lines))\n    f.close()\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "clay/ui/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../config.gni\")\nimport(\"../testing/testing.gni\")\nimport(\"common/build.gni\")\nimport(\"component/build.gni\")\nimport(\"compositing/build.gni\")\nimport(\"event/build.gni\")\nimport(\"gesture/build.gni\")\nimport(\"lynx_module/build.gni\")\nimport(\"painter/build.gni\")\nimport(\"platform/build.gni\")\nimport(\"public/build.gni\")\nimport(\"rendering/build.gni\")\nimport(\"resource/build.gni\")\nimport(\"semantics/build.gni\")\nimport(\"shadow/build.gni\")\nimport(\"window/build.gni\")\n\nconfig(\"ui_defines\") {\n  defines = []\n  defines += [ \"CLAY_LIBRARY_IMPLEMENTATION\" ]\n\n  if (is_win) {\n    # Required for M_PI and others.\n    defines += [ \"_USE_MATH_DEFINES\" ]\n\n    # For wstring_conversion. See issue #50053.\n    defines += [ \"_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING\" ]\n\n    cflags = [ \"-Wno-c99-designator\" ]\n  }\n}\n\nsource_set(\"ui\") {\n  sources = [\n    \"render_delegate.h\",\n    \"ui_rendering_backend.h\",\n  ]\n  sources += rebase_path(ui_common_sources, \"\", \"common\")\n  sources += rebase_path(ui_component_sources, \"\", \"component\")\n  sources += rebase_path(ui_compositing_sources, \"\", \"compositing\")\n  sources += rebase_path(ui_event_sources, \"\", \"event\")\n  sources += rebase_path(ui_gesture_sources, \"\", \"gesture\")\n  sources += rebase_path(ui_lynx_module_sources, \"\", \"lynx_module\")\n  sources += rebase_path(ui_painter_sources, \"\", \"painter\")\n  sources += rebase_path(ui_platform_sources, \"\", \"platform\")\n  sources += rebase_path(ui_public_sources, \"\", \"public\")\n  sources += rebase_path(ui_rendering_sources, \"\", \"rendering\")\n  sources += rebase_path(ui_resource_sources, \"\", \"resource\")\n  sources += rebase_path(ui_shadow_sources, \"\", \"shadow\")\n  sources += rebase_path(ui_window_sources, \"\", \"window\")\n\n  configs += [ \":ui_defines\" ]\n\n  if (use_accessbility) {\n    sources += rebase_path(ui_semantics_sources, \"\", \"semantics\")\n  }\n\n  if (enable_clay_lite || enable_clay_standalone || is_win || is_mac ||\n      is_ios) {\n    sources += rebase_path(ui_platform_fallback_sources, \"\", \"platform\")\n  }\n  if (!enable_clay_lite) {\n    sources += rebase_path(ui_extended_component_sources, \"\", \"component\")\n    sources += rebase_path(ui_extended_rendering_sources, \"\", \"rendering\")\n    sources += rebase_path(ui_extended_shadow_sources, \"\", \"shadow\")\n  }\n\n  if (enable_unittests) {\n    sources += rebase_path(ui_component_unittests_sources, \"\", \"component\")\n  }\n\n  if (use_net_loader) {\n    sources += rebase_path(ui_net_loader_sources, \"\", \"platform\")\n  }\n\n  deps = [\n    \"../common\",\n    \"../common/graphics\",\n    \"../fml:fml\",\n    \"../shell/platform/common:common_cpp_input\",\n  ]\n\n  public_deps = [\n    \"../fml:fml\",\n    \"../net:net\",\n    \"../third_party/txt\",\n  ]\n\n  public_configs = [\n    \"../:config\",\n    \"../../base/src:base_public_config\",\n  ]\n\n  if (enable_skity) {\n    public_deps += [ \"../gfx/skity\" ]\n  } else {\n    sources += [\n      \"../gfx/skia/skia_concurrent_executor.cc\",\n      \"../gfx/skia/skia_concurrent_executor.h\",\n    ]\n    public_deps += [ \"//third_party/skia\" ]\n  }\n\n  include_dirs = []\n}\n\nsource_set(\"ui_platform_fallback\") {\n  sources = []\n  sources += rebase_path(ui_platform_fallback_sources, \"\", \"platform\")\n\n  deps = [\n    \":ui\",\n    \"../common\",\n    \"../flow\",\n    \"../gfx\",\n    \"../shell/common\",\n  ]\n\n  configs += [ \":ui_defines\" ]\n}\n\nsource_set(\"ui_fixture_sources\") {\n  testonly = true\n\n  sources = [\n    \"testing/test_utils.cc\",\n    \"testing/ui_test.cc\",\n  ]\n\n  if (!enable_clay_lite && !enable_clay_standalone && !is_win && !is_mac) {\n    sources += rebase_path(ui_platform_fallback_sources, \"\", \"platform\")\n  }\n\n  deps = [\n    \":ui\",\n    \"../common\",\n    \"../flow\",\n    \"../gfx\",\n    \"../shell/common\",\n    \"../testing\",\n  ]\n\n  configs += [ \":ui_defines\" ]\n}\n\nif (enable_unittests) {\n  test_fixtures(\"ui_fixtures\") {\n    fixtures = []\n  }\n\n  executable(\"clay_unittests\") {\n    testonly = true\n    sources = [\n      \"common/attribute_utils_unittests.cc\",\n      \"component/base_view_unittests.cc\",\n      \"component/editable/textarea_ng_view_unittests.cc\",\n      \"component/keyframe_animator_unittests.cc\",\n      \"component/list/base_list_view_unittests.cc\",\n      \"component/list/focus_list_view_unittests.cc\",\n      \"component/list/list_item_length_cache_unittests.cc\",\n      \"component/native_view_unittests.cc\",\n      \"component/nested_scroll/nested_scrollable_unittests.cc\",\n      \"component/overlay_view_unittests.cc\",\n      \"component/page_view_unittests.cc\",\n      \"component/scroll_view_unittests.cc\",\n      \"compositing/frame_builder_unittests.cc\",\n      \"compositing/pending_container_layer_unittests.cc\",\n      \"compositing/pending_layer_unittests.cc\",\n      \"compositing/testing/mock_pending_layer.h\",\n      \"event/focus_isolate_unittests.cc\",\n      \"event/focus_manager_unittests.cc\",\n      \"event/gesture_event_unittests.cc\",\n      \"gesture/gesture_manager_unittests.cc\",\n      \"gesture/mouse_region_manager_unittests.cc\",\n      \"gesture/velocity_tracker_unittests.cc\",\n      \"lynx_module/lynx_module_unittests.cc\",\n      \"lynx_module/lynx_ui_method_unittests.cc\",\n      \"painter/box_painter_unittests.cc\",\n      \"painter/gradient_unittests.cc\",\n      \"painter/image_painter_unittests.cc\",\n      \"painter/painter_graphics_test.cc\",\n      \"painter/painter_graphics_test.h\",\n      \"painter/painting_context_unittests.cc\",\n      \"rendering/abstract_node_unittests.cc\",\n      \"rendering/render_box_unittests.cc\",\n      \"rendering/render_object_child_list_unittests.cc\",\n      \"rendering/render_object_unittests.cc\",\n      \"resource/font_resource_manager_unittests.cc\",\n      \"shadow/text_unittests.cc\",\n      \"testing/test_utils_unittests.cc\",\n    ]\n\n    public_configs = [\n      \"../:export_dynamic_symbols\",\n      \"../:config\",\n    ]\n\n    public_deps = [\n      \":ui\",\n      \":ui_fixture_sources\",\n      \":ui_fixtures\",\n      \"../../base/src:base_static\",\n      \"../common/service\",\n      \"../flow\",\n      \"../gfx\",\n      \"../testing\",\n      \"../testing:skia\",\n      \"//third_party/skia/modules/skparagraph:skparagraph\",\n    ]\n  }\n}\n"
  },
  {
    "path": "clay/ui/common/attribute_utils.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/attribute_utils.h\"\n\n#include <algorithm>\n#include <cstring>\n#include <iomanip>\n#include <string>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/fml/macros.h\"\n#include \"base/include/string/quickjs_dtoa.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/common/value_utils.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\nnamespace attribute_utils {\n\n#define RETURN_DEFAULT_IF_NULL(value, def_val) \\\n  do {                                         \\\n    if (IsNullOrInvalid(value)) {              \\\n      return def_val;                          \\\n    }                                          \\\n  } while (false)\n\ntemplate <typename T>\nT GetNumber(const clay::Value& value, T default_val = 0) {\n  RETURN_DEFAULT_IF_NULL(value, default_val);\n  if (value.IsInt()) {\n    return value.GetInt();\n  } else if (value.IsUint()) {\n    return value.GetUint();\n  } else if (value.IsLong()) {\n    return value.GetLong();\n  } else if (value.IsDouble()) {\n    return value.GetDouble();\n  } else if (value.IsFloat()) {\n    return value.GetFloat();\n  } else if (value.IsString()) {\n    double v;\n    if (lynx::base::StringToDouble(value.GetString(), v)) {\n      return v;\n    } else {\n      FML_DLOG(ERROR) << \"Failed convert \" << value.GetString() << \" to double\";\n    }\n  } else {\n    FML_UNREACHABLE();\n  }\n  return default_val;\n}\n\ndouble GetNum(const clay::Value& value, double default_val) {\n  return GetNumber(value, default_val);\n}\n\nint GetInt(const clay::Value& value, int default_val) {\n  return GetNumber(value, default_val);\n}\n\nuint32_t GetUint(const clay::Value& value, uint32_t default_val) {\n  return GetNumber(value, default_val);\n}\n\nint64_t GetLong(const clay::Value& value, int64_t default_val) {\n  return GetNumber(value, default_val);\n}\n\n// Lynx has no float value\ndouble GetDouble(const clay::Value& value, double default_val) {\n  return GetNumber(value, default_val);\n}\n\nstd::string GetCString(const clay::Value& value, std::string default_val) {\n  RETURN_DEFAULT_IF_NULL(value, default_val);\n  std::string str;\n  switch (value.type()) {\n    case clay::Value::kString: {\n      str = value.GetString();\n      break;\n    }\n    case clay::Value::kInt:\n    case clay::Value::kUInt:\n    case clay::Value::kLong:\n    case clay::Value::kFloat:\n    case clay::Value::kDouble: {\n      char tmp[128];\n      js_dtoa(tmp, GetNumber<double>(value));\n      str = tmp;\n      break;\n    }\n    case clay::Value::kBool: {\n      str = value.GetBool() ? \"true\" : \"false\";\n      break;\n    }\n    case clay::Value::kPointer:\n    default:\n      FML_DLOG(ERROR) << \"clay::Value to string not support type\";\n      break;\n  }\n  return str;\n}\n\nbool GetBool(const clay::Value& value, bool default_val) {\n  RETURN_DEFAULT_IF_NULL(value, default_val);\n  if (LIKELY(value.IsBool())) {\n    return value.GetBool();\n  } else if (value.IsString()) {\n    auto& str = value.GetString();\n    if (str == attr_value::kTrue || str == attr_value::kYes) {\n      return true;\n    } else if (str == attr_value::kFalse || str == attr_value::kNo) {\n      return false;\n    } else {\n      return default_val;\n    }\n  } else if (value.IsPointer()) {\n    return (value.GetPointer() != nullptr);\n  } else {\n    double res = 0.0;\n    res = GetNumber(value, res /*default value*/);\n    return (res > 0.0);\n  }\n}\n\nbool TryGetNum(const clay::Value& value, double& result, double default_val) {\n  if (IsNull(value)) {\n    result = default_val;\n    return true;\n  } else if (value.IsInt()) {\n    result = static_cast<double>(value.GetInt());\n    return true;\n  } else if (value.IsUint()) {\n    result = static_cast<double>(value.GetUint());\n    return true;\n  } else if (value.IsLong()) {\n    result = static_cast<double>(value.GetLong());\n    return true;\n  } else if (value.IsFloat()) {\n    result = static_cast<double>(value.GetFloat());\n    return true;\n  } else if (value.IsDouble()) {\n    result = value.GetDouble();\n    return true;\n  } else if (value.IsString()) {\n    if (lynx::base::StringToDouble(value.GetString(), result)) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool TryGetString(const clay::Value& value, std::string& result,\n                  std::string default_val) {\n  if (IsNull(value)) {\n    result = default_val;\n  } else {\n    result = GetCString(value, default_val);\n  }\n  if (result.empty()) {\n    return false;\n  }\n  return true;\n}\n\nstatic bool CheckLengthUnit(const std::string& input_copy, char* endptr,\n                            Unit& unit) {\n  if (input_copy.c_str() + input_copy.size() == endptr) {\n    unit = Unit::kNone;\n    return true;\n  }\n\n  if (!endptr) {\n    return false;\n  }\n\n  std::string str_endptr(endptr, endptr + strlen(endptr));\n  str_endptr = lynx::base::TrimString(str_endptr);\n  if (str_endptr == \"px\") {\n    unit = Unit::kPx;\n    return true;\n  }\n  if (str_endptr == \"ppx\") {\n    unit = Unit::kPpx;\n    return true;\n  }\n  if (str_endptr == \"rpx\") {\n    unit = Unit::kRpx;\n    return true;\n  }\n  if (str_endptr == \"em\") {\n    unit = Unit::kEm;\n    return true;\n  }\n  if (str_endptr == \"rem\") {\n    unit = Unit::kRem;\n    return true;\n  }\n  if (str_endptr == \"%\") {\n    unit = Unit::kPercent;\n    return true;\n  }\n  return false;\n}\n\n// Widgets like <x-input> may send string as value like \" 50 px \",\n// so we should get the value with unit and transform the value\n// after use `ToPxWithDisplayMetrics`\nbool TryGetLength(const clay::Value& value, Length& result,\n                  Length default_val) {\n  if (IsNull(value)) {\n    result = default_val;\n    return true;\n  }\n  // when lynx sends value like \" 50px \", it is a string type\n  if (value.IsString()) {\n    std::string input;\n    if (!TryGetString(value, input)) {\n      return false;\n    }\n    result.val = 0.0;\n    std::string input_copy = lynx::base::TrimString(input);\n    char* endptr = nullptr;\n    errno = 0;\n    double d = strtod(input_copy.c_str(), &endptr);\n    Unit unit = Unit::kOther;\n    bool valid = (errno == 0 && !input_copy.empty() &&\n                  CheckLengthUnit(input_copy, endptr, unit));\n    if (valid) {\n      result.val = d;\n      result.unit = unit;\n    }\n    return valid;\n  } else if (value.IsDouble() || value.IsInt() || value.IsUint() ||\n             value.IsFloat() || value.IsLong()) {\n    result.unit = Unit::kNone;\n    return TryGetNum(value, result.val, default_val.val);\n  }\n  return false;\n}\n\nbool TryGetPlatformLength(const clay::Value::Array& array, const int index,\n                          ClayPlatformLength& result,\n                          ClayPlatformLength default_val) {\n  int len = array.size();\n  if (index + 1 >= len) {\n    result = default_val;\n    return false;\n  }\n  if (!array[index].IsDouble() || !array[index + 1].IsInt()) {\n    result = default_val;\n    return false;\n  }\n  result.value = GetDouble(array[index]);\n  result.unit = static_cast<ClayPlatformLengthUnit>(GetInt(array[index + 1]));\n  return true;\n}\n\nfloat ResolvePlatformLength(const ClayPlatformLength& length, float size) {\n  return length.unit == ClayPlatformLengthUnit::kNumber ? length.value\n                                                        : length.value * size;\n}\n\nconst clay::Value::Array& GetArray(const clay::Value& value) {\n  static const clay::Value::Array default_val;\n  RETURN_DEFAULT_IF_NULL(value, default_val);\n  FML_DCHECK(value.IsArray());\n  if (!value.IsArray()) {\n    return default_val;\n  }\n  return value.GetArray();\n}\n\nconst clay::Value::Map& GetMap(const clay::Value& value) {\n  static const clay::Value::Map default_val{};\n  RETURN_DEFAULT_IF_NULL(value, default_val);\n  FML_DCHECK(value.IsMap());\n  if (!value.IsMap()) {\n    return default_val;\n  }\n  return value.GetMap();\n}\n\nconst clay::Value& GetMapItem(const clay::Value::Map& map,\n                              const std::string& key) {\n  const auto it = map.find(key);\n  if (it != map.end()) {\n    return it->second;\n  }\n  return GetNullClayValue();\n}\n\nPoint GetPoint(const clay::Value& value) {\n  Point point;\n  if (!value.IsString()) {\n    return point;\n  }\n  std::string_view view(value.GetString());\n\n  size_t pos = view.find(\",\");\n  if (pos == std::string_view::npos || pos >= view.size() - 1) {\n    return point;\n  }\n\n  point.SetX(atoi(view.data()));\n  point.SetY(atoi(view.data() + pos + 1));\n  return point;\n}\n\n// Get the resolved length value in clay pixels. Currently supports px, rpx, ppx\nfloat ToPxWithDisplayMetrics(const Length& value_with_unit,\n                             const PageView* page_view, float view_length) {\n  double value = value_with_unit.val;\n  Unit unit = value_with_unit.unit;\n  switch (unit) {\n    case Unit::kRpx:\n      return page_view->ConvertFrom<kPixelTypePhysical>(\n          page_view->physical_size().width() * value / 750.f);\n    case Unit::kPpx:\n      return page_view->ConvertFrom<kPixelTypePhysical>(value);\n    case Unit::kPercent:\n      return std::max(0.f, view_length) * value / 100.f;\n    // TODO(feiyue): temporarily we treat em, rem and other as px\n    // because em and rem need more info from lynx side and now clay\n    // cannot transform these types\n    case Unit::kPx:\n    default:\n      return page_view->ConvertFrom<kPixelTypeLogical>(value);\n  }\n}\n\n// Get the resolved length value in clay pixels. Currently supports px, rpx, ppx\nfloat ToPxWithDisplayMetrics(const std::string& value_with_unit,\n                             const PageView* page_view) {\n  auto value = clay::Value(value_with_unit);\n  Length result;\n  // if TryGetLength return false, it will set result\n  // to a default value where result.val will be 0.0\n  TryGetLength(value, result);\n  return ToPxWithDisplayMetrics(result, page_view);\n}\n\n#ifndef NDEBUG\nstd::string ToString(const clay::Value& value) {\n  switch (value.type()) {\n    case clay::Value::kNone:\n      return \"none\";\n    case clay::Value::kBool:\n      return value.GetBool() ? \"true\" : \"false\";\n    case clay::Value::kInt:\n      return std::string(\"I:\") + std::to_string(value.GetInt());\n    case clay::Value::kUInt:\n      return std::string(\"U:\") + std::to_string(value.GetUint());\n    case clay::Value::kLong:\n      return std::string(\"L:\") + std::to_string(value.GetLong());\n    case clay::Value::kFloat:\n      return std::string(\"F:\") + std::to_string(value.GetFloat());\n    case clay::Value::kDouble:\n      return std::string(\"D:\") + std::to_string(value.GetDouble());\n    case clay::Value::kString:\n      return value.GetString();\n    case clay::Value::kPointer: {\n      std::stringstream ss;\n      ss << \"P:\" << value.GetPointerType() << \" 0x\" << value.GetPointer();\n      return ss.str();\n    }\n    case clay::Value::kArray: {\n      std::stringstream ss;\n      const auto& array = value.GetArray();\n      ss << \"Array[\" << array.size() << \"]:{\\n\";\n      for (size_t i = 0; i < array.size(); ++i) {\n        ss << ToString(array[i]) << \"\\n\";\n      }\n      ss << \"}\";\n      return ss.str();\n    }\n    case clay::Value::kArrayBuffer: {\n      std::stringstream ss;\n      const auto& buf = value.GetArrayBuffer();\n      ss << \"ArrayBuffer[\" << buf.size() << \"]\";\n      for (size_t i = 0; i < buf.size(); ++i) {\n        ss << \" \" << std::setfill('0') << std::setw(2) << std::hex\n           << buf.data()[i];\n      }\n      return ss.str();\n    }\n    case clay::Value::kMap: {\n      const auto& map = GetMap(value);\n      std::stringstream ss;\n      ss << \"Map[\" << map.size() << \"]:{\\n\";\n      for (const auto& pair : map) {\n        ss << pair.first << \" : \" << ToString(pair.second) << \"\\n\";\n      }\n      ss << \"}\";\n      return ss.str();\n    }\n  }\n}\n\n#endif\n\n}  // namespace attribute_utils\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/attribute_utils.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_ATTRIBUTE_UTILS_H_\n#define CLAY_UI_COMMON_ATTRIBUTE_UTILS_H_\n\n#include <memory>\n#include <sstream>\n#include <string>\n\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/point.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/public/value.h\"\n\nnamespace clay {\n\nclass PageView;\n\nnamespace attribute_utils {\n\n/**\n * Compatible with bool-type, string-type, pointer-type, numeric-type.\n * Return default_val when value is none-type.\n */\nbool GetBool(const clay::Value& value, bool default_val = false);\n\nenum Unit { kNone, kPx, kPpx, kRpx, kEm, kRem, kOther, kPercent };\n\n/**\nwe store 50% as {50.0, kPercent}\n*/\nstruct Length {\n  double val = 0.0;\n  Unit unit = kNone;\n};\n\nbool TryGetNum(const clay::Value& value, double& result,\n               double default_val = 0);\nbool TryGetString(const clay::Value& value, std::string& result,\n                  std::string default_val = \"\");\nbool TryGetLength(const clay::Value& value, Length& result,\n                  Length default_val = {0.0, Unit::kNone});\nbool TryGetPlatformLength(const clay::Value::Array& array, const int index,\n                          ClayPlatformLength& result,\n                          ClayPlatformLength default_val = {});\n\nfloat ResolvePlatformLength(const ClayPlatformLength& length, float size);\n\nfloat ToPxWithDisplayMetrics(const std::string& value_with_unit,\n                             const PageView* page_view);\n\nfloat ToPxWithDisplayMetrics(const Length& value_with_unit,\n                             const PageView* page_view, float view_length = -1);\n\ndouble GetNum(const clay::Value& value, double default_val = 0);\nint GetInt(const clay::Value& value, int default_val = 0);\nuint32_t GetUint(const clay::Value& value, uint32_t default_val = 0);\nint64_t GetLong(const clay::Value& value, int64_t default_val = 0);\n\n// Lynx has no float value\ndouble GetDouble(const clay::Value& value, double default_val = 0);\n\nstd::string GetCString(const clay::Value& value, std::string default_val = \"\");\n\nconst clay::Value::Array& GetArray(const clay::Value& value);\nconst clay::Value::Map& GetMap(const clay::Value& value);\nconst clay::Value& GetMapItem(const clay::Value::Map&, const std::string& key);\n\ninline Color GetColor(const clay::Value& value,\n                      Color default_value = Color::RGBOColor(0, 0, 0, 0)) {\n  Color color;\n  if (Color::Parse(attribute_utils::GetCString(value), &color)) {\n    return color;\n  }\n  return default_value;\n}\n\n// Parse specific format, separated by comma, like \"3, -1\"\n// If parse failed, return {0, 0}\nPoint GetPoint(const clay::Value& value);\n\ninline bool IsNull(const clay::Value& value) { return value.IsNull(); }\n\ninline bool IsNullOrInvalid(const clay::Value& value) {\n  return value.IsNull() || value.IsNone();\n}\n\n#ifndef NDEBUG\nstd::string ToString(const clay::Value& value);\n#endif\n\n}  // namespace attribute_utils\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_ATTRIBUTE_UTILS_H_\n"
  },
  {
    "path": "clay/ui/common/attribute_utils_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/public/value.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(AttributeUtilsTest, GetBool) {\n  clay::Value value{true};\n  EXPECT_EQ(attribute_utils::GetBool(value), true);\n\n  value = clay::Value{false};\n  EXPECT_EQ(attribute_utils::GetBool(value), false);\n\n  value = clay::Value{\"true\"};\n  EXPECT_EQ(attribute_utils::GetBool(value), true);\n\n  value = clay::Value{\"false\"};\n  EXPECT_EQ(attribute_utils::GetBool(value), false);\n\n  value = clay::Value{\"anything\"};\n  EXPECT_EQ(attribute_utils::GetBool(value), false);\n\n  value = clay::Value{ClayPointer{ClayPointer::kClayPointerTypeVoidPtr,\n                                  reinterpret_cast<void*>(0x1)}};\n  EXPECT_EQ(attribute_utils::GetBool(value), true);\n\n  value =\n      clay::Value{ClayPointer{ClayPointer::kClayPointerTypeVoidPtr, nullptr}};\n  EXPECT_EQ(attribute_utils::GetBool(value), false);\n\n  value = clay::Value{1};\n  EXPECT_EQ(attribute_utils::GetBool(value), true);\n\n  value = clay::Value{0};\n  EXPECT_EQ(attribute_utils::GetBool(value), false);\n\n  value = clay::Value{int64_t(1)};\n  EXPECT_EQ(attribute_utils::GetBool(value), true);\n}\n\nTEST(AttributeUtilsTest, GetCString) {\n  clay::Value value{true};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"true\");\n\n  value = clay::Value{false};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"false\");\n\n  value = clay::Value{\"true\"};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"true\");\n\n  value = clay::Value{\"false\"};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"false\");\n\n  value = clay::Value{\"anything\"};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"anything\");\n\n  value = clay::Value{1};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"1\");\n\n  value = clay::Value{0};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"0\");\n\n  value = clay::Value{-1};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"-1\");\n\n  value = clay::Value{int64_t(1)};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"1\");\n\n  value = clay::Value{float(1)};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"1\");\n\n  value = clay::Value{0};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"0\");\n\n  value = clay::Value{-1};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"-1\");\n\n  value = clay::Value{double(1)};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"1\");\n\n  value = clay::Value{0};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"0\");\n\n  value = clay::Value{-1};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"-1\");\n\n  value = clay::Value{3.1000000009};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"3.1000000009\");\n\n  value = clay::Value{3.10000000000009};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"3.10000000000009\");\n\n  value = clay::Value{3.100000000000009};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"3.100000000000009\");\n\n  value = clay::Value{3.10000000000000009};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"3.1\");\n\n  value = clay::Value{3e60};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"3e+60\");\n\n  value = clay::Value{3e-60};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"3e-60\");\n\n  value = clay::Value{NAN};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"NaN\");\n\n  value = clay::Value{INFINITY};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"Infinity\");\n\n  value = clay::Value{-INFINITY};\n  EXPECT_EQ(attribute_utils::GetCString(value), \"-Infinity\");\n}\n\nTEST(AttributeUtilsTest, GetArray) {\n  clay::Value::Array array_wrapper;\n  array_wrapper.emplace_back(double(100));\n  array_wrapper.emplace_back(0);\n  auto array = clay::Value(std::move(array_wrapper));\n  unsigned long size = 2;\n  EXPECT_EQ(attribute_utils::GetArray(std::move(array)).size(), size);\n\n  clay::Value value = clay::Value{\"anything\"};\n  EXPECT_TRUE(attribute_utils::GetArray(value).empty());\n}\n\nTEST(AttributeUtilsTest, TryGetPlatformLength) {\n  auto equals = [](ClayPlatformLength& a, ClayPlatformLength& b) -> bool {\n    return a.value == b.value && a.unit == b.unit;\n  };\n  clay::Value::Array array;\n  array.emplace_back(double(100));\n  array.emplace_back(0);\n  array.emplace_back(4u);\n  array.emplace_back(10);\n  array.emplace_back(false);\n  ClayPlatformLength result;\n  ClayPlatformLength default_val{20, ClayPlatformLengthUnit::kPercentage};\n  attribute_utils::TryGetPlatformLength(array, 0, result, {});\n  ClayPlatformLength expected{100, ClayPlatformLengthUnit::kNumber};\n\n  EXPECT_TRUE(equals(result, expected));\n  attribute_utils::TryGetPlatformLength(array, 1, result, default_val);\n  EXPECT_TRUE(equals(result, default_val));\n  attribute_utils::TryGetPlatformLength(array, 2, result, default_val);\n  EXPECT_TRUE(equals(result, default_val));\n  attribute_utils::TryGetPlatformLength(array, 4, result, default_val);\n  EXPECT_TRUE(equals(result, default_val));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/background_data.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/background_data.h\"\n\n#include <algorithm>\n#include <utility>\n\n#include \"clay/gfx/geometry/rect.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/painter/gradient_factory.h\"\n\nnamespace clay {\n\nBackgroundImage::BackgroundImage(const BackgroundImage& background_image) {\n  if (background_image.image_resource_) {\n#ifndef ENABLE_SKITY\n    image_resource_ = std::make_unique<ImageResource>(\n        *background_image.image_resource_.get());\n\n#else\n    image_resource_ = std::make_unique<BaseImageInstance>(\n        *background_image.image_resource_.get());\n#endif  // ENABLE_SKITY\n  }\n  if (background_image.gradient_.has_value()) {\n    gradient_ = background_image.gradient_;\n  }\n}\n\n#ifndef ENABLE_SKITY\nvoid BackgroundImage::SetImageResource(\n    std::unique_ptr<ImageResource> image_resource) {\n  image_resource_ = std::move(image_resource);\n  gradient_ = std::nullopt;\n}\n#else\nvoid BackgroundImage::SetImageResource(\n    std::unique_ptr<BaseImageInstance> image) {\n  image_resource_ = std::move(image);\n  gradient_ = std::nullopt;\n}\n#endif  // ENABLE_SKITY\n\nvoid BackgroundImage::SetGradient(const Gradient& gradient) {\n  gradient_ = std::make_optional<Gradient>(gradient);\n  image_resource_.reset();\n}\n\nbool BackgroundImage::IsEmpty() const {\n  if (!image_resource_ && !gradient_.has_value()) {\n    return true;\n  }\n\n  if (image_resource_ &&\n      (image_resource_->GetWidth() <= 0 || image_resource_->GetHeight() <= 0)) {\n    return true;\n  }\n\n  return false;\n}\n\nbool BackgroundImage::IsSkImage() const {\n  if (image_resource_) {\n    return true;\n  }\n  return false;\n}\n\nint BackgroundImage::Width() const {\n  if (!image_resource_) {\n    return 0;\n  }\n\n  return image_resource_->GetWidth();\n}\n\nint BackgroundImage::Height() const {\n  if (!image_resource_) {\n    return 0;\n  }\n\n  return image_resource_->GetHeight();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/background_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_BACKGROUND_DATA_H_\n#define CLAY_UI_COMMON_BACKGROUND_DATA_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/gfx/image/image_resource.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/ui/painter/gradient.h\"\n\nnamespace clay {\n\nclass BackgroundImage {\n public:\n  BackgroundImage() = default;\n  BackgroundImage(const BackgroundImage& background_image);\n  ~BackgroundImage() = default;\n\n#ifndef ENABLE_SKITY\n  void SetImageResource(std::unique_ptr<ImageResource> image_resource);\n  const ImageResource* GetImageResource() const {\n    return image_resource_.get();\n  }\n#else\n  void SetImageResource(std::unique_ptr<BaseImageInstance> image_resource);\n  const BaseImageInstance* GetImageResource() const {\n    return image_resource_.get();\n  }\n#endif  // ENABLE_SKITY\n\n  void SetGradient(const Gradient& gradient);\n  const Gradient& GetGradient() const { return *gradient_; }\n\n  bool IsEmpty() const;\n  bool IsSkImage() const;\n  int Width() const;\n  int Height() const;\n\n private:\n  // The background of a box can have multiple layers in CSS3. Each layer cannot\n  // have image and gradient at the same time.\n  // https://www.w3.org/TR/css-backgrounds-3/#layering\n#ifndef ENABLE_SKITY\n  std::unique_ptr<ImageResource> image_resource_;\n#else\n  std::unique_ptr<BaseImageInstance> image_resource_;\n#endif\n  std::optional<Gradient> gradient_ = std::nullopt;\n};\n\nstruct BackgroundImageData {\n  ClayBackgroundImageType type = ClayBackgroundImageType::kNone;\n  // only for ClayBackgroundImageType::kUrl.\n  std::string src_str;\n  // only for ClayBackgroundImageType::kLinearGradient.\n  // non-empty |gradient_str| overrides |gradient_data|.\n  std::string gradient_str;\n  ClayLinearGradient gradient_data = {};\n\n  // BackgroundImage image;\n  ClayBackgroundSizeType size = ClayBackgroundSizeType::kContain;\n  // TODO(heke): Align position to RKBackgroundPositionType.\n  // Background-position, relative to ClayBackgroundOriginType.\n  FloatPoint position;\n  ClayBackgroundOriginType origin = ClayBackgroundOriginType::kBorderBox;\n  ClayBackgroundRepeatType repeat = ClayBackgroundRepeatType::kRepeat;\n  ClayBackgroundClipType clip = ClayBackgroundClipType::kBorderBox;\n};\n\nclass BackgroundPosition {\n public:\n  BackgroundPosition() = default;\n  BackgroundPosition(float value, ClayPlatformLengthUnit type)\n      : value_(value), type_(type) {}\n  float apply(float parent_value) const {\n    if (type_ == ClayPlatformLengthUnit::kPercentage) {\n      return parent_value * value_;\n    } else {\n      return value_;\n    }\n  }\n\n private:\n  float value_ = 0.f;\n  ClayPlatformLengthUnit type_ = ClayPlatformLengthUnit::kNumber;\n};\n\nclass BackgroundSize {\n public:\n  BackgroundSize() = default;\n  BackgroundSize(float value, int type)\n      : value_(value), type_(static_cast<ClayPlatformLengthUnit>(type)) {}\n  BackgroundSize(float value, ClayPlatformLengthUnit type)\n      : value_(value), type_(type) {}\n  bool IsCover() const {\n    return static_cast<ClayBackgroundSizeType>(-value_) ==\n           ClayBackgroundSizeType::kCover;\n  }\n  bool IsContain() const {\n    return static_cast<ClayBackgroundSizeType>(-value_) ==\n           ClayBackgroundSizeType::kContain;\n  }\n  bool IsAuto() const {\n    return static_cast<ClayBackgroundSizeType>(-value_) ==\n           ClayBackgroundSizeType::kAuto;\n  }\n  float apply(float parent_value, float current_value) const {\n    if (type_ == ClayPlatformLengthUnit::kPercentage) {\n      return value_ * parent_value;\n    } else if (IsAuto()) {\n      return current_value;\n    } else {\n      return value_;\n    }\n  }\n\n private:\n  float value_ = -static_cast<float>(ClayBackgroundSizeType::kAuto);\n  ClayPlatformLengthUnit type_ = ClayPlatformLengthUnit::kNumber;\n};\n\nstruct BackgroundData {\n  Color background_color = Color::RGBOColor(0, 0, 0, 0);  // The compatible code\n  std::vector<BackgroundImageData> background_images;     // The compatible code\n  std::vector<BackgroundImage> images;\n  std::vector<ClayBackgroundOriginType> origins;\n  std::vector<ClayBackgroundRepeatType> repeats;\n  std::vector<ClayBackgroundClipType> clips;\n  std::vector<BackgroundSize> sizes;\n  std::vector<BackgroundPosition> positions;\n};\n\n// Mask\nusing MaskImage = BackgroundImage;\nusing MaskPosition = BackgroundPosition;\nusing MaskSize = BackgroundSize;\n\nstruct MaskData {\n  std::vector<MaskImage> images;\n  std::vector<ClayMaskOriginType> origins;\n  std::vector<ClayMaskRepeatType> repeats;\n  std::vector<ClayMaskClipType> clips;\n  std::vector<MaskSize> sizes;\n  std::vector<MaskPosition> positions;\n};\n\n};  // namespace clay\n\n#endif  // CLAY_UI_COMMON_BACKGROUND_DATA_H_\n"
  },
  {
    "path": "clay/ui/common/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\nui_common_sources = [\n  \"attribute_utils.cc\",\n  \"attribute_utils.h\",\n  \"background_data.cc\",\n  \"background_data.h\",\n  \"editing_misc.h\",\n  \"fps_tracer.cc\",\n  \"fps_tracer.h\",\n  \"frame_timing_collector.cc\",\n  \"frame_timing_collector.h\",\n  \"gap_task.h\",\n  \"gap_worker.cc\",\n  \"gap_worker.h\",\n  \"input_client_manager.h\",\n  \"isolate.cc\",\n  \"isolate.h\",\n  \"json_utils.h\",\n  \"macros.h\",\n  \"measure_constraint.h\",\n  \"render_settings.cc\",\n  \"render_settings.h\",\n  \"text_input_type_traits.h\",\n  \"text_selection.h\",\n  \"type_info.h\",\n  \"utils/floating_comparison.h\",\n  \"utils/value_change_notifier.h\",\n  \"utils/watch_dog.cc\",\n  \"utils/watch_dog.h\",\n  \"value_utils.cc\",\n  \"value_utils.h\",\n]\n\nif (!enable_clay_lite) {\n  ui_common_sources += [\n    \"overlay_manager.cc\",\n    \"overlay_manager.h\",\n  ]\n}\n"
  },
  {
    "path": "clay/ui/common/editing_misc.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_EDITING_MISC_H_\n#define CLAY_UI_COMMON_EDITING_MISC_H_\n\n// Miscellaneous definition or helpers for text editing.\nnamespace clay {\n\n// When caret has more than 1 choice to locate, which side should it be.\n// For example: Assume case that container box width is too short, so that\n//   \"abc 123\" has to be soft wrapped, and imagine the whitespace is the soft\n//   line break. Now caret offset is 3, which can be after 'c' or before '1'.\n//   Thus, it depends on affinity to choice whether caret should at line\n//   trailing or next line's leading.\nenum class Affinity {\n  kUpstream = 0,\n  kDownstream,\n};\n\nenum class VerticalDirection {\n  kUp = 0,\n  kDown,\n};\n\nenum class HorizontalDirection {\n  kLeft = 0,\n  kRight,\n};\n\n// Returns true if |code_point| is a leading surrogate of a surrogate pair.\nbool inline IsLeadingSurrogate(char32_t code_point) {\n  return (code_point & 0xFFFFFC00) == 0xD800;\n}\n// Returns true if |code_point| is a trailing surrogate of a surrogate pair.\nbool inline IsTrailingSurrogate(char32_t code_point) {\n  return (code_point & 0xFFFFFC00) == 0xDC00;\n}\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_EDITING_MISC_H_\n"
  },
  {
    "path": "clay/ui/common/fps_tracer.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/fps_tracer.h\"\n\nnamespace clay {\n\nnamespace {\nconstexpr float kCapacityReserveFactor = 1.2;\n\nconstexpr int kDesiredRefreshRate = 60;\nconstexpr double kDesiredFrameIntervalInMillis =\n    1.0 / kDesiredRefreshRate * 1000.0;\nconstexpr int kOffsetToMillis = 100;\nconstexpr int kDesiredFrameIntervalInMillis100 =\n    kDesiredFrameIntervalInMillis * kOffsetToMillis;\n\nconstexpr int64_t kTimeMsToNs = 1000000;\nconstexpr int64_t kTimeSToMs = 1000;\n}  // namespace\n\nFpsTracer::FpsTracer(const std::string scene, const std::string tag,\n                     int32_t max_refresh_rate, int session_timeout_s)\n    : config_{scene, tag}, max_refresh_rate_(max_refresh_rate) {\n  const int refresh_rate =\n      max_refresh_rate_ > 0 ? max_refresh_rate_ : kDesiredRefreshRate;\n  refresh_interval_ns_ = 1.0 * kTimeSToMs * kTimeMsToNs / refresh_rate;\n\n  frame_time_samples_.reserve(static_cast<size_t>(\n      refresh_rate * session_timeout_s * kCapacityReserveFactor));\n}\nFpsTracer::~FpsTracer() = default;\n\nvoid FpsTracer::Start() { start_time_ = fml::TimePoint::Now(); }\n\nvoid FpsTracer::Stop() { end_time_ = fml::TimePoint::Now(); }\n\nvoid FpsTracer::GetFpsMetrics(FpsRawMetrics& fps_metrics) {\n  if (frame_time_samples_.size() < 2) {\n    return;\n  }\n\n  size_t sample_count = frame_time_samples_.size();\n  int frame_count = sample_count - 1;\n\n  int64_t duration_ms = (end_time_ - start_time_).ToMilliseconds();\n  fps_metrics.duration_ms = duration_ms;\n  fps_metrics.max_refresh_rate = max_refresh_rate_;\n\n  int64_t interval_ms =\n      (frame_time_samples_[sample_count - 1] - frame_time_samples_[0]) /\n      kTimeMsToNs;\n  fps_metrics.frames = frame_count;\n  fps_metrics.fps = (fps_metrics.frames * kTimeSToMs) / interval_ms;\n\n  for (size_t i = 1; i < sample_count; ++i) {\n    const int64_t cost_mills =\n        (frame_time_samples_[i] - frame_time_samples_[i - 1]) / kTimeMsToNs;\n    const int64_t cost_mills100 = cost_mills * kOffsetToMillis;\n    int drop_count =\n        computeDropCount(cost_mills100, kDesiredFrameIntervalInMillis100);\n    int64_t drop_duration = cost_mills - kDesiredFrameIntervalInMillis;\n    if (drop_count >= 1) {\n      fps_metrics.drop1_count++;\n      fps_metrics.drop1_duration_ms += drop_duration;\n    } else {\n      continue;\n    }\n\n    if (drop_count >= 3) {\n      fps_metrics.drop3_count++;\n      fps_metrics.drop3_duration_ms += drop_duration;\n    } else {\n      continue;\n    }\n\n    if (drop_count >= 7) {\n      fps_metrics.drop7_count++;\n      fps_metrics.drop7_duration_ms += drop_duration;\n    } else {\n      continue;\n    }\n\n    if (drop_count >= 25) {\n      fps_metrics.drop25_count++;\n      fps_metrics.drop25_duration_ms += drop_duration;\n    }\n  }\n}\n\n// Calculates the number of vsync intervals missed between two frames.\n// This is equivalent to ceil(cost_millis / frame_interval_millis) - 1,\n// but uses integer arithmetic for efficiency.\nint FpsTracer::computeDropCount(int64_t cost_mills100,\n                                int frame_interval_ms100) {\n  return static_cast<int>(\n      (cost_mills100 + (frame_interval_ms100 - 1)) / frame_interval_ms100 - 1);\n}\n\nvoid FpsTracer::AddFrameTimeSample(int64_t frame_time_nanos) {\n  frame_time_samples_.push_back(frame_time_nanos);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/fps_tracer.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_FPS_TRACER_H_\n#define CLAY_UI_COMMON_FPS_TRACER_H_\n\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace clay {\n\nstruct FpsRawMetrics {\n  int max_refresh_rate = 0;\n  int duration_ms = 0;\n  int frames = 0;\n  int fps = 0;\n\n  int drop1_count = 0;\n  int drop1_duration_ms = 0;\n  int drop3_count = 0;\n  int drop3_duration_ms = 0;\n  int drop7_count = 0;\n  int drop7_duration_ms = 0;\n  int drop25_count = 0;\n  int drop25_duration_ms = 0;\n};\n\nstruct FluencyTracerConfig {\n  std::string scene = \"\";\n  std::string tag = \"\";\n};\n\nclass FpsTracer {\n public:\n  FpsTracer(const std::string scene, const std::string tag,\n            int max_refresh_rate, int32_t session_timeout_s);\n  ~FpsTracer();\n\n  void Start();\n  void Stop();\n  void AddFrameTimeSample(int64_t frame_time_nanos);\n  void GetFpsMetrics(FpsRawMetrics& metrics);\n  const FluencyTracerConfig& GetConfig() { return config_; }\n\n private:\n  int computeDropCount(int64_t cost_mills100, int frame_interval_ms100);\n\n  FluencyTracerConfig config_;\n  int max_refresh_rate_;\n  double refresh_interval_ns_;\n  fml::TimePoint start_time_;\n  fml::TimePoint end_time_;\n  std::vector<int64_t> frame_time_samples_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_FPS_TRACER_H_\n"
  },
  {
    "path": "clay/ui/common/frame_timing_collector.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/frame_timing_collector.h\"\n\n#include <cstdint>\n#include <string>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/timer/time_utils.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nnamespace {\ntemplate <typename T>\nstatic constexpr int PerfToInt(T perf) noexcept {\n  return static_cast<int>(perf);\n}\n\nstatic constexpr size_t FIRST_THRESHOLD = PerfToInt(Perf::kFirstSep);\nstatic constexpr size_t UPDATE_THRESHOLD =\n    PerfToInt(Perf::kUpdateSep) - PerfToInt(Perf::kFirstSep) - 1;\n\nstatic constexpr int64_t LAYOUT_COST_THRESHOLD = 17;\n\nstatic constexpr int64_t RASTER_COST_THRESHOLD_LEVEL_1 = 17;\nstatic constexpr int64_t RASTER_COST_THRESHOLD_LEVEL_2 = 34;\nstatic constexpr int64_t RASTER_COST_THRESHOLD_LEVEL_3 = 68;\n\nstatic constexpr int64_t FRAME_TOTAL_COST_THRESHOLD_LEVEL_1 = 17;\nstatic constexpr int64_t FRAME_TOTAL_COST_THRESHOLD_LEVEL_2 = 34;\nstatic constexpr int64_t FRAME_TOTAL_COST_THRESHOLD_LEVEL_3 = 68;\n\nstatic constexpr const char* SETUP_FLAG = \"clay_setup\";\nstatic constexpr const char* FORCE_UPDATE_FLAG = \"clay_force_update\";\nstatic constexpr const char* UPDATE_FLAG = \"clay_update\";\n}  // namespace\n\nconst std::unordered_map<Perf, const char*>\n    FrameTimingCollector::perf_to_string_map = {\n        {Perf::kEnablePartialRepaint, \"rk_enable_partial_repaint\"},\n\n        {Perf::kFirstLayoutCost, \"rk_first_layout\"},\n        {Perf::kFirstPaintCost, \"rk_first_paint\"},\n        {Perf::kFirstBuildFrameCost, \"rk_first_build_frame\"},\n        {Perf::kFirstRasterCost, \"rk_first_raster\"},\n        {Perf::kFirstRasterEnd, \"clay_first_raster_end\"},\n        {Perf::kFirstPresentEnd, \"clay_first_present_end\"},\n\n        {Perf::kErrorCode, \"rk_error_code\"},\n\n        {Perf::kLayoutAndAnimationMax, \"rk_layout_and_animation_max\"},\n        {Perf::kLayoutAndAnimationAvg, \"rk_layout_and_animation_avg\"},\n        {Perf::kLayoutAndAnimationBad, \"rk_layout_and_animation_bad\"},\n\n        {Perf::kRasterMax, \"rk_raster_max\"},\n        {Perf::kRasterAvg, \"rk_raster_avg\"},\n        {Perf::kRasterBadLevel1, \"rk_raster_bad_level_1\"},\n        {Perf::kRasterBadLevel2, \"rk_raster_bad_level_2\"},\n        {Perf::kRasterBadLevel3, \"rk_raster_bad_level_3\"},\n\n        {Perf::kFrameTotalMax, \"rk_frame_total_max\"},\n        {Perf::kFrameTotalAvg, \"rk_frame_total_avg\"},\n        {Perf::kFrameTotalBadLevel1, \"rk_frame_total_bad_level_1\"},\n        {Perf::kFrameTotalBadLevel2, \"rk_frame_total_bad_level_2\"},\n        {Perf::kFrameTotalBadLevel3, \"rk_frame_total_bad_level_3\"},\n\n        {Perf::kListLayoutNewItem, \"rk_list_layout_new_item\"},\n        {Perf::kMoveFocusUntilDraw, \"rk_move_focus_until_draw\"},\n        {Perf::kMoveFocusUntilRaster, \"rk_move_focus_until_raster\"},\n        {Perf::kMoveFocusDirection, \"rk_move_focus_direction\"},\n};\n\nFrameTimingCollector::FrameTimingCollector(\n    fml::RefPtr<fml::TaskRunner> platform_task_runner)\n    : weak_factory_(this),\n      weak_(weak_factory_.GetWeakPtr()),\n      platform_task_runner_(platform_task_runner) {}\n\nFrameTimingCollector::~FrameTimingCollector() = default;\n\nbool FrameTimingCollector::FirstPerfReady() const {\n  int64_t error_code = 0;\n  auto error_code_iter =\n      first_perf_container_.find(PerfToString(Perf::kErrorCode));\n  if (error_code_iter != first_perf_container_.end()) {\n    error_code = error_code_iter->second;\n  }\n  // All first perf data collected or has error code.\n  return (first_perf_container_.size() == FIRST_THRESHOLD) || (error_code > 0);\n}\n\nbool FrameTimingCollector::UpdatePerfReady() const {\n  return update_perf_container_.size() == UPDATE_THRESHOLD;\n}\n\nconst char* FrameTimingCollector::PerfToString(Perf perf) const {\n  return perf_to_string_map.at(perf);\n}\n\nvoid FrameTimingCollector::BeginRecord(Perf perf) {\n  int64_t begin = std::chrono::duration_cast<std::chrono::milliseconds>(\n                      std::chrono::system_clock::now().time_since_epoch())\n                      .count();\n  fml::TaskRunner::RunNowOrPostTask(platform_task_runner_,\n                                    [weak = weak_, perf, begin]() {\n                                      if (weak) {\n                                        weak->perf_record_[perf] = begin;\n                                      }\n                                    });\n}\n\nvoid FrameTimingCollector::EndRecord(Perf perf) {\n  int64_t end = std::chrono::duration_cast<std::chrono::milliseconds>(\n                    std::chrono::system_clock::now().time_since_epoch())\n                    .count();\n  fml::TaskRunner::RunNowOrPostTask(\n      platform_task_runner_, [weak = weak_, perf, end]() {\n        if (weak) {\n          int64_t begin = weak->perf_record_[perf];\n          int64_t cost = end - begin;\n          weak->InsertRecordInternal(perf, cost);\n        }\n      });\n}\n\nvoid FrameTimingCollector::InsertRecord(Perf perf, int64_t cost) {\n  if (is_recording_first_frame_perf_ && perf == Perf::kFirstPresentEnd) {\n    // Finish recording first frame perf\n    is_recording_first_frame_perf_ = false;\n  }\n  fml::TaskRunner::RunNowOrPostTask(platform_task_runner_,\n                                    [weak = weak_, cost, perf]() {\n                                      if (weak) {\n                                        weak->InsertRecordInternal(perf, cost);\n                                      }\n                                    });\n}\n\nvoid FrameTimingCollector::InsertForceRecord(const PerfMap& perf_map) {\n  fml::TaskRunner::RunNowOrPostTask(\n      platform_task_runner_, [weak = weak_, perf_map]() {\n        if (weak) {\n          weak->InsertForceRecordInternal(perf_map);\n        }\n      });\n}\n\nvoid FrameTimingCollector::InsertLayoutAndAnimationRecord(\n    const clay::Stopwatch& stopwatch) {\n  // Record performance of 120 samples.\n  if (!stopwatch.CheckIfLastSampleInCycle()) {\n    return;\n  }\n  const auto& laps = stopwatch.GetLaps();\n  size_t bad_count = 0;\n  for (const auto& lap : laps) {\n    int64_t cost = lap.ToMilliseconds();\n    if (cost > LAYOUT_COST_THRESHOLD) {\n      ++bad_count;\n    }\n  }\n  int64_t max = stopwatch.MaxDelta().ToMilliseconds();\n  int64_t avg = stopwatch.AverageDelta().ToMilliseconds();\n  fml::TaskRunner::RunNowOrPostTask(\n      platform_task_runner_, [weak = weak_, max, avg, bad_count]() {\n        if (weak) {\n          weak->InsertRecordInternal(Perf::kLayoutAndAnimationMax, max);\n          weak->InsertRecordInternal(Perf::kLayoutAndAnimationAvg, avg);\n          weak->InsertRecordInternal(Perf::kLayoutAndAnimationBad, bad_count);\n        }\n      });\n}\n\nvoid FrameTimingCollector::InsertRasterRecord(\n    const clay::Stopwatch& stopwatch) {\n  // Record performance of 120 samples.\n  if (!stopwatch.CheckIfLastSampleInCycle()) {\n    return;\n  }\n  const auto& laps = stopwatch.GetLaps();\n  size_t level_1 = 0;\n  size_t level_2 = 0;\n  size_t level_3 = 0;\n  for (const auto& lap : laps) {\n    int64_t cost = lap.ToMilliseconds();\n    if (cost < RASTER_COST_THRESHOLD_LEVEL_1) {\n      continue;\n    }\n    if (cost >= RASTER_COST_THRESHOLD_LEVEL_3) {\n      ++level_3;\n    } else if (cost >= RASTER_COST_THRESHOLD_LEVEL_2) {\n      ++level_2;\n    } else if (cost >= RASTER_COST_THRESHOLD_LEVEL_1) {\n      ++level_1;\n    }\n  }\n  int64_t max = stopwatch.MaxDelta().ToMilliseconds();\n  int64_t avg = stopwatch.AverageDelta().ToMilliseconds();\n  fml::TaskRunner::RunNowOrPostTask(\n      platform_task_runner_,\n      [weak = weak_, max, avg, level_1, level_2, level_3]() {\n        if (weak) {\n          weak->InsertRecordInternal(Perf::kRasterMax, max);\n          weak->InsertRecordInternal(Perf::kRasterAvg, avg);\n          weak->InsertRecordInternal(Perf::kRasterBadLevel1, level_1);\n          weak->InsertRecordInternal(Perf::kRasterBadLevel2, level_2);\n          weak->InsertRecordInternal(Perf::kRasterBadLevel3, level_3);\n        }\n      });\n}\n\nvoid FrameTimingCollector::InsertFocusChangedUntilFirstRasterFinish() {\n  InsertForceTimeRecordUntilNow(receive_focus_time_,\n                                Perf::kMoveFocusUntilRaster);\n  // this method is the last one which is the consumption of\n  // receive_focus_time_.\n  receive_focus_time_ = 0;\n}\n\nvoid FrameTimingCollector::InsertFocusChangedUntilFirstPaintFinish() {\n  if (has_reported_after_focus_) {\n    return;\n  }\n  InsertForceTimeRecordUntilNow(receive_focus_time_, Perf::kMoveFocusUntilDraw);\n  has_reported_after_focus_ = true;\n}\n\nvoid FrameTimingCollector::InsertForceTimeRecordUntilNow(int64_t from,\n                                                         Perf perf) {\n  int64_t now = lynx::base::CurrentSystemTimeMilliseconds();\n  if (now > from) {\n    auto record_map = std::unordered_map<Perf, int64_t>();\n    record_map.emplace(perf, now - from);\n    record_map.emplace(Perf::kMoveFocusDirection,\n                       static_cast<int64_t>(focus_direction_));\n    InsertForceRecord(record_map);\n  }\n}\n\nvoid FrameTimingCollector::InsertFrameTotalCostRecord(\n    const clay::Stopwatch& stopwatch) {\n  // Record performance of 120 samples.\n  if (!stopwatch.CheckIfLastSampleInCycle()) {\n    return;\n  }\n  const auto& laps = stopwatch.GetLaps();\n  size_t level_1 = 0;\n  size_t level_2 = 0;\n  size_t level_3 = 0;\n  for (const auto& lap : laps) {\n    int64_t cost = lap.ToMilliseconds();\n    if (cost < FRAME_TOTAL_COST_THRESHOLD_LEVEL_1) {\n      continue;\n    }\n    if (cost >= FRAME_TOTAL_COST_THRESHOLD_LEVEL_3) {\n      ++level_3;\n    } else if (cost >= FRAME_TOTAL_COST_THRESHOLD_LEVEL_2) {\n      ++level_2;\n    } else if (cost >= FRAME_TOTAL_COST_THRESHOLD_LEVEL_1) {\n      ++level_1;\n    }\n  }\n  int64_t max = stopwatch.MaxDelta().ToMilliseconds();\n  int64_t avg = stopwatch.AverageDelta().ToMilliseconds();\n  fml::TaskRunner::RunNowOrPostTask(\n      platform_task_runner_,\n      [weak = weak_, max, avg, level_1, level_2, level_3]() {\n        if (weak) {\n          weak->InsertRecordInternal(Perf::kFrameTotalMax, max);\n          weak->InsertRecordInternal(Perf::kFrameTotalAvg, avg);\n          weak->InsertRecordInternal(Perf::kFrameTotalBadLevel1, level_1);\n          weak->InsertRecordInternal(Perf::kFrameTotalBadLevel2, level_2);\n          weak->InsertRecordInternal(Perf::kFrameTotalBadLevel3, level_3);\n        }\n      });\n}\n\nvoid FrameTimingCollector::InsertRecordInternal(Perf perf, int64_t cost) {\n  if (is_first_send_ && perf < Perf::kFirstSep) {\n    first_perf_container_.emplace(PerfToString(perf), cost);\n  } else if (perf > Perf::kFirstSep && perf < Perf::kUpdateSep) {\n    update_perf_container_.emplace(PerfToString(perf), cost);\n  } else {\n    return;\n  }\n\n  MaybeReport();\n}\n\nbool FrameTimingCollector::IsForcePushRecord(Perf perf) {\n  if (perf > Perf::kUpdateSep && perf < Perf::kForceSep) {\n    return true;\n  }\n  return false;\n}\n\nvoid FrameTimingCollector::InsertForceRecordInternal(const PerfMap& perf_map) {\n  if (perf_map.empty()) {\n    return;\n  }\n  for (const auto& perf : perf_map) {\n    if (IsForcePushRecord(perf.first)) {\n      force_perf_container_.emplace(PerfToString(perf.first), perf.second);\n    }\n  }\n\n  if (force_perf_container_.size() == 0) {\n    return;\n  }\n  MaybeReport(true);\n}\n\nvoid FrameTimingCollector::MaybeReport(bool is_force) {\n  if (page_view_) {\n    if (is_force) {\n      page_view_->ReportTiming(force_perf_container_, FORCE_UPDATE_FLAG);\n      force_perf_container_.clear();\n    } else if (is_first_send_ && FirstPerfReady()) {\n      is_first_send_ = false;\n      page_view_->ReportTiming(first_perf_container_, SETUP_FLAG);\n      first_perf_container_.clear();\n    } else if (!is_first_send_ && UpdatePerfReady()) {\n      page_view_->ReportTiming(update_perf_container_, UPDATE_FLAG);\n      update_perf_container_.clear();\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/frame_timing_collector.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_FRAME_TIMING_COLLECTOR_H_\n#define CLAY_UI_COMMON_FRAME_TIMING_COLLECTOR_H_\n\n#include <chrono>\n#include <cstdint>\n#include <string>\n#include <unordered_map>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/flow/stopwatch.h\"\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nclass PageView;\n\nstatic const uint32_t kPerfErrorCodeOK = 0;\nstatic const uint32_t kPerfErrorCodeSurfaceFailed = 1;\n\n// Timing flag constants for setup and update operations\nstatic constexpr const char* kSetupFlag = \"clay_setup\";\nstatic constexpr const char* kUpdateFlag = \"clay_update\";\nstatic constexpr const char* kForceUpdateFlag = \"clay_force_update\";\n\nenum class Perf {\n  kEnablePartialRepaint,\n\n  kFirstLayoutCost,\n  kFirstPaintCost,\n  kFirstBuildFrameCost,\n  kFirstRasterCost,\n  kFirstRasterEnd,\n  kFirstPresentEnd,\n\n  kErrorCode,\n\n  kFirstSep,  // DON'T USE THIS! THIS SHOULD ONLY BE USED BY PERFCOLLECTOR SELF\n\n  kLayoutAndAnimationMax,\n  kLayoutAndAnimationAvg,\n  kLayoutAndAnimationBad,\n\n  kRasterMax,\n  kRasterAvg,\n  kRasterBadLevel1,\n  kRasterBadLevel2,\n  kRasterBadLevel3,\n\n  kFrameTotalMax,\n  kFrameTotalAvg,\n  kFrameTotalBadLevel1,\n  kFrameTotalBadLevel2,\n  kFrameTotalBadLevel3,\n\n  kUpdateSep,  // DON'T USE THIS! THIS SHOULD ONLY BE USED BY PERFCOLLECTOR SELF\n\n  kListLayoutNewItem,\n  kMoveFocusUntilDraw,\n  kMoveFocusUntilRaster,\n  kMoveFocusDirection,\n  kForceSep,  // DON'T USE THIS! THIS SHOULD ONLY BE USED BY PERFCOLLECTOR SELF\n};\n\nclass FrameTimingCollector {\n public:\n  using PerfMap = std::unordered_map<Perf, int64_t>;\n\n  explicit FrameTimingCollector(\n      fml::RefPtr<fml::TaskRunner> platform_task_runner);\n\n  ~FrameTimingCollector();\n\n  void SetPageView(PageView* page_view) { page_view_ = page_view; }\n\n  void BeginRecord(Perf perf);\n  void EndRecord(Perf perf);\n  void InsertRecord(Perf perf, int64_t cost);\n  void InsertForceRecord(const PerfMap& perf_map);\n  bool IsForcePushRecord(Perf perf);\n\n  void InsertLayoutAndAnimationRecord(const clay::Stopwatch& stopwatch);\n  void InsertRasterRecord(const clay::Stopwatch& stopwatch);\n  void InsertFrameTotalCostRecord(const clay::Stopwatch& stopwatch);\n\n  bool IsRecordingFirstFramePerf() { return is_recording_first_frame_perf_; }\n\n  int64_t GetReceivedFocusTime() const { return receive_focus_time_; }\n  void SetReceivedFocusTime(int64_t timestamp,\n                            FocusManager::Direction direction) {\n    receive_focus_time_ = timestamp;\n    focus_direction_ = direction;\n    has_reported_after_focus_ = false;\n  }\n  void InsertFocusChangedUntilFirstRasterFinish();\n  void InsertFocusChangedUntilFirstPaintFinish();\n  void InsertForceTimeRecordUntilNow(int64_t from, Perf perf);\n\n private:\n  const char* PerfToString(Perf perf) const;\n\n  bool FirstPerfReady() const;\n  bool UpdatePerfReady() const;\n\n  void InsertRecordInternal(Perf perf, int64_t cost);\n  void InsertForceRecordInternal(const PerfMap& perf_map);\n\n  void MaybeReport(bool is_force = false);\n\n  fml::WeakPtrFactory<FrameTimingCollector> weak_factory_;\n  fml::WeakPtr<FrameTimingCollector> weak_;\n\n  fml::RefPtr<fml::TaskRunner> platform_task_runner_;\n\n  bool is_recording_first_frame_perf_ = true;\n  bool is_first_send_ = true;\n  PageView* page_view_ = nullptr;\n\n  static const std::unordered_map<Perf, const char*> perf_to_string_map;\n\n  std::unordered_map<Perf, int64_t> perf_record_;\n\n  std::unordered_map<std::string, int64_t> first_perf_container_;\n  std::unordered_map<std::string, int64_t> update_perf_container_;\n  std::unordered_map<std::string, int64_t> force_perf_container_;\n\n  int64_t receive_focus_time_ = 0;\n  FocusManager::Direction focus_direction_ = FocusManager::Direction::kUnknown;\n  bool has_reported_after_focus_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_FRAME_TIMING_COLLECTOR_H_\n"
  },
  {
    "path": "clay/ui/common/gap_task.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_GAP_TASK_H_\n#define CLAY_UI_COMMON_GAP_TASK_H_\n\n#include <algorithm>\n#include <limits>\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/list_recycler.h\"\n\nnamespace clay {\n\nclass GapTask {\n public:\n  GapTask(fml::WeakPtr<BaseView> host, int32_t id, int32_t estimate_time,\n          int32_t priority = -1, bool enable_force_run = false)\n      : id_(id),\n        estimate_time_(estimate_time),\n        priority_(priority),\n        host_view_(host),\n        enable_force_run_(enable_force_run) {}\n  virtual ~GapTask() = default;\n  virtual void Run() = 0;\n  auto estimate_time() const { return estimate_time_; }\n  auto priority() const { return priority_; }\n  auto id() const { return id_; }\n  auto enable_force_run() const { return enable_force_run_; }\n\n  struct GapTaskComparator {\n    bool operator()(const std::unique_ptr<GapTask>& l,\n                    const std::unique_ptr<GapTask>& r) const {\n      return l->priority() < r->priority();\n    }\n  };\n\n protected:\n  int32_t id_;\n  int32_t estimate_time_ = 0;\n  int32_t priority_ = -1;\n  fml::WeakPtr<BaseView> host_view_;\n  // Indicate that this task should be run even if the idle time is not enough.\n  // But the actual behavior will be depended on the actual gap worker\n  // implementation.\n  bool enable_force_run_;\n};\n\nclass GapTaskBundle : public fml::RefCountedThreadSafe<GapTaskBundle> {\n public:\n  explicit GapTaskBundle(fml::WeakPtr<BaseView> host) : host_view_(host) {}\n  void AddTask(std::unique_ptr<GapTask> task) {\n    priority_ = std::min(priority_, task->priority());\n    tasks_.push_back(std::move(task));\n  }\n  bool IsEmpty() const { return tasks_.empty(); }\n  bool IsValid() const { return static_cast<bool>(host_view_); }\n  void Clear() { tasks_.clear(); }\n  auto begin() { return tasks_.begin(); }\n  auto end() { return tasks_.end(); }\n  void sort() {\n    std::sort(tasks_.begin(), tasks_.end(), GapTask::GapTaskComparator());\n  }\n  auto erase(std::vector<std::unique_ptr<GapTask>>::iterator itr) {\n    return tasks_.erase(itr);\n  }\n  fml::WeakPtr<BaseView> host_view() const { return host_view_; }\n  auto priority() const { return priority_; }\n\n  struct GapTaskBundleComparator {\n    bool operator()(const fml::RefPtr<GapTaskBundle>& l,\n                    const fml::RefPtr<GapTaskBundle>& r) const {\n      return l->priority() < r->priority();\n    }\n  };\n\n private:\n  int32_t priority_ = std::numeric_limits<int32_t>::max();\n  fml::WeakPtr<BaseView> host_view_;\n  std::vector<std::unique_ptr<GapTask>> tasks_;\n\n  FML_FRIEND_MAKE_REF_COUNTED(GapTaskBundle);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(GapTaskBundle);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_GAP_TASK_H_\n"
  },
  {
    "path": "clay/ui/common/gap_worker.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/gap_worker.h\"\n\n#include <algorithm>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/rect.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/ui/common/gap_task.h\"\n\nnamespace clay {\n\nvoid GapWorker::CollectTaskIfNeeded() {\n  FML_DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n  TRACE_EVENT(\"clay\", \"GapWorker::CollectTaskIfNeeded\");\n  for (const auto& kv : collectors_) {\n    kv.second();\n  }\n}\n\nvoid GapWorker::RegisterTaskCollector(BaseView* view,\n                                      GapTaskCollector collector) {\n  FML_DCHECK(view);\n  collectors_[view] = collector;\n}\n\nvoid GapWorker::UnregisterTaskCollector(BaseView* view) {\n  collectors_.erase(view);\n  CancelTask(view);\n}\n\nvoid GapWorker::SubmitTask(fml::RefPtr<GapTaskBundle> tasks) {\n  if (tasks->IsValid() && !tasks->IsEmpty()) {\n    task_map_[tasks->host_view().get()] = tasks;\n    data_changed_ = true;\n  }\n}\n\nvoid GapWorker::CancelTask(BaseView* host) {\n  task_map_.erase(host);\n  data_changed_ = true;\n}\n\nvoid GapWorker::FlushTask(const fml::TimePoint& end_time) {\n  CollectTaskIfNeeded();\n\n  if (task_map_.empty()) {\n    return;\n  }\n  auto start_time = fml::TimePoint::Now();\n  if (start_time > end_time) {\n    return;\n  }\n  TRACE_EVENT(\"clay\", \"FlushTask\");\n  if (data_changed_) {\n    data_changed_ = false;\n    last_task_list_.clear();\n    for (auto itr = task_map_.begin(); itr != task_map_.end();) {\n      if (!itr->second || !itr->second->IsValid() || itr->second->IsEmpty()) {\n        itr = task_map_.erase(itr);\n        continue;\n      }\n      last_task_list_.emplace_back(itr->second);\n      itr++;\n    }\n    if (last_task_list_.size() > 1) {\n      std::sort(last_task_list_.begin(), last_task_list_.end(),\n                GapTaskBundle::GapTaskBundleComparator());\n    }\n    // change data might cost a little time, so we need to update start time.\n    start_time = fml::TimePoint::Now();\n  }\n\n  int64_t current_interval_ns = (end_time - start_time).ToNanoseconds();\n  for (auto itr = last_task_list_.begin(); itr != last_task_list_.end();) {\n    TRACE_EVENT(\"clay\", \"HandleTaskBundle\");\n    if (current_interval_ns < 0) {\n      return;\n    }\n    if (!(*itr)->IsValid() || (*itr)->IsEmpty()) {\n      itr = last_task_list_.erase(itr);\n      continue;\n    }\n    for (auto task = (*itr)->begin(); task != (*itr)->end();) {\n      auto item_duration = (*task)->estimate_time();\n      // Some task is very heavy and its estimate time might longer than\n      // frame interval (e.x. 25ms > 16.7ms), for these tasks, we will check\n      // if we can run it forcibly .\n      //\n      // First check if estimate duration is less than our current idle interval\n      if (item_duration > current_interval_ns) {\n        // Then check if the task enable force run.\n        if (!(*task)->enable_force_run() ||\n            current_interval_ns < max_estimate_duration_) {\n          // We can't run this task queue, try another one.\n          break;\n        }\n      }\n      (*task)->Run();\n      task = (*itr)->erase(task);\n      current_interval_ns = (end_time - fml::TimePoint::Now()).ToNanoseconds();\n    }\n    itr++;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/gap_worker.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_GAP_WORKER_H_\n#define CLAY_UI_COMMON_GAP_WORKER_H_\n\n#include <functional>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/ui/common/gap_task.h\"\n#include \"clay/ui/component/css_property.h\"\n\nnamespace clay {\n\nusing GapTaskCollector = std::function<void()>;\nclass BaseView;\n\nclass GapWorker {\n public:\n  explicit GapWorker(fml::RefPtr<fml::TaskRunner> ui_task_runner)\n      : ui_task_runner_(ui_task_runner) {\n    max_estimate_duration_ =\n        fml::TimeDelta::FromMilliseconds(8).ToNanoseconds();\n  }\n\n  void CollectTaskIfNeeded();\n  void RegisterTaskCollector(BaseView* view, GapTaskCollector collector);\n  void UnregisterTaskCollector(BaseView* view);\n  void SubmitTask(fml::RefPtr<GapTaskBundle> tasks);\n  void CancelTask(BaseView* host);\n  void FlushTask(const fml::TimePoint& end_time);\n  void SetRefreshRate(uint32_t refresh_rate) {\n    max_estimate_duration_ = 1.0E9F / refresh_rate / 2;\n  }\n  bool HasGapTask() const { return !collectors_.empty(); }\n\n private:\n  std::unordered_map<BaseView*, GapTaskCollector> collectors_;\n  std::unordered_map<BaseView*, fml::RefPtr<GapTaskBundle>> task_map_;\n  std::vector<fml::RefPtr<GapTaskBundle>> last_task_list_;\n  fml::RefPtr<fml::TaskRunner> ui_task_runner_;\n  int64_t max_estimate_duration_;\n  bool data_changed_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_GAP_WORKER_H_\n"
  },
  {
    "path": "clay/ui/common/input_client_manager.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_INPUT_CLIENT_MANAGER_H_\n#define CLAY_UI_COMMON_INPUT_CLIENT_MANAGER_H_\n\n#include <functional>\n#include <unordered_map>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nclass InputClientManager {\n public:\n  InputClientManager() = default;\n  ~InputClientManager() = default;\n\n  struct TextInputCallback {\n    std::function<void(uint64_t selection_base, uint64_t composing_extent,\n                       const char* selection_affinity, const char* text,\n                       uint64_t selection_extent, uint64_t composing_base)>\n        on_update_edit_state;\n    std::function<void()> on_perform_action;\n  };\n\n  void AddClientCallback(int client_id, const TextInputCallback& callback) {\n    text_input_callbacks_[client_id] = callback;\n  }\n\n  void RemoveClientCallback(int client_id) {\n    text_input_callbacks_.erase(client_id);\n  }\n\n  void InvokeUpdateEditState(int client_id, uint64_t selection_base,\n                             uint64_t composing_extent,\n                             const char* selection_affinity, const char* text,\n                             uint64_t selection_extent,\n                             uint64_t composing_base) {\n    if (text_input_callbacks_.find(client_id) == text_input_callbacks_.end()) {\n      FML_LOG(ERROR) << \"InputClientManager: client_id not found\";\n      return;\n    }\n    text_input_callbacks_[client_id].on_update_edit_state(\n        selection_base, composing_extent, selection_affinity, text,\n        selection_extent, composing_base);\n  }\n\n  void InvokePerformAction(int client_id) {\n    if (text_input_callbacks_.find(client_id) == text_input_callbacks_.end()) {\n      FML_LOG(ERROR) << \"InputClientManager: client_id not found\";\n      return;\n    }\n    text_input_callbacks_[client_id].on_perform_action();\n  }\n\n private:\n  std::unordered_map<int, TextInputCallback> text_input_callbacks_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_INPUT_CLIENT_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/common/isolate.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/isolate.h\"\n\n#if defined(OS_ANDROID) || defined(OS_HARMONY)\n#include <sys/resource.h>\n#include <unistd.h>\n#endif\n\n#include <algorithm>\n#include <utility>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/no_destructor.h\"\n#include \"clay/ui/resource/font_collection.h\"\n\nnamespace clay {\n\n#if defined(OS_ANDROID) || defined(OS_HARMONY)\nvoid Setter(const lynx::fml::Thread::ThreadConfig& config) {\n  if (::setpriority(PRIO_PROCESS, gettid(), 10) != 0) {\n    FML_LOG(ERROR) << \"Failed to set Workers task runner priority\";\n  }\n}\n#endif\n\n// static\nIsolate& Isolate::Instance() {\n  static fml::NoDestructor<Isolate> manager;\n  return *(manager.get());\n}\n\nIsolate::Isolate()\n    : concurrent_message_loop_(\n#if defined(OS_ANDROID) || defined(OS_HARMONY)\n          fml::ConcurrentMessageLoop::Create(\n              Setter, std::max(std::thread::hardware_concurrency() / 2, 1u))\n#else\n          fml::ConcurrentMessageLoop::Create(\n              std::max(std::thread::hardware_concurrency() / 2, 1u))\n#endif\n              )\n#ifndef ENABLE_SKITY\n      ,\n      skia_concurrent_executor_(\n          [runner = concurrent_message_loop_->GetTaskRunner()](\n              fml::closure work) { runner->PostTask(std::move(work)); })\n#endif  // ENABLE_SKITY\n{\n  // Setting the executor.\n#ifndef ENABLE_SKITY\n  SkExecutor::SetDefault(&skia_concurrent_executor_);\n#endif  // ENABLE_SKITY\n  GraphicsIsolate::Instance().SetDelegate(this);\n}\n\nIsolate::~Isolate() {\n#ifndef ENABLE_SKITY\n  SkExecutor::SetDefault(nullptr);\n#endif  // ENABLE_SKITY\n}\n\nstd::shared_ptr<clay::FontCollection> Isolate::GetFontCollection() {\n  return FontCollection::Instance();\n}\n\nstd::shared_ptr<txt::FontCollection> Isolate::GetTxtFontCollection() {\n  return FontCollection::Instance()->GetFontCollection();\n}\n\nstd::shared_ptr<fml::ConcurrentTaskRunner>\nIsolate::GetConcurrentWorkerTaskRunner() const {\n  return concurrent_message_loop_->GetTaskRunner();\n}\n\nstd::shared_ptr<fml::ConcurrentMessageLoop>\nIsolate::GetConcurrentMessageLoop() {\n  return concurrent_message_loop_;\n}\n\nGpuResourceCache* Isolate::GetResourceCache() {\n  if (!resource_cache_) {\n    resource_cache_ = std::make_unique<GpuResourceCache>();\n  }\n  return resource_cache_.get();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/isolate.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_ISOLATE_H_\n#define CLAY_UI_COMMON_ISOLATE_H_\n\n#include <map>\n#include <memory>\n#include <utility>\n\n#include \"base/include/fml/concurrent_message_loop.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/ui/resource/gpu_resource_cache.h\"\n\nnamespace txt {\nclass FontCollection;\n}\n\nnamespace clay {\n\nclass FontCollection;\nclass ViewContext;\n\n// Singleton to contain infras will be accessed by clay.\nclass Isolate : public GraphicsDelegate {\n public:\n  static Isolate& Instance();\n\n  Isolate();\n  ~Isolate();\n\n  std::shared_ptr<clay::FontCollection> GetFontCollection();\n  std::shared_ptr<txt::FontCollection> GetTxtFontCollection();\n\n  void SetPlatformTaskRunner(fml::RefPtr<fml::TaskRunner> runner) {\n    platform_task_runner_ = std::move(runner);\n  }\n\n  fml::RefPtr<fml::TaskRunner> GetPlatformTaskRunner() {\n    return platform_task_runner_;\n  }\n\n  void SetIOTaskRunner(fml::RefPtr<fml::TaskRunner> runner) {\n    io_task_runner_ = runner;\n  }\n\n  fml::RefPtr<fml::TaskRunner> GetIOTaskRunner() { return io_task_runner_; }\n\n  // The task runner whose tasks may be executed concurrently on a pool of\n  // worker threads. All subsystems within a running shell instance use this\n  // worker pool for their concurrent tasks. This also means that the concurrent\n  // worker pool may service tasks from multiple shell instances.\n  // The task runner for the concurrent worker thread pool.\n  std::shared_ptr<fml::ConcurrentTaskRunner> GetConcurrentWorkerTaskRunner()\n      const override;\n\n  // The concurrent message loop hosts threads that are used by the engine to\n  // perform tasks long running background tasks. Typically, to post tasks to\n  // this message loop, the GetConcurrentWorkerTaskRunner` method may be used.\n  std::shared_ptr<fml::ConcurrentMessageLoop> GetConcurrentMessageLoop();\n\n  GpuResourceCache* GetResourceCache();\n\n  void RegisterViewContext(uint32_t id, fml::WeakPtr<ViewContext> context) {\n    view_contexts_.emplace(id, context);\n  }\n\n  void UnregisterViewContext(uint32_t id) { view_contexts_.erase(id); }\n\n  fml::WeakPtr<ViewContext> GetViewContextById(uint32_t id) {\n    auto iter = view_contexts_.find(id);\n    if (iter != view_contexts_.end()) {\n      return iter->second;\n    }\n    return fml::WeakPtr<ViewContext>();\n  }\n\n  void CacheStoreImage(fml::RefPtr<SkImageHolder> img) override {\n    GetResourceCache()->StoreImage(img);\n  }\n  void CacheRemoveImage(fml::RefPtr<SkImageHolder> img) override {\n    GetResourceCache()->RemoveImage(img);\n  }\n\n  void UpdateResourceCacheMaxMemoryLimit(int limit, int low_end_limit) {\n    GetResourceCache()->UpdateResourceCacheMaxMemoryLimit(limit, low_end_limit);\n  }\n\n  void EnablePartialRepaint(bool enable_partial_repaint) {\n    enable_partial_repaint_ = enable_partial_repaint;\n  }\n  bool IsPartialRepaintEnabled() const { return enable_partial_repaint_; }\n\n private:\n  std::shared_ptr<fml::ConcurrentMessageLoop> concurrent_message_loop_;\n  std::unique_ptr<GpuResourceCache> resource_cache_;\n#ifndef ENABLE_SKITY\n  clay::SkiaConcurrentExecutor skia_concurrent_executor_;\n#endif  // ENABLE_SKITY\n  fml::RefPtr<fml::TaskRunner> platform_task_runner_;\n  fml::RefPtr<fml::TaskRunner> io_task_runner_;\n  std::map<uint32_t, fml::WeakPtr<ViewContext>> view_contexts_;\n  bool enable_partial_repaint_ = true;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_ISOLATE_H_\n"
  },
  {
    "path": "clay/ui/common/json_utils.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_JSON_UTILS_H_\n#define CLAY_UI_COMMON_JSON_UTILS_H_\n\n#include <optional>\n\nnamespace clay {\n\nnamespace json_utils {\n\ntemplate <typename T, typename K, typename Object>\nstd::optional<T> TryGetObject(Object& obj, K key) {\n  auto iter = obj.FindMember(key);\n  if (iter == obj.MemberEnd()) {\n    return {};\n  }\n  if (!iter->value.template Is<T>()) {\n    return {};\n  }\n  return iter->value.template Get<T>();\n}\n\ntemplate <typename T, typename Array>\nstd::optional<T> TryGetArray(Array& arr, uint32_t index) {\n  if (index >= arr.Size()) {\n    return {};\n  }\n  if (!arr[index].template Is<T>()) {\n    return {};\n  }\n  return arr[index].template Get<T>();\n}\n\n}  // namespace json_utils\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_JSON_UTILS_H_\n"
  },
  {
    "path": "clay/ui/common/macros.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_MACROS_H_\n#define CLAY_UI_COMMON_MACROS_H_\n\n#define DCHECK_RET0(__condition__) \\\n  FML_DCHECK(__condition__);       \\\n  do {                             \\\n    if (!(__condition__)) {        \\\n      return;                      \\\n    }                              \\\n  } while (0)\n\n#define DCHECK_RET1(__condition__, __ret__) \\\n  FML_DCHECK(__condition__);                \\\n  do {                                      \\\n    if (!(__condition__)) {                 \\\n      return (__ret__);                     \\\n    }                                       \\\n  } while (0)\n\n#endif  // CLAY_UI_COMMON_MACROS_H_\n"
  },
  {
    "path": "clay/ui/common/measure_constraint.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_MEASURE_CONSTRAINT_H_\n#define CLAY_UI_COMMON_MEASURE_CONSTRAINT_H_\n\n#include <optional>\n\nnamespace clay {\n\nenum class MeasureMode {\n  kIndefinite = 0,  // 不指定宽高\n  kDefinite,        // 指定宽高\n  kAtMost,          // 排版结果最大为指定的宽高\n};\n\nstruct MeasureConstraint {\n  std::optional<float> width;\n  MeasureMode width_mode;\n  std::optional<float> height;\n  MeasureMode height_mode;\n\n  bool IsValid() const {\n    return (width_mode == MeasureMode::kIndefinite ||\n            (width.has_value() && *width > 0.0)) &&\n           (height_mode == MeasureMode::kIndefinite ||\n            (height.has_value() && *height > 0.0));\n  }\n\n  bool operator==(const MeasureConstraint& constraint) const {\n    return this->width == constraint.width &&\n           this->width_mode == constraint.width_mode &&\n           this->height == constraint.height &&\n           this->height_mode == constraint.height_mode;\n  }\n};\n\nstruct MeasureResult {\n  float width = 0.f;\n  float height = 0.f;\n  float baseline = 0.f;\n};\n\n}  // namespace clay\n\nnamespace std {\ntemplate <>\nstruct hash<clay::MeasureConstraint> {\n  std::size_t operator()(\n      const clay::MeasureConstraint& measure_constraint) const {\n    int prime = 31;\n    size_t result = 1;\n    if (measure_constraint.width.has_value()) {\n      result = result * prime + std::hash<float>()(*measure_constraint.width);\n    }\n    if (measure_constraint.height.has_value()) {\n      result = result * prime + std::hash<float>()(*measure_constraint.height);\n    }\n    result = result * prime +\n             std::hash<clay::MeasureMode>()(measure_constraint.width_mode);\n    result = result * prime +\n             std::hash<clay::MeasureMode>()(measure_constraint.height_mode);\n    return result;\n  }\n};\n}  // namespace std\n\n#endif  // CLAY_UI_COMMON_MEASURE_CONSTRAINT_H_\n"
  },
  {
    "path": "clay/ui/common/overlay_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/overlay_manager.h\"\n\n#include <algorithm>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/overlay_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/event/key_event.h\"\n\nnamespace clay {\n\nnamespace {\nconstexpr const char* kDefaultOverlayIdPrefix = \"default_overlay_id_\";\n}\n\nOverlayManager::OverlayManager(PageView* page_view) : page_view_(page_view) {}\n\nstd::string OverlayManager::GenerateNextOverlayId() {\n  std::string overlay_id =\n      kDefaultOverlayIdPrefix + std::to_string(current_id_++);\n  return overlay_id;\n}\n\nOverlayView* OverlayManager::GetOverlay(std::string_view overlay_id) const {\n  if (overlay_id.empty()) {\n    return nullptr;\n  }\n  const auto it = std::find_if(overlays_.begin(), overlays_.end(),\n                               [overlay_id](OverlayView* overlay) {\n                                 return overlay_id == overlay->GetOverlayId();\n                               });\n  if (it == overlays_.end()) {\n    FML_DLOG(ERROR) << \"Unable to find OverlayView by overlay_id \"\n                    << overlay_id;\n    return nullptr;\n  }\n  return *it;\n}\n\nvoid OverlayManager::OnHideOverlay(OverlayView* overlay) {\n  FML_DCHECK(overlay);\n  auto iter = std::find(overlays_.begin(), overlays_.end(), overlay);\n  if (iter == overlays_.end()) {\n    return;\n  }\n\n  overlays_.erase(iter);\n  if (overlay->ShouldChangeOffset()) {\n    if (overlay->HasEvent(event_attr::kEventDismissOverlay)) {\n      page_view_->SendEvent(overlay->id(), event_attr::kEventDismissOverlay,\n                            {});\n    }\n  } else {\n    SendOverlayEvent(overlay, \"onDismissOverlay\");\n  }\n\n  if (!overlay->CanEventsPassThroughToViewsBehind() && overlay->Visible()) {\n    RestoreFocus();\n  }\n}\n\nvoid OverlayManager::OnShowOverlay(OverlayView* overlay) {\n  FML_DCHECK(overlay);\n\n  // Avoid add overlay twice\n  if (std::find(overlays_.begin(), overlays_.end(), overlay) !=\n      overlays_.end()) {\n    return;\n  }\n  if (!overlay->CanEventsPassThroughToViewsBehind() && overlay->Visible()) {\n    SaveFocus();\n    overlay->RequestFocus();\n  }\n\n  // Overlays are ordered by level with descent order. For overlays with the\n  // same level, newer is at the end.\n  // The actual UI level is managed by the platform side\n  auto it = overlays_.rbegin();\n  for (; it != overlays_.rend(); it++) {\n    if ((*it)->Level() < overlay->Level()) {\n      break;\n    }\n  }\n  if (overlays_.size() == 0) {\n    overlays_.push_back(overlay);\n  } else {\n    auto insert_pos = std::next(it.base(), 1);\n    overlays_.insert(insert_pos, overlay);\n  }\n  if (overlay->ShouldChangeOffset()) {\n    if (overlay->HasEvent(event_attr::kEventShowOverlay)) {\n      // In clay, overlays will never show failure\n      page_view_->SendEvent(overlay->id(), event_attr::kEventShowOverlay,\n                            {\"errorCode\", \"errorMsg\"}, 0, \"success\");\n    } else {\n      SendOverlayEvent(overlay, \"onShowOverlay\");\n    }\n  }\n}\n\nbool OverlayManager::DispatchKeyEvent(const KeyEvent* event) {\n  if (IsBackOrEscapeKey(event->GetLogical()) &&\n      event->GetType() != KeyEventType::kDown && ignore_up_event_once_) {\n    // Block kUp event once.\n    ignore_up_event_once_ = false;\n    return true;\n  }\n\n  for (auto it = overlays_.rbegin(); it != overlays_.rend(); ++it) {\n    auto overlay = *it;\n    if (!overlay->Visible()) {\n      continue;\n    }\n\n    if (overlay->GetFocusManager()->DispatchKeyEvent(event)) {\n      return true;\n    }\n\n    // Check whether should close the overlay.\n    if (IsBackOrEscapeKey(event->GetLogical()) &&\n        event->GetType() == KeyEventType::kDown) {\n      RequestCloseOverlay(overlay->GetOverlayId());\n      ignore_up_event_once_ = true;\n      return true;\n    }\n\n    if (!overlay->CanEventsPassThroughToViewsBehind()) {\n      break;\n    }\n  }\n\n  return false;\n}\n\nbool OverlayManager::HitTest(const PointerEvent& event, HitTestResult& result,\n                             bool& is_pass_through,\n                             PointerEvent& converted_position) {\n  for (auto it = overlays_.rbegin(); it != overlays_.rend(); ++it) {\n    auto overlay = *it;\n    if (!overlay->Visible()) {\n      continue;\n    }\n    auto overlay_result = overlay->HitTest(event, result, is_pass_through);\n    if (overlay_result) {\n      return true;\n    } else if (overlay->ShouldChangeOffset() && is_pass_through) {\n      auto offset = overlay->GetTouchOffset();\n      converted_position.position.Move(offset.x(), offset.y());\n    }\n  }\n  return false;\n}\n\nvoid OverlayManager::OnReportTopViewEvent(const PointerEvent& event,\n                                          ClayEventType type) {\n  for (auto it = overlays_.rbegin(); it != overlays_.rend(); ++it) {\n    auto overlay = *it;\n    if (!overlay->ShouldChangeOffset() || !overlay->Visible()) {\n      continue;\n    }\n    if (overlay->HasEvent(event_attr::kEventOverlayTouch)) {\n      int state = 0;\n      switch (type) {\n        case ClayEventType::kClayEventTypeTouchStart:\n          state = 0;\n          break;\n        case ClayEventType::kClayEventTypeTouchMove:\n          state = 1;\n          break;\n        case ClayEventType::kClayEventTypeTouchEnd:\n        case ClayEventType::kClayEventTypeTouchCancel:\n          state = 2;\n          break;\n        default:\n          state = -1;\n          break;\n      }\n      // TODO(ZhuChengCheng) we should pass velocity here to aligning the params\n      // with lynx Android/iOS\n      page_view_->SendEvent(overlay->id(), event_attr::kEventOverlayTouch,\n                            {\"x\", \"y\", \"state\"}, event.position.x(),\n                            event.position.y(), state);\n    }\n  }\n}\n\nBaseView* OverlayManager::GetTopViewToAcceptEvent(\n    const FloatPoint& position, FloatPoint* relative_position,\n    bool& is_pass_through, FloatPoint& converted_position) {\n  FML_DCHECK(relative_position);\n  bool found_visible_overlay = false;\n  Point offset;\n  for (auto it = overlays_.rbegin(); it != overlays_.rend(); ++it) {\n    auto overlay = *it;\n    if (!overlay->Visible()) {\n      continue;\n    }\n    if (overlay->ShouldChangeOffset() && !found_visible_overlay) {\n      found_visible_overlay = true;\n      // only record top one\n      offset = overlay->GetTouchOffset();\n    }\n    auto view = overlay->GetTopViewToAcceptEvent(position, relative_position);\n    if (view) {\n      if (view == overlay && overlay->CanEventsPassThroughToViewsBehind()) {\n        continue;\n      }\n      // consumed by overlay child\n      return view;\n    }\n  }\n  if (found_visible_overlay) {\n    // when pass to flutter view to comsume , should convert cordinate\n    is_pass_through = found_visible_overlay;\n    converted_position.Move(offset.x(), offset.y());\n  }\n  return nullptr;\n}\n\nvoid OverlayManager::SaveFocus() {\n  focus_saver_.Save(page_view_->GetFocusManager()->GetLeafFocusedNode());\n}\n\nvoid OverlayManager::RestoreFocus() { focus_saver_.Restore(); }\n\nvoid OverlayManager::RequestCloseOverlay(std::string_view overlay_id) {\n  FML_DCHECK(!overlay_id.empty());\n\n  OverlayView* overlay = GetOverlay(overlay_id);\n  FML_DCHECK(overlay);\n  SendOverlayEvent(overlay, \"onRequestClose\");\n}\n\nvoid OverlayManager::SendOverlayEvent(OverlayView* overlay,\n                                      const char* event_name) const {\n  if (!page_view_->GetEventDelegate()) {\n    return;\n  }\n  size_t overlay_count = 0;\n  std::vector<const char*> overlay_ids(overlays_.size());\n  for (auto& overlay : overlays_) {\n    overlay_ids[overlay_count++] = overlay->GetOverlayId().data();\n  }\n\n  page_view_->GetEventDelegate()->OnOverlayEvent(\n      overlay->id(), overlay->GetOverlayId().data(), overlay_count,\n      overlay_ids.data(), event_name);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/overlay_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_OVERLAY_MANAGER_H_\n#define CLAY_UI_COMMON_OVERLAY_MANAGER_H_\n\n#include <list>\n#include <memory>\n#include <stack>\n#include <string>\n#include <vector>\n\n#include \"clay/ui/component/overlay_view.h\"\n\nnamespace clay {\n\nclass PageView;\nclass OverlayView;\n\nclass OverlayManager {\n public:\n  explicit OverlayManager(PageView* page_view);\n\n  std::string GenerateNextOverlayId();\n\n  OverlayView* GetOverlay(std::string_view overlay_id) const;\n\n  void OnHideOverlay(OverlayView* overlay);\n  void OnShowOverlay(OverlayView* overlay);\n\n  bool HasOverlay() const { return !overlays_.empty(); }\n\n  bool DispatchKeyEvent(const KeyEvent* event);\n  bool HitTest(const PointerEvent& event, HitTestResult& result,\n               bool& is_pass_through, PointerEvent& converted_position);\n  BaseView* GetTopViewToAcceptEvent(const FloatPoint& position,\n                                    FloatPoint* relative_position,\n                                    bool& is_pass_through,\n                                    FloatPoint& converted_position);\n\n  void OnReportTopViewEvent(const PointerEvent& event, ClayEventType type);\n\n  void SaveFocus();\n  void RestoreFocus();\n\n private:\n  FRIEND_TEST(OverlayViewTest, ShowAndHide);\n  FRIEND_TEST(OverlayViewTest, Level);\n\n  // FIXME(yulitao): There will still be some bug remain. For example,\n  //  there is a view tree like: page -> modal1 -> modal2 -> modal3.\n  //  Now focus is on modal3. Meanwhile making modal1 invisible,\n  //  focus will be assigned to modal2 rather than not changed.\n  // To resolve this, make association between saved focus node and overlay.\n  class FocusSaver {\n   public:\n    void Save(FocusNode* node) {\n      saved_focus_stack_.emplace(node);\n      if (node) {\n        node->ClearFocus();\n      }\n    }\n\n    void Restore() {\n      if (!saved_focus_stack_.empty() && saved_focus_stack_.top()) {\n        saved_focus_stack_.top()->RequestFocus();\n        saved_focus_stack_.pop();\n      }\n    }\n\n   private:\n    // Helper class for monitor lifecycle of a FocusNode\n    class FocusNodeCacheWrapper : public FocusNode::LifecycleListener {\n     public:\n      explicit FocusNodeCacheWrapper(FocusNode* node) {\n        node_ = node;\n        if (node_) node_->AddLifecycleListener(this);\n      }\n\n      virtual ~FocusNodeCacheWrapper() {\n        if (node_) node_->RemoveLifecycleListener(this);\n      }\n\n      FocusNodeCacheWrapper& operator=(FocusNodeCacheWrapper&&) = delete;\n      FocusNodeCacheWrapper& operator=(const FocusNodeCacheWrapper&) = delete;\n      FocusNodeCacheWrapper(FocusNodeCacheWrapper&&) = delete;\n      FocusNodeCacheWrapper(const FocusNodeCacheWrapper&) = delete;\n\n      FocusNode* operator->() {\n        FML_DCHECK(node_);\n        return node_;\n      }\n      bool operator==(const FocusNode* other) { return node_ == other; }\n      bool operator!=(const FocusNode* other) { return !operator==(other); }\n      operator bool() { return !!node_; }\n\n     private:\n      void OnFocusNodeUnregistered(FocusNode* node) override {\n        FML_DCHECK(node_ == node);\n        if (node_) {\n          node_->RemoveLifecycleListener(this);\n        }\n        node_ = nullptr;\n      }\n\n      FocusNode* node_ = nullptr;\n    };\n\n    std::stack<FocusNodeCacheWrapper> saved_focus_stack_;\n  };\n\n  void RequestCloseOverlay(std::string_view overlay_id);\n\n  // Send callback to lynx\n  void SendOverlayEvent(OverlayView* overlay, const char* event_name) const;\n\n  PageView* page_view_;\n\n  FocusSaver focus_saver_;\n\n  bool ignore_up_event_once_ = false;\n  uint32_t current_id_ = 0;\n\n  // All visible overlay will manager by `OverlayManager`\n  std::list<OverlayView*> overlays_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_OVERLAY_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/common/render_settings.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/render_settings.h\"\n\n#include \"clay/common/sys_info.h\"\n\nnamespace clay {\n\nRenderSettings::RenderSettings() = default;\nRenderSettings::~RenderSettings() = default;\n\nvoid RenderSettings::RenewMode() {\n  if (is_touching_ || has_animation_) {\n    mode_ = kSMOOTH;\n    return;\n  }\n  mode_ = kNORMAL;\n}\n\nvoid RenderSettings::SetIsTouching(bool is_touching) {\n  if (is_touching_ == is_touching) {\n    return;\n  }\n  is_touching_ = is_touching;\n  RenewMode();\n}\n\nvoid RenderSettings::SetHasAnimation(bool has_animation) {\n  if (has_animation_ == has_animation) {\n    return;\n  }\n  has_animation_ = has_animation;\n  RenewMode();\n}\n\nbool RenderSettings::ShouldLowMemoryUsage() const {\n  if (mode_ != kNORMAL) {\n    return false;\n  }\n  if (SysInfo::IsLowEndDevice()) {\n    return true;\n  }\n  return false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/render_settings.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_RENDER_SETTINGS_H_\n#define CLAY_UI_COMMON_RENDER_SETTINGS_H_\n\n#include <atomic>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n\nnamespace clay {\nclass RenderSettings : public fml::RefCountedThreadSafe<RenderSettings> {\n public:\n  // kSMOOTH：Priority to guarantee the frame rate.\n  // kNEW_CONTENT：Priority to reduce content update time. not implemented.\n  enum RenderMode {\n    kNORMAL = 0,\n    kSMOOTH,\n    kNEW_CONTENT,\n  };\n  RenderSettings();\n  ~RenderSettings();\n  void RenewMode();\n  void SetIsTouching(bool);\n  void SetHasAnimation(bool);\n\n  RenderMode GetMode() const { return mode_; }\n  bool ShouldLowMemoryUsage() const;\n\n  void SetIgnoreRasterCache(bool value) { ignore_raster_cache = value; }\n  bool IgnoreRasterCache() const { return ignore_raster_cache; }\n\n private:\n  std::atomic<RenderMode> mode_ = kNORMAL;\n  bool is_touching_ = false;\n  bool has_animation_ = false;\n  // Wether ignore raster cache for page.\n  std::atomic<bool> ignore_raster_cache = false;\n};\n}  // namespace clay\n#endif  // CLAY_UI_COMMON_RENDER_SETTINGS_H_\n"
  },
  {
    "path": "clay/ui/common/text_input_type_traits.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_TEXT_INPUT_TYPE_TRAITS_H_\n#define CLAY_UI_COMMON_TEXT_INPUT_TYPE_TRAITS_H_\n\n#include \"clay/shell/platform/common/text_input_model.h\"\n\nnamespace clay {\n\nusing TextEditingValue = clay::TextInputModel;\nusing TextInputModel = clay::TextInputModel;\nusing TextRange = clay::TextRange;\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_TEXT_INPUT_TYPE_TRAITS_H_\n"
  },
  {
    "path": "clay/ui/common/text_selection.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_TEXT_SELECTION_H_\n#define CLAY_UI_COMMON_TEXT_SELECTION_H_\n\n#include <algorithm>\n\n#include \"clay/ui/common/editing_misc.h\"\n\nnamespace clay {\n\n// Represents cursor selection of range in text.\nclass TextSelection {\n public:\n  TextSelection() : base_offset_(0), extent_offset_(0) {}\n\n  explicit TextSelection(int same_offset)\n      : base_offset_(same_offset), extent_offset_(same_offset) {}\n\n  TextSelection(int base_offset, int extent_offset, Affinity affinity)\n      : base_offset_(base_offset),\n        extent_offset_(extent_offset),\n        affinity_(affinity) {}\n\n  void ResetToStart() { MoveTo(0, 0); }\n\n  void MoveLeft(int step) { Move(-step); }\n\n  void MoveRight(int step) { Move(step); }\n\n  // Move caret without selection.\n  void Move(int step) {\n    // Here we don't use |base_offset_| because there's no selection.\n    MoveTo(extent_offset_ + step, extent_offset_ + step);\n  }\n\n  void MoveExtent(int step) { extent_offset_ += step; }\n\n  void MoveTo(int base_offset, int extent_offset) {\n    base_offset_ = base_offset;\n    extent_offset_ = extent_offset;\n  }\n\n  bool IsValid() const { return base_offset_ >= 0 && extent_offset_ >= 0; }\n\n  int base_offset() const { return base_offset_; }\n  int extent_offset() const { return extent_offset_; }\n  int start() const { return std::min(base_offset_, extent_offset_); }\n  int end() const { return std::max(base_offset_, extent_offset_); }\n  bool collapsed() const { return base_offset_ == extent_offset_; }\n  Affinity affinity() const { return affinity_; }\n\n  void SetBaseOffset(int base_offset) { base_offset_ = base_offset; }\n  void SetExtentOffset(int extent_offset) { extent_offset_ = extent_offset; }\n  void SetSameOffset(int same_offset) {\n    base_offset_ = same_offset;\n    extent_offset_ = same_offset;\n  }\n  void set_affinity(Affinity value) { affinity_ = value; }\n\n private:\n  // TODO(yulitao): consider when need offset be -1.\n  int base_offset_ = -1;\n  // Extent offset is always where caret is.\n  int extent_offset_ = -1;\n  Affinity affinity_ = Affinity::kDownstream;\n};\n\ninline bool operator==(const TextSelection& lhs, const TextSelection& rhs) {\n  return lhs.base_offset() == rhs.base_offset() &&\n         lhs.extent_offset() == rhs.extent_offset() &&\n         lhs.affinity() == rhs.affinity();\n}\n\ninline bool operator!=(const TextSelection& lhs, const TextSelection& rhs) {\n  return !(lhs == rhs);\n}\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_TEXT_SELECTION_H_\n"
  },
  {
    "path": "clay/ui/common/type_info.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_TYPE_INFO_H_\n#define CLAY_UI_COMMON_TYPE_INFO_H_\n\n#include <type_traits>\n#include <utility>\n\nnamespace clay {\n\nnamespace detail {\n\nstruct TypeChain {\n  const TypeChain* parent;\n};\n\n// This helper class is used to check if a class is directly derived from\n// `WithTypeInfo`.\ntemplate <typename T>\nclass TypeRegisterChecker {};\n\n}  // namespace detail\n\nusing TypeId = const detail::TypeChain*;\n\n// This class is used to implement dynamic type checking. The root type you want\n// to check should inherit from `TypeIdentifiable<YourRootClass>`.\n// Example:\n//     class BaseView : public TypeIdentifiable<BaseView> {};\n//     class View1 : public WithTypeInfo<View1, BaseView> {};\n//     class View2 : public WithTypeInfo<View2, View1> {};\n//     BaseView* view = new View1();\n//     assert(view->Is<View1>());\n//     assert(!view->Is<View2>());\ntemplate <typename RootType>\nclass TypeIdentifiable {\n public:\n  TypeIdentifiable() : type_id_(StaticType()) {}\n\n  bool IsOfType(TypeId type) const {\n    auto t = GetTypeId();\n    do {\n      if (t == type) {\n        return true;\n      }\n      t = t->parent;\n    } while (t);\n    return false;\n  }\n\n  template <typename T>\n  bool Is() const {\n    static_assert(\n        std::is_base_of_v<detail::TypeRegisterChecker<T>, T>,\n        \"To use Is<T>(), T must be directly derived from WithTypeInfo<>\");\n    return IsOfType(T::StaticType());\n  }\n\n  static TypeId StaticType() { return &s_static_type_; }\n\n  TypeId GetTypeId() const { return type_id_; }\n\n private:\n  template <typename Derived, typename Base>\n  friend class WithTypeInfo;\n\n  inline static const detail::TypeChain s_static_type_ = {nullptr};\n\n  TypeId type_id_;\n};\n\n// This class is used to implement dynamic type checking. For detail, see\n// `TypeIdentifiable`.\ntemplate <typename Derived, typename Base>\nclass WithTypeInfo : public Base, public detail::TypeRegisterChecker<Derived> {\n public:\n  template <typename... Args>\n  explicit WithTypeInfo(Args&&... args) : Base(std::forward<Args>(args)...) {\n    this->type_id_ = StaticType();\n  }\n\n  static TypeId StaticType() { return &s_static_type_; }\n\n private:\n  // Using static member instead of static variable to avoid extra binary size.\n  inline static const detail::TypeChain s_static_type_ = {Base::StaticType()};\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_TYPE_INFO_H_\n"
  },
  {
    "path": "clay/ui/common/utils/floating_comparison.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_UTILS_FLOATING_COMPARISON_H_\n#define CLAY_UI_COMMON_UTILS_FLOATING_COMPARISON_H_\n\n#include <algorithm>\n\nnamespace clay {\n\nstatic const float kEpsilon = 1e-6f;\n\ninline bool RoughlyEqual(float v1, float v2) {\n  return fabsf(v1 - v2) < kEpsilon;\n}\n\ninline bool RoughlyNotEqual(float v1, float v2) {\n  return !RoughlyEqual(v1, v2);\n}\n\ninline bool RoughlyEqualToZero(float value) { return RoughlyEqual(value, 0.f); }\n\ninline bool RoughlyNotZero(float value) { return !RoughlyEqualToZero(value); }\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_UTILS_FLOATING_COMPARISON_H_\n"
  },
  {
    "path": "clay/ui/common/utils/value_change_notifier.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_UTILS_VALUE_CHANGE_NOTIFIER_H_\n#define CLAY_UI_COMMON_UTILS_VALUE_CHANGE_NOTIFIER_H_\n\n#include <unordered_set>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\n// An observer pattern util class for monitor changes of a given value.\n// The value being observed must correctly implements operator ==.\n// Notice: Used for single-thread only.\ntemplate <typename T>\nclass ValueChangeNotifier {\n public:\n  ValueChangeNotifier() = default;\n  explicit ValueChangeNotifier(const T& value) : value_(value) {}\n  explicit ValueChangeNotifier(T&& value) : value_(std::move(value)) {}\n\n  class Observer {\n   public:\n    virtual void OnValueChanged(const T& value, const ValueChangeNotifier*) = 0;\n  };\n\n  void AddObserver(Observer* observer) {\n    FML_CHECK(observer);\n#ifndef NDEBUG\n    FML_DCHECK(!notifying_) << \"Mustn't add observer during OnValueChanged.\";\n#endif\n    observers_.emplace(observer);\n  }\n\n  // As same as common sense, observers must be removed before destroyed.\n  // Must NOT RemoveObserver during Observer::OnValueChanged callback!\n  void RemoveObserver(Observer* observer) {\n    FML_CHECK(observer);\n#ifndef NDEBUG\n    FML_DCHECK(!notifying_) << \"Mustn't remove observer during OnValueChanged.\";\n#endif\n    if (!observers_.erase(observer)) {\n      FML_DCHECK(false) << \"Did you forget add observer?\";\n    }\n  }\n\n  void NotifyValueChanged() {\n#ifndef NDEBUG\n    notifying_ = true;\n#endif\n    for (Observer* observer : observers_) {\n      observer->OnValueChanged(value_, this);\n    }\n#ifndef NDEBUG\n    notifying_ = false;\n#endif\n  }\n\n  bool SetValue(const T& value) {\n    if (value == value_) {\n      return false;\n    }\n    value_ = value;\n    NotifyValueChanged();\n    return true;\n  }\n\n  T GetValue() { return value_; }\n\n private:\n  std::unordered_set<Observer*> observers_;\n  T value_;\n#ifndef NDEBUG\n  bool notifying_ = false;\n#endif\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_UTILS_VALUE_CHANGE_NOTIFIER_H_\n"
  },
  {
    "path": "clay/ui/common/utils/watch_dog.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/utils/watch_dog.h\"\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nWatchDog::WatchDog(fml::RefPtr<fml::TaskRunner> task_runner)\n    : timer_(task_runner, true) {}\n\nWatchDog::~WatchDog() = default;\n\nvoid WatchDog::Start(int64_t time_ms, int64_t interval_ms,\n                     std::function<void()> callback) {\n  FML_DCHECK(time_ms > interval_ms);\n  FML_DCHECK(callback);\n  if (time_ms % interval_ms) {\n    FML_LOG(WARNING) << \"time_ms is not a multiple of interval_ms.\";\n  }\n\n  callback_ = std::move(callback);\n  max_count_ = time_ms / interval_ms;\n  elapsed_count_ = 0;\n  timer_.Start(fml::TimeDelta::FromMilliseconds(interval_ms),\n               [this]() { this->TimeFired(); });\n}\n\nvoid WatchDog::Stop() {\n  callback_ = nullptr;\n  max_count_ = 0;\n  elapsed_count_ = 0;\n  timer_.Stop();\n}\n\nbool WatchDog::IsAlive() { return max_count_ > 0; }\n\nvoid WatchDog::FeedDog() { elapsed_count_ = 0; }\n\nvoid WatchDog::TimeFired() {\n  elapsed_count_++;\n  if (elapsed_count_ >= max_count_) {\n    FML_DCHECK(callback_);\n    callback_();\n    Stop();\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/utils/watch_dog.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_UTILS_WATCH_DOG_H_\n#define CLAY_UI_COMMON_UTILS_WATCH_DOG_H_\n\n#include \"base/include/fml/time/timer.h\"\n\nnamespace clay {\n\nclass WatchDog {\n public:\n  explicit WatchDog(fml::RefPtr<fml::TaskRunner> task_runner);\n  ~WatchDog();\n\n  void Start(int64_t time_ms, int64_t interval_ms,\n             std::function<void()> callback);\n  void Stop();\n  bool IsAlive();\n  void FeedDog();\n\n private:\n  void TimeFired();\n\n  fml::Timer timer_;\n  std::function<void()> callback_;\n  int elapsed_count_ = 0;\n  int max_count_ = 0;\n};\n}  // namespace clay\n#endif  // CLAY_UI_COMMON_UTILS_WATCH_DOG_H_\n"
  },
  {
    "path": "clay/ui/common/value_utils.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/common/value_utils.h\"\n\n#include <cmath>\n#include <cstdlib>\n#include <cstring>\n\nnamespace clay {\n\nconst clay::Value& GetNullClayValue() {\n  static clay::Value value{\n      ClayPointer{ClayPointer::kClayPointerTypeVoidPtr, nullptr}};\n  return value;\n}\n\nclay::Value CloneClayMap(const clay::Value& map) {\n  if (!map.IsMap()) {\n    return clay::Value::Null();\n  }\n  clay::Value::Map rk_map;\n  for (const auto& [key, value] : map.GetMap()) {\n    rk_map.emplace(key, CloneClayValue(value));\n  }\n  return clay::Value{std::move(rk_map)};\n}\n\nclay::Value CloneClayArray(const clay::Value& array) {\n  if (!array.IsArray()) {\n    return clay::Value::Null();\n  }\n  auto size = array.GetArray().size();\n  clay::Value::Array rk_array(size);\n  for (size_t i = 0; i < size; ++i) {\n    rk_array[i] = CloneClayValue(array.GetArray().at(i));\n  }\n  return clay::Value{std::move(rk_array)};\n}\n\nclay::Value CloneClayValue(const clay::Value& value) {\n  switch (value.type()) {\n    case clay::Value::kNone:\n      return clay::Value();\n    case clay::Value::kInt:\n      return clay::Value(value.GetInt());\n    case clay::Value::kLong:\n      return clay::Value(value.GetLong());\n    case clay::Value::kFloat:\n      return clay::Value(value.GetFloat());\n    case clay::Value::kBool:\n      return clay::Value(value.GetBool());\n    case clay::Value::kDouble:\n      return clay::Value(value.GetDouble());\n    case clay::Value::kString:\n      return clay::Value(value.GetString());\n    case clay::Value::kUInt:\n      return clay::Value(value.GetUint());\n    case clay::Value::kPointer:\n      return clay::Value{\n          ClayPointer{value.GetPointerType(), value.GetPointer()}};\n    case clay::Value::kArrayBuffer:\n      // ignore\n      FML_DCHECK(false);\n      return clay::Value();\n    case clay::Value::kArray:\n      return CloneClayArray(value);\n    case clay::Value::kMap:\n      return CloneClayMap(value);\n  }\n  FML_DCHECK(false);\n  return clay::Value{};\n}\n\nbool HasProperty(const clay::Value::Map& m, const char* name) {\n  const auto search = m.find(name);\n  return search != m.end();\n}\n\nbool GetBoolProperty(const clay::Value::Map& m, const char* name) {\n  const auto search = m.find(name);\n  if (search != m.end()) {\n    const auto& val = search->second;\n    if (val.IsBool()) {\n      return val.GetBool();\n    }\n    if (val.IsString()) {\n      return val.GetString() == \"true\";\n    }\n    if (val.IsInt()) {\n      return val.GetInt() != 0;\n    }\n    if (val.IsUint()) {\n      return val.GetUint() != 0;\n    }\n    if (val.IsLong()) {\n      return val.GetLong() != 0;\n    }\n    if (val.IsFloat()) {\n      return val.GetFloat() != 0 && !isnan(val.GetFloat());\n    }\n    if (val.IsDouble()) {\n      return val.GetDouble() != 0 && !isnan(val.GetDouble());\n    }\n  }\n  return false;\n}\n\nint64_t GetIntProperty(const clay::Value::Map& m, const char* name) {\n  const auto search = m.find(name);\n  if (search != m.end()) {\n    const auto& val = search->second;\n    if (val.IsInt()) {\n      return val.GetInt();\n    }\n    if (val.IsString()) {\n      return strtoll(val.GetString().c_str(), nullptr, 10);\n    }\n    if (val.IsUint()) {\n      return val.GetUint();\n    }\n    if (val.IsLong()) {\n      return val.GetLong();\n    }\n    if (val.IsFloat()) {\n      return val.GetFloat();\n    }\n    if (val.IsDouble()) {\n      return val.GetDouble();\n    }\n  }\n  return 0;\n}\n\ndouble GetDoubleProperty(const clay::Value::Map& m, const char* name) {\n  const auto search = m.find(name);\n  if (search != m.end()) {\n    const auto& val = search->second;\n    if (val.IsDouble()) {\n      return val.GetDouble();\n    }\n    if (val.IsInt()) {\n      return val.GetInt();\n    }\n    if (val.IsString()) {\n      return strtod(val.GetString().c_str(), nullptr);\n    }\n    if (val.IsUint()) {\n      return val.GetUint();\n    }\n    if (val.IsLong()) {\n      return val.GetLong();\n    }\n    if (val.IsFloat()) {\n      return val.GetFloat();\n    }\n  }\n  return 0;\n}\n\nconst char* GetStringProperty(const clay::Value::Map& m, const char* name) {\n  const auto search = m.find(name);\n  if (search != m.end()) {\n    const auto& val = search->second;\n    if (val.IsString()) {\n      return val.GetString().c_str();\n    }\n  }\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/common/value_utils.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMMON_VALUE_UTILS_H_\n#define CLAY_UI_COMMON_VALUE_UTILS_H_\n\n#include <algorithm>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/public/value.h\"\n\nnamespace clay {\n\nconst clay::Value& GetNullClayValue();\n\nclay::Value CloneClayValue(const clay::Value& value);\nclay::Value CloneClayMap(const clay::Value& map);\nclay::Value CloneClayArray(const clay::Value& array);\n\n// Usage:\n//  auto map = CreateClayMap({\"arg1\", \"arg2\", \"arg3\", val1, val2, val3});\ntemplate <typename... ValueTypes>\nclay::Value::Map CreateClayMap(const std::vector<std::string>& arg_names,\n                               ValueTypes&&... values) {\n  clay::Value::Map map;\n  if (arg_names.size() != sizeof...(values)) {\n    FML_DCHECK(false);\n    FML_LOG(ERROR) << \"Arg_names size must equal to values size!\";\n  } else {\n    std::size_t i{0};\n    ((map.emplace(arg_names[i++], std::move(values))), ...);\n  }\n  return map;\n}\n\n// Value helpers\nbool HasProperty(const clay::Value::Map& m, const char* name);\nbool GetBoolProperty(const clay::Value::Map& m, const char* name);\nint64_t GetIntProperty(const clay::Value::Map& m, const char* name);\ndouble GetDoubleProperty(const clay::Value::Map& m, const char* name);\nconst char* GetStringProperty(const clay::Value::Map& m, const char* name);\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMMON_VALUE_UTILS_H_\n"
  },
  {
    "path": "clay/ui/component/base_image_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/base_image_view.h\"\n\n#include <array>\n#include <memory>\n#include <string>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/timer/time_utils.h\"\n#include \"clay/common/graphics/shared_image_external_texture.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/value_animator.h\"\n#include \"clay/gfx/style/length.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/net/url/url_helper.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/painter/image_painter.h\"\n#include \"clay/ui/shadow/image_shadow_node.h\"\n\n#ifndef ENABLE_CLAY_LITE\n#include \"clay/ui/component/list/base_list_view.h\"\n#endif\n\n#ifdef ENABLE_SKITY\n#include \"clay/gfx/image/base_image.h\"\n#endif\n\n#ifdef ENABLE_NET_LOADER\n#include \"clay/net/net_loader_manager.h\"\n#endif\n\nnamespace clay {\n\nconstexpr double kImageFadeInDuration = 300;\n\nLYNX_UI_METHOD_BEGIN(BaseImageView) {\n  LYNX_UI_METHOD(BaseImageView, startAnimate);\n  LYNX_UI_METHOD(BaseImageView, stopAnimation);\n  LYNX_UI_METHOD(BaseImageView, pauseAnimation);\n  LYNX_UI_METHOD(BaseImageView, resumeAnimation);\n}\nLYNX_UI_METHOD_END(BaseImageView);\n\nBaseImageView::BaseImageView(std::unique_ptr<RenderObject> render_object,\n                             PageView* page_view)\n    : WithTypeInfo(std::move(render_object), page_view), weak_factory_(this) {\n#if FORCE_IMAGEVIEW_FOCUSABLE\n  SetFocusable(true);\n#endif\n  transition_animator_ = std::make_unique<ValueAnimator>();\n  transition_animator_->SetDuration(kImageFadeInDuration);\n  transition_animator_->SetAnimationHandler(GetAnimationHandler());\n  transition_animator_->AddUpdateListener(this);\n  GetRenderImage()->AddClient(this);\n}\n\nBaseImageView::BaseImageView(uint32_t id, std::string tag,\n                             std::unique_ptr<RenderObject> render_object,\n                             PageView* page_view)\n    : WithTypeInfo(id, std::move(tag), std::move(render_object), page_view),\n      weak_factory_(this) {\n#if FORCE_IMAGEVIEW_FOCUSABLE\n  SetFocusable(true);\n#endif\n  transition_animator_ = std::make_unique<ValueAnimator>();\n  transition_animator_->SetDuration(kImageFadeInDuration);\n  transition_animator_->SetAnimationHandler(GetAnimationHandler());\n  transition_animator_->AddUpdateListener(this);\n  GetRenderImage()->AddClient(this);\n}\n\nBaseImageView::~BaseImageView() {\n  GetRenderImage()->RemoveClient();\n  TryEndTransition();\n  transition_animator_.reset();\n  TryCancelFetch(source_, source_fetch_id_);\n  TryCancelFetch(placeholder_, placeholder_fetch_id_);\n}\n\nvoid BaseImageView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  switch (kw) {\n    case KeywordID::kMode:\n      SetMode(attribute_utils::GetCString(value));\n      break;\n    case KeywordID::kSrc:\n      incoming_source_ = attribute_utils::GetCString(value);\n      fetch_delay_source_ = true;\n      break;\n    case KeywordID::kRepeat:\n      if (attribute_utils::GetBool(value)) {\n        SetRepeat(ImageRepeat::kRepeat);\n      } else {\n        SetRepeat(ImageRepeat::kNoRepeat);\n      }\n      break;\n    case KeywordID::kPlaceholder:\n      incoming_placeholder_ = attribute_utils::GetCString(value);\n      fetch_delay_placeholder_ = true;\n      break;\n    case KeywordID::kSkipRedirection:\n      SetSkipRedirection(attribute_utils::GetBool(value));\n      break;\n    case KeywordID::kImageTransitionStyle:\n      SetImageTransitionStyle(attribute_utils::GetCString(value));\n      break;\n    case KeywordID::kPreventLoadingOnListScroll:\n      prevent_loading_on_list_scroll_ = attribute_utils::GetBool(value);\n      break;\n    case KeywordID::kBlurRadius: {\n      std::string valueWithUnit = attribute_utils::GetCString(value);\n      float blur =\n          attribute_utils::ToPxWithDisplayMetrics(valueWithUnit, page_view());\n      GetRenderImage()->SetBlurRadius(blur);\n    } break;\n    case KeywordID::kLowQuality: {\n      bool enable_low_quality_image = attribute_utils::GetBool(value);\n      GetRenderImage()->SetEnableLowQuality(enable_low_quality_image);\n    } break;\n    case KeywordID::kImageConfig: {\n      std::string image_config = attribute_utils::GetCString(value);\n      if (lynx::base::EqualsIgnoreCase(image_config, \"RGB_565\")) {\n        GetRenderImage()->SetEnableLowQuality(true);\n      } else if (lynx::base::EqualsIgnoreCase(image_config, \"ARGB_8888\")) {\n        GetRenderImage()->SetEnableLowQuality(false);\n      }\n    } break;\n    case KeywordID::kDownsampling: {\n      bool down_sampling = attribute_utils::GetBool(value);\n      GetRenderImage()->SetDownSampling(down_sampling);\n    }\n    case KeywordID::kCapInsets: {\n      std::string cap_insets = attribute_utils::GetCString(value);\n      SetCapInsets(cap_insets);\n    } break;\n    case KeywordID::kCapInsetsScale:\n      SetCapInsetsScale(attribute_utils::GetDouble(value));\n      break;\n    case KeywordID::kDeferSrcInvalidation:\n      defer_src_invalidation_ = attribute_utils::GetBool(value);\n      break;\n    case KeywordID::kAutoplay:\n      SetAutoPlay(attribute_utils::GetBool(value));\n      break;\n    case KeywordID::kLoopCount:\n      SetLoopCount(attribute_utils::GetInt(value));\n      break;\n    case KeywordID::kAutoSize:\n      SetAutoSize(attribute_utils::GetBool(value));\n      break;\n    case KeywordID::kTintColor: {\n      std::string tint_color = attribute_utils::GetCString(value);\n      Color color;\n      if (Color::Parse(tint_color, &color)) {\n        GetRenderImage()->SetTintColor(color);\n      }\n      break;\n    }\n    case KeywordID::kEnableReportInfo:\n      report_info_.enable_report_info = attribute_utils::GetBool(value);\n      break;\n    default:\n      BaseView::SetAttribute(attr_c, value);\n      break;\n  }\n}\n\nvoid BaseImageView::OnNodeReady() {\n  BaseView::OnNodeReady();\n  GetRenderImage()->OnNodeReady();\n}\n\nvoid BaseImageView::SetLocalCache(bool use_local_cache) {}\n\nvoid BaseImageView::SetSkipRedirection(bool skip_redirection) {\n  bool should_redirect_url = !skip_redirection;\n  if (should_redirect_url_ != should_redirect_url) {\n    should_redirect_url_ = should_redirect_url;\n    if (!source_.empty()) {\n      TryCancelFetch(source_, source_fetch_id_);\n      FetchSource();\n    }\n    if (!placeholder_.empty()) {\n      TryCancelFetch(placeholder_, placeholder_fetch_id_);\n      FetchPlaceholder();\n    }\n  }\n}\n\nvoid BaseImageView::SetImageTransitionStyle(const std::string& style) {\n  if (style == attr_value::kImageTransitionFadeIn) {\n    transition_style_ = ImageTransitionStyle::kFadeIn;\n    SetRepaintBoundary(true);\n  } else {\n    transition_style_ = ImageTransitionStyle::kNone;\n  }\n}\n\nvoid BaseImageView::SetImageLoadListener(Listener* listener) {\n  listener_ = listener;\n}\n\nvoid BaseImageView::SetPlaceholder(std::string original_url) {\n  if (original_url.compare(placeholder_) == 0) {\n    return;\n  }\n\n  TryCancelFetch(placeholder_, placeholder_fetch_id_);\n  placeholder_ = std::move(original_url);\n\n  TryEndTransition();\n  auto render_image = GetRenderImage();\n  render_image->SetPlaceholderImage(nullptr);\n  if (!render_image->GetImage()) {\n    FetchPlaceholder();\n  }\n}\n\nvoid BaseImageView::SetSource(std::string original_url) {\n  if (original_url.compare(source_) == 0) {\n    return;\n  }\n\n  TryCancelFetch(source_, source_fetch_id_);\n  TryEndTransition();\n  auto render_image = GetRenderImage();\n  if (!defer_src_invalidation_ || original_url.empty()) {\n    render_image->SetImage(nullptr);\n  }\n  if (!render_image->GetPlaceholderImage() &&\n      placeholder_fetch_id_ == kDefaultImageFetchID) {\n    FetchPlaceholder();\n  }\n\n  source_ = std::move(original_url);\n\n  FetchSource();\n}\n\nvoid BaseImageView::SetSource(const std::string&& url, const uint8_t* source,\n                              const int len) {\n#ifndef ENABLE_SKITY\n  page_view_->GetImageResourceFetcher()->GetImageResource(\n      url,\n      [self = weak_factory_.GetWeakPtr(),\n       ui_task_runner = page_view_->GetTaskRunners().GetUITaskRunner()](\n          std::unique_ptr<ImageResource> resource, bool from_cache) {\n        FML_DCHECK(ui_task_runner->RunsTasksOnCurrentThread());\n        if (self && resource) {\n          self->TriggerTransitionIfNeeded();\n          RenderImage* render_image = self->GetRenderImage();\n          render_image->SetImage(std::move(resource));\n        }\n      },\n      source, len, page_view_->UseTextureBackend(),\n      page_view_->DeferredImageDecode(), page_view_->ImageDecodeWithPriority(),\n      GetRenderImage() && GetRenderImage()->EnableLowQuality());\n#endif  // ENABLE_SKITY\n}\n\nvoid BaseImageView::SetRepeat(ImageRepeat repeat) {\n  auto render_image = GetRenderImage();\n  render_image->SetRepeat(repeat);\n}\n\nvoid BaseImageView::SetMode(const std::string& mode) {\n  FillMode fill_mode = FillMode::kScaleToFill;\n  if (mode.compare(attr_value::kModeScaleToFill) == 0) {\n    fill_mode = FillMode::kScaleToFill;\n  } else if (mode.compare(attr_value::kModeAspectFit) == 0) {\n    fill_mode = FillMode::kAspectFit;\n  } else if (mode.compare(attr_value::kModeAspectFill) == 0) {\n    fill_mode = FillMode::kAspectFill;\n  } else if (mode.compare(attr_value::kModeCenter) == 0) {\n    fill_mode = FillMode::kCenter;\n  } else {\n    return;\n  }\n  SetMode(fill_mode);\n}\n\nvoid BaseImageView::SetMode(FillMode fill_mode) {\n  GetRenderImage()->SetMode(fill_mode);\n}\n\nvoid BaseImageView::SetEffect(ImageEffect effect) {\n  GetRenderImage()->SetEffect(effect);\n}\n\nvoid BaseImageView::SetCapInsets(const std::string& cap_insets) {\n  std::vector<std::string_view> cap_insets_strs =\n      lynx::base::SplitToStringViews(cap_insets, \" \");\n  std::array<Length, 4> cap_insets_arr;\n  size_t count = 0;\n  for (const auto& s : cap_insets_strs) {\n    float value;\n    Length length;\n    std::string percent_str(s.substr(0, s.length() - 1));\n    std::string px_str(s.substr(0, s.length() - 2));\n    if (s.length() >= 2 && lynx::base::EndsWith(s, \"%\") &&\n        lynx::base::StringToFloat(percent_str, value)) {\n      length.SetValue(value / 100.f);\n      length.SetUnit(LengthUnit::kPercent);\n    } else if (s.length() >= 3 && lynx::base::EndsWith(s, \"px\") &&\n               lynx::base::StringToFloat(px_str, value)) {\n      length.SetValue(value);\n      length.SetUnit(LengthUnit::kNum);\n    } else {\n      FML_DLOG(ERROR) << \"ImageView set invalid cap-insets!\";\n      return;\n    }\n\n    cap_insets_arr[count] = length;\n    if (++count >= cap_insets_arr.size()) {\n      break;\n    }\n  }\n  if (count != 4) {\n    FML_DLOG(ERROR) << \"ImageView set invalid cap-insets!\";\n    return;\n  }\n  GetRenderImage()->SetCapInsets(cap_insets_arr);\n}\n\nvoid BaseImageView::SetCapInsetsScale(float scale) {\n  auto render_image = GetRenderImage();\n  render_image->SetCapInsetsScale(scale);\n}\n\nvoid BaseImageView::SetAutoPlay(bool auto_play) {\n  auto render_image = GetRenderImage();\n  render_image->SetAutoPlay(auto_play);\n}\n\nvoid BaseImageView::SetLoopCount(int loop_count) {\n  auto render_image = GetRenderImage();\n  render_image->SetLoopCount(loop_count);\n}\n\nvoid BaseImageView::startAnimate() {\n  auto render_image = GetRenderImage();\n  render_image->StartAnimate();\n}\nvoid BaseImageView::stopAnimation() {\n  auto render_image = GetRenderImage();\n  render_image->StopAnimation();\n}\nvoid BaseImageView::pauseAnimation() {\n  auto render_image = GetRenderImage();\n  render_image->PauseAnimation();\n}\nvoid BaseImageView::resumeAnimation() {\n  auto render_image = GetRenderImage();\n  render_image->ResumeAnimation();\n}\n\nvoid BaseImageView::SetAutoSize(bool auto_size) {\n  auto render_image = GetRenderImage();\n  render_image->SetAutoSize(auto_size);\n}\n\nvoid BaseImageView::NotifyLoadError(const std::string& error_msg) {\n  if (HasEvent(event_attr::kEventImageLoadError)) {\n    page_view()->SendEvent(id(), event_attr::kEventImageLoadError, {\"errMsg\"},\n                           error_msg.c_str());\n  }\n  if (listener_) {\n    listener_->OnImageLoadError(this, error_msg);\n  }\n}\n\nvoid BaseImageView::NotifyLoadSuccess(int width, int height) {\n  // Only send once if enable_report_info or extra_load_info is true.\n  if (HasEvent(event_attr::kEventImageLoadSuccess) &&\n      !report_info_.enable_report_info) {\n    page_view()->SendEvent(id(), event_attr::kEventImageLoadSuccess,\n                           {\"width\", \"height\"}, width, height);\n  }\n  if (listener_) {\n    listener_->OnImageLoadSuccess(this, width, height);\n  }\n}\n\nvoid BaseImageView::NotifyDecodedSuccess() {\n  if (listener_) {\n    listener_->OnImageDecodedSuccess(this);\n  }\n}\n\nvoid BaseImageView::NotifyStartPlay() {\n  if (HasEvent(event_attr::kEventImageStartPlay)) {\n    page_view()->SendEvent(id(), event_attr::kEventImageStartPlay, {});\n  }\n}\nvoid BaseImageView::NotifyCurrentLoopComplete() {\n  if (HasEvent(event_attr::kEventImageCurrentLoopComplete)) {\n    page_view()->SendEvent(id(), event_attr::kEventImageCurrentLoopComplete,\n                           {});\n  }\n}\nvoid BaseImageView::NotifyFinalLoopComplete() {\n  if (HasEvent(event_attr::kEventImageFinalLoopComplete)) {\n    page_view()->SendEvent(id(), event_attr::kEventImageFinalLoopComplete, {});\n  }\n}\n\nvoid BaseImageView::FetchPlaceholder() {\n  if (placeholder_.empty()) {\n    return;\n  }\n#ifndef ENABLE_SKITY\n  placeholder_fetch_id_ =\n      page_view_->GetImageResourceFetcher()->FetchImageAsync(\n          placeholder_,\n          [self = weak_factory_.GetWeakPtr()](\n              std::unique_ptr<ImageResource> resource, bool hit_cache) {\n            if (!self) {\n              return;\n            }\n            self->placeholder_fetch_id_ = kDefaultImageFetchID;\n            if (!resource || self->placeholder_ != resource->GetUrl()) {\n              return;\n            }\n\n            if (!hit_cache) {\n              self->TriggerTransitionIfNeeded();\n            } else {\n              self->report_info_.image_origin =\n                  ReportInfo::ImageOrigin::kImageMemoryDecoded;\n            }\n\n            RenderImage* render_image = self->GetRenderImage();\n            render_image->SetPlaceholderImage(std::move(resource));\n          },\n          page_view_->UseTextureBackend(),\n          page_view_->DeferredImageDecode() && !defer_src_invalidation_,\n          page_view_->ImageDecodeWithPriority(), should_redirect_url_,\n          GetRenderImage() && GetRenderImage()->EnableLowQuality());\n#else\n  source_fetch_id_ = page_view_->GetImageResourceFetcher()->FetchImage(\n      placeholder_, IsSVG(),\n      [self = weak_factory_.GetWeakPtr()](\n          std::unique_ptr<BaseImageInstance> image_instance, bool hit_cache) {\n        if (!self) {\n          return;\n        }\n        if (!image_instance) {\n          return;\n        }\n\n        if (!hit_cache) {\n          self->TriggerTransitionIfNeeded();\n        } else {\n          self->report_info_.image_origin =\n              ReportInfo::ImageOrigin::kImageMemoryDecoded;\n        }\n\n        image_instance->SetAnimationFrameCallback([self]() {\n          if (!self) {\n            return;\n          }\n          self->GetRenderImage()->MarkNeedsPaint();\n        });\n        auto render_image = self->GetRenderImage();\n        render_image->SetPlaceholderImage(std::move(image_instance));\n      });\n#endif  // ENABLE_SKITY\n}\n\nvoid BaseImageView::FetchSource() {\n  if (source_.empty()) {\n    return;\n  }\n  report_info_.download_start_time =\n      lynx::base::CurrentSystemTimeMilliseconds();\n#ifndef ENABLE_SKITY\n  source_fetch_id_ = page_view_->GetImageResourceFetcher()->FetchImageAsync(\n      source_,\n      [self = weak_factory_.GetWeakPtr()](\n          std::unique_ptr<ImageResource> resource, bool hit_cache) {\n        if (!self) {\n          return;\n        }\n        self->source_fetch_id_ = kDefaultImageFetchID;\n        if (!resource) {\n          std::string error_msg = \"{\\\"error\\\":\\\"failed to fetch resource\\\"\";\n#if ENABLE_NET_LOADER && (OS_WIN || OS_MAC || OS_LINUX)\n          std::string lookup_url = url::TrimUrl(self->GetSource());\n          if (self->should_redirect_url_ && self->page_view()) {\n            auto intercept = self->page_view()->GetResourceLoaderIntercept();\n            if (intercept) {\n              lookup_url =\n                  intercept->ShouldInterceptUrl(self->GetSource(), false);\n            }\n          }\n\n          std::string response =\n              NetLoaderManager::Instance().TakeLastResponse(lookup_url);\n          if (!response.empty()) {\n            error_msg += \",\\\"response\\\":\";\n            error_msg += response;\n          }\n#endif\n          error_msg += \"}\";\n          self->NotifyLoadError(error_msg);\n          return;\n        }\n\n        if (!ImageResourceFetcher::SameImage(self->GetSource(),\n                                             resource->GetUrl())) {\n          return;\n        }\n\n        // Give real image size rather than the paint size.\n        self->NotifyLoadSuccess(resource->GetWidth(), resource->GetHeight());\n\n        if (self->prevent_loading_on_list_scroll_) {\n#ifndef ENABLE_CLAY_LITE\n          auto parent_list = self->Parent();\n          while (parent_list && !parent_list->Is<BaseListView>()) {\n            parent_list = parent_list->Parent();\n          }\n          if (parent_list) {\n            static_cast<BaseListView*>(parent_list)\n                ->PostLowPriorityTask(fml::MakeCopyable(\n                    [self, hit_cache,\n                     resource = std::move(resource)]() mutable {\n                      if (!self) {\n                        return;\n                      }\n\n                      if (!hit_cache) {\n                        self->TriggerTransitionIfNeeded();\n                      }\n\n                      RenderImage* render_image = self->GetRenderImage();\n                      render_image->SetImage(std::move(resource));\n                    }));\n            return;\n          }\n#endif\n        }\n\n        if (!hit_cache) {\n          self->TriggerTransitionIfNeeded();\n        }\n\n        RenderImage* render_image = self->GetRenderImage();\n        render_image->SetImage(std::move(resource));\n      },\n      page_view_->UseTextureBackend(),\n      page_view_->DeferredImageDecode() && !defer_src_invalidation_,\n      page_view_->ImageDecodeWithPriority(), should_redirect_url_,\n      GetRenderImage() && GetRenderImage()->EnableLowQuality(), false, IsSVG());\n#else\n  source_fetch_id_ = page_view_->GetImageResourceFetcher()->FetchImage(\n      source_, IsSVG(),\n      [self = weak_factory_.GetWeakPtr()](\n          std::unique_ptr<BaseImageInstance> image_instance, bool hit_cache) {\n        if (!self) {\n          return;\n        }\n        if (!image_instance) {\n          FML_LOG(ERROR) << \"image is null\";\n          self->NotifyLoadError(\"resource fetch fail\");\n          return;\n        }\n        self->NotifyLoadSuccess(image_instance->GetWidth(),\n                                image_instance->GetHeight());\n\n        image_instance->SetAnimationFrameCallback([self]() {\n          if (!self) {\n            return;\n          }\n          self->GetRenderImage()->MarkNeedsPaint();\n        });\n        if (!hit_cache) {\n          self->TriggerTransitionIfNeeded();\n        }\n        auto render_image = self->GetRenderImage();\n        render_image->SetImage(std::move(image_instance));\n        self->ReportImageLoadInfo();\n      });\n#endif  // ENABLE_SKITY\n}\n\nvoid BaseImageView::TryCancelFetch(const std::string& url,\n                                   ImageFetchID& fetch_id) {\n  if (url.empty() || fetch_id == kDefaultImageFetchID) {\n    return;\n  }\n\n  page_view_->GetImageResourceFetcher()->TryCancelAsyncFetch(url, fetch_id);\n  fetch_id = kDefaultImageFetchID;\n}\n\nvoid BaseImageView::OnAnimationUpdate(ValueAnimator& animation) {\n  float opacity = 1.0f * animation.GetAnimatedFraction();\n  GetRenderImage()->SetImageOpacity(opacity);\n}\n\nvoid BaseImageView::TriggerTransitionIfNeeded() {\n  if (transition_animator_->IsRunning()) {\n    transition_animator_->End();\n    return;\n  }\n\n  auto render_image = GetRenderImage();\n  if (!render_image->GetImage() && !render_image->GetPlaceholderImage() &&\n      transition_style_ == ImageTransitionStyle::kFadeIn) {\n    transition_animator_->Start();\n  }\n}\n\nvoid BaseImageView::TryEndTransition() {\n  if (transition_animator_->IsRunning()) {\n    transition_animator_->End();\n  }\n}\n\nvoid BaseImageView::DidUpdateAttributes() {\n  BaseView::DidUpdateAttributes();\n  if (fetch_delay_source_) {\n    fetch_delay_source_ = false;\n    SetSource(std::move(incoming_source_));\n  }\n  if (fetch_delay_placeholder_) {\n    fetch_delay_placeholder_ = false;\n    SetPlaceholder(std::move(incoming_placeholder_));\n  }\n}\n\n// If decoding has not occurred but graphics image is prepared, it will also\n// calls this callback\nvoid BaseImageView::OnDecodeFinished(bool success, const std::string& url) {\n  if (!success) {\n    NotifyLoadError(GetSource());\n  } else {\n    NotifyDecodedSuccess();\n    // Only report load info if this image is not placeholder.\n    if (url == source_) {\n      ReportImageLoadInfo();\n    }\n  }\n}\n\nvoid BaseImageView::RegisterUploadTask(OneShotCallback<>&& task, int image_id) {\n  page_view()->RegisterUploadTask(std::move(task), image_id);\n}\n\nvoid BaseImageView::OnStartPlay() { NotifyStartPlay(); }\nvoid BaseImageView::OnCurrentLoopComplete() { NotifyCurrentLoopComplete(); }\nvoid BaseImageView::OnFinalLoopComplete() { NotifyFinalLoopComplete(); }\nvoid BaseImageView::AdjustSizeIfNeeded(bool auto_size, float bitmap_width,\n                                       float bitmap_height) {\n  auto* shadow_node = page_view()->GetShadowNodeById(id_);\n  if (shadow_node && shadow_node->IsImageShadowNode()) {\n    static_cast<ImageShadowNode*>(shadow_node)\n        ->AdjustSizeIfNeeded(auto_size, bitmap_width, bitmap_height);\n  }\n}\n\nvoid BaseImageView::TryDecodeImmediately() {\n  GetRenderImage()->SetNeedDecodeImmediately(true);\n  GetRenderImage()->TryDecodeImmediately();\n}\n\n#ifndef NDEBUG\nstd::string BaseImageView::ToString() const {\n  std::stringstream ss;\n  ss << BaseView::ToString();\n  ss << \" source_=(\" << source_ << \")\";\n  if (!placeholder_.empty()) {\n    ss << \" placeholder_=(\" << placeholder_ << \")\";\n  }\n  if (!incoming_source_.empty()) {\n    ss << \" incoming_source_=(\" << incoming_source_ << \")\";\n  }\n  if (!incoming_placeholder_.empty()) {\n    ss << \" incoming_placeholder_=(\" << incoming_placeholder_ << \")\";\n  }\n  return ss.str();\n}\n#endif\n\nvoid BaseImageView::ReportImageLoadInfo() {\n  if (!HasEvent(event_attr::kEventImageLoadSuccess) ||\n      !report_info_.enable_report_info) {\n    return;\n  }\n\n  // load finish time includes decode cost.\n  uint64_t load_finish = lynx::base::CurrentSystemTimeMilliseconds();\n  uint64_t cost = load_finish - report_info_.download_start_time;\n  int width = GetRenderImage()->GetImage()->GetWidth();\n  int height = GetRenderImage()->GetImage()->GetHeight();\n  size_t memory_cost =\n      GetRenderImage()->GetImage()->GetGraphicsImageAllocSize();\n  bool downsampled = GetRenderImage()->DownSampling();\n  std::string url = source_;\n  float view_width = Width();\n  float view_height = Height();\n\n  page_view()->SendEvent(\n      id(), event_attr::kEventImageLoadSuccess,\n      {\"load_start\", \"load_finish\", \"cost\", \"src\", \"width\", \"height\",\n       \"memory_cost\", \"downsampled\", \"view_width\", \"view_height\", \"origin\"},\n      std::to_string(report_info_.download_start_time),\n      std::to_string(load_finish), std::to_string(cost), url, width, height,\n      std::to_string(memory_cost), downsampled, view_width, view_height,\n      static_cast<int>(report_info_.image_origin));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/base_image_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_BASE_IMAGE_VIEW_H_\n#define CLAY_UI_COMPONENT_BASE_IMAGE_VIEW_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/rendering/render_image.h\"\n#include \"clay/ui/rendering/render_object.h\"\n#include \"clay/ui/resource/image_resource_fetcher.h\"\n\nnamespace clay {\n\nenum class ImageTransitionStyle {\n  kNone,\n  kFadeIn,\n};\n\nclass BaseImageView : public WithTypeInfo<BaseImageView, BaseView>,\n                      AnimatorUpdateListener,\n                      RenderImageClient {\n public:\n  class Listener {\n   public:\n    virtual void OnImageLoadSuccess(BaseImageView* image, int width,\n                                    int height) = 0;\n    virtual void OnImageDecodedSuccess(BaseImageView* image) = 0;\n    virtual void OnImageLoadError(BaseImageView* image,\n                                  const std::string& error_msg) = 0;\n  };\n  BaseImageView(std::unique_ptr<RenderObject> render_object,\n                PageView* page_view);\n  BaseImageView(uint32_t id, std::string tag,\n                std::unique_ptr<RenderObject> render_object,\n                PageView* page_view);\n  ~BaseImageView() override;\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  void OnNodeReady() override;\n\n  void SetLocalCache(bool use_local_cache);\n  void SetSkipRedirection(bool skip_redirection);\n  void SetSource(std::string original_url);\n  void SetSource(const std::string&& url, const uint8_t* source, const int len);\n  void SetRepeat(ImageRepeat repeat);\n  void SetPlaceholder(std::string original_url);\n  void SetMode(const std::string& fill_mode);\n  void SetMode(FillMode fill_mode);\n  void SetEffect(ImageEffect effect);\n  void SetCapInsets(const std::string& cap_insets);\n  void SetCapInsetsScale(float scale);\n  void SetImageTransitionStyle(const std::string& style);\n  void SetImageLoadListener(Listener* listener);\n  void SetAutoPlay(bool auto_play);\n  void SetLoopCount(int loop_count);\n  void SetAutoSize(bool auto_size);\n\n  std::string GetSource() const { return source_; }\n  virtual bool IsSVG() const { return false; }\n\n  void DidUpdateAttributes() override;\n\n  void startAnimate();\n  void stopAnimation();\n  void pauseAnimation();\n  void resumeAnimation();\n\n  void OnDecodeFinished(bool success, const std::string& url) override;\n  void RegisterUploadTask(OneShotCallback<>&& task, int image_id) override;\n  void OnStartPlay() override;\n  void OnCurrentLoopComplete() override;\n  void OnFinalLoopComplete() override;\n  void AdjustSizeIfNeeded(bool auto_size, float bitmap_width,\n                          float bitmap_height) override;\n\n  void TryDecodeImmediately();\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n protected:\n  RenderImage* GetRenderImage();\n  void NotifyLoadSuccess(int width, int height);\n\n private:\n  struct ReportInfo {\n    enum ImageOrigin {\n      kImageUnknown = 1,\n      kImageNetwork,\n      kImageDisk,\n      kImageMemoryEncoded,\n      kImageMemoryDecoded,\n      kImageLocal,\n      kImageMax,\n    };\n\n    bool enable_report_info = false;\n\n    uint64_t download_start_time = 0;\n    ImageOrigin image_origin = ImageOrigin::kImageUnknown;\n  };\n\n  void NotifyLoadError(const std::string& error_msg);\n  void NotifyDecodedSuccess();\n  void NotifyStartPlay();\n  void NotifyCurrentLoopComplete();\n  void NotifyFinalLoopComplete();\n  void FetchPlaceholder();\n  void FetchSource();\n  void TryCancelFetch(const std::string&, ImageFetchID&);\n\n  void OnAnimationUpdate(ValueAnimator& animation) override;\n\n  void TriggerTransitionIfNeeded();\n  void TryEndTransition();\n\n  void ReportImageLoadInfo();\n\n  bool should_redirect_url_ = true;\n  bool prevent_loading_on_list_scroll_ = false;\n\n  std::string source_;\n  ImageFetchID source_fetch_id_ = kDefaultImageFetchID;\n  std::string placeholder_;\n  ImageFetchID placeholder_fetch_id_ = kDefaultImageFetchID;\n  fml::WeakPtrFactory<BaseImageView> weak_factory_;\n\n  ImageTransitionStyle transition_style_ = ImageTransitionStyle::kNone;\n  std::unique_ptr<ValueAnimator> transition_animator_;\n  std::string incoming_source_ = \"\";\n  bool fetch_delay_source_ = false;\n  std::string incoming_placeholder_ = \"\";\n  bool fetch_delay_placeholder_ = false;\n  bool defer_src_invalidation_ = false;\n  Listener* listener_ = nullptr;\n  ReportInfo report_info_;\n};\n\ninline RenderImage* BaseImageView::GetRenderImage() {\n  return static_cast<RenderImage*>(render_object_.get());\n}\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_BASE_IMAGE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/base_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/base_view.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <cstddef>\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <utility>\n\n#include \"clay/fml/base64.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/animation_properties_util.h\"\n#include \"clay/gfx/animation/keyframes_manager.h\"\n#include \"clay/gfx/animation/transition_manager.h\"\n#include \"clay/gfx/geometry/filter_operations.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n#include \"clay/gfx/geometry/path.h\"\n#include \"clay/gfx/geometry/transform.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/borders_data.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/background_data.h\"\n#include \"clay/ui/component/base_view_animation_mutator.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/editable/editable_view.h\"\n#include \"clay/ui/component/editable/textarea_ng_view.h\"\n#include \"clay/ui/component/editable/textarea_view.h\"\n#include \"clay/ui/component/expose_manager/expose_observer.h\"\n#include \"clay/ui/component/inline_image_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/text/base_text_view.h\"\n#include \"clay/ui/component/text/raw_text_view.h\"\n#include \"clay/ui/component/text/unicode_util.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/event/event_utils.h\"\n#include \"clay/ui/gesture/mouse_region_manager.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n#include \"clay/ui/painter/gradient.h\"\n#include \"clay/ui/resource/image_resource_fetcher.h\"\n\nnamespace clay {\nnamespace {\nnamespace utils = attribute_utils;\nLYNX_UI_METHOD_BEGIN(BaseView) {\n  LYNX_UI_METHOD(BaseView, setFocus);\n  LYNX_UI_METHOD(BaseView, interceptBackKeyOnce);\n  LYNX_UI_METHOD(BaseView, cancelInterceptBackKey);\n  LYNX_UI_METHOD(BaseView, boundingClientRect);\n  LYNX_UI_METHOD(BaseView, scrollIntoView);\n  LYNX_UI_METHOD(BaseView, takeScreenshot);\n}\nLYNX_UI_METHOD_END(BaseView);\n\nconstexpr int64_t FORCE_CACHE_ANIMATION_DURATION = 500;\n\nbool ShouldPassEventToNativeInherited(BaseView* view) {\n  if (view == nullptr) {\n    return false;\n  } else if (view->CanEventThrough().has_value()) {\n    return *view->CanEventThrough();\n  } else if (view->Parent() == nullptr) {\n    return false;\n  } else {\n    return ShouldPassEventToNativeInherited(view->Parent());\n  }\n}\n\nconst int PLATFORM_PERSPECTIVE_UNIT_NUMBER = 0;\nconst int PLATFORM_PERSPECTIVE_UNIT_VW = 1;\nconst int PLATFORM_PERSPECTIVE_UNIT_VH = 2;\nconst int PLATFORM_PERSPECTIVE_UNIT_DEFAULT = 3;\nconst int PLATFORM_PERSPECTIVE_UNIT_PX = 4;\nconst int DEFAULT_PERSPECTIVE_FACTOR = 100;\n// INFO: Lynx sets CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER to sqrt(5)\nconst float CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER = 1;\n\n}  // namespace\n\nconst std::unordered_set<KeywordID> kExposureAttributes = {\n    KeywordID::kExposureScene,\n    KeywordID::kExposureId,\n    KeywordID::kExposureArea,\n    KeywordID::kEnableExposureUiMargin,\n    KeywordID::kEnableExposureUiClip,\n    KeywordID::kExposureUiMarginLeft,\n    KeywordID::kExposureUiMarginRight,\n    KeywordID::kExposureUiMarginTop,\n    KeywordID::kExposureUiMarginBottom,\n    KeywordID::kExposureScreenMarginLeft,\n    KeywordID::kExposureScreenMarginRight,\n    KeywordID::kExposureScreenMarginTop,\n    KeywordID::kExposureScreenMarginBottom,\n    KeywordID::kUiappear,\n    KeywordID::kUidisappear,\n};\n\nBaseView::BaseView(std::unique_ptr<RenderObject> render_object,\n                   PageView* page_view)\n    : BaseView(-1, \"\", std::move(render_object), page_view) {}\n\nBaseView::BaseView(int id, std::string tag_name,\n                   std::unique_ptr<RenderObject> render_object,\n                   PageView* page_view)\n    : id_(id),\n      tag_(tag_name),\n      page_view_(page_view),\n      render_object_(std::move(render_object)),\n      weak_factory_(this),\n      overflow_(RenderObject::DefaultOverflowValue()) {\n  render_object_->SetID(id_);\n#if defined(ENABLE_MOUSE_TRACKING)\n  if (page_view != this) {\n    auto* mouse_region_manager = page_view->mouse_region_manager();\n    if (mouse_region_manager) {\n      mouse_region_manager->RegisterEnterCallback(\n          this,\n          std::bind(&BaseView::OnMouseEnter, this, std::placeholders::_1));\n      mouse_region_manager->RegisterLeaveCallback(\n          this,\n          std::bind(&BaseView::OnMouseLeave, this, std::placeholders::_1));\n      mouse_region_manager->RegisterHoverCallback(\n          this,\n          std::bind(&BaseView::OnMouseHover, this, std::placeholders::_1));\n    }\n    // set default cursor for every base view\n    SetCursor({\"default\"});\n  }\n#endif\n  if (page_view_) {\n    render_object_->SetImageDecodeWithPriority(\n        page_view_->ImageDecodeWithPriority());\n  }\n}\n\nBaseView::~BaseView() {\n  if (page_view() != this) {\n    auto mouse_region_manager = page_view()->mouse_region_manager();\n    if (mouse_region_manager) {\n      mouse_region_manager->UnregisterCallback(this);\n    }\n\n    // needs layout is not equal to added to dirty\n    if (needs_layout_ || IsLayoutRootCandidate()) {\n      if (auto layout_controller = page_view_->GetLayoutController()) {\n        bool has_this_node =\n            layout_controller->RemoveDirtyNode(this->GetWeakPtr());\n        if (has_this_node) {\n          FML_LOG(ERROR) << \"remove a dirty node in destruct, tag:\" << tag_\n                         << \", id :\" << id_selector_;\n        }\n      }\n    }\n\n    if (has_intersection_observer_) {\n      page_view_->intersection_observer_manager()->RemoveAll(this);\n      has_intersection_observer_ = false;\n    }\n#ifdef ENABLE_ACCESSIBILITY\n    if (auto* semantics_owner = GetSemanticsOwner()) {\n      semantics_owner->RemoveDirtySemanticsForDescendants(this);\n    }\n#endif\n  }\n  if (destruct_listener_) {\n    destruct_listener_(this);\n  }\n  // Destruct animation manually, this would trigger SetProperty at end of\n  // animators.\n  keyframes_mgr_.reset();\n  transition_mgr_.reset();\n  // KeyframesMgr & TransitionMgr refs this, so we need to release it after.\n  animation_mutator_.reset();\n}\n\nvoid BaseView::Destroy() {\n  OnDestroy();\n\n  if (attach_to_tree_) {\n    OnDetachFromTree();\n  }\n  FML_DCHECK(render_object());\n  render_object()->Destroy();\n  SetFocusable(false);\n  // incase of double release\n  data_set_ = clay::Value::Null();\n}\n\nBaseViewAnimationMutator* BaseView::GetAnimationMutator() {\n  if (!animation_mutator_) {\n    animation_mutator_ = std::make_unique<BaseViewAnimationMutator>(this);\n  }\n  return animation_mutator_.get();\n}\n\nbool BaseView::IsAppRegionDraggable() {\n  if (app_region_.compare(attr_value::kAppRegionDrag) == 0) {\n    return true;\n  } else if (app_region_.compare(attr_value::kAppRegionNoDrag) == 0) {\n    return false;\n  } else if (app_region_.empty() && Parent()) {\n    return Parent()->IsAppRegionDraggable();\n  }\n  return false;\n}\n\nvoid BaseView::AddChild(BaseView* child) {\n  AddChild(child, child_count());\n\n#ifdef ENABLE_ACCESSIBILITY\n  MarkRebuildSemanticsTree();\n#endif\n}\n\nvoid BaseView::AddChild(BaseView* child, int index) {\n  if (static_cast<size_t>(index) > child_count() || index < 0) {\n    FML_DCHECK(false) << \"AddChild failure: Index out of bounds.\";\n    return;\n  }\n  BaseView* parent = child->parent_;\n  if (parent) {\n    FML_DCHECK(parent != this);\n    parent->RemoveChild(child);\n  }\n\n  child->parent_ = this;\n\n  FML_DCHECK(render_object());\n  RenderObject* before_child = nullptr;\n  if (static_cast<size_t>(index) < child_count()) {\n    before_child = children_[index]->render_object();\n  }\n\n  render_object()->AddChild(child->render_object(), before_child);\n  children_.insert(children_.begin() + index, child);\n\n  DirtyChildrenPaintingOrder();\n\n  // If the current node is in a layout tree, its descendants must be in a\n  // layout tree.\n  if (InLayoutTree()) {\n    child->SetInLayoutTree(true);\n  }\n\n  MarkNeedsLayout();\n\n  if (attach_to_tree_) {\n    child->OnAttachToTree();\n  }\n}\n\nvoid BaseView::RemoveChild(BaseView* child) {\n  std::vector<BaseView*>::iterator iter(\n      std::find(children_.begin(), children_.end(), child));\n  if (iter == children_.end()) {\n    return;\n  }\n\n  if (attach_to_tree_ && !child->remove_temporarily_) {\n    child->OnDetachFromTree();\n  }\n\n  // The child is removed from the layout tree, try to set its flag to false.\n  // NOTE: if the child is a layout root candidate, it is still in layout tree.\n  // See `SetInLayoutTree()`.\n  if (InLayoutTree()) {\n    child->SetInLayoutTree(false);\n  }\n\n  MarkNeedsLayout();\n\n#ifdef ENABLE_ACCESSIBILITY\n  MarkRebuildSemanticsTree();\n#endif\n\n  FML_DCHECK(render_object());\n  render_object()->RemoveChild(child->render_object());\n  child->parent_ = nullptr;\n  children_.erase(iter);\n\n  DirtyChildrenPaintingOrder();\n}\n\nvoid BaseView::RemoveChildTemporarily(BaseView* child) {\n  child->remove_temporarily_ = true;\n  RemoveChild(child);\n  child->remove_temporarily_ = false;\n}\n\nvoid BaseView::DestroyAllChildren() {\n  FML_DCHECK(render_object());\n  for (const auto& child : children_) {\n    if (attach_to_tree_) {\n      child->OnDetachFromTree();\n    }\n    if (InLayoutTree()) {\n      child->SetInLayoutTree(false);\n    }\n  }\n  DestroyChildrenRecursively(this);\n  children_.clear();\n\n  DirtyChildrenPaintingOrder();\n}\n\nvoid BaseView::BringChildToFront(BaseView* child) {\n  if (!children_.empty() && children_.back() == child) {\n    return;\n  }\n\n  std::vector<BaseView*>::iterator iter(\n      std::find(children_.begin(), children_.end(), child));\n  if (iter == children_.end()) {\n    return;\n  }\n\n  FML_DCHECK(render_object());\n  render_object()->BringChildToFront(child->render_object());\n  children_.erase(iter);\n  children_.push_back(child);\n\n  DirtyChildrenPaintingOrder();\n}\n\nScrollable* BaseView::FindAncestorScrollableView(BaseView* child) {\n  auto parent = child->Parent();\n  while (parent) {\n    if (parent->Is<Scrollable>()) {\n      return static_cast<Scrollable*>(parent);\n    }\n    parent = parent->Parent();\n  }\n  return nullptr;\n}\n\nvoid BaseView::AddGestureRecognizer(\n    std::unique_ptr<GestureRecognizer> recognizer) {\n  gesture_recognizers_.push_back(std::move(recognizer));\n}\n\nvoid BaseView::RemoveGestureRecognizer(GestureRecognizer* recognizer) {\n  if (!recognizer) {\n    return;\n  }\n  for (auto it = gesture_recognizers_.begin(); it != gesture_recognizers_.end();\n       it++) {\n    if (it->get() == recognizer) {\n      gesture_recognizers_.erase(it);\n      return;\n    }\n  }\n}\n\nvoid BaseView::ClearGestureRecognizers() { gesture_recognizers_.clear(); }\n\nint BaseView::GetChildIndex(BaseView* child) {\n  auto it = std::find(children_.begin(), children_.end(), child);\n  if (it != children_.end()) {\n    return static_cast<int>(std::distance(children_.begin(), it));\n  } else {\n    return -1;\n  }\n}\n\nfloat BaseView::GetTranslateZ() const {\n  return render_object()->GetTranslateZ();\n}\n\nvoid BaseView::SetPaintingOrder(int value) {\n  int old_painting_order = render_object()->GetPaintingOrder();\n  render_object()->SetPaintingOrder(value);\n  if (Parent() && old_painting_order != value) {\n    Parent()->DirtyChildrenPaintingOrder();\n  }\n}\n\nint BaseView::GetPaintingOrder() const {\n  return render_object()->GetPaintingOrder();\n}\n\nbool BaseView::IsDescendant(BaseView* a_view) const {\n  BaseView* to_check = a_view;\n  while (to_check != nullptr) {\n    if (to_check == this) {\n      return true;\n    }\n    to_check = to_check->Parent();\n  }\n  return false;\n}\n\nvoid BaseView::SetRepaintBoundary(bool repaint_boundary) {\n  render_object()->SetRepaintBoundary(repaint_boundary);\n}\n\nvoid BaseView::SetX(float x) {\n  if (left_ == x) {\n    return;\n  }\n  TransitionTo(ClayAnimationPropertyType::kLeft, x);\n}\n\nvoid BaseView::SetY(float y) {\n  if (top_ == y) {\n    return;\n  }\n  TransitionTo(ClayAnimationPropertyType::kTop, y);\n}\n\nvoid BaseView::SetWidth(float width) {\n  if (width_ == width) {\n    return;\n  }\n  TransitionTo(ClayAnimationPropertyType::kWidth, width);\n}\n\nvoid BaseView::SetHeight(float height) {\n  if (height_ == height) {\n    return;\n  }\n  TransitionTo(ClayAnimationPropertyType::kHeight, height);\n}\n\nvoid BaseView::SetBound(float left, float top, float width, float height) {\n  FloatRect old_bounds = GetBounds();\n  // TODO: Maybe we can use Class 'AutoReset' to save the original value of the\n  // variable and set a new value. When the life cycle ends, restore the\n  // original value of the variable.\n  // https://source.chromium.org/chromium/chromium/src/+/main:base/auto_reset.h;l=25;bpv=1;bpt=1?q=AutoReset&ss=chromium%2Fchromium%2Fsrc\n  ignore_size_change_checks_ = true;\n  SetX(left);\n  SetY(top);\n  SetWidth(width);\n  SetHeight(height);\n  ignore_size_change_checks_ = false;\n  NotifyBoundChangeIfNeeded(old_bounds);\n\n  OnLayoutChange();\n  // Force a request to repaint a frame.\n  Invalidate();\n#ifdef ENABLE_ACCESSIBILITY\n  // TODO: Check if this logic can be moved to `NotifyBoundChangeIfNeeded `.\n  FloatRect new_bounds = GetBounds();\n  if (old_bounds != new_bounds) {\n    // It's ok if this view is not acessible, because `FlushSemantics` will\n    // check again.\n    if (auto* semantics_owner = GetSemanticsOwner()) {\n      semantics_owner->AddDirtySemanticsForDescendants(this);\n    }\n  }\n#endif\n}\n\nvoid BaseView::SetOpacity(float opacity) {\n  float old_opacity;\n  GetProperty(ClayAnimationPropertyType::kOpacity, old_opacity);\n\n  if (old_opacity == opacity) {\n    return;\n  }\n  TransitionTo(ClayAnimationPropertyType::kOpacity, opacity);\n}\n\nfloat BaseView::Opacity() {\n  float res;\n  GetProperty(ClayAnimationPropertyType::kOpacity, res);\n  return res;\n}\n\nvoid BaseView::SetOverflowWithMask(uint8_t mask, int overflow) {\n  int new_val = overflow_;\n  if (overflow == static_cast<int>(ClayOverflowType::kVisible)) {\n    new_val |= mask;\n  } else {\n    new_val &= ~mask;\n  }\n  SetOverflow(new_val);\n}\n\nvoid BaseView::SetOverflow(int overflow) {\n  if (overflow_ == overflow) {\n    return;\n  }\n  overflow_ = overflow;\n  render_object()->SetOverflow(overflow_);\n}\n\nvoid BaseView::SetBorder(const BordersData& data) {\n  render_object()->SetBorders(data);\n  OnBorderChanged(data);\n}\n\nvoid BaseView::OnBorderChanged(const BordersData& data) {\n  // FIXME: The logic of checking content size has been broken.\n  FloatRect old_rect(PaddingLeft() + BorderLeft(), PaddingTop() + BorderTop(),\n                     Width() - PaddingRight() - BorderRight(),\n                     Height() - PaddingBottom() - BorderBottom());\n  render_object()->MarkNeedsPaint();\n  FloatRect new_rect(PaddingLeft() + BorderLeft(), PaddingTop() + BorderTop(),\n                     Width() - PaddingRight() - BorderRight(),\n                     Height() - PaddingBottom() - BorderBottom());\n  if (new_rect.width() != old_rect.width() ||\n      new_rect.height() != old_rect.height()) {\n    OnContentSizeChanged(old_rect, new_rect);\n  }\n}\n\nvoid BaseView::AppendShadow(const Shadow& shadow) {\n  render_object()->SetShadow(shadow);\n}\n\nvoid BaseView::SetShadows(std::vector<Shadow>&& shadows) {\n  render_object()->SetShadows(std::move(shadows));\n}\n\nvoid BaseView::SetBorderStyle(std::vector<Side> sides,\n                              std::vector<BorderStyleType> styles) {\n  FML_DCHECK(sides.size() == styles.size());\n  auto& border = render_object()->MutableBorder();\n  for (size_t i = 0; i < sides.size(); i++) {\n    Side side = sides[i];\n    BorderStyleType style = styles[i];\n    switch (side) {\n      case Side::kTop:\n        border.style_top_ = style;\n        break;\n      case Side::kRight:\n        border.style_right_ = style;\n        break;\n      case Side::kBottom:\n        border.style_bottom_ = style;\n        break;\n      case Side::kLeft:\n        border.style_left_ = style;\n        break;\n      case Side::kAll:\n        border.style_top_ = style;\n        border.style_right_ = style;\n        border.style_bottom_ = style;\n        border.style_left_ = style;\n        break;\n      default:\n        break;\n    }\n  }\n  OnBorderChanged(border);\n}\n\nvoid BaseView::SetBorderWidth(std::vector<Side> sides,\n                              std::vector<float> widths) {\n  FML_DCHECK(sides.size() == widths.size());\n  auto& border = render_object()->MutableBorder();\n  for (size_t i = 0; i < sides.size(); i++) {\n    Side side = sides[i];\n    float width = widths[i];\n    switch (side) {\n      case Side::kTop:\n        border.width_top_ = width;\n        break;\n      case Side::kRight:\n        border.width_right_ = width;\n        break;\n      case Side::kBottom:\n        border.width_bottom_ = width;\n        break;\n      case Side::kLeft:\n        border.width_left_ = width;\n        break;\n      case Side::kAll:\n        border.width_top_ = width;\n        border.width_right_ = width;\n        border.width_bottom_ = width;\n        border.width_left_ = width;\n        break;\n      default:\n        break;\n    }\n  }\n  OnBorderChanged(border);\n  OnLayoutChange();\n}\n\nvoid BaseView::SetBorderColor(std::vector<Side> sides,\n                              std::vector<uint32_t> colors) {\n  FML_DCHECK(sides.size() == colors.size());\n  auto& border = render_object()->MutableBorder();\n  for (size_t i = 0; i < sides.size(); i++) {\n    Side side = sides[i];\n    uint32_t color = colors[i];\n    switch (side) {\n      case Side::kTop:\n        border.color_top_ = color;\n        break;\n      case Side::kRight:\n        border.color_right_ = color;\n        break;\n      case Side::kBottom:\n        border.color_bottom_ = color;\n        break;\n      case Side::kLeft:\n        border.color_left_ = color;\n        break;\n      case Side::kAll:\n        border.color_top_ = color;\n        border.color_right_ = color;\n        border.color_bottom_ = color;\n        border.color_left_ = color;\n        break;\n      default:\n        break;\n    }\n  }\n  OnBorderChanged(border);\n}\n\nvoid BaseView::SetBorderRadius(const FloatSize& left_top,\n                               const FloatSize& right_top,\n                               const FloatSize& right_bottom,\n                               const FloatSize& left_bottom) {\n  auto& border = render_object()->MutableBorder();\n  border.radius_x_top_left_length_.SetValue(left_top.width());\n  border.radius_y_top_left_length_.SetValue(left_top.height());\n  border.radius_x_top_right_length_.SetValue(right_top.width());\n  border.radius_y_top_right_length_.SetValue(right_top.height());\n  border.radius_x_bottom_right_length_.SetValue(right_bottom.width());\n  border.radius_y_bottom_right_length_.SetValue(right_bottom.height());\n  border.radius_x_bottom_left_length_.SetValue(left_bottom.width());\n  border.radius_y_bottom_left_length_.SetValue(left_bottom.height());\n  border.UpdateRadius(width_, height_);\n  OnBorderChanged(border);\n  OnLayoutChange();\n}\n\nvoid BaseView::SetBorderRadius(size_t index, const std::vector<Length>& array) {\n  auto& border = render_object()->MutableBorder();\n  if (array.size() == 0 && index == 4) {  // reset all\n    for (size_t i = 0; i < index; i++) {\n      border.SetRadius(i, Length(), Length());\n    }\n\n  } else if (array.size() == 0 && index < 4) {  // reset single\n    border.SetRadius(index, Length(), Length());\n\n  } else if (array.size() == 8 && index == 4) {  // update all\n    for (size_t i = 0; i < index; i++) {\n      Length length_x = array.at(i * 2);\n      Length length_y = array.at(i * 2 + 1);\n      border.SetRadius(i, length_x, length_y);\n    }\n\n  } else if (array.size() == 2 && index < 4) {  // update single\n    Length length_x = array.at(0);\n    Length length_y = array.at(1);\n    border.SetRadius(index, length_x, length_y);\n\n  } else {\n    FML_DCHECK(false);\n    return;\n  }\n\n  border.UpdateRadius(width_, height_);\n  OnBorderChanged(border);\n}\n\nvoid BaseView::SetOutline(const OutlineData& outline) {\n  render_object()->SetOutline(outline);\n}\n\nvoid BaseView::SetOutlineStyle(const BorderStyleType style) {\n  auto& outline = render_object()->MutableOutline();\n  outline.style_ = style;\n  render_object()->SetOutline(outline);\n}\n\nvoid BaseView::SetOutlineWidth(int width) {\n  auto& outline = render_object()->MutableOutline();\n  outline.width_ = width;\n  render_object()->SetOutline(outline);\n}\n\nvoid BaseView::SetOutlineOffset(int offset) {\n  auto& outline = render_object()->MutableOutline();\n  outline.offset_ = offset;\n  render_object()->SetOutline(outline);\n}\n\nvoid BaseView::SetOutlineColor(unsigned int color) {\n  auto& outline = render_object()->MutableOutline();\n  outline.color_ = color;\n  render_object()->SetOutline(outline);\n}\n\nvoid BaseView::SetBackground(const BackgroundData& background) {\n  Color old_color;\n  GetProperty(ClayAnimationPropertyType::kBackgroundColor, old_color);\n\n  render_object()->SetBackgroundData(background);\n\n  if (old_color != background.background_color) {\n    // Restore old value and ensure that transition animation will be triggered.\n    render_object()->SetBackgroundColor(old_color);\n    SetBackgroundColor(background.background_color);\n  }\n\n  FML_DCHECK(page_view());\n\n  bg_image_loader_token_++;\n  for (size_t i = 0; i < background.background_images.size(); i++) {\n    const BackgroundImageData& image = background.background_images[i];\n\n    if (image.type == ClayBackgroundImageType::kUrl && !image.src_str.empty()) {\n      LoadBackgroundOrMaskImage(image.src_str, i);\n    } else if (image.type == ClayBackgroundImageType::kLinearGradient) {\n      std::optional<Gradient> gradient;\n      if (!image.gradient_str.empty()) {\n        // Create Gradient by |gradient_str|:\n        gradient = Gradient::Create(image.gradient_str);\n      } else {\n        // Create Gradient by |gradient_data|:\n        gradient = Gradient::CreateLinear(image.gradient_data);\n      }\n\n      if (gradient.has_value()) {\n        render_object()->SetBackgroundImage(i, *gradient);\n      }\n    }\n  }\n}\n\nvoid BaseView::SetBackgroundColor(const Color& color) {\n  if (OnBackgroundProperty(BaseView::BackgroundUpdate{\n          BaseView::BackgroundPropType::kColor, color})) {\n    return;\n  }\n  if (render_object()->HasBackground() &&\n      render_object()->Background().background_color == color) {\n    return;\n  }\n  TransitionTo(ClayAnimationPropertyType::kBackgroundColor, color);\n}\n\nvoid BaseView::SetCursor(const std::vector<std::string>& vec) {\n  cursor_ = std::make_unique<MouseCursor>(vec);\n  auto* mouse_region_manager = page_view_->mouse_region_manager();\n  if (mouse_region_manager) {\n    mouse_region_manager->AddCursorHolder(this);\n    // Force cursor update if mouse is currently hovering over this view\n    if (is_mouse_hover_) {\n      mouse_region_manager->ForceUpdateCursor();\n    }\n  }\n}\n\nMouseCursor* BaseView::GetMouseCursor() { return cursor_.get(); }\n\nvoid BaseView::SetCursor(const clay::Value::Array& array) {\n  // array like:\n  // 0(uint32_t)\n  // array(clay::Value::Array,size=3): path/to/image(string), x(int),y(int)\n  // 1(uint32_t)\n  // keyword(string)\n\n  cursor_ = std::make_unique<MouseCursor>();\n\n  bool is_parse_error = false;\n\n#define ADD_CHECK_OVERFLOW   \\\n  if (++i >= array.size()) { \\\n    is_parse_error = true;   \\\n    break;                   \\\n  }\n\n  for (size_t i = 0; i < array.size();) {\n    uint32_t int_type = utils::GetUint(array[i], -1);\n    ClayCursorType type = static_cast<ClayCursorType>(int_type);\n    switch (type) {\n      case ClayCursorType::kUrl: {\n        ADD_CHECK_OVERFLOW;\n        const auto& url_array = utils::GetArray(array[i]);\n\n        if (url_array.size() != 3) {\n          is_parse_error = true;\n          break;\n        }\n\n        // parse path\n        auto path = utils::GetCString(url_array[0], \"default\");\n        auto path_type = CursorTypeUtil::ParseCursorType(path);\n\n        // parse x\n        auto x = utils::GetInt(url_array[1], 0);\n\n        // parse y\n        auto y = utils::GetInt(url_array[2], 0);\n\n        cursor_->AddCursor({path_type, path, x, y});\n\n        ++i;\n        break;\n      }\n      case ClayCursorType::kKeyword: {\n        ADD_CHECK_OVERFLOW;\n        auto str = utils::GetCString(array[i], \"default\");\n        auto type = CursorTypeUtil::ParseCursorType(str);\n        cursor_->AddCursor({type, str});\n\n        ++i;\n        break;\n      }\n      default: {\n        is_parse_error = true;\n        break;\n      }\n    }\n\n    if (is_parse_error) {\n      FML_DLOG(ERROR)\n          << \"error cursor format! Use default cursor or first cursor\";\n      cursor_->AddCursor(Cursor(CursorTypes::kBasic, \"default\"));\n      break;\n    }\n  }\n#undef ADD_CHECK_OVERFLOW\n\n  // in case of `cursor:none`\n  if (array.size() == 0) {\n    cursor_->AddCursor(Cursor(CursorTypes::kNone, \"none\"));\n  }\n\n  auto* mouse_region_manager = page_view_->mouse_region_manager();\n  if (mouse_region_manager) {\n    mouse_region_manager->AddCursorHolder(this);\n    // Force cursor update if mouse is currently hovering over this view\n    if (is_mouse_hover_) {\n      mouse_region_manager->ForceUpdateCursor();\n    }\n  }\n}\n\n// Add Lamé curve in the target quadrant to the target path.\nstatic void AddLameCurveToPath(GrPath& path, float rx, float ry, float cx,\n                               float cy, float ex, float ey, int quadrant) {\n  double cosI, sinI, x, y;\n  float fx = (quadrant == 1 || quadrant == 4) ? 1 : -1;\n  float fy = (quadrant == 1 || quadrant == 2) ? 1 : -1;\n  for (float i = (M_PI / 2 * (quadrant - 1)); i < M_PI / 2 * quadrant;\n       i += 0.01) {\n    // abs for cos and sin\n    cosI = fx * cos(i);\n    sinI = fy * sin(i);\n    x = fx * rx * pow(cosI, 2 / ex) + cx;\n    y = fy * ry * pow(sinI, 2 / ey) + cy;\n    if (i == 0) {\n      PATH_MOVE_TO(path, x, y);\n    } else {\n      PATH_LINE_TO(path, x, y);\n    }\n  }\n}\n\nvoid BaseView::SetClipOffsetPath(const clay::Value::Array& array,\n                                 bool is_clip_path) {\n  auto shape_type = static_cast<ClayBasicShapeType>(utils::GetInt(array[0]));\n  auto& path_data = is_clip_path ? clip_path_data_ : offset_path_data_;\n  switch (shape_type) {\n    case ClayBasicShapeType::kCircle: {\n      ClayPlatformLength radius_length, x_pos_length, y_pos_length;\n      bool result = true;\n      result &= utils::TryGetPlatformLength(array, 1, radius_length);\n      result &= utils::TryGetPlatformLength(array, 3, x_pos_length);\n      result &= utils::TryGetPlatformLength(array, 5, y_pos_length);\n      if (!result) {\n        FML_DLOG(ERROR) << \"Setting clip-path: error Circle format!\";\n        return;\n      }\n      if (!path_data.has_value()) {\n        path_data = ClipPathData{};\n      }\n      path_data->params.emplace_back(radius_length);\n      path_data->params.emplace_back(radius_length);\n      path_data->params.emplace_back(x_pos_length);\n      path_data->params.emplace_back(y_pos_length);\n      path_data->clip_type = ClipPathData::ClipType::kCircle;\n      break;\n    }\n    case ClayBasicShapeType::kEllipse: {\n      ClayPlatformLength x_length, y_length, x_pos_length, y_pos_length;\n      bool result = true;\n      result &= utils::TryGetPlatformLength(array, 1, x_length);\n      result &= utils::TryGetPlatformLength(array, 3, y_length);\n      result &= utils::TryGetPlatformLength(array, 5, x_pos_length);\n      result &= utils::TryGetPlatformLength(array, 7, y_pos_length);\n      if (!result) {\n        FML_DLOG(ERROR) << \"Setting clip-path: error Ellipse format!\";\n        return;\n      }\n      if (!path_data.has_value()) {\n        path_data = ClipPathData{};\n      }\n      path_data->params.emplace_back(x_length);\n      path_data->params.emplace_back(y_length);\n      path_data->params.emplace_back(x_pos_length);\n      path_data->params.emplace_back(y_pos_length);\n      path_data->clip_type = ClipPathData::ClipType::kEllipse;\n      break;\n    }\n    case ClayBasicShapeType::kPath: {\n      std::string path_str = utils::GetCString(array[1]);\n      GrPath path;\n      if (!PathBuilder::ParsePathString(path_str.c_str(), &path)) {\n        FML_DLOG(ERROR) << \"Setting clip-path: error Path format!\";\n        return;\n      }\n      auto scaled_density =\n          page_view()->GetPixelRatio<kPixelTypeLogical, kPixelTypeClay>();\n      skity::Matrix density_scale =\n          skity::Matrix::Scale(scaled_density, scaled_density);\n      PATH_TRANSFORM(path, density_scale);\n      path_data.reset();\n      if (is_clip_path) {\n        render_object()->SetClipPath(path);\n      } else {\n        render_object()->SetOffsetPath(path);\n      }\n      break;\n    }\n    case ClayBasicShapeType::kSuperEllipse: {\n      ClayPlatformLength radius_x, radius_y, center_x, center_y;\n      ClayPlatformLength exponent_x, exponent_y;\n      bool result = true;\n      result &= utils::TryGetPlatformLength(array, 1, radius_x);\n      result &= utils::TryGetPlatformLength(array, 3, radius_y);\n      result &= utils::TryGetPlatformLength(array, 7, center_x);\n      result &= utils::TryGetPlatformLength(array, 9, center_y);\n      if (!result) {\n        FML_DLOG(ERROR) << \"Setting clip-path: error inset format!\";\n        return;\n      }\n      if (!path_data.has_value()) {\n        path_data = ClipPathData{};\n      }\n      path_data->params.emplace_back(radius_x);\n      path_data->params.emplace_back(radius_y);\n      path_data->params.emplace_back(center_x);\n      path_data->params.emplace_back(center_y);\n      path_data->exponents.emplace_back(array[5].GetDouble());\n      path_data->exponents.emplace_back(array[6].GetDouble());\n      path_data->clip_type = ClipPathData::ClipType::kSuperEllipse;\n      break;\n    }\n    case ClayBasicShapeType::kInset: {\n      // begin handling inset function params\n      // clang-format off\n      // params arrange as:\n      // first 8 fields are inset:\n      // | top | lengthUnit | right | lengthUnit | bottom | lengthUnit | left | lengthUnit |\n      // clang-format on\n      ClayPlatformLength top_length, right_length, bottom_length, left_length;\n      bool result = true;\n      result &= utils::TryGetPlatformLength(array, 1, top_length);\n      result &= utils::TryGetPlatformLength(array, 3, right_length);\n      result &= utils::TryGetPlatformLength(array, 5, bottom_length);\n      result &= utils::TryGetPlatformLength(array, 7, left_length);\n\n      if (!result) {\n        FML_DLOG(ERROR) << \"Setting clip-path: error inset format!\";\n        return;\n      }\n      if (!path_data.has_value()) {\n        path_data = ClipPathData{};\n      }\n      path_data->params.emplace_back(top_length);\n      path_data->params.emplace_back(right_length);\n      path_data->params.emplace_back(bottom_length);\n      path_data->params.emplace_back(left_length);\n      // rounded corner's radius value start at index 9\n      int radius_offset = 9;\n      if (array.size() == 9) {\n        // Rect only has first 8 params.\n        path_data->corner_type = ClipPathData::CornerType::kCornerRect;\n      }\n      if (array.size() == 27) {\n        // super-ellipse has two more fields for exponents before border-radius.\n        // | exponent-x | exponent-y | border-radius ...|\n        path_data->exponents.emplace_back(array[9].GetDouble());\n        path_data->exponents.emplace_back(array[10].GetDouble());\n        radius_offset = 11;\n        path_data->corner_type =\n            ClipPathData::CornerType::kCornerSuperElliptical;\n      }\n      if (array.size() == 27 || array.size() == 25) {\n        // clang-format off\n        // inset with rounded and lame corner has 16 radius fields:\n        // |   top-left-x  | lengthUnit |   top-left-y   | lengthUnit |\n        // top-right-x  | lengthUnit | |  top-right-y  | lengthUnit |\n        // bottom-right-x | lengthUnit | bottom-right-y | lengthUnit | |\n        // bottom-left-x | lengthUnit |  bottom-left-y | lengthUnit |\n        // clang-format on\n        ClipPathData::BorderRadius radius_top_left, radius_top_right,\n            radius_bottom_right, radius_bottom_left;\n        utils::TryGetPlatformLength(array, 0 + radius_offset,\n                                    radius_top_left.x);\n        utils::TryGetPlatformLength(array, 2 + radius_offset,\n                                    radius_top_left.y);\n        utils::TryGetPlatformLength(array, 4 + radius_offset,\n                                    radius_top_right.x);\n        utils::TryGetPlatformLength(array, 6 + radius_offset,\n                                    radius_top_right.y);\n        utils::TryGetPlatformLength(array, 8 + radius_offset,\n                                    radius_bottom_right.x);\n        utils::TryGetPlatformLength(array, 12 + radius_offset,\n                                    radius_bottom_right.y);\n        utils::TryGetPlatformLength(array, 12 + radius_offset,\n                                    radius_bottom_left.x);\n        utils::TryGetPlatformLength(array, 14 + radius_offset,\n                                    radius_bottom_left.y);\n\n        path_data->radius.emplace_back(radius_top_left);\n        path_data->radius.emplace_back(radius_top_right);\n        path_data->radius.emplace_back(radius_bottom_right);\n        path_data->radius.emplace_back(radius_bottom_left);\n        if (array.size() == 25) {\n          path_data->corner_type = ClipPathData::CornerType::kCornerRounded;\n        }\n      } else {\n        // invalid params array\n        return;\n      }\n      path_data->clip_type = ClipPathData::ClipType::kInset;\n      break;\n    }\n    case ClayBasicShapeType::kUnknown: {\n      FML_DLOG(ERROR) << \"Setting clip-path: Unknown type.\";\n      break;\n    }\n  }\n}\n\nvoid BaseView::DrawClipPath(bool is_clip_path) {\n#define SET_PATH(path)                    \\\n  if (is_clip_path) {                     \\\n    render_object()->SetClipPath(path);   \\\n  } else {                                \\\n    render_object()->SetOffsetPath(path); \\\n  }\n\n  auto& path_data = is_clip_path ? clip_path_data_ : offset_path_data_;\n  switch (path_data->clip_type) {\n    case ClipPathData::ClipType::kCircle: {\n      if (path_data->params.size() != 4) {\n        break;\n      }\n      float x_len = utils::ResolvePlatformLength(path_data->params[0], width_);\n      float y_len = utils::ResolvePlatformLength(path_data->params[1], height_);\n      float radius = std::min(x_len, y_len);\n      float x_pos = utils::ResolvePlatformLength(path_data->params[2], width_);\n      float y_pos = utils::ResolvePlatformLength(path_data->params[3], height_);\n      FloatRoundedRect rrect;\n      rrect.SetOval({x_pos - radius, y_pos - radius, 2 * radius, 2 * radius});\n      SET_PATH(rrect);\n      break;\n    }\n    case ClipPathData::ClipType::kEllipse: {\n      if (path_data->params.size() != 4) {\n        break;\n      }\n      float x_len = utils::ResolvePlatformLength(path_data->params[0], width_);\n      float y_len = utils::ResolvePlatformLength(path_data->params[1], height_);\n      float x_pos = utils::ResolvePlatformLength(path_data->params[2], width_);\n      float y_pos = utils::ResolvePlatformLength(path_data->params[3], height_);\n      FloatRoundedRect rrect;\n      rrect.SetOval({x_pos - x_len, y_pos - y_len, 2 * x_len, 2 * y_len});\n      SET_PATH(rrect);\n      break;\n    }\n    case ClipPathData::ClipType::kPath:\n      break;\n    case ClipPathData::ClipType::kSuperEllipse: {\n      if (path_data->params.size() != 4 || path_data->exponents.size() != 2) {\n        break;\n      }\n      GrPath path;\n      float rx = utils::ResolvePlatformLength(path_data->params[0], width_);\n      float ry = utils::ResolvePlatformLength(path_data->params[1], height_);\n      float cx = utils::ResolvePlatformLength(path_data->params[2], width_);\n      float cy = utils::ResolvePlatformLength(path_data->params[3], height_);\n      float ex = path_data->exponents[0];\n      float ey = path_data->exponents[1];\n      if (rx == 0 && ry == 0) {\n        // Nothing to do, keep the path empty.\n        break;\n      }\n      // Add super-ellipse to the target position cx, cy\n      for (int i = 1; i <= 4; i++) {\n        AddLameCurveToPath(path, rx, ry, cx, cy, ex, ey, i);\n      }\n      SET_PATH(path);\n      break;\n    }\n    case ClipPathData::ClipType::kInset: {\n      if (path_data->params.size() != 4) {\n        break;\n      }\n      double top = utils::ResolvePlatformLength(path_data->params[0], width_);\n      double right = utils::ResolvePlatformLength(path_data->params[1], width_);\n      double bottom =\n          utils::ResolvePlatformLength(path_data->params[2], width_);\n      double left = utils::ResolvePlatformLength(path_data->params[3], width_);\n\n      // Adjust inset value, values are proportionally reduce the inset effect\n      // to 100% if the pair of insets in either dimension add up to more than\n      // the side length.\n      double v_inset = top + bottom;\n      double h_inset = left + right;\n      if (v_inset != 0 && (v_inset > height_)) {\n        double s = height_ / v_inset;\n        top *= s;\n        bottom *= s;\n      }\n      if (h_inset != 0 && (h_inset > width_)) {\n        double s = width_ / h_inset;\n        left *= s;\n        right *= s;\n      }\n\n      float radius_top_left_x =\n          utils::ResolvePlatformLength(path_data->radius[0].x, width_);\n      float radius_top_left_y =\n          utils::ResolvePlatformLength(path_data->radius[0].y, width_);\n      float radius_top_right_x =\n          utils::ResolvePlatformLength(path_data->radius[1].x, width_);\n      float radius_top_right_y =\n          utils::ResolvePlatformLength(path_data->radius[1].y, width_);\n      float radius_bottom_right_x =\n          utils::ResolvePlatformLength(path_data->radius[2].x, width_);\n      float radius_bottom_right_y =\n          utils::ResolvePlatformLength(path_data->radius[2].y, width_);\n      float radius_bottom_left_x =\n          utils::ResolvePlatformLength(path_data->radius[3].x, width_);\n      float radius_bottom_left_y =\n          utils::ResolvePlatformLength(path_data->radius[3].y, width_);\n      switch (path_data->corner_type) {\n        case ClipPathData::CornerType::kCornerRect: {\n          GrPath path;\n          skity::Rect rect = skity::Rect::MakeLTRB(left, top, width_ - right,\n                                                   height_ - bottom);\n          PATH_ADD_RECT(path, rect);\n          SET_PATH(path);\n          break;\n        }\n        case ClipPathData::CornerType::kCornerRounded: {\n          GrPath path;\n          skity::Vec2 radii[4] = {\n              {radius_top_left_x, radius_top_left_y},\n              {radius_top_right_x, radius_top_right_y},\n              {radius_bottom_right_x, radius_bottom_right_y},\n              {radius_bottom_left_x, radius_bottom_left_y}};\n          skity::RRect rrect;\n          rrect.SetRectRadii(\n              skity::Rect::MakeXYWH(left, top, width_ - left - right,\n                                    height_ - top - bottom),\n              radii);\n          PATH_ADD_RRECT(path, rrect);\n          SET_PATH(path);\n          break;\n        }\n        case ClipPathData::CornerType::kCornerSuperElliptical: {\n          if (path_data->exponents.size() != 2) {\n            break;\n          }\n          GrPath path;\n          std::vector<float> radius = {\n              radius_top_left_x,     radius_top_left_y,\n              radius_top_right_x,    radius_top_right_y,\n              radius_bottom_right_x, radius_bottom_right_y,\n              radius_bottom_left_x,  radius_bottom_left_y};\n          // Add ellipse to the target rect at cx cy\n          // bottom-right corner\n          float rx = radius[4];\n          float ry = radius[5];\n          float cx = width_ - right - rx;\n          float cy = width_ - bottom - ry;\n          float ex = path_data->exponents[0];\n          float ey = path_data->exponents[1];\n          AddLameCurveToPath(path, rx, ry, cx, cy, ex, ey, 1);\n\n          // bottom-left corner\n          rx = radius[6];\n          rx = radius[7];\n          cx = left + rx;\n          cy = width_ - bottom - ry;\n          AddLameCurveToPath(path, rx, ry, cx, cy, ex, ey, 2);\n\n          // top-left corner\n          rx = radius[0];\n          ry = radius[1];\n          cx = left + rx;\n          cy = top + ry;\n          AddLameCurveToPath(path, rx, ry, cx, cy, ex, ey, 3);\n\n          // top-right corner\n          rx = radius[2];\n          ry = radius[3];\n          cx = width_ - right - rx;\n          cy = top + ry;\n          AddLameCurveToPath(path, rx, ry, cx, cy, ex, ey, 4);\n          SET_PATH(path);\n          break;\n        }\n        case ClipPathData::CornerType::kUnknown:\n          break;\n      }\n      break;\n    }\n    case ClipPathData::ClipType::kUnknown: {\n      FML_DLOG(ERROR) << \"Setting clip-path: Unknown type.\";\n      break;\n    }\n  }\n#undef SET_PATH\n}\n\nvoid BaseView::ClearClipPath() {\n  clip_path_data_.reset();\n  render_object()->ClearClipPath();\n}\n\nvoid BaseView::ClearOffsetPath() {\n  offset_path_data_.reset();\n  render_object()->ClearOffsetPath();\n}\n\nvoid BaseView::SetFilter(const clay::Value::Array& array) {\n  // Clear the old filter state.\n  ClearFilter();\n  auto size = array.size();\n  if (size == 0) {\n    return;\n  }\n  bool decoded_by_custom =\n      page_view() && page_view()->GetCustomFilterDecoder() &&\n      page_view()->GetCustomFilterDecoder()->Decode(array, this);\n  if (!decoded_by_custom && size >= 2) {\n    // array like: [type, amount, unit]\n    int int_type = utils::GetInt(array[0]);\n    ClayFilterType type = static_cast<ClayFilterType>(int_type);\n    float amount = utils::GetDouble(array[1]);\n    switch (type) {\n      case ClayFilterType::kGrayScale: {\n        // grayscale is a value between 0 and 1 (100%)\n        amount = std::clamp(amount, 0.f, 1.f);\n        if (amount > 0.f) {\n          render_object()->AppendGrayScale(amount);\n        }\n        break;\n      }\n      case ClayFilterType::kBlur: {\n        amount = std::max(amount, 0.f);\n        if (amount > 0.f) {\n          render_object()->SetBlurRadius(amount);\n        }\n        break;\n      }\n      case ClayFilterType::kNone: {\n        break;\n      }\n      default: {\n        FML_DLOG(ERROR) << \"Unsupported filter type\";\n        break;\n      }\n    }\n  }\n}\n\nvoid BaseView::ClearFilter() {\n  FML_DCHECK(render_object());\n  render_object()->ClearFilter();\n}\n\nvoid BaseView::SetImageRendering(ClayImageRendering image_rendering) {\n  // ClayImageRendering::kCrispEdges is not supported, behave as kAuto.\n  render_object()->SetImageFilterMode(\n      image_rendering == ClayImageRendering::kPixelated ? FilterMode::kNearest\n                                                        : FilterMode::kLinear);\n}\n\nvoid BaseView::SetID(int id) {\n  id_ = id;\n  if (render_object()) {\n    render_object()->SetID(id);\n  }\n}\n\nvoid BaseView::LoadBackgroundOrMaskImage(const std::string& uri, size_t index,\n                                         bool background) {\n  FML_DCHECK(page_view());\n\n#ifndef ENABLE_SKITY\n  page_view_->GetImageResourceFetcher()->FetchImageAsync(\n      uri,\n      [self = weak_factory_.GetWeakPtr(), uri, index, background,\n       bg_image_loader_token = bg_image_loader_token_,\n       mask_image_loader_token = mask_image_loader_token_](\n          std::unique_ptr<ImageResource> resource, bool hit_cache) {\n        if (!resource || !self) {\n          if (!resource && background && self) {\n            self->NotifyBgImageLoadStatus(\n                false, {\"errMsg\", \"url\", \"lynx_categorized_code\", \"error_code\"},\n                \"resource load fail\", uri, 0, 0);\n          }\n          return;\n        }\n        if (self->GetCurrentImageLoaderToken() != bg_image_loader_token &&\n            self->GetCurrentMaskImageLoaderToken() != mask_image_loader_token) {\n          return;\n        }\n\n        if (background) {\n          self->NotifyBgImageLoadStatus(true, {\"height\", \"width\", \"url\"},\n                                        resource->GetHeight(),\n                                        resource->GetWidth(), uri);\n          self->render_object()->SetBackgroundImage(index, std::move(resource));\n        } else {\n          self->render_object()->SetMaskImage(index, std::move(resource));\n        }\n      },\n      page_view_->UseTextureBackend(),\n      background && page_view_->DeferredImageDecode(),\n      page_view_->ImageDecodeWithPriority());\n#else\n  page_view_->GetImageResourceFetcher()->FetchImage(\n      uri, false,\n      [self = weak_factory_.GetWeakPtr(), uri, index, background](\n          std::unique_ptr<BaseImageInstance> image_instance, bool hit_cache) {\n        if (!image_instance || !self) {\n          if (!image_instance && background && self) {\n            self->NotifyBgImageLoadStatus(\n                false, {\"errMsg\", \"url\", \"lynx_categorized_code\", \"error_code\"},\n                \"resource load fail\", uri, 0, 0);\n          }\n          return;\n        }\n        auto image = image_instance->GetImage();\n        if (!image) {\n          if (background && self) {\n            self->NotifyBgImageLoadStatus(\n                false, {\"errMsg\", \"url\", \"lynx_categorized_code\", \"error_code\"},\n                \"resource load fail\", uri, 0, 0);\n          }\n          return;\n        }\n        image_instance->SetAnimationFrameCallback([self]() {\n          if (!self) {\n            return;\n          }\n          self->render_object()->MarkNeedsPaint();\n        });\n        if (background) {\n          self->NotifyBgImageLoadStatus(true, {\"height\", \"width\", \"url\"},\n                                        image_instance->GetHeight(),\n                                        image_instance->GetWidth(), uri);\n          self->render_object()->SetBackgroundImage(index,\n                                                    std::move(image_instance));\n        } else {\n          self->render_object()->SetMaskImage(index, std::move(image_instance));\n        }\n      });\n#endif  // ENABLE_SKITY\n}\n\nvoid BaseView::SetBackgroundImage(const clay::Value::Array& array) {\n  if (OnBackgroundProperty(BaseView::BackgroundUpdate{\n          BaseView::BackgroundPropType::kImage, &array})) {\n    return;\n  }\n  // The image count\n  render_object()->ResizeBackground(array.size() / 2);\n  if (array.size() == 0) {\n    return;\n  }\n\n  bg_image_loader_token_++;\n  for (size_t i = 0; i < array.size(); i = i + 2) {\n    const auto& type =\n        static_cast<ClayBackgroundImageType>(utils::GetUint(array[i]));\n\n    if (type == ClayBackgroundImageType::kUrl) {  // uri\n      const auto& uri = utils::GetCString(array[i + 1]);\n      LoadBackgroundOrMaskImage(uri, i / 2);\n    } else {\n      std::optional<Gradient> (*f)(const clay::Value::Array&) = nullptr;\n      if (type == ClayBackgroundImageType::kLinearGradient) {\n        f = Gradient::CreateLinear;\n      } else if (type == ClayBackgroundImageType::kRadialGradient) {\n        f = Gradient::CreateRadial;\n      } else if (type == ClayBackgroundImageType::kConicGradient) {\n        f = Gradient::CreateConic;\n      }\n      if (!f) {\n        FML_DLOG(WARNING) << \"Unknow ClayBackgroundImageType:\"\n                          << static_cast<int>(type);\n        continue;\n      }\n      const auto& array_param = utils::GetArray(array[i + 1]);\n      FML_DCHECK(array_param.size() > 0);\n      auto g = f(array_param);\n      if (g.has_value()) {\n        render_object()->SetBackgroundImage(i / 2, *g);\n      } else {\n        FML_DLOG(WARNING) << \"Create gradient failed\";\n      }\n    }\n  }\n}\n\nvoid BaseView::SetBackgroundClip(const clay::Value::Array& array) {\n  if (OnBackgroundProperty(BaseView::BackgroundUpdate{\n          BaseView::BackgroundPropType::kClip, &array})) {\n    return;\n  }\n  std::vector<ClayBackgroundClipType> clips(array.size());\n  for (size_t i = 0; i < array.size(); i++) {\n    clips[i] = static_cast<ClayBackgroundClipType>(utils::GetInt(array[i]));\n  }\n  render_object()->SetBackgroundClip(clips);\n}\n\nvoid BaseView::SetBackgroundOrigin(const clay::Value::Array& array) {\n  if (OnBackgroundProperty(BaseView::BackgroundUpdate{\n          BaseView::BackgroundPropType::kOrigin, &array})) {\n    return;\n  }\n  std::vector<ClayBackgroundOriginType> origins(array.size());\n  for (size_t i = 0; i < array.size(); i++) {\n    origins[i] = static_cast<ClayBackgroundOriginType>(utils::GetInt(array[i]));\n  }\n  render_object()->SetBackgroundOrigin(origins);\n}\n\nvoid BaseView::SetBackgroundPosition(\n    const std::vector<BackgroundPosition>& positions) {\n  if (OnBackgroundProperty(BaseView::BackgroundUpdate{\n          BaseView::BackgroundPropType::kPosition, &positions})) {\n    return;\n  }\n  render_object()->SetBackgroundPosition(positions);\n}\n\nvoid BaseView::SetBackgroundRepeat(const clay::Value::Array& array) {\n  if (OnBackgroundProperty(BaseView::BackgroundUpdate{\n          BaseView::BackgroundPropType::kRepeat, &array})) {\n    return;\n  }\n  std::vector<ClayBackgroundRepeatType> repeats(array.size());\n  for (size_t i = 0; i < array.size(); i++) {\n    repeats[i] = static_cast<ClayBackgroundRepeatType>(utils::GetInt(array[i]));\n  }\n  render_object()->SetBackgroundRepeat(repeats);\n}\n\nvoid BaseView::SetBackgroundSize(const std::vector<BackgroundSize>& sizes) {\n  if (OnBackgroundProperty(BaseView::BackgroundUpdate{\n          BaseView::BackgroundPropType::kSize, &sizes})) {\n    return;\n  }\n  render_object()->SetBackgroundSize(sizes);\n}\n\nvoid BaseView::SetMaskImage(const clay::Value::Array& array) {\n  if (array.size() == 0) {\n    return;\n  }\n\n  render_object()->ResizeMask(array.size() / 2);\n  for (size_t i = 0; i < array.size(); i = i + 2) {\n    const auto& type = static_cast<ClayMaskImageType>(utils::GetUint(array[i]));\n\n    if (type == ClayMaskImageType::kUrl) {  // uri\n      const auto& uri = utils::GetCString(array[i + 1]);\n      LoadBackgroundOrMaskImage(uri, i / 2, false);\n    } else {\n      std::optional<Gradient> (*f)(const clay::Value::Array&) = nullptr;\n      if (type == ClayMaskImageType::kLinearGradient) {\n        f = Gradient::CreateLinear;\n      } else if (type == ClayMaskImageType::kRadialGradient) {\n        f = Gradient::CreateRadial;\n      } else if (type == ClayMaskImageType::kConicGradient) {\n        f = Gradient::CreateConic;\n      }\n      if (!f) {\n        FML_DLOG(WARNING) << \"Unknow ClayMaskImageType:\"\n                          << static_cast<int>(type);\n        continue;\n      }\n      const auto& array_param = utils::GetArray(array[i + 1]);\n      FML_DCHECK(array_param.size() > 0);\n      auto g = f(array_param);\n      if (g.has_value()) {\n        render_object()->SetMaskImage(i / 2, *g);\n      } else {\n        FML_DLOG(WARNING) << \"Create gradient failed\";\n      }\n    }\n  }\n}\n\nvoid BaseView::SetMaskPosition(const std::vector<MaskPosition>& positions) {\n  render_object()->SetMaskPosition(positions);\n}\n\nvoid BaseView::SetMaskRepeat(const clay::Value::Array& array) {\n  std::vector<ClayMaskRepeatType> repeats(array.size());\n  for (size_t i = 0; i < array.size(); i++) {\n    repeats[i] = static_cast<ClayMaskRepeatType>(utils::GetInt(array[i]));\n  }\n  render_object()->SetMaskRepeat(repeats);\n}\n\nvoid BaseView::SetMaskOrigin(const clay::Value::Array& array) {\n  std::vector<ClayMaskOriginType> origins(array.size());\n  for (size_t i = 0; i < array.size(); i++) {\n    origins[i] = static_cast<ClayMaskOriginType>(utils::GetInt(array[i]));\n  }\n  render_object()->SetMaskOrigin(origins);\n}\n\nvoid BaseView::SetMaskSize(const std::vector<MaskSize>& mask_sizes) {\n  render_object()->SetMaskSize(mask_sizes);\n}\n\nvoid BaseView::SetMaskClip(const clay::Value::Array& array) {\n  std::vector<ClayMaskClipType> clips(array.size());\n  for (size_t i = 0; i < array.size(); i++) {\n    clips[i] = static_cast<ClayMaskClipType>(utils::GetInt(array[i]));\n  }\n  render_object()->SetMaskClip(clips);\n}\n\nTransitionManager* BaseView::TransitionMgr() {\n  if (!transition_mgr_) {\n    transition_mgr_ =\n        std::make_unique<TransitionManager>(GetAnimationMutator());\n    transition_mgr_->SetEventHandler(GetAnimationMutator());\n  }\n  return transition_mgr_.get();\n}\n\nvoid BaseView::TransitionTo(ClayAnimationPropertyType type, float value) {\n  switch (type) {\n    case ClayAnimationPropertyType::kLeft:\n    case ClayAnimationPropertyType::kTop:\n    case ClayAnimationPropertyType::kWidth:\n    case ClayAnimationPropertyType::kHeight:\n      // Start property transition animation if it is enabled\n      if (TransitionMgr()->Enabled(type) &&\n          TransitionMgr()->TransitionTo(type, value)) {\n        Invalidate();\n        return;\n      }\n      SetProperty(type, value, false);\n      break;\n    case ClayAnimationPropertyType::kOpacity:\n      if (TransitionMgr()->Enabled(type) &&\n          TransitionMgr()->TransitionTo(type, value)) {\n        UpdateTransitionRasterAnimation(type);\n        return;\n      }\n      SetProperty(type, value, false);\n      break;\n    default:\n      FML_DLOG(ERROR) << \"BaseView::TransitionTo with unsupported type: \"\n                      << static_cast<int>(type);\n      break;\n  }\n}\n\nvoid BaseView::TransitionTo(ClayAnimationPropertyType type,\n                            const Color& value) {\n  switch (type) {\n    case ClayAnimationPropertyType::kBackgroundColor:\n      // Start property transition animation if it is enabled\n      if (TransitionMgr()->Enabled(type) &&\n          TransitionMgr()->TransitionTo(type, value)) {\n        UpdateTransitionRasterAnimation(type);\n        return;\n      }\n      SetProperty(type, value, false);\n      break;\n    default:\n      FML_DLOG(ERROR) << \"BaseView::TransitionTo with unsupported type: \"\n                      << static_cast<int>(type);\n      break;\n  }\n}\n\nvoid BaseView::TransitionTo(ClayAnimationPropertyType type,\n                            const TransformOperations& value) {\n  switch (type) {\n    case ClayAnimationPropertyType::kTransform:\n      // Start property transition animation if it is enabled\n      if (TransitionMgr()->Enabled(type) &&\n          TransitionMgr()->TransitionTo(type, value)) {\n        UpdateTransitionRasterAnimation(type);\n        return;\n      }\n      SetProperty(type, value, false);\n      break;\n    default:\n      FML_DLOG(ERROR) << \"BaseView::TransitionTo with unsupported type: \"\n                      << static_cast<int>(type);\n      break;\n  }\n}\n\nvoid BaseView::SetProperty(ClayAnimationPropertyType type, float value,\n                           bool skip_update_for_raster_animation) {\n  skip_update_for_raster_animation =\n      skip_update_for_raster_animation && IsRasterAnimationEnabled();\n  FloatRect old_bounds = GetBounds();\n  switch (type) {\n    case ClayAnimationPropertyType::kLeft:\n      left_ = value;\n      render_object()->SetLeft(value);\n      break;\n    case ClayAnimationPropertyType::kTop:\n      top_ = value;\n      render_object()->SetTop(value);\n      break;\n    case ClayAnimationPropertyType::kWidth:\n      width_ = value;\n      render_object()->SetWidth(value);\n      UpdateRenderObjectTransformOrigin();\n      break;\n    case ClayAnimationPropertyType::kHeight:\n      height_ = value;\n      render_object()->SetHeight(value);\n      UpdateRenderObjectTransformOrigin();\n      break;\n    case ClayAnimationPropertyType::kOpacity:\n      render_object()->SetOpacity(value, skip_update_for_raster_animation);\n      break;\n    default:\n      FML_DLOG(ERROR) << \"BaseView::SetProperty with unsupported type: \"\n                      << static_cast<int>(type);\n      break;\n  }\n  NotifyBoundChangeIfNeeded(old_bounds);\n}\n\nvoid BaseView::NotifyBoundChangeIfNeeded(const FloatRect& old_bounds) {\n  if (ignore_size_change_checks_) {\n    return;\n  }\n  FloatRect bounds = GetBounds();\n  if (old_bounds.size() != bounds.size()) {\n    OnContentSizeChanged(old_bounds, bounds);\n  }\n  if (old_bounds != bounds) {\n    OnBoundsChanged(old_bounds, bounds);\n  }\n}\n\nvoid BaseView::UpdateChildrenBounds() {\n  if (needs_layout_updated_) {\n    return;\n  }\n  needs_layout_updated_ = true;\n  for (BaseView* child : children_) {\n    child->UpdateChildrenBounds();\n  }\n}\n\nvoid BaseView::SetProperty(ClayAnimationPropertyType type, const Color& value,\n                           bool skip_update_for_raster_animation) {\n  skip_update_for_raster_animation =\n      skip_update_for_raster_animation && IsRasterAnimationEnabled();\n  switch (type) {\n    case ClayAnimationPropertyType::kBackgroundColor:\n      render_object()->SetBackgroundColor(value,\n                                          skip_update_for_raster_animation);\n      break;\n    case ClayAnimationPropertyType::kColor:\n      // it's used for compositor animation.\n      // just run through here.\n      break;\n    default:\n      FML_DLOG(WARNING) << \"BaseView::SetProperty with unsupported type: \"\n                        << static_cast<int>(type);\n      break;\n  }\n}\n\nvoid BaseView::SetOffsetDistance(float distance) {\n  render_object()->SetOffsetDistance(distance);\n}\n\nvoid BaseView::SetOffsetRotate(float rotate) {\n  render_object()->SetOffsetRotate(rotate);\n}\n\nvoid BaseView::SetTransformOperations(const TransformOperations& value,\n                                      bool is_from_animation) {\n  transform_ops_ = value;\n#ifdef ENABLE_ACCESSIBILITY\n  Transform old_transform = GetTransform();\n#endif\n  float old_translate_z = render_object()->GetTranslateZ();\n  if (post_translation_.has_value()) {\n    TransformOperations transform;\n    transform.AppendTranslate(post_translation_->x(), post_translation_->y(),\n                              0);\n    transform.Append(value);\n    render_object()->SetTransformOperations(transform, is_from_animation);\n  } else {\n    render_object()->SetTransformOperations(transform_ops_, is_from_animation);\n  }\n#ifdef ENABLE_ACCESSIBILITY\n  Transform new_transform = GetTransform();\n  // Changes of transform will affect current semantics_node's and all\n  // descendants' accumulated transform to its real parent_node.\n  if (!old_transform.ApproximatelyEqual(new_transform)) {\n    if (auto* semantics_owner = GetSemanticsOwner()) {\n      semantics_owner->AddDirtySemanticsForDescendants(this);\n    }\n  }\n#endif\n  if (Parent() &&\n      std::abs(render_object()->GetTranslateZ() - old_translate_z) > 1e-6) {\n    Parent()->DirtyChildrenPaintingOrder();\n  }\n}\n\nvoid BaseView::SetProperty(ClayAnimationPropertyType type,\n                           const FilterOperations& value) {\n  switch (type) {\n    case ClayAnimationPropertyType::kFilter:\n      SetFilterOperations(value);\n      break;\n    default: {\n      FML_LOG(ERROR) << \"BaseView::SetProperty with unsupported type: \"\n                     << static_cast<int>(type);\n      break;\n    }\n  }\n}\n\nvoid BaseView::SetFilterOperations(const FilterOperations& value) {\n  color_matrix_ops_ = value;\n  render_object_->ClearFilter();\n  // apply will calculate the blur radius.\n  std::array<float, 20> m = value.Apply();\n  std::vector<float> v(m.data(), m.data() + 20);\n  render_object_->CombineColorFilter(v);\n  render_object_->SetBlurRadius(value.GetBlurRadius());\n}\n\nvoid BaseView::SetProperty(ClayAnimationPropertyType type,\n                           const TransformOperations& value,\n                           bool skip_update_for_raster_animation) {\n  skip_update_for_raster_animation =\n      skip_update_for_raster_animation && IsRasterAnimationEnabled();\n  switch (type)\n  case ClayAnimationPropertyType::kTransform: {\n    SetTransformOperations(value, skip_update_for_raster_animation);\n    break;\n    default: {\n      FML_DLOG(ERROR) << \"BaseView::SetProperty with unsupported type: \"\n                      << static_cast<int>(type);\n      break;\n    }\n  }\n}\n\nvoid BaseView::SetBoxShadowOperations(const BoxShadowOperations& value) {\n  box_shadow_ops_ = value;\n  render_object_->SetShadows(value.apply());\n}\n\nvoid BaseView::GetProperty(ClayAnimationPropertyType type, float& value) {\n  switch (type) {\n    case ClayAnimationPropertyType::kLeft:\n      value = left_;\n      break;\n    case ClayAnimationPropertyType::kTop:\n      value = top_;\n      break;\n    case ClayAnimationPropertyType::kWidth:\n      value = width_;\n      break;\n    case ClayAnimationPropertyType::kHeight:\n      value = height_;\n      break;\n    case ClayAnimationPropertyType::kOpacity:\n      value = render_object()->HasOpacity() ? render_object()->Opacity() : 1.0f;\n      break;\n    default:\n      FML_DLOG(ERROR) << \"BaseView::GetProperty with unsupported type: \"\n                      << static_cast<int>(type);\n      break;\n  }\n}\n\nvoid BaseView::GetProperty(ClayAnimationPropertyType type, Color& value) {\n  switch (type) {\n    case ClayAnimationPropertyType::kBackgroundColor:\n      if (render_object()->HasBackground()) {\n        value = render_object()->Background().background_color;\n      } else {\n        value = Color();\n      }\n      break;\n    default:\n      FML_DLOG(ERROR) << \"BaseView::GetProperty with unsupported type: \"\n                      << static_cast<int>(type);\n      break;\n  }\n}\n\nvoid BaseView::GetProperty(ClayAnimationPropertyType type,\n                           TransformOperations& value) {\n  switch (type) {\n    case ClayAnimationPropertyType::kTransform:\n      value = transform_ops_;\n      break;\n    default:\n      FML_DLOG(ERROR) << \"BaseView::GetProperty with unsupported type: \"\n                      << static_cast<int>(type);\n      break;\n  }\n}\n\nvoid BaseView::GetProperty(ClayAnimationPropertyType type,\n                           FilterOperations& value) {\n  switch (type) {\n    case ClayAnimationPropertyType::kFilter:\n      value = color_matrix_ops_;\n      break;\n    default:\n      FML_LOG(ERROR) << \"BaseView::GetProperty with unsupported type: \"\n                     << static_cast<int>(type);\n      break;\n  }\n}\n\nAnimationHandler* BaseView::GetAnimationHandler() {\n  return page_view_->GetAnimationHandler();\n}\n\nconst KeyframesMap* BaseView::GetKeyframesMap(\n    const std::string& animation_name) {\n  return page_view_->GetKeyframesMap(animation_name);\n}\n\nKeyframesManager* BaseView::KeyframesMgr() {\n  if (!keyframes_mgr_) {\n    keyframes_mgr_ = std::make_unique<KeyframesManager>(GetAnimationMutator());\n    keyframes_mgr_->SetEventHandler(GetAnimationMutator());\n  }\n  return keyframes_mgr_.get();\n}\n\nvoid BaseView::SetAnimation(const std::vector<AnimationData>& data) {\n  if (!data.empty()) {\n    SetRepaintBoundary(true);\n  }\n  animation_ = std::make_optional<std::vector<AnimationData>>(data);\n  auto result = KeyframesMgr()->UpdateData(data);\n  if (result.data_has_changed) {\n    // Animations may not play without this\n    page_view()->RequestPaint();\n\n    UpdateKeyframesRasterAnimation();\n  }\n  if (result.state_has_changed) {\n    Invalidate();\n  }\n}\n\nvoid BaseView::SetAnimation(const clay::Value::Array& array) {\n  std::vector<AnimationData> animations(array.size());\n  for (size_t i = 0; i < array.size(); i++) {\n    const auto& arr = utils::GetArray(array[i]);\n    if (arr.size() == 0) {\n      continue;\n    }\n    FML_DCHECK(arr.size() == 13);\n    int idx = 0;\n    animations[i].name = utils::GetCString(arr[idx++]);\n    animations[i].duration = utils::GetDouble(arr[idx++]);\n    animations[i].timing_func.timing_func =\n        static_cast<ClayTimingFunctionType>(utils::GetInt(arr[idx++]));\n    animations[i].timing_func.steps_type =\n        static_cast<ClayStepsType>(utils::GetInt(arr[idx++]));\n    animations[i].timing_func.x1 = utils::GetDouble(arr[idx++]);\n    animations[i].timing_func.y1 = utils::GetDouble(arr[idx++]);\n    animations[i].timing_func.x2 = utils::GetDouble(arr[idx++]);\n    animations[i].timing_func.y2 = utils::GetDouble(arr[idx++]);\n    animations[i].delay = utils::GetDouble(arr[idx++]);\n    animations[i].iteration_count = utils::GetInt(arr[idx++]) - 1;\n    animations[i].direction =\n        static_cast<ClayAnimationDirectionType>(utils::GetInt(arr[idx++]));\n    animations[i].fill_mode =\n        static_cast<ClayAnimationFillModeType>(utils::GetInt(arr[idx++]));\n    animations[i].play_state =\n        static_cast<ClayAnimationPlayStateType>(utils::GetInt(arr[idx++]));\n  }\n  SetAnimation(animations);\n}\n\nvoid BaseView::AppendTransition(const TransitionData& data) {\n  SetRepaintBoundary(true);\n  TransitionMgr()->AppendData(data);\n}\n\nvoid BaseView::SetTransition(const std::vector<TransitionData>& data) {\n  if (!data.empty()) {\n    SetRepaintBoundary(true);\n  }\n  TransitionMgr()->UpdateData(data);\n}\n\nvoid BaseView::SetTransition(const clay::Value::Array& array) {\n  if (array.size() != 0) {\n    SetRepaintBoundary(true);\n  }\n  std::vector<TransitionData> transitions(array.size());\n  for (size_t i = 0; i < array.size(); i++) {\n    const auto& arr = utils::GetArray(array[i]);\n    if (arr.size() == 0) {\n      continue;\n    }\n    int idx = 0;\n    transitions[i].property =\n        static_cast<ClayAnimationPropertyType>(utils::GetInt(arr[idx++]));\n    transitions[i].duration = utils::GetDouble(arr[idx++]);\n    transitions[i].timing_func.timing_func =\n        static_cast<ClayTimingFunctionType>(utils::GetInt(arr[idx++]));\n    transitions[i].timing_func.steps_type =\n        static_cast<ClayStepsType>(utils::GetInt(arr[idx++]));\n    transitions[i].timing_func.x1 = utils::GetDouble(arr[idx++]);\n    transitions[i].timing_func.y1 = utils::GetDouble(arr[idx++]);\n    transitions[i].timing_func.x2 = utils::GetDouble(arr[idx++]);\n    transitions[i].timing_func.y2 = utils::GetDouble(arr[idx++]);\n    transitions[i].delay = utils::GetDouble(arr[idx++]);\n  }\n  TransitionMgr()->UpdateData(transitions);\n}\n\nvoid BaseView::SetTransform(const TransformOperations& ops,\n                            const FloatPoint& origin) {\n  SetTransformOrigin(\n      std::make_optional<TransformOrigin>(origin.x(), origin.y()));\n\n  TransformOperations old_ops;\n  GetProperty(ClayAnimationPropertyType::kTransform, old_ops);\n  constexpr float tolerance = 0.001;\n  if (old_ops.ApproximatelyEqual(ops, tolerance)) {\n    if (TransitionMgr()->IsAnimationRunning(\n            ClayAnimationPropertyType::kTransform)) {\n      TransitionMgr()->CancelAnimator(ClayAnimationPropertyType::kTransform);\n    }\n    SetProperty(ClayAnimationPropertyType::kTransform, ops, false);\n    return;\n  }\n  TransitionTo(ClayAnimationPropertyType::kTransform, ops);\n}\n\nvoid BaseView::SetTransform(const std::vector<TransformRaw>& transform_raw) {\n  transform_raw_ = transform_raw;\n  auto ops = clay::TransformOperations(*transform_raw_, width_, height_);\n  TransformOperations old_ops;\n  GetProperty(ClayAnimationPropertyType::kTransform, old_ops);\n  constexpr float tolerance = 0.001;\n  if (old_ops.ApproximatelyEqual(ops, tolerance)) {\n    if (TransitionMgr()->IsAnimationRunning(\n            ClayAnimationPropertyType::kTransform)) {\n      TransitionMgr()->CancelAnimator(ClayAnimationPropertyType::kTransform);\n    }\n    SetProperty(ClayAnimationPropertyType::kTransform, ops, false);\n    return;\n  }\n  TransitionTo(ClayAnimationPropertyType::kTransform, ops);\n}\n\nvoid BaseView::SetTransformOrigin(std::optional<TransformOrigin> origin) {\n  transform_origin_ = std::move(origin);\n  UpdateRenderObjectTransformOrigin();\n}\n\nvoid BaseView::SetTransformOrigin(const std::vector<Length>& array) {\n  auto transform_origin = std::make_optional<TransformOrigin>();\n  if (array.size() == 0) {\n    transform_origin->Reset();\n  } else if (array.size() >= 1) {\n    transform_origin->SetX(array[0]);\n    if (array.size() == 2) {\n      transform_origin->SetY(array[1]);\n    }\n  }\n  SetTransformOrigin(std::move(transform_origin));\n}\n\nvoid BaseView::SetPerspective(const clay::Value::Array& array) {\n  float perspective = 0;\n  // CAUTION: scale and CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER will affect\n  // final result seriously.\n  float scale = 1;\n  double value = utils::GetInt(array[0]);\n  int type = utils::GetInt(array[1]);\n  if (array.size() > 1 && type != PLATFORM_PERSPECTIVE_UNIT_DEFAULT) {\n    if (type == PLATFORM_PERSPECTIVE_UNIT_NUMBER) {\n      perspective = (float)(value * scale * scale *\n                            CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER);\n    } else {\n      if (type == PLATFORM_PERSPECTIVE_UNIT_VW ||\n          type == PLATFORM_PERSPECTIVE_UNIT_VH) {\n        perspective = type == PLATFORM_PERSPECTIVE_UNIT_VW\n                          ? (float)(value / 100.f * width_)\n                          : (float)(value / 100.f * height_);\n      } else {\n        perspective = (float)value;\n      }\n      perspective =\n          perspective * scale * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER;\n    }\n  } else {\n    int max_length = std::max(width_, height_);\n    perspective = max_length * scale *\n                  CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER *\n                  DEFAULT_PERSPECTIVE_FACTOR;\n  }\n  if (perspective != perspective_value_) {\n    perspective_value_ = perspective;\n    render_object()->SetPerspective(perspective_value_.value());\n  }\n}\n\nvoid BaseView::SetConsumeSlideEventDirection(const clay::Value::Array& array) {\n  size_t array_size = array.size();\n  consume_slide_event_ranges_.resize(array_size, {0, 0});\n  for (size_t i = 0; i < array_size; i++) {\n    if (!array[i].IsArray()) {\n      continue;\n    }\n    const auto& angles = utils::GetArray(array[i]);\n    if (angles.size() != 2) {\n      continue;\n    }\n    consume_slide_event_ranges_[i] = {utils::GetNum(angles[0]),\n                                      utils::GetNum(angles[1])};\n  }\n}\n\nbool BaseView::ConsumeSlideEvent(float angle) {\n  for (const auto& range : consume_slide_event_ranges_) {\n    if (angle >= range.first && angle <= range.second) {\n      return true;\n    }\n  }\n  return false;\n}\n\nTransform BaseView::GetTransform() const {\n  if (render_object()->HasTransform()) {\n    return render_object()->GetTransform();\n  } else {\n    return Transform();\n  }\n}\n\nFloatPoint BaseView::GetTransformOrigin() const {\n  if (transform_origin_.has_value()) {\n    return transform_origin_->GetValue(width_, height_);\n  }\n  return FloatPoint(width_ / 2.0f, height_ / 2.0f);\n}\n\nvoid BaseView::SetPaddings(float padding_left, float padding_top,\n                           float padding_right, float padding_bottom) {\n  FloatRect old_rect(PaddingLeft() + BorderLeft(), PaddingTop() + BorderTop(),\n                     Width() - PaddingRight() - BorderRight(),\n                     Height() - PaddingBottom() - BorderBottom());\n  if (padding_left_ != padding_left) {\n    padding_left_ = padding_left;\n    render_object()->SetPaddingLeft(padding_left_);\n  }\n\n  if (padding_top_ != padding_top) {\n    padding_top_ = padding_top;\n    render_object()->SetPaddingTop(padding_top_);\n  }\n\n  if (padding_right_ != padding_right) {\n    padding_right_ = padding_right;\n    render_object()->SetPaddingRight(padding_right_);\n  }\n\n  if (padding_bottom_ != padding_bottom) {\n    padding_bottom_ = padding_bottom;\n    render_object()->SetPaddingBottom(padding_bottom_);\n  }\n  FloatRect new_rect(PaddingLeft() + BorderLeft(), PaddingTop() + BorderTop(),\n                     Width() - PaddingRight() - BorderRight(),\n                     Height() - PaddingBottom() - BorderBottom());\n  if (new_rect.width() != old_rect.width() ||\n      new_rect.height() != old_rect.height()) {\n    OnContentSizeChanged(old_rect, new_rect);\n  }\n  OnLayoutChange();\n}\n\nvoid BaseView::SetMargins(float margin_left, float margin_top,\n                          float margin_right, float margin_bottom) {\n  margin_left_ = margin_left;\n  render_object()->SetMarginLeft(margin_left);\n  margin_top_ = margin_top;\n  render_object()->SetMarginTop(margin_top);\n  margin_right_ = margin_right;\n  render_object()->SetMarginRight(margin_right);\n  margin_bottom_ = margin_bottom;\n  render_object()->SetMarginBottom(margin_bottom);\n}\n\nfloat BaseView::LeftWithScroll() const {\n  if (render_object()) {\n    return left_ - render_object()->ScrollLeft();\n  }\n  return 0.f;\n}\n\nfloat BaseView::TopWithScroll() const {\n  if (render_object()) {\n    return top_ - render_object()->ScrollTop();\n  }\n  return 0.f;\n}\n\nfloat BaseView::ContentInsetLeft() const {\n  return BorderLeft() + PaddingLeft();\n}\n\nfloat BaseView::ContentInsetTop() const { return BorderTop() + PaddingTop(); }\n\nFloatPoint BaseView::AbsoluteLocationWithScroll() const {\n  BaseView* parent = Parent();\n  float left = Left();\n  float top = Top();\n  auto self = this;\n  while (parent) {\n    if (self->IsIndependentSubViewTree()) {\n      break;\n    } else {\n      self = parent;\n      left += parent->LeftWithScroll();\n      top += parent->TopWithScroll();\n      parent = parent->Parent();\n    }\n  }\n\n  return FloatPoint(left, top);\n}\n\nvoid BaseView::OnViewPostionUpdate(FloatPoint scroll_offset) {\n  scroll_offset += FloatPoint(LeftWithScroll(), TopWithScroll());\n  for (auto child : GetChildren()) {\n    child->OnViewPostionUpdate(scroll_offset);\n  }\n}\n\nfloat BaseView::BorderLeft() const {\n  if (render_object()) {\n    return render_object()->BorderLeft();\n  }\n  return 0.f;\n}\n\nfloat BaseView::BorderTop() const {\n  if (render_object()) {\n    return render_object()->BorderTop();\n  }\n  return 0.f;\n}\n\nfloat BaseView::BorderRight() const {\n  if (render_object()) {\n    return render_object()->BorderRight();\n  }\n  return 0.f;\n}\n\nfloat BaseView::BorderBottom() const {\n  if (render_object()) {\n    return render_object()->BorderBottom();\n  }\n  return 0.f;\n}\n\nFocusManager* BaseView::GetParentFocusManager() {\n  if (Parent()) {\n    return Parent()->GetFocusManager();\n  }\n  return nullptr;\n}\n\nvoid BaseView::FocusHasChanged(bool focused, bool is_leaf) {\n  if (is_leaf) {\n    OnFocusChanged(focused);\n    page_view()->SendEvent(\n        GetCallbackId(),\n        focused ? event_attr::kEventFocus : event_attr::kEventBlur,\n        std::vector<std::string>{});\n    if (!IsFocusFence()) {\n      // This is only used to update pseudo styles. Skip `focus-isolate` nodes\n      // for performance\n      if (page_view()->GetEventDelegate()) {\n        page_view()->GetEventDelegate()->OnFocusChanged(GetCallbackId(),\n                                                        focused);\n      }\n    }\n\n    if (focused) {\n      ScrollToVisible();\n    }\n  }\n}\n\nvoid BaseView::SetHasDefaultFocusRing(bool has_focus_ring) {\n  if (page_view()->DefaultFocusRingEnabled()) {\n    render_object()->SetHasDefaultFocusRing(has_focus_ring);\n  }\n}\n\nFloatRect BaseView::CalcFocusRect() const {\n  FloatPoint point(left_, top_);\n  BaseView* parent = Parent();\n  while (parent && !parent->IsFocusScope()) {\n    FloatPoint offset = parent->GetScrollOffset();\n    point.MoveBy(-offset);\n\n    point.Move(parent->Left(), parent->Top());\n    parent = parent->Parent();\n  }\n\n  return FloatRect(point.x(), point.y(), width_, height_);\n}\n\nbool BaseView::DispatchKeyEventOnFocusNode(const KeyEvent* event) {\n  return OnKeyEvent(event);\n}\n\nFloatRect BaseView::GetContentVisibleRect() {\n  FloatPoint offset = GetScrollOffset();\n  return FloatRect(offset.x(), offset.y(), ContentWidth(), ContentHeight());\n}\n\nFloatSize BaseView::GetThicknessOffset() {\n  return FloatSize(PaddingLeft() + BorderLeft(), PaddingTop() + BorderTop());\n}\n\nvoid BaseView::SetInLayoutTree(bool in_layout_tree) {\n  if (IsLayoutRootCandidate()) {\n    // If the current node is a layout root candidate, it is already in a\n    // layout tree.\n    in_layout_tree = true;\n  }\n  if (in_layout_tree == in_layout_tree_) {\n    return;\n  }\n  in_layout_tree_ = in_layout_tree;\n  for (BaseView* child : children_) {\n    child->SetInLayoutTree(in_layout_tree);\n  }\n}\n\nvoid BaseView::MarkNeedsLayout(BaseView* view_to_layout) {\n  if (ShouldIgnoreLayoutRequest()) {\n    return;\n  }\n\n  if (!InLayoutTree() || !attach_to_tree()) {\n    return;\n  }\n\n  if (needs_layout_) {\n    return;\n  }\n  needs_layout_ = true;\n\n  // Only the actual layout root will handle the layout request.\n  // So a subview will go through its ancestors until a Layout Root is found.\n  if (IsLayoutRootCandidate()) {\n    page_view()->GetLayoutController()->AddNeedLayout(this->GetWeakPtr());\n    page_view()->RequestPaint();\n  } else if (Parent()) {\n    Parent()->MarkNeedsLayout(view_to_layout == nullptr ? this\n                                                        : view_to_layout);\n  }\n}\n\nvoid BaseView::Layout(LayoutContext* context) {\n  needs_layout_ = false;\n  OnLayout(context);\n}\n\nvoid BaseView::OnLayout(LayoutContext* context) {\n  // During the layout process of a page containing a list component,\n  // child elements may be inserted, causing a crash when iterating\n  // over std::vector.\n  for (size_t i = 0; i < children_.size(); i++) {\n    auto child = children_[i];\n    if (child->NeedsLayout()) {\n      child->Layout(context);\n    }\n  }\n}\n\nvoid BaseView::OnContentSizeChanged(const FloatRect& old_rect,\n                                    const FloatRect& new_rect) {\n  if (render_object()->HasBorder()) {\n    auto& border = render_object()->MutableBorder();\n    border.UpdateRadius(width_, height_);\n    render_object()->MarkNeedsPaint();\n  }\n  if (transform_raw_.has_value()) {  // update transform percent values\n    if (transition_mgr_ && transition_mgr_->IsAnimationRunning(\n                               ClayAnimationPropertyType::kTransform)) {\n      transition_mgr_->UpdateAnimationValue(\n          ClayAnimationPropertyType::kTransform,\n          clay::TransformOperations(*transform_raw_, width_, height_));\n    } else {\n      SetProperty(ClayAnimationPropertyType::kTransform,\n                  clay::TransformOperations(*transform_raw_, width_, height_),\n                  false);\n    }\n  }\n\n  if (clip_path_data_.has_value()) {\n    DrawClipPath(true);\n  }\n  if (offset_path_data_.has_value()) {\n    DrawClipPath(false);\n  }\n\n  UpdateChildrenBounds();\n}\n\nvoid BaseView::OnBoundsChanged(const FloatRect& old_bounds,\n                               const FloatRect& new_bounds) {\n  if (keyframes_mgr_ && old_bounds.size() != new_bounds.size()) {\n    keyframes_mgr_->UpdateLayoutSize();\n  }\n  // TODO(wanchen): There may be a better way to deal with incomplete scrolling\n  // of scroll-view\n  if (parent_ != nullptr) {\n    parent_->OnChildSizeChanged(this);\n  }\n\n  UpdateChildrenBounds();\n}\n\nbool BaseView::CanAcceptEvent() const {\n  if (!Visible() || !IsInteractable()) {\n    return false;\n  }\n  auto origin = GetTransformOrigin();\n  Transform transform(GetTransformOps()\n                          .Apply()\n                          .matrix()\n                          .PreTranslate(-origin.x(), -origin.y())\n                          .PostTranslate(origin.x(), origin.y()));\n  // If transform is not invertible, it means the view is not visible with\n  // 2D transform.\n  return transform.IsInvertible();\n}\n\nbool BaseView::HitTest(const PointerEvent& event, HitTestResult& result) {\n  if (!CanAcceptEvent()) {\n    return false;\n  }\n\n  FloatPoint point_by_self = GetPointBySelf(event.position);\n  // Transform translate_transform = LocalToGlobalTransform();\n  // SkVector4 sv(point_by_self.x(), point_by_self.y(), 0, 1);\n  // SkVector4 sv_result = translate_transform.matrix() * sv;\n  // FloatPoint point(sv_result.fData[0], sv_result.fData[1]);\n  //  FML_CHECK(event.position == point);\n\n  bool beyond_self = point_by_self.x() <= -hit_slop_left_ ||\n                     point_by_self.x() > width_ + hit_slop_right_ ||\n                     point_by_self.y() <= -hit_slop_top_ ||\n                     point_by_self.y() > height_ + hit_slop_bottom_;\n  if (beyond_self && !CanChildrenEscape()) {\n    return false;\n  }\n\n  // we should consider the case of overflow visible\n  bool clip_x = (GetOverflow() == CSSProperty::OVERFLOW_Y);\n  bool clip_y = (GetOverflow() == CSSProperty::OVERFLOW_X);\n  if (clip_x && (point_by_self.x() < -hit_slop_left_ ||\n                 point_by_self.x() > width_ + hit_slop_right_)) {\n    return false;\n  }\n\n  if (clip_y && (point_by_self.y() < -hit_slop_top_ ||\n                 point_by_self.y() > height_ + hit_slop_bottom_)) {\n    return false;\n  }\n\n  bool founded = false;\n  RebuildSortedChildrenIfNeeded();\n  for (auto it = sorted_children_.rbegin(); it != sorted_children_.rend();\n       it++) {\n    if ((*it)->HitTest(event, result)) {\n      founded = true;\n      break;  // if a view is hit, skip hitting its siblings\n    }\n  }\n\n  if (beyond_self) {\n    return founded;\n  }\n  should_pass_event_for_hittest_ = ShouldPassEventToNativeInherited(this);\n  result.emplace_back(GetHitTestTargetWeakPtr());\n  return true;\n}\n\nvoid BaseView::HandleEvent(const PointerEvent& event) {\n  if (event.type == PointerEvent::EventType::kDownEvent ||\n      event.type == PointerEvent::EventType::kPanZoomStartEvent) {\n    if (app_region_.compare(attr_value::kAppRegionDrag) == 0) {\n      FloatPoint relative_position;\n      auto top_view =\n          GetTopViewToAcceptEvent(event.position, &relative_position);\n      if (!top_view || (top_view && top_view->IsAppRegionDraggable())) {\n        event_draggable_ = event;\n        can_draggable_ = true;\n      }\n    }\n\n    for (auto& recognizer : gesture_recognizers_) {\n      recognizer->AddPointer(event);\n    }\n  } else if (event.type == PointerEvent::EventType::kMoveEvent ||\n             event.type == PointerEvent::EventType::kPanZoomUpdateEvent) {\n    if (can_draggable_) {\n      float pixel_tolerance = FromLogical(8);\n      float pixel_distance =\n          (event.position - event_draggable_.position).distance();\n      if (pixel_distance > pixel_tolerance) {\n        page_view_->MoveWindow();\n      }\n    }\n  } else {\n    can_draggable_ = false;\n  }\n}\n\nbool BaseView::HasDragGestureRecognizer(ScrollDirection direction) const {\n  GestureRecognizerType type = (direction == ScrollDirection::kVertical)\n                                   ? GestureRecognizerType::kVerticalDrag\n                                   : GestureRecognizerType::kHorizontalDrag;\n  for (auto& recognizer : gesture_recognizers_) {\n    if (recognizer) {\n      if (recognizer->getType() == type ||\n          recognizer->getType() == GestureRecognizerType::kDragGesture) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nbool BaseView::HasTapGestureRecognizer() const {\n  for (auto& recognizer : gesture_recognizers_) {\n    if (recognizer && recognizer->getType() == GestureRecognizerType::kTap) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool BaseView::HasLongPressGestureRecognizer() const {\n  for (auto& recognizer : gesture_recognizers_) {\n    if (recognizer &&\n        recognizer->getType() == GestureRecognizerType::kLongPress) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool BaseView::HasTapEvent() const { return HasEvent(event_attr::kEventTap); }\n\nbool BaseView::HasLongPressEvent() const {\n  return HasEvent(event_attr::kEventLongPress) ||\n         HasEvent(event_attr::kEventMouseLongPress);\n}\n\nvoid BaseView::DestroyChildrenRecursively(BaseView* view) {\n  if (view == nullptr) {\n    return;\n  }\n  if (view != this) {\n    view->Destroy();\n  }\n  auto& children = view->GetChildren();\n  auto iter = children.begin();\n  while (iter != children.end()) {\n    DestroyChildrenRecursively(*iter);\n    // Remove child dangling pointer in parent.\n    iter = children.erase(iter);\n  }\n  if (view != this) {\n    delete view;\n  }\n}\n\nvoid BaseView::OnDetachFromTree() {\n  FML_DCHECK(attach_to_tree_) << \"Node has already detached!\";\n  attach_to_tree_ = false;\n  for (auto& i : children_) {\n    i->OnDetachFromTree();\n  }\n  OnFocusDetach();\n\n  if (IsLayoutRootCandidate()) {\n    page_view_->GetLayoutController()->RemoveDirtyNode(this->GetWeakPtr());\n  }\n\n  if (has_intersection_observer_) {\n    page_view_->intersection_observer_manager()->NotifyTargetDetached(this);\n  }\n}\n\nvoid BaseView::OnAttachToTree() {\n  attach_to_tree_ = true;\n  // The need_layout_ has been set before. Re-trigger it so that we can dirty\n  // the ancestors and re-layout eventually.\n  if (InLayoutTree() && needs_layout_) {\n    needs_layout_ = false;\n  }\n  // TODO(wangchen): mark leaf node needs layout, could do better\n  MarkNeedsLayout();\n\n  for (auto& i : children_) {\n    i->OnAttachToTree();\n  }\n  OnFocusAttach();\n\n  if (has_intersection_observer_) {\n    page_view_->intersection_observer_manager()->NotifyTargetAttached(this);\n  }\n}\n\nvoid BaseView::Invalidate() {\n  render_object()->MarkNeedsPaint();\n#ifdef ENABLE_ACCESSIBILITY\n  if (SemanticsOwner* owner = GetSemanticsOwner()) {\n    owner->AddDirtySemanticsForDescendants(this);\n  }\n#endif\n}\n\nFloatRect BaseView::ContentBoundsInViewport() const {\n  auto location = AbsoluteLocationWithScroll();\n  return FloatRect(location.x() + BorderLeft() + PaddingLeft(),\n                   location.y() + BorderTop() + PaddingTop(), ContentWidth(),\n                   ContentHeight());\n}\n\nFloatRect BaseView::BoundsRelativeTo(BaseView* view) const {\n  auto location = AbsoluteLocationWithScroll();\n  if (view) {\n    location = location - view->AbsoluteLocationWithScroll();\n  }\n  return FloatRect(location.x(), location.y(), Width(), Height());\n}\n\nBaseView* BaseView::GetTopViewToAcceptEvent(const FloatPoint& position,\n                                            FloatPoint* relative_position) {\n  FML_DCHECK(relative_position);\n  if (!BaseView::CanAcceptEvent()) {\n    return nullptr;\n  }\n\n  FloatPoint point_by_self = GetPointBySelf(position);\n  bool is_point_inside = point_by_self.x() >= -hit_slop_left_ &&\n                         point_by_self.x() <= width_ + hit_slop_right_ &&\n                         point_by_self.y() >= -hit_slop_top_ &&\n                         point_by_self.y() <= height_ + hit_slop_bottom_;\n  if (!CanChildrenEscape() && !is_point_inside) {\n    return nullptr;\n  }\n\n  BaseView* view = nullptr;\n\n  RebuildSortedChildrenIfNeeded();\n  for (auto it = sorted_children_.rbegin(); it != sorted_children_.rend();\n       it++) {\n    if ((*it)->IsIndependentSubViewTree()) {\n      continue;\n    }\n    view = (*it)->GetTopViewToAcceptEvent(position, relative_position);\n    if (view) {\n      return view;\n    }\n  }\n\n  // An internally created view (with an id < 0) cannot be the event target.\n  if (is_point_inside && !IsAnonymousView()) {\n    if (IsIndependentSubViewTree() && CanEventsPassThroughToViewsBehind()) {\n      return nullptr;\n    }\n    *relative_position = point_by_self;\n    return CanEventThrough().has_value() && CanEventThrough().value() ? nullptr\n                                                                      : this;\n  }\n  return nullptr;\n}\n\nFloatPoint BaseView::GetPointBySelf(const FloatPoint& point_by_page) const {\n  FloatPoint point = point_by_page;\n  BaseView* parent = Parent();\n  if (IsIndependentSubViewTree()) {\n    parent = page_view();\n  }\n  if (parent) {\n    point = parent->GetPointBySelf(point);\n    FloatPoint offset = parent->GetScrollOffsetForPaint();\n    point.MoveBy(offset);\n  }\n  point.Move(-Left(), -Top());\n  if (sticky_) {\n    point.Move(-sticky_.value().offset_x, -sticky_.value().offset_y);\n  }\n\n  auto origin = GetTransformOrigin();\n  Transform transform(GetTransformOps()\n                          .Apply()\n                          .matrix()\n                          .PreTranslate(-origin.x(), -origin.y())\n                          .PostTranslate(origin.x(), origin.y()));\n  if (!transform.IsIdentity()) {\n    /*\n     * transform direction:\n     * forward: convert coordinates stored in BaseView to coordinates\n     * displayed reverse: convert coordinates displayed to coordinates stored\n     * in BaseView\n     */\n    transform.TransformPointReverse(&point);\n  }\n  return point;\n}\n\nTransform BaseView::LocalToGlobalTransform() const {\n  FloatPoint point(left_, top_);\n  BaseView* parent = Parent();\n  if (IsIndependentSubViewTree()) {\n    parent = page_view();\n  }\n  Transform transform = Transform(skity::Matrix());\n  while (parent) {\n    FloatPoint offset = parent->GetScrollOffset();\n    point.MoveBy(-offset);\n\n    point.Move(parent->Left(), parent->Top());\n    parent = parent->Parent();\n  }\n  transform.Translate(point.x(), point.y());\n  // TODO(wangchen): Consider the transform case\n  return transform;\n}\n\nvoid BaseView::SetVisible(bool visible) {\n  render_object()->SetVisible(visible);\n}\n\nbool BaseView::Visible() const { return render_object()->Visible(); }\n\nvoid BaseView::SetAttribute(const char* attr, const clay::Value& value) {\n  // NOTE: new attributes should be added to `HandleCommonAttribute`\n  HandleCommonAttribute(attr, value);\n}\n\nbool BaseView::HandleCommonAttribute(const char* attr,\n                                     const clay::Value& value) {\n  auto kw = GetKeywordID(attr);\n  switch (kw) {\n    case KeywordID::kIdselector:\n      id_selector_ = utils::GetCString(value);\n      break;\n    case KeywordID::kReactRef:\n      ref_id_selector_ = utils::GetCString(value);\n      break;\n    case KeywordID::kItemKey:\n      item_key_ = utils::GetCString(value);\n      break;\n    case KeywordID::kXAppRegion: {\n      int app_region_type = utils::GetInt(value);\n      if (app_region_type == app_region_value::kAppRegionDrag) {\n        app_region_ = attr_value::kAppRegionDrag;\n      } else if (app_region_type == app_region_value::kAppRegionNoDrag) {\n        app_region_ = attr_value::kAppRegionNoDrag;\n      }\n    } break;\n    case KeywordID::kFocusable:\n      SetFocusable(attribute_utils::GetBool(value));\n      break;\n    case KeywordID::kIgnoreFocus: {\n      auto ignore_focus = attribute_utils::GetBool(value);\n      ignore_focus_ = ignore_focus;\n    } break;\n    case KeywordID::kIntersectionObservers: {\n      auto* manager = page_view_->intersection_observer_manager();\n      if (manager) {\n        // first remove old observer\n        manager->RemoveIntersectionObserver(this);\n        const auto& observers = utils::GetArray(value);\n        for (size_t i = 0; i < observers.size(); ++i) {\n          const auto& map = utils::GetMap(observers[i]);\n          auto observer =\n              std::make_unique<IntersectionObserver>(manager, map, this);\n          manager->AddObserver(std::move(observer));\n          has_intersection_observer_ = true;\n        }\n      }\n    } break;\n    case KeywordID::kFocusIndex:\n      SetFocusIndex(attribute_utils::GetPoint(value));\n      break;\n    case KeywordID::kNextFocusUp:\n      SetNextFocusUp(utils::GetCString(value));\n      break;\n    case KeywordID::kNextFocusDown:\n      SetNextFocusDown(utils::GetCString(value));\n      break;\n    case KeywordID::kNextFocusLeft:\n      SetNextFocusLeft(utils::GetCString(value));\n      break;\n    case KeywordID::kNextFocusRight:\n      SetNextFocusRight(utils::GetCString(value));\n      break;\n    case KeywordID::kNextFocusFallback:\n      SetNextFocusFallback(utils::GetBool(value));\n    case KeywordID::kUserInteractionEnabled:\n      SetInteractable(utils::GetBool(value, true));\n      break;\n    case KeywordID::kBlockNativeEvent:\n      SetBlockNativeEvent(utils::GetBool(value));\n      break;\n    case KeywordID::kConsumeSlideEvent:\n      SetConsumeSlideEventDirection(utils::GetArray(value));\n      break;\n    case KeywordID::kEnableNewAnimator:\n      SetEnableNewAnimator(utils::GetBool(value));\n      break;\n    case KeywordID::kName:\n      name_ = utils::GetCString(value);\n      break;\n    case KeywordID::kDataset:\n      data_set_ = CloneClayValue(value);\n      break;\n    case KeywordID::kEventThrough:\n      event_through_ = utils::GetBool(value);\n      if (event_through_) {\n        auto task_runners = page_view_->GetTaskRunners();\n        if (task_runners.GetPlatformTaskRunner() !=\n            task_runners.GetUITaskRunner()) {\n          FML_LOG(ERROR)\n              << \"event through is only supported in MOST_ON_UI thread mode\";\n        }\n      }\n      break;\n    case KeywordID::kHitSlop:\n      if (value.IsMap()) {\n        const auto& map = value.GetMap();\n        if (map.empty()) {\n          return true;\n        }\n        auto& top = attribute_utils::GetMapItem(map, \"top\");\n        if (top.IsString()) {\n          hit_slop_top_ = attribute_utils::ToPxWithDisplayMetrics(\n              utils::GetCString(top, \"\"), page_view());\n        }\n        auto& left = attribute_utils::GetMapItem(map, \"left\");\n        if (left.IsString()) {\n          hit_slop_left_ = attribute_utils::ToPxWithDisplayMetrics(\n              utils::GetCString(left, \"\"), page_view());\n        }\n        auto& right = attribute_utils::GetMapItem(map, \"right\");\n        if (right.IsString()) {\n          hit_slop_right_ = attribute_utils::ToPxWithDisplayMetrics(\n              utils::GetCString(right, \"\"), page_view());\n        }\n        auto& bottom = attribute_utils::GetMapItem(map, \"bottom\");\n        if (bottom.IsString()) {\n          hit_slop_bottom_ = attribute_utils::ToPxWithDisplayMetrics(\n              utils::GetCString(bottom, \"\"), page_view());\n        }\n      } else if (value.IsString()) {\n        std::string value_with_unit = attribute_utils::GetCString(value, \"\");\n        float slop_value = attribute_utils::ToPxWithDisplayMetrics(\n            value_with_unit, page_view());\n        hit_slop_top_ = slop_value;\n        hit_slop_left_ = slop_value;\n        hit_slop_right_ = slop_value;\n        hit_slop_bottom_ = slop_value;\n      }\n      break;\n    default:\n      // Handle css properties\n      if (CSSProperty::SetAttribute(this, kw, value)) {\n        return true;\n      }\n      // Handle exposure properties\n      if (UpdateExposeAttrs(attr, value)) {\n        return true;\n      }\n      FML_DLOG(ERROR) << \"Setting attribute \" << attr\n                      << \" not supported by component \" << GetName();\n      return false;\n  }\n  return true;\n}\n\ntemplate <typename... Args>\nvoid BaseView::NotifyBgImageLoadStatus(bool success,\n                                       const std::vector<std::string>& keys,\n                                       Args&&... args) {\n  if (success && HasEvent(event_attr::kEventBgImageLoadSuccess)) {\n    page_view()->SendEvent(id(), event_attr::kEventBgImageLoadSuccess, keys,\n                           args...);\n  } else if (!success && HasEvent(event_attr::kEventBgImageLoadError)) {\n    page_view()->SendEvent(id(), event_attr::kEventBgImageLoadError, keys,\n                           args...);\n  }\n}\n\nbool BaseView::UpdateExposeAttrs(const char* attr, const clay::Value& value) {\n  auto kw = GetKeywordID(attr);\n  if (kExposureAttributes.find(kw) == kExposureAttributes.end()) {\n    return false;\n  }\n  auto* manager = page_view_->intersection_observer_manager();\n  if (manager) {\n    ExposeObserver* newAddedObserver = nullptr;\n    if (!manager->HasExposeObserver(this)) {\n      auto observer =\n          std::make_unique<ExposeObserver>(manager, clay::Value::Map(), this);\n      newAddedObserver = observer.get();\n      manager->AddObserver(std::move(observer));\n    }\n    auto result = manager->UpdateExposeData(attr, value, this);\n    if (result) {\n      has_intersection_observer_ = true;\n    } else if (newAddedObserver) {\n      // if is new added observer and operate failed, revert the added\n      // observer\n      manager->RemoveObserver(newAddedObserver);\n    }\n    return result;\n  }\n  return false;\n}\n\nvoid BaseView::AddEventCallback(const char* event_c) {\n  std::string event(event_c);\n  if (!events_) {\n    events_ = std::make_optional<std::vector<std::string>>();\n  }\n  if (event.compare(event_attr::kEventLayoutChange) == 0) {\n    enable_layout_change_event_ = true;\n  } else {\n    // Handle exposure properties\n    if (UpdateExposeAttrs(event_c, {})) {\n      return;\n    }\n  }\n  events_->emplace_back(event);\n}\n\n#ifndef NDEBUG\nvoid BaseView::DumpViewTree(int depth) const {\n  std::string intent;\n  for (int i = 0; i < depth; ++i) {\n    intent.append((i == depth - 1) ? \"|-\" : \"| \");\n  }\n  FML_LOG(ERROR) << intent << \"[\" << GetName() << \"] \" << this << ToString();\n  for (BaseView* child : children_) {\n    child->DumpViewTree(depth + 1);\n  }\n}\n\nstd::string BaseView::ToString() const {\n  std::stringstream ss;\n  ss << \" name=\" << GetName();\n  ss << \" id=\" << id();\n  ss << \" frame=(\" << Left() << \",\" << Top() << \",\" << Width() << \",\"\n     << Height() << \")\";\n  if (!GetScrollOffset().IsOrigin()) {\n    ss << \" scroll_offset=\" << GetScrollOffset().ToString();\n  }\n  if (PaddingLeft() != 0 || PaddingTop() != 0 || PaddingRight() != 0 ||\n      PaddingBottom() != 0) {\n    ss << \" padding=(\" << PaddingLeft() << \",\" << PaddingTop() << \",\"\n       << PaddingRight() << \",\" << PaddingBottom() << \")\";\n  }\n  if (!id_selector_.empty()) {\n    ss << \" id_selector=\" << id_selector_;\n  }\n  if (!Visible()) {\n    ss << \" hidden\";\n  }\n  return ss.str();\n}\n#endif\n\nvoid BaseView::setFocus(const LynxModuleValues& args,\n                        const LynxUIMethodCallback& callback) {\n  bool focus = IsFocused();\n  bool scroll = true;\n  CastNamedLynxModuleArgs({\"focus\", \"scroll\"}, args, focus, scroll);\n\n  if (focus) {\n    RequestFocus();\n    if (scroll) {\n      ScrollToFocus();\n    }\n  } else {\n    ClearFocus();\n  }\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid BaseView::interceptBackKeyOnce(const LynxModuleValues& args,\n                                    const LynxUIMethodCallback& callback) {\n  page_view()->SetInterceptBackKeyOnce(true);\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid BaseView::cancelInterceptBackKey(const LynxModuleValues& args,\n                                      const LynxUIMethodCallback& callback) {\n  page_view()->SetInterceptBackKeyOnce(false);\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid BaseView::boundingClientRect(const LynxModuleValues& args,\n                                  const LynxUIMethodCallback& callback) {\n  std::string target_view = utils::GetCString(args.Get(\"relativeTo\"), \"\");\n  BaseView* relative_to = nullptr;\n  if (target_view != \"\") {\n    if (!target_view.empty() && target_view[0] == '#') {\n      target_view.erase(0, 1);\n    }\n    relative_to = page_view()->FindViewByIdSelector(target_view);\n  }\n  auto rect = BoundsRelativeTo(relative_to);\n\n  clay::Value::Map map;\n  map[\"id\"] = clay::Value(GetIdSelector());\n  map[\"left\"] = clay::Value(ToLogical(rect.x()));\n  map[\"right\"] = clay::Value(ToLogical(rect.MaxX()));\n  map[\"top\"] = clay::Value(ToLogical(rect.y()));\n  map[\"bottom\"] = clay::Value(ToLogical(rect.MaxY()));\n  map[\"width\"] = clay::Value(ToLogical(rect.width()));\n  map[\"height\"] = clay::Value(ToLogical(rect.height()));\n  map[\"dataset\"] = CloneClayValue(data_set_);\n  callback(LynxUIMethodResult::kSuccess, clay::Value(std::move(map)));\n}\n\nvoid BaseView::scrollIntoView(const LynxModuleValues& args,\n                              const LynxUIMethodCallback& callback) {\n  if (!args.HasKey(\"scrollIntoViewOptions\")) {\n    callback(LynxUIMethodResult::kParamInvalid, clay::Value());\n  }\n  const auto& scroll_into_view_options = args.Get(\"scrollIntoViewOptions\");\n  const auto& map = utils::GetMap(scroll_into_view_options);\n  auto block = utils::GetCString(utils::GetMapItem(map, \"block\"));\n  auto inline_mode = utils::GetCString(utils::GetMapItem(map, \"inline\"));\n  auto behavior = utils::GetCString(utils::GetMapItem(map, \"behavior\"));\n\n  BaseView* node = this;\n  while (node->Parent()) {\n    if (node->Is<ScrollView>()) {\n      static_cast<ScrollView*>(node)->StartScrollInto(this, block, inline_mode,\n                                                      behavior);\n      break;\n    }\n    node = node->Parent();\n  }\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid BaseView::takeScreenshot(const LynxModuleValues& args,\n                              const LynxUIMethodCallback& callback) {\n  auto format = utils::GetCString(args.Get(\"format\"), \"png\");\n  auto scale = utils::GetNum(args.Get(\"scale\"), 1.0);\n  if (scale <= 0 || scale > 1.0) {\n    callback(LynxUIMethodResult::kParamInvalid, clay::Value());\n    return;\n  }\n\n  // 1. Make sure this View is repaint boundary;\n  // 2. Call PageView::MakeRasterSnapshot;\n  // 3. Gen sub LayerTree with FrameBuilder from the current View, apply the\n  //    scale using a TransformLayer;\n  // 4. Call Rasterizer::MakeRasterSnapshot with subtree, and get the result\n  //    dlimage;\n  // 5. Encode the dlimage to png or jpeg;\n  // 6. Convert to base64 and return it to the Frontend.\n  BaseView* node = this;\n  node->SetRepaintBoundary(true);\n  bool compress_jpeg = format == \"jpeg\";\n  page_view_->MakeRasterSnapshot(\n      node, compress_jpeg, scale,\n      [compress_jpeg, scale, callback](GrDataPtr data, int32_t w, int32_t h) {\n        if (!data) {\n          callback(LynxUIMethodResult::kOperationError, clay::Value());\n          return;\n        }\n#ifndef ENABLE_SKITY\n        size_t length =\n            fml::Base64::Encode(data->data(), data->size(), nullptr);\n        std::vector<char> base64_data(length);\n        fml::Base64::Encode(data->data(), data->size(), base64_data.data());\n#else\n        size_t length =\n            fml::Base64::Encode(data->RawData(), data->Size(), nullptr);\n        std::vector<char> base64_data(length);\n        fml::Base64::Encode(data->RawData(), data->Size(), base64_data.data());\n#endif  // ENABLE_SKITY\n        std::string result_data(compress_jpeg ? \"data:image/jpeg;base64,\"\n                                              : \"data:image/png;base64,\");\n        result_data.append(base64_data.begin(), base64_data.end());\n        clay::Value::Map map;\n        map[\"width\"] = clay::Value(w);\n        map[\"height\"] = clay::Value(h);\n        map[\"data\"] = clay::Value(result_data);\n        callback(LynxUIMethodResult::kSuccess, clay::Value(std::move(map)));\n      });\n}\n\nvoid BaseView::ScrollToFocus() {\n  FloatRect focused_view_rect = GetFocusManager()->GetFocusedNodeRect();\n  if (!focused_view_rect.IsEmpty()) {\n    ScrollChildViewToVisible(focused_view_rect);\n    if (Parent()) {\n      Parent()->ScrollToFocus();\n    }\n  }\n}\n\nvoid BaseView::OnAnimationEvent(const AnimationParams& animation_params) {\n  bool has_event = false;\n  auto event_type = animation_params.event_type;\n  switch (event_type) {\n    case kClayEventTypeAnimationStart:\n      has_event = HasEvent(event_attr::kEventAnimationStart);\n      break;\n    case kClayEventTypeAnimationRepeat:\n      has_event = HasEvent(event_attr::kEventAnimationIteration);\n      break;\n    case kClayEventTypeAnimationEnd:\n      has_event = HasEvent(event_attr::kEventAnimationEnd);\n      break;\n    case kClayEventTypeAnimationCancel:\n      has_event = HasEvent(event_attr::kEventAnimationCancel);\n      break;\n    default:\n      FML_DCHECK(false);\n      return;\n  }\n\n  if (has_event && !(page_view_->IsRasterAnimationEnabled() &&\n                     page_view_->GetKeyframesMap(\n                         animation_params.animation_name) != nullptr)) {\n    page_view()->DispatchAnimationEvent(animation_params, GetCallbackId());\n  }\n}\n\nbool BaseView::HasAnimationEvent(const ClayEventType& event_type) const {\n  std::string event;\n  switch (event_type) {\n    case kClayEventTypeAnimationStart:\n      event = event_attr::kEventAnimationStart;\n      break;\n    case kClayEventTypeAnimationRepeat:\n      event = event_attr::kEventAnimationIteration;\n      break;\n    case kClayEventTypeAnimationEnd:\n      event = event_attr::kEventAnimationEnd;\n      break;\n    case kClayEventTypeAnimationCancel:\n      event = event_attr::kEventAnimationCancel;\n      break;\n    case kClayEventTypeTransitionStart:\n      event = event_attr::kEventTransitionStart;\n      break;\n    case kClayEventTypeTransitionEnd:\n      event = event_attr::kEventTransitionEnd;\n      break;\n    default:\n      return false;\n  }\n  return !event.empty() && HasEvent(event);\n}\n\nvoid BaseView::OnTransitionEvent(const AnimationParams& animation_params,\n                                 ClayAnimationPropertyType property_type) {\n  bool has_event = false;\n  auto event_type = animation_params.event_type;\n  switch (event_type) {\n    case kClayEventTypeTransitionStart:\n      has_event = HasEvent(event_attr::kEventTransitionStart);\n      break;\n    case kClayEventTypeTransitionEnd:\n      has_event = HasEvent(event_attr::kEventTransitionEnd);\n      break;\n    default:\n      FML_DCHECK(false);\n      return;\n  }\n#ifdef ENABLE_RASTER_CACHE_SCALE\n  UpdateCacheStrategy();\n#endif\n  if (has_event && !(page_view_->IsRasterAnimationEnabled() &&\n                     IsRasterAnimationProperty(property_type))) {\n    page_view()->DispatchTransitionEvent(animation_params, GetCallbackId(),\n                                         property_type);\n  }\n}\n\nvoid BaseView::OnMouseEnter(const PointerEvent& event) {\n  is_mouse_hover_ = true;\n  OnMouseHoverChange();\n  OnMouseEvent(kClayEventTypeMouseEnter, event);\n}\n\nvoid BaseView::OnMouseLeave(const PointerEvent& event) {\n  is_mouse_hover_ = false;\n  OnMouseHoverChange();\n  OnMouseEvent(kClayEventTypeMouseLeave, event);\n}\n\nvoid BaseView::OnMouseHover(const PointerEvent& event) {\n  OnMouseEvent(kClayEventTypeMouseOver, event);\n}\n\nvoid BaseView::MarkDirty() { Invalidate(); }\n\nvoid BaseView::OnMouseHoverChange() {\n  if (page_view_->GetEventDelegate()) {\n    page_view_->GetEventDelegate()->OnHoverChanged(GetCallbackId(),\n                                                   is_mouse_hover_);\n  }\n}\n\nvoid BaseView::OnMouseEvent(const ClayEventType type,\n                            const PointerEvent& event) {\n  if (page_view_->GetEventDelegate()) {\n    auto position = event.position;\n    FloatPoint view_point = GetPointBySelf(position);\n    // In flutter, event has no `button` property.\n    // In web, only `buttons` has possible value for mouseenter/mouseleave.\n    int buttons = type == kClayEventTypeMouseOver ? 0 : event.buttons;\n    page_view_->GetEventDelegate()->OnMouseEvent(\n        EventTypeToString(type), GetCallbackId(), 0, buttons, 1, view_point.x(),\n        view_point.y(), position.x(), position.y());\n  }\n}\n\nvoid BaseView::OnLayoutChange() {\n  if (enable_layout_change_event_ && page_view_->GetEventDelegate()) {\n    const auto& id_selector = GetIdSelector();\n    FloatRect rect = page_view_->ConvertTo<kPixelTypeLogical>(GetBounds());\n    auto map = CreateClayMap(\n        {\"id\", \"left\", \"right\", \"top\", \"bottom\", \"width\", \"height\"},\n        id_selector.c_str(), rect.x(), rect.MaxX(), rect.y(), rect.MaxY(),\n        rect.width(), rect.height());\n    page_view_->GetEventDelegate()->OnLayoutChanged(GetCallbackId(),\n                                                    std::move(map));\n  }\n}\n\nvoid BaseView::LayoutUpdated() {\n  if (NeedsLayoutUpdated()) {\n    needs_layout_updated_ = false;\n    OnLayoutUpdated();\n  }\n  for (auto& i : children_) {\n    i->LayoutUpdated();\n  }\n}\n\nvoid BaseView::UpdateCacheStrategy() {\n  auto running_animators = TransitionMgr()->GetRunningAnimators();\n  bool force_cache = false;\n  for (auto animator : running_animators) {\n    if (animator->GetDuration() <= FORCE_CACHE_ANIMATION_DURATION) {\n      force_cache = true;\n    } else {\n      force_cache = false;\n      break;\n    }\n  }\n  // Update self and descendant render object's cache strategy.\n  if (force_cache) {\n    render_object()->SetCacheStrategyRecursively(CacheStrategy::ForceCache);\n  } else if (render_object()->GetCacheStrategy() == CacheStrategy::ForceCache) {\n    render_object()->SetCacheStrategyRecursively(CacheStrategy::NotCache);\n  }\n}\n\nvoid BaseView::EndAllTransitionsRecursively() {\n  if (transition_mgr_ && transition_mgr_->HasAnimationRunning()) {\n    transition_mgr_->EndAllAnimators();\n  }\n  for (auto* child : children_) {\n    child->EndAllTransitionsRecursively();\n  }\n}\n\nvoid BaseView::GetTransformValue(float left, float right, float top,\n                                 float bottom, TransOffset& res) {\n  GetLocationOnScreen(left, top, res.left_top);\n  GetLocationOnScreen(width_ + right, top, res.right_top);\n  GetLocationOnScreen(width_ + right, height_ + bottom, res.right_bottom);\n  GetLocationOnScreen(left, height_ + bottom, res.left_bottom);\n}\n\nvoid BaseView::GetLocationOnScreen(float in_out_location_x,\n                                   float in_out_location_y,\n                                   std::vector<float>& res) {\n  // TODO(wangyanyi): not considerate node which may be detached and overlay\n  res = TransformFromViewToRootView(this, in_out_location_x, in_out_location_y);\n}\n\nstd::vector<float> BaseView::TransformFromViewToRootView(\n    BaseView* view, float& in_out_location_x, float& in_out_location_y) {\n  auto float_point = FloatPoint(in_out_location_x, in_out_location_y);\n  Transform transform = Transform();\n  if (view->render_object()->HasTransform()) {\n    transform = view->render_object()->GetTransform();\n  }\n  if (!transform.IsIdentity()) {\n    transform.TransformPoint(&float_point);\n  }\n\n  while (view->Parent() && view != page_view_) {\n    auto parent_view = view->Parent();\n\n    float_point.SetX(float_point.x() + view->Left() -\n                     parent_view->render_object()->ScrollLeft());\n    float_point.SetY(float_point.y() + view->Top() -\n                     parent_view->render_object()->ScrollTop());\n\n    auto parent_transform = parent_view->GetTransform();\n    if (!parent_transform.IsIdentity()) {\n      parent_transform.TransformPoint(&float_point);\n    }\n\n    view = parent_view;\n  }\n  return std::vector<float>{float_point.x(), float_point.y()};\n}\n\nvoid BaseView::UpdateTransitionRasterAnimation(ClayAnimationPropertyType type) {\n  if (!IsRasterAnimationEnabled()) {\n    Invalidate();\n    return;\n  }\n  if (IsRasterAnimationProperty(type)) {\n    bool has_animation = TransitionMgr()->IsAnimationRunning(type);\n    render_object_->MarkHasTransition(type, has_animation);\n    if (has_animation) {\n      render_object_->SetTransitionManager(transition_mgr_.get());\n      SetRepaintBoundary(true);\n    }\n    Invalidate();\n  }\n}\n\nvoid BaseView::UpdateKeyframesRasterAnimation() {\n  if (!IsRasterAnimationEnabled()) {\n    Invalidate();\n    return;\n  }\n  bool keyframes_manager_set = false;\n  bool raster_animation_changed = false;\n  ForEachRasterAnimationProperty(\n      [this, &keyframes_manager_set,\n       &raster_animation_changed](ClayAnimationPropertyType type) {\n        bool has_animation = KeyframesMgr()->HasAnimationForType(type);\n        raster_animation_changed =\n            (render_object_->HasAnimation(type) == has_animation) ||\n            raster_animation_changed;\n        render_object_->MarkHasAnimation(type, has_animation);\n        if (has_animation && !keyframes_manager_set) {\n          render_object_->SetKeyframesManager(keyframes_mgr_.get());\n          SetRepaintBoundary(true);\n          keyframes_manager_set = true;\n        }\n      });\n  if (raster_animation_changed) {\n    Invalidate();\n  }\n}\n\nvoid BaseView::UpdateRenderObjectTransformOrigin() {\n  if (transform_origin_.has_value()) {\n    render_object()->SetTransformOrigin(\n        (*transform_origin_).GetValue(width_, height_));\n  } else {\n    render_object()->SetTransformOrigin({width_ / 2.0f, height_ / 2.0f});\n  }\n}\n\nbool BaseView::IsRasterAnimationEnabled() const {\n  return page_view_->IsRasterAnimationEnabled();\n}\n\nvoid BaseView::UpdateSticky(std::optional<StickyInfo> sticky) {\n  if (!sticky.has_value()) {\n    sticky_ = std::nullopt;\n    return;\n  }\n  sticky_ = sticky;\n\n  if (Parent() && Parent()->Is<ScrollView>()) {\n    static_cast<ScrollView*>(Parent())->EnableSticky();\n  }\n}\n\nvoid BaseView::CheckStickyOnParentScrollAndReset(int left, int top) {\n  if (!sticky_.has_value()) {\n    return;\n  }\n\n  float current_left = Left() - left;\n  float current_top = Top() - top;\n  if (current_left < sticky_->left) {\n    sticky_->offset_x = sticky_->left - current_left;\n  } else {\n    int parent_width = Parent()->Width();\n    float current_right = current_left + Width();\n    if (current_right + sticky_->right > parent_width) {\n      sticky_->offset_x =\n          std::max(parent_width - current_right - sticky_->right,\n                   sticky_->left - current_left);\n    } else {\n      sticky_->offset_x = 0;\n    }\n  }\n\n  if (current_top < sticky_->top) {\n    sticky_->offset_y = sticky_->top - current_top;\n  } else {\n    int parent_height = Parent()->Height();\n    float current_bottom = current_top + Height();\n    if (current_bottom + sticky_->bottom > parent_height) {\n      sticky_->offset_y =\n          std::max(parent_height - current_bottom - sticky_->bottom,\n                   sticky_->top - current_top);\n    } else {\n      sticky_->offset_y = 0;\n    }\n  }\n  post_translation_->SetX(sticky_->offset_x);\n  post_translation_->SetY(sticky_->offset_y);\n  TransformOperations transform_ops;\n  transform_ops.AppendTranslate(sticky_->offset_x, sticky_->offset_y, 0);\n  transform_ops.Append(transform_ops_);\n  float old_translate_z = render_object()->GetTranslateZ();\n  render_object()->SetTransformOperations(transform_ops, false);\n  if (std::abs(render_object()->GetTranslateZ() - old_translate_z) > 1e-6) {\n    Parent()->DirtyChildrenPaintingOrder();\n  }\n}\n#ifdef ENABLE_ACCESSIBILITY\nvoid BaseView::MarkRebuildSemanticsTree() {\n  if (auto* semantics_owner = GetSemanticsOwner()) {\n    if (!semantics_owner->NeedRebuildSemanticsTree()) {\n      semantics_owner->SetRebuildSemanticsTree(true);\n    }\n  }\n}\n\nvoid BaseView::UpdateSemantics(\n    const std::vector<fml::RefPtr<SemanticsNode>>& new_children,\n    BaseView* parent_node_view, bool need_check_children, bool force_update) {\n  FML_DCHECK(semantics_);\n  UpdateSemanticsData(parent_node_view);\n\n  semantics_->UpdateWith(new_children, need_check_children, force_update);\n\n  // If label is \"\", this semantics_node will use its children's labels instead.\n  if (page_view() != this && semantics_->GetSemanticsData().label.empty()) {\n    semantics_->GetSemanticsData().label =\n        semantics_->GetAccessibilityLabelWithChildren();\n    if (!semantics_->GetSemanticsData().label.empty()) {\n      semantics_->MarkDirty();\n    }\n  }\n}\n\nFloatRect BaseView::GetSemanticsBounds() const {\n  auto semantics_bounds = GetBounds();\n  float left = left_;\n  float top = top_;\n  auto* parent = Parent();\n  while (parent) {\n    left += parent->Left();\n    top += parent->Top();\n    if (parent->Is<ScrollView>()) {\n      auto delta = static_cast<ScrollView*>(parent)->GetScrollOffset();\n      left -= delta.x();\n      top -= delta.y();\n    }\n    parent = parent->Parent();\n  }\n  semantics_bounds.SetX(left);\n  semantics_bounds.SetY(top);\n  return semantics_bounds;\n}\n\nvoid BaseView::UpdateSemanticsData(BaseView* parent_node_view) {\n  FML_DCHECK(semantics_);\n  // Keep current data to old data, so that we can easily compare if\n  // SemanticsData has changed.\n  semantics_->TransferCurrentData();\n\n  semantics_->GetSemanticsData().actions = GetSemanticsActions();\n  semantics_->GetSemanticsData().flags = GetSemanticsFlags();\n  semantics_->GetSemanticsData().scroll_children = GetA11yScrollChildren();\n  // CAUTION: SemanticsBounds need to be the global bounds to PageView.\n  semantics_->GetSemanticsData().semantics_bounds = GetSemanticsBounds();\n  semantics_->GetSemanticsData().transform =\n      AccumulateTransformFromView(parent_node_view);\n\n  semantics_->GetSemanticsData().accessibility_elements =\n      accessibility_elements_.value_or(std::vector<std::string>());\n\n  semantics_->GetSemanticsData().id_selector = GetIdSelector();\n\n  semantics_->GetSemanticsData().label = GetAccessibilityLabel();\n}\n\nint32_t BaseView::GetA11yScrollChildren() const { return 0; }\n\nint32_t BaseView::GetSemanticsActions() const {\n  int32_t actions = 0;\n  if (HasTapEvent() || HasTapGestureRecognizer()) {\n    actions |= static_cast<int32_t>(SemanticsNode::SemanticsAction::kTap);\n  }\n  if (HasLongPressEvent() || HasLongPressGestureRecognizer()) {\n    actions |= static_cast<int32_t>(SemanticsNode::SemanticsAction::kLongPress);\n  }\n  if (Parent() && Parent()->Is<ScrollView>()) {\n    if (static_cast<ScrollView*>(Parent())->CanScrollX()) {\n      actions |=\n          static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollLeft) |\n          static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollRight);\n    } else if (static_cast<ScrollView*>(Parent())->CanScrollY()) {\n      actions |=\n          static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollUp) |\n          static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollDown);\n    }\n  }\n  return actions;\n}\n\nint32_t BaseView::GetSemanticsFlags() const {\n  int32_t flags = 0;\n  if (Parent() && Parent()->Is<ScrollView>()) {\n    flags |= static_cast<int32_t>(\n        SemanticsNode::SemanticsFlag::kHasImplicitScrolling);\n  }\n  return flags;\n}\n\nstd::u16string BaseView::GetAccessibilityLabel() const {\n  return accessibility_label_.value_or(u\"\");\n}\n\nTransform BaseView::AccumulateTransformFromView(BaseView* view) const {\n  Transform total_transform = render_object()->GetTransform();\n  BaseView* parent = Parent();\n  while (view != parent && parent) {\n    total_transform.ConcatTransform(parent->GetTransform());\n    parent = parent->Parent();\n  }\n  return total_transform;\n}\n\nvoid BaseView::PrepareSemantics(\n    fml::RefPtr<SemanticsNode> parent_node,\n    std::vector<fml::RefPtr<SemanticsNode>>& result,\n    const std::vector<std::string>& ancestor_a11y_elements) {\n  FML_DCHECK(GetSemanticsOwner() &&\n             GetSemanticsOwner()->NeedRebuildSemanticsTree());\n  FML_DCHECK(Parent());\n  // This view is not a11y visible when its ancestors have specified\n  // accessibility-elements and its id_selector is not inside it.\n  if (!IsAccessibilityElement() ||\n      (ancestor_a11y_elements.size() > 0 &&\n       (id_selector_.empty() ||\n        std::find(ancestor_a11y_elements.begin(), ancestor_a11y_elements.end(),\n                  id_selector_) == ancestor_a11y_elements.end()))) {\n    for (auto* view : children_) {\n      view->PrepareSemantics(parent_node, result, ancestor_a11y_elements);\n    }\n    return;\n  }\n  FML_DCHECK(parent_node);\n  if (!semantics_) {\n    FML_DCHECK(id() > 0);\n    semantics_ = fml::MakeRefCounted<SemanticsNode>(this, id());\n  }\n  if (!semantics_->Attached()) {\n    semantics_->Attach(parent_node->Owner());\n  }\n  std::vector<fml::RefPtr<SemanticsNode>> new_children;\n  std::vector<std::string> a11y_elements =\n      accessibility_elements_.value_or(ancestor_a11y_elements);\n  for (auto* view : children_) {\n    view->PrepareSemantics(semantics_, new_children, a11y_elements);\n  }\n  UpdateSemantics(new_children, parent_node->OwnerView(), true, true);\n  result.push_back(semantics_);\n}\n\nbool BaseView::IsAccessibilityElement() const {\n  FML_DCHECK(page_view());\n  // If talkback or voiceover is not enabled, there is no need to regard any\n  // nodes to be accessible.\n  if (!page_view()->IsSemanticsEnabled()) {\n    return false;\n  }\n  // Text and Image are not affected by lynx page config\n  // `enableAccessibilityElement`.\n  if (Is<BaseImageView>() || Is<BaseTextView>() || Is<EditableView>() ||\n      Is<TextAreaView>() || Is<TextAreaNGView>()) {\n    return accessibility_element_.value_or(true);\n  }\n  // Only views created from lynx should be visible to a11y system.\n  // RawTextView is also internal view but id is not -1.\n  if (IsInternalView() || Is<RawTextView>()) {\n    return false;\n  }\n  return accessibility_element_.value_or(\n      page_view() && page_view()->IsPageEnableAccessibilityElement());\n  return false;\n}\n\nvoid BaseView::SetAccessibilityElement(bool value) {\n  if (accessibility_element_ != value) {\n    accessibility_element_ = value;\n    // Since a11y visibility has changed, we need to rebuild the semantics\n    // tree.\n    MarkRebuildSemanticsTree();\n    page_view()->RequestNewFrameForSemantics();\n  }\n}\n\nvoid BaseView::SetAccessibilityLabel(const std::string& value) {\n  std::u16string value_u16 = UnicodeUtil::Utf8ToUtf16(value);\n  if (accessibility_label_ != value_u16) {\n    accessibility_label_ = value_u16;\n    if (auto* semantics_owner = GetSemanticsOwner()) {\n      semantics_owner->AddDirtySemanticsForDescendants(this);\n    }\n    page_view()->RequestNewFrameForSemantics();\n  }\n}\n\nvoid BaseView::SetAccessibilityElements(const std::string& value) {\n  accessibility_elements_ =\n      lynx::base::SplitString<std::string, std::string>(value, \",\");\n  MarkRebuildSemanticsTree();\n  page_view()->RequestNewFrameForSemantics();\n}\n\nSemanticsOwner* BaseView::GetSemanticsOwner() const {\n  return page_view()->GetSemanticsOwner();\n}\n#endif\n\nvoid BaseView::VisitChildren(const std::function<void(BaseView*)>& visitor) {\n  std::vector<BaseView*> children_copy(children_.begin(), children_.end());\n  for (auto* child : children_copy) {\n    visitor(child);\n    child->VisitChildren(visitor);\n  }\n}\n\nvoid BaseView::RebuildSortedChildrenIfNeeded() {\n  if (!sorted_children_.empty()) {\n    return;\n  }\n\n  sorted_children_ = GetChildren();\n  bool needs_reorder = false;\n  for (auto* child : sorted_children_) {\n    if (child->GetTranslateZ() || child->GetPaintingOrder()) {\n      needs_reorder = true;\n      break;\n    }\n  }\n\n  if (!needs_reorder) {\n    return;\n  }\n\n  std::stable_sort(\n      sorted_children_.begin(), sorted_children_.end(),\n      [](BaseView* node1, BaseView* node2) {\n        if (node1->GetTranslateZ() < node2->GetTranslateZ()) {\n          return true;\n        } else if (node1->GetTranslateZ() == node2->GetTranslateZ()) {\n          return node1->GetPaintingOrder() < node2->GetPaintingOrder();\n        }\n        return false;\n      });\n}\n\nfloat BaseView::FromLogical(float value) const {\n  return page_view()->ConvertFrom<kPixelTypeLogical>(value);\n}\n\nfloat BaseView::ToLogical(float value) const {\n  return page_view()->ConvertTo<kPixelTypeLogical>(value);\n}\n\nvoid BaseView::UpdateInlineImageInfo() {\n  for (auto child : GetChildren()) {\n    if (child->Is<InlineImageView>()) {\n      auto image = static_cast<InlineImageView*>(child);\n      child->SetX(image->GetLocation().x() + ContentInsetLeft());\n      child->SetY(image->GetLocation().y() + ContentInsetLeft());\n    } else {\n      child->UpdateInlineImageInfo();\n    }\n  }\n}\n\nvoid BaseView::DecodeImagesRecursively() {\n  VisitChildren([](BaseView* view) { view->render_object()->DecodeImages(); });\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/base_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_BASE_VIEW_H_\n#define CLAY_UI_COMPONENT_BASE_VIEW_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <variant>\n#include <vector>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/gfx/animation/animation_handler.h\"\n#include \"clay/gfx/animation/animator_target.h\"\n#include \"clay/gfx/animation/transition_data.h\"\n#include \"clay/gfx/geometry/box_shadow_operations.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/geometry/path.h\"\n#include \"clay/gfx/geometry/sticky_info.h\"\n#include \"clay/gfx/geometry/transform.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n#include \"clay/gfx/geometry/transform_origin.h\"\n#include \"clay/gfx/geometry/transform_raw.h\"\n#include \"clay/gfx/style/borders_data.h\"\n#include \"clay/gfx/style/length.h\"\n#include \"clay/gfx/style/outline_data.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/ui/common/background_data.h\"\n#include \"clay/ui/common/type_info.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/component/focus_event_handler.h\"\n#include \"clay/ui/component/focus_node.h\"\n#include \"clay/ui/component/key_event_handler.h\"\n#include \"clay/ui/component/layout_controller.h\"\n#include \"clay/ui/component/mouse_cursor.h\"\n#include \"clay/ui/gesture/gesture_manager.h\"\n#include \"clay/ui/gesture/gesture_recognizer.h\"\n#include \"clay/ui/gesture/hit_test.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n#ifdef ENABLE_ACCESSIBILITY\n#include \"clay/ui/semantics/semantics_node.h\"\n#endif\n\n#define SEND_EVENT(...) page_view()->SendEvent(id(), __VA_ARGS__);\n\n#define UI_METHOD_DEF(name)                     \\\n  void name(const clay::LynxModuleValues& args, \\\n            const clay::LynxUIMethodCallback& callback);\n\nnamespace clay {\nclass BaseViewAnimationMutator;\nclass InlineSpecStyles;\nclass KeyEvent;\nclass KeyframesManager;\nclass PageView;\nclass RenderObject;\nclass Scrollable;\nclass FilterOperations;\nstruct PointerEvent;\nstruct Shadow;\nclass TransitionManager;\nstruct TransOffset;\n\nclass BaseView : public TypeIdentifiable<BaseView>,\n                 public HitTestable,\n                 public HitTestTarget,\n                 public KeyEventHandler,\n                 public FocusEventHandler,\n                 public FocusNode {\n public:\n  BaseView(std::unique_ptr<RenderObject> render_object, PageView* page_view);\n\n  BaseView(int id, std::string tag, std::unique_ptr<RenderObject> render_object,\n           PageView* page_view);\n\n  ~BaseView() override;\n\n  void SetDestructListener(const std::function<void(BaseView*)>& func) {\n    destruct_listener_ = func;\n  }\n\n  bool ShouldPassEventToNative() const override {\n    return should_pass_event_for_hittest_;\n  }\n\n  bool IsAppRegionDraggable();\n  // TODO(wangchen) to do better\n  bool IsInternalView() const { return id_ == -1; }\n  // Override the destroy function to delete the custom children if they are not\n  // in the view tree\n  void Destroy();\n  virtual void AddChild(BaseView* child);\n  virtual void AddChild(BaseView* child, int index);\n  virtual void RemoveChild(BaseView* child);\n  // Remove the view and do not trigger the detach lifecycle. You MUST re-add\n  // the view to the tree subsequently. This is useful for moving a view to a\n  // new place without triggering the detach lifecycle, which could lead to\n  // focus loss or other problems.\n  void RemoveChildTemporarily(BaseView* child);\n  virtual void DestroyAllChildren();\n  void BringChildToFront(BaseView* child);\n\n  virtual void OnEnterForeground() {}\n  virtual void OnEnterBackground() {}\n\n  virtual void OnMouseHoverChange();\n  void OnMouseEvent(const ClayEventType type, const PointerEvent& event);\n\n  // For some internally created view events, the callback id needs to be\n  // returned.\n  virtual int GetCallbackId() { return id_; }\n  // add for layoutchange event\n  void OnLayoutChange();\n  virtual void OnLayoutUpdated() {}\n  virtual FloatPoint GetScrollOffset() const { return FloatPoint(); }\n  // FIXME(ZhuChengCheng) this is a workaround method for rtl scrollview, we\n  // will fix this soon by refactoring scrollview's rlt logic.\n  virtual FloatPoint GetScrollOffsetForPaint() const {\n    return GetScrollOffset();\n  }\n\n  virtual void OnNodeReady() {}\n\n  void UpdateInlineImageInfo();\n\n  void OnMouseEnter(const PointerEvent& event);\n  void OnMouseLeave(const PointerEvent& event);\n  void OnMouseHover(const PointerEvent& event);\n\n  void MarkDirty();\n\n  void SetFilterOperations(const FilterOperations& value);\n\n  // Some components may have child order which is diffferent from insertions\n  // like view-pager\n  virtual int GetChildIndex(BaseView* child);\n\n  float GetTranslateZ() const;\n\n  void SetPaintingOrder(int value);\n  int GetPaintingOrder() const;\n\n  void SetID(int id);\n  int id() const { return id_; }\n  const std::string& GetName() const { return tag_; }\n  std::vector<BaseView*>& GetChildren() { return children_; }\n  size_t child_count() const { return children_.size(); }\n\n  // An anonymous view is a view created internally and does not have a\n  // corresponding Lynx element.\n  bool IsAnonymousView() const { return id_ < 0; }\n\n  const std::string& GetComponentName() const { return name_; }\n  void SetComponentName(std::string name) { name_ = std::move(name); }\n\n  const std::string& GetIdSelector() const { return id_selector_; }\n  void SetIdSelector(std::string selector) {\n    id_selector_ = std::move(selector);\n  }\n\n  const std::string& GetRefIdSelector() const { return ref_id_selector_; }\n\n  BaseView* Parent() const { return parent_; }\n\n  // Check if `a_view` is descendant of the current node.\n  // Note: if a_view is the current view, return true.\n  bool IsDescendant(BaseView* a_view) const;\n\n  void SetRepaintBoundary(bool repaint_boundary);\n  virtual void SetDirection(int type){};\n  virtual void SetX(float x);\n  virtual void SetY(float y);\n  virtual void SetWidth(float width);\n  virtual void SetHeight(float height);\n  virtual void SetBound(float left, float top, float width, float height);\n  virtual void SetPaddings(float padding_left, float padding_top,\n                           float padding_right, float padding_bottom);\n  void SetMargins(float margin_left, float margin_top, float margin_right,\n                  float margin_bottom);\n  void SetOpacity(float opacity);\n  float Opacity();\n  void SetOverflowWithMask(uint8_t mask, int overflow);\n  virtual void SetOverflow(int overflow);\n  int GetOverflow() { return overflow_; }\n  virtual void SetBorder(const BordersData& data);\n  void OnBorderChanged(const BordersData& data);\n\n  void AppendShadow(const Shadow& shadow);\n  void SetShadows(std::vector<Shadow>&& shadows);\n  virtual void SetBorderStyle(std::vector<Side> sides,\n                              std::vector<BorderStyleType> styles);\n  virtual void SetBorderWidth(std::vector<Side> sides,\n                              std::vector<float> widths);\n  virtual void SetBorderColor(std::vector<Side> sides,\n                              std::vector<uint32_t> colors);\n  virtual void SetBorderRadius(const FloatSize& left_top,\n                               const FloatSize& right_top,\n                               const FloatSize& right_bottom,\n                               const FloatSize& left_bottom);\n  void SetBorderRadius(size_t index, const std::vector<Length>& array);\n  bool IsDelayDestroy() { return delay_destroy_; }\n  std::optional<bool> IgnoreFocus() { return ignore_focus_; }\n  void SetOutline(const OutlineData& outline);\n  void SetOutlineStyle(const BorderStyleType style);\n  void SetOutlineWidth(int width);\n  void SetOutlineOffset(int offset);\n  void SetOutlineColor(unsigned int color);\n  void SetCursor(const std::vector<std::string>& vec);\n  void SetCursor(const clay::Value::Array& array);\n  void SetClipOffsetPath(const clay::Value::Array& array, bool is_clip_path);\n  void ClearClipPath();\n  void ClearOffsetPath();\n  void SetOffsetRotate(float rotate);\n  void SetOffsetDistance(float distance);\n  void SetFilter(const clay::Value::Array& array);\n  void ClearFilter();\n  void SetImageRendering(ClayImageRendering image_rendering);\n  void SetBoxShadowOperations(const BoxShadowOperations& value);\n  MouseCursor* GetMouseCursor();\n\n  virtual void SetBackground(const BackgroundData& background);\n\n  enum class BackgroundPropType {\n    kColor,\n    kImage,\n    kClip,\n    kOrigin,\n    kPosition,\n    kRepeat,\n    kSize\n  };\n  struct BackgroundUpdate {\n    BackgroundPropType type;\n    std::variant<Color, const clay::Value::Array*,\n                 const std::vector<BackgroundPosition>*,\n                 const std::vector<BackgroundSize>*>\n        payload;\n  };\n\n  virtual bool OnBackgroundProperty(const BackgroundUpdate& update) {\n    return false;\n  }\n\n  void SetBackgroundColor(const Color& color);\n  void SetBackgroundImage(const clay::Value::Array& array);\n  void SetBackgroundClip(const clay::Value::Array& array);\n  void SetBackgroundOrigin(const clay::Value::Array& array);\n  void SetBackgroundPosition(const std::vector<BackgroundPosition>& positions);\n  void SetBackgroundRepeat(const clay::Value::Array& array);\n  void SetBackgroundSize(const std::vector<BackgroundSize>& sizes);\n\n  void SetMaskImage(const clay::Value::Array& array);\n  void SetMaskPosition(const std::vector<MaskPosition>& positions);\n  void SetMaskOrigin(const clay::Value::Array& array);\n  void SetMaskRepeat(const clay::Value::Array& array);\n  void SetMaskSize(const std::vector<MaskSize>& mask_sizes);\n  void SetMaskClip(const clay::Value::Array& array);\n\n  TransitionManager* TransitionMgr();\n  void TransitionTo(ClayAnimationPropertyType type, float value);\n  void TransitionTo(ClayAnimationPropertyType type, const Color& value);\n  void TransitionTo(ClayAnimationPropertyType type,\n                    const TransformOperations& ops);\n\n  const clay::Value& GetDataSet() { return data_set_; }\n\n  KeyframesManager* KeyframesMgr();\n  void SetAnimation(const std::vector<AnimationData>& data);\n  void SetAnimation(const clay::Value::Array& array);\n  bool HasAnimation() const { return animation_.has_value(); }\n  const std::vector<AnimationData>& Animation() const { return *animation_; }\n\n  void AppendTransition(const TransitionData& data);\n  void SetTransition(const std::vector<TransitionData>& data);\n  void SetTransition(const clay::Value::Array& array);\n  void SetTransform(const TransformOperations& ops, const FloatPoint& origin);\n  void SetTransform(const std::vector<TransformRaw>& transform_row);\n  void SetTransformOrigin(std::optional<TransformOrigin> origin);\n  void SetTransformOrigin(const std::vector<Length>& array);\n  void SetPerspective(const clay::Value::Array& array);\n  void SetInteractable(bool enable) { is_interactable_ = enable; }\n  void SetBlockNativeEvent(bool enable) { should_block_native_event_ = enable; }\n  void SetConsumeSlideEventDirection(const clay::Value::Array& array);\n  void SetEnableNewAnimator(bool enable) { enable_new_animator_ = enable; }\n  bool IsInteractable() const { return is_interactable_; }\n  const TransformOperations& GetTransformOps() const { return transform_ops_; }\n  FloatPoint GetTransformOrigin() const;\n  Transform GetTransform() const;\n\n  bool ConsumeSlideEvent(float angle) override;\n\n  virtual void DidUpdateAttributes() {\n    // Consistent with Lynx-Native behavior, flag dirty nodes after attribute\n    // updates.\n    Invalidate();\n  }\n  virtual void OnViewPostionUpdate(FloatPoint scroll_offset);\n  virtual void NotifyLowMemory() {}\n\n  virtual void SetAttribute(const char* attr, const clay::Value& value);\n  virtual void AddEventCallback(const char* event);\n\n  // Indicate whether all children should be restricted in this view.\n  virtual bool CanChildrenEscape() const {\n    return overflow_ != CSSProperty::OVERFLOW_HIDDEN;\n  }\n  bool HitTest(const PointerEvent& event, HitTestResult& result) override;\n  void HandleEvent(const PointerEvent& event) override;\n  bool HasDragGestureRecognizer(ScrollDirection direction) const override;\n  bool HasTapGestureRecognizer() const override;\n  bool HasLongPressGestureRecognizer() const override;\n  bool HasTapEvent() const override;\n  bool HasLongPressEvent() const override;\n  bool ShouldBlockNativeEvent() const override {\n    return should_block_native_event_;\n  }\n  bool HasConsumeSlideEventAngles() const override {\n    return !consume_slide_event_ranges_.empty();\n  }\n\n  // By default, focus scope self cannot accept focus while traversal.\n  FocusBehavior GetFocusBehavior() const override {\n    if (Visible()) {\n      bool skip_focus_traversal = true;\n      if (skip_focus_traversal_.has_value()) {\n        skip_focus_traversal = *skip_focus_traversal_;\n      } else {\n        skip_focus_traversal = IsFocusScope();\n      }\n      return skip_focus_traversal ? FocusBehavior::kStepIntoChild\n                                  : FocusBehavior::kFocus;\n    }\n    return FocusBehavior::kSkip;\n  }\n\n  void SetVisible(bool visible);\n  bool Visible() const;\n\n  float Left() const { return left_; }\n  float Top() const { return top_; }\n  float Width() const { return width_; }\n  float Height() const { return height_; }\n\n  FloatPoint AbsoluteLocationWithScroll() const;\n\n  float LeftWithScroll() const;\n  float TopWithScroll() const;\n  // TODO(yulitao): consider box-sizing.\n  float HorizontalThickness() const {\n    return PaddingLeft() + PaddingRight() + BorderLeft() + BorderRight();\n  }\n  float VerticalThickness() const {\n    return PaddingTop() + PaddingBottom() + BorderTop() + BorderBottom();\n  }\n  virtual float ContentWidth() const { return Width() - HorizontalThickness(); }\n  virtual float ContentHeight() const { return Height() - VerticalThickness(); }\n  void SetContentWidth(float content_width) {\n    SetWidth(content_width + HorizontalThickness());\n  }\n  void SetContentHeight(float content_height) {\n    SetHeight(content_height + VerticalThickness());\n  }\n  float MarginLeft() const { return margin_left_; }\n  float MarginTop() const { return margin_top_; }\n  float MarginRight() const { return margin_right_; }\n  float MarginBottom() const { return margin_bottom_; }\n\n  float PaddingLeft() const { return padding_left_; }\n  float PaddingTop() const { return padding_top_; }\n  float PaddingRight() const { return padding_right_; }\n  float PaddingBottom() const { return padding_bottom_; }\n\n  float BorderLeft() const;\n  float BorderTop() const;\n  float BorderRight() const;\n  float BorderBottom() const;\n  FloatRect GetBounds() const {\n    return FloatRect(left_, top_, width_, height_);\n  }\n\n  float ContentInsetLeft() const;\n  float ContentInsetTop() const;\n\n  virtual bool CanAcceptEvent() const;\n  virtual BaseView* GetTopViewToAcceptEvent(const FloatPoint& position,\n                                            FloatPoint* relative_position);\n  // Content bounds (without border and padding) in the viewport.\n  FloatRect ContentBoundsInViewport() const;\n  // Bounds (including border and padding) relative to an other view (or the\n  // viewport if the view parameter is null)\n  FloatRect BoundsRelativeTo(BaseView* view) const;\n\n  FloatPoint GetPointBySelf(const FloatPoint& point_by_page) const;\n  Transform LocalToGlobalTransform() const;\n\n  // FocusNode\n  FocusManager* GetParentFocusManager() override;\n  const std::string& FocusId() const override { return id_selector_; }\n\n  void FocusHasChanged(bool focus, bool is_leaf) override;\n  // Return true if need to propagate scrolling, or false to stop propagate.\n  virtual bool OnScrollToVisible() { return true; }\n  virtual bool OnScrollToMiddle(BaseView* target_view) { return true; }\n  // Make a view visible in scrollable container.\n  void ScrollToVisible() {\n    if (OnScrollToVisible() && Parent()) {\n      Parent()->ScrollToVisible();\n    }\n  }\n  void ScrollToMiddle(BaseView* target_view) {\n    if (OnScrollToMiddle(target_view) && Parent()) {\n      Parent()->ScrollToMiddle(target_view);\n    }\n  }\n  void SetHasDefaultFocusRing(bool has_focus_ring) override;\n  FloatRect CalcFocusRect() const override;\n  bool DispatchKeyEventOnFocusNode(const KeyEvent* event) override;\n  FloatRect GetContentVisibleRect() override;\n  FloatSize GetThicknessOffset() override;\n\n  // Layout related. Used by TextView and BaseListView.\n  // Assume this flag won't change during the lifetime.\n  virtual bool IsLayoutRootCandidate() const { return false; }\n  void SetInLayoutTree(bool in_layout_tree);\n  // A node is in a layout tree if one of its ancestor is a layout root\n  // candidate or the node itself is a layout root candidate.\n  bool InLayoutTree() const {\n    return IsLayoutRootCandidate() || in_layout_tree_;\n  }\n\n  // `view_to_layout` is the node that initiates the layout. It can be the view\n  // itself or its descendant.\n  // When `view_to_layout` is nullptr, it means it the view itself.\n  // NOTE: If this is called when a descendant is removed from the layout tree,\n  // `view_to_layout` can be no longer in the layout tree.\n  void MarkNeedsLayout(BaseView* view_to_layout = nullptr);\n  bool NeedsLayout() const { return needs_layout_; }\n  bool NeedsLayoutUpdated() const { return needs_layout_updated_; }\n\n  bool ShouldIgnoreLayoutRequest() const {\n    return ignore_layout_request_count_ > 0;\n  }\n\n  void PushIgnoreLayoutRequest() {\n    FML_DCHECK(ignore_layout_request_count_ >= 0);\n    ignore_layout_request_count_++;\n  }\n\n  void PopIgnoreLayoutRequest() {\n    ignore_layout_request_count_--;\n    FML_DCHECK(ignore_layout_request_count_ >= 0);\n  }\n\n  void VisitChildren(const std::function<void(BaseView*)>& visitor);\n\n  class LayoutIgnoreHelper {\n   public:\n    explicit LayoutIgnoreHelper(BaseView* view) : view_(view) {\n      FML_DCHECK(view_);\n      view_->PushIgnoreLayoutRequest();\n    }\n\n    ~LayoutIgnoreHelper() { view_->PopIgnoreLayoutRequest(); }\n\n   private:\n    BaseView* view_ = nullptr;\n  };\n\n  // context is nullable.\n  void Layout(LayoutContext* context = nullptr);\n  virtual void OnLayout(LayoutContext* context);\n\n  virtual void OnContentSizeChanged(const FloatRect& old_rect,\n                                    const FloatRect& new_rect);\n  virtual void OnBoundsChanged(const FloatRect& old_bounds,\n                               const FloatRect& new_bounds);\n  virtual void OnChildSizeChanged(BaseView* child) {}\n\n  fml::WeakPtr<BaseView> GetWeakPtr() const {\n    return weak_factory_.GetWeakPtr();\n  }\n  PageView* page_view() const { return page_view_; }\n  bool attach_to_tree() const { return attach_to_tree_; }\n\n  // physical_width & physical_height can be zero.\n  // It happens when the app just starts, the activity is not created but the\n  // pixel ratio is known. Then zero physical size is passed.\n  virtual void OnViewportMetricsUpdated(int physical_width, int physical_height,\n                                        float device_pixel_ratio) {}\n\n#ifndef NDEBUG\n  virtual void DumpViewTree(int depth) const;\n  virtual std::string ToString() const;\n#endif\n\n  // Lynx ui method\n#define UI_METHOD_LIST_DECLARATION(V) \\\n  V(setFocus)                         \\\n  V(interceptBackKeyOnce)             \\\n  V(cancelInterceptBackKey)           \\\n  V(boundingClientRect)               \\\n  V(scrollIntoView)                   \\\n  V(takeScreenshot)\n  UI_METHOD_LIST_DECLARATION(UI_METHOD_DEF);\n#undef UI_METHOD_LIST_DECLARATION\n\n  // animation event and transition event\n  void OnAnimationEvent(const AnimationParams& animation_params);\n  void OnTransitionEvent(const AnimationParams& animation_params,\n                         ClayAnimationPropertyType property_type);\n\n  bool HasAnimationEvent(const ClayEventType& event_type) const;\n  virtual bool HasEvent(const std::string& event) const {\n    return events_ &&\n           std::find(events_->begin(), events_->end(), event) != events_->end();\n  }\n  const std::string& ItemKey() const { return item_key_; }\n\n  Scrollable* FindAncestorScrollableView(BaseView* child);\n\n  RenderObject* render_object() const { return render_object_.get(); }\n  void AddGestureRecognizer(std::unique_ptr<GestureRecognizer> recognizer);\n  void RemoveGestureRecognizer(GestureRecognizer* recognizer);\n  void ClearGestureRecognizers();\n\n  void EndAllTransitionsRecursively();\n\n  void GetTransformValue(float left, float right, float top, float bottom,\n                         TransOffset& res);\n\n  void GetLocationOnScreen(float in_out_location_x, float in_out_location_y,\n                           std::vector<float>& res);\n\n  std::vector<float> TransformFromViewToRootView(BaseView* view,\n                                                 float& in_out_location_x,\n                                                 float& in_out_location_y);\n\n  void UpdateTransitionRasterAnimation(ClayAnimationPropertyType type);\n\n  void UpdateKeyframesRasterAnimation();\n\n  bool IsRasterAnimationEnabled() const;\n\n  void UpdateSticky(std::optional<StickyInfo> sticky);\n  void CheckStickyOnParentScrollAndReset(int left, int top);\n\n  // this means whether the entire page through the touch events.\n  std::optional<bool> CanEventThrough() const { return event_through_; }\n  // this means whether this view node pass through the events to the nodes\n  // behind it.\n  virtual bool CanEventsPassThroughToViewsBehind() const { return false; }\n\n  AnimationHandler* GetAnimationHandler();\n\n  virtual void OnLayoutFinish(BaseView* view) {}\n\n  float FromLogical(float value) const;\n  float ToLogical(float value) const;\n\n  // AnimatorTarget related interfaces\n  void GetProperty(ClayAnimationPropertyType type, float& value);\n  void GetProperty(ClayAnimationPropertyType type, Color& value);\n  void GetProperty(ClayAnimationPropertyType type, TransformOperations& value);\n  void GetProperty(ClayAnimationPropertyType type, FilterOperations& value);\n  // SetProperty will modify value without triggering transition animation.\n  virtual void SetProperty(ClayAnimationPropertyType type, float value,\n                           bool skip_update_for_raster_animation);\n  void SetProperty(ClayAnimationPropertyType type, const Color& value,\n                   bool skip_update_for_raster_animation);\n  void SetProperty(ClayAnimationPropertyType type,\n                   const TransformOperations& value,\n                   bool skip_update_for_raster_animation);\n  void SetProperty(ClayAnimationPropertyType type,\n                   const FilterOperations& value);\n  const KeyframesMap* GetKeyframesMap(const std::string& animation_name);\n  FloatSize PercentageResolutionSize() { return {Width(), Height()}; }\n\n  void DecodeImagesRecursively();\n\n#ifdef ENABLE_ACCESSIBILITY\n  virtual FloatRect GetSemanticsBounds() const;\n  // Whether enables a11y for this view.\n  void SetAccessibilityElement(bool value);\n  virtual bool IsAccessibilityElement() const;\n  void SetAccessibilityLabel(const std::string& value);\n  void SetAccessibilityElements(const std::string& value);\n  void MarkRebuildSemanticsTree();\n  void UpdateSemantics(\n      const std::vector<fml::RefPtr<SemanticsNode>>& new_children,\n      BaseView* parent_node_view, bool need_check_children,\n      bool force_update = false);\n  fml::RefPtr<SemanticsNode> GetSemantics() const { return semantics_; }\n  Transform AccumulateTransformFromView(BaseView* view) const;\n  // Prepre SemanticsNode for each BaseView.\n  virtual void PrepareSemantics(\n      fml::RefPtr<SemanticsNode> parent_node,\n      std::vector<fml::RefPtr<SemanticsNode>>& result,\n      const std::vector<std::string>& ancestor_a11y_elements);\n  // Some views may need to dispatch tap/long_press action in clay.\n  virtual void OnA11yTap() {}\n  virtual void OnA11yLongPress() {}\n  virtual std::u16string GetAccessibilityLabel() const;\n#endif\n\n protected:\n  friend class BaseListView;\n  friend class BaseViewWithChildrenTest;\n  friend class BaseViewAnimationMutator;\n\n  BaseViewAnimationMutator* GetAnimationMutator();\n\n  void DestroyChildrenRecursively(BaseView* view);\n  virtual bool IsIndependentSubViewTree() const { return false; }\n\n  virtual void OnDestroy() {}\n  virtual void OnDetachFromTree();\n  virtual void OnAttachToTree();\n  virtual void Invalidate();\n  void LayoutUpdated();\n  void ScrollToFocus();\n  virtual void ScrollChildViewToVisible(const FloatRect& rect) {}\n  int GetCurrentImageLoaderToken() const { return bg_image_loader_token_; }\n  int GetCurrentMaskImageLoaderToken() const {\n    return mask_image_loader_token_;\n  }\n  bool should_pass_event_for_hittest_ = false;\n  void LoadBackgroundOrMaskImage(const std::string& uri, size_t index,\n                                 bool background = true);\n\n  // Handle common attributes and styles for all views. Return true if the\n  // attribute is handled.\n  bool HandleCommonAttribute(const char* attr, const clay::Value& value);\n\n  void UpdateCacheStrategy();\n  void UpdateChildrenBounds();\n  void SetTransformOperations(const TransformOperations& operations,\n                              bool is_from_animation);\n\n  void UpdateRenderObjectTransformOrigin();\n\n#ifdef ENABLE_ACCESSIBILITY\n  // Some views can update its specific semantics data.\n  void UpdateSemanticsData(BaseView* parent_node_view);\n  virtual int32_t GetA11yScrollChildren() const;\n  virtual int32_t GetSemanticsActions() const;\n  virtual int32_t GetSemanticsFlags() const;\n\n  SemanticsOwner* GetSemanticsOwner() const;\n\n  // Whether enable a11y for this view. Only ImageView and TextView are set to\n  // true by default.\n  std::optional<bool> accessibility_element_ = std::nullopt;\n  std::optional<std::u16string> accessibility_label_ = std::nullopt;\n  std::optional<std::vector<std::string>> accessibility_elements_ =\n      std::nullopt;\n  fml::RefPtr<SemanticsNode> semantics_ = nullptr;\n#endif\n\n  int id_ = -1;\n  std::string id_selector_;\n  std::string ref_id_selector_;\n  std::string name_;\n  std::string app_region_;\n  bool can_draggable_ = false;\n  PointerEvent event_draggable_;\n  // left_ and top_ are relative to the upper left\n  // corner of the parent borderbox\n  float left_ = 0.f;\n  float top_ = 0.f;\n  float width_ = 0.f;\n  float height_ = 0.f;\n  float padding_left_ = 0.f;\n  float padding_top_ = 0.f;\n  float padding_right_ = 0.f;\n  float padding_bottom_ = 0.f;\n  float margin_left_ = 0.f;\n  float margin_top_ = 0.f;\n  float margin_right_ = 0.f;\n  float margin_bottom_ = 0.f;\n  clay::Value data_set_ = clay::Value(clay::Value::Map());\n  std::string tag_;\n  // FIXME(baiqiang): remove focus&text list then move to component\n  std::string item_key_;\n  bool attach_to_tree_ = false;\n  std::optional<bool> ignore_focus_;\n  BaseView* parent_ = nullptr;\n  PageView* page_view_ = nullptr;\n  std::vector<BaseView*> children_;\n  std::unique_ptr<RenderObject> render_object_;\n  int bg_image_loader_token_ = 0;\n  int mask_image_loader_token_ = 0;\n  std::unique_ptr<KeyframesManager> keyframes_mgr_;\n  std::unique_ptr<TransitionManager> transition_mgr_;\n  std::optional<std::vector<AnimationData>> animation_;\n  bool in_layout_tree_ = false;\n  bool needs_layout_ = false;\n  bool needs_layout_updated_ = false;\n  int ignore_layout_request_count_ = 0;\n  fml::WeakPtrFactory<BaseView> weak_factory_;\n  TransformOperations transform_ops_;\n  FilterOperations color_matrix_ops_;\n  BoxShadowOperations box_shadow_ops_;\n  std::optional<TransformOrigin> transform_origin_;\n  std::optional<std::vector<TransformRaw>> transform_raw_;\n  std::optional<float> perspective_value_;\n  std::optional<std::vector<std::string>> events_;\n  std::unique_ptr<MouseCursor> cursor_ = nullptr;\n  bool is_mouse_hover_ = false;\n  uint8_t overflow_;\n  std::function<void(BaseView*)> destruct_listener_ = nullptr;\n  std::vector<std::unique_ptr<GestureRecognizer>> gesture_recognizers_;\n  bool enable_layout_change_event_ = false;\n  bool is_interactable_ = true;\n  bool should_block_native_event_ = false;\n  bool has_intersection_observer_ = false;\n  std::optional<bool> event_through_;\n  // all slop values means extend x px\n  float hit_slop_top_ = 0.f;\n  float hit_slop_left_ = 0.f;\n  float hit_slop_right_ = 0.f;\n  float hit_slop_bottom_ = 0.f;\n  std::vector<std::pair<float, float>> consume_slide_event_ranges_;\n  DirectionType layout_direction_ = DirectionType::kLtr;\n  std::optional<StickyInfo> sticky_ = std::nullopt;\n  std::optional<FloatPoint> post_translation_;\n  bool enable_new_animator_ = false;\n  bool remove_temporarily_ = false;\n  std::optional<ClipPathData> clip_path_data_;\n  std::optional<OffsetPathData> offset_path_data_;\n  bool delay_destroy_ = false;\n  std::unique_ptr<BaseViewAnimationMutator> animation_mutator_;\n\n private:\n  template <typename... Args>\n  void NotifyBgImageLoadStatus(bool success,\n                               const std::vector<std::string>& keys,\n                               Args&&... args);\n  bool UpdateExposeAttrs(const char* attr, const clay::Value& value);\n  void DirtyChildrenPaintingOrder() { sorted_children_.clear(); }\n  void RebuildSortedChildrenIfNeeded();\n  void NotifyBoundChangeIfNeeded(const FloatRect& old_bounds);\n  void DrawClipPath(bool is_clip_path);\n\n  std::vector<BaseView*> sorted_children_;\n  bool ignore_size_change_checks_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_BASE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/base_view_animation_mutator.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/base_view_animation_mutator.h\"\n\n#include <string>\n\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nBaseViewAnimationMutator::BaseViewAnimationMutator(BaseView* view)\n    : view_(view) {}\n\nvoid BaseViewAnimationMutator::OnAnimationEvent(\n    const AnimationParams& animation_params) {\n  view_->OnAnimationEvent(animation_params);\n}\n\nvoid BaseViewAnimationMutator::OnTransitionEvent(\n    const AnimationParams& animation_params,\n    ClayAnimationPropertyType property_type) {\n  view_->OnTransitionEvent(animation_params, property_type);\n}\n\nvoid BaseViewAnimationMutator::GetProperty(ClayAnimationPropertyType type,\n                                           float& value) {\n  view_->GetProperty(type, value);\n}\n\nvoid BaseViewAnimationMutator::GetProperty(ClayAnimationPropertyType type,\n                                           Color& value) {\n  view_->GetProperty(type, value);\n}\n\nvoid BaseViewAnimationMutator::GetProperty(ClayAnimationPropertyType type,\n                                           TransformOperations& value) {\n  view_->GetProperty(type, value);\n}\n\nvoid BaseViewAnimationMutator::GetProperty(ClayAnimationPropertyType type,\n                                           FilterOperations& value) {\n  view_->GetProperty(type, value);\n}\n\nvoid BaseViewAnimationMutator::SetProperty(\n    ClayAnimationPropertyType type, float value,\n    bool skip_update_for_raster_animation) {\n  view_->SetProperty(type, value, skip_update_for_raster_animation);\n}\n\nvoid BaseViewAnimationMutator::SetProperty(\n    ClayAnimationPropertyType type, const Color& value,\n    bool skip_update_for_raster_animation) {\n  view_->SetProperty(type, value, skip_update_for_raster_animation);\n}\n\nvoid BaseViewAnimationMutator::SetProperty(\n    ClayAnimationPropertyType type, const TransformOperations& value,\n    bool skip_update_for_raster_animation) {\n  view_->SetProperty(type, value, skip_update_for_raster_animation);\n}\n\nvoid BaseViewAnimationMutator::SetProperty(ClayAnimationPropertyType type,\n                                           const FilterOperations& value) {\n  view_->SetProperty(type, value);\n}\n\nAnimationHandler* BaseViewAnimationMutator::GetAnimationHandler() {\n  return view_->GetAnimationHandler();\n}\n\nFloatSize BaseViewAnimationMutator::PercentageResolutionSize() {\n  return view_->PercentageResolutionSize();\n}\n\nconst KeyframesMap* BaseViewAnimationMutator::GetKeyframesMap(\n    const std::string& animation_name) {\n  return view_->GetKeyframesMap(animation_name);\n}\n\nvoid BaseViewAnimationMutator::SetProperty(ClayAnimationPropertyType type,\n                                           const BoxShadowOperations& value) {\n  if (type != ClayAnimationPropertyType::kBoxShadow) {\n    return;\n  }\n  view_->SetBoxShadowOperations(value);\n}\n\nvoid BaseViewAnimationMutator::GetProperty(ClayAnimationPropertyType type,\n                                           BoxShadowOperations& value) {\n  if (type != ClayAnimationPropertyType::kBoxShadow) {\n    return;\n  }\n  value = view_->box_shadow_ops_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/base_view_animation_mutator.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_BASE_VIEW_ANIMATION_MUTATOR_H_\n#define CLAY_UI_COMPONENT_BASE_VIEW_ANIMATION_MUTATOR_H_\n\n#include <string>\n\n#include \"clay/gfx/animation/animation_event_handler.h\"\n#include \"clay/gfx/animation/animator_target.h\"\n\nnamespace clay {\nclass BaseView;\n\n// Implementing AnimatorTarget in BaseView would significantly increase the\n// package size, so we use a proxy approach to avoid vtable bloat.\nclass BaseViewAnimationMutator : public AnimatorTarget,\n                                 public AnimationEventHandler {\n public:\n  explicit BaseViewAnimationMutator(BaseView* view);\n  ~BaseViewAnimationMutator() override = default;\n\n private:\n  void OnAnimationEvent(const AnimationParams& animation_params) override;\n  void OnTransitionEvent(const AnimationParams& animation_params,\n                         ClayAnimationPropertyType property_type) override;\n\n  void GetProperty(ClayAnimationPropertyType type, float& value) override;\n  void GetProperty(ClayAnimationPropertyType type, Color& value) override;\n  void GetProperty(ClayAnimationPropertyType type,\n                   TransformOperations& value) override;\n  void GetProperty(ClayAnimationPropertyType type,\n                   FilterOperations& value) override;\n\n  void SetProperty(ClayAnimationPropertyType type, float value,\n                   bool skip_update_for_raster_animation) override;\n  void SetProperty(ClayAnimationPropertyType type, const Color& value,\n                   bool skip_update_for_raster_animation) override;\n  void SetProperty(ClayAnimationPropertyType type,\n                   const TransformOperations& value,\n                   bool skip_update_for_raster_animation) override;\n  void SetProperty(ClayAnimationPropertyType type,\n                   const FilterOperations& value) override;\n\n  void SetProperty(ClayAnimationPropertyType type,\n                   const BoxShadowOperations& value) override;\n  void GetProperty(ClayAnimationPropertyType type,\n                   BoxShadowOperations& value) override;\n\n  AnimationHandler* GetAnimationHandler() override;\n\n  FloatSize PercentageResolutionSize() override;\n\n  const KeyframesMap* GetKeyframesMap(\n      const std::string& animation_name) override;\n\n  BaseView* view_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_BASE_VIEW_ANIMATION_MUTATOR_H_\n"
  },
  {
    "path": "clay/ui/component/base_view_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/rendering/render_container.h\"\n#include \"clay/ui/testing/ui_test.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nPointerEvent CreateDownPointer(float x, float y) {\n  PointerEvent event(PointerEvent::EventType::kDownEvent);\n  event.position = {x, y};\n  return event;\n}\n\nclass BaseViewTest : public UITest {};\n\nTEST_F_UI(BaseViewTest, TreeManipulation) {\n  int view_id = 0;\n  std::unique_ptr<BaseView> root =\n      std::make_unique<View>(view_id++, page_.get());\n  View* childView1 = new View(view_id++, page_.get());\n  root->AddChild(childView1);\n  View* childView2 = new View(view_id++, page_.get());\n  View* childView3 = new View(view_id++, page_.get());\n  root->AddChild(childView3);\n  root->AddChild(childView2, 1);\n  EXPECT_EQ(root->child_count(), 3u);\n  EXPECT_EQ(root->Parent(), nullptr);\n  EXPECT_EQ(childView1->Parent(), root.get());\n  EXPECT_EQ(childView2->Parent(), root.get());\n  EXPECT_EQ(childView3->Parent(), root.get());\n\n  root->RemoveChild(childView3);\n  EXPECT_EQ(childView3->Parent(), nullptr);\n  delete childView3;\n\n  EXPECT_EQ(root->child_count(), 2u);\n  root->DestroyAllChildren();\n  root->Destroy();\n  EXPECT_EQ(root->child_count(), 0u);\n}\n\nTEST_F_UI(BaseViewTest, HitTest) {\n  //     0     100     200 250    450 600   800\n  //     |---------------|\n  //     |     View1     |\n  // 200 |       |-------------------|------|\n  // 300 |-------|    View3          |      |\n  // 350         |         |--------||      |\n  //             |         |  View4 ||      |\n  //             |         |--------||      |\n  //             |                   |      |\n  // 700         |-------------------|      |\n  //             |                          |\n  //             |           View2          |\n  //             |                          |\n  // 1000        |--------------------------|\n  //\n\n  std::unique_ptr<BaseView> root = std::make_unique<View>(0, page_.get());\n  // View type doesn't matter. All views in the region will be added in.\n  BaseView* View1 = new View(1, page_.get());\n  BaseView* View2 = new View(2, page_.get());\n  BaseView* View3 = new View(3, page_.get());\n  BaseView* View4 = new View(4, page_.get());\n  root->AddChild(View1);\n  root->AddChild(View2);\n  View2->AddChild(View3);\n  View3->AddChild(View4);\n  EXPECT_EQ(root->child_count(), 2u);\n\n  root->SetX(0.f);\n  root->SetY(0.f);\n  root->SetWidth(1000.f);\n  root->SetHeight(1000.f);\n\n  View1->SetX(0.f);\n  View1->SetY(0.f);\n  View1->SetWidth(200.f);\n  View1->SetHeight(300.f);\n\n  View2->SetX(100.f);\n  View2->SetY(200.f);\n  View2->SetWidth(800.f);\n  View2->SetHeight(800.f);\n\n  View3->SetX(0.f);\n  View3->SetY(0.f);\n  View3->SetWidth(500.f);\n  View3->SetHeight(500.f);\n\n  View4->SetX(150.f);\n  View4->SetY(100.f);\n  View4->SetWidth(200.f);\n  View4->SetHeight(200.f);\n\n  View3->OnLayoutUpdated();\n  View4->OnLayoutUpdated();\n\n  {\n    HitTestResult hit_test_result;\n    root->HitTest(CreateDownPointer(300, 100), hit_test_result);\n    // root\n    EXPECT_EQ(static_cast<int>(hit_test_result.size()), 1);\n  }\n\n  {\n    HitTestResult hit_test_result;\n    root->HitTest(CreateDownPointer(150, 350), hit_test_result);\n    // root / view2 / view3\n    EXPECT_EQ(static_cast<int>(hit_test_result.size()), 3);\n  }\n\n  {\n    HitTestResult hit_test_result;\n    root->HitTest(CreateDownPointer(650, 750), hit_test_result);\n    EXPECT_EQ(static_cast<int>(hit_test_result.size()), 2);\n    int list[] = {2, 0};\n    int index = 0;\n    for (auto it = hit_test_result.begin(); it != hit_test_result.end(); ++it) {\n      EXPECT_EQ(static_cast<BaseView*>(it->get())->id(), list[index]);\n      index++;\n    }\n  }\n\n  root->DestroyAllChildren();\n  root->Destroy();\n}\n\nclass BaseViewWithChildrenTest : public UITest {\n protected:\n  void UISetUp() override {\n    for (int i = 0; i <= 6; i++) {\n      nodeList.push_back(std::make_unique<View>(i, page_.get()));\n    }\n\n    nodeList[0]->AddChild(nodeList[1].get());\n    nodeList[0]->AddChild(nodeList[2].get());\n    nodeList[1]->AddChild(nodeList[3].get());\n    nodeList[1]->AddChild(nodeList[4].get());\n    nodeList[1]->AddChild(nodeList[5].get());\n    nodeList[2]->AddChild(nodeList[6].get());\n  }\n\n  void UITearDown() override { nodeList.clear(); }\n\n  bool ChildrenPaintingOrderIsDirtyForTesting(BaseView* view) {\n    return !view->children_.empty() && view->sorted_children_.empty();\n  }\n\n  const std::vector<BaseView*>& GetSortedChildrenForTesting(BaseView* view) {\n    view->RebuildSortedChildrenIfNeeded();\n    return view->sorted_children_;\n  }\n\n  std::vector<std::unique_ptr<BaseView>> nodeList;\n};\n\nTEST_F_UI(BaseViewWithChildrenTest, PaintOrder) {\n  // Initial state.\n  EXPECT_EQ(ChildrenPaintingOrderIsDirtyForTesting(nodeList[1].get()), true);\n  const auto& sorted1 = GetSortedChildrenForTesting(nodeList[1].get());\n  EXPECT_EQ(ChildrenPaintingOrderIsDirtyForTesting(nodeList[1].get()), false);\n  EXPECT_EQ(sorted1[0], nodeList[3].get());\n  EXPECT_EQ(sorted1[1], nodeList[4].get());\n  EXPECT_EQ(sorted1[2], nodeList[5].get());\n\n  // Set z-index = 5 for node 3.\n  nodeList[3]->SetPaintingOrder(5);\n  EXPECT_EQ(ChildrenPaintingOrderIsDirtyForTesting(nodeList[1].get()), true);\n  const auto& sorted2 = GetSortedChildrenForTesting(nodeList[1].get());\n  EXPECT_EQ(sorted2[0], nodeList[4].get());\n  EXPECT_EQ(sorted2[1], nodeList[5].get());\n  EXPECT_EQ(sorted2[2], nodeList[3].get());\n\n  // Set z-index = 5 for node 3 again.\n  nodeList[3]->SetPaintingOrder(5);\n  EXPECT_EQ(ChildrenPaintingOrderIsDirtyForTesting(nodeList[1].get()), false);\n\n  // Set translate-z = 1 for node 4.\n  TransformOperations transform3;\n  transform3.AppendTranslate(0, 0, 1);\n  nodeList[4]->SetProperty(ClayAnimationPropertyType::kTransform, transform3,\n                           false);\n  EXPECT_EQ(ChildrenPaintingOrderIsDirtyForTesting(nodeList[1].get()), true);\n  const auto& sorted3 = GetSortedChildrenForTesting(nodeList[1].get());\n  EXPECT_EQ(sorted3[0], nodeList[5].get());\n  EXPECT_EQ(sorted3[1], nodeList[3].get());\n  EXPECT_EQ(sorted3[2], nodeList[4].get());\n\n  // Insert new child to node 1.\n  BaseView* obj = new View(7, page_.get());\n  nodeList[1]->AddChild(obj);\n  EXPECT_EQ(ChildrenPaintingOrderIsDirtyForTesting(nodeList[1].get()), true);\n  const auto& sorted4 = GetSortedChildrenForTesting(nodeList[1].get());\n  EXPECT_EQ(sorted4[0], nodeList[5].get());\n  EXPECT_EQ(sorted4[1], obj);\n  EXPECT_EQ(sorted4[2], nodeList[3].get());\n  EXPECT_EQ(sorted4[3], nodeList[4].get());\n\n  // Set z-index = 10 for obj.\n  obj->SetPaintingOrder(10);\n  const auto& sorted5 = GetSortedChildrenForTesting(nodeList[1].get());\n  EXPECT_EQ(sorted5[0], nodeList[5].get());\n  EXPECT_EQ(sorted5[1], nodeList[3].get());\n  EXPECT_EQ(sorted5[2], obj);\n  EXPECT_EQ(sorted5[3], nodeList[4].get());\n\n  // Set translate-z = 1 for obj.\n  TransformOperations transform6;\n  transform6.AppendTranslate(0, 0, 1);\n  obj->SetProperty(ClayAnimationPropertyType::kTransform, transform6, false);\n  const auto& sorted6 = GetSortedChildrenForTesting(nodeList[1].get());\n  EXPECT_EQ(sorted6[0], nodeList[5].get());\n  EXPECT_EQ(sorted6[1], nodeList[3].get());\n  EXPECT_EQ(sorted6[2], nodeList[4].get());\n  EXPECT_EQ(sorted6[3], obj);\n\n  // Set translate-z = 0 for obj and node 4.\n  TransformOperations transform7;\n  transform7.AppendTranslate(0, 0, 0);\n  nodeList[4]->SetProperty(ClayAnimationPropertyType::kTransform, transform7,\n                           false);\n  obj->SetProperty(ClayAnimationPropertyType::kTransform, transform7, false);\n  EXPECT_EQ(ChildrenPaintingOrderIsDirtyForTesting(nodeList[1].get()), true);\n  const auto& sorted7 = GetSortedChildrenForTesting(nodeList[1].get());\n  EXPECT_EQ(sorted7[0], nodeList[4].get());\n  EXPECT_EQ(sorted7[1], nodeList[5].get());\n  EXPECT_EQ(sorted7[2], nodeList[3].get());\n  EXPECT_EQ(sorted7[3], obj);\n\n  // Remove node 3.\n  nodeList[1]->RemoveChild(nodeList[3].get());\n  EXPECT_EQ(ChildrenPaintingOrderIsDirtyForTesting(nodeList[1].get()), true);\n  const auto& sorted8 = GetSortedChildrenForTesting(nodeList[1].get());\n  EXPECT_EQ(sorted8[0], nodeList[4].get());\n  EXPECT_EQ(sorted8[1], nodeList[5].get());\n  EXPECT_EQ(sorted8[2], obj);\n\n  // Update root node‘s painting order shouldn't trigger a crash.\n  TransformOperations transform8;\n  transform8.AppendTranslate(0, 0, 1);\n  auto* root = nodeList[0].get();\n  root->SetProperty(ClayAnimationPropertyType::kTransform, transform8, false);\n  root->SetPaintingOrder(1);\n}\n\nTEST_F_UI(BaseViewTest, OnBoundChange) {\n  class MockBaseView : public BaseView {\n   public:\n    using BaseView::BaseView;\n    MOCK_METHOD(void, OnBoundsChanged,\n                (const FloatRect& old_bounds, const FloatRect& new_bounds),\n                (override));\n  };\n\n  MockBaseView mock_view(std::make_unique<RenderContainer>(), page_.get());\n\n  EXPECT_CALL(mock_view, OnBoundsChanged(::testing::_, ::testing::_)).Times(1);\n  mock_view.SetBound(0, 0, 100, 100);\n  ::testing::Mock::VerifyAndClearExpectations(this);\n\n  EXPECT_CALL(mock_view, OnBoundsChanged(FloatRect(0, 0, 100, 100),\n                                         FloatRect(0, 0, 200, 300)))\n      .Times(1);\n  mock_view.SetBound(0, 0, 200, 300);\n  ::testing::Mock::VerifyAndClearExpectations(this);\n\n  EXPECT_CALL(mock_view, OnBoundsChanged(::testing::_, ::testing::_)).Times(1);\n  mock_view.SetX(10);\n  ::testing::Mock::VerifyAndClearExpectations(this);\n\n  EXPECT_CALL(mock_view, OnBoundsChanged(::testing::_, ::testing::_)).Times(1);\n  mock_view.SetY(10);\n  ::testing::Mock::VerifyAndClearExpectations(this);\n\n  EXPECT_CALL(mock_view, OnBoundsChanged(::testing::_, ::testing::_)).Times(0);\n  mock_view.SetBound(10, 10, 200, 300);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/bounce_view/bounce_view.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/bounce_view/bounce_view.h\"\n\n#include <memory>\n#include <string>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/rendering/render_bounce.h\"\n#include \"clay/ui/rendering/render_container.h\"\n\nnamespace clay {\n\nBounceView::BounceView(int id, PageView* page_view)\n    : WithTypeInfo(id, \"BounceView\", std::make_unique<RenderBounce>(),\n                   page_view) {}\n\nvoid BounceView::SetAttribute(const char* attr, const clay::Value& value) {\n  std::string attribute(attr);\n  if (attribute.compare(\"direction\") == 0) {\n    std::string direction;\n    attribute_utils::TryGetString(value, direction, \"direction\");\n    if (direction.compare(\"right\") == 0) {\n      SetBounceDirection(Direction::kRight);\n    } else if (direction.compare(\"left\") == 0) {\n      SetBounceDirection(Direction::kLeft);\n    } else if (direction.compare(\"bottom\") == 0) {\n      SetBounceDirection(Direction::kBottom);\n    } else if (direction.compare(\"top\") == 0) {\n      SetBounceDirection(Direction::kTop);\n    } else {\n      FML_DCHECK(false);\n    }\n    return;\n  }\n  BaseView::SetAttribute(attr, value);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/bounce_view/bounce_view.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_BOUNCE_VIEW_BOUNCE_VIEW_H_\n#define CLAY_UI_COMPONENT_BOUNCE_VIEW_BOUNCE_VIEW_H_\n\n#include \"clay/gfx/geometry/direction.h\"\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nclass BounceView : public WithTypeInfo<BounceView, BaseView> {\n public:\n  BounceView(int id, PageView* page_view);\n  ~BounceView() override = default;\n\n  Direction GetDirection() const { return direction_; }\n\n  void SetBounceDirection(Direction direction) { direction_ = direction; }\n\n  void SetAttribute(const char* attr, const clay::Value& value) override;\n\n private:\n  Direction direction_ = Direction::kRight;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_BOUNCE_VIEW_BOUNCE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\nui_component_sources = [\n  \"base_image_view.cc\",\n  \"base_image_view.h\",\n  \"base_view.cc\",\n  \"base_view.h\",\n  \"base_view_animation_mutator.cc\",\n  \"base_view_animation_mutator.h\",\n  \"bounce_view/bounce_view.cc\",\n  \"bounce_view/bounce_view.h\",\n  \"builtin_views.cc\",\n  \"builtin_views.h\",\n  \"component.cc\",\n  \"component.h\",\n  \"component_constants.cc\",\n  \"component_constants.h\",\n  \"css_property.cc\",\n  \"css_property.h\",\n  \"expose_manager/expose_observer.cc\",\n  \"expose_manager/expose_observer.h\",\n  \"focus/focus_isolate.cc\",\n  \"focus/focus_isolate.h\",\n  \"focus_event_handler.h\",\n  \"focus_node.cc\",\n  \"focus_node.h\",\n  \"hittest_request.h\",\n  \"image_view.cc\",\n  \"image_view.h\",\n  \"inline_image_view.cc\",\n  \"inline_image_view.h\",\n  \"intersection_observer.cc\",\n  \"intersection_observer.h\",\n  \"intersection_observer_manager.cc\",\n  \"intersection_observer_manager.h\",\n  \"isolated_gesture_detector.h\",\n  \"key_event_handler.cc\",\n  \"key_event_handler.h\",\n  \"keywords.cc\",\n  \"keywords.h\",\n  \"layout_controller.cc\",\n  \"layout_controller.h\",\n  \"list/list_common/layout_types.cc\",\n  \"list/list_common/layout_types.h\",\n  \"list/list_container/list_container_view.cc\",\n  \"list/list_container/list_container_view.h\",\n  \"list/list_container/list_container_wrapper.cc\",\n  \"list/list_container/list_container_wrapper.h\",\n  \"measurable.h\",\n  \"mouse_cursor.h\",\n  \"native_view.cc\",\n  \"native_view.h\",\n  \"nested_scroll/nested_scroll_manager.cc\",\n  \"nested_scroll/nested_scroll_manager.h\",\n  \"nested_scroll/nested_scrollable.cc\",\n  \"nested_scroll/nested_scrollable.h\",\n  \"nested_scroll/overscroll_effect.cc\",\n  \"nested_scroll/overscroll_effect.h\",\n  \"nested_scroll/raster_fling_manager.cc\",\n  \"nested_scroll/raster_fling_manager.h\",\n  \"page_view.cc\",\n  \"page_view.h\",\n  \"rubberband_distance.cc\",\n  \"rubberband_distance.h\",\n  \"scroll_view.cc\",\n  \"scroll_view.h\",\n  \"scroll_wrapper.cc\",\n  \"scroll_wrapper.h\",\n  \"scrollable.cc\",\n  \"scrollable.h\",\n  \"scrollbar/scrollbar_orientation_helper.cc\",\n  \"scrollbar/scrollbar_orientation_helper.h\",\n  \"scrollbar/scrollbar_view.cc\",\n  \"scrollbar/scrollbar_view.h\",\n  \"scrollbar/scrollbar_wrapper.cc\",\n  \"scrollbar/scrollbar_wrapper.h\",\n  \"scroller.cc\",\n  \"scroller.h\",\n  \"scroller_animator.cc\",\n  \"scroller_animator.h\",\n  \"soft_keyboard_resources.cc\",\n  \"soft_keyboard_resources.h\",\n  \"tappable_image_view.h\",\n  \"text/base_text_view.cc\",\n  \"text/base_text_view.h\",\n  \"text/inline_text_view.cc\",\n  \"text/inline_text_view.h\",\n  \"text/internal_text_view.cc\",\n  \"text/internal_text_view.h\",\n  \"text/layout_client.h\",\n  \"text/layout_context.cc\",\n  \"text/layout_context.h\",\n  \"text/raw_text_view.cc\",\n  \"text/raw_text_view.h\",\n  \"text/tappable_text_view.h\",\n  \"text/text_paragraph_builder.cc\",\n  \"text/text_paragraph_builder.h\",\n  \"text/text_style.h\",\n  \"text/text_view.cc\",\n  \"text/text_view.h\",\n  \"text/tttext_headers.h\",\n  \"text/unicode_util.h\",\n  \"view.cc\",\n  \"view.h\",\n  \"view_callback/list_container_event_callback_manager.cc\",\n  \"view_callback/list_container_event_callback_manager.h\",\n  \"view_callback/scroll_event_callback_manager.cc\",\n  \"view_callback/scroll_event_callback_manager.h\",\n  \"view_context.cc\",\n  \"view_context.h\",\n  \"view_registry.cc\",\n  \"view_registry.h\",\n  \"view_tree_observer.cc\",\n  \"view_tree_observer.h\",\n]\n\nif (is_mac || is_win) {\n  ui_component_sources += [\n    \"title_bar_view.cc\",\n    \"title_bar_view.h\",\n  ]\n}\n\nif (!enable_clay_lite) {\n  ui_component_sources += [\n    \"list/base_list_view.cc\",\n    \"list/base_list_view.h\",\n    \"list/list_adapter.cc\",\n    \"list/list_adapter.h\",\n    \"list/list_adapter_helper.cc\",\n    \"list/list_adapter_helper.h\",\n    \"list/list_adapter_updater.cc\",\n    \"list/list_adapter_updater.h\",\n    \"list/list_children_helper.cc\",\n    \"list/list_children_helper.h\",\n    \"list/list_item_length_cache.cc\",\n    \"list/list_item_length_cache.h\",\n    \"list/list_item_view.cc\",\n    \"list/list_item_view.h\",\n    \"list/list_item_view_holder.cc\",\n    \"list/list_item_view_holder.h\",\n    \"list/list_layout_manager.cc\",\n    \"list/list_layout_manager.h\",\n    \"list/list_layout_manager_grid.cc\",\n    \"list/list_layout_manager_grid.h\",\n    \"list/list_layout_manager_linear.cc\",\n    \"list/list_layout_manager_linear.h\",\n    \"list/list_layout_manager_staggered_grid.cc\",\n    \"list/list_layout_manager_staggered_grid.h\",\n    \"list/list_orientation_helper.cc\",\n    \"list/list_orientation_helper.h\",\n    \"list/list_recycler.cc\",\n    \"list/list_recycler.h\",\n    \"list/list_scroller.cc\",\n    \"list/list_scroller.h\",\n    \"list/list_view.cc\",\n    \"list/list_view.h\",\n    \"list/list_wrapper.cc\",\n    \"list/list_wrapper.h\",\n    \"list/lynx_list_adapter.cc\",\n    \"list/lynx_list_adapter.h\",\n    \"list/lynx_list_data.cc\",\n    \"list/lynx_list_data.h\",\n    \"list/lynx_list_item_view_holder.cc\",\n    \"list/lynx_list_item_view_holder.h\",\n    \"list/macros.h\",\n    \"view_callback/list_event_callback_manager.cc\",\n    \"view_callback/list_event_callback_manager.h\",\n  ]\n}\n\nui_extended_component_sources = [\n  \"editable/editable_view.cc\",\n  \"editable/editable_view.h\",\n  \"editable/ime_listener.h\",\n  \"editable/ime_utils.cc\",\n  \"editable/ime_utils.h\",\n  \"editable/input_ng_view.cc\",\n  \"editable/input_ng_view.h\",\n  \"editable/input_view.cc\",\n  \"editable/input_view.h\",\n  \"editable/text_editing_controller.cc\",\n  \"editable/text_editing_controller.h\",\n  \"editable/text_input_controller.cc\",\n  \"editable/text_input_controller.h\",\n  \"editable/text_span.cc\",\n  \"editable/text_span.h\",\n  \"editable/text_utils.h\",\n  \"editable/textarea_ng_view.cc\",\n  \"editable/textarea_ng_view.h\",\n  \"editable/textarea_view.cc\",\n  \"editable/textarea_view.h\",\n  \"overlay_view.cc\",\n  \"overlay_view.h\",\n  \"selection_handle_view.cc\",\n  \"selection_handle_view.h\",\n  \"selection_popup_view.cc\",\n  \"selection_popup_view.h\",\n]\n\nui_component_unittests_sources = []\n\nif (!enable_clay_lite) {\n  ui_component_unittests_sources += [\n    \"list/focus_list_adapter.cc\",\n    \"list/focus_list_adapter.h\",\n    \"list/focus_list_item_view_holder.cc\",\n    \"list/focus_list_item_view_holder.h\",\n    \"list/focus_list_view.cc\",\n    \"list/focus_list_view.h\",\n  ]\n}\n"
  },
  {
    "path": "clay/ui/component/builtin_views.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"clay/ui/component/builtin_views.h\"\n\n#include \"clay/ui/component/component.h\"\n\n#ifndef ENABLE_CLAY_LITE\n#include \"clay/ui/component/editable/input_ng_view.h\"\n#include \"clay/ui/component/editable/input_view.h\"\n#include \"clay/ui/component/editable/textarea_ng_view.h\"\n#include \"clay/ui/component/editable/textarea_view.h\"\n#include \"clay/ui/component/list/list_item_view.h\"\n#include \"clay/ui/component/list/list_wrapper.h\"\n#include \"clay/ui/shadow/editable_shadow_node.h\"\n#endif  // ENABLE_CLAY_LITE\n\n#include \"clay/ui/component/image_view.h\"\n#include \"clay/ui/component/list/list_container/list_container_wrapper.h\"\n#include \"clay/ui/component/scroll_wrapper.h\"\n#include \"clay/ui/component/text/inline_text_view.h\"\n#include \"clay/ui/component/text/raw_text_view.h\"\n#include \"clay/ui/component/text/text_view.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/component/view_registry.h\"\n#include \"clay/ui/shadow/image_shadow_node.h\"\n#include \"clay/ui/shadow/inline_text_shadow_node.h\"\n#include \"clay/ui/shadow/raw_text_shadow_node.h\"\n#include \"clay/ui/shadow/text_shadow_node.h\"\n\n#if (defined(OS_MAC) || defined(OS_WIN))\n#include \"clay/ui/component/title_bar_view.h\"\n#endif\n\nnamespace clay {\n\nvoid keepBuiltinElements() {}\n\nREGISTER_CLAY_ELEMENT(\"view\", View, void);\nREGISTER_CLAY_ELEMENT(\"image\", ImageView, ImageShadowNode);\nREGISTER_CLAY_ELEMENT(\"text\", TextView, TextShadowNode);\nREGISTER_CLAY_ELEMENT(\"x-text\", TextView, TextShadowNode);\nREGISTER_CLAY_ELEMENT(\"raw-text\", RawTextView, RawTextShadowNode);\nREGISTER_CLAY_ELEMENT(\"inline-text\", InlineTextView, InlineTextShadowNode);\nREGISTER_CLAY_ELEMENT(\"x-inline-text\", InlineTextView, InlineTextShadowNode);\nREGISTER_CLAY_ELEMENT(\"inline-image\", InlineImageView, InlineImageShadowNode);\nREGISTER_CLAY_ELEMENT(\"x-inline-image\", InlineImageView, InlineImageShadowNode);\nREGISTER_CLAY_ELEMENT(\"inline-view\", View, void);\nREGISTER_CLAY_ELEMENT(\"inline-truncation\", View, InlineTruncationShadowNode);\nREGISTER_CLAY_ELEMENT(\"x-inline-truncation\", View, InlineTruncationShadowNode);\nREGISTER_CLAY_ELEMENT(\"scroll-view\", ScrollWrapper, void);\nREGISTER_CLAY_ELEMENT(\"x-scroll-view\", ScrollWrapper, void);\nREGISTER_CLAY_ELEMENT(\"component\", Component, void);\nREGISTER_CLAY_ELEMENT(\"list-container\", ListContainerWrapper, void);\n#ifndef ENABLE_CLAY_LITE\nREGISTER_CLAY_ELEMENT(\"list-item\", ListItemView, void);\nREGISTER_CLAY_ELEMENT(\"x-input-ng\", InputNGView, EditableShadowNode);\nREGISTER_CLAY_ELEMENT(\"x-textarea-ng\", TextAreaNGView, EditableShadowNode);\nREGISTER_CLAY_ELEMENT(\"x-textarea\", TextAreaView, EditableShadowNode);\nREGISTER_CLAY_ELEMENT(\"textarea\", TextAreaView, EditableShadowNode);\nREGISTER_CLAY_ELEMENT(\"input\", InputView, EditableShadowNode);\nREGISTER_CLAY_ELEMENT(\"x-input\", InputView, EditableShadowNode);\nREGISTER_CLAY_ELEMENT(\"list\", ListWrapper, void);\n#else\nREGISTER_CLAY_ELEMENT(\"list\", ListContainerWrapper, void);\n#endif  // ENABLE_CLAY_LITE\n\n#if (defined(OS_MAC) || defined(OS_WIN))\nREGISTER_CLAY_ELEMENT(\"title-bar-view\", TitleBarView, void);\n#endif\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/builtin_views.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_UI_COMPONENT_BUILTIN_VIEWS_H_\n#define CLAY_UI_COMPONENT_BUILTIN_VIEWS_H_\n\nnamespace clay {\n\n// NOTE: This function must be referenced in another translation unit which will\n// be linked.\nvoid keepBuiltinElements();\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_BUILTIN_VIEWS_H_\n"
  },
  {
    "path": "clay/ui/component/component.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/component.h\"\n\n#include <memory>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/rendering/render_container.h\"\n\nnamespace clay {\n\nComponent::Component(int id, PageView* page_view)\n    : WithTypeInfo(id, \"component\", std::make_unique<RenderContainer>(),\n                   page_view) {}\n\nComponent::~Component() = default;\n\nvoid Component::SetAttribute(const char* attr_c, const clay::Value& value) {\n  // For the normal BaseView, the z-order has been set by the lynx side (the\n  // order that has been layout). But considering the ListView, the order of\n  // child should be the index.\n  if (GetKeywordID(attr_c) == KeywordID::kZIndex) {\n    SetPaintingOrder(attribute_utils::GetInt(value));\n    return;\n  }\n  BaseView::SetAttribute(attr_c, value);\n}\n\nvoid Component::OnNodeReady() {\n  BaseView::OnNodeReady();\n  if (node_ready_listener_) {\n    node_ready_listener_->OnComponentNodeReady(this);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/component.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_COMPONENT_H_\n#define CLAY_UI_COMPONENT_COMPONENT_H_\n\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nclass Component : public WithTypeInfo<Component, BaseView> {\n public:\n  class NodeReadyListener {\n   public:\n    virtual void OnComponentNodeReady(Component* component) = 0;\n  };\n\n  Component(int id, PageView* page_view);\n  ~Component() override;\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n\n  void SetNodeReadyListener(NodeReadyListener* node_ready_listener) {\n    node_ready_listener_ = node_ready_listener;\n  }\n\n  void OnNodeReady() override;\n\n private:\n  NodeReadyListener* node_ready_listener_{nullptr};\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_COMPONENT_H_\n"
  },
  {
    "path": "clay/ui/component/component_constants.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/component_constants.h\"\n\nnamespace clay {\n\nnamespace attr_value {\nconst char* kTrue = \"true\";\nconst char* kFalse = \"false\";\nconst char* kYes = \"yes\";\nconst char* kNo = \"no\";\nconst char* kModeScaleToFill = \"scaleToFill\";\nconst char* kModeAspectFit = \"aspectFit\";\nconst char* kModeAspectFill = \"aspectFill\";\nconst char* kModeCenter = \"center\";\nconst char* kTextOverflowClip = \"clip\";\nconst char* kListTypeWaterFall = \"waterfall\";\nconst char* kListTypeFlow = \"flow\";\nconst char* kListTypeSingle = \"single\";\nconst char* kListUpdateAnimationDefault = \"default\";\nconst char* kImageTransitionFadeIn = \"fadeIn\";\n\nconst char* kInputTypeText = \"text\";\nconst char* kInputTypeNumber = \"number\";\nconst char* kInputTypeDigit = \"digit\";\nconst char* kInputTypePassword = \"password\";\nconst char* kInputTypeTel = \"tel\";\nconst char* kInputTypeEmail = \"email\";\n\nconst char* kInputActionSend = \"send\";\nconst char* kInputActionSearch = \"search\";\nconst char* kInputActionGo = \"go\";\nconst char* kInputActionDone = \"done\";\nconst char* kInputActionNext = \"next\";\n\nconst char* kVideoViewObjectFitContain = \"contain\";\nconst char* kVideoViewObjectFitCover = \"cover\";\nconst char* kVideoViewObjectFitFill = \"fill\";\n\nconst char* kAppRegionDrag = \"drag\";\nconst char* kAppRegionNoDrag = \"no-drag\";\n}  // namespace attr_value\n\nnamespace event_attr {\nconst char* kEventDetail = \"detail\";\nconst char* kEventFocus = \"focus\";\nconst char* kEventBlur = \"blur\";\nconst char* kEventTap = \"tap\";\nconst char* kEventLongPress = \"longpress\";\nconst char* kEventScroll = \"scroll\";\nconst char* kEventScrollToUpper = \"scrolltoupper\";\nconst char* kEventScrollToLower = \"scrolltolower\";\nconst char* kEventScrollToBounce = \"scrolltobounce\";\nconst char* kEventScrollStart = \"scrollstart\";\nconst char* kEventScrollEnd = \"scrollend\";\nconst char* kEventContentSizeChanged = \"contentsizechanged\";\nconst char* kEventScrollStateChange = \"scrollstatechange\";\nconst char* kEventNodeAppear = \"nodeappear\";\nconst char* kEventNodeDisappear = \"nodedisappear\";\nconst char* kEventListLayoutComplete = \"layoutcomplete\";\nconst char* kEventListStickyStart = \"stickystart\";\nconst char* kEventListStickyEnd = \"stickyend\";\nconst char* kEventListStickyTop = \"stickytop\";\nconst char* kEventListStickyBottom = \"stickybottom\";\nconst char* kEventLayoutChange = \"layoutchange\";\nconst char* kEventMouseLongPress = \"mouselongpress\";\n\nconst char* kEventEditInput = \"input\";\nconst char* kEventEditSelectionChange = \"selectionchange\";\nconst char* kEventEditConfirm = \"confirm\";\n\nconst char* kEventImageLoadSuccess = \"load\";\nconst char* kEventImageLoadError = \"error\";\nconst char* kEventImageStartPlay = \"startplay\";\nconst char* kEventImageCurrentLoopComplete = \"currentloopcomplete\";\nconst char* kEventImageFinalLoopComplete = \"finalloopcomplete\";\nconst char* kEventBgImageLoadSuccess = \"bgload\";\nconst char* kEventBgImageLoadError = \"bgerror\";\n\nconst char* kEventLottieStart = \"start\";\nconst char* kEventLottieComplete = \"completion\";\nconst char* kEventLottieRepeat = \"repeat\";\nconst char* kEventLottieCancel = \"cancel\";\nconst char* kEventLottieReady = \"ready\";\nconst char* kEventLottieUpdate = \"update\";\nconst char* kEventLottieFPS = \"fps\";\nconst char* kEventLottieFirstFrame = \"firstframe\";\nconst char* kEventLottieError = \"error\";\n\nconst char* kEventIntersection = \"intersection\";\n\nconst char* kEventVideoPlay = \"play\";\nconst char* kEventVideoPause = \"pause\";\nconst char* kEventVideoResume = \"resume\";\nconst char* kEventVideoEnded = \"ended\";\nconst char* kEventVideoError = \"error\";\nconst char* kEventVideoStop = \"stop\";\nconst char* kEventVideoTimeUpdate = \"timeupdate\";\nconst char* kEventVideoBufferingChange = \"bufferingchange\";\nconst char* kEventVideoSeek = \"seek\";\nconst char* kEventVideoCanPlay = \"canplay\";\nconst char* kEventVideoLoadedMetadata = \"loadedmetadata\";\nconst char* kEventVideoPlaying = \"playing\";\nconst char* kEventVideoWaiting = \"waiting\";\nconst char* kEventVideoLoadStateChange = \"loadstatechange\";\nconst char* kEventVideoPlaybackStateChanged = \"playbackstatechanged\";\nconst char* kEventVideoAbort = \"abort\";\nconst char* kEventVideoCanplayThrough = \"canplaythrough\";\nconst char* kEventVideoDurationChange = \"durationchange\";\nconst char* kEventVideoEmptied = \"emptied\";\nconst char* kEventVideoLoadeddata = \"loadeddata\";\nconst char* kEventVideoLoadStart = \"loadstart\";\nconst char* kEventVideoProgress = \"progress\";\nconst char* kEventVideoRateChange = \"ratechange\";\nconst char* kEventVideoSeeked = \"seeked\";\nconst char* kEventVideoSeeking = \"seeking\";\nconst char* kEventVideoStalled = \"stalled\";\nconst char* kEventVideoSuspend = \"suspend\";\nconst char* kEventVideoVolumeChange = \"volumechange\";\nconst char* kEventVideoFirstFrame = \"firstframe\";\nconst char* kEventVideoVideoInfos = \"videoinfos\";\nconst char* kEventVideoCacheInfo = \"cacheinfo\";\nconst char* kEventVideoBuffering = \"buffering\";\nconst char* kEventVideoStart = \"start\";\nconst char* kEventVideoPrepare = \"prepare\";\nconst char* kEventVideoReady = \"ready\";\nconst char* kEventVideoCompletion = \"completion\";\nconst char* kEventVideoUpdate = \"update\";\n\nconst char* kEventCameraFrame = \"frame\";\nconst char* kEventCameraError = \"error\";\nconst char* kEventCameraReady = \"ready\";\nconst char* kEventCameraStop = \"stop\";\n\nconst char* kEventAnimationStart = \"animationstart\";\nconst char* kEventAnimationEnd = \"animationend\";\nconst char* kEventAnimationCancel = \"animationcancel\";\nconst char* kEventAnimationIteration = \"animationiteration\";\nconst char* kEventTransitionStart = \"transitionstart\";\nconst char* kEventTransitionEnd = \"transitionend\";\n\nconst char* kEventChange = \"change\";\nconst char* kEventWillChange = \"willchange\";\nconst char* kEventTransition = \"transition\";\nconst char* kEventOffsetChange = \"offsetchange\";\n\nconst char* kEventRefreshStartRefresh = \"startrefresh\";\nconst char* kEventRefreshHeaderOffset = \"headeroffset\";\nconst char* kEventRefreshStateChange = \"refreshstatechange\";\n// \"startloadmore\", \"headerreleased\", \"footerreleased\" will be deprecated\nconst char* kEventRefreshStartLoadmore = \"startloadmore\";\nconst char* kEventRefreshHeaderReleased = \"headerreleased\";\nconst char* kEventRefreshFooterReleased = \"footerreleased\";\n\nconst char* kEventSliderChange = \"change\";\nconst char* kEventSliderChanging = \"changing\";\n\nconst char* kEventLayout = \"layout\";\nconst char* kEventExit = \"exit\";\nconst char* kEventOffset = \"offset\";\n\nconst char* kEventFocusEscape = \"focusescape\";\nconst char* kEventFocusEnter = \"focusenter\";\n\nconst char* kEventMarkdownDrawStart = \"drawStart\";\nconst char* kEventMarkdownDrawEnd = \"drawEnd\";\nconst char* kEventMarkdownOverflow = \"overflow\";\nconst char* kEventMarkdownLink = \"link\";\nconst char* kEventMarkdownImageTap = \"imageTap\";\nconst char* kEventMarkdownParseEnd = \"parseEnd\";\nconst char* kEventMarkdownAnimationStep = \"animationStep\";\nconst char* kEventMarkdownTextClick = \"textClick\";\n\nconst char* kEventShowOverlay = \"showoverlay\";\nconst char* kEventDismissOverlay = \"dismissoverlay\";\nconst char* kEventRequestClose = \"requestclose\";\nconst char* kEventOverlayTouch = \"overlaytouch\";\n\n}  // namespace event_attr\n\nnamespace video_res {\nconst char* kPlayBtn =\n    \"data:image/\"\n    \"png;base64,\"\n    \"iVBORw0KGgoAAAANSUhEUgAAAE4AAABWCAYAAABhL6DrAAAACXBIWXMAABCcAAAQnAEmzTo0AA\"\n    \"AAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAVASURBVHgB7ZzNSyN3GMefmOgodivuBmts\"\n    \"xYNuC6JQYunqQbqn1oN72ku92FP/\"\n    \"JI9ePTQgXRDxUCGlLbQGjboh664plFobKUZjopL39HlwfrNjNpmdTOZ9fh8IGRNB88nzfH8vkw\"\n    \"wARxM+Nb9Ur9cFvBvD2wPxoQzdfD5fHjxKQOlJFDZSrVZn8DDU4vk03r1GgW/\"\n    \"AYzStOKqwSqUyFwgEPgV15IvF4l5vb+9r8AjviENp1I5f4+0RtI9nBDYT9xy0SZND2bfr5ha+\"\n    \"J65QKHwmCMJXoB+\"\n    \"UgVE3DiL3xGG1LcHbkVNPqPJ23SRQGlVzuVwQjJFG0CATwjcmgfISeFwHhyNVHL6oabybA+\"\n    \"NxRf51sQOcfhhVbY3Q33mKb9RTcQR3JPKKewYtJroG48j8YxWnaullEJR/\"\n    \"izSig4NgwnxYcd+CcYODWqjqfsbq+xdsjpRxuCa1suoY9MYtOiH/\"\n    \"usCeUPsulUqlL46PjwWwIVKr4qj6nd/v7wH7Ycv1rzzjvgd7Q/\"\n    \"m3YZfR166t2gzKvCW75J+TxDEo/55T/\"\n    \"oGFBMCZ9HR3d4ex8h6DRcs3J1acHLZ8e2Z2+\"\n    \"zpdHIOWiqbmn1NbtRXy7auXYCBuqTg5VHFztCmLN7Unm9rGjeIY0vZVJpP5EHTGSRPgjsCVUQJ\"\n    \"Pd77UawLt5oq7B0qbgrsNBF3a1zPiRFj7Uv6NQAd4TRyj4+\"\n    \"0rr4pjSNtX0CZum8dpQsvyzesVJ4fl3zdq2peLexf6HOB725eLa4HYvkutqo+\"\n    \"LU4Ztns40PsHFqWNGbF3pTCAXpxJqXZQnVR4X1wYk7/\"\n    \"b29mM65uLapK+\"\n    \"vL4x3Pi6ufUK5XO4RF6cBQRBGuDgNVKvVB1ycBnCQ6OHiNFAul0tcnAZqtdo1F6eBs7OzNBfXJ\"\n    \"jgwXE9MTJxzcW2SzWbjeFfn4toAJ777wWCQPuDIxamFpA0MDOziYY1+\"\n    \"5uJUgO25I0qrsse4OAVoIEgkEi8GBwcPQSaN4OJakMlkDjY3N3+cnp7+\"\n    \"D8T2lMNPDzZQLBbPotHorwsLC1loIozBxYlQW6bT6Z3R0dG/\"\n    \"4K4tFb8a6vlWRWElasu1tbUXKO1PfKgCKr5P6+mKu7m5OYnH47/\"\n    \"Pz89fgUJbNsOT4ijHksnkQTgc/gcaRku1eEoctWU+n9/\"\n    \"H6QV9vf29OaaEZzJOzLEISjsAlTmmhOsrjtoyFov9oiXHlHCtOJpeHB4e/\"\n    \"tZJjinhOnGUYxcXFwdDQ0P0PYeOckwJV2Xc5eXlK8oxlKZLjinhioqjHEulUvtTU1PUlrrlmBK\"\n    \"OFifLsVMwsC2b4chWlS+\"\n    \"TUNrfYHBbNsNxFVcoFFKRSOSP5eXlW7DwGk2OESdbJp2ASTmmhCSOyt+OV4Gg/\"\n    \"+v09DQ2NjZGJ0lMzTEl5BlXAhshy7EISkuCBTmmhFRxWG22+\"\n    \"aeMWibpia0yDqvs4ujoaAfnY2x6YVvkGXeDVWfJ9Tz03O4xCynj6vV6ESwAhSX13O4xC1Zx9XK\"\n    \"5fB0ImNe5as8m2RX54GDKNYsalkkVcCiSONyKSQ8PD4NRUI5ls9lXwWCQfZTA0VdmlTIuFApl6\"\n    \"MWBAbDtHpQWAwflmBLyUKvhOjDe39//\"\n    \"BHTCiu0es2i8vGNXpVJZxLzrqGet3O4xi8Ztpdrq6upP9MJBA2KO7eDo/\"\n    \"INV2z1m4W98YGNjo4ytlRofH+9HAQ9BBSQMcyyxvr4enZ2dtf2sXw+UrsTq39vb+\"\n    \"2RycvJzQRA+avYLlGG4ED/Z3t5+Y/X+mN0gsYGtra2HuBf2+Pz8/\"\n    \"MnV1dWX6XR6cmVl5QO4G1zscBlc0/kffo+5rAUmuPwAAAAASUVORK5CYII=\";\nconst char* kPauseBtn =\n    \"data:image/\"\n    \"png;base64,\"\n    \"iVBORw0KGgoAAAANSUhEUgAAAEYAAABICAYAAABLJIP0AAAACXBIWXMAABCcAAAQnAEmzTo0AA\"\n    \"AAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAHuSURBVHgB7dzNTsJAFAXgW0pwBZu68R10\"\n    \"QXRlwkvoc2oiLnTpjhW4QQlPIC5s/\"\n    \"EsITes4g60hxLMw3JmkzfmSRpCknRzunWGSggj9KaoeGGOSoihO4zg+\"\n    \"ED1P9riLouhDlIQaZ1RerGsvdm4v1hF9mT0uNMJx47R/zuyxJ/\"\n    \"qyNE0vkyR5d09a5T+PPYXidPI8PxIF9jwn4icUp9Pr9Q6rJ1UwXfHIVsu+\"\n    \"bLTtDufxOs52u51Uj1tSHzsH+5/\"\n    \"rhAzGSI0ECcbOXy6UUO+4iiDB2BXPhcKKaYKQwbCVALZSEzAYIORyXSshl+\"\n    \"taYSsBDAZgMACDARgMwGAA7pUA7pUAthLAVgJC7pXYStu4V2oQBgMwGICrEsAPeABbCWAwAIMB\"\n    \"OPkCnHwBVgzAigE4+\"\n    \"QIMBuAcA7BiAAYDcFUCOMcArBiAcwzAO8MB3hkOsJUABgMwGIDBAAwG4CdfgBUDcEsAsGIAfvs\"\n    \"E4B1VADeRADeRACdfgK0EsJUAthKwDma5XKbikTFG46eYTJZlL+\"\n    \"JRnuefUlb2Opj5fP5gyz0TD9x5p9PpvSgYj8ePoca5Dqbf77+NRqMr++\"\n    \"JCFK1Wq8VkMrl15xcFg8HgdTgcXtvKeRZFbpyz2exmc5zbK0UsuvPOV3loTrxuzC3RHWchP+\"\n    \"P89Q1luasMCocSlwAAAABJRU5ErkJggg==\";\n}  // namespace video_res\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/component_constants.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_COMPONENT_CONSTANTS_H_\n#define CLAY_UI_COMPONENT_COMPONENT_CONSTANTS_H_\n\n#include <memory>\n#include <string>\n\nnamespace clay {\n\nnamespace attr_value {\n\nextern const char* kTrue;\nextern const char* kFalse;\nextern const char* kYes;\nextern const char* kNo;\nextern const char* kModeScaleToFill;\nextern const char* kModeAspectFit;\nextern const char* kModeAspectFill;\nextern const char* kModeCenter;\nextern const char* kTextOverflowClip;\nextern const char* kListTypeWaterFall;\nextern const char* kListTypeFlow;\nextern const char* kListTypeSingle;\nextern const char* kListUpdateAnimationDefault;\nextern const char* kImageTransitionFadeIn;\n\n// for input\nextern const char* kInputTypeText;\nextern const char* kInputTypeNumber;\nextern const char* kInputTypeDigit;\nextern const char* kInputTypePassword;\nextern const char* kInputTypeTel;\nextern const char* kInputTypeEmail;\nextern const char* kInputActionSend;\nextern const char* kInputActionSearch;\nextern const char* kInputActionGo;\nextern const char* kInputActionDone;\nextern const char* kInputActionNext;\n\n// video - objectfit\nextern const char* kVideoViewObjectFitContain;\nextern const char* kVideoViewObjectFitCover;\nextern const char* kVideoViewObjectFitFill;\n\n// app region\nextern const char* kAppRegionDrag;\nextern const char* kAppRegionNoDrag;\n}  // namespace attr_value\n\nnamespace event_attr {\nextern const char* kEventDetail;\n\nextern const char* kEventFocus;\nextern const char* kEventBlur;\nextern const char* kEventTap;\nextern const char* kEventLongPress;\nextern const char* kEventScroll;\nextern const char* kEventScrollToUpper;\nextern const char* kEventScrollToLower;\nextern const char* kEventScrollToBounce;\nextern const char* kEventScrollStart;\nextern const char* kEventScrollEnd;\nextern const char* kEventContentSizeChanged;\nextern const char* kEventScrollStateChange;\nextern const char* kEventNodeAppear;\nextern const char* kEventNodeDisappear;\nextern const char* kEventListLayoutComplete;\nextern const char* kEventListStickyStart;\nextern const char* kEventListStickyEnd;\nextern const char* kEventListStickyTop;\nextern const char* kEventListStickyBottom;\nextern const char* kEventLayoutChange;\nextern const char* kEventMouseLongPress;\n\nextern const char* kEventEditInput;\nextern const char* kEventEditSelectionChange;\nextern const char* kEventEditConfirm;\n\nextern const char* kEventImageLoadSuccess;\nextern const char* kEventImageLoadError;\nextern const char* kEventImageStartPlay;\nextern const char* kEventImageCurrentLoopComplete;\nextern const char* kEventImageFinalLoopComplete;\nextern const char* kEventBgImageLoadSuccess;\nextern const char* kEventBgImageLoadError;\n\nextern const char* kEventLottieStart;\nextern const char* kEventLottieComplete;\nextern const char* kEventLottieRepeat;\nextern const char* kEventLottieCancel;\nextern const char* kEventLottieReady;\nextern const char* kEventLottieUpdate;\nextern const char* kEventLottieFPS;\nextern const char* kEventLottieFirstFrame;\nextern const char* kEventLottieError;\n\nextern const char* kEventIntersection;\n\n// video\nextern const char* kEventVideoPlay;\nextern const char* kEventVideoPause;\nextern const char* kEventVideoResume;\nextern const char* kEventVideoEnded;\nextern const char* kEventVideoError;\nextern const char* kEventVideoStop;\nextern const char* kEventVideoStalled;\nextern const char* kEventVideoTimeUpdate;\nextern const char* kEventVideoBufferingChange;\nextern const char* kEventVideoSeek;\nextern const char* kEventVideoCanPlay;\nextern const char* kEventVideoLoadedMetadata;\nextern const char* kEventVideoPlaying;\nextern const char* kEventVideoWaiting;\nextern const char* kEventVideoLoadStateChange;\nextern const char* kEventVideoPlaybackStateChanged;\nextern const char* kEventVideoAbort;\nextern const char* kEventVideoCanplayThrough;\nextern const char* kEventVideoDurationChange;\nextern const char* kEventVideoEmptied;\nextern const char* kEventVideoLoadeddata;\nextern const char* kEventVideoLoadStart;\nextern const char* kEventVideoProgress;\nextern const char* kEventVideoRateChange;\nextern const char* kEventVideoSeeked;\nextern const char* kEventVideoSeeking;\nextern const char* kEventVideoStalled;\nextern const char* kEventVideoSuspend;\nextern const char* kEventVideoVolumeChange;\nextern const char* kEventVideoFirstFrame;\nextern const char* kEventVideoVideoInfos;\nextern const char* kEventVideoCacheInfo;\nextern const char* kEventVideoBuffering;\nextern const char* kEventVideoStart;\nextern const char* kEventVideoPrepare;\nextern const char* kEventVideoReady;\nextern const char* kEventVideoCompletion;\nextern const char* kEventVideoUpdate;\n\n// camera\nextern const char* kEventCameraFrame;\nextern const char* kEventCameraError;\nextern const char* kEventCameraReady;\nextern const char* kEventCameraStop;\n\nextern const char* kEventAnimationStart;\nextern const char* kEventAnimationEnd;\nextern const char* kEventAnimationCancel;\nextern const char* kEventAnimationIteration;\nextern const char* kEventTransitionStart;\nextern const char* kEventTransitionEnd;\n\nextern const char* kEventChange;\nextern const char* kEventWillChange;\nextern const char* kEventTransition;\nextern const char* kEventOffsetChange;\n\nextern const char* kEventRefreshStartRefresh;\nextern const char* kEventRefreshHeaderOffset;\nextern const char* kEventRefreshStateChange;\n// \"startloadmore\", \"headerreleased\", \"footerreleased\" will be deprecated\nextern const char* kEventRefreshStartLoadmore;\nextern const char* kEventRefreshHeaderReleased;\nextern const char* kEventRefreshFooterReleased;\n\nextern const char* kEventSliderChange;\nextern const char* kEventSliderChanging;\n\nextern const char* kEventLayout;\nextern const char* kEventExit;\nextern const char* kEventOffset;\n\nextern const char* kEventFocusEscape;\nextern const char* kEventFocusEnter;\n\nextern const char* kEventMarkdownDrawStart;\nextern const char* kEventMarkdownDrawEnd;\nextern const char* kEventMarkdownOverflow;\nextern const char* kEventMarkdownLink;\nextern const char* kEventMarkdownImageTap;\nextern const char* kEventMarkdownParseEnd;\nextern const char* kEventMarkdownAnimationStep;\nextern const char* kEventMarkdownTextClick;\n\n// x-overlay-ng events\nextern const char* kEventShowOverlay;\nextern const char* kEventDismissOverlay;\nextern const char* kEventRequestClose;\nextern const char* kEventOverlayTouch;\n}  // namespace event_attr\n\nnamespace color_value {\nconstexpr uint32_t kColorWhite = 0xffffffff;\nconstexpr uint32_t kColorGreen = 0xff00ff00;\n}  // namespace color_value\n\nnamespace num_value {\nconstexpr int kFocusRingThickness = 10;\n}\n\nnamespace app_region_value {\nconstexpr int kAppRegionDrag = 1;\nconstexpr int kAppRegionNoDrag = 2;\n}  // namespace app_region_value\n\nnamespace video_res {\nextern const char* kPlayBtn;\nextern const char* kPauseBtn;\n}  // namespace video_res\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_COMPONENT_CONSTANTS_H_\n"
  },
  {
    "path": "clay/ui/component/css_property.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/css_property.h\"\n\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/style/length.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/background_data.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\nnamespace utils = attribute_utils;\n\nstatic Length GetLength(const clay::Value& value, const clay::Value& type) {\n  double length_value = utils::GetDouble(value);\n  LengthUnit length_type = static_cast<LengthUnit>(utils::GetInt(type));\n  return Length(length_value, length_type);\n}\n\nstatic std::vector<Length> GetBorderRadius(const clay::Value::Array& array) {\n  std::vector<Length> values;\n  if (array.size() % 2 != 0) {\n    FML_DCHECK(false);\n    return values;\n  }\n\n  for (size_t i = 0; i < array.size() / 2; i++) {\n    values.emplace_back(GetLength(array[i * 2], array[i * 2 + 1]));\n  }\n  return values;\n}\n\nbool CSSProperty::SetAttribute(BaseView* view, KeywordID property_id,\n                               const clay::Value& value) {\n  switch (property_id) {\n    case KeywordID::kOpacity: {\n      view->SetOpacity(utils::GetDouble(value, 1.0f));\n    } break;\n    case KeywordID::kBackgroundColor: {\n      view->SetBackgroundColor(Color(utils::GetUint(value, 0)));\n    } break;\n    case KeywordID::kBackgroundImage: {\n      view->SetBackgroundImage(utils::GetArray(value));\n    } break;\n    case KeywordID::kBackgroundClip: {\n      view->SetBackgroundClip(utils::GetArray(value));\n    } break;\n    case KeywordID::kBackgroundOrigin: {\n      view->SetBackgroundOrigin(utils::GetArray(value));\n    } break;\n\n    case KeywordID::kBackgroundPosition: {\n      const auto& array = utils::GetArray(value);\n      FML_DCHECK(array.size() % 2 == 0);\n      if (array.size() % 2 == 0) {\n        std::vector<BackgroundPosition> positions;\n        for (size_t i = 0; i < array.size(); i += 2) {\n          // For keyword values, Lynx is responsible for converting them to\n          // {value, kPercent} format. For example, 'top' would be converted to\n          // {0.f, kPercent}\n          double value = utils::GetDouble(array[i]);\n          ClayPlatformLengthUnit type =\n              static_cast<ClayPlatformLengthUnit>(utils::GetInt(array[i + 1]));\n          positions.emplace_back(value, type);\n        }\n        view->SetBackgroundPosition(std::move(positions));\n      }\n    } break;\n\n    case KeywordID::kBackgroundRepeat: {\n      view->SetBackgroundRepeat(utils::GetArray(value));\n    } break;\n\n    case KeywordID::kBackgroundSize: {\n      const auto& array = utils::GetArray(value);\n      FML_DCHECK(array.size() % 2 == 0);\n      if (array.size() % 2 == 0) {\n        std::vector<BackgroundSize> sizes;\n        for (size_t i = 0; i < array.size(); i += 2) {\n          double value = utils::GetDouble(array[i]);\n          ClayPlatformLengthUnit type =\n              static_cast<ClayPlatformLengthUnit>(utils::GetInt(array[i + 1]));\n          sizes.emplace_back(value, type);\n        }\n        view->SetBackgroundSize(std::move(sizes));\n      }\n    } break;\n\n    case KeywordID::kMaskImage: {\n      view->SetMaskImage(utils::GetArray(value));\n    } break;\n    case KeywordID::kMaskPosition: {\n      const auto& array = utils::GetArray(value);\n      FML_DCHECK(array.size() % 2 == 0);\n      if (array.size() % 2 == 0) {\n        std::vector<MaskPosition> positions;\n        for (size_t i = 0; i < array.size(); i += 2) {\n          // For keyword values, Lynx is responsible for converting them to\n          // {value, kPercent} format. For example, 'top' would be converted to\n          // {0.f, kPercent}\n          double value = utils::GetDouble(array[i]);\n          ClayPlatformLengthUnit type =\n              static_cast<ClayPlatformLengthUnit>(utils::GetInt(array[i + 1]));\n          positions.emplace_back(value, type);\n        }\n        view->SetMaskPosition(std::move(positions));\n      }\n    } break;\n    case KeywordID::kMaskOrigin: {\n      view->SetMaskOrigin(utils::GetArray(value));\n    } break;\n    case KeywordID::kMaskRepeat: {\n      view->SetMaskRepeat(utils::GetArray(value));\n    } break;\n    case KeywordID::kMaskSize: {\n      const auto& array = utils::GetArray(value);\n      FML_DCHECK(array.size() % 2 == 0);\n      if (array.size() % 2 == 0) {\n        std::vector<MaskSize> sizes;\n        for (size_t i = 0; i < array.size(); i += 2) {\n          double value = utils::GetDouble(array[i]);\n          ClayPlatformLengthUnit type =\n              static_cast<ClayPlatformLengthUnit>(utils::GetInt(array[i + 1]));\n          sizes.emplace_back(value, type);\n        }\n        view->SetMaskSize(std::move(sizes));\n      }\n    } break;\n    case KeywordID::kMaskClip: {\n      view->SetMaskClip(utils::GetArray(value));\n    } break;\n    case KeywordID::kBorderColor: {\n      view->SetBorderColor({Side::kAll}, {utils::GetUint(value)});\n    } break;\n    case KeywordID::kBorderTopColor: {\n      view->SetBorderColor({Side::kTop}, {utils::GetUint(value)});\n    } break;\n    case KeywordID::kBorderRightColor: {\n      view->SetBorderColor({Side::kRight}, {utils::GetUint(value)});\n    } break;\n    case KeywordID::kBorderBottomColor: {\n      view->SetBorderColor({Side::kBottom}, {utils::GetUint(value)});\n    } break;\n    case KeywordID::kBorderLeftColor: {\n      view->SetBorderColor({Side::kLeft}, {utils::GetUint(value)});\n    } break;\n    case KeywordID::kBorderStyle: {\n      view->SetBorderStyle(\n          {Side::kAll}, {static_cast<BorderStyleType>(utils::GetInt(value))});\n    } break;\n    case KeywordID::kBorderTopStyle: {\n      view->SetBorderStyle(\n          {Side::kTop}, {static_cast<BorderStyleType>(utils::GetInt(value))});\n    } break;\n    case KeywordID::kBorderRightStyle: {\n      view->SetBorderStyle(\n          {Side::kRight}, {static_cast<BorderStyleType>(utils::GetInt(value))});\n    } break;\n    case KeywordID::kBorderBottomStyle: {\n      view->SetBorderStyle(\n          {Side::kBottom},\n          {static_cast<BorderStyleType>(utils::GetInt(value))});\n    } break;\n    case KeywordID::kBorderLeftStyle: {\n      view->SetBorderStyle(\n          {Side::kLeft}, {static_cast<BorderStyleType>(utils::GetInt(value))});\n    } break;\n    case KeywordID::kBorderWidth: {\n      float width = utils::GetDouble(value);\n      view->SetBorderWidth({Side::kAll}, {width});\n    } break;\n    case KeywordID::kBorderTopWidth: {\n      float width = utils::GetDouble(value);\n      view->SetBorderWidth({Side::kTop}, {width});\n    } break;\n    case KeywordID::kBorderRightWidth: {\n      float width = utils::GetDouble(value);\n      view->SetBorderWidth({Side::kRight}, {width});\n    } break;\n    case KeywordID::kBorderBottomWidth: {\n      float width = utils::GetDouble(value);\n      view->SetBorderWidth({Side::kBottom}, {width});\n    } break;\n    case KeywordID::kBorderLeftWidth: {\n      float width = utils::GetDouble(value);\n      view->SetBorderWidth({Side::kLeft}, {width});\n    } break;\n    case KeywordID::kBorderRadius: {\n      view->SetBorderRadius(4, GetBorderRadius(utils::GetArray(value)));\n    } break;\n    case KeywordID::kBorderTopLeftRadius: {\n      view->SetBorderRadius(0, GetBorderRadius(utils::GetArray(value)));\n    } break;\n    case KeywordID::kBorderTopRightRadius: {\n      view->SetBorderRadius(1, GetBorderRadius(utils::GetArray(value)));\n    } break;\n    case KeywordID::kBorderBottomRightRadius: {\n      view->SetBorderRadius(2, GetBorderRadius(utils::GetArray(value)));\n    } break;\n    case KeywordID::kBorderBottomLeftRadius: {\n      view->SetBorderRadius(3, GetBorderRadius(utils::GetArray(value)));\n    } break;\n\n    case KeywordID::kTransform: {\n      const auto& array = utils::GetArray(value);\n      std::vector<TransformRaw> transform_raws;\n      for (size_t i = 0; i < array.size(); i++) {\n        const auto& values_arr = utils::GetArray(array[i]);\n        if (values_arr.size() < 7) {\n          continue;\n        }\n        TransformRaw transform_raw;\n        transform_raw.type = utils::GetInt(values_arr[0]);\n        if (transform_raw.type ==\n                static_cast<int>(ClayTransformType::kMatrix) ||\n            transform_raw.type ==\n                static_cast<int>(ClayTransformType::kMatrix3d)) {\n          if (values_arr.size() < 17) {\n            continue;\n          }\n          for (uint32_t j = 0; j < 16; j++) {\n            transform_raw.matrix[j] = utils::GetDouble(values_arr[j + 1]);\n          }\n          // append op\n          transform_raws.emplace_back(std::move(transform_raw));\n        } else {\n          // x\n          transform_raw.values[0] = GetLength(values_arr[1], values_arr[2]);\n          // y\n          transform_raw.values[1] = GetLength(values_arr[3], values_arr[4]);\n          // z\n          transform_raw.values[2] = GetLength(values_arr[5], values_arr[6]);\n          // append op\n          transform_raws.emplace_back(std::move(transform_raw));\n        }\n      }\n      view->SetTransform(std::move(transform_raws));\n    } break;\n\n    case KeywordID::kTransformOrigin: {\n      const auto& array = utils::GetArray(value);\n      FML_DCHECK(array.size() == 0 || array.size() == 2 || array.size() == 4);\n      if (array.size() == 0 || array.size() == 2 || array.size() == 4) {\n        std::vector<Length> transform_origin_values;\n        if (array.size() >= 2) {\n          transform_origin_values.emplace_back(GetLength(array[0], array[1]));\n          if (array.size() == 4) {\n            transform_origin_values.emplace_back(GetLength(array[2], array[3]));\n          }\n        }\n        view->SetTransformOrigin(std::move(transform_origin_values));\n      }\n    } break;\n\n    case KeywordID::kPerspective: {\n      const auto& array = utils::GetArray(value);\n      if (array.size() >= 2) {\n        view->SetPerspective(array);\n      }\n    } break;\n\n    case KeywordID::kBoxShadow: {\n      const auto& array = utils::GetArray(value);\n      std::vector<Shadow> shadows(array.size());\n      for (size_t i = 0; i < array.size(); i++) {\n        const auto& arr = utils::GetArray(array[i]);\n        shadows[i].offset_x = utils::GetDouble(arr[0]);\n        shadows[i].offset_y = utils::GetDouble(arr[1]);\n        shadows[i].blur_radius = utils::GetDouble(arr[2]);\n        shadows[i].spread_radius = utils::GetDouble(arr[3]);\n        auto option = static_cast<ClayShadowOption>(utils::GetInt(arr[4]));\n        shadows[i].inset = (option == ClayShadowOption::kInset);\n        shadows[i].color = Color(utils::GetUint(arr[5]));\n      }\n      view->SetShadows(std::move(shadows));\n    } break;\n\n    case KeywordID::kOutlineColor: {\n      view->SetOutlineColor(utils::GetUint(value, 0xff000000));\n    } break;\n    case KeywordID::kOutlineStyle: {\n      view->SetOutlineStyle(static_cast<BorderStyleType>(utils::GetInt(value)));\n    } break;\n    case KeywordID::kOutlineWidth: {\n      view->SetOutlineWidth(utils::GetInt(value));\n    } break;\n    case KeywordID::kAnimation: {\n      view->SetAnimation(utils::GetArray(value));\n    } break;\n    case KeywordID::kTransition: {\n      view->SetTransition(utils::GetArray(value));\n    } break;\n    case KeywordID::kOverflow: {\n      view->SetOverflowWithMask(\n          OVERFLOW_XY,\n          utils::GetInt(value, static_cast<int>(ClayOverflowType::kVisible)));\n    } break;\n    case KeywordID::kOverflowX: {\n      view->SetOverflowWithMask(\n          OVERFLOW_X,\n          utils::GetInt(value, static_cast<int>(ClayOverflowType::kVisible)));\n    } break;\n    case KeywordID::kOverflowY: {\n      view->SetOverflowWithMask(\n          OVERFLOW_Y,\n          utils::GetInt(value, static_cast<int>(ClayOverflowType::kVisible)));\n    } break;\n    case KeywordID::kVisibility: {\n      view->SetVisible(\n          utils::GetInt(value,\n                        static_cast<int>(ClayVisibilityType::kVisible)) !=\n          static_cast<int>(ClayVisibilityType::kHidden));\n    } break;\n    case KeywordID::kCursor: {\n      view->SetCursor(utils::GetArray(value));\n    } break;\n    case KeywordID::kFilter: {\n      if (value.IsArray()) {\n        view->SetFilter(utils::GetArray(value));\n      } else {\n        // value.type maybe clay::Value::kNone and in this case we clear the\n        // filter status.\n        view->ClearFilter();\n      }\n    } break;\n    case KeywordID::kImageRendering: {\n      view->SetImageRendering(\n          static_cast<ClayImageRendering>(utils::GetInt(value)));\n      break;\n    }\n    case KeywordID::kClipPath: {\n      if (value.IsArray()) {\n        view->SetClipOffsetPath(utils::GetArray(value), true);\n      } else {\n        view->ClearClipPath();\n      }\n      break;\n    }\n    case KeywordID::kOffsetPath: {\n      if (value.IsArray()) {\n        view->SetClipOffsetPath(utils::GetArray(value), false);\n      } else {\n        view->ClearOffsetPath();\n      }\n      break;\n    }\n    case KeywordID::kOffsetRotate: {\n      view->SetOffsetRotate(utils::GetDouble(value));\n      break;\n    }\n    case KeywordID::kOffsetDistance: {\n      view->SetOffsetDistance(utils::GetDouble(value));\n      break;\n    }\n#ifdef ENABLE_ACCESSIBILITY\n    case KeywordID::kAccessibilityElement: {\n      view->SetAccessibilityElement(utils::GetBool(value, false));\n      break;\n    }\n    case KeywordID::kAccessibilityLabel: {\n      if (value.IsString()) {\n        view->SetAccessibilityLabel(utils::GetCString(value));\n      }\n      break;\n    }\n    case KeywordID::kAccessibilityElements: {\n      if (value.IsString()) {\n        view->SetAccessibilityElements(utils::GetCString(value));\n      }\n      break;\n    }\n#endif\n    case KeywordID::kDirection: {\n      view->SetDirection(utils::GetInt(value));\n      break;\n    }\n    default: {\n      return false;\n    }\n  }\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/css_property.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_CSS_PROPERTY_H_\n#define CLAY_UI_COMPONENT_CSS_PROPERTY_H_\n\n#include <string>\n\n#include \"clay/public/clay.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/component/keywords.h\"\n\nnamespace clay {\n\nenum class Side { kTop, kRight, kBottom, kLeft, kAll };\n\n// Adapting lynx side direction layout properties\nenum class DirectionType : unsigned {\n  kNormal = 0,   // version:1.0\n  kLynxRtl = 1,  // version:1.0\n  kRtl = 2,      // version:1.0\n  kLtr = 3,      // version:2.0\n};\n\nclass BaseView;\n\nclass CSSProperty {\n public:\n  static constexpr uint8_t OVERFLOW_HIDDEN = 0x00;\n  static constexpr uint8_t OVERFLOW_X = 0x01;\n  static constexpr uint8_t OVERFLOW_Y = 0x02;\n  static constexpr uint8_t OVERFLOW_XY = OVERFLOW_X | OVERFLOW_Y;\n\n  static bool SetAttribute(BaseView* view, KeywordID id,\n                           const clay::Value& value);\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_CSS_PROPERTY_H_\n"
  },
  {
    "path": "clay/ui/component/editable/editable_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/editable_view.h\"\n\n#include <algorithm>\n#include <cstdint>\n#include <limits>\n#include <memory>\n#include <optional>\n#include <regex>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/string/string_utils.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/editing_misc.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/common/text_input_type_traits.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/editable/ime_utils.h\"\n#include \"clay/ui/component/editable/text_editing_controller.h\"\n#include \"clay/ui/component/editable/text_span.h\"\n#include \"clay/ui/component/editable/text_utils.h\"\n#include \"clay/ui/component/layout_controller.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/text/text_paragraph_builder.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n#include \"clay/ui/platform/keyboard_types.h\"\n#include \"clay/ui/rendering/editable/render_editable.h\"\n#include \"clay/ui/resource/font_collection.h\"\n#if defined(CLAY_ENABLE_TTTEXT)\n#include \"clay/third_party/txt/src/tttext/paragraph_tt_text.h\"\n#endif\n\nnamespace clay {\nnamespace {\n\n// All in ARGB\nconstexpr uint32_t kDefaultPlaceholderColor = 0xffaaaaaa;\n\nFontWeight ToPlaceHolderWeight(const std::string& font_weight_val) {\n  FontWeight font_weight = FontWeight::kNormal;\n  if (font_weight_val == \"normal\") {\n    font_weight = FontWeight::kNormal;\n  } else if (font_weight_val == \"bold\") {\n    font_weight = FontWeight::kBold;\n  } else if (font_weight_val == \"100\") {\n    font_weight = FontWeight::k100;\n  } else if (font_weight_val == \"200\") {\n    font_weight = FontWeight::k200;\n  } else if (font_weight_val == \"300\") {\n    font_weight = FontWeight::k300;\n  } else if (font_weight_val == \"400\") {\n    font_weight = FontWeight::k400;\n  } else if (font_weight_val == \"500\") {\n    font_weight = FontWeight::k500;\n  } else if (font_weight_val == \"600\") {\n    font_weight = FontWeight::k600;\n  } else if (font_weight_val == \"700\") {\n    font_weight = FontWeight::k700;\n  } else if (font_weight_val == \"800\") {\n    font_weight = FontWeight::k800;\n  } else if (font_weight_val == \"900\") {\n    font_weight = FontWeight::k900;\n  }\n  return font_weight;\n}\n\nLYNX_UI_METHOD_BEGIN(EditableView) {\n  LYNX_UI_METHOD(EditableView, focus);\n  LYNX_UI_METHOD(EditableView, blur);\n  LYNX_UI_METHOD(EditableView, setValue);\n  LYNX_UI_METHOD(EditableView, getValue);\n  LYNX_UI_METHOD(EditableView, setSelectionRange);\n}\nLYNX_UI_METHOD_END(EditableView);\n\n// All in pixels.\nconstexpr float kDefaultFontSizeInDip = 14.f;\nconstexpr float kDefaultLineHeight = 1.2f;\n\n// All in ARGB\nconstexpr uint32_t kDefaultEditableTextColor = 0xff000000;\n\nconstexpr uint64_t kCaretTwinkleIntervalMs = 500;\n\n// Hot key tags.\nconstexpr uint32_t kTagCommand = 1;\nconstexpr uint32_t kTagControl = 1 << 1;\nconstexpr uint32_t kTagShift = 1 << 2;\n\nuint32_t AddTag(uint32_t value, uint32_t tag) { return value | tag; }\n\nuint32_t RemoveTag(uint32_t value, uint32_t tag) { return value & ~tag; }\n\nFontWeight ToFontWeight(int font_weight_val) {\n  return static_cast<FontWeight>(font_weight_val);\n}\n\n}  // namespace\n\n// Returns units filtered. If nothing changed, returns 0.\nsize_t EditableView::FilterInput(TextEditingValue* value,\n                                 const std::string& pattern) {\n  auto input = value->GetText();\n  auto origin_length = input.length();\n  text_input_controller_->InputFilterAsync(\n      input, pattern,\n      [weak_self = weak_factory_.GetWeakPtr(), origin_length,\n       edit_value =\n           TextEditingValue(*value)](const std::string& result) mutable {\n        if (!weak_self) {\n          return;\n        }\n        auto editable_view = static_cast<EditableView*>(weak_self.get());\n        edit_value.SetTextAndReserveSelectionState(result);\n        if (origin_length - result.length()) {\n          editable_view->UpdateRemoteStateIfNeeded(edit_value);\n        }\n      });\n  return 0;\n}\n\nsize_t EditableView::FilterNewLine(TextEditingValue* value) {\n  // May costs time.\n  static auto pattern = std::string(\"\\n\");\n  return FilterInput(value, pattern);\n}\n\nsize_t EditableView::FilterMaxLength(TextEditingValue* value) {\n  size_t new_value_length = value->GetU16Length();\n  const auto& text_editing_value = GetTextEditingValue();\n  size_t old_value_length = text_editing_value.GetU16Length();\n  bool is_composing = value->composing();\n\n  if (!is_composing && new_value_length > max_length_) {\n    text_editing_controller_->SetNeedUpdateRemote(true);\n    if (old_value_length == max_length_) {\n      *value = GetTextEditingValue();\n      UpdateRemoteStateIfNeeded(*value);\n      return 0;\n    } else {\n      // We need to consider truncating the surrogate(such as emoji) in the\n      // middle.\n      auto u16_str = value->GetU16Text().substr(0, max_length_);\n      auto u32_str = lynx::base::U16StringToU32(u16_str);\n      u16_str = lynx::base::U32StringToU16(u32_str);\n      value->SetText(u16_str, TextRange(u16_str.length()));\n      UpdateRemoteStateIfNeeded(*value);\n      return new_value_length - max_length_;\n    }\n  }\n  return 0;\n}\n\nsize_t EditableView::FilterInputTextByType(TextEditingValue* value) {\n  switch (keyboard_input_type_) {\n    case KeyboardInputType::kClassNumber: {\n      if (max_lines_ > 1) {\n        static auto pattern = std::string(\"[^0-9\\\\.\\n]\");\n        return FilterInput(value, pattern);\n      } else {\n        static auto pattern = std::string(\"[^0-9\\\\.]\");\n        return FilterInput(value, pattern);\n      }\n    }\n    case KeyboardInputType::kClassPhone: {\n      if (max_lines_ > 1) {\n        static auto pattern = std::string(\"[^0-9\\n]\");\n        return FilterInput(value, pattern);\n      } else {\n        static auto pattern = std::string(\"[^0-9]\");\n        return FilterInput(value, pattern);\n      }\n    }\n    default:\n      return 0;\n  }\n}\n\nsize_t EditableView::FilterInputTextByUser(TextEditingValue* value) {\n  return FilterInput(value, input_filter_pattern_);\n}\n\nEditableView::EditableView(int id, std::string tag, PageView* page_view,\n                           bool is_multiline, bool layout_root_candidate)\n    : EditableView(id, id, tag, page_view, is_multiline,\n                   layout_root_candidate) {}\n\nEditableView::EditableView(int id, int callback_id, std::string tag,\n                           PageView* page_view, bool is_multiline,\n                           bool layout_root_candidate)\n    : WithTypeInfo(id, tag, std::make_unique<RenderEditable>(), page_view),\n      callback_id_(callback_id),\n      is_multiline_(is_multiline),\n      layout_root_candidate_(layout_root_candidate) {\n  text_editing_controller_ = std::make_unique<TextEditingController>();\n  text_editing_controller_->AddObserver(this);\n\n  GetRenderEditable()->SetTextEditingController(text_editing_controller_.get());\n  GetRenderEditable()->SetTextInputControllerDelegate(this);\n  GetRenderEditable()->SetMultiline(is_multiline_);\n\n  SetFocusable(true);\n\n  InitDefaultStyle();\n\n  ResetGestureRecognizers();\n\n  text_input_controller_ =\n      std::make_unique<TextInputController>(page_view, callback_id_, this);\n  text_editing_history_state_ = std::make_unique<TextEditingHistoryState>(this);\n\n#if defined(OS_WIN) || defined(OS_MAC)\n  SetCursor({\"text\"});\n#endif\n}\n\nEditableView::~EditableView() {\n  page_view()->StopInput(this);\n  if (text_editing_controller_) {\n    text_editing_controller_->RemoveObserver(this);\n  }\n}\n\nvoid EditableView::InitDefaultStyle() {\n  text_style_.text_align = TextAlignment::kLeft;\n  text_style_.text_color = Color(kDefaultEditableTextColor);\n  text_style_.font_size = GetDefaultFontSize();\n  text_style_.strut_font_size = GetDefaultFontSize();\n  text_style_.strut_height = kDefaultLineHeight;\n  text_style_.strut_enabled = true;\n}\n\nvoid EditableView::OnContentSizeChanged(const FloatRect& old_rect,\n                                        const FloatRect& new_rect) {\n  MarkNeedsLayout();\n}\n\nLayoutContextMeasure EditableView::CreateLayoutContext(\n    const MeasureConstraint& constraint) {\n  LayoutContextMeasure context;\n  switch (constraint.width_mode) {\n    case TextMeasureMode::kIndefinite:\n      context.layout_width = std::numeric_limits<float>::infinity();\n      break;\n    case TextMeasureMode::kAtMost:\n    case TextMeasureMode::kDefinite:\n      context.layout_width = *constraint.width;\n      break;\n  }\n  return context;\n}\n\nvoid EditableView::OnLayout(LayoutContext* context) {\n  MeasureConstraint constraint = {Width(), MeasureMode::kDefinite, Height(),\n                                  MeasureMode::kDefinite};\n  auto layout_context = CreateLayoutContext(constraint);\n  if (!context) {\n    context = &layout_context;\n  }\n  const auto& text_editing_value = GetTextEditingValue();\n  if (text_editing_value.empty() && !GetPlaceholder().empty()) {\n    // Layout placeholder\n    TextStyle temp_style = text_style_;\n    temp_style.strut_enabled = std::nullopt;\n    auto builder = std::make_unique<TextParagraphBuilder>(true, temp_style);\n    temp_style.font_size =\n        placeholder_font_size_.value_or(GetDefaultFontSize());\n    temp_style.text_color =\n        placeholder_color_.value_or(Color(kDefaultPlaceholderColor));\n    temp_style.font_weight =\n        placeholder_font_weight_.value_or(FontWeight::kNormal);\n    builder->PushStyle(temp_style);\n    builder->AddText(lynx::base::U8StringToU16(GetPlaceholder()));\n    builder->Pop();\n    paragraph_ = Build(std::move(builder));\n    GetRenderEditable()->SetPlaceholderLineHeight(\n        *temp_style.font_size *\n        text_style_.line_height.value_or(kDefaultLineHeight));\n    LayoutText(context);\n    SetPlaceholderHeight(paragraph_->GetHeight());\n  } else {\n    // Layout content\n    auto builder = std::make_unique<TextParagraphBuilder>(true, text_style_);\n    BuildTextSpan(text_style_)->Build(*builder);\n    paragraph_ = Build(std::move(builder));\n#if defined(CLAY_ENABLE_TTTEXT)\n    auto* impl = static_cast<txt::ParagraphTTText*>(paragraph_.get());\n    impl->SetNeedTrimSpace(true);\n#endif\n    LayoutText(context);\n  }\n\n  GetRenderEditable()->SetParagraph(paragraph_.get());\n  GetRenderEditable()->SetRoughTextLineHeight(\n      *text_style_.font_size *\n      text_style_.line_height.value_or(kDefaultLineHeight));\n}\n\nfloat EditableView::EstimateHeightWithMaxLines() {\n  // TODO(yulitao): Simply use template paragraph to do it.\n  // This can be refined to be more accurate.\n  bool infinite = max_lines_ == std::numeric_limits<uint32_t>::max();\n  // Note: treat unlimited lines as one line height.\n  float text_height;\n  if (!paragraph_) {\n    if (!template_paragraph_ || GetTextEditingValue().empty()) {\n      auto builder = std::make_unique<TextParagraphBuilder>(true, text_style_);\n      builder->PushStyle(text_style_);\n      builder->AddText(u\" \");\n      builder->Pop();\n      template_paragraph_ = Build(std::move(builder));\n      template_paragraph_->Layout(std::numeric_limits<float>::infinity());\n      GetRenderEditable()->SetDefaultLineHeight(\n          template_paragraph_->GetHeight());\n    }\n    text_height = template_paragraph_ ? template_paragraph_->GetHeight() *\n                                            (infinite ? 1 : max_lines_)\n                                      : 0;\n  } else {\n    text_height = paragraph_->GetHeight();\n  }\n  return text_height;\n}\n\n// Different from text measure, editable has some special logics:\n// - If height/width is definite, use it;\n// - If width is at_most or infinite, use it.\n// - If height is at_most or infinite, estimate height according to first line\n// height of current style.\nvoid EditableView::Measure(const MeasureConstraint& constraint,\n                           MeasureResult& result) {\n  auto context = CreateLayoutContext(constraint);\n  Layout(&context);\n  if (constraint.height_mode == MeasureMode::kDefinite) {\n    result.height = *constraint.height;\n  } else {\n    auto estimated_height = EstimateHeightWithMaxLines();\n    result.height = (constraint.height_mode == MeasureMode::kIndefinite ||\n                     !constraint.height.has_value())\n                        ? estimated_height\n                        : std::min(estimated_height, *constraint.height);\n  }\n\n  if (constraint.width_mode == MeasureMode::kIndefinite ||\n      !constraint.width.has_value()) {\n    result.width = std::numeric_limits<float>::infinity();\n  } else {\n    result.width = *constraint.width;\n  }\n  const auto& text_editing_value = GetTextEditingValue();\n  if (constraint.height_mode != MeasureMode::kDefinite &&\n      text_editing_value.empty()) {\n    // Empty content should at least strut a height by default style.\n    result.height =\n        std::max(result.height, GetRenderEditable()->GetRoughTextLineHeight() +\n                                    VerticalThickness());\n  }\n}\n\nvoid EditableView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  switch (kw) {\n    case KeywordID::kShowSoftInputOnfocus:\n      SetShowSoftInputOnFocus(attribute_utils::GetBool(value));\n      break;\n    case KeywordID::kType:\n      keyboard_input_type_ =\n          ConvertInputType(attribute_utils::GetCString(value));\n      ApplyFilter(&EditableView::FilterInputTextByType, true);\n      break;\n    case KeywordID::kDisabled:\n      SetDisabled(attribute_utils::GetBool(value));\n      break;\n    case KeywordID::kConfirmType:\n      keyboard_action_ = ConvertConfirmType(\n          attribute_utils::GetCString(value),\n          max_lines_ == 1 ? KeyboardAction::kDone : KeyboardAction::kMultiLine);\n      break;\n    case KeywordID::kColor:\n      SetFontColor(attribute_utils::GetInt(value));\n      break;\n    case KeywordID::kFontSize: {\n      double font_size = 0.0;\n      if (attribute_utils::TryGetNum(value, font_size)) {\n        SetFontSize(static_cast<float>(font_size));\n      }\n    } break;\n    case KeywordID::kFontWeight: {\n      double font_weight_val;\n      if (attribute_utils::TryGetNum(value, font_weight_val)) {\n        SetFontWeight(ToFontWeight(font_weight_val));\n      }\n    } break;\n    case KeywordID::kLetterSpacing: {\n      double letter_spacing = 0.0;\n      if (attribute_utils::TryGetNum(value, letter_spacing)) {\n        SetLetterSpacing(letter_spacing);\n      }\n    } break;\n    case KeywordID::kTextAlign: {\n      double text_align = 0.0;\n      if (attribute_utils::TryGetNum(value, text_align)) {\n        SetTextAlign(static_cast<TextAlignment>(text_align));\n      }\n    } break;\n    case KeywordID::kFontFamily:\n      SetFontFamily(attribute_utils::GetCString(value));\n      break;\n    case KeywordID::kCaretColor: {\n      Color color;\n      if (Color::Parse(attribute_utils::GetCString(value), &color)) {\n        GetRenderEditable()->SetCaretColor(color);\n      }\n    } break;\n    case KeywordID::kMaxlength: {\n      double max_length;\n      if (attribute_utils::TryGetNum(value, max_length) && max_length > 0) {\n        max_length_ = max_length;\n        ApplyFilter(&EditableView::FilterMaxLength, max_length_ > 0);\n      }\n    } break;\n    case KeywordID::kReadonly:\n      SetReadOnly(attribute_utils::GetBool(value));\n      break;\n    case KeywordID::kMaxlines: {\n      double max_lines;\n      if (attribute_utils::TryGetNum(value, max_lines) && max_lines > 0) {\n        SetMaxLines(max_lines);\n      }\n    } break;\n    case KeywordID::kPlaceholder:\n      SetPlaceholder(attribute_utils::GetCString(value));\n      break;\n    case KeywordID::kPlaceholderFontSize: {\n      attribute_utils::Length val_with_unit{0.0, attribute_utils::Unit::kNone};\n      if (attribute_utils::TryGetLength(value, val_with_unit)) {\n        SetPlaceholderFontSize(val_with_unit.val);\n      }\n    } break;\n    case KeywordID::kPlaceholderFontWeight: {\n      std::string font_weight_val;\n      if (attribute_utils::TryGetString(value, font_weight_val)) {\n        SetPlaceholderFontWeight(ToPlaceHolderWeight(font_weight_val));\n      }\n    } break;\n    case KeywordID::kPlaceholderColor: {\n      Color color;\n      if (value.IsUint()) {\n        color = attribute_utils::GetUint(value, 0xff000000);\n        SetPlaceholderColor(color);\n      }\n    } break;\n    default:\n      BaseView::SetAttribute(attr_c, value);\n      break;\n  }\n}\n\nvoid EditableView::SetFontFamily(const std::string& font_family) {\n  std::string new_font_family = font_family;\n  if (new_font_family.length() > 1) {\n    new_font_family = lynx::base::TrimString(\n        new_font_family, \"\\'\", lynx::base::TrimPositions::TRIM_ALL);\n    if (new_font_family.length() > 1) {\n      new_font_family = lynx::base::TrimString(\n          new_font_family, \"\\\"\", lynx::base::TrimPositions::TRIM_ALL);\n    }\n  }\n  if (text_style_.font_family != new_font_family) {\n    text_style_.font_family = new_font_family;\n    text_style_.strut_font_families.clear();\n    text_style_.strut_font_families.emplace_back(new_font_family);\n    MarkDirty();\n    RelayoutWhenSetFontFamily(new_font_family);\n  }\n}\n\nvoid EditableView::SetPlaceholder(const std::string& placeholder) {\n  if (placeholder != placeholder_) {\n    placeholder_ = placeholder;\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::SetPlaceholderFontSize(float font_size) {\n  if (placeholder_font_size_ != font_size) {\n    placeholder_font_size_ = font_size;\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::SetPlaceholderFontWeight(FontWeight font_weight) {\n  if (placeholder_font_weight_ != font_weight) {\n    placeholder_font_weight_ = font_weight;\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::SetPlaceholderColor(const Color& color) {\n  if (placeholder_color_ != color) {\n    placeholder_color_ = color;\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::RelayoutWhenSetFontFamily(const std::string& font_family) {\n  auto font_collection = Isolate::Instance().GetFontCollection();\n\n  if (font_collection) {\n    font_collection->RegisterCallback(font_family,\n                                      [self = weak_factory_.GetWeakPtr()]() {\n                                        if (!self) {\n                                          return;\n                                        }\n                                        self->MarkDirty();\n                                      });\n  }\n}\n\nvoid EditableView::OnValueChanged(const TextEditingValue& value,\n                                  const TextEditingController*) {\n  TwinkleCaretPeriodically();\n  if (!value.composing()) {\n    UpdateRemoteStateIfNeeded(value);\n  }\n  MarkNeedsLayout();\n}\n\nvoid EditableView::OnSelectionChanged(const TextEditingValue& value,\n                                      const TextEditingController*) {\n  TwinkleCaretPeriodically();\n  page_view()->SendEvent(callback_id_, event_attr::kEventEditSelectionChange,\n                         {\"start\", \"end\"},\n                         static_cast<int>(value.selection().start()),\n                         static_cast<int>(value.selection().end()));\n}\n\nvoid EditableView::OnUserInput(const TextEditingValue& value,\n                               const TextEditingController*) {\n  const auto& text_editing_value = GetTextEditingValue();\n  // Keep string alive to transfer c_str.\n  auto text_editing_str = text_editing_value.GetText();\n  page_view()->SendEvent(\n      callback_id_, event_attr::kEventEditInput,\n      {\"value\", \"selectionStart\", \"selectionEnd\", \"isComposing\"},\n      text_editing_str.c_str(),\n      static_cast<int>(text_editing_value.selection().start()),\n      static_cast<int>(text_editing_value.selection().end()),\n      text_editing_value.composing());\n}\n\nvoid EditableView::OnCommitText(std::string text) {\n  auto text_editing_value = GetTextEditingValue();\n  if (text_editing_value.composing()) {\n    text_editing_value.UpdateComposingText(\"\");\n    text_editing_value.EndComposing();\n  }\n  text_editing_value.AddText(text);\n  FilterInputIfNeeded(&text_editing_value);\n  SetTextEditingValue(text_editing_value);\n  MarkNeedsLayout();\n}\n\nvoid EditableView::OnComposingText(std::string text) {\n  auto text_editing_value = GetTextEditingValue();\n  if (!text_editing_value.composing()) {\n    text_editing_value.BeginComposing();\n  }\n  text_editing_value.AddText(text);\n  FilterInputIfNeeded(&text_editing_value);\n  SetTextEditingValue(text_editing_value, true);\n  MarkNeedsLayout();\n}\n\nvoid EditableView::ApplyFilter(FilterFunc func, bool take_effect) {\n  if (take_effect) {\n    filter_funcs_.push_back(func);\n  } else {\n    for (auto iter = filter_funcs_.begin(); iter != filter_funcs_.end();\n         iter++) {\n      if (*iter == func) {\n        filter_funcs_.erase(iter);\n      }\n    }\n  }\n}\n\nvoid EditableView::FilterInputIfNeeded(TextEditingValue* input) {\n  if (filter_funcs_.empty()) {\n    return;\n  }\n\n  for (FilterFunc func : filter_funcs_) {\n    auto filter_number = (this->*func)(input);\n    if (filter_number > 0) {\n      UpdateRemoteStateIfNeeded(*input);\n    }\n  }\n}\n\nvoid EditableView::OnKeyboardEvent(std::unique_ptr<KeyEvent> key_event) {\n  OnKeyEventInternal(key_event.get());\n}\n\nbool EditableView::OnKeyEvent(const KeyEvent* event) {\n  return OnKeyEventInternal(event);\n}\n\nvoid EditableView::OnPerformAction(KeyboardAction action) {\n  // TODO(yulitao): Deal with action other than |kDone|.\n  auto text_editing_str = GetTextEditingValue().GetText();\n  switch (action) {\n    case KeyboardAction::kMultiLine:\n      break;\n    case KeyboardAction::kGo:\n    case KeyboardAction::kNext:\n    case KeyboardAction::kSearch:\n    case KeyboardAction::kSend:\n    case KeyboardAction::kDone:\n      page_view()->SendEvent(callback_id_, event_attr::kEventEditConfirm,\n                             {\"value\"}, text_editing_str.c_str());\n    case KeyboardAction::kPrevious:\n      ClearFocus();\n  }\n}\n\nvoid EditableView::OnFinishInput() {\n  if (editing_) {\n    QuitEditing();\n  }\n}\n\nbool EditableView::OnKeyEventInternal(const KeyEvent* key_event) {\n  if (!key_event || disabled_) {\n    return false;\n  }\n  // Note(yulitao): Capture keycode use allowlist in case that some special\n  // keycodes may be swallowed by us.\n  bool is_down = key_event->GetType() == KeyEventType::kDown;\n  bool is_repeat = key_event->GetType() == KeyEventType::kRepeat;\n  KeyCode keycode = key_event->GetLogical();\n\n  if (!editing_ && keycode != KeyCode::kSelect && keycode != KeyCode::kEnter) {\n    return false;\n  }\n\n  if (ApplyHotKey(key_event)) {\n    return true;\n  }\n  switch (keycode) {\n    case KeyCode::kGoBack: {\n      if (editing_) {\n        if (!is_down) {\n          QuitEditing();\n        }\n        return true;\n      } else {\n        return false;\n      }\n    }\n    case KeyCode::kEscape: {\n      if (!is_down) {\n        QuitEditing();\n      }\n      return true;\n    }\n    case KeyCode::kSelect: {\n      if (is_down) {\n        BeginEditingIfNeeded();\n      }\n      return true;\n    }\n    case KeyCode::kDelete: {\n      if (is_down || is_repeat) {\n        DoDelete();\n      }\n      return true;\n    }\n    case KeyCode::kBackspace: {\n      if (is_down || is_repeat) {\n        DoBackspace();\n      }\n      return true;\n    }\n    case KeyCode::kClear: {\n      if (is_down) {\n        SetContent(\"\");\n      }\n      return true;\n    }\n    case KeyCode::kArrowLeft:\n    case KeyCode::kArrowRight:\n    case KeyCode::kArrowUp:\n    case KeyCode::kArrowDown: {\n      if (is_down || is_repeat) {\n        MoveCaret(keycode);\n      }\n      return true;\n    }\n    case KeyCode::kSpace: {\n#if defined(OS_ANDROID) || defined(OS_HARMONY)\n      // On some android IM client, space may be sent by keyevent.\n      if (is_down) {\n        OnCommitText(\" \");\n      }\n      return true;\n#else\n      if (key_event->GetSynthesized()) {\n        // For PC headless mode\n        return HandleSynthesizedKeyEvent(key_event);\n      }\n      return false;\n#endif\n    }\n    case KeyCode::kEnter: {\n      if (!editing_) {\n        if (!is_down) {\n          BeginEditingIfNeeded();\n        }\n        return true;\n      } else if (is_down && key_event->GetSynthesized() &&\n                 key_event->GetCharacter() == \"\\r\" && max_lines_ > 1) {\n        // Treat as line break in multi-line mode for PC headless mode.\n        OnCommitText(\"\\n\");\n        return true;\n      }\n    }\n    default: {\n#if defined(OS_ANDROID) || defined(OS_IOS) || \\\n    (defined(OS_HARMONY) && !defined(ENABLE_HEADLESS))\n      // In case a keyboard is used on a mobile system.\n      if (!key_event->GetCharacter().empty()) {\n        // Treat as unicode character.\n        if (is_down) {\n          OnCommitText(key_event->GetCharacter());\n        }\n        return true;\n      }\n#else\n      // We using synthesized key event for PC headless mode\n      if (key_event->GetSynthesized()) {\n        return HandleSynthesizedKeyEvent(key_event);\n      }\n      // PC IME will handle it.\n      return false;\n#endif\n    }\n  }\n  return false;\n}\n\nbool EditableView::HandleSynthesizedKeyEvent(const KeyEvent* key_event) {\n  if (!key_event->GetSynthesized()) {\n    return false;\n  }\n  if (key_event->GetType() == KeyEventType::kDown &&\n      !key_event->GetCharacter().empty()) {\n    // Treat as utf8 character.\n    OnCommitText(key_event->GetCharacter());\n    return true;\n  }\n  return false;\n}\n\n// TODO(wangyanyi): Currently, rtl language input zwj is not supported and\n// using the getwordboundary method is a more trick method.\nTextRange EditableView::FindNextOrPrevCharacterSelection(bool is_forward) {\n  auto text_editing_value = GetTextEditingValue();\n  const auto& text = text_editing_value.GetU16Text();\n  auto current_selection = TextRange(text_editing_value.selection());\n  if (is_forward) {\n    if (TextUtils::JudgeIfZWJCharacter(text_editing_value.GetU16Text(),\n                                       current_selection.extent(), true)) {\n      auto current_word =\n          GetRenderEditable()->GetWordBoundary(current_selection.extent());\n      auto prev_word = GetRenderEditable()->GetWordBoundary(\n          std::max(static_cast<int>(current_word.base() - 1), 0));\n      return prev_word;\n    } else {\n      size_t position = current_selection.extent();\n      if (position > 0) {\n        int count = IsTrailingSurrogate(text.at(position - 1)) ? 2 : 1;\n        return TextRange(current_selection.extent(),\n                         current_selection.extent() - count);\n      }\n    }\n  } else {\n    if (TextUtils::JudgeIfZWJCharacter(text_editing_value.GetU16Text(),\n                                       current_selection.extent(), false)) {\n      auto current_word =\n          GetRenderEditable()->GetWordBoundary(current_selection.extent());\n      return current_word;\n    } else {\n      size_t position = current_selection.extent();\n      if (position < text.length()) {\n        int count = IsLeadingSurrogate(text.at(position)) ? 2 : 1;\n        return TextRange(current_selection.extent(),\n                         current_selection.extent() + count);\n      }\n    }\n  }\n  return text_editing_value.selection();\n}\n\nvoid EditableView::MoveCaret(KeyCode keycode) {\n  // TODO(yulitao): Need determine how many utf16 units there is.\n  auto text_editing_value = GetTextEditingValue();\n  // If we are in a composing state, caret behaviors are managed remotely\n  // (i.e., by the platform layer) on MacOS.\n  // TODO(haochen): Currently we cannot handle left/right key in composing state\n  // on windows.\n  // TODO(haocehn): Check TV.\n#if defined(OS_MACOSX)\n  if (text_editing_value.composing()) {\n    return;\n  }\n#endif\n  if (keycode == KeyCode::kArrowLeft) {\n    auto prev = FindNextOrPrevCharacterSelection(true);\n    text_editing_value.SetSelection(TextRange(prev.start(), prev.start()));\n    SetTextEditingValue(text_editing_value);\n    GetRenderEditable()->MarkNeedsPaint();\n  } else if (keycode == KeyCode::kArrowRight) {\n    auto next = FindNextOrPrevCharacterSelection(false);\n    text_editing_value.SetSelection(TextRange(next.end(), next.end()));\n    SetTextEditingValue(text_editing_value);\n    GetRenderEditable()->MarkNeedsPaint();\n  } else if (keycode == KeyCode::kArrowUp) {\n    GetRenderEditable()->MoveCaretUpDown(VerticalDirection::kUp);\n  } else if (keycode == KeyCode::kArrowDown) {\n    GetRenderEditable()->MoveCaretUpDown(VerticalDirection::kDown);\n  }\n}\n\nvoid EditableView::OnDeleteSurroundingText(int before_length,\n                                           int after_length) {\n  DeleteSurroundingText(before_length, after_length);\n}\n\nvoid EditableView::DeleteSurroundingText(int before_length, int after_length) {\n  auto text_editing_value = GetTextEditingValue();\n  bool deleted = text_editing_value.DeleteSurrounding(\n      -before_length, before_length + after_length);\n  if (deleted) {\n    SetTextEditingValue(text_editing_value);\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::DoDelete() {\n  auto text_editing_value = GetTextEditingValue();\n  auto text = text_editing_value.GetU16Text();\n  text_editing_value.StopComposingIfNecessary();\n  auto& selection = text_editing_value.selection();\n  if (!selection.collapsed()) {\n    size_t start = selection.start();\n    text_editing_value.SetText(text.erase(start, selection.length()),\n                               TextRange(start));\n    if (text_editing_value.composing()) {\n      // This occurs only immediately after composing has begun with a\n      // selection.\n      text_editing_value.SetComposingRange(selection, 0);\n    }\n  } else {\n    auto next = FindNextOrPrevCharacterSelection(false);\n    text_editing_value.SetText(text.erase(next.start(), next.length()),\n                               TextRange(next.start(), next.start()));\n    if (text_editing_value.composing()) {\n      auto composing_range = text_editing_value.composing_range();\n      composing_range.set_end(composing_range.end() - next.length());\n      text_editing_value.SetComposingRange(composing_range, 0);\n    }\n  }\n  SetTextEditingValue(text_editing_value);\n  MarkNeedsLayout();\n}\n\nvoid EditableView::DoBackspace() {\n  auto text_editing_value = GetTextEditingValue();\n  auto text = text_editing_value.GetU16Text();\n  text_editing_value.StopComposingIfNecessary();\n  auto& selection = text_editing_value.selection();\n  if (!selection.collapsed()) {\n    size_t start = selection.start();\n    text_editing_value.SetText(text.erase(start, selection.length()),\n                               TextRange(start));\n    if (text_editing_value.composing()) {\n      // This occurs only immediately after composing has begun with a\n      // selection.\n      text_editing_value.SetComposingRange(selection, 0);\n    }\n  } else {\n    auto prev = FindNextOrPrevCharacterSelection(true);\n    text_editing_value.SetText(text.erase(prev.start(), prev.length()),\n                               TextRange(prev.start(), prev.start()));\n  }\n  SetTextEditingValue(text_editing_value);\n  GetRenderEditable()->MarkNeedsPaint();\n}\n\nRenderEditable* EditableView::GetRenderEditable() {\n  return static_cast<RenderEditable*>(render_object_.get());\n}\n\nvoid EditableView::OnGestureTap(const PointerEvent& pointer) {\n  if (last_tap_pointer_ == pointer.pointer_id) {\n    // Tap gesture would always comes later than the first tap of double-tap.\n    // Avoid redundant handling because finding offset by coordinate is a bit\n    // expensive.\n    return;\n  }\n  last_tap_pointer_ = pointer.pointer_id;\n\n  BeginEditingIfNeeded();\n  GetRenderEditable()->UpdateCaretByCoordinate(\n      GetPointBySelf(pointer.position));\n}\n\nvoid EditableView::OnGestureDoubleTap(const PointerEvent& pointer) {\n  GetRenderEditable()->SelectWord(GetPointBySelf(pointer.position));\n}\nvoid EditableView::OnGestureTripleTap(const PointerEvent& pointer) {\n  GetRenderEditable()->SelectLine(GetPointBySelf(pointer.position));\n}\n\nvoid EditableView::OnDragStart(const FloatPoint& position) {\n  BeginEditingIfNeeded();\n  GetRenderEditable()->UpdateCaretByCoordinate(GetPointBySelf(position));\n}\n\nvoid EditableView::OnDragUpdate(const FloatPoint& position,\n                                const FloatSize& delta) {\n  GetRenderEditable()->UpdateCaretByCoordinate(GetPointBySelf(position), true);\n}\n\ntxt::Paragraph* EditableView::GetParagraph() { return paragraph_.get(); }\n\nFloatRect EditableView::ComputeCaretRect() {\n  return GetRenderEditable()->ComputeCaretRect();\n}\n\nvoid EditableView::SetDisabled(bool disabled) {\n  if (disabled_ == disabled) {\n    return;\n  }\n\n  disabled_ = disabled;\n  GetRenderEditable()->SetDisabled(disabled);\n  page_view()->StopInput(this);\n}\n\nvoid EditableView::BeginEditingIfNeeded() {\n  if (readonly_ || disabled_) {\n    RequestFocus();\n  } else {\n    BeginEditing();\n  }\n}\n\nvoid EditableView::BeginEditing() {\n  if (editing_) {\n    text_input_controller_->Show();\n    RequestKeyboard();\n    return;\n  }\n  editing_ = true;\n  RequestFocus();\n  TwinkleCaretPeriodically();\n  text_input_controller_->SetClient(callback_id_, keyboard_action_,\n                                    KeyboardInputType::kClassText);\n  text_input_controller_->SetEditableTransform(ToGlobalTransform());\n  text_input_controller_->SetEditingState(GetTextEditingValue());\n  text_input_controller_->Show();\n  RequestKeyboard();\n  GetRenderEditable()->MarkNeedsPaint();\n}\n\nvoid EditableView::QuitEditing() {\n  FML_DCHECK(editing_);\n  editing_ = false;\n  hot_key_tag_ = 0;\n  page_view()->StopInput(this);\n  auto text_editing_value = GetTextEditingValue();\n  if (text_editing_value.composing()) {\n    text_editing_value.CommitComposing();\n    text_editing_value.EndComposing();\n    FilterInputIfNeeded(&text_editing_value);\n    SetTextEditingValue(text_editing_value);\n    MarkNeedsLayout();\n  }\n  if (caret_timer_) {\n    caret_timer_.reset();\n    GetRenderEditable()->SetCaretDisplay(false);\n  }\n  text_input_controller_->Hide();\n  text_input_controller_->ClearClient();\n}\n\nvoid EditableView::UpdateCaretRectIfNeeded() {\n  if (!text_input_controller_->ConnectKeyboard()) {\n    return;\n  }\n  text_input_controller_->SetCaretRect(\n      GetRenderEditable()->ComputeCaretRectRelativeToCanvas());\n}\n\nvoid EditableView::UpdateComposingRectIfNeeded() {\n  if (!text_input_controller_->ConnectKeyboard()) {\n    return;\n  }\n  const auto& text_editing_value = GetTextEditingValue();\n  const TextRange composing_range = text_editing_value.composing_range();\n  text_input_controller_->SetComposingRect(\n      GetRenderEditable()->ComputeComposingRect(composing_range));\n}\n\nvoid EditableView::RequestKeyboard() {\n  if (show_soft_input_on_focus_) {\n    page_view()->RequestInput(this, keyboard_input_type_, keyboard_action_);\n  }\n}\n\nvoid EditableView::FocusHasChanged(bool focused, bool is_leaf) {\n  if (focused && !editing_) {\n    BeginEditingIfNeeded();\n  }\n\n  if (focused && text_editing_controller_->MoveSelectionToEndIfNeeded()) {\n    UpdateRemoteStateIfNeeded(GetTextEditingValue());\n  }\n  const auto& text_editing_value = GetTextEditingValue();\n\n  // collapse selection when losing focus.\n  if (!focused) {\n    if (!text_editing_value.selection().collapsed()) {\n      GetRenderEditable()->SetSelection(text_editing_value.selection().end(),\n                                        text_editing_value.selection().end());\n    }\n  }\n\n  if (!focused && editing_) {\n    QuitEditing();\n  }\n\n  auto str = text_editing_value.GetText();\n  page_view()->SendEvent(\n      callback_id_, focused ? event_attr::kEventFocus : event_attr::kEventBlur,\n      {\"value\"}, str.c_str());\n}\n\nvoid EditableView::TwinkleCaretPeriodically() {\n  if (!editing_) {\n    return;\n  }\n  twinkle_flag_ = true;\n  GetRenderEditable()->SetCaretDisplay(true);\n\n  if (!caret_timer_) {\n    caret_timer_ =\n        std::make_unique<fml::RepeatingTimer>(page_view()->GetTaskRunner());\n  }\n  caret_timer_->Start(fml::TimeDelta::FromMilliseconds(kCaretTwinkleIntervalMs),\n                      [this] { ToggleCaret(); });\n}\n\nvoid EditableView::ToggleCaret() {\n  twinkle_flag_ = !twinkle_flag_;\n  GetRenderEditable()->SetCaretDisplay(twinkle_flag_);\n}\n\nvoid EditableView::SetDirection(int type) {\n  SetTextDirection(static_cast<TextDirection>(type));\n}\n\nvoid EditableView::SetMaxLines(uint32_t max_lines) {\n  if (max_lines == max_lines_) {\n    return;\n  }\n\n  max_lines_ = max_lines;\n  ApplyFilter(&EditableView::FilterNewLine, max_lines_ == 1);\n  GetRenderEditable()->SetMaxLines(max_lines_);\n  if (max_lines_ > 1) {\n    text_input_controller_->SetMultiline(true);\n  } else {\n    text_input_controller_->SetMultiline(false);\n  }\n  MarkNeedsLayout();\n}\n\nvoid EditableView::SetShowSoftInputOnFocus(bool show_soft_input_on_focus) {\n  show_soft_input_on_focus_ = show_soft_input_on_focus;\n  if (!show_soft_input_on_focus_) {\n    page_view()->StopInput(this);\n  }\n}\n\nvoid EditableView::SetContent(const std::string& content) {\n  auto text_editing_value = GetTextEditingValue();\n  text_editing_value.SetTextAndReserveSelectionState(content);\n  FilterInputIfNeeded(&text_editing_value);\n  SetTextEditingValue(text_editing_value);\n  MarkNeedsLayout();\n}\n\nvoid EditableView::SetFontColor(const Color& color) {\n  if (text_style_.text_color != color) {\n    text_style_.text_color = color;\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::SetFontSize(float font_size) {\n  if (text_style_.font_size != font_size) {\n    text_style_.font_size = font_size;\n    if (!placeholder_font_size_.has_value()) {\n      placeholder_font_size_ = font_size;\n    }\n    text_style_.strut_font_size = font_size;\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::SetFontWeight(FontWeight font_weight) {\n  if (text_style_.font_weight != font_weight) {\n    text_style_.font_weight = font_weight;\n    text_style_.strut_font_weight = font_weight;\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::SetLetterSpacing(float letter_spacing) {\n  if (text_style_.letter_spacing != letter_spacing) {\n    text_style_.letter_spacing = letter_spacing;\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::SetTextAlign(TextAlignment text_alignment) {\n  GetRenderEditable()->SetTextAlign(text_alignment);\n  if (is_multiline_ && text_style_.text_align != text_alignment) {\n    text_style_.text_align = text_alignment;\n  }\n  MarkNeedsLayout();\n}\n\nvoid EditableView::SetTextDirection(TextDirection text_direction) {\n  if (text_direction == TextDirection::kNormal) {\n    text_direction = TextDirection::kLtr;\n  }\n  if (text_style_.text_direction != text_direction) {\n    text_style_.text_direction = text_direction;\n    GetRenderEditable()->SetTextDirection(text_direction);\n    MarkNeedsLayout();\n  }\n}\n\nvoid EditableView::SetReadOnly(bool read_only) {\n  if (editing_) {\n    QuitEditing();\n  }\n  readonly_ = read_only;\n  if (is_focused_) {\n    BeginEditingIfNeeded();\n  }\n}\n\nfloat EditableView::LayoutWidth() {\n  if (max_lines_ == 1) {\n    // Single line means no soft wrap.\n    return std::numeric_limits<float>::infinity();\n  } else {\n    return ContentWidth();\n  }\n}\n\nvoid EditableView::setValue(const LynxModuleValues& args,\n                            const LynxUIMethodCallback& callback) {\n  std::string content;\n  int index = -1;\n  CastNamedLynxModuleArgs({\"value\", \"index\"}, args, content, index);\n  auto text_editing_value = GetTextEditingValue();\n  text_editing_value.SetText(\n      content,\n      TextRange(\n          index >= 0\n              ? std::min<size_t>(static_cast<size_t>(index), content.size())\n              : std::min(content.size(),\n                         text_editing_value.selection().extent())));\n  FilterInputIfNeeded(&text_editing_value);\n  SetTextEditingValue(text_editing_value);\n  MarkNeedsLayout();\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid EditableView::blur(const LynxModuleValues&,\n                        const LynxUIMethodCallback& callback) {\n  ClearFocus();\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid EditableView::focus(const LynxModuleValues& args,\n                         const LynxUIMethodCallback& callback) {\n  RequestFocus();\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid EditableView::setSelectionRange(const LynxModuleValues& args,\n                                     const LynxUIMethodCallback& callback) {\n  int begin = 0, end = 0;\n  auto text_editing_value = GetTextEditingValue();\n  CastNamedLynxModuleArgs({\"selectionStart\", \"selectionEnd\"}, args, begin, end);\n  text_editing_value.SetSelection(TextRange(begin, end));\n  SetTextEditingValue(text_editing_value);\n  MarkNeedsLayout();\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid EditableView::getValue(const LynxModuleValues&,\n                            const LynxUIMethodCallback& callback) {\n  auto str = GetTextEditingValue().GetText();\n  callback(LynxUIMethodResult::kSuccess, clay::Value(str));\n}\n\nbool EditableView::ApplyHotKey(const KeyEvent* key_event) {\n  auto key_code = key_event->GetLogical();\n  bool is_up = key_event->GetType() == KeyEventType::kUp;\n  UpdateHotKeyTag(key_code, is_up);\n  if (hot_key_tag_ == 0) {\n    // Not in hot key mode.\n    return false;\n  }\n  if (!is_up) {\n    // Only handle second hot key down and repeat.\n    if (hot_key_tag_ == kTagCommand) {\n      HandleCommandHotKey(key_code);\n    } else if (hot_key_tag_ == kTagControl) {\n      HandleCtrlHotKey(key_code);\n    } else if (hot_key_tag_ == kTagShift) {\n      return HandleShiftHotKey(key_code);\n    }\n    // Do not respond to multiple modifier key combination, like 'command' +\n    // 'control' + key.\n  }\n  return true;\n}\n\nvoid EditableView::UpdateHotKeyTag(LogicalKeyboardKey key_code, bool is_up) {\n  switch (key_code) {\n    case KeyCode::kMetaLeft:\n    case KeyCode::kMetaRight:\n    case KeyCode::kMeta:\n      hot_key_tag_ = is_up ? RemoveTag(hot_key_tag_, kTagCommand)\n                           : AddTag(hot_key_tag_, kTagCommand);\n      break;\n    case KeyCode::kControlLeft:\n    case KeyCode::kControlRight:\n    case KeyCode::kControl:\n      hot_key_tag_ = is_up ? RemoveTag(hot_key_tag_, kTagControl)\n                           : AddTag(hot_key_tag_, kTagControl);\n      break;\n    case KeyCode::kShiftLeft:\n    case KeyCode::kShiftRight:\n    case KeyCode::kShift:\n      hot_key_tag_ = is_up ? RemoveTag(hot_key_tag_, kTagShift)\n                           : AddTag(hot_key_tag_, kTagShift);\n      break;\n    default:\n      break;\n  }\n}\n\nvoid EditableView::HandleCommandHotKey(LogicalKeyboardKey key_code) {\n  HandleWinCtrlAndMacCommandHotKey(key_code);\n}\n\nvoid EditableView::HandleCtrlHotKey(LogicalKeyboardKey key_code) {\n#if defined(WIN32) || defined(ENABLE_HEADLESS)\n  HandleWinCtrlAndMacCommandHotKey(key_code);\n#endif\n}\n\nvoid EditableView::HandleWinCtrlAndMacCommandHotKey(\n    LogicalKeyboardKey key_code) {\n  const auto& text_editing_value = GetTextEditingValue();\n  switch (key_code) {\n    case KeyCode::kKeyV: {\n      // Paste from clipboard.\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n      auto clip_board_data = page_view()->GetClipboardData();\n      OnCommitText(lynx::base::U16StringToU8(clip_board_data));\n#endif\n      break;\n    }\n    case KeyCode::kKeyC:\n    case KeyCode::kKeyX: {\n      // Copy to clipboard.\n      std::u16string selected = u\"\";\n      const auto& selection = text_editing_value.selection();\n      if (!selection.collapsed()) {\n        selected = text_editing_value.GetU16Text().substr(\n            selection.start(), selection.end() - selection.start());\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n        page_view()->SetClipboardData(selected);\n#endif\n        if (key_code == KeyCode::kKeyX) {\n          DoDelete();\n        }\n      }\n      break;\n    }\n    case KeyCode::kKeyA: {\n      // Select all.\n      GetRenderEditable()->SetSelection(text_editing_value.text_range());\n      break;\n    }\n    case KeyCode::kKeyZ: {\n      // Revoke.\n      // SetRawContent(revoke_cache_, true);\n      text_editing_history_state_->Undo();\n      break;\n    }\n    default:\n      break;\n  }\n}\n\nbool EditableView::HandleShiftHotKey(LogicalKeyboardKey key_code) {\n  // Move 'extent' value left/right/up/down.\n  const auto& text_editing_value = GetTextEditingValue();\n  auto current_selection = TextRange(text_editing_value.selection());\n  switch (key_code) {\n    case KeyCode::kArrowLeft: {\n      if (text_editing_value.selection().extent() ==\n          text_editing_value.text_range().start()) {\n        break;\n      }\n      auto prev = FindNextOrPrevCharacterSelection(true);\n      GetRenderEditable()->SetSelection(\n          TextRange(current_selection.base(), prev.start()));\n      break;\n    }\n    case KeyCode::kArrowRight: {\n      if (text_editing_value.selection().extent() ==\n          text_editing_value.text_range().end()) {\n        break;\n      }\n      auto next = FindNextOrPrevCharacterSelection(false);\n      GetRenderEditable()->SetSelection(\n          TextRange(current_selection.base(), next.end()));\n      break;\n    }\n    case KeyCode::kArrowUp:\n      GetRenderEditable()->MoveCaretUpDown(VerticalDirection::kUp, true);\n      break;\n    case KeyCode::kArrowDown:\n      GetRenderEditable()->MoveCaretUpDown(VerticalDirection::kDown, true);\n      break;\n\n    default:\n      // other cases should be handled as normal key, e.g:\n      // shift + '2' -> '@'\n      // shift + 'a' -> 'A'\n      return false;\n  }\n  return true;\n}\n\nvoid EditableView::ResetGestureRecognizers() {\n  RemoveGestureRecognizer(multi_tap_recognizer_);\n  RemoveGestureRecognizer(drag_recognizer_);\n  multi_tap_recognizer_ = nullptr;\n  drag_recognizer_ = nullptr;\n  std::unique_ptr<MultiTapGestureRecognizer> multi_tap_recognizer =\n      std::make_unique<MultiTapGestureRecognizer>(\n          page_view()->gesture_manager());\n  multi_tap_recognizer->SetDelegate(this);\n  multi_tap_recognizer_ = multi_tap_recognizer.get();\n  std::unique_ptr<DragGestureRecognizer> drag_recognizer =\n      std::make_unique<HorizontalDragGestureRecognizer>(\n          page_view()->gesture_manager());\n  drag_recognizer->SetDelegate(this);\n  drag_recognizer_ = drag_recognizer.get();\n  multi_tap_recognizer->SetMultiTapCallback([this](auto&& PH1, int count) {\n    OnGestureTap(std::forward<decltype(PH1)>(PH1));\n    if (count == 2) {\n      OnGestureDoubleTap(std::forward<decltype(PH1)>(PH1));\n    }\n    if (count >= 3) {\n      OnGestureTripleTap(std::forward<decltype(PH1)>(PH1));\n    }\n  });\n  drag_recognizer->SetDragStartCallback(\n      [this](auto&& PH1) { OnDragStart(std::forward<decltype(PH1)>(PH1)); });\n  drag_recognizer->SetDragUpdateCallback([this](auto&& PH1, auto&& PH2) {\n    OnDragUpdate(std::forward<decltype(PH1)>(PH1),\n                 std::forward<decltype(PH2)>(PH2));\n  });\n  AddGestureRecognizer(std::move(drag_recognizer));\n  AddGestureRecognizer(std::move(multi_tap_recognizer));\n}\n\nbool EditableView::IsMultiline() { return is_multiline_; }\n\nvoid EditableView::LayoutText(LayoutContext* context) {\n  const double available_max_width =\n      std::max(0.0f, static_cast<LayoutContextMeasure*>(context)->layout_width -\n                         GetRenderEditable()->PaddingLeft() -\n                         GetRenderEditable()->PaddingRight() -\n                         GetRenderEditable()->CaretWidth());\n  const double text_max_width =\n      IsMultiline() ? available_max_width : std::numeric_limits<double>::max();\n  if (paragraph_) {\n    paragraph_->Layout(text_max_width);\n  }\n}\n\nstd::shared_ptr<TextSpan> EditableView::BuildTextSpan(TextStyle style) {\n  const auto& text_editing_value = GetTextEditingValue();\n  TextStyle composingStyle = style;\n#if defined(OS_MACOSX) || defined(OS_ANDROID)\n  composingStyle.text_decoration =\n      TextDecoration{TextDecorationLine::kTextDecorationLineUnderline,\n                     TextDecorationStyle::kSolid};\n#endif\n  std::u16string text_submit =\n      keyboard_input_type_ != KeyboardInputType::kPassword\n          ? text_editing_value.GetU16Text()\n          : std::u16string(text_editing_value.GetU16Length(), u'•');\n#if defined(CLAY_ENABLE_TTTEXT)\n  text_submit += u\"\\n\";\n#endif\n  auto selection = text_editing_value.selection();\n  auto composing = text_editing_value.composing_range();\n  std::vector<std::shared_ptr<TextSpan>> text_spans;\n  text_spans.push_back(std::make_shared<TextSpan>(\n      text_submit.substr(0, composing.start()), style));\n  text_spans.push_back(std::make_shared<TextSpan>(\n      text_submit.substr(composing.start(), composing.length()),\n      composingStyle));\n  text_spans.push_back(\n      std::make_shared<TextSpan>(text_submit.substr(composing.end()), style));\n  return std::make_shared<TextSpan>(std::u16string(), style, text_spans);\n}\n\nconst TextEditingValue& EditableView::GetTextEditingValue() {\n  return text_editing_controller_->GetValue();\n}\n\nvoid EditableView::SetTextEditingValue(const TextEditingValue& value,\n                                       bool is_composing,\n                                       bool need_update_remote) {\n  bool text_changed = value.GetU16Text() != GetTextEditingValue().GetU16Text();\n  bool trigger_input_event =\n      !is_composing && pre_text_value_ != value.GetU16Text();\n  if (trigger_input_event) {\n    pre_text_value_ = value.GetU16Text();\n  }\n  text_editing_controller_->SetValue(value, trigger_input_event,\n                                     need_update_remote);\n  if (text_editing_history_state_ && text_changed) {\n    text_editing_history_state_->Push();\n  }\n}\n\nvoid EditableView::UpdateEditingState(std::string text, TextSelection selection,\n                                      TextRange composing, Affinity affinity) {\n  bool is_composing = !(composing.collapsed());\n  const auto& old_value = GetTextEditingValue();\n  auto new_value = TextEditingValue(\n      text, TextRange(selection.base_offset(), selection.extent_offset()),\n      composing, is_composing, affinity);\n  if (old_value == new_value) {\n    return;\n  }\n  if (!is_composing) {\n    FilterInputIfNeeded(&new_value);\n  }\n  SetTextEditingValue(new_value, is_composing, true);\n  MarkNeedsLayout();\n}\n\nvoid EditableView::PerformAction() { OnPerformAction(keyboard_action_); }\n\nTransform EditableView::ToGlobalTransform() const {\n  return BaseView::LocalToGlobalTransform();\n}\n\nvoid EditableView::UpdateRemoteStateIfNeeded(\n    const TextEditingValue& text_editing_value) {\n  if (text_input_controller_) {\n    if (!text_input_controller_->ConnectKeyboard() ||\n        !text_editing_controller_->NeedUpdateRemote()) {\n      return;\n    }\n    text_input_controller_->SetEditingState(text_editing_value);\n  }\n}\n\nbool EditableView::MatchAttrSettings(KeywordID attr) {\n  switch (attr) {\n    case KeywordID::kShowSoftInputOnfocus:\n    case KeywordID::kConfirmType:\n    case KeywordID::kColor:\n    case KeywordID::kFontSize:\n    case KeywordID::kTextAlign:\n    case KeywordID::kFontFamily:\n    case KeywordID::kCaretColor:\n    case KeywordID::kMaxlength:\n    case KeywordID::kReadonly:\n    case KeywordID::kMaxlines:\n    case KeywordID::kValue:\n    case KeywordID::kDisabled:\n    case KeywordID::kPlaceholder:\n    case KeywordID::kPlaceholderColor:\n    case KeywordID::kPlaceholderFontSize:\n    case KeywordID::kPlaceholderFontWeight:\n      return true;\n    default:\n      break;\n  }\n  return false;\n}\n\nbool EditableView::MatchNGAttrSettings(KeywordID attr) {\n  switch (attr) {\n    case KeywordID::kShowSoftInputOnfocus:\n    case KeywordID::kType:\n    case KeywordID::kConfirmType:\n    case KeywordID::kColor:\n    case KeywordID::kFontSize:\n    case KeywordID::kTextAlign:\n    case KeywordID::kFontFamily:\n    case KeywordID::kCaretColor:\n    case KeywordID::kMaxlength:\n    case KeywordID::kReadonly:\n    case KeywordID::kMaxlines:\n    case KeywordID::kDisabled:\n    case KeywordID::kPlaceholder:\n    case KeywordID::kPlaceholderColor:\n    case KeywordID::kPlaceholderFontSize:\n    case KeywordID::kPlaceholderFontWeight:\n      return true;\n    default:\n      break;\n  }\n  return false;\n}\n\nvoid EditableView::PostPaint() {\n  UpdateCaretRectIfNeeded();\n  UpdateComposingRectIfNeeded();\n}\n\nfloat EditableView::GetDefaultFontSize() const {\n  return FromLogical(kDefaultFontSizeInDip);\n}\n\nbool EditableView::IsPointerAllowed(const GestureRecognizer& gesture_recognizer,\n                                    const PointerEvent& event) {\n  if (&gesture_recognizer == drag_recognizer_ ||\n      &gesture_recognizer == multi_tap_recognizer_) {\n    if (event.device == PointerEvent::DeviceType::kMouse) {\n      return event.buttons == PointerEvent::MouseButton::kPrimary;\n    }\n    return true;\n  } else {\n    FML_DLOG(ERROR) << \"unrecognized gesture recognizer: \"\n                    << gesture_recognizer.GetMemberTag();\n    return true;\n  }\n}\n\nTextEditingHistoryState::TextEditingHistoryState(EditableView* editable_view)\n    : editable_view_(editable_view) {\n  Push();\n}\n\nvoid TextEditingHistoryState::Redo() { Update(*stack_.Redo()); }\n\nvoid TextEditingHistoryState::Undo() {\n  if (throttle_timer_ && !throttle_timer_->Stopped()) {\n    throttle_timer_->Stop();\n    Update(stack_.CurrentValue());\n  } else {\n    Update(*stack_.Undo());\n  }\n}\n\nvoid TextEditingHistoryState::Push() {\n  if (!throttle_timer_) {\n    throttle_timer_ = ThrottledPush();\n  }\n  if (throttle_timer_->Stopped()) {\n    throttle_timer_->Start(duration_, [=]() {\n      if (editable_view_) {\n        auto value = editable_view_->GetTextEditingValue();\n        if (!value.composing()) {\n          stack_.Push(value);\n        }\n      }\n    });\n  }\n}\n\nvoid TextEditingHistoryState::Update(\n    std::optional<TextEditingValue> new_value) {\n  if (new_value.has_value() &&\n      *new_value == editable_view_->GetTextEditingValue()) {\n    return;\n  }\n  editable_view_->SetTextEditingValue(*new_value);\n}\n\nstd::unique_ptr<fml::Timer> TextEditingHistoryState::ThrottledPush() {\n  return std::make_unique<fml::Timer>(\n      editable_view_->page_view()->GetTaskRunner(), false);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/editable_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_EDITABLE_VIEW_H_\n#define CLAY_UI_COMPONENT_EDITABLE_EDITABLE_VIEW_H_\n\n#include <limits>\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/text_input_type_traits.h\"\n#include \"clay/ui/common/text_selection.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/editable/ime_listener.h\"\n#include \"clay/ui/component/editable/text_editing_controller.h\"\n#include \"clay/ui/component/editable/text_input_controller.h\"\n#include \"clay/ui/component/editable/text_span.h\"\n#include \"clay/ui/component/measurable.h\"\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/gesture/drag_gesture_recognizer.h\"\n#include \"clay/ui/gesture/multi_tap_gesture_recognizer.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n#include \"clay/ui/platform/keyboard_types.h\"\n#include \"clay/ui/rendering/editable/render_editable.h\"\n\nnamespace clay {\n\nusing PostPaintCallback = std::function<void()>;\nusing PostPaintCallbackIdType = int64_t;\n\nclass RawTextView;\nclass RenderEditable;\nclass TextEditingHistoryState;\n\nstruct LayoutContextMeasure : public LayoutContext {\n  // Width for text paragraph layout.\n  float layout_width = 0.f;\n};\n\nclass EditableView : public WithTypeInfo<EditableView, BaseView>,\n                     public Measurable,\n                     public IMEListener,\n                     public TextInputClient,\n                     public TextInputControllerDelegate,\n                     public TextEditingController::Observer,\n                     public GestureRecognizer::Delegate {\n public:\n  EditableView(int id, std::string tag, PageView* page_view,\n               bool is_multiline = false, bool layout_root_candidate = true);\n  EditableView(int id, int callback_id, std::string tag, PageView* page_view,\n               bool is_multiline = false, bool layout_root_candidate = true);\n  ~EditableView();\n\n  friend class TextEditingHistoryState;\n\n  int GetCallbackId() override { return callback_id_; }\n\n  bool IsLayoutRootCandidate() const override { return layout_root_candidate_; }\n\n  void OnLayout(LayoutContext* context) override;\n  void Measure(const MeasureConstraint& constraint,\n               MeasureResult& result) override;\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n\n  // TODO(yulitao): Style should be passed in or using theme manager.\n  // Currently use arco design's default styles.\n  void InitDefaultStyle();\n\n  // Override observers\n  void OnValueChanged(const TextEditingValue& value,\n                      const TextEditingController*) override;\n  void OnSelectionChanged(const TextEditingValue& value,\n                          const TextEditingController*) override;\n  void OnContentChanged(const TextEditingValue& value,\n                        const TextEditingController*) override{};\n  void OnUserInput(const TextEditingValue& value,\n                   const TextEditingController*) override;\n\n  void OnContentSizeChanged(const FloatRect& old_rect,\n                            const FloatRect& new_rect) override;\n\n  // Called each time tapped, including double-tap's first & second tap.\n  void OnGestureTap(const PointerEvent& pointer);\n  void OnGestureDoubleTap(const PointerEvent& pointer);\n  void OnGestureTripleTap(const PointerEvent& pointer);\n  void OnDragStart(const FloatPoint& position);\n  void OnDragUpdate(const FloatPoint& position, const FloatSize& delta);\n\n  // Add new_content at current selection and replace the old one if selected.\n  // If |style| is empty, follow previous style, else create new style run.\n  void EditContent(std::string new_content, std::optional<TextStyle> style) {}\n\n  void SetDisabled(bool disabled);\n  void SetDirection(int type) override;\n  void SetMaxLines(uint32_t max_lines);\n  void SetShowSoftInputOnFocus(bool show_soft_input_on_focus);\n  void SetContent(const std::string& content);\n  void SetFontColor(const Color& color);\n  void SetFontSize(float font_size);\n  void SetFontWeight(FontWeight font_weight);\n  void SetLetterSpacing(float letter_spacing);\n  void SetTextAlign(TextAlignment text_alignment);\n  void SetReadOnly(bool read_only);\n  void SetTextDirection(TextDirection text_direction);\n  void SetFontFamily(const std::string& font_family);\n  void SetPlaceholder(const std::string& placeholder);\n  void SetPlaceholderFontSize(float font_size);\n  void SetPlaceholderFontWeight(FontWeight font_weight);\n  void SetPlaceholderColor(const Color& color);\n  void RelayoutWhenSetFontFamily(const std::string& font_family);\n\n  float LayoutWidth();\n\n  LayoutContextMeasure CreateLayoutContext(const MeasureConstraint& constraint);\n\n  // Lynx module UI method\n#define UI_METHOD_LIST_DECLARATION(V) \\\n  V(setValue)                         \\\n  V(getValue)                         \\\n  V(focus)                            \\\n  V(blur)                             \\\n  V(setSelectionRange)\n  UI_METHOD_LIST_DECLARATION(UI_METHOD_DEF);\n#undef UI_METHOD_LIST_DECLARATION\n  // Added by clay end\n\n  void UpdateEditingState(std::string text, TextSelection selection,\n                          TextRange composing, Affinity affinity) override;\n  void PerformAction() override;\n\n  Transform ToGlobalTransform() const;\n\n  void UpdateRemoteStateIfNeeded(\n      const TextEditingValue& text_editing_value) override;\n\n  bool MatchAttrSettings(KeywordID attr);\n  bool MatchNGAttrSettings(KeywordID attr);\n\n  txt::Paragraph* GetParagraph();\n  FloatRect ComputeCaretRect();\n\n  float EstimateHeightWithMaxLines();\n\n  TextRange FindNextOrPrevCharacterSelection(bool is_forward);\n\n  RenderEditable* GetRenderEditable();\n  float GetDefaultFontSize() const;\n  const TextEditingValue& GetTextEditingValue();\n\n  void SetKeyboardType(KeyboardInputType type) { keyboard_input_type_ = type; }\n  void SetKeyboardAction(KeyboardAction action) { keyboard_action_ = action; }\n\n  bool IsPointerAllowed(const GestureRecognizer& gesture_recognizer,\n                        const PointerEvent& event) override;\n\n  void SetPlaceholderHeight(const double placeholder_height) {\n    placeholder_height_ = placeholder_height;\n  }\n  double GetPlaceholderHeight() { return placeholder_height_; }\n  std::string GetPlaceholder() { return placeholder_; }\n  std::optional<float> GetPlaceholderFontSize() {\n    return placeholder_font_size_;\n  }\n\n protected:\n  size_t FilterInputTextByType(TextEditingValue* value);\n  size_t FilterInputTextByUser(TextEditingValue* value);\n  size_t FilterInput(TextEditingValue* value, const std::string& pattern);\n  size_t FilterNewLine(TextEditingValue* value);\n  size_t FilterMaxLength(TextEditingValue* value);\n  // TODO(yulitao): Design formatters to deal with input types.\n  using FilterFunc = size_t (EditableView::*)(TextEditingValue*);\n  void ApplyFilter(FilterFunc func, bool take_effect);\n  void FilterInputIfNeeded(TextEditingValue* value);\n  bool OnKeyEventInternal(const KeyEvent* event);\n  bool HandleSynthesizedKeyEvent(const KeyEvent* event);\n\n  void DeleteSurroundingText(int before_length, int after_length);\n\n  void SetTextEditingValue(const TextEditingValue&, bool is_composing = false,\n                           bool need_update_remote = true);\n  void OnCommitText(std::string text) override;\n\n  virtual void BeginEditingIfNeeded();\n  void BeginEditing();\n  void QuitEditing();\n\n  void LayoutText(LayoutContext* context);\n  std::shared_ptr<TextSpan> BuildTextSpan(TextStyle style);\n\n  std::string input_filter_pattern_;\n  bool editing_ = false;\n  std::unique_ptr<txt::Paragraph> paragraph_;\n  TextStyle text_style_;\n  std::u16string pre_text_value_;\n\n private:\n  void OnComposingText(std::string text) override;\n  void OnDeleteSurroundingText(int before_length, int after_length) override;\n  void OnKeyboardEvent(std::unique_ptr<KeyEvent> key_event) override;\n  bool OnKeyEvent(const KeyEvent* event) override;\n  void OnPerformAction(KeyboardAction action) override;\n  void OnFinishInput() override;\n\n  void UpdateCaretRectIfNeeded();\n  void UpdateComposingRectIfNeeded();\n\n  void RequestKeyboard();\n\n  void FocusHasChanged(bool focused, bool is_leaf) override;\n\n  void DoBackspace();\n  void DoDelete();\n  void MoveCaret(KeyCode keycode);\n\n  void ToggleCaret();\n  void TwinkleCaretPeriodically();\n\n  bool ApplyHotKey(const KeyEvent* key_event);\n  void UpdateHotKeyTag(LogicalKeyboardKey key_code, bool is_up);\n  void HandleCommandHotKey(LogicalKeyboardKey key_code);\n  void HandleCtrlHotKey(LogicalKeyboardKey key_code);\n  bool HandleShiftHotKey(LogicalKeyboardKey key_code);\n  void HandleWinCtrlAndMacCommandHotKey(LogicalKeyboardKey key_code);\n  void ResetGestureRecognizers();\n  bool IsMultiline();\n\n  void PostPaint() override;\n\n  // Used to filter redundant handling for tap/double-tap.\n  int last_tap_pointer_ = 0;\n\n  bool disabled_ = false;\n  bool readonly_ = false;\n  // If false, soft keyboard will always be hidden.\n  bool show_soft_input_on_focus_ = true;\n  std::string placeholder_;\n  std::optional<float> placeholder_font_size_;\n  std::optional<FontWeight> placeholder_font_weight_;\n  std::optional<Color> placeholder_color_;\n  float placeholder_height_ = 0.f;\n  KeyboardInputType keyboard_input_type_ = KeyboardInputType::kClassText;\n  KeyboardAction keyboard_action_ = KeyboardAction::kDone;\n  uint32_t max_lines_ = std::numeric_limits<uint32_t>::max();\n  uint32_t max_length_ = std::numeric_limits<uint32_t>::max();\n\n  std::vector<FilterFunc> filter_funcs_;\n\n  // Indicate whether caret is shown or hidden while twinkling.\n  // False if hidden.\n  bool twinkle_flag_ = false;\n  std::unique_ptr<fml::RepeatingTimer> caret_timer_;\n\n  // When has no contents, use this paragraph to measure a default line height.\n  std::unique_ptr<txt::Paragraph> template_paragraph_;\n\n  // TODO(wangchen): replace by EditingTextValue\n  std::unique_ptr<TextEditingController> text_editing_controller_;\n  uint32_t hot_key_tag_ = 0;\n\n  std::unique_ptr<TextInputController> text_input_controller_;\n  std::unique_ptr<TextEditingHistoryState> text_editing_history_state_;\n  MultiTapGestureRecognizer* multi_tap_recognizer_ = nullptr;\n  DragGestureRecognizer* drag_recognizer_ = nullptr;\n  int32_t callback_id_ = -1;\n  bool is_multiline_;\n  bool layout_root_candidate_;\n};\n\nclass UndoStack {\n public:\n  UndoStack() = default;\n  std::optional<TextEditingValue> CurrentValue() {\n    if (list_.empty()) {\n      return std::nullopt;\n    }\n    return list_[*index_];\n  }\n  void Push(const TextEditingValue& value) {\n    if (list_.empty()) {\n      index_ = 0;\n      list_.push_back(value);\n      return;\n    }\n    FML_DCHECK(index_ < list_.size() && index_ >= 0);\n    const auto& current_value = CurrentValue();\n    if (current_value.has_value() && current_value == value) {\n      return;\n    }\n    if (index_.has_value() && index_ != list_.size() - 1) {\n      list_.erase(list_.begin() + *index_ + 1, list_.end());\n    }\n    list_.push_back(value);\n    index_ = list_.size() - 1;\n  }\n  std::optional<TextEditingValue> Undo() {\n    if (list_.empty()) {\n      return std::nullopt;\n    }\n    FML_DCHECK(index_ < list_.size() && index_ >= 0);\n    if (index_ != 0) {\n      index_ = *index_ - 1;\n    }\n    return CurrentValue();\n  }\n  std::optional<TextEditingValue> Redo() {\n    if (list_.empty()) {\n      return std::nullopt;\n    }\n    FML_DCHECK(index_ < list_.size() && index_ >= 0);\n    if (index_ < list_.size() - 1) {\n      index_ = *index_ + 1;\n    }\n    return CurrentValue();\n  }\n\n  void Clear() {\n    list_.clear();\n    index_ = -1;\n  }\n\n private:\n  std::vector<TextEditingValue> list_;\n  std::optional<int> index_;\n};\n\nclass TextEditingHistoryState {\n public:\n  static constexpr fml::TimeDelta kThrottleDuration =\n      fml::TimeDelta::FromMilliseconds(500);\n  explicit TextEditingHistoryState(EditableView* editable_view);\n  void Undo();\n  void Redo();\n  // Note(wangchen): If there is a lot of text it may cause memory bloat\n  void Push();\n  std::unique_ptr<fml::Timer> ThrottledPush();\n\n private:\n  void Update(std::optional<TextEditingValue> new_value);\n  UndoStack stack_;\n  std::unique_ptr<fml::Timer> throttle_timer_;\n  fml::TimeDelta duration_ = kThrottleDuration;\n  EditableView* editable_view_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_EDITABLE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/editable/ime_listener.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_IME_LISTENER_H_\n#define CLAY_UI_COMPONENT_EDITABLE_IME_LISTENER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/ui/event/key_event.h\"\n#include \"clay/ui/platform/keyboard_types.h\"\n\nnamespace clay {\n\nclass IMEListener {\n public:\n  virtual void OnCommitText(std::string text) = 0;\n  virtual void OnComposingText(std::string text) = 0;\n  virtual void OnKeyboardEvent(std::unique_ptr<KeyEvent> key_event) = 0;\n  virtual void OnDeleteSurroundingText(int before_length, int after_length) = 0;\n  virtual void OnPerformAction(KeyboardAction action) = 0;\n  virtual void OnFinishInput() = 0;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_EDITABLE_IME_LISTENER_H_\n"
  },
  {
    "path": "clay/ui/component/editable/ime_utils.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/ime_utils.h\"\n\nnamespace clay {\nKeyboardInputType ConvertInputType(const std::string& type) {\n  if (type == attr_value::kInputTypeNumber ||\n      type == attr_value::kInputTypeDigit) {\n    return KeyboardInputType::kClassNumber;\n  }\n  if (type == attr_value::kInputTypeTel) {\n    return KeyboardInputType::kClassPhone;\n  }\n  if (type == attr_value::kInputTypePassword) {\n    return KeyboardInputType::kPassword;\n  }\n  if (type == attr_value::kInputTypeEmail) {\n    return KeyboardInputType::kEmailAddress;\n  }\n  return KeyboardInputType::kClassText;\n}\n\nKeyboardAction ConvertConfirmType(const std::string& type,\n                                  KeyboardAction default_value) {\n  if (type == attr_value::kInputActionSend) {\n    return KeyboardAction::kSend;\n  }\n  if (type == attr_value::kInputActionSearch) {\n    return KeyboardAction::kSearch;\n  }\n  if (type == attr_value::kInputActionGo) {\n    return KeyboardAction::kGo;\n  }\n  if (type == attr_value::kInputActionDone) {\n    return KeyboardAction::kDone;\n  }\n  if (type == attr_value::kInputActionNext) {\n    return KeyboardAction::kNext;\n  }\n  return default_value;\n}\n\nconst char* ToKeyboardActionType(KeyboardAction action) {\n  switch (action) {\n    case KeyboardAction::kMultiLine:\n      return \"TextInputAction.newline\";\n    case KeyboardAction::kDone:\n      return \"TextInputAction.done\";\n    case KeyboardAction::kSearch:\n      return \"TextInputAction.search\";\n    case KeyboardAction::kGo:\n      return \"TextInputAction.go\";\n    case KeyboardAction::kNext:\n      return \"TextInputAction.next\";\n    default:\n      return \"TextInputAction.none\";\n  }\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/ime_utils.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_IME_UTILS_H_\n#define CLAY_UI_COMPONENT_EDITABLE_IME_UTILS_H_\n\n#include <string>\n\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/platform/keyboard_types.h\"\n\nnamespace clay {\n\nKeyboardInputType ConvertInputType(const std::string& type);\n\nKeyboardAction ConvertConfirmType(\n    const std::string& type,\n    KeyboardAction default_value = KeyboardAction::kMultiLine);\n\nconst char* ToKeyboardActionType(KeyboardAction action);\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_IME_UTILS_H_\n"
  },
  {
    "path": "clay/ui/component/editable/input_ng_view.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/input_ng_view.h\"\n\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nInputNGView::InputNGView(int id, PageView* page_view, bool is_multiline,\n                         bool layout_root_candidate)\n    : WithTypeInfo(id, id, \"input-ng\", page_view, is_multiline,\n                   layout_root_candidate) {\n  SetFocusable(true);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/input_ng_view.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_INPUT_NG_VIEW_H_\n#define CLAY_UI_COMPONENT_EDITABLE_INPUT_NG_VIEW_H_\n\n#include <map>\n#include <string>\n\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/editable/editable_view.h\"\n\nnamespace clay {\n\nclass InputNGView : public WithTypeInfo<InputNGView, EditableView> {\n public:\n  InputNGView(int id, PageView* page_view, bool is_multiline = false,\n              bool layout_root_candidate = true);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_INPUT_NG_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/editable/input_view.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/input_view.h\"\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <utility>\n\n#include \"clay/ui/component/editable/editable_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n\nnamespace clay {\nnamespace {\n\nLYNX_UI_METHOD_BEGIN(InputView) {\n  LYNX_UI_METHOD(InputView, addText);\n  LYNX_UI_METHOD(InputView, sendDelEvent);\n  LYNX_UI_METHOD(InputView, beginEdit);\n  LYNX_UI_METHOD(InputView, quitEdit);\n  LYNX_UI_METHOD(InputView, controlKeyBoard);\n  LYNX_UI_METHOD(InputView, setInputFilter);\n  LYNX_UI_METHOD(InputView, select);\n}\nLYNX_UI_METHOD_END(InputView);\n\n}  // namespace\n\nInputView::InputView(int id, PageView* page_view, bool is_multiline,\n                     bool layout_root_candidate)\n    : InputView(id, id, page_view, is_multiline, layout_root_candidate) {\n  // max_lines 1 by default, refactored from view_factory.cc\n  SetMaxLines(1);\n}\n\nInputView::InputView(int id, int callback_id, PageView* page_view,\n                     bool is_multiline, bool layout_root_candidate)\n    : WithTypeInfo(id, callback_id, \"input\", page_view, is_multiline,\n                   layout_root_candidate) {}\n\nvoid InputView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  switch (kw) {\n    case KeywordID::kEditOnFocus:\n      begin_edit_on_focus_ = attribute_utils::GetBool(value);\n      break;\n    case KeywordID::kFocus: {\n      bool focus = attribute_utils::GetBool(value);\n      if (focus) {\n        // NOLINTNEXTLINE\n        page_view()->GetTaskRunner()->PostTask([this] { RequestFocus(); });\n      } else {\n        ClearFocus();\n      }\n    } break;\n    case KeywordID::kValue:\n      SetContent(attribute_utils::GetCString(value));\n      break;\n    case KeywordID::kFontSize: {\n      double font_size = 0.0;\n      if (attribute_utils::TryGetNum(value, font_size)) {\n        SetFontSize(static_cast<float>(font_size));\n      }\n    } break;\n    case KeywordID::kLineHeight:\n      SetLineHeight(attribute_utils::GetDouble(value, GetDefaultFontSize()));\n      break;\n    case KeywordID::kSendComposingInput:\n      SetSendComposingInput(attribute_utils::GetBool(value));\n      break;\n    default:\n      EditableView::SetAttribute(attr_c, value);\n      break;\n  }\n}\n\nvoid InputView::SetContent(const std::string& content) {\n  auto text_editing_value = GetTextEditingValue();\n  text_editing_value.SetTextAndReserveSelectionState(content);\n  FilterInputIfNeeded(&text_editing_value);\n  SetTextEditingValue(text_editing_value);\n  MarkNeedsLayout();\n}\n\nvoid InputView::SetFontSize(float font_size) {\n  EditableView::SetFontSize(font_size);\n  UpdateLineHeightIfNeeded();\n}\n\nvoid InputView::UpdateLineHeightIfNeeded() {\n  if (line_height_.has_value()) {\n    text_style_.line_height = *line_height_ / *text_style_.font_size;\n  }\n}\n\nvoid InputView::SetLineHeight(float line_height) {\n  line_height_ = line_height;\n  UpdateLineHeightIfNeeded();\n  MarkNeedsLayout();\n}\n\nvoid InputView::addText(const LynxModuleValues& args,\n                        const LynxUIMethodCallback& callback) {\n  FML_DCHECK(args.names.size() == args.values.size());\n\n  std::string content;\n  CastNamedLynxModuleArgs({\"text\"}, args, content);\n  OnCommitText(content);\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid InputView::sendDelEvent(const LynxModuleValues& args,\n                             const LynxUIMethodCallback& callback) {\n  FML_DCHECK(args.names.size() == args.values.size());\n\n  int action = 0;\n  int length = 0;\n  CastNamedLynxModuleArgs({\"action\", \"length\"}, args, action, length);\n  if (action == 1) {\n    // According to lynx input UI method definition, if action == 1, delete\n    // text with length 1.\n    length = 1;\n  }\n  DeleteSurroundingText(length, 0);\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid InputView::controlKeyBoard(const LynxModuleValues& args,\n                                const LynxUIMethodCallback& callback) {\n  auto res = LynxUIMethodResult::kUnknown;\n  // null is not acceptable, so don't use `CastLynxModuleArgs`\n  if (auto it = std::find(args.names.begin(), args.names.end(), \"action\");\n      it != args.names.end()) {\n    int index = std::distance(args.names.begin(), it);\n    double action_num;\n    if (attribute_utils::TryGetNum(args.values.at(index), action_num)) {\n      switch (static_cast<int>(action_num)) {\n        case 0:  // Focus & show keyboard\n          BeginEditingIfNeeded();\n          res = LynxUIMethodResult::kSuccess;\n          break;\n        case 1:  // Focus & hide keyboard\n          FML_DLOG(WARNING) << \"controlKeyBoard(action = 1) not implemented\";\n          // TODO(renjiayi): complete this\n          break;\n        case 2:  // Focus & keep keyboard unchanged\n          RequestFocus();\n          res = LynxUIMethodResult::kSuccess;\n          break;\n        case 3:  // Blur\n          ClearFocus();\n          res = LynxUIMethodResult::kSuccess;\n          break;\n        default:\n          break;\n      }\n    }\n  }\n  callback(res, clay::Value());\n}\n\nvoid InputView::setInputFilter(const LynxModuleValues& args,\n                               const LynxUIMethodCallback& callback) {\n  std::string pattern;\n  CastNamedLynxModuleArgs({\"pattern\"}, args, pattern);\n  if (pattern.empty()) {\n    input_filter_pattern_ = {};\n    ApplyFilter(&InputView::FilterInputTextByUser, false);\n  } else {\n    input_filter_pattern_ = pattern;\n    ApplyFilter(&InputView::FilterInputTextByUser, true);\n  }\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid InputView::select(const LynxModuleValues& args,\n                       const LynxUIMethodCallback& callback) {\n  auto text_editing_value = GetTextEditingValue();\n  text_editing_value.SetSelection(text_editing_value.text_range());\n  SetTextEditingValue(text_editing_value);\n  MarkNeedsLayout();\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid InputView::beginEdit(const LynxModuleValues&,\n                          const LynxUIMethodCallback& callback) {\n  BeginEditingIfNeeded();\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid InputView::quitEdit(const LynxModuleValues&,\n                         const LynxUIMethodCallback& callback) {\n  if (editing_) {\n    QuitEditing();\n  }\n  callback(LynxUIMethodResult::kSuccess, clay::Value());\n}\n\nvoid InputView::UpdateEditingState(std::string text, TextSelection selection,\n                                   TextRange composing, Affinity affinity) {\n  bool is_composing = !(composing.collapsed());\n  const auto& old_value = GetTextEditingValue();\n  auto new_value = TextEditingValue(\n      text, TextRange(selection.base_offset(), selection.extent_offset()),\n      composing, is_composing, affinity);\n  if (old_value == new_value) {\n    return;\n  }\n  if (!is_composing) {\n    FilterInputIfNeeded(&new_value);\n  }\n  SetTextEditingValue(new_value, send_composing_input_ ? false : is_composing,\n                      true);\n  MarkNeedsLayout();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/input_view.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_INPUT_VIEW_H_\n#define CLAY_UI_COMPONENT_EDITABLE_INPUT_VIEW_H_\n\n#include <map>\n#include <string>\n\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/editable/editable_view.h\"\n\nnamespace clay {\n\nclass TextEditingHistoryState;\n\nclass InputView : public WithTypeInfo<InputView, EditableView> {\n public:\n  InputView(int id, PageView* page_view, bool is_multiline = false,\n            bool layout_root_candidate = true);\n  InputView(int id, int callback_id, PageView* page_view,\n            bool is_multiline = false, bool layout_root_candidate = true);\n  ~InputView() = default;\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  void UpdateEditingState(std::string text, TextSelection selection,\n                          TextRange composing, Affinity affinity) override;\n\n  void RequestKeyboard();\n  // Add new_content at current selection and replace the old one if selected.\n  // If |style| is empty, follow previous style, else create new style run.\n  void EditContent(std::string new_content, std::optional<TextStyle> style) {}\n  void UpdateLineHeightIfNeeded();\n\n  void SetContent(const std::string& content);\n  void SetFontSize(float font_size);\n  void SetLineHeight(float line_height);\n  void SetSendComposingInput(bool send_composing_input) {\n    send_composing_input_ = send_composing_input;\n  }\n\n  // Lynx module UI method\n#define UI_METHOD_LIST_DECLARATION(V) \\\n  V(addText)                          \\\n  V(sendDelEvent)                     \\\n  V(controlKeyBoard)                  \\\n  V(setInputFilter)                   \\\n  V(select)                           \\\n  V(beginEdit)                        \\\n  V(quitEdit)\n  UI_METHOD_LIST_DECLARATION(UI_METHOD_DEF);\n#undef UI_METHOD_LIST_DECLARATION\n\n private:\n  bool begin_edit_on_focus_ = true;\n\n  bool send_composing_input_ = false;\n  // The unit is px\n  std::optional<float> line_height_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_INPUT_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/editable/text_editing_controller.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/text_editing_controller.h\"\n\n#include <string>\n#include <vector>\n\n#include \"clay/ui/common/text_input_type_traits.h\"\n\nnamespace clay {\n\nvoid TextEditingController::AddObserver(Observer* observer) {\n  FML_CHECK(observer);\n  observers_.emplace_back(observer);\n}\n\nvoid TextEditingController::RemoveObserver(Observer* observer) {\n  FML_CHECK(observer);\n  auto position = std::find(observers_.begin(), observers_.end(), observer);\n  if (position == observers_.end()) {\n    FML_DCHECK(false) << \"Did you forget add observer?\";\n    return;\n  }\n  observers_.erase(position);\n}\n\nvoid TextEditingController::NotifyValueChanged(int type) {\n  for (Observer* observer : observers_) {\n    observer->OnValueChanged(value_, this);\n    if (type & kUserInput) {\n      observer->OnUserInput(value_, this);\n    }\n    if (type & kContent) {\n      observer->OnContentChanged(value_, this);\n    }\n    if (type & kSelection) {\n      observer->OnSelectionChanged(value_, this);\n    }\n  }\n}\n\nbool TextEditingController::SetValue(const TextEditingValue& value,\n                                     bool trigger_input_event,\n                                     bool need_update_remote) {\n  need_update_remote_ = need_update_remote;\n  if (value == value_) {\n    return false;\n  }\n  int type = ValueChangeType::kNone;\n  if (!(value_.selection() == value.selection())) {\n    type |= ValueChangeType::kSelection;\n    selection_set_ = true;\n  }\n  if (value_.GetText() != value.GetText()) {\n    type |= ValueChangeType::kContent;\n  }\n  if (trigger_input_event) {\n    type |= ValueChangeType::kUserInput;\n  }\n  value_ = value;\n  NotifyValueChanged(type);\n  return true;\n}\n\nbool TextEditingController::MoveSelectionToEndIfNeeded() {\n  if (!selection_set_) {\n    value_.SetSelection(TextRange{value_.GetU16Text().length()});\n    selection_set_ = true;\n    return true;\n  }\n  return false;\n}\n\n};  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/text_editing_controller.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_TEXT_EDITING_CONTROLLER_H_\n#define CLAY_UI_COMPONENT_EDITABLE_TEXT_EDITING_CONTROLLER_H_\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/common/text_input_type_traits.h\"\n\nnamespace clay {\n\nclass TextEditingController {\n public:\n  TextEditingController() = default;\n  explicit TextEditingController(const TextEditingValue& value)\n      : value_(value) {}\n  explicit TextEditingController(TextEditingValue&& value)\n      : value_(std::move(value)) {}\n  enum ValueChangeType : int {\n    kNone = 0,\n    kSelection = 1,\n    kContent = 2,\n    kUserInput = 4,\n  };\n  class Observer {\n   public:\n    virtual void OnValueChanged(const TextEditingValue& value,\n                                const TextEditingController*) = 0;\n    virtual void OnSelectionChanged(const TextEditingValue& value,\n                                    const TextEditingController*) = 0;\n    virtual void OnUserInput(const TextEditingValue& value,\n                             const TextEditingController*) = 0;\n    virtual void OnContentChanged(const TextEditingValue& value,\n                                  const TextEditingController*) = 0;\n  };\n  void AddObserver(Observer* observer);\n\n  // As same as common sense, observers must be removed before destroyed.\n  // Must NOT RemoveObserver during Observer::OnValueChanged callback!\n  void RemoveObserver(Observer* observer);\n\n  void NotifyValueChanged(int type);\n\n  bool SetValue(const TextEditingValue& value, bool trigger_input_event,\n                bool need_update_remote);\n\n  bool MoveSelectionToEndIfNeeded();\n\n  const TextEditingValue& GetValue() { return value_; }\n  bool NeedUpdateRemote() const { return need_update_remote_; }\n  void SetNeedUpdateRemote(bool need_update_remote) {\n    need_update_remote_ = need_update_remote;\n  }\n\n private:\n  std::vector<Observer*> observers_;\n  TextEditingValue value_;\n  bool selection_set_ = false;\n  bool need_update_remote_ = false;\n};\n\n};  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_TEXT_EDITING_CONTROLLER_H_\n"
  },
  {
    "path": "clay/ui/component/editable/text_input_controller.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/text_input_controller.h\"\n\n#include <stdint.h>\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/input_client_manager.h\"\n#include \"clay/ui/common/text_input_type_traits.h\"\n#include \"clay/ui/component/editable/editable_view.h\"\n#include \"clay/ui/component/editable/ime_utils.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nTextInputController::TextInputController(PageView* page_view, int client_id,\n                                         TextInputClient* client)\n    : page_view_(page_view),\n      client_(client),\n      client_id_(client_id),\n      weak_factory_(this) {\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  auto manager = page_view_->GetInputClientManager();\n  InputClientManager::TextInputCallback callback;\n\n  callback.on_update_edit_state =\n      [weak = weak_factory_.GetWeakPtr()](\n          uint64_t selection_base, uint64_t composing_extent,\n          const char* selection_affinity, const char* text,\n          uint64_t selection_extent, uint64_t composing_base) {\n        std::string text_str = std::string(text);\n        Affinity selection_affinity_value = Affinity::kDownstream;\n        if (selection_affinity != nullptr) {\n          if (strcmp(selection_affinity, \"TextAffinity.downstream\") == 0) {\n            selection_affinity_value = Affinity::kDownstream;\n          } else if (strcmp(selection_affinity, \"TextAffinity.upstream\") == 0) {\n            selection_affinity_value = Affinity::kUpstream;\n          }\n        }\n        weak->UpdateEditingState(\n            text_str,\n            TextSelection((int)selection_base, (int)selection_extent,\n                          selection_affinity_value),\n            TextRange(std::max((int)composing_base, 0),\n                      std::max((int)composing_extent, 0)),\n            selection_affinity_value);\n      };\n\n  callback.on_perform_action = [weak = weak_factory_.GetWeakPtr()]() {\n    weak->PerformAction();\n  };\n\n  manager->AddClientCallback(client_id, callback);\n#endif\n}\n\nTextInputController::~TextInputController() {\n  page_view_->GetInputClientManager()->RemoveClientCallback(client_id_);\n}\n\nvoid TextInputController::SetClient(int client_id, KeyboardAction input_action,\n                                    KeyboardInputType input_type) {\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  const char* input_type_chr =\n      multiline_ ? \"TextInputType.multiline\" : \"TextInputType.text\";\n  page_view_->SetTextInputClient(client_id, ToKeyboardActionType(input_action),\n                                 input_type_chr);\n  connect_keyboard_ = true;\n#endif\n}\n\nvoid TextInputController::ClearClient() {\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  page_view_->ClearTextInputClient();\n  connect_keyboard_ = false;\n#endif\n}\n\nvoid TextInputController::SetEditableTransform(const Transform& transform) {\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  const auto& matrix = transform.matrix();\n  float transform_matrix[16] = {\n      matrix.Get(0, 0), matrix.Get(1, 0), matrix.Get(2, 0), matrix.Get(3, 0),\n      matrix.Get(0, 1), matrix.Get(1, 1), matrix.Get(2, 1), matrix.Get(3, 1),\n      matrix.Get(0, 2), matrix.Get(1, 2), matrix.Get(2, 2), matrix.Get(3, 2),\n      matrix.Get(0, 3), matrix.Get(1, 3), matrix.Get(2, 3), matrix.Get(3, 3),\n  };\n  page_view_->SetEditableTransform(transform_matrix);\n#endif\n}\n\nvoid TextInputController::SetEditingState(\n    const TextEditingValue& text_editing_value) {\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  page_view_->SetEditingState(\n      (uint64_t)text_editing_value.selection().base(),\n      (uint64_t)text_editing_value.composing_range().extent(),\n      \"TextAffinity.downstream\", text_editing_value.GetText(), false,\n      (uint64_t)text_editing_value.selection().extent(),\n      (uint64_t)text_editing_value.composing_range().base());\n#endif\n}\n\nvoid TextInputController::SetCaretRect(FloatRect rect) {\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  page_view_->SetCaretRect(rect.x(), rect.y(), rect.width(), rect.height());\n#endif\n}\n\nvoid TextInputController::SetComposingRect(FloatRect rect) {\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  page_view_->setMarkedTextRect(rect.x(), rect.y(), rect.width(),\n                                rect.height());\n#endif\n}\n\nvoid TextInputController::Show() {\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  page_view_->ShowTextInput();\n#endif\n}\n\nvoid TextInputController::Hide() {\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  page_view_->HideTextInput();\n#endif\n}\n\nvoid TextInputController::InputFilterAsync(\n    const std::string& input, const std::string& pattern,\n    std::function<void(const std::string&)> callback) {\n  page_view_->FilterInputAsync(input, pattern, callback);\n}\n\nvoid TextInputController::UpdateEditingState(std::string text,\n                                             TextSelection selection,\n                                             TextRange composing,\n                                             Affinity affinity) {\n  client_->UpdateEditingState(text, selection, composing, affinity);\n}\n\nvoid TextInputController::PerformAction() { client_->PerformAction(); }\n\nvoid TextInputController::SetMultiline(bool multiline) {\n  multiline_ = multiline;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/text_input_controller.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_TEXT_INPUT_CONTROLLER_H_\n#define CLAY_UI_COMPONENT_EDITABLE_TEXT_INPUT_CONTROLLER_H_\n\n#include <string>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/transform.h\"\n#include \"clay/ui/common/text_input_type_traits.h\"\n#include \"clay/ui/common/text_selection.h\"\n#include \"clay/ui/component/editable/ime_utils.h\"\n#include \"clay/ui/event/key_event.h\"\n\nnamespace clay {\n\nclass PageView;\n\nenum class TextAffinity {\n  kDownstream,\n  kUpstream,\n};\n\nclass TextInputClient {\n public:\n  virtual void UpdateEditingState(std::string text, TextSelection selection,\n                                  TextRange composing, Affinity affinity) = 0;\n  virtual void PerformAction() = 0;\n};\n\nclass TextInputController {\n public:\n  explicit TextInputController(PageView* page_view, int client_id,\n                               TextInputClient* client);\n  ~TextInputController();\n\n  void SetClient(int client_id, KeyboardAction input_action,\n                 KeyboardInputType input_type);\n  void ClearClient();\n  void SetEditableTransform(const Transform& transform);\n  void SetEditingState(const TextEditingValue& text_editing_value);\n  void SetCaretRect(FloatRect rect);\n  void SetComposingRect(FloatRect rect);\n  void Show();\n  void Hide();\n  void InputFilterAsync(const std::string& input, const std::string& pattern,\n                        std::function<void(const std::string&)> callback);\n\n  void UpdateEditingState(std::string text, TextSelection selection,\n                          TextRange composing, Affinity affinity);\n  void PerformAction();\n\n  bool ConnectKeyboard() { return connect_keyboard_; }\n\n  void SetMultiline(bool multiline);\n\n private:\n  [[maybe_unused]] PageView* page_view_ = nullptr;\n  TextInputClient* client_ = nullptr;\n  [[maybe_unused]] int client_id_;\n  bool connect_keyboard_ = false;\n  bool multiline_ = true;\n\n  fml::WeakPtrFactory<TextInputController> weak_factory_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_TEXT_INPUT_CONTROLLER_H_\n"
  },
  {
    "path": "clay/ui/component/editable/text_span.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/text_span.h\"\n\n#include <memory>\n#include <utility>\n\nnamespace clay {\n\nTextSpan::TextSpan(std::u16string text, TextStyle text_style,\n                   std::vector<std::shared_ptr<TextSpan>> children)\n    : text_(text), text_style_(text_style), children_(std::move(children)) {}\n\nvoid TextSpan::Build(TextParagraphBuilder& builder) {\n  if (!text_.empty()) {\n    builder.PushStyle(text_style_);\n    builder.AddText(text_);\n  }\n  if (!children_.empty()) {\n    for (auto& child : children_) {\n      child->Build(builder);\n    }\n  }\n  if (!text_.empty()) {\n    builder.Pop();\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/text_span.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_TEXT_SPAN_H_\n#define CLAY_UI_COMPONENT_EDITABLE_TEXT_SPAN_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/string/string_utils.h\"\n#include \"clay/ui/component/text/text_paragraph_builder.h\"\n#include \"clay/ui/component/text/text_style.h\"\n\nnamespace clay {\n\nclass TextSpan {\n public:\n  TextSpan(std::u16string text = {}, TextStyle text_style = {},\n           std::vector<std::shared_ptr<TextSpan>> children = {});\n  ~TextSpan() = default;\n  void Build(TextParagraphBuilder& builder);\n\n private:\n  std::u16string text_;\n  TextStyle text_style_;\n  std::vector<std::shared_ptr<TextSpan>> children_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_TEXT_SPAN_H_\n"
  },
  {
    "path": "clay/ui/component/editable/text_utils.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2014 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_TEXT_UTILS_H_\n#define CLAY_UI_COMPONENT_EDITABLE_TEXT_UTILS_H_\n\n#include <algorithm>\n#include <optional>\n#include <string>\n\n#include \"clay/gfx/geometry/float_point.h\"\n\nnamespace clay {\n\nclass TextUtils {\n public:\n  /// Returns true iff the given value is a valid UTF-16 high (first) surrogate.\n  /// The value must be a UTF-16 code unit, meaning it must be in the range\n  /// 0x0000-0xFFFF.\n  ///\n  /// See also:\n  ///   * https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF\n  ///   * [isLowSurrogate], which checks the same thing for low (second)\n  /// surrogates.\n  static bool IsHighSurrogate(int value) { return (value & 0xFC00) == 0xD800; }\n\n  /// Returns true iff the given value is a valid UTF-16 low (second) surrogate.\n  /// The value must be a UTF-16 code unit, meaning it must be in the range\n  /// 0x0000-0xFFFF.\n  ///\n  /// See also:\n  ///   * https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF\n  ///   * [isHighSurrogate], which checks the same thing for high (first)\n  /// surrogates.\n  static bool IsLowSurrogate(int value) { return (value & 0xFC00) == 0xDC00; }\n\n  // Returns true iff the given value is a valid UTF-16 surrogate. The value\n  // must be a UTF-16 code unit, meaning it must be in the range 0x0000-0xFFFF.\n  //\n  // See also:\n  //   * https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF\n  static bool IsUtf16Surrogate(uint32_t value) {\n    return (value & 0xF800) == 0xD800;\n  }\n\n  // Returns true if the given position falls in the center of a surrogate pair.\n  static bool BreaksSurrogatePair(const std::u16string& text, int position) {\n    int length = text.length();\n    return IsHighSurrogate(text.at(position - 1 >= length\n                                       ? position - length - 1\n                                       : std::max(0, position - 1))) &&\n           IsLowSurrogate(text.at(position >= length ? position - length\n                                                     : std::max(0, position)));\n  }\n\n  // Checks if the glyph is either [Unicode.RLM] or [Unicode.LRM]. These values\n  // take up zero space and do not have valid bounding boxes around them.\n  //\n  // We do not directly use the [Unicode] constants since they are strings.\n  static bool IsUnicodeDirectionality(uint32_t value) {\n    return value == 0x200F || value == 0x200E;\n  }\n\n  static bool JudgeIfZWJCharacter(const std::u16string& text, int position,\n                                  bool prev) {\n    int length = text.length();\n    if (text.empty() || position > length || position < 0) {\n      return false;\n    }\n    if (prev && position > 0) {\n      size_t prev_code_unit = text.at(std::max(0, position - 1));\n      if (IsHighSurrogate(prev_code_unit) || IsLowSurrogate(prev_code_unit) ||\n          text.at(std::min(std::max(0, position), length - 1)) ==\n              TextUtils::kZWJUtf16 ||\n          IsUnicodeDirectionality(prev_code_unit)) {\n        return true;\n      }\n    } else if (position < length) {\n      size_t next_code_unit = text.at(position);\n      if (IsHighSurrogate(next_code_unit) || IsLowSurrogate(next_code_unit) ||\n          text.at(std::min(std::max(0, position), length - 1)) ==\n              TextUtils::kZWJUtf16 ||\n          IsUnicodeDirectionality(next_code_unit)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  static bool IsNewLine(size_t code_point) {\n    // 0x000A Line Feed | 0x0085 New Line |0x000B Form Feed | 0x000C Vertical\n    // Feed | 0x2028 Line Separator\n    if (code_point == 0x000A || code_point == 0x0085 || code_point == 0x000B ||\n        code_point == 0x000C || code_point == 0x2028 || code_point == 0x2029) {\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  // Unicode value for a zero width joiner character.\n  static constexpr uint32_t kZWJUtf16 = 0x200d;\n  static const char16_t kZeroWidthJoinerCharacter = u'\\u200D';\n  static const char16_t kZeroWidthSpaceCharacter = u'\\u200B';\n  static const char16_t kZeroWidthWordJoinerCharacter = u'\\u2060';\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_TEXT_UTILS_H_\n"
  },
  {
    "path": "clay/ui/component/editable/textarea_ng_view.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/textarea_ng_view.h\"\n\n#include <algorithm>\n#include <limits>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/ui/component/editable/editable_view.h\"\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n#include \"clay/ui/rendering/render_container.h\"\n#include \"clay/ui/shadow/editable_ng_shadow_node.h\"\n\nnamespace clay {\nnamespace {\nLYNX_UI_METHOD_BEGIN(TextAreaNGView) {\n  LYNX_UI_METHOD(TextAreaNGView, setValue);\n  LYNX_UI_METHOD(TextAreaNGView, getValue);\n  LYNX_UI_METHOD(TextAreaNGView, blur);\n  LYNX_UI_METHOD(TextAreaNGView, focus);\n  LYNX_UI_METHOD(TextAreaNGView, setSelectionRange);\n}\nLYNX_UI_METHOD_END(TextAreaNGView);\n}  // namespace\n\nTextAreaNGView::TextAreaNGView(int id, PageView* page_view)\n    : WithTypeInfo(id, \"textarea-ng\", std::make_unique<RenderContainer>(),\n                   page_view) {\n  editable_view_ = new EditableView(-1, id, \"editable\", page_view, true, false);\n  editable_view_->SetKeyboardAction(KeyboardAction::kMultiLine);\n  editable_view_->SetFocusable(true);\n  editable_view_->SetMaxLines(std::numeric_limits<uint32_t>::max());\n  editable_scroll_ = new ScrollView(-1, ScrollDirection::kVertical, page_view);\n  BaseView::AddChild(editable_scroll_);\n  editable_scroll_->BaseView::AddChild(editable_view_);\n  ResetGestureRecognizers();\n\n#if !defined(OS_ANDROID) && !defined(OS_IOS) && !defined(OS_HARMONY)\n  SetCursor({\"text\"});\n#endif\n}\n\nTextAreaNGView::~TextAreaNGView() = default;\n\nvoid TextAreaNGView::OnDestroy() {\n  DestroyAllChildren();\n  editable_view_ = nullptr;\n  editable_scroll_ = nullptr;\n}\n\nvoid TextAreaNGView::SetAttribute(const char* attr_c,\n                                  const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (editable_view_->MatchNGAttrSettings(kw)) {\n    editable_view_->SetAttribute(attr_c, value);\n  } else {\n    BaseView::SetAttribute(attr_c, value);\n  }\n}\n\nvoid TextAreaNGView::OnLayout(LayoutContext* context) {\n  editable_scroll_->SetWidth(ContentWidth());\n  editable_scroll_->SetHeight(ContentHeight());\n  editable_view_->SetWidth(ContentWidth());\n  BaseView::OnLayout(context);\n  editable_view_->SetHeight(\n      std::max(ContentHeight(), editable_view_->EstimateHeightWithMaxLines()));\n  auto node =\n      static_cast<EditableNGShadowNode*>(page_view()->GetShadowNodeById(id()));\n  if (node) {\n    node->SetTextHeight(editable_view_->GetParagraph()->GetHeight());\n  }\n  page_view()->PostUIMethodTask([weak_ptr = GetWeakPtr()] {\n    if (weak_ptr) {\n      auto editable_text_view = static_cast<TextAreaNGView*>(weak_ptr.get());\n      editable_text_view->ScheduleCaretOnScreen();\n    }\n  });\n}\n\nvoid TextAreaNGView::Measure(const MeasureConstraint& constraint,\n                             MeasureResult& result) {\n  editable_view_->Measure(constraint, result);\n}\n\nvoid TextAreaNGView::SetBound(float left, float top, float width,\n                              float height) {\n  BaseView::SetBound(left, top, width, height);\n  editable_scroll_->SetBound(0, 0, ContentWidth(), ContentHeight());\n  editable_view_->SetHeight(\n      std::max(ContentHeight(), editable_view_->EstimateHeightWithMaxLines()));\n  editable_view_->SetWidth(ContentWidth());\n}\n\nvoid TextAreaNGView::ScheduleCaretOnScreen() {\n  FloatRect wrapped = editable_view_->ComputeCaretRect();\n  FloatRect current = {0, 0, ContentWidth(), ContentHeight()};\n  FloatPoint scroll_offset = editable_scroll_->GetScrollOffset();\n  current.MoveBy(scroll_offset);\n  FloatPoint offset;\n  offset.MoveBy(scroll_offset);\n  if (wrapped.y() < current.y()) {\n    offset.Move(0.f, wrapped.y() - current.y());\n  } else if (wrapped.MaxY() > current.MaxY()) {\n    offset.Move(0.f, wrapped.MaxY() - current.MaxY());\n  }\n  editable_scroll_->ScrollTo(false, offset.y());\n}\n\nvoid TextAreaNGView::ResetGestureRecognizers() {\n  RemoveGestureRecognizer(tap_recognizer_);\n  std::unique_ptr<TapGestureRecognizer> tap_recognizer =\n      std::make_unique<TapGestureRecognizer>(page_view()->gesture_manager());\n  tap_recognizer_ = tap_recognizer.get();\n  tap_recognizer->SetTapUpCallback(\n      [this](auto&& PH1) { OnGestureTap(std::forward<decltype(PH1)>(PH1)); });\n  AddGestureRecognizer(std::move(tap_recognizer));\n}\n\nvoid TextAreaNGView::OnGestureTap(const PointerEvent& pointer) {\n  editable_view_->OnGestureTap(pointer);\n}\n\nvoid TextAreaNGView::setValue(const LynxModuleValues& args,\n                              const LynxUIMethodCallback& callback) {\n  editable_view_->setValue(args, callback);\n}\n\nvoid TextAreaNGView::blur(const LynxModuleValues& args,\n                          const LynxUIMethodCallback& callback) {\n  editable_view_->blur(args, callback);\n}\n\nvoid TextAreaNGView::focus(const LynxModuleValues& args,\n                           const LynxUIMethodCallback& callback) {\n  editable_view_->focus(args, callback);\n}\n\nvoid TextAreaNGView::setSelectionRange(const LynxModuleValues& args,\n                                       const LynxUIMethodCallback& callback) {\n  editable_view_->setSelectionRange(args, callback);\n}\n\nvoid TextAreaNGView::getValue(const LynxModuleValues& args,\n                              const LynxUIMethodCallback& callback) {\n  editable_view_->getValue(args, callback);\n}\n\n};  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/textarea_ng_view.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_TEXTAREA_NG_VIEW_H_\n#define CLAY_UI_COMPONENT_EDITABLE_TEXTAREA_NG_VIEW_H_\n\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/editable/editable_view.h\"\n#include \"clay/ui/component/measurable.h\"\n#include \"clay/ui/component/scroll_view.h\"\n\nnamespace clay {\n\nclass TextAreaNGView : public WithTypeInfo<TextAreaNGView, BaseView>,\n                       public Measurable {\n public:\n  TextAreaNGView(int id, PageView* page_view);\n  ~TextAreaNGView() override;\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  bool IsLayoutRootCandidate() const override { return true; }\n  void OnLayout(LayoutContext* context) override;\n  void Measure(const MeasureConstraint& constraint,\n               MeasureResult& result) override;\n  void SetBound(float left, float top, float width, float height) override;\n\n  void ScheduleCaretOnScreen();\n\n  void ResetGestureRecognizers();\n\n  void OnGestureTap(const PointerEvent& pointer);\n\n  txt::Paragraph* GetParagraph() { return editable_view_->GetParagraph(); }\n\n  // Lynx module UI method\n#define UI_METHOD_LIST_DECLARATION(V) \\\n  V(setValue)                         \\\n  V(blur)                             \\\n  V(focus)                            \\\n  V(setSelectionRange)                \\\n  V(getValue)\n  UI_METHOD_LIST_DECLARATION(UI_METHOD_DEF);\n#undef UI_METHOD_LIST_DECLARATION\n\n private:\n  FRIEND_TEST(TextAreaNGViewTest, scroll);\n  void OnDestroy() override;\n\n  EditableView* editable_view_ = nullptr;\n  ScrollView* editable_scroll_ = nullptr;\n  GestureRecognizer* tap_recognizer_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_TEXTAREA_NG_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/editable/textarea_ng_view_unittests.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/textarea_ng_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_types.h\"\n#include \"clay/ui/lynx_module/types.h\"\n#include \"clay/ui/testing/ui_test.h\"\n#include \"gtest/gtest.h\"\n\nnamespace clay {\n\nclass TextAreaNGViewTest : public UITest {\n  void UISetUp() override {}\n};\n\nTEST_F_UI(TextAreaNGViewTest, scroll) {\n  TextAreaNGView *text_area = new TextAreaNGView(-1, page_.get());\n  page_->AddChild(text_area);\n  const char *a_long_cstr =\n      \"hello world hello world hello world hello world\"\n      \"hello world hello world hello world hello world\"\n      \"hello world hello world hello world hello world\"\n      \"hello world hello world hello world hello world\";\n  LynxModuleValues params;\n  params.names.emplace_back(\"value\");\n  params.values.emplace_back(a_long_cstr);\n  text_area->setValue(params, [](LynxUIMethodResult code, clay::Value data) {});\n  text_area->SetBound(0, 0, 30, 200);\n  Layout();\n  DispatchDragEvent({20, 100}, {22, 0});\n  EXPECT_GT(text_area->editable_scroll_->TotalScrollOffset().y(), 0);\n  // because of the existence of touch_slop\n  EXPECT_LT(text_area->editable_scroll_->TotalScrollOffset().y(), 100);\n}\n\n};  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/textarea_view.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/editable/textarea_view.h\"\n\n#include <algorithm>\n#include <string>\n#include <utility>\n\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/editable/input_view.h\"\n#include \"clay/ui/rendering/render_container.h\"\n#include \"clay/ui/shadow/editable_shadow_node.h\"\n\nnamespace clay {\nnamespace {\nLYNX_UI_METHOD_BEGIN(TextAreaView) {\n  LYNX_UI_METHOD(TextAreaView, setValue);\n  LYNX_UI_METHOD(TextAreaView, addText);\n  LYNX_UI_METHOD(TextAreaView, sendDelEvent);\n  LYNX_UI_METHOD(TextAreaView, beginEdit);\n  LYNX_UI_METHOD(TextAreaView, quitEdit);\n  LYNX_UI_METHOD(TextAreaView, getValue);\n  LYNX_UI_METHOD(TextAreaView, blur);\n  LYNX_UI_METHOD(TextAreaView, controlKeyBoard);\n  LYNX_UI_METHOD(TextAreaView, focus);\n  LYNX_UI_METHOD(TextAreaView, setInputFilter);\n  LYNX_UI_METHOD(TextAreaView, select);\n  LYNX_UI_METHOD(TextAreaView, setSelectionRange);\n}\nLYNX_UI_METHOD_END(TextAreaView);\n}  // namespace\n\nTextAreaView::TextAreaView(int id, PageView* page_view)\n    : WithTypeInfo(id, \"textarea\", std::make_unique<RenderContainer>(),\n                   page_view) {\n  editable_view_ = new InputView(-1, id, page_view, true, false);\n  editable_view_->SetKeyboardAction(KeyboardAction::kMultiLine);\n  editable_view_->SetMaxLines(std::numeric_limits<uint32_t>::max());\n  editable_scroll_ = new ScrollView(-1, ScrollDirection::kVertical, page_view);\n  BaseView::AddChild(editable_scroll_);\n  editable_scroll_->BaseView::AddChild(editable_view_);\n  ResetGestureRecognizers();\n\n#if defined(OS_WIN) || defined(OS_MAC)\n  SetCursor({\"text\"});\n#endif\n}\n\nTextAreaView::~TextAreaView() = default;\n\nvoid TextAreaView::OnDestroy() {\n  DestroyAllChildren();\n  editable_view_ = nullptr;\n  editable_scroll_ = nullptr;\n}\n\nvoid TextAreaView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kMinHeight) {\n    std::string min_height;\n    attribute_utils::TryGetString(value, min_height);\n    min_height_ =\n        attribute_utils::ToPxWithDisplayMetrics(min_height, page_view());\n  } else if (kw == KeywordID::kMaxHeight) {\n    std::string max_height;\n    attribute_utils::TryGetString(value, max_height);\n    max_height_ =\n        attribute_utils::ToPxWithDisplayMetrics(max_height, page_view());\n  } else if (kw == KeywordID::kFocus) {\n    bool focus = attribute_utils::GetBool(value);\n    if (focus) {\n      // NOLINTNEXTLINE\n      page_view()->GetTaskRunner()->PostTask(\n          [this] { editable_view_->RequestFocus(); });\n    } else {\n      editable_view_->ClearFocus();\n    }\n  } else if (kw == KeywordID::kSendComposingInput) {\n    editable_view_->SetSendComposingInput(attribute_utils::GetBool(value));\n  } else if (editable_view_->MatchAttrSettings(kw)) {\n    editable_view_->SetAttribute(attr_c, value);\n  } else {\n    BaseView::SetAttribute(attr_c, value);\n  }\n}\n\nvoid TextAreaView::OnLayout(LayoutContext* context) {\n  editable_scroll_->SetX(PaddingLeft() + BorderLeft());\n  editable_scroll_->SetY(PaddingTop() + BorderTop());\n  editable_scroll_->SetWidth(ContentWidth());\n  editable_scroll_->SetHeight(ContentHeight());\n  editable_view_->SetWidth(ContentWidth());\n  BaseView::OnLayout(context);\n  editable_view_->SetHeight(\n      std::max(ContentHeight(), editable_view_->EstimateHeightWithMaxLines()));\n  bool auto_height =\n      min_height_ >= 0 && max_height_ >= 0 && max_height_ >= min_height_;\n  // In auto-height mode, the cursor should completely change the height before\n  // scrolling to the correct position. MarkDirty will only be measured in the\n  // next frame, so the cursor should also scroll in the next frame.\n  if (auto_height) {\n    // Currently clay cannot post tasks to the layout thread, so autoheight\n    // does not currently support opening asynchronous threads.\n    auto node =\n        static_cast<EditableShadowNode*>(page_view()->GetShadowNodeById(id()));\n    node->SetTextHeight(std::max(editable_view_->GetParagraph()->GetHeight(),\n                                 editable_view_->GetPlaceholderHeight()));\n    page_view()->PostUIMethodTask([weak_ptr = GetWeakPtr()] {\n      if (weak_ptr) {\n        auto editable_text_view = static_cast<TextAreaView*>(weak_ptr.get());\n        editable_text_view->ScheduleCaretOnScreen();\n      }\n    });\n  } else {\n    ScheduleCaretOnScreen();\n  }\n}\n\nvoid TextAreaView::Measure(const MeasureConstraint& constraint,\n                           MeasureResult& result) {\n  editable_view_->Measure(constraint, result);\n}\n\nvoid TextAreaView::SetBound(float left, float top, float width, float height) {\n  BaseView::SetBound(left, top, width, height);\n  editable_scroll_->SetBound(0, 0, ContentWidth(), ContentHeight());\n  editable_view_->SetHeight(\n      std::max(ContentHeight(), editable_view_->EstimateHeightWithMaxLines()));\n  editable_view_->SetWidth(ContentWidth());\n}\n\nvoid TextAreaView::ScheduleCaretOnScreen() {\n  FloatRect wrapped = editable_view_->ComputeCaretRect();\n  FloatRect current = {0, 0, ContentWidth(), ContentHeight()};\n  FloatPoint scroll_offset = editable_scroll_->GetScrollOffset();\n  current.MoveBy(scroll_offset);\n  FloatPoint offset;\n  offset.MoveBy(scroll_offset);\n  if (wrapped.y() < current.y()) {\n    offset.Move(0.f, wrapped.y() - current.y());\n  } else if (wrapped.MaxY() > current.MaxY()) {\n    offset.Move(0.f, wrapped.MaxY() - current.MaxY());\n  }\n  editable_scroll_->ScrollTo(false, offset.y());\n}\n\nvoid TextAreaView::ResetGestureRecognizers() {\n  RemoveGestureRecognizer(tap_recognizer_);\n  std::unique_ptr<TapGestureRecognizer> tap_recognizer =\n      std::make_unique<TapGestureRecognizer>(page_view()->gesture_manager());\n  tap_recognizer_ = tap_recognizer.get();\n  tap_recognizer->SetTapUpCallback(\n      [this](auto&& PH1) { OnGestureTap(std::forward<decltype(PH1)>(PH1)); });\n  AddGestureRecognizer(std::move(tap_recognizer));\n}\n\nvoid TextAreaView::OnGestureTap(const PointerEvent& pointer) {\n  editable_view_->OnGestureTap(pointer);\n}\n\nvoid TextAreaView::setValue(const LynxModuleValues& args,\n                            const LynxUIMethodCallback& callback) {\n  editable_view_->setValue(args, callback);\n}\n\nvoid TextAreaView::addText(const LynxModuleValues& args,\n                           const LynxUIMethodCallback& callback) {\n  editable_view_->addText(args, callback);\n}\n\nvoid TextAreaView::sendDelEvent(const LynxModuleValues& args,\n                                const LynxUIMethodCallback& callback) {\n  editable_view_->sendDelEvent(args, callback);\n}\n\nvoid TextAreaView::blur(const LynxModuleValues& args,\n                        const LynxUIMethodCallback& callback) {\n  editable_view_->blur(args, callback);\n}\n\nvoid TextAreaView::controlKeyBoard(const LynxModuleValues& args,\n                                   const LynxUIMethodCallback& callback) {\n  editable_view_->controlKeyBoard(args, callback);\n}\n\nvoid TextAreaView::focus(const LynxModuleValues& args,\n                         const LynxUIMethodCallback& callback) {\n  editable_view_->focus(args, callback);\n}\n\nvoid TextAreaView::setInputFilter(const LynxModuleValues& args,\n                                  const LynxUIMethodCallback& callback) {\n  editable_view_->setInputFilter(args, callback);\n}\n\nvoid TextAreaView::select(const LynxModuleValues& args,\n                          const LynxUIMethodCallback& callback) {\n  editable_view_->select(args, callback);\n}\n\nvoid TextAreaView::setSelectionRange(const LynxModuleValues& args,\n                                     const LynxUIMethodCallback& callback) {\n  editable_view_->setSelectionRange(args, callback);\n}\n\n// Added by clay\nvoid TextAreaView::beginEdit(const LynxModuleValues& args,\n                             const LynxUIMethodCallback& callback) {\n  editable_view_->beginEdit(args, callback);\n}\n\nvoid TextAreaView::quitEdit(const LynxModuleValues& args,\n                            const LynxUIMethodCallback& callback) {\n  editable_view_->quitEdit(args, callback);\n}\n\nvoid TextAreaView::getValue(const LynxModuleValues& args,\n                            const LynxUIMethodCallback& callback) {\n  editable_view_->getValue(args, callback);\n}\n};  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/editable/textarea_view.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EDITABLE_TEXTAREA_VIEW_H_\n#define CLAY_UI_COMPONENT_EDITABLE_TEXTAREA_VIEW_H_\n\n#include <limits>\n#include <memory>\n\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/editable/input_view.h\"\n#include \"clay/ui/component/measurable.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/scroll_wrapper.h\"\n\nnamespace clay {\n\nclass TextAreaView : public WithTypeInfo<TextAreaView, BaseView>,\n                     public Measurable {\n public:\n  TextAreaView(int id, PageView* page_view);\n  ~TextAreaView() override;\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  bool IsLayoutRootCandidate() const override { return true; }\n  void OnLayout(LayoutContext* context) override;\n  void Measure(const MeasureConstraint& constraint,\n               MeasureResult& result) override;\n  void SetBound(float left, float top, float width, float height) override;\n\n  void ScheduleCaretOnScreen();\n\n  void ResetGestureRecognizers();\n\n  void OnGestureTap(const PointerEvent& pointer);\n\n  txt::Paragraph* GetParagraph() { return editable_view_->GetParagraph(); }\n\n  // Lynx module UI method\n#define UI_METHOD_LIST_DECLARATION(V) \\\n  V(setValue)                         \\\n  V(addText)                          \\\n  V(sendDelEvent)                     \\\n  V(blur)                             \\\n  V(controlKeyBoard)                  \\\n  V(focus)                            \\\n  V(setInputFilter)                   \\\n  V(select)                           \\\n  V(setSelectionRange)                \\\n  V(beginEdit)                        \\\n  V(quitEdit)                         \\\n  V(getValue)\n  UI_METHOD_LIST_DECLARATION(UI_METHOD_DEF);\n#undef UI_METHOD_LIST_DECLARATION\n\n private:\n  void OnDestroy() override;\n\n  InputView* editable_view_ = nullptr;\n  ScrollView* editable_scroll_ = nullptr;\n  GestureRecognizer* tap_recognizer_ = nullptr;\n  float min_height_ = -1;\n  float max_height_ = -1;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EDITABLE_TEXTAREA_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/expose_manager/expose_observer.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/expose_manager/expose_observer.h\"\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/string/string_utils.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/value_utils.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/intersection_observer.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nnamespace {\nnamespace utils = attribute_utils;\nFloatRect BoundingRectWithScroll(BaseView* view) {\n  auto location = view->AbsoluteLocationWithScroll();\n  return FloatRect(location.x(), location.y(), view->Width(), view->Height());\n}\nfloat ParesLengthParam(const clay::Value& value, PageView* page_view) {\n  std::string length_str = utils::GetCString(value, \"\");\n  if (length_str.empty()) {\n    return 0;\n  }\n  return utils::ToPxWithDisplayMetrics(length_str, page_view);\n}\n\nfloat ParseLengthWithPercent(const attr::Length& value, PageView* page_view,\n                             float view_length) {\n  if (value.unit == attr::Unit::kNone || view_length <= 0) {\n    return 0;\n  }\n  return utils::ToPxWithDisplayMetrics(value, page_view, view_length);\n}\n\n}  // namespace\n\nnamespace detail_attr {\nconst char* kExposureScene = \"exposure-scene\";\nconst char* kExposureId = \"exposure-id\";\nconst char* kSign = \"sign\";\nconst char* kDataSet = \"dataset\";\nconst char* kUniqueId = \"unique-id\";\n// in order to compatable with old lynx versions\nconst char* kExposureSceneCompat = \"exposureScene\";\nconst char* kExposureIdCompat = \"exposureID\";\nconst char* kDataSetCompat = \"dataSet\";\n\n}  // namespace detail_attr\n\n/**\n * Detail data structure :\n *  {\n *      \"exposure-scene\": xxx,\n *      \"exposure-id\": xxx,\n *      \"dataset\": {...},\n *      \"unique-id\":xxx\n *      \"extra-data\": always null in lynx android, ignore here\n *      \"sign\": xxx,\n *      //.......\n *    }\n *  refer to:\n *  https://lynxjs.org/api/lynx-api/event/global-event#exposure\n */\nExposeObserver::ExposeObserver(IntersectionObserverManager* manager,\n                               const clay::Value::Map& map, BaseView* view)\n    : IntersectionObserver(manager, map, view) {}\n\nvoid ExposeObserver::StopExposure(bool send_event) {\n  if (send_event) {\n    CheckForIntersectionWithTarget();\n  }\n  expose_attrs_.exposure_stoped = true;\n  expose_attrs_.expose_state = kInit;\n}\n\nvoid ExposeObserver::ResumeExposure() {\n  expose_attrs_.exposure_stoped = false;\n  CheckForIntersectionWithTarget();\n}\n\nvoid ExposeObserver::NotifyAppearEvent(bool appear) {\n  if (attached_view_ && attached_view_->page_view()) {\n    const char* event_name = appear ? \"uiappear\" : \"uidisappear\";\n    clay::Value::Map params;\n    params[detail_attr::kSign] = clay::Value(attached_view_->GetCallbackId());\n    params[detail_attr::kExposureId] = clay::Value(expose_attrs_.exposure_id);\n    params[detail_attr::kExposureIdCompat] =\n        clay::Value(expose_attrs_.exposure_id);\n    params[detail_attr::kExposureScene] =\n        clay::Value(expose_attrs_.exposure_scene);\n    params[detail_attr::kExposureSceneCompat] =\n        clay::Value(expose_attrs_.exposure_scene);\n    params[detail_attr::kUniqueId] =\n        clay::Value(attached_view_->GetCallbackId());\n    params[detail_attr::kDataSet] =\n        CloneClayValue(attached_view_->GetDataSet());\n    params[detail_attr::kDataSetCompat] =\n        CloneClayValue(attached_view_->GetDataSet());\n    attached_view_->page_view()->SendCustomEvent(\n        attached_view_->GetCallbackId(), event_name, std::move(params));\n  }\n}\n\nvoid ExposeObserver::NotifyGlobalEvent(bool appear) {\n  if (!attached_view_ || !attached_view_->page_view()) {\n    return;\n  }\n  auto params = std::make_unique<clay::Value::Map>();\n  params->emplace(detail_attr::kExposureId,\n                  clay::Value(expose_attrs_.exposure_id));\n  params->emplace(detail_attr::kExposureIdCompat,\n                  clay::Value(expose_attrs_.exposure_id));\n  params->emplace(detail_attr::kExposureScene,\n                  clay::Value(expose_attrs_.exposure_scene));\n  params->emplace(detail_attr::kExposureSceneCompat,\n                  clay::Value(expose_attrs_.exposure_scene));\n  params->emplace(detail_attr::kSign,\n                  clay::Value(attached_view_->GetCallbackId()));\n  params->emplace(detail_attr::kUniqueId,\n                  clay::Value(attached_view_->GetCallbackId()));\n  attached_view_->page_view()->AddGlobalExposureEvent(appear, std::move(params),\n                                                      attached_view_);\n}\n\nvoid ExposeObserver::NotifyTarget() {\n  double ratio = 0;\n  auto map = now_entry_->ToMap();\n  const auto it = map.find(\"intersectionRatio\");\n  if (it != map.end()) {\n    attribute_utils::TryGetNum(it->second, ratio, 0);\n  }\n  bool show = ratio > 0;\n  if (expose_attrs_.exposure_should_notify_appear_ ||\n      expose_attrs_.exposure_should_notify_disappear_) {\n    NotifyAppearEvent(show);\n  };\n  if (!expose_attrs_.exposure_id.empty()) {\n    NotifyGlobalEvent(show);\n  } else {\n    FML_DLOG(ERROR) << \" fail notify by exposure id null, view \"\n                    << attached_view_\n                    << \"view id: \" << attached_view_->GetIdSelector();\n  }\n  expose_attrs_.expose_state =\n      show ? ExposureState::kExposed : ExposureState::kDisExposed;\n}\n\nvoid ExposeObserver::CheckForIntersectionWithTarget() {\n  FML_DCHECK(manager_ && attached_view_);\n  if (!available_ || !root_ || expose_attrs_.exposure_stoped ||\n      (expose_attrs_.exposure_id.empty() &&\n       !expose_attrs_.exposure_should_notify_appear_ &&\n       !expose_attrs_.exposure_should_notify_disappear_)) {\n    return;\n  }\n  now_entry_ = std::make_unique<IntersectionObserverEntry>();\n\n  FloatRect target_rect =\n      is_detaching_ ? FloatRect() : BoundingRectWithScroll(attached_view_);\n  FloatRect root_rect = BoundingRectWithScroll(root_);\n  if (!is_detaching_ &&\n      (expose_attrs_.exposure_ui_margin_enabled == kUndefined\n           ? manager_->GetExposureUIMarginEnabled()\n           : expose_attrs_.exposure_ui_margin_enabled == kEnable)) {\n    auto page_view = this->attached_view_->page_view();\n    float top = ParseLengthWithPercent(expose_attrs_.exposure_ui_margin_top,\n                                       page_view, target_rect.height());\n    float right = ParseLengthWithPercent(expose_attrs_.exposure_ui_margin_right,\n                                         page_view, target_rect.width());\n    float bottom =\n        ParseLengthWithPercent(expose_attrs_.exposure_ui_margin_bottom,\n                               page_view, target_rect.height());\n    float left = ParseLengthWithPercent(expose_attrs_.exposure_ui_margin_left,\n                                        page_view, target_rect.width());\n    target_rect.Expand(top, right, bottom, left);\n  }\n  root_rect.Expand(expose_attrs_.exposure_screen_margin_top,\n                   expose_attrs_.exposure_screen_margin_right,\n                   expose_attrs_.exposure_screen_margin_bottom,\n                   expose_attrs_.exposure_screen_margin_right);\n  now_entry_->bounding_client_rect_ = target_rect;\n  now_entry_->relative_rect_ = root_rect;\n  now_entry_->target_view_ = attached_view_;\n  now_entry_->root_ = root_;\n  now_entry_->ComputeIntersectionRect(exposure_ui_clip_enabled_);\n  now_entry_->time_ = 0;  // not support time\n  now_entry_->relative_to_id_ = attached_view_->GetIdSelector();\n  now_entry_->ComputeIntersectionRatio();\n\n  bool need_notify = false;\n  switch (expose_attrs_.expose_state) {\n    case kInit:\n    case kDisExposed:\n      need_notify = now_entry_->intersection_ratio_ > 0;\n      break;\n    case kExposed:\n      need_notify = now_entry_->intersection_ratio_ <= 0;\n  }\n  if (need_notify) {\n    NotifyTarget();\n  }\n}\n\nbool ExposeObserver::UpdateExposeData(const char* attr,\n                                      const clay::Value& value) {\n  if (!this->attached_view_ || !this->attached_view_->page_view()) {\n    return false;\n  }\n  auto page_view = this->attached_view_->page_view();\n  auto kw = GetKeywordID(attr);\n  if (KeywordID::kExposureId == kw) {\n    auto id = utils::GetCString(value, \"\");\n    if (!id.empty() && id != expose_attrs_.exposure_id) {\n      // send old disexposure first when changed\n      if (!expose_attrs_.exposure_id.empty()) {\n        NotifyGlobalEvent(false);\n      }\n      // treat as new added on next frame\n      if (attached_view_ && attached_view_->page_view()) {\n        expose_attrs_.expose_state = kInit;\n        attached_view_->page_view()->RequestPaint();\n      }\n    }\n    expose_attrs_.exposure_id = id;\n    return true;\n  } else if (KeywordID::kExposureScene == kw) {\n    auto exposure_scene = utils::GetCString(value, \"\");\n    if (exposure_scene != expose_attrs_.exposure_scene) {\n      // send old disexposure first when changed\n      if (!expose_attrs_.exposure_id.empty()) {\n        NotifyGlobalEvent(false);\n      }\n      // treat as new added on next frame\n      if (attached_view_ && attached_view_->page_view()) {\n        expose_attrs_.expose_state = kInit;\n        attached_view_->page_view()->RequestPaint();\n      }\n    }\n    expose_attrs_.exposure_scene = exposure_scene;\n    return true;\n  } else if (KeywordID::kExposureArea == kw) {\n    std::string area_str = utils::GetCString(value, \"\");\n    std::string input_copy = lynx::base::TrimString(area_str);\n    if (!input_copy.empty()) {\n      char* endptr = nullptr;\n      errno = 0;\n      [[maybe_unused]] double area_double = strtod(input_copy.c_str(), &endptr);\n      if (endptr) {\n        std::string_view str_endptr(endptr, strlen(endptr));\n        if ((input_copy.c_str() + input_copy.size() != endptr) &&\n            (str_endptr == \"%\")) {\n          expose_attrs_.exposure_area = area_double / 100;\n          thresholds_.push_back(expose_attrs_.exposure_area);\n        }\n      }\n    }\n    return true;\n  } else if (KeywordID::kEnableExposureUiMargin == kw) {\n    if (utils::GetBool(value, false)) {\n      expose_attrs_.exposure_ui_margin_enabled =\n          NodeExposureUIMarginEnabled::kEnable;\n    } else {\n      expose_attrs_.exposure_ui_margin_enabled =\n          NodeExposureUIMarginEnabled::kDisable;\n    }\n    return true;\n  } else if (KeywordID::kEnableExposureUiClip == kw) {\n    if (utils::GetBool(value, false)) {\n      exposure_ui_clip_enabled_ = true;\n    } else {\n      exposure_ui_clip_enabled_ = false;\n    }\n    return true;\n  } else if (KeywordID::kExposureScreenMarginLeft == kw) {\n    expose_attrs_.exposure_screen_margin_left =\n        ParesLengthParam(value, page_view);\n    return true;\n  } else if (KeywordID::kExposureScreenMarginRight == kw) {\n    expose_attrs_.exposure_screen_margin_right =\n        ParesLengthParam(value, page_view);\n    return true;\n  } else if (KeywordID::kExposureScreenMarginTop == kw) {\n    expose_attrs_.exposure_screen_margin_top =\n        ParesLengthParam(value, page_view);\n    return true;\n  } else if (KeywordID::kExposureScreenMarginBottom == kw) {\n    expose_attrs_.exposure_screen_margin_bottom =\n        ParesLengthParam(value, page_view);\n    return true;\n  } else if (KeywordID::kExposureUiMarginLeft == kw) {\n    if (!attr::TryGetLength(value, expose_attrs_.exposure_ui_margin_left)) {\n      expose_attrs_.exposure_ui_margin_left = {0, attribute_utils::kNone};\n    }\n    return true;\n  } else if (KeywordID::kExposureUiMarginRight == kw) {\n    if (!attr::TryGetLength(value, expose_attrs_.exposure_ui_margin_right)) {\n      expose_attrs_.exposure_ui_margin_right = {0, attribute_utils::kNone};\n    }\n    return true;\n  } else if (KeywordID::kExposureUiMarginTop == kw) {\n    if (!attr::TryGetLength(value, expose_attrs_.exposure_ui_margin_top)) {\n      expose_attrs_.exposure_ui_margin_top = {0, attribute_utils::kNone};\n    }\n    return true;\n  } else if (KeywordID::kExposureUiMarginBottom == kw) {\n    if (!attr::TryGetLength(value, expose_attrs_.exposure_ui_margin_bottom)) {\n      expose_attrs_.exposure_ui_margin_bottom = {0, attribute_utils::kNone};\n    }\n    return true;\n  } else if (KeywordID::kUiappear == kw) {\n    expose_attrs_.exposure_should_notify_appear_ = true;\n    return true;\n  } else if (KeywordID::kUidisappear == kw) {\n    expose_attrs_.exposure_should_notify_disappear_ = true;\n    return true;\n  } else {\n    return false;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/expose_manager/expose_observer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_EXPOSE_MANAGER_EXPOSE_OBSERVER_H_\n#define CLAY_UI_COMPONENT_EXPOSE_MANAGER_EXPOSE_OBSERVER_H_\n\n#include <functional>\n#include <string>\n\n#include \"clay/public/value.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/component/intersection_observer.h\"\n\nnamespace clay {\n\nenum NodeExposureUIMarginEnabled { kUndefined = 0, kEnable = 1, kDisable = 2 };\nenum ExposureState { kInit = 0, kExposed = 1, kDisExposed = 2 };\nnamespace attr = attribute_utils;\n\nstruct ExposeAttrs {\n  ExposureState expose_state = ExposureState::kInit;\n\n  bool exposure_stoped = false;\n  bool exposure_should_notify_appear_ = false;\n  bool exposure_should_notify_disappear_ = false;\n  // higher priority than pageConfig.exposure_ui_margin_enabled\n  NodeExposureUIMarginEnabled exposure_ui_margin_enabled = kUndefined;\n  float exposure_screen_margin_left = 0;\n  float exposure_screen_margin_right = 0;\n  float exposure_screen_margin_top = 0;\n  float exposure_screen_margin_bottom = 0;\n\n  attr::Length exposure_ui_margin_left = {0.0, attr::Unit::kNone};\n  attr::Length exposure_ui_margin_right = {0.0, attr::Unit::kNone};\n  attr::Length exposure_ui_margin_top = {0.0, attr::Unit::kNone};\n  attr::Length exposure_ui_margin_bottom = {0.0, attr::Unit::kNone};\n  float exposure_area = 0;\n  std::string exposure_scene = \"\";\n  std::string exposure_id = \"\";\n};\n\n/**\n *\n *   PageConfig.enableCheckExposureOptimize not implemented Since it is only for\n *iOS optimize.\n *  PageConfig.enableExposureWhenLayout not implemented Since it used to fix\n *android platform issues.\n **/\nclass ExposeObserver : public IntersectionObserver {\n public:\n  ExposeObserver(IntersectionObserverManager* manager,\n                 const clay::Value::Map& map, BaseView* view);\n\n  ~ExposeObserver() override = default;\n\n  bool UpdateExposeData(const char* attr, const clay::Value& value);\n\n  void StopExposure(bool send_event);\n\n  void ResumeExposure();\n\n  void CheckForIntersectionWithTarget() override;\n\n protected:\n  bool IsOfType(ObserverType type) const override {\n    return type == IntersectionObserver::kExposeObserver;\n  };\n\n private:\n  ExposeAttrs expose_attrs_ = {};\n  void NotifyAppearEvent(bool appear);\n  void NotifyGlobalEvent(bool appear);\n  void AssembleDetailData();\n  void NotifyTarget() override;\n  std::function<void(clay::Value::Map)> custom_event_callback_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_EXPOSE_MANAGER_EXPOSE_OBSERVER_H_\n"
  },
  {
    "path": "clay/ui/component/focus/focus_isolate.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/focus/focus_isolate.h\"\n\n#include <string>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n\nnamespace clay {\n\nFocusManager::TraversalResult FocusIsolate::OnTraversalOnScope(\n    FocusManager::Direction direction,\n    FocusManager::TraversalType traversal_type) {\n  FocusManager::TraversalResult res =\n      FocusNode::OnTraversalOnScope(direction, traversal_type);\n  if (!res.succeed) {\n    // Try leave, notify.\n    OnFocusEscaping(direction);\n  }\n\n  if (!allow_escape_) {\n    res.succeed = true;\n  }\n\n  return res;\n}\n\nvoid FocusIsolate::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kAllowEscape) {\n    allow_escape_ = attribute_utils::GetBool(value, false);\n  } else if (kw == KeywordID::kAutoSaveLastChild) {\n    save_last_focus_child_ = attribute_utils::GetBool(value, true);\n  } else if (kw == KeywordID::kPreferredFocusChild) {\n    preferred_child_ = attribute_utils::GetCString(value);\n  } else {\n    BaseView::SetAttribute(attr_c, value);\n  }\n}\n\nvoid FocusIsolate::OnFocusEscaping(FocusManager::Direction direction) {\n  auto* focus_node = GetFocusManager()->GetLeafFocusedNode();\n  FML_DCHECK(focus_node);\n  std::stringstream focus_index;\n  std::string id_selector;\n  if (focus_node) {\n    focus_index << focus_node->GetFocusIndex(Axis::kX) << \",\"\n                << focus_node->GetFocusIndex(Axis::kY);\n    id_selector = static_cast<BaseView*>(focus_node)->GetIdSelector();\n  }\n  // Keep string alive to transfer c_str.\n  auto index_str = focus_index.str();\n  page_view()->SendEvent(id(), event_attr::kEventFocusEscape,\n                         {\"direction\", \"focusIndex\", \"fromId\"},\n                         static_cast<int>(direction), index_str.c_str(),\n                         id_selector.c_str());\n}\n\nvoid FocusIsolate::OnFocusEntering(FocusManager::Direction direction) {\n  FocusNode* preferred_view = nullptr;\n  if (save_last_focus_child_ && last_focused_view_) {\n    preferred_view = last_focused_view_;\n    last_focused_view_ = nullptr;\n    FML_DLOG(INFO) << \"Use last focused child: \" << preferred_view;\n  } else if (!preferred_child_.empty()) {\n    preferred_view = ViewContext::FindViewByIdSelector(preferred_child_, this);\n    FML_DLOG(INFO) << \"Use preferred focus child(\" << preferred_child_\n                   << \"): \" << preferred_view;\n  }\n  if (preferred_view) {\n    preferred_view->RequestFocus();\n  }\n\n  page_view()->SendEvent(id(), event_attr::kEventFocusEnter, {\"direction\"},\n                         static_cast<int>(direction));\n}\n\nvoid FocusIsolate::OnFocusNodeDestructed(FocusNode* node) {\n  if (node == last_focused_view_) {\n    last_focused_view_ = nullptr;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/focus/focus_isolate.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_FOCUS_FOCUS_ISOLATE_H_\n#define CLAY_UI_COMPONENT_FOCUS_FOCUS_ISOLATE_H_\n\n#include <string>\n\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/event/focus_manager.h\"\n\nnamespace clay {\n\nclass PageView;\n\nclass FocusIsolate : public WithTypeInfo<FocusIsolate, View>,\n                     public FocusNode::LifecycleListener {\n public:\n  FocusIsolate(int id, PageView* page_view) : WithTypeInfo(id, page_view) {\n    SetIsFocusScope();\n    SetIsFocusFence();\n  }\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n\n  FocusManager::TraversalResult OnTraversalOnScope(\n      FocusManager::Direction direction,\n      FocusManager::TraversalType traversal_type) override;\n\n  void OnFocusEscaping(FocusManager::Direction direction) override;\n  void OnFocusEntering(FocusManager::Direction direction) override;\n\n  void SetHasDefaultFocusRing(bool has_focus_ring) override {}\n\n  void UpdateLastFocusChild(FocusNode* last_focused_view) {\n    if (last_focused_view_) {\n      last_focused_view_->RemoveLifecycleListener(this);\n    }\n    last_focused_view_ = last_focused_view;\n    if (last_focused_view_) {\n      last_focused_view_->AddLifecycleListener(this);\n    }\n  }\n\n  bool allow_escape() override { return allow_escape_; }\n\n protected:\n  void OnFocusNodeDestructed(FocusNode*) override;\n\n private:\n  bool allow_escape_ = false;\n  bool save_last_focus_child_ = true;\n  // There's risk that a FocusNode has been destroyed and another FocusNode\n  // created using same pointer value.\n  FocusNode* last_focused_view_ = nullptr;\n  std::string preferred_child_;\n\n  FRIEND_TEST(FocusIsolateTest, AllowEscape);\n  FRIEND_TEST(FocusIsolateTest, NestedScope);\n  FRIEND_TEST(FocusIsolateTest, AutoRestoreFocusAcrossScope);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_FOCUS_FOCUS_ISOLATE_H_\n"
  },
  {
    "path": "clay/ui/component/focus_event_handler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_FOCUS_EVENT_HANDLER_H_\n#define CLAY_UI_COMPONENT_FOCUS_EVENT_HANDLER_H_\n\n#include <functional>\n\nnamespace clay {\n\n// Decoupling from EventHandler.\nclass FocusEventHandler {\n public:\n  FocusEventHandler() = default;\n  virtual ~FocusEventHandler() = default;\n\n  // TODO(hupeng): Bridge to focus.\n  virtual void OnFocusChanged(bool focused) {\n    if (focus_change_callback_) {\n      focus_change_callback_(focused);\n    }\n  }\n\n  void SetFocusChangeCallback(std::function<void(bool)> callback) {\n    focus_change_callback_ = callback;\n  }\n\n protected:\n  std::function<void(bool)> focus_change_callback_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_FOCUS_EVENT_HANDLER_H_\n"
  },
  {
    "path": "clay/ui/component/focus_node.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/focus_node.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nFocusNode::FocusNode() = default;\nFocusNode::~FocusNode() {\n  if (life_listeners_) {\n    for (auto* listener : *life_listeners_) {\n      listener->OnFocusNodeDestructed(this);\n    }\n  }\n}\n\nFocusManager* FocusNode::GetFocusManager() {\n  if (IsFocusScope()) {\n    return focus_manager_.get();\n  }\n  return GetParentFocusManager();\n}\n\nvoid FocusNode::SetFocusable(bool focusable) {\n  if (pending_focusable_ == focusable) {\n    return;\n  }\n  pending_focusable_ = focusable;\n  SetFocusableInternal(pending_focusable_);\n}\n\nvoid FocusNode::RequestFocus(bool is_leaf) {\n  if (!focusable_ || is_focused_) {\n    if (!focusable_ && pending_focusable_) {\n      pending_request_focus_ = true;\n    }\n    return;\n  }\n  is_focused_ = true;\n  SetHasDefaultFocusRing(is_focused_);\n  FocusManager* manager = GetParentFocusManager();\n  if (manager) {\n    manager->RequestFocus(this);\n  }\n  if (IsFocusScope()) {\n    focus_manager_->SetFocusable(true);\n  }\n  if (!restoring_focus_) {\n    FocusHasChanged(is_focused_, is_leaf);\n  }\n}\n\nvoid FocusNode::ClearFocus() {\n  if (!focusable_ || !is_focused_) {\n    return;\n  }\n  is_focused_ = false;\n  SetHasDefaultFocusRing(is_focused_);\n  FocusManager* manager = GetParentFocusManager();\n  if (manager) {\n    manager->ClearFocus(this);\n  }\n  if (IsFocusScope()) {\n    focus_manager_->SetFocusable(false);\n  }\n  FocusHasChanged(is_focused_, true);\n}\n\nvoid FocusNode::SetIsFocusScope() {\n  focus_manager_ = std::make_unique<FocusManager>(this);\n}\n\nvoid FocusNode::SetIsFocusFence() {\n  FML_DCHECK(IsFocusScope());\n  is_fence_ = true;\n}\n\nvoid FocusNode::UpdateFocusRect() {\n  focus_rect_ = CalcFocusRect();\n  // focus-isolate use self rect rather than child's.\n  if (IsFocusScope() && IsFocused() && GetFocusManager()->HasFocusedNode() &&\n      !IsFocusFence()) {\n    FloatRect rect = GetFocusManager()->GetFocusedNodeRect();\n    FloatSize offset = GetThicknessOffset();\n    rect.Move(offset.width(), offset.height());\n    rect.Move(focus_rect_.x(), focus_rect_.y());\n    focus_rect_ = rect;\n  }\n}\n\nvoid FocusNode::OnFocusAttach() {\n  SetFocusableInternal(pending_focusable_);\n  restoring_focus_ = false;\n}\n\nvoid FocusNode::OnFocusDetach() {\n  restoring_focus_ = is_focused_ || pending_request_focus_;\n  SetFocusableInternal(false);\n  pending_request_focus_ = restoring_focus_;\n}\n\nint FocusNode::GetFocusIndex(Axis axis) const {\n  switch (axis) {\n    case Axis::kX:\n      return focus_index_.x();\n    case Axis::kY:\n      return focus_index_.y();\n    default:\n      FML_DCHECK(false);\n      return 0;\n  }\n}\n\nbool FocusNode::CanAcceptFocusOnAxis(Axis axis) const {\n  // TODO(yulitao): Skip invisible nodes.\n  switch (axis) {\n    case Axis::kX:\n      return focus_index_.x() >= 0;\n    case Axis::kY:\n      return focus_index_.y() >= 0;\n    default:\n      FML_DCHECK(false);\n      return true;\n  }\n}\n\nvoid FocusNode::SetFocusableInternal(bool focusable) {\n  if (focusable == focusable_) {\n    return;\n  }\n  FocusManager* manager = GetParentFocusManager();\n  if (!manager) {\n    return;\n  }\n  if (focusable) {\n    manager->RegisterFocusableView(this);\n  } else {\n    ClearFocus();\n    manager->UnregisterFocusableView(this);\n    if (life_listeners_) {\n      auto life_listeners_copy = *life_listeners_;\n      for (auto* listener : life_listeners_copy) {\n        listener->OnFocusNodeUnregistered(this);\n      }\n    }\n  }\n  focusable_ = focusable;\n  if (focusable_ && pending_request_focus_) {\n    pending_request_focus_ = false;\n    // If focus has already set, then no need to restore.\n    // And list items may reuse, it also may cause some problem.\n    if (!GetFocusManager()->GetRootFocusManager()->GetLeafFocusedNode()) {\n      RequestFocus();\n    }\n  }\n}\n\nFocusManager::TraversalResult FocusNode::OnTraversalOnScope(\n    FocusManager::Direction direction,\n    FocusManager::TraversalType traversal_type) {\n  if (!IsFocusScope()) {\n    return FocusManager::TraversalResult();\n  }\n  FocusManager::TraversalResult res =\n      GetFocusManager()->DoTraversalInternal(direction, traversal_type);\n  UpdateFocusRing();\n  return res;\n}\n\nvoid FocusNode::UpdateFocusRing() {\n  if (focus_manager_->HasFocusedNode()) {\n    SetHasDefaultFocusRing(false);\n  } else if (is_focused_) {\n    SetHasDefaultFocusRing(true);\n  }\n}\n\nconst std::string& FocusNode::GetNextFocusId(\n    FocusManager::Direction direction) const {\n  switch (direction) {\n    case FocusManager::Direction::kUp:\n      return next_focus_up_;\n    case FocusManager::Direction::kDown:\n      return next_focus_down_;\n    case FocusManager::Direction::kLeft:\n      return next_focus_left_;\n    default:\n      FML_DCHECK(false);\n    case FocusManager::Direction::kRight:\n      return next_focus_right_;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/focus_node.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_FOCUS_NODE_H_\n#define CLAY_UI_COMPONENT_FOCUS_NODE_H_\n\n#include <memory>\n#include <optional>\n#include <set>\n#include <string>\n#include <utility>\n\n#include \"clay/gfx/geometry/axis.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/point.h\"\n#include \"clay/ui/event/focus_manager.h\"\n\n/*\n * The front end wants to maintain focus by itself. Make all views not focusable\n * by default, so that arrow key events will not be handled by FocusManager to\n * change focus state, instead, reported to the front end.\n */\n#define FORCE_TEXTVIEW_FOCUSABLE 0\n#define FORCE_IMAGEVIEW_FOCUSABLE 0\n\nnamespace clay {\nclass FocusManager;\nclass KeyEvent;\n\n// Defines when focus is traversed onto self, how to deal with.\n// Doesn't affect behavior when RequestFocus.\nenum class FocusBehavior {\n  // Default behavior. Just accept focus for self.\n  kFocus = 0,\n  // Pass focus to the first child. Like \"step into\" when debug.\n  // Effective only if is a focus scope.\n  // This usually used by a list.\n  kStepIntoChild,\n  // Skip to next sibling. Usually used when node is invisible.\n  kSkip,\n};\n\nclass FocusNode {\n public:\n  FocusNode();\n  virtual ~FocusNode();\n\n  class LifecycleListener {\n   public:\n    virtual void OnFocusNodeUnregistered(FocusNode*) {}\n    virtual void OnFocusNodeDestructed(FocusNode*) {}\n  };\n\n  virtual FocusManager* GetFocusManager();\n  FocusManager* GetRootFocusManager();\n\n  virtual void SetFocusable(bool focusable);\n  bool GetFocusable() const { return pending_focusable_; }\n\n  // store and restore focusable attribute for focus scope when registering and\n  // unregistering child focusable views respectively\n  void StoreFocusable() { focusable_store_ = focusable_; }\n  void RestoreFocusable() { SetFocusable(focusable_store_); }\n\n  // |is_leaf| indicate whether the focus / blur node is the leaf node (not the\n  // focus manager node that get focus/blur recursively).\n  void RequestFocus(bool is_leaf = true);\n  void ClearFocus();\n\n  virtual FocusManager* GetParentFocusManager() = 0;\n  //\n  virtual void FocusHasChanged(bool focus, bool is_leaf) {}\n  virtual void SetHasDefaultFocusRing(bool) {}\n  virtual bool DispatchKeyEventOnFocusNode(const KeyEvent* event) = 0;\n  virtual FloatRect GetContentVisibleRect() = 0;\n  virtual FloatSize GetThicknessOffset() = 0;\n  virtual FocusManager::TraversalResult OnTraversalOnScope(\n      FocusManager::Direction direction,\n      FocusManager::TraversalType traversal_type);\n\n  bool IsFocused() { return is_focused_; }\n  void SetIsFocusScope();\n  void SetIsFocusFence();\n  bool IsFocusScope() const { return !!focus_manager_; }\n  bool IsFocusFence() const { return is_fence_; }\n  virtual bool allow_escape() { return false; }\n  FloatRect focus_rect() { return focus_rect_; }\n  void UpdateFocusRect();\n\n  void AddLifecycleListener(LifecycleListener* listener) {\n    if (!life_listeners_) {\n      life_listeners_.reset(new std::set<LifecycleListener*>());\n    }\n    life_listeners_->insert(listener);\n  }\n\n  void RemoveLifecycleListener(LifecycleListener* listener) {\n    if (life_listeners_) {\n      life_listeners_->erase(listener);\n    }\n  }\n\n  void SetFocusIndex(const Point& index) { focus_index_ = index; }\n\n  int GetFocusIndex(Axis axis) const;\n  // If index < 0, nodes cannot be focused while traversal focus, for example,\n  // by arrow keys.\n  bool CanAcceptFocusOnAxis(Axis axis) const;\n\n  // Although node is focusable, there still has some cases that should skip\n  // focus while traversed by arrow keys, mostly because it is not visible.\n  virtual FocusBehavior GetFocusBehavior() const {\n    return FocusBehavior::kFocus;\n  }\n\n  void SetNextFocusUp(std::string id) { next_focus_up_ = std::move(id); }\n  void SetNextFocusDown(std::string id) { next_focus_down_ = std::move(id); }\n  void SetNextFocusLeft(std::string id) { next_focus_left_ = std::move(id); }\n  void SetNextFocusRight(std::string id) { next_focus_right_ = std::move(id); }\n  void SetNextFocusFallback(bool enabled) { next_focus_fallback_ = enabled; }\n  bool NextFocusFallback() const { return next_focus_fallback_; }\n  const std::string& GetNextFocusId(FocusManager::Direction direction) const;\n  virtual const std::string& FocusId() const = 0;\n\n  // A fence node is interested with these events.\n  virtual void OnFocusEscaping(FocusManager::Direction direction) {}\n  virtual void OnFocusEntering(FocusManager::Direction direction) {}\n\n protected:\n  virtual FloatRect CalcFocusRect() const = 0;\n  void SetFocusableInternal(bool focusable);\n  void UpdateFocusRing();\n  void SetSkipFocusTraversal(bool skip_focus_traversal) {\n    skip_focus_traversal_ = skip_focus_traversal;\n  }\n  void OnFocusAttach();\n  void OnFocusDetach();\n\n  FloatRect focus_rect_;\n  // Effective only on FocusScope like Overlay / ListView / ScrollView\n  // If not set, skip focus traversal if it is focus scope.\n  std::optional<bool> skip_focus_traversal_;\n  bool focusable_ = false;\n  std::string next_focus_up_;\n  std::string next_focus_down_;\n  std::string next_focus_left_;\n  std::string next_focus_right_;\n  // Determines whether to fallback to automatic traversal if the node cannot be\n  // found using the specified `next-focus-*`.\n  bool next_focus_fallback_ = false;\n  bool pending_focusable_ = false;\n  bool pending_request_focus_ = false;\n  bool restoring_focus_ = false;\n  // store the original focusable for the focus scope\n  bool focusable_store_ = false;\n  bool is_focused_ = false;\n  // A focus fence means while moving focus, it cannot cross over a fence\n  // automatically.\n  bool is_fence_ = false;\n  // Same as web tabindex, but 2D.\n  Point focus_index_;\n  // In most cases it is unused. So make unique_ptr to save memory.\n  std::unique_ptr<std::set<LifecycleListener*>> life_listeners_;\n  std::unique_ptr<FocusManager> focus_manager_;\n};\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_FOCUS_NODE_H_\n"
  },
  {
    "path": "clay/ui/component/hittest_request.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_HITTEST_REQUEST_H_\n#define CLAY_UI_COMPONENT_HITTEST_REQUEST_H_\n\n#include \"clay/gfx/geometry/float_point.h\"\n\nnamespace clay {\n\nclass HitTestRequest {\n public:\n  enum class Type {\n    kScroll = 0,\n    kClick,\n    kTouch,\n    kTap,\n  };\n\n  HitTestRequest(Type type, const FloatPoint& point)\n      : type_(type), point_(point) {}\n  ~HitTestRequest() = default;\n\n  Type type() const { return type_; }\n  FloatPoint point() const { return point_; }\n\n private:\n  Type type_;\n  FloatPoint point_;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_HITTEST_REQUEST_H_\n"
  },
  {
    "path": "clay/ui/component/image_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/image_view.h\"\n\n#include <memory>\n\n#include \"clay/ui/rendering/render_image.h\"\n\nnamespace clay {\n\nImageView::ImageView(int id, PageView* page_view)\n    : BaseImageView(id, \"image\", std::make_unique<RenderImage>(), page_view) {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/image_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/base_image_view.h\"\n\n#ifndef CLAY_UI_COMPONENT_IMAGE_VIEW_H_\n#define CLAY_UI_COMPONENT_IMAGE_VIEW_H_\n\nnamespace clay {\n\nclass ImageView : public BaseImageView {\n public:\n  ImageView(int id, PageView* page_view);\n  ~ImageView() override = default;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_IMAGE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/inline_image_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/inline_image_view.h\"\n\n#include <memory>\n\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/rendering/render_image.h\"\n\nnamespace clay {\n\nInlineImageView::InlineImageView(int id, PageView* page_view)\n    : WithTypeInfo(id, \"inline-image\", std::make_unique<RenderImage>(),\n                   page_view) {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/inline_image_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/component/base_image_view.h\"\n\n#ifndef CLAY_UI_COMPONENT_INLINE_IMAGE_VIEW_H_\n#define CLAY_UI_COMPONENT_INLINE_IMAGE_VIEW_H_\n\nnamespace clay {\n\nclass InlineImageView : public WithTypeInfo<InlineImageView, BaseImageView> {\n public:\n  InlineImageView(int id, PageView* page_view);\n  ~InlineImageView() override = default;\n  void SetLocation(FloatPoint location) { location_ = location; }\n  const FloatPoint& GetLocation() const { return location_; }\n\n private:\n  FloatPoint location_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_INLINE_IMAGE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/intersection_observer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/intersection_observer.h\"\n\n#include <cstddef>\n#include <memory>\n#include <utility>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/component/intersection_observer_manager.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/scroll_view.h\"\n\nnamespace clay {\n\nnamespace utils = attribute_utils;\n\nfloat TryGetFloatFromMap(const std::string& key, const clay::Value::Map& map,\n                         float default_value) {\n  const auto& iter = map.find(key);\n  if (iter == map.end()) {\n    return default_value;\n  } else {\n    return static_cast<float>(utils::GetDouble(iter->second));\n  }\n}\n\nclay::Value::Map IntersectionRectToMap(const FloatRect& rect) {\n  clay::Value::Map map;\n  map.emplace(\"left\", clay::Value(rect.x()));\n  map.emplace(\"top\", clay::Value(rect.y()));\n  map.emplace(\"right\", clay::Value(rect.width() + rect.x()));\n  map.emplace(\"bottom\", clay::Value(rect.height() + rect.y()));\n  return map;\n}\n\nBaseView* FindAncestorViewByIdSelector(std::string_view id_selector,\n                                       BaseView* child) {\n  FML_DCHECK(child);\n\n  if (child->GetIdSelector() == id_selector) {\n    return child;\n  }\n\n  BaseView* parent = child->Parent();\n  while (parent) {\n    if (parent->GetIdSelector() == id_selector) {\n      return parent;\n    }\n    parent = parent->Parent();\n  }\n\n  return nullptr;\n}\n\nFloatRect BoundingRectWithScroll(BaseView* view) {\n  auto location = view->AbsoluteLocationWithScroll();\n  return FloatRect(location.x(), location.y(), view->Width(), view->Height());\n}\n\nvoid IntersectionObserverEntry::ComputeIntersectionRect(bool ui_clip_enabled) {\n  if (relative_rect_.IsEmpty() || bounding_client_rect_.IsEmpty()) {\n    intersection_rect_ = FloatRect();\n    return;\n  }\n\n  intersection_rect_ = bounding_client_rect_;\n  bool at_root = false;\n  BaseView* parent = target_view_->Parent();\n  while (!at_root && parent) {\n    if (!parent->Visible()) {\n      intersection_rect_ = FloatRect();\n      return;\n    }\n\n    FloatRect parent_rect;\n    if (parent == root_) {\n      at_root = true;\n      parent_rect = relative_rect_;\n    } else {\n      if (parent->GetOverflow() == CSSProperty::OVERFLOW_HIDDEN ||\n          parent->Is<ScrollView>() || parent->Is<BaseListView>() ||\n          ui_clip_enabled) {\n        parent_rect = BoundingRectWithScroll(parent);\n      }\n    }\n\n    if (!parent_rect.IsEmpty()) {\n      if (intersection_rect_.Intersects(parent_rect)) {\n        intersection_rect_.Intersect(parent_rect);\n      } else {\n        intersection_rect_ = FloatRect();\n      }\n    }\n\n    parent = parent->Parent();\n  }\n}\n\nclay::Value::Map IntersectionObserverEntry::ToMap() {\n  clay::Value::Map map;\n  map[\"observerId\"] = clay::Value(relative_to_id_);\n  map[\"relativeRect\"] = clay::Value(RectToMap(relative_rect_));\n  map[\"boundingClientRect\"] = clay::Value(RectToMap(bounding_client_rect_));\n  map[\"intersectionRect\"] = clay::Value(RectToMap(intersection_rect_));\n  map[\"intersectionRatio\"] = clay::Value(intersection_ratio_);\n  map[\"time\"] = clay::Value(time_);\n  return map;\n}\n\nclay::Value::Map IntersectionObserverEntry::RectToMap(FloatRect rect) {\n  clay::Value::Map map;\n  map.emplace(\"left\", clay::Value(static_cast<int>(rect.x())));\n  map.emplace(\"right\", clay::Value(static_cast<int>(rect.MaxX())));\n  map.emplace(\"top\", clay::Value(static_cast<int>(rect.y())));\n  map.emplace(\"bottom\", clay::Value(static_cast<int>(rect.MaxY())));\n  return map;\n}\n\nvoid IntersectionObserverEntry::ComputeIntersectionRatio() {\n  if (intersection_rect_.IsEmpty()) {\n    intersection_ratio_ = 0;\n    return;\n  }\n\n  float target_area =\n      bounding_client_rect_.width() * bounding_client_rect_.height();\n  float intersection_area =\n      intersection_rect_.width() * intersection_rect_.height();\n\n  if (target_area > 0) {\n    intersection_ratio_ = intersection_area / target_area;\n  } else {\n    intersection_ratio_ = 0;\n  }\n}\n\nIntersectionObserver::IntersectionObserver(IntersectionObserverManager* manager,\n                                           const clay::Value::Map& map,\n                                           BaseView* view)\n    : manager_(manager), attached_view_(view) {\n  if (map.find(\"relativeToIdSelector\") == map.end()) {\n    root_ = manager->page_view();\n  } else {\n    auto relative_to_id_selector =\n        utils::GetCString(map.at(\"relativeToIdSelector\"));\n    if (relative_to_id_selector.length() > 0 &&\n        relative_to_id_selector[0] == '#') {\n      root_ = FindAncestorViewByIdSelector(relative_to_id_selector.substr(1),\n                                           attached_view_);\n    } else {\n      root_ = manager->page_view();\n    }\n  }\n\n  margin_left_ = TryGetFloatFromMap(\"marginLeft\", map, 0.f);\n  margin_right_ = TryGetFloatFromMap(\"marginRight\", map, 0.f);\n  margin_top_ = TryGetFloatFromMap(\"marginTop\", map, 0.f);\n  margin_bottom_ = TryGetFloatFromMap(\"marginBottom\", map, 0.f);\n  initial_ratio_ = TryGetFloatFromMap(\"initialRatio\", map, 0.f);\n\n  if (map.find(\"thresholds\") != map.end()) {\n    const auto& array = utils::GetArray(map.at(\"thresholds\"));\n    for (size_t i = 0; i < array.size(); ++i) {\n      thresholds_.emplace_back(static_cast<float>(utils::GetDouble(array[i])));\n    }\n  } else {\n    thresholds_.emplace_back(0.f);\n  }\n\n  if (map.find(\"observerAll\") == map.end()) {\n    observer_all_ = false;\n  } else {\n    observer_all_ = utils::GetBool(map.at(\"observerAll\"));\n  }\n\n  if (map.find(\"customObserverId\") != map.end()) {\n    custom_observer_id_ = utils::GetInt(map.at(\"customObserverId\"));\n  }\n  if (map.find(\"customCallbackId\") != map.end()) {\n    custom_callback_id_ = utils::GetInt(map.at(\"customCallbackId\"));\n    is_custom_observer_ = true;\n  }\n\n  CheckForIntersectionWithTarget();\n}\n\nvoid IntersectionObserver::OnAttach() {\n  available_ = true;\n  is_initial_ = true;\n  CheckForIntersectionWithTarget();\n}\n\nvoid IntersectionObserver::OnDetach() {\n  if (!available_) {\n    return;\n  }\n  is_detaching_ = true;\n  CheckForIntersectionWithTarget();\n  available_ = false;\n  is_detaching_ = false;\n}\n\nvoid IntersectionObserver::CheckForIntersectionWithTarget() {\n  FML_DCHECK(manager_ && attached_view_);\n  if (!available_ || !root_) {\n    return;\n  }\n  old_entry_ = std::move(now_entry_);\n  now_entry_ = std::make_unique<IntersectionObserverEntry>();\n\n  FloatRect target_rect =\n      is_detaching_ ? FloatRect() : BoundingRectWithScroll(attached_view_);\n  FloatRect root_rect = BoundingRectWithScroll(root_);\n  root_rect.Expand(margin_left_, margin_right_, margin_top_, margin_bottom_);\n  now_entry_->bounding_client_rect_ = target_rect;\n  now_entry_->relative_rect_ = root_rect;\n  now_entry_->target_view_ = attached_view_;\n  now_entry_->root_ = root_;\n  now_entry_->ComputeIntersectionRect(exposure_ui_clip_enabled_);\n  now_entry_->time_ = 0;  // not support time\n  now_entry_->relative_to_id_ = attached_view_->GetIdSelector();\n  now_entry_->ComputeIntersectionRatio();\n\n  bool need_notify;\n  if (is_initial_) {\n    need_notify = initial_ratio_ < now_entry_->intersection_ratio_;\n    is_initial_ = false;\n  } else {\n    need_notify = HasCrossedThreshold();\n  }\n  if (need_notify) {\n    NotifyTarget();\n  }\n}\n\nvoid IntersectionObserver::NotifyTarget() {\n  if (attached_view_ && attached_view_->page_view()) {\n    if (is_custom_observer_) {\n      clay::Value::Map map = {};\n      map.emplace(\"isIntersecting\", clay::Value(true));\n      map.emplace(\"intersectionRatio\",\n                  clay::Value(now_entry_->intersection_ratio_));\n      map.emplace(\n          \"intersectionRect\",\n          clay::Value(IntersectionRectToMap(now_entry_->intersection_rect_)));\n      map.emplace(\"boundingClientRect\",\n                  clay::Value(IntersectionRectToMap(\n                      now_entry_->bounding_client_rect_)));\n      map.emplace(\n          \"relativeRect\",\n          clay::Value(IntersectionRectToMap(now_entry_->relative_rect_)));\n      map.emplace(\"observerId\",\n                  clay::Value(now_entry_->target_view_->GetIdSelector()));\n      map.emplace(\"time\", clay::Value(now_entry_->time_));\n      attached_view_->page_view()->CallJSIntersectionObserver(\n          custom_observer_id_, custom_callback_id_,\n          clay::Value(std::move(map)));\n    } else if (auto* delegate =\n                   attached_view_->page_view()->GetEventDelegate()) {\n      delegate->OnIntersectionEvent(attached_view_->GetCallbackId(),\n                                    now_entry_->ToMap());\n    }\n  }\n}\n\nbool IntersectionObserver::HasCrossedThreshold() {\n  float old_ratio = old_entry_ && !old_entry_->intersection_rect_.IsEmpty()\n                        ? old_entry_->intersection_ratio_\n                        : -1;\n  float now_ratio = now_entry_ && !now_entry_->intersection_rect_.IsEmpty()\n                        ? now_entry_->intersection_ratio_\n                        : -1;\n  if (old_ratio == now_ratio) {\n    return false;\n  }\n\n  for (const auto& threshold : thresholds_) {\n    // Return true if an entry matches a threshold or if the new ratio\n    // and the old ratio are on the opposite sides of a threshold.\n    if (threshold == old_ratio || threshold == now_ratio ||\n        ((threshold < old_ratio) != (threshold < now_ratio))) {\n      return true;\n    }\n  }\n  return false;\n}\n\nIntersectionObserver::~IntersectionObserver() {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/intersection_observer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_INTERSECTION_OBSERVER_H_\n#define CLAY_UI_COMPONENT_INTERSECTION_OBSERVER_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/component/css_property.h\"\n\nnamespace clay {\nclass IntersectionObserverManager;\n\nclass IntersectionObserverEntry {\n public:\n  IntersectionObserverEntry() = default;\n  ~IntersectionObserverEntry() = default;\n\n  void ComputeIntersectionRatio();\n  void ComputeIntersectionRect(bool ui_clip_enabled);\n\n  clay::Value::Map ToMap();\n  clay::Value::Map RectToMap(FloatRect rect);\n\n  BaseView* target_view_;\n  BaseView* root_;\n  FloatRect bounding_client_rect_;\n  FloatRect relative_rect_;\n  FloatRect intersection_rect_;\n  bool has_intersection_ = false;\n  float intersection_ratio_;\n  float time_;\n  std::string relative_to_id_;\n};\n\nclass IntersectionObserver {\n public:\n  IntersectionObserver(IntersectionObserverManager* manager,\n                       const clay::Value::Map& map, BaseView* view);\n  virtual ~IntersectionObserver();\n\n  BaseView* GetAttachedView() const { return attached_view_; }\n\n  virtual void CheckForIntersectionWithTarget();\n\n  void OnAttach();\n  void OnDetach();\n\n  enum ObserverType {\n    kIntersectionObserver,\n    kExposeObserver,\n  };\n\n  virtual bool IsOfType(ObserverType type) const {\n    return type == kIntersectionObserver;\n  }\n\n protected:\n  bool HasCrossedThreshold();\n\n  virtual void NotifyTarget();\n\n  bool is_custom_observer_ = false;\n  int custom_callback_id_ = 0;\n  int custom_observer_id_ = 0;\n\n  IntersectionObserverManager* manager_;\n  BaseView* root_;\n  BaseView* attached_view_;\n  float margin_left_;\n  float margin_right_;\n  float margin_top_;\n  float margin_bottom_;\n  float initial_ratio_;\n  std::vector<float> thresholds_;\n  bool observer_all_ = false;  // not support now;\n  bool is_initial_ = true;\n  bool is_detaching_ = false;\n  bool available_ = true;\n  bool exposure_ui_clip_enabled_ = false;\n\n  std::unique_ptr<IntersectionObserverEntry> old_entry_;\n  std::unique_ptr<IntersectionObserverEntry> now_entry_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_INTERSECTION_OBSERVER_H_\n"
  },
  {
    "path": "clay/ui/component/intersection_observer_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/intersection_observer_manager.h\"\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#include \"clay/public/value.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/expose_manager/expose_observer.h\"\n#include \"clay/ui/component/intersection_observer.h\"\n\nnamespace clay {\n\nvoid IntersectionObserverManager::StopExposure(bool send_event) {\n  for (auto& it : expose_observers_map_) {\n    it.second->StopExposure(send_event);\n  }\n}\n\nvoid IntersectionObserverManager::ResumeExposure() {\n  for (auto& it : expose_observers_map_) {\n    it.second->ResumeExposure();\n  }\n}\n\nvoid IntersectionObserverManager::AddObserver(\n    std::unique_ptr<IntersectionObserver> observer) {\n  if (observer->IsOfType(IntersectionObserver::kIntersectionObserver)) {\n    intersection_observers_.emplace_back(std::move(observer));\n  } else if (observer->IsOfType(IntersectionObserver::kExposeObserver)) {\n    std::unique_ptr<ExposeObserver> p(\n        static_cast<ExposeObserver*>(observer.release()));\n    expose_observers_map_.emplace(p.get()->GetAttachedView(), std::move(p));\n  }\n}\n\nvoid IntersectionObserverManager::EraseExposeObserver(\n    const BaseView* view, const ExposeObserver* target) {\n  if (view) {\n    expose_observers_map_.erase(view);\n  } else if (target) {\n    for (auto iter = expose_observers_map_.begin();\n         iter != expose_observers_map_.end();) {\n      if ((iter->second).get() == target) {\n        iter = expose_observers_map_.erase(iter);\n        return;\n      } else {\n        ++iter;\n      }\n    }\n  }\n}\n\nvoid IntersectionObserverManager::RemoveObserver(\n    const IntersectionObserver* target) {\n  if (target->IsOfType(IntersectionObserver::kIntersectionObserver)) {\n    EraseObserver(intersection_observers_, nullptr, target);\n  } else if (target->IsOfType(IntersectionObserver::kExposeObserver)) {\n    expose_observers_map_.erase(target->GetAttachedView());\n    EraseExposeObserver(nullptr, static_cast<const ExposeObserver*>(target));\n  }\n}\n\nvoid IntersectionObserverManager::RemoveAll(BaseView* view) {\n  EraseObserver(intersection_observers_, view, nullptr);\n  EraseExposeObserver(view);\n}\n\nvoid IntersectionObserverManager::RemoveIntersectionObserver(BaseView* view) {\n  EraseObserver(intersection_observers_, view, nullptr);\n}\n\nvoid IntersectionObserverManager::RemoveExposeObserver(BaseView* view) {\n  EraseExposeObserver(view);\n}\n\nvoid IntersectionObserverManager::SetExposureFrequency(int freq) {\n  expose_min_time_gap_ms_ = 1000 / std::min(1, std::max(freq, 60));\n}\n\nvoid IntersectionObserverManager::SetExposureUIMarginEnabled(bool enabled) {\n  exposure_ui_margin_enabled_ = enabled;\n}\n\nbool IntersectionObserverManager::UpdateExposeData(const char* attr_key,\n                                                   const clay::Value& value,\n                                                   BaseView* target_view) {\n  auto target = expose_observers_map_.find(target_view);\n  if (target != expose_observers_map_.end()) {\n    return ((target->second).get())->UpdateExposeData(attr_key, value);\n  }\n  return false;\n}\n\nbool IntersectionObserverManager::HasExposeObserver(BaseView* view) {\n  return expose_observers_map_.count(view) > 0;\n}\n\nvoid IntersectionObserverManager::NotifyObservers() {\n  NotifyObserver(intersection_observers_,\n                 &IntersectionObserver::CheckForIntersectionWithTarget,\n                 nullptr);\n  auto now = fml::TimePoint::Now().ToEpochDelta().ToMicroseconds();\n  if (last_expose_time_ == -1 ||\n      (now - last_expose_time_ > expose_min_time_gap_ms_)) {\n    last_expose_time_ = now;\n    NotifyExposures(&IntersectionObserver::CheckForIntersectionWithTarget);\n  }\n}\n\nvoid IntersectionObserverManager::NotifyTargetAttached(BaseView* view) {\n  NotifyAllObserver(&IntersectionObserver::OnAttach, view);\n}\n\nvoid IntersectionObserverManager::NotifyTargetDetached(BaseView* view) {\n  NotifyAllObserver(&IntersectionObserver::OnDetach, view);\n}\n\nvoid IntersectionObserverManager::NotifyExposures(\n    void (IntersectionObserver::*ptr)(), BaseView* view) {\n  for (auto& it : expose_observers_map_) {\n    if (view == nullptr || (it.second->GetAttachedView() == view)) {\n      (it.second.get()->*ptr)();\n    }\n  }\n}\n\nvoid IntersectionObserverManager::NotifyAllObserver(\n    void (IntersectionObserver::*ptr)(), BaseView* view) {\n  NotifyObserver(intersection_observers_, ptr, view);\n  NotifyExposures(ptr, view);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/intersection_observer_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_INTERSECTION_OBSERVER_MANAGER_H_\n#define CLAY_UI_COMPONENT_INTERSECTION_OBSERVER_MANAGER_H_\n\n#include <cstddef>\n#include <list>\n#include <map>\n#include <memory>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/component/expose_manager/expose_observer.h\"\n#include \"clay/ui/component/intersection_observer.h\"\n\nnamespace clay {\nclass IntersectionObserverManager {\n public:\n  explicit IntersectionObserverManager(BaseView* page_view)\n      : page_view_(page_view) {}\n  ~IntersectionObserverManager() = default;\n\n  void AddObserver(std::unique_ptr<IntersectionObserver> observer);\n  void RemoveIntersectionObserver(BaseView* view);\n\n  void RemoveAll(BaseView* view);\n  void RemoveObserver(const IntersectionObserver* target);\n\n  void NotifyObservers();\n  void NotifyTargetAttached(BaseView* view);\n  void NotifyTargetDetached(BaseView* view);\n\n  void RemoveExposeObserver(BaseView* view);\n  bool UpdateExposeData(const char* attr_key, const clay::Value& value,\n                        BaseView* target_view);\n  bool HasExposeObserver(BaseView* view);\n\n  void StopExposure(bool send_event);\n  void ResumeExposure();\n\n  void SetExposureFrequency(int freq);\n  void SetExposureUIMarginEnabled(bool enabled);\n  bool GetExposureUIMarginEnabled() { return exposure_ui_margin_enabled_; }\n\n  BaseView* page_view() { return page_view_; }\n\n private:\n  int64_t expose_min_time_gap_ms_ = 1000 / 20;\n  bool exposure_ui_margin_enabled_ = false;\n  int64_t last_expose_time_ = -1;\n\n  BaseView* page_view_;\n  std::list<std::unique_ptr<IntersectionObserver>> intersection_observers_;\n  std::unordered_map<const BaseView*, std::unique_ptr<ExposeObserver>>\n      expose_observers_map_;\n\n  void EraseExposeObserver(const BaseView* view = nullptr,\n                           const ExposeObserver* target = nullptr);\n\n  template <class T>\n  void EraseObserver(std::list<T>& container, BaseView* attached_view,\n                     const IntersectionObserver* ptr = nullptr) {\n    for (auto iter = container.begin(); iter != container.end();) {\n      if (((*iter).get() == ptr) ||\n          ((*iter)->GetAttachedView() == attached_view)) {\n        iter = container.erase(iter);\n        return;\n      } else {\n        ++iter;\n      }\n    }\n  }\n\n  template <typename T, typename = std::enable_if_t<\n                            std::is_base_of<IntersectionObserver, T>::value>>\n  void NotifyObserver(std::list<std::unique_ptr<T>>& container,\n                      void (IntersectionObserver::*ptr)(), BaseView* view) {\n    for (auto& observer : container) {\n      if (view == nullptr || (observer->GetAttachedView() == view)) {\n        (observer.get()->*ptr)();\n      }\n    }\n  }\n\n  void NotifyExposures(void (IntersectionObserver::*ptr)(),\n                       BaseView* view = nullptr);\n\n  void NotifyAllObserver(void (IntersectionObserver::*ptr)(),\n                         BaseView* view = nullptr);\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_INTERSECTION_OBSERVER_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/component/isolated_gesture_detector.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_ISOLATED_GESTURE_DETECTOR_H_\n#define CLAY_UI_COMPONENT_ISOLATED_GESTURE_DETECTOR_H_\n\n#include <list>\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/gesture_manager.h\"\n#include \"clay/ui/gesture/gesture_recognizer.h\"\n\nnamespace clay {\n\n// Each isolatedGestureDetector will create its own gesture / arena manager,\n// so that recognizers won't conflict with outer ones.\n// This is helpful when needs to report lynx about which gesture matches\n// no matter the gesture is accepted by some view or not.\nclass IsolatedGestureDetector : public HitTestable, public HitTestTarget {\n public:\n  explicit IsolatedGestureDetector(fml::RefPtr<fml::TaskRunner> task_runner)\n      : gesture_manager_(std::move(task_runner)) {}\n\n  void DispatchPointerEvent(std::vector<PointerEvent>& events,\n                            const HitTestResponsiveResult& result) {\n    hit_test_responsive_result_ = result;\n    gesture_manager_.HandlePointerEvents(this, events);\n  }\n\n  void AddRecognizer(std::unique_ptr<GestureRecognizer>&& recognizer) {\n    FML_DCHECK(recognizer->gesture_manager() == &gesture_manager_);\n    recognizers_.emplace_back(std::move(recognizer));\n  }\n\n  GestureManager* gesture_manager() { return &gesture_manager_; }\n\n private:\n  // Override HitTestable\n  bool HitTest(const PointerEvent& event, HitTestResult& result) override {\n    result.emplace_back(GetHitTestTargetWeakPtr());\n    return true;\n  }\n\n  // Override HitTestTarget\n  void HandleEvent(const PointerEvent& event) override {\n    if (event.type == PointerEvent::EventType::kDownEvent) {\n      for (auto& recognizer : recognizers_) {\n        if (recognizer->getType() == GestureRecognizerType::kLongPress &&\n            !hit_test_responsive_result_.has_longpress_event) {\n          continue;\n        }\n        recognizer->AddPointer(event);\n      }\n    }\n  }\n\n  bool HasDragGestureRecognizer(ScrollDirection direction) const override {\n    return false;\n  }\n\n  bool HasTapGestureRecognizer() const override { return false; }\n  bool HasLongPressGestureRecognizer() const override { return false; }\n\n  bool HasTapEvent() const override { return false; }\n  bool HasLongPressEvent() const override { return false; }\n\n  bool ShouldBlockNativeEvent() const override { return false; }\n\n private:\n  GestureManager gesture_manager_;\n  std::list<std::unique_ptr<GestureRecognizer>> recognizers_;\n  HitTestResponsiveResult hit_test_responsive_result_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_ISOLATED_GESTURE_DETECTOR_H_\n"
  },
  {
    "path": "clay/ui/component/key_event_handler.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/key_event_handler.h\"\n\nnamespace clay {\n\nKeyEventHandler::KeyEventHandler() = default;\n\nbool KeyEventHandler::OnKeyEvent(const KeyEvent* event) {\n  KeyCode key_code = static_cast<KeyCode>(event->GetLogical());\n  if (event->GetType() == KeyEventType::kDown && key_down_callback_) {\n    return key_down_callback_(key_code);\n  } else if (event->GetType() == KeyEventType::kUp && key_up_callback_) {\n    return key_up_callback_(key_code);\n  }\n\n  return false;\n}\n\nvoid KeyEventHandler::SetKeyDownCallback(\n    std::function<bool(KeyCode)> callback) {\n  key_down_callback_ = callback;\n}\n\nvoid KeyEventHandler::SetKeyUpCallback(std::function<bool(KeyCode)> callback) {\n  key_up_callback_ = callback;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/key_event_handler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_KEY_EVENT_HANDLER_H_\n#define CLAY_UI_COMPONENT_KEY_EVENT_HANDLER_H_\n\n#include <functional>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/event/key_event.h\"\n\nnamespace clay {\n\n// KeyEventHandler is refactored from EventHandler, separating focus and gesture\n// logics. Gesture logics are moved into ui/gesture, and focus related\n// are not used so far.\nclass KeyEventHandler {\n public:\n  KeyEventHandler();\n  virtual ~KeyEventHandler() = default;\n\n  // key event\n  virtual bool OnKeyEvent(const KeyEvent* event);\n\n  void SetKeyDownCallback(std::function<bool(KeyCode)> callback);\n  void SetKeyUpCallback(std::function<bool(KeyCode)> callback);\n\n protected:\n  std::function<bool(KeyCode)> key_down_callback_;\n  std::function<bool(KeyCode)> key_up_callback_;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_KEY_EVENT_HANDLER_H_\n"
  },
  {
    "path": "clay/ui/component/keyframe_animator_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <vector>\n\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/gfx/animation/keyframe.h\"\n#include \"clay/gfx/animation/keyframes_manager.h\"\n#include \"clay/gfx/animation/value_animator.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/rendering/render_container.h\"\n#include \"clay/ui/testing/ui_test.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nclass KeyFrameTest : public UITest {\n protected:\n  void UISetUp() override {\n    animator_view_ = std::make_unique<BaseView>(\n        -1, \"test_view\", std::make_unique<RenderContainer>(), page_.get());\n    animator_view_->SetAttribute(\"opacity\", clay::Value(0.1));\n    page_->AddChild(animator_view_.get());\n    page_->SetRasterAnimationEnabled(false);\n  }\n\n  void UITearDown() override {\n    animator_view_ = nullptr;\n    animation_data_.clear();\n  }\n\n  std::unique_ptr<BaseView> animator_view_;\n  std::vector<AnimationData> animation_data_;\n  AnimationData start_data_{\"opacity_test\",\n                            160,\n                            0,\n                            TimingFunctionData(),\n                            0,\n                            ClayAnimationFillModeType::kBoth,\n                            ClayAnimationDirectionType::kNormal,\n                            ClayAnimationPlayStateType::kRunning};\n};\n\nnamespace {\nclass MockAnimatorListener : public AnimatorListener {\n public:\n  // NOLINTNEXTLINE\n  MOCK_METHOD(void, OnAnimationStart, (Animator & animation), (override));\n  // NOLINTNEXTLINE\n  MOCK_METHOD(void, OnAnimationCancel, (Animator & animation), (override));\n  // NOLINTNEXTLINE\n  MOCK_METHOD(void, OnAnimationEnd, (Animator & animation), (override));\n  // NOLINTNEXTLINE\n  MOCK_METHOD(void, OnAnimationRepeat, (Animator & animation), (override));\n};\n}  // namespace\n\nnamespace {\n\nstatic std::string FractionKeyFromPercent(int percent) {\n  return std::to_string(static_cast<double>(percent) * 0.01);\n}\n\nstatic Value MakeOpacityTestKeyframes(\n    std::initializer_list<std::pair<int, double>> percent_to_opacity) {\n  Value::Map keyframes;\n  for (const auto& item : percent_to_opacity) {\n    const int percent = item.first;\n    const double opacity = item.second;\n    keyframes.emplace(FractionKeyFromPercent(percent),\n                      Value{{\"opacity\", Value{opacity}}});\n  }\n\n  return Value{{\"opacity_test\", Value{std::move(keyframes)}}};\n}\n\nValue CreateKeyFrameData1() {\n  return MakeOpacityTestKeyframes({{50, 0.3}, {90, 1.0}});\n}\n\nValue CreateKeyFrameData2() {\n  return MakeOpacityTestKeyframes({{50, 0.3}, {100, 1.0}});\n}\n\nValue CreateKeyFrameData3() {\n  return MakeOpacityTestKeyframes({{0, 0.3}, {100, 1.0}});\n}\n\n}  // namespace\n\n// Test start and default values\nTEST_F_UI(KeyFrameTest, DefaultStartAndEnd) {\n  Value keyframes_data = CreateKeyFrameData1();\n\n  page_->SetKeyframesData(keyframes_data);\n  animator_view_->SetBound(0, 0, 100, 100);\n  animation_data_.clear();\n  animation_data_.push_back(start_data_);\n  animator_view_->SetAnimation(animation_data_);\n\n  int64_t frame_time = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  for (size_t i = 0; i < 10; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  EXPECT_TRUE(\n      animator_view_->KeyframesMgr()->animations().front().keyframes_map.find(\n          ClayAnimationPropertyType::kOpacity) !=\n      animator_view_->KeyframesMgr()->animations().front().keyframes_map.end());\n  FloatKeyframeSet* keyframe_sets = static_cast<FloatKeyframeSet*>(\n      animator_view_->KeyframesMgr()\n          ->animations_.front()\n          .keyframes_map[ClayAnimationPropertyType::kOpacity]\n          .get());\n\n  EXPECT_EQ(keyframe_sets->keyframes_.front()->Value(), 0.1f);\n  EXPECT_EQ(keyframe_sets->keyframes_.back()->Value(), 0.1f);\n}\n\n// Test update animation properties\nTEST_F_UI(KeyFrameTest, UpdateAnimation) {\n  AnimationData update_data{\"opacity_test\",\n                            240,\n                            0,\n                            TimingFunctionData(),\n                            0,\n                            ClayAnimationFillModeType::kBoth,\n                            ClayAnimationDirectionType::kNormal,\n                            ClayAnimationPlayStateType::kRunning};\n\n  auto data = CreateKeyFrameData1();\n\n  page_->SetKeyframesData(data);\n  animator_view_->SetBound(0, 0, 100, 100);\n  animation_data_.clear();\n  animation_data_.push_back(start_data_);\n  animator_view_->SetAnimation(animation_data_);\n\n  int64_t frame_time = 10000;\n  for (size_t i = 0; i < 10; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n  // update animation\n  animation_data_.clear();\n  animation_data_.push_back(update_data);\n  animator_view_->SetAnimation(animation_data_);\n\n  EXPECT_EQ(animator_view_->KeyframesMgr()\n                ->animations()\n                .front()\n                .animator->start_time_,\n            10000);\n}\n\n// Test fillmode forwards changed to backwards\nTEST_F_UI(KeyFrameTest, ChangeFillmode) {\n  AnimationData update_data{\"opacity_test\",\n                            240,\n                            0,\n                            TimingFunctionData(),\n                            0,\n                            ClayAnimationFillModeType::kBackwards,\n                            ClayAnimationDirectionType::kNormal,\n                            ClayAnimationPlayStateType::kRunning};\n\n  auto data = CreateKeyFrameData2();\n\n  page_->SetKeyframesData(data);\n  animator_view_->SetBound(0, 0, 100, 100);\n  animation_data_.clear();\n  animation_data_.push_back(start_data_);\n  animator_view_->SetAnimation(animation_data_);\n\n  int64_t frame_time = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  for (size_t i = 0; i <= 10; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  EXPECT_EQ(animator_view_->render_object()->Opacity(), 1.f);\n  // update animation\n  animation_data_.clear();\n  animation_data_.push_back(update_data);\n  animator_view_->SetAnimation(animation_data_);\n\n  for (size_t i = 0; i < 10; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  EXPECT_EQ(animator_view_->render_object()->Opacity(), 0.1f);\n}\n\n// Test animation delay attribute\nTEST_F_UI(KeyFrameTest, AnimationDelay) {\n  // Test animation delay less than 0.\n  // With play_state paused, the animation should be frozen at the seeked\n  // position implied by the negative delay.\n  AnimationData start_data{\"opacity_test\",\n                           240,\n                           -120,\n                           TimingFunctionData(),\n                           0,\n                           ClayAnimationFillModeType::kForwards,\n                           ClayAnimationDirectionType::kNormal,\n                           ClayAnimationPlayStateType::kPaused};\n\n  Value keyframes_data = CreateKeyFrameData1();\n\n  page_->SetKeyframesData(keyframes_data);\n  animator_view_->SetBound(0, 0, 100, 100);\n  animation_data_.clear();\n  animation_data_.push_back(start_data);\n  animator_view_->SetAnimation(animation_data_);\n\n  int64_t frame_time = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  for (size_t i = 0; i < 10; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  EXPECT_EQ(animator_view_->render_object()->Opacity(), 0.3f);\n}\n\n// Test animation delay more than 0 and fillmode is forwards\nTEST_F_UI(KeyFrameTest, AnimationDelayCombineForwards) {\n  AnimationData start_data{\"opacity_test\",\n                           240,\n                           120,\n                           TimingFunctionData(),\n                           0,\n                           ClayAnimationFillModeType::kForwards,\n                           ClayAnimationDirectionType::kNormal,\n                           ClayAnimationPlayStateType::kPaused};\n\n  auto data = CreateKeyFrameData3();\n\n  page_->SetKeyframesData(data);\n  animator_view_->SetBound(0, 0, 100, 100);\n  animation_data_.clear();\n  animation_data_.push_back(start_data);\n  animator_view_->SetAnimation(animation_data_);\n\n  int64_t frame_time = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  for (size_t i = 0; i < 7; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  animator_view_->KeyframesMgr()\n      ->target_->GetAnimationHandler()\n      ->DoAnimationFrame(frame_time);\n\n  EXPECT_EQ(animator_view_->render_object()->Opacity(), 0.1f);\n}\n\n// Test animation delay more than 0 and fillmode is backwards\nTEST_F_UI(KeyFrameTest, AnimationDelayCombineBackwards) {\n  AnimationData start_data{\"opacity_test\",\n                           240,\n                           120,\n                           TimingFunctionData(),\n                           0,\n                           ClayAnimationFillModeType::kBackwards,\n                           ClayAnimationDirectionType::kNormal,\n                           ClayAnimationPlayStateType::kPaused};\n\n  auto data = CreateKeyFrameData3();\n\n  page_->SetKeyframesData(data);\n  animator_view_->SetBound(0, 0, 100, 100);\n  animation_data_.clear();\n  animation_data_.push_back(start_data);\n  animator_view_->SetAnimation(animation_data_);\n\n  int64_t frame_time = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  for (size_t i = 0; i < 7; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  animator_view_->KeyframesMgr()\n      ->target_->GetAnimationHandler()\n      ->DoAnimationFrame(frame_time);\n\n  EXPECT_EQ(animator_view_->render_object()->Opacity(), 0.3f);\n}\n\n// Test for animation start event\nTEST_F_UI(KeyFrameTest, AnimationStartEvent) {\n  AnimationData update_data1{\"opacity_test\",\n                             240,\n                             0,\n                             TimingFunctionData(),\n                             0,\n                             ClayAnimationFillModeType::kBoth,\n                             ClayAnimationDirectionType::kNormal,\n                             ClayAnimationPlayStateType::kRunning};\n\n  AnimationData update_data2{\"opacity_test\",\n                             480,\n                             0,\n                             TimingFunctionData(),\n                             0,\n                             ClayAnimationFillModeType::kBoth,\n                             ClayAnimationDirectionType::kNormal,\n                             ClayAnimationPlayStateType::kRunning};\n  MockAnimatorListener mock_listener;\n  auto data = CreateKeyFrameData1();\n\n  page_->SetKeyframesData(data);\n  animator_view_->SetBound(0, 0, 100, 100);\n  animation_data_.push_back(start_data_);\n  animator_view_->SetAnimation(animation_data_);\n\n  ValueAnimator* animator =\n      animator_view_->KeyframesMgr()->animations_.front().animator.get();\n  EXPECT_EQ(animator->StartListenersCalled(), true);\n\n  EXPECT_CALL(mock_listener, OnAnimationStart(::testing::_)).Times(1);\n  animator->AddListener(&mock_listener);\n\n  int64_t frame_time = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  for (size_t i = 0; i < 10; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  // update animation\n  animation_data_.clear();\n  animation_data_.push_back(update_data1);\n  animator_view_->SetAnimation(animation_data_);\n\n  for (size_t i = 0; i < 10; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  // update animation\n  animation_data_.clear();\n  animation_data_.push_back(update_data2);\n  animator_view_->SetAnimation(animation_data_);\n\n  for (size_t i = 0; i < 10; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  EXPECT_EQ(animator->StartListenersCalled(), true);\n\n  animator->RemoveListener(&mock_listener);\n}\n\n// Test for animation end event\nTEST_F_UI(KeyFrameTest, AnimationEndEvent) {\n  AnimationData update_data1{\"opacity_test\",\n                             240,\n                             0,\n                             TimingFunctionData(),\n                             0,\n                             ClayAnimationFillModeType::kBoth,\n                             ClayAnimationDirectionType::kNormal,\n                             ClayAnimationPlayStateType::kRunning};\n\n  AnimationData update_data2{\"opacity_test\",\n                             480,\n                             0,\n                             TimingFunctionData(),\n                             0,\n                             ClayAnimationFillModeType::kBoth,\n                             ClayAnimationDirectionType::kNormal,\n                             ClayAnimationPlayStateType::kRunning};\n  MockAnimatorListener mock_listener;\n  auto data = CreateKeyFrameData1();\n\n  page_->SetKeyframesData(data);\n  animator_view_->SetBound(0, 0, 100, 100);\n  animation_data_.push_back(start_data_);\n  animator_view_->SetAnimation(animation_data_);\n\n  ValueAnimator* animator =\n      animator_view_->KeyframesMgr()->animations_.front().animator.get();\n  EXPECT_EQ(animator->StartListenersCalled(), true);\n\n  EXPECT_CALL(mock_listener, OnAnimationEnd(::testing::_)).Times(2);\n  animator->AddListener(&mock_listener);\n\n  int64_t frame_time = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  for (size_t i = 0; i < 9; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  // update animation\n  animation_data_.clear();\n  animation_data_.push_back(update_data1);\n\n  animator_view_->SetAnimation(animation_data_);\n\n  for (size_t i = 0; i < 10; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  // update animation\n  animation_data_.clear();\n  animation_data_.push_back(update_data2);\n\n  animator_view_->SetAnimation(animation_data_);\n\n  for (size_t i = 0; i < 20; i++) {\n    animator_view_->KeyframesMgr()\n        ->target_->GetAnimationHandler()\n        ->DoAnimationFrame(frame_time);\n    frame_time += 16;\n  }\n\n  animator->RemoveListener(&mock_listener);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/keywords.cc",
    "content": "/* C++ code produced by gperf version 3.0.3 */\n/* Computed positions: -k'1,3,13,$' */\n\n#if !(                                                                         \\\n    (' ' == 32) && ('!' == 33) && ('\"' == 34) && ('#' == 35) && ('%' == 37) && \\\n    ('&' == 38) && ('\\'' == 39) && ('(' == 40) && (')' == 41) &&               \\\n    ('*' == 42) && ('+' == 43) && (',' == 44) && ('-' == 45) && ('.' == 46) && \\\n    ('/' == 47) && ('0' == 48) && ('1' == 49) && ('2' == 50) && ('3' == 51) && \\\n    ('4' == 52) && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) && \\\n    ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) && ('=' == 61) && \\\n    ('>' == 62) && ('?' == 63) && ('A' == 65) && ('B' == 66) && ('C' == 67) && \\\n    ('D' == 68) && ('E' == 69) && ('F' == 70) && ('G' == 71) && ('H' == 72) && \\\n    ('I' == 73) && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) && \\\n    ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) && ('R' == 82) && \\\n    ('S' == 83) && ('T' == 84) && ('U' == 85) && ('V' == 86) && ('W' == 87) && \\\n    ('X' == 88) && ('Y' == 89) && ('Z' == 90) && ('[' == 91) &&                \\\n    ('\\\\' == 92) && (']' == 93) && ('^' == 94) && ('_' == 95) &&               \\\n    ('a' == 97) && ('b' == 98) && ('c' == 99) && ('d' == 100) &&               \\\n    ('e' == 101) && ('f' == 102) && ('g' == 103) && ('h' == 104) &&            \\\n    ('i' == 105) && ('j' == 106) && ('k' == 107) && ('l' == 108) &&            \\\n    ('m' == 109) && ('n' == 110) && ('o' == 111) && ('p' == 112) &&            \\\n    ('q' == 113) && ('r' == 114) && ('s' == 115) && ('t' == 116) &&            \\\n    ('u' == 117) && ('v' == 118) && ('w' == 119) && ('x' == 120) &&            \\\n    ('y' == 121) && ('z' == 122) && ('{' == 123) && ('|' == 124) &&            \\\n    ('}' == 125) && ('~' == 126))\n/* The character set is not based on ISO-646.  */\n#error \\\n    \"gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>.\"\n#endif\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n//\n// DO NOT MODIFY THIS FILE! AUTO GENERATED BY clay/tools/make_keywords.py\n//\n\n#include \"clay/ui/component/keywords.h\"\n\n#include <string>\n\nnamespace clay {\nstruct TokenValue {\n  short name;\n  short id;\n};\n\n#define TOTAL_KEYWORDS 259\n#define MIN_WORD_LENGTH 3\n#define MAX_WORD_LENGTH 37\n#define MIN_HASH_VALUE 39\n#define MAX_HASH_VALUE 687\n/* maximum key range = 649, duplicates = 0 */\n\nclass KeywordHash {\n private:\n  static inline unsigned int hash(const char *str, unsigned int len);\n\n public:\n  static const struct TokenValue *find(const char *str, unsigned int len);\n};\n\ninline unsigned int KeywordHash::hash(const char *str, unsigned int len) {\n  static const unsigned short asso_values[] = {\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      215, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 9,   688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 47,  55,  103, 13,  9,   203, 186, 176,\n      113, 10,  27,  139, 17,  85,  197, 31,  688, 20,  12,  11,  257, 212, 57,\n      138, 154, 11,  688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688, 688,\n      688};\n  unsigned int hval = len;\n\n  switch (hval) {\n    default:\n      hval += asso_values[(unsigned char)str[12]];\n    /*FALLTHROUGH*/\n    case 12:\n    case 11:\n    case 10:\n    case 9:\n    case 8:\n    case 7:\n    case 6:\n    case 5:\n    case 4:\n    case 3:\n      hval += asso_values[(unsigned char)str[2]];\n    /*FALLTHROUGH*/\n    case 2:\n    case 1:\n      hval += asso_values[(unsigned char)str[0]];\n      break;\n  }\n  return hval + asso_values[(unsigned char)str[len - 1]];\n}\n\nstruct StringPool_t {\n  char StringPool_str39[sizeof(\"speed\")];\n  char StringPool_str42[sizeof(\"dataset\")];\n  char StringPool_str43[sizeof(\"mode\")];\n  char StringPool_str46[sizeof(\"disabled\")];\n  char StringPool_str47[sizeof(\"mask-size\")];\n  char StringPool_str48[sizeof(\"mask-image\")];\n  char StringPool_str51[sizeof(\"mask-repeat\")];\n  char StringPool_str54[sizeof(\"scroll-left\")];\n  char StringPool_str55[sizeof(\"type\")];\n  char StringPool_str57[sizeof(\"scroll-to-id\")];\n  char StringPool_str60[sizeof(\"preload\")];\n  char StringPool_str63[sizeof(\"scroll-enable\")];\n  char StringPool_str64[sizeof(\"exposure-id\")];\n  char StringPool_str68[sizeof(\"repeat\")];\n  char StringPool_str69[sizeof(\"mask-clip\")];\n  char StringPool_str71[sizeof(\"perspective\")];\n  char StringPool_str72[sizeof(\"markdown-effect\")];\n  char StringPool_str73[sizeof(\"scroll-top\")];\n  char StringPool_str76[sizeof(\"auto-size\")];\n  char StringPool_str77[sizeof(\"adjust-mode\")];\n  char StringPool_str80[sizeof(\"scroll-forward-mode\")];\n  char StringPool_str84[sizeof(\"transform\")];\n  char StringPool_str87[sizeof(\"exposure-screen-margin-left\")];\n  char StringPool_str88[sizeof(\"exposure-screen-margin-right\")];\n  char StringPool_str89[sizeof(\"bottom-inset\")];\n  char StringPool_str91[sizeof(\"exposure-ui-margin-left\")];\n  char StringPool_str92[sizeof(\"exposure-ui-margin-right\")];\n  char StringPool_str94[sizeof(\"scroll-bar-track-color\")];\n  char StringPool_str95[sizeof(\"exposure-screen-margin-bottom\")];\n  char StringPool_str96[sizeof(\"border-style\")];\n  char StringPool_str97[sizeof(\"border-left\")];\n  char StringPool_str98[sizeof(\"border-right\")];\n  char StringPool_str99[sizeof(\"exposure-ui-margin-bottom\")];\n  char StringPool_str101[sizeof(\"border\")];\n  char StringPool_str103[sizeof(\"auto-save-last-child\")];\n  char StringPool_str106[sizeof(\"exposure-screen-margin-top\")];\n  char StringPool_str107[sizeof(\"border-color\")];\n  char StringPool_str108[sizeof(\"scroll-backward-mode\")];\n  char StringPool_str109[sizeof(\"placeholder\")];\n  char StringPool_str110[sizeof(\"exposure-ui-margin-top\")];\n  char StringPool_str111[sizeof(\"border-top-style\")];\n  char StringPool_str112[sizeof(\"border-radius\")];\n  char StringPool_str113[sizeof(\"border-left-style\")];\n  char StringPool_str114[sizeof(\"word-break\")];\n  char StringPool_str115[sizeof(\"name\")];\n  char StringPool_str116[sizeof(\"border-top\")];\n  char StringPool_str118[sizeof(\"border-top-left-radius\")];\n  char StringPool_str119[sizeof(\"prev-scrollable\")];\n  char StringPool_str120[sizeof(\"border-bottom-style\")];\n  char StringPool_str122[sizeof(\"border-bottom\")];\n  char StringPool_str123[sizeof(\"min-height\")];\n  char StringPool_str125[sizeof(\"mask-origin\")];\n  char StringPool_str126[sizeof(\"tint-color\")];\n  char StringPool_str127[sizeof(\"direction\")];\n  char StringPool_str129[sizeof(\"border-bottom-left-radius\")];\n  char StringPool_str130[sizeof(\"border-bottom-right-radius\")];\n  char StringPool_str131[sizeof(\"border-bottom-color\")];\n  char StringPool_str134[sizeof(\"needs-visible-cells\")];\n  char StringPool_str140[sizeof(\"richtype\")];\n  char StringPool_str142[sizeof(\"itemkeys\")];\n  char StringPool_str143[sizeof(\"scroll-bar-enable\")];\n  char StringPool_str144[sizeof(\"scroll-direction\")];\n  char StringPool_str146[sizeof(\"scroll-orientation\")];\n  char StringPool_str147[sizeof(\"exposure-area\")];\n  char StringPool_str148[sizeof(\"exposure-scene\")];\n  char StringPool_str149[sizeof(\"cursor\")];\n  char StringPool_str152[sizeof(\"idSelector\")];\n  char StringPool_str153[sizeof(\"transition\")];\n  char StringPool_str154[sizeof(\"caret-color\")];\n  char StringPool_str156[sizeof(\"cap-insets\")];\n  char StringPool_str159[sizeof(\"edit-on-focus\")];\n  char StringPool_str160[sizeof(\"sticky-offset\")];\n  char StringPool_str162[sizeof(\"item-snap\")];\n  char StringPool_str164[sizeof(\"text\")];\n  char StringPool_str168[sizeof(\"select\")];\n  char StringPool_str169[sizeof(\"list-type\")];\n  char StringPool_str170[sizeof(\"text-maxline\")];\n  char StringPool_str171[sizeof(\"text-indent\")];\n  char StringPool_str173[sizeof(\"typewriter-height-transition-duration\")];\n  char StringPool_str175[sizeof(\"maxlines\")];\n  char StringPool_str176[sizeof(\"max-height\")];\n  char StringPool_str177[sizeof(\"preferred-focus-child\")];\n  char StringPool_str178[sizeof(\"scroll-x\")];\n  char StringPool_str180[sizeof(\"enable-new-animator\")];\n  char StringPool_str181[sizeof(\"background\")];\n  char StringPool_str183[sizeof(\"align-focus\")];\n  char StringPool_str184[sizeof(\"text-gradient\")];\n  char StringPool_str189[sizeof(\"enable-insert-platform-view-operation\")];\n  char StringPool_str190[sizeof(\"white-space\")];\n  char StringPool_str193[sizeof(\"text-mark-attachments\")];\n  char StringPool_str194[sizeof(\"scroll-y\")];\n  char StringPool_str195[sizeof(\"background-repeat\")];\n  char StringPool_str198[sizeof(\"scroll-to-index\")];\n  char StringPool_str199[sizeof(\"markdown-style\")];\n  char StringPool_str200[sizeof(\"background-image\")];\n  char StringPool_str202[sizeof(\"image-transition-style\")];\n  char StringPool_str204[sizeof(\"auto-fix\")];\n  char StringPool_str206[sizeof(\"content\")];\n  char StringPool_str207[sizeof(\"allow-escape\")];\n  char StringPool_str208[sizeof(\"controls\")];\n  char StringPool_str209[sizeof(\"confirm-type\")];\n  char StringPool_str210[sizeof(\"smart-scroll\")];\n  char StringPool_str211[sizeof(\"content-id\")];\n  char StringPool_str212[sizeof(\"mask-position\")];\n  char StringPool_str213[sizeof(\"send-composing-input\")];\n  char StringPool_str214[sizeof(\"animation-type\")];\n  char StringPool_str215[sizeof(\"border-left-color\")];\n  char StringPool_str217[sizeof(\"text-shadow\")];\n  char StringPool_str218[sizeof(\"placeholder-color\")];\n  char StringPool_str219[sizeof(\"content-range\")];\n  char StringPool_str220[sizeof(\"autoplay\")];\n  char StringPool_str221[sizeof(\"src\")];\n  char StringPool_str222[sizeof(\"ignore-focus\")];\n  char StringPool_str223[sizeof(\"border-top-right-radius\")];\n  char StringPool_str224[sizeof(\"outline\")];\n  char StringPool_str226[sizeof(\"hit-slop\")];\n  char StringPool_str227[sizeof(\"consume-slide-event\")];\n  char StringPool_str228[sizeof(\"enable-nested-scroll\")];\n  char StringPool_str229[sizeof(\"readonly\")];\n  char StringPool_str231[sizeof(\"enable-scrollbar\")];\n  char StringPool_str232[sizeof(\"typewriter-dynamic-height\")];\n  char StringPool_str233[sizeof(\"custom-text-selection\")];\n  char StringPool_str236[sizeof(\"animation-frame-rate\")];\n  char StringPool_str237[sizeof(\"skip-redirection\")];\n  char StringPool_str239[sizeof(\"outline-style\")];\n  char StringPool_str240[sizeof(\"visible\")];\n  char StringPool_str243[sizeof(\"scroll-bar-thumb-radius\")];\n  char StringPool_str244[sizeof(\"text-align\")];\n  char StringPool_str246[sizeof(\"line-height\")];\n  char StringPool_str250[sizeof(\"scroll-bar-thumb-color\")];\n  char StringPool_str254[sizeof(\"animation\")];\n  char StringPool_str256[sizeof(\"scroll-bar-thumb-hover-color\")];\n  char StringPool_str257[sizeof(\"scroll-bar-thumb-active-color\")];\n  char StringPool_str258[sizeof(\"next-focus-left\")];\n  char StringPool_str260[sizeof(\"box-shadow\")];\n  char StringPool_str261[sizeof(\"outline-color\")];\n  char StringPool_str262[sizeof(\"cap-insets-scale\")];\n  char StringPool_str263[sizeof(\"border-width\")];\n  char StringPool_str265[sizeof(\"column-count\")];\n  char StringPool_str267[sizeof(\"color\")];\n  char StringPool_str268[sizeof(\"downsampling\")];\n  char StringPool_str269[sizeof(\"z-index\")];\n  char StringPool_str271[sizeof(\"overflow\")];\n  char StringPool_str272[sizeof(\"transform-origin\")];\n  char StringPool_str273[sizeof(\"enable-font-scaling\")];\n  char StringPool_str275[sizeof(\"show-soft-input-onfocus\")];\n  char StringPool_str276[sizeof(\"text-overflow\")];\n  char StringPool_str277[sizeof(\"scroll-event-throttle\")];\n  char StringPool_str278[sizeof(\"drop-shadow\")];\n  char StringPool_str279[sizeof(\"react-ref\")];\n  char StringPool_str280[sizeof(\"background-origin\")];\n  char StringPool_str282[sizeof(\"enable-report-info\")];\n  char StringPool_str284[sizeof(\"item-key\")];\n  char StringPool_str285[sizeof(\"sticky\")];\n  char StringPool_str287[sizeof(\"border-bottom-width\")];\n  char StringPool_str289[sizeof(\"text-stroke-color\")];\n  char StringPool_str295[sizeof(\"background-size\")];\n  char StringPool_str298[sizeof(\"next-focus-up\")];\n  char StringPool_str301[sizeof(\"uidisappear\")];\n  char StringPool_str302[sizeof(\"next-scrollable\")];\n  char StringPool_str304[sizeof(\"experimental-recycle-sticky-item\")];\n  char StringPool_str306[sizeof(\"font-size\")];\n  char StringPool_str307[sizeof(\"font-style\")];\n  char StringPool_str308[sizeof(\"border-top-color\")];\n  char StringPool_str310[sizeof(\"font-weight\")];\n  char StringPool_str311[sizeof(\"placeholder-font-size\")];\n  char StringPool_str313[sizeof(\"list-main-axis-gap\")];\n  char StringPool_str314[sizeof(\"user-interaction-enabled\")];\n  char StringPool_str315[sizeof(\"placeholder-font-weight\")];\n  char StringPool_str316[sizeof(\"next-focus-fallback\")];\n  char StringPool_str317[sizeof(\"border-right-style\")];\n  char StringPool_str318[sizeof(\"scroll-bar-auto-hide\")];\n  char StringPool_str321[sizeof(\"scroll-without-focus\")];\n  char StringPool_str322[sizeof(\"prevent-loading-on-list-scroll\")];\n  char StringPool_str323[sizeof(\"focus\")];\n  char StringPool_str324[sizeof(\"focusable\")];\n  char StringPool_str325[sizeof(\"border-left-width\")];\n  char StringPool_str326[sizeof(\"initial-animation-step\")];\n  char StringPool_str327[sizeof(\"bounce\")];\n  char StringPool_str328[sizeof(\"border-right-color\")];\n  char StringPool_str331[sizeof(\"bounces\")];\n  char StringPool_str332[sizeof(\"uiappear\")];\n  char StringPool_str335[sizeof(\"blur-radius\")];\n  char StringPool_str336[sizeof(\"accessibility-element\")];\n  char StringPool_str337[sizeof(\"scroll-bar-width\")];\n  char StringPool_str338[sizeof(\"accessibility-elements\")];\n  char StringPool_str339[sizeof(\"list-cross-axis-gap\")];\n  char StringPool_str340[sizeof(\"maxlength\")];\n  char StringPool_str343[sizeof(\"background-clip\")];\n  char StringPool_str346[sizeof(\"vertical-orientation\")];\n  char StringPool_str347[sizeof(\"enable-scroll\")];\n  char StringPool_str348[sizeof(\"allow-break-around-punctuation\")];\n  char StringPool_str350[sizeof(\"text-maxlength\")];\n  char StringPool_str351[sizeof(\"use-soft-keyboard\")];\n  char StringPool_str352[sizeof(\"content-complete\")];\n  char StringPool_str354[sizeof(\"overflow-x\")];\n  char StringPool_str357[sizeof(\"loop-count\")];\n  char StringPool_str358[sizeof(\"image-config\")];\n  char StringPool_str361[sizeof(\"low-quality\")];\n  char StringPool_str362[sizeof(\"text-decoration\")];\n  char StringPool_str363[sizeof(\"next-focus-right\")];\n  char StringPool_str365[sizeof(\"value\")];\n  char StringPool_str367[sizeof(\"enable-exposure-ui-clip\")];\n  char StringPool_str368[sizeof(\"filter\")];\n  char StringPool_str370[sizeof(\"overflow-y\")];\n  char StringPool_str371[sizeof(\"loop\")];\n  char StringPool_str373[sizeof(\"intersection-observers\")];\n  char StringPool_str376[sizeof(\"list-container-info\")];\n  char StringPool_str380[sizeof(\"border-top-width\")];\n  char StringPool_str382[sizeof(\"update-animation\")];\n  char StringPool_str383[sizeof(\"event-through\")];\n  char StringPool_str386[sizeof(\"tag\")];\n  char StringPool_str388[sizeof(\"visibility\")];\n  char StringPool_str391[sizeof(\"background-color\")];\n  char StringPool_str397[sizeof(\"initial-scroll-offset\")];\n  char StringPool_str399[sizeof(\"text-stroke-width\")];\n  char StringPool_str401[sizeof(\"clip-path\")];\n  char StringPool_str404[sizeof(\"text-single-line-vertical-align\")];\n  char StringPool_str405[sizeof(\"opacity\")];\n  char StringPool_str406[sizeof(\"scroll-bar-thumb-width\")];\n  char StringPool_str411[sizeof(\"scroll-bar-thumb-min-length\")];\n  char StringPool_str421[sizeof(\"lower-threshold\")];\n  char StringPool_str422[sizeof(\"line-spacing\")];\n  char StringPool_str423[sizeof(\"enable-exposure-ui-margin\")];\n  char StringPool_str430[sizeof(\"lower-threshold-item-count\")];\n  char StringPool_str431[sizeof(\"offset-rotate\")];\n  char StringPool_str433[sizeof(\"scroll-monitor-tag\")];\n  char StringPool_str435[sizeof(\"letter-spacing\")];\n  char StringPool_str438[sizeof(\"full-screen\")];\n  char StringPool_str443[sizeof(\"experimental-batch-render-strategy\")];\n  char StringPool_str445[sizeof(\"text-selection\")];\n  char StringPool_str453[sizeof(\"font-family\")];\n  char StringPool_str455[sizeof(\"focus-index\")];\n  char StringPool_str459[sizeof(\"background-position\")];\n  char StringPool_str462[sizeof(\"accessibility-label\")];\n  char StringPool_str469[sizeof(\"scroll-bar-auto-hide-delay\")];\n  char StringPool_str471[sizeof(\"animation-velocity\")];\n  char StringPool_str474[sizeof(\"image-rendering\")];\n  char StringPool_str484[sizeof(\"border-right-width\")];\n  char StringPool_str493[sizeof(\"experimental-update-sticky-for-diff\")];\n  char StringPool_str495[sizeof(\"level\")];\n  char StringPool_str496[sizeof(\"block-native-event\")];\n  char StringPool_str509[sizeof(\"offset-distance\")];\n  char StringPool_str513[sizeof(\"upper-threshold\")];\n  char StringPool_str517[sizeof(\"vertical-align\")];\n  char StringPool_str520[sizeof(\"next-focus-down\")];\n  char StringPool_str522[sizeof(\"upper-threshold-item-count\")];\n  char StringPool_str523[sizeof(\"initial-scroll-index\")];\n  char StringPool_str528[sizeof(\"need-visible-item-info\")];\n  char StringPool_str529[sizeof(\"custom-context-menu\")];\n  char StringPool_str535[sizeof(\"defer-src-invalidation\")];\n  char StringPool_str563[sizeof(\"fullscreen-mode\")];\n  char StringPool_str573[sizeof(\"outline-width\")];\n  char StringPool_str587[sizeof(\"offset-path\")];\n  char StringPool_str596[sizeof(\"update-list-info\")];\n  char StringPool_str613[sizeof(\"-x-app-region\")];\n  char StringPool_str671[sizeof(\"-x-auto-font-size\")];\n  char StringPool_str679[sizeof(\"focus-smooth-scroll\")];\n  char StringPool_str687[sizeof(\"-x-auto-font-size-preset-sizes\")];\n};\nstatic const struct StringPool_t StringPool_contents = {\n    \"speed\",\n    \"dataset\",\n    \"mode\",\n    \"disabled\",\n    \"mask-size\",\n    \"mask-image\",\n    \"mask-repeat\",\n    \"scroll-left\",\n    \"type\",\n    \"scroll-to-id\",\n    \"preload\",\n    \"scroll-enable\",\n    \"exposure-id\",\n    \"repeat\",\n    \"mask-clip\",\n    \"perspective\",\n    \"markdown-effect\",\n    \"scroll-top\",\n    \"auto-size\",\n    \"adjust-mode\",\n    \"scroll-forward-mode\",\n    \"transform\",\n    \"exposure-screen-margin-left\",\n    \"exposure-screen-margin-right\",\n    \"bottom-inset\",\n    \"exposure-ui-margin-left\",\n    \"exposure-ui-margin-right\",\n    \"scroll-bar-track-color\",\n    \"exposure-screen-margin-bottom\",\n    \"border-style\",\n    \"border-left\",\n    \"border-right\",\n    \"exposure-ui-margin-bottom\",\n    \"border\",\n    \"auto-save-last-child\",\n    \"exposure-screen-margin-top\",\n    \"border-color\",\n    \"scroll-backward-mode\",\n    \"placeholder\",\n    \"exposure-ui-margin-top\",\n    \"border-top-style\",\n    \"border-radius\",\n    \"border-left-style\",\n    \"word-break\",\n    \"name\",\n    \"border-top\",\n    \"border-top-left-radius\",\n    \"prev-scrollable\",\n    \"border-bottom-style\",\n    \"border-bottom\",\n    \"min-height\",\n    \"mask-origin\",\n    \"tint-color\",\n    \"direction\",\n    \"border-bottom-left-radius\",\n    \"border-bottom-right-radius\",\n    \"border-bottom-color\",\n    \"needs-visible-cells\",\n    \"richtype\",\n    \"itemkeys\",\n    \"scroll-bar-enable\",\n    \"scroll-direction\",\n    \"scroll-orientation\",\n    \"exposure-area\",\n    \"exposure-scene\",\n    \"cursor\",\n    \"idSelector\",\n    \"transition\",\n    \"caret-color\",\n    \"cap-insets\",\n    \"edit-on-focus\",\n    \"sticky-offset\",\n    \"item-snap\",\n    \"text\",\n    \"select\",\n    \"list-type\",\n    \"text-maxline\",\n    \"text-indent\",\n    \"typewriter-height-transition-duration\",\n    \"maxlines\",\n    \"max-height\",\n    \"preferred-focus-child\",\n    \"scroll-x\",\n    \"enable-new-animator\",\n    \"background\",\n    \"align-focus\",\n    \"text-gradient\",\n    \"enable-insert-platform-view-operation\",\n    \"white-space\",\n    \"text-mark-attachments\",\n    \"scroll-y\",\n    \"background-repeat\",\n    \"scroll-to-index\",\n    \"markdown-style\",\n    \"background-image\",\n    \"image-transition-style\",\n    \"auto-fix\",\n    \"content\",\n    \"allow-escape\",\n    \"controls\",\n    \"confirm-type\",\n    \"smart-scroll\",\n    \"content-id\",\n    \"mask-position\",\n    \"send-composing-input\",\n    \"animation-type\",\n    \"border-left-color\",\n    \"text-shadow\",\n    \"placeholder-color\",\n    \"content-range\",\n    \"autoplay\",\n    \"src\",\n    \"ignore-focus\",\n    \"border-top-right-radius\",\n    \"outline\",\n    \"hit-slop\",\n    \"consume-slide-event\",\n    \"enable-nested-scroll\",\n    \"readonly\",\n    \"enable-scrollbar\",\n    \"typewriter-dynamic-height\",\n    \"custom-text-selection\",\n    \"animation-frame-rate\",\n    \"skip-redirection\",\n    \"outline-style\",\n    \"visible\",\n    \"scroll-bar-thumb-radius\",\n    \"text-align\",\n    \"line-height\",\n    \"scroll-bar-thumb-color\",\n    \"animation\",\n    \"scroll-bar-thumb-hover-color\",\n    \"scroll-bar-thumb-active-color\",\n    \"next-focus-left\",\n    \"box-shadow\",\n    \"outline-color\",\n    \"cap-insets-scale\",\n    \"border-width\",\n    \"column-count\",\n    \"color\",\n    \"downsampling\",\n    \"z-index\",\n    \"overflow\",\n    \"transform-origin\",\n    \"enable-font-scaling\",\n    \"show-soft-input-onfocus\",\n    \"text-overflow\",\n    \"scroll-event-throttle\",\n    \"drop-shadow\",\n    \"react-ref\",\n    \"background-origin\",\n    \"enable-report-info\",\n    \"item-key\",\n    \"sticky\",\n    \"border-bottom-width\",\n    \"text-stroke-color\",\n    \"background-size\",\n    \"next-focus-up\",\n    \"uidisappear\",\n    \"next-scrollable\",\n    \"experimental-recycle-sticky-item\",\n    \"font-size\",\n    \"font-style\",\n    \"border-top-color\",\n    \"font-weight\",\n    \"placeholder-font-size\",\n    \"list-main-axis-gap\",\n    \"user-interaction-enabled\",\n    \"placeholder-font-weight\",\n    \"next-focus-fallback\",\n    \"border-right-style\",\n    \"scroll-bar-auto-hide\",\n    \"scroll-without-focus\",\n    \"prevent-loading-on-list-scroll\",\n    \"focus\",\n    \"focusable\",\n    \"border-left-width\",\n    \"initial-animation-step\",\n    \"bounce\",\n    \"border-right-color\",\n    \"bounces\",\n    \"uiappear\",\n    \"blur-radius\",\n    \"accessibility-element\",\n    \"scroll-bar-width\",\n    \"accessibility-elements\",\n    \"list-cross-axis-gap\",\n    \"maxlength\",\n    \"background-clip\",\n    \"vertical-orientation\",\n    \"enable-scroll\",\n    \"allow-break-around-punctuation\",\n    \"text-maxlength\",\n    \"use-soft-keyboard\",\n    \"content-complete\",\n    \"overflow-x\",\n    \"loop-count\",\n    \"image-config\",\n    \"low-quality\",\n    \"text-decoration\",\n    \"next-focus-right\",\n    \"value\",\n    \"enable-exposure-ui-clip\",\n    \"filter\",\n    \"overflow-y\",\n    \"loop\",\n    \"intersection-observers\",\n    \"list-container-info\",\n    \"border-top-width\",\n    \"update-animation\",\n    \"event-through\",\n    \"tag\",\n    \"visibility\",\n    \"background-color\",\n    \"initial-scroll-offset\",\n    \"text-stroke-width\",\n    \"clip-path\",\n    \"text-single-line-vertical-align\",\n    \"opacity\",\n    \"scroll-bar-thumb-width\",\n    \"scroll-bar-thumb-min-length\",\n    \"lower-threshold\",\n    \"line-spacing\",\n    \"enable-exposure-ui-margin\",\n    \"lower-threshold-item-count\",\n    \"offset-rotate\",\n    \"scroll-monitor-tag\",\n    \"letter-spacing\",\n    \"full-screen\",\n    \"experimental-batch-render-strategy\",\n    \"text-selection\",\n    \"font-family\",\n    \"focus-index\",\n    \"background-position\",\n    \"accessibility-label\",\n    \"scroll-bar-auto-hide-delay\",\n    \"animation-velocity\",\n    \"image-rendering\",\n    \"border-right-width\",\n    \"experimental-update-sticky-for-diff\",\n    \"level\",\n    \"block-native-event\",\n    \"offset-distance\",\n    \"upper-threshold\",\n    \"vertical-align\",\n    \"next-focus-down\",\n    \"upper-threshold-item-count\",\n    \"initial-scroll-index\",\n    \"need-visible-item-info\",\n    \"custom-context-menu\",\n    \"defer-src-invalidation\",\n    \"fullscreen-mode\",\n    \"outline-width\",\n    \"offset-path\",\n    \"update-list-info\",\n    \"-x-app-region\",\n    \"-x-auto-font-size\",\n    \"focus-smooth-scroll\",\n    \"-x-auto-font-size-preset-sizes\"};\n#define StringPool ((const char *)&StringPool_contents)\n\nstatic const struct TokenValue wordlist[] = {\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str39), (short)KeywordID::kSpeed},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str42),\n     (short)KeywordID::kDataset},\n    {offsetof(struct StringPool_t, StringPool_str43), (short)KeywordID::kMode},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str46),\n     (short)KeywordID::kDisabled},\n    {offsetof(struct StringPool_t, StringPool_str47),\n     (short)KeywordID::kMaskSize},\n    {offsetof(struct StringPool_t, StringPool_str48),\n     (short)KeywordID::kMaskImage},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str51),\n     (short)KeywordID::kMaskRepeat},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str54),\n     (short)KeywordID::kScrollLeft},\n    {offsetof(struct StringPool_t, StringPool_str55), (short)KeywordID::kType},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str57),\n     (short)KeywordID::kScrollToId},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str60),\n     (short)KeywordID::kPreload},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str63),\n     (short)KeywordID::kScrollEnable},\n    {offsetof(struct StringPool_t, StringPool_str64),\n     (short)KeywordID::kExposureId},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str68),\n     (short)KeywordID::kRepeat},\n    {offsetof(struct StringPool_t, StringPool_str69),\n     (short)KeywordID::kMaskClip},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str71),\n     (short)KeywordID::kPerspective},\n    {offsetof(struct StringPool_t, StringPool_str72),\n     (short)KeywordID::kMarkdownEffect},\n    {offsetof(struct StringPool_t, StringPool_str73),\n     (short)KeywordID::kScrollTop},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str76),\n     (short)KeywordID::kAutoSize},\n    {offsetof(struct StringPool_t, StringPool_str77),\n     (short)KeywordID::kAdjustMode},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str80),\n     (short)KeywordID::kScrollForwardMode},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str84),\n     (short)KeywordID::kTransform},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str87),\n     (short)KeywordID::kExposureScreenMarginLeft},\n    {offsetof(struct StringPool_t, StringPool_str88),\n     (short)KeywordID::kExposureScreenMarginRight},\n    {offsetof(struct StringPool_t, StringPool_str89),\n     (short)KeywordID::kBottomInset},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str91),\n     (short)KeywordID::kExposureUiMarginLeft},\n    {offsetof(struct StringPool_t, StringPool_str92),\n     (short)KeywordID::kExposureUiMarginRight},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str94),\n     (short)KeywordID::kScrollBarTrackColor},\n    {offsetof(struct StringPool_t, StringPool_str95),\n     (short)KeywordID::kExposureScreenMarginBottom},\n    {offsetof(struct StringPool_t, StringPool_str96),\n     (short)KeywordID::kBorderStyle},\n    {offsetof(struct StringPool_t, StringPool_str97),\n     (short)KeywordID::kBorderLeft},\n    {offsetof(struct StringPool_t, StringPool_str98),\n     (short)KeywordID::kBorderRight},\n    {offsetof(struct StringPool_t, StringPool_str99),\n     (short)KeywordID::kExposureUiMarginBottom},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str101),\n     (short)KeywordID::kBorder},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str103),\n     (short)KeywordID::kAutoSaveLastChild},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str106),\n     (short)KeywordID::kExposureScreenMarginTop},\n    {offsetof(struct StringPool_t, StringPool_str107),\n     (short)KeywordID::kBorderColor},\n    {offsetof(struct StringPool_t, StringPool_str108),\n     (short)KeywordID::kScrollBackwardMode},\n    {offsetof(struct StringPool_t, StringPool_str109),\n     (short)KeywordID::kPlaceholder},\n    {offsetof(struct StringPool_t, StringPool_str110),\n     (short)KeywordID::kExposureUiMarginTop},\n    {offsetof(struct StringPool_t, StringPool_str111),\n     (short)KeywordID::kBorderTopStyle},\n    {offsetof(struct StringPool_t, StringPool_str112),\n     (short)KeywordID::kBorderRadius},\n    {offsetof(struct StringPool_t, StringPool_str113),\n     (short)KeywordID::kBorderLeftStyle},\n    {offsetof(struct StringPool_t, StringPool_str114),\n     (short)KeywordID::kWordBreak},\n    {offsetof(struct StringPool_t, StringPool_str115), (short)KeywordID::kName},\n    {offsetof(struct StringPool_t, StringPool_str116),\n     (short)KeywordID::kBorderTop},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str118),\n     (short)KeywordID::kBorderTopLeftRadius},\n    {offsetof(struct StringPool_t, StringPool_str119),\n     (short)KeywordID::kPrevScrollable},\n    {offsetof(struct StringPool_t, StringPool_str120),\n     (short)KeywordID::kBorderBottomStyle},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str122),\n     (short)KeywordID::kBorderBottom},\n    {offsetof(struct StringPool_t, StringPool_str123),\n     (short)KeywordID::kMinHeight},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str125),\n     (short)KeywordID::kMaskOrigin},\n    {offsetof(struct StringPool_t, StringPool_str126),\n     (short)KeywordID::kTintColor},\n    {offsetof(struct StringPool_t, StringPool_str127),\n     (short)KeywordID::kDirection},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str129),\n     (short)KeywordID::kBorderBottomLeftRadius},\n    {offsetof(struct StringPool_t, StringPool_str130),\n     (short)KeywordID::kBorderBottomRightRadius},\n    {offsetof(struct StringPool_t, StringPool_str131),\n     (short)KeywordID::kBorderBottomColor},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str134),\n     (short)KeywordID::kNeedsVisibleCells},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str140),\n     (short)KeywordID::kRichtype},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str142),\n     (short)KeywordID::kItemkeys},\n    {offsetof(struct StringPool_t, StringPool_str143),\n     (short)KeywordID::kScrollBarEnable},\n    {offsetof(struct StringPool_t, StringPool_str144),\n     (short)KeywordID::kScrollDirection},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str146),\n     (short)KeywordID::kScrollOrientation},\n    {offsetof(struct StringPool_t, StringPool_str147),\n     (short)KeywordID::kExposureArea},\n    {offsetof(struct StringPool_t, StringPool_str148),\n     (short)KeywordID::kExposureScene},\n    {offsetof(struct StringPool_t, StringPool_str149),\n     (short)KeywordID::kCursor},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str152),\n     (short)KeywordID::kIdselector},\n    {offsetof(struct StringPool_t, StringPool_str153),\n     (short)KeywordID::kTransition},\n    {offsetof(struct StringPool_t, StringPool_str154),\n     (short)KeywordID::kCaretColor},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str156),\n     (short)KeywordID::kCapInsets},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str159),\n     (short)KeywordID::kEditOnFocus},\n    {offsetof(struct StringPool_t, StringPool_str160),\n     (short)KeywordID::kStickyOffset},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str162),\n     (short)KeywordID::kItemSnap},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str164), (short)KeywordID::kText},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str168),\n     (short)KeywordID::kSelect},\n    {offsetof(struct StringPool_t, StringPool_str169),\n     (short)KeywordID::kListType},\n    {offsetof(struct StringPool_t, StringPool_str170),\n     (short)KeywordID::kTextMaxline},\n    {offsetof(struct StringPool_t, StringPool_str171),\n     (short)KeywordID::kTextIndent},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str173),\n     (short)KeywordID::kTypewriterHeightTransitionDuration},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str175),\n     (short)KeywordID::kMaxlines},\n    {offsetof(struct StringPool_t, StringPool_str176),\n     (short)KeywordID::kMaxHeight},\n    {offsetof(struct StringPool_t, StringPool_str177),\n     (short)KeywordID::kPreferredFocusChild},\n    {offsetof(struct StringPool_t, StringPool_str178),\n     (short)KeywordID::kScrollX},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str180),\n     (short)KeywordID::kEnableNewAnimator},\n    {offsetof(struct StringPool_t, StringPool_str181),\n     (short)KeywordID::kBackground},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str183),\n     (short)KeywordID::kAlignFocus},\n    {offsetof(struct StringPool_t, StringPool_str184),\n     (short)KeywordID::kTextGradient},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str189),\n     (short)KeywordID::kEnableInsertPlatformViewOperation},\n    {offsetof(struct StringPool_t, StringPool_str190),\n     (short)KeywordID::kWhiteSpace},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str193),\n     (short)KeywordID::kTextMarkAttachments},\n    {offsetof(struct StringPool_t, StringPool_str194),\n     (short)KeywordID::kScrollY},\n    {offsetof(struct StringPool_t, StringPool_str195),\n     (short)KeywordID::kBackgroundRepeat},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str198),\n     (short)KeywordID::kScrollToIndex},\n    {offsetof(struct StringPool_t, StringPool_str199),\n     (short)KeywordID::kMarkdownStyle},\n    {offsetof(struct StringPool_t, StringPool_str200),\n     (short)KeywordID::kBackgroundImage},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str202),\n     (short)KeywordID::kImageTransitionStyle},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str204),\n     (short)KeywordID::kAutoFix},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str206),\n     (short)KeywordID::kContent},\n    {offsetof(struct StringPool_t, StringPool_str207),\n     (short)KeywordID::kAllowEscape},\n    {offsetof(struct StringPool_t, StringPool_str208),\n     (short)KeywordID::kControls},\n    {offsetof(struct StringPool_t, StringPool_str209),\n     (short)KeywordID::kConfirmType},\n    {offsetof(struct StringPool_t, StringPool_str210),\n     (short)KeywordID::kSmartScroll},\n    {offsetof(struct StringPool_t, StringPool_str211),\n     (short)KeywordID::kContentId},\n    {offsetof(struct StringPool_t, StringPool_str212),\n     (short)KeywordID::kMaskPosition},\n    {offsetof(struct StringPool_t, StringPool_str213),\n     (short)KeywordID::kSendComposingInput},\n    {offsetof(struct StringPool_t, StringPool_str214),\n     (short)KeywordID::kAnimationType},\n    {offsetof(struct StringPool_t, StringPool_str215),\n     (short)KeywordID::kBorderLeftColor},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str217),\n     (short)KeywordID::kTextShadow},\n    {offsetof(struct StringPool_t, StringPool_str218),\n     (short)KeywordID::kPlaceholderColor},\n    {offsetof(struct StringPool_t, StringPool_str219),\n     (short)KeywordID::kContentRange},\n    {offsetof(struct StringPool_t, StringPool_str220),\n     (short)KeywordID::kAutoplay},\n    {offsetof(struct StringPool_t, StringPool_str221), (short)KeywordID::kSrc},\n    {offsetof(struct StringPool_t, StringPool_str222),\n     (short)KeywordID::kIgnoreFocus},\n    {offsetof(struct StringPool_t, StringPool_str223),\n     (short)KeywordID::kBorderTopRightRadius},\n    {offsetof(struct StringPool_t, StringPool_str224),\n     (short)KeywordID::kOutline},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str226),\n     (short)KeywordID::kHitSlop},\n    {offsetof(struct StringPool_t, StringPool_str227),\n     (short)KeywordID::kConsumeSlideEvent},\n    {offsetof(struct StringPool_t, StringPool_str228),\n     (short)KeywordID::kEnableNestedScroll},\n    {offsetof(struct StringPool_t, StringPool_str229),\n     (short)KeywordID::kReadonly},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str231),\n     (short)KeywordID::kEnableScrollbar},\n    {offsetof(struct StringPool_t, StringPool_str232),\n     (short)KeywordID::kTypewriterDynamicHeight},\n    {offsetof(struct StringPool_t, StringPool_str233),\n     (short)KeywordID::kCustomTextSelection},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str236),\n     (short)KeywordID::kAnimationFrameRate},\n    {offsetof(struct StringPool_t, StringPool_str237),\n     (short)KeywordID::kSkipRedirection},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str239),\n     (short)KeywordID::kOutlineStyle},\n    {offsetof(struct StringPool_t, StringPool_str240),\n     (short)KeywordID::kVisible},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str243),\n     (short)KeywordID::kScrollBarThumbRadius},\n    {offsetof(struct StringPool_t, StringPool_str244),\n     (short)KeywordID::kTextAlign},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str246),\n     (short)KeywordID::kLineHeight},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str250),\n     (short)KeywordID::kScrollBarThumbColor},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str254),\n     (short)KeywordID::kAnimation},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str256),\n     (short)KeywordID::kScrollBarThumbHoverColor},\n    {offsetof(struct StringPool_t, StringPool_str257),\n     (short)KeywordID::kScrollBarThumbActiveColor},\n    {offsetof(struct StringPool_t, StringPool_str258),\n     (short)KeywordID::kNextFocusLeft},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str260),\n     (short)KeywordID::kBoxShadow},\n    {offsetof(struct StringPool_t, StringPool_str261),\n     (short)KeywordID::kOutlineColor},\n    {offsetof(struct StringPool_t, StringPool_str262),\n     (short)KeywordID::kCapInsetsScale},\n    {offsetof(struct StringPool_t, StringPool_str263),\n     (short)KeywordID::kBorderWidth},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str265),\n     (short)KeywordID::kColumnCount},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str267),\n     (short)KeywordID::kColor},\n    {offsetof(struct StringPool_t, StringPool_str268),\n     (short)KeywordID::kDownsampling},\n    {offsetof(struct StringPool_t, StringPool_str269),\n     (short)KeywordID::kZIndex},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str271),\n     (short)KeywordID::kOverflow},\n    {offsetof(struct StringPool_t, StringPool_str272),\n     (short)KeywordID::kTransformOrigin},\n    {offsetof(struct StringPool_t, StringPool_str273),\n     (short)KeywordID::kEnableFontScaling},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str275),\n     (short)KeywordID::kShowSoftInputOnfocus},\n    {offsetof(struct StringPool_t, StringPool_str276),\n     (short)KeywordID::kTextOverflow},\n    {offsetof(struct StringPool_t, StringPool_str277),\n     (short)KeywordID::kScrollEventThrottle},\n    {offsetof(struct StringPool_t, StringPool_str278),\n     (short)KeywordID::kDropShadow},\n    {offsetof(struct StringPool_t, StringPool_str279),\n     (short)KeywordID::kReactRef},\n    {offsetof(struct StringPool_t, StringPool_str280),\n     (short)KeywordID::kBackgroundOrigin},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str282),\n     (short)KeywordID::kEnableReportInfo},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str284),\n     (short)KeywordID::kItemKey},\n    {offsetof(struct StringPool_t, StringPool_str285),\n     (short)KeywordID::kSticky},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str287),\n     (short)KeywordID::kBorderBottomWidth},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str289),\n     (short)KeywordID::kTextStrokeColor},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str295),\n     (short)KeywordID::kBackgroundSize},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str298),\n     (short)KeywordID::kNextFocusUp},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str301),\n     (short)KeywordID::kUidisappear},\n    {offsetof(struct StringPool_t, StringPool_str302),\n     (short)KeywordID::kNextScrollable},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str304),\n     (short)KeywordID::kExperimentalRecycleStickyItem},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str306),\n     (short)KeywordID::kFontSize},\n    {offsetof(struct StringPool_t, StringPool_str307),\n     (short)KeywordID::kFontStyle},\n    {offsetof(struct StringPool_t, StringPool_str308),\n     (short)KeywordID::kBorderTopColor},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str310),\n     (short)KeywordID::kFontWeight},\n    {offsetof(struct StringPool_t, StringPool_str311),\n     (short)KeywordID::kPlaceholderFontSize},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str313),\n     (short)KeywordID::kListMainAxisGap},\n    {offsetof(struct StringPool_t, StringPool_str314),\n     (short)KeywordID::kUserInteractionEnabled},\n    {offsetof(struct StringPool_t, StringPool_str315),\n     (short)KeywordID::kPlaceholderFontWeight},\n    {offsetof(struct StringPool_t, StringPool_str316),\n     (short)KeywordID::kNextFocusFallback},\n    {offsetof(struct StringPool_t, StringPool_str317),\n     (short)KeywordID::kBorderRightStyle},\n    {offsetof(struct StringPool_t, StringPool_str318),\n     (short)KeywordID::kScrollBarAutoHide},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str321),\n     (short)KeywordID::kScrollWithoutFocus},\n    {offsetof(struct StringPool_t, StringPool_str322),\n     (short)KeywordID::kPreventLoadingOnListScroll},\n    {offsetof(struct StringPool_t, StringPool_str323),\n     (short)KeywordID::kFocus},\n    {offsetof(struct StringPool_t, StringPool_str324),\n     (short)KeywordID::kFocusable},\n    {offsetof(struct StringPool_t, StringPool_str325),\n     (short)KeywordID::kBorderLeftWidth},\n    {offsetof(struct StringPool_t, StringPool_str326),\n     (short)KeywordID::kInitialAnimationStep},\n    {offsetof(struct StringPool_t, StringPool_str327),\n     (short)KeywordID::kBounce},\n    {offsetof(struct StringPool_t, StringPool_str328),\n     (short)KeywordID::kBorderRightColor},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str331),\n     (short)KeywordID::kBounces},\n    {offsetof(struct StringPool_t, StringPool_str332),\n     (short)KeywordID::kUiappear},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str335),\n     (short)KeywordID::kBlurRadius},\n    {offsetof(struct StringPool_t, StringPool_str336),\n     (short)KeywordID::kAccessibilityElement},\n    {offsetof(struct StringPool_t, StringPool_str337),\n     (short)KeywordID::kScrollBarWidth},\n    {offsetof(struct StringPool_t, StringPool_str338),\n     (short)KeywordID::kAccessibilityElements},\n    {offsetof(struct StringPool_t, StringPool_str339),\n     (short)KeywordID::kListCrossAxisGap},\n    {offsetof(struct StringPool_t, StringPool_str340),\n     (short)KeywordID::kMaxlength},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str343),\n     (short)KeywordID::kBackgroundClip},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str346),\n     (short)KeywordID::kVerticalOrientation},\n    {offsetof(struct StringPool_t, StringPool_str347),\n     (short)KeywordID::kEnableScroll},\n    {offsetof(struct StringPool_t, StringPool_str348),\n     (short)KeywordID::kAllowBreakAroundPunctuation},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str350),\n     (short)KeywordID::kTextMaxlength},\n    {offsetof(struct StringPool_t, StringPool_str351),\n     (short)KeywordID::kUseSoftKeyboard},\n    {offsetof(struct StringPool_t, StringPool_str352),\n     (short)KeywordID::kContentComplete},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str354),\n     (short)KeywordID::kOverflowX},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str357),\n     (short)KeywordID::kLoopCount},\n    {offsetof(struct StringPool_t, StringPool_str358),\n     (short)KeywordID::kImageConfig},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str361),\n     (short)KeywordID::kLowQuality},\n    {offsetof(struct StringPool_t, StringPool_str362),\n     (short)KeywordID::kTextDecoration},\n    {offsetof(struct StringPool_t, StringPool_str363),\n     (short)KeywordID::kNextFocusRight},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str365),\n     (short)KeywordID::kValue},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str367),\n     (short)KeywordID::kEnableExposureUiClip},\n    {offsetof(struct StringPool_t, StringPool_str368),\n     (short)KeywordID::kFilter},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str370),\n     (short)KeywordID::kOverflowY},\n    {offsetof(struct StringPool_t, StringPool_str371), (short)KeywordID::kLoop},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str373),\n     (short)KeywordID::kIntersectionObservers},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str376),\n     (short)KeywordID::kListContainerInfo},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str380),\n     (short)KeywordID::kBorderTopWidth},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str382),\n     (short)KeywordID::kUpdateAnimation},\n    {offsetof(struct StringPool_t, StringPool_str383),\n     (short)KeywordID::kEventThrough},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str386), (short)KeywordID::kTag},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str388),\n     (short)KeywordID::kVisibility},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str391),\n     (short)KeywordID::kBackgroundColor},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str397),\n     (short)KeywordID::kInitialScrollOffset},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str399),\n     (short)KeywordID::kTextStrokeWidth},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str401),\n     (short)KeywordID::kClipPath},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str404),\n     (short)KeywordID::kTextSingleLineVerticalAlign},\n    {offsetof(struct StringPool_t, StringPool_str405),\n     (short)KeywordID::kOpacity},\n    {offsetof(struct StringPool_t, StringPool_str406),\n     (short)KeywordID::kScrollBarThumbWidth},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str411),\n     (short)KeywordID::kScrollBarThumbMinLength},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str421),\n     (short)KeywordID::kLowerThreshold},\n    {offsetof(struct StringPool_t, StringPool_str422),\n     (short)KeywordID::kLineSpacing},\n    {offsetof(struct StringPool_t, StringPool_str423),\n     (short)KeywordID::kEnableExposureUiMargin},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str430),\n     (short)KeywordID::kLowerThresholdItemCount},\n    {offsetof(struct StringPool_t, StringPool_str431),\n     (short)KeywordID::kOffsetRotate},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str433),\n     (short)KeywordID::kScrollMonitorTag},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str435),\n     (short)KeywordID::kLetterSpacing},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str438),\n     (short)KeywordID::kFullScreen},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str443),\n     (short)KeywordID::kExperimentalBatchRenderStrategy},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str445),\n     (short)KeywordID::kTextSelection},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str453),\n     (short)KeywordID::kFontFamily},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str455),\n     (short)KeywordID::kFocusIndex},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str459),\n     (short)KeywordID::kBackgroundPosition},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str462),\n     (short)KeywordID::kAccessibilityLabel},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str469),\n     (short)KeywordID::kScrollBarAutoHideDelay},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str471),\n     (short)KeywordID::kAnimationVelocity},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str474),\n     (short)KeywordID::kImageRendering},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str484),\n     (short)KeywordID::kBorderRightWidth},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str493),\n     (short)KeywordID::kExperimentalUpdateStickyForDiff},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str495),\n     (short)KeywordID::kLevel},\n    {offsetof(struct StringPool_t, StringPool_str496),\n     (short)KeywordID::kBlockNativeEvent},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str509),\n     (short)KeywordID::kOffsetDistance},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str513),\n     (short)KeywordID::kUpperThreshold},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str517),\n     (short)KeywordID::kVerticalAlign},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str520),\n     (short)KeywordID::kNextFocusDown},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str522),\n     (short)KeywordID::kUpperThresholdItemCount},\n    {offsetof(struct StringPool_t, StringPool_str523),\n     (short)KeywordID::kInitialScrollIndex},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str528),\n     (short)KeywordID::kNeedVisibleItemInfo},\n    {offsetof(struct StringPool_t, StringPool_str529),\n     (short)KeywordID::kCustomContextMenu},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str535),\n     (short)KeywordID::kDeferSrcInvalidation},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str563),\n     (short)KeywordID::kFullscreenMode},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str573),\n     (short)KeywordID::kOutlineWidth},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str587),\n     (short)KeywordID::kOffsetPath},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str596),\n     (short)KeywordID::kUpdateListInfo},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str613),\n     (short)KeywordID::kXAppRegion},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str671),\n     (short)KeywordID::kXAutoFontSize},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str679),\n     (short)KeywordID::kFocusSmoothScroll},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {-1},\n    {offsetof(struct StringPool_t, StringPool_str687),\n     (short)KeywordID::kXAutoFontSizePresetSizes}};\n\nconst struct TokenValue *KeywordHash::find(const char *str, unsigned int len) {\n  if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) {\n    unsigned int key = hash(str, len);\n\n    if (key <= MAX_HASH_VALUE) {\n      int o = wordlist[key].name;\n      if (o >= 0) {\n        const char *s = o + StringPool;\n\n        if (*str == *s && !strcmp(str + 1, s + 1)) return &wordlist[key];\n      }\n    }\n  }\n  return 0;\n}\n\nstatic_assert(sizeof(StringPool_contents) < 32767);\n\nKeywordID GetKeywordID(const std::string &str) {\n  auto *tk = KeywordHash::find(str.c_str(), str.size());\n  return tk ? static_cast<KeywordID>(tk->id) : KeywordID::kInvalid;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/keywords.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n//\n// DO NOT MODIFY THIS FILE! AUTO GENERATED BY clay/tools/make_keywords.py\n//\n\n#ifndef CLAY_UI_COMPONENT_KEYWORDS_H_\n#define CLAY_UI_COMPONENT_KEYWORDS_H_\n\n#include <string>\n\nnamespace clay {\n\nenum class KeywordID {\n  kInvalid = -1,\n  kBackgroundColor,\n  kBorderLeftColor,\n  kBorderRightColor,\n  kBorderTopColor,\n  kBorderBottomColor,\n  kBorderRadius,\n  kBorderTopLeftRadius,\n  kBorderBottomLeftRadius,\n  kBorderTopRightRadius,\n  kBorderBottomRightRadius,\n  kBorderWidth,\n  kBorderLeftWidth,\n  kBorderRightWidth,\n  kBorderTopWidth,\n  kBorderBottomWidth,\n  kItemSnap,\n  kOpacity,\n  kBackground,\n  kBorderColor,\n  kTransform,\n  kBorderStyle,\n  kBoxShadow,\n  kTransformOrigin,\n  kPerspective,\n  kBackgroundImage,\n  kBackgroundPosition,\n  kBackgroundOrigin,\n  kBackgroundRepeat,\n  kBackgroundSize,\n  kBackgroundClip,\n  kMaskImage,\n  kMaskPosition,\n  kMaskOrigin,\n  kMaskRepeat,\n  kMaskSize,\n  kMaskClip,\n  kBorder,\n  kVisibility,\n  kBorderRight,\n  kBorderLeft,\n  kBorderTop,\n  kBorderBottom,\n  kBorderLeftStyle,\n  kBorderRightStyle,\n  kBorderTopStyle,\n  kBorderBottomStyle,\n  kOutline,\n  kOutlineColor,\n  kOutlineStyle,\n  kOutlineWidth,\n  kAnimation,\n  kTransition,\n  kOverflow,\n  kOverflowX,\n  kOverflowY,\n  kCursor,\n  kColor,\n  kFilter,\n  kImageRendering,\n  kImageConfig,\n  kClipPath,\n  kAccessibilityElement,\n  kAccessibilityLabel,\n  kAccessibilityElements,\n  kText,\n  kSrc,\n  kPreventLoadingOnListScroll,\n  kMode,\n  kRepeat,\n  kPlaceholder,\n  kBlurRadius,\n  kDropShadow,\n  kDownsampling,\n  kFontSize,\n  kTextGradient,\n  kTextOverflow,\n  kFontWeight,\n  kFontStyle,\n  kLineHeight,\n  kLineSpacing,\n  kWhiteSpace,\n  kTextMaxline,\n  kMaxlines,\n  kTextMaxlength,\n  kLetterSpacing,\n  kFontFamily,\n  kTextAlign,\n  kDirection,\n  kTextDecoration,\n  kTextShadow,\n  kTextSelection,\n  kCustomContextMenu,\n  kCustomTextSelection,\n  kTextStrokeColor,\n  kTextStrokeWidth,\n  kTextIndent,\n  kWordBreak,\n  kVerticalAlign,\n  kTextSingleLineVerticalAlign,\n  kEnableFontScaling,\n  kXAutoFontSize,\n  kXAutoFontSizePresetSizes,\n  kXAppRegion,\n  kLoopCount,\n  kAutoplay,\n  kScrollX,\n  kScrollY,\n  kBounce,\n  kBounces,\n  kScrollForwardMode,\n  kScrollBackwardMode,\n  kPrevScrollable,\n  kNextScrollable,\n  kEnableScroll,\n  kScrollMonitorTag,\n  kListContainerInfo,\n  kItemkeys,\n  kEnableNestedScroll,\n  kEnableNewAnimator,\n  kScrollBarEnable,\n  kEnableScrollbar,\n  kScrollEnable,\n  kScrollBarWidth,\n  kScrollBarThumbWidth,\n  kScrollBarThumbMinLength,\n  kScrollBarThumbRadius,\n  kScrollBarThumbColor,\n  kScrollBarThumbActiveColor,\n  kScrollBarThumbHoverColor,\n  kScrollBarTrackColor,\n  kScrollBarAutoHide,\n  kScrollBarAutoHideDelay,\n  kScrollTop,\n  kScrollLeft,\n  kScrollToIndex,\n  kScrollToId,\n  kLowerThreshold,\n  kUpperThreshold,\n  kLowerThresholdItemCount,\n  kUpperThresholdItemCount,\n  kScrollEventThrottle,\n  kListType,\n  kListCrossAxisGap,\n  kListMainAxisGap,\n  kUpdateAnimation,\n  kUpdateListInfo,\n  kColumnCount,\n  kSticky,\n  kStickyOffset,\n  kFocusSmoothScroll,\n  kScrollWithoutFocus,\n  kScrollDirection,\n  kScrollOrientation,\n  kVerticalOrientation,\n  kIdselector,\n  kReactRef,\n  kNeedsVisibleCells,\n  kNeedVisibleItemInfo,\n  kItemKey,\n  kContent,\n  kContentId,\n  kContentComplete,\n  kSkipRedirection,\n  kVisible,\n  kFullScreen,\n  kIntersectionObservers,\n  kFocusIndex,\n  kAlignFocus,\n  kName,\n  kImageTransitionStyle,\n  kCapInsets,\n  kCapInsetsScale,\n  kDeferSrcInvalidation,\n  kAutoSize,\n  kMarkdownStyle,\n  kMarkdownEffect,\n  kAnimationType,\n  kAnimationVelocity,\n  kAnimationFrameRate,\n  kInitialAnimationStep,\n  kTypewriterDynamicHeight,\n  kTypewriterHeightTransitionDuration,\n  kAllowBreakAroundPunctuation,\n  kTextMarkAttachments,\n  kContentRange,\n  kShowSoftInputOnfocus,\n  kUseSoftKeyboard,\n  kValue,\n  kType,\n  kConfirmType,\n  kPlaceholderFontSize,\n  kPlaceholderFontWeight,\n  kPlaceholderColor,\n  kDisabled,\n  kMaxlength,\n  kFocus,\n  kIgnoreFocus,\n  kEditOnFocus,\n  kCaretColor,\n  kTintColor,\n  kEnableReportInfo,\n  kRichtype,\n  kFullscreenMode,\n  kSmartScroll,\n  kAdjustMode,\n  kAutoFix,\n  kBottomInset,\n  kFocusable,\n  kNextFocusUp,\n  kNextFocusDown,\n  kNextFocusLeft,\n  kNextFocusRight,\n  kNextFocusFallback,\n  kAllowEscape,\n  kAutoSaveLastChild,\n  kPreferredFocusChild,\n  kReadonly,\n  kSendComposingInput,\n  kMinHeight,\n  kMaxHeight,\n  kInitialScrollIndex,\n  kPreload,\n  kLoop,\n  kSpeed,\n  kControls,\n  kLowQuality,\n  kUserInteractionEnabled,\n  kBlockNativeEvent,\n  kConsumeSlideEvent,\n  kSelect,\n  kTag,\n  kDataset,\n  kUiappear,\n  kUidisappear,\n  kExposureScene,\n  kExposureId,\n  kExposureArea,\n  kEnableExposureUiMargin,\n  kEnableExposureUiClip,\n  kExposureUiMarginLeft,\n  kExposureUiMarginRight,\n  kExposureUiMarginTop,\n  kExposureUiMarginBottom,\n  kExposureScreenMarginLeft,\n  kExposureScreenMarginRight,\n  kExposureScreenMarginTop,\n  kExposureScreenMarginBottom,\n  kEventThrough,\n  kHitSlop,\n  kLevel,\n  kEnableInsertPlatformViewOperation,\n  kInitialScrollOffset,\n  kExperimentalBatchRenderStrategy,\n  kZIndex,\n  kExperimentalRecycleStickyItem,\n  kExperimentalUpdateStickyForDiff,\n  kOffsetPath,\n  kOffsetRotate,\n  kOffsetDistance\n};\n\nKeywordID GetKeywordID(const std::string &str);\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_KEYWORDS_H_\n"
  },
  {
    "path": "clay/ui/component/keywords.in",
    "content": "background-color\nborder-left-color\nborder-right-color\nborder-top-color\nborder-bottom-color\nborder-radius\nborder-top-left-radius\nborder-bottom-left-radius\nborder-top-right-radius\nborder-bottom-right-radius\nborder-width\nborder-left-width\nborder-right-width\nborder-top-width\nborder-bottom-width\nitem-snap\nopacity\nbackground\nborder-color\ntransform\nborder-style\nbox-shadow\ntransform-origin\nperspective\nbackground-image\nbackground-position\nbackground-origin\nbackground-repeat\nbackground-size\nbackground-clip\nmask-image\nmask-position\nmask-origin\nmask-repeat\nmask-size\nmask-clip\nborder\nvisibility\nborder-right\nborder-left\nborder-top\nborder-bottom\nborder-left-style\nborder-right-style\nborder-top-style\nborder-bottom-style\noutline\noutline-color\noutline-style\noutline-width\nanimation\ntransition\noverflow\noverflow-x\noverflow-y\ncursor\ncolor\nfilter\nimage-rendering\nimage-config\nclip-path\naccessibility-element\naccessibility-label\naccessibility-elements\ntext\nsrc\nprevent-loading-on-list-scroll\nmode\nrepeat\nplaceholder\nblur-radius\ndrop-shadow\ndownsampling\nfont-size\ntext-gradient\ntext-overflow\nfont-weight\nfont-style\nline-height\nline-spacing\nwhite-space\ntext-maxline\nmaxlines\ntext-maxlength\nletter-spacing\nfont-family\ntext-align\ndirection\ntext-decoration\ntext-shadow\ntext-selection\ncustom-context-menu\ncustom-text-selection\ntext-stroke-color\ntext-stroke-width\ntext-indent\nword-break\nvertical-align\ntext-single-line-vertical-align\nenable-font-scaling\n-x-auto-font-size\n-x-auto-font-size-preset-sizes\n-x-app-region\nloop-count\nautoplay\nscroll-x\nscroll-y\nbounce\nbounces\nscroll-forward-mode\nscroll-backward-mode\nprev-scrollable\nnext-scrollable\nenable-scroll\nscroll-monitor-tag\nlist-container-info\nitemkeys\nenable-nested-scroll\nenable-new-animator\nscroll-bar-enable\nenable-scrollbar\nscroll-enable\nscroll-bar-width\nscroll-bar-thumb-width\nscroll-bar-thumb-min-length\nscroll-bar-thumb-radius\nscroll-bar-thumb-color\nscroll-bar-thumb-active-color\nscroll-bar-thumb-hover-color\nscroll-bar-track-color\nscroll-bar-auto-hide\nscroll-bar-auto-hide-delay\nscroll-top\nscroll-left\nscroll-to-index\nscroll-to-id\nlower-threshold\nupper-threshold\nlower-threshold-item-count\nupper-threshold-item-count\nscroll-event-throttle\nlist-type\nlist-cross-axis-gap\nlist-main-axis-gap\nupdate-animation\nupdate-list-info\ncolumn-count\nsticky\nsticky-offset\nfocus-smooth-scroll\nscroll-without-focus\nscroll-direction\nscroll-orientation\nvertical-orientation\nidSelector\nreact-ref\nneeds-visible-cells\nneed-visible-item-info\nitem-key\ncontent\ncontent-id\ncontent-complete\nskip-redirection\nvisible\nfull-screen\nintersection-observers\nfocus-index\nalign-focus\nname\nimage-transition-style\ncap-insets\ncap-insets-scale\ndefer-src-invalidation\nauto-size\nmarkdown-style\nmarkdown-effect\nanimation-type\nanimation-velocity\nanimation-frame-rate\ninitial-animation-step\ntypewriter-dynamic-height\ntypewriter-height-transition-duration\nallow-break-around-punctuation\ntext-mark-attachments\ncontent-range\nshow-soft-input-onfocus\nuse-soft-keyboard\nvalue\ntype\nconfirm-type\nplaceholder-font-size\nplaceholder-font-weight\nplaceholder-color\ndisabled\nmaxlength\nfocus\nignore-focus\nedit-on-focus\ncaret-color\ntint-color\nenable-report-info\nrichtype\nfullscreen-mode\nsmart-scroll\nadjust-mode\nauto-fix\nbottom-inset\nfocusable\nnext-focus-up\nnext-focus-down\nnext-focus-left\nnext-focus-right\nnext-focus-fallback\nallow-escape\nauto-save-last-child\npreferred-focus-child\nreadonly\nsend-composing-input\nmin-height\nmax-height\ninitial-scroll-index\npreload\nloop\nspeed\ncontrols\nlow-quality\nuser-interaction-enabled\nblock-native-event\nconsume-slide-event\nselect\ntag\ndataset\nuiappear\nuidisappear\nexposure-scene\nexposure-id\nexposure-area\nenable-exposure-ui-margin\nenable-exposure-ui-clip\nexposure-ui-margin-left\nexposure-ui-margin-right\nexposure-ui-margin-top\nexposure-ui-margin-bottom\nexposure-screen-margin-left\nexposure-screen-margin-right\nexposure-screen-margin-top\nexposure-screen-margin-bottom\nevent-through\nhit-slop\nlevel\nenable-insert-platform-view-operation\ninitial-scroll-offset\nexperimental-batch-render-strategy\nz-index\nexperimental-recycle-sticky-item\nexperimental-update-sticky-for-diff\noffset-path\noffset-rotate\noffset-distance\n"
  },
  {
    "path": "clay/ui/component/layout_controller.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/layout_controller.h\"\n\n#include <unordered_set>\n#include <utility>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nPreLayoutContext::PreLayoutContext() = default;\nPreLayoutContext::~PreLayoutContext() = default;\n\nLayoutContext::LayoutContext() = default;\nLayoutContext::~LayoutContext() = default;\n\nLayoutController::LayoutController() = default;\nLayoutController::~LayoutController() = default;\n\nvoid LayoutController::AddNeedLayout(fml::WeakPtr<BaseView> node_weak) {\n  if (node_weak.get() == nullptr) {\n    return;\n  }\n  nodes_needing_layout_.insert(node_weak);\n}\n\nvoid LayoutController::Layout() {\n  if (nodes_needing_layout_.empty()) {\n    return;\n  }\n  std::unordered_set<fml::WeakPtr<BaseView>, BaseViewWeakPtrHash> dirty_nodes;\n  dirty_nodes.swap(nodes_needing_layout_);\n  for (fml::WeakPtr<BaseView> layout_root_weak : dirty_nodes) {\n    if (layout_root_weak.get() == nullptr) {\n      continue;\n    }\n    layout_root_weak->Layout(nullptr);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/layout_controller.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LAYOUT_CONTROLLER_H_\n#define CLAY_UI_COMPONENT_LAYOUT_CONTROLLER_H_\n\n#include <unordered_set>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n\nnamespace clay {\n\nclass BaseView;\n\n// Context that for pre-layout phase. It should be overriden so that view can\n// collect information from subviews.\nclass PreLayoutContext {\n public:\n  PreLayoutContext();\n  virtual ~PreLayoutContext();\n};\n\n// Layout Context that should be overriden by views so that the parent can pass\n// some information to its subviews.\nclass LayoutContext {\n public:\n  LayoutContext();\n  virtual ~LayoutContext();\n};\n\nclass LayoutController {\n public:\n  LayoutController();\n  ~LayoutController();\n\n  // To simplify things, the view should call Invalidate() itself.\n  void Layout();\n  void AddNeedLayout(fml::WeakPtr<BaseView> node_weak);\n\n  bool RemoveDirtyNode(fml::WeakPtr<BaseView> node_weak) {\n    return nodes_needing_layout_.erase(node_weak) == 1;\n  }\n\n  bool HasDirtyNodes() const { return !nodes_needing_layout_.empty(); }\n\n  struct BaseViewWeakPtrHash {\n    size_t operator()(const fml::WeakPtr<clay::BaseView>& key) const {\n      auto base_view = key.get();\n      return std::hash<void*>{}((base_view));\n    }\n  };\n\n private:\n  std::unordered_set<fml::WeakPtr<BaseView>, BaseViewWeakPtrHash>\n      nodes_needing_layout_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LAYOUT_CONTROLLER_H_\n"
  },
  {
    "path": "clay/ui/component/list/base_list_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/base_list_view.h\"\n\n#include <algorithm>\n#include <cstdint>\n#include <limits>\n#include <memory>\n#include <optional>\n#include <sstream>\n#include <string>\n#include <utility>\n\n#include \"base/include/timer/time_utils.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/macros.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/keywords.h\"\n#include \"clay/ui/component/list/list_adapter.h\"\n#include \"clay/ui/component/list/list_adapter_updater.h\"\n#include \"clay/ui/component/list/list_children_helper.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_layout_manager.h\"\n#include \"clay/ui/component/list/list_layout_manager_linear.h\"\n#include \"clay/ui/component/list/list_orientation_helper.h\"\n#include \"clay/ui/component/list/list_recycler.h\"\n#include \"clay/ui/component/list/list_scroller.h\"\n#include \"clay/ui/component/list/lynx_list_adapter.h\"\n#include \"clay/ui/component/list/macros.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/event/focus_manager.h\"\n#include \"clay/ui/rendering/render_list.h\"\n\nnamespace clay {\n\nstatic bool sAllowListPrefetch = true;\n\nvoid SetAllowListPrefetch(bool allow) {\n  if (sAllowListPrefetch == allow) {\n    return;\n  }\n  sAllowListPrefetch = allow;\n}\n\nvoid LayoutPrefetchRegistry::AddPosition(int layout_position,\n                                         int pixel_distance) {\n  FML_DCHECK(pixel_distance > 0);\n  prefetch_item_infos_[layout_position] = pixel_distance;\n}\n\nvoid LayoutPrefetchRegistry::CollectPrefetchPostionFromView(\n    BaseListView* list_view, int prefetch_delta_x, int prefetch_delta_y) {\n  ListLayoutManager* layout_manager = list_view->GetLayoutManager();\n  if (layout_manager) {\n    layout_manager->CollectAdjacentPrefetchPositions(\n        prefetch_delta_x, prefetch_delta_y,\n        list_view->recycler_->GetCacheMaxLimit(), list_view->list_state_, this);\n  }\n}\n\nbool LayoutPrefetchRegistry::LastPrefetchIncludePosition(int position) {\n  return prefetch_item_infos_.count(position) > 0;\n}\n\nvoid LayoutPrefetchRegistry::ClearPrefetchPositions() {\n  prefetch_item_infos_.clear();\n}\n\nvoid BaseListView::ListPrefetchTask::Run() {\n  if (host_view_) {\n    TRACE_EVENT(\"clay\", \"ListPrefetchTask::Run\");\n    auto list_view = static_cast<BaseListView*>(host_view_.get());\n    ListRecycler* recycler = list_view->recycler_.get();\n    if (recycler->HasItemCached(id_)) {\n      return;\n    }\n    ListItemViewHolder* view_holder = recycler->GetItemForPosition(id_);\n    if (view_holder && view_holder->IsBound() && !view_holder->IsInvalid()) {\n      view_holder->AddFlags(ListItemViewHolder::Flag::kFlagPrefetch);\n      recycler->RecycleItem(view_holder);\n    }\n  }\n}\n\nBaseListView::BaseListView(int32_t id, std::string tag, PageView* page_view)\n    : BaseListView(id, id, tag, page_view) {}\n\nBaseListView::BaseListView(int32_t id, int32_t callback_id, std::string tag,\n                           PageView* page_view)\n    : WithTypeInfo(id, std::move(tag), std::make_unique<RenderList>(),\n                   page_view, ScrollDirection::kVertical, true),\n      callback_id_(callback_id),\n      children_helper_(std::make_unique<ListChildrenHelper>()),\n      callback_manager_(\n          std::make_unique<ListEventCallbackManager>(this, callback_id)) {\n  SetIsFocusScope();\n  GetFocusManager()->SetFirstFocusedNodeExpandRatio(1.f / 3.f);\n  if (sAllowListPrefetch) {\n    gap_task_collector_ = [weak_this = GetWeakPtr()]() {\n      if (weak_this) {\n        BaseListView* list_view = static_cast<BaseListView*>(weak_this.get());\n        list_view->StartPrefetch(-list_view->current_scroll_width_,\n                                 -list_view->current_scroll_height_);\n      }\n    };\n  }\n\n#if defined(OS_IOS) || defined(OS_OSX)\n  // Enable bounce effect by default on iOS and macOS.\n  SetOverscrollEnabled(true);\n#endif\n}\n\nBaseListView::~BaseListView() = default;\n\nvoid BaseListView::PostLowPriorityTask(std::function<void()> task) {\n  if (GetListScroller()->Scrolling()) {\n    pending_tasks_.push_back(task);\n    FlushTasks();\n  } else {\n    task();\n  }\n}\n\nvoid BaseListView::FlushTasks() {\n  if (GetListScroller()->Scrolling()) {\n    if (!task_timer_) {\n      task_timer_ =\n          std::make_unique<fml::Timer>(page_view()->GetTaskRunner(), false);\n    } else if (!task_timer_->Stopped()) {\n      return;\n    }\n    task_timer_->Start(\n        fml::TimeDelta::FromMilliseconds(100), [weak_this = GetWeakPtr()] {\n          if (weak_this) {\n            static_cast<BaseListView*>(weak_this.get())->FlushTasks();\n          }\n        });\n    return;\n  }\n\n  auto tasks = std::move(pending_tasks_);\n  for (auto task : tasks) {\n    task();\n  }\n}\n\nvoid BaseListView::SetWidth(float width) {\n  if (width_ == width) {\n    return;\n  }\n  BaseView::SetWidth(width);\n  MarkNeedsLayout();\n}\n\nvoid BaseListView::SetHeight(float height) {\n  if (height_ == height) {\n    return;\n  }\n  BaseView::SetHeight(height);\n  MarkNeedsLayout();\n}\n\nvoid BaseListView::SetAdapter(ListAdapter* adapter) {\n  adapter_ = adapter;\n  if (adapter_) {\n    adapter_->SetObserver(this);\n    recycler_ = std::make_unique<ListRecycler>(adapter_);\n    adapter_helper_ = std::make_unique<ListAdapterHelper>(this);\n    list_state_.structure_changed = true;\n    ProcessDataSetCompletelyChanged(false);\n    MarkNeedsLayout();\n  } else {\n    ResetListScroller();\n  }\n}\n\nvoid BaseListView::NotifyLowMemory() {\n  if (recycler_) {\n    // Delete prefetch cache\n    recycler_->MarkKnownViewsInvalid();\n  }\n}\n\nvoid BaseListView::SetLayoutManager(\n    std::unique_ptr<ListLayoutManager> layout_manager) {\n  // Reset to idle\n  SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n  if (layout_manager_ && recycler_) {\n    layout_manager_->RemoveAndRecycleAllViews(*recycler_);\n    recycler_->Clear();\n  }\n\n  ResetListScroller();\n\n  layout_manager_ = std::move(layout_manager);\n  layout_manager_->SetListView(this);\n  layout_manager_->SetChildrenHelper(children_helper_.get());\n  callback_manager_->SetLayoutManager(layout_manager_.get());\n  if (recycler_) {\n    recycler_->SetCacheMaxLimit(layout_span_count_);\n  }\n\n  DidUpdateAttributes();\n}\n\nvoid BaseListView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kListType) {\n    UpdateLayoutManager(attribute_utils::GetCString(value), layout_span_count_);\n  } else if (kw == KeywordID::kColumnCount) {\n    UpdateLayoutManager(layout_manager_type_,\n                        attribute_utils::GetNum(value, 1));\n  } else if (kw == KeywordID::kLowerThreshold) {\n    lower_threshold_pixel_ = attribute_utils::GetNum(value);\n  } else if (kw == KeywordID::kUpperThreshold) {\n    upper_threshold_pixel_ = attribute_utils::GetNum(value);\n  } else if (kw == KeywordID::kLowerThresholdItemCount) {\n    lower_threshold_item_count_ = attribute_utils::GetNum(value);\n  } else if (kw == KeywordID::kUpperThresholdItemCount) {\n    upper_threshold_item_count_ = attribute_utils::GetNum(value);\n  } else if (kw == KeywordID::kScrollEventThrottle) {\n    double throttle = 0.0;\n    if (!attribute_utils::TryGetNum(value, throttle)) {\n      return;\n    }\n    callback_manager_->SetScrollEventThrottle(\n        fml::TimeDelta::FromMilliseconds(static_cast<int>(throttle)));\n  } else if (kw == KeywordID::kNeedsVisibleCells) {\n    callback_manager_->SetNeedsVisibleCells(attribute_utils::GetBool(value));\n  } else if (kw == KeywordID::kEnableNestedScroll) {\n    bool enable_nested_scroll = attribute_utils::GetBool(value);\n    SetEnableNestedScroll(enable_nested_scroll);\n  } else if (kw == KeywordID::kEnableScroll) {\n    bool enabled = attribute_utils::GetBool(value);\n    SetScrollEnabled(enabled);\n  } else if (kw == KeywordID::kScrollMonitorTag) {\n    std::string tag;\n    if (attribute_utils::TryGetString(value, tag)) {\n      SetScrollMonitorTag(tag);\n    }\n  } else if (kw == KeywordID::kUpdateAnimation) {\n    bool update_animation = attribute_utils::GetCString(value) ==\n                            attr_value::kListUpdateAnimationDefault;\n    if (update_animation_enabled_ != update_animation) {\n      update_animation_enabled_ = update_animation;\n      if (update_animation) {\n        updater_ = std::make_unique<ListAdapterUpdater>(this);\n      }\n    }\n  } else if (kw == KeywordID::kFocusSmoothScroll) {\n    focus_smooth_scroll_ =\n        attribute_utils::GetBool(value, true);  // default enable\n  } else if (kw == KeywordID::kAlignFocus) {\n    std::string val = attribute_utils::GetCString(value);\n    if (val == \"middle\") {\n      align_focus_ = AlignTo::kMiddle;\n    } else if (val == \"start\") {\n      align_focus_ = AlignTo::kStart;\n    } else if (val == \"end\") {\n      align_focus_ = AlignTo::kEnd;\n    } else if (val == \"none\") {\n      align_focus_ = AlignTo::kNone;\n    } else {\n      FML_DLOG(ERROR) << \"Unrecognized align-focus value: \" << val;\n    }\n  } else if (kw == KeywordID::kSticky) {\n    SetStickyEnabled(attribute_utils::GetBool(value));\n  } else if (kw == KeywordID::kScrollWithoutFocus) {\n    scroll_without_focus_ = attribute_utils::GetBool(value, false);\n  } else if (kw == KeywordID::kScrollDirection ||\n             kw == KeywordID::kScrollOrientation) {\n    std::string val = attribute_utils::GetCString(value);\n    auto scroll_orientation = ScrollDirection::kVertical;\n    if (val == \"horizontal\") {\n      scroll_orientation = ScrollDirection::kHorizontal;\n    } else if (val == \"vertical\") {\n      scroll_orientation = ScrollDirection::kVertical;\n    } else {\n      return;\n    }\n    UpdateScrollOrientation(scroll_orientation);\n  } else if (kw == KeywordID::kVerticalOrientation) {\n    auto orientation_vertical = attribute_utils::GetBool(value, true);\n    auto scroll_orientation = orientation_vertical\n                                  ? ScrollDirection::kVertical\n                                  : ScrollDirection::kHorizontal;\n    UpdateScrollOrientation(scroll_orientation);\n  } else if (kw == KeywordID::kListCrossAxisGap) {\n    float gap = attribute_utils::GetDouble(value, 0);\n    cross_axis_gap_ = gap;\n    layout_manager_->SetCrossAxisGap(gap);\n  } else if (kw == KeywordID::kListMainAxisGap) {\n    float gap = attribute_utils::GetDouble(value, 0);\n    main_axis_gap_ = gap;\n    layout_manager_->SetMainAxisGap(gap);\n  } else if (kw == KeywordID::kBounce || kw == KeywordID::kBounces) {\n    auto enable_bounce = attribute_utils::GetBool(value);\n    SetOverscrollEnabled(enable_bounce);\n  } else if (kw == KeywordID::kStickyOffset) {\n    auto offset = attribute_utils::GetDouble(value);\n    layout_manager_->SetStickyOffset(offset);\n  } else if (kw == KeywordID::kInitialScrollIndex) {\n    auto index = attribute_utils::GetInt(value);\n    layout_manager_->ScrollToPosition(index);\n  } else {\n    BaseView::SetAttribute(attr_c, value);\n  }\n}\n\nvoid BaseListView::UpdateScrollOrientation(ScrollDirection scroll_orientation) {\n  // TODO(liuguoliang): Use `scroll_direction_` instead\n  this->scroll_orientation_ = scroll_orientation;\n  layout_manager_->SetOrientation(scroll_orientation);\n  SetScrollDirection(scroll_orientation);\n  ResetGestureRecognizers();\n}\n\nvoid BaseListView::AddEventCallback(const char* event_c) {\n  NestedScrollable::AddEventCallback(event_c);\n  std::string event(event_c);\n  callback_manager_->AddEventCallback(event);\n}\n\nvoid BaseListView::UpdateLayoutManager(const std::string& type,\n                                       int span_count) {\n  if (span_count != layout_span_count_ && type == attr_value::kListTypeSingle) {\n    // linear layout will not affected by span_count change.\n    layout_span_count_ = span_count;\n    // TODO(hanhaoshen): this for list demo, when lynx set span_count for list,\n    // they manually set width of item , in that case relayout is required.\n    MarkNeedsLayout();\n    return;\n  }\n  if (type == layout_manager_type_ && span_count == layout_span_count_) {\n    return;\n  }\n  LIST_LOG << \"UpdateLayoutManager layout_type: \" << type\n           << \" span_count: \" << span_count;\n  layout_manager_type_ = type;\n  layout_span_count_ = span_count;\n  SetLayoutManager(ListLayoutManager::Create(type, span_count));\n  layout_manager_->SetCrossAxisGap(cross_axis_gap_);\n  layout_manager_->SetMainAxisGap(main_axis_gap_);\n  // update with last set orientation\n  if (scroll_orientation_ != layout_manager_->GetOrientation()) {\n    layout_manager_->SetOrientation(scroll_orientation_);\n    SetScrollDirection(layout_manager_->GetOrientation());\n    ResetGestureRecognizers();\n  }\n  // Cache one line at most\n  recycler_->SetCacheMaxLimit(span_count);\n\n  MarkNeedsLayout();\n}\n\nvoid BaseListView::AddChild(BaseView* child, int index) {\n  FML_DCHECK(std::find(detached_children_.begin(), detached_children_.end(),\n                       child) == detached_children_.end());\n  // Make all list items repaint boundaries to improve scrolling performance\n  child->render_object()->SetRepaintBoundary(true);\n\n  BaseView* parent = child->parent_;\n  if (parent) {\n    parent->BaseView::RemoveChild(child);\n  }\n\n  // Note(Xietong): Don't set the parent\n  detached_children_.insert(detached_children_.end(), child);\n}\n\nvoid BaseListView::RemoveChild(BaseView* child) {\n  std::vector<BaseView*>::iterator iter(\n      std::find(detached_children_.begin(), detached_children_.end(), child));\n  if (iter != detached_children_.end()) {\n    FML_DCHECK(child->parent_ == nullptr);\n    detached_children_.erase(iter);\n  }\n  // Make sure we have removed this child from render tree.\n  // Because this child might still exist in render tree.\n  BaseView::RemoveChild(child);\n}\n\nvoid BaseListView::OnDestroy() {\n  page_view_->GetGapWorker()->UnregisterTaskCollector(this);\n  if (adapter_) {\n    adapter_->SetObserver(nullptr);\n  }\n\n  if (!pending_tasks_.empty()) {\n    auto tasks = std::move(pending_tasks_);\n    for (auto task : tasks) {\n      task();\n    }\n  }\n  callback_manager_ = nullptr;\n}\n\nvoid BaseListView::DestroyAllChildren() {\n  // Remove all children normally means cleanup the view. We will add the\n  // detached children list to the actual list so that the detached children\n  // will be removed as well.\n  children_.insert(children_.end(), detached_children_.begin(),\n                   detached_children_.end());\n  detached_children_.clear();\n  BaseView::DestroyAllChildren();\n}\n\nfloat BaseListView::GetScrollbarScrollOffset() {\n  // TODO(Xietong): whatif there is pending ops?\n  list_state_.item_count = adapter_->GetItemCount();\n  return layout_manager_->GetScrollOffset(list_state_);\n}\n\nfloat BaseListView::GetTotalScrollLength() {\n  // TODO(Xietong): whatif there is pending ops?\n  list_state_.item_count = adapter_->GetItemCount();\n  return layout_manager_->GetTotalLength(list_state_);\n}\n\nvoid BaseListView::OnCreateItem(ListItemViewHolder* item, int position) {\n  // When a view is created, it is not attached yet.\n  BaseView* view = HandleCreateView(item);\n\n  // `ListView` already calls `SetView()` during `HandleCreateView()`.\n  if (!item->GetView()) {\n    item->SetView(view);\n  }\n}\n\nvoid BaseListView::OnAddItem(ListItemViewHolder* item, int index) {\n  last_add_item_position_ = item->GetPosition();\n  if (item->HasAnyOfFlags(ListItemViewHolder::kFlagScrapped)) {\n    FML_DCHECK(item->GetViewAttached());\n    item->RemoveFlags(ListItemViewHolder::kFlagScrapped);\n    return;\n  }\n\n  LIST_LOG << \"Attach view(id): \" << item->GetView()->id()\n           << \" to list with index: \" << index;\n\n  // The below AddChild/RemoveChild will mark needs layout, which is unnecessary\n  // and may cause focus lost issue.\n  BaseView::LayoutIgnoreHelper helper(this);\n\n  // When adding an item, it means the view is detached and will be attached.\n  FML_DCHECK(!item->GetViewAttached());\n  RemoveChild(item->GetView());\n  BaseView::AddChild(item->GetView(), index);\n  FML_DCHECK(item->GetViewAttached());\n\n  if (item->GetPosition() == focus_node_pos_) {\n    FML_DCHECK(!focus_node_attached_);\n    focus_node_attached_ = true;\n  }\n\n  // In some cases, there may be item that is scrapped with the same view, and\n  // to ensure that scrapped item do not affect the attached view, we try to\n  // remove it if it exists.\n  recycler_->RemoveScrappedItemByView(item->GetView());\n}\n\nvoid BaseListView::OnRemoveItem(ListItemViewHolder* item) {\n  // Move view to detached vector.\n  FML_DCHECK(item->GetViewAttached());\n\n  // The below AddChild/RemoveChild will mark needs layout, which is unnecessary\n  // and may cause focus lost issue.\n  BaseView::LayoutIgnoreHelper helper(this);\n\n  LIST_LOG << \"Detached view_id: \" << item->GetView()->id();\n  BaseView::RemoveChild(item->GetView());\n  AddChild(item->GetView(), 0 /* not used */);\n\n  if (item->GetPosition() == focus_node_pos_) {\n    focus_node_attached_ = false;\n  }\n}\n\nvoid BaseListView::OnDestroyItem(ListItemViewHolder* item) {\n  FML_DCHECK(!item->GetViewAttached());\n  RemoveChild(item->GetView());\n  HandleDestroyView(item->GetView(), item);\n\n  if (item->GetPosition() == focus_node_pos_) {\n    ClearFocusNodeInfo();\n  }\n}\n\nvoid BaseListView::OnBindListItem(ListItemViewHolder* item, int prev_position,\n                                  int position, bool newly_created) {\n  if (prev_position != ListItemViewHolder::kNoPosition &&\n      prev_position != position && prev_position == focus_node_pos_) {\n    // The previous focus node's position is updated to different position,\n    // which means the item is recycled and re-used for other position. Cleanup\n    // the focus cache.\n    ClearFocusNodeInfo();\n  }\n}\n\nvoid BaseListView::OffsetChildren(float x, float y) {\n  children_helper_->ForEach([x, y](ListItemViewHolder* child) {\n    child->LayoutWithOffset({x, y});\n    return false;\n  });\n}\n\nvoid BaseListView::DispatchLayout() {\n  TRACE_EVENT(\"clay\", \"List::OnLayout\");\n  BaseView::LayoutIgnoreHelper helper(this);\n  DCHECK_RET0(adapter_);\n\n  LIST_LOG << \"------ Before OnLayout ------\";\n  PrintChildren();\n  adapter_->PrintViewHolders();\n  LIST_LOG << \"Children Helper:\" << children_helper_->ToString();\n\n  if (updater_ && updater_->IsRunning()) {\n    updater_->EndUpdate();\n  }\n\n  ProcessAdapterUpdates();\n\n  LIST_LOG << \"After ProcessAdapterUpdates.\";\n  PrintChildren();\n  adapter_->PrintViewHolders();\n  LIST_LOG << \"Children Helper:\" << children_helper_->ToString();\n\n  list_state_.item_count = adapter_->GetItemCount();\n\n  SaveFocusNodeInfo();\n\n  // Use `OffsetChildren` to copy the original view location to item's\n  // layout_location_.\n  OffsetChildren(0.f, 0.f);\n\n  layout_manager_->ScrapFloatingItems(recycler_.get());\n\n  layout_manager_->OnLayoutChildren(*recycler_, list_state_);\n\n  if (sticky_enabled_) {\n    layout_manager_->LayoutSticky(recycler_.get(), adapter_);\n  }\n\n  if (update_animation_enabled_ && updater_->hasPendingUpdateAnimation()) {\n    children_helper_->ForEach([this](ListItemViewHolder* view_holder) -> bool {\n      updater_->SaveItemEndAttribute(view_holder);\n      view_holder->FlushLayout();\n      return false;\n    });\n    updater_->PerformUpdate();\n  } else {\n    FlushChildrenLayout();\n  }\n\n  list_state_.structure_changed = false;\n\n  layout_manager_->OnLayoutCompleted(*recycler_, list_state_);\n  layout_manager_->UpdateItemsVisibility();\n\n  RestoreFocusNodeInfo();\n\n  data_set_has_changed_after_layout_ = false;\n  dispatch_items_changed_event_ = false;\n\n  if (delegate_) {\n    delegate_->OnListViewDidLayout();\n  }\n  OnLayoutComplete();\n  PrintChildren();\n  adapter_->PrintViewHolders();\n  LIST_LOG << \"Children Helper:\" << children_helper_->ToString();\n  LIST_LOG << \"------ After OnLayout ------\";\n}\n\nvoid BaseListView::OnLayout(LayoutContext* context) { DispatchLayout(); }\n\nListScroller* BaseListView::GetListScroller() {\n  if (!scroller_) {\n    scroller_ = std::make_unique<ListScroller>(this);\n  }\n  return scroller_.get();\n}\n\nvoid BaseListView::ResetListScroller() {\n  if (scroller_) {\n    if (scroller_->Scrolling()) {\n      scroller_->StopScroll();\n    }\n    scroller_ = nullptr;\n  }\n}\n\nvoid BaseListView::StopAnimation() {\n  NestedScrollable::StopAnimation();\n  if (scroller_ && scroller_->Scrolling()) {\n    scroller_->StopScroll();\n  }\n}\n\nFloatPoint BaseListView::DoScroll(FloatPoint delta, bool is_user_input,\n                                  bool ignore_repaint) {\n  if (!IsScrollEnabled()) {\n    return delta;\n  }\n  unconsumed_x_ = 0.f;\n  unconsumed_y_ = 0.f;\n  OnScrollBy({-delta.x(), -delta.y()});\n  return {unconsumed_x_, unconsumed_y_};\n}\n\nbool BaseListView::OnScrollBy(const FloatSize& distance) {\n  DCHECK_RET1(adapter_, false);\n  // Move the round operation to RenderBox.\n  const FloatSize& converted_distance = distance;\n  int64_t start = lynx::base::CurrentSystemTimeMilliseconds();\n  recycler_->SetListPerfFlag(false);\n  // Early out. And this is necessary for sticky to work correctly.\n  if (!((converted_distance.width() != 0 &&\n         layout_manager_->CanScrollHorizontally()) ||\n        (converted_distance.height() != 0 &&\n         layout_manager_->CanScrollVertically()))) {\n    return false;\n  }\n\n  LIST_LOG << \"------ Before Scroll ------\";\n  PrintChildren();\n  adapter_->PrintViewHolders();\n  LIST_LOG << \"Children Helper:\" << children_helper_->ToString();\n  if (adapter_helper_->HasPendingUpdates()) {\n    DispatchLayout();\n  }\n  // Use `OffsetChildren` to copy the original view location to item's\n  // layout_location_.\n  OffsetChildren(0.f, 0.f);\n\n  layout_manager_->ScrapFloatingItems(recycler_.get());\n\n  float consumed_x = 0.f, consumed_y = 0.f;\n  if (converted_distance.width() != 0 &&\n      layout_manager_->CanScrollHorizontally()) {\n    consumed_x = layout_manager_->ScrollHorizontallyBy(\n        -converted_distance.width(), *recycler_, list_state_);\n    unconsumed_x_ = -converted_distance.width() - consumed_x;\n  } else if (converted_distance.height() != 0 &&\n             layout_manager_->CanScrollVertically()) {\n    consumed_y = layout_manager_->ScrollVerticallyBy(\n        -converted_distance.height(), *recycler_, list_state_);\n    unconsumed_y_ = -converted_distance.height() - consumed_y;\n  }\n\n  // ListView only can scroll in one direction\n  scrolled_horizontal_ += consumed_x;\n  scrolled_vertical_ += consumed_y;\n  // Update current scroll offset for prefetch\n  current_scroll_height_ += consumed_y;\n  current_scroll_width_ += consumed_x;\n  // Should not add HasEvent(event_attr::kEventScroll) judgment here, it's\n  // always false, because the event is handled by the callback_manager.\n  // And it's also handle the kEventScrollToUpper and kEventScrollToLower.\n  callback_manager_->NotifyScrolled(FloatPoint(consumed_x, consumed_y),\n                                    TotalScrollOffset(),\n                                    CalculateBorderStatus());\n\n  // Flush the layout_location_ to the actual view position.\n  FlushChildrenLayout();\n\n  if (sticky_enabled_) {\n    layout_manager_->LayoutSticky(recycler_.get(), adapter_);\n  }\n\n  layout_manager_->UpdateItemsVisibility();\n\n  recycler_->RecycleScrappedItems(this);\n  if (recycler_->GetListPerfFlag()) {\n    int64_t end = lynx::base::CurrentSystemTimeMilliseconds();\n    auto record = FrameTimingCollector::PerfMap();\n    record.emplace(Perf::kListLayoutNewItem, end - start);\n    if (page_view()->GetFrameTimingCollector()) {\n      page_view()->GetFrameTimingCollector()->InsertForceRecord(record);\n    }\n  }\n  recycler_->SetListPerfFlag(false);\n\n  PrintChildren();\n  adapter_->PrintViewHolders();\n  LIST_LOG << \"Children Helper:\" << children_helper_->ToString();\n  LIST_LOG << \"------ End Scroll ------\";\n\n  if (consumed_x != 0.f || consumed_y != 0.f) {\n    // TODO(liuguoliang): Can we remove this?\n    Invalidate();\n    DidScroll();\n\n#ifdef ENABLE_ACCESSIBILITY\n    // We need to rebuild semantics tree, because some detached views may be\n    // attached again. But the semanticsNodes attached to those new attaced\n    // views are already detached.\n    MarkRebuildSemanticsTree();\n#endif\n\n    return true;\n  }\n\n  return false;\n}\n\nFloatPoint BaseListView::TotalScrollOffset() {\n  return GetRenderScroll()->ScrollOffset() +\n         FloatPoint(scrolled_horizontal_, scrolled_vertical_);\n}\n\nvoid BaseListView::RegisterPrefetch() {\n  if (sAllowListPrefetch && recycler_ && recycler_->GetCacheMaxLimit() > 0) {\n    page_view_->GetGapWorker()->RegisterTaskCollector(this,\n                                                      gap_task_collector_);\n  }\n}\n\nvoid BaseListView::UnregisterPrefetch() {\n  if (sAllowListPrefetch) {\n    current_scroll_height_ = 0;\n    current_scroll_width_ = 0;\n    last_add_item_position_when_prefetch_start_ = -1;\n    page_view_->GetGapWorker()->UnregisterTaskCollector(this);\n  }\n}\n\nvoid BaseListView::StartPrefetch(int32_t width, int32_t height) {\n  if ((width == 0 && height == 0) || last_add_item_position_ == -1) {\n    return;\n  }\n  if (last_add_item_position_when_prefetch_start_ == last_add_item_position_) {\n    // One long scroll can be broken down into many smaller scrolls. We do this\n    // to avoid post too many prefetch requests.\n    return;\n  }\n  if (!gap_task_bundle_) {\n    gap_task_bundle_ = fml::MakeRefCounted<GapTaskBundle>(GetWeakPtr());\n  }\n  // Clean old tasks because position has changed.\n  gap_task_bundle_->Clear();\n\n  // Record latest add item position when prefetch start.\n  last_add_item_position_when_prefetch_start_ = last_add_item_position_;\n\n  TRACE_EVENT(\"clay\", \"BuildPrefetchTaskList\");\n  prefetch_registry_.ClearPrefetchPositions();\n  prefetch_registry_.CollectPrefetchPostionFromView(this, -width, -height);\n  for (const auto& item_info : prefetch_registry_.prefetch_item_infos_) {\n    int distance_to_item = item_info.second;\n    auto type = adapter_->GetItemViewType(item_info.first);\n    auto task = std::make_unique<ListPrefetchTask>(\n        GetWeakPtr(), item_info.first,\n        adapter_->GetAverageBindTime(type).ToNanoseconds(), distance_to_item,\n        true);\n    gap_task_bundle_->AddTask(std::move(task));\n  }\n  // This step attempts to prioritize different subtasks by distance. But in\n  // the current scenario, the distance of each subtask is the same because\n  // they are placed in the same line.\n  gap_task_bundle_->sort();\n  page_view_->GetGapWorker()->SubmitTask(gap_task_bundle_);\n}\n\nbool BaseListView::CanDragScrollOnX() const {\n  return layout_manager_->CanScrollHorizontally();\n}\n\nbool BaseListView::CanDragScrollOnY() const {\n  return layout_manager_->CanScrollVertically();\n}\n\nbool BaseListView::IsMoveOnMainAxis(FocusManager::Direction direction) const {\n  if (direction == FocusManager::Direction::kLeft ||\n      direction == FocusManager::Direction::kRight) {\n    return layout_manager_->CanScrollHorizontally();\n  }\n  if (direction == FocusManager::Direction::kUp ||\n      direction == FocusManager::Direction::kDown) {\n    return layout_manager_->CanScrollVertically();\n  }\n  return false;\n}\n\nFocusManager::TraversalResult BaseListView::OnTraversalOnScopeInternal(\n    FocusManager::Direction direction,\n    FocusManager::TraversalType traversal_type) {\n  // to pass the test case add two condition\n  if (page_view()->GetFrameTimingCollector() &&\n      page_view()->GetFrameTimingCollector()->GetReceivedFocusTime() == 0.0) {\n    traversal_perf_start_ = lynx::base::CurrentSystemTimeMilliseconds();\n  }\n  FocusManager::TraversalResult result =\n      FocusNode::OnTraversalOnScope(direction, traversal_type);\n  if (result.succeed) {\n    pre_focus_node_pos_ = ListItemViewHolder::kNoPosition;\n    // Reset to idle\n    SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n    return result;\n  }\n\n  if (children_helper_->GetChildCount() == 0) {\n    return result;\n  }\n\n  const bool to_end = direction == FocusManager::Direction::kDown ||\n                      direction == FocusManager::Direction::kRight;\n  const bool move_on_main_axis = IsMoveOnMainAxis(direction);\n\n  if (layout_manager_type_ == attr_value::kListTypeSingle &&\n      !move_on_main_axis) {\n    return result;\n  }\n  // If the direction is not scrollable or already reach the start/end, the\n  // event will be handled by parent.\n  if (!to_end && !layout_manager_->HasSpaceToStart(list_state_)) {\n    return result;\n  } else if (to_end && !layout_manager_->HasSpaceToEnd(list_state_)) {\n    return result;\n  }\n  pre_focus_node_pos_ = ListItemViewHolder::kNoPosition;\n  ProcessAdapterUpdates();\n\n  // `OnFocusSearchFailed()` is very similar to `OnLayoutChildren()` which may\n  // trigger the Lynx issue. See `FlushChildrenLayout()` for more information.\n  // So we cache children's location and flush it back after\n  // `OnFocusSearchFailed()`.\n  OffsetChildren(0.f, 0.f);\n  layout_manager_->OnFocusSearchFailed(to_end, *recycler_, list_state_);\n  FlushChildrenLayout();\n\n  // After calling `OnFocusSearchFailed()`, more items are laid out even out of\n  // the visible area of the list view. We will try to find a focusable node\n  // again.\n  result = FocusNode::OnTraversalOnScope(direction, traversal_type);\n  // Reset to idle\n  SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n  if (!scroll_without_focus_ || result.succeed) {\n    return result;\n  }\n\n  if (!move_on_main_axis) {\n    // cross-axis-moving-caused load more shouldn't do scroll on main axis.\n    FML_DCHECK(layout_manager_type_ != attr_value::kListTypeSingle);\n    return result;\n  }\n\n  int target_pos = ListItemViewHolder::kNoPosition;\n  if (to_end) {\n    ListItemViewHolder* last = layout_manager_->GetLastInBoxChild(list_state_);\n    FML_DCHECK(last != nullptr);\n    target_pos = std::min(last->GetPosition() + 1,\n                          static_cast<int>(list_state_.item_count));\n  } else {\n    ListItemViewHolder* first =\n        layout_manager_->GetFirstInBoxChild(list_state_);\n    FML_DCHECK(first != nullptr);\n    target_pos = std::max(first->GetPosition() - 1, 0);\n  }\n  ScrollToPosition(false, target_pos, 0, AlignTo::kNone, \"\", nullptr);\n  result.succeed = true;\n  // Call `UpdateFocusRing` to set focus ring back to list when previous\n  // focus node be removed\n  UpdateFocusRing();\n  return result;\n}\n\nFocusManager::TraversalResult BaseListView::OnTraversalOnScope(\n    FocusManager::Direction direction,\n    FocusManager::TraversalType traversal_type) {\n  auto result = OnTraversalOnScopeInternal(direction, traversal_type);\n  if (result.succeed && page_view() && page_view()->GetFrameTimingCollector()) {\n    page_view()->GetFrameTimingCollector()->SetReceivedFocusTime(\n        traversal_perf_start_, direction);\n  }\n  traversal_perf_start_ = 0;\n  return result;\n}\n\nvoid BaseListView::OnFlingAnimationStart() { page_view()->OnFlingStart(); }\n\nvoid BaseListView::OnFlingAnimationEnd() { page_view()->OnFlingEnd(); }\n\nvoid BaseListView::OnItemRangeInserted(int position_start, int item_count) {\n  // Save insert operation to `adapter_helper_`, coalescence and consume\n  // in next layout pass.\n  adapter_helper_->OnItemRangeInserted(position_start, item_count);\n  list_state_.item_count = adapter_->GetItemCount();\n  MarkNeedsLayout();\n}\n\nvoid BaseListView::OnItemRangeRemoved(int position_start, int item_count) {\n  // Save remove operation to `adapter_helper_`, coalescence and consume\n  // in next layout pass.\n  adapter_helper_->OnItemRangeRemoved(position_start, item_count);\n  list_state_.item_count = adapter_->GetItemCount();\n  MarkNeedsLayout();\n}\n\nvoid BaseListView::OnItemRangeChanged(\n    int position_start, int item_count,\n    std::unique_ptr<ListAdapter::Payload> payload) {\n  // Save reload operation to `adapter_helper_`, coalescence and consume\n  // in next layout pass.\n  adapter_helper_->OnItemRangeChanged(position_start, item_count,\n                                      std::move(payload));\n  MarkNeedsLayout();\n}\n\nvoid BaseListView::OnItemMoved(int from_position, int to_position) {\n  // Save move operation to `adapter_helper_`, coalescence and consume\n  // in next layout pass.\n  adapter_helper_->OnItemMoved(from_position, to_position);\n  MarkNeedsLayout();\n}\n\nvoid BaseListView::OnChanged() {\n  list_state_.structure_changed = true;\n  ProcessDataSetCompletelyChanged(true);\n  MarkNeedsLayout();\n}\n\n// Mark: - Consuming ListAdapter data update begin.\nvoid BaseListView::OffsetPositionsForInsert(int position_start,\n                                            int item_count) {\n  children_helper_->ForEachWithSticky(\n      [this, position_start, item_count](ListItemViewHolder* view_holder) {\n        if (view_holder->GetPosition() >= position_start &&\n            !view_holder->IsRemoved()) {\n          // Visible item after `position_start` will be moved `item_count`\n          // step(s).\n          view_holder->OffsetPosition(item_count);\n          list_state_.structure_changed = true;\n          if (update_animation_enabled_) {\n            // save start origin\n            updater_->SaveItemStartAttribute(view_holder);\n          }\n        }\n        return false;\n      });\n  recycler_->OffsetPositionsForInsert(position_start, item_count);\n  layout_manager_->OnItemsAdded(this, position_start, item_count);\n  if (update_animation_enabled_) {\n    for (int i = position_start; i < position_start + item_count; ++i) {\n      updater_->AddInsertItemPosition(i);\n    }\n  }\n}\n\nvoid BaseListView::OffsetPositionsForRemove(int position_start,\n                                            int item_count) {\n  const int position_end = position_start + item_count;\n  children_helper_->ForEachWithSticky(\n      [this, position_start, item_count,\n       position_end](ListItemViewHolder* view_holder) {\n        if (view_holder->GetPosition() >= position_end) {\n          view_holder->OffsetPosition(-item_count, true);\n          list_state_.structure_changed = true;\n\n          if (update_animation_enabled_ && !view_holder->IsRemoved()) {\n            updater_->SaveItemStartAttribute(view_holder);\n          }\n        } else if (view_holder->GetPosition() >= position_start) {\n          view_holder->FlagRemovedAndOffsetPosition(position_start - 1,\n                                                    -item_count, true);\n          list_state_.structure_changed = true;\n        } else {\n          layout_manager_->OnRemoveItem(view_holder);\n        }\n        return false;\n      });\n  recycler_->OffsetPositionsForRemove(position_start, item_count);\n  layout_manager_->OnItemsRemoved(this, position_start, item_count);\n\n  // Current focused node gets deleted.\n  if (pre_focus_node_pos_ <= position_end &&\n      pre_focus_node_pos_ >= position_start) {\n    pre_focus_node_deleted_ = true;\n  }\n}\n\nvoid BaseListView::MarkViewHoldersChanged(\n    int position_start, int item_count,\n    std::unique_ptr<ListAdapter::Payload> payload) {\n  const int position_end = position_start + item_count;\n\n  std::optional<int> update_to_position = std::nullopt;\n  if (payload && payload->is_lynx_payload) {\n    auto* lynx_list_payload = static_cast<LynxListPayload*>(payload.get());\n    update_to_position = lynx_list_payload->update_to_position;\n  }\n\n  children_helper_->ForEachWithSticky([position_start, position_end, &payload](\n                                          ListItemViewHolder* view_holder) {\n    if (view_holder->GetPosition() >= position_start &&\n        view_holder->GetPosition() < position_end) {\n      // Visible Item between [position_start, position_end) will be flagged\n      // as update and store `payload`.\n      view_holder->AddFlags(ListItemViewHolder::kFlagUpdate);\n      view_holder->AddChangePayload(std::move(payload));\n    }\n    return false;\n  });\n  recycler_->MarkViewHoldersChanged(position_start, item_count);\n  if (update_to_position) {\n    // Update operation from Lynx will have `from` and `to`, so we call move\n    // instead of update.\n    layout_manager_->OnItemsMoved(this, position_start, *update_to_position,\n                                  item_count);\n  } else {\n    layout_manager_->OnItemsUpdated(this, position_start, item_count);\n  }\n}\n\nvoid BaseListView::OffsetPositionsForMove(int from, int to) {\n  int start, end, in_between_offset;\n  if (from < to) {\n    start = from;\n    end = to;\n    in_between_offset = -1;\n  } else {\n    start = to;\n    end = from;\n    in_between_offset = 1;\n  }\n\n  children_helper_->ForEachWithSticky(\n      [this, from, to, start, end,\n       in_between_offset](ListItemViewHolder* view_holder) {\n        const int position = view_holder->GetPosition();\n        if (position < start || position > end) {\n          return false;\n        }\n        if (position == from) {\n          view_holder->OffsetPosition(to - from, false);\n        } else {\n          view_holder->OffsetPosition(in_between_offset, false);\n        }\n        if (update_animation_enabled_) {\n          updater_->SaveItemStartAttribute(view_holder);\n        }\n        list_state_.structure_changed = true;\n        return false;\n      });\n  recycler_->OffsetPositionsForMove(from, to);\n  layout_manager_->OnItemsMoved(this, from, to, 1);\n}\n\nbool BaseListView::IsItemFullSpan(int position) const {\n  return adapter_->IsItemFullSpan(position);\n}\n\nvoid BaseListView::FlushChildrenLayout() {\n  children_helper_->ForEach([](ListItemViewHolder* child) {\n    child->FlushLayout();\n    return false;\n  });\n}\n\nbool BaseListView::OnScrollToVisible() {\n  FloatRect focused_view_rect = GetFocusManager()->GetFocusedNodeRect();\n  if (!focused_view_rect.IsEmpty()) {\n    MakeViewInBox(focused_view_rect, align_focus_);\n  }\n  return true;\n}\n\nvoid BaseListView::MakeViewInBox(const FloatRect& focus_view_rect,\n                                 AlignTo align_to) {\n  if (focus_smooth_scroll_) {\n    auto res = layout_manager_->GetChildPositionByRect(focus_view_rect);\n    if (res) {\n      auto& pos_and_rect = *res;\n      ScrollToPosition(true, pos_and_rect.first, 0, align_to, \"\",\n                       pos_and_rect.second, nullptr);\n    } else {\n      FML_DCHECK(false) << \"position not found with the specified rect\";\n    }\n  } else {\n    // When user traverse focus node fast and `focus_view_rect` is totally\n    // invisible, stop animation firstly and scroll without animation.\n    GetListScroller()->StopScroll();\n    // TODO(Chenfeng Pan): implement waterfall layout manager.\n    FloatSize to_scroll =\n        layout_manager_->ScrollToRect(focus_view_rect, align_to);\n    to_scroll.SetHeight(-to_scroll.height());\n    to_scroll.SetWidth(-to_scroll.width());\n    OnScrollBy(to_scroll);\n  }\n}\n\nvoid BaseListView::SaveFocusNodeInfo() {\n  auto* focused = static_cast<BaseView*>(focus_manager_->GetLeafFocusedNode());\n  if (focused) {\n    if (pre_focus_node_deleted_) {\n      LIST_LOG << \"Focused node was removed, set focus to list\";\n      focused->ClearFocus();\n      UpdateFocusRing();\n      return;\n    }\n    focus_node_cache_ = focused;\n    children_helper_->ForEach([this](ListItemViewHolder* child) {\n      if (child->GetView()->IsDescendant(focus_node_cache_)) {\n        focus_node_pos_ = child->GetPosition();\n        return true;\n      }\n      return false;\n    });\n    focus_node_attached_ = true;\n\n    focused->AddLifecycleListener(this);\n  }\n}\n\nvoid BaseListView::RestoreFocusNodeInfo() {\n  if (focus_node_cache_ != nullptr && focus_node_attached_ &&\n      focus_node_cache_->attach_to_tree_) {\n    focus_node_cache_->RequestFocus();\n  } else if (focus_node_pos_ != ListItemViewHolder::kNoPosition) {\n    LIST_LOG << \"Focused node was removed, set focus to list\";\n    UpdateFocusRing();\n  }\n\n  ClearFocusNodeInfo();\n}\n\nvoid BaseListView::OnFocusNodeDestructed(FocusNode* node) {\n  if (node == focus_node_cache_) {\n    focus_node_cache_ = nullptr;\n  }\n}\n\nvoid BaseListView::ClearFocusNodeInfo() {\n  pre_focus_node_pos_ = focus_node_pos_;\n  pre_focus_node_deleted_ = false;\n  if (focus_node_cache_) {\n    focus_node_cache_->RemoveLifecycleListener(this);\n    focus_node_cache_ = nullptr;\n  }\n  focus_node_pos_ = ListItemViewHolder::kNoPosition;\n  focus_node_attached_ = false;\n}\n\nvoid BaseListView::OnVisibilityChanged(ListItemViewHolder* item, bool visible) {\n  if (visible) {\n    callback_manager_->OnItemAppear(item);\n  } else {\n    callback_manager_->OnItemDisappear(item);\n  }\n}\n\nvoid BaseListView::ProcessDataSetCompletelyChanged(bool dispatch_item_changed) {\n  dispatch_items_changed_event_ |= dispatch_item_changed;\n  data_set_has_changed_after_layout_ = true;\n  MarkKnownViewsInvalid();\n}\n\nvoid BaseListView::MarkKnownViewsInvalid() {\n  children_helper_->ForEach([](ListItemViewHolder* child) {\n    child->AddFlags(static_cast<ListItemViewHolder::Flag>(\n        ListItemViewHolder::kFlagInvalid | ListItemViewHolder::kFlagUpdate));\n    return false;\n  });\n  recycler_->MarkKnownViewsInvalid();\n}\n\nvoid BaseListView::ProcessAdapterUpdates() {\n  if (data_set_has_changed_after_layout_) {\n    adapter_helper_->Reset();\n\n    if (dispatch_items_changed_event_) {\n      layout_manager_->OnItemsChanged(this);\n    }\n  }\n\n  adapter_helper_->ConsumeUpdates();\n}\n\nScrollEventCallbackManager::BorderStatus BaseListView::CalculateBorderStatus() {\n  ScrollEventCallbackManager::BorderStatus border_status;\n  if (lower_threshold_item_count_ || upper_threshold_item_count_) {\n    int first_child_position = std::numeric_limits<int>::max();\n    int last_child_position = std::numeric_limits<int>::min();\n    children_helper_->ForEach([&first_child_position, &last_child_position](\n                                  ListItemViewHolder* child) {\n      int position = child->GetPosition();\n      first_child_position = std::min(first_child_position, position);\n      last_child_position = std::max(last_child_position, position);\n      return false;\n    });\n    if (upper_threshold_item_count_ &&\n        first_child_position < *upper_threshold_item_count_) {\n      border_status.SetUpper(true);\n    }\n    if (lower_threshold_item_count_ &&\n        last_child_position >\n            adapter_->GetItemCount() - *lower_threshold_item_count_ - 1) {\n      border_status.SetLower(true);\n    }\n  }\n\n  bool first_item_visible = children_helper_->FindChildByPosition(0) != nullptr;\n  bool last_item_visible = children_helper_->FindChildByPosition(\n                               adapter_->GetItemCount() - 1) != nullptr;\n  if (first_item_visible || last_item_visible) {\n    // the min and max Y-offset of visible cells.\n    float top_bound = std::numeric_limits<float>::max();\n    float bottom_bound = std::numeric_limits<float>::lowest();\n    ListOrientationHelper* orientation_helper =\n        layout_manager_->GetOrientationHelper();\n    children_helper_->ForEach([&top_bound, &bottom_bound,\n                               orientation_helper](ListItemViewHolder* child) {\n      top_bound =\n          std::min(top_bound, orientation_helper->GetDecoratedStart(child));\n      bottom_bound =\n          std::max(bottom_bound, orientation_helper->GetDecoratedEnd(child));\n      return false;\n    });\n\n    int start_of_visible_area = orientation_helper->GetStartAfterPadding();\n    int end_of_visible_area = orientation_helper->GetEndAfterPadding();\n    if (first_item_visible) {\n      if (top_bound == start_of_visible_area) {\n        scrolled_vertical_ = 0;\n        scrolled_horizontal_ = 0;\n      }\n      // check if the top border threshold-px reached\n      if (top_bound > start_of_visible_area - upper_threshold_pixel_) {\n        border_status.SetUpper(true);\n      }\n    }\n    if (last_item_visible) {\n      // check if the bottom border threshold-px reached\n      if (bottom_bound < end_of_visible_area + lower_threshold_pixel_) {\n        border_status.SetLower(true);\n      }\n    }\n  }\n\n  return border_status;\n}\n\nvoid BaseListView::SetScrollState(ScrollState state) {\n  if (state == scroll_state_) {\n    return;\n  }\n  LIST_LOG << \"Setting scroll from \" << static_cast<int>(scroll_state_)\n           << \" to \" << static_cast<int>(state);\n  scroll_state_ = state;\n  if (state != ScrollState::kSettling) {\n    StopAnimation();\n  }\n  callback_manager_->OnScrollStateChange(state);\n}\n\nvoid BaseListView::OnScrollStatusChange(ScrollStatus old_status) {\n  NestedScrollable::OnScrollStatusChange(old_status);\n\n  if (status_ == Scrollable::ScrollStatus::kFling) {\n    OnFlingAnimationStart();\n  } else if (old_status == Scrollable::ScrollStatus::kFling) {\n    OnFlingAnimationEnd();\n  }\n\n  bool need_prefetch = status_ == Scrollable::ScrollStatus::kFling ||\n                       status_ == Scrollable::ScrollStatus::kDragging ||\n                       status_ == Scrollable::ScrollStatus::kAnimating;\n  bool old_need_prefetch = old_status == Scrollable::ScrollStatus::kFling ||\n                           old_status == Scrollable::ScrollStatus::kDragging ||\n                           old_status == Scrollable::ScrollStatus::kAnimating;\n  if (need_prefetch && !old_need_prefetch) {\n    RegisterPrefetch();\n  } else if (!need_prefetch && old_need_prefetch) {\n    UnregisterPrefetch();\n  }\n\n  switch (status_) {\n    case Scrollable::ScrollStatus::kFling:\n    case Scrollable::ScrollStatus::kBounce:\n      SetScrollState(ScrollState::kSettling);\n      break;\n    case Scrollable::ScrollStatus::kDragging:\n      SetScrollState(ScrollState::kDragging);\n      break;\n    case Scrollable::ScrollStatus::kIdle:\n      SetScrollState(ScrollState::kIdle);\n      break;\n    default:\n      break;\n  }\n\n  if (layout_manager_) {\n    layout_manager_->OnScrollStateChange(status_);\n  }\n}\n\nvoid BaseListView::ScrollToPosition(\n    bool smooth, int position, float offset, AlignTo align_to,\n    const std::string& id, const std::optional<FloatRect> target_rect,\n    const std::function<void(uint32_t, const std::string&)>& callback) {\n  if (position < 0 || position >= adapter_->GetItemCount()) {\n    if (callback) {\n      callback(static_cast<uint32_t>(LynxUIMethodResult::kParamInvalid), \"\");\n    }\n    return;\n  }\n\n  SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n  GetListScroller()->ScrollToPosition(smooth, position, offset, align_to, id,\n                                      target_rect, callback);\n}\n\nvoid BaseListView::ScrollToPosition(\n    bool smooth, int position, float offset, AlignTo align_to,\n    const std::string& id,\n    const std::function<void(uint32_t, const std::string&)>& callback) {\n  ScrollToPosition(smooth, position, offset, align_to, id, std::nullopt,\n                   callback);\n}\n\nvoid BaseListView::SetStickyEnabled(bool enabled) {\n  if (sticky_enabled_ != enabled) {\n    sticky_enabled_ = enabled;\n    MarkNeedsLayout();\n  }\n}\n\n#ifdef ENABLE_ACCESSIBILITY\nint32_t BaseListView::GetSemanticsActions() const {\n  int32_t actions = BaseView::GetSemanticsActions();\n  if (CanDragScrollOnY()) {\n    actions |=\n        static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollUp) |\n        static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollDown);\n  } else if (CanDragScrollOnX()) {\n    actions |=\n        static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollLeft) |\n        static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollRight);\n  } else {\n    FML_DLOG(ERROR) << \"BaseListView cannot draw scroll both on X and Y axis\";\n  }\n  return actions;\n}\n\nint32_t BaseListView::GetSemanticsFlags() const {\n  int32_t flags = BaseView::GetSemanticsFlags();\n  flags |=\n      static_cast<int32_t>(SemanticsNode::SemanticsFlag::kHasImplicitScrolling);\n  return flags;\n}\n\nint32_t BaseListView::GetA11yScrollChildren() const {\n  int32_t valid_count = 0;\n  for (auto child : children_) {\n    if (child->IsAccessibilityElement()) {\n      ++valid_count;\n    }\n  }\n  return valid_count;\n}\n#endif\n\nbool BaseListView::OnScrollToMiddle(BaseView* target_view) {\n  if (!target_view) {\n    FML_DLOG(ERROR) << \"OnScrollToMiddle but view is nullptr\";\n    return false;\n  }\n  FloatRect rect = target_view->GetBounds();\n  MakeViewInBox(rect, AlignTo::kMiddle);\n  return true;\n}\n\nScrollableDirection BaseListView::GetScrollableDirection() const {\n  ScrollableDirection result = ScrollableDirection::kNone;\n  if (CanDragScrollOnX()) {\n    if (layout_manager_->HasSpaceToStart(list_state_)) {\n      result |= ScrollableDirection::kLeftwards;\n    }\n    if (layout_manager_->HasSpaceToEnd(list_state_)) {\n      result |= ScrollableDirection::kRightwards;\n    }\n  }\n  if (CanDragScrollOnY()) {\n    if (layout_manager_->HasSpaceToStart(list_state_)) {\n      result |= ScrollableDirection::kUpwards;\n    }\n    if (layout_manager_->HasSpaceToEnd(list_state_)) {\n      result |= ScrollableDirection::kDownwards;\n    }\n  }\n  return result;\n}\n\nvoid BaseListView::PrintChildren() {\n#if (DEBUG_LIST)\n  auto print_child = [](std::string& str, int i, BaseView* child,\n                        bool visible) {\n    std::stringstream ss;\n    ss << \"[\" << i << \"] \" << child->GetName() << \" view_id:\" << child->id();\n    if (visible) {\n      ss << \" frame:(\" << child->Left() << \",\" << child->Top() << \",\"\n         << child->Width() << \",\" << child->Height() << \")\";\n    }\n    str.append(ss.str());\n  };\n  std::string attached_str;\n  for (size_t i = 0; i < child_count(); ++i) {\n    attached_str += \"\\n\";\n    print_child(attached_str, i, children_[i], true);\n  }\n  LIST_LOG << \"list attached children:\" << attached_str;\n  std::string detached_str;\n  for (size_t i = 0; i < detached_children_.size(); ++i) {\n    detached_str += \"\\n\";\n    print_child(detached_str, i, detached_children_[i], false);\n  }\n  LIST_LOG << \"list detached children:\" << detached_str;\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/base_list_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_BASE_LIST_VIEW_H_\n#define CLAY_UI_COMPONENT_LIST_BASE_LIST_VIEW_H_\n\n#include <functional>\n#include <map>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/gfx/animation/fling_animator.h\"\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/common/gap_task.h\"\n#include \"clay/ui/common/gap_worker.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/list/list_adapter.h\"\n#include \"clay/ui/component/list/list_adapter_helper.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_scroller.h\"\n#include \"clay/ui/component/nested_scroll/nested_scrollable.h\"\n#include \"clay/ui/component/scrollable.h\"\n#include \"clay/ui/component/view_callback/list_event_callback_manager.h\"\n#include \"clay/ui/gesture/drag_gesture_recognizer.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass ListChildrenHelper;\nclass ListItemViewHolder;\nclass ListLayoutManager;\nclass ListRecycler;\nclass ListAdapterUpdater;\nclass GapWorker;\n\nvoid SetAllowListPrefetch(bool allow);\n\nclass LayoutPrefetchRegistry {\n public:\n  void AddPosition(int layout_position, int pixel_distance);\n  void CollectPrefetchPostionFromView(BaseListView* list_view,\n                                      int prefetch_delta_x,\n                                      int prefetch_delta_y);\n  bool LastPrefetchIncludePosition(int position);\n  void ClearPrefetchPositions();\n\n private:\n  LayoutPrefetchRegistry() = default;\n\n  friend class BaseListView;\n  std::unordered_map<int, int> prefetch_item_infos_;\n};\n\nclass BaseListView : public WithTypeInfo<BaseListView, NestedScrollable>,\n                     public ListAdapter::Observer,\n                     public ListAdapterHelper::Consumer,\n                     public ListItemViewHolder::VisibilityObserver,\n                     public FocusNode::LifecycleListener {\n public:\n  class Delegate {\n   public:\n    virtual void OnListViewDidLayout() = 0;\n  };\n\n  using ScrollState = ListEventCallbackManager::ScrollState;\n\n  BaseListView(int32_t id, std::string tag, PageView* page_view);\n  BaseListView(int32_t id, int32_t callback_id, std::string tag,\n               PageView* page_view);\n  virtual ~BaseListView();\n\n  int GetCallbackId() override { return callback_id_; }\n\n  bool IsLayoutRootCandidate() const override { return true; }\n\n  // Post a task that will only run when the list is not scrolling. Run the task\n  // immediately if there is no scrolling, otherwise run it after scrolling.\n  void PostLowPriorityTask(std::function<void()> task);\n\n  void SetWidth(float width) override;\n  void SetHeight(float height) override;\n\n  void SetAdapter(ListAdapter* adapter);\n  void SetLayoutManager(std::unique_ptr<ListLayoutManager> layout_manager);\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  void AddEventCallback(const char* event_c) override;\n  void UpdateLayoutManager(const std::string& type, int span_count);\n  void NotifyLowMemory() override;\n\n  ////////////////// Override BaseView Start //////////////\n  // List View uses different model to manage its subview. Calling `AddChild`\n  // will add the child to a detached children list.\n  // A detach child means either it is just created or it is recycled.\n  // Child in detached children list won't be rendered.\n  // It is the layout manager to determine when to attach a child view. Then the\n  // child will be moved from the detached list to the actual children list.\n  void AddChild(BaseView* child, int index) override;\n  // Remove the child from the detached list. Then the child may be deleted or\n  // added to the actual children list.\n  void RemoveChild(BaseView* child) override;\n  void DestroyAllChildren() override;\n\n  // NOTE(Xietong): cannot override `GetScrollOffset()` because\n  // `GetScrollOffset()` is used by hittest. Overriding `GetScrollOffset()` will\n  // affect hittest.\n  float GetScrollbarScrollOffset();\n  float GetTotalScrollLength();\n\n  ////////////////// Override BaseView End ////////////////\n\n  /////////// Called by ListLayoutManager Start ///////////\n  // Called when an item is created.\n  // To align with Lynx's behaviour, `OnCreateItem` won't be called during\n  // adapter's `CreateListItem`. Instead, it is delayed to `BindListItem` so\n  // that we know the actual position of the item.\n  void OnCreateItem(ListItemViewHolder* item, int position);\n  // An item is added to the list by the list manager. The corresponding view\n  // should be attached.\n  void OnAddItem(ListItemViewHolder* item, int index);\n  // An item is removed to the list by the list manager. The corresponding view\n  // should be detached.\n  void OnRemoveItem(ListItemViewHolder* item);\n  // An item is cleanup by the recycler. The corresponding view should be\n  // destroyed.\n  void OnDestroyItem(ListItemViewHolder* item);\n  // Note(Xietong): Different from OnCreate/Add/Remove/DestroyItem, which\n  // BaseListView really does manipulate the item views. This method is used to\n  // update the focus logic. The actual binding logic is handled by list\n  // adapter.\n  void OnBindListItem(ListItemViewHolder* item, int prev_position, int position,\n                      bool newly_created);\n\n  void OffsetChildren(float x, float y);\n  /////////// Called by ListLayoutManager End /////////////\n\n  void OnLayout(LayoutContext* context) override;\n  void DispatchLayout();\n\n  bool OnScrollBy(const FloatSize& distance);\n\n  // Return scroll offset including overscroll\n  FloatPoint TotalScrollOffset();\n\n  // Override FocusNode\n  FocusManager::TraversalResult OnTraversalOnScope(\n      FocusManager::Direction direction,\n      FocusManager::TraversalType traversal_type) override;\n\n  // Override ListAdapter::Observer\n  void OnItemRangeInserted(int position_start, int item_count) override;\n  void OnItemRangeRemoved(int position_start, int item_count) override;\n  void OnItemRangeChanged(\n      int position_start, int item_count,\n      std::unique_ptr<ListAdapter::Payload> payload) override;\n  void OnItemMoved(int from_position, int to_position) override;\n  void OnChanged() override;\n\n  // Override ListAdapterHelper::Consumer\n  void OffsetPositionsForInsert(int position_start, int item_count) override;\n  void OffsetPositionsForRemove(int position_start, int item_count) override;\n  void MarkViewHoldersChanged(\n      int position_start, int item_count,\n      std::unique_ptr<ListAdapter::Payload> payload) override;\n  void OffsetPositionsForMove(int from, int to) override;\n\n  bool IsItemFullSpan(int position) const;\n\n  // Override ListItemViewHolder::VisibilityObserver\n  // Dispatch the notification to callback manager.\n  void OnVisibilityChanged(ListItemViewHolder* item, bool visible) override;\n\n  void SetDelegate(Delegate* delegate) { delegate_ = delegate; }\n\n  /**\n   * @brief UI method, scroll list to specified `position` item with `offset`\n   * and `align_to`. And even item's child specified by `id` is supported.\n   *\n   * @param smooth whether enable animation or not.\n   * @param position item's index in list.\n   * @param offset negative means scrolling to end with abs(offset_),\n   * positive has opposite effect.\n   * @param align_to how target item or item's child align with list.\n   * @param id item's child, can be omitted.\n   * @param callback\n   */\n  void ScrollToPosition(\n      bool smooth, int position, float offset, AlignTo align_to,\n      const std::string& id,\n      const std::function<void(uint32_t, const std::string&)>& callback);\n\n  ListLayoutManager* GetLayoutManager() const { return layout_manager_.get(); }\n\n  void SetStickyEnabled(bool enabled);\n  void OnFocusNodeDestructed(FocusNode*) override;\n\n  void StopAnimation() override;\n  FloatPoint DoScroll(FloatPoint delta, bool is_user_input = true,\n                      bool ignore_repaint = false) override;\n  bool CanDragScrollOnX() const override;\n  bool CanDragScrollOnY() const override;\n  void NotifyScrollAnimationStart() {\n    SetScrollStatus(Scrollable::ScrollStatus::kAnimating);\n  }\n  void NotifyScrollAnimationEnd() {\n    SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n  }\n  void PrintChildren();\n\n#ifdef ENABLE_ACCESSIBILITY\n  int32_t GetSemanticsActions() const override;\n  int32_t GetSemanticsFlags() const override;\n  int32_t GetA11yScrollChildren() const override;\n#endif\n\n  bool OnScrollToMiddle(BaseView* target_view) override;\n\n  ScrollableDirection GetScrollableDirection() const override;\n\n protected:\n  // Create a view and added to the end of children list.\n  // Assume item's position has been set.\n  virtual BaseView* HandleCreateView(ListItemViewHolder* item) = 0;\n  virtual void HandleDestroyView(BaseView* to_destroy,\n                                 ListItemViewHolder* item) = 0;\n  void StartPrefetch(int32_t width, int32_t height);\n  void RegisterPrefetch();\n  void UnregisterPrefetch();\n  void UpdateScrollOrientation(ScrollDirection scroll_orientation);\n  /**\n   * Maintain scroll state. Make sure every time scroll state change will\n   * call to this, it should handle stop scroll cleanup and send notify to\n   * `layout_manager_`/`callback_manager_`\n   */\n  // TODO(hanhaoshen) make sure SetScrollState be called when status change\n  // TODO(liuguoliang): merge with ScrollStatus in Scrollable\n  void SetScrollState(ScrollState state);\n\n  void OnScrollStatusChange(ScrollStatus old_status) override;\n\n  virtual void OnLayoutComplete() {}\n\n  /**\n   * Consumes adapter updates and calculates which type of animations we want to\n   * run. Called in onMeasure and dispatchLayout.\n   * <p> This method may process only the pre-layout state of updates or all of\n   * them.\n   */\n  virtual void ProcessAdapterUpdates();\n\n  // attribute set from client\n  std::string layout_manager_type_ = attr_value::kListTypeSingle;\n  int layout_span_count_ = 1;\n\n  ScrollDirection scroll_orientation_ = ScrollDirection::kVertical;\n\n  // view id that used as callback arguments. This should be the id of the\n  // wrapper view. With callback_id_, Lynx will think the callback is sent from\n  // the wrapper (which is known by Lynx) instead of the actual list view.\n  int32_t callback_id_ = -1;\n\n private:\n  void OnDestroy() override;\n  void OnFlingAnimationStart();\n  void OnFlingAnimationEnd();\n\n  void ScrollToPosition(\n      bool smooth, int position, float offset, AlignTo align_to,\n      const std::string& id, const std::optional<FloatRect> target_rect,\n      const std::function<void(uint32_t, const std::string&)>& callback);\n  // This is to workaround an issue of Lynx:\n  // The location of the item view supposes to be determined by the list view.\n  // During the layout process, however, Lynx may set a wrong location for the\n  // item view.\n  // The solution is to maintain the latest location set by the list view to a\n  // cache during the layout process and flush the cache value to the actual\n  // location after the layout process.\n  void FlushChildrenLayout();\n\n  /**\n   * @brief Try to scroll the list view so that the view (could be an item view\n   * or a descendant of an item view) is completely visible (in box). If the\n   * view is larger than the frame box of the list view, the view will be\n   * partially visible with one border aligning with the list view.\n   *\n   * @param focus_view_rect view's rect relative to list\n   * @param align_to same as in `ScrollToPosition`\n   */\n  void MakeViewInBox(const FloatRect& focus_view_rect, AlignTo align_to);\n  bool OnScrollToVisible() override;\n\n  // Methods used to maintain the focus node in the list view.\n  // During the layout process of list view, item views will be detached and may\n  // be attached again. The re-attached item may or may not correspond to the\n  // same position.\n  // When a view is detached the focus info will be cleared so the focus will\n  // lost even it is attached again in the same layout pass.\n  //\n  // For list view, if the re-attached view corresponds to the same position, we\n  // should recover the focus info after the view is attached.\n  // The current implementation is to save the focus node info before layout. If\n  // there is any update during the layout claims that the focus node should be\n  // cleared, then we cleanup the saved info. After the layout process, we check\n  // if the cached info is still valid, and restore it if so.\n  void SaveFocusNodeInfo();\n  void RestoreFocusNodeInfo();\n  void ClearFocusNodeInfo();\n\n  bool IsMoveOnMainAxis(FocusManager::Direction direction) const;\n\n  /**\n   * Processes the fact that, as far as we can tell, the data set has completely\n   * changed.\n   *\n   * <ul>\n   *   <li>Once layout occurs, all attached items should be discarded or\n   *       animated.\n   *   <li>Attached items are labeled as invalid.\n   *   <li>Because items may still be prefetched between a \"data set completely\n   *       changed\" event and a layout event, all cached items are discarded.\n   * </ul>\n   *\n   * @param dispatch_item_changed Whether to call `onItemsChanged` during\n   *                              measure/layout.\n   */\n  void ProcessDataSetCompletelyChanged(bool dispatch_item_changed);\n  /**\n   * Mark all known views as invalid. Used in response to a, \"the whole world\n   * might have changed\" data change event.\n   */\n  void MarkKnownViewsInvalid();\n\n  /**\n   * Calculate border status from current layout status.\n   */\n  ScrollEventCallbackManager::BorderStatus CalculateBorderStatus();\n\n  // Returns List Scroller. List scroller is created lazily.\n  ListScroller* GetListScroller();\n  void ResetListScroller();\n\n  void FlushTasks();\n\n  FocusManager::TraversalResult OnTraversalOnScopeInternal(\n      FocusManager::Direction direction,\n      FocusManager::TraversalType traversal_type);\n\n  class ListPrefetchTask : public GapTask {\n   public:\n    void Run() override;\n    using GapTask::GapTask;\n  };\n\n  FRIEND_TEST(ListLayoutManagerLinearTest, Scroll);\n  FRIEND_TEST(ListLayoutManagerLinearTest, ScrollHorizontally);\n  FRIEND_TEST(ListLayoutManagerLinearTest, ScrollDiffHeight);\n  FRIEND_TEST(ListLayoutManagerLinearTest, RemoveData);\n  FRIEND_TEST(ListLayoutManagerLinearTest, ScrollToPositionImmediately);\n  FRIEND_TEST(ListLayoutManagerLinearTest, DISABLED_ScrollToPositionSmooth);\n  FRIEND_TEST(ListLayoutManagerLinearTest,\n              DISABLED_ScrollToPositionSmoothDataChange);\n  FRIEND_TEST(BaseListView, Fling);\n  FRIEND_TEST(ListLayoutManagerGridTest, Scroll);\n  FRIEND_TEST(ListLayoutManagerStaggeredGridTest, Scroll);\n  FRIEND_TEST(ListLayoutManagerStaggeredGridTest, Fling);\n  FRIEND_TEST(FocusListViewTest, AppearDisappearEvent);\n  FRIEND_TEST(FocusListViewTest, EventFindFocus);\n  FRIEND_TEST(FocusListViewTest, EventThrottling);\n\n  friend class ListScroller;\n  friend class LayoutPrefetchRegistry;\n\n  bool data_set_has_changed_after_layout_ = false;\n\n  ListViewState list_state_;\n  ScrollState scroll_state_ = ScrollState::kIdle;\n  // Defines the behavior when focus moving between list items, where the\n  // focused item should be positioned.\n  AlignTo align_focus_ = AlignTo::kNone;\n\n  std::unique_ptr<ListChildrenHelper> children_helper_;\n  ListAdapter* adapter_ = nullptr;\n  std::unique_ptr<ListAdapterHelper> adapter_helper_;\n  std::unique_ptr<ListLayoutManager> layout_manager_;\n  std::unique_ptr<ListRecycler> recycler_;\n\n  std::vector<BaseView*> detached_children_;\n\n  float scrolled_vertical_ = 0.f;\n  float scrolled_horizontal_ = 0.f;\n  float lower_threshold_pixel_ = 0.f;\n  float upper_threshold_pixel_ = 0.f;\n  std::optional<int> lower_threshold_item_count_;\n  std::optional<int> upper_threshold_item_count_;\n  std::unique_ptr<ListEventCallbackManager> callback_manager_;\n\n  // The cached information of the focus node inside the list view.\n  BaseView* focus_node_cache_ = nullptr;\n  int focus_node_pos_ = ListItemViewHolder::kNoPosition;\n  bool focus_node_attached_ = false;\n  // Keep track the previous focused node,to check whether got deleted during\n  // update\n  int pre_focus_node_pos_ = ListItemViewHolder::kNoPosition;\n  bool pre_focus_node_deleted_ = false;\n\n  // Enable smooth scroll when focus change inside list.\n  bool focus_smooth_scroll_ = true;\n  // Whether scroll to next item even if the item is not focusable.\n  bool scroll_without_focus_ = false;\n\n  bool dispatch_items_changed_event_ = false;\n\n  std::unique_ptr<ListScroller> scroller_;\n\n  std::vector<std::function<void()>> pending_tasks_;\n  std::unique_ptr<fml::Timer> task_timer_;\n\n  Delegate* delegate_ = nullptr;\n\n  bool sticky_enabled_ = false;\n  bool update_animation_enabled_ = false;\n  std::unique_ptr<ListAdapterUpdater> updater_;\n\n  int64_t traversal_perf_start_ = 0;\n  float unconsumed_x_ = 0.f;\n  float unconsumed_y_ = 0.f;\n\n  float cross_axis_gap_ = 0;\n  float main_axis_gap_ = 0;\n\n  int32_t last_add_item_position_ = -1;\n  int32_t last_add_item_position_when_prefetch_start_ = -1;\n  int32_t current_scroll_width_ = 0;\n  int32_t current_scroll_height_ = 0;\n  LayoutPrefetchRegistry prefetch_registry_;\n  GapTaskCollector gap_task_collector_;\n\n  // Owned by the ListView and shared with gap worker.\n  fml::RefPtr<GapTaskBundle> gap_task_bundle_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_BASE_LIST_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/list/base_list_view_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <forward_list>\n#include <memory>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/gfx/animation/animation_handler.h\"\n#include \"clay/ui/component/layout_controller.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_adapter.h\"\n#include \"clay/ui/component/list/list_children_helper.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_layout_manager_grid.h\"\n#include \"clay/ui/component/list/list_layout_manager_staggered_grid.h\"\n#include \"clay/ui/component/list/list_recycler.h\"\n#include \"clay/ui/component/nested_scroll/nested_scrollable.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/gesture/velocity_tracker.h\"\n#include \"clay/ui/testing/ui_test.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nnamespace {\nconstexpr int kDefaultSpanCount = 3;\n}  // namespace\n\nclass MockListItemViewHolder : public ListItemViewHolder {\n public:\n  MockListItemViewHolder(PageView* page) : page_(page) {}\n  virtual ~MockListItemViewHolder() = default;\n\n  BaseView* InitView() {\n    static int id = 0;\n    my_view_ = std::make_unique<View>(id++, page_);\n    SetView(my_view_.get());\n    return my_view_.get();\n  }\n  void ReleaseView() {\n    SetView(nullptr);\n    my_view_.reset();\n  }\n\n  MeasureResult Measure(const MeasureConstraint& constraint) override {\n    BaseView::LayoutIgnoreHelper helper(GetView());\n    switch (constraint.height_mode) {\n      case MeasureMode::kDefinite: {\n        GetView()->SetHeight(*constraint.height);\n        break;\n      }\n      default: {\n        GetView()->SetHeight(height_);\n        break;\n      }\n    }\n    switch (constraint.width_mode) {\n      case MeasureMode::kDefinite: {\n        GetView()->SetWidth(*constraint.width);\n        break;\n      }\n      default: {\n        GetView()->SetWidth(width_);\n        break;\n      }\n    }\n    return {GetView()->Width(), GetView()->Height()};\n  }\n\n public:\n  float height_ = 0.f;\n  float width_ = 0.f;\n  PageView* page_;\n  std::unique_ptr<View> my_view_;\n};\n\nclass MockAdapter : public ListAdapter {\n public:\n  explicit MockAdapter(BaseListView* list_view) : ListAdapter(list_view) {}\n  virtual ~MockAdapter() = default;\n\n  ListItemViewHolder* OnCreateListItem(TypeId type) override {\n    holders_.emplace_front(new MockListItemViewHolder(list_view_->page_view()));\n    return holders_.front().get();\n  }\n\n  void OnBindListItem(ListItemViewHolder* item, int prev_position, int position,\n                      bool newly_created) override {\n    static_cast<MockListItemViewHolder*>(item)->height_ =\n        height_list_[position];\n    static_cast<MockListItemViewHolder*>(item)->width_ = height_list_[position];\n  }\n\n  void OnDeleteListItem(ListItemViewHolder* view_holder) override {\n    holders_.remove_if(\n        [this, view_holder](std::unique_ptr<MockListItemViewHolder>& vh) {\n          if (vh.get() == view_holder) {\n            deleted_.emplace_back(vh.release());\n            return true;\n          }\n          return false;\n        });\n  }\n\n  int GetItemCount() const override { return height_list_.size(); }\n\n public:\n  std::vector<float> height_list_;\n  std::forward_list<std::unique_ptr<MockListItemViewHolder>> holders_;\n  std::vector<std::unique_ptr<MockListItemViewHolder>> deleted_;\n};\n\nclass MockListView : public BaseListView {\n public:\n  explicit MockListView(PageView* page,\n                        ScrollDirection orientation = kDefaultScrollDirection)\n      : BaseListView(0, \"mock-list\", page) {\n    SetLayoutManager(std::make_unique<ListLayoutManagerLinear>(orientation));\n    page_view_->AddChild(this);\n  }\n  virtual ~MockListView() {\n    SetAdapter(nullptr);\n    StopAnimation();\n  }\n\n  AnimationHandler* GetAnimationHandler() { return &animator_handler_; }\n\n protected:\n  void Invalidate() override { invalidated_ = true; }\n\n  BaseView* HandleCreateView(ListItemViewHolder* item) override {\n    auto* text_item = static_cast<MockListItemViewHolder*>(item);\n    BaseView* new_view = text_item->InitView();\n    AddChild(new_view, child_count());\n    return new_view;\n  }\n\n  void HandleDestroyView(BaseView* to_destroy,\n                         ListItemViewHolder* item) override {\n    auto* text_item = static_cast<MockListItemViewHolder*>(item);\n    text_item->ReleaseView();\n  }\n\n public:\n  bool invalidated_ = false;\n  LayoutController controller_;\n  AnimationHandler animator_handler_;\n};\n\nclass MockGridView : public MockListView {\n public:\n  MockGridView(PageView* page, int span_count,\n               ScrollDirection orientation = kDefaultScrollDirection)\n      : MockListView(page, orientation) {\n    SetLayoutManager(\n        std::make_unique<ListLayoutManagerGrid>(span_count, orientation));\n  }\n  virtual ~MockGridView() { SetAdapter(nullptr); }\n};\n\nclass MockStaggeredGridView : public MockListView {\n public:\n  explicit MockStaggeredGridView(\n      PageView* page, int span_count,\n      ScrollDirection orientation = kDefaultScrollDirection)\n      : MockListView(page, orientation) {\n    SetLayoutManager(std::make_unique<ListLayoutManagerStaggeredGrid>(\n        span_count, orientation));\n  }\n  virtual ~MockStaggeredGridView() { SetAdapter(nullptr); }\n};\n\nclass ListLayoutManagerTest : public UITest {};\nclass ListLayoutManagerLinearTest : public ListLayoutManagerTest {};\nclass ListLayoutManagerGridTest : public ListLayoutManagerTest {};\nclass ListLayoutManagerStaggeredGridTest : public ListLayoutManagerTest {};\n\nTEST_F_UI(ListLayoutManagerLinearTest, Layout) {\n  // Empty list\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(50.f);\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 0);\n  }\n\n  // 10 items with identical height\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(50.f);\n    for (int i = 0; i < 10; ++i) {\n      adapter.height_list_.emplace_back(10.f);\n    }\n\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n    std::vector<BaseView*>& children = list_view->GetChildren();\n    float expected_height = 0.f;\n    for (int i = 0; i < 5; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n  }\n\n  // 10 items with identical height, list has border.\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(50.f);\n    BordersData scroll_border_data;\n    scroll_border_data.width_top_ = 5.f;\n    scroll_border_data.width_bottom_ = 5.f;\n    list_view->SetBorder(scroll_border_data);\n    for (int i = 0; i < 10; ++i) {\n      adapter.height_list_.emplace_back(10.f);\n    }\n    list_view->Layout();\n    // 50 - 5 - 5 = 40.\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 4);\n    std::vector<BaseView*>& children = list_view->GetChildren();\n    float expected_height = 5.f;\n    for (int i = 0; i < 4; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n  }\n\n  // 10 items with different height\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetX(10.f);\n    list_view->SetY(10.f);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(50.f);\n    // 10 + 20 + 30(partial) > 50\n    for (int i = 0; i < 10; ++i) {\n      adapter.height_list_.emplace_back((i + 1) * 10.f);\n    }\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 3);\n    std::vector<BaseView*>& children = list_view->GetChildren();\n    float expected_height = 0.f;\n    for (int i = 0; i < 3; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += (i + 1) * 10.f;\n    }\n  }\n}\n\nTEST_F_UI(ListLayoutManagerLinearTest, Scroll) {\n  MockListView* list_view = new MockListView(page_.get());\n  MockAdapter adapter(list_view);\n  list_view->SetAdapter(&adapter);\n  list_view->SetWidth(200.f);\n  list_view->SetHeight(50.f);\n  BordersData scroll_border_data;\n  scroll_border_data.width_top_ = 5.f;\n  scroll_border_data.width_bottom_ = 5.f;\n  list_view->SetBorder(scroll_border_data);\n  for (int i = 0; i < 10; ++i) {\n    adapter.height_list_.emplace_back(10.f);\n  }\n  list_view->Layout();\n  list_view->invalidated_ = false;\n\n  // Scroll up but no space left.\n  list_view->OnScrollBy(FloatSize(0.f, 10.f));\n  std::vector<BaseView*>& children = list_view->GetChildren();\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 4);\n  float expected_height = 5.f;\n  for (int i = 0; i < 4; ++i) {\n    EXPECT_EQ(children[i]->Top(), expected_height);\n    expected_height += 10.f;\n  }\n  EXPECT_FALSE(list_view->invalidated_);\n\n  // Scroll down 5 px. Item on the head is not hidden yet but a new item is\n  // added to the tail.\n  list_view->OnScrollBy(FloatSize(0.f, -5.f));\n  EXPECT_TRUE(list_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n  expected_height = 0.f;\n  for (int i = 0; i < 5; ++i) {\n    EXPECT_EQ(children[i]->Top(), expected_height);\n    expected_height += 10.f;\n  }\n  list_view->invalidated_ = false;\n\n  // Scroll down another 5 px. Item on the head is recycled.\n  list_view->OnScrollBy(FloatSize(0.f, -5.f));\n  EXPECT_TRUE(list_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 4);\n  expected_height = 5.f;\n  for (int i = 0; i < 4; ++i) {\n    EXPECT_EQ(children[i]->Top(), expected_height);\n    expected_height += 10.f;\n  }\n  list_view->invalidated_ = false;\n\n  // Scroll up back 5 px. A recycled item is used as the head.\n  list_view->OnScrollBy(FloatSize(0.f, 5.f));\n  EXPECT_TRUE(list_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n  expected_height = 0.f;\n  for (int i = 0; i < 5; ++i) {\n    EXPECT_EQ(children[i]->Top(), expected_height);\n    expected_height += 10.f;\n  }\n  list_view->invalidated_ = false;\n\n  // Scroll down to the end.\n  list_view->OnScrollBy(FloatSize(0.f, -1000.f));\n  EXPECT_TRUE(list_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 4);\n  expected_height = 5.f;\n  for (int i = 0; i < 4; ++i) {\n    EXPECT_EQ(children[i]->Top(), expected_height);\n    expected_height += 10.f;\n  }\n  list_view->invalidated_ = false;\n\n  // Already at the end.\n  list_view->OnScrollBy(FloatSize(0.f, -1000.f));\n  EXPECT_FALSE(list_view->invalidated_);\n}\n\nTEST_F_UI(ListLayoutManagerLinearTest, ScrollHorizontally) {\n  MockListView* list_view =\n      new MockListView(page_.get(), ScrollDirection::kHorizontal);\n  MockAdapter adapter(list_view);\n  list_view->SetAdapter(&adapter);\n  list_view->SetWidth(50.f);\n  list_view->SetHeight(200.f);\n  BordersData scroll_border_data;\n  scroll_border_data.width_left_ = 5.f;\n  scroll_border_data.width_right_ = 5.f;\n  list_view->SetBorder(scroll_border_data);\n  for (int i = 0; i < 10; ++i) {\n    adapter.height_list_.emplace_back(10.f);\n  }\n  list_view->Layout();\n  list_view->invalidated_ = false;\n\n  // Scroll left but no space left.\n  list_view->OnScrollBy(FloatSize(10.f, 0.f));\n  std::vector<BaseView*>& children = list_view->GetChildren();\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 4);\n  float expected_left = 5.f;\n  for (int i = 0; i < 4; ++i) {\n    EXPECT_EQ(children[i]->Left(), expected_left);\n    expected_left += 10.f;\n  }\n  EXPECT_FALSE(list_view->invalidated_);\n\n  // Scroll right 5 px. Item on the head is not hidden yet but a new item is\n  // added to the tail.\n  list_view->OnScrollBy(FloatSize(-5.f, 0.f));\n  EXPECT_TRUE(list_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n  expected_left = 0.f;\n  for (int i = 0; i < 5; ++i) {\n    EXPECT_EQ(children[i]->Left(), expected_left);\n    expected_left += 10.f;\n  }\n  list_view->invalidated_ = false;\n\n  // Scroll right another 5 px. Item on the head is recycled.\n  list_view->OnScrollBy(FloatSize(-5.f, 0.f));\n  EXPECT_TRUE(list_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 4);\n  expected_left = 5.f;\n  for (int i = 0; i < 4; ++i) {\n    EXPECT_EQ(children[i]->Left(), expected_left);\n    expected_left += 10.f;\n  }\n  list_view->invalidated_ = false;\n\n  // Scroll left back 5 px. A recycled item is used as the head.\n  list_view->OnScrollBy(FloatSize(5.f, 0.f));\n  EXPECT_TRUE(list_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n  expected_left = 0.f;\n  for (int i = 0; i < 5; ++i) {\n    EXPECT_EQ(children[i]->Left(), expected_left);\n    expected_left += 10.f;\n  }\n  list_view->invalidated_ = false;\n\n  // Scroll right to the end.\n  list_view->OnScrollBy(FloatSize(-1000.f, 0.f));\n  EXPECT_TRUE(list_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 4);\n  expected_left = 5.f;\n  for (int i = 0; i < 4; ++i) {\n    EXPECT_EQ(children[i]->Left(), expected_left);\n    expected_left += 10.f;\n  }\n  list_view->invalidated_ = false;\n\n  // Already at the end.\n  list_view->OnScrollBy(FloatSize(-1000.f, 0.f));\n  EXPECT_FALSE(list_view->invalidated_);\n}\n\nTEST_F_UI(ListLayoutManagerLinearTest, ScrollDiffHeight) {\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(40.f);\n    for (int i = 0; i < 10; ++i) {\n      if (i == 4) {\n        adapter.height_list_.emplace_back(40.f);\n      } else {\n        adapter.height_list_.emplace_back(10.f);\n      }\n    }\n    list_view->Layout();\n\n    // Scroll down 40 px. Four items disappear and one appear.\n    list_view->OnScrollBy(FloatSize(0.f, -40.f));\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 1);\n  }\n\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(40.f);\n    for (int i = 0; i < 10; ++i) {\n      if (i >= 4) {\n        adapter.height_list_.emplace_back(2.f);\n      } else {\n        adapter.height_list_.emplace_back(10.f);\n      }\n    }\n    list_view->Layout();\n\n    // Scroll down 9 px. Two items disappear and one appear.\n    list_view->OnScrollBy(FloatSize(0.f, -9.f));\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 9);\n\n    // Scroll up 9 px.\n    list_view->OnScrollBy(FloatSize(0.f, 9.f));\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 4);\n  }\n}\n\nTEST_F_UI(ListLayoutManagerLinearTest, DISABLED_InsertData) {\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(100.f);\n    std::vector<float>& height_list = adapter.height_list_;\n    for (int i = 0; i < 5; ++i) {\n      height_list.emplace_back(10.f);\n    }\n    EXPECT_TRUE(list_view->NeedsLayout());\n    list_view->Layout();\n    EXPECT_FALSE(list_view->NeedsLayout());\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n\n    // Insert an item with 1.f height to head.\n    height_list.insert(height_list.begin(), 1.f);\n    adapter.NotifyItemRangeInserted(0, 1);\n    EXPECT_TRUE(list_view->NeedsLayout());\n    list_view->Layout();\n    EXPECT_FALSE(list_view->NeedsLayout());\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 6);\n    std::vector<BaseView*>& children = list_view->GetChildren();\n    EXPECT_EQ(children[0]->Top(), 0.f);\n    EXPECT_EQ(children[0]->Height(), 1.f);\n\n    float expected_height = 1.f;\n    for (int i = 1; i < 6; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n\n    // Insert 4 items with 1.f height to head at once.\n    for (int i = 0; i < 4; ++i) {\n      height_list.insert(height_list.begin(), 1.f);\n      adapter.NotifyItemRangeInserted(0, 1);\n    }\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 10);\n    expected_height = 0.f;\n    for (int i = 0; i < 5; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 1.f;\n    }\n    for (int i = 5; i < 10; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n\n    // Insert 10 items with 2.f height with one call\n    auto itr = height_list.begin();\n    for (int i = 0; i < 5; ++i) {\n      ++itr;\n    }\n    for (int i = 0; i < 10; ++i) {\n      itr = height_list.insert(itr, 2.f);\n    }\n    adapter.NotifyItemRangeInserted(5, 10);\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 20);\n    expected_height = 0.f;\n    for (int i = 0; i < 5; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 1.f;\n    }\n    for (int i = 5; i < 15; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 2.f;\n    }\n    for (int i = 15; i < 20; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n  }\n\n  // Fill the list from the head. When the all items height exceeds list view's\n  // height, should align with the bottom item.\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(50.f);\n    std::vector<float>& height_list = adapter.height_list_;\n    for (int i = 0; i < 4; ++i) {\n      height_list.emplace_back(10.f);\n    }\n    list_view->Layout();\n    height_list.insert(height_list.begin(), 11.f);\n    adapter.NotifyItemRangeInserted(0, 1);\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n    std::vector<BaseView*>& children = list_view->GetChildren();\n    float expected_height = -1.f;\n    EXPECT_EQ(children[0]->Top(), expected_height);\n    expected_height += 11.f;\n    for (int i = 1; i < 5; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n  }\n\n  // When the inserted position is not in the frame, it won't affect the current\n  // view.\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(50.f);\n    std::vector<float>& height_list = adapter.height_list_;\n    for (int i = 0; i < 10; ++i) {\n      height_list.emplace_back(10.f);\n    }\n    for (int i = 0; i < 10; ++i) {\n      height_list.emplace_back(5.f);\n    }\n    for (int i = 0; i < 10; ++i) {\n      height_list.emplace_back(10.f);\n    }\n    list_view->Layout();\n    // Scroll down 100 px. Now it should show 10 children with 5px height.\n    list_view->OnScrollBy({0.f, -100.f});\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 10);\n    std::vector<BaseView*>& children = list_view->GetChildren();\n    float expected_height = 0.f;\n    for (int i = 0; i < 10; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 5.f;\n    }\n\n    // Insert an item to index 9.\n    auto itr = height_list.begin();\n    for (int i = 0; i < 9; i++) {\n      ++itr;\n    }\n    height_list.insert(itr, 20.f);\n    adapter.NotifyItemRangeInserted(9, 1);\n    list_view->Layout();\n    // Should not change the current view.\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 10);\n    expected_height = 0.f;\n    for (int i = 0; i < 10; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 5.f;\n    }\n\n    // Scroll up 30 px. Should see the new item and the original item at 9.\n    list_view->OnScrollBy({0.f, 30.f});\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 6);\n    expected_height = 0.f;\n    EXPECT_EQ(children[0]->Top(), expected_height);\n    expected_height += 20.f;\n    EXPECT_EQ(children[1]->Top(), expected_height);\n    expected_height += 10.f;\n    for (int i = 2; i < 6; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 5.f;\n    }\n  }\n}\n\n// Recycler's Max Limit is tested in this case.\nTEST_F_UI(ListLayoutManagerLinearTest, RemoveData) {\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(100.f);\n    std::vector<float>& height_list = adapter.height_list_;\n    for (int i = 0; i < 10; ++i) {\n      height_list.emplace_back(10.f);\n    }\n    height_list.emplace_back(20.f);\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 10);\n    std::vector<BaseView*>& children = list_view->GetChildren();\n    float expected_height = 0.f;\n    for (int i = 0; i < 10; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n\n    // Scroll down 5 px. All 11 items should in the frame.\n    list_view->OnScrollBy({0.f, -5.f});\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 11);\n\n    // Remove two items from the head\n    height_list.erase(height_list.begin());\n    height_list.erase(height_list.begin());\n    adapter.NotifyItemRangeRemoved(0, 2);\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 9);\n    expected_height = 0.f;\n    for (int i = 0; i < 9; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n\n    // Remove one item from the tail\n    height_list.pop_back();\n    adapter.NotifyItemRangeRemoved(height_list.size() - 2, 1);\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 8);\n    expected_height = 0.f;\n    for (int i = 0; i < 8; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n\n    // Remove all items\n    height_list.clear();\n    adapter.NotifyItemRangeRemoved(0, 8);\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 0);\n  }\n\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(50.f);\n    std::vector<float>& height_list = adapter.height_list_;\n    for (int i = 0; i < 10; ++i) {\n      height_list.emplace_back(1.f);\n    }\n    for (int i = 0; i < 20; ++i) {\n      height_list.emplace_back(10.f);\n    }\n    for (int i = 0; i < 10; ++i) {\n      height_list.emplace_back(1.f);\n    }\n    list_view->Layout();\n    std::vector<BaseView*>& children = list_view->GetChildren();\n\n    list_view->OnScrollBy({0.f, -55.f});\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 6);\n    float expected_height = -5.f;\n    for (int i = 0; i < 6; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n\n    // Removing item out of frame should not affect the current view\n    {\n      auto itr = height_list.begin();\n      ++itr;\n      ++itr;\n      height_list.erase(itr);\n      adapter.NotifyItemRangeRemoved(2, 1);\n    }\n    {\n      auto itr = height_list.begin();\n      for (int i = 0; i < 34; ++i) {\n        ++itr;\n      }\n      height_list.erase(itr);\n      adapter.NotifyItemRangeRemoved(34, 1);\n    }\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 6);\n    expected_height = -5.f;\n    for (int i = 0; i < 6; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n  }\n}\n\nTEST_F_UI(ListLayoutManagerLinearTest, MoveData) {\n  // from > to\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(50.f);\n    std::vector<float>& height_list = adapter.height_list_;\n    for (int i = 0; i < 10; ++i) {\n      height_list.emplace_back(10.f);\n    }\n    height_list.emplace_back(1.f);\n    list_view->Layout();\n    std::vector<BaseView*>& children = list_view->GetChildren();\n\n    // Scroll Down 50 px. Then 5 ~ 9 items are shown.\n    list_view->OnScrollBy({0.f, -50.f});\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n    float expected_height = 0.f;\n    for (int i = 0; i < 5; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n\n    // Move the second to last (9th) item to head.\n    // Because 0 - 9 items having the same value, we don't need to operate on\n    // height_list.\n    adapter.NotifyItemMoved(9, 1);\n    list_view->Layout();\n    // The bottom should align with the 10th item.\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 6);\n    expected_height = -1.f;\n    for (int i = 0; i < 6; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n  }\n\n  // If the moved item is the current anchor, the view will try to scroll to the\n  // new position that the moved item is still the anchor.\n  {\n    MockListView* list_view = new MockListView(page_.get());\n    MockAdapter adapter(list_view);\n    list_view->SetAdapter(&adapter);\n    list_view->SetWidth(200.f);\n    list_view->SetHeight(50.f);\n    std::vector<float>& height_list = adapter.height_list_;\n    height_list.emplace_back(10.f);\n    height_list.emplace_back(11.f);\n    for (int i = 0; i < 4; ++i) {\n      height_list.emplace_back(10.f);\n    }\n    for (int i = 0; i < 5; ++i) {\n      height_list.emplace_back(20.f);\n    }\n    list_view->Layout();\n    std::vector<BaseView*>& children = list_view->GetChildren();\n\n    // Scroll Down 11 px, so that [1] item is the anchor.\n    list_view->OnScrollBy({0.f, -11.f});\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n    float expected_height = -1.f;\n    EXPECT_EQ(children[0]->Top(), expected_height);\n    expected_height += 11.f;\n    for (int i = 1; i < 5; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 10.f;\n    }\n\n    // Move [1] item to index 5\n    height_list[1] = 10.f;\n    height_list[5] = 11.f;\n    adapter.NotifyItemMoved(1, 5);\n    list_view->Layout();\n    EXPECT_EQ(static_cast<int>(list_view->child_count()), 3);\n    // The anchor is still the moved item.\n    expected_height = -1.f;\n    EXPECT_EQ(children[0]->Top(), expected_height);\n    expected_height += 11.f;\n    for (int i = 1; i < 3; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      expected_height += 20.f;\n    }\n  }\n}\n\nTEST_F_UI(ListLayoutManagerLinearTest, ChangeData) {\n  MockListView* list_view = new MockListView(page_.get());\n  MockAdapter adapter(list_view);\n  list_view->SetAdapter(&adapter);\n  list_view->SetWidth(200.f);\n  list_view->SetHeight(50.f);\n  std::vector<float>& height_list = adapter.height_list_;\n  for (int i = 0; i < 30; ++i) {\n    height_list.emplace_back(10.f);\n  }\n  list_view->Layout();\n  std::vector<BaseView*>& children = list_view->GetChildren();\n\n  for (int i = 2; i < 12; ++i) {\n    height_list[i] = 1.f;\n  }\n  adapter.NotifyItemRangeChanged(2, 10);\n  list_view->Layout();\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 14);\n  float expected_height = 0.f;\n  EXPECT_EQ(children[0]->Top(), expected_height);\n  expected_height += 10.f;\n  EXPECT_EQ(children[1]->Top(), expected_height);\n  expected_height += 10.f;\n  for (int i = 2; i < 12; ++i) {\n    EXPECT_EQ(children[i]->Top(), expected_height);\n    expected_height += 1.f;\n  }\n  EXPECT_EQ(children[12]->Top(), expected_height);\n  expected_height += 10.f;\n  EXPECT_EQ(children[13]->Top(), expected_height);\n  expected_height += 10.f;\n}\n\nTEST_F_UI(ListLayoutManagerGridTest, Layout) {\n  // Empty list\n  {\n    MockGridView* grid_view = new MockGridView(page_.get(), kDefaultSpanCount);\n    MockAdapter adapter(grid_view);\n    grid_view->SetAdapter(&adapter);\n    grid_view->SetWidth(200.f);\n    grid_view->SetHeight(50.f);\n    grid_view->Layout();\n    EXPECT_EQ(static_cast<int>(grid_view->child_count()), 0);\n  }\n\n  // 30 items with identical height\n  {\n    MockGridView* grid_view = new MockGridView(page_.get(), kDefaultSpanCount);\n    MockAdapter adapter(grid_view);\n    grid_view->SetAdapter(&adapter);\n    grid_view->SetWidth(200.f);\n    grid_view->SetHeight(50.f);\n    for (int i = 0; i < 30; ++i) {\n      adapter.height_list_.emplace_back(10.f);\n    }\n    grid_view->Layout();\n    EXPECT_EQ(static_cast<int>(grid_view->child_count()), 15);\n    std::vector<BaseView*>& children = grid_view->GetChildren();\n    float expected_height = -10.f;\n    for (int i = 0; i < 15; ++i) {\n      if (i % kDefaultSpanCount == 0) {\n        expected_height += 10.f;\n      }\n      EXPECT_EQ(children[i]->Top(), expected_height);\n    }\n  }\n\n  // 30 items with identical height, list has border.\n  {\n    MockGridView* grid_view = new MockGridView(page_.get(), kDefaultSpanCount);\n    MockAdapter adapter(grid_view);\n    grid_view->SetAdapter(&adapter);\n    grid_view->SetWidth(200.f);\n    grid_view->SetHeight(50.f);\n    BordersData scroll_border_data;\n    scroll_border_data.width_top_ = 5.f;\n    scroll_border_data.width_bottom_ = 5.f;\n    grid_view->SetBorder(scroll_border_data);\n    for (int i = 0; i < 30; ++i) {\n      adapter.height_list_.emplace_back(10.f);\n    }\n    grid_view->Layout();\n    // 50 - 5 - 5 = 40.\n    EXPECT_EQ(static_cast<int>(grid_view->child_count()), 12);\n    std::vector<BaseView*>& children = grid_view->GetChildren();\n    float expected_height = 5.f;\n    for (int i = 0; i < 12; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      if (i % kDefaultSpanCount == kDefaultSpanCount - 1) {\n        expected_height += 10.f;\n      }\n    }\n  }\n\n  // 30 items with different height\n  {\n    MockGridView* grid_view = new MockGridView(page_.get(), kDefaultSpanCount);\n    MockAdapter adapter(grid_view);\n    grid_view->SetAdapter(&adapter);\n    grid_view->SetWidth(200.f);\n    grid_view->SetHeight(50.f);\n    // first row is 30(max height), second row is 60.\n    for (int i = 0; i < 10; ++i) {\n      adapter.height_list_.emplace_back((i + 1) * 10.f);\n    }\n    grid_view->Layout();\n    EXPECT_EQ(static_cast<int>(grid_view->child_count()), 6);\n    std::vector<BaseView*>& children = grid_view->GetChildren();\n    float expected_height = -30.f;\n    for (int i = 0; i < 6; ++i) {\n      if (i % kDefaultSpanCount == 0) {\n        expected_height += 30.f;\n      }\n      EXPECT_EQ(children[i]->Top(), expected_height);\n    }\n  }\n}\n\nTEST_F_UI(ListLayoutManagerGridTest, Scroll) {\n  MockGridView* grid_view = new MockGridView(page_.get(), kDefaultSpanCount);\n  MockAdapter adapter(grid_view);\n  grid_view->SetAdapter(&adapter);\n  grid_view->SetWidth(200.f);\n  grid_view->SetHeight(50.f);\n  grid_view->Layout();\n\n  // The height of row is 30.f in first three, 20.f in last row.\n  for (int i = 0; i < 10; ++i) {\n    int height = 20.f;\n    if (i % (kDefaultSpanCount + 1) == 0) {\n      height = 30.f;\n    }\n    adapter.height_list_.emplace_back(height);\n  }\n  grid_view->Layout();\n  grid_view->invalidated_ = false;\n\n  // Scroll up but no space left.\n  grid_view->OnScrollBy(FloatSize(0.f, 10.f));\n  std::vector<BaseView*>& children = grid_view->GetChildren();\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 6);\n  float expected_height = -30.f;\n  for (int i = 0; i < 6; ++i) {\n    if (i % kDefaultSpanCount == 0) {\n      expected_height += 30.f;\n    }\n    EXPECT_EQ(children[i]->Top(), expected_height);\n  }\n  EXPECT_FALSE(grid_view->invalidated_);\n\n  // Scroll down 20 px. Item on the head is not hidden yet but a new item is\n  // added to the tail.\n  grid_view->OnScrollBy(FloatSize(0.f, -20.f));\n  EXPECT_TRUE(grid_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 9);\n  expected_height = -50.f;\n  for (int i = 0; i < 9; ++i) {\n    if (i % kDefaultSpanCount == 0) {\n      expected_height += 30.f;\n    }\n    EXPECT_EQ(children[i]->Top(), expected_height);\n  }\n  grid_view->invalidated_ = false;\n\n  // Scroll down another 15 px. Items on the head was recycled.\n  grid_view->OnScrollBy(FloatSize(0.f, -15.f));\n  EXPECT_TRUE(grid_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 6);\n  expected_height = -35.f;\n  for (int i = 0; i < 6; ++i) {\n    if (i % kDefaultSpanCount == 0) {\n      expected_height += 30.f;\n    }\n    EXPECT_EQ(children[i]->Top(), expected_height);\n  }\n  grid_view->invalidated_ = false;\n\n  // Scroll up back 15 px. Recycled items was add to head.\n  grid_view->OnScrollBy(FloatSize(0.f, 15.f));\n  EXPECT_TRUE(grid_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 9);\n  expected_height = -50.f;\n  for (int i = 0; i < 9; ++i) {\n    if (i % kDefaultSpanCount == 0) {\n      expected_height += 30.f;\n    }\n    EXPECT_EQ(children[i]->Top(), expected_height);\n  }\n  grid_view->invalidated_ = false;\n\n  // Scroll down to the end.\n  grid_view->OnScrollBy(FloatSize(0.f, -1000.f));\n  EXPECT_TRUE(grid_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 4);\n  expected_height = -30.f;\n  for (int i = 0; i < 4; ++i) {\n    if (i % kDefaultSpanCount == 0) {\n      expected_height += 30.f;\n    }\n    EXPECT_EQ(children[i]->Top(), expected_height);\n  }\n  grid_view->invalidated_ = false;\n\n  // Already at the end.\n  grid_view->OnScrollBy(FloatSize(0.f, -1000.f));\n  EXPECT_FALSE(grid_view->invalidated_);\n}\n\nTEST_F_UI(ListLayoutManagerStaggeredGridTest, Layout) {\n  // Empty list\n  {\n    MockStaggeredGridView* staggered_view =\n        new MockStaggeredGridView(page_.get(), kDefaultSpanCount);\n    MockAdapter adapter(staggered_view);\n    staggered_view->SetAdapter(&adapter);\n    staggered_view->SetWidth(200.f);\n    staggered_view->SetHeight(50.f);\n    staggered_view->Layout();\n    EXPECT_EQ(static_cast<int>(staggered_view->child_count()), 0);\n  }\n\n  // 30 items with identical height\n  {\n    MockStaggeredGridView* staggered_view =\n        new MockStaggeredGridView(page_.get(), kDefaultSpanCount);\n    MockAdapter adapter(staggered_view);\n    staggered_view->SetAdapter(&adapter);\n    staggered_view->SetWidth(200.f);\n    staggered_view->SetHeight(50.f);\n    for (int i = 0; i < 30; ++i) {\n      adapter.height_list_.emplace_back(10.f);\n    }\n    staggered_view->Layout();\n    EXPECT_EQ(static_cast<int>(staggered_view->child_count()), 15);\n    std::vector<BaseView*>& children = staggered_view->GetChildren();\n    float expected_height = -10.f;\n    for (int i = 0; i < 15; ++i) {\n      if (i % kDefaultSpanCount == 0) {\n        expected_height += 10.f;\n      }\n      EXPECT_EQ(children[i]->Top(), expected_height);\n    }\n  }\n\n  // 30 items with identical height, list has border.\n  {\n    MockStaggeredGridView* grid_view =\n        new MockStaggeredGridView(page_.get(), kDefaultSpanCount);\n    MockAdapter adapter(grid_view);\n    grid_view->SetAdapter(&adapter);\n    grid_view->SetWidth(200.f);\n    grid_view->SetHeight(50.f);\n    BordersData scroll_border_data;\n    scroll_border_data.width_top_ = 5.f;\n    scroll_border_data.width_bottom_ = 5.f;\n    grid_view->SetBorder(scroll_border_data);\n    for (int i = 0; i < 30; ++i) {\n      adapter.height_list_.emplace_back(10.f);\n    }\n    grid_view->Layout();\n    // 50 - 5 - 5 = 40.\n    EXPECT_EQ(static_cast<int>(grid_view->child_count()), 12);\n    std::vector<BaseView*>& children = grid_view->GetChildren();\n    float expected_height = 5.f;\n    for (int i = 0; i < 12; ++i) {\n      EXPECT_EQ(children[i]->Top(), expected_height);\n      if (i % kDefaultSpanCount == kDefaultSpanCount - 1) {\n        expected_height += 10.f;\n      }\n    }\n  }\n\n  // 30 items with different height\n  {\n    MockStaggeredGridView* grid_view =\n        new MockStaggeredGridView(page_.get(), kDefaultSpanCount);\n    MockAdapter adapter(grid_view);\n    grid_view->SetAdapter(&adapter);\n    grid_view->SetWidth(200.f);\n    grid_view->SetHeight(50.f);\n    // first row is 30(max height).\n    for (int i = 0; i < 10; ++i) {\n      adapter.height_list_.emplace_back((i + 1) * 10.f);\n    }\n    grid_view->Layout();\n    EXPECT_EQ(static_cast<int>(grid_view->child_count()), 6);\n    std::vector<BaseView*>& children = grid_view->GetChildren();\n    std::array<float, kDefaultSpanCount> expected_heights;\n    expected_heights.fill(0.f);\n    for (int i = 0; i < 6; ++i) {\n      int span_idx = i % kDefaultSpanCount;\n      EXPECT_EQ(children[i]->Top(), expected_heights[span_idx]);\n      expected_heights[span_idx] += 10.f * (i + 1);\n    }\n  }\n}\n\nTEST_F_UI(ListLayoutManagerStaggeredGridTest, Scroll) {\n  MockStaggeredGridView* grid_view =\n      new MockStaggeredGridView(page_.get(), kDefaultSpanCount);\n  MockAdapter adapter(grid_view);\n  grid_view->SetAdapter(&adapter);\n  grid_view->SetWidth(200.f);\n  grid_view->SetHeight(60.f);\n  grid_view->Layout();\n\n  for (int i = 0; i < 10; ++i) {\n    adapter.height_list_.emplace_back((i + 1) * 10.f);\n  }\n  grid_view->Layout();\n  grid_view->invalidated_ = false;\n\n  // Scroll up but no space left.\n  grid_view->OnScrollBy(FloatSize(0.f, 10.f));\n  std::vector<BaseView*>& children = grid_view->GetChildren();\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 7);\n\n  std::array<float, kDefaultSpanCount> expected_heights;\n  expected_heights.fill(0.f);\n  for (int i = 0; i < 7; ++i) {\n    int span_idx = i % kDefaultSpanCount;\n    EXPECT_EQ(children[i]->Top(), expected_heights[span_idx]);\n    expected_heights[span_idx] += 10.f * (i + 1);\n  }\n  EXPECT_FALSE(grid_view->invalidated_);\n\n  // Scroll down 5 px. Item on the head is not hidden yet but a new item is\n  // added to the tail.\n  grid_view->OnScrollBy(FloatSize(0.f, -5.f));\n  EXPECT_TRUE(grid_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 7);\n  expected_heights.fill(-5.f);\n  for (int i = 0; i < 7; ++i) {\n    int span_idx = i % kDefaultSpanCount;\n    EXPECT_EQ(children[i]->Top(), expected_heights[span_idx]);\n    expected_heights[span_idx] += 10.f * (i + 1);\n  }\n  grid_view->invalidated_ = false;\n\n  // Scroll down another 15 px. Two items on the head was recycled.\n  grid_view->OnScrollBy(FloatSize(0.f, -15.f));\n  EXPECT_TRUE(grid_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 6);\n  expected_heights[0] = -20.f;  // span 3\n  expected_heights[1] = -10.f;  // span 1\n  expected_heights[2] = 0.f;    // span 2\n  for (int i = 0; i < 6; ++i) {\n    int span_idx = i % kDefaultSpanCount;\n    EXPECT_EQ(children[i]->Top(), expected_heights[span_idx]);\n    expected_heights[span_idx] += 10.f * (i + 3);\n  }\n  grid_view->invalidated_ = false;\n\n  // Scroll up back 15 px. Recycled items was add to head.\n  grid_view->OnScrollBy(FloatSize(0.f, 15.f));\n  EXPECT_TRUE(grid_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 7);\n  expected_heights.fill(-5.f);\n  for (int i = 0; i < 7; ++i) {\n    int span_idx = i % kDefaultSpanCount;\n    EXPECT_EQ(children[i]->Top(), expected_heights[span_idx]);\n    expected_heights[span_idx] += 10.f * (i + 1);\n  }\n  grid_view->invalidated_ = false;\n  // Scroll down to the end.\n  grid_view->OnScrollBy(FloatSize(0.f, -1000.f));\n  EXPECT_TRUE(grid_view->invalidated_);\n  EXPECT_EQ(static_cast<int>(grid_view->child_count()), 3);\n  EXPECT_EQ(children[0]->Top(), -90.f);\n  EXPECT_EQ(children[0]->Height(), 80.f);\n  EXPECT_EQ(children[1]->Top(), -70.f);\n  EXPECT_EQ(children[1]->Height(), 90.f);\n  EXPECT_EQ(children[2]->Top(), -40.f);\n  EXPECT_EQ(children[2]->Height(), 100.f);\n  grid_view->invalidated_ = false;\n\n  // Already at the end.\n  grid_view->OnScrollBy(FloatSize(0.f, -1000.f));\n  EXPECT_FALSE(grid_view->invalidated_);\n}\n\nTEST_F_UI(ListLayoutManagerLinearTest, ScrollToPositionImmediately) {\n  MockListView* list_view = new MockListView(page_.get());\n  MockAdapter adapter(list_view);\n  list_view->SetAdapter(&adapter);\n  list_view->SetWidth(200.f);\n  list_view->SetHeight(50.f);\n  for (int i = 0; i < 10; ++i) {\n    adapter.height_list_.emplace_back(10.f);\n  }\n  list_view->Layout();\n  list_view->invalidated_ = false;\n\n  // Offset or AlignTo is not supported yet.\n  bool callback_called = false;\n  list_view->ScrollToPosition(\n      false, 9, 0.f, AlignTo::kNone, \"\",\n      [&callback_called](uint32_t code, const std::string& unused) {\n        EXPECT_EQ(code, static_cast<uint32_t>(LynxUIMethodResult::kSuccess));\n        callback_called = true;\n      });\n  EXPECT_TRUE(callback_called);\n\n  std::vector<BaseView*>& children = list_view->GetChildren();\n  EXPECT_EQ(static_cast<int>(list_view->child_count()), 5);\n  EXPECT_EQ(children[0]->Top(), 0.f);\n  ListChildrenHelper* children_helper = list_view->children_helper_.get();\n  EXPECT_EQ(children_helper->GetChildAt(0)->GetPosition(), 5);\n}\n\nTEST_F_UI(ListLayoutManagerLinearTest, DISABLED_ScrollToPositionSmooth) {\n  MockListView* list_view = new MockListView(page_.get());\n  MockAdapter adapter(list_view);\n  list_view->SetAdapter(&adapter);\n  list_view->SetWidth(200.f);\n  list_view->SetHeight(50.f);\n  for (int i = 0; i < 200; ++i) {\n    adapter.height_list_.emplace_back(10.f);\n  }\n  list_view->Layout();\n  list_view->invalidated_ = false;\n\n  ListScroller* scroller = list_view->scroller_.get();\n  EXPECT_EQ(scroller, nullptr);\n\n  bool callback_called = false;\n  list_view->ScrollToPosition(\n      true, 100, 0.f, AlignTo::kMiddle, \"\",\n      [&callback_called](uint32_t code, const std::string& unused) {\n        EXPECT_EQ(code, static_cast<uint32_t>(LynxUIMethodResult::kSuccess));\n        callback_called = true;\n      });\n  // For smooth scrolling, callback will be called after the scrolling finished.\n  EXPECT_FALSE(callback_called);\n\n  scroller = list_view->scroller_.get();\n  EXPECT_NE(scroller, nullptr);\n  EXPECT_TRUE(scroller->Scrolling());\n  scroller->OnAnimation(fml::TimePoint::Now().ToEpochDelta().ToMillisecondsF());\n\n  EXPECT_TRUE(list_view->NeedsLayout());\n  list_view->Layout();\n\n  // The first iteration is the \"far-away\" path. Will scroll to position 52.\n  ListChildrenHelper* children_helper = list_view->children_helper_.get();\n  EXPECT_EQ(children_helper->GetChildAt(4)->GetPosition(), 52);\n\n  // Not reach the target position yet. But start to slow down.\n  int last_pos = 52;\n  const int step = 50 / 10;\n\n  auto start_time = fml::TimePoint::Now().ToEpochDelta().ToMillisecondsF();\n  scroller->OnAnimation(start_time);\n\n#define TARGET_ITEM() \\\n  scroller->GetChildrenHelper()->FindChildByPosition(scroller->position_)\n\n  ListItemViewHolder* target_item;\n  // Will use this step until seeing the target position\n  do {\n    start_time += 30;\n    EXPECT_EQ(last_pos + step, children_helper->GetChildAt(4)->GetPosition());\n    last_pos = children_helper->GetChildAt(4)->GetPosition();\n    EXPECT_TRUE(scroller->Scrolling());\n    target_item = TARGET_ITEM();\n    scroller->OnAnimation(start_time);\n  } while (target_item == nullptr);\n\n  float last_distance = scroller->DistanceToTarget(TARGET_ITEM());\n  while (scroller->Scrolling()) {\n    start_time += 16;\n    scroller->OnAnimation(start_time);\n    EXPECT_LT(scroller->DistanceToTarget(TARGET_ITEM()), last_distance);\n    last_distance = scroller->DistanceToTarget(TARGET_ITEM());\n  }\n\n  EXPECT_EQ(children_helper->GetChildAt(2)->GetPosition(), 100);\n  EXPECT_TRUE(callback_called);\n}\n\n// Data change during smooth scrolling\nTEST_F_UI(ListLayoutManagerLinearTest,\n          DISABLED_ScrollToPositionSmoothDataChange) {\n  MockListView* list_view = new MockListView(page_.get());\n  MockAdapter adapter(list_view);\n  list_view->SetAdapter(&adapter);\n  list_view->SetWidth(200.f);\n  list_view->SetHeight(50.f);\n  for (int i = 0; i < 200; ++i) {\n    adapter.height_list_.emplace_back(10.f);\n  }\n  list_view->Layout();\n  list_view->invalidated_ = false;\n\n  list_view->ScrollToPosition(true, 190, 0.f, AlignTo::kStart, \"\", nullptr);\n\n  // Case 1: remove items during scrolling.\n  ListScroller* scroller = list_view->scroller_.get();\n  ListChildrenHelper* children_helper = list_view->children_helper_.get();\n  while (children_helper->GetChildAt(4)->GetPosition() < 100) {\n    scroller->OnAnimation(\n        fml::TimePoint::Now().ToEpochDelta().ToMillisecondsF() + 16);\n  }\n\n  adapter.height_list_.resize(100);\n  adapter.NotifyItemRangeRemoved(100, 100);\n\n  // Will Stop earlier.\n  scroller->OnAnimation(fml::TimePoint::Now().ToEpochDelta().ToMillisecondsF() +\n                        540);\n  EXPECT_FALSE(scroller->Scrolling());\n\n  // Case 2: add items during scrolling.\n  list_view->ScrollToPosition(true, 0, 0.f, AlignTo::kStart, \"\", nullptr);\n  // The first iteration uses \"far-away\" path.\n  // Then it starts to scroll with step 5-positions/iteration. If we add 5 items\n  // to the list(which will \"push\" the current position down by 5 items), the\n  // scrolling should keep going.\n  for (int i = 0; i < 30; ++i) {\n    for (int j = 0; j < 5; ++j) {\n      // Should insert to the font. But it doesn't matter in our case because\n      // all items have the same value.\n      adapter.height_list_.push_back(10.f);\n    }\n    adapter.NotifyItemRangeInserted(0, 5);\n    scroller->OnAnimation(\n        fml::TimePoint::Now().ToEpochDelta().ToMillisecondsF());\n    EXPECT_TRUE(scroller->Scrolling());\n  }\n\n  while (scroller->Scrolling()) {\n    scroller->OnAnimation(\n        fml::TimePoint::Now().ToEpochDelta().ToMillisecondsF());\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/focus_list_adapter.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/focus_list_adapter.h\"\n\n#include <cstdlib>\n#include <ctime>\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/macros.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/focus_list_item_view_holder.h\"\n#include \"clay/ui/component/list/macros.h\"\n\nnamespace clay {\n\nFocusListAdapter::FocusListAdapter(BaseListView* list_view)\n    : ListAdapter(list_view) {}\nFocusListAdapter::~FocusListAdapter() = default;\n\nListItemViewHolder* FocusListAdapter::OnCreateListItem(TypeId type) {\n  auto item = std::make_unique<FocusListItemViewHolder>(\n      list_view_->page_view(), focusable_, column_count_, row_count_,\n      default_length_);\n  items_.emplace_front(std::move(item));\n  return items_.front().get();\n}\n\nvoid FocusListAdapter::OnBindListItem(ListItemViewHolder* item,\n                                      int prev_position, int position,\n                                      bool newly_created) {\n  FML_DCHECK(position < count_);\n  static_cast<FocusListItemViewHolder*>(item)->OnUpdatePosition();\n}\n\nvoid FocusListAdapter::OnDeleteListItem(ListItemViewHolder* view_holder) {\n  auto before = items_.before_begin();\n  auto itr = items_.begin();\n  for (; itr != items_.end();) {\n    if (itr->get() == view_holder) {\n      items_.erase_after(before);\n      break;\n    }\n    before = itr;\n    ++itr;\n  }\n}\n\nint FocusListAdapter::GetItemCount() const { return count_; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/focus_list_adapter.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_FOCUS_LIST_ADAPTER_H_\n#define CLAY_UI_COMPONENT_LIST_FOCUS_LIST_ADAPTER_H_\n\n#include <forward_list>\n#include <functional>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/ui/component/list/list_adapter.h\"\n\nnamespace clay {\n\nclass BaseListView;\n\n// List Adapter used to test the focus behaviour of list view.\n// Each item will have 9 sub views in the same color. An item may have sub views\n// with different colors from other items and there are 4 colors in total. Only\n// the red ones are focusable.\nclass FocusListAdapter : public ListAdapter {\n public:\n  using IsFocusable = std::function<bool(int position)>;\n\n  explicit FocusListAdapter(BaseListView* list_view);\n  virtual ~FocusListAdapter();\n\n  void SetCount(int count) { count_ = count; }\n\n  void SetFocusable(const IsFocusable& focusable) { focusable_ = focusable; }\n\n  void SetColumnRow(int column, int row) {\n    column_count_ = column;\n    row_count_ = row;\n  }\n\n  void SetItemDefaultDimension(float length) { default_length_ = length; }\n\n  int GetItemCount() const override;\n\n protected:\n  // Override ListAdapter\n  ListItemViewHolder* OnCreateListItem(TypeId type) override;\n  void OnBindListItem(ListItemViewHolder* item, int prev_position, int position,\n                      bool newly_created) override;\n  void OnDeleteListItem(ListItemViewHolder* view_holder) override;\n\n private:\n  int count_ = 0;\n  std::forward_list<std::unique_ptr<ListItemViewHolder>> items_;\n  IsFocusable focusable_;\n  int column_count_ = 3;\n  int row_count_ = 3;\n  float default_length_ = 0.f;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_FOCUS_LIST_ADAPTER_H_\n"
  },
  {
    "path": "clay/ui/component/list/focus_list_item_view_holder.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/focus_list_item_view_holder.h\"\n\n#include <algorithm>\n#include <array>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/ui/component/view.h\"\n\nnamespace clay {\nnamespace {\n\nconstexpr float kBoxSize = 50.f;\nconstexpr float kPadding = 5.f;\nconst std::array<Color, 4> kColors = {\n    Color::ARGBColor(0xFF, 0xFF, 0x00, 0x00),\n    Color::ARGBColor(0xFF, 0x00, 0xFF, 0x00),\n    Color::ARGBColor(0xFF, 0x00, 0x00, 0xFF),\n    Color::ARGBColor(0xFF, 0x00, 0x00, 0x00),\n};\n\n}  // namespace\n\nFocusListItemViewHolder::FocusListItemViewHolder(PageView* page_view,\n                                                 const IsFocusable& focusable,\n                                                 int column, int row,\n                                                 float default_length)\n    : page_view_(page_view),\n      focusable_(focusable),\n      column_(column),\n      row_(row),\n      default_length_(default_length) {}\n\nFocusListItemViewHolder::~FocusListItemViewHolder() = default;\n\nBaseView* FocusListItemViewHolder::InitViews() {\n  container_view_ = std::make_unique<View>(-1, page_view_);\n  BordersData scroll_border_data;\n  scroll_border_data.width_top_ = 5.f;\n  scroll_border_data.width_bottom_ = 5.f;\n  scroll_border_data.width_right_ = 5.f;\n  scroll_border_data.width_left_ = 5.f;\n  scroll_border_data.color_top_ = 0xFFFF0000;\n  scroll_border_data.color_bottom_ = 0xFFFF0000;\n  scroll_border_data.color_right_ = 0xFFFF0000;\n  scroll_border_data.color_left_ = 0xFFFF0000;\n  container_view_->SetBorder(scroll_border_data);\n  container_view_->SetPaddings(kPadding, kPadding, kPadding, kPadding);\n  boxes_.resize(column_ * row_);\n  for (auto& box : boxes_) {\n    box = std::make_unique<View>(-1, page_view_);\n    box->SetWidth(kBoxSize);\n    box->SetHeight(kBoxSize);\n    box->SetFocusable(true);\n    container_view_->AddChild(box.get());\n  }\n\n  SetView(container_view_.get());\n  return container_view_.get();\n}\n\nvoid FocusListItemViewHolder::ReleaseViews() {\n  SetView(nullptr);\n  for (auto& box : boxes_) {\n    container_view_->RemoveChild(box.get());\n    box = nullptr;\n  }\n  container_view_ = nullptr;\n}\n\nvoid FocusListItemViewHolder::OnUpdatePosition() {\n  for (const auto& box : boxes_) {\n    box->SetBackgroundColor(kColors[GetPosition() % kColors.size()]);\n    box->SetFocusable(focusable_(GetPosition()));\n  }\n  if (boxes_.size() == 0) {\n    container_view_->SetFocusable(focusable_(GetPosition()));\n  }\n}\n\nMeasureResult FocusListItemViewHolder::Measure(\n    const MeasureConstraint& constraint) {\n  MeasureResult res;\n\n  {\n    BaseView::LayoutIgnoreHelper helper(container_view_.get());\n    if (constraint.width_mode != MeasureMode::kIndefinite &&\n        constraint.width.has_value()) {\n      container_view_->SetWidth(*constraint.width);\n    } else if (constraint.width_mode == MeasureMode::kIndefinite) {\n      container_view_->SetWidth(default_length_);\n    }\n\n    if (constraint.height_mode != MeasureMode::kIndefinite &&\n        constraint.height.has_value()) {\n      container_view_->SetHeight(*constraint.height);\n    } else if (constraint.height_mode == MeasureMode::kIndefinite) {\n      container_view_->SetHeight(default_length_);\n    }\n  }\n\n  const float width = container_view_->Width();\n  const float height = container_view_->Height();\n\n  res.width = width;\n  res.height = height;\n\n  const float x_step = width / column_;\n  const float y_step = height / row_;\n\n  for (int i = 0; i < row_; ++i) {\n    for (int j = 0; j < column_; ++j) {\n      boxes_[i * column_ + j]->SetX(j * x_step);\n      boxes_[i * column_ + j]->SetY(i * y_step);\n    }\n  }\n\n  // Clear the dirty bit.\n  container_view_->Layout();\n\n  return res;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/focus_list_item_view_holder.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_FOCUS_LIST_ITEM_VIEW_HOLDER_H_\n#define CLAY_UI_COMPONENT_LIST_FOCUS_LIST_ITEM_VIEW_HOLDER_H_\n\n#include <functional>\n#include <memory>\n#include <vector>\n\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n\nnamespace clay {\n\nclass PageView;\nclass View;\n\nclass FocusListItemViewHolder : public ListItemViewHolder {\n public:\n  using IsFocusable = std::function<bool(int position)>;\n\n  explicit FocusListItemViewHolder(PageView* page_view,\n                                   const IsFocusable& focusable, int column,\n                                   int row, float default_length);\n  virtual ~FocusListItemViewHolder();\n\n  BaseView* InitViews();\n  void ReleaseViews();\n\n  void OnUpdatePosition();\n\n  MeasureResult Measure(const MeasureConstraint& constraint) override;\n\n private:\n  std::unique_ptr<View> container_view_;\n  std::vector<std::unique_ptr<View>> boxes_;\n  PageView* page_view_ = nullptr;\n  IsFocusable focusable_;\n  int column_;\n  int row_;\n  float default_length_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_FOCUS_LIST_ITEM_VIEW_HOLDER_H_\n"
  },
  {
    "path": "clay/ui/component/list/focus_list_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/focus_list_view.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/component/list/focus_list_adapter.h\"\n#include \"clay/ui/component/list/focus_list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_layout_manager_grid.h\"\n#include \"clay/ui/component/list/list_layout_manager_linear.h\"\n#include \"clay/ui/component/list/list_layout_manager_staggered_grid.h\"\n\nnamespace clay {\n\nBaseFocusListView::BaseFocusListView(int id, PageView* page_view)\n    : BaseListView(id, \"focus-list\", page_view) {\n  SetFocusable(true);\n}\n\nBaseFocusListView::~BaseFocusListView() { SetAdapter(nullptr); }\n\nvoid BaseFocusListView::Init() {\n  if (adapter_factory_) {\n    focus_list_adapter_ = adapter_factory_(this);\n  } else {\n    focus_list_adapter_ = std::make_unique<FocusListAdapter>(this);\n  }\n  focus_list_adapter_->SetCount(count_);\n  focus_list_adapter_->SetFocusable(focusable_rule_);\n  focus_list_adapter_->SetColumnRow(column_count_, row_count_);\n  focus_list_adapter_->SetItemDefaultDimension(default_length_);\n  SetAdapter(focus_list_adapter_.get());\n  SetLayoutManager(\n      std::make_unique<ListLayoutManagerLinear>(ScrollDirection::kVertical));\n}\n\nBaseView* BaseFocusListView::HandleCreateView(ListItemViewHolder* item) {\n  auto* a_item = static_cast<FocusListItemViewHolder*>(item);\n\n  BaseView* new_view = a_item->InitViews();\n\n  AddChild(new_view, child_count());\n  return new_view;\n}\n\nvoid BaseFocusListView::HandleDestroyView(BaseView* to_destroy,\n                                          ListItemViewHolder* item) {\n  RemoveChild(to_destroy);\n  auto* a_item = static_cast<FocusListItemViewHolder*>(item);\n\n  a_item->ReleaseViews();\n}\n\nFocusListView::FocusListView(int id, PageView* page_view)\n    : BaseFocusListView(id, page_view) {\n  SetCount(7);\n  SetFocusableRule([](int position) { return position % 4 == 0; });\n  SetColumnRow(3, 3);\n  Init();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/focus_list_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_FOCUS_LIST_VIEW_H_\n#define CLAY_UI_COMPONENT_LIST_FOCUS_LIST_VIEW_H_\n\n#include <functional>\n#include <memory>\n\n#include \"clay/ui/component/list/base_list_view.h\"\n\nnamespace clay {\n\nclass FocusListAdapter;\n\nclass BaseFocusListView : public BaseListView {\n public:\n  using IsFocusable = std::function<bool(int position)>;\n  using AdapterFactory =\n      std::function<std::unique_ptr<FocusListAdapter>(BaseFocusListView*)>;\n\n  BaseFocusListView(int id, PageView* page_view);\n  ~BaseFocusListView() override;\n\n  void SetCount(int count) { count_ = count; }\n  void SetFocusableRule(const IsFocusable& focusable_rule) {\n    focusable_rule_ = focusable_rule;\n  }\n\n  void SetAdapterFactory(const AdapterFactory& factory) {\n    adapter_factory_ = factory;\n  }\n\n  void SetColumnRow(int column, int row) {\n    column_count_ = column;\n    row_count_ = row;\n  }\n\n  void SetItemDefaultDimension(float length) { default_length_ = length; }\n\n  void Init();\n\n protected:\n  BaseView* HandleCreateView(ListItemViewHolder* item) override;\n  void HandleDestroyView(BaseView* to_destroy,\n                         ListItemViewHolder* item) override;\n\n private:\n  FRIEND_TEST(FocusListViewTest, Focus);\n  FRIEND_TEST(FocusListViewTest, EventFindFocus);\n\n  int count_ = 0;\n  IsFocusable focusable_rule_;\n  AdapterFactory adapter_factory_;\n  int column_count_ = 3;\n  int row_count_ = 3;\n  float default_length_ = 300.f;\n\n  std::unique_ptr<FocusListAdapter> focus_list_adapter_;\n};\n\nclass FocusListView : public BaseFocusListView {\n public:\n  FocusListView(int id, PageView* page_view);\n  ~FocusListView() override = default;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_FOCUS_LIST_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/list/focus_list_view_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/thread.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/focus_list_adapter.h\"\n#include \"clay/ui/component/list/focus_list_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/testing/ui_test.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace {\n[[maybe_unused]] std::unique_ptr<View> CreateView(PageView* page_view,\n                                                  float left, float top,\n                                                  float width, float height) {\n  auto view = std::unique_ptr<View>(new View(-1, page_view));\n  view->SetX(left);\n  view->SetY(top);\n  view->SetWidth(width);\n  view->SetHeight(height);\n  page_view->AddChild(view.get());\n  view->SetFocusable(true);\n  return view;\n}\n\n[[maybe_unused]] std::unique_ptr<KeyEvent> MakeEvent(KeyCode dir) {\n  return std::make_unique<KeyEvent>(\n      0, KeyEventType::kDown, PhysicalKeyboardKey::kUnknown, dir, false, \"\");\n}\n\nvoid SetAppearanceEvent(ListItemViewHolder* item, bool appear, bool disappear) {\n  BaseView* view = item->GetView();\n  FML_CHECK(view);\n  const std::string item_key = std::to_string(item->GetPosition());\n  view->SetAttribute(\"item-key\", clay::Value(item_key));\n  if (appear) {\n    view->AddEventCallback(event_attr::kEventNodeAppear);\n  }\n  if (disappear) {\n    view->AddEventCallback(event_attr::kEventNodeDisappear);\n  }\n}\n\nclass EventFocusListAdapter : public FocusListAdapter {\n public:\n  explicit EventFocusListAdapter(BaseListView* list_view)\n      : FocusListAdapter(list_view) {}\n\n protected:\n  void OnBindListItem(ListItemViewHolder* item, int prev_position, int position,\n                      bool newly_created) override {\n    SetAppearanceEvent(item, true, true);\n    FocusListAdapter::OnBindListItem(item, prev_position, position,\n                                     newly_created);\n  }\n};\n\nclass EventFocusListView : public BaseFocusListView {\n public:\n  explicit EventFocusListView(PageView* page_view)\n      : BaseFocusListView(-1, page_view) {\n    SetCount(50);\n    SetFocusableRule([](int position) { return position >= 8; });\n    SetAdapterFactory([](BaseFocusListView* thiz) {\n      return std::make_unique<EventFocusListAdapter>(thiz);\n    });\n    SetColumnRow(0, 0);\n    SetItemDefaultDimension(100.f);\n    Init();\n  }\n  ~EventFocusListView() override = default;\n};\n\n}  // namespace\n\nclass FocusListViewTest : public UITest {};\n#if !defined(OS_WIN) && !defined(OS_MAC) && !defined(OS_HARMONY)\nTEST_F_UI(FocusListViewTest, Focus) {\n  constexpr float kListWidth = 400.f;\n  constexpr float kListHeight = 500.f;\n  constexpr float kViewDefaultWidth = 100.f;\n  constexpr float kViewDefaultHeight = 50.f;\n  SetAllowListPrefetch(false);\n\n  auto thread = std::make_unique<fml::Thread>(\"platform\");\n\n  page_->SetDefaultFocusRingEnabled(true);\n\n  //          view1\n  // view0    list      view2\n  //          view3\n  std::vector<std::unique_ptr<View>> views;\n  views.emplace_back(CreateView(page_.get(), 0.f, kViewDefaultHeight,\n                                kViewDefaultWidth, kListHeight));\n  views.emplace_back(CreateView(page_.get(), kViewDefaultWidth, 0.f, kListWidth,\n                                kViewDefaultHeight));\n  views.emplace_back(CreateView(page_.get(), kViewDefaultWidth + kListWidth,\n                                kViewDefaultHeight, kViewDefaultWidth,\n                                kListHeight));\n  views.emplace_back(CreateView(page_.get(), kViewDefaultWidth,\n                                kViewDefaultHeight + kListHeight, kListWidth,\n                                kViewDefaultHeight));\n\n  // The list view will contain 7 items. Subviews in item 0 and 4 are focusable.\n  auto list_view = std::make_unique<BaseFocusListView>(-1, page_.get());\n  list_view->SetAttribute(\"scroll-without-focus\", clay::Value(true));\n  list_view->SetAttribute(\"focus-smooth-scroll\", clay::Value(false));\n  list_view->SetSkipFocusTraversal(false);\n  list_view->SetCount(7);\n  list_view->SetFocusableRule([](int position) { return position % 4 == 0; });\n  list_view->SetColumnRow(3, 3);\n  list_view->Init();\n\n  page_->AddChild(list_view.get());\n  list_view->SetX(kViewDefaultWidth);\n  list_view->SetY(kViewDefaultHeight);\n  list_view->SetWidth(kListWidth);\n  list_view->SetHeight(kListHeight);\n  list_view->Layout();\n  auto* manager = list_view->GetFocusManager();\n  manager->preference().pick_first = false;\n  manager->preference().pick_left_when_same_y = false;\n\n  FocusManager* root_focus = page_->GetFocusManager();\n  root_focus->preference().switch_up_to_once_per_frame = false;\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool handled) {\n#if !defined(OS_MACOSX) && !defined(OS_WIN)\n    EXPECT_TRUE(handled);\n#endif\n  });\n  EXPECT_EQ(root_focus->GetLeafFocusedNode(), views[1].get());\n\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  EXPECT_EQ(root_focus->GetLeafFocusedNode(), list_view.get());\n\n  // focus on child (0, 2)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowLeft), [](bool) {});\n  // focus on child (0, 1)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowLeft), [](bool) {});\n  // focus on child (0, 0)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowLeft), [](bool) {});\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowLeft), [](bool) {});\n  EXPECT_EQ(root_focus->GetLeafFocusedNode(), views[0].get());\n\n  // Focus back to list view\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowRight), [](bool) {});\n  // focus on child (0, 0)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowRight), [](bool) {});\n  // focus on child (0, 1)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowRight), [](bool) {});\n  // focus on child (0, 2)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowRight), [](bool) {});\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowRight), [](bool) {});\n  EXPECT_EQ(root_focus->GetLeafFocusedNode(), views[2].get());\n\n  // back to list view\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowLeft), [](bool) {});\n\n  std::vector<BaseView*>& children = list_view->GetChildren();\n  auto is_child_at = [](BaseView* parent, BaseView* child, int y, int x) {\n    const int index = y * 3 + x;\n    std::vector<BaseView*>& children = parent->GetChildren();\n    return children[index] == child;\n  };\n\n  // focus on child (0, 0)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  auto* focused = static_cast<BaseView*>(root_focus->GetLeafFocusedNode());\n  BaseView* item = children[0];\n  EXPECT_TRUE(is_child_at(item, focused, 0, 0));\n\n  // focus on child (0, 1)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowRight), [](bool) {});\n  focused = static_cast<BaseView*>(root_focus->GetLeafFocusedNode());\n  EXPECT_TRUE(is_child_at(item, focused, 0, 1));\n\n  // focus on child (1, 1)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  focused = static_cast<BaseView*>(root_focus->GetLeafFocusedNode());\n  EXPECT_TRUE(is_child_at(item, focused, 1, 1));\n\n  EXPECT_EQ(item->Top(), 0.f);\n\n  // focus on child (2, 1)\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  // still focus on child (2, 1) but the list view will scroll down a bit.\n  // Align the item 1\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  focused = static_cast<BaseView*>(root_focus->GetLeafFocusedNode());\n  EXPECT_TRUE(is_child_at(item, focused, 2, 1));\n  // The second item doesn't have focusable nodes. Align it's bottom with list's\n  // bottom.\n  item = children[1];\n  EXPECT_EQ(item->Top() + item->Height(), list_view->Height());\n  // Move down one more item. The previous focus view is out of bounds. The\n  // focus is back to the list view. (with focus ring)\n  // Align the item 2\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  focused = static_cast<BaseView*>(root_focus->GetLeafFocusedNode());\n  EXPECT_EQ(focused, list_view.get());\n  EXPECT_TRUE(list_view->render_object()->HasDefaultFocusRing());\n  // Align the item 3\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  EXPECT_TRUE(list_view->render_object()->HasDefaultFocusRing());\n\n  // There are focusable item in item 4. Won't align the item's bottom with the\n  // list. Instead, align the focused view with the list's bottom\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  focused = static_cast<BaseView*>(root_focus->GetLeafFocusedNode());\n  // There are 3 visible items.\n  EXPECT_EQ(list_view->child_count(), (size_t)3);\n  item = children[2];\n  EXPECT_EQ(item->Top() + focused->Top() + focused->Height(),\n            list_view->Height());\n\n  // Move down 6 times. The focus will move out of the list view.\n  for (int i = 0; i < 6; ++i) {\n    page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  }\n  focused = static_cast<BaseView*>(root_focus->GetLeafFocusedNode());\n  EXPECT_EQ(root_focus->GetLeafFocusedNode(), views[3].get());\n\n  // Move up. back to the list view\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowUp), [](bool) {});\n  // Move up again. The list view will search for a focusable point in area\n  // closed to the visible area.\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowUp), [](bool) {});\n  focused = static_cast<BaseView*>(root_focus->GetLeafFocusedNode());\n  item = children[0];\n  EXPECT_TRUE(is_child_at(item, focused, 2, 2));\n  // Align the focused view's top with the list.\n  EXPECT_EQ(item->Top() + focused->Top(), 0.f);\n}\n\nTEST_F_UI(FocusListViewTest, EventThrottling) {\n  SetAllowListPrefetch(false);\n\n  std::vector<std::tuple<bool, int, int>> events;\n  EXPECT_CALL(*this, OnCustomEvent(::testing::_, ::testing::_))\n      .WillRepeatedly(::testing::Invoke(\n          [&events](std::string event_name, const clay::Value::Map& params) {\n            if (event_name == event_attr::kEventNodeAppear ||\n                event_name == event_attr::kEventNodeDisappear) {\n              int item_key = std::stoi(params.at(\"key\").GetString());\n              events.emplace_back(event_name == event_attr::kEventNodeAppear,\n                                  params.at(\"position\").GetInt(), item_key);\n            }\n          }));\n\n  auto list_view = std::make_unique<EventFocusListView>(page_.get());\n  page_->AddChild(list_view.get());\n  list_view->SetX(0);\n  list_view->SetY(0);\n  list_view->SetWidth(300);\n  list_view->SetHeight(900);\n  list_view->Layout();\n  ListEventCallbackManager* callback_manager =\n      list_view->callback_manager_.get();\n\n  // Flush the first time, nothing emits, because only the 2nd gen event will be\n  // sent.\n  callback_manager->FlushItemAppearanceEvents();\n  EXPECT_EQ(events.size(), static_cast<size_t>(0));\n\n  callback_manager->FlushItemAppearanceEvents();\n  EXPECT_EQ(events.size(), static_cast<size_t>(9));\n  events.clear();\n\n  // \"Duplicated\" events happens in one iteration will be filtered.\n  list_view->OnScrollBy({0, -900.f});\n  EXPECT_EQ(callback_manager->appearance_events_gen1_.size(),\n            static_cast<size_t>(18));\n  list_view->OnScrollBy({0, 800.f});\n  EXPECT_EQ(callback_manager->appearance_events_gen1_.size(),\n            static_cast<size_t>(34));\n  callback_manager->FlushItemAppearanceEvents();\n  EXPECT_EQ(events.size(), static_cast<size_t>(0));\n  EXPECT_EQ(callback_manager->appearance_events_gen1_.size(),\n            static_cast<size_t>(0));\n  EXPECT_EQ(callback_manager->appearance_events_gen2_.size(),\n            static_cast<size_t>(2));\n  callback_manager->FlushItemAppearanceEvents();\n  EXPECT_EQ(events.size(), static_cast<size_t>(2));\n  EXPECT_EQ(callback_manager->appearance_events_gen1_.size(),\n            static_cast<size_t>(0));\n  EXPECT_EQ(callback_manager->appearance_events_gen2_.size(),\n            static_cast<size_t>(0));\n\n  // Reset to the top\n  list_view->OnScrollBy({0, 100.f});\n  callback_manager->FlushItemAppearanceEvents();\n  callback_manager->FlushItemAppearanceEvents();\n  events.clear();\n\n  // \"Duplicated\" events cross in two iterations will be filtered.\n  list_view->OnScrollBy({0, -900.f});\n  EXPECT_EQ(callback_manager->appearance_events_gen1_.size(),\n            static_cast<size_t>(18));\n  callback_manager->FlushItemAppearanceEvents();\n  EXPECT_EQ(events.size(), static_cast<size_t>(0));\n  EXPECT_EQ(callback_manager->appearance_events_gen1_.size(),\n            static_cast<size_t>(0));\n  EXPECT_EQ(callback_manager->appearance_events_gen2_.size(),\n            static_cast<size_t>(18));\n  list_view->OnScrollBy({0, -900.f});\n  EXPECT_EQ(callback_manager->appearance_events_gen1_.size(),\n            static_cast<size_t>(18));\n  callback_manager->FlushItemAppearanceEvents();\n  EXPECT_EQ(callback_manager->appearance_events_gen1_.size(),\n            static_cast<size_t>(0));\n  EXPECT_EQ(callback_manager->appearance_events_gen2_.size(),\n            static_cast<size_t>(9));\n  EXPECT_EQ(events.size(), static_cast<size_t>(9));\n  // Disappearing events from 0 to 8.\n  for (size_t i = 0; i < events.size(); ++i) {\n    EXPECT_FALSE(std::get<0>(events[i]));\n    EXPECT_EQ(std::get<1>(events[i]), static_cast<int>(i));\n    EXPECT_EQ(std::get<1>(events[i]), std::get<2>(events[i]));\n  }\n  events.clear();\n\n  callback_manager->FlushItemAppearanceEvents();\n  EXPECT_EQ(callback_manager->appearance_events_gen1_.size(),\n            static_cast<size_t>(0));\n  EXPECT_EQ(callback_manager->appearance_events_gen2_.size(),\n            static_cast<size_t>(0));\n  EXPECT_EQ(events.size(), static_cast<size_t>(9));\n  // Appearing events from 18 to 26.\n  for (size_t i = 0; i < events.size(); ++i) {\n    EXPECT_TRUE(std::get<0>(events[i]));\n    EXPECT_EQ(std::get<1>(events[i]), static_cast<int>(i + 18));\n    EXPECT_EQ(std::get<1>(events[i]), std::get<2>(events[i]));\n  }\n}\n#endif\n\nTEST_F_UI(FocusListViewTest, EventFindFocus) {\n  SetAllowListPrefetch(false);\n\n  std::vector<std::tuple<bool, int, int>> events;\n  EXPECT_CALL(*this, OnCustomEvent(::testing::_, ::testing::_))\n      .WillRepeatedly(::testing::Invoke(\n          [&events](std::string event_name, const clay::Value::Map& params) {\n            if (event_name == event_attr::kEventNodeAppear ||\n                event_name == event_attr::kEventNodeDisappear) {\n              int item_key = std::stoi(params.at(\"key\").GetString());\n              events.emplace_back(event_name == event_attr::kEventNodeAppear,\n                                  params.at(\"position\").GetInt(), item_key);\n            }\n          }));\n\n  auto list_view = std::make_unique<EventFocusListView>(page_.get());\n  page_->AddChild(list_view.get());\n  list_view->SetX(0);\n  list_view->SetY(0);\n  list_view->SetWidth(300);\n  list_view->SetHeight(900);\n  list_view->Layout();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n\n  // Each item has height 100px. There are 9 appearing items.\n  EXPECT_EQ(events.size(), static_cast<size_t>(9));\n  for (size_t i = 0; i < events.size(); ++i) {\n    EXPECT_TRUE(std::get<0>(events[i]));\n    EXPECT_EQ(std::get<1>(events[i]), static_cast<int>(i));\n    EXPECT_EQ(std::get<1>(events[i]), std::get<2>(events[i]));\n  }\n\n  events.clear();\n  EXPECT_TRUE(list_view->OnScrollBy({0, -1800.f}));\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  EXPECT_EQ(events.size(), static_cast<size_t>(18));\n  for (size_t i = 0; i < 9; ++i) {\n    EXPECT_FALSE(std::get<0>(events[i]));\n    EXPECT_EQ(std::get<1>(events[i]), static_cast<int>(i));\n    EXPECT_EQ(std::get<1>(events[i]), std::get<2>(events[i]));\n  }\n  for (size_t i = 9; i < 18; ++i) {\n    EXPECT_TRUE(std::get<0>(events[i]));\n    EXPECT_EQ(std::get<1>(events[i]), static_cast<int>(i + 9));\n    EXPECT_EQ(std::get<1>(events[i]), std::get<2>(events[i]));\n  }\n\n  // Remove items\n  // Scroll back to the top.\n  list_view->OnScrollBy({0, 1800.f});\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  events.clear();\n\n  list_view->focus_list_adapter_->SetCount(49);\n  list_view->focus_list_adapter_->NotifyItemRangeRemoved(0, 1);\n  list_view->Layout();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  EXPECT_EQ(events.size(), static_cast<size_t>(2));\n  EXPECT_FALSE(std::get<0>(events[0]));\n  EXPECT_EQ(std::get<1>(events[0]), static_cast<int>(0));\n  EXPECT_EQ(std::get<1>(events[0]), std::get<2>(events[0]));\n  EXPECT_TRUE(std::get<0>(events[1]));\n  EXPECT_EQ(std::get<1>(events[1]), static_cast<int>(8));\n  EXPECT_EQ(std::get<1>(events[1]), std::get<2>(events[1]));\n\n  // Add items\n  events.clear();\n  list_view->focus_list_adapter_->SetCount(50);\n  list_view->focus_list_adapter_->NotifyItemRangeInserted(1, 1);\n  list_view->Layout();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  EXPECT_EQ(events.size(), static_cast<size_t>(2));\n\n  EXPECT_FALSE(std::get<0>(events[0]));\n  // The disappearing item has latest position 9 but its item-key is still 8.\n  EXPECT_EQ(std::get<1>(events[0]), static_cast<int>(9));\n  EXPECT_EQ(std::get<2>(events[0]), static_cast<int>(8));\n\n  EXPECT_TRUE(std::get<0>(events[1]));\n  EXPECT_EQ(std::get<1>(events[1]), static_cast<int>(1));\n  EXPECT_EQ(std::get<1>(events[1]), std::get<2>(events[1]));\n}\n\n#if !defined(OS_WIN) && !defined(OS_MAC) && !defined(OS_HARMONY)\nTEST_F_UI(FocusListViewTest, AppearDisappearEvent) {\n  SetAllowListPrefetch(false);\n\n  std::vector<std::tuple<bool, int, int>> events;\n  EXPECT_CALL(*this, OnCustomEvent(::testing::_, ::testing::_))\n      .WillRepeatedly(::testing::Invoke(\n          [&events](std::string event_name, const clay::Value::Map& params) {\n            if (event_name == event_attr::kEventNodeAppear ||\n                event_name == event_attr::kEventNodeDisappear) {\n              int item_key = std::stoi(params.at(\"key\").GetString());\n              events.emplace_back(event_name == event_attr::kEventNodeAppear,\n                                  params.at(\"position\").GetInt(), item_key);\n            }\n          }));\n\n  auto list_view = std::make_unique<EventFocusListView>(page_.get());\n  list_view->SetSkipFocusTraversal(false);\n  list_view->SetAttribute(\"focus-smooth-scroll\", clay::Value(false));\n  page_->AddChild(list_view.get());\n  list_view->SetX(0);\n  list_view->SetY(0);\n  list_view->SetWidth(300);\n  list_view->SetHeight(900);\n  list_view->Layout();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  events.clear();\n\n  std::vector<BaseView*>& children = list_view->GetChildren();\n  EXPECT_EQ(children.size(), static_cast<size_t>(9));\n\n  FocusManager* root_focus = page_->GetFocusManager();\n  root_focus->preference().switch_up_to_once_per_frame = false;\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  EXPECT_EQ(root_focus->GetLeafFocusedNode(),\n            static_cast<FocusNode*>(list_view.get()));\n  EXPECT_TRUE(events.empty());\n\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  EXPECT_EQ(children.size(), static_cast<size_t>(9));\n  EXPECT_EQ(root_focus->GetLeafFocusedNode(),\n            static_cast<FocusNode*>(children[8]));\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  EXPECT_TRUE(events.empty());\n\n  page_->DispatchKeyEvent(MakeEvent(KeyCode::kArrowDown), [](bool) {});\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n  list_view->callback_manager_->FlushItemAppearanceEvents();\n\n  // There are still 9 visible items. However, more than 9 items are laid out in\n  // order to find the next focusable node.\n  EXPECT_GT(children.size(), static_cast<size_t>(9));\n  EXPECT_EQ(events.size(), static_cast<size_t>(2));\n  EXPECT_FALSE(std::get<0>(events[0]));\n  EXPECT_EQ(std::get<1>(events[0]), static_cast<int>(0));\n  EXPECT_EQ(std::get<2>(events[0]), static_cast<int>(0));\n\n  EXPECT_TRUE(std::get<0>(events[1]));\n  EXPECT_EQ(std::get<1>(events[1]), static_cast<int>(9));\n  EXPECT_EQ(std::get<1>(events[1]), std::get<2>(events[1]));\n}\n#endif\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_adapter.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_adapter.h\"\n\n#include <utility>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/macros.h\"\n\nnamespace {\nint64_t CalculateAverage(int64_t old_average, int64_t new_value) {\n  if (old_average == 0) {\n    return new_value;\n  }\n  return (old_average * 3 / 4) + (new_value / 4);\n}\n}  // namespace\n\nnamespace clay {\n\nListAdapter::Observer::Observer() = default;\nListAdapter::Observer::~Observer() = default;\n\nListAdapter::ListAdapter(BaseListView* list_view) : list_view_(list_view) {\n  FML_DCHECK(list_view_);\n}\nListAdapter::~ListAdapter() = default;\n\nListItemViewHolder* ListAdapter::CreateListItem(TypeId type) {\n  TRACE_EVENT(\"clay\", \"ListAdapter::CreateListItem\");\n  ListItemViewHolder* res = OnCreateListItem(type);\n  FML_DCHECK(res);\n  res->SetVisibilityObserver(list_view_);\n\n  // Delay the creation of view to match the behaviour of Lynx.\n  return res;\n}\n\n// The list item is not recycled and should be deleted.\nvoid ListAdapter::DeleteListItem(ListItemViewHolder* view_holder) {\n  // We won't get here in new arch mode because there's no limit to the size of\n  // recycling cache.\n  FML_DCHECK(!IsNewArch());\n\n  list_view_->OnDestroyItem(view_holder);\n  OnDeleteListItem(view_holder);\n}\n\nvoid ListAdapter::BindListItem(ListItemViewHolder* item, int index) {\n  TRACE_EVENT(\"LIST\", \"ListAdapter::BindListItem\");\n  FML_DCHECK(item);\n  int prev_position = item->GetPosition();\n\n  // NOTE(Xietong): this should be done before setting the new position.\n  if (prev_position != index && item->GetView()) {\n    // Mark the original view to invisible before assigning to a new position.\n    item->SetViewVisible(false);\n  }\n\n  item->SetPosition(index);\n  // TODO(Xietong): cleanup the set flag logic.\n  item->SetFlags(\n      ListItemViewHolder::kFlagBound,\n      static_cast<ListItemViewHolder::Flag>(\n          ListItemViewHolder::kFlagBound | ListItemViewHolder::kFlagUpdate |\n          ListItemViewHolder::kFlagInvalid |\n          ListItemViewHolder::kFlagAdapterPositionUnknown));\n  // When the new architecture of Lynx is enabled, the creation logic is handled\n  // in LynxListAdapter::OnBindListItem\n  if (!IsNewArch() && !item->GetView()) {\n    list_view_->OnCreateItem(item, index);\n    FML_DCHECK(item->GetView());\n\n    // Set the initial visibility to false because the just created item is not\n    // attached yet.\n    // We don't call `SetViewVisible()` here because we don't want to send\n    // NodeDisappear() notification for a newly created item.\n    item->GetView()->SetVisible(false);\n\n    OnBindListItem(item, ListItemViewHolder::kNoPosition, index, true);\n    // Notify the list view so that list view can update the focus information.\n    list_view_->OnBindListItem(item, ListItemViewHolder::kNoPosition, index,\n                               true);\n  } else {\n    OnBindListItem(item, prev_position, index, false);\n    // Notify the list view so that list view can update the focus information.\n    list_view_->OnBindListItem(item, prev_position, index, false);\n  }\n  item->ClearPayloads();\n}\n\nvoid ListAdapter::OnRecycleItem(ListItemViewHolder* item) {\n  FML_DCHECK(item->GetView());\n  item->SetViewVisible(false);\n}\n\nvoid ListAdapter::UpdateBindTime(TypeId type, fml::TimeDelta duration) {\n  PerformData& perform_data = id_to_perform_data_[type];\n  perform_data.average_bind_time_ns = CalculateAverage(\n      perform_data.average_bind_time_ns, duration.ToNanoseconds());\n}\n\nfml::TimeDelta ListAdapter::GetAverageBindTime(TypeId type) {\n  return fml::TimeDelta::FromNanoseconds(\n      id_to_perform_data_[type].average_bind_time_ns);\n}\n\nvoid ListAdapter::NotifyItemMoved(int from_position, int to_position) {\n  if (observer_) {\n    observer_->OnItemMoved(from_position, to_position);\n  }\n}\nvoid ListAdapter::NotifyItemRangeInserted(int position_start, int item_count) {\n  if (observer_) {\n    observer_->OnItemRangeInserted(position_start, item_count);\n  }\n}\nvoid ListAdapter::NotifyItemRangeRemoved(int position_start, int item_count) {\n  if (observer_) {\n    observer_->OnItemRangeRemoved(position_start, item_count);\n  }\n}\nvoid ListAdapter::NotifyItemRangeChanged(int position_start, int item_count,\n                                         std::unique_ptr<Payload> payload) {\n  if (observer_) {\n    observer_->OnItemRangeChanged(position_start, item_count,\n                                  std::move(payload));\n  }\n}\n\nvoid ListAdapter::NotifyDataSetChanged() {\n  if (observer_) {\n    observer_->OnChanged();\n  }\n}\n\nint ListAdapter::FindPreviousFullSpan(int position) const {\n  for (int i = position; i >= 0; i--) {\n    if (IsItemFullSpan(i)) {\n      return i;\n    }\n  }\n  return ListItemViewHolder::kNoPosition;\n}\n\nint ListAdapter::FindNextFullSpan(int position) const {\n  for (int i = position; i < GetItemCount(); i++) {\n    if (IsItemFullSpan(i)) {\n      return i;\n    }\n  }\n  return ListItemViewHolder::kNoPosition;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_adapter.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_ADAPTER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_ADAPTER_H_\n\n#include <memory>\n#include <optional>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace clay {\n\nclass BaseListView;\nclass ListItemViewHolder;\n\nclass ListAdapter {\n public:\n  using TypeId = int;\n  struct Payload {\n    bool is_lynx_payload = false;\n    Payload() = default;\n    virtual ~Payload() = default;\n  };\n  using Payloads = std::vector<std::unique_ptr<Payload>>;\n\n  class Observer {\n   public:\n    Observer();\n    virtual ~Observer();\n\n    virtual void OnItemMoved(int from_position, int to_position) {}\n    virtual void OnItemRangeInserted(int position_start, int item_count) {}\n    virtual void OnItemRangeRemoved(int position_start, int item_count) {}\n    virtual void OnItemRangeChanged(int position_start, int item_count,\n                                    std::unique_ptr<Payload> payload) {}\n    virtual void OnChanged() {}\n  };\n\n  static constexpr TypeId kDefaultId = -1;\n\n  explicit ListAdapter(BaseListView* list_view);\n  virtual ~ListAdapter();\n\n  ListItemViewHolder* CreateListItem(TypeId type);\n  void BindListItem(ListItemViewHolder* item, int index);\n  // The list item is not recycled and should by deleted.\n  void DeleteListItem(ListItemViewHolder* view_holder);\n\n  void UpdateBindTime(TypeId type, fml::TimeDelta duration);\n  fml::TimeDelta GetAverageBindTime(TypeId type);\n\n  virtual void OnRecycleItem(ListItemViewHolder* item);\n\n  virtual int GetItemCount() const = 0;\n  virtual TypeId GetItemViewType(int position) { return kDefaultId; }\n\n  void SetObserver(Observer* observer) { observer_ = observer; }\n\n  // Called after modifying the data.\n  void NotifyItemMoved(int from_position, int to_position);\n  void NotifyItemRangeInserted(int position_start, int item_count);\n  void NotifyItemRangeRemoved(int position_start, int item_count);\n  void NotifyItemRangeChanged(int position_start, int item_count,\n                              std::unique_ptr<Payload> payload = nullptr);\n  void NotifyDataSetChanged();\n\n  int FindPreviousFullSpan(int position) const;\n  int FindNextFullSpan(int position) const;\n\n  virtual bool IsItemFullSpan(int position) const { return false; }\n  virtual bool IsItemStickyTop(int position) const { return false; }\n  virtual bool IsItemStickyBottom(int position) const { return false; }\n  virtual bool IsNewArch() const { return false; }\n  virtual void PrintViewHolders() {}\n\n  struct PerformData {\n    int64_t average_create_time_ns;\n    int64_t average_bind_time_ns;\n  };\n\n protected:\n  virtual ListItemViewHolder* OnCreateListItem(TypeId type) = 0;\n  virtual void OnBindListItem(ListItemViewHolder* item, int prev_position,\n                              int position, bool newly_created) = 0;\n  virtual void OnDeleteListItem(ListItemViewHolder* view_holder) = 0;\n\n  BaseListView* list_view_ = nullptr;\n  std::unordered_map<ListAdapter::TypeId, PerformData> id_to_perform_data_;\n\n private:\n  // Not owned.\n  Observer* observer_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_ADAPTER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_adapter_helper.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_adapter_helper.h\"\n\n#include <numeric>\n#include <utility>\n\nnamespace clay {\n\nListAdapterHelper::Consumer::Consumer() = default;\n\nListAdapterHelper::Consumer::~Consumer() = default;\n\nListAdapterHelper::ListAdapterHelper(Consumer* consumer)\n    : consumer_(consumer) {}\n\nListAdapterHelper::~ListAdapterHelper() = default;\n\nvoid ListAdapterHelper::OnItemRangeInserted(int position_start,\n                                            int item_count) {\n  pending_updates_.emplace_back(Type::kInsert, position_start, item_count);\n}\n\nvoid ListAdapterHelper::OnItemRangeRemoved(int position_start, int item_count) {\n  pending_updates_.emplace_back(Type::kRemove, position_start, item_count);\n}\n\nvoid ListAdapterHelper::OnItemRangeChanged(\n    int position_start, int item_count,\n    std::unique_ptr<ListAdapter::Payload> payload) {\n  pending_updates_.emplace_back(Type::kChange, position_start, item_count,\n                                std::move(payload));\n}\n\nvoid ListAdapterHelper::OnItemMoved(int from_position, int to_position) {\n  pending_updates_.emplace_back(Type::kMove, from_position, to_position);\n}\n\nbool ListAdapterHelper::ConsumeUpdates() {\n  bool has_pending_updates = !pending_updates_.empty();\n  if (consumer_ != nullptr) {\n    for (UpdateOp& op : pending_updates_) {\n      switch (op.type) {\n        case Type::kInsert:\n          consumer_->OffsetPositionsForInsert(op.position_start, op.item_count);\n          break;\n        case Type::kRemove:\n          consumer_->OffsetPositionsForRemove(op.position_start, op.item_count);\n          break;\n        case Type::kChange:\n          consumer_->MarkViewHoldersChanged(op.position_start, op.item_count,\n                                            std::move(op.payload));\n          break;\n        case Type::kMove:\n          consumer_->OffsetPositionsForMove(op.position_start, op.item_count);\n          break;\n      }\n    }\n  }\n  pending_updates_.clear();\n  return has_pending_updates;\n}\n\nvoid ListAdapterHelper::Reset() { pending_updates_.clear(); }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_adapter_helper.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_ADAPTER_HELPER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_ADAPTER_HELPER_H_\n\n#include <memory>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/component/list/list_adapter.h\"\n\nnamespace clay {\n\nclass ListAdapterHelper {\n public:\n  class Consumer {\n   public:\n    Consumer();\n    virtual ~Consumer();\n\n    virtual void OffsetPositionsForInsert(int position_start, int item_count) {}\n    virtual void OffsetPositionsForRemove(int position_start, int item_count) {}\n    virtual void MarkViewHoldersChanged(\n        int position_start, int item_count,\n        std::unique_ptr<ListAdapter::Payload> payload) {}\n    virtual void OffsetPositionsForMove(int from, int to) {}\n  };\n\n  enum class Type {\n    kInsert = 1,\n    kRemove = 1 << 1,\n    kChange = 1 << 2,\n    kMove = 1 << 3,\n  };\n\n  explicit ListAdapterHelper(Consumer* consumer);\n  ~ListAdapterHelper();\n\n  void OnItemRangeInserted(int position_start, int item_count);\n  void OnItemRangeRemoved(int position_start, int item_count);\n  void OnItemRangeChanged(int position_start, int item_count,\n                          std::unique_ptr<ListAdapter::Payload> payload);\n  void OnItemMoved(int from_position, int to_position);\n\n  bool ConsumeUpdates();\n\n  bool HasPendingUpdates() { return !pending_updates_.empty(); }\n\n  void Reset();\n\n private:\n  struct UpdateOp {\n    Type type;\n    int position_start;\n    int item_count;  // For Move, this represents the to_position.\n    std::unique_ptr<ListAdapter::Payload> payload;\n\n    UpdateOp(Type t, int start, int count,\n             std::unique_ptr<ListAdapter::Payload> p = nullptr)\n        : type(t),\n          position_start(start),\n          item_count(count),\n          payload(std::move(p)) {}\n  };\n\n  // Not owned.\n  Consumer* consumer_ = nullptr;\n  std::vector<UpdateOp> pending_updates_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_ADAPTER_HELPER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_adapter_updater.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_adapter_updater.h\"\n\n#include <limits>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/value_animator.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n\nnamespace clay {\n\nstatic constexpr int64_t kUpdateAnimationDuration = 300;  // ms\n\nListAdapterUpdater::ListAdapterUpdater(BaseListView* list_view)\n    : list_view_(list_view) {\n  FML_DCHECK(list_view);\n  animator_ = std::make_unique<ValueAnimator>();\n  animator_->SetDuration(kUpdateAnimationDuration);\n  animator_->SetAnimationHandler(list_view->GetAnimationHandler());\n  animator_->AddUpdateListener(this);\n}\n\nListAdapterUpdater::~ListAdapterUpdater() = default;\n\nvoid ListAdapterUpdater::SaveItemStartAttribute(ListItemViewHolder* item) {\n  auto& pair = animations_[item];\n  pair.first = {item->GetLayoutOrigin(), item->GetView()->Opacity()};\n}\n\nvoid ListAdapterUpdater::SaveItemEndAttribute(ListItemViewHolder* item) {\n  auto itr = animations_.find(item);\n  if (itr != animations_.end()) {\n    auto& pair = animations_[item];\n    pair.second = {item->GetLayoutOrigin(), item->GetView()->Opacity()};\n    FloatPoint from = pair.first.value().origin;\n    item->Layout(from);\n    return;\n  }\n  auto pos = item->GetPosition();\n  if (insert_positions_.find(pos) != insert_positions_.end()) {\n    FloatPoint origin = item->GetLayoutOrigin();\n    float opacity = item->GetView()->Opacity();\n    auto& pair = animations_[item];\n    pair.first = {origin, 0.f};\n    pair.second = {origin, opacity};\n    insert_positions_.erase(pos);\n  }\n}\n\nvoid ListAdapterUpdater::AddInsertItemPosition(int pos) {\n  insert_positions_.insert(pos);\n}\n\nvoid ListAdapterUpdater::CompactAnimations() {\n  for (auto itr = animations_.begin(); itr != animations_.end();) {\n    auto& attr_pair = itr->second;\n    if (!attr_pair.second.has_value()) {\n      itr = animations_.erase(itr);\n    } else {\n      itr++;\n    }\n  }\n}\n\nvoid ListAdapterUpdater::PerformUpdate() {\n  CompactAnimations();\n  animator_->Start();\n}\n\nvoid ListAdapterUpdater::EndUpdate() { animator_->End(); }\n\nvoid ListAdapterUpdater::OnAnimationUpdate(ValueAnimator& animator) {\n  const float fraction = animator.GetAnimatedFraction();\n  auto animation_block = [this](const float fraction) {\n    for (auto& itr : animations_) {\n      auto* view_holder = itr.first;\n      auto& attr = itr.second;\n      FloatPoint from = attr.first.value().origin;\n      FloatPoint to = attr.second.value().origin;\n      float origin_opacity = attr.first.value().opacity;\n      float target_opacity = attr.second.value().opacity;\n      float deltaX = (to.x() - from.x()) * fraction;\n      float deltaY = (to.y() - from.y()) * fraction;\n      float delta_opacity = target_opacity - origin_opacity;\n      from.Move(deltaX, deltaY);\n      view_holder->Layout(from);\n      view_holder->GetView()->SetOpacity(origin_opacity +\n                                         delta_opacity * fraction);\n      view_holder->FlushLayout();\n    }\n  };\n  if (fraction == 0.f && !enter_update_) {\n    enter_update_ = true;\n    list_view_->page_view()->RequestPaint();\n  } else if (fraction == 1.f) {\n    enter_update_ = false;\n    animation_block(1.f);\n    insert_positions_.clear();\n    animations_.clear();\n  } else {\n    animation_block(fraction);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_adapter_updater.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_ADAPTER_UPDATER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_ADAPTER_UPDATER_H_\n\n#include <memory>\n#include <optional>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n\n#include \"clay/gfx/animation/animator_listener.h\"\n#include \"clay/gfx/animation/value_animator.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n\nnamespace clay {\n\nclass BaseListView;\nclass ListItemViewHolder;\nclass ValueAnimator;\n\nclass ListAdapterUpdater : public AnimatorUpdateListener {\n public:\n  struct Attribute {\n    FloatPoint origin;\n    float opacity;\n  };\n  explicit ListAdapterUpdater(BaseListView* list_view);\n  virtual ~ListAdapterUpdater();\n\n  bool IsRunning() { return animator_->IsRunning(); }\n  bool hasPendingUpdateAnimation() { return !animations_.empty(); }\n\n  void SaveItemStartAttribute(ListItemViewHolder* item);\n  void SaveItemEndAttribute(ListItemViewHolder* item);\n  void AddInsertItemPosition(int pos);\n  void PerformUpdate();\n  void EndUpdate();\n\n  void OnAnimationUpdate(ValueAnimator& animator) override;\n\n private:\n  using AnimationAttributePair =\n      std::pair<std::optional<Attribute>, std::optional<Attribute>>;\n  void CompactAnimations();\n  BaseListView* list_view_ = nullptr;\n  std::unordered_map<ListItemViewHolder*, AnimationAttributePair> animations_;\n  std::unordered_set<int> insert_positions_;\n  std::unique_ptr<ValueAnimator> animator_;\n  bool enter_update_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_ADAPTER_UPDATER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_children_helper.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_children_helper.h\"\n\n#include <functional>\n#include <string>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n\nnamespace clay {\n\nListChildrenHelper::ListChildrenHelper() = default;\nListChildrenHelper::~ListChildrenHelper() = default;\n\nint ListChildrenHelper::GetChildCount() const {\n  return static_cast<int>(children_.size());\n}\n\nListItemViewHolder* ListChildrenHelper::GetChildAt(int index) {\n  if (index < 0 || index >= GetChildCount()) {\n    return nullptr;\n  }\n  return *GetChildIterator(index);\n}\n\nvoid ListChildrenHelper::AddChild(ListItemViewHolder* item, int index) {\n  FML_DCHECK(index >= 0 && index <= GetChildCount());\n  FML_DCHECK(floating_top_item_ != item && floating_bottom_item_ != item);\n  ViewHolderList::iterator itr = GetChildIterator(index);\n  children_.insert(itr, item);\n}\n\nListItemViewHolder* ListChildrenHelper::RemoveChild(int index) {\n  FML_DCHECK(index >= 0 && index < GetChildCount());\n  ViewHolderList::iterator itr = GetChildIterator(index);\n  ListItemViewHolder* child = *itr;\n  children_.erase(itr);\n  return child;\n}\n\nListItemViewHolder* ListChildrenHelper::FindChildByPosition(int position) {\n  if (GetChildCount() == 0) {\n    return nullptr;\n  }\n\n  // We assume children's position is continuous.\n  const ListItemViewHolder* first = GetChildAt(0);\n  const int first_pos = first->GetPosition();\n  if (position < first_pos || position >= first_pos + GetChildCount()) {\n    return nullptr;\n  }\n  return GetChildAt(position - first_pos);\n}\n\nvoid ListChildrenHelper::ForEach(\n    const std::function<bool(ListItemViewHolder*)>& func) {\n  FML_DCHECK(func);\n  for (ListItemViewHolder* view_holder : children_) {\n    if (func(view_holder)) {\n      break;\n    }\n  }\n}\n\nvoid ListChildrenHelper::ReversedForEach(\n    const std::function<bool(ListItemViewHolder*)>& func) {\n  FML_DCHECK(func);\n  auto iter = children_.rbegin();\n  for (; iter != children_.rend(); ++iter) {\n    if (func(*iter)) {\n      break;\n    }\n  }\n}\n\nvoid ListChildrenHelper::ForEachInPositions(\n    const std::vector<int>& positions,\n    const std::function<bool(ListItemViewHolder*)>& func) {\n  FML_DCHECK(func);\n  ListChildrenHelper::ViewHolderList::iterator iter;\n  bool last_in_children = false;\n  for (size_t i = 0; i < positions.size(); ++i) {\n    int position = positions[i];\n    if (floating_top_item_ && position == floating_top_item_->GetPosition()) {\n      if (func(floating_top_item_)) {\n        return;\n      }\n      last_in_children = false;\n    } else if (floating_bottom_item_ &&\n               position == floating_bottom_item_->GetPosition()) {\n      if (func(floating_bottom_item_)) {\n        return;\n      }\n      last_in_children = false;\n    } else {\n      if (last_in_children && positions[i - 1] + 1 == position) {\n        // Continuous, forward the iterator\n        ++iter;\n      } else {\n        // Not continuous, find iterator by index\n        int index_in_item_list = GetChildIndexByPosition(positions[i]);\n        if (index_in_item_list == ListItemViewHolder::kNoPosition) {\n          FML_DLOG(ERROR) << \"Unable to find child at position \"\n                          << positions[i];\n          // If we can't find the front item, then can't find remaining either.\n          return;\n        }\n        iter = GetChildIterator(index_in_item_list);\n        last_in_children = true;\n      }\n      if (func(*iter)) {\n        return;\n      }\n    }\n  }\n}\n\nvoid ListChildrenHelper::ForEachWithSticky(\n    const std::function<bool(ListItemViewHolder*)>& func) {\n  FML_DCHECK(func);\n  if (floating_top_item_) {\n    if (func(floating_top_item_)) {\n      return;\n    }\n  }\n  for (ListItemViewHolder* view_holder : children_) {\n    if (func(view_holder)) {\n      return;\n    }\n  }\n  if (floating_bottom_item_) {\n    func(floating_bottom_item_);\n  }\n}\n\nListChildrenHelper::ViewHolderList::iterator\nListChildrenHelper::GetChildIterator(int index) {\n  const int count = GetChildCount();\n  FML_DCHECK(index >= 0 && index <= count);\n  if (index > count / 2) {\n    auto itr = children_.rbegin();\n    for (int i = count; i > index; --i) {\n      ++itr;\n    }\n    return itr.base();\n  } else {\n    auto itr = children_.begin();\n    for (int i = 0; i < index; ++i) {\n      ++itr;\n    }\n    return itr;\n  }\n}\n\nint ListChildrenHelper::GetChildIndexByPosition(int position) {\n  if (GetChildCount() == 0) {\n    return ListItemViewHolder::kNoPosition;\n  }\n  const ListItemViewHolder* first = GetChildAt(0);\n  const int first_pos = first->GetPosition();\n\n  if (position < first_pos || position >= first_pos + GetChildCount()) {\n    return ListItemViewHolder::kNoPosition;\n  }\n  return position - first_pos;\n}\n\nstd::string ListChildrenHelper::ToString() const {\n#if (DEBUG_LIST)\n  std::stringstream ss;\n  if (floating_top_item_) {\n    ss << std::endl << \"[floating_top]\" << floating_top_item_->ToString();\n  }\n  unsigned int idx = 0;\n  for (auto const& child : children_) {\n    ss << std::endl << \"[\" << idx << \"] \" << child->ToString();\n    idx++;\n  }\n  if (floating_bottom_item_) {\n    ss << std::endl\n       << \"[floating_bottom] \" << floating_bottom_item_->ToString();\n  }\n  return ss.str();\n#else\n  return \"\";\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_children_helper.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_CHILDREN_HELPER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_CHILDREN_HELPER_H_\n\n#include <functional>\n#include <list>\n#include <string>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nclass ListItemViewHolder;\n\n// In Android's recycler view, this is used as the container of the view holders\n// and handle the asynchronous states between the adapter and the recycler view\n// caused by animations.\n// We don't handle animation yet and this class is just used as the container\n// for view holders.\nclass ListChildrenHelper {\n public:\n  ListChildrenHelper();\n  ~ListChildrenHelper();\n\n  int GetChildCount() const;\n  ListItemViewHolder* GetChildAt(int index);\n  void AddChild(ListItemViewHolder* item, int index);\n  ListItemViewHolder* RemoveChild(int index);\n  ListItemViewHolder* FindChildByPosition(int position);\n\n  ListItemViewHolder* floating_top_item() const { return floating_top_item_; }\n  ListItemViewHolder* floating_bottom_item() const {\n    return floating_bottom_item_;\n  }\n\n  void set_floating_top_item(ListItemViewHolder* item) {\n    FML_DCHECK(std::find(children_.begin(), children_.end(), item) ==\n               children_.end());\n    floating_top_item_ = item;\n  }\n  void set_floating_bottom_item(ListItemViewHolder* item) {\n    FML_DCHECK(std::find(children_.begin(), children_.end(), item) ==\n               children_.end());\n    floating_bottom_item_ = item;\n  }\n\n  // Iterate the children list. Stop the iteration when func returns true.\n  void ForEach(const std::function<bool(ListItemViewHolder*)>& func);\n  void ReversedForEach(const std::function<bool(ListItemViewHolder*)>& func);\n  // Iterate a range of position(they may not continuous but must be sorted).\n  void ForEachInPositions(const std::vector<int>& positions,\n                          const std::function<bool(ListItemViewHolder*)>& func);\n\n  // Iterate all children (and containing sticky items). Stop the iteration when\n  // func returns true.\n  void ForEachWithSticky(const std::function<bool(ListItemViewHolder*)>& func);\n\n  std::string ToString() const;\n\n private:\n  using ViewHolderList = std::list<ListItemViewHolder*>;\n  ViewHolderList::iterator GetChildIterator(int index);\n\n  int GetChildIndexByPosition(int position);\n\n  ViewHolderList children_;\n\n  // If the current header item is not in `children_`, it will be stored here.\n  ListItemViewHolder* floating_top_item_ = nullptr;\n  // If the current footer item is not in `children_`, it will be stored here.\n  ListItemViewHolder* floating_bottom_item_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_CHILDREN_HELPER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_common/layout_types.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n\nnamespace clay {\nnamespace {\nconstexpr char kAlignNone[] = \"none\";\nconstexpr char kAlignTop[] = \"top\";\nconstexpr char kAlignBottom[] = \"bottom\";\nconstexpr char kAlignMiddle[] = \"middle\";\n}  // namespace\n\nAlignTo StringToAlign(const std::string& str) {\n  if (str == kAlignNone) {\n    return AlignTo::kNone;\n  } else if (str == kAlignTop) {\n    return AlignTo::kStart;\n  } else if (str == kAlignMiddle) {\n    return AlignTo::kMiddle;\n  } else if (str == kAlignBottom) {\n    return AlignTo::kEnd;\n  } else {\n    return AlignTo::kNone;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_common/layout_types.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_COMMON_LAYOUT_TYPES_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_COMMON_LAYOUT_TYPES_H_\n\n#include <optional>\n#include <string>\n\n#include \"clay/ui/component/text/text_style.h\"\n\nnamespace clay {\n\nenum class AlignTo { kNone, kStart, kEnd, kMiddle };\n\nAlignTo StringToAlign(const std::string& str);\n\n// State that passed to LayoutManager subclasses so that the layout logic\n// doesn't need to access ListView directly.\nstruct ListViewState {\n  bool structure_changed = false;\n  size_t item_count = 0;\n};\n\n// Defines the direction in which the data adapter is traversed.\nenum class ListItemDirection {\n  kToHead = -1,\n  kToTail = 1,\n};\n\n// Defines the direction in which the layout is filled.\nenum class ListLayoutDirection {\n  kToStart = -1,\n  kToEnd = 1,\n};\n\n// Should keep the same to\n// com.lynx.tasm.behavior.ui.ScrollStateChangeListener.java so that we have\n// the same protocol\nenum class ListScrollState {\n  kIdle = 1,  // Not scrolling\n  kDragging,  // Dragged by outside input\n  kSettling,  // Animating to a final position while not under outside control\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_COMMON_LAYOUT_TYPES_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_container/list_container_view.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_container/list_container_view.h\"\n\n#include <algorithm>\n#include <cstdint>\n#include <limits>\n#include <memory>\n#include <tuple>\n#include <utility>\n\n#include \"base/include/float_comparison.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/keywords.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/view_callback/list_container_event_callback_manager.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n\nnamespace clay {\n\nnamespace details {\nstatic constexpr const char kDataSourceItemKeys[] = \"itemkeys\";\nstatic constexpr const char kDataSourceStickyStart[] = \"stickyStart\";\nstatic constexpr const char kDataSourceStickyEnd[] = \"stickyEnd\";\n}  // namespace details\n\nListContainerView::ListContainerView(int32_t id, PageView* page_view,\n                                     int32_t callback_id)\n    : WithTypeInfo(id, callback_id, ScrollDirection::kVertical, page_view,\n                   std::make_unique<ListContainerEventCallbackManager>(\n                       this, callback_id, page_view),\n                   std::make_unique<RenderScroll>()) {\n  tag_ = \"ListContainerView\";\n  AddEventCallback(event_attr::kEventScrollStateChange);\n}\n\nvoid ListContainerView::SetAttribute(const char* attr_c,\n                                     const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kListContainerInfo) {\n    const auto& info = attribute_utils::GetMap(value);\n    if (auto it = info.find(details::kDataSourceItemKeys); it != info.end()) {\n      const auto& itemkeys = attribute_utils::GetArray(it->second);\n      item_keys_.clear();\n      item_key_map_.clear();\n      for (size_t i = 0; i < itemkeys.size(); i++) {\n        auto s = attribute_utils::GetCString(itemkeys[i]);\n        item_keys_.push_back(s);\n        item_key_map_[s] = i;\n      }\n    }\n    if (auto it = info.find(details::kDataSourceStickyStart);\n        it != info.end()) {\n      const auto& itemkeys = attribute_utils::GetArray(it->second);\n      sticky_top_indexes_.clear();\n      for (size_t i = 0; i < itemkeys.size(); i++) {\n        sticky_top_indexes_.push_back(attribute_utils::GetInt(itemkeys[i]));\n      }\n    }\n    if (auto it = info.find(details::kDataSourceStickyEnd); it != info.end()) {\n      const auto& itemkeys = attribute_utils::GetArray(it->second);\n      sticky_bottom_indexes_.clear();\n      for (size_t i = 0; i < itemkeys.size(); i++) {\n        sticky_bottom_indexes_.push_back(attribute_utils::GetInt(itemkeys[i]));\n      }\n    }\n  } else if (kw == KeywordID::kExperimentalBatchRenderStrategy) {\n    enable_batch_render_strategy_ = attribute_utils::GetInt(value) > 0;\n  } else if (kw == KeywordID::kSticky) {\n    enable_list_sticky_ = attribute_utils::GetBool(value);\n  } else if (kw == KeywordID::kStickyOffset) {\n    sticky_offset_ = attribute_utils::GetDouble(value);\n  } else if (kw == KeywordID::kExperimentalRecycleStickyItem) {\n    enable_recycle_sticky_item_ = attribute_utils::GetBool(value);\n  } else if (kw == KeywordID::kExperimentalUpdateStickyForDiff) {\n    update_sticky_for_diff_ = attribute_utils::GetBool(value);\n  } else if (kw == KeywordID::kItemSnap) {\n    ResolveItemSnapProp(value);\n  } else if (kw == KeywordID::kEnableInsertPlatformViewOperation) {\n    enable_insert_platform_view_operation_ = attribute_utils::GetBool(value);\n  } else if (kw == KeywordID::kNeedVisibleItemInfo ||\n             kw == KeywordID::kNeedsVisibleCells) {\n    need_visible_item_info_ = attribute_utils::GetBool(value);\n    GetEventCallbackManager()->SetNeedsVisibleCells(need_visible_item_info_);\n  } else {\n    ScrollView::SetAttribute(attr_c, value);\n  }\n}\n\nint ListContainerView::GetIndexFromItemKey(std::string item_key) const {\n  if (item_key.empty()) {\n    return -1;\n  }\n  auto it = item_key_map_.find(item_key);\n  if (it == item_key_map_.end()) {\n    return -1;\n  }\n  return it->second;\n}\n\nvoid ListContainerView::EraseStickyItem(BaseView* view) {\n  if (!view || !view->Is<Component>() || !enable_list_sticky_) return;\n  auto* component = static_cast<Component*>(view);\n  if (update_sticky_for_diff_) {\n    if (auto item_key = view->ItemKey(); !item_key.empty()) {\n#define ERASE_STICKY_ITEM(sticky_map_ref)                     \\\n  do {                                                        \\\n    auto it = (sticky_map_ref).find(item_key);                \\\n    if (it != (sticky_map_ref).end() && it->second == view) { \\\n      (sticky_map_ref).erase(it);                             \\\n      component->SetNodeReadyListener(nullptr);               \\\n      if (enable_recycle_sticky_item_) {                      \\\n        ResetStickyItem(component);                           \\\n      }                                                       \\\n      return;                                                 \\\n    }                                                         \\\n  } while (0)\n      ERASE_STICKY_ITEM(sticky_top_item_map_);\n      ERASE_STICKY_ITEM(sticky_bottom_item_map_);\n#undef ERASE_STICKY_ITEM\n    }\n  } else {\n    UpdateStickyInfoForDeletedChild(component, sticky_top_items_);\n    UpdateStickyInfoForDeletedChild(component, sticky_bottom_items_);\n  }\n}\n\nvoid ListContainerView::ResolveItemSnapProp(const clay::Value& value) {\n  ResetItemSnapProp();\n\n  if (!value.IsMap()) {\n    return;\n  }\n\n  const auto& map = attribute_utils::GetMap(value);\n\n  auto iter = map.find(\"factor\");\n  if (iter != map.end() && iter->second.IsNumber()) {\n    snap_factor_ =\n        std::clamp(attribute_utils::GetDouble(iter->second), 0.0, 1.0);\n  }\n\n  iter = map.find(\"offset\");\n  if (iter != map.end() && iter->second.IsNumber()) {\n    snap_offset_ = attribute_utils::GetDouble(iter->second);\n  }\n}\n\nvoid ListContainerView::ResetItemSnapProp() {\n  snap_factor_ = -1;\n  snap_offset_ = 0;\n}\n\nvoid ListContainerView::RemoveListItemPaintingNode(BaseView* view) {\n  RemoveChild(view);\n  EraseStickyItem(view);\n}\n\nvoid ListContainerView::InsertListItemPaintingNode(BaseView* view) {\n  // In multi thread, the child component has been rendered when invoking\n  // onLayoutFinish() but may not have any layout info, because we block the\n  // child component layout info's flushing which triggered by starlight\n  // engine, so we cannot add child view to the list until getting the real\n  // layout info of the child component.\n  // TODO(dongjiajian): considering async layout here.\n  InsertListItemPaintingNodeInternal(view);\n  view->render_object()->SetRepaintBoundary(true);\n  if (view->Is<Component>()) {\n    auto component = static_cast<Component*>(view);\n    if (enable_list_sticky_) {\n      if (update_sticky_for_diff_) {\n        std::string itemKey = view->ItemKey();\n        if (!itemKey.empty()) {\n          if (sticky_top_item_key_set_.count(itemKey)) {\n            sticky_top_item_map_[itemKey] = component;\n            component->SetNodeReadyListener(this);\n          } else if (sticky_bottom_item_key_set_.count(itemKey)) {\n            sticky_bottom_item_map_[itemKey] = component;\n            component->SetNodeReadyListener(this);\n          }\n        }\n      } else {\n        int index = GetIndexFromItemKey(component->ItemKey());\n        UpdateStickyInfoForUpdatedChild(component, sticky_top_items_,\n                                        sticky_top_indexes_, index);\n        UpdateStickyInfoForUpdatedChild(component, sticky_bottom_items_,\n                                        sticky_bottom_indexes_, index);\n      }\n    }\n  }\n  if (delegate_) {\n    delegate_->OnListViewDidLayout();\n  }\n}\n\nvoid ListContainerView::InsertListItemPaintingNodeInternal(BaseView* view) {\n  if (view && !view->Parent()) {\n    ScrollView::AddChild(view, child_count());\n  }\n}\n\nvoid ListContainerView::ScrollToPosition(\n    bool smooth, int position, float offset, AlignTo align_to,\n    const std::string& id,\n    const std::function<void(uint32_t, const std::string&)>& callback) {\n  if (position < 0) {\n    if (callback) {\n      callback(static_cast<uint32_t>(LynxUIMethodResult::kParamInvalid), \"\");\n    }\n    return;\n  }\n  int align_to_int = 0;\n  if (align_to == AlignTo::kStart) {\n    align_to_int = 0;\n  } else if (align_to == AlignTo::kMiddle) {\n    align_to_int = 1;\n  } else if (align_to == AlignTo::kEnd) {\n    align_to_int = 2;\n  }\n  if (delegate_) {\n    delegate_->OnScrollToPosition(position, offset, align_to_int, smooth);\n  }\n  if (callback) {\n    callback(static_cast<uint32_t>(LynxUIMethodResult::kSuccess), \"\");\n  }\n}\n\nvoid ListContainerView::UpdateContentOffsetForListContainer(\n    float content_size, float target_content_offset_x,\n    float target_content_offset_y) {\n  SetMaxContent(content_size);\n  should_block_did_scroll_ = true;\n  if (GetScrollDirection() == ScrollDirection::kVertical) {\n    ScrollWithDelta(false, target_content_offset_y);\n  } else {\n    ScrollWithDelta(false, target_content_offset_x);\n  }\n  should_block_did_scroll_ = false;\n}\n\nvoid ListContainerView::UpdateScrollInfo(bool smooth, float estimated_offset,\n                                         bool scrolling) {\n  scrolling_estimated_offset_ = estimated_offset;\n  if (!scrolling) {\n    ScrollTo(smooth, estimated_offset);\n    if (smooth) {\n      SetScrollStatus(Scrollable::ScrollStatus::kAnimating);\n    }\n  }\n}\n\nvoid ListContainerView::AddChild(BaseView* child, int position) {\n  // The list container view only add child in `OnLayoutFinish` and\n  // `InsertListItemPaintingNode`\n  if (!update_sticky_for_diff_) {\n    int index = GetIndexFromItemKey(child->ItemKey());\n    UpdateStickyInfoForInsertedChild(child, sticky_top_items_,\n                                     sticky_top_indexes_, index);\n    UpdateStickyInfoForInsertedChild(child, sticky_bottom_items_,\n                                     sticky_bottom_indexes_, index);\n  }\n}\n\nvoid ListContainerView::UpdateStickyInfoForInsertedChild(\n    BaseView* child, std::unordered_map<int, Component*>& sticky_items,\n    std::vector<int>& sticky_indexes, int index) {\n  if (!enable_list_sticky_ || !child->Is<Component>()) {\n    return;\n  }\n  for (unsigned i = 0; i < sticky_indexes.size(); ++i) {\n    if (index == sticky_indexes[i]) {\n      sticky_items[index] = static_cast<Component*>(child);\n    }\n  }\n}\n\nvoid ListContainerView::UpdateStickyInfoForDeletedChild(\n    BaseView* child, std::unordered_map<int, Component*>& sticky_items) {\n  if (!enable_list_sticky_ || !child || !child->Is<Component>()) {\n    return;\n  }\n  for (auto it = sticky_items.begin(); it != sticky_items.end(); ++it) {\n    if (it->second == child) {\n      if (enable_recycle_sticky_item_) {\n        ResetStickyItem(static_cast<Component*>(child));\n      }\n      sticky_items.erase(it);\n      break;\n    }\n  }\n}\n\nvoid ListContainerView::UpdateStickyInfoForUpdatedChild(\n    Component* child, std::unordered_map<int, Component*> sticky_items,\n    const std::vector<int>& sticky_indexes, int index) {\n  if (!enable_list_sticky_ || !child || !child->Is<Component>()) {\n    return;\n  }\n  for (unsigned i = 0; i < sticky_indexes.size(); ++i) {\n    if (index == sticky_indexes[i]) {\n      sticky_items[index] = static_cast<Component*>(child);\n      break;\n    }\n  }\n}\n\nvoid ListContainerView::ResetStickyItem(Component* child) {\n  if (child != nullptr) {\n    child->SetTransform(TransformOperations(), FloatPoint());\n  }\n};\n\nvoid ListContainerView::OnNodeReady() {\n  ScrollView::OnNodeReady();\n  UpdateStickyStarts(scroll_offset_.x(), scroll_offset_.y());\n  UpdateStickyEnds(scroll_offset_.x(), scroll_offset_.y());\n}\n\nvoid ListContainerView::OnComponentNodeReady(Component* component) {\n  if (enable_list_sticky_ && update_sticky_for_diff_ && component != nullptr) {\n    std::string item_key = component->ItemKey();\n    if (!item_key.empty()) {\n      if (sticky_top_item_key_set_.count(item_key)) {\n        UpdateStickyItemMap(component, sticky_top_item_map_, true);\n      } else if (sticky_bottom_item_key_set_.count(item_key)) {\n        UpdateStickyItemMap(component, sticky_bottom_item_map_, true);\n      } else {\n        // Not sticky top or bottom list item, remove it from map.\n        UpdateStickyItemMap(component, sticky_top_item_map_, false);\n        UpdateStickyItemMap(component, sticky_bottom_item_map_, false);\n      }\n    }\n  }\n}\n\nvoid ListContainerView::UpdateStickyEnds(float offset_x, float offset_y) {\n  if (!enable_list_sticky_) {\n    return;\n  }\n\n  bool is_vertical = GetScrollDirection() == ScrollDirection::kVertical;\n\n  int offset = 0;\n  if (is_vertical) {\n    offset = offset_y + Height() - sticky_offset_;\n  } else {\n    offset = offset_x + Width() - sticky_offset_;\n  }\n\n  Component* sticky_end_item = nullptr;\n  Component* next_sticky_end_item = nullptr;\n  for (int end_index : sticky_bottom_indexes_) {\n    Component* end_component = GetStickyItemWithIndex(end_index, false);\n    if (!end_component) {\n      continue;\n    }\n    auto cur_offset = is_vertical\n                          ? (end_component->Top() + end_component->Height())\n                          : (end_component->Left() + end_component->Width());\n    if (cur_offset < offset) {\n      next_sticky_end_item = end_component;\n      ResetStickyItem(end_component);\n    } else if (sticky_end_item != nullptr) {\n      ResetStickyItem(end_component);\n    } else {\n      sticky_end_item = end_component;\n    }\n  }\n  if (sticky_end_item != nullptr) {\n    if (prev_sticky_bottom_item_ != sticky_end_item) {\n      if (is_vertical) {\n        page_view_->SendEvent(GetCallbackId(),\n                              event_attr::kEventListStickyBottom, {\"bottom\"},\n                              sticky_end_item->ItemKey());\n      }\n      page_view_->SendEvent(GetCallbackId(), event_attr::kEventListStickyEnd,\n                            {\"end\"}, sticky_end_item->ItemKey());\n      prev_sticky_bottom_item_ = sticky_end_item;\n    }\n    int sticky_start_offset = offset - (is_vertical ? sticky_end_item->Height()\n                                                    : sticky_end_item->Width());\n    if (next_sticky_end_item != nullptr) {\n      int next_sticky_end_item_distance_from_offset = 0;\n      int squash_sticky_end_delta = 0;\n      if (is_vertical) {\n        next_sticky_end_item_distance_from_offset =\n            offset -\n            (next_sticky_end_item->Top() + next_sticky_end_item->Height());\n        squash_sticky_end_delta = sticky_end_item->Height() -\n                                  next_sticky_end_item_distance_from_offset;\n      } else {\n        next_sticky_end_item_distance_from_offset =\n            offset -\n            (next_sticky_end_item->Left() + next_sticky_end_item->Width());\n        squash_sticky_end_delta = sticky_end_item->Width() -\n                                  next_sticky_end_item_distance_from_offset;\n      }\n\n      if (squash_sticky_end_delta > 0) {\n        sticky_start_offset += squash_sticky_end_delta;\n      }\n    }\n    if (sticky_end_item != nullptr) {\n      TransformOperations ops;\n      if (is_vertical) {\n        ops.AppendTranslate(0, sticky_start_offset - sticky_end_item->Top(),\n                            std::numeric_limits<int>::max());\n      } else {\n        ops.AppendTranslate(sticky_start_offset - sticky_end_item->Left(), 0,\n                            std::numeric_limits<int>::max());\n      }\n\n      sticky_end_item->SetTransform(ops, FloatPoint());\n    }\n  }\n}\n\nvoid ListContainerView::UpdateStickyItemMap(\n    Component* component,\n    std::unordered_map<std::string, Component*>& sticky_item_map,\n    bool is_sticky_item) {\n  if (component && !component->ItemKey().empty()) {\n    if (is_sticky_item) {\n      std::string item_key = component->ItemKey();\n      auto it = sticky_item_map.find(item_key);\n      if (it != sticky_item_map.end() && it->second == component) {\n        return;\n      }\n      for (auto iter = sticky_item_map.begin(); iter != sticky_item_map.end();\n           ++iter) {\n        if (iter->second == component && iter->first != item_key) {\n          sticky_item_map.erase(iter);\n          sticky_item_map[item_key] = component;\n          break;\n        }\n      }\n    } else {\n      // The component is not sticky top or bottom list item, remove it from\n      // map.\n      for (auto it = sticky_item_map.begin(); it != sticky_item_map.end();\n           ++it) {\n        if (it->second == component) {\n          // Delete old <item-key, list-item> pair.\n          sticky_item_map.erase(it);\n          ResetStickyItem(component);\n          break;\n        }\n      }\n    }\n  }\n}\n\nvoid ListContainerView::UpdateStickyStarts(float offset_x, float offset_y) {\n  if (!enable_list_sticky_) {\n    return;\n  }\n\n  bool is_vertical = GetScrollDirection() == ScrollDirection::kVertical;\n\n  int offset = (is_vertical ? offset_y : offset_x) + sticky_offset_;\n  Component* sticky_start_item = nullptr;\n  Component* next_sticky_start_item = nullptr;\n  for (auto it = sticky_top_indexes_.rbegin(); it != sticky_top_indexes_.rend();\n       ++it) {\n    int start_index = *it;\n    Component* start_component = GetStickyItemWithIndex(start_index, true);\n    if (start_component == nullptr) {\n      continue;\n    }\n    auto cur_offset =\n        is_vertical ? start_component->Top() : start_component->Left();\n    if (cur_offset > offset) {\n      next_sticky_start_item = start_component;\n      ResetStickyItem(start_component);\n    } else if (sticky_start_item != nullptr) {\n      ResetStickyItem(start_component);\n    } else {\n      sticky_start_item = start_component;\n    }\n  }\n  if (sticky_start_item != nullptr) {\n    if (prev_sticky_top_item_ != sticky_start_item) {\n      if (is_vertical) {\n        page_view_->SendEvent(GetCallbackId(), event_attr::kEventListStickyTop,\n                              {\"top\"}, sticky_start_item->ItemKey());\n      }\n\n      page_view_->SendEvent(GetCallbackId(), event_attr::kEventListStickyStart,\n                            {\"start\"}, sticky_start_item->ItemKey());\n\n      prev_sticky_top_item_ = sticky_start_item;\n    }\n\n    int sticky_start_offset = offset;\n    if (next_sticky_start_item != nullptr) {\n      int squash_sticky_top_delta = 0;\n      if (is_vertical) {\n        squash_sticky_top_delta = sticky_start_item->Height() -\n                                  (next_sticky_start_item->Top() - offset);\n      } else {\n        squash_sticky_top_delta = sticky_start_item->Width() -\n                                  (next_sticky_start_item->Left() - offset);\n      }\n\n      if (squash_sticky_top_delta > 0) {\n        sticky_start_offset -= squash_sticky_top_delta;\n      }\n    }\n    if (sticky_start_item != nullptr) {\n      TransformOperations ops;\n      if (is_vertical) {\n        ops.AppendTranslate(0, sticky_start_offset - sticky_start_item->Top(),\n                            std::numeric_limits<int>::max());\n      } else {\n        ops.AppendTranslate(sticky_start_offset - sticky_start_item->Left(), 0,\n                            std::numeric_limits<int>::max());\n      }\n\n      sticky_start_item->SetTransform(ops, FloatPoint());\n    }\n  }\n}\n\nComponent* ListContainerView::GetStickyItemWithIndex(int index,\n                                                     bool is_sticky_top) {\n  Component* component = nullptr;\n  if (update_sticky_for_diff_) {\n    auto& sticky_item_map =\n        is_sticky_top ? sticky_top_item_map_ : sticky_bottom_item_map_;\n    if (index >= 0 && index < static_cast<int>(item_keys_.size())) {\n      const std::string& item_key = item_keys_[index];\n      if (!item_key.empty()) {\n        auto it = sticky_item_map.find(item_key);\n        if (it != sticky_item_map.end()) {\n          component = it->second;\n        }\n      }\n    }\n  } else {\n    auto& sticky_items =\n        is_sticky_top ? sticky_top_items_ : sticky_bottom_items_;\n    auto it = sticky_items.find(index);\n    if (it != sticky_items.end()) {\n      component = it->second;\n    }\n  }\n  return component;\n}\n\nvoid ListContainerView::GenerateStickyItemKeySet(\n    std::unordered_set<std::string>& sticky_item_key_set,\n    std::unordered_map<std::string, Component*>& sticky_item_map,\n    const std::vector<int>& sticky_item_indexes) {\n  sticky_item_key_set.clear();\n  for (unsigned i = 0; i < sticky_item_indexes.size(); ++i) {\n    int index = sticky_item_indexes[i];\n    if (index >= 0 && index < static_cast<int>(item_keys_.size())) {\n      sticky_item_key_set.insert(item_keys_[index]);\n    }\n  }\n  // Remove item from sticky dict if not sticky.\n  for (auto it = sticky_item_map.begin(); it != sticky_item_map.end();) {\n    if (it->second &&\n        sticky_item_key_set.find(it->first) == sticky_item_key_set.end()) {\n      ResetStickyItem(it->second);\n      it->second->SetNodeReadyListener(nullptr);\n      it = sticky_item_map.erase(it);\n    } else {\n      ++it;\n    }\n  }\n}\n\nvoid ListContainerView::OnLayoutFinish(BaseView* view) {\n  // In multi thread, the child component has been rendered when invoking\n  // onLayoutFinish() but may not have any layout info, because we block the\n  // child component layout info's flushing which triggered by starlight engine,\n  // so we cannot add child view to the list until getting the real layout info\n  // of the child component.\n  if (!enable_batch_render_strategy_ &&\n      !enable_insert_platform_view_operation_) {\n    InsertListItemPaintingNode(view);\n  }\n}\n\nvoid ListContainerView::CalculateOverFlow() {\n  ScrollView::CalculateOverFlow();\n  SetMaxContent(max_content_);\n}\n\nvoid ListContainerView::SetMaxContent(float value) {\n  max_content_ = value;\n  if (GetScrollDirection() == ScrollDirection::kVertical) {\n    GetRenderScroll()->SetOverflowRect(\n        FloatRect(0, 0, GetRenderScroll()->Width(), max_content_));\n  } else {\n    GetRenderScroll()->SetOverflowRect(\n        FloatRect(0, 0, max_content_, GetRenderScroll()->Height()));\n  }\n}\n\nvoid ListContainerView::HandleEvent(const PointerEvent& event) {\n  if (snap_factor_ >= 0 &&\n      (event.device == PointerEvent::DeviceType::kTouch ||\n       event.device == PointerEvent::DeviceType::kMouse ||\n       event.device == PointerEvent::DeviceType::kTrackpad)) {\n    DetectSnapScroll(event.type);\n  }\n  ScrollView::HandleEvent(event);\n}\n\nvoid ListContainerView::OnScrollStatusChange(ScrollStatus old_status) {\n  // NOTE: Here we skip the call of ScrollView::OnScrollStatusChange, because\n  // the logic is useless for list container.\n  NestedScrollable::OnScrollStatusChange(old_status);\n\n  is_scroll_animating_ = status_ == Scrollable::ScrollStatus::kAnimating;\n\n  switch (status_) {\n    case Scrollable::ScrollStatus::kFling:\n      SetScrollState(ListScrollState::kSettling);\n      break;\n    case Scrollable::ScrollStatus::kBounce:\n      SetScrollState(ListScrollState::kSettling);\n      break;\n    case Scrollable::ScrollStatus::kDragging:\n      last_scroll_offset_for_snap_ = scroll_offset_;\n      SetScrollState(ListScrollState::kDragging);\n      break;\n    case Scrollable::ScrollStatus::kIdle:\n      SetScrollState(ListScrollState::kIdle);\n      break;\n    default:\n      break;\n  }\n}\n\nvoid ListContainerView::SetScrollState(ListScrollState state) {\n  if (scroll_state_ == state) {\n    return;\n  }\n\n  scroll_state_ = state;\n  if (state == ListScrollState::kIdle && delegate_) {\n    delegate_->OnScrollStopped();\n  }\n\n  clay::Value::Map args;\n  args[\"state\"] = clay::Value(static_cast<int>(state));\n\n  if (need_visible_item_info_) {\n    auto cells_array = GetVisibleCells();\n    args[\"attachedCells\"] = clay::Value(std::move(cells_array));\n  }\n\n  page_view_->SendCustomEvent(\n      GetCallbackId(), event_attr::kEventScrollStateChange, std::move(args));\n}\n\nvoid ListContainerView::DidScroll() {\n  ScrollView::DidScroll();\n  if (!should_block_did_scroll_ && delegate_) {\n    delegate_->OnScrollByListContainer(scroll_offset_.x(), scroll_offset_.y(),\n                                       scroll_offset_.x(), scroll_offset_.y());\n    UpdateStickyStarts(scroll_offset_.x(), scroll_offset_.y());\n    UpdateStickyEnds(scroll_offset_.x(), scroll_offset_.y());\n  }\n}\n\nvoid ListContainerView::OnScrollUpdate(float offset) {\n  if (status_ == Scrollable::ScrollStatus::kDragging) {\n    last_scroll_offset_for_snap_ = scroll_offset_;\n  }\n  if (is_scroll_animating_) {\n    if (initial_scrolling_estimated_offset_ != 0) {\n      offset *=\n          scrolling_estimated_offset_ / initial_scrolling_estimated_offset_;\n    }\n    if (scrolling_estimated_offset_ > 0) {\n      if ((scroll_to_lower_ && offset > scrolling_estimated_offset_) ||\n          (!scroll_to_lower_ && offset < scrolling_estimated_offset_)) {\n        offset = scrolling_estimated_offset_;\n      }\n    }\n  }\n  ScrollView::OnScrollUpdate(offset);\n}\n\nvoid ListContainerView::OnScrollAnimationStart(float start, float delta,\n                                               int duration) {\n  ScrollView::OnScrollAnimationStart(start, delta, duration);\n  scroll_to_lower_ = delta > 0;\n  initial_scrolling_estimated_offset_ = start + delta;\n}\n\nvoid ListContainerView::DidUpdateAttributes() {\n  ScrollView::DidUpdateAttributes();\n  if (enable_list_sticky_ && update_sticky_for_diff_) {\n    GenerateStickyItemKeySet(sticky_top_item_key_set_, sticky_top_item_map_,\n                             sticky_top_indexes_);\n    GenerateStickyItemKeySet(sticky_bottom_item_key_set_,\n                             sticky_bottom_item_map_, sticky_bottom_indexes_);\n  }\n}\n\nclay::Value::Array ListContainerView::GetVisibleCells() {\n  std::vector<float> left_array, right_array, top_array, bottom_array;\n  std::vector<int> position_array;\n  std::vector<std::string> id_array;\n  std::vector<std::string> item_key_array;\n\n  size_t visible_count =\n      GetVisibleItemsInfo(top_array, bottom_array, left_array, right_array,\n                          position_array, id_array, item_key_array);\n\n  clay::Value::Array cells_array(visible_count);\n  for (size_t i = 0; i < visible_count; i++) {\n    clay::Value::Map cell;\n    cell[\"id\"] = clay::Value(id_array[i]);\n    cell[\"position\"] = clay::Value(position_array[i]);\n    cell[\"index\"] = clay::Value(position_array[i]);\n    cell[\"left\"] = clay::Value(left_array[i]);\n    cell[\"right\"] = clay::Value(right_array[i]);\n    cell[\"top\"] = clay::Value(top_array[i]);\n    cell[\"bottom\"] = clay::Value(bottom_array[i]);\n    cell[\"itemKey\"] = clay::Value(item_key_array[i]);\n    cells_array[i] = clay::Value(std::move(cell));\n  }\n\n  return cells_array;\n}\n\nvoid ListContainerView::DetectSnapScroll(PointerEvent::EventType type) {\n  if (snap_factor_ >= 0) {\n    switch (type) {\n      case PointerEvent::EventType::kDownEvent:\n      case PointerEvent::EventType::kMoveEvent:\n      case PointerEvent::EventType::kPanZoomUpdateEvent:\n        last_scroll_offset_for_snap_ = scroll_offset_;\n        break;\n      case PointerEvent::EventType::kUpEvent:\n      case PointerEvent::EventType::kPanZoomEndEvent: {\n        bool is_vertical = GetScrollDirection() == ScrollDirection::kVertical;\n        bool forward = false;\n        if (is_vertical) {\n          if (last_scroll_offset_for_snap_.y() == scroll_offset_.y() &&\n              scroll_offset_.y() == 0) {\n            forward = false;\n          } else {\n            forward = scroll_offset_.y() >= last_scroll_offset_for_snap_.y();\n          }\n        } else {\n          if (last_scroll_offset_for_snap_.x() == scroll_offset_.x() &&\n              scroll_offset_.x() == 0) {\n            forward = false;\n          } else {\n            forward = scroll_offset_.x() >= last_scroll_offset_for_snap_.x();\n          }\n        }\n\n        bool has_velocity =\n            is_vertical\n                ? last_scroll_offset_for_snap_.y() != scroll_offset_.y()\n                : last_scroll_offset_for_snap_.x() != scroll_offset_.x();\n\n        auto scroll_target = CalcSnapScroll(forward, has_velocity);\n        int32_t scroll_position = std::get<0>(scroll_target);\n        if (scroll_position != -1) {\n          float target_x = std::get<1>(scroll_target);\n          float target_y = std::get<2>(scroll_target);\n\n          float target_offset = is_vertical ? target_y : target_x;\n\n          // ScrollTo expects an absolute content offset.\n          // However, we must ensure we don't scroll beyond bounds (though\n          // ScrollTo handles some clamping, explicit safety is better).\n          // And importantly, ensure we are not already there to avoid\n          // unnecessary calls/animations.\n          ScrollTo(true, target_offset);\n        }\n\n        if (page_view_) {\n          clay::Value::Map dict;\n          dict[\"position\"] = clay::Value(scroll_position);\n          dict[\"currentScrollLeft\"] = clay::Value(scroll_offset_.x());\n          dict[\"currentScrollTop\"] = clay::Value(scroll_offset_.y());\n          dict[\"targetScrollLeft\"] = clay::Value(std::get<1>(scroll_target));\n          dict[\"targetScrollTop\"] = clay::Value(std::get<2>(scroll_target));\n          page_view_->SendCustomEvent(GetCallbackId(), \"snap\", std::move(dict));\n        }\n      } break;\n      case PointerEvent::EventType::kCancel:\n        break;\n      default:\n        break;\n    }\n  }\n}\n\nstd::tuple<int32_t, float, float> ListContainerView::CalcSnapScroll(\n    bool forward, bool has_velocity) const {\n  bool is_vertical = GetScrollDirection() == ScrollDirection::kVertical;\n  float content_offset = is_vertical ? scroll_offset_.y() : scroll_offset_.x();\n\n  Component* closest_item_before = nullptr;\n  Component* closest_item_after = nullptr;\n\n  float distance_before = std::numeric_limits<float>::lowest();\n  float distance_after = std::numeric_limits<float>::max();\n  float max_scroll_range = GetScrollRange();\n  float min_scroll_range = 0.f;\n\n  float viewport_size = is_vertical ? Height() : Width();\n  float viewport_start = content_offset;\n  float viewport_end = content_offset + viewport_size;\n  float check_start = viewport_start - viewport_size;\n  float check_end = viewport_end + viewport_size;\n\n  for (BaseView* child : children_) {\n    if (child && child->Is<Component>()) {\n      auto* list_item = static_cast<Component*>(child);\n      // Only consider visible items or items close to viewport\n      // Simple visibility check:\n      float item_start = is_vertical ? list_item->Top() : list_item->Left();\n      float item_end = is_vertical ? (list_item->Top() + list_item->Height())\n                                   : (list_item->Left() + list_item->Width());\n\n      if (item_end >= check_start && item_start <= check_end) {\n        float item_snap_offset = GetListItemSnapScrollOffset(list_item);\n        float clamped_snap_offset =\n            std::clamp(item_snap_offset, min_scroll_range, max_scroll_range);\n\n        float distance = clamped_snap_offset - content_offset;\n\n        if (lynx::base::FloatsLargerOrEqual(0, distance) &&\n            distance > distance_before) {\n          distance_before = distance;\n          closest_item_before = list_item;\n        }\n        if (lynx::base::FloatsLargerOrEqual(distance, 0) &&\n            distance < distance_after) {\n          distance_after = distance;\n          closest_item_after = list_item;\n        }\n      }\n    }\n  }\n\n  int32_t target_position = -1;\n  Component* target_item = nullptr;\n\n  if (!has_velocity) {\n    if (closest_item_after && closest_item_before) {\n      if (distance_after < std::abs(distance_before)) {\n        target_item = closest_item_after;\n      } else {\n        target_item = closest_item_before;\n      }\n    } else if (closest_item_after) {\n      target_item = closest_item_after;\n    } else if (closest_item_before) {\n      target_item = closest_item_before;\n    }\n  } else {\n    if (forward && closest_item_after) {\n      target_item = closest_item_after;\n    } else if (!forward && closest_item_before) {\n      target_item = closest_item_before;\n    }\n  }\n\n  if (target_item) {\n    target_position = GetIndexFromItemKey(target_item->ItemKey());\n    auto offsets = CalculateOffsets(target_item);\n    return {target_position, offsets.first, offsets.second};\n  }\n\n  // Fallback: extrapolate if no item found (edge case)\n  Component* visible_item = forward ? closest_item_before : closest_item_after;\n  if (!visible_item) {\n    visible_item = forward ? closest_item_after : closest_item_before;\n  }\n  if (!visible_item) return {-1, scroll_offset_.x(), scroll_offset_.y()};\n\n  target_position =\n      GetIndexFromItemKey(visible_item->ItemKey()) + (forward ? 1 : -1);\n  if (target_position < 0) target_position = 0;\n\n  // Try to find item by index\n  // Since children are not strictly ordered by index in children_ vector\n  // (z-index affects order), and we might not have all items loaded, this is\n  // tricky. For now, return current offset if target item not found.\n  for (BaseView* child : children_) {\n    if (child && child->Is<Component>()) {\n      auto* list_item = static_cast<Component*>(child);\n      if (GetIndexFromItemKey(list_item->ItemKey()) == target_position) {\n        auto offsets = CalculateOffsets(list_item);\n        return {target_position, offsets.first, offsets.second};\n      }\n    }\n  }\n\n  return {-1, scroll_offset_.x(), scroll_offset_.y()};\n}\n\nfloat ListContainerView::GetListItemSnapScrollOffset(\n    Component* list_item) const {\n  if (GetScrollDirection() == ScrollDirection::kHorizontal) {\n    return list_item->Left() - (Width() - list_item->Width()) * snap_factor_ +\n           snap_offset_;\n  } else {\n    return list_item->Top() - (Height() - list_item->Height()) * snap_factor_ +\n           snap_offset_;\n  }\n}\n\nstd::pair<float, float> ListContainerView::CalculateOffsets(\n    Component* item) const {\n  bool is_vertical = GetScrollDirection() == ScrollDirection::kVertical;\n  float range = GetScrollRange();\n  float snap_offset = GetListItemSnapScrollOffset(item);\n  float clamped_offset = std::clamp(snap_offset, 0.f, range);\n\n  if (is_vertical) {\n    return {0.f, clamped_offset};\n  } else {\n    return {clamped_offset, 0.f};\n  }\n}\n\nfloat ListContainerView::GetScrollRange() const {\n  bool is_horizontal = GetScrollDirection() == ScrollDirection::kHorizontal;\n  float content_size = is_horizontal\n                           ? GetRenderScroll()->OverflowRect().width()\n                           : GetRenderScroll()->OverflowRect().height();\n  float view_size = is_horizontal ? Width() : Height();\n  return std::max(0.f, content_size - view_size);\n}\n\nsize_t ListContainerView::GetVisibleItemsInfo(\n    std::vector<float>& top_array, std::vector<float>& bottom_array,\n    std::vector<float>& left_array, std::vector<float>& right_array,\n    std::vector<int>& position, std::vector<std::string>& id_array,\n    std::vector<std::string>& item_key_array) {\n  std::vector<BaseView*> visible_children;\n  for (BaseView* child : children_) {\n    if (child && child->Width() != 0 && child->Height() != 0) {\n      visible_children.emplace_back(child);\n    }\n  }\n  if (!visible_children.empty()) {\n    std::sort(visible_children.begin(), visible_children.end(),\n              [this](BaseView* lhs, BaseView* rhs) {\n                return GetIndexFromItemKey(lhs->ItemKey()) <\n                       GetIndexFromItemKey(rhs->ItemKey());\n              });\n    for (size_t i = 0; i < visible_children.size(); ++i) {\n      auto item = visible_children[i];\n      position.push_back(GetIndexFromItemKey(item->ItemKey()));\n      auto rect = page_view_->ConvertTo<kPixelTypeLogical>(\n          item->BoundsRelativeTo(this));\n      top_array.push_back(rect.y());\n      bottom_array.push_back(rect.MaxY());\n      left_array.push_back(rect.x());\n      right_array.push_back(rect.MaxX());\n      id_array.push_back(item->GetIdSelector());\n      item_key_array.push_back(item->ItemKey());\n    }\n  }\n  return top_array.size();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_container/list_container_view.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_CONTAINER_LIST_CONTAINER_VIEW_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_CONTAINER_LIST_CONTAINER_VIEW_H_\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <tuple>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/component/component.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/view_callback/list_container_event_callback_manager.h\"\n\n#ifndef ENABLE_CLAY_LITE\n#include \"clay/ui/component/view_callback/list_event_callback_manager.h\"\n#endif\n\nnamespace clay {\n\nclass ListContainerView : public WithTypeInfo<ListContainerView, ScrollView>,\n                          public Component::NodeReadyListener {\n public:\n  class Delegate {\n   public:\n    virtual void OnListViewDidLayout() = 0;\n    virtual void OnScrollByListContainer(float offset_x, float offset_y,\n                                         float original_x,\n                                         float original_y) = 0;\n    virtual void OnScrollToPosition(int position, float offset, int align,\n                                    bool smooth) = 0;\n    virtual void OnScrollStopped() = 0;\n  };\n\n  ListContainerView(int32_t id, PageView* page_view, int32_t callback_id);\n\n  void UpdateContentOffsetForListContainer(float content_size,\n                                           float target_content_offset_x,\n                                           float target_content_offset_y);\n\n  void UpdateScrollInfo(bool smooth, float estimated_offset, bool scrolling);\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n\n  void ScrollToPosition(\n      bool smooth, int position, float offset, AlignTo align_to,\n      const std::string& id,\n      const std::function<void(uint32_t, const std::string&)>& callback);\n\n  void AddChild(BaseView* child, int position) override;\n\n  void InsertListItemPaintingNode(BaseView* view);\n  void RemoveListItemPaintingNode(BaseView* view);\n  // Scroller::Delegate\n  void OnScrollUpdate(float scroll_offset) override;\n  void OnScrollAnimationStart(float start, float delta, int duration) override;\n  void SetDelegate(Delegate* delegate) { delegate_ = delegate; }\n\n  size_t GetVisibleItemsInfo(std::vector<float>& top_array,\n                             std::vector<float>& bottom_array,\n                             std::vector<float>& left_array,\n                             std::vector<float>& right_array,\n                             std::vector<int>& position,\n                             std::vector<std::string>& id_array,\n                             std::vector<std::string>& item_key_array);\n\n  clay::Value::Array GetVisibleCells();\n\n protected:\n  void CalculateOverFlow() override;\n\n private:\n  void DidScroll() override;\n  void DidUpdateAttributes() override;\n\n  void OnLayoutFinish(BaseView* view) override;\n  void InsertListItemPaintingNodeInternal(BaseView* view);\n\n  void OnScrollStatusChange(ScrollStatus old_status) override;\n  void SetScrollState(ListScrollState state);\n\n  void SetMaxContent(float value);\n  int GetIndexFromItemKey(std::string itemKey) const;\n\n  void ResolveItemSnapProp(const clay::Value& value);\n  void ResetItemSnapProp();\n  void DetectSnapScroll(PointerEvent::EventType type);\n  std::tuple<int32_t, float, float> CalcSnapScroll(bool forward,\n                                                   bool has_velocity) const;\n  float GetListItemSnapScrollOffset(Component* list_item) const;\n  std::pair<float, float> CalculateOffsets(Component* item) const;\n  float GetScrollRange() const;\n\n  ListContainerEventCallbackManager* GetEventCallbackManager() const {\n    return static_cast<ListContainerEventCallbackManager*>(\n        callback_manager_.get());\n  }\n\n  // sticky\n  void UpdateStickyInfoForInsertedChild(\n      BaseView* child, std::unordered_map<int, Component*>& sticky_items,\n      std::vector<int>& sticky_indexes, int index);\n  void UpdateStickyInfoForDeletedChild(\n      BaseView* child, std::unordered_map<int, Component*>& sticky_items);\n  void UpdateStickyInfoForUpdatedChild(\n      Component* child, std::unordered_map<int, Component*> sticky_items,\n      const std::vector<int>& sticky_indexes, int index);\n  void ResetStickyItem(Component* child);\n  void UpdateStickyStarts(float offset_x, float offset_y);\n  void UpdateStickyEnds(float offset_x, float offset_y);\n  Component* GetStickyItemWithIndex(int index, bool is_sticky_top);\n  void UpdateStickyItemMap(\n      Component* component,\n      std::unordered_map<std::string, Component*>& sticky_item_map,\n      bool is_sticky_item);\n  void GenerateStickyItemKeySet(\n      std::unordered_set<std::string>& sticky_item_key_set,\n      std::unordered_map<std::string, Component*>& sticky_item_map,\n      const std::vector<int>& sticky_item_indexes);\n  void EraseStickyItem(BaseView* view);\n\n  void HandleEvent(const PointerEvent& event) override;\n\n  // NodeReadyListener\n  void OnComponentNodeReady(Component* component) override;\n\n  void OnNodeReady() override;\n\n  std::unordered_map<std::string, int> item_key_map_;\n\n  float max_content_ = 0.f;\n\n  bool should_block_did_scroll_ = false;\n  bool enable_batch_render_strategy_ = false;\n\n  float initial_scrolling_estimated_offset_ = 0.f;\n  float scrolling_estimated_offset_ = 0.f;\n  bool scroll_to_lower_ = false;\n  bool is_scroll_animating_ = false;\n  ListScrollState scroll_state_ = ListScrollState::kIdle;\n\n  Delegate* delegate_ = nullptr;\n\n  std::vector<std::string> item_keys_;\n\n  bool enable_list_sticky_ = false;\n  int sticky_offset_ = 0;\n  float snap_factor_ = -1.f;\n  float snap_offset_ = 0.f;\n  FloatPoint last_scroll_offset_for_snap_{};\n\n  bool need_visible_item_info_ = false;\n\n  std::unordered_map<int, Component*> sticky_top_items_;\n  std::unordered_map<int, Component*> sticky_bottom_items_;\n\n  std::vector<int> sticky_top_indexes_;\n  std::vector<int> sticky_bottom_indexes_;\n\n  Component* prev_sticky_top_item_ = nullptr;\n  Component* prev_sticky_bottom_item_ = nullptr;\n\n  bool enable_recycle_sticky_item_ = true;\n  bool update_sticky_for_diff_ = true;\n  bool enable_insert_platform_view_operation_ = false;\n\n  std::unordered_set<std::string> sticky_top_item_key_set_;\n  std::unordered_set<std::string> sticky_bottom_item_key_set_;\n\n  std::unordered_map<std::string, Component*> sticky_top_item_map_;\n  std::unordered_map<std::string, Component*> sticky_bottom_item_map_;\n\n  friend class ListContainerWrapper;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_CONTAINER_LIST_CONTAINER_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_container/list_container_wrapper.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_container/list_container_wrapper.h\"\n\n#include <math.h>\n\n#include <cmath>\n#include <string>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/keywords.h\"\n#include \"clay/ui/component/list/list_scroller.h\"\n#include \"clay/ui/component/scrollbar/scrollbar_orientation_helper.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n\nnamespace clay {\nnamespace {\n\nconst std::unordered_set<KeywordID> kProxyAttributes = {\n    KeywordID::kSticky,\n    KeywordID::kStickyOffset,\n    KeywordID::kExperimentalRecycleStickyItem,\n    KeywordID::kExperimentalBatchRenderStrategy,\n    KeywordID::kExperimentalUpdateStickyForDiff,\n    KeywordID::kListContainerInfo,\n    KeywordID::kScrollX,\n    KeywordID::kScrollY,\n    KeywordID::kLowerThreshold,\n    KeywordID::kUpperThreshold,\n    KeywordID::kEnableScroll,\n    KeywordID::kEnableNestedScroll,\n    KeywordID::kScrollTop,\n    KeywordID::kScrollLeft,\n    KeywordID::kScrollToIndex,\n    KeywordID::kDirection,\n    KeywordID::kInitialScrollOffset,\n    KeywordID::kInitialScrollIndex,\n    KeywordID::kScrollOrientation,\n    KeywordID::kScrollForwardMode,\n    KeywordID::kScrollBackwardMode,\n    KeywordID::kPrevScrollable,\n    KeywordID::kNextScrollable,\n    KeywordID::kBounce,\n    KeywordID::kBounces,\n    KeywordID::kScrollToId,\n    KeywordID::kScrollEventThrottle,\n    KeywordID::kItemSnap,\n    KeywordID::kEnableInsertPlatformViewOperation,\n    KeywordID::kNeedVisibleItemInfo,\n    KeywordID::kNeedsVisibleCells};\nconstexpr char kListContainerWrapperTag[] = \"list-container-wrapper\";\n\nLYNX_UI_METHOD_BEGIN(ListContainerWrapper) {\n  LYNX_UI_METHOD(ListContainerWrapper, scrollToPosition);\n  LYNX_UI_METHOD(ListContainerWrapper, autoScroll);\n  LYNX_UI_METHOD(ListContainerWrapper, getVisibleItemsPositions);\n  LYNX_UI_METHOD(ListContainerWrapper, getVisibleCells);\n  LYNX_UI_METHOD(ListContainerWrapper, getScrollInfo);\n}\nLYNX_UI_METHOD_END(ListContainerWrapper);\n}  // namespace\n\nListContainerWrapper::ListContainerWrapper(int32_t id, PageView* page_view)\n    : WithTypeInfo(id, ScrollDirection::kVertical, kListContainerWrapperTag,\n                   page_view) {\n  view_ = new ListContainerView(-1, page_view, id_);\n  view_->SetOverflow(CSSProperty::OVERFLOW_HIDDEN);\n  view_->SetRepaintBoundary(true);\n  GetListContainerView()->SetDelegate(this);\n  GetListContainerView()->AddScrollListener(this);\n  BaseView::AddChild(view_, 0);\n}\n\nvoid ListContainerWrapper::OnScrollByListContainer(float offset_x,\n                                                   float offset_y,\n                                                   float original_x,\n                                                   float original_y) {\n  if (auto delegate = page_view_->GetUIComponentDelegate()) {\n    delegate->OnScrollByListContainer(id_, offset_x, offset_y, original_x,\n                                      original_y);\n  }\n}\n\nvoid ListContainerWrapper::OnScrollStopped() {\n  if (auto delegate = page_view_->GetUIComponentDelegate()) {\n    delegate->OnScrollStopped(id_);\n  }\n}\n\nvoid ListContainerWrapper::OnListViewDidLayout() { UpdateScrollbarIfNeeded(); }\n\nvoid ListContainerWrapper::OnDestroy() {\n  ScrollbarWrapper::OnDestroy();\n  GetListContainerView()->SetDelegate(nullptr);\n  GetListContainerView()->RemoveScrollListener(this);\n}\n\nvoid ListContainerWrapper::scrollToPosition(\n    const LynxModuleValues& args, const LynxUIMethodCallback& callback) {\n  bool smooth = false;\n  int position = -1;\n  int index = -1;\n  float offset = 0;\n  std::string align_to_str;\n  std::string id;\n  CastNamedLynxModuleArgs(\n      {\"smooth\", \"position\", \"index\", \"offset\", \"alignTo\", \"id\"}, args, smooth,\n      position, index, offset, align_to_str, id);\n  if (isnan(offset) || isinf(offset) || (index == -1 && position == -1)) {\n    if (callback) {\n      callback(LynxUIMethodResult::kParamInvalid, clay::Value());\n    }\n    return;\n  }\n\n  AlignTo align_to = StringToAlign(align_to_str);\n  GetListContainerView()->ScrollToPosition(\n      smooth, index == -1 ? position : index, FromLogical(offset), align_to, id,\n      [callback](uint32_t result, const std::string&) {\n        if (callback) {\n          callback(static_cast<LynxUIMethodResult>(result), clay::Value());\n        }\n      });\n}\n\nvoid ListContainerWrapper::autoScroll(const LynxModuleValues& args,\n                                      const LynxUIMethodCallback& callback) {\n  float rate = 0.f;\n  bool start = false;\n  bool auto_stop = true;  // TODO: supported in ScrollView\n  CastNamedLynxModuleArgs({\"rate\", \"start\", \"autoStop\"}, args, rate, start,\n                          auto_stop);\n  GetListContainerView()->AutoScroll(start, FromLogical(rate));\n  if (callback) {\n    callback(LynxUIMethodResult::kSuccess, clay::Value());\n  }\n}\n\nvoid ListContainerWrapper::UpdateContentOffsetForListContainer(\n    float content_size, float target_content_offset_x,\n    float target_content_offset_y) {\n  GetListContainerView()->UpdateContentOffsetForListContainer(\n      content_size, target_content_offset_x, target_content_offset_y);\n}\n\nvoid ListContainerWrapper::InsertListItemPaintingNode(BaseView* view) {\n  GetListContainerView()->InsertListItemPaintingNode(view);\n}\n\nvoid ListContainerWrapper::RemoveListItemPaintingNode(BaseView* view) {\n  GetListContainerView()->RemoveListItemPaintingNode(view);\n}\n\nvoid ListContainerWrapper::SetAttribute(const char* attr_c,\n                                        const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kProxyAttributes.find(kw) != kProxyAttributes.end()) {\n    view_->SetAttribute(attr_c, value);\n    if (kw == KeywordID::kScrollX || kw == KeywordID::kScrollY ||\n        kw == KeywordID::kScrollOrientation ||\n        kw == KeywordID::kScrollDirection) {\n      scrollbar_->SetScrollDirection(\n          GetListContainerView()->GetScrollDirection());\n    }\n  } else {\n    ScrollbarWrapper::SetAttribute(attr_c, value);\n  }\n}\n\nvoid ListContainerWrapper::DidUpdateAttributes() {\n  BaseView::DidUpdateAttributes();\n  view_->DidUpdateAttributes();\n}\n\nvoid ListContainerWrapper::OnNodeReady() {\n  GetListContainerView()->OnNodeReady();\n}\n\nvoid ListContainerWrapper::OnScrollToPosition(int position, float offset,\n                                              int align, bool smooth) {\n  if (auto delegate = page_view_->GetUIComponentDelegate()) {\n    delegate->OnScrollToPosition(id_, position, offset, align, smooth);\n  }\n}\n\nvoid ListContainerWrapper::getVisibleItemsPositions(\n    const LynxModuleValues&, const LynxUIMethodCallback& callback) {\n  std::vector<float> left_array, right_array, top_array, bottom_array;\n  std::vector<int> position_array;\n  std::vector<std::string> id_array;\n  std::vector<std::string> item_key_array;\n  size_t visible_count = 0;\n  visible_count = GetListContainerView()->GetVisibleItemsInfo(\n      top_array, bottom_array, left_array, right_array, position_array,\n      id_array, item_key_array);\n\n  clay::Value::Array visible_position_array(visible_count);\n  for (size_t i = 0; i < visible_count; ++i) {\n    visible_position_array[i] = clay::Value(position_array[i]);\n  }\n  callback(LynxUIMethodResult::kSuccess,\n           clay::Value(std::move(visible_position_array)));\n}\n\nvoid ListContainerWrapper::getScrollInfo(const LynxModuleValues& args,\n                                         const LynxUIMethodCallback& callback) {\n  if (callback) {\n    FloatPoint offset = view_->GetScrollOffset();\n    FloatSize zoomed_content = page_view_->ConvertTo<kPixelTypeLogical>(\n        FloatSize(view_->ContentWidth(), view_->ContentHeight()));\n    FloatPoint zoomed_offset = page_view_->ConvertTo<kPixelTypeLogical>(offset);\n    clay::Value::Map map;\n    map.emplace(\"scrollTop\", zoomed_offset.y());\n    map.emplace(\"scrollLeft\", zoomed_offset.x());\n    map.emplace(\"scrollHeight\", zoomed_content.height());\n    map.emplace(\"scrollWidth\", zoomed_content.width());\n    map.emplace(\"isDragging\",\n                static_cast<ScrollView*>(view_)->GetScrollStatus() ==\n                    ScrollView::ScrollStatus::kDragging);\n    callback(LynxUIMethodResult::kSuccess, clay::Value(std::move(map)));\n  }\n}\n\nvoid ListContainerWrapper::getVisibleCells(\n    const LynxModuleValues&, const LynxUIMethodCallback& callback) {\n  auto cells = GetListContainerView()->GetVisibleCells();\n  callback(LynxUIMethodResult::kSuccess, clay::Value(std::move(cells)));\n}\n\nvoid ListContainerWrapper::WillUpdateScrollbar() {}\n\nvoid ListContainerWrapper::OnLayoutFinish(clay::BaseView* view) {\n  view_->OnLayoutFinish(view);\n}\n\nvoid ListContainerWrapper::UpdateScrollInfo(bool smooth, float estimated_offset,\n                                            bool scrolling) {\n  GetListContainerView()->UpdateScrollInfo(smooth, estimated_offset, scrolling);\n}\n\nfloat ListContainerWrapper::GetScrollbarScrollOffset() {\n  return OrientationHelper().GetLocation(view_->GetScrollOffset());\n}\n\nfloat ListContainerWrapper::GetTotalLength() {\n  return GetListContainerView()->max_content_;\n}\n\nvoid ListContainerWrapper::OnScrollableScrolled() {\n  UpdateScrollbarIfNeeded();\n  scrollbar_->NotifyScrollViewScrolled();\n}\n\nvoid ListContainerWrapper::UpdateScrollbarIfNeeded() {\n  if (scrollbar_enabled_) {\n    UpdateScrollbarLengths();\n    UpdateScrollbarPosition();\n  }\n}\n\n// Override ScrollbarView::Delegate\nvoid ListContainerWrapper::OnScrollbarScrolled(float old_position,\n                                               float new_position,\n                                               bool by_interaction,\n                                               bool smooth) {\n  // Note: smooth scroll for listview's scrollbar is not implemented\n  if (by_interaction) {\n    const float delta =\n        (new_position - old_position) *\n        (scrollbar_->GetTotalLength() - scrollbar_->GetVisibleLength());\n    GetListContainerView()->ScrollWithDelta(false, delta);\n  }\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_container/list_container_wrapper.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_CONTAINER_LIST_CONTAINER_WRAPPER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_CONTAINER_LIST_CONTAINER_WRAPPER_H_\n\n#include <string>\n#include <vector>\n\n#include \"clay/ui/component/list/list_container/list_container_view.h\"\n#include \"clay/ui/component/scrollbar/scrollbar_wrapper.h\"\nnamespace clay {\n\nclass ListContainerWrapper\n    : public WithTypeInfo<ListContainerWrapper, ScrollbarWrapper>,\n      public ListContainerView::Delegate,\n      public Scrollable::Listener {\n public:\n  ListContainerWrapper(int32_t id, PageView* page_view);\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  void DidUpdateAttributes() override;\n\n  void UpdateContentOffsetForListContainer(float content_size,\n                                           float target_content_offset_x,\n                                           float target_content_offset_y);\n  void InsertListItemPaintingNode(BaseView* view);\n  void RemoveListItemPaintingNode(BaseView* view);\n\n  void UpdateScrollInfo(bool smooth, float estimated_offset, bool scrolling);\n\n#define UI_METHOD_LIST_DECLARATION(V) \\\n  V(scrollToPosition)                 \\\n  V(autoScroll)                       \\\n  V(getVisibleItemsPositions)         \\\n  V(getScrollInfo)                    \\\n  V(getVisibleCells)\n  UI_METHOD_LIST_DECLARATION(UI_METHOD_DEF);\n#undef UI_METHOD_LIST_DECLARATION\n\n  void OnListViewDidLayout() override;\n\n  ListContainerView* GetListContainerView() const {\n    return static_cast<ListContainerView*>(view_);\n  }\n\n  void AddChild(BaseView* child, int index) override {}\n\n private:\n  void OnDestroy() override;\n\n  void UpdateScrollbarIfNeeded();\n\n  // Override ScrollbarWrapper\n  void WillUpdateScrollbar() override;\n  float GetScrollbarScrollOffset() override;\n  float GetTotalLength() override;\n\n  // Override Scrollable::Listener\n  void OnScrollableScrolled() override;\n\n  // Override ListContainerView::Delegate\n  void OnScrollToPosition(int position, float offset, int align,\n                          bool smooth) override;\n  void OnScrollStopped() override;\n  void OnLayoutFinish(BaseView* view) override;\n  void OnScrollByListContainer(float offset_x, float offset_y, float original_x,\n                               float original_y) override;\n\n  // Override ScrollbarView::Delegate\n  void OnScrollbarScrolled(float old_position, float new_position,\n                           bool by_interaction, bool smooth) override;\n\n  void OnNodeReady() override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_CONTAINER_LIST_CONTAINER_WRAPPER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_item_length_cache.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_item_length_cache.h\"\n\n#include <algorithm>\n#include <sstream>\n#include <string>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nListItemLengthCache::ListItemLengthCache() = default;\nListItemLengthCache::~ListItemLengthCache() = default;\n\nvoid ListItemLengthCache::Clear() { sections_.clear(); }\n\nvoid ListItemLengthCache::SetLength(int position, float length) {\n  FML_DCHECK(position >= 0);\n\n  auto itr = FindPosition(position);\n\n  // The iterator pointing to the target position after updating. It will be\n  // used in merge phase.\n  auto new_itr = itr;\n  if (itr == sections_.end()) {\n    if (sections_.empty()) {\n      if (position > 0) {\n        // Insert placement between[0, position).\n        sections_.emplace_back(0, position);\n      }\n    } else {\n      if (sections_.back().to_pos != position) {\n        if (sections_.back().length == kInvalidLength) {\n          // Last section is placement, just extend this section.\n          sections_.back().to_pos = position;\n        } else {\n          // Insert placement between [last_section.to_pos, position)\n          sections_.emplace_back(sections_.back().to_pos, position);\n        }\n      }\n    }\n    // Insert single element section.\n    new_itr =\n        sections_.emplace(sections_.end(), position, position + 1, length);\n  } else {\n    // `itr->from_pos` <= `position` && `position` < `itr->to_pos`\n    Section& cur = *itr;\n    if (cur.length == length) {\n      return;\n    }\n\n    const Section cur_copy = cur;\n    if (cur_copy.from_pos == position) {\n      if (cur_copy.to_pos == position + 1) {\n        // Single element section, just update length.\n        cur.length = length;\n        new_itr = itr;\n      } else {\n        // Split [position, to_pos) to [position, position+1) [position+1,\n        // to_pos)\n        new_itr = sections_.emplace(itr, position, position + 1, length);\n        cur.from_pos = position + 1;\n      }\n    } else if (cur_copy.to_pos == position + 1) {\n      // Split [from_pos, position+1) to [from_pos, position) [position,\n      // position+1)\n      sections_.emplace(itr, cur.from_pos, position, cur.length);\n      itr->from_pos = position;\n      itr->length = length;\n      new_itr = itr;\n    } else {\n      // Split [from_pos, to_pos) to [from_pos, position) [position, position+1)\n      // [position+1, to_pos)\n      cur.from_pos = position + 1;\n      new_itr = sections_.emplace(itr, position, position + 1, length);\n      itr = sections_.emplace(new_itr, cur_copy.from_pos, position,\n                              cur_copy.length);\n    }\n  }\n\n  // Try to merge. If the length of the target section is the same as the\n  // previous or the next section, it will be merged into it. Try to merge\n  // previous.\n  if (new_itr != sections_.begin()) {\n    auto prev = new_itr;\n    prev--;\n    if (prev->length == length) {\n      prev->to_pos = new_itr->to_pos;\n      sections_.erase(new_itr);\n      new_itr = prev;\n    }\n  }\n\n  // Try to merge next.\n  auto next = new_itr;\n  next++;\n  if (next != sections_.end()) {\n    if (next->length == length) {\n      next->from_pos = new_itr->from_pos;\n      sections_.erase(new_itr);\n    }\n  }\n}\n\nvoid ListItemLengthCache::InvalidateLength(int position) {\n  if (position < GetSize()) {\n    SetLength(position, kInvalidLength);\n  }\n}\n\nstd::optional<float> ListItemLengthCache::GetLength(int position) {\n  auto itr = FindPosition(position);\n  if (itr == sections_.end()) {\n    return std::nullopt;\n  }\n\n  FML_DCHECK(itr->from_pos <= position && position < itr->to_pos);\n  return itr->length;\n}\n\nvoid ListItemLengthCache::OnItemsAdded(int position_start, int item_count) {\n  for (int i = position_start; i < position_start + item_count; ++i) {\n    auto itr = FindPosition(i);\n    if (itr == sections_.end()) {\n      SetLength(i, kInvalidLength);\n    } else {\n      if (itr->from_pos == i) {\n        // Split [i, to_pos) to [i, i+1) [i+1, to_pos + 1).\n        sections_.emplace(itr, i, i + 1);\n      } else {\n        // Split [from_pos, to_pos) to [from_pos, i) [i, i+1) [i+1, to_pos + 1).\n        sections_.emplace(itr, itr->from_pos, i, itr->length);\n        itr->from_pos = i;\n        itr->to_pos = i + 1;\n        itr->length = kInvalidLength;\n        itr++;\n      }\n      int from = i + 1;\n      while (itr != sections_.end()) {\n        itr->from_pos = from;\n        itr->to_pos += 1;\n        from = itr->to_pos;\n        itr++;\n      }\n    }\n  }\n}\n\nvoid ListItemLengthCache::OnItemsRemoved(int position_start, int item_count) {\n  for (int i = position_start; i < position_start + item_count; ++i) {\n    auto itr = FindPosition(i);\n    if (itr == sections_.end()) {\n      continue;\n    }\n    // `itr->from_pos` <= `i` && `i` < `itr->to_pos`.\n    int from = 0;\n    if (itr->from_pos == i && itr->to_pos == i + 1) {\n      // Remove single element section and update following sections.\n      itr = sections_.erase(itr);\n      from = i;\n    } else {\n      // Update current section and following sections.\n      itr->to_pos -= 1;\n      from = itr->to_pos;\n      itr++;\n    }\n    while (itr != sections_.end()) {\n      itr->from_pos = from;\n      itr->to_pos -= 1;\n      from = itr->to_pos;\n      itr++;\n    }\n  }\n}\n\nvoid ListItemLengthCache::OnItemsUpdated(int position_start, int item_count) {\n  for (int i = position_start; i < position_start + item_count; ++i) {\n    SetLength(i, kInvalidLength);\n  }\n}\n\nvoid ListItemLengthCache::OnItemsMoved(int from, int to, int item_count) {\n  auto from_itr = FindPosition(from);\n  auto to_itr = FindPosition(to);\n  if (from_itr == sections_.end() || to_itr == sections_.end()) {\n    return;\n  }\n  if (from == to) {\n    // In-place move operation means content of cell changes, we just keep the\n    // length cache same instead of invalidate it. Generally speaking, content\n    // change won't contribute to dramatic length change. So keeping length\n    // unchanged is more beneficial to ListLayoutManager.GetTotalLength.\n    return;\n  }\n  int step = from < to ? 1 : -1;\n  while (from != to) {\n    SetLength(from, kInvalidLength);\n    from += step;\n  }\n  SetLength(to, kInvalidLength);\n}\n\nint ListItemLengthCache::GetSize() const {\n  if (sections_.size() == 0) {\n    return 0;\n  }\n  return sections_.back().to_pos;\n}\n\n// static\nstd::string ListItemLengthCache::ToString(const Sections& sections) {\n#if (DEBUG_LIST)\n  std::stringstream ss;\n  for (const Section& section : sections) {\n    ss << \"[\" << section.from_pos << \",\" << section.to_pos << \") \"\n       << section.length << \" | \";\n  }\n  return ss.str();\n#else\n  return \"\";\n#endif\n}\n\n/**\n * @brief\n * return the first iterator of section whose `from_pos` <= `position` and\n * `to_pos` > `position` or iterator::end() if not found.\n * @param position list item position\n * @return ListItemLengthCache::Sections::iterator\n */\nListItemLengthCache::Sections::iterator ListItemLengthCache::FindPosition(\n    int position) {\n  if (position < GetSize()) {\n    return std::lower_bound(\n        sections_.begin(), sections_.end(), position,\n        [](const Section& section, int pos) { return section.to_pos <= pos; });\n  }\n  return sections_.end();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_item_length_cache.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_ITEM_LENGTH_CACHE_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_ITEM_LENGTH_CACHE_H_\n\n#include <list>\n#include <optional>\n#include <string>\n\nnamespace clay {\n\n// This class is used to cache the length (width/height depending on the\n// orientation) of items in a list view.\n// The length of an item is known only after it is laid out. `kInvalidLength` is\n// used for item that is not laid out yet.\n// Instead of representing the length of each item in a vector, a list of\n// sections is used. Starting from position 0, a section represents a continuous\n// range of items with the same length. For example, a section from 0 to 10 with\n// length 2 means items from 0 to 9 have length 2.\n// The whole cache is continuous: a section's `from_pos` must be equal to\n// `to_pos` of the previous section (if any).\nclass ListItemLengthCache {\n public:\n  static constexpr float kInvalidLength = -1.f;\n  struct Section {\n    int from_pos;  // closed\n    int to_pos;    // open\n    float length;\n\n    Section(int f, int t, float l = kInvalidLength)\n        : from_pos(f), to_pos(t), length(l) {}\n  };\n  using Sections = std::list<Section>;\n\n  ListItemLengthCache();\n  ~ListItemLengthCache();\n\n  void Clear();\n  void SetLength(int position, float length);\n  void InvalidateLength(int position);\n  std::optional<float> GetLength(int position);\n\n  const Sections& GetSections() const { return sections_; }\n\n  // Returns the to_pos of the last section.\n  int GetSize() const;\n\n  // Insert item at `position_start`, and move items following `position_start`\n  // backward.\n  void OnItemsAdded(int position_start, int item_count);\n  // Remove item at `position_start`, and move items following `position_start`\n  // forward.\n  void OnItemsRemoved(int position_start, int item_count);\n  // Update length of item at `position_start` to `kInvalidLength`.\n  void OnItemsUpdated(int position_start, int item_count);\n  // Update length of item at `to` to length of item at `from`.\n  void OnItemsMoved(int from, int to, int item_count);\n\n  static std::string ToString(const Sections& sections);\n\n private:\n  Sections::iterator FindPosition(int position);\n\n  Sections sections_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_ITEM_LENGTH_CACHE_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_item_length_cache_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/list/list_item_length_cache.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace {\n\nusing Section = ListItemLengthCache::Section;\nusing Sections = ListItemLengthCache::Sections;\n\nbool operator==(const Sections& l, const Sections& r) {\n  if (l.size() != r.size()) {\n    return false;\n  }\n\n  auto l_itr = l.begin();\n  auto r_itr = r.begin();\n  for (; l_itr != l.end(); ++l_itr, ++r_itr) {\n    if (l_itr->from_pos != r_itr->from_pos || l_itr->to_pos != r_itr->to_pos ||\n        l_itr->length != r_itr->length) {\n      return false;\n    }\n  }\n  return true;\n}\n\n}  // namespace\n\nTEST(ListItemLengthCache, Test) {\n  ListItemLengthCache cache;\n  cache.SetLength(0, 100);\n  auto expected = Sections{{0, 1, 100}};\n  EXPECT_TRUE(cache.GetSections() == expected);\n\n  for (int i = 1; i < 5; ++i) {\n    cache.SetLength(i, 100);\n  }\n  // Should merge into 1 section\n  expected = Sections{{0, 5, 100}};\n  EXPECT_TRUE(cache.GetSections() == expected);\n\n  cache.Clear();\n  // The first inserted item does not start from zero. Should insert a section\n  // with invalid length starting from 0.\n  cache.SetLength(50, 100);\n  expected = Sections{{0, 50, -1}, {50, 51, 100}};\n  EXPECT_TRUE(cache.GetSections() == expected);\n\n  cache.Clear();\n  for (int i = 0; i < 50; ++i) {\n    cache.SetLength(i, 10);\n  }\n  // Break a section into 3 sections.\n  cache.SetLength(25, 20);\n  expected = Sections{{0, 25, 10}, {25, 26, 20}, {26, 50, 10}};\n  EXPECT_TRUE(cache.GetSections() == expected);\n\n  // Merge them back into one.\n  cache.SetLength(25, 10);\n  expected = Sections{{0, 50, 10}};\n  EXPECT_TRUE(cache.GetSections() == expected);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_item_view.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_item_view.h\"\n\n#include <memory>\n\n#include \"clay/ui/component/component.h\"\n\nnamespace clay {\n\nListItemView::ListItemView(int32_t id, PageView* page_view)\n    : WithTypeInfo(id, page_view) {\n  tag_ = \"ListItemView\";\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_item_view.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_ITEM_VIEW_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_ITEM_VIEW_H_\n\n#include \"clay/ui/component/component.h\"\n\nnamespace clay {\n\n// In RL2, the child in a listContainer may not be a `list-item`, and in RL3 the\n// child must be a `list-item`, so the current logic of list-item should be\n// placed in component.cc.\n// TODO(dongjiajian): separate ListItem with Component.\nclass ListItemView : public WithTypeInfo<ListItemView, Component> {\n public:\n  ListItemView(int32_t id, PageView* page_view);\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_ITEM_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_item_view_holder.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n\n#include <sstream>\n#include <string>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_adapter.h\"\n\nnamespace clay {\n\nListItemViewHolder::ListItemViewHolder() = default;\nListItemViewHolder::~ListItemViewHolder() = default;\n\nvoid ListItemViewHolder::ResetOnRecycle() {\n  if (IsRemoved() || NeedsUpdate() || IsInvalid()) {\n    Reset();\n  }\n\n  // End all transition animations to avoid remaining dirty animations after\n  // reuse. We won't end keyframe animations here, as this may result in the\n  // animations not being resumed.\n  if (auto view = GetView()) {\n    view->EndAllTransitionsRecursively();\n  }\n}\n\nvoid ListItemViewHolder::Reset() {\n  position_ = kNoPosition;\n  flags_ = kFlagNone;\n  ClearPayloads();\n}\n\nvoid ListItemViewHolder::SetView(BaseView* view) {\n  if (view) {\n    FML_DCHECK(!view_);\n    view_ = view;\n  } else {\n    FML_DCHECK(view_);\n    view_ = nullptr;\n  }\n}\n\nfloat ListItemViewHolder::GetTop() const {\n  if (layout_location_) {\n    return layout_location_->y();\n  }\n\n  if (GetView() == nullptr) {\n    return 0.f;\n  }\n  return GetView()->Top();\n}\n\nfloat ListItemViewHolder::GetLeft() const {\n  if (layout_location_) {\n    return layout_location_->x();\n  }\n\n  if (GetView() == nullptr) {\n    return 0.f;\n  }\n  return GetView()->Left();\n}\n\nfloat ListItemViewHolder::GetWidth() const {\n  if (GetView() == nullptr) {\n    return 0.f;\n  }\n  return GetView()->Width();\n}\n\nfloat ListItemViewHolder::GetHeight() const {\n  if (GetView() == nullptr) {\n    return 0.f;\n  }\n  return GetView()->Height();\n}\n\nFloatPoint ListItemViewHolder::GetLayoutOrigin() const {\n  if (layout_location_) {\n    return layout_location_.value();\n  }\n  auto* view = GetView();\n  if (view == nullptr) {\n    return {0.f, 0.f};\n  } else {\n    return {view->Left(), view->Top()};\n  }\n}\n\nvoid ListItemViewHolder::SetPosition(int position) {\n  if (position > kNoPosition) {\n    last_valid_position_ = position;\n  }\n  position_ = position;\n}\n\nvoid ListItemViewHolder::Layout(const FloatPoint& location) {\n  layout_location_ = location;\n  ClearAdjustedLocation();\n}\n\nvoid ListItemViewHolder::LayoutWithOffset(const FloatPoint& offset) {\n  if (!GetView() || !GetViewAttached()) {\n    return;\n  }\n  if (!layout_location_) {\n    layout_location_ = {GetView()->Left(), GetView()->Top()};\n  }\n  layout_location_->Move(offset.x(), offset.y());\n  ClearAdjustedLocation();\n}\n\nvoid ListItemViewHolder::FlushLayout() {\n  {\n    auto view = GetView();\n    // Setting width/height should not dirty the layout bit.\n    BaseView::LayoutIgnoreHelper helper(view);\n\n    if (view && GetViewAttached() && layout_location_) {\n      // NOTE: The layout result is relative to the padding box of the list. We\n      // need to set the location relative to the border box of the list.\n      auto parent = view->Parent();\n      FML_DCHECK(parent->Is<BaseListView>());\n      view->SetX(layout_location_->x() + parent->BorderLeft());\n      view->SetY(adjusted_location_.value_or(layout_location_->y()) +\n                 parent->BorderTop());\n      if (sticky_type_ != ListItemStickyType::kNone) {\n        parent->BringChildToFront(view);\n      }\n    }\n  }\n}\n\nvoid ListItemViewHolder::OffsetPosition(int offset, bool apply_to_pre_layout) {\n  SetPosition(position_ + offset);\n}\n\nvoid ListItemViewHolder::FlagRemovedAndOffsetPosition(\n    int new_position, int offset, bool apply_to_pre_layout) {\n  flags_ = static_cast<Flag>(flags_ | kFlagRemoved);\n  OffsetPosition(offset, apply_to_pre_layout);\n  SetPosition(new_position);\n}\n\nvoid ListItemViewHolder::SetFlags(Flag flags, Flag mask) {\n  flags_ = static_cast<Flag>((flags_ & ~mask) | (flags & mask));\n}\n\nvoid ListItemViewHolder::AddFlags(Flag flags) {\n  flags_ = static_cast<Flag>(flags_ | flags);\n}\n\nvoid ListItemViewHolder::RemoveFlags(Flag flags) {\n  SetFlags(Flag::kFlagNone, flags);\n}\n\nvoid ListItemViewHolder::SetViewVisible(bool visible) {\n  if (view_) {\n    if (view_->Visible() != visible) {\n      view_->SetVisible(visible);\n      if (observer_) {\n        observer_->OnVisibilityChanged(this, visible);\n      }\n    }\n  }\n}\n\nvoid ListItemViewHolder::AddChangePayload(\n    std::unique_ptr<ListAdapter::Payload> payload) {\n  if (payload == nullptr) {\n    AddFlags(Flag::kFlagAdapterFullUpdate);\n  } else if ((flags_ & Flag::kFlagAdapterFullUpdate) == 0) {\n    payloads_.emplace_back(std::move(payload));\n  }\n}\n\nvoid ListItemViewHolder::ClearPayloads() {\n  payloads_.clear();\n  flags_ = static_cast<Flag>(flags_ & ~kFlagAdapterFullUpdate);\n}\n\nconst std::vector<std::unique_ptr<ListAdapter::Payload>>&\nListItemViewHolder::GetPayloads() const {\n  if ((flags_ & Flag::kFlagAdapterFullUpdate) == 0) {\n    return payloads_;\n  } else {\n    static const std::vector<std::unique_ptr<ListAdapter::Payload>> kDummy;\n    return kDummy;\n  }\n}\n\nstd::string ListItemViewHolder::ToString() const {\n#if (DEBUG_LIST)\n  std::stringstream ss;\n  ss << \"pos:\" << GetPosition() << \" type:\" << GetItemViewType()\n     << \" view_id:\" << (view_ != nullptr ? view_->id() : -1)\n     << \" isAttached:\" << (GetViewAttached() ? \"true\" : \"false\")\n     << \" isVisible:\" << (GetViewVisible() ? \"true\" : \"false\")\n     << \" isBound:\" << (IsBound() ? \"true\" : \"false\");\n  if (layout_location_) {\n    ss << \" loc:\" << layout_location_->x() << \",\" << layout_location_->y();\n  }\n  return ss.str();\n#else\n  return \"\";\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_item_view_holder.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_ITEM_VIEW_HOLDER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_ITEM_VIEW_HOLDER_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/list_adapter.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n\nnamespace clay {\n\nenum class ListItemStickyType {\n  kNone,\n  kTop,\n  kBottom,\n};\n\nclass ListItemViewHolder {\n public:\n  // Flag that indicate the view holder's state. Some of the valid is not used\n  // yet but we keep it to sync with Android RecyclerView.\n  // Only kFlagRemoved, kFlagUpdate, kFlagInvalid, kFlagScrapped are used yet.\n  enum Flag {\n    kFlagNone = 0,\n    // This view holder has been bound to a position, `position_` is valid.\n    kFlagBound = 1 << 0,\n    // Need to be rebound data.\n    kFlagUpdate = 1 << 1,\n    kFlagInvalid = 1 << 2,\n    // This view holder points at data that represents an item previously\n    // removed from the data set. Its view may still be used for things like\n    // outgoing animation.\n    kFlagRemoved = 1 << 3,\n    kFlagNotRecyclable = 1 << 4,\n    kFlagReturnFromScrap = 1 << 5,\n    kFlagIgnore = 1 << 7,\n    // When the view is detached from the parent, we set this flag so that we\n    // can take correct action when we need to remove it or add it back.\n    kFlagTmpDetached = 1 << 8,\n    kFlagAdapterPositionUnknown = 1 << 9,\n    kFlagAdapterFullUpdate = 1 << 10,\n    kFlagMoved = 1 << 11,\n    kFlagAdapterInPreLayout = 1 << 12,\n    kFlagBouncedFromHiddenList = 1 << 13,\n    // This means the item was adding to `attached_scrap_items_` and not be\n    // retrieved yet. This is only used during\n    // `ListLayoutManager::OnLayoutChildren`.\n    kFlagScrapped = 1 << 14,\n    // This means the item was prefetched and maybe used in the future.\n    kFlagPrefetch = 1 << 15,\n  };\n  static constexpr int kNoPosition = -1;\n\n  class VisibilityObserver {\n   public:\n    virtual void OnVisibilityChanged(ListItemViewHolder* item,\n                                     bool visible) = 0;\n  };\n\n  ListItemViewHolder();\n  virtual ~ListItemViewHolder();\n\n  // Will clean the state if the item is removed or updated. Otherwise, keep the\n  // state.\n  void ResetOnRecycle();\n\n  // Clean the state in all condition.\n  void Reset();\n\n  void SetView(BaseView* view);\n  BaseView* GetView() const { return view_; }\n\n  void SetVisibilityObserver(VisibilityObserver* observer) {\n    observer_ = observer;\n  }\n\n  // Each item should lay itself out with the constraint from the layout\n  // manager. For example,in vertical linear layout, the width is determined by\n  // the layout manager (constraint), and each item should determine its height.\n  virtual MeasureResult Measure(const MeasureConstraint& constraint) = 0;\n\n  virtual ListAdapter::TypeId GetItemViewType() const {\n    return ListAdapter::kDefaultId;\n  }\n\n  // Place the view to the desired location.\n  // NOTE(Xietong): instead of setting the latest location to view, the location\n  // is cached to layout_location_. During layout, the\n  // layout manager will get cached location instead of the actual view\n  // location. The cached location will be flush to the view when the whole list\n  // finishes scrolling or layout.\n  // This is to workaround the behaviour of Lynx:\n  // Location of children in the list should be determined by the list. When\n  // updating children, however, Lynx will move child view's position and this\n  // will influence the layout process. So we cache the desired location to view\n  // holder and use this value during layout. Flush the desired location when\n  // Lynx won't change the view location during the current layout pass.\n  void Layout(const FloatPoint& location);\n  // Offset the cached location.\n  void LayoutWithOffset(const FloatPoint& offset);\n  // Flush the cached location to the view.\n  void FlushLayout();\n\n  // Get the dimension of the view. If there's cached location, the cached\n  // location is returned.\n  float GetTop() const;\n  float GetLeft() const;\n  virtual float GetWidth() const;\n  virtual float GetHeight() const;\n  float GetRight() const { return GetLeft() + GetWidth(); }\n  float GetBottom() const { return GetTop() + GetHeight(); }\n  FloatPoint GetLayoutOrigin() const;\n\n  void SetPosition(int position);\n  int GetPosition() const { return position_; }\n  int GetLastValidPosition() const { return last_valid_position_; }\n  int GetLayoutPosition() const { return GetPosition(); }\n  void OffsetPosition(int offset, bool apply_to_pre_layout = false);\n  void FlagRemovedAndOffsetPosition(int new_position, int offset,\n                                    bool apply_to_pre_layout = false);\n  void SetFlags(Flag flags, Flag mask);\n  void AddFlags(Flag flags);\n  void RemoveFlags(Flag flags);\n\n  bool IsRemoved() const { return flags_ & kFlagRemoved; }\n  bool IsInvalid() const { return flags_ & kFlagInvalid; }\n  bool IsBound() const { return flags_ & kFlagBound; }\n  bool IsScrapped() const { return flags_ & kFlagScrapped; }\n  bool IsPrefetch() const { return flags_ & kFlagPrefetch; }\n  bool NeedsUpdate() const { return flags_ & kFlagUpdate; }\n  bool HasAnyOfFlags(Flag flags) const { return (flags_ & flags) != 0; }\n\n  // If item's view is attached, it is attached to the page view tree.\n  // If it is not attached, it means the item has been recycled.\n  bool GetViewAttached() const {\n    if (view_) {\n      return view_->Parent();\n    }\n    return false;\n  }\n\n  // If item's view is visible, it must be attached. However, an attached view\n  // is not necessarily visible.\n  // For example, when we search for focusable node, we may attach items out of\n  // bounds. In this case, the item is attached but not visible.\n  // Currently, there are 3 chances for an item to change its visibility:\n  //  1. When an item is recycled, recycler will notify adapter and adapter\n  //     marks item as invisible.\n  //  2. When an item is bound to a new position, adapter will also mark it as\n  //     invisible so that Lynx can receive notification that the item in the\n  //     original position disappears.\n  //  3. In the end of layout or scrolling, layout manager will iterate all the\n  //     attached items and mark the in bound items visible.\n  void SetViewVisible(bool visible);\n  bool GetViewVisible() const {\n    if (view_) {\n      return view_->Visible();\n    }\n    return false;\n  }\n\n  // adding nullptr once means fully update.\n  void AddChangePayload(std::unique_ptr<ListAdapter::Payload> payload);\n  void ClearPayloads();\n  const std::vector<std::unique_ptr<ListAdapter::Payload>>& GetPayloads() const;\n\n  // When full span is enabled, this item will ignore list view's padding in the\n  // secondary direction.\n  void SetFullSpan(bool enabled) { full_span_ = enabled; }\n  bool FullSpan() const { return full_span_; }\n\n  void SetStickyType(ListItemStickyType type) { sticky_type_ = type; }\n  ListItemStickyType StickyType() const { return sticky_type_; }\n\n  void SetAdjustedLocation(float value) { adjusted_location_ = value; }\n  void ClearAdjustedLocation() { adjusted_location_.reset(); }\n\n  std::string ToString() const;\n\n private:\n  BaseView* view_ = nullptr;\n\n  int position_ = kNoPosition;\n  // Used for node disappearing when the item is removed.\n  int last_valid_position_ = kNoPosition;\n\n  Flag flags_ = kFlagNone;\n\n  // The location in list. It may be overwritten by the `adjusted_location_` if\n  // it is a sticky item.\n  std::optional<FloatPoint> layout_location_;\n\n  // The header/footer items will be displayed at the top or bottom of list,\n  // instead of the `layout_location_`.\n  std::optional<float> adjusted_location_;\n\n  ListAdapter::Payloads payloads_;\n\n  bool full_span_ = false;\n  ListItemStickyType sticky_type_ = ListItemStickyType::kNone;\n\n  VisibilityObserver* observer_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_ITEM_VIEW_HOLDER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_layout_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_layout_manager.h\"\n\n#include <algorithm>\n#include <optional>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_children_helper.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_layout_manager_grid.h\"\n#include \"clay/ui/component/list/list_layout_manager_linear.h\"\n#include \"clay/ui/component/list/list_layout_manager_staggered_grid.h\"\n#include \"clay/ui/component/list/list_orientation_helper.h\"\n#include \"clay/ui/component/list/list_recycler.h\"\n#include \"clay/ui/component/list/macros.h\"\n\nnamespace clay {\n\nListLayoutManager::ListLayoutManager(ScrollDirection orientation) {\n  SetOrientation(orientation);\n  length_cache_ = std::make_unique<ListItemLengthCache>();\n}\n\nListLayoutManager::~ListLayoutManager() = default;\n\n// static\nstd::unique_ptr<ListLayoutManager> ListLayoutManager::Create(\n    const std::string& type, int span_count) {\n  // NOTE(hanhaoshen): Focus and `scroll_to_position` are not fully supported on\n  // grid / staggered grid.\n  if (type == attr_value::kListTypeSingle) {\n    return std::make_unique<ListLayoutManagerLinear>();\n  } else if (type == attr_value::kListTypeFlow) {\n    return std::make_unique<ListLayoutManagerGrid>(span_count);\n  } else if (type == attr_value::kListTypeWaterFall) {\n    return std::make_unique<ListLayoutManagerStaggeredGrid>(span_count);\n  }\n  FML_DLOG(ERROR) << \"Invalid list layout type \" << type\n                  << \". Set layout type to linear.\";\n  return std::make_unique<ListLayoutManagerLinear>();\n}\n\nvoid ListLayoutManager::SetListView(BaseListView* list_view) {\n  list_view_ = list_view;\n  FML_DCHECK(list_view_);\n}\n\nvoid ListLayoutManager::SetChildrenHelper(ListChildrenHelper* children_helper) {\n  children_helper_ = children_helper;\n  FML_DCHECK(children_helper_);\n}\n\nfloat ListLayoutManager::GetWidth() {\n  return list_view_->Width() - list_view_->BorderLeft() -\n         list_view_->BorderRight();\n}\n\nfloat ListLayoutManager::GetHeight() {\n  return list_view_->Height() - list_view_->BorderTop() -\n         list_view_->BorderBottom();\n}\n\nfloat ListLayoutManager::GetPaddingLeft() { return list_view_->PaddingLeft(); }\n\nfloat ListLayoutManager::GetPaddingRight() {\n  return list_view_->PaddingRight();\n}\n\nfloat ListLayoutManager::GetPaddingTop() { return list_view_->PaddingTop(); }\n\nfloat ListLayoutManager::GetPaddingBottom() {\n  return list_view_->PaddingBottom();\n}\n\n// This is similar to `RemoveAndScrapChildren` but for floating items\nvoid ListLayoutManager::ScrapFloatingItems(ListRecycler* recycler) {\n  if (auto item = children_helper_->floating_top_item()) {\n    item->ClearAdjustedLocation();\n    recycler->ScrapItem(item);\n    children_helper_->set_floating_top_item(nullptr);\n  }\n  if (auto item = children_helper_->floating_bottom_item()) {\n    item->ClearAdjustedLocation();\n    recycler->ScrapItem(item);\n    children_helper_->set_floating_bottom_item(nullptr);\n  }\n}\n\nvoid ListLayoutManager::LayoutSticky(ListRecycler* recycler,\n                                     ListAdapter* adapter) {\n  if (children_helper_->GetChildCount() == 0) {\n    return;\n  }\n\n  {\n    int header_position = ListItemViewHolder::kNoPosition;\n    int header_index = -1;\n\n    // find any possible header in visible area.\n    for (int i = GetChildCount() - 1; i >= 0; i--) {\n      if (adapter->IsItemStickyTop(GetChildAt(i)->GetPosition())) {\n        ListItemViewHolder* item = GetChildAt(i);\n        if (item->GetTop() < sticky_offset_) {\n          header_position = item->GetPosition();\n          header_index = i;\n        }\n      }\n    }\n\n    // find any possible header in invisible area.\n    if (header_position == ListItemViewHolder::kNoPosition) {\n      header_position =\n          adapter->FindPreviousFullSpan(GetFirstChild()->GetPosition());\n    }\n\n    if (!adapter->IsItemStickyTop(header_position)) {\n      header_position = ListItemViewHolder::kNoPosition;\n    }\n\n    if (header_position != ListItemViewHolder::kNoPosition) {\n      ListItemViewHolder* header_item = nullptr;\n      if (header_index != -1) {\n        header_item = GetChildAt(header_index);\n      } else {\n        // If the header item is not visible, then it's a floating item. We need\n        // to retrieve and add it to list view.\n        header_item = recycler->GetItemForPosition(header_position);\n        header_item->Measure(FullSpanConstraint());\n        header_item->Layout({});\n        list_view_->OnAddItem(header_item, list_view_->child_count());\n        children_helper_->set_floating_top_item(header_item);\n      }\n\n      // Adjust the location to make it sticky at the top of list.\n      float sticky_top_offset = sticky_offset_;\n      if (auto next_fullspan = children_helper_->FindChildByPosition(\n              adapter->FindNextFullSpan(header_position + 1))) {\n        // Handle the case of it being pushed up by the next full-span item.\n        sticky_top_offset =\n            std::min(sticky_top_offset, next_fullspan->GetTop() -\n                                            header_item->GetView()->Height());\n      }\n      header_item->SetAdjustedLocation(sticky_top_offset);\n      header_item->FlushLayout();\n    }\n  }\n  {\n    // Find the current footer item\n    int footer_position = ListItemViewHolder::kNoPosition;\n    int footer_index = -1;\n\n    // find any possible footer in visible area.\n    for (int i = 0; i < GetChildCount(); i++) {\n      if (adapter->IsItemStickyBottom(GetChildAt(i)->GetPosition())) {\n        ListItemViewHolder* item = GetChildAt(i);\n        if (item->GetBottom() + sticky_offset_ > GetHeight()) {\n          footer_position = item->GetPosition();\n          footer_index = i;\n        }\n      }\n    }\n\n    // find any possible header in invisible area.\n    if (footer_position == ListItemViewHolder::kNoPosition) {\n      footer_position =\n          adapter->FindNextFullSpan(GetLastChild()->GetPosition());\n    }\n\n    if (!adapter->IsItemStickyBottom(footer_position)) {\n      footer_position = ListItemViewHolder::kNoPosition;\n    }\n\n    if (footer_position != ListItemViewHolder::kNoPosition) {\n      ListItemViewHolder* footer_item = nullptr;\n      if (footer_index != -1) {\n        footer_item = GetChildAt(footer_index);\n      } else {\n        // If the footer item is not visible, then it's a floating item. We\n        // need to retrieve and add it to list view.\n        footer_item = recycler->GetItemForPosition(footer_position);\n        footer_item->Measure(FullSpanConstraint());\n        footer_item->Layout({});\n        list_view_->OnAddItem(footer_item, list_view_->child_count());\n        children_helper_->set_floating_bottom_item(footer_item);\n      }\n\n      // Adjust the location to make it sticky at the bottom of list.\n      float sticky_bottom_offset = sticky_offset_;\n      if (auto prev_fullspan = children_helper_->FindChildByPosition(\n              adapter->FindPreviousFullSpan(footer_position - 1))) {\n        // Handle the case of it being pushed down by the previous full-span\n        // item.\n        sticky_bottom_offset = std::min(\n            sticky_bottom_offset, GetHeight() - prev_fullspan->GetBottom() -\n                                      footer_item->GetView()->Height());\n      }\n      footer_item->SetAdjustedLocation(GetHeight() -\n                                       footer_item->GetView()->Height() -\n                                       sticky_bottom_offset);\n      footer_item->FlushLayout();\n    }\n  }\n}\n\nvoid ListLayoutManager::SetStickyOffset(double offset) {\n  if (sticky_offset_ != offset) {\n    sticky_offset_ = offset;\n    RequestLayout();\n  }\n}\n\nvoid ListLayoutManager::OffsetChildren(float x, float y) {\n  LIST_LOG << \"OffsetChildren: \" << x << \", \" << y;\n  list_view_->OffsetChildren(x, y);\n}\n\nbool ListLayoutManager::SetOrientation(ScrollDirection orientation) {\n  if (orientation_ == orientation && orientation_helper_ != nullptr) {\n    return false;\n  }\n\n  orientation_ = orientation;\n  orientation_helper_ =\n      ListOrientationHelper::CreateOrientationHelper(this, orientation);\n  return true;\n}\n\nvoid ListLayoutManager::SetCrossAxisGap(float cross_axis_gap) {\n  if (cross_axis_gap != cross_axis_gap_) {\n    cross_axis_gap_ = std::max(cross_axis_gap, 0.f);\n    RequestLayout();\n  }\n}\n\nvoid ListLayoutManager::SetMainAxisGap(float main_axis_gap) {\n  if (main_axis_gap != main_axis_gap_) {\n    main_axis_gap_ = std::max(main_axis_gap, 0.f);\n    RequestLayout();\n  }\n}\n\nvoid ListLayoutManager::RequestLayout() {\n  if (list_view_) {\n    list_view_->MarkNeedsLayout();\n  }\n}\n\nvoid ListLayoutManager::AddItem(ListItemViewHolder* item, int index) {\n  BaseView::LayoutIgnoreHelper helper(list_view_);\n  children_helper_->AddChild(item, index);\n  list_view_->OnAddItem(item, index);\n}\n\nMeasureResult ListLayoutManager::MeasureItem(\n    ListItemViewHolder* item, const MeasureConstraint& constraint) {\n  MeasureResult res = item->Measure(constraint);\n  if (orientation_ == ScrollDirection::kVertical) {\n    length_cache_->SetLength(item->GetPosition(), res.height);\n  } else {\n    length_cache_->SetLength(item->GetPosition(), res.width);\n  }\n  return res;\n}\n\nvoid ListLayoutManager::LayoutItem(ListItemViewHolder* item,\n                                   const FloatPoint& top_left) {\n  if (item->FullSpan()) {\n    FloatPoint new_location =\n        orientation_helper_->CalculateFullSpanLocation(top_left, item);\n    item->Layout(new_location);\n  } else {\n    item->Layout(top_left);\n  }\n}\n\nint ListLayoutManager::GetChildCount() const {\n  return children_helper_->GetChildCount();\n}\n\nListItemViewHolder* ListLayoutManager::GetChildAt(int index) {\n  return children_helper_->GetChildAt(index);\n}\n\nListItemViewHolder* ListLayoutManager::FindChildByPosition(int position) {\n  return children_helper_->FindChildByPosition(position);\n}\n\nvoid ListLayoutManager::UpdateItemsVisibility() {\n  const float start = 0.f;\n  const float end = orientation_helper_->GetEnd();\n  children_helper_->ForEachWithSticky(\n      [this, start, end](ListItemViewHolder* item) {\n        FML_DCHECK(item->GetViewAttached());\n        const float item_start = orientation_helper_->GetDecoratedStart(item);\n        const float item_end = orientation_helper_->GetDecoratedEnd(item);\n        item->SetViewVisible(item_end > start && item_start < end);\n        return false;\n      });\n}\n\nvoid ListLayoutManager::RemoveAndRecycleAllViews(ListRecycler& recycler) {\n  const int child_count = GetChildCount();\n  for (int i = 0; i < child_count; ++i) {\n    RemoveAndRecycleViewAt(recycler, 0);\n  }\n}\n\nvoid ListLayoutManager::RemoveAndRecycleViewAt(ListRecycler& recycler,\n                                               int index) {\n  ListItemViewHolder* child = children_helper_->RemoveChild(index);\n  list_view_->OnRemoveItem(child);\n  recycler.RecycleItem(child);\n}\n\nvoid ListLayoutManager::RemoveAndScrapChildren(ListRecycler& recycler) {\n  const int child_count = GetChildCount();\n  for (int i = 0; i < child_count; ++i) {\n    RemoveAndScrapChild(recycler, 0);\n  }\n}\n\nvoid ListLayoutManager::RemoveAndScrapChild(ListRecycler& recycler, int index) {\n  // remove view_holder from `children_helper_`.\n  ListItemViewHolder* child = children_helper_->RemoveChild(index);\n  if (child->IsInvalid() && !child->IsRemoved()) {\n    LIST_LOG << \"Remove child: \" << child->ToString();\n    // remove view_holder'view from layout tree and render tree.\n    list_view_->OnRemoveItem(child);\n    // move view_holder flagged as removed to 2nd layer cache.\n    recycler.RecycleItem(child);\n  } else {\n    LIST_LOG << \"Scrap child: \" << child->ToString();\n    // NOTE: We don't remove the item here, unlike Android's RecyclerView.\n    // Because in clay it is costly to remove and re-add the item's view\n    // (which would mark all items to be repainted). We will remove items that\n    // are no longer displayed at the end of the layout.\n\n    // move view_holder to 1st layer cache.\n    recycler.ScrapItem(child);\n  }\n}\n\nvoid ListLayoutManager::OnLayoutCompleted(ListRecycler& recycler,\n                                          const ListViewState& state) {\n  recycler.RecycleScrappedItems(list_view_);\n}\n\nbool ListLayoutManager::IsItemFullSpan(int position) const {\n  return list_view_->IsItemFullSpan(position);\n}\n\nsize_t ListLayoutManager::GetVisibleItemsInfo(\n    std::vector<float>& top_array, std::vector<float>& bottom_array,\n    std::vector<float>& left_array, std::vector<float>& right_array,\n    std::vector<int>& position, std::vector<std::string>& id_array) {\n  position = GetVisibleItemPositions();\n  if (children_helper_->floating_top_item()) {\n    position.push_back(children_helper_->floating_top_item()->GetPosition());\n  }\n  if (children_helper_->floating_bottom_item()) {\n    position.push_back(children_helper_->floating_bottom_item()->GetPosition());\n  }\n\n  // Sort and erase duplicated item\n  std::sort(position.begin(), position.end());\n  auto last = std::unique(position.begin(), position.end());\n  position.erase(last, position.end());\n\n  children_helper_->ForEachInPositions(\n      position,\n      [page_view = list_view_->page_view(), &top_array, &bottom_array,\n       &left_array, &right_array, &id_array](ListItemViewHolder* item) {\n        if (item == nullptr) {\n          return false;\n        }\n        top_array.push_back(\n            page_view->ConvertTo<kPixelTypeLogical>(item->GetTop()));\n        bottom_array.push_back(\n            page_view->ConvertTo<kPixelTypeLogical>(item->GetBottom()));\n        left_array.push_back(\n            page_view->ConvertTo<kPixelTypeLogical>(item->GetLeft()));\n        right_array.push_back(\n            page_view->ConvertTo<kPixelTypeLogical>(item->GetRight()));\n        id_array.push_back(item->GetView()->GetIdSelector());\n\n        return false;\n      });\n\n  LIST_LOG << \"Get \" << top_array.size() << \" VisibleItemsInfo \";\n  return top_array.size();\n}\n\nMeasureConstraint ListLayoutManager::FullSpanConstraint() {\n  MeasureConstraint constraint;\n  if (orientation_ == ScrollDirection::kVertical) {\n    constraint.width_mode = MeasureMode::kDefinite;\n    constraint.height_mode = MeasureMode::kIndefinite;\n    constraint.width = GetWidth();\n  } else {\n    constraint.height_mode = MeasureMode::kDefinite;\n    constraint.width_mode = MeasureMode::kIndefinite;\n    constraint.height = GetHeight();\n  }\n  return constraint;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_layout_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_H_\n\n#include <list>\n#include <memory>\n#include <optional>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/list/list_item_length_cache.h\"\n#include \"clay/ui/component/view_callback/list_event_callback_manager.h\"\n\nnamespace clay {\n\nclass BaseListView;\nclass LayoutPrefetchRegistry;\nclass ListAdapter;\nclass ListChildrenHelper;\nclass ListItemLengthCache;\nclass ListItemViewHolder;\nclass ListOrientationHelper;\nclass ListRecycler;\nstruct ListViewState;\n\nclass ListLayoutManager {\n public:\n  explicit ListLayoutManager(\n      ScrollDirection orientation = kDefaultScrollDirection);\n  virtual ~ListLayoutManager();\n\n  static std::unique_ptr<ListLayoutManager> Create(const std::string& type,\n                                                   int span_count);\n\n  void SetListView(BaseListView* list_view);\n  void SetChildrenHelper(ListChildrenHelper* children_helper);\n\n  virtual void OnLayoutChildren(ListRecycler& recycler,\n                                const ListViewState& state) = 0;\n\n  // Subclass should call this method.\n  virtual void OnLayoutCompleted(ListRecycler& recycler,\n                                 const ListViewState& state);\n\n  virtual float ScrollHorizontallyBy(float dx, ListRecycler& recycler,\n                                     const ListViewState& state) {\n    return 0.f;\n  }\n\n  virtual float ScrollVerticallyBy(float dy, ListRecycler& recycler,\n                                   const ListViewState& state) {\n    return 0.f;\n  }\n\n  void ScrapFloatingItems(ListRecycler* recycler);\n  void LayoutSticky(ListRecycler* recycler, ListAdapter* adapter);\n\n  void SetStickyOffset(double offset);\n\n  bool CanScrollHorizontally() const {\n    return orientation_ == ScrollDirection::kHorizontal;\n  }\n  bool CanScrollVertically() const {\n    return orientation_ == ScrollDirection::kVertical;\n  }\n\n  float GetWidth();\n  float GetHeight();\n  float GetPaddingLeft();\n  float GetPaddingRight();\n  float GetPaddingTop();\n  float GetPaddingBottom();\n\n  virtual void OffsetChildren(float x, float y);\n\n  // Delete item's layout param.\n  virtual void OnRemoveItem(ListItemViewHolder* item) {}\n\n  ListOrientationHelper* GetOrientationHelper() const {\n    return orientation_helper_.get();\n  }\n  ScrollDirection GetOrientation() const { return orientation_; }\n  // return whether orientation changes\n  virtual bool SetOrientation(ScrollDirection orientation);\n\n  // Layout the list with an area larger than the visible part.\n  virtual void OnFocusSearchFailed(bool to_end, ListRecycler& recycler,\n                                   const ListViewState& state) {\n    // Should implement by layout manager subclass.\n  }\n\n  /**\n   * Called in response to a call to `ListAdapter::NotifyDataSetChanged()` and\n   * signals that the the entire data set has changed.\n   *\n   * Used by staggered grid layout manager.\n   */\n  virtual void OnItemsChanged(BaseListView* list_view) {}\n\n  // A range of callback to handle item change from adapter.\n  virtual void OnItemsAdded(BaseListView* list_view, int position_start,\n                            int item_count) {\n    length_cache_->OnItemsAdded(position_start, item_count);\n  }\n  virtual void OnItemsRemoved(BaseListView* list_view, int position_start,\n                              int item_count) {\n    length_cache_->OnItemsRemoved(position_start, item_count);\n  }\n  virtual void OnItemsUpdated(BaseListView* list_view, int position_start,\n                              int item_count) {\n    length_cache_->OnItemsUpdated(position_start, item_count);\n  }\n  virtual void OnItemsMoved(BaseListView* list_view, int from, int to,\n                            int item_count) {\n    length_cache_->OnItemsMoved(from, to, item_count);\n  }\n\n  // scroll state changed notification\n  virtual void OnScrollStateChange(Scrollable::ScrollStatus state) {}\n\n  // The actual scroll will be delayed until the next `OnLayoutChildren()` call.\n  virtual void ScrollToPosition(int position,\n                                AlignTo align_to = AlignTo::kNone) {\n    // Should implement by layout manager subclass.\n  }\n\n  // `rect` is a location currently relative to the list view which may be in\n  // the invisible area of the list view (out of bounds). Call the method to\n  // scroll the list so that the rect will be in the visible area (in box).\n  // Only called by `BaseListView` to handle the focus logic.\n  // Returns the scroll distance in either horizontal or vertical direction and\n  // the actual scroll is done by `BaseListView`.\n  virtual FloatSize ScrollToRect(const FloatRect& rect, AlignTo align_to) {\n    // Should implement by layout manager subclass.\n    return {0.f, 0.f};\n  }\n\n  using PositionAndRelativeRect = std::pair<int, FloatRect>;\n  virtual std::optional<PositionAndRelativeRect> GetChildPositionByRect(\n      const FloatRect& rect) {\n    return std::nullopt;\n  }\n\n  // Can still scroll to the start direction.\n  virtual bool HasSpaceToStart(const ListViewState& state) { return false; }\n  // Can still scroll to the end direction.\n  virtual bool HasSpaceToEnd(const ListViewState& state) { return false; }\n\n  // Get first child that has start lower than list view's start. No such child\n  // is found, the first is returned.\n  virtual ListItemViewHolder* GetFirstInBoxChild(const ListViewState& state) {\n    return nullptr;\n  }\n  // Get last child that has end above list view's end. No such child is found,\n  // the last is returned.\n  virtual ListItemViewHolder* GetLastInBoxChild(const ListViewState& state) {\n    return nullptr;\n  }\n\n  bool IsItemFullSpan(int position) const;\n\n  // Get infomation of currently visible items in list\n  size_t GetVisibleItemsInfo(std::vector<float>& top_array,\n                             std::vector<float>& bottom_array,\n                             std::vector<float>& left_array,\n                             std::vector<float>& right_array,\n                             std::vector<int>& position,\n                             std::vector<std::string>& id_array);\n\n  void UpdateItemsVisibility();\n\n  void RemoveAndRecycleAllViews(ListRecycler& recycler);\n\n  virtual float GetScrollOffset(const ListViewState& state) { return 0.f; }\n  virtual float GetTotalLength(const ListViewState& state) { return 0.f; }\n\n  void SetItemPrefetchEnabled(bool enabled);\n  bool IsItemPrefetchEnabled() { return prefetch_enabled_; }\n\n  virtual void CollectAdjacentPrefetchPositions(\n      int delta_x, int delta_y, int max_limit,\n      const ListViewState& list_view_state,\n      LayoutPrefetchRegistry* layout_prefetch_registry) {}\n  virtual void CollectInitialPrefetchPositions(\n      int adapter_items_count, const ListViewState& list_view_state,\n      LayoutPrefetchRegistry* layout_prefetch_registry) {}\n\n  void SetCrossAxisGap(float cross_axis_gap);\n  void SetMainAxisGap(float main_axis_gap);\n\n protected:\n  void RequestLayout();\n  // Add item to RenderList.\n  // However, in Clay, the render object tree is not operated directly.\n  // Instead it is driven by the view tree.\n  // When index is -1, it means add the item as the last child.\n  void AddItem(ListItemViewHolder* item_view, int index);\n\n  MeasureResult MeasureItem(ListItemViewHolder* item,\n                            const MeasureConstraint& constraint);\n\n  void LayoutItem(ListItemViewHolder* item, const FloatPoint& top_left);\n\n  // Child (view holders) manipulation\n  int GetChildCount() const;\n  ListItemViewHolder* GetChildAt(int index);\n  ListItemViewHolder* GetFirstChild() { return GetChildAt(0); }\n  ListItemViewHolder* GetLastChild() { return GetChildAt(GetChildCount() - 1); }\n\n  ListItemViewHolder* FindChildByPosition(int position);\n\n  void RemoveAndRecycleViewAt(ListRecycler& recycler, int index);\n\n  void RemoveAndScrapChildren(ListRecycler& recycler);\n  void RemoveAndScrapChild(ListRecycler& recycler, int index);\n\n  virtual std::vector<int> GetVisibleItemPositions() { return {}; }\n\n  MeasureConstraint FullSpanConstraint();\n\n protected:\n  float cross_axis_gap_ = 0;\n  float main_axis_gap_ = 0;\n  // Not owned. It is borrowed from BaseListView.\n  ListChildrenHelper* children_helper_ = nullptr;\n\n  std::unique_ptr<ListOrientationHelper> orientation_helper_;\n  ScrollDirection orientation_ = ScrollDirection::kNone;\n\n  std::unique_ptr<ListItemLengthCache> length_cache_;\n\n private:\n  // Make list_view_ private. The subclass should not use it directly.\n  BaseListView* list_view_ = nullptr;\n  bool prefetch_enabled_ = true;\n  double sticky_offset_ = 0.0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_layout_manager_grid.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_layout_manager_grid.h\"\n\n#include <algorithm>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_children_helper.h\"\n#include \"clay/ui/component/list/list_item_length_cache.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_orientation_helper.h\"\n#include \"clay/ui/component/list/list_recycler.h\"\n#include \"clay/ui/component/list/macros.h\"\n\nnamespace clay {\n\nnamespace {\n\ninline size_t GetRowIndex(size_t index, size_t columns) {\n  return index / columns;\n}\n\n// Calculate the accumulated length of items from position 0 to `to_position`\n// based on the cached length sections.\n// Item at `to_position` is not included.\nfloat CalculateAccumulatedLength(const ListItemLengthCache::Sections& sections,\n                                 int to_position, size_t columns,\n                                 float main_axis_gap = 0) {\n  if (to_position <= 0) {\n    return 0;\n  }\n\n  FML_DCHECK(columns >= 1);\n  size_t rows = GetRowIndex(to_position - 1, columns) + 1;\n  std::vector<float> rows_height(rows, -1);  // -1 means unknown\n\n  for (const auto& section : sections) {\n    const int end_pos = std::min(section.to_pos, to_position);\n    if (section.length != ListItemLengthCache::kInvalidLength) {\n      for (size_t start = GetRowIndex(section.from_pos, columns),\n                  end = GetRowIndex(end_pos, columns);\n           start <= end; start++) {\n        if (start < rows) {\n          rows_height[start] =\n              std::max<float>(rows_height[start], section.length);\n        }\n      }\n    }\n    if (section.to_pos >= to_position) {\n      break;\n    }\n  }\n\n  float known_height = 0;\n  size_t known_rows = 0;\n  for (auto height : rows_height) {\n    if (height >= 0) {\n      known_rows++;\n      known_height += height + main_axis_gap;\n    }\n  }\n\n  if (known_rows == 0) {\n    return 0;\n  } else if (known_rows < rows_height.size()) {\n    float estimated_unknown_height =\n        known_height / known_rows * (rows_height.size() - known_rows);\n    return known_height + estimated_unknown_height;\n  } else {\n    return known_height;\n  }\n}\n\n}  // namespace\n\nvoid CalculateItemBorders(std::vector<float>& cached_borders, size_t span_count,\n                          float total_space) {\n  if (cached_borders.size() != span_count + 1) {\n    cached_borders.resize(span_count + 1);\n  }\n  if (cached_borders[span_count] == total_space) {\n    // if space didn't change, we don't need calculate again\n    return;\n  }\n  FML_DCHECK(span_count);\n  float size_per_span = total_space / span_count;\n  float consumed_pixels = 0;\n  for (size_t i = 0; i <= span_count; ++i) {\n    cached_borders[i] = consumed_pixels;\n    consumed_pixels += size_per_span;\n  }\n}\n\nclass FullSpanEnabledSpanSizeLookup\n    : public ListLayoutManagerGrid::SpanSizeLookup {\n public:\n  explicit FullSpanEnabledSpanSizeLookup(ListLayoutManagerGrid* manager)\n      : SpanSizeLookup(true), manager_(manager) {}\n  int GetSpanSize(int position) const override {\n    if (manager_->IsItemFullSpan(position)) {\n      return manager_->GetSpanCount();\n    }\n    return 1;\n  }\n\n private:\n  ListLayoutManagerGrid* manager_;\n};\n\nListLayoutManagerGrid::SpanSizeLookup::SpanSizeLookup(bool enable_cache)\n    : enable_cache_(enable_cache) {}\n\nint ListLayoutManagerGrid::SpanSizeLookup::GetCachedSpanIndex(int position,\n                                                              int span_count) {\n  if (!enable_cache_) {\n    return GetSpanIndex(position, span_count);\n  }\n  auto iter = span_index_cache_.find(position);\n  if (iter != span_index_cache_.end()) {\n    return iter->second;\n  }\n  int value = GetSpanIndex(position, span_count);\n  span_index_cache_.insert({position, value});\n  return value;\n}\n\nint ListLayoutManagerGrid::SpanSizeLookup::GetCachedSpanGroupIndex(\n    int adapter_position, int span_count) {\n  if (!enable_cache_) {\n    return GetSpanGroupIndex(adapter_position, span_count);\n  }\n  auto iter = span_group_index_cache_.find(adapter_position);\n  if (iter != span_group_index_cache_.end()) {\n    return iter->second;\n  }\n  int value = GetSpanIndex(adapter_position, span_count);\n  span_group_index_cache_.insert({adapter_position, value});\n  return value;\n}\n\nvoid ListLayoutManagerGrid::SpanSizeLookup::InvalidCaches() {\n  span_group_index_cache_.clear();\n  span_index_cache_.clear();\n}\n\nvoid ListLayoutManagerGrid::SpanSizeLookup::SetEnableCache(bool enable) {\n  if (!enable) {\n    InvalidCaches();\n  }\n  enable_cache_ = enable;\n}\n\nint ListLayoutManagerGrid::SpanSizeLookup::GetSpanIndex(int position,\n                                                        int span_count) {\n  int position_span_size = GetSpanSize(position);\n  if (position_span_size == span_count) {\n    return 0;\n  }\n  int span = 0;\n  int start_pos = 0;\n\n  for (int i = start_pos; i < position; ++i) {\n    int size = GetSpanSize(i);\n    span += size;\n    if (span == span_count) {\n      span = 0;\n    } else if (span > span_count) {\n      span = size;\n    }\n  }\n  if (span + position_span_size <= span_count) {\n    return span;\n  }\n  return 0;\n}\n\nint ListLayoutManagerGrid::SpanSizeLookup::GetSpanGroupIndex(\n    int adapter_position, int span_count) {\n  int span = 0;\n  int group = 0;\n  int start = 0;\n\n  int position_span_size = GetSpanSize(adapter_position);\n  for (int i = start; i < adapter_position; ++i) {\n    int size = GetSpanSize(i);\n    span += size;\n    if (span == span_count) {\n      span = 0;\n      group++;\n    } else if (span > span_count) {\n      span = size;\n      group++;\n    }\n  }\n  if (span + position_span_size > span_count) {\n    group++;\n  }\n  return group;\n}\n\nListLayoutManagerGrid::ListLayoutManagerGrid(int span_count,\n                                             ScrollDirection orientation)\n    : ListLayoutManagerLinear(orientation),\n      span_count_(span_count),\n      cached_borders_({}),\n      span_size_lookup_(std::make_unique<FullSpanEnabledSpanSizeLookup>(this)) {\n  FML_CHECK(span_count > 0);\n}\n\nListLayoutManagerGrid::~ListLayoutManagerGrid() = default;\n\nint ListLayoutManagerGrid::GetSpanSize(ListRecycler& recycler,\n                                       const ListViewState& state,\n                                       int pos) const {\n  return span_size_lookup_->GetSpanSize(pos);\n}\n\nint ListLayoutManagerGrid::GetSpanIndex(ListRecycler& recycler,\n                                        const ListViewState& state,\n                                        int pos) const {\n  return span_size_lookup_->GetCachedSpanIndex(pos, span_count_);\n}\n\nint ListLayoutManagerGrid::GetSpanGroupIndex(ListRecycler& recycler,\n                                             const ListViewState& state,\n                                             int pos) const {\n  return span_size_lookup_->GetCachedSpanGroupIndex(pos, span_count_);\n}\n\nfloat ListLayoutManagerGrid::ScrollVerticallyBy(float dy,\n                                                ListRecycler& recycler,\n                                                const ListViewState& state) {\n  UpdateMeasurements();\n  return ListLayoutManagerLinear::ScrollVerticallyBy(dy, recycler, state);\n}\n\nfloat ListLayoutManagerGrid::ScrollHorizontallyBy(float dx,\n                                                  ListRecycler& recycler,\n                                                  const ListViewState& state) {\n  UpdateMeasurements();\n  return ListLayoutManagerLinear::ScrollHorizontallyBy(dx, recycler, state);\n}\n\nvoid ListLayoutManagerGrid::OnItemsChanged(BaseListView* list_view) {\n  span_size_lookup_->InvalidCaches();\n}\n\nvoid ListLayoutManagerGrid::OnItemsAdded(BaseListView* list_view,\n                                         int position_start, int item_count) {\n  ListLayoutManager::OnItemsAdded(list_view, position_start, item_count);\n  span_size_lookup_->InvalidCaches();\n}\nvoid ListLayoutManagerGrid::OnItemsRemoved(BaseListView* list_view,\n                                           int position_start, int item_count) {\n  ListLayoutManager::OnItemsRemoved(list_view, position_start, item_count);\n  span_size_lookup_->InvalidCaches();\n}\nvoid ListLayoutManagerGrid::OnItemsUpdated(BaseListView* list_view,\n                                           int position_start, int item_count) {\n  ListLayoutManager::OnItemsUpdated(list_view, position_start, item_count);\n  span_size_lookup_->InvalidCaches();\n}\nvoid ListLayoutManagerGrid::OnItemsMoved(BaseListView* list_view, int from,\n                                         int to, int item_count) {\n  ListLayoutManager::OnItemsMoved(list_view, from, to, item_count);\n  span_size_lookup_->InvalidCaches();\n}\n\nvoid ListLayoutManagerGrid::SetSpanCount(int span_count) {\n  if (span_count == span_count_) {\n    return;\n  }\n  FML_CHECK(span_count > 0) << \"Span count must greater than 0!\";\n  span_count_ = span_count;\n  span_size_lookup_->InvalidCaches();\n  RequestLayout();\n}\n\nfloat ListLayoutManagerGrid::GetScrollOffset(const ListViewState& state) {\n  if (!HasSpaceToStart(state)) {\n    return 0.f;\n  } else if (!HasSpaceToEnd(state)) {\n    return GetTotalLength(state) - orientation_helper_->GetTotalSpace();\n  }\n\n  ListItemViewHolder* first = GetFirstInBoxChild(state);\n  if (first == nullptr) {\n    return 0.f;\n  }\n  const ListItemLengthCache::Sections& sections = length_cache_->GetSections();\n  float offset = CalculateAccumulatedLength(sections, first->GetPosition(),\n                                            span_count_, main_axis_gap_);\n  offset -= orientation_helper_->GetDecoratedStart(first);\n  return offset;\n}\n\nfloat ListLayoutManagerGrid::GetTotalLength(const ListViewState& state) {\n  const ListItemLengthCache::Sections& sections = length_cache_->GetSections();\n  return CalculateAccumulatedLength(sections, state.item_count, span_count_,\n                                    main_axis_gap_);\n}\n\nvoid ListLayoutManagerGrid::CollectPrefetchPositionForLayoutState(\n    const ListViewState& list_view_state, int abs_delta, int max_limit,\n    LayoutPrefetchRegistry* layout_prefetch_registry) {\n  int remaining_span_count = span_count_;\n  int scrolling_offset = layout_state_.scrolling_offset_.value_or(0);\n  if (GetChildCount() != 0 && scrolling_offset != 0) {\n    while (layout_state_.HasMore(list_view_state) && remaining_span_count > 0 &&\n           max_limit > 0) {\n      max_limit--;\n      int position = layout_state_.current_position_;\n      layout_prefetch_registry->AddPosition(\n          position,\n          std::max(0, static_cast<int>(\n                          layout_state_.scrolling_offset_.value_or(0))));\n      int span_size = span_size_lookup_->GetSpanSize(position);\n      remaining_span_count -= span_size;\n      layout_state_.current_position_ +=\n          static_cast<int>(layout_state_.item_direction_);\n    }\n  }\n}\n\n// Fill one row every time called.\nfloat ListLayoutManagerGrid::LayoutChunk(ListRecycler& recycler,\n                                         const ListViewState& state) {\n  LIST_LOG << \"------ LayoutChunk Start ------\";\n  std::vector<ListItemViewHolder*> temp_items;\n  layout_params_.clear();\n  bool laying_out_in_primary_direction =\n      layout_state_.item_direction_ == ListItemDirection::kToTail;\n\n  int count = 0;\n  int remaining_span;\n  if (laying_out_in_primary_direction) {\n    remaining_span = span_count_;\n  } else {\n    int item_span_index =\n        GetSpanIndex(recycler, state, layout_state_.current_position_);\n    int item_span_size =\n        GetSpanSize(recycler, state, layout_state_.current_position_);\n    remaining_span = item_span_index + item_span_size;\n  }\n  while (count < span_count_ && layout_state_.HasMore(state) &&\n         remaining_span > 0) {\n    int pos = layout_state_.current_position_;\n    int span_size = GetSpanSize(recycler, state, pos);\n    if (span_size > span_count_) {\n      // invalid case\n      FML_DLOG(ERROR) << \"Item at position \" << pos << \" requires \" << span_size\n                      << \" spans but GridLayoutManager has only \" << span_count_\n                      << \" spans. Skipping this item\";\n      continue;\n    }\n    remaining_span -= span_size;\n    // When remaining_span is insufficient, finish loop instead of retrieving\n    // item.\n    if (remaining_span < 0) {\n      break;\n    }\n    ListItemViewHolder* item = layout_state_.Next(recycler);\n    if (item == nullptr) {\n      break;\n    }\n\n    LIST_LOG << \"Add an Item To layout. view_id:\" << item->GetView()->id()\n             << \" name:\" << item->GetView()->GetName()\n             << \" pos:\" << item->GetPosition() << \" span_size: \" << span_size;\n    temp_items.push_back(item);\n    count++;\n  }\n\n  LIST_LOG << \"Total items to layout \" << count;\n\n  if (count == 0) {\n    return 0;\n  }\n\n  float max_size = 0.f;\n  // same constraint for the row\n  // first time we measure with constraint\n  MeasureConstraint constraint;\n  if (orientation_ == ScrollDirection::kVertical) {\n    constraint.width_mode = MeasureMode::kDefinite;\n    constraint.height_mode = MeasureMode::kIndefinite;\n  } else {\n    constraint.height_mode = MeasureMode::kDefinite;\n    constraint.width_mode = MeasureMode::kIndefinite;\n  }\n\n  AssignSpan(recycler, state, count, laying_out_in_primary_direction,\n             temp_items);\n  for (ListItemViewHolder* item : temp_items) {\n    if (laying_out_in_primary_direction) {\n      AddItem(item, GetChildCount());\n    } else {\n      AddItem(item, 0);\n    }\n\n    auto param = GetLayoutParam(item);\n    int total_space_in_other =\n        GetSpaceForSpanRange(param.span_index_, param.span_size_);\n    FML_DCHECK(total_space_in_other >= 0.f)\n        << \"Invalid span! span_index \" << param.span_index_ << \"\\tspan_size \"\n        << param.span_size_;\n    if (orientation_ == ScrollDirection::kVertical) {\n      constraint.width = total_space_in_other;\n    } else {\n      constraint.height = total_space_in_other;\n    }\n\n    MeasureResult res = MeasureItem(item, constraint);\n    if (orientation_ == ScrollDirection::kVertical) {\n      if (res.height > max_size) {\n        max_size = res.height;\n      }\n    } else {\n      if (res.width > max_size) {\n        max_size = res.width;\n      }\n    }\n  }\n\n  // MeasureAgain make sure they have same space in one row\n  if (orientation_ == ScrollDirection::kVertical) {\n    constraint.height = max_size;\n    constraint.height_mode = MeasureMode::kDefinite;\n  } else {\n    constraint.width = max_size;\n    constraint.width_mode = MeasureMode::kDefinite;\n  }\n\n  LIST_LOG << \"Measure Items with constraint width: \"\n           << constraint.width.value_or(-1)\n           << \" height: \" << constraint.height.value_or(-1);\n  for (ListItemViewHolder* item : temp_items) {\n    MeasureItem(item, constraint);\n  }\n\n  float left = 0.f, right = 0.f, top = 0.f, bottom = 0.f;\n  bool first_line = temp_items[0]->GetPosition() < span_count_;\n  bool fill_to_start =\n      layout_state_.layout_direction_ == ListLayoutDirection::kToStart;\n  float expected_main_axis_gap =\n      fill_to_start ? main_axis_gap_ : (first_line ? 0 : main_axis_gap_);\n  if (orientation_ == ScrollDirection::kVertical) {\n    if (layout_state_.layout_direction_ == ListLayoutDirection::kToStart) {\n      bottom = layout_state_.offset_;\n      bottom -= expected_main_axis_gap;\n      top = bottom - max_size;\n    } else {\n      top = layout_state_.offset_;\n      top += expected_main_axis_gap;\n      bottom = top + max_size;\n    }\n  } else {\n    if (layout_state_.layout_direction_ == ListLayoutDirection::kToStart) {\n      right = layout_state_.offset_;\n      right -= expected_main_axis_gap;\n      left = right - max_size;\n    } else {\n      left = layout_state_.offset_;\n      left += expected_main_axis_gap;\n      right = left + max_size;\n    }\n  }\n\n  for (ListItemViewHolder* item : temp_items) {\n    auto param = GetLayoutParam(item);\n    if (orientation_ == ScrollDirection::kVertical) {\n      left = cached_borders_[param.span_index_];\n      if (!item->FullSpan()) {\n        left += GetPaddingLeft();\n        left += cross_axis_gap_ * (param.span_index_ % span_count_);\n      }\n      right = left + orientation_helper_->GetSecondaryDecoratedMeasure(item);\n    } else {\n      top = cached_borders_[param.span_index_];\n      if (!item->FullSpan()) {\n        top += GetPaddingTop();\n        top += cross_axis_gap_ * (param.span_index_ % span_count_);\n      }\n      bottom = right + orientation_helper_->GetSecondaryDecoratedMeasure(item);\n    }\n    LayoutItem(item, FloatPoint(left, top));\n\n    LIST_LOG << \"Laid out an item to (\" << left << \",\" << top << \").\";\n  }\n\n  LIST_LOG << \"Grid Laid out a raw of item. consumed \" << max_size << \" items \"\n           << temp_items.size();\n  return max_size + expected_main_axis_gap;\n}\n\nvoid ListLayoutManagerGrid::UpdateMeasurements() {\n  float total_space = orientation_helper_->GetSecondaryTotalSpace();\n  CalculateItemBorders(cached_borders_, span_count_, total_space);\n}\n\nvoid ListLayoutManagerGrid::OnAnchorReady(ListRecycler& recycler,\n                                          const ListViewState& state,\n                                          AnchorInfo& anchor_info,\n                                          bool is_prime_direction) {\n  ListLayoutManagerLinear::OnAnchorReady(recycler, state, anchor_info,\n                                         is_prime_direction);\n  UpdateMeasurements();\n  if (state.item_count > 0) {\n    EnsureAnchorSpanPosition(recycler, state, anchor_info, is_prime_direction);\n  }\n}\n\nvoid ListLayoutManagerGrid::EnsureAnchorSpanPosition(\n    ListRecycler& recycler, const ListViewState& state, AnchorInfo& anchor_info,\n    bool is_prime_direction) const {\n  int span = GetSpanIndex(recycler, state, anchor_info.position_);\n  if (is_prime_direction) {\n    while (span > 0 && anchor_info.position_ > 0) {\n      anchor_info.position_--;\n      span = GetSpanIndex(recycler, state, anchor_info.position_);\n    }\n  } else {\n    int index_limit = state.item_count - 1;\n    int pos = anchor_info.position_;\n    int best_span = span;\n    while (pos < index_limit) {\n      int next = GetSpanIndex(recycler, state, pos + 1);\n      if (next > best_span) {\n        pos++;\n        best_span = next;\n      } else {\n        break;\n      }\n    }\n    anchor_info.position_ = pos;\n  }\n}\n\nListItemViewHolder* ListLayoutManagerGrid::FindReferenceChild(\n    ListRecycler& recycler, const ListViewState& state) {\n  ListItemViewHolder* invalid_match = nullptr;\n  ListItemViewHolder* out_of_bounds_match = nullptr;\n  ListItemViewHolder* in_bounds_match = nullptr;\n\n  children_helper_->ForEach(\n      [this, &invalid_match, &out_of_bounds_match, &in_bounds_match, &recycler,\n       &state, item_count = state.item_count,\n       bounds_start = orientation_helper_->GetStartAfterPadding(),\n       bounds_end = orientation_helper_->GetEndAfterPadding()](\n          ListItemViewHolder* child) -> bool {\n        const int position = child->GetLayoutPosition();\n        const int child_start = orientation_helper_->GetDecoratedStart(child);\n        const int child_end = orientation_helper_->GetDecoratedEnd(child);\n        if (position >= 0 && position < static_cast<int>(item_count)) {\n          int span = GetSpanIndex(recycler, state, position);\n          if (span != 0) {\n            return false;\n          }\n\n          if (child->IsRemoved()) {\n            if (invalid_match == nullptr) {\n              invalid_match = child;  // removed item, least preferred\n            }\n            return false;\n          }\n\n          // Usually if child_start >= bounds_end the child is out\n          // of bounds, except if the child is 0 pixels!\n          const bool out_of_bounds_before =\n              child_end <= bounds_start && child_start < bounds_start;\n          const bool out_of_bounds_after =\n              child_start >= bounds_end && child_end > bounds_end;\n          if (out_of_bounds_before || out_of_bounds_after) {\n            if (out_of_bounds_match == nullptr) {\n              out_of_bounds_match = child;\n              return false;\n            }\n          } else {\n            in_bounds_match = child;\n            return true;  // Found in bound. Should break the iteration.\n          }\n        }\n        return false;\n      });\n  if (in_bounds_match) {\n    return in_bounds_match;\n  }\n  if (out_of_bounds_match) {\n    return out_of_bounds_match;\n  }\n  return invalid_match;\n}\n\nvoid ListLayoutManagerGrid::AssignSpan(\n    ListRecycler& recycler, const ListViewState& state, int count,\n    bool laying_out_in_primary_direction,\n    const std::vector<ListItemViewHolder*>& temp_items) {\n  int span_index = 0, start, end, diff;\n  int span_size = 0;\n  // make sure we traverse from min position to max position\n  if (laying_out_in_primary_direction) {\n    start = 0;\n    end = count;\n    diff = 1;\n  } else {\n    start = count - 1;\n    end = -1;\n    diff = -1;\n  }\n  for (int i = start; i != end; i += diff) {\n    ListItemViewHolder* item = temp_items[i];\n    span_size = GetSpanSize(recycler, state, item->GetPosition());\n    layout_params_.insert({item->GetView()->id(), {span_index, span_size}});\n    span_index += span_size;\n  }\n}\n\nfloat ListLayoutManagerGrid::GetSpaceForSpanRange(int start_span,\n                                                  int span_size) const {\n  return cached_borders_[start_span + span_size] - cached_borders_[start_span];\n}\n\nconst ListLayoutManagerGrid::LayoutParam& ListLayoutManagerGrid::GetLayoutParam(\n    ListItemViewHolder* item) const {\n  FML_DCHECK(item);\n\n  int view_id = item->GetView()->id();\n  auto iter = layout_params_.find(view_id);\n\n  // ensure get layout param in a valid item.\n  FML_DCHECK(iter != layout_params_.end())\n      << \" grid layout fail to get layout param for \" << item->GetView()->id();\n  return iter->second;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_layout_manager_grid.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_GRID_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_GRID_H_\n\n#include <memory>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/component/list/list_layout_manager_linear.h\"\n\nnamespace clay {\n\n// TODO(hanhaoshen): do we need prelayout?\nclass ListLayoutManagerGrid : public ListLayoutManagerLinear {\n public:\n  explicit ListLayoutManagerGrid(\n      int span_count, ScrollDirection orientation = kDefaultScrollDirection);\n  ~ListLayoutManagerGrid() override;\n\n  // A helper class to provide the number of spans each item occupies.\n  // Default implementation sets each item to occupy exactly 1 span.\n  class SpanSizeLookup {\n   public:\n    explicit SpanSizeLookup(bool enable_cache);\n    virtual ~SpanSizeLookup() = default;\n    // Returns the number of span occupied by the item at position.\n    virtual int GetSpanSize(int position) const = 0;\n    // Returns the final span index of the provided position.\n    int GetCachedSpanIndex(int position, int span_count);\n    // Returns the index of the group this position belongs.\n    int GetCachedSpanGroupIndex(int adapter_position, int span_count);\n\n    void InvalidCaches();\n    void SetEnableCache(bool enable);\n\n   private:\n    virtual int GetSpanIndex(int position, int span_count);\n    virtual int GetSpanGroupIndex(int adapter_position, int span_count);\n\n    bool enable_cache_;\n    std::unordered_map<int, int> span_index_cache_;\n    std::unordered_map<int, int> span_group_index_cache_;\n  };\n\n  int GetSpanSize(ListRecycler& recycler, const ListViewState& state,\n                  int pos) const;\n  int GetSpanIndex(ListRecycler& recycler, const ListViewState& state,\n                   int pos) const;\n  int GetSpanGroupIndex(ListRecycler& recycler, const ListViewState& state,\n                        int pos) const;\n\n  float ScrollVerticallyBy(float dy, ListRecycler& recycler,\n                           const ListViewState& state) override;\n  float ScrollHorizontallyBy(float dx, ListRecycler& recycler,\n                             const ListViewState& state) override;\n\n  void OnItemsChanged(BaseListView* list_view) override;\n\n  void OnItemsAdded(BaseListView* list_view, int position_start,\n                    int item_count) override;\n  void OnItemsRemoved(BaseListView* list_view, int position_start,\n                      int item_count) override;\n  void OnItemsUpdated(BaseListView* list_view, int position_start,\n                      int item_count) override;\n  void OnItemsMoved(BaseListView* list_view, int from, int to,\n                    int item_count) override;\n\n  int GetSpanCount() const { return span_count_; }\n  void SetSpanCount(int span_count);\n  void SetSpanSizeLookup(std::unique_ptr<SpanSizeLookup> span_size_lookup) {\n    span_size_lookup_ = std::move(span_size_lookup);\n  }\n\n  float GetScrollOffset(const ListViewState& state) override;\n  float GetTotalLength(const ListViewState& state) override;\n\n  void CollectPrefetchPositionForLayoutState(\n      const ListViewState& list_view_state, int abs_delta, int max_limit,\n      LayoutPrefetchRegistry* layout_prefetch_registry) override;\n\n private:\n  struct LayoutParam {\n    // the indices of item in listview\n    int span_index_;\n\n    int span_size_;\n  };\n\n  float LayoutChunk(ListRecycler& recycler,\n                    const ListViewState& state) override;\n\n  void UpdateMeasurements();\n  void OnAnchorReady(ListRecycler& recycler, const ListViewState& state,\n                     AnchorInfo& anchor_info, bool is_prime_direction) override;\n  void EnsureAnchorSpanPosition(ListRecycler& recycler,\n                                const ListViewState& state,\n                                AnchorInfo& anchor_info,\n                                bool is_prime_direction) const;\n  ListItemViewHolder* FindReferenceChild(ListRecycler& recycler,\n                                         const ListViewState& state) override;\n  void AssignSpan(ListRecycler& recycler, const ListViewState& state, int count,\n                  bool laying_out_in_primary_direction,\n                  const std::vector<ListItemViewHolder*>& temp_items);\n\n  float GetSpaceForSpanRange(int span_index, int span_size) const;\n  const LayoutParam& GetLayoutParam(ListItemViewHolder* item) const;\n\n  // the number of columns in the grid\n  int span_count_;\n\n  std::vector<float> cached_borders_;\n  std::unique_ptr<SpanSizeLookup> span_size_lookup_;\n  // local cache for [key=view_id:value=layout_param]\n  std::unordered_map<int, LayoutParam> layout_params_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_GRID_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_layout_manager_linear.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_layout_manager_linear.h\"\n\n#include <algorithm>\n#include <optional>\n#include <string>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/ui/common/macros.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_children_helper.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/list/list_item_length_cache.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_orientation_helper.h\"\n#include \"clay/ui/component/list/list_recycler.h\"\n#include \"clay/ui/component/list/macros.h\"\n\nnamespace clay {\nnamespace {\n\nconstexpr float kMaxScrollFactor = 1.f / 3.f;\n\n// Calculate the accumulated length of items from position 0 to `to_position`\n// based on the cached length sections.\n// Item at `to_position` is not included.\nfloat CalculateAccumulatedLength(const ListItemLengthCache::Sections& sections,\n                                 int to_position, float main_axis_gap) {\n  float total_length = 0.f;\n\n  int last_position = -1;\n\n  // For item without cached length, we estimate its length with the closest\n  // item having valid length. It's possible that items in the first section\n  // have no cached length. If that's the case, we continue the iteration\n  // without accumulating their lengths and go back when we have the valid\n  // length of the next section.\n  int unknown_count = 0;\n  int known_count = 0;\n  for (const auto& section : sections) {\n    last_position = section.to_pos;\n    const int end_pos = std::min(section.to_pos, to_position);\n    if (section.length == ListItemLengthCache::kInvalidLength) {\n      unknown_count += end_pos - section.from_pos;\n    } else {\n      total_length += (end_pos - section.from_pos) * section.length;\n      known_count += end_pos - section.from_pos;\n    }\n\n    if (section.to_pos >= to_position) {\n      break;\n    }\n  }\n\n  if (to_position > last_position) {\n    unknown_count += to_position - last_position;\n  }\n\n  // It is possible that the previous loop may end with unknown_count is not\n  // resolved. For example, Items at [0, 100) are unknown and the input position\n  // is 50. Then we should find the first section with valid length and use it\n  // to calculate the unknown count.\n  if (unknown_count > 0) {\n    if (known_count == 0) {\n      return 0;\n    }\n    float avg_length = total_length / known_count;\n    total_length += unknown_count * avg_length;\n  }\n  // add gaps between first item and to_position item.\n  total_length += to_position * main_axis_gap;\n  return total_length;\n}\n\n}  // namespace\n\nvoid ListLayoutManagerLinear::AnchorInfo::Reset() {\n  valid_ = false;\n  layout_from_end_ = false;\n  position_ = ListItemViewHolder::kNoPosition;\n  coordinate_ = kInvalidOffset;\n}\n\nvoid ListLayoutManagerLinear::AnchorInfo::AssignCoordinateFromPadding(\n    const ListOrientationHelper& helper, const LayoutState& state) {\n  coordinate_ = layout_from_end_ ? helper.GetEndAfterPadding()\n                                 : helper.GetStartAfterPadding();\n}\n\nvoid ListLayoutManagerLinear::AnchorInfo::AssignFromChild(\n    ListItemViewHolder* child, const ListOrientationHelper& helper,\n    const LayoutState& state) {\n  if (state.layout_direction_ == ListLayoutDirection::kToStart) {\n    coordinate_ = helper.GetDecoratedEnd(child);\n  } else {\n    coordinate_ = helper.GetDecoratedStart(child);\n  }\n  position_ = child->GetLayoutPosition();\n}\n\nListLayoutManagerLinear::ListLayoutManagerLinear(ScrollDirection orientation)\n    : ListLayoutManager(orientation) {\n  ResetLayoutState();\n  anchor_info_.Reset();\n}\n\nListLayoutManagerLinear::~ListLayoutManagerLinear() = default;\n\nvoid ListLayoutManagerLinear::OnLayoutChildren(ListRecycler& recycler,\n                                               const ListViewState& state) {\n  LIST_LOG << \"------ LayoutChildren Start ------\";\n\n  if (pending_scroll_position_ != ListItemViewHolder::kNoPosition &&\n      state.item_count == 0) {\n    RemoveAndRecycleAllViews(recycler);\n    return;\n  }\n\n  ResetLayoutState();\n  layout_state_.recycle_enabled_ = false;\n\n  if (!anchor_info_.valid_ ||\n      pending_scroll_position_ != ListItemViewHolder::kNoPosition) {\n    anchor_info_.Reset();\n    UpdateAnchorInfo(recycler, state);\n    anchor_info_.valid_ = true;\n  }\n\n  LIST_LOG << \" anchor_info: position = \" << anchor_info_.position_\n           << \", coordinate = \" << anchor_info_.coordinate_;\n\n  int extra_for_start = orientation_helper_->GetStartAfterPadding();\n  int extra_for_end = orientation_helper_->GetEndPadding();\n\n  ListItemDirection first_layout_direction = anchor_info_.layout_from_end_\n                                                 ? ListItemDirection::kToHead\n                                                 : ListItemDirection::kToTail;\n\n  bool is_prime_direction =\n      first_layout_direction == ListItemDirection::kToTail;\n\n  OnAnchorReady(recycler, state, anchor_info_, is_prime_direction);\n  RemoveAndScrapChildren(recycler);\n  float start_offset = 0.f, end_offset = 0.f;\n\n  if (anchor_info_.layout_from_end_) {\n    // fill towards start\n    UpdateLayoutStateToFillStart(anchor_info_.position_,\n                                 anchor_info_.coordinate_);\n    layout_state_.extra_ = extra_for_start;\n    Fill(recycler, state);\n    start_offset = layout_state_.offset_;\n    if (layout_state_.available_ > 0) {\n      extra_for_end += layout_state_.available_;\n    }\n\n    // fill towards end\n    UpdateLayoutStateToFillEnd(anchor_info_.position_,\n                               anchor_info_.coordinate_);\n    layout_state_.extra_ = extra_for_end;\n    layout_state_.current_position_ +=\n        static_cast<int>(layout_state_.item_direction_);\n    Fill(recycler, state);\n    end_offset = layout_state_.offset_;\n  } else {\n    // fill towards end\n    UpdateLayoutStateToFillEnd(anchor_info_.position_,\n                               anchor_info_.coordinate_);\n    layout_state_.extra_ = extra_for_end;\n    LIST_LOG << \"Layout to the end.\";\n    Fill(recycler, state);\n    end_offset = layout_state_.offset_;\n    if (layout_state_.available_ > 0) {\n      extra_for_start += layout_state_.available_;\n    }\n\n    // fill towards start\n    UpdateLayoutStateToFillStart(anchor_info_.position_,\n                                 anchor_info_.coordinate_);\n    layout_state_.extra_ = extra_for_start;\n    layout_state_.current_position_ +=\n        static_cast<int>(layout_state_.item_direction_);\n    LIST_LOG << \"Layout to the start.\";\n    Fill(recycler, state);\n    start_offset = layout_state_.offset_;\n  }\n\n  // changes may cause gaps on the UI, try to fix them.\n  if (GetChildCount() > 0) {\n    // because layout from end may be changed by scroll to position we\n    // re-calculate it. find which side we should check for gaps.\n    float fix_offset = FixLayoutStartGap(start_offset, recycler, state, true);\n    start_offset += fix_offset;\n    end_offset += fix_offset;\n    fix_offset = FixLayoutEndGap(end_offset, recycler, state, false);\n    start_offset += fix_offset;\n    end_offset += fix_offset;\n  }\n\n  orientation_helper_->OnLayoutCompleted();\n  LIST_LOG << \"------ LayoutChildren End ------\";\n}\n\nvoid ListLayoutManagerLinear::OnLayoutCompleted(ListRecycler& recycler,\n                                                const ListViewState& state) {\n  ListLayoutManager::OnLayoutCompleted(recycler, state);\n  anchor_info_.Reset();\n}\n\nbool ListLayoutManagerLinear::LayoutState::HasMore(const ListViewState& state) {\n  return current_position_ >= 0 &&\n         static_cast<size_t>(current_position_) < state.item_count;\n}\n\nListItemViewHolder* ListLayoutManagerLinear::LayoutState::Next(\n    ListRecycler& recycler) {\n  ListItemViewHolder* item = recycler.GetItemForPosition(current_position_);\n  if (item == nullptr) {\n    LIST_LOG << \"GetItemForPosition:\" << current_position_ << \" failed\";\n  }\n  current_position_ += static_cast<int>(item_direction_);\n  return item;\n}\n\nstd::string ListLayoutManagerLinear::LayoutState::ToString() const {\n  std::string res;\n#if (DEBUG_LIST)\n  res += \"infinite: \";\n  res += infinite_ ? \"true\" : \"false\";\n  res += \"\\n\";\n\n  res += \"available: \";\n  res += std::to_string(available_);\n  res += \"\\n\";\n\n  res += \"offset: \";\n  res += std::to_string(offset_);\n  res += \"\\n\";\n\n  res += \"current_position: \";\n  res += std::to_string(current_position_);\n  res += \"\\n\";\n\n  res += \"scrolling_offset: \";\n  res += std::to_string(scrolling_offset_.value_or(0.f));\n  res += \"\\n\";\n\n  res += \"item_direction: \";\n  res += item_direction_ == ListItemDirection::kToHead ? \"Head\" : \"Tail\";\n  res += \"\\n\";\n\n  res += \"layout_direction: \";\n  res += layout_direction_ == ListLayoutDirection::kToStart ? \"Start\" : \"End\";\n  res += \"\\n\";\n\n  res += \"extra: \";\n  res += std::to_string(extra_);\n  res += \"\\n\";\n#endif\n  return res;\n}\n\nvoid ListLayoutManagerLinear::ResetLayoutState() {\n  layout_state_.infinite_ = false;\n  layout_state_.recycle_enabled_ = true;\n  layout_state_.available_ = 0.f;\n  layout_state_.offset_ = 0.f;\n  layout_state_.current_position_ = 0;\n  layout_state_.item_direction_ = ListItemDirection::kToTail;\n  layout_state_.layout_direction_ = ListLayoutDirection::kToEnd;\n  layout_state_.scrolling_offset_.reset();\n  layout_state_.last_scroll_delta_ = 0.f;\n}\n\nvoid ListLayoutManagerLinear::UpdateLayoutState(\n    ListLayoutDirection layout_direction, float required_space,\n    bool can_use_existing_space, const ListViewState& state) {\n  FML_DCHECK(GetChildCount());\n\n  layout_state_.layout_direction_ = layout_direction;\n  float scrolling_offset = 0.f;\n  if (layout_direction == ListLayoutDirection::kToEnd) {\n    layout_state_.extra_ = orientation_helper_->GetEndPadding();\n    const ListItemViewHolder* child = GetLastChild();\n    layout_state_.item_direction_ = ListItemDirection::kToTail;\n    layout_state_.current_position_ =\n        child->GetLayoutPosition() +\n        static_cast<int>(layout_state_.item_direction_);\n    layout_state_.offset_ = orientation_helper_->GetDecoratedEnd(child);\n    // Calculate how much we can scroll without adding new children (independent\n    // of layout)\n    int gap =\n        (child->GetLayoutPosition() != static_cast<int>(state.item_count - 1))\n            ? main_axis_gap_\n            : 0;\n    scrolling_offset = orientation_helper_->GetDecoratedEnd(child) + gap -\n                       orientation_helper_->GetEndAfterPadding();\n  } else {\n    const ListItemViewHolder* child = GetFirstChild();\n    layout_state_.extra_ = orientation_helper_->GetStartAfterPadding();\n    layout_state_.item_direction_ = ListItemDirection::kToHead;\n    layout_state_.current_position_ =\n        child->GetLayoutPosition() +\n        static_cast<int>(layout_state_.item_direction_);\n    layout_state_.offset_ = orientation_helper_->GetDecoratedStart(child);\n    int gap = child->GetLayoutPosition() == 0 ? 0 : main_axis_gap_;\n    scrolling_offset = -orientation_helper_->GetDecoratedStart(child) + gap +\n                       orientation_helper_->GetStartAfterPadding();\n  }\n  layout_state_.available_ = required_space;\n  if (can_use_existing_space) {\n    layout_state_.available_ -= scrolling_offset;\n  }\n  layout_state_.scrolling_offset_ = scrolling_offset;\n}\n\nvoid ListLayoutManagerLinear::UpdateLayoutStateToFillStart(int item_pos,\n                                                           float offset) {\n  layout_state_.available_ =\n      offset - orientation_helper_->GetStartAfterPadding();\n  layout_state_.current_position_ = item_pos;\n  layout_state_.item_direction_ = ListItemDirection::kToHead;\n  layout_state_.layout_direction_ = ListLayoutDirection::kToStart;\n  layout_state_.offset_ = offset;\n  layout_state_.scrolling_offset_.reset();\n}\n\nvoid ListLayoutManagerLinear::UpdateLayoutStateToFillEnd(int item_pos,\n                                                         float offset) {\n  layout_state_.available_ = orientation_helper_->GetEndAfterPadding() - offset;\n  layout_state_.current_position_ = item_pos;\n  layout_state_.item_direction_ = ListItemDirection::kToTail;\n  layout_state_.layout_direction_ = ListLayoutDirection::kToEnd;\n  layout_state_.offset_ = offset;\n  layout_state_.scrolling_offset_.reset();\n}\n\nvoid ListLayoutManagerLinear::UpdateAnchorInfo(ListRecycler& recycler,\n                                               const ListViewState& state) {\n  if (UpdateAnchorInfoFromPendingData(state)) {\n    LIST_LOG << \"Updated anchor info from pending data.\";\n    return;\n  }\n  if (UpdateAnchorInfoFromChildren(recycler, state)) {\n    LIST_LOG << \"Updated anchor info from children\";\n    return;\n  }\n  anchor_info_.AssignCoordinateFromPadding(*orientation_helper_, layout_state_);\n  anchor_info_.position_ = 0;\n}\n\nbool ListLayoutManagerLinear::UpdateAnchorInfoFromPendingData(\n    const ListViewState& state) {\n  if (pending_scroll_position_ == ListItemViewHolder::kNoPosition) {\n    return false;\n  }\n  // validate scroll position\n  if (pending_scroll_position_ < 0 ||\n      pending_scroll_position_ >= static_cast<int>(state.item_count)) {\n    LIST_LOG << \"Pending scroll position invalid: \" << pending_scroll_position_;\n    pending_scroll_position_ = ListItemViewHolder::kNoPosition;\n    return false;\n  }\n\n  // Here we just ensure the target item is visible. The next operations are\n  // handled in |ListScroller::ScrollImmediately|\n  ListItemViewHolder* child = FindChildByPosition(pending_scroll_position_);\n  if (child != nullptr) {\n    pending_scroll_position_ = ListItemViewHolder::kNoPosition;\n    return false;\n  } else {  // item is not visible.\n    anchor_info_.position_ = pending_scroll_position_;\n    if (GetChildCount() > 0) {\n      // get position of any child, does not matter\n      int pos = GetFirstChild()->GetPosition();\n      anchor_info_.layout_from_end_ = pending_scroll_position_ >= pos;\n    }\n    switch (pending_scroll_align_to_) {\n      case AlignTo::kNone:\n      case AlignTo::kMiddle: {\n        anchor_info_.coordinate_ =\n            anchor_info_.layout_from_end_ ? orientation_helper_->GetEnd() : 0;\n        break;\n      }\n      case AlignTo::kStart: {\n        anchor_info_.coordinate_ = 0;\n        anchor_info_.layout_from_end_ = false;\n        break;\n      }\n      case AlignTo::kEnd: {\n        anchor_info_.coordinate_ = orientation_helper_->GetEnd();\n        anchor_info_.layout_from_end_ = true;\n        break;\n      }\n      default:\n        break;\n    }\n  }\n  pending_scroll_position_ = ListItemViewHolder::kNoPosition;\n  return true;\n}\n\nbool ListLayoutManagerLinear::UpdateAnchorInfoFromChildren(\n    ListRecycler& recycler, const ListViewState& state) {\n  if (GetChildCount() == 0) {\n    return false;\n  }\n  // TODO(Xietong): anchor should be determined by the focus if any.\n  ListItemViewHolder* reference = FindReferenceChild(recycler, state);\n  if (reference) {\n    anchor_info_.AssignFromChild(reference, *orientation_helper_,\n                                 layout_state_);\n    return true;\n  }\n  return false;\n}\n\nListItemViewHolder* ListLayoutManagerLinear::FindReferenceChild(\n    ListRecycler& recycler, const ListViewState& state) {\n  ListItemViewHolder* invalid_match = nullptr;\n  ListItemViewHolder* best_first_find = nullptr;\n  ListItemViewHolder* best_second_find = nullptr;\n  ListItemViewHolder* in_bound = nullptr;\n\n  children_helper_->ForEach(\n      [this, &invalid_match, &best_first_find, &best_second_find, &in_bound,\n       item_count = state.item_count,\n       bounds_start = orientation_helper_->GetStartAfterPadding(),\n       bounds_end = orientation_helper_->GetEndAfterPadding()](\n          ListItemViewHolder* child) -> bool {\n        const int position = child->GetLayoutPosition();\n        const int child_start = orientation_helper_->GetDecoratedStart(child);\n        const int child_end = orientation_helper_->GetDecoratedEnd(child);\n        if (position >= 0 && position < static_cast<int>(item_count)) {\n          if (child->IsRemoved()) {\n            if (invalid_match == nullptr) {\n              invalid_match = child;  // removed item, least preferred\n            }\n            return false;\n          }\n\n          // Usually if child_start >= bounds_end the child is out\n          // of bounds, except if the child is 0 pixels!\n          const bool out_of_bounds_before =\n              child_end <= bounds_start && child_start < bounds_start;\n          const bool out_of_bounds_after =\n              child_start >= bounds_end && child_end > bounds_end;\n          if (out_of_bounds_before || out_of_bounds_after) {\n            // The item is out of bounds.\n            // We want to find the items closest to the in bounds items and\n            // because we are always going through the items linearly, the 2\n            // items we want are the last out of bounds item on the side we\n            // start searching on, and the first out of bounds item on the side\n            // we are ending on.  The side that we are ending on ultimately\n            // takes priority because we want items later in the layout to move\n            // forward if no in bounds anchors are found.\n            if (layout_state_.layout_direction_ ==\n                ListLayoutDirection::kToStart) {\n              if (out_of_bounds_after) {\n                best_first_find = child;\n              } else if (best_second_find == nullptr) {\n                best_second_find = child;\n              }\n            } else {\n              if (out_of_bounds_before) {\n                best_first_find = child;\n              } else if (best_second_find == nullptr) {\n                best_second_find = child;\n              }\n            }\n          } else {\n            in_bound = child;\n            return true;  // Found in bound. Should break the iteration.\n          }\n        }\n        return false;\n      });\n\n  if (in_bound) {\n    return in_bound;\n  } else if (best_second_find) {\n    return best_second_find;\n  } else if (best_first_find) {\n    return best_first_find;\n  } else {\n    return invalid_match;\n  }\n}\n\nfloat ListLayoutManagerLinear::Fill(ListRecycler& recycler,\n                                    const ListViewState& state) {\n  LIST_LOG << \"---- Filling Start ----\";\n  // max offset we should set is mFastScroll + available\n  const float start = layout_state_.available_;\n  if (layout_state_.scrolling_offset_) {\n    if (layout_state_.available_ < 0) {\n      *(layout_state_.scrolling_offset_) += layout_state_.available_;\n    }\n    RecycleByLayoutState(recycler);\n  }\n\n  float remaining = layout_state_.available_ + layout_state_.extra_;\n  while ((layout_state_.infinite_ || remaining > 0) &&\n         layout_state_.HasMore(state)) {\n    LIST_LOG << \"Layout State before layout item:\\n\"\n             << layout_state_.ToString();\n\n    const float consumed = LayoutChunk(recycler, state);\n    layout_state_.offset_ +=\n        consumed * static_cast<int>(layout_state_.layout_direction_);\n\n    layout_state_.available_ -= consumed;\n    // we keep a separate remaining space because available_ is important for\n    // recycling\n    remaining -= consumed;\n\n    if (layout_state_.scrolling_offset_) {\n      *(layout_state_.scrolling_offset_) += consumed;\n      if (layout_state_.available_ < 0) {\n        *(layout_state_.scrolling_offset_) += layout_state_.available_;\n      }\n      RecycleByLayoutState(recycler);\n    }\n  }\n  LIST_LOG << \"---- Filling End ----\";\n\n  return start - layout_state_.available_;\n}\n\nfloat ListLayoutManagerLinear::LayoutChunk(ListRecycler& recycler,\n                                           const ListViewState& state) {\n  LIST_LOG << \"------ LayoutChunk Start ------\";\n  ListItemViewHolder* item = layout_state_.Next(recycler);\n  DCHECK_RET1(item, 0);\n  LIST_LOG << \"Item To layout. view_id:\" << item->GetView()->id()\n           << \" name:\" << item->GetView()->GetName()\n           << \" pos:\" << item->GetPosition();\n\n  if (layout_state_.layout_direction_ == ListLayoutDirection::kToEnd) {\n    AddItem(item, GetChildCount());\n  } else {\n    AddItem(item, 0);\n  }\n  MeasureConstraint constraint;\n  if (orientation_ == ScrollDirection::kVertical) {\n    constraint.width_mode = MeasureMode::kDefinite;\n    constraint.height_mode = MeasureMode::kIndefinite;\n    constraint.width = GetWidth();\n    if (!item->FullSpan()) {\n      *constraint.width -= GetPaddingLeft() + GetPaddingRight();\n    }\n    *constraint.width = std::max(*constraint.width, 0.f);\n  } else {\n    constraint.height_mode = MeasureMode::kDefinite;\n    constraint.width_mode = MeasureMode::kIndefinite;\n    constraint.height = GetHeight();\n    if (!item->FullSpan()) {\n      *constraint.height -= GetPaddingTop() + GetPaddingBottom();\n    }\n    *constraint.height = std::max(*constraint.height, 0.f);\n  }\n  MeasureResult res = MeasureItem(item, constraint);\n  LIST_LOG << \"Measured an item. res.w:\" << res.width\n           << \" res.h:\" << res.height;\n  float consumed, left = 0.f, top = 0.f;\n  // when layout from start to end, offset is the bottom of the last item\n  // when layout from end to start, offset is the top of the item\n  float main_axis_gap_layout_to_end =\n      item->GetPosition() > 0 ? main_axis_gap_ : 0;\n  float gap = layout_state_.layout_direction_ == ListLayoutDirection::kToEnd\n                  ? main_axis_gap_layout_to_end\n                  : main_axis_gap_;\n  if (orientation_ == ScrollDirection::kVertical) {\n    consumed = res.height + gap;\n    left = 0.f;\n    if (!item->FullSpan()) {\n      left += GetPaddingLeft();\n    }\n    top = layout_state_.layout_direction_ == ListLayoutDirection::kToEnd\n              ? layout_state_.offset_ + gap\n              : layout_state_.offset_ - consumed;\n  } else {\n    consumed = res.width + gap;\n    left = layout_state_.layout_direction_ == ListLayoutDirection::kToEnd\n               ? layout_state_.offset_ + gap\n               : layout_state_.offset_ - consumed;\n    top = 0.f;\n    if (!item->FullSpan()) {\n      top += GetPaddingTop();\n    }\n  }\n  LayoutItem(item, FloatPoint(left, top));\n  LIST_LOG << \"Laid out an item to (\" << left << \",\" << top\n           << \"). Consumed:\" << consumed;\n  LIST_LOG << \"------ LayoutChunk End ------\";\n  return consumed;\n}\n\nfloat ListLayoutManagerLinear::FixLayoutStartGap(float start_offset,\n                                                 ListRecycler& recycler,\n                                                 const ListViewState& state,\n                                                 bool can_offset_children) {\n  float gap = start_offset - orientation_helper_->GetStartAfterPadding();\n  float fix_offset = 0.f;\n  if (gap > 0.f) {\n    // check if we should fix this gap.\n    if (orientation_ == ScrollDirection::kVertical) {\n      fix_offset = -ScrollVerticallyBy(gap, recycler, state);\n    } else {\n      fix_offset = -ScrollHorizontallyBy(gap, recycler, state);\n    }\n  } else {\n    return 0.f;  // nothing to fix\n  }\n  start_offset += fix_offset;\n  if (can_offset_children) {\n    // re-calculate gap, see if we could fix it\n    gap = start_offset - orientation_helper_->GetStartAfterPadding();\n    if (gap > 0.f) {\n      orientation_helper_->OffsetChildren(-gap);\n      return fix_offset - gap;\n    }\n  }\n  return fix_offset;\n}\n\nfloat ListLayoutManagerLinear::FixLayoutEndGap(float end_offset,\n                                               ListRecycler& recycler,\n                                               const ListViewState& state,\n                                               bool can_offset_children) {\n  float gap = orientation_helper_->GetEndAfterPadding() - end_offset;\n  float fix_offset = 0.f;\n  if (gap > 0.f) {\n    if (orientation_ == ScrollDirection::kVertical) {\n      fix_offset = -ScrollVerticallyBy(-gap, recycler, state);\n    } else {\n      fix_offset = -ScrollHorizontallyBy(-gap, recycler, state);\n    }\n  } else {\n    return 0.f;  // nothing to fix\n  }\n  // move offset according to scroll amount\n  end_offset += fix_offset;\n  if (can_offset_children) {\n    // re-calculate gap, see if we could fix it\n    gap = orientation_helper_->GetEndAfterPadding() - end_offset;\n    if (gap > 0.f) {\n      orientation_helper_->OffsetChildren(gap);\n      return gap + fix_offset;\n    }\n  }\n  return fix_offset;\n}\n\nvoid ListLayoutManagerLinear::RecycleByLayoutState(ListRecycler& recycler) {\n  if (!layout_state_.recycle_enabled_) {\n    return;\n  }\n\n  if (layout_state_.layout_direction_ == ListLayoutDirection::kToStart) {\n    RecycleViewsFromEnd(recycler,\n                        layout_state_.scrolling_offset_.value_or(0.f));\n  } else {\n    RecycleViewsFromStart(recycler,\n                          layout_state_.scrolling_offset_.value_or(0.f));\n  }\n}\n\nvoid ListLayoutManagerLinear::RecycleViewsFromEnd(ListRecycler& recycler,\n                                                  float dt) {\n  if (dt <= 0.f) {\n    return;\n  }\n  const float limit = orientation_helper_->GetEnd() - dt;\n  const int child_count = GetChildCount();\n\n  LIST_LOG << \"---- RecycleFromEnd Start ----\";\n  LIST_LOG << \"Children before recycle:\" << children_helper_->ToString();\n\n  for (int i = child_count - 1; i >= 0; i--) {\n    ListItemViewHolder* child = GetChildAt(i);\n    // Find first child whose top is above `limit`.\n    if (orientation_helper_->GetDecoratedStart(child) < limit) {\n      LIST_LOG << \"Recycle from:\" << child_count - 1 << \" to:\" << i;\n\n      // stop here\n      RecycleChildren(recycler, child_count - 1, i);  // (child_count-1, i]\n      LIST_LOG << \"---- RecycleFromEnd End ----\";\n      return;\n    }\n  }\n}\n\nvoid ListLayoutManagerLinear::RecycleViewsFromStart(ListRecycler& recycler,\n                                                    float dt) {\n  if (dt <= 0.f) {\n    return;\n  }\n\n  LIST_LOG << \"---- RecycleFromStart Start ----\";\n  LIST_LOG << \"Children before recycle:\" << children_helper_->ToString();\n\n  const float limit = dt;\n  const int child_count = GetChildCount();\n  for (int i = 0; i < child_count; i++) {\n    ListItemViewHolder* child = GetChildAt(i);\n    // Find first child whose bottom is below `limit`.\n    if (orientation_helper_->GetDecoratedEnd(child) > limit) {\n      LIST_LOG << \"Recycle from:0 to:\" << i;\n\n      // stop here\n      RecycleChildren(recycler, 0, i);  // [0, i)\n      LIST_LOG << \"---- RecycleFromStart End ----\";\n      return;\n    }\n  }\n}\n\nvoid ListLayoutManagerLinear::RecycleChildren(ListRecycler& recycler,\n                                              int start_index, int end_index) {\n  if (start_index == end_index) {\n    return;\n  }\n  if (end_index > start_index) {\n    for (int i = end_index - 1; i >= start_index; i--) {\n      RemoveAndRecycleViewAt(recycler, i);\n    }\n  } else {\n    for (int i = start_index; i > end_index; i--) {\n      RemoveAndRecycleViewAt(recycler, i);\n    }\n  }\n}\n\nfloat ListLayoutManagerLinear::ScrollVerticallyBy(float dy,\n                                                  ListRecycler& recycler,\n                                                  const ListViewState& state) {\n  if (orientation_ != ScrollDirection::kVertical) {\n    return 0;\n  }\n  return ScrollBy(dy, recycler, state);\n}\n\nfloat ListLayoutManagerLinear::ScrollHorizontallyBy(\n    float dx, ListRecycler& recycler, const ListViewState& state) {\n  if (orientation_ != ScrollDirection::kHorizontal) {\n    return 0;\n  }\n  return ScrollBy(dx, recycler, state);\n}\n\nfloat ListLayoutManagerLinear::ScrollBy(float delta, ListRecycler& recycler,\n                                        const ListViewState& state) {\n  LIST_LOG << \"ScrollBy: \" << delta;\n\n  if (delta == 0.f || GetChildCount() == 0) {\n    return 0.f;\n  }\n  layout_state_.recycle_enabled_ = true;\n\n  const ListLayoutDirection direction =\n      delta > 0.f ? ListLayoutDirection::kToEnd : ListLayoutDirection::kToStart;\n  const float absDelta = std::abs(delta);\n  UpdateLayoutState(direction, absDelta, true, state);\n\n  LIST_LOG << \"\\n\" << layout_state_.ToString();\n  const float consumed =\n      layout_state_.scrolling_offset_.value() + Fill(recycler, state);\n  if (consumed < 0.f) {\n    return 0.f;\n  }\n  const float scrolled =\n      absDelta > consumed ? static_cast<int>(direction) * consumed : delta;\n  orientation_helper_->OffsetChildren(-scrolled);\n\n  layout_state_.last_scroll_delta_ = scrolled;\n\n  return scrolled;\n}\n\nbool ListLayoutManagerLinear::SetOrientation(ScrollDirection orientation) {\n  if (ListLayoutManager::SetOrientation(orientation)) {\n    ResetLayoutState();\n    RequestLayout();\n    return true;\n  }\n  return false;\n}\n\nvoid ListLayoutManagerLinear::OnFocusSearchFailed(bool to_end,\n                                                  ListRecycler& recycler,\n                                                  const ListViewState& state) {\n  if (GetChildCount() == 0) {\n    return;\n  }\n\n  ListLayoutDirection layout_dir =\n      to_end ? ListLayoutDirection::kToEnd : ListLayoutDirection::kToStart;\n\n  const float max_scroll =\n      kMaxScrollFactor * orientation_helper_->GetTotalSpace();\n  UpdateLayoutState(layout_dir, max_scroll, false, state);\n  layout_state_.scrolling_offset_.reset();\n  layout_state_.recycle_enabled_ = false;\n  Fill(recycler, state);\n}\n\nvoid ListLayoutManagerLinear::ScrollToPosition(int position, AlignTo align_to) {\n  pending_scroll_position_ = position;\n  pending_scroll_align_to_ = align_to;\n}\n\nFloatSize ListLayoutManagerLinear::ScrollToRect(const FloatRect& rect,\n                                                AlignTo align_to) {\n  const float rect_start = orientation_helper_->GetRectStart(rect);\n  const float rect_end = orientation_helper_->GetRectEnd(rect);\n\n  // Ignore padding because we don't clip the padding.\n  const float bound_start = 0.f;\n  const float bound_end = orientation_helper_->GetEnd();\n\n  bool totally_in_view = rect_start >= bound_start && rect_end <= bound_end;\n  if (totally_in_view && align_to == AlignTo::kNone) {\n    return FloatSize();\n  }\n\n  float offset = 0.f;\n  if (align_to == AlignTo::kMiddle) {\n    offset = (rect_end + rect_start - (bound_end + bound_start)) / 2;\n  } else if (align_to == AlignTo::kStart) {\n    offset = rect_start - bound_start;\n  } else if (align_to == AlignTo::kEnd) {\n    offset = rect_end - bound_end;\n  } else {\n    auto start_offset = rect_start - bound_start;\n    auto end_offset = rect_end - bound_end;\n    if (start_offset < 0) {\n      if (end_offset <= 0) {\n        // item is [partially] above list, but not larger than list.\n        offset = start_offset;\n      }\n    } else if (end_offset > 0) {\n      // item is [partially] under list\n      offset = end_offset;\n    }\n  }\n\n  if (orientation_ == ScrollDirection::kHorizontal) {\n    return {offset, 0.f};\n  } else {\n    return {0.f, offset};\n  }\n}\n\nstd::optional<ListLayoutManagerLinear::PositionAndRelativeRect>\nListLayoutManagerLinear::GetChildPositionByRect(const FloatRect& rect) {\n  ListLayoutManagerLinear::PositionAndRelativeRect res;\n  bool find_value = false;\n  auto* orientation_helper = GetOrientationHelper();\n  children_helper_->ForEach(\n      [&res, &rect, &find_value, orientation_helper](ListItemViewHolder* item) {\n        auto start = orientation_helper->GetDecoratedStart(item);\n        auto end = orientation_helper->GetDecoratedEnd(item);\n        auto rect_start = orientation_helper->GetRectStart(rect);\n        auto rect_end = orientation_helper->GetRectEnd(rect);\n        if (start <= rect_start && rect_end <= end) {\n          find_value = true;\n          res.first = item->GetPosition();\n          // Convert rect relative to item.\n          res.second =\n              FloatRect(rect.x() - item->GetLeft(), rect.y() - item->GetTop(),\n                        rect.width(), rect.height());\n          return true;\n        }\n        return false;\n      });\n  if (find_value) {\n    return res;\n  } else {\n    return std::nullopt;\n  }\n}\n\nbool ListLayoutManagerLinear::HasSpaceToStart(const ListViewState& state) {\n  if (GetChildCount() == 0) {\n    return false;\n  }\n  ListItemViewHolder* first = GetFirstChild();\n  if (first->GetPosition() != 0) {\n    return true;\n  }\n  return orientation_helper_->GetDecoratedStart(first);\n}\n\nbool ListLayoutManagerLinear::HasSpaceToEnd(const ListViewState& state) {\n  if (GetChildCount() == 0) {\n    return false;\n  }\n  ListItemViewHolder* last = GetLastChild();\n  if (last->GetPosition() != static_cast<int>(state.item_count - 1)) {\n    return true;\n  }\n  return orientation_helper_->GetDecoratedEnd(last) >\n         orientation_helper_->GetEnd();\n}\n\nListItemViewHolder* ListLayoutManagerLinear::GetFirstInBoxChild(\n    const ListViewState& state) {\n  ListItemViewHolder* invalid_match = nullptr;\n  ListItemViewHolder* last_out_of_bounds = nullptr;\n  // in bounds means, the start of the child is out of the visible area but the\n  // end of the child is below the start of the list view.\n  ListItemViewHolder* in_bounds = nullptr;\n  ListItemViewHolder* in_box = nullptr;\n  children_helper_->ForEach(\n      [this, &invalid_match, &last_out_of_bounds, &in_bounds, &in_box,\n       item_count = state.item_count, start = 0.f,\n       end = orientation_helper_->GetEnd()](clay::ListItemViewHolder* child) {\n        const int position = child->GetLayoutPosition();\n        const int child_start = orientation_helper_->GetDecoratedStart(child);\n        const int child_end = orientation_helper_->GetDecoratedEnd(child);\n        if (position >= 0 && position < static_cast<int>(item_count)) {\n          if (child->IsRemoved()) {\n            if (invalid_match == nullptr) {\n              invalid_match = child;  // removed item, least preferred\n            }\n            return false;\n          }\n\n          if (child_end <= start) {\n            last_out_of_bounds = child;\n            return false;\n          }\n\n          if (child_start < start && child_end > start) {\n            in_bounds = child;\n\n            // We may still find the in box child, continue the iteration.\n            return false;\n          }\n\n          if (child_start >= start) {\n            in_box = child;\n            return true;\n          }\n\n          if (child_start >= end) {\n            return true;\n          }\n\n          FML_UNREACHABLE();\n        }\n        return false;\n      });\n\n  if (in_box) {\n    return in_box;\n  } else if (in_bounds) {\n    return in_bounds;\n  } else if (last_out_of_bounds) {\n    return last_out_of_bounds;\n  } else {\n    return invalid_match;\n  }\n}\n\nListItemViewHolder* ListLayoutManagerLinear::GetLastInBoxChild(\n    const ListViewState& state) {\n  ListItemViewHolder* invalid_match = nullptr;\n  ListItemViewHolder* last_out_of_bounds = nullptr;\n  ListItemViewHolder* in_bounds = nullptr;\n  ListItemViewHolder* in_box = nullptr;\n  children_helper_->ReversedForEach(\n      [this, &invalid_match, &last_out_of_bounds, &in_bounds, &in_box,\n       item_count = state.item_count, start = 0.f,\n       end = orientation_helper_->GetEnd()](clay::ListItemViewHolder* child) {\n        const int position = child->GetLayoutPosition();\n        const int child_start = orientation_helper_->GetDecoratedStart(child);\n        const int child_end = orientation_helper_->GetDecoratedEnd(child);\n        if (position >= 0 && position < static_cast<int>(item_count)) {\n          if (child->IsRemoved()) {\n            if (invalid_match == nullptr) {\n              invalid_match = child;  // removed item, least preferred\n            }\n            return false;\n          }\n\n          if (child_start >= end) {\n            last_out_of_bounds = child;\n            return false;\n          }\n\n          if (child_end > end && child_start < end) {\n            in_bounds = child;\n\n            // We may still find the in box child, continue the iteration.\n            return false;\n          }\n\n          if (child_end <= end) {\n            in_box = child;\n            return true;\n          }\n\n          if (child_end <= start) {\n            return true;\n          }\n\n          FML_UNREACHABLE();\n        }\n        return false;\n      });\n\n  if (in_box) {\n    return in_box;\n  } else if (in_bounds) {\n    return in_bounds;\n  } else if (last_out_of_bounds) {\n    return last_out_of_bounds;\n  } else {\n    return invalid_match;\n  }\n}\n\nfloat ListLayoutManagerLinear::GetScrollOffset(const ListViewState& state) {\n  if (!HasSpaceToStart(state)) {\n    return 0.f;\n  } else if (!HasSpaceToEnd(state)) {\n    return GetTotalLength(state) - orientation_helper_->GetTotalSpace();\n  }\n\n  ListItemViewHolder* first = GetFirstInBoxChild(state);\n  if (first == nullptr) {\n    return 0.f;\n  }\n  const ListItemLengthCache::Sections& sections = length_cache_->GetSections();\n  float offset = CalculateAccumulatedLength(sections, first->GetPosition(),\n                                            main_axis_gap_);\n  offset -= orientation_helper_->GetDecoratedStart(first);\n  return offset;\n}\n\nfloat ListLayoutManagerLinear::GetTotalLength(const ListViewState& state) {\n  // TODO(Xietong): can have a cache mechanism so that we don't need to\n  // accumulate the length every time.\n  const ListItemLengthCache::Sections& sections = length_cache_->GetSections();\n  return CalculateAccumulatedLength(sections, state.item_count, main_axis_gap_);\n}\n\nvoid ListLayoutManagerLinear::CollectAdjacentPrefetchPositions(\n    int delta_x, int delta_y, int max_limit,\n    const ListViewState& list_view_state,\n    LayoutPrefetchRegistry* layout_prefetch_registry) {\n  int delta = (orientation_ == ScrollDirection::kVertical ? delta_y : delta_x);\n  if (children_helper_->GetChildCount() == 0 || delta == 0) {\n    return;\n  }\n  ListLayoutDirection direction =\n      (delta > 0 ? ListLayoutDirection::kToEnd : ListLayoutDirection::kToStart);\n  UpdateLayoutState(direction, std::abs(delta), true, list_view_state);\n\n  CollectPrefetchPositionForLayoutState(list_view_state, std::abs(delta),\n                                        max_limit, layout_prefetch_registry);\n}\n\nvoid ListLayoutManagerLinear::CollectPrefetchPositionForLayoutState(\n    const ListViewState& list_view_state, int abs_velocity, int max_limit,\n    LayoutPrefetchRegistry* layout_prefetch_registry) {\n  int position = layout_state_.current_position_;\n  int scrolling_offset = layout_state_.scrolling_offset_.value_or(0);\n  while (0 <= position &&\n         position < static_cast<int>(list_view_state.item_count) &&\n         0 < scrolling_offset && max_limit > 0) {\n    max_limit--;\n    // Only when item will come into viewport in next frame, then prefetch it.\n    layout_prefetch_registry->AddPosition(position, scrolling_offset);\n    position += static_cast<int>(layout_state_.item_direction_);\n  }\n}\n\n/*\n *This method is only called when a ListView is nested in another\n *ListView. To be implemented.\n */\nvoid ListLayoutManagerLinear::CollectInitialPrefetchPositions(\n    int adapter_items_count, const ListViewState& list_view_state,\n    LayoutPrefetchRegistry* layout_prefetch_registry) {}\n\nstd::vector<int> ListLayoutManagerLinear::GetVisibleItemPositions() {\n  int start_pos = FindOneVisibleChildPosition(false, false);\n  int last_pos = FindOneVisibleChildPosition(true, false);\n  if (start_pos == ListItemViewHolder::kNoPosition) {\n    return {};\n  }\n  FML_DCHECK(last_pos != ListItemViewHolder::kNoPosition &&\n             start_pos <= last_pos);\n  std::vector<int> positions;\n  positions.reserve((last_pos - start_pos + 1));\n  while (start_pos <= last_pos) {\n    positions.push_back(start_pos);\n    start_pos++;\n  }\n  return positions;\n}\n\nint ListLayoutManagerLinear::FindOneVisibleChildPosition(\n    bool reverse, bool complete_visible) {\n  float start = 0;\n  float end = orientation_helper_->GetEnd();\n  int position = ListItemViewHolder::kNoPosition;\n  auto item_visiter = [this, start, end, complete_visible,\n                       &position](ListItemViewHolder* item) -> bool {\n    if (orientation_helper_->GetDecoratedMeasure(item) != 0.f) {\n      float child_start = orientation_helper_->GetDecoratedStart(item);\n      float child_end = orientation_helper_->GetDecoratedEnd(item);\n      // Intersect, partially visible\n      if (child_start <= end && child_end >= start) {\n        if (complete_visible) {\n          if (child_start >= start && child_end <= end) {\n            position = item->GetPosition();\n            return true;\n          }\n        } else {\n          position = item->GetPosition();\n          return true;\n        }\n      }\n      return false;\n    } else {\n      return false;\n    }\n  };\n  if (reverse) {\n    children_helper_->ReversedForEach(item_visiter);\n  } else {\n    children_helper_->ForEach(item_visiter);\n  }\n  return position;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_layout_manager_linear.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_LINEAR_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_LINEAR_H_\n\n#include <limits>\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_layout_manager.h\"\n\nnamespace clay {\n\nclass ListOrientationHelper;\n\nclass ListLayoutManagerLinear : public ListLayoutManager {\n public:\n  explicit ListLayoutManagerLinear(\n      ScrollDirection direction = kDefaultScrollDirection);\n  virtual ~ListLayoutManagerLinear();\n\n  void OnLayoutChildren(ListRecycler& recycler,\n                        const ListViewState& state) override;\n\n  void OnLayoutCompleted(ListRecycler& recycler,\n                         const ListViewState& state) override;\n\n  float ScrollVerticallyBy(float dy, ListRecycler& recycler,\n                           const ListViewState& state) override;\n  float ScrollHorizontallyBy(float dx, ListRecycler& recycler,\n                             const ListViewState& state) override;\n\n  bool SetOrientation(ScrollDirection direction) override;\n\n  void OnFocusSearchFailed(bool to_end, ListRecycler& recycler,\n                           const ListViewState& state) override;\n  void ScrollToPosition(int position, AlignTo align_to) override;\n  FloatSize ScrollToRect(const FloatRect& rect, AlignTo align_to) override;\n  std::optional<PositionAndRelativeRect> GetChildPositionByRect(\n      const FloatRect& rect) override;\n\n  bool HasSpaceToStart(const ListViewState& state) override;\n  bool HasSpaceToEnd(const ListViewState& state) override;\n  ListItemViewHolder* GetFirstInBoxChild(const ListViewState& state) override;\n  ListItemViewHolder* GetLastInBoxChild(const ListViewState& state) override;\n\n  float GetScrollOffset(const ListViewState& state) override;\n  float GetTotalLength(const ListViewState& state) override;\n\n  void CollectAdjacentPrefetchPositions(\n      int delta_x, int delta_y, int max_limit,\n      const ListViewState& list_view_state,\n      LayoutPrefetchRegistry* layout_prefetch_registry) override;\n  void CollectInitialPrefetchPositions(\n      int adapter_items_count, const ListViewState& list_view_state,\n      LayoutPrefetchRegistry* layout_prefetch_registry) override;\n\n  virtual void CollectPrefetchPositionForLayoutState(\n      const ListViewState& list_view_state, int abs_velocity, int max_limit,\n      LayoutPrefetchRegistry* layout_prefetch_registry);\n\n protected:\n  class LayoutState {\n   public:\n    // Used when there is no limit in how many views can be laid out.\n    bool infinite_;\n    bool recycle_enabled_;\n\n    ListItemDirection item_direction_;\n\n    ListLayoutDirection layout_direction_;\n\n    // Number of pixels that we should fill, in the layout direction.\n    float available_;\n\n    // Pixel offset where layout should start\n    float offset_;\n\n    // Current position on the adapter to get the next item.\n    int current_position_;\n\n    /**\n     * Used when LayoutState is constructed in a scrolling state.\n     * It should be set the amount of scrolling we can make without creating a\n     * new view. Settings this is required for efficient view recycling.\n     */\n    std::optional<float> scrolling_offset_;\n\n    /**\n     * Android use this to pre-layout items that are not yet visible. But\n     * currently we only use this to fix the layout issue when\n     * padding-top/-bottom exists.\n     * The difference with `available_` is that, when recycling, distance laid\n     * out for `extra_` is not considered to avoid recycling visible children.\n     */\n    float extra_ = 0;\n\n    float last_scroll_delta_;\n\n    bool HasMore(const ListViewState& state);\n    /**\n     * @brief Get ListItemViewHolder instance according to `current_position_`\n     * and increase/decrease `current_position_`\n     *\n     * @param recycler Recycler for items reusing\n     * @return ListItemViewHolder*\n     */\n    ListItemViewHolder* Next(ListRecycler& recycler);\n    std::string ToString() const;\n  };\n\n  class AnchorInfo {\n   public:\n    bool valid_;\n    bool layout_from_end_;\n    int position_;\n    float coordinate_;\n\n    static constexpr float kInvalidOffset =\n        std::numeric_limits<float>::lowest();\n\n    void Reset();\n    void AssignCoordinateFromPadding(const ListOrientationHelper& helper,\n                                     const LayoutState& state);\n    void AssignFromChild(ListItemViewHolder* child,\n                         const ListOrientationHelper& helper,\n                         const LayoutState& state);\n  };\n\n  virtual void OnAnchorReady(ListRecycler& recycler, const ListViewState& state,\n                             AnchorInfo& anchor_info, bool is_prime_direction) {\n  }\n\n  LayoutState layout_state_;\n  AnchorInfo anchor_info_;\n\n  int pending_scroll_position_ = ListItemViewHolder::kNoPosition;\n  AlignTo pending_scroll_align_to_ = AlignTo::kNone;\n\n private:\n  void ResetLayoutState();\n  void UpdateLayoutState(ListLayoutDirection layout_direction,\n                         float required_space, bool can_use_existing_space,\n                         const ListViewState& state);\n  void UpdateLayoutStateToFillStart(int item_pos, float offset);\n  void UpdateLayoutStateToFillEnd(int item_pos, float offset);\n\n  float ScrollBy(float delta, ListRecycler& recycler,\n                 const ListViewState& state);\n\n  void UpdateAnchorInfo(ListRecycler& recycler, const ListViewState& state);\n  bool UpdateAnchorInfoFromPendingData(const ListViewState& state);\n  bool UpdateAnchorInfoFromChildren(ListRecycler& recycler,\n                                    const ListViewState& state);\n  virtual ListItemViewHolder* FindReferenceChild(ListRecycler& recycler,\n                                                 const ListViewState& state);\n\n  float Fill(ListRecycler& recycler, const ListViewState& state);\n  virtual float LayoutChunk(ListRecycler& recycler, const ListViewState& state);\n\n  float FixLayoutStartGap(float start_offset, ListRecycler& recycler,\n                          const ListViewState& state, bool can_offset_children);\n\n  float FixLayoutEndGap(float end_offset, ListRecycler& recycler,\n                        const ListViewState& state, bool can_offset_children);\n\n  void RecycleByLayoutState(ListRecycler& recycler);\n  void RecycleViewsFromEnd(ListRecycler& recycler, float dt);\n  void RecycleViewsFromStart(ListRecycler& recycler, float dt);\n  void RecycleChildren(ListRecycler& recycler, int start_index, int end_index);\n\n  std::vector<int> GetVisibleItemPositions() override;\n  int FindOneVisibleChildPosition(bool reverse, bool complete_visible);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_LINEAR_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_layout_manager_staggered_grid.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_layout_manager_staggered_grid.h\"\n\n#include <algorithm>\n#include <deque>\n#include <map>\n#include <numeric>\n#include <optional>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_children_helper.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_orientation_helper.h\"\n#include \"clay/ui/component/list/list_recycler.h\"\n#include \"clay/ui/component/list/macros.h\"\n\nnamespace clay {\n\nnamespace {\nconstexpr int kInvalidSpanID = -1;\nconstexpr float kInvalidLine = std::numeric_limits<float>::lowest();\n}  // namespace\n\n// We keep information about full span items because they may create gaps in\n// the UI.\nclass FullSpanItem {\n public:\n  float GetGapForSpan(int index) const {\n    if (gap_per_span_.size() <= static_cast<size_t>(index)) {\n      return 0;\n    }\n    return gap_per_span_[index];\n  }\n\n  int position_;\n  ListLayoutDirection gap_dir_;\n  std::vector<int> gap_per_span_;\n  // A full span may be laid out in primary direction but may have gaps due to\n  // invalidation of views after it. This is recorded during a reverse scroll\n  // and if view is still on the screen after scroll stops, we have to\n  // recalculate layout\n  bool has_unwanted_gap_after_;\n};\n\nclass LazySpanLookup {\n public:\n  int ForceInvalidateAfter(size_t position) {\n    auto iter = full_span_items_.lower_bound(position);\n    while (iter != full_span_items_.end()) {\n      full_span_items_.erase(iter);\n      iter = full_span_items_.lower_bound(position);\n    }\n    return InvalidateAfter(position);\n  }\n\n  int InvalidateAfter(size_t position) {\n    if (position >= data_.size()) {\n      return ListItemViewHolder::kNoPosition;\n    }\n    int end_position = InvalidateFullSpansAfter(position);\n    if (end_position == ListItemViewHolder::kNoPosition) {\n      std::fill(data_.begin() + position, data_.end(), kInvalidSpanID);\n      return data_.size();\n    } else {\n      // Just invalidate items in between `position` and the next full span\n      // item, or the end of the tracked spans in `data_` if it's not been\n      // lengthened yet.\n      size_t invalidate_to_index =\n          std::min(static_cast<size_t>(end_position + 1), data_.size());\n      std::fill(data_.begin() + position, data_.begin() + invalidate_to_index,\n                kInvalidSpanID);\n      return invalidate_to_index;\n    }\n  }\n\n  int GetSpan(size_t position) const {\n    if (data_.size() <= position) {\n      return kInvalidSpanID;\n    }\n    return data_[position];\n  }\n\n  void SetSpan(size_t position, int index) {\n    EnsureSize(position);\n    data_[position] = index;\n  }\n\n  void AddFullSpanItem(std::unique_ptr<FullSpanItem> full_span_item) {\n    auto iter = full_span_items_.find(full_span_item->position_);\n    FML_DCHECK(iter == full_span_items_.end())\n        << \"two full span has same position\";\n    full_span_items_.insert(\n        {full_span_item->position_, std::move(full_span_item)});\n  }\n\n  FullSpanItem* GetFullSpanItem(size_t position) {\n    if (full_span_items_.empty()) {\n      return nullptr;\n    }\n    auto iter = full_span_items_.find(position);\n    if (iter != full_span_items_.end()) {\n      return iter->second.get();\n    }\n    return nullptr;\n  }\n\n  FullSpanItem* GetFirstFullSpanItemInRange(int min_pos, int max_pos,\n                                            ListLayoutDirection gap_dir,\n                                            bool has_unwanted_gap_after) {\n    if (full_span_items_.empty()) {\n      return nullptr;\n    }\n    auto iter = full_span_items_.lower_bound(min_pos);\n    while (iter != full_span_items_.end()) {\n      if (iter->first >= max_pos) {\n        return nullptr;\n      }\n      if (gap_dir == iter->second->gap_dir_ ||\n          (has_unwanted_gap_after && iter->second->has_unwanted_gap_after_)) {\n        return iter->second.get();\n      }\n      ++iter;\n    }\n    return nullptr;\n  }\n\n  void OffsetForAddition(size_t position_start, int item_count) {\n    if (position_start >= data_.size()) {\n      return;\n    }\n    EnsureSize(position_start + item_count);\n    std::copy_backward(data_.begin() + position_start, data_.end() - item_count,\n                       data_.end());\n    std::fill(data_.begin() + position_start,\n              data_.begin() + position_start + item_count, kInvalidSpanID);\n    OffsetFullSpansForAddition(position_start, item_count);\n  }\n\n  void OffsetForRemoval(size_t position_start, int item_count) {\n    if (position_start >= data_.size()) {\n      return;\n    }\n\n    EnsureSize(position_start + item_count);\n    std::copy(data_.begin() + position_start + item_count, data_.end(),\n              data_.begin() + position_start);\n    std::fill(data_.end() - item_count, data_.end(), kInvalidSpanID);\n    OffsetFullSpansForRemoval(position_start, item_count);\n  }\n\n  void Clear() {\n    data_.assign(data_.size(), kInvalidSpanID);\n    full_span_items_.clear();\n  }\n\n private:\n  int InvalidateFullSpansAfter(int position) {\n    full_span_items_.erase(position);\n    auto iter = full_span_items_.lower_bound(position);\n    if (iter != full_span_items_.end()) {\n      int position = iter->first;\n      full_span_items_.erase(iter);\n      return position;\n    } else {\n      return ListItemViewHolder::kNoPosition;\n    }\n  }\n\n  void EnsureSize(size_t target_position) {\n    size_t old_size = data_.size();\n    if (old_size <= target_position) {\n      // growth strategy\n      size_t new_size = target_position + old_size + 1;\n      data_.resize(new_size);\n      std::fill(data_.begin() + old_size, data_.end(), kInvalidSpanID);\n    }\n  }\n\n  void OffsetFullSpansForAddition(int position_start, int item_count) {\n    if (full_span_items_.empty()) {\n      return;\n    }\n\n    auto iter = full_span_items_.begin();\n    std::vector<std::unique_ptr<FullSpanItem>> pending_items;\n    while (iter != full_span_items_.end()) {\n      if (iter->first < position_start) {\n        ++iter;\n      } else {\n        iter->second->position_ += item_count;\n        pending_items.emplace_back(std::move(iter->second));\n        iter = full_span_items_.erase(iter);\n      }\n    }\n\n    for (auto& item : pending_items) {\n      full_span_items_.insert({item->position_, std::move(item)});\n    }\n  }\n\n  void OffsetFullSpansForRemoval(int position_start, int item_count) {\n    if (full_span_items_.empty()) {\n      return;\n    }\n    int end = position_start + item_count;\n\n    auto iter = full_span_items_.begin();\n    std::vector<std::unique_ptr<FullSpanItem>> pending_items;\n    while (iter != full_span_items_.end()) {\n      if (iter->first >= position_start) {\n        if (iter->first >= end) {\n          iter->second->position_ -= item_count;\n          pending_items.emplace_back(std::move(iter->second));\n        }\n        iter = full_span_items_.erase(iter);\n      } else {\n        ++iter;\n      }\n    }\n\n    for (auto& item : pending_items) {\n      full_span_items_.insert({item->position_, std::move(item)});\n    }\n  }\n\n  std::vector<int> data_;\n  std::map<int, std::unique_ptr<FullSpanItem>> full_span_items_;\n};\n\nclass Span {\n  using LayoutParam = ListLayoutManagerStaggeredGrid::LayoutParam;\n\n public:\n  explicit Span(int index, ListLayoutManagerStaggeredGrid* manager)\n      : index_(index), manager_(manager) {}\n\n  /*\n   * return cached_start_ if present, otherwise calculate and return\n   * cached_start_ if items_ is not empty, otherwise return default_line\n   */\n  float GetStartLine(float default_line = 0.f) {\n    if (cached_start_ != kInvalidLine) {\n      return cached_start_;\n    } else if (!items_.empty()) {\n      CalculateStart();\n      return cached_start_;\n    }\n    return default_line;\n  }\n\n  /*\n   * return cached_end_ if present, otherwise calculate and return cached_end_\n   * if items_ is not empty, otherwise return default_line\n   */\n  float GetEndLine(float default_line = 0.f) {\n    if (cached_end_ != kInvalidLine) {\n      return cached_end_;\n    } else if (!items_.empty()) {\n      CalculateEnd();\n      return cached_end_;\n    } else {\n      return default_line;\n    }\n  }\n\n  // prepend item to self and invalidate cached_start_( and cached_end_ if\n  // needed)\n  void PrependToSpan(ListItemViewHolder* item) {\n    LayoutParam& param = manager_->GetLayoutParam(item);\n    param.span_ = this;\n    cached_start_ = kInvalidLine;\n    if (items_.empty()) {\n      cached_end_ = kInvalidLine;\n    }\n    if (item->IsRemoved() || item->NeedsUpdate()) {\n      deleted_size_ += manager_->orientation_helper_->GetDecoratedMeasure(item);\n    }\n\n    items_.emplace_front(item);\n  }\n\n  // append item to self and invalidate cached_end_( and cached_start_ if\n  // needed)\n  void AppendToSpan(ListItemViewHolder* item) {\n    LayoutParam& param = manager_->GetLayoutParam(item);\n    param.span_ = this;\n    cached_end_ = kInvalidLine;\n    if (items_.empty()) {\n      cached_start_ = kInvalidLine;\n    }\n    if (item->IsRemoved() || item->NeedsUpdate()) {\n      deleted_size_ += manager_->orientation_helper_->GetDecoratedMeasure(item);\n    }\n\n    items_.emplace_back(item);\n  }\n\n  void OnOffset(float dt) {\n    if (cached_end_ != kInvalidLine) {\n      cached_end_ += dt;\n    }\n    if (cached_start_ != kInvalidLine) {\n      cached_start_ += dt;\n    }\n  }\n\n  void SetLine(float line) { cached_end_ = cached_start_ = line; }\n\n  void PopStart() {\n    FML_DCHECK(!items_.empty());\n    ListItemViewHolder* item = items_.front();\n    items_.pop_front();\n    LayoutParam& param = manager_->GetLayoutParam(item);\n    param.span_ = nullptr;\n    if (item->IsRemoved() || item->NeedsUpdate()) {\n      deleted_size_ -= manager_->orientation_helper_->GetDecoratedMeasure(item);\n    }\n    if (items_.empty()) {\n      cached_end_ = kInvalidLine;\n    }\n    cached_start_ = kInvalidLine;\n  }\n\n  void PopEnd() {\n    FML_DCHECK(!items_.empty());\n    ListItemViewHolder* item = items_.back();\n    items_.pop_back();\n    LayoutParam& param = manager_->GetLayoutParam(item);\n    param.span_ = nullptr;\n    if (item->IsRemoved() || item->NeedsUpdate()) {\n      deleted_size_ -= manager_->orientation_helper_->GetDecoratedMeasure(item);\n    }\n    if (items_.empty()) {\n      cached_start_ = kInvalidLine;\n    }\n    cached_end_ = kInvalidLine;\n  }\n\n  std::vector<int> GetVisibleItemPositions() {\n    int start = FindOneVisibleChildIndex(0, items_.size(), false);\n    int last = FindOneVisibleChildIndex(items_.size() - 1, -1, false);\n\n    if (start == ListItemViewHolder::kNoPosition) {\n      return {};\n    }\n    FML_DCHECK(last != ListItemViewHolder::kNoPosition && start <= last);\n    std::vector<int> positions;\n    positions.reserve(last - start + 1);\n    while (start <= last) {\n      positions.push_back(items_[start]->GetPosition());\n      start++;\n    }\n    return positions;\n  }\n\n  float GetDeletedSize() const {\n    // TODO(hanhaoshen): Fix wrong deleted_size_ during relayout.\n    return 0.f;\n    // return deleted_size_;\n  }\n\n  void CacheReferenceLineAndClear(bool reverse_layout, float offset) {\n    float reference;\n    if (reverse_layout) {\n      reference = GetEndLine(kInvalidLine);\n    } else {\n      reference = GetStartLine(kInvalidLine);\n    }\n    Clear();\n    if (reference == kInvalidLine) {\n      return;\n    }\n    if (!reverse_layout &&\n        reference > manager_->orientation_helper_->GetStartAfterPadding()) {\n      return;\n    }\n    if (reverse_layout &&\n        reference < manager_->orientation_helper_->GetEndAfterPadding()) {\n      return;\n    }\n    if (offset != ListLayoutManagerStaggeredGrid::AnchorInfo::kInvalidOffset) {\n      reference += offset;\n    }\n    SetLine(reference);\n  }\n\n  void Clear() {\n    items_.clear();\n    SetLine(kInvalidLine);\n    deleted_size_ = 0.f;\n  }\n\n  std::string DebugStatus() {\n    std::stringstream s;\n    s << \"index: \" << index_ << \"\\n\";\n    s << \"items: \" << items_.size() << \"\\n\";\n    s << \"deleted: \" << deleted_size_ << \"\\n\";\n    s << \"cached_start: \" << cached_start_ << \"\\n\";\n    s << \"cached_end: \" << cached_end_ << \"\\n\";\n    return s.str();\n  }\n\n  size_t size() const { return items_.size(); }\n  int index() const { return index_; }\n\n private:\n  void CalculateStart() {\n    FML_DCHECK(!items_.empty());\n\n    ListItemViewHolder* item = items_.front();\n    const LayoutParam& param = manager_->GetLayoutParam(item);\n    cached_start_ = manager_->orientation_helper_->GetDecoratedStart(item);\n    if (!param.full_span_) {\n      return;\n    }\n    FullSpanItem* full_span_item =\n        manager_->lazy_span_lookup_->GetFullSpanItem(item->GetPosition());\n    if (full_span_item != nullptr &&\n        full_span_item->gap_dir_ == ListLayoutDirection::kToStart) {\n      cached_start_ -= full_span_item->GetGapForSpan(index_);\n    }\n  }\n\n  void CalculateEnd() {\n    FML_DCHECK(!items_.empty());\n\n    ListItemViewHolder* item = items_.back();\n    cached_end_ = manager_->orientation_helper_->GetDecoratedEnd(item);\n    const LayoutParam& param = manager_->GetLayoutParam(item);\n    if (!param.full_span_) {\n      return;\n    }\n    FullSpanItem* full_span_item =\n        manager_->lazy_span_lookup_->GetFullSpanItem(item->GetPosition());\n    if (full_span_item != nullptr &&\n        full_span_item->gap_dir_ == ListLayoutDirection::kToEnd) {\n      cached_end_ += full_span_item->GetGapForSpan(index_);\n    }\n  }\n\n  int FindOneVisibleChildIndex(int from_index, int to_index,\n                               bool complete_visible) {\n    ListOrientationHelper* orientation_helper =\n        manager_->GetOrientationHelper();\n    float start = orientation_helper->GetStartAfterPadding();\n    float end = orientation_helper->GetEndAfterPadding();\n    int diff = to_index > from_index ? 1 : -1;\n    for (int i = from_index; i != to_index; i += diff) {\n      ListItemViewHolder* item = items_[i];\n      if (orientation_helper->GetDecoratedMeasure(item) == 0.f) {\n        continue;\n      }\n      float child_start = orientation_helper->GetDecoratedStart(item);\n      float child_end = orientation_helper->GetDecoratedEnd(item);\n      // Intersect, partially visible\n      if (child_start <= end && child_end >= start) {\n        if (complete_visible) {\n          if (child_start <= start && child_end >= end) {\n            return i;\n          }\n        } else {\n          return i;\n        }\n      }\n    }\n    return ListItemViewHolder::kNoPosition;\n  }\n\n  const int index_;\n\n  ListLayoutManagerStaggeredGrid* manager_;\n  float cached_start_ = kInvalidLine;\n  float cached_end_ = kInvalidLine;\n  float deleted_size_ = 0.f;\n  std::deque<ListItemViewHolder*> items_;\n};\n\nvoid ListLayoutManagerStaggeredGrid::AnchorInfo::Reset() {\n  position_ = ListItemViewHolder::kNoPosition;\n  offset_ = kInvalidOffset;\n  layout_from_end_ = false;\n  invalidate_offsets_ = false;\n  valid_ = false;\n  span_reference_lines_.assign(span_reference_lines_.size(), kInvalidSpanID);\n}\n\nvoid ListLayoutManagerStaggeredGrid::AnchorInfo::SaveSpanReferenceLines(\n    std::vector<std::unique_ptr<Span>>& spans) {\n  size_t span_count = spans.size();\n  if (span_reference_lines_.size() < span_count) {\n    span_reference_lines_.resize(span_count);\n  }\n  for (size_t i = 0; i < span_count; ++i) {\n    span_reference_lines_[i] = spans[i]->GetStartLine(kInvalidLine);\n  }\n}\n\nstd::string ListLayoutManagerStaggeredGrid::AnchorInfo::ToString() const {\n#if (DEBUG_LIST)\n  std::stringstream s;\n  s << \"position :\" << position_ << \"\\t\";\n  s << \"offset :\" << offset_ << \"\\t\";\n  s << \"layout_from_end : \" << layout_from_end_ << \"\\t\";\n  s << \"invalidate_offsets_ : \" << invalidate_offsets_ << \"\\t\";\n  s << \"valid_ : \" << valid_ << \"\\t\";\n  return s.str();\n#else\n  return \"\";\n#endif\n}\n\nbool ListLayoutManagerStaggeredGrid::LayoutState::HasMore(\n    const ListViewState& state) {\n  return current_position_ >= 0 &&\n         static_cast<size_t>(current_position_) < state.item_count;\n}\n\nListItemViewHolder* ListLayoutManagerStaggeredGrid::LayoutState::Next(\n    ListRecycler& recycler) {\n  ListItemViewHolder* item = recycler.GetItemForPosition(current_position_);\n  current_position_ += static_cast<int>(item_direction_);\n  return item;\n}\n\nstd::string ListLayoutManagerStaggeredGrid::LayoutState::ToString() const {\n#if (DEBUG_LIST)\n  std::stringstream res;\n  res << \"recycle: \" << (recycle_ ? \"true\" : \"false\") << \"\\n\";\n  res << \"infinite: \" << (infinite_ ? \"true\" : \"false\") << \"\\n\";\n  res << \"available: \" << std::to_string(available_) << \"\\n\";\n  res << \"offset: \" << std::to_string(offset_) << \"\\n\";\n\n  res << \"current_position: \" << std::to_string(current_position_) << \"\\n\";\n\n  res << \"item_direction: \"\n      << (item_direction_ == ListItemDirection::kToHead ? \"Head\" : \"Tail\")\n      << \"\\n\";\n\n  res << \"layout_direction: \"\n      << (layout_direction_ == ListLayoutDirection::kToStart ? \"Start\" : \"End\")\n      << \"\\n\";\n\n  res << \"start_line: \" << start_line_ << \"\\n\";\n  res << \"end_line: \" << end_line_;\n\n  return res.str();\n#else\n  return \"\";\n#endif\n}\n\nListLayoutManagerStaggeredGrid::ListLayoutManagerStaggeredGrid(\n    int span_count, ScrollDirection orientation)\n    : ListLayoutManager(orientation) {\n  lazy_span_lookup_ = std::make_unique<LazySpanLookup>();\n  SetSpanCount(span_count);\n  anchor_info_.Reset();\n}\n\nListLayoutManagerStaggeredGrid::~ListLayoutManagerStaggeredGrid() = default;\n\nvoid ListLayoutManagerStaggeredGrid::SetSpanCount(int span_count) {\n  if (span_count == span_count_) {\n    return;\n  }\n  FML_CHECK(span_count > 0) << \"Span count must greater than 0!\";\n  lazy_span_lookup_->Clear();\n  span_count_ = span_count;\n  remaining_spans_.resize(span_count);\n  spans_.reserve(span_count);\n  for (int i = 0; i < span_count; ++i) {\n    spans_.emplace_back(std::make_unique<Span>(i, this));\n  }\n  RequestLayout();\n}\n\nbool ListLayoutManagerStaggeredGrid::SetOrientation(\n    ScrollDirection orientation) {\n  if (ListLayoutManager::SetOrientation(orientation)) {\n    RequestLayout();\n    return true;\n  }\n  return false;\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnFocusSearchFailed(\n    bool to_end, ListRecycler& recycler, const ListViewState& state) {\n  if (GetChildCount() == 0) {\n    return;\n  }\n  int next_position;\n  if (to_end) {\n    next_position = GetLastChildPosition() + 1;\n    SetLayoutStateDirection(ListLayoutDirection::kToEnd);\n  } else {\n    next_position = GetFirstChildPosition() - 1;\n    SetLayoutStateDirection(ListLayoutDirection::kToStart);\n  }\n  LIST_LOG << \"OnFocusSearchFailed try to reach \" << next_position;\n  UpdateLayoutState(next_position, state);\n  Fill(recycler, state);\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnScrollStateChange(\n    Scrollable::ScrollStatus state) {\n  if (state == Scrollable::ScrollStatus::kIdle) {\n    CheckForGaps();\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::ScrollToPosition(int position,\n                                                      AlignTo align_to) {\n  pending_scroll_position_ = position;\n  pending_scroll_align_to_ = align_to;\n  LIST_LOG << \"ScrollToPosition \" << position;\n}\n\nFloatSize ListLayoutManagerStaggeredGrid::ScrollToRect(const FloatRect& rect,\n                                                       AlignTo align_to) {\n  const float rect_start = orientation_helper_->GetRectStart(rect);\n  const float rect_end = orientation_helper_->GetRectEnd(rect);\n\n  // Ignore padding because we don't clip the padding.\n  const float bound_start = 0.f;\n  const float bound_end = orientation_helper_->GetEnd();\n\n  bool totally_in_view = rect_start >= bound_start && rect_end <= bound_end;\n  if (totally_in_view && align_to == AlignTo::kNone) {\n    return FloatSize();\n  }\n\n  float offset = 0.f;\n  if (align_to == AlignTo::kMiddle) {\n    offset = (rect_end + rect_start - (bound_end + bound_start)) / 2;\n  } else if (align_to == AlignTo::kStart) {\n    offset = rect_start - bound_start;\n  } else if (align_to == AlignTo::kEnd) {\n    offset = rect_end - bound_end;\n  } else {\n    auto start_offset = rect_start - bound_start;\n    auto end_offset = rect_end - bound_end;\n    if (start_offset < 0) {\n      if (end_offset <= 0) {\n        // item is [partially] above list, but not larger than list.\n        offset = start_offset;\n      }\n    } else if (end_offset > 0) {\n      // item is [partially] under list\n      offset = end_offset;\n    }\n  }\n\n  if (orientation_ == ScrollDirection::kHorizontal) {\n    return {offset, 0.f};\n  } else {\n    return {0.f, offset};\n  }\n}\n\nbool ListLayoutManagerStaggeredGrid::HasSpaceToStart(\n    const ListViewState& state) {\n  if (GetChildCount() == 0) {\n    return false;\n  }\n  ListItemViewHolder* first = GetFirstChild();\n  if (first->GetPosition() != 0) {\n    return true;\n  }\n  return orientation_helper_->GetDecoratedStart(first);\n}\n\nbool ListLayoutManagerStaggeredGrid::HasSpaceToEnd(const ListViewState& state) {\n  if (GetChildCount() == 0) {\n    return false;\n  }\n  ListItemViewHolder* last = GetLastChild();\n  if (last->GetPosition() != static_cast<int>(state.item_count - 1)) {\n    return true;\n  }\n  return orientation_helper_->GetDecoratedEnd(last) >\n         orientation_helper_->GetEnd();\n}\n\nListItemViewHolder* ListLayoutManagerStaggeredGrid::GetFirstInBoxChild(\n    const ListViewState& state) {\n  ListItemViewHolder* invalid_match = nullptr;\n  ListItemViewHolder* last_out_of_bounds = nullptr;\n  // in bounds means, the start of the child is out of the visible area but the\n  // end of the child is below the start of the list view.\n  ListItemViewHolder* in_bounds = nullptr;\n  ListItemViewHolder* in_box = nullptr;\n  children_helper_->ForEach(\n      [this, &invalid_match, &last_out_of_bounds, &in_bounds, &in_box,\n       item_count = state.item_count, start = 0.f,\n       end = orientation_helper_->GetEnd()](clay::ListItemViewHolder* child) {\n        const int position = child->GetLayoutPosition();\n        const int child_start = orientation_helper_->GetDecoratedStart(child);\n        const int child_end = orientation_helper_->GetDecoratedEnd(child);\n        if (position >= 0 && position < static_cast<int>(item_count)) {\n          if (child->IsRemoved()) {\n            if (invalid_match == nullptr) {\n              invalid_match = child;  // removed item, least preferred\n            }\n            return false;\n          }\n\n          if (child_end <= start) {\n            last_out_of_bounds = child;\n            return false;\n          }\n\n          if (child_start < start && child_end > start) {\n            in_bounds = child;\n\n            // We may still find the in box child, continue the iteration.\n            return false;\n          }\n\n          if (child_start >= start) {\n            in_box = child;\n            return true;\n          }\n\n          if (child_start >= end) {\n            return true;\n          }\n\n          FML_UNREACHABLE();\n        }\n        return false;\n      });\n\n  if (in_box) {\n    return in_box;\n  } else if (in_bounds) {\n    return in_bounds;\n  } else if (last_out_of_bounds) {\n    return last_out_of_bounds;\n  } else {\n    return invalid_match;\n  }\n}\n\nListItemViewHolder* ListLayoutManagerStaggeredGrid::GetLastInBoxChild(\n    const ListViewState& state) {\n  ListItemViewHolder* invalid_match = nullptr;\n  ListItemViewHolder* last_out_of_bounds = nullptr;\n  ListItemViewHolder* in_bounds = nullptr;\n  ListItemViewHolder* in_box = nullptr;\n  children_helper_->ReversedForEach(\n      [this, &invalid_match, &last_out_of_bounds, &in_bounds, &in_box,\n       item_count = state.item_count, start = 0.f,\n       end = orientation_helper_->GetEnd()](clay::ListItemViewHolder* child) {\n        const int position = child->GetLayoutPosition();\n        const int child_start = orientation_helper_->GetDecoratedStart(child);\n        const int child_end = orientation_helper_->GetDecoratedEnd(child);\n        if (position >= 0 && position < static_cast<int>(item_count)) {\n          if (child->IsRemoved()) {\n            if (invalid_match == nullptr) {\n              invalid_match = child;  // removed item, least preferred\n            }\n            return false;\n          }\n\n          if (child_start >= end) {\n            last_out_of_bounds = child;\n            return false;\n          }\n\n          if (child_end > end && child_start < end) {\n            in_bounds = child;\n\n            // We may still find the in box child, continue the iteration.\n            return false;\n          }\n\n          if (child_end <= end) {\n            in_box = child;\n            return true;\n          }\n\n          if (child_end <= start) {\n            return true;\n          }\n\n          FML_UNREACHABLE();\n        }\n        return false;\n      });\n\n  if (in_box) {\n    return in_box;\n  } else if (in_bounds) {\n    return in_bounds;\n  } else if (last_out_of_bounds) {\n    return last_out_of_bounds;\n  } else {\n    return invalid_match;\n  }\n}\n\nfloat ListLayoutManagerStaggeredGrid::GetScrollOffset(\n    const ListViewState& state) {\n  if (!HasSpaceToStart(state)) {\n    return 0.f;\n  } else if (!HasSpaceToEnd(state)) {\n    return GetTotalLength(state) - orientation_helper_->GetTotalSpace();\n  }\n\n  ListItemViewHolder* first = GetFirstInBoxChild(state);\n  if (first == nullptr) {\n    return 0.f;\n  }\n  float offset = CalculateAccumulatedLength(first->GetPosition());\n  offset -= orientation_helper_->GetDecoratedStart(first);\n  return offset;\n}\n\nfloat ListLayoutManagerStaggeredGrid::GetTotalLength(\n    const ListViewState& state) {\n  return CalculateAccumulatedLength(state.item_count);\n}\n\nfloat ListLayoutManagerStaggeredGrid::CalculateAccumulatedLength(\n    int to_position) {\n  if (to_position <= 0) {\n    return 0.f;\n  }\n\n  FML_DCHECK(to_position > 0);\n  int target_span_idx = lazy_span_lookup_->GetSpan(to_position);\n  std::vector<int> known_item(span_count_, 0);\n  std::vector<float> heights(span_count_, 0.f);\n  int unassigned_span_item = 0;\n  for (int i = 0; i < to_position; ++i) {\n    int cur_span = lazy_span_lookup_->GetSpan(i);\n    bool is_full_span = (lazy_span_lookup_->GetFullSpanItem(i) != nullptr);\n    std::optional<int> length = length_cache_->GetLength(i);\n    if (length.has_value() &&\n        length.value() != ListItemLengthCache::kInvalidLength) {\n      if (is_full_span) {\n        std::for_each(known_item.begin(), known_item.end(),\n                      [](int& counter) { counter++; });\n        std::for_each(heights.begin(), heights.end(),\n                      [&length, this](float& h) {\n                        h += length.value() + main_axis_gap_;\n                      });\n      } else {\n        if (cur_span == kInvalidSpanID) {\n          unassigned_span_item += 1;\n        } else {\n          known_item[cur_span] += 1;\n          heights[cur_span] += length.value();\n          heights[cur_span] += main_axis_gap_;\n        }\n      }\n    } else {\n      unassigned_span_item += (is_full_span ? span_count_ : 1);\n    }\n  }\n  if (unassigned_span_item == 0 && target_span_idx != kInvalidSpanID) {\n    return heights[target_span_idx];\n  } else {\n    float item_avg_height =\n        std::accumulate(heights.begin(), heights.end(), 0.f) /\n        std::accumulate(known_item.begin(), known_item.end(), 0);\n    float estimated_height =\n        item_avg_height * unassigned_span_item / span_count_;\n    if (target_span_idx != kInvalidSpanID) {\n      return (known_item[target_span_idx] + estimated_height);\n    } else {\n      return (*std::max_element(heights.begin(), heights.end()) +\n              estimated_height);\n    }\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnLayoutChildren(\n    ListRecycler& recycler, const ListViewState& state) {\n  if (state.structure_changed) {\n    auto first_child = GetFirstChild();\n    if (first_child) {\n      lazy_span_lookup_->InvalidateAfter(first_child->GetPosition());\n    }\n  }\n  OnLayoutChildrenInternal(recycler, state, true);\n}\n\nvoid ListLayoutManagerStaggeredGrid::CollectAdjacentPrefetchPositions(\n    int delta_x, int delta_y, int max_limit,\n    const ListViewState& list_view_state,\n    LayoutPrefetchRegistry* layout_prefetch_registry) {\n  int delta = (orientation_ == ScrollDirection::kVertical ? delta_y : delta_x);\n  if (GetChildCount() == 0 || delta == 0) {\n    return;\n  }\n  int remaining_span_count = span_count_;\n  while (layout_state_.HasMore(list_view_state) && remaining_span_count > 0 &&\n         max_limit > 0) {\n    max_limit--;\n    int position = layout_state_.current_position_;\n    layout_prefetch_registry->AddPosition(\n        position,\n        std::max(0, static_cast<int>(GetScrollOffset(list_view_state))));\n\n    int span_size = (lazy_span_lookup_->GetFullSpanItem(position) != nullptr)\n                        ? span_count_\n                        : 1;\n    remaining_span_count -= span_size;\n    layout_state_.current_position_ +=\n        static_cast<int>(layout_state_.item_direction_);\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnLayoutChildrenInternal(\n    ListRecycler& recycler, const ListViewState& state,\n    bool should_check_for_gaps) {\n  if (pending_scroll_position_ != ListItemViewHolder::kNoPosition &&\n      state.item_count == 0) {\n    RemoveAndRecycleAllViews(recycler);\n    anchor_info_.Reset();\n    return;\n  }\n\n  bool recalculate_anchor =\n      !anchor_info_.valid_ ||\n      pending_scroll_position_ != ListItemViewHolder::kNoPosition;\n  if (recalculate_anchor) {\n    anchor_info_.Reset();\n    ResolveShouldLayoutReverse();\n    anchor_info_.layout_from_end_ = should_reverse_layout_;\n    UpdateAnchorInfo(state);\n    anchor_info_.valid_ = true;\n  }\n\n  if (pending_scroll_position_ == ListItemViewHolder::kNoPosition) {\n    if (anchor_info_.layout_from_end_ != last_layout_from_end_) {\n      lazy_span_lookup_->Clear();\n      anchor_info_.invalidate_offsets_ = true;\n    }\n  }\n\n  if (GetChildCount() > 0) {\n    if (anchor_info_.invalidate_offsets_) {\n      // clear cells'layout data in origin spans and re-layout from computed\n      // anchor_info_.offset_ as cached_start_&cached_end_\n      for (auto& span : spans_) {\n        span->Clear();\n        if (anchor_info_.offset_ != AnchorInfo::kInvalidOffset) {\n          span->SetLine(anchor_info_.offset_);\n        }\n      }\n    } else {\n      if (recalculate_anchor || anchor_info_.span_reference_lines_.empty()) {\n        for (auto& span : spans_) {\n          // clear cells'layout data in origin spans and re-layout from\n          // cached_start_&cached_end_ plus anchor_info_.offset_\n          span->CacheReferenceLineAndClear(should_reverse_layout_,\n                                           anchor_info_.offset_);\n        }\n        anchor_info_.SaveSpanReferenceLines(spans_);\n      } else {\n        for (int i = 0; i < span_count_; ++i) {\n          spans_[i]->Clear();\n          spans_[i]->SetLine(anchor_info_.span_reference_lines_[i]);\n        }\n      }\n    }\n  }\n\n  // update recycler\n  RemoveAndScrapChildren(recycler);\n  layout_state_.recycle_ = false;\n  laid_out_invalid_full_span_ = false;\n  UpdateMeasureSpecs(orientation_helper_->GetSecondaryTotalSpace());\n  // set anchor_info_.position_ as start point of layout\n  UpdateLayoutState(anchor_info_.position_, state);\n\n  // start fill\n  if (anchor_info_.layout_from_end_) {\n    // Layout start.\n    SetLayoutStateDirection(ListLayoutDirection::kToStart);\n    Fill(recycler, state);\n    // Layout end.\n    SetLayoutStateDirection(ListLayoutDirection::kToEnd);\n    layout_state_.current_position_ =\n        anchor_info_.position_ +\n        static_cast<int>(layout_state_.item_direction_);\n    Fill(recycler, state);\n  } else {\n    // Layout end.\n    SetLayoutStateDirection(ListLayoutDirection::kToEnd);\n    Fill(recycler, state);\n    // Layout start.\n    SetLayoutStateDirection(ListLayoutDirection::kToStart);\n    layout_state_.current_position_ =\n        anchor_info_.position_ +\n        static_cast<int>(layout_state_.item_direction_);\n    Fill(recycler, state);\n  }\n\n  // Fix Gaps\n  if (GetChildCount() > 0) {\n    FixLayoutStartGap(recycler, state, true);\n    FixLayoutEndGap(recycler, state, false);\n  }\n\n  bool has_gaps = false;\n  if (should_check_for_gaps) {\n    if (GetChildCount() > 0 &&\n        (laid_out_invalid_full_span_ || HasGapsToFix() != nullptr)) {\n      has_gaps = CheckForGaps();\n    }\n  }\n  last_layout_from_end_ = anchor_info_.layout_from_end_;\n  if (has_gaps) {\n    anchor_info_.Reset();\n    OnLayoutChildrenInternal(recycler, state, false);\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnLayoutCompleted(\n    ListRecycler& recycler, const ListViewState& state) {\n  ListLayoutManager::OnLayoutCompleted(recycler, state);\n\n  anchor_info_.Reset();\n  pending_scroll_position_ = ListItemViewHolder::kNoPosition;\n}\n\nfloat ListLayoutManagerStaggeredGrid::ScrollVerticallyBy(\n    float dy, ListRecycler& recycler, const ListViewState& state) {\n  if (orientation_ != ScrollDirection::kVertical) {\n    return 0;\n  }\n  return ScrollBy(dy, recycler, state);\n}\n\nfloat ListLayoutManagerStaggeredGrid::ScrollHorizontallyBy(\n    float dx, ListRecycler& recycler, const ListViewState& state) {\n  if (orientation_ != ScrollDirection::kHorizontal) {\n    return 0;\n  }\n  return ScrollBy(dx, recycler, state);\n}\n\nvoid ListLayoutManagerStaggeredGrid::OffsetChildren(float dx, float dy) {\n  // Can only be one direction to offset\n  FML_DCHECK(dx == 0.f || dy == 0.f);\n  ListLayoutManager::OffsetChildren(dx, dy);\n  for (auto& span : spans_) {\n    span->OnOffset(dx + dy);\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnRemoveItem(ListItemViewHolder* item) {\n  layout_params_.erase(item->GetView()->id());\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnItemsChanged(BaseListView* list_view) {\n  lazy_span_lookup_->Clear();\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnItemsAdded(BaseListView* list_view,\n                                                  int position_start,\n                                                  int item_count) {\n  ListLayoutManager::OnItemsAdded(list_view, position_start, item_count);\n  HandleUpdate(position_start, item_count, ListAdapterHelper::Type::kInsert);\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnItemsRemoved(BaseListView* list_view,\n                                                    int position_start,\n                                                    int item_count) {\n  ListLayoutManager::OnItemsRemoved(list_view, position_start, item_count);\n  HandleUpdate(position_start, item_count, ListAdapterHelper::Type::kRemove);\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnItemsUpdated(BaseListView* list_view,\n                                                    int position_start,\n                                                    int item_count) {\n  ListLayoutManager::OnItemsUpdated(list_view, position_start, item_count);\n  HandleUpdate(position_start, item_count, ListAdapterHelper::Type::kChange);\n}\n\nvoid ListLayoutManagerStaggeredGrid::OnItemsMoved(BaseListView* list_view,\n                                                  int from, int to,\n                                                  int item_count) {\n  ListLayoutManager::OnItemsMoved(list_view, from, to, item_count);\n  HandleUpdate(from, to, ListAdapterHelper::Type::kMove);\n}\n\nvoid ListLayoutManagerStaggeredGrid::PrepareLayoutStateForDelta(\n    float delta, const ListViewState& state) {\n  int reference_child_position;\n  ListLayoutDirection layout_dir;\n  if (delta > 0.f) {\n    layout_dir = ListLayoutDirection::kToEnd;\n    reference_child_position = GetLastChildPosition();\n  } else {\n    layout_dir = ListLayoutDirection::kToStart;\n    reference_child_position = GetFirstChildPosition();\n  }\n  layout_state_.recycle_ = true;\n  UpdateLayoutState(reference_child_position, state);\n  SetLayoutStateDirection(layout_dir);\n  layout_state_.current_position_ =\n      reference_child_position +\n      static_cast<int>(layout_state_.item_direction_);\n  layout_state_.available_ = std::abs(delta);\n}\n\nfloat ListLayoutManagerStaggeredGrid::ScrollBy(float delta,\n                                               ListRecycler& recycler,\n                                               const ListViewState& state) {\n  if (delta == 0.f || GetChildCount() == 0) {\n    return 0.f;\n  }\n  PrepareLayoutStateForDelta(delta, state);\n  const float consumed = Fill(recycler, state);\n  float available = layout_state_.available_;\n  float total_scroll;\n  if (available < consumed) {\n    total_scroll = delta;\n  } else if (delta < 0) {\n    total_scroll = -consumed;\n  } else {\n    total_scroll = consumed;\n  }\n  LIST_LOG << \"Scroll Asked: \" << delta << \" Scrolled: \" << total_scroll;\n\n  orientation_helper_->OffsetChildren(-total_scroll);\n  last_layout_from_end_ = should_reverse_layout_;\n  layout_state_.available_ = 0.f;\n  Recycle(recycler);\n  return total_scroll;\n}\n\nfloat ListLayoutManagerStaggeredGrid::Fill(ListRecycler& recycler,\n                                           const ListViewState& state) {\n  remaining_spans_.assign(span_count_, true);\n  // The target position we are trying to reach.\n  float target_line;\n\n  if (layout_state_.infinite_) {\n    if (layout_state_.layout_direction_ == ListLayoutDirection::kToEnd) {\n      target_line = std::numeric_limits<float>::max();\n    } else {\n      target_line = std::numeric_limits<float>::lowest();\n    }\n  } else {\n    if (layout_state_.layout_direction_ == ListLayoutDirection::kToEnd) {\n      target_line = layout_state_.end_line_ + layout_state_.available_;\n    } else {\n      target_line = layout_state_.start_line_ - layout_state_.available_;\n    }\n  }\n  int remaining_span_count =\n      span_count_ - CalculateNumberOfUnavailableSpans(\n                        layout_state_.layout_direction_, target_line);\n  LIST_LOG << \"---- Filling Start ----\\n\"\n           << \"target line: \" << target_line << \", direction: \"\n           << static_cast<int>(layout_state_.layout_direction_)\n           << \", remaining_span_count: \" << remaining_span_count\n           << \"\\nstate: \" << layout_state_.ToString();\n\n  int default_new_view_line = orientation_helper_->GetStartAfterPadding();\n  bool added = false;\n  bool laying_out_to_end =\n      layout_state_.layout_direction_ == ListLayoutDirection::kToEnd;\n  while (layout_state_.HasMore(state) &&\n         (layout_state_.infinite_ || remaining_span_count != 0)) {\n    if (IsItemFullSpan(layout_state_.current_position_) &&\n        remaining_span_count < span_count_) {\n      break;\n    }\n\n    ListItemViewHolder* item = layout_state_.Next(recycler);\n    FML_DCHECK(item);\n\n    int position = item->GetPosition();\n    int span_index = lazy_span_lookup_->GetSpan(position);\n    LayoutParam& param = GetLayoutParam(item);\n    param.full_span_ = item->FullSpan();\n    LIST_LOG << \"start fill one child at pos: \" << position\n             << \", id: \" << item->GetView()->id();\n\n    Span* current_span;\n    bool assign_span = span_index == kInvalidSpanID;\n    if (assign_span) {\n      current_span = param.full_span_ ? spans_.front().get() : GetNextSpan();\n      LIST_LOG << \" newly assign to span \" << current_span->index();\n\n      lazy_span_lookup_->SetSpan(position, current_span->index());\n    } else {\n      LIST_LOG << \"already exist in span \" << span_index;\n      current_span = spans_[span_index].get();\n    }\n\n    param.span_ = current_span;\n\n    if (laying_out_to_end) {\n      AddItem(item, GetChildCount());\n    } else {\n      AddItem(item, 0);\n    }\n\n    MeasureConstraint constraint;\n    if (param.full_span_) {\n      if (orientation_ == ScrollDirection::kVertical) {\n        constraint.width_mode = MeasureMode::kDefinite;\n        constraint.height_mode = MeasureMode::kIndefinite;\n        constraint.width = full_size_spec_;\n      } else {\n        constraint.height_mode = MeasureMode::kDefinite;\n        constraint.width_mode = MeasureMode::kIndefinite;\n        constraint.height = full_size_spec_;\n      }\n    } else {\n      if (orientation_ == ScrollDirection::kVertical) {\n        constraint.width_mode = MeasureMode::kDefinite;\n        constraint.height_mode = MeasureMode::kIndefinite;\n        constraint.width = size_per_span_;\n      } else {\n        constraint.height_mode = MeasureMode::kDefinite;\n        constraint.width_mode = MeasureMode::kIndefinite;\n        constraint.height = size_per_span_;\n      }\n    }\n    MeasureItem(item, constraint);\n\n    int start, end;\n    if (laying_out_to_end) {\n      start = param.full_span_\n                  ? GetMaxEnd(default_new_view_line)\n                  : current_span->GetEndLine(default_new_view_line);\n      if (position >= span_count_) {\n        // not first row\n        start += main_axis_gap_;\n      }\n      end = start + orientation_helper_->GetDecoratedMeasure(item);\n      if (assign_span && param.full_span_) {\n        std::unique_ptr<FullSpanItem> full_span_item =\n            CreateFullSpanItemFromEnd(start);\n        full_span_item->gap_dir_ = ListLayoutDirection::kToStart;\n        full_span_item->position_ = position;\n        lazy_span_lookup_->AddFullSpanItem(std::move(full_span_item));\n      }\n    } else {\n      end = param.full_span_\n                ? GetMinStart(default_new_view_line)\n                : current_span->GetStartLine(default_new_view_line);\n      // when fill from end, always consider gaps\n      end -= main_axis_gap_;\n      start = end - orientation_helper_->GetDecoratedMeasure(item);\n\n      if (assign_span && param.full_span_) {\n        std::unique_ptr<FullSpanItem> full_span_item =\n            CreateFullSpanItemFromStart(end);\n\n        full_span_item->gap_dir_ = ListLayoutDirection::kToEnd;\n        full_span_item->position_ = position;\n        lazy_span_lookup_->AddFullSpanItem(std::move(full_span_item));\n      }\n    }\n\n    // check if this item may create gaps in the future\n    if (param.full_span_ &&\n        layout_state_.item_direction_ == ListItemDirection::kToHead) {\n      if (assign_span) {\n        laid_out_invalid_full_span_ = true;\n      } else {\n        bool has_invalid_gap;\n        if (layout_state_.layout_direction_ == ListLayoutDirection::kToEnd) {\n          has_invalid_gap = !CheckAllEndsEqual();\n        } else {\n          has_invalid_gap = !CheckAllStartsEqual();\n        }\n        if (has_invalid_gap) {\n          FullSpanItem* item = lazy_span_lookup_->GetFullSpanItem(position);\n          if (item != nullptr) {\n            item->has_unwanted_gap_after_ = true;\n          }\n          laid_out_invalid_full_span_ = true;\n        }\n      }\n    }\n\n    AttachItemToSpans(item, param, laying_out_to_end);\n    int other_start = 0, other_end;\n    ALLOW_UNUSED_LOCAL(other_end);\n\n    // full_span has no padding\n    if (!param.full_span_) {\n      other_start = orientation_helper_->GetSecondaryStartAfterPadding();\n      other_start += current_span->index() * (size_per_span_ + cross_axis_gap_);\n    }\n    other_end =\n        other_start + orientation_helper_->GetSecondaryDecoratedMeasure(item);\n\n    FloatPoint layout_position;\n    if (orientation_ == ScrollDirection::kVertical) {\n      layout_position = FloatPoint(other_start, start);\n    } else {\n      layout_position = FloatPoint(start, other_start);\n    }\n\n    LIST_LOG << \"layout item to \" << layout_position.x() << \"\\t\"\n             << layout_position.y();\n    LayoutItem(item, layout_position);\n    if (param.full_span_) {\n      remaining_span_count -= CalculateNumberOfUnavailableSpans(\n          layout_state_.layout_direction_, target_line);\n    } else {\n      remaining_span_count -= UpdateRemainingSpans(\n          current_span, layout_state_.layout_direction_, target_line);\n    }\n    Recycle(recycler);\n    added = true;\n  }\n\n  LIST_LOG << \"---- Filling End ----\";\n\n  if (!added) {\n    Recycle(recycler);\n  }\n  float diff;\n  if (!laying_out_to_end) {\n    int min_start = GetMinStart(orientation_helper_->GetStartAfterPadding());\n    diff = orientation_helper_->GetStartAfterPadding() - min_start;\n  } else {\n    int max_end = GetMaxEnd(orientation_helper_->GetEndAfterPadding());\n    diff = max_end - orientation_helper_->GetEndAfterPadding();\n  }\n\n  float consumed = diff > 0 ? std::min(layout_state_.available_, diff) : 0;\n\n  LIST_LOG << \"Filling with \" << consumed << \"\\tdiff \" << diff << \"\\nminstart: \"\n           << GetMinStart(orientation_helper_->GetStartAfterPadding())\n           << \" start \" << orientation_helper_->GetStartAfterPadding()\n           << \"\\nmaxend: \"\n           << GetMaxEnd(orientation_helper_->GetEndAfterPadding()) << \" end \"\n           << orientation_helper_->GetEndAfterPadding();\n  return consumed;\n}\n\nvoid ListLayoutManagerStaggeredGrid::FixLayoutStartGap(\n    ListRecycler& recycler, const ListViewState& state,\n    bool can_offset_children) {\n  static float kMaxMinLine = std::numeric_limits<float>::max();\n  float min_start_line = GetMinStart(kMaxMinLine);\n  if (min_start_line == kMaxMinLine) {\n    return;\n  }\n  float gap = min_start_line - orientation_helper_->GetStartAfterPadding();\n  if (gap <= 0.f) {\n    return;\n  }\n\n  gap -= ScrollBy(gap, recycler, state);\n  if (can_offset_children && gap > 0.f) {\n    orientation_helper_->OffsetChildren(gap);\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::FixLayoutEndGap(ListRecycler& recycler,\n                                                     const ListViewState& state,\n                                                     bool can_offset_children) {\n  float max_end_line = GetMaxEnd(kInvalidLine);\n  if (max_end_line == kInvalidLine) {\n    return;\n  }\n  float gap = orientation_helper_->GetEndAfterPadding() - max_end_line;\n  if (gap <= 0.f) {\n    return;\n  }\n\n  gap += ScrollBy(-gap, recycler, state);\n  if (can_offset_children && gap > 0.f) {\n    orientation_helper_->OffsetChildren(gap);\n  }\n}\n\nbool ListLayoutManagerStaggeredGrid::CheckForGaps() {\n  if (GetChildCount() == 0) {\n    return false;\n  }\n  int min_pos = GetFirstChildPosition();\n  int max_pos = GetLastChildPosition();\n\n  if (min_pos == 0) {\n    ListItemViewHolder* item = HasGapsToFix();\n    if (item != nullptr) {\n      lazy_span_lookup_->Clear();\n      LIST_LOG << \"Has gap relayout\";\n      RequestLayout();\n      return true;\n    }\n  }\n\n  if (!laid_out_invalid_full_span_) {\n    return false;\n  }\n  ListLayoutDirection invalid_gap_dir = ListLayoutDirection::kToEnd;\n  FullSpanItem* invalid_item = lazy_span_lookup_->GetFirstFullSpanItemInRange(\n      min_pos, max_pos + 1, invalid_gap_dir, true);\n  if (invalid_item == nullptr) {\n    laid_out_invalid_full_span_ = false;\n    lazy_span_lookup_->ForceInvalidateAfter(max_pos + 1);\n    return false;\n  }\n  FullSpanItem* valid_item = lazy_span_lookup_->GetFirstFullSpanItemInRange(\n      min_pos, invalid_item->position_,\n      static_cast<ListLayoutDirection>(static_cast<int>(invalid_gap_dir) * -1),\n      true);\n  if (valid_item == nullptr) {\n    lazy_span_lookup_->ForceInvalidateAfter(invalid_item->position_);\n  } else {\n    lazy_span_lookup_->ForceInvalidateAfter(valid_item->position_ + 1);\n  }\n  LIST_LOG << \"Has gap relayout\";\n  RequestLayout();\n  return true;\n}\n\nListItemViewHolder* ListLayoutManagerStaggeredGrid::HasGapsToFix() {\n  int child_count = GetChildCount();\n  remaining_spans_.assign(span_count_, true);\n\n  bool preferred_span_dir = orientation_ == ScrollDirection::kVertical;\n  int idx = -1;\n  ListItemViewHolder* ret = nullptr;\n  children_helper_->ForEach([this, &idx, &ret, child_count, preferred_span_dir](\n                                ListItemViewHolder* item) -> bool {\n    LayoutParam& param = GetLayoutParam(item);\n    ++idx;\n    if (remaining_spans_[param.span_->index()]) {\n      remaining_spans_[param.span_->index()] = false;\n    }\n    if (param.full_span_) {\n      return false;\n    }\n\n    if (idx + 1 != child_count) {\n      ListItemViewHolder* next_item = GetChildAt(idx + 1);\n      bool compare_spans = false;\n      int cur_start = orientation_helper_->GetDecoratedStart(item);\n      int next_start = orientation_helper_->GetDecoratedStart(next_item);\n      if (cur_start > next_start) {\n        ret = item;\n        return true;\n      } else if (cur_start == next_start) {\n        compare_spans = true;\n      }\n\n      if (compare_spans) {\n        LayoutParam& next_param = GetLayoutParam(next_item);\n        if (param.span_->index() >= next_param.span_->index() ==\n            preferred_span_dir) {\n          ret = item;\n          return true;\n        }\n      }\n    }\n    return false;\n  });\n  return ret;\n}\n\nstd::unique_ptr<FullSpanItem>\nListLayoutManagerStaggeredGrid::CreateFullSpanItemFromEnd(int new_item_top) {\n  std::unique_ptr<FullSpanItem> item = std::make_unique<FullSpanItem>();\n  item->gap_per_span_.resize(span_count_);\n  for (int i = 0; i < span_count_; ++i) {\n    item->gap_per_span_[i] = new_item_top - spans_[i]->GetEndLine(new_item_top);\n  }\n  return item;\n}\n\nstd::unique_ptr<FullSpanItem>\nListLayoutManagerStaggeredGrid::CreateFullSpanItemFromStart(\n    int new_item_bottom) {\n  std::unique_ptr<FullSpanItem> item = std::make_unique<FullSpanItem>();\n  item->gap_per_span_.resize(span_count_);\n  for (int i = 0; i < span_count_; ++i) {\n    item->gap_per_span_[i] =\n        spans_[i]->GetStartLine(new_item_bottom) - new_item_bottom;\n  }\n  return item;\n}\n\nvoid ListLayoutManagerStaggeredGrid::UpdateAnchorInfo(\n    const ListViewState& state) {\n  if (UpdateAnchorInfoFromPendingData(state)) {\n    return;\n  }\n  UpdateAnchorInfoFromChildren(state);\n}\n\nbool ListLayoutManagerStaggeredGrid::UpdateAnchorInfoFromPendingData(\n    const ListViewState& state) {\n  if (pending_scroll_position_ == ListItemViewHolder::kNoPosition) {\n    return false;\n  }\n\n  if (pending_scroll_position_ < 0 ||\n      pending_scroll_position_ >= static_cast<int>(state.item_count)) {\n    pending_scroll_position_ = ListItemViewHolder::kNoPosition;\n    return false;\n  }\n\n  // Here we just ensure the target item is visible. The next operations are\n  // handled in |ListScroller::ScrollImmediately|\n  ListItemViewHolder* item =\n      children_helper_->FindChildByPosition(pending_scroll_position_);\n  if (item != nullptr) {  // item is visible\n    pending_scroll_position_ = ListItemViewHolder::kNoPosition;\n    return false;\n  } else {\n    anchor_info_.position_ = pending_scroll_position_;\n    ListLayoutDirection direction =\n        CalculateScrollDirectionForPosition(anchor_info_.position_);\n    anchor_info_.layout_from_end_ = ListLayoutDirection::kToEnd == direction;\n    switch (pending_scroll_align_to_) {\n      case AlignTo::kNone:\n      case AlignTo::kMiddle:\n        anchor_info_.offset_ =\n            anchor_info_.layout_from_end_ ? orientation_helper_->GetEnd() : 0;\n        break;\n      case AlignTo::kStart:\n        anchor_info_.offset_ = 0;\n        break;\n      case AlignTo::kEnd:\n        anchor_info_.offset_ = orientation_helper_->GetEnd();\n        break;\n      default:\n        break;\n    }\n    anchor_info_.invalidate_offsets_ = true;\n  }\n  return true;\n}\n\nbool ListLayoutManagerStaggeredGrid::UpdateAnchorInfoFromChildren(\n    const ListViewState& state) {\n  anchor_info_.position_ =\n      FindReferenceChildPosition(state.item_count, last_layout_from_end_);\n  anchor_info_.offset_ = AnchorInfo::kInvalidOffset;\n  return true;\n}\n\nListLayoutDirection\nListLayoutManagerStaggeredGrid::CalculateScrollDirectionForPosition(\n    int position) {\n  if (GetChildCount() == 0) {\n    return ListLayoutDirection::kToEnd;\n  }\n  float first_child_pos = GetFirstChildPosition();\n  if (position < first_child_pos) {\n    return ListLayoutDirection::kToStart;\n  } else {\n    return ListLayoutDirection::kToEnd;\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::UpdateMeasureSpecs(float total_space) {\n  size_per_span_ = total_space / span_count_;\n  full_size_spec_ = total_space;\n}\n\n// attach item to specified span or all spans, invalidate cached_start_ and\n// cached_end_\nvoid ListLayoutManagerStaggeredGrid::AttachItemToSpans(ListItemViewHolder* item,\n                                                       LayoutParam& param,\n                                                       bool laying_out_to_end) {\n  if (laying_out_to_end) {\n    if (param.full_span_) {\n      AppendItemToAllSpans(item);\n    } else {\n      param.span_->AppendToSpan(item);\n    }\n  } else {\n    if (param.full_span_) {\n      PrependItemToAllSpans(item);\n    } else {\n      param.span_->PrependToSpan(item);\n    }\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::AppendItemToAllSpans(\n    ListItemViewHolder* item) {\n  // traverse in reverse so that we end up assigning full span items to 0\n  for (int i = span_count_ - 1; i >= 0; --i) {\n    spans_[i]->AppendToSpan(item);\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::PrependItemToAllSpans(\n    ListItemViewHolder* item) {\n  // traverse in reverse so that we end up assigning full span items to 0\n  for (int i = span_count_ - 1; i >= 0; --i) {\n    spans_[i]->PrependToSpan(item);\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::HandleUpdate(int position_start,\n                                                  int item_count_or_to_position,\n                                                  ListAdapterHelper::Type cmd) {\n  // Note: currently every update will trigger layout on list, so we don't check\n  // whether should relayout in here\n  int affected_range_start, affected_range_end;\n  ALLOW_UNUSED_LOCAL(affected_range_end);\n  if (cmd == ListAdapterHelper::Type::kMove) {\n    if (position_start < item_count_or_to_position) {\n      affected_range_end = item_count_or_to_position + 1;\n      affected_range_start = position_start;\n    } else {\n      affected_range_end = position_start + 1;\n      affected_range_start = item_count_or_to_position;\n    }\n  } else {\n    affected_range_start = position_start;\n    affected_range_end = position_start + item_count_or_to_position;\n  }\n\n  lazy_span_lookup_->InvalidateAfter(affected_range_start);\n  switch (cmd) {\n    case ListAdapterHelper::Type::kInsert:\n      lazy_span_lookup_->OffsetForAddition(position_start,\n                                           item_count_or_to_position);\n      break;\n    case ListAdapterHelper::Type::kRemove:\n      lazy_span_lookup_->OffsetForRemoval(position_start,\n                                          item_count_or_to_position);\n      break;\n    case ListAdapterHelper::Type::kMove:\n      lazy_span_lookup_->OffsetForRemoval(position_start, 1);\n      lazy_span_lookup_->OffsetForAddition(item_count_or_to_position, 1);\n      break;\n    default:\n      break;\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::Recycle(ListRecycler& recycler) {\n  if (!layout_state_.recycle_ || layout_state_.infinite_) {\n    return;\n  }\n\n  if (layout_state_.available_ == 0.f) {\n    if (layout_state_.layout_direction_ == ListLayoutDirection::kToStart) {\n      RecycleFromEnd(recycler, layout_state_.end_line_);\n    } else {\n      RecycleFromStart(recycler, layout_state_.start_line_);\n    }\n  } else {\n    if (layout_state_.layout_direction_ == ListLayoutDirection::kToStart) {\n      float scrolled =\n          layout_state_.start_line_ - GetMaxStart(layout_state_.start_line_);\n      float line = layout_state_.end_line_;\n      if (scrolled >= 0.f) {\n        line -= std::min(scrolled, layout_state_.available_);\n      }\n      RecycleFromEnd(recycler, line);\n    } else {\n      float scrolled =\n          GetMinEnd(layout_state_.end_line_) - layout_state_.end_line_;\n      float line = layout_state_.start_line_;\n      if (scrolled >= 0.f) {\n        line += std::min(scrolled, layout_state_.available_);\n      }\n      RecycleFromStart(recycler, line);\n    }\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::RecycleFromStart(ListRecycler& recycler,\n                                                      float line) {\n  while (GetChildCount() > 0) {\n    ListItemViewHolder* item = GetFirstChild();\n    if (orientation_helper_->GetDecoratedEnd(item) <= line) {\n      const LayoutParam& param = GetLayoutParam(item);\n      // Don't recycle the last View in a span not to lose span's start/end\n      // lines\n      if (param.full_span_) {\n        for (const auto& span : spans_) {\n          if (span->size() == 1) {\n            return;\n          }\n        }\n        for (auto& span : spans_) {\n          span->PopStart();\n        }\n      } else {\n        FML_DCHECK(param.span_);\n        if (param.span_->size() == 1) {\n          return;\n        }\n        param.span_->PopStart();\n      }\n      RemoveAndRecycleViewAt(recycler, 0);\n    } else {\n      return;  // done for iterate\n    }\n  }\n}\n\nvoid ListLayoutManagerStaggeredGrid::RecycleFromEnd(ListRecycler& recycler,\n                                                    float line) {\n  while (GetChildCount() > 0) {\n    ListItemViewHolder* item = GetLastChild();\n    if (orientation_helper_->GetDecoratedStart(item) >= line) {\n      const LayoutParam& param = GetLayoutParam(item);\n      // Don't recycle the last View in a span not to lose span's start/end\n      // lines\n      if (param.full_span_) {\n        for (const auto& span : spans_) {\n          if (span->size() == 1) {\n            return;\n          }\n        }\n        for (auto& span : spans_) {\n          span->PopEnd();\n        }\n      } else {\n        FML_DCHECK(param.span_);\n        if (param.span_->size() == 1) {\n          return;\n        }\n        param.span_->PopEnd();\n      }\n\n      RemoveAndRecycleViewAt(recycler, GetChildCount() - 1);\n    } else {\n      return;  // done for iterate\n    }\n  }\n}\n\n// Return span will being placed the cell\nSpan* ListLayoutManagerStaggeredGrid::GetNextSpan() {\n  bool prefer_last_span =\n      (layout_state_.layout_direction_ == ListLayoutDirection::kToStart);\n  int start_idx = 0, end_idx = span_count_, diff = 1;\n  Span* span = nullptr;\n  if (prefer_last_span) {\n    start_idx = span_count_ - 1;\n    end_idx = -1;\n    diff = -1;\n  }\n\n  if (layout_state_.layout_direction_ == ListLayoutDirection::kToEnd) {\n    float min_line = std::numeric_limits<float>::max();\n    float default_line = orientation_helper_->GetStartAfterPadding();\n    for (; start_idx != end_idx; start_idx += diff) {\n      Span* other = spans_[start_idx].get();\n      float other_line = other->GetEndLine(default_line);\n      if (other_line < min_line) {\n        span = other;\n        min_line = other_line;\n      }\n    }\n  } else {\n    float max_line = std::numeric_limits<float>::lowest();\n    float default_line = orientation_helper_->GetEndAfterPadding();\n    for (; start_idx != end_idx; start_idx += diff) {\n      Span* other = spans_[start_idx].get();\n      float other_line = other->GetStartLine(default_line);\n      if (other_line > max_line) {\n        span = other;\n        max_line = other_line;\n      }\n    }\n  }\n  FML_DCHECK(span != nullptr);\n  return span;\n}\n\nstd::vector<int> ListLayoutManagerStaggeredGrid::GetVisibleItemPositions() {\n  std::vector<int> visible_positions;\n  for (auto& span : spans_) {\n    std::vector<int> positions = span->GetVisibleItemPositions();\n    visible_positions.insert(visible_positions.end(), positions.begin(),\n                             positions.end());\n  }\n  return visible_positions;\n}\n\nbool ListLayoutManagerStaggeredGrid::CheckAllEndsEqual() {\n  float end = spans_.front()->GetEndLine(kInvalidLine);\n  for (auto& span : spans_) {\n    if (span->GetEndLine(kInvalidLine) != end) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool ListLayoutManagerStaggeredGrid::CheckAllStartsEqual() {\n  float start = spans_.front()->GetStartLine(kInvalidLine);\n  for (auto& span : spans_) {\n    if (span->GetStartLine(kInvalidLine) != start) {\n      return false;\n    }\n  }\n  return true;\n}\n\nint ListLayoutManagerStaggeredGrid::FindReferenceChildPosition(int item_count,\n                                                               bool reversed) {\n  // Different with android, We prefer a visible child.\n  int visible_ret = ListItemViewHolder::kNoPosition;\n  int invisible_ret = ListItemViewHolder::kNoPosition;\n  // If no child be found at all, default position is 0.\n  int ret = 0;\n  auto finder = [&visible_ret, &invisible_ret,\n                 item_count](ListItemViewHolder* item) -> bool {\n    int position = item->GetPosition();\n    if (position >= 0 && position < item_count) {\n      if (item->GetViewVisible()) {\n        visible_ret = position;\n        return true;\n      } else if (invisible_ret == ListItemViewHolder::kNoPosition) {\n        invisible_ret = position;\n      }\n      return false;\n    }\n    return false;\n  };\n  if (!reversed) {\n    children_helper_->ForEach(finder);\n  } else {\n    children_helper_->ReversedForEach(finder);\n  }\n  if (visible_ret != ListItemViewHolder::kNoPosition) {\n    ret = visible_ret;\n  } else if (invisible_ret != ListItemViewHolder::kNoPosition) {\n    ret = invisible_ret;\n  }\n  return ret;\n}\n\nint ListLayoutManagerStaggeredGrid::GetLastChildPosition() {\n  FML_DCHECK(GetChildCount() != 0);\n  return GetLastChild()->GetPosition();\n}\n\nint ListLayoutManagerStaggeredGrid::GetFirstChildPosition() {\n  FML_DCHECK(GetChildCount() != 0);\n  return GetFirstChild()->GetPosition();\n}\n\n// find min start in spans_\nfloat ListLayoutManagerStaggeredGrid::GetMinStart(float default_line) {\n  FML_DCHECK(!spans_.empty());\n  float min_start = spans_.front()->GetStartLine(default_line);\n  for (auto& span : spans_) {\n    float span_start = span->GetStartLine(default_line);\n    if (span_start < min_start) {\n      min_start = span_start;\n    }\n  }\n  return min_start;\n}\n\nfloat ListLayoutManagerStaggeredGrid::GetMaxStart(float default_line) {\n  FML_DCHECK(!spans_.empty());\n  float max_start = spans_.front()->GetStartLine(default_line);\n  for (auto& span : spans_) {\n    float span_start = span->GetStartLine(default_line);\n    if (span_start > max_start) {\n      max_start = span_start;\n    }\n  }\n  return max_start;\n}\n\nfloat ListLayoutManagerStaggeredGrid::GetMinEnd(float default_line) {\n  FML_DCHECK(!spans_.empty());\n  float min_end = spans_.front()->GetEndLine(default_line);\n  for (auto& span : spans_) {\n    float span_end = span->GetEndLine(default_line);\n    if (span_end < min_end) {\n      min_end = span_end;\n    }\n  }\n  return min_end;\n}\n\n// find max end in spans_\nfloat ListLayoutManagerStaggeredGrid::GetMaxEnd(float default_line) {\n  FML_DCHECK(!spans_.empty());\n  float max_end = spans_.front()->GetEndLine(default_line);\n  for (auto& span : spans_) {\n    float span_end = span->GetEndLine(default_line);\n    if (span_end > max_end) {\n      max_end = span_end;\n    }\n  }\n  return max_end;\n}\n\nint ListLayoutManagerStaggeredGrid::CalculateNumberOfUnavailableSpans(\n    ListLayoutDirection layout_dir, float target_line) {\n  int updated = 0;\n  for (auto& span : spans_) {\n    if (span->size() == 0) {\n      continue;\n    }\n    updated += UpdateRemainingSpans(span.get(), layout_dir, target_line);\n  }\n  return updated;\n}\n\n// check whether there is available space in span with layout_dir & target_line,\n// and set remaining_spans_\nint ListLayoutManagerStaggeredGrid::UpdateRemainingSpans(\n    Span* span, ListLayoutDirection layout_dir, float target_line) {\n  float deleted_size = span->GetDeletedSize();\n  FML_DCHECK(span->size() != 0);\n  if (layout_dir == ListLayoutDirection::kToStart) {\n    float line = span->GetStartLine();\n    if (line + deleted_size <= target_line && remaining_spans_[span->index()]) {\n      // set false meaning current span is full\n      remaining_spans_[span->index()] = false;\n      return 1;\n    }\n  } else {\n    float line = span->GetEndLine();\n    if (line - deleted_size >= target_line && remaining_spans_[span->index()]) {\n      remaining_spans_[span->index()] = false;\n      return 1;\n    }\n  }\n  return 0;\n}\n\nvoid ListLayoutManagerStaggeredGrid::UpdateLayoutState(\n    int anchor_position, const ListViewState& state) {\n  layout_state_.available_ = 0;\n  layout_state_.current_position_ = anchor_position;\n\n  layout_state_.end_line_ = orientation_helper_->GetEnd();\n  layout_state_.start_line_ = 0.f;\n\n  layout_state_.recycle_ = true;\n  layout_state_.infinite_ = false;\n}\n\nListLayoutManagerStaggeredGrid::LayoutParam&\nListLayoutManagerStaggeredGrid::GetLayoutParam(ListItemViewHolder* item) {\n  FML_DCHECK(item);\n  int view_id = item->GetView()->id();\n  auto iter = layout_params_.find(view_id);\n\n  if (iter == layout_params_.end()) {\n    LayoutParam param = {false, nullptr};\n    iter = layout_params_.insert({view_id, param}).first;\n  }\n  return iter->second;\n}\n\nvoid ListLayoutManagerStaggeredGrid::SetLayoutStateDirection(\n    ListLayoutDirection direction) {\n  layout_state_.layout_direction_ = direction;\n  layout_state_.item_direction_ = direction == ListLayoutDirection::kToStart\n                                      ? ListItemDirection::kToHead\n                                      : ListItemDirection::kToTail;\n}\n\nvoid ListLayoutManagerStaggeredGrid::ResolveShouldLayoutReverse() {\n  // TODO(hanhaoshen) : more support to ltr and reversed layout\n  should_reverse_layout_ = false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_layout_manager_staggered_grid.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_STAGGERED_GRID_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_STAGGERED_GRID_H_\n\n#include <limits>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/list/list_adapter_helper.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/list/list_item_length_cache.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_layout_manager.h\"\n\nnamespace clay {\n\nclass Span;\nclass FullSpanItem;\nclass LazySpanLookup;\n\nclass ListLayoutManagerStaggeredGrid : public ListLayoutManager {\n public:\n  explicit ListLayoutManagerStaggeredGrid(\n      int span_count, ScrollDirection orientation = kDefaultScrollDirection);\n  ~ListLayoutManagerStaggeredGrid() override;\n\n  void OnLayoutChildren(ListRecycler& recycler,\n                        const ListViewState& state) override;\n\n  void OnLayoutCompleted(ListRecycler& recycler,\n                         const ListViewState& state) override;\n\n  float ScrollVerticallyBy(float dy, ListRecycler& recycler,\n                           const ListViewState& state) override;\n\n  float ScrollHorizontallyBy(float dx, ListRecycler& recycler,\n                             const ListViewState& state) override;\n\n  void OffsetChildren(float dx, float dy) override;\n\n  void OnRemoveItem(ListItemViewHolder* item) override;\n\n  void OnItemsChanged(BaseListView* list_view) override;\n\n  void OnItemsAdded(BaseListView* list_view, int position_start,\n                    int item_count) override;\n  void OnItemsRemoved(BaseListView* list_view, int position_start,\n                      int item_count) override;\n  void OnItemsUpdated(BaseListView* list_view, int position_start,\n                      int item_count) override;\n  void OnItemsMoved(BaseListView* list_view, int from, int to,\n                    int item_count) override;\n\n  int GetSpanCount() const { return span_count_; }\n  void SetSpanCount(int span_count);\n\n  bool SetOrientation(ScrollDirection orientation) override;\n\n  void OnFocusSearchFailed(bool to_end, ListRecycler& recycler,\n                           const ListViewState& state) override;\n\n  void OnScrollStateChange(Scrollable::ScrollStatus state) override;\n\n  void ScrollToPosition(int position, AlignTo align_to) override;\n  FloatSize ScrollToRect(const FloatRect& rect, AlignTo align_to) override;\n\n  bool HasSpaceToStart(const ListViewState& state) override;\n  bool HasSpaceToEnd(const ListViewState& state) override;\n  ListItemViewHolder* GetFirstInBoxChild(const ListViewState& state) override;\n  ListItemViewHolder* GetLastInBoxChild(const ListViewState& state) override;\n\n  float GetScrollOffset(const ListViewState& state) override;\n  float GetTotalLength(const ListViewState& state) override;\n  void CollectAdjacentPrefetchPositions(\n      int delta_x, int delta_y, int max_limit,\n      const ListViewState& list_view_state,\n      LayoutPrefetchRegistry* layout_prefetch_registry) override;\n\n private:\n  friend class Span;\n\n  struct LayoutParam {\n    bool full_span_;\n    Span* span_;\n  };\n\n  class LayoutState {\n   public:\n    // We may not want to recycle children in some cases (e.g. layout)\n    bool recycle_ = true;\n    // Used when there is no limit in how many views can be laid out.\n    bool infinite_;\n\n    ListItemDirection item_direction_;\n    ListLayoutDirection layout_direction_;\n\n    // Number of pixels that we should fill, in the layout direction.\n    float available_;\n\n    // Pixel offset where layout should start\n    float offset_;\n\n    // Current position on the adapter to get the next item.\n    int current_position_;\n\n    // This is the target pixel closest to the start of the layout that we are\n    // trying to fill\n    float start_line_;\n    float end_line_;\n    bool HasMore(const ListViewState& state);\n    ListItemViewHolder* Next(ListRecycler& recycler);\n    std::string ToString() const;\n  };\n\n  class AnchorInfo {\n   public:\n    int position_;\n    float offset_;\n    bool layout_from_end_;\n    bool invalidate_offsets_;\n    bool valid_;\n    std::vector<int> span_reference_lines_;\n\n    static constexpr float kInvalidOffset = std::numeric_limits<float>::min();\n\n    void Reset();\n    void AssignFromChild(ListItemViewHolder* child,\n                         const ListOrientationHelper& helper,\n                         const LayoutState& state);\n    void SaveSpanReferenceLines(std::vector<std::unique_ptr<Span>>& spans);\n\n    std::string ToString() const;\n  };\n\n  float ScrollBy(float delta, ListRecycler& recycler,\n                 const ListViewState& state);\n\n  void OnLayoutChildrenInternal(ListRecycler& recycler,\n                                const ListViewState& state,\n                                bool should_check_for_gaps);\n\n  float Fill(ListRecycler& recycler, const ListViewState& state);\n\n  void FixLayoutStartGap(ListRecycler& recycler, const ListViewState& state,\n                         bool can_offset_children);\n  void FixLayoutEndGap(ListRecycler& recycler, const ListViewState& state,\n                       bool can_offset_children);\n  bool CheckForGaps();\n  ListItemViewHolder* HasGapsToFix();\n\n  void SetLayoutStateDirection(ListLayoutDirection direction);\n  std::unique_ptr<FullSpanItem> CreateFullSpanItemFromEnd(int new_item_top);\n  std::unique_ptr<FullSpanItem> CreateFullSpanItemFromStart(\n      int new_item_bottom);\n\n  void UpdateAnchorInfo(const ListViewState& state);\n  bool UpdateAnchorInfoFromChildren(const ListViewState& state);\n  bool UpdateAnchorInfoFromPendingData(const ListViewState& state);\n  int FindReferenceChildPosition(int item_count, bool reversed);\n\n  ListLayoutDirection CalculateScrollDirectionForPosition(int position);\n  void UpdateMeasureSpecs(float total_space);\n  void PrepareLayoutStateForDelta(float delta, const ListViewState& state);\n  void UpdateLayoutState(int anchor_position, const ListViewState& state);\n\n  LayoutParam& GetLayoutParam(ListItemViewHolder* item);\n\n  void AttachItemToSpans(ListItemViewHolder* item, LayoutParam& layout_param,\n                         bool laying_out_in_primary_direction);\n  void AppendItemToAllSpans(ListItemViewHolder* item);\n  void PrependItemToAllSpans(ListItemViewHolder* item);\n\n  int GetLastChildPosition();\n  int GetFirstChildPosition();\n  float GetMinStart(float default_line);\n  float GetMaxStart(float default_line);\n  float GetMinEnd(float default_line);\n  float GetMaxEnd(float default_line);\n  Span* GetNextSpan();\n\n  std::vector<int> GetVisibleItemPositions() override;\n\n  bool CheckAllEndsEqual();\n  bool CheckAllStartsEqual();\n\n  float CalculateAccumulatedLength(int to_position);\n\n  // return the number of full spans in specified layout_dir and target_line\n  int CalculateNumberOfUnavailableSpans(ListLayoutDirection layout_dir,\n                                        float target_line);\n  int UpdateRemainingSpans(Span* span, ListLayoutDirection layout_dir,\n                           float target_line);\n\n  void HandleUpdate(int position_start, int item_count_or_to_position,\n                    ListAdapterHelper::Type cmd);\n\n  void Recycle(ListRecycler& recycler);\n  void RecycleFromStart(ListRecycler& recycler, float line);\n  void RecycleFromEnd(ListRecycler& recycler, float line);\n\n  void ResolveShouldLayoutReverse();\n\n  LayoutState layout_state_;\n\n  int span_count_ = 0;\n\n  float size_per_span_;\n  float full_size_spec_;\n\n  bool last_layout_from_end_ = false;\n\n  bool laid_out_invalid_full_span_ = false;\n\n  bool should_reverse_layout_ = false;\n\n  // When LayoutManager needs to scroll to a position, it sets this variable and\n  // requests a layout which will check this variable and re-layout accordingly.\n  int pending_scroll_position_ = ListItemViewHolder::kNoPosition;\n  AlignTo pending_scroll_align_to_ = AlignTo::kNone;\n\n  std::unique_ptr<LazySpanLookup> lazy_span_lookup_;\n  AnchorInfo anchor_info_;\n\n  // Temporary variable used during fill method to check which spans needs to\n  // be filled.\n  std::vector<bool> remaining_spans_;\n\n  std::vector<std::unique_ptr<Span>> spans_;\n\n  // local cache for [key=view_id:value=layout_param]\n  std::unordered_map<int, LayoutParam> layout_params_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_LAYOUT_MANAGER_STAGGERED_GRID_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_orientation_helper.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_orientation_helper.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_layout_manager.h\"\n\nnamespace clay {\n\nvoid ListOrientationHelper::OnLayoutCompleted() {\n  last_total_space_ = GetTotalSpace();\n}\n\nfloat ListOrientationHelper::GetTotalSpaceChange() const {\n  return last_total_space_ == kInvalidSize\n             ? 0.f\n             : GetTotalSpace() - last_total_space_;\n}\n\nclass ListOrientationHelperVertical : public ListOrientationHelper {\n public:\n  explicit ListOrientationHelperVertical(ListLayoutManager* manager)\n      : ListOrientationHelper(manager) {}\n  ~ListOrientationHelperVertical() override = default;\n\n  float GetEndAfterPadding() const override {\n    return manager_->GetHeight() - manager_->GetPaddingBottom();\n  }\n\n  float GetEnd() const override { return manager_->GetHeight(); }\n\n  float GetEndPadding() const override { return manager_->GetPaddingBottom(); }\n\n  float GetDecoratedEnd(const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    return child->GetBottom();\n  }\n\n  float GetStartAfterPadding() const override {\n    return manager_->GetPaddingTop();\n  }\n\n  float GetDecoratedStart(const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    return child->GetTop();\n  }\n\n  float GetSecondaryStartAfterPadding() const override {\n    return manager_->GetPaddingLeft();\n  }\n\n  float GetRectStart(const FloatRect& rect) const override { return rect.y(); }\n\n  float GetRectEnd(const FloatRect& rect) const override { return rect.MaxY(); }\n\n  float GetDecoratedMeasure(const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    return child->GetBottom() - child->GetTop();\n  }\n\n  float GetSecondaryDecoratedMeasure(\n      const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    return child->GetRight() - child->GetLeft();\n  }\n\n  void OffsetChildren(float amount) override {\n    manager_->OffsetChildren(0.f, amount);\n  }\n\n  float GetTotalSpace() const override {\n    float space = manager_->GetHeight() - manager_->GetPaddingTop() -\n                  manager_->GetPaddingBottom();\n    if (space < 0.f) {\n      return 0.f;\n    }\n    return space;\n  }\n\n  float GetSecondaryTotalSpace() const override {\n    float space = manager_->GetWidth() - manager_->GetPaddingLeft() -\n                  manager_->GetPaddingRight();\n    if (space < 0.f) {\n      return 0.f;\n    }\n    return space;\n  }\n\n  FloatPoint CalculateFullSpanLocation(\n      const FloatPoint& old_location,\n      const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    float remaining_space = manager_->GetWidth() - child->GetView()->Width();\n    float left = 0.f;\n    if (remaining_space > 0.f) {\n      float total_padding =\n          manager_->GetPaddingLeft() + manager_->GetPaddingRight();\n      float front_padding = manager_->GetPaddingLeft();\n      // if remained space is larger than total padding, use space - total\n      // padding else use scaled left padding in total space.\n      remaining_space -= total_padding;\n      if (remaining_space >= 0.f) {\n        left = front_padding;\n      } else {\n        float ratio = front_padding / total_padding;\n        left = front_padding + (remaining_space * ratio);\n      }\n    }\n    return FloatPoint(left, old_location.y());\n  }\n};\n\nclass ListOrientationHelperHorizontal : public ListOrientationHelper {\n public:\n  explicit ListOrientationHelperHorizontal(ListLayoutManager* manager)\n      : ListOrientationHelper(manager) {}\n  ~ListOrientationHelperHorizontal() override = default;\n\n  float GetEndAfterPadding() const override {\n    return manager_->GetWidth() - manager_->GetPaddingRight();\n  }\n\n  float GetEnd() const override { return manager_->GetWidth(); }\n\n  float GetEndPadding() const override { return manager_->GetPaddingRight(); }\n\n  float GetDecoratedEnd(const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    return child->GetRight();\n  }\n\n  float GetStartAfterPadding() const override {\n    return manager_->GetPaddingLeft();\n  }\n\n  float GetDecoratedStart(const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    return child->GetLeft();\n  }\n\n  float GetSecondaryStartAfterPadding() const override {\n    return manager_->GetPaddingTop();\n  }\n\n  float GetRectStart(const FloatRect& rect) const override { return rect.x(); }\n\n  float GetRectEnd(const FloatRect& rect) const override { return rect.MaxX(); }\n\n  float GetDecoratedMeasure(const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    return child->GetRight() - child->GetLeft();\n  }\n\n  float GetSecondaryDecoratedMeasure(\n      const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    return child->GetBottom() - child->GetTop();\n  }\n\n  void OffsetChildren(float amount) override {\n    manager_->OffsetChildren(amount, 0.f);\n  }\n\n  float GetTotalSpace() const override {\n    float space = manager_->GetWidth() - manager_->GetPaddingLeft() -\n                  manager_->GetPaddingRight();\n    if (space < 0.f) {\n      return 0.f;\n    }\n    return space;\n  }\n\n  float GetSecondaryTotalSpace() const override {\n    float space = manager_->GetHeight() - manager_->GetPaddingTop() -\n                  manager_->GetPaddingBottom();\n    if (space < 0.f) {\n      return 0.f;\n    }\n    return space;\n  }\n\n  FloatPoint CalculateFullSpanLocation(\n      const FloatPoint& old_location,\n      const ListItemViewHolder* child) const override {\n    FML_DCHECK(child);\n    float remaining_space = manager_->GetHeight() - child->GetView()->Height();\n    float top = 0.f;\n    if (remaining_space > 0.f) {\n      float total_padding =\n          manager_->GetPaddingTop() + manager_->GetPaddingBottom();\n      float front_padding = manager_->GetPaddingTop();\n      // if remained space is larger than total padding, use space - total\n      // padding else use scaled left padding in total space.\n      remaining_space -= total_padding;\n      if (remaining_space >= 0.f) {\n        top = front_padding;\n      } else {\n        float ratio = front_padding / total_padding;\n        top = front_padding + (remaining_space * ratio);\n      }\n    }\n    return FloatPoint(old_location.x(), top);\n  }\n};\n\nListOrientationHelper::ListOrientationHelper(ListLayoutManager* manager)\n    : manager_(manager) {\n  FML_DCHECK(manager_);\n}\nListOrientationHelper::~ListOrientationHelper() = default;\n\n// static\nstd::unique_ptr<ListOrientationHelper>\nListOrientationHelper::CreateOrientationHelper(ListLayoutManager* manager,\n                                               ScrollDirection orientation) {\n  if (orientation == ScrollDirection::kVertical) {\n    return std::make_unique<ListOrientationHelperVertical>(manager);\n  } else if (orientation == ScrollDirection::kHorizontal) {\n    return std::make_unique<ListOrientationHelperHorizontal>(manager);\n  }\n  FML_CHECK(0) << \"Invalid ListLayoutOrientation \"\n               << static_cast<int>(orientation);\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_orientation_helper.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_ORIENTATION_HELPER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_ORIENTATION_HELPER_H_\n\n#include <limits>\n#include <memory>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/scroll_direction.h\"\n\nnamespace clay {\n\nclass ListLayoutManager;\nclass ListItemViewHolder;\n\nclass ListOrientationHelper {\n public:\n  virtual ~ListOrientationHelper();\n\n  virtual float GetEndAfterPadding() const = 0;\n  virtual float GetEnd() const = 0;\n  virtual float GetEndPadding() const = 0;\n  virtual float GetDecoratedEnd(const ListItemViewHolder* child) const = 0;\n\n  virtual float GetStartAfterPadding() const = 0;\n  virtual float GetDecoratedStart(const ListItemViewHolder* child) const = 0;\n  virtual float GetSecondaryStartAfterPadding() const = 0;\n\n  virtual float GetRectStart(const FloatRect& rect) const = 0;\n  virtual float GetRectEnd(const FloatRect& rect) const = 0;\n\n  virtual float GetDecoratedMeasure(const ListItemViewHolder* child) const = 0;\n  virtual float GetSecondaryDecoratedMeasure(\n      const ListItemViewHolder* child) const = 0;\n\n  virtual void OffsetChildren(float amount) = 0;\n\n  virtual float GetTotalSpace() const = 0;\n  float GetTotalSpaceChange() const;\n  virtual float GetSecondaryTotalSpace() const = 0;\n\n  virtual FloatPoint CalculateFullSpanLocation(\n      const FloatPoint& old_location,\n      const ListItemViewHolder* child) const = 0;\n\n  static std::unique_ptr<ListOrientationHelper> CreateOrientationHelper(\n      ListLayoutManager* manager, ScrollDirection orientation);\n\n  /**\n   * Call this method after OnLayoutChildren method is complete. This method\n   * records information like layout bounds that might be useful in the next\n   * layout calculations.\n   */\n  void OnLayoutCompleted();\n\n protected:\n  explicit ListOrientationHelper(ListLayoutManager* manager);\n  // Not owned. Instead, it is the layout manager owning the helper.\n  ListLayoutManager* manager_;\n\n  static constexpr float kInvalidSize = std::numeric_limits<float>::lowest();\n\n  float last_total_space_ = kInvalidSize;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_ORIENTATION_HELPER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_recycler.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_recycler.h\"\n\n#include <cstdint>\n#include <string>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/macros.h\"\n\nnamespace clay {\n\nListRecycler::ListRecycler(ListAdapter* adapter) : adapter_(adapter) {\n  FML_DCHECK(adapter_ != nullptr);\n}\n\nListRecycler::~ListRecycler() = default;\n\nListItemViewHolder* ListRecycler::GetItemForPosition(int position) {\n  TRACE_EVENT(\"LIST\", \"ListRecycler::GetItemForPosition\");\n\n  if (position >= adapter_->GetItemCount()) {\n    // TODO(liuguoliang): This may happen when prefetching items. We add\n    // protection here to avoid crashes for now, but further investigation is\n    // required.\n    FML_DCHECK(false) << \"position out of range (\" << position << \" vs \"\n                      << adapter_->GetItemCount() << \")\";\n    return nullptr;\n  }\n\n  const ListAdapter::TypeId type = adapter_->GetItemViewType(position);\n\n  {\n    TRACE_EVENT(\"LIST\", \"Search attached scrap items\");\n    // 0) If there is a scrapped item, return it.\n    for (auto itr = attached_scrap_items_.begin();\n         itr != attached_scrap_items_.end(); ++itr) {\n      ListItemViewHolder* view_holder = *itr;\n      if (view_holder->GetLayoutPosition() == position &&\n          view_holder->GetItemViewType() == type && !view_holder->IsRemoved()) {\n        attached_scrap_items_.erase(itr);\n        if (view_holder->NeedsUpdate() || !view_holder->IsBound()) {\n          // view_holder which need reload rebind data here.\n          adapter_->BindListItem(view_holder, position);\n          // Unset the update flag.\n          view_holder->RemoveFlags(ListItemViewHolder::kFlagUpdate);\n        }\n        return view_holder;\n      }\n    }\n  }\n  ListItemViewHolder* item = nullptr;\n  std::vector<ListItemViewHolder*>& cached_list = cached_items_;\n  {\n    // 1) Find in prefetch cache.\n    if (!cached_list.empty()) {\n      TRACE_EVENT(\"LIST\", \"Search in prefetch cache\");\n      // find the item with identical pos\n      auto finder = [&cached_list, type](int pos) {\n        ListItemViewHolder* res = nullptr;\n        auto itr = std::find_if(cached_list.begin(), cached_list.end(),\n                                [pos, type](ListItemViewHolder* item) {\n                                  return item->GetPosition() == pos &&\n                                         item->GetItemViewType() == type;\n                                });\n        if (itr != cached_list.end()) {\n          res = *itr;\n          cached_list.erase(itr);\n          res->RemoveFlags(ListItemViewHolder::kFlagPrefetch);\n        }\n        return res;\n      };\n      // Find the one using `position`\n      item = finder(position);\n      if (item && item->IsBound() && !item->NeedsUpdate()) {\n        return item;\n      }\n    }\n  }\n\n  {\n    // 2) Check if in recycle pool in old arch.\n    if (!adapter_->IsNewArch()) {\n      item = old_arch_recycle_helper_.GetItemForPosition(position, type);\n    }\n  }\n\n  fml::TimePoint start = fml::TimePoint::Now();\n  {\n    TRACE_EVENT(\"LIST\", \"Create new item\");\n    // 3) Create new item.\n    if (item == nullptr) {\n      item = adapter_->CreateListItem(type);\n    }\n  }\n  {\n    TRACE_EVENT(\"LIST\", \"Bind item\");\n    // Finally, bind item.\n    adapter_->BindListItem(item, position);\n  }\n  adapter_->UpdateBindTime(type, fml::TimePoint::Now() - start);\n  list_perf_report_flag_ = true;\n  return item;\n}\n\nbool ListRecycler::HasItemCached(int position) const {\n  for (auto item : cached_items_) {\n    if (item->GetPosition() == position) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid ListRecycler::RecycleItem(ListItemViewHolder* item) {\n  auto type = item->GetItemViewType();\n  adapter_->OnRecycleItem(item);\n  if (item->IsScrapped()) {\n    for (auto itr = attached_scrap_items_.begin();\n         itr != attached_scrap_items_.end(); ++itr) {\n      if (*itr == item) {\n        attached_scrap_items_.erase(itr);\n        break;\n      }\n    }\n    item->RemoveFlags(ListItemViewHolder::Flag::kFlagScrapped);\n  }\n  item->ResetOnRecycle();\n  if (item->IsPrefetch() && max_limit_ > 0) {\n    int current_size = cached_items_.size();\n    while (current_size >= max_limit_) {\n      auto to_delete = *cached_items_.begin();\n      cached_items_.erase(cached_items_.begin());\n      to_delete->RemoveFlags(ListItemViewHolder::Flag::kFlagPrefetch);\n      to_delete->AddFlags(ListItemViewHolder::Flag::kFlagUpdate);\n      // Put it back to recycle pool in new arch.\n      adapter_->OnRecycleItem(to_delete);\n      --current_size;\n    }\n    cached_items_.push_back(item);\n  } else if (!adapter_->IsNewArch()) {\n    old_arch_recycle_helper_.RecycleItem(item, type);\n  }\n}\n\nvoid ListRecycler::ScrapItem(ListItemViewHolder* item) {\n  item->AddFlags(ListItemViewHolder::kFlagScrapped);\n  attached_scrap_items_.push_back(item);\n}\n\nvoid ListRecycler::RecycleScrappedItems(BaseListView* list_view) {\n  if (attached_scrap_items_.empty()) {\n    return;\n  }\n  for (ListItemViewHolder* item : attached_scrap_items_) {\n    FML_DCHECK(item->HasAnyOfFlags(ListItemViewHolder::kFlagScrapped));\n    item->RemoveFlags(ListItemViewHolder::kFlagScrapped);\n    list_view->OnRemoveItem(item);\n    RecycleItem(item);\n  }\n  attached_scrap_items_.clear();\n}\n\nvoid ListRecycler::RemoveScrappedItemByView(BaseView* view) {\n  for (auto itr = attached_scrap_items_.begin();\n       itr != attached_scrap_items_.end(); ++itr) {\n    ListItemViewHolder* view_holder = *itr;\n    if (view_holder->GetView() == view) {\n      LIST_LOG << \"Remove the scrapped item with target view:\"\n               << view_holder->ToString();\n      view_holder->SetView(nullptr);\n      attached_scrap_items_.erase(itr);\n      break;\n    }\n  }\n}\n\nvoid ListRecycler::OffsetPositionsForInsert(int position_start,\n                                            int item_count) {\n  FML_DCHECK(attached_scrap_items_.empty());\n  for (const auto& item : cached_items_) {\n    const int pos = item->GetPosition();\n    if (pos != ListItemViewHolder::kNoPosition && pos >= position_start) {\n      LIST_LOG << \"Insert. offset pos:\" << pos << \" count:\" << item_count;\n      item->OffsetPosition(item_count);\n    }\n  }\n  if (!adapter_->IsNewArch()) {\n    old_arch_recycle_helper_.OffsetPositionsForInsert(position_start,\n                                                      item_count);\n  }\n}\n\nvoid ListRecycler::OffsetPositionsForRemove(int position_start,\n                                            int item_count) {\n  FML_DCHECK(attached_scrap_items_.empty());\n  FML_DCHECK(position_start >= 0 && item_count >= 0);\n  const int position_end = position_start + item_count;\n  for (auto itr = cached_items_.begin(); itr != cached_items_.end();) {\n    auto item = *itr;\n    const int pos = item->GetPosition();\n    if (pos >= position_end) {\n      LIST_LOG << \"Remove. offset pos:\" << pos << \" count:\" << -item_count;\n      item->OffsetPosition(-item_count, true);\n    } else if (pos >= position_start) {\n      LIST_LOG << \"Remove. Reset pos:\" << pos;\n      item->Reset();\n      adapter_->OnRecycleItem(item);\n      itr = cached_items_.erase(itr);\n      continue;\n    }\n    itr++;\n  }\n  if (!adapter_->IsNewArch()) {\n    old_arch_recycle_helper_.OffsetPositionsForRemove(position_start,\n                                                      item_count);\n  }\n}\n\nvoid ListRecycler::MarkViewHoldersChanged(int position_start, int item_count) {\n  FML_DCHECK(attached_scrap_items_.empty());\n  FML_DCHECK(position_start >= 0 && item_count >= 0);\n  const int position_end = position_start + item_count;\n  for (auto itr = cached_items_.begin(); itr != cached_items_.end();) {\n    auto item = *itr;\n    const int pos = item->GetPosition();\n    if (pos >= position_start && pos < position_end) {\n      LIST_LOG << \"Changed. Reset pos:\" << pos;\n      item->Reset();\n      adapter_->OnRecycleItem(item);\n      itr = cached_items_.erase(itr);\n      continue;\n    }\n    itr++;\n  }\n\n  if (!adapter_->IsNewArch()) {\n    old_arch_recycle_helper_.MarkViewHoldersChanged(position_start, item_count);\n  }\n}\n\nvoid ListRecycler::OffsetPositionsForMove(int from, int to) {\n  FML_DCHECK(attached_scrap_items_.empty());\n\n  int start, end, in_between_offset;\n  if (from < to) {\n    start = from;\n    end = to;\n    in_between_offset = -1;\n  } else {\n    start = to;\n    end = from;\n    in_between_offset = 1;\n  }\n  for (const auto& item : cached_items_) {\n    const int pos = item->GetPosition();\n    if (pos == ListItemViewHolder::kNoPosition) {\n      return;\n    }\n    if (pos < start || pos > end) {\n      return;\n    }\n    if (pos == from) {\n      LIST_LOG << \"Moved. Offset pos:\" << pos << \" count:\" << to - from;\n      item->OffsetPosition(to - from, false);\n    } else {\n      LIST_LOG << \"Moved. Offset pos:\" << pos << \" count:\" << in_between_offset;\n      item->OffsetPosition(in_between_offset, false);\n    }\n  }\n  if (!adapter_->IsNewArch()) {\n    old_arch_recycle_helper_.OffsetPositionsForMove(from, to, start, end,\n                                                    in_between_offset);\n  }\n}\n\nvoid ListRecycler::MarkKnownViewsInvalid() {\n  for (const auto& item : cached_items_) {\n    item->Reset();\n    adapter_->OnRecycleItem(item);\n  }\n  cached_items_.clear();\n  if (!adapter_->IsNewArch()) {\n    old_arch_recycle_helper_.MarkKnownViewsInvalid();\n  }\n}\n\nvoid ListRecycler::Clear() {\n  LIST_LOG << __PRETTY_FUNCTION__;\n  cached_items_.clear();\n  attached_scrap_items_.clear();\n  old_arch_recycle_helper_.Clear();\n}\n\nListItemViewHolder* ListRecycler::RecycleHelperForOldArch::GetItemForPosition(\n    int position, ListAdapter::TypeId type) {\n  std::list<ListItemViewHolder*>& data_list = recycle_pool_for_old_arch_[type];\n  ListItemViewHolder* item = nullptr;\n  if (!data_list.empty()) {\n    // find the item with identical pos\n    auto finder = [&data_list, type](int pos) {\n      ListItemViewHolder* res = nullptr;\n      auto itr = std::find_if(data_list.begin(), data_list.end(),\n                              [pos, type](ListItemViewHolder* item) {\n                                return item->GetPosition() == pos &&\n                                       item->GetItemViewType() == type;\n                              });\n      if (itr != data_list.end()) {\n        res = *itr;\n        data_list.erase(itr);\n      }\n      return res;\n    };\n    // Step1: Find the one using `position`\n    item = finder(position);\n    bool item_need_rebind = false;\n    // Step2: Find the one using `kNoPosition`\n    if (item == nullptr) {\n      item = finder(ListItemViewHolder::kNoPosition);\n      item_need_rebind |= (item != nullptr);\n    }\n\n    // Step3: Just retrieve item with identical\n\n    if (item == nullptr) {\n      // only non-dryrun support\n      item = data_list.front();\n      data_list.pop_front();\n      item_need_rebind |= (item != nullptr);\n    }\n\n    if (item_need_rebind && item->IsBound()) {\n      item->RemoveFlags(ListItemViewHolder::kFlagBound);\n    }\n  }\n  return item;\n}\n\nvoid ListRecycler::RecycleHelperForOldArch::IterateItemsInRecyclePoolForOldArch(\n    const std::function<void(ListItemViewHolder*)>& func) {\n  for (auto& id_to_list : recycle_pool_for_old_arch_) {\n    std::list<ListItemViewHolder*>& cached_list = id_to_list.second;\n    for (ListItemViewHolder* item : cached_list) {\n      func(item);\n    }\n  }\n}\n\nvoid ListRecycler::RecycleHelperForOldArch::RecycleItem(\n    ListItemViewHolder* item, ListAdapter::TypeId type) {\n  if (recycle_pool_for_old_arch_.find(type) ==\n      recycle_pool_for_old_arch_.end()) {\n    recycle_pool_for_old_arch_[type] = {};\n  }\n  recycle_pool_for_old_arch_[type].push_front(item);\n}\n\nvoid ListRecycler::RecycleHelperForOldArch::OffsetPositionsForInsert(\n    int position_start, int item_count) {\n  IterateItemsInRecyclePoolForOldArch(\n      [position_start, item_count](ListItemViewHolder* item) {\n        const int pos = item->GetPosition();\n        if (pos != ListItemViewHolder::kNoPosition && pos >= position_start) {\n          LIST_LOG << \"Old Arch insert. offset pos:\" << pos\n                   << \" count:\" << item_count;\n          item->OffsetPosition(item_count);\n        }\n      });\n}\n\nvoid ListRecycler::RecycleHelperForOldArch::OffsetPositionsForRemove(\n    int position_start, int item_count) {\n  const int position_end = position_start + item_count;\n  IterateItemsInRecyclePoolForOldArch(\n      [position_start, item_count, position_end](ListItemViewHolder* item) {\n        const int pos = item->GetPosition();\n        if (pos == ListItemViewHolder::kNoPosition) {\n          return;\n        }\n        if (pos >= position_end) {\n          LIST_LOG << \"Old arch remove. offset pos:\" << pos\n                   << \" count:\" << -item_count;\n          item->OffsetPosition(-item_count, true);\n        } else if (pos >= position_start) {\n          LIST_LOG << \"Old arch remove. Reset pos:\" << pos;\n          item->Reset();\n        }\n      });\n}\n\nvoid ListRecycler::RecycleHelperForOldArch::MarkViewHoldersChanged(\n    int position_start, int item_count) {\n  const int position_end = position_start + item_count;\n  IterateItemsInRecyclePoolForOldArch(\n      [position_start, position_end](ListItemViewHolder* item) {\n        const int pos = item->GetPosition();\n        if (pos == ListItemViewHolder::kNoPosition) {\n          return;\n        }\n        if (pos >= position_start && pos < position_end) {\n          LIST_LOG << \"Old arch changed. Reset pos:\" << pos;\n          item->Reset();\n        }\n      });\n}\n\nvoid ListRecycler::RecycleHelperForOldArch::OffsetPositionsForMove(\n    int from, int to, int start, int end, int in_between_offset) {\n  IterateItemsInRecyclePoolForOldArch(\n      [from, to, start, end, in_between_offset](ListItemViewHolder* item) {\n        const int pos = item->GetPosition();\n        if (pos == ListItemViewHolder::kNoPosition) {\n          return;\n        }\n        if (pos < start || pos > end) {\n          return;\n        }\n        if (pos == from) {\n          LIST_LOG << \"Old arch moved. Offset pos:\" << pos\n                   << \" count:\" << to - from;\n          item->OffsetPosition(to - from, false);\n        } else {\n          LIST_LOG << \"Old arch moved. Offset pos:\" << pos\n                   << \" count:\" << in_between_offset;\n          item->OffsetPosition(in_between_offset, false);\n        }\n      });\n}\n\nvoid ListRecycler::RecycleHelperForOldArch::MarkKnownViewsInvalid() {\n  IterateItemsInRecyclePoolForOldArch(\n      [](ListItemViewHolder* item) { item->Reset(); });  // NOLINT\n}\n\nvoid ListRecycler::RecycleHelperForOldArch::Clear() {\n  recycle_pool_for_old_arch_.clear();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_recycler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_RECYCLER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_RECYCLER_H_\n\n#include <cstdint>\n#include <functional>\n#include <list>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/list_adapter.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass ListItemViewHolder;\n\n// Recycler is responsible for caching the item. Layout Manager does not acquire\n// an item from List directly. Instead, a Recycler instance is passed to the\n// layout manager.\n// Recycler will try to find an item from cache first. If no valid cache exists,\n// Recycler will ask Adapter to create one.\nclass ListRecycler {\n public:\n  explicit ListRecycler(ListAdapter* adapter);\n  ~ListRecycler();\n\n  /**\n   * Get the reused item with `position` and corresponding typeId if existing,\n   * otherwise create a new one. The find process consists of following steps.\n   * 1. retrieve typeId from `adapter_` using `position`\n   * 2. search in 1st layer cache(i.e.`attached_scrap_items_`), return if\n   *    existing\n   * 3. search in 2nd layer cache(i.e.`cache_items_`), using `position`, then\n   *    `kNoPosition`, then just same typeId, return if existing\n   * 4. create a new one\n   * @param position relative to whole list data\n   * @return ListItemViewHolder* corresponding view_holder\n   */\n  ListItemViewHolder* GetItemForPosition(int position);\n\n  /**\n   * Set view hidden, and save into 2nd layer cache.\n   *\n   * @param item view_holder\n   */\n  void RecycleItem(ListItemViewHolder* item);\n\n  // When an item is scrapped, it is possible that the item will be reused in\n  // the same layout pass then the item does not need to bind again as long\n  // as the changed flag is not set.\n  void ScrapItem(ListItemViewHolder* item);\n  void RecycleScrappedItems(BaseListView* list_view);\n\n  void RemoveScrappedItemByView(BaseView* view);\n\n  void OffsetPositionsForInsert(int position_start, int item_count);\n  void OffsetPositionsForRemove(int position_start, int item_count);\n  void MarkViewHoldersChanged(int position_start, int item_count);\n  void OffsetPositionsForMove(int from, int to);\n  void MarkKnownViewsInvalid();\n\n  bool HasItemCached(int position) const;\n\n  // Set the max cached item count.\n  void SetCacheMaxLimit(int max_limit) { max_limit_ = max_limit; }\n  int GetCacheMaxLimit() const { return max_limit_; }\n\n  void Clear();\n\n  void SetListPerfFlag(bool flag) { list_perf_report_flag_ = flag; }\n  bool GetListPerfFlag() const { return list_perf_report_flag_; }\n\n private:\n  FRIEND_TEST(ListLayoutManagerLinear, Scroll);\n  FRIEND_TEST(ListLayoutManagerLinear, ScrollHorizontally);\n  FRIEND_TEST(ListLayoutManagerLinear, ScrollDiffHeight);\n  FRIEND_TEST(ListLayoutManagerLinear, RemoveData);\n  FRIEND_TEST(ListLayoutManagerGrid, Scroll);\n  FRIEND_TEST(ListLayoutManagerStaggeredGrid, Scroll);\n\n  class RecycleHelperForOldArch {\n   public:\n    ListItemViewHolder* GetItemForPosition(int position,\n                                           ListAdapter::TypeId type);\n    void RecycleItem(ListItemViewHolder* item, ListAdapter::TypeId type);\n    void OffsetPositionsForInsert(int position_start, int item_count);\n    void OffsetPositionsForRemove(int position_start, int item_count);\n    void MarkViewHoldersChanged(int position_start, int item_count);\n    void OffsetPositionsForMove(int from, int to, int start, int end,\n                                int in_between_offset);\n    void MarkKnownViewsInvalid();\n    void IterateItemsInRecyclePoolForOldArch(\n        const std::function<void(ListItemViewHolder*)>& func);\n    void Clear();\n\n   private:\n    std::unordered_map<ListAdapter::TypeId, std::list<ListItemViewHolder*>>\n        recycle_pool_for_old_arch_;\n  } old_arch_recycle_helper_;\n\n  int max_limit_ = 0;\n\n  // the flag will be set up, when new item is added into list\n  bool list_perf_report_flag_ = false;\n\n  // ListAdapter is owned by RenderList.\n  ListAdapter* adapter_ = nullptr;\n  // 2nd prefetch cache.\n  std::vector<ListItemViewHolder*> cached_items_;\n  // 1st layer cache.\n  // Before layout: Store all items excluding flagged removed.\n  // In layout: Retrieve item(Only the item with consistent type and position\n  // is reused in layout.).\n  // After layout: Store remaining item to 2nd layer cache and clear.\n  std::list<ListItemViewHolder*> attached_scrap_items_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_RECYCLER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_scroller.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_scroller.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <limits>\n#include <optional>\n#include <string>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/animation_handler.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_children_helper.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/list/list_layout_manager.h\"\n#include \"clay/ui/component/list/list_orientation_helper.h\"\n#include \"clay/ui/component/scroller_animator.h\"\n#include \"clay/ui/component/view_context.h\"\n\nnamespace clay {\nnamespace {\n\nstatic float GetInterpolation(float t) {\n  t -= 1;\n  return t * t * t * t * t + 1.0f;\n}\n\nconstexpr char kAlignNone[] = \"none\";\nconstexpr char kAlignTop[] = \"top\";\nconstexpr char kAlignBottom[] = \"bottom\";\nconstexpr char kAlignMiddle[] = \"middle\";\n\ninline bool IsNear(float l, float r) {\n  return std::abs(l - r) < std::numeric_limits<float>::epsilon();\n}\n\n}  // namespace\n\nListScroller::ListScroller(BaseListView* list_view) : list_view_(list_view) {\n  FML_DCHECK(list_view_);\n}\n\nListScroller::~ListScroller() = default;\n\nint32_t ListScroller::CalculatePxPerFrame() const {\n  return list_view_->page_view()->DeviceDpi() / 4;\n}\n\nvoid ListScroller::ScrollToPosition(\n    bool smooth, int position, float offset, AlignTo align_to,\n    const std::string& id, const std::optional<FloatRect> target_rect,\n    std::function<void(uint32_t, const std::string&)> callback) {\n  position_ = position;\n  id_ = id;\n  offset_ = offset;\n  align_to_ = align_to;\n  target_rect_ = target_rect;\n\n  if (!smooth) {\n    auto result = ScrollImmediately();\n    if (callback) {\n      callback(result ? static_cast<uint32_t>(LynxUIMethodResult::kSuccess)\n                      : static_cast<uint32_t>(LynxUIMethodResult::kUnknown),\n               \"\");\n    }\n    return;\n  }\n\n  // reset final location and duration time when animation start\n  target_distance_ = std::nullopt;\n  duration_time_ = std::nullopt;\n  last_distance_ = 0;\n  start_time_ = fml::TimePoint::Now().ToEpochDelta().ToMillisecondsF();\n\n  if (Scrolling()) {\n    if (callback_) {\n      callback_(static_cast<uint32_t>(LynxUIMethodResult::kUnknown), \"\");\n      callback_ = nullptr;\n    }\n  }\n\n  callback_ = callback;\n\n  if (!Scrolling()) {\n    StartAnimator();\n  }\n}\n\nbool ListScroller::Scrolling() const {\n  if (!animator_) {\n    return false;\n  }\n  return animator_->Running();\n}\n\nvoid ListScroller::StopScroll(bool result) {\n  if (Scrolling()) {\n    StopAnimator();\n    if (callback_) {\n      callback_(result ? static_cast<uint32_t>(LynxUIMethodResult::kSuccess)\n                       : static_cast<uint32_t>(LynxUIMethodResult::kUnknown),\n                \"\");\n      callback_ = nullptr;\n    }\n  }\n}\n\nListLayoutManager* ListScroller::GetLayoutManager() const {\n  return list_view_->GetLayoutManager();\n}\n\nListAdapter* ListScroller::GetAdapter() const { return list_view_->adapter_; }\n\nListChildrenHelper* ListScroller::GetChildrenHelper() const {\n  return list_view_->children_helper_.get();\n}\n\nAnimationHandler* ListScroller::GetAnimationHandler() const {\n  return list_view_->GetAnimationHandler();\n}\n\nvoid ListScroller::StartAnimator() {\n  if (Scrolling()) {\n    return;\n  }\n  if (list_view_) {\n    list_view_->NotifyScrollAnimationStart();\n  }\n\n  if (!animator_) {\n    animator_ = std::make_unique<internal::ScrollerAnimator>(\n        [this](int64_t frame_time) { return OnAnimation(frame_time); },\n        GetAnimationHandler());\n  }\n  animator_->Start();\n}\n\nvoid ListScroller::StopAnimator() {\n  if (!Scrolling()) {\n    return;\n  }\n  if (list_view_) {\n    list_view_->NotifyScrollAnimationEnd();\n  }\n  animator_->Stop();\n}\n\nbool ListScroller::ScrollImmediately() {\n  if (Scrolling()) {\n    StopAnimator();\n  }\n\n  // First, we move the target item to visible area (if needed)\n  auto layout_manager = GetLayoutManager();\n  auto target_item = GetChildrenHelper()->FindChildByPosition(position_);\n  if (!target_item) {\n    layout_manager->ScrollToPosition(position_, align_to_);\n    list_view_->Layout();\n    target_item = GetChildrenHelper()->FindChildByPosition(position_);\n    if (!target_item) {\n      return false;\n    }\n  }\n\n  // Then we can calculate the final location\n  auto distance = DistanceToTarget(target_item);\n  if (distance != 0) {\n    FloatSize px_to_scroll;\n    if (layout_manager->CanScrollHorizontally()) {\n      px_to_scroll = {-distance, 0.f};\n    } else {\n      px_to_scroll = {0.f, -distance};\n    }\n    list_view_->OnScrollBy(px_to_scroll);\n  }\n  return true;\n}\n\nbool ListScroller::OnAnimation(int64_t frame_time) {\n  TRACE_EVENT(\"LIST\", \"ListScroller::OnAnimation\");\n  if (list_view_->NeedsLayout()) {\n    // This will consume the pending ops\n    list_view_->Layout();\n  }\n\n  const int child_count = GetChildrenHelper()->GetChildCount();\n  if (child_count == 0) {\n    StopScroll();\n    return true;\n  }\n\n  ListLayoutManager* layout_manager = GetLayoutManager();\n  UpdatePositions();\n\n  // For an iteration, we either scroll to a target position OR an offset in px.\n  int target_pos = ListItemViewHolder::kNoPosition;\n  float target_offset = 0.f;\n  bool should_stop = false;\n  bool is_horizontal = layout_manager->CanScrollHorizontally();\n\n  auto target_item = GetChildrenHelper()->FindChildByPosition(position_);\n  if (target_item == nullptr && !target_distance_.has_value()) {\n    const float list_height = list_view_->Height();\n    // target view is not visible\n    const bool scroll_to_start = start_position_ > position_;\n    int item_to_scroll = scroll_to_start ? (start_position_ - position_)\n                                         : (position_ - end_position_);\n\n    float estimate_distance = item_to_scroll * list_height / child_count;\n    const bool far_away =\n        IsFarAwayFromTarget(item_to_scroll, estimate_distance);\n    if (far_away) {\n      // scroll to near target by middle point\n      target_pos = scroll_to_start\n                       ? (start_position_ - position_) / 2 + position_\n                       : (end_position_ - position_) / 2 + position_;\n    } else {\n      // scroll slower when near to target\n      target_offset =\n          std::min(list_height,\n                   static_cast<float>(CalculatePxPerFrame() * item_to_scroll)) *\n          (scroll_to_start ? -1.f : 1.f);\n    }\n  } else {\n    // target view is visible, adjust to offset\n    if (!target_distance_.has_value()) {\n      target_distance_ = DistanceToTarget(target_item);\n      if (!is_horizontal) {\n        duration_time_ = ComputeScrollDuration(0, target_distance_.value());\n      } else {\n        duration_time_ = ComputeScrollDuration(target_distance_.value(), 0);\n      }\n    }\n\n    if (!ComputeScrollOffset(target_distance_.value(), frame_time,\n                             target_offset)) {\n      should_stop = true;\n    }\n  }\n\n  if (target_pos != ListItemViewHolder::kNoPosition) {\n    layout_manager->ScrollToPosition(target_pos, align_to_);\n    list_view_->MarkNeedsLayout();\n  } else if (!IsNear(target_offset, 0.f)) {\n    FloatSize px_to_scroll;\n    if (is_horizontal) {\n      px_to_scroll = {-target_offset, 0.f};\n    } else {\n      px_to_scroll = {0.f, -target_offset};\n    }\n    if (!list_view_->OnScrollBy(px_to_scroll)) {\n      should_stop = true;\n    }\n  }\n\n  if (should_stop) {\n    StopScroll(true);\n  }\n\n  return should_stop;\n}\n\nvoid ListScroller::UpdatePositions() {\n  position_ = std::min(position_, GetAdapter()->GetItemCount() - 1);\n  position_ = std::max(position_, 0);\n\n  ListChildrenHelper* children_helper = GetChildrenHelper();\n  const int child_count = children_helper->GetChildCount();\n  FML_DCHECK(child_count);\n\n  // FIXME(Xietong): Lynx use GetLayoutPosition here.\n  start_position_ = children_helper->GetChildAt(0)->GetPosition();\n  end_position_ = children_helper->GetChildAt(child_count - 1)->GetPosition();\n}\n\nbool ListScroller::IsFarAwayFromTarget(int to_scroll,\n                                       float estimated_distance) {\n  return to_scroll > 30 && estimated_distance > 10.f * list_view_->Height();\n}\n\nfloat ListScroller::DistanceToTarget(ListItemViewHolder* target_item) {\n  FML_DCHECK(target_item);\n\n  BaseView* target_view = nullptr;\n  if (!id_.empty()) {\n    target_view =\n        ViewContext::FindViewByIdSelector(id_, target_item->GetView());\n  }\n\n  ListLayoutManager* layout_manager = GetLayoutManager();\n  ListOrientationHelper* orientation_helper =\n      layout_manager->GetOrientationHelper();\n\n  float target_start = orientation_helper->GetDecoratedStart(target_item);\n  float target_height;\n  if (target_view) {\n    auto view_rect = target_view->BoundsRelativeTo(target_item->GetView());\n    target_start += orientation_helper->GetRectStart(view_rect);\n    target_height = orientation_helper->GetRectEnd(view_rect) -\n                    orientation_helper->GetRectStart(view_rect);\n  } else if (target_rect_) {\n    target_start += orientation_helper->GetRectStart(target_rect_.value());\n    target_height = orientation_helper->GetRectEnd(target_rect_.value()) -\n                    orientation_helper->GetRectStart(target_rect_.value());\n  } else {\n    target_height = orientation_helper->GetDecoratedMeasure(target_item);\n  }\n\n  // height of visible area\n  int available_height = orientation_helper->GetEnd();\n\n  // return delta of view Top and target-Y\n  float res = target_start - offset_;\n  if (align_to_ == AlignTo::kMiddle) {\n    res -= round((available_height - target_height) / 2.f);\n  } else if (align_to_ == AlignTo::kEnd) {\n    res -= available_height - target_height;\n  } else if (align_to_ == AlignTo::kNone) {\n    if (0 <= res && res <= available_height - target_height) {\n      // cell need not scrolling when totally in visible area\n      res = 0;\n    } else if (res > available_height - target_height) {\n      // calculate as AlignTo::kBottom when scrolling to head\n      res -= available_height - target_height;\n    }\n  }\n  return res;\n}\n\n/**\n * Call this when you want to know the new location. If it returns true, the\n * animation is not yet finished.\n */\nbool ListScroller::ComputeScrollOffset(const float& distance,\n                                       const int64_t& now_time,\n                                       float& target_offset) {\n  // Any scroller can be used for time, since they were started\n  // together in scroll mode. We use X here.\n  int64_t elapsed_time = std::max<int64_t>(0, now_time - start_time_);\n  float target_distance;\n  if (elapsed_time < duration_time_) {\n    float q = GetInterpolation(static_cast<float>(elapsed_time) /\n                               duration_time_.value());\n    // Temporarily does not support x-axis movement\n    target_distance = std::round(q * distance);\n    target_offset = target_distance - last_distance_;\n    if (std::abs(distance - target_distance) < 1.0) {\n      target_offset = distance - last_distance_;\n      return false;\n    }\n  } else {\n    target_offset = distance - last_distance_;\n    return false;\n  }\n  last_distance_ = target_distance;\n  return true;\n}\n\nint ListScroller::ComputeScrollDuration(int dx, int dy) {\n  int abs_dx = std::abs(dx);\n  int abs_dy = std::abs(dy);\n  bool horizontal = abs_dx > abs_dy;\n  int container_size = horizontal ? list_view_->Width() : list_view_->Height();\n\n  float abs_delta = static_cast<float>(horizontal ? abs_dx : abs_dy);\n  int duration = static_cast<int>(((abs_delta / container_size) + 1) * 300);\n  return std::min(duration, 2000);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_scroller.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_SCROLLER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_SCROLLER_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\nnamespace internal {\n\nclass ScrollerAnimator;\n\n}  // namespace internal\n\nclass BaseListView;\nclass ListAdapter;\nclass ListChildrenHelper;\nclass ListLayoutManager;\n\n// This class handles the smooth scrolling.\nclass ListScroller {\n public:\n  explicit ListScroller(BaseListView* list_view);\n  ~ListScroller();\n\n  void ScrollToPosition(\n      bool smooth, int position, float offset, AlignTo align_to,\n      const std::string& id, const std::optional<FloatRect> target_rect,\n      std::function<void(uint32_t, const std::string&)> callback);\n  bool Scrolling() const;\n  void StopScroll(bool result = false);\n\n private:\n  FRIEND_TEST(ListLayoutManagerLinearTest, DISABLED_ScrollToPositionSmooth);\n  FRIEND_TEST(ListLayoutManagerLinearTest,\n              DISABLED_ScrollToPositionSmoothDataChange);\n\n  ListLayoutManager* GetLayoutManager() const;\n  ListAdapter* GetAdapter() const;\n  ListChildrenHelper* GetChildrenHelper() const;\n  AnimationHandler* GetAnimationHandler() const;\n\n  void StartAnimator();\n  void StopAnimator();\n\n  bool ScrollImmediately();\n  bool OnAnimation(int64_t frame_time);\n  void UpdatePositions();\n  bool IsFarAwayFromTarget(int to_scroll, float estimated_distance);\n  float DistanceToTarget(ListItemViewHolder* target_item);\n\n  bool ComputeScrollOffset(const float& distance, const int64_t& now_time,\n                           float& target_offset);\n\n  int ComputeScrollDuration(int dx, int dy);\n  int32_t CalculatePxPerFrame() const;\n\n  BaseListView* list_view_ = nullptr;\n  int position_ = ListItemViewHolder::kNoPosition;\n  std::string id_;\n  float offset_ = 0.f;\n  AlignTo align_to_ = AlignTo::kNone;\n  std::optional<FloatRect> target_rect_;  // Relative to item if has value\n  std::function<void(uint32_t, const std::string&)> callback_;\n\n  std::unique_ptr<internal::ScrollerAnimator> animator_;\n\n  int start_position_ = ListItemViewHolder::kNoPosition;\n  int end_position_ = ListItemViewHolder::kNoPosition;\n\n  int64_t start_time_;\n  std::optional<int64_t> duration_time_;\n  std::optional<float> target_distance_;\n  float last_distance_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_SCROLLER_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_view.h\"\n\n#include <memory>\n#include <sstream>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/list_children_helper.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_layout_manager.h\"\n#include \"clay/ui/component/list/list_layout_manager_linear.h\"\n#include \"clay/ui/component/list/lynx_list_adapter.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nnamespace {\n\nconst clay::Value* FindMapItem(const clay::Value::Map& map,\n                               const std::string& key) {\n  const auto it = map.find(key);\n  if (it != map.end()) {\n    return &it->second;\n  }\n  return nullptr;\n}\n\ninline int SafeGetInt(const clay::Value* value, int default_value = 0) {\n  if (!value) {\n    return default_value;\n  }\n  double result = default_value;\n  attribute_utils::TryGetNum(*value, result, default_value);\n  return result;\n}\n\ninline bool SafeGetBool(const clay::Value* value, bool default_value = false) {\n  if (!value) {\n    return default_value;\n  }\n  return attribute_utils::GetBool(*value, default_value);\n}\n\ninline std::string SafeGetString(const clay::Value* value,\n                                 std::string default_value = \"\") {\n  if (!value) {\n    return default_value;\n  }\n  std::string result = default_value;\n  attribute_utils::TryGetString(*value, result, default_value);\n  return result;\n}\n\n}  // namespace\n\nListView::ListView(int id, PageView* page_view) : ListView(id, id, page_view) {}\n\nListView::ListView(int id, int callback_id, PageView* page_view)\n    : BaseListView(id, callback_id, \"list\", page_view) {\n  InitAdapter();\n\n  SetLayoutManager(\n      ListLayoutManager::Create(layout_manager_type_, layout_span_count_));\n}\n\nListView::~ListView() { SetAdapter(nullptr); }\n\nvoid ListView::AddChild(BaseView* child, int index) {\n  BaseListView::AddChild(child, index);\n  if (waiting_for_child_.has_value()) {\n    FML_DCHECK(waiting_for_child_.value() != nullptr);\n    (*waiting_for_child_)->SetView(child);\n    waiting_for_child_.reset();\n  }\n}\n\nvoid ListView::DidUpdateAttributes() {\n  BaseView::DidUpdateAttributes();\n  if (is_no_diff_mode_) {\n    lynx_adapter_->UpdateActions(no_diff_info_.get());\n    no_diff_info_.reset();\n  } else {\n    lynx_adapter_->UpdateData();\n  }\n}\n\nstd::unique_ptr<LynxListData> ListView::GetListData() {\n  auto delegate = page_view_->GetUIComponentDelegate();\n  if (!delegate) {\n    return nullptr;\n  }\n  LynxListData* raw_ptr = delegate->OnListGetData(callback_id_);\n  return std::unique_ptr<LynxListData>(raw_ptr);\n}\n\nvoid ListView::UpdateChildPosition(ListItemViewHolder* item, int position) {\n  TRACE_EVENT(\"LIST\", \"ListView::UpdateChildPosition\");\n  auto delegate = page_view_->GetUIComponentDelegate();\n  if (!delegate) {\n    return;\n  }\n  delegate->OnUpdateChild(callback_id_, item->GetView()->id(), position,\n                          GenerateOperationId());\n}\n\nBaseView* ListView::ObtainChild(ListItemViewHolder* item, int position) {\n  TRACE_EVENT(\"LIST\", \"ListView::ObtainChild\");\n  auto delegate = page_view_->GetUIComponentDelegate();\n  if (!delegate) {\n    return nullptr;\n  }\n  int child_id =\n      delegate->OnObtainChild(callback_id_, position, GenerateOperationId());\n  return page_view_->FindViewByViewId(child_id);\n}\n\nvoid ListView::RecycleChild(BaseView* child) {\n  TRACE_EVENT(\"LIST\", \"ListView::RecycleChild\");\n  auto delegate = page_view_->GetUIComponentDelegate();\n  if (!delegate) {\n    return;\n  }\n  delegate->OnRecycleChild(callback_id_, child->id());\n}\n\nBaseView* ListView::HandleCreateView(ListItemViewHolder* item) {\n  auto delegate = page_view_->GetUIComponentDelegate();\n  if (!delegate) {\n    return nullptr;\n  }\n  waiting_for_child_ = item;\n  const int position = item->GetPosition();\n  delegate->OnCreateAddChild(callback_id_, position, 0);\n  FML_DCHECK(!waiting_for_child_.has_value());\n  BaseView* temp = item->GetView();\n\n  return temp;\n}\n\nvoid ListView::ProcessAdapterUpdates() {\n  BaseListView::ProcessAdapterUpdates();\n  lynx_adapter_->MarkUpdatesConsumed();\n}\n\nvoid ListView::OnLayoutComplete() {\n  if (!HasEvent(event_attr::kEventListLayoutComplete)) {\n    return;\n  }\n  Value::Array cells;\n  for (int i = 0; i < lynx_adapter_->GetItemCount(); i++) {\n    cells.emplace_back(lynx_adapter_->GetItemTypeName(i));\n  }\n  page_view_->SendEvent(callback_id_, event_attr::kEventListLayoutComplete,\n                        {\"timestamp\", \"cells\"},\n                        fml::TimePoint::Now().ToEpochDelta().ToMilliseconds(),\n                        cells);\n}\n\nvoid ListView::InitAdapter() {\n  lynx_adapter_ = std::make_unique<LynxListAdapter>(this);\n  SetAdapter(lynx_adapter_.get());\n}\n\nint64_t ListView::GenerateOperationId() {\n  return (static_cast<int64_t>(callback_id_) << 32) + op_counter_++;\n}\n\nvoid ListView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kUpdateListInfo) {\n    SetNoDiffInfo(value);\n    is_no_diff_mode_ = true;\n  } else {\n    BaseListView::SetAttribute(attr_c, value);\n  }\n}\nvoid ListView::SetNoDiffInfo(const clay::Value& value) {\n  if (!value.IsMap()) {\n    FML_DLOG(ERROR) << \"the value of 'update-list-info' must be a map\";\n    return;\n  }\n\n  auto info = std::make_unique<ListNoDiffInfo>();\n  const auto& map = value.GetMap();\n  auto update_actions = FindMapItem(map, \"updateAction\");\n  auto insert_actions = FindMapItem(map, \"insertAction\");\n  auto remove_actions = FindMapItem(map, \"removeAction\");\n\n  if (update_actions && update_actions->IsArray()) {\n    const auto& arr = update_actions->GetArray();\n    for (size_t i = 0; i < arr.size(); i++) {\n      const auto& map = arr[i].GetMap();\n      ListNoDiffInfo::UpdateAction action;\n      action.from = SafeGetInt(FindMapItem(map, \"from\"));\n      action.to = SafeGetInt(FindMapItem(map, \"to\"));\n      action.item_key = SafeGetString(FindMapItem(map, \"item-key\"));\n      action.type = SafeGetString(FindMapItem(map, \"type\"));\n      action.full_span = SafeGetBool(FindMapItem(map, \"full-span\"));\n      action.sticky_top = SafeGetBool(FindMapItem(map, \"sticky-top\"));\n      action.sticky_bottom = SafeGetBool(FindMapItem(map, \"sticky-bottom\"));\n      action.estimated_height_px =\n          SafeGetInt(FindMapItem(map, \"estimated-height-px\"), -1);\n      action.is_flush = SafeGetBool(FindMapItem(map, \"flush\"));\n      info->update_actions.push_back(action);\n    }\n  }\n  if (insert_actions && insert_actions->IsArray()) {\n    const auto& arr = insert_actions->GetArray();\n    for (size_t i = 0; i < arr.size(); i++) {\n      const auto& map = arr[i].GetMap();\n      ListNoDiffInfo::InsertAction action;\n      action.position = SafeGetInt(FindMapItem(map, \"position\"));\n      action.item_key = SafeGetString(FindMapItem(map, \"item-key\"));\n      action.type = SafeGetString(FindMapItem(map, \"type\"));\n      action.full_span = SafeGetBool(FindMapItem(map, \"full-span\"));\n      action.sticky_top = SafeGetBool(FindMapItem(map, \"sticky-top\"));\n      action.sticky_bottom = SafeGetBool(FindMapItem(map, \"sticky-bottom\"));\n      action.estimated_height_px =\n          SafeGetInt(FindMapItem(map, \"estimated-height-px\"), -1);\n      info->insert_actions.push_back(action);\n    }\n  }\n  if (remove_actions && remove_actions->IsArray()) {\n    const auto& arr = remove_actions->GetArray();\n    for (size_t i = 0; i < arr.size(); i++) {\n      info->remove_actions.push_back(SafeGetInt(&arr[i]));\n    }\n  }\n\n  no_diff_info_ = std::move(info);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_VIEW_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_VIEW_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/lynx_list_data.h\"\n\nnamespace clay {\n\nclass LynxListAdapter;\n\nclass ListView : public BaseListView {\n public:\n  ListView(int id, PageView* page_view);\n  ListView(int id, int callback_id, PageView* page_view);\n  ~ListView() override;\n\n  void AddChild(BaseView* child, int index) override;\n\n  bool CanChildrenEscape() const override { return false; }\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  void DidUpdateAttributes() override;\n\n  //---------- Called by LynxListAdapter START ----------\n  std::unique_ptr<LynxListData> GetListData();\n  void UpdateChildPosition(ListItemViewHolder* item, int position);\n  BaseView* ObtainChild(ListItemViewHolder* item, int position);\n  void RecycleChild(BaseView* child);\n  //---------- Called by LynxListAdapter END ------------\n\n protected:\n  BaseView* HandleCreateView(ListItemViewHolder* item) override;\n  void HandleDestroyView(BaseView* to_destroy,\n                         ListItemViewHolder* item) override {}\n  void ProcessAdapterUpdates() override;\n  void OnLayoutComplete() override;\n\n private:\n  void InitAdapter();\n  int64_t GenerateOperationId();\n  void SetNoDiffInfo(const clay::Value& value);\n  std::unique_ptr<LynxListAdapter> lynx_adapter_;\n  std::optional<ListItemViewHolder*> waiting_for_child_;\n  uint64_t op_counter_ = 0;\n  std::unique_ptr<ListNoDiffInfo> no_diff_info_;\n  bool is_no_diff_mode_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/list/list_wrapper.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/list_wrapper.h\"\n\n#include <math.h>\n\n#include <cmath>\n#include <string>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/list/list_common/layout_types.h\"\n#include \"clay/ui/component/list/list_layout_manager.h\"\n#include \"clay/ui/component/list/list_view.h\"\n#include \"clay/ui/component/scrollbar/scrollbar_orientation_helper.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n\nnamespace clay {\nnamespace {\n\nconst std::unordered_set<KeywordID> kProxyAttributes = {\n    KeywordID::kListType,\n    KeywordID::kColumnCount,\n    KeywordID::kLowerThreshold,\n    KeywordID::kUpperThreshold,\n    KeywordID::kLowerThresholdItemCount,\n    KeywordID::kUpperThresholdItemCount,\n    KeywordID::kScrollEventThrottle,\n    KeywordID::kEnableScroll,\n    KeywordID::kEnableNestedScroll,\n    KeywordID::kNeedsVisibleCells,\n    KeywordID::kUpdateAnimation,\n    KeywordID::kAlignFocus,\n    KeywordID::kSticky,\n    KeywordID::kFocusSmoothScroll,\n    KeywordID::kScrollWithoutFocus,\n    KeywordID::kScrollDirection,\n    KeywordID::kUpdateListInfo,\n    KeywordID::kListCrossAxisGap,\n    KeywordID::kListMainAxisGap,\n    KeywordID::kVerticalOrientation,\n    KeywordID::kScrollOrientation,\n    KeywordID::kBounce,\n    KeywordID::kBounces,\n    KeywordID::kInitialScrollIndex,\n    KeywordID::kStickyOffset,\n};\n\nLYNX_UI_METHOD_BEGIN(ListWrapper) {\n  LYNX_UI_METHOD(ListWrapper, scrollToPosition);\n  LYNX_UI_METHOD(ListWrapper, getVisibleItemsPositions);\n  LYNX_UI_METHOD(ListWrapper, getVisibleCells);\n}\nLYNX_UI_METHOD_END(ListWrapper);\n}  // namespace\n\nListWrapper::ListWrapper(BaseListView* inner_view, int32_t id, std::string tag,\n                         PageView* page_view)\n    : WithTypeInfo(id, ScrollDirection::kVertical, std::move(tag), page_view) {\n  inner_view->SetDelegate(this);\n  inner_view->AddScrollListener(this);\n\n  view_ = inner_view;\n  view_->SetOverflow(CSSProperty::OVERFLOW_HIDDEN);\n  view_->SetRepaintBoundary(true);\n  BaseView::AddChild(view_, 0);\n}\n\nListWrapper::ListWrapper(int32_t id, PageView* page_view)\n    : WithTypeInfo(id, ScrollDirection::kVertical, \"list\", page_view) {\n  BaseListView* inner_view = new ListView(-1, id, page_view);\n  inner_view->SetDelegate(this);\n  inner_view->AddScrollListener(this);\n\n  view_ = inner_view;\n  view_->SetOverflow(CSSProperty::OVERFLOW_HIDDEN);\n  view_->SetRepaintBoundary(true);\n  BaseView::AddChild(view_, 0);\n}\n\nListWrapper::~ListWrapper() {}\n\nvoid ListWrapper::OnDestroy() {\n  ScrollbarWrapper::OnDestroy();\n  static_cast<BaseListView*>(view_)->SetDelegate(nullptr);\n  static_cast<BaseListView*>(view_)->RemoveScrollListener(this);\n}\n\nvoid ListWrapper::OnLayout(LayoutContext* context) {\n  if (view_->NeedsLayout()) {\n    view_->Layout(context);\n  }\n}\n\nvoid ListWrapper::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kProxyAttributes.find(kw) != kProxyAttributes.end()) {\n    view_->SetAttribute(attr_c, value);\n  } else {\n    ScrollbarWrapper::SetAttribute(attr_c, value);\n  }\n}\n\nvoid ListWrapper::DidUpdateAttributes() {\n  BaseView::DidUpdateAttributes();\n  view_->DidUpdateAttributes();\n}\n\nvoid ListWrapper::scrollToPosition(const LynxModuleValues& args,\n                                   const LynxUIMethodCallback& callback) {\n  bool smooth = false;\n  int position = -1;\n  int index = -1;\n  float offset = 0;\n  std::string align_to_str;\n  std::string id;\n  CastNamedLynxModuleArgs(\n      {\"smooth\", \"position\", \"index\", \"offset\", \"alignTo\", \"id\"}, args, smooth,\n      position, index, offset, align_to_str, id);\n  if (isnan(offset) || isinf(offset) || (index == -1 && position == -1)) {\n    FML_DLOG(ERROR)\n        << \"scrollToPosition error! offset or position/index is invalid!\";\n    if (callback) {\n      callback(LynxUIMethodResult::kParamInvalid, clay::Value());\n    }\n    return;\n  }\n\n  AlignTo align_to = StringToAlign(align_to_str);\n  GetListView()->ScrollToPosition(\n      smooth, index == -1 ? position : index, FromLogical(offset), align_to, id,\n      [callback](uint32_t result, const std::string&) {\n        if (callback) {\n          callback(static_cast<LynxUIMethodResult>(result), clay::Value());\n        }\n      });\n}\n\nvoid ListWrapper::getVisibleItemsPositions(\n    const LynxModuleValues&, const LynxUIMethodCallback& callback) {\n  std::vector<float> left_array, right_array, top_array, bottom_array;\n  std::vector<int> position_array;\n  std::vector<std::string> id_array;\n  size_t visible_count = 0;\n  auto* layout_manager = GetListView()->GetLayoutManager();\n  visible_count = layout_manager->GetVisibleItemsInfo(top_array, bottom_array,\n                                                      left_array, right_array,\n                                                      position_array, id_array);\n\n  clay::Value::Array visible_position_array(visible_count);\n  for (size_t i = 0; i < visible_count; ++i) {\n    visible_position_array[i] = clay::Value(position_array[i]);\n  }\n  callback(LynxUIMethodResult::kSuccess,\n           clay::Value(std::move(visible_position_array)));\n}\n\nvoid ListWrapper::getVisibleCells(const LynxModuleValues&,\n                                  const LynxUIMethodCallback& callback) {\n  std::vector<float> lefts, rights, tops, bottoms;\n  std::vector<int> positions;\n  std::vector<std::string> ids;\n  auto* layout_manager = GetListView()->GetLayoutManager();\n  size_t visible_count = layout_manager->GetVisibleItemsInfo(\n      tops, bottoms, lefts, rights, positions, ids);\n\n  clay::Value::Array cells(visible_count);\n  for (size_t i = 0; i < visible_count; ++i) {\n    clay::Value::Map cell;\n    cell[\"id\"] = clay::Value(ids[i]);\n    cell[\"position\"] = clay::Value(positions[i]);\n    cell[\"top\"] = clay::Value(tops[i]);\n    cell[\"bottom\"] = clay::Value(bottoms[i]);\n    cell[\"left\"] = clay::Value(lefts[i]);\n    cell[\"right\"] = clay::Value(rights[i]);\n    cells[i] = clay::Value(std::move(cell));\n  }\n  callback(LynxUIMethodResult::kSuccess, clay::Value(std::move(cells)));\n}\n\nvoid ListWrapper::WillUpdateScrollbar() {}\n\nfloat ListWrapper::GetScrollbarScrollOffset() {\n  return GetListView()->GetScrollbarScrollOffset();\n}\n\nfloat ListWrapper::GetTotalLength() {\n  return GetListView()->GetTotalScrollLength();\n}\n\nvoid ListWrapper::OnScrollableScrolled() {\n  UpdateScrollbarIfNeeded();\n  scrollbar_->NotifyScrollViewScrolled();\n}\n\nvoid ListWrapper::OnListViewDidLayout() { UpdateScrollbarIfNeeded(); }\n\nvoid ListWrapper::UpdateScrollbarIfNeeded() {\n  if (scrollbar_enabled_) {\n    UpdateScrollbarLengths();\n    UpdateScrollbarPosition();\n  }\n}\n\n// Override ScrollbarView::Delegate\nvoid ListWrapper::OnScrollbarScrolled(float old_position, float new_position,\n                                      bool by_interaction, bool smooth) {\n  // Note: smooth scroll for listview's scrollbar is not implemented\n  if (by_interaction) {\n    const float delta =\n        (new_position - old_position) *\n        (scrollbar_->GetTotalLength() - scrollbar_->GetVisibleLength());\n    if (OrientationHelper().GetDirection() == ScrollDirection::kVertical) {\n      GetListView()->OnScrollBy({0.f, -delta});\n    } else {\n      GetListView()->OnScrollBy({-delta, 0.f});\n    }\n  }\n}\n\n#ifdef ENABLE_ACCESSIBILITY\nint32_t ListWrapper::GetSemanticsActions() const {\n  return GetListView()->GetSemanticsActions();\n}\n\nint32_t ListWrapper::GetSemanticsFlags() const {\n  return GetListView()->GetSemanticsFlags();\n}\n\nint32_t ListWrapper::GetA11yScrollChildren() const {\n  return GetListView()->GetA11yScrollChildren();\n}\n\nbool ListWrapper::IsAccessibilityElement() const {\n  bool result = BaseView::IsAccessibilityElement();\n  if (!result) {\n    for (auto child : GetListView()->GetChildren()) {\n      if (child->IsAccessibilityElement()) {\n        result = true;\n        break;\n      }\n    }\n  }\n  return result;\n}\n\nFloatRect ListWrapper::GetSemanticsBounds() const {\n  return GetListView()->GetSemanticsBounds();\n}\n#endif\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/list_wrapper.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LIST_WRAPPER_H_\n#define CLAY_UI_COMPONENT_LIST_LIST_WRAPPER_H_\n\n#include <string>\n#include <vector>\n\n#include \"clay/ui/component/list/base_list_view.h\"\n#include \"clay/ui/component/scrollbar/scrollbar_wrapper.h\"\n\nnamespace clay {\n\nclass ListWrapper : public WithTypeInfo<ListWrapper, ScrollbarWrapper>,\n                    public BaseListView::Delegate,\n                    public Scrollable::Listener {\n public:\n  ListWrapper(BaseListView* inner_view, int32_t id, std::string tag,\n              PageView* page_view);\n\n  ListWrapper(int32_t id, PageView* page_view);\n  ~ListWrapper() override;\n\n  bool IsLayoutRootCandidate() const override { return true; }\n  void OnLayout(LayoutContext* context) override;\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  void DidUpdateAttributes() override;\n\n#define UI_METHOD_LIST_DECLARATION(V) \\\n  V(scrollToPosition)                 \\\n  V(getVisibleItemsPositions)         \\\n  V(getVisibleCells)\n  UI_METHOD_LIST_DECLARATION(UI_METHOD_DEF);\n#undef UI_METHOD_LIST_DECLARATION\n\n  FocusBehavior GetFocusBehavior() const override {\n    return view_->GetFocusBehavior();\n  }\n\n#ifdef ENABLE_ACCESSIBILITY\n  int32_t GetSemanticsActions() const override;\n  int32_t GetSemanticsFlags() const override;\n  int32_t GetA11yScrollChildren() const override;\n  bool IsAccessibilityElement() const override;\n  FloatRect GetSemanticsBounds() const override;\n#endif\n\n  BaseListView* GetListView() const {\n    return static_cast<BaseListView*>(view_);\n  }\n\n private:\n  void OnDestroy() override;\n\n  void UpdateScrollbarIfNeeded();\n\n  // Override ScrollbarWrapper\n  void WillUpdateScrollbar() override;\n  float GetScrollbarScrollOffset() override;\n  float GetTotalLength() override;\n\n  // Override Scrollable::Listener\n  void OnScrollableScrolled() override;\n\n  // Override BaseListView::Delegate\n  void OnListViewDidLayout() override;\n\n  // Override ScrollbarView::Delegate\n  void OnScrollbarScrolled(float old_position, float new_position,\n                           bool by_interaction, bool smooth) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LIST_WRAPPER_H_\n"
  },
  {
    "path": "clay/ui/component/list/lynx_list_adapter.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/lynx_list_adapter.h\"\n\n#include <cstddef>\n#include <sstream>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/list/list_view.h\"\n#include \"clay/ui/component/list/lynx_list_item_view_holder.h\"\n#include \"clay/ui/component/list/macros.h\"\n\nnamespace clay {\n\nnamespace {\n\ntemplate <typename T>\nvoid RemoveVectorValue(std::vector<T>* vec, T value) {\n  for (auto it = vec->begin(); it != vec->end(); it++) {\n    if (*it == value) {\n      vec->erase(it);\n      return;\n    }\n  }\n}\n\n}  // namespace\n\nLynxListAdapter::LynxListAdapter(ListView* list_view)\n    : ListAdapter(list_view) {}\n\nLynxListAdapter::~LynxListAdapter() = default;\n\nint LynxListAdapter::GetItemCount() const {\n  if (list_data_) {\n    return list_data_->GetViewTypes().size();\n  }\n  return 0;\n}\n\nListAdapter::TypeId LynxListAdapter::GetItemViewType(int position) {\n  FML_DCHECK(list_data_);\n  const size_t index = list_data_->GetViewTypes()[position];\n  FML_DCHECK(index < list_data_->GetViewTypeNames().size());\n  const std::string& type_name = list_data_->GetViewTypeNames()[index];\n  return type_names_[type_name];\n}\n\nstd::string LynxListAdapter::GetItemTypeName(int position) const {\n  FML_DCHECK(list_data_);\n  const size_t index = list_data_->GetViewTypes()[position];\n  FML_DCHECK(index < list_data_->GetViewTypeNames().size());\n  return list_data_->GetViewTypeNames()[index];\n}\n\nvoid LynxListAdapter::UpdateData() {\n  bool full_flush = false;\n  if (list_data_ == nullptr) {\n    full_flush = true;\n  } else if (list_data_->GetViewTypes().size() ==\n             list_data_->GetFullSpan().size()) {\n    full_flush = true;\n  }\n\n  {\n    TRACE_EVENT(\"clay\", \"RK::GetListData\");\n    list_data_ = GetListView()->GetListData();\n  }\n\n  FML_DCHECK(list_data_->GetUpdateFrom().size() ==\n             list_data_->GetUpdateTo().size());\n  FML_DCHECK(list_data_->GetMoveFrom().size() ==\n             list_data_->GetMoveTo().size());\n\n  UpdateTypeNames();\n\n  bool diffable = list_data_ && list_data_->GetDiffable();\n  if (!full_flush && diffable && updates_consumed_) {\n    DispatchDataUpdate();\n  } else {\n    NotifyDataSetChanged();\n  }\n\n#if (DEBUG_LIST)\n  auto print_vector = [](const auto& vec) -> std::string {\n    std::stringstream ss;\n    for (auto element : vec) {\n      ss << element << \",\";\n    }\n    return ss.str();\n  };\n  LIST_LOG << \"After fetching list data from lynx\\n\";\n  LIST_LOG << \"ViewTypes:\" << print_vector(list_data_->GetViewTypes());\n  LIST_LOG << \"ViewTypeNames:\" << print_vector(list_data_->GetViewTypeNames());\n  LIST_LOG << \"FullSpan:\" << print_vector(list_data_->GetFullSpan());\n  LIST_LOG << \"Insertions:\" << print_vector(list_data_->GetInsertions());\n  LIST_LOG << \"Removals:\" << print_vector(list_data_->GetRemovals());\n  LIST_LOG << \"UpdateFrom:\" << print_vector(list_data_->GetUpdateFrom());\n  LIST_LOG << \"UpdateTo:\" << print_vector(list_data_->GetUpdateTo());\n  LIST_LOG << \"MoveFrom:\" << print_vector(list_data_->GetMoveFrom());\n  LIST_LOG << \"MoveTo:\" << print_vector(list_data_->GetMoveTo());\n#endif\n}\n\nvoid LynxListAdapter::UpdateActions(ListNoDiffInfo* info) {\n  if (!info || (info->update_actions.empty() && info->insert_actions.empty() &&\n                info->remove_actions.empty())) {\n    return;\n  }\n  if (!list_data_) {\n    list_data_ = std::make_unique<LynxListData>(0);\n  }\n  list_data_->SetNewArch(true);\n\n  list_data_->DispatchNoDiffActions(info);\n  list_data_->TransformExtraData();\n  FlushNoDiffActions(info);\n}\n\nvoid LynxListAdapter::PrintViewHolders() {\n#if (DEBUG_LIST)\n  int i = 0;\n  std::stringstream ss;\n  for (const auto& child : view_holders_) {\n    ss << \"\\n[\" << i << \"]\" << child->ToString();\n    ++i;\n  }\n  LIST_LOG << \"ViewHolders:\" << ss.str();\n#endif\n}\n\nListItemViewHolder* LynxListAdapter::OnCreateListItem(TypeId type) {\n  auto item = std::make_unique<LynxListItemViewHolder>(type);\n  view_holders_.emplace_back(std::move(item));\n  return view_holders_.back().get();\n}\n\nvoid LynxListAdapter::OnBindListItem(ListItemViewHolder* item,\n                                     int prev_position, int position,\n                                     bool newly_created) {\n  item->SetFullSpan(IsItemFullSpan(position));\n  item->SetStickyType(IsItemStickyTop(position) ? ListItemStickyType::kTop\n                      : IsItemStickyBottom(position)\n                          ? ListItemStickyType::kBottom\n                          : ListItemStickyType::kNone);\n\n  if (IsNewArch()) {\n    OnBindListItemOnNewArch(item, position);\n    return;\n  }\n\n  if (newly_created) {\n    return;\n  }\n\n  const Payloads& payloads = item->GetPayloads();\n  if (payloads.size() == 0) {\n    LIST_LOG << \"item type:\" << item->GetItemViewType()\n             << \" view_id:\" << item->GetView()->id()\n             << \" move from:\" << prev_position << \" to:\" << position;\n    GetListView()->UpdateChildPosition(item, position);\n  } else {\n    const auto* lynx_payload =\n        static_cast<LynxListPayload*>(payloads.back().get());\n    LIST_LOG << \"item type:\" << item->GetItemViewType()\n             << \" view_id:\" << item->GetView()->id()\n             << \" move from:\" << prev_position << \" to:\" << position\n             << \" payload:\" << lynx_payload->update_to_position;\n    GetListView()->UpdateChildPosition(item, lynx_payload->update_to_position);\n  }\n}\n\nvoid LynxListAdapter::OnBindListItemOnNewArch(ListItemViewHolder* item,\n                                              int position) {\n  auto child = GetListView()->ObtainChild(item, position);\n  if (child != item->GetView()) {\n    if (child->Parent()) {\n      child->Parent()->BaseView::RemoveChild(child);\n    }\n    // recycle item\n    if (item->GetView()) {\n      GetListView()->RecycleChild(item->GetView());\n      item->SetView(nullptr);\n    } else {\n      // Set the initial visibility to false because the just created item is\n      // not  attached yet.\n      // We don't call `SetViewVisible()` here because we don't want to send\n      // NodeDisappear() notification for a newly created item.\n      child->SetVisible(false);\n    }\n    item->SetView(child);\n  }\n}\n\nvoid LynxListAdapter::OnDeleteListItem(ListItemViewHolder* view_holder) {\n  if (auto view = view_holder->GetView()) {\n    view->DestroyAllChildren();\n    view->Destroy();\n    delete view;\n    view_holder->SetView(nullptr);\n  }\n}\n\nvoid LynxListAdapter::OnRecycleItem(ListItemViewHolder* item) {\n  ListAdapter::OnRecycleItem(item);\n  // If the item is prefetched, we might use it in the future.\n  // So we should not recycle it(another word, should not recycle its dom node,\n  // just let it be).\n  if (IsNewArch() && !item->IsPrefetch()) {\n    GetListView()->RecycleChild(item->GetView());\n    item->SetView(nullptr);\n  }\n}\n\nvoid LynxListAdapter::UpdateTypeNames() {\n  if (list_data_) {\n    for (const std::string& type_name : list_data_->GetViewTypeNames()) {\n      if (type_names_.find(type_name) == type_names_.end()) {\n        type_names_[type_name] = type_names_.size();\n      }\n    }\n  }\n}\n\nListView* LynxListAdapter::GetListView() {\n  return static_cast<ListView*>(list_view_);\n}\n\nvoid LynxListAdapter::DispatchDataUpdate() {\n  FML_DCHECK(list_data_);\n\n  if (!list_data_->GetRemovals().empty() ||\n      !list_data_->GetInsertions().empty() ||\n      !list_data_->GetUpdateFrom().empty() ||\n      !list_data_->GetUpdateTo().empty() ||\n      !list_data_->GetMoveFrom().empty() || !list_data_->GetMoveTo().empty()) {\n    updates_consumed_ = false;\n  }\n\n  for (size_t i = 0; i < list_data_->GetUpdateFrom().size(); ++i) {\n    NotifyItemRangeChanged(\n        list_data_->GetUpdateFrom()[i], 1,\n        std::make_unique<LynxListPayload>(list_data_->GetUpdateTo()[i]));\n  }\n\n  for (size_t i = 0; i < list_data_->GetMoveFrom().size(); ++i) {\n    NotifyItemMoved(list_data_->GetMoveFrom()[i], list_data_->GetMoveTo()[i]);\n  }\n\n  for (int i = list_data_->GetRemovals().size() - 1; i >= 0; --i) {\n    NotifyItemRangeRemoved(list_data_->GetRemovals()[i], 1);\n  }\n\n  for (int i : list_data_->GetInsertions()) {\n    NotifyItemRangeInserted(i, 1);\n  }\n}\n\nbool LynxListAdapter::IsItemFullSpan(int position) const {\n  if (!list_data_) {\n    return false;\n  }\n\n  const auto& full_spans = list_data_->GetFullSpan();\n  return std::binary_search(full_spans.begin(), full_spans.end(), position);\n}\n\nbool LynxListAdapter::IsItemStickyTop(int position) const {\n  if (!list_data_) {\n    return false;\n  }\n\n  const auto& items = list_data_->GetStickyTop();\n  return std::binary_search(items.begin(), items.end(), position);\n}\n\nbool LynxListAdapter::IsItemStickyBottom(int position) const {\n  if (!list_data_) {\n    return false;\n  }\n\n  const auto& items = list_data_->GetStickyBottom();\n  return std::binary_search(items.begin(), items.end(), position);\n}\n\nbool LynxListAdapter::IsNewArch() const {\n  return list_data_ && list_data_->GetNewArch();\n}\n\nvoid LynxListAdapter::MarkUpdatesConsumed() { updates_consumed_ = true; }\n\nvoid LynxListAdapter::FlushNoDiffActions(ListNoDiffInfo* info) {\n  for (auto position : info->remove_actions) {\n    NotifyItemRangeRemoved(position, 1);\n  }\n  for (const auto& action : info->insert_actions) {\n    NotifyItemRangeInserted(action.position, 1);\n  }\n  for (const auto& action : info->update_actions) {\n    if (action.is_flush) {\n      NotifyItemRangeChanged(action.from, 1,\n                             std::make_unique<LynxListPayload>(action.to));\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/lynx_list_adapter.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LYNX_LIST_ADAPTER_H_\n#define CLAY_UI_COMPONENT_LIST_LYNX_LIST_ADAPTER_H_\n\n#include <list>\n#include <memory>\n#include <string>\n#include <unordered_map>\n\n#include \"clay/ui/component/list/list_adapter.h\"\n#include \"clay/ui/component/list/lynx_list_data.h\"\n\nnamespace clay {\n\nclass ListView;\nclass LynxListItemViewHolder;\n\nstruct LynxListPayload : ListAdapter::Payload {\n  int update_to_position;\n  explicit LynxListPayload(int position) : update_to_position(position) {\n    is_lynx_payload = true;\n  }\n};\n\nclass LynxListAdapter : public ListAdapter {\n public:\n  explicit LynxListAdapter(ListView* list_view);\n  virtual ~LynxListAdapter();\n\n  int GetItemCount() const override;\n  TypeId GetItemViewType(int position) override;\n  std::string GetItemTypeName(int position) const;\n\n  void UpdateData();\n\n  // Similar with UpdateData, but used in Lynx NoDiff\n  void UpdateActions(ListNoDiffInfo* info);\n\n  void MarkUpdatesConsumed();\n\n  bool IsItemFullSpan(int position) const override;\n  bool IsItemStickyTop(int position) const override;\n  bool IsItemStickyBottom(int position) const override;\n  bool IsNewArch() const override;\n  void PrintViewHolders() override;\n\n protected:\n  // Override ListAdapter\n  ListItemViewHolder* OnCreateListItem(TypeId type) override;\n  void OnBindListItem(ListItemViewHolder* item, int prev_position, int position,\n                      bool newly_created) override;\n  void OnBindListItemOnNewArch(ListItemViewHolder* item, int position);\n  void OnDeleteListItem(ListItemViewHolder* view_holder) override;\n  void OnRecycleItem(ListItemViewHolder* item) override;\n\n private:\n  void UpdateTypeNames();\n  ListView* GetListView();\n  // Notify `observer_` data update.\n  void DispatchDataUpdate();\n\n  void FlushNoDiffActions(ListNoDiffInfo* info);\n\n  // Note(Xietong): Don't get confused with this member and `view_type_names_`\n  // in `LynxListData`. `view_type_names_` is only used to avoid copying type\n  // name (a string) for each position. The same index may have different type\n  // names when the list data is updated. For example:\n  // When the first list_data_ is updated, there are two types: header and\n  // footer. Then type name at index 1 is \"footer\".\n  // The next time, there are three types: \"header\", \"data\" and \"footer\". Then\n  // type name at index 1 becomes \"data\".\n  // We should maintain our own mapping so that the same type name can have the\n  // same type id.\n  std::unordered_map<std::string, int> type_names_;\n  std::unique_ptr<LynxListData> list_data_;\n  /*\n   * Truth of the source.\n   */\n  std::list<std::unique_ptr<LynxListItemViewHolder>> view_holders_;\n\n  bool updates_consumed_ = true;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LYNX_LIST_ADAPTER_H_\n"
  },
  {
    "path": "clay/ui/component/list/lynx_list_data.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/lynx_list_data.h\"\n\n#include <algorithm>\n#include <string>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nLynxListData::LynxListData(int count) : count_(count) {\n  view_types_.reserve(count_);\n}\n\nLynxListData::~LynxListData() = default;\n\nvoid LynxListData::PushViewType(const char* name) {\n  FML_DCHECK(name);\n  FML_DCHECK((int)view_types_.size() < count_);\n  view_types_.push_back(GetViewTypeIndex(name));\n}\n\nvoid LynxListData::SetFullSpan(const uint32_t* indices, int size) {\n  full_span_.clear();\n  full_span_.insert(full_span_.end(), indices, indices + size);\n  std::sort(full_span_.begin(), full_span_.end());\n}\n\nvoid LynxListData::SetStickyTop(const uint32_t* indices, int size) {\n  sticky_top_.clear();\n  sticky_top_.insert(sticky_top_.end(), indices, indices + size);\n  std::sort(sticky_top_.begin(), sticky_top_.end());\n}\n\nvoid LynxListData::SetStickyBottom(const uint32_t* indices, int size) {\n  sticky_bottom_.clear();\n  sticky_bottom_.insert(sticky_bottom_.end(), indices, indices + size);\n  std::sort(sticky_bottom_.begin(), sticky_bottom_.end());\n}\n\nvoid LynxListData::SetInsertions(const int32_t* indices, int size) {\n  insertions_.clear();\n  insertions_.insert(insertions_.end(), indices, indices + size);\n}\n\nvoid LynxListData::SetRemovals(const int32_t* indices, int size) {\n  removals_.clear();\n  removals_.insert(removals_.end(), indices, indices + size);\n}\n\nvoid LynxListData::SetUpdateFrom(const int32_t* indices, int size) {\n  update_from_.clear();\n  update_from_.insert(update_from_.end(), indices, indices + size);\n}\n\nvoid LynxListData::SetUpdateTo(const int32_t* indices, int size) {\n  update_to_.clear();\n  update_to_.insert(update_to_.end(), indices, indices + size);\n}\n\nvoid LynxListData::SetMoveFrom(const int32_t* indices, int size) {\n  move_from_.clear();\n  move_from_.insert(move_from_.end(), indices, indices + size);\n}\n\nvoid LynxListData::SetMoveTo(const int32_t* indices, int size) {\n  move_to_.clear();\n  move_to_.insert(move_to_.end(), indices, indices + size);\n}\n\nvoid LynxListData::InsertViewType(int index, const std::string& type_name) {\n  if (static_cast<size_t>(index) > view_types_.size()) {\n    FML_DCHECK(false)\n        << \"[LIST][LynxListData::InsertViewType] index out of bounds (\" << index\n        << \"/\" << view_types_.size() << \")\";\n    return;\n  }\n  view_types_.insert(view_types_.begin() + index, GetViewTypeIndex(type_name));\n}\n\nvoid LynxListData::SetViewType(int index, const std::string& type_name) {\n  if (static_cast<size_t>(index) >= view_types_.size()) {\n    FML_DCHECK(false)\n        << \"[LIST][LynxListData::SetViewType] index out of bounds (\" << index\n        << \"/\" << view_types_.size() << \")\";\n    return;\n  }\n  view_types_[index] = GetViewTypeIndex(type_name);\n}\n\nvoid LynxListData::RemoveViewType(int index) {\n  if (static_cast<int>(view_types_.size()) > index) {\n    view_types_.erase(view_types_.begin() + index);\n  } else {\n    FML_DCHECK(false) << \"[LIST] The index(\" << index\n                      << \") to be removed does not exist\";\n  }\n}\n\nint LynxListData::GetViewTypeIndex(const std::string& type_name) {\n  int name_index;\n\n  // Find if the name has been registered or not\n  auto itr = registered_.find(type_name);\n  if (itr != registered_.end()) {\n    name_index = itr->second;\n  } else {\n    name_index = view_type_names_.size();\n    view_type_names_.emplace_back(type_name);\n    registered_.emplace(type_name, name_index);\n  }\n\n  return name_index;\n}\n\nvoid LynxListData::DispatchNoDiffActions(ListNoDiffInfo* info) {\n  for (int i = info->remove_actions.size() - 1; i >= 0; i--) {\n    int position = info->remove_actions[i];\n    RemoveViewType(position);\n    fiber_full_span_.erase(fiber_full_span_.begin() + position);\n    fiber_sticky_top_.erase(fiber_sticky_top_.begin() + position);\n    fiber_sticky_bottom_.erase(fiber_sticky_bottom_.begin() + position);\n  }\n\n  for (const auto& action : info->insert_actions) {\n    int position = action.position;\n    InsertViewType(position, action.type);\n    fiber_full_span_.insert(fiber_full_span_.begin() + position,\n                            action.full_span);\n    fiber_sticky_top_.insert(fiber_sticky_top_.begin() + position,\n                             action.sticky_top);\n    fiber_sticky_bottom_.insert(fiber_sticky_bottom_.begin() + position,\n                                action.sticky_bottom);\n  }\n\n  for (const auto& action : info->update_actions) {\n    SetViewType(action.from, action.type);\n    fiber_full_span_[action.from] = action.full_span;\n    fiber_sticky_top_[action.from] = action.sticky_top;\n    fiber_sticky_bottom_[action.from] = action.sticky_bottom;\n  }\n}\n\nvoid LynxListData::TransformExtraData() {\n  full_span_.clear();\n  for (size_t i = 0; i < fiber_full_span_.size(); i++) {\n    if (fiber_full_span_[i]) {\n      full_span_.push_back(i);\n    }\n  }\n\n  sticky_top_.clear();\n  for (size_t i = 0; i < fiber_sticky_top_.size(); i++) {\n    if (fiber_sticky_top_[i]) {\n      sticky_top_.push_back(i);\n    }\n  }\n\n  sticky_bottom_.clear();\n  for (size_t i = 0; i < fiber_sticky_bottom_.size(); i++) {\n    if (fiber_sticky_bottom_[i]) {\n      sticky_bottom_.push_back(i);\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/lynx_list_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LYNX_LIST_DATA_H_\n#define CLAY_UI_COMPONENT_LIST_LYNX_LIST_DATA_H_\n\n#include <algorithm>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\nnamespace clay {\n\nstruct ListNoDiffInfo {\n  struct Action {\n    std::string item_key;\n    std::string type;\n    bool full_span;\n    bool sticky_top;\n    bool sticky_bottom;\n    int estimated_height_px;\n  };\n  struct UpdateAction : Action {\n    int from;\n    int to;\n    bool is_flush;\n  };\n  struct InsertAction : Action {\n    int position;\n  };\n\n  std::vector<UpdateAction> update_actions;\n  std::vector<int> remove_actions;\n  std::vector<InsertAction> insert_actions;\n};\n\nclass LynxListData {\n public:\n  explicit LynxListData(int count);\n  ~LynxListData();\n\n  void PushViewType(const char* name);\n\n  const std::vector<int>& GetViewTypes() const { return view_types_; }\n  const std::vector<std::string>& GetViewTypeNames() const {\n    return view_type_names_;\n  }\n\n  void SetNewArch(bool new_arch) { new_arch_ = new_arch; }\n  void SetDiffable(bool diffable) { diffable_ = diffable; }\n  void SetFullSpan(const uint32_t* indices, int size);\n  void SetStickyTop(const uint32_t* indices, int size);\n  void SetStickyBottom(const uint32_t* indices, int size);\n  void SetInsertions(const int32_t* indices, int size);\n  void SetRemovals(const int32_t* indices, int size);\n  void SetUpdateFrom(const int32_t* indices, int size);\n  void SetUpdateTo(const int32_t* indices, int size);\n  void SetMoveFrom(const int32_t* indices, int size);\n  void SetMoveTo(const int32_t* indices, int size);\n\n  void SetFullSpan(const std::vector<int32_t>& indices) {\n    full_span_.clear();\n    full_span_.insert(full_span_.end(), indices.begin(), indices.end());\n    std::sort(full_span_.begin(), full_span_.end());\n  }\n\n  void SetStickyTop(const std::vector<int32_t>& indices) {\n    sticky_top_.clear();\n    sticky_top_.insert(sticky_top_.end(), indices.begin(), indices.end());\n    std::sort(sticky_top_.begin(), sticky_top_.end());\n  }\n\n  void SetStickyBottom(const std::vector<int32_t>& indices) {\n    sticky_bottom_.clear();\n    sticky_bottom_.insert(sticky_bottom_.end(), indices.begin(), indices.end());\n    std::sort(sticky_bottom_.begin(), sticky_bottom_.end());\n  }\n\n  void SetInsertions(const std::vector<int32_t>& indices) {\n    insertions_.clear();\n    insertions_.insert(insertions_.end(), indices.begin(), indices.end());\n  }\n\n  void SetRemovals(const std::vector<int32_t>& indices) {\n    removals_.clear();\n    removals_.insert(removals_.end(), indices.begin(), indices.end());\n  }\n\n  void SetUpdateFrom(const std::vector<int32_t>& indices) {\n    update_from_.clear();\n    update_from_.insert(update_from_.end(), indices.begin(), indices.end());\n  }\n\n  void SetUpdateTo(const std::vector<int32_t>& indices) {\n    update_to_.clear();\n    update_to_.insert(update_to_.end(), indices.begin(), indices.end());\n  }\n\n  void SetMoveFrom(const std::vector<int32_t>& indices) {\n    move_from_.clear();\n    move_from_.insert(move_from_.end(), indices.begin(), indices.end());\n  }\n\n  void SetMoveTo(const std::vector<int32_t>& indices) {\n    move_to_.clear();\n    move_to_.insert(move_to_.end(), indices.begin(), indices.end());\n  }\n\n  bool GetNewArch() const { return new_arch_; }\n  bool GetDiffable() const { return diffable_; }\n  const std::vector<int32_t>& GetFullSpan() const { return full_span_; }\n  const std::vector<int32_t>& GetStickyTop() const { return sticky_top_; }\n  const std::vector<int32_t>& GetStickyBottom() const { return sticky_bottom_; }\n  const std::vector<int32_t>& GetInsertions() const { return insertions_; }\n  const std::vector<int32_t>& GetRemovals() const { return removals_; }\n  const std::vector<int32_t>& GetUpdateFrom() const { return update_from_; }\n  const std::vector<int32_t>& GetUpdateTo() const { return update_to_; }\n  const std::vector<int32_t>& GetMoveFrom() const { return move_from_; }\n  const std::vector<int32_t>& GetMoveTo() const { return move_to_; }\n\n  void DispatchNoDiffActions(ListNoDiffInfo* info);\n  void TransformExtraData();\n\n private:\n  int GetViewTypeIndex(const std::string& type_name);\n  void InsertViewType(int index, const std::string& type_name);\n  void SetViewType(int index, const std::string& type_name);\n  void RemoveViewType(int index);\n\n  int count_;\n  std::vector<int> view_types_;\n  std::vector<std::string> view_type_names_;\n  std::unordered_map<std::string, int> registered_;\n  bool new_arch_ = false;\n  bool diffable_ = false;\n  std::vector<int32_t> full_span_;\n  std::vector<int32_t> sticky_top_;\n  std::vector<int32_t> sticky_bottom_;\n  std::vector<int32_t> insertions_;\n  std::vector<int32_t> removals_;\n  std::vector<int32_t> update_from_;\n  std::vector<int32_t> update_to_;\n  std::vector<int32_t> move_from_;\n  std::vector<int32_t> move_to_;\n\n  std::vector<int32_t> fiber_full_span_;\n  std::vector<int32_t> fiber_sticky_top_;\n  std::vector<int32_t> fiber_sticky_bottom_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LYNX_LIST_DATA_H_\n"
  },
  {
    "path": "clay/ui/component/list/lynx_list_item_view_holder.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/list/lynx_list_item_view_holder.h\"\n\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nLynxListItemViewHolder::LynxListItemViewHolder(ListAdapter::TypeId type)\n    : type_(type) {}\n\nLynxListItemViewHolder::~LynxListItemViewHolder() = default;\n\nMeasureResult LynxListItemViewHolder::Measure(\n    const MeasureConstraint& constraint) {\n  MeasureResult res;\n  if (!GetView()) {\n    return res;\n  }\n\n  {\n    // Setting width/height should not dirty the layout bit.\n    BaseView::LayoutIgnoreHelper helper(GetView());\n\n    // The item's size is set by Lynx (through `ViewContext::SetBounds()`), so\n    // we don't change the actual size here. Instead, we set the desired size to\n    // `layout_size_`, which may be different from the actual size in grid\n    // layout for alignment purpose.\n    if (constraint.width_mode != MeasureMode::kIndefinite &&\n        constraint.width.has_value()) {\n      layout_size_.SetWidth(constraint.width.value());\n    } else {\n      layout_size_.SetWidth(GetView()->Width());\n    }\n\n    if (constraint.height_mode != MeasureMode::kIndefinite &&\n        constraint.height.has_value()) {\n      layout_size_.SetHeight(constraint.height.value());\n    } else {\n      layout_size_.SetHeight(GetView()->Height());\n    }\n  }\n  // Call layout to clear the dirty bit.\n  GetView()->Layout();\n\n  res.width = layout_size_.width();\n  res.height = layout_size_.height();\n  return res;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/list/lynx_list_item_view_holder.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_LYNX_LIST_ITEM_VIEW_HOLDER_H_\n#define CLAY_UI_COMPONENT_LIST_LYNX_LIST_ITEM_VIEW_HOLDER_H_\n\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n\nnamespace clay {\n\nclass LynxListItemViewHolder : public ListItemViewHolder {\n public:\n  explicit LynxListItemViewHolder(ListAdapter::TypeId type);\n  virtual ~LynxListItemViewHolder();\n\n  MeasureResult Measure(const MeasureConstraint& constraint) override;\n\n  ListAdapter::TypeId GetItemViewType() const override { return type_; }\n\n  float GetWidth() const override { return layout_size_.width(); }\n  float GetHeight() const override { return layout_size_.height(); }\n\n private:\n  // Hint what size we want item to have, won't really set to item.\n  FloatSize layout_size_;\n\n  ListAdapter::TypeId type_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_LIST_LYNX_LIST_ITEM_VIEW_HOLDER_H_\n"
  },
  {
    "path": "clay/ui/component/list/macros.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_LIST_MACROS_H_\n#define CLAY_UI_COMPONENT_LIST_MACROS_H_\n\n#define LIST_DEBUG_TAG \"[Clay]-[LIST] \"\n#if (DEBUG_LIST)\n#define LIST_LOG FML_LOG(ERROR) << LIST_DEBUG_TAG\n#else\n#define LIST_LOG FML_EAT_STREAM_PARAMETERS(true)\n#endif\n\n#endif  // CLAY_UI_COMPONENT_LIST_MACROS_H_\n"
  },
  {
    "path": "clay/ui/component/measurable.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_MEASURABLE_H_\n#define CLAY_UI_COMPONENT_MEASURABLE_H_\n\n#include \"clay/ui/common/measure_constraint.h\"\n\nnamespace clay {\n\nclass Measurable {\n public:\n  virtual void Measure(const MeasureConstraint& constraint,\n                       MeasureResult& result) = 0;\n};\n\nclass CustomMeasurable {\n public:\n  virtual MeasureResult Measure(const MeasureConstraint& constraint) = 0;\n  virtual void Align() = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_MEASURABLE_H_\n"
  },
  {
    "path": "clay/ui/component/mouse_cursor.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_MOUSE_CURSOR_H_\n#define CLAY_UI_COMPONENT_MOUSE_CURSOR_H_\n\n#include <string>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/platform/cursor_types.h\"\n\nnamespace clay {\n\n// It allows specifying the coordinates of the cursor's hotspot, which will be\n// clamped to the boundaries of the cursor image. If none are specified, the\n// coordinates of the hotspot are read from the file itself (for CUR and XBM\n// files) or are set to the top left corner of the image.\nstruct Hotspot {\n  int x;\n  int y;\n  explicit Hotspot(int x_val = -1, int y_val = -1) : x(x_val), y(y_val) {}\n};\n\nstruct Cursor {\n  CursorTypes type;\n  std::string val;\n  Hotspot hspot;\n  Cursor(CursorTypes m_type, const std::string& m_str, int x = -1, int y = -1)\n      : type(m_type), val(m_str), hspot(x, y) {}\n};\n\nclass MouseCursor {\n public:\n  MouseCursor() = default;\n  explicit MouseCursor(const std::vector<std::string>& cursor_vec) {\n    // see [https://developer.mozilla.org/zh-CN/docs/Web/CSS/cursor]\n    // cursor_vec look like:\n    // image/path\n    // image/path x y\n    // keyword\n    const char identify = ' ';\n\n    for (auto& one_url : cursor_vec) {\n      int index = one_url.find(identify);\n      if (index == -1) {\n        cursors_.emplace_back(CursorTypeUtil::ParseCursorType(one_url),\n                              one_url);\n        continue;\n      }\n\n      const int len = one_url.length();\n      int x = -1;\n      int y = -1;\n      std::string cursor_path = one_url.substr(0, index);\n      std::string temp = \"\";\n\n      // parse x\n      while (index < len && one_url[index] == ' ') {\n        ++index;\n      }\n\n      while (index < len) {\n        if (one_url[index] == ' ') break;\n        temp += one_url[index];\n        index++;\n      }\n\n      if (!temp.empty()) {\n        x = std::atoi(temp.c_str());\n        temp = \"\";\n      }\n\n      // parse y\n      while (index < len && one_url[index] == ' ') {\n        ++index;\n      }\n\n      while (index < len) {\n        if (one_url[index] == ' ') break;\n        temp += one_url[index];\n        index++;\n      }\n\n      if (!temp.empty()) {\n        y = std::atoi(temp.c_str());\n      }\n      cursors_.emplace_back(CursorTypeUtil::ParseCursorType(cursor_path),\n                            cursor_path, x, y);\n    }\n  }\n\n  const std::vector<Cursor>& GetCursors() { return cursors_; }\n  void AddCursor(const Cursor& cursor) { cursors_.emplace_back(cursor); }\n\n private:\n  std::vector<Cursor> cursors_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_MOUSE_CURSOR_H_\n"
  },
  {
    "path": "clay/ui/component/native_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/native_view.h\"\n\n#include <cstdint>\n#include <tuple>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/common/graphics/shared_image_external_texture.h\"\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n#include \"clay/ui/rendering/render_external_content.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\nNativeView::NativeView(int id, std::string tag, PageView* page_view)\n    : WithTypeInfo(id, std::move(tag),\n                   std::make_unique<RenderExternalContent>(), page_view) {\n  Puppet<Owner::kUI, NativeViewService> native_view_service =\n      page_view->GetServiceManager()->GetService<NativeViewService>();\n  native_view_plugin_ = native_view_service.CreateObjectInActorThread(\n      [id, self_ptr = this](auto& service) {\n        return service.CreateNativeViewPlugin(id, self_ptr);\n      });\n  native_view_plugin_.Act(\n      [tag = GetName()](auto& plugin)\n          -> std::tuple<fml::RefPtr<SharedImageSink>, bool, bool, bool> {\n        if (!plugin.OnCreate(tag)) {\n          return std::make_tuple(nullptr, false, false, false);\n        }\n        return std::make_tuple(plugin.GetSharedImageSink(),\n                               plugin.SupportHybridComposition(),\n                               plugin.SupportScrolling(), true);\n      },\n      [weak_self = fml::WeakPtr<NativeView>(GetWeakPtr())](\n          std::tuple<fml::RefPtr<SharedImageSink>, bool, bool, bool> ret) {\n        if (weak_self) {\n          auto& [image_sink, support_hybrid_composition, support_scrolling,\n                 available] = ret;\n          auto content =\n              static_cast<RenderExternalContent*>(weak_self->render_object());\n          if (image_sink) {\n            auto texture =\n                std::make_shared<SharedImageExternalTexture>(image_sink);\n            weak_self->page_view()->RegisterDrawableImage(texture);\n            weak_self->tex_id_ = texture->Id();\n            content->SetDrawableImageId(*weak_self->tex_id_);\n            content->SetFitMode(DrawableImage::FitMode::kClipToBounds);\n            content->SetRenderMode(\n                RenderExternalContent::RenderMode::kExternalTexture);\n          } else if (support_hybrid_composition) {\n            content->SetViewId(weak_self->id());\n          }\n          weak_self->is_scroll_enabled_ = support_scrolling;\n          weak_self->is_available_ = available;\n        }\n      });\n  SetFocusable(true);\n}\n\nvoid NativeView::FocusHasChanged(bool focused, bool is_leaf) {\n  native_view_plugin_.Act([focused, is_leaf](auto& plugin) {\n    plugin.OnFocusChanged(focused, is_leaf);\n  });\n  BaseView::FocusHasChanged(focused, is_leaf);\n}\n\nvoid NativeView::SendMotionEvent(const PointerEvent& point_event,\n                                 const FloatPoint& transformed_postion) {\n  native_view_plugin_.Act(\n      [point_event,\n       transformed_postion = page_view()->ConvertTo<kPixelTypePlatform>(\n           transformed_postion)](auto& plugin) {\n        return plugin.OnTouchEvent(point_event, transformed_postion);\n      });\n}\n\n// This function is easily confused with the destructor.\n// Although 'Destroy' will be called first then the destructor  second\n// currently. Maybe we can reactor this and make the destruction process more\n// unified.\nvoid NativeView::OnDestroy() {\n  native_view_plugin_.Act([](auto& plugin) { return plugin.OnDestroy(); });\n  if (tex_id_.has_value()) {\n    page_view_->UnregisterDrawableImage(*tex_id_);\n  }\n}\n\nvoid NativeView::SetPaddings(float padding_left, float padding_top,\n                             float padding_right, float padding_bottom) {\n  // we set padding to platform view to avoid hit zone too small when we add\n  // paddings. platform unit , we just pass it to platform\n  native_view_plugin_.Act(\n      [padding_left, padding_top, padding_right, padding_bottom](auto& plugin) {\n        plugin.UpdatePaddings(padding_left, padding_top, padding_right,\n                              padding_bottom);\n      });\n}\n\nvoid NativeView::DidUpdateAttributes() {\n  // TODO(liuguoliang): Should strip render properties like border, padding to\n  // prevent Lynx renders border again.\n  BaseView::DidUpdateAttributes();\n  // Apply all attributes which in the cache once.\n  clay::Value::Array events;\n  if (events_) {\n    for (const auto& event : *events_) {\n      events.emplace_back(event);\n    }\n  }\n  native_view_plugin_.Act(\n      [staging_attrs = std::move(staging_attrs_), events = std::move(events),\n       weak_self = fml::WeakPtr<NativeView>(GetWeakPtr())](auto& plugin) {\n        plugin.UpdatePlatformAttributes(staging_attrs, events);\n\n        // Try to create SharedImageSink if necessary.\n        if (weak_self && !weak_self->tex_id_.has_value()) {\n          fml::RefPtr<SharedImageSink> image_sink = plugin.GetSharedImageSink();\n          if (image_sink) {\n            auto texture =\n                std::make_shared<SharedImageExternalTexture>(image_sink);\n            weak_self->page_view()->RegisterDrawableImage(texture);\n            weak_self->tex_id_ = texture->Id();\n\n            auto content =\n                static_cast<RenderExternalContent*>(weak_self->render_object());\n            content->SetDrawableImageId(*weak_self->tex_id_);\n            content->SetFitMode(DrawableImage::FitMode::kClipToBounds);\n            content->SetRenderMode(\n                RenderExternalContent::RenderMode::kExternalTexture);\n          }\n        }\n      });\n}\n\nvoid NativeView::HandleEvent(const PointerEvent& event) {\n  if (!IsScrollEnabled()) {\n    return;\n  }\n  BaseView::HandleEvent(event);\n  if (event.type == PointerEvent::EventType::kSignalEvent) {\n    page_view()->gesture_manager()->RegisterSignalRoute(\n        [weak = this->GetWeakPtr()](const PointerEvent& event) {\n          // eat signal event\n        });\n  }\n}\n\nvoid NativeView::InvokePlatformMethod(const std::string& method_name,\n                                      clay::Value::Map args,\n                                      const LynxUIMethodCallback& callback) {\n  native_view_plugin_.Act(\n      [method_name, args = std::move(args), callback](auto& plugin) {\n        plugin.InvokePlatformMethod(method_name, args, callback);\n      });\n}\n\nvoid NativeView::SetAttribute(const char* attr, const clay::Value& value) {\n  if (!HandleCommonAttribute(attr, value)) {\n    staging_attrs_.emplace(attr, CloneClayValue(value));\n  } else if (GetKeywordID(attr) == KeywordID::kName) {\n    staging_attrs_.emplace(attr, CloneClayValue(value));\n  }\n}\n\nvoid NativeView::ApplyUpdateChanged() {\n  if (attach_to_tree()) {\n    FloatRect bounds = ContentBoundsInViewport();\n    float ratio = page_view()->DevicePixelRatio();\n    // Need to update the size & position of platform view.\n    // It's necessary for some cases of platform view, we should apply the\n    // right layout information. For example, the platform input may show the\n    // overlay depends on the layout information.\n    if (bounds == bounds_ && ratio == device_pixel_ratio_) {\n      return;\n    }\n    bounds_ = bounds;\n    device_pixel_ratio_ = ratio;\n    native_view_plugin_.Act(\n        [bounds =\n             page_view()->ConvertTo<kPixelTypePlatform>(bounds)](auto& plugin) {\n          plugin.LayoutChanged(bounds.x(), bounds.y(), bounds.width(),\n                               bounds.height());\n        });\n  }\n}\n\nvoid NativeView::OnPainting() {\n  // We have no idea to know whether the content bounds has changed or not.\n  // Refer to Android SurfaceView, it use the ViewTreeObserver.onPreDraw to\n  // update surface.\n  // We check it before global Painting event.\n  ApplyUpdateChanged();\n}\n\nvoid NativeView::OnAttachToTree() {\n  BaseView::OnAttachToTree();\n\n  native_view_plugin_.Act([](auto& plugin) { return plugin.OnAttach(); });\n\n  page_view_->GetViewTreeObserver()->AddOnPaintingListener(this);\n}\n\nvoid NativeView::OnDetachFromTree() {\n  if (is_editing_) {\n    is_editing_ = false;\n    page_view_->SetEditingPlatformView(nullptr);\n  }\n  BaseView::OnDetachFromTree();\n  native_view_plugin_.Act([](auto& plugin) { return plugin.OnDetach(); });\n  page_view_->GetViewTreeObserver()->RemoveOnPaintingListener(this);\n}\n\nMeasureResult NativeView::Measure(const MeasureConstraint& constraint) {\n  MeasureConstraint platform_constraint = constraint;\n  if (constraint.width.has_value()) {\n    platform_constraint.width =\n        page_view()->ConvertTo<kPixelTypePlatform>(*constraint.width);\n  }\n  if (constraint.height.has_value()) {\n    platform_constraint.height =\n        page_view()->ConvertTo<kPixelTypePlatform>(*constraint.height);\n  }\n  auto result = native_view_plugin_\n                    .ActWithPromise([platform_constraint](auto& plugin) {\n                      return plugin.Measure(platform_constraint);\n                    })\n                    .get()\n                    .value_or(MeasureResult{});\n  result.width = page_view()->ConvertFrom<kPixelTypePlatform>(result.width);\n  result.height = page_view()->ConvertFrom<kPixelTypePlatform>(result.height);\n  result.baseline =\n      page_view()->ConvertFrom<kPixelTypePlatform>(result.baseline);\n  return result;\n}\n\nvoid NativeView::MarkLayoutDirty() {\n  auto shadow_node = page_view()->GetShadowNodeById(id());\n  if (shadow_node) {\n    shadow_node->MarkDirty();\n  } else {\n    FML_DCHECK(false) << \"NativeViewShadowNode not found: \" << id();\n  }\n}\n\nvoid NativeView::MarkAsEditing() {\n  if (is_editing_) return;\n  is_editing_ = true;\n  page_view_->SetEditingPlatformView(this);\n}\n\nvoid NativeView::ResignFirstResponder() {\n  is_editing_ = false;\n  native_view_plugin_.Act([](auto& plugin) { plugin.ResignFirstResponder(); });\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/native_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_NATIVE_VIEW_H_\n#define CLAY_UI_COMPONENT_NATIVE_VIEW_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/view_tree_observer.h\"\n#include \"clay/ui/platform/native_view_service.h\"\n\nnamespace clay {\n\nclass NativeView : public WithTypeInfo<NativeView, BaseView>,\n                   public OnPaintingListener {\n public:\n  NativeView(int id, std::string tag, PageView* page_view);\n  ~NativeView() override = default;\n\n  void OnPainting() override;\n  void OnAttachToTree() override;\n  void OnDetachFromTree() override;\n  void SetPaddings(float padding_left, float padding_top, float padding_right,\n                   float padding_bottom) override;\n  void SendMotionEvent(const PointerEvent& point_event,\n                       const FloatPoint& transformed_postion);\n  void SetAttribute(const char* attr, const clay::Value& value) override;\n  void DidUpdateAttributes() override;\n  void HandleEvent(const PointerEvent& event) override;\n\n  // Invoke platform view's platform method\n  void InvokePlatformMethod(const std::string& method_name,\n                            clay::Value::Map args,\n                            const LynxUIMethodCallback& callback);\n  FloatRect bounds() const { return bounds_; }\n  bool IsNativeViewAvailable() const { return is_available_; }\n  bool IsScrollEnabled() const { return is_scroll_enabled_; }\n\n  MeasureResult Measure(const MeasureConstraint& constraint);\n  void MarkLayoutDirty();\n\n  void MarkAsEditing();\n  void ResignFirstResponder();\n\n private:\n  void OnDestroy() override;\n  void FocusHasChanged(bool focused, bool is_leaf) override;\n\n  void ApplyUpdateChanged();\n\n  Puppet<Owner::kUI, std::unique_ptr<NativeViewPlugin>> native_view_plugin_;\n  // Cache all attributes which need to be updated\n  // then apply these to platform view if needed.\n  clay::Value::Map staging_attrs_;\n\n  std::optional<int64_t> tex_id_;\n  FloatRect bounds_;\n  float device_pixel_ratio_ = 1.0;\n  bool is_scroll_enabled_;\n  bool is_editing_ = false;\n  bool is_available_ = false;\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_NATIVE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/native_view_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/ui/component/native_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(NativeViewTest, TextureRegistry) {\n  int view_id = 0;\n  fml::Thread thread(\"mock_ui_platform\");\n  thread.GetTaskRunner()->PostSyncTask([&] {\n    std::unique_ptr<PageView> page_view =\n        std::make_unique<PageView>(view_id++, nullptr, thread.GetTaskRunner());\n    std::unique_ptr<NativeView> root = std::make_unique<NativeView>(\n        view_id++, \"test-platform\", page_view.get());\n    EXPECT_EQ(root->IsNativeViewAvailable(), false);\n  });\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/nested_scroll/nested_scroll_manager.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/nested_scroll/nested_scroll_manager.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/nested_scroll/nested_scrollable.h\"\n#include \"clay/ui/component/nested_scroll/raster_fling_manager.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nNestedScrollManager::NestedScrollManager(PageView* page_view)\n    : page_view_(page_view) {}\n\nvoid NestedScrollManager::CancelAnimations() {\n  if (fling_animator_ && fling_animator_->IsRunning()) {\n    fling_animator_->Cancel();\n  }\n  for (auto& view : scrollable_chain_) {\n    if (view) {\n      view->StopAnimation();\n    }\n  }\n  if (raster_fling_manager_) {\n    raster_fling_manager_->StopAnimation();\n  }\n}\n\nvoid NestedScrollManager::DragStart(NestedScrollable* scrollable) {\n  FML_DCHECK(scrollable && scrollable->Is<NestedScrollable>());\n\n  // We reset the touch slop for all scrollables before creating the new\n  // scrollable chain.\n  for (auto& scrollable : scrollable_chain_) {\n    if (scrollable) {\n      scrollable->SetResolveDragImmediately(false);\n      scrollable->SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n    }\n  }\n\n  source_scrollable_ = scrollable;\n  direction_ = source_scrollable_->GetScrollDirection();\n  ConstructScrollableChain(scrollable);\n  SetScrollStatus(Scrollable::ScrollStatus::kDragging);\n\n  CancelAnimations();\n}\n\nvoid NestedScrollManager::DragUpdate(NestedScrollable* source_scrollable,\n                                     FloatPoint delta) {\n  if (source_scrollable_ != source_scrollable) {\n    FML_DCHECK(false) << \"source_scrollable is not match\";\n    return;\n  }\n\n  DispatchScroll(delta, true);\n  if (current_scrollable_) {\n    current_scrollable_->DecodeImagesRecursively();\n  }\n}\n\nvoid NestedScrollManager::DragEnd(NestedScrollable* source_scrollable,\n                                  FloatSize velocity) {\n  if (source_scrollable_ != source_scrollable) {\n    // FML_DCHECK(false) << \"source_scrollable is not match\";\n    return;\n  }\n\n  auto v = source_scrollable->GetScrollDirection() == ScrollDirection::kVertical\n               ? velocity.height()\n               : velocity.width();\n  if (ShouldTriggerFling(v)) {\n    StartFling(v, IsSmoothScrollEnabled());\n  } else if (auto overscroll_view = FindOverscrollView()) {\n    TryStartBounce(overscroll_view, v);\n  } else {\n    SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n  }\n  if (current_scrollable_) {\n    current_scrollable_->DecodeImagesRecursively();\n  }\n}\n\nNestedScrollable* NestedScrollManager::FindOuterScrollable(\n    NestedScrollable* scrollable) {\n  auto it = std::find(scrollable_chain_.rbegin(), scrollable_chain_.rend(),\n                      scrollable->GetWeakPtr());\n  if (it == scrollable_chain_.rend() || it == scrollable_chain_.rbegin()) {\n    return nullptr;\n  } else {\n    return (--it)->get();\n  }\n}\n\nvoid NestedScrollManager::ConstructScrollableChain(\n    NestedScrollable* scrollable) {\n  scrollable = scrollable->FindBeginningScrollable();\n  current_scrollable_ = scrollable->GetWeakPtr();\n  scrollable_chain_.clear();\n  scrollable_chain_.push_back(scrollable->GetWeakPtr());\n\n  while (scrollable->IsEnableNestedScroll()) {\n    scrollable = scrollable->FindNextScrollable();\n    if (scrollable && scrollable->GetScrollDirection() == direction_ &&\n        std::find(scrollable_chain_.begin(), scrollable_chain_.end(),\n                  scrollable->GetWeakPtr()) == scrollable_chain_.end()) {\n      scrollable_chain_.push_back(scrollable->GetWeakPtr());\n    } else {\n      break;\n    }\n  }\n}\n\nstd::tuple<FloatPoint, NestedScrollable*> NestedScrollManager::DispatchScroll(\n    FloatPoint delta, bool is_dragging) {\n  CleanUpScrollableChain();\n  if (scrollable_chain_.size() == 0 || delta.IsOrigin()) {\n    return {delta, nullptr};\n  }\n\n  // Handle the overscroll first if there is a view under overscroll.\n  FloatPoint unconsumed = delta;\n  if (auto overscroll_view = FindOverscrollView()) {\n    unconsumed = overscroll_view->DoOverscroll(unconsumed);\n  }\n\n  // Capture phase\n  for (auto it = scrollable_chain_.rbegin(); it != scrollable_chain_.rend();\n       it++) {\n    auto scrollable = it->get();\n    if (scrollable->IsScrollEnabled() &&\n        scrollable->CaptureScroll(unconsumed)) {\n      auto old_unconsumed = unconsumed;\n      unconsumed = scrollable->DoScroll(unconsumed);\n      if (unconsumed != old_unconsumed) {\n        OnScrollableScroll(scrollable);\n      }\n    }\n  }\n\n  auto scrollable = scrollable_chain_[0];\n  if (!unconsumed.IsOrigin()) {\n    return scrollable->HandleNestedScroll(unconsumed, is_dragging);\n  }\n\n  return {unconsumed, scrollable.get()};\n}\n\nbool NestedScrollManager::ShouldTriggerFling(float velocity) {\n  float velocity_threshold = page_view_->FromLogical(50);\n  if (std::abs(velocity) < velocity_threshold) {\n    return false;\n  }\n\n  // In the overscroll state, we will check the target position to determine\n  // whether to trigger fling. If the target position exceeds the scroll\n  // boundary, we trigger a bounce animation instead of a fling.\n  if (auto overscroll_view = FindOverscrollView()) {\n    float overscroll_offset =\n        overscroll_view->GetScrollDirection() == ScrollDirection::kVertical\n            ? overscroll_view->OverscrollOffset().y()\n            : overscroll_view->OverscrollOffset().x();\n    if (overscroll_offset * velocity > 0) {\n      return false;\n    }\n\n    // Use animator to calculate the target distance.\n    FlingAnimator animator{};\n    animator.SetStartVelocity(velocity);\n    animator.SetFriction(1.0f);\n    animator.SetDensity(\n        page_view_->GetPixelRatio<kPixelTypeLogical, kPixelTypeClay>());\n    animator.FlingInitialize();\n    animator.GetDistance();\n    return std::abs(animator.GetDistance()) > std::abs(overscroll_offset);\n  }\n\n  return true;\n}\n\nvoid NestedScrollManager::StartFling(float velocity, bool try_raster_fling) {\n  if (try_raster_fling) {\n    FML_DCHECK(IsSmoothScrollEnabled());\n    if (current_scrollable_.get() == source_scrollable_ &&\n        GetOrCreateRasterFlingManager()->StartAnimation(source_scrollable_,\n                                                        velocity)) {\n      // Run fling on raster successfully.\n      FML_DLOG(INFO) << \"run fling on raster velocity=\" << velocity;\n      SetScrollStatus(Scrollable::ScrollStatus::kFling);\n      current_scrollable_->GetRenderScroll()->SetInScrollAnimation(true);\n      return;\n    } else {\n      FML_DLOG(INFO) << \"fling on raster failed and fallback to fling on UI.\";\n    }\n  }\n  if (!fling_animator_) {\n    fling_animator_ = std::make_unique<FlingAnimator>();\n    fling_animator_->SetAnimationHandler(page_view_->GetAnimationHandler());\n    fling_animator_->AddListener(this);\n    fling_animator_->SetFriction(1.0f);\n    fling_animator_->SetDensity(\n        page_view_->GetPixelRatio<kPixelTypeLogical, kPixelTypeClay>());\n  } else {\n    fling_animator_->Cancel();\n  }\n  fling_animator_->SetStartValue(0);\n  fling_animator_->SetStartVelocity(velocity);\n  fling_animator_->FlingInitialize();\n  fling_animator_->Start();\n\n  SetScrollStatus(Scrollable::ScrollStatus::kFling);\n  current_scrollable_->GetRenderScroll()->SetInScrollAnimation(true);\n}\n\nbool NestedScrollManager::TryStartBounce(NestedScrollable* target,\n                                         float velocity) {\n  if (!target->DoBounce(velocity)) {\n    SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n    return false;\n  }\n\n  current_scrollable_ = target->GetWeakPtr();\n  SetScrollStatus(Scrollable::ScrollStatus::kBounce);\n  return true;\n}\n\nvoid NestedScrollManager::OnDynamicAnimationUpdate(DynamicAnimator& animation,\n                                                   float value,\n                                                   float velocity) {\n  FML_DCHECK(direction_ != ScrollDirection::kNone);\n  if (&animation == fling_animator_.get()) {\n    float delta = animation.GetDelta();\n    FloatPoint delta_point = direction_ == ScrollDirection::kHorizontal\n                                 ? FloatPoint{delta, 0}\n                                 : FloatPoint{0, delta};\n    auto [unconsumed, scrollable] = DispatchScroll(delta_point, false);\n\n    if (!unconsumed.IsOrigin()) {\n      // If there is unconsumed scroll, that means we meet the boundary of a\n      // scrollable. Stop the fling and try to start bounce at that scrollable.\n      animation.Cancel();\n      if (scrollable) {\n        TryStartBounce(scrollable, velocity);\n      } else {\n        SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n      }\n    }\n  }\n}\n\nvoid NestedScrollManager::OnDynamicAnimationEnd(DynamicAnimator& animation,\n                                                bool canceled, float value,\n                                                float velocity) {\n  if (!canceled) {\n    // For canceled animations, we should manage the state at the point\n    // of cancellation, as the new state may not be idle.\n    SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n  }\n  if (current_scrollable_) {\n    current_scrollable_->GetRenderScroll()->SetInScrollAnimation(false);\n    current_scrollable_->DecodeImagesRecursively();\n  }\n}\n\nvoid NestedScrollManager::SetScrollStatus(Scrollable::ScrollStatus status) {\n  if (status_ != status) {\n    status_ = status;\n    if (current_scrollable_) {\n      current_scrollable_->SetScrollStatus(status);\n    }\n\n    bool resolve_drag_immediately =\n        status == Scrollable::ScrollStatus::kFling ||\n        status == Scrollable::ScrollStatus::kBounce;\n    for (auto& scrollable : scrollable_chain_) {\n      if (scrollable) {\n        scrollable->SetResolveDragImmediately(resolve_drag_immediately);\n      }\n    }\n  }\n}\n\nvoid NestedScrollManager::OnScrollableScroll(NestedScrollable* scrollable) {\n  if (std::find(scrollable_chain_.begin(), scrollable_chain_.end(),\n                scrollable->GetWeakPtr()) == scrollable_chain_.end()) {\n    return;\n  }\n\n  // The scroll is handed off to another scrollable. We should also hand off the\n  // status.\n  if (current_scrollable_.get() != scrollable) {\n    if (current_scrollable_) {\n      current_scrollable_->SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n    }\n    current_scrollable_ = scrollable->GetWeakPtr();\n    scrollable->SetScrollStatus(status_);\n  }\n}\n\nvoid NestedScrollManager::OnScrollableBounceEnd(NestedScrollable* scrollable) {\n  if (status_ == Scrollable::ScrollStatus::kBounce &&\n      std::find(scrollable_chain_.begin(), scrollable_chain_.end(),\n                scrollable->GetWeakPtr()) != scrollable_chain_.end()) {\n    SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n  }\n}\n\nNestedScrollable* NestedScrollManager::FindOverscrollView() const {\n  for (auto& scrollable : scrollable_chain_) {\n    if (scrollable && scrollable->IsUnderOverscroll()) {\n      return scrollable.get();\n    }\n  }\n\n  return nullptr;\n}\n\nvoid NestedScrollManager::CleanUpScrollableChain() {\n  scrollable_chain_.erase(\n      std::remove_if(scrollable_chain_.begin(), scrollable_chain_.end(),\n                     [](const auto& scrollable) { return !scrollable; }),\n      scrollable_chain_.end());\n}\n\nvoid NestedScrollManager::OnFlingEndFromRaster(NestedScrollable* scrollable,\n                                               float remaining_velocity) {\n  if (scrollable != source_scrollable_) {\n    return;\n  }\n  if (std::abs(remaining_velocity) > 0.f) {\n    // The raster fling animation is finished, we try to use the remaining\n    // velocity to start a new fling animation on UI to continue the nested\n    // scroll if needed.\n    FML_DLOG(INFO) << \"run fling on ui with the remaining velocity=\"\n                   << remaining_velocity;\n    StartFling(remaining_velocity, false);\n  } else {\n    if (status_ == Scrollable::ScrollStatus::kFling) {\n      SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n      current_scrollable_->GetRenderScroll()->SetInScrollAnimation(false);\n    }\n  }\n}\n\nRasterFlingManager* NestedScrollManager::GetOrCreateRasterFlingManager() {\n  FML_DCHECK(IsSmoothScrollEnabled());\n  if (!raster_fling_manager_) {\n    raster_fling_manager_ = std::make_unique<RasterFlingManager>(this);\n  }\n  return raster_fling_manager_.get();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/nested_scroll/nested_scroll_manager.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_NESTED_SCROLL_NESTED_SCROLL_MANAGER_H_\n#define CLAY_UI_COMPONENT_NESTED_SCROLL_NESTED_SCROLL_MANAGER_H_\n\n#include <memory>\n#include <tuple>\n#include <vector>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/gfx/animation/fling_animator.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/component/nested_scroll/nested_scrollable.h\"\n#include \"clay/ui/component/nested_scroll/raster_fling_manager.h\"\n\nnamespace clay {\n\nclass PageView;\n\n// NestedScrollManager handles the scroll behavior of nested scrollable views.\nclass NestedScrollManager : public DynamicAnimator::AnimationListener {\n public:\n  explicit NestedScrollManager(PageView* page_view);\n\n  void DragStart(NestedScrollable* source_scrollable);\n  void DragUpdate(NestedScrollable* source_scrollable, FloatPoint delta);\n  void DragEnd(NestedScrollable* source_scrollable, FloatSize velocity);\n\n  NestedScrollable* FindOuterScrollable(NestedScrollable* scrollable);\n\n  void OnScrollableScroll(NestedScrollable* scrollable);\n  void OnScrollableBounceEnd(NestedScrollable* scrollable);\n\n  void CancelAnimations();\n\n  float GetFlingVelocity() const {\n    if (fling_animator_ && fling_animator_->IsRunning()) {\n      return fling_animator_->GetCurrentVelocity();\n    } else {\n      return 0.f;\n    }\n  }\n\n  Scrollable::ScrollStatus GetScrollStatus() const { return status_; }\n\n  // Return true if raster fling animation is enabled.\n  // TODO: will be removed once smooth scroll is stable.\n  bool IsSmoothScrollEnabled() const {\n#if OS_IOS || OS_ANDROID\n    // Disable it on iOS for better bounce effect. If the compositor animation\n    // is enabled, frame drops occur when scrolling to the container's edge and\n    // switching to the bounce animation.\n    return false;\n#else\n    return true;\n#endif\n  }\n\n  RasterFlingManager* raster_fling_manager() const {\n    return raster_fling_manager_.get();\n  }\n\n private:\n  void ConstructScrollableChain(NestedScrollable* scrollable);\n  std::tuple<FloatPoint, NestedScrollable*> DispatchScroll(FloatPoint delta,\n                                                           bool is_dragging);\n  bool ShouldTriggerFling(float velocity);\n  void StartFling(float velocity, bool try_raster_fling = false);\n  bool TryStartBounce(NestedScrollable* target, float velocity);\n\n  // DynamicAnimator::AnimationListener\n  void OnDynamicAnimationUpdate(DynamicAnimator& animation, float value,\n                                float velocity) override;\n  void OnDynamicAnimationEnd(DynamicAnimator& animation, bool canceled,\n                             float value, float velocity) override;\n\n  void SetScrollStatus(Scrollable::ScrollStatus status);\n\n  bool IsFlingRunning() const {\n    return fling_animator_ && fling_animator_->IsRunning();\n  }\n\n  NestedScrollable* FindOverscrollView() const;\n\n  // Remove invalid WeakPtr from scrollable_chain_.\n  void CleanUpScrollableChain();\n\n  void OnFlingEndFromRaster(NestedScrollable* scrollable,\n                            float remaining_velocity);\n  RasterFlingManager* GetOrCreateRasterFlingManager();\n\n  std::vector<fml::WeakPtr<NestedScrollable>> scrollable_chain_;\n  fml::WeakPtr<NestedScrollable> current_scrollable_;\n  NestedScrollable* source_scrollable_ = nullptr;\n  ScrollDirection direction_ = ScrollDirection::kNone;\n  PageView* page_view_;\n  std::unique_ptr<FlingAnimator> fling_animator_;\n  Scrollable::ScrollStatus status_ = NestedScrollable::ScrollStatus::kIdle;\n\n  std::unique_ptr<RasterFlingManager> raster_fling_manager_;\n  friend class RasterFlingManager;\n\n  FRIEND_TEST(NestedScrollableTest, SimpleScroll);\n  FRIEND_TEST(NestedScrollableTest, Fling);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_NESTED_SCROLL_NESTED_SCROLL_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/component/nested_scroll/nested_scrollable.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/nested_scroll/nested_scrollable.h\"\n\n#include <float.h>\n\n#include <algorithm>\n#include <cstdlib>\n#include <limits>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/animation/cubic_bezier_interpolator.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/list/list_container/list_container_wrapper.h\"\n#include \"clay/ui/component/list/list_wrapper.h\"\n#include \"clay/ui/component/nested_scroll/nested_scroll_manager.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/scroll_wrapper.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/scrollable_direction.h\"\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n\nnamespace clay {\n\nstatic constexpr int64_t kMillisecondsPerSecond = 1000;\n\nconst double kDurationDivisor = 60.0;\n\nconst double kInverseDeltaRampStartPx = 120.0;\nconst double kInverseDeltaRampEndPx = 480.0;\nconst double kInverseDeltaMinDuration = 6.0;\nconst double kInverseDeltaMaxDuration = 12.0;\n\nconst double kInverseDeltaSlope =\n    (kInverseDeltaMinDuration - kInverseDeltaMaxDuration) /\n    (kInverseDeltaRampEndPx - kInverseDeltaRampStartPx);\n\nconst double kInverseDeltaOffset =\n    kInverseDeltaMaxDuration - kInverseDeltaRampStartPx * kInverseDeltaSlope;\n\nstatic float MaximumDimension(const FloatPoint& delta) {\n  return std::abs(delta.x()) > std::abs(delta.y()) ? delta.x() : delta.y();\n}\n\nbool PointEqual(const FloatPoint& point1, const FloatPoint& point2) {\n  return std::abs(point1.x() - point2.x()) < FLT_EPSILON &&\n         std::abs(point1.y() - point2.y()) < FLT_EPSILON;\n}\n\nNestedScrollable::NestedScrollable(int id, std::string tag,\n                                   std::unique_ptr<RenderScroll> render_object,\n                                   PageView* page_view,\n                                   ScrollDirection direction,\n                                   bool enable_nested_scroll)\n    : WithTypeInfo(id, std::move(tag), std::move(render_object), page_view,\n                   direction),\n      enable_nested_scroll_(enable_nested_scroll) {\n  ResetGestureRecognizers();\n  wheel_animator_ = std::make_unique<ValueAnimator>();\n  wheel_animator_->SetAnimationHandler(GetAnimationHandler());\n  wheel_animator_->AddUpdateListener(this);\n  wheel_animator_->AddListener(this);\n  wheel_animator_->SetInterpolator(\n      CubicBezierInterpolator::Create(0.42, 0.0, 0.58, 1.0));\n}\n\nNestedScrollable::~NestedScrollable() {\n  if (wheel_animator_->IsRunning()) {\n    wheel_animator_->End();\n  }\n  if (handle_signal_event_) {\n    page_view()->gesture_manager()->EndMouseWheelTransactionByForce();\n  }\n  signal_scroll_started_ = false;\n}\n\nvoid NestedScrollable::SetScrollEnabled(bool enabled) {\n  if (IsScrollEnabled() != enabled) {\n    Scrollable::SetScrollEnabled(enabled);\n    if (enabled) {\n      ResetGestureRecognizers();\n    } else {\n#if defined(OS_ANDROID) || defined(OS_IOS) || defined(OS_HARMONY)\n      RemoveGestureRecognizer(drag_recognizer_);\n      drag_recognizer_ = nullptr;\n#endif\n    }\n  }\n}\n\nbool NestedScrollable::CanDragScrollOnX() const {\n  return scroll_direction_ == ScrollDirection::kHorizontal;\n}\n\nbool NestedScrollable::CanDragScrollOnY() const {\n  return scroll_direction_ == ScrollDirection::kVertical;\n}\n\nstd::tuple<FloatPoint, NestedScrollable*> NestedScrollable::HandleNestedScroll(\n    FloatPoint delta, bool is_dragging) {\n  std::tuple<FloatPoint, NestedScrollable*> result = {delta, this};\n  if (delta.IsOrigin()) {\n    return result;\n  }\n\n  auto handle_parent = [&] {\n    if (auto parent_scrollable =\n            nested_scroll_manager()->FindOuterScrollable(this)) {\n      result = parent_scrollable->HandleNestedScroll(std::get<0>(result),\n                                                     is_dragging);\n    }\n  };\n\n  auto& unconsumed = std::get<0>(result);\n  bool is_forward = (CanDragScrollOnX() ? delta.x() : delta.y()) >= 0;\n  auto mode = is_forward ? scroll_forward_mode_ : scroll_backward_mode_;\n  if (mode == NestedScrollMode::kParentFirst) {\n    handle_parent();\n    if (unconsumed.IsOrigin()) {\n      return result;\n    }\n  }\n  if (IsScrollEnabled()) {\n    auto old_unconsumed = unconsumed;\n    unconsumed = DoScroll(unconsumed);\n    if (unconsumed != old_unconsumed) {\n      nested_scroll_manager()->OnScrollableScroll(this);\n    }\n    if (unconsumed.IsOrigin()) {\n      return result;\n    }\n    if (IsOverscrollEnabled(is_forward)) {\n      // We only enter the overscroll state when dragging. Otherwise, the scroll\n      // is triggered by fling animation. In that case, we should not do\n      // overscroll here, and it will be handled by a bounce animator instead.\n      if (is_dragging) {\n        auto old_unconsumed = unconsumed;\n        unconsumed = DoOverscroll(unconsumed);\n        if (unconsumed != old_unconsumed) {\n          nested_scroll_manager()->OnScrollableScroll(this);\n        }\n      }\n      return result;\n    }\n  }\n\n  if (mode == NestedScrollMode::kSelfFirst) {\n    handle_parent();\n  }\n  return result;\n}\n\nvoid NestedScrollable::OnOverscroll(FloatPoint prev_overscroll_offset) {\n  DidScroll();\n}\n\nvoid NestedScrollable::OnBounceEnd(bool canceled) {\n  // We need to notify the manager to update the scroll status\n  nested_scroll_manager()->OnScrollableBounceEnd(this);\n}\n\nvoid NestedScrollable::ResetGestureRecognizers() {\n  RemoveGestureRecognizer(drag_recognizer_);\n  drag_recognizer_ = nullptr;\n\n  std::unique_ptr<DragGestureRecognizer> drag_recognizer =\n      CreateDragRecognizerByDirection(scroll_direction_,\n                                      page_view()->gesture_manager());\n  drag_recognizer_ = drag_recognizer.get();\n  drag_recognizer->SetDragStartCallback([this](const FloatPoint& point) {\n    nested_scroll_manager()->DragStart(this);\n  });\n  drag_recognizer->SetDragUpdateCallback(\n      [this](const FloatPoint& position, const FloatSize& delta) {\n        nested_scroll_manager()->DragUpdate(this,\n                                            {-delta.width(), -delta.height()});\n      });\n  drag_recognizer->SetDragEndCallback([this](const Velocity& velocity) {\n    nested_scroll_manager()->DragEnd(this, -velocity.pixels_per_second());\n    auto v = -velocity.pixels_per_second();\n    last_drag_end_velocity_ = scroll_direction_ == ScrollDirection::kVertical\n                                  ? v.height()\n                                  : v.width();\n  });\n  drag_recognizer->SetDragCancelCallback(\n      [this]() { nested_scroll_manager()->DragEnd(this, {}); });\n  drag_recognizer->SetDelegate(this);\n  AddGestureRecognizer(std::move(drag_recognizer));\n}\n\nNestedScrollManager* NestedScrollable::nested_scroll_manager() const {\n  return page_view()->nested_scroll_manager();\n}\n\nNestedScrollable* NestedScrollable::FindAncestorNestedDraggableView() {\n  auto parent = Parent();\n  while (parent) {\n    if (parent->Is<NestedScrollable>()) {\n      return static_cast<NestedScrollable*>(parent);\n    }\n    parent = parent->Parent();\n  }\n  return nullptr;\n}\n\nNestedScrollable* NestedScrollable::FindBeginningScrollable() {\n  std::vector<NestedScrollable*> visited = {this};\n  while (true) {\n    auto pre_scrollable = visited.back()->PreviousScrollable();\n    if (pre_scrollable && std::find(visited.begin(), visited.end(),\n                                    pre_scrollable) == visited.end()) {\n      visited.push_back(pre_scrollable);\n    } else {\n      break;\n    }\n  }\n  return visited.back();\n}\n\nNestedScrollable* NestedScrollable::FindNextScrollable() {\n  return NextScrollable() ?: FindAncestorNestedDraggableView();\n}\n\nbool NestedScrollable::CanScrollBy(float delta_x, float delta_y) const {\n  auto direction = GetScrollableDirection();\n  if (delta_x > 0 && (direction & ScrollableDirection::kRightwards) !=\n                         ScrollableDirection::kNone) {\n    return true;\n  }\n  if (delta_x < 0 && (direction & ScrollableDirection::kLeftwards) !=\n                         ScrollableDirection::kNone) {\n    return true;\n  }\n  if (delta_y > 0 && (direction & ScrollableDirection::kDownwards) !=\n                         ScrollableDirection::kNone) {\n    return true;\n  }\n  if (delta_y < 0 && (direction & ScrollableDirection::kUpwards) !=\n                         ScrollableDirection::kNone) {\n    return true;\n  }\n  return false;\n}\n\nvoid NestedScrollable::HandleEvent(const PointerEvent& event) {\n  if (!IsScrollEnabled()) {\n    return;\n  }\n  BaseView::HandleEvent(event);\n  if (event.type == PointerEvent::EventType::kSignalEvent) {\n    page_view()->gesture_manager()->RegisterSignalRoute(\n        [weak = this->GetWeakPtr()](const PointerEvent& event) {\n          auto scrollable = static_cast<NestedScrollable*>(weak.get());\n          if (!scrollable) {\n            return;\n          }\n          switch (event.signal_kind) {\n            case PointerEvent::SignalKind::kStartScroll: {\n              scrollable->handle_signal_event_ = true;\n              if (scrollable->CanScrollBy(event.scroll_delta_x,\n                                          event.scroll_delta_y)) {\n                if ((event.scroll_delta_x != 0.f && scrollable->CanScrollX()) ||\n                    (event.scroll_delta_y != 0.f && scrollable->CanScrollY())) {\n                  scrollable->signal_scroll_started_ = true;\n                }\n              }\n              scrollable->DispatchMouseWheelEvent(event);\n              break;\n            }\n            case PointerEvent::SignalKind::kScroll: {\n              // TODO(liuguoliang): Support more nested hierarchy, currently\n              // only one level is supported.\n              scrollable->DispatchMouseWheelEvent(event);\n              break;\n            }\n            case PointerEvent::SignalKind::kEndScroll:\n              scrollable->DispatchMouseWheelEvent(event);\n              scrollable->signal_scroll_started_ = false;\n              scrollable->handle_signal_event_ = false;\n              break;\n            default:\n              break;\n          }\n        });\n  }\n}\n\nFloatPoint NestedScrollable::DoScroll(FloatPoint delta, bool by_user_input,\n                                      bool ignore_repaint) {\n  FloatPoint old_delta = delta;\n  auto scroll = GetRenderScroll();\n  auto max_scroll_width = scroll->MaxScrollWidth();\n  auto max_scroll_height = scroll->MaxScrollHeight();\n  if (delta.x() != 0) {\n    float new_left = scroll_offset_.x() + delta.x();\n    if (new_left < 0) {\n      delta.SetX(new_left);\n      new_left = 0;\n    } else if (new_left > max_scroll_width) {\n      // Avoid delta changing by float calculation.\n      if (scroll_offset_.x() != max_scroll_width) {\n        delta.SetX(new_left - max_scroll_width);\n      }\n      new_left = max_scroll_width;\n    } else {\n      delta.SetX(0);\n    }\n    scroll->SetScrollLeft(new_left, ignore_repaint);\n  }\n  if (delta.y() != 0) {\n    float new_top = scroll_offset_.y() + delta.y();\n    if (new_top < 0) {\n      delta.SetY(new_top);\n      new_top = 0;\n    } else if (new_top > max_scroll_height) {\n      // Avoid delta changing by float calculation.\n      if (scroll_offset_.y() != max_scroll_height) {\n        delta.SetY(new_top - max_scroll_height);\n      }\n      new_top = max_scroll_height;\n    } else {\n      delta.SetY(0);\n    }\n    scroll->SetScrollTop(new_top, ignore_repaint);\n  }\n  scroll_offset_ = scroll->ScrollOffset();\n\n  if (!PointEqual(delta, old_delta)) {\n    DidScroll();\n    if (!ignore_repaint) {\n      Invalidate();\n    }\n  }\n  return delta;\n}\n\nvoid NestedScrollable::SetTouchSlop(float slop) {\n  touch_slop_ = slop;\n  UpdateTouchSlop();\n}\n\nvoid NestedScrollable::SetResolveDragImmediately(\n    bool resolve_drag_immediately) {\n  if (resolve_drag_immediately_ != resolve_drag_immediately) {\n    resolve_drag_immediately_ = resolve_drag_immediately;\n    UpdateTouchSlop();\n  }\n}\n\nvoid NestedScrollable::UpdateTouchSlop() {\n  float used_slop = resolve_drag_immediately_ ? -1 : FromLogical(touch_slop_);\n  if (drag_recognizer_) {\n    drag_recognizer_->SetTouchSlop(used_slop);\n  }\n}\n\ndouble GetDuration(float delta) {\n  auto duration = kInverseDeltaOffset + std::abs(delta) * kInverseDeltaSlope;\n  duration =\n      std::clamp(duration, kInverseDeltaMinDuration, kInverseDeltaMaxDuration);\n  duration = duration / kDurationDivisor * kMillisecondsPerSecond;\n  return duration;\n}\n\ndouble VelocityBasedDurationBound(FloatPoint delta, double velocity) {\n  double delta_max_dimension = MaximumDimension(delta);\n  if (std::abs(velocity) < FLT_EPSILON) {\n    return std::numeric_limits<double>::max();\n  }\n  double bound = delta_max_dimension / velocity * 2.5f;\n  return bound < 0 ? std::numeric_limits<double>::max()\n                   : bound * kMillisecondsPerSecond;\n}\n\nvoid NestedScrollable::UpdateWheelAnimation(FloatPoint delta) {\n  auto t = wheel_animator_->GetActivatedTime();\n  if (t < FLT_EPSILON || t - last_retarget_ < FLT_EPSILON) {\n    return;\n  }\n  auto new_delta = MaximumDimension(target_scroll_delta_) -\n                   MaximumDimension(last_target_scroll_delta_) *\n                       wheel_animator_->GetAnimatedFraction();\n  auto velocity = CalculateVelocity(t);\n  // Use the velocity-based duration bound when it is less than the constant\n  // segment duration. This minimizes the \"rubber-band\" bouncing effect when\n  // |velocity| is large and |new_delta| is small.\n  double new_duration = std::min(GetDuration(new_delta),\n                                 VelocityBasedDurationBound(delta, velocity));\n  wheel_animator_->SetDuration(t + new_duration);\n  double new_slope =\n      velocity * ((new_duration / kMillisecondsPerSecond) / new_delta);\n  new_slope = std::clamp(new_slope, -1000.0, 1000.0);\n  wheel_animator_->SetInterpolator(\n      CubicBezierInterpolator::Create(0.42, 0.42 * new_slope, 0.58, 1.0));\n  last_retarget_ = t;\n  last_target_scroll_delta_ = target_scroll_delta_;\n}\n\nvoid NestedScrollable::DoWheelScroll(FloatPoint delta) {\n  if (need_scroll_animation_) {\n    if (delta.IsOrigin()) {\n      return;\n    }\n    if (!wheel_animator_->IsRunning()) {\n      target_scroll_delta_ = delta;\n      last_scroll_delta_ = 0;\n      last_retarget_ = 0;\n      last_target_scroll_delta_ = FloatPoint();\n      wheel_animator_->SetDuration(GetDuration(MaximumDimension(delta)));\n      wheel_animator_->Start();\n      // ValueAnimator may not request frames on the start\n      page_view()->RequestPaint();\n    } else {\n      if (delta.distance() > FLT_EPSILON) {\n        // If reverse scrolling occurs, stop the current animation and restart a\n        // new animation\n        if (MaximumDimension(delta) * MaximumDimension(target_scroll_delta_) <\n            0) {\n          wheel_animator_->End();\n          DoWheelScroll(delta);\n        } else {\n          target_scroll_delta_ += delta;\n          UpdateWheelAnimation(delta);\n        }\n      }\n    }\n  } else {\n    DoScroll(delta);\n  }\n}\n\ndouble NestedScrollable::CalculateVelocity(float time) {\n  auto duration = wheel_animator_->GetDuration() - last_retarget_;\n  const double slope = wheel_animator_->GetInterpolator()->Velocity(\n      (time - last_retarget_) / duration);\n  auto delta = MaximumDimension(last_target_scroll_delta_) *\n               (1 - wheel_animator_->GetAnimatedFraction());\n  // Interpolator velocity just gives the slope of the curve. Convert it to\n  // units of pixels per second.\n  return slope * (delta / duration);\n}\n\nvoid NestedScrollable::OnAnimationEnd(Animator& animation) {\n  SetScrollStatus(ScrollStatus::kIdle);\n}\n\nvoid NestedScrollable::OnAnimationCancel(Animator& animation) {\n  SetScrollStatus(ScrollStatus::kIdle);\n}\n\nvoid NestedScrollable::OnAnimationUpdate(ValueAnimator& animation) {\n  float fraction = wheel_animator_->GetAnimatedFraction();\n  float scroll_delta = MaximumDimension(target_scroll_delta_) * fraction;\n  auto delta = target_scroll_delta_.x()\n                   ? FloatPoint(scroll_delta - last_scroll_delta_, 0)\n                   : FloatPoint(0, scroll_delta - last_scroll_delta_);\n  auto unconsumed = DoScroll(delta);\n  if (!unconsumed.IsOrigin()) {\n    wheel_animator_->Cancel();\n  }\n  last_scroll_delta_ = scroll_delta;\n}\n\nNestedScrollable* NestedScrollable::GetScrollable(BaseView* view) {\n  if (view && view->Is<NestedScrollable>()) {\n    return static_cast<NestedScrollable*>(view);\n  } else if (view->Is<ScrollWrapper>()) {\n    // TODO(liuguoliang): common method to find scrollable view\n    return static_cast<ScrollWrapper*>(view)->GetScrollView();\n  }\n#ifndef ENABLE_CLAY_LITE\n  else if (view->Is<ListWrapper>()) {\n    return static_cast<ListWrapper*>(view)->GetListView();\n  }\n#endif\n  else if (view->Is<ListContainerWrapper>()) {\n    return static_cast<ListContainerWrapper*>(view)->GetListContainerView();\n  }\n  return nullptr;\n}\n\nvoid NestedScrollable::DispatchMouseWheelEvent(const PointerEvent& event) {\n  NestedScrollable* s =\n      signal_scroll_started_ ? this : FindAncestorNestedDraggableView();\n  if (!s) {\n    return;\n  }\n  FloatPoint delta = {event.scroll_delta_x, event.scroll_delta_y};\n  if (event.is_precise_scroll) {\n    s->DoScroll(delta);\n  } else {\n    s->DoWheelScroll(delta);\n  }\n  if (event.signal_kind == PointerEvent::SignalKind::kStartScroll) {\n    s->SetScrollStatus(ScrollStatus::kDragging);\n  }\n  if (!need_scroll_animation_ &&\n      event.signal_kind == PointerEvent::SignalKind::kEndScroll) {\n    // Some unittests set `need_scroll_animation_` to false which causes mouse\n    // wheel event triggering no animation. In such case, we need to set scroll\n    // status in the end of mouse wheel event.\n    s->SetScrollStatus(ScrollStatus::kIdle);\n  }\n}\n\nbool NestedScrollable::IsPointerAllowed(\n    const GestureRecognizer& gesture_recognizer, const PointerEvent& event) {\n  return event.device == PointerEvent::DeviceType::kTouch ||\n         event.device == PointerEvent::DeviceType::kTrackpad\n#if defined(ENABLE_HEADLESS)\n         || event.device == PointerEvent::DeviceType::kMouse\n#endif\n      ;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/nested_scroll/nested_scrollable.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_NESTED_SCROLL_NESTED_SCROLLABLE_H_\n#define CLAY_UI_COMPONENT_NESTED_SCROLL_NESTED_SCROLLABLE_H_\n\n#include <memory>\n#include <string>\n#include <tuple>\n\n#include \"clay/gfx/animation/value_animator.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/component/scrollable.h\"\n#include \"clay/ui/gesture/drag_gesture_recognizer.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\nclass NestedScrollManager;\nclass BaseView;\nclass PageView;\n\nenum class NestedScrollMode {\n  kSelfOnly,\n  kSelfFirst,\n  kParentFirst,\n};\n\nclass NestedScrollable : public WithTypeInfo<NestedScrollable, Scrollable>,\n                         public AnimatorListener,\n                         public AnimatorUpdateListener,\n                         public GestureRecognizer::Delegate {\n public:\n  NestedScrollable(int id, std::string tag,\n                   std::unique_ptr<RenderScroll> render_object,\n                   PageView* page_view, ScrollDirection direction,\n                   bool enable_nested_scroll = false);\n  ~NestedScrollable() override;\n\n  void SetScrollEnabled(bool enabled) override;\n\n  // TODO(liuguoliang.lgl): Consider moving this to NestedScrollManager.\n  /**\n   * Handle the scroll distance along the scrollable chain.\n   * By default, the scroll offset will be passed from the innermost scrollable\n   * view to outer.\n   *\n   * @param delta: the scroll distance that needs to be consumed\n   * @param is_dragging is this scroll distance resulting from dragging\n   * @return unconsumed distance and the last scrollable that consumes scroll\n   */\n  std::tuple<FloatPoint, NestedScrollable*> HandleNestedScroll(\n      FloatPoint delta, bool is_dragging = true);\n\n  // The scrollable has been scrolled.\n  // Note that you should call this method if you override the DoScroll.\n  virtual void DidScroll() { NotifyScrolled(); }\n\n  // Capture the scroll offset of its descendants. By default, the scroll offset\n  // is passed from child to parent, the inner scrollable will consume the\n  // scroll offset first. But sometimes (like the fold view) we want the outer\n  // scrollable to consume the scroll offset first, regardless of the descendant\n  // scrollables.\n  virtual bool CaptureScroll(FloatPoint delta) { return false; }\n\n  /*\n   * Scrolling should be handled by current view.\n   *\n   * @param delta: scroll by delta offset\n   * @ret: unconsumed distance, unconsumed equals delta minus consumed_delta\n   */\n  virtual FloatPoint DoScroll(FloatPoint delta, bool by_user_input = true,\n                              bool ignore_repaint = false);\n\n  void DoWheelScroll(FloatPoint delta);\n\n  NestedScrollable* FindAncestorNestedDraggableView();\n  NestedScrollManager* nested_scroll_manager() const;\n  bool IsEnableNestedScroll() const { return enable_nested_scroll_; }\n\n  void SetEnableNestedScroll(bool enable_nested_scroll) {\n    enable_nested_scroll_ = enable_nested_scroll;\n  }\n\n  NestedScrollable* FindBeginningScrollable();\n  NestedScrollable* FindNextScrollable();\n\n  virtual NestedScrollable* NextScrollable() { return next_scrollable_.get(); }\n  virtual NestedScrollable* PreviousScrollable() {\n    return pre_scrollable_.get();\n  }\n\n  void HandleEvent(const PointerEvent& event) override;\n\n  bool CanScrollBy(float delta_x, float delta_y) const;\n\n  bool CanScrollX() const {\n    return static_cast<int>(scroll_direction_) &\n           static_cast<int>(ScrollDirection::kHorizontal);\n  }\n  bool CanScrollY() const {\n    return static_cast<int>(scroll_direction_) &\n           static_cast<int>(ScrollDirection::kVertical);\n  }\n\n  void SetNextScrollable(NestedScrollable* scrollable) {\n    next_scrollable_.reset();\n    if (scrollable) {\n      next_scrollable_ = scrollable->GetWeakPtr();\n    }\n  }\n  void SetPreScrollable(NestedScrollable* scrollable) {\n    pre_scrollable_.reset();\n    if (scrollable) {\n      pre_scrollable_ = scrollable->GetWeakPtr();\n    }\n  }\n\n  void SetForwardMode(NestedScrollMode mode) { scroll_forward_mode_ = mode; }\n\n  void SetBackwardMode(NestedScrollMode mode) { scroll_backward_mode_ = mode; }\n\n  // For now this is only used for unit tests.\n  void SetTouchSlop(float slop);\n\n  void SetResolveDragImmediately(bool resolve_drag_immediately);\n\n  bool IsRtlDirection() const {\n    auto layout_direction = GetRenderScroll()->GetLayoutDirection();\n    return layout_direction == DirectionType::kLynxRtl ||\n           layout_direction == DirectionType::kRtl;\n  }\n\n  static NestedScrollable* GetScrollable(BaseView* view);\n\n  bool IsPointerAllowed(const GestureRecognizer& gesture_recognizer,\n                        const PointerEvent& event) override;\n\n protected:\n  // check can scroll in direction\n  virtual bool CanDragScrollOnX() const;\n  virtual bool CanDragScrollOnY() const;\n\n  void ResetGestureRecognizers() override;\n\n  float GetScrollTop() const {\n    RenderBox* box = static_cast<RenderBox*>(render_object_.get());\n    return box->ScrollTop();\n  }\n  float GetScrollLeft() const {\n    RenderBox* box = static_cast<RenderBox*>(render_object_.get());\n    return box->ScrollLeft();\n  }\n  void OnOverscroll(FloatPoint prev_overscroll_offset) override;\n\n  void OnBounceEnd(bool canceled) override;\n  void OnAnimationStart(Animator& animation) override{};\n  void OnAnimationEnd(Animator& animation) override;\n  void OnAnimationCancel(Animator& animation) override;\n  void OnAnimationRepeat(Animator& animation) override{};\n  void OnAnimationUpdate(ValueAnimator& animation) override;\n\n  double CalculateVelocity(float time);\n\n  float last_drag_end_velocity_ = 0.f;  // pixels/second\n\n  DragGestureRecognizer* drag_recognizer_ = nullptr;\n\n  NestedScrollMode scroll_forward_mode_ = NestedScrollMode::kSelfFirst;\n  NestedScrollMode scroll_backward_mode_ = NestedScrollMode::kSelfFirst;\n  fml::WeakPtr<NestedScrollable> next_scrollable_;\n  fml::WeakPtr<NestedScrollable> pre_scrollable_;\n\n private:\n  FRIEND_TEST(ScrollViewTest, NestedScrollGestureOnPC);\n  FRIEND_TEST(ScrollViewTest, ScrollEvent);\n  void UpdateTouchSlop();\n\n  void UpdateWheelAnimation(FloatPoint delta);\n  void DispatchMouseWheelEvent(const PointerEvent& event);\n\n  bool enable_nested_scroll_ = false;\n  float touch_slop_ = kTouchSlop;\n  // We ensure to resolve the drag gesture immediately. This results in a better\n  // user experience and reasonable state transition.\n  //   1. Avoid triggering the tap gesture when the scrollable is scrolling.\n  //   2. Immediately resolve the drag gesture when the scrollable is scrolling,\n  //   so that there is no delay for scrolling again.\n  //   3. Immediately transition to drag state without a period of idle state,\n  //   which is weird.\n  bool resolve_drag_immediately_ = false;\n\n  BaseView* parent_scroll_view_ = nullptr;\n\n  friend NestedScrollManager;\n\n  std::unique_ptr<ValueAnimator> wheel_animator_;\n  // unittests only\n  bool need_scroll_animation_ = true;\n  // Wheel animator related variables\n  FloatPoint target_scroll_delta_;\n  FloatPoint last_target_scroll_delta_;\n  float last_scroll_delta_ = 0.f;\n  float last_retarget_ = 0.f;\n\n  // indicate current scroll-view to handle scroll event, instead of dispatching\n  // to parent\n  bool signal_scroll_started_ = false;\n  // indicate current scroll-view to handle signal event\n  bool handle_signal_event_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_NESTED_SCROLL_NESTED_SCROLLABLE_H_\n"
  },
  {
    "path": "clay/ui/component/nested_scroll/nested_scrollable_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/nested_scroll/nested_scroll_manager.h\"\n#include \"clay/ui/component/nested_scroll/nested_scrollable.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/testing/ui_test.h\"\n\nnamespace clay {\n\nclass TestScrollable : public NestedScrollable {\n public:\n  explicit TestScrollable(PageView* page_view, float max_scroll_offset)\n      : NestedScrollable(-1, \"\", std::make_unique<RenderScroll>(), page_view,\n                         ScrollDirection::kVertical),\n        max_scroll_offset_(max_scroll_offset) {\n    SetEnableNestedScroll(true);\n    SetBound(0, 0, max_scroll_offset, max_scroll_offset);\n  }\n\n  FloatPoint DoScroll(FloatPoint delta, bool is_user_input,\n                      bool ignore_repaint) override {\n    if (delta.y() != 0) {\n      float new_offset = scroll_offset_ + delta.y();\n      if (new_offset < 0) {\n        delta.SetY(new_offset);\n        new_offset = 0;\n      } else if (new_offset > max_scroll_offset_) {\n        delta.SetY(new_offset - max_scroll_offset_);\n        new_offset = max_scroll_offset_;\n      } else {\n        delta.SetY(0);\n      }\n      scroll_offset_ = new_offset;\n    }\n    return delta;\n  }\n\n  void OnScrollStatusChange(ScrollStatus old_status) override {\n    NestedScrollable::OnScrollStatusChange(old_status);\n    if (status_ == ScrollStatus::kBounce) {\n      bounced_ = true;\n    }\n  }\n\n  float max_scroll_offset_;\n  float scroll_offset_ = 0;\n  bool bounced_ = false;\n};\n\nclass NestedScrollableTest : public UITest {\n  void UISetUp() override {}\n};\n\nTEST_F_UI(NestedScrollableTest, Chain) {\n  NestedScrollable* scrollable1 =\n      new ScrollView(-1, ScrollDirection::kVertical, page_.get());\n  page_->AddChild(scrollable1);\n  NestedScrollable* scrollable2 =\n      new ScrollView(-1, ScrollDirection::kHorizontal, page_.get());\n  scrollable1->AddChild(scrollable2);\n  NestedScrollable* scrollable3 =\n      new ScrollView(-1, ScrollDirection::kVertical, page_.get());\n  scrollable2->AddChild(scrollable3);\n  NestedScrollable* scrollable4 =\n      new ScrollView(-1, ScrollDirection::kVertical, page_.get());\n  scrollable3->AddChild(scrollable4);\n  NestedScrollable* scrollable5 =\n      new ScrollView(-1, ScrollDirection::kVertical, page_.get());\n  scrollable4->AddChild(scrollable5);\n\n  // The scrollable chain will stop on encountering a scrollable oriented in a\n  // different direction\n  page_->nested_scroll_manager()->DragStart(scrollable5);\n  EXPECT_EQ(page_->nested_scroll_manager()->FindOuterScrollable(scrollable5),\n            scrollable4);\n  EXPECT_EQ(page_->nested_scroll_manager()->FindOuterScrollable(scrollable4),\n            scrollable3);\n  EXPECT_EQ(page_->nested_scroll_manager()->FindOuterScrollable(scrollable3),\n            nullptr);\n\n  // The scrollable chain will stop at the scrollable with nested scrolling\n  // disabled\n  scrollable4->SetEnableNestedScroll(false);\n  page_->nested_scroll_manager()->DragStart(scrollable5);\n  EXPECT_EQ(page_->nested_scroll_manager()->FindOuterScrollable(scrollable5),\n            scrollable4);\n  EXPECT_EQ(page_->nested_scroll_manager()->FindOuterScrollable(scrollable4),\n            nullptr);\n}\n\nTEST_F_UI(NestedScrollableTest, SimpleScroll) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable);\n\n  // No fling\n  DispatchDragEvent({50, 50}, {50, 0}, false);\n  EXPECT_FALSE(scrollable->nested_scroll_manager()->IsFlingRunning());\n  EXPECT_EQ(scrollable->scroll_offset_, 50);\n  DoAnimation(1000);\n  EXPECT_EQ(scrollable->scroll_offset_, 50);\n}\n\nTEST_F_UI(NestedScrollableTest, NestedDrag) {\n  TestScrollable* scrollable1 = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable1);\n  TestScrollable* scrollable2 = new TestScrollable(page_.get(), 100);\n  scrollable1->AddChild(scrollable2);\n\n  page_->nested_scroll_manager()->DragStart(scrollable2);\n\n  // First scroll the inner scrollable\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, 50));\n  EXPECT_EQ(scrollable2->scroll_offset_, 50);\n  EXPECT_EQ(scrollable1->scroll_offset_, 0);\n\n  // Pass the unconsumed delta to the outer scrollable\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, 100));\n  EXPECT_EQ(scrollable2->scroll_offset_, 100);\n  EXPECT_EQ(scrollable1->scroll_offset_, 50);\n\n  // The inner scrollable can't scroll any more\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, 20));\n  EXPECT_EQ(scrollable2->scroll_offset_, 100);\n  EXPECT_EQ(scrollable1->scroll_offset_, 70);\n\n  // Still scroll the inner first\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, -50));\n  EXPECT_EQ(scrollable2->scroll_offset_, 50);\n  EXPECT_EQ(scrollable1->scroll_offset_, 70);\n\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, -100));\n  EXPECT_EQ(scrollable2->scroll_offset_, 0);\n  EXPECT_EQ(scrollable1->scroll_offset_, 20);\n}\n\nTEST_F_UI(NestedScrollableTest, NestedDisabled) {\n  TestScrollable* scrollable1 = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable1);\n  TestScrollable* scrollable2 = new TestScrollable(page_.get(), 100);\n  scrollable2->SetEnableNestedScroll(false);\n  scrollable1->AddChild(scrollable2);\n\n  page_->nested_scroll_manager()->DragStart(scrollable2);\n\n  // Outer scrollable will not scroll as nested scroll is disabled\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, 150));\n  EXPECT_EQ(scrollable2->scroll_offset_, 100);\n  EXPECT_EQ(scrollable1->scroll_offset_, 0);\n\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, -70));\n  EXPECT_EQ(scrollable2->scroll_offset_, 30);\n  EXPECT_EQ(scrollable1->scroll_offset_, 0);\n  page_->nested_scroll_manager()->DragEnd(scrollable2, {});\n\n  // Fling only works on the inner scrollable\n  DispatchDragEvent({50, 50}, {50, 0}, true, 20, 2);\n  DoAnimation();\n  EXPECT_EQ(scrollable2->scroll_offset_, 100);\n  EXPECT_EQ(scrollable1->scroll_offset_, 0);\n}\n\nTEST_F_UI(NestedScrollableTest, RubberBandEffectAndBounce) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  scrollable->SetOverscrollEnabled(true);\n  page_->AddChild(scrollable);\n\n  // Drag to trigger the rubber band effect\n  page_->nested_scroll_manager()->DragStart(scrollable);\n  page_->nested_scroll_manager()->DragUpdate(scrollable, {0, -100});\n  EXPECT_EQ(scrollable->OverscrollOffset().y(), -100);\n  EXPECT_GT(scrollable->ClampedOverscrollOffset().y(), -60);\n  EXPECT_LT(scrollable->ClampedOverscrollOffset().y(), -30);\n\n  // Release to trigger the bounce animation\n  float bounce_start_offset = scrollable->ClampedOverscrollOffset().y();\n  page_->nested_scroll_manager()->DragEnd(scrollable, {0, 0});\n  DoAnimation(1);\n  EXPECT_GT(scrollable->ClampedOverscrollOffset().y(), bounce_start_offset);\n  EXPECT_LT(scrollable->ClampedOverscrollOffset().y(), 0);\n  DoAnimation(1000);\n  EXPECT_EQ(scrollable->OverscrollOffset().y(), 0);\n}\n\nTEST_F_UI(NestedScrollableTest, Fling) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable);\n\n  // Already at top, drag down should not trigger fling\n  DispatchDragEvent({50, 50}, {50, 100}, true);\n  EXPECT_EQ(scrollable->scroll_offset_, 0);\n  EXPECT_TRUE(scrollable->nested_scroll_manager()->IsFlingRunning());\n  DoAnimation(1);\n  EXPECT_FALSE(scrollable->nested_scroll_manager()->IsFlingRunning());\n\n  // Drag to trigger fling animation\n  DispatchDragEvent({50, 50}, {50, 0}, true);\n  EXPECT_TRUE(scrollable->nested_scroll_manager()->IsFlingRunning());\n  EXPECT_GT(scrollable->scroll_offset_, 0);\n  EXPECT_LE(scrollable->scroll_offset_, 50);\n  DoAnimation(1);\n  EXPECT_TRUE(scrollable->nested_scroll_manager()->IsFlingRunning());\n\n  // Wait a second to finish the fling animation\n  DoAnimation(1000);\n  EXPECT_GT(scrollable->scroll_offset_, 50);\n}\n\nTEST_F_UI(NestedScrollableTest, FlingToBounce) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  scrollable->SetOverscrollEnabled(true);\n  page_->AddChild(scrollable);\n\n  // Drag to trigger the bounce animation\n  DispatchDragEvent({50, 50}, {50, 0}, true, 10, 1);\n  EXPECT_GT(scrollable->scroll_offset_, 0);\n  EXPECT_LE(scrollable->scroll_offset_, 50);\n\n  // Wait a second to finish the bounce animation\n  DoAnimation();\n  EXPECT_EQ(scrollable->bounced_, true);\n}\n\nTEST_F_UI(NestedScrollableTest, NestedFling) {\n  TestScrollable* scrollable1 = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable1);\n  TestScrollable* scrollable2 = new TestScrollable(page_.get(), 100);\n  scrollable1->AddChild(scrollable2);\n\n  DispatchDragEvent({50, 50}, {50, 0}, true, 10, 1);\n  EXPECT_LT(scrollable2->scroll_offset_, 100);\n  EXPECT_EQ(scrollable1->scroll_offset_, 0);\n\n  // The outer scrollable also scrolled by the fling animation\n  DoAnimation();\n  EXPECT_EQ(scrollable2->scroll_offset_, 100);\n  EXPECT_EQ(scrollable1->scroll_offset_, 100);\n}\n\nTEST_F_UI(NestedScrollableTest, RestoreOverscrollFirst) {\n  TestScrollable* scrollable1 = new TestScrollable(page_.get(), 100);\n  scrollable1->SetOverscrollEnabled(true);\n  page_->AddChild(scrollable1);\n  TestScrollable* scrollable2 = new TestScrollable(page_.get(), 100);\n  scrollable1->AddChild(scrollable2);\n\n  // The outer scrollable is overscrolled\n  page_->nested_scroll_manager()->DragStart(scrollable2);\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, -50));\n  EXPECT_EQ(scrollable2->scroll_offset_, 0);\n  EXPECT_EQ(scrollable1->scroll_offset_, 0);\n  EXPECT_EQ(scrollable1->OverscrollOffset().y(), -50);\n\n  // First restore the overscroll, then scroll the inner scrollable\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, 20));\n  EXPECT_EQ(scrollable2->scroll_offset_, 0);\n  EXPECT_EQ(scrollable1->scroll_offset_, 0);\n  EXPECT_EQ(scrollable1->OverscrollOffset().y(), -30);\n  page_->nested_scroll_manager()->DragUpdate(scrollable2, FloatPoint(0, 50));\n  EXPECT_EQ(scrollable2->scroll_offset_, 20);\n  EXPECT_EQ(scrollable1->scroll_offset_, 0);\n  EXPECT_EQ(scrollable1->OverscrollOffset().y(), 0);\n}\n\nTEST_F_UI(NestedScrollableTest, StopFlingOnTouch) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable);\n\n  // Trigger fling animation\n  DispatchDragEvent({50, 50}, {50, 0}, true, 10, 1);\n  EXPECT_GT(scrollable->scroll_offset_, 0);\n  EXPECT_LE(scrollable->scroll_offset_, 50);\n\n  // The fling animation has not been finished\n  DoAnimation(10);\n  EXPECT_LT(scrollable->scroll_offset_, 100);\n\n  // Stop the fling animation by touch\n  float current_offset = scrollable->scroll_offset_;\n  DispatchTapEvent({50, 50});\n  DoAnimation();\n  EXPECT_EQ(scrollable->scroll_offset_, current_offset);\n}\n\nTEST_F_UI(NestedScrollableTest, StopBounceOnTouch) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  scrollable->SetOverscrollEnabled(true);\n  page_->AddChild(scrollable);\n\n  // Drag to overscroll\n  DispatchDragEvent({50, 50}, {50, 100}, false);\n  EXPECT_EQ(scrollable->scroll_offset_, 0);\n  EXPECT_LT(scrollable->OverscrollOffset().y(), 0);\n  EXPECT_GE(scrollable->OverscrollOffset().y(), -50);\n\n  // The bounce animation has not been finished\n  DoAnimation(10);\n  EXPECT_LT(scrollable->OverscrollOffset().y(), 0);\n\n  // Touch to stop the bounce animation\n  float current_offset = scrollable->OverscrollOffset().y();\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kDownEvent, {50, 50})});\n  DoAnimation();\n  EXPECT_EQ(scrollable->OverscrollOffset().y(), current_offset);\n\n  // Release the touch to trigger bounce again\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kUpEvent, {50, 50})});\n  DoAnimation();\n  EXPECT_EQ(scrollable->OverscrollOffset().y(), 0);\n}\n\nTEST_F_UI(NestedScrollableTest, ScrollMode) {\n  TestScrollable* outer = new TestScrollable(page_.get(), 100);\n  page_->AddChild(outer);\n  TestScrollable* inner = new TestScrollable(page_.get(), 100);\n  outer->AddChild(inner);\n\n  inner->SetForwardMode(NestedScrollMode::kSelfOnly);\n  page_->nested_scroll_manager()->DragStart(inner);\n  // Only the inner scrollable can scroll\n  page_->nested_scroll_manager()->DragUpdate(inner, FloatPoint(0, 150));\n  EXPECT_EQ(inner->scroll_offset_, 100);\n  EXPECT_EQ(outer->scroll_offset_, 0);\n\n  inner->SetForwardMode(NestedScrollMode::kSelfFirst);\n  page_->nested_scroll_manager()->DragUpdate(inner, FloatPoint(0, -50));\n  EXPECT_EQ(inner->scroll_offset_, 50);\n  // The inner scrollable scrolls first, then the outer scrollable\n  page_->nested_scroll_manager()->DragUpdate(inner, FloatPoint(0, 100));\n  EXPECT_EQ(inner->scroll_offset_, 100);\n  EXPECT_EQ(outer->scroll_offset_, 50);\n\n  inner->SetBackwardMode(NestedScrollMode::kParentFirst);\n  // The outer scrollable scrolls first, then the inner scrollable\n  page_->nested_scroll_manager()->DragUpdate(inner, FloatPoint(0, -100));\n  EXPECT_EQ(inner->scroll_offset_, 50);\n  EXPECT_EQ(outer->scroll_offset_, 0);\n}\n\nTEST_F_UI(NestedScrollableTest, DestroyOnDrag) {\n  auto outer = new ScrollView(-1, ScrollDirection::kVertical, page_.get());\n  outer->SetBound(0, 0, 100, 100);\n  page_->AddChild(outer);\n\n  auto inner = new ScrollView(-1, ScrollDirection::kVertical, page_.get());\n  inner->SetBound(0, 0, 100, 300);\n  inner->SetTouchSlop(-1);\n  outer->AddChild(inner, 0);\n\n  auto box_view = new View(-1, page_.get());\n  box_view->SetBound(0, 0, 100, 500);\n  inner->AddChild(box_view, 0);\n  // Make sure recalculate the overflow rect\n  inner->OnLayoutUpdated();\n  outer->OnLayoutUpdated();\n  EXPECT_EQ(200, inner->GetRenderScroll()->MaxScrollHeight());\n  EXPECT_EQ(200, outer->GetRenderScroll()->MaxScrollHeight());\n\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kDownEvent, {50, 50})});\n  page_->DispatchPointerEvent({CreatePointer(\n      0, PointerEvent::EventType::kMoveEvent, {50, 20}, {0, -30})});\n  EXPECT_EQ(inner->GetScrollOffset().y(), 30);\n  page_->DestroyAllChildren();\n  page_->DispatchPointerEvent({CreatePointer(\n      0, PointerEvent::EventType::kMoveEvent, {50, 0}, {0, -20})});\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kUpEvent, {50, 20}, {0, 0})});\n\n  // Just ensure there is no crash\n}\n\nTEST_F_UI(NestedScrollableTest, DestroyOnFling) {\n  auto outer = new ScrollView(-1, ScrollDirection::kVertical, page_.get());\n  outer->SetBound(0, 0, 100, 100);\n  page_->AddChild(outer);\n\n  auto inner = new ScrollView(-1, ScrollDirection::kVertical, page_.get());\n  inner->SetBound(0, 0, 100, 300);\n  inner->SetTouchSlop(-1);\n  outer->AddChild(inner, 0);\n\n  auto box_view = new View(-1, page_.get());\n  box_view->SetBound(0, 0, 100, 500);\n  inner->AddChild(box_view, 0);\n  // Make sure recalculate the overflow rect\n  inner->OnLayoutUpdated();\n  outer->OnLayoutUpdated();\n  EXPECT_EQ(200, inner->GetRenderScroll()->MaxScrollHeight());\n  EXPECT_EQ(200, outer->GetRenderScroll()->MaxScrollHeight());\n\n  DispatchDragEvent({50, 50}, {50, 0}, true);\n  EXPECT_EQ(inner->GetScrollOffset().y(), 50);\n  DoAnimation(20);\n  EXPECT_GT(inner->GetScrollOffset().y(), 50);\n  page_->DestroyAllChildren();\n  DoAnimation(100);\n\n  // Just ensure there is no crash\n}\n\nTEST_F_UI(NestedScrollableTest, ManagerStatusDrag) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable);\n\n  page_->nested_scroll_manager()->DragStart(scrollable);\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kDragging);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kDragging);\n  page_->nested_scroll_manager()->DragEnd(scrollable, {0, 0});\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n}\n\nTEST_F_UI(NestedScrollableTest, ManagerStatusDragWithFling) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable);\n\n  page_->nested_scroll_manager()->DragStart(scrollable);\n  page_->nested_scroll_manager()->DragEnd(scrollable, {0, 500});\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kFling);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kFling);\n  DoAnimation(1000);\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  EXPECT_GT(scrollable->scroll_offset_, 0);\n}\n\nTEST_F_UI(NestedScrollableTest, ManagerStatusDragWithBounce) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  scrollable->SetOverscrollEnabled(true);\n  page_->AddChild(scrollable);\n\n  page_->nested_scroll_manager()->DragStart(scrollable);\n  page_->nested_scroll_manager()->DragUpdate(scrollable, {0, -10});\n  EXPECT_EQ(scrollable->OverscrollOffset().y(), -10);\n  page_->nested_scroll_manager()->DragEnd(scrollable, {0, 0});\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kBounce);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kBounce);\n  DoAnimation(1000);\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(scrollable->scroll_offset_, 0);\n  EXPECT_EQ(scrollable->OverscrollOffset().y(), 0);\n}\n\nTEST_F_UI(NestedScrollableTest, ManagerStatusDragWithBounceDisabled) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable);\n\n  page_->nested_scroll_manager()->DragStart(scrollable);\n  page_->nested_scroll_manager()->DragUpdate(scrollable, {0, -10});\n  EXPECT_EQ(scrollable->scroll_offset_, 0);\n  EXPECT_EQ(scrollable->OverscrollOffset().y(), 0);\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kDragging);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kDragging);\n  page_->nested_scroll_manager()->DragEnd(scrollable, {0, 0});\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n}\n\nTEST_F_UI(NestedScrollableTest, ManagerStatusFlingWithBounce) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  scrollable->SetOverscrollEnabled(true);\n  page_->AddChild(scrollable);\n\n  page_->nested_scroll_manager()->DragStart(scrollable);\n  page_->nested_scroll_manager()->DragUpdate(scrollable, {0, 30});\n  page_->nested_scroll_manager()->DragEnd(scrollable, {0, -1000});\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kFling);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kFling);\n  bool bounced = false;\n  for (int i = 0; i < 200; i++) {\n    DoAnimation(5);\n    if (scrollable->nested_scroll_manager()->GetScrollStatus() ==\n        Scrollable::ScrollStatus::kBounce) {\n      bounced = true;\n    }\n  }\n  EXPECT_TRUE(bounced);\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(scrollable->scroll_offset_, 0);\n  EXPECT_EQ(scrollable->OverscrollOffset().y(), 0);\n}\n\nTEST_F_UI(NestedScrollableTest, ManagerStatusFlingWithBounceDisabled) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable);\n\n  page_->nested_scroll_manager()->DragStart(scrollable);\n  page_->nested_scroll_manager()->DragUpdate(scrollable, {0, 30});\n  page_->nested_scroll_manager()->DragEnd(scrollable, {0, -1000});\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kFling);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kFling);\n  for (int i = 0; i < 200; i++) {\n    DoAnimation(5);\n    if (scrollable->scroll_offset_ == 0) {\n      break;\n    }\n  }\n  EXPECT_EQ(scrollable->scroll_offset_, 0);\n  // Verify immediate transition to idle status upon reaching the boundary\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n}\n\nTEST_F_UI(NestedScrollableTest, ManagerStatusTouchOnFling) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  page_->AddChild(scrollable);\n\n  page_->nested_scroll_manager()->DragStart(scrollable);\n  page_->nested_scroll_manager()->DragEnd(scrollable, {0, 500});\n  DoAnimation(10);\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kFling);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kFling);\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kDownEvent, {50, 50})});\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kDragging);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kDragging);\n}\n\nTEST_F_UI(NestedScrollableTest, ManagerStatusTouchOnBounce) {\n  TestScrollable* scrollable = new TestScrollable(page_.get(), 100);\n  scrollable->SetOverscrollEnabled(true);\n  page_->AddChild(scrollable);\n\n  page_->nested_scroll_manager()->DragStart(scrollable);\n  page_->nested_scroll_manager()->DragEnd(scrollable, {0, -1000});\n  DoAnimation(10);\n  // The fling animation should be changed to a bounce animation immediately.\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kBounce);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kBounce);\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kDownEvent, {50, 50})});\n  EXPECT_EQ(scrollable->nested_scroll_manager()->GetScrollStatus(),\n            Scrollable::ScrollStatus::kDragging);\n  EXPECT_EQ(scrollable->GetScrollStatus(), Scrollable::ScrollStatus::kDragging);\n}\n\nTEST_F_UI(NestedScrollableTest, ScrollableStatusDrag) {\n  TestScrollable* outer = new TestScrollable(page_.get(), 100);\n  page_->AddChild(outer);\n  TestScrollable* inner = new TestScrollable(page_.get(), 100);\n  outer->AddChild(inner);\n\n  page_->nested_scroll_manager()->DragStart(inner);\n  EXPECT_EQ(inner->GetScrollStatus(), Scrollable::ScrollStatus::kDragging);\n  EXPECT_EQ(outer->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  page_->nested_scroll_manager()->DragUpdate(inner, {0, 50});\n  EXPECT_EQ(inner->GetScrollStatus(), Scrollable::ScrollStatus::kDragging);\n  EXPECT_EQ(outer->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  page_->nested_scroll_manager()->DragUpdate(inner, {0, 60});\n  // The drag status has been transferred to the outer scrollable\n  EXPECT_EQ(inner->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(outer->GetScrollStatus(), Scrollable::ScrollStatus::kDragging);\n  page_->nested_scroll_manager()->DragEnd(inner, {0, 0});\n  EXPECT_EQ(outer->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n}\n\nTEST_F_UI(NestedScrollableTest, ScrollableStatusFling) {\n  TestScrollable* outer = new TestScrollable(page_.get(), 100);\n  page_->AddChild(outer);\n  TestScrollable* inner = new TestScrollable(page_.get(), 100);\n  outer->AddChild(inner);\n\n  page_->nested_scroll_manager()->DragStart(inner);\n  EXPECT_EQ(inner->GetScrollStatus(), Scrollable::ScrollStatus::kDragging);\n  EXPECT_EQ(outer->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  page_->nested_scroll_manager()->DragUpdate(inner, {0, 99});\n  EXPECT_EQ(inner->GetScrollStatus(), Scrollable::ScrollStatus::kDragging);\n  EXPECT_EQ(outer->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  page_->nested_scroll_manager()->DragEnd(inner, {0, 2000});\n  EXPECT_EQ(inner->GetScrollStatus(), Scrollable::ScrollStatus::kFling);\n  EXPECT_EQ(outer->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  DoAnimation(10);\n  // The fling status has been transferred to the outer scrollable\n  EXPECT_EQ(inner->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n  EXPECT_EQ(outer->GetScrollStatus(), Scrollable::ScrollStatus::kFling);\n  DoAnimation(1000);\n  EXPECT_EQ(outer->GetScrollStatus(), Scrollable::ScrollStatus::kIdle);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/nested_scroll/overscroll_effect.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/nested_scroll/overscroll_effect.h\"\n\nnamespace clay {\n\nvoid OffsetOverscrollEffect::OnOverscroll(FloatPoint new_offset,\n                                          FloatPoint prev_offset) {\n  if (new_offset.x() != prev_offset.x()) {\n    bool left_side =\n        new_offset.x() < 0 || (new_offset.x() == 0 && prev_offset.x() < 0);\n    if (left_side) {\n      render_scroll_->SetScrollLeft(new_offset.x());\n    } else {\n      render_scroll_->SetScrollLeft(render_scroll_->MaxScrollWidth() +\n                                    new_offset.x());\n    }\n  }\n\n  if (new_offset.y() != prev_offset.y()) {\n    bool top_side =\n        new_offset.y() < 0 || (new_offset.y() == 0 && prev_offset.y() < 0);\n    if (top_side) {\n      render_scroll_->SetScrollTop(new_offset.y());\n    } else {\n      render_scroll_->SetScrollTop(render_scroll_->MaxScrollHeight() +\n                                   new_offset.y());\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/nested_scroll/overscroll_effect.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_NESTED_SCROLL_OVERSCROLL_EFFECT_H_\n#define CLAY_UI_COMPONENT_NESTED_SCROLL_OVERSCROLL_EFFECT_H_\n\n#include \"clay/ui/rendering/render_scroll.h\"\n\nnamespace clay {\n\nclass OverscrollEffect {\n public:\n  explicit OverscrollEffect(RenderScroll* render_scroll)\n      : render_scroll_(render_scroll) {}\n  virtual ~OverscrollEffect() = default;\n\n  virtual void OnOverscroll(FloatPoint offset, FloatPoint prev_offset) = 0;\n\n protected:\n  RenderScroll* render_scroll_;\n};\n\nclass OffsetOverscrollEffect : public OverscrollEffect {\n public:\n  using OverscrollEffect::OverscrollEffect;\n\n  void OnOverscroll(FloatPoint offset, FloatPoint prev_offset) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_NESTED_SCROLL_OVERSCROLL_EFFECT_H_\n"
  },
  {
    "path": "clay/ui/component/nested_scroll/raster_fling_manager.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/nested_scroll/raster_fling_manager.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/flow/animation/scroll_offset_animation.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/nested_scroll/nested_scroll_manager.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/rendering/render_scroll.h\"\n\nnamespace clay {\n\nRasterFlingManager::RasterFlingManager(\n    NestedScrollManager* nested_scroll_manager)\n    : nested_scroll_manager_(nested_scroll_manager) {\n  FML_DCHECK(nested_scroll_manager->IsSmoothScrollEnabled());\n}\n\nbool RasterFlingManager::StartAnimation(NestedScrollable* scrollable,\n                                        float velocity) {\n  // Stop the current raster fling animation if exists.\n  StopAnimation();\n  // Only support ScrollView now.\n  if (!scrollable->Is<ScrollView>()) {\n    FML_DLOG(INFO) << \"only ScrollView support raster fling now.\";\n    return false;\n  }\n  if (scrollable->IsUnderOverscroll()) {\n    // The raster fling animation is not supported when target scrollable is\n    // under overscroll, fallback to fling on UI.\n    FML_DLOG(INFO) << \"the target scrollable is under overscroll.\";\n    return false;\n  }\n  RenderScroll* render_scroll =\n      static_cast<RenderScroll*>(scrollable->render_object());\n  if (!render_scroll->HasValidID()) {\n    FML_DLOG(INFO) << \"the target scrollable has no valid id.\";\n    return false;\n  }\n\n  current_scrollable_ = scrollable->GetWeakPtr();\n  ScrollDirection direction = scrollable->GetScrollDirection();\n  float start_value;\n  if (direction == ScrollDirection::kVertical) {\n    start_value = render_scroll->ScrollTop();\n  } else {\n    start_value = render_scroll->ScrollLeft();\n  }\n  auto animator = std::make_unique<FlingAnimator>();\n  animator->SetFriction(1.0f);\n  animator->SetDensity(\n      nested_scroll_manager_->page_view_\n          ->GetPixelRatio<kPixelTypeLogical, kPixelTypeClay>());\n  animator->SetStartValue(start_value);\n  animator->SetStartVelocity(velocity);\n  // Construct the `ScrollOffsetAnimation` with fling animator and bind to\n  // RenderScroll.\n  auto scroll_offset_animation = std::make_unique<clay::ScrollOffsetAnimation>(\n      current_session_id_, direction, std::move(animator));\n  render_scroll->StartRasterFling(std::move(scroll_offset_animation));\n  return true;\n}\n\nvoid RasterFlingManager::StopAnimation() {\n  current_session_id_++;\n  if (current_scrollable_) {\n    RenderScroll* render_scroll =\n        static_cast<RenderScroll*>(current_scrollable_->render_object());\n    // Mark fling animation end for RenderScroll;\n    render_scroll->StopRasterFling();\n    current_scrollable_.reset();\n  }\n}\n\nvoid RasterFlingManager::OnAnimationEnd(NestedScrollable* scrollable,\n                                        int32_t session_id, float value,\n                                        float velocity) {\n  RenderScroll* render_scroll =\n      static_cast<RenderScroll*>(scrollable->render_object());\n  // Mark fling animation end for RenderScroll;\n  render_scroll->StopRasterFling();\n  // Session dismatch, ignore the update opration.\n  if (session_id != current_session_id_) {\n    return;\n  }\n  nested_scroll_manager_->OnFlingEndFromRaster(scrollable, velocity);\n}\n\nvoid RasterFlingManager::OnAnimationUpdate(NestedScrollable* scrollable,\n                                           int32_t session_id,\n                                           float scroll_offset,\n                                           bool ignore_ui_repaint) {\n  // Session dismatch, ignore the update opration.\n  if (session_id != current_session_id_) {\n    return;\n  }\n  // Do scroll and update the scroll offset of the ScrollView.\n  ScrollView* scroll_view = static_cast<ScrollView*>(scrollable);\n  // Should ignore repaint if `ignore_ui_repaint`.\n  scroll_view->DoScrollFromRaster(scroll_offset, ignore_ui_repaint);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/nested_scroll/raster_fling_manager.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_NESTED_SCROLL_RASTER_FLING_MANAGER_H_\n#define CLAY_UI_COMPONENT_NESTED_SCROLL_RASTER_FLING_MANAGER_H_\n\n#include \"clay/ui/component/nested_scroll/nested_scrollable.h\"\n\nnamespace clay {\nclass NestedScrollManager;\n\nclass RasterFlingManager {\n public:\n  explicit RasterFlingManager(NestedScrollManager* nested_scroll_manager);\n\n  // Start a new raster fling animation.\n  // |scrollable| is the target scrollable to fling.\n  bool StartAnimation(NestedScrollable* scrollable, float velocity);\n  // Stop the current raster fling animation if exists.\n  void StopAnimation();\n  // It's called when the fling animation is finished from raster.\n  // |value| is the final value after fling animation.\n  // |velocity| is the remaining velocity after fling animation.\n  // We can use the remaining velocity to do the nested scroll if needed.\n  void OnAnimationEnd(NestedScrollable* scrollable, int32_t session_id,\n                      float value, float velocity);\n  // It's called when the fling animation is updated from raster.\n  void OnAnimationUpdate(NestedScrollable* scrollable, int32_t session_id,\n                         float scroll_offset, bool ignore_ui_repaint);\n\n private:\n  NestedScrollManager* nested_scroll_manager_;\n  // Hold the weak pointer of the current scrollable to make it safe-access.\n  fml::WeakPtr<BaseView> current_scrollable_;\n  int32_t current_session_id_ = 0;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_NESTED_SCROLL_RASTER_FLING_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/component/overlay_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/overlay_view.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/overlay_manager.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/gesture/gesture_manager.h\"\n#include \"clay/ui/rendering/render_container.h\"\n#include \"clay/ui/rendering/render_overlay.h\"\n\nnamespace clay {\n\nOverlayView::OverlayView(uint32_t id, PageView* page_view)\n    : WithTypeInfo(id, \"overlay-view\", std::make_unique<RenderOverlay>(),\n                   page_view) {\n  overlay_manager_ = page_view->overlay_manager();\n  overlay_id_ = (overlay_manager_->GenerateNextOverlayId());\n  render_object_->SetOverlay(true);\n  SetIsFocusScope();\n  SetIsFocusFence();\n}\nOverlayView::OverlayView(uint32_t id, const std::string& tag,\n                         PageView* page_view)\n    : WithTypeInfo(id, tag, std::make_unique<RenderOverlay>(), page_view) {\n  overlay_manager_ = page_view->overlay_manager();\n  overlay_id_ = overlay_manager_->GenerateNextOverlayId();\n  render_object_->SetOverlay(true);\n  SetIsFocusScope();\n  SetIsFocusFence();\n}\n\nOverlayView::~OverlayView() = default;\n\nvoid OverlayView::SetPassEventsThrough(bool pass_events_through) {\n  // NOTE(hanhaoshen): Currently we only support switch pass_events_through to\n  // false once.\n  if (!pass_events_through && pass_events_through_) {\n    pass_events_through_ = pass_events_through;\n    focus_manager_->SetIsRootScope();\n    overlay_manager_->SaveFocus();\n  }\n}\n\nvoid OverlayView::SetFullScreen(bool full_screen) {\n  // NOTE(hanhaoshen): Currently we only support switch full_screen to true\n  // once.\n  if (full_screen && !full_screen_) {\n    full_screen_ = true;\n    SetX(0);\n    SetY(0);\n    SetWidth(page_view_->Width());\n    SetHeight(page_view_->Height());\n  }\n}\n\nvoid OverlayView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  std::string attr(attr_c);\n  if (attr.compare(\"visible\") == 0) {\n    bool visible = attribute_utils::GetBool(value);\n    visible ? Show() : Hide();\n  } else if (attr.compare(\"events-pass-through\") == 0) {\n    SetPassEventsThrough(attribute_utils::GetBool(value));\n  } else if (attr.compare(\"overlay-id\") == 0) {\n    std::string new_overlay_id = attribute_utils::GetCString(value);\n    overlay_id_ = new_overlay_id;\n  } else if (attr.compare(\"full-screen\") == 0) {\n    SetFullScreen(attribute_utils::GetBool(value));\n  } else {\n    BaseView::SetAttribute(attr_c, value);\n  }\n}\n\nvoid OverlayView::SetBound(float left, float top, float width, float height) {\n  if (full_screen_) {\n    return;\n  }\n\n  BaseView::SetBound(left, top, width, height);\n}\n\nvoid OverlayView::Hide() {\n  if (!Visible()) {\n    return;\n  }\n  FML_DCHECK(overlay_manager_);\n  SetVisible(false);\n  if (attach_to_tree_) {\n    SetFocusable(false);\n    overlay_manager_->OnHideOverlay(this);\n  }\n}\n\nvoid OverlayView::Show() {\n  if (Visible()) {\n    return;\n  }\n  FML_DCHECK(overlay_manager_);\n  SetVisible(true);\n  if (attach_to_tree_) {\n    SetFocusable(true);\n    overlay_manager_->OnShowOverlay(this);\n  }\n}\n\nbool OverlayView::HitTest(const PointerEvent& event, HitTestResult& result,\n                          bool& is_pass_through) {\n  bool ret = BaseView::HitTest(event, result);\n  if (pass_events_through_ && !result.empty() && result.back().get() == this) {\n    // When `events-pass-through` is enabled, hit testing is prevented on the\n    // overlay itself.\n    is_pass_through = true;\n    result.pop_back();\n    if (!result.empty()) {\n      // Insert a null elememt as a boundary symbol. It will be checked\n      // in gesture_manager after hittest.\n      result.emplace_back();\n    }\n    return !result.empty();\n  }\n  if (!result.empty()) {\n    // Insert a null elememt as a boundary symbol. It will be checked\n    // in gesture_manager after hittest.\n    result.emplace_back();\n  }\n  return ret;\n}\n\nbool OverlayView::HitTest(const PointerEvent& event, HitTestResult& result) {\n  bool is_pass_through = false;\n  return HitTest(event, result, is_pass_through);\n}\n\nvoid OverlayView::OnAttachToTree() {\n  BaseView::OnAttachToTree();\n\n  if (Visible()) {\n    SetFocusable(true);\n    overlay_manager_->OnShowOverlay(this);\n  }\n}\n\nvoid OverlayView::OnDetachFromTree() {\n  Hide();\n  BaseView::OnDetachFromTree();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/overlay_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_OVERLAY_VIEW_H_\n#define CLAY_UI_COMPONENT_OVERLAY_VIEW_H_\n\n#include <memory>\n#include <string>\n#include <string_view>\n#include <vector>\n\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nclass GestureManager;\nclass OverlayManager;\nclass OverlayView : public WithTypeInfo<OverlayView, BaseView> {\n public:\n  OverlayView(uint32_t id, PageView* page_view);\n  OverlayView(uint32_t id, const std::string& tag, PageView* page_view);\n  ~OverlayView() override;\n\n  void SetPassEventsThrough(bool pass_events_through);\n\n  bool IsFullScreen() const { return full_screen_; }\n  void SetFullScreen(bool full_screen);\n\n  void SetAttribute(const char* attr, const clay::Value& value) override;\n\n  const std::string_view GetOverlayId() const { return overlay_id_; }\n  void SetOverlayId(std::string_view new_id) { overlay_id_ = new_id; }\n\n  void SetBound(float left, float top, float width, float height) override;\n\n  bool HitTest(const PointerEvent& event, HitTestResult& result,\n               bool& is_pass_through);\n  bool HitTest(const PointerEvent& event, HitTestResult& result) override;\n\n  void OnAttachToTree() override;\n  void OnDetachFromTree() override;\n\n  void SetLevel(int level) { level_ = level; }\n  int Level() const { return level_; }\n\n  virtual bool ShouldChangeOffset() const { return false; }\n\n  bool CanEventsPassThroughToViewsBehind() const override {\n    return pass_events_through_;\n  }\n\n  virtual Point GetTouchOffset() const { return Point(); }\n\n protected:\n  void Show();\n  void Hide();\n  int level_ = 1;\n  OverlayManager* overlay_manager_;\n  std::string overlay_id_;\n  // Handle touch/focus event in overlay or not.\n  bool pass_events_through_ = true;\n  bool full_screen_ = false;\n\n  bool IsIndependentSubViewTree() const override { return true; }\n\n private:\n  FRIEND_TEST(OverlayViewTest, ShowAndHide);\n  FRIEND_TEST(OverlayViewTest, Level);\n\n  // Control the display order of overlays. The larger the level, the lower it\n  // will be displayed. You can only adjust the level when the overlay is not\n  // visible.\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_OVERLAY_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/overlay_view_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"base/include/fml/thread.h\"\n#include \"clay/ui/common/overlay_manager.h\"\n#include \"clay/ui/component/overlay_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/rendering/render_object.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(OverlayViewTest, ShowAndHide) {\n  auto thread = std::make_unique<fml::Thread>(\"ui\");\n  std::unique_ptr<PageView> page_view =\n      std::make_unique<PageView>(0, nullptr, thread->GetTaskRunner());\n  page_view->SetBound(0, 0, 1000, 1000);\n  std::unique_ptr<OverlayView> overlay1 =\n      std::make_unique<OverlayView>(1, page_view.get());\n  std::unique_ptr<OverlayView> overlay2 =\n      std::make_unique<OverlayView>(2, page_view.get());\n  overlay1->SetBound(0, 0, 1000, 1000);\n  overlay2->SetBound(0, 0, 1000, 1000);\n\n  auto child1 = std::make_unique<View>(3, page_view.get());\n  child1->SetBound(0, 0, 100, 100);\n  overlay1->AddChild(child1.get());\n\n  auto child2 = std::make_unique<View>(4, page_view.get());\n  child2->SetBound(100, 100, 200, 200);\n  overlay2->AddChild(child2.get());\n\n  FloatPoint unused;\n  EXPECT_EQ(page_view->GetTopViewToAcceptEvent(FloatPoint(10, 10), &unused),\n            page_view.get());\n\n  EXPECT_EQ(page_view->overlay_manager()->overlays_.size(), (size_t)0);\n\n  page_view->AddChild(overlay1.get());\n  page_view->AddChild(overlay2.get());\n\n  page_view->Layout();\n\n  EXPECT_EQ(page_view->overlay_manager()->overlays_.size(), (size_t)2);\n\n  overlay2->Hide();\n\n  EXPECT_EQ(page_view->overlay_manager()->overlays_.size(), (size_t)1);\n\n  EXPECT_EQ(page_view->GetTopViewToAcceptEvent(FloatPoint(10, 10), &unused),\n            child1.get());\n  EXPECT_EQ(page_view->GetTopViewToAcceptEvent(FloatPoint(110, 10), &unused),\n            page_view.get());\n\n  overlay2->SetPassEventsThrough(false);\n  overlay2->Show();\n\n  EXPECT_EQ(page_view->overlay_manager()->overlays_.size(), (size_t)2);\n  EXPECT_EQ(page_view->GetTopViewToAcceptEvent(FloatPoint(110, 10), &unused),\n            overlay2.get());\n  EXPECT_EQ(page_view->GetTopViewToAcceptEvent(FloatPoint(110, 110), &unused),\n            child2.get());\n}\n\nTEST(OverlayViewTest, Level) {\n  auto thread = std::make_unique<fml::Thread>(\"ui\");\n  std::unique_ptr<PageView> page_view =\n      std::make_unique<PageView>(-1, nullptr, thread->GetTaskRunner());\n  page_view->SetBound(0, 0, 1000, 1000);\n  std::unique_ptr<OverlayView> overlay1 =\n      std::make_unique<OverlayView>(-1, page_view.get());\n  std::unique_ptr<OverlayView> overlay2 =\n      std::make_unique<OverlayView>(-1, page_view.get());\n\n  page_view->AddChild(overlay1.get());\n  page_view->AddChild(overlay2.get());\n\n  page_view->Layout();\n\n  // The larger the level, the lower it will be displayed\n  overlay1->Hide();\n  overlay2->Hide();\n  overlay1->SetLevel(1);\n  overlay2->SetLevel(2);\n  overlay1->Show();\n  overlay2->Show();\n  EXPECT_EQ(page_view->overlay_manager()->overlays_.back(), overlay1.get());\n  overlay1->Hide();\n  overlay2->Hide();\n  overlay1->SetLevel(2);\n  overlay2->SetLevel(1);\n  overlay1->Show();\n  overlay2->Show();\n  EXPECT_EQ(page_view->overlay_manager()->overlays_.back(), overlay2.get());\n\n  // For overlays with the same level, the newly added ones are at the front (to\n  // user).\n  overlay1->Hide();\n  overlay2->Hide();\n  overlay1->SetLevel(1);\n  overlay2->SetLevel(1);\n  overlay1->Show();\n  overlay2->Show();\n  EXPECT_EQ(page_view->overlay_manager()->overlays_.back(), overlay2.get());\n  overlay1->Hide();\n  overlay2->Hide();\n  overlay1->SetLevel(1);\n  overlay2->SetLevel(1);\n  overlay2->Show();\n  overlay1->Show();\n  EXPECT_EQ(page_view->overlay_manager()->overlays_.back(), overlay1.get());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/page_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/page_view.h\"\n\n#include <algorithm>\n#include <cstddef>\n#include <cstdint>\n#include <functional>\n#include <list>\n#include <memory>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/common/graphics/screenshot.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/animation_handler.h\"\n#include \"clay/gfx/animation/keyframe.h\"\n#include \"clay/gfx/animation/keyframe_set.h\"\n#include \"clay/gfx/geometry/box_shadow_operations.h\"\n#include \"clay/gfx/geometry/box_shadow_value.h\"\n#include \"clay/gfx/geometry/filter_operations.h\"\n#include \"clay/gfx/geometry/filter_value.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/gfx/image/image_data_cache.h\"\n#include \"clay/gfx/image/image_upload_manager.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/net/loader/resource_loader_factory.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/shell/common/pipeline_timing_delegate.h\"\n#include \"clay/shell/common/scroll_fluency_monitor_delegate.h\"\n#include \"clay/shell/common/services/instrumentation_service.h\"\n#include \"clay/shell/common/services/raster_frame_service.h\"\n#include \"clay/shell/common/services/vsync_waiter_service.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/overlay_manager.h\"\n#include \"clay/ui/common/utils/watch_dog.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/image_view.h\"\n#include \"clay/ui/component/inline_image_view.h\"\n#include \"clay/ui/component/intersection_observer.h\"\n#include \"clay/ui/component/keywords.h\"\n#include \"clay/ui/component/native_view.h\"\n#include \"clay/ui/component/nested_scroll/nested_scroll_manager.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/event/event_utils.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/event/key_code_converter.h\"\n#include \"clay/ui/gesture/gesture_manager.h\"\n#include \"clay/ui/gesture/long_press_gesture_recognizer.h\"\n#include \"clay/ui/gesture/mouse_cursor_manager.h\"\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/render_page.h\"\n#include \"clay/ui/resource/image_resource_fetcher.h\"\n#include \"clay/version/version.h\"\n\n#ifdef ENABLE_SCREENSHOT_SERVICE\n#include \"clay/shell/common/services/screenshot_service.h\"\n#endif\n\nnamespace clay {\nnamespace {\n\nbool ShouldIgnoreFocus(BaseView* view) {\n  if (view == nullptr) {\n    return false;\n  } else if (view->IgnoreFocus().has_value()) {\n    return *view->IgnoreFocus();\n  } else if (view->Parent() != nullptr) {\n    return ShouldIgnoreFocus(view->Parent());\n  } else {\n    return false;\n  }\n}\n\nclay::Value CreateExposeArray(\n    ExposureDataList& data_arr,\n    std::vector<fml::WeakPtr<BaseView>>& data_set_arr) {\n  clay::Value::Array wrapper_array(data_arr.size());\n  for (size_t i = 0; i < data_arr.size() && i < data_set_arr.size(); i++) {\n    auto& exposure_event = *data_arr[i];\n    BaseView* data_set_owner = data_set_arr[i].get();\n    if (data_set_owner) {\n      const auto& data_set = data_set_owner->GetDataSet();\n      exposure_event[\"dataset\"] = CloneClayValue(data_set);\n      exposure_event[\"dataSet\"] = CloneClayValue(data_set);\n    }\n    wrapper_array[i] = clay::Value(std::move(exposure_event));\n  }\n  data_arr.clear();\n  data_set_arr.clear();\n  return clay::Value(std::move(wrapper_array));\n}\n\nstatic constexpr int64_t kEventStateUpdateDelayTime = 1000;\nstatic constexpr int64_t kEventStateUpdateIntervalTime = 100;\n\nstruct TransformRawDataIndex {\n  static constexpr int INDEX_FUNC = 0;\n  static constexpr int INDEX_TRANSLATE_0 = 1;\n  static constexpr int INDEX_TRANSLATE_0_UNIT = 2;\n  static constexpr int INDEX_TRANSLATE_1 = 3;\n  static constexpr int INDEX_TRANSLATE_1_UNIT = 4;\n  static constexpr int INDEX_TRANSLATE_2 = 5;\n  static constexpr int INDEX_TRANSLATE_2_UNIT = 6;\n};\n}  // namespace\n\n#define DEBUG_KEYFRAMES 0\n\nPageView::PageView(uint32_t id, fml::RefPtr<GPUUnrefQueue> unref_queue,\n                   fml::RefPtr<fml::TaskRunner> ui_task_runner)\n    : PageView(\n          id,\n          clay::ServiceManager::Create(\n              {ui_task_runner ? ui_task_runner\n                              : fml::MessageLoop::GetCurrent().GetTaskRunner(),\n               ui_task_runner ? ui_task_runner\n                              : fml::MessageLoop::GetCurrent().GetTaskRunner(),\n               nullptr, nullptr}),\n          unref_queue,\n          clay::TaskRunners(\"\", ui_task_runner, nullptr, ui_task_runner,\n                            nullptr)) {}\n\nPageView::PageView(uint32_t id, std::shared_ptr<ServiceManager> service_manager,\n                   fml::RefPtr<GPUUnrefQueue> unref_queue,\n                   clay::TaskRunners task_runners)\n    : BaseView(id, \"page\", std::make_unique<RenderPage>(), this),\n      task_runners_(std::move(task_runners)),\n      gesture_manager_(std::make_unique<GestureManager>(\n          task_runners_.GetUITaskRunner(), service_manager)),\n      nested_scroll_manager_(std::make_unique<NestedScrollManager>(this)),\n#if defined(ENABLE_MOUSE_TRACKING)\n      mouse_region_manager_(std::make_unique<MouseRegionManager>()),\n#endif\n      isolated_gesture_detector_(task_runners_.GetUITaskRunner()),\n      focus_manager_(this),\n      keyboard_bridge_(this),\n      unref_queue_(unref_queue),\n      service_manager_(service_manager),\n      page_unique_id_(render_object()->element_id().unique_id())\n#if !defined(ENABLE_CLAY_LITE)\n      ,\n      overlay_manager_(std::make_unique<OverlayManager>(this))\n#endif\n{\n  SetupIsolatedGestures();\n  frame_builder_ = std::make_unique<FrameBuilder>(\n      skity::Vec2{static_cast<int32_t>(metrics_.physical_width),\n                  static_cast<int32_t>(metrics_.physical_height)},\n      metrics_.device_pixel_ratio, unref_queue_);\n  animation_handler_ = std::make_unique<AnimationHandler>();\n  animation_handler_->SetOnNewAnimationCallback([this] { RequestNewFrame(); });\n  renderer_ = std::make_unique<Renderer>(this, unref_queue_);\n  renderer_->SetRoot(render_object_.get());\n\n  InitManagers();\n\n  layout_controller_ = std::make_unique<LayoutController>();\n\n  attach_to_tree_ = true;\n\n#ifdef ENABLE_ACCESSIBILITY\n  accessibility_element_ = true;\n  semantics_owner_ = std::make_unique<SemanticsOwner>(\n      [weak = GetWeakPtr()](const SemanticsUpdateNodes& update_nodes) {\n        if (weak) {\n          auto page = static_cast<PageView*>(weak.get());\n          page->SendUpdatedSemantics(update_nodes);\n        }\n      });\n#endif\n\n  FML_DLOG(INFO) << \"PageView construction; id = \" << id;\n  FML_DLOG(INFO) << \"Clay build version=\" << clay::GetBuildVersion()\n                 << \" number=\" << clay::GetBuildNumber();\n}\n\nPageView::~PageView() {\n  FML_DLOG(INFO) << \"PageView destruction; id = \" << id_;\n  UnRegisterUploadTask();\n}\n\nvoid PageView::OnDestroy() {\n  FML_DLOG(INFO) << \"Page OnDestroy; id = \" << id_;\n  animation_handler_->ClearCallbacks();\n  DestroyAllChildren();\n  image_resource_fetcher_ = nullptr;\n  exposure_event_arr_.clear();\n  disexposure_event_arr_.clear();\n#if !defined(ENABLE_CLAY_LITE)\n  overlay_manager_ = nullptr;\n#endif\n}\n\nvoid PageView::CleanForRecycle() { ResetPageView(true); }\n\nbool PageView::ShouldIgnoreFocusChange(const PointerEvent& event) {\n  FloatPoint relative_pos;\n  auto target = GetTopViewToAcceptEvent(event.position, &relative_pos);\n  if (target == nullptr) {\n    return false;\n  }\n  return ShouldIgnoreFocus(target);\n}\n\nvoid PageView::InitManagers() {\n  // init focus manager\n  focus_manager_.SetIsRootScope();\n  gesture_manager_->SetListenerForNotCaredPointer(\n      [this](const PointerEvent& event) {\n        if (ShouldIgnoreFocusChange(event)) {\n          return;\n        }\n\n        auto* focus_node = GetFocusManager()->GetLeafFocusedNode();\n        if (focus_node) {\n          focus_node->ClearFocus();\n        }\n      });\n\n#if defined(ENABLE_MOUSE_TRACKING)\n  // init mouse region manager, mouse cursor manager\n  MouseCursorManager::ActiveCursorCallback cursor_callback = nullptr;\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  cursor_callback = [weak_view =\n                         weak_factory_.GetWeakPtr()](const Cursor& cursor) {\n    PageView* page_view = static_cast<PageView*>(weak_view.get());\n\n    if (!page_view) {\n      FML_DCHECK(false) << \"PageView : ActiveCursorCallback is called after \"\n                           \"pageView released\";\n      return;\n    }\n\n    page_view->ActivateSystemCursor(static_cast<int>(cursor.type), cursor.val);\n  };\n#endif\n  mouse_region_manager_->InitSubManager(cursor_callback);\n  SetCursor({\"default\"});\n#endif\n}\n\nvoid PageView::TriggerFirstPaintCallback() {\n  if (!first_paint_ && event_delegate_) {\n    first_paint_ = true;\n    event_delegate_->OnFirstMeaningfulPaint();\n  }\n}\n\nvoid PageView::OnPlatformViewCreated() {}\n\nvoid PageView::OnOutputSurfaceCreated() {\n  if (frame_timing_collector_) {\n    frame_timing_collector_->InsertRecord(clay::Perf::kErrorCode,\n                                          kPerfErrorCodeOK);\n  }\n  Isolate::Instance().GetResourceCache()->SetIsLowMemory(false);\n  // Repaint page when LynxView enter foreground.\n  // Must mark dirty to pass dirty_nodes check in BeginFrame\n  RequestPaintBase();\n}\n\nvoid PageView::OnOutputSurfaceCreateFailed() {\n  FML_LOG(ERROR) << \"OnOutputSurfaceCreateFailed!\";\n  // Insert error code with empty perf record and trigger first perf.\n  if (frame_timing_collector_) {\n    frame_timing_collector_->InsertRecord(clay::Perf::kErrorCode,\n                                          kPerfErrorCodeSurfaceFailed);\n  }\n}\n\nvoid PageView::OnOutputSurfaceDestroyed() {\n  // GpuResourceCache is unused now.\n  // Isolate::Instance().GetResourceCache()->ClearCache();\n  render_object()->MarkSubtreeDirty();\n  frame_builder_->Reset();\n}\n\nvoid PageView::OnFirstMeaningfulLayout() {\n  first_meaningful_layout_ = true;\n  render_delegate_->OnFirstMeaningfulLayout();\n}\n\nbool PageView::BeginFrame(\n    std::unique_ptr<clay::FrameTimingsRecorder> recorder) {\n  if (!render_delegate_) {\n    return false;\n  }\n\n  TRACE_EVENT(\"clay\", \"Clay::BeginFrame\");\n  auto target_time = recorder->GetVsyncTargetTime();\n  // Ignore frame if `physical_size_` is empty. The `physical_size_` is set in\n  // `SetViewportMetrics`.\n  if (physical_size_.IsEmpty() || (width_ <= 0 || height_ <= 0)) {\n    FML_DLOG(WARNING) << \"PageView::BeginFrame has no size\";\n    return false;\n  }\n\n  if (!Visible()) {\n    FML_DLOG(WARNING) << \"PageView::BeginFrame no visible\";\n    return false;\n  }\n\n  // We only do the actual rendering when the first meaningful layout has been\n  // received. Now the scheduler with state machine can already dispatch\n  // `BeginFrame` based on `MeaningfulLayoutState`, but the calling of\n  // `ForceBeginFrame` that outside the state machine need to depend on\n  // `first_meaningful_layout_`.\n  if (!first_meaningful_layout_) {\n    FML_DLOG(WARNING) << \"PageView::BeginFrame no meaningful layout\";\n    return false;\n  }\n\n  TriggerFirstPaintCallback();\n  render_phase_ = RenderPhase::kLayout;\n  if (frame_timing_collector_ &&\n      !frame_timing_collector_->IsRecordingFirstFramePerf()) {\n    layout_and_animation_time_.Start();\n  }\n\n  // TODO(jinsong): Temporarily remove the engine old layer and rebuild it\n  // always.\n  frame_builder_->Reset();\n  {\n    TRACE_EVENT(\"clay\", \"Clay::Layout\");\n    recorder->RecordFrameTime(FrameTimingKey::kLayoutStart);\n    LayoutInternal();\n    LayoutUpdated();\n    recorder->RecordFrameTime(FrameTimingKey::kLayoutEnd);\n  }\n\n  if (animation_handler_->GetAnimationCount() > 0) {\n    recorder->RecordFrameTime(FrameTimingKey::kDoAnimationStart);\n    TRACE_EVENT(\"clay\", \"Clay::Animation\");\n    RequestNewFrame();\n    animation_handler_->DoAnimationFrame(\n        recorder->GetVsyncTargetTime().ToEpochDelta().ToMilliseconds());\n    recorder->RecordFrameTime(FrameTimingKey::kDoAnimationEnd);\n  }\n\n  if (frame_timing_collector_ &&\n      !frame_timing_collector_->IsRecordingFirstFramePerf()) {\n    layout_and_animation_time_.Stop();\n    frame_timing_collector_->InsertLayoutAndAnimationRecord(\n        layout_and_animation_time_);\n  }\n\n  if (render_settings_) {\n    render_settings_->SetHasAnimation(animation_handler_->GetAnimationCount() >\n                                      0);\n  }\n\n  if (intersection_observer_manager_) {\n    intersection_observer_manager_->NotifyObservers();\n  }\n\n  GetFocusManager()->RestoreFocusSwitching();\n\n  if (!force_raster_ && !renderer_->HasDirtyNodes()) {\n    render_phase_ = RenderPhase::kIdle;\n    FlushUIMethodTasks();\n    FlushGapTaskIfNecessary(target_time);\n\n#ifdef ENABLE_ACCESSIBILITY\n    // We still need to update the semantics even though the UI has not changed.\n    FlushSemantics();\n#endif\n\n    return false;\n  }\n  force_raster_ = false;\n\n  render_phase_ = RenderPhase::kPaint;\n  if (view_tree_observer_) {\n    view_tree_observer_->DispatchOnPainting();\n  }\n  {\n    TRACE_EVENT(\"clay\", \"Clay::Paint\");\n    recorder->RecordFrameTime(FrameTimingKey::kPaintStart);\n    Paint();\n    recorder->RecordFrameTime(FrameTimingKey::kPaintEnd);\n  }\n\n  render_phase_ = RenderPhase::kBuildFrame;\n\n  {\n    TRACE_EVENT(\"clay\", \"Clay::Composite\");\n    CompositeFrame(std::move(recorder));\n  }\n\n#ifdef ENABLE_ACCESSIBILITY\n  FlushSemantics();\n#endif\n\n  render_phase_ = RenderPhase::kIdle;\n  FlushUIMethodTasks();\n  FlushGapTaskIfNecessary(target_time);\n  SendGlobalExposureEvent();\n  SendDrawEndEvent();\n  return true;\n}\n\nvoid PageView::LayoutInternal() {\n  if (frame_timing_collector_ &&\n      frame_timing_collector_->IsRecordingFirstFramePerf()) {\n    frame_timing_collector_->BeginRecord(Perf::kFirstLayoutCost);\n  }\n\n  layout_controller_->Layout();\n\n  if (frame_timing_collector_ &&\n      frame_timing_collector_->IsRecordingFirstFramePerf()) {\n    frame_timing_collector_->EndRecord(Perf::kFirstLayoutCost);\n  }\n}\n\nBaseView* PageView::FindViewByViewId(int view_id) {\n  return view_context_->FindViewByViewId(view_id);\n}\n\nvoid PageView::SetRenderDelegate(RenderDelegate* delegate) {\n  render_delegate_ = delegate;\n}\n\nvoid PageView::SendGlobalExposureEvent() {\n  if (event_delegate_) {\n    if (!exposure_event_arr_.empty()) {\n      auto params =\n          CreateExposeArray(exposure_event_arr_, exposure_event_data_set_);\n      event_delegate_->OnSendGlobalEvent(\"exposure\", std::move(params));\n    }\n    if (!disexposure_event_arr_.empty()) {\n      auto params = CreateExposeArray(disexposure_event_arr_,\n                                      disexposure_event_data_set_);\n      event_delegate_->OnSendGlobalEvent(\"disexposure\", std::move(params));\n    }\n  }\n}\n\nvoid PageView::CallJSIntersectionObserver(int observer_id, int callback_id,\n                                          clay::Value params) {\n  if (event_delegate_) {\n    event_delegate_->CallJSIntersectionObserver(observer_id, callback_id,\n                                                std::move(params));\n  }\n}\n\nvoid PageView::SendDrawEndEvent() {\n  if (event_delegate_) {\n    event_delegate_->OnDrawEndEvent();\n  }\n}\n\nvoid PageView::SetRenderSettings(fml::RefPtr<RenderSettings> render_settings) {\n  render_settings_ = render_settings;\n  render_settings_->SetHasAnimation(animation_handler_->GetAnimationCount() >\n                                    0);\n}\n\nvoid PageView::SetViewportMetrics(const clay::ViewportMetrics& metrics) {\n  if (metrics_ == metrics) {\n    return;\n  }\n\n  // NOTE: Window insets are not considered in this place because they are not\n  // used by now.\n  bool needs_to_change_size = NeedsToChangeSize(metrics_, metrics);\n  bool needs_to_change_system_params =\n      NeedsToChangeSystemParameters(metrics_, metrics);\n  metrics_ = metrics;\n  if (!needs_to_change_size && !needs_to_change_system_params) {\n    return;\n  }\n  if (needs_to_change_size) {\n    auto physical_width = metrics_.physical_width;\n    auto physical_height = metrics_.physical_height;\n    auto pixel_ratio = metrics_.device_pixel_ratio;\n    auto dpi = metrics_.device_density_dpi;\n    frame_builder_->UpdateFrameSize(physical_width, physical_height,\n                                    pixel_ratio);\n    physical_size_.SetWidth(physical_width);\n    physical_size_.SetHeight(physical_height);\n    int logical_width = lround(physical_size_.width() / pixel_ratio);\n    int logical_height = lround(physical_size_.height() / pixel_ratio);\n    size_.SetWidth(logical_width);\n    size_.SetHeight(logical_height);\n    renderer_->SetPixelRatio(pixel_ratio);\n    renderer_->SetDPI(dpi);\n    if (gesture_manager_) {\n      gesture_manager_->SetPixelRatio(DevicePixelRatio());\n    }\n    isolated_gesture_detector_.gesture_manager()->SetPixelRatio(\n        DevicePixelRatio());\n    renderer_->SetFrameSize(physical_size_);\n\n    SetWidth(ConvertFrom<kPixelTypePhysical>(physical_width));\n    SetHeight(ConvertFrom<kPixelTypePhysical>(physical_height));\n    static_cast<RenderPage*>(render_object())\n        ->SetScaleRatio(GetPixelRatio<kPixelTypeClay, kPixelTypePhysical>());\n  }\n\n  DispatchViewportMetricsUpdate();\n\n  RequestPaintBase();\n}\n\nvoid PageView::UpdateRootSize(int32_t width, int32_t height) {\n  fml::TaskRunner::RunNowOrPostTask(\n      task_runners_.GetUITaskRunner(),\n      [delegate = render_delegate_, width, height]() {\n        delegate->UpdateRootSize(width, height);\n      });\n}\n\nvoid PageView::Paint() {\n  // Paint the dirty render objects and build the composited layer tree.\n  if (frame_timing_collector_ &&\n      frame_timing_collector_->IsRecordingFirstFramePerf()) {\n    frame_timing_collector_->BeginRecord(Perf::kFirstPaintCost);\n  }\n\n  renderer_->Paint();\n\n  if (frame_timing_collector_ &&\n      frame_timing_collector_->IsRecordingFirstFramePerf()) {\n    frame_timing_collector_->EndRecord(Perf::kFirstPaintCost);\n  }\n  ReportAfterPaint();\n}\n\nvoid PageView::ReportAfterPaint() {\n  if (frame_timing_collector_) {\n    frame_timing_collector_->InsertFocusChangedUntilFirstPaintFinish();\n  }\n}\n\nvoid PageView::CompositeFrame(\n    std::unique_ptr<clay::FrameTimingsRecorder> recorder) {\n  // Build a frame for the composited layer tree.\n  if (frame_timing_collector_ &&\n      frame_timing_collector_->IsRecordingFirstFramePerf()) {\n    frame_timing_collector_->BeginRecord(Perf::kFirstBuildFrameCost);\n  }\n\n  recorder->RecordFrameTime(FrameTimingKey::kBuildFrameStart);\n  frame_builder_->BuildFrame(renderer_->GetLayer());\n  recorder->RecordFrameTime(FrameTimingKey::kBuildFrameEnd);\n  if (performance_overlay_enabled_) {\n    frame_builder_->AddPerformanceOverlay(15, 0, physical_size_.width(), 0,\n                                          physical_size_.height() / 5);\n  }\n\n  if (frame_timing_collector_ &&\n      frame_timing_collector_->IsRecordingFirstFramePerf()) {\n    frame_timing_collector_->EndRecord(Perf::kFirstBuildFrameCost);\n  }\n\n  // Submit a frame to the engine.\n  auto layer_tree = frame_builder_->TakeLayerTree();\n  recorder->RecordVsyncTimeOnGeneration(recorder->GetVsyncStartTime());\n  recorder->RecordVsyncSequenceId(recorder->GetVsyncSequenceId());\n\n  if (layer_tree) {\n    layer_tree->SetVsyncTimeOnGeneration(recorder->GetVsyncStartTime());\n    layer_tree->AppendFrameTimings(recorder->TakeFrameTimings());\n    if (pipeline_timing_delegate_ &&\n        pipeline_timing_delegate_->HasPipelineIds()) {\n      layer_tree->SetPipelineIdList(\n          pipeline_timing_delegate_->GetPipelineIds());\n      std::weak_ptr<PipelineTimingDelegate> weak_delegate =\n          pipeline_timing_delegate_;\n      layer_tree->SetPipelineEndCallback(\n          [weak_delegate](const std::vector<FrameTimingItem>& timings,\n                          const std::vector<std::string>& pipeline_ids) {\n            if (auto delegate = weak_delegate.lock()) {\n              std::vector<std::pair<std::string, uint64_t>> report_timings;\n              report_timings.reserve(timings.size());\n\n              for (const auto& timing_item : timings) {\n                report_timings.emplace_back(\n                    std::string(ToStringView(timing_item.key)),\n                    timing_item.timestamp);\n              }\n              delegate->OnPipelineEnd(std::move(report_timings),\n                                      std::move(pipeline_ids));\n            }\n          });\n    }\n  }\n  if (!render_delegate_->Raster(std::move(layer_tree), std::move(recorder))) {\n    // If the raster fails, we request a new frame to try to fix it and ensure\n    // that the next raster is not skipped.\n    force_raster_ = true;\n    RequestNewFrame();\n  }\n}\n\n#ifdef ENABLE_ACCESSIBILITY\nvoid PageView::FlushSemantics() {\n  FML_DCHECK(semantics_owner_);\n  if (!IsSemanticsEnabled()) {\n    return;\n  }\n  TRACE_EVENT(\"clay\", \"Clay::FlushSemantics\");\n\n  if (semantics_owner_->NeedRebuildSemanticsTree()) {\n    // Prepare the whole semantics tree.\n    semantics_owner_->semantics_nodes_to_update_descendants_.clear();\n    std::vector<fml::RefPtr<SemanticsNode>> new_children;\n    PrepareSemantics(nullptr, new_children, {});\n    semantics_owner_->SetRebuildSemanticsTree(false);\n  } else {\n    // Only update the dirty nodes.\n    std::unordered_set<BaseView*> node_to_process, node_process_descendants;\n    node_process_descendants.swap(\n        semantics_owner_->semantics_nodes_to_update_descendants_);\n\n    for (auto* node : node_process_descendants) {\n      if (node->IsAccessibilityElement()) {\n        node_to_process.insert(node);\n      }\n      node->VisitChildren([&node_to_process](BaseView* child_view) {\n        if (child_view->IsAccessibilityElement()) {\n          node_to_process.insert(child_view);\n        }\n      });\n    }\n    for (auto* node : node_to_process) {\n      if (node->IsAccessibilityElement()) {\n        FML_DCHECK(node->GetSemantics());\n        BaseView* parent_node_view =\n            node->GetSemantics()->Parent()\n                ? static_cast<SemanticsNode*>(node->GetSemantics()->Parent())\n                      ->OwnerView()\n                : nullptr;\n        node->UpdateSemantics({}, parent_node_view, false);\n      }\n    }\n  }\n  semantics_owner_->SendSemanticsUpdate();\n}\n\nvoid PageView::SendUpdatedSemantics(const SemanticsUpdateNodes& update_nodes) {\n  if (render_delegate_) {\n    render_delegate_->UpdateSemantics(update_nodes);\n  }\n}\n\nvoid PageView::PrepareSemantics(\n    fml::RefPtr<SemanticsNode> parent_node,\n    std::vector<fml::RefPtr<SemanticsNode>>& result,\n    const std::vector<std::string>& ancestor_a11y_elements) {\n  FML_DCHECK(semantics_owner_ && semantics_owner_->NeedRebuildSemanticsTree());\n  FML_DCHECK(!parent_node);\n  if (!semantics_) {\n    semantics_ =\n        fml::MakeRefCounted<SemanticsNode>(semantics_owner_.get(), this, 0);\n  }\n  // ResetPageView will let this semantics_ detach.\n  if (!semantics_->Attached()) {\n    semantics_->Attach(semantics_owner_.get());\n  }\n  std::vector<fml::RefPtr<SemanticsNode>> new_children;\n  std::vector<std::string> a11y_elements =\n      accessibility_elements_.value_or(std::vector<std::string>());\n  for (auto* view : children_) {\n    view->PrepareSemantics(semantics_, new_children, a11y_elements);\n  }\n  UpdateSemantics(new_children, nullptr, true, true);\n}\n\nvoid PageView::SetSemanticsEnabled(bool enabled) {\n  FML_DCHECK(semantics_owner_);\n  semantics_owner_->SetSemanticsEnabled(enabled);\n  // Call a new frame and update the semantics node tree.\n  if (enabled) {\n    semantics_owner_->SetRebuildSemanticsTree(true);\n    RequestNewFrameForSemantics();\n  }\n}\n\nbool PageView::IsSemanticsEnabled() const {\n  FML_DCHECK(semantics_owner_);\n  return semantics_owner_->IsSemanticsEnabled();\n}\n\nvoid PageView::RequestNewFrameForSemantics() {\n  FML_DCHECK(semantics_owner_);\n  if (IsSemanticsEnabled()) {\n    RequestNewFrame();\n  }\n}\n\nvoid PageView::SetPageEnableAccessibilityElement(bool enabled) {\n  FML_DCHECK(semantics_owner_);\n  if (semantics_owner_->SetPageEnableAccessibilityElement(enabled)) {\n    RequestNewFrameForSemantics();\n  }\n}\n\nbool PageView::IsPageEnableAccessibilityElement() const {\n  FML_DCHECK(semantics_owner_);\n  return semantics_owner_->IsPageEnableAccessibilityElement();\n}\n\nvoid PageView::HandleA11yTapEvent(BaseView* view) {\n  FloatRect local_bounds = GetBounds();\n  FloatRect global_bounds = BoundsRelativeTo(nullptr);\n  if (event_delegate_) {\n    event_delegate_->OnTouchEvent(\n        \"tap\", view->id(), local_bounds.Center().x(), local_bounds.Center().y(),\n        global_bounds.Center().x(), global_bounds.Center().y());\n  }\n\n  OnA11yTap();\n}\n\nvoid PageView::HandleA11yLongPressEvent(BaseView* view) {\n  FloatRect local_bounds = GetBounds();\n  FloatRect global_bounds = BoundsRelativeTo(nullptr);\n\n  if (event_delegate_) {\n    event_delegate_->OnTouchEvent(\n        \"longpress\", view->id(), local_bounds.Center().x(),\n        local_bounds.Center().y(), global_bounds.Center().x(),\n        global_bounds.Center().y());\n  }\n\n  OnA11yLongPress();\n}\n\nvoid PageView::DispatchSemanticsAction(int virtual_view_id, int action) {\n  SemanticsNode::SemanticsAction semantics_action =\n      static_cast<SemanticsNode::SemanticsAction>(action);\n  BaseView* view = semantics_owner_->GetViewFromId(virtual_view_id);\n  // If PageView just reset, this view may be nullptr;\n  if (!view) {\n    return;\n  }\n  if (!view->IsAccessibilityElement()) {\n    FML_DLOG(ERROR)\n        << \"View: \" << view->GetName()\n        << \" is not accessibility element. But dispatch semantics action: \"\n        << action;\n    return;\n  }\n\n  switch (semantics_action) {\n    case SemanticsNode::SemanticsAction::kTap: {\n      HandleA11yTapEvent(view);\n      break;\n    }\n    case SemanticsNode::SemanticsAction::kLongPress: {\n      HandleA11yLongPressEvent(view);\n      break;\n    }\n    case SemanticsNode::SemanticsAction::kShowOnScreen: {\n      view->ScrollToMiddle(view);\n      break;\n    }\n    default:\n      FML_DLOG(ERROR) << \"Unsupported semantics action: \" << action;\n      break;\n  }\n}\n\nvoid PageView::EnsureSemanticsOwner() {\n  if (!semantics_owner_) {\n    semantics_owner_ = std::make_unique<SemanticsOwner>(\n        [weak = GetWeakPtr()](const SemanticsUpdateNodes& update_nodes) {\n          if (weak) {\n            auto page = static_cast<PageView*>(weak.get());\n            page->SendUpdatedSemantics(update_nodes);\n          }\n        });\n  }\n}\n#endif\n\nbool PageView::DispatchPointerEvent(std::vector<PointerEvent> events) {\n  // The events data is in physical pixels, we need to convert them to clay\n  // pixels.\n  for (PointerEvent& event : events) {\n    event.position = ConvertFrom<kPixelTypePhysical>(event.position);\n    event.delta = ConvertFrom<kPixelTypePhysical>(event.delta);\n    event.pan = ConvertFrom<kPixelTypePhysical>(event.pan);\n    event.pan_delta = ConvertFrom<kPixelTypePhysical>(event.pan_delta);\n    event.scroll_delta_x =\n        ConvertFrom<kPixelTypePhysical>(event.scroll_delta_x);\n    event.scroll_delta_y =\n        ConvertFrom<kPixelTypePhysical>(event.scroll_delta_y);\n  }\n\n  bool consumed = gesture_manager_->HandlePointerEvents(this, events);\n#if defined(ENABLE_MOUSE_TRACKING)\n  mouse_region_manager_->HandleEvents(this, events);\n#endif\n\n  // For Lynx event&gesture report\n  if (consumed) {\n    // if not consumed by clay elements, it should not be consumed by lynx as\n    // well.\n    isolated_gesture_detector_.DispatchPointerEvent(\n        events, gesture_manager_->GetHitTestResponsiveResult());\n    ReportTopViewRawEvents(events);\n  } else {\n#if defined(ENABLE_MOUSE_TRACKING)\n    for (PointerEvent& event : events) {\n      if (event.type == PointerEvent::EventType::kHoverEvent) {\n        ReportTopViewEvent(event);\n      }\n    }\n#endif\n  }\n\n  if (render_settings_) {\n    render_settings_->SetIsTouching(true);\n    if (!event_watch_dog_) {\n      event_watch_dog_ = std::make_unique<WatchDog>(GetTaskRunner());\n    }\n\n    if (event_watch_dog_->IsAlive()) {\n      event_watch_dog_->FeedDog();\n    } else {\n      event_watch_dog_->Start(kEventStateUpdateDelayTime,\n                              kEventStateUpdateIntervalTime,\n                              [render_settings = render_settings_] {\n                                render_settings->SetIsTouching(false);\n                              });\n    }\n  }\n  return consumed;\n}\n\nvoid PageView::DispatchEnterForeground() {\n  std::list<BaseView*> to_visit{this};\n  while (!to_visit.empty()) {\n    BaseView* cur = to_visit.front();\n    to_visit.pop_front();\n    to_visit.insert(to_visit.end(), cur->GetChildren().begin(),\n                    cur->GetChildren().end());\n    cur->OnEnterForeground();\n  }\n}\n\nvoid PageView::DispatchEnterBackground() {\n  std::list<BaseView*> to_visit{this};\n  while (!to_visit.empty()) {\n    BaseView* cur = to_visit.front();\n    to_visit.pop_front();\n    to_visit.insert(to_visit.end(), cur->GetChildren().begin(),\n                    cur->GetChildren().end());\n    cur->OnEnterBackground();\n  }\n}\n\nvoid PageView::ReportTopViewRawEvents(const std::vector<PointerEvent>& events) {\n  for (const auto& event : events) {\n    ReportTopViewEvent(event);\n  }\n}\n\nvoid PageView::SetupIsolatedGestures() {\n  auto tap_recognizer = std::make_unique<TapGestureRecognizer>(\n      isolated_gesture_detector_.gesture_manager());\n  tap_recognizer->SetTaskRunner(GetTaskRunner());\n  tap_recognizer->SetTapUpCallback([this](const PointerEvent& event) {\n    if (event.device == PointerEvent::DeviceType::kTouch) {\n      ReportTopViewEvent(event, kClayEventTypeTap);\n    } else {\n      ReportTopViewEvent(event, kClayEventTypeMouseClick);\n      // simulate primary mouse click as touch tap to adapt front-end code\n      auto tap_event = event;\n      tap_event.device = PointerEvent::DeviceType::kTouch;\n      ReportTopViewEvent(tap_event, kClayEventTypeTap);\n    }\n  });\n  isolated_gesture_detector_.AddRecognizer(std::move(tap_recognizer));\n\n  auto long_press_recognizer = std::make_unique<LongPressGestureRecognizer>(\n      isolated_gesture_detector_.gesture_manager());\n  long_press_recognizer->SetLongPressStartCallback(\n      [this](const PointerEvent& event) {\n        if (event.device == PointerEvent::DeviceType::kTouch) {\n          ReportTopViewEvent(event, kClayEventTypeLongPress);\n        } else {  // mouse\n          ReportTopViewEvent(event, kClayEventTypeMouseLongPress);\n          // simulate primary mouse longpress as touch longpress to adapt\n          // front-end code\n          auto long_press_event = event;\n          long_press_event.device = PointerEvent::DeviceType::kTouch;\n          ReportTopViewEvent(long_press_event, kClayEventTypeLongPress);\n        }\n      });\n  long_press_recognizer->SetTaskRunner(GetTaskRunner());\n  isolated_gesture_detector_.AddRecognizer(std::move(long_press_recognizer));\n}\n\nbool PageView::HitTest(const PointerEvent& event, HitTestResult& result) {\n  PointerEvent converted_event = event;\n  bool is_pass_through_from_overlay = false;\n  bool overlay_result = false;\n#if !defined(ENABLE_CLAY_LITE)\n  overlay_result = overlay_manager_->HitTest(\n      event, result, is_pass_through_from_overlay, converted_event);\n#endif\n  bool base_view_result = false;\n  if (is_pass_through_from_overlay) {\n    base_view_result = BaseView::HitTest(converted_event, result);\n  } else {\n    base_view_result = BaseView::HitTest(event, result);\n  }\n  return overlay_result || base_view_result;\n}\n\nBaseView* PageView::GetTopViewToAcceptEvent(const FloatPoint& position,\n                                            FloatPoint* relative_position) {\n  FloatPoint converted_position = position;\n  bool is_pass_through_from_overlay = false;\n#ifndef ENABLE_CLAY_LITE\n  auto overlay_result = overlay_manager_->GetTopViewToAcceptEvent(\n      position, relative_position, is_pass_through_from_overlay,\n      converted_position);\n  if (overlay_result) {\n    return overlay_result;\n  }\n#endif\n  if (is_pass_through_from_overlay) {\n    return BaseView::GetTopViewToAcceptEvent(converted_position,\n                                             relative_position);\n  } else {\n    return BaseView::GetTopViewToAcceptEvent(position, relative_position);\n  }\n}\n\nvoid PageView::ReportTopViewEvent(const PointerEvent& event,\n                                  ClayEventType type) {\n#ifndef ENABLE_CLAY_LITE\n  overlay_manager_->OnReportTopViewEvent(event, type);\n#endif\n  auto position = event.position;\n  BaseView* top_view = nullptr;\n  FloatPoint transformed_position;\n  if (type == kClayEventTypeTouchMove || type == kClayEventTypeTouchEnd ||\n      type == kClayEventTypeTouchCancel) {\n    auto it = touch_view_map_.find(event.pointer_id);\n    if (it == touch_view_map_.end()) {\n      FML_DLOG(WARNING) << \"The touch down event of pointer_id \"\n                        << event.pointer_id\n                        << \" not existed or associated with a anonymous view.\";\n    }\n    if (it != touch_view_map_.end()) {\n      top_view = this->render_delegate_->FindViewById(it->second);\n      if (type == kClayEventTypeTouchEnd || type == kClayEventTypeTouchCancel) {\n        FML_DCHECK(touch_view_map_.find(event.pointer_id) !=\n                   touch_view_map_.end());\n        touch_view_map_.erase(it);\n      }\n    }\n    if (!top_view || !top_view->attach_to_tree()) {\n      return;\n    }\n    auto view_pos = top_view->AbsoluteLocationWithScroll();\n    transformed_position = position;\n    transformed_position.Move(-view_pos.x(), -view_pos.y());\n  } else {\n    top_view = GetTopViewToAcceptEvent(position, &transformed_position);\n  }\n\n  if (!top_view || top_view->IsAnonymousView()) {\n    return;\n  }\n\n  switch (event.device) {\n    case PointerEvent::DeviceType::kTouch: {\n      if (type == kClayEventTypeTouchStart) {\n        FML_DCHECK(touch_view_map_.find(event.pointer_id) ==\n                   touch_view_map_.end());\n        touch_view_map_[event.pointer_id] = top_view->id();\n        ResignFirstResponderIfNeeded(top_view);\n      }\n\n      bool is_raw_events =\n          type == kClayEventTypeTouchStart || type == kClayEventTypeTouchEnd ||\n          type == kClayEventTypeTouchMove || type == kClayEventTypeTouchCancel;\n      if (is_raw_events && UNLIKELY(top_view->Is<NativeView>())) {\n        // Only dispatch the raw touch events to the NativeView. Generated\n        // events such as tapping or long pressing should not be dispatched to\n        // the NativeViews.\n        static_cast<NativeView*>(top_view)->SendMotionEvent(\n            event, transformed_position);\n      }\n      if (event_delegate_) {\n        event_delegate_->OnTouchEvent(\n            EventTypeToString(type), top_view->id(), transformed_position.x(),\n            transformed_position.y(), position.x(), position.y());\n      }\n    } break;\n    case PointerEvent::DeviceType::kMouse: {\n      bool is_signal_event = type == kClayEventTypeWheel;\n      bool is_raw_events = type == kClayEventTypeMouseDown ||\n                           type == kClayEventTypeMouseUp ||\n                           type == kClayEventTypeMouseMove;\n      if (UNLIKELY(top_view->Is<NativeView>())) {\n        auto native_view = static_cast<NativeView*>(top_view);\n        if (is_raw_events ||\n            (is_signal_event && native_view->IsScrollEnabled())) {\n          native_view->SendMotionEvent(event, transformed_position);\n        }\n      }\n      // update buttons state to detect which button is changed now\n      if (buttons_state_ != event.buttons) {\n        button_state_ = event.buttons ^ buttons_state_;\n        buttons_state_ = event.buttons;\n      }\n      // wheel event\n      if (event_delegate_) {\n        if (is_signal_event) {\n          if (event.signal_kind == PointerEvent::SignalKind::kStartScroll) {\n            wheel_target_ = top_view;\n          }\n          FML_DCHECK(wheel_target_ != nullptr);\n          if (!wheel_target_) {\n            break;\n          }\n          event_delegate_->OnWheelEvent(\n              EventTypeToString(type), wheel_target_->id(),\n              transformed_position.x(), transformed_position.y(), position.x(),\n              position.y(), event.scroll_delta_x, event.scroll_delta_y);\n          if (event.signal_kind == PointerEvent::SignalKind::kEndScroll) {\n            wheel_target_ = nullptr;\n          }\n        } else {\n          event_delegate_->OnMouseEvent(\n              EventTypeToString(type), top_view->id(), button_state_,\n              buttons_state_, 1, transformed_position.x(),\n              transformed_position.y(), position.x(), position.y());\n        }\n      }\n    } break;\n    case PointerEvent::DeviceType::kTrackpad: {\n      if (event.type == PointerEvent::EventType::kPanZoomStartEvent) {\n        pan_zoom_target_ = top_view;\n        return;\n      }\n      if (event.type == PointerEvent::EventType::kPanZoomEndEvent) {\n        pan_zoom_target_ = nullptr;\n        return;\n      }\n      if (event.type != PointerEvent::EventType::kPanZoomUpdateEvent) {\n        FML_DLOG(INFO) << \"omit trackpad event: \"\n                       << static_cast<int>(event.type);\n        return;\n      }\n      bool has_pan_data =\n          event.pan_delta.width() > 0 || event.pan_delta.height() > 0;\n      if (UNLIKELY(top_view->Is<NativeView>())) {\n        auto native_view = static_cast<NativeView*>(top_view);\n        if (native_view->IsScrollEnabled() && has_pan_data) {\n          native_view->SendMotionEvent(event, transformed_position);\n        }\n      }\n      bool is_scale = event.scale != 1;\n      if (event_delegate_ && pan_zoom_target_) {\n        if (is_scale) {\n          event_delegate_->OnMouseEvent(\n              \"zoom\", pan_zoom_target_->id(), button_state_, buttons_state_,\n              event.scale, transformed_position.x(), transformed_position.y(),\n              position.x(), position.y());\n        } else {\n          event_delegate_->OnWheelEvent(\n              \"wheel\", pan_zoom_target_->id(), transformed_position.x(),\n              transformed_position.y(), position.x(), position.y(),\n              event.pan_delta.width(), event.pan_delta.height());\n        }\n      }\n    } break;\n    default:\n      // TODO(Chenfeng Pan): report event from *[Inverted]Stylus*\n      break;\n  }\n}\n\nvoid PageView::ReportTopViewEvent(const PointerEvent& event) {\n  ReportTopViewEvent(event, ToClayEventType(event));\n}\n\nvoid PageView::ReportKeyEvent(const KeyEvent& event) {\n  if (event_delegate_ && (event.GetType() == KeyEventType::kUp ||\n                          event.GetType() == KeyEventType::kDown ||\n                          event.GetType() == KeyEventType::kRepeat)) {\n    int view_id;\n    if (auto* node = focus_manager_.GetLeafFocusedNode()) {\n      view_id = static_cast<BaseView*>(node)->id();\n    } else {\n      view_id = this->id();  // <page>\n    }\n\n    std::string web_key = KeyCodeConverter::ConvertToWebKey(\n        event.GetLogical(), event.GetCharacter());\n    if (web_key.empty() || web_key[0] == '\\0') {\n      return;\n    }\n    auto temp_type = ToClayEventType(event.GetType());\n    event_delegate_->OnKeyEvent(EventTypeToString(temp_type), view_id,\n                                web_key.c_str(),\n                                event.GetType() == KeyEventType::kRepeat);\n  }\n}\n\nvoid PageView::SetRefreshRate(uint32_t refresh_rate) {\n  GetGapWorker()->SetRefreshRate(refresh_rate);\n}\n\nvoid PageView::ReportAnimationEvent(const AnimationParams& animation_params,\n                                    int view_id) {\n  if (event_delegate_) {\n    event_delegate_->OnAnimationEvent(\n        EventTypeToString(animation_params.event_type),\n        animation_params.animation_name, view_id);\n  }\n}\nvoid PageView::ReportTransitionEvent(const AnimationParams& animation_params,\n                                     int view_id,\n                                     ClayAnimationPropertyType property_type) {\n  if (event_delegate_) {\n    event_delegate_->OnTransitionEvent(\n        EventTypeToString(animation_params.event_type),\n        animation_params.animation_name, view_id, property_type);\n  }\n}\nvoid PageView::DispatchAnimationEvent(const AnimationParams& animation_params,\n                                      int view_id) {\n  ReportAnimationEvent(animation_params, view_id);\n}\nvoid PageView::DispatchTransitionEvent(\n    const AnimationParams& animation_params, int view_id,\n    ClayAnimationPropertyType property_type) {\n  ReportTransitionEvent(animation_params, view_id, property_type);\n}\nvoid PageView::RequestPaint() { Invalidate(); }\nvoid PageView::RequestPaintBase() { BaseView::Invalidate(); }\n\nvoid PageView::DispatchKeyEvent(\n    std::unique_ptr<KeyEvent> event,\n    std::function<void(bool /* handled */)> callback) {\n  FML_DCHECK(event);\n  bool keyevent_handled = false;\n#ifndef ENABLE_CLAY_LITE\n  if (overlay_manager_->DispatchKeyEvent(event.get())) {\n    keyevent_handled = true;\n  }\n#endif\n\n  // We need to first report the keyevent then handle the focus change\n  ReportKeyEvent(*event);\n\n  if (!keyevent_handled && focus_manager_.DispatchKeyEvent(event.get())) {\n    keyevent_handled = true;\n  }\n\n#ifndef NDEBUG\n  // In debug mode, we can press the 'D' key to print tree hierarchy (use scrcpy\n  // or `adb shell input text d`)\n  if (event->GetType() == KeyEventType::kDown &&\n      static_cast<int>(event->GetLogical()) == 'd') {\n    DumpRenderingTrees();\n  }\n#endif\n\n  if (intercept_back_key_once_ && IsBackOrEscapeKey(event->GetLogical())) {\n    keyevent_handled = true;\n    if (event->GetType() != KeyEventType::kDown) {\n      intercept_back_key_once_ = false;\n    }\n  }\n  callback(keyevent_handled);\n  keyevent_handled = false;\n}\n\nGrDataPtr PageView::TakeScreenshotHardware(\n    ScreenshotRequest screenshot_request) {\n  if (!render_delegate_) {\n    return nullptr;\n  }\n#ifdef ENABLE_SCREENSHOT_SERVICE\n  screenshot_request.page_width_ = physical_size_.width();\n  screenshot_request.page_height_ = physical_size_.height();\n  clay::Puppet<clay::Owner::kPlatform, clay::ScreenshotService>\n      screenshot_service =\n          service_manager_->GetService<clay::ScreenshotService>();\n  return screenshot_service->TakeScreenshotHardware(screenshot_request);\n#else\n  return nullptr;\n#endif\n}\n\nvoid PageView::AddToKeyboardHostView(BaseView* keyboard_view) {\n  AddChild(keyboard_view);\n}\n\nvoid PageView::RemoveFromKeyboardHostView(BaseView* keyboard_view) {\n  RemoveChild(keyboard_view);\n}\n\nvoid PageView::OnKeyboardEvent(std::unique_ptr<KeyEvent> key_event) {\n  if (!ime_listener_) {\n    return;\n  }\n  if (key_event->GetType() == KeyEventType::kCommitText) {\n    ime_listener_->OnCommitText(key_event->GetCharacter());\n    return;\n  }\n  if (key_event->GetType() == KeyEventType::kCommitComposingText) {\n    ime_listener_->OnComposingText(key_event->GetCharacter());\n    return;\n  }\n\n  ime_listener_->OnKeyboardEvent(std::move(key_event));\n}\n\nvoid PageView::OnPerformAction(KeyboardAction action) {\n  if (ime_listener_) {\n    ime_listener_->OnPerformAction(action);\n  }\n}\n\nvoid PageView::OnFinishInput() {\n  if (ime_listener_) {\n    ime_listener_->OnFinishInput();\n  }\n}\n\nvoid PageView::OnDeleteSurroundingText(int before_length, int after_length) {\n  if (!ime_listener_) {\n    return;\n  }\n  ime_listener_->OnDeleteSurroundingText(before_length, after_length);\n}\n\nvoid PageView::PlatformShowSoftInput(int type, int action) {\n  render_delegate_->ShowSoftInput(type, action);\n}\n\nvoid PageView::PlatformHideSoftInput() { render_delegate_->HideSoftInput(); }\n\nstd::string PageView::ShouldInterceptUrl(const std::string& origin_url,\n                                         bool should_decode) {\n  return render_delegate_->ShouldInterceptUrl(origin_url, should_decode);\n}\n\nstd::shared_ptr<clay::ResourceLoaderIntercept>\nPageView::GetResourceLoaderIntercept() {\n  return render_delegate_->GetResourceLoaderIntercept();\n}\n\nInputClientManager* PageView::GetInputClientManager() {\n  if (!input_client_manager_) {\n    input_client_manager_ = std::make_unique<InputClientManager>();\n  }\n  return input_client_manager_.get();\n}\n\nvoid PageView::RequestInput(IMEListener* ime_listener, KeyboardInputType type,\n                            KeyboardAction action) {\n  ime_listener_ = ime_listener;\n  keyboard_bridge_.RequestSoftKeyboard(type, action, this);\n}\n\nvoid PageView::StopInput(IMEListener* ime_listener) {\n  if (ime_listener_ == ime_listener) {\n    ime_listener_ = nullptr;\n    keyboard_bridge_.HideSoftKeyboard();\n  }\n}\n\nvoid PageView::Invalidate() { RequestNewFrame(); }\n\nvoid PageView::RequestNewFrame() {\n  if (LIKELY(render_delegate_)) {\n    render_delegate_->ScheduleFrame();\n  }\n}\n\n// It is used when decode with priority enabled to notify the scheduler that\n// the image is decoded.\nvoid PageView::RegisterUploadTask(OneShotCallback<>&& task, int image_id) {\n  if (!ImageDecodeWithPriority()) {\n    return;\n  }\n\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n  // Register upload task.\n  auto unique_id = PageUniqueId();\n  ImageUploadManager::GetInstance().AddImageUploadTask(\n      unique_id, std::move(task), image_id);\n\n  // Notify scheduler.\n  clay::Puppet<Owner::kUI, RasterFrameService> raster_frame_service =\n      service_manager_->GetService<RasterFrameService>();\n  raster_frame_service.Act(\n      [](auto& impl) { impl.NotifyUploadTaskRegistered(); });\n}\n\nvoid PageView::UnRegisterUploadTask() {\n  if (!ImageDecodeWithPriority()) {\n    return;\n  }\n\n  auto unique_id = PageUniqueId();\n  ImageUploadManager::GetInstance().RemoveImageUploadTaskByQueueId(unique_id);\n}\n\nvoid PageView::DispatchViewportMetricsUpdate() {\n  std::list<BaseView*> to_visit{this};\n  while (!to_visit.empty()) {\n    BaseView* cur = to_visit.front();\n    to_visit.pop_front();\n    to_visit.insert(to_visit.end(), cur->GetChildren().begin(),\n                    cur->GetChildren().end());\n    cur->OnViewportMetricsUpdated(physical_size_.width(),\n                                  physical_size_.height(),\n                                  metrics_.device_pixel_ratio);\n  }\n  if (event_delegate_) {\n    event_delegate_->OnViewportMetricsChanged(\n        metrics_.device_pixel_ratio, metrics_.device_density_dpi, size_.width(),\n        size_.height(), metrics_.physical_screen_width,\n        metrics_.physical_screen_height, metrics_.font_scale,\n        metrics_.night_mode);\n  }\n}\n\nvoid PageView::SetKeyframesData(const Value& keyframes_value) {\n  if (!keyframes_value.IsMap()) {\n    return;\n  }\n\n  const auto& rules_map = keyframes_value.GetMap();\n  auto parse_filter_values = [](const clay::Value& value) {\n    std::vector<FilterValue> values;\n    if (!value.IsArray()) {\n      return values;\n    }\n    const auto& ary = value.GetArray();\n    for (const auto& item : ary) {\n      if (!item.IsArray()) {\n        continue;\n      }\n      const auto& ary1 = item.GetArray();\n      if (ary1.size() != 2) {\n        continue;\n      }\n      FilterValue v;\n      v.type = static_cast<int>(ary1[0].GetUint());\n      v.value = ary1[1].GetDouble();\n      values.push_back(v);\n    }\n    return values;\n  };\n\n  auto parse_box_shadow_values = [](const clay::Value& value) {\n    std::vector<BoxShadowValue> values;\n    if (!value.IsArray()) {\n      return values;\n    }\n    const auto& shadows = value.GetArray();\n    for (const auto& shadow : shadows) {\n      if (!shadow.IsArray()) {\n        continue;\n      }\n      const auto& shadow_ary = shadow.GetArray();\n      if (shadow_ary.size() < 6) {\n        continue;\n      }\n      BoxShadowValue v;\n      v.h_offset = shadow_ary[0].GetFloat();\n      v.v_offset = shadow_ary[1].GetFloat();\n      v.blur = shadow_ary[2].GetFloat();\n      v.spread = shadow_ary[3].GetFloat();\n      v.option = shadow_ary[4].GetDouble();\n      v.color = shadow_ary[5].GetDouble();\n      values.push_back(v);\n    }\n    return values;\n  };\n\n  auto parse_raw_transform_ops = [](const clay::Value& value) {\n    std::vector<ClayTransformOP> ops;\n    if (!value.IsArray()) {\n      return ops;\n    }\n    const auto& items = value.GetArray();\n    ops.reserve(items.size());\n    for (const auto& item : items) {\n      if (!item.IsArray()) {\n        continue;\n      }\n      const auto& arr = item.GetArray();\n      if (arr.size() != 7u) {\n        continue;\n      }\n      ClayTransformOP op{};\n      op.type = static_cast<ClayTransformType>(\n          arr[TransformRawDataIndex::INDEX_FUNC].GetInt());\n      op.value[0] = static_cast<float>(\n          arr[TransformRawDataIndex::INDEX_TRANSLATE_0].GetDouble());\n      op.value[1] = static_cast<float>(\n          arr[TransformRawDataIndex::INDEX_TRANSLATE_1].GetDouble());\n      op.value[2] = static_cast<float>(\n          arr[TransformRawDataIndex::INDEX_TRANSLATE_2].GetDouble());\n      op.unit[0] = static_cast<ClayPlatformLengthUnit>(\n          arr[TransformRawDataIndex::INDEX_TRANSLATE_0_UNIT].GetInt());\n      op.unit[1] = static_cast<ClayPlatformLengthUnit>(\n          arr[TransformRawDataIndex::INDEX_TRANSLATE_1_UNIT].GetInt());\n      op.unit[2] = static_cast<ClayPlatformLengthUnit>(\n          arr[TransformRawDataIndex::INDEX_TRANSLATE_2_UNIT].GetInt());\n      ops.emplace_back(op);\n    }\n    return ops;\n  };\n\n  for (const auto& rule : rules_map) {\n    const std::string& anim_name = rule.first;\n    const auto& keyframes_val = rule.second;\n    if (!keyframes_val.IsMap()) {\n      continue;\n    }\n\n    KeyframesMap keyframes_map;\n    const auto& keyframes_map_val = keyframes_val.GetMap();\n    for (const auto& kf : keyframes_map_val) {\n      const std::string& fraction_str = kf.first;\n      const auto& properties_val = kf.second;\n      if (!properties_val.IsMap()) {\n        continue;\n      }\n      float fraction = 0.0f;\n      // 与原 KeyframesData 一致地解析百分比键\n      fraction = std::stof(fraction_str);\n\n      const auto& properties_map = properties_val.GetMap();\n      for (const auto& prop : properties_map) {\n        const std::string& prop_name = prop.first;\n        const auto& prop_value = prop.second;\n        auto kw = GetKeywordID(prop_name);\n        switch (kw) {\n          case KeywordID::kOpacity: {\n            auto it = keyframes_map.find(ClayAnimationPropertyType::kOpacity);\n            if (it == keyframes_map.end()) {\n              it = keyframes_map\n                       .try_emplace(ClayAnimationPropertyType::kOpacity,\n                                    FloatKeyframeSet::Create(\n                                        ClayAnimationPropertyType::kOpacity))\n                       .first;\n            }\n            auto* float_set = static_cast<FloatKeyframeSet*>(it->second.get());\n            float_set->AddKeyframe(FloatKeyframe::Create(\n                fraction, static_cast<float>(prop_value.GetDouble()),\n                Interpolator::CreateDefaultInterpolator()));\n            break;\n          }\n          case KeywordID::kBackgroundColor: {\n            auto it =\n                keyframes_map.find(ClayAnimationPropertyType::kBackgroundColor);\n            if (it == keyframes_map.end()) {\n              it = keyframes_map\n                       .try_emplace(\n                           ClayAnimationPropertyType::kBackgroundColor,\n                           ColorKeyframeSet::Create(\n                               ClayAnimationPropertyType::kBackgroundColor))\n                       .first;\n            }\n            auto* color_set = static_cast<ColorKeyframeSet*>(it->second.get());\n            color_set->AddKeyframe(ColorKeyframe::Create(\n                fraction, Color(prop_value.GetUint()),\n                Interpolator::CreateDefaultInterpolator()));\n            break;\n          }\n          case KeywordID::kColor: {\n            auto it = keyframes_map.find(ClayAnimationPropertyType::kColor);\n            if (it == keyframes_map.end()) {\n              it = keyframes_map\n                       .try_emplace(ClayAnimationPropertyType::kColor,\n                                    ColorKeyframeSet::Create(\n                                        ClayAnimationPropertyType::kColor))\n                       .first;\n            }\n            auto* color_set = static_cast<ColorKeyframeSet*>(it->second.get());\n            color_set->AddKeyframe(ColorKeyframe::Create(\n                fraction, Color(prop_value.GetUint()),\n                Interpolator::CreateDefaultInterpolator()));\n            break;\n          }\n          case KeywordID::kTransform: {\n            auto it = keyframes_map.find(ClayAnimationPropertyType::kTransform);\n            if (it == keyframes_map.end()) {\n              it = keyframes_map\n                       .try_emplace(ClayAnimationPropertyType::kTransform,\n                                    RawTransformKeyframeSet::Create())\n                       .first;\n            }\n            auto* transform_set =\n                static_cast<RawTransformKeyframeSet*>(it->second.get());\n            auto ops = parse_raw_transform_ops(prop_value);\n            transform_set->AddKeyframe(RawTransformKeyframe::Create(\n                fraction, ops, Interpolator::CreateDefaultInterpolator()));\n            break;\n          }\n          case KeywordID::kFilter: {\n            auto it = keyframes_map.find(ClayAnimationPropertyType::kFilter);\n            if (it == keyframes_map.end()) {\n              it = keyframes_map\n                       .try_emplace(ClayAnimationPropertyType::kFilter,\n                                    FilterKeyframeSet::Create())\n                       .first;\n            }\n            auto* filter_set =\n                static_cast<FilterKeyframeSet*>(it->second.get());\n            auto values = parse_filter_values(prop_value);\n            filter_set->AddKeyframe(FilterKeyframe::Create(\n                fraction, FilterOperations(values),\n                Interpolator::CreateDefaultInterpolator()));\n            break;\n          }\n          case KeywordID::kBoxShadow: {\n            auto it = keyframes_map.find(ClayAnimationPropertyType::kBoxShadow);\n            if (it == keyframes_map.end()) {\n              it = keyframes_map\n                       .try_emplace(ClayAnimationPropertyType::kBoxShadow,\n                                    BoxShadowKeyframeSet::Create())\n                       .first;\n            }\n            auto* shadow_set =\n                static_cast<BoxShadowKeyframeSet*>(it->second.get());\n            auto values = parse_box_shadow_values(prop_value);\n            shadow_set->AddKeyframe(BoxShadowKeyframe::Create(\n                fraction, BoxShadowOperations(values),\n                Interpolator::CreateDefaultInterpolator()));\n            break;\n          }\n          default: {\n            // 保持原有错误处理\n            FML_DLOG(ERROR)\n                << \"SetKeyframes doesn't support property: \" << prop_name;\n            break;\n          }\n        }\n      }\n    }\n\n    if (!keyframes_map.empty()) {\n      auto ret =\n          keyframes_data_.insert_or_assign(anim_name, std::move(keyframes_map));\n      if (!ret.second) {\n        FML_DLOG(WARNING) << \"SetKeyframesData duplicated name:\" << anim_name;\n      }\n    }\n  }\n\n#if (DEBUG_KEYFRAMES)\n  FML_LOG(ERROR) << \"PageView::SetKeyframesData size=\"\n                 << keyframes_data_.size();\n  for (const auto& node : keyframes_data_) {\n    FML_LOG(ERROR) << \"PageView::SetKeyframesData \\t name=\" << node.first\n                   << \" property size=\" << node.second.size();\n    for (const auto& keyframe_set : node.second) {\n      FML_LOG(ERROR) << \"PageView::SetKeyframesData \\t\\t keyframe_set=\"\n                     << keyframe_set.second->ToString();\n    }\n  }\n#endif\n}\n\nconst KeyframesMap* PageView::GetKeyframesMap(\n    const std::string& animation_name) {\n  auto iter = keyframes_data_.find(animation_name);\n  if (iter == keyframes_data_.end()) {\n    return nullptr;\n  }\n  return &(iter->second);\n}\n\n#ifndef ENABLE_SKITY\nfml::RefPtr<ImageResourceFetcher> PageView::GetImageResourceFetcher() {\n  if (!image_resource_fetcher_) {\n    FML_DCHECK(GetTaskRunner()->RunsTasksOnCurrentThread());\n    image_resource_fetcher_ = fml::MakeRefCounted<ImageResourceFetcher>(\n        GetResourceLoaderIntercept(), GetTaskRunners(), unref_queue_,\n        GetServiceManager());\n  }\n  return image_resource_fetcher_;\n}\n\nvoid PageView::SetImageResourceFetcher(\n    fml::RefPtr<ImageResourceFetcher> image_resource_fetcher) {\n  image_resource_fetcher_ = image_resource_fetcher;\n}\n#else\nfml::RefPtr<ImageFetcher> PageView::GetImageResourceFetcher() {\n  if (!image_resource_fetcher_) {\n    FML_DCHECK(GetTaskRunner()->RunsTasksOnCurrentThread());\n    image_resource_fetcher_ =\n        ImageFetcher::Create(GetResourceLoaderIntercept(), GetTaskRunners(),\n                             unref_queue_, GetServiceManager());\n  }\n  return image_resource_fetcher_;\n}\n\nvoid PageView::SetImageResourceFetcher(\n    fml::RefPtr<ImageFetcher> image_resource_fetcher) {\n  image_resource_fetcher_ = image_resource_fetcher;\n}\n#endif  // ENABLE_SKITY\n\nViewTreeObserver* PageView::GetViewTreeObserver() {\n  if (!view_tree_observer_) {\n    view_tree_observer_ = std::make_unique<ViewTreeObserver>();\n  }\n  return view_tree_observer_.get();\n}\n\nbool PageView::HasIntersectionObserverManager() {\n  return intersection_observer_manager_ ? true : false;\n}\n\nvoid PageView::NotifyLowMemory() {\n  Isolate::Instance().GetResourceCache()->ClearCache();\n  ImageDataCache::GetInstance().ClearCache();\n#ifndef ENABLE_SKITY\n  if (image_resource_fetcher_) {\n    image_resource_fetcher_->ClearCache();\n  }\n#endif  // ENABLE_SKITY\n  render_delegate_->ClearTextCache();\n}\n\nvoid PageView::PostUIMethodTask(std::function<void()> task) {\n  ui_method_tasks_.emplace_back(std::move(task));\n  RequestPaint();\n}\n\nvoid PageView::FlushUIMethodTasks() {\n  auto tasks = std::move(ui_method_tasks_);\n  for (const auto& task : tasks) {\n    task();\n  }\n}\n\nvoid PageView::ResetPageView(bool recycle) {\n  DestroyAllChildren();\n  padding_left_ = 0.f;\n  padding_top_ = 0.f;\n  padding_right_ = 0.f;\n  padding_bottom_ = 0.f;\n  keyframes_mgr_.reset();\n  transition_mgr_.reset();\n#ifdef ENABLE_ACCESSIBILITY\n  semantics_owner_->Reset();\n#endif\n  render_object_ = std::make_unique<RenderPage>();\n  render_object_->SetWidth(width_);\n  render_object_->SetHeight(height_);\n  render_object_->SetTop(top_);\n  render_object_->SetLeft(left_);\n  static_cast<RenderPage*>(render_object_.get())\n      ->SetScaleRatio(GetPixelRatio<kPixelTypeClay, kPixelTypePhysical>());\n  renderer_ = std::make_unique<Renderer>(this, unref_queue_);\n  layout_controller_ = std::make_unique<LayoutController>();\n  renderer_->SetRoot(render_object_.get());\n  renderer_->SetPixelRatio(metrics_.device_pixel_ratio);\n  renderer_->SetDPI(metrics_.device_density_dpi);\n  renderer_->SetFrameSize({static_cast<int32_t>(metrics_.physical_width),\n                           static_cast<int32_t>(metrics_.physical_height)});\n  animation_handler_->ClearCallbacks();\n  animation_handler_->SetOnNewAnimationCallback([this] { RequestNewFrame(); });\n  frame_builder_ = std::make_unique<FrameBuilder>(\n      skity::Vec2{static_cast<int32_t>(metrics_.physical_width),\n                  static_cast<int32_t>(metrics_.physical_height)},\n      metrics_.device_pixel_ratio, unref_queue_);\n  focus_manager_ = FocusManager(this);\n  focus_manager_.SetIsRootScope();\n  view_tree_observer_.reset();\n  UnRegisterUploadTask();\n\n  if (recycle) {\n    first_paint_ = false;\n    first_meaningful_layout_ = false;\n    frame_timing_collector_.reset();\n    image_resource_fetcher_ = nullptr;\n  }\n\n  exposure_event_arr_.clear();\n  disexposure_event_arr_.clear();\n  RequestPaintBase();\n}\n\nvoid PageView::AddGlobalExposureEvent(bool exposure,\n                                      std::unique_ptr<clay::Value::Map> params,\n                                      BaseView* view) {\n  if (exposure) {\n    // should keep following vector size equal\n    exposure_event_arr_.emplace_back(std::move(params));\n    exposure_event_data_set_.push_back(view->GetWeakPtr());\n  } else {\n    // should keep following vector size equal\n    disexposure_event_arr_.emplace_back(std::move(params));\n    disexposure_event_data_set_.push_back(view->GetWeakPtr());\n  }\n}\n\nvoid PageView::MoveWindow() {\n#if (defined(OS_MAC) || defined(OS_WIN))\n  WindowMove();\n#endif\n}\n\nvoid PageView::MakeRasterSnapshot(\n    BaseView* target, bool compress_jpeg, float scale,\n    std::function<void(GrDataPtr, int32_t, int32_t)> callback) {\n  if (!render_delegate_) {\n    callback(nullptr, 0, 0);\n    return;\n  }\n  if (target->render_object()->NeedsPaint() ||\n      target->render_object()->NeedsEffect()) {\n    // Needs to repaint first if the RenderObject has been marked dirty.\n    render_delegate_->ForceBeginFrame();\n  }\n  FML_DCHECK(!(target->render_object()->NeedsPaint() ||\n               target->render_object()->NeedsEffect()));\n  FML_DCHECK(target->render_object()->IsRepaintBoundary());\n  auto* layer =\n      static_cast<PendingOffsetLayer*>(target->render_object()->GetLayer());\n  if (!layer) {\n    callback(nullptr, 0, 0);\n    return;\n  }\n  auto offset = layer->Offset();\n  // Reset the offset of the target view to (0, 0) to take the snapshot, and\n  // will be restored after.\n  layer->SetOffset(FloatPoint(0, 0));\n  // Get the frame size with physical pixel.\n  int32_t width = static_cast<int32_t>(\n      ConvertTo<kPixelTypePhysical>(target->Width()) * scale);\n  int32_t height = static_cast<int32_t>(\n      ConvertTo<kPixelTypePhysical>(target->Height()) * scale);\n  // Create a temporary FrameBuilder to generate the layer tree.\n  std::unique_ptr<FrameBuilder> frame_builder = std::make_unique<FrameBuilder>(\n      skity::Vec2{width, height}, metrics_.device_pixel_ratio, unref_queue_);\n  // Apply the scale ratio using a transform layer to get the final image.\n  float scale_ratio =\n      GetPixelRatio<kPixelTypeClay, kPixelTypePhysical>() * scale;\n  skity::Matrix matrix;\n  matrix.Reset();\n  matrix.SetScaleX(scale_ratio);\n  matrix.SetScaleY(scale_ratio);\n  auto transform_layer = std::make_shared<PendingTransformLayer>(matrix);\n  frame_builder->PushStaticTransform(matrix, transform_layer.get());\n  frame_builder->BuildSubtreeFrame(layer);\n  // Restore the offset of the target view.\n  layer->SetOffset(offset);\n  // Take the subtree of the target view.\n  auto layer_tree = frame_builder->TakeLayerTree();\n  if (!layer_tree) {\n    callback(nullptr, 0, 0);\n    return;\n  }\n  // Make raster snapshot with the subtree, resulting in a CPU-backed DlImage.\n  render_delegate_->MakeRasterSnapshot(\n      std::move(layer_tree),\n      [compress_jpeg, callback](fml::RefPtr<PaintImage> paint_image) {\n        // The callback should run on IO thread.\n        auto image = paint_image ? paint_image->gr_image() : nullptr;\n        if (!image) {\n          callback(nullptr, 0, 0);\n          return;\n        }\n    // The image must be CPU-backed.\n#ifndef ENABLE_SKITY\n        FML_DCHECK(!image->isTextureBacked());\n        GrDataPtr gr_data = nullptr;\n        // The scale factor has already been applied to the image with transform\n        // layer, so we don't need to scale the image here again. Just compress\n        // the image to png or jpeg format.\n        if (compress_jpeg) {\n          SkJpegEncoder::Options options;\n          gr_data = SkJpegEncoder::Encode(nullptr, image.get(), options);\n        } else {\n          SkPngEncoder::Options options;\n          gr_data = SkPngEncoder::Encode(nullptr, image.get(), options);\n        }\n        callback(gr_data, image->width(), image->height());\n#else\n        FML_DCHECK(!image->IsTextureBackend());\n        GrDataPtr gr_data = nullptr;\n        // The scale factor has already been applied to the image with transform\n        // layer, so we don't need to scale the image here again. Just compress\n        // the image to png or jpeg format.\n        auto codec = compress_jpeg ? skity::Codec::MakeJPEGCodec()\n                                   : skity::Codec::MakePngCodec();\n        if (!codec) {\n          callback(nullptr, 0, 0);\n        } else {\n          gr_data = codec->Encode(image->GetPixmap()->get());\n          callback(gr_data, image->Width(), image->Height());\n        }\n#endif  // ENABLE_SKITY\n      });\n}\n\nfml::RefPtr<PaintImage> PageView::MakeRasterSnapshot(GrPicturePtr picture,\n                                                     skity::Vec2 picture_size) {\n  return render_delegate_->MakeRasterSnapshot(std::move(picture), picture_size);\n}\n\nint PageView::GetViewIdForLocation(int x, int y) {\n  FloatPoint unused;\n  BaseView* top_view = GetTopViewToAcceptEvent(FloatPoint(x, y), &unused);\n  if (top_view) {\n    return top_view->id();\n  }\n  return -1;\n}\n\n#ifndef NDEBUG\nstd::string PageView::ToString() const {\n  std::stringstream ss;\n  ss << BaseView::ToString();\n  ss << \" physical_size=(\" << physical_size().width() << \",\"\n     << physical_size().height() << \")\";\n  ss << \" logical_size=(\" << logical_size().width() << \",\"\n     << logical_size().height() << \")\";\n  ss << \" DevicePixelRatio=\" << DevicePixelRatio();\n  return ss.str();\n}\n\nvoid PageView::DumpRenderingTrees() const {\n  FML_LOG(ERROR) << \">>>>>>>>>> DumpViewTree\";\n  DumpViewTree(0);\n  FML_LOG(ERROR) << \">>>>>>>>>> DumpRenderTree\";\n  render_object()->DumpRenderTree();\n  FML_LOG(ERROR) << \">>>>>>>>>> DumpLayerTree\";\n  render_object()->GetLayer()->DumpPendingLayerTree();\n  FML_LOG(ERROR) << \">>>>>>>>>> DumpEngineLayerTree\";\n  frame_builder_->DumpLayerTree();\n}\n#endif\n\nvoid PageView::FlushGapTaskIfNecessary(const fml::TimePoint& target_end_time) {\n  if (GetGapWorker()->HasGapTask() && fml::TimePoint::Now() < target_end_time) {\n    // We still have time, try flush gap work at ui thread.\n    task_runners_.GetUITaskRunner()->PostTask(\n        [weak = GetWeakPtr(), target_end_time]() {\n          if (weak) {\n            auto page = static_cast<PageView*>(weak.get());\n            page->GetGapWorker()->FlushTask(target_end_time);\n            if (page->GetGapWorker()->HasGapTask()) {\n              // Check if there are still tasks that need to be executed at\n              // subsequent frame intervals. Because sometimes the rendering\n              // state may remain static or nothing new needs to be rendered, it\n              // is also possible to run some tasks in idle time.\n              page->RequestNewFrame();\n            }\n          }\n        });\n  }\n}\n\nGapWorker* PageView::GetGapWorker() {\n  if (!gap_worker_) {\n    gap_worker_ = std::make_unique<GapWorker>(task_runners_.GetUITaskRunner());\n  }\n  return gap_worker_.get();\n}\n\nvoid PageView::ReportTiming(\n    const std::unordered_map<std::string, int64_t>& timing,\n    const std::string& flag) {\n  if (render_delegate_) {\n    render_delegate_->ReportTiming(timing, flag);\n  }\n}\n\nvoid PageView::RegisterFirstFrameAvailable(int64_t image_id,\n                                           const fml::closure& callback) {\n  first_frame_callbacks_[image_id] = callback;\n}\n\nvoid PageView::UnRegisterFirstFrameAvailable(int64_t image_id) {\n  first_frame_callbacks_.erase(image_id);\n}\n\nbool PageView::MarkDrawableImageFrameAvailable(int64_t image_id) {\n  auto it = first_frame_callbacks_.find(image_id);\n  if (it == first_frame_callbacks_.end()) {\n    return false;\n  }\n  // The first frame has been available, call callback to do something.\n  // Finally, a new frame must be requested.\n  it->second();\n  return true;\n}\n\nvoid PageView::SetEditingPlatformView(NativeView* view) {\n  editing_native_view_ = view;\n}\n\nvoid PageView::OnPlatformUpdateEditState(int client_id, uint64_t selection_base,\n                                         uint64_t composing_extent,\n                                         const char* selection_affinity,\n                                         const char* text,\n                                         uint64_t selection_extent,\n                                         uint64_t composing_base) {\n  input_client_manager_->InvokeUpdateEditState(\n      client_id, selection_base, composing_extent, selection_affinity, text,\n      selection_extent, composing_base);\n}\n\nvoid PageView::OnPlatformPerformInputAction(int client_id) {\n  input_client_manager_->InvokePerformAction(client_id);\n}\n\nvoid PageView::SetScrollFluencyMonitorDelegate(\n    std::shared_ptr<ScrollFluencyMonitorDelegate> delegate) {\n  clay::Puppet<clay::Owner::kPlatform, InstrumentationService> instrumentation =\n      service_manager_->GetService<InstrumentationService>();\n\n  if (scroll_fluency_monitor_delegate_) {\n    auto old_listener = scroll_fluency_monitor_delegate_;\n    instrumentation.Act([old_listener](auto& impl) {\n      impl.RemoveFrameTimingListener(old_listener);\n    });\n  }\n\n  scroll_fluency_monitor_delegate_ = std::move(delegate);\n  if (!scroll_fluency_monitor_delegate_) {\n    return;\n  }\n\n  auto new_listener = scroll_fluency_monitor_delegate_;\n  instrumentation.Act([new_listener](auto& impl) {\n    impl.AddFrameTimingListener(new_listener);\n  });\n}\n\nvoid PageView::StartFluencyMonitor(uintptr_t id, const std::string& scene,\n                                   const std::string& scroll_monitor_tag) {\n  if (scroll_fluency_monitor_delegate_) {\n    clay::Puppet<clay::Owner::kUI, VsyncWaiterService> vsync_waiter_service =\n        service_manager_->GetService<VsyncWaiterService>();\n    scroll_fluency_monitor_delegate_->StartFluencyMonitor(\n        id, scene, scroll_monitor_tag, vsync_waiter_service->GetRefreshRate());\n  }\n}\n\nvoid PageView::EndFluencyMonitor(uintptr_t id) {\n  if (scroll_fluency_monitor_delegate_) {\n    scroll_fluency_monitor_delegate_->EndFluencyMonitor(id);\n  }\n}\n\nvoid PageView::ResignFirstResponderIfNeeded(BaseView* current_responder) {\n  if (editing_native_view_ && editing_native_view_ != current_responder) {\n    editing_native_view_->ResignFirstResponder();\n    editing_native_view_ = nullptr;\n  }\n}\n\nvoid PageView::RegisterDrawableImage(\n    std::shared_ptr<DrawableImage> drawable_image) {\n  render_delegate_->RegisterDrawableImage(drawable_image);\n}\n\nvoid PageView::UnregisterDrawableImage(int64_t id) {\n  render_delegate_->UnregisterDrawableImage(id);\n}\n\nvoid PageView::SetExternalScreenshotCallback(\n    ExternalScreenshotCallback callback) {\n#ifdef ENABLE_SCREENSHOT_SERVICE\n  clay::Puppet<clay::Owner::kPlatform, clay::ScreenshotService>\n      screenshot_service =\n          service_manager_->GetService<clay::ScreenshotService>();\n  screenshot_service->SetExternalScreenshotCallback(std::move(callback));\n#endif\n}\n\nClayEventType ToClayEventType(PointerEvent::EventType event_type,\n                              PointerEvent::DeviceType device) {\n  if (device == PointerEvent::DeviceType::kTouch) {\n    switch (event_type) {\n      case PointerEvent::EventType::kSignalEvent:\n        return kClayEventTypeWheel;\n      case PointerEvent::EventType::kUnkownEvent:\n        return kClayEventTypeUnknown;\n      case PointerEvent::EventType::kDownEvent:\n        return kClayEventTypeTouchStart;\n      case PointerEvent::EventType::kUpEvent:\n        return kClayEventTypeTouchEnd;\n      case PointerEvent::EventType::kMoveEvent:\n        return kClayEventTypeTouchMove;\n      case PointerEvent::EventType::kCancel:\n        return kClayEventTypeTouchCancel;\n      case PointerEvent::EventType::kHoverEvent:\n        // TODO(yangliu): report hover event to lynx\n        return kClayEventTypeUnknown;\n      case PointerEvent::EventType::kPanZoomStartEvent:\n        return kClayEventTypeUnknown;\n      case PointerEvent::EventType::kPanZoomUpdateEvent:\n        return kClayEventTypeWheel;\n      case PointerEvent::EventType::kPanZoomEndEvent:\n        return kClayEventTypeUnknown;\n    }\n  } else if (device == PointerEvent::DeviceType::kTrackpad) {\n    switch (event_type) {\n      case PointerEvent::EventType::kPanZoomStartEvent:\n      case PointerEvent::EventType::kPanZoomUpdateEvent:\n      case PointerEvent::EventType::kPanZoomEndEvent:\n        return kClayEventTypePanZoom;\n      default:\n        return kClayEventTypeUnknown;\n    }\n  } else {  // mouse\n    switch (event_type) {\n      case PointerEvent::EventType::kSignalEvent:\n        return kClayEventTypeWheel;\n      case PointerEvent::EventType::kUnkownEvent:\n        return kClayEventTypeUnknown;\n      case PointerEvent::EventType::kDownEvent:\n        return kClayEventTypeMouseDown;\n      case PointerEvent::EventType::kUpEvent:\n        return kClayEventTypeMouseUp;\n      case PointerEvent::EventType::kMoveEvent:\n        return kClayEventTypeMouseMove;\n      case PointerEvent::EventType::kCancel:\n        return kClayEventTypeUnknown;\n      case PointerEvent::EventType::kHoverEvent:\n        // TODO(yangliu): report hover event to lynx\n        return kClayEventTypeUnknown;\n      default:\n        return kClayEventTypeUnknown;\n    }\n  }\n  return kClayEventTypeUnknown;\n}\n\nClayEventType ToClayEventType(const PointerEvent& event) {\n  return ToClayEventType(event.type, event.device);\n}\n\nClayEventType ToClayEventType(KeyEventType type) {\n  if (type == KeyEventType::kDown || type == KeyEventType::kRepeat) {\n    return kClayEventTypeKeyDown;\n  } else if (type == KeyEventType::kUp) {\n    return kClayEventTypeKeyUp;\n  }\n  return kClayEventTypeUnknown;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/page_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_PAGE_VIEW_H_\n#define CLAY_UI_COMPONENT_PAGE_VIEW_H_\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"build/build_config.h\"\n#include \"clay/common/graphics/screenshot.h\"\n#include \"clay/common/recyclable.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/gfx/geometry/size.h\"\n#include \"clay/gfx/pixel_helper.h\"\n#include \"clay/public/event_delegate.h\"\n#include \"clay/public/ui_component_delegate.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/common/frame_timing_collector.h\"\n#include \"clay/ui/common/gap_worker.h\"\n#include \"clay/ui/common/input_client_manager.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/common/render_settings.h\"\n#include \"clay/ui/common/value_utils.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/editable/ime_listener.h\"\n#include \"clay/ui/component/intersection_observer_manager.h\"\n#include \"clay/ui/component/isolated_gesture_detector.h\"\n#include \"clay/ui/component/layout_controller.h\"\n#include \"clay/ui/component/nested_scroll/nested_scroll_manager.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/component/view_tree_observer.h\"\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/event/focus_manager.h\"\n#include \"clay/ui/event/key_event.h\"\n#include \"clay/ui/gesture/gesture_manager.h\"\n#include \"clay/ui/gesture/mouse_region_manager.h\"\n#include \"clay/ui/platform/keyboard_bridge.h\"\n#include \"clay/ui/render_delegate.h\"\n#include \"clay/ui/rendering/renderer.h\"\n#include \"clay/ui/resource/image_resource_fetcher.h\"\n#include \"clay/ui/window/viewport_metrics.h\"\n#ifdef ENABLE_ACCESSIBILITY\n#include \"clay/ui/semantics/semantics_owner.h\"\n#endif\n#ifdef ENABLE_SKITY\n#include \"clay/ui/resource/image_fetcher.h\"\n#endif\n\nnamespace clay {\n\nclass WatchDog;\nclass GapWorker;\ntypedef std::vector<std::unique_ptr<clay::Value::Map>> ExposureDataList;\n\nclass ServiceManager;\nclass ViewContext;\nclass NativeView;\nclass OverlayManager;\nclass ScrollFluencyMonitorDelegate;\nclass PipelineTimingDelegate;\n\nClayEventType ToClayEventType(PointerEvent::EventType event_type,\n                              PointerEvent::DeviceType device);\n\nClayEventType ToClayEventType(const PointerEvent& event);\n\nClayEventType ToClayEventType(KeyEventType type);\n\nclass PageView : public BaseView,\n                 public RendererClient,\n                 public KeyboardClient,\n                 public Recyclable,\n                 public PixelHelper<kPixelTypeClay> {\n public:\n  // Only for unittests compatiblity.\n  PageView(uint32_t id, fml::RefPtr<GPUUnrefQueue> unref_queue,\n           fml::RefPtr<fml::TaskRunner> ui_task_runner);\n\n  PageView(uint32_t id, std::shared_ptr<ServiceManager> service_manager,\n           fml::RefPtr<GPUUnrefQueue> unref_queue,\n           clay::TaskRunners task_runners);\n  ~PageView() override;\n\n  bool IsLayoutRootCandidate() const override { return true; }\n\n  void SetEventDelegate(EventDelegate* delegate) { event_delegate_ = delegate; }\n  EventDelegate* GetEventDelegate() { return event_delegate_; }\n  UIComponentDelegate* GetUIComponentDelegate() {\n    return ui_component_delegate_;\n  }\n\n  void SetPipelineTimingDelegate(\n      std::shared_ptr<PipelineTimingDelegate> delegate) {\n    pipeline_timing_delegate_ = delegate;\n  }\n\n  void SetScrollFluencyMonitorDelegate(\n      std::shared_ptr<ScrollFluencyMonitorDelegate> delegate);\n\n  BaseView* FindViewByViewId(int view_id);\n\n  BaseView* FindViewByIdSelector(std::string_view id_selector) {\n    if (view_context_ == nullptr) {\n      return nullptr;\n    }\n    return view_context_->FindViewByIdSelector(id_selector, this);\n  }\n  void SetRenderDelegate(RenderDelegate* delegate);\n\n  // BaseView\n  FocusManager* GetFocusManager() override { return &focus_manager_; }\n  AnimationHandler* GetAnimationHandler() { return animation_handler_.get(); }\n  void DispatchEnterForeground();\n  void DispatchEnterBackground();\n  void FlushGapTaskIfNecessary(const fml::TimePoint& target_end_time);\n\n  CustomFilterDecoder* GetCustomFilterDecoder() {\n    return view_context_ ? view_context_->GetCustomFilterDecoder() : nullptr;\n  }\n\n  void RequestPaint();\n  void RequestPaintBase();\n  bool BeginFrame(std::unique_ptr<clay::FrameTimingsRecorder>);\n  void SetRefreshRate(uint32_t refresh_rate);\n  void SetRenderSettings(fml::RefPtr<RenderSettings> render_settings);\n  fml::RefPtr<RenderSettings> GetRenderSettings() const {\n    return render_settings_;\n  }\n  void SetViewportMetrics(const clay::ViewportMetrics& metrics);\n  const clay::ViewportMetrics& GetViewportMetrics() const { return metrics_; }\n  // The real pixel ratio.\n  float DevicePixelRatio() const override {\n    return metrics_.device_pixel_ratio;\n  }\n  float DeviceDpi() const { return metrics_.device_density_dpi; }\n\n  void UpdateRootSize(int32_t width, int32_t height);\n\n  bool DispatchPointerEvent(std::vector<PointerEvent> events);\n  virtual void DispatchKeyEvent(\n      std::unique_ptr<KeyEvent> event,\n      std::function<void(bool /* handled */)> callback);\n  void DispatchAnimationEvent(const AnimationParams& animation_params,\n                              int view_id);\n  void DispatchTransitionEvent(const AnimationParams& animation_params,\n                               int view_id,\n                               ClayAnimationPropertyType property_type);\n  void CallJSIntersectionObserver(int observer_id, int callback_id,\n                                  clay::Value params);\n  void ReportAfterPaint();\n\n  GrDataPtr TakeScreenshotHardware(\n      ScreenshotRequest screenshot_request = ScreenshotRequest());\n\n  Size physical_size() const { return physical_size_; }\n  Size logical_size() const { return size_; }\n\n  GestureManager* gesture_manager() { return gesture_manager_.get(); }\n  NestedScrollManager* nested_scroll_manager() {\n    return nested_scroll_manager_.get();\n  }\n  MouseRegionManager* mouse_region_manager() {\n    return mouse_region_manager_.get();\n  }\n\n  ViewTreeObserver* GetViewTreeObserver();\n\n  bool HasIntersectionObserverManager();\n  IntersectionObserverManager* intersection_observer_manager() {\n    if (!intersection_observer_manager_) {\n      intersection_observer_manager_ =\n          std::make_unique<IntersectionObserverManager>(this);\n    }\n    return intersection_observer_manager_.get();\n  }\n\n  const clay::TaskRunners& GetTaskRunners() const { return task_runners_; }\n\n  fml::RefPtr<GPUUnrefQueue> GetUnrefQueue() const { return unref_queue_; }\n\n  // KeyboardClient\n  fml::RefPtr<fml::TaskRunner> GetTaskRunner() override {\n    return task_runners_.GetUITaskRunner();\n  }\n\n  FloatSize KeyboardHostViewSize() override {\n    return FloatSize(Width(), Height());\n  }\n  void AddToKeyboardHostView(BaseView* keyboard_view) override;\n  void RemoveFromKeyboardHostView(BaseView* keyboard_view) override;\n  void OnKeyboardEvent(std::unique_ptr<KeyEvent> key_event) override;\n  void OnDeleteSurroundingText(int before_length, int after_length) override;\n  void OnPerformAction(KeyboardAction action) override;\n  void OnFinishInput() override;\n\n  void RequestInput(IMEListener* ime_listener, KeyboardInputType type,\n                    KeyboardAction action);\n  void StopInput(IMEListener* ime_listener);\n\n  void TriggerFirstPaintCallback();\n\n  BaseView* GetTouchableViewOnTop(const FloatPoint& point);\n  void SetDefaultFocusRingEnabled(bool enable) {\n    default_focus_ring_enable_ = enable;\n  }\n  bool DefaultFocusRingEnabled() const { return default_focus_ring_enable_; }\n  void SetPerformanceOverlayEnabled(bool enable) {\n    performance_overlay_enabled_ = enable;\n  }\n\n  void SetKeyframesData(const Value& keyframes_value);\n  const KeyframesMap* GetKeyframesMap(const std::string& animation_name);\n\n  LayoutController* GetLayoutController() const {\n    return layout_controller_.get();\n  }\n\n#ifndef ENABLE_SKITY\n  fml::RefPtr<ImageResourceFetcher> GetImageResourceFetcher();\n  void SetImageResourceFetcher(\n      fml::RefPtr<ImageResourceFetcher> image_resource_fetcher);\n#else\n  fml::RefPtr<ImageFetcher> GetImageResourceFetcher();\n  void SetImageResourceFetcher(\n      fml::RefPtr<ImageFetcher> image_resource_fetcher);\n#endif\n\n  virtual void OnPlatformViewCreated();\n\n  void OnOutputSurfaceCreated();\n  void OnOutputSurfaceCreateFailed();\n  void OnOutputSurfaceDestroyed();\n  void OnFirstMeaningfulLayout();\n\n  void SetFrameTimingCollector(\n      std::shared_ptr<FrameTimingCollector> frame_timing_collector) {\n    frame_timing_collector_ = frame_timing_collector;\n  }\n  FrameTimingCollector* GetFrameTimingCollector() const {\n    return frame_timing_collector_.get();\n  }\n  void ReportTiming(const std::unordered_map<std::string, int64_t>& timing,\n                    const std::string& flag);\n\n  std::string ShouldInterceptUrl(const std::string& origin_url,\n                                 bool should_decode = false);\n  std::shared_ptr<ResourceLoaderIntercept> GetResourceLoaderIntercept();\n\n  InputClientManager* GetInputClientManager();\n\n  template <typename... Args>\n  void SendEvent(int id, const char* event_name,\n                 const std::vector<std::string>& keys, Args&&... args) {\n    SendCustomEvent(id, event_name, CreateClayMap(keys, args...));\n  }\n\n  void SendCustomEvent(int id, const char* event_name,\n                       clay::Value::Map params) {\n    if (event_delegate_) {\n      event_delegate_->OnSendCustomEvent(id, event_name, std::move(params));\n    }\n  }\n\n  void AddGlobalExposureEvent(bool exposure,\n                              std::unique_ptr<clay::Value::Map> params,\n                              BaseView* view);\n\n  void SetInterceptBackKeyOnce(bool intercept) {\n    intercept_back_key_once_ = intercept;\n  }\n\n  void MoveWindow();\n\n  void PostUIMethodTask(std::function<void()> task);\n  void NotifyLowMemory() override;\n\n  void MakeRasterSnapshot(\n      BaseView* target, bool compress_jpeg, float scale,\n      std::function<void(GrDataPtr, int32_t, int32_t)> callback);\n  fml::RefPtr<PaintImage> MakeRasterSnapshot(GrPicturePtr picture,\n                                             skity::Vec2 picture_size) override;\n\n  void DumpInfoToDevtoolEnabled(bool enabled) {\n    render_delegate_->DumpInfoToDevtoolEnabled(enabled);\n  }\n\n  void SetClipboardData(const std::u16string& data) {\n    render_delegate_->SetClipboardData(data);\n  }\n\n  std::u16string GetClipboardData() const {\n    return render_delegate_->GetClipboardData();\n  }\n\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  // Text input related functions Begin.\n  void SetTextInputClient(int client_id, const char* input_action,\n                          const char* input_type) {\n    render_delegate_->SetTextInputClient(client_id, input_action, input_type);\n  }\n\n  void ClearTextInputClient() { render_delegate_->ClearTextInputClient(); }\n\n  void SetEditableTransform(const float transform_matrix[16]) {\n    render_delegate_->SetEditableTransform(transform_matrix);\n  }\n\n  void SetEditingState(uint64_t selection_base, uint64_t composing_extent,\n                       const std::string& selection_affinity,\n                       const std::string& text, bool selection_directional,\n                       uint64_t selection_extent, uint64_t composing_base) {\n    render_delegate_->SetEditingState(\n        selection_base, composing_extent, selection_affinity, text,\n        selection_directional, selection_extent, composing_base);\n  }\n\n  void SetCaretRect(float x, float y, float width, float height) {\n    render_delegate_->SetCaretRect(x, y, width, height);\n  }\n\n  void setMarkedTextRect(float x, float y, float width, float height) {\n    render_delegate_->setMarkedTextRect(x, y, width, height);\n  }\n\n  void ShowTextInput() { render_delegate_->ShowTextInput(); }\n  void HideTextInput() { render_delegate_->HideTextInput(); }\n  // Text input related functions End.\n\n  void WindowMove() { render_delegate_->WindowMove(); }\n\n  void ActivateSystemCursor(int type, const std::string& path) {\n    render_delegate_->ActivateSystemCursor(type, path);\n  }\n#endif\n\n  void FilterInputAsync(const std::string& input, const std::string& pattern,\n                        std::function<void(const std::string&)> callback) {\n    render_delegate_->FilterInputAsync(input, pattern, callback);\n  }\n\n  void DispatchLynxLayout();\n  virtual void ResetPageView(bool recycle = false);\n\n  bool IsRasterAnimationEnabled() const {\n    if (ui_component_delegate_) {\n      return ui_component_delegate_->OnEnableRasterAnimation();\n    }\n    return raster_animation_enabled_;\n  }\n\n  // The current switch of ui and raster animation is normally set by Lynx.\n  // For the purpose of local test, add a interface to enable raster\n  // animation.\n  void SetRasterAnimationEnabled(bool value) {\n    raster_animation_enabled_ = value;\n  }\n\n  bool HitTest(const PointerEvent& event, HitTestResult& result) override;\n  BaseView* GetTopViewToAcceptEvent(const FloatPoint& position,\n                                    FloatPoint* relative_position) override;\n  int GetViewIdForLocation(int x, int y);\n#ifndef NDEBUG\n  std::string ToString() const override;\n  void DumpRenderingTrees() const;\n#endif\n\n  void RegisterFirstFrameAvailable(int64_t image_id,\n                                   const fml::closure& callback);\n  void UnRegisterFirstFrameAvailable(int64_t image_id);\n  bool MarkDrawableImageFrameAvailable(int64_t image_id);\n\n  GapWorker* GetGapWorker();\n\n  bool UseTextureBackend() { return use_texture_backend_; }\n  void SetUseTextureBackend(bool use_texture_backend) {\n    use_texture_backend_ = use_texture_backend;\n  }\n\n  void SetExposureProps(int freq, bool exposure_ui_margin_enabled) {\n    intersection_observer_manager()->SetExposureFrequency(freq);\n    intersection_observer_manager()->SetExposureUIMarginEnabled(\n        exposure_ui_margin_enabled);\n  }\n\n  // Enable deferred image decode by default.\n  bool DeferredImageDecode() const {\n#ifndef ENABLE_SKITY\n    return false;\n#else\n    return true;\n#endif  // ENABLE_SKITY\n  }\n\n  // Enable new image decoding strategies.\n  // Will not affect skity when using platform decoders.\n  bool ImageDecodeWithPriority() const { return false; }\n\n  void RegisterDrawableImage(std::shared_ptr<DrawableImage> drawable_image);\n  void UnregisterDrawableImage(int64_t id);\n\n#ifdef ENABLE_ACCESSIBILITY\n  SemanticsOwner* GetSemanticsOwner() const { return semantics_owner_.get(); }\n  void SetSemanticsEnabled(bool enabled);\n  bool IsSemanticsEnabled() const;\n  void SetPageEnableAccessibilityElement(bool enabled);\n  bool IsPageEnableAccessibilityElement() const;\n\n  void RequestNewFrameForSemantics();\n  void DispatchSemanticsAction(int virtual_view_id, int action);\n#endif\n\n  const std::shared_ptr<ServiceManager>& GetServiceManager() const {\n    return service_manager_;\n  }\n\n  ShadowNode* GetShadowNodeById(int node_id) {\n    return render_delegate_->FindShadowNodeById(node_id);\n  }\n\n  void SetExternalScreenshotCallback(ExternalScreenshotCallback callback);\n\n  void CleanForRecycle() override;\n\n  // When using `input-view` or `textarea-view` backed by platform view, we\n  // record corresponding platform view when it begins to edit. If user touch\n  // any places other than `input-view` or `textarea-view`, we notity the\n  // platform view to resign first responder. Maybe we can combine this feature\n  // with focus system in the furture.\n  void SetEditingPlatformView(NativeView* view);\n\n  void OnPlatformUpdateEditState(int client_id, uint64_t selection_base,\n                                 uint64_t composing_extent,\n                                 const char* selection_affinity,\n                                 const char* text, uint64_t selection_extent,\n                                 uint64_t composing_base);\n\n  void OnPlatformPerformInputAction(int client_id);\n\n  virtual void OnFlingStart() {}\n  virtual void OnFlingEnd() {}\n\n  void RegisterUploadTask(OneShotCallback<>&& task, int image_id) override;\n\n  uint64_t PageUniqueId() const { return page_unique_id_; }\n\n#if !defined(ENABLE_CLAY_LITE)\n  OverlayManager* overlay_manager() { return overlay_manager_.get(); }\n#endif\n  void StartFluencyMonitor(uintptr_t id, const std::string& scene,\n                           const std::string& scroll_monitor_tag);\n  void EndFluencyMonitor(uintptr_t id);\n\n protected:\n  void OnDestroy() override;\n\n  bool ShouldIgnoreFocusChange(const PointerEvent& event);\n  void InitManagers();\n  void Invalidate() override;\n  void LayoutInternal();\n  void Paint();\n  void CompositeFrame(std::unique_ptr<clay::FrameTimingsRecorder> recorder);\n  void CompositeFrame();\n#ifdef ENABLE_ACCESSIBILITY\n  void FlushSemantics();\n  void PrepareSemantics(\n      fml::RefPtr<SemanticsNode> parent_node,\n      std::vector<fml::RefPtr<SemanticsNode>>& result,\n      const std::vector<std::string>& ancestor_a11y_elements) override;\n  void SendUpdatedSemantics(const SemanticsUpdateNodes& update_nodes);\n#endif\n  void RequestNewFrame() override;\n  RenderPhase GetRenderPhase() const override { return render_phase_; }\n  void DispatchViewportMetricsUpdate();\n\n  void PlatformShowSoftInput(int type, int action) override;\n  void PlatformHideSoftInput() override;\n\n  void SetupIsolatedGestures();\n  // Report the deepest leaf view in the position to lynx.\n  void ReportTopViewRawEvents(const std::vector<PointerEvent>& events);\n  // Report pointer event with specified type\n  void ReportTopViewEvent(const PointerEvent& event, ClayEventType type);\n  // Report pointer event with the type deduced by event.device and\n  // event.type\n  void ReportTopViewEvent(const PointerEvent& event);\n  // Report key event and current focused view\n  void ReportKeyEvent(const KeyEvent& event);\n  void ReportAnimationEvent(const AnimationParams& animation_params,\n                            int view_id);\n  void ReportTransitionEvent(const AnimationParams& animation_params,\n                             int view_id,\n                             ClayAnimationPropertyType property_type);\n  void FlushUIMethodTasks();\n  void SendGlobalExposureEvent();\n\n  void SendDrawEndEvent();\n  void HandleA11yTapEvent(BaseView* view);\n  void HandleA11yLongPressEvent(BaseView* view);\n\n  void EnsureSemanticsOwner();\n  void ResignFirstResponderIfNeeded(BaseView* current_responder);\n\n  void UnRegisterUploadTask();\n\n  Size physical_size_;\n  Size size_;\n  clay::ViewportMetrics metrics_;\n  bool first_paint_ = false;\n  bool first_meaningful_layout_ = false;\n  bool default_focus_ring_enable_ = false;\n  bool performance_overlay_enabled_ = false;\n  // Frontend may want to intercept kGoBack key from the default behavior.\n  // As kGoBack is a sensitive key, just intercept once for each calling.\n  bool intercept_back_key_once_ = false;\n  // If set to true, we will do raster in this frame even if there are no\n  // nodes need to paint.\n  bool force_raster_ = false;\n  int button_state_ = 0;   // the one button changed recently\n  int buttons_state_ = 0;  // bit field, all buttons pressed\n  RenderPhase render_phase_ = RenderPhase::kIdle;\n  const clay::TaskRunners task_runners_;\n\n  std::unique_ptr<Renderer> renderer_;\n  std::unique_ptr<LayoutController> layout_controller_;\n  std::unique_ptr<FrameBuilder> frame_builder_;\n  std::unique_ptr<AnimationHandler> animation_handler_;\n#ifndef ENABLE_SKITY\n  fml::RefPtr<ImageResourceFetcher> image_resource_fetcher_;\n#else\n  fml::RefPtr<ImageFetcher> image_resource_fetcher_;\n#endif\n  std::unique_ptr<GestureManager> gesture_manager_;\n  std::unique_ptr<NestedScrollManager> nested_scroll_manager_;\n  std::unique_ptr<MouseRegionManager> mouse_region_manager_;\n  IsolatedGestureDetector isolated_gesture_detector_;\n  FocusManager focus_manager_;\n  KeyboardBridge keyboard_bridge_;\n  IMEListener* ime_listener_ = nullptr;\n  KeyframesMapData keyframes_data_;\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n  std::unique_ptr<WatchDog> event_watch_dog_;\n  fml::RefPtr<RenderSettings> render_settings_;\n  std::unique_ptr<InputClientManager> input_client_manager_;\n  std::unique_ptr<ViewTreeObserver> view_tree_observer_;\n  std::shared_ptr<FrameTimingCollector> frame_timing_collector_;\n  std::unique_ptr<IntersectionObserverManager> intersection_observer_manager_;\n  std::vector<std::function<void()>> ui_method_tasks_;\n  bool raster_animation_enabled_ = true;\n  std::string base_uri_;\n  clay::FixedRefreshRateStopwatch layout_and_animation_time_;\n  std::unordered_map<int64_t, fml::closure> first_frame_callbacks_;\n  // This records the views that received touch down events. Subsequent\n  // touch events with the same pointer_id should be dispatched to the same\n  // view, regardless of whether the touch point remains within the view's\n  // boundaries.\n  std::unordered_map<int, int> touch_view_map_;\n\n  std::unique_ptr<GapWorker> gap_worker_;\n\n  ExposureDataList exposure_event_arr_;\n  ExposureDataList disexposure_event_arr_;\n\n  std::vector<fml::WeakPtr<BaseView>> exposure_event_data_set_;\n  std::vector<fml::WeakPtr<BaseView>> disexposure_event_data_set_;\n\n  bool use_texture_backend_ = true;\n  EventDelegate* event_delegate_ = nullptr;\n  UIComponentDelegate* ui_component_delegate_ = nullptr;\n  RenderDelegate* render_delegate_ = nullptr;\n\n  std::shared_ptr<PipelineTimingDelegate> pipeline_timing_delegate_;\n  std::shared_ptr<ScrollFluencyMonitorDelegate>\n      scroll_fluency_monitor_delegate_;\n\n  friend class ViewContext;\n  ViewContext* view_context_ = nullptr;\n\n#ifdef ENABLE_ACCESSIBILITY\n  std::unique_ptr<SemanticsOwner> semantics_owner_;\n#endif\n\n  const std::shared_ptr<ServiceManager> service_manager_;\n  NativeView* editing_native_view_ = nullptr;\n\n  const uint64_t page_unique_id_;\n  BaseView* pan_zoom_target_ = nullptr;\n  BaseView* wheel_target_ = nullptr;\n#if !defined(ENABLE_CLAY_LITE)\n  std::unique_ptr<OverlayManager> overlay_manager_;\n#endif\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_PAGE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/page_view_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <array>\n#include <memory>\n\n#include \"clay/ui/component/page_view.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(PageViewTest, EmptyKeyframesData) {\n  std::unique_ptr<PageView> page_view =\n      std::make_unique<PageView>(0, nullptr, nullptr);\n  Value keyframes_data;\n  page_view->SetKeyframesData(keyframes_data);\n  EXPECT_EQ(page_view->GetKeyframesMap(\"name\"), nullptr);\n}\n\nTEST(PageViewTest, KeyframesData) {\n  std::unique_ptr<PageView> page_view =\n      std::make_unique<PageView>(0, nullptr, nullptr);\n\n  Value keyframes_data = Value{\n      {\"anim_1\",\n       Value{\n           {\"0\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                       {\"opacity\", Value{0.0}}}},\n           {\"0.5\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                         {\"opacity\", Value{0.5}}}},\n           {\"1\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                       {\"opacity\", Value{1.0}}}},\n       }},\n      {\"anim_2\",\n       Value{\n           {\"0\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                       {\"opacity\", Value{0.0}}}},\n           {\"0.5\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                         {\"opacity\", Value{0.5}}}},\n           {\"1\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                       {\"opacity\", Value{1.0}}}},\n       }},\n      {\"anim_3\",\n       Value{\n           {\"0\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                       {\"opacity\", Value{0.0}}}},\n           {\"0.5\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                         {\"opacity\", Value{0.5}}}},\n           {\"1\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                       {\"opacity\", Value{1.0}}}},\n       }},\n  };\n\n  page_view->SetKeyframesData(keyframes_data);\n\n  auto check_keyframes_map = [&page_view](const char* anim_name) {\n    const KeyframesMap* ret = page_view->GetKeyframesMap(anim_name);\n    EXPECT_TRUE(ret);\n    auto it = ret->find(ClayAnimationPropertyType::kBackgroundColor);\n    EXPECT_TRUE(it != ret->end());\n    it = ret->find(ClayAnimationPropertyType::kOpacity);\n    EXPECT_TRUE(it != ret->end());\n  };\n\n  check_keyframes_map(\"anim_1\");\n  check_keyframes_map(\"anim_2\");\n  check_keyframes_map(\"anim_3\");\n\n  Value keyframes_data_2 = Value{\n      {\"anim_1\",\n       Value{\n           {\"0\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                       {\"opacity\", Value{0.0}}}},\n           {\"0.5\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                         {\"opacity\", Value{0.5}}}},\n           {\"1\", Value{{\"background-color\", Value{0xFFFF0000u}},\n                       {\"opacity\", Value{1.0}}}},\n       }},\n  };\n  page_view->SetKeyframesData(keyframes_data_2);\n\n  check_keyframes_map(\"anim_1\");\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/rubberband_distance.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/rubberband_distance.h\"\n\n#include <cmath>\n\nnamespace clay {\n\n/**\n Alternative formula: f(x) = (x * dimension * resistance) / (dimension *\n resistance + |x|)\n */\nfloat RubberBandDistance(float x, float dimension,\n                         float resistance /* = 0.55 */) {\n  float res =\n      (x * dimension * resistance) / (std::abs(x) * resistance + dimension);\n  return (isnan(res) || isinf(res)) ? 0 : res;\n}\n\nfloat ReverseRubberBandDistance(float x, float dimension,\n                                float resistance /* = 0.55 */) {\n  float res = (x * dimension) / (resistance * (dimension - std::abs(x)));\n  return (isnan(res) || isinf(res)) ? 0 : res;\n}\n\nFloatPoint RubberBandDistance(FloatPoint offset, float width, float height) {\n  return FloatPoint(RubberBandDistance(offset.x(), width),\n                    RubberBandDistance(offset.y(), height));\n}\n\nFloatPoint ReverseRubberBandDistance(FloatPoint offset, float width,\n                                     float height) {\n  return FloatPoint(ReverseRubberBandDistance(offset.x(), width),\n                    ReverseRubberBandDistance(offset.y(), height));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/rubberband_distance.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_RUBBERBAND_DISTANCE_H_\n#define CLAY_UI_COMPONENT_RUBBERBAND_DISTANCE_H_\n\n#include \"clay/gfx/geometry/float_point.h\"\n\nnamespace clay {\n\nfloat RubberBandDistance(float x, float dimension, float resistance = 0.55);\nfloat ReverseRubberBandDistance(float x, float dimension,\n                                float resistance = 0.55);\nFloatPoint RubberBandDistance(FloatPoint offset, float width_dimension,\n                              float height_dimension);\nFloatPoint ReverseRubberBandDistance(FloatPoint offset, float width_dimension,\n                                     float height_dimension);\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_RUBBERBAND_DISTANCE_H_\n"
  },
  {
    "path": "clay/ui/component/scroll_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/scroll_view.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/viscous_fluid_interpolator.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/utils/floating_comparison.h\"\n#include \"clay/ui/component/base_image_view.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/component/nested_scroll/nested_scrollable.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_callback/scroll_event_callback_manager.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n#include \"clay/ui/rendering/render_box.h\"\n#include \"clay/ui/rendering/render_scroll.h\"\n\nnamespace clay {\nnamespace {\n\nconstexpr char kScrollViewTag[] = \"scroll-view-impl\";\n\nconstexpr char kArgOffset[] = \"offset\";\nconstexpr char kArgSmooth[] = \"smooth\";\nconst std::vector<std::string> kScrollToArgs{kArgSmooth, kArgOffset};\n\nNestedScrollMode ParseNestedScrollMode(const std::string& value) {\n  if (value == \"self-only\") {\n    return NestedScrollMode::kSelfOnly;\n  } else if (value == \"self-first\") {\n    return NestedScrollMode::kSelfFirst;\n  } else if (value == \"parent-first\") {\n    return NestedScrollMode::kParentFirst;\n  } else {\n    FML_DLOG(ERROR) << \"invalid nested scroll mode: \" << value;\n    return NestedScrollMode::kSelfOnly;\n  }\n}\n\ninline ScrollDirection ParseScrollDirection(const std::string& value) {\n  if (value == \"horizontal\") {\n    return ScrollDirection::kHorizontal;\n  } else {\n    return ScrollDirection::kVertical;\n  }\n}\n\ninline ScrollEventCallbackManager::ScrollState ConvertScrollState(\n    Scrollable::ScrollStatus status) {\n  if (status == Scrollable::ScrollStatus::kAnimating ||\n      status == Scrollable::ScrollStatus::kFling ||\n      status == Scrollable::ScrollStatus::kBounce) {\n    return ScrollEventCallbackManager::ScrollState::kAnimating;\n  } else if (status == Scrollable::ScrollStatus::kIdle) {\n    return ScrollEventCallbackManager::ScrollState::kIdle;\n  } else {\n    return ScrollEventCallbackManager::ScrollState::kDragging;\n  }\n}\n\n}  // namespace\n\nScrollView::ScrollView(int32_t id, PageView* page_view)\n    : ScrollView(id, ScrollDirection::kVertical, page_view) {}\n\nScrollView::ScrollView(int32_t id, ScrollDirection direction,\n                       PageView* page_view,\n                       std::unique_ptr<RenderScroll> render_scroll)\n    : ScrollView(id, id, direction, page_view, std::move(render_scroll)) {}\n\nScrollView::ScrollView(int32_t id, int32_t callback_id,\n                       ScrollDirection direction, PageView* page_view,\n                       std::unique_ptr<RenderScroll> render_scroll)\n    : WithTypeInfo(id, kScrollViewTag, std::move(render_scroll), page_view,\n                   direction, true),\n      callback_manager_(\n          std::make_unique<ScrollEventCallbackManager>(this, callback_id)),\n      callback_id_(callback_id),\n      weak_factory_(this) {\n  InitScrollView();\n}\n\nScrollView::ScrollView(\n    int32_t id, int32_t callback_id, ScrollDirection direction,\n    PageView* page_view,\n    std::unique_ptr<ScrollEventCallbackManager> callback_manager,\n    std::unique_ptr<RenderScroll> render_scroll)\n    : WithTypeInfo(id, kScrollViewTag, std::move(render_scroll), page_view,\n                   direction, true),\n      callback_manager_(std::move(callback_manager)),\n      callback_id_(callback_id),\n      weak_factory_(this) {\n  InitScrollView();\n}\n\nvoid ScrollView::InitScrollView() {\n  scroller_ =\n      std::make_unique<Scroller>(this, GetAnimationHandler(),\n                                 std::make_unique<ViscousFluidInterpolator>());\n  SetIsFocusScope();\n  GetFocusManager()->SetFirstFocusedNodeExpandRatio(1.f / 3.f);\n#if defined(OS_IOS) || defined(OS_OSX)\n  // Enable bounce effect by default on iOS and macOS.\n  SetOverscrollEnabled(true);\n#endif\n}\n\nScrollView::~ScrollView() {}\n\nvoid ScrollView::AddChild(BaseView* child, int index) {\n  if (child->Is<BounceView>()) {\n    bounce_view_ = static_cast<BounceView*>(child);\n    MarkNeedsLayout();\n  }\n  BaseView::AddChild(child, index);\n  if (delegate_) {\n    delegate_->OnScrollViewChildAdded();\n  }\n  CorrectScrollOffset();\n  if (!page_view()->ImageDecodeWithPriority()) {\n    auto func = [](BaseView* v) {\n      if (v && v->Is<BaseImageView>()) {\n        static_cast<BaseImageView*>(v)->TryDecodeImmediately();\n      }\n    };\n    child->VisitChildren(func);\n  }\n}\n\nvoid ScrollView::RemoveChild(BaseView* child) {\n  if (child->Is<BounceView>()) {\n    bounce_view_ = nullptr;\n    MarkNeedsLayout();\n  }\n  BaseView::RemoveChild(child);\n  if (delegate_) {\n    delegate_->OnScrollViewChildRemoved();\n  }\n  CorrectScrollOffset();\n}\n\nvoid ScrollView::OnLayout(LayoutContext* context) {\n  BaseView::OnLayout(context);\n  if (content_size_ != old_content_size_) {\n    UpdateScrollOffsetForRTL();\n    NotifyContentSizeChanged(content_size_);\n    old_content_size_ = content_size_;\n  }\n  if (pending_scroll_offset_) {\n    bool is_vertical = scroll_direction_ == ScrollDirection::kVertical;\n    auto render_scroll = static_cast<RenderScroll*>(render_object());\n    if ((is_vertical &&\n         *pending_scroll_offset_ <= render_scroll->MaxScrollHeight()) ||\n        (!is_vertical &&\n         *pending_scroll_offset_ <= render_scroll->MaxScrollWidth())) {\n      OnScrollUpdate(*pending_scroll_offset_);\n      pending_scroll_offset_ = std::nullopt;\n    }\n  }\n  if (pending_scroll_index_ > -1) {\n    SetScrollToIndex(pending_scroll_index_);\n    pending_scroll_index_ = -1;\n  }\n  if (!bounce_view_) {\n    return;\n  }\n  auto bounce_direction = bounce_view_->GetDirection();\n  if (bounce_direction == Direction::kTop) {\n    bounce_view_->SetY(Top() - bounce_view_->Height() -\n                       bounce_view_->MarginBottom());\n  } else if (bounce_direction == Direction::kLeft) {\n    bounce_view_->SetX(Left() - bounce_view_->Width() -\n                       bounce_view_->MarginRight());\n  } else if (bounce_direction == Direction::kBottom) {\n    auto render_box = static_cast<RenderBox*>(render_object());\n    auto height = render_box->OverflowRect().MaxY();\n    bounce_view_->SetY(height + bounce_view_->MarginTop());\n  } else if (bounce_direction == Direction::kRight) {\n    auto render_box = static_cast<RenderBox*>(render_object());\n    auto width = render_box->OverflowRect().MaxX();\n    bounce_view_->SetX(width + bounce_view_->MarginLeft());\n  }\n}\n\nvoid ScrollView::CalculateOverFlow() {\n  GetRenderScroll()->AddOverflowFromChildren();\n  GetRenderScroll()->MarkNeedsPaint();\n\n  content_size_ =\n      FloatSize(std::max<float>(GetRenderScroll()->OverflowRect().MaxX(),\n                                BaseView::ContentWidth()),\n                std::max<float>(GetRenderScroll()->OverflowRect().MaxY(),\n                                BaseView::ContentHeight()));\n}\n\nvoid ScrollView::OnLayoutUpdated() {\n  CalculateOverFlow();\n  CorrectScrollOffset();\n}\n\n#ifdef ENABLE_ACCESSIBILITY\nint32_t ScrollView::GetA11yScrollChildren() const {\n  int32_t valid_count = 0;\n  for (auto child : children_) {\n    if (child->IsAccessibilityElement()) {\n      ++valid_count;\n    }\n  }\n  return valid_count;\n}\n\nint32_t ScrollView::GetSemanticsActions() const {\n  int32_t actions = BaseView::GetSemanticsActions();\n  if (CanScrollY()) {\n    actions |=\n        static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollUp) |\n        static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollDown);\n  } else if (CanScrollX()) {\n    actions |=\n        static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollLeft) |\n        static_cast<int32_t>(SemanticsNode::SemanticsAction::kScrollRight);\n  } else {\n    FML_DLOG(ERROR) << \"scrollview cannot scroll, direction: \"\n                    << static_cast<int32_t>(scroll_direction_);\n  }\n  return actions;\n}\n\nint32_t ScrollView::GetSemanticsFlags() const {\n  int32_t flags = BaseView::GetSemanticsFlags();\n  flags |=\n      static_cast<int32_t>(SemanticsNode::SemanticsFlag::kHasImplicitScrolling);\n  return flags;\n}\n#endif\n\nbool ScrollView::OnScrollToMiddle(BaseView* target_view) {\n  if (!target_view) {\n    FML_DLOG(ERROR) << \"DispatchA11yShowOnScreenEvent but view is nullptr\";\n    return false;\n  }\n  FloatRect rect = target_view->GetBounds();\n  rect.Move(PaddingLeft(), PaddingTop());\n  ScrollChildViewToMiddle(rect);\n  return true;\n}\n\nvoid ScrollView::DoScrollFromRaster(float scroll_offset, bool ignore_repaint) {\n  auto delta = scroll_direction_ == ScrollDirection::kVertical\n                   ? FloatPoint{0, scroll_offset - scroll_offset_.y()}\n                   : FloatPoint{scroll_offset - scroll_offset_.x(), 0};\n  DoScroll(delta, false, ignore_repaint);\n}\n\nvoid ScrollView::OnScrollUpdate(float scroll_offset) {\n  auto delta = scroll_direction_ == ScrollDirection::kVertical\n                   ? FloatPoint{0, scroll_offset - scroll_offset_.y()}\n                   : FloatPoint{scroll_offset - scroll_offset_.x(), 0};\n  DoScroll(delta, false);\n}\n\nbool ScrollView::OnScrollToVisible() {\n  FloatRect focused_view_rect = GetFocusManager()->GetFocusedNodeRect();\n  if (!focused_view_rect.IsEmpty()) {\n    ScrollChildViewToVisible(focused_view_rect);\n  }\n  return true;\n}\n\nvoid ScrollView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kScrollX) {\n    bool scroll_x = attribute_utils::GetBool(value);\n    SetScrollDirection(scroll_x ? ScrollDirection::kHorizontal\n                                : ScrollDirection::kVertical);\n  } else if (kw == KeywordID::kScrollEventThrottle) {\n    int throttle_ms = attribute_utils::GetInt(value);\n    if (throttle_ms >= 0) {\n      callback_manager_->SetScrollEventThrottle(\n          fml::TimeDelta::FromMilliseconds(throttle_ms));\n    }\n  } else if (kw == KeywordID::kScrollY) {\n    bool scroll_y = attribute_utils::GetBool(value);\n    SetScrollDirection(scroll_y ? ScrollDirection::kVertical\n                                : ScrollDirection::kHorizontal);\n  } else if (kw == KeywordID::kLowerThreshold) {\n    lower_threshold_ = FromLogical(attribute_utils::GetNum(value));\n  } else if (kw == KeywordID::kUpperThreshold) {\n    upper_threshold_ = FromLogical(attribute_utils::GetNum(value));\n  } else if (kw == KeywordID::kScrollTop) {\n    // keyword: scroll-top is deprecated, use initial-scroll-offset and ui\n    // method instead.\n    float offset = FromLogical(attribute_utils::GetNum(value));\n    if (offset <= GetRenderScroll()->MaxScrollHeight()) {\n      pending_scroll_offset_ = std::nullopt;\n      OnScrollUpdate(offset);\n    } else {\n      pending_scroll_offset_ = offset;\n    }\n  } else if (kw == KeywordID::kScrollLeft) {\n    // keyword: scroll-left is deprecated, use initial-scroll-offset and ui\n    // method instead.\n    float offset = FromLogical(attribute_utils::GetNum(value));\n    if (offset <= GetRenderScroll()->MaxScrollWidth()) {\n      pending_scroll_offset_ = std::nullopt;\n      OnScrollUpdate(offset);\n    } else {\n      pending_scroll_offset_ = offset;\n    }\n  } else if (kw == KeywordID::kScrollToIndex) {\n    // keyword: scroll-to-index is deprecated, use ui method and\n    // initial-scroll-index instead.\n    size_t index = attribute_utils::GetInt(value);\n    if (index < child_count() && CanInvokeScrollImmediately()) {\n      pending_scroll_index_ = -1;\n      SetScrollToIndex(index);\n    } else {\n      pending_scroll_index_ = attribute_utils::GetNum(value);\n    }\n  } else if (kw == KeywordID::kScrollToId) {\n    std::string result;\n    if (attribute_utils::TryGetString(value, result)) {\n      SetScrollToId(result);\n    }\n  } else if (kw == KeywordID::kEnableNestedScroll) {\n    bool enable_nested_scroll = attribute_utils::GetBool(value);\n    SetEnableNestedScroll(enable_nested_scroll);\n  } else if (kw == KeywordID::kEnableScroll) {\n    bool enabled = attribute_utils::GetBool(value, true);\n    SetScrollEnabled(enabled);\n  } else if (kw == KeywordID::kScrollMonitorTag) {\n    std::string tag;\n    if (attribute_utils::TryGetString(value, tag)) {\n      SetScrollMonitorTag(tag);\n    }\n  } else if (kw == KeywordID::kScrollOrientation) {\n    std::string orientation;\n    attribute_utils::TryGetString(value, orientation);\n    auto direction = ParseScrollDirection(orientation);\n    if (GetScrollDirection() == direction) {\n      return;\n    }\n    OnScrollUpdate(0);\n    SetScrollDirection(direction);\n  } else if (kw == KeywordID::kInitialScrollIndex) {\n    size_t index = attribute_utils::GetInt(value);\n    if (!initial_scroll_index_set_ && index >= 0) {\n      initial_scroll_index_set_ = true;\n      if (CanInvokeScrollImmediately()) {\n        pending_scroll_index_ = -1;\n        ScrollTo(false, 0, index);\n      } else {\n        pending_scroll_index_ = index;\n      }\n    }\n  } else if (kw == KeywordID::kInitialScrollOffset) {\n    int offset = attribute_utils::GetNum(value);\n    if (!initial_scroll_offset_set_ && offset >= 0) {\n      initial_scroll_offset_set_ = true;\n      if (CanInvokeScrollImmediately()) {\n        pending_scroll_offset_ = std::nullopt;\n        ScrollTo(false, offset);\n      } else {\n        pending_scroll_offset_ = offset;\n      }\n    }\n  } else if (kw == KeywordID::kBounce || kw == KeywordID::kBounces) {\n    auto enable_bounce = attribute_utils::GetBool(value);\n    SetOverscrollEnabled(enable_bounce);\n  } else if (kw == KeywordID::kScrollForwardMode) {\n    scroll_forward_mode_ =\n        ParseNestedScrollMode(attribute_utils::GetCString(value));\n  } else if (kw == KeywordID::kScrollBackwardMode) {\n    scroll_backward_mode_ =\n        ParseNestedScrollMode(attribute_utils::GetCString(value));\n  } else if (kw == KeywordID::kPrevScrollable) {\n    pre_scrollable_ = attribute_utils::GetCString(value);\n  } else if (kw == KeywordID::kNextScrollable) {\n    next_scrollable_ = attribute_utils::GetCString(value);\n  } else {\n    BaseView::SetAttribute(attr_c, value);\n  }\n}\n\nvoid ScrollView::SetDirection(int type) {\n  float new_offset = 0;\n  if (scroll_direction_ == ScrollDirection::kHorizontal &&\n      (type == static_cast<int>(DirectionType::kLynxRtl) ||\n       type == static_cast<int>(DirectionType::kRtl))) {\n    new_offset = GetRenderScroll()->MaxScrollWidth();\n  }\n  OnScrollUpdate(new_offset);\n  GetRenderScroll()->SetLayoutDirection(static_cast<DirectionType>(type));\n}\n\nvoid ScrollView::UpdateScrollOffsetForRTL() {\n  if (scroll_direction_ == ScrollDirection::kHorizontal && IsRtlDirection()) {\n    float previous_offset_x =\n        old_content_size_.width() - scroll_offset_.x() - Width();\n    GetScroller()->ScrollToImmediately(GetRenderScroll()->MaxScrollWidth() -\n                                       previous_offset_x);\n  }\n}\n\nvoid ScrollView::AddEventCallback(const char* event_c) {\n  BaseView::AddEventCallback(event_c);\n  std::string event(event_c);\n  if (callback_manager_->AddEventCallback(event)) {\n    return;\n  }\n  if (event.compare(event_attr::kEventContentSizeChanged) == 0) {\n    enable_content_size_changed_event_ = true;\n  }\n}\n\nfloat ScrollView::ContentWidth() const {\n  if (content_size_.width() != 0.f) {\n    return content_size_.width();\n  }\n  return BaseView::ContentWidth();\n}\n\nfloat ScrollView::ContentHeight() const {\n  if (content_size_.height() != 0.f) {\n    return content_size_.height();\n  }\n  return BaseView::ContentHeight();\n}\n\nconst FloatRect& ScrollView::OverflowRect() const {\n  RenderBox* box = static_cast<RenderBox*>(render_object_.get());\n  return box->OverflowRect();\n}\n\nvoid ScrollView::OnScrollBegin() {\n  // confirm the scroll area\n  CalculateOverFlow();\n  StopAnimation();\n}\n\nvoid ScrollView::OnScrollStatusChange(ScrollView::ScrollStatus old_status) {\n  NestedScrollable::OnScrollStatusChange(old_status);\n  if (status_ == Scrollable::ScrollStatus::kDragging) {\n    OnScrollBegin();\n  } else if (status_ == Scrollable::ScrollStatus::kBounce) {\n    float offset = scroll_direction_ == ScrollDirection::kVertical\n                       ? ClampedOverscrollOffset().y()\n                       : ClampedOverscrollOffset().x();\n    OnBounceStart(offset);\n  } else if (status_ == Scrollable::ScrollStatus::kIdle) {\n    callback_manager_->NotifyScrollEnd(scroll_offset_);\n  }\n\n  auto current_callback_scroll_status = ConvertScrollState(status_);\n  if (last_callback_scroll_state_ == current_callback_scroll_status) {\n    return;\n  }\n  auto old_callback_scroll_status = last_callback_scroll_state_;\n  last_callback_scroll_state_ = current_callback_scroll_status;\n  float velocity = 0;\n  if (current_callback_scroll_status ==\n      ScrollEventCallbackManager::ScrollState::kAnimating) {\n    velocity = page_view()->nested_scroll_manager()->GetFlingVelocity();\n    if (velocity == 0) {\n      // in some cases, the first fling will be handled by rasterfling animator,\n      // so we use drag end velocity instead.\n      velocity = last_drag_end_velocity_;\n    }\n    // logicalpx/ms\n    velocity = page_view()->ConvertTo<kPixelTypeLogical>(velocity) / 1000.0f;\n  }\n  callback_manager_->NotifyScrollStateChange(\n      old_callback_scroll_status, current_callback_scroll_status, velocity,\n      GetScrollStatus() != ScrollStatus::kAnimating);\n}\n\nvoid ScrollView::NotifyContentSizeChanged(const FloatSize& new_size) {\n  if (enable_content_size_changed_event_) {\n    auto event_name = \"contentsizechanged\";\n    if (HasEvent(event_name)) {\n      page_view()->SendEvent(\n          callback_id_, event_name, {\"scrollWidth\", \"scrollHeight\"},\n          ToLogical(new_size.width()), ToLogical(new_size.height()));\n    }\n  }\n}\n\nvoid ScrollView::OnChildSizeChanged(BaseView* child) {\n  if (delegate_) {\n    delegate_->OnScrollViewChildUpdated();\n  }\n\n  CalculateOverFlow();\n\n  CorrectScrollOffset();\n  MarkNeedsLayout();\n}\n\nvoid ScrollView::OnContentSizeChanged(const FloatRect& old_rect,\n                                      const FloatRect& new_rect) {\n  BaseView::OnContentSizeChanged(old_rect, new_rect);\n  content_size_.SetWidth(std::max(new_rect.width(), content_size_.width()));\n  content_size_.SetHeight(std::max(new_rect.height(), content_size_.height()));\n\n  CorrectScrollOffset();\n}\n\nbool ScrollView::CanScroll() { return CanScrollX() || CanScrollY(); }\n\nvoid ScrollView::StopAnimation() {\n  NestedScrollable::StopAnimation();\n  if (scroller_ && scroller_->IsScrolling()) {\n    scroller_->AbortAnimation();\n  }\n}\n\nvoid ScrollView::ScrollChildViewToVisible(const FloatRect& rect) {\n  // A fake scroll-begin call to:\n  // 1. Generate the lazy-create items which are not in the visible rect.\n  // 2. Stop previous animation.\n  OnScrollBegin();\n  FloatRect scroll_view_rect = GetBounds();\n  scroll_view_rect.SetLocation(FloatPoint(0, 0));\n\n  FloatRect focused_view_rect = rect;\n  focused_view_rect.MoveBy(-scroll_offset_);\n\n  if (scroll_view_rect.Contains(focused_view_rect)) {\n    return;\n  }\n\n  float offset = 0.f;\n  if (CanScrollX()) {\n    offset = rect.x() + rect.width() - scroll_view_rect.width();\n    if (focused_view_rect.x() < scroll_view_rect.x()) {\n      offset = rect.x();\n    }\n  }\n  if (CanScrollY()) {\n    offset = rect.y() + rect.height() - scroll_view_rect.height();\n    if (focused_view_rect.y() < scroll_view_rect.y()) {\n      offset = rect.y();\n    }\n  }\n\n  OnScrollUpdate(offset);\n}\n\nvoid ScrollView::ScrollChildViewToMiddle(const FloatRect& rect) {\n  OnScrollBegin();\n  FloatRect scroll_view_rect = GetBounds();\n  scroll_view_rect.SetLocation(FloatPoint(0, 0));\n\n  FloatRect view_rect = rect;\n  view_rect.MoveBy(-scroll_offset_);\n\n  if (scroll_view_rect.Contains(view_rect)) {\n    return;\n  }\n\n  float offset = 0.f;\n  if (CanScrollX()) {\n    offset = rect.x() + rect.width() / 2.f - scroll_view_rect.width() / 2.f;\n    if (view_rect.x() < scroll_view_rect.x()) {\n      offset = rect.x() - scroll_view_rect.width() / 2.f + rect.width() / 2.f;\n    }\n  }\n  if (CanScrollY()) {\n    offset = rect.y() + rect.height() / 2.f - scroll_view_rect.height() / 2.f;\n    if (view_rect.y() < scroll_view_rect.y()) {\n      offset = rect.y() - scroll_view_rect.height() / 2.f + rect.height() / 2.f;\n    }\n  }\n\n  ScrollTo(true, offset);\n}\n\nvoid ScrollView::Invalidate() {\n  FML_DCHECK(render_object());\n  // ScrollView always fully loads all children. During scrolling, only\n  // ClipRect needs to be updated without re-recording.\n  render_object()->MarkNeedsEffect();\n}\n\nvoid ScrollView::ScrollWithDelta(bool smooth, float delta) {\n  if (RoughlyEqualToZero(delta)) {\n    return;\n  }\n  float start_offset = scroll_direction_ == ScrollDirection::kVertical\n                           ? scroll_offset_.y()\n                           : scroll_offset_.x();\n  if (smooth) {\n    GetScroller()->StartScroll(start_offset, delta);\n  } else {\n    GetScroller()->ScrollToImmediately(start_offset + delta);\n  }\n}\n\nvoid ScrollView::ScrollTo(bool smooth, float offset, int index) {\n  FML_DCHECK(scroll_direction_ != ScrollDirection::kNone);\n  float start_offset = scroll_direction_ == ScrollDirection::kVertical\n                           ? scroll_offset_.y()\n                           : scroll_offset_.x();\n  float end_offset = 0;\n  auto& children = GetChildren();\n  float child_offset = 0.f;\n  if (index >= 0 && static_cast<unsigned int>(index) < children.size()) {\n    if (scroll_direction_ == ScrollDirection::kVertical) {\n      child_offset = children[index]->Top();\n    } else {\n      if (IsRtlDirection()) {\n        child_offset =\n            children[index]->Left() + children[index]->Width() - Width();\n        offset = -offset;\n      } else {\n        child_offset = children[index]->Left();\n      }\n    }\n  }\n  end_offset = child_offset + offset;\n  if (smooth) {\n    GetScroller()->StartScroll(start_offset, end_offset - start_offset);\n  } else {\n    GetScroller()->ScrollToImmediately(end_offset);\n  }\n}\n\nvoid ScrollView::OnBounceStart(float start_offset) {\n  bool trigger_event = false;\n  if (start_offset < 0) {\n    trigger_event =\n        bounce_view_ && (bounce_view_->GetDirection() == Direction::kLeft ||\n                         bounce_view_->GetDirection() == Direction::kTop);\n  } else if (start_offset > 0) {\n    trigger_event =\n        bounce_view_ && (bounce_view_->GetDirection() == Direction::kRight ||\n                         bounce_view_->GetDirection() == Direction::kBottom);\n  }\n  if (trigger_event) {\n    auto bounce_threshold = scroll_direction_ == ScrollDirection::kVertical\n                                ? bounce_view_->ContentHeight()\n                                : bounce_view_->ContentWidth();\n    if (HasEvent(event_attr::kEventScrollToBounce) &&\n        std::abs(start_offset) > bounce_threshold * 0.8 && trigger_event) {\n      page_view()->SendEvent(callback_id_, event_attr::kEventScrollToBounce,\n                             {});\n    }\n  }\n}\n\nvoid ScrollView::DidScroll() {\n  NestedScrollable::DidScroll();\n\n  // Trigger ScrollWrapper and descendants to be updated in a11y system.\n#ifdef ENABLE_ACCESSIBILITY\n  if (Parent() && Parent()->IsAccessibilityElement()) {\n    if (SemanticsOwner* semantics_owner = GetSemanticsOwner()) {\n      semantics_owner->AddDirtySemanticsForDescendants(this);\n    }\n  }\n#endif\n\n  auto delta = TotalScrollOffset() - last_scroll_offset_;\n  last_scroll_offset_ = TotalScrollOffset();\n  FML_DCHECK(!delta.IsOrigin()) << \"delta should not be empty\";\n  RenderScroll* scroll = GetRenderScroll();\n  if (enable_sticky_) {\n    HandleSticky();\n  }\n  bool is_upper, is_lower;\n  if (static_cast<int>(scroll_direction_) &\n      static_cast<int>(ScrollDirection::kVertical)) {\n    // vertical\n    is_lower =\n        scroll_offset_.y() >= scroll->MaxScrollHeight() - lower_threshold_;\n    is_upper = scroll_offset_.y() <= upper_threshold_;\n  } else {\n    // horizontal\n    if (IsRtlDirection()) {\n      is_upper =\n          scroll_offset_.x() >= scroll->MaxScrollWidth() - lower_threshold_;\n      is_lower = scroll_offset_.x() <= upper_threshold_;\n    } else {\n      is_lower =\n          scroll_offset_.x() >= scroll->MaxScrollWidth() - lower_threshold_;\n      is_upper = scroll_offset_.x() <= upper_threshold_;\n    }\n  }\n  ScrollEventCallbackManager::BorderStatus border_status;\n  border_status.SetUpper(is_upper);\n  border_status.SetLower(is_lower);\n  callback_manager_->NotifyScrolled(\n      delta, scroll_offset_, border_status,\n      GetScrollStatus() == ScrollStatus::kDragging);\n\n  // Trigger ScrollWrapper and descendants to be updated in a11y system.\n#ifdef ENABLE_ACCESSIBILITY\n  if (Parent() && Parent()->IsAccessibilityElement()) {\n    if (SemanticsOwner* semantics_owner = GetSemanticsOwner()) {\n      semantics_owner->AddDirtySemanticsForDescendants(Parent());\n    }\n  }\n#endif\n}\n\nFloatPoint ScrollView::TotalScrollOffset() {\n  return GetRenderScroll()->ScrollOffset();\n}\n\nvoid ScrollView::SetScrollToId(const std::string& id_str, bool smooth) {\n  for (BaseView* child : GetChildren()) {\n    // since lynx native use name instead of id here, we should use name\n    // too.\n    if (child && child->GetComponentName() == id_str) {\n      if (scroll_direction_ == ScrollDirection::kVertical) {\n        int offset = child->Top() - ContentInsetTop();\n        ScrollTo(smooth, offset);\n      } else {\n        int offset = child->Left() - ContentInsetLeft();\n        ScrollTo(smooth, offset);\n      }\n      break;\n    }\n  }\n}\n\nvoid ScrollView::SetScrollToIndex(int index) {\n  auto& children = GetChildren();\n\n  if (index < 0 || static_cast<unsigned int>(index) >= children.size()) {\n    return;\n  }\n  int offset;\n  if (scroll_direction_ == ScrollDirection::kVertical) {\n    offset = children[index]->Top();\n  } else {\n    if (IsRtlDirection()) {\n      offset = children[index]->Left() + children[index]->Width() - Width();\n    } else {\n      offset = children[index]->Left();\n    }\n  }\n  OnScrollUpdate(offset);\n}\n\nvoid ScrollView::AutoScroll(bool start, float rate) {\n  if (start && IsScrollEnabled()) {\n    if (rate > 0 && !auto_scroll_) {\n      const int rate_per_frame = static_cast<int>(std::max(1.f, (rate / 60)));\n      auto_scroll_ = true;\n      auto_scroll_rate_ = rate_per_frame;\n      StartSmoothScroll();\n    }\n  } else {\n    auto_scroll_ = false;\n    auto_scroll_rate_ = 0;\n  }\n}\n\nvoid ScrollView::OnScrollAnimationStart(float start, float delta,\n                                        int duration) {\n  SetScrollStatus(Scrollable::ScrollStatus::kAnimating);\n}\n\nvoid ScrollView::OnScrollAnimationEnd() {\n  SetScrollStatus(Scrollable::ScrollStatus::kIdle);\n}\n\n// TODO(liuguoliang): Check if ScrollStatus::kAnimating is needed.\nvoid ScrollView::StartSmoothScroll() {\n  if (auto_scroll_) {\n    if (scroll_direction_ == ScrollDirection::kVertical) {\n      ScrollTo(false, auto_scroll_rate_ + scroll_offset_.y());\n      if (scroll_offset_.y() <\n          static_cast<RenderScroll*>(render_object())->MaxScrollHeight()) {\n        page_view()->GetTaskRunner()->PostDelayedTask(\n            [self = weak_factory_.GetWeakPtr()]() {\n              if (self) {\n                self->StartSmoothScroll();\n              }\n            },\n            fml::TimeDelta::FromMilliseconds(16));\n      } else {\n        auto_scroll_ = false;\n      }\n    } else {\n      float offset = 0.f;\n      offset = scroll_offset_.x() +\n               (IsRtlDirection() ? (-auto_scroll_rate_) : auto_scroll_rate_);\n      ScrollTo(false, offset);\n      bool has_space_to_scroll =\n          IsRtlDirection()\n              ? (scroll_offset_.x() > 0)\n              : (scroll_offset_.x() <\n                 static_cast<RenderScroll*>(render_object())->MaxScrollWidth());\n      if (has_space_to_scroll) {\n        page_view()->GetTaskRunner()->PostDelayedTask(\n            [self = weak_factory_.GetWeakPtr()]() {\n              if (self) {\n                self->StartSmoothScroll();\n              }\n            },\n            fml::TimeDelta::FromMilliseconds(16));\n      } else {\n        auto_scroll_ = false;\n      }\n    }\n  }\n}\n\nvoid ScrollView::StartScrollInto(BaseView* node, std::string block,\n                                 std::string inline_mode,\n                                 std::string behavior) {\n  auto* render_scroll = static_cast<RenderScroll*>(render_object_.get());\n  float scroll_offset = 0;\n  bool is_smooth = false;\n  if (behavior == \"smooth\") {\n    is_smooth = true;\n  }\n  if (CanScrollY()) {\n    scroll_offset +=\n        node->BoundsRelativeTo(this).location().y() + GetScrollOffset().y();\n    if (block == \"center\") {\n      scroll_offset -= (Height() - node->Height()) / 2;\n    } else if (block == \"end\") {\n      scroll_offset -= (Height() - node->Height());\n    }\n    scroll_offset = std::max(\n        0.f, std::min<float>(scroll_offset, render_scroll->MaxScrollHeight()));\n  } else {\n    scroll_offset +=\n        (IsRtlDirection() ? node->BoundsRelativeTo(this).MaxX() - Width()\n                          : node->BoundsRelativeTo(this).x()) +\n        GetScrollOffset().x();\n    float width_delta = Width() - node->Width();\n    if (inline_mode == \"center\") {\n      scroll_offset -=\n          (IsRtlDirection() ? (-width_delta / 2) : width_delta / 2);\n    } else if (inline_mode == \"end\") {\n      scroll_offset -= (IsRtlDirection() ? -width_delta : width_delta);\n    }\n    scroll_offset = std::max(\n        0.f, std::min<float>(scroll_offset, render_scroll->MaxScrollWidth()));\n  }\n  ScrollTo(is_smooth, scroll_offset);\n}\n\nvoid ScrollView::CorrectScrollOffset() {\n  RenderScroll* scroll = static_cast<RenderScroll*>(render_object_.get());\n  if (scroll_direction_ == ScrollDirection::kVertical) {\n    if (scroll_offset_.y() > scroll->MaxScrollHeight()) {\n      OnScrollUpdate(scroll->MaxScrollHeight());\n    }\n  } else {\n    if (scroll_offset_.x() > scroll->MaxScrollWidth()) {\n      OnScrollUpdate(scroll->MaxScrollWidth());\n    }\n  }\n}\n\nvoid ScrollView::EnableSticky() {\n  enable_sticky_ = true;\n  // This function will only be called when the sticky is updated\n  HandleSticky();\n}\n\nvoid ScrollView::HandleSticky() {\n  int left = scroll_offset_.x();\n  int top = scroll_offset_.y();\n  for (auto* child : children_) {\n    child->CheckStickyOnParentScrollAndReset(left, top);\n  }\n}\n\n#ifndef NDEBUG\nstd::string ScrollView::ToString() const {\n  std::stringstream ss;\n  ss << BaseView::ToString();\n  ss << \" direction=\" << static_cast<uint8_t>(scroll_direction_);\n  ss << \" scroll_offset=\" << GetScrollOffset().ToString();\n  ss << \" content_size_=\" << content_size_.ToString();\n  return ss.str();\n}\n#endif\n\nNestedScrollable* ScrollView::NextScrollable() {\n  return FindScrollableById(next_scrollable_)\n             ?: NestedScrollable::NextScrollable();\n}\n\nNestedScrollable* ScrollView::PreviousScrollable() {\n  return FindScrollableById(pre_scrollable_)\n             ?: NestedScrollable::PreviousScrollable();\n}\n\nNestedScrollable* ScrollView::FindScrollableById(const std::string& id) {\n  if (!id.empty()) {\n    auto view = ViewContext::FindViewByIdSelector(id, page_view());\n    return NestedScrollable::GetScrollable(view);\n  }\n  return nullptr;\n}\n\nbool ScrollView::CanInvokeScrollImmediately() const {\n  // This logic is copied from Lynx UIScrollView.canInvokeScrollImmediately().\n  int view_size = 0;\n  int content_size = 0;\n  if (scroll_direction_ == ScrollDirection::kVertical) {\n    view_size = Height();\n    content_size = GetRenderScroll()->OverflowRect().height();\n    return view_size != 0 &&\n           content_size > GetRenderScroll()->PaddingBoxRect().height();\n  } else {\n    view_size = Width();\n    content_size = GetRenderScroll()->OverflowRect().width();\n    return view_size != 0 &&\n           content_size > GetRenderScroll()->PaddingBoxRect().width();\n  }\n}\n\nFloatPoint ScrollView::DoScroll(FloatPoint delta, bool by_user_input,\n                                bool ignore_repaint) {\n  auto point = NestedScrollable::DoScroll(delta, by_user_input, ignore_repaint);\n  OnViewPostionUpdate(FloatPoint(0, 0));\n  return point;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/scroll_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SCROLL_VIEW_H_\n#define CLAY_UI_COMPONENT_SCROLL_VIEW_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/component/bounce_view/bounce_view.h\"\n#include \"clay/ui/component/nested_scroll/nested_scrollable.h\"\n#include \"clay/ui/component/scroller.h\"\n#include \"clay/ui/component/view_callback/scroll_event_callback_manager.h\"\n#include \"clay/ui/rendering/render_scroll.h\"\n\nnamespace clay {\n\nclass ScrollView : public WithTypeInfo<ScrollView, NestedScrollable>,\n                   public Scroller::Delegate {\n public:\n  class Delegate {\n   public:\n    virtual void OnScrollViewChildAdded() = 0;\n    virtual void OnScrollViewChildRemoved() = 0;\n    virtual void OnScrollViewChildUpdated() = 0;\n  };\n  ScrollView(int32_t id, PageView* page_view);\n  ScrollView(int32_t id, ScrollDirection direction, PageView* page_view,\n             std::unique_ptr<RenderScroll> render_scroll =\n                 std::make_unique<RenderScroll>());\n  ScrollView(int32_t id, int32_t callback_id, ScrollDirection direction,\n             PageView* page_view,\n             std::unique_ptr<RenderScroll> render_scroll =\n                 std::make_unique<RenderScroll>());\n  ScrollView(int32_t id, int32_t callback_id, ScrollDirection direction,\n             PageView* page_view,\n             std::unique_ptr<ScrollEventCallbackManager> callback_manager,\n             std::unique_ptr<RenderScroll> render_scroll);\n\n  ~ScrollView() override;\n\n  int GetCallbackId() override { return callback_id_; }\n  void AddChild(BaseView* child, int index) override;\n  void RemoveChild(BaseView* child) override;\n\n  void OnLayoutUpdated() override;\n  void OnLayout(LayoutContext* context) override;\n\n  bool CanChildrenEscape() const override { return false; }\n\n  FloatPoint GetScrollOffsetForPaint() const override {\n    return GetRenderScroll()->GetPaintOffsetForScroll();\n  }\n\n  bool OnScrollToVisible() override;\n  bool OnScrollToMiddle(BaseView* target_view) override;\n\n  void DoScrollFromRaster(float scroll_offset, bool ignore_repaint);\n  // Scroller::Delegate\n  void OnScrollUpdate(float scroll_offset) override;\n  void OnScrollAnimationStart(float start, float delta, int duration) override;\n  void OnScrollAnimationEnd() override;\n\n  void StopAnimation() override;\n\n  NestedScrollable* NextScrollable() override;\n  NestedScrollable* PreviousScrollable() override;\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  void SetDirection(int type) override;\n  void AddEventCallback(const char* event) override;\n\n  // content width/height in scroll_view include invisible part\n  float ContentWidth() const override;\n  float ContentHeight() const override;\n\n  const FloatRect& OverflowRect() const;\n\n  void OnScrollBegin();\n  void OnScrollEnd();\n  // bool OnFling(const FloatSize& distance);\n  bool CanScroll();\n\n  void OnScrollStatusChange(ScrollStatus old_status) override;\n\n  void OnChildSizeChanged(BaseView* child) override;\n  void OnContentSizeChanged(const FloatRect& old_rect,\n                            const FloatRect& new_rect) override;\n\n  // When offset and index are both set, the effect stack\n  void ScrollTo(bool smooth, float offset, int index = -1);\n  void ScrollWithDelta(bool smooth, float delta);\n  void SetScrollToIndex(int index);\n  void SetScrollToId(const std::string& id_str, bool smooth = true);\n  void AutoScroll(bool start, float rate);\n  void StartScrollInto(BaseView* node, std::string block,\n                       std::string inline_mode, std::string behavior);\n  void DidScroll() override;\n  // Return scroll offset including overscroll\n  FloatPoint TotalScrollOffset();\n\n  void CorrectScrollOffset();\n\n  void HandleSticky();\n  void EnableSticky();\n  Scroller* GetScroller() { return scroller_.get(); }\n\n#ifdef ENABLE_ACCESSIBILITY\n  int32_t GetSemanticsActions() const override;\n  int32_t GetSemanticsFlags() const override;\n  int32_t GetA11yScrollChildren() const override;\n#endif\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n  FloatPoint DoScroll(FloatPoint delta, bool by_user_input = true,\n                      bool ignore_repaint = false) override;\n\n protected:\n  virtual void CalculateOverFlow();\n\n  void ResetScroll() override { OnScrollUpdate(0); }\n\n  std::unique_ptr<ScrollEventCallbackManager> callback_manager_;\n\n private:\n  static constexpr float kDefaultThreshold = 0.f;\n  friend class ScrollWrapper;\n  friend class ListContainerWrapper;\n\n  void InitScrollView();\n  void SetDelegate(Delegate* delegate) { delegate_ = delegate; }\n  void OnBounceStart(float start_offset);\n  void NotifyContentSizeChanged(const FloatSize& new_size);\n  void StartSmoothScroll();\n\n  void ScrollChildViewToVisible(const FloatRect&) override;\n  void ScrollChildViewToMiddle(const FloatRect& rect);\n  void Invalidate() override;\n\n  NestedScrollable* FindScrollableById(const std::string& id);\n\n  bool CanInvokeScrollImmediately() const;\n\n  void UpdateScrollOffsetForRTL();\n\n  bool enable_content_size_changed_event_ = false;\n\n  // all content include invisible child\n  FloatSize content_size_;\n  FloatSize old_content_size_;\n\n  float upper_threshold_ = kDefaultThreshold;\n  float lower_threshold_ = kDefaultThreshold;\n\n  // view id that used as callback arguments. This should be the id of the\n  // wrapper view. With callback_id_, Lynx will think the callback is sent from\n  // the wrapper (which is known by Lynx) instead of the actual list view.\n  int32_t callback_id_ = -1;\n\n  BaseView* parent_scroll_view_ = nullptr;\n\n  ScrollEventCallbackManager::ScrollState last_callback_scroll_state_ =\n      ScrollEventCallbackManager::ScrollState::kIdle;\n\n  BounceView* bounce_view_ = nullptr;\n  bool auto_scroll_ = false;\n  int auto_scroll_rate_;\n  std::optional<int> pending_scroll_offset_ = std::nullopt;\n  int pending_scroll_index_ = -1;\n  fml::WeakPtrFactory<ScrollView> weak_factory_;\n  Delegate* delegate_ = nullptr;\n  bool initial_scroll_index_set_ = false;\n  bool initial_scroll_offset_set_ = false;\n  bool enable_sticky_ = false;\n  // TODO(liuguoliang): This can be moved to Scrollable and reused by other\n  // scrollable components.\n  std::unique_ptr<Scroller> scroller_;\n\n  FloatPoint last_scroll_offset_{};\n\n  std::string next_scrollable_;\n  std::string pre_scrollable_;\n\n  FRIEND_TEST(ScrollViewTest, NestedScrollOnPC);\n  FRIEND_TEST(ScrollViewTest, NestedScrollGestureOnPC);\n  FRIEND_TEST(ScrollViewTest, ScrollToId);\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_SCROLL_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/scroll_view_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define CLAY_UNIT_TESTS 1\n\n#include <algorithm>\n#include <memory>\n\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/scroll_wrapper.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/rendering/render_scroll.h\"\n#include \"clay/ui/testing/ui_test.h\"\n\nnamespace clay {\n\nusing ::testing::_;\nusing ::testing::AtLeast;\nusing ::testing::Pair;\nusing ::testing::UnorderedElementsAre;\n\nclass ScrollViewTest : public UITest {\n protected:\n  void UISetUp() override {\n    outer_view_ = std::make_unique<ScrollView>(-1, ScrollDirection::kVertical,\n                                               page_.get());\n    page_->AddChild(outer_view_.get());\n    inner_view_ = std::make_unique<ScrollView>(-1, ScrollDirection::kHorizontal,\n                                               page_.get());\n    outer_view_->AddChild(inner_view_.get(), 0);\n    inner_view_->SetBound(0, 0, 300, 300);\n    inner_view_->SetEnableNestedScroll(true);\n    outer_view_->SetBound(0, 0, 100, 100);\n    outer_view_->SetEnableNestedScroll(true);\n    box_view_ =\n        std::make_unique<ScrollView>(-1, ScrollDirection::kNone, page_.get());\n    box_view_->SetScrollEnabled(false);\n    box_view_->SetBound(0, 0, 600, 600);\n    inner_view_->AddChild(box_view_.get(), 0);\n  }\n\n  void UITearDown() override {\n    outer_view_.reset();\n    inner_view_.reset();\n    box_view_.reset();\n  }\n\n  std::unique_ptr<ScrollView> outer_view_;\n  std::unique_ptr<ScrollView> inner_view_;\n  std::unique_ptr<ScrollView> box_view_;\n};\n\nTEST_F_UI(ScrollViewTest, ScrollToId) {\n  std::unique_ptr<View> view_a = std::make_unique<View>(-1, page_.get());\n  view_a->SetBound(0, 0, 100, 100);\n  std::unique_ptr<View> view_b = std::make_unique<View>(-1, page_.get());\n  view_b->SetBound(0, 100, 100, 100);\n  view_b->SetComponentName(\"view_b\");\n  std::unique_ptr<View> view_c = std::make_unique<View>(-1, page_.get());\n  view_c->SetBound(0, 200, 100, 100);\n  view_c->SetComponentName(\"view_c\");\n  outer_view_->AddChild(view_a.get(), 1);\n  outer_view_->AddChild(view_b.get(), 2);\n  outer_view_->AddChild(view_c.get(), 3);\n  outer_view_->OnLayoutUpdated();\n  outer_view_->SetScrollToId(\"view_c\", false);\n  EXPECT_EQ(200, outer_view_->GetScrollOffset().y());\n  view_a.reset();\n  view_b.reset();\n  view_c.reset();\n}\n\nTEST_F_UI(ScrollViewTest, NestedScrollGestureOnPC) {\n  outer_view_->need_scroll_animation_ = false;\n  inner_view_->need_scroll_animation_ = false;\n  outer_view_->OnLayoutUpdated();\n  inner_view_->OnLayoutUpdated();\n  auto inner_offset = inner_view_->GetScrollOffset();\n  auto outer_offset = outer_view_->GetScrollOffset();\n  // the main movement on y axis, so outer will moved\n  EXPECT_EQ(0.0, inner_offset.x());\n  EXPECT_EQ(0.0, outer_offset.y());\n  inner_view_->HandleEvent(PointerEvent(PointerEvent::EventType::kSignalEvent));\n  outer_view_->HandleEvent(PointerEvent(PointerEvent::EventType::kSignalEvent));\n  DispatchTouchPadEvent({0, 0}, {50, 100}, 5);\n\n  inner_offset = inner_view_->GetScrollOffset();\n  outer_offset = outer_view_->GetScrollOffset();\n  EXPECT_EQ(0.0, inner_offset.x());\n  EXPECT_EQ(100.0, outer_offset.y());\n\n  // move within 500ms continue with prev move\n  DispatchTouchPadEvent({0, 0}, {50, 10}, 5);\n  inner_offset = inner_view_->GetScrollOffset();\n  outer_offset = outer_view_->GetScrollOffset();\n  EXPECT_EQ(0.0, inner_offset.x());\n  EXPECT_EQ(110.0, outer_offset.y());\n\n  // simulate 500ms passed and start a new scroll.\n  page_->gesture_manager()->EndMouseWheelTransactionByForce();\n  DispatchTouchPadEvent({0, 0}, {50, 10}, 5);\n  inner_offset = inner_view_->GetScrollOffset();\n  outer_offset = outer_view_->GetScrollOffset();\n  EXPECT_EQ(50.0, inner_offset.x());\n  EXPECT_EQ(110.0, outer_offset.y());\n}\n\nTEST_F_UI(ScrollViewTest, ScrollIntoView) {\n  auto scroll_view =\n      std::make_unique<ScrollView>(-1, ScrollDirection::kVertical, page_.get());\n  scroll_view->SetBound(0, 0, 300, 100);\n  scroll_view->SetScrollDirection(ScrollDirection::kHorizontal);\n  scroll_view->SetPaddings(50, 0, 50, 0);\n\n  auto view1 = std::make_unique<View>(-1, page_.get());\n  view1->SetBound(50, 0, 500, 100);\n  scroll_view->AddChild(view1.get(), 0);\n\n  auto view2 = std::make_unique<View>(-1, page_.get());\n  view2->SetBound(450, 0, 100, 100);\n  scroll_view->AddChild(view2.get(), 0);\n\n  scroll_view->OnLayoutUpdated();\n\n  auto* render_scroll =\n      static_cast<RenderScroll*>(scroll_view->render_object());\n  EXPECT_EQ(render_scroll->MaxScrollWidth(), 300);\n  EXPECT_EQ(view1->BoundsRelativeTo(scroll_view.get()).location().x(), 50);\n  scroll_view->StartScrollInto(view1.get(), \"\", \"start\", \"\");\n  EXPECT_EQ(view1->BoundsRelativeTo(scroll_view.get()).location().x(), 0);\n  scroll_view->StartScrollInto(view1.get(), \"\", \"end\", \"\");\n  EXPECT_EQ(view1->BoundsRelativeTo(scroll_view.get()).location().x(), -200);\n  scroll_view->StartScrollInto(view1.get(), \"\", \"center\", \"\");\n  EXPECT_EQ(view1->BoundsRelativeTo(scroll_view.get()).location().x(), -100);\n\n  scroll_view->StartScrollInto(view2.get(), \"\", \"end\", \"\");\n  EXPECT_EQ(view2->BoundsRelativeTo(scroll_view.get()).location().x(), 200);\n  scroll_view->StartScrollInto(view2.get(), \"\", \"center\", \"\");\n  // This view is not centered because of reaching the max scroll width.\n  EXPECT_EQ(view2->BoundsRelativeTo(scroll_view.get()).location().x(), 150);\n}\n\nTEST_F_UI(ScrollViewTest, AppendChild) {\n  auto scroll_view = std::make_unique<ScrollWrapper>(\n      -1, ScrollDirection::kVertical, page_.get());\n\n  auto view0 = std::make_unique<View>(-1, page_.get());\n  scroll_view->AddChild(view0.get());\n\n  auto view1 = std::make_unique<View>(-1, page_.get());\n  scroll_view->AddChild(view1.get());\n\n  auto view2 = std::make_unique<View>(-1, page_.get());\n  scroll_view->AddChild(view2.get());\n\n  auto inner = scroll_view->GetChildren()[0];\n  EXPECT_EQ(inner->GetChildren()[0], view0.get());\n  EXPECT_EQ(inner->GetChildren()[1], view1.get());\n  EXPECT_EQ(inner->GetChildren()[2], view2.get());\n}\n\nTEST_F_UI(ScrollViewTest, ScrollTop) {\n  auto scroll_view = std::make_unique<ScrollView>(\n      -1, ScrollDirection::kHorizontal, page_.get());\n  scroll_view->SetBound(0, 0, 300, 100);\n  page_->AddChild(scroll_view.get());\n\n  auto view0 = std::make_unique<View>(-1, page_.get());\n  view0->SetBound(0, 0, 500, 100);\n  scroll_view->AddChild(view0.get(), 0);\n\n  auto view1 = std::make_unique<View>(-1, page_.get());\n  view1->SetBound(0, 100, 500, 100);\n  scroll_view->AddChild(view1.get(), 1);\n\n  auto view2 = std::make_unique<View>(-1, page_.get());\n  view2->SetBound(0, 200, 500, 100);\n  scroll_view->AddChild(view2.get(), 2);\n\n  scroll_view->SetAttribute(\"scroll-top\", clay::Value(100));\n  scroll_view->SetAttribute(\"scroll-x\", clay::Value(false));\n  scroll_view->SetAttribute(\"scroll-y\", clay::Value(true));\n\n  scroll_view->OnLayoutUpdated();\n  Layout();\n\n  EXPECT_EQ(100, scroll_view->GetScrollOffset().y());\n}\n\nTEST_F_UI(ScrollViewTest, ScrollToIndex) {\n  auto scroll_view = std::make_unique<ScrollView>(\n      -1, ScrollDirection::kHorizontal, page_.get());\n  scroll_view->SetBound(0, 0, 300, 100);\n  page_->AddChild(scroll_view.get());\n\n  auto view0 = std::make_unique<View>(-1, page_.get());\n  view0->SetBound(0, 0, 500, 100);\n  scroll_view->AddChild(view0.get(), 0);\n\n  auto view1 = std::make_unique<View>(-1, page_.get());\n  view1->SetBound(0, 100, 500, 100);\n  scroll_view->AddChild(view1.get(), 1);\n\n  auto view2 = std::make_unique<View>(-1, page_.get());\n  view2->SetBound(0, 200, 500, 100);\n  scroll_view->AddChild(view2.get(), 2);\n\n  scroll_view->SetAttribute(\"scroll-to-index\", clay::Value(2));\n  scroll_view->SetAttribute(\"scroll-x\", clay::Value(false));\n  scroll_view->SetAttribute(\"scroll-y\", clay::Value(true));\n\n  scroll_view->OnLayoutUpdated();\n  Layout();\n\n  EXPECT_EQ(200, scroll_view->GetScrollOffset().y());\n}\n\n// TODO(liuguoliang): Fix scrollWidth/scrollHeight and add test case\nTEST_F_UI(ScrollViewTest, ScrollEvent) {\n  auto scroll_view = std::make_unique<ScrollWrapper>(\n      -1, ScrollDirection::kVertical, page_.get());\n  scroll_view->GetScrollView()->need_scroll_animation_ = false;\n  scroll_view->SetBound(0, 0, 100, 100);\n  scroll_view->SetAttribute(\"scroll-y\", Value(true));\n  scroll_view->GetScrollView()->SetOverscrollEnabled(true);\n  scroll_view->AddEventCallback(event_attr::kEventScroll);\n  scroll_view->AddEventCallback(event_attr::kEventScrollEnd);\n  page_->AddChild(scroll_view.get());\n  auto content_view = std::make_unique<View>(-1, page_.get());\n  content_view->SetBound(0, 0, 100, 500);\n  scroll_view->AddChild(content_view.get());\n\n  // By attribute\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(\n      *this,\n      OnCustomEvent(\n          event_attr::kEventScroll,\n          UnorderedElementsAre(\n              Pair(\"deltaX\", Value(0.f)), Pair(\"deltaY\", Value(200.f)),\n              Pair(\"scrollLeft\", Value(0.f)), Pair(\"scrollTop\", Value(200.f)),\n              Pair(\"scrollWidth\", Value(100.f)), Pair(\"scrollHeight\", _),\n              Pair(\"isDragging\", _))))\n      .Times(1);\n\n  scroll_view->SetAttribute(\"scroll-top\", Value(200));\n\n  // By dragging\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScroll, _))\n      .Times(::testing::AtLeast(5));\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollEnd, _)).Times(1);\n  DispatchDragEvent({50, 50}, {50, 100}, false);\n\n  // By fling\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScroll, _))\n      .Times(::testing::AtLeast(5));\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollEnd, _)).Times(0);\n  DispatchDragEvent({50, 50}, {50, 100}, true);\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScroll, _))\n      .Times(AtLeast(2));\n  EXPECT_CALL(\n      *this,\n      OnCustomEvent(event_attr::kEventScrollEnd,\n                    UnorderedElementsAre(\n                        Pair(\"deltaX\", _), Pair(\"deltaY\", _),\n                        Pair(\"scrollLeft\", Value(0.f)), Pair(\"scrollTop\", _),\n                        Pair(\"scrollWidth\", Value(100.f)),\n                        Pair(\"scrollHeight\", _), Pair(\"isDragging\", _))))\n      .Times(1);\n  DoAnimation(10);\n  DoAnimation(1000);\n  EXPECT_GT(scroll_view->GetScrollView()->GetScrollOffset().y(), 0);\n\n  // By bounce\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  scroll_view->SetAttribute(\"scroll-top\", Value(0));\n  EXPECT_EQ(scroll_view->GetScrollView()->GetScrollOffset().y(), 0);\n  DispatchDragEvent({50, 50}, {50, 100});\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScroll, _))\n      .Times(AtLeast(2));\n  EXPECT_CALL(\n      *this, OnCustomEvent(event_attr::kEventScrollEnd,\n                           UnorderedElementsAre(\n                               Pair(\"deltaX\", _), Pair(\"deltaY\", _),\n                               Pair(\"scrollLeft\", Value(0.f)),\n                               Pair(\"scrollTop\", Value(0.f)),\n                               Pair(\"scrollWidth\", Value(100.f)),\n                               Pair(\"scrollHeight\", _), Pair(\"isDragging\", _))))\n      .Times(1);\n  ResetAnimationTime();\n  DoAnimation(10);\n  DoAnimation(1000);\n  EXPECT_EQ(scroll_view->GetScrollView()->GetScrollOffset().y(), 0);\n\n  // By fling and bounce\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  DispatchDragEvent({50, 50}, {50, -200}, true, 10, 1);\n  EXPECT_GT(scroll_view->GetScrollView()->GetScrollOffset().y(), 200);\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScroll, _))\n      .Times(AtLeast(2));\n  EXPECT_CALL(\n      *this, OnCustomEvent(event_attr::kEventScrollEnd,\n                           UnorderedElementsAre(\n                               Pair(\"deltaX\", _), Pair(\"deltaY\", _),\n                               Pair(\"scrollLeft\", Value(0.f)),\n                               Pair(\"scrollTop\", Value(400.f)),\n                               Pair(\"scrollWidth\", Value(100.f)),\n                               Pair(\"scrollHeight\", _), Pair(\"isDragging\", _))))\n      .Times(1);\n  ResetAnimationTime();\n  DoAnimation(10);\n  DoAnimation(1000);\n  DoAnimation(1000);\n  EXPECT_EQ(scroll_view->GetScrollView()->GetScrollOffset().y(), 400);\n\n  // By signal\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  scroll_view->SetAttribute(\"scroll-top\", Value(0));\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScroll, _)).Times(5);\n  EXPECT_CALL(\n      *this, OnCustomEvent(event_attr::kEventScrollEnd,\n                           UnorderedElementsAre(\n                               Pair(\"deltaX\", _), Pair(\"deltaY\", _),\n                               Pair(\"scrollLeft\", Value(0.f)),\n                               Pair(\"scrollTop\", Value(50.f)),\n                               Pair(\"scrollWidth\", Value(100.f)),\n                               Pair(\"scrollHeight\", _), Pair(\"isDragging\", _))))\n      .Times(1);\n  DispatchTouchPadEvent({50, 50}, {50, 100}, 5);\n  // Ensure to immediately fire the end signal event for `scrollend`.\n  page_->gesture_manager()->EndMouseWheelTransactionByForce();\n\n  // By scrollTo (smooth: false)\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(\n      *this,\n      OnCustomEvent(\n          event_attr::kEventScroll,\n          UnorderedElementsAre(\n              Pair(\"deltaX\", Value(0.f)), Pair(\"deltaY\", Value(-50.f)),\n              Pair(\"scrollLeft\", Value(0.f)), Pair(\"scrollTop\", Value(0.f)),\n              Pair(\"scrollWidth\", Value(100.f)), Pair(\"scrollHeight\", _),\n              Pair(\"isDragging\", _))))\n      .Times(1);\n  scroll_view->scrollTo(CreateLynxModuleValues({\"offset\"}, {Value(0)}));\n\n  // By scrollTo (smooth: true)\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScroll, _))\n      .Times(AtLeast(2));\n  EXPECT_CALL(\n      *this, OnCustomEvent(event_attr::kEventScrollEnd,\n                           UnorderedElementsAre(\n                               Pair(\"deltaX\", _), Pair(\"deltaY\", _),\n                               Pair(\"scrollLeft\", Value(0.f)),\n                               Pair(\"scrollTop\", Value(100.f)),\n                               Pair(\"scrollWidth\", Value(100.f)),\n                               Pair(\"scrollHeight\", _), Pair(\"isDragging\", _))))\n      .Times(1);\n  scroll_view->scrollTo(\n      CreateLynxModuleValues({\"offset\", \"smooth\"}, {Value(100), Value(true)}));\n  ResetAnimationTime();\n  DoAnimation(10);\n  DoAnimation(1000);\n}\n\nTEST_F_UI(ScrollViewTest, ScrollToUpperLowerEvent) {\n  auto scroll_view = std::make_unique<ScrollWrapper>(\n      -1, ScrollDirection::kVertical, page_.get());\n  scroll_view->SetBound(0, 0, 100, 100);\n  scroll_view->SetAttribute(\"scroll-y\", Value(true));\n  scroll_view->SetAttribute(\"lower-threshold\", Value(50));\n  scroll_view->SetAttribute(\"upper-threshold\", Value(50));\n  scroll_view->AddEventCallback(event_attr::kEventScrollToUpper);\n  scroll_view->AddEventCallback(event_attr::kEventScrollToLower);\n  page_->AddChild(scroll_view.get());\n  auto content_view = std::make_unique<View>(-1, page_.get());\n  content_view->SetBound(0, 0, 100, 500);\n  scroll_view->AddChild(content_view.get());\n\n  // To lower\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollToLower, _))\n      .Times(1);\n  scroll_view->SetAttribute(\"scroll-top\", Value(360));\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollToLower, _))\n      .Times(0);\n  scroll_view->SetAttribute(\"scroll-top\", Value(380));\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollToLower, _))\n      .Times(1);\n  scroll_view->SetAttribute(\"scroll-top\", Value(340));\n  scroll_view->SetAttribute(\"scroll-top\", Value(380));\n\n  // To upper\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollToUpper, _))\n      .Times(1);\n  scroll_view->SetAttribute(\"scroll-top\", Value(40));\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollToUpper, _))\n      .Times(0);\n  scroll_view->SetAttribute(\"scroll-top\", Value(20));\n  ::testing::Mock::VerifyAndClearExpectations(this);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollToUpper, _))\n      .Times(1);\n  scroll_view->SetAttribute(\"scroll-top\", Value(60));\n  scroll_view->SetAttribute(\"scroll-top\", Value(20));\n}\n\nTEST_F_UI(ScrollViewTest, BounceView) {\n  auto scroll_view = std::make_unique<ScrollWrapper>(\n      -1, ScrollDirection::kHorizontal, page_.get());\n  scroll_view->SetBound(0, 0, 100, 100);\n  scroll_view->SetAttribute(\"bounce\", Value(true));\n  scroll_view->AddEventCallback(event_attr::kEventScrollToBounce);\n\n  auto content_view = std::make_unique<View>(-1, page_.get());\n  content_view->SetBound(0, 0, 200, 100);\n  scroll_view->AddChild(content_view.get());\n\n  auto bounce_view = std::make_unique<BounceView>(-1, page_.get());\n  bounce_view->SetBound(0, 0, 20, 20);\n  bounce_view->SetAttribute(\"direction\", Value(\"right\"));\n  scroll_view->AddChild(bounce_view.get());\n\n  page_->AddChild(scroll_view.get());\n\n  Layout();\n\n  EXPECT_EQ(bounce_view->BoundsRelativeTo(nullptr).x(), 200);\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollToBounce, _))\n      .Times(0);\n\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kDownEvent, {50, 50})});\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kMoveEvent, {50, 50})});\n  page_->DispatchPointerEvent({CreatePointer(\n      0, PointerEvent::EventType::kMoveEvent, {-150, 50}, {-200, 0})});\n  EXPECT_LT(bounce_view->BoundsRelativeTo(nullptr).x(), 80);\n  EXPECT_GT(bounce_view->BoundsRelativeTo(nullptr).x(), 20);\n  ::testing::Mock::VerifyAndClearExpectations(this);\n\n  // scrolltobounce should be called after touch up\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollToBounce, _))\n      .Times(1);\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kUpEvent, {-150, 50})});\n  ::testing::Mock::VerifyAndClearExpectations(this);\n\n  DoAnimation();\n  DispatchDragEvent({50, 50}, {200, 50}, false);\n  // Fling should not trigger scrolltobounce event\n  EXPECT_CALL(*this, OnCustomEvent(event_attr::kEventScrollToBounce, _))\n      .Times(0);\n\n  DoAnimation();\n\n  DispatchDragEvent({50, 50}, {-40, 50}, true, 5, 5);\n  float max_scroll_offset = 0;\n  ResetAnimationTime();\n  for (int i = 0; i < 50; i++) {\n    DoAnimation(16);\n    max_scroll_offset =\n        std::max(max_scroll_offset,\n                 scroll_view->GetScrollView()->TotalScrollOffset().x());\n  }\n  EXPECT_GT(max_scroll_offset, 120);\n  EXPECT_EQ(scroll_view->GetScrollView()->TotalScrollOffset().x(), 100);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/scroll_wrapper.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/scroll_wrapper.h\"\n\n#include <math.h>\n\n#include <cmath>\n#include <string>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n\nnamespace clay {\n\nnamespace {\n\n// Attributes that should be forwarded to the scroll view.\nconst std::unordered_set<KeywordID> kProxyAttributes = {\n    KeywordID::kScrollX,\n    KeywordID::kScrollY,\n    KeywordID::kLowerThreshold,\n    KeywordID::kUpperThreshold,\n    KeywordID::kEnableScroll,\n    KeywordID::kEnableNestedScroll,\n    KeywordID::kScrollMonitorTag,\n    KeywordID::kScrollTop,\n    KeywordID::kScrollLeft,\n    KeywordID::kScrollToIndex,\n    KeywordID::kInitialScrollOffset,\n    KeywordID::kInitialScrollIndex,\n    KeywordID::kScrollOrientation,\n    KeywordID::kScrollForwardMode,\n    KeywordID::kScrollBackwardMode,\n    KeywordID::kPrevScrollable,\n    KeywordID::kNextScrollable,\n    KeywordID::kBounce,\n    KeywordID::kBounces,\n    KeywordID::kScrollToId,\n    KeywordID::kScrollEventThrottle};\nconstexpr char kScrollWrapperTag[] = \"scroll-view\";\n\nLYNX_UI_METHOD_BEGIN(ScrollWrapper) {\n  LYNX_UI_METHOD(ScrollWrapper, scrollTo);\n  LYNX_UI_METHOD(ScrollWrapper, autoScroll);\n  LYNX_UI_METHOD(ScrollWrapper, getScrollInfo);\n}\nLYNX_UI_METHOD_END(ScrollWrapper);\n\nconstexpr char kArgOffset[] = \"offset\";\nconstexpr char kArgSmooth[] = \"smooth\";\nconstexpr char kArgIndex[] = \"index\";\nconstexpr char kArgStart[] = \"start\";\nconstexpr char kArgRate[] = \"rate\";\nconst std::vector<std::string> kScrollToArgs{kArgSmooth, kArgOffset, kArgIndex};\nconst std::vector<std::string> kAutoScrollArgs{kArgStart, kArgRate};\n\n}  // namespace\n\nScrollWrapper::ScrollWrapper(int id, PageView* page_view)\n    : ScrollWrapper(id, ScrollDirection::kVertical, page_view) {}\n\nScrollWrapper::ScrollWrapper(int id, ScrollDirection direction,\n                             PageView* page_view)\n    : WithTypeInfo(id, direction, kScrollWrapperTag, page_view) {\n  view_ = new ScrollView(-1, id, direction, page_view);\n  // Set the real element id to RenderScroll.\n  view_->render_object()->SetID(id);\n  view_->SetOverflow(CSSProperty::OVERFLOW_HIDDEN);\n  view_->SetRepaintBoundary(true);\n  GetScrollView()->SetDelegate(this);\n  GetScrollView()->AddScrollListener(this);\n  BaseView::AddChild(view_, 0);\n}\n\nScrollWrapper::~ScrollWrapper() = default;\n\nvoid ScrollWrapper::OnDestroy() {\n  ScrollbarWrapper::OnDestroy();\n  GetScrollView()->SetDelegate(nullptr);\n  GetScrollView()->RemoveScrollListener(this);\n}\n\n#ifdef ENABLE_ACCESSIBILITY\nint32_t ScrollWrapper::GetSemanticsActions() const {\n  return GetScrollView()->GetSemanticsActions();\n}\n\nint32_t ScrollWrapper::GetSemanticsFlags() const {\n  return GetScrollView()->GetSemanticsFlags();\n}\n\nint32_t ScrollWrapper::GetA11yScrollChildren() const {\n  return GetScrollView()->GetA11yScrollChildren();\n}\n\nbool ScrollWrapper::IsAccessibilityElement() const {\n  bool result = BaseView::IsAccessibilityElement();\n  if (!result) {\n    for (auto child : GetScrollView()->children_) {\n      if (child->IsAccessibilityElement()) {\n        result = true;\n        break;\n      }\n    }\n  }\n  return result;\n}\n\nFloatRect ScrollWrapper::GetSemanticsBounds() const {\n  return GetScrollView()->GetSemanticsBounds();\n}\n#endif\n\nBaseView* ScrollWrapper::GetTopViewToAcceptEvent(\n    const FloatPoint& position, FloatPoint* relative_position) {\n  BaseView* view =\n      BaseView::GetTopViewToAcceptEvent(position, relative_position);\n  if (view == view_) {\n    return this;\n  }\n  return view;\n}\n\nvoid ScrollWrapper::scrollTo(const LynxModuleValues& args) {\n  bool smooth = false;\n  float offset = 0;\n  int index = -1;\n  if (CastNamedLynxModuleArgs(kScrollToArgs, args, smooth, offset, index)) {\n    if (isnan(offset) || isinf(offset)) {\n      FML_DLOG(ERROR) << \"Cannot scrollTo nan or infinite!\";\n      return;\n    }\n    GetScrollView()->ScrollTo(smooth, FromLogical(offset), index);\n  }\n}\n\nvoid ScrollWrapper::autoScroll(const LynxModuleValues& args) {\n  bool start = false;\n  float rate = 0;\n  if (CastNamedLynxModuleArgs(kAutoScrollArgs, args, start, rate)) {\n    if (isnan(rate) || isinf(rate)) {\n      FML_DLOG(ERROR) << \"rate cannot be nan or infinite!\";\n      return;\n    }\n    GetScrollView()->AutoScroll(start, FromLogical(rate));\n  }\n}\n\nvoid ScrollWrapper::getScrollInfo(const LynxModuleValues& args,\n                                  const LynxUIMethodCallback& callback) {\n  if (callback) {\n    FloatPoint offset = view_->GetScrollOffset();\n    FloatSize zoomed_content = page_view_->ConvertTo<kPixelTypeLogical>(\n        FloatSize(view_->ContentWidth(), view_->ContentHeight()));\n    FloatPoint zoomed_offset = page_view_->ConvertTo<kPixelTypeLogical>(offset);\n    clay::Value::Map map;\n    map.emplace(\"scrollTop\", zoomed_offset.y());\n    map.emplace(\"scrollLeft\", zoomed_offset.x());\n    map.emplace(\"scrollHeight\", zoomed_content.height());\n    map.emplace(\"scrollWidth\", zoomed_content.width());\n    map.emplace(\"isDragging\",\n                static_cast<ScrollView*>(view_)->GetScrollStatus() ==\n                    ScrollView::ScrollStatus::kDragging);\n    callback(LynxUIMethodResult::kSuccess, clay::Value(std::move(map)));\n  }\n}\n\nvoid ScrollWrapper::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kProxyAttributes.find(kw) != kProxyAttributes.end()) {\n    view_->SetAttribute(attr_c, value);\n    if (kw == KeywordID::kScrollX || kw == KeywordID::kScrollY) {\n      scrollbar_->SetScrollDirection(\n          static_cast<ScrollView*>(view_)->GetScrollDirection());\n    } else if (kw == KeywordID::kScrollOrientation) {\n      std::string orientation;\n      attribute_utils::TryGetString(value, orientation);\n      if (orientation == \"vertical\") {\n        scrollbar_->SetScrollDirection(ScrollDirection::kVertical);\n      } else if (orientation == \"horizontal\") {\n        scrollbar_->SetScrollDirection(ScrollDirection::kHorizontal);\n      }\n    }\n  } else {\n    ScrollbarWrapper::SetAttribute(attr_c, value);\n  }\n}\n\nvoid ScrollWrapper::SetDirection(int type) {\n  view_->SetDirection(type);\n  scrollbar_->SetDirection(type);\n}\n\nvoid ScrollWrapper::SetOverflow(int overflow) {\n  BaseView::SetOverflow(overflow);\n  view_->SetOverflow(overflow);\n}\n\nvoid ScrollWrapper::WillUpdateScrollbar() { view_->OnLayoutUpdated(); }\n\nfloat ScrollWrapper::GetScrollbarScrollOffset() {\n  return OrientationHelper().GetLocation(view_->GetScrollOffset());\n}\n\nfloat ScrollWrapper::GetTotalLength() {\n  return OrientationHelper().GetLength(GetScrollView()->OverflowRect());\n}\n\nvoid ScrollWrapper::OnScrollViewChildAdded() {\n  view_->OnLayoutUpdated();\n  if (scrollbar_enabled_) {\n    UpdateScrollbarLengths();\n    UpdateScrollbarPosition();\n  }\n}\n\nvoid ScrollWrapper::OnScrollViewChildRemoved() {\n  view_->OnLayoutUpdated();\n  if (scrollbar_enabled_) {\n    UpdateScrollbarLengths();\n    UpdateScrollbarPosition();\n  }\n}\n\nvoid ScrollWrapper::OnScrollViewChildUpdated() {\n  view_->OnLayoutUpdated();\n  if (scrollbar_enabled_) {\n    UpdateScrollbarLengths();\n    UpdateScrollbarPosition();\n  }\n}\n\nvoid ScrollWrapper::OnScrollableScrolled() {\n  UpdateScrollbarPosition();\n  scrollbar_->NotifyScrollViewScrolled();\n}\n\nvoid ScrollWrapper::OnScrollbarScrolled(float old_position, float new_position,\n                                        bool by_interaction, bool smooth) {\n  if (by_interaction) {\n    const float overflow_length = GetTotalLength();\n    const float self_length = OrientationHelper().GetContentLength(*this);\n\n    GetScrollView()->ScrollTo(smooth,\n                              (overflow_length - self_length) * new_position);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/scroll_wrapper.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SCROLL_WRAPPER_H_\n#define CLAY_UI_COMPONENT_SCROLL_WRAPPER_H_\n\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/scrollbar/scrollbar_wrapper.h\"\n#include \"clay/ui/lynx_module/types.h\"\n\nnamespace clay {\n\nclass ScrollWrapper : public WithTypeInfo<ScrollWrapper, ScrollbarWrapper>,\n                      public ScrollView::Delegate,\n                      public Scrollable::Listener {\n public:\n  ScrollWrapper(int id, PageView* page_view);\n  ScrollWrapper(int id, ScrollDirection direction, PageView* page_view);\n  ~ScrollWrapper() override;\n\n  // UI Method\n  void scrollTo(const LynxModuleValues& args);\n  void autoScroll(const LynxModuleValues& args);\n  void getScrollInfo(const LynxModuleValues& args,\n                     const LynxUIMethodCallback& callback);\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n\n  BaseView* GetTopViewToAcceptEvent(const FloatPoint& position,\n                                    FloatPoint* relative_position) override;\n\n  bool IsLayoutRootCandidate() const override { return true; }\n\n  void SetDirection(int type) override;\n\n  void SetOverflow(int overflow) override;\n\n#ifdef ENABLE_ACCESSIBILITY\n  int32_t GetSemanticsActions() const override;\n  int32_t GetSemanticsFlags() const override;\n  int32_t GetA11yScrollChildren() const override;\n  bool IsAccessibilityElement() const override;\n  FloatRect GetSemanticsBounds() const override;\n#endif\n\n  ScrollView* GetScrollView() const { return static_cast<ScrollView*>(view_); }\n\n private:\n  void OnDestroy() override;\n\n  // Override ScrollbarWrapper\n  void WillUpdateScrollbar() override;\n  float GetScrollbarScrollOffset() override;\n  float GetTotalLength() override;\n\n  // Override ScrollView::Delegate\n  void OnScrollViewChildAdded() override;\n  void OnScrollViewChildRemoved() override;\n  void OnScrollViewChildUpdated() override;\n\n  // Override Scrollable::Listener\n  void OnScrollableScrolled() override;\n\n  // Override ScrollbarView::Delegate\n  void OnScrollbarScrolled(float old_position, float new_position,\n                           bool by_interaction, bool smooth) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SCROLL_WRAPPER_H_\n"
  },
  {
    "path": "clay/ui/component/scrollable.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/scrollable.h\"\n\n#include <utility>\n\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/rubberband_distance.h\"\n#include \"clay/ui/rendering/render_scroll.h\"\n\nnamespace clay {\n\nScrollable::Scrollable(int id, std::string tag,\n                       std::unique_ptr<RenderScroll> render_object,\n                       PageView* page_view, ScrollDirection direction)\n    : WithTypeInfo(id, std::move(tag), std::move(render_object), page_view),\n      scroll_direction_(direction),\n      overscroll_effect_(\n          std::make_unique<OffsetOverscrollEffect>(GetRenderScroll())) {\n  GetRenderScroll()->SetScrollDirection(scroll_direction_);\n}\n\nvoid Scrollable::AddScrollListener(Listener* listener) {\n  if (!listener) {\n    return;\n  }\n  listeners_.push_front(listener);\n}\n\nvoid Scrollable::RemoveScrollListener(Listener* listener) {\n  if (!listener) {\n    return;\n  }\n  listeners_.remove(listener);\n}\n\nvoid Scrollable::RemoveAllScrollListener() { listeners_.clear(); }\n\nvoid Scrollable::NotifyScrolled() {\n  for (auto& listener : listeners_) {\n    listener->OnScrollableScrolled();\n  }\n}\n\nbool Scrollable::IsUnderOverscroll() const {\n  return !overscroll_offset_.IsOrigin();\n}\n\nFloatPoint Scrollable::DoOverscroll(FloatPoint delta) {\n  auto offset = overscroll_offset_;\n  if (delta.x() != 0) {\n    if (offset.x() * delta.x() >= 0 && !IsOverscrollEnabled(delta.x() > 0)) {\n      // The direction is not allowed to overscroll, do nothing.\n    } else {\n      float new_offset = offset.x() + delta.x();\n      if (new_offset * offset.x() < 0) {\n        // The direction of the overscroll has been altered, indicating that the\n        // former overscrolled offset is fully consumed. To maintain consistency\n        // in the scrolling behavior, the direction is kept unchanged, and the\n        // new_offset represents the exact amount of the overscroll offset that\n        // remains unconsumed.\n        delta.SetX(new_offset);\n        new_offset = 0;\n      } else {\n        delta.SetX(0);\n      }\n      offset.SetX(new_offset);\n    }\n  }\n  if (delta.y() != 0) {\n    if (offset.y() * delta.y() >= 0 && !IsOverscrollEnabled(delta.y() > 0)) {\n      // The direction is not allowed to overscroll, do nothing.\n    } else {\n      float new_offset = offset.y() + delta.y();\n      if (new_offset * offset.y() < 0) {\n        delta.SetY(new_offset);\n        new_offset = 0;\n      } else {\n        delta.SetY(0);\n      }\n      offset.SetY(new_offset);\n    }\n  }\n  SetOverscrollOffset(offset);\n  return delta;\n}\n\nvoid Scrollable::SetOverscrollOffset(FloatPoint offset) {\n  if (overscroll_offset_ != offset) {\n    auto old_offset = clamped_overscroll_offset_;\n    overscroll_offset_ = offset;\n    clamped_overscroll_offset_ = RubberBandDistance(offset, Width(), Height());\n    overscroll_effect_->OnOverscroll(clamped_overscroll_offset_, old_offset);\n    OnOverscroll(old_offset);\n  }\n}\n\nvoid Scrollable::SetClampedOverscrollOffset(FloatPoint offset) {\n  if (clamped_overscroll_offset_ != offset) {\n    auto old_offset = clamped_overscroll_offset_;\n    clamped_overscroll_offset_ = offset;\n    overscroll_offset_ =\n        RubberBandDistance(clamped_overscroll_offset_, Width(), Height());\n    overscroll_effect_->OnOverscroll(clamped_overscroll_offset_, old_offset);\n    OnOverscroll(old_offset);\n  }\n}\n\nvoid Scrollable::SetScrollStatus(ScrollStatus status) {\n  if (status_ != status) {\n    if (status == ScrollStatus::kIdle) {\n      page_view_->EndFluencyMonitor(GetCallbackId());\n    } else {\n      page_view_->StartFluencyMonitor(GetCallbackId(), \"scroll\",\n                                      scroll_monitor_tag_);\n    }\n\n    auto old_status = status_;\n    status_ = status;\n    OnScrollStatusChange(old_status);\n  }\n}\n\nbool Scrollable::DoBounce(float velocity) {\n  float start_offset = scroll_direction_ == ScrollDirection::kVertical\n                           ? ClampedOverscrollOffset().y()\n                           : ClampedOverscrollOffset().x();\n  if (start_offset == 0 && velocity == 0) {\n    return false;\n  }\n\n  bool is_lower = start_offset > 0 || (start_offset == 0 && velocity > 0);\n  if (!IsOverscrollEnabled(is_lower)) {\n    return false;\n  }\n  if (!bounce_animator_) {\n    bounce_animator_ = std::make_unique<BounceAnimator>();\n    bounce_animator_->SetAnimationHandler(page_view_->GetAnimationHandler());\n    bounce_animator_->AddListener(this);\n  } else {\n    bounce_animator_->Cancel();\n  }\n  bounce_animator_->SetStartVelocity(velocity);\n  bounce_animator_->SetStartValue(start_offset);\n  bounce_animator_->SetTargetValue(0);\n  bounce_animator_->Init();\n  bounce_animator_->Start();\n  return true;\n}\n\nvoid Scrollable::OnDynamicAnimationUpdate(DynamicAnimator& animation,\n                                          float value, float velocity) {\n  if (&animation == bounce_animator_.get()) {\n    FML_DCHECK(scroll_direction_ != ScrollDirection::kNone);\n    SetClampedOverscrollOffset(scroll_direction_ == ScrollDirection::kVertical\n                                   ? FloatPoint(0, value)\n                                   : FloatPoint(value, 0));\n  }\n}\n\nvoid Scrollable::OnDynamicAnimationEnd(DynamicAnimator& animation,\n                                       bool canceled, float value,\n                                       float velocity) {\n  if (&animation == bounce_animator_.get()) {\n    if (!canceled) {\n      // If canceled, the status is handled by the NestedScrollManager.\n      SetScrollStatus(ScrollStatus::kIdle);\n    }\n    OnBounceEnd(canceled);\n  }\n}\n\nvoid Scrollable::StopAnimation() {\n  if (bounce_animator_ && bounce_animator_->IsRunning()) {\n    bounce_animator_->Cancel();\n  }\n}\n\nvoid Scrollable::SetScrollDirection(ScrollDirection direction) {\n  if (scroll_direction_ != direction) {\n    ResetScroll();\n    scroll_direction_ = direction;\n    ResetGestureRecognizers();\n  }\n}\n\nScrollDirection Scrollable::GetScrollDirection() const {\n  return scroll_direction_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/scrollable.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SCROLLABLE_H_\n#define CLAY_UI_COMPONENT_SCROLLABLE_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/gfx/animation/bounce_animator.h\"\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/nested_scroll/overscroll_effect.h\"\n\nnamespace clay {\nclass Scrollable : public WithTypeInfo<Scrollable, BaseView>,\n                   public DynamicAnimator::AnimationListener {\n public:\n  class Listener {\n   public:\n    virtual void OnScrollableScrolled() = 0;\n  };\n\n  enum class ScrollStatus {\n    // The scrollable is still\n    kIdle,\n    // The scrollable is dragging by user's finger\n    kDragging,\n    // The scrollable is in fling animation\n    kFling,\n    // The scrollable is in bounce animation\n    kBounce,\n    // The scrollable is in other animations, such as smooth scrolling triggered\n    // by the `scrollTo` UI method\n    kAnimating,\n  };\n\n  Scrollable(int id, std::string tag,\n             std::unique_ptr<RenderScroll> render_object, PageView* page_view,\n             ScrollDirection direction);\n\n  virtual void SetScrollEnabled(bool enabled) { scroll_enabled_ = enabled; }\n  bool IsScrollEnabled() const { return scroll_enabled_; }\n  void SetUpperOverscrollEnabled(bool enabled) {\n    upper_overscroll_enabled_ = enabled;\n  }\n  void SetLowerOverscrollEnabled(bool enabled) {\n    lower_overscroll_enabled_ = enabled;\n  }\n  void SetOverscrollEnabled(bool enabled) {\n    SetUpperOverscrollEnabled(enabled);\n    SetLowerOverscrollEnabled(enabled);\n  }\n  bool IsOverscrollEnabled(bool is_lower) const {\n    return is_lower ? lower_overscroll_enabled_ : upper_overscroll_enabled_;\n  }\n  void AddScrollListener(Listener* listener);\n  void RemoveScrollListener(Listener* listener);\n  void RemoveAllScrollListener();\n  FloatPoint GetScrollOffset() const override { return scroll_offset_; }\n\n  bool IsUnderOverscroll() const;\n  FloatPoint DoOverscroll(FloatPoint delta);\n\n  void SetOverscrollOffset(FloatPoint offset);\n  void SetClampedOverscrollOffset(FloatPoint offset);\n  FloatPoint OverscrollOffset() const { return overscroll_offset_; }\n  FloatPoint ClampedOverscrollOffset() const {\n    return clamped_overscroll_offset_;\n  }\n  void SetScrollMonitorTag(const std::string& tag) {\n    scroll_monitor_tag_ = tag;\n  }\n  const std::string& ScrollMonitorTag() const { return scroll_monitor_tag_; }\n  RenderScroll* GetRenderScroll() const {\n    return static_cast<RenderScroll*>(render_object());\n  }\n\n  virtual void OnOverscroll(FloatPoint prev_overscroll_offset) {}\n\n  bool DoBounce(float velocity);\n\n  // Stop scroll animations\n  virtual void StopAnimation();\n\n  ScrollStatus GetScrollStatus() const { return status_; }\n\n  virtual void ResetScroll() {}\n  virtual void ResetGestureRecognizers() {}\n\n  void SetScrollDirection(ScrollDirection direction);\n  ScrollDirection GetScrollDirection() const;\n\n  ScrollableDirection GetScrollableDirection() const override {\n    if (!IsScrollEnabled()) {\n      return ScrollableDirection::kNone;\n    }\n    return GetRenderScroll()->GetScrollableDirection();\n  }\n\n protected:\n  void SetScrollStatus(ScrollStatus status);\n\n  virtual void OnScrollStatusChange(ScrollStatus old_status) {}\n  virtual void OnBounceEnd(bool canceled) {}\n\n  void NotifyScrolled();\n\n  FloatPoint scroll_offset_;\n  ScrollDirection scroll_direction_ = ScrollDirection::kVertical;\n  ScrollStatus status_ = Scrollable::ScrollStatus::kIdle;\n\n  // DynamicAnimator::AnimationListener\n  void OnDynamicAnimationUpdate(DynamicAnimator& animation, float value,\n                                float velocity) override;\n  void OnDynamicAnimationEnd(DynamicAnimator& animation, bool canceled,\n                             float value, float velocity) override;\n\n private:\n  std::forward_list<Listener*> listeners_;\n  bool scroll_enabled_ = true;\n  bool upper_overscroll_enabled_ = false;\n  bool lower_overscroll_enabled_ = false;\n  FloatPoint overscroll_offset_ = {0, 0};\n  std::string scroll_monitor_tag_;\n\n  // The difference between overscroll_offset_ and clamped_overscroll_offset_ is\n  // that overscroll_offset_ is used on dragging, and clamped_overscroll_offset_\n  // is used on bouncing animation. For dragging, rubber band effect is applied\n  // to the dragged offset. For bouncing animation, we directly animate the\n  // clamped_overscroll_offset_ to avoid the rubber band effect being applied.\n  FloatPoint clamped_overscroll_offset_ = {0, 0};\n  std::unique_ptr<OverscrollEffect> overscroll_effect_;\n\n  std::unique_ptr<BounceAnimator> bounce_animator_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SCROLLABLE_H_\n"
  },
  {
    "path": "clay/ui/component/scrollbar/scrollbar_orientation_helper.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/scrollbar/scrollbar_orientation_helper.h\"\n\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nScrollbarOrientationHelper::ScrollbarOrientationHelper() = default;\nScrollbarOrientationHelper::~ScrollbarOrientationHelper() = default;\n\nfloat ScrollbarOrientationHelper::GetLocation(const FloatPoint& point) const {\n  if (direction_ == ScrollDirection::kVertical) {\n    return point.y();\n  } else {\n    return point.x();\n  }\n}\n\nfloat ScrollbarOrientationHelper::GetLength(const FloatSize& delta) const {\n  if (direction_ == ScrollDirection::kVertical) {\n    return delta.height();\n  } else {\n    return delta.width();\n  }\n}\n\nfloat ScrollbarOrientationHelper::GetLength(const FloatRect& rect) const {\n  if (direction_ == ScrollDirection::kVertical) {\n    return rect.height();\n  } else {\n    return rect.width();\n  }\n}\n\nfloat ScrollbarOrientationHelper::GetLength(const BaseView& view) const {\n  if (direction_ == ScrollDirection::kVertical) {\n    return view.Height();\n  } else {\n    return view.Width();\n  }\n}\n\nfloat ScrollbarOrientationHelper::GetSecondaryLength(\n    const BaseView& view) const {\n  if (direction_ == ScrollDirection::kVertical) {\n    return view.Width();\n  } else {\n    return view.Height();\n  }\n}\n\nfloat ScrollbarOrientationHelper::GetContentLength(const BaseView& view) const {\n  if (direction_ == ScrollDirection::kVertical) {\n    return view.ContentHeight();\n  } else {\n    return view.ContentWidth();\n  }\n}\n\nfloat ScrollbarOrientationHelper::GetSecondaryContentLength(\n    const BaseView& view) const {\n  if (direction_ == ScrollDirection::kVertical) {\n    return view.ContentWidth();\n  } else {\n    return view.ContentHeight();\n  }\n}\n\nvoid ScrollbarOrientationHelper::SetBound(BaseView* view, float location,\n                                          float secondary_location,\n                                          float length,\n                                          float secondary_length) const {\n  FML_CHECK(view);\n  if (direction_ == ScrollDirection::kVertical) {\n    view->SetBound(secondary_location, location, secondary_length, length);\n  } else {\n    view->SetBound(location, secondary_location, length, secondary_length);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/scrollbar/scrollbar_orientation_helper.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SCROLLBAR_SCROLLBAR_ORIENTATION_HELPER_H_\n#define CLAY_UI_COMPONENT_SCROLLBAR_SCROLLBAR_ORIENTATION_HELPER_H_\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/scroll_direction.h\"\n\nnamespace clay {\n\nclass BaseView;\n\nclass ScrollbarOrientationHelper {\n public:\n  ScrollbarOrientationHelper();\n  ~ScrollbarOrientationHelper();\n\n  void SetDirection(ScrollDirection direction) { direction_ = direction; }\n  ScrollDirection GetDirection() const { return direction_; }\n\n  // Return x() or y() of `point` according to the direction.\n  float GetLocation(const FloatPoint& point) const;\n  // Return width() or height() of `delta` according to the direction.\n  float GetLength(const FloatSize& delta) const;\n  float GetLength(const FloatRect& rect) const;\n  // Return Width() or Height() of `view` according to the direction.\n  float GetLength(const BaseView& view) const;\n  float GetSecondaryLength(const BaseView& view) const;\n\n  // Return ContentWidth() or ContentHeight() of `view` according to the\n  // direction.\n  float GetContentLength(const BaseView& view) const;\n  float GetSecondaryContentLength(const BaseView& view) const;\n\n  void SetBound(BaseView* view, float location, float secondary_location,\n                float length, float secondary_length) const;\n\n private:\n  friend class ScrollbarView;\n\n  ScrollDirection direction_ = ScrollDirection::kVertical;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SCROLLBAR_SCROLLBAR_ORIENTATION_HELPER_H_\n"
  },
  {
    "path": "clay/ui/component/scrollbar/scrollbar_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/scrollbar/scrollbar_view.h\"\n\n#include <algorithm>\n#include <limits>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/gfx/animation/value_animator.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/gesture/drag_gesture_recognizer.h\"\n#include \"clay/ui/rendering/render_container.h\"\n\nnamespace clay {\n\nconst double kScrollbarFadeOutDuration = 300;\nconst double kScrollbarFadeInDuration = 100;\nconst double kDefaultThumbWidth = 8.0f;  // in logical pixels\nconst double kThumbMinLength = 18.0f;    // in logical pixels\nconst double kThumbRadius = 4.0f;        // in logical pixels\n\nnamespace {\n\ninline bool NearlyZeroFloat(float value) {\n  return std::abs(value) < std::numeric_limits<float>::epsilon();\n}\n\nusing OnDragCallback = std::function<void(const FloatSize& delta)>;\n\n}  // namespace\n\nnamespace internal {\n\nclass ScrollbarThumb : public BaseView {\n public:\n  ScrollbarThumb(PageView* page_view, ScrollDirection direction)\n      : BaseView(-1, \"scrollbar-thumb\", std::make_unique<RenderBox>(),\n                 page_view) {\n    SetScrollDirection(direction);\n  }\n\n  ~ScrollbarThumb() override = default;\n\n  RenderBox* GetRenderBox() {\n    return static_cast<RenderBox*>(render_object_.get());\n  }\n\n  void SetOnDrag(OnDragCallback on_drag) { on_drag_ = on_drag; }\n  void SetOnStatusChange(\n      std::function<void(ScrollbarView::ThumbStatus)> on_status_change) {\n    on_status_change_ = on_status_change;\n  }\n\n  void OnMouseHoverChange() override {\n    if (status_ != ScrollbarView::ThumbStatus::kDragging) {\n      SetStatus(is_mouse_hover_ ? ScrollbarView::ThumbStatus::kHovered\n                                : ScrollbarView::ThumbStatus::kIdle);\n    }\n  }\n\n  void SetScrollDirection(ScrollDirection direction) {\n    direction_ = direction;\n    ResetDragRecognizer();\n  }\n\n private:\n  void SetStatus(ScrollbarView::ThumbStatus status) {\n    if (status_ != status) {\n      status_ = status;\n      if (on_status_change_) {\n        on_status_change_(status);\n      }\n    }\n  }\n  void ResetDragRecognizer() {\n    ClearGestureRecognizers();\n    std::unique_ptr<DragGestureRecognizer> drag_recognizer;\n    if (direction_ == ScrollDirection::kVertical) {\n      drag_recognizer = std::make_unique<VerticalDragGestureRecognizer>(\n          page_view_->gesture_manager());\n    } else {\n      drag_recognizer = std::make_unique<HorizontalDragGestureRecognizer>(\n          page_view_->gesture_manager());\n    }\n    drag_recognizer->SetTouchSlop(-1.f);\n    drag_recognizer->SetDragUpdateCallback(\n        [this](const FloatPoint& position, const FloatSize& delta) {\n          if (on_drag_) {\n            on_drag_(delta);\n          }\n        });\n    drag_recognizer->SetDragStartCallback([this](const FloatPoint&) {\n      SetStatus(ScrollbarView::ThumbStatus::kDragging);\n    });\n    drag_recognizer->SetDragEndCallback([this](const Velocity&) {\n      SetStatus(is_mouse_hover_ ? ScrollbarView::ThumbStatus::kHovered\n                                : ScrollbarView::ThumbStatus::kIdle);\n    });\n    drag_recognizer->SetDragCancelCallback([this]() {\n      SetStatus(is_mouse_hover_ ? ScrollbarView::ThumbStatus::kHovered\n                                : ScrollbarView::ThumbStatus::kIdle);\n    });\n    AddGestureRecognizer(std::move(drag_recognizer));\n  }\n\n  ScrollbarView::ThumbStatus status_;\n  OnDragCallback on_drag_;\n  std::function<void(ScrollbarView::ThumbStatus)> on_status_change_;\n  ScrollDirection direction_ = ScrollDirection::kVertical;\n};\n\n}  // namespace internal\n\nScrollbarView::ScrollbarView(PageView* page_view)\n    : BaseView(-1, \"scrollbar\", std::make_unique<RenderContainer>(), page_view),\n      thumb_width_(FromLogical(kDefaultThumbWidth)),\n      thumb_min_length_(FromLogical(kThumbMinLength)),\n      thumb_radius_(FromLogical(kThumbRadius)) {\n  thumb_ = new internal::ScrollbarThumb(page_view,\n                                        orientation_helper_.GetDirection());\n  thumb_->SetOnDrag([this](auto&& delta) { OnThumbDrag(delta); });\n  thumb_->SetOnStatusChange(\n      [this](auto status) { OnThumbStatusChange(status); });\n  AddChild(thumb_);\n\n  InitStyle();\n\n  ResetGestureRecognizer();\n\n  fadeout_animator_ = std::make_unique<ValueAnimator>();\n  fadeout_animator_->SetDuration(kScrollbarFadeOutDuration);\n  fadeout_animator_->SetAnimationHandler(GetAnimationHandler());\n  fadeout_animator_->AddUpdateListener(this);\n}\n\nScrollbarView::~ScrollbarView() {\n  if (fadeout_timer_) {\n    fadeout_timer_->Stop();\n  }\n}\n\nvoid ScrollbarView::SetDirection(int type) {\n  layout_direction_ = static_cast<DirectionType>(type);\n}\n\n/**\nFIXME(ZhuChengcheng): currently scrollview's rtl is achieved by manuly convert\noffset from offset to length - offset in painting only. However we cannot do\nthis in scrollbar by the reason of different render object. Therefore we covert\nit here. This is tricky both for ScrollView and Scrollbar,We should refactor\nthem both.\n**/\nfloat ScrollbarView::GetConvertedPosition(float origin) const {\n  if (layout_direction_ == DirectionType::kLynxRtl ||\n      layout_direction_ == DirectionType::kRtl) {\n    return 1 - origin;\n  } else {\n    return origin;\n  }\n}\n\nvoid ScrollbarView::HandleTrackTapDown(const PointerEvent& event) {\n  double scroll_increment = visible_length_ * 0.8;\n  auto tap_location =\n      orientation_helper_.GetLocation(GetPointBySelf(event.position));\n  if (tap_location < GetThumbLocation()) {\n    scroll_increment = -scroll_increment;\n  } else if (tap_location <= GetThumbLocation() + GetThumbLength()) {\n    // Do nothing when tapping on the thumb area\n    return;\n  }\n\n  const float old_position = position_;\n  position_ = std::clamp(\n      position_ + scroll_increment / (total_length_ - visible_length_), 0.0,\n      1.0);\n  UpdateThumb();\n  if (delegate_ && old_position != position_) {\n    delegate_->OnScrollbarScrolled(GetConvertedPosition(old_position),\n                                   GetConvertedPosition(position_), true, true);\n  }\n}\n\nvoid ScrollbarView::OnContentSizeChanged(const FloatRect& old_rect,\n                                         const FloatRect& new_rect) {\n  BaseView::OnContentSizeChanged(old_rect, new_rect);\n  UpdateThumb();\n}\n\nvoid ScrollbarView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kScrollBarThumbColor) {\n    thumb_color_ = attribute_utils::GetColor(value);\n    InitStyle();\n  } else if (kw == KeywordID::kScrollBarThumbActiveColor) {\n    thumb_active_color_ = attribute_utils::GetColor(value);\n  } else if (kw == KeywordID::kScrollBarThumbHoverColor) {\n    thumb_hover_color_ = attribute_utils::GetColor(value);\n  } else if (kw == KeywordID::kScrollBarTrackColor) {\n    track_color_ = attribute_utils::GetColor(value);\n    InitStyle();\n  } else if (kw == KeywordID::kScrollBarThumbRadius) {\n    thumb_radius_ = FromLogical(attribute_utils::GetNum(value));\n    InitStyle();\n  } else if (kw == KeywordID::kScrollBarThumbWidth) {\n    thumb_width_ = FromLogical(attribute_utils::GetNum(value));\n    UpdateThumb();\n  } else if (kw == KeywordID::kScrollBarThumbMinLength) {\n    thumb_min_length_ = FromLogical(attribute_utils::GetNum(value));\n    UpdateThumb();\n  } else if (kw == KeywordID::kScrollBarAutoHide) {\n    SetAutoHide(attribute_utils::GetBool(value));\n  } else if (kw == KeywordID::kScrollBarAutoHideDelay) {\n    auto_hide_delay_ = attribute_utils::GetNum(value);\n  } else {\n    BaseView::SetAttribute(attr_c, value);\n  }\n}\n\nvoid ScrollbarView::SetVisibleLength(float length) {\n  if (visible_length_ != length) {\n    visible_length_ = length;\n    UpdateThumb();\n  }\n}\n\nvoid ScrollbarView::SetTotalLength(float length) {\n  if (total_length_ != length) {\n    total_length_ = length;\n    UpdateThumb();\n  }\n}\n\nvoid ScrollbarView::SetPosition(float position) {\n  auto position_converted = GetConvertedPosition(position);\n  if (position_ != position_converted) {\n    const float old_position = position_;\n    position_ = position_converted;\n    UpdateThumb();\n    if (delegate_) {\n      delegate_->OnScrollbarScrolled(GetConvertedPosition(old_position),\n                                     GetConvertedPosition(position_), false,\n                                     false);\n    }\n  }\n}\n\nvoid ScrollbarView::SetScrollDirection(ScrollDirection direction) {\n  orientation_helper_.SetDirection(direction);\n  thumb_->SetScrollDirection(direction);\n}\n\nvoid ScrollbarView::SetAutoHide(bool value) {\n  if (auto_hide_ == value) {\n    return;\n  }\n\n  auto_hide_ = value;\n  if (auto_hide_) {\n    StartFadeOutTimer();\n  } else {\n    StartFadeIn();\n  }\n}\n\nvoid ScrollbarView::NotifyScrollViewScrolled() {\n  if (!Parent()) {\n    return;\n  }\n  StartFadeIn();\n  StartFadeOutTimer();\n}\n\nvoid ScrollbarView::OnDestroy() {\n  // Finalize animation before being removed.\n  if (fadeout_animator_->IsRunning()) {\n    fadeout_animator_->End();\n  }\n  // Memory resource will be released in `DestroyChildrenRecursively`.\n  thumb_ = nullptr;\n}\n\nvoid ScrollbarView::InitStyle() {\n  thumb_->SetBackgroundColor(thumb_color_);\n  SetBackgroundColor(track_color_);\n  FloatSize radius(thumb_radius_, thumb_radius_);\n  thumb_->SetBorderRadius(radius, radius, radius, radius);\n\n  if (auto_hide_) {\n    SetOpacity(0);\n  }\n}\n\nfloat ScrollbarView::GetThumbLocation() const {\n  return position_ * (orientation_helper_.GetLength(*this) - GetThumbLength());\n}\n\nfloat ScrollbarView::GetThumbLength() const {\n  const float self_length = orientation_helper_.GetLength(*this);\n\n  if (NearlyZeroFloat(total_length_)) {\n    return 0.f;\n  }\n\n  auto safe_min_length = std::min(thumb_min_length_, GetVisibleLength());\n  return std::max(safe_min_length,\n                  visible_length_ / total_length_ * self_length);\n}\n\nvoid ScrollbarView::UpdateThumb() {\n  const float self_length = orientation_helper_.GetLength(*this);\n  const float thumb_length = std::ceil(GetThumbLength());\n  float thumb_location = std::floor(GetThumbLocation());\n\n  // if thumb_length + thumb_location is very close to the end of the scrollbar,\n  // make them align.\n  if (std::abs(thumb_location + thumb_length - self_length) < 1.0) {\n    thumb_location = self_length - thumb_length;\n  }\n  orientation_helper_.SetBound(\n      thumb_, thumb_location,\n      (orientation_helper_.GetSecondaryLength(*this) - thumb_width_) / 2,\n      thumb_length, thumb_width_);\n}\n\nvoid ScrollbarView::HandleEvent(const PointerEvent& event) {\n  BaseView::HandleEvent(event);\n  StartFadeIn();\n}\n\nvoid ScrollbarView::OnMouseHoverChange() {\n  BaseView::OnMouseHoverChange();\n\n  if (is_mouse_hover_) {\n    StartFadeIn();\n  } else {\n    StartFadeOutTimer();\n  }\n}\n\nvoid ScrollbarView::UpdatePosition(float offset_in_pixel) {\n  const float thumb_length = GetThumbLength();\n  const float half = thumb_length / 2.f;\n  const float self_length = orientation_helper_.GetLength(*this);\n  position_ = (std::clamp(offset_in_pixel, half, self_length - half) - half) /\n              (self_length - thumb_length);\n  UpdateThumb();\n}\n\nvoid ScrollbarView::StartFadeOutTimer() {\n  if (fadeout_timer_) {\n    fadeout_timer_->Stop();\n  }\n  if (!auto_hide_) {\n    return;\n  }\n  fadeout_timer_ =\n      std::make_unique<fml::Timer>(page_view()->GetTaskRunner(), false);\n  fadeout_timer_->Start(fml::TimeDelta::FromMilliseconds(auto_hide_delay_),\n                        [this] { StartFadeOut(); });\n}\n\nvoid ScrollbarView::StartFadeOut() {\n  if (!auto_hide_ || is_mouse_hover_ || thumb_status_ != ThumbStatus::kIdle) {\n    return;\n  }\n  fadeout_animator_->SetDuration(kScrollbarFadeOutDuration);\n  fadeout_animator_->Reverse();\n  // ValueAnimator may not request frames on the start\n  page_view()->RequestPaint();\n}\n\nvoid ScrollbarView::StartFadeIn() {\n  if (!fadeout_animator_->IsReversing() && !fadeout_animator_->IsRunning() &&\n      fadeout_animator_->GetAnimatedFraction() != 1) {\n    fadeout_animator_->SetDuration(kScrollbarFadeInDuration);\n    fadeout_animator_->Start();\n    // ValueAnimator may not request frames on the start\n    page_view()->RequestPaint();\n  }\n}\n\nvoid ScrollbarView::OnAnimationUpdate(ValueAnimator& animation) {\n  SetOpacity(animation.GetAnimatedFraction());\n}\n\nvoid ScrollbarView::OnThumbDrag(const FloatSize& delta) {\n  const float old_position = position_;\n  float offset = GetThumbLocation() + GetThumbLength() / 2.f;\n  offset += orientation_helper_.GetLength(delta);\n  UpdatePosition(offset);\n  if (delegate_ && old_position != position_) {\n    delegate_->OnScrollbarScrolled(GetConvertedPosition(old_position),\n                                   GetConvertedPosition(position_), true,\n                                   false);\n  }\n  thumb_->SetBackgroundColor(thumb_active_color_);\n}\n\nvoid ScrollbarView::OnThumbStatusChange(ThumbStatus thumb_status) {\n  thumb_status_ = thumb_status;\n  switch (thumb_status) {\n    case ThumbStatus::kIdle:\n      thumb_->SetBackgroundColor(thumb_color_);\n      StartFadeOutTimer();\n      break;\n    case ThumbStatus::kHovered:\n      thumb_->SetBackgroundColor(thumb_hover_color_);\n      break;\n    case ThumbStatus::kDragging:\n      thumb_->SetBackgroundColor(thumb_active_color_);\n      break;\n  }\n}\n\nvoid ScrollbarView::ResetGestureRecognizer() {\n  RemoveGestureRecognizer(tap_recognizer_);\n  tap_recognizer_ = nullptr;\n  std::unique_ptr<TapGestureRecognizer> tap_recognizer =\n      std::make_unique<TapGestureRecognizer>(page_view()->gesture_manager());\n  tap_recognizer_ = tap_recognizer.get();\n  tap_recognizer->SetTapUpCallback([this](auto&& PH1) {\n    HandleTrackTapDown(std::forward<decltype(PH1)>(PH1));\n  });\n  AddGestureRecognizer(std::move(tap_recognizer));\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/scrollbar/scrollbar_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SCROLLBAR_SCROLLBAR_VIEW_H_\n#define CLAY_UI_COMPONENT_SCROLLBAR_SCROLLBAR_VIEW_H_\n\n#include <memory>\n\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/scrollbar/scrollbar_orientation_helper.h\"\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n\nnamespace clay {\n\nnamespace internal {\n\nclass ScrollbarThumb;\n\n}  // namespace internal\n\nclass ScrollbarView : public BaseView, AnimatorUpdateListener {\n public:\n  class Delegate {\n   public:\n    virtual void OnScrollbarScrolled(float old_position, float new_position,\n                                     bool by_interaction, bool smooth) = 0;\n  };\n\n  enum class ThumbStatus {\n    kIdle,\n    kHovered,\n    kDragging,\n  };\n\n  explicit ScrollbarView(PageView* page_view);\n  ~ScrollbarView() override;\n\n  void OnContentSizeChanged(const FloatRect& old_rect,\n                            const FloatRect& new_rect) override;\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n\n  void SetVisibleLength(float length);\n  float GetVisibleLength() const { return visible_length_; }\n  void SetTotalLength(float length);\n  float GetTotalLength() const { return total_length_; }\n  void SetPosition(float position);\n\n  void SetDelegate(Delegate* delegate) { delegate_ = delegate; }\n\n  const ScrollbarOrientationHelper& GetOrientationHelper() const {\n    return orientation_helper_;\n  }\n\n  void SetDirection(int type) override;\n\n  float GetConvertedPosition(float origin) const;\n\n  void SetScrollDirection(ScrollDirection direction);\n\n  void SetAutoHide(bool value);\n\n  void NotifyScrollViewScrolled();\n\n  void ResetGestureRecognizer();\n\n protected:\n  void OnMouseHoverChange() override;\n\n private:\n  void OnDestroy() override;\n\n  void InitStyle();\n\n  // Instead of returning the view dimension of thumb, the ideal value\n  // (calculated from visible length, total length and position) is returned.\n  // The view dimension of thumb may have minor difference from this value\n  // because the view dimension is either ceiled or floored.\n  float GetThumbLocation() const;\n  float GetThumbLength() const;\n\n  void UpdateThumb();\n\n  void HandleEvent(const PointerEvent& event) override;\n  void UpdatePosition(float offset_in_pixel);\n\n  void HandleTrackTapDown(const PointerEvent& event);\n\n  void StartFadeOutTimer();\n\n  void StartFadeOut();\n  void StartFadeIn();\n\n  void OnAnimationUpdate(ValueAnimator& animation) override;\n\n  void OnThumbDrag(const FloatSize& delta);\n  void OnThumbStatusChange(ThumbStatus thumb_status);\n\n  internal::ScrollbarThumb* thumb_;\n\n  float visible_length_ = 0.f;\n  float total_length_ = 1.f;\n  float position_ = 0.f;  // in percentage\n  ThumbStatus thumb_status_ = ThumbStatus::kIdle;\n\n  float thumb_width_ = 8;        // in logical pixels\n  float thumb_min_length_ = 18;  // in logical pixels\n  float thumb_radius_ = 4;       // in logical pixels\n  Color thumb_color_ = Color::RGBOColor(0, 0, 0, 0.4);\n  Color thumb_active_color_ = Color::RGBOColor(0, 0, 0, 0.8);\n  Color thumb_hover_color_ = Color::RGBOColor(0, 0, 0, 0.8);\n  Color track_color_ = Color::RGBOColor(0, 0, 0, 0);\n  bool auto_hide_ = true;\n  float auto_hide_delay_ = 1000;  // in milliseconds\n\n  Delegate* delegate_ = nullptr;\n  std::unique_ptr<fml::Timer> fadeout_timer_;\n  std::unique_ptr<ValueAnimator> fadeout_animator_;\n\n  ScrollbarOrientationHelper orientation_helper_;\n  TapGestureRecognizer* tap_recognizer_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SCROLLBAR_SCROLLBAR_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/scrollbar/scrollbar_wrapper.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/scrollbar/scrollbar_wrapper.h\"\n\n#include <limits>\n#include <string>\n#include <unordered_set>\n#include <utility>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/rendering/render_container.h\"\n\nnamespace clay {\n\nconst double kDefaultScrollbarWidth = 12;  // in logical pixels\n\nnamespace {\n\nconst std::unordered_set<KeywordID> kProxyAttributes = {\n    KeywordID::kScrollBarThumbWidth,       KeywordID::kScrollBarThumbMinLength,\n    KeywordID::kScrollBarThumbRadius,      KeywordID::kScrollBarThumbColor,\n    KeywordID::kScrollBarThumbActiveColor, KeywordID::kScrollBarThumbHoverColor,\n    KeywordID::kScrollBarTrackColor,       KeywordID::kScrollBarAutoHide,\n    KeywordID::kScrollBarAutoHideDelay,\n};\n\ninline bool NearlyZeroFloat(float value) {\n  return std::abs(value) < std::numeric_limits<float>::epsilon();\n}\n\ninline bool NearlyEqual(float left, float right) {\n  return NearlyZeroFloat(left - right);\n}\n\n}  // namespace\n\nScrollbarWrapper::ScrollbarWrapper(int id, ScrollDirection direction,\n                                   std::string tag, PageView* page_view)\n    : BaseView(id, std::move(tag), std::make_unique<RenderContainer>(),\n               page_view),\n      scrollbar_width_(FromLogical(kDefaultScrollbarWidth)) {\n  SetOverflow(CSSProperty::OVERFLOW_HIDDEN);\n  SetRepaintBoundary(true);\n  scrollbar_ = new ScrollbarView(page_view);\n  scrollbar_->SetDelegate(this);\n  scrollbar_->SetScrollDirection(direction);\n}\n\nScrollbarWrapper::~ScrollbarWrapper() = default;\n\nvoid ScrollbarWrapper::SetScrollbarEnabled(bool enabled) {\n  if (scrollbar_enabled_ != enabled) {\n    scrollbar_enabled_ = enabled;\n    if (scrollbar_enabled_) {\n      UpdateScrollbarLengths();\n    }\n    UpdateScrollbarBounds();\n  }\n}\n\nvoid ScrollbarWrapper::AddChild(BaseView* child) {\n  view_->BaseView::AddChild(child);\n}\n\nvoid ScrollbarWrapper::AddChild(BaseView* child, int index) {\n  view_->AddChild(child, index);\n}\n\nvoid ScrollbarWrapper::RemoveChild(BaseView* child) {\n  view_->RemoveChild(child);\n}\n\nvoid ScrollbarWrapper::OnDestroy() {\n  if (scrollbar_ && !scrollbar_->Parent()) {\n    DestroyChildrenRecursively(scrollbar_);\n  }\n  scrollbar_ = nullptr;\n}\n\nvoid ScrollbarWrapper::SetAttribute(const char* attr_c,\n                                    const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kProxyAttributes.find(kw) != kProxyAttributes.end()) {\n    scrollbar_->SetAttribute(attr_c, value);\n  } else if (kw == KeywordID::kScrollBarWidth) {\n    scrollbar_width_ = FromLogical(attribute_utils::GetNum(value));\n    UpdateScrollbarBounds();\n  } else if (kw == KeywordID::kScrollBarEnable ||\n             kw == KeywordID::kEnableScrollbar) {\n    // keyword 'scroll-bar-enable' is deprecated. use 'enable-scrollbar'\n    // instead.\n    SetScrollbarEnabled(attribute_utils::GetBool(value));\n  } else {\n    BaseView::SetAttribute(attr_c, value);\n  }\n}\n\nvoid ScrollbarWrapper::SetWidth(float width) {\n  BaseView::SetWidth(width);\n  view_->SetWidth(width);\n}\nvoid ScrollbarWrapper::SetHeight(float height) {\n  BaseView::SetHeight(height);\n  view_->SetHeight(height);\n}\nvoid ScrollbarWrapper::SetBound(float left, float top, float width,\n                                float height) {\n  BaseView::SetBound(left, top, width, height);\n  view_->SetBound(0, 0, width, height);\n}\n\nvoid ScrollbarWrapper::SetPaddings(float padding_left, float padding_top,\n                                   float padding_right, float padding_bottom) {\n  view_->SetPaddings(padding_left, padding_top, padding_right, padding_bottom);\n}\n\nvoid ScrollbarWrapper::SetBorder(const BordersData& data) {\n  view_->SetBorder(data);\n}\n\nvoid ScrollbarWrapper::SetBorderStyle(std::vector<Side> sides,\n                                      std::vector<BorderStyleType> styles) {\n  view_->SetBorderStyle(sides, styles);\n}\nvoid ScrollbarWrapper::SetBorderWidth(std::vector<Side> sides,\n                                      std::vector<float> widths) {\n  view_->SetBorderWidth(sides, widths);\n}\n\nvoid ScrollbarWrapper::SetBorderColor(std::vector<Side> sides,\n                                      std::vector<uint32_t> colors) {\n  view_->SetBorderColor(sides, colors);\n}\n\nvoid ScrollbarWrapper::SetBorderRadius(const FloatSize& left_top,\n                                       const FloatSize& right_top,\n                                       const FloatSize& right_bottom,\n                                       const FloatSize& left_bottom) {\n  view_->SetBorderRadius(left_top, right_top, right_bottom, left_bottom);\n}\n\nvoid ScrollbarWrapper::SetBackground(const BackgroundData& background) {\n  view_->SetBackground(background);\n}\n\nbool ScrollbarWrapper::OnBackgroundProperty(\n    const BaseView::BackgroundUpdate& update) {\n  switch (update.type) {\n    case BaseView::BackgroundPropType::kColor: {\n      const auto& color = std::get<Color>(update.payload);\n      view_->SetBackgroundColor(color);\n      return true;\n    }\n    case BaseView::BackgroundPropType::kImage: {\n      const auto* arr = std::get<const clay::Value::Array*>(update.payload);\n      view_->SetBackgroundImage(*arr);\n      return true;\n    }\n    case BaseView::BackgroundPropType::kClip: {\n      const auto* arr = std::get<const clay::Value::Array*>(update.payload);\n      view_->SetBackgroundClip(*arr);\n      return true;\n    }\n    case BaseView::BackgroundPropType::kOrigin: {\n      const auto* arr = std::get<const clay::Value::Array*>(update.payload);\n      view_->SetBackgroundOrigin(*arr);\n      return true;\n    }\n    case BaseView::BackgroundPropType::kPosition: {\n      const auto* positions =\n          std::get<const std::vector<BackgroundPosition>*>(update.payload);\n      view_->SetBackgroundPosition(*positions);\n      return true;\n    }\n    case BaseView::BackgroundPropType::kRepeat: {\n      const auto* arr = std::get<const clay::Value::Array*>(update.payload);\n      view_->SetBackgroundRepeat(*arr);\n      return true;\n    }\n    case BaseView::BackgroundPropType::kSize: {\n      const auto* sizes =\n          std::get<const std::vector<BackgroundSize>*>(update.payload);\n      view_->SetBackgroundSize(*sizes);\n      return true;\n    }\n    default:\n      break;\n  }\n  return false;\n}\n\nvoid ScrollbarWrapper::AddEventCallback(const char* event) {\n  view_->AddEventCallback(event);\n}\n\nbool ScrollbarWrapper::HasEvent(const std::string& event) const {\n  return view_->HasEvent(event);\n}\n\nvoid ScrollbarWrapper::OnContentSizeChanged(const FloatRect& old_rect,\n                                            const FloatRect& new_rect) {\n  BaseView::OnContentSizeChanged(old_rect, new_rect);\n  view_->SetBound(0, 0, new_rect.width(), new_rect.height());\n  UpdateScrollbarBounds();\n}\n\nvoid ScrollbarWrapper::OnViewportMetricsUpdated(int physical_width,\n                                                int physical_height,\n                                                float device_pixel_ratio) {\n  UpdateScrollbarBounds();\n}\n\nvoid ScrollbarWrapper::UpdateScrollbarBounds() {\n  if (!scrollbar_enabled_) {\n    SetScrollbarVisible(false);\n    return;\n  }\n  const float secondary_length =\n      OrientationHelper().GetSecondaryContentLength(*this);\n  const float self_length = OrientationHelper().GetContentLength(*this);\n\n  if (scrollbar_width_ >= secondary_length) {\n    SetScrollbarVisible(false);\n    return;\n  }\n\n  if (scrollbar_visible_) {\n    OrientationHelper().SetBound(scrollbar_, 0.f,\n                                 secondary_length - scrollbar_width_,\n                                 self_length, scrollbar_width_);\n    WillUpdateScrollbar();\n    UpdateScrollbarLengths();\n    UpdateScrollbarPosition();\n  }\n}\n\nvoid ScrollbarWrapper::UpdateScrollbarLengths() {\n  FML_DCHECK(scrollbar_enabled_);\n  const float self_length = OrientationHelper().GetContentLength(*this);\n  const float overflow_length = GetTotalLength();\n  scrollbar_->SetVisibleLength(self_length);\n  scrollbar_->SetTotalLength(overflow_length);\n  SetScrollbarVisible(overflow_length > self_length);\n}\n\nvoid ScrollbarWrapper::UpdateScrollbarPosition() {\n  const float overflow_length = GetTotalLength();\n  const float scroll_offset = GetScrollbarScrollOffset();\n  const float self_length = OrientationHelper().GetContentLength(*this);\n\n  if (NearlyZeroFloat(overflow_length) || NearlyZeroFloat(self_length) ||\n      NearlyEqual(overflow_length, self_length)) {\n    scrollbar_->SetPosition(0.f);\n  } else {\n    scrollbar_->SetPosition(scroll_offset / (overflow_length - self_length));\n  }\n}\n\nvoid ScrollbarWrapper::SetScrollbarVisible(bool visible) {\n  if (scrollbar_visible_ == visible) {\n    return;\n  }\n\n  scrollbar_visible_ = visible;\n  if (visible && !scrollbar_->Parent()) {\n    BaseView::AddChild(scrollbar_, 1);\n    UpdateScrollbarBounds();\n  } else if (!visible && scrollbar_->Parent()) {\n    BaseView::RemoveChild(scrollbar_);\n  }\n}\n\n#ifndef NDEBUG\nstd::string ScrollbarWrapper::ToString() const {\n  std::stringstream ss;\n  ss << BaseView::ToString();\n  if (scrollbar_enabled_) {\n    ss << \" scrollbar_width_=\" << static_cast<uint8_t>(scrollbar_width_);\n  }\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/scrollbar/scrollbar_wrapper.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SCROLLBAR_SCROLLBAR_WRAPPER_H_\n#define CLAY_UI_COMPONENT_SCROLLBAR_SCROLLBAR_WRAPPER_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/scrollbar/scrollbar_view.h\"\n#include \"clay/ui/lynx_module/types.h\"\n\nnamespace clay {\n\nclass ScrollbarWrapper : public BaseView, public ScrollbarView::Delegate {\n public:\n  ScrollbarWrapper(int id, ScrollDirection direction, std::string tag,\n                   PageView* page_view);\n  ~ScrollbarWrapper() override;\n\n  void AddChild(BaseView* child) override;\n  void AddChild(BaseView* child, int index) override;\n  void RemoveChild(BaseView* child) override;\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n\n  // Redirect the following settings to scroll view.\n  void SetWidth(float width) override;\n  void SetHeight(float height) override;\n  void SetBound(float left, float top, float width, float height) override;\n\n  void SetPaddings(float padding_left, float padding_top, float padding_right,\n                   float padding_bottom) override;\n  void SetBorder(const BordersData& data) override;\n  void SetBorderStyle(std::vector<Side> sides,\n                      std::vector<BorderStyleType> styles) override;\n  void SetBorderWidth(std::vector<Side> sides,\n                      std::vector<float> widths) override;\n  void SetBorderColor(std::vector<Side> sides,\n                      std::vector<uint32_t> colors) override;\n  void SetBorderRadius(const FloatSize& left_top, const FloatSize& right_top,\n                       const FloatSize& right_bottom,\n                       const FloatSize& left_bottom) override;\n\n  void SetBackground(const BackgroundData& background) override;\n\n  bool OnBackgroundProperty(const BaseView::BackgroundUpdate& update) override;\n\n  void OnContentSizeChanged(const FloatRect& old_rect,\n                            const FloatRect& new_rect) override;\n  void OnViewportMetricsUpdated(int physical_width, int physical_height,\n                                float device_pixel_ratio) override;\n\n  void SetScrollbarEnabled(bool enabled);\n\n  // Pass through all bound events to inner scroll-view.\n  void AddEventCallback(const char* event) override;\n  /*\n   * We pass all bound events to inner `view_`, and when some events happen we\n   * filter through `HasEvent` to decide to whether callback or not. Then\n   * problem occurs. For keyframe animation, we use wrapper itself as a\n   * listener, so animation related callback will call wrapper's\n   * `OnAnimationEvent`. And `OnAnimationEvent` filters event using wrapper's\n   * `events_`, which results in problem.\n   */\n  bool HasEvent(const std::string& event) const override;\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n protected:\n  void OnDestroy() override;\n\n  const ScrollbarOrientationHelper& OrientationHelper() const {\n    return scrollbar_->GetOrientationHelper();\n  }\n\n  virtual void WillUpdateScrollbar() = 0;\n  virtual float GetScrollbarScrollOffset() = 0;\n  virtual float GetTotalLength() = 0;\n\n  // Update the dimension of scrollbar.\n  void UpdateScrollbarBounds();\n\n  void UpdateScrollbarLengths();\n  void UpdateScrollbarPosition();\n\n  void SetScrollbarVisible(bool visible);\n\n  BaseView* view_;\n  ScrollbarView* scrollbar_;\n\n  bool scrollbar_visible_ = false;\n\n  bool scrollbar_enabled_ = false;\n  float scrollbar_width_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SCROLLBAR_SCROLLBAR_WRAPPER_H_\n"
  },
  {
    "path": "clay/ui/component/scroller.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/scroller.h\"\n\n#include <algorithm>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nScroller::Scroller(Delegate* delegate, AnimationHandler* animation_handler,\n                   std::unique_ptr<Interpolator> interpolator)\n    : interpolator_(std::move(interpolator)), delegate_(delegate) {\n  FML_DCHECK(delegate);\n  FML_DCHECK(animation_handler);\n  animator_ = std::make_unique<internal::ScrollerAnimator>(\n      [this](int64_t frame_time) { return OnAnimation(frame_time); },\n      animation_handler);\n}\n\nbool Scroller::StartScroll(float start, float delta, int duration) {\n  if (delta == 0) {\n    return false;\n  }\n  if (duration == 0) {\n    ScrollToImmediately(start + delta);\n    return false;\n  }\n  // Assign start_time_ during `OnAnimation`.\n  start_time_ = 0;\n  duration_ = duration;\n  start_offset_ = start;\n  delta_ = delta;\n  StartAnimator();\n  delegate_->OnScrollAnimationStart(start, delta, duration);\n  return true;\n}\n\nvoid Scroller::AbortAnimation() { StopAnimator(); }\n\nvoid Scroller::StartAnimator() {\n  if (IsScrolling()) {\n    return;\n  }\n  animator_->Start();\n}\n\nvoid Scroller::StopAnimator() {\n  if (!IsScrolling()) {\n    return;\n  }\n  animator_->Stop();\n}\n\nbool Scroller::OnAnimation(int64_t frame_time) {\n  if (start_time_ == 0) {\n    start_time_ = frame_time;\n  }\n  int elapsed = frame_time - start_time_;\n  bool finished = elapsed >= duration_;\n  float t = std::clamp<float>(static_cast<float>(elapsed) / duration_, 0, 1);\n  float q = interpolator_->Interpolate(t);\n  float target = start_offset_ + q * delta_;\n  curr_offset_ = target;\n  delegate_->OnScrollUpdate(target);\n\n  if (finished) {\n    StopAnimator();\n    delegate_->OnScrollAnimationEnd();\n  }\n  return finished;\n}\n\nvoid Scroller::ScrollToImmediately(float offset) {\n  StopAnimator();\n  if (delegate_) {\n    delegate_->OnScrollUpdate(offset);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/scroller.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SCROLLER_H_\n#define CLAY_UI_COMPONENT_SCROLLER_H_\n\n#include <memory>\n\n#include \"clay/gfx/animation/interpolator.h\"\n#include \"clay/ui/component/scroller_animator.h\"\n\nnamespace clay {\n\nclass Scroller {\n public:\n  class Delegate {\n   public:\n    virtual void OnScrollUpdate(float scroll_offset) {}\n    virtual void OnScrollAnimationStart(float start, float delta,\n                                        int duration) {}\n    virtual void OnScrollAnimationEnd() {}\n  };\n\n  static constexpr int DEFAULT_DURATION = 250;\n\n  Scroller(Delegate* delegate, AnimationHandler* animation_handler,\n           std::unique_ptr<Interpolator> interpolator);\n\n  // If return false, it means there is no animation started.\n  bool StartScroll(float start, float delta, int duration = DEFAULT_DURATION);\n  void ScrollToImmediately(float offset);\n  void AbortAnimation();\n\n  bool IsScrolling() const { return animator_->Running(); }\n  float GetCurrentOffset() const { return curr_offset_; }\n  float GetStartOffset() const { return start_offset_; }\n  float GetDuration() const { return duration_; }\n\n private:\n  void StartAnimator();\n  void StopAnimator();\n  bool OnAnimation(int64_t frame_time);\n\n  std::unique_ptr<Interpolator> interpolator_;\n  std::unique_ptr<internal::ScrollerAnimator> animator_;\n  Delegate* delegate_;\n\n  int duration_ = 0;\n  int64_t start_time_ = 0;\n  float curr_offset_ = 0.f;\n  float start_offset_ = 0.f;\n  float delta_ = 0.f;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SCROLLER_H_\n"
  },
  {
    "path": "clay/ui/component/scroller_animator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/scroller_animator.h\"\n\n#include <functional>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\nnamespace internal {\n\nScrollerAnimator::ScrollerAnimator(\n    const std::function<bool(int64_t frame_time)>& on_animation,\n    AnimationHandler* handler)\n    : on_animation_(on_animation), handler_(handler) {\n  FML_DCHECK(on_animation_);\n  FML_DCHECK(handler_);\n}\n\nScrollerAnimator::~ScrollerAnimator() { Stop(); }\n\nbool ScrollerAnimator::DoAnimationFrame(int64_t frame_time) {\n  return on_animation_(frame_time);\n}\n\nvoid ScrollerAnimator::Start() {\n  if (running_) {\n    return;\n  }\n  running_ = true;\n  handler_->AddAnimationFrameCallback(this, 0);\n}\n\nvoid ScrollerAnimator::Stop() {\n  if (!running_) {\n    return;\n  }\n  running_ = false;\n  handler_->RemoveCallback(this);\n}\n\n}  // namespace internal\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/scroller_animator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SCROLLER_ANIMATOR_H_\n#define CLAY_UI_COMPONENT_SCROLLER_ANIMATOR_H_\n\n#include <functional>\n#include <utility>\n\n#include \"clay/gfx/animation/animation_handler.h\"\n\nnamespace clay {\nnamespace internal {\n\nclass ScrollerAnimator : public AnimationHandler::AnimationFrameCallback {\n public:\n  ScrollerAnimator(const std::function<bool(int64_t frame_time)>& on_animation,\n                   AnimationHandler* handler);\n  ~ScrollerAnimator() override;\n\n  bool DoAnimationFrame(int64_t frame_time) override;\n\n  void Start();\n  void Stop();\n\n  bool Running() const { return running_; }\n\n private:\n  std::function<bool(int64_t frame_time)> on_animation_;\n  AnimationHandler* handler_ = nullptr;\n  bool running_ = false;\n};\n\n}  // namespace internal\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SCROLLER_ANIMATOR_H_\n"
  },
  {
    "path": "clay/ui/component/selection_handle_view.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/selection_handle_view.h\"\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/selection_handle_view.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\nconstexpr float kSelectionHandleRadius = 6;\nconstexpr float kSelectionHandleOverlap = 1.5;\nconstexpr float kHalfStrokeWidth = 1.0;\n\nSelectionHandleView::SelectionHandleView(PageView* page_view,\n                                         TextSelectionHandleType type)\n    : WithTypeInfo(-1, \"handle_bar_view\", std::make_unique<RenderBox>(),\n                   page_view) {\n  type_ = type;\n  selection_handle_radius_ = FromLogical(kSelectionHandleRadius);\n  selection_handle_overlap_ = FromLogical(kSelectionHandleOverlap);\n  half_stroke_width_ = FromLogical(kHalfStrokeWidth);\n  auto drag_recognizer =\n      std::make_unique<DragGestureRecognizer>(page_view_->gesture_manager());\n  drag_recognizer_ = drag_recognizer.get();\n  drag_recognizer_->SetDragUpdateCallback(\n      [this](const FloatPoint& point, const FloatSize& delta) {\n        handle_bar_function_(point, this);\n      });\n  AddGestureRecognizer(std::move(drag_recognizer));\n}\n\nSelectionHandleView::~SelectionHandleView() {\n  RemoveGestureRecognizer(drag_recognizer_);\n  drag_recognizer_ = nullptr;\n}\n\nFloatSize SelectionHandleView::GetHandleSize(double text_line_height) {\n  return FloatSize(selection_handle_radius_ * 2,\n                   text_line_height + selection_handle_radius_ * 2 -\n                       selection_handle_overlap_);\n}\n\n// Helper method to create path with an oval and a rectangle\nGrPath SelectionHandleView::CreateHandlePath(const FloatRect& oval_rect,\n                                             const FloatRect& rect) {\n  GrPath path;\n#ifdef ENABLE_SKITY\n  path.AddOval(oval_rect);\n  path.AddRect(rect);\n#else\n  path.addOval(SkRect::MakeLTRB(oval_rect.left(), oval_rect.top(),\n                                oval_rect.right(), oval_rect.bottom()));\n  path.addRect(\n      SkRect::MakeLTRB(rect.left(), rect.top(), rect.right(), rect.bottom()));\n#endif\n  return path;\n}\n\nvoid SelectionHandleView::BuildSelectionHandle(float line_height,\n                                               FloatPoint offset) {\n  switch (type_) {\n    case TextSelectionHandleType::kLeft: {\n      // Left handle: circle at top, line extends downward\n      FloatRect circle_rect(0, 0, 2 * selection_handle_radius_,\n                            2 * selection_handle_radius_);\n\n      FloatRect line_rect(\n          selection_handle_radius_ - half_stroke_width_,\n          2 * selection_handle_radius_ - selection_handle_overlap_,\n          2 * half_stroke_width_, line_height + selection_handle_overlap_);\n\n      // Create path and position the handle\n      GrPath handle_path = CreateHandlePath(circle_rect, line_rect);\n      render_object()->SetClipPath(handle_path);\n\n      // Position the left handle (circle at top aligns with text line top)\n      SetY(offset.y() - 2 * selection_handle_radius_);\n      SetX(offset.x() - half_stroke_width_ - selection_handle_radius_);\n      break;\n    }\n    case TextSelectionHandleType::kRight: {\n      // Right handle: circle at bottom, line extends upward\n      FloatRect circle_rect(0, line_height - selection_handle_overlap_,\n                            2 * selection_handle_radius_,\n                            2 * selection_handle_radius_);\n\n      FloatRect line_rect(selection_handle_radius_ - half_stroke_width_,\n                          0,  // Start line from top of handle area\n                          2 * half_stroke_width_,\n                          line_height + selection_handle_overlap_);\n\n      // Create path and position the handle\n      GrPath handle_path = CreateHandlePath(circle_rect, line_rect);\n      render_object()->SetClipPath(handle_path);\n\n      // Position the right handle (circle at bottom aligns with text line\n      // bottom)\n      SetY(offset.y());\n      SetX(offset.x() + half_stroke_width_ - selection_handle_radius_);\n      break;\n    }\n  }\n\n  // Common setup for both handle types\n  origin_top_ = Top();\n  origin_left_ = Left();\n  render_object()->SetBackgroundColor(Color::kBlue());\n\n  // Set fixed dimensions for the handle\n  SetWidth(2 * selection_handle_radius_);\n  SetHeight(2 * selection_handle_radius_ + line_height);\n}\n\nfloat SelectionHandleView::ProcessHandlePos(FloatPoint point, TextBox left_box,\n                                            TextBox right_box) {\n  if (type_ == TextSelectionHandleType::kLeft) {\n    return std::min<float>(0, point.y() - left_box.GetTop());\n  } else if (type_ == TextSelectionHandleType::kRight) {\n    return std::max<float>(0, point.y() - right_box.GetBottom());\n  }\n  return 0;\n}\n\nvoid SelectionHandleView::UpdatePosWithScroll(FloatPoint delta) {\n  origin_top_ += delta.y();\n  origin_left_ += delta.x();\n  SetY(origin_top_);\n  SetX(origin_left_);\n  render_object()->MarkNeedsPaint();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/selection_handle_view.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SELECTION_HANDLE_VIEW_H_\n#define CLAY_UI_COMPONENT_SELECTION_HANDLE_VIEW_H_\n\n#include <utility>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/gesture/drag_gesture_recognizer.h\"\n#include \"clay/ui/painter/text_painter.h\"\n\nnamespace clay {\n\nenum TextSelectionHandleType { kLeft, kRight };\n\nclass SelectionHandleView : public WithTypeInfo<SelectionHandleView, BaseView> {\n public:\n  SelectionHandleView(PageView* page_view, TextSelectionHandleType type);\n\n  ~SelectionHandleView();\n\n  void SetHandleType(TextSelectionHandleType type) { type_ = type; }\n  TextSelectionHandleType GetHandleType() { return type_; }\n\n  FloatSize GetHandleSize(double text_line_height);\n\n  using HandleBarFunctionListener = std::function<void(\n      const FloatPoint& position, SelectionHandleView* view)>;\n  void SetHandleMove(HandleBarFunctionListener&& handle_bar_function) {\n    handle_bar_function_ = std::move(handle_bar_function);\n  }\n\n  void BuildSelectionHandle(float line_height, FloatPoint offset);\n\n  float ProcessHandlePos(FloatPoint point, TextBox left_box, TextBox right_box);\n\n  void UpdatePosWithScroll(FloatPoint delta);\n\n  void SetScrollOffset(FloatPoint offset) { scroll_offset_ = offset; }\n\n  float GetSelectionHandleRadius() const { return selection_handle_radius_; }\n\n private:\n  // Helper function to create the handle bar path with a circle and a line\n  GrPath CreateHandlePath(const FloatRect& circle_rect,\n                          const FloatRect& line_rect);\n\n  float selection_handle_radius_ = 0;\n  float selection_handle_overlap_ = 0;\n  double half_stroke_width_ = 0;\n  float origin_top_ = 0;\n  float origin_left_ = 0;\n  FloatPoint scroll_offset_;\n  HandleBarFunctionListener handle_bar_function_;\n  DragGestureRecognizer* drag_recognizer_;\n  TextSelectionHandleType type_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SELECTION_HANDLE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/selection_popup_view.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/selection_popup_view.h\"\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#include \"clay/gfx/style/color.h\"\n#include \"clay/gfx/style/shadow.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/text/internal_text_view.h\"\n#include \"clay/ui/component/text/raw_text_view.h\"\n#include \"clay/ui/component/text/text_view.h\"\n#include \"clay/ui/rendering/render_container.h\"\n#include \"clay/ui/shadow/raw_text_shadow_node.h\"\n\nnamespace clay {\n\nnamespace {\n\nconstexpr double kDefaultBorderWidth = 0.0;\nconstexpr double kDefaultRadius = 12.0;\nconstexpr double kDefaultLeftPadding = 10.0;\nconstexpr double kDefaultMidPadding = 6.0;\nconstexpr double kDefaultRightPadding = 10.0;\nconstexpr double kDefaultTopPadding = 6.0;\nconstexpr double kDefaultBottomPadding = 6.0;\nconstexpr double kDefaultFontSize = 14.0;\nconstexpr double kDefaultLineHeight = 22.0;\nconstexpr double kPopupContentDistance = 8.0;\n\nconst Size kDefaultMenuItemSize = Size(28, 22);\n\n}  // namespace\n\nSelectionPopupView::SelectionPopupView(PageView* page_view)\n    : WithTypeInfo(-1, \"popup_view\", std::make_unique<RenderContainer>(),\n                   page_view) {}\n\nSelectionPopupView::~SelectionPopupView() {\n  for (auto* child : children_) {\n    delete child;\n  }\n}\n\nvoid SelectionPopupView::BuildSelectionPopup(\n    const std::vector<ActionType>& types) {\n  FML_DCHECK(this->child_count() == 0);\n  FloatSize radius(FromLogical(kDefaultRadius), FromLogical(kDefaultRadius));\n  this->SetBorderRadius(radius, radius, radius, radius);\n  this->SetBorderColor({Side::kAll}, {Color::kBlack()});\n  float border_width = kDefaultBorderWidth;\n  this->SetBorderWidth({Side::kAll}, {border_width});\n  this->SetBorderStyle({Side::kAll}, {BorderStyleType::kSolid});\n  this->SetBackgroundColor(Color::kWhite());\n  float menu_width = 0;\n  float menu_height = 0;\n  for (auto type : types) {\n    if (type == ActionType::kCopy) {\n      auto text_view = CreateTextViewByText(\"\\xE5\\xA4\\x8D\\xE5\\x88\\xB6\", 0, 0,\n                                            MenuIndex::kLeft);\n      menu_width += text_view->Width();\n      menu_height = std::max(menu_height, text_view->Height());\n      text_view->AddTapUpListener([this](const PointerEvent&) {\n        if (handle_copy_) {\n          handle_copy_();\n        }\n      });\n      this->AddChild(text_view);\n    } else if (type == ActionType::kPaste) {\n      // TODO(wangyanyi) text componet dont need paste and cut function, but\n      // these function needed by editable view\n    } else if (type == ActionType::kCut) {\n    } else if (type == ActionType::kSelectAll) {\n      auto text_view = CreateTextViewByText(\"\\xE5\\x85\\xA8\\xE9\\x80\\x89\",\n                                            menu_width, 0, MenuIndex::kRight);\n      menu_width += text_view->Width();\n      menu_height = std::max(menu_height, text_view->Height());\n      text_view->AddTapUpListener([this](const PointerEvent&) {\n        if (select_all_) {\n          select_all_();\n        }\n      });\n      this->AddChild(text_view);\n    }\n  }\n  FloatPoint offset =\n      GetPositionForChild(FloatSize(bounds_width_, bounds_height_),\n                          FloatSize(menu_width, menu_height));\n  this->SetX(offset.x());\n  this->SetY(offset.y());\n  origin_top_ = Top();\n  origin_left_ = Left();\n  this->SetContentWidth(menu_width);\n  this->SetContentHeight(menu_height);\n  auto shadows = CreateShadow();\n  render_object()->SetShadows(std::move(shadows));\n}\n\nstd::vector<Shadow> SelectionPopupView::CreateShadow() {\n  std::vector<Shadow> shadows;\n  Shadow shadow1;\n  shadow1.inset = false;\n  shadow1.offset_x = 0;\n  shadow1.offset_y = 4;\n  shadow1.blur_radius = 20;\n  shadow1.spread_radius = 0;\n  shadow1.color = Color::ARGBColor(round(0.08 * 255), 0, 0, 0);\n  shadows.emplace_back(shadow1);\n  Shadow shadow2;\n  shadow2.inset = false;\n  shadow2.offset_x = 0;\n  shadow2.offset_y = 2;\n  shadow2.blur_radius = 4;\n  shadow2.spread_radius = 0;\n  shadow2.color = Color::ARGBColor(round(0.06 * 255), 0, 0, 0);\n  shadows.emplace_back(shadow2);\n  return shadows;\n}\n\nInternalTextView* SelectionPopupView::CreateTextViewByText(\n    const std::string& text, int left, int top, MenuIndex index) const {\n  auto text_view = new InternalTextView(\n      -1, \"text_select_menu_item\", std::make_unique<RenderText>(), page_view());\n  text_view->SetX(left);\n  text_view->SetY(top);\n  text_view->SetText(text);\n  text_view->SetFontSize(FromLogical(kDefaultFontSize));\n  text_view->SetLineHeight(FromLogical(kDefaultLineHeight));\n  if (index == MenuIndex::kLeft) {\n    text_view->SetPaddings(\n        FromLogical(kDefaultLeftPadding), FromLogical(kDefaultTopPadding),\n        FromLogical(kDefaultMidPadding), FromLogical(kDefaultBottomPadding));\n  } else if (index == MenuIndex::kMid) {\n    text_view->SetPaddings(\n        FromLogical(kDefaultMidPadding), FromLogical(kDefaultTopPadding),\n        FromLogical(kDefaultMidPadding), FromLogical(kDefaultBottomPadding));\n  } else if (index == MenuIndex::kRight) {\n    text_view->SetPaddings(\n        FromLogical(kDefaultMidPadding), FromLogical(kDefaultTopPadding),\n        FromLogical(kDefaultRightPadding), FromLogical(kDefaultBottomPadding));\n  }\n  MeasureResult result;\n  text_view->Measure(\n      {FromLogical(kDefaultMenuItemSize.width()), TextMeasureMode::kAtMost,\n       FromLogical(kDefaultMenuItemSize.height()), TextMeasureMode::kAtMost},\n      result);\n  text_view->SetContentWidth(result.width);\n  text_view->SetContentHeight(result.height);\n  return text_view;\n}\n\nFloatPoint SelectionPopupView::GetPositionForChild(FloatSize size,\n                                                   FloatSize child_size) {\n  if (anchor_offset_.size() == 0) {\n    return FloatPoint(0, 0);\n  }\n  FloatPoint anchor_above = anchor_offset_[0];\n  FloatPoint anchor_below = anchor_offset_[1];\n  bool fits_above =\n      anchor_above.y() >= child_size.height() + kPopupContentDistance;\n  bool fits_below = anchor_below.y() <=\n                    size.height() - child_size.height() - kPopupContentDistance;\n  FloatPoint anchor;\n  if (!fits_above && !fits_below) {\n    anchor = FloatPoint(size.width() / 2, size.height() / 2);\n  } else {\n    anchor = fits_above ? anchor_above : anchor_below;\n  }\n  return FloatPoint(\n      CenterOn(anchor.x(), child_size.width(), size.width()),\n      fits_above ? std::max<float>(0.0, anchor.y() - child_size.height() -\n                                            FromLogical(kPopupContentDistance))\n                 : anchor.y() + FromLogical(kPopupContentDistance));\n}\n\ndouble SelectionPopupView::CenterOn(double position, double width, double max) {\n  if (position - width / 2.0 < 0.0) {\n    return 0.0;\n  }\n  if (position + width / 2.0 > max) {\n    return max - width;\n  }\n  return position - width / 2.0;\n}\n\nvoid SelectionPopupView::UpdatePosWithScroll(FloatPoint scroll_offset,\n                                             FloatRect bounding_rect) {\n  SetY(scroll_offset.y() < 0\n           ? std::max(scroll_offset.y() + origin_top_,\n                      bounding_rect.top() - height_ -\n                          FromLogical(kPopupContentDistance))\n           : std::min(scroll_offset.y() + origin_top_, bounding_rect.bottom()));\n  SetX(scroll_offset.x() + origin_left_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/selection_popup_view.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SELECTION_POPUP_VIEW_H_\n#define CLAY_UI_COMPONENT_SELECTION_POPUP_VIEW_H_\n\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nusing TextFunctionListener = std::function<void()>;\n\nclass InternalTextView;\n\nenum class ActionType {\n  kPaste,\n  kCopy,\n  kCut,\n  kSelectAll,\n};\n\nenum class MenuIndex {\n  kLeft,\n  kMid,\n  kRight,\n};\n\nclass SelectionPopupView : public WithTypeInfo<SelectionPopupView, BaseView> {\n public:\n  explicit SelectionPopupView(PageView* page_view);\n\n  ~SelectionPopupView();\n\n  void SetAnchorOffset(std::vector<FloatPoint> offset) {\n    anchor_offset_ = offset;\n  }\n\n  void SetCopyFunction(TextFunctionListener handle_copy) {\n    handle_copy_ = handle_copy;\n  }\n\n  void SetSelectAllFunction(TextFunctionListener select_all) {\n    select_all_ = select_all;\n  }\n\n  void BuildSelectionPopup(const std::vector<ActionType>& types);\n\n  FloatPoint GetPositionForChild(FloatSize size, FloatSize child_size);\n\n  double CenterOn(double position, double width, double max);\n\n  void SetBoundsHeightAndWidth(int width, int height) {\n    bounds_width_ = width;\n    bounds_height_ = height;\n  }\n\n  void UpdatePosWithScroll(FloatPoint scroll_offset, FloatRect bounding_rect);\n\n  std::vector<Shadow> CreateShadow();\n\n private:\n  InternalTextView* CreateTextViewByText(const std::string& text, int left,\n                                         int top, MenuIndex index) const;\n  TextFunctionListener handle_copy_;\n  TextFunctionListener select_all_;\n\n  std::vector<FloatPoint> anchor_offset_;\n\n  int bounds_width_ = 0;\n  int bounds_height_ = 0;\n\n  float origin_top_ = 0;\n  float origin_left_ = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SELECTION_POPUP_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/soft_keyboard_resources.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/soft_keyboard_resources.h\"\n\nnamespace clay {\nnamespace keyboard_res {\n\n// TODO(yulitao): use .9.png instead.\nconst uint8_t kBackspace[] = {\n    0x89, 0x50, 0x4e, 0x47, 0xd,  0xa,  0x1a, 0xa,  0x0,  0x0,  0x0,  0xd,\n    0x49, 0x48, 0x44, 0x52, 0x0,  0x0,  0x0,  0x7f, 0x0,  0x0,  0x0,  0x62,\n    0x8,  0x6,  0x0,  0x0,  0x0,  0x79, 0xf5, 0xc,  0x56, 0x0,  0x0,  0x0,\n    0x9,  0x70, 0x48, 0x59, 0x73, 0x0,  0x0,  0x21, 0x38, 0x0,  0x0,  0x21,\n    0x38, 0x1,  0x45, 0x96, 0x31, 0x60, 0x0,  0x0,  0x0,  0x1,  0x73, 0x52,\n    0x47, 0x42, 0x0,  0xae, 0xce, 0x1c, 0xe9, 0x0,  0x0,  0x0,  0x4,  0x67,\n    0x41, 0x4d, 0x41, 0x0,  0x0,  0xb1, 0x8f, 0xb,  0xfc, 0x61, 0x5,  0x0,\n    0x0,  0x6,  0xd0, 0x49, 0x44, 0x41, 0x54, 0x78, 0x1,  0xed, 0x9d, 0x5d,\n    0x52, 0x1b, 0x47, 0x10, 0xc7, 0x7b, 0x57, 0x8,  0x63, 0x89, 0x7,  0xf4,\n    0x80, 0x84, 0xca, 0x2f, 0xab, 0x9c, 0x0,  0x4e, 0x10, 0x4b, 0x17, 0x8,\n    0xc9, 0x5,  0xec, 0x70, 0x1,  0x3b, 0xbe, 0x80, 0x21, 0x17, 0xb0, 0x4f,\n    0x60, 0xc8, 0x5,  0x62, 0x72, 0x1,  0x9b, 0x5c, 0xc0, 0xf8, 0x6,  0x22,\n    0xf,  0x2e, 0x59, 0x4b, 0xa5, 0xe0, 0x85, 0x17, 0x93, 0xdd, 0x49, 0x37,\n    0xec, 0xca, 0x2,  0x1,  0xda, 0x8f, 0xf9, 0xe8, 0x59, 0xcd, 0xaf, 0x4a,\n    0xb5, 0xd2, 0x16, 0xb6, 0xa4, 0xfd, 0xf7, 0xce, 0x47, 0xcf, 0xbf, 0x47,\n    0x0,  0xe,  0x87, 0x63, 0xf1, 0xf0, 0x80, 0x39, 0xc3, 0xe1, 0x70, 0x6d,\n    0x65, 0xa5, 0xb9, 0xed, 0x79, 0xf0, 0x23, 0x3e, 0x36, 0xf1, 0xd4, 0x1a,\n    0x38, 0xb2, 0x70, 0x8e, 0xd7, 0xeb, 0x3c, 0x8a, 0xc4, 0xdf, 0xb5, 0x9a,\n    0x77, 0xd8, 0x6e, 0xb7, 0x3f, 0xdf, 0xfe, 0x3,  0xb6, 0xe2, 0x93, 0xe8,\n    0xab, 0xab, 0xab, 0x2f, 0x84, 0x80, 0x97, 0x42, 0x8,  0x27, 0x78, 0x49,\n    0x30, 0x10, 0x3e, 0x46, 0x51, 0xb4, 0xd3, 0xed, 0x76, 0x4f, 0x26, 0xe7,\n    0x80, 0x21, 0xa3, 0xd1, 0x28, 0xf0, 0xfd, 0xda, 0x7b, 0x7c, 0xba, 0x9,\n    0xe,  0x99, 0x8,  0xdf, 0xf7, 0xf6, 0xd6, 0xd7, 0xd7, 0xf7, 0xe8, 0x5,\n    0x3b, 0xf1, 0x13, 0xe1, 0x3f, 0xe2, 0xd3, 0x0,  0x1c, 0x2a, 0x98, 0x4,\n    0x0,  0x2b, 0xf1, 0xa9, 0xa9, 0x6f, 0x34, 0x9a, 0xc7, 0xe0, 0x84, 0x57,\n    0x8d, 0x88, 0x63, 0x6f, 0xe0, 0x3,  0x23, 0x9a, 0xcd, 0xd5, 0xd7, 0xe0,\n    0x84, 0xd7, 0x81, 0xe7, 0xfb, 0xe2, 0x1d, 0x9b, 0x3b, 0x7f, 0x3c, 0x3e,\n    0x45, 0xe1, 0xc5, 0x2e, 0x38, 0x74, 0x21, 0x58, 0x88, 0xff, 0xe5, 0xcb,\n    0x78, 0x7b, 0x69, 0xc9, 0x7b, 0xf,  0xe,  0xad, 0x18, 0x17, 0x9f, 0x6,\n    0x78, 0xb5, 0xda, 0xd2, 0xb1, 0x9b, 0xce, 0xe9, 0xc7, 0x68, 0x9f, 0x9f,\n    0x8e, 0xec, 0x9d, 0xf0, 0x66, 0x30, 0x2a, 0x7e, 0x32, 0x97, 0xf,  0xc0,\n    0x61, 0x4,  0x63, 0xe2, 0x87, 0xe1, 0xe9, 0x1b, 0x70, 0x49, 0x1c, 0xa3,\n    0x18, 0x11, 0x9f, 0x46, 0xf6, 0xd8, 0xd4, 0xbf, 0x4,  0x87, 0x51, 0xb4,\n    0xf,  0xf8, 0xc2, 0x30, 0xdc, 0xc4, 0x7c, 0xfd, 0x31, 0x38, 0x8c, 0xa3,\n    0xf5, 0xce, 0xa7, 0x1,  0x1e, 0xa,  0xef, 0xa6, 0x74, 0x4c, 0xd0, 0x26,\n    0x3e, 0xa5, 0x6e, 0x5d, 0xce, 0x9e, 0x17, 0xda, 0xc4, 0xc7, 0xe5, 0xd9,\n    0x7d, 0x70, 0xc2, 0xb3, 0x42, 0x8b, 0xf8, 0x34, 0xc0, 0x8b, 0x63, 0xb1,\n    0xd,  0xe,  0x56, 0x28, 0x1f, 0xf0, 0x85, 0xe1, 0xbf, 0x2f, 0x84, 0x88,\n    0xde, 0x82, 0x83, 0x1d, 0x4a, 0xc5, 0x77, 0xa9, 0x5b, 0xde, 0x28, 0x6b,\n    0xf6, 0x5d, 0xea, 0x96, 0x3f, 0xca, 0xc4, 0x77, 0xa9, 0x5b, 0xfe, 0x28,\n    0x11, 0xdf, 0xa5, 0x6e, 0xed, 0x40, 0xba, 0xf8, 0x2e, 0x75, 0x6b, 0xf,\n    0x52, 0xc5, 0x27, 0x53, 0x86, 0x5,  0x6e, 0x9c, 0xcf, 0x9e, 0x7,  0x47,\n    0xa0, 0x97, 0x13, 0x3,  0xef, 0x39, 0x17, 0x69, 0xe2, 0xd3, 0x0,  0xaf,\n    0x5e, 0xf7, 0xf7, 0x81, 0x29, 0x54, 0xc0, 0x10, 0xc7, 0x51, 0xbf, 0xd3,\n    0x69, 0x6f, 0xb5, 0xdb, 0xed, 0x3e, 0xbe, 0xde, 0xa2, 0x73, 0xa0, 0x16,\n    0x11, 0xc7, 0xf0, 0x2b, 0xbe, 0x67, 0x2f, 0x8a, 0xa2, 0x3e, 0xbe, 0xff,\n    0xf,  0x78, 0xea, 0x4,  0x98, 0x20, 0x65, 0xaa, 0xc7, 0xdd, 0x6e, 0x9d,\n    0x88, 0xdc, 0xbf, 0x5d, 0xb5, 0x42, 0x8b, 0x4c, 0x78, 0xc0, 0x19, 0x89,\n    0x92, 0x2a, 0x20, 0x12, 0x7e, 0xa7, 0xdb, 0x6d, 0x1f, 0xa4, 0x27, 0xf0,\n    0x3a, 0xd1, 0xa1, 0xe7, 0xfb, 0xfe, 0x7,  0xfc, 0x54, 0x1,  0x18, 0x46,\n    0xca, 0x9d, 0x5f, 0xab, 0xd5, 0xd8, 0xa6, 0x6e, 0xef, 0x13, 0x9e, 0x48,\n    0xce, 0xf5, 0x15, 0xb4, 0x0,  0x33, 0xc2, 0x13, 0xdd, 0x6e, 0x97, 0xe,\n    0xc3, 0x38, 0x8e, 0x7,  0x1c, 0x5a, 0x80, 0xd2, 0xe2, 0x5f, 0xf,  0xf0,\n    0xe0, 0x29, 0x30, 0xe4, 0x21, 0xe1, 0x53, 0x14, 0x4,  0xc0, 0x9d, 0xc2,\n    0xa7, 0x70, 0xa,  0x80, 0x52, 0xcd, 0x3e, 0xe7, 0xd4, 0x6d, 0x16, 0xe1,\n    0xa7, 0xc1, 0x2e, 0x60, 0xb,  0xf,  0x1f, 0x4a, 0x76, 0x1,  0xf,  0xa,\n    0x3f, 0xd,  0x87, 0x2e, 0xa0, 0xb0, 0xf8, 0x9c, 0x4d, 0x19, 0x79, 0x85,\n    0x27, 0xf0, 0xfb, 0xd0, 0xa1, 0x4c, 0x0,  0x64, 0x16, 0x3e, 0xc5, 0x74,\n    0x0,  0x14, 0x6a, 0xf6, 0x39, 0x9b, 0x32, 0x8a, 0x8,  0x4f, 0xe0, 0xdf,\n    0xd3, 0x81, 0x82, 0x79, 0x50, 0xa0, 0xb,  0xc8, 0x2d, 0x3c, 0x61, 0xba,\n    0xb,  0xc8, 0x2d, 0x3e, 0x67, 0x53, 0x46, 0x51, 0xe1, 0x53, 0xa,  0x6,\n    0x40, 0x21, 0xe1, 0x53, 0x4c, 0x6,  0x40, 0x6e, 0xf1, 0x39, 0x9b, 0x32,\n    0xf0, 0x2,  0xee, 0x15, 0x15, 0x3e, 0x25, 0x67, 0x0,  0x94, 0x12, 0x3e,\n    0x65, 0x2a, 0x0,  0x76, 0x40, 0x23, 0xb9, 0xc4, 0xe7, 0x6e, 0xca, 0xc0,\n    0xbe, 0xf3, 0x8,  0x24, 0x90, 0x31, 0x0,  0xa4, 0x8,  0x9f, 0xb2, 0xb2,\n    0xb2, 0x42, 0x8f, 0x52, 0x81, 0x9b, 0x97, 0xcc, 0xe2, 0xd3, 0xc8, 0x9e,\n    0x7b, 0xea, 0x36, 0x8a, 0xe4, 0x2d, 0x26, 0xcd, 0x9,  0x0,  0xa9, 0xc2,\n    0x13, 0x17, 0x17, 0x17, 0xf4, 0x8,  0x40, 0x23, 0x99, 0x46, 0xfb, 0x16,\n    0x99, 0x32, 0x4e, 0x28, 0x85, 0x3b, 0xbd, 0xf5, 0x48, 0x59, 0xee, 0x98,\n    0x5,  0x48, 0x17, 0x1e, 0xc7, 0x51, 0x38, 0x5e, 0xf1, 0x5a, 0x8f, 0x1f,\n    0x37, 0x3e, 0x81, 0xc6, 0x2e, 0x75, 0xee, 0x9d, 0x6f, 0x99, 0x29, 0xe3,\n    0xea, 0xb3, 0xd2, 0x67, 0x6,  0x49, 0xdc, 0x6a, 0x1,  0xce, 0x54, 0x8,\n    0x8f, 0xf7, 0x20, 0x9,  0x8f, 0xd3, 0x3d, 0xbd, 0x63, 0xa9, 0xb9, 0x77,\n    0xfe, 0x78, 0x1c, 0xd2, 0x17, 0xb7, 0x6d, 0x6d, 0x5e, 0x7a, 0xb,  0x70,\n    0x2d, 0x12, 0xac, 0xf5, 0x7a, 0x3d, 0x69, 0xa9, 0xe0, 0x54, 0xf8, 0x46,\n    0xe3, 0x4a, 0x78, 0xed, 0xd7, 0xf8, 0x41, 0xf1, 0xc9, 0x94, 0x61, 0xf1,\n    0xda, 0xbc, 0xf4, 0x0,  0x90, 0x89, 0x69, 0xe1, 0x89, 0x7b, 0x9b, 0xfd,\n    0xa,  0x98, 0x32, 0xa4, 0x77, 0x1,  0xb2, 0xe0, 0x20, 0x3c, 0x71, 0xe7,\n    0x9d, 0x5f, 0xb1, 0x9d, 0x32, 0x58, 0xb5, 0x0,  0x5c, 0x84, 0x27, 0x66,\n    0xc4, 0xaf, 0xe8, 0x56, 0x68, 0x2c, 0x2,  0x80, 0x93, 0xf0, 0xc4, 0x4c,\n    0xb3, 0x5f, 0xd1, 0x7a, 0x3a, 0xe3, 0x5d, 0x0,  0x37, 0xe1, 0x89, 0x1b,\n    0xe2, 0x8f, 0x46, 0xe1, 0x73, 0xa8, 0xae, 0xdd, 0xda, 0x58, 0x0,  0x70,\n    0x14, 0x9e, 0xb8, 0x21, 0x7e, 0xad, 0x6,  0xcf, 0xa0, 0xda, 0x68, 0xf,\n    0x0,  0xae, 0xc2, 0x13, 0x93, 0x3e, 0x3f, 0xe9, 0xeb, 0x87, 0xb0, 0x18,\n    0x68, 0x19, 0x3,  0x70, 0x16, 0x9e, 0x98, 0xbe, 0xf3, 0x3,  0x58, 0x1c,\n    0x94, 0xb7, 0x0,  0xdc, 0x85, 0x27, 0xa6, 0xc4, 0xaf, 0x5,  0xb0, 0x58,\n    0x28, 0xb,  0x0,  0x1b, 0x84, 0x27, 0x58, 0xed, 0xbd, 0x6b, 0x80, 0x20,\n    0x8e, 0x7d, 0xe9, 0xe2, 0x34, 0x9b, 0x4d, 0x7c, 0x34, 0x2,  0x5c, 0xb,\n    0x8,  0x80, 0x31, 0x13, 0xf1, 0x7d, 0x5f, 0xc8, 0xb6, 0x2f, 0xb3, 0x87,\n    0xa,  0x2a, 0x9e, 0x3c, 0xe9, 0x1c, 0x82, 0x64, 0x4a, 0x5a, 0xc2, 0xb4,\n    0x31, 0x11, 0x7f, 0x79, 0x79, 0xf9, 0x8,  0x16, 0x8,  0x12, 0x5e, 0xe6,\n    0xea, 0xdc, 0x6d, 0x6c, 0x8,  0x80, 0x89, 0xf8, 0xad, 0x56, 0xeb, 0x9c,\n    0x63, 0x3d, 0x99, 0xa,  0x54, 0xb,  0x9f, 0xc2, 0x3d, 0x0,  0x6e, 0xf4,\n    0xf9, 0x51, 0x4,  0x7f, 0x40, 0xb5, 0x11, 0xba, 0x84, 0x4f, 0xe1, 0x1c,\n    0x0,  0x33, 0xb9, 0xfd, 0x30, 0xc,  0xc9, 0xb1, 0xd2, 0x87, 0xea, 0x21,\n    0xdd, 0x81, 0x93, 0x7,  0x9,  0x75, 0x1,  0xd2, 0x99, 0x19, 0xed, 0xd3,\n    0x2f, 0x30, 0x91, 0x63, 0x5,  0xaa, 0x85, 0x51, 0xe1, 0x9,  0x8e, 0x2d,\n    0xc0, 0x8c, 0xf8, 0x94, 0xf5, 0xc2, 0x0,  0xf8, 0x5,  0x9f, 0xa,  0xa8,\n    0x6,  0xc6, 0x85, 0x4f, 0xe1, 0x16, 0x0,  0x77, 0xce, 0xf3, 0x31, 0x0,\n    0x8e, 0xa2, 0x28, 0x7e, 0x5,  0xf6, 0xc3, 0x46, 0xf8, 0x14, 0x4e, 0x1,\n    0xf0, 0xa0, 0x8d, 0xeb, 0xeb, 0xd7, 0xf0, 0xd,  0x7e, 0x40, 0x5b, 0xdd,\n    0x3c, 0xec, 0x84, 0x9f, 0x86, 0xc3, 0x18, 0x20, 0x8b, 0x81, 0x93, 0xec,\n    0xc4, 0x5b, 0x60, 0x17, 0xac, 0x85, 0x4f, 0x31, 0x1d, 0x0,  0x73, 0xd3,\n    0xbb, 0xb8, 0xfa, 0x85, 0xfd, 0xbf, 0xb0, 0x69, 0xb5, 0x4f, 0xba, 0xf0,\n    0x54, 0x4d, 0x8b, 0x8f, 0x1e, 0xcd, 0x84, 0x14, 0xda, 0xc2, 0xb5, 0x77,\n    0x1,  0x99, 0x8a, 0x36, 0x92, 0xed, 0x4b, 0x28, 0x3a, 0x5b, 0xc0, 0x1c,\n    0xd9, 0xf3, 0xf8, 0xef, 0x65, 0xd4, 0xb5, 0xd4, 0x57, 0x3f, 0xc4, 0x1b,\n    0x62, 0xa0, 0xb8, 0x30, 0x44, 0xb,  0x99, 0x16, 0x76, 0xa8, 0xf8, 0x11,\n    0x67, 0x0,  0x16, 0xc,  0x0,  0xc5, 0x9e, 0x62, 0xe1, 0x27, 0xaf, 0x65,\n    0xb7, 0x0,  0x78, 0x7d, 0x8f, 0x93, 0x59, 0x96, 0x36, 0x32, 0xaf, 0xea,\n    0x61, 0xa4, 0x1f, 0xc4, 0xb1, 0xd8, 0x3,  0xc6, 0x78, 0x9e, 0x27, 0x6d,\n    0x91, 0xe6, 0x1e, 0xe1, 0x53, 0xa4, 0x7,  0x0,  0xeb, 0x42, 0x4d, 0xa2,\n    0xdb, 0xed, 0xec, 0xe2, 0xdd, 0x75, 0x0,  0x4c, 0xf1, 0x7d, 0x3f, 0x48,\n    0x44, 0x2b, 0xc5, 0x1c, 0xe1, 0x53, 0xa4, 0x6,  0x80, 0x89, 0x42, 0xcd,\n    0xdc, 0xeb, 0xf9, 0xb8, 0xfa, 0xf7, 0x1b, 0x0,  0xcf, 0xed, 0x58, 0x30,\n    0x37, 0x41, 0xdb, 0xbe, 0xf6, 0xca, 0x4,  0x40, 0x46, 0xe1, 0x53, 0xa4,\n    0x4,  0x0,  0x99, 0x3f, 0x2e, 0x2f, 0xff, 0x6b, 0xd5, 0xeb, 0xcb, 0xef,\n    0x40, 0x23, 0xb9, 0xc5, 0xa7, 0xd5, 0x3f, 0xc6, 0x33, 0x80, 0x20, 0x11,\n    0xad, 0x50, 0x0,  0xe4, 0x14, 0x3e, 0xa5, 0x54, 0x0,  0x98, 0x74, 0xfd,\n    0x94, 0xdd, 0x90, 0xe9, 0x53, 0x99, 0xff, 0x43, 0x21, 0x64, 0xd0, 0x1c,\n    0xe0, 0x71, 0x98, 0xec, 0x7a, 0x31, 0x97, 0x82, 0xc2, 0x4f, 0x93, 0x7b,\n    0x16, 0x60, 0xda, 0xee, 0x55, 0xd8, 0xc6, 0x75, 0x3d, 0x3,  0x60, 0x9b,\n    0x2,  0x2e, 0xd2, 0x2,  0x94, 0x11, 0x7e, 0xf2, 0xef, 0xb3, 0xb6, 0x0,\n    0x1c, 0x7c, 0x7e, 0xa5, 0xef, 0x5a, 0xe6, 0x29, 0xe0, 0x4c, 0x2d, 0x80,\n    0xe4, 0x12, 0xb5, 0xb9, 0x2d, 0x0,  0xeb, 0x42, 0xcd, 0xbc, 0x30, 0xf7,\n    0x0,  0x3c, 0x18, 0x0,  0x8a, 0x6a, 0x13, 0xef, 0xd,  0x0,  0x4e, 0xce,\n    0x5e, 0x29, 0xee, 0xdd, 0x7a, 0xbd, 0xce, 0x39, 0x5,  0x7c, 0x6f, 0x17,\n    0xa0, 0xb0, 0x28, 0xf5, 0xce, 0x2e, 0x80, 0x7d, 0xa1, 0x66, 0x11, 0xae,\n    0x67, 0x0,  0xf1, 0x80, 0xb1, 0x9,  0x64, 0x12, 0x0,  0xe9, 0x9,  0xd,\n    0xd5, 0xc8, 0x37, 0x2,  0x80, 0xa3, 0x97, 0x5f, 0xea, 0x48, 0x1d, 0xbf,\n    0xe8, 0xd3, 0xe4, 0x82, 0x72, 0xe5, 0x44, 0x88, 0xf8, 0x2f, 0x7a, 0xe2,\n    0x79, 0xfe, 0x4f, 0xa0, 0xa7, 0x4a, 0xe9, 0xea, 0x87, 0x16, 0xa2, 0x48,\n    0xfc, 0xe3, 0xfb, 0xde, 0x33, 0x4d, 0xef, 0x99, 0x9,  0xe9, 0xd3, 0xb4,\n    0xd1, 0x68, 0xbc, 0x8b, 0x5f, 0xf2, 0x35, 0x38, 0xd8, 0xa3, 0x64, 0x8e,\n    0x6e, 0xb9, 0x9,  0x64, 0x61, 0x50, 0x52, 0xae, 0xf5, 0xe8, 0x51, 0x9d,\n    0x16, 0x80, 0xdc, 0xcf, 0xa4, 0x33, 0x47, 0x89, 0xf8, 0xcc, 0x53, 0xc0,\n    0x8e, 0x4,  0xa5, 0xa9, 0x59, 0x9b, 0x4c, 0x20, 0x8b, 0x88, 0xd2, 0x2a,\n    0x5d, 0x7b, 0x4c, 0x20, 0x8b, 0x89, 0xf2, 0x12, 0x6d, 0x1b, 0x4c, 0x20,\n    0x8b, 0x8a, 0xb6, 0x15, 0xb9, 0xf1, 0xf8, 0xf4, 0x4f, 0x1c, 0x3,  0xfc,\n    0xc,  0xe,  0x36, 0x68, 0xdb, 0x9c, 0x61, 0x79, 0x79, 0x69, 0xc7, 0xd,\n    0x0,  0x79, 0xa1, 0x4d, 0x7c, 0xb,  0x52, 0xc0, 0xb,  0x87, 0xd6, 0x6d,\n    0x59, 0x2a, 0x58, 0x7,  0x68, 0x35, 0xda, 0xf7, 0xe4, 0xa9, 0x50, 0x1d,\n    0xa0, 0xf5, 0x18, 0xb3, 0x60, 0xb9, 0x14, 0xb0, 0x79, 0x8c, 0xfa, 0xef,\n    0x2a, 0xbc, 0x11, 0x84, 0x15, 0x18, 0xdd, 0x8a, 0x8d, 0xb9, 0x9,  0xa4,\n    0xf2, 0x18, 0x15, 0xdf, 0xcd, 0x0,  0xcc, 0x62, 0x7c, 0x13, 0x46, 0x9a,\n    0x1,  0x8,  0x21, 0xb4, 0xfe, 0x98, 0xa0, 0xe3, 0x1a, 0x16, 0x3b, 0x70,\n    0x76, 0x3a, 0x9d, 0x43, 0x97, 0x2,  0xd6, 0x8e, 0x60, 0x55, 0x70, 0x31,\n    0x1e, 0x8f, 0xf7, 0x71, 0xc,  0xfa, 0x1c, 0x1c, 0x1a, 0x10, 0x43, 0x56,\n    0xe2, 0x9f, 0x9d, 0x9d, 0xad, 0x7d, 0xfb, 0x76, 0x49, 0x6,  0x47, 0xdb,\n    0x76, 0x2,  0xb1, 0xd,  0x41, 0xd6, 0x72, 0x56, 0x1b, 0x2f, 0x3b, 0x13,\n    0x88, 0x2e, 0xc4, 0xef, 0x94, 0x6c, 0xe3, 0x58, 0x67, 0x97, 0xd8, 0xaa,\n    0x7d, 0x6c, 0x1,  0xbc, 0x1e, 0x38, 0x64, 0x82, 0x63, 0xeb, 0xf8, 0xd5,\n    0xc6, 0xc6, 0xc6, 0x5b, 0x7a, 0xf1, 0x3f, 0x36, 0xd,  0x5,  0x3f, 0xa0,\n    0xc3, 0xe8, 0xb,  0x0,  0x0,  0x0,  0x0,  0x49, 0x45, 0x4e, 0x44, 0xae,\n    0x42, 0x60, 0x82};\n\nconst char* kBackspaceUrl = \"kBackspace\";\n\nconst int kBackspaceLength = sizeof(kBackspace) / sizeof(kBackspace[0]);\n\nconst uint8_t kArrowLeft[] = {\n    0x89, 0x50, 0x4e, 0x47, 0xd,  0xa,  0x1a, 0xa,  0x0,  0x0,  0x0,  0xd,\n    0x49, 0x48, 0x44, 0x52, 0x0,  0x0,  0x0,  0x5e, 0x0,  0x0,  0x0,  0x5c,\n    0x8,  0x6,  0x0,  0x0,  0x0,  0xe7, 0x1a, 0x2,  0x65, 0x0,  0x0,  0x0,\n    0x9,  0x70, 0x48, 0x59, 0x73, 0x0,  0x0,  0x21, 0x38, 0x0,  0x0,  0x21,\n    0x38, 0x1,  0x45, 0x96, 0x31, 0x60, 0x0,  0x0,  0x0,  0x1,  0x73, 0x52,\n    0x47, 0x42, 0x0,  0xae, 0xce, 0x1c, 0xe9, 0x0,  0x0,  0x0,  0x4,  0x67,\n    0x41, 0x4d, 0x41, 0x0,  0x0,  0xb1, 0x8f, 0xb,  0xfc, 0x61, 0x5,  0x0,\n    0x0,  0x4,  0xb,  0x49, 0x44, 0x41, 0x54, 0x78, 0x1,  0xed, 0x9d, 0x4d,\n    0x6e, 0xd3, 0x40, 0x14, 0xc7, 0x9f, 0x1d, 0x88, 0x4a, 0xb3, 0x49, 0x16,\n    0xc4, 0x49, 0x37, 0xb4, 0xbd, 0x0,  0x94, 0x13, 0xb4, 0x3d, 0x1,  0x9c,\n    0xa0, 0x29, 0x17, 0xa0, 0x70, 0x81, 0xb6, 0x5c, 0xa0, 0xbd, 0x41, 0xc3,\n    0x9,  0xca, 0x8e, 0x65, 0xb9, 0x1,  0x9c, 0x80, 0x64, 0xd5, 0x60, 0x17,\n    0xe4, 0x6c, 0x2a, 0x55, 0x5,  0xcf, 0xf0, 0xc6, 0xf9, 0x80, 0xa8, 0x49,\n    0x1c, 0x7f, 0x8d, 0x67, 0x3c, 0xef, 0x27, 0x45, 0xb2, 0x9c, 0x76, 0xf3,\n    0xd3, 0x68, 0xfc, 0xe6, 0x3f, 0x6f, 0x1c, 0x0,  0x82, 0x20, 0x34, 0x61,\n    0x30, 0x18, 0x6c, 0x8a, 0xf,  0x68, 0x88, 0xd,  0x1a, 0x32, 0x18, 0xdc,\n    0xec, 0xba, 0xae, 0xf7, 0xd5, 0xb6, 0x2b, 0x3d, 0xf1, 0xc1, 0xeb, 0xef,\n    0xe2, 0x1e, 0x68, 0x84, 0x5,  0x9a, 0xe1, 0xba, 0x3f, 0xf,  0x0,  0x58,\n    0x77, 0xce, 0x57, 0xbe, 0x65, 0xc1, 0x7e, 0xb3, 0xd9, 0xfc, 0x6,  0x1a,\n    0xa0, 0xd5, 0x88, 0xf7, 0xbc, 0x5f, 0x6f, 0x17, 0x48, 0x17, 0x34, 0x18,\n    0x63, 0xc7, 0xa0, 0x9,  0xda, 0x88, 0x77, 0xdd, 0x9b, 0x63, 0xce, 0x83,\n    0xf3, 0x65, 0x7f, 0x63, 0x59, 0xf6, 0x73, 0xd0, 0x4,  0x2d, 0xc4, 0xb,\n    0xe9, 0x0,  0xfc, 0x4,  0x4a, 0xc4, 0x23, 0x50, 0x1c, 0xcf, 0xbb, 0x39,\n    0xe3, 0x9c, 0x1f, 0x41, 0xc9, 0x50, 0x5a, 0xbc, 0xe7, 0x79, 0x17, 0x28,\n    0xbd, 0x3,  0x25, 0x44, 0x49, 0xf1, 0xbd, 0x5e, 0xaf, 0x5e, 0xab, 0xd5,\n    0x2e, 0x39, 0x87, 0x5d, 0x28, 0x29, 0xca, 0x89, 0x17, 0xd2, 0xd7, 0xd7,\n    0x6b, 0x57, 0x28, 0xfd, 0x5,  0x94, 0x18, 0xa5, 0xc4, 0x8b, 0x55, 0x28,\n    0x2e, 0x88, 0xae, 0xf0, 0x72, 0x13, 0x4a, 0x8e, 0x32, 0x55, 0x8d, 0x49,\n    0xd2, 0x5,  0x4a, 0x88, 0x37, 0x4d, 0xba, 0xa0, 0xf0, 0xa9, 0x6,  0x2b,\n    0x17, 0x9c, 0xcb, 0x2d, 0x9c, 0xd3, 0x79, 0x1d, 0xc,  0xa2, 0xd0, 0x11,\n    0x3f, 0xa,  0xb6, 0xcc, 0x93, 0x2e, 0x28, 0x4c, 0xbc, 0x8,  0xbb, 0x6c,\n    0x9b, 0x1b, 0x29, 0x5d, 0x50, 0x88, 0xf8, 0x88, 0xb0, 0xcb, 0x8,  0xa4,\n    0x8b, 0x5f, 0x25, 0xec, 0x32, 0x1,  0xa9, 0xe2, 0xcb, 0x18, 0x76, 0x25,\n    0x45, 0x5a, 0x55, 0x53, 0xd6, 0xb0, 0x2b, 0x29, 0x52, 0xc4, 0x97, 0x39,\n    0xec, 0x4a, 0x4a, 0xae, 0xe2, 0x4d, 0x8,  0xbb, 0x92, 0x92, 0xdb, 0x9e,\n    0xeb, 0x24, 0xec, 0xc2, 0x4b, 0x99, 0x61, 0x57, 0x8f, 0xb1, 0x60, 0x1f,\n    0x14, 0xe1, 0xee, 0xee, 0x6e, 0xb8, 0xb5, 0xb5, 0x35, 0x9c, 0xf7, 0x5d,\n    0x2e, 0xe2, 0x4d, 0x8c, 0x0,  0x96, 0xd0, 0xc7, 0x4d, 0xf8, 0x2f, 0x41,\n    0x10, 0x9c, 0xb6, 0xdb, 0xed, 0xfe, 0xe4, 0x66, 0xe6, 0xe2, 0x49, 0xfa,\n    0x42, 0x7c, 0xce, 0xd9, 0x87, 0x56, 0xab, 0x15, 0x96, 0xd2, 0x99, 0x8a,\n    0x27, 0xe9, 0x91, 0x60, 0x8d, 0xc1, 0xde, 0xb,  0xf9, 0x99, 0x89, 0x37,\n    0x35, 0xec, 0x4a, 0x80, 0x8f, 0xcf, 0xa1, 0x97, 0x15, 0xc8, 0x0,  0x11,\n    0x76, 0xd9, 0xb6, 0xf5, 0x99, 0xa4, 0xaf, 0xc4, 0x13, 0xcb, 0xb2, 0x9e,\n    0xa5, 0x1e, 0xf1, 0x4b, 0x3a, 0xbb, 0x88, 0xc5, 0xf8, 0xa9, 0x22, 0x3,\n    0xa,  0xbb, 0x12, 0x53, 0x4f, 0x2c, 0x9e, 0xc2, 0xae, 0x74, 0x24, 0x12,\n    0x4f, 0x61, 0x57, 0x7a, 0x62, 0x47, 0x6,  0x14, 0x76, 0x65, 0x43, 0xac,\n    0x87, 0xeb, 0x28, 0xec, 0x82, 0xe,  0x10, 0x69, 0xe1, 0x2b, 0x8d, 0x78,\n    0xa,  0xbb, 0xb2, 0x27, 0x72, 0xc4, 0x17, 0x14, 0x76, 0x95, 0x9d, 0xe5,\n    0x23, 0x9e, 0x22, 0x80, 0xfc, 0x58, 0x58, 0xd5, 0x90, 0xf4, 0x7c, 0x99,\n    0x2b, 0x9e, 0xa4, 0xe7, 0xcf, 0x83, 0xa9, 0x86, 0xc2, 0x2e, 0x39, 0xcc,\n    0x8c, 0x78, 0x92, 0x2e, 0x8f, 0xe9, 0x88, 0x17, 0xd3, 0xb,  0x96, 0x8b,\n    0x97, 0xf8, 0xc0, 0x25, 0xe9, 0x12, 0x98, 0x8e, 0x78, 0xdb, 0xb6, 0x4f,\n    0x80, 0xe6, 0x74, 0x69, 0x84, 0x75, 0xfc, 0xf8, 0x61, 0xda, 0x3,  0x42,\n    0x16, 0x3c, 0x1c, 0xf1, 0x38, 0xda, 0x69, 0x71, 0x24, 0x99, 0x50, 0x3c,\n    0x63, 0xb4, 0x2a, 0x95, 0x8d, 0x96, 0x2f, 0x91, 0x28, 0x3,  0x63, 0xf1,\n    0x56, 0x1f, 0x8,  0xa9, 0x84, 0xe2, 0xd7, 0xd6, 0x1e, 0x7f, 0x2,  0x42,\n    0x2a, 0xa1, 0xf8, 0x46, 0xa3, 0x31, 0x14, 0xdd, 0x4e, 0x40, 0x48, 0x63,\n    0x3a, 0xc7, 0x7,  0x41, 0x70, 0x88, 0xf2, 0x87, 0x40, 0x48, 0x61, 0x2a,\n    0x7e, 0xdc, 0xd7, 0xb7, 0x47, 0xf2, 0xe5, 0x30, 0x53, 0xd5, 0x8c, 0xdf,\n    0x6e, 0xb4, 0x87, 0xf5, 0x7d, 0x1f, 0x88, 0x5c, 0x99, 0xbb, 0x3,  0x35,\n    0x5a, 0xc9, 0xda, 0x18, 0xb,  0x5b, 0x9b, 0x40, 0xe4, 0x1,  0x9f, 0x5b,\n    0xc7, 0x8b, 0x69, 0x87, 0x31, 0x46, 0x23, 0x3f, 0x47, 0x16, 0x2e, 0xa0,\n    0x48, 0x7e, 0xbe, 0x44, 0x6e, 0x76, 0xfb, 0xbe, 0x5f, 0xbf, 0xbf, 0xff,\n    0x4d, 0x9b, 0xdd, 0xd9, 0xc2, 0x23, 0x23, 0x3,  0x51, 0xe3, 0x57, 0xab,\n    0x8f, 0x71, 0xe4, 0x5b, 0xb4, 0xc8, 0xca, 0x90, 0x58, 0xd,  0x4d, 0xae,\n    0xeb, 0x76, 0xf1, 0x5f, 0xe,  0x80, 0x48, 0xb,  0x8f, 0x15, 0x92, 0x39,\n    0x8e, 0xd3, 0x61, 0x8c, 0x9f, 0x2,  0x91, 0x9a, 0xd8, 0xe9, 0x64, 0xbb,\n    0xed, 0x9c, 0x90, 0xfc, 0xf4, 0x24, 0x8a, 0x85, 0x49, 0x7e, 0x7a, 0x52,\n    0x9d, 0x8,  0xb9, 0xbe, 0xfe, 0x71, 0x54, 0xa9, 0xd8, 0x67, 0x40, 0xc4,\n    0x85, 0xa7, 0x3e, 0x8a, 0x83, 0xab, 0xdc, 0xe,  0xee, 0xd7, 0x5e, 0x0,\n    0x11, 0x87, 0xf4, 0xe2, 0x5,  0xa3, 0x7e, 0x1c, 0x10, 0xaf, 0x2c, 0xa4,\n    0xd6, 0x90, 0x15, 0x40, 0x4f, 0x17, 0x99, 0x1e, 0xb7, 0xe4, 0x9c, 0x5f,\n    0x52, 0xbe, 0x13, 0x9,  0x67, 0x2c, 0xd8, 0xce, 0x6c, 0xcf, 0x55, 0x24,\n    0x9b, 0x14, 0x31, 0x44, 0x12, 0x1e, 0x30, 0x16, 0x71, 0x4c, 0xa6, 0x9b,\n    0xdd, 0x94, 0xef, 0x2c, 0x6,  0xf7, 0x39, 0x7c, 0xc6, 0xe0, 0xcd, 0xe4,\n    0x48, 0x7d, 0xe6, 0x5d, 0x6,  0x24, 0x7f, 0x16, 0xb1, 0xb1, 0x24, 0x4a,\n    0xef, 0xdb, 0xdb, 0xdb, 0xed, 0x76, 0xbb, 0xd9, 0x9d, 0xde, 0x87, 0x9c,\n    0x28, 0x28, 0x5c, 0x33, 0xfb, 0xb5, 0x29, 0x13, 0x46, 0xf2, 0xff, 0x60,\n    0xa9, 0xc9, 0x5f, 0x81, 0x1c, 0x7a, 0x8e, 0xd3, 0xdc, 0x6,  0xd,  0xc8,\n    0xb5, 0xa1, 0x49, 0x24, 0x9b, 0x8e, 0xf3, 0xf4, 0x35, 0x8a, 0xff, 0x8,\n    0xc4, 0xc,  0x52, 0x3a, 0xc9, 0x28, 0x5c, 0x7b, 0x88, 0xb4, 0x16, 0x3e,\n    0xca, 0x77, 0x66, 0x91, 0xda, 0x3b, 0x49, 0xf2, 0xff, 0x21, 0xbd, 0x69,\n    0x55, 0xc8, 0xf,  0x2,  0xf6, 0xe,  0xc,  0xa7, 0x90, 0x6e, 0xe1, 0x8d,\n    0x8d, 0xd6, 0x39, 0x96, 0x7d, 0x87, 0x60, 0x30, 0x85, 0xb5, 0x69, 0xe3,\n    0x42, 0xab, 0x8b, 0x8b, 0x8b, 0x1d, 0x53, 0x3b, 0xd7, 0xa,  0xed, 0x8f,\n    0x37, 0xb9, 0x73, 0xad, 0xf0, 0x83, 0x9,  0xa6, 0x86, 0x6b, 0x4a, 0x9c,\n    0x8,  0x31, 0x31, 0xdf, 0x51, 0xe6, 0x28, 0x8e, 0x69, 0xf2, 0x95, 0x3a,\n    0x3,  0x25, 0xe4, 0x57, 0xab, 0xd5, 0x1d, 0xbc, 0xd4, 0xe2, 0x37, 0x59,\n    0xd3, 0xa0, 0xdc, 0xe1, 0x33, 0x53, 0x3a, 0xd7, 0x94, 0x3c, 0xf5, 0x67,\n    0x42, 0xb8, 0xa6, 0xf4, 0x71, 0xcb, 0x32, 0x87, 0x6b, 0xca, 0x9f, 0x73,\n    0x2d, 0x6b, 0xbe, 0xa3, 0xc5, 0x1,  0xe3, 0x55, 0xe5, 0xeb, 0xb4, 0xa,\n    0xd6, 0xe6, 0x64, 0xf7, 0x2a, 0xe1, 0x1a, 0x6e, 0x26, 0x6b, 0x53, 0xd,\n    0xe5, 0xba, 0xf5, 0x97, 0x7,  0x4b, 0x3a, 0xd7, 0xc2, 0x7e, 0x95, 0xff,\n    0x7f, 0x95, 0x40, 0x65, 0xfe, 0x2,  0x3a, 0xa9, 0x7,  0x3e, 0xc6, 0x76,\n    0x0,  0xc7, 0x0,  0x0,  0x0,  0x0,  0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,\n    0x60, 0x82};\n\nconst char* kArrowLeftUrl = \"kArrowLeft\";\n\nconst int kArrowLeftLength = sizeof(kArrowLeft) / sizeof(kArrowLeft[0]);\n\nconst uint8_t kArrowRight[] = {\n    0x89, 0x50, 0x4e, 0x47, 0xd,  0xa,  0x1a, 0xa,  0x0,  0x0,  0x0,  0xd,\n    0x49, 0x48, 0x44, 0x52, 0x0,  0x0,  0x0,  0x5e, 0x0,  0x0,  0x0,  0x5c,\n    0x8,  0x6,  0x0,  0x0,  0x0,  0xe7, 0x1a, 0x2,  0x65, 0x0,  0x0,  0x0,\n    0x9,  0x70, 0x48, 0x59, 0x73, 0x0,  0x0,  0x21, 0x38, 0x0,  0x0,  0x21,\n    0x38, 0x1,  0x45, 0x96, 0x31, 0x60, 0x0,  0x0,  0x0,  0x1,  0x73, 0x52,\n    0x47, 0x42, 0x0,  0xae, 0xce, 0x1c, 0xe9, 0x0,  0x0,  0x0,  0x4,  0x67,\n    0x41, 0x4d, 0x41, 0x0,  0x0,  0xb1, 0x8f, 0xb,  0xfc, 0x61, 0x5,  0x0,\n    0x0,  0x3,  0xea, 0x49, 0x44, 0x41, 0x54, 0x78, 0x1,  0xed, 0x9d, 0x4b,\n    0x52, 0xdb, 0x40, 0x10, 0x86, 0x7b, 0xe4, 0x2c, 0xa8, 0x98, 0x45, 0xbc,\n    0xf1, 0x83, 0x95, 0x7d, 0x82, 0x10, 0x2e, 0x10, 0x8a, 0x1b, 0xc4, 0x17,\n    0xa0, 0xc8, 0x5,  0x2,  0xb9, 0x0,  0x76, 0xe,  0x90, 0xf8, 0x4,  0xc4,\n    0xb9, 0x40, 0x8e, 0x0,  0x9c, 0x0,  0x72, 0x3,  0xb1, 0x33, 0xb6, 0x93,\n    0xa,  0x1b, 0xcc, 0x6,  0x69, 0xd2, 0x23, 0x83, 0x83, 0x4a, 0x2f, 0x6b,\n    0x34, 0x1a, 0x49, 0x9e, 0xfe, 0x8a, 0x57, 0x21, 0xb3, 0xf9, 0xdc, 0x35,\n    0xf4, 0xfc, 0x6e, 0x8d, 0x1,  0x8,  0x82, 0xa8, 0x8,  0x93, 0xc9, 0xa4,\n    0x2b, 0x3e, 0xa1, 0x82, 0x30, 0xa8, 0x20, 0x93, 0xc9, 0x7c, 0xdf, 0xb2,\n    0xf8, 0x37, 0xfc, 0x71, 0xf7, 0xe9, 0x57, 0x36, 0x63, 0xd0, 0x6f, 0x36,\n    0x9b, 0xbf, 0xa0, 0x22, 0x54, 0x4e, 0xbc, 0xa8, 0x70, 0xcb, 0xaa, 0xd9,\n    0x21, 0x97, 0x38, 0x7e, 0x1c, 0xb5, 0x5a, 0xad, 0x1f, 0x50, 0x1,  0x2c,\n    0xa8, 0x18, 0x96, 0x65, 0xd,  0x22, 0x2e, 0x61, 0x11, 0xb1, 0xef, 0xb7,\n    0xb7, 0xb7, 0xc7, 0x50, 0x1,  0x2a, 0x27, 0x1e, 0xe5, 0xbe, 0x8d, 0xbb,\n    0xc8, 0x98, 0xf5, 0x75, 0x3e, 0x9f, 0x9f, 0x42, 0xc9, 0xa9, 0xa0, 0x78,\n    0x78, 0x93, 0x70, 0x9d, 0xb9, 0x2e, 0x3f, 0x2d, 0xbb, 0xfc, 0x2a, 0x8a,\n    0x5f, 0x87, 0xd2, 0xcb, 0xdf, 0x54, 0xf1, 0x2,  0x21, 0x7f, 0x30, 0x9b,\n    0xcd, 0xce, 0xa0, 0x84, 0x6c, 0xb2, 0x78, 0xf,  0xce, 0xe1, 0x68, 0x3a,\n    0x9d, 0xfe, 0xb4, 0x6d, 0x3b, 0x69, 0x89, 0xd2, 0xca, 0xc6, 0x8b, 0x5f,\n    0xc2, 0x3e, 0xbc, 0x7e, 0x5d, 0x3f, 0x2f, 0x93, 0x7c, 0x43, 0xc4, 0x7b,\n    0xbc, 0x43, 0xf9, 0x57, 0x65, 0xd9, 0xe9, 0x9a, 0x24, 0x5e, 0xd0, 0xc3,\n    0xcd, 0xd7, 0x79, 0x19, 0xe4, 0x9b, 0x26, 0x5e, 0x50, 0xa,  0xf9, 0x26,\n    0x8a, 0x17, 0x78, 0xf2, 0xb1, 0xe3, 0xd9, 0x85, 0x82, 0x30, 0x55, 0xbc,\n    0xa0, 0x87, 0x1d, 0x4f, 0x61, 0xf2, 0x4d, 0x16, 0x2f, 0x68, 0xa0, 0xfc,\n    0x2b, 0x6c, 0x37, 0xf,  0x41, 0x33, 0xa6, 0x8b, 0x17, 0x14, 0x12, 0xae,\n    0x91, 0xf8, 0x25, 0xda, 0xc3, 0x35, 0x12, 0xff, 0x1f, 0xad, 0xf9, 0xe,\n    0x89, 0xf7, 0xa3, 0x4d, 0x3e, 0x89, 0xf,  0xa2, 0x25, 0x5c, 0x23, 0xf1,\n    0x11, 0xe4, 0x1d, 0xae, 0x25, 0xbe, 0xe6, 0x5a, 0xb6, 0x57, 0xf1, 0x71,\n    0xe3, 0x73, 0x81, 0xdf, 0xba, 0xa0, 0x8f, 0xeb, 0xc5, 0xe2, 0xfe, 0xa0,\n    0xd7, 0xeb, 0xdd, 0x81, 0x42, 0x42, 0xc5, 0x8b, 0x67, 0x79, 0x7b, 0x7b,\n    0xfb, 0x13, 0x3e, 0xeb, 0xc7, 0x9c, 0xf3, 0x52, 0xc5, 0xa9, 0x5,  0x61,\n    0xbb, 0xae, 0x73, 0xd0, 0xe9, 0x74, 0x6e, 0x40, 0x11, 0x1,  0xf1, 0xd3,\n    0xe9, 0xef, 0x43, 0xc6, 0xf8, 0x88, 0x84, 0x7,  0x50, 0x2a, 0xdf, 0x27,\n    0x7e, 0x36, 0xfb, 0x83, 0x55, 0xee, 0x8c, 0x80, 0x88, 0x42, 0x99, 0xfc,\n    0x95, 0xf8, 0x98, 0x79, 0x15, 0xc2, 0x8f, 0x92, 0xe1, 0xa9, 0x55, 0x57,\n    0x13, 0x33, 0xaf, 0x42, 0xf8, 0x51, 0x12, 0xae, 0xbd, 0x68, 0x27, 0x99,\n    0xf6, 0xa0, 0xa8, 0xc2, 0x64, 0xe,  0xd7, 0x3c, 0xf1, 0xb8, 0xcc, 0xec,\n    0x3,  0x91, 0x96, 0x4c, 0xe1, 0x1a, 0x6d, 0xa0, 0xb2, 0x21, 0x1d, 0xae,\n    0x91, 0xf8, 0xec, 0x48, 0xe5, 0x3b, 0x24, 0x5e, 0xd,  0xa9, 0xe5, 0x93,\n    0x78, 0x75, 0xa4, 0xa,  0xd7, 0x48, 0xbc, 0x62, 0xd6, 0xd,  0xd7, 0x48,\n    0x7c, 0x2e, 0x24, 0x4f, 0xae, 0x91, 0xf8, 0xfc, 0x88, 0x9d, 0x5c, 0x23,\n    0xf1, 0xf9, 0x12, 0x39, 0x3c, 0x45, 0xe2, 0xf3, 0x27, 0x54, 0x3e, 0x89,\n    0xd7, 0x43, 0x60, 0x72, 0x8d, 0xc4, 0xeb, 0xc3, 0x17, 0xae, 0x91, 0x78,\n    0xbd, 0x88, 0x70, 0xcd, 0x5b, 0x76, 0x48, 0xbc, 0x7e, 0x1a, 0xb5, 0x5a,\n    0xed, 0x8c, 0xc4, 0x17, 0x0,  0x56, 0xfd, 0x3e, 0x89, 0x2f, 0x8,  0x12,\n    0x5f, 0xc,  0x8c, 0xc4, 0x17, 0x4,  0x89, 0x2f, 0x6,  0x4e, 0xe2, 0xb,\n    0x82, 0xc4, 0x17, 0x0,  0x76, 0x35, 0xe3, 0x57, 0x40, 0x68, 0x86, 0xdb,\n    0x9c, 0xbb, 0x5f, 0x48, 0xbc, 0x46, 0x18, 0x83, 0xbf, 0xf8, 0xb5, 0x2f,\n    0x26, 0xd1, 0x68, 0xa9, 0xd1, 0xc4, 0x52, 0x3a, 0x1c, 0x3c, 0x4f, 0xa0,\n    0x91, 0x78, 0x2d, 0x70, 0xdb, 0x71, 0x9c, 0xbd, 0x97, 0x63, 0x7f, 0x24,\n    0x3e, 0x77, 0xb8, 0xed, 0xba, 0x6e, 0x60, 0xd0, 0x95, 0xc4, 0xe7, 0x4a,\n    0xb8, 0x74, 0x1,  0x89, 0xcf, 0xd,  0x7e, 0xbd, 0x58, 0x2c, 0xf6, 0xa2,\n    0x46, 0xba, 0x49, 0x7c, 0xe,  0x60, 0x9f, 0x7e, 0x81, 0xd2, 0x63, 0x6f,\n    0xdf, 0xa1, 0x76, 0x52, 0x39, 0x7c, 0xdc, 0x6e, 0xb7, 0x8e, 0x92, 0x1e,\n    0x45, 0x15, 0xaf, 0x10, 0xdc, 0x18, 0x8d, 0x5a, 0xad, 0x64, 0xe9, 0x2,\n    0x12, 0xaf, 0xc,  0x3e, 0x6c, 0xb7, 0xdb, 0x27, 0xeb, 0x3e, 0x9a, 0xc4,\n    0x2b, 0x81, 0xf,  0xb1, 0xd2, 0x7,  0x69, 0xfe, 0xc2, 0x5b, 0xe3, 0xb1,\n    0xb9, 0xbf, 0xb3, 0xac, 0x1a, 0x10, 0xa9, 0xe1, 0xb8, 0xbc, 0x7c, 0xc6,\n    0x4a, 0x4f, 0x7d, 0xc3, 0x9e, 0x57, 0xf1, 0xf5, 0x7a, 0xfd, 0x6,  0xb7,\n    0xb4, 0x4a, 0x6f, 0xa0, 0x35, 0x0,  0xee, 0xba, 0xf0, 0x51, 0x46, 0xba,\n    0xc0, 0x13, 0xdf, 0x68, 0x34, 0xee, 0x5c, 0x97, 0x5d, 0x2,  0xb1, 0x16,\n    0x22, 0x77, 0x59, 0xde, 0x76, 0xd9, 0x1c, 0x83, 0x24, 0xab, 0x35, 0x9e,\n    0xf3, 0xc7, 0x13, 0xaa, 0xfa, 0x64, 0x9e, 0xc3, 0x2e, 0xdc, 0x18, 0x5d,\n    0x42, 0x6,  0x56, 0xe2, 0xc5, 0xe,  0xeb, 0xf1, 0xd1, 0x1d, 0x2,  0x11,\n    0x43, 0x30, 0xec, 0x92, 0xc5, 0xd7, 0xd5, 0xec, 0xec, 0xb4, 0x47, 0x8e,\n    0xe3, 0x52, 0xe5, 0x87, 0x12, 0x9d, 0xbb, 0xc8, 0x10, 0x7a, 0x88, 0xc4,\n    0x72, 0xb2, 0xd5, 0x1a, 0x58, 0x16, 0xbc, 0xc7, 0x87, 0x74, 0xc1, 0x78,\n    0xd4, 0x4a, 0x17, 0x24, 0x1e, 0x9b, 0x22, 0xee, 0x6a, 0xd8, 0xda, 0xda,\n    0x2a, 0xcf, 0x99, 0xbc, 0xda, 0x8f, 0x4d, 0xf1, 0xc2, 0x2e, 0x3d, 0xc7,\n    0xa6, 0x94, 0x99, 0xe9, 0x74, 0x26, 0xce, 0x5b, 0xe8, 0x82, 0x6,  0x44,\n    0xd8, 0xf5, 0xf0, 0x70, 0xdf, 0x57, 0x2d, 0x5d, 0x40, 0x21, 0x59, 0x24,\n    0xeb, 0x85, 0x5d, 0xb2, 0x50, 0x64, 0x10, 0x42, 0x9a, 0xb0, 0x4b, 0x16,\n    0x12, 0x1f, 0x20, 0x5d, 0xd8, 0x25, 0xb,  0x89, 0xf7, 0x91, 0x3e, 0xec,\n    0x92, 0x85, 0xd6, 0xf8, 0x25, 0xd2, 0x61, 0x97, 0x2c, 0x24, 0xfe, 0x29,\n    0xec, 0xea, 0x74, 0xda, 0x63, 0xd0, 0x88, 0xd1, 0xe2, 0x45, 0xee, 0x82,\n    0x11, 0x40, 0x3f, 0x6b, 0xee, 0x22, 0x83, 0xb1, 0xe2, 0x5f, 0x84, 0x5d,\n    0x85, 0xbc, 0x31, 0xa3, 0xa1, 0xff, 0x5c, 0xd5, 0x85, 0x5d, 0xb2, 0x18,\n    0x28, 0x5e, 0x7d, 0xee, 0x22, 0x83, 0x61, 0xe2, 0xcb, 0x21, 0x5d, 0x60,\n    0x90, 0xf8, 0xf8, 0xc9, 0x2e, 0xdd, 0x18, 0x21, 0x7e, 0x9d, 0xc9, 0x2e,\n    0xdd, 0x18, 0xd0, 0xd5, 0xe4, 0x1b, 0x76, 0xc9, 0xb2, 0xd1, 0x15, 0xaf,\n    0x23, 0xec, 0x92, 0x65, 0x83, 0xc5, 0xeb, 0x9,  0xbb, 0x64, 0xd9, 0x50,\n    0xf1, 0xfa, 0xc2, 0x2e, 0x59, 0xaa, 0xf8, 0x9e, 0xdd, 0x71, 0x9b, 0x1e,\n    0x11, 0x76, 0x9d, 0x94, 0x5d, 0xba, 0xa0, 0x72, 0xe2, 0x19, 0xe3, 0xc3,\n    0x88, 0x29, 0x88, 0x4c, 0x93, 0x5d, 0xba, 0xf9, 0x7,  0xe7, 0xd1, 0xe8,\n    0x93, 0xfe, 0x27, 0xd6, 0x94, 0x0,  0x0,  0x0,  0x0,  0x49, 0x45, 0x4e,\n    0x44, 0xae, 0x42, 0x60, 0x82};\n\nconst char* kArrowRightUrl = \"kArrowRight\";\n\nconst int kArrowRightLength = sizeof(kArrowRight) / sizeof(kArrowRight[0]);\n\nconst uint8_t kHideKeyboard[] = {\n    0x89, 0x50, 0x4e, 0x47, 0xd,  0xa,  0x1a, 0xa,  0x0,  0x0,  0x0,  0xd,\n    0x49, 0x48, 0x44, 0x52, 0x0,  0x0,  0x0,  0x68, 0x0,  0x0,  0x0,  0x41,\n    0x8,  0x6,  0x0,  0x0,  0x0,  0x3d, 0xbf, 0xe1, 0x5f, 0x0,  0x0,  0x0,\n    0x9,  0x70, 0x48, 0x59, 0x73, 0x0,  0x0,  0x21, 0x38, 0x0,  0x0,  0x21,\n    0x38, 0x1,  0x45, 0x96, 0x31, 0x60, 0x0,  0x0,  0x0,  0x1,  0x73, 0x52,\n    0x47, 0x42, 0x0,  0xae, 0xce, 0x1c, 0xe9, 0x0,  0x0,  0x0,  0x4,  0x67,\n    0x41, 0x4d, 0x41, 0x0,  0x0,  0xb1, 0x8f, 0xb,  0xfc, 0x61, 0x5,  0x0,\n    0x0,  0x3,  0xa6, 0x49, 0x44, 0x41, 0x54, 0x78, 0x1,  0xed, 0xdc, 0x6f,\n    0x4e, 0x1a, 0x41, 0x18, 0xc7, 0xf1, 0x67, 0x67, 0x13, 0x63, 0x7c, 0x53,\n    0x7c, 0x21, 0x82, 0xaf, 0xe4, 0x6,  0xde, 0xa0, 0x70, 0x83, 0xf6, 0x4,\n    0xb5, 0x27, 0xa8, 0x9e, 0x0,  0x38, 0x81, 0xf6, 0x4,  0xc5, 0x1b, 0xb4,\n    0x27, 0xc0, 0x1b, 0x48, 0x4f, 0x50, 0xde, 0xe1, 0xc2, 0xb,  0x79, 0x3,\n    0x31, 0xb4, 0x3b, 0xd3, 0xf9, 0x49, 0xb5, 0xe2, 0xf2, 0x67, 0x61, 0x67,\n    0x76, 0x67, 0xe1, 0xf9, 0x24, 0x8d, 0x84, 0x40, 0x63, 0xfc, 0x66, 0x60,\n    0x77, 0xe7, 0x1,  0x22, 0xe6, 0x34, 0x2f, 0xee, 0x3,  0xfb, 0xfd, 0xfe,\n    0x19, 0x91, 0xff, 0x5e, 0xca, 0xf0, 0xcc, 0xf3, 0x54, 0xc1, 0xf3, 0xbc,\n    0x2,  0xb1, 0x44, 0xa4, 0xa4, 0xae, 0x10, 0x7e, 0x27, 0xc,  0x27, 0x3f,\n    0xca, 0xe5, 0x72, 0x77, 0xde, 0x63, 0x56, 0x6,  0xea, 0xf5, 0x6,  0x55,\n    0xdf, 0x57, 0x75, 0xa5, 0xa8, 0x4a, 0xcc, 0x1a, 0xcf, 0xa3, 0x5b, 0xfd,\n    0xe3, 0xb2, 0x58, 0x2c, 0x76, 0x5e, 0xdf, 0x2f, 0x96, 0x3d, 0x29, 0x8,\n    0x6,  0x75, 0x21, 0x54, 0x9b, 0xe3, 0xd8, 0x87, 0xbf, 0xb1, 0xfe, 0x77,\n    0x37, 0x18, 0xc,  0xea, 0xaf, 0xef, 0x5f, 0xb8, 0x82, 0xfa, 0xfd, 0xc1,\n    0x95, 0x52, 0xea, 0x82, 0x58, 0xea, 0x84, 0xf0, 0x1a, 0x47, 0x47, 0x47,\n    0x4d, 0xdc, 0x9e, 0x1b, 0x8,  0x2b, 0x47, 0x37, 0x6d, 0x10, 0xcb, 0x8c,\n    0x52, 0xf2, 0xb2, 0x54, 0x2a, 0x5d, 0x47, 0x2,  0xf5, 0x7a, 0xbd, 0x53,\n    0xfd, 0xc6, 0xf5, 0x8b, 0x58, 0xd6, 0x86, 0xe3, 0xf1, 0xa8, 0x12, 0x79,\n    0xf,  0x12, 0x42, 0x34, 0x88, 0xb9, 0xa0, 0x70, 0x70, 0x70, 0x70, 0x11,\n    0x59, 0x41, 0x41, 0xd0, 0xc7, 0xea, 0x39, 0x25, 0xe6, 0x82, 0xe1, 0xcc,\n    0xa,  0xd2, 0x2f, 0x6f, 0x55, 0xe2, 0x38, 0x2e, 0x29, 0xcc, 0x4,  0xd2,\n    0x2f, 0x6f, 0x7c, 0xf2, 0xe9, 0x98, 0x99, 0x40, 0x52, 0xf2, 0xd5, 0x1,\n    0xd7, 0x8,  0x62, 0x4e, 0x7b, 0x13, 0x28, 0xec, 0x12, 0x73, 0xca, 0x4c,\n    0xa0, 0xfd, 0xfd, 0xfd, 0x8e, 0xbe, 0x26, 0x34, 0x24, 0xe6, 0x8c, 0x99,\n    0x40, 0x87, 0x87, 0x87, 0x43, 0xfd, 0x3e, 0x74, 0x4b, 0xcc, 0x9,  0xfa,\n    0xda, 0x5c, 0x4b, 0x44, 0xef, 0xfc, 0xf3, 0x95, 0x98, 0x13, 0x94, 0xa,\n    0x9b, 0x91, 0x40, 0x7a, 0x5f, 0xe2, 0x56, 0x97, 0xe3, 0x48, 0x99, 0x53,\n    0x4d, 0xec, 0x11, 0x2d, 0xbc, 0x9a, 0x1d, 0x4,  0x41, 0x4b, 0x5f, 0x4b,\n    0xfd, 0x44, 0x2c, 0x3,  0xea, 0xe6, 0xf8, 0xf8, 0xf8, 0x1c, 0xb7, 0x16,\n    0x1e, 0x66, 0xef, 0xed, 0xed, 0x61, 0xab, 0xa1, 0x43, 0x2c, 0x65, 0xaa,\n    0x33, 0x1e, 0x8f, 0x5f, 0xb6, 0x79, 0x96, 0xee, 0xa8, 0x3e, 0x3c, 0x3c,\n    0x14, 0x26, 0x93, 0xdf, 0x6d, 0x7d, 0xf3, 0x8c, 0x58, 0xa,  0x9e, 0xe2,\n    0xd4, 0x2a, 0x95, 0xca, 0xcb, 0x91, 0xf4, 0xca, 0x2d, 0x6f, 0x8e, 0x94,\n    0x96, 0x68, 0x1c, 0x88, 0x35, 0x34, 0x32, 0x8d, 0x34, 0xb9, 0xd3, 0xf,\n    0x3f, 0x25, 0x66, 0x81, 0xea, 0x4a, 0x29, 0x6b, 0xf3, 0x6,  0x47, 0x62,\n    0x5d, 0xea, 0x99, 0x9e, 0x1f, 0xc9, 0x1a, 0xfe, 0x23, 0x62, 0x86, 0x2d,\n    0x8e, 0x3,  0xb1, 0xc7, 0xae, 0x60, 0xba, 0xdb, 0x2a, 0xda, 0xbc, 0x92,\n    0x4c, 0x59, 0x1e, 0x7,  0xd6, 0xa,  0x4,  0x1c, 0xc9, 0x94, 0xd5, 0x71,\n    0x60, 0xed, 0x40, 0xc0, 0x91, 0x92, 0x8a, 0x17, 0x7,  0x36, 0xa,  0x4,\n    0x1c, 0x69, 0x53, 0xf1, 0xe3, 0xc0, 0xc6, 0x81, 0x0,  0x91, 0x7c, 0xdf,\n    0xbf, 0xd3, 0x97, 0x86, 0x78, 0xa3, 0x2f, 0x96, 0xf5, 0xe2, 0x40, 0xa2,\n    0x40, 0x30, 0x9d, 0xd9, 0xa6, 0x36, 0x47, 0x5a, 0xee, 0xdf, 0x36, 0x4e,\n    0xed, 0xed, 0x68, 0xef, 0xca, 0xe7, 0x91, 0x1,  0x1c, 0x69, 0xb9, 0x4d,\n    0xe3, 0x3c, 0x3d, 0x97, 0xc,  0xe1, 0x48, 0xf3, 0x25, 0x89, 0x3,  0xc6,\n    0x66, 0x12, 0xf0, 0xb,  0x84, 0x61, 0x78, 0x49, 0x6c, 0x46, 0x18, 0x46,\n    0x3f, 0xb1, 0xb0, 0xe,  0x63, 0x2b, 0xe8, 0x99, 0x3e, 0x70, 0x38, 0x17,\n    0xc2, 0xff, 0x46, 0xc,  0x9f, 0xff, 0xf9, 0x5c, 0x2e, 0x17, 0x5b, 0x94,\n    0x80, 0xf1, 0x40, 0xc0, 0x91, 0xcc, 0xc4, 0x1,  0x2b, 0x63, 0x57, 0xfa,\n    0x30, 0xb2, 0x15, 0x86, 0x72, 0x87, 0x5f, 0xee, 0xb0, 0x1b, 0x9a, 0x3c,\n    0xe,  0x58, 0x9b, 0x8b, 0x3b, 0x39, 0x29, 0x5d, 0x4b, 0xa9, 0x9a, 0xb4,\n    0x73, 0x54, 0x53, 0xef, 0x86, 0x36, 0xc8, 0x10, 0x2b, 0x2f, 0x71, 0xaf,\n    0xf5, 0x7a, 0x41, 0x43, 0x8,  0xaf, 0x4e, 0x3b, 0xc1, 0x6c, 0x1c, 0xb0,\n    0x1e, 0x8,  0x76, 0x23, 0x92, 0xf9, 0x38, 0x90, 0x4a, 0x20, 0xd8, 0xee,\n    0x48, 0x76, 0xe2, 0x40, 0x6a, 0x81, 0x60, 0x3b, 0x23, 0xd9, 0x8b, 0x3,\n    0xa9, 0x6,  0x82, 0xfb, 0xfb, 0xfe, 0xb5, 0x3e, 0xbb, 0xfe, 0x42, 0x5b,\n    0xc1, 0x6e, 0x1c, 0x48, 0x3d, 0x10, 0x6c, 0xc7, 0xcc, 0xdd, 0xff, 0xd9,\n    0x35, 0x9b, 0x32, 0x9,  0x4,  0xf9, 0x8e, 0x94, 0x4e, 0x1c, 0xc8, 0x2c,\n    0x10, 0xe4, 0x33, 0x52, 0x7a, 0x71, 0x20, 0xd3, 0xf,  0x70, 0xe5, 0x6f,\n    0x7a, 0x75, 0x76, 0xea, 0x33, 0xd,  0x99, 0xae, 0x20, 0xc8, 0xcf, 0x60,\n    0xe4, 0xfc, 0xc1, 0x42, 0xdb, 0x32, 0xf,  0x4,  0xee, 0x47, 0xca, 0x26,\n    0xe,  0x38, 0x11, 0x8,  0xdc, 0x9d, 0x5e, 0x5d, 0x7f, 0x8e, 0xc0, 0x24,\n    0x67, 0x3e, 0x44, 0xec, 0xe6, 0xf4, 0x6a, 0xb6, 0x71, 0xc0, 0x99, 0x15,\n    0xf4, 0xcc, 0x9d, 0x71, 0xae, 0xec, 0xe3, 0x80, 0x73, 0x81, 0x20, 0xfb,\n    0x48, 0x6e, 0xc4, 0x1,  0x27, 0x3,  0x41, 0x76, 0x91, 0xdc, 0x89, 0x3,\n    0xce, 0x6,  0x82, 0xf4, 0x23, 0xb9, 0x15, 0x7,  0x9c, 0xe,  0x4,  0xe9,\n    0x4d, 0xaf, 0xba, 0x17, 0x7,  0x9c, 0xf,  0x4,  0xb6, 0x67, 0xee, 0x92,\n    0xce, 0xae, 0xd9, 0x94, 0x8b, 0x40, 0x60, 0x2b, 0x92, 0xcb, 0x71, 0x20,\n    0x37, 0x81, 0xc0, 0x74, 0x24, 0xd7, 0xe3, 0x40, 0xae, 0xbe, 0xed, 0xca,\n    0xf4, 0xf4, 0x6a, 0xd2, 0xa9, 0xcf, 0x34, 0xe4, 0x6a, 0x5,  0x3d, 0x33,\n    0x31, 0x18, 0x69, 0x6a, 0xb0, 0xd0, 0xb6, 0x5c, 0x6,  0x82, 0x24, 0x91,\n    0xf2, 0x12, 0x7,  0x72, 0x1b, 0x8,  0xf4, 0x86, 0xdf, 0x7,  0xfd, 0xe3,\n    0x2a, 0xee, 0x79, 0x12, 0xde, 0x73, 0xf4, 0x4b, 0xe4, 0x47, 0x7c, 0x1f,\n    0x11, 0xe5, 0x44, 0xae, 0xbf, 0x71, 0x51, 0xef, 0x6c, 0x7e, 0xc7, 0xb9,\n    0x8b, 0x94, 0xea, 0x66, 0xd9, 0x45, 0x56, 0x84, 0xc1, 0x94, 0xeb, 0x68,\n    0x34, 0xaa, 0xe4, 0x29, 0xe,  0xe4, 0x7a, 0x5,  0xbd, 0x35, 0xfd, 0xd6,\n    0x62, 0x51, 0x55, 0x4a, 0x15, 0xf4, 0x15, 0x88, 0x77, 0x42, 0x10, 0xe,\n    0x2a, 0x7e, 0x3e, 0x3e, 0x3e, 0x76, 0xb2, 0xd8, 0xcb, 0x61, 0x3b, 0xe0,\n    0x2f, 0xcf, 0xad, 0xa,  0x93, 0x40, 0xbe, 0x2e, 0xa3, 0x0,  0x0,  0x0,\n    0x0,  0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82};\n\nconst char* kHideKeyboardUrl = \"kHideKeyboard\";\n\nconst int kHideKeyboardLength =\n    sizeof(kHideKeyboard) / sizeof(kHideKeyboard[0]);\n\nconst char* kKeyBackground =\n    \"data:image/\"\n    \"png;base64,iVBORw0KGgoAAAANSUhEUgAAAGEAAABJCAYAAAAt+\"\n    \"Uj4AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAA1\"\n    \"ASURBVHgBzVtdmuQoDlS4snvm/tfbl73B3iDZsg04FEiYzKqaafrLNgZJCIV+sDML//nv/\"\n    \"0oxs/2/Zzmv7b7Ivcmcmb+2/t5+/\"\n    \"W32+G224RoDXwvdl2t8mDPhl7k2xvc4aApd64f75Xl87Pl5tc/\"\n    \"r3t+vxyafl1JW6EMKFFqYDWHEpuMF9R40D3s8ydhP88bf21OM7/\"\n    \"o2Gv8wxlYB2M711FC9o3zUKWTsRneoX/UH6QCILUDSyrn3neYEBScYVvu7kjsgzfhWP3v/\"\n    \"EPa0uJX8tlCntA21IRANThA6ABoBNnp85P1R+/\"\n    \"3XCcAOBjfn1dVYKNdEYaAYpNqHLgTP00FiPgLjRKtUW2xnRHxu/\"\n    \"ARoj47TKLCNAGl9AqSYV8yNmVcgAqfAzftIKFeUFe7beJ81fJh9/\"\n    \"D4BwJBLbGQOIqLTkdfTkJcDwUoYjihoiepzst3vnr//w3Ya/\"\n    \"6T4jIhPxcvzvFpNW8ZR0r2xXIZihTTdqL4MYI2Gh9aCDAAvQBoZ+\"\n    \"9eehh4XAM6AzUAtClRJsbZLSQoigefYYC4LgPpBLJGg/\"\n    \"bNVYHab7xHSlHqeQJbds54eBAdGIS8vMRCF9TivDy3GnZaBtcT+kiZ2A/\"\n    \"z6i4oxLu90KYbSj7NNcbpdutMapaWwiM8WgGgCinhBS2Of49s+\"\n    \"tzXDPo2BwgyIwd1LeDuAMADAkaCteXcZx/a2p6GPFgU0Tqm4txKNk/G4VjgVdJycD0Vkt/\"\n    \"3w4eAwNnqhbnOo4Bx+XwE4C/\"\n    \"ipFOrniJRaeM71Wr2g9OH61oJJd9KVfbANQuOLt6t3sSfvUQCYrwWNT8agaaWlHzWwDTb3AUVA\"\n    \"FtI14utrN6//7OCoEw19BHXorB/WAKj/d0CuEKrpq1yhy2kkcMSWKh5mZHwkDIQAPH/\"\n    \"3+u3jrAdHQSaefnrhNdQywbouUjQ9WewEWkvYYY5pkt+AKAcQV0SUcnr6KaoZu/Y/\"\n    \"CUuNgEKR0SKlVMXggBBli4BgFQTT8cAjiWc49ez3v/\"\n    \"+28UTEtFdavQzirBqsy8uXoK5opEBE0louHVG/AeBqBIVkA6B5PUeAA2A3/\"\n    \"vPkOOdtBGLY1An2g+\"\n    \"xy7UMNkXhkSz1HLfhl4YlI6QtPaASQFw9ry1i3UwlVO9fnKDBzNag4AHyKOkxMinQVOzbXA99h\"\n    \"SJzPG2fNLofgvkVNTRrVe01o6SR78Op8GgU4PX9PQ8fT8QelJxPDFLI7zEVFFBESeG6aIyIFgr\"\n    \"yAi3aankTWqQPqIYBTklEMtFpx+nPZzgWOB9RWI/oaRTZRFeJ0FHp/\"\n    \"ZAhctEcUfNRngs1cfeh8vHHRpb9+\"\n    \"KOSllcfYyHYZzD1nqHJRRLDnm5dl6pg9Eq51rrTFqamXYw9MVbZwhFiLhOaZlJ4q1a7LwxlNG8\"\n    \"/BG/\"\n    \"p4LfFxHkmjVxRGPEaGzoBwSigQzdsphUTHUSvJHsioGgVaL9hWfH9GhB3H23aU7SQOvCtCWOhl\"\n    \"30vJts6jG29wo8tQ7P09Cmoq2jaiCRpHgm7UZI4cRB6wrusUiAaagsERUfmuVCFRYnJD+\"\n    \"jy7rrhUIkfturlYKObSbj+GnUJ32kePFAShbB6ADsJ2PhU3ALxb8G7c/uXGyy/\"\n    \"m564NeYM3phAIkjUFIhjnFGUSsQNPXweUuuozR6UGGhgVkOHEsS965vEeCd3YrJyNILQogIAwH\"\n    \"C05tKPUYde8maQUUH4282nDZI73RPr2U08ZdXLOEGSALn8ff1ZZFDXHXH2bUUgGxBvhPFGRvSL\"\n    \"iMbxuhvQRAFEjgVNU2Go+Vy/velkQKcxHuVmNOZz4gtQS0gUKOEcoNhjcRaiux45CC/\"\n    \"W0xDJJcLfLZ3ts26gcWBiSaGjGzwBgGebTS19HjMA8RgXZZOPchlRlHoi22SEb2Cg3NTpsKBxP\"\n    \"6hcBr+03Vgok+\"\n    \"Lz1IJDRGIQGjCvMcCxhcwY3n6t5cwjoWUkXMuLBHeBwYRIBG2sFGy1jp0n29BLpZxKJJvtI1nD\"\n    \"piI3frvpa2gSACIRicdMjKsstJUhNbfMUNSEglqehFnGNNIocXW+4h0+\"\n    \"p7iHPbEhhWuTNAtl26dgjwQFAffAcBozCFkVin6vfFqrBGxCDkOLTmQLgTki48rOCeIi8M34Sb\"\n    \"X0fIlOjwX2fw8ZXsGhqb2NNaHIFkACf5eYMLkVXQ9/\"\n    \"VjToWRU8KhPla4lI5jfMeykT29bbVQm9mIzMo4ZtpoWntAQx0A1Fwu1QLIifTzfF4BIAJTVZgh\"\n    \"+8S2CtJ3GAMVdQm4wlvIc9vHs/\"\n    \"3anj9tvKxwc2n7RXvFyfJgYhoKLdaQNvlRsw8VHyN6DILgREY02TOpZU2V+WbREmR/\"\n    \"fF9kT3x7wHacf/2E7XZnNKF9KvhVRmdJ9/\"\n    \"w93N4ZOygX2gdlcXjzjkiuiYP1k9kvVjX65Ov5g8/\"\n    \"ywbFwpiOI5o0G4+6sP5sAqENgQiEO4+0WGH56c9bbUidiTwGoMi1/\"\n    \"rIpBv0uIhAo8i6IIV0Ajq533KuhA4AGL7eRJlZC+uwEMP+\"\n    \"todDpmhwV7h4VhK803NyvMGOBpt8GxFmK4E1n8lbWdGvNnCNLKzLeU1Qd/\"\n    \"jIIUbtLV0OagkSbeFoINAlBtmEa65vOQnfFm2BpTdPXOMNrHQWIPj8CAq9514rm+\"\n    \"0QOhhu7TjATpjB9zTwCAW1gfI4+\"\n    \"fYmJqcf5qb39GAhRi5wteopFxkBjGg0FeYHVk1RZUE5rijO6xVHqxgU8fTvNTvWjIOBmjlNRyo\"\n    \"NkDmsZpI2vAsHHSUcngA9pxcZr3x8BA4n843sZ+5MaKe/\"\n    \"2pvUh2bR7ap2kpNZKch10mXmTtsSjhq8EqP/\"\n    \"jIIQpSMcQjx+tmM1CCrMxBcnGI617Cg8EDiescAM+/\"\n    \"WiqdKnILqfqNUFlJmsM3vlKU8edPlvIIsMXO3ajAxYevlShBdLeAuUxYRpSEfwzxH7ZmC8CY+\"\n    \"ppL7QkSt9vCLvh/aqoMpt/ETjczHP7s2qCzfcaAimFbsrUWrGhZjinC1JSKjLzAOR76J/\"\n    \"a+eNA2BsmE31ulqIQzCHow9L0a8qjchGqNx1EwvMWCO++\"\n    \"8Jq11GvK1NmmQqL0mi6uvIijgoFxbJiI1tQpir0FAuwHGuZTdwaPWnbiSeUgp1EdSjA/\"\n    \"o+e540KT3/ICb/CKF/iWCCdzoKuLEMTAAS/\"\n    \"oOakzWZS4yOC0F6SlpsvbILxqdOZL51Y8ERYbXWTonOr7MhAY+SDzkT48qA7Q+\"\n    \"v9KYcbdXFk30ivGvGNCNreySBlpB0flekLg/\"\n    \"Wuno9BrFnhasdZxHWgbdfMJEFm6GNamKHSRAXMRqjQzHb6UjrI2oH9DG/\"\n    \"bZY26JL54sDVgiO1IIQjfIS8ZYz+HnQrofSa/\"\n    \"bK0b7qfbO+\"\n    \"piMY5X4jUXVwwfv31sxZ2jcyBheW0SfjOZG32W6cBwxDcuMNhgKT9KXpiEVion3a5Te6QFRnIF\"\n    \"bSkcrxvxqe1V+lL5SWoxGU2EZoGF9yAwCD7C7t9ExUem+tSbgxXGdnzhlngaEFpIGhu96s/\"\n    \"XJaJD1cMfL+nAEwwavd3O1fQmECN2ovfKaI5KFCTFuiYgcBBIsTkdevOcNZEUMs+ed0bP+\"\n    \"oSPqKlgh44KMYRxTUfPJTHg0V8ci9llacqLxLx9RI97ZYFOa14mOg87TzcJUNug7WCdeB5FuuB\"\n    \"lrupGOLH9712CrDYtjfU42vypzVZlZtHBNGFoRIzYWAtuNyb0uy8D3n0HyZ9ZWPX0hinPeJA3N\"\n    \"mF/R26UKnWePD6IDgbw2MDi/\"\n    \"gMUybl9bYPJhmq+2VUBDXozGVEO2QQSLTvcTGU+\"\n    \"nkzGNtNnrkBZF31ITVqJoRoMJPRO8yhutFxkpe01y9Pc0VALhjQYq2E33NSL21r7liKpj78rqr\"\n    \"eRrpF4dKcKhjyCViEEHsCWFOPGIjT08G0hE9HmKxi0A8ktNfya06r13LSpuEQ3Z7TL0LePYH7w\"\n    \"YgdPJnI5lQ+owmxJ9FYxIVgZE6uWJEmlYv4L6yibF+zEgG+\"\n    \"ukY6yfOgRHxMuFOdFl2uQXJul62byjhd3m2GVjMfnNwkwzOI+\"\n    \"AFo5hXOdbCvMqMEiuWdtekBndH3xllIEgItRwA83K4nZ5+h0QEfuPPDG/\"\n    \"S5cZbABa3tc3og33f4SHgC+kSTwmrRFYA4LTUGs/\"\n    \"9pc6K2Dc0iWTxwZmJyjcR9FqKorm+RRkFgORydLr/vnxv09YAeOn+A8ZCCJJjWc2rRnZ/\"\n    \"ECU0A8PfAwW/\"\n    \"qGvNxF8XmGOePT9zJ2MsIgkHg6lwTWv9Kxj78PieqR0JpHwloG+\"\n    \"qWGRYNCPc20wt7x4AFAG8gyIO7nN+5V06Yj6XW325c4SEIjHkZFlBsvkcysyrOklkBvhEHm/\"\n    \"aTqyhYbk82pDItOSvluHflwVnnAQrxWlhVcf+jrNHRBIIhW5Y+\"\n    \"zt219bvNNYh21CdNDJ3y8hEhTfDnPIJmYC7gyFRCdYWh++9NriO6JjJne4L0l6UGa+\"\n    \"xej90SJuD4jpdF7rRliobaIzJo73FcN+FxCzvzVGQICMjochCyRFCiu/\"\n    \"hYVcdQ1JQQM5roh4+XdHK8C8A96rMiKv7Y4zMSK/Ur47+2cRFkVEtEYoNkiZ/\"\n    \"weMkAeH1T+t7AAAAABJRU5ErkJggg==\";\n\n}  // namespace keyboard_res\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/soft_keyboard_resources.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_SOFT_KEYBOARD_RESOURCES_H_\n#define CLAY_UI_COMPONENT_SOFT_KEYBOARD_RESOURCES_H_\n\n#include <cstdint>\n\nnamespace clay {\nnamespace keyboard_res {\n\nextern const uint8_t kHideKeyboard[];\nextern const uint8_t kArrowLeft[];\nextern const uint8_t kArrowRight[];\nextern const uint8_t kBackspace[];\nextern const int kHideKeyboardLength;\nextern const int kArrowLeftLength;\nextern const int kArrowRightLength;\nextern const int kBackspaceLength;\nextern const char* kHideKeyboardUrl;\nextern const char* kArrowLeftUrl;\nextern const char* kArrowRightUrl;\nextern const char* kBackspaceUrl;\nextern const char* kKeyBackground;\n\n}  // namespace keyboard_res\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_SOFT_KEYBOARD_RESOURCES_H_\n"
  },
  {
    "path": "clay/ui/component/tappable_image_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <utility>\n\n#include \"clay/ui/component/image_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n\n#ifndef CLAY_UI_COMPONENT_TAPPABLE_IMAGE_VIEW_H_\n#define CLAY_UI_COMPONENT_TAPPABLE_IMAGE_VIEW_H_\n\nnamespace clay {\n\n// Currently only used for soft keyboard.\nclass TappableImageView : public ImageView {\n public:\n  TappableImageView(int id, PageView* page_view) : ImageView(id, page_view) {}\n\n  void SetTapUpCallback(OnTapUpCallback&& callback) {\n    if (!tap_recognizer_) {\n      tap_recognizer_.reset(\n          new TapGestureRecognizer(page_view()->gesture_manager()));\n    }\n    tap_recognizer_->SetTapUpCallback(std::move(callback));\n  }\n\n  void HandleEvent(const PointerEvent& event) override {\n    if (event.type == PointerEvent::EventType::kDownEvent) {\n      tap_recognizer_->AddPointer(event);\n    }\n  }\n\n private:\n  std::unique_ptr<TapGestureRecognizer> tap_recognizer_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TAPPABLE_IMAGE_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/text/base_text_view.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/text/base_text_view.h\"\n\n#include <utility>\n\nnamespace clay {\n\nBaseTextView::BaseTextView(uint32_t id, std::string tag,\n                           std::unique_ptr<RenderObject> render_object,\n                           PageView* page_view)\n    : WithTypeInfo(id, std::move(tag), std::move(render_object), page_view) {\n#if FORCE_TEXTVIEW_FOCUSABLE\n  SetFocusable(true);\n#endif\n}\n\nBaseTextView::~BaseTextView() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/text/base_text_view.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_BASE_TEXT_VIEW_H_\n#define CLAY_UI_COMPONENT_TEXT_BASE_TEXT_VIEW_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/rendering/render_object.h\"\n\nnamespace clay {\n\nclass BaseTextView : public WithTypeInfo<BaseTextView, BaseView> {\n public:\n  BaseTextView(uint32_t id, std::string tag,\n               std::unique_ptr<RenderObject> render_object,\n               PageView* page_view);\n  ~BaseTextView() override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_BASE_TEXT_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/text/inline_spec_styles.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_INLINE_SPEC_STYLES_H_\n#define CLAY_UI_COMPONENT_TEXT_INLINE_SPEC_STYLES_H_\n\nnamespace clay {\n\n// Styles like 'margin' won't be used by clay in most cases, because lynx\n// will deal with it. But inline views need to handle margin itself.\n// For code size consideration, use it independently.\nclass InlineSpecStyles {\n public:\n  void SetInlineMargin(float margin_left, float margin_right) {\n    margin_left_ = margin_left;\n    margin_right_ = margin_right;\n  }\n\n  float margin_left() const { return margin_left_; }\n  float margin_right() const { return margin_right_; }\n\n private:\n  float margin_left_ = 0.f;\n  float margin_right_ = 0.f;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_INLINE_SPEC_STYLES_H_\n"
  },
  {
    "path": "clay/ui/component/text/inline_text_view.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/text/inline_text_view.h\"\n\n#include <memory>\n\n#include \"clay/ui/rendering/text/render_inline_text.h\"\n\nnamespace clay {\n\nInlineTextView::InlineTextView(int id, PageView* page_view)\n    : WithTypeInfo(id, \"inline-text\", std::make_unique<RenderInlineText>(),\n                   page_view) {}\n\nInlineTextView::~InlineTextView() = default;\n\nBaseView* InlineTextView::GetDeepestViewInPos(\n    txt::Paragraph::PositionWithAffinity text_pos) {\n  for (const auto& child : children_) {\n    if (child->Is<InlineTextView>()) {\n      auto view =\n          static_cast<InlineTextView*>(child)->GetDeepestViewInPos(text_pos);\n      if (view != nullptr) {\n        return view;\n      }\n    }\n  }\n  for (const auto& range : range_in_paragraph_) {\n    auto pos = text_pos.position;\n    if (text_pos.affinity == txt::Paragraph::UPSTREAM && pos > 0) {\n      pos--;\n    }\n    if (pos >= range.start() && pos < range.end()) {\n      return this;\n    }\n  }\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/text/inline_text_view.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_INLINE_TEXT_VIEW_H_\n#define CLAY_UI_COMPONENT_TEXT_INLINE_TEXT_VIEW_H_\n\n#include <list>\n#include <utility>\n\n#include \"clay/shell/platform/common/text_range.h\"\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/inline_image_view.h\"\n#include \"clay/ui/component/text/base_text_view.h\"\n\nnamespace clay {\n\nclass InlineTextView : public WithTypeInfo<InlineTextView, BaseTextView> {\n public:\n  InlineTextView(int id, PageView* page_view);\n  ~InlineTextView() override;\n\n  void SetTextRange(std::list<clay::TextRange>& range_in_paragraph) {\n    range_in_paragraph_ = std::move(range_in_paragraph);\n  }\n  BaseView* GetDeepestViewInPos(txt::Paragraph::PositionWithAffinity text_pos);\n\n private:\n  std::list<clay::TextRange> range_in_paragraph_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_INLINE_TEXT_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/text/internal_text_view.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/text/internal_text_view.h\"\n\n#include <algorithm>\n#include <codecvt>\n#include <limits>\n#include <locale>\n#include <utility>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/component/text/unicode_util.h\"\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n#include \"clay/ui/painter/gradient_factory.h\"\n#include \"clay/ui/rendering/text/render_text.h\"\n\nnamespace clay {\n\nnamespace {\n\nnamespace utils = attribute_utils;\nstatic const Color kDefaultTextColor = Color(0xFF000000);\nstatic constexpr float kDefaultFontSizeInDip = 14.f;\nstatic constexpr TextAlignment kDefaultAlignment = TextAlignment::kLeft;\n// Used for avoid unnecessary second layout under kAtMost mode.\n// For example:\n//  Given a text with actual width 100px, and it's measured with\n// width 200px and measure mode kAtMost. After layout, max intrinsic width will\n// be 100px. Thus second layout is needed because 100 < 200.\n//  While given a text with width 200px, and layout width 100px at most. After\n// layout, the max intrinsic width will be less than 100px, like \"99.42\".\n// But actually there's no need to be layout again.\nconstexpr float kLayoutTolerance = 1.f;\n\nclass LayoutContextMeasure : public LayoutContext {\n public:\n  // Width for text paragraph layout.\n  float layout_width_ = 0.f;\n\n  // Indicate the intrinsic width after layout using |layout_width_|.\n  int measured_width_ = 0;\n  // Indicate the height after layout using |layout_width_|.\n  int measured_height_ = 0;\n};\n\n}  // namespace\n\nInternalTextView::InternalTextView(int id, PageView* page_view)\n    : InternalTextView(id, \"internal-text\", std::make_unique<RenderText>(),\n                       page_view) {}\n\nInternalTextView::InternalTextView(int id, const std::string& tag,\n                                   std::unique_ptr<RenderObject> render_object,\n                                   PageView* page_view)\n    : BaseView(id, tag, std::move(render_object), page_view) {\n  default_style_.text_color = kDefaultTextColor;\n  default_style_.text_align = kDefaultAlignment;\n  text_style_ = default_style_;\n  UpdateDefaultFontSize();\n}\n\nInternalTextView::~InternalTextView() {\n  RemoveGestureRecognizer(tap_recognizer_);\n}\n\nvoid InternalTextView::DidUpdateStyle() {\n  update_flag_ = static_cast<UpdateFlag>(update_flag_ | kUpdateFlagStyle);\n}\n\nvoid InternalTextView::SetText(const std::string& text) {\n  std::u16string text_u16 = UnicodeUtil::Utf8ToUtf16(text);\n  if (text_u16 != text_) {\n    text_ = std::move(text_u16);\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::OnLayout(LayoutContext* context) {\n  const float content_width = LayoutWidth();\n  float layout_width = content_width;\n  auto* context_measure = static_cast<LayoutContextMeasure*>(context);\n  if (context_measure) {\n    layout_width = context_measure->layout_width_;\n  }\n\n  const bool force_rebuild = update_flag_ != kUpdateFlagNone;\n  const bool should_relayout =\n      force_rebuild || prev_layout_width_ != layout_width;\n\n  if (force_rebuild) {\n    auto builder =\n        std::make_unique<TextParagraphBuilder>(use_skia_, text_style_);\n    int max_length = text_.size();\n    if (max_length_.has_value() && *max_length_ > 0) {\n      max_length = max_length_.value();\n    }\n    builder->PushStyle(text_style_.value());\n    builder->AddText(text_.substr(0, max_length));\n    builder->Pop();\n    paragraph_ = Build(std::move(builder));\n  }\n\n  if (should_relayout) {\n    paragraph_->Layout(layout_width);\n    GetRenderText()->SetParagraph(paragraph_.get(), text_);\n    if (text_style_->text_gradient.has_value()) {\n      GetRenderText()->SetGradient(text_style_->text_gradient);\n    } else {\n      GetRenderText()->SetGradient(std::nullopt);\n    }\n  }\n\n  if (context_measure) {\n    context_measure->measured_width_ =\n        std::ceil(paragraph_->GetMaxIntrinsicWidth());\n    context_measure->measured_height_ = std::ceil(paragraph_->GetHeight());\n  }\n  update_flag_ = kUpdateFlagNone;\n  prev_layout_width_ = layout_width;\n}\n\nvoid InternalTextView::Measure(const MeasureConstraint& constraint,\n                               MeasureResult& result) {\n  if (!constraint.IsValid()) {\n    FML_DLOG(WARNING) << \"Invalid measure metrics.\";\n    result.width = 0;\n    result.height = 0;\n    return;\n  }\n\n  LayoutContextMeasure context;\n  switch (constraint.width_mode) {\n    case TextMeasureMode::kIndefinite:\n      context.layout_width_ = std::numeric_limits<float>::infinity();\n      break;\n    case TextMeasureMode::kDefinite:\n    case TextMeasureMode::kAtMost:\n      context.layout_width_ = *constraint.width;\n      break;\n  }\n\n  Layout(&context);\n\n  if (constraint.width_mode == TextMeasureMode::kIndefinite) {\n    if (text_style_ && text_style_->text_align.value_or(TextAlignment::kLeft) !=\n                           TextAlignment::kLeft) {\n      // Do second layout if the width mode is indefinite and the alignment is\n      // not left. Because in this case, the paragraph_ won't have the actual\n      // line and nothing will be painted.\n      context.layout_width_ = context.measured_width_;\n      Layout(&context);\n    }\n  }\n\n  if (constraint.width_mode == TextMeasureMode::kAtMost &&\n      context.measured_width_ + kLayoutTolerance < context.layout_width_) {\n    context.layout_width_ = context.measured_width_;\n    // Do second layout if actual text width is less than constraints.\n    // For example, given at most 200px width and actually 100px is needed,\n    // use 100px to layout again. Otherwise text align will be problem.\n    // Note: Here maybe some optimizations to avoid second layout.\n    Layout(&context);\n  }\n\n  if (constraint.width_mode == TextMeasureMode::kDefinite) {\n    result.width = *constraint.width;\n  } else if (constraint.width_mode == TextMeasureMode::kIndefinite) {\n    result.width = std::ceil(context.measured_width_);\n  } else {\n    result.width =\n        std::min(std::ceil(static_cast<float>(context.measured_width_)),\n                 *constraint.width);\n  }\n\n  auto desired_height = context.measured_height_;\n  switch (constraint.height_mode) {\n    case TextMeasureMode::kIndefinite: {\n      result.height = desired_height;\n      break;\n    }\n    case TextMeasureMode::kDefinite: {\n      result.height = *constraint.height;\n      break;\n    }\n    case TextMeasureMode::kAtMost: {\n      result.height = std::min(std::ceil(static_cast<float>(desired_height)),\n                               *constraint.height);\n      break;\n    }\n  }\n}\n\nRenderText* InternalTextView::GetRenderText() {\n  return static_cast<RenderText*>(render_object_.get());\n}\n\nbool InternalTextView::ShouldCreateStyle() {\n  if (text_style_) {\n    return false;\n  }\n  text_style_ = std::make_optional<TextStyle>();\n  return true;\n}\n\nvoid InternalTextView::SetEnableFontScaling(bool enabled) {}\n\nvoid InternalTextView::SetFontSize(float font_size) {\n  if (ShouldCreateStyle() || text_style_->font_size != font_size) {\n    text_style_->font_size = font_size;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetLineHeight(float line_height) {\n  if (line_height <= 0) {\n    line_height = 1.0f;\n  }\n  if (ShouldCreateStyle() || text_style_->line_height != line_height) {\n    text_style_->line_height = line_height;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetLineSpacing(float line_spacing) {\n  if (ShouldCreateStyle() || text_style_->line_spacing != line_spacing) {\n    text_style_->line_spacing = line_spacing;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetLetterSpacing(float letter_spacing) {\n  if (ShouldCreateStyle() || text_style_->letter_spacing != letter_spacing) {\n    text_style_->letter_spacing = letter_spacing;\n    DidUpdateStyle();\n  }\n}\nvoid InternalTextView::SetTextAlign(TextAlignment text_align) {\n  if (ShouldCreateStyle() || text_style_->text_align != text_align) {\n    text_style_->text_align = text_align;\n    DidUpdateStyle();\n  }\n}\nvoid InternalTextView::SetFontWeight(FontWeight font_weight) {\n  if (ShouldCreateStyle() || text_style_->font_weight != font_weight) {\n    text_style_->font_weight = font_weight;\n    DidUpdateStyle();\n  }\n}\nvoid InternalTextView::SetFontStyle(FontStyle font_style) {\n  if (ShouldCreateStyle() || text_style_->font_style != font_style) {\n    text_style_->font_style = font_style;\n    DidUpdateStyle();\n  }\n}\nvoid InternalTextView::SetTextColor(const Color& text_color) {\n  if (ShouldCreateStyle() || text_style_->text_color != text_color) {\n    text_style_->text_color = text_color;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetTextBackgroundColor(const Color& color) {\n  if (ShouldCreateStyle() || text_style_->background_color != color) {\n    text_style_->background_color = color;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetFontFamily(const std::string& font_family) {\n  if (ShouldCreateStyle() || text_style_->font_family != font_family) {\n    text_style_->font_family = font_family;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetTextDecoration(\n    const TextDecoration& text_decoration) {\n  if (ShouldCreateStyle() || text_style_->text_decoration != text_decoration) {\n    text_style_->text_decoration = text_decoration;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::AppendTextShadow(Shadow&& text_shadow) {\n  if (!text_style_) {\n    text_style_ = std::make_optional<TextStyle>();\n  }\n  if (!text_style_->text_shadows) {\n    text_style_->text_shadows = std::vector<Shadow>();\n  }\n\n  text_style_->text_shadows->emplace_back(std::move(text_shadow));\n  DidUpdateStyle();\n}\n\nvoid InternalTextView::SetTextShadows(std::vector<Shadow>&& text_shadows) {\n  if (ShouldCreateStyle() || text_style_->text_shadows != text_shadows) {\n    text_style_->text_shadows = std::move(text_shadows);\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetTextGradient(const Gradient& gradient) {\n  if (ShouldCreateStyle() || text_style_->text_gradient != gradient) {\n    text_style_->text_gradient = gradient;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetTextMaxLine(uint32_t max_lines) {\n  if (ShouldCreateStyle() || text_style_->max_lines != max_lines) {\n    text_style_->max_lines = max_lines;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetTextMaxLength(uint32_t max_length) {\n  if (ShouldCreateStyle()) {\n    max_length_ = max_length;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetTextOverflow(TextOverflow overflow) {\n  if (ShouldCreateStyle() || text_style_->overflow != overflow) {\n    text_style_->overflow = overflow;\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::SetTextEllipsis(std::u16string ellipsis) {\n  if (ShouldCreateStyle() || text_style_->ellipsis != ellipsis) {\n    text_style_->ellipsis = std::move(ellipsis);\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::UpdateDefaultFontSize() {\n  const float prev = default_style_.font_size.value_or(0.f);\n  default_style_.font_size = GetDefaultFontSize();\n  if (prev != default_style_.font_size.value()) {\n    DidUpdateStyle();\n  }\n}\n\nvoid InternalTextView::AddTapUpListener(\n    std::function<void(const PointerEvent& down_event)> func) {\n  auto tap_recognizer =\n      std::make_unique<TapGestureRecognizer>(page_view()->gesture_manager());\n  tap_recognizer_ = tap_recognizer.get();\n  tap_recognizer->SetTapUpCallback(std::move(func));\n  AddGestureRecognizer(std::move(tap_recognizer));\n}\n\nfloat InternalTextView::GetDefaultFontSize() const {\n  return FromLogical(kDefaultFontSizeInDip);\n}\n\n#ifndef NDEBUG\nstd::string InternalTextView::ToString() const {\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\"\n  std::stringstream ss;\n  ss << BaseView::ToString();\n  ss << \" text_=(\" << lynx::base::U16StringToU8(text_) << \")\";\n  return ss.str();\n#pragma GCC diagnostic pop\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/text/internal_text_view.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_INTERNAL_TEXT_VIEW_H_\n#define CLAY_UI_COMPONENT_TEXT_INTERNAL_TEXT_VIEW_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/measurable.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n#include \"clay/ui/rendering/text/render_text.h\"\n#if defined(CLAY_ENABLE_SKSHAPER)\n#include \"clay/third_party/txt/src/skia/paragraph_skia.h\"\n#endif\n#if defined(CLAY_ENABLE_MINIKIN)\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#endif\n\nnamespace clay {\n\n// without shadow node and need measure、layout  by parent component\nclass InternalTextView : public BaseView, public Measurable {\n public:\n  explicit InternalTextView(int id, PageView* page_view);\n  explicit InternalTextView(int id, const std::string& tag,\n                            std::unique_ptr<RenderObject> render_object,\n                            PageView* page_view);\n  ~InternalTextView();\n\n  const std::u16string& text() { return text_; }\n\n  bool ShouldCreateStyle();\n  void SetEnableFontScaling(bool enabled);\n  void SetFontSize(float font_size);\n  void SetLineHeight(float line_height);\n  // TODO(Xietong): Not supported by the underlining layout engine.\n  void SetLineSpacing(float line_spacing);\n  void SetLetterSpacing(float letter_spacing);\n  void SetTextAlign(TextAlignment text_align);\n  void SetFontWeight(FontWeight font_weight);\n  void SetFontStyle(FontStyle font_style);\n  void SetTextColor(const Color& text_color);\n  void SetTextBackgroundColor(const Color& color);\n  void SetFontFamily(const std::string& font_family);\n  std::optional<TextDecoration> GetTextDecoration() const {\n    return text_style_ ? std::nullopt : text_style_->text_decoration;\n  }\n  void SetTextDecoration(const TextDecoration& text_decoration);\n  void AppendTextShadow(Shadow&& text_shadow);\n  void SetTextShadows(std::vector<Shadow>&& text_shadows);\n  void SetTextGradient(const Gradient& gradient);\n  void SetTextMaxLine(uint32_t max_lines);\n  void SetTextMaxLength(uint32_t max_length);\n  void SetTextOverflow(TextOverflow overflow);\n  void SetTextEllipsis(std::u16string ellipsis);\n  void SetText(const std::string& text);\n\n  RenderText* GetRenderText();\n\n  void OnLayout(LayoutContext* context) override;\n  void Measure(const MeasureConstraint& constraint,\n               MeasureResult& result) override;\n\n  void DidUpdateStyle();\n  void SetUseSkia(bool use_skia) { use_skia_ = use_skia; }\n\n  void AddTapUpListener(\n      std::function<void(const PointerEvent& down_event)> func);\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n protected:\n  float LayoutWidth() { return ContentWidth(); }\n  bool use_skia_ = true;\n\n private:\n  void UpdateDefaultFontSize();\n  float GetDefaultFontSize() const;\n  std::unique_ptr<txt::Paragraph> paragraph_;\n\n  TextStyle default_style_;\n\n  enum UpdateFlag {\n    // None means either there is no update or the text view's width/height\n    // changed.\n    // In this case, we don't need to re-create the text builder.\n    kUpdateFlagNone = 0,\n    kUpdateFlagStyle = 1,\n    kUpdateFlagChildren = 1 << 1,\n  };\n  UpdateFlag update_flag_ = kUpdateFlagNone;\n  float prev_layout_width_ = 0;\n\n  std::optional<TextStyle> text_style_;\n  std::optional<uint32_t> max_length_;\n\n  std::u16string text_;\n  TapGestureRecognizer* tap_recognizer_ = nullptr;\n\n  FRIEND_TEST(ViewPagerLegacyTest, BuiltinTabBar_TextStyles);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_INTERNAL_TEXT_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/text/layout_client.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_LAYOUT_CLIENT_H_\n#define CLAY_UI_COMPONENT_TEXT_LAYOUT_CLIENT_H_\n\n#include \"clay/gfx/geometry/float_point.h\"\n\nnamespace clay {\n\nclass BaseView;\n\n// As text layout attributes and results are separated from text view,\n// so delegate it to shadow node.\nclass LayoutClient {\n public:\n  // Used for get the internal view like inline text/image hit by give point.\n  virtual BaseView* GetViewAtPosition(const FloatPoint& point_by_paragraph,\n                                      const FloatPoint& point_by_page) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_LAYOUT_CLIENT_H_\n"
  },
  {
    "path": "clay/ui/component/text/layout_context.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/text/layout_context.h\"\n\n#include <algorithm>\n#include <string_view>\n#include <utility>\n\n#include \"base/include/string/string_utils.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/shadow/inline_image_shadow_node.h\"\n\nnamespace clay {\n\nstatic const std::u16string kDefaultEllipsis = u\"\\u2026\";\n\nPreLayoutContextText::PreLayoutContextText() = default;\nPreLayoutContextText::~PreLayoutContextText() = default;\n\nvoid PreLayoutContextText::CollectInlineImages(InlineImageView* image) {\n  inline_images_.emplace_back(image);\n}\n\nstd::vector<InlineImageView*> PreLayoutContextText::GetInlineImages() {\n  return std::move(inline_images_);\n}\n\nvoid LayoutContextText::AddText(const std::u16string& text,\n                                bool need_text_indent) {\n  if (builder_) {\n    std::u16string sub_text = text;\n    if (max_length_.has_value() && *max_length_ > 0) {\n      if (text.size() + TextSizeIncludingPlaceholders() > max_length_) {\n        sub_text = text.substr(\n            0, std::max(*max_length_ - TextSizeIncludingPlaceholders(),\n                        static_cast<size_t>(0)));\n        sub_text += kDefaultEllipsis;\n      }\n    }\n    if (!need_text_indent) {\n      builder_->AddText(sub_text);\n      text_.append(sub_text);\n    } else {\n      char16_t newline_character = u'\\u000A';\n      std::u16string newline_string = std::u16string(1, newline_character);\n      auto tokens = lynx::base::SplitString<std::u16string>(\n          sub_text, newline_string, true);\n      if (text_.length() == 0) {\n        txt::PlaceholderRun placeholder(text_indent_.value_or(0), 0,\n                                        txt::PlaceholderAlignment::kBaseline,\n                                        txt::TextBaseline::kAlphabetic, 0);\n        AddPlaceholder(placeholder);\n      }\n      for (size_t i = 0; i < tokens.size(); i++) {\n        if (i > 0) {\n          text_.append(newline_string);\n          builder_->AddText(newline_string);\n          txt::PlaceholderRun placeholder(text_indent_.value_or(0), 0,\n                                          txt::PlaceholderAlignment::kBaseline,\n                                          txt::TextBaseline::kAlphabetic, 0);\n          AddPlaceholder(placeholder);\n        }\n        text_.append(tokens[i]);\n        builder_->AddText(tokens[i]);\n      }\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/text/layout_context.h",
    "content": "\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_LAYOUT_CONTEXT_H_\n#define CLAY_UI_COMPONENT_TEXT_LAYOUT_CONTEXT_H_\n\n#include <algorithm>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/ui/component/layout_controller.h\"\n#include \"clay/ui/component/text/text_paragraph_builder.h\"\n#include \"clay/ui/component/text/text_style.h\"\n\nnamespace clay {\n\nclass InlineImageView;\nclass InlineImageShadowNode;\n\nclass PreLayoutContextText : public PreLayoutContext {\n public:\n  PreLayoutContextText();\n  virtual ~PreLayoutContextText();\n\n  void CollectInlineImages(InlineImageView*);\n\n  // Consume the collected inline images\n  std::vector<InlineImageView*> GetInlineImages();\n\n private:\n  std::vector<InlineImageView*> inline_images_;\n};\n\nclass LayoutContextText : public LayoutContext {\n public:\n  void SetBuilder(TextParagraphBuilder* builder) { builder_ = builder; }\n\n  void SetMaxLength(uint32_t max_length) { max_length_ = max_length; }\n  void SetTextIndent(size_t text_indent) { text_indent_ = text_indent; }\n\n  TextParagraphBuilder* builder() { return builder_; }\n\n  void AddText(const std::u16string& text, bool need_text_indent = false);\n\n  void ProcessTextWithIndent();\n\n  // Used for simulate paddings/margins of inline views.\n  int AddFakePlaceholder(float width) {\n    txt::PlaceholderRun fake_placeholder;\n    fake_placeholder.height = 1.0;\n    fake_placeholder.width = width;\n    return AddPlaceholder(fake_placeholder);\n  }\n\n  int AddPlaceholder(txt::PlaceholderRun& placeholder) {\n    if (builder_) {\n      if (max_length_ &&\n          (max_length_.value() <= TextSizeIncludingPlaceholders())) {\n        return -1;\n      }\n      builder_->AddPlaceholder(placeholder);\n      return placeholder_num_++;\n    }\n    return -1;\n  }\n\n  size_t TextSizeIncludingPlaceholders() {\n    return text_.size() + placeholder_num_;\n  }\n\n private:\n  TextParagraphBuilder* builder_ = nullptr;\n  std::u16string text_;\n  size_t placeholder_num_ = 0;\n  std::optional<uint32_t> max_length_;\n  std::optional<size_t> text_indent_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_LAYOUT_CONTEXT_H_\n"
  },
  {
    "path": "clay/ui/component/text/raw_text_view.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/text/raw_text_view.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/ui/rendering/render_dummy.h\"\n\nnamespace clay {\n\nRawTextView::RawTextView(int id, PageView* page_view)\n    : WithTypeInfo(id, \"raw-text\", std::make_unique<RenderDummy>(), page_view) {\n}\n\nRawTextView::~RawTextView() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/text/raw_text_view.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_RAW_TEXT_VIEW_H_\n#define CLAY_UI_COMPONENT_TEXT_RAW_TEXT_VIEW_H_\n\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nclass RawTextView : public WithTypeInfo<RawTextView, BaseView> {\n public:\n  RawTextView(int id, PageView* page_view);\n  ~RawTextView() override;\n\n  // avoid some text attribute send to base view\n  void SetAttribute(const char* attr, const clay::Value& value) override {}\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_RAW_TEXT_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/text/tappable_text_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <utility>\n\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/text/text_view.h\"\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n\n#ifndef CLAY_UI_COMPONENT_TEXT_TAPPABLE_TEXT_VIEW_H_\n#define CLAY_UI_COMPONENT_TEXT_TAPPABLE_TEXT_VIEW_H_\n\nnamespace clay {\n\n// Currently only used for soft keyboard.\nclass TappableTextView : public TextView {\n public:\n  TappableTextView(int id, PageView* page_view) : TextView(id, page_view) {}\n\n  void SetTapUpCallback(OnTapUpCallback&& callback) {\n    if (!tap_recognizer_) {\n      tap_recognizer_.reset(\n          new TapGestureRecognizer(page_view()->gesture_manager()));\n    }\n    tap_recognizer_->SetTapUpCallback(std::move(callback));\n  }\n\n  void HandleEvent(const PointerEvent& event) override {\n    if (event.type == PointerEvent::EventType::kDownEvent) {\n      tap_recognizer_->AddPointer(event);\n    }\n  }\n\n private:\n  std::unique_ptr<TapGestureRecognizer> tap_recognizer_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_TAPPABLE_TEXT_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/text/text_paragraph_builder.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/text/text_paragraph_builder.h\"\n\n#include <optional>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/third_party/txt/src/txt/paragraph_style.h\"\n#include \"clay/third_party/txt/src/txt/text_style.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/shadow/vertical_align_style.h\"\n\nnamespace clay {\nnamespace {\n\nstatic const std::u16string kDefaultEllipsis = u\"\\u2026\";\n\n#if defined(CLAY_ENABLE_TTTEXT)\nttoffice::tttext::CharacterVerticalAlignment ToTTTextAlign(\n    VerticalAlignType type, txt::TextStyle& txt_style,\n    const TextStyle& clay_style) {\n  if (type == VerticalAlignType::kVerticalAlignTop) {\n    return ttoffice::tttext::CharacterVerticalAlignment::kTop;\n  } else if (type == VerticalAlignType::kVerticalAlignCenter) {\n    return ttoffice::tttext::CharacterVerticalAlignment::kMiddle;\n  } else if (type == VerticalAlignType::kVerticalAlignBottom) {\n    return ttoffice::tttext::CharacterVerticalAlignment::kBottom;\n  } else if (type == VerticalAlignType::kVerticalAlignMiddle) {\n    return ttoffice::tttext::CharacterVerticalAlignment::kMiddle;\n  } else if (type == VerticalAlignType::kVerticalAlignTextTop) {\n    return ttoffice::tttext::CharacterVerticalAlignment::kTextTop;\n  } else if (type == VerticalAlignType::kVerticalAlignTextBottom) {\n    return ttoffice::tttext::CharacterVerticalAlignment::kTextBottom;\n  } else if (type == VerticalAlignType::kVerticalAlignSub) {\n    return ttoffice::tttext::CharacterVerticalAlignment::kSubScript;\n  } else if (type == VerticalAlignType::kVerticalAlignSuper) {\n    return ttoffice::tttext::CharacterVerticalAlignment::kSuperScript;\n  } else if (type == VerticalAlignType::kVerticalAlignLength) {\n    txt_style.text_baseline_shift -= clay_style.baseline_shift.value_or(0);\n  } else if (type == VerticalAlignType::kVerticalAlignPercent) {\n    txt_style.text_baseline_shift -=\n        clay_style.baseline_shift.value_or(0) / 100 *\n        (clay_style.line_height ? clay_style.line_height.value()\n                                : clay_style.font_size.value_or(0) * 1.2);\n  }\n  return ttoffice::tttext::CharacterVerticalAlignment::kBaseLine;\n}\n#endif\n\ntxt::FontWeight ToTxtFontWeight(FontWeight font_weight) {\n  static const std::unordered_map<FontWeight, txt::FontWeight> kMapping = {\n      {FontWeight::kNormal, txt::FontWeight::w400},\n      {FontWeight::kBold, txt::FontWeight::w700},\n      {FontWeight::k100, txt::FontWeight::w100},  // Thin\n      {FontWeight::k200, txt::FontWeight::w200},  // Extra-Light\n      {FontWeight::k300, txt::FontWeight::w300},  // Light\n      {FontWeight::k400, txt::FontWeight::w400},  // Normal/Regular\n      {FontWeight::k500, txt::FontWeight::w500},  // Medium\n      {FontWeight::k600, txt::FontWeight::w600},  // Semi-bold\n      {FontWeight::k700, txt::FontWeight::w700},  // Bold\n      {FontWeight::k800, txt::FontWeight::w800},  // Extra-Bold\n      {FontWeight::k900, txt::FontWeight::w900},  // Black\n  };\n  return kMapping.at(font_weight);\n}\n\ntxt::FontStyle ToTxtFontStyle(FontStyle font_style) {\n  if (font_style == FontStyle::kNormal) {\n    return txt::FontStyle::normal;\n  }\n  if (font_style == FontStyle::kItalic) {\n    return txt::FontStyle::italic;\n  }\n  return txt::FontStyle::oblique;\n}\n\ntxt::TextAlign ToTxtAlign(TextAlignment align) {\n  if (align == TextAlignment::kLeft) {\n    return txt::TextAlign::left;\n  } else if (align == TextAlignment::kCenter) {\n    return txt::TextAlign::center;\n  } else if (align == TextAlignment::kRight) {\n    return txt::TextAlign::right;\n  } else if (align == TextAlignment::kStart) {\n    return txt::TextAlign::start;\n  } else if (align == TextAlignment::kEnd) {\n    return txt::TextAlign::end;\n  } else if (align == TextAlignment::kJustify) {\n    return txt::TextAlign::justify;\n  }\n\n  FML_DCHECK(false) << \"unsupported TextAlignment: \" << static_cast<int>(align);\n  return txt::TextAlign::start;\n}\n\ntxt::TextDirection ToTxtDirection(TextDirection direction) {\n  if (direction == TextDirection::kLtr) {\n    return txt::TextDirection::ltr;\n  } else if (direction == TextDirection::kRtl) {\n    return txt::TextDirection::rtl;\n  }\n  FML_DCHECK(false);\n  return txt::TextDirection::ltr;\n}\n\ntxt::TextDecorationStyle ToTxtDecorationStyle(TextDecorationStyle style) {\n  static const std::unordered_map<TextDecorationStyle, txt::TextDecorationStyle>\n      kRDToTxt{\n          {TextDecorationStyle::kSolid, txt::TextDecorationStyle::kSolid},\n          {TextDecorationStyle::kDouble, txt::TextDecorationStyle::kDouble},\n          {TextDecorationStyle::kDotted, txt::TextDecorationStyle::kDotted},\n          {TextDecorationStyle::kDashed, txt::TextDecorationStyle::kDashed},\n          {TextDecorationStyle::kWavy, txt::TextDecorationStyle::kWavy},\n      };\n\n  const auto itr = kRDToTxt.find(style);\n  FML_DCHECK(itr != kRDToTxt.end());\n  if (itr != kRDToTxt.end()) {\n    return itr->second;\n  }\n\n  FML_UNREACHABLE();\n}\n\ntxt::TextDecoration ToTxtDecorationLine(uint8_t line) {\n  int res = txt::TextDecoration::kNone;\n  if (line & kTextDecorationLineUnderline) {\n    res |= txt::TextDecoration::kUnderline;\n  }\n  if (line & kTextDecorationLineOverline) {\n    res |= txt::TextDecoration::kOverline;\n  }\n  if (line & kTextDecorationLineLineThrough) {\n    res |= txt::TextDecoration::kLineThrough;\n  }\n  return static_cast<txt::TextDecoration>(res);\n}\n\ntxt::TextShadow ToTextShadow(const Shadow& shadow) {\n  return txt::TextShadow(\n      shadow.color, {shadow.offset_x, shadow.offset_y},\n      GraphicsContext::ConvertRadiusToSigma(shadow.blur_radius));\n}\n\nvoid ApplyParagraphStyle(const TextStyle& clay_style,\n                         txt::ParagraphStyle& txt_style) {\n  if (clay_style.font_weight) {\n    txt_style.font_weight = ToTxtFontWeight(clay_style.font_weight.value());\n  }\n\n  if (clay_style.font_style) {\n    txt_style.font_style = ToTxtFontStyle(clay_style.font_style.value());\n  }\n\n  if (clay_style.font_size) {\n    txt_style.font_size = clay_style.font_size.value();\n  }\n\n  if (clay_style.text_align) {\n    txt_style.text_align = ToTxtAlign(clay_style.text_align.value());\n  }\n\n  if (clay_style.text_direction) {\n    txt_style.text_direction =\n        ToTxtDirection(clay_style.text_direction.value());\n  }\n\n  if (clay_style.max_lines) {\n    txt_style.max_lines = static_cast<size_t>(*clay_style.max_lines);\n\n    if (clay_style.overflow == TextOverflow::kEllipsis) {\n      if (clay_style.ellipsis) {\n        txt_style.ellipsis = *clay_style.ellipsis;\n      } else {\n        txt_style.ellipsis = kDefaultEllipsis;\n      }\n    }\n  }\n\n  if (clay_style.line_height) {\n    txt_style.height = clay_style.line_height.value();\n    txt_style.has_height_override = true;\n    // TODO(WeiGuoliang): lxblxb kEvenLeading not found\n    txt_style.text_height_behavior = txt::TextHeightBehavior::kAll;\n    txt_style.height_type = txt::RulerType::kExact;\n  }\n\n  if (clay_style.line_spacing) {\n    txt_style.line_spacing = clay_style.line_spacing.value();\n  }\n\n  if (clay_style.keep_trailing_spaces) {\n    txt_style.keep_trailing_spaces = clay_style.keep_trailing_spaces.value();\n  }\n\n  if (clay_style.strut_enabled) {\n    txt_style.strut_enabled = clay_style.strut_enabled.value();\n\n    if (clay_style.strut_font_size) {\n      txt_style.strut_font_size = clay_style.strut_font_size.value();\n    }\n\n    if (clay_style.strut_font_style) {\n      txt_style.strut_font_style =\n          ToTxtFontStyle(clay_style.strut_font_style.value());\n    }\n\n    if (clay_style.strut_font_weight) {\n      txt_style.strut_font_weight =\n          ToTxtFontWeight(clay_style.strut_font_weight.value());\n    }\n\n    if (clay_style.strut_height) {\n      txt_style.strut_height = clay_style.strut_height.value();\n    }\n\n    txt_style.strut_font_families = clay_style.strut_font_families;\n    txt_style.strut_has_height_override = true;\n    txt_style.strut_half_leading = true;\n    txt_style.force_strut_height = true;\n  }\n\n#if defined(CLAY_ENABLE_TTTEXT)\n  if (clay_style.enable_text_bounds) {\n    txt_style.enable_text_bounds = clay_style.enable_text_bounds.value();\n  }\n#endif\n}\n\nvoid ApplyTextStyle(const TextStyle& clay_style, txt::TextStyle& txt_style) {\n  if (clay_style.text_color && !clay_style.text_gradient) {\n    txt_style.has_foreground = false;\n    txt_style.color = ToSk(clay_style.text_color.value());\n  }\n\n  if (clay_style.foreground_id) {\n    // This attribute if for gradient and text-stroke.\n    txt_style.has_foreground_id = true;\n    txt_style.foreground_id = clay_style.foreground_id.value();\n  }\n\n  if (clay_style.background_color) {\n    PAINT_SET_COLOR(txt_style.background,\n                    ToSk(clay_style.background_color.value()));\n    txt_style.has_background = true;\n  }\n\n  if (clay_style.font_weight) {\n    txt_style.font_weight = ToTxtFontWeight(clay_style.font_weight.value());\n  }\n\n  if (clay_style.font_style) {\n    txt_style.font_style = ToTxtFontStyle(clay_style.font_style.value());\n  }\n\n  if (clay_style.font_size) {\n    txt_style.font_size = clay_style.font_size.value();\n  }\n\n  if (clay_style.half_leading) {\n    txt_style.half_leading = clay_style.half_leading.value();\n  }\n\n  if (clay_style.letter_spacing) {\n    txt_style.letter_spacing = clay_style.letter_spacing.value();\n  }\n\n  if (clay_style.line_height) {\n    txt_style.height = clay_style.line_height.value();\n    txt_style.has_height_override = true;\n  }\n\n  if (clay_style.line_spacing) {\n    txt_style.line_spacing = clay_style.line_spacing.value();\n  }\n\n  if (clay_style.text_decoration) {\n    txt_style.decoration_style =\n        ToTxtDecorationStyle(clay_style.text_decoration->style);\n    txt_style.decoration =\n        ToTxtDecorationLine(clay_style.text_decoration->line);\n    txt_style.decoration_color = clay_style.text_decoration->color.has_value()\n                                     ? clay_style.text_decoration->color.value()\n                                     : txt_style.color;\n  }\n\n  if (clay_style.text_shadows) {\n    txt_style.text_shadows.clear();\n    for (const Shadow& shadow : clay_style.text_shadows.value()) {\n      txt_style.text_shadows.emplace_back(ToTextShadow(shadow));\n    }\n  }\n  if (clay_style.word_break) {\n    switch (clay_style.word_break.value()) {\n      case kNormal:\n        txt_style.word_break = txt::kNormal;\n        break;\n      case kBreakAll:\n        txt_style.word_break = txt::kBreakAll;\n        break;\n      case kKeepAll:\n        txt_style.word_break = txt::kKeepAll;\n        break;\n    }\n  }\n\n  if (clay_style.font_family) {\n    txt_style.font_families.insert(txt_style.font_families.begin(),\n                                   clay_style.font_family.value());\n  }\n\n#if defined(CLAY_ENABLE_TTTEXT)\n  if (clay_style.align_type) {\n    txt_style.align_type =\n        ToTTTextAlign(clay_style.align_type.value(), txt_style, clay_style);\n    txt_style.enable_text_bounds_ =\n        clay_style.enable_text_bounds.value_or(false);\n  }\n  if (clay_style.stroke_color) {\n    txt_style.stroke_color = clay_style.stroke_color.value();\n  }\n  if (clay_style.stroke_width) {\n    txt_style.stroke_width = clay_style.stroke_width.value();\n  }\n#else\n  if (clay_style.baseline_shift) {\n    txt_style.text_baseline_shift = clay_style.baseline_shift.value();\n  }\n#endif\n}\n\n}  // namespace\n\nstd::unique_ptr<txt::Paragraph> Build(\n    std::unique_ptr<TextParagraphBuilder> builder) {\n  return std::unique_ptr<txt::Paragraph>(\n      static_cast<txt::Paragraph*>(builder->builder_->Build().release()));\n}\n\nTextParagraphBuilder::TextParagraphBuilder(\n    bool use_skia, const std::optional<TextStyle>& paragraph_style) {\n  txt::ParagraphStyle style;\n\n  if (paragraph_style) {\n    ApplyParagraphStyle(paragraph_style.value(), style);\n  }\n\n#if defined(CLAY_ENABLE_SKSHAPER)\n  builder_ = txt::ParagraphBuilder::CreateSkiaBuilder(\n      style, Isolate::Instance().GetTxtFontCollection());\n#endif\n#if defined(CLAY_ENABLE_MINIKIN)\n  builder_ = txt::ParagraphBuilder::CreateTxtBuilder(\n      style, Isolate::Instance().GetTxtFontCollection());\n#endif  // CLAY_ENABLE_MINIKIN\n#if defined(CLAY_ENABLE_TTTEXT)\n  builder_ = txt::ParagraphBuilder::CreateTTTextBuilder(\n      style, Isolate::Instance().GetTxtFontCollection());\n#endif\n}\n\nTextParagraphBuilder::~TextParagraphBuilder() = default;\n\nvoid TextParagraphBuilder::PushStyle(const TextStyle& style) {\n  txt::TextStyle txt_style = builder_->PeekStyle();\n  FML_DCHECK(style.font_size.has_value());\n  ApplyTextStyle(style, txt_style);\n  builder_->PushStyle(txt_style);\n}\n\nvoid TextParagraphBuilder::Pop() { builder_->Pop(); }\n\nvoid TextParagraphBuilder::AddText(const std::u16string& text) {\n  builder_->AddText(text);\n}\n\nvoid TextParagraphBuilder::AddPlaceholder(txt::PlaceholderRun& placeholder) {\n  builder_->AddPlaceholder(placeholder);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/text/text_paragraph_builder.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_TEXT_PARAGRAPH_BUILDER_H_\n#define CLAY_UI_COMPONENT_TEXT_TEXT_PARAGRAPH_BUILDER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/third_party/txt/src/txt/paragraph_builder.h\"\n#include \"clay/ui/component/text/text_style.h\"\n\nnamespace clay {\n\nclass TextParagraphBuilder;\n\nstd::unique_ptr<txt::Paragraph> Build(\n    std::unique_ptr<TextParagraphBuilder> builder);\n\nclass TextParagraphBuilder {\n public:\n  // Use TextStyle as ParagraphStyle for now.\n  explicit TextParagraphBuilder(\n      bool use_skia, const std::optional<TextStyle>& paragraph_style);\n  ~TextParagraphBuilder();\n\n  void PushStyle(const TextStyle& style);\n  void Pop();\n  void AddText(const std::u16string& text);\n  void AddPlaceholder(txt::PlaceholderRun& placeholder);\n\n private:\n  friend std::unique_ptr<txt::Paragraph> Build(\n      std::unique_ptr<TextParagraphBuilder> builder);\n\n  std::unique_ptr<txt::ParagraphBuilder> builder_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_TEXT_PARAGRAPH_BUILDER_H_\n"
  },
  {
    "path": "clay/ui/component/text/text_style.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_TEXT_STYLE_H_\n#define CLAY_UI_COMPONENT_TEXT_TEXT_STYLE_H_\n\n#include <algorithm>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/style/color.h\"\n#include \"clay/gfx/style/shadow.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/painter/gradient.h\"\n#include \"clay/ui/shadow/vertical_align_style.h\"\n\nnamespace clay {\n\nusing TextMeasureMode = MeasureMode;\n\nenum class TextAlignment { kLeft = 0, kCenter, kRight, kStart, kEnd, kJustify };\n\nenum class TextDirection {\n  kNormal = 0,\n  kRtl = 2,\n  kLtr = 3,\n};\n\nenum class FontStyle {\n  kNormal = 0,\n  kItalic,\n  kOblique,\n};\n\nenum class FontWeight {\n  kNormal = 0,\n  kBold,\n  k100,\n  k200,\n  k300,\n  k400,\n  k500,\n  k600,\n  k700,\n  k800,\n  k900,\n};\n\n// Multiple decorations can be applied at once. Ex: Underline and overline is\n// (0x1 | 0x2)\n// Note(Xietong): Use enum instead of enum class to do bit operation.\nenum TextDecorationLine : uint8_t {\n  kTextDecorationLineNone = 0x0,\n  kTextDecorationLineUnderline = 0x1,\n  kTextDecorationLineOverline = 0x2,\n  kTextDecorationLineLineThrough = 0x4,\n};\n\nenum class TextDecorationStyle {\n  kSolid = 1 << 2,\n  kDouble = 1 << 3,\n  kDotted = 1 << 4,\n  kDashed = 1 << 5,\n  kWavy = 1 << 6,\n};\n\nenum TextOverflow {\n  kClip,\n  kEllipsis,\n};\n\nenum WordBreak : uint8_t {\n  kNormal = 0,\n  kBreakAll = 1,\n  kKeepAll = 2,\n};\n\nenum class WhiteSpace {\n  kNormal = 0,\n  kNoWrap = 1,\n};\n\nstruct TextDecoration {\n  // Support bit combination of TextDecorationLine\n  uint8_t line;\n  TextDecorationStyle style = TextDecorationStyle::kSolid;\n  std::optional<Color> color;\n\n  bool operator!=(const TextDecoration& oth) const {\n    return line != oth.line || style != oth.style || color != oth.color;\n  }\n  bool operator==(const TextDecoration& oth) const {\n    return line == oth.line && style == oth.style && color == oth.color;\n  }\n};\n\nstruct TextStyle {\n  std::optional<bool> enable_font_scaling;\n  std::optional<bool> half_leading;\n  std::optional<float> font_size;\n  std::optional<float> baseline_shift;\n  std::optional<VerticalAlignType> align_type;\n  std::optional<float> line_height;\n  std::optional<float> line_spacing;\n  std::optional<float> letter_spacing;\n  std::optional<float> text_indent;\n  std::optional<TextAlignment> text_align;\n  std::optional<TextDirection> text_direction;\n  std::optional<FontWeight> font_weight;\n  std::optional<FontStyle> font_style;\n  std::optional<Color> text_color;\n  std::optional<Color> background_color;\n  std::optional<std::string> font_family;\n  std::optional<TextDecoration> text_decoration;\n  std::optional<Gradient> text_gradient;\n  std::optional<std::vector<Shadow>> text_shadows;\n  std::optional<uint32_t> max_lines;\n  std::optional<TextOverflow> overflow;\n  std::optional<std::u16string> ellipsis;\n  std::optional<WordBreak> word_break;\n  std::optional<WhiteSpace> white_space;\n  std::optional<int> foreground_id;\n  std::optional<bool> keep_trailing_spaces;\n\n  // Strut properties. strut_enabled must be set to true for the rest of the\n  // properties to take effect.\n  // TODO(garyq): Break the strut properties into a separate class.\n  std::optional<bool> strut_enabled;\n  std::optional<FontWeight> strut_font_weight;\n  std::optional<FontStyle> strut_font_style;\n  std::vector<std::string> strut_font_families;\n  std::optional<double> strut_font_size;\n  std::optional<double> strut_height;\n  // Negative to use font's default leading. [0,inf)\n  // to use custom leading as a ratio of font size.\n  std::optional<double> strut_leading;\n  std::optional<bool> enable_text_bounds;\n#ifdef CLAY_ENABLE_TTTEXT\n  std::optional<float> stroke_width;\n  std::optional<Color> stroke_color;\n#endif\n\n  bool operator==(const TextStyle& style) const {\n    return this->enable_font_scaling == style.enable_font_scaling &&\n           this->half_leading == style.half_leading &&\n           this->font_size == style.font_size &&\n           this->baseline_shift == style.baseline_shift &&\n           this->line_height == style.line_height &&\n           this->line_spacing == style.line_spacing &&\n           this->letter_spacing == style.letter_spacing &&\n           this->text_indent == style.text_indent &&\n           this->text_align == style.text_align &&\n           this->text_direction == style.text_direction &&\n           this->font_weight == style.font_weight &&\n           this->font_style == style.font_style &&\n           this->text_color == style.text_color &&\n           this->background_color == style.background_color &&\n           this->font_family == style.font_family &&\n           this->text_decoration == style.text_decoration &&\n           this->text_gradient == style.text_gradient &&\n           this->text_shadows == style.text_shadows &&\n           this->max_lines == style.max_lines &&\n           this->overflow == style.overflow &&\n           this->ellipsis == style.ellipsis &&\n           this->word_break == style.word_break &&\n           this->white_space == style.white_space &&\n           this->foreground_id == style.foreground_id &&\n           this->keep_trailing_spaces == style.keep_trailing_spaces &&\n           this->align_type == style.align_type &&\n           this->enable_text_bounds == style.enable_text_bounds;\n  }\n};\n\n}  // namespace clay\n\nnamespace std {\ntemplate <>\nstruct hash<clay::TextStyle> {\n  size_t operator()(const clay::TextStyle& text_style) const {\n    int prime = 31;\n    size_t result =\n        prime + std::clamp(text_style.font_size\n                               ? static_cast<int>(*text_style.font_size)\n                               : 0,\n                           0, 99999);\n    result = result * prime +\n             std::clamp(text_style.baseline_shift\n                            ? static_cast<int>(*text_style.baseline_shift)\n                            : 0,\n                        0, 99999);\n    result = result * prime +\n             std::clamp(text_style.line_height\n                            ? static_cast<int>(*text_style.line_height)\n                            : 0,\n                        0, 99999);\n    result = result * prime +\n             std::clamp(text_style.line_spacing\n                            ? static_cast<int>(*text_style.line_spacing)\n                            : 0,\n                        0, 99999);\n    result = result * prime +\n             std::clamp(text_style.letter_spacing\n                            ? static_cast<int>(*text_style.letter_spacing)\n                            : 0,\n                        0, 99999);\n    result = result * prime +\n             std::clamp(text_style.text_indent\n                            ? static_cast<int>(*text_style.text_indent)\n                            : 0,\n                        0, 99999);\n    result =\n        result * prime +\n        (text_style.text_align ? static_cast<int>(*text_style.text_align) : 0);\n    result =\n        result * prime + (text_style.text_direction\n                              ? static_cast<int>(*text_style.text_direction)\n                              : 0);\n    result = result * prime + (text_style.font_weight\n                                   ? static_cast<int>(*text_style.font_weight)\n                                   : 0);\n    result =\n        result * prime +\n        (text_style.font_style ? static_cast<int>(*text_style.font_style) : 0);\n    result = result * prime +\n             (text_style.text_color ? text_style.text_color->Value() : 0);\n    result = result * prime + (text_style.background_color\n                                   ? text_style.background_color->Value()\n                                   : 0);\n    result =\n        result * prime + (text_style.max_lines ? *text_style.max_lines : 0);\n    result = result * prime +\n             (text_style.overflow ? static_cast<int>(*text_style.overflow) : 0);\n    result =\n        result * prime +\n        (text_style.align_type ? static_cast<int>(*text_style.align_type) : 0);\n    result =\n        result * prime + (text_style.enable_text_bounds\n                              ? static_cast<int>(*text_style.enable_text_bounds)\n                              : 0);\n    return result;\n  }\n};\n}  // namespace std\n\n#endif  // CLAY_UI_COMPONENT_TEXT_TEXT_STYLE_H_\n"
  },
  {
    "path": "clay/ui/component/text/text_view.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/text/text_view.h\"\n\n#include <algorithm>\n#include <cstddef>\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/text_input_type_traits.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/keywords.h\"\n#include \"clay/ui/component/overlay_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/selection_handle_view.h\"\n#include \"clay/ui/component/text/inline_text_view.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/gesture/long_press_gesture_recognizer.h\"\n#include \"clay/ui/gesture/multi_tap_gesture_recognizer.h\"\n#include \"clay/ui/lynx_module/type_utils.h\"\n#include \"clay/ui/rendering/render_scroll.h\"\n\nnamespace clay {\n\nnamespace {\n\nLYNX_UI_METHOD_BEGIN(TextView) {\n  LYNX_UI_METHOD(TextView, getTextBoundingRect);\n  LYNX_UI_METHOD(TextView, setTextSelection);\n  LYNX_UI_METHOD(TextView, getSelectedText);\n}\nLYNX_UI_METHOD_END(TextView);\n\nconstexpr int kDefaultContentDistance = 12;\n\n// Hot key tags.\nconstexpr uint32_t kTagCommand = 1;\nconstexpr uint32_t kTagControl = 1 << 1;\n\nuint32_t AddTag(uint32_t value, uint32_t tag) { return value | tag; }\n\nuint32_t RemoveTag(uint32_t value, uint32_t tag) { return value & ~tag; }\n\n}  // namespace\n\nstatic clay::Value::Map CreateRectMap(const FloatRect& rect) {\n  clay::Value::Map map;\n  map[\"left\"] = clay::Value(rect.x());\n  map[\"right\"] = clay::Value(rect.MaxX());\n  map[\"top\"] = clay::Value(rect.y());\n  map[\"bottom\"] = clay::Value(rect.MaxY());\n  map[\"width\"] = clay::Value(rect.width());\n  map[\"height\"] = clay::Value(rect.height());\n  return map;\n}\n\nstatic clay::Value::Map CreateHandleMap(float x, float y, float radius) {\n  clay::Value::Map map;\n  map[\"x\"] = clay::Value(x);\n  map[\"y\"] = clay::Value(y);\n  map[\"radius\"] = clay::Value(radius);\n  return map;\n}\n\nTextView::TextView(int id, PageView* page_view)\n    : TextView(id, \"text\", std::make_unique<RenderText>(), page_view) {}\n\nTextView::TextView(int id, const std::string& tag,\n                   std::unique_ptr<RenderObject> render_object,\n                   PageView* page_view)\n    : WithTypeInfo(id, tag, std::move(render_object), page_view),\n      weak_factory_(this) {}\n\nTextView::~TextView() { HideSelectionPopup(); }\n\nvoid TextView::SetAttribute(const char* attr, const clay::Value& value) {\n  auto kw = GetKeywordID(attr);\n  if (kw == KeywordID::kTextSelection) {\n    if ((is_text_selection_ = attribute_utils::GetBool(value))) {\n      SetFocusable(true);\n      if (!custom_text_selection_) {\n        ResetGestureRecognizers();\n      }\n      GetRenderText()->SetSelectionChangedListener(\n          [this](int selection_start, int selection_end) {\n            OnSelectionChanged(selection_start, selection_end);\n          });\n    } else {\n      ClearGestureRecognizers();\n    }\n  } else if (kw == KeywordID::kCustomContextMenu) {\n    custom_context_menu_ = attribute_utils::GetBool(value);\n  } else if (kw == KeywordID::kCustomTextSelection) {\n    custom_text_selection_ = attribute_utils::GetBool(value);\n  } else if (kw == KeywordID::kColor) {\n    if (value.IsUint()) {\n      SetColor(Color(attribute_utils::GetUint(value, 0xff000000)));\n    } else if (value.IsArray()) {\n      // The `setColor` interface of `baseView` is only used to trigger\n      // animation. And the color of array value is linear gradient, which is\n      // not supported currently. See `BaseTextShadowNode::RegisterSetters`.\n    } else if (!value.IsNone()) {\n      FML_DLOG(WARNING) << \"KeywordID::kColor value is not valid.\";\n    }\n  } else {\n    BaseView ::SetAttribute(attr, value);\n  }\n}\n\nvoid TextView::SetBorderWidth(std::vector<Side> sides,\n                              std::vector<float> widths) {\n  BaseView::SetBorderWidth(sides, widths);\n  UpdateInlineImageInfo();\n}\nvoid TextView::SetPaddings(float padding_left, float padding_top,\n                           float padding_right, float padding_bottom) {\n  BaseView::SetPaddings(padding_left, padding_top, padding_right,\n                        padding_bottom);\n  UpdateInlineImageInfo();\n}\n\nvoid TextView::PushInlineImageIndex(int id, int placeholder_id) {\n  inline_images_index_.emplace(id, placeholder_id);\n}\nvoid TextView::PushInlineViewIndex(int id, int placeholder_id) {\n  inline_views_index_.emplace(id, placeholder_id);\n}\n\nvoid TextView::SetColor(Color color) {\n  if (TransitionMgr()->Enabled(ClayAnimationPropertyType::kColor) &&\n      TransitionMgr()->TransitionTo(ClayAnimationPropertyType::kColor, color)) {\n    UpdateTransitionRasterAnimation(ClayAnimationPropertyType::kColor);\n    return;\n  }\n  SetProperty(ClayAnimationPropertyType::kColor, color, false);\n}\n\nbool TextView::OnKeyEvent(const KeyEvent* key_event) {\n  if (ApplyHotKey(key_event)) {\n    return false;\n  }\n  return true;\n}\n\nbool TextView::ApplyHotKey(const KeyEvent* key_event) {\n  auto key_code = key_event->GetLogical();\n  bool is_up = key_event->GetType() == KeyEventType::kUp;\n  UpdateHotKeyTag(key_code, is_up);\n  if (hot_key_tag_ == 0) {\n    // Not in hot key mode.\n    return false;\n  }\n  if (!is_up) {\n    // Only handle second hot key down and repeat.\n    if (hot_key_tag_ == kTagCommand) {\n      HandleCommandHotKey(key_code);\n    } else if (hot_key_tag_ == kTagControl) {\n      HandleCtrlHotKey(key_code);\n    }\n    // Do not respond to multiple modifier key combination, like 'command' +\n    // 'control' + key.\n  }\n  return true;\n}\n\nvoid TextView::UpdateHotKeyTag(LogicalKeyboardKey key_code, bool is_up) {\n  switch (key_code) {\n    case KeyCode::kMetaLeft:\n    case KeyCode::kMetaRight:\n    case KeyCode::kMeta:\n      hot_key_tag_ = is_up ? RemoveTag(hot_key_tag_, kTagCommand)\n                           : AddTag(hot_key_tag_, kTagCommand);\n      break;\n    case KeyCode::kControlLeft:\n    case KeyCode::kControlRight:\n    case KeyCode::kControl:\n      hot_key_tag_ = is_up ? RemoveTag(hot_key_tag_, kTagControl)\n                           : AddTag(hot_key_tag_, kTagControl);\n      break;\n    default:\n      break;\n  }\n}\n\nvoid TextView::ClearGestureRecognizers() {\n#if defined(OS_ANDROID) || defined(OS_IOS)\n  RemoveGestureRecognizer(long_press_recognizer_);\n  RemoveGestureRecognizer(double_tap_recognizer_);\n  double_tap_recognizer_ = nullptr;\n  long_press_recognizer_ = nullptr;\n#else\n  RemoveGestureRecognizer(drag_recognizer_);\n  drag_recognizer_ = nullptr;\n#endif\n}\n\nvoid TextView::HandleCommandHotKey(LogicalKeyboardKey key_code) {\n  HandleWinCtrlAndMacCommandHotKey(key_code);\n}\n\nvoid TextView::HandleCtrlHotKey(LogicalKeyboardKey key_code) {\n#if defined(WIN32) || defined(ENABLE_HEADLESS)\n  HandleWinCtrlAndMacCommandHotKey(key_code);\n#endif\n}\n\nvoid TextView::HandleWinCtrlAndMacCommandHotKey(LogicalKeyboardKey key_code) {\n  switch (key_code) {\n    case KeyCode::kKeyC: {\n      // Copy to clipboard.\n      std::u16string selected = GetRenderText()->GetSelectionString();\n      if (selected.length() > 0) {\n        page_view()->SetClipboardData(selected);\n      }\n      break;\n    }\n    case KeyCode::kKeyA: {\n      // Select all.\n      GetRenderText()->SetAllSelection();\n      break;\n    }\n    default:\n      break;\n  }\n}\n\nvoid TextView::ResetGestureRecognizers() {\n  ClearGestureRecognizers();\n#if defined(OS_ANDROID) || defined(OS_IOS)\n#ifndef ENABLE_CLAY_LITE\n  auto double_tap_recognizer = std::make_unique<MultiTapGestureRecognizer>(\n      page_view()->gesture_manager());\n  double_tap_recognizer_ = double_tap_recognizer.get();\n  double_tap_recognizer->SetMultiTapCallback(\n      [this](const PointerEvent& up_event, int tap_counts) {\n        if (tap_counts == 2) {\n          auto point = up_event.position - BoundsRelativeTo(nullptr).location();\n          RequestFocus();\n          auto render_text = GetRenderText();\n          auto glyph_pos =\n              render_text->GetPainter()->GetGlyphPositionAtCoordinate(\n                  point.x(), point.y());\n          auto word = SelectWord(glyph_pos.first);\n          render_text->SetSelection(word);\n          auto range = GetRenderText()->GetSelection();\n          selection_start_pos_ = range.start();\n          selection_end_pos_ = range.end();\n          ShowSelectionHandle();\n          if (!custom_context_menu_) {\n            ShowSelectionPopup();\n          }\n        }\n      });\n  auto long_press_recognizer = std::make_unique<LongPressGestureRecognizer>(\n      page_view()->gesture_manager());\n  long_press_recognizer_ = long_press_recognizer.get();\n  long_press_recognizer->SetLongPressStartCallback(\n      [this](const PointerEvent& event) {\n        RequestFocus();\n        GetRenderText()->SetAllSelection();\n        auto range = GetRenderText()->GetSelection();\n        selection_start_pos_ = range.start();\n        selection_end_pos_ = range.end();\n        ShowSelectionHandle();\n        if (!custom_context_menu_) {\n          ShowSelectionPopup();\n        }\n      });\n  AddGestureRecognizer(std::move(long_press_recognizer));\n  AddGestureRecognizer(std::move(double_tap_recognizer));\n#endif\n#else\n  auto drag_recognizer =\n      std::make_unique<DragGestureRecognizer>(page_view()->gesture_manager());\n  drag_recognizer->SetDelegate(this);\n  drag_recognizer->SetTouchSlop(1);\n  drag_recognizer_ = drag_recognizer.get();\n  drag_recognizer->SetDragDownCallback(\n      [this](const PointerEvent&) { RequestFocus(); });\n  drag_recognizer->SetDragStartCallback(\n      [this](const FloatPoint& event) { PerformBeginSelection(event); });\n  drag_recognizer->SetDragUpdateCallback(\n      [this](const FloatPoint& event, const FloatSize& delta) {\n        PerformMoveSelection(event);\n      });\n  drag_recognizer->SetDragCancelCallback(\n      [this]() { PerformCancelSelection(); });\n  AddGestureRecognizer(std::move(drag_recognizer));\n#endif\n}\n\nTextRange TextView::SelectWord(size_t pos) {\n  auto painter = GetRenderText()->GetPainter();\n  auto word_range = painter->GetWordBoundary(pos);\n  GetRenderText()->SetSelection(word_range);\n  return word_range;\n}\n\nvoid TextView::PerformBeginSelection(FloatPoint point) {\n#ifndef ENABLE_CLAY_LITE\n  point -= BoundsRelativeTo(nullptr).location();\n  selection_start_pos_ =\n      GetRenderText()\n          ->GetPainter()\n          ->GetGlyphPositionAtCoordinate(point.x(), point.y())\n          .first;\n  selection_end_pos_ = selection_start_pos_;\n  static_cast<RenderText*>(render_object())\n      ->SetSelection(TextRange(selection_start_pos_, selection_end_pos_));\n#endif\n}\n\nvoid TextView::PerformMoveSelection(FloatPoint point,\n                                    SelectionHandleView* handle_bar) {\n#ifndef ENABLE_CLAY_LITE\n  point -= BoundsRelativeTo(nullptr).location();\n  selection_end_pos_ = GetRenderText()\n                           ->GetPainter()\n                           ->GetGlyphPositionAtCoordinate(point.x(), point.y())\n                           .first;\n  auto render_text = GetRenderText();\n  render_text->SetSelection(\n      TextRange(selection_start_pos_, selection_end_pos_));\n  auto text_box = render_text->GetEndTextPositionTopAndBottom();\n  BringIntoView(&text_box);\n#endif\n}\n\nvoid TextView::PerformCancelSelection() {\n  HideSelectionPopup();\n  HideSelectionHandle();\n}\n\nvoid TextView::OnSelectionChanged(int selection_start, int selection_end) {\n  std::string direction;\n  if (selection_start > selection_end) {\n    direction = \"backward\";\n  } else {\n    direction = \"forward\";\n  }\n  page_view()->SendEvent(\n      id(), event_attr::kEventEditSelectionChange,\n      {\"start\", \"end\", \"direction\"}, std::min(selection_start, selection_end),\n      std::max(selection_start, selection_end), direction.c_str());\n}\n\nvoid TextView::setTextSelection(const LynxModuleValues& args,\n                                const LynxUIMethodCallback& callback) {\n  int start_x = 0, start_y = 0, end_x = 0, end_y = 0;\n  bool show_start_handle = true, show_end_handle = true;\n  CastNamedLynxModuleArgs(\n      {\"startX\", \"startY\", \"endX\", \"endY\", \"showStartHandle\", \"showEndHandle\"},\n      args, start_x, start_y, end_x, end_y, show_start_handle, show_end_handle);\n  auto render_text = GetRenderText();\n  auto start_index = render_text->GetPainter()\n                         ->GetGlyphPositionAtCoordinate(start_x, start_y)\n                         .first;\n  auto end_index = render_text->GetPainter()\n                       ->GetGlyphPositionAtCoordinate(end_x, end_y)\n                       .first;\n  render_text->SetSelection(TextRange(start_index, end_index));\n  ShowSelectionHandle(show_start_handle, show_end_handle);\n\n  const auto& line_rects =\n      GetRenderText()->GetTextLineRects(start_index, end_index);\n  const auto& bounding_rect = page_view()->ConvertTo<kPixelTypeLogical>(\n      GetRenderText()->GetTextBoundingRect(start_index, end_index, line_rects));\n  clay::Value::Map result;\n  result[\"boundingRect\"] = clay::Value(CreateRectMap(bounding_rect));\n  clay::Value::Array box_array(line_rects.size());\n  for (size_t i = 0; i < line_rects.size(); i++) {\n    box_array[i] = clay::Value(CreateRectMap(\n        page_view()->ConvertTo<kPixelTypeLogical>(line_rects[i])));\n  }\n  clay::Value::Array handle_array(2);\n  if (!line_rects.empty()) {\n    auto start_handle_x =\n        page_view()->ConvertTo<kPixelTypeLogical>(line_rects.front().left()) -\n        -1;\n    auto start_handle_y =\n        page_view()->ConvertTo<kPixelTypeLogical>(line_rects.front().top()) - 6;\n    auto end_handle_x =\n        page_view()->ConvertTo<kPixelTypeLogical>(line_rects.front().right()) +\n        1;\n    auto end_handle_y =\n        page_view()->ConvertTo<kPixelTypeLogical>(line_rects.front().bottom()) +\n        6;\n    handle_array[0] =\n        clay::Value(CreateHandleMap(start_handle_x, start_handle_y, 6));\n    handle_array[1] =\n        clay::Value(CreateHandleMap(end_handle_x, end_handle_y, 6));\n  } else {\n    handle_array[0] = clay::Value();\n    handle_array[1] = clay::Value();\n  }\n  result[\"boxes\"] = clay::Value(std::move(box_array));\n  result[\"handles\"] = clay::Value(std::move(handle_array));\n  callback(LynxUIMethodResult::kSuccess, clay::Value(std::move(result)));\n}\n\nvoid TextView::getTextBoundingRect(const LynxModuleValues& args,\n                                   const LynxUIMethodCallback& callback) {\n  int start = -1, end = -1;\n  CastNamedLynxModuleArgs({\"start\", \"end\"}, args, start, end);\n  if (start >= end || start < 0 || end < 0 ||\n      start > static_cast<int>(GetRenderText()->GetText().length()) ||\n      end > static_cast<int>(GetRenderText()->GetText().length())) {\n    callback(LynxUIMethodResult::kParamInvalid, clay::Value());\n    return;\n  }\n  const auto& line_rects = GetRenderText()->GetTextLineRects(start, end);\n  const auto& bounding_rect = page_view()->ConvertTo<kPixelTypeLogical>(\n      GetRenderText()->GetTextBoundingRect(start, end, line_rects));\n\n  clay::Value::Map result;\n  result[\"boundingRect\"] = clay::Value(CreateRectMap(bounding_rect));\n  clay::Value::Array array(line_rects.size());\n  for (size_t i = 0; i < line_rects.size(); i++) {\n    array[i] = clay::Value(CreateRectMap(\n        page_view()->ConvertTo<kPixelTypeLogical>(line_rects[i])));\n  }\n  result[\"boxes\"] = clay::Value(std::move(array));\n  callback(LynxUIMethodResult::kSuccess, clay::Value(std::move(result)));\n}\n\nvoid TextView::getSelectedText(const LynxUIMethodCallback& callback) {\n  auto text = lynx::base::U16StringToU8(GetRenderText()->GetSelectionString());\n  callback(LynxUIMethodResult::kSuccess, clay::Value(std::move(text)));\n}\n\nRenderText* TextView::GetRenderText() {\n  return static_cast<RenderText*>(render_object_.get());\n}\n\nvoid TextView::OnContentSizeChanged(const FloatRect& old_rect,\n                                    const FloatRect& new_rect) {\n  BaseView::OnContentSizeChanged(old_rect, new_rect);\n  if (old_rect.width() == new_rect.width()) {\n    return;\n  }\n  MarkNeedsLayout();\n}\n\nvoid TextView::OnBoundsChanged(const FloatRect& old_bounds,\n                               const FloatRect& new_bounds) {\n  BaseView::OnBoundsChanged(old_bounds, new_bounds);\n  MarkNeedsLayout();\n}\n\nvoid TextView::FocusHasChanged(bool focused, bool is_leaf) {\n#ifndef ENABLE_CLAY_LITE\n  GetRenderText()->SetSelection(\n      TextRange(selection_end_pos_, selection_end_pos_));\n#endif\n  BaseView::FocusHasChanged(focused, is_leaf);\n  HideSelectionPopup();\n  HideSelectionHandle();\n}\n\nBaseView* TextView::GetTopViewToAcceptEvent(const FloatPoint& position,\n                                            FloatPoint* relative_position) {\n  FML_DCHECK(relative_position);\n  if (!BaseView::CanAcceptEvent()) {\n    // Not layouted yet.\n    return nullptr;\n  }\n\n  FloatPoint point_by_self = GetPointBySelf(position);\n  if (point_by_self.x() < 0 || point_by_self.x() > Width() ||\n      point_by_self.y() < 0 || point_by_self.y() > Height()) {\n    return nullptr;\n  }\n\n  FloatPoint point_by_paragraph = point_by_self;\n  point_by_paragraph.Move(-BorderLeft() - PaddingLeft(),\n                          -BorderTop() - PaddingTop());\n  *relative_position = point_by_paragraph;\n  BaseView* view = nullptr;\n  view = GetViewAtPosition(point_by_paragraph, position);\n  return view ?: this;\n}\n\nBaseView* TextView::GetViewAtPosition(const FloatPoint& point_by_paragraph,\n                                      const FloatPoint& point_by_page) {\n  auto paragraph = GetRenderText()->GetPainter()->GetParagraph();\n  if (!paragraph) {\n    return nullptr;\n  }\n\n  // Check if the point is located in a placeholder which is inline image /\n  // inline view.\n  int index = -1;\n  for (const auto& box : paragraph->GetRectsForPlaceholders()) {\n    if (box.rect.Contains(point_by_paragraph.x(), point_by_paragraph.y())) {\n      index = box.placeholder_id;\n      break;\n    }\n  }\n  if (index >= 0) {\n    for (auto image_index : inline_images_index_) {\n      if (image_index.second == index) {\n        return page_view_->FindViewByViewId(image_index.first);\n      }\n    }\n    for (auto view_index : inline_views_index_) {\n      if (view_index.second == index) {\n        auto view = page_view_->FindViewByViewId(view_index.first);\n        FloatPoint relative_position;\n        auto top_view =\n            view->GetTopViewToAcceptEvent(point_by_page, &relative_position);\n        return top_view ? top_view : view;\n      }\n    }\n    return nullptr;\n  }\n\n  auto text_pos = paragraph->GetGlyphPositionAtCoordinate(\n      point_by_paragraph.x(), point_by_paragraph.y());\n\n  // If there is no click on the text, no event response is required\n  if (!ClickOnText(text_pos.position, point_by_paragraph, paragraph)) {\n    return nullptr;\n  }\n\n  // Check if the point is located in an inline text / inline truncation.\n  for (auto child : children_) {\n    if (child->Is<View>()) {\n      for (auto truncation_child : child->GetChildren()) {\n        if (truncation_child->Is<InlineTextView>()) {\n          auto view = static_cast<InlineTextView*>(truncation_child)\n                          ->GetDeepestViewInPos(text_pos);\n          if (view != nullptr) {\n            return view;\n          }\n        }\n      }\n    } else if (child->Is<InlineTextView>()) {\n      auto view =\n          static_cast<InlineTextView*>(child)->GetDeepestViewInPos(text_pos);\n      if (view != nullptr) {\n        return view;\n      }\n    }\n  }\n  return nullptr;\n}\n\nbool TextView::ClickOnText(size_t glyph_index,\n                           const FloatPoint& point_by_paragraph,\n                           txt::Paragraph* paragraph) {\n  if (paragraph) {\n    auto line_metrics = paragraph->GetLineMetrics();\n    for (auto line_metric : line_metrics) {\n      if (glyph_index < line_metric.start_index ||\n          glyph_index >= line_metric.end_index) {\n        continue;\n      }\n      auto text_boxes = paragraph->GetRectsForRange(\n          std::max(int(glyph_index) - 1, int(line_metric.start_index)),\n          std::min(glyph_index + 1, line_metric.end_index),\n          txt::Paragraph::RectHeightStyle::kTight,\n          txt::Paragraph::RectWidthStyle::kTight);\n      for (auto box : text_boxes) {\n        if (point_by_paragraph.x() >= box.rect.Left() &&\n            point_by_paragraph.x() <= box.rect.Right() &&\n            point_by_paragraph.y() >= box.rect.Top() &&\n            point_by_paragraph.y() <= box.rect.Bottom()) {\n          return true;\n        }\n      }\n    }\n  }\n  return false;\n}\n\nstd::vector<FloatPoint> TextView::GetAnchorPosition() {\n#ifndef ENABLE_CLAY_LITE\n  RenderText* render_text = GetRenderText();\n  auto select_range = render_text->GetSelectionRange();\n  std::vector<Point> end_points = render_text->GetPointsFromRangeSelection(\n      select_range[0], select_range[1]);\n  if (end_points.empty()) {\n    return std::vector<FloatPoint>();\n  }\n  FML_DCHECK(selection_handle_container_);\n  auto container_bounds_rect =\n      selection_handle_container_->BoundsRelativeTo(nullptr);\n  auto bounds_rect = BoundsRelativeTo(nullptr);\n  auto start_point = FloatPoint(0 + bounds_rect.left(), 0 + bounds_rect.top());\n  auto end_point =\n      FloatPoint(width_ + bounds_rect.left(),\n                 std::min(height_ + bounds_rect.top(), bounds_rect.bottom()));\n  auto left = std::min(start_point.x(), end_point.x());\n  auto top = std::min(start_point.y(), end_point.y());\n  auto right = std::max(start_point.x(), end_point.x());\n  auto bottom = std::max(start_point.y(), end_point.y());\n  FloatRect editing_region = FloatRect(left, top, right - left, bottom - top);\n  bool is_multiline =\n      end_points.back().y() - end_points.front().y() >\n      render_text->GetPainter()->GetLineHeightForPosition(select_range[1]) / 2;\n  double mid_x = is_multiline\n                     ? editing_region.width() / 2\n                     : (end_points.front().x() + end_points.back().x()) / 2;\n  // TODO(wangyanyi) now just not consider the iOS, because in iOS there is a\n  // safe area concept\n  double anchor_x =\n      std::clamp(static_cast<double>(mid_x + editing_region.x()), 8.0,\n                 static_cast<double>(page_view()->Width()) - 8.0);\n  FloatPoint anchor_above = FloatPoint(\n      anchor_x,\n      std::max<float>(end_points.front().y() -\n                          render_text->GetPainter()->GetLineHeightForPosition(\n                              select_range[1]) +\n                          editing_region.y(),\n                      container_bounds_rect.top()));\n  FloatPoint anchor_blow =\n      FloatPoint(anchor_x, end_points.back().y() + editing_region.y());\n  return std::vector<FloatPoint>{anchor_above, anchor_blow};\n#else\n  return std::vector<FloatPoint>();\n#endif\n}\n\nvoid TextView::ShowSelectionPopup() {\n#ifndef ENABLE_CLAY_LITE\n  if (GetRenderText()->IsCollapsed()) {\n    return;\n  }\n  HideSelectionPopup();\n  if (!selection_popup_) {\n    selection_popup_ = new SelectionPopupView(page_view());\n    selection_popup_->SetCopyFunction([weak = weak_factory_.GetWeakPtr()]() {\n      if (weak) {\n        weak->HandleCopy();\n      }\n    });\n    selection_popup_->SetSelectAllFunction(\n        [weak = weak_factory_.GetWeakPtr()]() {\n          if (weak) {\n            weak->HandleSelectAll();\n          }\n        });\n  }\n  selection_popup_->SetAnchorOffset(GetAnchorPosition());\n  selection_popup_->SetBoundsHeightAndWidth(page_view()->Width(),\n                                            page_view()->Height());\n  selection_popup_->BuildSelectionPopup(\n      std::vector<ActionType>{ActionType::kCopy, ActionType::kSelectAll});\n  page_view()->AddChild(selection_popup_);\n#endif\n}\n\nvoid TextView::HideSelectionPopup() {\n#ifndef ENABLE_CLAY_LITE\n  if (selection_popup_) {\n    page_view()->RemoveChild(selection_popup_);\n    delete selection_popup_;\n    selection_popup_ = nullptr;\n  }\n#endif\n}\n\nFloatRect TextView::GetDisplayRect() {\n  FloatRect result = BoundsRelativeTo(nullptr);\n  auto parent = Parent();\n  while (parent) {\n    auto parent_rect = parent->BoundsRelativeTo(nullptr);\n    result.Intersect(parent_rect);\n    parent = parent->Parent();\n  }\n  return result;\n}\n\nvoid TextView::ShowSelectionHandle(bool show_start_handle,\n                                   bool show_end_handle) {\n#ifndef ENABLE_CLAY_LITE\n  if (GetRenderText()->IsCollapsed()) {\n    return;\n  }\n  HideSelectionHandle();\n  auto stroke_width = page_view()->ConvertFrom<kPixelTypeLogical>(2);\n  if (!start_selection_handle_ && show_start_handle) {\n    start_selection_handle_ =\n        new SelectionHandleView(page_view(), TextSelectionHandleType::kLeft);\n    start_selection_handle_->SetHandleMove(\n        [this](const FloatPoint& position, SelectionHandleView* view) {\n          UpdateSelectionHandle(position, view);\n          if (!custom_context_menu_) {\n            ShowSelectionPopup();\n          }\n        });\n    auto text_box = GetRenderText()->GetStartTextPositionTopAndBottom();\n    auto offset =\n        FloatPoint(text_box.GetLeft() + scroll_offset_.x() + stroke_width,\n                   text_box.GetTop() + scroll_offset_.y() + stroke_width);\n    start_selection_handle_->BuildSelectionHandle(text_box.rect.height(),\n                                                  offset);\n  }\n  if (!end_selection_handle_ && show_end_handle) {\n    end_selection_handle_ =\n        new SelectionHandleView(page_view(), TextSelectionHandleType::kRight);\n    end_selection_handle_->SetHandleMove(\n        [this](const FloatPoint& position, SelectionHandleView* view) {\n          UpdateSelectionHandle(position, view);\n          if (!custom_context_menu_) {\n            ShowSelectionPopup();\n          }\n        });\n    auto text_box = GetRenderText()->GetEndTextPositionTopAndBottom();\n    auto offset =\n        FloatPoint(text_box.GetRight() + scroll_offset_.x() + stroke_width,\n                   text_box.GetTop() + scroll_offset_.y() + stroke_width);\n    end_selection_handle_->BuildSelectionHandle(text_box.rect.height(), offset);\n  }\n  if (!selection_handle_container_) {\n    selection_handle_container_ =\n        new OverlayView(-1, \"handle_container\", page_view());\n    selection_handle_container_->SetOverflow(CSSProperty::OVERFLOW_HIDDEN);\n    page_view()->AddChild(selection_handle_container_);\n    auto display_rect = GetDisplayRect();\n    selection_handle_container_->SetBound(\n        display_rect.left() - stroke_width, display_rect.top() - stroke_width,\n        display_rect.width() + 2 * stroke_width,\n        display_rect.height() + 2 * stroke_width);\n    selection_handle_container_->AddChild(start_selection_handle_);\n    selection_handle_container_->AddChild(end_selection_handle_);\n  }\n#endif\n}\n\nvoid TextView::UpdateSelectionHandle(FloatPoint point,\n                                     SelectionHandleView* handle_bar) {\n#ifndef ENABLE_CLAY_LITE\n  if (!handle_bar) {\n    ShowSelectionHandle();\n  }\n  auto stroke_width = page_view()->ConvertFrom<kPixelTypeLogical>(2);\n  auto render_text = GetRenderText();\n  auto handle_point = BoundsRelativeTo(this).location();\n  float delta =\n      handle_bar->ProcessHandlePos(handle_point, render_text->GetLeftTextBox(),\n                                   render_text->GetRightTextBox());\n  point -= BoundsRelativeTo(nullptr).location();\n  point.SetY(point.y() - delta);\n  auto end_pos = render_text->GetPainter()->GetGlyphPositionAtCoordinate(\n      point.x(), point.y());\n  auto selection_range = render_text->GetSelectionRange();\n  if (handle_bar->GetHandleType() == TextSelectionHandleType::kLeft) {\n    render_text->SetSelection(TextRange(selection_range[1], end_pos.first));\n    selection_start_pos_ = selection_range[1];\n  } else {\n    render_text->SetSelection(TextRange(selection_range[0], end_pos.first));\n    selection_start_pos_ = selection_range[0];\n  }\n  selection_end_pos_ = end_pos.first;\n  TextBox end_box = GetRenderText()->GetEndTextPositionTopAndBottom();\n  BringIntoView(&end_box);\n\n  FloatPoint left_offset;\n  FloatPoint right_offset;\n  if (selection_end_pos_ > selection_start_pos_) {\n    auto left_box = render_text->GetLeftTextBox();\n    auto right_box = render_text->GetRightTextBox();\n    left_offset =\n        FloatPoint(left_box.GetLeft() + scroll_offset_.x() + stroke_width,\n                   left_box.GetTop() + scroll_offset_.y() + stroke_width);\n    right_offset =\n        FloatPoint(right_box.GetRight() + scroll_offset_.x() + stroke_width,\n                   right_box.GetTop() + scroll_offset_.y() + stroke_width);\n    handle_bar->SetHandleType(TextSelectionHandleType::kRight);\n    handle_bar->BuildSelectionHandle(right_box.rect.height(), right_offset);\n    if (handle_bar == start_selection_handle_ && end_selection_handle_) {\n      end_selection_handle_->SetHandleType(TextSelectionHandleType::kLeft);\n      end_selection_handle_->BuildSelectionHandle(left_box.rect.height(),\n                                                  left_offset);\n    } else if (handle_bar == end_selection_handle_ && start_selection_handle_) {\n      start_selection_handle_->SetHandleType(TextSelectionHandleType::kLeft);\n      start_selection_handle_->BuildSelectionHandle(left_box.rect.height(),\n                                                    left_offset);\n    }\n\n  } else if (selection_end_pos_ < selection_start_pos_) {\n    auto left_box = render_text->GetLeftTextBox();\n    auto right_box = render_text->GetRightTextBox();\n    left_offset =\n        FloatPoint(left_box.GetLeft() + scroll_offset_.x() + stroke_width,\n                   left_box.GetTop() + scroll_offset_.y() + stroke_width);\n    right_offset =\n        FloatPoint(right_box.GetRight() + scroll_offset_.x() + stroke_width,\n                   right_box.GetTop() + scroll_offset_.y() + stroke_width);\n    handle_bar->SetHandleType(TextSelectionHandleType::kLeft);\n    handle_bar->BuildSelectionHandle(left_box.rect.height(), left_offset);\n    if (handle_bar == start_selection_handle_ && end_selection_handle_) {\n      end_selection_handle_->SetHandleType(TextSelectionHandleType::kRight);\n      end_selection_handle_->BuildSelectionHandle(right_box.rect.height(),\n                                                  right_offset);\n    } else if (handle_bar == end_selection_handle_ && start_selection_handle_) {\n      start_selection_handle_->SetHandleType(TextSelectionHandleType::kRight);\n      start_selection_handle_->BuildSelectionHandle(right_box.rect.height(),\n                                                    right_offset);\n    }\n  } else {\n    HideSelectionHandle();\n    HideSelectionPopup();\n  }\n#endif\n}\n\nvoid TextView::HideSelectionHandle() {\n#ifndef ENABLE_CLAY_LITE\n  page_view()->RemoveChild(selection_handle_container_);\n  if (start_selection_handle_) {\n    selection_handle_container_->RemoveChild(start_selection_handle_);\n    delete start_selection_handle_;\n    start_selection_handle_ = nullptr;\n  }\n  if (end_selection_handle_) {\n    selection_handle_container_->RemoveChild(end_selection_handle_);\n    delete end_selection_handle_;\n    end_selection_handle_ = nullptr;\n  }\n  delete selection_handle_container_;\n  selection_handle_container_ = nullptr;\n#endif\n}\n\nvoid TextView::HandleCopy() {\n#ifndef ENABLE_CLAY_LITE\n  auto editing_text = GetRenderText()->GetSelectionString();\n  page_view()->SetClipboardData(editing_text);\n  GetRenderText()->SetSelection(\n      TextRange(selection_end_pos_, selection_end_pos_));\n  page_view()->GetTaskRunner()->PostTask([weak = weak_factory_.GetWeakPtr()]() {\n    if (weak) {\n      weak->HideSelectionPopup();\n    }\n  });\n#endif\n}\n\nvoid TextView::HandleSelectAll() {\n#ifndef ENABLE_CLAY_LITE\n  GetRenderText()->SetAllSelection();\n  page_view()->GetTaskRunner()->PostTask([weak = weak_factory_.GetWeakPtr()]() {\n    if (weak) {\n      weak->HideSelectionPopup();\n    }\n  });\n#endif\n}\n\nvoid TextView::BringIntoView(TextBox* text_box) {\n  if (text_box == nullptr) {\n    return;\n  }\n  FloatSize scroll_offset(0, 0);\n  ScrollView* scroll_view = FindScrollView();\n  if (scroll_view == nullptr) {\n    return;\n  }\n  double target_offset = 0;\n  if (scroll_view->CanScrollY()) {\n    auto additional_offset = std::clamp(\n        static_cast<float>(0.0),\n        text_box->GetBottom() - scroll_view->Height() -\n            scroll_view->GetScrollOffset().y() + kDefaultContentDistance,\n        text_box->GetTop() - scroll_view->GetScrollOffset().y());\n    target_offset = std::clamp(\n        (additional_offset + scroll_view->GetScrollOffset().y()), 0.f,\n        static_cast<RenderScroll*>(scroll_view->render_object())\n            ->MaxScrollHeight());\n#ifndef ENABLE_CLAY_LITE\n    if (start_selection_handle_) {\n      start_selection_handle_->SetScrollOffset(FloatPoint(0, target_offset));\n    }\n    if (end_selection_handle_) {\n      end_selection_handle_->SetScrollOffset(FloatPoint(0, target_offset));\n    }\n#endif\n  } else if (scroll_view->CanScrollX()) {\n    auto additional_offset = std::clamp(\n        static_cast<float>(0.0),\n        text_box->GetRight() - scroll_view->Width() -\n            scroll_view->GetScrollOffset().x() + kDefaultContentDistance,\n        text_box->GetLeft() - scroll_view->GetScrollOffset().x());\n    target_offset = std::clamp(\n        (additional_offset + scroll_view->GetScrollOffset().x()), 0.f,\n        static_cast<RenderScroll*>(scroll_view->render_object())\n            ->MaxScrollWidth());\n#ifndef ENABLE_CLAY_LITE\n    if (start_selection_handle_) {\n      start_selection_handle_->SetScrollOffset(FloatPoint(target_offset, 0));\n    }\n    if (end_selection_handle_) {\n      end_selection_handle_->SetScrollOffset(FloatPoint(target_offset, 0));\n    }\n#endif\n  }\n  scroll_view->StopAnimation();\n  scroll_view->ScrollTo(false, target_offset);\n}\n\nScrollView* TextView::FindScrollView() {\n  BaseView* parent = Parent();\n  while (parent) {\n    if (parent->Is<ScrollView>()) {\n      return static_cast<ScrollView*>(parent);\n    }\n    parent = parent->Parent();\n  }\n  return nullptr;\n}\n\n#ifdef ENABLE_ACCESSIBILITY\nstd::u16string TextView::GetAccessibilityLabel() const {\n  FML_DCHECK(render_object());\n  return static_cast<RenderText*>(render_object())->GetText();\n}\n#endif\n\nbool TextView::IsPointerAllowed(const GestureRecognizer& gesture_recognizer,\n                                const PointerEvent& event) {\n  if (event.device == PointerEvent::DeviceType::kMouse) {\n    return event.buttons == PointerEvent::MouseButton::kPrimary;\n  } else {\n    return true;\n  }\n}\n\nvoid TextView::OnViewPostionUpdate(FloatPoint scroll_offset) {\n#ifndef ENABLE_CLAY_LITE\n  if (start_selection_handle_) {\n    start_selection_handle_->UpdatePosWithScroll(scroll_offset -\n                                                 scroll_offset_);\n  }\n  if (end_selection_handle_) {\n    end_selection_handle_->UpdatePosWithScroll(scroll_offset - scroll_offset_);\n  }\n  if (selection_popup_) {\n    selection_popup_->UpdatePosWithScroll(\n        scroll_offset, selection_handle_container_->BoundsRelativeTo(nullptr));\n  }\n  scroll_offset_ = scroll_offset;\n#endif\n  BaseView::OnViewPostionUpdate(scroll_offset);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/text/text_view.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_TEXT_VIEW_H_\n#define CLAY_UI_COMPONENT_TEXT_TEXT_VIEW_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/common/text_input_type_traits.h\"\n#include \"clay/ui/component/overlay_view.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/selection_handle_view.h\"\n#include \"clay/ui/component/selection_popup_view.h\"\n#include \"clay/ui/component/text/base_text_view.h\"\n#include \"clay/ui/component/text/layout_client.h\"\n#include \"clay/ui/gesture/drag_gesture_recognizer.h\"\n#include \"clay/ui/gesture/long_press_gesture_recognizer.h\"\n#include \"clay/ui/gesture/multi_tap_gesture_recognizer.h\"\n#include \"clay/ui/rendering/text/render_text.h\"\n\nnamespace clay {\n\nclass TextView : public WithTypeInfo<TextView, BaseTextView>,\n                 public GestureRecognizer::Delegate {\n public:\n  TextView(int id, PageView* page_view);\n  TextView(int id, const std::string& tag,\n           std::unique_ptr<RenderObject> render_object, PageView* page_view);\n  ~TextView() override;\n\n  void SetAttribute(const char* attr, const clay::Value& value) override;\n  void PushInlineImageIndex(int id, int placeholder_id);\n  void PushInlineViewIndex(int id, int placeholder_id);\n  void SetBorderWidth(std::vector<Side> sides,\n                      std::vector<float> widths) override;\n  void SetPaddings(float padding_left, float padding_top, float padding_right,\n                   float padding_bottom) override;\n\n  /**\n   * @brief Change the foreground color of view.\n   *\n   * In CSS, the color property is mainly used to set the foreground color of an\n   * element, which is the color of the text in BaseView.\n   *\n   * Because the process of text layout is mainly completed in the asynchronous\n   * threads of Lynx, this interface is currently only available for Compositor\n   * animations.\n   *\n   * @param color The target color of text or indicator.\n   */\n  void SetColor(Color color);\n\n  bool OnKeyEvent(const KeyEvent* event) override;\n  bool ApplyHotKey(const KeyEvent* key_event);\n  void UpdateHotKeyTag(LogicalKeyboardKey key_code, bool is_up);\n  void HandleCommandHotKey(LogicalKeyboardKey key_code);\n  void HandleCtrlHotKey(LogicalKeyboardKey key_code);\n  void HandleWinCtrlAndMacCommandHotKey(LogicalKeyboardKey key_code);\n\n  void ResetGestureRecognizers();\n  void ClearGestureRecognizers();\n\n  void FocusHasChanged(bool focused, bool is_leaf) override;\n  bool OnScrollToVisible() override { return false; }\n\n  void PerformBeginSelection(FloatPoint point);\n  void PerformMoveSelection(FloatPoint point,\n                            SelectionHandleView* handle_bar = nullptr);\n  void PerformCancelSelection();\n\n  TextRange SelectWord(size_t pos);\n\n  void OnSelectionChanged(int selection_start, int selection_end);\n\n  void setTextSelection(const LynxModuleValues& args,\n                        const LynxUIMethodCallback& callback);\n  void getTextBoundingRect(const LynxModuleValues& args,\n                           const LynxUIMethodCallback& callback);\n  void getSelectedText(const LynxUIMethodCallback& callback);\n\n  void OnContentSizeChanged(const FloatRect& old_rect,\n                            const FloatRect& new_rect) override;\n  void OnBoundsChanged(const FloatRect& old_bounds,\n                       const FloatRect& new_bounds) override;\n\n  void SetParagraph(std::unique_ptr<txt::Paragraph> paragraph,\n                    std::u16string text) {\n    paragraph_ = std::move(paragraph);\n    GetRenderText()->SetParagraph(paragraph_.get(), text);\n  }\n\n  void SetParagraph(txt::Paragraph* paragraph, std::u16string text) {\n    GetRenderText()->SetParagraph(paragraph, text);\n  }\n\n#ifdef ENABLE_ACCESSIBILITY\n  std::u16string GetAccessibilityLabel() const override;\n#endif\n\n  RenderText* GetRenderText();\n\n  std::vector<FloatPoint> GetAnchorPosition();\n\n  void ShowSelectionPopup();\n  void HideSelectionPopup();\n  void ShowSelectionHandle(bool show_start_handle = true,\n                           bool show_end_handle = true);\n  void HideSelectionHandle();\n  void UpdateSelectionHandle(FloatPoint point,\n                             SelectionHandleView* handle_bar = nullptr);\n\n  FloatRect GetDisplayRect();\n\n  void HandleCopy();\n  void HandleSelectAll();\n\n  void BringIntoView(TextBox* text_box);\n\n  ScrollView* FindScrollView();\n\n  bool IsPointerAllowed(const GestureRecognizer& gesture_recognizer,\n                        const PointerEvent& event) override;\n\n  void OnViewPostionUpdate(FloatPoint scroll_offset) override;\n\n private:\n  BaseView* GetTopViewToAcceptEvent(const FloatPoint& position,\n                                    FloatPoint* relative_position) override;\n  BaseView* GetViewAtPosition(const FloatPoint& point_by_paragraph,\n                              const FloatPoint& point_by_page);\n  bool ClickOnText(size_t glyph_index, const FloatPoint& point_by_paragraph,\n                   txt::Paragraph* paragraph);\n  bool is_text_selection_ = false;\n#if defined(OS_ANDROID) || defined(OS_IOS)\n  MultiTapGestureRecognizer* double_tap_recognizer_ = nullptr;\n  LongPressGestureRecognizer* long_press_recognizer_ = nullptr;\n#else\n  DragGestureRecognizer* drag_recognizer_ = nullptr;\n#endif\n#ifndef ENABLE_CLAY_LITE\n  SelectionPopupView* selection_popup_ = nullptr;\n  OverlayView* selection_handle_container_ = nullptr;\n  SelectionHandleView* start_selection_handle_ = nullptr;\n  SelectionHandleView* end_selection_handle_ = nullptr;\n  int selection_start_pos_ = -1;\n  int selection_end_pos_ = -1;\n  FloatPoint scroll_offset_;\n#endif\n\n  bool custom_text_selection_ = false;\n  bool custom_context_menu_ = false;\n  uint32_t hot_key_tag_ = 0;\n\n  fml::WeakPtrFactory<TextView> weak_factory_;\n  std::unique_ptr<txt::Paragraph> paragraph_;\n  std::unordered_map<int, int> inline_images_index_ = {};\n  std::unordered_map<int, int> inline_views_index_ = {};\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TEXT_TEXT_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/text/text_view_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"base/include/fml/thread.h\"\n#include \"clay/fml/icu_util.h\"\n#include \"clay/fml/paths.h\"\n#include \"clay/third_party/txt/src/txt/font_collection.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/text/raw_text_view.h\"\n#include \"clay/ui/component/text/text_view.h\"\n#include \"clay/ui/resource/font_collection.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/modules/skparagraph/include/TypefaceFontProvider.h\"\n\nnamespace clay {\n\nnamespace {\nconstexpr float kEnoughSpace = 1000.f;\nconstexpr float kInsufficientWidth = 100.f;\nconstexpr float kInsufficientHeight = 10.f;\n\nconstexpr float kHelloWorldMaxIntrinsicWidth = 247.f;\nconstexpr float kHelloWorldNoWrapHeight = 59.f;\nconstexpr float kHelloWorldSoftWrapHeight = kHelloWorldNoWrapHeight * 4;\n\nconst char* kTestTypefacePath =\n    \"gen/lynx/clay/third_party/txt/assets/Roboto-Bold.ttf\";\nconst char* kTestFontFamily = \"Roboto\";\n\nclass TextViewTest : public ::testing::Test {\n protected:\n  TextViewTest() : thread_(\"ui\") {\n    fml::icu::InitializeICU(\"icudtl.dat\");\n    InitializeFontCollection();\n  }\n\n  ~TextViewTest() = default;\n\n  void InitializeFontCollection() {\n    auto font_collection = clay::FontCollection::Instance();\n    font_collection->SetupDefaultFontManager(0);\n    auto txt_font_collection = font_collection->GetFontCollection();\n    txt_font_collection->SetAssetFontManager(CreateTestFontProvider());\n    txt_font_collection->DisableFontFallback();\n    auto skt_fc = txt_font_collection->CreateSktFontCollection();\n  }\n\n  sk_sp<skia::textlayout::TypefaceFontProvider> CreateTestFontProvider() {\n    auto font_provider = sk_make_sp<skia::textlayout::TypefaceFontProvider>();\n    auto directory = fml::paths::GetExecutableDirectoryPath();\n    if (directory.first) {\n      auto ttf_path =\n          fml::paths::JoinPaths({directory.second, kTestTypefacePath});\n      font_provider->registerTypeface(\n          SkTypeface::MakeFromFile(ttf_path.c_str()));\n    } else {\n      FML_LOG(ERROR) << \"Failed get test font ttf files.\";\n    }\n    return font_provider;\n  }\n\n  void SetUp() override {\n    page_view_.reset(new PageView(-1, nullptr, thread_.GetTaskRunner()));\n    text_view_.reset(new TextView(-1, page_view_.get()));\n    text_view_->SetFontWeight(FontWeight::kBold);\n    text_view_->SetFontSize(50);\n    text_view_->SetPaddings(0.f, 0.f, 0.f, 0.f);\n    text_view_->SetFontFamily(kTestFontFamily);\n  }\n\n protected:\n  void SetupTestText(const char* content) {\n    auto raw_text = std::make_unique<RawTextView>(-1, nullptr);\n    raw_text->SetText(content);\n    text_view_->AddChild(raw_text.get());\n    holder_.emplace_back(std::move(raw_text));\n  }\n\n  void SetupHelloWorldText() { SetupTestText(\"hello world\"); }\n\n  void SetupMultilinesText() { SetupTestText(\"hello world\\nhello world\"); }\n\n  // For lifecycle management.\n  std::vector<std::unique_ptr<BaseView>> holder_;\n  std::unique_ptr<TextView> text_view_;\n  std::unique_ptr<PageView> page_view_;\n  fml::Thread thread_;\n};\n}  // namespace\n\nTEST_F(TextViewTest, MeasureWithEnoughSpaceTest) {\n  SetupHelloWorldText();\n  MeasureResult result;\n\n  text_view_->Measure({kEnoughSpace, MeasureMode::kDefinite, kEnoughSpace,\n                       MeasureMode::kDefinite},\n                      result);\n  EXPECT_EQ(result.width, kEnoughSpace);\n  EXPECT_EQ(result.height, kEnoughSpace);\n\n  text_view_->Measure(\n      {kEnoughSpace, MeasureMode::kAtMost, kEnoughSpace, MeasureMode::kAtMost},\n      result);\n  EXPECT_EQ(result.width, kHelloWorldMaxIntrinsicWidth);\n  EXPECT_EQ(result.height, kHelloWorldNoWrapHeight);\n\n  text_view_->Measure(\n      {0.f, MeasureMode::kIndefinite, 0.f, MeasureMode::kIndefinite}, result);\n  EXPECT_EQ(result.width, kHelloWorldMaxIntrinsicWidth);\n  EXPECT_EQ(result.height, kHelloWorldNoWrapHeight);\n}\n\nTEST_F(TextViewTest, MeasureWithoutEnoughSpaceTest) {\n  SetupHelloWorldText();\n  MeasureResult result;\n\n  text_view_->Measure({kInsufficientWidth, MeasureMode::kDefinite,\n                       kInsufficientHeight, MeasureMode::kDefinite},\n                      result);\n  EXPECT_EQ(result.width, kInsufficientWidth);\n  EXPECT_EQ(result.height, kInsufficientHeight);\n\n  // will wrap into 3 lines.\n  text_view_->Measure(\n      {kInsufficientWidth, MeasureMode::kAtMost, 0.f, MeasureMode::kIndefinite},\n      result);\n  EXPECT_EQ(result.width, kInsufficientWidth);\n  EXPECT_EQ(result.height, kHelloWorldSoftWrapHeight);\n}\n\nTEST_F(TextViewTest, MeasureLimitedLinesTest) {\n  text_view_->SetTextMaxLine(1);\n\n  SetupHelloWorldText();\n  MeasureResult result;\n\n  // No wrap. It will be clipped.\n  text_view_->Measure({kInsufficientWidth, MeasureMode::kDefinite, 0.f,\n                       MeasureMode::kIndefinite},\n                      result);\n  EXPECT_EQ(result.width, kInsufficientWidth);\n  EXPECT_EQ(result.height, kHelloWorldNoWrapHeight);\n\n  text_view_->SetTextMaxLine(2);\n\n  text_view_->Measure({kInsufficientWidth, MeasureMode::kDefinite, 0.f,\n                       MeasureMode::kIndefinite},\n                      result);\n  EXPECT_EQ(result.width, kInsufficientWidth);\n  EXPECT_EQ(result.height, kHelloWorldNoWrapHeight * 2);\n}\n\nTEST_F(TextViewTest, MeasureMultiLineContentTest) {\n  SetupMultilinesText();\n  MeasureResult result;\n\n  // No wrap. It will be clipped.\n  text_view_->Measure(\n      {0.f, MeasureMode::kIndefinite, 0.f, MeasureMode::kIndefinite}, result);\n  EXPECT_EQ(result.width, kHelloWorldMaxIntrinsicWidth);\n  EXPECT_EQ(result.height, kHelloWorldNoWrapHeight * 2);\n\n  text_view_->SetTextMaxLine(1);\n\n  // Ignore second line.\n  text_view_->Measure(\n      {0.f, MeasureMode::kIndefinite, 0.f, MeasureMode::kIndefinite}, result);\n  EXPECT_EQ(result.width, kHelloWorldMaxIntrinsicWidth);\n  EXPECT_EQ(result.height, kHelloWorldNoWrapHeight);\n}\n\nTEST_F(TextViewTest, MeasureWithNoContentTest) {\n  MeasureResult result;\n\n  // No content is equal to has content with enough space.\n  text_view_->Measure({kEnoughSpace, MeasureMode::kIndefinite, kEnoughSpace,\n                       MeasureMode::kIndefinite},\n                      result);\n  EXPECT_EQ(result.width, 0.f);\n  EXPECT_EQ(result.height, kHelloWorldNoWrapHeight);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/text/tttext_headers.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_TTTEXT_HEADERS_H_\n#define CLAY_UI_COMPONENT_TEXT_TTTEXT_HEADERS_H_\n\n#if defined(OS_IOS)\n#import <textra/font_info.h>\n#import <textra/fontmgr_collection.h>\n#import <textra/i_canvas_helper.h>\n#import <textra/i_font_manager.h>\n#import <textra/i_typeface_helper.h>\n#import <textra/macro.h>\n#import <textra/painter.h>\n#ifdef ENABLE_SKITY\n#import <textra/platform/skity/skity_canvas_helper.h>\n#import <textra/platform/skity/skity_font_manager.h>\n#import <textra/platform/skity/skity_font_manager_coretext.h>\n#endif\n#import <textra/run_delegate.h>\n#import <textra/style.h>\n#import <textra/text_layout.h>\n#else\n#include <textra/font_info.h>\n#include <textra/fontmgr_collection.h>\n#include <textra/i_canvas_helper.h>\n#include <textra/i_font_manager.h>\n#include <textra/i_typeface_helper.h>\n#include <textra/icu_wrapper.h>\n#include <textra/macro.h>\n#include <textra/painter.h>\n#ifdef ENABLE_SKITY\n#include <textra/platform/skity/skity_canvas_helper.h>\n#include <textra/platform/skity/skity_font_manager.h>\n#if defined(OS_IOS) || defined(OS_OSX)\n#include <textra/platform/skity/skity_font_manager_coretext.h>\n#endif\n#endif\n#include <textra/run_delegate.h>\n#include <textra/style.h>\n#include <textra/text_layout.h>\n#endif\n#endif  // CLAY_UI_COMPONENT_TEXT_TTTEXT_HEADERS_H_\n"
  },
  {
    "path": "clay/ui/component/text/unicode_util.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TEXT_UNICODE_UTIL_H_\n#define CLAY_UI_COMPONENT_TEXT_UNICODE_UTIL_H_\n\n#include <string>\n\n#include \"base/include/string/string_utils.h\"\n\nclass UnicodeUtil {\n public:\n  // support icon-font\n  // e.g. \"&#xE134;今天天气good#xE134;\" -> \"\\uE134今天天气good\\uxE134\"\n  static std::u16string Utf8ToUtf16(const std::string& str) {\n    const std::string identify = \"&#\";\n    int start = 0;\n    int idx = str.find(identify, start);\n    if (idx != -1) {\n      int len = str.length();\n      std::string substr;\n      std::u16string str_16;\n      const char* data = str.data();\n      while (idx != -1) {\n        substr = str.substr(start, idx - start);\n        str_16 += lynx::base::U8StringToU16(substr);\n        char* src = const_cast<char*>(data) + idx;\n        char* end = 0;\n        if (idx + 2 < len && str.at(idx + 2) == 'x') {\n          unsigned int code_point = strtoul(src + 3, &end, 16);\n          str_16 += (char16_t)code_point;\n        } else {\n          unsigned int code_point = strtoul(src + 2, &end, 10);\n          str_16 += (char16_t)code_point;\n        }\n\n        // reset\n        start = end - data;\n        if (data[start] == ';') {\n          // skip ; e.g. &#xE813; -> \\uE813\n          start++;\n        }\n        idx = str.find(identify, start);\n      }\n      if (start != len) {\n        substr = str.substr(start);\n        str_16 += lynx::base::U8StringToU16(substr);\n      }\n      return str_16;\n    }\n    return lynx::base::U8StringToU16(str);\n  }\n};\n\n#endif  // CLAY_UI_COMPONENT_TEXT_UNICODE_UTIL_H_\n"
  },
  {
    "path": "clay/ui/component/title_bar_view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/title_bar_view.h\"\n\n#include <memory>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/rendering/render_container.h\"\n\nnamespace clay {\n\nTitleBarView::TitleBarView(int id, PageView* page_view)\n    : BaseView(id, \"title-bar-view\", std::make_unique<RenderContainer>(),\n               page_view) {}\n\nvoid TitleBarView::HandleEvent(const PointerEvent& event) {\n  if (!moveable_) {\n    return;\n  }\n\n  if (event.type == PointerEvent::EventType::kMoveEvent) {\n    if (move_event_continue_ > 0) {\n      page_view_->MoveWindow();\n    } else {\n      move_event_continue_++;\n    }\n  } else {\n    move_event_continue_ = 0;\n  }\n}\n\nvoid TitleBarView::SetMoveable(const std::string& moveable) {\n  if (moveable == \"TRUE\" || moveable == \"true\") {\n    moveable_ = true;\n  } else {\n    moveable_ = false;\n  }\n}\n\nvoid TitleBarView::SetAttribute(const char* attr_c, const clay::Value& value) {\n  std::string attr = attr_c;\n  if (attr == \"moveable\") {\n    SetMoveable(attribute_utils::GetCString(value));\n  } else {\n    BaseView::SetAttribute(attr_c, value);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/title_bar_view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_TITLE_BAR_VIEW_H_\n#define CLAY_UI_COMPONENT_TITLE_BAR_VIEW_H_\n\n#include <string>\n\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nclass PageView;\n\nclass TitleBarView : public BaseView {\n public:\n  TitleBarView(int id, PageView* page_view);\n  ~TitleBarView() override = default;\n\n  void HandleEvent(const PointerEvent& event) override;\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n\n private:\n  void SetMoveable(const std::string& moveable);\n  bool moveable_ = false;\n  int move_event_continue_ = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_TITLE_BAR_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/view.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/view.h\"\n\n#include <memory>\n\n#include \"clay/ui/rendering/render_container.h\"\n\nnamespace clay {\n\nView::View(int id, PageView* page_view)\n    : WithTypeInfo(id, \"view\", std::make_unique<RenderContainer>(), page_view) {\n}\n\nView::~View() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/view.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_VIEW_H_\n#define CLAY_UI_COMPONENT_VIEW_H_\n\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\n\nclass View : public WithTypeInfo<View, BaseView> {\n public:\n  View(int id, PageView* page_view);\n  ~View() override;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_VIEW_H_\n"
  },
  {
    "path": "clay/ui/component/view_callback/list_container_event_callback_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n//\n\n#include \"clay/ui/component/view_callback/list_container_event_callback_manager.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/component/list/list_container/list_container_view.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nListContainerEventCallbackManager::ListContainerEventCallbackManager(\n    BaseView* view, int32_t callback_id, PageView* page_view)\n    : ScrollEventCallbackManager(view, callback_id) {\n  page_view_ = page_view;\n}\n\nListContainerEventCallbackManager::~ListContainerEventCallbackManager() =\n    default;\n\nvoid ListContainerEventCallbackManager::NotifyScrollStateChange(\n    ScrollState old_state, ScrollState current_state, float velocity,\n    bool is_dragging) const {\n  if (ShouldSendEvent(kScrollStateChange)) {\n    SendScrollStateChangeEvent(current_state);\n  }\n}\n\nvoid ListContainerEventCallbackManager::SendScrollEvent(\n    const char* event_name, const FloatPoint& scrolled,\n    const FloatPoint& offset, const FloatSize& content,\n    const bool is_dragging) const {\n  clay::Value::Array cells_array;\n  if (needs_visible_cells_) {\n    cells_array = static_cast<ListContainerView*>(view_)->GetVisibleCells();\n  }\n  page_view_->SendEvent(\n      callback_id_, event_name,\n      {\"scrollLeft\", \"scrollTop\", \"scrollHeight\", \"scrollWidth\", \"deltaX\",\n       \"deltaY\", \"isDragging\", \"attachedCells\"},\n      offset.x(), offset.y(), content.height(), content.width(), scrolled.x(),\n      scrolled.y(), is_dragging, std::move(cells_array));\n}\n\nvoid ListContainerEventCallbackManager::SendScrollStateChangeEvent(\n    ScrollState state) const {\n  std::vector<float> left_array, right_array, top_array, bottom_array;\n  std::vector<int> position_array;\n  std::vector<std::string> id_array;\n  std::vector<std::string> item_key_array;\n  clay::Value::Map args;\n  args[\"state\"] = clay::Value(static_cast<int>(state));\n  if (needs_visible_cells_) {\n    args[\"attachedCells\"] =\n        clay::Value(static_cast<ListContainerView*>(view_)->GetVisibleCells());\n  }\n  page_view_->SendCustomEvent(callback_id_, event_attr::kEventScrollStateChange,\n                              std::move(args));\n}\n};  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/view_callback/list_container_event_callback_manager.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_VIEW_CALLBACK_LIST_CONTAINER_EVENT_CALLBACK_MANAGER_H_\n#define CLAY_UI_COMPONENT_VIEW_CALLBACK_LIST_CONTAINER_EVENT_CALLBACK_MANAGER_H_\n\n#include \"clay/ui/component/view_callback/scroll_event_callback_manager.h\"\n\nnamespace clay {\n\nclass PageView;\n\nclass ListContainerEventCallbackManager : public ScrollEventCallbackManager {\n public:\n  ListContainerEventCallbackManager(BaseView* view, int32_t callback_id,\n                                    PageView* page_view);\n  ~ListContainerEventCallbackManager();\n\n  void NotifyScrollStateChange(ScrollState old_state, ScrollState current_state,\n                               float velocity, bool is_dragging) const override;\n\n  void SendScrollEvent(const char* event_name, const FloatPoint& scrolled,\n                       const FloatPoint& offset, const FloatSize& content,\n                       const bool is_dragging) const override;\n\n  void SendScrollStateChangeEvent(ScrollState state) const;\n\n  void SetNeedsVisibleCells(bool enable) { needs_visible_cells_ = enable; }\n\n private:\n  bool needs_visible_cells_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_VIEW_CALLBACK_LIST_CONTAINER_EVENT_CALLBACK_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/component/view_callback/list_event_callback_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/view_callback/list_event_callback_manager.h\"\n\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/list/list_item_view_holder.h\"\n#include \"clay/ui/component/list/list_layout_manager.h\"\n\nnamespace clay {\nnamespace {\n\nconstexpr fml::TimeDelta kItemAppearanceEventThrottle =\n    fml::TimeDelta::FromMilliseconds(50);\n\nconstexpr fml::TimeDelta kDefaultEventThrottle =\n    fml::TimeDelta::FromMilliseconds(200);\n\nusing ItemAppearanceEvent = ListEventCallbackManager::ItemAppearanceEvent;\n\nvoid FilterDuplication(std::vector<ItemAppearanceEvent>& events) {\n  std::vector<ItemAppearanceEvent> res;\n  std::vector<bool> should_send(events.size(), false);\n  std::unordered_map<std::string, int> key_to_index;\n\n  for (size_t i = 0; i < events.size(); ++i) {\n    const std::string& key = events[i].item_key;\n    FML_DCHECK(key.length());\n    auto itr = key_to_index.find(key);\n    if (itr == key_to_index.end()) {\n      should_send[i] = true;\n      key_to_index[key] = i;\n    } else {\n      should_send[i] = false;\n      should_send[itr->second] = false;\n      key_to_index.erase(key);\n    }\n  }\n\n  for (size_t i = 0; i < events.size(); ++i) {\n    if (should_send[i]) {\n      res.emplace_back(std::move(events[i]));\n    }\n  }\n\n  std::swap(events, res);\n}\n\nvoid FilterDuplication(std::vector<ItemAppearanceEvent>& gen1,\n                       std::vector<ItemAppearanceEvent>& gen2) {\n  std::unordered_set<std::string> gen1_keys;\n  gen1_keys.reserve(gen1.size());\n  std::unordered_set<std::string> gen2_keys;\n  gen2_keys.reserve(gen2.size());\n\n  for (auto& i : gen1) {\n    gen1_keys.emplace(i.item_key);\n  }\n  for (auto& i : gen2) {\n    const std::string& key = i.item_key;\n    if (gen1_keys.find(key) != gen1_keys.end()) {\n      gen1_keys.erase(key);\n    } else {\n      gen2_keys.emplace(key);\n    }\n  }\n\n  if (gen1_keys.size() == gen1.size()) {\n    FML_DCHECK(gen2_keys.size() == gen2.size());\n    return;\n  }\n\n  std::vector<ItemAppearanceEvent> new_gen1;\n  new_gen1.reserve(gen1_keys.size());\n  for (auto& i : gen1) {\n    if (gen1_keys.find(i.item_key) != gen1_keys.end()) {\n      new_gen1.emplace_back(std::move(i));\n    }\n  }\n\n  std::vector<ItemAppearanceEvent> new_gen2;\n  new_gen2.reserve(gen2_keys.size());\n  for (auto& i : gen2) {\n    if (gen2_keys.find(i.item_key) != gen2_keys.end()) {\n      new_gen2.emplace_back(std::move(i));\n    }\n  }\n\n  std::swap(new_gen1, gen1);\n  std::swap(new_gen2, gen2);\n}\n\n}  // namespace\n\nListEventCallbackManager::ListEventCallbackManager(BaseView* view,\n                                                   int32_t callback_id)\n    : ScrollEventCallbackManager(view, callback_id) {\n  SetScrollEventThrottle(kDefaultEventThrottle);\n}\n\nListEventCallbackManager::~ListEventCallbackManager() = default;\n\nbool ListEventCallbackManager::AddEventCallback(const std::string& event) {\n  if (ScrollEventCallbackManager::AddEventCallback(event)) {\n    return true;\n  }\n  if (event.compare(event_attr::kEventScrollStateChange) == 0) {\n    enable_scroll_state_change_event_ = true;\n    return true;\n  }\n\n  return false;\n}\n\nvoid ListEventCallbackManager::OnScrollStateChange(ScrollState state) {\n  if (!enable_scroll_state_change_event_) {\n    return;\n  }\n  FML_DCHECK(view_ != nullptr);\n  FML_DCHECK(manager_ != nullptr);\n  SendScrollStateChangeEvent(state);\n}\n\nstatic bool ShouldSendItemAppearanceEvent(bool appear,\n                                          ListItemViewHolder* item) {\n  if (!item) {\n    return false;\n  }\n  return !item->GetView()->ItemKey().empty() &&\n         (appear ? item->GetView()->HasEvent(event_attr::kEventNodeAppear)\n                 : item->GetView()->HasEvent(event_attr::kEventNodeDisappear));\n}\n\nvoid ListEventCallbackManager::OnItemAppear(ListItemViewHolder* item) {\n  if (!item) {\n    return;\n  }\n\n  FML_DCHECK(item->GetView() != nullptr);\n  if (ShouldSendItemAppearanceEvent(true, item)) {\n    SendItemAppearanceEvent(true, item);\n  }\n}\n\nvoid ListEventCallbackManager::OnItemDisappear(ListItemViewHolder* item) {\n  if (!item) {\n    return;\n  }\n\n  FML_DCHECK(item->GetView() != nullptr);\n  if (ShouldSendItemAppearanceEvent(false, item)) {\n    SendItemAppearanceEvent(false, item);\n  }\n}\n\nvoid ListEventCallbackManager::NotifyScrolled(\n    const FloatPoint& scrolled, const FloatPoint& offset,\n    const BorderStatus& current_status, const bool is_dragging) {\n  HandleScrolled(scrolled, offset, current_status);\n}\n\nvoid ListEventCallbackManager::SendScrollEvent(const char* event_name,\n                                               const FloatPoint& scrolled,\n                                               const FloatPoint& offset,\n                                               const FloatSize& content,\n                                               const bool is_dragging) const {\n  std::vector<float> left_array, right_array, top_array, bottom_array;\n  std::vector<int> position_array;\n  std::vector<std::string> id_array;\n  size_t visible_count = 0;\n  if (needs_visible_cells_) {\n    visible_count =\n        manager_->GetVisibleItemsInfo(top_array, bottom_array, left_array,\n                                      right_array, position_array, id_array);\n  }\n  clay::Value::Map args;\n  args[\"scrollLeft\"] = clay::Value(0.f);\n  args[\"scrollTop\"] = clay::Value(offset.y());\n  args[\"deltaX\"] = clay::Value(0.f);\n  args[\"deltaY\"] = clay::Value(scrolled.y());\n  clay::Value::Array cells_array(visible_count);\n  for (size_t i = 0; i < visible_count; i++) {\n    clay::Value::Map cell;\n    cell[\"id\"] = clay::Value(id_array[i]);\n    cell[\"position\"] = clay::Value(position_array[i]);\n    cell[\"left\"] = clay::Value(left_array[i]);\n    cell[\"right\"] = clay::Value(right_array[i]);\n    cell[\"top\"] = clay::Value(top_array[i]);\n    cell[\"bottom\"] = clay::Value(bottom_array[i]);\n    cells_array[i] = clay::Value(std::move(cell));\n  }\n  args[\"attachedCells\"] = clay::Value(std::move(cells_array));\n  page_view_->SendCustomEvent(callback_id_, event_name, std::move(args));\n}\n\nvoid ListEventCallbackManager::SendScrollStateChangeEvent(ScrollState state) {\n  std::vector<float> left_array, right_array, top_array, bottom_array;\n  std::vector<int> position_array;\n  std::vector<std::string> id_array;\n  size_t visible_count = 0;\n  if (needs_visible_cells_) {\n    visible_count =\n        manager_->GetVisibleItemsInfo(top_array, bottom_array, left_array,\n                                      right_array, position_array, id_array);\n  }\n\n  clay::Value::Map args;\n  args[\"state\"] = clay::Value(static_cast<int>(state));\n  clay::Value::Array cells_array(visible_count);\n  for (size_t i = 0; i < visible_count; i++) {\n    clay::Value::Map cell;\n    cell[\"id\"] = clay::Value(id_array[i]);\n    cell[\"position\"] = clay::Value(position_array[i]);\n    cell[\"left\"] = clay::Value(left_array[i]);\n    cell[\"right\"] = clay::Value(right_array[i]);\n    cell[\"top\"] = clay::Value(top_array[i]);\n    cell[\"bottom\"] = clay::Value(bottom_array[i]);\n    cells_array[i] = clay::Value(std::move(cell));\n  }\n  args[\"attachedCells\"] = clay::Value(std::move(cells_array));\n  page_view_->SendCustomEvent(callback_id_, event_attr::kEventScrollStateChange,\n                              std::move(args));\n}\n\nvoid ListEventCallbackManager::SendItemAppearanceEvent(\n    bool appear, ListItemViewHolder* item) {\n  int position = item->GetPosition();\n  if (item->IsRemoved()) {\n    position = item->GetLastValidPosition();\n  }\n  appearance_events_gen1_.emplace_back(appear, item->GetView()->id(), position,\n                                       item->GetView()->ItemKey());\n  StartItemAppearanceEventTimer();\n}\n\nvoid ListEventCallbackManager::StartItemAppearanceEventTimer() {\n  if (appearance_event_timer_) {\n    return;\n  }\n  appearance_event_timer_ =\n      std::make_unique<fml::OneshotTimer>(page_view_->GetTaskRunner());\n  appearance_event_timer_->Start(kItemAppearanceEventThrottle,\n                                 [this]() { FlushItemAppearanceEvents(); });\n}\n\nvoid ListEventCallbackManager::FlushItemAppearanceEvents() {\n  appearance_event_timer_ = nullptr;\n  const bool should_continue = appearance_events_gen1_.size() > 0;\n\n  // Before sending the events, we go through two passes:\n  // 1. Filter the generation 1 events so it won't have events for the same\n  // item.\n  // 2. Filter generation 1 with generation 2. The events left in generation 2\n  // are ready to send.\n  FilterDuplication(appearance_events_gen1_);\n  FilterDuplication(appearance_events_gen1_, appearance_events_gen2_);\n\n  for (const ItemAppearanceEvent& event : appearance_events_gen2_) {\n    page_view_->SendEvent(event.view_id,\n                          event.appeared ? event_attr::kEventNodeAppear\n                                         : event_attr::kEventNodeDisappear,\n                          {\"position\", \"key\"}, event.position, event.item_key);\n  }\n\n  // The previous generation 1 events becomes generation 2.\n  appearance_events_gen2_.clear();\n  std::swap(appearance_events_gen1_, appearance_events_gen2_);\n\n  if (should_continue) {\n    StartItemAppearanceEventTimer();\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/view_callback/list_event_callback_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_VIEW_CALLBACK_LIST_EVENT_CALLBACK_MANAGER_H_\n#define CLAY_UI_COMPONENT_VIEW_CALLBACK_LIST_EVENT_CALLBACK_MANAGER_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/component/view_callback/scroll_event_callback_manager.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass ListLayoutManager;\nclass ListItemViewHolder;\nclass ListView;\n\nclass ListEventCallbackManager : public ScrollEventCallbackManager {\n public:\n  struct ItemAppearanceEvent {\n    bool appeared;\n    int view_id;\n    int position;\n    std::string item_key;\n    ItemAppearanceEvent(bool _appeared, int _id, int _pos,\n                        const std::string& _key)\n        : appeared(_appeared), view_id(_id), position(_pos), item_key(_key) {}\n    ItemAppearanceEvent(ItemAppearanceEvent&& other)\n        : appeared(other.appeared),\n          view_id(other.view_id),\n          position(other.position),\n          item_key(std::move(other.item_key)) {}\n  };\n\n  ListEventCallbackManager(BaseView* view, int32_t callback_id);\n  ~ListEventCallbackManager();\n\n  // Should keep the same to\n  // com.lynx.tasm.behavior.ui.ScrollStateChangeListener.java so that we have\n  // the same protocol\n  enum class ScrollState {\n    kIdle = 1,  // Not scrolling\n    kDragging,  // Dragged by outside input\n    kSettling,  // Animating to a final position while not under outside control\n  };\n\n  bool AddEventCallback(const std::string& event) override;\n\n  // Scroll state changed notification\n  void OnScrollStateChange(ScrollState state);\n\n  // Item appear/disappear notification\n  void OnItemAppear(ListItemViewHolder* item);\n  void OnItemDisappear(ListItemViewHolder* item);\n\n  void SetLayoutManager(ListLayoutManager* manager) { manager_ = manager; }\n  void SetNeedsVisibleCells(bool enable) { needs_visible_cells_ = enable; }\n\n  void NotifyScrolled(const FloatPoint& scrolled, const FloatPoint& offset,\n                      const BorderStatus& current_status,\n                      const bool is_dragging = false) override;\n\n  void SendScrollEvent(const char* event_name, const FloatPoint& scrolled,\n                       const FloatPoint& offset, const FloatSize& content,\n                       const bool is_dragging = false) const override;\n\n private:\n  FRIEND_TEST(FocusListViewTest, AppearDisappearEvent);\n  FRIEND_TEST(FocusListViewTest, EventFindFocus);\n  FRIEND_TEST(FocusListViewTest, EventThrottling);\n\n  void SendScrollStateChangeEvent(ScrollState state);\n  void SendItemAppearanceEvent(bool appear, ListItemViewHolder* item);\n  void StartItemAppearanceEventTimer();\n  void FlushItemAppearanceEvents();\n\n  bool enable_scroll_state_change_event_ = false;\n  bool needs_visible_cells_ = false;\n\n  ListLayoutManager* manager_;\n\n  // We use a timer to throttle the list item appearance events.\n  std::unique_ptr<fml::OneshotTimer> appearance_event_timer_;\n  // An event is sent only when it \"survives\" after two generations (two timer\n  // periods).\n  // During the period, if an item appears and then disappears we will discard\n  // the item's events and they won't be sent.\n  std::vector<ItemAppearanceEvent> appearance_events_gen1_;\n  std::vector<ItemAppearanceEvent> appearance_events_gen2_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_VIEW_CALLBACK_LIST_EVENT_CALLBACK_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/component/view_callback/scroll_event_callback_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/view_callback/scroll_event_callback_manager.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nScrollEventCallbackManager::ScrollEventCallbackManager(BaseView* view,\n                                                       int32_t callback_id)\n    : view_(view), callback_id_(callback_id) {\n  FML_DCHECK(view_);\n  page_view_ = view_->page_view();\n}\n\nScrollEventCallbackManager::~ScrollEventCallbackManager() = default;\n\nvoid ScrollEventCallbackManager::NotifyScrolled(\n    const FloatPoint& scrolled, const FloatPoint& offset,\n    const BorderStatus& current_status, bool is_dragging) {\n  HandleScrolled(scrolled, offset, current_status, is_dragging);\n}\n\nvoid ScrollEventCallbackManager::NotifyScrollStateChange(\n    ScrollState old_state, ScrollState current_state, float velocity,\n    bool is_dragging) const {\n  if (ShouldSendEvent(kScrollStateChange)) {\n    page_view_->SendEvent(\n        callback_id_, event_attr::kEventScrollStateChange,\n        {\"currentState\", \"previousState\", \"velocity\", \"source\"},\n        static_cast<int>(current_state), static_cast<int>(old_state), velocity,\n        is_dragging ? \"dragging\" : \"code invocation\");\n  }\n}\n\nvoid ScrollEventCallbackManager::HandleScrolled(\n    const FloatPoint& scrolled, const FloatPoint& offset,\n    const BorderStatus& current_status, const bool is_dragging) {\n  FML_DCHECK(view_ != nullptr);\n  FloatSize zoomed_content = page_view_->ConvertTo<kPixelTypeLogical>(\n      FloatSize(view_->ContentWidth(), view_->ContentHeight()));\n  FloatPoint zoomed_offset = page_view_->ConvertTo<kPixelTypeLogical>(offset);\n  FloatPoint zoomed_scroll = page_view_->ConvertTo<kPixelTypeLogical>(scrolled);\n  auto now = fml::TimePoint::Now();\n  if (ShouldSendEvent(kScrollEvent) &&\n      now - last_scroll_event_ > scroll_event_throttle_) {\n    last_scroll_event_ = now;\n    SendScrollEvent(event_attr::kEventScroll, zoomed_scroll, zoomed_offset,\n                    zoomed_content, is_dragging);\n  }\n\n  if (ShouldSendEvent(kScrollToUpper) && current_status.IsUpper() &&\n      !last_status_.IsUpper()) {\n    SendScrollEvent(event_attr::kEventScrollToUpper, zoomed_scroll,\n                    zoomed_offset, zoomed_content, is_dragging);\n  }\n\n  if (ShouldSendEvent(kScrollToLower) && current_status.IsLower() &&\n      !last_status_.IsLower()) {\n    SendScrollEvent(event_attr::kEventScrollToLower, zoomed_scroll,\n                    zoomed_offset, zoomed_content, is_dragging);\n  }\n  last_status_ = current_status;\n}\n\nvoid ScrollEventCallbackManager::NotifyScrollEnd(\n    const FloatPoint& offset) const {\n  if (ShouldSendEvent(kScrollEnd)) {\n    FML_DCHECK(view_ != nullptr);\n\n    FloatSize zoomed_content = page_view_->ConvertTo<kPixelTypeLogical>(\n        FloatSize(view_->ContentWidth(), view_->ContentHeight()));\n    FloatPoint zoomed_offset = page_view_->ConvertTo<kPixelTypeLogical>(offset);\n    SendScrollEvent(event_attr::kEventScrollEnd, FloatPoint(0, 0),\n                    zoomed_offset, zoomed_content);\n  }\n}\n\nvoid ScrollEventCallbackManager::SendScrollEvent(const char* event_name,\n                                                 const FloatPoint& scrolled,\n                                                 const FloatPoint& offset,\n                                                 const FloatSize& content,\n                                                 const bool is_dragging) const {\n  page_view_->SendEvent(callback_id_, event_name,\n                        {\"scrollLeft\", \"scrollTop\", \"scrollHeight\",\n                         \"scrollWidth\", \"deltaX\", \"deltaY\", \"isDragging\"},\n                        offset.x(), offset.y(), content.height(),\n                        content.width(), scrolled.x(), scrolled.y(),\n                        is_dragging);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/view_callback/scroll_event_callback_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_VIEW_CALLBACK_SCROLL_EVENT_CALLBACK_MANAGER_H_\n#define CLAY_UI_COMPONENT_VIEW_CALLBACK_SCROLL_EVENT_CALLBACK_MANAGER_H_\n\n#include <string>\n\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nclass ScrollEventCallbackManager {\n public:\n  // Because of the wrapper mechanism, the wrapper's id is used when sending\n  // callbacks so we cannot use view->id() directly.\n  ScrollEventCallbackManager(BaseView* view, int32_t callback_id);\n  virtual ~ScrollEventCallbackManager();\n\n  class BorderStatus {\n   public:\n    BorderStatus() : state_(State::kBorderNone) {}\n    bool IsUpper() const {\n      return (state_ & State::kBorderUpper) == State::kBorderUpper;\n    }\n    bool IsLower() const {\n      return (state_ & State::kBorderLower) == State::kBorderLower;\n    }\n    void SetUpper(bool enable) {\n      if (enable) {\n        state_ = static_cast<BorderStatus::State>(state_ | kBorderUpper);\n      } else {\n        state_ = static_cast<BorderStatus::State>(state_ & kBorderLower);\n      }\n    }\n    void SetLower(bool enable) {\n      if (enable) {\n        state_ = static_cast<BorderStatus::State>(state_ | kBorderLower);\n      } else {\n        state_ = static_cast<BorderStatus::State>(state_ & kBorderUpper);\n      }\n    }\n\n   private:\n    enum State {\n      kBorderNone = 0,\n      kBorderUpper = 1 << 0,\n      kBorderLower = 1 << 1,\n      kBorderBoth = kBorderUpper | kBorderLower,\n    };\n    State state_;\n  };\n\n  enum class ScrollState {\n    kIdle = 1,   // Not scrolling\n    kDragging,   // Dragged by outside input\n    kAnimating,  // Animating to a final position while not under outside\n                 // control\n  };\n\n  virtual bool AddEventCallback(const std::string& event) {\n    if (event.compare(event_attr::kEventScroll) == 0) {\n      EnableSendEvent(ScrollEvents::kScrollEvent);\n      return true;\n    } else if (event.compare(event_attr::kEventScrollToUpper) == 0) {\n      EnableSendEvent(ScrollEvents::kScrollToUpper);\n      return true;\n    } else if (event.compare(event_attr::kEventScrollToLower) == 0) {\n      EnableSendEvent(ScrollEvents::kScrollToLower);\n      return true;\n    } else if (event.compare(event_attr::kEventScrollEnd) == 0) {\n      EnableSendEvent(ScrollEvents::kScrollEnd);\n      return true;\n    } else if (event.compare(event_attr::kEventScrollStateChange) == 0) {\n      EnableSendEvent(ScrollEvents::kScrollStateChange);\n      return true;\n    }\n    return false;\n  }\n\n  void SetScrollEventThrottle(fml::TimeDelta throttle) {\n    scroll_event_throttle_ = throttle;\n  }\n\n  virtual void NotifyScrolled(const FloatPoint& scrolled,\n                              const FloatPoint& offset,\n                              const BorderStatus& current_status,\n                              const bool is_dragging = false);\n  void NotifyScrollEnd(const FloatPoint& offset) const;\n\n  virtual void NotifyScrollStateChange(ScrollState old_state,\n                                       ScrollState current_state,\n                                       float velocity, bool is_dragging) const;\n\n  enum ScrollEvents {\n    kNone = 0,\n    kScrollEvent = 1 << 0,\n    kScrollToUpper = 1 << 1,\n    kScrollToLower = 1 << 2,\n    kScrollEnd = 1 << 3,\n    kScrollStateChange = 1 << 4,\n  };\n\n protected:\n  void HandleScrolled(const FloatPoint& scrolled, const FloatPoint& offset,\n                      const BorderStatus& current_status,\n                      bool const is_dragging = false);\n\n  virtual void SendScrollEvent(const char* event_name,\n                               const FloatPoint& scrolled,\n                               const FloatPoint& offset,\n                               const FloatSize& content,\n                               const bool is_dragging = false) const;\n\n  bool ShouldSendEvent(ScrollEvents event) const { return event_flag_ & event; }\n\n  PageView* page_view_ = nullptr;\n  BaseView* view_ = nullptr;\n  int32_t callback_id_ = -1;\n\n private:\n  void EnableSendEvent(ScrollEvents event) {\n    event_flag_ = static_cast<ScrollEvents>(event_flag_ | event);\n  }\n\n  fml::TimeDelta scroll_event_throttle_;\n  fml::TimePoint last_scroll_event_;\n  ScrollEvents event_flag_ = ScrollEvents::kNone;\n  BorderStatus last_status_;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_VIEW_CALLBACK_SCROLL_EVENT_CALLBACK_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/component/view_context.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/view_context.h\"\n\n#include <list>\n#include <map>\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"base/include/string/string_number_convert.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/sticky_info.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/component.h\"\n#include \"clay/ui/component/editable/editable_view.h\"\n#include \"clay/ui/component/list/list_container/list_container_wrapper.h\"\n#include \"clay/ui/component/list/lynx_list_data.h\"\n#include \"clay/ui/component/native_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n#include \"clay/ui/shadow/bundle.h\"\n#ifdef ENABLE_NET_LOADER\n#include \"clay/net/net_loader_manager.h\"\n#endif\n#include <cstdlib>\n\n#include \"clay/public/value.h\"\n#include \"clay/ui/component/view_registry.h\"\n#include \"clay/ui/rendering/render_object.h\"\n#include \"clay/ui/resource/font_collection.h\"\n#include \"clay/ui/shadow/editable_shadow_node.h\"\n#include \"clay/ui/shadow/inline_view_shadow_node.h\"\n#include \"clay/ui/shadow/native_view_shadow_node.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n#include \"clay/ui/shadow/text_shadow_node.h\"\n#define DEBUG_KEYFRAMES 0\n\n#define DEBUG_CLAY_CTX 0\n#define CTX_DEBUG_TAG \"[CLAY]-[CTX] \"\n#if (DEBUG_CLAY_CTX)\n#define CTX_LOG FML_LOG(ERROR) << CTX_DEBUG_TAG\n#else\n#define CTX_LOG FML_EAT_STREAM_PARAMETERS(true)\n#endif\n\nnamespace clay {\n\nViewContext::ViewContext(PageView* root, ShadowNodeOwner* shadow_node_owner)\n    : page_view_(root),\n      shadow_node_owner_(shadow_node_owner),\n      weak_factory_(this) {\n  static uint32_t id = 0;\n  unique_id_ = id++;\n  Isolate::Instance().RegisterViewContext(unique_id_,\n                                          weak_factory_.GetWeakPtr());\n  page_view_->view_context_ = this;\n  FML_DLOG(INFO) << \"ViewContext construction; id = \" << unique_id_;\n}\n\nViewContext::~ViewContext() {\n  Isolate::Instance().UnregisterViewContext(unique_id_);\n  FML_DLOG(INFO) << \"ViewContext destruction; id = \" << unique_id_;\n}\n\nvoid ViewContext::CleanLeakedViews() {\n  std::list<int> leaked_view_ids_;\n  for (auto& pair : view_map_) {\n    if (pair.second != page_view_) {\n      leaked_view_ids_.emplace_back(pair.first);\n    }\n  }\n  for (int id : leaked_view_ids_) {\n    RemoveView(id, -1);\n    DestroyView(id);\n  }\n}\n\nvoid ViewContext::OnOutputSurfaceDestroyed() {\n  for (auto& pair : view_map_) {\n    if (!pair.second->attach_to_tree()) {\n      pair.second->render_object()->MarkSubtreeDirty();\n    }\n  }\n}\n\nint ViewContext::IndexOf(int id) const {\n  auto it = view_map_.find(id);\n  if (it == view_map_.end()) {\n    return -1;\n  }\n  auto child = it->second;\n  auto parent = it->second->Parent();\n  if (!parent) {\n    return -1;\n  }\n  return parent->GetChildIndex(child);\n}\n\nbool ViewContext::CreateView(int id, const std::string& tag_name) {\n  CTX_LOG << \"CreateView id:\" << id << \" tag:\" << tag_name;\n  FML_DCHECK(id != -1);\n\n  if (tag_name == \"page\") {\n    view_map_[id] = page_view_;\n    page_view_->SetID(id);\n    return true;\n  }\n  auto view = ViewRegistry::GetInstance()->CreateView(id, tag_name, page_view_);\n\n  if (!view) {\n    FML_DLOG(ERROR) << \"unsupported view type: \" << tag_name\n                    << \" , fallback to view.\";\n    view = new View(id, page_view_);\n  }\n\n  view->SetDestructListener(\n      [this](BaseView* view) { view_map_.erase(view->id()); });\n  view_map_[id] = view;\n  ConsumeInitialAttributes(view_map_[id]);\n  return true;\n}\n\nvoid ViewContext::AddView(int id, int parent_id, int index) {\n  CTX_LOG << \"AddView id:\" << id << \" parent id:\" << parent_id\n          << \" index: \" << index;\n\n  auto target = view_map_.find(id);\n  auto parent = view_map_.find(parent_id);\n\n  if (parent == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view parent: \" << id << \" not found\";\n    return;\n  }\n\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  if (index < 0) {\n    parent->second->AddChild(target->second);\n    return;\n  }\n\n  parent->second->AddChild(target->second, index);\n}\n\nvoid ViewContext::InsertListItemPaintingNode(int list_id, int child_id) {\n  auto parent = view_map_.find(list_id);\n  auto child = view_map_.find(child_id);\n  if (parent == view_map_.end()) {\n    FML_LOG(ERROR) << \"parent view: \" << list_id << \" not found\";\n    return;\n  }\n  if (child == view_map_.end()) {\n    FML_LOG(ERROR) << \"child view: \" << child_id << \" not found\";\n    return;\n  }\n  if (parent->second->Is<ListContainerWrapper>()) {\n    static_cast<ListContainerWrapper*>(parent->second)\n        ->InsertListItemPaintingNode(child->second);\n  }\n}\n\nvoid ViewContext::RemoveListItemPaintingNode(int list_id, int child_id) {\n  auto parent = view_map_.find(list_id);\n  auto child = view_map_.find(child_id);\n  if (parent == view_map_.end()) {\n    FML_LOG(ERROR) << \"parent view: \" << list_id << \" not found\";\n    return;\n  }\n  if (child == view_map_.end()) {\n    FML_LOG(ERROR) << \"child view: \" << child_id << \" not found\";\n    return;\n  }\n  if (parent->second->Is<ListContainerWrapper>()) {\n    static_cast<ListContainerWrapper*>(parent->second)\n        ->RemoveListItemPaintingNode(child->second);\n  }\n}\n\nvoid ViewContext::RemoveView(int id, int parent_id,\n                             bool is_temporarily_removed) {\n  CTX_LOG << \"RemoveView id:\" << id;\n\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  BaseView* child = target->second;\n  BaseView* parent;\n  if (parent_id < 0) {\n    parent = child->Parent();\n  } else {\n    auto it = view_map_.find(parent_id);\n    if (it == view_map_.end()) {\n      FML_LOG(ERROR) << \"target view parent: \" << id << \" not found\";\n      return;\n    }\n    parent = it->second;\n  }\n\n  if (parent) {\n    if (is_temporarily_removed) {\n      parent->RemoveChildTemporarily(child);\n    } else {\n      parent->RemoveChild(child);\n    }\n  }\n}\n\nvoid ViewContext::DestroyAnonymousView(BaseView* view) {\n  if (!view->IsAnonymousView()) {\n    FML_DCHECK(false);\n    // Anonymous view id is -1, and named view may be destroyed previously.\n    return;\n  }\n  view->Destroy();\n  auto& children = view->GetChildren();\n  auto iter = children.begin();\n  while (iter != children.end()) {\n    if (!DestroyView((*iter)->id())) {\n      DestroyAnonymousView(*iter);\n    }\n    // Remove child dangling pointer in parent.\n    iter = children.erase(iter);\n  }\n  delete view;\n}\n\nbool ViewContext::DestroyView(int id) {\n  CTX_LOG << \"DestroyView id:\" << id;\n\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_DLOG(ERROR) << \"target view: \" << id << \" not found\";\n    return false;\n  }\n\n  BaseView* view = target->second;\n  if (view->IsDelayDestroy()) {\n    view_map_.erase(id);\n    return true;\n  }\n\n  view->Destroy();\n  auto& children = view->GetChildren();\n  auto iter = children.begin();\n  while (iter != children.end()) {\n    if (!DestroyView((*iter)->id())) {\n      DestroyAnonymousView(*iter);\n    }\n    // Remove child dangling pointer in parent.\n    iter = children.erase(iter);\n  }\n\n  delete view;\n  view_map_.erase(id);\n  return true;\n}\n\nvoid ViewContext::ResetPageView() {\n  CTX_LOG << \"ResetPageView\";\n\n  auto& children = page_view_->GetChildren();\n  auto iter = children.begin();\n  while (iter != children.end()) {\n    if (!DestroyView((*iter)->id())) {\n      DestroyAnonymousView(*iter);\n    }\n    // Remove child dangling pointer in parent.\n    iter = children.erase(iter);\n  }\n\n  page_view_->ResetPageView();\n  component_id_to_ui_id_map_.clear();\n  CleanLeakedViews();\n}\n\nShadowNode* ViewContext::CreateShadowNode(int id, const std::string& tag_name,\n                                          bool allow_inline) {\n  CTX_LOG << \"CreateLayoutNode id:\" << id << \" tag:\" << tag_name;\n\n  auto node = ViewRegistry::GetInstance()->CreateShadowNode(\n      id, shadow_node_owner_, tag_name);\n  if (node) {\n    shadow_node_owner_->AddNode(id, node);\n  } else if (allow_inline) {\n    node = new InlineViewShadowNode(shadow_node_owner_, tag_name, id);\n    shadow_node_owner_->AddNode(id, node);\n    // NOTE: Here we must return nullptr instead of the node to pass the proper\n    // flags to Lynx. This requires refactoring.\n    return nullptr;\n  }\n  return node;\n}\n\nint32_t ViewContext::GetTagInfo(const std::string& tag_name) {\n  return ViewRegistry::GetInstance()->GetTagInfo(tag_name);\n}\n\nvoid ViewContext::AddShadowNode(int id, int parent_id, int index) {\n  CTX_LOG << \"AddLayoutNode id:\" << id << \" parent id:\" << parent_id\n          << \" index: \" << index;\n\n  auto target = shadow_node_owner_->GetNode(id);\n  auto parent = shadow_node_owner_->GetNode(parent_id);\n\n  if (!parent || !target) {\n    return;\n  }\n\n  if (index < 0) {\n    parent->AddChild(target);\n  } else {\n    parent->AddChild(target, index);\n  }\n}\n\nvoid ViewContext::NotifyLowMemory() {\n  for (auto& pair : view_map_) {\n    pair.second->NotifyLowMemory();\n  }\n}\n\nvoid ViewContext::RemoveShadowNode(int id) {\n  CTX_LOG << \"RemoveLayout id:\" << id;\n\n  auto child = shadow_node_owner_->GetNode(id);\n  if (!child) {\n    return;\n  }\n\n  ShadowNode* parent = child->Parent();\n  if (parent) {\n    parent->RemoveChild(child);\n  }\n}\n\nbool ViewContext::DestroyShadowNode(int id) {\n  CTX_LOG << \"Destroy shadow node id:\" << id;\n\n  auto node = shadow_node_owner_->GetNode(id);\n  if (!node) {\n    return false;\n  }\n\n  node->Destroy();\n  shadow_node_owner_->RemoveNode(id);\n  return true;\n}\n\nvoid ViewContext::DestroyAllShadowNode() { shadow_node_owner_->ClearNodes(); }\n\nvoid ViewContext::TextMeasure(int id, float width, TextMeasureMode width_mode,\n                              float height, TextMeasureMode height_mode,\n                              float& out_width, float& out_height) {\n  auto node = shadow_node_owner_->GetNode(id);\n  if (!node) {\n    return;\n  }\n\n  Measurable* measurable = node->GetMeasurable();\n  CustomMeasurable* custom_measurable = node->GetCustomMeasurable();\n  if (measurable) {\n    MeasureResult result;\n    measurable->Measure({width, width_mode, height, height_mode}, result);\n    out_width = result.width;\n    out_height = result.height;\n  } else if (custom_measurable) {\n    auto result =\n        custom_measurable->Measure({width, width_mode, height, height_mode});\n    out_width = result.width;\n    out_height = result.height;\n  } else {\n    FML_LOG(ERROR) << \"target shadow node: \" << id << \" is not measurable\";\n  }\n}\n\nvoid ViewContext::OnLayout(int id, float width, TextMeasureMode width_mode,\n                           float height, TextMeasureMode height_mode,\n                           const std::array<float, 4>& paddings,\n                           const std::array<float, 4>& borders) {\n  auto node = shadow_node_owner_->GetNode(id);\n  if (node) {\n    node->OnLayout(width, width_mode, height, height_mode, paddings, borders);\n  }\n}\n\nvoid ViewContext::UpdateRootSize(int32_t width, int32_t height) {\n  page_view_->UpdateRootSize(width, height);\n}\n\nvoid ViewContext::SetShadowNodeAttribute(int id, const char* attr,\n                                         const clay::Value& value) {\n  auto node = shadow_node_owner_->GetNode(id);\n  if (node) {\n    node->SetAttribute(attr, value);\n  }\n}\n\nvoid ViewContext::ScheduleLayout() { shadow_node_owner_->ScheduleLayout(); }\n\nvoid ViewContext::Alignment(int id) {\n  auto node = shadow_node_owner_->GetNode(id);\n  if (node) {\n    auto custom_measurable = node->GetCustomMeasurable();\n    if (custom_measurable) {\n      custom_measurable->Align();\n    }\n  }\n}\n\nvoid ViewContext::Invalidate() { page_view_->RequestPaint(); }\n\nvoid ViewContext::SetBounds(int id, float left, float top, float width,\n                            float height) {\n  CTX_LOG << \"SetBounds id:\" << id << \" \" << left << \",\" << top << \" \" << width\n          << \",\" << height;\n\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetBound(\n      GetPageView()->RoundPixels(left), GetPageView()->RoundPixels(top),\n      GetPageView()->RoundPixels(width), GetPageView()->RoundPixels(height));\n}\n\nvoid ViewContext::SetPaddings(int id, float padding_left, float padding_top,\n                              float padding_right, float padding_bottom) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetPaddings(GetPageView()->RoundPixels(padding_left),\n                    GetPageView()->RoundPixels(padding_top),\n                    GetPageView()->RoundPixels(padding_right),\n                    GetPageView()->RoundPixels(padding_bottom));\n}\n\nvoid ViewContext::SetMargins(int id, float margin_left, float margin_top,\n                             float margin_right, float margin_bottom) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n  auto view = target->second;\n  view->SetMargins(GetPageView()->RoundPixels(margin_left),\n                   GetPageView()->RoundPixels(margin_top),\n                   GetPageView()->RoundPixels(margin_right),\n                   GetPageView()->RoundPixels(margin_bottom));\n}\n\nvoid ViewContext::SetOpacity(int id, float opacity) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetOpacity(opacity);\n}\n\nvoid ViewContext::SetOverflow(int id, int overflow) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetOverflow(overflow);\n}\n\nvoid ViewContext::SetBorderStyle(int id, BorderStyleType left,\n                                 BorderStyleType top, BorderStyleType right,\n                                 BorderStyleType bottom) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetBorderStyle({Side::kLeft, Side::kTop, Side::kRight, Side::kBottom},\n                       {left, top, right, bottom});\n}\n\nvoid ViewContext::SetBorderWidth(int id, int left_width, int top_width,\n                                 int right_width, int bottom_width) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetBorderWidth({Side::kLeft, Side::kTop, Side::kRight, Side::kBottom},\n                       {GetPageView()->RoundPixels(left_width),\n                        GetPageView()->RoundPixels(top_width),\n                        GetPageView()->RoundPixels(right_width),\n                        GetPageView()->RoundPixels(bottom_width)});\n}\n\nvoid ViewContext::SetBorderColor(int id, unsigned int left_color,\n                                 unsigned int top_color,\n                                 unsigned int right_color,\n                                 unsigned int bottom_color) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetBorderColor({Side::kLeft, Side::kTop, Side::kRight, Side::kBottom},\n                       {left_color, top_color, right_color, bottom_color});\n}\n\nvoid ViewContext::SetBorderRadius(int id, const FloatSize& left_top,\n                                  const FloatSize& right_top,\n                                  const FloatSize& right_bottom,\n                                  const FloatSize& left_bottom) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  FloatSize scale_left_top = left_top;\n  FloatSize scale_right_top = right_top;\n  FloatSize scale_right_bottom = right_bottom;\n  FloatSize scale_left_bottom = left_bottom;\n  view->SetBorderRadius(scale_left_top, scale_right_top, scale_right_bottom,\n                        scale_left_bottom);\n}\n\nvoid ViewContext::SetOutlineStyle(int id, BorderStyleType style) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetOutlineStyle(style);\n}\n\nvoid ViewContext::SetOutlineWidth(int id, int width) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetOutlineWidth(width);\n}\n\nvoid ViewContext::SetOutlineColor(int id, unsigned int color) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetOutlineColor(color);\n}\n\nvoid ViewContext::SetCursor(int id, const char* src[], int size) {\n  auto view = GetViewById(id);\n  if (view) {\n    std::vector<std::string> vec;\n    for (int i = 0; i < size; ++i) {\n      vec.emplace_back(src[i]);\n    }\n\n    view->SetCursor(vec);\n  }\n}\n\nvoid ViewContext::SetBackgroundColor(int id, unsigned int color) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  CTX_LOG << \"SetBackgroundColor. id:\" << id << \" color:\" << color;\n\n  auto view = target->second;\n  view->SetBackgroundColor(Color(color));\n}\n\nvoid ViewContext::SetBackground(int id, const BackgroundData& background) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  CTX_LOG << \"SetBackground. id:\" << id;\n\n  auto view = target->second;\n  view->SetBackground(background);\n}\n\nvoid ViewContext::AppendShadow(int id, const Shadow& shadow) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n\n  view->AppendShadow(shadow);\n}\n\nvoid ViewContext::SetTransform(int id, const TransformOperations& ops,\n                               const FloatPoint& origin) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetTransform(ops, origin);\n}\n\nvoid ViewContext::SetTransition(\n    int id, const std::vector<TransitionData>& transition_data) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetTransition(transition_data);\n}\n\nvoid ViewContext::SetAnimation(\n    int id, const std::vector<AnimationData>& animation_data) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetAnimation(animation_data);\n}\n\nvoid ViewContext::SetKeyframes(const clay::Value& keyframes_value) {\n  page_view_->SetKeyframesData(keyframes_value);\n}\n\nvoid ViewContext::SetAttribute(int id, const char* attr,\n                               const clay::Value& value) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  view->SetAttribute(attr, value);\n\n  if (view->Is<Component>() && strcmp(attr, \"ComponentID\") == 0) {\n    std::string component_id;\n    if (value.IsString()) {\n      component_id = value.GetString();\n    } else if (value.IsInt()) {\n      component_id = std::to_string(value.GetInt());\n    } else {\n      component_id = \"-1\";\n    }\n    component_id_to_ui_id_map_[component_id] = id;\n  }\n}\n\nconst clay::ViewportMetrics& ViewContext::GetViewportMetrics() const {\n  return page_view_->GetViewportMetrics();\n}\n\nvoid ViewContext::DidUpdateAttributes(int id) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n  auto view = target->second;\n  CTX_LOG << \"id:\" << id << \" \" << view->GetName() << \" DidUpdateAttributes.\";\n  view->DidUpdateAttributes();\n}\n\nvoid ViewContext::AddEventProp(int id, const char* event) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n  auto view = target->second;\n  view->AddEventCallback(event);\n}\n\nvoid ViewContext::AddShadowNodeEventProp(int id, const char* event) {\n  auto node = shadow_node_owner_->GetNode(id);\n  if (node && node->IsTextShadowNode()) {\n    node->AddEventCallback(event);\n  }\n}\n\nvoid ViewContext::SetRepaintBoundary(int id, bool repaint_boundary) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n\n  auto view = target->second;\n  CTX_LOG << \"id:\" << id << \" \" << view->GetName() << \" SetRepaintBoundary.\";\n  view->SetRepaintBoundary(repaint_boundary);\n}\n\nvoid HostNetLoadFinish(size_t request_seq, bool success, const uint8_t* data,\n                       size_t length) {\n#ifdef ENABLE_NET_LOADER\n  NetLoaderManager::Instance().OnFinished(request_seq, success, data, length);\n#endif\n}\n\nvoid ViewContext::SetNetLoadCallback(NetLoadCallback net_load_callback) {\n#ifdef ENABLE_NET_LOADER\n  if (net_load_callback) {\n    NetLoaderManager::Instance().SetHostNetLoader(\n        [net_load_callback](const std::string& url, const std::string& method,\n                            const std::string& body,\n                            const std::map<std::string, std::string>& headers,\n                            size_t request_seq) {\n          size_t headers_size = headers.size();\n          const char** headers_ptr = nullptr;\n          if (headers_size > 0) {\n            headers_ptr = new const char*[headers_size];\n            size_t index = 0;\n            for (const auto& header : headers) {\n#ifdef _MSC_VER\n              headers_ptr[index++] =\n                  _strdup((header.first + \":\" + header.second).c_str());\n#else\n              headers_ptr[index++] =\n                  strdup((header.first + \":\" + header.second).c_str());\n#endif\n            }\n          }\n\n          net_load_callback(url.c_str(), method.c_str(), body.c_str(),\n                            headers_ptr, headers_size, request_seq,\n                            HostNetLoadFinish);\n          for (size_t i = 0; i < headers_size; ++i) {\n            free(const_cast<char*>(headers_ptr[i]));\n          }\n\n          if (headers_ptr) {\n            delete[] headers_ptr;\n          }\n        });\n  }\n#endif\n}\n\nvoid ViewContext::OnFirstMeaningfulLayout() {\n  page_view_->OnFirstMeaningfulLayout();\n}\n\nvoid ViewContext::UpdateNodeReadyPatching(std::vector<int32_t> ready_ids,\n                                          std::vector<int32_t> remove_ids) {\n  for (auto id : ready_ids) {\n    auto view = FindViewByViewId(id);\n    if (view) {\n      view->OnNodeReady();\n    }\n  }\n}\n\nvoid ViewContext::ConsumeInitialAttributes(BaseView* view) {\n  view->DidUpdateAttributes();\n}\n\nBaseView* ViewContext::FindViewByViewId(int view_id) {\n  BaseView* view = nullptr;\n  if (view_id == -1) {\n    view = page_view_;\n  }\n  if (view == nullptr) {\n    auto itr = view_map_.find(view_id);\n    if (itr != view_map_.end()) {\n      view = itr->second;\n    }\n  }\n  return view;\n}\n\nShadowNode* ViewContext::FindShadowNodeByNodeId(int node_id) {\n  return shadow_node_owner_->GetNode(node_id);\n}\n\nBaseView* ViewContext::FindViewByComponentId(const std::string& component_id) {\n  if (component_id == \"-1\") {\n    return page_view_;\n  }\n  auto itr = component_id_to_ui_id_map_.find(component_id);\n  int sign = 0;\n  if (itr != component_id_to_ui_id_map_.end()) {\n    sign = itr->second;\n  } else {\n    if (!lynx::base::StringToInt(component_id, &sign)) {\n      sign = -1;\n    }\n  }\n\n  return FindViewByViewId(sign);\n}\n\nBaseView* ViewContext::FindViewByIdSelector(std::string_view id_selector,\n                                            BaseView* from) {\n  FML_DCHECK(from);\n\n  if (from->GetIdSelector() == id_selector) {\n    return from;\n  }\n\n  // DFS\n  BaseView* res = nullptr;\n  for (BaseView* child : from->GetChildren()) {\n    res = FindViewByIdSelector(id_selector, child);\n    if (res) {\n      break;\n    }\n  }\n  return res;\n}\n\nBaseView* ViewContext::FindViewByRefIdSelector(std::string_view ref_id_selector,\n                                               BaseView* from) {\n  FML_DCHECK(from);\n\n  if (from->GetRefIdSelector() == ref_id_selector) {\n    return from;\n  }\n\n  // DFS\n  BaseView* res = nullptr;\n  for (BaseView* child : from->GetChildren()) {\n    res = FindViewByRefIdSelector(ref_id_selector, child);\n    if (res) {\n      break;\n    }\n  }\n  return res;\n}\n\nvoid ViewContext::SetFontFace(const char* font_family, const char* src[],\n                              int size) {\n  if (size < 1) {\n    return;\n  }\n\n  std::vector<std::string> src_vec;\n  for (int i = 0; i < size; ++i) {\n    src_vec.emplace_back(src[i]);\n  }\n\n  auto font_collection = Isolate::Instance().GetFontCollection();\n  font_collection->PreLoadFontOnMem(\n      page_view_->GetTaskRunner(), page_view_->GetResourceLoaderIntercept(),\n      page_view_->GetServiceManager(), std::string(font_family),\n      std::move(src_vec));\n}\n\nvoid ViewContext::ShowToast(const char* message, const char* type,\n                            float duration) {\n  // TODO(zhangxiao.ninja): for toast\n}\n\nvoid ViewContext::GetAbsolutePosition(int id, float& top, float& left) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n  auto view = target->second;\n  auto position = view->AbsoluteLocationWithScroll();\n  left = position.x();\n  top = position.y();\n  return;\n}\n\nBaseView* ViewContext::GetViewById(int id) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return nullptr;\n  }\n\n  return target->second;\n}\n\nint ViewContext::GetViewIdForLocation(int x, int y) {\n  return page_view_->GetViewIdForLocation(x, y);\n}\n\nvoid ViewContext::InvokeUIMethod(int view_id, const std::string& method,\n                                 const LynxModuleValues& params,\n                                 const LynxUIMethodCallback& callback) {\n  auto view = FindViewByViewId(view_id);\n  if (view != nullptr) {\n    LynxUIMethodRegistrar::Instance().Invoke(method, view, params, callback);\n  } else {\n    callback(LynxUIMethodResult::kNodeNotFound, clay::Value(\"node not found\"));\n  }\n}\n\nvoid ViewContext::UpdateContentOffsetForListContainer(\n    int id, float content_size, float target_content_offset_x,\n    float target_content_offset_y) {\n  auto it = view_map_.find(id);\n  if (it != view_map_.end()) {\n    auto list_container_view = static_cast<ListContainerWrapper*>(it->second);\n    list_container_view->UpdateContentOffsetForListContainer(\n        content_size, target_content_offset_x, target_content_offset_y);\n  }\n}\n\nvoid ViewContext::UpdateScrollInfo(int id, bool smooth, float estimated_offset,\n                                   bool scrolling) {\n  auto it = view_map_.find(id);\n  if (it != view_map_.end()) {\n    auto list_container_view = static_cast<ListContainerWrapper*>(it->second);\n    list_container_view->UpdateScrollInfo(smooth, estimated_offset, scrolling);\n  }\n}\n\nvoid ViewContext::FinishLayoutOperation(int child_view_id, int parent_view_id) {\n  auto parent = view_map_.find(parent_view_id);\n  auto child = view_map_.find(child_view_id);\n  if (parent != view_map_.end()) {\n    parent->second->OnLayoutFinish(child != view_map_.end() ? child->second\n                                                            : nullptr);\n  }\n}\n\nvoid ViewContext::GetTransformValue(int id,\n                                    const float* pad_border_margin_layout,\n                                    int size, float* res) {\n  // res is std::vector<float>(32 ,0) passed by lynx\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n  BaseView* current_view = target->second;\n  std::vector<float> vec;\n  for (int i = 0; i < size; i++) {\n    vec.push_back(pad_border_margin_layout[i]);\n  }\n  TransOffset arr;\n\n  // Returns the coordinates of the four types of boxes\n  // - border-box:\n  // That is, the width and height of the view set by the front-end, including\n  // content, padding, border, corresponding to the real rendering view\n  // - content-box: border-box with border and padding width and height removed\n  // - padding-box: border-box without the padding width and height of the box\n  // - margin-box: border-box plus margin box\n  for (int i = 0; i < 4; i++) {\n    if (i == 0) {\n      current_view->GetTransformValue(\n          pad_border_margin_layout[BoxModelOffset::PAD_LEFT] +\n              pad_border_margin_layout[BoxModelOffset::BORDER_LEFT] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_LEFT],\n          -pad_border_margin_layout[BoxModelOffset::PAD_RIGHT] -\n              pad_border_margin_layout[BoxModelOffset::BORDER_RIGHT] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_RIGHT],\n          pad_border_margin_layout[BoxModelOffset::PAD_TOP] +\n              pad_border_margin_layout[BoxModelOffset::BORDER_TOP] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_TOP],\n          -pad_border_margin_layout[BoxModelOffset::PAD_BOTTOM] -\n              pad_border_margin_layout[BoxModelOffset::BORDER_BOTTOM] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_BOTTOM],\n          arr);\n    } else if (i == 1) {\n      current_view->GetTransformValue(\n          pad_border_margin_layout[BoxModelOffset::BORDER_LEFT] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_LEFT],\n          -pad_border_margin_layout[BoxModelOffset::BORDER_RIGHT] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_RIGHT],\n          pad_border_margin_layout[BoxModelOffset::BORDER_TOP] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_TOP],\n          -pad_border_margin_layout[BoxModelOffset::BORDER_BOTTOM] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_BOTTOM],\n          arr);\n    } else if (i == 2) {\n      current_view->GetTransformValue(\n          pad_border_margin_layout[BoxModelOffset::LAYOUT_LEFT],\n          -pad_border_margin_layout[BoxModelOffset::LAYOUT_RIGHT],\n          pad_border_margin_layout[BoxModelOffset::LAYOUT_TOP],\n          -pad_border_margin_layout[BoxModelOffset::LAYOUT_BOTTOM], arr);\n    } else {\n      current_view->GetTransformValue(\n          -pad_border_margin_layout[BoxModelOffset::MARGIN_LEFT] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_LEFT],\n          pad_border_margin_layout[BoxModelOffset::MARGIN_RIGHT] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_RIGHT],\n          -pad_border_margin_layout[BoxModelOffset::MARGIN_TOP] +\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_TOP],\n          pad_border_margin_layout[BoxModelOffset::MARGIN_BOTTOM] -\n              pad_border_margin_layout[BoxModelOffset::LAYOUT_BOTTOM],\n          arr);\n    }\n    res[i * 8] = arr.left_top[0];\n    res[i * 8 + 1] = arr.left_top[1];\n    res[i * 8 + 2] = arr.right_top[0];\n    res[i * 8 + 3] = arr.right_top[1];\n    res[i * 8 + 4] = arr.right_bottom[0];\n    res[i * 8 + 5] = arr.right_bottom[1];\n    res[i * 8 + 6] = arr.left_bottom[0];\n    res[i * 8 + 7] = arr.left_bottom[1];\n  }\n}\n\nvoid ViewContext::UpdateSticky(int id, const float* sticky) {\n  auto target = view_map_.find(id);\n  if (target == view_map_.end()) {\n    FML_LOG(ERROR) << \"target view: \" << id << \" not found\";\n    return;\n  }\n  BaseView* view = target->second;\n  if (sticky == nullptr) {\n    view->UpdateSticky(std::nullopt);\n    return;\n  }\n  StickyInfo sticky_info;\n  sticky_info.left = sticky[0];\n  sticky_info.top = sticky[1];\n  sticky_info.right = sticky[2];\n  sticky_info.bottom = sticky[3];\n  sticky_info.offset_x = 0;\n  sticky_info.offset_y = 0;\n  view->UpdateSticky(sticky_info);\n}\n\nBundle* ViewContext::GetTextBundle(int32_t id) {\n  auto node = shadow_node_owner_->GetNode(id);\n  if (node) {\n    return node->MoveBundle();\n  } else {\n    return nullptr;\n  }\n}\n\nvoid ViewContext::UpdateExtraData(int id, Bundle* bundle) {\n  auto view = FindViewByViewId(id);\n  if (view && bundle) {\n    bundle->UpdateExtraData(view);\n  }\n}\n\nfloat ViewContext::GetBaseline(int id) const {\n  auto node = shadow_node_owner_->GetNode(id);\n  if (node && node->IsTextShadowNode()) {\n    return static_cast<TextShadowNode*>(node)->GetBaseline();\n  }\n  return 0.f;\n}\n\nvoid ViewContext::SetDefaultOverflowVisible(bool value) {\n  RenderObject::ChangeDefaultOverflowValue(value);\n}\n\nvoid ViewContext::SetExposureProps(int freq, bool exposure_ui_margin_enabled) {\n  page_view_->SetExposureProps(freq, exposure_ui_margin_enabled);\n}\n\nvoid ViewContext::SetExternalScreenshotCallback(\n    ExternalScreenshotCallback callback) {\n  page_view_->SetExternalScreenshotCallback(std::move(callback));\n}\n\nfml::RefPtr<fml::TaskRunner> ViewContext::GetUITaskRunner() const {\n  return page_view_->GetTaskRunner();\n}\n\nconst clay::TaskRunners& ViewContext::GetTaskRunners() const {\n  return page_view_->GetTaskRunners();\n}\n\nconst std::shared_ptr<ServiceManager>& ViewContext::GetServiceManager() const {\n  return page_view_->GetServiceManager();\n}\n\nvoid ViewContext::SetEventDelegate(EventDelegate* delegate) {\n  if (page_view_) {\n    page_view_->SetEventDelegate(delegate);\n  }\n}\n\nvoid ViewContext::SetUIComponentDelegate(UIComponentDelegate* delegate) {\n  if (page_view_) {\n    page_view_->ui_component_delegate_ = delegate;\n  }\n}\n\nvoid ViewContext::SetLayoutDelegate(LayoutDelegate* delegate) {\n  shadow_node_owner_->SetLayoutDelegate(delegate);\n}\n\nvoid ViewContext::SetPipelineTimingDelegate(\n    std::shared_ptr<PipelineTimingDelegate> delegate) {\n  if (page_view_) {\n    page_view_->SetPipelineTimingDelegate(delegate);\n  }\n}\n\nvoid ViewContext::SetScrollFluencyMonitorDelegate(\n    std::shared_ptr<ScrollFluencyMonitorDelegate> delegate) {\n  if (page_view_) {\n    page_view_->SetScrollFluencyMonitorDelegate(delegate);\n  }\n}\n\nvoid ViewContext::SyncNativeViewTags(std::unordered_set<std::string> tags) {\n  for (const auto& tag : tags) {\n    ViewRegistry::GetInstance()->RegisterView(\n        tag,\n        [tag](int id, PageView* page_view) {\n          return new NativeView(id, tag, page_view);\n        },\n        GetShadowNodeCreator<NativeViewShadowNode>(), true);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/view_context.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_VIEW_CONTEXT_H_\n#define CLAY_UI_COMPONENT_VIEW_CONTEXT_H_\n\n#include <memory>\n#include <optional>\n#include <set>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"clay/common/graphics/screenshot.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/gfx/animation/animation_data.h\"\n#include \"clay/gfx/animation/transition_data.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/pixel_helper.h\"\n#include \"clay/gfx/style/borders_data.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/event_delegate.h\"\n#include \"clay/public/ui_component_delegate.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n#include \"clay/ui/shadow/bundle.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n#include \"clay/ui/window/viewport_metrics.h\"\n\nnamespace clay {\n\nenum BoxModelOffset {\n  PAD_LEFT,\n  PAD_TOP,\n  PAD_RIGHT,\n  PAD_BOTTOM,\n  BORDER_LEFT,\n  BORDER_TOP,\n  BORDER_RIGHT,\n  BORDER_BOTTOM,\n  MARGIN_LEFT,\n  MARGIN_TOP,\n  MARGIN_RIGHT,\n  MARGIN_BOTTOM,\n  LAYOUT_LEFT,\n  LAYOUT_TOP,\n  LAYOUT_RIGHT,\n  LAYOUT_BOTTOM\n};\nstruct TransOffset {\n  std::vector<float> left_top;\n  std::vector<float> right_top;\n  std::vector<float> right_bottom;\n  std::vector<float> left_bottom;\n};\n\nusing NetLoadCallback = std::function<void(\n    const char* url, const char* method, const char* body,\n    const char* headers[], size_t headers_size, size_t request_seq,\n    ClayNetLoadResultCallback result_callback)>;\n\nclass TransformOperations;\nclass PageView;\nstruct BackgroundData;\nclass ServiceManager;\nclass CustomFilterDecoder;\nclass PipelineTimingDelegate;\nclass ScrollFluencyMonitorDelegate;\n\nclass FrameObserver {\n public:\n  virtual void OnBeginFrame() = 0;\n};\n\nclass ViewContext : public std::enable_shared_from_this<ViewContext> {\n public:\n  // The '#' in `id_selector` should not by passed.\n  static BaseView* FindViewByIdSelector(std::string_view id_selector,\n                                        BaseView* root);\n  // Find view by react ref id (prop: `react-ref`).\n  static BaseView* FindViewByRefIdSelector(std::string_view ref_id_selector,\n                                           BaseView* root);\n  ViewContext(PageView* root, ShadowNodeOwner* shadow_node_owner);\n  virtual ~ViewContext();\n\n  void CleanLeakedViews();\n\n  void OnOutputSurfaceDestroyed();\n\n  bool CreateView(int id, const std::string& tag_name);\n\n  int IndexOf(int id) const;\n\n  void AddView(int id, int parent_id, int index);\n\n  void RemoveView(int id, int parent_id, bool is_temporarily_removed = false);\n\n  bool DestroyView(int id);\n\n  void ResetPageView();\n\n  ShadowNode* CreateShadowNode(int id, const std::string& tag_name,\n                               bool is_parent_inline_container);\n\n  int32_t GetTagInfo(const std::string& tag_name);\n\n  void AddShadowNode(int id, int parent_id, int index);\n\n  void RemoveShadowNode(int id);\n\n  void InsertListItemPaintingNode(int list_id, int child_id);\n\n  void RemoveListItemPaintingNode(int list_id, int child_id);\n\n  bool DestroyShadowNode(int id);\n\n  void DestroyAllShadowNode();\n\n  void NotifyLowMemory();\n\n  void OnLayout(int id, float width, TextMeasureMode width_mode, float height,\n                TextMeasureMode height_mode,\n                const std::array<float, 4>& paddings,\n                const std::array<float, 4>& borders);\n\n  void UpdateRootSize(int32_t width, int32_t height);\n\n  void SetShadowNodeAttribute(int id, const char* attr,\n                              const clay::Value& value);\n\n  void ScheduleLayout();\n\n  void Alignment(int id);\n\n  // Anonymous view is views which created by internal, and its id is\n  // invalid (-1). For anonymous view self, just destroy and delete it.\n  // But there maybe some views created by outside (by RKCreateView),\n  // for those views who has valid id, call `DestroyView` so that\n  // id in view_map_ can be erased.\n  void DestroyAnonymousView(BaseView* view);\n\n  void Invalidate();\n\n  void SetCustomFilterDecoder(std::unique_ptr<CustomFilterDecoder> decoder) {\n    custom_filter_decoder_ = std::move(decoder);\n  }\n\n  CustomFilterDecoder* GetCustomFilterDecoder() {\n    if (custom_filter_decoder_) {\n      return custom_filter_decoder_.get();\n    }\n    return nullptr;\n  }\n\n  void SetBounds(int id, float left, float top, float width, float height);\n\n  void SetPaddings(int id, float padding_left, float padding_top,\n                   float padding_right, float padding_bottom);\n\n  void SetMargins(int id, float margin_left, float margin_top,\n                  float margin_right, float margin_bottom);\n\n  void SetOpacity(int id, float opacity);\n\n  void SetOverflow(int id, int overflow);\n\n  void SetBorderStyle(int id, BorderStyleType left, BorderStyleType top,\n                      BorderStyleType right, BorderStyleType bottom);\n\n  void SetBorderWidth(int id, int left_width, int top_width, int right_width,\n                      int bottom_width);\n\n  void SetBorderColor(int id, unsigned int left_color, unsigned int top_color,\n                      unsigned int right_color, unsigned int bottom_color);\n\n  void SetBorderRadius(int id, const FloatSize& left_top,\n                       const FloatSize& right_top,\n                       const FloatSize& right_bottom,\n                       const FloatSize& left_bottom);\n\n  void SetOutlineStyle(int id, BorderStyleType style);\n\n  void SetOutlineWidth(int id, int width);\n\n  void SetOutlineColor(int id, unsigned int color);\n\n  void SetCursor(int id, const char* src[], int size);\n\n  void SetBackgroundColor(int id, unsigned int color);\n\n  void SetBackground(int id, const BackgroundData& background);\n\n  void AppendShadow(int id, const Shadow& shadow);\n\n  void SetAttribute(int id, const char* attr, const clay::Value& value);\n  const clay::ViewportMetrics& GetViewportMetrics() const;\n\n  void DidUpdateAttributes(int id);\n\n  void AddEventProp(int id, const char* event);\n\n  void AddShadowNodeEventProp(int id, const char* event);\n\n  void SetTransform(int id, const TransformOperations& ops,\n                    const FloatPoint& origin);\n\n  void SetTransition(int id,\n                     const std::vector<TransitionData>& transition_data);\n\n  void SetAnimation(int id, const std::vector<AnimationData>& animation_data);\n\n  void SetKeyframes(const clay::Value& keyframes_value);\n\n  void SetRepaintBoundary(int id, bool repaint_boundary);\n\n  void TextMeasure(int id, float width, TextMeasureMode width_mode,\n                   float height, TextMeasureMode height_mode, float& out_width,\n                   float& out_height);\n\n  void SetFontFace(const char* font_family, const char* src[], int size);\n  void SetNetLoadCallback(NetLoadCallback net_load_callback);\n\n  void GetTransformValue(int id, const float* pad_border_margin_layout,\n                         int size, float* res);\n\n  void OnFirstMeaningfulLayout();\n\n  void UpdateNodeReadyPatching(std::vector<int32_t> ready_ids,\n                               std::vector<int32_t> remove_ids);\n\n  fml::RefPtr<fml::TaskRunner> GetUITaskRunner() const;\n  const clay::TaskRunners& GetTaskRunners() const;\n  const std::shared_ptr<ServiceManager>& GetServiceManager() const;\n\n  PageView* GetPageView() const { return page_view_; }\n  BaseView* FindViewByViewId(int view_id);\n  ShadowNode* FindShadowNodeByNodeId(int node_id);\n  BaseView* FindViewByComponentId(const std::string& component_id);\n  void ShowToast(const char* message, const char* type, float duration);\n  void GetAbsolutePosition(int id, float& top, float& left);\n  int GetViewIdForLocation(int x, int y);\n\n  void InvokeUIMethod(int view_id, const std::string& method,\n                      const LynxModuleValues& params,\n                      const LynxUIMethodCallback& callback);\n\n  uint32_t unique_id() { return unique_id_; }\n\n  BaseView* GetViewById(int id);\n\n  void UpdateSticky(int id, const float* sticky);\n\n  float GetBaseline(int id) const;\n\n  void SetDefaultOverflowVisible(bool value);\n  void SetExposureProps(int freq, bool exposure_ui_margin_enabled);\n  void SetEventDelegate(EventDelegate* delegate);\n  void SetUIComponentDelegate(UIComponentDelegate* delegate);\n  void SetLayoutDelegate(LayoutDelegate* delegate);\n\n  void SetPipelineTimingDelegate(\n      std::shared_ptr<PipelineTimingDelegate> delegate);\n  void SetScrollFluencyMonitorDelegate(\n      std::shared_ptr<ScrollFluencyMonitorDelegate> delegate);\n\n  void SetExternalScreenshotCallback(ExternalScreenshotCallback callback);\n\n  // Lynx list containers.\n  void UpdateContentOffsetForListContainer(int id, float content_size,\n                                           float target_content_offset_x,\n                                           float target_content_offset_y);\n  void UpdateScrollInfo(int id, bool smooth, float estimated_offset,\n                        bool scrolling);\n  void FinishLayoutOperation(int child_view_id, int parent_view_id);\n  void SetDefaultOverflowAlwaysVisible(bool visible) {\n    default_overflow_always_visible = visible;\n  }\n\n  bool DefaultOverflowAlwaysVisible() {\n    return default_overflow_always_visible;\n  }\n\n  Bundle* GetTextBundle(int32_t id);\n  void UpdateExtraData(int id, Bundle* bundle);\n\n  bool UsesLogicalPixels() const {\n    return clay::kPixelTypeFramework == clay::kPixelTypeLogical;\n  }\n\n  // Sync native view tags. should be called before view tree created.\n  // tag in this set will be created as native view.\n  // these tags has a higher priority than builtin tags.\n  void SyncNativeViewTags(std::unordered_set<std::string> tags);\n\n  std::weak_ptr<ViewContext> GetWeakPtr() { return shared_from_this(); }\n\n  void SetFrameObserver(FrameObserver* observer) { frame_observer_ = observer; }\n\n  FrameObserver* GetFrameObserver() const { return frame_observer_; }\n\n protected:\n  // FIXME(Xietong):\n  // In Lynx, the initial properties are passed during view creation. That's\n  // missing for now.\n  void ConsumeInitialAttributes(BaseView* view);\n\n  bool default_overflow_always_visible = true;\n\n  PageView* page_view_;\n\n  std::unordered_map<int, BaseView*> view_map_;\n  std::set<std::string> events_;\n  uint32_t unique_id_ = 0;\n  ShadowNodeOwner* shadow_node_owner_;\n\n  // component_id_to_ui_id_map_ is used to map radon component id to element id.\n  // In method invokeUIMethod, we need to use this map and radon(js) component\n  // id to find related views.\n  std::unordered_map<std::string, int> component_id_to_ui_id_map_;\n\n  fml::WeakPtrFactory<ViewContext> weak_factory_;\n  std::unique_ptr<CustomFilterDecoder> custom_filter_decoder_;\n  FrameObserver* frame_observer_ = nullptr;\n};\n\nclass CustomFilterDecoder {\n public:\n  virtual ~CustomFilterDecoder() = default;\n  virtual bool Decode(const clay::Value::Array& array, BaseView* base_view) = 0;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_VIEW_CONTEXT_H_\n"
  },
  {
    "path": "clay/ui/component/view_registry.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/view_registry.h\"\n\n#include \"clay/ui/component/builtin_views.h\"\n#include \"clay/ui/component/native_view.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\nViewRegistry* ViewRegistry::GetInstance() {\n  static ViewRegistry* instance = new ViewRegistry();\n  return instance;\n}\n\nViewRegistry::ViewRegistry() {\n  keepBuiltinElements();\n#if (defined(OS_OSX) || defined(OS_IOS))\n  for (auto p = __start_clayview; p != __stop_clayview; ++p) {\n    this->RegisterView(std::string(p->name), p->view_creator,\n                       p->shadow_node_creator);\n  }\n#endif\n}\n\nvoid ViewRegistry::RegisterView(const std::string& tag,\n                                ViewCreator view_creator,\n                                ShadowNodeCreator shadow_node_creator,\n                                bool is_native_view) {\n  registry_[tag] = {view_creator, shadow_node_creator, is_native_view};\n}\n\nBaseView* ViewRegistry::CreateView(int32_t id, const std::string& tag_name,\n                                   PageView* page_view) {\n  BaseView* view = nullptr;\n\n  auto itr = registry_.find(tag_name);\n  if (itr != registry_.end()) {\n    view = itr->second.view_creator(id, page_view);\n  }\n\n  if (UNLIKELY(view && itr->second.is_native_view &&\n               !static_cast<NativeView*>(view)->IsNativeViewAvailable())) {\n    // Create native view failed. We will destroy this view so that we can\n    // avoid costly performance overhead in NativeView without many if\n    // statements.\n    FML_DLOG(ERROR) << \"Create native view fail(tag:\" << tag_name << \")\";\n    delete view;\n    view = nullptr;\n  }\n  if (!view) {\n    FML_DLOG(ERROR) << \" unsupported view type: \" << tag_name;\n  }\n  return view;\n}\n\nShadowNode* ViewRegistry::CreateShadowNode(int32_t id, ShadowNodeOwner* owner,\n                                           const std::string& tag_name) {\n  auto itr = registry_.find(tag_name);\n  if (itr != registry_.end() && itr->second.shadow_node_creator) {\n    return itr->second.shadow_node_creator(id, owner, tag_name);\n  }\n  FML_DLOG(ERROR) << \" unsupported shadownode type: \" << tag_name;\n  return nullptr;\n}\n\nint32_t ViewRegistry::GetTagInfo(const std::string& tag_name) {\n  const int32_t kTagInfoCustom = 1 << 2;\n  // result can also store is_virtual information when it is needed.\n  int32_t result = 0;\n  auto itr = registry_.find(tag_name);\n  if (itr != registry_.end() && itr->second.shadow_node_creator) {\n    result |= kTagInfoCustom;\n  }\n  return result;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/view_registry.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_VIEW_REGISTRY_H_\n#define CLAY_UI_COMPONENT_VIEW_REGISTRY_H_\n\n#include <functional>\n#include <string>\n#include <unordered_map>\n\n#include \"build/build_config.h\"\n\n#ifdef CLAY_ELEMENTS_HEADER\n#define TO_STR_(x) #x\n#define TO_STR(x) TO_STR_(x)\n#include TO_STR(CLAY_ELEMENTS_HEADER)\n#undef TO_STR_\n#undef TO_STR\n#endif\n\nnamespace clay {\n\n#if defined(OS_ANDROID)\n#define KEEP_SYMBOL __attribute__((used))\n#elif defined(__clang__) || defined(__GNUC__)\n#define KEEP_SYMBOL __attribute__((used, retain))\n#else\n#define KEEP_SYMBOL __attribute__((used))\n#endif\n\n#define CONCAT2_(A, B) A##B\n#define CONCAT2(A, B) CONCAT2_(A, B)\n#define UNIQUE_NAME(base) CONCAT2(base, __COUNTER__)\n\n#define REGISTER_VIEW_TAG(TAG, VIEW, SHADOW_NODE)                             \\\n  KEEP_SYMBOL inline static const bool UNIQUE_NAME(VIEW##_registered_) = [] { \\\n    ViewRegistry::GetInstance()->RegisterView(                                \\\n        TAG, GetViewCreator<VIEW>(), GetShadowNodeCreator<SHADOW_NODE>());    \\\n    return true;                                                              \\\n  }();\n\nclass BaseView;\nclass ShadowNode;\nclass PageView;\nclass ShadowNodeOwner;\n\nusing ViewCreator = std::function<BaseView*(int id, PageView* page_view)>;\nusing ShadowNodeCreator = std::function<ShadowNode*(\n    int id, ShadowNodeOwner* owner, const std::string& tag_name)>;\n\ntemplate <typename T>\ninline ViewCreator GetViewCreator() {\n  return [](int id, PageView* page_view) { return new T(id, page_view); };\n}\n\ntemplate <typename T>\ninline ShadowNodeCreator GetShadowNodeCreator() {\n  return [](int id, ShadowNodeOwner* owner, const std::string& tag_name) {\n    return new T(owner, tag_name, id);\n  };\n}\n\ntemplate <>\ninline ShadowNodeCreator GetShadowNodeCreator<void>() {\n  return nullptr;\n}\n\nstruct ViewRegistryDescription {\n  std::string_view name;\n  ViewCreator view_creator;\n  ShadowNodeCreator shadow_node_creator;\n};\n\n#if (defined(OS_OSX) || defined(OS_IOS))\n\n// section name for Mac and iOS\n#define CLAYVIEW_SEC_STR \"__DATA,__clayview\"\n\n// section start and end ptr\nextern \"C\" const ViewRegistryDescription __start_clayview[] __asm(\n    \"section$start$__DATA$__clayview\");\nextern \"C\" const ViewRegistryDescription __stop_clayview[] __asm(\n    \"section$end$__DATA$__clayview\");\n\n// regist to secitons\n#define REGISTER_CLAY_ELEMENT(TAG, VIEW, SHADOW_NODE)         \\\n  extern \"C\" __attribute__((used, section(CLAYVIEW_SEC_STR))) \\\n  const ViewRegistryDescription UNIQUE_NAME(VIEW##_desc){     \\\n      TAG, GetViewCreator<VIEW>(), GetShadowNodeCreator<SHADOW_NODE>()};\n#else\n#define REGISTER_CLAY_ELEMENT REGISTER_VIEW_TAG\n#endif\n\nclass ViewRegistry {\n public:\n  struct Entry {\n    ViewCreator view_creator;\n    ShadowNodeCreator shadow_node_creator;\n    bool is_native_view;\n  };\n\n  static ViewRegistry* GetInstance();\n\n  // NOTE: Use macro `REGISTER_VIEW_TAG` to register views.\n  void RegisterView(const std::string& tag, ViewCreator view_creator,\n                    ShadowNodeCreator shadow_node_creator = nullptr,\n                    bool is_native_view = false);\n\n  BaseView* CreateView(int32_t id, const std::string& tag_name,\n                       PageView* page_view);\n\n  ShadowNode* CreateShadowNode(int32_t id, ShadowNodeOwner* owner,\n                               const std::string& tag_name);\n\n  int32_t GetTagInfo(const std::string& tag_name);\n\n private:\n  ViewRegistry();\n\n  std::unordered_map<std::string, Entry> registry_;\n#ifdef CLAY_ELEMENTS_REF_FUNC_DEFINES\n  CLAY_ELEMENTS_REF_FUNC_DEFINES\n#endif\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPONENT_VIEW_REGISTRY_H_\n"
  },
  {
    "path": "clay/ui/component/view_tree_observer.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/view_tree_observer.h\"\n\n#include <algorithm>\n\nnamespace clay {\n\nvoid ViewTreeObserver::AddOnPaintingListener(OnPaintingListener* listener) {\n  if (!listener) {\n    return;\n  }\n  if (std::find(on_painting_listeners_.begin(), on_painting_listeners_.end(),\n                listener) != on_painting_listeners_.end()) {\n    // Already exists, do not add duplicate.\n    return;\n  }\n  on_painting_listeners_.push_back(listener);\n}\n\nvoid ViewTreeObserver::RemoveOnPaintingListener(OnPaintingListener* listener) {\n  if (listener) {\n    on_painting_listeners_.remove(listener);\n  }\n}\n\nvoid ViewTreeObserver::DispatchOnPainting() {\n  if (on_painting_listeners_.empty()) {\n    return;\n  }\n  // Use a temporary list to avoid the list being modified in the loop.\n  std::list<OnPaintingListener*> listeners = on_painting_listeners_;\n  for (auto& listener : on_painting_listeners_) {\n    listener->OnPainting();\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/component/view_tree_observer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPONENT_VIEW_TREE_OBSERVER_H_\n#define CLAY_UI_COMPONENT_VIEW_TREE_OBSERVER_H_\n\n#include <list>\n\nnamespace clay {\n\n// Interface definition for a callback to be invoked when the view tree is about\n// to be painting.\nclass OnPaintingListener {\n public:\n  virtual ~OnPaintingListener() = default;\n  virtual void OnPainting() = 0;\n};\n\n// Refer to android, A view tree observer is used to register\n// listeners that can be notified of global changes in the view tree. Such\n// global events now only define the painting event.\n// We can define more global events in the future, such as layout events, etc.\nclass ViewTreeObserver {\n public:\n  ViewTreeObserver() = default;\n  ~ViewTreeObserver() = default;\n\n  // Add a listener to be invoked when the view tree about to be painting.\n  void AddOnPaintingListener(OnPaintingListener* listener);\n  void RemoveOnPaintingListener(OnPaintingListener* listener);\n  // Notifies registered listeners that the view tree is about to be painting.\n  void DispatchOnPainting();\n\n private:\n  std::list<OnPaintingListener*> on_painting_listeners_;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_COMPONENT_VIEW_TREE_OBSERVER_H_\n"
  },
  {
    "path": "clay/ui/compositing/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nui_compositing_sources = [\n  \"frame_builder.cc\",\n  \"frame_builder.h\",\n  \"pending_container_layer.cc\",\n  \"pending_container_layer.h\",\n  \"pending_drawable_image_layer.cc\",\n  \"pending_drawable_image_layer.h\",\n  \"pending_effect_layer.cc\",\n  \"pending_effect_layer.h\",\n  \"pending_external_view_layer.cc\",\n  \"pending_external_view_layer.h\",\n  \"pending_layer.cc\",\n  \"pending_layer.h\",\n  \"pending_offset_layer.cc\",\n  \"pending_offset_layer.h\",\n  \"pending_picture_layer.cc\",\n  \"pending_picture_layer.h\",\n  \"pending_platform_view_layer.cc\",\n  \"pending_platform_view_layer.h\",\n  \"pending_punch_hole_layer.cc\",\n  \"pending_punch_hole_layer.h\",\n  \"pending_scroll_offset_layer.cc\",\n  \"pending_scroll_offset_layer.h\",\n  \"pending_transform_layer.cc\",\n  \"pending_transform_layer.h\",\n]\n"
  },
  {
    "path": "clay/ui/compositing/frame_builder.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/frame_builder.h\"\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"clay/common/element_id.h\"\n#include \"clay/flow/animation/animation_host.h\"\n#include \"clay/flow/animation/animation_mutator.h\"\n#include \"clay/flow/layers/backdrop_filter_layer.h\"\n#include \"clay/flow/layers/clip_path_layer.h\"\n#include \"clay/flow/layers/clip_rect_layer.h\"\n#include \"clay/flow/layers/clip_rrect_layer.h\"\n#include \"clay/flow/layers/color_filter_layer.h\"\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/flow/layers/drawable_image_layer.h\"\n#include \"clay/flow/layers/external_view_layer.h\"\n#include \"clay/flow/layers/image_filter_layer.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/layers/opacity_layer.h\"\n#include \"clay/flow/layers/performance_overlay_layer.h\"\n#include \"clay/flow/layers/picture_layer.h\"\n#include \"clay/flow/layers/platform_view_layer.h\"\n#include \"clay/flow/layers/punch_hole_layer.h\"\n#include \"clay/flow/layers/shader_mask_layer.h\"\n#include \"clay/flow/layers/transform_layer.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/picture.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/ui/compositing/pending_layer.h\"\n#include \"clay/ui/compositing/pending_picture_layer.h\"\n#include \"clay/ui/rendering/render_object.h\"\n#include \"skity/geometry/vector.hpp\"\n\nnamespace clay {\nnamespace {\n\nvoid InitializeAnimationMutator(RenderObject* owner,\n                                clay::AnimationMutator* mutator,\n                                ClayAnimationPropertyType type) {\n  // For Keyframe animation has a higher priority than Transition animation\n  // in the CSS style rules.\n  if (owner->HasAnimation(type)) {\n    FML_DCHECK(owner->GetKeyframesManager() != nullptr);\n    if (auto manager = owner->GetKeyframesManager()->CloneForRasterAnimation(\n            type, mutator)) {\n      manager->SetEventHandler(mutator);\n      mutator->AddKeyframesManager(std::move(manager));\n    }\n  } else if (owner->HasTransition(type)) {\n    FML_DCHECK(owner->GetTransitionManager() != nullptr);\n    if (auto manager = owner->GetTransitionManager()->CloneForRasterAnimation(\n            type, mutator)) {\n      manager->SetEventHandler(mutator);\n      mutator->AddTransitionManager(std::move(manager));\n    }\n  }\n}\n\n}  // namespace\n\nFrameBuilder::FrameBuilder(const skity::Vec2& frame_size,\n                           float device_pixel_ratio,\n                           fml::RefPtr<GPUUnrefQueue> unref_queue)\n    : frame_size_(frame_size),\n      device_pixel_ratio_(device_pixel_ratio),\n      unref_queue_(std::move(unref_queue)) {\n  // Add a ContainerLayer as the root layer, so that AddLayer operations are\n  // always valid.\n  PushLayer(std::make_shared<clay::ContainerLayer>());\n}\n\nFrameBuilder::~FrameBuilder() = default;\n\nvoid FrameBuilder::Reset() {\n  // Clear the composited layer stack.\n  layer_stack_.clear();\n  // Add a TransformLayer as the root layer again.\n  PushLayer(std::make_shared<clay::TransformLayer>());\n}\n\nvoid FrameBuilder::UpdateFrameSize(const skity::Vec2& size,\n                                   float device_pixel_ratio) {\n  if (size == frame_size_ && device_pixel_ratio == device_pixel_ratio_) {\n    return;\n  }\n  // TODO(jinsong): frame size and device pixel ratio has been changed, need to\n  // rebuild frame.\n  layer_tree_.reset();\n  frame_size_ = size;\n  device_pixel_ratio_ = device_pixel_ratio;\n}\n\nstd::unique_ptr<Picture> FrameBuilder::GeneratePicture(\n    const skity::Rect& bounds) {\n  return nullptr;\n}\n\nvoid FrameBuilder::BuildFrame(PendingLayer* root_layer) {\n  if (!root_layer) {\n    layer_tree_.reset();\n    return;\n  }\n  FML_DCHECK(!root_layer->Parent());\n  animation_host_ = std::make_shared<clay::AnimationHost>();\n  root_layer->UpdateSubtreeNeedsAddToFrame();\n  root_layer->AddToFrame(this);\n  root_layer->need_add_to_frame_ = false;\n  FinishBuild();\n}\n\nvoid FrameBuilder::BuildSubtreeFrame(PendingLayer* root_layer) {\n  // The root layer may be a sub layer of the current page.\n  // Use this function to avoid dcheck failure.\n  if (!root_layer) {\n    layer_tree_.reset();\n    return;\n  }\n  animation_host_ = std::make_shared<clay::AnimationHost>();\n  root_layer->UpdateSubtreeNeedsAddToFrame();\n  root_layer->AddToFrame(this);\n  root_layer->need_add_to_frame_ = false;\n  FinishBuild();\n}\n\nvoid FrameBuilder::FinishBuild() {\n  if (frame_size_.x == 0 && frame_size_.y == 0) {\n    layer_tree_.reset();\n    return;\n  }\n  // The layer tree has been constructed and is ready to be submitted to\n  // Engine.\n  layer_tree_ =\n      std::make_unique<clay::LayerTree>(frame_size_, device_pixel_ratio_);\n  layer_tree_->set_root_layer(RootLayer());\n  layer_tree_->SetAnimationHost(std::move(animation_host_));\n}\n\nvoid FrameBuilder::PushTransformOperations(const TransformOperations& transform,\n                                           double origin_x, double origin_y,\n                                           double offset_x, double offset_y,\n                                           PendingLayer* old_layer) {\n  auto layer = std::make_shared<clay::TransformLayer>(\n      transform, skity::Vec2(origin_x, origin_y),\n      skity::Vec2(offset_x, offset_y));\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n\n  PendingLayer* owned_layer = FindOwnedLayer(old_layer);\n  if (owned_layer && owned_layer->owner_->HasRasterAnimation()) {\n    RenderObject* owner = owned_layer->owner_;\n    auto mutator = clay::AnimationMutator::Create(\n        owner->element_id(), clay::AnimationMutatorType::kTransform,\n        layer.get());\n    InitializeAnimationMutator(owner, mutator.get(),\n                               ClayAnimationPropertyType::kTransform);\n    animation_host_->AddAnimationMutator(layer.get(), std::move(mutator));\n  }\n}\n\nvoid FrameBuilder::PushStaticTransform(skity::Matrix transform,\n                                       PendingLayer* old_layer) {\n  auto layer = std::make_shared<clay::TransformLayer>(transform);\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n}\n\nvoid FrameBuilder::PushOffset(double dx, double dy, PendingLayer* old_layer) {\n  skity::Matrix sk_matrix = skity::Matrix::Translate(dx, dy);\n  auto layer = std::make_shared<clay::TransformLayer>(sk_matrix);\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n}\n\nvoid FrameBuilder::PushScrollOffset(\n    double x, double y, double scroll_x, double scroll_y,\n    const FloatRect& offset_range, const FloatRect& max_offset_range,\n    std::shared_ptr<clay::ScrollOffsetAnimation> animation,\n    PendingLayer* old_layer) {\n  skity::Matrix sk_matrix =\n      skity::Matrix::Translate(x + scroll_x, y + scroll_y);\n  auto layer = std::make_shared<clay::TransformLayer>(sk_matrix);\n  PushLayer(layer);\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n\n  PendingLayer* owned_layer = FindOwnedLayer(old_layer);\n  if (owned_layer) {\n    RenderObject* owner = owned_layer->owner_;\n    auto mutator = clay::AnimationMutator::Create(\n        owner->element_id(), clay::AnimationMutatorType::kScrollOffset,\n        layer.get());\n    mutator->asScrollOffset()->Initialize(skity::Vec2{x, y},\n                                          skity::Vec2{scroll_x, scroll_y},\n                                          offset_range, max_offset_range);\n    // Bind ScrollOffsetAnimation.\n    mutator->SetScrollOffsetAnimation(std::move(animation));\n    animation_host_->AddAnimationMutator(layer.get(), std::move(mutator));\n  }\n}\n\nPendingLayer* FrameBuilder::FindOwnedLayer(PendingLayer* layer) const {\n  while (layer) {\n    if (layer->owner_) {\n      return layer;\n    }\n    layer = static_cast<PendingLayer*>(layer->Parent());\n  }\n  return nullptr;\n}\n\nvoid FrameBuilder::PushOpacity(int alpha, double dx, double dy,\n                               PendingLayer* old_layer) {\n  auto layer = std::make_shared<clay::OpacityLayer>(alpha, skity::Vec2(dx, dy));\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n\n  PendingLayer* owned_layer = FindOwnedLayer(old_layer);\n  if (owned_layer && owned_layer->owner_->HasRasterAnimation()) {\n    RenderObject* owner = owned_layer->owner_;\n    auto mutator = clay::AnimationMutator::Create(\n        owner->element_id(), clay::AnimationMutatorType::kOpacity, layer.get());\n    InitializeAnimationMutator(owner, mutator.get(),\n                               ClayAnimationPropertyType::kOpacity);\n    animation_host_->AddAnimationMutator(layer.get(), std::move(mutator));\n  }\n}\n\nvoid FrameBuilder::PushColorFilter(std::shared_ptr<ColorFilter> color_filter,\n                                   PendingLayer* old_layer) {\n  auto layer = std::make_shared<clay::ColorFilterLayer>(color_filter);\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n}\n\nvoid FrameBuilder::PushImageFilter(std::shared_ptr<ImageFilter> image_filter,\n                                   PendingLayer* old_layer) {\n  auto layer = std::make_shared<clay::ImageFilterLayer>(image_filter);\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n}\n\nvoid FrameBuilder::PushShaderMask(std::shared_ptr<ColorSource> color_source,\n                                  const FloatRect& mask_rect,\n                                  BlendMode blend_mode,\n                                  PendingLayer* old_layer) {\n  auto layer = std::make_shared<clay::ShaderMaskLayer>(color_source, mask_rect,\n                                                       blend_mode);\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n}\n\nvoid FrameBuilder::PushBackdropFilter(std::shared_ptr<ImageFilter> filter,\n                                      PendingLayer* old_layer) {\n  auto layer =\n      std::make_shared<clay::BackdropFilterLayer>(filter, BlendMode::kSrcOver);\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n}\n\nvoid FrameBuilder::PushClipRect(const FloatRect& clip_rect, int clip_behavior,\n                                PendingLayer* old_layer) {\n  clay::Clip behavior = static_cast<clay::Clip>(clip_behavior);\n  auto layer = std::make_shared<clay::ClipRectLayer>(clip_rect, behavior);\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n}\n\nvoid FrameBuilder::PushClipRRect(const FloatRoundedRect& clip_rrect,\n                                 int clip_behavior, PendingLayer* old_layer) {\n  clay::Clip behavior = static_cast<clay::Clip>(clip_behavior);\n  auto layer = std::make_shared<clay::ClipRRectLayer>(clip_rrect, behavior);\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n}\n\nvoid FrameBuilder::PushClipPath(const GrPath& path, int clip_behavior,\n                                PendingLayer* old_layer) {\n  clay::Clip behavior = static_cast<clay::Clip>(clip_behavior);\n  auto layer = std::make_shared<clay::ClipPathLayer>(path, behavior);\n  PushLayer(layer);\n\n  if (old_layer->ReuseLayer()) {\n    layer->AssignOldLayer(old_layer->ReuseLayer().get());\n  }\n  old_layer->RetainLayer(layer);\n}\n\nvoid FrameBuilder::Pop() { PopLayer(); }\n\nvoid FrameBuilder::AddPicture(double dx, double dy,\n                              PendingPictureLayer* picture_layer,\n                              bool complex_hint, bool change_hint,\n                              CacheStrategy strategy) {\n  FML_DCHECK(picture_layer->picture());\n  auto picture = picture_layer->picture()->picture();\n  if (picture) {\n    auto layer = std::make_unique<clay::PictureLayer>(\n        skity::Vec2(dx, dy), CreateGPUObject(picture), complex_hint,\n        change_hint, strategy, picture_layer->picture()->HasLazyImage());\n    if (!picture->GetDynamicOps().empty()) {\n      FML_DCHECK(picture_layer->Parent());\n      auto* container_layer =\n          static_cast<PendingLayer*>(picture_layer->Parent());\n      FML_DCHECK(container_layer->Owner());\n      // PictureLayer does not have an owner, but its parent which should be a\n      // `ContainerLayer` is supposed to have one.\n      RenderObject* owner = container_layer->Owner();\n      auto mutator = clay::AnimationMutator::Create(\n          owner->element_id(), clay::AnimationMutatorType::kPicture,\n          layer.get());\n      InitializeAnimationMutator(owner, mutator.get(),\n                                 ClayAnimationPropertyType::kBackgroundColor);\n      InitializeAnimationMutator(owner, mutator.get(),\n                                 ClayAnimationPropertyType::kColor);\n      animation_host_->AddAnimationMutator(layer.get(), std::move(mutator));\n    }\n    AddLayer(std::move(layer));\n  }\n}\n\nvoid FrameBuilder::AddDrawableImage(double dx, double dy, double width,\n                                    double height, int64_t image_id,\n                                    clay::DrawableImage::FitMode fit_mode) {\n  auto layer = std::make_unique<clay::DrawableImageLayer>(\n      skity::Vec2(dx, dy), skity::Vec2(width, height), image_id, false,\n      ImageSampling::kLinear, fit_mode);\n  AddLayer(std::move(layer));\n}\n\nvoid FrameBuilder::AddPlatformView(double dx, double dy, double width,\n                                   double height, int64_t view_id) {\n  auto layer = std::make_unique<clay::PlatformViewLayer>(\n      skity::Vec2(dx, dy), skity::Vec2(width, height), view_id);\n  AddLayer(std::move(layer));\n}\n\nvoid FrameBuilder::AddPunchHole(const skity::Rect& rect) {\n  auto layer = std::make_unique<clay::PunchHoleLayer>(rect);\n  AddLayer(std::move(layer));\n}\n\nvoid FrameBuilder::AddPerformanceOverlay(uint64_t enable_options, double left,\n                                         double right, double top,\n                                         double bottom) {\n  skity::Rect rect = skity::Rect::MakeLTRB(left, top, right, bottom);\n  auto layer = std::make_unique<clay::PerformanceOverlayLayer>(enable_options);\n  layer->set_paint_bounds(rect);\n  AddLayer(std::move(layer));\n}\n\nstd::shared_ptr<clay::Layer> FrameBuilder::RootLayer() const {\n  FML_DCHECK(layer_stack_.size() >= 1);\n\n  return layer_stack_[0];\n}\n\nstd::unique_ptr<clay::LayerTree> FrameBuilder::TakeLayerTree() {\n  return std::move(layer_tree_);\n}\n\nvoid FrameBuilder::AddLayer(std::shared_ptr<clay::Layer> layer) {\n  FML_DCHECK(layer);\n\n  if (!layer_stack_.empty()) {\n    layer_stack_.back()->Add(std::move(layer));\n  }\n}\n\nvoid FrameBuilder::PushLayer(std::shared_ptr<clay::ContainerLayer> layer) {\n  AddLayer(layer);\n  layer_stack_.push_back(std::move(layer));\n}\n\nvoid FrameBuilder::PopLayer() {\n  // We never pop the root layer, so that AddLayer operations are always valid.\n  if (layer_stack_.size() > 1) {\n    layer_stack_.pop_back();\n  }\n}\n\nvoid FrameBuilder::AddRetained(std::shared_ptr<clay::Layer> engine_layer) {\n  AddLayer(engine_layer);\n  CopyRasterAnimationsFromRetained(engine_layer);\n}\n\nvoid FrameBuilder::CopyRasterAnimationsFromRetained(\n    const std::shared_ptr<clay::Layer>& layer) {\n  if (layer->HasAnimation()) {\n    animation_host_->AddRetainedLayerId(layer->unique_id());\n  }\n  if (layer->IsContainer()) {\n    for (auto& child :\n         std::static_pointer_cast<clay::ContainerLayer>(layer)->layers()) {\n      CopyRasterAnimationsFromRetained(child);\n    }\n  }\n}\n\nvoid FrameBuilder::PushExternalViewLayer(const ElementId& element_id,\n                                         const skity::Vec2& size) {\n  auto layer = std::make_unique<clay::ExternalViewLayer>(element_id, size);\n  PushLayer(std::move(layer));\n}\n\n#ifndef NDEBUG\nvoid FrameBuilder::DumpLayerTree() const {\n  FML_LOG(ERROR) << \"frame_size_=(\" << frame_size_.x << \",\" << frame_size_.y\n                 << \") \"\n                 << \"device_pixel_ratio_=\" << device_pixel_ratio_;\n  if (layer_tree_.get() && layer_tree_->root_layer()) {\n    layer_tree_->root_layer()->DebugDumpTree(0);\n  } else if (layer_stack_.size() > 0) {\n    layer_stack_[0]->DebugDumpTree(0);\n  }\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/frame_builder.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_FRAME_BUILDER_H_\n#define CLAY_UI_COMPOSITING_FRAME_BUILDER_H_\n\n#include <cstdint>\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/flow/raster_cache.h\"\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/picture.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/color_filter.h\"\n#include \"clay/gfx/style/image_filter.h\"\n\nnamespace clay {\nclass AnimationHost;\nclass ContainerLayer;\nclass Layer;\nclass LayerTree;\n}  // namespace clay\n\nnamespace clay {\n\nclass PendingLayer;\nclass PendingPictureLayer;\n\nusing CacheStrategy = clay::CacheStrategy;\n\nclass FrameBuilder {\n public:\n  FrameBuilder(const skity::Vec2& frame_size, float device_pixel_ratio,\n               fml::RefPtr<GPUUnrefQueue> unref_queue);\n  ~FrameBuilder();\n\n  FrameBuilder(const FrameBuilder&) = delete;\n  FrameBuilder& operator=(const FrameBuilder&) = delete;\n\n  void UpdateFrameSize(const skity::Vec2& size, float device_pixel_ratio);\n  void UpdateFrameSize(uint32_t physical_width, uint32_t physical_height,\n                       float device_pixel_ratio) {\n    UpdateFrameSize({physical_width, physical_height}, device_pixel_ratio);\n  }\n\n  std::unique_ptr<Picture> GeneratePicture(const skity::Rect& bounds);\n\n  void PushTransformOperations(const TransformOperations& transform,\n                               double origin_x, double origin_y,\n                               double offset_x, double offset_y,\n                               PendingLayer* old_layer);\n  void PushStaticTransform(skity::Matrix transform, PendingLayer* old_layer);\n  void PushOffset(double dx, double dy, PendingLayer* old_layer);\n  void PushScrollOffset(double x, double y, double scroll_x, double scroll_y,\n                        const FloatRect& offset_range,\n                        const FloatRect& max_offset_range,\n                        std::shared_ptr<clay::ScrollOffsetAnimation> animation,\n                        PendingLayer* old_layer);\n  void PushOpacity(int alpha, double dx, double dy, PendingLayer* old_layer);\n  void PushColorFilter(std::shared_ptr<ColorFilter> color_filter,\n                       PendingLayer* old_layer);\n  void PushImageFilter(std::shared_ptr<ImageFilter> image_filter,\n                       PendingLayer* old_layer);\n  void PushShaderMask(std::shared_ptr<ColorSource> color_source,\n                      const FloatRect& mask_rect, BlendMode blend_mode,\n                      PendingLayer* old_layer);\n  void PushBackdropFilter(std::shared_ptr<ImageFilter>,\n                          PendingLayer* old_layer);\n  void PushClipRect(const FloatRect& clip_rect, int clip_behavior,\n                    PendingLayer* old_layer);\n  void PushClipRRect(const FloatRoundedRect& clip_rrect, int clip_behavior,\n                     PendingLayer* old_layer);\n  void PushClipPath(const GrPath& path, int clip_behavior,\n                    PendingLayer* old_layer);\n  void Pop();\n\n  void AddPicture(double dx, double dy, PendingPictureLayer* picture_layer,\n                  bool complex_hint, bool change_hint,\n                  CacheStrategy strategy = CacheStrategy::None);\n\n  void AddDrawableImage(double dx, double dy, double width, double height,\n                        int64_t image_id,\n                        clay::DrawableImage::FitMode fit_mode);\n\n  void AddPlatformView(double dx, double dy, double width, double height,\n                       int64_t view_id);\n\n  void AddPunchHole(const skity::Rect& rect);\n\n  void PushExternalViewLayer(const ElementId& element_id,\n                             const skity::Vec2& size);\n\n  void AddPerformanceOverlay(uint64_t enable_options, double left, double right,\n                             double top, double bottom);\n\n  std::shared_ptr<clay::Layer> RootLayer() const;\n  std::unique_ptr<clay::LayerTree> TakeLayerTree();\n\n  template <class T>\n  GPUObject<T> CreateGPUObject(fml::RefPtr<T> object) {\n    if (!object) {\n      return {};\n    }\n    return {std::move(object), unref_queue_};\n  }\n\n  // Build a frame (a tree of layers) in the engine.\n  void BuildFrame(PendingLayer* root_layer);\n\n  // Build a frame (a tree of layers) in the engine.\n  void BuildSubtreeFrame(PendingLayer* root_layer);\n\n  // Reset |FrameBuilder| for reuse.\n  void Reset();\n\n  void AddRetained(std::shared_ptr<clay::Layer> engine_layer);\n\n#ifndef NDEBUG\n  void DumpLayerTree() const;\n#endif\n\n private:\n  // Friend tests that need direct access to private methods.\n  friend class FrameBuilderTest;\n\n  void AddLayer(std::shared_ptr<clay::Layer> layer);\n  void PushLayer(std::shared_ptr<clay::ContainerLayer> layer);\n  void PopLayer();\n\n  // Finish building the layer tree.\n  void FinishBuild();\n\n  PendingLayer* FindOwnedLayer(PendingLayer* layer) const;\n  void CopyRasterAnimationsFromRetained(\n      const std::shared_ptr<clay::Layer>& layer);\n\n  // Physical pixels.\n  skity::Vec2 frame_size_ = {0, 0};\n  // Logical / Physical pixels ratio.\n  float device_pixel_ratio_ = 1.0f;\n  std::unique_ptr<clay::LayerTree> layer_tree_;\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n  std::vector<std::shared_ptr<clay::ContainerLayer>> layer_stack_;\n  std::shared_ptr<clay::AnimationHost> animation_host_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_FRAME_BUILDER_H_\n"
  },
  {
    "path": "clay/ui/compositing/frame_builder_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstddef>\n#include <memory>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/flow/layers/layer.h\"\n#include \"clay/flow/layers/layer_tree.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/picture.h\"\n#include \"clay/testing/thread_test.h\"\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/compositing/pending_picture_layer.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkRect.h\"\n\nnamespace clay {\nnamespace {\n\nstatic sk_sp<SkPicture> MakeSizedPicture(int width, int height) {\n  SkPictureRecorder recorder;\n  SkCanvas* canvas = recorder.beginRecording(SkRect::MakeIWH(width, height));\n  canvas->drawRect(SkRect::MakeXYWH(0, 0, width, height),\n                   SkPaint(SkColor4f::FromColor(SK_ColorRED)));\n  return recorder.finishRecordingAsPicture();\n}\n\n}  // namespace\n\nclass FrameBuilderTest : public clay::testing::ThreadTest {\n public:\n  FrameBuilderTest()\n      : queue_(fml::MakeRefCounted<GPUUnrefQueue>(GetCurrentTaskRunner())),\n        frame_builder_(\n            std::make_unique<FrameBuilder>(skity::Vec2{0, 0}, 1.0f, queue_)) {}\n  ~FrameBuilderTest() {\n    frame_builder_ = nullptr;\n    queue_->Drain();\n  }\n\n  std::shared_ptr<clay::Layer> RootLayer() const {\n    return frame_builder_->RootLayer();\n  }\n  std::unique_ptr<clay::LayerTree> TakeLayerTree() {\n    return frame_builder_->TakeLayerTree();\n  }\n  void FinishBuild() { frame_builder_->FinishBuild(); }\n\n  FrameBuilder* GetFrameBuilder() { return frame_builder_.get(); }\n\n  std::unique_ptr<Picture> CreateNeutralPicture(sk_sp<SkPicture> picture) {\n    clay::DynamicOps ops;\n    auto picture_skia =\n        fml::MakeRefCounted<clay::PictureSkia>(picture, std::move(ops));\n    return std::make_unique<Picture>(\n        GPUObject<clay::PictureSkia>(picture_skia, queue_));\n  }\n\n private:\n  fml::RefPtr<GPUUnrefQueue> queue_;\n  std::unique_ptr<FrameBuilder> frame_builder_;\n};\n\nTEST_F(FrameBuilderTest, Simple) {\n  EXPECT_TRUE(GetFrameBuilder()->RootLayer());\n  EXPECT_FALSE(GetFrameBuilder()->TakeLayerTree());\n}\n\nTEST_F(FrameBuilderTest, BuildFrame) {\n  GetFrameBuilder()->UpdateFrameSize(100, 100, 1.0f);\n\n  // Construct a picture layer.\n  std::unique_ptr<Picture> picture =\n      CreateNeutralPicture(MakeSizedPicture(100, 100));\n  std::unique_ptr<PendingPictureLayer> layer =\n      std::make_unique<PendingPictureLayer>();\n  layer->set_picture(std::move(picture));\n  GetFrameBuilder()->AddPicture(0, 0, layer.get(), false, false);\n  EXPECT_FALSE(GetFrameBuilder()->TakeLayerTree());\n\n  FinishBuild();\n  EXPECT_TRUE(GetFrameBuilder()->TakeLayerTree());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_container_layer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_container_layer.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/rendering/render_object.h\"\n\nnamespace clay {\n\nPendingContainerLayer::PendingContainerLayer() = default;\n\nPendingContainerLayer::~PendingContainerLayer() { RemoveAllChildren(); }\n\nvoid PendingContainerLayer::AppendChild(PendingLayer* child) {\n  FML_DCHECK(child != this);\n  FML_DCHECK(child != first_child_);\n  FML_DCHECK(child != last_child_);\n  FML_DCHECK(child->Parent() == nullptr);\n  FML_DCHECK(child->NextSibling() == nullptr);\n  FML_DCHECK(child->PreviousSibling() == nullptr);\n\n  AddChild(child);\n\n  // Add child to the children list.\n  child->SetPreviousSibling(last_child_);\n  if (last_child_) {\n    last_child_->SetNextSibling(child);\n  }\n\n  last_child_ = child;\n\n  if (!first_child_) {\n    first_child_ = child;\n  }\n}\n\nvoid PendingContainerLayer::RemoveChild(PendingLayer* child) {\n  FML_DCHECK(child->Parent() == this);\n\n  if (!child->PreviousSibling()) {\n    FML_DCHECK(child == first_child_);\n    first_child_ = child->NextSibling();\n  } else {\n    child->PreviousSibling()->SetNextSibling(child->NextSibling());\n  }\n\n  if (!child->NextSibling()) {\n    FML_DCHECK(last_child_ == child);\n    last_child_ = child->PreviousSibling();\n  } else {\n    child->NextSibling()->SetPreviousSibling(child->PreviousSibling());\n  }\n  child->SetPreviousSibling(nullptr);\n  child->SetNextSibling(nullptr);\n\n  PendingLayer::RemoveChild(child);\n\n  // The owner[RenderObject] is responsible for deleting the subtree. If the\n  // owner is not dirty, then we can reuse this subtree or contents.\n  if (!child->Attached()) {\n    delete child;\n    child = nullptr;\n  }\n}\n\nvoid PendingContainerLayer::RemoveAllChildren() {\n  PendingLayer* child = first_child_;\n  while (child) {\n    PendingLayer* next = child->NextSibling();\n    child->SetPreviousSibling(nullptr);\n    child->SetNextSibling(nullptr);\n\n    PendingLayer::RemoveChild(child);\n\n    if (!child->Attached()) {\n      delete child;\n    }\n    child = next;\n  }\n  first_child_ = nullptr;\n  last_child_ = nullptr;\n}\n\nvoid PendingContainerLayer::AddToFrame(FrameBuilder* builder,\n                                       const FloatPoint& offset) {\n  AddChildrenToFrame(builder, offset);\n}\n\nvoid PendingContainerLayer::AddChildrenToFrame(FrameBuilder* builder,\n                                               const FloatPoint& offset) {\n  PendingLayer* child = first_child_;\n  while (child) {\n    if (!child->need_add_to_frame_ && child->ReuseLayer()) {\n      builder->AddRetained(child->ReuseLayer());\n    } else {\n      child->AddToFrame(builder, offset);\n      child->need_add_to_frame_ = false;\n    }\n\n    child = child->NextSibling();\n  }\n}\n\nvoid PendingContainerLayer::UpdateSubtreeNeedsAddToFrame() {\n  PendingLayer::UpdateSubtreeNeedsAddToFrame();\n  PendingLayer* child = first_child_;\n  while (child) {\n    child->UpdateSubtreeNeedsAddToFrame();\n    need_add_to_frame_ = need_add_to_frame_ || child->need_add_to_frame_;\n\n    child = child->NextSibling();\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_container_layer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_CONTAINER_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_CONTAINER_LAYER_H_\n\n#include <string>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/compositing/pending_layer.h\"\n\nnamespace clay {\n\n// A pending container layer that has a list of children.\n//\n// A [PendingContainerLayer] instance merely takes a list of children and\n// inserts them into the composited rendering in order. There are subclasses of\n// [PendingContainerLayer] which apply more elaborate effects in the process.\nclass PendingContainerLayer : public PendingLayer {\n public:\n  PendingContainerLayer();\n  ~PendingContainerLayer() override;\n\n  std::string GetName() const override { return \"PendingContainerLayer\"; }\n\n  PendingLayer* FirstChild() const override { return first_child_; }\n  PendingLayer* LastChild() const override { return last_child_; }\n\n  void SetFirstChild(PendingLayer* child) { first_child_ = child; }\n  void SetLastChild(PendingLayer* child) { last_child_ = child; }\n\n  // Adds the given layer to the end of this layer's child list.\n  void AppendChild(PendingLayer* child);\n  void RemoveChild(PendingLayer* child) override;\n  void RemoveAllChildren();\n\n  bool HasChildren() const { return first_child_; }\n\n  void UpdateSubtreeNeedsAddToFrame() override;\n\n protected:\n  void AddToFrame(FrameBuilder* builder, const FloatPoint& offset) override;\n  // Uploads all of this layer's children to the engine.\n  void AddChildrenToFrame(FrameBuilder* builder,\n                          const FloatPoint& offset = FloatPoint());\n\n private:\n  PendingLayer* first_child_ = nullptr;\n  PendingLayer* last_child_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_CONTAINER_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_container_layer_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_container_layer.h\"\n#include \"clay/ui/compositing/testing/mock_pending_layer.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(PendingContainerLayerTest, Simple) {\n  // Empty layer tree.\n  auto root_layer = std::make_unique<PendingContainerLayer>();\n\n  EXPECT_FALSE(root_layer->HasChildren());\n\n  // Add a child.\n  auto child_layer = new MockPendingLayer();\n  EXPECT_EQ(child_layer->Parent(), nullptr);\n\n  root_layer->AppendChild(child_layer);\n  EXPECT_TRUE(root_layer->HasChildren());\n  EXPECT_EQ(root_layer->FirstChild(), root_layer->LastChild());\n  EXPECT_EQ(child_layer->Parent(), root_layer.get());\n\n  // Remove a child.\n  root_layer->RemoveChild(child_layer);\n  EXPECT_FALSE(root_layer->HasChildren());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_drawable_image_layer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_drawable_image_layer.h\"\n\n#include \"clay/ui/compositing/frame_builder.h\"\n\nnamespace clay {\n\nPendingDrawableImageLayer::PendingDrawableImageLayer(\n    float dx, float dy, float width, float height, int64_t image_id,\n    clay::DrawableImage::FitMode fit_mode)\n    : dx_(dx),\n      dy_(dy),\n      width_(width),\n      height_(height),\n      image_id_(image_id),\n      fit_mode_(fit_mode) {}\n\nPendingDrawableImageLayer::~PendingDrawableImageLayer() = default;\n\nvoid PendingDrawableImageLayer::AddToFrame(FrameBuilder* builder,\n                                           const FloatPoint& offset) {\n  builder->AddDrawableImage(offset.x() + dx_, offset.y() + dy_, width_, height_,\n                            image_id_, fit_mode_);\n}\n\n#ifndef NDEBUG\nstd::string PendingDrawableImageLayer::ToString() const {\n  std::stringstream ss;\n  ss << PendingLayer::ToString();\n  ss << \" image_id_=\" << image_id_;\n  ss << \" texture_offset=(\" << dx_ << \",\" << dy_ << \")\";\n  ss << \" texture_size=(\" << width_ << \",\" << height_ << \")\";\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_drawable_image_layer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_DRAWABLE_IMAGE_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_DRAWABLE_IMAGE_LAYER_H_\n\n#include <string>\n\n#include \"clay/ui/compositing/pending_layer.h\"\n\nnamespace clay {\n\nclass PendingDrawableImageLayer : public PendingLayer {\n public:\n  PendingDrawableImageLayer(float dx, float dy, float width, float height,\n                            int64_t image_id,\n                            clay::DrawableImage::FitMode fit_mode);\n  ~PendingDrawableImageLayer() override;\n\n  std::string GetName() const override { return \"PendingDrawableImageLayer\"; }\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  void AddToFrame(FrameBuilder* builder, const FloatPoint& offset) override;\n\n  float dx_ = 0.f;\n  float dy_ = 0.f;\n  float width_ = 0.f;\n  float height_ = 0.f;\n  int64_t image_id_;\n  clay::DrawableImage::FitMode fit_mode_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_DRAWABLE_IMAGE_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_effect_layer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_effect_layer.h\"\n\n#include \"clay/gfx/geometry/float_size.h\"\n\nnamespace clay {\n\nPendingEffectLayer::PendingEffectLayer() = default;\n\nPendingEffectLayer::~PendingEffectLayer() = default;\n\nvoid PendingEffectLayer::AddToFrame(FrameBuilder* builder,\n                                    const FloatPoint& offset) {\n  // Apply effects to the subtree.\n  if (opacity_.has_value()) {\n    builder->PushOpacity(opacity_.value(), offset_.x() + offset.x(),\n                         offset_.y() + offset.y(), this);\n    AddChildrenToFrame(builder);\n    builder->Pop();\n  } else if (color_filter_) {\n    builder->PushColorFilter(color_filter_, this);\n    AddChildrenToFrame(builder);\n    builder->Pop();\n  } else if (image_filter_) {\n    builder->PushImageFilter(image_filter_, this);\n    AddChildrenToFrame(builder);\n    builder->Pop();\n  } else if (backdrop_filter_) {\n    builder->PushBackdropFilter(backdrop_filter_, this);\n    AddChildrenToFrame(builder);\n    builder->Pop();\n  } else if (clip_rect_.has_value()) {\n    FloatRect clip_rect = clip_rect_.value();\n    clip_rect.Move(offset.x(), offset.y());\n    int hard_edge = 1;  // Hard edge as default.\n    builder->PushClipRect(clip_rect, hard_edge, this);\n    AddChildrenToFrame(builder);\n    builder->Pop();\n  } else if (clip_rrect_.has_value()) {\n    FloatRoundedRect clip_rrect = clip_rrect_.value();\n    clip_rrect.Move(FloatSize(offset.x(), offset.y()));\n    int anti_alias = 2;  // Anti Alias as default.\n    builder->PushClipRRect(clip_rrect, anti_alias, this);\n    AddChildrenToFrame(builder);\n    builder->Pop();\n  } else if (clip_path_.has_value()) {\n    GrPath clip_path = clip_path_.value();\n    int anti_alias = 2;\n    builder->PushClipPath(clip_path, anti_alias, this);\n    AddChildrenToFrame(builder);\n    builder->Pop();\n  } else if (color_source_) {\n    FloatRect mask_rect = mask_rect_.value();\n    mask_rect.Move(offset.x(), offset.y());\n    builder->PushShaderMask(color_source_, mask_rect, blend_mode_, this);\n    AddChildrenToFrame(builder);\n    builder->Pop();\n  }\n}\n\n#ifndef NDEBUG\nstd::string PendingEffectLayer::ToString() const {\n  std::stringstream ss;\n  ss << PendingLayer::ToString();\n  ss << \" effect_offset_=\" << offset_.ToString();\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_effect_layer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_EFFECT_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_EFFECT_LAYER_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n#include \"clay/gfx/geometry/path.h\"\n#include \"clay/gfx/style/color_filter.h\"\n#include \"clay/gfx/style/color_source.h\"\n#include \"clay/gfx/style/image_filter.h\"\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/compositing/pending_container_layer.h\"\n\nnamespace clay {\n\nenum class EffectType {\n  kOpacity = 0,\n  kColorFilter,\n  kImageFilter,\n  kBackdropFilter,\n  kShaderMask,\n\n  kLast = kShaderMask,\n};\n\n// A pending effect layer that applies an effect [Opacity, Filter ...] to its\n// children.\nclass PendingEffectLayer : public PendingContainerLayer {\n public:\n  PendingEffectLayer();\n  ~PendingEffectLayer() override;\n\n  std::string GetName() const override { return \"PendingEffectLayer\"; }\n\n  void SetOffset(const FloatPoint& offset) {\n    if (offset != offset_) {\n      offset_ = offset;\n      MarkNeedsAddToFrame();\n    }\n  }\n  const FloatPoint& GetOffset() const { return offset_; }\n\n  void SetOpacity(int opacity) {\n    if (!opacity_.has_value() || *opacity_ != opacity) {\n      opacity_ = opacity;\n      MarkNeedsAddToFrame();\n    }\n  }\n  int GetOpacity() const { return *opacity_; }\n\n  void SetColorFilter(std::shared_ptr<ColorFilter> color_filter) {\n    color_filter_ = color_filter;\n    MarkNeedsAddToFrame();\n  }\n\n  void SetImageFilter(std::shared_ptr<ImageFilter> image_filter) {\n    image_filter_ = image_filter;\n    MarkNeedsAddToFrame();\n  }\n\n  void SetBackdropFilter(std::shared_ptr<ImageFilter> backdrop_filter) {\n    backdrop_filter_ = backdrop_filter;\n    MarkNeedsAddToFrame();\n  }\n\n  void SetClipRect(const FloatRect& clip_rect) {\n    clip_rect_ = clip_rect;\n    MarkNeedsAddToFrame();\n  }\n\n  void SetClipRRect(const FloatRoundedRect& clip_rrect) {\n    clip_rrect_ = clip_rrect;\n    MarkNeedsAddToFrame();\n  }\n\n  void SetClipPath(const GrPath& path) {\n    clip_path_ = path;\n    MarkNeedsAddToFrame();\n  }\n\n  void SetShaderMask(std::shared_ptr<ColorSource> color_source,\n                     const FloatRect& mask_rect, BlendMode blend_mode) {\n    color_source_ = color_source;\n    blend_mode_ = blend_mode;\n    mask_rect_ = mask_rect;\n    MarkNeedsAddToFrame();\n  }\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  void AddToFrame(FrameBuilder* builder, const FloatPoint& offset) override;\n\n  // Offset from parent in the parent's coordinate system.\n  FloatPoint offset_;\n  // For opacity effect.\n  std::optional<int> opacity_ = std::nullopt;\n\n  // For shader mask\n  std::shared_ptr<ColorSource> color_source_;\n  std::optional<FloatRect> mask_rect_;\n  BlendMode blend_mode_ = BlendMode::kClear;\n\n  std::shared_ptr<ColorFilter> color_filter_;\n  std::shared_ptr<ImageFilter> image_filter_;\n  std::shared_ptr<ImageFilter> backdrop_filter_;\n  std::optional<FloatRect> clip_rect_ = std::nullopt;\n  std::optional<FloatRoundedRect> clip_rrect_ = std::nullopt;\n  std::optional<GrPath> clip_path_ = std::nullopt;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_EFFECT_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_external_view_layer.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_external_view_layer.h\"\n\nnamespace clay {\n\nPendingExternalViewLayer::PendingExternalViewLayer(const ElementId &element_id,\n                                                   const skity::Vec2 &size)\n    : element_id_(element_id), size_(size) {}\nvoid PendingExternalViewLayer::AddToFrame(FrameBuilder *builder,\n                                          const FloatPoint &offset) {\n  builder->PushExternalViewLayer(element_id_, size_);\n  AddChildrenToFrame(builder, offset);\n  builder->Pop();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_external_view_layer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_EXTERNAL_VIEW_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_EXTERNAL_VIEW_LAYER_H_\n\n#include <string>\n\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/compositing/pending_container_layer.h\"\n\nnamespace clay {\n\nclass PendingExternalViewLayer : public PendingContainerLayer {\n public:\n  PendingExternalViewLayer(const ElementId &element_id,\n                           const skity::Vec2 &size);\n  std::string GetName() const override { return \"PendingExternalViewLayer\"; }\n\n protected:\n  void AddToFrame(FrameBuilder *builder, const FloatPoint &offset) override;\n\n private:\n  ElementId element_id_;\n  skity::Vec2 size_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_EXTERNAL_VIEW_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_layer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"clay/ui/compositing/pending_layer.h\"\n\n#include <sstream>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/rendering/render_object.h\"\n\nnamespace clay {\n\nPendingLayer::PendingLayer() = default;\n\nPendingLayer::~PendingLayer() = default;\n\nvoid PendingLayer::AddChild(PendingLayer* child) {\n  MarkNeedsAddToFrame();\n  AdoptChild(child);\n}\n\nvoid PendingLayer::RemoveChild(PendingLayer* child) {\n  MarkNeedsAddToFrame();\n  DropChild(child);\n}\n\nvoid PendingLayer::Attach(RenderObject* owner) {\n  FML_DCHECK(!owner_);\n  owner_ = owner;\n}\n\nvoid PendingLayer::Detach() {\n  FML_DCHECK(owner_);\n  Remove();\n  owner_->ResetLayer();\n  owner_ = nullptr;\n}\n\nvoid PendingLayer::RedepthChildren() {\n  PendingLayer* child = FirstChild();\n  while (child) {\n    RedepthChild(child);\n    child = child->NextSibling();\n  }\n}\n\nvoid PendingLayer::SetCacheStrategyRecursively(CacheStrategy strategy) {\n  strategy_ = strategy;\n  auto child_layer = FirstChild();\n  while (child_layer) {\n    child_layer->SetCacheStrategyRecursively(strategy);\n    child_layer = child_layer->NextSibling();\n  }\n}\n\nvoid PendingLayer::UpdateSubtreeNeedsAddToFrame() {}\n\nvoid PendingLayer::MarkNeedsAddToFrame() { need_add_to_frame_ = true; }\n\n#ifndef NDEBUG\nvoid PendingLayer::DumpPendingLayerTree() const {\n  std::string intent;\n  intent.append(2 * Depth(), ' ');\n  FML_LOG(ERROR) << intent << \"[\" << GetName() << \"] \" << this << ToString();\n\n  PendingLayer* child = FirstChild();\n  while (child) {\n    child->DumpPendingLayerTree();\n    child = child->NextSibling();\n  }\n}\n\nstd::string PendingLayer::ToString() const {\n  std::stringstream ss;\n  if (Attached()) {\n    ss << \" Owner=\" << owner_->GetName() << \"(\" << owner_\n       << \" Owner id=\" << owner_->ID() << \")\";\n  }\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_layer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_LAYER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/flow/layers/container_layer.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/rendering/abstract_node.h\"\n\nnamespace clay {\n\nclass RenderObject;\nusing CacheStrategy = clay::CacheStrategy;\n\n// A pending layer is a collection of paint ops that will end up in the same\n// engine layer.\n// During painting, the render tree generates a tree of composited layers that\n// are uploaded into the engine and displayed by the compositor.\n// This class is the base class for all composited layers.\nclass PendingLayer : public AbstractNode {\n public:\n  PendingLayer();\n  virtual ~PendingLayer();\n\n  virtual std::string GetName() const { return \"PendingLayer\"; }\n\n  virtual PendingLayer* FirstChild() const { return nullptr; }\n  virtual PendingLayer* LastChild() const { return nullptr; }\n\n  PendingLayer* PreviousSibling() const { return previous_; }\n  PendingLayer* NextSibling() const { return next_; }\n\n  virtual void AddChild(PendingLayer* child);\n  virtual void RemoveChild(PendingLayer* child);\n\n  void Remove() {\n    if (Parent()) static_cast<PendingLayer*>(Parent())->RemoveChild(this);\n  }\n\n  void SetPreviousSibling(PendingLayer* previous) { previous_ = previous; }\n  void SetNextSibling(PendingLayer* next) { next_ = next; }\n\n  bool Attached() const { return owner_ != nullptr; }\n  void Attach(RenderObject* owner);\n  void Detach();\n\n  RenderObject* Owner() const { return owner_; }\n\n  void RetainLayer(std::shared_ptr<clay::ContainerLayer> layer) {\n    engine_layer_ = layer;\n    PendingLayer* parent = static_cast<PendingLayer*>(Parent());\n    if (parent) {\n      parent->MarkNeedsAddToFrame();\n    }\n  }\n\n  std::shared_ptr<clay::ContainerLayer> ReuseLayer() const {\n    return engine_layer_;\n  }\n\n  // Upload this pending layer to the engine.\n  virtual void AddToFrame(FrameBuilder* builder,\n                          const FloatPoint& offset = FloatPoint()) = 0;\n\n  void SetCacheStrategy(CacheStrategy strategy) { strategy_ = strategy; }\n  void SetCacheStrategyRecursively(CacheStrategy strategy);\n  CacheStrategy GetCacheStrategy() const { return strategy_; }\n\n  virtual void UpdateSubtreeNeedsAddToFrame();\n\n  void MarkNeedsAddToFrame();\n\n#ifndef NDEBUG\n  void DumpPendingLayerTree() const;\n  virtual std::string ToString() const;\n#endif\n\n protected:\n  CacheStrategy strategy_ = CacheStrategy::None;\n\n private:\n  void RedepthChildren() override;\n\n  // The owner_ owns this pending layer.\n  RenderObject* owner_ = nullptr;\n  // This layer's previous sibling in the parent layer's child list.\n  PendingLayer* previous_ = nullptr;\n  // This layer's next sibling in the parent layer's child list.\n  PendingLayer* next_ = nullptr;\n\n  // For reuse.\n  std::shared_ptr<clay::ContainerLayer> engine_layer_;\n\n  bool need_add_to_frame_ = true;\n\n  friend class PendingContainerLayer;\n  friend class FrameBuilder;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_layer_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_layer.h\"\n#include \"clay/ui/compositing/testing/mock_pending_layer.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nTEST(LayerTest, Simple) {\n  auto layer = std::make_unique<MockPendingLayer>();\n\n  EXPECT_FALSE(layer->PreviousSibling());\n  EXPECT_FALSE(layer->NextSibling());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_offset_layer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_offset_layer.h\"\n\nnamespace clay {\n\nPendingOffsetLayer::PendingOffsetLayer(const FloatPoint& offset)\n    : offset_(offset) {}\n\nPendingOffsetLayer::~PendingOffsetLayer() = default;\n\nvoid PendingOffsetLayer::AddToFrame(FrameBuilder* builder,\n                                    const FloatPoint& offset) {\n  builder->PushOffset(offset.x() + offset_.x(), offset.y() + offset_.y(), this);\n  AddChildrenToFrame(builder);\n  builder->Pop();\n}\n\n#ifndef NDEBUG\nstd::string PendingOffsetLayer::ToString() const {\n  std::stringstream ss;\n  ss << PendingContainerLayer::ToString() << \" \" << offset_.ToString();\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_offset_layer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_OFFSET_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_OFFSET_LAYER_H_\n\n#include <string>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/compositing/pending_container_layer.h\"\n\nnamespace clay {\n\nclass PendingOffsetLayer : public PendingContainerLayer {\n public:\n  explicit PendingOffsetLayer(const FloatPoint& offset = FloatPoint());\n  ~PendingOffsetLayer() override;\n\n  std::string GetName() const override { return \"PendingOffsetLayer\"; }\n\n  const FloatPoint& Offset() const { return offset_; }\n  void SetOffset(const FloatPoint& offset) {\n    if (offset_ != offset) {\n      offset_ = offset;\n      MarkNeedsAddToFrame();\n    }\n  }\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  void AddToFrame(FrameBuilder* builder, const FloatPoint& offset) override;\n\n  FloatPoint offset_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_OFFSET_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_picture_layer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_picture_layer.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/image/image_resource.h\"\n\nnamespace clay {\n\nPendingPictureLayer::PendingPictureLayer() = default;\n\nPendingPictureLayer::~PendingPictureLayer() = default;\n\nvoid PendingPictureLayer::AddToFrame(FrameBuilder* builder,\n                                     const FloatPoint& offset) {\n  if (!picture()) {\n    return;\n  }\n  builder->AddPicture(offset.x(), offset.y(), this, IsComplexHint(),\n                      WillChangeHint(), strategy_);\n  if (strategy_ == CacheStrategy::NotCache) {\n    // CacheStrategy::NotCache only used for one frame.\n    strategy_ = CacheStrategy::None;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_picture_layer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_PICTURE_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_PICTURE_LAYER_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/picture.h\"\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/compositing/pending_layer.h\"\n\nnamespace clay {\n\nclass ImageResource;\n\n// A pending picture layer contains a [Picture].\n//\n// Picture layers are always leaves in the layer tree.\nclass PendingPictureLayer : public PendingLayer {\n public:\n  PendingPictureLayer();\n  ~PendingPictureLayer() override;\n\n  std::string GetName() const override { return \"PendingPictureLayer\"; }\n\n  bool IsComplexHint() const { return is_complex_hint_; }\n  void SetIsComplexHint() {\n    is_complex_hint_ = true;\n    MarkNeedsAddToFrame();\n  }\n\n  bool WillChangeHint() const { return will_change_hint_; }\n  void SetWillChangeHint() {\n    will_change_hint_ = true;\n    MarkNeedsAddToFrame();\n  }\n\n  void set_picture(std::unique_ptr<Picture> picture) {\n    picture_ = std::move(picture);\n    MarkNeedsAddToFrame();\n  }\n  const Picture* picture() const { return picture_.get(); }\n\n private:\n  void AddToFrame(FrameBuilder* builder, const FloatPoint& offset) override;\n\n  // Hints that the painting in this layer is complex and would benefit from\n  // caching.\n  bool is_complex_hint_ = false;\n  // Hints that the painting in this layer is likely to change next frame.\n  bool will_change_hint_ = false;\n  std::unique_ptr<Picture> picture_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_PICTURE_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_platform_view_layer.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_platform_view_layer.h\"\n\n#include \"clay/ui/compositing/frame_builder.h\"\n\nnamespace clay {\n\nPendingPlatformViewLayer::PendingPlatformViewLayer(float dx, float dy,\n                                                   float width, float height,\n                                                   int64_t view_id)\n    : dx_(dx), dy_(dy), width_(width), height_(height), view_id_(view_id) {}\n\nPendingPlatformViewLayer::~PendingPlatformViewLayer() = default;\n\nvoid PendingPlatformViewLayer::AddToFrame(FrameBuilder* builder,\n                                          const FloatPoint& offset) {\n  builder->AddPlatformView(offset.x() + dx_, offset.y() + dy_, width_, height_,\n                           view_id_);\n}\n\n#ifndef NDEBUG\nstd::string PendingPlatformViewLayer::ToString() const {\n  std::stringstream ss;\n  ss << PendingLayer::ToString();\n  ss << \" platform_view_id=\" << view_id_;\n  ss << \" platform_view_offset=(\" << dx_ << \",\" << dy_ << \")\";\n  ss << \" platform_view_size=(\" << width_ << \",\" << height_ << \")\";\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_platform_view_layer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_PLATFORM_VIEW_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_PLATFORM_VIEW_LAYER_H_\n\n#include <string>\n\n#include \"clay/ui/compositing/pending_layer.h\"\n\nnamespace clay {\n\nclass PendingPlatformViewLayer : public PendingLayer {\n public:\n  PendingPlatformViewLayer(float dx, float dy, float width, float height,\n                           int64_t view_id);\n  ~PendingPlatformViewLayer() override;\n\n  std::string GetName() const override { return \"PendingPlatformViewLayer\"; }\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  void AddToFrame(FrameBuilder* builder, const FloatPoint& offset) override;\n\n  float dx_ = 0.f;\n  float dy_ = 0.f;\n  float width_ = 0.f;\n  float height_ = 0.f;\n  int64_t view_id_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_PLATFORM_VIEW_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_punch_hole_layer.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_punch_hole_layer.h\"\n\nnamespace clay {\n\nPendingPunchHoleLayer::PendingPunchHoleLayer(const skity::Rect& rect)\n    : punch_hole_rect_(rect) {}\n\nPendingPunchHoleLayer::~PendingPunchHoleLayer() = default;\n\nvoid PendingPunchHoleLayer::AddToFrame(FrameBuilder* builder,\n                                       const FloatPoint& offset) {\n  builder->AddPunchHole(punch_hole_rect_);\n}\n\n#ifndef NDEBUG\nstd::string PendingPunchHoleLayer::ToString() const {\n  std::stringstream ss;\n  ss << PendingLayer::ToString();\n  ss << \" punch_hole_rect=(\" << punch_hole_rect_.X() << \", \"\n     << punch_hole_rect_.Y() << \", \" << punch_hole_rect_.Width() << \", \"\n     << punch_hole_rect_.Height() << \")\";\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_punch_hole_layer.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_PUNCH_HOLE_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_PUNCH_HOLE_LAYER_H_\n\n#include <string>\n\n#include \"clay/ui/compositing/pending_layer.h\"\n\nnamespace clay {\n\nclass PendingPunchHoleLayer : public PendingLayer {\n public:\n  explicit PendingPunchHoleLayer(const skity::Rect& rect);\n  ~PendingPunchHoleLayer() override;\n\n  std::string GetName() const override { return \"PendingPunchHoleLayer\"; }\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  void AddToFrame(FrameBuilder* builder,\n                  const FloatPoint& offset = FloatPoint()) override;\n\n  skity::Rect punch_hole_rect_;\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_PUNCH_HOLE_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_scroll_offset_layer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_scroll_offset_layer.h\"\n\n#include <utility>\n\nnamespace clay {\n\nPendingScrollOffsetLayer::PendingScrollOffsetLayer(\n    const FloatPoint& offset, const FloatPoint& scroll_offset,\n    const FloatRect& visible_offset_range, const FloatRect& max_offset_range,\n    std::shared_ptr<clay::ScrollOffsetAnimation> animation)\n    : PendingOffsetLayer(offset),\n      scroll_offset_(scroll_offset),\n      visible_offset_range_(visible_offset_range),\n      max_offset_range_(max_offset_range),\n      animation_(std::move(animation)) {}\n\nvoid PendingScrollOffsetLayer::AddToFrame(FrameBuilder* builder,\n                                          const FloatPoint& offset) {\n  builder->PushScrollOffset(offset.x() + Offset().x(),\n                            offset.y() + Offset().y(), scroll_offset_.x(),\n                            scroll_offset_.y(), visible_offset_range_,\n                            max_offset_range_, animation_, this);\n  AddChildrenToFrame(builder);\n  builder->Pop();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_scroll_offset_layer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_SCROLL_OFFSET_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_SCROLL_OFFSET_LAYER_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/flow/animation/scroll_offset_animation.h\"\n#include \"clay/ui/compositing/pending_offset_layer.h\"\n\nnamespace clay {\n\nclass PendingScrollOffsetLayer : public PendingOffsetLayer {\n public:\n  PendingScrollOffsetLayer(\n      const FloatPoint& offset, const FloatPoint& scroll_offset,\n      const FloatRect& visible_offset_range, const FloatRect& max_offset_range,\n      std::shared_ptr<clay::ScrollOffsetAnimation> animation);\n\n  std::string GetName() const override { return \"PendingScrollOffsetLayer\"; }\n\n private:\n  void AddToFrame(FrameBuilder* builder, const FloatPoint& offset) override;\n\n  // `scroll_offset_` is the current scroll offset of the scroll view.\n  FloatPoint scroll_offset_;\n  // `visible_offset_range_` is the visible scroll offset range of the scroll\n  // (maybe has more invisible items out of this range, but should not stop the\n  // animation). If the scroll animation runs beyond this range, but still is\n  // not out of the `max_offset_range_`, it means the animation should be\n  // blocked at the edge and waiting for more visible items.\n  FloatRect visible_offset_range_;\n  // `max_offset_range_` is the max scroll offset range of the scroll view. If\n  // the scroll animation runs beyond this range, it means the animation needs\n  // to end.\n  FloatRect max_offset_range_;\n  // The `ScrollOffsetAnimation` is constructed by RasterFlingManager when\n  // starting a new fling animation, and will be passed to the\n  // `ScrollOffsetMutator` with FrameBuilder during the animation.\n  std::shared_ptr<clay::ScrollOffsetAnimation> animation_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_SCROLL_OFFSET_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/pending_transform_layer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/compositing/pending_transform_layer.h\"\n\n#include <variant>\n\n#include \"clay/gfx/geometry/transform_operations.h\"\n\nnamespace clay {\n\nPendingTransformLayer::PendingTransformLayer(const skity::Matrix& transform,\n                                             const FloatPoint& offset)\n    : PendingOffsetLayer(offset), transform_(transform) {}\n\nPendingTransformLayer::PendingTransformLayer(\n    const TransformOperations& transform, const FloatPoint& origin,\n    const FloatPoint& offset)\n    : PendingOffsetLayer(offset),\n      transform_(transform),\n      transform_origin_(origin) {}\n\nPendingTransformLayer::~PendingTransformLayer() = default;\n\nvoid PendingTransformLayer::AddToFrame(FrameBuilder* builder,\n                                       const FloatPoint& offset) {\n  FloatPoint total_offset = offset + Offset();\n\n  if (std::holds_alternative<skity::Matrix>(transform_)) {\n    skity::Matrix matrix = std::get<skity::Matrix>(transform_);\n    if (total_offset != FloatPoint()) {\n      matrix.PreTranslate(total_offset.x(), total_offset.y());\n    }\n    builder->PushStaticTransform(matrix, this);\n  } else if (std::holds_alternative<TransformOperations>(transform_)) {\n    builder->PushTransformOperations(\n        std::get<TransformOperations>(transform_), transform_origin_.x(),\n        transform_origin_.y(), total_offset.x(), total_offset.y(), this);\n  } else {\n    return;\n  }\n  AddChildrenToFrame(builder, FloatPoint());\n  builder->Pop();\n}\n\nvoid PendingTransformLayer::SetTransform(const skity::Matrix& transform) {\n  transform_.emplace<0>(transform);\n}\n\n#ifndef NDEBUG\nstd::string PendingTransformLayer::ToString() const {\n  std::stringstream ss;\n  skity::Matrix transform;\n  if (std::holds_alternative<skity::Matrix>(transform_)) {\n    transform = std::get<skity::Matrix>(transform_);\n  } else if (std::holds_alternative<TransformOperations>(transform_)) {\n    transform = std::get<TransformOperations>(transform_).Apply().matrix();\n  }\n  ss << PendingOffsetLayer::ToString();\n  ss << \" translate=(\" << transform.Get(0, 3) << \",\" << transform.Get(1, 3)\n     << \")\";\n  ss << \" scale=(\" << transform.Get(0, 0) << \",\" << transform.Get(1, 1) << \")\";\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/compositing/pending_transform_layer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_PENDING_TRANSFORM_LAYER_H_\n#define CLAY_UI_COMPOSITING_PENDING_TRANSFORM_LAYER_H_\n\n#include <optional>\n#include <string>\n#include <variant>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/transform_operations.h\"\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/compositing/pending_offset_layer.h\"\n\nnamespace clay {\n\nclass PendingTransformLayer : public PendingOffsetLayer {\n public:\n  explicit PendingTransformLayer(const skity::Matrix& transform,\n                                 const FloatPoint& offset = FloatPoint());\n  PendingTransformLayer(const TransformOperations& transform,\n                        const FloatPoint& transform_origin,\n                        const FloatPoint& offset = FloatPoint());\n  ~PendingTransformLayer() override;\n\n  std::string GetName() const override { return \"PendingTransformLayer\"; }\n\n  void SetTransform(const skity::Matrix& transform);\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  void AddToFrame(FrameBuilder* builder, const FloatPoint& offset) override;\n\n  std::variant<skity::Matrix, TransformOperations> transform_;\n  FloatPoint transform_origin_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_PENDING_TRANSFORM_LAYER_H_\n"
  },
  {
    "path": "clay/ui/compositing/testing/mock_pending_layer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_COMPOSITING_TESTING_MOCK_PENDING_LAYER_H_\n#define CLAY_UI_COMPOSITING_TESTING_MOCK_PENDING_LAYER_H_\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/compositing/frame_builder.h\"\n#include \"clay/ui/compositing/pending_layer.h\"\n\nnamespace clay {\nnamespace testing {\n\n// Mock implementation of the |PendingLayer| interface that does nothing.\nclass MockPendingLayer : public PendingLayer {\n public:\n  MockPendingLayer() {}\n  ~MockPendingLayer() override {}\n\n  void AddToFrame(FrameBuilder* builder, const FloatPoint& offset) override {}\n};\n\n}  // namespace testing\n}  // namespace clay\n\n#endif  // CLAY_UI_COMPOSITING_TESTING_MOCK_PENDING_LAYER_H_\n"
  },
  {
    "path": "clay/ui/event/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nui_event_sources = [\n  \"event_utils.cc\",\n  \"event_utils.h\",\n  \"focus_manager.cc\",\n  \"focus_manager.h\",\n  \"gesture_event.cc\",\n  \"gesture_event.h\",\n  \"key_code_converter.cc\",\n  \"key_code_converter.h\",\n  \"key_codes.h\",\n  \"key_event.cc\",\n  \"key_event.h\",\n  \"keyboard_key.h\",\n]\n"
  },
  {
    "path": "clay/ui/event/event_utils.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/event/event_utils.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nconst char* EventTypeToString(ClayEventType type) {\n  switch (type) {\n    case kClayEventTypeUnknown:\n      return \"unknown\";\n    case kClayEventTypeTouchStart:\n      return \"touchstart\";\n    case kClayEventTypeTouchMove:\n      return \"touchmove\";\n    case kClayEventTypeTouchCancel:\n      return \"touchcancel\";\n    case kClayEventTypeTouchEnd:\n      return \"touchend\";\n    case kClayEventTypeTap:\n      return \"tap\";\n    case kClayEventTypeLongPress:\n      return \"longpress\";\n    case kClayEventTypeMouseUp:\n      return \"mouseup\";\n    case kClayEventTypeMouseDown:\n      return \"mousedown\";\n    case kClayEventTypeMouseMove:\n      return \"mousemove\";\n    case kClayEventTypeMouseClick:\n      return \"mouseclick\";\n    case kClayEventTypeMouseDoubleClick:\n      return \"mousedblclick\";\n    case kClayEventTypeMouseLongPress:\n      return \"mouselongpress\";\n    case kClayEventTypeMouseEnter:\n      return \"mouseenter\";\n    case kClayEventTypeMouseOver:\n      return \"mouseover\";\n    case kClayEventTypeMouseLeave:\n      return \"mouseleave\";\n    case kClayEventTypeDragEnter:\n      return \"dragenter\";\n    case kClayEventTypeDragOver:\n      return \"dragover\";\n    case kClayEventTypeDragLeave:\n      return \"dragleave\";\n    case kClayEventTypeDrop:\n      return \"drop\";\n    case kClayEventTypeWheel:\n      return \"wheel\";\n    case kClayEventTypeKeyDown:\n      return \"keydown\";\n    case kClayEventTypeKeyUp:\n      return \"keyup\";\n    case kClayEventTypeAnimationStart:\n      return \"animationstart\";\n    case kClayEventTypeAnimationRepeat:\n      return \"animationiteration\";\n    case kClayEventTypeAnimationEnd:\n      return \"animationend\";\n    case kClayEventTypeAnimationCancel:\n      return \"animationcancel\";\n    case kClayEventTypeTransitionStart:\n      return \"transitionstart\";\n    case kClayEventTypeTransitionEnd:\n      return \"transitionend\";\n    default:\n      FML_DLOG(ERROR) << \"unsupported event type:\" << type;\n      return \"\";\n  }\n  return \"\";\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/event/event_utils.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_EVENT_EVENT_UTILS_H_\n#define CLAY_UI_EVENT_EVENT_UTILS_H_\n\n#include \"clay/public/clay.h\"\n\nnamespace clay {\n\nconst char* EventTypeToString(ClayEventType type);\n\n}\n\n#endif  // CLAY_UI_EVENT_EVENT_UTILS_H_\n"
  },
  {
    "path": "clay/ui/event/focus_isolate_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/component/focus/focus_isolate.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/testing/ui_test.h\"\n\nnamespace clay {\n\nconstexpr int kIsolateLeftId = 1;\nconstexpr int kIsolateRightId = 2;\nconstexpr int kIsolateNested = 3;\n\nclass FocusIsolateTest : public UITest {\n protected:\n  void UISetUp() override {\n    //                  isolate-left\n    //                   with               isolate-right\n    //                  2 child\n    //                 ┌──────────┐        ┌──────────┐\n    // ┌─────────┐     │┼────────┼│        │┼────────┼│\n    // │         │     ││        ││        ││        ││\n    // │  1-1    │     ││  2-1   ││        ││  3-1   ││\n    // │         │     ││        ││        ││        ││\n    // │         │     ││        ││        ││        ││\n    // └─────────┘     ├┴────────┴┤        │┼────────┼│\n    //                 │          │        ├──────────┤\n    //                 │          │        │          │\n    //                 │          │        │          │\n    //                 ├┬────────┬┤        ├┬────────┬┤\n    //                 ││        ││        ││        ││\n    //                 ││ 2-2    ││        ││        ││\n    //                 ││        ││        ││  3-2   ││\n    //                 ││        ││        ││        ││\n    //                 │┼────────┼│        │┼────────┼│\n    //                 └──────────┘        └──────────┘\n\n    page_->AddChild(CreateFocusableView({}, {100, 100}, \"1-1\"));\n\n    isolate_left_.reset(\n        CreateFocusIsolate(kIsolateLeftId, {200, 0}, {100, 300}));\n    isolate_left_->AddChild(CreateFocusableView({}, {100, 100}, \"2-1\"));\n    isolate_left_->AddChild(CreateFocusableView({0, 200}, {100, 100}, \"2-2\"));\n    page_->AddChild(isolate_left_.get());\n\n    isolate_right_.reset(\n        CreateFocusIsolate(kIsolateRightId, {400, 0}, {100, 100}));\n    page_->AddChild(isolate_right_.get());\n    isolate_right_->AddChild(CreateFocusableView({}, {100, 100}, \"3-1\"));\n    isolate_right_->AddChild(CreateFocusableView({0, 200}, {100, 100}, \"3-2\"));\n\n    custom_event_callback_ = [this](int id, const char* event_name,\n                                    clay::Value::Map map) {\n      if (strcmp(event_name, \"focusescape\") == 0 ||\n          strcmp(event_name, \"focusenter\") == 0) {\n        events_.emplace_back(event_name, id);\n      }\n    };\n  }\n\n  void UITearDown() override {\n    isolate_left_->DestroyAllChildren();\n    isolate_right_->DestroyAllChildren();\n    isolate_left_ = nullptr;\n    isolate_right_ = nullptr;\n  }\n\n  void AssignFocusFor(const std::string& id) { ViewForId(id)->RequestFocus(); }\n\n  BaseView* GetCurrentFocus() {\n    return static_cast<BaseView*>(\n        page_->GetFocusManager()->GetLeafFocusedNode());\n  }\n\n protected:\n  FocusIsolate* CreateFocusIsolate(int id, const FloatPoint& pos,\n                                   const FloatSize& size) {\n    auto* view = new FocusIsolate(id, page_.get());\n    view->SetBound(pos.x(), pos.y(), size.width(), size.height());\n    return view;\n  }\n\n  View* CreateFocusableView(const FloatPoint& pos, const FloatSize& size,\n                            const std::string& id_selector) {\n    auto* view = new View(-1, page_.get());\n    view->SetBound(pos.x(), pos.y(), size.width(), size.height());\n    view->SetFocusable(true);\n    view->SetIdSelector(id_selector);\n    return view;\n  }\n\n  std::unique_ptr<FocusIsolate> isolate_left_;\n  std::unique_ptr<FocusIsolate> isolate_right_;\n\n  std::vector<std::pair<std::string, int>> events_;\n};\n\nusing ::testing::ElementsAre;\n\n#define MOVE_AND_CHECK_FOCUS(dir, id_selector)             \\\n  {                                                        \\\n    NavigateByDirection(FocusManager::Direction::dir);     \\\n    BaseView* focused_view = GetCurrentFocus();            \\\n    ASSERT_TRUE(!!focused_view) << \"focus has lost\";       \\\n    EXPECT_EQ(id_selector, focused_view->GetIdSelector()); \\\n  }\n\n#define PAIR(e, i) std::pair<std::string, int>(e, i)\n\n#if !defined(OS_WIN) && !defined(OS_MAC) && !defined(OS_HARMONY)\nTEST_F_UI(FocusIsolateTest, NotAllowEscape) {\n  AssignFocusFor(\"2-1\");\n  // Not allow to leave isolate\n  MOVE_AND_CHECK_FOCUS(kRight, \"2-1\");\n\n  MOVE_AND_CHECK_FOCUS(kDown, \"2-2\");\n\n  // trigger escape\n  EXPECT_THAT(events_, ElementsAre(PAIR(\"focusescape\", kIsolateLeftId)));\n}\n\nTEST_F_UI(FocusIsolateTest, AllowEscape) {\n  AssignFocusFor(\"2-1\");\n  isolate_left_->allow_escape_ = true;\n\n  // isolate-right hasn't set any isolate, so focus still remain\n  MOVE_AND_CHECK_FOCUS(kRight, \"2-1\");\n  EXPECT_THAT(events_, ElementsAre(PAIR(\"focusescape\", kIsolateLeftId),\n                                   PAIR(\"focusenter\", kIsolateRightId)));\n\n  MOVE_AND_CHECK_FOCUS(kDown, \"2-2\");\n\n  // Test save last focus child.\n  isolate_left_->save_last_focus_child_ = true;\n  isolate_left_->preferred_child_ = \"2-1\";\n  MOVE_AND_CHECK_FOCUS(kLeft, \"1-1\");\n\n  // Back to 2-2 although preferred 2-1 as priority preset.\n  MOVE_AND_CHECK_FOCUS(kRight, \"2-2\");\n\n  isolate_left_->save_last_focus_child_ = false;\n  isolate_right_->preferred_child_ = \"3-2\";\n  isolate_right_->allow_escape_ = true;\n  // isolate-left's saved focus child will be cleared.\n  MOVE_AND_CHECK_FOCUS(kRight, \"3-2\");\n\n  // Back to isolate-left, preferred 2-1.\n  MOVE_AND_CHECK_FOCUS(kLeft, \"2-1\");\n}\n\n#endif\n\nTEST_F_UI(FocusIsolateTest, NestedScope) {\n  // 1. Nested Isolate\n  // Add a nested focus-isolate into 2-1\n  auto* inner_isolate = CreateFocusIsolate(kIsolateNested, {}, {50, 50});\n  ViewForId(\"2-1\")->AddChild(inner_isolate);\n  auto* inner_view = CreateFocusableView({10, 10}, {30, 30}, \"2-1-1\");\n  inner_isolate->AddChild(inner_view);\n\n  // Simulate setFocus directly to a specific node.\n  inner_view->RequestFocus();\n  EXPECT_EQ(inner_isolate->last_focused_view_, inner_view);\n  EXPECT_NE(isolate_left_->last_focused_view_, inner_view);\n\n  // 2. Nested Scope\n  ScrollView* scroll_view = new ScrollView(-1, page_.get());\n  ViewForId(\"2-2\")->AddChild(scroll_view);\n  scroll_view->SetBound(0, 0, 50, 50);\n  auto* inner_view2 = CreateFocusableView({10, 10}, {30, 30}, \"2-1-1\");\n  scroll_view->AddChild(inner_view2, 0);\n  inner_view2->RequestFocus();\n  EXPECT_EQ(isolate_left_->last_focused_view_, inner_view2);\n}\n\n// Test current leaf focus node is global unique no matter in which isolate.\nTEST_F_UI(FocusIsolateTest, FocusNodeIsUnique) {\n  AssignFocusFor(\"1-1\");\n  EXPECT_EQ(GetCurrentFocus(), ViewForId(\"1-1\"));\n  AssignFocusFor(\"2-1\");\n  EXPECT_EQ(GetCurrentFocus(), ViewForId(\"2-1\"));\n  AssignFocusFor(\"3-1\");\n  EXPECT_EQ(GetCurrentFocus(), ViewForId(\"3-1\"));\n\n  EXPECT_FALSE(ViewForId(\"1-1\")->IsFocused());\n  EXPECT_FALSE(ViewForId(\"2-1\")->IsFocused());\n  EXPECT_TRUE(ViewForId(\"3-1\")->IsFocused());\n\n  // What if focus-isolate is setFocus\n  isolate_left_->RequestFocus();\n\n  EXPECT_FALSE(ViewForId(\"3-1\")->IsFocused());\n}\n\n#if !defined(OS_WIN) && !defined(OS_MAC) && !defined(OS_HARMONY)\n// Test if can restore the last focused child across focus scope.\nTEST_F_UI(FocusIsolateTest, AutoRestoreFocusAcrossScope) {\n  // Focus node is nested in a scroll-view, which has a focus scope.\n  isolate_left_->allow_escape_ = true;\n\n  ScrollView* scroll_view = new ScrollView(-1, page_.get());\n  ViewForId(\"2-1\")->AddChild(scroll_view);\n  scroll_view->SetBound(0, 0, 50, 50);\n  auto* inner_view = CreateFocusableView({10, 10}, {30, 30}, \"2-1-1\");\n  scroll_view->AddChild(inner_view, 0);\n  inner_view->RequestFocus();\n\n  MOVE_AND_CHECK_FOCUS(kLeft, \"1-1\");\n\n  // Destroy the current focus node\n  scroll_view->RemoveChild(inner_view);\n  delete inner_view;\n\n  isolate_left_->preferred_child_ = \"2-2\";\n  MOVE_AND_CHECK_FOCUS(kRight, \"2-2\");\n}\n\n#endif\n\n#undef PAIR\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/event/focus_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/event/focus_manager.h\"\n\n#include <cmath>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/focus/focus_isolate.h\"\n#include \"clay/ui/component/focus_node.h\"\n#include \"clay/ui/event/key_event.h\"\n\nnamespace clay {\nFocusManager::FocusManager(FocusNode* focus_scope_node)\n    : focus_scope_node_(focus_scope_node) {}\n\nvoid FocusManager::RegisterFocusableView(FocusNode* view) {\n  FML_DCHECK(view);\n  UnregisterFocusableView(view);\n  focusable_views_.push_back(view);\n\n  // the focus scope is unfocusable by default, and is set focusable\n  // when it has focusable child views\n  if (focus_scope_node_ && focusable_views_.size() == 1) {\n    focus_scope_node_->StoreFocusable();\n    focus_scope_node_->SetFocusable(true);\n  }\n}\n\nvoid FocusManager::UnregisterFocusableView(FocusNode* view) {\n  FML_DCHECK(view);\n  if (current_focus_view_ == view) {\n    current_focus_view_ = nullptr;\n  }\n  for (auto it = focusable_views_.begin(); it != focusable_views_.end(); ++it) {\n    if (*it == view) {\n      focusable_views_.erase(it);\n      // when the child focusable views of a focus scope are all removed,\n      // restore the focusable attribute of the focus scope to its original\n      // value.\n      if (focus_scope_node_ && focusable_views_.empty()) {\n        focus_scope_node_->RestoreFocusable();\n      }\n      break;\n    }\n  }\n}\n\nvoid FocusManager::RequestFocus(FocusNode* view) {\n  FML_DCHECK(view);\n\n  if (current_focus_view_ == view) {\n    return;\n  }\n  if (current_focus_view_) {\n    current_focus_view_->ClearFocus();\n  }\n  current_focus_view_ = view;\n  if (focus_scope_node_ && !focus_scope_node_->IsFocused()) {\n    focus_scope_node_->RequestFocus(false);\n  }\n\n  if (!view->IsFocusScope()) {\n    FocusManager* manager = this;\n    while (manager) {\n      FocusNode* focus_scope = manager->focus_scope_node_;\n      if (static_cast<BaseView*>(focus_scope)->Is<FocusIsolate>()) {\n        static_cast<FocusIsolate*>(focus_scope)->UpdateLastFocusChild(view);\n        break;\n      }\n      manager = focus_scope->GetParentFocusManager();\n    }\n  }\n}\n\nvoid FocusManager::ClearFocus(FocusNode* view) {\n  FML_DCHECK(view);\n\n  if (current_focus_view_ == view) {\n    current_focus_view_ = nullptr;\n  }\n}\n\nvoid FocusManager::SetFocusable(bool focusable) {\n  focusable_ = focusable;\n  if (!focusable_ && current_focus_view_) {\n    current_focus_view_->ClearFocus();\n  }\n}\n\nvoid FocusManager::SetIsRootScope() {\n  SetFocusable(true);\n  is_root_ = true;\n}\n\nFocusManager* FocusManager::GetRootFocusManager() {\n  if (is_root_) {\n    return this;\n  }\n  return focus_scope_node_->GetParentFocusManager();\n}\n\nbool FocusManager::DispatchKeyEvent(const KeyEvent* event) {\n  if (OnKeyEvent(event)) {\n    return true;\n  }\n\n  auto now = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n  if (now < disable_focus_until_time_) {\n    return false;\n  }\n\n  Direction direction = Direction::kUnknown;\n  // On Desktop, we use the Tab key to determine the direction of focus, rather\n  // than the arrow keys.\n#if !defined(OS_WIN) && !defined(OS_MAC) && !defined(OS_HARMONY)\n  KeyCode code = event->GetLogical();\n  switch (code) {\n    case KeyCode::kArrowUp:\n      direction = Direction::kUp;\n      break;\n    case KeyCode::kArrowDown:\n      direction = Direction::kDown;\n      break;\n    case KeyCode::kArrowLeft:\n      direction = Direction::kLeft;\n      break;\n    case KeyCode::kArrowRight:\n      direction = Direction::kRight;\n      break;\n    default:\n      break;\n  }\n#endif\n\n  FocusManager::TraversalResult traversal_result;\n  if (direction != Direction::kUnknown &&\n      event->GetType() != KeyEventType::kUp) {\n    if (last_trigger_time_ > 0 &&\n        (now - last_trigger_time_) < trigger_interval_) {\n      // Skip if this key event is triggered too often\n      return false;\n    }\n    last_trigger_time_ = now;\n    traversal_result = DoTraversal(direction);\n  }\n  if (traversal_result.succeed) {\n    if (preference_.switch_up_to_once_per_frame) {\n      // Trigger focus switching too often (e.g. long press a arrow key) can\n      // cause performance issues. Here we disable focus switching for this\n      // frame after it has been successfully triggered.\n      DisableFocusUntilNextFrame();\n    }\n    return true;\n  }\n\n  return false;\n}\n\nFocusManager::TraversalResult FocusManager::DoTraversal(Direction direction) {\n  // Handle `next-focus-*` attributes.\n  if (auto focus_node = GetLeafFocusedNode()) {\n    const auto& next_focus_id = focus_node->GetNextFocusId(direction);\n    if (!next_focus_id.empty()) {\n      TraversalResult result{false, direction};\n      if (auto next_focus_node = FindFocusViewById(next_focus_id)) {\n        next_focus_node->RequestFocus();\n        result.succeed = true;\n      }\n      if (result.succeed || !focus_node->NextFocusFallback()) {\n        OnTraversalEnd(result);\n        return result;\n      }\n      // Fallback to default traversal\n    }\n  }\n\n  TraversalResult result =\n      DoTraversalInternal(direction, TraversalType::kDefault);\n  // Traversal by kDefault is irreversible. It will cause the focus state to get\n  // stuck. So we need to try traversal again.\n  if (!result.succeed) {\n    result = DoTraversalInternal(direction, TraversalType::kCenter);\n  }\n\n  OnTraversalEnd(result);\n  return result;\n}\n\nFocusManager::TraversalResult FocusManager::DoTraversalInternal(\n    Direction direction, TraversalType traversal_type) {\n  TraversalResult result;\n  result.direction = direction;\n  if (focusable_views_.empty() || direction == Direction::kUnknown) {\n    return result;\n  }\n\n  if (current_focus_view_ && current_focus_view_->IsFocusScope()) {\n    TraversalResult res =\n        current_focus_view_->OnTraversalOnScope(direction, traversal_type);\n    if (res.succeed || (current_focus_view_->IsFocusFence() &&\n                        !current_focus_view_->allow_escape())) {\n      result.succeed = true;\n      return result;\n    }\n  }\n\n  if (!focusable_) {\n    return result;\n  }\n\n  UpdateFocusRect();\n  FocusNode* next_focus_view = FindNextFocusView(direction, traversal_type);\n  if (!next_focus_view) {\n    return result;\n  }\n\n  if (next_focus_view->IsFocusFence()) {\n    next_focus_view->OnFocusEntering(direction);\n    // Focus fence always has priority to deal with focus.\n    result.succeed = true;\n    return result;\n  }\n  next_focus_view->RequestFocus();\n\n  // Check if a node self cannot be focused while doing traversal, continue\n  // traversal into its children.\n  if (next_focus_view->IsFocusScope() &&\n      next_focus_view->GetFocusBehavior() == FocusBehavior::kStepIntoChild) {\n    result = next_focus_view->OnTraversalOnScope(direction, traversal_type);\n    if (result.succeed == true) {\n      // Keep current focus view in current scope.\n      return result;\n    } else {\n      // Find next again.\n      next_focus_view = FindNextFocusView(direction, traversal_type);\n    }\n  }\n\n  if (!next_focus_view) {\n    return result;\n  }\n  next_focus_view->RequestFocus();\n  FML_DCHECK(current_focus_view_ == next_focus_view);\n\n  result.succeed = true;\n  return result;\n}\n\nvoid FocusManager::OnTraversalEnd(TraversalResult result) {\n  if (HasFocusedNode() && current_focus_view_->IsFocusScope()) {\n    current_focus_view_->GetFocusManager()->OnTraversalEnd(result);\n  }\n}\n\nvoid FocusManager::SortViews(Direction direction) {\n  bool vertical =\n      (direction == Direction::kUp || direction == Direction::kDown);\n  bool forward =\n      (direction == Direction::kDown || direction == Direction::kRight);\n\n  const Axis axis =\n      (direction == Direction::kLeft || direction == Direction::kRight)\n          ? Axis::kX\n          : Axis::kY;\n\n  focusable_views_.sort([vertical, forward, axis, this](FocusNode* a,\n                                                        FocusNode* b) {\n    if (!a) {\n      return false;\n    }\n    if (!b) {\n      return true;\n    }\n\n    if (a->GetFocusIndex(axis) != b->GetFocusIndex(axis)) {\n      return forward ? a->GetFocusIndex(axis) < b->GetFocusIndex(axis)\n                     : a->GetFocusIndex(axis) > b->GetFocusIndex(axis);\n    }\n    FloatRect a_rect = a->focus_rect();\n    FloatRect b_rect = b->focus_rect();\n    if (vertical) {\n      if (forward) {\n        return a_rect.y() == b_rect.y() ? a_rect.x() < b_rect.x()\n                                        : a_rect.y() < b_rect.y();\n      } else {\n        return a_rect.MaxY() == b_rect.MaxY()\n                   ? (preference_.pick_left_when_same_y\n                          ? a_rect.x() < b_rect.x()\n                          : a_rect.x() > b_rect.x())\n                   : b_rect.MaxY() < a_rect.MaxY();\n      }\n    } else {\n      if (forward) {\n        return a_rect.x() == b_rect.x() ? a_rect.y() < b_rect.y()\n                                        : a_rect.x() < b_rect.x();\n      } else {\n        return a_rect.MaxX() == b_rect.MaxX() ? a_rect.y() > b_rect.y()\n                                              : b_rect.MaxX() < a_rect.MaxX();\n      }\n    }\n    return true;\n  });\n}\n\nvoid FocusManager::SortViewsByCenter(Direction direction) {\n  bool forward =\n      (direction == Direction::kDown || direction == Direction::kRight);\n  focusable_views_.sort([forward, direction](FocusNode* a, FocusNode* b) {\n    if (!a) {\n      return false;\n    }\n    if (!b) {\n      return true;\n    }\n\n    const Axis axis =\n        (direction == Direction::kLeft || direction == Direction::kRight)\n            ? Axis::kX\n            : Axis::kY;\n    if (a->GetFocusIndex(axis) != b->GetFocusIndex(axis)) {\n      return forward ? a->GetFocusIndex(axis) < b->GetFocusIndex(axis)\n                     : a->GetFocusIndex(axis) > b->GetFocusIndex(axis);\n    }\n\n    FloatPoint a_center = a->focus_rect().Center();\n    FloatPoint b_center = b->focus_rect().Center();\n    if ((direction == FocusManager::Direction::kUp ||\n         direction == FocusManager::Direction::kDown) &&\n        (a_center.y() == b_center.y())) {\n      return a_center.x() < a_center.x();\n    } else if ((direction == FocusManager::Direction::kLeft ||\n                direction == FocusManager::Direction::kRight) &&\n               (a_center.x() == b_center.x())) {\n      return a_center.y() < a_center.y();\n    }\n\n    switch (direction) {\n      case FocusManager::Direction::kLeft:\n        return a_center.x() > b_center.x();\n      case FocusManager::Direction::kRight:\n        return a_center.x() < b_center.x();\n      case FocusManager::Direction::kUp:\n        return a_center.y() > b_center.y();\n      case FocusManager::Direction::kDown:\n      default:\n        return a_center.y() < b_center.y();\n    }\n    return true;\n  });\n}\n\nFocusNode* FocusManager::FindNextFocusView(Direction direction,\n                                           TraversalType traversal_type) {\n  if (traversal_type == TraversalType::kDefault) {\n    SortViews(direction);\n  } else if (traversal_type == TraversalType::kCenter) {\n    SortViewsByCenter(direction);\n  }\n\n  const Axis axis =\n      (direction == Direction::kLeft || direction == Direction::kRight)\n          ? Axis::kX\n          : Axis::kY;\n  // Filter unfocusable node out.\n  std::list<FocusNode*> focusable_views_when_move;\n  std::copy_if(\n      focusable_views_.begin(), focusable_views_.end(),\n      std::back_inserter(focusable_views_when_move),\n      [axis](FocusNode* node) { return node->CanAcceptFocusOnAxis(axis); });\n\n  // in root scope, visibleRect is invalid. return the first node.\n  if (!current_focus_view_ && is_root_) {\n    return focusable_views_when_move.front();\n  }\n\n  FloatRect visibleRect;\n  if (focus_scope_node_) {\n    visibleRect = focus_scope_node_->GetContentVisibleRect();\n  }\n  if (!current_focus_view_ && first_node_expand_ratio_ > 0.f) {\n    switch (direction) {\n      case FocusManager::Direction::kLeft:\n        visibleRect.Expand(0, 0, 0,\n                           visibleRect.width() * first_node_expand_ratio_);\n        break;\n      case FocusManager::Direction::kRight:\n        visibleRect.Expand(0, visibleRect.width() * first_node_expand_ratio_, 0,\n                           0);\n        break;\n      case FocusManager::Direction::kUp:\n        visibleRect.Expand(visibleRect.height() * first_node_expand_ratio_, 0,\n                           0, 0);\n        break;\n      case FocusManager::Direction::kDown:\n        visibleRect.Expand(0, 0,\n                           visibleRect.height() * first_node_expand_ratio_, 0);\n        break;\n      default:\n        break;\n    }\n  }\n\n  // Forward iterator to the next candidate.\n  auto it = focusable_views_when_move.begin();\n  if (!current_focus_view_ || current_focus_view_->CanAcceptFocusOnAxis(axis)) {\n    // Find next focus node.\n    for (; it != focusable_views_when_move.end(); ++it) {\n      if (!current_focus_view_) {\n        FML_DCHECK(!visibleRect.IsEmpty());\n        if (!visibleRect.Intersects((*it)->focus_rect())) {\n          continue;\n        } else {\n          // If there's no focused view, and this node is just in rect.\n          return (*it);\n        }\n      } else if (current_focus_view_ == *it) {\n        it++;\n        break;\n      }\n    }\n  }\n\n  if (it == focusable_views_when_move.end()) {\n    return nullptr;\n  }\n\n  bool is_vertical =\n      (direction == Direction::kUp || direction == Direction::kDown);\n  bool is_horizontal =\n      (direction == Direction::kLeft || direction == Direction::kRight);\n  FloatRect current_focus_rect = current_focus_view_->focus_rect();\n  FocusNode* next_focus_view = nullptr;\n  // The first candidate focus node\n  FocusNode* candidate = nullptr;\n  for (; it != focusable_views_when_move.end(); ++it) {\n    int focus_index = (*it)->GetFocusIndex(axis);\n    if (focus_index < 0) {\n      // As sorted, all nodes following are < 0.\n      break;\n    }\n    FloatRect rect = (*it)->focus_rect();\n\n    if (focus_index == current_focus_view_->GetFocusIndex(axis)) {\n      bool outside_band =\n          (is_vertical && (rect.MaxX() <= current_focus_rect.x() ||\n                           rect.x() >= current_focus_rect.MaxX())) ||\n          (is_horizontal && (rect.MaxY() <= current_focus_rect.y() ||\n                             rect.y() >= current_focus_rect.MaxY()));\n      if ((traversal_type == TraversalType::kDefault ||\n           traversal_type == TraversalType::kCenter) &&\n          outside_band) {\n        if (!candidate) {\n          candidate = *it;\n        }\n        continue;\n      }\n\n      bool outside_visible_band =\n          (is_vertical && (rect.MaxX() <= visibleRect.x() ||\n                           rect.x() >= visibleRect.MaxX())) ||\n          (is_horizontal &&\n           (rect.MaxY() <= visibleRect.y() || rect.y() >= visibleRect.MaxY()));\n      if (traversal_type == TraversalType::kNearest && outside_visible_band) {\n        if (!candidate) {\n          candidate = *it;\n        }\n        continue;\n      }\n    }\n\n    if (next_focus_view == nullptr) {\n      if (candidate && current_focus_view_->GetFocusIndex(axis) !=\n                           candidate->GetFocusIndex(axis)) {\n        // Though there's no suitable node within the band, but there is a\n        // customized focus sequence node. For example, there 4 nodes:\n        //   [0,2] [0,-1]\n        //   [0,0] [1,1] <--\n        // Now focus is at [1,1], and ArrowDown pressed, the sorted view may be\n        // [1,1], [0, 2], [0, 0], [0, -1]\n        // so the closest node is [0, 2], but it is outside the vertical band.\n        // So it would be candidate and because of 2 > 1, it is the next focus\n        // view.\n        next_focus_view = candidate;\n        break;\n      }\n      next_focus_view = *it;\n      if (preference_.pick_first) {\n        break;\n      } else {\n        continue;\n      }\n    }\n    if (next_focus_view->GetFocusIndex(axis) != focus_index) {\n      // Stop forward as focus_index changed.\n      break;\n    }\n    FloatRect pri_rect = next_focus_view->focus_rect();\n    if ((direction == Direction::kUp && pri_rect.MaxY() - rect.MaxY() > 0.1f) ||\n        (direction == Direction::kDown && rect.y() - pri_rect.y() > 0.1f) ||\n        (direction == Direction::kLeft &&\n         pri_rect.MaxX() - rect.MaxX() > 0.1f) ||\n        (direction == Direction::kRight && rect.x() - pri_rect.x() > 0.1f)) {\n      break;\n    }\n\n    FloatPoint pri_distance = pri_rect.Center() - current_focus_rect.Center();\n    FloatPoint distance = rect.Center() - current_focus_rect.Center();\n\n    if ((is_vertical && std::abs(distance.x()) < std::abs(pri_distance.x())) ||\n        (is_horizontal &&\n         std::abs(distance.y()) < std::abs(pri_distance.y()))) {\n      next_focus_view = *it;\n    }\n  }\n\n  return next_focus_view;\n}\n\nFocusNode* FocusManager::FindFocusViewById(const std::string& id) {\n  for (auto node : focusable_views_) {\n    if (node->FocusId() == id) {\n      return node;\n    } else if (node->IsFocusScope()) {\n      if (auto manager = node->GetFocusManager()) {\n        if (auto found = manager->FindFocusViewById(id)) {\n          return found;\n        }\n      }\n    }\n  }\n  return nullptr;\n}\n\nFocusNode* FocusManager::GetLeafFocusedNode() {\n  FocusNode* focus_node = current_focus_view_;\n  while (focus_node && focus_node->IsFocusScope()) {\n    FocusManager* manager = focus_node->GetFocusManager();\n    if (!manager || !manager->current_focus_view_) {\n      break;\n    }\n    focus_node = manager->current_focus_view_;\n  }\n  return focus_node;\n}\n\nFloatRect FocusManager::GetFocusedNodeRect() {\n  if (!current_focus_view_ || !focus_scope_node_) {\n    return FloatRect();\n  }\n  FloatRect visible_rect = current_focus_view_->GetContentVisibleRect();\n  current_focus_view_->UpdateFocusRect();\n  FloatRect focus_rect = current_focus_view_->focus_rect();\n  focus_rect.Move(-visible_rect.x(), -visible_rect.y());\n  return focus_rect;\n}\n\nvoid FocusManager::UpdateFocusRect() {\n  for (auto& focusable_view : focusable_views_) {\n    focusable_view->UpdateFocusRect();\n  }\n}\n\nbool FocusManager::OnKeyEvent(const KeyEvent* event) {\n  bool handled = false;\n  if (current_focus_view_ && current_focus_view_->IsFocusScope()) {\n    handled = current_focus_view_->GetFocusManager()->OnKeyEvent(event);\n  } else if (current_focus_view_) {\n    handled = current_focus_view_->DispatchKeyEventOnFocusNode(event);\n  }\n  if (!handled && focus_scope_node_) {\n    handled = focus_scope_node_->DispatchKeyEventOnFocusNode(event);\n  }\n  return handled;\n}\n\nvoid FocusManager::DisableFocusUntilNextFrame() {\n  disable_focus_until_time_ =\n      fml::TimePoint::Now().ToEpochDelta().ToMilliseconds() + 1000;\n}\n\nvoid FocusManager::RestoreFocusSwitching() { disable_focus_until_time_ = 0; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/event/focus_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_EVENT_FOCUS_MANAGER_H_\n#define CLAY_UI_EVENT_FOCUS_MANAGER_H_\n\n#include <list>\n#include <memory>\n#include <string>\n#include <unordered_set>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\nclass FocusNode;\nclass KeyEvent;\n\nclass FocusManager {\n public:\n  explicit FocusManager(FocusNode* focus_scope_node = nullptr);\n  ~FocusManager() = default;\n\n  struct Preference {\n    // if false, traversal until the find the most match node.\n    bool pick_first = true;\n    // if false, pick the right node if nodes' y()is same.\n    // for example,\n    //   node1  node2\n    //       node3\n    //  when move up from node3, node1 will get focus by default.\n    bool pick_left_when_same_y = true;\n    // This sets whether we can trigger multiple focus switches in a single\n    // frame. If true, only the first switch in a frame will take effect.\n    bool switch_up_to_once_per_frame = true;\n  };\n\n  void SetPreference(const Preference& preference) { preference_ = preference; }\n  Preference& preference() { return preference_; }\n\n  enum class Direction {\n    kUnknown = 0,\n    kUp,\n    kDown,\n    kLeft,\n    kRight,\n  };\n  enum class TraversalType {\n    kDefault = 0,  // Traversal by edge node's edge.\n    kCenter,       // Traversal by edge node's center point.\n    kNearest,      // Find nearest node.\n  };\n\n  struct TraversalResult {\n    bool succeed = false;\n    Direction direction = Direction::kUnknown;\n  };\n\n  void RegisterFocusableView(FocusNode*);\n  void UnregisterFocusableView(FocusNode*);\n\n  void RequestFocus(FocusNode*);\n  void ClearFocus(FocusNode*);\n  void SetFocusable(bool focusable);\n\n  void SetIsRootScope();\n  FocusManager* GetRootFocusManager();\n  bool DispatchKeyEvent(const KeyEvent* event);\n  bool HasFocusedNode() { return !!current_focus_view_; }\n  // Note: If caller needs cache focus node, a FocusNodeListener should be\n  // added and listening OnFocusNodeUnregistered to avoid dangling pointer.\n  FocusNode* GetLeafFocusedNode();\n  FloatRect GetFocusedNodeRect();\n\n  void SetFirstFocusedNodeExpandRatio(float ratio) {\n    first_node_expand_ratio_ = ratio;\n  }\n\n  // Set the trigger interval for changing the focus by pressing keys\n  void SetTriggerInterval(int interval) { trigger_interval_ = interval; }\n\n  // This will disable focus switching until the next frame\n  // (`RestoreFocusSwitching` will be called in the next frame). There is a 1\n  // second timeout to avoid potential problems (e.g. focus switching without\n  // requesting a new frame).\n  void DisableFocusUntilNextFrame();\n  void RestoreFocusSwitching();\n\n  TraversalResult DoTraversal(Direction direction);\n\n private:\n  FRIEND_TEST(FocusManagerTest, SortViews);\n  FRIEND_TEST(FocusManagerTest, FindNextFocusView);\n  friend class FocusNode;\n\n  TraversalResult DoTraversalInternal(Direction direction,\n                                      TraversalType traversal_type);\n  void OnTraversalEnd(TraversalResult result);\n  void SortViews(Direction direction);\n  void SortViewsByCenter(Direction direction);\n  FocusNode* FindNextFocusView(Direction direction,\n                               TraversalType traversal_type);\n  FocusNode* FindFocusViewById(const std::string& id);\n  void UpdateFocusRect();\n  bool OnKeyEvent(const KeyEvent* event);\n\n  Preference preference_;\n\n  bool focusable_ = false;\n  bool is_root_ = false;\n  float first_node_expand_ratio_ = 0.f;\n  // FocusManager is holden by focus_scope_node_.\n  FocusNode* focus_scope_node_ = nullptr;\n  std::list<FocusNode*> focusable_views_;\n  FocusNode* current_focus_view_ = nullptr;\n\n  int trigger_interval_ = 0;        // in milliseconds\n  int64_t last_trigger_time_ = -1;  // in milliseconds\n  // This is used to temporarily disable focus switching until the given time.\n  int64_t disable_focus_until_time_ = 0;  // in milliseconds\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_EVENT_FOCUS_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/event/focus_manager_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/thread.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/scroll_view.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/event/focus_manager.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nnamespace {\nView* AddView(int id, PageView* root, float left, float top, float width,\n              float height) {\n  View* childView = new View(id, root);\n  childView->SetX(left);\n  childView->SetY(top);\n  childView->SetWidth(width);\n  childView->SetHeight(height);\n  root->AddChild(childView);\n  childView->SetFocusable(true);\n  return childView;\n}\n\n[[maybe_unused]] View* AddViewToParent(int id, PageView* root, BaseView* parent,\n                                       float left, float top, float width,\n                                       float height) {\n  View* childView = new View(id, root);\n  childView->SetX(left);\n  childView->SetY(top);\n  childView->SetWidth(width);\n  childView->SetHeight(height);\n  parent->AddChild(childView);\n  return childView;\n}\n}  // namespace\n\nTEST(FocusManagerTest, SortViews) {\n  auto thread = std::make_unique<fml::Thread>(\"ui\");\n  std::unique_ptr<PageView> root =\n      std::make_unique<PageView>(0, nullptr, thread->GetTaskRunner());\n\n  //  view1               view2\n  //              view3\n  //                      view4\n  //  view5 view6\n\n  AddView(1, root.get(), 100, 100, 100, 100);\n  AddView(2, root.get(), 700, 100, 100, 100);\n  AddView(3, root.get(), 500, 300, 100, 100);\n  AddView(4, root.get(), 700, 500, 100, 100);\n  AddView(5, root.get(), 100, 700, 100, 100);\n  AddView(6, root.get(), 300, 700, 100, 100);\n\n  FocusManager* manager = root->GetFocusManager();\n  manager->preference().pick_first = false;\n  manager->preference().pick_left_when_same_y = false;\n  manager->UpdateFocusRect();\n\n  manager->SortViews(FocusManager::Direction::kUp);\n  int res_up[] = {6, 5, 4, 3, 2, 1};\n  int index = 0;\n  for (auto it = manager->focusable_views_.begin();\n       it != manager->focusable_views_.end(); ++it) {\n    EXPECT_EQ(static_cast<BaseView*>(*it)->id(), res_up[index]);\n    index++;\n  }\n\n  manager->SortViews(FocusManager::Direction::kDown);\n  int res_down[] = {1, 2, 3, 4, 5, 6};\n  index = 0;\n  for (auto it = manager->focusable_views_.begin();\n       it != manager->focusable_views_.end(); ++it) {\n    EXPECT_EQ(static_cast<BaseView*>(*it)->id(), res_down[index]);\n    index++;\n  }\n\n  manager->SortViews(FocusManager::Direction::kLeft);\n  int res_left[] = {4, 2, 3, 6, 5, 1};\n  index = 0;\n  for (auto it = manager->focusable_views_.begin();\n       it != manager->focusable_views_.end(); ++it) {\n    EXPECT_EQ(static_cast<BaseView*>(*it)->id(), res_left[index]);\n    index++;\n  }\n\n  manager->SortViews(FocusManager::Direction::kRight);\n  int res_right[] = {1, 5, 6, 3, 2, 4};\n  index = 0;\n  for (auto it = manager->focusable_views_.begin();\n       it != manager->focusable_views_.end(); ++it) {\n    EXPECT_EQ(static_cast<BaseView*>(*it)->id(), res_right[index]);\n    index++;\n  }\n\n  root->DestroyAllChildren();\n}\n\nTEST(FocusManagerTest, FindNextFocusView) {\n  auto thread = std::make_unique<fml::Thread>(\"ui\");\n  std::unique_ptr<PageView> root =\n      std::make_unique<PageView>(0, nullptr, thread->GetTaskRunner());\n\n  //                      |----------------|\n  //                      |                |\n  //    |----------|      |      view1     |\n  //    |   view2  |      |----------------|\n  //    |          |\n  //    |----------|\n  //\n  //         |--------------|     |----------------|\n  //         |              |     |     view4      |\n  //         |    view3     |     |----------------|\n  //         |              |\n  //         |--------------|\n  //\n  //                        |-----|\n  //                        |view5|\n  //                        |-----|\n\n  View* view1 = AddView(1, root.get(), 300, 100, 200, 200);\n  View* view2 = AddView(2, root.get(), 100, 200, 150, 200);\n  View* view3 = AddView(3, root.get(), 200, 500, 150, 200);\n  View* view4 = AddView(4, root.get(), 400, 500, 150, 100);\n  AddView(5, root.get(), 350, 800, 50, 100);\n\n  FocusManager* manager = root->GetFocusManager();\n  manager->preference().pick_first = false;\n  manager->preference().pick_left_when_same_y = false;\n  manager->UpdateFocusRect();\n\n  FocusNode* next_focus_view = manager->FindNextFocusView(\n      FocusManager::Direction::kDown, FocusManager::TraversalType::kDefault);\n  EXPECT_EQ(static_cast<BaseView*>(next_focus_view), view1);\n  manager->current_focus_view_ = next_focus_view;\n\n  next_focus_view = manager->FindNextFocusView(\n      FocusManager::Direction::kDown, FocusManager::TraversalType::kDefault);\n  EXPECT_EQ(static_cast<BaseView*>(next_focus_view), view4);\n  manager->current_focus_view_ = next_focus_view;\n\n  next_focus_view = manager->FindNextFocusView(\n      FocusManager::Direction::kDown, FocusManager::TraversalType::kDefault);\n  EXPECT_EQ(next_focus_view, nullptr);\n\n  next_focus_view = manager->FindNextFocusView(\n      FocusManager::Direction::kLeft, FocusManager::TraversalType::kDefault);\n  EXPECT_EQ(static_cast<BaseView*>(next_focus_view), view3);\n  manager->current_focus_view_ = next_focus_view;\n\n  next_focus_view = manager->FindNextFocusView(\n      FocusManager::Direction::kUp, FocusManager::TraversalType::kDefault);\n  EXPECT_EQ(static_cast<BaseView*>(next_focus_view), view2);\n  manager->current_focus_view_ = next_focus_view;\n\n  next_focus_view = manager->FindNextFocusView(\n      FocusManager::Direction::kRight, FocusManager::TraversalType::kDefault);\n  EXPECT_EQ(static_cast<BaseView*>(next_focus_view), view1);\n  manager->current_focus_view_ = next_focus_view;\n\n  root->DestroyAllChildren();\n}\n\n#if !defined(OS_WIN) && !defined(OS_MAC) && !defined(OS_HARMONY)\nTEST(FocusManagerTest, ScrollViewFocusTest) {\n  auto thread = std::make_unique<fml::Thread>(\"ui\");\n  std::unique_ptr<PageView> root =\n      std::make_unique<PageView>(0, nullptr, thread->GetTaskRunner());\n  ScrollView* scroll_view =\n      new ScrollView(1, ScrollDirection::kVertical, root.get());\n  scroll_view->SetX(300);\n  scroll_view->SetY(300);\n  scroll_view->SetWidth(700);\n  scroll_view->SetHeight(300);\n  root->AddChild(scroll_view);\n  scroll_view->SetFocusable(true);\n\n  View* view2 = AddViewToParent(2, root.get(), scroll_view, 0, 0, 500, 200);\n  View* view3 = AddViewToParent(3, root.get(), view2, 100, 100, 200, 100);\n  View* view4 = AddViewToParent(4, root.get(), scroll_view, 0, 300, 300, 400);\n  View* view5 = AddViewToParent(5, root.get(), view4, 100, 100, 200, 200);\n  View* view6 = AddViewToParent(6, root.get(), scroll_view, 400, 400, 300, 400);\n  View* view7 = AddViewToParent(7, root.get(), view6, 100, 100, 200, 200);\n  view3->SetFocusable(true);\n  view5->SetFocusable(true);\n  view7->SetFocusable(true);\n  scroll_view->RequestFocus();\n  scroll_view->OnLayoutUpdated();\n  //\n  //     |----------------------------|\n  //     |             view2          |\n  //     |     |-----------|          |\n  //     |     |   view3   |          |\n  //     |-----|-----------|----------|\n  //\n  //     |-----------------|\n  //     |      view4      |\n  //     |     |-----------|   |---------------|\n  //     |     |   view5   |   |     view6     |\n  //     |     |           |   |    |----------|\n  //     |     |-----------|   |    |   view7  |\n  //     |                 |   |    |          |\n  //     |-----------------|   |    |----------|\n  //                           |               |\n  //                           |---------------|\n\n  KeyEvent key_down(0, KeyEventType::kDown, PhysicalKeyboardKey::kUnknown,\n                    KeyCode::kArrowDown, false, \"\");\n  KeyEvent key_right(0, KeyEventType::kDown, PhysicalKeyboardKey::kUnknown,\n                     KeyCode::kArrowRight, false, \"\");\n\n  scroll_view->GetFocusManager()->preference().switch_up_to_once_per_frame =\n      false;\n  scroll_view->GetFocusManager()->DispatchKeyEvent(&key_down);\n  EXPECT_TRUE(view3->IsFocused());\n  EXPECT_EQ(view3->focus_rect(), FloatRect(100, 100, 200, 100));\n  EXPECT_EQ(view5->focus_rect(), FloatRect(100, 400, 200, 200));\n  EXPECT_EQ(view7->focus_rect(), FloatRect(500, 500, 200, 200));\n\n  scroll_view->GetFocusManager()->DispatchKeyEvent(&key_down);\n  EXPECT_TRUE(view5->IsFocused());\n  EXPECT_EQ(scroll_view->GetScrollOffset().y(), 300);\n\n  scroll_view->GetFocusManager()->DispatchKeyEvent(&key_right);\n  EXPECT_TRUE(view7->IsFocused());\n  EXPECT_EQ(scroll_view->GetScrollOffset().y(), 400);\n\n  root->DestroyAllChildren();\n}\n#endif\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/event/gesture_event.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/event/gesture_event.h\"\n\n#include <sstream>\n\nnamespace clay {\nnamespace {\nconst char* c_point_event_type_string[] = {\n    \"Unkown\", \"TouchDown\", \"TouchUp\",      \"TouchMove\",     \"Hover\",\n    \"Signal\", \"Cancel\",    \"PanZoomStart\", \"PanZoomUpdate\", \"PanZoomEnd\"};\n}  // namespace\n\nstd::string PointerEvent::ToString() const {\n  if (type < EventType::kUnkownEvent || type > EventType::kPanZoomEndEvent) {\n    return \"InvalidPointerEvent\";\n  }\n  std::stringstream ss;\n  ss << \"PointerEvent{\" << c_point_event_type_string[static_cast<int>(type)]\n     << \" pointer_id=\" << pointer_id << \" Position=\" << position.x() << \",\"\n     << position.y() << \" Delta=\" << delta.width() << \",\" << delta.height()\n     << \" PanDelta=\" << pan_delta.width() << \",\" << pan_delta.height()\n     << \" ScrollDeltaX=\" << scroll_delta_x << \" ScrollDeltaY=\" << scroll_delta_y\n     << \"}\";\n  return ss.str();\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/event/gesture_event.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_EVENT_GESTURE_EVENT_H_\n#define CLAY_UI_EVENT_GESTURE_EVENT_H_\n\n#include <stdint.h>\n\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n\nnamespace clay {\n\n/// Action code for when a primary pointer touched the screen.\n///\n/// Android's\n/// [MotionEvent.ACTION_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_DOWN)\nstatic constexpr int kActionDown = 0;\n\n/// Action code for when a primary pointer stopped touching the screen.\n///\n/// Android's\n/// [MotionEvent.ACTION_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_UP)\nstatic constexpr int kActionUp = 1;\n\n/// Action code for when the event only includes information about pointer\n/// movement.\n///\n/// Android's\n/// [MotionEvent.ACTION_MOVE](https://developer.android.com/reference/android/view/MotionEvent#ACTION_MOVE)\nstatic constexpr int kActionMove = 2;\n\n/// Action code for when a motion event has been canceled.\n///\n/// Android's\n/// [MotionEvent.ACTION_CANCEL](https://developer.android.com/reference/android/view/MotionEvent#ACTION_CANCEL)\nstatic constexpr int kActionCancel = 3;\n\n/// Action code for when a secondary pointer touched the screen.\n///\n/// Android's\n/// [MotionEvent.ACTION_POINTER_DOWN](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_DOWN)\nstatic constexpr int kActionPointerDown = 5;\n\n/// Action code for when a secondary pointer stopped touching the screen.\n///\n/// Android's\n/// [MotionEvent.ACTION_POINTER_UP](https://developer.android.com/reference/android/view/MotionEvent#ACTION_POINTER_UP)\nstatic constexpr int kActionPointerUp = 6;\n\n/// Android's\n/// [InputDevice.SOURCE_UNKNOWN](https://developer.android.com/reference/android/view/InputDevice#SOURCE_UNKNOWN)\nstatic const int kInputDeviceSourceUnknown = 0;\n\n/// Android's\n/// [InputDevice.SOURCE_TOUCHSCREEN](https://developer.android.com/reference/android/view/InputDevice#SOURCE_TOUCHSCREEN)\nstatic const int kInputDeviceSourceTouchScreen = 4098;\n\n/// Android's\n/// [InputDevice.SOURCE_MOUSE](https://developer.android.com/reference/android/view/InputDevice#SOURCE_MOUSE)\nstatic const int kInputDeviceSourceMouse = 8194;\n\n/**\n * Base class for touch, stylus, or mouse events.\n *\n * @note It contains the union set of members in the derived class. Therefore, a\n * value of a member variable is not necessarily meaningful for all derived\n * class. Using the getters in the derived classes is high recommended.\n *\n * Pointer events operate in the coordinate space of the screen, scaled to\n * logical pixels. Logical pixels approximate a grid with about 38 pixels per\n * centimeter, or 96 pixels per inch.\n *\n * This allows gestures to be recognized independent of the precise hardware\n * characteristics of the device. In particular, features such as touch slop\n * (see [kTouchSlop]) can be defined in terms of roughly physical lengths so\n * that the user can shift their finger by the same distance on a high-density\n * display as on a low-resolution device.\n *\n * For similar reasons, pointer events are not affected by any transforms in\n * the rendering layer. This means that deltas may need to be scaled before\n * being applied to movement within the rendering. For example, if a scrolling\n * list is shown scaled by 2x, the pointer deltas will have to be scaled by the\n * inverse amount if the list is to appear to scroll with the user's finger.\n */\nstruct PointerEvent {\n  enum class EventType {\n    kUnkownEvent = 0,\n    kDownEvent,\n    kUpEvent,\n    kMoveEvent,\n    kHoverEvent,\n    kSignalEvent,\n    kCancel,\n    kPanZoomStartEvent,\n    kPanZoomUpdateEvent,\n    kPanZoomEndEvent,\n  };\n\n  enum DeviceType {\n    kTouch,\n    kMouse,\n    kStylus,\n    kInvertedStylus,\n    kTrackpad,\n  };\n\n  enum class SignalKind {\n    kNone,\n    kStartScroll,\n    kScroll,\n    kEndScroll,\n  };\n\n  // used in bit field buttons to show which buttons are pressed\n  // MouseBotton should keep the same as FlutterPointerMouseButtons\n  enum MouseButton {\n    kPrimary = 1 << 0,\n    kSecondary = 1 << 1,\n    kMiddle = 1 << 2,\n    kBack = 1 << 3,\n    kForward = 1 << 4,\n  };\n\n  PointerEvent() = default;\n\n  explicit PointerEvent(PointerEvent::EventType event_type)\n      : type(event_type) {}\n\n  std::string ToString() const;\n\n  EventType type = EventType::kUnkownEvent;\n  DeviceType device = kTouch;\n\n  /**\n   * Unique identifier that ties the `PointerEvent` to the embedder event that\n   * created it.\n   *\n   * No two pointer events can have the same `embedder_id` on platforms that set\n   * it. This is different from `pointer_id` identifier - used for hit-testing,\n   * whereas `embedder_id` is used to identify the platform event.\n   *\n   * On Android this is ID of the underlying\n   * [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent).\n   */\n  int embedder_id = 0;\n  /**\n   * Time of event dispatch, relative to an arbitrary timeline.\n   */\n  uint64_t timestamp = 0;\n  /**\n   * Unique identifier for the pointer, not reused. Changes for each new pointer\n   * down event.\n   */\n  int pointer_id = 0;\n  /**\n   * Unique identifier for the pointing device, reused across interactions.\n   */\n  int device_id = 0;\n  /**\n   * Coordinate of the position of the pointer, in logical pixels in the global\n   * coordinate space.\n   */\n  FloatPoint position;\n  /**\n   * Distance in logical pixels that the pointer moved since the last\n   * `PointerMoveEvent` or `PointerHoverEvent`.\n   *\n   * This value is always 0.0 for down, up, and cancel events.\n   */\n  FloatSize delta;\n  /**\n   * Set if the pointer is currently down.\n   *\n   * For touch and stylus pointers, this means the object (finger, pen) is in\n   * contact with the input surface. For mice, it means a button is pressed.\n   */\n  bool down = false;\n\n  int64_t buttons = 0;  // bit field to indicate which buttons are pressed\n\n  /**\n   * The pressure of the touch.\n   *\n   * This value is a number ranging from 0.0, indicating a touch with no\n   * discernible pressure, to 1.0, indicating a touch with \"normal\" pressure,\n   * and possibly beyond, indicating a stronger touch. For devices that do not\n   * detect pressure (e.g. mice), returns 1.0.\n   */\n  double pressure = 1.0;\n  /**\n   *  The minimum value that `pressure` can return for this pointer.\n   *\n   *  For devices that do not detect pressure (e.g. mice), returns 1.0.\n   *  This will always be a number less than or equal to 1.0.\n   */\n  double pressure_min = 1.0;\n  /**\n   * The maximum value that `pressure` can return for this pointer.\n   *\n   * For devices that do not detect pressure (e.g. mice), returns 1.0.\n   * This will always be a greater than or equal to 1.0.\n   */\n  double pressure_max = 1.0;\n  /**\n   * The distance of the detected object from the input surface.\n   *\n   * For instance, this value could be the distance of a stylus or finger\n   * from a touch screen, in arbitrary units on an arbitrary (not necessarily\n   * linear) scale. If the pointer is down, this is 0.0 by definition.\n   */\n  double distance = 0.0;\n  /**\n   * The maximum value that `distance` can return for this pointer.\n   *\n   * If this input device cannot detect \"hover touch\" input events, then this\n   * will be 0.0.\n   */\n  double distance_max = 0.0;\n  /**\n   * The area of the screen being pressed.\n   *\n   * This value is scaled to a range between 0 and 1. It can be used to\n   * determine fat touch events. This value is only set on Android and is\n   * a device specific approximation within the range of detectable values.\n   * So, for example, the value of 0.1 could mean a touch with the tip of\n   * the finger, 0.2 a touch with full finger, and 0.3 the full palm.\n   *\n   * Because this value uses device-specific range and is uncalibrated,\n   * it is of limited use and is primarily retained in order to be able\n   * to reconstruct original pointer events for [AndroidView].\n   */\n  double size = 0.0;\n  /**\n   * The radius of the contact ellipse along the major axis, in logical pixels.\n   */\n  double radius_major = 0.0;\n  /**\n   * The radius of the contact ellipse along the minor axis, in logical pixels.\n   */\n  double radius_minor = 0.0;\n  /**\n   * The minimum value that could be reported for `radius_major` and\n   * `radius_minor` for this pointer, in logical pixels.\n   */\n  double radius_min = 0.0;\n  /**\n   * The maximum value that could be reported for `radiusMajor` and\n   * `radiusMinor` for this pointer, in logical pixels.\n   */\n  double radius_max = 0.0;\n  /**\n   * The orientation angle of the detected object, in radians.\n   *\n   * For [PointerDeviceKind.touch] events:\n   *\n   * The angle of the contact ellipse, in radians in the range:\n   *\n   *     -pi/2 < orientation <= pi/2\n   *\n   * ...giving the angle of the major axis of the ellipse with the y-axis\n   * (negative angles indicating an orientation along the top-left /\n   * bottom-right diagonal, positive angles indicating an orientation along the\n   * top-right / bottom-left diagonal, and zero indicating an orientation\n   * parallel with the y-axis).\n   *\n   * For [PointerDeviceKind.stylus] and [PointerDeviceKind.invertedStylus]\n   * events:\n   *\n   * The angle of the stylus, in radians in the range:\n   *\n   *     -pi < orientation <= pi\n   *\n   * ...giving the angle of the axis of the stylus projected onto the input\n   * surface, relative to the positive y-axis of that surface (thus 0.0\n   * indicates the stylus, if projected onto that surface, would go from the\n   * contact point vertically up in the positive y-axis direction, pi would\n   * indicate that the stylus would go down in the negative y-axis direction;\n   * pi/4 would indicate that the stylus goes up and to the right, -pi/2 would\n   * indicate that the stylus goes to the left, etc).\n   */\n  double orientation = 0.0;\n  /**\n   * The tilt angle of the detected object, in radians.\n   *\n   * For [PointerDeviceKind.stylus] and [PointerDeviceKind.invertedStylus]\n   * events:\n   *\n   * The angle of the stylus, in radians in the range:\n   *\n   *     0 <= tilt <= pi/2\n   *\n   * ...giving the angle of the axis of the stylus, relative to the axis\n   * perpendicular to the input surface (thus 0.0 indicates the stylus is\n   * orthogonal to the plane of the input surface, while pi/2 indicates that\n   * the stylus is flat on that surface).\n   */\n  double tilt = 0.0;\n  /**\n   * Opaque platform-specific data associated with the event.\n   */\n  int platform_data = 0;\n  /**\n   * Set if the event was synthesized by Flutter.\n   *\n   * We occasionally synthesize PointerEvents that aren't exact translations of\n   * `PointerData` from the engine to cover small cross-OS discrepancies in\n   * pointer behaviors.\n   *\n   * For instance, on end events, Android always drops any location changes\n   * that happened between its reporting intervals when emitting the end events.\n   *\n   * On iOS, minor incorrect location changes from the previous move events can\n   * be reported on end events. We synthesize a `PointerEvent` to cover the\n   * difference between the 2 events in that case.\n   */\n  bool synthesized = false;\n\n  SignalKind signal_kind = SignalKind::kNone;\n\n  float scroll_delta_x = 0.0;\n  float scroll_delta_y = 0.0;\n\n  /// The total pan offset of the pan/zoom.\n  FloatPoint pan = {0.0, 0.0};\n  /// The amount the pan offset changed since the last event.\n  FloatSize pan_delta = {0.0, 0.0};\n  /// The scale (zoom factor) of the pan/zoom.\n  double scale = 1.0;\n  /// The amount the pan/zoom has rotated in radians so far.\n  double rotation = 0.0;\n\n  bool is_precise_scroll = true;\n\n  double source = kInputDeviceSourceTouchScreen;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_EVENT_GESTURE_EVENT_H_\n"
  },
  {
    "path": "clay/ui/event/gesture_event_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/event/gesture_event.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nnamespace testing {\n\nTEST(GestureEventTest, BasicTest) {\n  PointerEvent event(PointerEvent::EventType::kUnkownEvent);\n  EXPECT_EQ(event.type, PointerEvent::EventType::kUnkownEvent);\n\n  event.type = PointerEvent::EventType::kMoveEvent;\n  EXPECT_NE(event.type, PointerEvent::EventType::kUpEvent);\n  EXPECT_EQ(event.type, PointerEvent::EventType::kMoveEvent);\n\n  FloatPoint p1(1, 1);\n  FloatPoint p2(2, 3);\n  event.position = p1 + p2;\n  EXPECT_EQ(event.position, FloatPoint(3, 4));\n\n  FloatSize d1(0.1, 0.2);\n  d1 += FloatSize(0.01, 0.02);\n  event.delta = d1;\n  EXPECT_EQ(event.delta, FloatSize(0.11, 0.22));\n}\n\n}  // namespace testing\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/event/key_code_converter.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/event/key_code_converter.h\"\n\n#include <sstream>\n#include <unordered_map>\n\n#include \"clay/ui/event/key_codes.h\"\n\nnamespace clay {\n\nusing namespace keycodes;  // NOLINT\n\nnamespace {  // anonymous namespace to force internal linkage\n\nconst std::unordered_map<uint64_t, const char*> logical_key2web_key = {\n    // translate from kWebToLogicalKey\n    {0x00100000d08, \"AVRInput\"},\n    {0x00100000d09, \"AVRPower\"},\n    {0x00100000101, \"Accel\"},\n    {0x00100000501, \"Accept\"},\n    {0x00100000502, \"Again\"},\n    {0x00100000701, \"AllCandidates\"},\n    {0x00100000702, \"Alphanumeric\"},\n    {0x00100000103, \"AltGraph\"},\n    {0x00100001001, \"AppSwitch\"},\n    {0x00100000301, \"ArrowDown\"},\n    {0x00100000302, \"ArrowLeft\"},\n    {0x00100000303, \"ArrowRight\"},\n    {0x00100000304, \"ArrowUp\"},\n    {0x00100000503, \"Attn\"},\n    {0x00100000d01, \"AudioBalanceLeft\"},\n    {0x00100000d02, \"AudioBalanceRight\"},\n    {0x00100000d03, \"AudioBassBoostDown\"},\n    {0x00100000e02, \"AudioBassBoostToggle\"},\n    {0x00100000d04, \"AudioBassBoostUp\"},\n    {0x00100000d05, \"AudioFaderFront\"},\n    {0x00100000d06, \"AudioFaderRear\"},\n    {0x00100000d07, \"AudioSurroundModeNext\"},\n    {0x00100000e04, \"AudioTrebleDown\"},\n    {0x00100000e05, \"AudioTrebleUp\"},\n    {0x00100000a0f, \"AudioVolumeDown\"},\n    {0x00100000a11, \"AudioVolumeMute\"},\n    {0x00100000a10, \"AudioVolumeUp\"},\n    {0x00100000008, \"Backspace\"},\n    {0x00100000601, \"BrightnessDown\"},\n    {0x00100000602, \"BrightnessUp\"},\n    {0x00100000c01, \"BrowserBack\"},\n    {0x00100000c02, \"BrowserFavorites\"},\n    {0x00100000c03, \"BrowserForward\"},\n    {0x00100000c04, \"BrowserHome\"},\n    {0x00100000c05, \"BrowserRefresh\"},\n    {0x00100000c06, \"BrowserSearch\"},\n    {0x00100000c07, \"BrowserStop\"},\n    {0x00100001002, \"Call\"},\n    {0x00100000603, \"Camera\"},\n    {0x00100001003, \"CameraFocus\"},\n    {0x00100000504, \"Cancel\"},\n    {0x00100000104, \"CapsLock\"},\n    {0x00100000d0a, \"ChannelDown\"},\n    {0x00100000d0b, \"ChannelUp\"},\n    {0x00100000401, \"Clear\"},\n    {0x00100000a01, \"Close\"},\n    {0x00100000d12, \"ClosedCaptionToggle\"},\n    {0x00100000703, \"CodeInput\"},\n    {0x00100000d0c, \"ColorF0Red\"},\n    {0x00100000d0d, \"ColorF1Green\"},\n    {0x00100000d0e, \"ColorF2Yellow\"},\n    {0x00100000d0f, \"ColorF3Blue\"},\n    {0x00100000d10, \"ColorF4Grey\"},\n    {0x00100000d11, \"ColorF5Brown\"},\n    {0x00100000704, \"Compose\"},\n    {0x00100000505, \"ContextMenu\"},\n    {0x00100000705, \"Convert\"},\n    {0x00100000402, \"Copy\"},\n    {0x00100000403, \"CrSel\"},\n    {0x00100000404, \"Cut\"},\n    {0x00100000d4f, \"DVR\"},\n    {0x0010000007f, \"Delete\"},\n    {0x00100000d13, \"Dimmer\"},\n    {0x00100000d14, \"DisplaySwap\"},\n    {0x00100000714, \"Eisu\"},\n    {0x00100000604, \"Eject\"},\n    {0x00100000305, \"End\"},\n    {0x00100001004, \"EndCall\"},\n    {0x0010000000d, \"Enter\"},\n    {0x00100000405, \"EraseEof\"},\n    {0x0010000001b, \"Escape\"},\n    {0x00100000406, \"ExSel\"},\n    {0x00100000506, \"Execute\"},\n    {0x00100000d15, \"Exit\"},\n    {0x00100000801, \"F1\"},\n    {0x0010000080a, \"F10\"},\n    {0x0010000080b, \"F11\"},\n    {0x0010000080c, \"F12\"},\n    {0x0010000080d, \"F13\"},\n    {0x0010000080e, \"F14\"},\n    {0x0010000080f, \"F15\"},\n    {0x00100000810, \"F16\"},\n    {0x00100000811, \"F17\"},\n    {0x00100000812, \"F18\"},\n    {0x00100000813, \"F19\"},\n    {0x00100000802, \"F2\"},\n    {0x00100000814, \"F20\"},\n    {0x00100000815, \"F21\"},\n    {0x00100000816, \"F22\"},\n    {0x00100000817, \"F23\"},\n    {0x00100000818, \"F24\"},\n    {0x00100000803, \"F3\"},\n    {0x00100000804, \"F4\"},\n    {0x00100000805, \"F5\"},\n    {0x00100000806, \"F6\"},\n    {0x00100000807, \"F7\"},\n    {0x00100000808, \"F8\"},\n    {0x00100000809, \"F9\"},\n    {0x00100000d16, \"FavoriteClear0\"},\n    {0x00100000d17, \"FavoriteClear1\"},\n    {0x00100000d18, \"FavoriteClear2\"},\n    {0x00100000d19, \"FavoriteClear3\"},\n    {0x00100000d1a, \"FavoriteRecall0\"},\n    {0x00100000d1b, \"FavoriteRecall1\"},\n    {0x00100000d1c, \"FavoriteRecall2\"},\n    {0x00100000d1d, \"FavoriteRecall3\"},\n    {0x00100000d1e, \"FavoriteStore0\"},\n    {0x00100000d1f, \"FavoriteStore1\"},\n    {0x00100000d20, \"FavoriteStore2\"},\n    {0x00100000d21, \"FavoriteStore3\"},\n    {0x00100000706, \"FinalMode\"},\n    {0x00100000507, \"Find\"},\n    {0x00100000106, \"Fn\"},\n    {0x00100000107, \"FnLock\"},\n    {0x00100001005, \"GoBack\"},\n    {0x00100001006, \"GoHome\"},\n    {0x00100000707, \"GroupFirst\"},\n    {0x00100000708, \"GroupLast\"},\n    {0x00100000709, \"GroupNext\"},\n    {0x0010000070a, \"GroupPrevious\"},\n    {0x00100000d22, \"Guide\"},\n    {0x00100000d23, \"GuideNextDay\"},\n    {0x00100000d24, \"GuidePreviousDay\"},\n    {0x00100000711, \"HangulMode\"},\n    {0x00100000712, \"HanjaMode\"},\n    {0x00100000715, \"Hankaku\"},\n    {0x00100001007, \"HeadsetHook\"},\n    {0x00100000508, \"Help\"},\n    {0x00100000609, \"Hibernate\"},\n    {0x00100000716, \"Hiragana\"},\n    {0x00100000717, \"HiraganaKatakana\"},\n    {0x00100000306, \"Home\"},\n    {0x00100000108, \"Hyper\"},\n    {0x00100000d25, \"Info\"},\n    {0x00100000407, \"Insert\"},\n    {0x00100000d26, \"InstantReplay\"},\n    {0x00100000713, \"JunjaMode\"},\n    {0x00100000718, \"KanaMode\"},\n    {0x00100000719, \"KanjiMode\"},\n    {0x0010000071a, \"Katakana\"},\n    {0x00100001201, \"Key11\"},\n    {0x00100001202, \"Key12\"},\n    {0x00100001008, \"LastNumberRedial\"},\n    {0x00100000b06, \"LaunchApplication1\"},\n    {0x00100000b01, \"LaunchApplication2\"},\n    {0x00100000b0e, \"LaunchAssistant\"},\n    {0x00100000b02, \"LaunchCalendar\"},\n    {0x00100000b0c, \"LaunchContacts\"},\n    {0x00100000b0f, \"LaunchControlPanel\"},\n    {0x00100000b03, \"LaunchMail\"},\n    {0x00100000b04, \"LaunchMediaPlayer\"},\n    {0x00100000b05, \"LaunchMusicPlayer\"},\n    {0x00100000b0d, \"LaunchPhone\"},\n    {0x00100000b07, \"LaunchScreenSaver\"},\n    {0x00100000b08, \"LaunchSpreadsheet\"},\n    {0x00100000b09, \"LaunchWebBrowser\"},\n    {0x00100000b0a, \"LaunchWebCam\"},\n    {0x00100000b0b, \"LaunchWordProcessor\"},\n    {0x00100000d27, \"Link\"},\n    {0x00100000d28, \"ListProgram\"},\n    {0x00100000d29, \"LiveContent\"},\n    {0x00100000d2a, \"Lock\"},\n    {0x00100000605, \"LogOff\"},\n    {0x00100000a02, \"MailForward\"},\n    {0x00100000a03, \"MailReply\"},\n    {0x00100000a04, \"MailSend\"},\n    {0x0010000100a, \"MannerMode\"},\n    {0x00100000d2b, \"MediaApps\"},\n    {0x00100000d50, \"MediaAudioTrack\"},\n    {0x00100000d5b, \"MediaClose\"},\n    {0x00100000d2c, \"MediaFastForward\"},\n    {0x00100000d2d, \"MediaLast\"},\n    {0x00100000d2e, \"MediaPause\"},\n    {0x00100000d2f, \"MediaPlay\"},\n    {0x00100000a05, \"MediaPlayPause\"},\n    {0x00100000d30, \"MediaRecord\"},\n    {0x00100000d31, \"MediaRewind\"},\n    {0x00100000d32, \"MediaSkip\"},\n    {0x00100000d51, \"MediaSkipBackward\"},\n    {0x00100000d52, \"MediaSkipForward\"},\n    {0x00100000d53, \"MediaStepBackward\"},\n    {0x00100000d54, \"MediaStepForward\"},\n    {0x00100000a07, \"MediaStop\"},\n    {0x00100000d55, \"MediaTopMenu\"},\n    {0x00100000a08, \"MediaTrackNext\"},\n    {0x00100000a09, \"MediaTrackPrevious\"},\n    {0x00100000e06, \"MicrophoneToggle\"},\n    {0x00100000e07, \"MicrophoneVolumeDown\"},\n    {0x00100000e09, \"MicrophoneVolumeMute\"},\n    {0x00100000e08, \"MicrophoneVolumeUp\"},\n    {0x0010000070b, \"ModeChange\"},\n    {0x00100000d56, \"NavigateIn\"},\n    {0x00100000d57, \"NavigateNext\"},\n    {0x00100000d58, \"NavigateOut\"},\n    {0x00100000d59, \"NavigatePrevious\"},\n    {0x00100000a0a, \"New\"},\n    {0x0010000070c, \"NextCandidate\"},\n    {0x00100000d33, \"NextFavoriteChannel\"},\n    {0x00100000d34, \"NextUserProfile\"},\n    {0x0010000070d, \"NonConvert\"},\n    {0x00100001009, \"Notification\"},\n    {0x0010000010a, \"NumLock\"},\n    {0x00100000d35, \"OnDemand\"},\n    {0x00100000a0b, \"Open\"},\n    {0x00100000307, \"PageDown\"},\n    {0x00100000308, \"PageUp\"},\n    {0x00100000d5a, \"Pairing\"},\n    {0x00100000408, \"Paste\"},\n    {0x00100000509, \"Pause\"},\n    {0x00100000d36, \"PinPDown\"},\n    {0x00100000d37, \"PinPMove\"},\n    {0x00100000d38, \"PinPToggle\"},\n    {0x00100000d39, \"PinPUp\"},\n    {0x0010000050a, \"Play\"},\n    {0x00100000d3a, \"PlaySpeedDown\"},\n    {0x00100000d3b, \"PlaySpeedReset\"},\n    {0x00100000d3c, \"PlaySpeedUp\"},\n    {0x00100000606, \"Power\"},\n    {0x00100000607, \"PowerOff\"},\n    {0x0010000070e, \"PreviousCandidate\"},\n    {0x00100000a0c, \"Print\"},\n    {0x00100000608, \"PrintScreen\"},\n    {0x0010000070f, \"Process\"},\n    {0x0010000050b, \"Props\"},\n    {0x00100000d3d, \"RandomToggle\"},\n    {0x00100000d3e, \"RcLowBattery\"},\n    {0x00100000d3f, \"RecordSpeedNext\"},\n    {0x00100000409, \"Redo\"},\n    {0x00100000d40, \"RfBypass\"},\n    {0x0010000071b, \"Romaji\"},\n    {0x00100000d45, \"STBInput\"},\n    {0x00100000d46, \"STBPower\"},\n    {0x00100000a0d, \"Save\"},\n    {0x00100000d41, \"ScanChannelsToggle\"},\n    {0x00100000d42, \"ScreenModeNext\"},\n    {0x0010000010c, \"ScrollLock\"},\n    {0x0010000050c, \"Select\"},\n    {0x00100000d43, \"Settings\"},\n    {0x00100000111, \"ShiftLevel5\"},\n    {0x00100000710, \"SingleCandidate\"},\n    {0x00100000901, \"Soft1\"},\n    {0x00100000902, \"Soft2\"},\n    {0x00100000903, \"Soft3\"},\n    {0x00100000904, \"Soft4\"},\n    {0x00100000905, \"Soft5\"},\n    {0x00100000906, \"Soft6\"},\n    {0x00100000907, \"Soft7\"},\n    {0x00100000908, \"Soft8\"},\n    {0x00100000f01, \"SpeechCorrectionList\"},\n    {0x00100000f02, \"SpeechInputToggle\"},\n    {0x00100000a0e, \"SpellCheck\"},\n    {0x00100000d44, \"SplitScreenToggle\"},\n    {0x0010000060a, \"Standby\"},\n    {0x00100000d47, \"Subtitle\"},\n    {0x0010000010e, \"Super\"},\n    {0x0010000010f, \"Symbol\"},\n    {0x00100000110, \"SymbolLock\"},\n    {0x00100000d49, \"TV\"},\n    {0x00100001101, \"TV3DMode\"},\n    {0x00100001102, \"TVAntennaCable\"},\n    {0x00100001103, \"TVAudioDescription\"},\n    {0x00100001104, \"TVAudioDescriptionMixDown\"},\n    {0x00100001105, \"TVAudioDescriptionMixUp\"},\n    {0x00100001106, \"TVContentsMenu\"},\n    {0x00100001107, \"TVDataService\"},\n    {0x00100000d4a, \"TVInput\"},\n    {0x00100001108, \"TVInputComponent1\"},\n    {0x00100001109, \"TVInputComponent2\"},\n    {0x0010000110a, \"TVInputComposite1\"},\n    {0x0010000110b, \"TVInputComposite2\"},\n    {0x0010000110c, \"TVInputHDMI1\"},\n    {0x0010000110d, \"TVInputHDMI2\"},\n    {0x0010000110e, \"TVInputHDMI3\"},\n    {0x0010000110f, \"TVInputHDMI4\"},\n    {0x00100001110, \"TVInputVGA1\"},\n    {0x00100001111, \"TVMediaContext\"},\n    {0x00100001112, \"TVNetwork\"},\n    {0x00100001113, \"TVNumberEntry\"},\n    {0x00100000d4b, \"TVPower\"},\n    {0x00100001114, \"TVRadioService\"},\n    {0x00100001115, \"TVSatellite\"},\n    {0x00100001116, \"TVSatelliteBS\"},\n    {0x00100001117, \"TVSatelliteCS\"},\n    {0x00100001118, \"TVSatelliteToggle\"},\n    {0x00100001119, \"TVTerrestrialAnalog\"},\n    {0x0010000111a, \"TVTerrestrialDigital\"},\n    {0x0010000111b, \"TVTimer\"},\n    {0x00100000009, \"Tab\"},\n    {0x00100000d48, \"Teletext\"},\n    {0x0010000040a, \"Undo\"},\n    {0x00100000001, \"Unidentified\"},\n    {0x00100000d4c, \"VideoModeNext\"},\n    {0x0010000100b, \"VoiceDial\"},\n    {0x0010000060b, \"WakeUp\"},\n    {0x00100000d4d, \"Wink\"},\n    {0x0010000071c, \"Zenkaku\"},\n    {0x0010000071d, \"ZenkakuHankaku\"},\n    {0x0010000050d, \"ZoomIn\"},\n    {0x0010000050e, \"ZoomOut\"},\n    {0x00100000d4e, \"ZoomToggle\"},\n\n    // translate from kWebLogicalLocationMap\n    {kLogicalShiftLeft, \"Shift\"},\n    {kLogicalShiftRight, \"Shift\"},\n    {kLogicalAltLeft, \"Alt\"},\n    {kLogicalAltRight, \"Alt\"},\n    {kLogicalControlLeft, \"Control\"},\n    {kLogicalControlRight, \"Control\"},\n    {kLogicalMetaLeft, \"Meta\"},\n    {kLogicalMetaRight, \"Meta\"},\n};\n\n}  // namespace\n\nstd::string KeyCodeConverter::ConvertToWebKey(LogicalKeyboardKey key_code,\n                                              const std::string& character) {\n  uint64_t logical = static_cast<uint64_t>(key_code);\n  auto iter = logical_key2web_key.find(logical);\n  if (iter != logical_key2web_key.end()) {  // key map\n    return iter->second;\n  } else if (!character.empty()) {  // visual characters\n    return character;\n  } else if (logical <= 0xFF) {  // visual characters\n    return std::string(1, logical);\n  } else {  // fallback, return logical key in hexadecimal form\n    std::stringstream ss;\n    ss << std::hex << \"0x\" << logical;\n    return ss.str();\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/event/key_code_converter.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_EVENT_KEY_CODE_CONVERTER_H_\n#define CLAY_UI_EVENT_KEY_CODE_CONVERTER_H_\n\n#include <string>\n\n#include \"clay/ui/event/keyboard_key.h\"\n\nnamespace clay {\nclass KeyCodeConverter {\n public:\n  // used for reporting keycode to lynx\n  // align with web standard KeyboardEvent.key\n  static std::string ConvertToWebKey(LogicalKeyboardKey key_code,\n                                     const std::string& character);\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_EVENT_KEY_CODE_CONVERTER_H_\n"
  },
  {
    "path": "clay/ui/event/key_codes.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_EVENT_KEY_CODES_H_\n#define CLAY_UI_EVENT_KEY_CODES_H_\n\n#include <cinttypes>\n\nnamespace clay {\n\nnamespace keycodes {\n\nconstexpr uint64_t kPhysicalHyper = 0x00000010;\nconstexpr uint64_t kPhysicalSuperKey = 0x00000011;\nconstexpr uint64_t kPhysicalFn = 0x00000012;\nconstexpr uint64_t kPhysicalFnLock = 0x00000013;\nconstexpr uint64_t kPhysicalSuspend = 0x00000014;\nconstexpr uint64_t kPhysicalResume = 0x00000015;\nconstexpr uint64_t kPhysicalTurbo = 0x00000016;\nconstexpr uint64_t kPhysicalPrivacyScreenToggle = 0x00000017;\nconstexpr uint64_t kPhysicalSleep = 0x00010082;\nconstexpr uint64_t kPhysicalWakeUp = 0x00010083;\nconstexpr uint64_t kPhysicalDisplayToggleIntExt = 0x000100b5;\nconstexpr uint64_t kPhysicalGameButton1 = 0x0005ff01;\nconstexpr uint64_t kPhysicalGameButton2 = 0x0005ff02;\nconstexpr uint64_t kPhysicalGameButton3 = 0x0005ff03;\nconstexpr uint64_t kPhysicalGameButton4 = 0x0005ff04;\nconstexpr uint64_t kPhysicalGameButton5 = 0x0005ff05;\nconstexpr uint64_t kPhysicalGameButton6 = 0x0005ff06;\nconstexpr uint64_t kPhysicalGameButton7 = 0x0005ff07;\nconstexpr uint64_t kPhysicalGameButton8 = 0x0005ff08;\nconstexpr uint64_t kPhysicalGameButton9 = 0x0005ff09;\nconstexpr uint64_t kPhysicalGameButton10 = 0x0005ff0a;\nconstexpr uint64_t kPhysicalGameButton11 = 0x0005ff0b;\nconstexpr uint64_t kPhysicalGameButton12 = 0x0005ff0c;\nconstexpr uint64_t kPhysicalGameButton13 = 0x0005ff0d;\nconstexpr uint64_t kPhysicalGameButton14 = 0x0005ff0e;\nconstexpr uint64_t kPhysicalGameButton15 = 0x0005ff0f;\nconstexpr uint64_t kPhysicalGameButton16 = 0x0005ff10;\nconstexpr uint64_t kPhysicalGameButtonA = 0x0005ff11;\nconstexpr uint64_t kPhysicalGameButtonB = 0x0005ff12;\nconstexpr uint64_t kPhysicalGameButtonC = 0x0005ff13;\nconstexpr uint64_t kPhysicalGameButtonLeft1 = 0x0005ff14;\nconstexpr uint64_t kPhysicalGameButtonLeft2 = 0x0005ff15;\nconstexpr uint64_t kPhysicalGameButtonMode = 0x0005ff16;\nconstexpr uint64_t kPhysicalGameButtonRight1 = 0x0005ff17;\nconstexpr uint64_t kPhysicalGameButtonRight2 = 0x0005ff18;\nconstexpr uint64_t kPhysicalGameButtonSelect = 0x0005ff19;\nconstexpr uint64_t kPhysicalGameButtonStart = 0x0005ff1a;\nconstexpr uint64_t kPhysicalGameButtonThumbLeft = 0x0005ff1b;\nconstexpr uint64_t kPhysicalGameButtonThumbRight = 0x0005ff1c;\nconstexpr uint64_t kPhysicalGameButtonX = 0x0005ff1d;\nconstexpr uint64_t kPhysicalGameButtonY = 0x0005ff1e;\nconstexpr uint64_t kPhysicalGameButtonZ = 0x0005ff1f;\nconstexpr uint64_t kPhysicalUsbReserved = 0x00070000;\nconstexpr uint64_t kPhysicalUsbErrorRollOver = 0x00070001;\nconstexpr uint64_t kPhysicalUsbPostFail = 0x00070002;\nconstexpr uint64_t kPhysicalUsbErrorUndefined = 0x00070003;\nconstexpr uint64_t kPhysicalKeyA = 0x00070004;\nconstexpr uint64_t kPhysicalKeyB = 0x00070005;\nconstexpr uint64_t kPhysicalKeyC = 0x00070006;\nconstexpr uint64_t kPhysicalKeyD = 0x00070007;\nconstexpr uint64_t kPhysicalKeyE = 0x00070008;\nconstexpr uint64_t kPhysicalKeyF = 0x00070009;\nconstexpr uint64_t kPhysicalKeyG = 0x0007000a;\nconstexpr uint64_t kPhysicalKeyH = 0x0007000b;\nconstexpr uint64_t kPhysicalKeyI = 0x0007000c;\nconstexpr uint64_t kPhysicalKeyJ = 0x0007000d;\nconstexpr uint64_t kPhysicalKeyK = 0x0007000e;\nconstexpr uint64_t kPhysicalKeyL = 0x0007000f;\nconstexpr uint64_t kPhysicalKeyM = 0x00070010;\nconstexpr uint64_t kPhysicalKeyN = 0x00070011;\nconstexpr uint64_t kPhysicalKeyO = 0x00070012;\nconstexpr uint64_t kPhysicalKeyP = 0x00070013;\nconstexpr uint64_t kPhysicalKeyQ = 0x00070014;\nconstexpr uint64_t kPhysicalKeyR = 0x00070015;\nconstexpr uint64_t kPhysicalKeyS = 0x00070016;\nconstexpr uint64_t kPhysicalKeyT = 0x00070017;\nconstexpr uint64_t kPhysicalKeyU = 0x00070018;\nconstexpr uint64_t kPhysicalKeyV = 0x00070019;\nconstexpr uint64_t kPhysicalKeyW = 0x0007001a;\nconstexpr uint64_t kPhysicalKeyX = 0x0007001b;\nconstexpr uint64_t kPhysicalKeyY = 0x0007001c;\nconstexpr uint64_t kPhysicalKeyZ = 0x0007001d;\nconstexpr uint64_t kPhysicalDigit1 = 0x0007001e;\nconstexpr uint64_t kPhysicalDigit2 = 0x0007001f;\nconstexpr uint64_t kPhysicalDigit3 = 0x00070020;\nconstexpr uint64_t kPhysicalDigit4 = 0x00070021;\nconstexpr uint64_t kPhysicalDigit5 = 0x00070022;\nconstexpr uint64_t kPhysicalDigit6 = 0x00070023;\nconstexpr uint64_t kPhysicalDigit7 = 0x00070024;\nconstexpr uint64_t kPhysicalDigit8 = 0x00070025;\nconstexpr uint64_t kPhysicalDigit9 = 0x00070026;\nconstexpr uint64_t kPhysicalDigit0 = 0x00070027;\nconstexpr uint64_t kPhysicalEnter = 0x00070028;\nconstexpr uint64_t kPhysicalEscape = 0x00070029;\nconstexpr uint64_t kPhysicalBackspace = 0x0007002a;\nconstexpr uint64_t kPhysicalTab = 0x0007002b;\nconstexpr uint64_t kPhysicalSpace = 0x0007002c;\nconstexpr uint64_t kPhysicalMinus = 0x0007002d;\nconstexpr uint64_t kPhysicalEqual = 0x0007002e;\nconstexpr uint64_t kPhysicalBracketLeft = 0x0007002f;\nconstexpr uint64_t kPhysicalBracketRight = 0x00070030;\nconstexpr uint64_t kPhysicalBackslash = 0x00070031;\nconstexpr uint64_t kPhysicalSemicolon = 0x00070033;\nconstexpr uint64_t kPhysicalQuote = 0x00070034;\nconstexpr uint64_t kPhysicalBackquote = 0x00070035;\nconstexpr uint64_t kPhysicalComma = 0x00070036;\nconstexpr uint64_t kPhysicalPeriod = 0x00070037;\nconstexpr uint64_t kPhysicalSlash = 0x00070038;\nconstexpr uint64_t kPhysicalCapsLock = 0x00070039;\nconstexpr uint64_t kPhysicalF1 = 0x0007003a;\nconstexpr uint64_t kPhysicalF2 = 0x0007003b;\nconstexpr uint64_t kPhysicalF3 = 0x0007003c;\nconstexpr uint64_t kPhysicalF4 = 0x0007003d;\nconstexpr uint64_t kPhysicalF5 = 0x0007003e;\nconstexpr uint64_t kPhysicalF6 = 0x0007003f;\nconstexpr uint64_t kPhysicalF7 = 0x00070040;\nconstexpr uint64_t kPhysicalF8 = 0x00070041;\nconstexpr uint64_t kPhysicalF9 = 0x00070042;\nconstexpr uint64_t kPhysicalF10 = 0x00070043;\nconstexpr uint64_t kPhysicalF11 = 0x00070044;\nconstexpr uint64_t kPhysicalF12 = 0x00070045;\nconstexpr uint64_t kPhysicalPrintScreen = 0x00070046;\nconstexpr uint64_t kPhysicalScrollLock = 0x00070047;\nconstexpr uint64_t kPhysicalPause = 0x00070048;\nconstexpr uint64_t kPhysicalInsert = 0x00070049;\nconstexpr uint64_t kPhysicalHome = 0x0007004a;\nconstexpr uint64_t kPhysicalPageUp = 0x0007004b;\nconstexpr uint64_t kPhysicalDelete = 0x0007004c;\nconstexpr uint64_t kPhysicalEnd = 0x0007004d;\nconstexpr uint64_t kPhysicalPageDown = 0x0007004e;\nconstexpr uint64_t kPhysicalArrowRight = 0x0007004f;\nconstexpr uint64_t kPhysicalArrowLeft = 0x00070050;\nconstexpr uint64_t kPhysicalArrowDown = 0x00070051;\nconstexpr uint64_t kPhysicalArrowUp = 0x00070052;\nconstexpr uint64_t kPhysicalNumLock = 0x00070053;\nconstexpr uint64_t kPhysicalNumpadDivide = 0x00070054;\nconstexpr uint64_t kPhysicalNumpadMultiply = 0x00070055;\nconstexpr uint64_t kPhysicalNumpadSubtract = 0x00070056;\nconstexpr uint64_t kPhysicalNumpadAdd = 0x00070057;\nconstexpr uint64_t kPhysicalNumpadEnter = 0x00070058;\nconstexpr uint64_t kPhysicalNumpad1 = 0x00070059;\nconstexpr uint64_t kPhysicalNumpad2 = 0x0007005a;\nconstexpr uint64_t kPhysicalNumpad3 = 0x0007005b;\nconstexpr uint64_t kPhysicalNumpad4 = 0x0007005c;\nconstexpr uint64_t kPhysicalNumpad5 = 0x0007005d;\nconstexpr uint64_t kPhysicalNumpad6 = 0x0007005e;\nconstexpr uint64_t kPhysicalNumpad7 = 0x0007005f;\nconstexpr uint64_t kPhysicalNumpad8 = 0x00070060;\nconstexpr uint64_t kPhysicalNumpad9 = 0x00070061;\nconstexpr uint64_t kPhysicalNumpad0 = 0x00070062;\nconstexpr uint64_t kPhysicalNumpadDecimal = 0x00070063;\nconstexpr uint64_t kPhysicalIntlBackslash = 0x00070064;\nconstexpr uint64_t kPhysicalContextMenu = 0x00070065;\nconstexpr uint64_t kPhysicalPower = 0x00070066;\nconstexpr uint64_t kPhysicalNumpadEqual = 0x00070067;\nconstexpr uint64_t kPhysicalF13 = 0x00070068;\nconstexpr uint64_t kPhysicalF14 = 0x00070069;\nconstexpr uint64_t kPhysicalF15 = 0x0007006a;\nconstexpr uint64_t kPhysicalF16 = 0x0007006b;\nconstexpr uint64_t kPhysicalF17 = 0x0007006c;\nconstexpr uint64_t kPhysicalF18 = 0x0007006d;\nconstexpr uint64_t kPhysicalF19 = 0x0007006e;\nconstexpr uint64_t kPhysicalF20 = 0x0007006f;\nconstexpr uint64_t kPhysicalF21 = 0x00070070;\nconstexpr uint64_t kPhysicalF22 = 0x00070071;\nconstexpr uint64_t kPhysicalF23 = 0x00070072;\nconstexpr uint64_t kPhysicalF24 = 0x00070073;\nconstexpr uint64_t kPhysicalOpen = 0x00070074;\nconstexpr uint64_t kPhysicalHelp = 0x00070075;\nconstexpr uint64_t kPhysicalSelect = 0x00070077;\nconstexpr uint64_t kPhysicalAgain = 0x00070079;\nconstexpr uint64_t kPhysicalUndo = 0x0007007a;\nconstexpr uint64_t kPhysicalCut = 0x0007007b;\nconstexpr uint64_t kPhysicalCopy = 0x0007007c;\nconstexpr uint64_t kPhysicalPaste = 0x0007007d;\nconstexpr uint64_t kPhysicalFind = 0x0007007e;\nconstexpr uint64_t kPhysicalAudioVolumeMute = 0x0007007f;\nconstexpr uint64_t kPhysicalAudioVolumeUp = 0x00070080;\nconstexpr uint64_t kPhysicalAudioVolumeDown = 0x00070081;\nconstexpr uint64_t kPhysicalNumpadComma = 0x00070085;\nconstexpr uint64_t kPhysicalIntlRo = 0x00070087;\nconstexpr uint64_t kPhysicalKanaMode = 0x00070088;\nconstexpr uint64_t kPhysicalIntlYen = 0x00070089;\nconstexpr uint64_t kPhysicalConvert = 0x0007008a;\nconstexpr uint64_t kPhysicalNonConvert = 0x0007008b;\nconstexpr uint64_t kPhysicalLang1 = 0x00070090;\nconstexpr uint64_t kPhysicalLang2 = 0x00070091;\nconstexpr uint64_t kPhysicalLang3 = 0x00070092;\nconstexpr uint64_t kPhysicalLang4 = 0x00070093;\nconstexpr uint64_t kPhysicalLang5 = 0x00070094;\nconstexpr uint64_t kPhysicalAbort = 0x0007009b;\nconstexpr uint64_t kPhysicalProps = 0x000700a3;\nconstexpr uint64_t kPhysicalNumpadParenLeft = 0x000700b6;\nconstexpr uint64_t kPhysicalNumpadParenRight = 0x000700b7;\nconstexpr uint64_t kPhysicalNumpadBackspace = 0x000700bb;\nconstexpr uint64_t kPhysicalNumpadMemoryStore = 0x000700d0;\nconstexpr uint64_t kPhysicalNumpadMemoryRecall = 0x000700d1;\nconstexpr uint64_t kPhysicalNumpadMemoryClear = 0x000700d2;\nconstexpr uint64_t kPhysicalNumpadMemoryAdd = 0x000700d3;\nconstexpr uint64_t kPhysicalNumpadMemorySubtract = 0x000700d4;\nconstexpr uint64_t kPhysicalNumpadSignChange = 0x000700d7;\nconstexpr uint64_t kPhysicalNumpadClear = 0x000700d8;\nconstexpr uint64_t kPhysicalNumpadClearEntry = 0x000700d9;\nconstexpr uint64_t kPhysicalControlLeft = 0x000700e0;\nconstexpr uint64_t kPhysicalShiftLeft = 0x000700e1;\nconstexpr uint64_t kPhysicalAltLeft = 0x000700e2;\nconstexpr uint64_t kPhysicalMetaLeft = 0x000700e3;\nconstexpr uint64_t kPhysicalControlRight = 0x000700e4;\nconstexpr uint64_t kPhysicalShiftRight = 0x000700e5;\nconstexpr uint64_t kPhysicalAltRight = 0x000700e6;\nconstexpr uint64_t kPhysicalMetaRight = 0x000700e7;\nconstexpr uint64_t kPhysicalInfo = 0x000c0060;\nconstexpr uint64_t kPhysicalClosedCaptionToggle = 0x000c0061;\nconstexpr uint64_t kPhysicalBrightnessUp = 0x000c006f;\nconstexpr uint64_t kPhysicalBrightnessDown = 0x000c0070;\nconstexpr uint64_t kPhysicalBrightnessToggle = 0x000c0072;\nconstexpr uint64_t kPhysicalBrightnessMinimum = 0x000c0073;\nconstexpr uint64_t kPhysicalBrightnessMaximum = 0x000c0074;\nconstexpr uint64_t kPhysicalBrightnessAuto = 0x000c0075;\nconstexpr uint64_t kPhysicalKbdIllumUp = 0x000c0079;\nconstexpr uint64_t kPhysicalKbdIllumDown = 0x000c007a;\nconstexpr uint64_t kPhysicalMediaLast = 0x000c0083;\nconstexpr uint64_t kPhysicalLaunchPhone = 0x000c008c;\nconstexpr uint64_t kPhysicalProgramGuide = 0x000c008d;\nconstexpr uint64_t kPhysicalExit = 0x000c0094;\nconstexpr uint64_t kPhysicalChannelUp = 0x000c009c;\nconstexpr uint64_t kPhysicalChannelDown = 0x000c009d;\nconstexpr uint64_t kPhysicalMediaPlay = 0x000c00b0;\nconstexpr uint64_t kPhysicalMediaPause = 0x000c00b1;\nconstexpr uint64_t kPhysicalMediaRecord = 0x000c00b2;\nconstexpr uint64_t kPhysicalMediaFastForward = 0x000c00b3;\nconstexpr uint64_t kPhysicalMediaRewind = 0x000c00b4;\nconstexpr uint64_t kPhysicalMediaTrackNext = 0x000c00b5;\nconstexpr uint64_t kPhysicalMediaTrackPrevious = 0x000c00b6;\nconstexpr uint64_t kPhysicalMediaStop = 0x000c00b7;\nconstexpr uint64_t kPhysicalEject = 0x000c00b8;\nconstexpr uint64_t kPhysicalMediaPlayPause = 0x000c00cd;\nconstexpr uint64_t kPhysicalSpeechInputToggle = 0x000c00cf;\nconstexpr uint64_t kPhysicalBassBoost = 0x000c00e5;\nconstexpr uint64_t kPhysicalMediaSelect = 0x000c0183;\nconstexpr uint64_t kPhysicalLaunchWordProcessor = 0x000c0184;\nconstexpr uint64_t kPhysicalLaunchSpreadsheet = 0x000c0186;\nconstexpr uint64_t kPhysicalLaunchMail = 0x000c018a;\nconstexpr uint64_t kPhysicalLaunchContacts = 0x000c018d;\nconstexpr uint64_t kPhysicalLaunchCalendar = 0x000c018e;\nconstexpr uint64_t kPhysicalLaunchApp2 = 0x000c0192;\nconstexpr uint64_t kPhysicalLaunchApp1 = 0x000c0194;\nconstexpr uint64_t kPhysicalLaunchInternetBrowser = 0x000c0196;\nconstexpr uint64_t kPhysicalLogOff = 0x000c019c;\nconstexpr uint64_t kPhysicalLockScreen = 0x000c019e;\nconstexpr uint64_t kPhysicalLaunchControlPanel = 0x000c019f;\nconstexpr uint64_t kPhysicalSelectTask = 0x000c01a2;\nconstexpr uint64_t kPhysicalLaunchDocuments = 0x000c01a7;\nconstexpr uint64_t kPhysicalSpellCheck = 0x000c01ab;\nconstexpr uint64_t kPhysicalLaunchKeyboardLayout = 0x000c01ae;\nconstexpr uint64_t kPhysicalLaunchScreenSaver = 0x000c01b1;\nconstexpr uint64_t kPhysicalLaunchAudioBrowser = 0x000c01b7;\nconstexpr uint64_t kPhysicalLaunchAssistant = 0x000c01cb;\nconstexpr uint64_t kPhysicalNewKey = 0x000c0201;\nconstexpr uint64_t kPhysicalClose = 0x000c0203;\nconstexpr uint64_t kPhysicalSave = 0x000c0207;\nconstexpr uint64_t kPhysicalPrint = 0x000c0208;\nconstexpr uint64_t kPhysicalBrowserSearch = 0x000c0221;\nconstexpr uint64_t kPhysicalBrowserHome = 0x000c0223;\nconstexpr uint64_t kPhysicalBrowserBack = 0x000c0224;\nconstexpr uint64_t kPhysicalBrowserForward = 0x000c0225;\nconstexpr uint64_t kPhysicalBrowserStop = 0x000c0226;\nconstexpr uint64_t kPhysicalBrowserRefresh = 0x000c0227;\nconstexpr uint64_t kPhysicalBrowserFavorites = 0x000c022a;\nconstexpr uint64_t kPhysicalZoomIn = 0x000c022d;\nconstexpr uint64_t kPhysicalZoomOut = 0x000c022e;\nconstexpr uint64_t kPhysicalZoomToggle = 0x000c0232;\nconstexpr uint64_t kPhysicalRedo = 0x000c0279;\nconstexpr uint64_t kPhysicalMailReply = 0x000c0289;\nconstexpr uint64_t kPhysicalMailForward = 0x000c028b;\nconstexpr uint64_t kPhysicalMailSend = 0x000c028c;\nconstexpr uint64_t kPhysicalKeyboardLayoutSelect = 0x000c029d;\nconstexpr uint64_t kPhysicalShowAllWindows = 0x000c029f;\n\nconstexpr uint64_t kLogicalSpace = 0x00000000020;\nconstexpr uint64_t kLogicalExclamation = 0x00000000021;\nconstexpr uint64_t kLogicalQuote = 0x00000000022;\nconstexpr uint64_t kLogicalNumberSign = 0x00000000023;\nconstexpr uint64_t kLogicalDollar = 0x00000000024;\nconstexpr uint64_t kLogicalPercent = 0x00000000025;\nconstexpr uint64_t kLogicalAmpersand = 0x00000000026;\nconstexpr uint64_t kLogicalQuoteSingle = 0x00000000027;\nconstexpr uint64_t kLogicalParenthesisLeft = 0x00000000028;\nconstexpr uint64_t kLogicalParenthesisRight = 0x00000000029;\nconstexpr uint64_t kLogicalAsterisk = 0x0000000002a;\nconstexpr uint64_t kLogicalAdd = 0x0000000002b;\nconstexpr uint64_t kLogicalComma = 0x0000000002c;\nconstexpr uint64_t kLogicalMinus = 0x0000000002d;\nconstexpr uint64_t kLogicalPeriod = 0x0000000002e;\nconstexpr uint64_t kLogicalSlash = 0x0000000002f;\nconstexpr uint64_t kLogicalDigit0 = 0x00000000030;\nconstexpr uint64_t kLogicalDigit1 = 0x00000000031;\nconstexpr uint64_t kLogicalDigit2 = 0x00000000032;\nconstexpr uint64_t kLogicalDigit3 = 0x00000000033;\nconstexpr uint64_t kLogicalDigit4 = 0x00000000034;\nconstexpr uint64_t kLogicalDigit5 = 0x00000000035;\nconstexpr uint64_t kLogicalDigit6 = 0x00000000036;\nconstexpr uint64_t kLogicalDigit7 = 0x00000000037;\nconstexpr uint64_t kLogicalDigit8 = 0x00000000038;\nconstexpr uint64_t kLogicalDigit9 = 0x00000000039;\nconstexpr uint64_t kLogicalColon = 0x0000000003a;\nconstexpr uint64_t kLogicalSemicolon = 0x0000000003b;\nconstexpr uint64_t kLogicalLess = 0x0000000003c;\nconstexpr uint64_t kLogicalEqual = 0x0000000003d;\nconstexpr uint64_t kLogicalGreater = 0x0000000003e;\nconstexpr uint64_t kLogicalQuestion = 0x0000000003f;\nconstexpr uint64_t kLogicalAt = 0x00000000040;\nconstexpr uint64_t kLogicalBracketLeft = 0x0000000005b;\nconstexpr uint64_t kLogicalBackslash = 0x0000000005c;\nconstexpr uint64_t kLogicalBracketRight = 0x0000000005d;\nconstexpr uint64_t kLogicalCaret = 0x0000000005e;\nconstexpr uint64_t kLogicalUnderscore = 0x0000000005f;\nconstexpr uint64_t kLogicalBackquote = 0x00000000060;\nconstexpr uint64_t kLogicalKeyA = 0x00000000061;\nconstexpr uint64_t kLogicalKeyB = 0x00000000062;\nconstexpr uint64_t kLogicalKeyC = 0x00000000063;\nconstexpr uint64_t kLogicalKeyD = 0x00000000064;\nconstexpr uint64_t kLogicalKeyE = 0x00000000065;\nconstexpr uint64_t kLogicalKeyF = 0x00000000066;\nconstexpr uint64_t kLogicalKeyG = 0x00000000067;\nconstexpr uint64_t kLogicalKeyH = 0x00000000068;\nconstexpr uint64_t kLogicalKeyI = 0x00000000069;\nconstexpr uint64_t kLogicalKeyJ = 0x0000000006a;\nconstexpr uint64_t kLogicalKeyK = 0x0000000006b;\nconstexpr uint64_t kLogicalKeyL = 0x0000000006c;\nconstexpr uint64_t kLogicalKeyM = 0x0000000006d;\nconstexpr uint64_t kLogicalKeyN = 0x0000000006e;\nconstexpr uint64_t kLogicalKeyO = 0x0000000006f;\nconstexpr uint64_t kLogicalKeyP = 0x00000000070;\nconstexpr uint64_t kLogicalKeyQ = 0x00000000071;\nconstexpr uint64_t kLogicalKeyR = 0x00000000072;\nconstexpr uint64_t kLogicalKeyS = 0x00000000073;\nconstexpr uint64_t kLogicalKeyT = 0x00000000074;\nconstexpr uint64_t kLogicalKeyU = 0x00000000075;\nconstexpr uint64_t kLogicalKeyV = 0x00000000076;\nconstexpr uint64_t kLogicalKeyW = 0x00000000077;\nconstexpr uint64_t kLogicalKeyX = 0x00000000078;\nconstexpr uint64_t kLogicalKeyY = 0x00000000079;\nconstexpr uint64_t kLogicalKeyZ = 0x0000000007a;\nconstexpr uint64_t kLogicalBraceLeft = 0x0000000007b;\nconstexpr uint64_t kLogicalBar = 0x0000000007c;\nconstexpr uint64_t kLogicalBraceRight = 0x0000000007d;\nconstexpr uint64_t kLogicalTilde = 0x0000000007e;\nconstexpr uint64_t kLogicalUnidentified = 0x00100000001;\nconstexpr uint64_t kLogicalBackspace = 0x00100000008;\nconstexpr uint64_t kLogicalTab = 0x00100000009;\nconstexpr uint64_t kLogicalEnter = 0x0010000000d;\nconstexpr uint64_t kLogicalEscape = 0x0010000001b;\nconstexpr uint64_t kLogicalDelete = 0x0010000007f;\nconstexpr uint64_t kLogicalAccel = 0x00100000101;\nconstexpr uint64_t kLogicalAltGraph = 0x00100000103;\nconstexpr uint64_t kLogicalCapsLock = 0x00100000104;\nconstexpr uint64_t kLogicalFn = 0x00100000106;\nconstexpr uint64_t kLogicalFnLock = 0x00100000107;\nconstexpr uint64_t kLogicalHyper = 0x00100000108;\nconstexpr uint64_t kLogicalNumLock = 0x0010000010a;\nconstexpr uint64_t kLogicalScrollLock = 0x0010000010c;\nconstexpr uint64_t kLogicalSuperKey = 0x0010000010e;\nconstexpr uint64_t kLogicalSymbol = 0x0010000010f;\nconstexpr uint64_t kLogicalSymbolLock = 0x00100000110;\nconstexpr uint64_t kLogicalShiftLevel5 = 0x00100000111;\nconstexpr uint64_t kLogicalArrowDown = 0x00100000301;\nconstexpr uint64_t kLogicalArrowLeft = 0x00100000302;\nconstexpr uint64_t kLogicalArrowRight = 0x00100000303;\nconstexpr uint64_t kLogicalArrowUp = 0x00100000304;\nconstexpr uint64_t kLogicalEnd = 0x00100000305;\nconstexpr uint64_t kLogicalHome = 0x00100000306;\nconstexpr uint64_t kLogicalPageDown = 0x00100000307;\nconstexpr uint64_t kLogicalPageUp = 0x00100000308;\nconstexpr uint64_t kLogicalClear = 0x00100000401;\nconstexpr uint64_t kLogicalCopy = 0x00100000402;\nconstexpr uint64_t kLogicalCrSel = 0x00100000403;\nconstexpr uint64_t kLogicalCut = 0x00100000404;\nconstexpr uint64_t kLogicalEraseEof = 0x00100000405;\nconstexpr uint64_t kLogicalExSel = 0x00100000406;\nconstexpr uint64_t kLogicalInsert = 0x00100000407;\nconstexpr uint64_t kLogicalPaste = 0x00100000408;\nconstexpr uint64_t kLogicalRedo = 0x00100000409;\nconstexpr uint64_t kLogicalUndo = 0x0010000040a;\nconstexpr uint64_t kLogicalAccept = 0x00100000501;\nconstexpr uint64_t kLogicalAgain = 0x00100000502;\nconstexpr uint64_t kLogicalAttn = 0x00100000503;\nconstexpr uint64_t kLogicalCancel = 0x00100000504;\nconstexpr uint64_t kLogicalContextMenu = 0x00100000505;\nconstexpr uint64_t kLogicalExecute = 0x00100000506;\nconstexpr uint64_t kLogicalFind = 0x00100000507;\nconstexpr uint64_t kLogicalHelp = 0x00100000508;\nconstexpr uint64_t kLogicalPause = 0x00100000509;\nconstexpr uint64_t kLogicalPlay = 0x0010000050a;\nconstexpr uint64_t kLogicalProps = 0x0010000050b;\nconstexpr uint64_t kLogicalSelect = 0x0010000050c;\nconstexpr uint64_t kLogicalZoomIn = 0x0010000050d;\nconstexpr uint64_t kLogicalZoomOut = 0x0010000050e;\nconstexpr uint64_t kLogicalBrightnessDown = 0x00100000601;\nconstexpr uint64_t kLogicalBrightnessUp = 0x00100000602;\nconstexpr uint64_t kLogicalCamera = 0x00100000603;\nconstexpr uint64_t kLogicalEject = 0x00100000604;\nconstexpr uint64_t kLogicalLogOff = 0x00100000605;\nconstexpr uint64_t kLogicalPower = 0x00100000606;\nconstexpr uint64_t kLogicalPowerOff = 0x00100000607;\nconstexpr uint64_t kLogicalPrintScreen = 0x00100000608;\nconstexpr uint64_t kLogicalHibernate = 0x00100000609;\nconstexpr uint64_t kLogicalStandby = 0x0010000060a;\nconstexpr uint64_t kLogicalWakeUp = 0x0010000060b;\nconstexpr uint64_t kLogicalAllCandidates = 0x00100000701;\nconstexpr uint64_t kLogicalAlphanumeric = 0x00100000702;\nconstexpr uint64_t kLogicalCodeInput = 0x00100000703;\nconstexpr uint64_t kLogicalCompose = 0x00100000704;\nconstexpr uint64_t kLogicalConvert = 0x00100000705;\nconstexpr uint64_t kLogicalFinalMode = 0x00100000706;\nconstexpr uint64_t kLogicalGroupFirst = 0x00100000707;\nconstexpr uint64_t kLogicalGroupLast = 0x00100000708;\nconstexpr uint64_t kLogicalGroupNext = 0x00100000709;\nconstexpr uint64_t kLogicalGroupPrevious = 0x0010000070a;\nconstexpr uint64_t kLogicalModeChange = 0x0010000070b;\nconstexpr uint64_t kLogicalNextCandidate = 0x0010000070c;\nconstexpr uint64_t kLogicalNonConvert = 0x0010000070d;\nconstexpr uint64_t kLogicalPreviousCandidate = 0x0010000070e;\nconstexpr uint64_t kLogicalProcess = 0x0010000070f;\nconstexpr uint64_t kLogicalSingleCandidate = 0x00100000710;\nconstexpr uint64_t kLogicalHangulMode = 0x00100000711;\nconstexpr uint64_t kLogicalHanjaMode = 0x00100000712;\nconstexpr uint64_t kLogicalJunjaMode = 0x00100000713;\nconstexpr uint64_t kLogicalEisu = 0x00100000714;\nconstexpr uint64_t kLogicalHankaku = 0x00100000715;\nconstexpr uint64_t kLogicalHiragana = 0x00100000716;\nconstexpr uint64_t kLogicalHiraganaKatakana = 0x00100000717;\nconstexpr uint64_t kLogicalKanaMode = 0x00100000718;\nconstexpr uint64_t kLogicalKanjiMode = 0x00100000719;\nconstexpr uint64_t kLogicalKatakana = 0x0010000071a;\nconstexpr uint64_t kLogicalRomaji = 0x0010000071b;\nconstexpr uint64_t kLogicalZenkaku = 0x0010000071c;\nconstexpr uint64_t kLogicalZenkakuHankaku = 0x0010000071d;\nconstexpr uint64_t kLogicalF1 = 0x00100000801;\nconstexpr uint64_t kLogicalF2 = 0x00100000802;\nconstexpr uint64_t kLogicalF3 = 0x00100000803;\nconstexpr uint64_t kLogicalF4 = 0x00100000804;\nconstexpr uint64_t kLogicalF5 = 0x00100000805;\nconstexpr uint64_t kLogicalF6 = 0x00100000806;\nconstexpr uint64_t kLogicalF7 = 0x00100000807;\nconstexpr uint64_t kLogicalF8 = 0x00100000808;\nconstexpr uint64_t kLogicalF9 = 0x00100000809;\nconstexpr uint64_t kLogicalF10 = 0x0010000080a;\nconstexpr uint64_t kLogicalF11 = 0x0010000080b;\nconstexpr uint64_t kLogicalF12 = 0x0010000080c;\nconstexpr uint64_t kLogicalF13 = 0x0010000080d;\nconstexpr uint64_t kLogicalF14 = 0x0010000080e;\nconstexpr uint64_t kLogicalF15 = 0x0010000080f;\nconstexpr uint64_t kLogicalF16 = 0x00100000810;\nconstexpr uint64_t kLogicalF17 = 0x00100000811;\nconstexpr uint64_t kLogicalF18 = 0x00100000812;\nconstexpr uint64_t kLogicalF19 = 0x00100000813;\nconstexpr uint64_t kLogicalF20 = 0x00100000814;\nconstexpr uint64_t kLogicalF21 = 0x00100000815;\nconstexpr uint64_t kLogicalF22 = 0x00100000816;\nconstexpr uint64_t kLogicalF23 = 0x00100000817;\nconstexpr uint64_t kLogicalF24 = 0x00100000818;\nconstexpr uint64_t kLogicalSoft1 = 0x00100000901;\nconstexpr uint64_t kLogicalSoft2 = 0x00100000902;\nconstexpr uint64_t kLogicalSoft3 = 0x00100000903;\nconstexpr uint64_t kLogicalSoft4 = 0x00100000904;\nconstexpr uint64_t kLogicalSoft5 = 0x00100000905;\nconstexpr uint64_t kLogicalSoft6 = 0x00100000906;\nconstexpr uint64_t kLogicalSoft7 = 0x00100000907;\nconstexpr uint64_t kLogicalSoft8 = 0x00100000908;\nconstexpr uint64_t kLogicalClose = 0x00100000a01;\nconstexpr uint64_t kLogicalMailForward = 0x00100000a02;\nconstexpr uint64_t kLogicalMailReply = 0x00100000a03;\nconstexpr uint64_t kLogicalMailSend = 0x00100000a04;\nconstexpr uint64_t kLogicalMediaPlayPause = 0x00100000a05;\nconstexpr uint64_t kLogicalMediaStop = 0x00100000a07;\nconstexpr uint64_t kLogicalMediaTrackNext = 0x00100000a08;\nconstexpr uint64_t kLogicalMediaTrackPrevious = 0x00100000a09;\nconstexpr uint64_t kLogicalNewKey = 0x00100000a0a;\nconstexpr uint64_t kLogicalOpen = 0x00100000a0b;\nconstexpr uint64_t kLogicalPrint = 0x00100000a0c;\nconstexpr uint64_t kLogicalSave = 0x00100000a0d;\nconstexpr uint64_t kLogicalSpellCheck = 0x00100000a0e;\nconstexpr uint64_t kLogicalAudioVolumeDown = 0x00100000a0f;\nconstexpr uint64_t kLogicalAudioVolumeUp = 0x00100000a10;\nconstexpr uint64_t kLogicalAudioVolumeMute = 0x00100000a11;\nconstexpr uint64_t kLogicalLaunchApplication2 = 0x00100000b01;\nconstexpr uint64_t kLogicalLaunchCalendar = 0x00100000b02;\nconstexpr uint64_t kLogicalLaunchMail = 0x00100000b03;\nconstexpr uint64_t kLogicalLaunchMediaPlayer = 0x00100000b04;\nconstexpr uint64_t kLogicalLaunchMusicPlayer = 0x00100000b05;\nconstexpr uint64_t kLogicalLaunchApplication1 = 0x00100000b06;\nconstexpr uint64_t kLogicalLaunchScreenSaver = 0x00100000b07;\nconstexpr uint64_t kLogicalLaunchSpreadsheet = 0x00100000b08;\nconstexpr uint64_t kLogicalLaunchWebBrowser = 0x00100000b09;\nconstexpr uint64_t kLogicalLaunchWebCam = 0x00100000b0a;\nconstexpr uint64_t kLogicalLaunchWordProcessor = 0x00100000b0b;\nconstexpr uint64_t kLogicalLaunchContacts = 0x00100000b0c;\nconstexpr uint64_t kLogicalLaunchPhone = 0x00100000b0d;\nconstexpr uint64_t kLogicalLaunchAssistant = 0x00100000b0e;\nconstexpr uint64_t kLogicalLaunchControlPanel = 0x00100000b0f;\nconstexpr uint64_t kLogicalBrowserBack = 0x00100000c01;\nconstexpr uint64_t kLogicalBrowserFavorites = 0x00100000c02;\nconstexpr uint64_t kLogicalBrowserForward = 0x00100000c03;\nconstexpr uint64_t kLogicalBrowserHome = 0x00100000c04;\nconstexpr uint64_t kLogicalBrowserRefresh = 0x00100000c05;\nconstexpr uint64_t kLogicalBrowserSearch = 0x00100000c06;\nconstexpr uint64_t kLogicalBrowserStop = 0x00100000c07;\nconstexpr uint64_t kLogicalAudioBalanceLeft = 0x00100000d01;\nconstexpr uint64_t kLogicalAudioBalanceRight = 0x00100000d02;\nconstexpr uint64_t kLogicalAudioBassBoostDown = 0x00100000d03;\nconstexpr uint64_t kLogicalAudioBassBoostUp = 0x00100000d04;\nconstexpr uint64_t kLogicalAudioFaderFront = 0x00100000d05;\nconstexpr uint64_t kLogicalAudioFaderRear = 0x00100000d06;\nconstexpr uint64_t kLogicalAudioSurroundModeNext = 0x00100000d07;\nconstexpr uint64_t kLogicalAvrInput = 0x00100000d08;\nconstexpr uint64_t kLogicalAvrPower = 0x00100000d09;\nconstexpr uint64_t kLogicalChannelDown = 0x00100000d0a;\nconstexpr uint64_t kLogicalChannelUp = 0x00100000d0b;\nconstexpr uint64_t kLogicalColorF0Red = 0x00100000d0c;\nconstexpr uint64_t kLogicalColorF1Green = 0x00100000d0d;\nconstexpr uint64_t kLogicalColorF2Yellow = 0x00100000d0e;\nconstexpr uint64_t kLogicalColorF3Blue = 0x00100000d0f;\nconstexpr uint64_t kLogicalColorF4Grey = 0x00100000d10;\nconstexpr uint64_t kLogicalColorF5Brown = 0x00100000d11;\nconstexpr uint64_t kLogicalClosedCaptionToggle = 0x00100000d12;\nconstexpr uint64_t kLogicalDimmer = 0x00100000d13;\nconstexpr uint64_t kLogicalDisplaySwap = 0x00100000d14;\nconstexpr uint64_t kLogicalExit = 0x00100000d15;\nconstexpr uint64_t kLogicalFavoriteClear0 = 0x00100000d16;\nconstexpr uint64_t kLogicalFavoriteClear1 = 0x00100000d17;\nconstexpr uint64_t kLogicalFavoriteClear2 = 0x00100000d18;\nconstexpr uint64_t kLogicalFavoriteClear3 = 0x00100000d19;\nconstexpr uint64_t kLogicalFavoriteRecall0 = 0x00100000d1a;\nconstexpr uint64_t kLogicalFavoriteRecall1 = 0x00100000d1b;\nconstexpr uint64_t kLogicalFavoriteRecall2 = 0x00100000d1c;\nconstexpr uint64_t kLogicalFavoriteRecall3 = 0x00100000d1d;\nconstexpr uint64_t kLogicalFavoriteStore0 = 0x00100000d1e;\nconstexpr uint64_t kLogicalFavoriteStore1 = 0x00100000d1f;\nconstexpr uint64_t kLogicalFavoriteStore2 = 0x00100000d20;\nconstexpr uint64_t kLogicalFavoriteStore3 = 0x00100000d21;\nconstexpr uint64_t kLogicalGuide = 0x00100000d22;\nconstexpr uint64_t kLogicalGuideNextDay = 0x00100000d23;\nconstexpr uint64_t kLogicalGuidePreviousDay = 0x00100000d24;\nconstexpr uint64_t kLogicalInfo = 0x00100000d25;\nconstexpr uint64_t kLogicalInstantReplay = 0x00100000d26;\nconstexpr uint64_t kLogicalLink = 0x00100000d27;\nconstexpr uint64_t kLogicalListProgram = 0x00100000d28;\nconstexpr uint64_t kLogicalLiveContent = 0x00100000d29;\nconstexpr uint64_t kLogicalLock = 0x00100000d2a;\nconstexpr uint64_t kLogicalMediaApps = 0x00100000d2b;\nconstexpr uint64_t kLogicalMediaFastForward = 0x00100000d2c;\nconstexpr uint64_t kLogicalMediaLast = 0x00100000d2d;\nconstexpr uint64_t kLogicalMediaPause = 0x00100000d2e;\nconstexpr uint64_t kLogicalMediaPlay = 0x00100000d2f;\nconstexpr uint64_t kLogicalMediaRecord = 0x00100000d30;\nconstexpr uint64_t kLogicalMediaRewind = 0x00100000d31;\nconstexpr uint64_t kLogicalMediaSkip = 0x00100000d32;\nconstexpr uint64_t kLogicalNextFavoriteChannel = 0x00100000d33;\nconstexpr uint64_t kLogicalNextUserProfile = 0x00100000d34;\nconstexpr uint64_t kLogicalOnDemand = 0x00100000d35;\nconstexpr uint64_t kLogicalPInPDown = 0x00100000d36;\nconstexpr uint64_t kLogicalPInPMove = 0x00100000d37;\nconstexpr uint64_t kLogicalPInPToggle = 0x00100000d38;\nconstexpr uint64_t kLogicalPInPUp = 0x00100000d39;\nconstexpr uint64_t kLogicalPlaySpeedDown = 0x00100000d3a;\nconstexpr uint64_t kLogicalPlaySpeedReset = 0x00100000d3b;\nconstexpr uint64_t kLogicalPlaySpeedUp = 0x00100000d3c;\nconstexpr uint64_t kLogicalRandomToggle = 0x00100000d3d;\nconstexpr uint64_t kLogicalRcLowBattery = 0x00100000d3e;\nconstexpr uint64_t kLogicalRecordSpeedNext = 0x00100000d3f;\nconstexpr uint64_t kLogicalRfBypass = 0x00100000d40;\nconstexpr uint64_t kLogicalScanChannelsToggle = 0x00100000d41;\nconstexpr uint64_t kLogicalScreenModeNext = 0x00100000d42;\nconstexpr uint64_t kLogicalSettings = 0x00100000d43;\nconstexpr uint64_t kLogicalSplitScreenToggle = 0x00100000d44;\nconstexpr uint64_t kLogicalStbInput = 0x00100000d45;\nconstexpr uint64_t kLogicalStbPower = 0x00100000d46;\nconstexpr uint64_t kLogicalSubtitle = 0x00100000d47;\nconstexpr uint64_t kLogicalTeletext = 0x00100000d48;\nconstexpr uint64_t kLogicalTv = 0x00100000d49;\nconstexpr uint64_t kLogicalTvInput = 0x00100000d4a;\nconstexpr uint64_t kLogicalTvPower = 0x00100000d4b;\nconstexpr uint64_t kLogicalVideoModeNext = 0x00100000d4c;\nconstexpr uint64_t kLogicalWink = 0x00100000d4d;\nconstexpr uint64_t kLogicalZoomToggle = 0x00100000d4e;\nconstexpr uint64_t kLogicalDvr = 0x00100000d4f;\nconstexpr uint64_t kLogicalMediaAudioTrack = 0x00100000d50;\nconstexpr uint64_t kLogicalMediaSkipBackward = 0x00100000d51;\nconstexpr uint64_t kLogicalMediaSkipForward = 0x00100000d52;\nconstexpr uint64_t kLogicalMediaStepBackward = 0x00100000d53;\nconstexpr uint64_t kLogicalMediaStepForward = 0x00100000d54;\nconstexpr uint64_t kLogicalMediaTopMenu = 0x00100000d55;\nconstexpr uint64_t kLogicalNavigateIn = 0x00100000d56;\nconstexpr uint64_t kLogicalNavigateNext = 0x00100000d57;\nconstexpr uint64_t kLogicalNavigateOut = 0x00100000d58;\nconstexpr uint64_t kLogicalNavigatePrevious = 0x00100000d59;\nconstexpr uint64_t kLogicalPairing = 0x00100000d5a;\nconstexpr uint64_t kLogicalMediaClose = 0x00100000d5b;\nconstexpr uint64_t kLogicalAudioBassBoostToggle = 0x00100000e02;\nconstexpr uint64_t kLogicalAudioTrebleDown = 0x00100000e04;\nconstexpr uint64_t kLogicalAudioTrebleUp = 0x00100000e05;\nconstexpr uint64_t kLogicalMicrophoneToggle = 0x00100000e06;\nconstexpr uint64_t kLogicalMicrophoneVolumeDown = 0x00100000e07;\nconstexpr uint64_t kLogicalMicrophoneVolumeUp = 0x00100000e08;\nconstexpr uint64_t kLogicalMicrophoneVolumeMute = 0x00100000e09;\nconstexpr uint64_t kLogicalSpeechCorrectionList = 0x00100000f01;\nconstexpr uint64_t kLogicalSpeechInputToggle = 0x00100000f02;\nconstexpr uint64_t kLogicalAppSwitch = 0x00100001001;\nconstexpr uint64_t kLogicalCall = 0x00100001002;\nconstexpr uint64_t kLogicalCameraFocus = 0x00100001003;\nconstexpr uint64_t kLogicalEndCall = 0x00100001004;\nconstexpr uint64_t kLogicalGoBack = 0x00100001005;\nconstexpr uint64_t kLogicalGoHome = 0x00100001006;\nconstexpr uint64_t kLogicalHeadsetHook = 0x00100001007;\nconstexpr uint64_t kLogicalLastNumberRedial = 0x00100001008;\nconstexpr uint64_t kLogicalNotification = 0x00100001009;\nconstexpr uint64_t kLogicalMannerMode = 0x0010000100a;\nconstexpr uint64_t kLogicalVoiceDial = 0x0010000100b;\nconstexpr uint64_t kLogicalTv3DMode = 0x00100001101;\nconstexpr uint64_t kLogicalTvAntennaCable = 0x00100001102;\nconstexpr uint64_t kLogicalTvAudioDescription = 0x00100001103;\nconstexpr uint64_t kLogicalTvAudioDescriptionMixDown = 0x00100001104;\nconstexpr uint64_t kLogicalTvAudioDescriptionMixUp = 0x00100001105;\nconstexpr uint64_t kLogicalTvContentsMenu = 0x00100001106;\nconstexpr uint64_t kLogicalTvDataService = 0x00100001107;\nconstexpr uint64_t kLogicalTvInputComponent1 = 0x00100001108;\nconstexpr uint64_t kLogicalTvInputComponent2 = 0x00100001109;\nconstexpr uint64_t kLogicalTvInputComposite1 = 0x0010000110a;\nconstexpr uint64_t kLogicalTvInputComposite2 = 0x0010000110b;\nconstexpr uint64_t kLogicalTvInputHDMI1 = 0x0010000110c;\nconstexpr uint64_t kLogicalTvInputHDMI2 = 0x0010000110d;\nconstexpr uint64_t kLogicalTvInputHDMI3 = 0x0010000110e;\nconstexpr uint64_t kLogicalTvInputHDMI4 = 0x0010000110f;\nconstexpr uint64_t kLogicalTvInputVGA1 = 0x00100001110;\nconstexpr uint64_t kLogicalTvMediaContext = 0x00100001111;\nconstexpr uint64_t kLogicalTvNetwork = 0x00100001112;\nconstexpr uint64_t kLogicalTvNumberEntry = 0x00100001113;\nconstexpr uint64_t kLogicalTvRadioService = 0x00100001114;\nconstexpr uint64_t kLogicalTvSatellite = 0x00100001115;\nconstexpr uint64_t kLogicalTvSatelliteBS = 0x00100001116;\nconstexpr uint64_t kLogicalTvSatelliteCS = 0x00100001117;\nconstexpr uint64_t kLogicalTvSatelliteToggle = 0x00100001118;\nconstexpr uint64_t kLogicalTvTerrestrialAnalog = 0x00100001119;\nconstexpr uint64_t kLogicalTvTerrestrialDigital = 0x0010000111a;\nconstexpr uint64_t kLogicalTvTimer = 0x0010000111b;\nconstexpr uint64_t kLogicalKey11 = 0x00100001201;\nconstexpr uint64_t kLogicalKey12 = 0x00100001202;\nconstexpr uint64_t kLogicalSuspend = 0x00200000000;\nconstexpr uint64_t kLogicalResume = 0x00200000001;\nconstexpr uint64_t kLogicalSleep = 0x00200000002;\nconstexpr uint64_t kLogicalAbort = 0x00200000003;\nconstexpr uint64_t kLogicalLang1 = 0x00200000010;\nconstexpr uint64_t kLogicalLang2 = 0x00200000011;\nconstexpr uint64_t kLogicalLang3 = 0x00200000012;\nconstexpr uint64_t kLogicalLang4 = 0x00200000013;\nconstexpr uint64_t kLogicalLang5 = 0x00200000014;\nconstexpr uint64_t kLogicalIntlBackslash = 0x00200000020;\nconstexpr uint64_t kLogicalIntlRo = 0x00200000021;\nconstexpr uint64_t kLogicalIntlYen = 0x00200000022;\nconstexpr uint64_t kLogicalControlLeft = 0x00200000100;\nconstexpr uint64_t kLogicalControlRight = 0x00200000101;\nconstexpr uint64_t kLogicalShiftLeft = 0x00200000102;\nconstexpr uint64_t kLogicalShiftRight = 0x00200000103;\nconstexpr uint64_t kLogicalAltLeft = 0x00200000104;\nconstexpr uint64_t kLogicalAltRight = 0x00200000105;\nconstexpr uint64_t kLogicalMetaLeft = 0x00200000106;\nconstexpr uint64_t kLogicalMetaRight = 0x00200000107;\nconstexpr uint64_t kLogicalControl = 0x002000001f0;\nconstexpr uint64_t kLogicalShift = 0x002000001f2;\nconstexpr uint64_t kLogicalAlt = 0x002000001f4;\nconstexpr uint64_t kLogicalMeta = 0x002000001f6;\nconstexpr uint64_t kLogicalNumpadEnter = 0x0020000020d;\nconstexpr uint64_t kLogicalNumpadParenLeft = 0x00200000228;\nconstexpr uint64_t kLogicalNumpadParenRight = 0x00200000229;\nconstexpr uint64_t kLogicalNumpadMultiply = 0x0020000022a;\nconstexpr uint64_t kLogicalNumpadAdd = 0x0020000022b;\nconstexpr uint64_t kLogicalNumpadComma = 0x0020000022c;\nconstexpr uint64_t kLogicalNumpadSubtract = 0x0020000022d;\nconstexpr uint64_t kLogicalNumpadDecimal = 0x0020000022e;\nconstexpr uint64_t kLogicalNumpadDivide = 0x0020000022f;\nconstexpr uint64_t kLogicalNumpad0 = 0x00200000230;\nconstexpr uint64_t kLogicalNumpad1 = 0x00200000231;\nconstexpr uint64_t kLogicalNumpad2 = 0x00200000232;\nconstexpr uint64_t kLogicalNumpad3 = 0x00200000233;\nconstexpr uint64_t kLogicalNumpad4 = 0x00200000234;\nconstexpr uint64_t kLogicalNumpad5 = 0x00200000235;\nconstexpr uint64_t kLogicalNumpad6 = 0x00200000236;\nconstexpr uint64_t kLogicalNumpad7 = 0x00200000237;\nconstexpr uint64_t kLogicalNumpad8 = 0x00200000238;\nconstexpr uint64_t kLogicalNumpad9 = 0x00200000239;\nconstexpr uint64_t kLogicalNumpadEqual = 0x0020000023d;\nconstexpr uint64_t kLogicalGameButton1 = 0x00200000301;\nconstexpr uint64_t kLogicalGameButton2 = 0x00200000302;\nconstexpr uint64_t kLogicalGameButton3 = 0x00200000303;\nconstexpr uint64_t kLogicalGameButton4 = 0x00200000304;\nconstexpr uint64_t kLogicalGameButton5 = 0x00200000305;\nconstexpr uint64_t kLogicalGameButton6 = 0x00200000306;\nconstexpr uint64_t kLogicalGameButton7 = 0x00200000307;\nconstexpr uint64_t kLogicalGameButton8 = 0x00200000308;\nconstexpr uint64_t kLogicalGameButton9 = 0x00200000309;\nconstexpr uint64_t kLogicalGameButton10 = 0x0020000030a;\nconstexpr uint64_t kLogicalGameButton11 = 0x0020000030b;\nconstexpr uint64_t kLogicalGameButton12 = 0x0020000030c;\nconstexpr uint64_t kLogicalGameButton13 = 0x0020000030d;\nconstexpr uint64_t kLogicalGameButton14 = 0x0020000030e;\nconstexpr uint64_t kLogicalGameButton15 = 0x0020000030f;\nconstexpr uint64_t kLogicalGameButton16 = 0x00200000310;\nconstexpr uint64_t kLogicalGameButtonA = 0x00200000311;\nconstexpr uint64_t kLogicalGameButtonB = 0x00200000312;\nconstexpr uint64_t kLogicalGameButtonC = 0x00200000313;\nconstexpr uint64_t kLogicalGameButtonLeft1 = 0x00200000314;\nconstexpr uint64_t kLogicalGameButtonLeft2 = 0x00200000315;\nconstexpr uint64_t kLogicalGameButtonMode = 0x00200000316;\nconstexpr uint64_t kLogicalGameButtonRight1 = 0x00200000317;\nconstexpr uint64_t kLogicalGameButtonRight2 = 0x00200000318;\nconstexpr uint64_t kLogicalGameButtonSelect = 0x00200000319;\nconstexpr uint64_t kLogicalGameButtonStart = 0x0020000031a;\nconstexpr uint64_t kLogicalGameButtonThumbLeft = 0x0020000031b;\nconstexpr uint64_t kLogicalGameButtonThumbRight = 0x0020000031c;\nconstexpr uint64_t kLogicalGameButtonX = 0x0020000031d;\nconstexpr uint64_t kLogicalGameButtonY = 0x0020000031e;\nconstexpr uint64_t kLogicalGameButtonZ = 0x0020000031f;\n\n}  // namespace keycodes\n\n}  // namespace clay\n\n#endif  // CLAY_UI_EVENT_KEY_CODES_H_\n"
  },
  {
    "path": "clay/ui/event/key_event.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/event/key_event.h\"\n\n#include <sstream>\n\nnamespace clay {\nnamespace {\nconst char* c_key_event_type_string[] = {\n    \"KeyDown\",\n    \"KeyUp\",\n    \"KeyRepeat\",\n};\n}  // namespace\n\nstd::string KeyEvent::ToString() const {\n  if (type_ < KeyEventType::kDown || type_ > KeyEventType::kRepeat) {\n    return \"InvalidKeyEvent\";\n  }\n  std::stringstream ss;\n  ss << \"KeyEvent{\" << c_key_event_type_string[static_cast<int>(type_)]\n     << \",Time=\" << timestamp_ << std::hex << \",PhysicalKey=0x\"\n     << static_cast<uint64_t>(physical_) << \",LogicalKey=0x\"\n     << static_cast<uint64_t>(logical_) << \",Character=\" << character_ << \"} \";\n  return ss.str();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/event/key_event.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_EVENT_KEY_EVENT_H_\n#define CLAY_UI_EVENT_KEY_EVENT_H_\n\n#include <stdint.h>\n\n#include <string>\n\n#include \"clay/ui/event/keyboard_key.h\"\n\nnamespace clay {\n\nenum class KeyEventType {\n  kDown = 0,\n  kUp,\n  kRepeat,\n  kCommitText,\n  kCommitComposingText,\n};\n\nclass KeyEvent {\n public:\n  KeyEvent(uint64_t timestamp, KeyEventType type, PhysicalKeyboardKey physical,\n           LogicalKeyboardKey logical, bool synthesized,\n           const std::string& character)\n      : timestamp_(timestamp),\n        type_(type),\n        physical_(physical),\n        logical_(logical),\n        synthesized_(synthesized),\n        character_(character) {}\n\n  KeyEvent(uint64_t timestamp, KeyEventType type, uint64_t physical,\n           uint64_t logical, bool synthesized, const std::string& character)\n      : KeyEvent(timestamp, type, static_cast<PhysicalKeyboardKey>(physical),\n                 static_cast<LogicalKeyboardKey>(logical), synthesized,\n                 character) {}\n\n  ~KeyEvent() = default;\n\n  uint64_t GetTimestamp() const { return timestamp_; }\n  KeyEventType GetType() const { return type_; }\n  PhysicalKeyboardKey GetPhysical() const { return physical_; }\n  LogicalKeyboardKey GetLogical() const { return logical_; }\n  bool GetSynthesized() const { return synthesized_; }\n  const std::string& GetCharacter() const { return character_; }\n  std::string ToString() const;\n\n private:\n  uint64_t timestamp_;\n  KeyEventType type_;\n  PhysicalKeyboardKey physical_;\n  LogicalKeyboardKey logical_;\n  // True if the event does not correspond to a native event.\n  //\n  // The value is 1 for true, and 0 for false.\n  bool synthesized_;\n  std::string character_;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_EVENT_KEY_EVENT_H_\n"
  },
  {
    "path": "clay/ui/event/keyboard_key.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_EVENT_KEYBOARD_KEY_H_\n#define CLAY_UI_EVENT_KEYBOARD_KEY_H_\n\n#include <cstdint>\n\n#include \"clay/ui/event/key_codes.h\"\n\nnamespace clay {\n\nenum class PhysicalKeyboardKey : uint64_t {\n  kUnknown = 0,\n  kHyper = keycodes::kPhysicalHyper,\n  kSuperKey = keycodes::kPhysicalSuperKey,\n  kFn = keycodes::kPhysicalFn,\n  kFnLock = keycodes::kPhysicalFnLock,\n  kSuspend = keycodes::kPhysicalSuspend,\n  kResume = keycodes::kPhysicalResume,\n  kTurbo = keycodes::kPhysicalTurbo,\n  kPrivacyScreenToggle = keycodes::kPhysicalPrivacyScreenToggle,\n  kSleep = keycodes::kPhysicalSleep,\n  kWakeUp = keycodes::kPhysicalWakeUp,\n  kDisplayToggleIntExt = keycodes::kPhysicalDisplayToggleIntExt,\n  kGameButton1 = keycodes::kPhysicalGameButton1,\n  kGameButton2 = keycodes::kPhysicalGameButton2,\n  kGameButton3 = keycodes::kPhysicalGameButton3,\n  kGameButton4 = keycodes::kPhysicalGameButton4,\n  kGameButton5 = keycodes::kPhysicalGameButton5,\n  kGameButton6 = keycodes::kPhysicalGameButton6,\n  kGameButton7 = keycodes::kPhysicalGameButton7,\n  kGameButton8 = keycodes::kPhysicalGameButton8,\n  kGameButton9 = keycodes::kPhysicalGameButton9,\n  kGameButton10 = keycodes::kPhysicalGameButton10,\n  kGameButton11 = keycodes::kPhysicalGameButton11,\n  kGameButton12 = keycodes::kPhysicalGameButton12,\n  kGameButton13 = keycodes::kPhysicalGameButton13,\n  kGameButton14 = keycodes::kPhysicalGameButton14,\n  kGameButton15 = keycodes::kPhysicalGameButton15,\n  kGameButton16 = keycodes::kPhysicalGameButton16,\n  kGameButtonA = keycodes::kPhysicalGameButtonA,\n  kGameButtonB = keycodes::kPhysicalGameButtonB,\n  kGameButtonC = keycodes::kPhysicalGameButtonC,\n  kGameButtonLeft1 = keycodes::kPhysicalGameButtonLeft1,\n  kGameButtonLeft2 = keycodes::kPhysicalGameButtonLeft2,\n  kGameButtonMode = keycodes::kPhysicalGameButtonMode,\n  kGameButtonRight1 = keycodes::kPhysicalGameButtonRight1,\n  kGameButtonRight2 = keycodes::kPhysicalGameButtonRight2,\n  kGameButtonSelect = keycodes::kPhysicalGameButtonSelect,\n  kGameButtonStart = keycodes::kPhysicalGameButtonStart,\n  kGameButtonThumbLeft = keycodes::kPhysicalGameButtonThumbLeft,\n  kGameButtonThumbRight = keycodes::kPhysicalGameButtonThumbRight,\n  kGameButtonX = keycodes::kPhysicalGameButtonX,\n  kGameButtonY = keycodes::kPhysicalGameButtonY,\n  kGameButtonZ = keycodes::kPhysicalGameButtonZ,\n  kUsbReserved = keycodes::kPhysicalUsbReserved,\n  kUsbErrorRollOver = keycodes::kPhysicalUsbErrorRollOver,\n  kUsbPostFail = keycodes::kPhysicalUsbPostFail,\n  kUsbErrorUndefined = keycodes::kPhysicalUsbErrorUndefined,\n  kKeyA = keycodes::kPhysicalKeyA,\n  kKeyB = keycodes::kPhysicalKeyB,\n  kKeyC = keycodes::kPhysicalKeyC,\n  kKeyD = keycodes::kPhysicalKeyD,\n  kKeyE = keycodes::kPhysicalKeyE,\n  kKeyF = keycodes::kPhysicalKeyF,\n  kKeyG = keycodes::kPhysicalKeyG,\n  kKeyH = keycodes::kPhysicalKeyH,\n  kKeyI = keycodes::kPhysicalKeyI,\n  kKeyJ = keycodes::kPhysicalKeyJ,\n  kKeyK = keycodes::kPhysicalKeyK,\n  kKeyL = keycodes::kPhysicalKeyL,\n  kKeyM = keycodes::kPhysicalKeyM,\n  kKeyN = keycodes::kPhysicalKeyN,\n  kKeyO = keycodes::kPhysicalKeyO,\n  kKeyP = keycodes::kPhysicalKeyP,\n  kKeyQ = keycodes::kPhysicalKeyQ,\n  kKeyR = keycodes::kPhysicalKeyR,\n  kKeyS = keycodes::kPhysicalKeyS,\n  kKeyT = keycodes::kPhysicalKeyT,\n  kKeyU = keycodes::kPhysicalKeyU,\n  kKeyV = keycodes::kPhysicalKeyV,\n  kKeyW = keycodes::kPhysicalKeyW,\n  kKeyX = keycodes::kPhysicalKeyX,\n  kKeyY = keycodes::kPhysicalKeyY,\n  kKeyZ = keycodes::kPhysicalKeyZ,\n  kDigit1 = keycodes::kPhysicalDigit1,\n  kDigit2 = keycodes::kPhysicalDigit2,\n  kDigit3 = keycodes::kPhysicalDigit3,\n  kDigit4 = keycodes::kPhysicalDigit4,\n  kDigit5 = keycodes::kPhysicalDigit5,\n  kDigit6 = keycodes::kPhysicalDigit6,\n  kDigit7 = keycodes::kPhysicalDigit7,\n  kDigit8 = keycodes::kPhysicalDigit8,\n  kDigit9 = keycodes::kPhysicalDigit9,\n  kDigit0 = keycodes::kPhysicalDigit0,\n  kEnter = keycodes::kPhysicalEnter,\n  kEscape = keycodes::kPhysicalEscape,\n  kBackspace = keycodes::kPhysicalBackspace,\n  kTab = keycodes::kPhysicalTab,\n  kSpace = keycodes::kPhysicalSpace,\n  kMinus = keycodes::kPhysicalMinus,\n  kEqual = keycodes::kPhysicalEqual,\n  kBracketLeft = keycodes::kPhysicalBracketLeft,\n  kBracketRight = keycodes::kPhysicalBracketRight,\n  kBackslash = keycodes::kPhysicalBackslash,\n  kSemicolon = keycodes::kPhysicalSemicolon,\n  kQuote = keycodes::kPhysicalQuote,\n  kBackquote = keycodes::kPhysicalBackquote,\n  kComma = keycodes::kPhysicalComma,\n  kPeriod = keycodes::kPhysicalPeriod,\n  kSlash = keycodes::kPhysicalSlash,\n  kCapsLock = keycodes::kPhysicalCapsLock,\n  kF1 = keycodes::kPhysicalF1,\n  kF2 = keycodes::kPhysicalF2,\n  kF3 = keycodes::kPhysicalF3,\n  kF4 = keycodes::kPhysicalF4,\n  kF5 = keycodes::kPhysicalF5,\n  kF6 = keycodes::kPhysicalF6,\n  kF7 = keycodes::kPhysicalF7,\n  kF8 = keycodes::kPhysicalF8,\n  kF9 = keycodes::kPhysicalF9,\n  kF10 = keycodes::kPhysicalF10,\n  kF11 = keycodes::kPhysicalF11,\n  kF12 = keycodes::kPhysicalF12,\n  kPrintScreen = keycodes::kPhysicalPrintScreen,\n  kScrollLock = keycodes::kPhysicalScrollLock,\n  kPause = keycodes::kPhysicalPause,\n  kInsert = keycodes::kPhysicalInsert,\n  kHome = keycodes::kPhysicalHome,\n  kPageUp = keycodes::kPhysicalPageUp,\n  kDelete = keycodes::kPhysicalDelete,\n  kEnd = keycodes::kPhysicalEnd,\n  kPageDown = keycodes::kPhysicalPageDown,\n  kArrowRight = keycodes::kPhysicalArrowRight,\n  kArrowLeft = keycodes::kPhysicalArrowLeft,\n  kArrowDown = keycodes::kPhysicalArrowDown,\n  kArrowUp = keycodes::kPhysicalArrowUp,\n  kNumLock = keycodes::kPhysicalNumLock,\n  kNumpadDivide = keycodes::kPhysicalNumpadDivide,\n  kNumpadMultiply = keycodes::kPhysicalNumpadMultiply,\n  kNumpadSubtract = keycodes::kPhysicalNumpadSubtract,\n  kNumpadAdd = keycodes::kPhysicalNumpadAdd,\n  kNumpadEnter = keycodes::kPhysicalNumpadEnter,\n  kNumpad1 = keycodes::kPhysicalNumpad1,\n  kNumpad2 = keycodes::kPhysicalNumpad2,\n  kNumpad3 = keycodes::kPhysicalNumpad3,\n  kNumpad4 = keycodes::kPhysicalNumpad4,\n  kNumpad5 = keycodes::kPhysicalNumpad5,\n  kNumpad6 = keycodes::kPhysicalNumpad6,\n  kNumpad7 = keycodes::kPhysicalNumpad7,\n  kNumpad8 = keycodes::kPhysicalNumpad8,\n  kNumpad9 = keycodes::kPhysicalNumpad9,\n  kNumpad0 = keycodes::kPhysicalNumpad0,\n  kNumpadDecimal = keycodes::kPhysicalNumpadDecimal,\n  kIntlBackslash = keycodes::kPhysicalIntlBackslash,\n  kContextMenu = keycodes::kPhysicalContextMenu,\n  kPower = keycodes::kPhysicalPower,\n  kNumpadEqual = keycodes::kPhysicalNumpadEqual,\n  kF13 = keycodes::kPhysicalF13,\n  kF14 = keycodes::kPhysicalF14,\n  kF15 = keycodes::kPhysicalF15,\n  kF16 = keycodes::kPhysicalF16,\n  kF17 = keycodes::kPhysicalF17,\n  kF18 = keycodes::kPhysicalF18,\n  kF19 = keycodes::kPhysicalF19,\n  kF20 = keycodes::kPhysicalF20,\n  kF21 = keycodes::kPhysicalF21,\n  kF22 = keycodes::kPhysicalF22,\n  kF23 = keycodes::kPhysicalF23,\n  kF24 = keycodes::kPhysicalF24,\n  kOpen = keycodes::kPhysicalOpen,\n  kHelp = keycodes::kPhysicalHelp,\n  kSelect = keycodes::kPhysicalSelect,\n  kAgain = keycodes::kPhysicalAgain,\n  kUndo = keycodes::kPhysicalUndo,\n  kCut = keycodes::kPhysicalCut,\n  kCopy = keycodes::kPhysicalCopy,\n  kPaste = keycodes::kPhysicalPaste,\n  kFind = keycodes::kPhysicalFind,\n  kAudioVolumeMute = keycodes::kPhysicalAudioVolumeMute,\n  kAudioVolumeUp = keycodes::kPhysicalAudioVolumeUp,\n  kAudioVolumeDown = keycodes::kPhysicalAudioVolumeDown,\n  kNumpadComma = keycodes::kPhysicalNumpadComma,\n  kIntlRo = keycodes::kPhysicalIntlRo,\n  kKanaMode = keycodes::kPhysicalKanaMode,\n  kIntlYen = keycodes::kPhysicalIntlYen,\n  kConvert = keycodes::kPhysicalConvert,\n  kNonConvert = keycodes::kPhysicalNonConvert,\n  kLang1 = keycodes::kPhysicalLang1,\n  kLang2 = keycodes::kPhysicalLang2,\n  kLang3 = keycodes::kPhysicalLang3,\n  kLang4 = keycodes::kPhysicalLang4,\n  kLang5 = keycodes::kPhysicalLang5,\n  kAbort = keycodes::kPhysicalAbort,\n  kProps = keycodes::kPhysicalProps,\n  kNumpadParenLeft = keycodes::kPhysicalNumpadParenLeft,\n  kNumpadParenRight = keycodes::kPhysicalNumpadParenRight,\n  kNumpadBackspace = keycodes::kPhysicalNumpadBackspace,\n  kNumpadMemoryStore = keycodes::kPhysicalNumpadMemoryStore,\n  kNumpadMemoryRecall = keycodes::kPhysicalNumpadMemoryRecall,\n  kNumpadMemoryClear = keycodes::kPhysicalNumpadMemoryClear,\n  kNumpadMemoryAdd = keycodes::kPhysicalNumpadMemoryAdd,\n  kNumpadMemorySubtract = keycodes::kPhysicalNumpadMemorySubtract,\n  kNumpadSignChange = keycodes::kPhysicalNumpadSignChange,\n  kNumpadClear = keycodes::kPhysicalNumpadClear,\n  kNumpadClearEntry = keycodes::kPhysicalNumpadClearEntry,\n  kControlLeft = keycodes::kPhysicalControlLeft,\n  kShiftLeft = keycodes::kPhysicalShiftLeft,\n  kAltLeft = keycodes::kPhysicalAltLeft,\n  kMetaLeft = keycodes::kPhysicalMetaLeft,\n  kControlRight = keycodes::kPhysicalControlRight,\n  kShiftRight = keycodes::kPhysicalShiftRight,\n  kAltRight = keycodes::kPhysicalAltRight,\n  kMetaRight = keycodes::kPhysicalMetaRight,\n  kInfo = keycodes::kPhysicalInfo,\n  kClosedCaptionToggle = keycodes::kPhysicalClosedCaptionToggle,\n  kBrightnessUp = keycodes::kPhysicalBrightnessUp,\n  kBrightnessDown = keycodes::kPhysicalBrightnessDown,\n  kBrightnessToggle = keycodes::kPhysicalBrightnessToggle,\n  kBrightnessMinimum = keycodes::kPhysicalBrightnessMinimum,\n  kBrightnessMaximum = keycodes::kPhysicalBrightnessMaximum,\n  kBrightnessAuto = keycodes::kPhysicalBrightnessAuto,\n  kKbdIllumUp = keycodes::kPhysicalKbdIllumUp,\n  kKbdIllumDown = keycodes::kPhysicalKbdIllumDown,\n  kMediaLast = keycodes::kPhysicalMediaLast,\n  kLaunchPhone = keycodes::kPhysicalLaunchPhone,\n  kProgramGuide = keycodes::kPhysicalProgramGuide,\n  kExit = keycodes::kPhysicalExit,\n  kChannelUp = keycodes::kPhysicalChannelUp,\n  kChannelDown = keycodes::kPhysicalChannelDown,\n  kMediaPlay = keycodes::kPhysicalMediaPlay,\n  kMediaPause = keycodes::kPhysicalMediaPause,\n  kMediaRecord = keycodes::kPhysicalMediaRecord,\n  kMediaFastForward = keycodes::kPhysicalMediaFastForward,\n  kMediaRewind = keycodes::kPhysicalMediaRewind,\n  kMediaTrackNext = keycodes::kPhysicalMediaTrackNext,\n  kMediaTrackPrevious = keycodes::kPhysicalMediaTrackPrevious,\n  kMediaStop = keycodes::kPhysicalMediaStop,\n  kEject = keycodes::kPhysicalEject,\n  kMediaPlayPause = keycodes::kPhysicalMediaPlayPause,\n  kSpeechInputToggle = keycodes::kPhysicalSpeechInputToggle,\n  kBassBoost = keycodes::kPhysicalBassBoost,\n  kMediaSelect = keycodes::kPhysicalMediaSelect,\n  kLaunchWordProcessor = keycodes::kPhysicalLaunchWordProcessor,\n  kLaunchSpreadsheet = keycodes::kPhysicalLaunchSpreadsheet,\n  kLaunchMail = keycodes::kPhysicalLaunchMail,\n  kLaunchContacts = keycodes::kPhysicalLaunchContacts,\n  kLaunchCalendar = keycodes::kPhysicalLaunchCalendar,\n  kLaunchApp2 = keycodes::kPhysicalLaunchApp2,\n  kLaunchApp1 = keycodes::kPhysicalLaunchApp1,\n  kLaunchInternetBrowser = keycodes::kPhysicalLaunchInternetBrowser,\n  kLogOff = keycodes::kPhysicalLogOff,\n  kLockScreen = keycodes::kPhysicalLockScreen,\n  kLaunchControlPanel = keycodes::kPhysicalLaunchControlPanel,\n  kSelectTask = keycodes::kPhysicalSelectTask,\n  kLaunchDocuments = keycodes::kPhysicalLaunchDocuments,\n  kSpellCheck = keycodes::kPhysicalSpellCheck,\n  kLaunchKeyboardLayout = keycodes::kPhysicalLaunchKeyboardLayout,\n  kLaunchScreenSaver = keycodes::kPhysicalLaunchScreenSaver,\n  kLaunchAudioBrowser = keycodes::kPhysicalLaunchAudioBrowser,\n  kLaunchAssistant = keycodes::kPhysicalLaunchAssistant,\n  kNewKey = keycodes::kPhysicalNewKey,\n  kClose = keycodes::kPhysicalClose,\n  kSave = keycodes::kPhysicalSave,\n  kPrint = keycodes::kPhysicalPrint,\n  kBrowserSearch = keycodes::kPhysicalBrowserSearch,\n  kBrowserHome = keycodes::kPhysicalBrowserHome,\n  kBrowserBack = keycodes::kPhysicalBrowserBack,\n  kBrowserForward = keycodes::kPhysicalBrowserForward,\n  kBrowserStop = keycodes::kPhysicalBrowserStop,\n  kBrowserRefresh = keycodes::kPhysicalBrowserRefresh,\n  kBrowserFavorites = keycodes::kPhysicalBrowserFavorites,\n  kZoomIn = keycodes::kPhysicalZoomIn,\n  kZoomOut = keycodes::kPhysicalZoomOut,\n  kZoomToggle = keycodes::kPhysicalZoomToggle,\n  kRedo = keycodes::kPhysicalRedo,\n  kMailReply = keycodes::kPhysicalMailReply,\n  kMailForward = keycodes::kPhysicalMailForward,\n  kMailSend = keycodes::kPhysicalMailSend,\n  kKeyboardLayoutSelect = keycodes::kPhysicalKeyboardLayoutSelect,\n  kShowAllWindows = keycodes::kPhysicalShowAllWindows,\n};\n\nenum class LogicalKeyboardKey : uint64_t {\n  kUnknown = 0,\n  kSpace = keycodes::kLogicalSpace,\n  kExclamation = keycodes::kLogicalExclamation,\n  kQuote = keycodes::kLogicalQuote,\n  kNumberSign = keycodes::kLogicalNumberSign,\n  kDollar = keycodes::kLogicalDollar,\n  kPercent = keycodes::kLogicalPercent,\n  kAmpersand = keycodes::kLogicalAmpersand,\n  kQuoteSingle = keycodes::kLogicalQuoteSingle,\n  kParenthesisLeft = keycodes::kLogicalParenthesisLeft,\n  kParenthesisRight = keycodes::kLogicalParenthesisRight,\n  kAsterisk = keycodes::kLogicalAsterisk,\n  kAdd = keycodes::kLogicalAdd,\n  kComma = keycodes::kLogicalComma,\n  kMinus = keycodes::kLogicalMinus,\n  kPeriod = keycodes::kLogicalPeriod,\n  kSlash = keycodes::kLogicalSlash,\n  kDigit0 = keycodes::kLogicalDigit0,\n  kDigit1 = keycodes::kLogicalDigit1,\n  kDigit2 = keycodes::kLogicalDigit2,\n  kDigit3 = keycodes::kLogicalDigit3,\n  kDigit4 = keycodes::kLogicalDigit4,\n  kDigit5 = keycodes::kLogicalDigit5,\n  kDigit6 = keycodes::kLogicalDigit6,\n  kDigit7 = keycodes::kLogicalDigit7,\n  kDigit8 = keycodes::kLogicalDigit8,\n  kDigit9 = keycodes::kLogicalDigit9,\n  kColon = keycodes::kLogicalColon,\n  kSemicolon = keycodes::kLogicalSemicolon,\n  kLess = keycodes::kLogicalLess,\n  kEqual = keycodes::kLogicalEqual,\n  kGreater = keycodes::kLogicalGreater,\n  kQuestion = keycodes::kLogicalQuestion,\n  kAt = keycodes::kLogicalAt,\n  kBracketLeft = keycodes::kLogicalBracketLeft,\n  kBackslash = keycodes::kLogicalBackslash,\n  kBracketRight = keycodes::kLogicalBracketRight,\n  kCaret = keycodes::kLogicalCaret,\n  kUnderscore = keycodes::kLogicalUnderscore,\n  kBackquote = keycodes::kLogicalBackquote,\n  kKeyA = keycodes::kLogicalKeyA,\n  kKeyB = keycodes::kLogicalKeyB,\n  kKeyC = keycodes::kLogicalKeyC,\n  kKeyD = keycodes::kLogicalKeyD,\n  kKeyE = keycodes::kLogicalKeyE,\n  kKeyF = keycodes::kLogicalKeyF,\n  kKeyG = keycodes::kLogicalKeyG,\n  kKeyH = keycodes::kLogicalKeyH,\n  kKeyI = keycodes::kLogicalKeyI,\n  kKeyJ = keycodes::kLogicalKeyJ,\n  kKeyK = keycodes::kLogicalKeyK,\n  kKeyL = keycodes::kLogicalKeyL,\n  kKeyM = keycodes::kLogicalKeyM,\n  kKeyN = keycodes::kLogicalKeyN,\n  kKeyO = keycodes::kLogicalKeyO,\n  kKeyP = keycodes::kLogicalKeyP,\n  kKeyQ = keycodes::kLogicalKeyQ,\n  kKeyR = keycodes::kLogicalKeyR,\n  kKeyS = keycodes::kLogicalKeyS,\n  kKeyT = keycodes::kLogicalKeyT,\n  kKeyU = keycodes::kLogicalKeyU,\n  kKeyV = keycodes::kLogicalKeyV,\n  kKeyW = keycodes::kLogicalKeyW,\n  kKeyX = keycodes::kLogicalKeyX,\n  kKeyY = keycodes::kLogicalKeyY,\n  kKeyZ = keycodes::kLogicalKeyZ,\n  kBraceLeft = keycodes::kLogicalBraceLeft,\n  kBar = keycodes::kLogicalBar,\n  kBraceRight = keycodes::kLogicalBraceRight,\n  kTilde = keycodes::kLogicalTilde,\n  kUnidentified = keycodes::kLogicalUnidentified,\n  kBackspace = keycodes::kLogicalBackspace,\n  kTab = keycodes::kLogicalTab,\n  kEnter = keycodes::kLogicalEnter,\n  kEscape = keycodes::kLogicalEscape,\n  kDelete = keycodes::kLogicalDelete,\n  kAccel = keycodes::kLogicalAccel,\n  kAltGraph = keycodes::kLogicalAltGraph,\n  kCapsLock = keycodes::kLogicalCapsLock,\n  kFn = keycodes::kLogicalFn,\n  kFnLock = keycodes::kLogicalFnLock,\n  kHyper = keycodes::kLogicalHyper,\n  kNumLock = keycodes::kLogicalNumLock,\n  kScrollLock = keycodes::kLogicalScrollLock,\n  kSuperKey = keycodes::kLogicalSuperKey,\n  kSymbol = keycodes::kLogicalSymbol,\n  kSymbolLock = keycodes::kLogicalSymbolLock,\n  kShiftLevel5 = keycodes::kLogicalShiftLevel5,\n  kArrowDown = keycodes::kLogicalArrowDown,\n  kArrowLeft = keycodes::kLogicalArrowLeft,\n  kArrowRight = keycodes::kLogicalArrowRight,\n  kArrowUp = keycodes::kLogicalArrowUp,\n  kEnd = keycodes::kLogicalEnd,\n  kHome = keycodes::kLogicalHome,\n  kPageDown = keycodes::kLogicalPageDown,\n  kPageUp = keycodes::kLogicalPageUp,\n  kClear = keycodes::kLogicalClear,\n  kCopy = keycodes::kLogicalCopy,\n  kCrSel = keycodes::kLogicalCrSel,\n  kCut = keycodes::kLogicalCut,\n  kEraseEof = keycodes::kLogicalEraseEof,\n  kExSel = keycodes::kLogicalExSel,\n  kInsert = keycodes::kLogicalInsert,\n  kPaste = keycodes::kLogicalPaste,\n  kRedo = keycodes::kLogicalRedo,\n  kUndo = keycodes::kLogicalUndo,\n  kAccept = keycodes::kLogicalAccept,\n  kAgain = keycodes::kLogicalAgain,\n  kAttn = keycodes::kLogicalAttn,\n  kCancel = keycodes::kLogicalCancel,\n  kContextMenu = keycodes::kLogicalContextMenu,\n  kExecute = keycodes::kLogicalExecute,\n  kFind = keycodes::kLogicalFind,\n  kHelp = keycodes::kLogicalHelp,\n  kPause = keycodes::kLogicalPause,\n  kPlay = keycodes::kLogicalPlay,\n  kProps = keycodes::kLogicalProps,\n  kSelect = keycodes::kLogicalSelect,\n  kZoomIn = keycodes::kLogicalZoomIn,\n  kZoomOut = keycodes::kLogicalZoomOut,\n  kBrightnessDown = keycodes::kLogicalBrightnessDown,\n  kBrightnessUp = keycodes::kLogicalBrightnessUp,\n  kCamera = keycodes::kLogicalCamera,\n  kEject = keycodes::kLogicalEject,\n  kLogOff = keycodes::kLogicalLogOff,\n  kPower = keycodes::kLogicalPower,\n  kPowerOff = keycodes::kLogicalPowerOff,\n  kPrintScreen = keycodes::kLogicalPrintScreen,\n  kHibernate = keycodes::kLogicalHibernate,\n  kStandby = keycodes::kLogicalStandby,\n  kWakeUp = keycodes::kLogicalWakeUp,\n  kAllCandidates = keycodes::kLogicalAllCandidates,\n  kAlphanumeric = keycodes::kLogicalAlphanumeric,\n  kCodeInput = keycodes::kLogicalCodeInput,\n  kCompose = keycodes::kLogicalCompose,\n  kConvert = keycodes::kLogicalConvert,\n  kFinalMode = keycodes::kLogicalFinalMode,\n  kGroupFirst = keycodes::kLogicalGroupFirst,\n  kGroupLast = keycodes::kLogicalGroupLast,\n  kGroupNext = keycodes::kLogicalGroupNext,\n  kGroupPrevious = keycodes::kLogicalGroupPrevious,\n  kModeChange = keycodes::kLogicalModeChange,\n  kNextCandidate = keycodes::kLogicalNextCandidate,\n  kNonConvert = keycodes::kLogicalNonConvert,\n  kPreviousCandidate = keycodes::kLogicalPreviousCandidate,\n  kProcess = keycodes::kLogicalProcess,\n  kSingleCandidate = keycodes::kLogicalSingleCandidate,\n  kHangulMode = keycodes::kLogicalHangulMode,\n  kHanjaMode = keycodes::kLogicalHanjaMode,\n  kJunjaMode = keycodes::kLogicalJunjaMode,\n  kEisu = keycodes::kLogicalEisu,\n  kHankaku = keycodes::kLogicalHankaku,\n  kHiragana = keycodes::kLogicalHiragana,\n  kHiraganaKatakana = keycodes::kLogicalHiraganaKatakana,\n  kKanaMode = keycodes::kLogicalKanaMode,\n  kKanjiMode = keycodes::kLogicalKanjiMode,\n  kKatakana = keycodes::kLogicalKatakana,\n  kRomaji = keycodes::kLogicalRomaji,\n  kZenkaku = keycodes::kLogicalZenkaku,\n  kZenkakuHankaku = keycodes::kLogicalZenkakuHankaku,\n  kF1 = keycodes::kLogicalF1,\n  kF2 = keycodes::kLogicalF2,\n  kF3 = keycodes::kLogicalF3,\n  kF4 = keycodes::kLogicalF4,\n  kF5 = keycodes::kLogicalF5,\n  kF6 = keycodes::kLogicalF6,\n  kF7 = keycodes::kLogicalF7,\n  kF8 = keycodes::kLogicalF8,\n  kF9 = keycodes::kLogicalF9,\n  kF10 = keycodes::kLogicalF10,\n  kF11 = keycodes::kLogicalF11,\n  kF12 = keycodes::kLogicalF12,\n  kF13 = keycodes::kLogicalF13,\n  kF14 = keycodes::kLogicalF14,\n  kF15 = keycodes::kLogicalF15,\n  kF16 = keycodes::kLogicalF16,\n  kF17 = keycodes::kLogicalF17,\n  kF18 = keycodes::kLogicalF18,\n  kF19 = keycodes::kLogicalF19,\n  kF20 = keycodes::kLogicalF20,\n  kF21 = keycodes::kLogicalF21,\n  kF22 = keycodes::kLogicalF22,\n  kF23 = keycodes::kLogicalF23,\n  kF24 = keycodes::kLogicalF24,\n  kSoft1 = keycodes::kLogicalSoft1,\n  kSoft2 = keycodes::kLogicalSoft2,\n  kSoft3 = keycodes::kLogicalSoft3,\n  kSoft4 = keycodes::kLogicalSoft4,\n  kSoft5 = keycodes::kLogicalSoft5,\n  kSoft6 = keycodes::kLogicalSoft6,\n  kSoft7 = keycodes::kLogicalSoft7,\n  kSoft8 = keycodes::kLogicalSoft8,\n  kClose = keycodes::kLogicalClose,\n  kMailForward = keycodes::kLogicalMailForward,\n  kMailReply = keycodes::kLogicalMailReply,\n  kMailSend = keycodes::kLogicalMailSend,\n  kMediaPlayPause = keycodes::kLogicalMediaPlayPause,\n  kMediaStop = keycodes::kLogicalMediaStop,\n  kMediaTrackNext = keycodes::kLogicalMediaTrackNext,\n  kMediaTrackPrevious = keycodes::kLogicalMediaTrackPrevious,\n  kNewKey = keycodes::kLogicalNewKey,\n  kOpen = keycodes::kLogicalOpen,\n  kPrint = keycodes::kLogicalPrint,\n  kSave = keycodes::kLogicalSave,\n  kSpellCheck = keycodes::kLogicalSpellCheck,\n  kAudioVolumeDown = keycodes::kLogicalAudioVolumeDown,\n  kAudioVolumeUp = keycodes::kLogicalAudioVolumeUp,\n  kAudioVolumeMute = keycodes::kLogicalAudioVolumeMute,\n  kLaunchApplication2 = keycodes::kLogicalLaunchApplication2,\n  kLaunchCalendar = keycodes::kLogicalLaunchCalendar,\n  kLaunchMail = keycodes::kLogicalLaunchMail,\n  kLaunchMediaPlayer = keycodes::kLogicalLaunchMediaPlayer,\n  kLaunchMusicPlayer = keycodes::kLogicalLaunchMusicPlayer,\n  kLaunchApplication1 = keycodes::kLogicalLaunchApplication1,\n  kLaunchScreenSaver = keycodes::kLogicalLaunchScreenSaver,\n  kLaunchSpreadsheet = keycodes::kLogicalLaunchSpreadsheet,\n  kLaunchWebBrowser = keycodes::kLogicalLaunchWebBrowser,\n  kLaunchWebCam = keycodes::kLogicalLaunchWebCam,\n  kLaunchWordProcessor = keycodes::kLogicalLaunchWordProcessor,\n  kLaunchContacts = keycodes::kLogicalLaunchContacts,\n  kLaunchPhone = keycodes::kLogicalLaunchPhone,\n  kLaunchAssistant = keycodes::kLogicalLaunchAssistant,\n  kLaunchControlPanel = keycodes::kLogicalLaunchControlPanel,\n  kBrowserBack = keycodes::kLogicalBrowserBack,\n  kBrowserFavorites = keycodes::kLogicalBrowserFavorites,\n  kBrowserForward = keycodes::kLogicalBrowserForward,\n  kBrowserHome = keycodes::kLogicalBrowserHome,\n  kBrowserRefresh = keycodes::kLogicalBrowserRefresh,\n  kBrowserSearch = keycodes::kLogicalBrowserSearch,\n  kBrowserStop = keycodes::kLogicalBrowserStop,\n  kAudioBalanceLeft = keycodes::kLogicalAudioBalanceLeft,\n  kAudioBalanceRight = keycodes::kLogicalAudioBalanceRight,\n  kAudioBassBoostDown = keycodes::kLogicalAudioBassBoostDown,\n  kAudioBassBoostUp = keycodes::kLogicalAudioBassBoostUp,\n  kAudioFaderFront = keycodes::kLogicalAudioFaderFront,\n  kAudioFaderRear = keycodes::kLogicalAudioFaderRear,\n  kAudioSurroundModeNext = keycodes::kLogicalAudioSurroundModeNext,\n  kAvrInput = keycodes::kLogicalAvrInput,\n  kAvrPower = keycodes::kLogicalAvrPower,\n  kChannelDown = keycodes::kLogicalChannelDown,\n  kChannelUp = keycodes::kLogicalChannelUp,\n  kColorF0Red = keycodes::kLogicalColorF0Red,\n  kColorF1Green = keycodes::kLogicalColorF1Green,\n  kColorF2Yellow = keycodes::kLogicalColorF2Yellow,\n  kColorF3Blue = keycodes::kLogicalColorF3Blue,\n  kColorF4Grey = keycodes::kLogicalColorF4Grey,\n  kColorF5Brown = keycodes::kLogicalColorF5Brown,\n  kClosedCaptionToggle = keycodes::kLogicalClosedCaptionToggle,\n  kDimmer = keycodes::kLogicalDimmer,\n  kDisplaySwap = keycodes::kLogicalDisplaySwap,\n  kExit = keycodes::kLogicalExit,\n  kFavoriteClear0 = keycodes::kLogicalFavoriteClear0,\n  kFavoriteClear1 = keycodes::kLogicalFavoriteClear1,\n  kFavoriteClear2 = keycodes::kLogicalFavoriteClear2,\n  kFavoriteClear3 = keycodes::kLogicalFavoriteClear3,\n  kFavoriteRecall0 = keycodes::kLogicalFavoriteRecall0,\n  kFavoriteRecall1 = keycodes::kLogicalFavoriteRecall1,\n  kFavoriteRecall2 = keycodes::kLogicalFavoriteRecall2,\n  kFavoriteRecall3 = keycodes::kLogicalFavoriteRecall3,\n  kFavoriteStore0 = keycodes::kLogicalFavoriteStore0,\n  kFavoriteStore1 = keycodes::kLogicalFavoriteStore1,\n  kFavoriteStore2 = keycodes::kLogicalFavoriteStore2,\n  kFavoriteStore3 = keycodes::kLogicalFavoriteStore3,\n  kGuide = keycodes::kLogicalGuide,\n  kGuideNextDay = keycodes::kLogicalGuideNextDay,\n  kGuidePreviousDay = keycodes::kLogicalGuidePreviousDay,\n  kInfo = keycodes::kLogicalInfo,\n  kInstantReplay = keycodes::kLogicalInstantReplay,\n  kLink = keycodes::kLogicalLink,\n  kListProgram = keycodes::kLogicalListProgram,\n  kLiveContent = keycodes::kLogicalLiveContent,\n  kLock = keycodes::kLogicalLock,\n  kMediaApps = keycodes::kLogicalMediaApps,\n  kMediaFastForward = keycodes::kLogicalMediaFastForward,\n  kMediaLast = keycodes::kLogicalMediaLast,\n  kMediaPause = keycodes::kLogicalMediaPause,\n  kMediaPlay = keycodes::kLogicalMediaPlay,\n  kMediaRecord = keycodes::kLogicalMediaRecord,\n  kMediaRewind = keycodes::kLogicalMediaRewind,\n  kMediaSkip = keycodes::kLogicalMediaSkip,\n  kNextFavoriteChannel = keycodes::kLogicalNextFavoriteChannel,\n  kNextUserProfile = keycodes::kLogicalNextUserProfile,\n  kOnDemand = keycodes::kLogicalOnDemand,\n  kPInPDown = keycodes::kLogicalPInPDown,\n  kPInPMove = keycodes::kLogicalPInPMove,\n  kPInPToggle = keycodes::kLogicalPInPToggle,\n  kPInPUp = keycodes::kLogicalPInPUp,\n  kPlaySpeedDown = keycodes::kLogicalPlaySpeedDown,\n  kPlaySpeedReset = keycodes::kLogicalPlaySpeedReset,\n  kPlaySpeedUp = keycodes::kLogicalPlaySpeedUp,\n  kRandomToggle = keycodes::kLogicalRandomToggle,\n  kRcLowBattery = keycodes::kLogicalRcLowBattery,\n  kRecordSpeedNext = keycodes::kLogicalRecordSpeedNext,\n  kRfBypass = keycodes::kLogicalRfBypass,\n  kScanChannelsToggle = keycodes::kLogicalScanChannelsToggle,\n  kScreenModeNext = keycodes::kLogicalScreenModeNext,\n  kSettings = keycodes::kLogicalSettings,\n  kSplitScreenToggle = keycodes::kLogicalSplitScreenToggle,\n  kStbInput = keycodes::kLogicalStbInput,\n  kStbPower = keycodes::kLogicalStbPower,\n  kSubtitle = keycodes::kLogicalSubtitle,\n  kTeletext = keycodes::kLogicalTeletext,\n  kTv = keycodes::kLogicalTv,\n  kTvInput = keycodes::kLogicalTvInput,\n  kTvPower = keycodes::kLogicalTvPower,\n  kVideoModeNext = keycodes::kLogicalVideoModeNext,\n  kWink = keycodes::kLogicalWink,\n  kZoomToggle = keycodes::kLogicalZoomToggle,\n  kDvr = keycodes::kLogicalDvr,\n  kMediaAudioTrack = keycodes::kLogicalMediaAudioTrack,\n  kMediaSkipBackward = keycodes::kLogicalMediaSkipBackward,\n  kMediaSkipForward = keycodes::kLogicalMediaSkipForward,\n  kMediaStepBackward = keycodes::kLogicalMediaStepBackward,\n  kMediaStepForward = keycodes::kLogicalMediaStepForward,\n  kMediaTopMenu = keycodes::kLogicalMediaTopMenu,\n  kNavigateIn = keycodes::kLogicalNavigateIn,\n  kNavigateNext = keycodes::kLogicalNavigateNext,\n  kNavigateOut = keycodes::kLogicalNavigateOut,\n  kNavigatePrevious = keycodes::kLogicalNavigatePrevious,\n  kPairing = keycodes::kLogicalPairing,\n  kMediaClose = keycodes::kLogicalMediaClose,\n  kAudioBassBoostToggle = keycodes::kLogicalAudioBassBoostToggle,\n  kAudioTrebleDown = keycodes::kLogicalAudioTrebleDown,\n  kAudioTrebleUp = keycodes::kLogicalAudioTrebleUp,\n  kMicrophoneToggle = keycodes::kLogicalMicrophoneToggle,\n  kMicrophoneVolumeDown = keycodes::kLogicalMicrophoneVolumeDown,\n  kMicrophoneVolumeUp = keycodes::kLogicalMicrophoneVolumeUp,\n  kMicrophoneVolumeMute = keycodes::kLogicalMicrophoneVolumeMute,\n  kSpeechCorrectionList = keycodes::kLogicalSpeechCorrectionList,\n  kSpeechInputToggle = keycodes::kLogicalSpeechInputToggle,\n  kAppSwitch = keycodes::kLogicalAppSwitch,\n  kCall = keycodes::kLogicalCall,\n  kCameraFocus = keycodes::kLogicalCameraFocus,\n  kEndCall = keycodes::kLogicalEndCall,\n  kGoBack = keycodes::kLogicalGoBack,\n  kGoHome = keycodes::kLogicalGoHome,\n  kHeadsetHook = keycodes::kLogicalHeadsetHook,\n  kLastNumberRedial = keycodes::kLogicalLastNumberRedial,\n  kNotification = keycodes::kLogicalNotification,\n  kMannerMode = keycodes::kLogicalMannerMode,\n  kVoiceDial = keycodes::kLogicalVoiceDial,\n  kTv3DMode = keycodes::kLogicalTv3DMode,\n  kTvAntennaCable = keycodes::kLogicalTvAntennaCable,\n  kTvAudioDescription = keycodes::kLogicalTvAudioDescription,\n  kTvAudioDescriptionMixDown = keycodes::kLogicalTvAudioDescriptionMixDown,\n  kTvAudioDescriptionMixUp = keycodes::kLogicalTvAudioDescriptionMixUp,\n  kTvContentsMenu = keycodes::kLogicalTvContentsMenu,\n  kTvDataService = keycodes::kLogicalTvDataService,\n  kTvInputComponent1 = keycodes::kLogicalTvInputComponent1,\n  kTvInputComponent2 = keycodes::kLogicalTvInputComponent2,\n  kTvInputComposite1 = keycodes::kLogicalTvInputComposite1,\n  kTvInputComposite2 = keycodes::kLogicalTvInputComposite2,\n  kTvInputHDMI1 = keycodes::kLogicalTvInputHDMI1,\n  kTvInputHDMI2 = keycodes::kLogicalTvInputHDMI2,\n  kTvInputHDMI3 = keycodes::kLogicalTvInputHDMI3,\n  kTvInputHDMI4 = keycodes::kLogicalTvInputHDMI4,\n  kTvInputVGA1 = keycodes::kLogicalTvInputVGA1,\n  kTvMediaContext = keycodes::kLogicalTvMediaContext,\n  kTvNetwork = keycodes::kLogicalTvNetwork,\n  kTvNumberEntry = keycodes::kLogicalTvNumberEntry,\n  kTvRadioService = keycodes::kLogicalTvRadioService,\n  kTvSatellite = keycodes::kLogicalTvSatellite,\n  kTvSatelliteBS = keycodes::kLogicalTvSatelliteBS,\n  kTvSatelliteCS = keycodes::kLogicalTvSatelliteCS,\n  kTvSatelliteToggle = keycodes::kLogicalTvSatelliteToggle,\n  kTvTerrestrialAnalog = keycodes::kLogicalTvTerrestrialAnalog,\n  kTvTerrestrialDigital = keycodes::kLogicalTvTerrestrialDigital,\n  kTvTimer = keycodes::kLogicalTvTimer,\n  kKey11 = keycodes::kLogicalKey11,\n  kKey12 = keycodes::kLogicalKey12,\n  kSuspend = keycodes::kLogicalSuspend,\n  kResume = keycodes::kLogicalResume,\n  kSleep = keycodes::kLogicalSleep,\n  kAbort = keycodes::kLogicalAbort,\n  kLang1 = keycodes::kLogicalLang1,\n  kLang2 = keycodes::kLogicalLang2,\n  kLang3 = keycodes::kLogicalLang3,\n  kLang4 = keycodes::kLogicalLang4,\n  kLang5 = keycodes::kLogicalLang5,\n  kIntlBackslash = keycodes::kLogicalIntlBackslash,\n  kIntlRo = keycodes::kLogicalIntlRo,\n  kIntlYen = keycodes::kLogicalIntlYen,\n  kControlLeft = keycodes::kLogicalControlLeft,\n  kControlRight = keycodes::kLogicalControlRight,\n  kShiftLeft = keycodes::kLogicalShiftLeft,\n  kShiftRight = keycodes::kLogicalShiftRight,\n  kAltLeft = keycodes::kLogicalAltLeft,\n  kAltRight = keycodes::kLogicalAltRight,\n  kMetaLeft = keycodes::kLogicalMetaLeft,\n  kMetaRight = keycodes::kLogicalMetaRight,\n  kControl = keycodes::kLogicalControl,\n  kShift = keycodes::kLogicalShift,\n  kAlt = keycodes::kLogicalAlt,\n  kMeta = keycodes::kLogicalMeta,\n  kNumpadEnter = keycodes::kLogicalNumpadEnter,\n  kNumpadParenLeft = keycodes::kLogicalNumpadParenLeft,\n  kNumpadParenRight = keycodes::kLogicalNumpadParenRight,\n  kNumpadMultiply = keycodes::kLogicalNumpadMultiply,\n  kNumpadAdd = keycodes::kLogicalNumpadAdd,\n  kNumpadComma = keycodes::kLogicalNumpadComma,\n  kNumpadSubtract = keycodes::kLogicalNumpadSubtract,\n  kNumpadDecimal = keycodes::kLogicalNumpadDecimal,\n  kNumpadDivide = keycodes::kLogicalNumpadDivide,\n  kNumpad0 = keycodes::kLogicalNumpad0,\n  kNumpad1 = keycodes::kLogicalNumpad1,\n  kNumpad2 = keycodes::kLogicalNumpad2,\n  kNumpad3 = keycodes::kLogicalNumpad3,\n  kNumpad4 = keycodes::kLogicalNumpad4,\n  kNumpad5 = keycodes::kLogicalNumpad5,\n  kNumpad6 = keycodes::kLogicalNumpad6,\n  kNumpad7 = keycodes::kLogicalNumpad7,\n  kNumpad8 = keycodes::kLogicalNumpad8,\n  kNumpad9 = keycodes::kLogicalNumpad9,\n  kNumpadEqual = keycodes::kLogicalNumpadEqual,\n  kGameButton1 = keycodes::kLogicalGameButton1,\n  kGameButton2 = keycodes::kLogicalGameButton2,\n  kGameButton3 = keycodes::kLogicalGameButton3,\n  kGameButton4 = keycodes::kLogicalGameButton4,\n  kGameButton5 = keycodes::kLogicalGameButton5,\n  kGameButton6 = keycodes::kLogicalGameButton6,\n  kGameButton7 = keycodes::kLogicalGameButton7,\n  kGameButton8 = keycodes::kLogicalGameButton8,\n  kGameButton9 = keycodes::kLogicalGameButton9,\n  kGameButton10 = keycodes::kLogicalGameButton10,\n  kGameButton11 = keycodes::kLogicalGameButton11,\n  kGameButton12 = keycodes::kLogicalGameButton12,\n  kGameButton13 = keycodes::kLogicalGameButton13,\n  kGameButton14 = keycodes::kLogicalGameButton14,\n  kGameButton15 = keycodes::kLogicalGameButton15,\n  kGameButton16 = keycodes::kLogicalGameButton16,\n  kGameButtonA = keycodes::kLogicalGameButtonA,\n  kGameButtonB = keycodes::kLogicalGameButtonB,\n  kGameButtonC = keycodes::kLogicalGameButtonC,\n  kGameButtonLeft1 = keycodes::kLogicalGameButtonLeft1,\n  kGameButtonLeft2 = keycodes::kLogicalGameButtonLeft2,\n  kGameButtonMode = keycodes::kLogicalGameButtonMode,\n  kGameButtonRight1 = keycodes::kLogicalGameButtonRight1,\n  kGameButtonRight2 = keycodes::kLogicalGameButtonRight2,\n  kGameButtonSelect = keycodes::kLogicalGameButtonSelect,\n  kGameButtonStart = keycodes::kLogicalGameButtonStart,\n  kGameButtonThumbLeft = keycodes::kLogicalGameButtonThumbLeft,\n  kGameButtonThumbRight = keycodes::kLogicalGameButtonThumbRight,\n  kGameButtonX = keycodes::kLogicalGameButtonX,\n  kGameButtonY = keycodes::kLogicalGameButtonY,\n  kGameButtonZ = keycodes::kLogicalGameButtonZ,\n};\n\nusing KeyCode = LogicalKeyboardKey;\n\ninline bool IsBackOrEscapeKey(KeyCode key_code) {\n  return key_code == LogicalKeyboardKey::kGoBack ||\n         key_code == LogicalKeyboardKey::kEscape;\n}\n\n}  // namespace clay\n#endif  // CLAY_UI_EVENT_KEYBOARD_KEY_H_\n"
  },
  {
    "path": "clay/ui/gesture/arena.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_ARENA_H_\n#define CLAY_UI_GESTURE_ARENA_H_\n\n#include <list>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n\nnamespace clay {\n\nclass ArenaMember;\n\nstruct Arena {\n  void RemoveMember(const fml::WeakPtr<ArenaMember>& member) {\n    for (auto iter = members.begin(); iter != members.end(); ++iter) {\n      if (iter->get() == member.get()) {\n        members.erase(iter);\n        break;\n      }\n    }\n  }\n  // Order matters because when sweeping arena, the first member will win.\n  std::list<fml::WeakPtr<ArenaMember>> members;\n  // Close arena after all members entered in.\n  bool is_open = true;\n  // Hold the arena to prevent from closing.\n  bool is_held = false;\n  // If arena is held when sweep, |has_pending_sweep| should be set to true.\n  bool has_pending_sweep = false;\n  // Temp record the member who declared to be accepted.\n  fml::WeakPtr<ArenaMember> eager_winner;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_ARENA_H_\n"
  },
  {
    "path": "clay/ui/gesture/arena_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/arena_manager.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/ui/gesture/macros.h\"\n\nnamespace clay {\n\nstd::unique_ptr<ArenaEntry> ArenaManager::Add(\n    int pointer_id, fml::WeakPtr<ArenaMember> member) {\n  auto& arena = arenas_[pointer_id];\n  if (!arena) {\n    arena = std::make_unique<Arena>();\n  }\n  arena->members.emplace_back(member);\n  return std::make_unique<ArenaEntry>(this, pointer_id, std::move(member));\n}\n\nvoid ArenaEntry::Resolve(GestureDisposition disposition) {\n  GESTURE_LOG << \"pointer \" << pointer_id_ << \" of \" << member_->GetMemberTag()\n              << member_.get() << \" resolved with \"\n              << (disposition == GestureDisposition::kAccept ? \"ACCEPT\"\n                                                             : \"REJECT\");\n  arena_manager_->Resolve(pointer_id_, member_, disposition);\n}\n\nvoid ArenaManager::Close(const PointerEvent& event) {\n  int pointer_id = event.pointer_id;\n  recording_events_.emplace(pointer_id, event);\n  auto iter = arenas_.find(pointer_id);\n  if (iter == arenas_.end()) {\n    OnPointerNotCared(event);\n    return;\n  }\n\n  Arena* arena = iter->second.get();\n  FML_DCHECK(arena->is_open);\n  arena->is_open = false;\n  TryResolve(event, arena);\n}\n\nvoid ArenaManager::Sweep(int pointer_id) {\n  auto iter = arenas_.find(pointer_id);\n  if (iter == arenas_.end()) {\n    // `OnPointerNotCared` has already been notified when `Close`\n    return;\n  }\n\n  Arena* arena = iter->second.get();\n  FML_DCHECK(!arena->is_open);\n\n  if (arena->is_held) {\n    arena->has_pending_sweep = true;\n    // Arena will be swept when released.\n    GESTURE_LOG << \"[sweep] arena was held.\";\n    return;\n  }\n\n  std::unique_ptr<Arena> holder = TakeArenaOwnership(pointer_id);\n  bool first_member = true;\n  for (auto& member : holder->members) {\n    if (!member) {\n      continue;\n    }\n    if (first_member) {\n      GESTURE_LOG << \"[sweep] accept first member: \" << member.get()\n                  << member->GetMemberTag();\n      // Choose first member win. Others reject.\n      member->OnGestureAccepted(pointer_id);\n      first_member = false;\n    } else {\n      GESTURE_LOG << \"[sweep] reject other members: \" << member.get()\n                  << member->GetMemberTag();\n      member->OnGestureRejected(pointer_id);\n    }\n  }\n}\n\nvoid ArenaManager::Hold(int pointer_id) {\n  auto iter = arenas_.find(pointer_id);\n  if (iter == arenas_.end()) {\n    return;\n  }\n\n  iter->second->is_held = true;\n}\n\nvoid ArenaManager::Release(int pointer_id) {\n  auto iter = arenas_.find(pointer_id);\n  if (iter == arenas_.end()) {\n    GESTURE_LOG << \"Invalid release op for pointer \" << pointer_id;\n    return;\n  }\n\n  iter->second->is_held = false;\n  if (iter->second->has_pending_sweep) {\n    iter->second->has_pending_sweep = false;\n    GESTURE_LOG << \"Release arena of pointer \" << pointer_id;\n    Sweep(pointer_id);\n  }\n}\n\nvoid ArenaManager::Resolve(int pointer_id,\n                           const fml::WeakPtr<ArenaMember>& member,\n                           GestureDisposition disposition) {\n  auto iter = arenas_.find(pointer_id);\n  if (iter == arenas_.end()) {\n    GESTURE_LOG << \"pointer_id=\" << pointer_id << \" already been resolved.\";\n    return;\n  }\n\n  Arena* arena = iter->second.get();\n  if (!member) {\n    arena->RemoveMember(member);\n    return;\n  }\n\n  GESTURE_LOG << \"arena member \" << member->GetMemberTag() << member.get()\n              << \"(pointer=\" << pointer_id\n              << \") resolve arena with disposition \"\n              << (disposition == GestureDisposition::kAccept ? \"accept\"\n                                                             : \"reject\");\n\n  PointerEvent event;\n  if (recording_events_.find(pointer_id) != recording_events_.end()) {\n    event = recording_events_[pointer_id];\n  } else {\n    // we pass the point event to TryResolve function in order to let the\n    //   OnPointerNotCared callback has a chance to know the positions, then to\n    //  find the ignore-focus nodes. in this exectue path , we only exceute the\n    // callback when there is no areas, so no nodes gesture will be handled, and\n    // the positions is uselesss;\n    event = PointerEvent();\n    event.pointer_id = pointer_id;\n  }\n  // FML_DCHECK(arena->members.find(member) != arena->members.end());\n  if (disposition == GestureDisposition::kReject) {\n    arena->RemoveMember(member);\n    GESTURE_LOG << \"arena member \" << member->GetMemberTag() << member.get()\n                << \" was removed. Members count: \" << arena->members.size();\n    member->OnGestureRejected(pointer_id);\n    if (!arena->is_open) {\n      TryResolve(event, arena);\n    }\n  } else {\n    if (arena->is_open) {\n      GESTURE_LOG << \"arena wasn't closed. set as eager winner.\";\n      // If arena is not closed, we need to set the member as eager winner,\n      // which will be resolved immediately when the arena is closed. Do not\n      // overwrite the previous winner.\n      if (!arena->eager_winner) {\n        arena->eager_winner = member;\n      }\n    } else {\n      ResolveInFavorOf(pointer_id, arena, member);\n    }\n  }\n}\n\nvoid ArenaManager::TryResolve(const PointerEvent& event, Arena* arena) {\n  int pointer_id = event.pointer_id;\n  if (arena->members.size() == 1 && !has_outer_gestures_) {\n    ResolveByDefault(pointer_id, arena);\n  } else if (arena->members.empty()) {\n    GESTURE_LOG << \"arena has no members, remove it.\";\n    arenas_.erase(pointer_id);\n    recording_events_.erase(pointer_id);\n    OnPointerNotCared(event);\n  } else if (arena->eager_winner) {\n    ResolveInFavorOf(pointer_id, arena, arena->eager_winner);\n  }\n}\n\nvoid ArenaManager::ResolveInFavorOf(\n    int pointer_id, Arena* arena,\n    const fml::WeakPtr<ArenaMember>& favor_winner) {\n  FML_DCHECK(!arena->is_open);\n  std::unique_ptr<Arena> holder = TakeArenaOwnership(pointer_id);\n  if (!favor_winner) {\n    return;\n  }\n  GESTURE_LOG << \"resolve arena in favor of eager winner \"\n              << favor_winner->GetMemberTag() << favor_winner.get();\n  FML_DCHECK(holder.get() == arena);\n  for (auto& member : arena->members) {\n    if (member.get() != nullptr && favor_winner.get() != nullptr) {\n      if (member != favor_winner) {\n        member->OnGestureRejected(pointer_id);\n      }\n    }\n  }\n\n  favor_winner->OnGestureAccepted(pointer_id);\n}\n\nvoid ArenaManager::ResolveByDefault(int pointer_id, Arena* arena) {\n  GESTURE_LOG << \"resolve arena by default\";\n  FML_DCHECK(arena->members.size() == 1);\n  std::unique_ptr<Arena> holder = TakeArenaOwnership(pointer_id);\n  FML_DCHECK(holder.get() == arena);\n  const auto& default_winner = *arena->members.begin();\n  if (default_winner) {\n    // Remove first to avoid dead lock.\n    default_winner->OnGestureAccepted(pointer_id);\n  }\n}\n\nstd::unique_ptr<Arena> ArenaManager::TakeArenaOwnership(int pointer_id) {\n  std::unique_ptr<Arena> owner;\n  auto iter = arenas_.find(pointer_id);\n  if (iter == arenas_.end()) {\n    FML_UNREACHABLE();\n    return nullptr;\n  }\n  owner.swap(iter->second);\n  arenas_.erase(iter);\n  return owner;\n}\n\nvoid ArenaManager::OnPointerNotCared(const PointerEvent& event) {\n  if (on_pointer_not_cared_) {\n    on_pointer_not_cared_(event);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/arena_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_ARENA_MANAGER_H_\n#define CLAY_UI_GESTURE_ARENA_MANAGER_H_\n\n#include <functional>\n#include <map>\n#include <memory>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/arena.h\"\n#include \"clay/ui/gesture/arena_member.h\"\n\nnamespace clay {\n\nenum class GestureDisposition { kAccept, kReject };\n\nclass ArenaManager;\n\n// Entry for resolve gesture.\n// It acts like a delegate for ArenaManager.\nclass ArenaEntry {\n public:\n  ArenaEntry(ArenaManager* arena_manager, int pointer_id,\n             fml::WeakPtr<ArenaMember> member)\n      : arena_manager_(arena_manager),\n        pointer_id_(pointer_id),\n        member_(member) {\n    FML_DCHECK(arena_manager_ && member_);\n  }\n\n  // Interface for operating arena manager.\n  void Resolve(GestureDisposition disposition);\n\n private:\n  ArenaManager* arena_manager_;\n  int pointer_id_;\n  fml::WeakPtr<ArenaMember> member_;\n};\n\nclass ArenaManager final {\n public:\n  ArenaManager() = default;\n  ArenaManager(const ArenaManager&) = delete;\n  ArenaManager& operator=(const ArenaManager&) = delete;\n\n  std::unique_ptr<ArenaEntry> Add(int pointer_id,\n                                  fml::WeakPtr<ArenaMember> member);\n  void Close(const PointerEvent& event);\n  void Sweep(int pointer_id);\n  void Hold(int pointer_id);\n  void Release(int pointer_id);\n\n  void SetListenerForNotCaredPointer(\n      std::function<void(const PointerEvent&)> cb) {\n    on_pointer_not_cared_ = cb;\n  }\n\n  void SetHasOuterGestures(bool value) { has_outer_gestures_ = value; }\n\n private:\n  friend class ArenaEntry;\n  void Resolve(int pointer_id, const fml::WeakPtr<ArenaMember>& member,\n               GestureDisposition disposition);\n\n  // Called in Resolve when someone rejected\n  void TryResolve(const PointerEvent& event, Arena* arena);\n  // Called when only one member left (not rejected), make it winner.\n  void ResolveByDefault(int pointer_id, Arena* arena);\n  // Reject all members except the favor one\n  void ResolveInFavorOf(int pointer_id, Arena* arena,\n                        const fml::WeakPtr<ArenaMember>& favor_winner);\n\n  std::unique_ptr<Arena> TakeArenaOwnership(int pointer_id);\n\n  void OnPointerNotCared(const PointerEvent& event);\n\n private:\n  // For those primary pointers which haven't been converted to a gesture that\n  // some view cares.\n  std::function<void(const PointerEvent&)> on_pointer_not_cared_;\n  std::map<int, std::unique_ptr<Arena>> arenas_;\n  std::map<int, PointerEvent> recording_events_;\n  bool has_outer_gestures_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_ARENA_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/gesture/arena_member.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_ARENA_MEMBER_H_\n#define CLAY_UI_GESTURE_ARENA_MEMBER_H_\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n\nnamespace clay {\n\nclass ArenaMember {\n public:\n  ArenaMember() : weak_factory_(this) {}\n  // Callbacks when it has been accepted or rejected.\n  virtual void OnGestureAccepted(int pointer_id) = 0;\n  virtual void OnGestureRejected(int pointer_id) = 0;\n  // Useful for debug\n  virtual const char* GetMemberTag() const = 0;\n\n protected:\n  fml::WeakPtrFactory<ArenaMember> weak_factory_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_ARENA_MEMBER_H_\n"
  },
  {
    "path": "clay/ui/gesture/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../common/config.gni\")\n\nui_gesture_sources = [\n  \"arena.h\",\n  \"arena_manager.cc\",\n  \"arena_manager.h\",\n  \"arena_member.h\",\n  \"drag_gesture_recognizer.cc\",\n  \"drag_gesture_recognizer.h\",\n  \"gesture_manager.cc\",\n  \"gesture_manager.h\",\n  \"gesture_recognizer.cc\",\n  \"gesture_recognizer.h\",\n  \"hit_test.h\",\n  \"hit_test_responsive_result.h\",\n  \"long_press_gesture_recognizer.cc\",\n  \"long_press_gesture_recognizer.h\",\n  \"macros.h\",\n  \"mouse_cursor_manager.cc\",\n  \"mouse_cursor_manager.h\",\n  \"mouse_region_manager.cc\",\n  \"mouse_region_manager.h\",\n  \"mouse_wheel_phase_handler.cc\",\n  \"mouse_wheel_phase_handler.h\",\n  \"multi_tap_gesture_recognizer.cc\",\n  \"multi_tap_gesture_recognizer.h\",\n  \"pointer_router.cc\",\n  \"pointer_router.h\",\n  \"scrollable_direction.h\",\n  \"tap_gesture_recognizer.cc\",\n  \"tap_gesture_recognizer.h\",\n  \"velocity_tracker.cc\",\n  \"velocity_tracker.h\",\n]\n\nif (is_win || is_mac || is_headless) {\n  ui_gesture_sources += [\n    \"drag_drop_manager.cc\",\n    \"drag_drop_manager.h\",\n  ]\n}\n"
  },
  {
    "path": "clay/ui/gesture/drag_drop_manager.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/drag_drop_manager.h\"\n\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nnamespace {\nconst char* DragDropEventTypeToString(ClayEventType type) {\n  switch (type) {\n    case ClayEventType::kClayEventTypeDragEnter:\n      return \"dragenter\";\n    case ClayEventType::kClayEventTypeDragOver:\n      return \"dragover\";\n    case ClayEventType::kClayEventTypeDragLeave:\n      return \"dragleave\";\n    case ClayEventType::kClayEventTypeDrop:\n      return \"drop\";\n    default:\n      FML_DCHECK(false) << \"Unknown drag drop event type: \" << type;\n      return \"unknown\";\n  }\n}\n}  // namespace\n\nDragDropManager::DragDropManager(PageView* page_view) : page_view_(page_view) {}\n\nvoid DragDropManager::DropEnterAndHover(FloatPoint point) {\n  FML_DLOG(INFO) << \"DragDropManager::DropEnterAndHover\";\n  point = page_view_->ConvertFrom<kPixelTypePhysical>(point);\n  if (page_view_) {\n    int view_id = page_view_->GetViewIdForLocation(point.x(), point.y());\n    if (view_id >= 0) {\n      if (current_view_id_ == view_id) {\n        // hover\n        SendEvent(ClayEventType::kClayEventTypeDragOver, view_id,\n                  clay::Value::Map());\n      } else {\n        // enter\n        SendEvent(ClayEventType::kClayEventTypeDragEnter, view_id,\n                  clay::Value::Map());\n        // leave previous view\n        DropLeave();\n      }\n    } else {\n      DropLeave();\n    }\n    current_view_id_ = view_id;\n  }\n}\n\nvoid DragDropManager::DropLeave() {\n  FML_DLOG(INFO) << \"DragDropManager::DropEnterAndHover: EXITED\";\n  if (current_view_id_ >= 0) {\n    // leave\n    SendEvent(ClayEventType::kClayEventTypeDragLeave, current_view_id_,\n              clay::Value::Map());\n  }\n  current_view_id_ = -1;\n}\n\nvoid DragDropManager::DragDrop(\n    FloatPoint point, std::string drag_type, std::string content_text,\n    const std::list<std::unordered_map<std::string, std::string>>&\n        content_files) {\n  point = page_view_->ConvertFrom<kPixelTypePhysical>(point);\n  if (drag_type.compare(clay::kDragTextType) == 0) {\n    // Currently we do nothing when dragging text.\n    return;\n  } else if (drag_type.compare(clay::kDragFileType) == 0) {\n    clay::Value::Map map;\n    map[\"dropEffect\"] = clay::Value(\"link\");\n    clay::Value::Array array(1);\n    array[0] = clay::Value(\"Files\");\n    map[\"types\"] = clay::Value(std::move(array));\n\n    clay::Value::Array file_array;\n    for (const auto& item : content_files) {\n      clay::Value::Map file_map;\n      for (const auto& iter : item) {\n        auto key_str = iter.first;\n        clay::Value value;\n        if (key_str.compare(clay::kDragDropSizeKey) == 0) {\n          int64_t file_size = static_cast<int64_t>(std::stoll(iter.second));\n          value = clay::Value(file_size);\n        } else {\n          auto& tmp_str = iter.second;\n          value = clay::Value(tmp_str);\n        }\n        file_map[key_str] = std::move(value);\n      }\n      file_array.emplace_back(std::move(file_map));\n    }\n    map[\"files\"] = clay::Value(std::move(file_array));\n    Drop(point, std::move(map));\n  } else {\n    FML_UNIMPLEMENTED();\n  }\n}\n\nvoid DragDropManager::Drop(FloatPoint point, clay::Value::Map map) {\n  FML_DLOG(INFO) << \"DragDropManager::DropEnterAndHover: DROP\";\n  if (page_view_) {\n    int view_id = page_view_->GetViewIdForLocation(point.x(), point.y());\n    if (view_id >= 0) {\n      SendEvent(ClayEventType::kClayEventTypeDrop, view_id, std::move(map));\n    } else {\n      DropLeave();\n    }\n  }\n  current_view_id_ = -1;\n}\n\nvoid DragDropManager::SendEvent(ClayEventType type, int view_id,\n                                clay::Value::Map map) {\n  if (!page_view_) {\n    return;\n  }\n  if (page_view_->GetEventDelegate()) {\n    page_view_->GetEventDelegate()->OnDragDropEvent(\n        DragDropEventTypeToString(type), view_id, std::move(map));\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/drag_drop_manager.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_DRAG_DROP_MANAGER_H_\n#define CLAY_UI_GESTURE_DRAG_DROP_MANAGER_H_\n\n#include <functional>\n#include <list>\n#include <map>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/value.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass PageView;\n\nconstexpr char kDragTextType[] = \"drag_text\";\nconstexpr char kDragFileType[] = \"drag_file\";\nconstexpr char kDragDropPathKey[] = \"path\";\nconstexpr char kDragDropNameKey[] = \"name\";\nconstexpr char kDragDropTypeKey[] = \"type\";\nconstexpr char kDragDropSizeKey[] = \"size\";\nconstexpr char kDragDropLastModifiedKey[] = \"lastModified\";\n\nclass DragDropManager {\n public:\n  explicit DragDropManager(PageView* page_view);\n  DragDropManager(const DragDropManager&) = delete;\n  DragDropManager& operator=(const DragDropManager&) = delete;\n\n  void DropEnterAndHover(FloatPoint point);\n  void DropLeave();\n  void DragDrop(FloatPoint point, std::string drag_type,\n                std::string content_text,\n                const std::list<std::unordered_map<std::string, std::string>>&\n                    content_files);\n\n private:\n  void Drop(FloatPoint point, clay::Value::Map map);\n  void SendEvent(ClayEventType type, int view_id, clay::Value::Map map);\n\n  PageView* page_view_ = nullptr;\n  int current_view_id_ = -1;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_DRAG_DROP_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/gesture/drag_gesture_recognizer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/drag_gesture_recognizer.h\"\n\n#include <memory>\n\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/gesture_manager.h\"\n#include \"clay/ui/gesture/macros.h\"\n\nnamespace clay {\n\nnamespace {\n// In pixels per second.\nconstexpr float kMinFlingVelocity = 50.f;\nconstexpr float kMaxFlingVelocity = 8000.f;\n}  // namespace\n\nstd::unique_ptr<DragGestureRecognizer> CreateDragRecognizerByDirection(\n    ScrollDirection direction, GestureManager* gesture_manager) {\n  switch (direction) {\n    case ScrollDirection::kHorizontal:\n      return std::make_unique<HorizontalDragGestureRecognizer>(gesture_manager);\n    case ScrollDirection::kVertical:\n      return std::make_unique<VerticalDragGestureRecognizer>(gesture_manager);\n    default:\n      return std::make_unique<DragGestureRecognizer>(gesture_manager);\n  }\n}\n\nDragGestureRecognizer::DragGestureRecognizer(GestureManager* gesture_manager)\n    : super(gesture_manager) {}\n\nvoid DragGestureRecognizer::AddAllowedPointer(const PointerEvent& pointer) {\n  super::AddAllowedPointer(pointer);\n  if (state_ == DragState::kReady) {\n    state_ = DragState::kPossible;\n    initial_pointer_ = pointer;\n    pending_distance_ = FloatSize();\n    if (on_drag_down_) {\n      on_drag_down_(pointer);\n    }\n  } else if (state_ == DragState::kAccepted) {\n    // Notify arena associated with the newer come-in pointer to win.\n    ResolveAll(GestureDisposition::kAccept);\n  }\n}\n\nvoid DragGestureRecognizer::HandleEvent(const PointerEvent& pointer) {\n  // No longer active.\n  if (state_ == DragState::kReady) {\n    return;\n  }\n  GESTURE_LOG << GetMemberTag() << this\n              << \" handle pointer event: \" << pointer.ToString();\n  if (!timestamp_offset_) {\n    const fml::TimePoint now = fml::TimePoint::Now();\n    const fml::TimeDelta timestamp =\n        fml::TimeDelta::FromMicroseconds(pointer.timestamp);\n    timestamp_offset_ = now - timestamp;\n  }\n\n  auto& velocity_tracker = velocity_trackers_[pointer.pointer_id];\n  if (pointer.type == PointerEvent::EventType::kDownEvent ||\n      pointer.type == PointerEvent::EventType::kMoveEvent ||\n      pointer.type == PointerEvent::EventType::kUpEvent ||\n      pointer.type == PointerEvent::EventType::kPanZoomStartEvent ||\n      pointer.type == PointerEvent::EventType::kPanZoomUpdateEvent) {\n    FloatPoint position{};\n    switch (pointer.type) {\n      case PointerEvent::EventType::kPanZoomStartEvent:\n        position = {};\n        break;\n      case PointerEvent::EventType::kPanZoomUpdateEvent:\n        position = pointer.pan;\n        break;\n      default:\n        position = pointer.position;\n        break;\n    }\n    velocity_tracker.AddPosition(position, pointer.timestamp);\n  }\n\n  // When touch slop is less than zero, enter to accept state without move\n  // event.\n  if ((pointer.type == PointerEvent::EventType::kDownEvent ||\n       pointer.type == PointerEvent::EventType::kPanZoomStartEvent) &&\n      GetTouchSlop() < 0) {\n    ResolveAll(GestureDisposition::kAccept);\n  }\n\n  if (pointer.type == PointerEvent::EventType::kMoveEvent ||\n      pointer.type == PointerEvent::EventType::kPanZoomUpdateEvent) {\n    FloatSize delta = pointer.type == PointerEvent::EventType::kMoveEvent\n                          ? pointer.delta\n                          : pointer.pan_delta;\n    if (state_ == DragState::kAccepted) {\n      if (on_drag_update_) {\n        on_drag_update_(pointer.position, GetDeltaForDetails(delta));\n      }\n    } else {\n      // See if moved sufficient distance to resolve accept.\n      pending_distance_ += delta;\n      if (HasSufficientDistanceToAccept(pending_distance_)) {\n        ResolveAll(GestureDisposition::kAccept);\n      }\n    }\n  }\n\n  if (pointer.type == PointerEvent::EventType::kUpEvent ||\n      pointer.type == PointerEvent::EventType::kCancel ||\n      pointer.type == PointerEvent::EventType::kPanZoomEndEvent) {\n    GiveupPointer(pointer.pointer_id);\n  }\n}\n\nvoid DragGestureRecognizer::DidStopTrackingLastPointer(int pointer_id) {\n  GESTURE_LOG << GetMemberTag() << this << \" did stop tracking last pointer \"\n              << pointer_id << \" while current drag state is \"\n              << static_cast<int>(state_);\n  switch (state_) {\n    case DragState::kReady:\n      FML_UNREACHABLE();\n      break;\n    case DragState::kPossible:\n      ResolveAll(GestureDisposition::kReject);\n      if (on_drag_cancel_) {\n        on_drag_cancel_();\n      }\n      break;\n    case DragState::kAccepted:\n      if (on_drag_end_) {\n        auto velocity_estimate =\n            velocity_trackers_[pointer_id].GetVelocityEstimate();\n        getType() == GestureRecognizerType::kHorizontalDrag\n            ? velocity_estimate.pixels_per_second.SetHeight(0)\n            : velocity_estimate.pixels_per_second.SetWidth(0);\n        GESTURE_LOG << GetMemberTag() << this << \" end, velocity is \"\n                    << velocity_estimate.pixels_per_second.ToString()\n                    << \", calculated in distance: \"\n                    << velocity_estimate.movement.ToString();\n        if (IsFlingGesture(velocity_estimate)) {\n          GESTURE_LOG << \"is fling\";\n          on_drag_end_(\n              Velocity(velocity_estimate.pixels_per_second)\n                  .Clamp(GetMinFlingVelocity(), GetMaxFlingVelocity()));\n        } else {\n          GESTURE_LOG << \"not fling\";\n          on_drag_end_(Velocity());\n        }\n      }\n      break;\n  }\n  ResetState();\n}\n\nFloatSize DragGestureRecognizer::GetDeltaForDetails(\n    const FloatSize& delta) const {\n  return delta;\n}\n\nbool DragGestureRecognizer::HasSufficientDistanceToAccept(\n    const FloatSize& movement) {\n  return movement.distance() > GetTouchSlop();\n}\n\nbool DragGestureRecognizer::IsFlingGesture(const VelocityEstimate& velocity) {\n  return velocity.movement.distance() > GetTouchSlop() &&\n         velocity.pixels_per_second.distance() > kMinFlingVelocity;\n}\n\nvoid DragGestureRecognizer::OnGestureAccepted(int pointer_id) {\n  GestureRecognizer::OnGestureAccepted(pointer_id);\n  accepted_active_pointers_.emplace(pointer_id);\n  // Maybe called by multiple pointers.\n  if (state_ != DragState::kAccepted) {\n    state_ = DragState::kAccepted;\n    if (on_drag_start_) {\n      // Default use the pointer when trigger start as the start position.\n      auto start_position = initial_pointer_.position;\n      start_position.MoveBy(pending_distance_);\n      on_drag_start_(start_position);\n    }\n    // Notify other pointers.\n    ResolveAll(GestureDisposition::kAccept);\n  }\n}\n\nvoid DragGestureRecognizer::OnGestureRejected(int pointer_id) {\n  GiveupPointer(pointer_id);\n}\n\nvoid DragGestureRecognizer::GiveupPointer(int pointer_id) {\n  GESTURE_LOG << GetMemberTag() << this << \" gives up pointer \" << pointer_id;\n  StopTrackingPointer(pointer_id);\n  auto iter = accepted_active_pointers_.find(pointer_id);\n  if (iter == accepted_active_pointers_.end()) {\n    // Never accepted. Resolve as rejected.\n    ResolveOne(pointer_id, GestureDisposition::kReject);\n  }\n}\n\nvoid DragGestureRecognizer::ResetState() {\n  state_ = DragState::kReady;\n  velocity_trackers_.clear();\n}\n\nfloat DragGestureRecognizer::GetTouchSlop() {\n  return touch_slop_.value_or(\n      gesture_manager_->ConvertFrom<kPixelTypeLogical>(kTouchSlop));\n}\n\nFloatSize HorizontalDragGestureRecognizer::GetDeltaForDetails(\n    const FloatSize& delta) const {\n  return {delta.width(), 0.0};\n}\n\nbool HorizontalDragGestureRecognizer::HasSufficientDistanceToAccept(\n    const FloatSize& distance) {\n  return std::abs(distance.width()) > GetTouchSlop();\n}\n\nbool HorizontalDragGestureRecognizer::IsFlingGesture(\n    const VelocityEstimate& velocity) {\n  return abs(velocity.movement.width()) > GetTouchSlop() &&\n         abs(velocity.pixels_per_second.width()) > GetMinFlingVelocity();\n}\n\nFloatSize VerticalDragGestureRecognizer::GetDeltaForDetails(\n    const FloatSize& delta) const {\n  return {0.0, delta.height()};\n}\n\nbool VerticalDragGestureRecognizer::HasSufficientDistanceToAccept(\n    const FloatSize& distance) {\n  return std::abs(distance.height()) > GetTouchSlop();\n}\n\nbool VerticalDragGestureRecognizer::IsFlingGesture(\n    const VelocityEstimate& velocity) {\n  return abs(velocity.movement.height()) > GetTouchSlop() &&\n         abs(velocity.pixels_per_second.height()) > GetMinFlingVelocity();\n}\n\nfloat DragGestureRecognizer::GetMaxFlingVelocity() const {\n  return gesture_manager_->ConvertFrom<kPixelTypeLogical>(kMaxFlingVelocity);\n}\n\nfloat DragGestureRecognizer::GetMinFlingVelocity() const {\n  return gesture_manager_->ConvertFrom<kPixelTypeLogical>(kMinFlingVelocity);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/drag_gesture_recognizer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_DRAG_GESTURE_RECOGNIZER_H_\n#define CLAY_UI_GESTURE_DRAG_GESTURE_RECOGNIZER_H_\n\n#include <functional>\n#include <map>\n#include <memory>\n#include <optional>\n#include <set>\n#include <utility>\n\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/gesture_recognizer.h\"\n#include \"clay/ui/gesture/velocity_tracker.h\"\n\nnamespace clay {\n\n// Happens whenever pointer down.\nusing DragDownCallback = std::function<void(const PointerEvent&)>;\n// Happens after drag gesture is accepted.\nusing DragStartCallback = std::function<void(const FloatPoint&)>;\nusing DragUpdateCallback =\n    std::function<void(const FloatPoint& position, const FloatSize& delta)>;\n// Happens when pointer up with state == accepted.\n// If velocity is not zero means fling gesture is detected.\nusing DragEndCallback = std::function<void(const Velocity&)>;\n// Happens when pointer up with state != accepted.\nusing DragCancelCallback = std::function<void()>;\n\nclass GestureManager;\n\nclass DragGestureRecognizer : public OneSequenceGestureRecognizer {\n public:\n  explicit DragGestureRecognizer(GestureManager* gesture_manager);\n\n  const char* GetMemberTag() const override { return \"[Drag]\"; }\n\n  void HandleEvent(const PointerEvent& pointer) override;\n\n  GestureRecognizerType getType() const override {\n    return GestureRecognizerType::kDragGesture;\n  }\n\n  void SetDragDownCallback(DragDownCallback&& on_drag_down) {\n    on_drag_down_ = std::move(on_drag_down);\n  }\n  void SetDragStartCallback(DragStartCallback&& on_drag_start) {\n    on_drag_start_ = std::move(on_drag_start);\n  }\n  void SetDragUpdateCallback(DragUpdateCallback&& on_drag_update) {\n    on_drag_update_ = std::move(on_drag_update);\n  }\n  void SetDragEndCallback(DragEndCallback&& on_drag_end) {\n    on_drag_end_ = std::move(on_drag_end);\n  }\n  void SetDragCancelCallback(DragCancelCallback&& on_drag_cancel) {\n    on_drag_cancel_ = std::move(on_drag_cancel);\n  }\n\n  float GetTouchSlop();\n\n  void SetTouchSlop(float slop) { touch_slop_ = slop; }\n\n protected:\n  enum class DragState {\n    kReady,\n    kPossible,\n    kAccepted,\n  };\n\n  virtual FloatSize GetDeltaForDetails(const FloatSize& delta) const;\n  virtual bool HasSufficientDistanceToAccept(const FloatSize& movement);\n  virtual bool IsFlingGesture(const VelocityEstimate& velocity);\n\n  float GetMaxFlingVelocity() const;\n  float GetMinFlingVelocity() const;\n\n private:\n  using super = OneSequenceGestureRecognizer;\n\n  void AddAllowedPointer(const PointerEvent& pointer) override;\n\n  void DidStopTrackingLastPointer(int pointer_id) override;\n\n  void OnGestureAccepted(int pointer_id) override;\n  void OnGestureRejected(int pointer_id) override;\n\n  void GiveupPointer(int pointer_id);\n  void ResetState();\n\n  DragDownCallback on_drag_down_;\n  DragStartCallback on_drag_start_;\n  DragUpdateCallback on_drag_update_;\n  DragEndCallback on_drag_end_;\n  DragCancelCallback on_drag_cancel_;\n\n  DragState state_ = DragState::kReady;\n\n  // For temporally store distance before accept to judge drag gesture.\n  FloatSize pending_distance_;\n\n  PointerEvent initial_pointer_;\n\n  std::set<int> accepted_active_pointers_;\n\n  // Movement permitted after touch and before accept. In arbitrary direction.\n  std::optional<float> touch_slop_;\n\n  // May have multiple pointers touch down one by one. That means the second\n  // pointer may touch down before the first pointer touch up.\n  std::map<int, VelocityTracker> velocity_trackers_;\n\n  std::optional<fml::TimePoint> timestamp_offset_;\n};\n\nclass HorizontalDragGestureRecognizer : public DragGestureRecognizer {\n public:\n  explicit HorizontalDragGestureRecognizer(GestureManager* gesture_manager)\n      : DragGestureRecognizer(gesture_manager) {}\n  GestureRecognizerType getType() const override {\n    return GestureRecognizerType::kHorizontalDrag;\n  }\n  FloatSize GetDeltaForDetails(const FloatSize& delta) const override;\n  bool HasSufficientDistanceToAccept(const FloatSize& distance) override;\n  bool IsFlingGesture(const VelocityEstimate& velocity) override;\n  const char* GetMemberTag() const override { return \"[HorizontalDrag]\"; }\n};\n\nclass VerticalDragGestureRecognizer : public DragGestureRecognizer {\n public:\n  explicit VerticalDragGestureRecognizer(GestureManager* gesture_manager)\n      : DragGestureRecognizer(gesture_manager) {}\n  GestureRecognizerType getType() const override {\n    return GestureRecognizerType::kVerticalDrag;\n  }\n  FloatSize GetDeltaForDetails(const FloatSize& delta) const override;\n  bool HasSufficientDistanceToAccept(const FloatSize& distance) override;\n  bool IsFlingGesture(const VelocityEstimate& velocity) override;\n  const char* GetMemberTag() const override { return \"[VerticalDrag]\"; }\n};\n\nstd::unique_ptr<DragGestureRecognizer> CreateDragRecognizerByDirection(\n    ScrollDirection direction, GestureManager* gesture_manager);\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_DRAG_GESTURE_RECOGNIZER_H_\n"
  },
  {
    "path": "clay/ui/gesture/gesture_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/gesture_manager.h\"\n\n#include <cmath>\n#include <memory>\n#include <utility>\n\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/gesture_recognizer.h\"\n#include \"clay/ui/gesture/macros.h\"\n\nnamespace clay {\nnamespace {\n// treat the scrolling events those time difference within 500ms as the same\n// scrolling event.\n// TODO(yuchi): on mac platfrom, the touchpad will simulate the fling effect by\n// hardware, so the delay will be longer than 500ms. A better solution will be\n// researched in future.\nstatic const int kWheelScrollDelay = 500;\n\n}  // namespace\n\nGestureManager::GestureManager(fml::RefPtr<fml::TaskRunner> task_runner,\n                               std::shared_ptr<ServiceManager> service_manager)\n    : arena_manager_(std::make_unique<ArenaManager>()),\n      task_runner_(std::move(task_runner)),\n      mouse_wheel_phase_handler_(task_runner_, this) {\n  if (service_manager) {\n    gesture_mediate_puppet_ =\n        service_manager->GetService<GestureMediateService>();\n  }\n}\n\nbool GestureManager::HandlePointerEvents(HitTestable* root,\n                                         std::vector<PointerEvent>& events) {\n  FML_DCHECK(root);\n  GESTURE_LOG << \"Manager\" << this\n              << \" HandlePointerEvents size:\" << events.size();\n  bool consumed = false;\n  for (auto& event : events) {\n    GESTURE_LOG << \"Handle pointer: \" << event.ToString();\n    consumed |= HandlePointerEvent(root, event);\n  }\n  return consumed;\n}\n\nbool GestureManager::HandlePointerEvent(HitTestable* root,\n                                        PointerEvent& event) {\n  bool consumed = true;\n  if (event.type == PointerEvent::EventType::kDownEvent ||\n      event.type == PointerEvent::EventType::kSignalEvent ||\n      event.type == PointerEvent::EventType::kPanZoomStartEvent) {\n    // Due to the possibility of multiple touch events, reset is only performed\n    // on the first DownEvent. Since the Tap gesture is recognized after the\n    // UpEvent, clean up to retain only the initial DownEvent of the first\n    // finger.\n    if (gesture_mediate_puppet_ && hit_tests_.empty() &&\n        (event.type == PointerEvent::EventType::kDownEvent)) {\n      bool has_outer_gestures = false;\n      gesture_mediate_puppet_.Act([&](auto& impl) {\n        static_assert(\n            is_owner_same_thread<Owner::kPlatform, Owner::kUI>::value,\n            \"Currently GestureMediateService relies on UI and Platform in the \"\n            \"same thread\");\n        has_outer_gestures =\n            impl.GetOuterScrollableDirection() != ScrollableDirection::kNone;\n      });\n      arena_manager_->SetHasOuterGestures(has_outer_gestures);\n      down_event_point_ = event.position;\n      down_event_pointer_id_ = event.pointer_id;\n      consume_slide_event_status_ = ConsumeSlideEventStatus::kUndefined;\n      ResetHitTestTargetResponsive();\n    }\n    // First pointer, do hittest\n    // Override old value.\n    hit_tests_.insert_or_assign(event.pointer_id, HitTestResult{});\n    gesture_accepted_map_.insert_or_assign(event.pointer_id,\n                                           GestureRecognizerType::kNone);\n    root->HitTest(event, hit_tests_[event.pointer_id]);\n\n    auto& hit_test_result = hit_tests_[event.pointer_id];\n    auto iter = hit_test_result.begin();\n    while (iter != hit_test_result.end()) {\n      while (iter != hit_test_result.end() && !(*iter)) {\n        iter = hit_test_result.erase(iter);\n      }\n      if (iter == hit_test_result.end()) {\n        break;\n      }\n\n      auto segment_start = iter;\n      bool should_pass_event_to_native =\n          segment_start->get() &&\n          segment_start->get()->ShouldPassEventToNative();\n\n      auto segment_end = segment_start;\n      while (segment_end != hit_test_result.end() && (*segment_end)) {\n        ++segment_end;\n      }\n\n      if (should_pass_event_to_native) {\n        iter = hit_test_result.erase(segment_start, segment_end);\n      } else {\n        iter = segment_end;\n      }\n    }\n\n    if (hit_test_result.empty()) {\n      consumed = false;\n    }\n    DispatchEvent(event, &hit_test_result);\n  } else if (event.type == PointerEvent::EventType::kMoveEvent ||\n             event.type == PointerEvent::EventType::kPanZoomUpdateEvent) {\n    DispatchEvent(event, &hit_tests_[event.pointer_id]);\n  } else if (event.type == PointerEvent::EventType::kUpEvent ||\n             event.type == PointerEvent::EventType::kCancel ||\n             event.type == PointerEvent::EventType::kPanZoomEndEvent) {\n    auto iter = hit_tests_.find(event.pointer_id);\n    if (iter != hit_tests_.end()) {\n      DispatchEvent(event, &iter->second);\n      hit_tests_.erase(iter);\n      gesture_accepted_map_.erase(event.pointer_id);\n    } else {\n      consumed = false;\n    }\n    if (hit_tests_.empty()) {\n      // All pointers are empty, current gesture is over. Reset hit test target\n      // responsive.\n      ResetHitTestTargetResponsive();\n    }\n  } else {\n    // TODO(yulitao): Events like hover / cancel may enter here.\n    consumed = false;\n  }\n\n  if (event.device == PointerEvent::DeviceType::kMouse) {\n    mouse_wheel_phase_handler_.DispatchWheelEndEventIfNeeded(event);\n  }\n  if (event.type == PointerEvent::EventType::kDownEvent ||\n      event.type == PointerEvent::EventType::kPanZoomStartEvent) {\n    GESTURE_LOG << \"close arena\";\n    arena_manager_->Close(event);\n  } else if (event.type == PointerEvent::EventType::kUpEvent ||\n             event.type == PointerEvent::EventType::kPanZoomEndEvent) {\n    GESTURE_LOG << \"try sweep arena\";\n    arena_manager_->Sweep(event.pointer_id);\n  } else if (event.type == PointerEvent::EventType::kSignalEvent) {\n    mouse_wheel_phase_handler_.UpdatePhaseAndScheduleEndEvent(event);\n    if (signal_event_route_) {\n      signal_event_route_(event);\n    }\n  }\n  return consumed;\n}\n\n// This logic should keep consistent with Lynx. See:\n// TouchEventDispatcher.java#consumeSlideEvent\nbool GestureManager::ConsumeSlideEvent(const PointerEvent& event) {\n  const auto& hit_test_result = hit_tests_[event.pointer_id];\n\n  if (event.type == PointerEvent::EventType::kDownEvent) {\n    consume_slide_event_status_ = ConsumeSlideEventStatus::kUndefined;\n    can_consume_slide_event_ = false;\n    if (!hit_test_result.empty()) {\n      for (auto target : hit_test_result) {\n        if (target && target->HasConsumeSlideEventAngles()) {\n          can_consume_slide_event_ = true;\n          break;\n        }\n      }\n    }\n    return false;\n  } else if (event.type == PointerEvent::EventType::kMoveEvent) {\n    if (!can_consume_slide_event_) {\n      return false;\n    }\n\n    float distance_x = event.position.x() - down_event_point_.x();\n    float distance_y = event.position.y() - down_event_point_.y();\n\n    if (abs(distance_x) <= ConvertFrom<kPixelTypeLogical>(kTouchSlop) &&\n        abs(distance_y) <= ConvertFrom<kPixelTypeLogical>(kTouchSlop)) {\n      return false;\n    }\n\n    if (consume_slide_event_status_ == ConsumeSlideEventStatus::kUndefined) {\n      consume_slide_event_status_ = ConsumeSlideEventStatus::kUnconsumed;\n      float angle = atan2(distance_y, distance_x) * 180 / M_PI;\n      for (auto target : hit_test_result) {\n        if (target && target->ConsumeSlideEvent(angle)) {\n          consume_slide_event_status_ = ConsumeSlideEventStatus::kConsumed;\n          break;\n        }\n      }\n    }\n  }\n  return consume_slide_event_status_ == ConsumeSlideEventStatus::kConsumed;\n}\n\nvoid GestureManager::DispatchEvent(const PointerEvent& event,\n                                   HitTestResult* hit_test_result) {\n  auto event_copy = event;\n  bool consumed = ConsumeSlideEvent(event);\n  UpdateHitTestTargetResponsive(\n      event.pointer_id, event.type == PointerEvent::EventType::kDownEvent);\n  if (consumed) {\n    if (event.type == PointerEvent::EventType::kUpEvent ||\n        event.type == PointerEvent::EventType::kCancel) {\n      // We must send the cancel event, otherwise the tracking pointers of\n      // gestures won't be cleaned and DidStopTrackingLastPointer can't be\n      // called.\n      event_copy.type = PointerEvent::EventType::kCancel;\n    } else {\n      return;\n    }\n  }\n\n  if (hit_test_result) {\n    for (auto target : *hit_test_result) {\n      if (target) {\n        target->HandleEvent(event_copy);\n      }\n    }\n  }\n  DispatchEventByRouter(event_copy);\n}\n\nvoid GestureManager::DispatchEventByRouter(const PointerEvent& event) {\n  pointer_router_.Route(event);\n}\n\nvoid GestureManager::OnGestureAccepted(int pointer_id,\n                                       GestureRecognizerType type) {\n  if (const auto& it = gesture_accepted_map_.find(pointer_id);\n      it != gesture_accepted_map_.end()) {\n    it->second = type;\n    UpdateHitTestTargetResponsive(pointer_id, false);\n  }\n}\n\nvoid GestureManager::RegisterSignalRoute(const SignalEventRoute& route) {\n  if (!signal_event_route_) {\n    signal_event_route_ = route;\n  }\n}\n\nvoid GestureManager::ResetHitTestTargetResponsive() {\n  hit_test_responsive_result_ = {};\n  if (gesture_mediate_puppet_) {\n    gesture_mediate_puppet_.Act(\n        [responsive_result = hit_test_responsive_result_](auto& impl) {\n          impl.UpdateResponsiveResult(responsive_result);\n        });\n  }\n}\n\nvoid GestureManager::UpdateHitTestTargetResponsive(int pointer_id,\n                                                   bool is_down) {\n  HitTestResponsiveResult old_value = hit_test_responsive_result_;\n  if (is_down) {\n    hit_test_responsive_result_ = HitTestResponsiveResult();\n  }\n  hit_test_responsive_result_.scrollable_direction = ScrollableDirection::kNone;\n  for (auto target : hit_tests_[pointer_id]) {\n    if (!target) {\n      continue;\n    }\n    hit_test_responsive_result_.scrollable_direction =\n        hit_test_responsive_result_.scrollable_direction |\n        target->GetScrollableDirection();\n    if (is_down) {\n      // These values don't change on touch move.\n      hit_test_responsive_result_.tappable |=\n          target->HasTapGestureRecognizer() || target->HasTapEvent();\n      hit_test_responsive_result_.should_block_native_event |=\n          target->ShouldBlockNativeEvent();\n      hit_test_responsive_result_.has_consume_slide_event |=\n          target->HasConsumeSlideEventAngles();\n      // Considering that longpress callback bound in the node is accomplished\n      // by IsolatedGestureDetector, such node only has longpress in its\n      // events_. So we don't take `HasLongPressGestureRecognizer` into\n      // consideration.\n      hit_test_responsive_result_.has_longpress_event |=\n          target->HasLongPressEvent();\n    }\n  }\n\n  if (const auto& it = gesture_accepted_map_.find(pointer_id);\n      it != gesture_accepted_map_.end()) {\n    hit_test_responsive_result_.recognized_gesture_type = it->second;\n  }\n  hit_test_responsive_result_.slide_event_consumed =\n      consume_slide_event_status_ == ConsumeSlideEventStatus::kConsumed;\n\n  if (gesture_mediate_puppet_ &&\n      hit_test_responsive_result_.value != old_value.value) {\n    gesture_mediate_puppet_.Act(\n        [responsive_result = hit_test_responsive_result_](auto& impl) {\n          impl.UpdateResponsiveResult(responsive_result);\n        });\n  }\n}\n\nvoid GestureManager::SendSyntheticWheelEventWithPhaseEnd(\n    const PointerEvent& mouse_wheel_event) {\n  if (signal_event_route_) {\n    signal_event_route_(mouse_wheel_event);\n    signal_event_route_ = nullptr;\n  }\n}\n\nvoid GestureManager::EndMouseWheelTransactionByForce() {\n  auto dummyEvent = PointerEvent(PointerEvent::EventType::kHoverEvent);\n  mouse_wheel_phase_handler_.DispatchWheelEndEventIfNeeded(dummyEvent, true);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/gesture_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_GESTURE_MANAGER_H_\n#define CLAY_UI_GESTURE_GESTURE_MANAGER_H_\n\n#include <functional>\n#include <map>\n#include <memory>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/pixel_helper.h\"\n#include \"clay/shell/common/services/gesture_mediate_service.h\"\n#include \"clay/ui/gesture/arena_manager.h\"\n#include \"clay/ui/gesture/hit_test.h\"\n#include \"clay/ui/gesture/hit_test_responsive_result.h\"\n#include \"clay/ui/gesture/mouse_wheel_phase_handler.h\"\n#include \"clay/ui/gesture/pointer_router.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nstruct PointerEvent;\nusing SignalEventRoute = std::function<void(const PointerEvent&)>;\n\n// Top level class for gestures recognition.\n// It does hit test, collect hit targets and dispatches events to targets.\nclass GestureManager final : public PixelHelper<kPixelTypeClay>,\n                             MouseWheelPhaseHandler::Delegate {\n public:\n  enum class ConsumeSlideEventStatus : uint8_t {\n    kUndefined,\n    kUnconsumed,\n    kConsumed,\n  };\n\n  explicit GestureManager(\n      fml::RefPtr<fml::TaskRunner> task_runner = nullptr,\n      std::shared_ptr<ServiceManager> service_manager = nullptr);\n  GestureManager(const GestureManager&) = delete;\n  GestureManager& operator=(const GestureManager&) = delete;\n\n  bool HandlePointerEvents(HitTestable* root,\n                           std::vector<PointerEvent>& events);\n\n  void RegisterSignalRoute(const SignalEventRoute& route);\n\n  PointerRouter& pointer_router() { return pointer_router_; }\n\n  ArenaManager* arena_manager() const { return arena_manager_.get(); }\n\n  void SetPixelRatio(float pixel_ratio) { pixel_ratio_ = pixel_ratio; }\n\n  float DevicePixelRatio() const override { return pixel_ratio_; }\n\n  void SetListenerForNotCaredPointer(\n      std::function<void(const PointerEvent&)> cb) {\n    arena_manager_->SetListenerForNotCaredPointer(cb);\n  }\n\n  const fml::RefPtr<fml::TaskRunner>& GetTaskRunner() { return task_runner_; }\n\n  void OnGestureAccepted(int pointer_id, GestureRecognizerType type);\n\n  const HitTestResponsiveResult& GetHitTestResponsiveResult() const {\n    return hit_test_responsive_result_;\n  }\n\n  void SendSyntheticWheelEventWithPhaseEnd(const PointerEvent&) override;\n\n  void EndMouseWheelTransactionByForce();\n\n private:\n  bool HandlePointerEvent(HitTestable* root, PointerEvent& event);\n  bool ConsumeSlideEvent(const PointerEvent& event);\n  void DispatchEvent(const PointerEvent& event, HitTestResult* hit_test_result);\n  void DispatchEventByRouter(const PointerEvent& event);\n\n  void ResetHitTestTargetResponsive();\n  void UpdateHitTestTargetResponsive(int pointer_id, bool is_down);\n\n private:\n  float pixel_ratio_ = 1.f;\n  std::unique_ptr<ArenaManager> arena_manager_;\n  PointerRouter pointer_router_;\n  // This route is used for purpose of mediating disputes over which listener\n  // should handle current signal event when multiple listeners exists,\n  // for example, scroll on two nested scrollable view.\n  // It stores the first registered target during traversal hit test targets.\n  // Once triggered, route will be reset.\n  SignalEventRoute signal_event_route_;\n  // We need get value from map and judge if it's empty or not set.\n  std::map<int, HitTestResult> hit_tests_;\n  std::unordered_map<int, GestureRecognizerType> gesture_accepted_map_;\n  const fml::RefPtr<fml::TaskRunner> task_runner_;\n\n  FloatPoint down_event_point_;\n  int down_event_pointer_id_ = 0;\n  bool can_consume_slide_event_ = false;\n  ConsumeSlideEventStatus consume_slide_event_status_ =\n      ConsumeSlideEventStatus::kUndefined;\n  HitTestResponsiveResult hit_test_responsive_result_;\n  Puppet<Owner::kUI, GestureMediateService> gesture_mediate_puppet_;\n  MouseWheelPhaseHandler mouse_wheel_phase_handler_;\n\n  FRIEND_TEST(ScrollViewTest, NestedScrollGestureOnPC);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_GESTURE_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/gesture/gesture_manager_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <future>\n#include <memory>\n\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/thread.h\"\n#include \"clay/gfx/scroll_direction.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/drag_gesture_recognizer.h\"\n#include \"clay/ui/gesture/gesture_manager.h\"\n#include \"clay/ui/gesture/long_press_gesture_recognizer.h\"\n#include \"clay/ui/gesture/macros.h\"\n#include \"clay/ui/gesture/multi_tap_gesture_recognizer.h\"\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nnamespace testing {\n\nconstexpr uint64_t kFastMoveTime = 1000ul;      // in micro second\nconstexpr uint64_t kLongPressTimeout = 1000ul;  // in milli second\n\nstatic int ID() {\n  static int id = 0;\n  return id++;\n}\n\nPointerEvent CreateSinglePointer(int pointer_id, PointerEvent::EventType type,\n                                 FloatPoint position = FloatPoint()) {\n  PointerEvent pointer(type);\n  pointer.buttons = PointerEvent::kPrimary;\n  pointer.pointer_id = pointer_id;\n  pointer.position = position;\n  return pointer;\n}\n\nstd::vector<PointerEvent> CreatePointer(int pointer_id,\n                                        PointerEvent::EventType type,\n                                        FloatPoint position = FloatPoint()) {\n  return {CreateSinglePointer(pointer_id, type, position)};\n}\n\nvoid MovePointer(PointerEvent& pointer, const FloatSize& delta,\n                 uint64_t time_elapsed) {\n  pointer.type = PointerEvent::EventType::kMoveEvent;\n  pointer.delta = delta;\n  pointer.position.MoveBy(delta);\n  pointer.timestamp += time_elapsed;\n}\n\nvoid UpPointer(PointerEvent& pointer) {\n  pointer.type = PointerEvent::EventType::kUpEvent;\n  pointer.delta = FloatSize();\n}\n\n#define MOVE_MULTI_STEPS(steps, args...) \\\n  for (int i = 0; i < steps; ++i) {      \\\n    MovePointer(args);                   \\\n  }\n\n// Default target do nothing.\nclass MockHitTestTargetBase : public HitTestTarget {\n public:\n  virtual ~MockHitTestTargetBase() = default;\n  void HandleEvent(const PointerEvent& event) override {}\n  bool HasDragGestureRecognizer(ScrollDirection direction) const override {\n    return false;\n  }\n  bool HasTapGestureRecognizer() const override { return false; }\n  bool HasLongPressGestureRecognizer() const override { return false; }\n  bool HasTapEvent() const override { return false; }\n  bool HasLongPressEvent() const override { return false; }\n  bool ShouldBlockNativeEvent() const override { return false; }\n};\n\nclass MultiRecognizerHitTestTarget : public MockHitTestTargetBase {\n public:\n  void HandleEvent(const PointerEvent& event) override {\n    if (event.type == PointerEvent::EventType::kDownEvent) {\n      for (const auto& recognizer : recognizers_) {\n        recognizer->AddPointer(event);\n      }\n    }\n  }\n\n  std::list<std::unique_ptr<GestureRecognizer>> recognizers_;\n};\n\nclass MockHitTestable : public HitTestable {\n public:\n  MockHitTestable() = default;\n  virtual ~MockHitTestable() = default;\n\n  bool HitTest(const PointerEvent& event, HitTestResult& result) override {\n    result = hit_test_result_;\n    return true;\n  }\n\n  // Simplify hit test process.\n  HitTestResult hit_test_result_;\n};\n\nclass GestureManagerTest : public ::testing::Test {\n protected:\n  void SetUp() override {\n    thread_ = std::make_unique<fml::Thread>(\"gesture\");\n    fml::AutoResetWaitableEvent latch;\n    task_runner()->PostTask([&]() {\n      hit_test_root_.reset(new MockHitTestable());\n      gesture_manager_.reset(new GestureManager());\n      latch.Signal();\n    });\n    latch.Wait();\n  }\n\n  void TearDown() override {\n    fml::AutoResetWaitableEvent latch;\n    task_runner()->PostTask([&]() {\n      hit_test_root_.reset();\n      gesture_manager_.reset();\n      latch.Signal();\n    });\n    latch.Wait();\n    thread_ = nullptr;\n  }\n\n  fml::RefPtr<fml::TaskRunner> task_runner() {\n    return thread_->GetTaskRunner();\n  }\n\n  void AddHitTestTarget(HitTestTarget* target) {\n    hit_test_root_->hit_test_result_.emplace_back(\n        target->GetHitTestTargetWeakPtr());\n  }\n\n  void ClearHitTestTarget() { hit_test_root_->hit_test_result_.clear(); }\n\n  GestureManager* gesture_manager() { return gesture_manager_.get(); }\n\n  HitTestable* root() { return hit_test_root_.get(); }\n\n private:\n  std::unique_ptr<MockHitTestable> hit_test_root_;\n  std::unique_ptr<GestureManager> gesture_manager_;\n\n  // long press or double tap needs timer.\n  std::unique_ptr<fml::Thread> thread_;\n};\n\n// Test with abnormal case like pointers are not come by sequence.\n// For example, Up/Move comes before Down. Or event with type Unknown.\n// No crash is what we want.\nTEST_F(GestureManagerTest, Exception_Test) {\n  int pointer_id = ID();\n\n  auto pointers =\n      CreatePointer(pointer_id, PointerEvent::EventType::kUnkownEvent);\n  gesture_manager()->HandlePointerEvents(root(), pointers);\n  auto pointers2 =\n      CreatePointer(pointer_id, PointerEvent::EventType::kMoveEvent);\n  gesture_manager()->HandlePointerEvents(root(), pointers2);\n  auto pointers3 = CreatePointer(pointer_id, PointerEvent::EventType::kUpEvent);\n  gesture_manager()->HandlePointerEvents(root(), pointers3);\n  auto pointers4 =\n      CreatePointer(pointer_id, PointerEvent::EventType::kDownEvent);\n  gesture_manager()->HandlePointerEvents(root(), pointers4);\n  auto pointers5 = CreatePointer(pointer_id, PointerEvent::EventType::kUpEvent);\n  gesture_manager()->HandlePointerEvents(root(), pointers5);\n}\n\nTEST_F(GestureManagerTest, NoConflict_Tap_Test) {\n  auto empty_target = std::make_unique<MockHitTestTargetBase>();\n  AddHitTestTarget(empty_target.get());\n\n  auto target_with_tap = std::make_unique<MultiRecognizerHitTestTarget>();\n  auto tap_recognizer =\n      std::make_unique<TapGestureRecognizer>(gesture_manager());\n  bool tapped = false;\n  tap_recognizer->SetTapUpCallback(\n      [&tapped](const PointerEvent& pointer) { tapped = true; });\n  target_with_tap->recognizers_.emplace_back(std::move(tap_recognizer));\n  AddHitTestTarget(target_with_tap.get());\n\n  {\n    auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n    auto& pointer = pointers[0];\n\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    // Although tap recognizer has already won, tap shouldn't callback until\n    // up.\n    EXPECT_TRUE(!tapped);\n\n    MovePointer(pointer, FloatSize(1.f, 1.f), kFastMoveTime);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    EXPECT_TRUE(!tapped);\n\n    UpPointer(pointer);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    EXPECT_TRUE(tapped);\n  }\n\n  // Test drift too much.\n  {\n    tapped = false;\n    auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n    auto& pointer = pointers[0];\n\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    MovePointer(pointer, FloatSize(100.f, 0.f), kFastMoveTime);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    MovePointer(pointer, FloatSize(-100.f, 0.f), kFastMoveTime);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    UpPointer(pointer);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    EXPECT_TRUE(!tapped);\n  }\n}\n\nenum LongPressStatus : uint32_t {\n  kNoLongPressStatus = 0,\n  kLongPressDown = 1 << 0,\n  kLongPressStart = 1 << 1,\n  kLongPressMove = 1 << 2,\n  kLongPressEnd = 1 << 3,\n  kLongPressCancel = 1 << 4,\n};\n\nTEST_F(GestureManagerTest, NoConflict_Long_Press_Test) {\n  constexpr float kDriftTolerance = 20.f;\n  fml::AutoResetWaitableEvent latch;\n  auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n  std::unique_ptr<MockHitTestTargetBase> empty_target;\n  std::unique_ptr<MultiRecognizerHitTestTarget> hit_test_target;\n  std::unique_ptr<LongPressGestureRecognizer> long_press_recognizer;\n  LongPressStatus long_press_status = kNoLongPressStatus;\n  LongPressStatus expected_status = kLongPressDown;\n\n  task_runner()->PostTask([&, this]() {\n    empty_target = std::make_unique<MockHitTestTargetBase>();\n    AddHitTestTarget(empty_target.get());\n\n    hit_test_target = std::make_unique<MultiRecognizerHitTestTarget>();\n    long_press_recognizer = std::make_unique<LongPressGestureRecognizer>(\n        gesture_manager(), kLongPressTimeout);\n    long_press_recognizer->SetTaskRunner(task_runner());\n    long_press_recognizer->SetDriftTolerance(kDriftTolerance);\n    long_press_recognizer->SetLongPressDownCallback(\n        [&long_press_status](const PointerEvent&) {\n          long_press_status =\n              static_cast<LongPressStatus>(long_press_status | kLongPressDown);\n        });\n    long_press_recognizer->SetLongPressStartCallback(\n        [&long_press_status](const PointerEvent&) {\n          long_press_status =\n              static_cast<LongPressStatus>(long_press_status | kLongPressStart);\n        });\n    long_press_recognizer->SetLongPressMoveCallback(\n        [&long_press_status](const PointerEvent&) {\n          long_press_status =\n              static_cast<LongPressStatus>(long_press_status | kLongPressMove);\n        });\n    long_press_recognizer->SetLongPressEndCallback(\n        [&long_press_status](const PointerEvent&) {\n          long_press_status =\n              static_cast<LongPressStatus>(long_press_status | kLongPressEnd);\n        });\n    long_press_recognizer->SetLongPressCancelCallback([&long_press_status]() {\n      long_press_status =\n          static_cast<LongPressStatus>(long_press_status | kLongPressCancel);\n    });\n\n    hit_test_target->recognizers_.emplace_back(\n        std::move(long_press_recognizer));\n    AddHitTestTarget(hit_test_target.get());\n\n    // Normal case\n    {\n      long_press_status = kNoLongPressStatus;\n      auto& pointer = pointers[0];\n\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      EXPECT_EQ(long_press_status, expected_status);\n      MovePointer(pointer, FloatSize(kDriftTolerance - 1.f, 0.f),\n                  kFastMoveTime);\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      // Not trigger start\n      EXPECT_EQ(long_press_status, expected_status);\n\n      task_runner()->PostDelayedTask(\n          [&]() {\n            expected_status =\n                static_cast<LongPressStatus>(expected_status | kLongPressStart);\n            EXPECT_EQ(long_press_status, expected_status);\n\n            MovePointer(pointer, FloatSize(1.f, 0.f), kFastMoveTime);\n            gesture_manager()->HandlePointerEvents(root(), pointers);\n            expected_status =\n                static_cast<LongPressStatus>(expected_status | kLongPressMove);\n            EXPECT_EQ(long_press_status, expected_status);\n\n            UpPointer(pointer);\n            gesture_manager()->HandlePointerEvents(root(), pointers);\n            expected_status =\n                static_cast<LongPressStatus>(expected_status | kLongPressEnd);\n            EXPECT_EQ(long_press_status, expected_status);\n\n            latch.Signal();\n          },\n          fml::TimeDelta::FromMilliseconds(kLongPressTimeout));\n    }\n  });\n  latch.Wait();\n\n  // Test drift too much.\n  {\n    latch.Reset();\n    long_press_status = kNoLongPressStatus;\n    expected_status = kLongPressDown;\n\n    pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n    auto& pointer = pointers[0];\n\n    task_runner()->PostTask([&]() {\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      EXPECT_EQ(long_press_status, expected_status);\n      MovePointer(pointer, FloatSize(kDriftTolerance + 1.f, 0.f),\n                  kFastMoveTime);\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      expected_status =\n          static_cast<LongPressStatus>(expected_status | kLongPressCancel);\n      EXPECT_EQ(long_press_status, expected_status);\n\n      task_runner()->PostDelayedTask(\n          [&]() {\n            EXPECT_EQ(long_press_status, expected_status);\n\n            MovePointer(pointer, FloatSize(1.f, 0.f), kFastMoveTime);\n            gesture_manager()->HandlePointerEvents(root(), pointers);\n            EXPECT_EQ(long_press_status, expected_status);\n\n            UpPointer(pointer);\n            gesture_manager()->HandlePointerEvents(root(), pointers);\n            EXPECT_EQ(long_press_status, expected_status);\n\n            hit_test_target.reset();  // reset at last.\n            empty_target.reset();\n\n            latch.Signal();\n          },\n          fml::TimeDelta::FromMilliseconds(kLongPressTimeout));\n    });\n    latch.Wait();\n  }\n}\n\nenum DragStatus : uint8_t {\n  kNoDragStatus = 0,\n  kHasDragDown = 1 << 0,\n  kHasDragStart = 1 << 1,\n  kHasDragUpdate = 1 << 2,\n  kHasDragEnd = 1 << 3,\n  kHasDragCancel = 1 << 4,\n};\n\nTEST_F(GestureManagerTest, NoConflict_Drag_Normal_Test) {\n  // hypotenuse value is 10.f\n  const FloatSize kMoveStep(6.f, 8.f);\n\n  auto target_with_drag = std::make_unique<MultiRecognizerHitTestTarget>();\n\n  DragStatus expected_status = kNoDragStatus;\n  DragStatus status = kNoDragStatus;\n  auto drag_recognizer =\n      std::make_unique<DragGestureRecognizer>(gesture_manager());\n  drag_recognizer->SetTouchSlop(18.f);\n  drag_recognizer->SetDragDownCallback([&status](const PointerEvent& pointer) {\n    status = static_cast<DragStatus>(status | kHasDragDown);\n  });\n  drag_recognizer->SetDragStartCallback([&status](const FloatPoint& position) {\n    status = static_cast<DragStatus>(status | kHasDragStart);\n  });\n  drag_recognizer->SetDragUpdateCallback(\n      [&status](const FloatPoint& position, const FloatSize& delta) {\n        status = static_cast<DragStatus>(status | kHasDragUpdate);\n      });\n  drag_recognizer->SetDragEndCallback([&status](const Velocity& velocity) {\n    status = static_cast<DragStatus>(status | kHasDragEnd);\n  });\n  drag_recognizer->SetDragCancelCallback([&status]() {\n    status = static_cast<DragStatus>(status | kHasDragCancel);\n  });\n  target_with_drag->recognizers_.emplace_back(std::move(drag_recognizer));\n  AddHitTestTarget(target_with_drag.get());\n\n  auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n  auto& pointer = pointers[0];\n  gesture_manager()->HandlePointerEvents(root(), pointers);\n  // Accepted immediately because no conflict.\n  expected_status = static_cast<DragStatus>(expected_status | kHasDragStart);\n  expected_status = static_cast<DragStatus>(expected_status | kHasDragDown);\n  EXPECT_EQ(status, expected_status);\n\n  MovePointer(pointer, kMoveStep, kFastMoveTime);\n  gesture_manager()->HandlePointerEvents(root(), pointers);\n  expected_status = static_cast<DragStatus>(expected_status | kHasDragUpdate);\n  EXPECT_EQ(status, expected_status);\n\n  for (int i = 0; i < 10; ++i) {\n    MovePointer(pointer, kMoveStep, kFastMoveTime);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n  }\n\n  pointer.type = PointerEvent::EventType::kUpEvent;\n  gesture_manager()->HandlePointerEvents(root(), pointers);\n  expected_status = static_cast<DragStatus>(expected_status | kHasDragEnd);\n  EXPECT_EQ(status, expected_status);\n}\n\n// Mixed tap & drag & long press\nTEST_F(GestureManagerTest, Conflict_Drag_Tap_LongPress_Test) {\n  // hypotenuse value is 10.f\n  const FloatSize kMoveStep(6.f, 8.f);\n\n  auto hit_test_target = std::make_unique<MultiRecognizerHitTestTarget>();\n\n  DragStatus status = kNoDragStatus;\n  auto drag_recognizer =\n      std::make_unique<DragGestureRecognizer>(gesture_manager());\n  drag_recognizer->SetTouchSlop(18.f);\n  drag_recognizer->SetDragDownCallback([&status](const PointerEvent& pointer) {\n    status = static_cast<DragStatus>(status | kHasDragDown);\n  });\n  drag_recognizer->SetDragStartCallback([&status](const FloatPoint& position) {\n    status = static_cast<DragStatus>(status | kHasDragStart);\n  });\n  drag_recognizer->SetDragUpdateCallback(\n      [&status](const FloatPoint& position, const FloatSize& delta) {\n        status = static_cast<DragStatus>(status | kHasDragUpdate);\n      });\n  drag_recognizer->SetDragEndCallback([&status](const Velocity& velocity) {\n    status = static_cast<DragStatus>(status | kHasDragEnd);\n  });\n  drag_recognizer->SetDragCancelCallback([&status]() {\n    status = static_cast<DragStatus>(status | kHasDragCancel);\n  });\n\n  auto tap_recognizer =\n      std::make_unique<TapGestureRecognizer>(gesture_manager());\n  bool tapped = false;\n  tap_recognizer->SetTapUpCallback(\n      [&tapped](const PointerEvent& pointer) { tapped = true; });\n  // Set large tolerance in case tap reject before drag start.\n  tap_recognizer->SetDriftTolerance(50.f);\n\n  auto long_press_recognizer = std::make_unique<LongPressGestureRecognizer>(\n      gesture_manager(), kLongPressTimeout);\n  bool long_press_started = false;\n  long_press_recognizer->SetTaskRunner(task_runner());\n  long_press_recognizer->SetDriftTolerance(20.f);\n  long_press_recognizer->SetLongPressStartCallback(\n      [&long_press_started](const PointerEvent&) {\n        long_press_started = true;\n      });\n\n  hit_test_target->recognizers_.emplace_back(std::move(tap_recognizer));\n  hit_test_target->recognizers_.emplace_back(std::move(drag_recognizer));\n  hit_test_target->recognizers_.emplace_back(std::move(long_press_recognizer));\n  AddHitTestTarget(hit_test_target.get());\n\n  {\n    DragStatus expected_status = kNoDragStatus;\n    status = kNoDragStatus;\n    // Begin drag gesture\n    auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n    auto& pointer = pointers[0];\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    // Because of tap conflict, cannot resolve immediately.\n    expected_status = static_cast<DragStatus>(expected_status | kHasDragDown);\n    EXPECT_EQ(status, expected_status);\n\n    MovePointer(pointer, kMoveStep, kFastMoveTime);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    // Not reach touch slop for recognizing as drag gesture. (10.f < 18.f)\n    EXPECT_EQ(status, expected_status);\n    MovePointer(pointer, kMoveStep, kFastMoveTime);\n    // Moved enough space for drag gesture (20.f > 18.f)\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    expected_status = static_cast<DragStatus>(expected_status | kHasDragStart);\n    EXPECT_EQ(status, expected_status);\n\n    for (int i = 0; i < 10; ++i) {\n      MovePointer(pointer, kMoveStep, kFastMoveTime);\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n    }\n    expected_status = static_cast<DragStatus>(expected_status | kHasDragUpdate);\n\n    pointer.type = PointerEvent::EventType::kUpEvent;\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    expected_status = static_cast<DragStatus>(expected_status | kHasDragEnd);\n    EXPECT_EQ(status, expected_status);\n  }\n\n  EXPECT_FALSE(tapped);\n  {\n    status = kNoDragStatus;\n    // Begin tap gesture: Down -> Move little -> Up\n    auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n    auto& pointer = pointers[0];\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    MovePointer(pointer, kMoveStep, kFastMoveTime);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n\n    pointer.type = PointerEvent::EventType::kUpEvent;\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    EXPECT_TRUE(tapped);\n    // Now that tap is accepted, drag should be canceled.\n    EXPECT_EQ(static_cast<uint8_t>(status & kHasDragCancel), kHasDragCancel);\n  }\n\n  {\n    // Test recognizer reuse.\n    status = kNoDragStatus;\n    // Begin drag gesture\n    auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n    auto& pointer = pointers[0];\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    MovePointer(pointer, kMoveStep, kFastMoveTime);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n\n    for (int i = 0; i < 10; ++i) {\n      MovePointer(pointer, kMoveStep, kFastMoveTime);\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n    }\n\n    pointer.type = PointerEvent::EventType::kUpEvent;\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    EXPECT_EQ(status, 0xf);  // Down | Start | Update | End\n  }\n\n  EXPECT_FALSE(long_press_started);\n}\n\n// Test when same gesture in parent/child, child will always win.\nTEST_F(GestureManagerTest, HitTest_Hierarchy_Test) {\n  int tap_flag = 0;\n\n  auto target_child = std::make_unique<MultiRecognizerHitTestTarget>();\n  auto child_recognizer =\n      std::make_unique<TapGestureRecognizer>(gesture_manager());\n  child_recognizer->SetTapUpCallback(\n      [&tap_flag](const PointerEvent& pointer) { tap_flag = 1; });\n  target_child->recognizers_.emplace_back(std::move(child_recognizer));\n\n  auto target_parent = std::make_unique<MultiRecognizerHitTestTarget>();\n  auto parent_recognizer =\n      std::make_unique<TapGestureRecognizer>(gesture_manager());\n  parent_recognizer->SetTapUpCallback(\n      [&tap_flag](const PointerEvent& pointer) { tap_flag = 2; });\n  target_parent->recognizers_.emplace_back(std::move(parent_recognizer));\n\n  AddHitTestTarget(target_child.get());\n  AddHitTestTarget(target_parent.get());\n\n  int pointer_id = ID();\n  auto pointers =\n      CreatePointer(pointer_id, PointerEvent::EventType::kDownEvent);\n  gesture_manager()->HandlePointerEvents(root(), pointers);\n  auto pointers2 = CreatePointer(pointer_id, PointerEvent::EventType::kUpEvent);\n  gesture_manager()->HandlePointerEvents(root(), pointers2);\n  EXPECT_EQ(tap_flag, 1);  // child wins.\n}\n\nTEST_F(GestureManagerTest, NoConflict_MultiTap_Test) {\n  std::unique_ptr<MockHitTestTargetBase> empty_target;\n  std::unique_ptr<MultiRecognizerHitTestTarget> hit_test_target;\n  std::unique_ptr<MultiTapGestureRecognizer> multi_tap_recognizer;\n  fml::AutoResetWaitableEvent latch;\n  int tap_counts = 0;\n\n  auto init_all = [&]() {\n    tap_counts = 0;\n    empty_target = std::make_unique<MockHitTestTargetBase>();\n    AddHitTestTarget(empty_target.get());\n\n    hit_test_target = std::make_unique<MultiRecognizerHitTestTarget>();\n    multi_tap_recognizer =\n        std::make_unique<MultiTapGestureRecognizer>(gesture_manager());\n    multi_tap_recognizer->SetMultiTapCallback(\n        [&tap_counts](const PointerEvent& pointer, int count) {\n          tap_counts = count;\n        });\n    multi_tap_recognizer->SetTaskRunner(task_runner());\n    hit_test_target->recognizers_.emplace_back(std::move(multi_tap_recognizer));\n    AddHitTestTarget(hit_test_target.get());\n  };\n\n  auto reset_all = [&]() {\n    ClearHitTestTarget();\n    empty_target.reset();\n    hit_test_target.reset();\n    multi_tap_recognizer.reset();\n  };\n\n  task_runner()->PostTask([&, this]() {\n    init_all();\n\n    GESTURE_LOG << \"Test second tap drift too much.\";\n\n    auto pointers =\n        CreatePointer(ID(), PointerEvent::EventType::kDownEvent, {1000, 0});\n    auto& pointer = pointers[0];\n\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n    UpPointer(pointer);\n    gesture_manager()->HandlePointerEvents(root(), pointers);\n\n    auto pointers2 =\n        CreatePointer(ID(), PointerEvent::EventType::kDownEvent, {1000, 0});\n    auto& pointer2 = pointers2[0];\n    gesture_manager()->HandlePointerEvents(root(), pointers2);\n    MovePointer(pointer2, FloatSize(1000.f, 1000.f), kFastMoveTime);\n    gesture_manager()->HandlePointerEvents(root(), pointers2);\n    UpPointer(pointer2);\n    gesture_manager()->HandlePointerEvents(root(), pointers2);\n    EXPECT_EQ(tap_counts, 1);\n    reset_all();\n    latch.Signal();\n  });\n  latch.Wait();\n\n  {\n    GESTURE_LOG << \"Test normal case.\";\n    latch.Reset();\n\n    task_runner()->PostTask([&]() {\n      init_all();\n      auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n      auto& pointer = pointers[0];\n\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      // Although tap recognizer has already won, tap shouldn't callback until\n      // up.\n      EXPECT_EQ(tap_counts, 0);\n\n      UpPointer(pointer);\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      EXPECT_EQ(tap_counts, 1);\n\n      auto pointers2 = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n      auto& pointer2 = pointers2[0];\n      gesture_manager()->HandlePointerEvents(root(), pointers2);\n      EXPECT_EQ(tap_counts, 1);\n      MovePointer(pointer2, FloatSize(1.f, 1.f), kFastMoveTime);\n      gesture_manager()->HandlePointerEvents(root(), pointers2);\n      UpPointer(pointer2);\n      gesture_manager()->HandlePointerEvents(root(), pointers2);\n      EXPECT_EQ(tap_counts, 2);\n      reset_all();\n      latch.Signal();\n    });\n    latch.Wait();\n  }\n\n  // Test drift too much.\n  {\n    GESTURE_LOG << \"Test drift too much.\";\n    latch.Reset();\n    task_runner()->PostTask([&]() {\n      init_all();\n      auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n      auto& pointer = pointers[0];\n\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      // Although tap recognizer has already won, tap shouldn't callback until\n      // up.\n      EXPECT_EQ(tap_counts, 0);\n\n      UpPointer(pointer);\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      EXPECT_EQ(tap_counts, 1);\n\n      auto pointers2 = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n      auto& pointer2 = pointers2[0];\n      // Second tap is too far from the first tap.\n      MovePointer(pointer2, FloatSize(1000.f, 1000.f), kFastMoveTime);\n      gesture_manager()->HandlePointerEvents(root(), pointers2);\n      UpPointer(pointer2);\n      gesture_manager()->HandlePointerEvents(root(), pointers2);\n      EXPECT_EQ(tap_counts, 1);\n\n      // Although pointer 2 is abandoned, pointer 3 still can be accepted.\n      auto pointers3 = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n      auto& pointer3 = pointers3[0];\n      gesture_manager()->HandlePointerEvents(root(), pointers3);\n      UpPointer(pointer3);\n      gesture_manager()->HandlePointerEvents(root(), pointers3);\n      EXPECT_EQ(tap_counts, 2);\n      reset_all();\n      latch.Signal();\n    });\n    latch.Wait();\n  }\n\n  {\n    GESTURE_LOG << \"Test timeout.\";\n    latch.Reset();\n    auto pointers = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n    auto& pointer = pointers[0];\n    auto pointers2 = CreatePointer(ID(), PointerEvent::EventType::kDownEvent);\n    auto& pointer2 = pointers2[0];\n\n    task_runner()->PostTask([&]() {\n      init_all();\n      EXPECT_EQ(tap_counts, 0);\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      UpPointer(pointer);\n      gesture_manager()->HandlePointerEvents(root(), pointers);\n      EXPECT_EQ(tap_counts, 1);\n      task_runner()->PostDelayedTask(\n          [&]() {\n            gesture_manager()->HandlePointerEvents(root(), pointers2);\n            MovePointer(pointer2, {}, 1000);\n            gesture_manager()->HandlePointerEvents(root(), pointers2);\n            UpPointer(pointer2);\n            gesture_manager()->HandlePointerEvents(root(), pointers2);\n            reset_all();\n            latch.Signal();\n          },\n          fml::TimeDelta::FromMilliseconds(1000));\n    });\n    latch.Wait();\n    EXPECT_EQ(tap_counts, 1);\n  }\n}\n\n// TODO(yulitao): Test multi pointer in one sequence.\n\n}  // namespace testing\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/gesture_recognizer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/gesture_recognizer.h\"\n\n#include <math.h>\n\n#include <memory>\n#include <utility>\n\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/gesture/gesture_manager.h\"\n#include \"clay/ui/gesture/macros.h\"\n\nnamespace clay {\n\nvoid GestureRecognizer::AddPointer(const PointerEvent& pointer) {\n  if (delegate_ && !delegate_->IsPointerAllowed(*this, pointer)) {\n    return;\n  }\n  AddAllowedPointer(pointer);\n}\n\nvoid GestureRecognizer::OnGestureAccepted(int pointer_id) {\n  gesture_manager_->OnGestureAccepted(pointer_id, getType());\n}\n\nvoid OneSequenceGestureRecognizer::StartTrackingPointer(int pointer_id) {\n  tracking_pointers_.emplace(pointer_id);\n  GESTURE_LOG << GetMemberTag() << this << \" start tracking pointer \"\n              << pointer_id << \" , add to router id = \" << route_id_;\n  gesture_manager_->pointer_router().AddRoute(\n      pointer_id, route_id_,\n      [this](auto&& PH1) { HandleEvent(std::forward<decltype(PH1)>(PH1)); });\n  arena_entries_[pointer_id] = gesture_manager_->arena_manager()->Add(\n      pointer_id, weak_factory_.GetWeakPtr());\n}\n\nvoid OneSequenceGestureRecognizer::StopTrackingPointer(int pointer_id) {\n  if (tracking_pointers_.count(pointer_id)) {\n    GESTURE_LOG << GetMemberTag() << this << \" stop tracking pointer \"\n                << pointer_id;\n    tracking_pointers_.erase(pointer_id);\n    gesture_manager_->pointer_router().RemoveRoute(pointer_id, route_id_);\n    if (tracking_pointers_.empty()) {\n      DidStopTrackingLastPointer(pointer_id);\n    }\n  }\n}\n\nOneSequenceGestureRecognizer::~OneSequenceGestureRecognizer() {\n  for (int pointer_id : tracking_pointers_) {\n    gesture_manager_->pointer_router().RemoveRoute(pointer_id, route_id_);\n  }\n}\n\nvoid OneSequenceGestureRecognizer::ResolveAll(GestureDisposition disposition) {\n  OnResolveAll(disposition);\n  decltype(arena_entries_) local_entries = std::move(arena_entries_);\n  for (auto& pair : local_entries) {\n    pair.second->Resolve(disposition);\n  }\n}\n\nvoid OneSequenceGestureRecognizer::ResolveOne(int pointer_id,\n                                              GestureDisposition disposition) {\n  OnResolveOne(pointer_id, disposition);\n  auto iter = arena_entries_.find(pointer_id);\n  if (iter != arena_entries_.end()) {\n    std::unique_ptr<ArenaEntry> entry = std::move(iter->second);\n    arena_entries_.erase(iter);\n    entry->Resolve(disposition);\n  }\n}\n\nvoid PrimaryPointerGestureRecognizer::AddAllowedPointer(\n    const PointerEvent& event) {\n  super::AddAllowedPointer(event);\n  if (recognizer_state_ == GestureRecognizerState::kReady) {\n    recognizer_state_ = GestureRecognizerState::kPossible;\n    primary_pointer_ = event.pointer_id;\n    initial_position_ = event.position;\n\n    if (timeout_ms_) {\n      if (!timer_) {\n        timer_ = std::make_unique<fml::OneshotTimer>(\n            timer_runner() ? timer_runner()\n                           : gesture_manager_->GetTaskRunner());\n      }\n      GESTURE_LOG << \"start a timer with timeout: \" << *timeout_ms_;\n      timer_->Start(fml::TimeDelta::FromMilliseconds(*timeout_ms_),\n                    [this] { OnTimeout(); });\n    }\n  }\n}\n\nvoid PrimaryPointerGestureRecognizer::OnTimeout() {\n  FML_DCHECK(false) << \"Now that derived recognizers have set timeout, \"\n                       \"so override to do something!\";\n}\n\nvoid PrimaryPointerGestureRecognizer::HandleEvent(const PointerEvent& event) {\n  GESTURE_LOG << GetMemberTag() << this\n              << \" handle primary pointer event: \" << event.ToString();\n  if (event.pointer_id == primary_pointer_ &&\n      recognizer_state_ == GestureRecognizerState::kPossible) {\n    if ((event.type == PointerEvent::EventType::kMoveEvent ||\n         event.type == PointerEvent::EventType::kPanZoomUpdateEvent) &&\n        !IsWithinDriftTolerance(event.position)) {\n      GESTURE_LOG << \"drift too long to be recognized as primary gesture\";\n      ResolveAll(GestureDisposition::kReject);\n      StopTrackingPointer(event.pointer_id);\n    } else {\n      HandlePrimaryPointerEvent(event);\n    }\n  }\n\n  if (event.type == PointerEvent::EventType::kUpEvent ||\n      event.type == PointerEvent::EventType::kCancel ||\n      event.type == PointerEvent::EventType::kPanZoomEndEvent) {\n    StopTrackingPointer(event.pointer_id);\n  }\n}\n\nbool PrimaryPointerGestureRecognizer::IsWithinDriftTolerance(\n    const FloatPoint& position) {\n  auto delta_point = position - initial_position_;\n  auto delta_size = FloatSize(delta_point.x(), delta_point.y());\n  auto tolerance = drift_tolerance_.value_or(\n      gesture_manager_->ConvertFrom<kPixelTypeLogical>(kTouchSlop));\n  return std::abs(delta_size.distance()) <= tolerance;\n}\n\nvoid PrimaryPointerGestureRecognizer::DidStopTrackingLastPointer(\n    int pointer_id) {\n  Reset();\n}\n\nvoid PrimaryPointerGestureRecognizer::OnGestureAccepted(int pointer_id) {\n  GestureRecognizer::OnGestureAccepted(pointer_id);\n  if (pointer_id == primary_pointer_ && timer_) {\n    timer_.reset();\n  }\n}\n\nvoid PrimaryPointerGestureRecognizer::OnGestureRejected(int pointer_id) {\n  if (pointer_id == primary_pointer_ && timer_) {\n    timer_.reset();\n  }\n}\n\nvoid PrimaryPointerGestureRecognizer::Reset() {\n  initial_position_ = FloatPoint();\n  recognizer_state_ = GestureRecognizerState::kReady;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/gesture_recognizer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_GESTURE_RECOGNIZER_H_\n#define CLAY_UI_GESTURE_GESTURE_RECOGNIZER_H_\n\n#include <map>\n#include <memory>\n#include <optional>\n#include <set>\n#include <unordered_set>\n#include <utility>\n\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/arena_manager.h\"\n#include \"clay/ui/gesture/arena_member.h\"\n#include \"clay/ui/gesture/pointer_router.h\"\n\nnamespace clay {\n\n// From flutter. Indicate how much distance moving is needed before accept.\n#if OS_ANDROID\nconstexpr float kTouchSlop = 8.f;\n#elif OS_HARMONY\nconstexpr float kTouchSlop = 5.f;\n#else\n// touch slop's default value is 8 the same as Lynx.\nconstexpr float kTouchSlop = 8.f;\n#endif\n\nusing OnMultiTapCallback =\n    std::function<void(const PointerEvent& up_event, int tap_counts)>;\nusing OnTapUpCallback = std::function<void(const PointerEvent& up_event)>;\nusing OnTapDownCallback = std::function<void(const PointerEvent& down_event)>;\nusing OnTapCallback = std::function<void()>;\nusing OnTapCancelCallback = std::function<void()>;\n\nclass GestureManager;\n\nenum class GestureRecognizerState {\n  kReady,\n  kPossible,\n  // Not possible to accept, unless state back to kReady.\n  kDefunct,\n};\n\nenum class GestureRecognizerType {\n  kNone = 0,\n  kTap,\n  kDoubleTap,\n  kLongPress,\n  kDragGesture,\n  kHorizontalDrag,\n  kVerticalDrag\n};\n\nclass GestureRecognizer : public ArenaMember {\n public:\n  class Delegate {\n   public:\n    virtual bool IsPointerAllowed(const GestureRecognizer& gesture_recognizer,\n                                  const PointerEvent& event) = 0;\n  };\n\n  explicit GestureRecognizer(GestureManager* gesture_manager)\n      : gesture_manager_(gesture_manager) {\n    FML_DCHECK(gesture_manager_);\n  }\n  virtual ~GestureRecognizer() = default;\n\n  virtual GestureRecognizerType getType() const {\n    return GestureRecognizerType::kNone;\n  }\n\n  // Called when pointer down.\n  void AddPointer(const PointerEvent& pointer);\n  GestureManager* gesture_manager() const { return gesture_manager_; }\n  RouteId route_id() const { return route_id_; }\n  const char* GetMemberTag() const override { return \"[unknown_gesture]\"; }\n  void SetTaskRunner(const fml::RefPtr<fml::TaskRunner>& task_runner) {\n    timer_runner_ = task_runner;\n  }\n  const fml::RefPtr<fml::TaskRunner>& timer_runner() { return timer_runner_; }\n\n  void SetDelegate(Delegate* delegate) { delegate_ = delegate; }\n\n protected:\n  void OnGestureAccepted(int pointer_id) override;\n  void OnGestureRejected(int pointer_id) override {}\n\n  // Called when a pointer down.\n  virtual void AddAllowedPointer(const PointerEvent& pointer) {}\n\n  GestureManager* gesture_manager_;\n  RouteId route_id_ = GenerateRouteId();\n  fml::RefPtr<fml::TaskRunner> timer_runner_;\n  Delegate* delegate_{nullptr};\n};\n\n// All pointers are done in one sequence, that means if at one time point\n// all pointers are up, the recognition will be end.\nclass OneSequenceGestureRecognizer : public GestureRecognizer {\n public:\n  explicit OneSequenceGestureRecognizer(GestureManager* gesture_manager)\n      : GestureRecognizer(gesture_manager) {}\n  virtual ~OneSequenceGestureRecognizer();\n\n  virtual void HandleEvent(const PointerEvent&) = 0;\n\n protected:\n  void AddAllowedPointer(const PointerEvent& pointer) override {\n    StartTrackingPointer(pointer.pointer_id);\n  }\n\n  void StartTrackingPointer(int pointer_id);\n  void StopTrackingPointer(int pointer_id);\n  virtual void DidStopTrackingLastPointer(int pointer_id) {}\n\n  // Resolve and destroy related entries.\n  virtual void ResolveAll(GestureDisposition disposition);\n  virtual void ResolveOne(int pointer_id, GestureDisposition disposition);\n\n  // Called when ResolveAll / ResolveOne.\n  // As OnGestureAccepted / OnGestureRejected are called passively, and not\n  // guaranteed whether will be called when resolve.\n  // So provides this callbacks for listening.\n  virtual void OnResolveAll(GestureDisposition disposition) {}\n  virtual void OnResolveOne(int pointer_id, GestureDisposition disposition) {}\n\n  std::set<int> tracking_pointers_;\n  std::map<int, std::unique_ptr<ArenaEntry>> arena_entries_;\n};\n\n// Only handle one pointer.\nclass PrimaryPointerGestureRecognizer : public OneSequenceGestureRecognizer {\n public:\n  explicit PrimaryPointerGestureRecognizer(GestureManager* gesture_manager,\n                                           std::optional<uint64_t> timeout_ms)\n      : super(gesture_manager), timeout_ms_(timeout_ms) {}\n\n  void HandleEvent(const PointerEvent&) override;\n\n  void SetDriftTolerance(float tolerance) { drift_tolerance_ = tolerance; }\n\n protected:\n  void AddAllowedPointer(const PointerEvent& pointer) override;\n\n  void DidStopTrackingLastPointer(int pointer_id) override;\n\n  void OnGestureAccepted(int pointer_id) override;\n  void OnGestureRejected(int pointer_id) override;\n\n  virtual void HandlePrimaryPointerEvent(const PointerEvent& event) = 0;\n  virtual void OnTimeout();\n\n  int primary_pointer() const { return primary_pointer_; }\n\n  GestureRecognizerState recognizer_state_ = GestureRecognizerState::kReady;\n\n  void Reset();\n\n private:\n  using super = OneSequenceGestureRecognizer;\n\n  bool IsWithinDriftTolerance(const FloatPoint& position);\n\n  // Tolerance of movement between down and up event.\n  std::optional<float> drift_tolerance_;\n  // If set, timer will start when touch down.\n  // Note that if gesture accepted or rejected, timer will be canceled.\n  std::optional<uint64_t> timeout_ms_;\n  std::unique_ptr<fml::OneshotTimer> timer_;\n  FloatPoint initial_position_;\n  int primary_pointer_ = -1;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_GESTURE_RECOGNIZER_H_\n"
  },
  {
    "path": "clay/ui/gesture/hit_test.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_HIT_TEST_H_\n#define CLAY_UI_GESTURE_HIT_TEST_H_\n\n#include <functional>\n#include <list>\n#include <map>\n#include <utility>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/ui/gesture/scrollable_direction.h\"\n\nnamespace clay {\n\nenum class SlideDirection : uint8_t;\nenum class ScrollDirection;\nstruct PointerEvent;\n\nclass HitTestTarget {\n public:\n  HitTestTarget() : hit_test_weak_factory_(this) {}\n\n  virtual void HandleEvent(const PointerEvent& event) = 0;\n\n  virtual bool HasDragGestureRecognizer(ScrollDirection direction) const = 0;\n\n  // TODO(zuojinglong.9): Implement a generic method to handle additional\n  // gesture recognizers.\n  virtual bool HasTapGestureRecognizer() const = 0;\n  virtual bool HasLongPressGestureRecognizer() const = 0;\n\n  // TODO(zuojinglong.9): Implement a generic method to handle additional event\n  // types.\n  virtual bool HasTapEvent() const = 0;\n  virtual bool HasLongPressEvent() const = 0;\n\n  virtual bool ShouldBlockNativeEvent() const = 0;\n\n  virtual bool ShouldPassEventToNative() const { return false; }\n\n  virtual bool HasConsumeSlideEventAngles() const { return false; }\n  virtual bool ConsumeSlideEvent(float angle) { return false; }\n  virtual ScrollableDirection GetScrollableDirection() const {\n    return ScrollableDirection::kNone;\n  }\n\n  fml::WeakPtr<HitTestTarget> GetHitTestTargetWeakPtr() const {\n    return hit_test_weak_factory_.GetWeakPtr();\n  }\n\n protected:\n  fml::WeakPtrFactory<HitTestTarget> hit_test_weak_factory_;\n};\n\nusing HitTestResult = std::list<fml::WeakPtr<HitTestTarget>>;\n\nclass HitTestable {\n public:\n  virtual bool HitTest(const PointerEvent& event, HitTestResult& result) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_HIT_TEST_H_\n"
  },
  {
    "path": "clay/ui/gesture/hit_test_responsive_result.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_HIT_TEST_RESPONSIVE_RESULT_H_\n#define CLAY_UI_GESTURE_HIT_TEST_RESPONSIVE_RESULT_H_\n\n#include \"clay/ui/gesture/gesture_recognizer.h\"\n#include \"clay/ui/gesture/scrollable_direction.h\"\n\nnamespace clay {\n\nunion HitTestResponsiveResult {\n  int32_t value = 0;\n  // CAUTION: Don't change the order of these fields, or you will need to update\n  // related code on the platform side.\n  struct {\n    ScrollableDirection scrollable_direction : 4;\n    GestureRecognizerType recognized_gesture_type : 4;\n    bool tappable : 1;\n    bool should_block_native_event : 1;\n    bool has_consume_slide_event : 1;\n    bool slide_event_consumed : 1;\n    bool has_longpress_event : 1;\n  };\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_HIT_TEST_RESPONSIVE_RESULT_H_\n"
  },
  {
    "path": "clay/ui/gesture/long_press_gesture_recognizer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/long_press_gesture_recognizer.h\"\n\n#include <memory>\n\n#include \"clay/ui/gesture/macros.h\"\n\nnamespace clay {\n\nvoid LongPressGestureRecognizer::Reset() {\n  down_event_.reset();\n  up_event_.reset();\n  state_ = LongPressState::kUnknown;\n}\n\nvoid LongPressGestureRecognizer::HandlePrimaryPointerEvent(\n    const PointerEvent& event) {\n  if (event.type == PointerEvent::EventType::kUpEvent) {\n    up_event_ = std::make_unique<PointerEvent>(event);\n    if (state_ == LongPressState::kDetected) {\n      if (on_long_press_end_) {\n        on_long_press_end_(event);\n      }\n    } else {\n      ResolveAll(GestureDisposition::kReject);\n    }\n    Reset();\n  } else if (event.type == PointerEvent::EventType::kDownEvent) {\n    // We should reset to the initial state on touch down. This is needed to fix\n    // the issue where long press gesture cannot be recognized correctly after a\n    // drag gesture.\n    Reset();\n    down_event_ = std::make_unique<PointerEvent>(event);\n    if (on_long_press_down_) {\n      on_long_press_down_(event);\n    }\n  } else if (event.type == PointerEvent::EventType::kMoveEvent) {\n    if (state_ == LongPressState::kDetected && on_long_press_move_) {\n      on_long_press_move_(event);\n    }\n  } else if (event.type == PointerEvent::EventType::kCancel) {\n    ResolveAll(GestureDisposition::kReject);\n    Reset();\n  }\n}\n\nvoid LongPressGestureRecognizer::OnGestureAccepted(int pointer_id) {\n  // Ignore winning. We care about long press timeout.\n}\n\nvoid LongPressGestureRecognizer::OnResolveAll(GestureDisposition disposition) {\n  GESTURE_LOG << GetMemberTag() << this << \" OnResolveAll with disposition:\"\n              << static_cast<int>(disposition);\n  if (disposition == GestureDisposition::kReject) {\n    if (state_ == LongPressState::kDetected) {\n      Reset();\n    } else {\n      state_ = LongPressState::kDefunct;\n      if (on_long_press_cancel_) {\n        on_long_press_cancel_();\n      }\n    }\n  }\n}\n\nvoid LongPressGestureRecognizer::OnTimeout() {\n  GESTURE_LOG << GetMemberTag() << this\n              << \" OnTimeout current state:\" << static_cast<int>(state_);\n  if (state_ == LongPressState::kDefunct) {\n    return;\n  }\n\n  state_ = LongPressState::kDetected;\n\n  ResolveAll(GestureDisposition::kAccept);\n\n  if (on_long_press_start_ && down_event_) {\n    on_long_press_start_(*down_event_);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/long_press_gesture_recognizer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_LONG_PRESS_GESTURE_RECOGNIZER_H_\n#define CLAY_UI_GESTURE_LONG_PRESS_GESTURE_RECOGNIZER_H_\n\n#include <functional>\n#include <memory>\n\n#include \"clay/ui/gesture/gesture_recognizer.h\"\n\nnamespace clay {\n\nclass GestureManager;\n\n// Some kind like drag gesture, except the trigger condition.\nusing OnLongPressDownCallback = std::function<void(const PointerEvent&)>;\nusing OnLongPressStartCallback = std::function<void(const PointerEvent&)>;\nusing OnLongPressMoveCallback = std::function<void(const PointerEvent&)>;\nusing OnLongPressEndCallback = std::function<void(const PointerEvent&)>;\nusing OnLongPressCancelCallback = std::function<void()>;\n\nclass LongPressGestureRecognizer : public PrimaryPointerGestureRecognizer {\n public:\n  explicit LongPressGestureRecognizer(\n      GestureManager* gesture_manager,\n      uint64_t press_timeout = kLongPressTimeout)\n      : super(gesture_manager, press_timeout) {}\n\n  const char* GetMemberTag() const override { return \"[LongPress]\"; }\n\n  GestureRecognizerType getType() const override {\n    return GestureRecognizerType::kLongPress;\n  }\n\n  void SetLongPressDownCallback(OnLongPressDownCallback&& callback) {\n    on_long_press_down_ = callback;\n  }\n  void SetLongPressStartCallback(OnLongPressStartCallback&& callback) {\n    on_long_press_start_ = callback;\n  }\n  void SetLongPressMoveCallback(OnLongPressMoveCallback&& callback) {\n    on_long_press_move_ = callback;\n  }\n  void SetLongPressEndCallback(OnLongPressEndCallback&& callback) {\n    on_long_press_end_ = callback;\n  }\n  void SetLongPressCancelCallback(OnLongPressCancelCallback&& callback) {\n    on_long_press_cancel_ = callback;\n  }\n\n private:\n  using super = PrimaryPointerGestureRecognizer;\n\n  enum class LongPressState {\n    kUnknown,\n    kDetected,  // is long press.\n    kDefunct,   // can not be long press.\n  };\n\n  void OnGestureAccepted(int pointer_id) override;\n\n  void OnResolveAll(GestureDisposition disposition) override;\n\n  void HandlePrimaryPointerEvent(const PointerEvent& event) override;\n\n  void OnTimeout() override;\n\n  void Reset();\n\n  static constexpr uint64_t kLongPressTimeout = 500ul;\n\n  // 'Accept' doesn't mean it is a real tap. Maybe there no others arena members\n  // competes together. So record accept state and check state when pointer up.\n  LongPressState state_ = LongPressState::kUnknown;\n  std::unique_ptr<PointerEvent> down_event_;\n  std::unique_ptr<PointerEvent> up_event_;\n\n  // Fired immediately after down event.\n  OnLongPressDownCallback on_long_press_down_;\n  // Fired when long press timeout (and of course not rejected)\n  OnLongPressStartCallback on_long_press_start_;\n  // Fired after on_long_press_start_ when event move.\n  OnLongPressMoveCallback on_long_press_move_;\n  // Fired after on_long_press_start_ when event up.\n  OnLongPressEndCallback on_long_press_end_;\n  // Fired before on_long_press_start_ when event up.\n  OnLongPressCancelCallback on_long_press_cancel_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_LONG_PRESS_GESTURE_RECOGNIZER_H_\n"
  },
  {
    "path": "clay/ui/gesture/macros.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_MACROS_H_\n#define CLAY_UI_GESTURE_MACROS_H_\n\nnamespace clay {\n\n#if (DEBUG_GESTURE)\n#define GESTURE_LOG FML_LOG(ERROR) << \"[Gesture] \"\n#else\n#define GESTURE_LOG FML_EAT_STREAM_PARAMETERS(true)\n#endif\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_MACROS_H_\n"
  },
  {
    "path": "clay/ui/gesture/mouse_cursor_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/mouse_cursor_manager.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n\nnamespace clay {\nMouseCursorManager::MouseCursorManager(\n    ActiveCursorCallback active_cursor_callback)\n    : active_cursor_callback_(active_cursor_callback) {}\n\nvoid MouseCursorManager::AddCursorHolder(BaseView* holder) {\n  FML_DCHECK(holder != nullptr);\n\n  if (cursor_cache_.find(holder) != cursor_cache_.end()) {\n    cursor_cache_.erase(holder);\n  }\n\n  cursor_cache_.emplace(holder, nullptr);\n}\n\nvoid MouseCursorManager::DeleteHolder(BaseView* holder) {\n  cursor_cache_.erase(holder);\n}\n\nvoid MouseCursorManager::HandleHitTestResult(\n    const std::list<fml::WeakPtr<BaseView>>& result) {\n  for (const auto& iter : result) {\n    auto* iter_base_view = static_cast<BaseView*>(iter.get());\n    auto cursor_iter = cursor_cache_.find(iter_base_view);\n    if (cursor_iter == cursor_cache_.end() ||\n        iter_base_view->IsInternalView()) {\n      continue;\n    }\n\n    if (iter_base_view->IsInternalView()) {\n      return;\n    }\n    if (cursor_iter->second == nullptr) {\n      BaseView* view = static_cast<BaseView*>(cursor_iter->first);\n      auto* mouse_cursor = view->GetMouseCursor();\n      if (mouse_cursor == nullptr) {\n        cursor_iter->second = &default_cursor_;\n      } else {\n        auto& cursor_vec = mouse_cursor->GetCursors();\n        cursor_iter->second = GetCursorFromCursors(cursor_vec);\n      }\n    }\n\n    DisplayCursor(*cursor_iter->second);\n    return;\n  }\n}\n\nconst Cursor* MouseCursorManager::GetCursorFromCursors(\n    const std::vector<Cursor>& vec) {\n  auto iter = vec.begin();\n  if (iter != vec.end()) {\n    if (iter->type == CursorTypes::kNet || iter->type == CursorTypes::kFile) {\n      // TODO(jiangwenlong) : handle network and file\n    } else {\n      return &(*iter);\n    }\n  }\n  return &default_cursor_;\n}\n\nvoid MouseCursorManager::DisplayCursor(const Cursor& cursor) {\n  if (cursor.type == CursorTypes::kNet || cursor.type == CursorTypes::kFile) {\n    FML_DCHECK(false) << \"TODO(jiangwenlong) : support net and local file\";\n    return;\n  }\n\n  if (active_cursor_callback_) {\n    active_cursor_callback_(cursor);\n  }\n}\n\nvoid MouseCursorManager::CleanCache() {\n  // Now wd don't need this. Support it when we support local and network\n  // cursor.\n  FML_DCHECK(false) << \"TODO(jiangwenlong) : support this\";\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/mouse_cursor_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_MOUSE_CURSOR_MANAGER_H_\n#define CLAY_UI_GESTURE_MOUSE_CURSOR_MANAGER_H_\n\n#include <functional>\n#include <list>\n#include <map>\n#include <string>\n#include <vector>\n\n#include \"clay/net/resource_type.h\"\n#include \"clay/ui/component/mouse_cursor.h\"\n#include \"clay/ui/gesture/hit_test.h\"\n#include \"clay/ui/platform/cursor_types.h\"\n\nnamespace clay {\n\nclass BaseView;\n\nclass MouseCursorManager {\n public:\n  using ActiveCursorCallback = std::function<void(const Cursor&)>;\n\n  explicit MouseCursorManager(ActiveCursorCallback active_cursor_callback);\n\n  void AddCursorHolder(BaseView* holder);\n\n  void DeleteHolder(BaseView* holder);\n\n  // Decide which one to display or not\n  void HandleHitTestResult(const std::list<fml::WeakPtr<BaseView>>& result);\n\n  // clean cursor cache either [cursor_cache_ ] or [FlutterEngine] if it has.\n  void CleanCache();\n\n private:\n  void DisplayCursor(const Cursor& cursor);\n  const Cursor* GetCursorFromCursors(const std::vector<Cursor>& vec);\n\n  const Cursor default_cursor_ = {CursorTypes::kBasic, std::string(\"\")};\n\n  ActiveCursorCallback active_cursor_callback_;\n\n  std::map<BaseView*, const Cursor*> cursor_cache_;\n  // cache net and local resource\n  std::map<std::string, RawResource> resource_cache_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_MOUSE_CURSOR_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/gesture/mouse_region_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/mouse_region_manager.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/page_view.h\"\n\nnamespace clay {\n\nvoid MouseRegionManager::RegisterEnterCallback(BaseView* target,\n                                               EnterCallback callback) {\n  mouse_region_routes_[target].on_enter = callback;\n}\nvoid MouseRegionManager::RegisterLeaveCallback(BaseView* target,\n                                               LeaveCallback callback) {\n  mouse_region_routes_[target].on_leave = callback;\n}\nvoid MouseRegionManager::RegisterHoverCallback(BaseView* target,\n                                               HoverCallback callback) {\n  mouse_region_routes_[target].on_hover = callback;\n}\n\nvoid MouseRegionManager::UnregisterCallback(BaseView* target) {\n  // TODO(yangliu): support multi callbacks\n  mouse_region_routes_.erase(target);\n}\n\nvoid MouseRegionManager::HandleEvents(BaseView* root,\n                                      const std::vector<PointerEvent>& events) {\n  for (auto& event : events) {\n    HandleEvent(root, event);\n  }\n}\nvoid MouseRegionManager::HandleEvent(BaseView* root,\n                                     const PointerEvent& event) {\n  // TODO: Consider to be refactored with TouchEventHandler in future, see:\n  // lynx/core/renderer/events/touch_event_handler.cc.\n  BaseView* top_view = nullptr;\n  if (event.type != PointerEvent::EventType::kCancel) {\n    FloatPoint relative_position;\n    top_view = root->page_view()->GetTopViewToAcceptEvent(event.position,\n                                                          &relative_position);\n  }\n  std::list<fml::WeakPtr<BaseView>> view_chain;\n  if (top_view != nullptr) {\n    if (root->page_view()->GetUIComponentDelegate()) {\n      // Get the origin elements with ancestors from UIComponentDelegate, and\n      // should not affected by z-index attribute.\n      std::list<int32_t> element_chain =\n          root->page_view()->GetUIComponentDelegate()->GetAncestorElements(\n              top_view->GetCallbackId());\n      for (auto id : element_chain) {\n        auto* view = root->page_view()->FindViewByViewId(id);\n        if (view) {\n          view_chain.emplace_back(view->GetWeakPtr());\n        }\n      }\n    } else {\n      // For cases with no UIComponentDelegate like running unittests, we just\n      // get the element chain from the top view.\n      view_chain.emplace_back(top_view->GetWeakPtr());\n      BaseView* parent = top_view->Parent();\n      while (parent != nullptr) {\n        view_chain.emplace_back(parent->GetWeakPtr());\n        parent = parent->Parent();\n      }\n    }\n  }\n\n  mouse_cursor_manager_->HandleHitTestResult(view_chain);\n\n  if (prev_chain_ == view_chain) {\n    // stay in the same mouse region\n    auto target_iter = view_chain.begin();\n    while (target_iter != view_chain.end()) {\n      auto route_iter = mouse_region_routes_.find(target_iter->get());\n      if (route_iter != mouse_region_routes_.end() &&\n          route_iter->second.on_hover) {\n        route_iter->second.on_hover(event);\n      }\n      ++target_iter;\n    }\n    return;\n  }\n\n  // detect the unchanging mouse regions\n  auto curr_riter = view_chain.rbegin();\n  auto prev_riter = prev_chain_.rbegin();\n  while (curr_riter != view_chain.rend() && prev_riter != prev_chain_.rend() &&\n         *curr_riter == *prev_riter) {\n    ++curr_riter;\n    ++prev_riter;\n  }\n\n  // leave old mouse regions from child to parent\n  if (prev_riter != prev_chain_.rend()) {\n    auto target_iter = prev_chain_.begin();\n    auto target_end = prev_riter.base();\n    while (target_iter != target_end) {\n      auto route_iter = mouse_region_routes_.find(target_iter->get());\n      if (route_iter != mouse_region_routes_.end() &&\n          route_iter->second.on_leave) {\n        route_iter->second.on_leave(event);\n      }\n      ++target_iter;\n    }\n  }\n\n  // enter new mouse regions from parent to child\n  if (curr_riter != view_chain.rend()) {\n    auto target_riter = curr_riter;\n    auto target_rend = view_chain.rend();\n    while (target_riter != target_rend) {\n      auto route_iter = mouse_region_routes_.find(target_riter->get());\n      if (route_iter != mouse_region_routes_.end() &&\n          route_iter->second.on_enter) {\n        route_iter->second.on_enter(event);\n      }\n      ++target_riter;\n    }\n  }\n\n  // update chain\n  prev_chain_ = view_chain;\n}\n\nvoid MouseRegionManager::InitSubManager(\n    MouseCursorManager::ActiveCursorCallback active_cursor_callback) {\n  // init MouseCursorManager\n  mouse_cursor_manager_ =\n      std::make_unique<MouseCursorManager>(active_cursor_callback);\n}\n\nvoid MouseRegionManager::AddCursorHolder(BaseView* holder) {\n  FML_DCHECK(mouse_cursor_manager_)\n      << \"MouseRegionManager : should init sub manager\";\n  mouse_cursor_manager_->AddCursorHolder(holder);\n}\n\nvoid MouseRegionManager::ForceUpdateCursor() {\n  mouse_cursor_manager_->HandleHitTestResult(prev_chain_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/mouse_region_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_MOUSE_REGION_MANAGER_H_\n#define CLAY_UI_GESTURE_MOUSE_REGION_MANAGER_H_\n\n#include <functional>\n#include <list>\n#include <map>\n#include <memory>\n#include <vector>\n\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/hit_test.h\"\n#include \"clay/ui/gesture/mouse_cursor_manager.h\"\n\nnamespace clay {\n\nclass HitTestTarget;\nclass BaseView;\n\nclass MouseRegionManager {\n public:\n  MouseRegionManager() = default;\n  MouseRegionManager(const MouseRegionManager&) = delete;\n  MouseRegionManager& operator=(const MouseRegionManager&) = delete;\n\n  using EnterCallback = std::function<void(const PointerEvent&)>;\n  using LeaveCallback = std::function<void(const PointerEvent&)>;\n  using HoverCallback = std::function<void(const PointerEvent&)>;\n\n  void RegisterEnterCallback(BaseView* target, EnterCallback callback);\n  void RegisterLeaveCallback(BaseView* target, LeaveCallback callback);\n  void RegisterHoverCallback(BaseView* target, HoverCallback callback);\n\n  void UnregisterCallback(BaseView* target);\n\n  void HandleEvents(BaseView* root, const std::vector<PointerEvent>& events);\n  void HandleEvent(BaseView* root, const PointerEvent& event);\n\n  // init sub manager. e.g. MouseCursorManager\n  void InitSubManager(\n      MouseCursorManager::ActiveCursorCallback active_cursor_callback);\n\n  void AddCursorHolder(BaseView* holder);\n\n  void ForceUpdateCursor();\n\n private:\n  struct MouseRegionRoute {\n    EnterCallback on_enter = nullptr;\n    LeaveCallback on_leave = nullptr;\n    HoverCallback on_hover = nullptr;\n  };\n\n  std::map<BaseView*, MouseRegionRoute> mouse_region_routes_;\n  std::list<fml::WeakPtr<BaseView>> prev_chain_;\n  std::unique_ptr<MouseCursorManager> mouse_cursor_manager_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_MOUSE_REGION_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/gesture/mouse_region_manager_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/thread.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/gesture/mouse_region_manager.h\"\n#include \"clay/ui/testing/ui_test.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nusing ::testing::ElementsAre;\n\nnamespace clay {\nnamespace testing {\n\nclass MouseRegionManagerTest : public UITest {\n protected:\n  std::vector<PointerEvent> CreateHoverPointer(float x, float y) {\n    return {\n        CreatePointer(-1, clay::PointerEvent::EventType::kHoverEvent, {x, y})};\n  }\n};\n\nTEST_F_UI(MouseRegionManagerTest, EnterLeaveMouseRegion) {\n  //     0     100     200 250    450 600   800\n  //     |---------------|\n  //     |     View1     |\n  // 200 |       |-------------------|------|\n  // 300 |-------|    View3          |      |\n  // 350         |         |--------||      |\n  //             |         |  View4 ||      |\n  //             |         |--------||      |\n  //             |                   |      |\n  // 700         |-------------------|      |\n  //             |                          |\n  //             |           View2          |\n  //             |                          |\n  // 1000        |--------------------------|\n  //\n\n  // the parent-child relation of views:\n  //        view1\n  //     ↙\n  // root\n  //     ↖\n  //        view2 <- view3 <- view4\n\n  auto& root = page_;\n  // View type doesn't matter. All views in the region will be added in.\n  BaseView* View1 = new View(1, root.get());\n  BaseView* View2 = new View(2, root.get());\n  BaseView* View3 = new View(3, root.get());\n  BaseView* View4 = new View(4, root.get());\n  root->AddChild(View1);\n  root->AddChild(View2);\n  View2->AddChild(View3);\n  View3->AddChild(View4);\n  EXPECT_EQ(root->child_count(), 2u);\n\n  root->SetX(0.f);\n  root->SetY(0.f);\n  root->SetWidth(1000.f);\n  root->SetHeight(1000.f);\n\n  View1->SetX(0.f);\n  View1->SetY(0.f);\n  View1->SetWidth(200.f);\n  View1->SetHeight(300.f);\n\n  View2->SetX(100.f);\n  View2->SetY(200.f);\n  View2->SetWidth(800.f);\n  View2->SetHeight(800.f);\n\n  View3->SetX(0.f);\n  View3->SetY(0.f);\n  View3->SetWidth(500.f);\n  View3->SetHeight(500.f);\n\n  View4->SetX(150.f);\n  View4->SetY(100.f);\n  View4->SetWidth(200.f);\n  View4->SetHeight(200.f);\n\n  View3->OnLayoutUpdated();\n  View4->OnLayoutUpdated();\n\n  std::vector<int> views_enter, views_leave;\n  auto on_enter = [&views_enter](int view_id) {\n    views_enter.push_back(view_id);\n  };\n  auto on_leave = [&views_leave](int view_id) {\n    views_leave.push_back(view_id);\n  };\n  auto clear = [&views_enter, &views_leave] {\n    views_enter.clear();\n    views_leave.clear();\n  };\n\n  // register callbacks for mouse entering and leaving mouse regions\n  auto* manager = root->mouse_region_manager();\n  if (manager) {\n    std::vector<BaseView*> views = {root.get(), View1, View2, View3, View4};\n    for (size_t view_id = 0; view_id < views.size(); ++view_id) {\n      auto* view = views[view_id];\n      manager->RegisterEnterCallback(view, std::bind(on_enter, view_id));\n      manager->RegisterLeaveCallback(view, std::bind(on_leave, view_id));\n    }\n  }\n\n  // hover to view4\n  root->DispatchPointerEvent(CreateHoverPointer(400, 400));\n  EXPECT_THAT(views_leave, ElementsAre());\n  EXPECT_THAT(views_enter, ElementsAre(0, 2, 3, 4));\n  clear();\n\n  // hover to view1\n  root->DispatchPointerEvent(CreateHoverPointer(50, 100));\n  EXPECT_THAT(views_leave, ElementsAre(4, 3, 2));\n  EXPECT_THAT(views_enter, ElementsAre(1));\n  clear();\n\n  // hover to view3\n  root->DispatchPointerEvent(CreateHoverPointer(150, 500));\n  EXPECT_THAT(views_leave, ElementsAre(1));\n  EXPECT_THAT(views_enter, ElementsAre(2, 3));\n  clear();\n\n  // hover to view4\n  root->DispatchPointerEvent(CreateHoverPointer(400, 400));\n  EXPECT_THAT(views_leave, ElementsAre());\n  EXPECT_THAT(views_enter, ElementsAre(4));\n  clear();\n\n  // hover to view4\n  root->DispatchPointerEvent(CreateHoverPointer(400, 450));\n  EXPECT_THAT(views_leave, ElementsAre());\n  EXPECT_THAT(views_enter, ElementsAre());\n  clear();\n\n  // hover to view3\n  root->DispatchPointerEvent(CreateHoverPointer(200, 600));\n  EXPECT_THAT(views_leave, ElementsAre(4));\n  EXPECT_THAT(views_enter, ElementsAre());\n  clear();\n}\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/mouse_wheel_phase_handler.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/mouse_wheel_phase_handler.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/macros.h\"\n\nnamespace clay {\n\nconstexpr float kMouseWheelLatchingSlopDistance = 10.0;\n\nMouseWheelPhaseHandler::MouseWheelPhaseHandler(\n    fml::RefPtr<fml::TaskRunner> task_runner, Delegate* delegate)\n    : mouse_wheel_end_dispatcher_timer_(task_runner), delegate_(delegate) {}\n\nvoid MouseWheelPhaseHandler::UpdatePhaseAndScheduleEndEvent(\n    PointerEvent& mouse_wheel_event) {\n  FML_DCHECK(mouse_wheel_event.type == PointerEvent::EventType::kSignalEvent);\n\n  if (mouse_wheel_end_dispatcher_timer_.Stopped()) {\n    GESTURE_LOG << \"mouse wheel transaction begin\";\n    mouse_wheel_event.signal_kind = PointerEvent::SignalKind::kStartScroll;\n    if (std::abs(mouse_wheel_event.scroll_delta_x) >\n        std::abs(mouse_wheel_event.scroll_delta_y)) {\n      likely_scroll_x_ = true;\n      mouse_wheel_event.scroll_delta_y = 0;\n    } else {\n      likely_scroll_x_ = false;\n      mouse_wheel_event.scroll_delta_x = 0;\n    }\n    initial_mouse_wheel_event_ = mouse_wheel_event;\n    ScheduleMouseWheelEndDispatching(mouse_wheel_end_dispatch_timeout_);\n  } else {  // !Stopped\n    mouse_wheel_event.signal_kind = PointerEvent::SignalKind::kScroll;\n    if (likely_scroll_x_) {\n      mouse_wheel_event.scroll_delta_y = 0;\n    } else {\n      mouse_wheel_event.scroll_delta_x = 0;\n    }\n    ScheduleMouseWheelEndDispatching(mouse_wheel_end_dispatch_timeout_);\n  }\n  last_mouse_wheel_event_ = mouse_wheel_event;\n}\n\nvoid MouseWheelPhaseHandler::DispatchWheelEndEventIfNeeded(\n    const PointerEvent& mouse_event, bool force) {\n  if (force) {\n    GESTURE_LOG << \"mouse wheel transaction end by force\";\n    mouse_wheel_end_dispatcher_timer_.FireImmediately();\n  } else if (!mouse_wheel_end_dispatcher_timer_.Stopped() &&\n             mouse_event.type == PointerEvent::EventType::kHoverEvent &&\n             !IsWithinSlopRegion(mouse_event)) {\n    FML_DCHECK(mouse_event.device == PointerEvent::DeviceType::kMouse);\n    GESTURE_LOG << \"mouse wheel transaction end due to move too much\";\n    mouse_wheel_end_dispatcher_timer_.FireImmediately();\n  }\n}\n\nvoid MouseWheelPhaseHandler::ScheduleMouseWheelEndDispatching(\n    fml::TimeDelta timeout) {\n  mouse_wheel_end_dispatcher_timer_.Start(\n      timeout, [this] { SendSyntheticWheelEventWithPhaseEnd(); });\n}\n\nvoid MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnd() {\n  GESTURE_LOG << \"mouse wheel transaction end\";\n  PointerEvent event = last_mouse_wheel_event_;\n  event.signal_kind = PointerEvent::SignalKind::kEndScroll;\n  event.synthesized = true;\n  event.scroll_delta_x = 0;\n  event.scroll_delta_y = 0;\n  if (delegate_) {\n    delegate_->SendSyntheticWheelEventWithPhaseEnd(event);\n  }\n  last_mouse_wheel_event_ = {};\n}\n\nbool MouseWheelPhaseHandler::IsWithinSlopRegion(\n    const PointerEvent& mouse_hover_event) {\n  FloatPoint initial_pointer_position = initial_mouse_wheel_event_.position;\n  return (mouse_hover_event.position - initial_pointer_position).distance() <=\n         kMouseWheelLatchingSlopDistance;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/mouse_wheel_phase_handler.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CLAY_UI_GESTURE_MOUSE_WHEEL_PHASE_HANDLER_H_\n#define CLAY_UI_GESTURE_MOUSE_WHEEL_PHASE_HANDLER_H_\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/time/timer.h\"\n#include \"clay/ui/event/gesture_event.h\"\n\nnamespace clay {\n\nconstexpr fml::TimeDelta kDefaultMouseWheelLatchingTransaction =\n    fml::TimeDelta::FromMilliseconds(500);\n\n/**\n *           |\n *           v\n * +-------------------+\n * |       None        |\n * +-------------------+\n *           |\n *           | (MouseWheelEvent)\n *           v\n * +-------------------+  (mouse move too much)   +------------------+\n * |    StartScroll    |------------------------->|    EndScroll     |\n * +-------------------+      (timeout)           +------------------+\n *           |                                             ^\n *           | (MouseWheelEvent)                           |\n *           v                                             |\n * +-------------------+        (mouse move too much)      |\n * |      Scroll       |-----------------------------------+\n * +-------------------+            (timeout)\n *      ^          |\n *      |          v\n *      |          |\n *      +----------+\n *   (MouseWheelEvent)\n */\nclass MouseWheelPhaseHandler final {\n public:\n  class Delegate {\n   public:\n    virtual void SendSyntheticWheelEventWithPhaseEnd(const PointerEvent&) = 0;\n  };\n\n  MouseWheelPhaseHandler(fml::RefPtr<fml::TaskRunner>, Delegate*);\n\n  // Set after initialization if you want to use different transaction latching.\n  void SetMouseWheelEndDispatchTimeout(fml::TimeDelta timeout) {\n    mouse_wheel_end_dispatch_timeout_ = timeout;\n  }\n\n  void UpdatePhaseAndScheduleEndEvent(PointerEvent&);\n\n  void DispatchWheelEndEventIfNeeded(const PointerEvent&, bool force = false);\n\n private:\n  void ScheduleMouseWheelEndDispatching(fml::TimeDelta timeout);\n  void SendSyntheticWheelEventWithPhaseEnd();\n  bool IsWithinSlopRegion(const PointerEvent& mouse_hover_event);\n\n  fml::OneshotTimer mouse_wheel_end_dispatcher_timer_;\n  fml::TimeDelta mouse_wheel_end_dispatch_timeout_{\n      kDefaultMouseWheelLatchingTransaction};\n  PointerEvent initial_mouse_wheel_event_;\n  PointerEvent last_mouse_wheel_event_;\n  Delegate* delegate_{nullptr};\n  bool likely_scroll_x_{false};\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_MOUSE_WHEEL_PHASE_HANDLER_H_\n"
  },
  {
    "path": "clay/ui/gesture/multi_tap_gesture_recognizer.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/multi_tap_gesture_recognizer.h\"\n\n#include <memory>\n\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/gesture/gesture_manager.h\"\n#include \"clay/ui/gesture/macros.h\"\n\nnamespace clay {\n\n#if (defined(OS_MAC) || defined(OS_WIN))\nconstexpr float kMultiTapSlop = 2.5f;\n#else\nconstexpr float kMultiTapSlop = 18.f;\n#endif\nconstexpr uint64_t kMultiTapTimeout = 300;\n\nTapTracker::TapTracker(GestureManager* gesture_manager,\n                       const PointerEvent& event,\n                       std::unique_ptr<ArenaEntry> entry)\n    : gesture_manager_(gesture_manager),\n      event_(event),\n      arena_entry_(std::move(entry)) {}\n\nTapTracker::~TapTracker() { StopTracking(); }\n\nvoid TapTracker::StartTracking(RouteId route_id, PointerRoute&& route) {\n  has_start_tracking_ = true;\n  route_id_ = route_id;\n  gesture_manager_->pointer_router().AddRoute(event_.pointer_id, route_id_,\n                                              std::move(route));\n}\n\nvoid TapTracker::StopTracking() {\n  if (has_start_tracking_) {\n    has_start_tracking_ = false;\n    gesture_manager_->pointer_router().RemoveRoute(event_.pointer_id,\n                                                   route_id_);\n  }\n}\n\nbool TapTracker::IsWithinTolerance(const PointerEvent& other, float tolerance) {\n  return (other.position - event_.position).distance() < tolerance;\n}\n\nvoid TapTracker::Resolve(GestureDisposition disposition) {\n  arena_entry_->Resolve(disposition);\n}\n\nvoid MultiTapGestureRecognizer::AddAllowedPointer(const PointerEvent& pointer) {\n  if (last_tap_) {\n    if ((first_tap_position_ - pointer.position).distance() >=\n        gesture_manager_->ConvertFrom<kPixelTypeLogical>(kMultiTapSlop)) {\n      // Don't reject. Just abandon this tap as latter taps can trigger gesture.\n      GESTURE_LOG << \"New pointer's position is too far from the first tap. \"\n                     \"Discard this pointer.\";\n      return;\n    }\n  }\n  TrackTap(pointer);\n}\n\nvoid MultiTapGestureRecognizer::TrackTap(const PointerEvent& pointer) {\n  timer_.reset();\n  auto tracker = std::make_unique<TapTracker>(\n      gesture_manager_, pointer,\n      gesture_manager_->arena_manager()->Add(pointer.pointer_id,\n                                             weak_factory_.GetWeakPtr()));\n  tracker->StartTracking(route_id(), [this](auto&& PH1) {\n    HandleEvent(std::forward<decltype(PH1)>(PH1));\n  });\n  tracking_taps_.emplace(pointer.pointer_id, std::move(tracker));\n}\n\nvoid MultiTapGestureRecognizer::HandleEvent(const PointerEvent& event) {\n  auto iter = tracking_taps_.find(event.pointer_id);\n  FML_DCHECK(iter != tracking_taps_.end());\n\n  auto& tracker = iter->second;\n  if (event.type == PointerEvent::EventType::kUpEvent) {\n    HandleMultiTap(tracker);\n  } else if (event.type == PointerEvent::EventType::kMoveEvent) {\n    if (!tracker->IsWithinTolerance(\n            event,\n            gesture_manager_->ConvertFrom<kPixelTypeLogical>(kMultiTapSlop))) {\n      Reject(tracker.get());\n    }\n  }\n}\n\nvoid MultiTapGestureRecognizer::HandleMultiTap(\n    std::unique_ptr<TapTracker>& tracker) {\n  GESTURE_LOG << \"Handle Multi tap \" << tracker->pointer().pointer_id\n              << \" position=(\" << tracker->pointer().position.x() << \",\"\n              << tracker->pointer().position.y() << \")\";\n  timer_ = std::make_unique<fml::OneshotTimer>(\n      timer_runner() ? timer_runner() : gesture_manager_->GetTaskRunner());\n  timer_->Start(fml::TimeDelta::FromMilliseconds(kMultiTapTimeout), [this]() {\n    GESTURE_LOG << \"MultiTap timeout!\";\n    Reset();\n  });\n  current_taps_++;\n  if (last_tap_) {\n    last_tap_->Resolve(GestureDisposition::kAccept);\n  } else {\n    first_tap_position_ = tracker->pointer().position;\n  }\n  if (on_multi_tap_) {\n    on_multi_tap_(tracker->pointer(), current_taps_);\n  }\n  gesture_manager_->arena_manager()->Hold(tracker->pointer().pointer_id);\n  tracker->StopTracking();\n  last_tap_ = std::move(tracker);\n  // ownership transfered to last_tap_\n  tracking_taps_.clear();\n}\n\nvoid MultiTapGestureRecognizer::Reset() {\n  timer_.reset();\n  current_taps_ = 0;\n  if (last_tap_) {\n    auto holder = std::move(last_tap_);\n    holder->Resolve(GestureDisposition::kReject);\n    GESTURE_LOG << \"Release arena when reset.\";\n    gesture_manager_->arena_manager()->Release(holder->pointer().pointer_id);\n  }\n\n  auto temp = std::move(tracking_taps_);\n  for (auto& pair : temp) {\n    Reject(pair.second.get());\n  }\n}\n\nvoid MultiTapGestureRecognizer::Reject(TapTracker* tracker) {\n  auto iter = tracking_taps_.find(tracker->pointer().pointer_id);\n  if (iter == tracking_taps_.end()) {\n    // Already rejected\n    return;\n  }\n  GESTURE_LOG << \"Reject tracker \" << tracker->pointer().pointer_id;\n  auto holder = std::move(iter->second);\n  tracking_taps_.erase(iter);\n  tracker->StopTracking();\n  tracker->Resolve(GestureDisposition::kReject);\n  Reset();\n}\n\nvoid MultiTapGestureRecognizer::OnGestureAccepted(int pointer_id) {}\n\nvoid MultiTapGestureRecognizer::OnGestureRejected(int pointer_id) {\n  auto iter = tracking_taps_.find(pointer_id);\n  if (iter == tracking_taps_.end()) {\n    if (last_tap_ && last_tap_->pointer().pointer_id == pointer_id) {\n      Reset();\n    }\n  } else {\n    Reject(iter->second.get());\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/multi_tap_gesture_recognizer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_MULTI_TAP_GESTURE_RECOGNIZER_H_\n#define CLAY_UI_GESTURE_MULTI_TAP_GESTURE_RECOGNIZER_H_\n\n#include <map>\n#include <memory>\n#include <utility>\n\n#include \"clay/ui/gesture/gesture_recognizer.h\"\n\nnamespace clay {\n\nclass GestureManager;\n\n// Track tap gesture for one pointer for multi taps gestures.\nclass TapTracker {\n public:\n  TapTracker(GestureManager* gesture_manager, const PointerEvent& event,\n             std::unique_ptr<ArenaEntry> entry);\n  ~TapTracker();\n  void StartTracking(RouteId route_id, PointerRoute&& route);\n  void StopTracking();\n  bool IsWithinTolerance(const PointerEvent& other, float tolerance);\n  void Resolve(GestureDisposition disposition);\n\n  const PointerEvent& pointer() { return event_; }\n\n private:\n  bool has_start_tracking_ = false;\n  RouteId route_id_ = 0;\n  GestureManager* gesture_manager_;\n  PointerEvent event_;\n  std::unique_ptr<ArenaEntry> arena_entry_;\n};\n\nclass MultiTapGestureRecognizer : public GestureRecognizer {\n public:\n  explicit MultiTapGestureRecognizer(GestureManager* gesture_manager)\n      : GestureRecognizer(gesture_manager) {}\n\n  // Set Multi Tap Callbacks, index start from 1\n  void SetMultiTapCallback(OnMultiTapCallback&& callback) {\n    on_multi_tap_ = std::move(callback);\n  }\n  const char* GetMemberTag() const override { return \"[Multi-Tap]\"; }\n\n protected:\n  void AddAllowedPointer(const PointerEvent& pointer) override;\n\n  void OnGestureAccepted(int pointer_id) override;\n\n  void OnGestureRejected(int pointer_id) override;\n\n  std::unique_ptr<fml::OneshotTimer> timer_;\n  std::unique_ptr<TapTracker> last_tap_;\n\n  std::map<int, std::unique_ptr<TapTracker>> tracking_taps_;\n  // TODO(wangchen): need to distinguish Tap、TapDown、TapUp、TapCancel\n  OnMultiTapCallback on_multi_tap_;\n  int current_taps_ = 0;\n  FloatPoint first_tap_position_;\n\n  GestureRecognizerType getType() const override {\n    return GestureRecognizerType::kDoubleTap;\n  }\n\n private:\n  void HandleEvent(const PointerEvent& event);\n  void HandleMultiTap(std::unique_ptr<TapTracker>& tracker);\n  void TrackTap(const PointerEvent& event);\n  void Reset();\n  void Reject(TapTracker* tracker);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_MULTI_TAP_GESTURE_RECOGNIZER_H_\n"
  },
  {
    "path": "clay/ui/gesture/pointer_router.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/pointer_router.h\"\n\n#include <algorithm>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/macros.h\"\n\nnamespace clay {\n\nvoid PointerRouter::AddRoute(int pointer_id, RouteId id, PointerRoute&& route) {\n  routes_[pointer_id].emplace_back(id, std::move(route));\n}\n\nvoid PointerRouter::RemoveRoute(int pointer_id, RouteId id) {\n  auto list_iter = routes_.find(pointer_id);\n  if (list_iter == routes_.end()) {\n    // Maybe gesture recognizer has been destructed.\n    return;\n  }\n\n  std::list<std::pair<RouteId, PointerRoute>>& route_list = list_iter->second;\n  auto route_iter = route_list.begin();\n  bool erased = false;\n  for (; route_iter != route_list.end(); ++route_iter) {\n    if (route_iter->first == id) {\n      route_list.erase(route_iter);\n      erased = true;\n      break;\n    }\n  }\n  FML_DCHECK(erased);\n\n  if (route_list.empty()) {\n    routes_.erase(list_iter);\n  }\n}\n\nvoid PointerRouter::Route(const PointerEvent& event) {\n  auto list_iter = routes_.find(event.pointer_id);\n  if (list_iter == routes_.end()) {\n    return;\n  }\n\n  // Copy route list in case it will be removed in callback.\n  auto route_list = list_iter->second;\n  auto& list_ref = list_iter->second;\n  for (std::pair<RouteId, PointerRoute>& route_pair : route_list) {\n    // verify that the route still exists in the list\n    if (std::any_of(list_ref.begin(), list_ref.end(),\n                    [&route_pair](auto& pair) {\n                      return pair.first == route_pair.first;\n                    })) {\n      route_pair.second(event);\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/pointer_router.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_POINTER_ROUTER_H_\n#define CLAY_UI_GESTURE_POINTER_ROUTER_H_\n\n#include <functional>\n#include <list>\n#include <map>\n#include <utility>\n\nnamespace clay {\n\nstruct PointerEvent;\n\nusing PointerRoute = std::function<void(const PointerEvent&)>;\n// As std::function can not do comparison, so use identifier.\nusing RouteId = uint64_t;\n\ninline RouteId GenerateRouteId() {\n  static RouteId id = 0;\n  return id++;\n}\n\nclass PointerRouter {\n public:\n  void AddRoute(int pointer_id, RouteId id, PointerRoute&& route);\n  void RemoveRoute(int pointer_id, RouteId id);\n  void Route(const PointerEvent& event);\n\n private:\n  // Routes within one pointer_id must be ordered, so use list instead of map.\n  std::map<int, std::list<std::pair<RouteId, PointerRoute>>> routes_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_POINTER_ROUTER_H_\n"
  },
  {
    "path": "clay/ui/gesture/scrollable_direction.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_SCROLLABLE_DIRECTION_H_\n#define CLAY_UI_GESTURE_SCROLLABLE_DIRECTION_H_\n\n#include <cstdint>\n#include <type_traits>\n\nnamespace clay {\n\nenum class ScrollableDirection : uint8_t {\n  kNone = 0,\n  kUpwards = 1 << 0,\n  kDownwards = 1 << 1,\n  kLeftwards = 1 << 2,\n  kRightwards = 1 << 3,\n  kVertical = kUpwards | kDownwards,\n  kHorizontal = kLeftwards | kRightwards,\n  kAll = kVertical | kHorizontal,\n};\n\ninline ScrollableDirection operator|(const ScrollableDirection& lhs,\n                                     const ScrollableDirection& rhs) {\n  return static_cast<ScrollableDirection>(\n      static_cast<std::underlying_type_t<ScrollableDirection>>(lhs) |\n      static_cast<std::underlying_type_t<ScrollableDirection>>(rhs));\n}\n\ninline ScrollableDirection& operator|=(ScrollableDirection& lhs,\n                                       const ScrollableDirection& rhs) {\n  lhs = lhs | rhs;\n  return lhs;\n}\n\ninline ScrollableDirection operator&(const ScrollableDirection& lhs,\n                                     const ScrollableDirection& rhs) {\n  return static_cast<ScrollableDirection>(\n      static_cast<std::underlying_type_t<ScrollableDirection>>(lhs) &\n      static_cast<std::underlying_type_t<ScrollableDirection>>(rhs));\n}\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_SCROLLABLE_DIRECTION_H_\n"
  },
  {
    "path": "clay/ui/gesture/tap_gesture_recognizer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/tap_gesture_recognizer.h\"\n\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/gesture/arena_manager.h\"\n#include \"clay/ui/gesture/macros.h\"\n\nnamespace clay {\n\nvoid TapGestureRecognizer::AddAllowedPointer(const PointerEvent& pointer) {\n  if (recognizer_state_ == GestureRecognizerState::kReady) {\n    if (down_event_ || up_event_) {\n      Reset();\n    }\n    down_event_ = std::make_unique<PointerEvent>(pointer);\n  }\n  super::AddAllowedPointer(pointer);\n}\n\nvoid TapGestureRecognizer::Reset() {\n  super::Reset();\n  down_event_.reset();\n  up_event_.reset();\n  accepted_ = false;\n  sent_tap_down_ = false;\n}\n\nvoid TapGestureRecognizer::HandlePrimaryPointerEvent(\n    const PointerEvent& event) {\n  if (event.type == PointerEvent::EventType::kUpEvent) {\n    up_event_ = std::make_unique<PointerEvent>(event);\n    CheckIfTapUp();\n  } else if (event.type == PointerEvent::EventType::kDownEvent) {\n    down_event_ = std::make_unique<PointerEvent>(event);\n    CheckIfTapDown();\n  } else if (event.type == PointerEvent::EventType::kCancel) {\n    ResolveAll(GestureDisposition::kReject);\n    if (sent_tap_down_) {\n      CheckIfTapCancel();\n    }\n    Reset();\n  }\n}\n\nvoid TapGestureRecognizer::CheckIfTapUp() {\n  if (!accepted_ || !up_event_) {\n    return;\n  }\n\n  GESTURE_LOG << GetMemberTag() << this\n              << \" CheckIfTapUp. pointer_id: \" << up_event_->pointer_id;\n\n  if (down_event_->device == PointerEvent::kTouch) {\n    if (on_tap_up_) {\n      on_tap_up_(*up_event_);\n    }\n  } else {\n    if (down_event_->buttons == PointerEvent::kPrimary) {\n      if (on_tap_up_) {\n        on_tap_up_(*up_event_);\n      }\n      if (on_tap_) {\n        on_tap_();\n      }\n    }\n    if (down_event_->buttons == PointerEvent::kSecondary) {\n      if (on_second_tap_) {\n        on_second_tap_();\n      }\n      if (on_second_tap_up_) {\n        on_second_tap_up_(*up_event_);\n      }\n    }\n  }\n  Reset();\n}\n\nvoid TapGestureRecognizer::CheckIfTapDown() {\n  if (sent_tap_down_ || !down_event_) {\n    return;\n  }\n\n  GESTURE_LOG << GetMemberTag() << this\n              << \" CheckIfTapDown. pointer_id: \" << down_event_->pointer_id;\n\n  if (down_event_->buttons == PointerEvent::kPrimary) {\n    if (on_tap_down_) {\n      on_tap_down_(*down_event_);\n    }\n  } else if (down_event_->buttons == PointerEvent::kSecondary) {\n    if (on_second_tap_down_) {\n      on_second_tap_down_(*down_event_);\n    }\n  }\n\n  sent_tap_down_ = true;\n}\n\nvoid TapGestureRecognizer::CheckIfTapCancel() {\n  if (down_event_->buttons == PointerEvent::kPrimary) {\n    if (on_tap_cancel_) {\n      on_tap_cancel_();\n    }\n  } else if (down_event_->buttons == PointerEvent::kSecondary) {\n    if (on_second_tap_cancel_) {\n      on_second_tap_cancel_();\n    }\n  }\n}\n\nvoid TapGestureRecognizer::OnGestureAccepted(int pointer_id) {\n  super::OnGestureAccepted(pointer_id);\n\n  if (primary_pointer() == pointer_id) {\n    accepted_ = true;\n    CheckIfTapUp();\n  }\n}\n\nvoid TapGestureRecognizer::OnGestureRejected(int pointer_id) {\n  super::OnGestureRejected(pointer_id);\n\n  if (primary_pointer() == pointer_id) {\n    Reset();\n  }\n}\n\nvoid TapGestureRecognizer::OnTimeout() {}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/tap_gesture_recognizer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_TAP_GESTURE_RECOGNIZER_H_\n#define CLAY_UI_GESTURE_TAP_GESTURE_RECOGNIZER_H_\n\n#include <functional>\n#include <memory>\n\n#include \"clay/ui/gesture/gesture_recognizer.h\"\n\nnamespace clay {\n\nclass GestureManager;\n\nclass TapGestureRecognizer : public PrimaryPointerGestureRecognizer {\n public:\n  explicit TapGestureRecognizer(GestureManager* gesture_manager)\n      : super(gesture_manager, std::nullopt) {}\n\n  void SetTapUpCallback(OnTapUpCallback&& callback) { on_tap_up_ = callback; }\n  void SetTapCallback(OnTapCallback&& callback) { on_tap_ = callback; }\n  void SetTapDownCallback(OnTapDownCallback&& callback) {\n    on_tap_down_ = callback;\n  }\n  void SetTapCancelCallback(OnTapCancelCallback&& callback) {\n    on_tap_cancel_ = callback;\n  }\n  void SetSecondTapUpCallback(OnTapUpCallback&& callback) {\n    on_second_tap_up_ = callback;\n  }\n  void SetSecondTapDownCallback(OnTapDownCallback&& callback) {\n    on_second_tap_down_ = callback;\n  }\n  void SetSecondTapCallback(OnTapCallback&& callback) {\n    on_second_tap_ = callback;\n  }\n  void SetSecondTapCancelCallback(OnTapCancelCallback&& callback) {\n    on_second_tap_cancel_ = callback;\n  }\n  const char* GetMemberTag() const override { return \"[Tap]\"; }\n\n  GestureRecognizerType getType() const override {\n    return GestureRecognizerType::kTap;\n  }\n\n private:\n  using super = PrimaryPointerGestureRecognizer;\n\n  void AddAllowedPointer(const PointerEvent& pointer) override;\n\n  void OnGestureAccepted(int pointer_id) override;\n  void OnGestureRejected(int pointer_id) override;\n\n  void HandlePrimaryPointerEvent(const PointerEvent& event) override;\n\n  void Reset();\n\n  void CheckIfTapUp();\n\n  void CheckIfTapDown();\n\n  void CheckIfTapCancel();\n\n  void OnTimeout() override;\n\n  // 'Accept' doesn't mean it is a real tap. Maybe there no others arena members\n  // competes together. So record accept state and check state when pointer up.\n  bool accepted_ = false;\n  bool sent_tap_down_ = false;\n  std::unique_ptr<PointerEvent> down_event_;\n  std::unique_ptr<PointerEvent> up_event_;\n  OnTapUpCallback on_tap_up_;\n  OnTapDownCallback on_tap_down_;\n  OnTapCallback on_tap_;\n  OnTapCancelCallback on_tap_cancel_;\n  OnTapUpCallback on_second_tap_up_;\n  OnTapCallback on_second_tap_;\n  OnTapDownCallback on_second_tap_down_;\n  OnTapCancelCallback on_second_tap_cancel_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_TAP_GESTURE_RECOGNIZER_H_\n"
  },
  {
    "path": "clay/ui/gesture/velocity_tracker.cc",
    "content": "// Copyright 2014 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/velocity_tracker.h\"\n\n#include <list>\n\nnamespace clay {\n\nnamespace {\n\n#if defined(OS_HARMONY)\nconstexpr float kVelocityScale = 1.5f;\n#else\nconstexpr float kVelocityScale = 1.0f;\n#endif\n\nconstexpr uint32_t kMicrosPerMills = 1000;\nconstexpr uint32_t kMillsPerSecond = 1000;\nconstexpr uint32_t kMicrosPerSecond = kMicrosPerMills * kMillsPerSecond;\n\n// Threshold between Action::MOVE events for determining that a pointer has\n// stopped moving. Some input devices do not send Action::MOVE events in the\n// case where a pointer has stopped.  We need to detect this case so that we can\n// accurately predict the velocity after the pointer starts moving again.\nconstexpr int32_t kAssumePointerMoveStoppedTimeMicros = 40 * kMicrosPerMills;\n\n// Threshold between Action::MOVE and Action::{UP|POINTER_UP} events for\n// determining that a pointer has stopped moving. This is a larger threshold\n// than |kAssumePointerMoveStoppedTimeMs|, as some devices may delay synthesis\n// of Action::{UP|POINTER_UP} to reduce risk of noisy release.\nconstexpr int32_t kAssumePointerUpStoppedTimeMicros = 80 * kMicrosPerMills;\n\nfloat VectorDot(const float* left, const float* right, uint32_t length) {\n  float result = 0;\n  while (length--) {\n    result += *(left++) * *(right++);\n  }\n  return result;\n}\n\nfloat VectorNorm(const float* vec, uint32_t length) {\n  float result = 0;\n  while (length--) {\n    float value = *(vec++);\n    result += value * value;\n  }\n  return sqrtf(result);\n}\n\n// Velocity tracker algorithm based on least-squares linear regression.\n// degree: 2\n// weighting: none\n// restriction: aligned_directions\nclass LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {\n public:\n  // Number of samples to keep.\n  static const uint8_t kHistorySize = 20;\n\n  // Degree must be no greater than Estimator::kMaxDegree.\n  explicit LeastSquaresVelocityTrackerStrategy(\n      uint32_t degree = Estimator::kDefaultDegree);\n\n  ~LeastSquaresVelocityTrackerStrategy() override = default;\n\n  void Clear() override;\n\n  void AddPosition(uint64_t event_time, const FloatPoint& position) override;\n\n  bool GetEstimator(Estimator* out_estimator) const override;\n\n private:\n  // Sample horizon.\n  // We don't use too much history by default since we want to react to quick\n  // changes in direction.\n  static const int32_t kHorizonMicros = 100 * kMicrosPerMills;\n\n  struct Movement {\n    Movement(uint64_t event_time, const FloatPoint& pos)\n        : event_time_in_micros(event_time), position(pos) {}\n    uint64_t event_time_in_micros = 0;\n    FloatPoint position;\n  };\n\n  const uint32_t degree_;\n  std::list<Movement> movements_;\n};\n\n}  // namespace\n\nVelocity& Velocity::Clamp(float min_velocity, float max_velocity) {\n  float velocity = pixels_per_second_.distance();\n  if (velocity < min_velocity) {\n    pixels_per_second_.ExpandByRatio(min_velocity / velocity);\n  } else if (velocity > max_velocity) {\n    pixels_per_second_.ExpandByRatio(max_velocity / velocity);\n  }\n  return *this;\n}\n\nVelocityTracker::VelocityTracker()\n    : is_first_(true),\n      last_event_time_in_micros_(0),\n      strategy_(new LeastSquaresVelocityTrackerStrategy()) {}\n\nvoid VelocityTracker::Clear() { strategy_->Clear(); }\n\nvoid VelocityTracker::AddPosition(const FloatPoint& position,\n                                  uint64_t event_time_in_micros, bool end) {\n  if (end) {\n    // Note that Action::UP and Action::POINTER_UP always report the last\n    // known position of the pointers that went up.  Action::POINTER_UP does\n    // include the new position of pointers that remained down but we will\n    // also receive an Action::MOVE with this information if any of them\n    // actually moved.  Since we don't know how many pointers will be going up\n    // at once it makes sense to just wait for the following Action::MOVE\n    // before adding the movement. However, if the up event itself is delayed\n    // because of (difficult albeit possible) prolonged stationary screen\n    // contact, assume that motion has stopped.\n    if (event_time_in_micros - last_event_time_in_micros_ >=\n        kAssumePointerUpStoppedTimeMicros) {\n      // We have not received any movements for too long. Assume that all\n      // pointers have stopped.\n      strategy_->Clear();\n      return;\n    }\n  } else if (!is_first_ && (event_time_in_micros - last_event_time_in_micros_ >=\n                            kAssumePointerMoveStoppedTimeMicros)) {\n    // We have not received any movements for too long. Assume that all pointers\n    // have stopped.\n    strategy_->Clear();\n  }\n\n  strategy_->AddPosition(event_time_in_micros, position);\n\n  if (!is_first_) {\n    total_movement_.Expand(position.x() - last_position_.x(),\n                           position.y() - last_position_.y());\n  }\n  is_first_ = false;\n  last_event_time_in_micros_ = event_time_in_micros;\n  last_position_ = position;\n}\n\nVelocityEstimate VelocityTracker::GetVelocityEstimate() {\n  VelocityEstimate result;\n  result.movement = total_movement_;\n  Estimator out_estimator;\n  if (strategy_->GetEstimator(&out_estimator) && out_estimator.degree >= 1) {\n    result.pixels_per_second = {out_estimator.x_coeff[1] * kVelocityScale,\n                                out_estimator.y_coeff[1] * kVelocityScale};\n  } else {\n    result.pixels_per_second = {0, 0};\n  }\n\n  return result;\n}\n\n/**\n * Solves a linear least squares problem to obtain a N degree polynomial that\n * fits the specified input data as nearly as possible.\n *\n * Returns true if a solution is found, false otherwise.\n *\n * The input consists of two vectors of data points X and Y with indices 0..m-1\n * along with a weight vector W of the same size.\n *\n * The output is a vector B with indices 0..n that describes a polynomial\n * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1]\n * X[i] * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is\n * minimized.\n *\n * Accordingly, the weight vector W should be initialized by the caller with the\n * reciprocal square root of the variance of the error in each input data point.\n * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 /\n * stddev(Y[i]).\n * The weights express the relative importance of each data point.  If the\n * weights are* all 1, then the data points are considered to be of equal\n * importance when fitting the polynomial.  It is a good idea to choose weights\n * that diminish the importance of data points that may have higher than usual\n * error margins.\n *\n * Errors among data points are assumed to be independent.  W is represented\n * here as a vector although in the literature it is typically taken to be a\n * diagonal matrix.\n *\n * That is to say, the function that generated the input data can be\n * approximated by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.\n *\n * The coefficient of determination (R^2) is also returned to describe the\n * goodness of fit of the model for the given data.  It is a value between 0\n * and 1, where 1 indicates perfect correspondence.\n *\n * This function first expands the X vector to a m by n matrix A such that\n * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then\n * multiplies it by w[i]./\n *\n * Then it calculates the QR decomposition of A yielding an m by m orthonormal\n * matrix Q and an m by n upper triangular matrix R.  Because R is upper\n * triangular (lower part is all zeroes), we can simplify the decomposition into\n * an m by n matrix Q1 and a n by n matrix R1 such that A = Q1 R1.\n *\n * Finally we solve the system of linear equations given by\n * R1 B = (Q's transpose W Y) to find B.\n *\n * For efficiency, we lay out A and Q column-wise in memory because we\n * frequently operate on the column vectors.  Conversely, we lay out R row-wise.\n *\n * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares\n * http://en.wikipedia.org/wiki/Gram-Schmidt\n */\nstatic bool SolveLeastSquares(const float* x, const float* y, const float* w,\n                              uint32_t m, uint32_t n, float* out_b,\n                              float* out_det) {\n  const uint32_t M_ARRAY_LENGTH = m;\n  const uint32_t N_ARRAY_LENGTH = n;\n\n  // Expand the X vector to a matrix A, pre-multiplied by the weights.\n  float a[N_ARRAY_LENGTH][M_ARRAY_LENGTH];  // column-major order\n  for (uint32_t h = 0; h < m; h++) {\n    a[0][h] = w[h];\n    for (uint32_t i = 1; i < n; i++) {\n      a[i][h] = a[i - 1][h] * x[h];\n    }\n  }\n\n  // Apply the Gram-Schmidt process to A to obtain its QR decomposition.\n\n  // Orthonormal basis, column-major order.\n  float q[N_ARRAY_LENGTH][M_ARRAY_LENGTH];\n  // Upper triangular matrix, row-major order.\n  float r[N_ARRAY_LENGTH][N_ARRAY_LENGTH];\n  for (uint32_t j = 0; j < n; j++) {\n    for (uint32_t h = 0; h < m; h++) {\n      q[j][h] = a[j][h];\n    }\n    for (uint32_t i = 0; i < j; i++) {\n      float dot = VectorDot(&q[j][0], &q[i][0], m);\n      for (uint32_t h = 0; h < m; h++) {\n        q[j][h] -= dot * q[i][h];\n      }\n    }\n\n    float norm = VectorNorm(&q[j][0], m);\n    if (norm < 0.000001f) {\n      // vectors are linearly dependent or zero so no solution\n      return false;\n    }\n\n    float invNorm = 1.0f / norm;\n    for (uint32_t h = 0; h < m; h++) {\n      q[j][h] *= invNorm;\n    }\n    for (uint32_t i = 0; i < n; i++) {\n      r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m);\n    }\n  }\n\n  // Solve R B = Qt W Y to find B.  This is easy because R is upper triangular.\n  // We just work from bottom-right to top-left calculating B's coefficients.\n  float wy[M_ARRAY_LENGTH];\n  for (uint32_t h = 0; h < m; h++) {\n    wy[h] = y[h] * w[h];\n  }\n  for (uint32_t i = n; i-- != 0;) {\n    out_b[i] = VectorDot(&q[i][0], wy, m);\n    for (uint32_t j = n - 1; j > i; j--) {\n      out_b[i] -= r[i][j] * out_b[j];\n    }\n    out_b[i] /= r[i][i];\n  }\n\n  // Calculate the coefficient of determination as 1 - (SSerr / SStot) where\n  // SSerr is the residual sum of squares (variance of the error),\n  // and SStot is the total sum of squares (variance of the data) where each\n  // has been weighted.\n  float y_mean = 0;\n  for (uint32_t h = 0; h < m; h++) {\n    y_mean += y[h];\n  }\n  y_mean /= m;\n\n  float ss_err = 0;\n  float ss_tot = 0;\n  for (uint32_t h = 0; h < m; h++) {\n    float err = y[h] - out_b[0];\n    float term = 1;\n    for (uint32_t i = 1; i < n; i++) {\n      term *= x[h];\n      err -= term * out_b[i];\n    }\n    ss_err += w[h] * w[h] * err * err;\n    float var = y[h] - y_mean;\n    ss_tot += w[h] * w[h] * var * var;\n  }\n  *out_det = ss_tot > 0.000001f ? 1.0f - (ss_err / ss_tot) : 1;\n  return true;\n}\n\nLeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(\n    uint32_t degree)\n    : degree_(degree) {}\n\nvoid LeastSquaresVelocityTrackerStrategy::Clear() { movements_.clear(); }\n\nvoid LeastSquaresVelocityTrackerStrategy::AddPosition(\n    uint64_t event_time_in_micros, const FloatPoint& position) {\n  movements_.emplace_back(event_time_in_micros, position);\n  if (movements_.size() > kHistorySize) {\n    movements_.pop_front();\n  }\n}\n\nbool LeastSquaresVelocityTrackerStrategy::GetEstimator(\n    Estimator* out_estimator) const {\n  out_estimator->Clear();\n\n  if (movements_.empty()) {\n    return false;\n  }\n\n  // Iterate over movement samples in reverse time order and collect samples.\n  float x[kHistorySize];\n  float y[kHistorySize];\n  float w[kHistorySize];\n  float time[kHistorySize];\n\n  const Movement& newest_movement = *(movements_.rbegin());\n  uint32_t sample_num = 0;\n  for (auto iter = movements_.rbegin(); iter != movements_.rend(); ++iter) {\n    uint64_t age =\n        newest_movement.event_time_in_micros - iter->event_time_in_micros;\n    if (age > kHorizonMicros) break;\n\n    const FloatPoint position = iter->position;\n    x[sample_num] = position.x();\n    y[sample_num] = position.y();\n    w[sample_num] = 1.0f;\n    time[sample_num] = -static_cast<float>(age) / kMicrosPerSecond;\n    sample_num++;\n  }\n\n  if (sample_num == 0) return false;  // no data\n\n  // Calculate a least squares polynomial fit.\n  uint32_t degree = degree_;\n  if (degree > sample_num - 1) degree = sample_num - 1;\n\n  if (degree >= 1) {\n    float x_det = 0;\n    float y_det = 0;\n    uint32_t n = degree + 1;\n    if (SolveLeastSquares(time, x, w, sample_num, n, out_estimator->x_coeff,\n                          &x_det) &&\n        SolveLeastSquares(time, y, w, sample_num, n, out_estimator->y_coeff,\n                          &y_det)) {\n      const Movement& oldest_movement = *(movements_.begin());\n      FloatPoint dp = newest_movement.position - oldest_movement.position;\n      if (out_estimator->x_coeff[1] * dp.x() +\n              out_estimator->y_coeff[1] * dp.y() <\n          0) {\n        // Note: Corresponding LSQ2_RESTRICTED in Chromium.\n        // If the velocity is in a\n        // sufficiently different direction from the primary movement, ignore\n        // it.\n        return false;\n      }\n      out_estimator->time = newest_movement.event_time_in_micros;\n      out_estimator->degree = degree;\n      out_estimator->confidence = x_det * y_det;\n      return true;\n    }\n  }\n\n  // No velocity data available for this pointer, but we do have its current\n  // position.\n  out_estimator->x_coeff[0] = x[0];\n  out_estimator->y_coeff[0] = y[0];\n  out_estimator->time = newest_movement.event_time_in_micros;\n  out_estimator->degree = 0;\n  out_estimator->confidence = 1.f;\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/gesture/velocity_tracker.h",
    "content": "// Copyright 2014 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_GESTURE_VELOCITY_TRACKER_H_\n#define CLAY_UI_GESTURE_VELOCITY_TRACKER_H_\n\n#include <memory>\n\n#include \"clay/gfx/geometry/float_point.h\"\n\nnamespace clay {\n\nstruct Estimator {\n  static const uint8_t kMaxDegree = 4;\n  static const uint8_t kDefaultDegree = 2;\n\n  // Estimator time base.\n  int64_t time;\n  // Polynomial coefficients describing motion in X and Y.\n  float x_coeff[kMaxDegree + 1], y_coeff[kMaxDegree + 1];\n  // Polynomial degree (number of coefficients), or zero if no information is\n  // available.\n  uint32_t degree;\n  // Confidence (coefficient of determination), between 0 (no fit)\n  // and 1 (perfect fit).\n  float confidence;\n\n  inline void Clear() {\n    time = 0;\n    degree = 0;\n    confidence = 0;\n    for (size_t i = 0; i <= kMaxDegree; i++) {\n      x_coeff[i] = 0;\n      y_coeff[i] = 0;\n    }\n  }\n};\n\nclass VelocityTrackerStrategy {\n public:\n  virtual ~VelocityTrackerStrategy() = default;\n\n  virtual void Clear() = 0;\n  virtual void AddPosition(uint64_t event_time_in_micros,\n                           const FloatPoint& position) = 0;\n  virtual bool GetEstimator(Estimator* out_estimator) const = 0;\n\n protected:\n  VelocityTrackerStrategy() = default;\n};\n\nclass Velocity {\n public:\n  Velocity() = default;\n  explicit Velocity(const FloatSize& velocity) : pixels_per_second_(velocity) {}\n\n  const FloatSize& pixels_per_second() const { return pixels_per_second_; }\n\n  Velocity& Clamp(float min_velocity, float max_velocity);\n\n private:\n  FloatSize pixels_per_second_;\n};\n\n// A intermediate util class stands for an estimated velocity including\n// informations about how the velocity calculated.\nstruct VelocityEstimate {\n  FloatSize pixels_per_second;\n  // The distance corresponding to the estimated |pixels_per_second|.\n  FloatSize movement;\n};\n\nclass VelocityTracker {\n public:\n  VelocityTracker();\n\n  void AddPosition(const FloatPoint& position, uint64_t event_time_in_micros,\n                   bool end = false);\n  void Clear();\n\n  VelocityEstimate GetVelocityEstimate();\n\n private:\n  bool is_first_;\n  uint64_t last_event_time_in_micros_;\n  FloatPoint last_position_;\n  FloatSize total_movement_;\n  std::unique_ptr<VelocityTrackerStrategy> strategy_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_GESTURE_VELOCITY_TRACKER_H_\n"
  },
  {
    "path": "clay/ui/gesture/velocity_tracker_unittests.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/gesture/velocity_tracker.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nnamespace testing {\n\n#if defined(OS_HARMONY)\nconstexpr float kVelocityScale = 1.5f;\n#else\nconstexpr float kVelocityScale = 1.0f;\n#endif\n\nconstexpr float kMicrosPerSecond = 1000000.0f;\n\nfloat VelocityFromDelta(float delta, uint64_t dt_us) {\n  return delta * kMicrosPerSecond / static_cast<float>(dt_us);\n}\n\nvoid AddSample(VelocityTracker& tracker, const FloatPoint& position,\n               uint64_t time_us, bool end = false) {\n  tracker.AddPosition(position, time_us, end);\n}\n\nvoid AddLinearSamples(VelocityTracker& tracker, uint64_t start_time_us,\n                      uint64_t dt_us, FloatPoint start, const FloatSize& step,\n                      int count) {\n  uint64_t time_us = start_time_us;\n  FloatPoint position = start;\n  for (int i = 0; i < count; ++i) {\n    tracker.AddPosition(position, time_us);\n    time_us += dt_us;\n    position.MoveBy(step);\n  }\n}\n\nclass VelocityTrackerTest : public ::testing::Test {};\n\nTEST_F(VelocityTrackerTest, GetVelocityEstimate_Empty_ReturnsZero) {\n  VelocityTracker tracker;\n  VelocityEstimate estimate = tracker.GetVelocityEstimate();\n  EXPECT_FLOAT_EQ(estimate.pixels_per_second.width(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.pixels_per_second.height(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.width(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.height(), 0.0f);\n}\n\nTEST_F(VelocityTrackerTest, GetVelocityEstimate_OneSample_ReturnsZeroVelocity) {\n  VelocityTracker tracker;\n  AddSample(tracker, FloatPoint(10.0f, 20.0f), 0);\n  VelocityEstimate estimate = tracker.GetVelocityEstimate();\n  EXPECT_FLOAT_EQ(estimate.pixels_per_second.width(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.pixels_per_second.height(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.width(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.height(), 0.0f);\n}\n\nTEST_F(VelocityTrackerTest,\n       GetVelocityEstimate_LinearMotionX_ComputesExpectedVelocityAndMovement) {\n  VelocityTracker tracker;\n  constexpr uint64_t dt_us = 10000;\n  constexpr float dx = 10.0f;\n  constexpr int samples = 6;\n  AddLinearSamples(tracker, 0, dt_us, FloatPoint(0.0f, 0.0f),\n                   FloatSize(dx, 0.0f), samples);\n  VelocityEstimate estimate = tracker.GetVelocityEstimate();\n  float expected_velocity = VelocityFromDelta(dx, dt_us) * kVelocityScale;\n  EXPECT_NEAR(estimate.pixels_per_second.width(), expected_velocity, 1e-3f);\n  EXPECT_NEAR(estimate.pixels_per_second.height(), 0.0f, 1e-3f);\n  EXPECT_FLOAT_EQ(estimate.movement.width(), dx * (samples - 1));\n  EXPECT_FLOAT_EQ(estimate.movement.height(), 0.0f);\n}\n\nTEST_F(\n    VelocityTrackerTest,\n    GetVelocityEstimate_LinearMotionDiagonal_ComputesExpectedVelocityAndMovement) {\n  VelocityTracker tracker;\n  constexpr uint64_t dt_us = 20000;\n  constexpr float dx = 10.0f;\n  constexpr float dy = -16.0f;\n  constexpr int samples = 6;\n  AddLinearSamples(tracker, 0, dt_us, FloatPoint(0.0f, 0.0f), FloatSize(dx, dy),\n                   samples);\n  VelocityEstimate estimate = tracker.GetVelocityEstimate();\n  float expected_velocity_x = VelocityFromDelta(dx, dt_us) * kVelocityScale;\n  float expected_velocity_y = VelocityFromDelta(dy, dt_us) * kVelocityScale;\n  EXPECT_NEAR(estimate.pixels_per_second.width(), expected_velocity_x, 1e-3f);\n  EXPECT_NEAR(estimate.pixels_per_second.height(), expected_velocity_y, 1e-3f);\n  EXPECT_FLOAT_EQ(estimate.movement.width(), dx * (samples - 1));\n  EXPECT_FLOAT_EQ(estimate.movement.height(), dy * (samples - 1));\n}\n\nTEST_F(VelocityTrackerTest,\n       GetVelocityEstimate_OppositeDirectionRejected_ReturnsZeroVelocity) {\n  VelocityTracker tracker;\n  AddSample(tracker, FloatPoint(0.0f, 0.0f), 0);\n  AddSample(tracker, FloatPoint(100.0f, 0.0f), 10000);\n  AddSample(tracker, FloatPoint(1.0f, 0.0f), 20000);\n  VelocityEstimate estimate = tracker.GetVelocityEstimate();\n  EXPECT_FLOAT_EQ(estimate.pixels_per_second.width(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.pixels_per_second.height(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.width(), 1.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.height(), 0.0f);\n}\n\nTEST_F(VelocityTrackerTest,\n       GetVelocityEstimate_GapClearsHistory_UsesRecentSegmentOnly) {\n  VelocityTracker tracker;\n  constexpr uint64_t dt_us = 10000;\n  constexpr uint64_t gap_us = 50000;\n  uint64_t time_us = 0;\n  FloatPoint position(0.0f, 0.0f);\n  AddSample(tracker, position, time_us);\n  time_us += dt_us;\n  position.MoveBy(FloatSize(10.0f, 0.0f));\n  AddSample(tracker, position, time_us);\n  time_us += dt_us;\n  position.MoveBy(FloatSize(10.0f, 0.0f));\n  AddSample(tracker, position, time_us);\n  time_us += gap_us;\n  position.MoveBy(FloatSize(-20.0f, 0.0f));\n  AddSample(tracker, position, time_us);\n  time_us += dt_us;\n  position.MoveBy(FloatSize(-20.0f, 0.0f));\n  AddSample(tracker, position, time_us);\n  time_us += dt_us;\n  position.MoveBy(FloatSize(-20.0f, 0.0f));\n  AddSample(tracker, position, time_us);\n  VelocityEstimate estimate = tracker.GetVelocityEstimate();\n  float expected_velocity = VelocityFromDelta(-20.0f, dt_us) * kVelocityScale;\n  EXPECT_NEAR(estimate.pixels_per_second.width(), expected_velocity, 1e-2f);\n  EXPECT_NEAR(estimate.pixels_per_second.height(), 0.0f, 1e-3f);\n  EXPECT_FLOAT_EQ(estimate.movement.width(), -40.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.height(), 0.0f);\n}\n\nTEST_F(VelocityTrackerTest,\n       GetVelocityEstimate_EndTrueLongGap_ClearsAndDoesNotAddSample) {\n  VelocityTracker tracker;\n  AddSample(tracker, FloatPoint(0.0f, 0.0f), 0);\n  AddSample(tracker, FloatPoint(10.0f, 0.0f), 10000);\n  AddSample(tracker, FloatPoint(10.0f, 0.0f), 90000, true);\n  VelocityEstimate estimate = tracker.GetVelocityEstimate();\n  EXPECT_FLOAT_EQ(estimate.pixels_per_second.width(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.pixels_per_second.height(), 0.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.width(), 10.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.height(), 0.0f);\n}\n\nTEST_F(VelocityTrackerTest,\n       GetVelocityEstimate_StationaryTail_DrivesVelocityToZero) {\n  VelocityTracker tracker;\n  constexpr uint64_t dt_us = 10000;\n  constexpr float dx = 10.0f;\n  constexpr int moving_samples = 6;\n  uint64_t time_us = 0;\n  FloatPoint position(0.0f, 0.0f);\n  for (int i = 0; i < moving_samples; ++i) {\n    AddSample(tracker, position, time_us);\n    time_us += dt_us;\n    if (i < moving_samples - 1) {\n      position.MoveBy(FloatSize(dx, 0.0f));\n    }\n  }\n  for (int i = 0; i < 20; ++i) {\n    AddSample(tracker, position, time_us);\n    time_us += dt_us;\n  }\n  VelocityEstimate estimate = tracker.GetVelocityEstimate();\n  EXPECT_NEAR(estimate.pixels_per_second.width(), 0.0f, 1.0f);\n  EXPECT_NEAR(estimate.pixels_per_second.height(), 0.0f, 1.0f);\n  EXPECT_FLOAT_EQ(estimate.movement.width(), dx * (moving_samples - 1));\n  EXPECT_FLOAT_EQ(estimate.movement.height(), 0.0f);\n}\n\nTEST_F(VelocityTrackerTest,\n       GetVelocityEstimate_IncreasingSpeed_EstimateExceedsLastSample) {\n  VelocityTracker tracker;\n  constexpr uint64_t dt_us = 10000;\n  constexpr int samples = 6;\n  constexpr float dt_s = static_cast<float>(dt_us) / kMicrosPerSecond;\n\n  constexpr float b0_x = 1000.0f;\n  constexpr float b1_x = 1000.0f;\n  constexpr float b2_x = 5000.0f;\n\n  constexpr float b0_y = 500.0f;\n  constexpr float b1_y = 2000.0f;\n  constexpr float b2_y = 3000.0f;\n\n  uint64_t time_us = 0;\n  float x_prev = 0.0f;\n  float y_prev = 0.0f;\n  for (int i = 0; i < samples; ++i) {\n    const int age_steps = (samples - 1) - i;\n    const float t = -static_cast<float>(age_steps) * dt_s;\n    const float x = b0_x + b1_x * t + b2_x * t * t;\n    const float y = b0_y + b1_y * t + b2_y * t * t;\n    AddSample(tracker, FloatPoint(x, y), time_us);\n    if (i == samples - 2) {\n      x_prev = x;\n      y_prev = y;\n    }\n    time_us += dt_us;\n  }\n\n  const float x_newest = b0_x;\n  const float y_newest = b0_y;\n  const float last_velocity_x =\n      VelocityFromDelta(x_newest - x_prev, dt_us) * kVelocityScale;\n  const float last_velocity_y =\n      VelocityFromDelta(y_newest - y_prev, dt_us) * kVelocityScale;\n  VelocityEstimate estimate = tracker.GetVelocityEstimate();\n  EXPECT_GT(estimate.pixels_per_second.width(), last_velocity_x);\n  EXPECT_GT(estimate.pixels_per_second.height(), last_velocity_y);\n}\n\n};  // namespace testing\n\n};  // namespace clay\n"
  },
  {
    "path": "clay/ui/lynx_module/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nui_lynx_module_sources = [\n  \"lynx_ui_method_registrar.cc\",\n  \"lynx_ui_method_registrar.h\",\n  \"lynx_ui_method_types.h\",\n  \"type_utils.cc\",\n  \"type_utils.h\",\n  \"types.cc\",\n  \"types.h\",\n]\n"
  },
  {
    "path": "clay/ui/lynx_module/lynx_module_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/lynx_module/type_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(LynxModuleTypes, TypesUtils) {\n  LynxModuleValues module_values;\n  auto& values = module_values.values;\n\n  values.emplace_back(10);\n  values.emplace_back(20);\n\n  clay::Value::Array array;\n  const char* strs[] = {\"0\", \"1\", \"2\", \"3\", \"4\"};\n  for (int i = 0; i < 5; ++i) {\n    array.emplace_back(strs[i]);\n  }\n  values.emplace_back(std::move(array));\n\n  int int_value_to_cast_1;\n  int int_value_to_cast_2;\n  std::vector<std::string> str_to_cast;\n  CastLynxModuleArgs(module_values, int_value_to_cast_1, int_value_to_cast_2,\n                     str_to_cast);\n  EXPECT_EQ(int_value_to_cast_1, 10);\n  EXPECT_EQ(int_value_to_cast_2, 20);\n  EXPECT_EQ(static_cast<int>(str_to_cast.size()), 5);\n  EXPECT_STREQ(str_to_cast[2].c_str(), \"2\");\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/lynx_module/lynx_ui_method_registrar.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n\n#include <string>\n#include <utility>\n\n#include \"base/include/compiler_specific.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/native_view.h\"\n\nnamespace clay {\n\nLynxUIMethodRegistrar& LynxUIMethodRegistrar::Instance() {\n  static LynxUIMethodRegistrar instance;\n  return instance;\n}\n\nvoid LynxUIMethodRegistrar::Register(std::string&& method_name,\n                                     TypeId validation_data,\n                                     LynxUIMethod&& method) {\n  FML_DCHECK(method);\n  methods_.emplace(std::move(method_name),\n                   Method(validation_data, std::move(method)));\n}\n\nbool LynxUIMethodRegistrar::Invoke(const std::string& method_name,\n                                   BaseView* view, const LynxModuleValues& args,\n                                   const LynxUIMethodCallback& callback) const {\n  auto itr_pair = methods_.equal_range(method_name);\n  for (auto itr = itr_pair.first; itr != itr_pair.second; ++itr) {\n    const Method& method = itr->second;\n    if (view->IsOfType(method.view_type)) {\n      method.method(view, args, callback);\n      return true;\n    }\n  }\n  // Check if we are trying to call platform ui method in platform view\n  if (UNLIKELY(view->Is<NativeView>())) {\n    // Platform Method has two ways to notify callback value.\n    // 1. Platform search callback id to invoke callback.\n    // 2. Platform return result and clay invoke external callback ref\n    clay::Value::Map args_map;\n    auto& mutable_args = const_cast<LynxModuleValues&>(args);\n    for (size_t i = 0; i < mutable_args.names.size(); i++) {\n      args_map.insert(\n          {mutable_args.names[i], std::move(mutable_args.values[i])});\n    }\n    static_cast<clay::NativeView*>(view)->InvokePlatformMethod(\n        method_name, std::move(args_map), callback);\n    return true;\n  }\n\n  callback(LynxUIMethodResult::kMethodNotFound,\n           clay::Value(\"method not found: \" + method_name));\n\n  FML_LOG(ERROR) << \"UI Method (\" << method_name << \") can't match view type\";\n  return false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/lynx_module/lynx_ui_method_registrar.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_LYNX_MODULE_LYNX_UI_METHOD_REGISTRAR_H_\n#define CLAY_UI_LYNX_MODULE_LYNX_UI_METHOD_REGISTRAR_H_\n\n#include <functional>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"clay/ui/common/type_info.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_types.h\"\n#include \"clay/ui/lynx_module/types.h\"\n\nnamespace clay {\n\nclass BaseView;\n\nusing LynxUIMethodCallback =\n    std::function<void(LynxUIMethodResult code, clay::Value data)>;\n\nusing LynxUIMethod =\n    std::function<void(BaseView* view, const LynxModuleValues& args,\n                       const LynxUIMethodCallback& callback)>;\n\nclass LynxUIMethodRegistrar {\n public:\n  static LynxUIMethodRegistrar& Instance();\n\n  void Register(std::string&& method_name, TypeId view_type,\n                LynxUIMethod&& method);\n\n  bool Invoke(const std::string& method_name, BaseView* view,\n              const LynxModuleValues& args,\n              const LynxUIMethodCallback& callback) const;\n\n private:\n  struct Method {\n    // Validation can be null.\n    TypeId view_type;\n    LynxUIMethod method;\n\n    Method(Method&& other)\n        : view_type(other.view_type), method(std::move(other.method)) {}\n\n    Method(TypeId v, LynxUIMethod&& m) : view_type(v), method(std::move(m)) {}\n  };\n\n  std::unordered_multimap<std::string, Method> methods_;\n};\n\n// Overloading `GenerateLynxUIMethod()` so that we can accept UI methods with\n// different args.\ntemplate <typename ViewClass>\nLynxUIMethod GenerateLynxUIMethod(void (ViewClass::*ui_method)(\n    const LynxModuleValues& args, const LynxUIMethodCallback& callback)) {\n  return [ui_method](BaseView* view, const LynxModuleValues& args,\n                     const LynxUIMethodCallback& callback) {\n    (static_cast<ViewClass*>(view)->*ui_method)(args, callback);\n  };\n}\n\ntemplate <typename ViewClass>\nLynxUIMethod GenerateLynxUIMethod(\n    void (ViewClass::*ui_method)(const LynxModuleValues& args)) {\n  return [ui_method](BaseView* view, const LynxModuleValues& args,\n                     const LynxUIMethodCallback& callback) {\n    (static_cast<ViewClass*>(view)->*ui_method)(args);\n    callback(LynxUIMethodResult::kSuccess, clay::Value());\n  };\n}\n\ntemplate <typename ViewClass>\nLynxUIMethod GenerateLynxUIMethod(\n    void (ViewClass::*ui_method)(const LynxUIMethodCallback& callback)) {\n  return [ui_method](BaseView* view, const LynxModuleValues& args,\n                     const LynxUIMethodCallback& callback) {\n    (static_cast<ViewClass*>(view)->*ui_method)(callback);\n  };\n}\n\ntemplate <typename ViewClass>\nLynxUIMethod GenerateLynxUIMethod(void (ViewClass::*ui_method)()) {\n  return [ui_method](BaseView* view, const LynxModuleValues& args,\n                     const LynxUIMethodCallback& callback) {\n    (static_cast<ViewClass*>(view)->*ui_method)();\n    callback(LynxUIMethodResult::kSuccess, clay::Value());\n  };\n}\n\n#define LYNX_UI_METHOD_BEGIN(__view__)   \\\n  struct __view__##Registrar {           \\\n    static __view__##Registrar registar; \\\n    __view__##Registrar()\n\n#define LYNX_UI_METHOD_END(__view__) \\\n  }                                  \\\n  ;                                  \\\n  __view__##Registrar __view__##Registrar::registar\n\n#define LYNX_UI_METHOD(__view__, __method__)  \\\n  LynxUIMethodRegistrar::Instance().Register( \\\n      \"\" #__method__, __view__::StaticType(), \\\n      GenerateLynxUIMethod(&__view__::__method__))\n\n}  // namespace clay\n\n#endif  // CLAY_UI_LYNX_MODULE_LYNX_UI_METHOD_REGISTRAR_H_\n"
  },
  {
    "path": "clay/ui/lynx_module/lynx_ui_method_types.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_LYNX_MODULE_LYNX_UI_METHOD_TYPES_H_\n#define CLAY_UI_LYNX_MODULE_LYNX_UI_METHOD_TYPES_H_\n\nnamespace clay {\n\n// should sync with LynxUIMethodConstants.java in Lynx.\nenum class LynxUIMethodResult {\n  kSuccess = 0,\n  kUnknown = 1,\n  kNodeNotFound = 2,\n  kMethodNotFound = 3,\n  kParamInvalid = 4,\n  kSelectorNotSupported = 5,\n  kNoUiForNode = 6,\n  kInvalidStateError = 7,\n  kOperationError = 8\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_LYNX_MODULE_LYNX_UI_METHOD_TYPES_H_\n"
  },
  {
    "path": "clay/ui/lynx_module/lynx_ui_method_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/testing/ui_test.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nclass LynxUIMethodTest : public UITest {};\n\nTEST_F_UI(LynxUIMethodTest, InvokeTest) {\n  bool called = false;\n  LynxUIMethodResult ret_code;\n  auto callback = [&](LynxUIMethodResult code, clay::Value data) {\n    called = true;\n    ret_code = code;\n  };\n\n  auto& registar = clay::LynxUIMethodRegistrar::Instance();\n\n  // Call none-exists mothod\n  auto* view = new View(0, page_.get());\n  LynxModuleValues values;\n  bool ret = registar.Invoke(\"selectTab\", view, values, callback);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(called);\n  EXPECT_EQ(ret_code, LynxUIMethodResult::kMethodNotFound);\n  called = false;\n\n  // Call existing method on BaseView\n  ret = registar.Invoke(\"setFocus\", view, values, callback);\n  EXPECT_TRUE(ret);\n  EXPECT_TRUE(called);\n  EXPECT_EQ(ret_code, LynxUIMethodResult::kSuccess);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/lynx_module/type_utils.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/lynx_module/type_utils.h\"\n\n#include <cstring>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nvoid LynxModuleArgCastor<bool>::Cast(const clay::Value& value,\n                                     bool& dst_value) {\n  dst_value = attribute_utils::GetBool(value, dst_value);\n}\n\nvoid LynxModuleArgCastor<int32_t>::Cast(const clay::Value& value,\n                                        int32_t& dst_value) {\n  dst_value = static_cast<int>(attribute_utils::GetNum(value, dst_value));\n}\n\nvoid LynxModuleArgCastor<uint32_t>::Cast(const clay::Value& value,\n                                         uint32_t& dst_value) {\n  dst_value =\n      static_cast<unsigned int>(attribute_utils::GetNum(value, dst_value));\n}\n\nvoid LynxModuleArgCastor<float>::Cast(const clay::Value& value,\n                                      float& dst_value) {\n  dst_value = static_cast<float>(attribute_utils::GetNum(value, dst_value));\n}\n\nvoid LynxModuleArgCastor<std::string>::Cast(const clay::Value& value,\n                                            std::string& dst_value) {\n  dst_value = attribute_utils::GetCString(value, dst_value);\n}\n\nvoid LynxModuleArgCastor<LynxModuleValues>::Cast(const clay::Value& value,\n                                                 LynxModuleValues& dst_value) {\n  FML_DCHECK(value.IsMap());\n  if (!value.IsMap()) {\n    return;\n  }\n  dst_value.names.clear();\n  dst_value.values.clear();\n  auto& mutable_value = const_cast<clay::Value&>(value);\n  for (auto& [key, value] : mutable_value.GetMap()) {\n    dst_value.names.push_back(key);\n    dst_value.values.push_back(std::move(value));\n  }\n}\n\nvoid LynxModuleArgCastor<clay::Value::Map>::Cast(const clay::Value& value,\n                                                 clay::Value::Map& dst_value) {\n  FML_DCHECK(value.IsMap());\n  if (!value.IsMap()) {\n    return;\n  }\n  auto& mutable_value = const_cast<clay::Value&>(value);\n  dst_value = std::move(mutable_value.GetMap());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/lynx_module/type_utils.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_LYNX_MODULE_TYPE_UTILS_H_\n#define CLAY_UI_LYNX_MODULE_TYPE_UTILS_H_\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <type_traits>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/lynx_module/types.h\"\n\nnamespace clay {\n\n// Cast a `clay::Value` to a c++ value. The default `Cast()` is not implemented.\n// Define a specialization to define the casting behaviour.\ntemplate <typename DstType>\nstruct LynxModuleArgCastor {\n  static void Cast(const clay::Value& value, DstType& dst_value) = delete;\n};\n\ntemplate <>\nstruct LynxModuleArgCastor<bool> {\n  static void Cast(const clay::Value& value, bool& dst_value);\n};\n\ntemplate <>\nstruct LynxModuleArgCastor<int32_t> {\n  static void Cast(const clay::Value& value, int32_t& dst_value);\n};\n\ntemplate <>\nstruct LynxModuleArgCastor<uint32_t> {\n  static void Cast(const clay::Value& value, uint32_t& dst_value);\n};\n\ntemplate <>\nstruct LynxModuleArgCastor<float> {\n  static void Cast(const clay::Value& value, float& dst_value);\n};\n\ntemplate <>\nstruct LynxModuleArgCastor<std::string> {\n  static void Cast(const clay::Value& value, std::string& dst_value);\n};\n\ntemplate <>\nstruct LynxModuleArgCastor<LynxModuleValues> {\n  static void Cast(const clay::Value& value, LynxModuleValues& dst_value);\n};\n\ntemplate <>\nstruct LynxModuleArgCastor<clay::Value::Map> {\n  static void Cast(const clay::Value& value, clay::Value::Map& dst_value);\n};\n\ntemplate <class T>\nstruct LynxModuleArgCastor<std::vector<T>> {\n  static void Cast(const clay::Value& value, std::vector<T>& dst_value) {\n    FML_DCHECK(value.IsArray());\n    if (!value.IsArray()) {\n      return;\n    }\n    auto& array = value.GetArray();\n    dst_value.resize(array.size());\n    for (size_t i = 0; i < array.size(); ++i) {\n      LynxModuleArgCastor<T>::Cast(array[i], dst_value[i]);\n    }\n  }\n};\n\n// Casting multiple `clay::Value`s to the destination objects.\n// NOTE: You must initialize the dst_values first. If a parsing error occurs,\n// the passed value will be used.\ntemplate <typename... DstTypes>\nbool CastLynxModuleArgs(const LynxModuleValues& values,\n                        DstTypes&... dst_values) {\n  FML_DCHECK(values.values.size() >= sizeof...(dst_values));\n  if (values.values.size() < sizeof...(dst_values)) {\n    return false;\n  }\n  std::size_t i{0};\n  ((LynxModuleArgCastor<DstTypes>::Cast(values.values[i++], dst_values)), ...);\n  return true;\n}\n\ntemplate <typename... DstTypes>\nbool CastNamedLynxModuleArgs(const std::vector<std::string>& arg_names,\n                             const LynxModuleValues& values,\n                             DstTypes&... dst_values) {\n  FML_DCHECK(arg_names.size() != 0);\n  FML_DCHECK(arg_names.size() == sizeof...(dst_values));\n  std::size_t i{0};\n  ((LynxModuleArgCastor<DstTypes>::Cast(values.Get(arg_names[i++]),\n                                        dst_values)),\n   ...);\n  return true;\n}\n\n}  // namespace clay\n\n#endif  // CLAY_UI_LYNX_MODULE_TYPE_UTILS_H_\n"
  },
  {
    "path": "clay/ui/lynx_module/types.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/lynx_module/types.h\"\n\n#include <string>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nLynxModuleValues::LynxModuleValues() {}\n\nLynxModuleValues::~LynxModuleValues() {\n  FML_DCHECK(cleanup_callback == nullptr) << \"Forget to cleanup.\";\n}\n\nvoid LynxModuleValues::DoCleanup() {\n  auto cleanup = cleanup_callback;\n  cleanup_callback = nullptr;\n  if (cleanup) {\n    cleanup();\n  }\n}\n\nbool LynxModuleValues::HasKey(const std::string& name) const {\n  return std::find(names.begin(), names.end(), name) != names.end();\n}\n\nconst clay::Value& LynxModuleValues::Get(const std::string& name) const {\n  auto it = std::find(names.begin(), names.end(), name);\n  if (it != names.end()) {\n    return values[it - names.begin()];\n  }\n  static const clay::Value invalid_value;\n  return invalid_value;\n}\n\nvoid LynxModuleMethod::AppendArg(const std::string& name,\n                                 clay::Value::Type type) {\n  arg_names.emplace_back(name);\n  arg_types.emplace(name, type);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/lynx_module/types.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_LYNX_MODULE_TYPES_H_\n#define CLAY_UI_LYNX_MODULE_TYPES_H_\n\n#include <functional>\n#include <memory>\n#include <optional>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/public/value.h\"\n\nnamespace clay {\n\nstruct LynxModuleMethod {\n  bool is_async = false;\n  std::string name;\n  std::vector<std::string> arg_names;\n  std::unordered_map<std::string, clay::Value::Type> arg_types;\n  std::optional<clay::Value::Type> return_type;\n\n  LynxModuleMethod(bool async, std::string&& method_name)\n      : is_async(async), name(std::move(method_name)) {}\n  void AppendArg(const std::string& name, clay::Value::Type type);\n};\n\nstruct LynxModuleValues {\n  LynxModuleValues();\n  ~LynxModuleValues();\n  LynxModuleValues(LynxModuleValues&&) = default;\n  LynxModuleValues(const LynxModuleValues&) = delete;\n  LynxModuleValues& operator=(const LynxModuleValues&) = delete;\n\n  void DoCleanup();\n  bool HasKey(const std::string& name) const;\n  const clay::Value& Get(const std::string& name) const;\n\n  std::vector<std::string> names;\n  clay::Value::Array values;\n  // Callback passed by lynx that used to cleanup values. `LynxModuleValues`\n  // itself may be cleaned up as well, so don't access `LynxModuleValues` after\n  // this is called.\n  std::function<void()> cleanup_callback;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_LYNX_MODULE_TYPES_H_\n"
  },
  {
    "path": "clay/ui/painter/border_side.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_BORDER_SIDE_H_\n#define CLAY_UI_PAINTER_BORDER_SIDE_H_\n\n#include \"clay/gfx/style/borders_data.h\"\n#include \"clay/gfx/style/color.h\"\n\nnamespace clay {\n\nclass BorderSide {\n public:\n  enum SideIndex {\n    TOP = 0,\n    RIGHT = 1,\n    BOTTOM = 2,\n    LEFT = 3,\n  };\n\n  BorderSide(float border_width, const Color& border_color,\n             BorderStyleType border_style)\n      : width_(border_width),\n        color_(border_color),\n        side_index_(TOP),\n        style_(border_style) {\n    if (style_ == BorderStyleType::kDouble && border_width < 3)\n      style_ = BorderStyleType::kSolid;\n  }\n\n  BorderSide(float border_width, const Color& border_color, SideIndex index,\n             BorderStyleType border_style)\n      : width_(border_width),\n        color_(border_color),\n        side_index_(index),\n        style_(border_style) {\n    if (style_ == BorderStyleType::kDouble && border_width < 3)\n      style_ = BorderStyleType::kSolid;\n  }\n\n  BorderSide()\n      : width_(0),\n        color_(0),\n        side_index_(TOP),\n        style_(BorderStyleType::kHide) {}\n\n  bool Visible() const { return color_.Alpha(); }\n  void GetDoubleBorderStripeWidths(float& outer_width,\n                                   float& inner_width) const {\n    outer_width = width_ / 3;\n    inner_width = width_ * 2 / 3;\n  }\n\n  float InnerWidth() const { return width_ * 2 / 3; }\n\n  float OuterWidth() const { return width_ / 3; }\n\n  bool ObscuresBackgroundEdge() const {\n    if (color_.Alpha() < 255 || style_ == BorderStyleType::kHide ||\n        style_ == BorderStyleType::kNone)\n      return false;\n\n    if (style_ == BorderStyleType::kDotted ||\n        style_ == BorderStyleType::kDashed)\n      return false;\n\n    return true;\n  }\n\n  float width_;\n  Color color_;\n  SideIndex side_index_;\n  BorderStyleType style_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_BORDER_SIDE_H_\n"
  },
  {
    "path": "clay/ui/painter/box_decoration_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_BOX_DECORATION_DATA_H_\n#define CLAY_UI_PAINTER_BOX_DECORATION_DATA_H_\n\nnamespace clay {\n\nstruct BoxDecorationData {\n public:\n  bool background_color = false;\n  bool has_background = false;\n  bool has_border = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_BOX_DECORATION_DATA_H_\n"
  },
  {
    "path": "clay/ui/painter/box_painter.cc",
    "content": "// Copyright 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/painter/box_painter.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <memory>\n#include <vector>\n\n#include \"clay/gfx/animation/picture_animation_type.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/style/borders_data.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/gfx/style/outline_data.h\"\n#include \"clay/ui/common/background_data.h\"\n#include \"clay/ui/common/utils/floating_comparison.h\"\n#include \"clay/ui/painter/border_side.h\"\n#include \"clay/ui/painter/box_shadow_painter.h\"\n#include \"clay/ui/painter/image_painter.h\"\n#include \"clay/ui/painter/object_painter.h\"\n\nnamespace clay {\n\nstatic const float kExtendFill = 1e-2f;\n\nstatic FloatRect CalculateSideRectIncludingInner(\n    const FloatRoundedRect& outer_border, const BorderSide& side,\n    const BordersData& border_data) {\n  FloatRect side_rect = outer_border.rect();\n  float width;\n\n  switch (side.side_index_) {\n    case BorderSide::TOP:\n      width = side_rect.height() - border_data.width_bottom_;\n      side_rect.SetHeight(width);\n      break;\n    case BorderSide::BOTTOM:\n      width = side_rect.height() - border_data.width_top_;\n      side_rect.SetY(side_rect.MaxY() - width);\n      side_rect.SetHeight(width);\n      break;\n    case BorderSide::LEFT:\n      width = side_rect.width() - border_data.width_right_;\n      side_rect.SetWidth(width);\n      break;\n    case BorderSide::RIGHT:\n      width = side_rect.width() - border_data.width_left_;\n      side_rect.SetX(side_rect.MaxX() - width);\n      side_rect.SetWidth(width);\n      break;\n    default:\n      break;\n  }\n\n  return side_rect;\n}\n\nstatic FloatRoundedRect CalculateAdjustedInnerBorder(\n    const FloatRoundedRect& inner_border, const BorderSide& side) {\n  // Expand the inner border as necessary to make it a rounded rect (i.e. radii\n  // contained within each edge). This function relies on the fact we only get\n  // radii not contained within each edge if one of the radii for an edge is\n  // zero, so we can shift the arc towards the zero radius corner.\n  FloatRoundedRect::Radii new_radii = inner_border.radii();\n  FloatRect new_rect = inner_border.rect();\n\n  float overshoot;\n  float max_radii;\n\n  switch (side.side_index_) {\n    case BorderSide::TOP:\n      overshoot = new_radii.TopLeft().width() + new_radii.TopRight().width() -\n                  new_rect.width();\n      // FIXME: once we start pixel-snapping rounded rects after this point, the\n      // overshoot concept should disappear.\n      if (overshoot > 0.1) {\n        new_rect.SetWidth(new_rect.width() + overshoot);\n        if (!new_radii.TopLeft().width()) {\n          new_rect.Move(-overshoot, 0);\n        }\n      }\n      new_radii.SetBottomLeft(FloatSize(0, 0));\n      new_radii.SetBottomRight(FloatSize(0, 0));\n      max_radii =\n          std::max(new_radii.TopLeft().height(), new_radii.TopRight().height());\n      if (max_radii > new_rect.height()) {\n        new_rect.SetHeight(max_radii);\n      }\n      break;\n\n    case BorderSide::BOTTOM:\n      overshoot = new_radii.BottomLeft().width() +\n                  new_radii.BottomRight().width() - new_rect.width();\n      if (overshoot > 0.1) {\n        new_rect.SetWidth(new_rect.width() + overshoot);\n        if (!new_radii.BottomLeft().width()) {\n          new_rect.Move(-overshoot, 0);\n        }\n      }\n      new_radii.SetTopLeft(FloatSize(0, 0));\n      new_radii.SetTopRight(FloatSize(0, 0));\n      max_radii = std::max(new_radii.BottomLeft().height(),\n                           new_radii.BottomRight().height());\n      if (max_radii > new_rect.height()) {\n        new_rect.Move(0, new_rect.height() - max_radii);\n        new_rect.SetHeight(max_radii);\n      }\n      break;\n\n    case BorderSide::LEFT:\n      overshoot = new_radii.TopLeft().height() +\n                  new_radii.BottomLeft().height() - new_rect.height();\n      if (overshoot > 0.1) {\n        new_rect.SetHeight(new_rect.height() + overshoot);\n        if (!new_radii.TopLeft().height()) {\n          new_rect.Move(0, -overshoot);\n        }\n      }\n      new_radii.SetTopRight(FloatSize(0, 0));\n      new_radii.SetBottomRight(FloatSize(0, 0));\n      max_radii =\n          std::max(new_radii.TopLeft().width(), new_radii.BottomLeft().width());\n      if (max_radii > new_rect.width()) {\n        new_rect.SetWidth(max_radii);\n      }\n      break;\n\n    case BorderSide::RIGHT:\n      overshoot = new_radii.TopRight().height() +\n                  new_radii.BottomRight().height() - new_rect.height();\n      if (overshoot > 0.1) {\n        new_rect.SetHeight(new_rect.height() + overshoot);\n        if (!new_radii.TopRight().height()) {\n          new_rect.Move(0, -overshoot);\n        }\n      }\n      new_radii.SetTopLeft(FloatSize(0, 0));\n      new_radii.SetBottomLeft(FloatSize(0, 0));\n      max_radii = std::max(new_radii.TopRight().width(),\n                           new_radii.BottomRight().width());\n      if (max_radii > new_rect.width()) {\n        new_rect.Move(new_rect.width() - max_radii, 0);\n        new_rect.SetWidth(max_radii);\n      }\n      break;\n  }\n\n  return FloatRoundedRect(new_rect, new_radii);\n}\n\nstatic void SetRRectRadii(skity::RRect& rrect, const skity::Rect& rect,\n                          const BordersData& borders_data,\n                          float radii_offset = 0) {\n  skity::Vec2 radii[4];\n  radii[skity::RRect::Corner::kUpperLeft] = {\n      borders_data.radius_x_top_left_ - radii_offset,\n      borders_data.radius_y_top_left_ - radii_offset};\n  radii[skity::RRect::Corner::kUpperRight] = {\n      borders_data.radius_x_top_right_ - radii_offset,\n      borders_data.radius_y_top_right_ - radii_offset};\n  radii[skity::RRect::Corner::kLowerRight] = {\n      borders_data.radius_x_bottom_right_ - radii_offset,\n      borders_data.radius_y_bottom_right_ - radii_offset};\n  radii[skity::RRect::Corner::kLowerLeft] = {\n      borders_data.radius_x_bottom_left_ - radii_offset,\n      borders_data.radius_y_bottom_left_ - radii_offset};\n  rrect.SetRectRadii(rect, radii);\n}\n\nstatic void PaintSolidBorder(GraphicsContext* context, const FloatRect& rect,\n                             const BordersData& borders_data,\n                             bool is_outline = false) {\n  int color = borders_data.color_left_;\n  float border_width = borders_data.width_left_;\n\n  Paint paint;\n  paint.setColor(Color(color));\n  // Activate antialiasing here to ensure that Skia enters the fast path\n  paint.setAntiAlias(true);\n  paint.setDrawStyle(DrawStyle::kStroke);\n  paint.setStrokeWidth(border_width);\n  if (!borders_data.HasBorderRadius()) {\n    FloatRect stroke_rect(rect);\n    stroke_rect.Inflate(-border_width / 2.f);\n    context->DrawRect(stroke_rect, paint);\n  } else {\n    auto get_rect = [&](float radii_offset) -> skity::RRect {\n      FloatRect stroke_rect(rect);\n      stroke_rect.Inflate(-radii_offset);\n      skity::RRect rrect;\n      SetRRectRadii(rrect, stroke_rect, borders_data, radii_offset);\n      return rrect;\n    };\n    skity::RRect rrect = get_rect(border_width * 0.5f);\n    context->DrawRRect(rrect, paint);\n  }\n}\n\nstatic void PaintDoubleBorder(GraphicsContext* context, const FloatRect& rect,\n                              const BordersData& borders_data,\n                              bool is_outline = false) {\n  int color = borders_data.color_left_;\n  float border_width = borders_data.width_left_;\n  BorderSide double_side(border_width, Color(color), BorderStyleType::kDouble);\n  float outer_width, inner_width = 0;\n  double_side.GetDoubleBorderStripeWidths(outer_width, inner_width);\n\n  skity::Rect outer_rect =\n      skity::Rect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());\n  skity::Rect inner_rect = skity::Rect::MakeXYWH(\n      rect.x() + border_width, rect.y() + border_width,\n      rect.width() - border_width * 2, rect.height() - border_width * 2);\n\n  skity::Rect inner_third_rect = skity::Rect::MakeXYWH(\n      rect.x() + inner_width, rect.y() + inner_width,\n      rect.width() - inner_width * 2, rect.height() - inner_width * 2);\n  Paint paint;\n  paint.setDrawStyle(DrawStyle::kStroke);\n  paint.setColor(Color(color));\n\n  skity::Rect draw_outer_rect = outer_rect;\n  draw_outer_rect.Inset(outer_width / 2);\n  float inner_stroke_width = inner_rect.X() - inner_third_rect.X();\n  skity::Rect draw_inner_rect = inner_third_rect;\n  draw_inner_rect.Inset(inner_stroke_width / 2);\n  if (!borders_data.HasBorderRadius()) {\n    paint.setStrokeWidth(outer_width);\n    context->DrawRect(draw_outer_rect, paint);\n\n    paint.setStrokeWidth(inner_stroke_width);\n    context->DrawRect(draw_inner_rect, paint);\n  } else {\n#if defined(ENABLE_ANTIALIAS)\n    paint.setAntiAlias(true);\n#endif\n    skity::RRect round_outer_rect;\n    SetRRectRadii(round_outer_rect, draw_outer_rect, borders_data,\n                  outer_width / 2.f);\n    paint.setStrokeWidth(outer_width);\n    context->DrawRRect(round_outer_rect, paint);\n\n    skity::RRect round_inner_rect;\n    SetRRectRadii(round_inner_rect, draw_inner_rect, borders_data,\n                  inner_width + inner_stroke_width / 2.f);\n    paint.setStrokeWidth(inner_stroke_width);\n    context->DrawRRect(round_inner_rect, paint);\n  }\n}\n\nBoxPainter::BoxPainter(RenderBox* object) : render_object_(object) {\n  FloatRect box_rect(render_object_->GetFrameRect());\n  SetOuterRect(box_rect);\n\n  if (render_object_->HasOutline()) {\n    SetOutline(render_object_->Outline(), box_rect);\n  }\n\n  if (render_object_->HasBorder()) {\n    SetBorder(render_object_->Border(), box_rect);\n  }\n}\n\nvoid BoxPainter::SetOutline(const OutlineData& outline_data,\n                            const FloatRect& box_rect) {\n  float width_offset = outline_data.width_ + outline_data.offset_;\n  FloatRect outline_outer_rect(box_rect);\n  outline_outer_rect.Expand(width_offset, width_offset, width_offset,\n                            width_offset);\n  outline_outer_rounded_rect_.SetRect(outline_outer_rect);\n  FloatRect outline_inner_rect(box_rect);\n  outline_inner_rect.Expand(outline_data.offset_, outline_data.offset_,\n                            outline_data.offset_, outline_data.offset_);\n  outline_inner_rounded_rect_.SetRect(outline_inner_rect);\n\n  outline_box_sides_[BorderSide::TOP] =\n      BorderSide(outline_data.width_, Color(outline_data.color_),\n                 BorderSide::TOP, outline_data.style_);\n  outline_box_sides_[BorderSide::RIGHT] =\n      BorderSide(outline_data.width_, Color(outline_data.color_),\n                 BorderSide::RIGHT, outline_data.style_);\n  outline_box_sides_[BorderSide::BOTTOM] =\n      BorderSide(outline_data.width_, Color(outline_data.color_),\n                 BorderSide::BOTTOM, outline_data.style_);\n  outline_box_sides_[BorderSide::LEFT] =\n      BorderSide(outline_data.width_, Color(outline_data.color_),\n                 BorderSide::LEFT, outline_data.style_);\n}\n\nvoid BoxPainter::SetBorder(const BordersData& borders_data,\n                           const FloatRect& box_rect) {\n  float left_width = borders_data.width_left_;\n  float top_width = borders_data.width_top_;\n  float right_width = borders_data.width_right_;\n  float bottom_width = borders_data.width_bottom_;\n  FloatRect inner_rect(box_rect);\n  inner_rect.Expand(-top_width, -right_width, -bottom_width, -left_width);\n  inner_rounded_rect_.SetRect(inner_rect);\n  if (borders_data.HasBorderRadius()) {\n    FloatRoundedRect::Radii radii(\n        FloatSize(borders_data.radius_x_top_left_,\n                  borders_data.radius_y_top_left_),\n        FloatSize(borders_data.radius_x_top_right_,\n                  borders_data.radius_y_top_right_),\n        FloatSize(borders_data.radius_x_bottom_left_,\n                  borders_data.radius_y_bottom_left_),\n        FloatSize(borders_data.radius_x_bottom_right_,\n                  borders_data.radius_y_bottom_right_));\n    outer_rounded_rect_.SetRadii(radii);\n    outer_rounded_rect_.ConstraintRadii();\n    FloatRoundedRect::Radii inner_rect_radii = outer_rounded_rect_.radii();\n    inner_rect_radii.Shrink(top_width, right_width, bottom_width, left_width);\n    inner_rounded_rect_.SetRadii(inner_rect_radii);\n    inner_rounded_rect_.ConstraintRadii();\n  }\n  box_sides_[BorderSide::TOP] =\n      BorderSide(top_width, Color(borders_data.color_top_), BorderSide::TOP,\n                 borders_data.style_top_);\n  box_sides_[BorderSide::RIGHT] =\n      BorderSide(right_width, Color(borders_data.color_right_),\n                 BorderSide::RIGHT, borders_data.style_right_);\n  box_sides_[BorderSide::BOTTOM] =\n      BorderSide(bottom_width, Color(borders_data.color_bottom_),\n                 BorderSide::BOTTOM, borders_data.style_bottom_);\n  box_sides_[BorderSide::LEFT] =\n      BorderSide(left_width, Color(borders_data.color_left_), BorderSide::LEFT,\n                 borders_data.style_left_);\n}\n\nvoid BoxPainter::Paint(GraphicsContext* context, const FloatPoint& offset) {\n  auto rect = render_object_->GetFrameRect();\n  // remove left and top\n  rect.SetX(0);\n  rect.SetY(0);\n  PaintBoxDecorationBackground(context, offset, rect);\n  if (render_object_->HasDefaultFocusRing()) {\n    GraphicsContext::AutoRestore saver(context, true);\n    context->Translate(offset.x(), offset.y());\n    FloatRoundedRect round_rect;\n    FloatRect float_rect(\n        -num_value::kFocusRingThickness / 2.f,\n        -num_value::kFocusRingThickness / 2.f,\n        outer_rounded_rect_.rect().width() + num_value::kFocusRingThickness,\n        outer_rounded_rect_.rect().height() + num_value::kFocusRingThickness);\n\n    FloatRoundedRect::Radii radii = outer_rounded_rect_.radii();\n    if (!radii.IsZero()) {\n      radii.Expand(num_value::kFocusRingThickness / 2.f,\n                   num_value::kFocusRingThickness / 2.f,\n                   num_value::kFocusRingThickness / 2.f,\n                   num_value::kFocusRingThickness / 2.f);\n    }\n    round_rect.SetRect(float_rect);\n    round_rect.SetRadii(radii);\n    ObjectPainter::DrawDefaultFocusRing(context, round_rect);\n  }\n}\n\n// Paint sequence: background -> shadows -> borders -> outline\nvoid BoxPainter::PaintBoxDecorationBackground(GraphicsContext* context,\n                                              const FloatPoint& offset,\n                                              const FloatRect& box_rect) {\n  bool has_border = render_object_->HasBorder();\n\n  skity::Rect draw_rect = skity::Rect::MakeXYWH(\n      box_rect.x(), box_rect.y(), box_rect.width(), box_rect.height());\n  skity::RRect round_rect;\n  bool has_radius = has_border && render_object_->Border().HasBorderRadius();\n  if (has_radius) {\n    const auto& borders_data = render_object_->Border();\n    SetRRectRadii(round_rect, draw_rect, borders_data);\n  } else {\n    round_rect.SetRect(draw_rect);\n  }\n\n  if (render_object_->HasBackground()) {\n    GraphicsContext::AutoRestore saver(context, true);\n    context->Translate(offset.x(), offset.y());\n\n    PaintBackground(context, render_object_->Background(), box_rect);\n  }\n\n  if (render_object_->HasShadow()) {\n    GraphicsContext::AutoRestore saver(context, true);\n    context->Translate(offset.x(), offset.y());\n    BoxShadowPainter(render_object_).Paint(context, box_rect);\n  }\n\n  if (has_border) {\n    GraphicsContext::AutoRestore saver(context, true);\n    context->Translate(offset.x(), offset.y());\n    if (IsBorderClip()) {\n      context->ClipRRect(round_rect, GrClipOp::kIntersect, true);\n    }\n    PaintBorders(context, box_rect, render_object_->Border());\n  }\n\n  if (render_object_->HasOutline()) {\n    GraphicsContext::AutoRestore saver(context, true);\n    context->Translate(offset.x(), offset.y());\n    PaintOutline(context, render_object_->Outline());\n  }\n\n#ifndef NDEBUG\n  if (render_object_->EnableRepaintBoundaryBorders() &&\n      render_object_->IsRepaintBoundary()) {\n    GraphicsContext::AutoRestore saver(context, true);\n    context->Translate(offset.x(), offset.y());\n    // Tag repaint boundary areas for debugging.\n    class Paint paint;\n    paint.setColor(Color::kBlue());\n    paint.setDrawStyle(DrawStyle::kStroke);\n    paint.setStrokeWidth(2.f);\n    context->DrawRect(box_rect, paint);\n  }\n#endif  // NDEBUG\n}\n\nvoid BoxPainter::PaintBackground(GraphicsContext* context,\n                                 const BackgroundData& background,\n                                 const FloatRect& paint_rect) {\n  bool has_visible_color = (background.background_color.Alpha() != 0);\n  bool has_image = !background.images.empty();\n  if (!has_visible_color && !has_image) {\n    return;\n  }\n\n  bool has_border = render_object_->HasBorder();\n  bool has_radius = has_border && render_object_->Border().HasBorderRadius();\n\n  // if background is not transparent, draw color background\n  if (has_visible_color) {\n    class Paint paint;\n    paint.setColor(background.background_color);\n#if defined(ENABLE_ANTIALIAS)\n    // setAntiAlias in case topper transform layer has rotation.\n    // INFO(feiyue.1998): disable antialias for background, or e2e test\n    // maskWithTransform will fail.\n    paint.setAntiAlias(render_object_->CanApplyAA());\n#endif\n    if (background.clips.size() > 0) {\n      FloatRect clip_rect = render_object_->GetFrameRect();\n      FloatPoint offset(render_object_->location());\n      auto clip = background.clips[0];\n      if (clip == ClayBackgroundClipType::kPaddingBox) {\n        clip_rect = render_object_->PaddingRect();\n      } else if (clip == ClayBackgroundClipType::kContentBox) {\n        clip_rect = render_object_->ContentRect();\n      }\n      // opposite to canvas.Translate() in PaintBoxDecorationBackground().\n      clip_rect.Move(-offset.x(), -offset.y());\n      if (!clip_rect.IsEmpty()) {\n        context->ClipRect(clip_rect, GrClipOp::kIntersect, false);\n      }\n      if (clip == ClayBackgroundClipType::kBorderArea) {\n        FloatRect padding_rect = render_object_->PaddingRect();\n        padding_rect.Move(-offset.x(), -offset.y());\n        context->ClipRect(padding_rect, GrClipOp::kDifference, false);\n      }\n    }\n    if (render_object_->HasBackgroundColorRasterAnimation()) {\n      FML_DCHECK(render_object_->HasValidID());\n      paint.setDynamicOpType(DynamicOpType::kSetBackgroundColor);\n    }\n    if (has_radius) {\n      context->DrawRRect(GetBackgroundRoundedRect(paint_rect), paint);\n    } else {\n      context->DrawRect(paint_rect, paint);\n    }\n  }\n  FloatRoundedRect paint_rrect;\n  if (has_radius && has_image) {\n    auto rrect = GetBackgroundRoundedRect(paint_rect);\n    if (background.images.size() > 1) {\n      context->ClipRRect(rrect, GrClipOp::kIntersect, true);\n    } else {\n      paint_rrect = rrect;\n    }\n  }\n\n  // The background images are drawn on stacking context layers on top of each\n  // other. The first layer specified is drawn as if it is closest to the user.\n  for (int index = background.images.size() - 1; index >= 0; index--) {\n    ClayBackgroundOriginType origin = ClayBackgroundOriginType::kBorderBox;\n    if (!background.origins.empty()) {\n      size_t origin_index = index % background.origins.size();\n      origin = background.origins[origin_index];\n    }\n    ClayBackgroundClipType clip = ClayBackgroundClipType::kBorderBox;\n    if (!background.clips.empty()) {\n      size_t clip_index = index % background.clips.size();\n      clip = background.clips[clip_index];\n    }\n    BackgroundSize size_x;\n    BackgroundSize size_y;\n    if (!background.sizes.empty() && background.sizes.size() >= 2) {\n      if (static_cast<size_t>(index) * 2 >= background.sizes.size()) {\n        size_x = background.sizes[background.sizes.size() - 2];\n        size_y = background.sizes[background.sizes.size() - 1];\n      } else {\n        size_x = background.sizes[index * 2];\n        size_y = background.sizes[index * 2 + 1];\n      }\n    }\n    BackgroundPosition position_x{0.f, ClayPlatformLengthUnit::kPercentage};\n    BackgroundPosition position_y{0.f, ClayPlatformLengthUnit::kPercentage};\n    if (background.positions.size() >= 2) {\n      size_t position_index = index % (background.positions.size() / 2);\n      position_x = background.positions[position_index * 2];\n      position_y = background.positions[position_index * 2 + 1];\n    }\n    ClayBackgroundRepeatType repeat_x = ClayBackgroundRepeatType::kRepeat;\n    ClayBackgroundRepeatType repeat_y = ClayBackgroundRepeatType::kRepeat;\n    if (background.repeats.size() >= 2) {\n      size_t repeat_index = index % (background.repeats.size() / 2);\n      repeat_x = background.repeats[repeat_index * 2];\n      repeat_y = background.repeats[repeat_index * 2 + 1];\n    }\n\n    // |paint_rect| was translated with RenderObject's location.\n    ImagePainter(render_object_)\n        .PaintBackgroundImage(\n            context, paint_rect, paint_rrect, background.images[index], origin,\n            clip, size_x, size_y, position_x, position_y, repeat_x, repeat_y);\n  }\n}\n\nvoid BoxPainter::PaintBorders(GraphicsContext* context, const FloatRect& rect,\n                              const BordersData& borders_data,\n                              bool is_outline) {\n  if (borders_data.HasSameWidth() && borders_data.HasSameStyle() &&\n      borders_data.HasSameColor() && borders_data.width_top_ != 0) {\n    // Notice: we should make sure in each corner, x-radius equals to\n    // y-radius(HasSimpleRadii) and greater than or equal to border-width. But\n    // no need to promise all corners are same(HasSameRadii) with this simple\n    // paint method. They are two different cases.\n    if (!borders_data.HasBorderRadius() ||\n        (borders_data.HasSimpleRadii() &&\n         borders_data.radius_x_top_left_ >= borders_data.width_top_ &&\n         borders_data.radius_x_top_right_ >= borders_data.width_top_ &&\n         borders_data.radius_x_bottom_left_ >= borders_data.width_top_ &&\n         borders_data.radius_x_bottom_right_ >= borders_data.width_top_)) {\n      if (PaintSimpleSameBorder(context, rect, borders_data, is_outline)) {\n        return;\n      }\n    }\n  }\n  if (!is_outline && borders_data.HasBorderWidth() &&\n      borders_data.HasBorderRadius() && CheckSameColorAndSolidBorder()) {\n    PaintSameColorAndSolidBorder(context);\n    return;\n  }\n\n  PaintBorderSide(context, rect, borders_data, is_outline);\n}\n\nvoid BoxPainter::PaintOutline(GraphicsContext* context,\n                              const OutlineData& outline_data) {\n  BordersData borders_data;\n  borders_data.width_top_ = borders_data.width_right_ =\n      borders_data.width_bottom_ = borders_data.width_left_ =\n          outline_data.width_;\n  borders_data.color_top_ = borders_data.color_right_ =\n      borders_data.color_bottom_ = borders_data.color_left_ =\n          outline_data.color_;\n  borders_data.style_top_ = borders_data.style_right_ =\n      borders_data.style_bottom_ = borders_data.style_left_ =\n          outline_data.style_;\n\n  FloatRect rect = outline_outer_rounded_rect_.rect();\n  // (0,0) is border_box location at top left\n  // outline is out of the border_box\n  rect.SetX(0 - borders_data.width_left_);\n  rect.SetY(0 - borders_data.width_top_);\n  if (outline_data.style_ == BorderStyleType::kDashed ||\n      outline_data.style_ == BorderStyleType::kDotted) {\n    context->ClipRect(rect, GrClipOp::kIntersect, true);\n  }\n  PaintBorderSide(context, rect, borders_data, true);\n}\n\nbool BoxPainter::PaintSimpleSameBorder(GraphicsContext* context,\n                                       const FloatRect& border_rect,\n                                       const BordersData& borders_data,\n                                       bool is_outline) {\n  if (borders_data.style_left_ == BorderStyleType::kSolid) {\n    PaintSolidBorder(context, border_rect, borders_data, is_outline);\n    return true;\n  } else if (borders_data.style_left_ == BorderStyleType::kDouble) {\n    PaintDoubleBorder(context, border_rect, borders_data, is_outline);\n    return true;\n  }\n\n  return false;\n}\n\nbool BoxPainter::CheckSameColorAndSolidBorder() {\n  bool flag = false;\n  for (size_t i = 0; i < 4; ++i) {\n    if (box_sides_[i].width_ != 0 &&\n        box_sides_[i].style_ == BorderStyleType::kSolid) {\n      flag = true;\n      for (size_t k = 0; k < 4; ++k) {\n        if (k != i && box_sides_[k].width_ != 0 &&\n            (box_sides_[i].style_ != box_sides_[k].style_ ||\n             box_sides_[i].color_ != box_sides_[k].color_)) {\n          return false;\n        }\n      }\n      break;\n    }\n  }\n  return flag;\n}\n\nvoid BoxPainter::PaintSameColorAndSolidBorder(GraphicsContext* context) {\n  Color valid_color;\n  BorderStyleType valid_style;\n  for (auto& box_side : box_sides_) {\n    if (box_side.width_ != 0) {\n      valid_color = box_side.color_;\n      valid_style = box_side.style_;\n      FML_DCHECK(valid_style == BorderStyleType::kSolid);\n      break;\n    }\n  }\n  GraphicsContext::AutoRestore saver(context, true);\n  FloatPoint offset(render_object_->location());\n  context->Translate(-offset.x(), -offset.y());\n\n  class Paint paint;\n  // Activate antialiasing here to ensure that Skia enters the fast path\n  paint.setAntiAlias(true);\n  paint.setColor(valid_color);\n\n  paint.setDrawStyle(DrawStyle::kFill);\n  context->DrawDRRect(outer_rounded_rect_, inner_rounded_rect_, paint);\n}\n\nvoid BoxPainter::PaintBorderSide(GraphicsContext* context,\n                                 const FloatRect& border_rect,\n                                 const BordersData& borders_data,\n                                 bool is_outline) {\n  std::vector<BorderSide> box_sides;\n  if (is_outline) {\n    // build top side\n    if (outline_box_sides_[BorderSide::TOP].Visible()) {\n      box_sides.push_back(outline_box_sides_[BorderSide::TOP]);\n    }\n    // build right side\n    if (outline_box_sides_[BorderSide::RIGHT].Visible()) {\n      box_sides.push_back(outline_box_sides_[BorderSide::RIGHT]);\n    }\n    if (outline_box_sides_[BorderSide::BOTTOM].Visible()) {\n      box_sides.push_back(outline_box_sides_[BorderSide::BOTTOM]);\n    }\n    // build left side\n    if (outline_box_sides_[BorderSide::LEFT].Visible()) {\n      box_sides.push_back(outline_box_sides_[BorderSide::LEFT]);\n    }\n  } else {\n    // build top side\n    if (box_sides_[BorderSide::TOP].Visible()) {\n      box_sides.push_back(box_sides_[BorderSide::TOP]);\n    }\n    // build right side\n    if (box_sides_[BorderSide::RIGHT].Visible()) {\n      box_sides.push_back(box_sides_[BorderSide::RIGHT]);\n    }\n    if (box_sides_[BorderSide::BOTTOM].Visible()) {\n      box_sides.push_back(box_sides_[BorderSide::BOTTOM]);\n    }\n    // build left side\n    if (box_sides_[BorderSide::LEFT].Visible()) {\n      box_sides.push_back(box_sides_[BorderSide::LEFT]);\n    }\n  }\n\n  for (auto box_side : box_sides) {\n    PaintSide(context, border_rect, box_side, borders_data, is_outline);\n  }\n}\n\nvoid BoxPainter::PaintSide(GraphicsContext* context,\n                           const FloatRect& border_rect, const BorderSide& side,\n                           const BordersData& borders_data, bool is_outline) {\n  if (side.width_ <= 0) {\n    return;\n  }\n\n  GrPath path;\n  skity::RRect round_rect = outer_rounded_rect_;\n  if (is_outline) {\n    round_rect = outline_outer_rounded_rect_;\n  }\n  bool use_path =\n      !(borders_data.HasSameWidth() && borders_data.HasSameColor() &&\n        borders_data.HasSameStyle() && !borders_data.HasBorderRadius() &&\n        (side.style_ == BorderStyleType::kSolid ||\n         side.style_ == BorderStyleType::kDouble));\n\n  if ((side.style_ == BorderStyleType::kDashed ||\n       side.style_ == BorderStyleType::kDotted) &&\n      !borders_data.HasBorderRadius()) {\n    // non-RRect dashed/dotted shoule be painted by lines\n    use_path = false;\n  }\n  if (is_outline) {\n    if (side.style_ == BorderStyleType::kDashed ||\n        side.style_ == BorderStyleType::kDotted) {\n      use_path = false;\n    } else {\n      use_path = true;\n    }\n  }\n\n  switch (side.side_index_) {\n    case BorderSide::TOP: {\n      FloatRect side_rect;\n      if (use_path) {\n        PATH_ADD_RRECT(path, round_rect);\n      } else {\n        side_rect = FloatRect(border_rect.x(), border_rect.y(),\n                              border_rect.width(), side.width_);\n      }\n\n      if (is_outline) {\n        PaintOneBorderSide(\n            context, side_rect, side, outline_box_sides_[BorderSide::LEFT],\n            outline_box_sides_[BorderSide::RIGHT], path, is_outline);\n      } else {\n        PaintOneBorderSide(context, side_rect, side,\n                           box_sides_[BorderSide::LEFT],\n                           box_sides_[BorderSide::RIGHT], path, is_outline);\n      }\n      break;\n    }\n    case BorderSide::BOTTOM: {\n      FloatRect side_rect;\n      if (use_path) {\n        PATH_ADD_RRECT(path, round_rect);\n      } else {\n        side_rect = FloatRect(border_rect.x(), border_rect.MaxY() - side.width_,\n                              border_rect.width(), side.width_);\n      }\n\n      if (is_outline) {\n        PaintOneBorderSide(\n            context, side_rect, side, outline_box_sides_[BorderSide::LEFT],\n            outline_box_sides_[BorderSide::RIGHT], path, is_outline);\n      } else {\n        PaintOneBorderSide(context, side_rect, side,\n                           box_sides_[BorderSide::LEFT],\n                           box_sides_[BorderSide::RIGHT], path, is_outline);\n      }\n      break;\n    }\n    case BorderSide::RIGHT: {\n      FloatRect side_rect;\n      if (use_path) {\n        PATH_ADD_RRECT(path, round_rect);\n      } else {\n        side_rect = FloatRect(border_rect.MaxX() - side.width_, border_rect.y(),\n                              side.width_, border_rect.height());\n      }\n\n      if (is_outline) {\n        PaintOneBorderSide(\n            context, side_rect, side, outline_box_sides_[BorderSide::TOP],\n            outline_box_sides_[BorderSide::BOTTOM], path, is_outline);\n      } else {\n        PaintOneBorderSide(context, side_rect, side,\n                           box_sides_[BorderSide::TOP],\n                           box_sides_[BorderSide::BOTTOM], path, is_outline);\n      }\n      break;\n    }\n    case BorderSide::LEFT: {\n      FloatRect side_rect;\n      if (use_path) {\n        PATH_ADD_RRECT(path, round_rect);\n      } else {\n        side_rect = FloatRect(border_rect.x(), border_rect.y(), side.width_,\n                              border_rect.height());\n      }\n      if (is_outline) {\n        PaintOneBorderSide(\n            context, side_rect, side, outline_box_sides_[BorderSide::TOP],\n            outline_box_sides_[BorderSide::BOTTOM], path, is_outline);\n      } else {\n        PaintOneBorderSide(context, side_rect, side,\n                           box_sides_[BorderSide::TOP],\n                           box_sides_[BorderSide::BOTTOM], path, is_outline);\n      }\n      break;\n    }\n    default:\n      break;\n  }\n}\n\nvoid BoxPainter::PaintOneBorderSide(GraphicsContext* context,\n                                    const FloatRect& side_rect,\n                                    const BorderSide& side,\n                                    const BorderSide& relative_side1,\n                                    const BorderSide& relative_side2,\n                                    const GrPath& path, bool is_outline) const {\n  bool should_aa1 =\n      !relative_side1.Visible() || relative_side1.color_ != side.color_;\n  bool should_aa2 =\n      !relative_side2.Visible() || relative_side2.color_ != side.color_;\n  if (relative_side1.style_ == BorderStyleType::kNone ||\n      relative_side1.style_ == BorderStyleType::kHide) {\n    should_aa1 = false;\n  } else {\n    should_aa1 = true;\n  }\n\n  if (relative_side2.style_ == BorderStyleType::kNone ||\n      relative_side2.style_ == BorderStyleType::kHide) {\n    should_aa2 = false;\n  } else {\n    should_aa2 = true;\n  }\n\n  if (side.style_ == BorderStyleType::kInset ||\n      side.style_ == BorderStyleType::kOutset) {\n    should_aa1 = true;\n    should_aa2 = true;\n  }\n  if (!PATH_IS_EMPTY(path)) {\n    GraphicsContext::AutoRestore saver(context, true);\n    FloatPoint offset(render_object_->location());\n    context->Translate(-offset.x(), -offset.y());\n    // clip to get the real border area\n    if (!is_outline) {\n      context->ClipRRect(outer_rounded_rect_, GrClipOp::kIntersect, true);\n      if (inner_rounded_rect_.IsRenderable() &&\n          !inner_rounded_rect_.IsEmpty()) {\n        context->ClipRRect(inner_rounded_rect_, GrClipOp::kDifference, true);\n      }\n    }\n    if (is_outline) {\n      ClipBorderSidePolygon(context, side, should_aa1, should_aa2, true);\n    } else {\n      if (inner_rounded_rect_.IsRenderable()) {\n        ClipBorderSidePolygon(context, side, should_aa1, should_aa2);\n      } else {\n        ClipBorderSideForComplexInnerPath(context, side);\n      }\n    }\n    float thickness = std::max(std::max(side.width_, relative_side1.width_),\n                               relative_side2.width_);\n    if (is_outline) {\n      DrawBorderSideFromPath(context, outline_outer_rounded_rect_.rect(), path,\n                             side.width_, thickness, side, side.color_,\n                             side.style_, true);\n    } else {\n      DrawBorderSideFromPath(context, outer_rounded_rect_.rect(), path,\n                             side.width_, thickness, side, side.color_,\n                             side.style_);\n    }\n\n  } else if (side.style_ == BorderStyleType::kDashed ||\n             side.style_ == BorderStyleType::kDotted) {\n    // dashed and dotted will be drawn with help of DashEffectPath\n    GraphicsContext::AutoRestore saver(context, true);\n    FloatPoint offset(render_object_->location());\n    context->Translate(-offset.x(), -offset.y());\n    ClipBorderSidePolygon(context, side, should_aa1, should_aa2, is_outline);\n    if (is_outline) {\n      DrawLineForDashed(context, outline_outer_rounded_rect_.rect(),\n                        side.width_, side, side.color_, side.style_);\n    } else {\n      DrawLineForDashed(context, outer_rounded_rect_.rect(), side.width_, side,\n                        side.color_, side.style_);\n    }\n  } else {\n    // it seems that logic can not jump to this branch\n    ObjectPainter::DrawLineForBoxSide(\n        context, side_rect.x(), side_rect.y(), side_rect.MaxX(),\n        side_rect.MaxY(), side.side_index_, side.color_, side.style_,\n        relative_side1.width_, relative_side2.width_, true);\n  }\n}\n\nvoid BoxPainter::DrawLineForDashed(GraphicsContext* context,\n                                   const FloatRect& side_rect, float thickness,\n                                   const BorderSide& side, Color color,\n                                   BorderStyleType border_style,\n                                   bool is_outline) const {\n  class Paint paint;\n#if defined(ENABLE_ANTIALIAS)\n  paint.setAntiAlias(true);\n#endif\n  paint.setColor(color);\n  // The stroke is doubled here because the provided path is the\n  // outside edge of the border so half the stroke is clipped off.\n  // The extra multiplier is so that the clipping mask can antialias\n  // the edges to prevent jaggies.\n  paint.setStrokeWidth(thickness * 2.0f);\n  float dash_x1 = 0.f, dash_x2 = 0.f, dash_y1 = 0.f, dash_y2 = 0.f;\n  auto getDashPathEffect = [&border_style](float border_width,\n                                           float border_length) {\n    float section_length = (border_width >= 1 ? border_width : 1) *\n                           (border_style == BorderStyleType::kDotted ? 2 : 6) *\n                           0.5f;\n    int new_section_count =\n        (static_cast<int>((border_length / section_length - 0.5f) * 0.5f)) * 2 +\n        1;\n    float intervals[] = {border_length / new_section_count,\n                         border_length / new_section_count};\n    std::shared_ptr<PathEffect> ret = DashPathEffect::Make(intervals, 2, 0);\n    return ret;\n  };\n  float length = 0.f;\n  switch (side.side_index_) {\n    case BorderSide::TOP: {\n      length = side_rect.width();\n      dash_x1 = side_rect.x();\n      dash_x2 = side_rect.MaxX();\n      dash_y1 = side_rect.y();\n      dash_y2 = side_rect.y();\n      break;\n    }\n    case BorderSide::BOTTOM: {\n      length = side_rect.width();\n      dash_x1 = side_rect.x();\n      dash_x2 = side_rect.MaxX();\n      dash_y1 = side_rect.MaxY();\n      dash_y2 = side_rect.MaxY();\n      break;\n    }\n    case BorderSide::LEFT: {\n      length = side_rect.height();\n      dash_x1 = side_rect.x();\n      dash_x2 = side_rect.x();\n      dash_y1 = side_rect.y();\n      dash_y2 = side_rect.MaxY();\n      break;\n    }\n    case BorderSide::RIGHT: {\n      length = side_rect.height();\n      dash_x1 = side_rect.MaxX();\n      dash_x2 = side_rect.MaxX();\n      dash_y1 = side_rect.y();\n      dash_y2 = side_rect.MaxY();\n      break;\n    }\n  }\n  auto ret = getDashPathEffect(thickness, length);\n  if (ret) {\n    paint.setPathEffect(ret);\n    context->DrawLine(dash_x1, dash_y1, dash_x2, dash_y2, paint);\n  }\n}\n\nvoid BoxPainter::SetupPaintDashPathEffect(\n    class Paint& paint, const int length, const float border_thickness,\n    const BorderStyleType border_style) const {\n  auto SelectBestDashGap = [](float stroke_length, float dash_length,\n                              float gap_length) {\n    // Determine what number of dashes gives the minimum deviation\n    // from gapLength between dashes. Set the gap to that width.\n    float min_num_dashes =\n        floorf((stroke_length + gap_length) / (dash_length + gap_length));\n    float max_num_dashes = min_num_dashes + 1;\n    float min_gap =\n        (stroke_length - min_num_dashes * dash_length) / (min_num_dashes - 1);\n    float max_gap =\n        (stroke_length - max_num_dashes * dash_length) / (max_num_dashes - 1);\n    return (max_gap <= 0) ||\n                   (fabs(min_gap - gap_length) < fabs(max_gap - gap_length))\n               ? min_gap\n               : max_gap;\n  };\n  float dash_width = border_thickness;\n  if (border_style == BorderStyleType::kDashed) {\n    float dash_length = dash_width;\n    float gap_length = dash_length;\n    // gap_ratio and dash_ratio can be changed to support\n    // different render logics\n    float gap_ratio = 3.f, dash_ratio = 3.f;\n    dash_length *= dash_ratio;\n    gap_length *= gap_ratio;\n    if (length <= 2 * dash_length + gap_length) {\n      // set end dashes properly by shrink both dash and gap length\n      float multiplier = length / (2 * dash_length + gap_length);\n      float intervals[2] = {dash_length * multiplier, gap_length * multiplier};\n      paint.setPathEffect(DashPathEffect::Make(intervals, 2, 0));\n    } else {\n      float gap = SelectBestDashGap(length, dash_length, gap_length);\n      float intervals[2] = {dash_length, gap};\n      paint.setPathEffect(DashPathEffect::Make(intervals, 2, 0));\n    }\n  } else if (border_style == BorderStyleType::kDotted) {\n    // Adjust the width to get equal dot spacing as much as possible.\n    float per_dot_length = dash_width * 2;\n    if (length < per_dot_length) {\n      // Not enough space for 2 dots. Just draw 1 by giving a gap that is\n      // bigger than the length.\n      float intervals[2] = {static_cast<float>(dash_width), per_dot_length};\n      paint.setPathEffect(DashPathEffect::Make(intervals, 2, 0));\n      return;\n    }\n    // Epsilon ensures that we get a whole dot at the end of the line,\n    // even if that dot is a little inside the true endpoint. Without it\n    // we can drop the end dot due to rounding along the line.\n    static const float kEpsilon = 1.0e-2f;\n    float gap = SelectBestDashGap(length, dash_width, dash_width);\n    float intervals[2] = {static_cast<float>(dash_width), gap - kEpsilon};\n    paint.setPathEffect(DashPathEffect::Make(intervals, 2, 0));\n  }\n}\n\nvoid BoxPainter::DrawBorderSideFromPath(GraphicsContext* context,\n                                        const FloatRect& border_rect,\n                                        const GrPath& border_path,\n                                        float thickness, float draw_thickness,\n                                        const BorderSide& side, Color color,\n                                        BorderStyleType border_style,\n                                        bool is_outline) const {\n  if (thickness <= 0) {\n    return;\n  }\n\n  if (border_style == BorderStyleType::kDouble && thickness < 3) {\n    border_style = BorderStyleType::kSolid;\n  }\n\n  switch (border_style) {\n    case BorderStyleType::kNone:\n    case BorderStyleType::kHide:\n    case BorderStyleType::kUndefined:\n      return;\n    case BorderStyleType::kDotted:\n    case BorderStyleType::kDashed: {\n      GraphicsContext::AutoRestore saver(context, true);\n      class Paint paint;\n#if defined(ENABLE_ANTIALIAS)\n      paint.setAntiAlias(true);\n#endif\n      paint.setColor(color);\n      paint.setDrawStyle(DrawStyle::kStroke);\n      // The stroke is doubled here because the provided path is the\n      // outside edge of the border so half the stroke is clipped off.\n      // The extra multiplier is so that the clipping mask can antialias\n      // the edges to prevent jaggies.\n      paint.setStrokeWidth(side.width_ * 2.0f);\n      int length =\n          static_cast<int>(GraphicsContext::GetPathLength(border_path));\n      SetupPaintDashPathEffect(paint, length, side.width_, border_style);\n      context->DrawPath(border_path, paint);\n      return;\n    }\n    case BorderStyleType::kSolid: {\n      GraphicsContext::AutoRestore saver(context, true);\n      class Paint paint;\n#if defined(ENABLE_ANTIALIAS)\n      paint.setAntiAlias(true);\n#endif\n      paint.setDrawStyle(DrawStyle::kFill);\n      paint.setColor(color);\n      if (is_outline) {\n        context->DrawDRRect(outline_outer_rounded_rect_,\n                            outline_inner_rounded_rect_, paint);\n      } else {\n        context->DrawRRect(outer_rounded_rect_, paint);\n      }\n\n      return;\n    }\n    case BorderStyleType::kDouble: {\n      {\n        GraphicsContext::AutoRestore saver(context, true);\n        FloatRect inner_rect = outer_rounded_rect_.rect();\n        inner_rect.Expand(\n            -box_sides_[0].InnerWidth(), -box_sides_[1].InnerWidth(),\n            -box_sides_[2].InnerWidth(), -box_sides_[3].InnerWidth());\n        FloatRoundedRect::Radii radii = outer_rounded_rect_.radii();\n        if (!radii.IsZero()) {\n          radii.Shrink(box_sides_[0].InnerWidth(), box_sides_[1].InnerWidth(),\n                       box_sides_[2].InnerWidth(), box_sides_[3].InnerWidth());\n        }\n\n        if (is_outline) {\n          inner_rect = outline_outer_rounded_rect_.rect();\n          inner_rect.Expand(-outline_box_sides_[0].InnerWidth(),\n                            -outline_box_sides_[1].InnerWidth(),\n                            -outline_box_sides_[2].InnerWidth(),\n                            -outline_box_sides_[3].InnerWidth());\n          radii = outline_outer_rounded_rect_.radii();\n        }\n\n        FloatRoundedRect inner_clip(inner_rect, radii);\n        if (inner_clip.IsRounded()) {\n          context->ClipRRect(inner_clip, GrClipOp::kIntersect, true);\n        } else {\n          context->ClipRect(inner_clip.rect(), GrClipOp::kIntersect, true);\n        }\n        DrawBorderSideFromPath(context, border_rect, border_path, thickness,\n                               draw_thickness, side, color,\n                               BorderStyleType::kSolid, is_outline);\n      }\n\n      {\n        GraphicsContext::AutoRestore saver(context, true);\n        FloatRect outer_rect = outer_rounded_rect_.rect();\n        outer_rect.Expand(\n            -box_sides_[0].OuterWidth(), -box_sides_[1].OuterWidth(),\n            -box_sides_[2].OuterWidth(), -box_sides_[3].OuterWidth());\n        FloatRoundedRect::Radii radii = outer_rounded_rect_.radii();\n        if (!radii.IsZero()) {\n          radii.Shrink(box_sides_[0].OuterWidth(), box_sides_[1].OuterWidth(),\n                       box_sides_[2].OuterWidth(), box_sides_[3].OuterWidth());\n        }\n\n        if (is_outline) {\n          outer_rect = outline_outer_rounded_rect_.rect();\n          outer_rect.Expand(-outline_box_sides_[0].OuterWidth(),\n                            -outline_box_sides_[1].OuterWidth(),\n                            -outline_box_sides_[2].OuterWidth(),\n                            -outline_box_sides_[3].OuterWidth());\n          radii = outline_outer_rounded_rect_.radii();\n        }\n\n        FloatRoundedRect outer_clip(outer_rect, radii);\n        if (outer_clip.IsRounded()) {\n          context->ClipRRect(outer_clip, GrClipOp::kDifference, true);\n        } else {\n          context->ClipRect(outer_clip.rect(), GrClipOp::kDifference, true);\n        }\n\n        DrawBorderSideFromPath(context, border_rect, border_path, thickness,\n                               draw_thickness, side, color,\n                               BorderStyleType::kSolid, is_outline);\n      }\n      return;\n    }\n    case BorderStyleType::kRidge:\n    case BorderStyleType::kGroove: {\n      BorderStyleType s1;\n      BorderStyleType s2;\n      if (border_style == BorderStyleType::kGroove) {\n        s1 = BorderStyleType::kInset;\n        s2 = BorderStyleType::kOutset;\n      } else {\n        s1 = BorderStyleType::kOutset;\n        s2 = BorderStyleType::kInset;\n      }\n\n      float top_width = (box_sides_[0].width_) / 2;\n      float right_width = (box_sides_[1].width_) / 2;\n      float bottom_width = (box_sides_[2].width_) / 2;\n      float left_width = (box_sides_[3].width_) / 2;\n\n      if (is_outline) {\n        top_width = (outline_box_sides_[0].width_) / 2;\n        right_width = (outline_box_sides_[1].width_) / 2;\n        bottom_width = (outline_box_sides_[2].width_) / 2;\n        left_width = (outline_box_sides_[3].width_) / 2;\n      }\n\n      // Paint outer only\n      {\n        GraphicsContext::AutoRestore saver(context, true);\n\n        FloatRect inner_rect(border_rect);\n        inner_rect.Expand(-top_width, -right_width, -bottom_width, -left_width);\n        FloatRoundedRect::Radii radii = outer_rounded_rect_.radii();\n        if (is_outline) {\n          radii = outline_outer_rounded_rect_.radii();\n        }\n        if (!radii.IsZero()) {\n          radii.Shrink(top_width, right_width, bottom_width, left_width);\n        }\n        FloatRoundedRect inner_clip(inner_rect, radii);\n        if (inner_clip.IsRounded()) {\n          context->ClipRRect(inner_clip, GrClipOp::kDifference, true);\n        } else {\n          context->ClipRect(inner_clip.rect(), GrClipOp::kDifference, false);\n        }\n        DrawBorderSideFromPath(context, border_rect, border_path, thickness,\n                               draw_thickness, side, color, s1, is_outline);\n      }\n\n      // Paint inner only\n      {\n        GraphicsContext::AutoRestore saver(context, true);\n\n        FloatRect inner_rect(border_rect);\n        inner_rect.Expand(-top_width, -right_width, -bottom_width, -left_width);\n        FloatRoundedRect::Radii radii = outer_rounded_rect_.radii();\n        if (is_outline) {\n          radii = outline_outer_rounded_rect_.radii();\n        }\n        if (!radii.IsZero()) {\n          radii.Shrink(top_width, right_width, bottom_width, left_width);\n        }\n        FloatRoundedRect inner_clip(inner_rect, radii);\n        if (inner_clip.IsRounded()) {\n          context->ClipRRect(inner_clip, GrClipOp::kIntersect, true);\n        } else {\n          context->ClipRect(inner_clip.rect(), GrClipOp::kIntersect, false);\n        }\n        DrawBorderSideFromPath(context, border_rect, border_path, thickness,\n                               draw_thickness, side, color, s2, is_outline);\n      }\n      break;\n    }\n    case BorderStyleType::kInset:\n      if (side.side_index_ == BorderSide::TOP ||\n          side.side_index_ == BorderSide::LEFT) {\n        color = color.Dark();\n      }\n      DrawBorderSideFromPath(context, border_rect, border_path, thickness,\n                             draw_thickness, side, color,\n                             BorderStyleType::kSolid, is_outline);\n      break;\n    case BorderStyleType::kOutset:\n      if (side.side_index_ == BorderSide::BOTTOM ||\n          side.side_index_ == BorderSide::RIGHT) {\n        color = color.Dark();\n      }\n      DrawBorderSideFromPath(context, border_rect, border_path, thickness,\n                             draw_thickness, side, color,\n                             BorderStyleType::kSolid, is_outline);\n      break;\n    default:\n      break;\n  }\n}\n\nvoid BoxPainter::ClipBorderSideForComplexInnerPath(GraphicsContext* context,\n                                                   const BorderSide& side,\n                                                   bool is_outline) const {\n  context->ClipRect(CalculateSideRectIncludingInner(outer_rounded_rect_, side,\n                                                    render_object_->Border()),\n                    GrClipOp::kIntersect, false);\n  FloatRoundedRect adjusted_inner_rect =\n      CalculateAdjustedInnerBorder(inner_rounded_rect_, side);\n  if (!adjusted_inner_rect.IsEmpty()) {\n    if (!adjusted_inner_rect.IsRounded()) {\n      context->ClipRect(adjusted_inner_rect.rect(), GrClipOp::kDifference,\n                        true);\n      return;\n    }\n    context->ClipRRect(adjusted_inner_rect, GrClipOp::kDifference, true);\n  }\n}\n\nvoid BoxPainter::ClipBorderSidePolygon(GraphicsContext* context,\n                                       const BorderSide& side, bool should_aa1,\n                                       bool should_aa2, bool is_outline) const {\n  FloatPoint quad[4];\n\n  FloatRect outer_rect(outer_rounded_rect_.rect());\n  FloatRect inner_rect(inner_rounded_rect_.rect());\n  if (is_outline) {\n    outer_rect = outline_outer_rounded_rect_.rect();\n    inner_rect = outline_inner_rounded_rect_.rect();\n  }\n\n  // For each side, create a quad that encompasses all parts of that side that\n  // may draw, including areas inside the innerBorder.\n  //\n  //         0----------------3\n  //       0  \\              /  0\n  //       |\\  1----------- 2  /|\n  //       | 1                1 |\n  //       | |                | |\n  //       | |                | |\n  //       | 2                2 |\n  //       |/  1------------2  \\|\n  //       3  /              \\  3\n  //         0----------------3\n  //\n  switch (side.side_index_) {\n    case BorderSide::TOP:\n      quad[0] = outer_rect.MinXMinYCorner();\n      quad[1] = inner_rect.MinXMinYCorner();\n      quad[2] = inner_rect.MaxXMinYCorner();\n      quad[3] = outer_rect.MaxXMinYCorner();\n\n      if (!is_outline) {\n        if (!inner_rounded_rect_.radii().TopLeft().IsZero()) {\n          FindIntersection(\n              quad[0], quad[1],\n              FloatPoint(\n                  quad[1].x() + inner_rounded_rect_.radii().TopLeft().width(),\n                  quad[1].y()),\n              FloatPoint(\n                  quad[1].x(),\n                  quad[1].y() + inner_rounded_rect_.radii().TopLeft().height()),\n              &quad[1]);\n        }\n\n        if (!inner_rounded_rect_.radii().TopRight().IsZero()) {\n          FindIntersection(\n              quad[3], quad[2],\n              FloatPoint(\n                  quad[2].x() - inner_rounded_rect_.radii().TopRight().width(),\n                  quad[2].y()),\n              FloatPoint(quad[2].x(),\n                         quad[2].y() +\n                             inner_rounded_rect_.radii().TopRight().height()),\n              &quad[2]);\n        }\n      }\n\n      break;\n\n    case BorderSide::LEFT:\n      quad[0] = (outer_rect.MinXMinYCorner());\n      quad[1] = (inner_rect.MinXMinYCorner());\n      quad[2] = (inner_rect.MinXMaxYCorner());\n      quad[3] = (outer_rect.MinXMaxYCorner());\n\n      if (!is_outline) {\n        if (!inner_rounded_rect_.radii().TopLeft().IsZero()) {\n          FindIntersection(\n              quad[0], quad[1],\n              FloatPoint(\n                  quad[1].x() + inner_rounded_rect_.radii().TopLeft().width(),\n                  quad[1].y()),\n              FloatPoint(\n                  quad[1].x(),\n                  quad[1].y() + inner_rounded_rect_.radii().TopLeft().height()),\n              &quad[1]);\n        }\n\n        if (!inner_rounded_rect_.radii().BottomLeft().IsZero()) {\n          FindIntersection(\n              quad[3], quad[2],\n              FloatPoint(quad[2].x() +\n                             inner_rounded_rect_.radii().BottomLeft().width(),\n                         quad[2].y()),\n              FloatPoint(quad[2].x(),\n                         quad[2].y() -\n                             inner_rounded_rect_.radii().BottomLeft().height()),\n              &quad[2]);\n        }\n      }\n\n      break;\n\n    case BorderSide::BOTTOM:\n      quad[0] = (outer_rect.MinXMaxYCorner());\n      quad[1] = (inner_rect.MinXMaxYCorner());\n      quad[2] = (inner_rect.MaxXMaxYCorner());\n      quad[3] = (outer_rect.MaxXMaxYCorner());\n      if (!is_outline) {\n        if (!inner_rounded_rect_.radii().BottomLeft().IsZero()) {\n          FindIntersection(\n              quad[0], quad[1],\n              FloatPoint(quad[1].x() +\n                             inner_rounded_rect_.radii().BottomLeft().width(),\n                         quad[1].y()),\n              FloatPoint(quad[1].x(),\n                         quad[1].y() -\n                             inner_rounded_rect_.radii().BottomLeft().height()),\n              &quad[1]);\n        }\n        if (!inner_rounded_rect_.radii().BottomRight().IsZero()) {\n          FindIntersection(\n              quad[3], quad[2],\n              FloatPoint(quad[2].x() -\n                             inner_rounded_rect_.radii().BottomRight().width(),\n                         quad[2].y()),\n              FloatPoint(\n                  quad[2].x(),\n                  quad[2].y() -\n                      inner_rounded_rect_.radii().BottomRight().height()),\n              &quad[2]);\n        }\n      }\n      break;\n\n    case BorderSide::RIGHT:\n      quad[0] = (outer_rect.MaxXMinYCorner());\n      quad[1] = (inner_rect.MaxXMinYCorner());\n      quad[2] = (inner_rect.MaxXMaxYCorner());\n      quad[3] = (outer_rect.MaxXMaxYCorner());\n\n      if (!is_outline) {\n        if (!inner_rounded_rect_.radii().TopRight().IsZero()) {\n          FindIntersection(\n              quad[0], quad[1],\n              FloatPoint(\n                  quad[1].x() - inner_rounded_rect_.radii().TopRight().width(),\n                  quad[1].y()),\n              FloatPoint(quad[1].x(),\n                         quad[1].y() +\n                             inner_rounded_rect_.radii().TopRight().height()),\n              &quad[1]);\n        }\n\n        if (!inner_rounded_rect_.radii().BottomRight().IsZero()) {\n          FindIntersection(\n              quad[3], quad[2],\n              FloatPoint(quad[2].x() -\n                             inner_rounded_rect_.radii().BottomRight().width(),\n                         quad[2].y()),\n              FloatPoint(\n                  quad[2].x(),\n                  quad[2].y() -\n                      inner_rounded_rect_.radii().BottomRight().height()),\n              &quad[2]);\n        }\n      }\n\n      break;\n  }\n\n  if (should_aa1 && should_aa2) {\n    GrPath path;\n    GraphicsContext::SetPathFromPoints(&path, 4, quad);\n    context->ClipPath(path, GrClipOp::kIntersect, false);\n    return;\n  }\n  // If antialiasing settings for the first edge and second edge is different,\n  // they have to be addressed separately. We do this by breaking the quad into\n  // two parallelograms, made by moving quad[1] and quad[2].\n  float ax = quad[1].x() - quad[0].x();\n  float ay = quad[1].y() - quad[0].y();\n  float bx = quad[2].x() - quad[1].x();\n  float by = quad[2].y() - quad[1].y();\n  float cx = quad[3].x() - quad[2].x();\n  float cy = quad[3].y() - quad[2].y();\n\n  float r1, r2;\n  if (RoughlyEqualToZero(bx) && RoughlyEqualToZero(by)) {\n    // The quad was actually a triangle.\n    r1 = r2 = 1.0f;\n  } else {\n    r1 = (-ax * by + ay * bx) / (cx * by - cy * bx) + kExtendFill;\n    r2 = (-cx * by + cy * bx) / (ax * by - ay * bx) + kExtendFill;\n  }\n\n  if (!(should_aa1 || should_aa2)) {\n    FloatPoint first_quad[4];\n    first_quad[0] = quad[0];\n    first_quad[1] = FloatPoint(quad[0].x() - r1 * cx, quad[0].y() - r1 * cy);\n    first_quad[2] = FloatPoint(quad[3].x() + r2 * ax, quad[3].y() + r2 * ay);\n    first_quad[3] = quad[3];\n\n    GrPath path;\n    GraphicsContext::SetPathFromPoints(&path, 4, first_quad);\n    context->ClipPath(path, GrClipOp::kIntersect, should_aa1);\n    return;\n  }\n\n  if (should_aa1) {\n    FloatPoint first_quad[4];\n    first_quad[0] = quad[0];\n    first_quad[1] = quad[1];\n    first_quad[2] = FloatPoint(quad[3].x() + r2 * ax, quad[3].y() + r2 * ay);\n    first_quad[3] = quad[3];\n    GrPath path;\n    GraphicsContext::SetPathFromPoints(&path, 4, first_quad);\n    context->ClipPath(path, GrClipOp::kIntersect, should_aa1);\n  }\n\n  if (should_aa2) {\n    FloatPoint second_quad[4];\n    second_quad[0] = quad[0];\n    second_quad[1] = FloatPoint(quad[0].x() - r1 * cx, quad[0].y() - r1 * cy);\n    second_quad[2] = quad[2];\n    second_quad[3] = quad[3];\n\n    GrPath path;\n    GraphicsContext::SetPathFromPoints(&path, 4, second_quad);\n    context->ClipPath(path, GrClipOp::kIntersect, should_aa2);\n  }\n}\n\nbool BoxPainter::IsBackgroundBleedAvoidanceShrink() {\n  for (auto& side : box_sides_) {\n    if (!side.ObscuresBackgroundEdge()) {\n      return false;\n    }\n  }\n  return true;\n}\n\nFloatRoundedRect BoxPainter::GetBackgroundRoundedRect(\n    const FloatRect& paint_rect) {\n  FloatRoundedRect round_rect;\n  if (IsBackgroundBleedAvoidanceShrink()) {\n    float fractionalInset = 0.5f;\n    for (auto& side : box_sides_) {\n      if (side.style_ == BorderStyleType::kDouble) {\n        fractionalInset = 0.16f;\n        break;\n      }\n    }\n\n    float top_inset = -fractionalInset * (box_sides_[0].width_);\n    float right_inset = -fractionalInset * (box_sides_[1].width_);\n    float bottom_inset = -fractionalInset * (box_sides_[2].width_);\n    float left_inset = -fractionalInset * (box_sides_[3].width_);\n\n    FloatRect inset_rect(paint_rect.x(), paint_rect.y(),\n                         outer_rounded_rect_.rect().width(),\n                         outer_rounded_rect_.rect().height());\n    inset_rect.Expand(top_inset, right_inset, bottom_inset, left_inset);\n    FloatRoundedRect::Radii inset_radii(outer_rounded_rect_.radii());\n    inset_radii.Expand(top_inset, right_inset, bottom_inset, left_inset);\n\n    round_rect.SetRect(inset_rect);\n    round_rect.SetRadii(inset_radii);\n  } else {\n    FloatRect rect(paint_rect.x(), paint_rect.y(),\n                   outer_rounded_rect_.rect().width(),\n                   outer_rounded_rect_.rect().height());\n    round_rect.SetRect(rect);\n    round_rect.SetRadii(outer_rounded_rect_.radii());\n  }\n  return round_rect;\n}\n\nbool BoxPainter::IsBorderClip() {\n  for (auto& side : box_sides_) {\n    if (side.style_ == BorderStyleType::kDotted ||\n        side.style_ == BorderStyleType::kDashed) {\n      return true;\n    }\n  }\n  return false;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/box_painter.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_BOX_PAINTER_H_\n#define CLAY_UI_PAINTER_BOX_PAINTER_H_\n\n#include <optional>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/ui/painter/border_side.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\nstruct BackgroundData;\nclass BordersData;\nclass OutlineData;\nclass RenderObject;\n\nclass BoxPainter {\n public:\n  explicit BoxPainter(RenderBox* box);\n\n  void SetOuterRect(const FloatRect& rect) {\n    outer_rounded_rect_.SetRect(rect);\n  }\n\n  void SetOutline(const OutlineData& outline_data, const FloatRect& rect_box);\n\n  void SetBorder(const BordersData& borders_data, const FloatRect& box_rect);\n\n  void Paint(GraphicsContext* context, const FloatPoint& offset);\n  void PaintBoxDecorationBackground(GraphicsContext* context,\n                                    const FloatPoint& offset,\n                                    const FloatRect& box_rect);\n  // TODO(zhangxiao.ninja): add paint shdow, mask here\n\n private:\n  void SetupPaintDashPathEffect(class Paint& paint, const int length,\n                                const float border_thickness,\n                                const BorderStyleType border_style) const;\n  void PaintBackground(GraphicsContext* context,\n                       const BackgroundData& background,\n                       const FloatRect& paint_rect);\n  void DrawLineForDashed(GraphicsContext* context, const FloatRect& side_rect,\n                         float thickness, const BorderSide& side, Color color,\n                         BorderStyleType border_style,\n                         bool is_outline = false) const;\n  void PaintBorders(GraphicsContext* context, const FloatRect& rect,\n                    const BordersData& border_data, bool is_outline = false);\n  void PaintOutline(GraphicsContext* context, const OutlineData& outline_data);\n  void PaintBorderSide(GraphicsContext* context, const FloatRect& border_rect,\n                       const BordersData& borders_data,\n                       bool is_outline = false);\n  void PaintSide(GraphicsContext* context, const FloatRect& border_rect,\n                 const BorderSide& side, const BordersData& borders_data,\n                 bool is_outline = false);\n  void PaintOneBorderSide(GraphicsContext* context, const FloatRect& side_rect,\n                          const BorderSide& side,\n                          const BorderSide& relative_side1,\n                          const BorderSide& relative_side2, const GrPath& path,\n                          bool is_outline = false) const;\n  void DrawBorderSideFromPath(GraphicsContext* context,\n                              const FloatRect& border_rect,\n                              const GrPath& border_path, float thickness,\n                              float draw_thickness, const BorderSide& side,\n                              Color color, BorderStyleType border_style,\n                              bool is_outline = false) const;\n  bool PaintSimpleSameBorder(GraphicsContext* context,\n                             const FloatRect& border_rect,\n                             const BordersData& borders_data,\n                             bool is_outline = false);\n\n  bool CheckSameColorAndSolidBorder();\n  void PaintSameColorAndSolidBorder(GraphicsContext* context);\n\n  void ClipBorderSideForComplexInnerPath(GraphicsContext* context,\n                                         const BorderSide& side,\n                                         bool is_outline = false) const;\n  void ClipBorderSidePolygon(GraphicsContext* context, const BorderSide& side,\n                             bool should_aa1, bool should_aa2,\n                             bool is_outline = false) const;\n\n  bool IsBackgroundBleedAvoidanceShrink();\n  FloatRoundedRect GetBackgroundRoundedRect(const FloatRect& paint_rect);\n  bool IsBorderClip();\n\n  BorderSide box_sides_[4];\n  BorderSide outline_box_sides_[4];\n  FloatRoundedRect outer_rounded_rect_;\n  FloatRoundedRect inner_rounded_rect_;\n  FloatRoundedRect outline_outer_rounded_rect_;\n  FloatRoundedRect outline_inner_rounded_rect_;\n  RenderBox* render_object_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_BOX_PAINTER_H_\n"
  },
  {
    "path": "clay/ui/painter/box_painter_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/testing/canvas_test.h\"\n#include \"clay/ui/painter/box_painter.h\"\n#include \"clay/ui/painter/painter_graphics_test.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nusing clay::testing::MockCanvas;\n\nclass BoxPainterTest : public ::testing::Test {\n protected:\n  void SetUp() override {\n    render_box_ = std::make_unique<RenderBox>();\n    render_box_->SetLeft(0);\n    render_box_->SetTop(0);\n    render_box_->SetWidth(100);\n    render_box_->SetHeight(100);\n  }\n\n  std::unique_ptr<RenderBox> render_box_;\n  PainterGraphicsTest graphics_owner_;\n};\n\nTEST_F(BoxPainterTest, EmptyBox) {\n  EXPECT_FALSE(render_box_->HasBackground());\n  EXPECT_FALSE(render_box_->HasBorder());\n  std::unique_ptr<BoxPainter> box_painter =\n      std::make_unique<BoxPainter>(render_box_.get());\n  box_painter->Paint(&graphics_owner_.mock_context(), FloatPoint());\n  auto picture = graphics_owner_.mock_context().FinishRecording();\n  picture->picture()->raw()->playback(&graphics_owner_.mock_canvas());\n  EXPECT_TRUE(graphics_owner_.mock_canvas().draw_calls().empty());\n}\n\nTEST_F(BoxPainterTest, Background) {\n  BackgroundData bg_data;\n  EXPECT_TRUE(Color::Parse(\"blue\", &bg_data.background_color));\n  render_box_->SetBackgroundData(bg_data);\n  EXPECT_TRUE(render_box_->HasBackground());\n  std::unique_ptr<BoxPainter> box_painter =\n      std::make_unique<BoxPainter>(render_box_.get());\n  box_painter->Paint(&graphics_owner_.mock_context(), FloatPoint());\n  auto picture = graphics_owner_.mock_context().FinishRecording();\n  picture->picture()->raw()->playback(&graphics_owner_.mock_canvas());\n\n  SkPaint paint;\n  paint.setColor(SK_ColorBLUE);\n  paint.setAntiAlias(true);\n  constexpr SkRect rect = SkRect::MakeLTRB(0, 0, 100, 100);\n  const auto expected_draw_calls = std::vector{\n      MockCanvas::DrawCall{0, MockCanvas::DrawRectData{rect, paint}}};\n\n  EXPECT_EQ(graphics_owner_.mock_canvas().draw_calls(), expected_draw_calls);\n}\n\nTEST_F(BoxPainterTest, SimpleBorder) {\n  BordersData border_data;\n  border_data.width_top_ = 10.f;\n  border_data.width_bottom_ = 10.f;\n  border_data.width_right_ = 10.f;\n  border_data.width_left_ = 10.f;\n  border_data.color_top_ = SK_ColorBLUE;\n  border_data.color_right_ = SK_ColorBLUE;\n  border_data.color_bottom_ = SK_ColorBLUE;\n  border_data.color_left_ = SK_ColorBLUE;\n  render_box_->SetBorders(border_data);\n  EXPECT_TRUE(render_box_->HasBorder());\n  EXPECT_FALSE(render_box_->HasBackground());\n  std::unique_ptr<BoxPainter> box_painter =\n      std::make_unique<BoxPainter>(render_box_.get());\n  box_painter->Paint(&graphics_owner_.mock_context(), FloatPoint());\n  auto picture = graphics_owner_.mock_context().FinishRecording();\n  picture->picture()->raw()->playback(&graphics_owner_.mock_canvas());\n\n  SkPaint paint;\n  paint.setStyle(SkPaint::kStroke_Style);\n  paint.setStrokeWidth(border_data.width_top_);\n  paint.setAntiAlias(true);\n  paint.setColor(SK_ColorBLUE);\n  constexpr SkRect rect = SkRect::MakeLTRB(5, 5, 95, 95);\n  const auto expected_draw_calls = std::vector{\n      MockCanvas::DrawCall{0, MockCanvas::DrawRectData{rect, paint}}};\n\n  EXPECT_EQ(graphics_owner_.mock_canvas().draw_calls(), expected_draw_calls);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/box_shadow_painter.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/painter/box_shadow_painter.h\"\n\n#include <memory>\n#include <vector>\n\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/shadow.h\"\n#include \"clay/ui/common/utils/floating_comparison.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\nnamespace {\n// Return the inner contour rect with border radius considered in.\n// New radii will be reduced according to border width and may be reach 0.f\nskity::RRect RoundedRectInBorder(const skity::Rect& rect,\n                                 const BordersData& borders) {\n  skity::RRect contour_rrect;\n  skity::Vec2 corners[4] = {\n      {borders.radius_x_top_left_, borders.radius_y_top_left_},\n      {borders.radius_x_top_right_, borders.radius_y_top_right_},\n      {borders.radius_x_bottom_right_, borders.radius_y_bottom_right_},\n      {borders.radius_x_bottom_left_, borders.radius_y_bottom_left_}};\n  contour_rrect.SetRectRadii(rect, corners);\n  return contour_rrect;\n}\n\n// Calculate the rect in borders.\n// Besides widths, radii should be considered too.\nvoid RRectInBorder(const BordersData& borders, skity::RRect& rrect) {\n  auto tempRect = rrect.GetRect();\n  tempRect = skity::Rect::MakeLTRB(tempRect.Left() + borders.width_left_,\n                                   tempRect.Top() + borders.width_top_,\n                                   tempRect.Right() - borders.width_right_,\n                                   tempRect.Bottom() - borders.width_bottom_);\n\n  auto ul_radii = rrect.Radii(skity::RRect::kUpperLeft);\n  auto ur_radii = rrect.Radii(skity::RRect::kUpperRight);\n  auto lr_radii = rrect.Radii(skity::RRect::kLowerRight);\n  auto ll_radii = rrect.Radii(skity::RRect::kLowerLeft);\n  if (RoughlyNotZero(borders.width_left_)) {\n    ul_radii.x -= borders.width_left_;\n    ll_radii.x -= borders.width_left_;\n  }\n  if (RoughlyNotZero(borders.width_top_)) {\n    ul_radii.y -= borders.width_top_;\n    ur_radii.y -= borders.width_top_;\n  }\n  if (RoughlyNotZero(borders.width_right_)) {\n    ur_radii.x -= borders.width_right_;\n    lr_radii.x -= borders.width_right_;\n  }\n  if (RoughlyNotZero(borders.width_bottom_)) {\n    ll_radii.y -= borders.width_bottom_;\n    lr_radii.y -= borders.width_bottom_;\n  }\n\n  skity::Vec2 radii[4] = {ul_radii, ur_radii, lr_radii, ll_radii};\n  rrect.SetRectRadii(tempRect, radii);\n}\n\nvoid PreparePaint(Paint& paint, Color color, float blur_radius) {\n  paint.setColor(color);\n  if (RoughlyNotZero(blur_radius)) {\n    paint.setMaskFilter(std::make_shared<BlurMaskFilter>(\n        BLUR_STYLE_NORMAL, GraphicsContext::ConvertRadiusToSigma(blur_radius)));\n  } else {\n    paint.setMaskFilter(nullptr);\n  }\n}\n\nvoid PaintShadow(GraphicsContext* context, const Shadow& shadow,\n                 const skity::RRect& contour_rrect,\n                 std::optional<BordersData> borders, Paint& paint) {\n  PreparePaint(paint, shadow.color, shadow.blur_radius);\n  GraphicsContext::AutoRestore saver(context, true);\n  if (!shadow.inset) {\n    // Only keep the painting region out of box.\n    GrPath path;\n    PATH_ADD_RRECT(path, contour_rrect);\n    context->ClipPath(path, GrClipOp::kDifference, true);\n\n    auto shadow_rect = contour_rrect;\n    shadow_rect.Offset(shadow.offset_x, shadow.offset_y);\n    shadow_rect.Outset(shadow.spread_radius, shadow.spread_radius,\n                       &shadow_rect);\n    context->DrawRRect(shadow_rect, paint);\n  } else {\n    auto hole_rrect = contour_rrect;\n    hole_rrect.Offset(shadow.offset_x, shadow.offset_y);\n    auto inner_rrect = contour_rrect;\n    if (borders.has_value()) {\n      // Inset shadow will be affected by borders.\n      RRectInBorder(*borders, hole_rrect);\n      RRectInBorder(*borders, inner_rrect);\n    }\n    if (RoughlyNotZero(shadow.spread_radius)) {\n      hole_rrect.Inset(shadow.spread_radius, shadow.spread_radius, &hole_rrect);\n    }\n    // This is a workaround for skia's anti alias issue. We need to calculate\n    // the intersection first and then draw the new path. See meego\n    // issue:3000303346 for more details.\n    GrPath path_hole_rrect, path_inner_rrect, intersect_path, draw_path;\n    PATH_ADD_RRECT(path_hole_rrect, hole_rrect);\n    PATH_ADD_RRECT(path_inner_rrect, inner_rrect);\n    // Cast int to PathOp.\n    PATH_OP(path_hole_rrect, path_inner_rrect, 1, intersect_path);\n    PATH_ADD_RRECT(draw_path, inner_rrect);\n    PATH_ADD_PATH(draw_path, intersect_path);\n    // Cast int to fill type.\n    PATH_SET_FILL_TYPE(draw_path, 1);\n    // Only keep painting region in the box without border.\n    context->ClipRRect(inner_rrect, GrClipOp::kIntersect, true);\n    context->DrawPath(draw_path, paint);\n  }\n}\n\n// Paint a series of shadows one by one sequentially onto a region.\n// |layout_rect| contains border width but without radii.\nvoid PaintShadows(GraphicsContext* context, const std::vector<Shadow>& shadows,\n                  const skity::Rect& layout_rect,\n                  std::optional<BordersData> borders) {\n  Paint paint;\n  paint.setAntiAlias(true);\n  // create contour rounded rect with border radius considered.\n  skity::RRect contour_rrect = skity::RRect::MakeRect(layout_rect);\n  if (borders.has_value()) {\n    contour_rrect = RoundedRectInBorder(layout_rect, borders.value());\n  }\n\n  // the first shadow would be displayed on the top\n  // most, so the drawing order should be reverse\n  for (auto it = shadows.rbegin(); it != shadows.rend(); ++it) {\n    PaintShadow(context, *it, contour_rrect, borders, paint);\n  }\n}\n}  // namespace\n\nvoid BoxShadowPainter::Paint(GraphicsContext* context,\n                             const FloatRect& box_rect) {\n  if (!render_object_ || !render_object_->HasShadow()) {\n    return;\n  }\n\n  auto layout_rect = skity::Rect::MakeXYWH(box_rect.x(), box_rect.y(),\n                                           box_rect.width(), box_rect.height());\n  PaintShadows(context, render_object_->Shadows(), layout_rect,\n               render_object_->HasBorder() ? render_object_->Border()\n                                           : std::optional<BordersData>());\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/box_shadow_painter.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_BOX_SHADOW_PAINTER_H_\n#define CLAY_UI_PAINTER_BOX_SHADOW_PAINTER_H_\n\n#include \"clay/gfx/graphics_context.h\"\n\nnamespace clay {\n\nclass RenderBox;\n\nclass BoxShadowPainter {\n public:\n  explicit BoxShadowPainter(const RenderBox* render_box)\n      : render_object_(render_box) {}\n\n  void Paint(GraphicsContext* context, const FloatRect& box_rect);\n\n private:\n  const RenderBox* render_object_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_BOX_SHADOW_PAINTER_H_\n"
  },
  {
    "path": "clay/ui/painter/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nui_painter_sources = [\n  \"border_side.h\",\n  \"box_painter.cc\",\n  \"box_painter.h\",\n  \"box_shadow_painter.cc\",\n  \"box_shadow_painter.h\",\n  \"gradient.cc\",\n  \"gradient.h\",\n  \"gradient_factory.cc\",\n  \"gradient_factory.h\",\n  \"image_painter.cc\",\n  \"image_painter.h\",\n  \"object_painter.cc\",\n  \"object_painter.h\",\n  \"painting_context.cc\",\n  \"painting_context.h\",\n  \"text_painter.cc\",\n  \"text_painter.h\",\n]\n"
  },
  {
    "path": "clay/ui/painter/gradient.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/painter/gradient.h\"\n\n#include <optional>\n#include <string>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/painter/gradient_factory.h\"\n\nnamespace clay {\n\nnamespace utils = attribute_utils;\n\nnamespace {\n// These values are defined in Lynx\nconstexpr int kPositionTypeNumber = 2;\nconstexpr int kPositionTypePercent = 11;\n\ntemplate <typename T>\nvoid SetColorAndStop(const clay::Value::Array& color_array,\n                     const clay::Value::Array& position_array, T& gradient) {\n  // colors:\n  for (size_t j = 0; j < color_array.size(); j++) {\n    gradient.colors[j] = utils::GetUint(color_array[j]);\n  }\n  // positions (maybe empty):\n  for (size_t j = 0; j < position_array.size(); j++) {\n    float value = utils::GetNum(position_array[j]) / 100;\n    auto type = kPositionTypePercent;\n    if (type == kPositionTypeNumber) {\n      gradient.position_types[j] = ClayGradientPositionType::kNumber;\n    } else {\n      FML_DCHECK(type == kPositionTypePercent);\n      gradient.position_types[j] = ClayGradientPositionType::kPercent;\n    }\n    gradient.positions[j] = value;\n  }\n  if (position_array.size() > 0) {\n    FML_DCHECK(position_array.size() == color_array.size());\n  }\n}\n\n}  // namespace\n\nGradient::Gradient() : type_(GradientType::kNotSet) {}\n\n// static\nstd::optional<Gradient> Gradient::CreateLinear(\n    const clay::Value::Array& linear_array) {\n  FML_DCHECK(linear_array.size() > 0);\n  ClayLinearGradient gradient;\n  // degree:\n  gradient.degree = utils::GetDouble(linear_array[0]);\n  std::vector<unsigned int> colors(utils::GetArray(linear_array[1]).size());\n  std::vector<float> positions(utils::GetArray(linear_array[2]).size());\n  std::vector<ClayGradientPositionType> types(\n      utils::GetArray(linear_array[2]).size());\n  gradient.colors = colors.data();\n  gradient.colors_length = colors.size();\n  gradient.positions = positions.data();\n  gradient.positions_length = positions.size();\n  gradient.position_types = types.data();\n  SetColorAndStop(utils::GetArray(linear_array[1]),\n                  utils::GetArray(linear_array[2]), gradient);\n  return CreateLinear(gradient);\n}\n\n// static\nstd::optional<Gradient> Gradient::CreateRadial(\n    const clay::Value::Array& radial_array) {\n  FML_DCHECK(radial_array.size() > 0);\n  ClayRadialGradient gradient;\n  auto& shape_size_position = utils::GetArray(radial_array[0]);\n  // shape type and size:\n  gradient.shape_type = static_cast<ClayRadialGradientShapeType>(\n      utils::GetInt(shape_size_position[0]));\n  gradient.shape_size = static_cast<ClayRadialGradientSizeType>(\n      utils::GetInt(shape_size_position[1]));\n  // [x-position-type, x-position, y-position-type y-position]:\n  gradient.center_x = static_cast<ClayRadialGradientCenterType>(\n      utils::GetInt(shape_size_position[2]));\n  gradient.center_x_value =\n      static_cast<float>(utils::GetDouble(shape_size_position[3]));\n  gradient.center_y = static_cast<ClayRadialGradientCenterType>(\n      utils::GetInt(shape_size_position[4]));\n  gradient.center_y_value =\n      static_cast<float>(utils::GetDouble(shape_size_position[5]));\n  // For length value: [x_pattern, x_value, y_pattern, y_value]\n  if (gradient.shape_size == ClayRadialGradientSizeType::kLength) {\n    gradient.length_x =\n        static_cast<float>(utils::GetDouble(shape_size_position[10]));\n    gradient.length_x_unit = static_cast<ClayPlatformLengthUnit>(\n        utils::GetInt(shape_size_position[11]));\n    gradient.length_y =\n        static_cast<float>(utils::GetDouble(shape_size_position[12]));\n    gradient.length_y_unit = static_cast<ClayPlatformLengthUnit>(\n        utils::GetInt(shape_size_position[13]));\n  }\n  // colors:\n  std::vector<unsigned int> colors(utils::GetArray(radial_array[1]).size());\n  // positions\n  std::vector<float> positions(utils::GetArray(radial_array[2]).size());\n  std::vector<ClayGradientPositionType> types(\n      utils::GetArray(radial_array[2]).size());\n  gradient.colors = colors.data();\n  gradient.colors_length = colors.size();\n  gradient.positions = positions.data();\n  gradient.positions_length = positions.size();\n  gradient.position_types = types.data();\n  SetColorAndStop(utils::GetArray(radial_array[1]),\n                  utils::GetArray(radial_array[2]), gradient);\n  return CreateRadial(gradient);\n}\n\n/**\n parameter convention is as following\n [\n   start_angle,\n   [x, x_is_percent, y, y_is_percent],\n   [color, color, ...],\n   [[start_angle, end_angle], [start_angle, end_angle], ...] // default\n   [pos, pos, ...] // lynx\n ]\n */\n// static\nstd::optional<Gradient> Gradient::CreateConic(\n    const clay::Value::Array& conic_array) {\n  FML_DCHECK(conic_array.size() > 0);\n  ClayConicGradient gradient;\n  gradient.start_angle = utils::GetDouble(conic_array[0]);\n  auto& center_array = utils::GetArray(conic_array[1]);\n  gradient.center_x = static_cast<float>(utils::GetDouble(center_array[0]));\n  gradient.x_is_percent = static_cast<bool>(utils::GetInt(center_array[1]));\n  gradient.center_y = static_cast<float>(utils::GetDouble(center_array[2]));\n  gradient.y_is_percent = static_cast<bool>(utils::GetInt(center_array[3]));\n  auto& color_array = utils::GetArray(conic_array[2]);\n  auto& position_array = utils::GetArray(conic_array[3]);\n  bool angle_pair = false;\n  int factor = 1;\n  if (!position_array\n           .empty()) {  // Check if the position array is a angle-pair array\n    angle_pair = position_array[0].IsArray();\n    factor = 2;\n  }\n  std::vector<unsigned int> colors(color_array.size() * factor);\n  std::vector<float> positions(position_array.size() * factor);\n  std::vector<ClayGradientPositionType> types(position_array.size() * factor);\n  gradient.colors = colors.data();\n  gradient.colors_length = colors.size();\n  gradient.positions = positions.data();\n  gradient.positions_length = positions.size();\n  gradient.position_types = types.data();\n  if (angle_pair) {\n    // Flatten angle-pair array to angle pair, and also transform color array.\n    FML_DCHECK(color_array.size() == position_array.size());\n    for (size_t i = 0; i < color_array.size(); i++) {\n      gradient.colors[i * 2] = utils::GetUint(color_array[i]);\n      gradient.colors[i * 2 + 1] = utils::GetUint(color_array[i]);\n    }\n    for (size_t i = 0; i < position_array.size(); i++) {\n      auto& angles = utils::GetArray(position_array[i]);\n      FML_DCHECK(angles.size() == 2);\n      gradient.positions[i * 2] = utils::GetDouble(angles[0]);\n      gradient.positions[i * 2 + 1] = utils::GetDouble(angles[1]);\n      gradient.position_types[i * 2] = ClayGradientPositionType::kNumber;\n      gradient.position_types[i * 2 + 1] = ClayGradientPositionType::kNumber;\n    }\n  } else {\n    SetColorAndStop(color_array, position_array, gradient);\n  }\n  return CreateConic(gradient);\n}\n\n// static\nstd::optional<Gradient> Gradient::Create(std::string raw_data) {\n  return GradientFactory::Create(std::move(raw_data));\n}\n\n// static\nstd::optional<Gradient> Gradient::CreateLinear(\n    const ClayLinearGradient& gradient_data) {\n  return GradientFactory::CreateLinear(gradient_data);\n}\n\n// static\nstd::optional<Gradient> Gradient::CreateRadial(\n    const ClayRadialGradient& gradient_data) {\n  return GradientFactory::CreateRadial(gradient_data);\n}\n\n// static\nstd::optional<Gradient> Gradient::CreateConic(\n    const ClayConicGradient& gradient_data) {\n  return GradientFactory::CreateConic(gradient_data);\n}\n\nbool Gradient::operator==(const Gradient& oth) const {\n  return raw_data_ == oth.raw_data_;\n}\n\nbool Gradient::operator!=(const Gradient& oth) const {\n  return raw_data_ != oth.raw_data_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/gradient.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_GRADIENT_H_\n#define CLAY_UI_PAINTER_GRADIENT_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/public/value.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nenum class LinearGradientDirection {\n  kNone = 0,\n  kToTop,\n  kToBottom,\n  kToLeft,\n  kToRight,\n  kToTopRight,\n  kToTopLeft,\n  kToBottomRight,\n  kToBottomLeft,\n  kAngle,\n};\n\n// original enum RadialGradientExtent here.\n// In order to be consistent with lynx, we change\n// the enum order.\n/*\nenum class RadialGradientExtent {\n  kFarthestCorner = 0,\n  kClosestCorner,\n  kFarthestSide,\n  kClosestSide,\n};\n*/\n\nenum class RadialGradientExtent {\n  kFarthestCorner = 0,\n  kFarthestSide,\n  kClosestCorner,\n  kClosestSide,\n  kLength,\n};\n\nenum class RadialCenterType {\n  BACKGROUND_POSITION_TOP = -(1 << 5),\n  BACKGROUND_POSITION_RIGHT = -(1 << 5) - 1,\n  BACKGROUND_POSITION_BOTTOM = -(1 << 5) - 2,\n  BACKGROUND_POSITION_LEFT = -(1 << 5) - 3,\n  BACKGROUND_POSITION_CENTER = -(1 << 5) - 4,\n  RADIAL_CENTER_TYPE_PERCENTAGE = -11,  // according to lynx\n};\n\nenum class RadialShapeType {\n  kEllipse = 0,\n  kCircle,\n};\n\nstruct RadialCenter {\n  RadialCenterType center_x;\n  RadialCenterType center_y;\n  float center_x_value;\n  float center_y_value;\n};\n\nenum class GradientPositionType {\n  kPercent,\n  kNumber,\n};\n\nstruct GradientPosition {\n  float value;\n  GradientPositionType type;\n};\n\nenum class GradientLengthType {\n  kNumber,\n  kPercent,\n};\n\nstruct GradientLength {\n  float value;\n  GradientLengthType type;\n};\n\nenum class GradientType {\n  kNotSet = 0,\n  kLinear,\n  kRadial,\n  kConic,\n};\n\nclass GradientFactory;\n\nclass Gradient {\n public:\n  using ColorAndStops = std::vector<std::pair<GradientPosition, Color>>;\n\n  Gradient();\n\n  static std::optional<Gradient> Create(std::string raw_data);\n\n  // Linear Gradient\n  static std::optional<Gradient> CreateLinear(\n      const clay::Value::Array& linear_array);\n  static std::optional<Gradient> CreateLinear(\n      const ClayLinearGradient& gradient_data);\n  // Radial Gradient\n  static std::optional<Gradient> CreateRadial(\n      const clay::Value::Array& radial_array);\n  static std::optional<Gradient> CreateRadial(\n      const ClayRadialGradient& gradient_data);\n  // Conic Gradient\n  static std::optional<Gradient> CreateConic(\n      const clay::Value::Array& conic_array);\n  static std::optional<Gradient> CreateConic(\n      const ClayConicGradient& gradient_data);\n\n  GradientType Type() const { return type_; }\n\n  const ColorAndStops& PositionColors() const { return position_colors_; }\n\n  // Only valid when type is linear.\n  LinearGradientDirection Direction() const { return direction_; }\n  double Angle() const { return angle_; }\n\n  // Only valid when type is radial.\n  const FloatPoint& At() const { return at_; }\n  RadialGradientExtent Extent() const { return extent_; }\n\n  const std::string& RawData() const { return raw_data_; }\n\n  const RadialCenter& Center() const { return center_; }\n\n  // Conic\n  const GradientPosition& ConicCenterX() const { return conic_center_x_; }\n  const GradientPosition& ConicCenterY() const { return conic_center_y_; }\n  float StartAngle() const { return start_angle_; }\n  float EndAngle() const { return end_angle_; }\n\n  // NOTE: Only use Raw Data to compare.\n  bool operator!=(const Gradient& oth) const;\n  bool operator==(const Gradient& oth) const;\n\n private:\n  friend class GradientFactory;\n  FRIEND_TEST(GradientTest, DirectionToPoints);\n\n  GradientType type_;\n  ColorAndStops position_colors_;\n\n  // Only valid when type is linear.\n  LinearGradientDirection direction_;\n  double angle_;\n\n  // Only valid when type is radial.\n  FloatPoint at_;\n  RadialGradientExtent extent_;\n  RadialCenter center_;\n  RadialShapeType radial_shape_;\n  GradientLength length_x_;\n  GradientLength length_y_;\n\n  // Conic\n  GradientPosition conic_center_x_;\n  GradientPosition conic_center_y_;\n  float start_angle_;\n  float end_angle_;\n\n  std::string raw_data_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_GRADIENT_H_\n"
  },
  {
    "path": "clay/ui/painter/gradient_factory.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/painter/gradient_factory.h\"\n\n#include <ctype.h>\n#include <math.h>\n\n#include <algorithm>\n#include <cmath>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/utils/floating_comparison.h\"\n\nnamespace clay {\n\nnamespace {\n\nstatic constexpr char kLinearPrefix[] = \"linear-gradient(\";\nstatic constexpr char kRadialPrefix[] = \"radial-gradient(\";\nstatic const std::unordered_map<std::string, LinearGradientDirection>\n    kDirectionMap = {\n        {\"top\", LinearGradientDirection::kToTop},\n        {\"bottom\", LinearGradientDirection::kToBottom},\n        {\"left\", LinearGradientDirection::kToLeft},\n        {\"right\", LinearGradientDirection::kToRight},\n        {\"topright\", LinearGradientDirection::kToTopRight},\n        {\"righttop\", LinearGradientDirection::kToTopRight},\n        {\"topleft\", LinearGradientDirection::kToTopLeft},\n        {\"lefttop\", LinearGradientDirection::kToTopLeft},\n        {\"bottomright\", LinearGradientDirection::kToBottomRight},\n        {\"rightbottom\", LinearGradientDirection::kToBottomRight},\n        {\"bottomleft\", LinearGradientDirection::kToBottomLeft},\n        {\"leftbottom\", LinearGradientDirection::kToBottomLeft},\n};\nstatic constexpr float kPositionNotSet = -2.f;\n\nstatic const std::unordered_map<LinearGradientDirection, FloatPoint>\n    kDirectionToStartPoints = {\n        {LinearGradientDirection::kToTop, {0.f, -1.f}},\n        {LinearGradientDirection::kToBottom, {0.f, 1.f}},\n        {LinearGradientDirection::kToLeft, {-1.f, 0.f}},\n        {LinearGradientDirection::kToRight, {1.f, 0.f}},\n        {LinearGradientDirection::kToTopRight, {1.f, -1.f}},\n        {LinearGradientDirection::kToTopLeft, {-1.f, -1.f}},\n        {LinearGradientDirection::kToBottomRight, {1.f, 1.f}},\n        {LinearGradientDirection::kToBottomLeft, {-1.f, 1.f}},\n};\n\ntemplate <typename T>\nvoid ConvertColorAndStops(Gradient::ColorAndStops* position_colors,\n                          const T& gradient_data) {\n  // invalid or empty positions:\n  bool has_valid_positions = true;\n  std::vector<GradientPosition> positions;\n  if ((!gradient_data.positions) ||\n      (gradient_data.positions && gradient_data.positions_length < 2) ||\n      (gradient_data.positions &&\n       gradient_data.positions_length != gradient_data.colors_length)) {\n    has_valid_positions = false;\n\n    // If given positions are invalid or empty, generate by ourself.\n    // positions.size() should be same as |gradient_data.colors_length|.\n    // push the first element:\n    positions.push_back({0.f, GradientPositionType::kPercent});\n\n    auto position = positions[0];\n    float delta = 1.f / (gradient_data.colors_length - 1);\n    // push the middle |gradient_data.colors_length - 2| elements:\n    for (int i = 0; i < gradient_data.colors_length - 2; i++) {\n      position.value += delta;\n      positions.push_back(position);\n    }\n    // push the last element:\n    positions.push_back({1.f, GradientPositionType::kPercent});\n  }\n\n  for (int i = 0; i < gradient_data.colors_length; i++) {\n    GradientPosition position;\n    if (has_valid_positions) {\n      position.value = gradient_data.positions[i];\n      position.type =\n          static_cast<GradientPositionType>(gradient_data.position_types[i]);\n    } else {\n      position = positions[i];\n    }\n    position_colors->emplace_back(position, gradient_data.colors[i]);\n  }\n}\n\nuint32_t LerpColor(uint32_t start_color, uint32_t end_color, float start_pos,\n                   float end_pos, float current_pos) {\n  float t = (current_pos - start_pos) / (end_pos - start_pos);\n  return static_cast<uint32_t>(\n      Color::Lerp(Color(start_color), Color(end_color), t));\n}\n\nvoid ClampColorAndStopListAtFront(std::vector<uint32_t>& colors,\n                                  std::vector<float>& stops,\n                                  uint32_t first_positive_index) {\n  float prev_stop = stops[first_positive_index - 1];\n  uint32_t prev_color = colors[first_positive_index - 1];\n\n  float current_stop = stops[first_positive_index];\n  uint32_t current_color = colors[first_positive_index];\n\n  uint32_t result_color =\n      LerpColor(prev_color, current_color, prev_stop, current_stop, 0.f);\n  // update prev content\n  stops[first_positive_index - 1] = 0.f;\n  colors[first_positive_index - 1] = result_color;\n\n  // remove all other negative stops and colors\n  if (first_positive_index - 1 > 0) {\n    stops.erase(stops.begin(), stops.begin() + first_positive_index - 1);\n    colors.erase(colors.begin(), colors.begin() + first_positive_index - 1);\n  }\n}\n\nvoid ClampColorAndStopListAtBack(std::vector<uint32_t>& colors,\n                                 std::vector<float>& stops,\n                                 uint32_t tail_position) {\n  float prev_stop = stops[tail_position - 1];\n  uint32_t prev_color = colors[tail_position - 1];\n\n  float current_stop = stops[tail_position];\n  uint32_t current_color = colors[tail_position];\n\n  uint32_t result_color =\n      LerpColor(prev_color, current_color, prev_stop, current_stop, 1.f);\n  // update tail content\n  stops[tail_position] = 1.f;\n  colors[tail_position] = result_color;\n\n  // remote all other greater than 100% stops and colors\n  if (tail_position + 1 < stops.size()) {\n    stops.erase(stops.begin() + tail_position + 1, stops.end());\n    colors.erase(colors.begin() + tail_position + 1, colors.end());\n  }\n}\n\nvoid ClampColorAndStopList(std::vector<uint32_t>& colors,\n                           std::vector<float>& stops) {\n  if (stops.size() < 2) {\n    return;\n  }\n  bool clamp_front = stops.front() < 0.f;\n  bool clamp_back = stops.back() > 1.f;\n\n  if (!clamp_front && !clamp_back) {\n    return;\n  }\n\n  if (clamp_front) {\n    // find first positive position\n    uint32_t first_positive_index = 0;\n    auto result = std::find_if(stops.begin(), stops.end(),\n                               [](float v) { return v >= 0.f; });\n    if (result != stops.end()) {\n      first_positive_index = static_cast<uint32_t>(result - stops.begin());\n    }\n\n    if (first_positive_index != 0) {\n      ClampColorAndStopListAtFront(colors, stops, first_positive_index);\n    }\n  }\n\n  if (clamp_back) {\n    // find fist greater than 1.f position\n    uint32_t tail_position = 0;\n    auto result = std::find_if(stops.begin(), stops.end(),\n                               [](float v) { return v >= 1.f; });\n    if (result != stops.end()) {\n      tail_position = static_cast<uint32_t>(result - stops.begin());\n    }\n    if (tail_position != 0) {\n      ClampColorAndStopListAtBack(colors, stops, tail_position);\n    }\n  }\n}\n\n}  // namespace\n\n// static\nstd::optional<Gradient> GradientFactory::CreateLinear(\n    const ClayLinearGradient& gradient_data) {\n  // invalid colors:\n  if (!gradient_data.colors || gradient_data.colors_length < 2) {\n    return std::nullopt;\n  }\n\n  std::optional<Gradient> res(std::in_place);\n  res->type_ = GradientType::kLinear;\n  res->direction_ = LinearGradientDirection::kAngle;\n  res->angle_ = gradient_data.degree / 180.0 * M_PI;\n  ConvertColorAndStops(&res->position_colors_, gradient_data);\n\n  return res;\n}\n\n// static\nstd::optional<Gradient> GradientFactory::CreateRadial(\n    const ClayRadialGradient& gradient_data) {\n  // invalid colors:\n  if (!gradient_data.colors || gradient_data.colors_length < 2) {\n    return std::nullopt;\n  }\n\n  std::optional<Gradient> res(std::in_place);\n  res->type_ = GradientType::kRadial;\n\n  // shape\n  res->radial_shape_ = static_cast<RadialShapeType>(gradient_data.shape_type);\n\n  // center\n  res->center_.center_x = static_cast<RadialCenterType>(gradient_data.center_x);\n  res->center_.center_x_value = gradient_data.center_x_value;\n  res->center_.center_y = static_cast<RadialCenterType>(gradient_data.center_y);\n  res->center_.center_y_value = gradient_data.center_y_value;\n\n  // extent: will be converted to radius later\n  res->extent_ = static_cast<RadialGradientExtent>(gradient_data.shape_size);\n\n  res->length_x_.type =\n      static_cast<GradientLengthType>(gradient_data.length_x_unit);\n  res->length_x_.value = gradient_data.length_x;\n  res->length_y_.type =\n      static_cast<GradientLengthType>(gradient_data.length_y_unit);\n  res->length_y_.value = gradient_data.length_y;\n\n  ConvertColorAndStops(&res->position_colors_, gradient_data);\n\n  return res;\n}\n\n// static\nstd::optional<Gradient> GradientFactory::CreateConic(\n    const ClayConicGradient& gradient_data) {\n  if (!gradient_data.colors || gradient_data.colors_length < 2) {\n    return std::nullopt;\n  }\n\n  std::optional<Gradient> res(std::in_place);\n  res->type_ = GradientType::kConic;\n  res->conic_center_x_.type = gradient_data.x_is_percent\n                                  ? GradientPositionType::kPercent\n                                  : GradientPositionType::kNumber;\n  res->conic_center_x_.value = gradient_data.center_x;\n  res->conic_center_y_.type = gradient_data.y_is_percent\n                                  ? GradientPositionType::kPercent\n                                  : GradientPositionType::kNumber;\n  res->conic_center_y_.value = gradient_data.center_y;\n\n  res->start_angle_ = gradient_data.start_angle;\n  res->end_angle_ = gradient_data.end_angle;\n\n  ConvertColorAndStops(&res->position_colors_, gradient_data);\n  return res;\n}\n\n// static\nstd::optional<Gradient> GradientFactory::Create(std::string raw_data) {\n  GradientType type;\n  std::string args_str;\n  if (lynx::base::BeginsWith(raw_data, kLinearPrefix)) {\n    type = GradientType::kLinear;\n    args_str = raw_data.substr(strlen(kLinearPrefix));\n  } else if (lynx::base::BeginsWith(raw_data, kRadialPrefix)) {\n    type = GradientType::kRadial;\n    args_str = raw_data.substr(strlen(kRadialPrefix));\n  }\n  if (args_str.empty()) {\n    return std::nullopt;\n  }\n\n  std::vector<std::string_view> args = ParseArgs(args_str);\n  if (args.empty()) {\n    return std::nullopt;\n  }\n\n  std::optional<Gradient> res(std::in_place);\n  res->type_ = type;\n\n  int color_start_idx = 0;\n  ParseResult parse_res = ParseDirection(args[0], res.value());\n  if (parse_res == ParseResult::kFailed) {\n    return std::nullopt;\n  } else if (parse_res == ParseResult::kSucceeded) {\n    color_start_idx = 1;\n  } else {  // NotFound\n    color_start_idx = 0;\n    res->direction_ = LinearGradientDirection::kToBottom;\n  }\n\n  parse_res = ParseColorPositions(args, color_start_idx, res.value());\n  if (parse_res != ParseResult::kSucceeded) {\n    return std::nullopt;\n  }\n  res->raw_data_ = std::move(raw_data);\n\n  return res;\n}\n\n// static\nstd::vector<std::string_view> GradientFactory::ParseArgs(\n    const std::string_view& args) {\n  std::vector<std::string_view> res;\n  // used to handle xxx-gradient(xxx, rgba(xxx), xxx)\n  int left_parenthesis = 1;\n  size_t substr_head = 0;\n  size_t cur_idx = 0;\n\n  const auto add_arg = [&args, &substr_head, &cur_idx, &res]() -> bool {\n    if (substr_head >= cur_idx) {\n      return false;\n    }\n\n    res.emplace_back(lynx::base::TrimToStringView(\n        args.substr(substr_head, cur_idx - substr_head)));\n    substr_head = cur_idx + 1;\n    return true;\n  };\n\n  while (cur_idx < args.length()) {\n    const char cur = args[cur_idx];\n    switch (cur) {\n      case '(':\n        ++left_parenthesis;\n        break;\n      case ')':\n        --left_parenthesis;\n        if (left_parenthesis == 0) {\n          if (!add_arg()) {\n            return {};\n          }\n        }\n        break;\n      case ',':\n        if (left_parenthesis <= 1) {\n          if (!add_arg()) {\n            return {};\n          }\n        }\n        break;\n      default:\n        break;\n    }\n    cur_idx++;\n  }\n  return res;\n}\n\n// static\nGradientFactory::ParseResult GradientFactory::ParseDirection(\n    const std::string_view& arg, Gradient& gradient) {\n  if (lynx::base::BeginsWith(arg, \"to\")) {\n    // case 1: to xxx (xxx)\n    // Note(Xietong): refer to iOS implementation. Tolerate string without\n    // space (e.g. \"tobottomleft\")\n    std::string dir = lynx::base::RemoveSpaces(arg.substr(strlen(\"to\")));\n    const auto itr = kDirectionMap.find(dir);\n    if (itr == kDirectionMap.end()) {\n      return ParseResult::kFailed;\n    }\n    gradient.direction_ = itr->second;\n  } else if (lynx::base::EndsWith(arg, \"deg\")) {\n    // case 2: xxx deg\n    gradient.direction_ = LinearGradientDirection::kAngle;\n    const std::string value_str(arg.substr(0, arg.length() - 3));\n    double value;\n    if (lynx::base::StringToDouble(value_str, value)) {\n      gradient.angle_ = value / 180.0 * M_PI;\n    } else {\n      return ParseResult::kFailed;\n    }\n  } else if (lynx::base::EndsWith(arg, \"rad\")) {\n    // case 3: xxx rad\n    gradient.direction_ = LinearGradientDirection::kAngle;\n    const std::string value_str(arg.substr(0, arg.length() - 3));\n    double value;\n    if (lynx::base::StringToDouble(value_str, value)) {\n      gradient.angle_ = value;\n    } else {\n      return ParseResult::kFailed;\n    }\n  } else if (lynx::base::EndsWith(arg, \"turn\")) {\n    // case 4: xxx turn\n    gradient.direction_ = LinearGradientDirection::kAngle;\n    const std::string value_str(arg.substr(0, arg.length() - 4));\n    double value;\n    if (lynx::base::StringToDouble(value_str, value)) {\n      gradient.angle_ = value * 2.0 * M_PI;\n    } else {\n      return ParseResult::kFailed;\n    }\n  } else {\n    return ParseResult::kNotFound;\n  }\n  return ParseResult::kSucceeded;\n}\n\n// static\nGradientFactory::ParseResult GradientFactory::ParseColorPositions(\n    const std::vector<std::string_view>& args, size_t start_idx,\n    Gradient& gradient) {\n  int parsed_color_count = 0;\n  int not_set_pos_idx = -1;\n  for (size_t idx = start_idx; idx < args.size(); ++idx) {\n    std::string_view cur_str = args[idx];\n    std::string_view prefix;\n    size_t color_end = cur_str.find_first_of(')');\n    if (color_end != std::string_view::npos) {\n      prefix = cur_str.substr(0, color_end);\n      cur_str = cur_str.substr(color_end);\n    }\n    std::vector<std::string_view> tokens =\n        lynx::base::SplitToStringViews(cur_str, \" \");\n    FML_DCHECK(tokens.size() > 0);\n    std::string color_str(prefix);\n    color_str += tokens[0];\n    Color to_parse;\n    if (!Color::Parse(color_str, &to_parse)) {\n      continue;\n    }\n\n    float position = kPositionNotSet;\n    if (parsed_color_count == 0) {\n      position = 0.f;\n    }\n    ++parsed_color_count;\n\n    if (tokens.size() == 1 && idx != args.size() - 1) {\n      gradient.position_colors_.emplace_back(\n          GradientPosition{position, GradientPositionType::kPercent}, to_parse);\n      if (position == kPositionNotSet && not_set_pos_idx == -1) {\n        not_set_pos_idx = gradient.position_colors_.size() - 1;\n      }\n      continue;\n    } else if (tokens.size() == 1 && idx == args.size() - 1) {\n      // TODO(Xietong): confirm if this is right.\n      position = 1.f;\n    } else if (lynx::base::EndsWith(tokens[1], \"%\")) {\n      if (!lynx::base::StringToFloat(\n              std::string(tokens[1].substr(0, tokens[1].length() - 1)),\n              position)) {\n        return ParseResult::kFailed;\n      }\n      position = position / 100.f;\n    } else {\n      // NOTE: We do not support non-percentage values (e.g. 10px) here, as this\n      // method is for internal use only.\n      if (!lynx::base::StringToFloat(std::string(tokens[1]), position)) {\n        return ParseResult::kFailed;\n      }\n    }\n    gradient.position_colors_.emplace_back(\n        GradientPosition{position, GradientPositionType::kPercent}, to_parse);\n\n    // Interpolate the previous not set positions\n    if (not_set_pos_idx != -1) {\n      const float low =\n          gradient.position_colors_[not_set_pos_idx - 1].first.value;\n      const float high = position;\n      const float num =\n          static_cast<float>(parsed_color_count - 1 - not_set_pos_idx) + 1.f;\n      for (int i = not_set_pos_idx; i < parsed_color_count - 1; ++i) {\n        gradient.position_colors_[i].first.value =\n            low + (high - low) / num * (i - not_set_pos_idx + 1);\n      }\n    }\n    not_set_pos_idx = -1;\n  }\n\n  if (gradient.position_colors_.empty()) {\n    return ParseResult::kNotFound;\n  }\n\n  return ParseResult::kSucceeded;\n}\n\n// Support both linear-gradient and radial-gradient\n// static\nstd::shared_ptr<ColorSource> GradientFactory::CreateShader(\n    const Gradient& gradient, const FloatRect& rect) {\n  if (gradient.Type() == GradientType::kLinear) {\n    return CreateLinearShader(gradient, rect);\n  } else if (gradient.Type() == GradientType::kRadial) {\n    return CreateRadialShader(gradient, rect);\n  } else if (gradient.Type() == GradientType::kConic) {\n    return CreateConicShader(gradient, rect);\n  } else {\n    // GradientType::kNoSet\n    return std::shared_ptr<ColorSource>();\n  }\n}\n\n// static\nstd::shared_ptr<ColorSource> GradientFactory::CreateLinearShader(\n    const Gradient& gradient, const FloatRect& rect) {\n  skity::Vec2 pts[2];\n  ParseLinearGradientStartPoints(gradient, rect, pts);\n\n  float total_length = sqrtf((pts[1].x - pts[0].x) * (pts[1].x - pts[0].x) +\n                             (pts[1].y - pts[0].y) * (pts[1].y - pts[0].y));\n  const Gradient::ColorAndStops& pos_to_colors = gradient.PositionColors();\n  uint32_t stop_count = pos_to_colors.size();\n  std::vector<float> positions(stop_count);\n  std::vector<uint32_t> colors(stop_count);\n  for (size_t i = 0; i < stop_count; i++) {\n    auto item = pos_to_colors[i];\n    switch (item.first.type) {\n      case GradientPositionType::kNumber:\n        positions[i] = item.first.value / total_length;\n        break;\n      case GradientPositionType::kPercent:\n      default:\n        positions[i] = item.first.value;\n        break;\n    }\n    colors[i] = item.second.Value();\n  }\n  ClampColorAndStopList(colors, positions);\n\n  static_assert(sizeof(Color) == sizeof(uint32_t));\n  return ColorSource::MakeLinear(pts[0], pts[1], stop_count,\n                                 reinterpret_cast<Color*>(colors.data()),\n                                 positions.data(), TileMode::kClamp, nullptr);\n}\n\n// static\nstd::shared_ptr<ColorSource> GradientFactory::CreateRadialShader(\n    const Gradient& gradient, const FloatRect& rect) {\n  FloatPoint at;\n  ParseRadialCenter(gradient, rect, at);\n  skity::Vec2 sk_center = skity::Vec2(at.x(), at.y());\n  double radius = 0;\n  float width = rect.width();\n  float height = rect.height();\n  skity::Matrix matrix;\n  ParseRadialRadiusAndMatrix(gradient, radius, width, height, at, matrix);\n  float sk_radius = radius;\n  const Gradient::ColorAndStops& pos_to_colors = gradient.PositionColors();\n  uint32_t stop_count = pos_to_colors.size();\n  std::vector<float> positions(stop_count);\n  std::vector<uint32_t> colors(stop_count);\n  for (size_t i = 0; i < stop_count; i++) {\n    auto item = pos_to_colors[i];\n    switch (item.first.type) {\n      case GradientPositionType::kNumber:\n        positions[i] = item.first.value / radius;\n        break;\n      case GradientPositionType::kPercent:\n      default:\n        positions[i] = item.first.value;\n        break;\n    }\n    colors[i] = item.second.Value();\n  }\n  ClampColorAndStopList(colors, positions);\n\n  static_assert(sizeof(Color) == sizeof(uint32_t));\n  return ColorSource::MakeRadial(sk_center, sk_radius, stop_count,\n                                 reinterpret_cast<Color*>(colors.data()),\n                                 positions.data(), TileMode::kClamp, &matrix);\n}\n\n// static\nstd::shared_ptr<ColorSource> GradientFactory::CreateConicShader(\n    const Gradient& gradient, const FloatRect& rect) {\n  FloatPoint center;\n  ParseConicCenter(gradient, rect, center);\n  const Gradient::ColorAndStops& pos_to_colors = gradient.PositionColors();\n  uint32_t stop_count = pos_to_colors.size();\n  std::vector<float> positions(stop_count);\n  std::vector<uint32_t> colors(stop_count);\n  for (size_t i = 0; i < stop_count; i++) {\n    auto& item = pos_to_colors[i];\n    const GradientPosition& pos = item.first;\n    const Color& color = item.second;\n    switch (pos.type) {\n      case GradientPositionType::kNumber:\n        positions[i] = item.first.value / 360.0;\n        break;\n      case GradientPositionType::kPercent:\n      default:\n        positions[i] = item.first.value;\n        break;\n    }\n    colors[i] = color.Value();\n  }\n  ClampColorAndStopList(colors, positions);\n  skity::Vec2 sk_center = skity::Vec2(center.x(), center.y());\n  // Skia and Skity default start at positive x axis, but web standard start at\n  // negative y axis. Thus rotate counterclockwise by 90'' here to comply with\n  // web standard.\n  skity::Matrix mtx =\n      skity::Matrix::RotateDeg(gradient.StartAngle() - 90.0, sk_center);\n\n  static_assert(sizeof(Color) == sizeof(uint32_t));\n  return ColorSource::MakeSweep(sk_center, 0, gradient.EndAngle(), stop_count,\n                                reinterpret_cast<Color*>(colors.data()),\n                                positions.data(), TileMode::kClamp, &mtx);\n}\n\n// static\nvoid GradientFactory::ParseRadialRadiusAndMatrix(\n    const Gradient& gradient, double& radius, const float& width,\n    const float& height, const FloatPoint& at, skity::Matrix& matrix) {\n  float center_x_val = at.x();\n  float center_y_val = at.y();\n  switch (gradient.Extent()) {\n    case RadialGradientExtent::kClosestSide: {\n      float x = std::min(center_x_val, width - center_x_val);\n      float y = std::min(center_y_val, height - center_y_val);\n      radius = std::min(x, y);\n      if (gradient.radial_shape_ == RadialShapeType::kEllipse) {\n        matrix.PostScale(x / radius, y / radius);\n        matrix.PostTranslate(center_x_val - center_x_val * x / radius,\n                             center_y_val - center_y_val * y / radius);\n      }\n      break;\n    }\n    case RadialGradientExtent::kClosestCorner: {\n      float x = std::min(center_x_val, width - center_x_val);\n      float y = std::min(center_y_val, height - center_y_val);\n      if (gradient.radial_shape_ == RadialShapeType::kCircle) {\n        radius = std::sqrt(x * x + y * y);\n      } else {\n        radius = std::min(x, y);\n        matrix.PostScale(x / radius, y / radius);\n        matrix.PostTranslate(center_x_val - center_x_val * x / radius,\n                             center_y_val - center_y_val * y / radius);\n        radius *= 1.41421356f;\n      }\n      break;\n    }\n    case RadialGradientExtent::kFarthestSide: {\n      float x = std::max(center_x_val, width - center_x_val);\n      float y = std::max(center_y_val, height - center_y_val);\n      radius = std::max(x, y);\n      if (gradient.radial_shape_ == RadialShapeType::kEllipse) {\n        matrix.PostScale(x / radius, y / radius);\n        matrix.PostTranslate(center_x_val - center_x_val * x / radius,\n                             center_y_val - center_y_val * y / radius);\n      }\n      break;\n    }\n    case RadialGradientExtent::kFarthestCorner: {\n      float x = std::max(center_x_val, width - center_x_val);\n      float y = std::max(center_y_val, height - center_y_val);\n      if (gradient.radial_shape_ == RadialShapeType::kCircle) {\n        radius = std::sqrt(x * x + y * y);\n      } else {\n        radius = std::max(x, y);\n        matrix.PostScale(x / radius, y / radius);\n        matrix.PostTranslate(center_x_val - center_x_val * x / radius,\n                             center_y_val - center_y_val * y / radius);\n        radius *= 1.41421356f;\n      }\n      break;\n    }\n    case RadialGradientExtent::kLength: {\n      float x = gradient.length_x_.value;\n      float y = gradient.length_y_.value;\n      if (gradient.length_x_.type == GradientLengthType::kPercent) {\n        x *= width;\n      }\n      if (gradient.length_y_.type == GradientLengthType::kPercent) {\n        y *= height;\n      }\n      radius = std::min(x, y);\n      matrix.PostScale(x / radius, y / radius);\n      matrix.PostTranslate(center_x_val - center_x_val * x / radius,\n                           center_y_val - center_y_val * y / radius);\n      break;\n    }\n  }\n  if (RoughlyEqualToZero(radius)) {\n    FML_DLOG(WARNING) << \"Paint a radial-gradient with 0 radius.\";\n  }\n}\n\nvoid GradientFactory::ParseRadialCenter(const Gradient& gradient,\n                                        const FloatRect& rect, FloatPoint& at) {\n  float width = rect.width();\n  float height = rect.height();\n  auto calculate = [](RadialCenterType type, float value, float base) {\n    type = static_cast<RadialCenterType>(-static_cast<int>(type));\n    switch (type) {\n      case RadialCenterType::BACKGROUND_POSITION_CENTER:\n        return base * 0.5f;\n      case RadialCenterType::BACKGROUND_POSITION_LEFT:\n      case RadialCenterType::BACKGROUND_POSITION_TOP:\n        return 0.f;\n      case RadialCenterType::BACKGROUND_POSITION_RIGHT:\n      case RadialCenterType::BACKGROUND_POSITION_BOTTOM:\n        return base;\n      case RadialCenterType::RADIAL_CENTER_TYPE_PERCENTAGE:\n        return base * value / 100.f;\n      default:\n        return value;\n    }\n  };\n  auto& center = gradient.Center();\n  at.SetX(calculate(center.center_x, center.center_x_value, width));\n  at.SetY(calculate(center.center_y, center.center_y_value, height));\n}\n\n// static\nvoid GradientFactory::ParseConicCenter(const Gradient& gradient,\n                                       const FloatRect& rect,\n                                       FloatPoint& center) {\n  float width = rect.width();\n  float height = rect.height();\n  auto& x = gradient.ConicCenterX();\n  auto& y = gradient.ConicCenterY();\n  center.SetX(x.type == GradientPositionType::kNumber ? x.value\n                                                      : x.value * width);\n  center.SetY(y.type == GradientPositionType::kNumber ? y.value\n                                                      : y.value * height);\n}\n\nGradientFactory::ParseResult GradientFactory::ParseLinearGradientStartPoints(\n    const Gradient& gradient, const FloatRect& rect, skity::Vec2* pts) {\n  const float width = rect.width();\n  const float height = rect.height();\n\n  // If width/height is null, x/y can be infinite.\n  if (width == 0 || height == 0) {\n    FML_DLOG(ERROR) << \"ParseLinearGradientStartPoints with empty width/height\";\n    pts[0] = skity::Vec2(rect.x(), rect.y());\n    pts[1] = skity::Vec2(rect.x(), rect.y());\n    return ParseResult::kFailed;\n  }\n\n  float angle = 0.f;\n\n  auto direction_itr = kDirectionToStartPoints.find(gradient.Direction());\n  if (direction_itr != kDirectionToStartPoints.end()) {\n    const FloatPoint& start_point = direction_itr->second;\n    angle = atan2(start_point.x() * height, -start_point.y() * width) -\n            static_cast<float>(M_PI) * 2.f;\n  } else if (gradient.Direction() == LinearGradientDirection::kAngle) {\n    angle = gradient.Angle();\n  }\n\n  const float sin_val = sin(angle);\n  const float cos_val = cos(angle);\n\n  const float length = std::abs(sin_val * width) + std::abs(cos_val * height);\n  const float x = sin_val * length / width;\n  const float y = cos_val * length / height;\n  const float halfWidth = width / 2.0;\n  const float halfHeight = height / 2.0;\n  pts[0] = skity::Vec2(rect.x() + halfWidth + -x * halfWidth,\n                       rect.y() + halfHeight + y * halfHeight);\n  pts[1] = skity::Vec2(rect.x() + halfWidth + x * halfWidth,\n                       rect.y() + halfHeight - y * halfHeight);\n\n  return ParseResult::kSucceeded;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/gradient_factory.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_GRADIENT_FACTORY_H_\n#define CLAY_UI_PAINTER_GRADIENT_FACTORY_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/style/color_source.h\"\n#include \"clay/ui/painter/gradient.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\nclass GradientFactory {\n public:\n  static std::optional<Gradient> CreateLinear(\n      const ClayLinearGradient& gradient_data);\n  static std::optional<Gradient> CreateRadial(\n      const ClayRadialGradient& gradient_data);\n  static std::optional<Gradient> CreateConic(\n      const ClayConicGradient& gradient_data);\n  static std::optional<Gradient> Create(std::string raw_data);\n  static std::shared_ptr<ColorSource> CreateShader(const Gradient& gradient,\n                                                   const FloatRect& rect);\n\n private:\n  FRIEND_TEST(GradientTest, DirectionToPoints);\n\n  static std::vector<std::string_view> ParseArgs(const std::string_view& args);\n\n  enum class ParseResult {\n    kNotFound = 0,\n    kSucceeded,\n    kFailed,\n  };\n\n  static std::shared_ptr<ColorSource> CreateLinearShader(\n      const Gradient& gradient, const FloatRect& rect);\n\n  static std::shared_ptr<ColorSource> CreateRadialShader(\n      const Gradient& gradient, const FloatRect& rect);\n\n  static std::shared_ptr<ColorSource> CreateConicShader(\n      const Gradient& gradient, const FloatRect& rect);\n\n  static void ParseRadialCenter(const Gradient& gradient, const FloatRect& rect,\n                                FloatPoint& at);\n\n  static void ParseRadialRadiusAndMatrix(const Gradient& gradient,\n                                         double& radius, const float& width,\n                                         const float& height,\n                                         const FloatPoint& at,\n                                         skity::Matrix& matrix);\n\n  static void ParseConicCenter(const Gradient& gradient, const FloatRect& rect,\n                               FloatPoint& center);\n\n  static ParseResult ParseDirection(const std::string_view& arg,\n                                    Gradient& gradient);\n\n  static ParseResult ParseColorPositions(\n      const std::vector<std::string_view>& args, size_t start_idx,\n      Gradient& gradient);\n\n  static ParseResult ParseLinearGradientStartPoints(const Gradient& gradient,\n                                                    const FloatRect& rect,\n                                                    skity::Vec2* pts);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_GRADIENT_FACTORY_H_\n"
  },
  {
    "path": "clay/ui/painter/gradient_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cmath>\n\n#include \"clay/gfx/style/color_unittests_helper.h\"\n#include \"clay/ui/painter/gradient.h\"\n#include \"clay/ui/painter/gradient_factory.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(GradientTest, CreateLinearGradient) {\n  std::string raw_data(\"linear-gradient(45deg, blue, red)\");\n  std::optional<Gradient> gradient = Gradient::Create(raw_data);\n  EXPECT_TRUE(gradient.has_value());\n  EXPECT_EQ(gradient->Type(), GradientType::kLinear);\n  EXPECT_EQ(gradient->Direction(), LinearGradientDirection::kAngle);\n  EXPECT_DOUBLE_EQ(gradient->Angle(), 45.0 * M_PI / 180.0);\n\n  {\n    const auto& pos_colors = gradient->PositionColors();\n    EXPECT_EQ(pos_colors.size(), 2u);\n    EXPECT_EQ(pos_colors[0].first.value, 0.f);\n    EXPECT_EQ_RGBO(pos_colors[0].second, 0, 0, 255, 1.0);\n\n    EXPECT_EQ(pos_colors[1].first.value, 1.f);\n    EXPECT_EQ_RGBO(pos_colors[1].second, 255, 0, 0, 1.0);\n  }\n\n  EXPECT_EQ(raw_data, gradient->RawData());\n\n  // Invalid Case\n  raw_data = \"linear-gradient(45deg)\";\n  gradient = Gradient::Create(raw_data);\n  EXPECT_FALSE(gradient.has_value());\n\n  raw_data = \"linear-gradient(to top, black, cyan)\";\n  gradient = Gradient::Create(raw_data);\n  EXPECT_TRUE(gradient.has_value());\n  EXPECT_EQ(gradient->Type(), GradientType::kLinear);\n  EXPECT_EQ(gradient->Direction(), LinearGradientDirection::kToTop);\n\n  raw_data = \"linear-gradient(tolefttop, black, cyan)\";\n  gradient = Gradient::Create(raw_data);\n  EXPECT_TRUE(gradient.has_value());\n  EXPECT_EQ(gradient->Type(), GradientType::kLinear);\n  EXPECT_EQ(gradient->Direction(), LinearGradientDirection::kToTopLeft);\n\n  // No direction\n  raw_data = \"linear-gradient(black, cyan)\";\n  gradient = Gradient::Create(raw_data);\n  EXPECT_TRUE(gradient.has_value());\n  EXPECT_EQ(gradient->Type(), GradientType::kLinear);\n  EXPECT_EQ(gradient->Direction(), LinearGradientDirection::kToBottom);\n  EXPECT_EQ(gradient->PositionColors().size(), 2u);\n\n  // More than 2 colors.\n  raw_data = \"linear-gradient(135deg, red, red 60%, blue)\";\n  gradient = Gradient::Create(raw_data);\n  EXPECT_TRUE(gradient.has_value());\n  {\n    const auto& pos_colors = gradient->PositionColors();\n    EXPECT_EQ(pos_colors.size(), 3u);\n    EXPECT_EQ(pos_colors[0].first.value, 0.f);\n    EXPECT_EQ(pos_colors[1].first.value, 0.6f);\n    EXPECT_EQ(pos_colors[2].first.value, 1.f);\n  }\n\n  // Interpolate pos.\n  raw_data =\n      \"linear-gradient(135deg, white, white, white 20%, red, red, red, red \"\n      \"60%, blue, blue, blue, blue)\";\n  gradient = Gradient::Create(raw_data);\n  EXPECT_TRUE(gradient.has_value());\n  {\n    const auto& pos_colors = gradient->PositionColors();\n    EXPECT_EQ(pos_colors.size(), 11u);\n    EXPECT_FLOAT_EQ(pos_colors[0].first.value, 0.f);\n    EXPECT_FLOAT_EQ(pos_colors[1].first.value, 0.1f);\n    EXPECT_FLOAT_EQ(pos_colors[2].first.value, 0.2f);\n    EXPECT_FLOAT_EQ(pos_colors[3].first.value, 0.3f);\n    EXPECT_FLOAT_EQ(pos_colors[4].first.value, 0.4f);\n    EXPECT_FLOAT_EQ(pos_colors[5].first.value, 0.5f);\n    EXPECT_FLOAT_EQ(pos_colors[6].first.value, 0.6f);\n    EXPECT_FLOAT_EQ(pos_colors[7].first.value, 0.7f);\n    EXPECT_FLOAT_EQ(pos_colors[8].first.value, 0.8f);\n    EXPECT_FLOAT_EQ(pos_colors[9].first.value, 0.9f);\n    EXPECT_FLOAT_EQ(pos_colors[10].first.value, 1.f);\n  }\n}\n\nTEST(GradientTest, DirectionToPoints) {\n  Gradient gradient;\n  gradient.type_ = GradientType::kLinear;\n\n  gradient.direction_ = LinearGradientDirection::kToTop;\n  skity::Vec2 pts[2];\n  GradientFactory::ParseLinearGradientStartPoints(\n      gradient, FloatRect(50.f, 100.f, 20, 40.f), pts);\n  EXPECT_FLOAT_EQ(pts[0].x, 60.f);\n  EXPECT_FLOAT_EQ(pts[0].y, 140.f);\n  EXPECT_FLOAT_EQ(pts[1].x, 60.f);\n  EXPECT_FLOAT_EQ(pts[1].y, 100.f);\n\n  gradient.direction_ = LinearGradientDirection::kToBottom;\n  GradientFactory::ParseLinearGradientStartPoints(\n      gradient, FloatRect(50.f, 100.f, 20, 40.f), pts);\n  EXPECT_FLOAT_EQ(pts[1].x, 60.f);\n  EXPECT_FLOAT_EQ(pts[1].y, 140.f);\n  EXPECT_FLOAT_EQ(pts[0].x, 60.f);\n  EXPECT_FLOAT_EQ(pts[0].y, 100.f);\n\n  gradient.direction_ = LinearGradientDirection::kToLeft;\n  GradientFactory::ParseLinearGradientStartPoints(\n      gradient, FloatRect(50.f, 100.f, 20, 40.f), pts);\n  EXPECT_FLOAT_EQ(pts[0].x, 70.f);\n  EXPECT_FLOAT_EQ(pts[0].y, 120.f);\n  EXPECT_FLOAT_EQ(pts[1].x, 50.f);\n  EXPECT_FLOAT_EQ(pts[1].y, 120.f);\n\n  gradient.direction_ = LinearGradientDirection::kToTopRight;\n  GradientFactory::ParseLinearGradientStartPoints(\n      gradient, FloatRect(50.f, 100.f, 20, 40.f), pts);\n  EXPECT_FLOAT_EQ(pts[0].x, 44.f);\n  EXPECT_FLOAT_EQ(pts[0].y, 128.f);\n  EXPECT_FLOAT_EQ(pts[1].x, 76.f);\n  EXPECT_FLOAT_EQ(pts[1].y, 112.f);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/image_painter.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/painter/image_painter.h\"\n\n#include <algorithm>\n#include <array>\n#include <cmath>\n#include <memory>\n#include <vector>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/image/image_resource.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/ui/common/background_data.h\"\n#include \"clay/ui/painter/gradient_factory.h\"\n#include \"clay/ui/rendering/decode_utils.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\nnamespace {\n\nvoid CalculateSizeByMode(FillMode mode, const skity::Vec2& input_size,\n                         const skity::Vec2& output_size, skity::Vec2& src_size,\n                         skity::Vec2& dest_size) {\n  switch (mode) {\n    case FillMode::kScaleToFill:  // BoxFit.fill\n      src_size = input_size;\n      dest_size = output_size;\n      break;\n    case FillMode::kAspectFit:  // BoxFit.contain\n      src_size = input_size;\n      if (output_size.x / output_size.y > src_size.x / src_size.y) {\n        dest_size =\n            skity::Vec2(src_size.x * output_size.y / src_size.y, output_size.y);\n      } else {\n        dest_size =\n            skity::Vec2(output_size.x, src_size.y * output_size.x / src_size.x);\n      }\n      break;\n    case FillMode::kAspectFill:  // BoxFit.fitWidth or BoxFit.fitHeight\n      // if output_size w/h radio is higher, stretch width and cut height, which\n      // means \"fitWidth\".\n      if (input_size.x / input_size.y < output_size.x / output_size.y) {\n        src_size = skity::Vec2(input_size.x,\n                               input_size.x * output_size.y / output_size.x);\n      } else {\n        src_size = skity::Vec2(input_size.y * output_size.x / output_size.y,\n                               input_size.y);\n      }\n      // no need to calculate |dest_size| here.\n      dest_size = output_size;\n      break;\n    case FillMode::kCenter:  // BoxFit.none\n      src_size = skity::Vec2(std::min(input_size.x, output_size.x),\n                             std::min(input_size.y, output_size.y));\n      dest_size = src_size;\n      break;\n    default:\n      break;\n  }\n}\n\nstd::vector<skity::Rect> GenerateImageTileRects(\n    const skity::Rect& output_rect, const skity::Rect& fundamental_rect,\n    ImageRepeat repeat) {\n  int start_x = 0;\n  int start_y = 0;\n  int stop_x = 0;\n  int stop_y = 0;\n  float stride_x = fundamental_rect.Width();\n  float stride_y = fundamental_rect.Height();\n\n  if (repeat == ImageRepeat::kRepeat || repeat == ImageRepeat::kRepeatX) {\n    start_x = floor((output_rect.Left() - fundamental_rect.Left()) / stride_x);\n    stop_x = ceil((output_rect.Right() - fundamental_rect.Right()) / stride_x);\n  }\n\n  if (repeat == ImageRepeat::kRepeat || repeat == ImageRepeat::kRepeatY) {\n    start_y = floor((output_rect.Top() - fundamental_rect.Top()) / stride_y);\n    stop_y =\n        ceil((output_rect.Bottom() - fundamental_rect.Bottom()) / stride_y);\n  }\n\n  std::vector<skity::Rect> tile_rects = {};\n  for (int i = start_x; i <= stop_x; ++i) {\n    for (int j = start_y; j <= stop_y; ++j) {\n      auto rect = fundamental_rect;\n      rect.Offset(i * stride_x, j * stride_y);\n      tile_rects.push_back(rect);\n    }\n  }\n\n  return tile_rects;\n}\n\nImageRepeat BackgroundRepeatToImageRepeat(\n    const ClayBackgroundRepeatType& bg_repeat) {\n  ImageRepeat repeat = ImageRepeat::kRepeat;\n  if (bg_repeat == ClayBackgroundRepeatType::kNoRepeat) {\n    repeat = ImageRepeat::kNoRepeat;\n  } else if (bg_repeat == ClayBackgroundRepeatType::kRepeat) {\n    repeat = ImageRepeat::kRepeat;\n  } else if (bg_repeat == ClayBackgroundRepeatType::kRepeatX) {\n    repeat = ImageRepeat::kRepeatX;\n  } else if (bg_repeat == ClayBackgroundRepeatType::kRepeatY) {\n    repeat = ImageRepeat::kRepeatY;\n  }\n  return repeat;\n}\n\nFloatRect GetPaintingBox(RenderBox* render_box, const FloatRect& frame_rect,\n                         ClayBackgroundOriginType origin) {\n  float output_x = frame_rect.x();\n  float output_y = frame_rect.y();\n  float output_w = frame_rect.width();\n  float output_h = frame_rect.height();\n  if (origin == ClayBackgroundOriginType::kPaddingBox) {\n    output_x += render_box->BorderLeft();\n    output_y += render_box->BorderTop();\n    output_w -= (render_box->BorderLeft() + render_box->BorderRight());\n    output_h -= (render_box->BorderTop() + render_box->BorderBottom());\n  } else if (origin == ClayBackgroundOriginType::kContentBox) {\n    output_x += (render_box->BorderLeft() + render_box->PaddingLeft());\n    output_y += (render_box->BorderTop() + render_box->PaddingTop());\n    output_w -= (render_box->BorderLeft() + render_box->BorderRight() +\n                 render_box->PaddingLeft() + render_box->PaddingRight());\n    output_h -= (render_box->BorderTop() + render_box->BorderBottom() +\n                 render_box->PaddingTop() + render_box->PaddingBottom());\n  }\n  return FloatRect(output_x, output_y, output_w, output_h);\n}\n\nFloatSize GetBackgroundSize(float width, float height,\n                            const BackgroundSize& size_x,\n                            const BackgroundSize& size_y, float output_w,\n                            float output_h, bool is_image) {\n  float aspect = width / height;\n  if (size_x.IsCover()) {  // apply cover\n    width = output_w;\n    height = width / aspect;\n    if (height < output_h) {\n      height = output_h;\n      width = aspect * height;\n    }\n  } else if (size_x.IsContain()) {  // apply contain\n    width = output_w;\n    height = width / aspect;\n\n    if (height > output_h) {\n      height = output_h;\n      width = aspect * height;\n    }\n  } else {\n    width = size_x.apply(output_w, width);\n    height = size_y.apply(output_h, height);\n\n    if (size_x.IsAuto()) {\n      // gradient has no aspect\n      width = is_image ? aspect * height : output_w;\n    }\n    if (size_y.IsAuto()) {\n      // gradient has no aspect\n      height = is_image ? width / aspect : output_h;\n    }\n  }\n  return FloatSize(width, height);\n}\n\ninline float GetPixelRatio(Renderer* renderer) {\n  if (!renderer) {\n    return 1.0f;\n  }\n  return renderer->GetPixelRatio<kPixelTypeLogical, kPixelTypeClay>();\n}\n}  // namespace\n\nskity::Vec2 ImagePainter::CalculateSize(FillMode mode,\n                                        const skity::Vec2& input_size,\n                                        const skity::Vec2& output_size) {\n  skity::Vec2 src_size, dest_size;\n  CalculateSizeByMode(mode, input_size, output_size, src_size, dest_size);\n  float scale = std::max(dest_size.x / src_size.x, dest_size.y / src_size.y);\n  return skity::Vec2(input_size.x * scale, input_size.y * scale);\n}\n\nskity::Vec2 ImagePainter::CalculateSize(RenderBox* render_box,\n                                        const skity::Vec2& input_size,\n                                        const FloatRect& frame_rect,\n                                        ClayBackgroundOriginType origin,\n                                        const BackgroundSize& size_x,\n                                        const BackgroundSize& size_y) {\n  auto rect = GetPaintingBox(render_box, frame_rect, origin);\n  FloatSize size = GetBackgroundSize(input_size.x, input_size.y, size_x, size_y,\n                                     rect.width(), rect.height(), false);\n  float scale =\n      std::max(size.width() / input_size.x, size.height() / input_size.y);\n  return skity::Vec2(input_size.x * scale, input_size.y * scale);\n}\n\nImagePainter::ImagePainter(RenderBox* render_box) : render_box_(render_box) {\n  FML_DCHECK(render_box_);\n}\n\nvoid ImagePainter::PaintImage(GraphicsContext* context,\n                              const ImageData& image_data) {\n  TRACE_EVENT(\"clay\", \"PaintImage\");\n  FML_DCHECK(context);\n\n  if (!image_data.image_resource) {\n    return;\n  }\n\n  FloatRect content = render_box_->ContentRect();\n  if (content.IsEmpty()) {\n    return;\n  }\n\n#ifdef ENABLE_SKITY\n  if (!image_data.image_resource->GetImage()) {\n    return;\n  }\n  image_data.image_resource->GetImage()->Upload(\n      context->GetUnrefQueue(),\n      Size{static_cast<int>(\n               render_box_->GetRenderer()->ConvertTo<kPixelTypePhysical>(\n                   content.width())),\n           static_cast<int>(\n               render_box_->GetRenderer()->ConvertTo<kPixelTypePhysical>(\n                   content.height()))});\n#endif  // ENABLE_SKITY\n\n#ifndef ENABLE_SKITY\n  DecodePriority priority = DecodePriority::kImmediate;\n  if (render_box_->ImageDecodeWithPriority() &&\n      image_data.image_resource->GetImage()->NeedDecode()) {\n    priority = DecodeUtils::GetDecodePriority(render_box_);\n  }\n  auto image = image_data.image_resource->GetGraphicsImage(priority);\n#else\n  auto image = image_data.image_resource->GetGraphicsImage();\n#endif  // ENABLE_SKITY\n\n  if (!image || image->width() == 0 || image->height() == 0) {\n    return;\n  }\n\n  // output_rect's offset is (0.f, 0.f).\n  skity::Rect output_rect =\n      skity::Rect::MakeXYWH(0.f, 0.f, content.width(), content.height());\n\n  // If it is a 9-patch image, Ignore FillMode.\n  // Ignore FillMode and ImageRepeat when drawing 9-patch.\n  if (image_data.has_cap_insets) {\n    const float pixel_ratio = GetPixelRatio(render_box_->GetRenderer());\n    std::array<float, 4> cap_insets_px;\n    for (size_t i = 0; i < 4; ++i) {\n      if (i == 0 || i == 2) {\n        cap_insets_px[i] = image_data.cap_insets[i].GetValue(image->height());\n      } else {\n        cap_insets_px[i] = image_data.cap_insets[i].GetValue(image->width());\n      }\n    }\n    context->DrawNinePatch(image.get(), cap_insets_px,\n                           image_data.cap_insets_scale, pixel_ratio,\n                           output_rect);\n    return;\n  }\n\n  // If not a 9-patch image, calculate src and dst sizes by mode:\n  skity::Vec2 input_size = skity::Vec2(image->width(), image->height());\n  skity::Vec2 output_size =\n      skity::Vec2(output_rect.Width(), output_rect.Height());\n  skity::Vec2 src_size = skity::Vec2{0, 0};\n  skity::Vec2 dest_size = skity::Vec2{0, 0};\n  CalculateSizeByMode(image_data.mode, input_size, output_size, src_size,\n                      dest_size);\n\n  float output_x = output_rect.Left();\n  float output_y = output_rect.Top();\n  float dest_x = output_x + (output_size.x - dest_size.x) * 0.5f;\n  float dest_y = output_y + (output_size.y - dest_size.y) * 0.5f;\n  skity::Rect dst_rect =\n      skity::Rect::MakeXYWH(dest_x, dest_y, dest_size.x, dest_size.y);\n\n  float src_x = (input_size.x - src_size.x) * 0.5f;\n  float src_y = (input_size.y - src_size.y) * 0.5f;\n  skity::Rect src_rect =\n      skity::Rect::MakeXYWH(src_x, src_y, src_size.x, src_size.y);\n\n  ImageRepeat repeat = image_data.repeat;\n\n  Paint paint;\n  // Always disable anti-alias when paint image. On particular low-end devices,\n  // 'anti-alias' may result in the failure to display the image.\n  paint.setAntiAlias(false);\n  paint.setOpacity(image_data.image_opacity);\n\n  if (image_data.tint_color.has_value()) {\n    std::shared_ptr<ColorFilter> tint_filter = ColorFilter::MakeBlend(\n        image_data.tint_color.value(), BlendMode::kSrcIn);\n    paint.setColorFilter(tint_filter);\n  }\n\n  skity::RRect round_rect = image_data.round_rect;\n  if (repeat == ImageRepeat::kNoRepeat) {\n    if (round_rect.IsEmpty() || round_rect.IsRect()) {\n      context->DrawImageRect(image, src_rect, dst_rect, GetSamplingOptions(),\n                             &paint);\n    } else {\n      Paint work_paint = paint;\n      work_paint.setDrawStyle(DrawStyle::kFill);\n      work_paint.setAntiAlias(true);\n      skity::Matrix local_matrix =\n          skity::Matrix::Translate(dst_rect.Left(), dst_rect.Top()) *\n          skity::Matrix::Scale(dst_rect.Width() / src_rect.Width(),\n                               dst_rect.Height() / src_rect.Height()) *\n          skity::Matrix::Translate(-src_rect.Left(), -src_rect.Top());\n      auto shader = ColorSource::MakeImage(\n          PaintImage::Make(image), TileMode::kDecal, TileMode::kDecal,\n          render_box_->ImageFilterMode() == FilterMode::kLinear\n              ? ImageSampling::kLinear\n              : ImageSampling::kNearestNeighbor,\n          &local_matrix);\n      work_paint.setColorSource(shader);\n      context->DrawRRect(round_rect, work_paint);\n    }\n  } else {\n    GraphicsContext::AutoRestore saver(context, true);\n    if (round_rect.IsEmpty() || round_rect.IsRect()) {\n      context->ClipRect(output_rect, GrClipOp::kIntersect, false);\n    } else {\n      context->ClipRRect(round_rect, GrClipOp::kIntersect, true);\n    }\n    std::vector<skity::Rect> tiles =\n        GenerateImageTileRects(output_rect, dst_rect, repeat);\n    for (auto& tile : tiles) {\n      context->DrawImageRect(image, src_rect, tile, GetSamplingOptions(),\n                             &paint);\n    }\n  }\n}\n\nvoid ImagePainter::PaintBackgroundImage(\n    GraphicsContext* context, const FloatRect& frame_rect,\n    const skity::RRect& rounded_rect, const BackgroundImage& bg_image,\n    ClayBackgroundOriginType origin, ClayBackgroundClipType clip,\n    const BackgroundSize& size_x, const BackgroundSize& size_y,\n    const BackgroundPosition& position_x, const BackgroundPosition& position_y,\n    ClayBackgroundRepeatType repeat_x, ClayBackgroundRepeatType repeat_y) {\n  FML_DCHECK(context);\n  if (frame_rect.IsEmpty() || !render_box_ || !render_box_->CanDisplay()) {\n    return;\n  }\n  if (bg_image.IsEmpty()) {\n    return;\n  }\n\n  auto rect = GetPaintingBox(render_box_, frame_rect, origin);\n  float output_x = rect.x();\n  float output_y = rect.y();\n  float output_w = rect.width();\n  float output_h = rect.height();\n\n  static const float pixel_ratio = GetPixelRatio(render_box_->GetRenderer());\n  float image_width, image_height, width, height;\n  if (bg_image.IsSkImage()) {\n    image_width = bg_image.Width();\n    image_height = bg_image.Height();\n    width = image_width * pixel_ratio;\n    height = image_height * pixel_ratio;\n  } else {\n    width = image_width = output_w;\n    height = image_height = output_h;\n  }\n\n  FloatSize size = GetBackgroundSize(width, height, size_x, size_y, output_w,\n                                     output_h, (bg_image.GetImageResource()));\n  width = size.width();\n  height = size.height();\n  if (width < 1 || height < 1) {\n    return;\n  }\n\n  // linear-gradient size should be updated by\n  // the background-size\n  if (!bg_image.GetImageResource()) {\n    image_width = width;\n    image_height = height;\n  }\n  output_x += position_x.apply(output_w - width);\n  output_y += position_y.apply(output_h - height);\n\n  if (output_x >= frame_rect.MaxX() || output_y >= frame_rect.MaxY()) {\n    return;\n  }\n\n  // Draw with ClayBackgroundClipType and ClayBackgroundRepeatType:\n  FloatRect clip_rect = frame_rect;\n  // The canvas was translated in PaintBoxDecorationBackground(), now we\n  // are in the scope of its |GraphicsContext::AutoRestore|. When calculate the\n  // clip_rect, we have to consider the offset.\n  FloatPoint offset(render_box_->location());\n  if (clip == ClayBackgroundClipType::kPaddingBox) {\n    clip_rect = render_box_->PaddingRect();\n    // opposite to canvas.Translate() in PaintBoxDecorationBackground().\n    clip_rect.Move(-offset.x(), -offset.y());\n  } else if (clip == ClayBackgroundClipType::kContentBox) {\n    clip_rect = render_box_->ContentRect();\n    // opposite to canvas.Translate() in PaintBoxDecorationBackground().\n    clip_rect.Move(-offset.x(), -offset.y());\n  }\n\n  if (!clip_rect.IsEmpty()) {\n    context->ClipRect(clip_rect, GrClipOp::kIntersect, false);\n  }\n  if (clip == ClayBackgroundClipType::kBorderArea) {\n    FloatRect padding_rect = render_box_->PaddingRect();\n    padding_rect.Move(-offset.x(), -offset.y());\n    context->ClipRect(padding_rect, GrClipOp::kDifference, false);\n  }\n\n  GraphicsContext::AutoRestore saver(context, true);\n  skity::Rect src_rect = skity::Rect::MakeXYWH(0, 0, image_width, image_height);\n  skity::Rect dst_rect = skity::Rect::MakeXYWH(0, 0, width, height);\n  skity::RRect draw_rounded_rect = skity::RRect::MakeRect(dst_rect);\n  if (!rounded_rect.IsEmpty()) {\n    draw_rounded_rect.SetRectRadii(dst_rect, rounded_rect.Radii());\n  }\n  ImageRepeat image_repeat_x = BackgroundRepeatToImageRepeat(repeat_x);\n  ImageRepeat image_repeat_y = BackgroundRepeatToImageRepeat(repeat_y);\n  if (image_repeat_x == ImageRepeat::kNoRepeat &&\n      image_repeat_y == ImageRepeat::kNoRepeat) {\n    context->Translate(output_x, output_y);\n    PaintBackgroundImage(context, bg_image, src_rect, draw_rounded_rect);\n    return;\n  }\n  // Draw the repeating images\n  float end_x = std::max<float>(output_x + output_w, clip_rect.MaxX());\n  float end_y = std::max<float>(output_y + output_h, clip_rect.MaxY());\n  float start_x = output_x;\n  float start_y = output_y;\n  if (image_repeat_x == ImageRepeat::kRepeat ||\n      image_repeat_x == ImageRepeat::kRepeatX) {\n    start_x = start_x - std::ceil(start_x / width) * width;\n  }\n  if (image_repeat_y == ImageRepeat::kRepeat ||\n      image_repeat_y == ImageRepeat::kRepeatY) {\n    start_y = start_y - std::ceil(start_y / height) * height;\n  }\n\n  // Calculate number of repetitions in X and Y directions\n  int num_repeats_x = std::ceil((end_x - start_x) / width);\n  int num_repeats_y = std::ceil((end_y - start_y) / height);\n\n  // Clip to the target rect when there are multiple repeat objects\n  if ((num_repeats_x > 1 || num_repeats_y > 1) && !rounded_rect.IsEmpty()) {\n    context->ClipRRect(rounded_rect, GrClipOp::kIntersect, true);\n    draw_rounded_rect.SetRect(dst_rect);\n  }\n\n  for (float x = start_x; x < end_x; x += width) {\n    for (float y = start_y; y < end_y; y += height) {\n      GraphicsContext::AutoRestore saver(context, true);\n      context->Translate(x, y);\n      PaintBackgroundImage(context, bg_image, src_rect, draw_rounded_rect);\n      if (image_repeat_y == ImageRepeat::kNoRepeat) {\n        break;\n      }\n    }\n    if (image_repeat_x == ImageRepeat::kNoRepeat) {\n      break;\n    }\n  }\n}\n\nvoid ImagePainter::PaintBackgroundImage(GraphicsContext* context,\n                                        const BackgroundImage& bg_image,\n                                        const skity::Rect& src_rect,\n                                        const skity::RRect& dst_rrect) {\n  FML_DCHECK(context);\n\n  if (bg_image.IsEmpty()) {\n    return;\n  }\n\n  auto image_resource = bg_image.GetImageResource();\n  if (image_resource) {\n#ifdef ENABLE_SKITY\n    if (!image_resource->GetImage()) {\n      return;\n    }\n    image_resource->GetImage()->Upload(\n        context->GetUnrefQueue(),\n        Size{static_cast<int>(\n                 render_box_->GetRenderer()->ConvertTo<kPixelTypePhysical>(\n                     src_rect.Width())),\n             static_cast<int>(\n                 render_box_->GetRenderer()->ConvertTo<kPixelTypePhysical>(\n                     src_rect.Height()))});\n#endif  // ENABLE_SKITY\n\n#ifndef ENABLE_SKITY\n    DecodePriority priority = DecodePriority::kImmediate;\n    if (render_box_->ImageDecodeWithPriority() &&\n        image_resource->GetImage()->NeedDecode()) {\n      priority = DecodeUtils::GetDecodePriority(render_box_);\n    }\n    auto image = image_resource->GetGraphicsImage(priority);\n#else\n    auto image = image_resource->GetGraphicsImage();\n#endif  // ENABLE_SKITY\n    if (!image) {\n      return;\n    }\n    auto dst_rect = dst_rrect.GetRect();\n    Paint paint;\n    skity::Matrix local_matrix =\n        skity::Matrix::Translate(dst_rect.Left(), dst_rect.Top()) *\n        skity::Matrix::Scale(dst_rect.Width() / src_rect.Width(),\n                             dst_rect.Height() / src_rect.Height()) *\n        skity::Matrix::Translate(-src_rect.Left(), -src_rect.Top());\n    auto shader = ColorSource::MakeImage(\n        PaintImage::Make(image), TileMode::kDecal, TileMode::kDecal,\n        render_box_->ImageFilterMode() == FilterMode::kLinear\n            ? ImageSampling::kLinear\n            : ImageSampling::kNearestNeighbor,\n        &local_matrix);\n    paint.setColorSource(shader);\n    paint.setAntiAlias(true);\n    context->DrawRRect(dst_rrect, paint);\n  } else {\n    auto color_source = GradientFactory::CreateShader(bg_image.GetGradient(),\n                                                      FloatRect(src_rect));\n    Paint paint;\n    paint.setDither(true);\n    paint.setColorSource(color_source);\n    paint.setAntiAlias(true);\n    context->DrawRRect(dst_rrect, paint);\n  }\n}\n\nGrSamplingOptions ImagePainter::GetSamplingOptions() const {\n  return SAMPLING_OPTIONS(render_box_->ImageFilterMode(), 0);\n}\n\nvoid ImagePainter::PaintMaskImage(GrCanvas* canvas,\n                                  fml::RefPtr<GPUUnrefQueue> unref_queue) {\n  auto pixel_ratio = GetPixelRatio(render_box_->GetRenderer());\n  const MaskData& mask = render_box_->Mask();\n\n  auto frame_rect = render_box_->GetFrameRect();\n  frame_rect.SetX(0);\n  frame_rect.SetY(0);\n  for (size_t index = 0; index < mask.images.size(); index++) {\n    const MaskImage& mask_image = mask.images[index];\n    if (mask_image.IsEmpty()) {\n      continue;\n    }\n\n    // Consider origin.\n    ClayMaskOriginType origin = ClayMaskOriginType::kBorderBox;\n    if (!mask.origins.empty()) {\n      auto origin_index = index % mask.origins.size();\n      origin = mask.origins[origin_index];\n    }\n    auto painting_box = GetPaintingBox(render_box_, frame_rect, origin);\n    float image_width, image_height, width, height;\n    if (mask_image.IsSkImage()) {\n      image_width = mask_image.Width();\n      image_height = mask_image.Height();\n      width = image_width * pixel_ratio;\n      height = image_height * pixel_ratio;\n    } else {\n      image_width = width = painting_box.width();\n      image_height = height = painting_box.height();\n    }\n\n    // Consider size.\n    if (mask.sizes.size() >= 2) {\n      auto size_index = index % mask.sizes.size();\n      MaskSize size_x, size_y;\n      if (index * 2 >= mask.sizes.size()) {\n        size_x = mask.sizes[mask.sizes.size() - 2];\n        size_y = mask.sizes[mask.sizes.size() - 1];\n      } else {\n        size_x = mask.sizes[size_index * 2];\n        size_y = mask.sizes[size_index * 2 + 1];\n      }\n      FloatSize size =\n          GetBackgroundSize(width, height, size_x, size_y, painting_box.width(),\n                            painting_box.height(), mask_image.IsSkImage());\n      width = size.width();\n      height = size.height();\n      if (width < 1 || height < 1) {\n        return;\n      }\n      // linear-gradient size should be updated by mask-size.\n      if (!mask_image.IsSkImage()) {\n        image_width = width;\n        image_height = height;\n      }\n    }\n\n    // Consider clip.\n    FloatRect clip_rect;\n    FloatPoint offset(render_box_->location());\n    ClayMaskClipType clip_type = ClayMaskClipType::kBorderBox;\n    if (!mask.clips.empty()) {\n      auto clip_index = index % mask.clips.size();\n      clip_type = mask.clips[clip_index];\n    }\n    switch (clip_type) {\n      case ClayMaskClipType::kBorderBox: {\n        clip_rect = frame_rect;\n        break;\n      }\n      case ClayMaskClipType::kPaddingBox: {\n        clip_rect = render_box_->PaddingRect();\n        clip_rect.Move(-offset.x(), -offset.y());\n        break;\n      }\n      case ClayMaskClipType::kContentBox: {\n        clip_rect = render_box_->ContentRect();\n        clip_rect.Move(-offset.x(), -offset.y());\n        break;\n      }\n    }\n\n    // Consider positions.\n    float offset_x = painting_box.x(), offset_y = painting_box.y();\n    if (mask.positions.size() < 2) {\n      // Default position for mask is 'center'\n      offset_x += (painting_box.width() - width) * 0.5;\n      offset_y += (painting_box.height() - height) * 0.5;\n    } else {\n      auto pos_index = index % (mask.positions.size() / 2);\n\n      float delta_width = painting_box.width() - width;\n      float delta_height = painting_box.height() - height;\n\n      MaskPosition pos_x = mask.positions[pos_index * 2];\n      MaskPosition pos_y = mask.positions[pos_index * 2 + 1];\n      offset_x += pos_x.apply(delta_width);\n      offset_y += pos_y.apply(delta_height);\n    }\n\n    // Consider repeat.\n    ClayMaskRepeatType repeat_x = ClayMaskRepeatType::kRepeat,\n                       repeat_y = ClayMaskRepeatType::kRepeat;\n    if (mask.repeats.size() >= 2) {\n      auto repeat_index = index % (mask.repeats.size() / 2);\n      repeat_x = mask.repeats[repeat_index * 2];\n      repeat_y = mask.repeats[repeat_index * 2 + 1];\n    }\n\n    FloatRect src_rect = FloatRect(0, 0, image_width, image_height);\n    FloatRect dst_rect = FloatRect(0, 0, width, height);\n    int save_count = CANVAS_GET_SAVE_COUNT(canvas);\n    CANVAS_CLIP_RECT(canvas, clip_rect);\n    if (repeat_x == ClayBackgroundRepeatType::kNoRepeat &&\n        repeat_y == ClayBackgroundRepeatType::kNoRepeat) {\n      CANVAS_SAVE(canvas);\n      CANVAS_TRANSLATE(canvas, offset_x, offset_y);\n      PaintSingleMaskImage(canvas, mask_image, src_rect, dst_rect, unref_queue);\n      CANVAS_RESTORE(canvas);\n    } else {\n      float end_x = std::max(painting_box.x() + painting_box.width(),\n                             clip_rect.x() + clip_rect.width());\n      float end_y = std::max(painting_box.y() + painting_box.height(),\n                             clip_rect.y() + clip_rect.height());\n      float start_x = offset_x, start_y = offset_y;\n\n      if (repeat_x == ClayBackgroundRepeatType::kRepeat ||\n          repeat_x == ClayBackgroundRepeatType::kRepeatX) {\n        start_x = start_x - ((int)std::ceil((double)start_x / width)) * width;\n      }\n      if (repeat_y == ClayBackgroundRepeatType::kRepeat ||\n          repeat_y == ClayBackgroundRepeatType::kRepeatY) {\n        start_y = start_y - ((int)std::ceil((double)start_y / height)) * height;\n      }\n\n      CANVAS_SAVE(canvas);\n      for (float x = start_x; x < end_x; x += width) {\n        for (float y = start_y; y < end_y; y += height) {\n          CANVAS_SAVE(canvas);\n          CANVAS_TRANSLATE(canvas, x, y);\n          PaintSingleMaskImage(canvas, mask_image, src_rect, dst_rect,\n                               unref_queue);\n          CANVAS_RESTORE(canvas);\n          if (repeat_y == ClayBackgroundRepeatType::kNoRepeat) {\n            break;\n          }\n        }\n        if (repeat_x == ClayBackgroundRepeatType::kNoRepeat) {\n          break;\n        }\n      }\n      CANVAS_RESTORE(canvas);\n    }\n    CANVAS_RESTORE_TO_COUNT(canvas, save_count);\n  }\n}\n\nvoid ImagePainter::PaintSingleMaskImage(\n    GrCanvas* canvas, const MaskImage& mask_image, const FloatRect& src_rect,\n    const FloatRect& dst_rect, fml::RefPtr<GPUUnrefQueue> unref_queue) {\n  auto image_resource = mask_image.GetImageResource();\n  GrPaint mask_paint;\n  PAINT_SET_BLEND_MODE(mask_paint, BlendMode::kPlus);\n\n#ifdef ENABLE_SKITY\n  if (image_resource) {\n    FML_DCHECK(unref_queue);\n    if (!image_resource->GetImage()) {\n      return;\n    }\n    image_resource->GetImage()->Upload(\n        unref_queue,\n        Size{static_cast<int>(\n                 render_box_->GetRenderer()->ConvertTo<kPixelTypePhysical>(\n                     src_rect.width())),\n             static_cast<int>(\n                 render_box_->GetRenderer()->ConvertTo<kPixelTypePhysical>(\n                     src_rect.height()))});\n  }\n#endif  // ENABLE_SKITY\n\n  if (image_resource && image_resource->GetGraphicsImage() &&\n      image_resource->GetGraphicsImage()->gr_image()) {\n    auto image = image_resource->GetGraphicsImage()->gr_image();\n\n    CANVAS_DRAW_IMAGE_SRC_RECT(canvas, image, src_rect, dst_rect,\n                               GetSamplingOptions(), mask_paint);\n  } else if (!mask_image.IsSkImage()) {\n    auto color_source =\n        GradientFactory::CreateShader(mask_image.GetGradient(), src_rect);\n    auto shader = color_source->gr_object();\n    PAINT_SET_SHADER(mask_paint, shader);\n    CANVAS_DRAW_RECT(canvas, dst_rect, mask_paint);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/image_painter.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_IMAGE_PAINTER_H_\n#define CLAY_UI_PAINTER_IMAGE_PAINTER_H_\n\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/gfx/image/image_data.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/ui/common/background_data.h\"\n\nnamespace clay {\n\nclass BackgroundImage;\nclass ImageResource;\nclass RenderBox;\nclass BackgroundSize;\nclass BackgroundPosition;\n\nclass ImagePainter {\n public:\n  static skity::Vec2 CalculateSize(FillMode mode, const skity::Vec2& input_size,\n                                   const skity::Vec2& output_size);\n  static skity::Vec2 CalculateSize(RenderBox* render_box,\n                                   const skity::Vec2& input_size,\n                                   const FloatRect& frame_rect,\n                                   ClayBackgroundOriginType origin,\n                                   const BackgroundSize& size_x,\n                                   const BackgroundSize& size_y);\n\n  explicit ImagePainter(RenderBox* render_box);\n  ~ImagePainter() = default;\n\n  void PaintImage(GraphicsContext* context, const ImageData& image_data);\n  void PaintBackgroundImage(\n      GraphicsContext* context, const FloatRect& frame_rect,\n      const skity::RRect& rounded_rect, const BackgroundImage& bg_image,\n      ClayBackgroundOriginType origin, ClayBackgroundClipType clip,\n      const BackgroundSize& size_x, const BackgroundSize& size_y,\n      const BackgroundPosition& position_x,\n      const BackgroundPosition& position_y, ClayBackgroundRepeatType repeat_x,\n      ClayBackgroundRepeatType repeat_y);\n\n  void PaintMaskImage(GrCanvas* canvas,\n                      fml::RefPtr<GPUUnrefQueue> unref_queue = nullptr);\n\n private:\n  void PaintBackgroundImage(GraphicsContext* context,\n                            const BackgroundImage& bg_image,\n                            const skity::Rect& src_rect,\n                            const skity::RRect& dst_rrect);\n  GrSamplingOptions GetSamplingOptions() const;\n\n  void PaintSingleMaskImage(GrCanvas* canvas, const MaskImage& mask_image,\n                            const FloatRect& src_rect,\n                            const FloatRect& dst_rect,\n                            fml::RefPtr<GPUUnrefQueue> unref_queue);\n\n  RenderBox* render_box_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_IMAGE_PAINTER_H_\n"
  },
  {
    "path": "clay/ui/painter/image_painter_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <iostream>\n#include <memory>\n#include <string>\n\n#include \"clay/common/thread_host.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/image/image.h\"\n#include \"clay/gfx/image/image_descriptor.h\"\n#include \"clay/gfx/style/length.h\"\n#include \"clay/testing/canvas_test.h\"\n#include \"clay/ui/painter/image_painter.h\"\n#include \"clay/ui/painter/painter_graphics_test.h\"\n#include \"clay/ui/rendering/render_image.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkImage.h\"\n#include \"third_party/skia/include/core/SkM44.h\"\n#include \"third_party/skia/include/core/SkMaskFilter.h\"\n#include \"third_party/skia/include/gpu/GrContextOptions.h\"\n#include \"third_party/skia/include/gpu/GrDirectContext.h\"\n\nnamespace clay {\nnamespace testing {\n\nusing clay::testing::MockCanvas;\n\nclass ImagePainterTest : public ::testing::Test {\n protected:\n  void SetUp() override {\n    SkISize image_size = SkISize::Make(32, 32);\n    bitmap_.allocPixels(::SkImageInfo::MakeN32(\n        image_size.width(), image_size.width(), kOpaque_SkAlphaType));\n    bitmap_.eraseColor(SK_ColorGREEN);\n    bitmap_.setImmutable();\n    render_image_ = std::make_unique<RenderImage>();\n\n    render_image_->SetLeft(10);\n    render_image_->SetTop(10);\n    render_image_->SetWidth(100);\n    render_image_->SetHeight(100);\n    render_image_->SetRepeat(ImageRepeat::kNoRepeat);\n    image_painter_ = std::make_unique<ImagePainter>(render_image_.get());\n\n    image_ = bitmap_.asImage();\n\n    raw_image_ = Image::CreateImage(\"\", nullptr, nullptr,\n                                    std::weak_ptr<Image::Notifier>(), nullptr,\n                                    nullptr, false, true);\n    render_image_->SetImage(std::make_unique<ImageResource>(raw_image_));\n    render_image_->image_resource_->SetTestImage(GraphicsImage::Make(image_));\n  }\n\n  void SetImageData(ImageData& image_data) {\n    image_data.image_resource = render_image_->image_resource_.get();\n    image_data.mode = render_image_->mode_;\n    image_data.repeat = render_image_->repeat_;\n    image_data.has_cap_insets = render_image_->has_cap_insets_;\n    image_data.cap_insets = render_image_->cap_insets_;\n    image_data.cap_insets_scale = render_image_->cap_insets_scale_;\n    image_data.drop_shadow_offset_x = render_image_->drop_shadow_offset_x_;\n    image_data.drop_shadow_offset_y = render_image_->drop_shadow_offset_y_;\n    image_data.drop_shadow_color = render_image_->drop_shadow_color_;\n  }\n\n  void TearDown() override {}\n\n  FloatPoint Offset() const { return FloatPoint(10, 10); }\n\n  // For debug\n  void PrintDrawCalls() {\n    for (auto& draw_call : graphics_owner_.mock_canvas().draw_calls()) {\n      std::cout << draw_call << std::endl;\n    }\n  }\n\n  SkBitmap bitmap_;\n  // not owned.\n  sk_sp<SkImage> image_ = nullptr;\n  std::shared_ptr<Image> raw_image_;\n  std::unique_ptr<RenderImage> render_image_;\n  std::unique_ptr<ImagePainter> image_painter_;\n  PainterGraphicsTest graphics_owner_;\n};\n\nTEST_F(ImagePainterTest, FillMode) {\n  ImageData image_data;\n  render_image_->SetMode(FillMode::kScaleToFill);\n  SetImageData(image_data);\n  image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n  render_image_->SetMode(FillMode::kAspectFit);\n  SetImageData(image_data);\n  image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n  render_image_->SetMode(FillMode::kAspectFill);\n  SetImageData(image_data);\n  image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n  render_image_->SetMode(FillMode::kCenter);\n  SetImageData(image_data);\n  image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n  auto picture = graphics_owner_.mock_context().FinishRecording();\n  picture->picture()->raw()->playback(&graphics_owner_.mock_canvas());\n\n  constexpr SkRect src = SkRect::MakeLTRB(0, 0, 32, 32);\n  constexpr SkRect dst = SkRect::MakeLTRB(0, 0, 100, 100);\n  constexpr SkRect dst_center = SkRect::MakeLTRB(34, 34, 66, 66);\n\n  SkPaint paint;\n  paint.setAntiAlias(false);\n  const auto expected_draw_calls = std::vector{\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst_center,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n  };\n\n  EXPECT_EQ(graphics_owner_.mock_canvas().draw_calls(), expected_draw_calls);\n}\n\nTEST_F(ImagePainterTest, ImageRepeat) {\n  ImageData image_data;\n  render_image_->SetWidth(64);\n  render_image_->SetHeight(32);\n  render_image_->SetMode(FillMode::kCenter);\n  render_image_->SetRepeat(ImageRepeat::kNoRepeat);\n  SetImageData(image_data);\n  image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n  render_image_->SetRepeat(ImageRepeat::kRepeat);\n  SetImageData(image_data);\n  image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n  render_image_->SetRepeat(ImageRepeat::kRepeatX);\n  SetImageData(image_data);\n  image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n  auto picture = graphics_owner_.mock_context().FinishRecording();\n  picture->picture()->raw()->playback(&graphics_owner_.mock_canvas());\n\n  constexpr SkRect src = SkRect::MakeLTRB(0, 0, 32, 32);\n  constexpr SkRect clipRect = SkRect::MakeLTRB(0, 0, 64, 32);\n  constexpr SkRect dst_1 = SkRect::MakeLTRB(16, 0, 48, 32);\n  constexpr SkRect dst_2 = SkRect::MakeLTRB(-16, 0, 16, 32);\n  constexpr SkRect dst_3 = SkRect::MakeLTRB(48, 0, 80, 32);\n\n  SkPaint paint;\n  paint.setAntiAlias(false);\n  const auto expected_draw_calls = std::vector{\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst_1,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n      MockCanvas::DrawCall{\n          1, MockCanvas::ClipRectData{clipRect, SkClipOp::kIntersect,\n                                      MockCanvas::kHard_ClipEdgeStyle}},\n      MockCanvas::DrawCall{1,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst_2,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{1,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst_1,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{1,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst_3,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}},\n      MockCanvas::DrawCall{0, MockCanvas::SaveData{1}},\n      MockCanvas::DrawCall{\n          1, MockCanvas::ClipRectData{clipRect, SkClipOp::kIntersect,\n                                      MockCanvas::kHard_ClipEdgeStyle}},\n      MockCanvas::DrawCall{1,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst_2,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{1,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst_1,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{1,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src, dst_3,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint, paint}},\n      MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}};\n  EXPECT_EQ(graphics_owner_.mock_canvas().draw_calls(), expected_draw_calls);\n}\n\nTEST_F(ImagePainterTest, Blur) {\n  // Normal blur will not be recorded in single displaylist, in fact, blur will\n  // be applied in layer tree\n\n  /*\n      ImageData image_data;\n\n      render_image_->SetBlurRadius(0.1f);\n      SetImageData(image_data);\n      image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n      render_image_->SetBlurRadius(0.0f);\n      SetImageData(image_data);\n      image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n      auto picture = graphics_owner_.mock_context().FinishRecording();\n      picture->picture()->RenderTo(&graphics_owner_.mock_canvas());\n\n      // 4 draw_calls for FIRST PaintImage and another draw_call for SECOND:\n      EXPECT_EQ(graphics_owner_.mock_canvas().draw_calls().size(), 5u);\n\n      MockCanvas::DrawImageRectData blur_image_draw_call =\n          std::get<MockCanvas::DrawImageRectData>(\n              graphics_owner_.mock_canvas().draw_calls()[2].data);\n      // Draws the blur_image (did not create a new image).\n      EXPECT_TRUE(blur_image_draw_call.image_unique_id == image_->uniqueID());\n\n      blur_image_draw_call = std::get<MockCanvas::DrawImageRectData>(\n          graphics_owner_.mock_canvas().draw_calls()[4].data);\n      // Draws the original image.\n      EXPECT_TRUE(blur_image_draw_call.image_unique_id == image_->uniqueID());\n      */\n}\n\nTEST_F(ImagePainterTest, NinePatchImage) {\n  ImageData image_data;\n  render_image_->SetWidth(100);\n  render_image_->SetHeight(100);\n\n  std::array<Length, 4> cap_insets_vec = {\n      Length(5, LengthUnit::kNum),\n      Length(10, LengthUnit::kNum),\n      Length(0.25, LengthUnit::kPercent),\n      Length(0.5, LengthUnit::kPercent),\n  };\n  render_image_->SetCapInsets(cap_insets_vec);\n  SetImageData(image_data);\n  image_painter_->PaintImage(&graphics_owner_.mock_context(), image_data);\n\n  auto picture = graphics_owner_.mock_context().FinishRecording();\n  picture->picture()->raw()->playback(&graphics_owner_.mock_canvas());\n\n  // 9 draw_calls for individually drawing nine-patch image:\n  EXPECT_EQ(graphics_owner_.mock_canvas().draw_calls().size(), 9u);\n\n  constexpr SkRect src1 = SkRect::MakeLTRB(0, 0, 16, 5);\n  constexpr SkRect src2 = SkRect::MakeLTRB(16, 0, 22, 5);\n  constexpr SkRect src3 = SkRect::MakeLTRB(22, 0, 32, 5);\n  constexpr SkRect src4 = SkRect::MakeLTRB(0, 5, 16, 24);\n  constexpr SkRect src5 = SkRect::MakeLTRB(16, 5, 22, 24);\n  constexpr SkRect src6 = SkRect::MakeLTRB(22, 5, 32, 24);\n  constexpr SkRect src7 = SkRect::MakeLTRB(0, 24, 16, 32);\n  constexpr SkRect src8 = SkRect::MakeLTRB(16, 24, 22, 32);\n  constexpr SkRect src9 = SkRect::MakeLTRB(22, 24, 32, 32);\n\n  constexpr SkRect dst1 = SkRect::MakeLTRB(0, 0, 16, 5);\n  constexpr SkRect dst2 = SkRect::MakeLTRB(16, 0, 90, 5);\n  constexpr SkRect dst3 = SkRect::MakeLTRB(90, 0, 100, 5);\n  constexpr SkRect dst4 = SkRect::MakeLTRB(0, 5, 16, 92);\n  constexpr SkRect dst5 = SkRect::MakeLTRB(16, 5, 90, 92);\n  constexpr SkRect dst6 = SkRect::MakeLTRB(90, 5, 100, 92);\n  constexpr SkRect dst7 = SkRect::MakeLTRB(0, 92, 16, 100);\n  constexpr SkRect dst8 = SkRect::MakeLTRB(16, 92, 90, 100);\n  constexpr SkRect dst9 = SkRect::MakeLTRB(90, 92, 100, 100);\n\n  const auto expected_draw_calls = std::vector{\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src1, dst1,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src2, dst2,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src3, dst3,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src4, dst4,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src5, dst5,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src6, dst6,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src7, dst7,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src8, dst8,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint}},\n      MockCanvas::DrawCall{0,\n                           MockCanvas::DrawImageRectData{\n                               image_->uniqueID(), src9, dst9,\n                               SkSamplingOptions(SkFilterMode::kLinear),\n                               SkCanvas::kFast_SrcRectConstraint}},\n  };\n\n  EXPECT_EQ(graphics_owner_.mock_canvas().draw_calls(), expected_draw_calls);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/object_painter.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/painter/object_painter.h\"\n\n#include <algorithm>\n\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n\nnamespace clay {\n\nvoid ObjectPainter::DrawLineForBoxSide(GraphicsContext* context, int x1, int y1,\n                                       int x2, int y2,\n                                       BorderSide::SideIndex side, Color color,\n                                       BorderStyleType style,\n                                       int adjacent_width1, int adjacent_width2,\n                                       bool antialias) {\n  int thickness;\n  int length;\n  if (side == BorderSide::TOP || side == BorderSide::BOTTOM) {\n    thickness = y2 - y1;\n    length = x2 - x1;\n  } else {\n    thickness = x2 - x1;\n    length = y2 - y1;\n  }\n\n  // We would like this check to be an ASSERT as we don't want to draw empty\n  // borders. However nothing guarantees that the following recursive calls to\n  // DrawLineForBoxSide will have positive thickness and length.\n  if (length <= 0 || thickness <= 0) {\n    return;\n  }\n\n  if (style == BorderStyleType::kDouble && thickness < 3) {\n    style = BorderStyleType::kSolid;\n  }\n\n  switch (style) {\n    case BorderStyleType::kNone:\n    case BorderStyleType::kHide:\n      return;\n    case BorderStyleType::kDotted:\n    case BorderStyleType::kDashed:\n      DrawDashedOrDottedBoxSide(context, x1, y1, x2, y2, side, color, thickness,\n                                length, style, antialias);\n      break;\n    case BorderStyleType::kDouble:\n      DrawDoubleBoxSide(context, x1, y1, x2, y2, length, side, color, thickness,\n                        adjacent_width1, adjacent_width2, antialias);\n      break;\n    case BorderStyleType::kRidge:\n    case BorderStyleType::kGroove:\n      DrawRidgeOrGrooveBoxSide(context, x1, y1, x2, y2, side, color, style,\n                               adjacent_width1, adjacent_width2, antialias);\n      break;\n    case BorderStyleType::kInset:\n      if (side == BorderSide::TOP || side == BorderSide::LEFT) {\n        DrawSolidBoxSide(context, x1, y1, x2, y2, side, color, adjacent_width1,\n                         adjacent_width2, antialias);\n      }\n      break;\n    case BorderStyleType::kOutset:\n      if (side == BorderSide::BOTTOM || side == BorderSide::RIGHT) {\n        DrawSolidBoxSide(context, x1, y1, x2, y2, side, color, adjacent_width1,\n                         adjacent_width2, antialias);\n      }\n      break;\n    case BorderStyleType::kSolid:\n      DrawSolidBoxSide(context, x1, y1, x2, y2, side, color, adjacent_width1,\n                       adjacent_width2, antialias);\n      break;\n    case BorderStyleType::kUndefined:\n    default:\n      break;\n  }\n}\n\nvoid ObjectPainter::DrawDashedOrDottedBoxSide(GraphicsContext* context, int x1,\n                                              int y1, int x2, int y2,\n                                              BorderSide::SideIndex side,\n                                              Color color, int thickness,\n                                              int length, BorderStyleType style,\n                                              bool antialias) {\n  Paint paint;\n  paint.setColor(color);\n  paint.setStrokeWidth(thickness);\n  BorderStyleType stroke_style =\n      (style == BorderStyleType::kDashed ? BorderStyleType::kDashed\n                                         : BorderStyleType::kDotted);\n\n  GraphicsContext::SetupPaintDashPathEffect(&paint, length, thickness,\n                                            stroke_style);\n  switch (side) {\n    case BorderSide::BOTTOM:\n    case BorderSide::TOP: {\n      int mid_y = y1 + thickness / 2;\n      context->DrawLine(paint, FloatPoint(x1, mid_y), FloatPoint(x2, mid_y),\n                        thickness, stroke_style);\n      break;\n    }\n    case BorderSide::RIGHT:\n    case BorderSide::LEFT: {\n      int mid_x = x1 + thickness / 2;\n      context->DrawLine(paint, FloatPoint(mid_x, y1), FloatPoint(mid_x, y2),\n                        thickness, stroke_style);\n      break;\n    }\n  }\n}\n\nvoid ObjectPainter::DrawDoubleBoxSide(GraphicsContext* context, int x1, int y1,\n                                      int x2, int y2, int length,\n                                      BorderSide::SideIndex side, Color color,\n                                      int thickness, int adjacent_width1,\n                                      int adjacent_width2, bool antialias) {\n  int third_of_thickness = (thickness + 1) / 3;\n\n  if (adjacent_width1 != 0.0 && adjacent_width2 != 0.0) {\n    switch (side) {\n      case BorderSide::TOP:\n      case BorderSide::BOTTOM: {\n        Paint paint;\n        paint.setColor(color);\n        context->DrawRect(\n            paint, skity::Rect::MakeXYWH(x1, y1, length, third_of_thickness));\n        context->DrawRect(\n            paint, skity::Rect::MakeXYWH(x1, y2 - third_of_thickness, length,\n                                         third_of_thickness));\n        break;\n      }\n      case BorderSide::LEFT:\n      case BorderSide::RIGHT: {\n        Paint paint;\n        paint.setColor(color);\n        context->DrawRect(\n            paint, skity::Rect::MakeXYWH(x1, y1, third_of_thickness, length));\n        context->DrawRect(paint,\n                          skity::Rect::MakeXYWH(x2 - third_of_thickness, y1,\n                                                third_of_thickness, length));\n        break;\n      }\n    }\n\n    return;\n  }\n\n  int adjacent1_big_third =\n      ((adjacent_width1 > 0) ? adjacent_width1 + 1 : adjacent_width1 - 1) / 3;\n  int adjacent2_big_third =\n      ((adjacent_width2 > 0) ? adjacent_width2 + 1 : adjacent_width2 - 1) / 3;\n\n  switch (side) {\n    case BorderSide::TOP:\n      DrawLineForBoxSide(\n          context, x1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), y1,\n          x2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0),\n          y1 + third_of_thickness, side, color, BorderStyleType::kSolid,\n          adjacent1_big_third, adjacent2_big_third, antialias);\n      DrawLineForBoxSide(context,\n                         x1 + std::max((adjacent_width1 * 2 + 1) / 3, 0),\n                         y2 - third_of_thickness,\n                         x2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), y2,\n                         side, color, BorderStyleType::kSolid,\n                         adjacent1_big_third, adjacent2_big_third, antialias);\n      break;\n    case BorderSide::LEFT:\n      DrawLineForBoxSide(context, x1,\n                         y1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0),\n                         x1 + third_of_thickness,\n                         y2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), side,\n                         color, BorderStyleType::kSolid, adjacent1_big_third,\n                         adjacent2_big_third, antialias);\n      DrawLineForBoxSide(context, x2 - third_of_thickness,\n                         y1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), x2,\n                         y2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), side,\n                         color, BorderStyleType::kSolid, adjacent1_big_third,\n                         adjacent2_big_third, antialias);\n      break;\n    case BorderSide::BOTTOM:\n      DrawLineForBoxSide(\n          context, x1 + std::max((adjacent_width1 * 2 + 1) / 3, 0), y1,\n          x2 - std::max((adjacent_width2 * 2 + 1) / 3, 0),\n          y1 + third_of_thickness, side, color, BorderStyleType::kSolid,\n          adjacent1_big_third, adjacent2_big_third, antialias);\n      DrawLineForBoxSide(context,\n                         x1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0),\n                         y2 - third_of_thickness,\n                         x2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), y2,\n                         side, color, BorderStyleType::kSolid,\n                         adjacent1_big_third, adjacent2_big_third, antialias);\n      break;\n    case BorderSide::RIGHT:\n      DrawLineForBoxSide(context, x1,\n                         y1 + std::max((adjacent_width1 * 2 + 1) / 3, 0),\n                         x1 + third_of_thickness,\n                         y2 - std::max((adjacent_width2 * 2 + 1) / 3, 0), side,\n                         color, BorderStyleType::kSolid, adjacent1_big_third,\n                         adjacent2_big_third, antialias);\n      DrawLineForBoxSide(context, x2 - third_of_thickness,\n                         y1 + std::max((-adjacent_width1 * 2 + 1) / 3, 0), x2,\n                         y2 - std::max((-adjacent_width2 * 2 + 1) / 3, 0), side,\n                         color, BorderStyleType::kSolid, adjacent1_big_third,\n                         adjacent2_big_third, antialias);\n      break;\n    default:\n      break;\n  }\n}\n\nvoid ObjectPainter::DrawRidgeOrGrooveBoxSide(\n    GraphicsContext* context, int x1, int y1, int x2, int y2,\n    BorderSide::SideIndex side, Color color, BorderStyleType style,\n    int adjacent_width1, int adjacent_width2, bool antialias) {\n  BorderStyleType s1;\n  BorderStyleType s2;\n  if (style == BorderStyleType::kGroove) {\n    s1 = BorderStyleType::kInset;\n    s2 = BorderStyleType::kOutset;\n  } else {\n    s1 = BorderStyleType::kOutset;\n    s2 = BorderStyleType::kInset;\n  }\n\n  int adjacent1_big_half =\n      ((adjacent_width1 > 0) ? adjacent_width1 + 1 : adjacent_width1 - 1) / 2;\n  int adjacent2_big_half =\n      ((adjacent_width2 > 0) ? adjacent_width2 + 1 : adjacent_width2 - 1) / 2;\n\n  switch (side) {\n    case BorderSide::TOP:\n      DrawLineForBoxSide(context, x1 + std::max(-adjacent_width1, 0) / 2, y1,\n                         x2 - std::max(-adjacent_width2, 0) / 2,\n                         (y1 + y2 + 1) / 2, side, color, s1, adjacent1_big_half,\n                         adjacent2_big_half, antialias);\n      DrawLineForBoxSide(\n          context, x1 + std::max(adjacent_width1 + 1, 0) / 2, (y1 + y2 + 1) / 2,\n          x2 - std::max(adjacent_width2 + 1, 0) / 2, y2, side, color, s2,\n          adjacent_width1 / 2, adjacent_width2 / 2, antialias);\n      break;\n    case BorderSide::LEFT:\n      DrawLineForBoxSide(context, x1, y1 + std::max(-adjacent_width1, 0) / 2,\n                         (x1 + x2 + 1) / 2,\n                         y2 - std::max(-adjacent_width2, 0) / 2, side, color,\n                         s1, adjacent1_big_half, adjacent2_big_half, antialias);\n      DrawLineForBoxSide(\n          context, (x1 + x2 + 1) / 2, y1 + std::max(adjacent_width1 + 1, 0) / 2,\n          x2, y2 - std::max(adjacent_width2 + 1, 0) / 2, side, color, s2,\n          adjacent_width1 / 2, adjacent_width2 / 2, antialias);\n      break;\n    case BorderSide::BOTTOM:\n      DrawLineForBoxSide(context, x1 + std::max(adjacent_width1, 0) / 2, y1,\n                         x2 - std::max(adjacent_width2, 0) / 2,\n                         (y1 + y2 + 1) / 2, side, color, s2, adjacent1_big_half,\n                         adjacent2_big_half, antialias);\n      DrawLineForBoxSide(\n          context, x1 + std::max(-adjacent_width1 + 1, 0) / 2,\n          (y1 + y2 + 1) / 2, x2 - std::max(-adjacent_width2 + 1, 0) / 2, y2,\n          side, color, s1, adjacent_width1 / 2, adjacent_width2 / 2, antialias);\n      break;\n    case BorderSide::RIGHT:\n      DrawLineForBoxSide(context, x1, y1 + std::max(adjacent_width1, 0) / 2,\n                         (x1 + x2 + 1) / 2,\n                         y2 - std::max(adjacent_width2, 0) / 2, side, color, s2,\n                         adjacent1_big_half, adjacent2_big_half, antialias);\n      DrawLineForBoxSide(context, (x1 + x2 + 1) / 2,\n                         y1 + std::max(-adjacent_width1 + 1, 0) / 2, x2,\n                         y2 - std::max(-adjacent_width2 + 1, 0) / 2, side,\n                         color, s1, adjacent_width1 / 2, adjacent_width2 / 2,\n                         antialias);\n      break;\n  }\n}\n\nvoid ObjectPainter::DrawSolidBoxSide(GraphicsContext* context, int x1, int y1,\n                                     int x2, int y2, BorderSide::SideIndex side,\n                                     Color color, int adjacent_width1,\n                                     int adjacent_width2, bool antialias) {\n  if (adjacent_width1 == 0.0 && adjacent_width2 == 0.0) {\n    Paint paint;\n    paint.setColor(color);\n    context->DrawRect(paint, skity::Rect::MakeXYWH(x1, y1, x2 - x1, y2 - y1));\n    return;\n  }\n\n  FloatPoint quad[4];\n  switch (side) {\n    case BorderSide::TOP:\n      quad[0] = FloatPoint(x1 + std::max(-adjacent_width1, 0), y1);\n      quad[1] = FloatPoint(x1 + std::max(adjacent_width1, 0), y2);\n      quad[2] = FloatPoint(x2 - std::max(adjacent_width2, 0), y2);\n      quad[3] = FloatPoint(x2 - std::max(-adjacent_width2, 0), y1);\n      break;\n    case BorderSide::BOTTOM:\n      quad[0] = FloatPoint(x1 + std::max(adjacent_width1, 0), y1);\n      quad[1] = FloatPoint(x1 + std::max(-adjacent_width1, 0), y2);\n      quad[2] = FloatPoint(x2 - std::max(-adjacent_width2, 0), y2);\n      quad[3] = FloatPoint(x2 - std::max(adjacent_width2, 0), y1);\n      break;\n    case BorderSide::LEFT:\n      quad[0] = FloatPoint(x1, y1 + std::max(-adjacent_width1, 0));\n      quad[1] = FloatPoint(x1, y2 - std::max(-adjacent_width2, 0));\n      quad[2] = FloatPoint(x2, y2 - std::max(adjacent_width2, 0));\n      quad[3] = FloatPoint(x2, y1 + std::max(adjacent_width1, 0));\n      break;\n    case BorderSide::RIGHT:\n      quad[0] = FloatPoint(x1, y1 + std::max(adjacent_width1, 0));\n      quad[1] = FloatPoint(x1, y2 - std::max(adjacent_width2, 0));\n      quad[2] = FloatPoint(x2, y2 - std::max(-adjacent_width2, 0));\n      quad[3] = FloatPoint(x2, y1 + std::max(-adjacent_width1, 0));\n      break;\n  }\n\n  context->FillPolygon(4, quad, color, antialias);\n}\n\nvoid ObjectPainter::DrawDefaultFocusRing(GraphicsContext* context,\n                                         const FloatRoundedRect& round_rect,\n                                         int thickness, Color color) {\n  Paint paint;\n  paint.setColor(color);\n  paint.setDrawStyle(DrawStyle::kStroke);\n  paint.setStrokeWidth(static_cast<float>(thickness));\n\n  context->DrawRRect(round_rect, paint);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/object_painter.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_OBJECT_PAINTER_H_\n#define CLAY_UI_PAINTER_OBJECT_PAINTER_H_\n\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/gfx/style/borders_data.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/painter/border_side.h\"\n\nnamespace clay {\n\nclass FloatPoint;\nclass FloatRect;\nclass FloatSize;\nclass FloatRoundedRect;\nclass RenderObject;\n\nclass ObjectPainter {\n public:\n  explicit ObjectPainter(const RenderObject& layout_object)\n      : layout_object_(layout_object) {}\n\n  static void DrawLineForBoxSide(GraphicsContext* context, int x1, int y1,\n                                 int x2, int y2, BorderSide::SideIndex side,\n                                 Color, BorderStyleType, int adjbw1, int adjbw2,\n                                 bool antialias = false);\n\n  static void DrawDashedOrDottedBoxSide(GraphicsContext* context, int x1,\n                                        int y1, int x2, int y2,\n                                        BorderSide::SideIndex side, Color,\n                                        int thickness, int length,\n                                        BorderStyleType, bool antialias);\n  static void DrawDoubleBoxSide(GraphicsContext* context, int x1, int y1,\n                                int x2, int y2, int length,\n                                BorderSide::SideIndex side, Color,\n                                int thickness, int adjacent_width1,\n                                int adjacent_width2, bool antialias);\n  static void DrawRidgeOrGrooveBoxSide(GraphicsContext* context, int x1, int y1,\n                                       int x2, int y2,\n                                       BorderSide::SideIndex side, Color,\n                                       BorderStyleType, int adjacent_width1,\n                                       int adjacent_width2, bool antialias);\n  static void DrawSolidBoxSide(GraphicsContext* context, int x1, int y1, int x2,\n                               int y2, BorderSide::SideIndex side, Color,\n                               int adjacent_width1, int adjacent_width2,\n                               bool antialias);\n  static void DrawDefaultFocusRing(\n      GraphicsContext* context, const FloatRoundedRect& round_rect,\n      int thickness = num_value::kFocusRingThickness,\n      Color color = Color(color_value::kColorGreen));\n\n  const RenderObject& layout_object_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_OBJECT_PAINTER_H_\n"
  },
  {
    "path": "clay/ui/painter/painter_graphics_test.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/painter/painter_graphics_test.h\"\n\n#include \"clay/testing/mock_canvas.h\"\n#include \"clay/testing/thread_test.h\"\n\nnamespace clay {\n\nnamespace {\n\nfml::RefPtr<fml::TaskRunner> GetCurrentTaskRunner() {\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  return fml::MessageLoop::GetCurrent().GetTaskRunner();\n}\n\n}  // namespace\n\nPainterGraphicsTest::PainterGraphicsTest()\n    : unref_queue_(fml::MakeRefCounted<GPUUnrefQueue>(GetCurrentTaskRunner())),\n      context_(unref_queue_) {\n  context_.BeginRecording(skity::Rect::MakeWH(64, 64));\n  mock_canvas_ = new clay::testing::MockCanvas();\n}\n\nPainterGraphicsTest::~PainterGraphicsTest() {\n  unref_queue_->Drain();\n  delete mock_canvas_;\n}\n\nclay::testing::MockCanvas& PainterGraphicsTest::mock_canvas() {\n  return *mock_canvas_;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/painter_graphics_test.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_PAINTER_GRAPHICS_TEST_H_\n#define CLAY_UI_PAINTER_PAINTER_GRAPHICS_TEST_H_\n\n#include \"clay/gfx/graphics_context.h\"\n\nnamespace clay {\nnamespace testing {\nclass MockCanvas;\n}\n}  // namespace clay\n\nnamespace clay {\n\nclass PainterGraphicsTest {\n public:\n  PainterGraphicsTest();\n  ~PainterGraphicsTest();\n  GraphicsContext& mock_context() { return context_; }\n  clay::testing::MockCanvas& mock_canvas();\n\n private:\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n  GraphicsContext context_;\n  clay::testing::MockCanvas* mock_canvas_;\n  BASE_DISALLOW_COPY_AND_ASSIGN(PainterGraphicsTest);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_PAINTER_GRAPHICS_TEST_H_\n"
  },
  {
    "path": "clay/ui/painter/painting_context.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/painter/painting_context.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/ui/compositing/pending_container_layer.h\"\n#include \"clay/ui/compositing/pending_drawable_image_layer.h\"\n#include \"clay/ui/compositing/pending_effect_layer.h\"\n#include \"clay/ui/compositing/pending_external_view_layer.h\"\n#include \"clay/ui/compositing/pending_layer.h\"\n#include \"clay/ui/compositing/pending_offset_layer.h\"\n#include \"clay/ui/compositing/pending_picture_layer.h\"\n#include \"clay/ui/compositing/pending_platform_view_layer.h\"\n#include \"clay/ui/compositing/pending_punch_hole_layer.h\"\n#include \"clay/ui/compositing/pending_transform_layer.h\"\n#include \"clay/ui/painter/image_painter.h\"\n#include \"clay/ui/rendering/render_external_view.h\"\n#include \"clay/ui/rendering/render_object.h\"\n\nnamespace clay {\n\n// static\nvoid PaintingContext::RepaintCompositedChild(\n    RenderObject* child, fml::RefPtr<GPUUnrefQueue> unref_queue,\n    PaintingContext* parent_context) {\n  PendingContainerLayer* layer = child->GetLayer();\n  if (layer) {\n    layer->RemoveAllChildren();\n    child->UnloadAllEffectLayers();\n  } else {\n    layer = new PendingOffsetLayer();\n    child->SetLayer(std::unique_ptr<PendingContainerLayer>(layer));\n  }\n\n  // TODO(jinsong): Refactor PushXXX to make it easier to create\n  // PendingEffectLayer.\n  std::function<void(PaintingContext&, const FloatPoint&)> painter =\n      [child](PaintingContext& ctx, const FloatPoint& offset) {\n        child->PaintWithContext(ctx, offset);\n      };\n  if (child->CanDisplay()) {\n    // PendingContainerLayer is used to contain all content layers of this\n    // render object.\n    if (child->IsRepaintBoundary() && !child->GetContainerLayer()) {\n      painter = [old_painter = painter, child](PaintingContext& ctx,\n                                               const FloatPoint& offset) {\n        if (child->IsExternalView()) {\n          // The ExternalView has different render backing , so we need to\n          // create a new contianer layer to update it\n          auto renderer = static_cast<RenderExternalView*>(child);\n          skity::Vec2 size = renderer->GetSize();\n          ctx.PushExternalContainer(renderer->element_id(), old_painter, size);\n        } else {\n          ctx.PushContainer(offset, old_painter);\n        }\n      };\n    }\n\n    if (child->HasOpacity() || child->HasOpacityRasterAnimation()) {\n      painter = [child, old_painter = painter](PaintingContext& ctx,\n                                               const FloatPoint& offset) {\n        int alpha = 255;\n        if (child->HasOpacity()) {\n          // Convert opacity[0, 1.0] to alpha[0, 255].\n          alpha = static_cast<int>(child->Opacity() * 255);\n        }\n        ctx.PushOpacity(alpha, offset, old_painter);\n      };\n    }\n\n    if (child->HasColorFilter()) {\n      painter = [child, old_painter = painter](PaintingContext& ctx,\n                                               const FloatPoint& offset) {\n        float m[20];\n        // Get the matrix from SkColorMatrix.\n        std::copy_n(child->ColorFilterMatrix().data(), 20, m);\n\n        auto v = ColorFilter::MakeMatrix(m);\n        ctx.PushColorFilter(v, offset, old_painter);\n      };\n    }\n\n    if (child->HasBlur()) {\n      painter = [child, old_painter = painter](PaintingContext& ctx,\n                                               const FloatPoint& offset) {\n        float blur = GraphicsContext::ConvertRadiusToSigma(child->BlurRadius());\n        auto blur_filter =\n            std::make_shared<BlurImageFilter>(blur, blur, TileMode::kDecal);\n        ctx.PushImageFilter(blur_filter, offset, old_painter);\n      };\n    }\n    if (child->HasClipPath()) {\n      painter = [child, old_painter = painter](PaintingContext& ctx,\n                                               const FloatPoint& offset) {\n        auto& clip_path = child->ClipPath();\n        if (auto ptr = std::get_if<FloatRoundedRect>(&clip_path)) {\n          ctx.PushClipRRect(*ptr, offset, old_painter);\n        } else if (auto ptr = std::get_if<GrPath>(&clip_path)) {\n          ctx.PushClipPath(*ptr, offset, old_painter);\n        }\n      };\n    }\n    painter = child->FixupPainterIfNeeded(painter);\n  }\n\n  PaintingContext child_context(layer, child, unref_queue);\n  // Inherit valid CacheStrategy from parent_context or from render object.\n  if (parent_context &&\n      parent_context->GetCacheStrategy() != CacheStrategy::None) {\n    child_context.SetCacheStrategy(parent_context->GetCacheStrategy());\n  } else {\n    child_context.SetCacheStrategy(child->GetCacheStrategy());\n  }\n\n  if (child->HasMask()) {\n    float width = child->GetFrameRect().width(),\n          height = child->GetFrameRect().height();\n#ifndef ENABLE_SKITY\n    SkPictureRecorder recorder;\n    SkCanvas* canvas = recorder.beginRecording(SkRect::MakeXYWH(\n        0, 0, child->GetFrameRect().width(), child->GetFrameRect().height()));\n    ImagePainter(static_cast<RenderBox*>(child)).PaintMaskImage(canvas);\n\n    auto composite_picture = recorder.finishRecordingAsPicture();\n    auto composite_shader = composite_picture->makeShader(\n        SkTileMode::kClamp, SkTileMode::kClamp, SkFilterMode::kLinear);\n    auto composite_color_source =\n        std::make_shared<UnknownColorSource>(composite_shader);\n#else\n    auto deferred_image = skity::Image::MakeDeferredTextureImage(\n        skity::TextureFormat::kRGBA, width, height,\n        skity::AlphaType::kPremul_AlphaType);\n    auto composite_shader = skity::Shader::MakeShader(deferred_image);\n    auto composite_color_source =\n        std::make_shared<UnknownColorSource>(composite_shader);\n\n    skity::PictureRecorder recorder;\n    recorder.BeginRecording(skity::Rect::MakeWH(width, height));\n    skity::RecordingCanvas* canvas = recorder.GetRecordingCanvas();\n    ImagePainter(static_cast<RenderBox*>(child))\n        .PaintMaskImage(canvas, unref_queue);\n    auto dl = recorder.FinishRecording();\n\n    if (unref_queue && unref_queue->GetContext()) {\n      unref_queue->GetTaskRunner()->PostTask(\n          [dl = std::move(dl), context = unref_queue->GetContext(), width,\n           height, deferred_image] {\n            auto gpu_context = context;\n            skity::GPURenderTargetDescriptor desc = {\n                .width = static_cast<uint32_t>(width),\n                .height = static_cast<uint32_t>(height),\n            };\n            auto render_target = gpu_context->CreateRenderTarget(desc);\n            auto canvas = render_target->GetCanvas();\n            dl->Draw(canvas);\n            canvas->Flush();\n            std::shared_ptr<skity::Image> snapshot =\n                gpu_context->MakeSnapshot(std::move(render_target));\n            deferred_image->SetTexture(*(snapshot->GetTexture()));\n          });\n    }\n#endif  // ENABLE_SKITY\n    painter = [width, height, composite_color_source, old_painter = painter](\n                  PaintingContext& ctx, const FloatPoint& offset) {\n      ctx.PushShaderMask(composite_color_source, FloatRect(0, 0, width, height),\n                         BlendMode::kDstIn, offset, old_painter);\n    };\n  }\n\n  if (child->HasOffsetTransform()) {\n    FML_DCHECK(child->IsRepaintBoundary());\n    painter = [child, old_painter = painter](PaintingContext& ctx,\n                                             const FloatPoint& offset) {\n      ctx.PushTransform(child->GetOffsetTransformOperations(), {0, 0}, 0,\n                        FloatPoint(), old_painter);\n    };\n  }\n\n  if (child->HasTransform() || child->HasTransformRasterAnimation()) {\n    FML_DCHECK(child->IsRepaintBoundary());\n    painter = [child, old_painter = painter](PaintingContext& ctx,\n                                             const FloatPoint& offset) {\n      ctx.PushTransform(child->HasTransformOperations()\n                            ? child->GetTransformOperations()\n                            : TransformOperations(),\n                        child->GetTransformOrigin(), child->GetPerspective(),\n                        FloatPoint(), old_painter);\n    };\n  }\n\n  painter(child_context, FloatPoint());\n}\n\nPaintingContext::PaintingContext(PendingContainerLayer* layer,\n                                 RenderObject* object,\n                                 fml::RefPtr<GPUUnrefQueue> unref_queue)\n    : graphics_context_(unref_queue),\n      render_object_(object),\n      container_layer_(layer) {\n  FML_DCHECK(render_object_);\n}\n\nPaintingContext::~PaintingContext() { StopRecordingIfNeeded(); }\n\nGraphicsContext* PaintingContext::GetGraphicsContext() {\n  if (!graphics_context_.IsRecording()) {\n    StartRecording();\n  }\n  return &graphics_context_;\n}\n\nbool PaintingContext::IsRecording() const {\n  return graphics_context_.IsRecording();\n}\n\nvoid PaintingContext::StartRecording() {\n  FML_DCHECK(render_object());\n  FML_DCHECK(!IsRecording());\n\n  current_layer_ = new PendingPictureLayer();\n  current_layer_->SetCacheStrategy(strategy_);\n  double giant_scalar = 1.0E+9;\n  skity::Rect rect = skity::Rect::MakeLTRB(-giant_scalar, -giant_scalar,\n                                           giant_scalar, giant_scalar);\n  auto success = graphics_context_.BeginRecording(rect);\n  FML_DCHECK(success);\n  container_layer_->AppendChild(current_layer_);\n}\n\nvoid PaintingContext::StopRecordingIfNeeded() {\n  if (!IsRecording()) {\n    return;\n  }\n\n  auto picture = graphics_context_.FinishRecording();\n  if (!picture->IsEmpty()) {\n    current_layer_->set_picture(std::move(picture));\n  }\n  current_layer_ = nullptr;\n}\n\nvoid PaintingContext::SetIsComplexHint() {\n  FML_DCHECK(current_layer_);\n  if (current_layer_) {\n    current_layer_->SetIsComplexHint();\n  }\n}\n\nvoid PaintingContext::SetWillChangeHint() {\n  FML_DCHECK(current_layer_);\n  if (current_layer_) {\n    current_layer_->SetWillChangeHint();\n  }\n}\n\nvoid PaintingContext::PaintChild(RenderObject* child,\n                                 const FloatPoint& offset) {\n  if (!child->Visible()) {\n    return;\n  }\n  // If the child has its own composited layer, the child will be composited\n  // into the layer subtree associated with this painting context. Otherwise,\n  // the child will be painted into the current PendingPictureLayer for this\n  // context\n  if (!child->IsRepaintBoundary()) {\n    child->PaintWithContext(*this, offset);\n  } else {\n    // In actual scenarios, the child may overlap with its own brother or\n    // father's brother, or there may be other more complex situations.\n    // Similar to how flutter handles overlay case, use different\n    // |PendingPictureLayer| to record the remaining nodes.\n    StopRecordingIfNeeded();\n    CompositeChild(child, offset);\n  }\n}\n\nvoid PaintingContext::CompositeChild(RenderObject* child,\n                                     const FloatPoint& offset) {\n  FML_DCHECK(child->IsRepaintBoundary());\n  if (child->NeedsPaint() || child->NeedsEffect()) {\n    RepaintCompositedChild(child, graphics_context_.GetUnrefQueue(), this);\n  }\n  FML_DCHECK(child->GetLayer());\n  // Overlay will to add root layer to keep absolute position in page rather\n  // add to parent's layer.\n  if (!child->IsOverlay()) {\n    AppendLayer(child->GetLayer());\n  }\n\n  PendingOffsetLayer* layer =\n      static_cast<PendingOffsetLayer*>(child->GetLayer());\n  if (layer) {\n    if (!child->IsExternalView()) {\n      layer->SetOffset(offset);\n    } else {\n      layer->SetOffset({});\n    }\n  }\n}\n\nvoid PaintingContext::AppendLayer(PendingLayer* layer) {\n  layer->Remove();\n  container_layer_->AppendChild(layer);\n  if (strategy_ != CacheStrategy::None) {\n    // When append a layer with CacheStrategy::None, do not recursively set in\n    // case of descendant layers may using different strategy.\n    layer->SetCacheStrategyRecursively(strategy_);\n  }\n}\n\nvoid PaintingContext::AddLayer(PendingLayer* layer) { AppendLayer(layer); }\n\nvoid PaintingContext::PushLayer(PendingContainerLayer* child_layer,\n                                const PaintingContextCallback& painter,\n                                const FloatPoint& offset) {\n  // If a layer is being reused, it may already contain children. We remove\n  // them so that `painter` can add children that are relevant for this frame.\n  if (child_layer->HasChildren()) {\n    child_layer->RemoveAllChildren();\n  }\n  AppendLayer(child_layer);\n  PaintingContext child_context(child_layer, render_object(),\n                                graphics_context_.GetUnrefQueue());\n  child_context.SetCacheStrategy(strategy_);\n  painter(child_context, offset);\n}\n\nvoid PaintingContext::PushOpacity(int alpha, const FloatPoint& offset,\n                                  const PaintingContextCallback& painter) {\n  PendingEffectLayer* opacity_layer =\n      render_object()->GetOrCreateEffectLayer(EffectType::kOpacity);\n  opacity_layer->SetOpacity(alpha);\n  opacity_layer->SetOffset(offset);\n  PushLayer(opacity_layer, painter, FloatPoint());\n}\n\nvoid PaintingContext::PushColorFilter(std::shared_ptr<ColorFilter> color_filter,\n                                      const FloatPoint& offset,\n                                      const PaintingContextCallback& painter) {\n  PendingEffectLayer* effect_layer =\n      render_object()->GetOrCreateEffectLayer(EffectType::kColorFilter);\n  effect_layer->SetColorFilter(color_filter);\n  PushLayer(effect_layer, painter, offset);\n}\n\nvoid PaintingContext::PushImageFilter(std::shared_ptr<ImageFilter> image_filter,\n                                      const FloatPoint& offset,\n                                      const PaintingContextCallback& painter) {\n  PendingEffectLayer* effect_layer =\n      render_object()->GetOrCreateEffectLayer(EffectType::kImageFilter);\n  effect_layer->SetImageFilter(image_filter);\n  PushLayer(effect_layer, painter, offset);\n}\n\nvoid PaintingContext::PushBackdropFilter(\n    std::shared_ptr<ImageFilter> backdrop_filter, const FloatPoint& offset,\n    const PaintingContextCallback& painter) {\n  PendingEffectLayer* effect_layer =\n      render_object()->GetOrCreateEffectLayer(EffectType::kBackdropFilter);\n  effect_layer->SetBackdropFilter(backdrop_filter);\n\n  PushLayer(effect_layer, painter, offset);\n}\n\nvoid PaintingContext::PushTransform(const TransformOperations& operations,\n                                    const FloatPoint& origin,\n                                    const float perspective,\n                                    const FloatPoint& offset,\n                                    const PaintingContextCallback& painter) {\n  TransformOperations tmp = operations;\n  tmp.SetPerspective(perspective);\n  PendingTransformLayer* layer = new PendingTransformLayer(tmp, origin);\n  AppendLayer(layer);\n  PaintingContext child_context(layer, render_object(),\n                                graphics_context_.GetUnrefQueue());\n  child_context.SetCacheStrategy(strategy_);\n  painter(child_context, offset);\n}\n\nvoid PaintingContext::PushClipRect(const FloatRect& clip_rect,\n                                   const FloatPoint& offset,\n                                   const PaintingContextCallback& painter) {\n  PendingEffectLayer* effect_layer = new PendingEffectLayer();\n  effect_layer->SetClipRect(clip_rect);\n  PushLayer(effect_layer, painter, offset);\n}\n\nvoid PaintingContext::PushClipRRect(const FloatRoundedRect& clip_rrect,\n                                    const FloatPoint& offset,\n                                    const PaintingContextCallback& painter) {\n  PendingEffectLayer* effect_layer = new PendingEffectLayer();\n  effect_layer->SetClipRRect(clip_rrect);\n  PushLayer(effect_layer, painter, offset);\n}\n\nvoid PaintingContext::PushClipPath(const GrPath& clip_path,\n                                   const FloatPoint& offset,\n                                   const PaintingContextCallback& painter) {\n  PendingEffectLayer* effect_layer = new PendingEffectLayer();\n  effect_layer->SetClipPath(clip_path);\n  PushLayer(effect_layer, painter, offset);\n}\n\nvoid PaintingContext::PushShaderMask(std::shared_ptr<ColorSource> color_source,\n                                     const FloatRect& mask_rect,\n                                     BlendMode blend_mode,\n                                     const FloatPoint& offset,\n                                     const PaintingContextCallback& painter) {\n  PendingEffectLayer* effect_layer = new PendingEffectLayer();\n  effect_layer->SetShaderMask(std::move(color_source), mask_rect, blend_mode);\n  PushLayer(effect_layer, painter, offset);\n}\n\nvoid PaintingContext::PushContainer(const FloatPoint& offset,\n                                    const PaintingContextCallback& painter) {\n  PendingContainerLayer* container_layer = new PendingContainerLayer();\n  PushLayer(container_layer, painter, offset);\n}\n\nvoid PaintingContext::AddDrawableImage(int dx, int dy, int width, int height,\n                                       int64_t image_id,\n                                       clay::DrawableImage::FitMode fit_mode) {\n  StopRecordingIfNeeded();\n  PendingDrawableImageLayer* image_layer =\n      new PendingDrawableImageLayer(dx, dy, width, height, image_id, fit_mode);\n  AppendLayer(image_layer);\n}\n\nvoid PaintingContext::AddPlatformView(int dx, int dy, int width, int height,\n                                      int64_t view_id) {\n  StopRecordingIfNeeded();\n  PendingPlatformViewLayer* platform_view_layer =\n      new PendingPlatformViewLayer(dx, dy, width, height, view_id);\n  AppendLayer(platform_view_layer);\n}\n\nvoid PaintingContext::AddPunchHole(const Rect& punch_hole_rect) {\n  StopRecordingIfNeeded();\n  PendingPunchHoleLayer* punch_hole_layer =\n      new PendingPunchHoleLayer(punch_hole_rect);\n  AppendLayer(punch_hole_layer);\n}\n\nvoid PaintingContext::PushExternalContainer(\n    const ElementId& element_id, const PaintingContextCallback& painter,\n    const skity::Vec2& size) {\n  StopRecordingIfNeeded();\n  PendingExternalViewLayer* external_view_layer =\n      new PendingExternalViewLayer(element_id, size);\n  PushLayer(external_view_layer, painter, {});\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/painting_context.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_PAINTING_CONTEXT_H_\n#define CLAY_UI_PAINTER_PAINTING_CONTEXT_H_\n\n#include <memory>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n#include \"clay/gfx/geometry/path.h\"\n#include \"clay/gfx/geometry/rect.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/ui/compositing/pending_layer.h\"\n#include \"clay/ui/compositing/pending_picture_layer.h\"\n\nnamespace clay {\n\nclass ImageResource;\nclass PendingContainerLayer;\nclass PendingLayer;\nclass PendingPictureLayer;\nclass RenderObject;\n\nclass PaintingContext;\ntypedef std::function<void(PaintingContext&, const FloatPoint&)>\n    PaintingContextCallback;\n\n// A place to paint and build a composited layer.\nclass PaintingContext {\n public:\n  PaintingContext(PendingContainerLayer* layer, RenderObject* object,\n                  fml::RefPtr<GPUUnrefQueue> unref_queue);\n  ~PaintingContext();\n\n  static void RepaintCompositedChild(RenderObject* object,\n                                     fml::RefPtr<GPUUnrefQueue> unref_queue,\n                                     PaintingContext* parent_context = nullptr);\n\n  void SetIsComplexHint();\n  void SetWillChangeHint();\n\n  // Paint a child [RenderObject].\n  void PaintChild(RenderObject* child, const FloatPoint& offset);\n\n  bool IsRecording() const;\n  GraphicsContext* GetGraphicsContext();\n  PendingLayer* ContentLayer() const { return current_layer_; }\n  PendingContainerLayer* GetContainerLayer() const { return container_layer_; }\n\n  void StopRecordingIfNeeded();\n\n  RenderObject* render_object() { return render_object_; }\n\n  // Adds a layer to the recording requiring that the recording is already\n  // stopped.\n  void AppendLayer(PendingLayer* layer);\n  // Adds a composited leaf layer to the recording.\n  void AddLayer(PendingLayer* layer);\n  // Appends the given layer to the recording, and calls the `painter` callback\n  // with that layer.\n  void PushLayer(PendingContainerLayer* child_layer,\n                 const PaintingContextCallback& painter,\n                 const FloatPoint& offset);\n\n  void PushOpacity(int alpha, const FloatPoint& offset,\n                   const PaintingContextCallback& painter);\n  void PushColorFilter(std::shared_ptr<ColorFilter> color_filter,\n                       const FloatPoint& offset,\n                       const PaintingContextCallback& painter);\n  void PushImageFilter(std::shared_ptr<ImageFilter> image_filter,\n                       const FloatPoint& offset,\n                       const PaintingContextCallback& painter);\n  void PushBackdropFilter(std::shared_ptr<ImageFilter> backdrop_filter,\n                          const FloatPoint& offset,\n                          const PaintingContextCallback& painter);\n  void PushTransform(const TransformOperations& operations,\n                     const FloatPoint& origin, const float perspective,\n                     const FloatPoint& offset,\n                     const PaintingContextCallback& painter);\n  // Clip further painting using a rectangle.\n  void PushClipRect(const FloatRect& clip_rect, const FloatPoint& offset,\n                    const PaintingContextCallback& painter);\n  // Clip further painting using a rounded rectangle.\n  void PushClipRRect(const FloatRoundedRect& clip_rrect,\n                     const FloatPoint& offset,\n                     const PaintingContextCallback& painter);\n  void PushClipPath(const GrPath& clip_rrect, const FloatPoint& offset,\n                    const PaintingContextCallback& painter);\n  void PushShaderMask(std::shared_ptr<ColorSource> color_source,\n                      const FloatRect& mask_rect, BlendMode blend_mode,\n                      const FloatPoint& offset,\n                      const PaintingContextCallback& painter);\n\n  // Container layer will contain picture layer and other layer subtree.\n  void PushContainer(const FloatPoint& offset,\n                     const PaintingContextCallback& painter);\n\n  void PushExternalContainer(const ElementId& element_id,\n                             const PaintingContextCallback& painter,\n                             const skity::Vec2& size);\n\n  void AddDrawableImage(int dx, int dy, int width, int height, int64_t image_id,\n                        clay::DrawableImage::FitMode fit_mode);\n  void AddPlatformView(int dx, int dy, int width, int height, int64_t view_id);\n  void AddPunchHole(const Rect& punch_hole_rect);\n  void SetCacheStrategy(CacheStrategy strategy) { strategy_ = strategy; }\n  CacheStrategy GetCacheStrategy() const { return strategy_; }\n\n private:\n  void StartRecording();\n  void CompositeChild(RenderObject* child, const FloatPoint& offset);\n\n  // Recording state.\n  PendingPictureLayer* current_layer_ = nullptr;\n  GraphicsContext graphics_context_;\n\n  RenderObject* render_object_ = nullptr;\n  PendingContainerLayer* container_layer_ = nullptr;\n  // The strategy used for recorded layer and appended layers. This will inherit\n  // from parent context or get from render object.\n  CacheStrategy strategy_ = CacheStrategy::None;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_PAINTING_CONTEXT_H_\n"
  },
  {
    "path": "clay/ui/painter/painting_context_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/compositing/pending_container_layer.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/render_container.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkCanvas.h\"\n\nnamespace clay {\n\nnamespace {\n\nfml::RefPtr<fml::TaskRunner> GetCurrentTaskRunner() {\n  fml::MessageLoop::EnsureInitializedForCurrentThread();\n  return fml::MessageLoop::GetCurrent().GetTaskRunner();\n}\n\n}  // namespace\n\nnamespace testing {\n\nTEST(PaintingContextTest, Simple) {\n  auto unref_queue = fml::MakeRefCounted<GPUUnrefQueue>(GetCurrentTaskRunner());\n  auto render_object = std::make_unique<RenderContainer>();\n  render_object->SetLeft(0);\n  render_object->SetTop(0);\n  render_object->SetWidth(100);\n  render_object->SetHeight(100);\n  auto root_layer = std::make_unique<PendingContainerLayer>();\n  EXPECT_FALSE(root_layer->HasChildren());\n\n  PaintingContext painting_context(root_layer.get(), render_object.get(),\n                                   unref_queue);\n\n  EXPECT_FALSE(painting_context.IsRecording());\n\n  // Start recording.\n  painting_context.GetGraphicsContext();\n  EXPECT_TRUE(painting_context.IsRecording());\n  EXPECT_TRUE(root_layer->HasChildren());\n\n  painting_context.StopRecordingIfNeeded();\n  EXPECT_FALSE(painting_context.IsRecording());\n  root_layer = nullptr;\n  unref_queue->Drain();\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/text_painter.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/painter/text_painter.h\"\n\n#include <algorithm>\n#include <map>\n#include <memory>\n#include <optional>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/paint.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/ui/common/editing_misc.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/painter/gradient_factory.h\"\n#include \"skity/geometry/rect.hpp\"\n#if defined(CLAY_ENABLE_SKSHAPER)\n#include \"clay/third_party/txt/src/skia/paragraph_builder_skia.h\"\n#include \"clay/third_party/txt/src/skia/paragraph_skia.h\"\n#include \"third_party/skia/modules/skparagraph/src/ParagraphImpl.h\"  // nogncheck\n#elif defined(CLAY_ENABLE_TTTEXT)\n#include \"clay/third_party/txt/src/tttext/paragraph_tt_text.h\"\n#endif\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n\nnamespace clay {\n\nTextPainter::TextPainter() = default;\n\nTextPainter::~TextPainter() = default;\n\nvoid TextPainter::SetWidth(float width) {\n  if (width != width_) {\n    width_ = width;\n    is_gradient_dirty_ = true;\n  }\n}\n\nvoid TextPainter::SetHeight(float height) {\n  if (height != height_) {\n    height_ = height;\n    is_gradient_dirty_ = true;\n  }\n}\n\nvoid TextPainter::SetParagraph(txt::Paragraph* paragraph) {\n  paragraph_ = paragraph;\n  is_gradient_dirty_ = true;\n}\n\nvoid TextPainter::SetGradient(const std::optional<Gradient>& gradient) {\n  is_gradient_dirty_ = false;\n  if (!paragraph_) {\n    FML_DCHECK(false) << \"paragraph is null\";\n    return;\n  }\n  if (gradient) {\n    auto gradient_shader = GradientFactory::CreateShader(\n        gradient.value(),\n        FloatRect(0, 0, GetLongestLine(), paragraph_->GetHeight()));\n    class Paint paint;\n    if (gradient_shader) {\n      paint.setColorSource(gradient_shader);\n    }\n#if CLAY_ENABLE_SKSHAPER\n    static_cast<txt::ParagraphSkia*>(paragraph_)\n        ->UpdateForegroundPaint(GetTextSize(), paint.gr_object());\n#elif CLAY_ENABLE_TTTEXT\n    static_cast<txt::ParagraphTTText*>(paragraph_)\n        ->UpdateForegroundPaint(GetTextSize(), paint.gr_object());\n#endif\n  }\n}\n\nvoid TextPainter::SetGradientShaderMap(\n    std::map<int, std::shared_ptr<ColorSource>>&& gradient_shader_map,\n    std::map<int, std::pair<size_t, size_t>>&& range_map) {\n  gradient_shader_map_ = std::move(gradient_shader_map);\n  gradient_shader_range_map_ = std::move(range_map);\n  is_gradient_dirty_ = true;\n}\n\nvoid TextPainter::SetTextStrokeMap(\n    std::unordered_map<int, TextStroke>&& text_stroke_map) {\n  text_stroke_map_ = std::move(text_stroke_map);\n}\n\nstd::vector<TextBox> TextPainter::GetRectsForPlaceholders() {\n  if (!paragraph_) {\n    return {};\n  }\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph_->GetRectsForPlaceholders();\n  std::vector<TextBox> res;\n  res.reserve(boxes.size());\n  for (const txt::Paragraph::TextBox& box : boxes) {\n    res.emplace_back(FloatRect(box.rect));\n  }\n  return res;\n}\n\nstd::vector<TextBox> TextPainter::GetRectsForRange(int start, int end,\n                                                   RectHeightStyle height_style,\n                                                   RectWidthStyle width_style) {\n  if (!paragraph_) {\n    return {};\n  }\n  std::vector<txt::Paragraph::TextBox> boxes =\n      paragraph_->GetRectsForRange(start, end, height_style, width_style);\n  std::vector<TextBox> res;\n  res.reserve(boxes.size());\n  for (const txt::Paragraph::TextBox& box : boxes) {\n    res.emplace_back(FloatRect(box.rect));\n  }\n  return res;\n}\n\nTextPosWithAffinity TextPainter::GetGlyphPositionAtCoordinate(float x,\n                                                              float y) {\n  if (!paragraph_) {\n    return {0, Affinity::kDownstream};\n  }\n  auto result = paragraph_->GetGlyphPositionAtCoordinate(x, y);\n  return {result.position, result.affinity == txt::Paragraph::UPSTREAM\n                               ? Affinity::kUpstream\n                               : Affinity::kDownstream};\n}\n\ndouble TextPainter::GetLineHeightForPosition(size_t position, Affinity affinity,\n                                             size_t* line_number) {\n  if (!paragraph_) {\n    return 0.0;\n  }\n  for (const auto& metrics : paragraph_->GetLineMetrics()) {\n    if (position > metrics.end_index) {\n      continue;\n    }\n    // Assume metrics are incremental.\n    if (position >= metrics.start_index) {\n      if (line_number) {\n        // Taking into account the position of the cursor when wrapping\n        if (position == metrics.end_index &&\n            metrics.end_including_newline == metrics.end_index &&\n            affinity == Affinity::kDownstream) {\n          *line_number = metrics.line_number + 1;\n        } else {\n          *line_number = metrics.line_number;\n        }\n      }\n      return metrics.height;\n    }\n    break;\n  }\n  return 0.0;\n}\n\ndouble TextPainter::GetUpDownLineHeightForPosition(\n    TextPosWithAffinity pos_with_affinity, VerticalDirection direction) {\n  size_t current_line = 0;\n  if (!GetLineHeightForPosition(pos_with_affinity.first,\n                                pos_with_affinity.second, &current_line)) {\n    // Not valid position.\n    return 0.0;\n  }\n\n  const auto& metrics = paragraph_->GetLineMetrics();\n  auto target_line = direction == VerticalDirection::kUp\n                         ? std::max(static_cast<size_t>(0), current_line - 1)\n                         : std::min(current_line + 1, metrics.size() - 1);\n  if (target_line == current_line) {\n    // current_line is the first or last line.\n    return 0.0;\n  }\n\n  auto find_iter = std::find_if(\n      metrics.begin(), metrics.end(), [target_line](const auto& line_metrics) {\n        // |line_number| starts from 0.\n        return target_line == line_metrics.line_number;\n      });\n  return find_iter != metrics.end() ? find_iter->height : 0.0;\n}\n\nTextRange TextPainter::GetLineRangeForPosition(size_t position) {\n  if (!paragraph_) {\n    return {};\n  }\n  for (const auto& metrics : paragraph_->GetLineMetrics()) {\n    if (position > metrics.end_index) {\n      continue;\n    }\n    // Assume metrics are incremental.\n    if (position >= metrics.start_index) {\n      return TextRange{metrics.start_index, metrics.end_index};\n    }\n    break;\n  }\n  return {};\n}\n\nvoid TextPainter::Paint(GraphicsContext* context, double x_offset,\n                        double y_offset) {\n  UpdateGradientIfNeeded(context);\n  if (paragraph_) {\n#ifndef ENABLE_SKITY\n    paragraph_->Paint(context->GetGrCanvas(), x_offset, y_offset);\n\n    if (text_stroke_map_.size() > 0) {\n      UpdateTextStrokePaint();\n      paragraph_->Paint(context->GetGrCanvas(), x_offset, y_offset);\n      UpdateTextFillPaint();\n      is_gradient_dirty_ = true;\n    }\n#else\n    txt::ParagraphTTText* paragraph_tt_text =\n        static_cast<txt::ParagraphTTText*>(paragraph_);\n    paragraph_tt_text->Paint(context->Canvas(), x_offset, y_offset);\n#endif  // ENABLE_SKITY\n  }\n}\n\nsize_t TextPainter::GetTextSize() {\n  if (paragraph_ == nullptr) {\n    return 0;\n  }\n\n#if defined(CLAY_ENABLE_SKSHAPER)\n  auto* impl = static_cast<skia::textlayout::ParagraphImpl*>(\n      (static_cast<txt::ParagraphSkia*>(paragraph_)->paragraph_).get());\n  return impl->text().size();\n#elif defined(CLAY_ENABLE_TTTEXT)\n  auto* impl = static_cast<txt::ParagraphTTText*>(paragraph_);\n  return impl->GetTextSize();\n#endif\n  return 0;\n}\n\nvoid TextPainter::UpdateGradientIfNeeded(GraphicsContext* context) {\n  if (!is_gradient_dirty_ || gradient_shader_map_.size() == 0) {\n    return;\n  }\n  is_gradient_dirty_ = false;\n\n  if (paragraph_ == nullptr) {\n    return;\n  }\n\n  // enable_skity implies enable_tttext\n#ifndef ENABLE_SKITY\n  std::unordered_map<int, SkPaint> paint_map;\n  class Paint paint;\n  for (auto gradient_shader : gradient_shader_map_) {\n    if (gradient_shader.second) {\n      paint.setColorSource(gradient_shader.second);\n    }\n    paint_map[gradient_shader.first] = paint.gr_object();\n  }\n\n#if CLAY_ENABLE_SKSHAPER\n  static_cast<txt::ParagraphSkia*>(paragraph_)\n      ->UpdateForegroundPaint(paint_map);\n#elif CLAY_ENABLE_TTTEXT\n  // Currently tttext dont support inline-text set gradient attribute\n  static_cast<txt::ParagraphTTText*>(paragraph_)\n      ->UpdateForegroundPaint(GetTextSize(), paint.gr_object());\n#endif\n#else\n  std::unordered_map<int, skity::Paint> paint_map;\n  class Paint paint;\n  for (auto gradient_shader : gradient_shader_map_) {\n    if (gradient_shader.second) {\n      paint.setColorSource(gradient_shader.second);\n    }\n    paint_map[gradient_shader.first] = paint.gr_object();\n\n    if (gradient_shader_range_map_.find(gradient_shader.first) !=\n        gradient_shader_range_map_.end()) {\n      auto& [start, end] = gradient_shader_range_map_.at(gradient_shader.first);\n      static_cast<txt::ParagraphTTText*>(paragraph_)\n          ->UpdateForegroundPaint(start, end, paint.gr_object());\n    }\n  }\n#endif  // ENABLE_SKITY\n}\n\nvoid TextPainter::UpdateTextFillPaint() {\n  std::unordered_map<int, GrPaint> paint_map;\n  class Paint paint;\n  for (auto text_stroke : text_stroke_map_) {\n    paint.setDrawStyle(DrawStyle::kFill);\n    paint.setColor(Color(text_stroke.second.fill_color));\n    paint_map[text_stroke.first] = paint.gr_object();\n  }\n\n#ifndef ENABLE_SKITY\n#if CLAY_ENABLE_SKSHAPER\n  static_cast<txt::ParagraphSkia*>(paragraph_)\n      ->UpdateForegroundPaint(paint_map);\n#elif CLAY_ENABLE_TTTEXT\n  // Currently tttext dont support inline-text set text stroke attribute\n  static_cast<txt::ParagraphTTText*>(paragraph_)\n      ->UpdateForegroundPaint(GetTextSize(), paint.gr_object());\n#endif\n#else\n#if CLAY_ENABLE_TTTEXT\n  // Currently tttext dont support inline-text set text stroke attribute\n  static_cast<txt::ParagraphTTText*>(paragraph_)\n      ->UpdateForegroundPaint(GetTextSize(), paint.gr_object());\n#endif\n#endif  // ENABLE_SKITY\n}\n\nvoid TextPainter::UpdateTextStrokePaint() {\n  std::unordered_map<int, GrPaint> paint_map;\n  class Paint paint;\n  for (auto text_stroke : text_stroke_map_) {\n    paint.setDrawStyle(DrawStyle::kStroke);\n    paint.setStrokeWidth(text_stroke.second.width);\n    paint.setColor(Color(text_stroke.second.stroke_color));\n    paint_map[text_stroke.first] = paint.gr_object();\n  }\n\n#ifndef ENABLE_SKITY\n#if CLAY_ENABLE_SKSHAPER\n  static_cast<txt::ParagraphSkia*>(paragraph_)\n      ->UpdateForegroundPaint(paint_map);\n#elif CLAY_ENABLE_TTTEXT\n  // Currently tttext dont support inline-text set text stroke attribute\n  static_cast<txt::ParagraphTTText*>(paragraph_)\n      ->UpdateForegroundPaint(GetTextSize(), paint.gr_object());\n#endif\n#else\n#if CLAY_ENABLE_TTTEXT\n  // Currently tttext dont support inline-text set text stroke attribute\n  static_cast<txt::ParagraphTTText*>(paragraph_)\n      ->UpdateForegroundPaint(GetTextSize(), paint.gr_object());\n#endif\n#endif  // ENABLE_SKITY\n}\n\nTextRange TextPainter::GetWordBoundary(size_t offset) {\n  if (!paragraph_) {\n    return TextRange();\n  }\n  auto range = paragraph_->GetWordBoundary(offset);\n  return TextRange(range.start, range.end);\n}\n\nstd::vector<FloatRect> TextPainter::GetTextLineRects(int start, int end) {\n  std::vector<FloatRect> line_rects;\n  if (start < end || !paragraph_) {\n    return line_rects;\n  }\n  auto line_metrics = paragraph_->GetLineMetrics();\n  for (auto metric : line_metrics) {\n    if (std::max(start, static_cast<int>(metric.start_index)) <\n        std::min(end, static_cast<int>(metric.end_index))) {\n      auto text_boxes = paragraph_->GetRectsForRange(\n          std::max(start, static_cast<int>(metric.start_index)),\n          std::min(end, static_cast<int>(metric.end_index)),\n          RectHeightStyle::kMax, RectWidthStyle::kMax);\n      if (text_boxes.empty()) {\n        return line_rects;\n      }\n      skity::Rect rect = text_boxes.front().rect;\n      for (auto& box : text_boxes) {\n        rect.Join(box.rect);\n      }\n      line_rects.emplace_back(rect);\n    }\n  }\n  return line_rects;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/painter/text_painter.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PAINTER_TEXT_PAINTER_H_\n#define CLAY_UI_PAINTER_TEXT_PAINTER_H_\n\n#include <map>\n#include <memory>\n#include <optional>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/third_party/txt/src/txt/placeholder_run.h\"\n#include \"clay/ui/common/editing_misc.h\"\n#include \"clay/ui/common/text_input_type_traits.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/painter/gradient.h\"\n\nnamespace clay {\n\nstruct TextBox {\n  FloatRect rect;\n  // TODO(Xietong): text direction\n\n  explicit TextBox(const FloatRect& _rect) : rect(_rect) {}\n  float GetLeft() { return rect.x(); }\n  float GetTop() { return rect.y(); }\n  float GetBottom() { return rect.y() + rect.height(); }\n  float GetRight() { return rect.x() + rect.width(); }\n};\n\nstruct TextStroke {\n  double width = 0;\n  Color stroke_color;\n  Color fill_color;\n};\n\nusing TextPosWithAffinity = std::pair<size_t, Affinity>;\n\nusing RectHeightStyle = txt::Paragraph::RectHeightStyle;\nusing RectWidthStyle = txt::Paragraph::RectWidthStyle;\n\nclass TextPainter {\n public:\n  TextPainter();\n  ~TextPainter();\n\n  void SetWidth(float width);\n  void SetHeight(float height);\n\n  void SetParagraph(txt::Paragraph* paragraph);\n\n  void SetGradient(const std::optional<Gradient>& gradient);\n  void SetGradientShaderMap(\n      std::map<int, std::shared_ptr<ColorSource>>&& gradient_shader_map,\n      std::map<int, std::pair<size_t, size_t>>&& range_map);\n\n  void SetTextStrokeMap(std::unordered_map<int, TextStroke>&& text_stroke_map);\n\n  std::vector<TextBox> GetRectsForPlaceholders();\n  std::vector<TextBox> GetRectsForRange(\n      int start, int end,\n      RectHeightStyle height_style = RectHeightStyle::kTight,\n      RectWidthStyle width_style = RectWidthStyle::kTight);\n  TextPosWithAffinity GetGlyphPositionAtCoordinate(float x, float y);\n  txt::Paragraph* GetParagraph() { return paragraph_; }\n\n  // Returns the line height for glyph position. If no line match, return zero.\n  double GetLineHeightForPosition(size_t position,\n                                  Affinity affinity = Affinity::kDownstream,\n                                  size_t* line_number = nullptr);\n\n  // Return the line height up or down the line of provided position.\n  // If no line match, return zero.\n  double GetUpDownLineHeightForPosition(TextPosWithAffinity pos_with_affinity,\n                                        VerticalDirection direction);\n\n  TextRange GetLineRangeForPosition(size_t position);\n\n  bool CanPaint() const { return paragraph_ != nullptr; }\n  void Paint(GraphicsContext* context, double x_offset = 0,\n             double y_offset = 0);\n\n  // Finds the first and last glyphs that define a word containing the glyph at\n  // index offset.\n  TextRange GetWordBoundary(size_t offset);\n\n  double GetLongestLine() { return paragraph_->GetLongestLine(); }\n\n  std::vector<FloatRect> GetTextLineRects(int start, int end);\n\n private:\n  size_t GetTextSize();\n  void UpdateGradientIfNeeded(GraphicsContext* context);\n  void UpdateTextFillPaint();\n  void UpdateTextStrokePaint();\n\n  float width_ = 0.f;\n  float height_ = 0.f;\n\n  txt::Paragraph* paragraph_ = nullptr;\n  bool is_gradient_dirty_ = true;\n  std::map<int, std::shared_ptr<ColorSource>> gradient_shader_map_;\n  std::map<int, std::pair<size_t, size_t>> gradient_shader_range_map_;\n  std::unordered_map<int, TextStroke> text_stroke_map_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PAINTER_TEXT_PAINTER_H_\n"
  },
  {
    "path": "clay/ui/platform/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\nimport(\"../../common/config.gni\")\n\nui_platform_sources = [\n  \"common/utils.h\",\n  \"cursor_types.cc\",\n  \"cursor_types.h\",\n  \"keyboard_bridge.cc\",\n  \"keyboard_bridge.h\",\n  \"keyboard_types.h\",\n  \"native_view_service.h\",\n]\n\nui_net_loader_sources = [\n  \"common/resource_loader_common_net.cc\",\n  \"common/resource_loader_common_net.h\",\n]\n\nui_platform_fallback_sources = [\n  \"common/native_view_service_common.cc\",\n  \"common/native_view_service_common.h\",\n  \"common/resource_loader_common.cc\",\n]\n\nif (is_android) {\n  if (enable_clay_lite) {\n    ui_platform_fallback_sources -= [\n      \"common/native_view_service_common.cc\",\n      \"common/native_view_service_common.h\",\n      \"common/resource_loader_common.cc\",\n    ]\n  } else {\n    ui_platform_fallback_sources += [\n      \"../../shell/common/vsync_waiter_fallback.cc\",\n      \"../../shell/common/vsync_waiter_fallback.h\",\n    ]\n  }\n}\nif ((is_win || is_mac || is_ios) && !enable_unittests) {\n  ui_platform_fallback_sources -= [\n    \"common/native_view_service_common.cc\",\n    \"common/native_view_service_common.h\",\n  ]\n}\n"
  },
  {
    "path": "clay/ui/platform/common/native_view_service_common.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/platform/common/native_view_service_common.h\"\n\n#include <memory>\n\nnamespace clay {\n\nstd::unique_ptr<NativeViewPlugin>\nNativeViewServiceCommon::CreateNativeViewPlugin(int id, NativeView* view_ptr) {\n  return nullptr;\n}\n\nstd::shared_ptr<NativeViewService> NativeViewService::Create() {\n  return std::make_shared<NativeViewServiceCommon>();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/platform/common/native_view_service_common.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PLATFORM_COMMON_NATIVE_VIEW_SERVICE_COMMON_H_\n#define CLAY_UI_PLATFORM_COMMON_NATIVE_VIEW_SERVICE_COMMON_H_\n\n#include <memory>\n\n#include \"clay/ui/platform/native_view_service.h\"\n\nnamespace clay {\n\nclass NativeViewServiceCommon final : public NativeViewService {\n public:\n  std::unique_ptr<NativeViewPlugin> CreateNativeViewPlugin(\n      int id, NativeView* view_ptr) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PLATFORM_COMMON_NATIVE_VIEW_SERVICE_COMMON_H_\n"
  },
  {
    "path": "clay/ui/platform/common/resource_loader_common.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/net/loader/resource_loader_platform.h\"\n\n#if defined(ENABLE_NET_LOADER)\n#include \"clay/ui/platform/common/resource_loader_common_net.h\"\n#endif  // ENABLE_NET_LOADER\n\nnamespace clay {\n\nstd::shared_ptr<ResourceLoader> CreatePlatformResourceLoader(\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    fml::RefPtr<fml::TaskRunner> task_runner,\n    std::shared_ptr<ServiceManager> service_manager) {\n#if defined(ENABLE_NET_LOADER)\n  return std::make_shared<ResourceLoaderCommon>(intercept,\n                                                std::move(task_runner));\n#endif\n  return nullptr;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/platform/common/resource_loader_common_net.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/platform/common/resource_loader_common_net.h\"\n\n#include <cstddef>\n#include <cstring>\n#include <memory>\n#include <utility>\n#if defined(OS_WIN)\n#include <Shlwapi.h>\n#endif\n\n#include \"clay/fml/mapping.h\"\n#include \"clay/fml/paths.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/net/net_loader_callback.h\"\n#include \"clay/net/url/url_helper.h\"\n#include \"clay/ui/common/isolate.h\"\n\nnamespace clay {\n\nResourceLoaderCommon::ResourceLoaderCommon(\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    fml::RefPtr<fml::TaskRunner> task_runner)\n    : resource_loader_intercept_(intercept),\n      ui_task_runner_(std::move(task_runner)) {}\n\nResourceLoaderCommon::~ResourceLoaderCommon() = default;\n\nvoid ResourceLoaderCommon::Load(\n    const std::string& src,\n    const std::function<void(const uint8_t*, size_t)>& callback,\n    const ResourceType resource_type, bool need_redirect) {\n  std::weak_ptr<ResourceLoaderCommon> weak_ref = shared_from_this();\n  Isolate::Instance().GetIOTaskRunner()->PostTask(\n      [weak_ref, src, callback, need_redirect, task_runner = ui_task_runner_,\n       intercept = resource_loader_intercept_]() {\n        auto self = weak_ref.lock();\n        if (!self) {\n          FML_LOG(ERROR) << \"load resource fail: ResourceLoaderCommon is null\";\n          if (callback) {\n            task_runner->PostTask([callback]() { callback(nullptr, 0); });\n          }\n          return;\n        }\n        std::string intercept_url = src;\n        if (need_redirect && intercept) {\n          intercept_url = intercept->ShouldInterceptUrl(src, false);\n        }\n        self->RealLoad(weak_ref, task_runner, intercept_url, callback);\n      });\n}\n\nvoid ResourceLoaderCommon::RealLoad(\n    std::weak_ptr<ResourceLoaderCommon> weak_ref,\n    fml::RefPtr<fml::TaskRunner> ui_task_runner, const std::string& src,\n    const std::function<void(const uint8_t*, size_t)>& callback) {\n  url::UriSchemeType scheme_type = url::ParseUriScheme(src);\n\n  // Since base64(data) is handled in data_image_loader, we will not handle it.\n  if (scheme_type == url::UriSchemeType::kNet) {\n    LoadOnNet(weak_ref, ui_task_runner, src, callback);\n  } else if (scheme_type == url::UriSchemeType::kLocalFile) {\n    LoadByFile(ui_task_runner, src, callback);\n  } else {\n    ui_task_runner->PostTask([callback]() { callback(nullptr, 0); });\n  }\n}\n\nvoid ResourceLoaderCommon::LoadOnNet(\n    std::weak_ptr<ResourceLoaderCommon> weak_ref,\n    fml::RefPtr<fml::TaskRunner> ui_task_runner, const std::string& src,\n    const std::function<void(const uint8_t*, size_t)>& callback) {\n  NetLoaderCallback loader_callback;\n  loader_callback.set_succeeded_func(\n      [weak_ref, callback, ui_task_runner](size_t request_seq,\n                                           RawResource&& raw_resource) {\n        auto self = weak_ref.lock();\n        if (!self) {\n          FML_LOG(ERROR) << \"load resource fail: ResourceLoaderCommon is null\";\n          if (callback) {\n            ui_task_runner->PostTask([callback]() { callback(nullptr, 0); });\n          }\n          return;\n        }\n        self->OnLoadFinished(request_seq);\n        ui_task_runner->PostTask(\n            [callback, resource = std::move(raw_resource)]() mutable {\n              if (callback) {\n                callback(resource.data.get(), resource.length);\n              }\n            });\n      });\n  loader_callback.set_failed_func(\n      [weak_ref, src, callback, ui_task_runner](size_t request_seq,\n                                                const std::string& reason) {\n        auto self = weak_ref.lock();\n        if (!self) {\n          FML_LOG(ERROR) << \"load resource fail: ResourceLoaderCommon is null\";\n          if (callback) {\n            ui_task_runner->PostTask([callback]() { callback(nullptr, 0); });\n          }\n          return;\n        }\n        self->OnLoadFinished(request_seq);\n        FML_LOG(WARNING) << \"fail to load \" << src << \" with reason \" << reason;\n        ui_task_runner->PostTask([callback] {\n          if (callback) {\n            callback(nullptr, 0);\n          }\n        });\n      });\n  size_t pending_seq =\n      NetLoaderManager::Instance().Request(src, std::move(loader_callback));\n  if (pending_seq != NetLoaderManager::kInvalidRequestSeq) {\n    std::scoped_lock lock(pending_requests_mutex_);\n    pending_requests_.insert(pending_seq);\n  }\n}\n\nvoid ResourceLoaderCommon::LoadByFile(\n    fml::RefPtr<fml::TaskRunner> ui_task_runner, const std::string& src,\n    const std::function<void(const uint8_t*, size_t)>& callback) {\n  ui_task_runner->PostTask([src, callback]() {\n    std::string file_path = fml::paths::AbsolutePath(fml::paths::FromURI(src));\n    auto mapping = fml::FileMapping::CreateReadOnly(file_path);\n    if (mapping && mapping->IsValid()) {\n      callback(mapping->GetMapping(), mapping->GetSize());\n    } else {\n      callback(nullptr, 0);\n    }\n  });\n}\n\nvoid ResourceLoaderCommon::OnLoadFinished(size_t request_seq) {\n  std::scoped_lock lock(pending_requests_mutex_);\n  pending_requests_.erase(request_seq);\n}\n\nRawResource ResourceLoaderCommon::LoadSync(const std::string& src,\n                                           const ResourceType resource_type,\n                                           bool need_redirect) {\n  std::string intercept_url = src;\n  if (need_redirect && resource_loader_intercept_) {\n    intercept_url = resource_loader_intercept_->ShouldInterceptUrl(src, false);\n  }\n\n  url::UriSchemeType scheme_type = url::ParseUriScheme(intercept_url);\n\n  // Since base64(data) is handled in data_image_loader, resource loader common\n  // will not handle it.\n  if (scheme_type == url::UriSchemeType::kNet) {\n    resource_ = NetLoaderManager::Instance().RequestSync(intercept_url);\n    return resource_;\n  } else if (scheme_type == url::UriSchemeType::kLocalFile) {\n    std::string file_path = fml::paths::FromURI(intercept_url);\n    auto mapping = fml::FileMapping::CreateReadOnly(file_path);\n\n    if (mapping && mapping->IsValid()) {\n      return RawResource::MakeWithCopy(mapping->GetMapping(),\n                                       mapping->GetSize());\n    }\n  }\n  return {0, nullptr};\n}\n\nvoid ResourceLoaderCommon::CancelAll() {\n  std::set<size_t> pending_requests;\n  {\n    std::scoped_lock lock(pending_requests_mutex_);\n    pending_requests.swap(pending_requests_);\n  }\n  for (auto pending_request : pending_requests) {\n    NetLoaderManager::Instance().CancelBySeq(pending_request);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/platform/common/resource_loader_common_net.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PLATFORM_COMMON_RESOURCE_LOADER_COMMON_NET_H_\n#define CLAY_UI_PLATFORM_COMMON_RESOURCE_LOADER_COMMON_NET_H_\n\n#include <memory>\n#include <mutex>\n#include <set>\n#include <string>\n\n#include \"clay/net/loader/resource_loader.h\"\n#include \"clay/net/net_loader_manager.h\"\n#include \"clay/net/resource_type.h\"\n\nnamespace clay {\n\nstruct RawResource;\nclass ResourceLoaderIntercept;\nclass ResourceLoaderCommon\n    : public ResourceLoader,\n      public std::enable_shared_from_this<ResourceLoaderCommon> {\n public:\n  explicit ResourceLoaderCommon(\n      std::shared_ptr<ResourceLoaderIntercept> intercept,\n      fml::RefPtr<fml::TaskRunner> task_runner);\n  ~ResourceLoaderCommon() override;\n\n  void Load(const std::string& src,\n            const std::function<void(const uint8_t*, size_t)>& callback,\n            const ResourceType resource_type = ResourceType::kOthers,\n            bool need_redirect = false) override;\n\n  RawResource LoadSync(const std::string& src,\n                       const ResourceType resource_type = ResourceType::kOthers,\n                       bool need_redirect = false) override;\n\n  void CancelAll() override;\n\n  bool NeedInterceptUrl() override { return true; }\n\n private:\n  void RealLoad(std::weak_ptr<ResourceLoaderCommon> weak_ref,\n                fml::RefPtr<fml::TaskRunner> ui_task_runner,\n                const std::string& src,\n                const std::function<void(const uint8_t*, size_t)>& callback);\n  void LoadOnNet(std::weak_ptr<ResourceLoaderCommon> weak_ref,\n                 fml::RefPtr<fml::TaskRunner> ui_task_runner,\n                 const std::string& src,\n                 const std::function<void(const uint8_t*, size_t)>& callback);\n  void LoadByFile(fml::RefPtr<fml::TaskRunner> ui_task_runner,\n                  const std::string& src,\n                  const std::function<void(const uint8_t*, size_t)>& callback);\n  void OnLoadFinished(size_t request_seq);\n\n  std::set<size_t> pending_requests_;\n  std::mutex pending_requests_mutex_;\n\n  RawResource resource_;\n\n  std::shared_ptr<ResourceLoaderIntercept> resource_loader_intercept_;\n  fml::RefPtr<fml::TaskRunner> ui_task_runner_;\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_PLATFORM_COMMON_RESOURCE_LOADER_COMMON_NET_H_\n"
  },
  {
    "path": "clay/ui/platform/common/utils.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PLATFORM_COMMON_UTILS_H_\n#define CLAY_UI_PLATFORM_COMMON_UTILS_H_\n\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/ui/common/isolate.h\"\n\nnamespace clay {\n\ninline void RunOnPlatformThread(lynx::base::closure task) {\n  fml::TaskRunner::RunNowOrPostTask(Isolate::Instance().GetPlatformTaskRunner(),\n                                    std::move(task));\n}\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PLATFORM_COMMON_UTILS_H_\n"
  },
  {
    "path": "clay/ui/platform/cursor_types.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/platform/cursor_types.h\"\n\n#include \"clay/net/url/url_helper.h\"\n\nnamespace clay {\n\n#define ADDTOMAP(name, type) \\\n  { name, type }\n\nconst std::map<std::string, CursorTypes> CursorTypeUtil::cursor_type_map_ = {\n    ADDTOMAP(\"auto\", CursorTypes::kAuto),\n    ADDTOMAP(\"default\", CursorTypes::kBasic),\n    ADDTOMAP(\"none\", CursorTypes::kNone),\n    ADDTOMAP(\"context-menu\", CursorTypes::kContextmenu),\n    ADDTOMAP(\"help\", CursorTypes::kHelp),\n    ADDTOMAP(\"pointer\", CursorTypes::kClick),\n    ADDTOMAP(\"progress\", CursorTypes::kProgress),\n    ADDTOMAP(\"wait\", CursorTypes::kWait),\n    ADDTOMAP(\"cell\", CursorTypes::kCell),\n    ADDTOMAP(\"crosshair\", CursorTypes::kPrecise),\n    ADDTOMAP(\"text\", CursorTypes::kText),\n    ADDTOMAP(\"vertical-text\", CursorTypes::kVerticaltext),\n    ADDTOMAP(\"alias\", CursorTypes::kAlias),\n    ADDTOMAP(\"copy\", CursorTypes::kSystemmousecursor),\n    ADDTOMAP(\"move\", CursorTypes::kMove),\n    ADDTOMAP(\"no-drop\", CursorTypes::kNodrop),\n    ADDTOMAP(\"not-allowed\", CursorTypes::kForbidden),\n    ADDTOMAP(\"grab\", CursorTypes::kGrab),\n    ADDTOMAP(\"grabbing\", CursorTypes::kGrabbing),\n    ADDTOMAP(\"all-scroll\", CursorTypes::kAllscroll),\n    ADDTOMAP(\"col-resize\", CursorTypes::kResizecolumn),\n    ADDTOMAP(\"row-resize\", CursorTypes::kResizerow),\n    ADDTOMAP(\"n-resize\", CursorTypes::kResizeup),\n    ADDTOMAP(\"e-resize\", CursorTypes::kResizeright),\n    ADDTOMAP(\"s-resize\", CursorTypes::kResizedown),\n    ADDTOMAP(\"w-resize\", CursorTypes::kResizeleft),\n    ADDTOMAP(\"ne-resize\", CursorTypes::kResizeupright),\n    ADDTOMAP(\"nw-resize\", CursorTypes::kResizeupleft),\n    ADDTOMAP(\"se-resize\", CursorTypes::kResizedownright),\n    ADDTOMAP(\"sw-resize\", CursorTypes::kResizedownleft),\n    ADDTOMAP(\"ew-resize\", CursorTypes::kResizeleftright),\n    ADDTOMAP(\"ns-resize\", CursorTypes::kResizeupdown),\n    ADDTOMAP(\"nesw-resize\", CursorTypes::kResizeuprightdownleft),\n    ADDTOMAP(\"nwse-resize\", CursorTypes::kResizeupleftdownright),\n    ADDTOMAP(\"zoom-in\", CursorTypes::kZoomin),\n    ADDTOMAP(\"zoom-out\", CursorTypes::kZoomout),\n};\n\nCursorTypes CursorTypeUtil::ParseCursorType(const std::string& str) {\n  auto iter = cursor_type_map_.find(str);\n  if (iter != cursor_type_map_.end()) {\n    return iter->second;\n  }\n\n  // network url or local file paths\n  url::UriSchemeType type = url::ParseUriScheme(str);\n  if (type == url::UriSchemeType::kNet) {\n    return CursorTypes::kNet;\n  }\n\n  return CursorTypes::kFile;\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/platform/cursor_types.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PLATFORM_CURSOR_TYPES_H_\n#define CLAY_UI_PLATFORM_CURSOR_TYPES_H_\n\n#include <map>\n#include <string>\n\nnamespace clay {\nenum class CursorTypes {\n  kUnknow = 1,\n\n  // cursor is a image which is downloaded from the internet.\n  kNet,\n\n  // cursor is a image which is obtained from the local file.\n  kFile,\n\n  ///  Determines the pointer style based on the current content\n  ///  e.g. use the text style when the context is text\n  kAuto,\n\n  /// Hide the cursor.\n  ///\n  /// Any cursor other than [none] or [MouseCursor.uncontrolled] unhides the\n  /// cursor.\n  kNone,\n\n  // STATUS\n\n  /// The platform-dependent basic cursor.\n  ///\n  /// Typically the shape of an arrow.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_DEFAULT, TYPE_ARROW\n  ///  * Web: default\n  ///  * Windows: IDC_ARROW\n  ///  * Linux: default\n  ///  * macOS: arrowCursor\n  kBasic,\n\n  /// A cursor that emphasizes an element being clickable, such as a\n  /// hyperlink.\n  ///\n  /// Typically the shape of a pointing hand.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_HAND\n  ///  * Web: pointer\n  ///  * Windows: IDC_HAND\n  ///  * Linux: pointer\n  ///  * macOS: pointingHandCursor\n  kClick,\n\n  /// A cursor indicating an operation that will not be carried out.\n  ///\n  /// Typically the shape of a circle with a diagonal line. May fall back to\n  /// [noDrop].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_NO_DROP\n  ///  * Web: not-allowed\n  ///  * Windows: IDC_NO\n  ///  * Linux: not-allowed\n  ///  * macOS: operationNotAllowedCursor\n  ///\n  /// See also:\n  ///\n  ///  * [noDrop], which indicates somewhere that the current item may not be\n  ///    dropped.\n  kForbidden,\n\n  /// A cursor indicating the status that the program is busy and therefore\n  /// can not be interacted with.\n  ///\n  /// Typically the shape of an hourglass or a watch.\n  ///\n  /// This cursor is not available as a system cursor on macOS. Although macOS\n  /// displays a \"spinning ball\" cursor when busy, it's handled by the OS and\n  /// not exposed for applications to choose.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_WAIT\n  ///  * Windows: IDC_WAIT\n  ///  * Web: wait\n  ///  * Linux: wait\n  ///\n  /// See also:\n  ///\n  ///  * [progress], which is similar to [wait] but the program can still be\n  ///    interacted with.\n  kWait,\n\n  /// A cursor indicating the status that the program is busy but can still be\n  /// interacted with.\n  ///\n  /// Typically the shape of an arrow with an hourglass or a watch at the\n  /// corner. Does *not* fall back to [wait] if unavailable.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Web: progress\n  ///  * Windows: IDC_APPSTARTING\n  ///  * Linux: progress\n  ///\n  /// See also:\n  ///\n  ///  * [wait], which is similar to [progress] but the program can not be\n  ///    interacted with.\n  kProgress,\n\n  /// A cursor indicating somewhere the user can trigger a context menu.\n  ///\n  /// Typically the shape of an arrow with a small menu at the corner.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_CONTEXT_MENU\n  ///  * Web: context-menu\n  ///  * Linux: context-menu\n  ///  * macOS: contextualMenuCursor\n  kContextmenu,\n\n  /// A cursor indicating help information.\n  ///\n  /// Typically the shape of a question mark, or an arrow therewith.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_HELP\n  ///  * Windows: IDC_HELP\n  ///  * Web: help\n  ///  * Linux: help\n  kHelp,\n\n  // SELECTION\n\n  /// A cursor indicating selectable text.\n  ///\n  /// Typically the shape of a capital I.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_TEXT\n  ///  * Web: text\n  ///  * Windows: IDC_IBEAM\n  ///  * Linux: text\n  ///  * macOS: IBeamCursor\n  kText,\n\n  /// A cursor indicating selectable vertical text.\n  ///\n  /// Typically the shape of a capital I rotated to be horizontal. May fall\n  /// back to [text].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_VERTICAL_TEXT\n  ///  * Web: vertical-text\n  ///  * Linux: vertical-text\n  ///  * macOS: IBeamCursorForVerticalLayout\n  kVerticaltext,\n\n  /// A cursor indicating selectable table cells.\n  ///\n  /// Typically the shape of a hollow plus sign.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_CELL\n  ///  * Web: cell\n  ///  * Linux: cell\n  kCell,\n\n  /// A cursor indicating precise selection, such as selecting a pixel in a\n  /// bitmap.\n  ///\n  /// Typically the shape of a crosshair.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_CROSSHAIR\n  ///  * Web: crosshair\n  ///  * Windows: IDC_CROSS\n  ///  * Linux: crosshair\n  ///  * macOS: crosshairCursor\n  kPrecise,\n\n  // DRAG-AND-DROP\n\n  /// A cursor indicating moving something.\n  ///\n  /// Typically the shape of four-way arrow. May fall back to [allScroll].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_ALL_SCROLL\n  ///  * Windows: IDC_SIZEALL\n  ///  * Web: move\n  ///  * Linux: move\n  kMove,\n\n  /// A cursor indicating something that can be dragged.\n  ///\n  /// Typically the shape of an open hand.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_GRAB\n  ///  * Web: grab\n  ///  * Linux: grab\n  ///  * macOS: openHandCursor\n  kGrab,\n\n  /// A cursor indicating something that is being dragged.\n  ///\n  /// Typically the shape of a closed hand.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_GRABBING\n  ///  * Web: grabbing\n  ///  * Linux: grabbing\n  ///  * macOS: closedHandCursor\n  kGrabbing,\n\n  /// A cursor indicating somewhere that the current item may not be dropped.\n  ///\n  /// Typically the shape of a hand with a [forbidden] sign at the corner. May\n  /// fall back to [forbidden].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_NO_DROP\n  ///  * Web: no-drop\n  ///  * Windows: IDC_NO\n  ///  * Linux: no-drop\n  ///  * macOS: operationNotAllowedCursor\n  ///\n  /// See also:\n  ///\n  ///  * [forbidden], which indicates an action that will not be carried out.\n  kNodrop,\n\n  /// A cursor indicating that the current operation will create an alias of,\n  /// or a shortcut of the item.\n  ///\n  /// Typically the shape of an arrow with a shortcut icon at the corner.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_ALIAS\n  ///  * Web: alias\n  ///  * Linux: alias\n  ///  * macOS: dragLinkCursor\n  kAlias,\n\n  /// A cursor indicating that the current operation will copy the item.\n  ///\n  /// Typically the shape of an arrow with a boxed plus sign at the corner.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_COPY\n  ///  * Web: copy\n  ///  * Linux: copy\n  ///  * macOS: dragCopyCursor\n  kSystemmousecursor,\n\n  /// A cursor indicating that the current operation will result in the\n  /// disappearance of the item.\n  ///\n  /// Typically the shape of an arrow with a cloud of smoke at the corner.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * macOS: disappearingItemCursor\n  kDisappearing,\n\n  // RESIZING AND SCROLLING\n\n  /// A cursor indicating scrolling in any direction.\n  ///\n  /// Typically the shape of a dot surrounded by 4 arrows.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_ALL_SCROLL\n  ///  * Windows: IDC_SIZEALL\n  ///  * Web: all-scroll\n  ///  * Linux: all-scroll\n  ///\n  /// See also:\n  ///\n  ///  * [move], which indicates moving in any direction.\n  kAllscroll,\n\n  /// A cursor indicating resizing an object bidirectionally from its left or\n  /// right edge.\n  ///\n  /// Typically the shape of a bidirectional arrow pointing left and right.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_HORIZONTAL_DOUBLE_ARROW\n  ///  * Web: ew-resize\n  ///  * Windows: IDC_SIZEWE\n  ///  * Linux: ew-resize\n  ///  * macOS: resizeLeftRightCursor\n  kResizeleftright,\n\n  /// A cursor indicating resizing an object bidirectionally from its top or\n  /// bottom edge.\n  ///\n  /// Typically the shape of a bidirectional arrow pointing up and down.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_VERTICAL_DOUBLE_ARROW\n  ///  * Web: ns-resize\n  ///  * Windows: IDC_SIZENS\n  ///  * Linux: ns-resize\n  ///  * macOS: resizeUpDownCursor\n  kResizeupdown,\n\n  /// A cursor indicating resizing an object bidirectionally from its top left\n  /// or bottom right corner.\n  ///\n  /// Typically the shape of a bidirectional arrow pointing upper left and\n  /// lower right.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW\n  ///  * Web: nwse-resize\n  ///  * Windows: IDC_SIZENWSE\n  ///  * Linux: nwse-resize\n  kResizeupleftdownright,\n\n  /// A cursor indicating resizing an object bidirectionally from its top\n  /// right or bottom left corner.\n  ///\n  /// Typically the shape of a bidirectional arrow pointing upper right and\n  /// lower left.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW\n  ///  * Windows: IDC_SIZENESW\n  ///  * Web: nesw-resize\n  ///  * Linux: nesw-resize\n  kResizeuprightdownleft,\n\n  /// A cursor indicating resizing an object from its top edge.\n  ///\n  /// Typically the shape of an arrow pointing up. May fallback to\n  /// [resizeUpDown].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_VERTICAL_DOUBLE_ARROW\n  ///  * Web: n-resize\n  ///  * Windows: IDC_SIZENS\n  ///  * Linux: n-resize\n  ///  * macOS: resizeUpCursor\n  kResizeup,\n\n  /// A cursor indicating resizing an object from its bottom edge.\n  ///\n  /// Typically the shape of an arrow pointing down. May fallback to\n  /// [resizeUpDown].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_VERTICAL_DOUBLE_ARROW\n  ///  * Web: s-resize\n  ///  * Windows: IDC_SIZENS\n  ///  * Linux: s-resize\n  ///  * macOS: resizeDownCursor\n  kResizedown,\n\n  /// A cursor indicating resizing an object from its left edge.\n  ///\n  /// Typically the shape of an arrow pointing left. May fallback to\n  /// [resizeLeftRight].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_HORIZONTAL_DOUBLE_ARROW\n  ///  * Web: w-resize\n  ///  * Windows: IDC_SIZEWE\n  ///  * Linux: w-resize\n  ///  * macOS: resizeLeftCursor\n  kResizeleft,\n\n  /// A cursor indicating resizing an object from its right edge.\n  ///\n  /// Typically the shape of an arrow pointing right. May fallback to\n  /// [resizeLeftRight].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_HORIZONTAL_DOUBLE_ARROW\n  ///  * Web: e-resize\n  ///  * Windows: IDC_SIZEWE\n  ///  * Linux: e-resize\n  ///  * macOS: resizeRightCursor\n  kResizeright,\n\n  /// A cursor indicating resizing an object from its top-left corner.\n  ///\n  /// Typically the shape of an arrow pointing upper left. May fallback to\n  /// [resizeUpLeftDownRight].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW\n  ///  * Web: nw-resize\n  ///  * Windows: IDC_SIZENWSE\n  ///  * Linux: nw-resize\n  kResizeupleft,\n\n  /// A cursor indicating resizing an object from its top-right corner.\n  ///\n  /// Typically the shape of an arrow pointing upper right. May fallback to\n  /// [resizeUpRightDownLeft].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW\n  ///  * Web: ne-resize\n  ///  * Windows: IDC_SIZENESW\n  ///  * Linux: ne-resize\n  kResizeupright,\n\n  /// A cursor indicating resizing an object from its bottom-left corner.\n  ///\n  /// Typically the shape of an arrow pointing lower left. May fallback to\n  /// [resizeUpRightDownLeft].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW\n  ///  * Web: sw-resize\n  ///  * Windows: IDC_SIZENESW\n  ///  * Linux: sw-resize\n  kResizedownleft,\n\n  /// A cursor indicating resizing an object from its bottom-right corner.\n  ///\n  /// Typically the shape of an arrow pointing lower right. May fallback to\n  /// [resizeUpLeftDownRight].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW\n  ///  * Web: se-resize\n  ///  * Windows: IDC_SIZENWSE\n  ///  * Linux: se-resize\n  kResizedownright,\n\n  /// A cursor indicating resizing a column, or an item horizontally.\n  ///\n  /// Typically the shape of arrows pointing left and right with a vertical\n  /// bar separating them. May fallback to [resizeLeftRight].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_HORIZONTAL_DOUBLE_ARROW\n  ///  * Web: col-resize\n  ///  * Windows: IDC_SIZEWE\n  ///  * Linux: col-resize\n  ///  * macOS: resizeLeftRightCursor\n  kResizecolumn,\n\n  /// A cursor indicating resizing a row, or an item vertically.\n  ///\n  /// Typically the shape of arrows pointing up and down with a horizontal bar\n  /// separating them. May fallback to [resizeUpDown].\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_VERTICAL_DOUBLE_ARROW\n  ///  * Web: row-resize\n  ///  * Windows: IDC_SIZENS\n  ///  * Linux: row-resize\n  ///  * macOS: resizeUpDownCursor\n  kResizerow,\n\n  // OTHER OPERATIONS\n\n  /// A cursor indicating zooming in.\n  ///\n  /// Typically a magnifying glass with a plus sign.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_ZOOM_IN\n  ///  * Web: zoom-in\n  ///  * Linux: zoom-in\n  kZoomin,\n\n  /// A cursor indicating zooming out.\n  ///\n  /// Typically a magnifying glass with a minus sign.\n  ///\n  /// Corresponds to:\n  ///\n  ///  * Android: TYPE_ZOOM_OUT\n  ///  * Web: zoom-out\n  ///  * Linux: zoom-out\n  kZoomout,\n};\n\nclass CursorTypeUtil {\n public:\n  static const std::map<std::string, CursorTypes> cursor_type_map_;\n  static CursorTypes ParseCursorType(const std::string& str);\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_PLATFORM_CURSOR_TYPES_H_\n"
  },
  {
    "path": "clay/ui/platform/keyboard_bridge.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/platform/keyboard_bridge.h\"\n\n#include <utility>\n\n#include \"build/build_config.h\"\n\nnamespace clay {\nKeyboardBridge::KeyboardBridge(KeyboardClient* client) : client_(client) {}\n\nvoid KeyboardBridge::RequestSoftKeyboard(KeyboardInputType type,\n                                         KeyboardAction action,\n                                         PageView* page_view) {\n#if OS_ANDROID\n  client_->PlatformShowSoftInput(static_cast<int>(type),\n                                 static_cast<int>(action));\n#endif\n}  // namespace clay\n\nvoid KeyboardBridge::HideSoftKeyboard() {\n#if OS_ANDROID\n  client_->PlatformHideSoftInput();\n#endif\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/platform/keyboard_bridge.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PLATFORM_KEYBOARD_BRIDGE_H_\n#define CLAY_UI_PLATFORM_KEYBOARD_BRIDGE_H_\n\n#include <memory>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/platform/keyboard_types.h\"\n\nnamespace clay {\n\nclass KeyboardClient {\n public:\n  virtual fml::RefPtr<fml::TaskRunner> GetTaskRunner() = 0;\n  virtual FloatSize KeyboardHostViewSize() = 0;\n  virtual void AddToKeyboardHostView(BaseView* keyboard_view) = 0;\n  virtual void RemoveFromKeyboardHostView(BaseView* keyboard_view) = 0;\n  virtual void OnKeyboardEvent(std::unique_ptr<KeyEvent> key_event) = 0;\n  virtual void OnDeleteSurroundingText(int before_length, int after_length) = 0;\n  virtual void OnPerformAction(KeyboardAction action) = 0;\n  virtual void OnFinishInput() = 0;\n\n  virtual void PlatformShowSoftInput(int type, int action) = 0;\n  virtual void PlatformHideSoftInput() = 0;\n};\n\nclass KeyboardBridge {\n public:\n  explicit KeyboardBridge(KeyboardClient* client);\n  ~KeyboardBridge() = default;\n\n  void RequestSoftKeyboard(KeyboardInputType type, KeyboardAction action,\n                           PageView* page_view);\n  void HideSoftKeyboard();\n\n  KeyboardClient* client_;\n};\n}  // namespace clay\n#endif  // CLAY_UI_PLATFORM_KEYBOARD_BRIDGE_H_\n"
  },
  {
    "path": "clay/ui/platform/keyboard_types.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PLATFORM_KEYBOARD_TYPES_H_\n#define CLAY_UI_PLATFORM_KEYBOARD_TYPES_H_\n\nnamespace clay {\n\nenum class KeyboardInputType {\n  kClassText = 0,\n  kClassNumber,\n  kClassPhone,\n  kUrl,\n  kEmailAddress,\n  kPassword,\n};\n\nenum class KeyboardAction {\n  kMultiLine = 0,\n  kGo,\n  kSearch,\n  kSend,\n  kNext,\n  kDone,\n  kPrevious,\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PLATFORM_KEYBOARD_TYPES_H_\n"
  },
  {
    "path": "clay/ui/platform/native_view_service.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_PLATFORM_NATIVE_VIEW_SERVICE_H_\n#define CLAY_UI_PLATFORM_NATIVE_VIEW_SERVICE_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/common/service/service.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/lynx_module/lynx_ui_method_registrar.h\"\n\nnamespace clay {\n\nclass NativeView;\nclass SharedImageSink;\n\nclass NativeViewPlugin : public ActorObject<Owner::kPlatform> {\n public:\n  explicit NativeViewPlugin(int id) : id_(id) {}\n  virtual ~NativeViewPlugin() = default;\n\n  virtual std::string GetName() const = 0;\n\n  // lifecycle should only called once\n  virtual bool OnCreate(std::string tag) = 0;\n  virtual void OnAttach() = 0;\n  virtual void OnDetach() = 0;\n  virtual void OnDestroy() = 0;\n  virtual void OnTouchEvent(const PointerEvent& point_event,\n                            const FloatPoint& transformed_postion) = 0;\n  virtual void OnFocusChanged(bool focused, bool is_leaf) {}\n  virtual void LayoutChanged(float left, float top, float width,\n                             float height) = 0;\n  virtual MeasureResult Measure(const MeasureConstraint& constraint) = 0;\n\n  virtual void UpdatePaddings(float padding_left, float padding_top,\n                              float padding_right, float padding_bottom) {}\n\n  // Implement this function if we need to apply custom attributes on platform\n  // view\n  virtual void UpdatePlatformAttributes(const clay::Value::Map& attrs,\n                                        const clay::Value::Array& events) {}\n  // Implement this function if we need to handle custom method on platform\n  // view\n  virtual void InvokePlatformMethod(const std::string& method,\n                                    const clay::Value::Map& args,\n                                    const LynxUIMethodCallback& callback) {}\n\n  virtual bool SupportHybridComposition() { return false; }\n\n  virtual bool SupportScrolling() { return false; }\n  virtual void ResignFirstResponder() {}\n\n  virtual fml::RefPtr<SharedImageSink> GetSharedImageSink() = 0;\n\n protected:\n  int id_;\n};\n\nclass NativeViewService : public Service<NativeViewService, Owner::kPlatform> {\n public:\n  static std::shared_ptr<NativeViewService> Create();\n\n  virtual std::unique_ptr<NativeViewPlugin> CreateNativeViewPlugin(\n      int id, NativeView* view_ptr) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_PLATFORM_NATIVE_VIEW_SERVICE_H_\n"
  },
  {
    "path": "clay/ui/public/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nui_public_sources = [\n  \"clay.cc\",\n  \"value.cc\",\n]\n"
  },
  {
    "path": "clay/ui/public/clay.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/public/clay.h\"\n\n#include <future>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"build/build_config.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/image/image_descriptor.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/shared_image/fence_sync.h\"\n#include \"clay/gfx/shared_image/shared_image_backing.h\"\n#include \"clay/gfx/shared_image/shared_image_sink.h\"\n#include \"clay/gfx/shared_image/shared_image_sink_accessor.h\"\n#include \"clay/net/loader/data_image_loader.h\"\n#include \"clay/ui/common/isolate.h\"\n\nnamespace clay {\n\nnamespace {\n\nSharedImageBacking::BackingType ClaySharedImageBackingTypeToBackingType(\n    ClaySharedImageBackingType type) {\n  switch (type) {\n    case kClaySharedImageBackingTypeIOSurface:\n      return SharedImageBacking::BackingType::kIOSurface;\n    case kClaySharedImageBackingTypeCVPixelBuffer:\n      return SharedImageBacking::BackingType::kCVPixelBuffer;\n    case kClaySharedImageBackingTypeD3DTexture:\n      return SharedImageBacking::BackingType::kD3DTexture;\n    case kClaySharedImageBackingTypeNativeImage:\n      return SharedImageBacking::BackingType::kNativeImage;\n    case kClaySharedImageBackingTypeShmImage:\n      return SharedImageBacking::BackingType::kShmImage;\n  }\n  FML_UNREACHABLE();\n}\n\nSharedImageBacking::PixelFormat ClaySharedImageBackingPixelFormatToPixelFormat(\n    ClaySharedImageBackingPixelFormat type) {\n  switch (type) {\n    case kClaySharedImageBackingPixelFormatNative8888:\n      return SharedImageBacking::PixelFormat::kNative8888;\n    case kClaySharedImageBackingPixelFormatRGBA8:\n      return SharedImageBacking::PixelFormat::kRGBA8888;\n  }\n  FML_UNREACHABLE();\n}\n\nclass ExternalFenceSync final : public FenceSync {\n public:\n  ExternalFenceSync(ClayVoidCallback wait_callback,\n                    ClayVoidCallback delete_callback, void* user_data)\n      : wait_callback_(wait_callback),\n        delete_callback_(delete_callback),\n        user_data_(user_data) {}\n\n  ~ExternalFenceSync() override {\n    if (delete_callback_) {\n      delete_callback_(user_data_);\n    }\n  }\n\n  bool ClientWait() override {\n    if (wait_callback_) {\n      wait_callback_(user_data_);\n      return true;\n    }\n    return false;\n  }\n\n  Type GetType() const override {\n    return clay::FenceSync::Type::kClientWaitOnly;\n  }\n\n private:\n  ClayVoidCallback wait_callback_;\n  ClayVoidCallback delete_callback_;\n  void* user_data_;\n};\n\nGrDataPtr GetImagePixels(fml::RefPtr<clay::GraphicsImage> image) {\n#ifndef ENABLE_SKITY\n  SkPixmap pixmap;\n  if (!image || !image->peekPixels(&pixmap)) {\n    FML_LOG(ERROR) << \"Could not peekPixels from image.\";\n    return nullptr;\n  }\n\n  if (pixmap.colorType() == kRGBA_8888_SkColorType &&\n      pixmap.alphaType() == kPremul_SkAlphaType) {\n    return SkData::MakeWithCopy(pixmap.addr(), pixmap.computeByteSize());\n  }\n\n  // Perform swizzle if the type doesnt match the specification.\n  auto surface = SkSurface::MakeRaster(\n      SkImageInfo::Make(image->width(), image->height(), kRGBA_8888_SkColorType,\n                        kPremul_SkAlphaType, nullptr));\n\n  if (!surface) {\n    FML_LOG(ERROR) << \"Could not set up the surface for swizzle.\";\n    return nullptr;\n  }\n\n  surface->writePixels(pixmap, 0, 0);\n\n  if (!surface->peekPixels(&pixmap)) {\n    FML_LOG(ERROR) << \"Pixel address is not available.\";\n    return nullptr;\n  }\n\n  return SkData::MakeWithCopy(pixmap.addr(), pixmap.computeByteSize());\n#else\n  if (!image) {\n    FML_LOG(ERROR) << \"Could not peekPixels from null image.\";\n    return nullptr;\n  }\n  std::shared_ptr<skity::Pixmap> pixmap = image->peekPixels();\n  if (pixmap->GetColorType() == skity::ColorType::kRGBA &&\n      pixmap->GetAlphaType() == skity::AlphaType::kPremul_AlphaType) {\n    return skity::Data::MakeWithCopy(pixmap->Addr(),\n                                     pixmap->Height() * pixmap->RowBytes());\n  }\n\n  // TODO: Need raster surface to write pixels.\n  return skity::Data::MakeEmpty();\n#endif  // ENABLE_SKITY\n}\n\nvoid DecodeImage(\n    GrDataPtr data,\n    std::function<void(const char* error_message, const ClayBitmap* bitmap)>\n        callback) {\n  fml::RefPtr<clay::ImageDescriptor> image_descriptor =\n      clay::ImageDescriptor::Create(std::move(data));\n\n  // It seems that the GetImageDecoder is not thread safe\n  // so we post the task to UI thread first\n  fml::TaskRunner::RunNowOrPostTask(\n      clay::Isolate::Instance().GetIOTaskRunner(),\n      [image_descriptor = std::move(image_descriptor),\n       callback = std::move(callback)] {\n        // TODO(yudingqian): Implement with PlatformCodec later.\n        clay::GraphicsIsolate::Instance().GetImageDecoder()->Decode(\n            image_descriptor, image_descriptor->width(),\n            image_descriptor->height(),\n            [callback =\n                 std::move(callback)](fml::RefPtr<clay::GraphicsImage> image) {\n              auto image_pixels = GetImagePixels(image);\n              if (!image_pixels) {\n                callback(\"Failed to decode image\", nullptr);\n              } else {\n                auto* shared_ptr_holder =\n                    new GrDataPtr(std::move(image_pixels));\n                ClayBitmap result{};\n                result.struct_size = sizeof(result);\n                result.width = image->width();\n                result.height = image->height();\n                result.pixels.size = DATA_GET_SIZE((*shared_ptr_holder));\n                result.pixels.ptr = DATA_GET_DATA((*shared_ptr_holder));\n                result.pixels.user_data = shared_ptr_holder;\n                result.pixels.destruction_callback = [](const void*,\n                                                        void* user_data) {\n                  delete static_cast<GrDataPtr*>(user_data);\n                };\n                callback(nullptr, &result);\n              }\n            });\n      });\n}\n\nclass ClayBitmapCallbackWrapper {\n public:\n  ClayBitmapCallbackWrapper(ClayBitmapDecodeCallback callback, void* user_data)\n      : callback_(callback), user_data_(user_data) {}\n\n  void OnDecode(const ClayBitmap* result) {\n    callback_(nullptr, result, user_data_);\n    callback_ = nullptr;\n  }\n\n  void OnError(const char* error_message) {\n    callback_(error_message, nullptr, user_data_);\n    callback_ = nullptr;\n  }\n\n  ~ClayBitmapCallbackWrapper() {\n    if (callback_) {\n      callback_(\"Unable to call Callback due to shutdown\", nullptr, user_data_);\n    }\n  }\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(ClayBitmapCallbackWrapper);\n\n private:\n  ClayBitmapDecodeCallback callback_;\n  void* user_data_;\n};\n\n}  // namespace\n\nCLAY_EXTERN_C ClaySharedImageRef ClayCreateSharedImage(\n    ClaySharedImageBackingType type,\n    ClaySharedImageBackingPixelFormat pixel_format, const ClaySize* size) {\n  skity::Vec2 sk_size{static_cast<int32_t>(size->width),\n                      static_cast<int32_t>(size->height)};\n  fml::RefPtr<SharedImageBacking> backing = SharedImageBacking::Create(\n      ClaySharedImageBackingTypeToBackingType(type),\n      ClaySharedImageBackingPixelFormatToPixelFormat(pixel_format), sk_size,\n      {});\n  backing->AddRef();\n  return reinterpret_cast<ClaySharedImageRef>(backing.get());\n}\n\nCLAY_EXTERN_C ClaySharedImageRef ClayCreateSharedImageFromHandle(\n    ClaySharedImageBackingType type,\n    ClaySharedImageBackingPixelFormat pixel_format, const ClaySize* size,\n    ClaySharedImageNativeHandle handle) {\n  skity::Vec2 sk_size{static_cast<int32_t>(size->width),\n                      static_cast<int32_t>(size->height)};\n\n  fml::RefPtr<SharedImageBacking> backing = SharedImageBacking::Create(\n      ClaySharedImageBackingTypeToBackingType(type),\n      ClaySharedImageBackingPixelFormatToPixelFormat(pixel_format), sk_size,\n      reinterpret_cast<GraphicsMemoryHandle>(handle));\n  backing->AddRef();\n  return reinterpret_cast<ClaySharedImageRef>(backing.get());\n}\n\nCLAY_EXTERN_C ClaySharedImageRef\nClayRetainSharedImage(ClaySharedImageRef shared_image_ref) {\n  reinterpret_cast<SharedImageBacking*>(shared_image_ref)->AddRef();\n  return shared_image_ref;\n}\n\nCLAY_EXTERN_C void ClayReleaseSharedImage(ClaySharedImageRef shared_image_ref) {\n  if (shared_image_ref) {\n    reinterpret_cast<SharedImageBacking*>(shared_image_ref)->Release();\n  }\n}\n\nCLAY_EXTERN_C void ClaySharedImageGetBacking(\n    ClaySharedImageRef shared_image_ref, ClaySharedImageBackingType* out_type,\n    ClaySharedImageBackingPixelFormat* out_format,\n    ClaySharedImageNativeHandle* out_handle) {\n  SharedImageBacking* shared_image =\n      reinterpret_cast<SharedImageBacking*>(shared_image_ref);\n  if (out_type) {\n    switch (shared_image->GetType()) {\n      case SharedImageBacking::BackingType::kIOSurface:\n        *out_type = kClaySharedImageBackingTypeIOSurface;\n        break;\n      case SharedImageBacking::BackingType::kD3DTexture:\n        *out_type = kClaySharedImageBackingTypeD3DTexture;\n        break;\n      case SharedImageBacking::BackingType::kNativeImage:\n        *out_type = kClaySharedImageBackingTypeNativeImage;\n        break;\n      case SharedImageBacking::BackingType::kShmImage:\n        *out_type = kClaySharedImageBackingTypeShmImage;\n        break;\n      default:\n        break;\n    }\n  }\n  if (out_format) {\n    switch (shared_image->GetPixelFormat()) {\n      case SharedImageBacking::PixelFormat::kNative8888:\n        *out_format = kClaySharedImageBackingPixelFormatNative8888;\n        break;\n      case SharedImageBacking::PixelFormat::kRGBA8888:\n        *out_format = kClaySharedImageBackingPixelFormatRGBA8;\n        break;\n      default:\n        break;\n    }\n  }\n  if (out_handle) {\n    *out_handle = reinterpret_cast<ClaySharedImageNativeHandle>(\n        shared_image->GetGFXHandle());\n  }\n}\n\nCLAY_EXTERN_C void ClaySharedImageGetSize(ClaySharedImageRef shared_image_ref,\n                                          ClaySize* out) {\n  auto& size =\n      reinterpret_cast<SharedImageBacking*>(shared_image_ref)->GetSize();\n  out->width = size.x;\n  out->height = size.y;\n}\n\nCLAY_EXTERN_C void ClaySharedImageGetTransformation(\n    ClaySharedImageRef shared_image_ref, ClayTransformation* out) {\n  auto& mat = reinterpret_cast<SharedImageBacking*>(shared_image_ref)\n                  ->GetTransformation();\n\n  out->scaleX = mat.GetScaleX();\n  out->skewX = mat.GetSkewX();\n  out->transX = mat.GetTranslateX();\n  out->skewY = mat.GetSkewY();\n  out->scaleY = mat.GetScaleY();\n  out->transY = mat.GetTranslateY();\n  out->pers0 = mat.GetPersp0();\n  out->pers1 = mat.GetPersp1();\n  out->pers2 = mat.GetPersp2();\n}\n\nCLAY_EXTERN_C void ClaySharedImageSetTransformation(\n    ClaySharedImageRef shared_image_ref,\n    const ClayTransformation* transformation) {\n  skity::Matrix mat = skity::Matrix(\n      transformation->scaleX, transformation->skewX, transformation->transX,\n      transformation->skewY, transformation->scaleY, transformation->transY,\n      transformation->pers0, transformation->pers1, transformation->pers2);\n\n  reinterpret_cast<SharedImageBacking*>(shared_image_ref)\n      ->SetTransformation(mat);\n}\n\nCLAY_EXTERN_C ClayFenceSyncRef\nClayCreateExternalFenceSync(ClayVoidCallback wait_callback,\n                            ClayVoidCallback delete_callback, void* user_data) {\n  return reinterpret_cast<ClayFenceSyncRef>(\n      new ExternalFenceSync(wait_callback, delete_callback, user_data));\n}\n\nCLAY_EXTERN_C bool ClayFenceSyncClientWait(ClayFenceSyncRef fence_sync) {\n  if (fence_sync) {\n    return reinterpret_cast<FenceSync*>(fence_sync)->ClientWait();\n  }\n  return true;\n}\n\nCLAY_EXTERN_C void ClayDestroyFenceSync(ClayFenceSyncRef fence_sync) {\n  delete reinterpret_cast<FenceSync*>(fence_sync);\n}\n\nCLAY_EXTERN_C void ClaySharedImageSetFenceSync(\n    ClaySharedImageRef shared_image_ref, ClayFenceSyncRef fence_sync_ref) {\n  reinterpret_cast<SharedImageBacking*>(shared_image_ref)\n      ->SetFenceSync(std::unique_ptr<FenceSync>(\n          reinterpret_cast<FenceSync*>(fence_sync_ref)));\n}\n\nCLAY_EXTERN_C ClayFenceSyncRef\nClaySharedImageGetFenceSync(ClaySharedImageRef shared_image_ref) {\n  std::unique_ptr<FenceSync> fence_sync =\n      reinterpret_cast<SharedImageBacking*>(shared_image_ref)->GetFenceSync();\n  return reinterpret_cast<ClayFenceSyncRef>(fence_sync.release());\n}\n\nCLAY_EXTERN_C ClaySharedImageSinkRef\nClayCreateSharedImageSink(ClaySharedImageSinkBufferMode clay_buffer_mode,\n                          ClaySharedImageBackingType type,\n                          ClaySharedImageBackingPixelFormat pixel_format) {\n  SharedImageSink::BufferMode buffer_mode;\n  switch (clay_buffer_mode) {\n    case kClaySharedImageSinkBufferModeSingleBuffer:\n      buffer_mode = SharedImageSink::kSingleBuffer;\n      break;\n    case kClaySharedImageSinkBufferModeDoubleBuffer:\n      buffer_mode = SharedImageSink::kDoubleBuffer;\n      break;\n    case kClaySharedImageSinkBufferModeTripleBuffer:\n      buffer_mode = SharedImageSink::kTripleBuffer;\n      break;\n    default:\n      FML_LOG(ERROR) << \"unknown ClaySharedImageSinkBufferMode value: \"\n                     << clay_buffer_mode;\n      return nullptr;\n  }\n  fml::RefPtr<SharedImageSink> sink =\n      fml::MakeRefCounted<SharedImageSinkManaged>(\n          buffer_mode,\n          [type, pixel_format](skity::Vec2 size,\n                               std::optional<GraphicsMemoryHandle> gfx_handle)\n              -> fml::RefPtr<SharedImageBacking> {\n            ClaySize clay_size;\n            clay_size.width = size.x;\n            clay_size.height = size.y;\n            SharedImageBacking* backing;\n            if (gfx_handle.has_value() && gfx_handle.value()) {\n              backing = reinterpret_cast<SharedImageBacking*>(\n                  ClayCreateSharedImageFromHandle(\n                      type, pixel_format, &clay_size, gfx_handle.value()));\n            } else {\n              backing = reinterpret_cast<SharedImageBacking*>(\n                  ClayCreateSharedImage(type, pixel_format, &clay_size));\n            }\n            fml::RefPtr<SharedImageBacking> result(backing);\n            backing->Release();\n            return result;\n          });\n  sink->AddRef();\n\n  return reinterpret_cast<ClaySharedImageSinkRef>(sink.get());\n}\n\nCLAY_EXTERN_C ClaySharedImageSinkRef\nClayRetainSharedImageSink(ClaySharedImageSinkRef sink_ref) {\n  reinterpret_cast<SharedImageSink*>(sink_ref)->AddRef();\n  return sink_ref;\n}\n\nCLAY_EXTERN_C void ClayReleaseSharedImageSink(ClaySharedImageSinkRef sink_ref) {\n  if (sink_ref) {\n    reinterpret_cast<SharedImageSink*>(sink_ref)->Release();\n  }\n}\n\nCLAY_EXTERN_C ClaySharedImageSinkBufferMode\nClaySharedImageSinkGetBufferMode(ClaySharedImageSinkRef sink_ref) {\n  uint32_t capacity = reinterpret_cast<SharedImageSink*>(sink_ref)->Capacity();\n  switch (capacity) {\n    case 1:\n      return kClaySharedImageSinkBufferModeSingleBuffer;\n    case 2:\n      return kClaySharedImageSinkBufferModeDoubleBuffer;\n    case 3:\n      return kClaySharedImageSinkBufferModeTripleBuffer;\n  }\n  FML_LOG(ERROR) << \"Unknown buffer mode, capacity: \" << capacity;\n  // We don't known the result, but single buffer is safest\n  return kClaySharedImageSinkBufferModeSingleBuffer;\n}\n\nCLAY_EXTERN_C void ClaySharedImageSinkSetFrameAvailableCallback(\n    ClaySharedImageSinkRef sink_ref, ClayVoidCallback callback,\n    void* user_data) {\n  if (callback) {\n    reinterpret_cast<SharedImageSink*>(sink_ref)->SetFrameAvailableCallback(\n        [callback, user_data] { callback(user_data); });\n  } else {\n    reinterpret_cast<SharedImageSink*>(sink_ref)->SetFrameAvailableCallback(\n        nullptr);\n  }\n}\n\nCLAY_EXTERN_C bool ClaySharedImageSinkUpdateFront(\n    ClaySharedImageSinkRef sink_ref, ClayFenceSyncRef produced_fence_sync_ref,\n    ClaySharedImageRef* out) {\n  SharedImageSink* sink = reinterpret_cast<SharedImageSink*>(sink_ref);\n  std::unique_ptr<FenceSync> produced_fence_sync(\n      reinterpret_cast<FenceSync*>(produced_fence_sync_ref));\n  fml::RefPtr<SharedImageBacking> backing =\n      sink->UpdateFront(std::move(produced_fence_sync));\n  if (!backing) {\n    return false;\n  }\n  if (out) {\n    *out = reinterpret_cast<ClaySharedImageRef>(backing.get());\n  }\n  return true;\n}\n\nCLAY_EXTERN_C void ClaySharedImageSinkReleaseFront(\n    ClaySharedImageRef sink_ref, ClayFenceSyncRef produced_fence_sync_ref) {\n  SharedImageSink* sink = reinterpret_cast<SharedImageSink*>(sink_ref);\n  std::unique_ptr<FenceSync> produced_fence_sync(\n      reinterpret_cast<FenceSync*>(produced_fence_sync_ref));\n  sink->ReleaseFront(std::move(produced_fence_sync));\n}\n\nCLAY_EXTERN_C bool ClaySharedImageSinkAcquireBack(\n    ClaySharedImageSinkRef sink_ref, const ClaySize* size,\n    ClaySharedImageRef* out, uint32_t* out_buffer_age) {\n  SharedImageSink* sink = reinterpret_cast<SharedImageSink*>(sink_ref);\n  auto [backing, buffer_age] =\n      sink->AcquireBack(skity::Vec2(size->width, size->height));\n  if (!backing) {\n    return false;\n  }\n  if (out) {\n    *out = reinterpret_cast<ClaySharedImageRef>(backing.get());\n  }\n  if (out_buffer_age) {\n    *out_buffer_age = buffer_age;\n  }\n  return true;\n}\n\nCLAY_EXTERN_C bool ClaySharedImageSinkTryAcquireBack(\n    ClaySharedImageSinkRef sink_ref, const ClaySize* size,\n    ClaySharedImageRef* out, uint32_t* out_buffer_age,\n    ClaySharedImageNativeHandle handle) {\n  SharedImageSink* sink = reinterpret_cast<SharedImageSink*>(sink_ref);\n  std::optional<GraphicsMemoryHandle> gfx_handle;\n  if (handle) {\n    gfx_handle = handle;\n  }\n  auto [backing, buffer_age, status] =\n      sink->TryAcquireBack(skity::Vec2(size->width, size->height), gfx_handle);\n  if (!backing) {\n    return false;\n  }\n  auto fenceSync = backing->GetFenceSync();\n  if (fenceSync) {\n    fenceSync->ClientWait();\n  }\n  if (out) {\n    *out = reinterpret_cast<ClaySharedImageRef>(backing.get());\n  }\n  if (out_buffer_age) {\n    *out_buffer_age = buffer_age;\n  }\n  return true;\n}\n\nCLAY_EXTERN_C bool ClaySharedImageSinkSwapBack(\n    ClaySharedImageSinkRef sink_ref, ClayFenceSyncRef fence_sync_ref) {\n  SharedImageSink* sink = reinterpret_cast<SharedImageSink*>(sink_ref);\n  std::unique_ptr<FenceSync> fence_sync(\n      reinterpret_cast<FenceSync*>(fence_sync_ref));\n  return sink->SwapBack(std::move(fence_sync));\n}\n\nCLAY_EXTERN_C ClaySharedImageSinkAccessorRef ClayCreateSharedImageSinkAccessor(\n    ClaySharedImageSinkRef sink_ref,\n    const ClaySharedImageRepresentationConfig* config) {\n  SharedImageSink* sink = reinterpret_cast<SharedImageSink*>(sink_ref);\n  return reinterpret_cast<ClaySharedImageSinkAccessorRef>(\n      new SharedImageSinkAccessor(\n          fml::Ref(sink),\n          [config = *config](fml::RefPtr<SharedImageBacking> backing)\n              -> fml::RefPtr<SharedImageRepresentation> {\n            return backing->CreateRepresentation(&config);\n          }));\n}\n\nCLAY_EXTERN_C void ClayDestroySharedImageSinkAccessor(\n    ClaySharedImageSinkAccessorRef sink_accessor) {\n  delete reinterpret_cast<SharedImageSinkAccessor*>(sink_accessor);\n}\n\nCLAY_EXTERN_C bool ClaySharedImageSinkRead(\n    ClaySharedImageSinkAccessorRef sink_accessor_ref,\n    ClaySharedImageRef* out_image, ClaySharedImageReadResult* out) {\n  SharedImageSinkAccessor* sink_accessor =\n      reinterpret_cast<SharedImageSinkAccessor*>(sink_accessor_ref);\n  fml::RefPtr<SharedImageRepresentation> repr = sink_accessor->UpdateFront();\n  if (!repr) {\n    return false;\n  }\n  if (out_image) {\n    *out_image = reinterpret_cast<ClaySharedImageRef>(repr->GetBacking());\n  }\n  if (out) {\n    if (!repr->BeginRead(out)) {\n      return false;\n    }\n  }\n  return true;\n}\n\nCLAY_EXTERN_C void ClaySharedImageSinkEndRead(\n    ClaySharedImageSinkAccessorRef sink_accessor_ref) {\n  SharedImageSinkAccessor* sink_accessor =\n      reinterpret_cast<SharedImageSinkAccessor*>(sink_accessor_ref);\n  sink_accessor->ReleaseFront();\n}\n\nCLAY_EXTERN_C bool ClaySharedImageSinkBeginWrite(\n    ClaySharedImageSinkAccessorRef sink_accessor_ref, const ClaySize* size,\n    ClaySharedImageRef* out_image, ClaySharedImageWriteResult* out,\n    uint32_t* out_buffer_age) {\n  SharedImageSinkAccessor* sink_accessor =\n      reinterpret_cast<SharedImageSinkAccessor*>(sink_accessor_ref);\n  auto [repr, buffer_age] =\n      sink_accessor->AcquireBack(skity::Vec2(size->width, size->height));\n  if (!repr) {\n    return false;\n  }\n  if (out_image) {\n    *out_image = reinterpret_cast<ClaySharedImageRef>(repr->GetBacking());\n  }\n  if (out) {\n    if (!repr->BeginWrite(out)) {\n      return false;\n    }\n  }\n  if (out_buffer_age) {\n    *out_buffer_age = buffer_age;\n  }\n  return true;\n}\n\nCLAY_EXTERN_C bool ClaySharedImageSinkEndWrite(\n    ClaySharedImageSinkAccessorRef sink_accessor_ref) {\n  SharedImageSinkAccessor* sink_accessor =\n      reinterpret_cast<SharedImageSinkAccessor*>(sink_accessor_ref);\n  return sink_accessor->SwapBack();\n}\n\nCLAY_EXTERN_C void ClayDecodeImage(const ClayDataHolder* data_holder,\n                                   ClayBitmapDecodeCallback decode_callback,\n                                   void* user_data) {\n  GrDataPtr data = GrData::MakeWithProc(data_holder->ptr, data_holder->size,\n                                        data_holder->destruction_callback,\n                                        data_holder->user_data);\n\n  std::shared_ptr<ClayBitmapCallbackWrapper> callback =\n      std::make_shared<ClayBitmapCallbackWrapper>(decode_callback, user_data);\n\n  DecodeImage(std::move(data),\n              [callback = std::move(callback)](const char* error_message,\n                                               const ClayBitmap* result) {\n                if (error_message) {\n                  callback->OnError(error_message);\n                } else {\n                  callback->OnDecode(result);\n                }\n              });\n}\n\nCLAY_EXTERN_C bool ClayDecodeDataUrlImage(const char* data_url, size_t length,\n                                          ClayBitmap* out) {\n  clay::DataImageLoader data_image_loader(nullptr);\n  clay::RawResource raw_resource =\n      data_image_loader.LoadSync(std::string(data_url, length));\n  if (!raw_resource.data) {\n    return false;\n  }\n\n  std::promise<std::optional<ClayBitmap>> promise;\n  // It's safe to MakeWithoutCopy since the decoding will block the thread\n  GrDataPtr data = GrData::MakeWithProc(raw_resource.data.get(),\n                                        raw_resource.length, nullptr, nullptr);\n\n  DecodeImage(std::move(data),\n              [&promise](const char* error_message, const ClayBitmap* bitmap) {\n                if (error_message) {\n                  promise.set_value({});\n                } else {\n                  promise.set_value(*bitmap);\n                }\n              });\n\n  std::optional<ClayBitmap> result = promise.get_future().get();\n  if (result) {\n    *out = *result;\n    return true;\n  } else {\n    return false;\n  }\n}\n\nCLAY_EXTERN_C bool ClayEncodeBitmap(const ClayBitmap* bitmap,\n                                    ClayImageFormat encoding,\n                                    float compress_ratio, ClayDataHolder* out) {\n#ifndef ENABLE_SKITY\n  SkEncodedImageFormat format;\n\n  switch (encoding) {\n    case kClayImageFormatJPEG:\n      format = SkEncodedImageFormat::kJPEG;\n      break;\n    case kClayImageFormatPNG:\n      format = SkEncodedImageFormat::kPNG;\n      break;\n    default:\n      return false;\n  }\n\n  SkImageInfo image_info =\n      SkImageInfo::Make(bitmap->width, bitmap->height, kRGBA_8888_SkColorType,\n                        kPremul_SkAlphaType);\n  SkPixmap pixmap(image_info, bitmap->pixels.ptr, bitmap->width * 4);\n  sk_sp<SkImage> image = SkImages::RasterFromPixmap(pixmap, nullptr, nullptr);\n  sk_sp<SkData> data = image->encodeToData(format, compress_ratio * 100);\n  if (!data) {\n    return false;\n  }\n  out->size = data->size();\n  out->ptr = data->data();\n  out->destruction_callback = [](const void*, void* user_data) {\n    static_cast<SkData*>(user_data)->unref();\n  };\n  out->user_data = data.release();\n#else\n  FML_UNIMPLEMENTED();\n  return false;\n#endif  // ENABLE_SKITY\n  return true;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/public/value.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/public/value.h\"\n\n#include <utility>\n\nnamespace clay {\n\nValue::Value(Value&& that) {\n  value_ = std::move(that.value_);\n  that.value_.emplace<std::monostate>();\n  if (IsExternal()) {\n    wrapper_ = std::move(that.wrapper_);\n  } else {\n    wrapper_.reset();\n  }\n}\n\nValue::Value(std::initializer_list<Value>&& v) {\n  Value::Array array;\n  for (auto& item : v) {\n    array.emplace_back(std::move(const_cast<Value&>(item)));\n  }\n  value_ = std::make_shared<Array>(std::move(array));\n}\n\nValue::Value(std::initializer_list<std::pair<std::string, Value>>&& v) {\n  Value::Map map;\n  for (auto& item : v) {\n    auto& ref = const_cast<std::pair<std::string, Value>&>(item);\n    map.emplace(std::move(ref.first), std::move(ref.second));\n  }\n  value_ = std::make_shared<Map>(std::move(map));\n}\n\nValue& Value::operator=(Value&& that) {\n  value_ = std::move(that.value_);\n  that.value_.emplace<std::monostate>();\n  if (IsExternal()) {\n    wrapper_ = std::move(that.wrapper_);\n  } else {\n    wrapper_.reset();\n  }\n  return *this;\n}\n\nValue::~Value() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/render_delegate.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDER_DELEGATE_H_\n#define CLAY_UI_RENDER_DELEGATE_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/flow/frame_timings.h\"\n#include \"clay/gfx/paint_image.h\"\n#ifdef ENABLE_ACCESSIBILITY\n#include \"clay/ui/semantics/semantics_update_node.h\"\n#endif\n\nnamespace clay {\n\nclass BaseView;\nclass DrawableImage;\nclass LayerTree;\nclass ResourceLoaderIntercept;\nclass ShadowNode;\n\nclass RenderDelegate {\n public:\n  virtual ~RenderDelegate() {}\n\n  virtual void ScheduleFrame() = 0;\n  virtual void ForceBeginFrame() = 0;\n  virtual void OnFirstMeaningfulLayout() = 0;\n  virtual void ScheduleLayout() {}\n  virtual bool Raster(\n      std::unique_ptr<clay::LayerTree> layer_tree,\n      std::unique_ptr<clay::FrameTimingsRecorder> recorder = nullptr,\n      bool force = false) = 0;\n  virtual void ShowSoftInput(int type, int action) = 0;\n  virtual void HideSoftInput() = 0;\n  virtual std::string ShouldInterceptUrl(const std::string& origin_url,\n                                         bool should_decode) = 0;\n  virtual std::shared_ptr<ResourceLoaderIntercept>\n  GetResourceLoaderIntercept() {\n    return nullptr;\n  }\n\n  virtual void MakeRasterSnapshot(\n      std::unique_ptr<LayerTree> layer_tree,\n      std::function<void(fml::RefPtr<PaintImage>)> callback) = 0;\n  virtual fml::RefPtr<PaintImage> MakeRasterSnapshot(\n      GrPicturePtr picture, skity::Vec2 picture_size) = 0;\n\n  virtual void DumpInfoToDevtoolEnabled(bool enabled) {}\n  virtual void SetClipboardData(const std::u16string& data) = 0;\n  virtual std::u16string GetClipboardData() = 0;\n\n#if defined(OS_WIN) || defined(OS_MAC) || defined(ENABLE_HEADLESS)\n  // Text input related functions Begin.\n  virtual void SetTextInputClient(int client_id, const char* input_action,\n                                  const char* input_type) = 0;\n  virtual void ClearTextInputClient() = 0;\n  virtual void SetEditableTransform(const float transform_matrix[16]) = 0;\n  virtual void SetEditingState(uint64_t selection_base,\n                               uint64_t composing_extent,\n                               const std::string& selection_affinity,\n                               const std::string& text,\n                               bool selection_directional,\n                               uint64_t selection_extent,\n                               uint64_t composing_base) = 0;\n  virtual void SetCaretRect(float x, float y, float width, float height) = 0;\n  virtual void setMarkedTextRect(float x, float y, float width,\n                                 float height) = 0;\n  virtual void ShowTextInput() = 0;\n  virtual void HideTextInput() = 0;\n  // Text input related functions End.\n  virtual void WindowMove() = 0;\n  virtual void ActivateSystemCursor(int type, const std::string& path) = 0;\n#endif\n\n  virtual void FilterInputAsync(\n      const std::string& input, const std::string& pattern,\n      std::function<void(const std::string&)> callback) = 0;\n  virtual void ReportTiming(\n      const std::unordered_map<std::string, int64_t>& timing,\n      const std::string& flag) = 0;\n\n  virtual void ClearTextCache() {}\n  virtual BaseView* FindViewById(int view_id) = 0;\n  virtual ShadowNode* FindShadowNodeById(int node_id) = 0;\n  virtual void UpdateRootSize(int32_t width, int32_t height) {}\n#ifdef ENABLE_ACCESSIBILITY\n  virtual void UpdateSemantics(const SemanticsUpdateNodes& update_nodes) {}\n#endif\n\n  virtual void RegisterDrawableImage(\n      std::shared_ptr<DrawableImage> drawable_image) = 0;\n  virtual void UnregisterDrawableImage(int64_t id) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDER_DELEGATE_H_\n"
  },
  {
    "path": "clay/ui/rendering/abstract_node.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/abstract_node.h\"\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nvoid AbstractNode::RedepthChild(AbstractNode* child) const {\n  if (child->Depth() <= depth_) {\n    child->SetDepth(depth_ + 1);\n    child->RedepthChildren();\n  }\n}\n\nvoid AbstractNode::AdoptChild(AbstractNode* child) {\n  FML_DCHECK(child->Parent() == nullptr);\n  FML_DCHECK(CheckCycle(child));\n\n  child->SetParent(this);\n  RedepthChild(child);\n}\n\nvoid AbstractNode::DropChild(AbstractNode* child) const {\n  FML_DCHECK(child->Parent() == this);\n\n  child->SetParent(nullptr);\n}\n\nbool AbstractNode::CheckCycle(const AbstractNode* child) const {\n  const AbstractNode* node = this;\n  while (node->Parent() != nullptr) {\n    node = node->Parent();\n  }\n\n  FML_DCHECK(node != child);\n  return node != child;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/abstract_node.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_ABSTRACT_NODE_H_\n#define CLAY_UI_RENDERING_ABSTRACT_NODE_H_\n\nnamespace clay {\n\n// An abstract node in a tree, which will be used as the base class of\n// RenderObject and RenderLayer.\nclass AbstractNode {\n public:\n  AbstractNode() = default;\n  virtual ~AbstractNode() = default;\n\n  unsigned Depth() const { return depth_; }\n\n  // Mark the given node as being a child of this node.\n  void AdoptChild(AbstractNode* child);\n\n  // Disconnect the given node from this node.\n  void DropChild(AbstractNode* child) const;\n\n  // Adjust the [depth] of this node's children, if any.\n  virtual void RedepthChildren() = 0;\n\n  AbstractNode* Parent() { return parent_; }\n  const AbstractNode* Parent() const { return parent_; }\n\n protected:\n  // Adjust the [depth] of the given [child] to be greater than this node's own\n  // [depth].\n  // Only call this method from overrides of [RedepthChildren].\n  void RedepthChild(AbstractNode* child) const;\n\n  void SetDepth(unsigned depth) { depth_ = depth; }\n  void SetParent(AbstractNode* parent) { parent_ = parent; }\n\n  // TRUE means the check passed, there is no a cycle.\n  bool CheckCycle(const AbstractNode* child) const;\n\n private:\n  // The depth of this node in the tree.\n  unsigned depth_ = 0;\n  // The parent of this node in the tree.\n  AbstractNode* parent_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_ABSTRACT_NODE_H_\n"
  },
  {
    "path": "clay/ui/rendering/abstract_node_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/abstract_node.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nnamespace {\nclass MockAbstractNode : public AbstractNode {\n public:\n  MOCK_METHOD(void, RedepthChildren, (), (override));\n};\n}  // namespace\n\nTEST(AbstractNodeTest, ModifyTree) {\n  MockAbstractNode rootNode;\n  MockAbstractNode childNode;\n  MockAbstractNode grandchildNode;\n  EXPECT_CALL(childNode, RedepthChildren()).Times(1);\n  EXPECT_CALL(grandchildNode, RedepthChildren()).Times(1);\n\n  rootNode.AdoptChild(&childNode);\n  childNode.AdoptChild(&grandchildNode);\n  EXPECT_EQ(childNode.Parent(), &rootNode);\n  EXPECT_EQ(childNode.Depth(), 1u);\n  EXPECT_EQ(grandchildNode.Parent(), &childNode);\n  EXPECT_EQ(grandchildNode.Depth(), 2u);\n\n  rootNode.DropChild(&childNode);\n  childNode.DropChild(&grandchildNode);\n  EXPECT_FALSE(childNode.Parent());\n  EXPECT_FALSE(grandchildNode.Parent());\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/build.gni",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nui_rendering_sources = [\n  \"abstract_node.cc\",\n  \"abstract_node.h\",\n  \"decode_utils.cc\",\n  \"decode_utils.h\",\n  \"render_bounce.h\",\n  \"render_box.cc\",\n  \"render_box.h\",\n  \"render_container.cc\",\n  \"render_container.h\",\n  \"render_dummy.h\",\n  \"render_external_content.cc\",\n  \"render_external_content.h\",\n  \"render_external_view.cc\",\n  \"render_external_view.h\",\n  \"render_image.cc\",\n  \"render_image.h\",\n  \"render_list.cc\",\n  \"render_list.h\",\n  \"render_object.cc\",\n  \"render_object.h\",\n  \"render_object_child_list.cc\",\n  \"render_object_child_list.h\",\n  \"render_overlay.cc\",\n  \"render_overlay.h\",\n  \"render_page.cc\",\n  \"render_page.h\",\n  \"render_scroll.cc\",\n  \"render_scroll.h\",\n  \"renderer.cc\",\n  \"renderer.h\",\n  \"text/render_inline_text.cc\",\n  \"text/render_inline_text.h\",\n  \"text/render_text.cc\",\n  \"text/render_text.h\",\n]\n\nui_extended_rendering_sources = [\n  \"editable/render_editable.cc\",\n  \"editable/render_editable.h\",\n]\n"
  },
  {
    "path": "clay/ui/rendering/decode_utils.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/decode_utils.h\"\n\n#include <algorithm>\n\n#include \"clay/ui/rendering/render_object.h\"\n#include \"clay/ui/rendering/render_scroll.h\"\n\nnamespace clay {\n\nnamespace {\n\nfloat LeftWithScroll(RenderObject* render_object) {\n  if (render_object) {\n    return render_object->Left() - render_object->ScrollLeft();\n  }\n  return 0.f;\n}\n\nfloat TopWithScroll(RenderObject* render_object) {\n  if (render_object) {\n    return render_object->Top() - render_object->ScrollTop();\n  }\n  return 0.f;\n}\n\n// @Param render_object: child render object of a scrollable parent.\n// @Param parent_scroll: will be set to the first scrollable parent of render\n// object.\n// @Return: the visible area of render_object in its parent scroll area.\nstd::optional<FloatRect> VisibleAreaInParentScroll(\n    RenderObject* render_object, RenderScroll** parent_scroll) {\n  auto self = render_object;\n  RenderObject* parent = static_cast<RenderObject*>(render_object->Parent());\n  FloatRect visible_rect(render_object->Left(), render_object->Top(),\n                         render_object->Width(), render_object->Height());\n  while (parent) {\n    if (self->IsOverlay()) {\n      break;\n    } else {\n      self = parent;\n      float left = LeftWithScroll(parent);\n      float top = TopWithScroll(parent);\n      visible_rect.Move(left, top);\n      if (parent->IsScrollable()) {\n        // parent_scroll will be set to the first render scroll parent.\n        if (*parent_scroll == nullptr) {\n          *parent_scroll = static_cast<RenderScroll*>(parent);\n        }\n        FloatRect parent_rect(0, 0, parent->Width(), parent->Height());\n        // child is not visible in its parent scroll area.\n        if (!visible_rect.Intersects(parent_rect)) {\n          return std::nullopt;\n        }\n        visible_rect.Intersect(parent_rect);\n      }\n      parent = static_cast<RenderObject*>(parent->Parent());\n    }\n  }\n  return visible_rect;\n}\n\n// @Param render_object: child render object of a scrollable parent.\n// @Param parent_scroll: will be set to the first scrollable parent of\n// render_object.\n// @Return: true if render_object is in the visible area of its parent scroll\n// area.\nbool IsInVisibleArea(RenderObject* render_object,\n                     RenderScroll** parent_scroll) {\n  Renderer* renderer = render_object->GetRenderer();\n  Size frame_size = renderer->GetFrameSize();\n  FloatRect visible_area =\n      FloatRect(0, 0, frame_size.width(), frame_size.height());\n\n  auto bounds = VisibleAreaInParentScroll(render_object, parent_scroll);\n  return bounds.has_value() ? visible_area.Intersects(*bounds) : false;\n}\n\nbool InScrollAnimation(RenderScroll* render_scroll) {\n  return render_scroll->InScrollAnimation();\n}\n\n// @Param render_object: child render object of a scrollable parent.\n// @Param parent_scroll: the scrollable parent of render_object.\n// @Return: true if render_object is in the preload range of parent_scroll.\nbool IsInPreloadRange(RenderObject* render_object,\n                      RenderScroll* parent_scroll) {\n  // We would like to preload at the top and bottom of the scrolling container,\n  // with some distances if scroll direction is vertical. Otherwise, preload at\n  // the left and right.\n  skity::Rect preload_bounds;\n  float distance = 0.f;\n  Renderer* renderer = render_object->GetRenderer();\n  Size frame_size = renderer->GetFrameSize();\n  if (parent_scroll->GetScrollDirection() == ScrollDirection::kVertical) {\n    distance = std::min(parent_scroll->Height(),\n                        static_cast<float>(frame_size.height())) /\n               4.f;\n    preload_bounds =\n        skity::Rect::MakeXYWH(0, -distance, parent_scroll->Width(),\n                              parent_scroll->Height() + 2 * distance);\n  } else {\n    distance = std::min(parent_scroll->Width(),\n                        static_cast<float>(frame_size.width())) /\n               4.f;\n    preload_bounds = skity::Rect::MakeXYWH(\n        -distance, 0, parent_scroll->Width() + 2 * distance,\n        parent_scroll->Height());\n  }\n\n  float left = render_object->Left();\n  float top = render_object->Top();\n  RenderObject* parent = static_cast<RenderObject*>(render_object->Parent());\n  while (parent != parent_scroll) {\n    left += LeftWithScroll(parent);\n    top += TopWithScroll(parent);\n    parent = static_cast<RenderObject*>(parent->Parent());\n  }\n  left += LeftWithScroll(parent_scroll);\n  top += TopWithScroll(parent_scroll);\n  skity::Rect child_rect = skity::Rect::MakeXYWH(\n      left, top, render_object->Width(), render_object->Height());\n\n  return preload_bounds.Intersect(child_rect);\n}\n\n}  // namespace\n\nDecodePriority DecodeUtils::GetDecodePriority(RenderObject* render_object) {\n  if (!render_object) {\n    return DecodePriority::kPending;\n  }\n  if (!render_object->ImageDecodeWithPriority()) {\n    return DecodePriority::kImmediate;\n  }\n  TRACE_EVENT(\"clay\", __PRETTY_FUNCTION__);\n\n  RenderScroll* parent_scroll = nullptr;\n  bool visible = IsInVisibleArea(render_object, &parent_scroll);\n  if (visible) {\n    if (parent_scroll) {\n      // If the parent scroll is in scroll animation, the parent scroll\n      // container will be responsible for decoding its children nodes due to\n      // visibility after animation. So here just return kPending to avoid\n      // unnecessary decode.\n      if (InScrollAnimation(parent_scroll)) {\n        return DecodePriority::kPending;\n      }\n      return DecodePriority::kImmediate;\n    } else {\n      return DecodePriority::kImmediate;\n    }\n  } else {\n    if (parent_scroll) {\n      if (InScrollAnimation(parent_scroll)) {\n        return DecodePriority::kPending;\n      }\n      // If parent scrollable is visible and render object is in preload range,\n      // return kDeferred to decode later.\n      if (IsInPreloadRange(render_object, parent_scroll) &&\n          IsInVisibleArea(parent_scroll, nullptr)) {\n        return DecodePriority::kDeferred;\n      }\n      return DecodePriority::kPending;\n    } else {\n      return DecodePriority::kPending;\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/decode_utils.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_DECODE_UTILS_H_\n#define CLAY_UI_RENDERING_DECODE_UTILS_H_\n\n#include \"clay/gfx/image/decode_priority.h\"\n\nnamespace clay {\n\nclass RenderObject;\n\nclass DecodeUtils {\n public:\n  static DecodePriority GetDecodePriority(RenderObject* render_object);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_DECODE_UTILS_H_\n"
  },
  {
    "path": "clay/ui/rendering/editable/render_editable.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/editable/render_editable.h\"\n\n#include <algorithm>\n#include <cstddef>\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/gfx/style/color.h\"\n#include \"clay/ui/common/utils/floating_comparison.h\"\n#include \"clay/ui/component/editable/text_utils.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/painter/text_painter.h\"\n\nnamespace clay {\n\nnamespace {\n\n#if defined(OS_MAC) || defined(OS_WIN)\nconstexpr float kCaretWidth = 1.f;\n#else\nconstexpr float kCaretWidth = 2.f;\n#endif\nconstexpr float kCaretVerticalPreserveSpace = 2.f;\nconstexpr uint32_t kCaretColor = 0xff000000;\nconstexpr uint32_t kSelectionColor = 0xff90caf9;  // material blue[200]\n\n}  // namespace\n\nRenderEditable::RenderEditable()\n    : painter_(std::make_unique<TextPainter>()), weak_factory_(this) {\n  SetPaddingLeft(2);\n  SetPaddingRight(2);\n}\n\nvoid RenderEditable::SetSelection(const TextRange& selection,\n                                  Affinity selection_affinity) {\n  auto text_editing_value = GetTextEditingValue();\n  if (text_editing_value.selection() == selection &&\n      text_editing_value.SelectionAffinity() == selection_affinity) {\n    return;\n  }\n  text_editing_value.SetSelection(selection);\n  text_editing_value.SetSelectionAffinity(selection_affinity);\n  SetTextEditingValue(text_editing_value);\n  MarkNeedsPaint();\n}\n\nvoid RenderEditable::SetSelection(int base, int extent,\n                                  Affinity selection_affinity) {\n  SetSelection(TextRange(base, extent), selection_affinity);\n}\n\nvoid RenderEditable::Paint(PaintingContext& context, const FloatPoint& offset) {\n  RenderBox::Paint(context, offset);\n\n  auto content_rect = GetFrameRect();\n  content_rect.SetLocation({0, 0});\n  content_rect.SetWidth(content_rect.width() - HorizontalThickness() -\n                        CaretWidth());\n  content_rect.SetHeight(content_rect.height() - VerticalThickness());\n\n  // caret_rect's offset is relative to text.\n  FloatRect caret_rect = ComputeCaretRect();\n  const auto& text_editing_value = GetTextEditingValue();\n  if (need_paint_placeholder_) {\n    EnsurePlaceholderCenterInVertical(content_rect, caret_rect);\n  }\n  EnsureCaretCenterInVertical(content_rect, caret_rect);\n  EnsureCaretInVisibleArea(content_rect, &caret_rect);\n  ClampContent(content_rect, caret_rect);\n  bool show_selection = !text_editing_value.selection().collapsed();\n\n  if (painter_->CanPaint() || display_caret_ || show_selection) {\n    GraphicsContext* graphics_context = context.GetGraphicsContext();\n    GraphicsContext::AutoRestore auto_canvas(graphics_context, true);\n    paint_offset_ = offset + PaintOffset();\n    // Translate to the start of content region.\n    graphics_context->Translate(paint_offset_.x(), paint_offset_.y());\n    graphics_context->ClipRect(\n        skity::Rect::MakeWH(static_cast<int>(content_rect.width()),\n                            static_cast<int>(content_rect.height())),\n        GrClipOp::kIntersect, false);\n\n    // We implement textalign by manipulating the drawing position\n    float x_offset = 0;\n    if (!is_multiline_) {\n      if (text_align_ == TextAlignment::kCenter) {\n        x_offset =\n            std::max(0.0, (content_rect.width() -\n                           painter_->GetParagraph()->GetMaxIntrinsicWidth()) /\n                              2);\n      } else if (text_align_ == TextAlignment::kRight) {\n        x_offset =\n            std::max(0.0, content_rect.width() -\n                              painter_->GetParagraph()->GetMaxIntrinsicWidth() -\n                              2 * offset_for_caret_visible_.x());\n      }\n    }\n\n    if (text_editing_value.empty() && need_paint_placeholder_) {\n      {\n        GraphicsContext::AutoRestore saver(graphics_context, true);\n        graphics_context->Translate(\n            offset_for_placeholder_visible_.x() + x_offset,\n            offset_for_placeholder_visible_.y());\n        if (painter_->CanPaint()) {\n          painter_->SetWidth(content_rect.width());\n          painter_->SetHeight(content_rect.height());\n          painter_->Paint(graphics_context);\n        }\n      }\n      graphics_context->Translate(offset_for_caret_visible_.x(),\n                                  offset_for_caret_visible_.y());\n      if (display_caret_) {\n        PaintCaret(graphics_context, caret_rect);\n        context.SetWillChangeHint();\n      }\n    } else {\n      graphics_context->Translate(offset_for_caret_visible_.x() + x_offset,\n                                  offset_for_caret_visible_.y());\n      if (show_selection) {\n        auto boxes =\n            painter_->GetRectsForRange(text_editing_value.selection().start(),\n                                       text_editing_value.selection().end(),\n                                       txt::Paragraph::RectHeightStyle::kMax,\n                                       txt::Paragraph::RectWidthStyle::kMax);\n        PaintSelection(graphics_context, boxes);\n      } else if (display_caret_) {\n        PaintCaret(graphics_context, caret_rect);\n        context.SetWillChangeHint();\n      }\n      if (painter_->CanPaint()) {\n        painter_->SetWidth(content_rect.width());\n        painter_->SetHeight(content_rect.height());\n        painter_->Paint(graphics_context);\n      }\n    }\n  }\n\n  RenderBox::PaintChildren(context, offset);\n\n  if (controller_) {\n    controller_->PostPaint();\n  }\n}\n\nvoid RenderEditable::PaintCaret(GraphicsContext* context,\n                                const FloatRect& caret_rect) {\n  class Paint paint;\n  paint.setColor(caret_color_ ? caret_color_->Value() : kCaretColor);\n  context->DrawRect(caret_rect, paint);\n}\n\n// Input: boxes - list of bounding boxes for each glyph\n// Output: res - list of updated bounding boxes for each line\nvoid RenderEditable::UpdateSelectionForMultiLine(\n    const std::vector<TextBox>& boxes, std::vector<skity::Rect>& res) {\n  // TODO(haochen): RTL support\n  if (text_direction_ == TextDirection::kRtl) {\n    std::transform(boxes.begin(), boxes.end(), res.begin(),\n                   [](TextBox box) -> skity::Rect { return box.rect; });\n    return;\n  }\n  for (auto& box : boxes) {\n    // first line\n    if (res.empty()) {\n      res.emplace_back(box.rect);\n      continue;\n    }\n    auto& last_box = res.back();\n    // same line\n    if (skity::Rect rect = box.rect; RoughlyEqual(last_box.Top(), rect.Top())) {\n      last_box.Join(rect);\n      continue;\n    }\n    // new line\n    if (float t = GetFrameRect().MaxX(); last_box.Right() < t) {\n      last_box.SetRight(t);\n    }\n    res.emplace_back(box.rect);\n  }\n}\n\nvoid RenderEditable::PaintSelection(GraphicsContext* context,\n                                    const std::vector<TextBox>& boxes) {\n  class Paint paint;\n  paint.setColor(kSelectionColor);\n  std::vector<skity::Rect> paint_boxes;\n  UpdateSelectionForMultiLine(boxes, paint_boxes);\n  for (const auto& box : paint_boxes) {\n    context->DrawRect(box, paint);\n  }\n}\n\nFloatRect RenderEditable::ComputeCaretRect() {\n  // Default draw a vertical line.\n  float caret_height =\n      std::min(GetRoughTextLineHeight(), static_cast<float>(ContentHeight())) -\n      2 * CaretVerticalPreserveSpace();\n  const FloatRect caret_proto =\n      FloatRect(0, CaretVerticalPreserveSpace(), CaretWidth(), caret_height);\n  const auto& text_editing_value = GetTextEditingValue();\n  int caret_offset = text_editing_value.selection().extent();\n  if (text_direction_ == TextDirection::kRtl &&\n      text_align_ != TextAlignment::kLeft) {\n    caret_offset = caret_offset - 1;\n  }\n  if (!painter_ || text_editing_value.GetU16Length() == 0) {\n    return caret_proto;\n  }\n\n  FloatRect caret_rect = caret_proto;\n  const auto& text = text_editing_value.GetU16Text();\n  // TODO(yulitao): support affinity.\n  bool upstream =\n      text_editing_value.SelectionAffinity() == Affinity::kUpstream &&\n      !TextUtils::IsNewLine(text.at(std::max(0, caret_offset - 1)));\n  if (upstream) {\n    // If caret is at position 0, there's no upstream glyphs.\n    // So need fallback to downstream.\n    if (!UpdateCaretRectUpstream(caret_offset, caret_rect)) {\n      UpdateCaretRectDownstream(caret_offset, caret_rect);\n    }\n  } else if (!UpdateCaretRectDownstream(caret_offset, caret_rect)) {\n    // If caret is at the end, there's no downstream glyphs. Fallback.\n    UpdateCaretRectUpstream(caret_offset, caret_rect);\n  }\n  return caret_rect;\n}\n\nFloatRect RenderEditable::ComputeCaretRectRelativeToCanvas() {\n  FloatRect rect = ComputeCaretRect();\n  // Clip invisible region to fix coordinates of caret.\n  auto correct_rect_x = rect.x() + offset_for_caret_visible_.x();\n  auto correct_rect_y = rect.y() + offset_for_caret_visible_.y();\n  return FloatRect(correct_rect_x + paint_offset_.x(),\n                   correct_rect_y + paint_offset_.y(), rect.width(),\n                   rect.height());\n}\n\nFloatRect RenderEditable::ComputeComposingRect(TextRange composing_range) {\n  auto caret_rect = ComputeCaretRectRelativeToCanvas();\n  if (composing_range.collapsed()) {\n    return caret_rect;\n  }\n  auto boxes = painter_->GetRectsForRange(composing_range.start(),\n                                          composing_range.end());\n  if (boxes.size() == 0) {\n    return caret_rect;\n  }\n  FloatRect rect = boxes[0].rect;\n  for (auto& box : boxes) {\n    rect.ExpandToInclude(box.rect);\n  }\n  float x_offset = 0;\n  if (!is_multiline_) {\n    if (text_align_ == TextAlignment::kCenter) {\n      x_offset = std::max(\n          0.0,\n          (ContentWidth() - painter_->GetParagraph()->GetLongestLine()) / 2);\n    } else if (text_align_ == TextAlignment::kRight) {\n      x_offset = std::max(0.0, ContentWidth() -\n                                   painter_->GetParagraph()->GetLongestLine() -\n                                   CaretWidth());\n    }\n  }\n  return FloatRect(\n      rect.x() + x_offset + paint_offset_.x() + offset_for_caret_visible_.x(),\n      rect.y() + paint_offset_.y() + offset_for_caret_visible_.y(),\n      rect.width(), rect.height());\n}\n\nvoid RenderEditable::EnsurePlaceholderCenterInVertical(const FloatRect& content,\n                                                       const FloatRect& caret) {\n  // centering the text and  cursor in the input type\n  if (!is_multiline_) {\n    FloatPoint offset_for_placeholder_visible(\n        0, (content.height() - GetPlaceholderLineHeight()) / 2.0f);\n    offset_for_placeholder_visible_.MoveBy(\n        last_offset_for_placeholder_visible_);\n    offset_for_placeholder_visible_.MoveBy(offset_for_placeholder_visible);\n    last_offset_for_placeholder_visible_ =\n        FloatPoint(-offset_for_placeholder_visible.x(),\n                   -offset_for_placeholder_visible.y());\n  }\n}\n\nvoid RenderEditable::EnsureCaretCenterInVertical(const FloatRect& content,\n                                                 const FloatRect& caret) {\n  // centering the text and  cursor in the input type\n  if (!is_multiline_) {\n    FloatPoint offset_for_caret_visible(\n        0, (content.height() - GetRoughTextLineHeight()) / 2.0f);\n    offset_for_caret_visible_.MoveBy(last_offset_for_caret_visible_);\n    offset_for_caret_visible_.MoveBy(offset_for_caret_visible);\n    last_offset_for_caret_visible_ = FloatPoint(-offset_for_caret_visible.x(),\n                                                -offset_for_caret_visible.y());\n  }\n}\n\nvoid RenderEditable::EnsureCaretInVisibleArea(const FloatRect& content,\n                                              FloatRect* caret) {\n  FloatRect current =\n      FloatRect(content.x() - offset_for_caret_visible_.x(),\n                content.y() - offset_for_caret_visible_.y(),\n                content.width() - 2 * CaretWidth(), content.height());\n  if (!is_multiline_) {\n    // move caret to visible\n    if (caret->x() < current.x()) {\n      offset_for_caret_visible_.Move(\n          std::max(0.f, std::min(current.x() - caret->x(),\n                                 current.MaxX() - caret->MaxX())),\n          0.f);\n    } else if (caret->MaxX() > current.MaxX()) {\n      offset_for_caret_visible_.Move(\n          std::min(0.f, std::max(current.x() - caret->x(),\n                                 current.MaxX() - caret->MaxX())),\n          0.f);\n    }\n    if (caret->y() < current.y()) {\n      offset_for_caret_visible_.Move(\n          0.f, std::max(0.f, std::min(current.y() - caret->y(),\n                                      current.MaxY() - caret->MaxY())));\n    } else if (caret->MaxY() > current.MaxY()) {\n      offset_for_caret_visible_.Move(\n          0.f, std::min(0.f, std::max(current.y() - caret->y(),\n                                      current.MaxY() - caret->MaxY())));\n    }\n  } else {\n    if (caret->x() > current.MaxX() - caret->width()) {\n      caret->SetX(current.MaxX() - caret->width());\n    }\n  }\n}\n\nvoid RenderEditable::ClampContent(const FloatRect& content,\n                                  const FloatRect& caret) {\n  if (is_multiline_) {\n    return;\n  }\n  // [offset.x(), offset.x() + content.width()] in [-caret.width(),\n  // paragraph.width() + caret.width()]\n  float offset_x = std::max(\n      std::clamp(\n          -offset_for_caret_visible_.x() + content.width(), -caret.width(),\n          static_cast<float>(painter_->GetParagraph()->GetMaxIntrinsicWidth() +\n                             caret.width())) -\n          content.width(),\n      -caret.width());\n  offset_for_caret_visible_.SetX(-offset_x);\n}\n\n// Update offset onto |caret_rect|. If update failed, nothing changes.\nbool RenderEditable::UpdateCaretRectUpstream(int caret_offset,\n                                             FloatRect& caret_rect) {\n  // TODO(yulitao): quick reject\n  if (caret_offset == 0) {\n    return false;\n  }\n  FML_CHECK(painter_);\n\n  auto text = GetTextEditingValue().GetU16Text();\n  int length = text.length();\n  size_t prev_code_unit = text.at(std::clamp(caret_offset - 1, 0, length - 1));\n  // If the upstream character is a newline, cursor is at start of next line\n  const int NEWLINE_CODE_UNIT = 10;\n  bool needs_search = TextUtils::IsHighSurrogate(prev_code_unit) ||\n                      TextUtils::IsLowSurrogate(prev_code_unit) ||\n                      text.at(std::clamp(caret_offset, 0, length - 1)) ==\n                          TextUtils::kZWJUtf16 ||\n                      TextUtils::IsUnicodeDirectionality(prev_code_unit);\n  int graphemeClusterLength = needs_search ? 2 : 1;\n  std::vector<TextBox> boxes;\n  while (boxes.empty()) {\n    int prev_offset = std::max(caret_offset - graphemeClusterLength, 0);\n    // As code point is not the minimum draw unit, so given 1 or 2 code point\n    // there may not be a complete box.\n    boxes = painter_->GetRectsForRange(prev_offset, caret_offset,\n                                       RectHeightStyle::kMax);\n    if (boxes.empty()) {\n      if (!needs_search && prev_code_unit == NEWLINE_CODE_UNIT) {\n        // Only perform one iteration if no search is required.\n        break;\n      }\n      if (prev_offset <= 0) {\n        // Stop iterating when beyond the max length of the text.\n        break;\n      }\n      graphemeClusterLength *= 2;\n      continue;\n    }\n  }\n\n  if (boxes.empty()) {\n    return false;\n  }\n\n  // Get the closest box to caret.\n  const auto& box = boxes[boxes.size() - 1];\n  // TODO(yulitao): assume LTR. fix it after text directional is done.\n  UpdateCaretRectBesideBox(caret_rect, box.rect, true);\n  return true;\n}\n\nbool RenderEditable::UpdateCaretRectDownstream(int caret_offset,\n                                               FloatRect& caret_rect) {\n  FML_CHECK(painter_);\n\n  auto text = GetTextEditingValue().GetU16Text();\n  size_t next_code_unit =\n      text.at(std::clamp(caret_offset, 0, static_cast<int>(text.length() - 1)));\n  bool needs_search = TextUtils::IsHighSurrogate(next_code_unit) ||\n                      TextUtils::IsLowSurrogate(next_code_unit) ||\n                      next_code_unit == TextUtils::kZWJUtf16 ||\n                      TextUtils::IsUnicodeDirectionality(next_code_unit);\n  int graphemeClusterLength = needs_search ? 2 : 1;\n  std::vector<TextBox> boxes;\n  while (boxes.empty()) {\n    // TODO(yulitao): First check whether next_offset exceeds length.\n    size_t next_offset =\n        static_cast<size_t>(caret_offset + graphemeClusterLength);\n    boxes = painter_->GetRectsForRange(caret_offset, next_offset,\n                                       RectHeightStyle::kStrut);\n    if (boxes.empty()) {\n      if (!needs_search) {\n        // Only perform one iteration if no search is required.\n        break;\n      }\n      if (next_offset >= text.length()) {\n        // Stop iterating when beyond the max length of the text.\n        break;\n      }\n      // Multiply by two to log(n) time cover the entire text span. This allows\n      // faster discovery of very long clusters and reduces the possibility\n      // of certain large clusters taking much longer than others, which can\n      // cause jank.\n      graphemeClusterLength *= 2;\n      continue;\n    }\n  }\n\n  if (boxes.empty()) {\n    return false;\n  }\n\n  const auto& box = boxes[0];\n  UpdateCaretRectBesideBox(caret_rect, box.rect, false);\n  return true;\n}\n\n// Get line height for current caret offset.\nfloat RenderEditable::GetRoughTextLineHeight() {\n  auto line_height = rough_text_height_;\n  if (!line_height) {\n    return default_line_height_;\n  }\n  return line_height;\n}\n\nfloat RenderEditable::GetPlaceholderLineHeight() {\n  auto placeholder_line_height = placeholder_line_height_;\n  if (!placeholder_line_height) {\n    return default_line_height_;\n  }\n  return placeholder_line_height;\n}\n\nvoid RenderEditable::SetMaxLines(uint32_t max_lines) { max_lines_ = max_lines; }\n\nfloat RenderEditable::CaretWidth() const {\n  return renderer_->ConvertFrom<kPixelTypeLogical>(kCaretWidth);\n}\n\nfloat RenderEditable::CaretVerticalPreserveSpace() const {\n  return renderer_->ConvertFrom<kPixelTypeLogical>(kCaretVerticalPreserveSpace);\n}\n\nvoid RenderEditable::SelectWord(FloatPoint point) {\n  point.Move(-PaddingLeft() - BorderLeft(), -PaddingTop() - BorderTop());\n  point.Move(-offset_for_caret_visible_.x(), -offset_for_caret_visible_.y());\n  TextPosWithAffinity pair =\n      painter_->GetGlyphPositionAtCoordinate(point.x(), point.y());\n  const auto& word_range = painter_->GetWordBoundary(pair.first);\n  if (!word_range.collapsed()) {\n    SetSelection(word_range.start(), word_range.end(), pair.second);\n  }\n}\n\nvoid RenderEditable::SelectLine(FloatPoint point) {\n  point.Move(-PaddingLeft() - BorderLeft(), -PaddingTop() - BorderTop());\n  point.Move(-offset_for_caret_visible_.x(), -offset_for_caret_visible_.y());\n  TextPosWithAffinity pair =\n      painter_->GetGlyphPositionAtCoordinate(point.x(), point.y());\n  const auto& line_range = painter_->GetLineRangeForPosition(pair.first);\n  if (!line_range.collapsed()) {\n    SetSelection(line_range.start(), line_range.end());\n  }\n}\n\nvoid RenderEditable::UpdateCaretByCoordinate(const FloatPoint& point,\n                                             bool is_select) {\n  FloatPoint pointer_in_paragraph = point;\n  pointer_in_paragraph.Move(-PaddingLeft() - BorderLeft(),\n                            -PaddingTop() - BorderTop());\n  pointer_in_paragraph.Move(-offset_for_caret_visible_.x(),\n                            -offset_for_caret_visible_.y());\n  UpdateCaretByParagraph(pointer_in_paragraph, is_select);\n}\n\nvoid RenderEditable::UpdateCaretByParagraph(const FloatPoint& point,\n                                            bool is_select) {\n  for (const auto& box : painter_->GetRectsForPlaceholders()) {\n    if (box.rect.Contains(point)) {\n      // TODO(yulitao): Currently editable doesn't support placeholders.\n      return;\n    }\n  }\n  // Because we offset the drawing, clicks also need to be processed.\n  float x_offset = 0;\n  if (!GetTextEditingValue().empty() && !is_multiline_) {\n    if (text_align_ == TextAlignment::kCenter) {\n      x_offset = std::max(\n          0.0,\n          (ContentWidth() - painter_->GetParagraph()->GetLongestLine()) / 2);\n    } else if (text_align_ == TextAlignment::kRight) {\n      x_offset = std::max(0.0, ContentWidth() -\n                                   painter_->GetParagraph()->GetLongestLine() -\n                                   PaddingLeft() - BorderLeft() -\n                                   offset_for_caret_visible_.x());\n    }\n  }\n  TextPosWithAffinity pair =\n      painter_->GetGlyphPositionAtCoordinate(point.x() - x_offset, point.y());\n  // TODO(yulitao): Deal with affinity when support RTL.\n  if (is_select) {\n    SetSelection(\n        TextRange(GetTextEditingValue().selection().base(), pair.first),\n        pair.second);\n  } else {\n    SetSelection(TextRange(pair.first), pair.second);\n  }\n}\n\nvoid RenderEditable::MoveCaretUpDown(VerticalDirection direction,\n                                     bool key_combination) {\n  auto height = painter_->GetUpDownLineHeightForPosition(\n      std::make_pair(\n          static_cast<size_t>(GetTextEditingValue().selection().extent()),\n          text_editing_controller_->GetValue().SelectionAffinity()),\n      direction);\n  bool is_select = !GetTextEditingValue().selection().collapsed();\n  if (!height && !is_select) {\n    // No up or down line.\n    return;\n  }\n  FloatRect caret_rect;\n  if (is_select && !key_combination) {\n    auto up_position = std::min(GetTextEditingValue().selection().base(),\n                                GetTextEditingValue().selection().extent());\n    float caret_height = std::min(GetRoughTextLineHeight(),\n                                  static_cast<float>(ContentHeight())) -\n                         2 * CaretVerticalPreserveSpace();\n    caret_rect =\n        FloatRect(0, CaretVerticalPreserveSpace(), CaretWidth(), caret_height);\n    FloatRect down_rect = caret_rect;\n    UpdateCaretRectDownstream(up_position, caret_rect);\n    if (direction == VerticalDirection::kDown) {\n      auto down_position = std::max(GetTextEditingValue().selection().base(),\n                                    GetTextEditingValue().selection().extent());\n      UpdateCaretRectUpstream(down_position, down_rect);\n      caret_rect.SetY(down_rect.y());\n    }\n  } else {\n    caret_rect = ComputeCaretRect();\n  }\n  FloatPoint new_position(caret_rect.x(), 0.f);\n  if (direction == VerticalDirection::kUp) {\n    new_position.SetY(caret_rect.y() - height / 2);\n  } else {\n    new_position.SetY(caret_rect.MaxY() + height / 2);\n  }\n  UpdateCaretByParagraph(new_position, key_combination);\n}\n\nvoid RenderEditable::SetCaretDisplay(bool display) {\n  if (display_caret_ != display) {\n    display_caret_ = display;\n    MarkNeedsPaint();\n  }\n}\n\nvoid RenderEditable::SetDisabled(bool disabled) {\n  if (disabled_ != disabled) {\n    disabled_ = disabled;\n    MarkNeedsPaint();\n  }\n}\n\nvoid RenderEditable::SetCaretColor(const Color& color) {\n  caret_color_ = color;\n  MarkNeedsPaint();\n}\n\nvoid RenderEditable::UpdateCaretRectBesideBox(FloatRect& caret_rect,\n                                              const FloatRect& box_nearby,\n                                              bool caret_after_box) {\n  float box_nearby_height =\n      std::min(box_nearby.height(), static_cast<float>(ContentHeight()));\n  caret_rect.SetHeight(box_nearby_height);\n  caret_rect.SetY(box_nearby.y());\n  if (caret_after_box) {\n    caret_rect.SetX(box_nearby.MaxX());\n  } else {\n    caret_rect.SetX(box_nearby.x());\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/editable/render_editable.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_EDITABLE_RENDER_EDITABLE_H_\n#define CLAY_UI_RENDERING_EDITABLE_RENDER_EDITABLE_H_\n\n#include <limits>\n#include <memory>\n#include <optional>\n#include <vector>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/ui/common/editing_misc.h\"\n#include \"clay/ui/common/text_input_type_traits.h\"\n#include \"clay/ui/component/editable/text_editing_controller.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/painter/text_painter.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace txt {\nclass Paragraph;\n}\n\nnamespace clay {\n\nclass PaintingContext;\nclass TextInputControllerDelegate;\n\nclass RenderEditable : public RenderBox {\n public:\n  RenderEditable();\n\n  const char* GetName() const override { return \"RenderEditable\"; }\n  void SetTextEditingController(TextEditingController* controller) {\n    text_editing_controller_ = controller;\n  }\n\n  void SetTextEditingValue(const TextEditingValue& value) {\n    text_editing_controller_->SetValue(value, false, true);\n  }\n\n  const TextEditingValue& GetTextEditingValue() {\n    return text_editing_controller_->GetValue();\n  }\n\n  void SetMultiline(bool is_multiline) { is_multiline_ = is_multiline; }\n\n  void SetParagraph(txt::Paragraph* paragraph) {\n    painter_->SetParagraph(paragraph);\n    MarkNeedsPaint();\n  }\n\n  TextRange GetWordBoundary(size_t offset) {\n    return painter_->GetWordBoundary(offset);\n  }\n\n  void SetRoughTextLineHeight(float rough_text_height) {\n    rough_text_height_ = rough_text_height;\n    MarkNeedsPaint();\n  }\n\n  void SetPlaceholderLineHeight(float placeholder_line_height) {\n    need_paint_placeholder_ = true;\n    placeholder_line_height_ = placeholder_line_height;\n    MarkNeedsPaint();\n  }\n\n  void Paint(PaintingContext& context, const FloatPoint& offset) override;\n\n  void SetSelection(const TextRange& selection,\n                    Affinity selection_affinity = Affinity::kDownstream);\n  void SetSelection(int base, int extent,\n                    Affinity selection_affinity = Affinity::kDownstream);\n  void SelectWord(FloatPoint point);\n  void SelectLine(FloatPoint point);\n\n  bool IsRepaintBoundary() const override { return true; }\n\n  // |point| is relative to self.\n  void UpdateCaretByCoordinate(const FloatPoint& point, bool is_select = false);\n  void MoveCaretUpDown(VerticalDirection direction,\n                       bool key_combination = false);\n\n  bool Disabled() { return disabled_; }\n  void SetDisabled(bool disabled);\n\n  void SetCaretDisplay(bool display);\n\n  void SetDefaultLineHeight(float default_height) {\n    default_line_height_ = default_height;\n  }\n\n  void SetTextAlign(TextAlignment text_align) { text_align_ = text_align; }\n  void SetTextDirection(TextDirection text_direction) {\n    text_direction_ = text_direction;\n  }\n\n  void SetMaxLines(uint32_t max_lines);\n\n  float GetRoughTextLineHeight();\n\n  float GetPlaceholderLineHeight();\n\n  void SetCaretColor(const Color& color);\n\n  float CaretWidth() const;\n\n  // compute caret position offset by glyph offset.\n  FloatRect ComputeCaretRect();\n  FloatRect ComputeCaretRectRelativeToCanvas();\n\n  FloatRect ComputeComposingRect(TextRange composing_range);\n  void SetTextInputControllerDelegate(TextInputControllerDelegate* controller) {\n    controller_ = controller;\n  }\n\n  void EnsurePlaceholderCenterInVertical(const FloatRect& content,\n                                         const FloatRect& caret);\n  void EnsureCaretCenterInVertical(const FloatRect& content,\n                                   const FloatRect& caret);\n  void EnsureCaretInVisibleArea(const FloatRect& content, FloatRect* caret);\n  void ClampContent(const FloatRect& content, const FloatRect& caret);\n\n private:\n  void PaintCaret(GraphicsContext* context, const FloatRect& caret_rect);\n  void PaintSelection(GraphicsContext* context,\n                      const std::vector<TextBox>& boxes);\n  void UpdateSelectionForMultiLine(const std::vector<TextBox>& boxes,\n                                   std::vector<skity::Rect>& res);\n\n  // |caret_rect| is inited as caret proto metrics with zero offset.\n  // |caret_offset| is the code point offset from start of text.\n  bool UpdateCaretRectUpstream(int caret_offset, FloatRect& caret_rect);\n  bool UpdateCaretRectDownstream(int caret_offset, FloatRect& caret_rect);\n  // |point| is relative to paragraph.\n  void UpdateCaretByParagraph(const FloatPoint& point, bool is_select = false);\n\n  void UpdateCaretRectBesideBox(FloatRect& caret_rect,\n                                const FloatRect& box_nearby,\n                                bool caret_after_box);\n\n  float CaretVerticalPreserveSpace() const;\n\n  // TODO(yulitao): Decoupling caret painting logic from editable.\n  std::optional<Color> caret_color_;\n  float rough_text_height_ = 0.f;\n  float placeholder_line_height_ = 0.f;\n  bool display_caret_ = false;\n  bool disabled_ = false;\n  // To ensure caret is in the region of content region, all contents may should\n  // translate some offsets.\n  FloatPoint offset_for_caret_visible_;\n  FloatPoint last_offset_for_caret_visible_;\n  FloatPoint paint_offset_;\n  FloatPoint offset_for_placeholder_visible_;\n  FloatPoint last_offset_for_placeholder_visible_;\n  bool need_paint_placeholder_ = false;\n\n  float default_line_height_ = 0.f;\n  uint32_t max_lines_ = std::numeric_limits<uint32_t>::max();\n\n  TextEditingController* text_editing_controller_ = nullptr;\n  std::unique_ptr<TextPainter> painter_;\n  TextAlignment text_align_ = TextAlignment::kLeft;\n  TextDirection text_direction_ = TextDirection::kLtr;\n\n  fml::WeakPtrFactory<RenderEditable> weak_factory_;\n  TextInputControllerDelegate* controller_ = nullptr;\n  bool is_multiline_ = false;\n};\n\nclass TextInputControllerDelegate {\n public:\n  virtual void UpdateRemoteStateIfNeeded(const TextEditingValue&) = 0;\n\n  virtual void PostPaint() = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_EDITABLE_RENDER_EDITABLE_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_bounce.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_BOUNCE_H_\n#define CLAY_UI_RENDERING_RENDER_BOUNCE_H_\n\n#include \"clay/ui/rendering/render_container.h\"\n\nnamespace clay {\n\nclass RenderBounce : public RenderContainer {\n public:\n  RenderBounce() = default;\n  ~RenderBounce() override = default;\n\n  const char* GetName() const override { return \"RenderBounce\"; }\n\n  bool IsRenderBounceView() const override { return true; }\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_RENDERING_RENDER_BOUNCE_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_box.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_box.h\"\n\n#include <algorithm>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/image/image.h\"\n#include \"clay/ui/common/background_data.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/painter/box_painter.h\"\n#include \"clay/ui/painter/image_painter.h\"\n#include \"clay/ui/rendering/renderer.h\"\n\nnamespace clay {\n\nconst char* RenderBox::GetName() const { return \"RenderBox\"; }\n\nfloat RenderBox::ClientWidth() const {\n  return Width() - BorderLeft() - BorderRight();\n}\n\nfloat RenderBox::ClientHeight() const {\n  return Height() - BorderTop() - BorderBottom();\n}\n\nfloat RenderBox::ScrollWidth() const {\n  return std::max(ClientWidth(), OverflowRect().MaxX() - BorderLeft());\n}\n\nfloat RenderBox::ScrollHeight() const {\n  return std::max(ClientHeight(), OverflowRect().MaxY() - BorderTop());\n}\n\nvoid RenderBox::SetScrollLeft(float scroll_left,\n                              bool ignore_repaint /* = false */) {\n  scroll_left_ = scroll_left;\n  if (!ignore_repaint) {\n    MarkNeedsPaint();\n  }\n}\n\nvoid RenderBox::SetScrollTop(float scroll_top,\n                             bool ignore_repaint /* = false */) {\n  scroll_top_ = scroll_top;\n  if (!ignore_repaint) {\n    MarkNeedsPaint();\n  }\n}\n\nbool RenderBox::ScrollsOverflowX() const {\n  // TODO(jinsong): Support scrolls overflow.\n  return false;\n}\n\nbool RenderBox::ScrollsOverflowY() const {\n  // TODO(jinsong): Support scrolls overflow.\n  return false;\n}\n\nfloat RenderBox::MaxScrollHeight() const {\n  float max_height = overflow_rect_.height() - ClientHeight();\n  return std::max(max_height, 0.f);\n}\n\nfloat RenderBox::MaxScrollWidth() const {\n  float max_width = overflow_rect_.width() - ClientWidth();\n  return std::max(max_width, 0.f);\n}\n\nvoid RenderBox::Paint(PaintingContext& context, const FloatPoint& offset) {\n  if (!CanDisplay()) {\n    return;\n  }\n\n  BoxPainter(this).Paint(context.GetGraphicsContext(), offset);\n}\n\nvoid RenderBox::PaintChildren(PaintingContext& context,\n                              const FloatPoint& offset) {\n  if (!CanDisplay()) {\n    return;\n  }\n\n  DoPaintChildren(context, offset);\n}\n\nFloatPoint RenderBox::GetPaintOffsetForScroll() const {\n  if (renderer_ == nullptr) {\n    return {0.0f, 0.0f};\n  }\n  FloatPoint offset = FloatPoint(ScrollLeft(), ScrollTop());\n  // To improve the TextBlob cache hit rate, round the scroll offset here.\n  // Because the TextBlob cache only works when the translation between the\n  // positionMatrix is integer.\n  // See: third_party/skia/src/text/gpu/TextBlob.cpp#can_use_direct\n  offset = FloatPoint(renderer_->RoundPixels(offset.x()),\n                      renderer_->RoundPixels(offset.y()));\n  return offset;\n}\n\nvoid RenderBox::DoPaintChildren(PaintingContext& context,\n                                const FloatPoint& offset) {\n  auto visitor = [&](RenderObject* child) {\n    if (child->IsOverlay() && child->Visible()) {\n      renderer_->AddOverlayChild(child);\n      // Overlay won't apply offset from parent.\n      context.PaintChild(child, child->OffsetInLayer());\n    } else {\n      FloatPoint layer_offset;\n      layer_offset = offset - GetPaintOffsetForScroll();\n      context.PaintChild(child, layer_offset + child->OffsetInLayer());\n    }\n  };\n  VisitChildren(visitor);\n}\n\nvoid RenderBox::AddOverflowFromChildren() {\n  overflow_rect_ = {};\n  for (RenderObject* child = SlowFirstChild(); child != nullptr;\n       child = child->NextSibling()) {\n    if (!child->IsRenderBounceView()) {\n      auto child_rect = child->GetFrameRect();\n      child_rect.Expand(child->MarginTop(), child->MarginRight(),\n                        child->MarginBottom(), child->MarginLeft());\n      overflow_rect_.ExpandToInclude(child_rect, true);\n    }\n  }\n\n  overflow_rect_.ShiftMaxXEdgeTo(overflow_rect_.MaxX() + PaddingRight());\n  overflow_rect_.ShiftMaxYEdgeTo(overflow_rect_.MaxY() + PaddingBottom());\n\n  overflow_rect_.ExpandToInclude(PaddingBoxRect(), true);\n}\n\nvoid RenderBox::SetOverflowRect(const FloatRect& rect) {\n  overflow_rect_ = rect;\n}\n\n#ifndef NDEBUG\nstd::string RenderBox::ToString() const {\n  std::stringstream ss;\n  ss << RenderObject::ToString();\n  ss << \" scroll_size=(\" << ScrollWidth() << \",\" << ScrollHeight() << \")\";\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_box.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_BOX_H_\n#define CLAY_UI_RENDERING_RENDER_BOX_H_\n\n#include <optional>\n#include <string>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/render_object.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest_prod.h\"  // nogncheck\n\nnamespace clay {\n\n// A render object in a 2D Cartesian coordinate system.\nclass RenderBox : public RenderObject {\n public:\n  RenderBox() = default;\n  virtual ~RenderBox() = default;\n\n  const char* GetName() const override;\n\n  FloatSize size() const { return FloatSize(Width(), Height()); }\n\n  FloatRect BorderBoxRect() const { return FloatRect({}, size()); }\n  FloatRect PaddingBoxRect() const {\n    return FloatRect(BorderLeft(), BorderTop(), ClientWidth(), ClientHeight());\n  }\n\n  // The content area of the box (excludes padding - and intrinsic padding for\n  // table cells, etc... - and border).\n  FloatRect ContentBoxRect() const {\n    return FloatRect(BorderLeft() + PaddingLeft(), BorderTop() + PaddingTop(),\n                     ContentWidth(), ContentHeight());\n  }\n\n  // The padding area.\n  FloatRect PaddingRect() const {\n    FloatRect frame_rect = GetFrameRect();\n    return FloatRect(frame_rect.x() + BorderLeft(),\n                     frame_rect.y() + BorderTop(), ClientWidth(),\n                     ClientHeight());\n  }\n\n  // The content area.\n  FloatRect ContentRect() const {\n    FloatRect frame_rect = GetFrameRect();\n    return FloatRect(frame_rect.x() + BorderLeft() + PaddingLeft(),\n                     frame_rect.y() + BorderTop() + PaddingTop(),\n                     ContentWidth(), ContentHeight());\n  }\n\n  FloatRect ClientRect() const {\n    FloatRect frame_rect = GetFrameRect();\n    return FloatRect(frame_rect.x() + BorderLeft(),\n                     frame_rect.y() + BorderTop(), ClientWidth(),\n                     ClientHeight());\n  }\n\n  float ContentWidth() const {\n    return ClientWidth() - PaddingLeft() - PaddingRight();\n  }\n  float ContentHeight() const {\n    return ClientHeight() - PaddingTop() - PaddingBottom();\n  }\n\n  // ClientWidth and ClientHeight represent the interior of an object excluding\n  // border and scrollbar.  ClientLeft/Top are just the BorderLeftWidth and\n  // BorderTopWidth.\n  float ClientLeft() const { return BorderLeft(); }\n  float ClientTop() const { return BorderTop(); }\n  float ClientWidth() const;\n  float ClientHeight() const;\n  FloatRect ClientBoxRect() const {\n    return FloatRect(ClientLeft(), ClientTop(), ClientWidth(), ClientHeight());\n  }\n\n  FloatPoint ScrollOffset() const {\n    return FloatPoint(scroll_left_, scroll_top_);\n  }\n  float ScrollLeft() const override { return scroll_left_; }\n  float ScrollTop() const override { return scroll_top_; }\n  float ScrollWidth() const;\n  float ScrollHeight() const;\n  void SetScrollLeft(float scroll_left, bool ignore_repaint = false);\n  void SetScrollTop(float scroll_top, bool ignore_repaint = false);\n\n  const FloatRect& OverflowRect() const { return overflow_rect_; }\n\n  void ResetOverflowRect() {\n    overflow_rect_.SetX(0);\n    overflow_rect_.SetY(0);\n    overflow_rect_.SetWidth(0);\n    overflow_rect_.SetHeight(0);\n  }\n\n  bool ScrollsOverflow() const {\n    return ScrollsOverflowX() || ScrollsOverflowY();\n  }\n\n  bool ScrollsOverflowX() const;\n  bool ScrollsOverflowY() const;\n\n  float MaxScrollHeight() const;\n  float MaxScrollWidth() const;\n\n  void Paint(PaintingContext& context, const FloatPoint& offset) override;\n  void PaintChildren(PaintingContext& context,\n                     const FloatPoint& offset) override;\n\n  FloatPoint GetPaintOffsetForScroll() const;\n\n  void AddOverflowFromChildren();\n  void SetOverflowRect(const FloatRect& rect);\n\n  void SetLayoutDirection(DirectionType layout_direction) {\n    layout_direction_ = layout_direction;\n    MarkNeedsPaint();\n  }\n  DirectionType GetLayoutDirection() { return layout_direction_; }\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  FRIEND_TEST(RenderBoxTest, Overflow);\n\n  void DoPaintChildren(PaintingContext& context, const FloatPoint& offset);\n\n  float scroll_left_ = 0.f;\n  float scroll_top_ = 0.f;\n\n  FloatRect overflow_rect_;\n  std::optional<FloatRect> clip_rect_;\n  DirectionType layout_direction_ = DirectionType::kLtr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_BOX_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_box_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/ui/rendering/render_box.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nnamespace {\n\nstd::unique_ptr<RenderBox> CreateRenderBox(float l, float t, float w, float h) {\n  auto res = std::make_unique<RenderBox>();\n  res->SetLeft(l);\n  res->SetTop(t);\n  res->SetWidth(w);\n  res->SetHeight(h);\n  return res;\n}\n\n}  // namespace\n\nTEST(RenderBoxTest, BoxModel) {\n  std::unique_ptr<RenderBox> obj = std::make_unique<RenderBox>();\n  EXPECT_FALSE(obj->HasBackground());\n  EXPECT_FALSE(obj->HasBorder());\n  EXPECT_EQ(obj->location(), FloatPoint());\n  EXPECT_EQ(obj->Width(), 0.f);\n  EXPECT_EQ(obj->Height(), 0.f);\n\n  obj->SetLeft(10.f);\n  obj->SetTop(10.f);\n  obj->SetWidth(100.f);\n  obj->SetHeight(100.f);\n  obj->SetPaddingLeft(10.f);\n  obj->SetPaddingTop(10.f);\n  obj->SetPaddingRight(20.f);\n  obj->SetPaddingBottom(20.f);\n  EXPECT_EQ(obj->location(), FloatPoint(10.f, 10.f));\n  EXPECT_EQ(obj->Width(), 100.f);\n  EXPECT_EQ(obj->Height(), 100.f);\n  EXPECT_EQ(obj->PaddingLeft(), 10.f);\n  EXPECT_EQ(obj->PaddingTop(), 10.f);\n  EXPECT_EQ(obj->PaddingRight(), 20.f);\n  EXPECT_EQ(obj->PaddingBottom(), 20.f);\n\n  EXPECT_TRUE(FloatRect(obj->BorderBoxRect()) ==\n              FloatRect(0.f, 0.f, 100.f, 100.f));\n  EXPECT_TRUE(FloatRect(obj->PaddingBoxRect()) ==\n              FloatRect(0.f, 0.f, 100.f, 100.f));\n  EXPECT_TRUE(FloatRect(obj->PaddingRect()) ==\n              FloatRect(10.f, 10.f, 100.f, 100.f));\n  EXPECT_TRUE(FloatRect(obj->ContentBoxRect()) ==\n              FloatRect(10.f, 10.f, 70.f, 70.f));\n  EXPECT_TRUE(FloatRect(obj->ContentRect()) ==\n              FloatRect(20.f, 20.f, 70.f, 70.f));\n}\n\nTEST(RenderBoxTest, Overflow) {\n  // No overflow\n  {\n    std::unique_ptr<RenderBox> parent = CreateRenderBox(2.f, 4.f, 10.f, 20.f);\n\n    std::unique_ptr<RenderBox> child1 = CreateRenderBox(1.f, 1.f, 1.f, 1.f);\n    std::unique_ptr<RenderBox> child2 = CreateRenderBox(8.f, 1.f, 1.f, 1.f);\n    std::unique_ptr<RenderBox> child3 = CreateRenderBox(1.f, 18.f, 1.f, 1.f);\n    std::unique_ptr<RenderBox> child4 = CreateRenderBox(8.f, 18.f, 1.f, 1.f);\n    parent->AddChild(child1.get());\n    parent->AddChild(child2.get());\n    parent->AddChild(child3.get());\n    parent->AddChild(child4.get());\n    parent->AddOverflowFromChildren();\n    EXPECT_EQ(parent->overflow_rect_.x(), 0);\n    EXPECT_EQ(parent->overflow_rect_.y(), 0);\n    EXPECT_EQ(parent->overflow_rect_.MaxX(), 10);\n    EXPECT_EQ(parent->overflow_rect_.MaxY(), 20);\n  }\n\n  {\n    std::unique_ptr<RenderBox> parent = CreateRenderBox(2.f, 4.f, 10.f, 20.f);\n\n    std::unique_ptr<RenderBox> child1 = CreateRenderBox(-1.f, 0.f, 1.f, 1.f);\n    std::unique_ptr<RenderBox> child2 = CreateRenderBox(9.f, -1.f, 1.f, 1.f);\n    std::unique_ptr<RenderBox> child3 = CreateRenderBox(10.f, 19.f, 1.f, 1.f);\n    std::unique_ptr<RenderBox> child4 = CreateRenderBox(0.f, 20.f, 1.f, 1.f);\n    parent->AddChild(child1.get());\n    parent->AddChild(child2.get());\n    parent->AddChild(child3.get());\n    parent->AddChild(child4.get());\n    parent->AddOverflowFromChildren();\n    EXPECT_EQ(parent->overflow_rect_.x(), -1);\n    EXPECT_EQ(parent->overflow_rect_.y(), -1);\n    EXPECT_EQ(parent->overflow_rect_.MaxX(), 11);\n    EXPECT_EQ(parent->overflow_rect_.MaxY(), 21);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_container.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_container.h\"\n\n#include <algorithm>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n\nnamespace clay {\nnamespace {\n\n#define RADIUS_X(BORDER, POS_Y, POS_X) BORDER.radius_x_##POS_Y##_##POS_X##_\n#define RADIUS_Y(BORDER, POS_Y, POS_X) BORDER.radius_y_##POS_Y##_##POS_X##_\n#define WIDTH(BORDER, POSITION) BORDER.width_##POSITION##_\n\n#define RADIUS_SIZE(BORDER, POS_Y, POS_X)                                   \\\n  FloatSize(                                                                \\\n      std::max(0.f, RADIUS_X(BORDER, POS_Y, POS_X) - WIDTH(BORDER, POS_X)), \\\n      std::max(0.f, RADIUS_Y(BORDER, POS_Y, POS_X) - WIDTH(BORDER, POS_Y)))\n\nFloatRoundedRect MakeRounded(const FloatRect& rect, const BordersData& border) {\n  return FloatRoundedRect(\n      rect, RADIUS_SIZE(border, top, left), RADIUS_SIZE(border, top, right),\n      RADIUS_SIZE(border, bottom, left), RADIUS_SIZE(border, bottom, right));\n}\n\n}  // namespace\n\nconst char* RenderContainer::GetName() const { return \"RenderContainer\"; }\n\nvoid RenderContainer::Paint(PaintingContext& context,\n                            const FloatPoint& offset) {\n  if (!CanDisplay()) {\n    return;\n  }\n\n  RenderBox::Paint(context, offset);\n  if (HasClipOrOverflowClip()) {\n    auto painter = [this](PaintingContext& ctx,\n                          const FloatPoint& layer_offset) {\n      PaintChildren(ctx, layer_offset);\n    };\n\n    FloatPoint paint_offset = offset + ClipOffset();\n    FloatRect clip_rect(paint_offset.x(), paint_offset.y(), ClientWidth(),\n                        ClientHeight());\n    bool has_radius = false;\n    if (Overflow() == CSSProperty::OVERFLOW_X) {\n      clip_rect.SetWidth(renderer_->GetFrameSize().width() * 2);\n      clip_rect.SetX(clip_rect.x() - renderer_->GetFrameSize().width());\n    } else if (Overflow() == CSSProperty::OVERFLOW_Y) {\n      clip_rect.SetHeight(renderer_->GetFrameSize().height() * 2);\n      clip_rect.SetY(clip_rect.y() - renderer_->GetFrameSize().height());\n    } else if (HasBorder() && Border().HasBorderRadius()) {\n      has_radius = true;\n    }\n    if (has_radius) {\n      context.PushClipRRect(MakeRounded(clip_rect, Border()), offset, painter);\n    } else {\n      context.PushClipRect(clip_rect, offset, painter);\n    }\n  } else {\n    PaintChildren(context, offset);\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_container.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_CONTAINER_H_\n#define CLAY_UI_RENDERING_RENDER_CONTAINER_H_\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\nclass RenderContainer : public RenderBox {\n public:\n  RenderContainer() = default;\n  ~RenderContainer() override = default;\n\n  const char* GetName() const override;\n\n  void Paint(PaintingContext& context, const FloatPoint& offset) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_CONTAINER_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_dummy.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_DUMMY_H_\n#define CLAY_UI_RENDERING_RENDER_DUMMY_H_\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/rendering/render_object.h\"\n\nnamespace clay {\n\n// RenderDummy does NOT paint anything but it is useful to propagate the\n// MarkNeedsPaint() to ancestor.\nclass RenderDummy : public RenderObject {\n public:\n  RenderDummy() = default;\n  virtual ~RenderDummy() = default;\n\n  const char* GetName() const override { return \"Dummy\"; }\n\n  void Paint(PaintingContext& context, const FloatPoint& offset) override {}\n  void PaintChildren(PaintingContext& context,\n                     const FloatPoint& offset) override {}\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_DUMMY_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_external_content.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_external_content.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/compositing/pending_offset_layer.h\"\n#include \"clay/ui/painter/painting_context.h\"\n\nnamespace clay {\n\nRenderExternalContent::RenderExternalContent() = default;\n\nbool RenderExternalContent::HasClip() const {\n  if (has_clip_.has_value()) {\n    return has_clip_.value();\n  }\n  return RenderBox::HasClip();\n}\n\nvoid RenderExternalContent::Paint(PaintingContext& context,\n                                  const FloatPoint& offset) {\n  RenderBox::Paint(context, offset);\n\n  bool draw_external_image =\n      drawable_image_id_.has_value() && drawable_image_id_.value() > -1;\n  bool draw_punch_hole =\n      render_mode_.has_value() && render_mode_.value() == RenderMode::kHybrid;\n\n  [[maybe_unused]] FloatPoint paint_offset = offset + PaintOffset();\n  if (view_id_.has_value()) {\n    FML_DCHECK(!drawable_image_id_.has_value());\n    context.PushLayer(\n        new PendingOffsetLayer({paint_offset.x(), paint_offset.y()}),\n        [&](PaintingContext& ctx, const FloatPoint& layer_offset) {\n          ctx.AddPlatformView(0, 0, ContentWidth(), ContentHeight(),\n                              view_id_.value());\n        },\n        FloatPoint(0, 0));\n  } else if (draw_external_image) {\n    FML_DCHECK(!view_id_.has_value());\n    context.AddDrawableImage(paint_offset.x(), paint_offset.y(), ContentWidth(),\n                             ContentHeight(), drawable_image_id_.value(),\n                             fit_mode_);\n  } else if (draw_punch_hole) {\n    Rect punch_hole_rect(0, 0, ContentWidth(), ContentHeight());\n    context.AddPunchHole(punch_hole_rect);\n  }\n  PaintChildren(context, offset);\n}\n\nvoid RenderExternalContent::SetDrawableImageId(int64_t drawable_image_id) {\n  if (drawable_image_id == drawable_image_id_) {\n    return;\n  }\n  drawable_image_id_ = drawable_image_id;\n  MarkNeedsPaint();\n}\n\nvoid RenderExternalContent::SetViewId(int64_t view_id) {\n  if (view_id_ == view_id) {\n    return;\n  }\n  view_id_ = view_id;\n  MarkNeedsPaint();\n}\n\nvoid RenderExternalContent::SetRenderMode(RenderMode render_mode) {\n  if (render_mode_ == render_mode) {\n    return;\n  }\n  render_mode_ = render_mode;\n  MarkNeedsPaint();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_external_content.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_EXTERNAL_CONTENT_H_\n#define CLAY_UI_RENDERING_RENDER_EXTERNAL_CONTENT_H_\n\n#include \"clay/common/graphics/drawable_image.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\nclass RenderExternalContent : public RenderBox {\n public:\n  enum class RenderMode { kHybrid = 0, kExternalTexture = 1 };\n  RenderExternalContent();\n\n  ~RenderExternalContent() override = default;\n\n  const char* GetName() const override { return \"RenderExternalContent\"; }\n  bool IsRepaintBoundary() const override { return true; }\n  bool HasClip() const override;\n  void SetClip() { has_clip_ = true; }\n\n  void Paint(PaintingContext& context, const FloatPoint& offset) override;\n\n  // For hybrid composition\n  void SetViewId(int64_t view_id);\n  bool HasViewId() const { return view_id_.has_value(); }\n\n  // For drawable image layer\n  void SetDrawableImageId(int64_t drawable_image_id);\n  void SetFitMode(DrawableImage::FitMode fit_mode) { fit_mode_ = fit_mode; }\n\n  // For video\n  void SetRenderMode(RenderMode render_mode);\n  RenderMode GetRenderMode() const { return render_mode_.value(); }\n\n private:\n  DrawableImage::FitMode fit_mode_ = DrawableImage::FitMode::kScaleToFill;\n  std::optional<bool> has_clip_;\n  std::optional<int64_t> view_id_;  // has value if use hybrid composition\n  std::optional<int64_t>\n      drawable_image_id_;  // has value if use drawable image layer\n  // has value if use video\n  std::optional<RenderMode> render_mode_ = RenderMode::kExternalTexture;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_EXTERNAL_CONTENT_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_external_view.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_external_view.h\"\n\n#include <memory>\n\n#include \"clay/ui/compositing/pending_external_view_layer.h\"\n\nnamespace clay {\n\nvoid RenderExternalView::SetBackingSize(skity::Vec2 size) {\n  if (size.x == size_.x && size.y == size_.y) {\n    return;\n  }\n  size_ = skity::Vec2(size.x, size.y);\n  SetContainerLayer(\n      std::make_unique<PendingExternalViewLayer>(element_id(), size_));\n  MarkNeedsPaint(true);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_external_view.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_EXTERNAL_VIEW_H_\n#define CLAY_UI_RENDERING_RENDER_EXTERNAL_VIEW_H_\n\n#include \"clay/ui/rendering/render_container.h\"\n\nnamespace clay {\n\nclass RenderExternalView : public RenderContainer {\n public:\n  RenderExternalView() = default;\n  ~RenderExternalView() override = default;\n\n  const char* GetName() const override { return \"render_external_view\"; }\n  bool IsRepaintBoundary() const override { return true; }\n\n  void SetBackingSize(skity::Vec2 size);\n\n  skity::Vec2 GetSize() const { return size_; }\n\n  bool IsExternalView() const override { return true; }\n\n private:\n  skity::Vec2 size_ = {0, 0};\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_EXTERNAL_VIEW_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_image.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_image.h\"\n\n#include <cstdint>\n#include <string>\n#include <utility>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/graphics_context.h\"\n#include \"clay/gfx/image/image.h\"\n#ifdef ENABLE_SKITY\n#include \"clay/gfx/image/base_image.h\"\n#endif\n\nnamespace clay {\n\nRenderImage::~RenderImage() {\n#ifndef ENABLE_SKITY\n  if (image_resource_) {\n    image_resource_->RemoveImageResourceClient(this);\n  }\n\n  if (pending_image_resource_) {\n    pending_image_resource_->RemoveImageResourceClient(this);\n  }\n\n  if (placeholder_resource_) {\n    placeholder_resource_->RemoveImageResourceClient(this);\n  }\n#endif  // ENABLE_SKITY\n}\n\nconst char* RenderImage::GetName() const { return \"RenderImage\"; }\n\n#ifndef ENABLE_SKITY\nvoid RenderImage::SetImage(std::unique_ptr<ImageResource> resource) {\n  bool resource_is_pending = false;\n\n  if (resource) {\n    resource->AddImageResourceClient(this);\n    Image* image = resource->GetImage();\n\n    // TODO(yudingqian): Set RepaintBoundary here would cause some overlap\n    // problem. Need further research.\n    // Set repaint boundary for animated image, e.g. GIF for performance.\n    // if (!IsRepaintBoundary() && image && !image->IsSingleFrameImage()) {\n    //   SetRepaintBoundary(true);\n    // }\n\n    if (image) {\n      image->SetAutoPlay(auto_play_);\n      image->SetLoopCount(loop_count_);\n    }\n\n    if (image && image->UseTextureBackend()) {\n      // Check if the image has been decoded without triggerring decode\n      auto graphics_image = image->TryGetGraphicsImage();\n      if (graphics_image) {\n        DecodeImageFinish(true, resource->GetUrl());\n      } else if (!graphics_image && !image->UsePromise()) {\n        resource_is_pending = true;\n      }\n    }\n  }\n\n  // Replacing the `image_resource_` immedidately will result in blank screen.\n  // To prevent it, we memoize the new resource to `pending_image_resource_` and\n  // check whether new image decoded success in `RequestRenderImage`\n  if (image_resource_ && resource_is_pending) {\n    pending_image_resource_ = std::move(resource);\n  } else {\n    image_resource_ = std::move(resource);\n  }\n\n  if (!ImageDecodeWithPriority()) {\n    TryDecodeImmediately();\n  }\n\n  // For now there are 3 modes for rendering images:\n  //   1. non-texture mode: Synchronously decode image data and do not upload\n  //   texture to GPU.\n  //   2. promised-texture mode: Asynchronously decode image data and upload\n  //   texture to GPU. Before uploading, we will use promise to synchronously\n  //   wait the decoded data.\n  //   3. async-texture mode: Asynchronously decode image data and upload\n  //   texture to GPU. This is the default mode in most cases for performance.\n  //\n  // In the first 2 modes, we mark dirty immediately, and the image will be\n  // rendered in the next frame.\n  //\n  // In async-texture mode, we need to mark dirty to trigger image decoding\n  // during paint\n  //\n  // For summary, we should always mark dirty when image changes.\n  MarkNeedsPaint();\n\n  AdjustSizeIfNeeded();\n\n  if (image_resource_ && placeholder_resource_) {\n    placeholder_resource_->RemoveImageResourceClient(this);\n    placeholder_resource_.reset();\n  }\n}\n\nvoid RenderImage::SetPlaceholderImage(\n    std::unique_ptr<ImageResource> placeholder_resource) {\n  if (image_resource_) {\n    // Discard placeholder resource if the formal image is ready.\n    return;\n  }\n  placeholder_resource_ = std::move(placeholder_resource);\n  if (placeholder_resource_) {\n    placeholder_resource_->AddImageResourceClient(this);\n  }\n\n  if (!ImageDecodeWithPriority()) {\n    TryDecodeImmediately();\n  }\n\n  MarkNeedsPaint();\n  AdjustSizeIfNeeded();\n}\n#else\nvoid RenderImage::SetImage(std::unique_ptr<BaseImageInstance> image_instance) {\n  if (image_instance) {\n    auto image = image_instance->GetImage();\n    if (image && image->GetType() == ImageType::kAnimated) {\n      auto animated_image = std::static_pointer_cast<AnimatedImage>(image);\n      animated_image->SetAutoPlay(auto_play_);\n      animated_image->SetLoopCount(loop_count_);\n    }\n  }\n\n  image_resource_ = std::move(image_instance);\n\n  MarkNeedsPaint();\n\n  AdjustSizeIfNeeded();\n\n  if (image_resource_ && placeholder_resource_) {\n    placeholder_resource_.reset();\n  }\n}\n\nvoid RenderImage::SetPlaceholderImage(\n    std::unique_ptr<BaseImageInstance> placeholder_resource) {\n  if (image_resource_) {\n    // Discard placeholder resource if the formal image is ready.\n    return;\n  }\n  placeholder_resource_ = std::move(placeholder_resource);\n  MarkNeedsPaint();\n  AdjustSizeIfNeeded();\n}\n#endif  // ENABLE_SKITY\n\nvoid RenderImage::SetMode(FillMode mode) {\n  if (mode_ == mode) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  mode_ = mode;\n}\n\nvoid RenderImage::SetEffect(ImageEffect effect) {\n  if (effect_ == effect) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  effect_ = effect;\n}\n\nvoid RenderImage::SetRepeat(ImageRepeat repeat) {\n  if (repeat_ == repeat) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  repeat_ = repeat;\n}\n\nvoid RenderImage::SetDropShadow(float offset_x, float offset_y,\n                                float blur_radius, const GrColor& color) {\n  if (drop_shadow_offset_x_ == offset_x && drop_shadow_offset_y_ == offset_y &&\n      drop_shadow_blur_radius_ == blur_radius && drop_shadow_color_ == color) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  drop_shadow_offset_x_ = offset_x;\n  drop_shadow_offset_y_ = offset_y;\n  drop_shadow_blur_radius_ = blur_radius;\n  drop_shadow_color_ = color;\n}\n\nvoid RenderImage::SetCapInsets(const std::array<Length, 4>& cap_insets) {\n  if (cap_insets_ == cap_insets) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  cap_insets_ = cap_insets;\n  has_cap_insets_ = HasCapInsets();\n}\n\nvoid RenderImage::SetCapInsetsScale(float scale) {\n  if (cap_insets_scale_ == scale) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  cap_insets_scale_ = scale;\n}\n\nvoid RenderImage::SetAutoPlay(bool auto_play) {\n  if (auto_play_ == auto_play) {\n    return;\n  }\n\n  // No need to `MarkNeedsPaint`. Only has effect before resource loaded.\n  auto_play_ = auto_play;\n#ifndef ENABLE_SKITY\n  if (image_resource_ && image_resource_->GetImage()) {\n    image_resource_->GetImage()->SetAutoPlay(auto_play_);\n  }\n#else\n  if (image_resource_ && image_resource_->GetImage()) {\n    auto image = image_resource_->GetImage();\n    if (image->GetType() == ImageType::kAnimated) {\n      auto animated_image = std::static_pointer_cast<AnimatedImage>(image);\n      animated_image->SetAutoPlay(auto_play_);\n    }\n  }\n#endif  // ENABLE_SKITY\n}\n\nvoid RenderImage::SetLoopCount(int loop_count) {\n  if (loop_count_ == loop_count) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  loop_count_ = loop_count;\n#ifndef ENABLE_SKITY\n  if (image_resource_ && image_resource_->GetImage()) {\n    image_resource_->GetImage()->SetLoopCount(loop_count_);\n  }\n#else\n  if (image_resource_ && image_resource_->GetImage()) {\n    auto image = image_resource_->GetImage();\n    if (image->GetType() == ImageType::kAnimated) {\n      auto animated_image = std::static_pointer_cast<AnimatedImage>(image);\n      animated_image->SetLoopCount(loop_count_);\n    }\n  }\n#endif\n}\n\nvoid RenderImage::SetAutoSize(bool auto_size) {\n  if (auto_size_ == auto_size) {\n    return;\n  }\n\n  auto_size_ = auto_size;\n  AdjustSizeIfNeeded();\n}\n\nvoid RenderImage::SetTintColor(const Color& tint_color) {\n  if (tint_color_ == tint_color) {\n    return;\n  }\n\n  tint_color_ = tint_color;\n  MarkNeedsPaint();\n}\n\nvoid RenderImage::OnNodeReady() { AdjustSizeIfNeeded(); }\n\nvoid RenderImage::AdjustSizeIfNeeded() {\n  auto image_resource = image_resource_.get();\n  if (!image_resource) {\n    image_resource = placeholder_resource_.get();\n  }\n  if (!image_resource) {\n    return;\n  }\n\n  float bitmap_width = image_resource->GetWidth();\n  float bitmap_height = image_resource->GetHeight();\n\n  if (client_) {\n    client_->AdjustSizeIfNeeded(auto_size_, bitmap_width, bitmap_height);\n  }\n}\n\nvoid RenderImage::CreateImageData(ImageData& image_data) {\n  image_data.image_resource = GetResourceForDisplay();\n  image_data.mode = mode_;\n  image_data.repeat = repeat_;\n  image_data.has_cap_insets = has_cap_insets_;\n  image_data.cap_insets = cap_insets_;\n  image_data.cap_insets_scale = cap_insets_scale_;\n  image_data.drop_shadow_offset_x = drop_shadow_offset_x_;\n  image_data.drop_shadow_offset_y = drop_shadow_offset_y_;\n  image_data.drop_shadow_color = drop_shadow_color_;\n  image_data.drop_shadow_blur_radius = drop_shadow_blur_radius_;\n  image_data.image_opacity = image_opacity_;\n  image_data.tint_color = tint_color_;\n}\n\nbool RenderImage::IsRepaintBoundary() const {\n  if (repeat_ == ImageRepeat::kNoRepeat &&\n      (drop_shadow_blur_radius_ != 0.0f || drop_shadow_offset_x_ != 0.0f ||\n       drop_shadow_offset_y_ != 0.0f)) {\n    return true;\n  }\n  return RenderObject::IsRepaintBoundary();\n}\n\nvoid RenderImage::Paint(PaintingContext& context, const FloatPoint& offset) {\n  if (!CanDisplay()) {\n    return;\n  }\n  // Ask parent to paint background and borders first.\n  RenderBox::Paint(context, offset);\n  GraphicsContext* graphics_context = context.GetGraphicsContext();\n  FML_DCHECK(graphics_context);\n\n  {\n    GraphicsContext::AutoRestore canvas_saver(graphics_context, true);\n    graphics_context->Translate(offset.x(), offset.y());\n\n    // radius clip\n    skity::Rect draw_rect =\n        skity::Rect::MakeXYWH(0, 0, ContentWidth(), ContentHeight());\n    skity::RRect round_rect;\n    round_rect.SetRect(draw_rect);\n    bool has_border_radius = HasBorder() && Border().HasBorderRadius();\n    if (has_border_radius) {\n      const auto& borders_data = Border();\n      skity::Vec2 radii[4];\n      radii[skity::RRect::kUpperLeft] = {\n          borders_data.radius_x_top_left_ - PaddingLeft() - BorderLeft(),\n          borders_data.radius_y_top_left_ - PaddingTop() - BorderTop()};\n      radii[skity::RRect::kUpperRight] = {\n          borders_data.radius_x_top_right_ - PaddingRight() - BorderRight(),\n          borders_data.radius_y_top_right_ - PaddingTop() - BorderTop()};\n      radii[skity::RRect::kLowerRight] = {\n          borders_data.radius_x_bottom_right_ - PaddingRight() - BorderRight(),\n          borders_data.radius_y_bottom_right_ - PaddingBottom() -\n              BorderBottom()};\n      radii[skity::RRect::kLowerLeft] = {\n          borders_data.radius_x_bottom_left_ - PaddingLeft() - BorderLeft(),\n          borders_data.radius_y_bottom_left_ - PaddingBottom() -\n              BorderBottom()};\n      round_rect.SetRectRadii(draw_rect, radii);\n    }\n\n    // Translate to content_rect where the ImagePainter is going to paint into.\n    graphics_context->Translate(PaintOffset().x(), PaintOffset().y());\n\n    // Paint image into content_rect.\n    ImageData image_data;\n    CreateImageData(image_data);\n    image_data.round_rect = round_rect;\n    ImagePainter(static_cast<RenderBox*>(this))\n        .PaintImage(context.GetGraphicsContext(), image_data);\n\n#ifndef ENABLE_SKITY\n    ImageResource* resource = GetResourceForDisplay();\n    // Disable raster cache for the animating image to save memory.\n    if (resource && resource->GetImage() &&\n        resource->GetImage()->MaybeAnimated()) {\n      context.SetWillChangeHint();\n    }\n\n    // Trigger `pending_image_resource_` decode after `WillPaint`\n    // which can set image size by it's UI layout\n    if (pending_image_resource_) {\n      auto* image = pending_image_resource_->GetImage();\n      if (image && image->UseTextureBackend()) {\n        image->GetGraphicsImage();\n      }\n    }\n#endif  // ENABLE_SKITY\n  }\n}\n\nvoid RenderImage::SetImageOpacity(float opacity) {\n  if (image_opacity_ == opacity) {\n    return;\n  }\n  MarkNeedsPaint();\n  // This opacity is used to accomplish the `fadeIn` animation only.\n  image_opacity_ = opacity;\n}\n\nPaintFunction RenderImage::FixupPainterIfNeeded(const PaintFunction& painter) {\n  auto result = painter;\n  if (effect_ == ImageEffect::kInverseColor) {\n    result = [old_painter = result](PaintingContext& context,\n                                    const FloatPoint& offset) {\n#ifndef ENABLE_SKITY\n      constexpr float mx[] = {-1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,  -1.0f,\n                              0.0f,  1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f,\n                              0.0f,  1.0f, 1.0f, 1.0f, 1.0f, 0.0f};\n\n      auto color_filter = ColorFilter::MakeMatrix(mx);\n      context.PushColorFilter(color_filter, offset, old_painter);\n#endif  // ENABLE_SKITY\n    };\n  }\n\n  if (repeat_ == ImageRepeat::kNoRepeat &&\n      (drop_shadow_blur_radius_ != 0.0f || drop_shadow_offset_x_ != 0.0f ||\n       drop_shadow_offset_y_ != 0.0f)) {\n    result = [this, old_painter = result](PaintingContext& context,\n                                          const FloatPoint& offset) {\n      auto filter = std::make_shared<DropShadowImageFilter>(\n          drop_shadow_offset_x_, drop_shadow_offset_y_,\n          drop_shadow_blur_radius_, drop_shadow_blur_radius_,\n          drop_shadow_color_);\n      context.PushImageFilter(filter, offset, old_painter);\n    };\n  }\n\n  if (repeat_ == ImageRepeat::kNoRepeat && HasBlur()) {\n    result = [this, old_painter = result](PaintingContext& ctx,\n                                          const FloatPoint& offset) {\n      // The ClipRect's offset is actually controlled by an `OffsetLayer`, so we\n      // should just set clipRect's x and y to be 0.\n      ctx.PushClipRect({0, 0, Width(), Height()}, offset, old_painter);\n    };\n  }\n  return result;\n}\n\nvoid RenderImage::WillPaint() {\n  // Check if any transform expansion would be applied to this image.\n  // If there is an expansion operation, we will force the image to use the\n  // original size for decoding to avoid blurring issues.\n  // This is not very accurate, cause the size when enlarged may still be\n  // smaller than the original size. And this will lead to some memory\n  // waste. Maybe need further research.\n  bool force_use_original_size = !down_sampling_ || HasTransformExpansion();\n  for (auto* resource : {image_resource_.get(), pending_image_resource_.get(),\n                         placeholder_resource_.get()}) {\n    if (resource && resource->GetImage()) {\n      FloatRect content_rect = ContentRect();\n      skity::Vec2 output_size =\n          skity::Vec2(content_rect.width(), content_rect.height());\n      if (!resource->GetImage()->IsSVG()) {\n        resource->GetImage()->SetExpectSizeCalculator(\n            [has_cap_insets = has_cap_insets_, mode = mode_, output_size,\n             pixel_ratio =\n                 GetRenderer()\n                     ->GetPixelRatio<kPixelTypeClay, kPixelTypePhysical>()](\n                skity::Vec2 size) {\n              if (has_cap_insets) {\n                return size;\n              }\n              skity::Vec2 render_size = ImagePainter::CalculateSize(\n                  mode, skity::Vec2(size.x, size.y), output_size);\n              // We should use the size in physical pixels.\n              render_size.y *= pixel_ratio;\n              render_size.x *= pixel_ratio;\n              return render_size;\n            },\n            force_use_original_size);\n      } else {\n        resource->GetImage()->SetExpectSizeCalculator(\n            [output_size,\n             pixel_ratio =\n                 GetRenderer()\n                     ->GetPixelRatio<kPixelTypeClay, kPixelTypePhysical>()](\n                skity::Vec2 size) {\n              // There is no `mode` or `cap_insets` provided for SVG image.\n              // Just consider the output_size as render_size.\n              skity::Vec2 render_size = output_size;\n              // We should use the size in physical pixels.\n              render_size.y *= pixel_ratio;\n              render_size.x *= pixel_ratio;\n              return render_size;\n            },\n            force_use_original_size);\n      }\n    }\n  }\n  RenderObject::WillPaint();\n}\n\nvoid RenderImage::RequestRenderImage(ImageResource* image_resource,\n                                     bool success) {\n#ifndef ENABLE_SKITY\n  if (success) {\n    MarkNeedsPaint();\n    if (image_resource == pending_image_resource_.get()) {\n      image_resource_ = std::move(pending_image_resource_);\n    }\n  }\n#endif  // ENABLE_SKITY\n}\n\nvoid RenderImage::DecodeImageFinish(bool success, const std::string& url) {\n  if (client_) {\n    client_->OnDecodeFinished(success, url);\n  }\n}\n\nvoid RenderImage::RegisterUploadTask(OneShotCallback<>&& task, int image_id) {\n  if (client_) {\n    client_->RegisterUploadTask(std::move(task), image_id);\n  }\n}\n\nvoid RenderImage::OnImageChanged() {\n  if (!IsActualVisible()) {\n    return;\n  }\n\n  MarkNeedsPaint();\n}\n\nbool RenderImage::HasCapInsets() const {\n  for (const auto& value : cap_insets_) {\n    if (value != Length()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid RenderImage::StartAnimate() {\n#ifndef ENABLE_SKITY\n  if (image_resource_) {\n    Image* image = image_resource_->GetImage();\n    if (image) {\n      image->StartAnimate();\n      MarkNeedsPaint();\n    }\n  }\n#else\n  if (image_resource_) {\n    auto image = image_resource_->GetImage();\n    if (image && image->GetType() == ImageType::kAnimated) {\n      auto animated_image = std::static_pointer_cast<AnimatedImage>(image);\n      animated_image->StartAnimate();\n    }\n  }\n#endif  // ENABLE_SKITY\n}\n\nvoid RenderImage::StopAnimation() {\n#ifndef ENABLE_SKITY\n  if (image_resource_) {\n    Image* image = image_resource_->GetImage();\n    if (image) {\n      image->StopAnimation();\n      MarkNeedsPaint();\n    }\n  }\n#else\n  if (image_resource_) {\n    auto image = image_resource_->GetImage();\n    if (image && image->GetType() == ImageType::kAnimated) {\n      auto animated_image = std::static_pointer_cast<AnimatedImage>(image);\n      animated_image->StopAnimation();\n    }\n  }\n#endif\n}\n\nvoid RenderImage::PauseAnimation() {\n#ifndef ENABLE_SKITY\n  if (image_resource_) {\n    Image* image = image_resource_->GetImage();\n    if (image) {\n      image->PauseAnimation();\n      MarkNeedsPaint();\n    }\n  }\n#else\n  if (image_resource_) {\n    auto image = image_resource_->GetImage();\n    if (image && image->GetType() == ImageType::kAnimated) {\n      auto animated_image = std::static_pointer_cast<AnimatedImage>(image);\n      animated_image->PauseAnimation();\n    }\n  }\n#endif  // ENABLE_SKITY\n}\n\nvoid RenderImage::ResumeAnimation() {\n#ifndef ENABLE_SKITY\n  if (image_resource_) {\n    Image* image = image_resource_->GetImage();\n    if (image) {\n      image->ResumeAnimation();\n      MarkNeedsPaint();\n    }\n  }\n#else\n  if (image_resource_) {\n    auto image = image_resource_->GetImage();\n    if (image && image->GetType() == ImageType::kAnimated) {\n      auto animated_image = std::static_pointer_cast<AnimatedImage>(image);\n      animated_image->ResumeAnimation();\n    }\n  }\n#endif\n}\n\nvoid RenderImage::OnStartPlay() {\n  if (client_) {\n    client_->OnStartPlay();\n  }\n}\n\nvoid RenderImage::OnCurrentLoopComplete() {\n  if (client_) {\n    client_->OnCurrentLoopComplete();\n  }\n}\n\nvoid RenderImage::OnFinalLoopComplete() {\n  if (client_) {\n    client_->OnFinalLoopComplete();\n  }\n}\n\nvoid RenderImage::TryDecodeImmediately() {\n  if (!need_decode_immediately) {\n    return;\n  }\n#ifndef ENABLE_SKITY\n  DecodePriority priority = DecodeUtils::GetDecodePriority(this);\n  if (image_resource_) {\n    image_resource_->GetImage()->GetGraphicsImage(priority);\n  }\n  if (pending_image_resource_) {\n    pending_image_resource_->GetImage()->GetGraphicsImage(priority);\n  }\n  if (placeholder_resource_) {\n    placeholder_resource_->GetImage()->GetGraphicsImage(priority);\n  }\n#else\n  if (image_resource_) {\n    image_resource_->GetImage()->GetGraphicsImage();\n  }\n  if (pending_image_resource_) {\n    pending_image_resource_->GetImage()->GetGraphicsImage();\n  }\n  if (placeholder_resource_) {\n    placeholder_resource_->GetImage()->GetGraphicsImage();\n  }\n#endif  // ENABLE_SKITY\n}\n\n// This function is called before scroll actions to decode images\n// for those which will be visible after scroll.\nvoid RenderImage::DecodeImages() {\n#ifndef ENABLE_SKITY\n  RenderObject::DecodeImages();\n\n  if (image_resource_ && image_resource_->GetImage()->NeedDecode()) {\n    DecodePriority priority = DecodeUtils::GetDecodePriority(this);\n    image_resource_->GetImage()->GetGraphicsImage(priority);\n  }\n  if (pending_image_resource_ &&\n      pending_image_resource_->GetImage()->NeedDecode()) {\n    DecodePriority priority = DecodeUtils::GetDecodePriority(this);\n    pending_image_resource_->GetImage()->GetGraphicsImage(priority);\n  }\n  if (placeholder_resource_ &&\n      placeholder_resource_->GetImage()->NeedDecode()) {\n    DecodePriority priority = DecodeUtils::GetDecodePriority(this);\n    placeholder_resource_->GetImage()->GetGraphicsImage(priority);\n  }\n#endif  // ENABLE_SKITY\n}\n\n#ifndef NDEBUG\nstd::string RenderImage::ToString() const {\n  std::stringstream ss;\n  ss << RenderBox::ToString();\n  if (image_resource_) {\n#ifndef ENABLE_SKITY\n    ss << \" image_src=\" << image_resource_->GetUrl();\n#endif  // ENABLE_SKITY\n    ss << \" image_size=(\" << image_resource_->GetWidth() << \",\"\n       << image_resource_->GetHeight() << \")\";\n  }\n  if (placeholder_resource_) {\n#ifndef ENABLE_SKITY\n    ss << \" placeholder_src=\" << placeholder_resource_->GetUrl();\n#endif  // ENABLE_SKITY\n    ss << \" placeholder_size=(\" << placeholder_resource_->GetWidth() << \",\"\n       << placeholder_resource_->GetHeight() << \")\";\n  }\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_image.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_IMAGE_H_\n#define CLAY_UI_RENDERING_RENDER_IMAGE_H_\n\n#include <array>\n#include <memory>\n#include <string>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/image/image_resource.h\"\n#include \"clay/gfx/image/image_resource_client.h\"\n#include \"clay/ui/painter/image_painter.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/render_box.h\"\n#include \"clay/ui/ui_rendering_backend.h\"\n\nnamespace clay {\nnamespace testing {\nclass ImagePainterTest;\n}\n\nclass RenderImageClient {\n public:\n  virtual void OnDecodeFinished(bool success, const std::string& url) = 0;\n  virtual void RegisterUploadTask(OneShotCallback<>&& task, int image_id) = 0;\n  virtual void OnStartPlay() = 0;\n  virtual void OnCurrentLoopComplete() = 0;\n  virtual void OnFinalLoopComplete() = 0;\n  virtual void AdjustSizeIfNeeded(bool auto_size, float bitmap_width,\n                                  float bitmap_height) = 0;\n\n protected:\n  virtual ~RenderImageClient() = default;\n};\n\nclass RenderImage : public RenderBox, public ImageResourceClient {\n public:\n  RenderImage() = default;\n  ~RenderImage() override;\n\n  const char* GetName() const override;\n\n  void AddClient(RenderImageClient* client) { client_ = client; }\n  void RemoveClient() { client_ = nullptr; }\n\n#ifndef ENABLE_SKITY\n  void SetImage(std::unique_ptr<ImageResource> resource);\n  const ImageResource* GetImage() { return image_resource_.get(); }\n  void SetPlaceholderImage(std::unique_ptr<ImageResource> placeholder_resource);\n  const ImageResource* GetPlaceholderImage() {\n    return placeholder_resource_.get();\n  }\n#else\n  void SetImage(std::unique_ptr<BaseImageInstance> image);\n  BaseImageInstance* GetImage() { return image_resource_.get(); }\n  void SetPlaceholderImage(\n      std::unique_ptr<BaseImageInstance> placeholder_resource);\n  BaseImageInstance* GetPlaceholderImage() {\n    return placeholder_resource_.get();\n  }\n#endif  // ENABLE_SKITY\n  void SetMode(FillMode mode);\n  void SetEffect(ImageEffect effect);\n  void SetRepeat(ImageRepeat repeat);\n  void SetDropShadow(float offset_x, float offset_y, float blur_radius,\n                     const GrColor& color);\n  void SetCapInsets(const std::array<Length, 4>& cap_insets_vec);\n  void SetCapInsetsScale(float scale);\n  void SetImageOpacity(float opacity);\n  void SetAutoPlay(bool auto_play);\n  void SetLoopCount(int loop_count);\n  void SetAutoSize(bool auto_size);\n  void SetTintColor(const Color& tint_color);\n\n  void OnNodeReady();\n\n  const ImageData& GetOrCreateImageData();\n\n  void Paint(PaintingContext& context, const FloatPoint& offset) override;\n  PaintFunction FixupPainterIfNeeded(const PaintFunction& painter) override;\n\n  bool IsRepaintBoundary() const override;\n\n  void StartAnimate();\n  void StopAnimation();\n  void PauseAnimation();\n  void ResumeAnimation();\n\n  void OnStartPlay() override;\n  void OnCurrentLoopComplete() override;\n  void OnFinalLoopComplete() override;\n\n  void SetNeedDecodeImmediately(bool value) { need_decode_immediately = value; }\n  void TryDecodeImmediately();\n  void DecodeImages() override;\n  void SetEnableLowQuality(bool enable) { enable_low_quality_ = enable; }\n  bool EnableLowQuality() const { return enable_low_quality_; }\n\n  void SetDownSampling(bool down_sampling) { down_sampling_ = down_sampling; }\n  bool DownSampling() const { return down_sampling_; }\n\n  DecodePriority GetDecodePriority() override {\n    return DecodeUtils::GetDecodePriority(this);\n  }\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  void WillPaint() override;\n  bool WillRenderImage() override { return IsActualVisible(); }\n  void RequestRenderImage(ImageResource* image_resource, bool success) override;\n  void DecodeImageFinish(bool success, const std::string& url) override;\n  void RegisterUploadTask(OneShotCallback<>&& task, int image_id) override;\n  void OnImageChanged() override;\n  void AdjustSizeIfNeeded();\n\n  void CreateImageData(ImageData& resource);\n\n#ifndef ENABLE_SKITY\n  ImageResource* GetResourceForDisplay() {\n    return image_resource_ ? image_resource_.get()\n                           : placeholder_resource_.get();\n  }\n#else\n  BaseImageInstance* GetResourceForDisplay() {\n    return image_resource_ ? image_resource_.get()\n                           : placeholder_resource_.get();\n  }\n#endif  // ENABLE_SKITY\n  bool HasCapInsets() const;\n\n  friend class testing::ImagePainterTest;\n\n  FillMode mode_ = FillMode::kScaleToFill;\n  ImageEffect effect_ = ImageEffect::kEffectNone;\n  ImageRepeat repeat_ = ImageRepeat::kNoRepeat;\n  // filter-image-view drop shadow:\n  float drop_shadow_offset_x_ = 0.0f;\n  float drop_shadow_offset_y_ = 0.0f;\n  float drop_shadow_blur_radius_ = 0.0f;\n  GrColor drop_shadow_color_ = ToSk(Color::kBlack());\n  // 9-patch:\n  bool has_cap_insets_ = false;\n  std::array<Length, 4> cap_insets_ = {};\n  float cap_insets_scale_ = 1.0f;\n\n  float image_opacity_ = 1.0f;\n  bool auto_play_ = true;\n  int loop_count_ = 0;\n  bool auto_size_ = false;\n\n  RenderImageClient* client_ = nullptr;\n\n  bool need_decode_immediately = false;\n  // Decide color type as ARGB_8888 or RGB_565.\n  bool enable_low_quality_ = false;\n  // Decide whether decode image into the size of real rendering areas rather\n  // than original size.\n  bool down_sampling_ = false;\n\n  std::optional<Color> tint_color_ = std::nullopt;\n\n#ifndef ENABLE_SKITY\n  std::unique_ptr<ImageResource> image_resource_;\n  std::unique_ptr<ImageResource> pending_image_resource_;\n  std::unique_ptr<ImageResource> placeholder_resource_;\n#else\n  std::unique_ptr<BaseImageInstance> image_resource_;\n  std::unique_ptr<BaseImageInstance> pending_image_resource_;\n  std::unique_ptr<BaseImageInstance> placeholder_resource_;\n#endif  // ENABLE_SKITY\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_IMAGE_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_list.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_list.h\"\n\nnamespace clay {\n\nconst char* RenderList::GetName() const { return \"RenderList\"; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_list.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_LIST_H_\n#define CLAY_UI_RENDERING_RENDER_LIST_H_\n\n#include \"clay/ui/rendering/render_scroll.h\"\n\nnamespace clay {\n\nclass RenderList : public RenderScroll {\n public:\n  RenderList() = default;\n  ~RenderList() override = default;\n\n  const char* GetName() const override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_LIST_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_object.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_object.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <sstream>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/animation/animation_properties_util.h\"\n#include \"clay/gfx/geometry/float_rounded_rect.h\"\n#include \"clay/gfx/geometry/math_util.h\"\n#include \"clay/gfx/image/image_resource_client.h\"\n#include \"clay/gfx/style/borders_data.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/compositing/pending_offset_layer.h\"\n\nnamespace clay {\n\nstatic const float kOffsetRotateAuto = -1024.0f;\nstatic const float kFloatEpsilon = 0.00001f;\n\n#define ENSURE_BACKGROUND()                                    \\\n  do {                                                         \\\n    if (!background_data_.has_value()) {                       \\\n      background_data_ = std::make_optional<BackgroundData>(); \\\n    }                                                          \\\n  } while (false)\n\n#define ENSURE_MASKIMAGE()                         \\\n  do {                                             \\\n    if (!mask_data_.has_value()) {                 \\\n      mask_data_ = std::make_optional<MaskData>(); \\\n    }                                              \\\n  } while (false)\n\nclass BackgroundOrMaskImageClient : public ImageResourceClient {\n public:\n  explicit BackgroundOrMaskImageClient(RenderObject* obj)\n      : render_object_(obj) {}\n  ~BackgroundOrMaskImageClient() override = default;\n\n  DecodePriority GetDecodePriority() override {\n    return DecodeUtils::GetDecodePriority(render_object_);\n  }\n\n  void RegisterUploadTask(OneShotCallback<>&& task, int image_id) override {\n    if (render_object_->GetRenderer() &&\n        render_object_->GetRenderer()->renderer_client()) {\n      render_object_->GetRenderer()->renderer_client()->RegisterUploadTask(\n          std::move(task), image_id);\n    }\n  }\n\n private:\n  void RequestRenderImage(ImageResource* image_resource,\n                          bool success) override {\n    if (success && render_object_ &&\n        (render_object_->HasBackground() || render_object_->HasMask())) {\n      render_object_->MarkNeedsPaint();\n    }\n  }\n\n  bool WillRenderImage() override {\n    return render_object_ && render_object_->CanDisplay();\n  }\n\n  void OnImageChanged() override {}\n\n  RenderObject* render_object_;\n};\n\nuint8_t RenderObject::default_overflow_ = CSSProperty::OVERFLOW_XY;\n\nRenderObject::RenderObject()\n    : raster_transition_properties_(ClayAnimationPropertyType::kNone),\n      raster_animation_properties_(ClayAnimationPropertyType::kNone),\n      transition_manager_(nullptr),\n      keyframes_manager_(nullptr),\n      previous_(nullptr),\n      next_(nullptr) {}\n\nRenderObject::~RenderObject() {\n  // Remove this layer from the composite layer tree.\n  {\n    std::unique_ptr<PendingContainerLayer> temp = nullptr;\n    std::swap(temp, compositor_layer_);\n    if (temp) {\n      temp->Detach();\n    }\n  }\n\n  {\n    std::unique_ptr<PendingContainerLayer> temp = nullptr;\n    std::swap(temp, container_layer_);\n    if (temp) {\n      temp->Detach();\n    }\n  }\n\n  for (auto& effect_layer : effect_layers_) {\n    if (effect_layer) {\n      effect_layer->Remove();\n    }\n  }\n}\n\nstd::string RenderObject::DebugName() const { return GetName(); }\n\nvoid RenderObject::SetLeft(float left) {\n  if (Left() == left) {\n    return;\n  }\n\n  if (IsRepaintBoundary() && Parent()) {\n    // TODO(jinsong): If there is a parent, let the parent re-composite this\n    // object, otherwise re-composite ourselves. If the cost of repainting\n    // parent is high, this is a bad case.\n    static_cast<RenderObject*>(Parent())->MarkNeedsPaint();\n  } else {\n    MarkNeedsPaint();\n  }\n\n  box_data_.SetLeft(left);\n}\n\nvoid RenderObject::SetTop(float top) {\n  if (Top() == top) {\n    return;\n  }\n\n  if (IsRepaintBoundary() && Parent()) {\n    // TODO(jinsong): If there is a parent, let the parent re-composite this\n    // object, otherwise re-composite ourselves. If the cost of repainting\n    // parent is high, this is a bad case.\n    static_cast<RenderObject*>(Parent())->MarkNeedsPaint();\n  } else {\n    MarkNeedsPaint();\n  }\n\n  box_data_.SetTop(top);\n}\n\nvoid RenderObject::SetWidth(float width) {\n  if (Width() == width) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  box_data_.SetWidth(width);\n}\n\nvoid RenderObject::SetHeight(float height) {\n  if (Height() == height) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  box_data_.SetHeight(height);\n}\n\nvoid RenderObject::SetPaddingLeft(float left) {\n  if (PaddingLeft() == left) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  box_data_.SetPaddingLeft(left);\n}\n\nvoid RenderObject::SetPaddingRight(float right) {\n  if (PaddingRight() == right) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  box_data_.SetPaddingRight(right);\n}\n\nvoid RenderObject::SetPaddingTop(float top) {\n  if (PaddingTop() == top) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  box_data_.SetPaddingTop(top);\n}\n\nvoid RenderObject::SetPaddingBottom(float bottom) {\n  if (PaddingBottom() == bottom) {\n    return;\n  }\n\n  MarkNeedsPaint();\n  box_data_.SetPaddingBottom(bottom);\n}\n\nvoid RenderObject::SetBorders(const BordersData& borders_data) {\n  border_ = std::make_optional<BordersData>(borders_data);\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetOutline(const OutlineData& outline_data) {\n  outline_ = std::make_optional<OutlineData>(outline_data);\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetVisible(bool visible) {\n  if (visible == visible_) {\n    return;\n  }\n\n  visible_ = visible;\n  MarkSubtreeDirty();\n}\n\nvoid RenderObject::SetShadow(const Shadow& shadow) {\n  if (!shadows_.has_value()) {\n    shadows_ = std::make_optional<std::vector<Shadow>>();\n  }\n\n  shadows_->push_back(shadow);\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetShadows(std::vector<Shadow>&& shadows) {\n  if (!shadows_.has_value() || shadows != shadows_) {\n    shadows_ = std::move(shadows);\n    MarkNeedsPaint();\n  }\n}\n\nFloatRect RenderObject::GetFrameRectEx() const {\n  FloatRect box_rect(0, 0, Width(), Height());\n  if (!HasShadow() && !HasOutline() && !HasDefaultFocusRing()) {\n    return box_rect;\n  }\n\n  FloatRect bounds = box_rect;\n\n  if (HasShadow()) {\n    // Union a series of shadows with BoxRect.\n    for (const auto& shadow : shadows_.value()) {\n      if (shadow.inset) {\n        continue;\n      }\n\n      FloatRect shadow_rect = box_rect;\n      auto radius = 2.0 * shadow.blur_radius + shadow.spread_radius;\n      shadow_rect.Move(shadow.offset_x - radius, shadow.offset_y - radius);\n      shadow_rect.Expand(2 * radius, 2 * radius);\n      shadow_rect.SetLocation(\n          {std::min(0.f, shadow_rect.location().x() - box_data_.Left()),\n           std::min(0.f, shadow_rect.location().y() - box_data_.Top())});\n      bounds.ExpandToInclude(shadow_rect);\n    }\n  }\n\n  if (HasOutline()) {\n    FloatRect outline_rect = box_rect;\n    outline_rect.Move(-outline_->width_, -outline_->width_);\n    outline_rect.Expand(2 * outline_->width_, 2 * outline_->width_);\n    bounds.ExpandToInclude(outline_rect);\n  }\n\n  if (HasDefaultFocusRing()) {\n    FloatRect focus_ring_rect = box_rect;\n    focus_ring_rect.Move(-num_value::kFocusRingThickness,\n                         -num_value::kFocusRingThickness);\n    focus_ring_rect.Expand(2 * num_value::kFocusRingThickness,\n                           2 * num_value::kFocusRingThickness);\n    bounds.ExpandToInclude(focus_ring_rect);\n  }\n\n  return bounds;\n}\n\nFloatPoint RenderObject::OffsetInLayer() const {\n  if (!HasShadow() && !HasOutline() && !HasDefaultFocusRing()) {\n    return location();\n  }\n\n  FloatPoint offset = location();\n  FloatPoint offset_ex = GetFrameRectEx().location();\n\n  // The figure below illustrates how to calculate the layer offset.\n  //     |\n  //     |    offset   _____________\n  //     |<---------->|    View     |\n  // |   |            | case1       |\n  // |   |    | case2 |<---->|      |\n  // |   |    |<----->|      |      |\n  // |<--|----------->|             |\n  //     |  case3     |             |\n  //     |            |_____________|\n  //\n  // case1: offset_ex > 0, layer_offset = offset;\n  // case2: offset_ex < 0 & abs(offset_ex) < offset,\n  //        layer_offset = offset_ex + offset;\n  // case3: offset_ex < 0 & abs(offset_ex) > offset,\n  //        layer_offset = 0;\n  float offset_x = 0;\n  if (offset_ex.x() >= 0) {\n    offset_x = offset.x();\n  } else {\n    offset_x = std::max(0.f, offset.x() + offset_ex.x());\n  }\n\n  float offset_y = 0;\n  if (offset_ex.y() >= 0) {\n    offset_y = offset.y();\n  } else {\n    offset_y = std::max(0.f, offset.y() + offset_ex.y());\n  }\n  return FloatPoint(offset_x, offset_y);\n}\n\nFloatPoint RenderObject::PaintOffsetEx() const {\n  if (!HasShadow() && !HasOutline() && !HasDefaultFocusRing()) {\n    return FloatPoint();\n  }\n\n  FloatPoint offset = location();\n  FloatPoint offset_ex = GetFrameRectEx().location();\n  float offset_x = 0;\n  if (offset_ex.x() >= 0) {\n    offset_x = 0;\n  } else if (std::abs(offset_ex.x()) < offset.x()) {\n    offset_x = std::abs(offset_ex.x());\n  } else {\n    offset_x = offset.x();\n  }\n  float offset_y = 0;\n  if (offset_ex.y() >= 0) {\n    offset_y = 0;\n  } else if (std::abs(offset_ex.y()) < offset.y()) {\n    offset_y = std::abs(offset_ex.y());\n  } else {\n    offset_y = offset.y();\n  }\n  return FloatPoint(offset_x, offset_y);\n}\n\nvoid RenderObject::SetBackgroundData(const BackgroundData& background_data) {\n  background_data_ = std::make_optional<BackgroundData>();\n  background_data_->background_color = background_data.background_color;\n  background_data_->images.resize(background_data.background_images.size());\n  for (const auto& bg : background_data.background_images) {\n    background_data_->clips.emplace_back(bg.clip);\n    background_data_->origins.emplace_back(bg.origin);\n    background_data_->repeats.emplace_back(bg.repeat);  // repeat-x\n    background_data_->repeats.emplace_back(bg.repeat);  // repeat-y\n  }\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::ResizeBackground(size_t size) {\n  ENSURE_BACKGROUND();\n  if (background_data_->images.size() != size) {\n    background_data_->images.resize(size);\n    MarkNeedsPaint();\n  }\n}\n\nvoid RenderObject::ResizeMask(size_t size) {\n  ENSURE_MASKIMAGE();\n  if (mask_data_->images.size() != size) {\n    mask_data_->images.resize(size);\n    MarkNeedsPaint();\n  }\n}\n\n#ifndef ENABLE_SKITY\nvoid RenderObject::SetBackgroundImage(size_t index,\n                                      std::unique_ptr<ImageResource> resource) {\n  if (!HasBackground() || index >= background_data_->images.size()) {\n    return;\n  }\n\n  if (resource) {\n    if (!bg_image_client_) {\n      bg_image_client_ = std::make_unique<BackgroundOrMaskImageClient>(this);\n    }\n    resource->AddImageResourceClient(bg_image_client_.get());\n    background_data_->images[index].SetImageResource(std::move(resource));\n  }\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetMaskImage(size_t index,\n                                std::unique_ptr<ImageResource> resource) {\n  if (!HasMask() || index >= mask_data_->images.size()) {\n    return;\n  }\n\n  if (resource) {\n    if (!mask_image_client_) {\n      mask_image_client_ = std::make_unique<BackgroundOrMaskImageClient>(this);\n    }\n    resource->AddImageResourceClient(mask_image_client_.get());\n    mask_data_->images[index].SetImageResource(std::move(resource));\n  }\n  MarkNeedsPaint();\n}\n#else\nvoid RenderObject::SetBackgroundImage(\n    size_t index, std::unique_ptr<BaseImageInstance> image) {\n  if (!HasBackground() || index >= background_data_->images.size()) {\n    return;\n  }\n  if (image) {\n    background_data_->images[index].SetImageResource(std::move(image));\n  }\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetMaskImage(size_t index,\n                                std::unique_ptr<BaseImageInstance> image) {\n  if (!HasMask() || index >= mask_data_->images.size()) {\n    return;\n  }\n  if (image) {\n    mask_data_->images[index].SetImageResource(std::move(image));\n  }\n  MarkNeedsPaint();\n}\n#endif  // ENABLE_SKITY\n\nvoid RenderObject::SetBackgroundColor(const Color& color,\n                                      bool skip_update_for_raster_animation) {\n  ENSURE_BACKGROUND();\n  if (background_data_->background_color == color) {\n    return;\n  }\n  background_data_->background_color = color;\n  if (!skip_update_for_raster_animation) {\n    MarkNeedsPaint();\n  }\n}\n\nvoid RenderObject::SetBackgroundImage(size_t index, const Gradient& gradient) {\n  if (!HasBackground() || index >= background_data_->images.size()) {\n    return;\n  }\n  background_data_->images[index].SetGradient(gradient);\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetBackgroundClip(\n    const std::vector<ClayBackgroundClipType>& clips) {\n  ENSURE_BACKGROUND();\n  background_data_->clips = clips;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetBackgroundOrigin(\n    const std::vector<ClayBackgroundOriginType>& origins) {\n  ENSURE_BACKGROUND();\n  background_data_->origins = origins;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetBackgroundPosition(\n    const std::vector<BackgroundPosition>& positions) {\n  ENSURE_BACKGROUND();\n  background_data_->positions = positions;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetBackgroundRepeat(\n    const std::vector<ClayBackgroundRepeatType>& repeats) {\n  ENSURE_BACKGROUND();\n  background_data_->repeats = repeats;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetBackgroundSize(const std::vector<BackgroundSize>& sizes) {\n  ENSURE_BACKGROUND();\n  background_data_->sizes = sizes;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetMaskImage(size_t index, const Gradient& gradient) {\n  ENSURE_MASKIMAGE();\n  if (!HasMask() || index >= mask_data_->images.size()) {\n    return;\n  }\n  mask_data_->images[index].SetGradient(gradient);\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetMaskPosition(const std::vector<MaskPosition>& positions) {\n  ENSURE_MASKIMAGE();\n  mask_data_->positions = positions;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetMaskRepeat(\n    const std::vector<ClayMaskRepeatType>& repeats) {\n  ENSURE_MASKIMAGE();\n  mask_data_->repeats = repeats;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetMaskOrigin(\n    const std::vector<ClayMaskOriginType>& origins) {\n  ENSURE_MASKIMAGE();\n  mask_data_->origins = origins;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetMaskSize(const std::vector<MaskSize>& mask_sizes) {\n  ENSURE_MASKIMAGE();\n  mask_data_->sizes = mask_sizes;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetMaskClip(const std::vector<ClayMaskClipType>& clips) {\n  ENSURE_MASKIMAGE();\n  mask_data_->clips = clips;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetHasDefaultFocusRing(bool has_default_focus_ring) {\n  if (has_default_focus_ring == has_default_focus_ring_) {\n    return;\n  }\n  has_default_focus_ring_ = has_default_focus_ring;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetImageFilterMode(FilterMode mode) {\n  if (image_filter_mode_ != mode) {\n    image_filter_mode_ = mode;\n    MarkNeedsPaint();\n  }\n}\n\nbool RenderObject::HasTransformOperations() const {\n  return transform_.has_value();\n}\n\nvoid RenderObject::SetTransformOperations(const TransformOperations& transform,\n                                          bool is_from_animation) {\n  if (transform_ &&\n      transform_->ApproximatelyEqual(\n          transform, TransformOperations::kApproximatelyEqualTolerance)) {\n    return;\n  }\n  float translate_z = transform.GetTranslateZ();\n  if (std::abs(translate_z_ - translate_z) > 1e-6) {\n    // translate_z_ changes.\n    translate_z_ = translate_z;\n    if (auto* parent = static_cast<RenderObject*>(Parent())) {\n      parent->DirtyChildrenPaintingOrder();\n    }\n  }\n  transform_ = transform;\n  if (!HasTransform()) {\n    // Resets transform_ to identity and needs mark dirty to repaint.\n    MarkNeedsPaint();\n  } else {\n    // Don't dirty this node, only update transform effect.\n    // Don't ever update transform effect while using rasterizer animation\n    if (!is_from_animation) {\n      MarkNeedsEffect();\n    }\n  }\n}\n\nvoid RenderObject::SetPerspective(float value) {\n  if (value != perspective_.value_or(0.f)) {\n    if (!HasPerspective()) {\n      MarkNeedsPaint();\n    } else {\n      MarkNeedsEffect();\n    }\n    perspective_ = value;\n  }\n}\n\nvoid RenderObject::RebuildSortedChildrenIfNeeded() {\n  if (!sorted_children_.empty()) {\n    return;\n  }\n\n  bool needs_reorder = false;\n  for (auto* child = VirtualChildren().FirstChild(); child != nullptr;\n       child = child->NextSibling()) {\n    needs_reorder |= child->GetTranslateZ() || child->GetPaintingOrder();\n    sorted_children_.push_back(child);\n  }\n\n  if (!needs_reorder) {\n    return;\n  }\n\n  std::stable_sort(\n      sorted_children_.begin(), sorted_children_.end(),\n      [](RenderObject* node1, RenderObject* node2) {\n        if (node1->GetTranslateZ() < node2->GetTranslateZ()) {\n          return true;\n        } else if (node1->GetTranslateZ() == node2->GetTranslateZ()) {\n          return node1->GetPaintingOrder() < node2->GetPaintingOrder();\n        }\n        return false;\n      });\n}\n\nvoid RenderObject::VisitChildren(\n    const std::function<void(RenderObject*)> visitor) {\n  RebuildSortedChildrenIfNeeded();\n  for (auto* child : sorted_children_) {\n    visitor(child);\n  }\n}\n\nvoid RenderObject::SetTransformOrigin(const FloatPoint& origin) {\n  transform_origin_ = origin;\n  MarkNeedsEffect();\n}\n\nvoid RenderObject::SetOpacity(float opacity, bool is_from_animation) {\n  if (opacity_.has_value() && opacity_.value() == opacity) {\n    return;\n  }\n\n  opacity_ = opacity;\n  if (!HasOpacity()) {\n    // The latest opacity is 1.0, which means it changes from translucent to\n    // opaque and needs mark dirty to repaint.\n    MarkNeedsPaint();\n  } else {\n    // Don't dirty this node, only update opacity effect.\n    // Don't ever update opacity effect while using rasterizer animation\n    if (!is_from_animation) {\n      MarkNeedsEffect();\n    }\n  }\n}\n\nvoid RenderObject::ClearFilter() {\n  ClearColorFilter();\n  ClearImageFilter();\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::ClearColorFilter() { current_color_filter_matrix_.clear(); }\n\nvoid RenderObject::ClearImageFilter() { blur_radius_ = 0.f; }\n\nvoid RenderObject::SetBlurRadius(float radius) {\n  if (blur_radius_ == radius) {\n    return;\n  }\n  blur_radius_ = radius;\n  if (!HasBlur()) {\n    MarkNeedsPaint();\n  } else {\n    MarkNeedsEffect();\n  }\n}\n\nvoid RenderObject::CombineColorFilter(const std::vector<float>& matrix) {\n  if (current_color_filter_matrix_.empty()) {\n    current_color_filter_matrix_ = matrix;\n  } else {\n    std::vector<float> target(20);\n\n    int index = 0;\n    for (int j = 0; j < 20; j += 5) {\n      for (int i = 0; i < 4; i++) {\n        target[index++] = matrix[j + 0] * current_color_filter_matrix_[i + 0] +\n                          matrix[j + 1] * current_color_filter_matrix_[i + 5] +\n                          matrix[j + 2] * current_color_filter_matrix_[i + 10] +\n                          matrix[j + 3] * current_color_filter_matrix_[i + 15];\n      }\n      target[index++] = matrix[j + 0] * current_color_filter_matrix_[4] +\n                        matrix[j + 1] * current_color_filter_matrix_[9] +\n                        matrix[j + 2] * current_color_filter_matrix_[14] +\n                        matrix[j + 3] * current_color_filter_matrix_[19] +\n                        matrix[j + 4];\n    }\n    current_color_filter_matrix_ = target;\n  }\n}\n\nvoid RenderObject::AppendGrayScale(float gray_scale) {\n  FML_DCHECK(gray_scale >= 0 && gray_scale <= 1);\n  if (gray_scale != 0.f) {\n    // ref: cc/paint/render_surface_filters.cc\n    auto v = 1 - gray_scale;\n    auto m01 = 0.2126f + 0.7874f * v;\n    auto m02 = 0.7152f - 0.7152f * v;\n    auto m10 = 0.2126f - 0.2126f * v;\n    auto m11 = 0.7152f + 0.2848f * v;\n    std::vector<float> m{m01,\n                         m02,\n                         1.f - (m01 + m02),\n                         0.f,\n                         0.f,\n                         m10,\n                         m11,\n                         1.f - (m10 + m11),\n                         0.f,\n                         0.f,\n                         m10,\n                         m02,\n                         1.f - (m10 + m02),\n                         0.f,\n                         0.f,\n                         0.f,\n                         0.f,\n                         0.f,\n                         1.f,\n                         0.f};\n    CombineColorFilter(m);\n    MarkNeedsEffect();\n  }\n}\n\nvoid RenderObject::AppendBrightness(float brightness) {\n  FML_DCHECK(brightness >= 0);\n  if (brightness != 1.0) {\n    // ref: cc/paint/render_surface_filters.cc\n    // [0, +inf]\n    std::vector<float> m{brightness, 0.f, 0.f, 0.f, 0.f, 0.f,        brightness,\n                         0.f,        0.f, 0.f, 0.f, 0.f, brightness, 0.f,\n                         0.f,        0.f, 0.f, 0.f, 1.f, 0.f};\n    CombineColorFilter(m);\n    MarkNeedsEffect();\n  };\n}\n\nvoid RenderObject::AppendContrast(float contrast) {\n  FML_DCHECK(contrast >= 0);\n  if (contrast != 1.0f) {\n    // ref: cc/paint/render_surface_filters.cc\n    auto v = -0.5f * contrast + 0.5f;\n    std::vector<float> m{\n        contrast, 0.f, 0.f,      0.f, v, 0.f, contrast, 0.f, 0.f, v,\n        0.f,      0.f, contrast, 0.f, v, 0.f, 0.f,      0.f, 1.f, 0.f,\n    };\n    CombineColorFilter(m);\n    MarkNeedsEffect();\n  }\n}\n\nvoid RenderObject::AppendSaturate(float saturate) {\n  FML_DCHECK(saturate >= 0);\n  if (saturate != 1.0f) {\n    auto m01 = 0.213f + 0.787f * saturate;\n    auto m02 = 0.715f - 0.715f * saturate;\n    auto m10 = 0.213f - 0.213f * saturate;\n    auto m11 = 0.715f + 0.285f * saturate;\n    std::vector<float> m{m01,\n                         m02,\n                         1.f - (m01 + m02),\n                         0.f,\n                         0.f,\n                         m10,\n                         m11,\n                         1.f - (m10 + m11),\n                         0.f,\n                         0.f,\n                         m10,\n                         m02,\n                         1.f - (m10 + m02),\n                         0.f,\n                         0.f,\n                         0.f,\n                         0.f,\n                         0.f,\n                         1.f,\n                         0.f};\n    CombineColorFilter(m);\n    MarkNeedsEffect();\n  }\n}\nvoid RenderObject::AppendHueRotate(float deg) {\n  FML_CHECK(deg >= 0);\n  if (deg != 0) {\n    float rad = deg * 3.1415926 / 180;\n    float cos_hue = std::cosf(rad);\n    float sin_hue = std::sinf(rad);\n    std::vector<float> m{0.213f + cos_hue * 0.787f - sin_hue * 0.213f,\n                         0.715f - cos_hue * 0.715f - sin_hue * 0.715f,\n                         0.072f - cos_hue * 0.072f + sin_hue * 0.928f,\n                         0.f,\n                         0.f,\n                         0.213f - cos_hue * 0.213f + sin_hue * 0.143f,\n                         0.715f + cos_hue * 0.285f + sin_hue * 0.140f,\n                         0.072f - cos_hue * 0.072f - sin_hue * 0.283f,\n                         0.f,\n                         0.f,\n                         0.213f - cos_hue * 0.213f - sin_hue * 0.787f,\n                         0.715f - cos_hue * 0.715f + sin_hue * 0.715f,\n                         0.072f + cos_hue * 0.928f + sin_hue * 0.072f,\n                         0.f,\n                         0.f,\n                         0.f,\n                         0.f,\n                         0.f,\n                         1.f,\n                         0.f};\n    CombineColorFilter(m);\n    MarkNeedsEffect();\n  }\n}\n\nvoid RenderObject::AppendCustomColorMatrix(\n    const std::array<float, 20>& matrix) {\n  std::vector<float> m(matrix.begin(), matrix.end());\n  CombineColorFilter(m);\n  MarkNeedsEffect();\n}\n\nvoid RenderObject::SetOverflow(uint8_t overflow) {\n  overflow_ = overflow;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetClipPath(const FloatRoundedRect& rrect) {\n  if (auto ptr = std::get_if<FloatRoundedRect>(&clip_shape_)) {\n    if (*ptr == rrect) {\n      return;\n    }\n  }\n  clip_shape_ = rrect;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetClipPath(const GrPath& path) {\n  if (auto ptr = std::get_if<GrPath>(&clip_shape_)) {\n    if (*ptr == path) {\n      return;\n    }\n  }\n  clip_shape_ = path;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetOffsetPath(const FloatRoundedRect& rrect) {\n  GrPath path;\n  PATH_ADD_RRECT(path, rrect);\n  if (path == motion_path_.offset_path) {\n    return;\n  }\n  motion_path_.offset_path = path;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetOffsetPath(const GrPath& path) {\n  if (motion_path_.offset_path == path) {\n    return;\n  }\n  motion_path_.offset_path = path;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetOffsetRotate(float rotate) {\n  bool is_auto = IsApproximatelyEqual(rotate, kOffsetRotateAuto, kFloatEpsilon);\n  if (motion_path_.offset_rotate == rotate &&\n      motion_path_.offset_rotate_auto == is_auto) {\n    return;\n  }\n  motion_path_.offset_rotate = rotate;\n  motion_path_.offset_rotate_auto = is_auto;\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::SetOffsetDistance(float distance) {\n  if (motion_path_.offset_distance == distance) {\n    return;\n  }\n  motion_path_.offset_distance = distance;\n\n  // Calculate the x,y,deg\n  if (motion_path_.offset_path.has_value()) {\n    MotionState state =\n        PathBuilder::CalculateMotionState(*motion_path_.offset_path, distance);\n    float x = state.x;\n    float y = state.y;\n    float deg = motion_path_.offset_rotate_auto ? state.deg\n                                                : motion_path_.offset_rotate;\n    skity::Matrix matrix;\n    matrix.PostRotate(deg);\n    matrix.PostTranslate(x, y);\n    UpdateOffsetTransform(matrix);\n  }\n\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::UpdateOffsetTransform(const skity::Matrix& matrix) {\n  Transform transform(matrix);\n  TransformOperations transform_ops;\n  transform_ops.AppendMatrix(transform);\n\n  offset_transform_ = transform_ops;\n}\n\nvoid RenderObject::ClearClipPath() {\n  if (std::get_if<std::monostate>(&clip_shape_)) {\n    return;\n  }\n  clip_shape_ = std::monostate{};\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::ClearOffsetPath() {\n  if (!motion_path_.offset_path.has_value()) {\n    return;\n  }\n  motion_path_.offset_path.reset();\n  offset_transform_.reset();\n  MarkNeedsPaint();\n}\n\nRenderObject::ClipPathType& RenderObject::ClipPath() { return clip_shape_; }\n\nbool RenderObject::IsDescendantOf(const RenderObject* parent) const {\n  for (const RenderObject* object = this; object;\n       object = static_cast<const RenderObject*>(object->Parent())) {\n    if (object == parent) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid RenderObject::RedepthChildren() {\n  RenderObject* child = SlowFirstChild();\n  while (child) {\n    RedepthChild(child);\n    child = child->NextSibling();\n  }\n}\n\nvoid RenderObject::AddChild(RenderObject* new_child,\n                            RenderObject* before_child) {\n  FML_DCHECK(!new_child->Parent());\n  AdoptChild(new_child);\n\n  VirtualChildren().InsertChild(this, new_child, before_child);\n\n  // Set Renderer for child.\n  new_child->SetRenderer(renderer_);\n  new_child->MarkSubtreeDirty();\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::RemoveChild(RenderObject* old_child) {\n  VirtualChildren().RemoveChild(this, old_child);\n\n  DropChild(old_child);\n\n  old_child->SetRenderer(nullptr);\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::RemoveAllChildren() {\n  RenderObject* child = VirtualChildren().FirstChild();\n  while (child) {\n    VirtualChildren().RemoveChild(this, child);\n    DropChild(child);\n    child->SetRenderer(nullptr);\n    child = VirtualChildren().FirstChild();\n  }\n  MarkNeedsPaint();\n}\n\nvoid RenderObject::RemoveAndCollectChildren(\n    std::vector<RenderObject*>& objects) {\n  while (VirtualChildren().FirstChild()) {\n    RenderObject* child = VirtualChildren().FirstChild();\n    objects.push_back(child);\n    VirtualChildren().RemoveChild(this, child);\n    DropChild(child);\n  }\n}\n\nvoid RenderObject::BringChildToFront(RenderObject* child) {\n  if (VirtualChildren().LastChild() == child) {\n    return;\n  }\n  VirtualChildren().RemoveChild(this, child);\n  VirtualChildren().InsertChild(this, child, nullptr);\n  MarkNeedsPaint();\n}\n\nRenderObject* RenderObject::NextInPreOrder() const {\n  if (RenderObject* object = SlowFirstChild()) {\n    return object;\n  }\n\n  return NextInPreOrderAfterChildren();\n}\n\nRenderObject* RenderObject::NextInPreOrderAfterChildren() const {\n  const RenderObject* object = NextSibling();\n  if (!object) {\n    object = static_cast<const RenderObject*>(Parent());\n    while (object && !object->NextSibling()) {\n      object = static_cast<const RenderObject*>(object->Parent());\n    }\n    if (object) {\n      object = object->NextSibling();\n    }\n  }\n\n  return const_cast<RenderObject*>(object);\n}\n\nRenderObject* RenderObject::NextInPreOrder(\n    const RenderObject* stay_within) const {\n  if (RenderObject* object = SlowFirstChild()) {\n    return object;\n  }\n\n  return NextInPreOrderAfterChildren(stay_within);\n}\n\nRenderObject* RenderObject::NextInPreOrderAfterChildren(\n    const RenderObject* stay_within) const {\n  if (this == stay_within) {\n    return nullptr;\n  }\n\n  const RenderObject* current = this;\n  RenderObject* next = current->NextSibling();\n  for (; !next; next = current->NextSibling()) {\n    current = static_cast<const RenderObject*>(current->Parent());\n    if (!current || current == stay_within) {\n      return nullptr;\n    }\n  }\n  return next;\n}\n\nRenderObject* RenderObject::PreviousInPreOrder() const {\n  if (RenderObject* object = PreviousSibling()) {\n    while (RenderObject* last = object->SlowLastChild()) {\n      object = last;\n    }\n    return object;\n  }\n\n  return const_cast<RenderObject*>(static_cast<const RenderObject*>(Parent()));\n}\n\nRenderObject* RenderObject::PreviousInPreOrder(\n    const RenderObject* stay_within) const {\n  if (this == stay_within) {\n    return nullptr;\n  }\n\n  return PreviousInPreOrder();\n}\n\nRenderObject* RenderObject::ChildAt(unsigned index) const {\n  RenderObject* child = SlowFirstChild();\n  for (unsigned i = 0; child && i < index; i++) {\n    child = child->NextSibling();\n  }\n  return child;\n}\n\nRenderObject* RenderObject::LastLeafChild() const {\n  RenderObject* leaf = SlowLastChild();\n  while (leaf) {\n    RenderObject* object = nullptr;\n    object = leaf->SlowLastChild();\n    if (!object) {\n      break;\n    }\n    leaf = object;\n  }\n  return leaf;\n}\n\nvoid RenderObject::Destroy() {\n  SetRenderer(nullptr);\n  for (auto& effect_layer : effect_layers_) {\n    if (effect_layer) {\n      effect_layer->Remove();\n    }\n  }\n  std::fill(effect_layers_.begin(), effect_layers_.end(), nullptr);\n  Remove();\n}\n\nvoid RenderObject::ValidateForPaint(bool parent_visibility) {\n  bool visible = parent_visibility && visible_;\n  if (!visible && (needs_paint_ || needs_effect_)) {\n    renderer_->RemoveDirtyNode(this);\n    needs_paint_ = false;\n    needs_effect_ = false;\n  }\n  RenderObject* child = VirtualChildren().FirstChild();\n  while (child) {\n    child->ValidateForPaint(visible);\n    child = child->NextSibling();\n  }\n}\n\nbool RenderObject::IsActualVisible() {\n  // TODO(dongjiajian): Wait for image deferred decode\n  if (!renderer_ || !Visible()) {\n    return false;\n  }\n  if (!Parent()) {\n    return true;\n  }\n  return static_cast<RenderObject*>(Parent())->IsActualVisible();\n}\n\nvoid RenderObject::SetRepaintBoundary(bool repaint_boundary) {\n  if (repaint_boundary_ == repaint_boundary) {\n    return;\n  }\n  // Don't flatten RenderObjects with raster animations.\n  if (!repaint_boundary && HasRasterAnimation()) {\n    return;\n  }\n\n  repaint_boundary_ = repaint_boundary;\n  MarkNeedsPaint();\n}\n\nbool RenderObject::WasRepaintBoundary() {\n  // This is a hint for this RenderObject with repaint boundary.\n  if (GetContainerLayer()) {\n    return true;\n  }\n\n  return renderer_ && renderer_->Contains(this);\n}\n\nvoid RenderObject::SetRenderer(Renderer* renderer) {\n  if (renderer_ == renderer) {\n    return;\n  }\n\n  if (renderer_) {\n    // Remove from old one.\n    renderer_->RemoveDirtyNode(this);\n  }\n\n  renderer_ = renderer;\n\n  if (renderer_ && needs_paint_) {\n    needs_paint_ = false;\n    MarkNeedsPaint();\n  }\n\n  if (CanHaveChildren()) {\n    RenderObject* child = VirtualChildren().FirstChild();\n    while (child) {\n      child->SetRenderer(renderer_);\n      child = child->NextSibling();\n    }\n  }\n}\n\nvoid RenderObject::MarkSubtreeDirty() {\n  MarkNeedsPaint();\n  auto* child = VirtualChildren().FirstChild();\n  while (child) {\n    child->MarkSubtreeDirty();\n    child = child->NextSibling();\n  }\n}\n\nvoid RenderObject::MarkNeedsPaint(bool force_repaint) {\n  if (IsPainting()) {\n    return;\n  }\n\n  bool was_repaint_boundary = WasRepaintBoundary();\n  if (!force_repaint && needs_paint_ &&\n      was_repaint_boundary == IsRepaintBoundary()) {\n    // The current render object has been marked as dirty, no need to traverse,\n    // only request repaint.\n    if (renderer_) {\n      renderer_->RequestPaint();\n    }\n    return;\n  }\n\n  needs_paint_ = true;\n\n  if (was_repaint_boundary && !IsRepaintBoundary()) {\n    // When the render object changes from a repaint boundary to a non-boundary,\n    // we should move its descendant boundary layers (i.e. `OffsetLayer`s) to\n    // its parent (Here we just remove all children, the `OffsetLayer`s will be\n    // added to its parent automatically in next paint) and remove its layers\n    auto layer = GetLayer();\n    if (layer) {\n      layer->RemoveAllChildren();\n      layer->Remove();\n    }\n    ResetLayer();\n\n    if (renderer_) {\n      renderer_->RemoveDirtyNode(this);\n    }\n  }\n\n  if (IsRepaintBoundary()) {\n    // If we always have our own picture layer, then we can just repaint\n    // ourselves without involving any other nodes.\n    if (renderer_) {\n      if (!GetContainerLayer() && Parent()) {\n        // If there is no content layer on it, means it was not a repaint\n        // boundary before. And we should mark its parent to paint to rebuild\n        // the layer tree.\n        static_cast<RenderObject*>(Parent())->MarkNeedsPaint();\n      }\n\n      if (!needs_effect_) {\n        renderer_->AddNeedPaint(this);\n        renderer_->RequestPaint();\n      }\n    }\n    DestroyContainerLayer();\n  } else if (Parent()) {\n    static_cast<RenderObject*>(Parent())->MarkNeedsPaint();\n  } else {\n    // If we're the root of the render tree, then we have to paint ourselves.\n    if (renderer_) {\n      renderer_->RequestPaint();\n    }\n  }\n}\n\nvoid RenderObject::MarkNeedsEffect() {\n  // Changes of state of repaint_boundary will change the structure of UI layer\n  // tree. In this case we need to repaint the render object instead of just\n  // updating effect.\n  if (WasRepaintBoundary() != IsRepaintBoundary()) {\n    needs_effect_ = false;\n    MarkNeedsPaint();\n    return;\n  }\n\n  if (needs_effect_) {\n    return;\n  }\n\n  needs_effect_ = true;\n  if (IsRepaintBoundary()) {\n    // If we always have our own offset layer, then we can just update effect\n    // layers without involving any other nodes.\n    if (renderer_) {\n      if (!needs_paint_) {\n        renderer_->AddNeedPaint(this);\n        renderer_->RequestPaint();\n      }\n    }\n  } else if (Parent()) {\n    static_cast<RenderObject*>(Parent())->MarkNeedsEffect();\n  } else {\n    // If we're the root of the render tree, then we have to paint ourselves.\n    if (renderer_) {\n      renderer_->RequestPaint();\n    }\n  }\n}\n\nvoid RenderObject::DestroyContainerLayer() {\n  RemoveContainerLayerFromParent();\n  container_layer_.reset();\n  for (auto& effect_layer : effect_layers_) {\n    if (effect_layer) {\n      effect_layer->Remove();\n    }\n  }\n  std::fill(effect_layers_.begin(), effect_layers_.end(), nullptr);\n  if (compositor_layer_) {\n    compositor_layer_->RemoveAllChildren();\n    compositor_layer_->RetainLayer(nullptr);\n  }\n}\n\nvoid RenderObject::RemoveContainerLayerFromParent() {\n  if (!GetContainerLayer()) {\n    return;\n  }\n\n  PendingContainerLayer* parent =\n      static_cast<PendingContainerLayer*>(GetContainerLayer()->Parent());\n  if (parent) {\n    parent->RemoveChild(GetContainerLayer());\n  }\n}\n\nvoid RenderObject::PaintWithContext(PaintingContext& context,\n                                    const FloatPoint& offset) {\n  if (!CanDisplay()) {\n    // Resume painting status.\n    painting_ = false;\n    needs_paint_ = false;\n    needs_effect_ = false;\n    return;\n  }\n\n  painting_ = true;\n  needs_paint_ = false;\n  needs_effect_ = false;\n  // FIXME(fangzheyuan): Read IsRepaintBoundary before actual rendering starts.\n  // Some render object's properties(like 'repaint_boundary_') can be mutable in\n  // painting, this is a temporary workaround to avoid this scenario.\n  bool is_repaint_boundary = IsRepaintBoundary();\n\n  if (is_repaint_boundary && GetContainerLayer()) {\n    context.AddLayer(GetContainerLayer());\n  } else {\n    // If this object has shadow, should add shadow offset to show shadow.\n    Paint(context, offset + PaintOffsetEx());\n\n    if (is_repaint_boundary && !GetContainerLayer() &&\n        context.GetContainerLayer()) {\n      SetContainerLayer(\n          std::unique_ptr<PendingContainerLayer>(context.GetContainerLayer()));\n    }\n  }\n\n  // Check that the Paint() method didn't mark us dirty again.\n  FML_DCHECK(!needs_paint_);\n  FML_DCHECK(!needs_effect_);\n  painting_ = false;\n#ifdef ENABLE_RASTER_CACHE_SCALE\n  if (strategy_ == CacheStrategy::NotCache) {\n    // Reset strategy for self and descendant render objects.\n    SetCacheStrategyRecursively(CacheStrategy::None, false);\n  }\n#endif\n}\n\nvoid RenderObject::SetCacheStrategyRecursively(CacheStrategy strategy,\n                                               bool set_layer) {\n  strategy_ = strategy;\n  if (set_layer && compositor_layer_) {\n    compositor_layer_->SetCacheStrategyRecursively(strategy);\n  }\n  auto child = VirtualChildren().FirstChild();\n  while (child) {\n    child->SetCacheStrategyRecursively(strategy, set_layer);\n    child = child->NextSibling();\n  }\n}\n\nvoid RenderObject::WillPaint() {\n  RenderObject* child = VirtualChildren().FirstChild();\n  while (child) {\n    child->WillPaint();\n    child = child->NextSibling();\n  }\n}\n\n#ifndef NDEBUG\nvoid RenderObject::DumpRenderTree() const {\n  std::string intent;\n  intent.append(2 * Depth(), ' ');\n  FML_LOG(ERROR) << intent << \"[\" << GetName() << \"] \" << this << ToString();\n\n  RenderObject* child = VirtualChildren().FirstChild();\n  while (child) {\n    child->DumpRenderTree();\n    child = child->NextSibling();\n  }\n}\n\nstd::string RenderObject::ToString() const {\n  std::stringstream ss;\n  ss << \" id=\" << id_.ToString();\n  if (!Visible()) {\n    ss << \" visible=false\";\n  }\n  ss << \" frame=(\" << Left() << \",\" << Top() << \",\" << Width() << \",\"\n     << Height() << \")\";\n  if (PaddingLeft() != 0 || PaddingTop() != 0 || PaddingRight() != 0 ||\n      PaddingBottom() != 0) {\n    ss << \" padding=(\" << PaddingLeft() << \",\" << PaddingTop() << \",\"\n       << PaddingRight() << \",\" << PaddingBottom() << \")\";\n  }\n  if (GetLayer()) {\n    ss << \" \" << GetLayer()->GetName() << \"(\" << GetLayer() << \")\";\n  }\n  if (GetContainerLayer()) {\n    std::string children_info = \"children layers:[\";\n    auto* child = GetContainerLayer()->FirstChild();\n    while (child) {\n      children_info += child->GetName() + \",\";\n      child = child->NextSibling();\n    }\n    children_info += \"]\";\n    ss << \" Container(\" << GetContainerLayer() << \"),\" << children_info;\n  }\n  if (IsRepaintBoundary()) {\n    ss << \" (RepaintBoundary)\";\n  }\n  if (HasShadow()) {\n    ss << \" shadow=(\";\n    for (const auto& shadow : Shadows()) {\n      if (shadow.inset) {\n        ss << \"inset \";\n      }\n      ss << shadow.offset_x << \" \" << shadow.offset_y << \" \"\n         << shadow.blur_radius << \" \" << shadow.spread_radius << \" \"\n         << shadow.color.ToString();\n    }\n    ss << \")\";\n  }\n  if (HasBorder()) {\n    ss << \" \" << Border().ToString();\n  }\n  if (HasBackground()) {\n    if (Background().background_color.Alpha() != 0) {\n      ss << \" background-color=\" << Background().background_color.ToString();\n    }\n    if (!Background().background_images.empty() &&\n        Background().background_images.at(0).type ==\n            ClayBackgroundImageType::kUrl) {\n      ss << \" background-image=(\"\n         << Background().background_images.at(0).src_str << \")\";\n    }\n    if (!Background().images.empty()) {\n      if (Background().images.at(0).GetImageResource() != nullptr) {\n#ifndef ENABLE_SKITY\n        ss << \" images=(\"\n           << Background().images.at(0).GetImageResource()->GetUrl() << \")\";\n#endif  // ENABLE_SKITY\n      } else {\n        ss << \" images=(\"\n           << \"gradient\"\n           << \")\";\n      }\n    }\n  }\n  return ss.str();\n}\n#endif\n\nbool RenderObject::HasTransition(ClayAnimationPropertyType type) const {\n  return AnimationPropertyTest(raster_transition_properties_, type);\n}\n\nvoid RenderObject::MarkHasTransition(ClayAnimationPropertyType type,\n                                     bool value) {\n  AnimationPropertySetIf(raster_transition_properties_, type, value);\n}\n\nbool RenderObject::HasAnimation(ClayAnimationPropertyType type) const {\n  return AnimationPropertyTest(raster_animation_properties_, type);\n}\n\nvoid RenderObject::MarkHasAnimation(ClayAnimationPropertyType type,\n                                    bool value) {\n  AnimationPropertySetIf(raster_animation_properties_, type, value);\n}\n\nvoid RenderObject::SetPaintingOrder(int painting_order) {\n  if (painting_order_ == painting_order) {\n    return;\n  }\n  painting_order_ = painting_order;\n\n  if (auto* parent = static_cast<RenderObject*>(Parent())) {\n    parent->DirtyChildrenPaintingOrder();\n  }\n}\n\nbool RenderObject::HasTransformExpansion() const {\n  const RenderObject* object = this;\n  skity::Matrix transform;\n  while (object) {\n    transform.PostConcat(object->GetTransform().matrix());\n    object = static_cast<const RenderObject*>(object->Parent());\n  }\n  if (transform.Get(0, 0) > 1 || transform.Get(1, 1) > 1) {\n    return true;\n  }\n  return false;\n}\n\nbool RenderObject::HasBackgroundClipText() const {\n  if (!HasBackground()) {\n    return false;\n  }\n  const BackgroundData& background = Background();\n  for (auto type : background.clips) {\n    if (ClayBackgroundClipType::kText == type) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool RenderObject::CanApplyAA() const {\n  // Currently if a node has mask-image, then it can not apply AA when drawing\n  // background.\n  const RenderObject* node = this;\n  do {\n    if (node->IsRepaintBoundary()) {\n      return !node->HasMask();\n    }\n    node = static_cast<const RenderObject*>(node->Parent());\n  } while (node);\n  return true;\n}\n\nvoid RenderObject::DecodeImages() {\n#ifndef ENABLE_SKITY\n  if (!HasBackground()) {\n    return;\n  }\n  // Decode background images.\n  for (auto& image : Background().images) {\n    if (image.IsSkImage() &&\n        image.GetImageResource()->GetImage()->NeedDecode()) {\n      DecodePriority priority = DecodeUtils::GetDecodePriority(this);\n      image.GetImageResource()->GetGraphicsImage(priority);\n    }\n  }\n#endif  // ENABLE_SKITY\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_object.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_OBJECT_H_\n#define CLAY_UI_RENDERING_RENDER_OBJECT_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <utility>\n#include <variant>\n#include <vector>\n\n#include \"clay/common/element_id.h\"\n#include \"clay/gfx/animation/keyframes_manager.h\"\n#include \"clay/gfx/animation/transition_manager.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/transform.h\"\n#include \"clay/gfx/style/borders_data.h\"\n#include \"clay/gfx/style/box_data.h\"\n#include \"clay/gfx/style/outline_data.h\"\n#include \"clay/gfx/style/shadow.h\"\n#include \"clay/public/clay.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/ui/common/background_data.h\"\n#include \"clay/ui/component/css_property.h\"\n#include \"clay/ui/compositing/pending_container_layer.h\"\n#include \"clay/ui/compositing/pending_effect_layer.h\"\n#include \"clay/ui/compositing/pending_layer.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/abstract_node.h\"\n#include \"clay/ui/rendering/render_object_child_list.h\"\n#include \"clay/ui/rendering/renderer.h\"\n\nnamespace clay {\n\nusing CacheStrategy = clay::CacheStrategy;\nusing PaintFunction = std::function<void(PaintingContext&, const FloatPoint&)>;\nclass BackgroundOrMaskImageClient;\n\n// An object in the render tree.\nclass RenderObject : public AbstractNode {\n  friend class MockRenderObject;\n  friend class RenderObjectChildList;\n\n public:\n  RenderObject();\n  RenderObject(const RenderObject&) = delete;\n  RenderObject& operator=(const RenderObject&) = delete;\n  virtual ~RenderObject();\n\n  virtual const char* GetName() const { return \"RenderObject\"; }\n\n  std::string DebugName() const;\n\n  int ID() const { return id_.view_id(); }\n  const ElementId& element_id() const { return id_; }\n  void SetID(int id) { id_.UpdateViewId(id); }\n  bool HasValidID() const { return id_.view_id() != -1; }\n\n  bool IsDescendantOf(const RenderObject*) const;\n\n  RenderObject* PreviousSibling() const { return previous_; }\n  RenderObject* NextSibling() const { return next_; }\n\n  RenderObject* SlowFirstChild() const {\n    return VirtualChildren().FirstChild();\n  }\n\n  RenderObject* SlowLastChild() const { return VirtualChildren().LastChild(); }\n\n  static void ChangeDefaultOverflowValue(bool default_overflow_visible) {\n    default_overflow_ = default_overflow_visible ? CSSProperty::OVERFLOW_XY\n                                                 : CSSProperty::OVERFLOW_HIDDEN;\n  }\n\n  static uint8_t DefaultOverflowValue() { return default_overflow_; }\n\n  virtual bool HasClip() const { return false; }\n  bool HasOverflowClip() const { return overflow_ != CSSProperty::OVERFLOW_XY; }\n  virtual PaintFunction FixupPainterIfNeeded(const PaintFunction& painter) {\n    return painter;\n  }\n  bool HasClipOrOverflowClip() const { return HasClip() || HasOverflowClip(); }\n  uint8_t Overflow() const { return overflow_; }\n\n  // RenderObject tree traverse.\n  RenderObject* NextInPreOrder() const;\n  RenderObject* NextInPreOrder(const RenderObject* stay_within) const;\n  RenderObject* NextInPreOrderAfterChildren() const;\n  RenderObject* NextInPreOrderAfterChildren(\n      const RenderObject* stay_within) const;\n  RenderObject* PreviousInPreOrder() const;\n  RenderObject* PreviousInPreOrder(const RenderObject* stay_within) const;\n  RenderObject* ChildAt(unsigned) const;\n\n  RenderObject* LastLeafChild() const;\n\n  RenderObjectChildList& VirtualChildren() { return children_; }\n  const RenderObjectChildList& VirtualChildren() const { return children_; }\n\n  // RenderObject tree manipulation.\n  bool CanHaveChildren() const { return VirtualChildren().FirstChild(); }\n  void AddChild(RenderObject* new_child, RenderObject* before_child = nullptr);\n  void RemoveChild(RenderObject*);\n  void RemoveAllChildren();\n  void RemoveAndCollectChildren(std::vector<RenderObject*>&);\n\n  void Remove() {\n    if (Parent()) {\n      static_cast<RenderObject*>(Parent())->RemoveChild(this);\n    }\n  }\n\n  void BringChildToFront(RenderObject* child);\n\n  // The box model information.\n  void SetLeft(float left);\n  void SetTop(float top);\n  void SetWidth(float width);\n  void SetHeight(float height);\n  float Left() const { return box_data_.Left(); }\n  float Top() const { return box_data_.Top(); }\n  float Width() const { return box_data_.Width(); }\n  float Height() const { return box_data_.Height(); }\n\n  virtual float ScrollLeft() const { return 0.0f; }\n  virtual float ScrollTop() const { return 0.0f; }\n  void ClearFilter();\n  void SetOpacity(float opacity, bool is_from_animation = false);\n  void SetBlurRadius(float radius);\n  void AppendGrayScale(float scale);\n  void SetBackdropBlurRadius(float radius);\n  void SetOverflow(uint8_t overflow);\n  void SetClipPath(const FloatRoundedRect& rrect);\n  void SetClipPath(const GrPath& path);\n  void SetOffsetPath(const FloatRoundedRect& rrect);\n  void SetOffsetPath(const GrPath& path);\n  void ClearClipPath();\n  void ClearOffsetPath();\n  void SetOffsetRotate(float rotate);\n  void SetOffsetDistance(float distance);\n  virtual bool IsRenderBounceView() const { return false; }\n  virtual bool IsScrollable() const { return false; }\n\n  float Opacity() const { return *opacity_; }\n  float BlurRadius() const { return blur_radius_; }\n  using ClipPathType = std::variant<std::monostate, FloatRoundedRect, GrPath>;\n  ClipPathType& ClipPath();\n\n  bool HasOpacity() const { return opacity_.has_value() && Opacity() != 1.0f; }\n  bool HasBlur() const { return blur_radius_ > 0.f; }\n\n  void AppendBrightness(float brightness);\n  void AppendContrast(float contrast);\n  void AppendSaturate(float saturate);\n  void AppendHueRotate(float deg);\n  void AppendCustomColorMatrix(const std::array<float, 20>& matrix);\n  bool HasColorFilter() const { return !current_color_filter_matrix_.empty(); }\n  const std::vector<float>& ColorFilterMatrix() const {\n    return current_color_filter_matrix_;\n  }\n\n  bool HasClipPath() const {\n    return !std::holds_alternative<std::monostate>(clip_shape_);\n  }\n\n  virtual bool IsExternalView() const { return false; }\n  void SetOverlay(bool is_overlay) { overlay_ = is_overlay; }\n  bool IsOverlay() const { return overlay_; }\n  void SetOverlayLevel(int level) { overlay_level_ = level; }\n  int OverlayLevel() const { return overlay_level_; }\n\n  float HorizontalThickness() const {\n    return PaddingLeft() + PaddingRight() + BorderLeft() + BorderRight();\n  }\n  float VerticalThickness() const {\n    return PaddingTop() + PaddingBottom() + BorderTop() + BorderBottom();\n  }\n\n  FloatPoint location() const {\n    return FloatPoint(box_data_.Left(), box_data_.Top());\n  }\n\n  FloatRect GetFrameRect() const {\n    return FloatRect(box_data_.Left(), box_data_.Top(), box_data_.Width(),\n                     box_data_.Height());\n  }\n\n  virtual bool CanDisplay() {\n    if (!Visible()) {\n      return false;\n    }\n    return (box_data_.Width() > 0.f && box_data_.Height() > 0.f) ||\n           !HasOverflowClip();\n  }\n\n  // When the render object has a layer, use this bounds to construct the\n  // |PaintingContext|.  Usually FrameRect is equal to BoxRect, but when the\n  // render object has Shadow or Outline, FrameRect is the union of BoxRect and\n  // ShadowRect and OutlineRect.\n  FloatRect GetFrameRectEx() const;\n\n  FloatPoint OffsetInLayer() const;\n\n  FloatPoint PaintOffset() const {\n    return FloatPoint(BorderLeft() + PaddingLeft(), BorderTop() + PaddingTop());\n  }\n\n  FloatPoint ClipOffset() const {\n    return FloatPoint(BorderLeft(), BorderTop());\n  }\n\n  float PaddingLeft() const { return box_data_.PaddingLeft(); }\n  float PaddingRight() const { return box_data_.PaddingRight(); }\n  float PaddingTop() const { return box_data_.PaddingTop(); }\n  float PaddingBottom() const { return box_data_.PaddingBottom(); }\n  void SetPaddingLeft(float left);\n  void SetPaddingRight(float right);\n  void SetPaddingTop(float top);\n  void SetPaddingBottom(float bottom);\n\n  float MarginLeft() const { return box_data_.MarginLeft(); }\n  float MarginRight() const { return box_data_.MarginRight(); }\n  float MarginTop() const { return box_data_.MarginTop(); }\n  float MarginBottom() const { return box_data_.MarginBottom(); }\n  void SetMarginLeft(float left) { box_data_.SetMarginLeft(left); }\n  void SetMarginRight(float right) { box_data_.SetMarginRight(right); }\n  void SetMarginTop(float top) { box_data_.SetMarginTop(top); }\n  void SetMarginBottom(float bottom) { box_data_.SetMarginBottom(bottom); }\n\n  void SetBorders(const BordersData& borders_data);\n  bool HasBorder() const { return border_.has_value(); }\n  const BordersData& Border() const { return *border_; }\n  BordersData& MutableBorder() {\n    if (!border_.has_value()) {\n      border_ = std::make_optional<BordersData>(BordersData());\n    }\n    return *border_;\n  }\n\n  float BorderLeft() const { return HasBorder() ? border_->width_left_ : 0.f; }\n  float BorderTop() const { return HasBorder() ? border_->width_top_ : 0.f; }\n  float BorderRight() const {\n    return HasBorder() ? border_->width_right_ : 0.f;\n  }\n  float BorderBottom() const {\n    return HasBorder() ? border_->width_bottom_ : 0.f;\n  }\n\n  void SetOutline(const OutlineData& outline_data);\n  bool HasOutline() const { return outline_.has_value(); }\n  const OutlineData& Outline() const { return *outline_; }\n  OutlineData& MutableOutline() {\n    if (!outline_.has_value()) {\n      outline_ = std::make_optional<OutlineData>(OutlineData());\n    }\n    return *outline_;\n  }\n\n  bool IsPainting() const { return painting_; }\n  void SetVisible(bool visible);\n  bool Visible() const { return visible_; }\n  bool IsActualVisible();\n\n  void SetShadow(const Shadow& shadow);\n  void SetShadows(std::vector<Shadow>&& shadows);\n  bool HasShadow() const { return shadows_.has_value() && !shadows_->empty(); }\n  const std::vector<Shadow>& Shadows() const { return *shadows_; }\n\n  void SetBackgroundColor(const Color& color,\n                          bool skip_update_for_raster_animation = false);\n  void SetBackgroundData(const BackgroundData& background_data);\n  bool HasBackground() const { return background_data_.has_value(); }\n  const BackgroundData& Background() const { return *background_data_; }\n  void ResizeBackground(size_t size);\n  void ResizeMask(size_t size);\n\n  bool HasMask() const { return mask_data_.has_value(); }\n  const MaskData& Mask() const { return *mask_data_; }\n\n  virtual void DecodeImages();\n\n#ifndef ENABLE_SKITY\n  void SetBackgroundImage(size_t index,\n                          std::unique_ptr<ImageResource> accessor);\n  void SetMaskImage(size_t index, std::unique_ptr<ImageResource> accessor);\n#else\n  void SetBackgroundImage(size_t index,\n                          std::unique_ptr<BaseImageInstance> image);\n  void SetMaskImage(size_t index, std::unique_ptr<BaseImageInstance> image);\n#endif  // ENABLE_SKITY\n  void SetBackgroundImage(size_t index, const Gradient& gradient);\n  void SetBackgroundClip(const std::vector<ClayBackgroundClipType>& clips);\n  void SetBackgroundOrigin(\n      const std::vector<ClayBackgroundOriginType>& origins);\n  void SetBackgroundPosition(const std::vector<BackgroundPosition>& positions);\n  void SetBackgroundRepeat(\n      const std::vector<ClayBackgroundRepeatType>& repeats);\n  void SetBackgroundSize(const std::vector<BackgroundSize>& sizes);\n\n  void SetMaskImage(size_t index, const Gradient& gradient);\n  void SetMaskPosition(const std::vector<MaskPosition>& positions);\n  void SetMaskRepeat(const std::vector<ClayMaskRepeatType>& repeats);\n  void SetMaskOrigin(const std::vector<ClayMaskOriginType>& origins);\n  void SetMaskSize(const std::vector<MaskSize>& mask_sizes);\n  void SetMaskClip(const std::vector<ClayMaskClipType>& clips);\n\n  void SetHasDefaultFocusRing(bool has_default_focus_ring);\n  bool HasDefaultFocusRing() const { return has_default_focus_ring_; }\n\n  void SetImageFilterMode(FilterMode mode);\n  FilterMode ImageFilterMode() const { return image_filter_mode_; }\n\n  void SetTransformOperations(const TransformOperations& transform,\n                              bool is_from_animation = false);\n  void SetTransformOrigin(const FloatPoint& origin);\n  void SetPerspective(float value);\n  bool HasTransform() const {\n    return transform_.has_value() && !transform_->IsIdentity();\n  }\n  bool HasOffsetTransform() const { return offset_transform_.has_value(); }\n  bool HasTransformOperations() const;\n  const TransformOperations& GetTransformOperations() const {\n    return *transform_;\n  }\n  const TransformOperations& GetOffsetTransformOperations() const {\n    return *offset_transform_;\n  }\n  Transform GetTransform() const {\n    return transform_.has_value() ? (*transform_).Apply() : Transform();\n  }\n  const FloatPoint& GetTransformOrigin() const { return transform_origin_; }\n  bool HasPerspective() const { return perspective_.has_value(); }\n  float GetPerspective() const { return perspective_.value_or(0.f); }\n\n  void RedepthChildren() override;\n\n  void SetLayer(std::unique_ptr<PendingContainerLayer> layer) {\n    compositor_layer_ = std::move(layer);\n    compositor_layer_->Attach(this);\n  }\n  void ResetLayer() {\n    RemoveContainerLayerFromParent();\n    compositor_layer_.reset();\n    if (NeedsPaint()) {\n      container_layer_.reset();\n    }\n  }\n  PendingContainerLayer* GetLayer() const { return compositor_layer_.get(); }\n\n  void SetContainerLayer(std::unique_ptr<PendingContainerLayer> layer) {\n    container_layer_ = std::move(layer);\n    container_layer_->Attach(this);\n  }\n\n  PendingContainerLayer* GetContainerLayer() const {\n    return container_layer_.get();\n  }\n\n  PendingEffectLayer* GetOrCreateEffectLayer(EffectType type) {\n    size_t type_index = static_cast<size_t>(type);\n    if (!effect_layers_[type_index]) {\n      std::unique_ptr<PendingEffectLayer> layer =\n          std::make_unique<PendingEffectLayer>();\n      layer->Attach(this);\n      effect_layers_[type_index] = std::move(layer);\n    }\n    return effect_layers_[type_index].get();\n  }\n\n  void UnloadAllEffectLayers() {\n    for (auto& layer : effect_layers_) {\n      if (layer) {\n        layer->Remove();\n        layer->RemoveAllChildren();\n      }\n    }\n  }\n\n  // Only used for when a view is added, the whole subtree should mark dirty.\n  void MarkSubtreeDirty();\n  void MarkNeedsPaint(bool force_repaint = false);\n  bool NeedsPaint() const { return needs_paint_; }\n\n  void MarkNeedsEffect();\n  bool NeedsEffect() const { return needs_effect_; }\n\n  Renderer* GetRenderer() { return renderer_; }\n  void SetRenderer(Renderer* renderer);\n  void SetRepaintBoundary(bool repaint_boundary);\n  virtual bool IsRepaintBoundary() const {\n    return repaint_boundary_ || HasShadow() || HasTransform() ||\n           HasOffsetTransform() || HasPerspective() || HasOpacity() ||\n           HasClipOrOverflowClip() || HasBlur() || HasColorFilter() ||\n           HasClipPath() || HasBackgroundClipText() || HasMask();\n  }\n\n  bool CanApplyAA() const;\n\n  void SetCacheStrategyRecursively(CacheStrategy strategy,\n                                   bool set_layer = true);\n  CacheStrategy GetCacheStrategy() const { return strategy_; }\n\n  // Don't override this function instead using Paint.\n  void PaintWithContext(PaintingContext& context, const FloatPoint& offset);\n\n  // Just paint self.\n  //            Layer [Canvas]\n  //    -----------------------------\n  //    |\\                          |\n  //    | \\ Offset                  |\n  //    |  \\                        |\n  //    |   \\___________            |\n  //    |   |\\_________/|PaintOffset|\n  //    |   ||         ||           |\n  //    |   || content ||           |\n  //    |   ||         ||           |\n  //    |   ||_________||           |\n  //    |   |/_________\\|           |\n  //    |                           |\n  //    |                           |\n  //    |                           |\n  //    |___________________________|\n  //\n  //\n  // Offset = the distance to Canvas, that is, the offset of the layer.\n  // PaintOffset = Border + Padding.\n  // Paint order:\n  // 1. Translate canvas by Offset;\n  // 2. Paint background, shadow, border ...\n  // 3. Translate canvas by PaintOffset;\n  // 4. Paint content;\n  // 5. Paint Children.\n  virtual void Paint(PaintingContext& context, const FloatPoint& offset) = 0;\n  // Paint children and their children.\n  virtual void PaintChildren(PaintingContext& context,\n                             const FloatPoint& offset) {}\n  virtual void WillPaint();\n  void Destroy();\n  // Validate if this node is qualified for paint.\n  // Once a node is not visible, all its descendants are not qualified,\n  // meanwhile this node should be removed from renderer_.\n  void ValidateForPaint(bool parent_visibility);\n\n#ifndef NDEBUG\n  void DumpRenderTree() const;\n  virtual std::string ToString() const;\n  bool EnableRepaintBoundaryBorders() const { return false; }\n#endif\n\n  bool HasTransition(ClayAnimationPropertyType type) const;\n  void MarkHasTransition(ClayAnimationPropertyType type, bool value);\n\n  bool HasAnimation(ClayAnimationPropertyType type) const;\n  void MarkHasAnimation(ClayAnimationPropertyType type, bool value);\n\n  bool HasOpacityRasterAnimation() const {\n    return HasTransition(ClayAnimationPropertyType::kOpacity) ||\n           HasAnimation(ClayAnimationPropertyType::kOpacity);\n  }\n  bool HasTransformRasterAnimation() const {\n    return HasTransition(ClayAnimationPropertyType::kTransform) ||\n           HasAnimation(ClayAnimationPropertyType::kTransform);\n  }\n  bool HasBackgroundColorRasterAnimation() const {\n    return HasTransition(ClayAnimationPropertyType::kBackgroundColor) ||\n           HasAnimation(ClayAnimationPropertyType::kBackgroundColor);\n  }\n  bool HasColorRasterAnimation() const {\n    return HasTransition(ClayAnimationPropertyType::kColor) ||\n           HasAnimation(ClayAnimationPropertyType::kColor);\n  }\n\n  bool HasRasterAnimation() const {\n    return HasOpacityRasterAnimation() || HasTransformRasterAnimation() ||\n           HasBackgroundColorRasterAnimation() || HasColorRasterAnimation();\n  }\n\n  bool HasBackgroundClipText() const;\n\n  TransitionManager* GetTransitionManager() const {\n    return transition_manager_;\n  }\n\n  void SetTransitionManager(TransitionManager* manager) {\n    transition_manager_ = manager;\n  }\n\n  KeyframesManager* GetKeyframesManager() const { return keyframes_manager_; }\n\n  void SetKeyframesManager(KeyframesManager* manager) {\n    keyframes_manager_ = manager;\n  }\n\n  float GetTranslateZ() const { return translate_z_; }\n\n  void DirtyChildrenPaintingOrder() {\n    if (sorted_children_.empty()) {\n      return;\n    }\n    sorted_children_.clear();\n    MarkNeedsPaint();\n  }\n\n  void SetPaintingOrder(int painting_order);\n  int GetPaintingOrder() const { return painting_order_; }\n\n  void CombineColorFilter(const std::vector<float>& matrix);\n\n  void SetImageDecodeWithPriority(bool value) {\n    image_decode_with_priority_ = value;\n  }\n  bool ImageDecodeWithPriority() const { return image_decode_with_priority_; }\n\n protected:\n  // Helper functions.\n  void SetPreviousSibling(RenderObject* previous) { previous_ = previous; }\n  void SetNextSibling(RenderObject* next) { next_ = next; }\n\n  void VisitChildren(const std::function<void(RenderObject*)> visitor);\n\n  bool HasTransformExpansion() const;\n\n  Renderer* renderer_ = nullptr;\n\n private:\n  struct MotionPath {\n    std::optional<GrPath> offset_path;\n    float offset_rotate = 0.f;\n    float offset_distance = 0.f;\n    bool offset_rotate_auto = true;\n  };\n\n  FloatPoint PaintOffsetEx() const;\n\n  bool WasRepaintBoundary();\n\n  // Destroy the old container layer for the dirty render object.\n  void DestroyContainerLayer();\n\n  // Remove the container layer from Parent for reusing.\n  void RemoveContainerLayerFromParent();\n\n  void RebuildSortedChildrenIfNeeded();\n  void ClearColorFilter();\n  void ClearImageFilter();\n\n  void UpdateOffsetTransform(const skity::Matrix& matrix);\n\n  ElementId id_{-1};\n\n  // Whether this render object repaints separately from its parent.\n  bool repaint_boundary_ = false;\n  // Whether this render object's paint information is dirty.\n  bool needs_paint_ = true;\n  // Whether this render object's paint effect is updated, if needs_paint_ is\n  // false, we only update effects and reuse the last draw recording.\n  bool needs_effect_ = false;\n\n  bool visible_ = true;\n\n  bool painting_ = false;\n\n  bool overlay_ = false;\n  bool is_overlay_ng_ = false;\n  //  1 to 4 . 4 levels totally. level 4 is the top level, and level 1 is the\n  //  bottom level.\n  int overlay_level_ = 1;\n\n  // hack for demo\n  bool has_default_focus_ring_ = false;\n\n  CacheStrategy strategy_ = CacheStrategy::None;\n\n  // The width/height of the contents + borders + padding.  The left/top\n  // location is relative to our container (which is not always our parent).\n  BoxData box_data_;\n  std::optional<BackgroundData> background_data_;\n  std::optional<MaskData> mask_data_;\n  std::optional<BordersData> border_;\n  std::optional<OutlineData> outline_;\n  std::optional<std::vector<Shadow>> shadows_;\n  std::optional<TransformOperations> offset_transform_;\n  std::optional<TransformOperations> transform_;\n  std::optional<float> perspective_;\n  FloatPoint transform_origin_;\n  std::optional<float> opacity_ = std::nullopt;\n  float blur_radius_ = 0.f;\n  std::vector<float> current_color_filter_matrix_;\n  ClipPathType clip_shape_;\n  MotionPath motion_path_;\n  FilterMode image_filter_mode_ = FilterMode::kLinear;\n  ClayAnimationPropertyType raster_transition_properties_;\n  ClayAnimationPropertyType raster_animation_properties_;\n  TransitionManager* transition_manager_;\n  KeyframesManager* keyframes_manager_;\n\n  RenderObject* previous_;\n  RenderObject* next_;\n\n  RenderObjectChildList children_;\n\n  std::unique_ptr<BackgroundOrMaskImageClient> bg_image_client_;\n  std::unique_ptr<BackgroundOrMaskImageClient> mask_image_client_;\n  // The compositor_layer_ is the root of the composited layer subtree.\n  std::unique_ptr<PendingContainerLayer> compositor_layer_;\n  // The container_layer_ is the root of the content layer and other subtree.\n  std::unique_ptr<PendingContainerLayer> container_layer_;\n  std::array<std::unique_ptr<PendingEffectLayer>,\n             static_cast<size_t>(EffectType::kLast) + 1u>\n      effect_layers_;\n  uint8_t overflow_ = DefaultOverflowValue();\n\n  static uint8_t default_overflow_;\n\n  float translate_z_ = 0.f;\n  int painting_order_ = 0;\n  std::vector<RenderObject*> sorted_children_;\n\n  bool image_decode_with_priority_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_OBJECT_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_object_child_list.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_object_child_list.h\"\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/rendering/render_object.h\"\n\nnamespace clay {\n\nvoid RenderObjectChildList::DestroyLeftoverChildren() {\n  while (FirstChild()) {\n    FirstChild()->Destroy();\n  }\n}\n\nint RenderObjectChildList::IndexOfChild(RenderObject* child) const {\n  RenderObject* first = FirstChild();\n  for (int index = 0; first != nullptr; index++) {\n    if (first == child) {\n      return index;\n    }\n    first = first->NextSibling();\n  }\n  return -1;\n}\n\nvoid RenderObjectChildList::RemoveChild(RenderObject* owner,\n                                        RenderObject* old_child) {\n  FML_DCHECK(old_child->Parent() == owner);\n\n  if (old_child->PreviousSibling()) {\n    old_child->PreviousSibling()->SetNextSibling(old_child->NextSibling());\n  }\n  if (old_child->NextSibling()) {\n    old_child->NextSibling()->SetPreviousSibling(old_child->PreviousSibling());\n  }\n\n  if (FirstChild() == old_child) {\n    SetFirstChild(old_child->NextSibling());\n  }\n  if (LastChild() == old_child) {\n    SetLastChild(old_child->PreviousSibling());\n  }\n\n  old_child->SetPreviousSibling(nullptr);\n  old_child->SetNextSibling(nullptr);\n\n  if (owner) {\n    owner->DirtyChildrenPaintingOrder();\n  }\n}\n\nvoid RenderObjectChildList::InsertChild(RenderObject* owner,\n                                        RenderObject* new_child,\n                                        RenderObject* before_child) {\n  while (before_child && before_child->Parent() &&\n         before_child->Parent() != owner) {\n    before_child = static_cast<RenderObject*>(before_child->Parent());\n  }\n\n  // This should never happen, but if it does prevent render tree corruption\n  // where child->parent() ends up being owner but\n  // child->NextSibling()->Parent() is not owner.\n  if (before_child && before_child->Parent() != owner) {\n    FML_UNREACHABLE();\n    return;\n  }\n\n  if (FirstChild() == before_child) {\n    SetFirstChild(new_child);\n  }\n\n  if (before_child) {\n    RenderObject* previous_sibling = before_child->PreviousSibling();\n    if (previous_sibling) {\n      previous_sibling->SetNextSibling(new_child);\n    }\n    new_child->SetPreviousSibling(previous_sibling);\n    new_child->SetNextSibling(before_child);\n    before_child->SetPreviousSibling(new_child);\n  } else {\n    if (LastChild()) {\n      LastChild()->SetNextSibling(new_child);\n    }\n    new_child->SetPreviousSibling(LastChild());\n    SetLastChild(new_child);\n  }\n\n  if (owner) {\n    owner->DirtyChildrenPaintingOrder();\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_object_child_list.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_OBJECT_CHILD_LIST_H_\n#define CLAY_UI_RENDERING_RENDER_OBJECT_CHILD_LIST_H_\n\nnamespace clay {\n\nclass RenderObject;\n\n// Provides a child model for a render object subclass that has a doubly-linked\n// list of children.\nclass RenderObjectChildList {\n public:\n  RenderObjectChildList() : first_child_(nullptr), last_child_(nullptr) {}\n\n  RenderObject* FirstChild() const { return first_child_; }\n  RenderObject* LastChild() const { return last_child_; }\n\n  void SetFirstChild(RenderObject* child) { first_child_ = child; }\n  void SetLastChild(RenderObject* child) { last_child_ = child; }\n\n  void DestroyLeftoverChildren();\n  int IndexOfChild(RenderObject* child) const;\n\n  void RemoveChild(RenderObject* owner, RenderObject*);\n  void InsertChild(RenderObject* owner, RenderObject* new_child,\n                   RenderObject* before_child);\n\n  void AppendChild(RenderObject* owner, RenderObject* new_child) {\n    InsertChild(owner, new_child, nullptr);\n  }\n\n private:\n  RenderObject* first_child_;\n  RenderObject* last_child_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_OBJECT_CHILD_LIST_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_object_child_list_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/rendering/render_object.h\"\n#include \"clay/ui/rendering/render_object_child_list.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nnamespace {\nclass MockRenderObject : public RenderObject {\n public:\n  MOCK_METHOD(const char*, GetName, (), (const, override));\n  MOCK_METHOD(void, Paint, (PaintingContext & context, const FloatPoint&),\n              (override));\n  MOCK_METHOD(void, PaintChildren,\n              (PaintingContext & context, const FloatPoint&), (override));\n};\n}  // namespace\n\nTEST(RenderObjectChildListTest, Basic) {\n  MockRenderObject rootNode;\n  MockRenderObject childNode1;\n  MockRenderObject childNode2;\n  MockRenderObject childNode3;\n  RenderObjectChildList childrenList;\n\n  rootNode.AdoptChild(&childNode1);\n  childrenList.AppendChild(&rootNode, &childNode1);\n  EXPECT_EQ(childrenList.FirstChild(), childrenList.LastChild());\n  EXPECT_EQ(childrenList.FirstChild(), &childNode1);\n  EXPECT_EQ(childrenList.IndexOfChild(&childNode1), 0);\n\n  rootNode.AdoptChild(&childNode3);\n  childrenList.InsertChild(&rootNode, &childNode3, nullptr);\n  rootNode.AdoptChild(&childNode2);\n  childrenList.InsertChild(&rootNode, &childNode2, &childNode3);\n  EXPECT_EQ(childrenList.FirstChild(), &childNode1);\n  EXPECT_EQ(childrenList.LastChild(), &childNode3);\n  EXPECT_EQ(childrenList.IndexOfChild(&childNode1), 0);\n  EXPECT_EQ(childrenList.IndexOfChild(&childNode2), 1);\n  EXPECT_EQ(childrenList.IndexOfChild(&childNode3), 2);\n\n  childrenList.RemoveChild(&rootNode, &childNode2);\n  rootNode.DropChild(&childNode2);\n  EXPECT_EQ(childrenList.FirstChild(), &childNode1);\n  EXPECT_EQ(childrenList.LastChild(), &childNode3);\n  EXPECT_EQ(childNode1.PreviousSibling(), nullptr);\n  EXPECT_EQ(childNode1.NextSibling(), &childNode3);\n  EXPECT_EQ(childNode3.PreviousSibling(), &childNode1);\n  EXPECT_EQ(childNode3.NextSibling(), nullptr);\n\n  childrenList.RemoveChild(&rootNode, &childNode1);\n  rootNode.DropChild(&childNode1);\n  childrenList.RemoveChild(&rootNode, &childNode3);\n  rootNode.DropChild(&childNode3);\n  EXPECT_EQ(childrenList.FirstChild(), childrenList.LastChild());\n  EXPECT_EQ(childrenList.FirstChild(), nullptr);\n  EXPECT_EQ(childrenList.IndexOfChild(&childNode1), -1);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_object_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/rendering/render_box.h\"\n#include \"clay/ui/rendering/render_image.h\"\n#include \"clay/ui/rendering/render_object.h\"\n#include \"clay/ui/rendering/text/render_text.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nclass MockRenderObject : public RenderObject {\n public:\n  MOCK_METHOD(const char*, GetName, (), (const, override));\n  MOCK_METHOD(void, Paint, (PaintingContext & context, const FloatPoint&),\n              (override));\n  MOCK_METHOD(void, PaintChildren,\n              (PaintingContext & context, const FloatPoint&), (override));\n\n  bool ChildrenPaintingOrderIsDirtyForTesting() const {\n    return children_.FirstChild() && sorted_children_.empty();\n  }\n\n  const std::vector<RenderObject*>& GetSortedChildrenForTesting() {\n    RebuildSortedChildrenIfNeeded();\n    return sorted_children_;\n  }\n};\n\nclass RenderObjectTest : public ::testing::Test {\n protected:\n  void SetUp() override {\n    //             0\n    //          /    \\\n    //         1       2\n    //       / | \\      \\\n    //      3  4  5      6\n    // set up a tree as above\n    for (int i = 0; i <= 6; i++)\n      nodeList.push_back(std::make_unique<MockRenderObject>());\n\n    nodeList[0]->AddChild(nodeList[1].get());\n    nodeList[0]->AddChild(nodeList[2].get());\n    nodeList[1]->AddChild(nodeList[3].get());\n    nodeList[1]->AddChild(nodeList[4].get());\n    nodeList[1]->AddChild(nodeList[5].get());\n    nodeList[2]->AddChild(nodeList[6].get());\n  }\n\n  std::vector<std::unique_ptr<MockRenderObject>> nodeList;\n};\n\nTEST_F(RenderObjectTest, TreeTraversal) {\n  EXPECT_EQ(nodeList[0]->PreviousInPreOrder(), nullptr);\n  EXPECT_EQ(nodeList[6]->NextInPreOrder(), nullptr);\n\n  RenderObject* currentNode = nodeList[0].get();\n  for (size_t i = 0; i < 6; i++) {\n    currentNode = currentNode->NextInPreOrder();\n  }\n  EXPECT_EQ(currentNode, nodeList[6].get());\n\n  for (size_t i = 0; i < 6; i++) {\n    currentNode = currentNode->PreviousInPreOrder();\n  }\n  EXPECT_EQ(currentNode, nodeList[0].get());\n}\n\nTEST_F(RenderObjectTest, MiscOperation) {\n  EXPECT_EQ(nodeList[0]->ChildAt(0), nodeList[1].get());\n  EXPECT_EQ(nodeList[0]->ChildAt(1), nodeList[2].get());\n  EXPECT_EQ(nodeList[1]->ChildAt(1), nodeList[4].get());\n  EXPECT_EQ(nodeList[1]->ChildAt(2), nodeList[5].get());\n  EXPECT_EQ(nodeList[2]->ChildAt(0), nodeList[6].get());\n  EXPECT_EQ(nodeList[2]->ChildAt(1), nullptr);\n\n  EXPECT_EQ(nodeList[0]->LastLeafChild(), nodeList[6].get());\n  EXPECT_EQ(nodeList[1]->LastLeafChild(), nodeList[5].get());\n\n  EXPECT_TRUE(nodeList[1]->IsDescendantOf(nodeList[0].get()));\n  EXPECT_TRUE(nodeList[3]->IsDescendantOf(nodeList[0].get()));\n  EXPECT_TRUE(nodeList[3]->IsDescendantOf(nodeList[1].get()));\n  EXPECT_TRUE(nodeList[6]->IsDescendantOf(nodeList[0].get()));\n  EXPECT_FALSE(nodeList[3]->IsDescendantOf(nodeList[2].get()));\n  EXPECT_FALSE(nodeList[3]->IsDescendantOf(nodeList[4].get()));\n  EXPECT_FALSE(nodeList[6]->IsDescendantOf(nodeList[1].get()));\n}\n\nTEST_F(RenderObjectTest, TreeManipulation) {\n  std::unique_ptr<MockRenderObject> obj = std::make_unique<MockRenderObject>();\n  nodeList[2]->AddChild(obj.get(), nodeList[6].get());\n  EXPECT_EQ(nodeList[6]->PreviousSibling(), obj.get());\n\n  EXPECT_EQ(nodeList[0]->LastLeafChild(), nodeList[6].get());\n  nodeList[2]->RemoveAllChildren();\n  EXPECT_EQ(nodeList[0]->LastLeafChild(), nodeList[2].get());\n}\n\nTEST_F(RenderObjectTest, BoxModel) {\n  std::unique_ptr<MockRenderObject> obj = std::make_unique<MockRenderObject>();\n  EXPECT_FALSE(obj->HasBackground());\n  EXPECT_FALSE(obj->HasBorder());\n  EXPECT_EQ(obj->location(), FloatPoint());\n  EXPECT_EQ(obj->Width(), 0.f);\n  EXPECT_EQ(obj->Height(), 0.f);\n\n  obj->SetLeft(10.f);\n  obj->SetTop(10.f);\n  obj->SetWidth(100.f);\n  obj->SetHeight(100.f);\n  obj->SetPaddingLeft(10.f);\n  obj->SetPaddingTop(10.f);\n  obj->SetPaddingRight(20.f);\n  obj->SetPaddingBottom(20.f);\n  EXPECT_EQ(obj->location(), FloatPoint(10.f, 10.f));\n  EXPECT_EQ(obj->Width(), 100.f);\n  EXPECT_EQ(obj->Height(), 100.f);\n  EXPECT_EQ(obj->PaddingLeft(), 10.f);\n  EXPECT_EQ(obj->PaddingTop(), 10.f);\n  EXPECT_EQ(obj->PaddingRight(), 20.f);\n  EXPECT_EQ(obj->PaddingBottom(), 20.f);\n}\n\nTEST_F(RenderObjectTest, PaintOrder) {\n  // Initial state.\n  EXPECT_EQ(nodeList[1]->ChildrenPaintingOrderIsDirtyForTesting(), true);\n  const auto& sorted1 = nodeList[1]->GetSortedChildrenForTesting();\n  EXPECT_EQ(nodeList[1]->ChildrenPaintingOrderIsDirtyForTesting(), false);\n  EXPECT_EQ(sorted1[0], nodeList[3].get());\n  EXPECT_EQ(sorted1[1], nodeList[4].get());\n  EXPECT_EQ(sorted1[2], nodeList[5].get());\n\n  // Set z-index = 5 for node 3.\n  nodeList[3]->SetPaintingOrder(5);\n  EXPECT_EQ(nodeList[1]->ChildrenPaintingOrderIsDirtyForTesting(), true);\n  const auto& sorted2 = nodeList[1]->GetSortedChildrenForTesting();\n  EXPECT_EQ(sorted2[0], nodeList[4].get());\n  EXPECT_EQ(sorted2[1], nodeList[5].get());\n  EXPECT_EQ(sorted2[2], nodeList[3].get());\n\n  // Set z-index = 5 for node 3 again.\n  nodeList[3]->SetPaintingOrder(5);\n  EXPECT_EQ(nodeList[1]->ChildrenPaintingOrderIsDirtyForTesting(), false);\n\n  // Set translate-z = 1 for node 4.\n  TransformOperations transform3;\n  transform3.AppendTranslate(0, 0, 1);\n  nodeList[4]->SetTransformOperations(transform3);\n  EXPECT_EQ(nodeList[1]->ChildrenPaintingOrderIsDirtyForTesting(), true);\n  const auto& sorted3 = nodeList[1]->GetSortedChildrenForTesting();\n  EXPECT_EQ(sorted3[0], nodeList[5].get());\n  EXPECT_EQ(sorted3[1], nodeList[3].get());\n  EXPECT_EQ(sorted3[2], nodeList[4].get());\n\n  // Insert new child to node 1.\n  std::unique_ptr<MockRenderObject> obj = std::make_unique<MockRenderObject>();\n  nodeList[1]->AddChild(obj.get());\n  EXPECT_EQ(nodeList[1]->ChildrenPaintingOrderIsDirtyForTesting(), true);\n  const auto& sorted4 = nodeList[1]->GetSortedChildrenForTesting();\n  EXPECT_EQ(sorted4[0], nodeList[5].get());\n  EXPECT_EQ(sorted4[1], obj.get());\n  EXPECT_EQ(sorted4[2], nodeList[3].get());\n  EXPECT_EQ(sorted4[3], nodeList[4].get());\n\n  // Set z-index = 10 for obj.\n  obj->SetPaintingOrder(10);\n  const auto& sorted5 = nodeList[1]->GetSortedChildrenForTesting();\n  EXPECT_EQ(sorted5[0], nodeList[5].get());\n  EXPECT_EQ(sorted5[1], nodeList[3].get());\n  EXPECT_EQ(sorted5[2], obj.get());\n  EXPECT_EQ(sorted5[3], nodeList[4].get());\n\n  // Set translate-z = 1 for obj.\n  TransformOperations transform6;\n  transform6.AppendTranslate(0, 0, 1);\n  obj->SetTransformOperations(transform6);\n  const auto& sorted6 = nodeList[1]->GetSortedChildrenForTesting();\n  EXPECT_EQ(sorted6[0], nodeList[5].get());\n  EXPECT_EQ(sorted6[1], nodeList[3].get());\n  EXPECT_EQ(sorted6[2], nodeList[4].get());\n  EXPECT_EQ(sorted6[3], obj.get());\n\n  // Set translate-z = 0 for obj and node 4.\n  TransformOperations transform7;\n  transform7.AppendTranslate(0, 0, 0);\n  nodeList[4]->SetTransformOperations(transform7);\n  obj->SetTransformOperations(transform7);\n  EXPECT_EQ(nodeList[1]->ChildrenPaintingOrderIsDirtyForTesting(), true);\n  const auto& sorted7 = nodeList[1]->GetSortedChildrenForTesting();\n  EXPECT_EQ(sorted7[0], nodeList[4].get());\n  EXPECT_EQ(sorted7[1], nodeList[5].get());\n  EXPECT_EQ(sorted7[2], nodeList[3].get());\n  EXPECT_EQ(sorted7[3], obj.get());\n\n  // Remove node 3.\n  nodeList[1]->RemoveChild(nodeList[3].get());\n  EXPECT_EQ(nodeList[1]->ChildrenPaintingOrderIsDirtyForTesting(), true);\n  const auto& sorted8 = nodeList[1]->GetSortedChildrenForTesting();\n  EXPECT_EQ(sorted8[0], nodeList[4].get());\n  EXPECT_EQ(sorted8[1], nodeList[5].get());\n  EXPECT_EQ(sorted8[2], obj.get());\n\n  // Update root node‘s painting order shouldn't trigger a crash.\n  TransformOperations transform8;\n  transform8.AppendTranslate(0, 0, 1);\n  auto* root = nodeList[0].get();\n  root->SetTransformOperations(transform8);\n  root->SetPaintingOrder(1);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_overlay.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_overlay.h\"\n\nnamespace clay {\n\nRenderOverlay::RenderOverlay() = default;\nRenderOverlay::~RenderOverlay() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_overlay.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_OVERLAY_H_\n#define CLAY_UI_RENDERING_RENDER_OVERLAY_H_\n\n#include \"clay/ui/rendering/render_container.h\"\n\nnamespace clay {\n\nclass RenderOverlay : public RenderContainer {\n public:\n  RenderOverlay();\n  ~RenderOverlay() override;\n\n  const char* GetName() const override { return \"RenderOverlay\"; }\n  bool IsRepaintBoundary() const override { return true; }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDER_OVERLAY_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_page.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_page.h\"\n\nnamespace clay {\n\nRenderPage::RenderPage() {\n  SetLayer(std::make_unique<PendingTransformLayer>(skity::Matrix()));\n}\n\nRenderPage::~RenderPage() = default;\n\nconst char* RenderPage::GetName() const { return \"RenderPage\"; }\n\nvoid RenderPage::Paint(PaintingContext& context, const FloatPoint& offset) {\n  RenderBox::Paint(context, offset);\n  PaintChildren(context, offset);\n}\n\nvoid RenderPage::SetScaleRatio(float ratio) {\n  if (scale_ratio_ == ratio) {\n    return;\n  }\n\n  scale_ratio_ = ratio;\n\n  skity::Matrix matrix;\n  matrix.Reset();\n  matrix.SetScaleX(scale_ratio_);\n  matrix.SetScaleY(scale_ratio_);\n\n  PendingTransformLayer* layer =\n      static_cast<PendingTransformLayer*>(GetLayer());\n  if (layer) {\n    layer->SetTransform(matrix);\n  }\n  MarkNeedsPaint();\n}\n\n#ifndef NDEBUG\nstd::string RenderPage::ToString() const {\n  std::stringstream ss;\n  ss << RenderBox::ToString();\n  ss << \" scale_ratio_=\" << scale_ratio_;\n  return ss.str();\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_page.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_PAGE_H_\n#define CLAY_UI_RENDERING_RENDER_PAGE_H_\n\n#include <memory>\n#include <string>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/compositing/pending_transform_layer.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\n// The root of the render tree.\nclass RenderPage : public RenderBox {\n public:\n  RenderPage();\n  ~RenderPage() override;\n\n  const char* GetName() const override;\n  bool IsRepaintBoundary() const override { return true; }\n  void Paint(PaintingContext& context, const FloatPoint& offset) override;\n  void SetScaleRatio(float ratio);\n\n#ifndef NDEBUG\n  std::string ToString() const override;\n#endif\n\n private:\n  // This is the clay to physical pixel ratio. We scale the root layer by dpr if\n  // logical pixels are used in clay.\n  float scale_ratio_ = 1.0f;\n  std::unique_ptr<PendingTransformLayer> CreateNewRootLayer();\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_RENDERING_RENDER_PAGE_H_\n"
  },
  {
    "path": "clay/ui/rendering/render_scroll.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/render_scroll.h\"\n\n#include <algorithm>\n#include <memory>\n#include <utility>\n\n#include \"clay/flow/animation/scroll_offset_animation.h\"\n#include \"clay/ui/compositing/pending_offset_layer.h\"\n#include \"clay/ui/compositing/pending_scroll_offset_layer.h\"\n\nnamespace clay {\n\nconst char* RenderScroll::GetName() const { return \"RenderScroll\"; }\n\nvoid RenderScroll::Paint(PaintingContext& context, const FloatPoint& offset) {\n  RenderBox::Paint(context, offset);\n\n  auto painter = [this](PaintingContext& ctx, const FloatPoint& offset) {\n    // Static offset includes border, padding and margin.\n    FloatPoint layer_offset = offset;\n    FloatPoint scroll_offset = GetPaintOffsetForScroll();\n\n    PendingOffsetLayer* offset_layer = nullptr;\n    if (is_raster_fling_) {\n      auto visible_rect = VisibleOverflowRect();\n      FloatRect visible_offset_range(\n          visible_rect.x(), visible_rect.y(),\n          std::max(visible_rect.MaxX() - ContentWidth() - visible_rect.x(),\n                   0.f),\n          std::max(visible_rect.MaxY() - ContentHeight() - visible_rect.y(),\n                   0.f));\n      FloatRect max_offset_range(0, 0, MaxScrollWidth(), MaxScrollHeight());\n      // Use ScrollOffsetLayer as sub layer of the RenderScroll to handle scroll\n      // animtion on raster. It will build a TransformLayer and\n      // ScrollOffsetMutator in flow.\n      offset_layer = new PendingScrollOffsetLayer(\n          layer_offset, FloatPoint() - scroll_offset, visible_offset_range,\n          max_offset_range, raster_fling_animation_);\n    } else {\n      // Use OffsetLayer to handle static offset, and it will build a\n      // TransformLayer in flow.\n      offset_layer = new PendingOffsetLayer(layer_offset - scroll_offset);\n    }\n    auto painter = [this](PaintingContext& ctx,\n                          const FloatPoint& layer_offset) {\n      auto visitor = [&](RenderObject* child) {\n        if (child->IsOverlay() && child->Visible()) {\n          renderer_->AddOverlayChild(child);\n        }\n        ctx.PaintChild(child, child->OffsetInLayer());\n      };\n      VisitChildren(visitor);\n    };\n    ctx.PushLayer(offset_layer, painter, FloatPoint(0, 0));\n  };\n\n  // RenderScroll do not need clip because ScrollWrapper\n  FloatPoint clip_offset = offset + ClipOffset();\n  FloatRect clip_rect(clip_offset.x(), clip_offset.y(), ClientWidth(),\n                      ClientHeight());\n  if (Overflow() == CSSProperty::OVERFLOW_X) {\n    clip_rect.SetWidth(renderer_->GetFrameSize().width() * 2);\n    clip_rect.SetX(clip_rect.x() - renderer_->GetFrameSize().width());\n  } else if (Overflow() == CSSProperty::OVERFLOW_Y) {\n    clip_rect.SetHeight(renderer_->GetFrameSize().height() * 2);\n    clip_rect.SetY(clip_rect.y() - renderer_->GetFrameSize().height());\n  }\n  context.PushClipRect(clip_rect, offset, painter);\n}\n\nbool RenderScroll::ScrollTo(float scroll_x, float scroll_y) {\n  if (!overscroll_enabled_) {\n    scroll_x = std::clamp<float>(scroll_x, 0, MaxScrollWidth());\n    scroll_y = std::clamp<float>(scroll_y, 0, MaxScrollHeight());\n  }\n\n  if (scroll_x != ScrollLeft() || scroll_y != ScrollTop()) {\n    SetScrollLeft(scroll_x);\n    SetScrollTop(scroll_y);\n    return true;\n  }\n  return false;\n}\n\nFloatRect RenderScroll::VisibleOverflowRect() const {\n  // For normal ScrollView, the overflow visible rect is the same as the\n  // overflow rect.\n  if (visible_overflow_rect_.IsEmpty()) {\n    return OverflowRect();\n  }\n  return visible_overflow_rect_;\n}\n\nvoid RenderScroll::SetVisibleOverflowRect(const FloatRect& rect) {\n  visible_overflow_rect_ = rect;\n  MarkNeedsPaint();\n}\n\nvoid RenderScroll::StartRasterFling(\n    std::shared_ptr<clay::ScrollOffsetAnimation> animation) {\n  is_raster_fling_ = true;\n  raster_fling_animation_ = std::move(animation);\n  MarkNeedsPaint();\n}\n\nvoid RenderScroll::StopRasterFling() {\n  is_raster_fling_ = false;\n  raster_fling_animation_.reset();\n  MarkNeedsPaint();\n}\n\nScrollableDirection RenderScroll::GetScrollableDirection() const {\n  ScrollableDirection result = ScrollableDirection::kNone;\n  if (ScrollLeft() > 0) {\n    result |= ScrollableDirection::kLeftwards;\n  }\n  if (ScrollLeft() < MaxScrollWidth()) {\n    result |= ScrollableDirection::kRightwards;\n  }\n  if (ScrollTop() > 0) {\n    result |= ScrollableDirection::kUpwards;\n  }\n  if (ScrollTop() < MaxScrollHeight()) {\n    result |= ScrollableDirection::kDownwards;\n  }\n  return static_cast<ScrollableDirection>(result);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/render_scroll.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDER_SCROLL_H_\n#define CLAY_UI_RENDERING_RENDER_SCROLL_H_\n\n#include <memory>\n\n#include \"clay/flow/animation/scroll_offset_animation.h\"\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/ui/gesture/scrollable_direction.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\nclass RenderScroll : public RenderBox {\n public:\n  RenderScroll() = default;\n  ~RenderScroll() override = default;\n\n  const char* GetName() const override;\n  void Paint(PaintingContext& context, const FloatPoint& offset) override;\n  bool IsRepaintBoundary() const override { return true; }\n  bool HasClip() const override { return true; }\n\n  virtual bool ScrollTo(float scroll_x, float scroll_y);\n\n  void SetOverscrollEnabled(bool value) { overscroll_enabled_ = value; }\n  bool OverscrollEnabled() const { return overscroll_enabled_; }\n\n  FloatRect VisibleOverflowRect() const;\n  void SetVisibleOverflowRect(const FloatRect& rect);\n\n  // Try to start fling animation on raster thread with\n  // `clay::ScrollOffsetAnimation`.\n  void StartRasterFling(std::shared_ptr<clay::ScrollOffsetAnimation>);\n  void StopRasterFling();\n\n  ScrollableDirection GetScrollableDirection() const;\n\n  bool IsScrollable() const override { return true; }\n\n  void SetScrollDirection(ScrollDirection direction) {\n    scroll_direction_ = direction;\n  }\n  ScrollDirection GetScrollDirection() const { return scroll_direction_; }\n\n  void SetInScrollAnimation(bool value) { in_scroll_animation_ = value; }\n  bool InScrollAnimation() const { return in_scroll_animation_; }\n\n private:\n  // If overscroll is enabled, we can keep scrolling regardless of whether\n  // exceeding the overflow rect.\n  bool overscroll_enabled_ = false;\n\n  // Indicates whether the scroll is currently being animated by the raster.\n  bool is_raster_fling_ = false;\n  std::shared_ptr<clay::ScrollOffsetAnimation> raster_fling_animation_;\n  // The visible overflow rect, distinguish from the overflow rect, components\n  // like ListContainerView may has more invisible items out of it's visible\n  // range. The animation should not finish, just blocked at the edge unless\n  // reach the real overflow rect. The raster scroll animation will use it to\n  // calculate the offset range.\n  FloatRect visible_overflow_rect_;\n  ScrollDirection scroll_direction_;\n  bool in_scroll_animation_ = false;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_RENDERING_RENDER_SCROLL_H_\n"
  },
  {
    "path": "clay/ui/rendering/renderer.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/renderer.h\"\n\n#include <algorithm>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/render_object.h\"\n\nnamespace clay {\n\nRenderer::Renderer(RendererClient* client,\n                   fml::RefPtr<GPUUnrefQueue> unref_queue)\n    : client_(client), unref_queue_(unref_queue) {}\n\nRenderer::~Renderer() = default;\n\nvoid Renderer::SetRoot(RenderObject* root) {\n  FML_DCHECK(root);\n  root_ = root;\n  root_->SetRenderer(this);\n}\n\nvoid Renderer::AddOverlayChild(RenderObject* node) {\n  FML_DCHECK(node && node->IsOverlay() && node->Visible());\n  // Overlays are ordered by level with descent order. For overlays with the\n  // same level, newer is at the end.\n  auto iter =\n      std::find(overlay_children_.begin(), overlay_children_.end(), node);\n  if (iter != overlay_children_.end()) {\n    overlay_children_.erase(iter);\n  }\n  auto it = overlay_children_.begin();\n  for (; it != overlay_children_.end(); it++) {\n    if ((*it)->OverlayLevel() < node->OverlayLevel()) {\n      break;\n    }\n  }\n  overlay_children_.insert(it, node);\n}\n\nPendingContainerLayer* Renderer::GetLayer() const {\n  return root_ ? root_->GetLayer() : nullptr;\n}\n\nvoid Renderer::RemoveDirtyNode(RenderObject* node) {\n  nodes_needing_paint_.remove(node);\n  if (node->IsOverlay()) {\n    auto iter =\n        std::find(overlay_children_.begin(), overlay_children_.end(), node);\n    if (iter != overlay_children_.end()) {\n      overlay_children_.erase(iter);\n    }\n    // Remove overlay's layer from layer tree.\n    if (node->GetLayer()) {\n      node->GetLayer()->Remove();\n    }\n  }\n}\n\nbool Renderer::Contains(RenderObject* node) const {\n  auto it =\n      std::find(nodes_needing_paint_.begin(), nodes_needing_paint_.end(), node);\n  return it != nodes_needing_paint_.end();\n}\n\nvoid Renderer::Paint() {\n  if (nodes_needing_paint_.empty()) {\n    return;\n  }\n\n  root_->ValidateForPaint(true);  // root is always visible\n  root_->WillPaint();\n  std::list<RenderObject*> dirty_nodes;\n  dirty_nodes.swap(nodes_needing_paint_);\n  nodes_needing_paint_.clear();\n  dirty_nodes.unique();\n  // Sort the dirty nodes in reverse order (deepest first).\n  dirty_nodes.sort([](RenderObject* node1, RenderObject* node2) {\n    return node1->Depth() > node2->Depth();\n  });\n\n  for (auto* node : dirty_nodes) {\n    if (!node->IsRepaintBoundary()) {\n      FML_DLOG(ERROR) << \"find dirty node: \" << node->ID()\n                      << \" which is not IsRepaintBoundary\";\n      node->MarkNeedsPaint(true);\n      continue;\n    }\n    if (node->NeedsPaint() || node->NeedsEffect()) {\n      PaintingContext::RepaintCompositedChild(node, unref_queue_);\n    }\n  }\n\n  // Append all layers of overlay children to root layer.\n  AddOverlayToRootLayer();\n}\n\nvoid Renderer::RequestPaint() {\n  // If we are in the layout phase, there is no need to request a new frame\n  if (client_ && client_->GetRenderPhase() != RenderPhase::kLayout) {\n    client_->RequestNewFrame();\n  }\n}\n\nvoid Renderer::AddOverlayToRootLayer() {\n  if (overlay_children_.empty()) {\n    return;\n  }\n\n  for (auto* child : overlay_children_) {\n    PendingContainerLayer* overlay_layer = child->GetLayer();\n    if (!overlay_layer) {\n      continue;\n    }\n    if (!overlay_layer->Parent()) {\n      root_->GetLayer()->AppendChild(overlay_layer);\n    } else {\n      FML_DCHECK(overlay_layer->Parent() == root_->GetLayer());\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/renderer.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_RENDERER_H_\n#define CLAY_UI_RENDERING_RENDERER_H_\n\n#include <list>\n#include <unordered_set>\n\n#include \"clay/gfx/pixel_helper.h\"\n#include \"clay/ui/compositing/pending_container_layer.h\"\n\nnamespace clay {\n\nclass RenderObject;\n\nenum class RenderPhase {\n  kIdle,\n  kLayout,\n  kPaint,\n  kBuildFrame,\n};\n\nclass RendererClient {\n public:\n  virtual void RequestNewFrame() = 0;\n  virtual RenderPhase GetRenderPhase() const = 0;\n  virtual fml::RefPtr<PaintImage> MakeRasterSnapshot(GrPicturePtr picture,\n                                                     skity::Vec2 size) = 0;\n  virtual void RegisterUploadTask(OneShotCallback<>&& task, int image_id) = 0;\n\n protected:\n  virtual ~RendererClient() = default;\n};\n\n// The renderer manages the rendering pipeline.\n//\n// The renderer provides an interface for driving the rendering pipeline and\n// stores the state about which render objects have requested to be visited\n// in each stage of the pipeline.\nclass Renderer : public PixelHelper<kPixelTypeClay> {\n public:\n  explicit Renderer(RendererClient* client,\n                    fml::RefPtr<GPUUnrefQueue> unref_queue);\n  ~Renderer();\n\n  void SetRoot(RenderObject* root);\n  void SetFrameSize(Size size) { frame_size_ = size; }\n  Size GetFrameSize() { return frame_size_; }\n\n  void Paint();\n  void RequestPaint();\n\n  void AddNeedPaint(RenderObject* node) {\n    nodes_needing_paint_.push_back(node);\n  }\n  void AddOverlayChild(RenderObject* node);\n  bool Contains(RenderObject* node) const;\n\n  PendingContainerLayer* GetLayer() const;\n\n  void SetPixelRatio(float pixel_ratio) { pixel_ratio_ = pixel_ratio; }\n\n  float DevicePixelRatio() const override { return pixel_ratio_; }\n\n  void SetDPI(float dpi) { dpi_ = dpi; }\n\n  float DPI() const { return dpi_; }\n\n  void RemoveDirtyNode(RenderObject* node);\n\n  bool HasDirtyNodes() const { return !nodes_needing_paint_.empty(); }\n\n  RendererClient* renderer_client() const { return client_; }\n\n private:\n  // Append all layers of overlay children to root layer.\n  void AddOverlayToRootLayer();\n\n  // The unique object managed by the renderer that has no parent.\n  RenderObject* root_ = nullptr;\n\n  RendererClient* client_;\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n  float pixel_ratio_ = 1.0f;\n  float dpi_ = 90.0f;\n  std::list<RenderObject*> nodes_needing_paint_;\n  std::list<RenderObject*> overlay_children_;\n  Size frame_size_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_RENDERER_H_\n"
  },
  {
    "path": "clay/ui/rendering/text/render_inline_text.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/text/render_inline_text.h\"\n\n#include \"clay/ui/painter/box_painter.h\"\n#include \"clay/ui/painter/box_shadow_painter.h\"\n\nnamespace clay {\n\nvoid RenderInlineText::Paint(PaintingContext& context,\n                             const FloatPoint& offset) {\n  auto box_painter = BoxPainter(this);\n\n  // Merge boxes in same line to one box.\n  std::list<FloatRect> merged_box;\n  for (const auto& box : text_boxes_) {\n    if (!merged_box.empty() && merged_box.back().y() == box.y()) {\n      merged_box.back().ShiftMaxXEdgeTo(box.MaxX());\n    } else {\n      merged_box.push_back(box);\n    }\n  }\n\n  for (auto box : merged_box) {\n    box.Expand(PaddingTop(), PaddingRight(), PaddingBottom(), PaddingLeft());\n    box_painter.SetOuterRect(box);\n    if (HasOutline()) {\n      box_painter.SetOutline(Outline(), box);\n    }\n    if (HasBorder()) {\n      box_painter.SetBorder(Border(), box);\n    }\n    box_painter.PaintBoxDecorationBackground(context.GetGraphicsContext(),\n                                             offset, box);\n  }\n  RenderBox::PaintChildren(context, offset);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/text/render_inline_text.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_TEXT_RENDER_INLINE_TEXT_H_\n#define CLAY_UI_RENDERING_TEXT_RENDER_INLINE_TEXT_H_\n\n#include <list>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\nclass RenderInlineText : public RenderBox {\n public:\n  RenderInlineText() = default;\n  virtual ~RenderInlineText() = default;\n\n  const char* GetName() const override { return \"RenderInlineText\"; }\n\n  // Use same layer with parent 'text' view.\n  bool IsRepaintBoundary() const override { return false; }\n\n  void Paint(PaintingContext& context, const FloatPoint& offset) override;\n\n  void AddTextBox(const skity::Rect& box) { text_boxes_.emplace_back(box); }\n\n  void ClearTextBox() { text_boxes_.clear(); }\n\n  // Inline text can always be displayed when `Paint()` called because once\n  // the parent text view is set to display:none, inline text won't be\n  // requested to paint.\n  bool CanDisplay() override { return true; }\n\n private:\n  std::list<FloatRect> text_boxes_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_TEXT_RENDER_INLINE_TEXT_H_\n"
  },
  {
    "path": "clay/ui/rendering/text/render_text.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/rendering/text/render_text.h\"\n\n#include <algorithm>\n#include <map>\n#include <optional>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/rendering_backend.h\"\n#include \"clay/gfx/style/color_source.h\"\n#include \"clay/gfx/style/tile_mode.h\"\n#include \"clay/ui/common/text_input_type_traits.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/painter/text_painter.h\"\n#include \"clay/ui/rendering/renderer.h\"\n\nnamespace clay {\n\nnamespace {\n\nconstexpr uint32_t kSelectionColor = 0x402196F3;  // material blue[200]\n\n}  // namespace\n\nRenderText::RenderText() : painter_(std::make_unique<TextPainter>()) {}\n\nRenderText::~RenderText() { paragraph_ = nullptr; }\n\nconst char* RenderText::GetName() const { return \"RenderText\"; }\n\nvoid RenderText::SetParagraph(txt::Paragraph* paragraph,\n                              const std::u16string& text) {\n  paragraph_ = paragraph;\n  text_ = text;\n  painter_->SetParagraph(paragraph_);\n  MarkNeedsPaint();\n}\n\nvoid RenderText::SetGradient(const std::optional<Gradient>& gradient) {\n  painter_->SetGradient(gradient);\n  MarkNeedsPaint();\n}\n\nvoid RenderText::SetGradientShaderMap(\n    std::map<int, std::shared_ptr<ColorSource>>&& gradient_shader_map,\n    std::map<int, std::pair<size_t, size_t>>&& range_map) {\n  painter_->SetGradientShaderMap(std::move(gradient_shader_map),\n                                 std::move(range_map));\n  MarkNeedsPaint();\n}\n\nvoid RenderText::SetTextStrokeMap(\n    std::unordered_map<int, TextStroke>&& text_stroke_map) {\n  painter_->SetTextStrokeMap(std::move(text_stroke_map));\n  MarkNeedsPaint();\n}\n\nvoid RenderText::Paint(PaintingContext& context, const FloatPoint& offset) {\n  if (HasBackgroundClipText() && painter_->CanPaint()) {\n    auto painter = [this](PaintingContext& ctx,\n                          const FloatPoint& layer_offset) {\n      RenderBox::Paint(ctx, layer_offset);\n      // Paint inline children background first.\n      RenderBox::PaintChildren(ctx, layer_offset);\n    };\n\n    GraphicsContext child_context(\n        context.GetGraphicsContext()->GetUnrefQueue());\n    skity::Rect rect = skity::Rect::MakeXYWH(0, 0, Width(), Height());\n    child_context.BeginRecording(rect);\n    FloatPoint paint_offset = offset + PaintOffset();\n    // Draw text into a mask that can be used to clip background.\n    PaintText(&child_context, paint_offset);\n    auto picture = child_context.FinishRecording();\n    // TODO(Jinsong): Generate a Lazy PaintImage to avoid blocking.\n    auto image = renderer_->renderer_client()->MakeRasterSnapshot(\n        picture->picture()->raw(),\n        skity::Vec2(ContentWidth(), ContentHeight()));\n\n    if (image) {\n      auto gradient_shader = std::make_shared<ImageColorSource>(\n          image, TileMode::kDecal, TileMode::kDecal);\n      context.PushShaderMask(gradient_shader,\n                             FloatRect(paint_offset.x(), paint_offset.y(),\n                                       ClientWidth(), ClientHeight()),\n                             BlendMode::kDstIn, offset, painter);\n      return;\n    }\n  }\n  // Draw text as normal.\n  RenderBox::Paint(context, offset);\n  // Paint inline children background first.\n  RenderBox::PaintChildren(context, offset);\n  if (painter_->CanPaint()) {\n    FloatPoint paint_offset = offset + PaintOffset();\n    GraphicsContext* graphics_context = context.GetGraphicsContext();\n    PaintText(graphics_context, paint_offset);\n  }\n}\n\nvoid RenderText::PaintText(GraphicsContext* graphics_context,\n                           const FloatPoint& offset) {\n  GraphicsContext::AutoRestore saver(graphics_context, true);\n  graphics_context->Translate(offset.x(), offset.y());\n  bool needs_clip_x = Overflow() == CSSProperty::OVERFLOW_Y ||\n                      Overflow() == CSSProperty::OVERFLOW_HIDDEN;\n  bool needs_clip_y = Overflow() == CSSProperty::OVERFLOW_X ||\n                      Overflow() == CSSProperty::OVERFLOW_HIDDEN;\n  if (!needs_clip_x && needs_clip_y) {\n    skity::Rect rect = skity::Rect::MakeXYWH(\n        -renderer_->GetFrameSize().width(), 0,\n        2 * renderer_->GetFrameSize().width(), ContentHeight());\n    graphics_context->ClipRect(rect, GrClipOp::kIntersect, false);\n  } else if (needs_clip_x && !needs_clip_y) {\n    skity::Rect rect = skity::Rect::MakeXYWH(\n        0, -renderer_->GetFrameSize().height(), ContentWidth(),\n        2 * renderer_->GetFrameSize().height());\n    graphics_context->ClipRect(rect, GrClipOp::kIntersect, false);\n  } else if (needs_clip_x && needs_clip_y) {\n    skity::Rect rect =\n        skity::Rect::MakeXYWH(0, 0, ContentWidth(), ContentHeight());\n    graphics_context->ClipRect(rect, GrClipOp::kIntersect, false);\n  }\n  painter_->SetWidth(ContentWidth());\n  painter_->SetHeight(ContentHeight());\n  double x_offset = 0;\n  // Not aligned with web behavior in bidirectional text but consistent\n  // with lynx behavior\n  if (text_paint_align_ == TextAlignment::kCenter) {\n    x_offset =\n        std::max(0.0, (ContentWidth() - paragraph_->GetLongestLine()) / 2);\n  } else if (text_paint_align_ == TextAlignment::kRight) {\n    x_offset = std::max(0.0, ContentWidth() - paragraph_->GetLongestLine());\n  } else {\n    FML_DCHECK(text_paint_align_ == TextAlignment::kLeft);\n  }\n  if (HasColorRasterAnimation()) {\n    graphics_context->Canvas()->OnDrawDynamicTextBlobsStart();\n    painter_->Paint(graphics_context, x_offset, line_spacing_offset_);\n    graphics_context->Canvas()->OnDrawDynamicTextBlobsEnd();\n  } else {\n    painter_->Paint(graphics_context, x_offset, line_spacing_offset_);\n  }\n  if (select_end_ != select_start_) {\n    PaintSelection(graphics_context);\n  }\n}\n\nvoid RenderText::SetSelection(const TextRange& range) {\n  select_start_ = range.start();\n  select_end_ = range.end();\n  if (selection_changed_callback_) {\n    selection_changed_callback_(select_start_, select_end_);\n  }\n  pre_select_end_ = select_end_;\n  MarkNeedsPaint();\n}\n\nvoid RenderText::SetAllSelection() {\n  SetSelection(TextRange(0, text_.length()));\n}\n\nvoid RenderText::PaintSelection(GraphicsContext* context) {\n  class Paint paint;\n  paint.setColor(kSelectionColor);\n  auto text_boxes =\n      painter_->GetRectsForRange(std::min(select_start_, select_end_),\n                                 std::max(select_start_, select_end_),\n                                 RectHeightStyle::kMax, RectWidthStyle::kMax);\n  clay::GrPath path;\n  for (auto box : text_boxes) {\n    PATH_ADD_RECT(path, box.rect);\n  }\n  context->DrawPath(path, paint);\n}\n\nstd::u16string RenderText::GetSelectionString() const {\n  return text_.substr(std::min(select_start_, select_end_),\n                      std::abs(select_end_ - select_start_));\n}\n\nstd::vector<Point> RenderText::GetPointsFromRangeSelection(\n    int select_start, int select_end) const {\n  if (select_start == select_end) {\n    return std::vector<Point>{};\n  } else {\n    std::vector<TextBox> boxes = painter_->GetRectsForRange(\n        std::min(select_start, select_end), std::max(select_start, select_end));\n    if (boxes.empty()) {\n      return std::vector<Point>();\n    }\n    // TODO(wangyanyi) now it is assumed textdirection is 'left'\n    return std::vector<Point>{\n        Point(boxes.front().GetLeft(), boxes.front().GetBottom()),\n        Point(boxes.back().GetRight(), boxes.back().GetBottom())};\n  }\n}\n\nTextBox RenderText::GetEndTextPositionTopAndBottom() const {\n  std::vector<TextBox> boxes;\n  if (select_start_ <= select_end_) {\n    boxes = painter_->GetRectsForRange(select_end_ - 1, select_end_);\n  } else {\n    boxes = painter_->GetRectsForRange(select_end_, select_end_ + 1);\n  }\n\n  if (boxes.empty()) {\n    return TextBox(FloatRect());\n  }\n  return boxes.front();\n}\n\nTextBox RenderText::GetStartTextPositionTopAndBottom() const {\n  std::vector<TextBox> boxes;\n  if (select_start_ <= select_end_) {\n    boxes = painter_->GetRectsForRange(select_start_, select_start_ + 1);\n  } else {\n    boxes = painter_->GetRectsForRange(select_start_ - 1, select_start_);\n  }\n  if (boxes.empty()) {\n    TextBox(FloatRect());\n  }\n  return boxes.front();\n}\n\nTextBox RenderText::GetLeftTextBox() {\n  std::vector<TextBox> boxes;\n  boxes = painter_->GetRectsForRange(std::min(select_start_, select_end_),\n                                     std::min(select_start_, select_end_) + 1);\n  if (boxes.empty()) {\n    TextBox(FloatRect());\n  }\n  return boxes.front();\n}\n\nTextBox RenderText::GetRightTextBox() {\n  std::vector<TextBox> boxes;\n  boxes = painter_->GetRectsForRange(std::max(select_start_, select_end_) - 1,\n                                     std::max(select_start_, select_end_));\n  if (boxes.empty()) {\n    TextBox(FloatRect());\n  }\n  return boxes.back();\n}\n\nstd::vector<FloatRect> RenderText::GetTextLineRects(int start, int end) {\n  return painter_->GetTextLineRects(start, end);\n}\n\nFloatRect RenderText::GetTextBoundingRect(\n    int start, int end, const std::vector<FloatRect>& line_rect) {\n  if (line_rect.empty()) {\n    return FloatRect();\n  }\n\n  FloatRect result = line_rect.front();\n\n  for (auto rect : line_rect) {\n    result.ExpandToInclude(rect);\n  }\n  return result;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/rendering/text/render_text.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RENDERING_TEXT_RENDER_TEXT_H_\n#define CLAY_UI_RENDERING_TEXT_RENDER_TEXT_H_\n\n#include <algorithm>\n#include <map>\n#include <memory>\n#include <optional>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/painter/gradient.h\"\n#include \"clay/ui/painter/painting_context.h\"\n#include \"clay/ui/painter/text_painter.h\"\n#include \"clay/ui/rendering/render_box.h\"\n\nnamespace clay {\n\nclass TextPainter;\n\nusing SelectionChangedCallback = std::function<void(int, int)>;\n\nclass RenderText : public RenderBox {\n public:\n  RenderText();\n  virtual ~RenderText();\n\n  const char* GetName() const override;\n\n  void SetParagraph(txt::Paragraph* paragraph, const std::u16string& text);\n\n  void SetGradient(const std::optional<Gradient>& gradient);\n  void SetGradientShaderMap(\n      std::map<int, std::shared_ptr<ColorSource>>&& gradient_shader_map,\n      std::map<int, std::pair<size_t, size_t>>&& range_map);\n\n  void SetTextStrokeMap(std::unordered_map<int, TextStroke>&& text_stroke_map);\n\n  void Paint(PaintingContext& context, const FloatPoint& offset) override;\n\n  TextPainter* GetPainter() { return painter_.get(); }\n\n  void SetSelection(const TextRange& range);\n  void SetAllSelection();\n\n  void PaintSelection(GraphicsContext* context);\n\n  std::u16string GetSelectionString() const;\n\n  const std::u16string& GetText() const { return text_; }\n\n  std::vector<Point> GetPointsFromRangeSelection(int select_start,\n                                                 int select_end) const;\n\n  std::vector<int> GetSelectionRange() const {\n    return std::vector<int>{std::min(select_start_, select_end_),\n                            std::max(select_start_, select_end_)};\n  }\n  TextRange GetSelection() { return TextRange(select_start_, select_end_); }\n\n  std::vector<FloatRect> GetTextLineRects(int start, int end);\n  FloatRect GetTextBoundingRect(int start, int end,\n                                const std::vector<FloatRect>& line_rect);\n\n  void SetSelectionChangedListener(\n      SelectionChangedCallback selection_changed_callback) {\n    selection_changed_callback_ = selection_changed_callback;\n  }\n\n  bool IsCollapsed() { return select_start_ == select_end_; }\n\n  TextBox GetLeftTextBox();\n  TextBox GetRightTextBox();\n  TextBox GetEndTextPositionTopAndBottom() const;\n  TextBox GetStartTextPositionTopAndBottom() const;\n\n  void SetLineSpacingOffset(double offset) { line_spacing_offset_ = offset; }\n  void SetTextPaintAlign(TextAlignment align) { text_paint_align_ = align; }\n\n protected:\n  std::unique_ptr<TextPainter> painter_;\n  txt::Paragraph* paragraph_;\n  std::u16string text_;\n  int select_start_ = -1;\n  int select_end_ = -1;\n  int pre_select_end_ = -1;\n  double line_spacing_offset_ = 0;\n  TextAlignment text_paint_align_ = TextAlignment::kLeft;\n\n private:\n  void PaintText(GraphicsContext* graphics_context, const FloatPoint& offset);\n\n  SelectionChangedCallback selection_changed_callback_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RENDERING_TEXT_RENDER_TEXT_H_\n"
  },
  {
    "path": "clay/ui/resource/asset_font_manager_clay.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_ASSET_FONT_MANAGER_CLAY_H_\n#define CLAY_UI_RESOURCE_ASSET_FONT_MANAGER_CLAY_H_\n\n#include <memory>\n\n#include \"clay/third_party/txt/src/txt/font_asset_provider.h\"\n#include \"clay/ui/ui_rendering_backend.h\"\n\nnamespace clay {\nclass AssetFontManagerClay : public txt::AssetFontManager {\n public:\n  explicit AssetFontManagerClay(\n      std::shared_ptr<FontResourceManager> font_resource_manager)\n      : AssetFontManager(\n            std::make_unique<AssetManagerFontProvider>(font_resource_manager)) {\n  }\n\n  ~AssetFontManagerClay() {}\n\n  AssetManagerFontProvider& font_provider() {\n    return static_cast<AssetManagerFontProvider&>(*font_provider_);\n  }\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_RESOURCE_ASSET_FONT_MANAGER_CLAY_H_\n"
  },
  {
    "path": "clay/ui/resource/asset_manager_font_provider_skia.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/resource/asset_manager_font_provider_skia.h\"\n\n#include <fstream>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/net/resource_type.h\"\n#include \"third_party/skia/include/core/SkStream.h\"\n\nnamespace clay {\n\nAssetManagerFontProvider::AssetManagerFontProvider(\n    std::shared_ptr<FontResourceManager> font_resource_manager)\n    : font_resource_manager_(font_resource_manager) {}\n\nAssetManagerFontProvider::~AssetManagerFontProvider() = default;\n\n// |FontAssetProvider|\nsize_t AssetManagerFontProvider::GetFamilyCount() const {\n  return family_names_.size();\n}\n\n// |FontAssetProvider|\nstd::string AssetManagerFontProvider::GetFamilyName(int index) const {\n  FML_DCHECK(index >= 0 && static_cast<size_t>(index) < family_names_.size());\n  return family_names_[index];\n}\n\n// |FontAssetProvider|\nsk_sp<SkFontStyleSet> AssetManagerFontProvider::MatchFamily(\n    const std::string& family_name) {\n  auto found = registered_families_.find(CanonicalFamilyName(family_name));\n  if (found == registered_families_.end()) {\n    return nullptr;\n  }\n  sk_sp<SkFontStyleSet> font_style_set = found->second;\n  return font_style_set;\n}\n\nvoid AssetManagerFontProvider::RegisterAsset(const std::string& family_name,\n                                             const std::string& file_path) {\n  std::string canonical_name = CanonicalFamilyName(family_name);\n  auto family_it = registered_families_.find(canonical_name);\n\n  if (family_it == registered_families_.end()) {\n    family_names_.push_back(family_name);\n    auto value = std::make_pair(canonical_name,\n                                sk_make_sp<AssetManagerFontStyleSet>(\n                                    family_name, font_resource_manager_));\n    family_it = registered_families_.emplace(value).first;\n  }\n\n  family_it->second->RegisterAsset(file_path);\n}\n\nAssetManagerFontStyleSet::AssetManagerFontStyleSet(\n    const std::string& family_name,\n    std::shared_ptr<FontResourceManager> font_resource_manager)\n    : family_name_(family_name),\n      font_resource_manager_(font_resource_manager) {}\n\nAssetManagerFontStyleSet::~AssetManagerFontStyleSet() = default;\n\nvoid AssetManagerFontStyleSet::RegisterAsset(const std::string& asset) {\n  assets_.emplace_back(asset);\n}\n\nint AssetManagerFontStyleSet::count() { return assets_.size(); }\n\nvoid AssetManagerFontStyleSet::getStyle(int index, SkFontStyle* style,\n                                        SkString* name) {\n  FML_DCHECK(index < static_cast<int>(assets_.size()));\n  if (style) {\n    sk_sp<SkTypeface> typeface(createTypeface(index));\n    if (typeface) {\n      *style = typeface->fontStyle();\n    }\n  }\n  if (name) {\n    *name = family_name_.c_str();\n  }\n}\n\nsk_sp<SkTypeface> AssetManagerFontStyleSet::createTypeface(int i) {\n  size_t index = i;\n  if (index >= assets_.size()) {\n    return nullptr;\n  }\n\n  TypefaceAsset& asset = assets_[index];\n  if (!asset.typeface) {\n    // TODO(jiangwenlong) : now we only support one font file ,support more font\n    // files\n    RawResource resource = font_resource_manager_->GetResource(family_name_);\n    if (resource.data == nullptr) {\n      return nullptr;\n    }\n\n    std::unique_ptr<SkMemoryStream> stream =\n        SkMemoryStream::MakeDirect(resource.data.get(), resource.length);\n\n    // Ownership of the stream is transferred.\n    asset.typeface = SkTypeface::MakeFromStream(std::move(stream));\n    if (!asset.typeface) {\n      FML_DLOG(ERROR) << \"Unable to load font asset for family: \"\n                      << family_name_;\n      return nullptr;\n    }\n  }\n\n  return asset.typeface;\n}\n\nsk_sp<SkTypeface> AssetManagerFontStyleSet::matchStyle(\n    const SkFontStyle& pattern) {\n  return matchStyleCSS3(pattern);\n}\n\nAssetManagerFontStyleSet::TypefaceAsset::TypefaceAsset(\n    const std::string& file_path)\n    : path(file_path) {}\n\nAssetManagerFontStyleSet::TypefaceAsset::TypefaceAsset(\n    const AssetManagerFontStyleSet::TypefaceAsset& other) = default;\n\nAssetManagerFontStyleSet::TypefaceAsset::~TypefaceAsset() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/resource/asset_manager_font_provider_skia.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_ASSET_MANAGER_FONT_PROVIDER_SKIA_H_\n#define CLAY_UI_RESOURCE_ASSET_MANAGER_FONT_PROVIDER_SKIA_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/third_party/txt/src/txt/font_asset_provider.h\"\n#include \"clay/ui/resource/font_resource_manager.h\"\n#include \"third_party/skia/include/core/SkFontMgr.h\"\n#include \"third_party/skia/include/core/SkTypeface.h\"\n\nnamespace clay {\n\nclass AssetManagerFontStyleSet : public SkFontStyleSet {\n public:\n  AssetManagerFontStyleSet(\n      const std::string& family_name,\n      std::shared_ptr<FontResourceManager> font_resource_manager);\n\n  ~AssetManagerFontStyleSet() override;\n\n  void RegisterAsset(const std::string& file_path);\n\n  // |SkFontStyleSet|\n  int count() override;\n\n  // |SkFontStyleSet|\n  void getStyle(int index, SkFontStyle*, SkString* style) override;\n\n  // |SkFontStyleSet|\n  sk_sp<SkTypeface> createTypeface(int index) override;\n\n  // |SkFontStyleSet|\n  sk_sp<SkTypeface> matchStyle(const SkFontStyle& pattern) override;\n\n private:\n  std::string family_name_;\n  std::shared_ptr<FontResourceManager> font_resource_manager_;\n\n  struct TypefaceAsset {\n    explicit TypefaceAsset(const std::string& file_path);\n\n    TypefaceAsset(const TypefaceAsset& other);\n\n    ~TypefaceAsset();\n\n    std::string path;\n    sk_sp<SkTypeface> typeface;\n  };\n  std::vector<TypefaceAsset> assets_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(AssetManagerFontStyleSet);\n};\n\nclass AssetManagerFontProvider : public txt::FontAssetProvider {\n public:\n  explicit AssetManagerFontProvider(\n      std::shared_ptr<FontResourceManager> font_resource_manager);\n\n  ~AssetManagerFontProvider() override;\n\n  void RegisterAsset(const std::string& family_name,\n                     const std::string& file_path);\n\n  // |FontAssetProvider|\n  size_t GetFamilyCount() const override;\n\n  // |FontAssetProvider|\n  std::string GetFamilyName(int index) const override;\n\n  // |FontAssetProvider|\n  sk_sp<SkFontStyleSet> MatchFamily(const std::string& family_name) override;\n\n private:\n  std::shared_ptr<FontResourceManager> font_resource_manager_;\n  std::unordered_map<std::string, sk_sp<AssetManagerFontStyleSet>>\n      registered_families_;\n  std::vector<std::string> family_names_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(AssetManagerFontProvider);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RESOURCE_ASSET_MANAGER_FONT_PROVIDER_SKIA_H_\n"
  },
  {
    "path": "clay/ui/resource/asset_manager_font_provider_skity.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/resource/asset_manager_font_provider_skity.h\"\n\n#include <fstream>\n#include <utility>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/net/resource_type.h\"\n\nnamespace clay {\nAssetManagerFontProvider::AssetManagerFontProvider(\n    std::shared_ptr<FontResourceManager> font_resource_manager)\n    : font_resource_manager_(font_resource_manager) {}\n\nAssetManagerFontProvider::~AssetManagerFontProvider() = default;\n\n// |FontAssetProvider|\nsize_t AssetManagerFontProvider::GetFamilyCount() const {\n  return family_names_.size();\n}\n\n// |FontAssetProvider|\nstd::string AssetManagerFontProvider::GetFamilyName(int index) const {\n  FML_DCHECK(index >= 0 && static_cast<size_t>(index) < family_names_.size());\n  return family_names_[index];\n}\n\n// |FontAssetProvider|\nstd::shared_ptr<skity::FontStyleSet> AssetManagerFontProvider::MatchFamily(\n    const std::string& family_name) {\n  auto found = registered_families_.find(CanonicalFamilyName(family_name));\n  if (found == registered_families_.end()) {\n    return nullptr;\n  }\n  return found->second;\n}\n\nvoid AssetManagerFontProvider::RegisterAsset(const std::string& family_name,\n                                             const std::string& file_path) {\n  std::string canonical_name = CanonicalFamilyName(family_name);\n  auto family_it = registered_families_.find(canonical_name);\n\n  if (family_it == registered_families_.end()) {\n    family_names_.push_back(family_name);\n    auto value = std::make_pair(canonical_name,\n                                std::make_shared<AssetManagerFontStyleSet>(\n                                    family_name, font_resource_manager_));\n    family_it = registered_families_.emplace(std::move(value)).first;\n  }\n\n  family_it->second->RegisterAsset(file_path);\n}\n\nAssetManagerFontStyleSet::AssetManagerFontStyleSet(\n    const std::string& family_name,\n    std::shared_ptr<FontResourceManager> font_resource_manager)\n    : family_name_(family_name),\n      font_resource_manager_(font_resource_manager) {}\n\nAssetManagerFontStyleSet::~AssetManagerFontStyleSet() = default;\n\nvoid AssetManagerFontStyleSet::RegisterAsset(const std::string& asset) {\n  assets_.emplace_back(asset);\n}\n\nint AssetManagerFontStyleSet::Count() { return assets_.size(); }\n\nvoid AssetManagerFontStyleSet::GetStyle(int index, skity::FontStyle* style,\n                                        std::string* name) {\n  FML_DCHECK(index < static_cast<int>(assets_.size()));\n  if (style) {\n    auto typeface = CreateTypeface(index);\n    if (typeface) {\n      *style = typeface->GetFontStyle();\n    }\n  }\n  if (name) {\n    *name = family_name_.c_str();\n  }\n}\n\nstd::shared_ptr<skity::Typeface> AssetManagerFontStyleSet::CreateTypeface(\n    int i) {\n  size_t index = i;\n  if (index >= assets_.size()) {\n    return nullptr;\n  }\n\n  TypefaceAsset& asset = assets_[index];\n  if (!asset.typeface) {\n    // TODO(jiangwenlong) : now we only support one font file ,support more font\n    // files\n    RawResource resource = font_resource_manager_->GetResource(family_name_);\n    if (resource.data == nullptr) {\n      return nullptr;\n    }\n\n    std::shared_ptr<skity::Data> data =\n        skity::Data::MakeWithCopy(resource.data.get(), resource.length);\n    auto typeface = skity::Typeface::MakeFromData(data);\n    asset.typeface = std::move(typeface);\n    if (!asset.typeface) {\n      FML_DLOG(ERROR) << \"Unable to load font asset for family: \"\n                      << family_name_;\n      return nullptr;\n    }\n  }\n\n  return asset.typeface;\n}\n\nstd::shared_ptr<skity::Typeface> AssetManagerFontStyleSet::MatchStyle(\n    const skity::FontStyle& pattern) {\n  return MatchStyleCSS3(pattern);\n}\n\nAssetManagerFontStyleSet::TypefaceAsset::TypefaceAsset(\n    const std::string& file_path)\n    : path(file_path) {}\n\nAssetManagerFontStyleSet::TypefaceAsset::TypefaceAsset(\n    const AssetManagerFontStyleSet::TypefaceAsset& other) = default;\n\nAssetManagerFontStyleSet::TypefaceAsset::~TypefaceAsset() = default;\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/resource/asset_manager_font_provider_skity.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_ASSET_MANAGER_FONT_PROVIDER_SKITY_H_\n#define CLAY_UI_RESOURCE_ASSET_MANAGER_FONT_PROVIDER_SKITY_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/third_party/txt/src/txt/font_asset_provider.h\"\n#include \"clay/ui/resource/font_resource_manager.h\"\n#include \"skity/text/font_manager.hpp\"\n\nnamespace clay {\n\nclass AssetManagerFontStyleSet : public skity::FontStyleSet {\n public:\n  AssetManagerFontStyleSet(\n      const std::string& family_name,\n      std::shared_ptr<FontResourceManager> font_resource_manager);\n\n  ~AssetManagerFontStyleSet() override;\n\n  void RegisterAsset(const std::string& file_path);\n\n  int Count() override;\n\n  void GetStyle(int index, skity::FontStyle* style, std::string* name) override;\n\n  std::shared_ptr<skity::Typeface> CreateTypeface(int index) override;\n\n  std::shared_ptr<skity::Typeface> MatchStyle(\n      const skity::FontStyle& pattern) override;\n\n private:\n  std::string family_name_;\n  std::shared_ptr<FontResourceManager> font_resource_manager_;\n\n  struct TypefaceAsset {\n    explicit TypefaceAsset(const std::string& file_path);\n\n    TypefaceAsset(const TypefaceAsset& other);\n\n    ~TypefaceAsset();\n\n    std::string path;\n    std::shared_ptr<skity::Typeface> typeface = nullptr;\n  };\n  std::vector<TypefaceAsset> assets_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(AssetManagerFontStyleSet);\n};\n\nclass AssetManagerFontProvider : public txt::FontAssetProvider {\n public:\n  explicit AssetManagerFontProvider(\n      std::shared_ptr<FontResourceManager> font_resource_manager);\n  ~AssetManagerFontProvider() override;\n\n  void RegisterAsset(const std::string& family_name,\n                     const std::string& file_path);\n\n  // |FontAssetProvider|\n  size_t GetFamilyCount() const override;\n\n  // |FontAssetProvider|\n  std::string GetFamilyName(int index) const override;\n\n  // |FontAssetProvider|\n  std::shared_ptr<skity::FontStyleSet> MatchFamily(\n      const std::string& family_name) override;\n\n private:\n  std::shared_ptr<FontResourceManager> font_resource_manager_;\n  std::unordered_map<std::string, std::shared_ptr<AssetManagerFontStyleSet>>\n      registered_families_;\n  std::vector<std::string> family_names_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(AssetManagerFontProvider);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RESOURCE_ASSET_MANAGER_FONT_PROVIDER_SKITY_H_\n"
  },
  {
    "path": "clay/ui/resource/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\nimport(\"//config.gni\")\nimport(\"../../common/config.gni\")\n\nui_resource_sources = [\n  \"asset_font_manager_clay.h\",\n  \"font_collection.cc\",\n  \"font_collection.h\",\n  \"font_resource_manager.cc\",\n  \"font_resource_manager.h\",\n  \"gpu_resource_cache.cc\",\n  \"gpu_resource_cache.h\",\n  \"image_cache.h\",\n  \"image_manager.cc\",\n  \"image_manager.h\",\n  \"image_resource_fetcher.cc\",\n  \"image_resource_fetcher.h\",\n]\n\nif (enable_skity) {\n  ui_resource_sources += [\n    \"asset_manager_font_provider_skity.cc\",\n    \"asset_manager_font_provider_skity.h\",\n    \"image_fetcher.cc\",\n    \"image_fetcher.h\",\n  ]\n} else {\n  ui_resource_sources += [\n    \"asset_manager_font_provider_skia.cc\",\n    \"asset_manager_font_provider_skia.h\",\n  ]\n}\n"
  },
  {
    "path": "clay/ui/resource/font_collection.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/resource/font_collection.h\"\n\n#include <string>\n\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/third_party/txt/src/txt/platform.h\"\n#include \"clay/ui/common/isolate.h\"\n\nnamespace clay {\n\nstd::shared_ptr<FontCollection> FontCollection::Instance() {\n  static std::shared_ptr<FontCollection> instance = nullptr;\n  if (!instance) {\n    instance = std::shared_ptr<FontCollection>(new FontCollection());\n  }\n  return instance;\n}\n\nFontCollection::FontCollection()\n    : collection_(std::make_shared<txt::FontCollection>()) {\n  font_resource_manager_ = std::make_shared<FontResourceManager>();\n#ifndef ENABLE_SKITY\n  asset_font_manager_ =\n      sk_make_sp<AssetFontManagerClay>(font_resource_manager_);\n#else\n  asset_font_manager_ =\n      std::make_shared<AssetFontManagerClay>(font_resource_manager_);\n#endif  // ENABLE_SKITY\n  collection_->SetAssetFontManager(asset_font_manager_);\n}\n\nFontCollection::~FontCollection() {\n  collection_.reset();\n  asset_font_manager_.reset();\n  font_resource_manager_.reset();\n#ifndef ENABLE_SKITY\n  SkGraphics::PurgeFontCache();\n#endif  // ENABLE_SKITY\n}\n\nstd::shared_ptr<txt::FontCollection> FontCollection::GetFontCollection() const {\n  return collection_;\n}\n\nvoid FontCollection::SetupDefaultFontManager(\n    uint32_t font_initialization_data) {\n  collection_->SetupDefaultFontManager(font_initialization_data);\n}\n\nvoid FontCollection::RegisterAssetFont(const std::string& family_name,\n                                       const std::string& asset_path) {\n  if (!asset_font_manager_) {\n    FML_LOG(ERROR) << \"asset_font_manager should not be null\";\n    FML_DCHECK(false);\n    return;\n  }\n  AssetManagerFontProvider& font_provider =\n      asset_font_manager_->font_provider();\n\n  font_provider.RegisterAsset(family_name, asset_path);\n}\n\nvoid FontCollection::PreLoadFontOnMem(\n    fml::RefPtr<fml::TaskRunner> load_task_runner,\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    std::shared_ptr<ServiceManager> service_manager,\n    const std::string& font_family, std::vector<std::string> urls) {\n  if (!font_resource_manager_) {\n    FML_LOG(ERROR) << \"font_resource_manager should not be null\";\n    FML_DCHECK(false);\n    return;\n  }\n\n  FontCallback callback = [weak = weak_from_this()](\n                              const std::string& font_family,\n                              const std::string& url) {\n    // first : register font\n    // One font family may specify multiple fonts, supporting matching the most\n    // appropriate font to show, now we only use one font\n    auto self = weak.lock();\n    if (!self) {\n      return;\n    }\n    const std::string str = \"not use\";\n    self->RegisterAssetFont(font_family, str);\n\n    self->OnLoadFontEnd(font_family);\n  };\n  font_resource_manager_->LoadFontAsync(load_task_runner, intercept,\n                                        service_manager, font_family,\n                                        std::move(urls), callback);\n}\n\nvoid FontCollection::RegisterCallback(const std::string& font_family,\n                                      const FontDownloadCallback& callback) {\n  if (HasFontResourceLoading(font_family)) {\n    font_download_callback_.emplace(font_family, callback);\n  }\n}\n\nbool FontCollection::HasFontResource(const std::string& font_family) {\n  if (!font_resource_manager_) {\n    FML_LOG(ERROR) << \"font_resource_manager should not be null\";\n    FML_DCHECK(false);\n    return false;\n  }\n\n  return font_resource_manager_->HasFontResource(font_family);\n}\n\nbool FontCollection::HasFontResourceLoading(const std::string& font_family) {\n  if (!font_resource_manager_) {\n    FML_LOG(ERROR) << \"font_resource_manager should not be null\";\n    FML_DCHECK(false);\n    return false;\n  }\n\n  return font_resource_manager_->HasFontResourceLoading(font_family);\n}\n\nvoid FontCollection::ClearFontFamilyCache() {\n  collection_->ClearFontFamilyCache();\n}\n\nvoid FontCollection::OnLoadFontEnd(const std::string& font_family) {\n  auto range = font_download_callback_.equal_range(font_family);\n  collection_->ClearFontFamilyCache();\n\n  for (auto it = range.first; it != range.second; ++it) {\n    it->second();\n  }\n\n  font_download_callback_.erase(font_family);\n}\n\nbool FontCollection::IfSystemFontFamily(std::string& font_family) {\n  auto default_font_manager = txt::GetDefaultFontManager(0);\n  auto font_style_set =\n      FONT_MANAGER_MATCH_FAMILY(default_font_manager, font_family.c_str());\n  return FONT_STYLE_SET_COUNT(font_style_set) != 0;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/resource/font_collection.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_FONT_COLLECTION_H_\n#define CLAY_UI_RESOURCE_FONT_COLLECTION_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/ui/resource/asset_font_manager_clay.h\"\n#include \"clay/ui/resource/font_resource_manager.h\"\n#include \"clay/ui/ui_rendering_backend.h\"\n\nnamespace clay {\n\nusing FontDownloadCallback = std::function<void()>;\n\nclass ResourceLoaderIntercept;\nclass ServiceManager;\n\n// FontCollection is the collection of font for Clay. It is a singleton which is\n// also can be accessed by `clay::Isolate`.\n//\n// IMPORTANT: For reuse of resource, the singleton would not clear resource\n// actively. In other words, it will keep all resource until called by\n// `PageView::NotifyLowMemory()` or the process termination.\n// FontCollection is singleton now, we could not use fml::WeakPtr here because\n// it is not thread safe in destruction when the process termination.\nclass FontCollection : public std::enable_shared_from_this<FontCollection> {\n public:\n  static std::shared_ptr<FontCollection> Instance();\n\n  ~FontCollection();\n\n  std::shared_ptr<txt::FontCollection> GetFontCollection() const;\n\n  void SetupDefaultFontManager(uint32_t font_initialization_data);\n\n  void PreLoadFontOnMem(fml::RefPtr<fml::TaskRunner> load_task_runner,\n                        std::shared_ptr<ResourceLoaderIntercept> intercept,\n                        std::shared_ptr<ServiceManager> service_manager,\n                        const std::string& font_family,\n                        std::vector<std::string> urls);\n\n  void RegisterCallback(const std::string& font_family,\n                        const FontDownloadCallback& callback);\n\n  bool HasFontResource(const std::string& font_family);\n\n  bool HasFontResourceLoading(const std::string& font_family);\n\n  void OnLoadFontEnd(const std::string& font_family);\n\n  void RegisterAssetFont(const std::string& family_name,\n                         const std::string& asset_path);\n\n  void ClearFontFamilyCache();\n\n  bool IfSystemFontFamily(std::string& font_family);\n\n#ifndef ENABLE_SKITY\n  sk_sp<AssetFontManagerClay> asset_font_manager() const {\n    return asset_font_manager_;\n  }\n#else\n  std::shared_ptr<AssetFontManagerClay> asset_font_manager() const {\n    return asset_font_manager_;\n  }\n#endif  // ENABLE_SKITY\n\n private:\n  std::shared_ptr<txt::FontCollection> collection_;\n  std::shared_ptr<FontResourceManager> font_resource_manager_;\n#ifndef ENABLE_SKITY\n  sk_sp<AssetFontManagerClay> asset_font_manager_;\n#else\n  std::shared_ptr<AssetFontManagerClay> asset_font_manager_;\n#endif  // ENABLE_SKITY\n  mutable std::atomic<int32_t> asset_font_manager_ref_;\n\n  FontCollection();\n\n  std::unordered_multimap<std::string, FontDownloadCallback>\n      font_download_callback_;\n\n  FRIEND_TEST(FontResourceManagerTest, GetLocalResourceTest);\n  FRIEND_TEST(FontResourceManagerTest, DISABLED_GetNetWorkResourceTest);\n  FRIEND_TEST(FontResourceManagerTest, FontCollectionTest);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(FontCollection);\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_RESOURCE_FONT_COLLECTION_H_\n"
  },
  {
    "path": "clay/ui/resource/font_resource_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/resource/font_resource_manager.h\"\n\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"clay/fml/base64.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/net/loader/resource_loader_factory.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/net/url/url_helper.h\"\n#include \"clay/ui/common/isolate.h\"\n\nnamespace clay {\n\nFontResourceManager::FontResourceManager() = default;\n\nFontResourceManager::~FontResourceManager() = default;\n\nRawResource FontResourceManager::GetResource(const std::string& family_name) {\n  auto iter = font_resource_map_.find(family_name);\n  if (iter == font_resource_map_.end()) {\n    return {0, nullptr};\n  }\n\n  return iter->second;\n}\n\nvoid FontResourceManager::DownloadFont(\n    fml::RefPtr<fml::TaskRunner> load_task_runner,\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    std::shared_ptr<ServiceManager> service_manager, const std::string& url,\n    const int url_index, const std::string& font_family) {\n  if (url::ParseUriScheme(url) == url::UriSchemeType::kData) {\n    fml::TaskRunner::RunNowOrPostTask(\n        load_task_runner,\n        [load_task_runner, intercept, service_manager, url, url_index,\n         font_name = font_family, weak = weak_from_this()]() {\n          auto self = weak.lock();\n          if (!self) {\n            return;\n          }\n          bool success = false;\n          auto data = self->DecodeBase64Str(url);\n          if (data.length > 0) {\n            success = true;\n          }\n          self->OnDownloadEnd(success, url_index, font_name, data,\n                              load_task_runner, intercept, service_manager);\n        });\n    return;\n  }\n\n  auto resource_loader = ResourceLoaderFactory::Create(\n      url, load_task_runner, intercept, service_manager);\n  if (!resource_loader) {\n    FML_LOG(ERROR) << \"create ResourceLoader fail\";\n    return;\n  }\n  resource_loader->Load(\n      url,\n      [load_task_runner, intercept, service_manager, weak = weak_from_this(),\n       font_name = font_family,\n       url_index](const uint8_t* font_stream, size_t len) {\n        auto self = weak.lock();\n        if (!self) {\n          return;\n        }\n\n        bool success = (font_stream != nullptr && len > 0);\n        RawResource data;\n        if (success) {\n          data = RawResource::MakeWithCopy(font_stream, len);\n        } else {\n          data = {0, nullptr};\n        }\n\n        self->OnDownloadEnd(success, url_index, font_name, data,\n                            load_task_runner, intercept, service_manager);\n      },\n      ResourceType::kFont, true);\n  font_loader_map_.emplace(font_family, std::move(resource_loader));\n}\n\nvoid FontResourceManager::OnDownloadEnd(\n    bool success, const int url_index, const std::string& font_family,\n    RawResource data, fml::RefPtr<fml::TaskRunner> load_task_runner,\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    std::shared_ptr<ServiceManager> service_manager) {\n  auto iter = font_url_map_.find(font_family);\n  if (iter == font_url_map_.end()) {\n    FML_DLOG(ERROR) << \"Unable to get font for family: \" << font_family;\n    return;\n  }\n\n  std::vector<std::string>& url_vec = iter->second;\n\n  // fail\n  if (!success) {\n    // download by next url\n    unsigned int index = url_index + 1;\n    if (index >= url_vec.size()) {\n      FML_DLOG(ERROR) << \"Unable to get font for family: \" << font_family;\n      font_call_back_map_.erase(font_family);\n      font_loader_map_.erase(font_family);\n      return;\n    } else {\n      DownloadFont(load_task_runner, intercept, service_manager, url_vec[index],\n                   index, font_family);\n    }\n    return;\n  }\n\n  // success , first : create font and cache\n  font_resource_map_.emplace(font_family, data);\n\n  // second : callback\n  auto callback_iter = font_call_back_map_.find(font_family);\n  if (callback_iter != font_call_back_map_.end()) {\n    callback_iter->second(font_family, url_vec[url_index]);\n  }\n  font_call_back_map_.erase(font_family);\n  font_loader_map_.erase(font_family);\n}\n\nbool FontResourceManager::HasFontResourceLoading(std::string font_family) {\n  return font_loader_map_.find(font_family) != font_loader_map_.end();\n}\n\nvoid FontResourceManager::LoadFontAsync(\n    fml::RefPtr<fml::TaskRunner> load_task_runner,\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    std::shared_ptr<ServiceManager> service_manager,\n    const std::string& font_family, std::vector<std::string> url_vec,\n    const FontCallback& callback) {\n  // first : find cache , avoid reloading\n  if (font_url_map_.find(font_family) != font_url_map_.end()) {\n    return;\n  }\n\n  // second : cache src\n  if (url_vec.empty()) {\n    return;\n  }\n\n  font_url_map_.emplace(font_family, std::move(url_vec));\n  font_call_back_map_.emplace(font_family, callback);\n\n  const int url_index = 0;\n  const std::string& first_url = font_url_map_[font_family][url_index];\n\n  // third : download by first url\n  DownloadFont(load_task_runner, intercept, service_manager, first_url,\n               url_index, font_family);\n}\n\nRawResource FontResourceManager::DecodeBase64Str(\n    const std::string& base64_str) {\n  if (!CheckFontScheme(base64_str.c_str())) {\n    return {0, nullptr};\n  }\n\n  const std::string identify = \"base64,\";\n  const int index = base64_str.find(identify);\n\n  if (index == -1) {\n    return {0, nullptr};\n  }\n\n  const int len = index + identify.length();\n  const int data_len = base64_str.length() - len;\n  const char* data = base64_str.c_str() + len;\n  size_t encode_data_length;\n\n  if (fml::Base64::Decode(data, data_len, nullptr, &encode_data_length) !=\n      fml::Base64::kNoError) {\n    return {0, nullptr};\n  }\n\n  auto encode_data = std::shared_ptr<uint8_t>(new uint8_t[encode_data_length],\n                                              std::default_delete<uint8_t[]>());\n\n  if (fml::Base64::Decode(data, data_len, encode_data.get(),\n                          &encode_data_length) != fml::Base64::kNoError) {\n    return {0, nullptr};\n  }\n\n  return {encode_data_length, encode_data};\n}\n\nvoid FontResourceManager::LoadFontSync(const std::string& font_family,\n                                       std::vector<std::string>& url_vec) {\n  // first : find cache\n  if (font_url_map_.find(font_family) != font_url_map_.end()) {\n    return;\n  }\n\n  // second : cache src\n  if (url_vec.empty()) {\n    return;\n  }\n  font_url_map_.emplace(font_family, std::move(url_vec));\n\n  int index = -1;\n  for (auto& one_url : font_url_map_[font_family]) {\n    ++index;\n    if (url::ParseUriScheme(one_url) == url::UriSchemeType::kData) {\n      auto data = DecodeBase64Str(one_url);\n      if (data.length > 0) {\n        OnDownloadEnd(true, index, font_family, data, nullptr);\n        break;\n      }\n      continue;\n    }\n\n    std::shared_ptr<ResourceLoader> loader =\n        ResourceLoaderFactory::Create(one_url, nullptr);\n    if (!loader) {\n      FML_LOG(ERROR) << \"create ResourceLoader fail\";\n      return;\n    }\n    auto data = loader->LoadSync(one_url, ResourceType::kFont);\n    if (data.length > 0) {\n      OnDownloadEnd(true, index, font_family, data, nullptr);\n      break;\n    }\n  }\n\n  return;\n}\n\nbool FontResourceManager::HasFontResource(const std::string& font_family) {\n  if (font_resource_map_.find(font_family) != font_resource_map_.end()) {\n    return true;\n  }\n  return false;\n}\n\nbool FontResourceManager::CheckFontScheme(const std::string_view base64_str) {\n  // https://www.rfc-editor.org/rfc/rfc8081#ref-W3C.CR-css-fonts-3-20131003\n\n  const char* scheme1 = \"data:application/\";\n  const char* scheme2 = \"data:font/\";\n\n  return lynx::base::BeginsWith(base64_str, scheme1) ||\n         lynx::base::BeginsWith(base64_str, scheme2);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/resource/font_resource_manager.h",
    "content": "\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_FONT_RESOURCE_MANAGER_H_\n#define CLAY_UI_RESOURCE_FONT_RESOURCE_MANAGER_H_\n\n#include <map>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/net/loader/resource_loader.h\"\n#include \"clay/net/resource_type.h\"\n\nnamespace clay {\n\nusing FontCallback =\n    std::function<void(const std::string& font_family, const std::string& url)>;\nclass ResourceLoaderIntercept;\nclass ServiceManager;\n\n// pre load font and cache it util call TakeResource func\nclass FontResourceManager\n    : public std::enable_shared_from_this<FontResourceManager> {\n public:\n  FontResourceManager();\n  ~FontResourceManager();\n\n  RawResource GetResource(const std::string& font_family);\n\n  void LoadFontAsync(fml::RefPtr<fml::TaskRunner> load_task_runner,\n                     std::shared_ptr<ResourceLoaderIntercept> intercept,\n                     std::shared_ptr<ServiceManager> service_manager,\n                     const std::string& font_family,\n                     std::vector<std::string> url_vec,\n                     const FontCallback& callback);\n\n  // call TakeResource to get resource\n  void LoadFontSync(const std::string& font_family,\n                    std::vector<std::string>& url_vec);\n\n  bool HasFontResource(const std::string& font_family);\n\n  void SetCallback(const FontCallback& callback);\n\n  bool HasFontResourceLoading(std::string font_family);\n\n private:\n  void OnDownloadEnd(\n      bool success, const int url_index, const std::string& font_family,\n      RawResource data, fml::RefPtr<fml::TaskRunner> load_task_runner,\n      std::shared_ptr<ResourceLoaderIntercept> intercept = nullptr,\n      std::shared_ptr<ServiceManager> service_manager = nullptr);\n  void DownloadFont(fml::RefPtr<fml::TaskRunner> load_task_runner,\n                    std::shared_ptr<ResourceLoaderIntercept> intercept,\n                    std::shared_ptr<ServiceManager> service_manager,\n                    const std::string& url, const int url_index,\n                    const std::string& font_family);\n\n  RawResource DecodeBase64Str(const std::string& base64_str);\n  bool CheckFontScheme(const std::string_view base64_str);\n\n  std::map<std::string, std::vector<std::string>> font_url_map_;\n  std::map<std::string, std::shared_ptr<ResourceLoader>> font_loader_map_;\n  std::map<std::string, RawResource> font_resource_map_;\n  std::map<std::string, FontCallback> font_call_back_map_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RESOURCE_FONT_RESOURCE_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/resource/font_resource_manager_unittests.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/thread.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/fml/paths.h\"\n#include \"clay/net/net_loader_manager.h\"\n#include \"clay/testing/thread_test.h\"\n#include \"clay/ui/resource/font_collection.h\"\n#include \"clay/ui/resource/font_resource_manager.h\"\n\nnamespace clay {\n\nclass FontResourceManagerTest : public clay::testing::ThreadTest {\n protected:\n  void SetUp() override { font_collection_ = FontCollection::Instance(); }\n\n  void TearDown() override {}\n\n  static std::vector<std::string> GetLocalFilePath();\n  static std::vector<std::string> GetNetFileURL();\n\n protected:\n  std::shared_ptr<FontCollection> font_collection_;\n};\n\nstd::vector<std::string> FontResourceManagerTest::GetLocalFilePath() {\n  // first : init\n  const std::string test_file =\n      \"gen/lynx/clay/third_party/txt/assets/Roboto-Bold.ttf\";\n  auto directory = fml::paths::GetExecutableDirectoryPath();\n  std::string ttf_path;\n  if (directory.first) {\n    auto dir = directory.second;\n    auto pos = dir.find_last_of('/');\n    if (pos != std::string::npos && dir.substr(pos + 1) == \"exe.unstripped\") {\n      dir = dir.substr(0, pos);\n    }\n    ttf_path = fml::paths::JoinPaths({dir, test_file});\n  } else {\n    FML_LOG(ERROR) << \"Failed get test font ttf files.\";\n    EXPECT_TRUE(false);\n  }\n#if OS_WIN\n  return {\"file:///\" + ttf_path};\n#else\n  return {\"file://\" + ttf_path};\n#endif\n}\n\nstd::vector<std::string> FontResourceManagerTest::GetNetFileURL() {\n  const std::string url = \"https://www.fontsaddict.com/fontface/raw.ttf\";\n  const std::string fail_url = \"https://1\";\n  return {fail_url, url};\n}\n\nTEST_F(FontResourceManagerTest, GetLocalResourceTest) {\n  const std::string family_name = \"local_font_file\";\n  auto local_files = GetLocalFilePath();\n  font_collection_->font_resource_manager_->LoadFontSync(family_name,\n                                                         local_files);\n  auto font_resource =\n      font_collection_->font_resource_manager_->GetResource(family_name);\n\n  EXPECT_NE(font_resource.data, nullptr);\n  EXPECT_EQ((int)font_resource.length, 170760);\n}\n\nTEST_F(FontResourceManagerTest, DISABLED_GetNetWorkResourceTest) {\n  const std::string family_name = \"net_font_file\";\n  auto net_files = GetNetFileURL();\n  font_collection_->font_resource_manager_->LoadFontSync(family_name,\n                                                         net_files);\n  auto font_resource =\n      font_collection_->font_resource_manager_->GetResource(family_name);\n\n  EXPECT_NE(font_resource.data, nullptr);\n  EXPECT_EQ((int)font_resource.length, 61260);\n}\n\nTEST_F(FontResourceManagerTest, FontCollectionTest) {\n  const std::string family_name = \"local_font_file\";\n  auto local_files = GetLocalFilePath();\n  font_collection_->font_resource_manager_->LoadFontSync(family_name,\n                                                         local_files);\n  auto font_resource =\n      font_collection_->font_resource_manager_->GetResource(family_name);\n\n  EXPECT_NE(font_resource.data, nullptr);\n\n  EXPECT_EQ((int)font_resource.length, 170760);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/resource/gpu_resource_cache.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/resource/gpu_resource_cache.h\"\n\n#include \"clay/common/sys_info.h\"\n\nnamespace clay {\n\nnamespace {\n// The maximum number of images that we can lock simultaneously in our working\n// set. This is separate from the memory limit, as keeping very large numbers\n// of small images simultaneously locked can lead to performance issues and\n// memory spikes.\nconstexpr size_t kMaxItemsInCache = 256;\n\nconstexpr size_t kMaxMemoryLimitBytes = 32 * 1024 * 1024;        // 32MB\nconstexpr size_t kLowEndMaxMemoryLimitBytes = 16 * 1024 * 1024;  // 16MB\n\nconst fml::TimeDelta kTimeToLiveForActiveResource =\n    fml::TimeDelta::FromMilliseconds(fml::kDefaultFrameBudget.count() * 3);\n\nconst fml::TimeDelta kDefaultTimeToLive =\n    fml::TimeDelta::FromSeconds(3600 * 12);\n\nconstexpr size_t kMBytes = 1024 * 1024;  // 1MB\nconst float kRecycleRamThreshold = 0.8f;\n}  // namespace\n\nGpuResourceCache::GpuResourceCache()\n    : max_memory_limit_(SysInfo::IsLowEndDevice() ? kLowEndMaxMemoryLimitBytes\n                                                  : kMaxMemoryLimitBytes) {}\n\nvoid GpuResourceCache::StoreImage(fml::RefPtr<SkImageHolder> img) {\n  ReduceCacheUsage();\n\n  fml::TimePoint timestamp = fml::TimePoint::Now();\n  if (!is_low_memory_ && img->ShouldUseCache()) {\n    timestamp = timestamp + kDefaultTimeToLive;\n  } else {\n    timestamp = timestamp + kTimeToLiveForActiveResource;\n  }\n\n  auto iter = images_.find(img);\n  if (iter != images_.end()) {\n    // Need to maintain ordering by removing then inserting.\n    images_.erase(iter);\n    img->set_timestamp(timestamp);\n    images_.insert(img);\n    return;\n  }\n\n  current_memory_bytes_ += img->GetAllocationSize();\n  img->set_timestamp(timestamp);\n  images_.insert(img);\n}\n\nvoid GpuResourceCache::RemoveImage(fml::RefPtr<SkImageHolder> img) {\n  auto iter = images_.find(img);\n  if (iter != images_.end()) {\n    iter->get()->ReleaseResource();\n    current_memory_bytes_ -= iter->get()->GetAllocationSize();\n    images_.erase(iter);\n    return;\n  }\n}\n\nvoid GpuResourceCache::RecycleInternal() {\n  // Release expired images\n  fml::TimePoint now = fml::TimePoint::Now();\n  while (!images_.empty()) {\n    auto iter = images_.begin();\n    if (iter->get()->timestamp() > now) {\n      break;\n    }\n    iter->get()->ReleaseResource();\n    current_memory_bytes_ -= iter->get()->GetAllocationSize();\n    images_.erase(iter);\n  }\n\n  // Release more images to stay under the limits\n  while (!images_.empty()) {\n    if (current_memory_bytes_ < max_memory_limit_ &&\n        images_.size() < kMaxItemsInCache) {\n      break;\n    }\n    auto iter = images_.begin();\n    iter->get()->ReleaseResource();\n    current_memory_bytes_ -= iter->get()->GetAllocationSize();\n    images_.erase(iter);\n  }\n}\n\nvoid GpuResourceCache::ReduceCacheUsage(bool force) {\n  if (force || NeedToRecycle()) {\n    RecycleInternal();\n  }\n}\n\nbool GpuResourceCache::NeedToRecycle() const {\n  return static_cast<float>(current_memory_bytes_) / max_memory_limit_ >=\n             kRecycleRamThreshold ||\n         images_.size() >= kMaxItemsInCache;\n}\n\nvoid GpuResourceCache::ClearCache() {\n  while (!images_.empty()) {\n    auto iter = images_.begin();\n    iter->get()->ReleaseResource();\n    images_.erase(iter);\n  }\n  current_memory_bytes_ = 0u;\n}\n\nvoid GpuResourceCache::SetIsLowMemory(bool value) {\n  is_low_memory_ = value;\n  if (is_low_memory_) {\n    ClearCache();\n  }\n}\n\nvoid GpuResourceCache::UpdateResourceCacheMaxMemoryLimit(int limit,\n                                                         int low_end_limit) {\n  if (!SysInfo::IsLowEndDevice() && limit != -1) {\n    max_memory_limit_ = limit * kMBytes;\n  }\n  if (SysInfo::IsLowEndDevice() && low_end_limit != -1) {\n    max_memory_limit_ = low_end_limit * kMBytes;\n  }\n}\n\n#ifndef NDEBUG\nvoid GpuResourceCache::PrintCacheUsage() const {\n  FML_DLOG(ERROR) << \" Max count: \" << kMaxItemsInCache\n                  << \" Ram limitation: \" << kMaxMemoryLimitBytes\n                  << \" Count usage: \"\n                  << static_cast<float>(images_.size()) / kMaxItemsInCache\n                  << \" RAM usage:\"\n                  << static_cast<float>(current_memory_bytes_) /\n                         max_memory_limit_;\n}\n#endif\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/resource/gpu_resource_cache.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_GPU_RESOURCE_CACHE_H_\n#define CLAY_UI_RESOURCE_GPU_RESOURCE_CACHE_H_\n\n#include <set>\n\n#include \"clay/gfx/image/skimage_holder.h\"\n\nnamespace clay {\n\nclass GpuResourceCache {\n public:\n  GpuResourceCache();\n  ~GpuResourceCache() = default;\n\n  void StoreImage(fml::RefPtr<SkImageHolder> img);\n  void RemoveImage(fml::RefPtr<SkImageHolder> img);\n\n  void ReduceCacheUsage(bool force = false);\n  void ClearCache();\n\n  size_t GetMaxMemoryLimitBytes() const { return max_memory_limit_; }\n  void SetMaxMemoryLimitBytes(size_t limit) { max_memory_limit_ = limit; }\n\n  void SetIsLowMemory(bool value);\n\n  void UpdateResourceCacheMaxMemoryLimit(int limit, int low_end_limit);\n  bool NeedToRecycle() const;\n#ifndef NDEBUG\n  void PrintCacheUsage() const;\n#endif\n\n private:\n  struct ImageCompare {\n    bool operator()(const fml::RefPtr<SkImageHolder>& lhs,\n                    const fml::RefPtr<SkImageHolder>& rhs) const {\n      if (lhs->timestamp() == rhs->timestamp()) {\n        return lhs < rhs;\n      }\n      return lhs->timestamp() < rhs->timestamp();\n    }\n  };\n  void RecycleInternal();\n\n  std::set<fml::RefPtr<SkImageHolder>, ImageCompare> images_;\n  size_t max_memory_limit_;\n  size_t current_memory_bytes_ = 0u;\n  bool is_low_memory_ = false;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_RESOURCE_GPU_RESOURCE_CACHE_H_\n"
  },
  {
    "path": "clay/ui/resource/image_cache.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_IMAGE_CACHE_H_\n#define CLAY_UI_RESOURCE_IMAGE_CACHE_H_\n\n#include <list>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"clay/common/sys_info.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/image/image.h\"\n\nnamespace clay {\n\nconstexpr size_t kMBytes = 1024 * 1024;  // 1MB\n#if OS_ANDROID\nconstexpr size_t kImageCacheMaxBytes = 32 * kMBytes;  // 32MB\n#elif OS_WIN || OS_MAC\nconstexpr size_t kImageCacheMaxBytes = 128 * kMBytes;  // 128MB\n#else\nconstexpr size_t kImageCacheMaxBytes = 16 * kMBytes;  // 16MB\n#endif\nconstexpr size_t kImageCacheMaxBytesLowMem = 16 * kMBytes;  // 16MB\nconstexpr float kDesiredOccupancyRatio = 0.8f;\n\nconstexpr size_t kMaxItemBytes = 4 * kMBytes;\nconstexpr size_t kMaxCleanItemsPerCell = 10;\n\n#if OS_WIN || OS_MAC\nconstexpr int64_t kMaxTimeToLive = 360;  // 360 seconds\nconstexpr int64_t kPruneInterval = 60;   // 60 second.\n#else\nconstexpr int64_t kMaxTimeToLive = 60;  // 60 seconds\nconstexpr int64_t kPruneInterval = 1;   // 1 second.\n#endif\n\ntemplate <class T>\nclass ImageCache : public std::enable_shared_from_this<ImageCache<T>> {\n public:\n  explicit ImageCache(fml::RefPtr<fml::TaskRunner> ui_task_runner)\n      : ui_task_runner_(ui_task_runner),\n        current_cache_bytes_(0),\n        max_cached_bytes_(SysInfo::IsLowEndDevice() ? kImageCacheMaxBytesLowMem\n                                                    : kImageCacheMaxBytes) {\n    desired_cache_bytes_ = max_cached_bytes_ * kDesiredOccupancyRatio;\n  }\n  ~ImageCache() { ClearCache(); }\n  void StoreImage(size_t cache_key_hash, const std::string& identifier,\n                  std::shared_ptr<T> image, bool is_prune_old_images = true) {\n#if defined(ENABLE_PLATFORM_DECODE) || OS_WIN || OS_MAC\n    FML_DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n\n    size_t bytes = image->GetGraphicsImageAllocSize();\n    if (bytes > kMaxItemBytes || bytes == 0) {\n      FML_LOG(ERROR) << \"image bytes is \" + std::to_string(bytes);\n      return;\n    }\n\n    cache_list_.emplace_back(cache_key_hash, identifier, fml::TimePoint::Now(),\n                             bytes, image);\n    cache_map_[cache_key_hash] = --cache_list_.end();\n    current_cache_bytes_ += bytes;\n\n    // Clean to max bytes if cache bytes is too large.\n    if (current_cache_bytes_ > max_cached_bytes_) {\n      CleanTo(desired_cache_bytes_);\n    }\n\n    if (!has_flying_prune_task_ && is_prune_old_images) {\n      PruneOldImages();\n    }\n#endif\n  }\n\n  std::shared_ptr<T> TakeImage(size_t cache_key_hash,\n                               const std::string& identifier) {\n#if defined(ENABLE_PLATFORM_DECODE) || OS_WIN || OS_MAC\n    FML_DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n\n    auto found = cache_map_.find(cache_key_hash);\n    if (found == cache_map_.end()) {\n      return nullptr;\n    }\n\n    // Double check: if identifier is provided, verify if it matches\n    if (!identifier.empty() && found->second->identifier_ != identifier) {\n      return nullptr;\n    }\n\n    auto image = found->second->image_;\n    current_cache_bytes_ -= found->second->bytes_;\n    // Delete from inactive cache.\n    cache_list_.erase(found->second);\n    cache_map_.erase(found);\n\n    return image;\n#else\n    return nullptr;\n#endif\n  }\n\n  std::shared_ptr<T> FindImage(size_t cache_key_hash,\n                               const std::string& identifier) {\n#if defined(ENABLE_PLATFORM_DECODE) || OS_WIN || OS_MAC\n    FML_DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n\n    auto found = cache_map_.find(cache_key_hash);\n    if (found == cache_map_.end()) {\n      return nullptr;\n    }\n\n    // Double check: if identifier is provided, verify if it matches\n    if (!identifier.empty() && found->second->identifier_ != identifier) {\n      return nullptr;\n    }\n\n    auto image = found->second->image_;\n    return image;\n#else\n    return nullptr;\n#endif\n  }\n\n  void ClearCache() {\n#if defined(ENABLE_PLATFORM_DECODE) || OS_WIN || OS_MAC\n    FML_DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n\n    ClearCacheInternal();\n#endif\n  }\n\n private:\n  void CleanTo(size_t bytes) {\n    std::vector<std::shared_ptr<T>> images;\n    size_t cleaned_items = 0;\n    auto iter = cache_list_.begin();\n    while (iter != cache_list_.end() && (current_cache_bytes_ > bytes) &&\n           (cleaned_items++ < kMaxCleanItemsPerCell)) {\n      images.push_back(iter->image_);\n      current_cache_bytes_ -= iter->bytes_;\n      cache_map_.erase(iter->cache_key_hash_);\n      iter = cache_list_.erase(iter);\n    }\n\n    if (!images.empty()) {\n      ReleaseImagesOnWorkerThread(images);\n    }\n  }\n  void ClearCacheInternal() {\n    if (current_cache_bytes_ > 0) {\n      current_cache_bytes_ = 0;\n      GraphicsIsolate::Instance().GetConcurrentWorkerTaskRunner()->PostTask(\n          [cache_list = std::move(cache_list_),\n           cache_map = std::move(cache_map_)]() mutable {\n            cache_map.clear();\n            cache_list.clear();\n          });\n    }\n  }\n  void PruneOldImages() {\n    std::vector<std::shared_ptr<T>> images;\n    size_t cleaned_items = 0;\n\n    fml::TimePoint now = fml::TimePoint::Now();\n    auto iter = cache_list_.begin();\n    while (iter != cache_list_.end()) {\n      fml::TimePoint expiry_time =\n          iter->time_stamp_ + fml::TimeDelta::FromSeconds(kMaxTimeToLive);\n      bool expired = now > expiry_time;\n      if (!expired || cleaned_items >= kMaxCleanItemsPerCell) {\n        FML_DLOG(INFO) << \"post delayed task to prune old images, expired: \"\n                       << expired << \", cleaned_items: \" << cleaned_items;\n        has_flying_prune_task_ = true;\n        std::weak_ptr<ImageCache<T>> weak_this = this->shared_from_this();\n        ui_task_runner_->PostDelayedTask(\n            [weak_this]() {\n              auto self = weak_this.lock();\n              if (self) {\n                self->has_flying_prune_task_ = false;\n                self->PruneOldImages();\n              }\n            },\n            !expired ? expiry_time - now\n                     : fml::TimeDelta::FromSeconds(kPruneInterval));\n        break;\n      } else {\n        images.push_back(iter->image_);\n        current_cache_bytes_ -= iter->bytes_;\n        cache_map_.erase(iter->cache_key_hash_);\n        iter = cache_list_.erase(iter);\n        cleaned_items++;\n      }\n    }\n    if (!images.empty()) {\n      ReleaseImagesOnWorkerThread(images);\n    }\n  }\n\n  void ReleaseImagesOnWorkerThread(\n      const std::vector<std::shared_ptr<T>>& images) {\n    // Destroy images maybe time consuming, we post to worker thread to avoid\n    // blocking ui.\n    GraphicsIsolate::Instance().GetConcurrentWorkerTaskRunner()->PostTask(\n        [released_images = std::move(images)]() mutable {\n          released_images.clear();\n        });\n  }\n\n  struct ImageItem {\n    ImageItem(size_t cache_key_hash, const std::string& identifier,\n              const fml::TimePoint& time_stamp, size_t bytes,\n              std::shared_ptr<T> image)\n        : cache_key_hash_(cache_key_hash),\n          identifier_(identifier),\n          time_stamp_(time_stamp),\n          bytes_(bytes),\n          image_(image) {}\n    size_t cache_key_hash_;\n    // Identifier for the image:\n    // - For non-SVG images: Trimmed URL\n    // - For SVG images: MD5 hash of the source content\n    // (lynx::base::md5(source))\n    std::string identifier_;\n    fml::TimePoint time_stamp_;\n    size_t bytes_;\n    std::shared_ptr<T> image_;\n  };\n\n  std::list<ImageItem> cache_list_;\n  std::unordered_map<size_t, decltype(cache_list_.begin())> cache_map_;\n\n  fml::RefPtr<fml::TaskRunner> ui_task_runner_;\n\n  size_t current_cache_bytes_;\n  size_t max_cached_bytes_;\n  size_t desired_cache_bytes_;\n  bool has_flying_prune_task_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RESOURCE_IMAGE_CACHE_H_\n"
  },
  {
    "path": "clay/ui/resource/image_fetcher.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"clay/ui/resource/image_fetcher.h\"\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/md5.h\"\n#include \"clay/common/service/service_manager.h\"\n#include \"clay/gfx/image/animated_image.h\"\n#include \"clay/gfx/image/base_image.h\"\n#include \"clay/gfx/image/static_image.h\"\n#include \"clay/gfx/image/svg_image.h\"\n#include \"clay/net/loader/resource_loader.h\"\n#include \"clay/net/loader/resource_loader_factory.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/net/url/url_helper.h\"\n\nnamespace clay {\n\nnamespace {\n\nuint64_t NextUniqueID() {\n  static std::atomic<uint64_t> next_id(1);\n  uint64_t id;\n  do {\n    id = next_id.fetch_add(1);\n  } while (id == 0);  // 0 is reserved for an invalid id.\n  return id;\n}\n\nstd::shared_ptr<ResourceLoader> GetOrCreateResourceLoader(\n    std::shared_ptr<ResourceLoaderIntercept> intercept, const std::string& url,\n    fml::RefPtr<fml::TaskRunner> task_runner,\n    std::shared_ptr<ServiceManager> service_manager) {\n#if OS_ANDROID\n  // Assuming that the `task_runner` will never be changed.\n  if (url.compare(0, 5, \"data:\") == 0) {\n    static auto data_loader =\n        ResourceLoaderFactory::Create(\"data:\", task_runner);\n    return data_loader;\n  }\n\n  static auto url_loader =\n      ResourceLoaderFactory::Create(\"https://\", std::move(task_runner));\n  return url_loader;\n#else\n  std::shared_ptr<ResourceLoader> loader = ResourceLoaderFactory::Create(\n      url, task_runner, intercept, service_manager);\n  return loader;\n#endif\n}\n\n}  // namespace\n\nImageFetcher::ImageFetcher(std::shared_ptr<ResourceLoaderIntercept> intercept,\n                           clay::TaskRunners task_runners,\n                           fml::RefPtr<GPUUnrefQueue> unref_queue,\n                           std::shared_ptr<ServiceManager> service_manager)\n    : weak_factory_(this),\n      resource_loader_intercept_(std::move(intercept)),\n      service_manager_(std::move(service_manager)),\n      task_runners_(std::move(task_runners)),\n      unref_queue_(unref_queue),\n      inactive_image_cache_(std::make_shared<ImageCache<BaseImage>>(\n          task_runners_.GetUITaskRunner())) {}\n\nImageFetcher::~ImageFetcher() = default;\n\nuint64_t ImageFetcher::FetchImage(const std::string& original_url, bool is_svg,\n                                  const ImageCallback& callback) {\n  auto fetchID = NextUniqueID();\n\n  std::string trimmed_url = url::TrimUrl(original_url);\n  if (trimmed_url.empty()) {\n    callback(nullptr, false);\n    return fetchID;\n  }\n\n  std::string identifier = trimmed_url.compare(0, 5, \"data:\") == 0\n                               ? lynx::base::md5(trimmed_url)\n                               : trimmed_url;\n  size_t cache_key_hash = std::hash<std::string>{}(identifier);\n  auto image = FindImageFromCache(cache_key_hash, identifier);\n  if (image) {\n    callback(image->NewInstance(), true);\n    return fetchID;\n  }\n  image_callback_map_.insert({trimmed_url, callback});\n  auto it = url_loader_map_.find(trimmed_url);\n  if (it == url_loader_map_.end()) {\n    if (is_svg) {\n      auto loader = GetOrCreateResourceLoader(\n          resource_loader_intercept_, trimmed_url,\n          task_runners_.GetUITaskRunner(), service_manager_);\n      if (!loader) {\n        OnFetchFinish(trimmed_url, nullptr);\n        return fetchID;\n      }\n      url_loader_map_.insert({trimmed_url, loader});\n      loader->Load(trimmed_url, [self = GetWeakPtr(), trimmed_url, identifier,\n                                 callback](const uint8_t* data, size_t size) {\n        if (!self) {\n          callback(nullptr, false);\n          return;\n        }\n        if (data == nullptr || size == 0) {\n          self->OnFetchFinish(trimmed_url, nullptr);\n          return;\n        }\n\n        auto image = SVGImage::Make(\n            self->weak_factory_.GetWeakPtr(), trimmed_url,\n            std::string(reinterpret_cast<const char*>(data), size));\n        image->SetCacheIdentifier(identifier);\n        self->active_image_map_.insert({identifier, image});\n        self->OnFetchFinish(trimmed_url, image);\n      });\n    } else {\n      url_loader_map_.insert({trimmed_url, nullptr});\n      FetchImage(trimmed_url,\n                 [self = GetWeakPtr(), trimmed_url, identifier,\n                  callback](std::shared_ptr<PlatformImage> platform_image) {\n                   if (!self) {\n                     callback(nullptr, false);\n                     return;\n                   }\n                   if (!platform_image) {\n                     self->OnFetchFinish(trimmed_url, nullptr);\n                     return;\n                   }\n                   std::shared_ptr<BaseImage> image;\n                   if (platform_image->IsAnimated()) {\n                     image = AnimatedImage::Make(\n                         self->weak_factory_.GetWeakPtr(), trimmed_url,\n                         self->task_runners_.GetUITaskRunner(), platform_image);\n                   } else {\n                     image = StaticImage::Make(self->weak_factory_.GetWeakPtr(),\n                                               trimmed_url, platform_image);\n                   }\n                   image->SetCacheIdentifier(identifier);\n                   self->active_image_map_.insert({identifier, image});\n                   self->OnFetchFinish(trimmed_url, image);\n                 });\n    }\n  }\n  return fetchID;\n}\n\nuint64_t ImageFetcher::FetchSVGImageWithContent(const std::string& content,\n                                                const ImageCallback& callback) {\n  auto fetchID = NextUniqueID();\n\n  auto content_md5 = lynx::base::md5(content);\n  size_t cache_key_hash = std::hash<std::string>{}(content_md5);\n  auto image = FindImageFromCache(cache_key_hash, content_md5);\n  if (image) {\n    callback(image->NewInstance(), true);\n    return fetchID;\n  }\n  auto svg_image = SVGImage::Make(weak_factory_.GetWeakPtr(), \"\", content);\n  svg_image->SetCacheKeyHash(cache_key_hash);\n  svg_image->SetContentMD5(content_md5);\n  active_image_map_.insert({content_md5, svg_image});\n  callback(svg_image->NewInstance(), false);\n  return fetchID;\n}\n\nvoid ImageFetcher::OnFetchFinish(const std::string& trimmed_url,\n                                 std::shared_ptr<BaseImage> image) {\n  auto loader_it = url_loader_map_.find(trimmed_url);\n  if (loader_it != url_loader_map_.end()) {\n    url_loader_map_.erase(loader_it);\n  }\n\n  auto range = image_callback_map_.equal_range(trimmed_url);\n  for (auto it = range.first; it != range.second; ++it) {\n    if (image) {\n      it->second(image->NewInstance(), false);\n    } else {\n      it->second(nullptr, false);\n    }\n  }\n  image_callback_map_.erase(trimmed_url);\n}\n\nvoid ImageFetcher::TryCancelAsyncFetch(const std::string& original_url,\n                                       uint64_t fetch_id) {\n  std::string trimmed_url = url::TrimUrl(original_url);\n  auto iter = url_loader_map_.find(trimmed_url);\n  if (iter != url_loader_map_.end()) {\n    if (iter->second) {\n      iter->second->CancelAll();\n    }\n    url_loader_map_.erase(iter);\n  }\n  image_callback_map_.erase(trimmed_url);\n}\n\nstd::shared_ptr<BaseImage> ImageFetcher::FindImageFromCache(\n    size_t cache_key_hash, const std::string& identifier) {\n  auto it = active_image_map_.find(identifier);\n  if (it != active_image_map_.end()) {\n    return it->second;\n  }\n\n  auto image = inactive_image_cache_->TakeImage(cache_key_hash, identifier);\n  if (image) {\n    active_image_map_.insert({identifier, image});\n    return image;\n  }\n  return nullptr;\n}\n\nvoid ImageFetcher::OnImageHasNoAccessor(BaseImage* image) {\n  if (!image->GetCacheIdentifier().empty()) {\n    size_t cache_key_hash =\n        std::hash<std::string>{}(image->GetCacheIdentifier());\n    MoveToInactiveCacheIfNeeded(cache_key_hash, image->GetCacheIdentifier(),\n                                image);\n    return;\n  }\n\n  if (image->IsSVG()) {\n    SVGImage* svg_image = static_cast<SVGImage*>(image);\n    if (svg_image->GetContentMD5().empty()) {\n      return;\n    }\n    MoveToInactiveCacheIfNeeded(svg_image->GetCacheKeyHash(),\n                                svg_image->GetContentMD5(), svg_image);\n  }\n}\n\nvoid ImageFetcher::MoveToInactiveCacheIfNeeded(size_t cache_key_hash,\n                                               const std::string& identifier,\n                                               const BaseImage* image) {\n  // Remove the image from the active_image_map_.\n  auto range = active_image_map_.equal_range(identifier);\n  size_t count = std::distance(range.first, range.second);\n  if (count == 1) {\n    // If there is only one image, then move it to inactive image cache.\n    inactive_image_cache_->StoreImage(cache_key_hash, identifier,\n                                      range.first->second);\n    active_image_map_.erase(range.first);\n  } else {\n    for (auto image_iter = range.first; image_iter != range.second;\n         ++image_iter) {\n      if (image_iter->second.get() == image) {\n        active_image_map_.erase(image_iter);\n        break;\n      }\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/resource/image_fetcher.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_IMAGE_FETCHER_H_\n#define CLAY_UI_RESOURCE_IMAGE_FETCHER_H_\n\n#include <map>\n#include <memory>\n#include <string>\n#include <unordered_map>\n\n#include \"clay/common/task_runners.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/base_image.h\"\n#include \"clay/net/loader/resource_loader.h\"\n#include \"clay/ui/resource/image_cache.h\"\n\nnamespace clay {\n\nclass ResourceLoaderIntercept;\nclass ServiceManager;\n\nclass ImageFetcher : public fml::RefCountedThreadSafe<ImageFetcher> {\n public:\n  using ImageCallback =\n      std::function<void(std::unique_ptr<BaseImageInstance>, bool)>;\n  static fml::RefPtr<ImageFetcher> Create(\n      std::shared_ptr<ResourceLoaderIntercept> intercept,\n      clay::TaskRunners task_runners, fml::RefPtr<GPUUnrefQueue> unref_queue,\n      std::shared_ptr<ServiceManager> service_manager);\n\n  virtual ~ImageFetcher();\n  ImageFetcher(std::shared_ptr<ResourceLoaderIntercept> intercept,\n               clay::TaskRunners task_runners,\n               fml::RefPtr<GPUUnrefQueue> unref_queue,\n               std::shared_ptr<ServiceManager> service_manager);\n  uint64_t FetchImage(const std::string& original_url, bool is_svg,\n                      const ImageCallback& callback);\n  uint64_t FetchSVGImageWithContent(const std::string& content,\n                                    const ImageCallback& callback);\n  void TryCancelAsyncFetch(const std::string& original_url, uint64_t fetch_id);\n\n  void OnImageHasNoAccessor(BaseImage* image);\n\n protected:\n  fml::WeakPtr<ImageFetcher> GetWeakPtr() const {\n    return weak_factory_.GetWeakPtr();\n  }\n  virtual void FetchImage(\n      const std::string& trimmed_url,\n      const std::function<void(std::shared_ptr<PlatformImage>)>& callback) = 0;\n\n  void OnFetchFinish(const std::string& trimmed_url,\n                     std::shared_ptr<BaseImage> image);\n\n  std::shared_ptr<BaseImage> FindImageFromCache(size_t cache_key_hash,\n                                                const std::string& identifier);\n  void MoveToInactiveCacheIfNeeded(size_t cache_key_hash,\n                                   const std::string& identifier,\n                                   const BaseImage* image);\n\n protected:\n  fml::WeakPtrFactory<ImageFetcher> weak_factory_;\n  std::shared_ptr<ResourceLoaderIntercept> resource_loader_intercept_;\n  std::shared_ptr<ServiceManager> service_manager_;\n  clay::TaskRunners task_runners_;\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n  std::unordered_map<std::string, std::shared_ptr<BaseImage>> active_image_map_;\n  std::shared_ptr<ImageCache<BaseImage>> inactive_image_cache_;\n  std::unordered_map<std::string, std::shared_ptr<ResourceLoader>>\n      url_loader_map_;\n  std::multimap<std::string, ImageCallback> image_callback_map_;\n};\n\n}  // namespace clay\n#endif  // CLAY_UI_RESOURCE_IMAGE_FETCHER_H_\n"
  },
  {
    "path": "clay/ui/resource/image_manager.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/resource/image_manager.h\"\n\n#include <string>\n#include <utility>\n\n#include \"base/include/md5.h\"\n#include \"clay/gfx/graphics_isolate.h\"\n#include \"clay/gfx/image/image_data_cache.h\"\n#include \"clay/gfx/image/image_producer.h\"\n#include \"clay/net/url/url_helper.h\"\n#include \"clay/ui/resource/image_cache.h\"\n\nnamespace clay {\n\nnamespace {\n// [key]: raster task queue id, [value]: ImageManager.\nstatic std::unordered_map<size_t, std::shared_ptr<ImageManager>>\n    image_manager_map;\nstatic uint64_t clean_up_data_task_id = 0;\nconstexpr int64_t kDeferredCleanupImageDataInterval = 30;  // 30 seconds\n\nuint64_t GenerateTaskID() {\n  static uint64_t task_id = 0;\n  ++task_id;\n  if (task_id == 0) {\n    ++task_id;\n  }\n  return task_id;\n}\n\n}  // namespace\n\nImageManager::ImageManager(clay::TaskRunners task_runners,\n                           fml::RefPtr<GPUUnrefQueue> unref_queue)\n    : task_runners_(task_runners),\n      unref_queue_(unref_queue),\n      inactive_image_cache_(std::make_shared<ImageCache<Image>>(\n          task_runners_.GetUITaskRunner())) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n}\n\nImageManager::~ImageManager() {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n}\n\nvoid ImageManager::AddAccessor(ImageResourceFetcher* accessor) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  accessors_.insert(accessor);\n}\n\nvoid ImageManager::RemoveAccessor(ImageResourceFetcher* accessor) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  accessors_.erase(accessor);\n  // If there is no accessor, then perform removal.\n  if (accessors_.empty()) {\n    PerformRemoval();\n  }\n}\n\nvoid ImageManager::PerformRemoval() {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  auto task_runner = task_runners_.GetUITaskRunner();\n  size_t id = task_runners_.GetRasterTaskRunner()->GetTaskQueueId();\n  image_manager_map.erase(id);\n\n  // Perform the deferred cleanup of ImageDataCache when the last ImageManager\n  // is destructed. This is to ensure that the resources managed by DataCache\n  // are not prematurely cleaned up if there is a possibility of creating new\n  // ImageManager instances shortly after.\n  if (image_manager_map.empty()) {\n    clean_up_data_task_id = GenerateTaskID();\n    task_runners_.GetUITaskRunner()->PostDelayedTask(\n        [id = clean_up_data_task_id]() {\n          if (id == clean_up_data_task_id && image_manager_map.empty()) {\n            ImageDataCache::GetInstance().ClearCache();\n          }\n        },\n        fml::TimeDelta::FromSeconds(kDeferredCleanupImageDataInterval));\n  }\n}\n\nvoid ImageManager::ClearCache() { inactive_image_cache_->ClearCache(); }\n\nvoid ImageManager::ImageHasNoAccessor(const Image* image) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n  FML_DCHECK(image != nullptr);\n#if defined(ENABLE_SVG)\n  if (image->IsSVG() && !image->GetContentMD5().empty()) {\n    MoveToInactiveCacheIfNeeded(image->GetCacheKeyHash(),\n                                image->GetContentMD5(), image);\n    return;\n  }\n#endif\n\n  std::string identifier = image->GetCacheIdentifier();\n  size_t cache_key_hash = std::hash<std::string>{}(identifier);\n  MoveToInactiveCacheIfNeeded(cache_key_hash, identifier, image);\n}\n\nvoid ImageManager::MoveToInactiveCacheIfNeeded(size_t cache_key_hash,\n                                               const std::string& identifier,\n                                               const Image* image) {\n  if (identifier.empty()) {\n    return;\n  }\n  const_cast<Image*>(image)->SetIsActive(false);\n  // Remove the image from the active_image_map_.\n  auto range = active_image_map_.equal_range(identifier);\n  size_t count = std::distance(range.first, range.second);\n  if (count == 1) {\n    // If there is only one image, then move it to inactive image cache.\n    inactive_image_cache_->StoreImage(cache_key_hash, identifier,\n                                      range.first->second);\n    active_image_map_.erase(range.first);\n  } else {\n    for (auto image_iter = range.first; image_iter != range.second;\n         ++image_iter) {\n      if (image_iter->second.get() == image) {\n        active_image_map_.erase(image_iter);\n        break;\n      }\n    }\n  }\n}\n\nbool ImageManager::GetImageResource(const std::string& url,\n                                    const std::string& cache_identifier,\n                                    const ImageResourceCallback& callback,\n                                    bool use_texture_backend, bool is_deferred,\n                                    bool decode_with_priority,\n                                    bool enable_low_quality_image,\n                                    bool is_promise, bool is_svg) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  // First check if the image is already cached.\n  auto image_resource = GetImageResourceFromCache(cache_identifier);\n  if (image_resource) {\n    callback(std::move(image_resource), true);\n    return true;\n  }\n\n  // If the image is not cached, then check if data is cached.\n  auto sk_data = ImageDataCache::GetInstance().GetImageData(url);\n  if (sk_data) {\n    std::shared_ptr<Image> image = CreateAndCacheImage(\n        url, cache_identifier, sk_data, is_svg, use_texture_backend, is_promise,\n        enable_low_quality_image, is_deferred, decode_with_priority);\n    callback(image->GetAccessor(), true);\n    return true;\n  }\n\n  return false;\n}\n\nvoid ImageManager::GetImageResource(const std::string& url,\n                                    const std::string& cache_identifier,\n                                    const ImageResourceCallback& callback,\n                                    const uint8_t* source, const int len,\n                                    bool use_texture_backend, bool is_deferred,\n                                    bool decode_with_priority,\n                                    bool enable_low_quality_image) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  // First check if the image is already cached.\n  auto image_resource = GetImageResourceFromCache(cache_identifier);\n  if (image_resource) {\n    callback(std::move(image_resource), true);\n    return;\n  }\n\n  if (source == nullptr || len <= 0) {\n    callback(nullptr, false);\n    return;\n  }\n\n  // If the image is not cached, create new image by the source.\n  auto sk_data = GrData::MakeWithCopy(source, len);\n  auto image = CreateAndCacheImage(\n      url, cache_identifier, sk_data, false, use_texture_backend, false,\n      enable_low_quality_image, is_deferred, decode_with_priority);\n  callback(image->GetAccessor(), false);\n}\n\nbool ImageManager::UpdateCachedImageData(const std::string& cache_identifier,\n                                         GrDataPtr data) {\n  auto it = active_image_map_.find(cache_identifier);\n  // if the url is not in the map, it means that the image is not cached.\n  if (it == active_image_map_.end()) {\n    return false;\n  } else {\n    // if the identifier is in the map, it means that the image is cached, and\n    // we need to update the image data if needed (e.g. promise image).\n    auto range = active_image_map_.equal_range(cache_identifier);\n    for (auto iter = range.first; iter != range.second; ++iter) {\n      iter->second->SetRawData(data);\n    }\n    return true;\n  }\n}\n\nstd::unique_ptr<ImageResource> ImageManager::GetImageResourceFromCache(\n    const std::string& cache_identifier) {\n  size_t key_hash = std::hash<std::string>{}(cache_identifier);\n  return GetImageResourceFromCache(key_hash, cache_identifier);\n}\n\nstd::unique_ptr<ImageResource> ImageManager::GetImageResourceFromCache(\n    size_t cache_key_hash, const std::string& cache_identifier) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  if (cache_identifier.empty()) {\n    return nullptr;\n  }\n\n  // First check if the image is already cached in active image map.\n  auto it = active_image_map_.find(cache_identifier);\n  if (it != active_image_map_.end()) {\n    std::shared_ptr<Image> image = it->second;\n    // If the image is single frame image, then return a new ui accessor.\n    if (image->IsSingleFrameImage()) {\n      return image->GetAccessor();\n    } else {\n      // The first found image is multi frame image, then check all the images\n      // whth the identifier in the map to see if there is any image with no ui\n      // accessor.\n      auto range = active_image_map_.equal_range(cache_identifier);\n      for (auto iter = range.first; iter != range.second; ++iter) {\n        if (image->GetUIAccessorCount() == 0) {\n          return image->GetAccessor();\n        }\n      }\n\n      // If all the multi frame images with the cache_identifier in the map have\n      // ui accessor, then create a new image, and return it's ui accessor.\n      auto copied_image = Image::CloneImage(image);\n      copied_image->SetCacheIdentifier(cache_identifier);\n      copied_image->SetIsActive(true);\n      active_image_map_.insert({cache_identifier, copied_image});\n      return copied_image->GetAccessor();\n    }\n  }\n\n  // If the image is not cached, then check if the image is cached in inactive\n  // image map.\n  auto image =\n      inactive_image_cache_->TakeImage(cache_key_hash, cache_identifier);\n  if (image) {\n    image->SetIsActive(true);\n    active_image_map_.insert({cache_identifier, image});\n    return image->GetAccessor();\n  }\n\n  return nullptr;\n}\n\n#if defined(ENABLE_SVG)\nstd::unique_ptr<ImageResource> ImageManager::GetImageResourceFromSVGContent(\n    const std::string& source, bool use_texture_backend, bool is_deferred,\n    bool decode_with_priority) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  if (source.empty()) {\n    return nullptr;\n  }\n\n  // First check if the image is already cached in Image Resource map.\n  auto content_md5 = lynx::base::md5(source);\n  size_t cache_key_hash = std::hash<std::string>{}(content_md5);\n  auto image_resource = GetImageResourceFromCache(cache_key_hash, content_md5);\n  if (image_resource) {\n    return image_resource;\n  }\n  // If the image is not cached, then create a new image.\n  auto sk_data = GrData::MakeWithCopy(source.data(), source.length());\n  std::shared_ptr<Image> image = Image::CreateImage(\n      \"\", sk_data, nullptr, shared_from_this(), task_runners_.GetUITaskRunner(),\n      task_runners_.GetRasterTaskRunner(), true, use_texture_backend, false,\n      false, is_deferred, decode_with_priority);\n  image->SetContentMD5(content_md5);\n  image->SetCacheKeyHash(cache_key_hash);\n  image->SetIsActive(true);\n  active_image_map_.insert({content_md5, image});\n  return image->GetAccessor();\n}\n#endif\n\nstd::shared_ptr<Image> ImageManager::CreateAndCacheImage(\n    const std::string& url, const std::string& cache_identifier, GrDataPtr data,\n    bool is_svg, bool use_texture_backend, bool is_promise,\n    bool enable_low_quality_image, bool is_deferred,\n    bool decode_with_priority) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  std::shared_ptr<Image> image = Image::CreateImage(\n      url, data, nullptr, shared_from_this(), task_runners_.GetUITaskRunner(),\n      task_runners_.GetRasterTaskRunner(), is_svg, use_texture_backend,\n      is_promise, enable_low_quality_image, is_deferred, decode_with_priority);\n  image->SetCacheIdentifier(cache_identifier);\n  image->SetIsActive(true);\n  active_image_map_.insert({image->GetCacheIdentifier(), image});\n  return image;\n}\n\nstd::unique_ptr<ImageResource> ImageManager::CreateImageResourceFromCachedData(\n    const std::string& url, const std::string& cache_identifier, bool is_svg,\n    bool use_texture_backend, bool is_promise, bool enable_low_quality_image,\n    bool is_deferred, bool decode_with_priority) {\n  auto sk_data = ImageDataCache::GetInstance().GetImageData(url);\n  if (sk_data) {\n    auto image = CreateAndCacheImage(\n        url, cache_identifier, sk_data, is_svg, use_texture_backend, is_promise,\n        enable_low_quality_image, is_deferred, decode_with_priority);\n    return image->GetAccessor();\n  }\n  return nullptr;\n}\n\nstd::shared_ptr<ImageManager> ImageManager::GetOrCreateImageManager(\n    clay::TaskRunners task_runners, fml::RefPtr<GPUUnrefQueue> unref_queue) {\n  FML_DCHECK(task_runners.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  // If the image manager is already created, return it.\n  auto queue_id = task_runners.GetRasterTaskRunner()->GetTaskQueueId();\n  auto iter = image_manager_map.find(queue_id);\n  if (iter != image_manager_map.end()) {\n    return iter->second;\n  }\n\n  // Otherwise, create a new image manager and return it.\n  auto image_manager =\n      std::make_shared<ImageManager>(task_runners, unref_queue);\n  image_manager_map.emplace(queue_id, image_manager);\n\n  return image_manager;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/resource/image_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_IMAGE_MANAGER_H_\n#define CLAY_UI_RESOURCE_IMAGE_MANAGER_H_\n\n#include <map>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/common/task_runners.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/gfx/image/image.h\"\n#include \"clay/ui/resource/image_cache.h\"\n\nnamespace clay {\n\nclass ImageResourceFetcher;\nclass ServiceManager;\n\ntypedef std::function<void(std::unique_ptr<ImageResource>, bool hit_cache)>\n    ImageResourceCallback;\ntypedef std::function<void(std::shared_ptr<Image> image)> ImageCallback;\n\n// On some platforms, multiple engine instances share a single Raster thread,\n// while on others, there are multiple Raster threads. The number of\n// GrDirectContexts and GPUUnrefQueues corresponds one-to-one with the number\n// of Raster threads. When creating an Image, a GrDirectContext and\n// GPUUnrefQueues are required. Therefore, to enhance the reusability of\n// Images, the same Raster thread shares a single ImageManager, allowing for the\n// sharing of cached Images within it.\nclass ImageManager : public Image::Notifier,\n                     public std::enable_shared_from_this<ImageManager> {\n public:\n  ImageManager(clay::TaskRunners task_runners,\n               fml::RefPtr<GPUUnrefQueue> unref_queue);\n  ~ImageManager() override;\n\n  void AddAccessor(ImageResourceFetcher* accessor);\n  void RemoveAccessor(ImageResourceFetcher* accessor);\n\n  void ClearCache();\n\n  bool GetImageResource(const std::string& url,\n                        const std::string& cache_identifier,\n                        const ImageResourceCallback& callback,\n                        bool use_texture_backend, bool is_deferred,\n                        bool decode_with_priority,\n                        bool enable_low_quality_image, bool is_promise,\n                        bool is_svg);\n\n  void GetImageResource(const std::string& url,\n                        const std::string& cache_identifier,\n                        const ImageResourceCallback& callback,\n                        const uint8_t* source, const int len,\n                        bool use_texture_backend, bool is_deferred,\n                        bool decode_with_priority,\n                        bool enable_low_quality_image);\n\n  std::unique_ptr<ImageResource> GetImageResourceFromCache(\n      const std::string& cache_identifier);\n\n  bool UpdateCachedImageData(const std::string& cache_identifier,\n                             GrDataPtr data);\n\n#if defined(ENABLE_SVG)\n  std::unique_ptr<ImageResource> GetImageResourceFromSVGContent(\n      const std::string& source, bool use_texture_backend, bool is_deferred,\n      bool decode_with_priority);\n#endif\n\n  std::shared_ptr<Image> CreateAndCacheImage(\n      const std::string& url, const std::string& cache_identifier,\n      GrDataPtr data, bool is_svg, bool use_texture_backend, bool is_promise,\n      bool enable_low_quality_image, bool is_deferred,\n      bool decode_with_priority);\n\n  std::unique_ptr<ImageResource> CreateImageResourceFromCachedData(\n      const std::string& url, const std::string& cache_identifier, bool is_svg,\n      bool use_texture_backend, bool is_promise, bool enable_low_quality_image,\n      bool is_deferred, bool decode_with_priority);\n\n  // impelementation of Image::Notifier\n  void ImageHasNoAccessor(const Image* image) override;\n  fml::RefPtr<GPUUnrefQueue> GetUnrefQueue() override { return unref_queue_; }\n\n  static std::shared_ptr<ImageManager> GetOrCreateImageManager(\n      clay::TaskRunners task_runners, fml::RefPtr<GPUUnrefQueue> unref_queue);\n\n private:\n  std::unique_ptr<ImageResource> GetImageResourceFromCache(\n      size_t key_hash, const std::string& cache_identifier);\n  void MoveToInactiveCacheIfNeeded(size_t key_hash,\n                                   const std::string& identifier,\n                                   const Image* image);\n\n  void PerformRemoval();\n\n  clay::TaskRunners task_runners_;\n  fml::RefPtr<GPUUnrefQueue> unref_queue_;\n\n  // Contains ImageResourceFetcher which are currently referencing\n  // ImageManager. After all ImageResourceFetcher are removed, ImageManager\n  // will be destroyed.\n  std::unordered_set<ImageResourceFetcher*> accessors_;\n\n  // Contains images which are being used by rendering.\n  std::multimap<std::string, std::shared_ptr<Image>> active_image_map_;\n  // Contains images which are not being used by rendering.\n  std::shared_ptr<ImageCache<Image>> inactive_image_cache_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RESOURCE_IMAGE_MANAGER_H_\n"
  },
  {
    "path": "clay/ui/resource/image_resource_fetcher.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/resource/image_resource_fetcher.h\"\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"base/include/md5.h\"\n#include \"clay/gfx/image/image_descriptor.h\"\n#include \"clay/net/loader/resource_loader.h\"\n#include \"clay/net/loader/resource_loader_factory.h\"\n#include \"clay/net/loader/resource_loader_intercept.h\"\n#include \"clay/net/url/url_helper.h\"\n#include \"clay/ui/resource/image_manager.h\"\n\nnamespace clay {\n\nnamespace {\n\n// To improve performance, we can reuse the `ResourceLoader` for all images.\nstd::shared_ptr<ResourceLoader> GetOrCreateResourceLoader(\n    std::shared_ptr<ResourceLoaderIntercept> intercept, const std::string& url,\n    fml::RefPtr<fml::TaskRunner> task_runner,\n    std::shared_ptr<ServiceManager> service_manager) {\n#if OS_ANDROID\n  // Assuming that the `task_runner` will never be changed.\n  if (url.compare(0, 5, \"data:\") == 0) {\n    static auto data_loader =\n        ResourceLoaderFactory::Create(\"data:\", task_runner);\n    return data_loader;\n  }\n\n  static auto url_loader =\n      ResourceLoaderFactory::Create(\"https://\", std::move(task_runner));\n  return url_loader;\n#else\n  std::shared_ptr<ResourceLoader> loader = ResourceLoaderFactory::Create(\n      url, task_runner, intercept, service_manager);\n  return loader;\n#endif\n}\n\n}  // namespace\n\nImageResourceFetcher::ImageResourceFetcher(\n    std::shared_ptr<ResourceLoaderIntercept> intercept,\n    clay::TaskRunners task_runners, fml::RefPtr<GPUUnrefQueue> unref_queue,\n    std::shared_ptr<ServiceManager> service_manager)\n    : resource_loader_intercept_(intercept),\n      service_manager_(service_manager),\n      task_runners_(std::move(task_runners)),\n      weak_factory_(this) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  image_manager_ =\n      ImageManager::GetOrCreateImageManager(task_runners_, unref_queue);\n  image_manager_->AddAccessor(this);\n}\n\nImageResourceFetcher::~ImageResourceFetcher() {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  image_manager_->RemoveAccessor(this);\n}\n\nImageFetchID ImageResourceFetcher::FetchImageAsync(\n    const std::string& original_url, const ImageResourceCallback& callback,\n    bool use_texture_backend, bool is_deferred, bool decode_with_priority,\n    bool need_redirect, bool enable_low_quality_image, bool is_promise,\n    bool is_svg) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  std::string trimmed_url = url::TrimUrl(original_url);\n  if (trimmed_url.empty()) {\n    callback(nullptr, false);\n    return kDefaultImageFetchID;\n  }\n\n  auto begin = fml::TimePoint::Now();\n  std::string cache_identifier = trimmed_url.compare(0, 5, \"data:\") == 0\n                                     ? lynx::base::md5(trimmed_url)\n                                     : trimmed_url;\n  // check if image is already cached.\n  if (image_manager_->GetImageResource(\n          trimmed_url, cache_identifier,\n          [callback, begin, trimmed_url,\n           ui_task_runner = task_runners_.GetUITaskRunner()](\n              std::unique_ptr<ImageResource> image_resource, bool hit_cache) {\n            FML_DCHECK(ui_task_runner->RunsTasksOnCurrentThread());\n\n            auto end = fml::TimePoint::Now();\n            FML_DLOG(INFO)\n                << \"ImageResourceFetcher: fetch image and hit cache; url = \"\n                << trimmed_url\n                << \" , cost_time = \" << (end - begin).ToMicroseconds();\n            callback(std::move(image_resource), hit_cache);\n          },\n          use_texture_backend, is_deferred, decode_with_priority,\n          enable_low_quality_image, is_promise, is_svg)) {\n    return kDefaultImageFetchID;\n  }\n\n  return FetchImageAsyncInternal(trimmed_url, cache_identifier, callback,\n                                 use_texture_backend, is_deferred,\n                                 decode_with_priority, need_redirect,\n                                 enable_low_quality_image, is_promise, is_svg);\n}\n\nImageFetchID ImageResourceFetcher::FetchImageAsyncInternal(\n    const std::string& trimmed_url, const std::string& cache_identifier,\n    const ImageResourceCallback& callback, bool use_texture_backend,\n    bool is_deferred, bool decode_with_priority, bool need_redirect,\n    bool enable_low_quality_image, bool is_promise, bool is_svg) {\n  auto begin = fml::TimePoint::Now();\n  ImageFetchID current_fetch_id = GenerateFetchID();\n  auto it = url_loader_map_.find(trimmed_url);\n  if (it == url_loader_map_.end()) {\n    std::shared_ptr<ResourceLoader> loader = GetOrCreateResourceLoader(\n        resource_loader_intercept_, trimmed_url,\n        task_runners_.GetUITaskRunner(), service_manager_);\n    if (!loader) {\n      callback(nullptr, false);\n      return kDefaultImageFetchID;\n    }\n    // task will be executed in ui_task_runner.\n    auto task = [self = weak_factory_.GetWeakPtr(), trimmed_url,\n                 cache_identifier, begin, is_svg, use_texture_backend,\n                 enable_low_quality_image, is_deferred, decode_with_priority,\n                 is_promise](const uint8_t* image, size_t len) {\n      auto end = fml::TimePoint::Now();\n      FML_DLOG(INFO) << \"ImageResourceFetcher: fetch image finish; url = \"\n                     << trimmed_url\n                     << \" , cost_time = \" << (end - begin).ToMicroseconds();\n      if (!self) {\n        return;\n      }\n      bool success = (image != nullptr && len > 0);\n      GrDataPtr data;\n      if (success) {\n        data = GrData::MakeWithCopy(image, len);\n      } else {\n        data = GrData::MakeEmpty();\n      }\n      self->OnDownloadEnd(success, trimmed_url, cache_identifier, data, is_svg,\n                          use_texture_backend, enable_low_quality_image,\n                          is_deferred, decode_with_priority, is_promise);\n    };\n    url_loader_map_.insert({trimmed_url, loader});\n    image_resource_callback_map_.insert(\n        {trimmed_url, {current_fetch_id, callback}});\n    loader->Load(trimmed_url, task, ResourceType::kImage, need_redirect);\n  } else {\n    image_resource_callback_map_.insert(\n        {trimmed_url, {current_fetch_id, callback}});\n  }\n  return current_fetch_id;\n}\n\nstd::unique_ptr<ImageResource> ImageResourceFetcher::FetchPromiseImage(\n    const std::string& original_url, const ImageResourceCallback& callback,\n    bool use_texture_backend, bool need_redirect) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  std::string trimmed_url = url::TrimUrl(original_url);\n  if (trimmed_url.empty()) {\n    callback(nullptr, false);\n    return nullptr;\n  }\n\n  bool use_promise = true;\n  if (!use_texture_backend) {\n    use_promise = false;\n  }\n\n  std::string cache_identifier = trimmed_url.compare(0, 5, \"data:\") == 0\n                                     ? lynx::base::md5(trimmed_url)\n                                     : trimmed_url;\n  auto image_resource =\n      image_manager_->GetImageResourceFromCache(cache_identifier);\n  if (image_resource) {\n    // if image has been cached, there is no need to invoke callback\n    return image_resource;\n  }\n\n  image_resource = image_manager_->CreateImageResourceFromCachedData(\n      trimmed_url, cache_identifier, false, use_texture_backend, use_promise,\n      false, false, false);\n  if (image_resource) {\n    // if image data has been downloaded, there is no need to invoke callback\n    return image_resource;\n  }\n\n  // image data is not prepared and initiate an asynchronous load request\n  FetchImageAsyncInternal(trimmed_url, cache_identifier, callback,\n                          use_texture_backend, false, false, need_redirect,\n                          false, use_promise, false);\n\n  // create a clay::Image without actual data in it, when download finished,\n  // put the download data to the image.\n  auto image = image_manager_->CreateAndCacheImage(\n      trimmed_url, cache_identifier, nullptr, false, use_texture_backend,\n      use_promise, false, false, false);\n  return image->GetAccessor();\n}\n\nvoid ImageResourceFetcher::GetImageResource(\n    const std::string& original_url, const ImageResourceCallback& callback,\n    const uint8_t* source, const int len, bool use_texture_backend,\n    bool is_deferred, bool decode_with_priority,\n    bool enable_low_quality_image) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  std::string trimmed_url = url::TrimUrl(original_url);\n  std::string cache_identifier = trimmed_url.compare(0, 5, \"data:\") == 0\n                                     ? lynx::base::md5(trimmed_url)\n                                     : trimmed_url;\n  image_manager_->GetImageResource(\n      trimmed_url, cache_identifier, callback, source, len, use_texture_backend,\n      is_deferred, decode_with_priority, enable_low_quality_image);\n}\n\n#if defined(ENABLE_SVG)\nstd::unique_ptr<ImageResource>\nImageResourceFetcher::GetImageResourceFromSVGContent(\n    const std::string& source, bool use_texture_backend, bool is_deferred,\n    bool decode_with_priority) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  return image_manager_->GetImageResourceFromSVGContent(\n      source, use_texture_backend, is_deferred, decode_with_priority);\n}\n#endif\n\nvoid ImageResourceFetcher::TryCancelAsyncFetch(const std::string& original_url,\n                                               ImageFetchID fetch_id) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  if (fetch_id == kDefaultImageFetchID) {\n    return;\n  }\n\n  std::string trimmed_url = url::TrimUrl(original_url);\n  if (trimmed_url.empty()) {\n    return;\n  }\n\n  // Remove the ImageResourceCallback from the map.\n  auto range = image_resource_callback_map_.equal_range(trimmed_url);\n  for (auto it = range.first; it != range.second; ++it) {\n    if (it->second.first == fetch_id) {\n      image_resource_callback_map_.erase(it);\n      break;\n    }\n  }\n\n  // Cancel the loading only when the number of ImageResourceCallbacks reaches\n  // zero, because multiple ImageResourceCallbacks may exist for the same url.\n  if (!image_resource_callback_map_.count(trimmed_url)) {\n    auto it = url_loader_map_.find(trimmed_url);\n    if (it != url_loader_map_.end()) {\n      FML_LOG(INFO) << \"Cancel image fetch: \" << fetch_id\n                    << \", url: \" << trimmed_url;\n      it->second->CancelAll();\n      url_loader_map_.erase(it);\n    }\n  }\n}\n\nvoid ImageResourceFetcher::ClearCache() {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n  image_manager_->ClearCache();\n}\n\nvoid ImageResourceFetcher::OnDownloadEnd(\n    bool success, const std::string& trimmed_url,\n    const std::string& cache_identifier, GrDataPtr data, bool is_svg,\n    bool use_texture_backend, bool enable_low_quality_image, bool is_deferred,\n    bool decode_with_priority, bool is_promise) {\n  FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());\n  // remove loader first.\n  auto loader_it = url_loader_map_.find(trimmed_url);\n  if (loader_it != url_loader_map_.end()) {\n    url_loader_map_.erase(loader_it);\n  }\n\n  if (!success) {\n    RunImageResourceCallback(trimmed_url, cache_identifier);\n    return;\n  }\n\n  if (!image_manager_->UpdateCachedImageData(cache_identifier, data)) {\n    // If the image is not cached, create a new image and cache it.\n    image_manager_->CreateAndCacheImage(trimmed_url, cache_identifier, data,\n                                        is_svg, use_texture_backend, is_promise,\n                                        enable_low_quality_image, is_deferred,\n                                        decode_with_priority);\n    RunImageResourceCallback(trimmed_url, cache_identifier);\n  } else {\n    RunImageResourceCallback(trimmed_url, cache_identifier);\n  }\n}\n\nvoid ImageResourceFetcher::RunImageResourceCallback(\n    const std::string& trimmed_url, const std::string& cache_identifier) {\n  auto range = image_resource_callback_map_.equal_range(trimmed_url);\n  for (auto it = range.first; it != range.second; ++it) {\n    it->second.second(\n        image_manager_->GetImageResourceFromCache(cache_identifier), false);\n  }\n  image_resource_callback_map_.erase(trimmed_url);\n}\n\nbool ImageResourceFetcher::SameImage(const std::string& lhs,\n                                     const std::string& rhs) {\n  return url::TrimUrl(lhs) == url::TrimUrl(rhs);\n}\n\nImageFetchID ImageResourceFetcher::GenerateFetchID() {\n  static ImageFetchID fetch_id = kDefaultImageFetchID;\n  ++fetch_id;\n  if (fetch_id == kDefaultImageFetchID) {\n    ++fetch_id;\n  }\n  return fetch_id;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/resource/image_resource_fetcher.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_RESOURCE_IMAGE_RESOURCE_FETCHER_H_\n#define CLAY_UI_RESOURCE_IMAGE_RESOURCE_FETCHER_H_\n\n#include <map>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/common/task_runners.h\"\n#include \"clay/gfx/gpu_object.h\"\n#include \"clay/ui/resource/image_manager.h\"\n\nnamespace clay {\n\nclass Image;\nclass ImageResource;\nclass ResourceLoader;\nclass ResourceLoaderIntercept;\nclass ServiceManager;\n\ntypedef uint64_t ImageFetchID;\nstatic constexpr ImageFetchID kDefaultImageFetchID = 0;\n\nclass ImageResourceFetcher\n    : public fml::RefCountedThreadSafe<ImageResourceFetcher> {\n public:\n  ImageResourceFetcher(std::shared_ptr<ResourceLoaderIntercept> intercept,\n                       clay::TaskRunners task_runners,\n                       fml::RefPtr<GPUUnrefQueue> unref_queue,\n                       std::shared_ptr<ServiceManager> service_manager);\n  ~ImageResourceFetcher();\n\n  ImageFetchID FetchImageAsync(const std::string& original_url,\n                               const ImageResourceCallback& callback,\n                               bool use_texture_backend,\n                               bool is_deferred = false,\n                               bool decode_with_priority = false,\n                               bool need_redirect = true,\n                               bool enable_low_quality_image = false,\n                               bool is_promise = false, bool is_svg = false);\n  void TryCancelAsyncFetch(const std::string& original_url,\n                           ImageFetchID fetch_id);\n\n  // if fetching an image who has not been downloaded, an ImageResource without\n  // image data will be return immediately, and a asynchronous loading task will\n  // be post, then the image data will be put into the ImageResource in UI\n  // thread when finishing the download, else if an image has been downloaded\n  // before, the an ImageResource with image data will be return directly. The\n  // design idea is similar to std::promise.\n  std::unique_ptr<ImageResource> FetchPromiseImage(\n      const std::string& original_url, const ImageResourceCallback& callback,\n      bool use_texture_backend, bool need_redirect = false);\n\n  void GetImageResource(const std::string& original_url,\n                        const ImageResourceCallback& callback,\n                        const uint8_t* source, const int len,\n                        bool use_texture_backend, bool is_deferred,\n                        bool decode_with_priority,\n                        bool enable_low_quality_image);\n  fml::WeakPtr<ImageResourceFetcher> GetWeakPtr() {\n    return weak_factory_.GetWeakPtr();\n  }\n\n#if defined(ENABLE_SVG)\n  std::unique_ptr<ImageResource> GetImageResourceFromSVGContent(\n      const std::string& source, bool use_texture_backend, bool is_deferred,\n      bool decode_with_priority);\n#endif\n\n  void ClearCache();\n\n  // Compare whether two urls point to a same image content. Url may be trimmed\n  // to compare.\n  static bool SameImage(const std::string& lhs, const std::string& rhs);\n\n private:\n  ImageFetchID GenerateFetchID();\n  ImageFetchID FetchImageAsyncInternal(\n      const std::string& trimmed_url, const std::string& cache_identifier,\n      const ImageResourceCallback& callback, bool use_texture_backend,\n      bool is_deferred, bool decode_with_priority, bool need_redirect,\n      bool enable_low_quality_image, bool is_promise, bool is_svg);\n  void OnDownloadEnd(bool success, const std::string& trimmed_url,\n                     const std::string& cache_identifier, GrDataPtr data,\n                     bool is_svg, bool use_texture_backend,\n                     bool enable_low_quality_image = false,\n                     bool is_deferred = false,\n                     bool decode_with_priority = false,\n                     bool is_promise = false);\n  void RunImageResourceCallback(const std::string& trimmed_url,\n                                const std::string& cache_identifier);\n\n  std::shared_ptr<ResourceLoaderIntercept> resource_loader_intercept_;\n  std::shared_ptr<ServiceManager> service_manager_;\n  clay::TaskRunners task_runners_;\n\n  fml::WeakPtrFactory<ImageResourceFetcher> weak_factory_;\n\n  std::shared_ptr<ImageManager> image_manager_;\n\n  std::unordered_map<std::string, std::shared_ptr<ResourceLoader>>\n      url_loader_map_;\n  std::multimap<std::string, std::pair<ImageFetchID, ImageResourceCallback>>\n      image_resource_callback_map_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_RESOURCE_IMAGE_RESOURCE_FETCHER_H_\n"
  },
  {
    "path": "clay/ui/semantics/build.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\nui_semantics_sources = [\n  \"semantics_node.cc\",\n  \"semantics_node.h\",\n  \"semantics_owner.cc\",\n  \"semantics_owner.h\",\n  \"semantics_update_builder.cc\",\n  \"semantics_update_builder.h\",\n  \"semantics_update_node.h\",\n]\n"
  },
  {
    "path": "clay/ui/semantics/semantics_node.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/semantics/semantics_node.h\"\n\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/semantics/semantics_owner.h\"\n\nnamespace clay {\n\nSemanticsNode::SemanticsNode(SemanticsOwner* owner, BaseView* view, int id)\n    : id_(id), owner_view_(view) {\n  Attach(owner);\n}\n\nSemanticsNode::SemanticsNode(BaseView* view, int id)\n    : id_(id), owner_view_(view) {}\n\nvoid SemanticsNode::MarkDirty() {\n  if (dirty_) {\n    return;\n  }\n  dirty_ = true;\n  if (Attached()) {\n    fml::RefPtr<SemanticsNode> node = owner_->nodes_[id_];\n    FML_DCHECK(node);\n    FML_DCHECK(owner_->dirty_nodes_.find(node) == owner_->dirty_nodes_.end());\n    owner_->dirty_nodes_.insert(node);\n  }\n}\n\nSemanticsNode::~SemanticsNode() = default;\n\nvoid SemanticsNode::Attach(SemanticsOwner* owner) {\n  owner_ = owner;\n  FML_DCHECK(owner_ && owner_->nodes_.find(id_) == owner_->nodes_.end());\n\n  owner->nodes_[id_] = fml::RefPtr<SemanticsNode>(this);\n  owner->detached_nodes_.erase(fml::RefPtr<SemanticsNode>(this));\n  if (dirty_) {\n    dirty_ = false;\n    MarkDirty();\n  }\n}\n\nvoid SemanticsNode::Detach() {\n  FML_DCHECK(owner_ && owner_->nodes_.find(id_) != owner_->nodes_.end());\n  fml::RefPtr<SemanticsNode> node = owner_->nodes_[id_];\n  FML_DCHECK(owner_ && owner_->detached_nodes_.find(node) ==\n                           owner_->detached_nodes_.end());\n  owner_->nodes_.erase(id_);\n  owner_->detached_nodes_.insert(node);\n  owner_ = nullptr;\n\n  if (!children_.empty()) {\n    for (auto child : children_) {\n      // The list of children may be stale and may contain nodes that have\n      // been assigned to a different parent.\n      if (child->Parent() == this) {\n        child->Detach();\n      }\n    }\n    children_.clear();\n  }\n}\n\nvoid SemanticsNode::AddToUpdate(SemanticsUpdateBuilder* builder) {\n  FML_DCHECK(dirty_);\n  std::vector<int> children_in_hittest_order;\n  if (!children_.empty()) {\n    auto child_count = children_.size();\n    children_in_hittest_order.resize(child_count);\n\n    // children_ is in paint order, so we invert it to get the hit test\n    // order.\n    for (int i = child_count - 1; i >= 0; --i) {\n      children_in_hittest_order[i] = children_[child_count - i - 1]->id_;\n    }\n  }\n\n  builder->UpdateNode(id_, data_.actions, data_.flags, data_.scroll_children,\n                      data_.semantics_bounds, data_.label, data_.id_selector,\n                      data_.accessibility_elements, data_.transform,\n                      children_in_hittest_order);\n\n  dirty_ = false;\n}\n\nvoid SemanticsNode::RedepthChildren() {\n  for (auto child : children_) {\n    RedepthChild(child.get());\n  }\n}\n\nvoid SemanticsNode::UpdateWith(\n    const std::vector<fml::RefPtr<SemanticsNode>>& new_children,\n    bool need_check_children, bool force_update) {\n  FML_DCHECK(Attached());\n  // First check if semantics data has changed during last update.\n  if (old_data_ != data_ || force_update) {\n    MarkDirty();\n  }\n  if (!need_check_children) {\n    return;\n  }\n\n  for (auto node : children_) {\n    node->dead_ = true;\n  }\n  for (auto node : new_children) {\n    node->dead_ = false;\n  }\n  bool saw_change = false;\n  for (auto child : children_) {\n    if (child->dead_) {\n      saw_change = true;\n      DropChild(child.get());\n      FML_DCHECK(child->Attached());\n      child->Detach();\n    }\n  }\n  for (auto child : new_children) {\n    if (child->Parent()) {\n      child->Parent()->DropChild(child.get());\n    }\n    AdoptChild(child.get());\n    saw_change = true;\n  }\n  if (!saw_change) {\n    FML_DCHECK(children_.size() == new_children.size());\n    // Did the order change?\n    for (size_t i = 0; i < children_.size(); ++i) {\n      if (children_[i]->Id() != new_children[i]->Id()) {\n        saw_change = true;\n        break;\n      }\n    }\n  }\n  children_ = new_children;\n  if (saw_change) {\n    MarkDirty();\n  }\n}\n\nstd::u16string SemanticsNode::GetAccessibilityLabelWithChildren() const {\n  if (data_.label.size() > 0) {\n    return data_.label;\n  }\n  std::u16string label(u\"\");\n  for (auto child : children_) {\n    label += child->GetSemanticsData().label;\n  }\n  return label;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/semantics/semantics_node.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SEMANTICS_SEMANTICS_NODE_H_\n#define CLAY_UI_SEMANTICS_SEMANTICS_NODE_H_\n\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/transform.h\"\n#include \"clay/ui/rendering/abstract_node.h\"\n#include \"clay/ui/semantics/semantics_update_builder.h\"\n\nnamespace clay {\n\nclass SemanticsOwner;\nclass BaseView;\n\nclass SemanticsNode : public AbstractNode,\n                      public fml::RefCountedThreadSafe<SemanticsNode> {\n public:\n  enum class SemanticsAction : int32_t {\n    kTap = 1 << 0,\n    kLongPress = 1 << 1,\n    kScrollLeft = 1 << 2,\n    kScrollRight = 1 << 3,\n    kScrollUp = 1 << 4,\n    kScrollDown = 1 << 5,\n    kShowOnScreen = 1 << 6,\n  };\n\n  enum class SemanticsFlag : int32_t {\n    kHasImplicitScrolling = 1 << 0,\n  };\n\n  SemanticsNode(SemanticsOwner* owner, BaseView* view, int id);\n  SemanticsNode(BaseView* view, int id);\n  ~SemanticsNode() override;\n  struct SemanticsData {\n    SemanticsData() = default;\n\n    int32_t actions;\n    int32_t flags;\n    int32_t scroll_children;\n    std::string id_selector;\n    std::u16string label;\n    FloatRect semantics_bounds;\n    Transform transform;\n    std::vector<std::string> accessibility_elements;\n\n    bool operator==(const SemanticsData& other) {\n      return actions == other.actions && flags == other.flags &&\n             scroll_children == other.scroll_children &&\n             id_selector == other.id_selector && label == other.label &&\n             semantics_bounds == other.semantics_bounds &&\n             transform == other.transform &&\n             accessibility_elements == other.accessibility_elements;\n    }\n\n    bool operator!=(const SemanticsData& other) { return !(*this == other); }\n  };\n\n  void Attach(SemanticsOwner* owner);\n  void Detach();\n  bool Attached() const { return owner_ != nullptr; }\n  bool IsDirty() const { return dirty_; }\n\n  void AddToUpdate(SemanticsUpdateBuilder* builder);\n\n  int Id() const { return id_; }\n\n  SemanticsData& GetSemanticsData() { return data_; }\n\n  SemanticsOwner* Owner() const { return owner_; }\n\n  void UpdateWith(const std::vector<fml::RefPtr<SemanticsNode>>& new_children,\n                  bool need_check_children, bool force_update = false);\n\n  void RedepthChildren() override;\n  void MarkDirty();\n\n  // This should happen before updating data.\n  void TransferCurrentData() { old_data_ = data_; }\n\n  const std::vector<fml::RefPtr<SemanticsNode>>& GetChildren() const {\n    return children_;\n  }\n\n  BaseView* OwnerView() const { return owner_view_; }\n\n  std::u16string GetAccessibilityLabelWithChildren() const;\n\n private:\n  int32_t id_;\n  // Contains the children in inverse hit test order (i.e. paint order).\n  std::vector<fml::RefPtr<SemanticsNode>> children_;\n  SemanticsOwner* owner_ = nullptr;\n  bool dirty_ = false;\n\n  // Help to replace children to check if need MarkDirty.\n  bool dead_ = false;\n\n  BaseView* owner_view_ = nullptr;\n\n  // Basic data that Semantics Node must have.\n  SemanticsData data_;\n  SemanticsData old_data_;\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_SEMANTICS_SEMANTICS_NODE_H_\n"
  },
  {
    "path": "clay/ui/semantics/semantics_owner.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/semantics/semantics_owner.h\"\n\n#include <vector>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nvoid SemanticsOwner::SendSemanticsUpdate() {\n  if (dirty_nodes_.empty()) {\n    return;\n  }\n  std::vector<fml::RefPtr<SemanticsNode>> local_dirty_nodes;\n  // local_dirty_nodes should hava all dirty nodes which are not detached.\n  std::copy_if(dirty_nodes_.begin(), dirty_nodes_.end(),\n               std::back_inserter(local_dirty_nodes),\n               [this](fml::RefPtr<SemanticsNode> node) {\n                 return std::none_of(\n                     detached_nodes_.begin(), detached_nodes_.end(),\n                     [&node](fml::RefPtr<SemanticsNode> detached_node) {\n                       return node == detached_node;\n                     });\n               });\n  dirty_nodes_.clear();\n  detached_nodes_.clear();\n  std::stable_sort(\n      local_dirty_nodes.begin(), local_dirty_nodes.end(),\n      [](fml::RefPtr<SemanticsNode> a, fml::RefPtr<SemanticsNode> b) {\n        return a->Depth() < b->Depth();\n      });\n\n  SemanticsUpdateBuilder builder;\n  for (auto node : local_dirty_nodes) {\n    // All nodes that have been visited will be marked as not dirty.\n    // And parent nodes will be visited prior to children nodes.\n    FML_DCHECK(!node->Parent() ||\n               !static_cast<SemanticsNode*>(node->Parent())->IsDirty());\n    if (node->IsDirty() && node->Attached()) {\n      node->AddToUpdate(&builder);\n    }\n  }\n  if (on_semantics_update_) {\n    on_semantics_update_(builder.GetSemanticsNodeUpdates());\n  }\n}\n\nvoid SemanticsOwner::AddDirtySemanticsForDescendants(BaseView* node) {\n  if (!IsSemanticsEnabled()) {\n    return;\n  }\n  semantics_nodes_to_update_descendants_.insert(node);\n}\nvoid SemanticsOwner::RemoveDirtySemanticsForDescendants(BaseView* node) {\n  if (!IsSemanticsEnabled()) {\n    return;\n  }\n  semantics_nodes_to_update_descendants_.erase(node);\n}\n\nBaseView* SemanticsOwner::GetViewFromId(int id) const {\n  auto it = nodes_.find(id);\n  if (it != nodes_.end()) {\n    return it->second->OwnerView();\n  }\n  return nullptr;\n}\n\nvoid SemanticsOwner::Reset() {\n  if (nodes_.size() != 0) {\n    // Detach root node to detach the whole semantics tree.\n    nodes_[0]->Detach();\n  }\n  FML_DCHECK(nodes_.size() == 0);\n  dirty_nodes_.clear();\n  detached_nodes_.clear();\n  semantics_nodes_to_update_descendants_.clear();\n  need_rebuild_semantics_tree_ = true;\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/semantics/semantics_owner.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SEMANTICS_SEMANTICS_OWNER_H_\n#define CLAY_UI_SEMANTICS_SEMANTICS_OWNER_H_\n\n#include <functional>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"clay/ui/semantics/semantics_node.h\"\n#include \"clay/ui/semantics/semantics_update_node.h\"\n\nnamespace clay {\nclass BaseView;\nclass PageView;\n\nclass SemanticsOwner {\n public:\n  explicit SemanticsOwner(\n      std::function<void(const SemanticsUpdateNodes& update_nodes)>\n          update_callback)\n      : on_semantics_update_(update_callback) {}\n\n  void SendSemanticsUpdate();\n\n  void AddDirtySemanticsForDescendants(BaseView* node);\n  void RemoveDirtySemanticsForDescendants(BaseView* node);\n\n  void SetSemanticsEnabled(bool enabled) {\n    if (enabled != semantics_enabled_) {\n      semantics_enabled_ = enabled;\n    }\n  }\n  bool IsSemanticsEnabled() const { return semantics_enabled_; }\n\n  void SetRebuildSemanticsTree(bool value) {\n    if (semantics_enabled_ && need_rebuild_semantics_tree_ != value) {\n      need_rebuild_semantics_tree_ = value;\n    }\n  }\n  bool NeedRebuildSemanticsTree() const { return need_rebuild_semantics_tree_; }\n\n  // Return whether this value changes.\n  bool SetPageEnableAccessibilityElement(bool enabled) {\n    if (page_enable_accessibility_element_ != enabled) {\n      page_enable_accessibility_element_ = enabled;\n      need_rebuild_semantics_tree_ = true;\n      return true;\n    }\n    return false;\n  }\n\n  bool IsPageEnableAccessibilityElement() const {\n    return page_enable_accessibility_element_;\n  }\n\n  BaseView* GetViewFromId(int id) const;\n\n  void Reset();\n\n private:\n  friend class SemanticsNode;\n  friend class PageView;\n\n  std::unordered_map<int, fml::RefPtr<SemanticsNode>> nodes_;\n  std::unordered_set<fml::RefPtr<SemanticsNode>> dirty_nodes_;\n  std::unordered_set<fml::RefPtr<SemanticsNode>> detached_nodes_;\n\n  std::function<void(SemanticsUpdateNodes)> on_semantics_update_;\n\n  // Set that contains all the nodes that need update semantics of both\n  // themselves and all descendants.\n  std::unordered_set<BaseView*> semantics_nodes_to_update_descendants_;\n  bool semantics_enabled_ = false;\n  bool page_enable_accessibility_element_ = false;\n  bool need_rebuild_semantics_tree_ = true;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SEMANTICS_SEMANTICS_OWNER_H_\n"
  },
  {
    "path": "clay/ui/semantics/semantics_update_builder.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/semantics/semantics_update_builder.h\"\n\n#include <string>\n#include <vector>\n\n#include \"base/include/string/string_utils.h\"\n\nnamespace clay {\n\nstd::vector<std::u16string> ConvertFromUtf8Vector(\n    const std::vector<std::string>& strings) {\n  std::vector<std::u16string> result(strings.size());\n  for (auto i = 0u; i < strings.size(); ++i) {\n    result[i] = lynx::base::U8StringToU16(strings[i]);\n  }\n  return result;\n}\n\nvoid SemanticsUpdateBuilder::UpdateNode(\n    int32_t id, int32_t actions, int32_t flags, int32_t scroll_children,\n    const FloatRect& rect, const std::u16string& label,\n    const std::string& id_selector,\n    const std::vector<std::string>& accessibility_elements,\n    const Transform& transform,\n    const std::vector<int>& children_in_hittest_order) {\n  SemanticsUpdateNode node;\n  node.id = id;\n  node.actions = actions;\n  node.flags = flags;\n  node.scroll_children = scroll_children;\n  node.rect = rect;\n  node.label = label;\n  node.id_selector = lynx::base::U8StringToU16(id_selector);\n  node.accessibility_elements = ConvertFromUtf8Vector(accessibility_elements);\n  node.transform = transform.matrix();\n  node.children_in_hittest_order = children_in_hittest_order;\n\n  nodes_[id] = node;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/semantics/semantics_update_builder.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SEMANTICS_SEMANTICS_UPDATE_BUILDER_H_\n#define CLAY_UI_SEMANTICS_SEMANTICS_UPDATE_BUILDER_H_\n\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/transform.h\"\n#include \"clay/ui/semantics/semantics_update_node.h\"\n\nnamespace clay {\n\nclass SemanticsUpdateBuilder {\n public:\n  SemanticsUpdateBuilder() = default;\n  ~SemanticsUpdateBuilder() = default;\n\n  // TODO(feiyue.1998): Determine more attributes that need update to platform\n  // side.\n  void UpdateNode(int32_t id, int32_t actions, int32_t flags,\n                  int32_t scroll_children, const FloatRect& rect,\n                  const std::u16string& label, const std::string& id_selector,\n                  const std::vector<std::string>& accessibility_elements,\n                  const Transform& transform,\n                  const std::vector<int>& children_in_hittest_order);\n\n  const SemanticsUpdateNodes& GetSemanticsNodeUpdates() const { return nodes_; }\n\n private:\n  SemanticsUpdateNodes nodes_;\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_SEMANTICS_SEMANTICS_UPDATE_BUILDER_H_\n"
  },
  {
    "path": "clay/ui/semantics/semantics_update_node.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SEMANTICS_SEMANTICS_UPDATE_NODE_H_\n#define CLAY_UI_SEMANTICS_SEMANTICS_UPDATE_NODE_H_\n\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"skity/geometry/matrix.hpp\"\n#include \"skity/geometry/rect.hpp\"\n\nnamespace clay {\n\nstruct SemanticsUpdateNode {\n  SemanticsUpdateNode() = default;\n  SemanticsUpdateNode(const SemanticsUpdateNode& other) = default;\n  ~SemanticsUpdateNode() = default;\n\n  int32_t id;\n  int32_t actions;\n  int32_t flags;\n  int32_t scroll_children;\n  std::u16string label;\n  std::u16string id_selector;\n  std::vector<std::u16string> accessibility_elements;\n  skity::Rect rect =\n      skity::Rect::MakeEmpty();  // Local space, relative to parent.\n  skity::Matrix transform;       // Identity\n  std::vector<int> children_in_hittest_order;\n};\n\n// Contains semantic nodes that need to be updated.\n//\n// The keys in the map are stable node IDd, and the values contain\n// semantic information for the node corresponding to the ID.\nusing SemanticsUpdateNodes = std::unordered_map<int, SemanticsUpdateNode>;\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SEMANTICS_SEMANTICS_UPDATE_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/base_text_shadow_node.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/base_text_shadow_node.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <codecvt>\n#include <cstdint>\n#include <limits>\n#include <memory>\n#include <string_view>\n#include <utility>\n\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/keywords.h\"\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/component/text/text_paragraph_builder.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/resource/font_collection.h\"\n#include \"clay/ui/shadow/inline_text_shadow_node.h\"\n#include \"clay/ui/shadow/inline_view_shadow_node.h\"\n#include \"clay/ui/shadow/measure_utils.h\"\n#include \"clay/ui/shadow/raw_text_shadow_node.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n#include \"clay/ui/shadow/vertical_align_style.h\"\n\nnamespace clay {\nnamespace utils = attribute_utils;\nnamespace {\nstatic constexpr float kDefaultFontSizeInDip = 14.f;\n}\n\nBaseTextShadowNode::BaseTextShadowNode(ShadowNodeOwner* owner, std::string tag,\n                                       int id)\n    : ShadowNode(owner, tag, id), weak_factory_(this) {}\n\nvoid BaseTextShadowNode::AddChild(ShadowNode* node) {\n  AddChild(node, ChildCount());\n}\n\nvoid BaseTextShadowNode::AddChild(ShadowNode* child, int index) {\n  ShadowNode::AddChild(child, index);\n  MarkNeedsUpdate(TextUpdateFlag::kUpdateFlagChildren);\n}\n\nvoid BaseTextShadowNode::RemoveChild(ShadowNode* child) {\n  ShadowNode::RemoveChild(child);\n  MarkNeedsUpdate(TextUpdateFlag::kUpdateFlagChildren);\n}\n\nvoid BaseTextShadowNode::OnLayout(float width, TextMeasureMode width_mode,\n                                  float height, TextMeasureMode height_mode,\n                                  const std::array<float, 4>& paddings,\n                                  const std::array<float, 4>& borders) {\n  ShadowNode::OnLayout(width, width_mode, height, height_mode, paddings,\n                       borders);\n  for (auto child : children_) {\n    child->OnLayout(width, width_mode, height, height_mode, paddings, borders);\n  }\n}\n\nvoid BaseTextShadowNode::SetAttribute(const char* attr_c,\n                                      const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  SetAttribute(kw, attr_c, value);\n}\n\nvoid BaseTextShadowNode::SetAttribute(KeywordID kw, const char* attr_c,\n                                      const clay::Value& value) {\n  switch (kw) {\n    case KeywordID::kFontSize:\n      SetFontSize(utils::GetDouble(\n          value, kDefaultFontSizeInDip * Logical2ClayPixelRatio()));\n      break;\n    case KeywordID::kColor:\n      ResetTextColorAndGradient();\n      if (value.IsUint()) {\n        SetTextColor(Color(utils::GetUint(value, 0xff000000)));\n      } else if (value.IsArray()) {\n        const auto& array = utils::GetArray(value);\n        if (array.size() == 0) {\n          return;\n        }\n        const auto& type =\n            static_cast<ClayBackgroundImageType>(utils::GetUint(array[0]));\n        if (type == ClayBackgroundImageType::kLinearGradient &&\n            array.size() > 1) {\n          const auto& linear_array = utils::GetArray(array[1]);\n          std::optional<Gradient> g = Gradient::CreateLinear(linear_array);\n          if (g.has_value()) {\n            SetTextGradient(g.value());\n          }\n        }\n      }\n      break;\n    case KeywordID::kTextOverflow:\n      SetTextOverflow(static_cast<TextOverflow>(utils::GetInt(value)));\n      break;\n    case KeywordID::kFontWeight:\n      SetFontWeight(static_cast<FontWeight>(utils::GetInt(value)));\n      break;\n    case KeywordID::kFontStyle:\n      SetFontStyle(static_cast<FontStyle>(utils::GetInt(value)));\n      break;\n    case KeywordID::kLineHeight: {\n      double line_height = 0.f;\n      if (utils::TryGetNum(value, line_height)) {\n        SetLineHeight(line_height);\n      }\n    } break;\n    case KeywordID::kLineSpacing:\n      SetLineSpacing(utils::GetDouble(value));\n      break;\n    case KeywordID::kLetterSpacing:\n      SetLetterSpacing(utils::GetDouble(value));\n      break;\n    case KeywordID::kFontFamily:\n      SetFontFamily(utils::GetCString(value));\n      break;\n    case KeywordID::kTextAlign:\n      SetTextAlign(static_cast<TextAlignment>(utils::GetInt(value)));\n      break;\n    case KeywordID::kTextDecoration: {\n      const auto& decoration_array = utils::GetArray(value);\n      if (decoration_array.size() != 3) {\n        return;\n      }\n      auto decoration_type = utils::GetInt(decoration_array[0]);\n      auto decoration_style = utils::GetInt(decoration_array[1]);\n      auto decoration_color = utils::GetInt(decoration_array[2]);\n      auto decoration = GetTextDecoration().value_or(TextDecoration());\n      int decoration_line = 0;\n      if (decoration_type &\n          static_cast<int>(ClayTextDecorationType::kUnderLine)) {\n        decoration_line |= static_cast<int>(kTextDecorationLineUnderline);\n      }\n      if (decoration_type &\n          static_cast<int>(ClayTextDecorationType::kLineThrough)) {\n        decoration_line |= static_cast<int>(kTextDecorationLineLineThrough);\n      }\n      decoration.style = static_cast<TextDecorationStyle>(decoration_style);\n      decoration.line = decoration_line;\n      decoration.color = decoration_color;\n      SetTextDecoration(decoration);\n    } break;\n    case KeywordID::kDirection: {\n      double text_direction = 0.0;\n      if (attribute_utils::TryGetNum(value, text_direction)) {\n        SetTextDirection(static_cast<TextDirection>(text_direction));\n      }\n    } break;\n    case KeywordID::kTextShadow: {\n      const auto& array = utils::GetArray(value);\n      std::vector<Shadow> shadows(array.size());\n      for (size_t i = 0; i < array.size(); i++) {\n        const auto& arr = utils::GetArray(array[i]);\n        shadows[i].offset_x = utils::GetDouble(arr[0]);\n        shadows[i].offset_y = utils::GetDouble(arr[1]);\n        shadows[i].blur_radius = utils::GetDouble(arr[2]);\n        shadows[i].spread_radius = utils::GetDouble(arr[3]);\n        auto option = static_cast<ClayShadowOption>(utils::GetInt(arr[4]));\n        shadows[i].inset = (option == ClayShadowOption::kInset);\n        shadows[i].color = Color(utils::GetUint(arr[5]));\n      }\n      SetTextShadows(std::move(shadows));\n    } break;\n    case KeywordID::kTextStrokeColor: {\n      auto color = Color(utils::GetUint(value, 0xff000000));\n      SetTextStrokeColor(color);\n    } break;\n    case KeywordID::kTextStrokeWidth: {\n      auto width = utils::GetDouble(value);\n      SetTextStrokeWidth(width);\n    } break;\n    case KeywordID::kEnableFontScaling:\n      break;\n    case KeywordID::kTextMaxline: {\n      int max_lines = 0;\n      if (lynx::base::StringToInt(utils::GetCString(value), &max_lines)) {\n        SetTextMaxLine(static_cast<uint32_t>(max_lines) > 0\n                           ? static_cast<uint32_t>(max_lines)\n                           : std::numeric_limits<uint32_t>::max());\n      } else {\n        SetTextMaxLine(std::numeric_limits<uint32_t>::max());\n      }\n    } break;\n    case KeywordID::kWordBreak:\n      SetWordBreak(static_cast<WordBreak>(utils::GetInt(value)));\n      break;\n    case KeywordID::kWhiteSpace:\n      SetWhiteSpaceType(static_cast<WhiteSpace>(utils::GetInt(value)));\n      break;\n    case KeywordID::kTextIndent: {\n      const auto& array = utils::GetArray(value);\n      if (array.size() < 2) {\n        return;\n      }\n      text_indent_use_percent_ = utils::GetInt(array[1]);\n      SetTextIndent(static_cast<double>(utils::GetDouble(array[0])));\n    } break;\n    case KeywordID::kTextMaxlength:\n      max_length_ = utils::GetInt(value);\n      break;\n    case KeywordID::kXAutoFontSize: {\n      const auto& array = utils::GetArray(value);\n      FML_DCHECK(array.size() == 4);\n      if (array.size() != 4) {\n        return;\n      }\n      enable_auto_font_size_ = utils::GetBool(array[0]);\n      auto_font_size_min_size_ = utils::GetDouble(array[1]);\n      auto_font_size_max_size_ = utils::GetDouble(array[2]);\n      auto_font_size_step_granularity_ = utils::GetDouble(array[3]);\n    } break;\n    case KeywordID::kXAutoFontSizePresetSizes: {\n      const auto& array = utils::GetArray(value);\n      for (size_t i = 0; i < array.size(); i++) {\n        auto_font_size_preset_sizes_.push_back(utils::GetDouble(array[i]));\n      }\n      std::sort(auto_font_size_preset_sizes_.begin(),\n                auto_font_size_preset_sizes_.end());\n    } break;\n    case clay::KeywordID::kTextSingleLineVerticalAlign: {\n      SetTextSingleLineVerticalAlign(value);\n    } break;\n    default:\n      if (attr_c) {\n        auto kw = GetKeywordID(attr_c);\n        if (kw == KeywordID::kText) {\n          CreateRawTextNodeIfNeed(attribute_utils::GetCString(value));\n        }\n        ShadowNode::SetAttribute(attr_c, value);\n      }\n      break;\n  }\n}\n\nvoid BaseTextShadowNode::CreateRawTextNodeIfNeed(std::string text) {\n  if (IsTextShadowNode() || IsInlineTextShadowNode()) {\n    for (auto child : GetChildren()) {\n      if (child->IsRawTextShadowNode()) {\n        if (child->id() > 0) {\n          return;\n        } else {\n          static_cast<RawTextShadowNode*>(child)->SetText(text);\n          return;\n        }\n      }\n    }\n    auto raw_text = new RawTextShadowNode(owner_, \"raw-text\", -1);\n    raw_text->SetText(text);\n    MarkNeedsUpdate(TextUpdateFlag::kUpdateFlagChildren);\n    AddChild(raw_text);\n  }\n}\n\nvoid BaseTextShadowNode::SetFontSize(float font_size) {\n  auto context = owner_->GetViewContext();\n  double device_pixel_ratio = 1.0;\n  if (context) {\n    device_pixel_ratio = owner_->GetViewContext()\n                             ->GetPageView()\n                             ->GetViewportMetrics()\n                             .device_pixel_ratio;\n  }\n  font_size = std::roundf(font_size * device_pixel_ratio) / device_pixel_ratio;\n#if OS_MAC || OS_WIN\n  font_size = std::roundf(font_size);\n#endif\n  EnsureDefaultStyle();\n  if (text_style_->font_size != font_size) {\n    text_style_->font_size = font_size;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetLineHeight(float line_height) {\n#if OS_MAC || OS_WIN\n  line_height = std::roundf(line_height);\n#endif\n  EnsureDefaultStyle();\n#ifndef CLAY_ENABLE_TTTEXT\n  if (line_height_.has_value() && line_height == *line_height_) {\n    return;\n  }\n  if (MeasureUtils::isUndefined(line_height)) {\n    line_height_.reset();\n  } else {\n    line_height_ = line_height;\n  }\n#else\n  if (text_style_->line_height != line_height) {\n    text_style_->line_height = line_height;\n  }\n#endif\n\n  MarkDirty();\n}\n\nvoid BaseTextShadowNode::SetLineSpacing(float line_spacing) {\n  EnsureDefaultStyle();\n  if (text_style_->line_spacing != line_spacing) {\n    text_style_->line_spacing = line_spacing;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetLetterSpacing(float letter_spacing) {\n  EnsureDefaultStyle();\n  if (text_style_->letter_spacing != letter_spacing) {\n    text_style_->letter_spacing = letter_spacing;\n    MarkDirty();\n  }\n}\nvoid BaseTextShadowNode::SetTextAlign(TextAlignment text_align) {\n  EnsureDefaultStyle();\n  if (text_style_->text_align != text_align) {\n    text_style_->text_align = text_align;\n    MarkDirty();\n  }\n}\nvoid BaseTextShadowNode::SetFontWeight(FontWeight font_weight) {\n  EnsureDefaultStyle();\n  if (text_style_->font_weight != font_weight) {\n    text_style_->font_weight = font_weight;\n    MarkDirty();\n  }\n}\nvoid BaseTextShadowNode::SetFontStyle(FontStyle font_style) {\n  EnsureDefaultStyle();\n  if (text_style_->font_style != font_style) {\n    text_style_->font_style = font_style;\n    MarkDirty();\n  }\n}\nvoid BaseTextShadowNode::SetTextColor(const Color& text_color) {\n  EnsureDefaultStyle();\n  if (text_style_->text_color != text_color) {\n    text_style_->text_color = text_color;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetTextBackgroundColor(const Color& color) {\n  EnsureDefaultStyle();\n  if (text_style_->background_color != color) {\n    text_style_->background_color = color;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetFontFamily(const std::string& font_family) {\n  EnsureDefaultStyle();\n  auto font_collection = Isolate::Instance().GetFontCollection();\n  std::string_view str_view = font_family;\n  std::vector<std::string> font_families;\n  lynx::base::SplitString(str_view, ',', font_families);\n  for (auto& new_font_family : font_families) {\n    if (new_font_family.length() > 1) {\n      new_font_family = lynx::base::TrimString(\n          new_font_family, \" \", lynx::base::TrimPositions::TRIM_ALL);\n      if (new_font_family.length() > 1) {\n        new_font_family = lynx::base::TrimString(\n            new_font_family, \"\\'\", lynx::base::TrimPositions::TRIM_ALL);\n      }\n      if (new_font_family.length() > 1) {\n        new_font_family = lynx::base::TrimString(\n            new_font_family, \"\\\"\", lynx::base::TrimPositions::TRIM_ALL);\n      }\n    }\n    if (font_collection->HasFontResourceLoading(new_font_family)) {\n      RelayoutWhenSetFontFamily(new_font_family);\n    } else if (font_collection->HasFontResource(new_font_family)) {\n      text_style_->font_family = new_font_family;\n      break;\n    } else if (font_collection->IfSystemFontFamily(new_font_family)) {\n      text_style_->font_family = new_font_family;\n      break;\n    }\n  }\n  MarkDirty();\n}\n\nvoid BaseTextShadowNode::SetTextDecoration(\n    const TextDecoration& text_decoration) {\n  EnsureDefaultStyle();\n  if (text_style_->text_decoration != text_decoration) {\n    text_style_->text_decoration = text_decoration;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetTextDirection(TextDirection text_direction) {\n  EnsureDefaultStyle();\n  if (text_direction == TextDirection::kNormal) {\n    text_direction = TextDirection::kLtr;\n  }\n  if (text_style_->text_direction != text_direction) {\n    text_style_->text_direction = text_direction;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::AppendTextShadow(Shadow&& text_shadow) {\n  if (!text_style_) {\n    text_style_ = std::make_optional<TextStyle>();\n  }\n  if (!text_style_->text_shadows) {\n    text_style_->text_shadows = std::vector<Shadow>();\n  }\n\n  text_style_->text_shadows->emplace_back(std::move(text_shadow));\n  MarkDirty();\n}\n\nvoid BaseTextShadowNode::SetTextShadows(std::vector<Shadow>&& text_shadows) {\n  EnsureDefaultStyle();\n  if (text_style_->text_shadows != text_shadows) {\n    text_style_->text_shadows = std::move(text_shadows);\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetTextStrokeColor(Color color) {\n#ifndef CLAY_ENABLE_TTTEXT\n  EnsureDefaultStyle();\n  if (stroke_color_ != color) {\n    stroke_color_ = color;\n    MarkDirty();\n  }\n  text_style_->foreground_id = id_;\n#else\n  EnsureDefaultStyle();\n  if (text_style_->stroke_color != color) {\n    text_style_->stroke_color = color;\n    MarkDirty();\n  }\n#endif\n}\nvoid BaseTextShadowNode::SetTextStrokeWidth(double width) {\n#ifndef CLAY_ENABLE_TTTEXT\n  EnsureDefaultStyle();\n  if (stroke_width_ != width) {\n    stroke_width_ = width;\n    MarkDirty();\n  }\n  text_style_->foreground_id = id_;\n#else\n  EnsureDefaultStyle();\n  if (text_style_->stroke_width != width) {\n    text_style_->stroke_width = width;\n    MarkDirty();\n  }\n#endif\n}\n\nvoid BaseTextShadowNode::SetTextGradient(const Gradient& gradient) {\n  EnsureDefaultStyle();\n  if (text_style_->text_gradient != gradient) {\n    text_style_->text_gradient = gradient;\n    text_style_->foreground_id = id_;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetTextMaxLine(uint32_t max_lines) {\n  EnsureDefaultStyle();\n  if (text_style_->max_lines != max_lines) {\n    text_style_->max_lines = max_lines;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetTextOverflow(TextOverflow overflow) {\n  EnsureDefaultStyle();\n  if (text_style_->overflow != overflow) {\n    text_style_->overflow = overflow;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetTextEllipsis(std::u16string ellipsis) {\n  EnsureDefaultStyle();\n  if (text_style_->ellipsis != ellipsis) {\n    text_style_->ellipsis = std::move(ellipsis);\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetWordBreak(WordBreak break_type) {\n  EnsureDefaultStyle();\n  if (text_style_->word_break != break_type) {\n    text_style_->word_break = break_type;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetWhiteSpaceType(WhiteSpace white_space) {\n  EnsureDefaultStyle();\n  if (text_style_->white_space != white_space) {\n    text_style_->white_space = white_space;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetTextIndent(double text_indent) {\n  EnsureDefaultStyle();\n  if (text_indent_ != text_indent) {\n    text_indent_ = text_indent;\n    MarkDirty();\n  }\n}\n\nvoid BaseTextShadowNode::SetTextSingleLineVerticalAlign(\n    const clay::Value& value) {\n  EnsureDefaultStyle();\n  auto type = attribute_utils::GetCString(value);\n  if (type == \"bottom\") {\n    text_style_->align_type = VerticalAlignType::kVerticalAlignBottom;\n  } else if (type == \"center\") {\n    text_style_->align_type = VerticalAlignType::kVerticalAlignCenter;\n  } else if (type == \"top\") {\n    text_style_->align_type = VerticalAlignType::kVerticalAlignTop;\n  }\n  text_style_->enable_text_bounds = true;\n  MarkDirty();\n}\n\nvoid BaseTextShadowNode::PreLayout(PreLayoutContext* context) {\n  auto* context_text = static_cast<PreLayoutContextText*>(context);\n  for (ShadowNode* child : children_) {\n    child->PreLayout(context_text);\n  }\n}\n\nvoid BaseTextShadowNode::TextLayout(LayoutContext* context) {\n  TRACE_EVENT(\"clay\", \"BaseTextShadowNode::TextLayout\");\n  TextParagraphBuilder* builder =\n      static_cast<LayoutContextText*>(context)->builder();\n\n  EnsureDefaultStyle();\n  builder->PushStyle(text_style_.value());\n  ProcessChildLayout(context);\n  if (text_style_) {\n    builder->Pop();\n  }\n}\n\nvoid BaseTextShadowNode::ProcessChildLayout(LayoutContext* context) {\n  for (ShadowNode* child : children_) {\n    if (child->IsInlineTextShadowNode() || child->IsInlineImageShadowNode()) {\n      child->UpdateLayoutStylesFromLynx();\n    }\n    if (child->MarginLeft() > 0.f || child->PaddingLeft() > 0.f) {\n      static_cast<LayoutContextText*>(context)->AddFakePlaceholder(\n          child->MarginLeft() + child->PaddingLeft());\n    }\n\n    child->TextLayout(context);\n\n    if (child->MarginRight() > 0.f || child->PaddingRight() > 0.f) {\n      static_cast<LayoutContextText*>(context)->AddFakePlaceholder(\n          child->MarginRight() + child->PaddingRight());\n    }\n  }\n}\n\nvoid BaseTextShadowNode::RelayoutWhenSetFontFamily(\n    const std::string& font_family) {\n  auto font_collection = Isolate::Instance().GetFontCollection();\n\n  if (font_collection) {\n    font_collection->RegisterCallback(\n        font_family, [self = weak_factory_.GetWeakPtr(), font_family]() {\n          if (!self) {\n            return;\n          }\n          self->text_style_->font_family = font_family;\n          self->MarkDirty();\n        });\n  }\n}\n\nvoid BaseTextShadowNode::MeasureInlineView(\n    const MeasureConstraint& constraint) {\n  for (auto* child : children_) {\n    if (child->IsInlineViewShadowNode()) {\n      auto result =\n          static_cast<InlineViewShadowNode*>(child)->MeasureNativeNode(\n              constraint);\n      child->SetWidth(result.width);\n      child->SetHeight(result.height);\n    } else if (child->IsBaseTextShadowNode()) {\n      static_cast<BaseTextShadowNode*>(child)->MeasureInlineView(constraint);\n    }\n  }\n}\n\nvoid BaseTextShadowNode::AlignNativeNode(txt::Paragraph* paragraph) {\n  for (auto* child : children_) {\n    if (child->IsInlineViewShadowNode()) {\n      auto* inline_view_shadow_node = static_cast<InlineViewShadowNode*>(child);\n      auto text_box =\n          paragraph->GetRectsForRange(inline_view_shadow_node->StartGlyph(),\n                                      inline_view_shadow_node->EndGlyph(),\n                                      txt::Paragraph::RectHeightStyle::kTight,\n                                      txt::Paragraph::RectWidthStyle::kTight);\n      if (child->GetEndIndex() > 0 && text_box.size() > 0) {\n        // When sets text-overflow: ellipsis, when the view and ellipsis\n        // are adjacent, the textbox will return two values ​​(a box of\n        // the image and a box of ellipsis, which may be a bug of skia).\n        // Here we use text-direction to avoid this problem.\n        if (text_box.back().direction == txt::TextDirection::rtl) {\n          if (width_mode_ != TextMeasureMode::kDefinite) {\n            inline_view_shadow_node->AlignNativeNode(\n                text_box.back().rect.Top(),\n                text_box.back().rect.Left() -\n                    std::max(paragraph->GetMaxWidth() -\n                                 paragraph->GetMaxIntrinsicWidth(),\n                             0.0));\n          } else {\n            inline_view_shadow_node->AlignNativeNode(\n                text_box.back().rect.Top(), text_box.back().rect.Left());\n          }\n        } else {\n          inline_view_shadow_node->AlignNativeNode(\n              text_box.front().rect.Top(), text_box.front().rect.Left());\n        }\n      } else {\n        inline_view_shadow_node->SetEndIndex(0);\n      }\n    } else {\n      static_cast<BaseTextShadowNode*>(child)->AlignNativeNode(paragraph);\n    }\n  }\n}\n\nvoid BaseTextShadowNode::CollectMaxLineHeight(float& line_height,\n                                              float& font_size) {\n  if (line_height_.has_value() && *line_height_ > line_height) {\n    line_height = *line_height_;\n  }\n  if (text_style_.has_value() && text_style_->font_size.has_value() &&\n      text_style_->font_size.value() > font_size) {\n    font_size = text_style_->font_size.value();\n  }\n\n  for (auto* child : children_) {\n    if (child->IsBaseTextShadowNode()) {\n      static_cast<BaseTextShadowNode*>(child)->CollectMaxLineHeight(line_height,\n                                                                    font_size);\n    }\n  }\n}\n\nvoid BaseTextShadowNode::ResetTextColorAndGradient() {\n  EnsureDefaultStyle();\n  if (text_style_->text_color.has_value()) {\n    text_style_->text_color = std::nullopt;\n  }\n  if (text_style_->text_gradient.has_value()) {\n    text_style_->text_gradient = std::nullopt;\n  }\n}\n\nstd::u16string BaseTextShadowNode::GetRawText() {\n  if (GetChildren().empty()) {\n    return std::u16string();\n  } else {\n    auto result = std::u16string();\n    for (auto* child : children_) {\n      if (child->IsRawTextShadowNode()) {\n        result += static_cast<RawTextShadowNode*>(child)->Text();\n      } else if (child->IsInlineTextShadowNode()) {\n        result += static_cast<InlineTextShadowNode*>(child)->GetRawText();\n      }\n    }\n    return result;\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/base_text_shadow_node.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_BASE_TEXT_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_BASE_TEXT_SHADOW_NODE_H_\n\n#include <cstdint>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/keywords.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n#include \"clay/ui/shadow/vertical_align_style.h\"\n\nnamespace clay {\n\nclass BaseTextShadowNode : public ShadowNode {\n public:\n  BaseTextShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n\n  void OnLayout(float width, TextMeasureMode width_mode, float height,\n                TextMeasureMode height_mode,\n                const std::array<float, 4>& paddings,\n                const std::array<float, 4>& borders) override;\n\n  void AddChild(ShadowNode* node) override;\n  void AddChild(ShadowNode* child, int index) override;\n\n  void RemoveChild(ShadowNode* child) override;\n\n  bool IsBaseTextShadowNode() override { return true; }\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n\n  void SetEnableFontScaling(bool enabled);\n  void SetFontSize(float font_size);\n  void SetLineHeight(float line_height);\n  // Not supported by the underlining layout engine.\n  void SetLineSpacing(float line_spacing);\n  void SetLetterSpacing(float letter_spacing);\n  void SetTextAlign(TextAlignment text_align);\n  void SetFontWeight(FontWeight font_weight);\n  void SetFontStyle(FontStyle font_style);\n  void SetTextColor(const Color& text_color);\n  void SetTextBackgroundColor(const Color& color);\n  void SetFontFamily(const std::string& font_family);\n  std::optional<TextDecoration> GetTextDecoration() const {\n    return text_style_ ? std::nullopt : text_style_->text_decoration;\n  }\n  void SetTextDirection(TextDirection text_direction);\n  void SetTextDecoration(const TextDecoration& text_decoration);\n  void AppendTextShadow(Shadow&& text_shadow);\n  void SetTextShadows(std::vector<Shadow>&& text_shadows);\n  void SetTextStrokeColor(Color color);\n  void SetTextStrokeWidth(double width);\n  void SetTextGradient(const Gradient& gradient);\n  void SetTextMaxLine(uint32_t max_lines);\n  void SetTextOverflow(TextOverflow overflow);\n  void SetTextEllipsis(std::u16string ellipsis);\n  void SetWordBreak(WordBreak break_type);\n  void SetTextSingleLineVerticalAlign(const clay::Value& value);\n\n  void RelayoutWhenSetFontFamily(const std::string& font_family);\n\n  void TextLayout(LayoutContext* context) override;\n  void PreLayout(PreLayoutContext* context) override;\n  void ProcessChildLayout(LayoutContext* context);\n\n  void MeasureInlineView(const MeasureConstraint& constraint);\n\n  void AlignNativeNode(txt::Paragraph* paragraph);\n\n  // Used for calculate skia paragraph line_height multiplier.\n  // Retrieve the maximum line height and font size.\n  void CollectMaxLineHeight(float& line_height, float& font_size);\n\n  void SetBaselineOffset(double baseline_offset) override {\n    EnsureDefaultStyle();\n    text_style_->baseline_shift = -baseline_offset;\n  }\n\n  void SetWhiteSpaceType(WhiteSpace white_space);\n  void SetTextIndent(double indent);\n\n  std::u16string GetRawText();\n  // The absolute line height.\n  std::optional<float> line_height_;\n  std::optional<uint32_t> max_length_;\n\n  double stroke_width_ = 0.0;\n  std::optional<Color> stroke_color_;\n\n  bool enable_auto_font_size_ = false;\n  double auto_font_size_max_size_ = 0.;\n  double auto_font_size_min_size_ = 0.;\n  double auto_font_size_step_granularity_ = 1.;\n  std::vector<double> auto_font_size_preset_sizes_;\n  bool text_indent_use_percent_ = false;\n  float text_indent_ = 0.f;\n  TextMeasureMode width_mode_ = TextMeasureMode::kDefinite;\n  void SetAttribute(KeywordID attr, const char* attr_c,\n                    const clay::Value&) override;\n  void CreateRawTextNodeIfNeed(std::string text);\n\n protected:\n private:\n  void ResetTextColorAndGradient();\n\n  fml::WeakPtrFactory<BaseTextShadowNode> weak_factory_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_BASE_TEXT_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\nimport(\"../../common/config.gni\")\n\nui_shadow_sources = [\n  \"base_text_shadow_node.cc\",\n  \"base_text_shadow_node.h\",\n  \"bundle.h\",\n  \"icu_substitute.h\",\n  \"image_shadow_node.cc\",\n  \"image_shadow_node.h\",\n  \"inline_image_shadow_node.cc\",\n  \"inline_image_shadow_node.h\",\n  \"inline_text_shadow_node.cc\",\n  \"inline_text_shadow_node.h\",\n  \"inline_truncation_shadow_node.cc\",\n  \"inline_truncation_shadow_node.h\",\n  \"inline_view_shadow_node.cc\",\n  \"inline_view_shadow_node.h\",\n  \"inner_inline_text_shadow_node.cc\",\n  \"inner_inline_text_shadow_node.h\",\n  \"inner_text_shadow_node.cc\",\n  \"inner_text_shadow_node.h\",\n  \"measure_utils.h\",\n  \"native_view_shadow_node.cc\",\n  \"native_view_shadow_node.h\",\n  \"raw_text_shadow_node.cc\",\n  \"raw_text_shadow_node.h\",\n  \"shadow_layout_context.cc\",\n  \"shadow_layout_context.h\",\n  \"shadow_node.cc\",\n  \"shadow_node.h\",\n  \"shadow_node_owner.cc\",\n  \"shadow_node_owner.h\",\n  \"text_render.cc\",\n  \"text_render.h\",\n  \"text_shadow_node.cc\",\n  \"text_shadow_node.h\",\n  \"text_update_bundle.cc\",\n  \"text_update_bundle.h\",\n  \"vertical_align_style.h\",\n]\n\nif (enable_skity && is_ios) {\n  ui_shadow_sources += [ \"icu_substitute.cc\" ]\n} else {\n  ui_shadow_sources += [ \"icu_no_substitute.cc\" ]\n}\n\nui_extended_shadow_sources = [\n  \"editable_ng_shadow_node.cc\",\n  \"editable_ng_shadow_node.h\",\n  \"editable_shadow_node.cc\",\n  \"editable_shadow_node.h\",\n]\n"
  },
  {
    "path": "clay/ui/shadow/bundle.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_BUNDLE_H_\n#define CLAY_UI_SHADOW_BUNDLE_H_\n\nnamespace clay {\n\nclass BaseView;\n\nclass Bundle {\n public:\n  Bundle() = default;\n  virtual ~Bundle() = default;\n  virtual void UpdateExtraData(BaseView* view) = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_BUNDLE_H_\n"
  },
  {
    "path": "clay/ui/shadow/editable_ng_shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/editable_ng_shadow_node.h\"\n\n#include <algorithm>\n#include <limits>\n#include <string>\n\n#include \"clay/fml/logging.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/component/editable/textarea_ng_view.h\"\n\nnamespace clay {\n\nnamespace {\n\nconstexpr float kDefaultLineHeightFactor = 1.2f;\n\n}  // namespace\n\nEditableNGShadowNode::EditableNGShadowNode(ShadowNodeOwner* owner,\n                                           std::string tag, int id)\n    : EditableShadowNode(owner, tag, id) {}\n\nvoid EditableNGShadowNode::Measure(const MeasureConstraint& constraint,\n                                   MeasureResult& result) {\n  if (constraint.height_mode == MeasureMode::kDefinite) {\n    result.height = *constraint.height;\n  } else {\n    FML_DCHECK(owner_->GetUITaskRunner()->RunsTasksOnCurrentThread());\n    auto editable_view =\n        static_cast<TextAreaNGView*>(owner_->FindViewByViewId(id()));\n    editable_view->Measure(constraint, result);\n    return;\n  }\n  if (constraint.width_mode == MeasureMode::kIndefinite ||\n      !constraint.width.has_value()) {\n    result.width = std::numeric_limits<float>::infinity();\n  } else {\n    result.width = *constraint.width;\n  }\n\n  if (constraint.width_mode == MeasureMode::kAtMost) {\n    result.width = std::min(result.width, constraint.width.value_or(0));\n  }\n  if (constraint.height_mode == MeasureMode::kAtMost) {\n    result.height = std::min(result.height, constraint.height.value_or(0));\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/editable_ng_shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_EDITABLE_NG_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_EDITABLE_NG_SHADOW_NODE_H_\n\n#include <string>\n\n#include \"clay/ui/shadow/editable_shadow_node.h\"\n\nnamespace clay {\n\nclass EditableNGShadowNode : public EditableShadowNode {\n public:\n  EditableNGShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  ~EditableNGShadowNode() override = default;\n\n  void Measure(const MeasureConstraint& constraint,\n               MeasureResult& result) override;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_EDITABLE_NG_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/editable_shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/editable_shadow_node.h\"\n\n#include <algorithm>\n#include <string>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/component/editable/textarea_view.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\nnamespace {\n\nconstexpr float kDefaultLineHeightFactor = 1.2f;\n\n}  // namespace\n\nEditableShadowNode::EditableShadowNode(ShadowNodeOwner* owner, std::string tag,\n                                       int id)\n    : ShadowNode(owner, tag, id) {\n  font_size_ =\n      owner->GetViewContext()->GetPageView()->ConvertFrom<kPixelTypeLogical>(\n          kDefaultFontSizeInDip);\n}\n\nvoid EditableShadowNode::SetAttribute(const char* attr_c,\n                                      const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kFontSize) {\n    double font_size = 0.0;\n    if (attribute_utils::TryGetNum(value, font_size)) {\n      font_size_ = font_size;\n    }\n  } else if (kw == KeywordID::kLineHeight) {\n    double line_height = 0.0;\n    if (attribute_utils::TryGetNum(value, line_height)) {\n      line_height_ = line_height;\n    }\n  } else if (kw == KeywordID::kMaxlines) {\n    double max_lines = std::numeric_limits<uint32_t>::max();\n    if (attribute_utils::TryGetNum(value, max_lines)) {\n      max_lines_ = max_lines;\n    }\n  } else if (kw == KeywordID::kMinHeight) {\n    std::string min_height;\n    attribute_utils::TryGetString(value, min_height);\n    min_height_ = attribute_utils::ToPxWithDisplayMetrics(\n        min_height, owner_->GetViewContext()->GetPageView());\n    MarkDirty();\n  } else if (kw == KeywordID::kMaxHeight) {\n    std::string max_height;\n    attribute_utils::TryGetString(value, max_height);\n    max_height_ = attribute_utils::ToPxWithDisplayMetrics(\n        max_height, owner_->GetViewContext()->GetPageView());\n    MarkDirty();\n  }\n}\n\nvoid EditableShadowNode::SetTextHeight(float text_height) {\n  if (text_height_ != text_height) {\n    text_height_ = text_height;\n    MarkDirty();\n  }\n}\n\nvoid EditableShadowNode::Measure(const MeasureConstraint& constraint,\n                                 MeasureResult& result) {\n  bool infinite = max_lines_ == std::numeric_limits<uint32_t>::max();\n  if (constraint.height_mode == MeasureMode::kDefinite) {\n    result.height = *constraint.height;\n  } else {\n    // Currently the autoheight attribute does not support asynchronous\n    if (min_height_ >= 0 && max_height_ >= 0 && max_height_ >= min_height_) {\n      if (owner_->GetUITaskRunner()->RunsTasksOnCurrentThread()) {\n        auto editable_view =\n            static_cast<TextAreaView*>(owner_->FindViewByViewId(id()));\n        editable_view->Measure(constraint, result);\n        result.height = std::clamp(result.height, min_height_, max_height_);\n        return;\n      } else {\n        FML_DCHECK(false);\n      }\n    } else {\n      if (line_height_.has_value()) {\n        result.height = std::max(kDefaultLineHeightFactor * font_size_,\n                                 line_height_.value());\n      } else {\n        result.height = kDefaultLineHeightFactor * font_size_;\n      }\n      result.height = result.height * (infinite ? 1 : max_lines_);\n    }\n  }\n  if (constraint.width_mode == MeasureMode::kIndefinite ||\n      !constraint.width.has_value()) {\n    result.width = std::numeric_limits<float>::infinity();\n  } else {\n    result.width = *constraint.width;\n  }\n\n  if (constraint.width_mode == MeasureMode::kAtMost) {\n    result.width = std::min(result.width, constraint.width.value_or(0));\n  }\n  if (constraint.height_mode == MeasureMode::kAtMost) {\n    result.height = std::min(result.height, constraint.height.value_or(0));\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/editable_shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_EDITABLE_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_EDITABLE_SHADOW_NODE_H_\n\n#include <limits>\n#include <optional>\n#include <string>\n\n#include \"clay/ui/component/measurable.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\nconstexpr float kDefaultFontSizeInDip = 14.f;\n\nclass EditableShadowNode : public ShadowNode, public Measurable {\n public:\n  EditableShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  ~EditableShadowNode() override = default;\n\n  Measurable* GetMeasurable() override { return this; }\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  bool IsEditableShadowNode() override { return true; }\n  void Measure(const MeasureConstraint& constraint,\n               MeasureResult& result) override;\n\n  void SetTextHeight(float text_height);\n\n private:\n  float font_size_;\n  std::optional<float> line_height_;\n  uint32_t max_lines_ = std::numeric_limits<uint32_t>::max();\n  float min_height_ = -1;\n  float max_height_ = -1;\n  float text_height_ = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_EDITABLE_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/icu_no_substitute.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/icu_substitute.h\"\n\n#if defined(ENABLE_SKITY) && defined(OS_ANDROID)\n#include \"clay/ui/component/text/tttext_headers.h\"\n#else\n#include <unicode/ubidi.h>\n#include <unicode/uscript.h>\n#endif\n\nnamespace clay {\n\nnamespace icu_substitute {\n\n#if defined(ENABLE_SKITY) && defined(OS_ANDROID)\n#define UScriptGetScript tttext::ICUWrapper::uscriptGetScript\n#define UBidiOpenSized tttext::ICUWrapper::ubidiOpenSized\n#define UBidiSetPara tttext::ICUWrapper::ubidiSetPara\n#define UBidiGetDirection tttext::ICUWrapper::ubidiGetDirection\n#define UBidiClose tttext::ICUWrapper::ubidiClose\n#else\n#define UScriptGetScript uscript_getScript\n#define UBidiOpenSized ubidi_openSized\n#define UBidiSetPara ubidi_setPara\n#define UBidiGetDirection ubidi_getDirection\n#define UBidiClose ubidi_close\n#endif\n\nbool IsCJKCharacter(uint32_t unicode) {\n  UErrorCode status = U_ZERO_ERROR;\n  UScriptCode script = UScriptGetScript(unicode, &status);\n  return (script == USCRIPT_HAN || script == USCRIPT_HIRAGANA ||\n          script == USCRIPT_KATAKANA);\n}\n\nbool IsRTLCharacter(const std::u16string& text) {\n  UErrorCode status = U_ZERO_ERROR;\n  UBiDi* bidi = UBidiOpenSized(text.length(), 0, &status);\n  // There are differences between uchar types on the Windows platform and uchar\n  // types on other platforms.\n  const UChar* text_ptr = reinterpret_cast<const UChar*>(text.data());\n  UBidiSetPara(bidi, text_ptr, text.length(), UBIDI_DEFAULT_LTR, nullptr,\n               &status);\n  UBiDiDirection direction = UBidiGetDirection(bidi);\n\n  UBidiClose(bidi);\n\n  return direction == UBIDI_RTL;\n}\n\n}  // namespace icu_substitute\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/icu_substitute.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/icu_substitute.h\"\n\nnamespace clay {\n\nnamespace icu_substitute {\n\nbool IsCJKCharacter(uint32_t unicode) {\n  return (unicode >= 0x4e00 && unicode <= 0x9FFF) ||\n         (unicode >= 0x3400 && unicode <= 0x4dbf) ||\n         (unicode >= 0x20000 && unicode <= 0x2ffff);\n}\n\nbool IsRTLCharacter(const std::u16string& text) { return false; }\n\n}  // namespace icu_substitute\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/icu_substitute.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_ICU_SUBSTITUTE_H_\n#define CLAY_UI_SHADOW_ICU_SUBSTITUTE_H_\n\n#include <cstdint>\n#include <string>\n\nnamespace clay {\n\nnamespace icu_substitute {\n\nbool IsCJKCharacter(uint32_t unicode);\nbool IsRTLCharacter(const std::u16string& text);\n\n}  // namespace icu_substitute\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_ICU_SUBSTITUTE_H_\n"
  },
  {
    "path": "clay/ui/shadow/image_shadow_node.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/image_shadow_node.h\"\n\n#include <limits>\n#include <mutex>\n\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/component/base_image_view.h\"\n#include \"clay/ui/component/component_constants.h\"\n\nnamespace clay {\n\nImageShadowNode::ImageShadowNode(ShadowNodeOwner* owner, std::string tag,\n                                 int id)\n    : ShadowNode(owner, tag, id) {}\n\n// Called from UI thread\nvoid ImageShadowNode::AdjustSizeIfNeeded(bool auto_size, float bitmap_width,\n                                         float bitmap_height) {\n  bool need_update_layout = false;\n  {\n    std::lock_guard<std::mutex> l(mutex_);\n    need_update_layout = auto_size || (auto_size_ != auto_size);\n\n    if (auto_size_ == auto_size && bitmap_width_ == bitmap_width &&\n        bitmap_height_ == bitmap_height) {\n      return;\n    }\n\n    bitmap_width_ = bitmap_width;\n    bitmap_height_ = bitmap_height;\n    auto_size_ = auto_size;\n  }\n  if (need_update_layout) {\n    MarkDirty();\n  }\n}\n\nMeasureResult ImageShadowNode::Measure(const MeasureConstraint& constraint) {\n  if (!constraint.IsValid()) {\n    return {};\n  }\n\n  float constraint_width = constraint.width.value();\n  float constraint_height = constraint.height.value();\n  if (constraint.width_mode == MeasureMode::kDefinite &&\n      constraint.height_mode == MeasureMode::kDefinite) {\n    return {constraint_width, constraint_height, 0.f};\n  }\n\n  float bitmap_width, bitmap_height;\n  bool auto_size;\n  {\n    std::lock_guard<std::mutex> l(mutex_);\n    bitmap_width = bitmap_width_;\n    bitmap_height = bitmap_height_;\n    auto_size = auto_size_;\n  }\n\n  if (!auto_size) {\n    return {constraint.width_mode == MeasureMode::kDefinite ? constraint_width\n                                                            : 0.f,\n            constraint.height_mode == MeasureMode::kDefinite ? constraint_height\n                                                             : 0.f,\n            0.f};\n  }\n\n  float result_width = constraint_width;\n  float result_height = constraint_height;\n  if (constraint.width_mode == MeasureMode::kDefinite) {\n    // Height is determined by width\n    float expected_height = constraint_width * (bitmap_height / bitmap_width);\n    if (constraint.height_mode == MeasureMode::kAtMost &&\n        expected_height > constraint_height) {\n      result_height = constraint_height;\n    } else {\n      result_height = expected_height;\n    }\n  } else if (constraint.height_mode == MeasureMode::kDefinite) {\n    // Width is determined by height\n    float expected_width = constraint_height * (bitmap_width / bitmap_height);\n    if (constraint.width_mode == MeasureMode::kAtMost &&\n        expected_width > constraint_width) {\n      result_width = constraint_width;\n    } else {\n      result_width = expected_width;\n    }\n  } else {\n    if (constraint.width_mode == MeasureMode::kIndefinite) {\n      result_width = std::numeric_limits<float>::max();\n    }\n    if (constraint.height_mode == MeasureMode::kIndefinite) {\n      result_height = std::numeric_limits<float>::max();\n    }\n\n    if (result_width >= bitmap_width && result_height >= bitmap_height) {\n      // The bitmap size is smaller than the constraint.\n      result_width = bitmap_width;\n      result_height = bitmap_height;\n    } else {\n      // Keep the aspect ratio of bitmap\n      float aspect_ratio = bitmap_height / bitmap_width;\n      if (result_height / result_width < aspect_ratio) {\n        result_width = result_height / aspect_ratio;\n      } else {\n        result_height = aspect_ratio * result_width;\n      }\n    }\n  }\n\n  return {result_width, result_height, 0.f};\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/image_shadow_node.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_IMAGE_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_IMAGE_SHADOW_NODE_H_\n\n#include <string>\n\n#include \"clay/ui/component/measurable.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\nclass ImageShadowNode : public ShadowNode, public CustomMeasurable {\n public:\n  ImageShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n\n  bool IsImageShadowNode() override { return true; }\n\n  void AdjustSizeIfNeeded(bool auto_size, float bitmap_width,\n                          float bitmap_height);\n\n  // |CustomMeasurable|\n  MeasureResult Measure(const MeasureConstraint& constraint) override;\n  void Align() override {}\n  CustomMeasurable* GetCustomMeasurable() override { return this; }\n\n private:\n  bool auto_size_ = false;\n  float bitmap_width_ = 0.f;\n  float bitmap_height_ = 0.f;\n  std::mutex mutex_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_IMAGE_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/inline_image_shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/inline_image_shadow_node.h\"\n\n#include \"clay/ui/component/inline_image_view.h\"\n#include \"clay/ui/rendering/render_image.h\"\n#include \"clay/ui/shadow/shadow_layout_context.h\"\n\nnamespace clay {\n\nInlineImageShadowNode::InlineImageShadowNode(ShadowNodeOwner* owner,\n                                             std::string tag, int id)\n    : ShadowNode(owner, tag, id) {}\n\nvoid InlineImageShadowNode::PreLayout(PreLayoutContext* context) {\n  auto* context_text = static_cast<PreShadowLayoutContextText*>(context);\n  if (GetEndIndex() > 0) {\n    context_text->CollectInlineImages(this);\n  }\n}\n\nvoid InlineImageShadowNode::TextLayout(LayoutContext* context) {\n  if (GetEndIndex() == 0) {\n    placeholder_index_ = -1;\n    return;\n  }\n  TextParagraphBuilder* builder =\n      static_cast<LayoutContextText*>(context)->builder();\n  builder->PushStyle(text_style_.value());\n  txt::PlaceholderRun placeholder(\n      Width(), Height() + MarginTop() + MarginBottom(),\n      txt::PlaceholderAlignment::kBaseline, txt::TextBaseline::kAlphabetic,\n      Height() + MarginTop() + MarginBottom() + baseline_offset_);\n  start_glyph_ =\n      static_cast<LayoutContextText*>(context)->TextSizeIncludingPlaceholders();\n  end_glyph_ = start_glyph_ + 1;\n  placeholder_index_ =\n      static_cast<LayoutContextText*>(context)->AddPlaceholder(placeholder);\n  builder->Pop();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/inline_image_shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_INLINE_IMAGE_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_INLINE_IMAGE_SHADOW_NODE_H_\n\n#include <string>\n\n#include \"clay/ui/component/layout_controller.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\nclass InlineImageShadowNode : public ShadowNode {\n public:\n  InlineImageShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  ~InlineImageShadowNode() = default;\n\n  void PreLayout(PreLayoutContext* context) override;\n  void TextLayout(LayoutContext* context) override;\n\n  int placeholder_index() const { return placeholder_index_; }\n\n  size_t StartGlyph() { return start_glyph_; }\n  size_t EndGlyph() { return end_glyph_; }\n\n  bool IsInlineImageShadowNode() override { return true; }\n\n  bool IsVirtual() override { return true; }\n\n  void SetBaselineOffset(double baseline_offset) override {\n    baseline_offset_ = baseline_offset;\n  }\n\n private:\n  int placeholder_index_ = -1;\n  double baseline_offset_ = 0.f;\n  size_t start_glyph_ = 0;\n  size_t end_glyph_ = 0;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_INLINE_IMAGE_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/inline_text_shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/inline_text_shadow_node.h\"\n\n#include <string>\n#include <utility>\n\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/rendering/text/render_inline_text.h\"\n\nnamespace clay {\n\nInlineTextShadowNode::InlineTextShadowNode(ShadowNodeOwner* owner,\n                                           std::string tag, int id)\n    : BaseTextShadowNode(owner, tag, id) {}\n\nInlineTextShadowNode::~InlineTextShadowNode() = default;\n\nvoid InlineTextShadowNode::AddTextRange(size_t start, size_t end) {\n  range_in_paragraph_.emplace_back(start, end);\n}\n\nvoid InlineTextShadowNode::LayoutRange(txt::Paragraph* paragraph) {\n  for (auto child : children_) {\n    if (child->IsInlineTextShadowNode()) {\n      static_cast<InlineTextShadowNode*>(child)->LayoutRange(paragraph);\n    }\n  }\n}\n\nvoid InlineTextShadowNode::TextLayout(LayoutContext* context) {\n  range_in_paragraph_.clear();\n  BaseTextShadowNode::TextLayout(context);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/inline_text_shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_INLINE_TEXT_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_INLINE_TEXT_SHADOW_NODE_H_\n\n#include <list>\n#include <string>\n\n#include \"clay/shell/platform/common/text_range.h\"\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/shadow/base_text_shadow_node.h\"\n\nnamespace clay {\n\nusing SendTextBoxesToUiThreadCallback =\n    std::function<void(void* text_box_list)>;\n\nclass InlineTextShadowNode : public BaseTextShadowNode {\n public:\n  InlineTextShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  ~InlineTextShadowNode() override;\n\n  void TextLayout(LayoutContext* context) override;\n\n  // Add a range [start, end).\n  void AddTextRange(size_t start, size_t end);\n\n  void LayoutRange(txt::Paragraph* paragraph);\n\n  bool IsInlineTextShadowNode() override { return true; }\n\n  bool IsVirtual() override { return true; }\n\n  std::list<clay::TextRange> range_in_paragraph_;\n\n private:\n  SendTextBoxesToUiThreadCallback text_boxex_callback_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_INLINE_TEXT_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/inline_truncation_shadow_node.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/inline_truncation_shadow_node.h\"\n\n#include <limits>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/shadow/base_text_shadow_node.h\"\n\nnamespace clay {\n\nInlineTruncationShadowNode::InlineTruncationShadowNode(ShadowNodeOwner* owner,\n                                                       std::string tag, int id)\n    : BaseTextShadowNode(owner, tag, id) {}\n\nvoid InlineTruncationShadowNode::TextLayout(LayoutContext* context) {\n  if (need_layout_) {\n    BaseTextShadowNode::TextLayout(context);\n  }\n}\n\nvoid InlineTruncationShadowNode::UpdateTruncatedSize(float width,\n                                                     float height) {\n  styles_.width = width;\n  styles_.height = height;\n}\n\nFloatSize InlineTruncationShadowNode::CalculateTruncatedSize() {\n  TRACE_EVENT(\"clay\",\n              \"InlineTruncationShadowNode::CalculateTruncatedStringWidth\");\n  auto builder = std::make_unique<TextParagraphBuilder>(true, text_style_);\n  LayoutContextText context;\n  context.SetBuilder(builder.get());\n  ProcessChildLayout(&context);\n  auto paragraph = Build(std::move(builder));\n  paragraph->Layout(std::numeric_limits<float>::infinity());\n  return {static_cast<float>(paragraph->GetMaxIntrinsicWidth()),\n          static_cast<float>(paragraph->GetHeight())};\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/inline_truncation_shadow_node.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_INLINE_TRUNCATION_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_INLINE_TRUNCATION_SHADOW_NODE_H_\n\n#include <string>\n\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/gfx/geometry/float_size.h\"\n#include \"clay/ui/shadow/base_text_shadow_node.h\"\n\nnamespace clay {\n\nclass InlineTruncationShadowNode : public BaseTextShadowNode {\n public:\n  InlineTruncationShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  ~InlineTruncationShadowNode() override = default;\n\n  bool IsInlineTruncationShadowNode() override { return true; }\n\n  void TextLayout(LayoutContext* context) override;\n\n  FloatSize CalculateTruncatedSize();\n  void UpdateTruncatedSize(float width, float height);\n\n  void SetNeedLayout(bool need_layout) { need_layout_ = need_layout; }\n  void SetNeedMount(bool need_mount) { need_mount_ = need_mount; }\n  bool IfNeedMount() const { return need_mount_; }\n\n  bool IsVirtual() override { return true; }\n\n private:\n  bool need_layout_ = false;\n  bool need_mount_ = false;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_INLINE_TRUNCATION_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/inline_view_shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/inline_view_shadow_node.h\"\n\n#include \"clay/third_party/txt/src/txt/placeholder_run.h\"\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/shadow/shadow_layout_context.h\"\n\nnamespace clay {\n\nInlineViewShadowNode::InlineViewShadowNode(ShadowNodeOwner* owner,\n                                           std::string tag, int id)\n    : ShadowNode(owner, tag, id) {}\n\nvoid InlineViewShadowNode::PreLayout(PreLayoutContext* context) {\n  auto* context_text = static_cast<PreShadowLayoutContextText*>(context);\n  if (GetEndIndex() > 0) {\n    context_text->CollectInlineViews(this);\n  }\n}\n\nvoid InlineViewShadowNode::TextLayout(LayoutContext* context) {\n  if (GetEndIndex() == 0) {\n    placeholder_index_ = -1;\n    return;\n  }\n  TextParagraphBuilder* builder =\n      static_cast<LayoutContextText*>(context)->builder();\n  builder->PushStyle(text_style_.value());\n  txt::PlaceholderRun placeholder(\n      Width(), Height(), txt::PlaceholderAlignment::kBaseline,\n      txt::TextBaseline::kAlphabetic, Height() + baseline_offset_);\n  start_glyph_ =\n      static_cast<LayoutContextText*>(context)->TextSizeIncludingPlaceholders();\n  end_glyph_ = start_glyph_ + 1;\n  placeholder_index_ =\n      static_cast<LayoutContextText*>(context)->AddPlaceholder(placeholder);\n  builder->Pop();\n}\n\nMeasureResult InlineViewShadowNode::MeasureNativeNode(\n    const MeasureConstraint& constraint) {\n  return owner_->MeasureNativeNode(this, constraint);\n}\n\nvoid InlineViewShadowNode::AlignNativeNode(float top, float left) {\n  return owner_->AlignNativeNode(this, top, left);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/inline_view_shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_INLINE_VIEW_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_INLINE_VIEW_SHADOW_NODE_H_\n\n#include <string>\n\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\nclass InlineViewShadowNode : public ShadowNode {\n public:\n  InlineViewShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  ~InlineViewShadowNode() override = default;\n\n  void PreLayout(PreLayoutContext* context) override;\n\n  bool IsInlineViewShadowNode() override { return true; }\n\n  bool IsVirtual() override { return true; }\n\n  void TextLayout(LayoutContext* context) override;\n\n  size_t StartGlyph() { return start_glyph_; }\n  size_t EndGlyph() { return end_glyph_; }\n\n  int placeholder_index() const { return placeholder_index_; }\n\n  void SetBaselineOffset(double baseline_offset) override {\n    baseline_offset_ = baseline_offset;\n  }\n\n  MeasureResult MeasureNativeNode(const MeasureConstraint& constraint);\n  void AlignNativeNode(float top, float left);\n\n private:\n  int placeholder_index_ = -1;\n  size_t start_glyph_ = 0;\n  size_t end_glyph_ = 0;\n  double baseline_offset_ = 0.f;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_INLINE_VIEW_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/inner_inline_text_shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/inner_inline_text_shadow_node.h\"\n\n#include <string>\n\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/rendering/text/render_inline_text.h\"\n\nnamespace clay {\n\nInnerInlineTextShadowNode::InnerInlineTextShadowNode(ShadowNodeOwner* owner,\n                                                     std::string tag, int id)\n    : InlineTextShadowNode(owner, tag, id) {}\n\nvoid InnerInlineTextShadowNode::LayoutRange(txt::Paragraph* paragraph) {\n  fml::TaskRunner::RunNowOrPostTask(\n      inline_text_view_->page_view()->GetTaskRunner(), [this, paragraph]() {\n        if (auto render_object = inline_text_view_->render_object()) {\n          static_cast<RenderInlineText*>(render_object)->ClearTextBox();\n          for (const auto& range : range_in_paragraph_) {\n            auto boxes = paragraph->GetRectsForRange(\n                range.start(), range.end(),\n                txt::Paragraph::RectHeightStyle::kTight,\n                txt::Paragraph::RectWidthStyle::kTight);\n            for (auto& box : boxes) {\n              static_cast<RenderInlineText*>(render_object)\n                  ->AddTextBox(box.rect);\n            }\n          }\n        } else {\n          FML_DLOG(ERROR)\n              << \"The view corresponding to the node cannot be found\";\n        }\n      });\n  for (auto child : children_) {\n    if (child->IsInlineTextShadowNode()) {\n      static_cast<InnerInlineTextShadowNode*>(child)->LayoutRange(paragraph);\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/inner_inline_text_shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_INNER_INLINE_TEXT_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_INNER_INLINE_TEXT_SHADOW_NODE_H_\n\n#include <string>\n\n#include \"clay/ui/component/text/inline_text_view.h\"\n#include \"clay/ui/shadow/inline_text_shadow_node.h\"\n\nnamespace clay {\n\nclass InnerInlineTextShadowNode : public InlineTextShadowNode {\n public:\n  explicit InnerInlineTextShadowNode(InlineTextView* inline_text_view)\n      : InnerInlineTextShadowNode(nullptr, \"inner-inline-text\", -1) {\n    inline_text_view_ = inline_text_view;\n  }\n\n  InnerInlineTextShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  ~InnerInlineTextShadowNode() = default;\n\n  void LayoutRange(txt::Paragraph* paragraph);\n\n private:\n  InlineTextView* inline_text_view_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_INNER_INLINE_TEXT_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/inner_text_shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/inner_text_shadow_node.h\"\n\n#include <algorithm>\n#include <limits>\n#include <memory>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/painter/gradient_factory.h\"\n#include \"clay/ui/shadow/inner_inline_text_shadow_node.h\"\n\nnamespace clay {\n\nnamespace {\nconstexpr float kLayoutTolerance = 1.f;\n}\n\nvoid InnerTextShadowNode::ProcessParagraph(\n    ShadowLayoutContextMeasure* context_measure,\n    std::unique_ptr<txt::Paragraph> paragraph) {\n  // layout inline text\n  for (auto child : children_) {\n    if (child->IsInlineTextShadowNode()) {\n      static_cast<InnerInlineTextShadowNode*>(child)->LayoutRange(\n          paragraph.get());\n    }\n  }\n\n  if (context_measure) {\n    measured_width_ = std::ceil(paragraph->GetMaxIntrinsicWidth());\n    measured_height_ = std::ceil(paragraph->GetHeight());\n  }\n  SetCacheParagraph(std::move(paragraph));\n}\n\nvoid InnerTextShadowNode::TextLayout(LayoutContext* context) {\n  auto* context_measure = static_cast<ShadowLayoutContextMeasure*>(context);\n  auto layout_width = context_measure->layout_width_;\n\n  const bool force_rebuild = update_flag_ != kUpdateFlagNone;\n  const bool should_layout =\n      force_rebuild || prev_layout_width_ != layout_width;\n\n  if (force_rebuild) {\n    PreLayout(nullptr);\n  }\n\n  std::unique_ptr<txt::Paragraph> paragraph;\n  if (should_layout) {\n    auto builder = std::make_unique<TextParagraphBuilder>(false, text_style_);\n    LayoutContextText context_text;\n    context_text.SetBuilder(builder.get());\n    builder->PushStyle(text_style_.value());\n    BaseTextShadowNode::TextLayout(&context_text);\n    builder->Pop();\n    paragraph = Build(std::move(builder));\n    paragraph->Layout(layout_width);\n    ProcessParagraph(context_measure, std::move(paragraph));\n  }\n\n  if (context_measure) {\n    context_measure->measured_width_ = measured_width_;\n    context_measure->measured_height_ = measured_height_;\n  }\n\n  update_flag_ = kUpdateFlagNone;\n  prev_layout_width_ = layout_width;\n}\n\nMeasureResult InnerTextShadowNode::Measure(\n    const MeasureConstraint& constraint) {\n  MeasureResult result;\n  if (!constraint.IsValid()) {\n    FML_DLOG(WARNING) << \"Invalid measure metrics.\";\n    result.width = 0;\n    result.height = 0;\n    if (text_view_) {\n      text_view_->SetWidth(result.width);\n      text_view_->SetHeight(result.height);\n    } else {\n      FML_DLOG(ERROR) << \"The text view is null\";\n    }\n    return result;\n  }\n  constraint_ = constraint;\n\n  ShadowLayoutContextMeasure context;\n  switch (constraint.width_mode) {\n    case TextMeasureMode::kIndefinite:\n      context.layout_width_ = std::numeric_limits<float>::infinity();\n      break;\n    case TextMeasureMode::kDefinite:\n    case TextMeasureMode::kAtMost:\n      context.layout_width_ = *constraint.width;\n      break;\n  }\n\n  TextLayout(&context);\n\n  if (constraint.width_mode == TextMeasureMode::kIndefinite) {\n    if (GetResolvedTextAlign() != TextAlignment::kLeft) {\n      // Do second layout if the width mode is indefinite and the alignment is\n      // not left. Because in this case, the paragraph_ won't have the actual\n      // line and nothing will be painted.\n      context.layout_width_ = context.measured_width_;\n      TextLayout(&context);\n    }\n  }\n\n  if (constraint.width_mode == TextMeasureMode::kAtMost &&\n      context.measured_width_ + kLayoutTolerance < context.layout_width_) {\n    context.layout_width_ = context.measured_width_;\n    // Do second layout if actual text width is less than constraints.\n    // For example, given at most 200px width and actually 100px is needed,\n    // use 100px to layout again. Otherwise text align will be problem.\n    // Note: Here maybe some optimizations to avoid second layout.\n    TextLayout(&context);\n  }\n\n  if (constraint.width_mode == TextMeasureMode::kDefinite) {\n    result.width = *constraint.width;\n  } else if (constraint.width_mode == TextMeasureMode::kIndefinite) {\n    result.width = std::ceil(context.measured_width_);\n  } else {\n    result.width =\n        std::min(std::ceil(static_cast<float>(context.measured_width_)),\n                 *constraint.width);\n  }\n\n  auto desired_height = context.measured_height_;\n  switch (constraint.height_mode) {\n    case TextMeasureMode::kIndefinite: {\n      result.height = desired_height;\n      break;\n    }\n    case TextMeasureMode::kDefinite: {\n      result.height = *constraint.height;\n      break;\n    }\n    case TextMeasureMode::kAtMost: {\n      result.height = std::min(std::ceil(static_cast<float>(desired_height)),\n                               *constraint.height);\n      break;\n    }\n  }\n\n  if (text_view_) {\n    text_view_->SetWidth(result.width);\n    text_view_->SetHeight(result.height);\n    text_view_->SetParagraph(GetCacheParagraph(), GetRawText());\n    if (text_style_->text_gradient) {\n      text_view_->GetRenderText()->SetGradient(text_style_->text_gradient);\n    } else {\n      text_view_->GetRenderText()->SetGradient(std::nullopt);\n    }\n  } else {\n    FML_DLOG(ERROR) << \"The view corresponding to the node cannot be found\";\n  }\n\n  return result;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/inner_text_shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_INNER_TEXT_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_INNER_TEXT_SHADOW_NODE_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/component/text/text_view.h\"\n#include \"clay/ui/shadow/text_shadow_node.h\"\n\nnamespace clay {\n\n// this class just for soft keyboard view and video controller\nclass InnerTextShadowNode : public TextShadowNode {\n public:\n  explicit InnerTextShadowNode(TextView* view)\n      : InnerTextShadowNode(nullptr, \"tappable-text\", -1, view) {}\n\n  InnerTextShadowNode(ShadowNodeOwner* owner, std::string tag, int id,\n                      TextView* view)\n      : TextShadowNode(owner, tag, id), text_view_(view) {\n    text_style_ = std::make_optional<TextStyle>();\n  }\n\n  void ProcessParagraph(ShadowLayoutContextMeasure* context_measure,\n                        std::unique_ptr<txt::Paragraph> paragraph);\n\n  MeasureResult Measure(const MeasureConstraint& constraint) override;\n\n  void TextLayout(LayoutContext* context) override;\n\n private:\n  TextView* text_view_;\n  enum UpdateFlag {\n    // None means either there is no update or the text view's width/height\n    // changed.\n    // In this case, we don't need to re-create the text builder.\n    kUpdateFlagNone = 0,\n    kUpdateFlagStyle = 1,\n    kUpdateFlagChildren = 1 << 1,\n  };\n  UpdateFlag update_flag_ = kUpdateFlagNone;\n  float prev_layout_width_ = 0;\n  int measured_width_;\n  int measured_height_;\n  MeasureConstraint constraint_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_INNER_TEXT_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/measure_utils.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_MEASURE_UTILS_H_\n#define CLAY_UI_SHADOW_MEASURE_UTILS_H_\n\nnamespace clay {\n\nclass MeasureUtils {\n public:\n  static bool isUndefined(float value) {\n    return (value >= static_cast<float>(10E8) ||\n            value <= static_cast<float>(-10E8));\n  }\n};\n\nenum class TextUpdateFlag {\n  // None means either there is no update or the text view's width/height\n  // changed.\n  // In this case, we don't need to re-create the text builder.\n  kUpdateFlagNone = 0,\n  kUpdateFlagStyle = 1,\n  kUpdateFlagChildren = 1 << 1,\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_MEASURE_UTILS_H_\n"
  },
  {
    "path": "clay/ui/shadow/native_view_shadow_node.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/native_view_shadow_node.h\"\n\n#include \"clay/ui/component/native_view.h\"\n#include \"clay/ui/component/view_context.h\"\n\nnamespace clay {\n\nMeasureResult NativeViewShadowNode::Measure(\n    const MeasureConstraint& constraint) {\n  // NOTE: For now we do not support layout thread for NativeViews.\n  FML_DCHECK(owner_->GetUITaskRunner()->RunsTasksOnCurrentThread());\n\n  auto view = owner_->GetViewContext()->GetViewById(id_);\n  if (view && view->Is<NativeView>()) {\n    return static_cast<NativeView*>(view)->Measure(constraint);\n  }\n  return {};\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/native_view_shadow_node.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_NATIVE_VIEW_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_NATIVE_VIEW_SHADOW_NODE_H_\n\n#include \"clay/ui/component/measurable.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\n// This is only used for internal NativeViews. For external NativeViews\n// (i.e. Lynx NativeViews), the ShadowNode is created and managed by Lynx.\nclass NativeViewShadowNode : public ShadowNode, public CustomMeasurable {\n public:\n  using ShadowNode::ShadowNode;\n\n  CustomMeasurable* GetCustomMeasurable() override { return this; }\n\n  // CustomMeasurable\n  MeasureResult Measure(const MeasureConstraint& constraint) override;\n  void Align() override {\n    // For now we do not support custom alignment for internal NativeViews.\n  }\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_NATIVE_VIEW_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/raw_text_shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/raw_text_shadow_node.h\"\n\n#include <algorithm>\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"base/include/string/string_utils.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/component/text/unicode_util.h\"\n#include \"clay/ui/rendering/render_dummy.h\"\n#include \"clay/ui/shadow/base_text_shadow_node.h\"\n#include \"clay/ui/shadow/icu_substitute.h\"\n#include \"clay/ui/shadow/inline_text_shadow_node.h\"\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n#include \"clay/ui/shadow/text_shadow_node.h\"\n\nnamespace clay {\n\nstatic const char16_t kZeroWidthJoinerCharacter = u'\\u200D';\nstatic const char16_t kZeroWidthSpaceCharacter = u'\\u200B';\nstatic const char16_t kZeroWidthWordJoinerCharacter = u'\\u2060';\n\nRawTextShadowNode::RawTextShadowNode(ShadowNodeOwner* owner, std::string tag,\n                                     int id)\n    : ShadowNode(owner, tag, id) {}\n\nRawTextShadowNode::~RawTextShadowNode() = default;\n\nvoid RawTextShadowNode::SetAttribute(const char* attr_c,\n                                     const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kText) {\n    SetText(attribute_utils::GetCString(value));\n  }\n  // set parent text node update flag\n  MarkNeedsUpdate(TextUpdateFlag::kUpdateFlagChildren);\n}\n\nvoid RawTextShadowNode::SetText(const std::string& text) {\n  std::u16string text_u16 = UnicodeUtil::Utf8ToUtf16(text);\n  SetText(text_u16);\n}\n\nvoid RawTextShadowNode::SetText(const std::u16string& text) {\n  if (text != text_) {\n    text_ = text;\n    origin_text_ = text_;\n    SetEndIndex(text_.length());\n    MarkDirty();\n  }\n}\n\nvoid RawTextShadowNode::TextLayout(LayoutContext* context) {\n  if (!context) {\n    return;\n  }\n#ifndef CLAY_ENABLE_TTTEXT\n  text_ = ProcessWordBreakIfNeed(origin_text_);\n#endif\n  auto need_text_indent = IfNeedTextIndent();\n  LayoutContextText* text_context = static_cast<LayoutContextText*>(context);\n  if (parent_ && parent_->IsInlineTextShadowNode()) {\n    auto start = text_context->TextSizeIncludingPlaceholders();\n    auto end = start + std::min(GetEndIndex(), text_.length());\n    static_cast<InlineTextShadowNode*>(parent_)->AddTextRange(start, end);\n  }\n  auto parent_text_shadow_node = FindTextShadowNodeAncestor();\n  std::optional<TextStyle> text_style = std::nullopt;\n  if (parent_text_shadow_node) {\n    text_style = parent_text_shadow_node->text_style_;\n  }\n  if (text_style.has_value() && text_style->white_space.has_value() &&\n      (text_style->white_space == WhiteSpace::kNoWrap ||\n       text_style->white_space == WhiteSpace::kNormal)) {\n    auto text = CollapsesWhitespaces(text_.substr(0, GetEndIndex()));\n    text_context->AddText(text, need_text_indent);\n  } else {\n    text_context->AddText(text_.substr(0, GetEndIndex()), need_text_indent);\n  }\n}\n\nstd::u16string RawTextShadowNode::ProcessWordBreakIfNeed(\n    const std::u16string& text) {\n  WordBreak word_break = WordBreak::kNormal;\n  auto parent = Parent();\n  while (parent && parent->IsBaseTextShadowNode()) {\n    auto text_node = static_cast<BaseTextShadowNode*>(parent);\n    if (text_node->text_style_ && text_node->text_style_->word_break) {\n      word_break = text_node->text_style_->word_break.value();\n      break;\n    }\n    parent = parent->Parent();\n  }\n  if (word_break == WordBreak::kBreakAll) {\n    std::u16string res;\n    res.reserve(text.length());\n    for (size_t i = 0; i < text.length(); i++) {\n      res.push_back(text[i]);\n      if (i < text.length() - 1 && !lynx::base::IsLeadingSurrogate(text[i]) &&\n          text[i + 1] != kZeroWidthJoinerCharacter &&\n          text[i] != kZeroWidthJoinerCharacter) {\n        res.push_back(kZeroWidthSpaceCharacter);\n      }\n    }\n    return res;\n  } else if (word_break == WordBreak::kKeepAll) {\n    std::u16string res;\n    res.reserve(text.length());\n    for (size_t i = 0; i < text.length(); i++) {\n      res.push_back(text[i]);\n      if (i < text.length() - 1 && icu_substitute::IsCJKCharacter(text[i]) &&\n          !ispunct(text[i]) && icu_substitute::IsCJKCharacter(text[i + 1]) &&\n          !ispunct(text[i + 1])) {\n        res.push_back(kZeroWidthWordJoinerCharacter);\n      }\n    }\n    return res;\n  } else {\n    return text;\n  }\n}\n\nTextShadowNode* RawTextShadowNode::FindTextShadowNodeAncestor() {\n  auto parent = Parent();\n  while (parent) {\n    if (parent->IsTextShadowNode()) {\n      return static_cast<TextShadowNode*>(parent);\n    } else {\n      parent = parent->Parent();\n    }\n  }\n  return nullptr;\n}\n\nbool RawTextShadowNode::IfNeedTextIndent() {\n  auto parent = Parent();\n  while (parent) {\n    if (parent->IsTextShadowNode() &&\n        static_cast<BaseTextShadowNode*>(parent)\n            ->text_style_->text_indent.has_value()) {\n      return true;\n    }\n    parent = parent->Parent();\n  }\n  return false;\n}\n\nstd::u16string RawTextShadowNode::CollapsesWhitespaces(std::u16string text) {\n  auto end = std::unique(text.begin(), text.end(), [](char16_t a, char16_t b) {\n    return iswspace(a) && iswspace(b);\n  });\n  text.erase(end, text.end());\n  std::replace_if(\n      text.begin(), text.end(),\n      [](char16_t c) { return c != ' ' && iswspace(c); }, u' ');\n  return text;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/raw_text_shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_RAW_TEXT_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_RAW_TEXT_SHADOW_NODE_H_\n\n#include <string>\n\n#include \"clay/ui/shadow/shadow_node.h\"\n#include \"clay/ui/shadow/text_shadow_node.h\"\n\nnamespace clay {\n\nclass RenderRawText;\nclass RawTextShadowNode : public ShadowNode {\n public:\n  RawTextShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  ~RawTextShadowNode() override;\n\n  void SetAttribute(const char* attr_c, const clay::Value& value) override;\n  void SetText(const std::string& text);\n  void SetText(const std::u16string& text);\n\n  void TextLayout(LayoutContext* context) override;\n  bool IfNeedTextIndent();\n\n  std::u16string Text() { return text_.substr(0, truncated_index_); }\n\n  bool IsVirtual() override { return true; }\n\n  bool IsRawTextShadowNode() override { return true; }\n\n  std::u16string CollapsesWhitespaces(std::u16string text);\n\n  TextShadowNode* FindTextShadowNodeAncestor();\n\n  std::u16string ProcessWordBreakIfNeed(const std::u16string& text);\n\n private:\n  std::u16string origin_text_;\n  std::u16string text_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_RAW_TEXT_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/shadow_layout_context.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/shadow_layout_context.h\"\n\n#include <utility>\n\nnamespace clay {\n\nvoid PreShadowLayoutContextText::CollectInlineImages(\n    InlineImageShadowNode* image) {\n  inline_images_.emplace_back(image);\n}\n\nvoid PreShadowLayoutContextText::CollectInlineViews(\n    InlineViewShadowNode* view) {\n  inline_views_.emplace_back(view);\n}\n\nstd::vector<InlineImageShadowNode*>\nPreShadowLayoutContextText::TakeInlineImages() {\n  return std::move(inline_images_);\n}\n\nstd::vector<InlineViewShadowNode*>\nPreShadowLayoutContextText::TakeInlineViews() {\n  return std::move(inline_views_);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/shadow_layout_context.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_SHADOW_LAYOUT_CONTEXT_H_\n#define CLAY_UI_SHADOW_SHADOW_LAYOUT_CONTEXT_H_\n\n#include <vector>\n\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/shadow/inline_view_shadow_node.h\"\n\nnamespace clay {\n\nclass ShadowLayoutContextMeasure : public LayoutContext {\n public:\n  // Width for text paragraph layout.\n  float layout_width_ = 0.f;\n\n  // Indicate the intrinsic width after layout using |layout_width_|.\n  int measured_width_ = 0;\n  // Indicate the height after layout using |layout_width_|.\n  int measured_height_ = 0;\n};\n\nclass PreShadowLayoutContextText : public PreLayoutContext {\n public:\n  PreShadowLayoutContextText() = default;\n  virtual ~PreShadowLayoutContextText() = default;\n\n  void CollectInlineImages(InlineImageShadowNode*);\n\n  void CollectInlineViews(InlineViewShadowNode*);\n\n  // Consume the collected inline images\n  std::vector<InlineImageShadowNode*> TakeInlineImages();\n\n  // Consume the collected inline views\n  std::vector<InlineViewShadowNode*> TakeInlineViews();\n\n private:\n  std::vector<InlineImageShadowNode*> inline_images_;\n  std::vector<InlineViewShadowNode*> inline_views_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_SHADOW_LAYOUT_CONTEXT_H_\n"
  },
  {
    "path": "clay/ui/shadow/shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/shadow_node.h\"\n\n#include <cstddef>\n#include <cstring>\n#include <limits>\n#include <optional>\n\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/shadow/inline_image_shadow_node.h\"\n#include \"clay/ui/shadow/inline_text_shadow_node.h\"\n#include \"clay/ui/shadow/text_shadow_node.h\"\n#include \"clay/ui/shadow/text_update_bundle.h\"\n#include \"clay/ui/shadow/vertical_align_style.h\"\n\nnamespace clay {\n\nstatic constexpr float kDefaultFontSizeInDip = 14.f;\nstatic constexpr Color kDefaultTextColor = Color(0xFF000000);\nstatic constexpr TextAlignment kDefaultAlignment = TextAlignment::kStart;\n\nShadowNode::ShadowNode(ShadowNodeOwner* owner, std::string tag, int id)\n    : tag_(tag), id_(id), owner_(owner) {\n  EnsureDefaultStyle();\n}\n\nvoid ShadowNode::SetAttribute(const char* attr_c, const clay::Value& value) {\n  auto kw = GetKeywordID(attr_c);\n  if (kw == KeywordID::kIdselector) {\n    id_selector_ = attribute_utils::GetCString(value);\n  }\n  if (kw == KeywordID::kVerticalAlign) {\n    vertical_align_ = std::make_optional<VerticalAlign>();\n    const auto& array = attribute_utils::GetArray(value);\n    if (array.size() < 2) {\n      vertical_align_->type = VerticalAlignType::kVerticalAlignDefault;\n      vertical_align_->length = 0;\n    } else {\n      vertical_align_->type =\n          static_cast<VerticalAlignType>(attribute_utils::GetInt(array[0]));\n      vertical_align_->length = attribute_utils::GetDouble(array[1]);\n    }\n    SetVerticalAlign(vertical_align_->type, vertical_align_->length);\n  }\n}\n\nvoid ShadowNode::SetVerticalAlign(VerticalAlignType type, float length) {\n  EnsureDefaultStyle();\n  text_style_->align_type = type;\n  text_style_->baseline_shift = length;\n  MarkDirty();\n}\n\nvoid ShadowNode::EnsureDefaultStyle() {\n  if (text_style_) {\n    return;\n  }\n  text_style_ = std::make_optional<TextStyle>();\n  text_style_->font_size = kDefaultFontSizeInDip * Logical2ClayPixelRatio();\n  text_style_->text_color = kDefaultTextColor;\n  text_style_->text_align = kDefaultAlignment;\n}\n\nvoid ShadowNode::AddChild(ShadowNode* node) { AddChild(node, ChildCount()); }\n\nvoid ShadowNode::AddChild(ShadowNode* child, int index) {\n  if (static_cast<size_t>(index) > ChildCount() || index < 0) {\n    return;\n  }\n\n  ShadowNode* parent = child->parent_;\n  if (parent) {\n    parent->RemoveChild(child);\n  }\n\n  child->parent_ = this;\n  children_.insert(children_.begin() + index, child);\n}\n\nvoid ShadowNode::RemoveChild(ShadowNode* child) {\n  std::vector<ShadowNode*>::iterator iter(\n      std::find(children_.begin(), children_.end(), child));\n  if (iter == children_.end()) {\n    return;\n  }\n\n  child->parent_ = nullptr;\n  children_.erase(iter);\n}\n\nvoid ShadowNode::RemoveAllChildren() {\n  for (auto* child : children_) {\n    child->parent_ = nullptr;\n  }\n  children_.clear();\n}\n\nvoid ShadowNode::AddEventCallback(const char* event_c) {\n  std::string event(event_c);\n  if (!events_) {\n    events_ = std::make_optional<std::vector<std::string>>();\n  }\n  events_->emplace_back(event);\n}\n\nbool ShadowNode::HasInlineObject() {\n  for (ShadowNode* child : children_) {\n    if (child->IsInlineImageShadowNode() || child->IsInlineTextShadowNode() ||\n        child->IsInlineViewShadowNode() ||\n        child->IsInlineTruncationShadowNode()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid ShadowNode::MarkNeedsUpdate(TextUpdateFlag flag) {\n  auto node = this;\n  while (node) {\n    if (node->IsTextShadowNode()) {\n      auto text_node = static_cast<TextShadowNode*>(node);\n      text_node->AddUpdateFlag(flag);\n      break;\n    }\n    node = node->Parent();\n  }\n}\n\nShadowNode* ShadowNode::FindNoneVirtualNode() {\n  if (!IsVirtual()) {\n    return this;\n  }\n  ShadowNode* temp = this->Parent();\n  while (temp != nullptr && temp->IsVirtual()) {\n    temp = temp->Parent();\n  }\n  return temp;\n}\n\nvoid ShadowNode::MarkDirty() {\n  if (!IsVirtual()) {\n    // mark lynx layout node dirty\n    if (!is_dirty_) {\n      is_dirty_ = true;\n      owner_->MarkDirty(this);\n    }\n    return;\n  }\n  ShadowNode* visible_node = FindNoneVirtualNode();\n  if (visible_node != nullptr) {\n    visible_node->MarkDirty();\n  }\n}\n\nvoid ShadowNode::UpdateLayoutStylesFromLynx() {\n  if (!owner_) {\n    return;\n  }\n  ClayLayoutStyles styles = owner_->GetLayoutStyles(this);\n  styles_ = styles;\n}\n\nvoid ShadowNode::SetBaselineOffset(const FontMetrics& parent_metrics,\n                                   double child_descent, double child_ascent) {\n  SetBaselineOffset(\n      CalculateBaselineOffset(parent_metrics, child_descent, child_ascent));\n}\n\ndouble ShadowNode::CalculateBaselineOffset(const FontMetrics& parent_metrics,\n                                           double child_descent,\n                                           double child_ascent) {\n  auto vertical_align = GetVerticalAlign();\n  if (!vertical_align.has_value()) {\n    return 0.f;\n  }\n  double baseline_shift = 0.f;\n  switch (vertical_align->type) {\n    case kVerticalAlignDefault:\n      baseline_shift = 0.f;\n      break;\n    case kVerticalAlignBaseline:\n      break;\n    case kVerticalAlignSub:\n      baseline_shift =\n          -(parent_metrics.glyph_bottom - parent_metrics.glyph_top) * 0.1f;\n      break;\n    case kVerticalAlignSuper:\n      baseline_shift =\n          (parent_metrics.glyph_bottom - parent_metrics.glyph_top) * 0.1f;\n      break;\n    case kVerticalAlignTop:\n      baseline_shift = child_ascent - parent_metrics.ascent;\n      break;\n    case kVerticalAlignTextTop:\n      baseline_shift =\n          -parent_metrics.glyph_top - parent_metrics.ascent + child_ascent;\n      break;\n    case kVerticalAlignMiddle:\n      baseline_shift =\n          (parent_metrics.x_height + child_descent + child_ascent) * 0.5;\n      break;\n    case kVerticalAlignBottom:\n      baseline_shift = child_descent - parent_metrics.descent;\n      break;\n    case kVerticalAlignTextBottom:\n      baseline_shift =\n          -parent_metrics.ascent - parent_metrics.glyph_bottom + child_descent;\n      break;\n    case VerticalAlignType::kVerticalAlignLength:\n      baseline_shift = -vertical_align->length;\n      break;\n    case kVerticalAlignPercent:\n      baseline_shift =\n          vertical_align->length * parent_metrics.line_height / 100.f;\n      break;\n    case kVerticalAlignCenter:\n      baseline_shift = (parent_metrics.bottom - parent_metrics.top +\n                        child_descent - child_ascent) *\n                           0.5 +\n                       child_ascent - parent_metrics.descent;\n      break;\n  }\n  return baseline_shift;\n}\n\nvoid ShadowNode::ResetEndIndex() {\n  for (auto* child : GetChildren()) {\n    if (child->IsRawTextShadowNode() || child->IsInlineImageShadowNode() ||\n        child->IsInlineViewShadowNode()) {\n      child->SetEndIndex(std::numeric_limits<size_t>::max());\n    } else {\n      child->ResetEndIndex();\n    }\n  }\n}\n\nvoid ShadowNode::CreateTextInfo(txt::Paragraph* paragraph) {\n  auto bundle = static_cast<TextUpdateBundle*>(FindTextBundle());\n  if (!bundle) {\n    return;\n  }\n  for (auto child : children_) {\n    TextInfo info;\n    info.id = child->id();\n    info.parent_id = this->id();\n    size_t last_glyph = 0;\n    if (paragraph->GetLineMetrics().size() > 0) {\n      last_glyph = paragraph->GetLineMetrics().back().end_index;\n    }\n    if (child->IsInlineTextShadowNode()) {\n      info.range_ =\n          static_cast<InlineTextShadowNode*>(child)->range_in_paragraph_;\n      info.view_style = child->styles_;\n      child->CreateTextInfo(paragraph);\n    }\n    if (child->IsInlineImageShadowNode()) {\n      auto placeholder_index =\n          static_cast<InlineImageShadowNode*>(child)->placeholder_index();\n      if (placeholder_index < 0 ||\n          static_cast<InlineImageShadowNode*>(child)->StartGlyph() >\n              last_glyph) {\n        info.need_mount = false;\n        return;\n      }\n      info.placeholder_index = placeholder_index;\n      info.view_style = child->styles_;\n      auto node = static_cast<InlineImageShadowNode*>(child);\n      auto text_box =\n          paragraph->GetRectsForRange(node->StartGlyph(), node->EndGlyph(),\n                                      txt::Paragraph::RectHeightStyle::kTight,\n                                      txt::Paragraph::RectWidthStyle::kTight);\n      if (!text_box.empty()) {\n        info.need_mount = true;\n        if (text_box.back().direction == txt::TextDirection::rtl) {\n          info.location =\n              FloatPoint(text_box.back().rect.Left(),\n                         text_box.back().rect.Top() + node->MarginTop());\n        } else {\n          info.location =\n              FloatPoint(text_box.front().rect.Left(),\n                         text_box.front().rect.Top() + node->MarginTop());\n        }\n      } else {\n        info.need_mount = false;\n        info.placeholder_index = -1;\n      }\n    }\n    if (child->IsInlineViewShadowNode()) {\n      info.placeholder_index =\n          static_cast<InlineViewShadowNode*>(child)->placeholder_index();\n      if (info.placeholder_index.value_or(-1) >= 0 &&\n          static_cast<InlineViewShadowNode*>(child)->EndGlyph() <= last_glyph) {\n        info.need_mount = true;\n        child->CreateTextInfo(paragraph);\n      } else {\n        info.need_mount = false;\n      }\n    }\n    if (child->IsInlineTruncationShadowNode()) {\n      if (static_cast<InlineTruncationShadowNode*>(child)->IfNeedMount()) {\n        info.view_style = child->styles_;\n        info.need_mount = true;\n      } else {\n        info.need_mount = false;\n      }\n      child->CreateTextInfo(paragraph);\n    }\n    bundle->PushTextInfo(info);\n  }\n}\n\nBundle* ShadowNode::FindTextBundle() {\n  auto parent = this;\n  while (!parent->IsTextShadowNode()) {\n    if (parent->Parent()) {\n      parent = parent->Parent();\n    } else {\n      return nullptr;\n    }\n  }\n  return static_cast<TextShadowNode*>(parent)->GetTextBundle();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_SHADOW_NODE_H_\n\n#include <cstddef>\n#include <limits>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"clay/public/style_types.h\"\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/component/keywords.h\"\n#include \"clay/ui/component/layout_controller.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/shadow/bundle.h\"\n#include \"clay/ui/shadow/measure_utils.h\"\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n#include \"clay/ui/shadow/vertical_align_style.h\"\n\nnamespace clay {\n\nclass Measurable;\nclass CustomMeasurable;\nclass TextUpdateBundle;\n\nclass ShadowNode {\n public:\n  ShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  virtual ~ShadowNode() = default;\n\n  virtual bool IsRawTextShadowNode() { return false; }\n  virtual bool IsInlineTextShadowNode() { return false; }\n  virtual bool IsInlineImageShadowNode() { return false; }\n  virtual bool IsTextShadowNode() { return false; }\n  virtual bool IsInlineViewShadowNode() { return false; }\n  virtual bool IsBaseTextShadowNode() { return false; }\n  virtual bool IsSliderShadowNode() { return false; }\n  virtual bool IsEditableShadowNode() { return false; }\n  virtual bool IsInlineTruncationShadowNode() { return false; }\n  virtual bool IsImageShadowNode() { return false; }\n  virtual bool IsMarkDownShadowNode() { return false; }\n\n  virtual Measurable* GetMeasurable() { return nullptr; }\n  virtual CustomMeasurable* GetCustomMeasurable() { return nullptr; }\n\n  // The return value represents whether the text is displayed completely after\n  // layout\n  virtual void TextLayout(LayoutContext* context) {}\n\n  virtual void OnLayout(float width, TextMeasureMode width_mode, float height,\n                        TextMeasureMode height_mode,\n                        const std::array<float, 4>& paddings,\n                        const std::array<float, 4>& borders) {\n    is_dirty_ = false;\n  }\n\n  virtual void PreLayout(PreLayoutContext* context) {}\n\n  virtual bool IsVirtual() { return false; }\n\n  virtual void SetAttribute(const char* attr_c, const clay::Value& value);\n\n  Bundle* MoveBundle() { return bundle_.release(); }\n\n  std::string GetName() { return tag_; }\n\n  void SetWidth(float width) { styles_.width = width; }\n  void SetHeight(float height) { styles_.height = height; }\n\n  void SetVerticalAlign(VerticalAlignType type, float length);\n\n  float Width() const { return styles_.width; }\n  float Height() const { return styles_.height; }\n  float PaddingLeft() const { return styles_.padding_left; }\n  float PaddingTop() const { return styles_.padding_top; }\n  float PaddingRight() const { return styles_.padding_right; }\n  float PaddingBottom() const { return styles_.padding_bottom; }\n  float MarginLeft() const { return styles_.margin_left; }\n  float MarginTop() const { return styles_.margin_top; }\n  float MarginRight() const { return styles_.margin_right; }\n  float MarginBottom() const { return styles_.margin_bottom; }\n\n  virtual void AddChild(ShadowNode* node);\n\n  virtual void AddChild(ShadowNode* child, int index);\n\n  virtual void RemoveChild(ShadowNode* child);\n\n  void RemoveAllChildren();\n\n  ShadowNode* Parent() { return parent_; }\n\n  std::vector<ShadowNode*>& GetChildren() { return children_; }\n\n  int id() const { return id_; }\n\n  std::string id_selector() const { return id_selector_; }\n\n  size_t ChildCount() const { return children_.size(); }\n\n  void Destroy() { RemoveAllChildren(); }\n\n  bool IsDirty() const { return is_dirty_; }\n\n  virtual void MarkDirty();\n\n  ShadowNode* FindNoneVirtualNode();\n\n  void MarkNeedsUpdate(TextUpdateFlag flag);\n\n  bool HasInlineObject();\n\n  void AddEventCallback(const char* event);\n  bool HasEvent(const std::string& event) const {\n    return events_ &&\n           std::find(events_->begin(), events_->end(), event) != events_->end();\n  }\n\n  float Logical2ClayPixelRatio() {\n    if (owner_) {\n      return owner_->Logical2ClayPixelRatio();\n    }\n    return 1.0;\n  }\n\n  void UpdateLayoutStylesFromLynx();\n\n  std::optional<VerticalAlign> GetVerticalAlign() const {\n    return vertical_align_;\n  }\n\n  void SetBaselineOffset(const FontMetrics& parent_metrics,\n                         double child_descent, double child_ascent);\n  // when offset is positive, text will move up\n  virtual void SetBaselineOffset(double offset) {}\n  double CalculateBaselineOffset(const FontMetrics& parent_metrics,\n                                 double child_descent, double child_ascent);\n\n  void SetEndIndex(size_t end_index) { truncated_index_ = end_index; }\n  size_t GetEndIndex() { return truncated_index_; }\n  void ResetEndIndex();\n\n  void CreateTextInfo(txt::Paragraph* paragraph);\n  Bundle* FindTextBundle();\n  virtual void SetAttribute(KeywordID attr, const char* attr_c,\n                            const clay::Value&){};\n\n  void EnsureDefaultStyle();\n  std::optional<TextStyle> text_style_;\n\n protected:\n  ClayLayoutStyles styles_{};\n  std::string tag_;\n  std::string id_selector_;\n  ShadowNode* parent_ = nullptr;\n  std::vector<ShadowNode*> children_;\n  bool is_dirty_ = true;\n  int id_;\n  ShadowNodeOwner* owner_;\n  std::optional<std::vector<std::string>> events_;\n\n  std::optional<VerticalAlign> vertical_align_ = std::nullopt;\n  // This value will be used to limit the display area of ​raw-​text and\n  // inline-images and inline-view\n  size_t truncated_index_ = std::numeric_limits<size_t>::max();\n  std::unique_ptr<Bundle> bundle_ = nullptr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/shadow_node_owner.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n\n#include <utility>\n\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\nShadowNodeOwner::ShadowNodeOwner(fml::RefPtr<fml::TaskRunner> ui_task_runner)\n    : ui_task_runner_(ui_task_runner) {}\n\nfloat ShadowNodeOwner::Logical2ClayPixelRatio() {\n  if (context_ && context_->GetPageView()) {\n    return context_->GetPageView()\n        ->GetPixelRatio<kPixelTypeLogical, kPixelTypeClay>();\n  } else {\n    return 1.0;\n  }\n}\n\nBaseView* ShadowNodeOwner::FindViewByViewId(int id) {\n  FML_DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());\n  if (context_) {\n    return context_->FindViewByViewId(id);\n  }\n  return nullptr;\n}\n\nvoid ShadowNodeOwner::ScheduleLayout() { delegate_->ScheduleLayout(); }\n\nvoid ShadowNodeOwner::TriggerLayout() {\n  if (layout_delegate_) {\n    layout_delegate_->OnTriggerLayout();\n  }\n}\nuint32_t ShadowNodeOwner::GetContextId() const {\n  if (context_) {\n    return context_->unique_id();\n  }\n  return -1;\n}\n\nSize ShadowNodeOwner::ViewportSize() const {\n  if (context_) {\n    return context_->GetPageView()->physical_size();\n  } else {\n    return Size();\n  }\n}\n\nvoid ShadowNodeOwner::MarkDirty(ShadowNode* node) const {\n  if (layout_delegate_) {\n    layout_delegate_->OnMarkDirty(node->id());\n  }\n}\n\nClayLayoutStyles ShadowNodeOwner::GetLayoutStyles(ShadowNode* node) const {\n  if (layout_delegate_) {\n    return layout_delegate_->OnGetLayoutStyles(node->id());\n  }\n  return ClayLayoutStyles();\n}\n\nMeasureResult ShadowNodeOwner::MeasureNativeNode(\n    ShadowNode* node, const MeasureConstraint& constraint) const {\n  auto measure_output = layout_delegate_->OnMeasureNativeNode(\n      node->id(), constraint.width.value_or(0),\n      static_cast<int>(constraint.width_mode), constraint.height.value_or(0),\n      static_cast<int>(constraint.height_mode));\n  MeasureResult result;\n  result.width = measure_output.width;\n  result.height = measure_output.height;\n  result.baseline = measure_output.baseline;\n  return result;\n}\n\nvoid ShadowNodeOwner::AlignNativeNode(ShadowNode* node, float top,\n                                      float left) const {\n  layout_delegate_->OnAlignNativeNode(node->id(), top, left);\n}\n\nvoid ShadowNodeOwner::AddNode(int id, ShadowNode* node) {\n  shadow_node_map_[id] = node;\n}\n\nvoid ShadowNodeOwner::RemoveNode(int id) {\n  auto it = shadow_node_map_.find(id);\n  if (it != shadow_node_map_.end()) {\n    delete it->second;\n    shadow_node_map_.erase(it);\n  }\n}\n\nShadowNode* ShadowNodeOwner::GetNode(int id) {\n  auto it = shadow_node_map_.find(id);\n  if (it != shadow_node_map_.end()) {\n    return it->second;\n  }\n  FML_DLOG(ERROR) << \"target shadow node: \" << id << \" not found\";\n  return nullptr;\n}\n\nvoid ShadowNodeOwner::ClearNodes() {\n  for (auto& it : shadow_node_map_) {\n    delete it.second;\n  }\n  shadow_node_map_.clear();\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/shadow_node_owner.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_SHADOW_NODE_OWNER_H_\n#define CLAY_UI_SHADOW_SHADOW_NODE_OWNER_H_\n\n#include <functional>\n#include <memory>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"clay/gfx/geometry/size.h\"\n#include \"clay/public/layout_delegate.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n\nnamespace clay {\n\nclass BaseView;\nclass ViewContext;\nclass RenderDelegate;\nclass ShadowNode;\n\nclass ShadowNodeOwner {\n public:\n  explicit ShadowNodeOwner(fml::RefPtr<fml::TaskRunner> ui_task_runner);\n  ~ShadowNodeOwner() { context_ = nullptr; }\n\n  float Logical2ClayPixelRatio();\n\n  void SetViewContext(ViewContext* context) { context_ = context; }\n  ViewContext* GetViewContext() const { return context_; }\n\n  void ScheduleLayout();\n  void TriggerLayout();\n\n  void SetDelegate(RenderDelegate* delegate) { delegate_ = delegate; }\n  void SetLayoutDelegate(LayoutDelegate* delegate) {\n    layout_delegate_ = delegate;\n  }\n\n  fml::RefPtr<fml::TaskRunner> GetUITaskRunner() const {\n    return ui_task_runner_;\n  }\n\n  BaseView* FindViewByViewId(int id);\n\n  uint32_t GetContextId() const;\n\n  Size ViewportSize() const;\n\n  void AddNode(int id, ShadowNode* node);\n  void RemoveNode(int id);\n  ShadowNode* GetNode(int id);\n  void ClearNodes();\n\n  void MarkDirty(ShadowNode* node) const;\n  ClayLayoutStyles GetLayoutStyles(ShadowNode* node) const;\n  MeasureResult MeasureNativeNode(ShadowNode* node,\n                                  const MeasureConstraint& constraint) const;\n  void AlignNativeNode(ShadowNode* node, float top, float left) const;\n\n private:\n  fml::RefPtr<fml::TaskRunner> ui_task_runner_;\n  ViewContext* context_ = nullptr;\n  RenderDelegate* delegate_ = nullptr;\n  LayoutDelegate* layout_delegate_ = nullptr;\n\n  std::unordered_map<int, ShadowNode*> shadow_node_map_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_SHADOW_NODE_OWNER_H_\n"
  },
  {
    "path": "clay/ui/shadow/text_render.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/text_render.h\"\n\n#include <algorithm>\n#include <cstddef>\n#include <cstdlib>\n#include <limits>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/common/attribute_utils.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/component/inline_image_view.h\"\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/component/text/text_paragraph_builder.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/component/view.h\"\n#include \"clay/ui/rendering/render_container.h\"\n#include \"clay/ui/rendering/render_image.h\"\n#include \"clay/ui/resource/font_collection.h\"\n#include \"clay/ui/shadow/icu_substitute.h\"\n#include \"clay/ui/shadow/inline_text_shadow_node.h\"\n#include \"clay/ui/shadow/inline_truncation_shadow_node.h\"\n#include \"clay/ui/shadow/measure_utils.h\"\n#include \"clay/ui/shadow/raw_text_shadow_node.h\"\n#include \"clay/ui/shadow/text_shadow_node.h\"\n\nnamespace clay {\nnamespace utils = attribute_utils;\n\nconstexpr float kLayoutTolerance = 1.f;\nstatic constexpr float kDefaultFontSizeInDip = 14.f;\n\nclay::Value TextRender::GetTextInfo(const char* text,\n                                    const clay::Value& params) {\n  const auto& info = utils::GetMap(params);\n  TextStyle text_style;\n  auto pixel_ratio = utils::GetInt(utils::GetMapItem(info, \"pixelRatio\"));\n  if (pixel_ratio == 0) {\n    pixel_ratio = 1;\n  }\n  auto font_size_str = utils::GetCString(utils::GetMapItem(info, \"fontSize\"));\n  char* endptr;\n  double num = std::strtod(font_size_str.c_str(), &endptr);\n  if (endptr == font_size_str.c_str()) {\n    // TODO(ZhuChengCheng) There may be problems here:\n    // The front end receive this size with unit 'px', which should be the same\n    // meaning in the css, i.e. measuring precess. thus, the unit here shuld be\n    // the same as the platform unit, i.e. Android px, iOS pt.\n    text_style.font_size = kDefaultFontSizeInDip * pixel_ratio;\n  } else {\n    text_style.font_size = num * pixel_ratio;\n  }\n  double layout_width = std::numeric_limits<double>::max();\n  auto it_max_width = info.find(\"maxWidth\");\n  if (it_max_width != info.end()) {\n    auto layout_width_str = utils::GetCString(it_max_width->second);\n    layout_width = std::strtod(layout_width_str.c_str(), &endptr);\n    if (endptr == layout_width_str.c_str()) {\n      layout_width = std::numeric_limits<double>::max();\n    }\n  }\n\n  auto it_max_len = info.find(\"maxLine\");\n  if (it_max_len != info.end()) {\n    text_style.max_lines = utils::GetDouble(it_max_len->second);\n  } else {\n    text_style.max_lines = 1;\n  }\n  auto builder = std::make_unique<TextParagraphBuilder>(true, text_style);\n  builder->AddText(UnicodeUtil::Utf8ToUtf16(text));\n  auto paragraph = Build(std::move(builder));\n  paragraph->Layout(layout_width);\n  auto line_metrics = paragraph->GetLineMetrics();\n  clay::Value::Array content_array(line_metrics.size());\n  for (size_t i = 0; i < content_array.size(); i++) {\n    auto value = clay::Value(\n        std::string(text + line_metrics[i].start_index,\n                    line_metrics[i].end_index - line_metrics[i].start_index));\n    content_array[i] = std::move(value);\n  }\n  clay::Value::Map ret;\n  ret.emplace(\"width\", clay::Value(std::min(\n                           layout_width, paragraph->GetMaxIntrinsicWidth())));\n  ret.emplace(\"content\", clay::Value(std::move(content_array)));\n  return clay::Value(std::move(ret));\n}\n\nvoid TextRender::MeasureText(const std::string& text, bool show_content,\n                             const std::optional<double>& max_width,\n                             const std::optional<uint32_t>& max_length,\n                             const TextStyle& text_style, float* measured_width,\n                             float* measured_height,\n                             std::vector<std::string>* measured_content) {\n  auto builder = std::make_unique<TextParagraphBuilder>(true, text_style);\n  builder->PushStyle(text_style);\n  LayoutContextText measure_context;\n  measure_context.SetBuilder(builder.get());\n  uint32_t max_length_value = max_length.value_or(0);\n  float text_indent_value = text_style.text_indent.value_or(0.f);\n  if (max_length_value > 0) {\n    measure_context.SetMaxLength(max_length_value);\n  }\n  if (text_indent_value > 0.f) {\n    measure_context.SetTextIndent(text_indent_value);\n  }\n  measure_context.AddText(UnicodeUtil::Utf8ToUtf16(text),\n                          text_indent_value > 0.f);\n  auto paragraph = Build(std::move(builder));\n  double layout_width = max_width.has_value()\n                            ? max_width.value()\n                            : std::numeric_limits<double>::max();\n  paragraph->Layout(layout_width);\n  if (measured_width) {\n    *measured_width = std::min(layout_width, paragraph->GetMaxIntrinsicWidth());\n  }\n  if (measured_height) {\n    if (text_style.line_spacing.has_value() &&\n        paragraph->GetLineMetrics().size() > 0) {\n      auto line_spacing =\n          std::max(text_style.line_spacing.value() -\n                       paragraph->GetLineMetrics().begin()->ascent -\n                       paragraph->GetLineMetrics().begin()->descent,\n                   0.0);\n      *measured_height = std::ceil(paragraph->GetHeight() - line_spacing);\n    } else {\n      *measured_height = std::ceil(paragraph->GetHeight());\n    }\n  }\n  if (show_content && measured_content) {\n    size_t indent = text_indent_value > 0.f ? 1 : 0;\n    auto line_metrics = paragraph->GetLineMetrics();\n    if (!line_metrics.empty() && indent) {\n      auto first_line = line_metrics.begin();\n      measured_content->emplace_back(\n          text.c_str() + first_line->start_index,\n          first_line->end_index - first_line->start_index - indent);\n      line_metrics.erase(line_metrics.begin());\n    }\n    std::unique_ptr<txt::LineMetrics> last_line;\n    if (!line_metrics.empty() && max_length_value > 0 &&\n        max_length_value < text.length()) {\n      last_line = std::make_unique<txt::LineMetrics>(line_metrics.back());\n      line_metrics.pop_back();\n    }\n    for (auto& line_metric : line_metrics) {\n      measured_content->emplace_back(\n          text.c_str() + line_metric.start_index - indent,\n          line_metric.end_index - line_metric.start_index);\n    }\n    if (last_line) {\n      measured_content->emplace_back(\n          text.c_str() + last_line->start_index - indent,\n          last_line->end_index - last_line->start_index - 1 /*ellipsis size*/);\n    }\n  }\n}\n\nTextAlignment TextRender::EffectAlign() {\n  auto text_align =\n      measure_node_->text_style_->text_align.value_or(TextAlignment::kStart);\n  auto text_direction =\n      measure_node_->text_style_->text_direction.value_or(TextDirection::kLtr);\n  if (text_align == TextAlignment::kStart) {\n    return (text_direction == TextDirection::kLtr) ? TextAlignment::kLeft\n                                                   : TextAlignment::kRight;\n  } else if (text_align == TextAlignment::kEnd) {\n    return (text_direction == TextDirection::kLtr) ? TextAlignment::kRight\n                                                   : TextAlignment::kLeft;\n  } else {\n    return text_align;\n  }\n}\n\nvoid TextRender::Measure(const MeasureConstraint& constraint,\n                         ShadowLayoutContextMeasure* context) {\n  BuildTextLayout(constraint, context);\n\n  if (constraint.width_mode == TextMeasureMode::kIndefinite &&\n      measure_node_->text_style_) {\n    // Do second layout if the width mode is indefinite and the\n    // alignment is not left. Because in this case, the paragraph_\n    // won't have the actual line and nothing will be painted.\n    if (measure_node_->GetResolvedTextAlign() != TextAlignment::kLeft) {\n      measure_node_->SetNeedSecondLayout(true);\n    }\n  }\n\n  if (constraint.width_mode == TextMeasureMode::kAtMost &&\n      context->measured_width_ + kLayoutTolerance < context->layout_width_) {\n    // Do second layout if actual text width is less than constraints.\n    // For example, given at most 200px width and actually 100px is needed,\n    // use 100px to layout again. Otherwise text align will be problem.\n    // Note: Here maybe some optimizations to avoid second layout.\n    measure_node_->SetNeedSecondLayout(true);\n  }\n\n  if (cache_paragraph_) {\n    HandleAutoSize(constraint, context);\n    HandleInlineTruncation(constraint, context);\n  }\n}\n\nstd::vector<LineInfo> TextRender::GetLineInfo() {\n  auto res = std::vector<LineInfo>{};\n  if (cache_paragraph_ == nullptr) {\n    return res;\n  }\n  auto line_metrics = cache_paragraph_->GetLineMetrics();\n  for (auto line_metric : line_metrics) {\n    res.emplace_back(LineInfo{static_cast<int>(line_metric.start_index),\n                              static_cast<int>(line_metric.end_index), 0});\n  }\n  if (res.size() == 0) {\n    return res;\n  }\n  int text_length = static_cast<int>(measure_node_->GetRawText().length());\n  if (res.back().end < text_length) {\n    res.back().ellipsis_count = text_length - res.back().end + 1;\n  }\n  return res;\n}\n\nstd::unique_ptr<txt::Paragraph> TextRender::LayoutParagraph(\n    double layout_width) {\n  TRACE_EVENT(\"clay\", \"TextRender::LayoutParagraph\");\n#ifndef CLAY_ENABLE_TTTEXT\n  ReprocessAttributeIfNeeded(layout_width);\n#endif\n  auto text_style = measure_node_->text_style_.value();\n  if (text_style.white_space == WhiteSpace::kNoWrap) {\n    text_style.max_lines = 1;\n    if (text_style.overflow != TextOverflow::kEllipsis) {\n      layout_width = std::numeric_limits<float>::infinity();\n    }\n    text_style.text_align = TextAlignment::kLeft;\n  }\n  auto raw_text = measure_node_->GetRawText();\n  if (!text_style.text_direction.has_value() && !raw_text.empty() &&\n      icu_substitute::IsRTLCharacter(std::u16string(1, raw_text.front()))) {\n    text_style.text_direction = TextDirection::kRtl;\n    truncation_direction_ = TextDirection::kRtl;\n  } else {\n    truncation_direction_ =\n        text_style.text_direction.value_or(TextDirection::kLtr);\n  }\n  auto builder = std::make_unique<TextParagraphBuilder>(true, text_style);\n  LayoutContextText context_text;\n  if (measure_node_->max_length_.has_value()) {\n    context_text.SetMaxLength(measure_node_->max_length_.value());\n  }\n  context_text.SetBuilder(builder.get());\n  context_text.SetTextIndent(\n      measure_node_->text_style_->text_indent.value_or(0));\n  measure_node_->TextLayout(&context_text);\n  auto paragraph = Build(std::move(builder));\n  TRACE_EVENT(\"clay\", \"TextRender::Layout\");\n  paragraph->Layout(layout_width);\n  end_glyph_position_ = context_text.TextSizeIncludingPlaceholders();\n  return paragraph;\n}\n\nvoid TextRender::BuildTextLayout(const MeasureConstraint& constraint,\n                                 LayoutContext* context) {\n  TRACE_EVENT(\"clay\", \"TextRender::BuildTextLayout\");\n  auto* context_measure = static_cast<ShadowLayoutContextMeasure*>(context);\n  auto layout_width = context_measure->layout_width_;\n\n  const bool force_rebuild = update_flag_ != TextUpdateFlag::kUpdateFlagNone;\n  const bool should_layout =\n      force_rebuild || prev_layout_width_ != layout_width;\n\n  if (force_rebuild) {\n    measure_node_->PreLayout(nullptr);\n  }\n\n  std::unique_ptr<txt::Paragraph> paragraph;\n  if (should_layout) {\n    if (measure_node_->text_indent_ != 0) {\n      if (measure_node_->text_indent_use_percent_) {\n        measure_node_->text_style_->text_indent =\n            measure_node_->text_indent_ * layout_width;\n      } else {\n        measure_node_->text_style_->text_indent = measure_node_->text_indent_;\n      }\n    }\n    if (measure_node_->text_style_->white_space == WhiteSpace::kNoWrap) {\n      if (measure_node_->text_style_->text_align == TextAlignment::kCenter) {\n        measure_node_->SetTextPaintAlign(TextAlignment::kCenter);\n      } else if (measure_node_->GetResolvedTextAlign() ==\n                 TextAlignment::kRight) {\n        measure_node_->SetTextPaintAlign(TextAlignment::kRight);\n      } else {\n        FML_DCHECK(measure_node_->GetResolvedTextAlign() ==\n                   TextAlignment::kLeft);\n        measure_node_->SetTextPaintAlign(TextAlignment::kLeft);\n      }\n    } else {\n      measure_node_->SetTextPaintAlign(TextAlignment::kLeft);\n    }\n    paragraph = LayoutParagraph(layout_width);\n    cache_paragraph_ = std::move(paragraph);\n    measured_width_ = std::ceil(cache_paragraph_->GetMaxIntrinsicWidth());\n    if (measure_node_->text_style_->line_spacing.has_value() &&\n        cache_paragraph_->GetLineMetrics().size() > 0) {\n#ifndef CLAY_ENABLE_TTTEXT\n      auto line_spacing =\n          std::max(measure_node_->text_style_->line_spacing.value() -\n                       cache_paragraph_->GetLineMetrics().begin()->ascent -\n                       cache_paragraph_->GetLineMetrics().begin()->descent,\n                   0.0);\n      measured_height_ =\n          std::ceil(cache_paragraph_->GetHeight() - line_spacing);\n#else\n      measured_height_ = std::ceil(cache_paragraph_->GetHeight());\n#endif\n    } else {\n      measured_height_ = std::ceil(cache_paragraph_->GetHeight());\n    }\n  }\n\n  if (context_measure) {\n    context_measure->measured_width_ = measured_width_;\n    context_measure->measured_height_ = measured_height_;\n  }\n\n  update_flag_ = TextUpdateFlag::kUpdateFlagNone;\n  prev_layout_width_ = layout_width;\n}\n\n#ifndef CLAY_ENABLE_TTTEXT\nvoid TextRender::ReprocessAttributeIfNeeded(double layout_width) {\n  // Determine if the baseline_shift property needs to be set\n  for (auto* child : measure_node_->GetChildren()) {\n    if (!child->IsRawTextShadowNode() &&\n        child->GetVerticalAlign().has_value()) {\n      if (child->GetVerticalAlign()->type ==\n          VerticalAlignType::kVerticalAlignLength) {\n        child->SetBaselineOffset(child->GetVerticalAlign()->length);\n        continue;\n      }\n      TextStyle style = measure_node_->text_style_.value();\n      // According to lynx's current implementation of the vertical-align\n      // attribute,\n      // the largest text among all texts is aligned. Maybe it is more\n      // reasonable to align the largest text on the same line.\n      style.font_size = GetMaxFontSize();\n      auto parent_paragraph = LayoutXCharacter(layout_width, style);\n      auto line_metrics = parent_paragraph->GetLineMetrics();\n      if (line_metrics.empty()) {\n        return;\n      }\n      auto parent_run_metrics = line_metrics.front().run_metrics;\n      if (parent_run_metrics.empty()) {\n        return;\n      }\n      auto box = parent_paragraph->GetRectsForRange(\n          0, 1, txt::Paragraph::RectHeightStyle::kTight,\n          txt::Paragraph::RectWidthStyle::kTight);\n      if (box.empty()) {\n        return;\n      }\n      FontMetrics parent_metrics{\n          0 - parent_paragraph->GetLineMetrics().front().ascent,\n          parent_paragraph->GetLineMetrics().front().descent,\n          parent_run_metrics.begin()->second.font_metrics.fXHeight,\n          measure_node_->text_style_->line_height.value_or(0.f),\n          0.f,\n          static_cast<float>(parent_paragraph->GetHeight()),\n          box[0].rect.Top(),\n          box[0].rect.Bottom()};\n      if (child->IsInlineTextShadowNode()) {\n        static_cast<InlineTextShadowNode*>(child)->EnsureDefaultStyle();\n        TextStyle child_text_style =\n            static_cast<InlineTextShadowNode*>(child)->text_style_.value();\n        auto child_paragraph = LayoutXCharacter(layout_width, child_text_style);\n        auto child_line_metrics = child_paragraph->GetLineMetrics();\n        if (child_line_metrics.empty()) {\n          continue;\n        }\n        auto child_run_metrics = child_line_metrics.front().run_metrics;\n        if (child_run_metrics.empty()) {\n          continue;\n        }\n        child->SetBaselineOffset(\n            parent_metrics,\n            child_run_metrics.begin()->second.font_metrics.fDescent,\n            child_run_metrics.begin()->second.font_metrics.fAscent);\n      } else {\n        // inline-image need to get real width and height from lynx\n        if (child->IsInlineImageShadowNode()) {\n          child->UpdateLayoutStylesFromLynx();\n        }\n        child->SetBaselineOffset(\n            parent_metrics, 0,\n            0 - (child->Height() + child->MarginTop() + child->MarginBottom()));\n      }\n    }\n  }\n}\n#endif\n\nstd::shared_ptr<txt::Paragraph> TextRender::LayoutXCharacter(\n    double layout_width, const TextStyle& style) {\n  auto builder = std::make_unique<TextParagraphBuilder>(true, style);\n  builder->PushStyle(style);\n  // To get the x-height we default the layout \"x\"\n  std::u16string text = u\"x\";\n  builder->AddText(text);\n  auto paragraph = Build(std::move(builder));\n  paragraph->Layout(layout_width);\n  return paragraph;\n}\n\nfloat TextRender::GetMaxFontSize() {\n  auto res = measure_node_->text_style_->font_size.value();\n  for (auto* child : measure_node_->GetChildren()) {\n    if (child->IsInlineTextShadowNode()) {\n      static_cast<InlineTextShadowNode*>(child)->EnsureDefaultStyle();\n      res = std::max(res, static_cast<InlineTextShadowNode*>(child)\n                              ->text_style_->font_size.value());\n    }\n  }\n  return res;\n}\n\nvoid TextRender::HandleAutoSize(const MeasureConstraint& constraint,\n                                ShadowLayoutContextMeasure* context) {\n  if (measure_node_->enable_auto_font_size_) {\n    if (!CheckTextFullyDisplayed(constraint, context)) {\n      TryShrinkFontSize(constraint, context);\n    } else {\n      TryExpandFontSize(constraint, context);\n    }\n  }\n}\n\nbool TextRender::CheckTextFullyDisplayed(\n    const MeasureConstraint& constraint,\n    ShadowLayoutContextMeasure* context_measure) {\n  bool exceed_maxlines = measure_node_->text_style_->max_lines.has_value() &&\n                         cache_paragraph_->DidExceedMaxLines();\n  bool height_has_overflow =\n      constraint.height_mode != TextMeasureMode::kIndefinite &&\n      context_measure->measured_height_ > constraint.height;\n  bool width_has_overflow = false;\n  if (measure_node_->text_style_->white_space &&\n      measure_node_->text_style_->white_space == WhiteSpace::kNoWrap) {\n    width_has_overflow =\n        cache_paragraph_->GetMinIntrinsicWidth() > constraint.width;\n  }\n  return !(exceed_maxlines || height_has_overflow || width_has_overflow);\n}\n\nvoid TextRender::TryShrinkFontSize(\n    const MeasureConstraint& constraint,\n    ShadowLayoutContextMeasure* context_measure) {\n  // FIXME: if the number of auto_font_size_preset_sizes_ is too much, we\n  // should use dichotomy to find target\n  if (!measure_node_->auto_font_size_preset_sizes_.empty()) {\n    if (measure_node_->text_style_->font_size <=\n        measure_node_->auto_font_size_preset_sizes_.front()) {\n      return;\n    } else {\n      auto sizes = measure_node_->auto_font_size_preset_sizes_.size();\n      for (size_t i = 0; i < sizes; i++) {\n        if (measure_node_->text_style_->font_size <\n            measure_node_->auto_font_size_preset_sizes_[sizes - i - 1])\n          continue;\n        measure_node_->text_style_->font_size =\n            measure_node_->auto_font_size_preset_sizes_[sizes - i - 1];\n        update_flag_ = TextUpdateFlag::kUpdateFlagStyle;\n        BuildTextLayout(constraint, context_measure);\n        if (CheckTextFullyDisplayed(constraint, context_measure)) {\n          return;\n        }\n      }\n    }\n    return;\n  }\n\n  if (measure_node_->text_style_->font_size <=\n      measure_node_->auto_font_size_min_size_) {\n    measure_node_->text_style_->font_size =\n        measure_node_->auto_font_size_min_size_;\n  } else if (measure_node_->text_style_->font_size >=\n                 measure_node_->auto_font_size_max_size_ &&\n             measure_node_->auto_font_size_max_size_ >\n                 measure_node_->auto_font_size_min_size_) {\n    measure_node_->text_style_->font_size =\n        measure_node_->auto_font_size_max_size_;\n  }\n  while (measure_node_->text_style_->font_size >=\n         measure_node_->auto_font_size_min_size_) {\n    measure_node_->text_style_->font_size =\n        measure_node_->text_style_->font_size.value_or(\n            kDefaultFontSizeInDip * measure_node_->Logical2ClayPixelRatio()) -\n        measure_node_->auto_font_size_step_granularity_;\n    FlexInlineFontSize(true, measure_node_->text_style_->font_size.value(),\n                       measure_node_);\n    if (measure_node_->text_style_->font_size >=\n        measure_node_->auto_font_size_min_size_) {\n      update_flag_ = TextUpdateFlag::kUpdateFlagStyle;\n      BuildTextLayout(constraint, context_measure);\n      if (CheckTextFullyDisplayed(constraint, context_measure)) {\n        return;\n      }\n    }\n  }\n}\n\nvoid TextRender::TryExpandFontSize(\n    const MeasureConstraint& constraint,\n    ShadowLayoutContextMeasure* context_measure) {\n  // FIXME: if the number of auto_font_size_preset_sizes_ is too much, we\n  // should use dichotomy to find target\n  if (!measure_node_->auto_font_size_preset_sizes_.empty()) {\n    if (measure_node_->text_style_->font_size >=\n        measure_node_->auto_font_size_preset_sizes_.back()) {\n      return;\n    } else {\n      for (auto auto_font_size_preset_size :\n           measure_node_->auto_font_size_preset_sizes_) {\n        if (measure_node_->text_style_->font_size >=\n            auto_font_size_preset_size) {\n          continue;\n        }\n        auto pre_paragraph = std::move(cache_paragraph_);\n        auto original_font_size = measure_node_->text_style_->font_size;\n        measure_node_->text_style_->font_size = auto_font_size_preset_size;\n        update_flag_ = TextUpdateFlag::kUpdateFlagStyle;\n        BuildTextLayout(constraint, context_measure);\n        if (!CheckTextFullyDisplayed(constraint, context_measure)) {\n          measure_node_->text_style_->font_size = original_font_size;\n          cache_paragraph_ = std::move(pre_paragraph);\n          return;\n        }\n      }\n    }\n    return;\n  }\n\n  if (measure_node_->text_style_->font_size <=\n      measure_node_->auto_font_size_min_size_) {\n    measure_node_->text_style_->font_size =\n        measure_node_->auto_font_size_min_size_;\n  } else if (measure_node_->text_style_->font_size >=\n                 measure_node_->auto_font_size_max_size_ &&\n             measure_node_->auto_font_size_max_size_ >\n                 measure_node_->auto_font_size_min_size_) {\n    measure_node_->text_style_->font_size =\n        measure_node_->auto_font_size_max_size_;\n  }\n  while (measure_node_->text_style_->font_size <=\n         measure_node_->auto_font_size_max_size_) {\n    std::unique_ptr<txt::Paragraph> pre_paragraph = std::move(cache_paragraph_);\n    measure_node_->text_style_->font_size =\n        measure_node_->text_style_->font_size.value_or(\n            kDefaultFontSizeInDip * measure_node_->Logical2ClayPixelRatio()) +\n        measure_node_->auto_font_size_step_granularity_;\n    FlexInlineFontSize(false, measure_node_->text_style_->font_size.value(),\n                       measure_node_);\n    if (measure_node_->text_style_->font_size <=\n        measure_node_->auto_font_size_max_size_) {\n      update_flag_ = TextUpdateFlag::kUpdateFlagStyle;\n      BuildTextLayout(constraint, context_measure);\n      if (!CheckTextFullyDisplayed(constraint, context_measure)) {\n        measure_node_->text_style_->font_size =\n            measure_node_->text_style_->font_size.value_or(\n                kDefaultFontSizeInDip *\n                measure_node_->Logical2ClayPixelRatio()) -\n            measure_node_->auto_font_size_step_granularity_;\n        cache_paragraph_ = std::move(pre_paragraph);\n        return;\n      }\n    } else {\n      cache_paragraph_ = std::move(pre_paragraph);\n    }\n  }\n}\n\nvoid TextRender::FlexInlineFontSize(bool shrink_or_expand, float font_size,\n                                    ShadowNode* shadow_node) {\n  for (auto* child : shadow_node->GetChildren()) {\n    if (child->IsInlineTextShadowNode()) {\n      auto inline_text_child = static_cast<InlineTextShadowNode*>(child);\n      if ((shrink_or_expand &&\n           inline_text_child->text_style_->font_size.value() > font_size) ||\n          (!shrink_or_expand &&\n           inline_text_child->text_style_->font_size.value() < font_size)) {\n        static_cast<InlineTextShadowNode*>(child)->text_style_->font_size =\n            font_size;\n      }\n      FlexInlineFontSize(shrink_or_expand, font_size, child);\n    } else if (child->IsInlineViewShadowNode()) {\n      FlexInlineFontSize(shrink_or_expand, font_size, child);\n    }\n  }\n}\n\nvoid TextRender::HandleInlineTruncation(const MeasureConstraint& constraint,\n                                        ShadowLayoutContextMeasure* context) {\n  for (auto child : measure_node_->GetChildren()) {\n    if (child->IsInlineTruncationShadowNode()) {\n      auto truncation_node = static_cast<InlineTruncationShadowNode*>(child);\n      if (cache_paragraph_ &&\n          (cache_paragraph_->DidExceedMaxLines() ||\n           (constraint.height_mode != MeasureMode::kIndefinite &&\n            cache_paragraph_->GetHeight() > constraint.height &&\n            cache_paragraph_->GetLineMetrics().size() > 1))) {\n        FloatSize truncation_size = truncation_node->CalculateTruncatedSize();\n        if (truncation_size.width() > constraint.width) {\n          truncation_node->SetNeedMount(false);\n          return;\n        }\n        truncation_node->SetNeedLayout(true);\n        truncation_node->SetNeedMount(true);\n#ifndef CLAY_ENABLE_TTTEXT\n        auto end_dx = truncation_direction_ == TextDirection::kRtl\n                          ? truncation_size.width()\n                          : prev_layout_width_ - truncation_size.width();\n#else\n        auto end_dx = prev_layout_width_ - truncation_size.width();\n#endif\n        auto line_metrics = cache_paragraph_->GetLineMetrics();\n        if (line_metrics.empty()) {\n          truncation_node->SetNeedMount(false);\n          return;\n        }\n        auto last_line_height = line_metrics.back().height;\n        auto end_dy = constraint.height_mode == MeasureMode::kIndefinite\n                          ? cache_paragraph_->GetHeight()\n                          : std::min<double>(cache_paragraph_->GetHeight(),\n                                             constraint.height.value_or(0)) -\n                                last_line_height;\n        auto end_glyph_index =\n            cache_paragraph_->GetGlyphPositionAtCoordinate(end_dx, end_dy);\n        auto end_glyph_boxes = cache_paragraph_->GetRectsForRange(\n            end_glyph_index.position - 1, end_glyph_index.position,\n            txt::Paragraph::RectHeightStyle::kTight,\n            txt::Paragraph::RectWidthStyle::kTight);\n        size_t display_glyph_num = end_glyph_index.position;\n        if (!end_glyph_boxes.empty()) {\n          auto glyph_box = end_glyph_boxes.front();\n          if (glyph_box.rect.Right() >\n              prev_layout_width_ - truncation_size.width()) {\n            display_glyph_num = end_glyph_index.position - 1;\n          }\n        }\n        ProcessTruncationContent(display_glyph_num, measure_node_);\n        measure_node_->text_style_->overflow = TextOverflow::kClip;\n        update_flag_ = TextUpdateFlag::kUpdateFlagStyle;\n        BuildTextLayout(constraint, context);\n        for (auto truncation_child : child->GetChildren()) {\n          if (truncation_child->IsInlineTextShadowNode()) {\n            static_cast<InlineTextShadowNode*>(truncation_child)\n                ->LayoutRange(cache_paragraph_.get());\n          }\n        }\n        truncation_node->UpdateTruncatedSize(truncation_size.width(),\n                                             truncation_size.height());\n        truncation_node->SetNeedLayout(false);\n        return;\n      } else {\n        truncation_node->SetNeedMount(false);\n      }\n      return;\n    }\n  }\n}\n\nvoid TextRender::ProcessTruncationContent(size_t& display_glyph_num,\n                                          ShadowNode* node) {\n  for (auto* child : node->GetChildren()) {\n    if (child->IsRawTextShadowNode()) {\n      auto text = static_cast<RawTextShadowNode*>(child)->Text();\n      if (text.length() < display_glyph_num) {\n        display_glyph_num = display_glyph_num - text.length();\n      } else {\n        static_cast<RawTextShadowNode*>(child)->SetEndIndex(display_glyph_num);\n        measure_node_->MarkNeedsUpdate(TextUpdateFlag::kUpdateFlagChildren);\n        display_glyph_num = 0;\n      }\n    } else if (child->IsInlineTextShadowNode()) {\n      ProcessTruncationContent(display_glyph_num, child);\n    } else if (child->IsInlineImageShadowNode() ||\n               child->IsInlineViewShadowNode()) {\n      if (display_glyph_num > 0) {\n        display_glyph_num--;\n      } else {\n        child->SetEndIndex(0);\n      }\n    }\n  }\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/text_render.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_TEXT_RENDER_H_\n#define CLAY_UI_SHADOW_TEXT_RENDER_H_\n\n#include <limits>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/component/text/unicode_util.h\"\n#include \"clay/ui/shadow/inline_truncation_shadow_node.h\"\n#include \"clay/ui/shadow/shadow_layout_context.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n\nnamespace clay {\n\nclass TextShadowNode;\n\nstruct LineInfo {\n  int start;\n  int end;\n  int ellipsis_count;\n};\n\nclass TextRender {\n public:\n  explicit TextRender(TextShadowNode* measure_node)\n      : measure_node_(measure_node) {}\n  ~TextRender() = default;\n\n  void Measure(const MeasureConstraint& constraint,\n               ShadowLayoutContextMeasure* context);\n  void BuildTextLayout(const MeasureConstraint& constraint,\n                       LayoutContext* context);\n  std::unique_ptr<txt::Paragraph> LayoutParagraph(double layout_width);\n\n  void SetUpdateFlag(TextUpdateFlag flag) { update_flag_ = flag; }\n\n  void AddUpdateFlag(TextUpdateFlag flag) {\n    update_flag_ = static_cast<TextUpdateFlag>(static_cast<int>(update_flag_) |\n                                               static_cast<int>(flag));\n  }\n  txt::Paragraph* GetCacheParagraph() { return cache_paragraph_.get(); }\n  void SetCacheParagraph(std::unique_ptr<txt::Paragraph> paragraph) {\n    cache_paragraph_ = std::move(paragraph);\n  }\n  std::unique_ptr<txt::Paragraph> MoveParagraph() {\n    return std::move(cache_paragraph_);\n  }\n\n  TextAlignment EffectAlign();\n\n#ifndef CLAY_ENABLE_TTTEXT\n  // support vertical-align attribute (only in skia)\n  void ReprocessAttributeIfNeeded(double layout_width);\n#endif\n  std::shared_ptr<txt::Paragraph> LayoutXCharacter(double layout_width,\n                                                   const TextStyle& style);\n  float GetMaxFontSize();\n\n  std::vector<LineInfo> GetLineInfo();\n\n  // support -x-auto-font-size attribute\n  void HandleAutoSize(const MeasureConstraint& constraint,\n                      ShadowLayoutContextMeasure* context);\n  bool CheckTextFullyDisplayed(const MeasureConstraint& constraint,\n                               ShadowLayoutContextMeasure* context_measure);\n  void TryShrinkFontSize(const MeasureConstraint& constraint,\n                         ShadowLayoutContextMeasure* context_measure);\n  void TryExpandFontSize(const MeasureConstraint& constraint,\n                         ShadowLayoutContextMeasure* context_measure);\n  // \"shrink_or_expand = true\" represents text needs to be shrink\n  // \"shrink_or_expand = false\" represents text needs to be expand\n  void FlexInlineFontSize(bool shrink_or_expand, float font_size,\n                          ShadowNode* shadow_node);\n\n  // support inline-truncation\n  void HandleInlineTruncation(const MeasureConstraint& constraint,\n                              ShadowLayoutContextMeasure* context);\n  void ProcessTruncationContent(size_t& display_glyph_num, ShadowNode* node);\n\n  static clay::Value GetTextInfo(const char* text, const clay::Value& params);\n\n  static void MeasureText(const std::string& text, bool show_content,\n                          const std::optional<double>& max_width,\n                          const std::optional<uint32_t>& max_length,\n                          const TextStyle& text_style, float* measured_width,\n                          float* measured_height,\n                          std::vector<std::string>* measured_content);\n\n private:\n  TextShadowNode* measure_node_;\n  float prev_layout_width_ = std::numeric_limits<float>::quiet_NaN();\n  TextUpdateFlag update_flag_ = TextUpdateFlag::kUpdateFlagNone;\n  int measured_width_;\n  int measured_height_;\n  std::unique_ptr<txt::Paragraph> cache_paragraph_;\n  size_t end_glyph_position_ = 0;\n  TextDirection truncation_direction_ = TextDirection::kLtr;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_TEXT_RENDER_H_\n"
  },
  {
    "path": "clay/ui/shadow/text_shadow_node.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/text_shadow_node.h\"\n\n#include <algorithm>\n#include <limits>\n#include <map>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/make_copyable.h\"\n#include \"clay/fml/logging.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/common/isolate.h\"\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/common/value_utils.h\"\n#include \"clay/ui/component/component_constants.h\"\n#include \"clay/ui/component/text/layout_context.h\"\n#include \"clay/ui/component/text/raw_text_view.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/component/text/text_view.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/painter/gradient_factory.h\"\n#include \"clay/ui/rendering/text/render_text.h\"\n#include \"clay/ui/shadow/base_text_shadow_node.h\"\n#include \"clay/ui/shadow/inline_image_shadow_node.h\"\n#include \"clay/ui/shadow/inline_text_shadow_node.h\"\n#include \"clay/ui/shadow/inline_truncation_shadow_node.h\"\n#include \"clay/ui/shadow/measure_utils.h\"\n#include \"clay/ui/shadow/raw_text_shadow_node.h\"\n#include \"clay/ui/shadow/shadow_node.h\"\n#include \"clay/ui/shadow/vertical_align_style.h\"\n#if defined(CLAY_ENABLE_SKSHAPER)\n#include \"clay/third_party/txt/src/skia/paragraph_skia.h\"\n#endif\n#if defined(CLAY_ENABLE_TTTEXT)\n#include \"clay/third_party/txt/src/tttext/paragraph_tt_text.h\"\n#endif\n#include \"base/trace/native/trace_event.h\"\n\nnamespace clay {\n\nnamespace {\n\nstatic constexpr float kDefaultFontSizeInDip = 14.f;\n\n}  // namespace\n\nTextShadowNode::TextShadowNode(ShadowNodeOwner* owner, std::string tag, int id)\n    : BaseTextShadowNode(owner, tag, id) {\n  text_render_ = std::make_unique<TextRender>(this);\n}\n\nTextShadowNode::~TextShadowNode() { text_render_ = nullptr; }\n\nvoid TextShadowNode::OnLayout(float width, TextMeasureMode width_mode,\n                              float height, TextMeasureMode height_mode,\n                              const std::array<float, 4>& paddings,\n                              const std::array<float, 4>& borders) {\n  BaseTextShadowNode::OnLayout(width, width_mode, height, height_mode, paddings,\n                               borders);\n  auto paragraph = text_render_->GetCacheParagraph();\n  if (!paragraph) {\n    return;\n  }\n  if (HasEvent(event_attr::kEventLayout)) {\n    std::vector<LineInfo> lines_info = text_render_->GetLineInfo();\n    auto lines_size = lines_info.size();\n    clay::Value::Array array(lines_size);\n    for (size_t i = 0; i < lines_size; i++) {\n      auto map = CreateClayMap({\"ellipsisCount\", \"end\", \"start\"},\n                               lines_info[i].ellipsis_count, lines_info[i].end,\n                               lines_info[i].start);\n      array[i] = clay::Value(std::move(map));\n    }\n    clay::Value::Map map;\n    map[\"lineCount\"] = clay::Value(static_cast<int>(lines_size));\n    map[\"lines\"] = clay::Value(std::move(array));\n    map[\"size\"] = clay::Value(CreateClayMap(\n        {\"width\", \"height\"}, paragraph->GetMaxWidth(), paragraph->GetHeight()));\n    owner_->GetViewContext()->GetPageView()->SendCustomEvent(\n        id(), event_attr::kEventLayout, std::move(map));\n  }\n\n  UpdateBundleData();\n}\n\nvoid TextShadowNode::MarkDirty() {\n  DidUpdateStyle();\n  ShadowNode::MarkDirty();\n}\n\nvoid TextShadowNode::UpdateBundleData() {\n  if (!owner_ || !text_render_->GetCacheParagraph()) {\n    return;\n  }\n\n  if (!bundle_) {\n    CreateTextBundle();\n  }\n\n  auto paragraph = text_render_->GetCacheParagraph();\n  // Traverse the tree structure to create textinfo\n  CreateTextInfo(paragraph);\n\n  auto bundle = static_cast<TextUpdateBundle*>(bundle_.get());\n  bundle->SetParagraph(text_render_->MoveParagraph());\n  bundle->SetText(GetRawText());\n  bundle->SetTextPaintAlign(text_paint_align_);\n  if (text_style_) {\n    std::map<int, std::shared_ptr<ColorSource>> gradient_shader_map;\n    std::map<int, std::pair<size_t, size_t>> gradient_shader_range_map;\n    CreateGradientShaderMap(this, paragraph, gradient_shader_map,\n                            gradient_shader_range_map);\n    if (gradient_shader_map.size() > 0) {\n      bundle->SetGradientShaderMap(gradient_shader_map,\n                                   gradient_shader_range_map);\n    }\n    std::unordered_map<int, TextStroke> text_stroke_map;\n    CreateTextStrokeMap(this, text_stroke_map);\n    if (text_stroke_map.size() > 0) {\n      bundle->SetTextStrokeMap(text_stroke_map);\n    }\n  }\n}\n\nvoid TextShadowNode::CreateGradientShaderMap(\n    ShadowNode* node, txt::Paragraph* paragraph,\n    std::map<int, std::shared_ptr<ColorSource>>& map,\n    std::map<int, std::pair<size_t, size_t>>& range_map) {\n  if (!paragraph || !node) {\n    return;\n  }\n  if (node->IsTextShadowNode()) {\n    auto text_node = static_cast<TextShadowNode*>(node);\n    if (text_node->text_style_->text_gradient &&\n        text_node->text_style_->foreground_id.has_value()) {\n      auto gradient_shader = GradientFactory::CreateShader(\n          text_node->text_style_->text_gradient.value(),\n          FloatRect(0, 0, paragraph->GetLongestLine(), paragraph->GetHeight()));\n      map[text_node->text_style_->foreground_id.value()] = gradient_shader;\n      size_t text_length = 0;\n#if defined(CLAY_ENABLE_TTTEXT)\n      auto* impl = static_cast<txt::ParagraphTTText*>(paragraph);\n      text_length = impl->GetTextSize();\n#endif\n      range_map.emplace(text_node->text_style_->foreground_id.value(),\n                        std::pair{0, text_length});\n    }\n  }\n  if (node->IsInlineTextShadowNode()) {\n    auto inline_text_node = static_cast<InlineTextShadowNode*>(node);\n    if (inline_text_node->text_style_ &&\n        inline_text_node->text_style_->text_gradient &&\n        inline_text_node->text_style_->foreground_id.has_value()) {\n      auto boxes = paragraph->GetRectsForRange(\n          inline_text_node->range_in_paragraph_.front().start(),\n          inline_text_node->range_in_paragraph_.back().end(),\n          RectHeightStyle::kTight, RectWidthStyle::kTight);\n      if (!boxes.empty()) {\n        auto res_rect = boxes.front().rect;\n        for (auto box : boxes) {\n          res_rect.Join(box.rect);\n        }\n        auto gradient_shader = GradientFactory::CreateShader(\n            inline_text_node->text_style_->text_gradient.value(),\n            FloatRect(res_rect));\n        map[inline_text_node->text_style_->foreground_id.value()] =\n            gradient_shader;\n        range_map.emplace(\n            inline_text_node->text_style_->foreground_id.value(),\n            std::pair{\n                inline_text_node->range_in_paragraph_.front().start(),\n                inline_text_node->range_in_paragraph_.back().end(),\n            });\n      }\n    }\n  }\n  if (node->IsInlineViewShadowNode()) {\n    return;\n  }\n  for (auto* child : node->GetChildren()) {\n    CreateGradientShaderMap(child, paragraph, map, range_map);\n  }\n}\n\nvoid TextShadowNode::CreateTextStrokeMap(\n    ShadowNode* node, std::unordered_map<int, TextStroke>& map) {\n  if (node->IsBaseTextShadowNode()) {\n    auto text_node = static_cast<BaseTextShadowNode*>(node);\n    if ((text_node->stroke_color_ || text_node->stroke_width_ > 0) &&\n        text_node->text_style_->foreground_id.has_value()) {\n      TextStroke text_stroke;\n      text_stroke.fill_color =\n          text_node->text_style_->text_color.value_or(Color(0xFF000000));\n      text_stroke.stroke_color =\n          text_node->stroke_color_.value_or(text_stroke.fill_color);\n      text_stroke.width = text_node->stroke_width_;\n      map[text_node->text_style_->foreground_id.value()] = text_stroke;\n    }\n  }\n  for (auto* child : node->GetChildren()) {\n    CreateTextStrokeMap(child, map);\n  }\n}\n\nvoid TextShadowNode::ProcessParagraph(\n    const MeasureConstraint& constraint,\n    ShadowLayoutContextMeasure* context_measure, txt::Paragraph* paragraph) {\n  LayoutInlineText(paragraph);\n\n  if (context_measure && owner_) {\n    auto line_metrics = paragraph->GetLineMetrics();\n#ifndef CLAY_ENABLE_TTTEXT\n    auto line_spacing = std::max(\n        text_style_->line_spacing.value_or(0) -\n            (line_metrics.size() > 0\n                 ? line_metrics.begin()->ascent - line_metrics.begin()->descent\n                 : 0),\n        0.0);\n    static_cast<TextUpdateBundle*>(bundle_.get())\n        ->SetLineSpacingOffset(-line_spacing / 2);\n#endif\n  }\n}\n\n// Collect the inline-image views in this phase.\nvoid TextShadowNode::PreLayout(PreLayoutContext* context) {\n  PreShadowLayoutContextText context_text;\n  BaseTextShadowNode::PreLayout(&context_text);\n  inline_images_ = context_text.TakeInlineImages();\n  inline_views_ = context_text.TakeInlineViews();\n}\n\nvoid TextShadowNode::TextLayout(LayoutContext* context) {\n  BaseTextShadowNode::TextLayout(context);\n}\n\nvoid TextShadowNode::LayoutInlineText(txt::Paragraph* paragraph) {\n  for (auto child : children_) {\n    if (child->IsInlineTextShadowNode() && owner_) {\n      static_cast<InlineTextShadowNode*>(child)->LayoutRange(paragraph);\n    }\n  }\n}\n\nMeasureResult TextShadowNode::Measure(const MeasureConstraint& constraint) {\n  MeasureResult result;\n  TRACE_EVENT(\"clay\", \"TextShadowNode::Measure\");\n  CreateTextBundle();\n  if (!constraint.IsValid()) {\n    // Only logged on debug, because it may be very frequent.\n    // And it's normal when MeasureConstraint value is zero.\n    FML_DLOG(WARNING) << \"Invalid measure metrics.\";\n    result.width = 0;\n    result.height = 0;\n    return result;\n  }\n  width_mode_ = constraint.width_mode;\n  auto context = CreateLayoutContext(constraint);\n\n#ifndef CLAY_ENABLE_TTTEXT\n  UpdateLineHeight();\n#endif\n\n  // inline view measure\n  MeasureInlineView(constraint);\n\n  ResetEndIndex();\n  text_render_->Measure(constraint, &context);\n\n  if (auto paragraph = text_render_->GetCacheParagraph()) {\n    ProcessParagraph(constraint, &context, paragraph);\n  }\n\n  if (constraint.width_mode == TextMeasureMode::kDefinite) {\n    result.width = *constraint.width;\n  } else if (constraint.width_mode == TextMeasureMode::kIndefinite) {\n    result.width = std::ceil(context.measured_width_);\n  } else {\n    result.width =\n        std::min(std::ceil(static_cast<float>(context.measured_width_)),\n                 *constraint.width);\n  }\n\n  auto desired_height = context.measured_height_;\n  // if TextMeasureMode is kAtMost and the desired height triggers\n  // flex shrink, lynx will calculate the ratio of every flex element\n  // and relayout the paragraph by invoking a new Measure while\n  // constraint.width_mode == TextMeasureMode::kDefinite\n  switch (constraint.height_mode) {\n    case TextMeasureMode::kAtMost:\n    case TextMeasureMode::kIndefinite: {\n      result.height = desired_height;\n      break;\n    }\n    case TextMeasureMode::kDefinite: {\n      result.height = *constraint.height;\n      break;\n    }\n  }\n\n  if (need_second_layout_) {\n    need_second_layout_ = false;\n    auto constraint =\n        MeasureConstraint({result.width, TextMeasureMode::kDefinite,\n                           result.height, TextMeasureMode::kDefinite});\n    context = CreateLayoutContext(constraint);\n    text_render_->Measure(constraint, &context);\n  }\n  return result;\n}\n\nShadowLayoutContextMeasure TextShadowNode::CreateLayoutContext(\n    const MeasureConstraint& constraint) {\n  ShadowLayoutContextMeasure context;\n  switch (constraint.width_mode) {\n    case TextMeasureMode::kIndefinite:\n      context.layout_width_ = std::numeric_limits<float>::infinity();\n      break;\n    case TextMeasureMode::kAtMost:\n    case TextMeasureMode::kDefinite:\n      context.layout_width_ = *constraint.width;\n      break;\n  }\n  return context;\n}\n\nvoid TextShadowNode::DidUpdateStyle() {\n  MarkNeedsUpdate(TextUpdateFlag::kUpdateFlagStyle);\n}\n\nvoid TextShadowNode::Align() {\n  if (auto paragraph = text_render_->GetCacheParagraph()) {\n    AlignNativeNode(paragraph);\n  }\n}\n\nvoid TextShadowNode::UpdateLineHeight() {\n  EnsureDefaultStyle();\n  float line_height = 0.f;\n  float font_size = text_style_->font_size.value_or(kDefaultFontSizeInDip *\n                                                    Logical2ClayPixelRatio());\n  CollectMaxLineHeight(line_height, font_size);\n  if (line_height > 0) {\n    text_style_->line_height = line_height / font_size;\n  }\n}\n\nvoid TextShadowNode::SetUpdateFlag(TextUpdateFlag flag) {\n  if (text_render_) {\n    text_render_->SetUpdateFlag(flag);\n  }\n}\n\nvoid TextShadowNode::AddUpdateFlag(TextUpdateFlag flag) {\n  if (text_render_) {\n    text_render_->AddUpdateFlag(flag);\n  }\n}\n\ndouble TextShadowNode::GetBaseline() const {\n  if (text_render_ && text_render_->GetCacheParagraph()) {\n    return text_render_->GetCacheParagraph()->GetAlphabeticBaseline();\n  }\n  return 0;\n}\n\ntxt::Paragraph* TextShadowNode::GetCacheParagraph() {\n  if (text_render_) {\n    return std::move(text_render_->GetCacheParagraph());\n  } else {\n    return nullptr;\n  }\n}\nvoid TextShadowNode::SetCacheParagraph(\n    std::unique_ptr<txt::Paragraph> paragraph) {\n  if (text_render_) {\n    text_render_->SetCacheParagraph(std::move(paragraph));\n  }\n}\n\nTextAlignment TextShadowNode::GetResolvedTextAlign() {\n  auto align = text_style_->text_align.value_or(TextAlignment::kStart);\n  auto direction = text_style_->text_direction.value_or(TextDirection::kLtr);\n  if (align == TextAlignment::kStart) {\n    if (direction == TextDirection::kRtl) {\n      return TextAlignment::kRight;\n    } else {\n      return TextAlignment::kLeft;\n    }\n  } else if (align == TextAlignment::kEnd) {\n    if (direction == TextDirection::kRtl) {\n      return TextAlignment::kLeft;\n    } else {\n      return TextAlignment::kRight;\n    }\n  }\n  return align;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/text_shadow_node.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_TEXT_SHADOW_NODE_H_\n#define CLAY_UI_SHADOW_TEXT_SHADOW_NODE_H_\n\n#include <map>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/style/color_source.h\"\n#include \"clay/third_party/txt/src/txt/paragraph.h\"\n#include \"clay/ui/component/measurable.h\"\n#include \"clay/ui/component/text/layout_client.h\"\n#include \"clay/ui/component/text/text_style.h\"\n#include \"clay/ui/shadow/base_text_shadow_node.h\"\n#include \"clay/ui/shadow/inline_image_shadow_node.h\"\n#include \"clay/ui/shadow/shadow_layout_context.h\"\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n#include \"clay/ui/shadow/text_render.h\"\n#include \"clay/ui/shadow/text_update_bundle.h\"\n\nnamespace clay {\n\nstruct TextStroke;\n\nclass TextShadowNode : public BaseTextShadowNode, public CustomMeasurable {\n public:\n  TextShadowNode(ShadowNodeOwner* owner, std::string tag, int id);\n  ~TextShadowNode() override;\n\n  bool IsTextShadowNode() override { return true; }\n  CustomMeasurable* GetCustomMeasurable() override { return this; }\n\n  void OnLayout(float width, TextMeasureMode width_mode, float height,\n                TextMeasureMode height_mode,\n                const std::array<float, 4>& paddings,\n                const std::array<float, 4>& borders) override;\n\n  MeasureResult Measure(const MeasureConstraint& constraint) override;\n\n  ShadowLayoutContextMeasure CreateLayoutContext(\n      const MeasureConstraint& constraint);\n\n  void TextLayout(LayoutContext* context) override;\n\n  void PreLayout(PreLayoutContext* context) override;\n\n  void LayoutInlineText(txt::Paragraph* paragraph);\n\n  void DidUpdateStyle();\n\n  void ProcessParagraph(const MeasureConstraint& constraint,\n                        ShadowLayoutContextMeasure* context_measure,\n                        txt::Paragraph* paragraph);\n\n  void MarkDirty() override;\n\n  BaseView* FindViewByViewId(int id) { return owner_->FindViewByViewId(id); }\n\n  void Align() override;\n\n  // Only one line-height can take effect for a paragraph. So any line-height\n  // set on inline-text-view are not reasonable. But for compatible reason,\n  // we allow developer to set line-height on inline-text-view, and the final\n  // line-height will be the maximum one.\n  void UpdateLineHeight();\n\n  void UpdateBundleData();\n\n  void SetUpdateFlag(TextUpdateFlag flag);\n\n  void AddUpdateFlag(TextUpdateFlag flag);\n\n  double GetBaseline() const;\n\n  void CreateGradientShaderMap(\n      ShadowNode* node, txt::Paragraph* paragraph,\n      std::map<int, std::shared_ptr<ColorSource>>& map,\n      std::map<int, std::pair<size_t, size_t>>& range_map);\n\n  void CreateTextStrokeMap(ShadowNode* node,\n                           std::unordered_map<int, TextStroke>& map);\n\n  ShadowNodeOwner* GetOwner() { return owner_; }\n\n  void SetTextPaintAlign(TextAlignment align) { text_paint_align_ = align; }\n  TextAlignment GetResolvedTextAlign();\n  void SetNeedSecondLayout(bool need_second_layout) {\n    need_second_layout_ = need_second_layout;\n  }\n\n  void CreateTextBundle() { bundle_ = std::make_unique<TextUpdateBundle>(); }\n  Bundle* GetTextBundle() { return bundle_.get(); }\n\n protected:\n  txt::Paragraph* GetCacheParagraph();\n  void SetCacheParagraph(std::unique_ptr<txt::Paragraph> paragraph);\n\n private:\n  std::unique_ptr<TextRender> text_render_ = nullptr;\n  bool need_second_layout_ = false;\n  std::vector<InlineImageShadowNode*> inline_images_;\n  std::vector<InlineViewShadowNode*> inline_views_;\n  TextAlignment text_paint_align_ = TextAlignment::kLeft;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_TEXT_SHADOW_NODE_H_\n"
  },
  {
    "path": "clay/ui/shadow/text_unittests.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <string>\n\n#include \"clay/ui/common/measure_constraint.h\"\n#include \"clay/ui/component/text/raw_text_view.h\"\n#include \"clay/ui/shadow/inline_text_shadow_node.h\"\n#include \"clay/ui/shadow/inline_truncation_shadow_node.h\"\n#include \"clay/ui/shadow/raw_text_shadow_node.h\"\n#include \"clay/ui/shadow/shadow_node_owner.h\"\n#include \"clay/ui/shadow/text_shadow_node.h\"\n#include \"clay/ui/testing/ui_test.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nclass TextTest : public UITest {\n protected:\n  void UISetUp() override {\n    owner_ =\n        new ShadowNodeOwner(fml::MessageLoop::GetCurrent().GetTaskRunner());\n    text_shadow_node_ =\n        std::make_unique<TextShadowNode>(owner_, std::string(\"text\"), -1);\n    raw_text_shadow_node_ = std::make_unique<RawTextShadowNode>(\n        owner_, std::string(\"raw_text\"), -1);\n    inline_text_shadow_node_ = std::make_unique<InlineTextShadowNode>(\n        owner_, std::string(\"inline-text\"), -1);\n    text_shadow_node_->AddChild(raw_text_shadow_node_.get());\n    text_shadow_node_->AddChild(inline_text_shadow_node_.get());\n    text_shadow_node_->SetFontSize(42);\n  }\n  void UITearDown() override {\n    text_shadow_node_.reset();\n    raw_text_shadow_node_.reset();\n    inline_text_shadow_node_.reset();\n    delete owner_;\n  }\n  ShadowNodeOwner* owner_;\n  std::unique_ptr<TextShadowNode> text_shadow_node_;\n  std::unique_ptr<InlineTextShadowNode> inline_text_shadow_node_;\n  std::unique_ptr<RawTextShadowNode> raw_text_shadow_node_;\n};\n\nTEST_F_UI(TextTest, AutoFontSizeStepGranularity) {\n  MeasureConstraint constraint{1000, MeasureMode::kDefinite, 100,\n                               MeasureMode::kDefinite};\n  text_shadow_node_->enable_auto_font_size_ = true;\n  text_shadow_node_->auto_font_size_max_size_ = 50;\n  text_shadow_node_->auto_font_size_min_size_ = 30;\n  text_shadow_node_->auto_font_size_step_granularity_ = 3;\n  std::string text = std::string(\n      \"Hello, Compiler NG Hello, Compiler NG Hello, Compiler NG Hello, \"\n      \"Compiler NG Hello, Compiler NG Hello, Compiler NG \");\n  raw_text_shadow_node_->SetText(text);\n  text_shadow_node_->Measure(constraint);\n  auto font_size = text_shadow_node_->text_style_->font_size;\n  EXPECT_NE(font_size, 42);\n  constraint.width = 4000;\n  constraint.height = 1000;\n  text_shadow_node_->Measure(constraint);\n  EXPECT_NE(text_shadow_node_->text_style_->font_size, font_size);\n}\n\nTEST_F_UI(TextTest, DISABLED_AutoFontSizePreset) {\n  MeasureConstraint constraint{1000, MeasureMode::kDefinite, 100,\n                               MeasureMode::kDefinite};\n  text_shadow_node_->enable_auto_font_size_ = true;\n  text_shadow_node_->auto_font_size_max_size_ = 50;\n  text_shadow_node_->auto_font_size_min_size_ = 30;\n  text_shadow_node_->auto_font_size_step_granularity_ = 3;\n  text_shadow_node_->auto_font_size_preset_sizes_ = std::vector<double>{40, 45};\n  std::string text = std::string(\n      \"Hello, Compiler NG Hello, Compiler NG Hello, Compiler NG Hello, \"\n      \"Compiler NG Hello, Compiler NG Hello, Compiler NG \");\n  raw_text_shadow_node_->SetText(text);\n  text_shadow_node_->Measure(constraint);\n  auto font_size = text_shadow_node_->text_style_->font_size;\n  EXPECT_EQ(font_size, 40);\n}\n\nTEST_F_UI(TextTest, DISABLED_InlineTruncation) {\n  MeasureConstraint constraint{1080, MeasureMode::kDefinite, 100,\n                               MeasureMode::kDefinite};\n  auto inline_truncation_node = std::make_unique<InlineTruncationShadowNode>(\n      owner_, std::string(\"inline-truncation\"), -1);\n  auto inline_text_node = std::make_unique<InlineTextShadowNode>(\n      owner_, std::string(\"inline-text\"), -1);\n  auto inline_raw_text_shadow_node =\n      std::make_unique<RawTextShadowNode>(owner_, std::string(\"raw-text\"), -1);\n  inline_raw_text_shadow_node->SetText(\"extend\");\n  inline_text_node->AddChild(inline_raw_text_shadow_node.get());\n  inline_truncation_node->AddChild(inline_text_node.get());\n  text_shadow_node_->AddChild(inline_truncation_node.get());\n  text_shadow_node_->SetTextMaxLine(1);\n  std::string text = std::string(\n      \"This is a test text. We will use this text to test the function of \"\n      \"inline-truncation. If this text exceeds the limited width, it will be \"\n      \"truncated.\");\n  raw_text_shadow_node_->SetText(text);\n  text_shadow_node_->Measure(constraint);\n  int truncation_text_size = text_shadow_node_->GetRawText().size();\n  EXPECT_NE(truncation_text_size, 147);\n}\n\nTEST_F_UI(TextTest, VerticalAlign) {\n  MeasureConstraint constraint{1000, MeasureMode::kDefinite, 100,\n                               MeasureMode::kDefinite};\n  inline_text_shadow_node_->SetVerticalAlign(\n      VerticalAlignType::kVerticalAlignLength, 20);\n  std::string text = std::string(\"test\");\n  raw_text_shadow_node_->SetText(text);\n  text_shadow_node_->Measure(constraint);\n  auto baseline_shift = inline_text_shadow_node_->text_style_->baseline_shift;\n  EXPECT_EQ(baseline_shift, 20);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/text_update_bundle.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/shadow/text_update_bundle.h\"\n\n#include \"clay/ui/component/text/inline_text_view.h\"\n#include \"clay/ui/component/text/text_view.h\"\n#include \"clay/ui/rendering/text/render_inline_text.h\"\n\nnamespace clay {\n\nTextUpdateBundle::~TextUpdateBundle() {}\n\nvoid TextUpdateBundle::UpdateExtraData(BaseView* view) {\n  if (view && view->Is<TextView>() && paragraph_) {\n    auto text_view = static_cast<TextView*>(view);\n    for (auto info : info_) {\n      auto info_view = text_view->page_view()->FindViewByViewId(info.id);\n      auto parent_view =\n          text_view->page_view()->FindViewByViewId(info.parent_id);\n      if (!info_view || !parent_view) {\n        break;\n      }\n      if (info.need_mount) {\n        if (info.placeholder_index.value_or(-1) >= 0) {\n          if (info_view->Is<InlineImageView>()) {\n            text_view->PushInlineImageIndex(info.id,\n                                            info.placeholder_index.value());\n            static_cast<InlineImageView*>(info_view)->SetLocation(\n                info.location.value());\n          } else {\n            text_view->PushInlineViewIndex(info.id,\n                                           info.placeholder_index.value());\n          }\n        }\n\n        if (info.view_style) {\n          info_view->SetWidth(info.view_style->width);\n          info_view->SetHeight(info.view_style->height);\n          info_view->SetPaddings(\n              info.view_style->padding_left, info.view_style->padding_top,\n              info.view_style->padding_right, info.view_style->padding_bottom);\n        }\n        if (info.range_) {\n          if (info_view->Is<InlineTextView>()) {\n            auto render_object = info_view->render_object();\n            static_cast<RenderInlineText*>(render_object)->ClearTextBox();\n            for (auto range : info.range_.value()) {\n              auto boxes = paragraph_->GetRectsForRange(\n                  range.start(), range.end(),\n                  txt::Paragraph::RectHeightStyle::kTight,\n                  txt::Paragraph::RectWidthStyle::kTight);\n              for (auto& box : boxes) {\n                static_cast<RenderInlineText*>(render_object)\n                    ->AddTextBox(box.rect);\n              }\n            }\n            static_cast<InlineTextView*>(info_view)->SetTextRange(\n                info.range_.value());\n          }\n        }\n        if (!info_view->Parent() && info.id > 0) {\n          parent_view->AddChild(info_view);\n        }\n      } else {\n        parent_view->RemoveChild(info_view);\n      }\n    }\n    text_view->UpdateInlineImageInfo();\n    text_view->SetParagraph(std::move(paragraph_), text_);\n    text_view->GetRenderText()->SetTextStrokeMap(std::move(text_stroke_map_));\n    text_view->GetRenderText()->SetGradientShaderMap(\n        std::move(gradient_shader_map_), std::move(range_map_));\n    text_view->GetRenderText()->SetTextPaintAlign(text_paint_align_);\n    text_view->GetRenderText()->SetLineSpacingOffset(line_spacing_offset_);\n  }\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/shadow/text_update_bundle.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_TEXT_UPDATE_BUNDLE_H_\n#define CLAY_UI_SHADOW_TEXT_UPDATE_BUNDLE_H_\n\n#include <list>\n#include <map>\n#include <memory>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/public/style_types.h\"\n#include \"clay/ui/component/base_view.h\"\n#include \"clay/ui/component/text/text_view.h\"\n#include \"clay/ui/shadow/bundle.h\"\nnamespace clay {\nstruct TextInfo {\n  int id = -1;\n  int parent_id = -1;\n  bool need_mount = true;\n  // for inline_image/inline_view/inline_truncation\n  std::optional<int> placeholder_index;\n  // for inline_image/inline_text/inline_truncation\n  std::optional<ClayLayoutStyles> view_style;\n  // for inline_image\n  std::optional<FloatPoint> location;\n  // for inline_text\n  std::optional<std::list<TextRange>> range_;\n};\n\nclass TextUpdateBundle : public Bundle {\n public:\n  TextUpdateBundle() = default;\n  ~TextUpdateBundle();\n  void SetParagraph(std::unique_ptr<txt::Paragraph> paragraph) {\n    paragraph_ = std::move(paragraph);\n  }\n  void SetTextPaintAlign(TextAlignment align) { text_paint_align_ = align; }\n  void SetText(std::u16string text) { text_ = text; }\n  void SetGradientShaderMap(\n      std::map<int, std::shared_ptr<ColorSource>>& gradient_shader_map,\n      std::map<int, std::pair<size_t, size_t>>& range_map) {\n    gradient_shader_map_ = std::move(gradient_shader_map);\n    range_map_ = std::move(range_map);\n  }\n  void SetTextStrokeMap(std::unordered_map<int, TextStroke>& text_stroke_map) {\n    text_stroke_map_ = std::move(text_stroke_map);\n  }\n  void SetLineSpacingOffset(double offset) { line_spacing_offset_ = offset; }\n  void PushTextInfo(TextInfo& info) { info_.emplace_back(info); }\n  void UpdateExtraData(BaseView* view) override;\n\n private:\n  std::unique_ptr<txt::Paragraph> paragraph_ = nullptr;\n  TextAlignment text_paint_align_;\n  std::u16string text_;\n  double line_spacing_offset_;\n  std::map<int, std::shared_ptr<ColorSource>> gradient_shader_map_;\n  std::map<int, std::pair<size_t, size_t>> range_map_;\n  std::unordered_map<int, TextStroke> text_stroke_map_;\n  std::vector<TextInfo> info_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_TEXT_UPDATE_BUNDLE_H_\n"
  },
  {
    "path": "clay/ui/shadow/vertical_align_style.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_SHADOW_VERTICAL_ALIGN_STYLE_H_\n#define CLAY_UI_SHADOW_VERTICAL_ALIGN_STYLE_H_\n\nnamespace clay {\n\nenum VerticalAlignType {\n  kVerticalAlignDefault = 0,\n  kVerticalAlignBaseline = 1,\n  kVerticalAlignSub = 2,\n  kVerticalAlignSuper = 3,\n  kVerticalAlignTop = 4,\n  kVerticalAlignTextTop = 5,\n  kVerticalAlignMiddle = 6,\n  kVerticalAlignBottom = 7,\n  kVerticalAlignTextBottom = 8,\n  kVerticalAlignLength = 9,\n  kVerticalAlignPercent = 10,\n  kVerticalAlignCenter = 11,\n};\n\nstruct FontMetrics {\n  double ascent = 0.f;\n  double descent = 0.f;\n  float x_height = 0.f;\n  float line_height = 0.f;\n  float top = 0.f;\n  float bottom = 0.f;\n  float glyph_top = 0.f;\n  float glyph_bottom = 0.f;\n};\n\nstruct VerticalAlign {\n  VerticalAlignType type;\n  float length;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_SHADOW_VERTICAL_ALIGN_STYLE_H_\n"
  },
  {
    "path": "clay/ui/testing/test_utils.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/testing/test_utils.h\"\n\n#include <cstring>\n#include <utility>\n\nnamespace clay {\n\nbool operator==(const clay::Value& a, const clay::Value& b) {\n  if (a.type() != b.type()) {\n    return false;\n  }\n  switch (a.type()) {\n    case clay::Value::kNone:\n      return true;\n    case clay::Value::kBool:\n      return a.GetBool() == b.GetBool();\n    case clay::Value::kInt:\n      return a.GetInt() == b.GetInt();\n    case clay::Value::kUInt:\n      return a.GetUint() == b.GetUint();\n    case clay::Value::kLong:\n      return a.GetLong() == b.GetLong();\n    case clay::Value::kFloat:\n      return a.GetFloat() == b.GetFloat();\n    case clay::Value::kDouble:\n      return a.GetDouble() == b.GetDouble();\n    case clay::Value::kString:\n      return a.GetString() == b.GetString();\n    case clay::Value::kPointer:\n      return a.GetPointer() == b.GetPointer();\n    case clay::Value::kArray: {\n      if (a.GetArray().size() != b.GetArray().size()) {\n        return false;\n      }\n      for (size_t i = 0; i < a.GetArray().size(); i++) {\n        if (!(a.GetArray()[i] == b.GetArray()[i])) {\n          return false;\n        }\n      }\n      return true;\n    }\n    case clay::Value::kArrayBuffer:\n      return &a.GetArrayBuffer() == &b.GetArrayBuffer();\n    case clay::Value::kMap:\n      return &a.GetMap() == &b.GetMap();\n  }\n}\n\nbool operator!=(const Value& a, const Value& b) { return !(a == b); }\n\nstd::ostream& operator<<(std::ostream& stream, const FloatPoint& p) {\n  return stream << \"(\" << p.x() << \", \" << p.y() << \")\";\n}\n\nstd::ostream& operator<<(std::ostream& stream, const FloatRect& r) {\n  return stream << \"(\" << r.x() << \", \" << r.y() << \", \" << r.width() << \", \"\n                << r.height() << \")\";\n}\n\nstd::ostream& operator<<(std::ostream& stream, const clay::Value& v) {\n  switch (v.type()) {\n    case clay::Value::kNone:\n      stream << \"<none>\";\n      break;\n    case clay::Value::kBool:\n      stream << (v.GetBool() ? \"true\" : \"false\");\n      break;\n    case clay::Value::kInt:\n      stream << \"(int) \" << v.GetInt();\n      break;\n    case clay::Value::kUInt:\n      stream << \"(uint) \" << v.GetUint();\n      break;\n    case clay::Value::kLong:\n      stream << \"(int64) \" << v.GetLong();\n      break;\n    case clay::Value::kFloat:\n      stream << \"(float) \" << v.GetFloat();\n      break;\n    case clay::Value::kDouble:\n      stream << \"(double) \" << v.GetDouble();\n      break;\n    case clay::Value::kString:\n      stream << '\"' << v.GetString() << '\"';\n      break;\n    case clay::Value::kPointer:\n      stream << \"<\" << v.GetPointerType() << \": \" << v.GetPointer() << \">\";\n      break;\n    case clay::Value::kArray:\n      stream << \"[\";\n      for (size_t i = 0; i < v.GetArray().size(); i++) {\n        if (i > 0) {\n          stream << \", \";\n        }\n        stream << v.GetArray()[i];\n      }\n      stream << \"]\";\n      break;\n    case clay::Value::kArrayBuffer:\n      stream << \"<ArrayBuffer: \" << v.GetArrayBuffer().data() << \"|\"\n             << v.GetArrayBuffer().size() << \">\";\n      break;\n    case clay::Value::kMap:\n      stream << \"{\";\n      const auto& map = v.GetMap();\n      bool first = true;\n      for (const auto& pair : map) {\n        if (!first) {\n          stream << \", \";\n        }\n        first = false;\n        stream << pair.first << \": \" << pair.second;\n      }\n      stream << \"}\";\n      break;\n  }\n  return stream;\n}\n\nLynxModuleValues CreateLynxModuleValues(std::vector<std::string>&& names,\n                                        std::initializer_list<Value>&& values) {\n  LynxModuleValues value;\n  value.names = std::move(names);\n  for (auto& item : values) {\n    value.values.emplace_back(std::move(const_cast<Value&>(item)));\n  }\n  return value;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/testing/test_utils.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_TESTING_TEST_UTILS_H_\n#define CLAY_UI_TESTING_TEST_UTILS_H_\n\n#include <ostream>\n#include <string>\n#include <vector>\n\n#include \"clay/gfx/geometry/float_point.h\"\n#include \"clay/gfx/geometry/float_rect.h\"\n#include \"clay/public/value.h\"\n#include \"clay/ui/lynx_module/types.h\"\n\nnamespace clay {\n\nbool operator==(const clay::Value& a, const clay::Value& b);\nbool operator!=(const clay::Value& a, const clay::Value& b);\n\n// Utility functions for better printing values when assertion failure\nstd::ostream& operator<<(std::ostream& stream, const FloatPoint& p);\nstd::ostream& operator<<(std::ostream& stream, const FloatRect& r);\nstd::ostream& operator<<(std::ostream& stream, const clay::Value& v);\n\nLynxModuleValues CreateLynxModuleValues(\n    std::vector<std::string>&& names,\n    std::initializer_list<clay::Value>&& values);\n\n}  // namespace clay\n\n#endif  // CLAY_UI_TESTING_TEST_UTILS_H_\n"
  },
  {
    "path": "clay/ui/testing/test_utils_unittests.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/testing/test_utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\n\nTEST(TestUtilsTest, ValueEquals) {\n  Value a(1);\n  Value b(1);\n  Value c(2);\n  Value d(1.0);\n  EXPECT_EQ(a, b);\n  EXPECT_NE(a, c);\n  EXPECT_NE(a, d);\n\n  Value arr1{Value(1), Value(2)};\n  Value arr2{Value(1), Value(2)};\n  Value arr3{Value(2), Value(1)};\n  EXPECT_EQ(arr1, arr2);\n  EXPECT_NE(arr1, arr3);\n}\n\nTEST(TestUtilsTest, ValuePrint) {\n  Value a(1);\n  Value b(1.0);\n  Value c(std::string(\"abc\"));\n  Value d(false);\n  Value e{Value(1), Value(2)};\n  Value f{{\"a\", Value(true)}};\n\n  EXPECT_EQ(::testing::PrintToString(a), \"(int) 1\");\n  EXPECT_EQ(::testing::PrintToString(b), \"(double) 1\");\n  EXPECT_EQ(::testing::PrintToString(c), \"\\\"abc\\\"\");\n  EXPECT_EQ(::testing::PrintToString(d), \"false\");\n  EXPECT_EQ(::testing::PrintToString(e), \"[(int) 1, (int) 2]\");\n  EXPECT_EQ(::testing::PrintToString(f), \"{a: true}\");\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/testing/ui_test.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/testing/ui_test.h\"\n\n#include \"clay/gfx/testing_utils.h\"\n#include \"clay/ui/component/view_context.h\"\n#include \"clay/ui/resource/font_collection.h\"\n\nnamespace clay {\n\nclass MockDelegate : public clay::RenderDelegate {\n public:\n  void ScheduleFrame() override {}\n  void ForceBeginFrame() override {}\n  void OnFirstMeaningfulLayout() override {}\n\n  bool Raster(std::unique_ptr<clay::LayerTree> layer_tree,\n              std::unique_ptr<clay::FrameTimingsRecorder> recorder,\n              bool force) override {\n    return false;\n  }\n  void ShowSoftInput(int type, int action) override {}\n  void HideSoftInput() override {}\n  std::string ShouldInterceptUrl(const std::string& origin_url,\n                                 bool should_decode) override {\n    return \"\";\n  }\n\n  void MakeRasterSnapshot(\n      std::unique_ptr<LayerTree> layer_tree,\n      std::function<void(fml::RefPtr<DlImage>)> callback) override {}\n  fml::RefPtr<PaintImage> MakeRasterSnapshot(\n      sk_sp<SkPicture> picture, skity::Vec2 picture_size) override {\n    return nullptr;\n  }\n  void SetClipboardData(const std::u16string& data) override {}\n  std::u16string GetClipboardData() override { return {}; }\n#if defined(OS_WIN) || defined(OS_MAC)\n  void SetTextInputClient(int client_id, const char* input_action,\n                          const char* input_type) override {}\n  void ClearTextInputClient() override {}\n  void SetEditableTransform(const float transform_matrix[16]) override {}\n  void SetEditingState(uint64_t selection_base, uint64_t composing_extent,\n                       const std::string& selection_affinity,\n                       const std::string& text, bool selection_directional,\n                       uint64_t selection_extent,\n                       uint64_t composing_base) override {}\n  void SetCaretRect(float x, float y, float width, float height) override {}\n  void setMarkedTextRect(float x, float y, float width, float height) override {\n  }\n  void ShowTextInput() override {}\n  void HideTextInput() override {}\n  void WindowMove() override {}\n  void ActivateSystemCursor(int type, const std::string& path) override {}\n#endif\n  void ReportTiming(const std::unordered_map<std::string, int64_t>& timing,\n                    const std::string& flag) override {}\n  void FilterInputAsync(\n      const std::string& input, const std::string& pattern,\n      std::function<void(const std::string&)> callback) override {}\n  BaseView* FindViewById(int view_id) override { return nullptr; }\n  ShadowNode* FindShadowNodeById(int node_id) override { return nullptr; }\n\n  void RegisterDrawableImage(\n      std::shared_ptr<DrawableImage> drawable_image) override {}\n  void UnregisterDrawableImage(int64_t id) override {}\n};\n\nclass MockEventDelegate : public clay::EventDelegate {\n public:\n  MockEventDelegate(UITest* uitest) : uitest_(uitest) {}\n\n  void OnTouchEvent(const std::string& event_name, int tag, float x, float y,\n                    float page_x, float page_y) override {}\n  void OnMouseEvent(const std::string& event_name, int view_id, int button,\n                    int buttons, float scale, float x, float y, float page_x,\n                    float page_y) override {}\n  void OnWheelEvent(const std::string& event_name, int view_id, float x,\n                    float y, float page_x, float page_y, float delta_x,\n                    float delta_y) override {}\n  void OnKeyEvent(const std::string& event_name, int view_id, const char* key,\n                  bool repeat) override {}\n  void OnAnimationEvent(const std::string& event_name,\n                        const char* animation_name, int view_id) override {}\n  void OnTransitionEvent(const std::string& event_name,\n                         const char* animation_name, int view_id,\n                         ClayAnimationPropertyType type) override {}\n  void OnFocusChanged(int view_id, bool focus) override {}\n  void OnHoverChanged(int view_id, bool hover) override {}\n  void OnDragDropEvent(const std::string& event_name, int view_id,\n                       clay::Value::Map map) override {}\n  void OnViewportMetricsChanged(double device_pixel_ratio,\n                                double device_density_dpi, double logical_width,\n                                double logical_height,\n                                double physical_screen_width,\n                                double physical_screen_height,\n                                double font_scale, bool night_mode) override {}\n  void OnSendGlobalEvent(const std::string& event_name,\n                         clay::Value args) override {}\n  void OnDrawEndEvent() override {}\n  void OnFirstMeaningfulPaint() override {}\n  void OnOverlayEvent(int view_id, const char* overlay_id, int overlay_count,\n                      const char** overlay_ids,\n                      const char* event_name) override {}\n  void OnLayoutChanged(int view_id, clay::Value::Map map) override {}\n  void OnIntersectionEvent(int view_id, clay::Value::Map map) override {}\n  void OnCallJSApiCallback(int callback_id, clay::Value value) override {}\n  void CallJSIntersectionObserver(int observer_id, int callback_id,\n                                  clay::Value params) override {}\n\n  void OnSendCustomEvent(int view_id, const std::string& event_name,\n                         clay::Value::Map args) override {\n    if (uitest_->custom_event_callback_) {\n      uitest_->custom_event_callback_(view_id, event_name.c_str(),\n                                      std::move(args));\n    }\n  }\n\n private:\n  UITest* uitest_;\n};\n\nvoid UITest::DispatchDragEvent(FloatPoint start, FloatPoint end, bool fling,\n                               float steps, float interval_ms) {\n  int64_t interval = interval_ms * 1000;\n  int64_t timestamp = fml::TimePoint::Now().ToEpochDelta().ToMicroseconds();\n  page_->DispatchPointerEvent({CreatePointer(\n      0, PointerEvent::EventType::kDownEvent, start, {}, timestamp)});\n  FloatPoint last = start;\n  for (int i = 1; i <= steps; i++) {\n    timestamp += interval;\n    auto point = FloatPoint::Lerp(start, end, static_cast<float>(i) / steps);\n    page_->DispatchPointerEvent(\n        {CreatePointer(0, PointerEvent::EventType::kMoveEvent, point,\n                       point - last, timestamp)});\n    last = point;\n  }\n  if (!fling) {\n    // Stop at the end point for 20 samples to ensure the calculated velocity\n    // is 0. See `VelocityTracker::GetVelocityEstimate`\n    for (int i = 0; i < 20; i++) {\n      timestamp += interval;\n      page_->DispatchPointerEvent({CreatePointer(\n          0, PointerEvent::EventType::kMoveEvent, end, end - last, timestamp)});\n    }\n  }\n  timestamp += interval;\n  page_->DispatchPointerEvent({CreatePointer(\n      0, PointerEvent::EventType::kUpEvent, end, {}, timestamp)});\n}\n\nvoid UITest::Layout() { page_->GetLayoutController()->Layout(); }\n\nvoid UITest::ResetAnimationTime() {\n  page_->GetAnimationHandler()->DoAnimationFrame(\n      fml::TimePoint::Now().ToEpochDelta().ToMilliseconds());\n}\n\nvoid UITest::DoAnimation(int ms) {\n  auto last_frame_time =\n      page_->GetAnimationHandler()->GetCurrentAnimationTime();\n  if (last_frame_time < 0) {\n    last_frame_time = fml::TimePoint::Now().ToEpochDelta().ToMilliseconds();\n    // The animation will be started in the next frame.\n    // So we should trigger an extra `DoAnimationFrame`\n    page_->GetAnimationHandler()->DoAnimationFrame(last_frame_time);\n  }\n  // In the ui test, the start_time_ in scroller may not be initialized,we need\n  // do a additional DoAnimationFrame to initialize scroller (test only).\n  page_->GetAnimationHandler()->DoAnimationFrame(last_frame_time);\n  page_->GetAnimationHandler()->DoAnimationFrame(last_frame_time + ms);\n}\n\nvoid UITest::InvokeUIMethod(\n    BaseView* view, const std::string& method_name,\n    std::initializer_list<std::pair<std::string, clay::Value&&>> params,\n    std::function<void(LynxUIMethodResult code, const clay::Value& data)>\n        callback) {\n  LynxModuleValues values;\n  for (auto& pair : params) {\n    values.names.push_back(pair.first);\n    values.values.push_back(std::move(pair.second));\n  }\n  LynxUIMethodCallback wrapped_callback = [callback](LynxUIMethodResult code,\n                                                     const clay::Value& data) {\n    if (callback) {\n      callback(code, data);\n    }\n  };\n  LynxUIMethodRegistrar::Instance().Invoke(method_name, view, values,\n                                           wrapped_callback);\n}\n\nvoid UITest::SetUp() {\n  ui_thread_ = std::make_unique<fml::Thread>(\"ui\");\n  delegate_ = std::make_unique<MockDelegate>();\n  event_delegate_ = std::make_unique<MockEventDelegate>(this);\n  auto font_collection = FontCollection::Instance();\n  font_collection->SetupDefaultFontManager(0);\n  fml::AutoResetWaitableEvent latch;\n  ui_task_runner()->PostTask([&]() {\n    page_ = std::make_unique<PageView>(0, nullptr, ui_thread_->GetTaskRunner());\n    page_->SetEventDelegate(event_delegate_.get());\n    page_->SetRenderDelegate(delegate_.get());\n    page_->SetBound(0, 0, 1000, 1000);\n    custom_event_callback_ = [this](int id, const char* event_name,\n                                    clay::Value::Map map) {\n      OnCustomEvent(event_name, std::move(map));\n    };\n    page_->GetFocusManager()->preference().switch_up_to_once_per_frame = false;\n    UISetUp();\n    latch.Signal();\n  });\n  latch.Wait();\n}\n\nvoid UITest::TearDown() {\n  fml::AutoResetWaitableEvent latch;\n  ui_task_runner()->PostTask([&]() {\n    UITearDown();\n    page_.reset();\n    latch.Signal();\n  });\n  latch.Wait();\n}\n\nvoid UITest::AsyncStart() { async_level_++; }\n\nvoid UITest::AsyncEnd() {\n  async_level_--;\n  if (async_level_ == 0) {\n    body_latch_.Signal();\n  }\n}\n\nvoid UITest::AsyncWait() {\n  if (async_level_ > 0) {\n    body_latch_.Wait();\n  }\n}\n\nBaseView* UITest::ViewForId(const std::string& id_selector) {\n  return ViewContext::FindViewByIdSelector(id_selector, page_.get());\n}\n\nvoid UITest::NavigateByDirection(FocusManager::Direction direction) {\n  PhysicalKeyboardKey physical = PhysicalKeyboardKey::kUnknown;\n  LogicalKeyboardKey logical = LogicalKeyboardKey::kUnknown;\n  switch (direction) {\n    case FocusManager::Direction::kLeft:\n      physical = PhysicalKeyboardKey::kArrowLeft;\n      logical = LogicalKeyboardKey::kArrowLeft;\n      break;\n    case FocusManager::Direction::kRight:\n      physical = PhysicalKeyboardKey::kArrowRight;\n      logical = LogicalKeyboardKey::kArrowRight;\n      break;\n    case FocusManager::Direction::kUp:\n      physical = PhysicalKeyboardKey::kArrowUp;\n      logical = LogicalKeyboardKey::kArrowUp;\n      break;\n    case FocusManager::Direction::kDown:\n      physical = PhysicalKeyboardKey::kArrowDown;\n      logical = LogicalKeyboardKey::kArrowDown;\n      break;\n    default:\n      FML_DCHECK(false) << \"Unhandled key for NavigateByDirection\";\n      return;\n  }\n  page_->DispatchKeyEvent(\n      std::make_unique<KeyEvent>(\n          fml::TimePoint::Now().ToEpochDelta().ToMilliseconds(),\n          KeyEventType::kDown, physical, logical, false, \"\"),\n      [](bool) {});\n\n  page_->DispatchKeyEvent(\n      std::make_unique<KeyEvent>(\n          fml::TimePoint::Now().ToEpochDelta().ToMilliseconds(),\n          KeyEventType::kUp, physical, logical, false, \"\"),\n      [](bool) {});\n}\n\nPointerEvent UITest::CreatePointer(int pointer_id, PointerEvent::EventType type,\n                                   FloatPoint position, FloatPoint delta,\n                                   int64_t timestamp) {\n  PointerEvent pointer(type);\n  pointer.buttons = PointerEvent::kPrimary;\n  pointer.pointer_id = pointer_id;\n  pointer.position = position;\n  pointer.delta = FloatSize(delta.x(), delta.y());\n  pointer.timestamp = timestamp;\n  return pointer;\n}\n\nvoid UITest::DispatchTapEvent(FloatPoint point) {\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kDownEvent, point)});\n  page_->DispatchPointerEvent(\n      {CreatePointer(0, PointerEvent::EventType::kUpEvent, point)});\n}\n\nvoid UITest::DispatchTouchPadEvent(FloatPoint from, FloatPoint to,\n                                   int event_num) {\n  std::vector<PointerEvent> events;\n  float x_stride = (to.x() - from.x()) / event_num;\n  float y_stride = (to.y() - from.y()) / event_num;\n  for (int i = 1; i < event_num + 1; i++) {\n    auto temp_event = PointerEvent(PointerEvent::EventType::kSignalEvent);\n    temp_event.device = PointerEvent::kMouse;\n    temp_event.signal_kind = PointerEvent::SignalKind::kScroll;\n    temp_event.scroll_delta_x = x_stride;\n    temp_event.scroll_delta_y = y_stride;\n    temp_event.position = {from.x() + i * temp_event.scroll_delta_x,\n                           from.y() + i * temp_event.scroll_delta_y};\n    events.push_back(temp_event);\n  }\n  page_->DispatchPointerEvent(events);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/testing/ui_test.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_TESTING_UI_TEST_H_\n#define CLAY_UI_TESTING_UI_TEST_H_\n\n#include <cstddef>\n#include <initializer_list>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/fml/thread.h\"\n#include \"clay/ui/component/page_view.h\"\n#include \"clay/ui/event/gesture_event.h\"\n#include \"clay/ui/render_delegate.h\"\n#include \"clay/ui/testing/test_utils.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/skia/include/core/SkSize.h\"\n\nnamespace clay {\n\nclass UITest : public ::testing::Test {\n public:\n  PointerEvent CreatePointer(int pointer_id, PointerEvent::EventType type,\n                             FloatPoint position, FloatPoint delta = {},\n                             int64_t timestamp = 0);\n  void DispatchTapEvent(FloatPoint point);\n  void DispatchDragEvent(FloatPoint start, FloatPoint end, bool fling = false,\n                         float steps = 10,\n                         float interval_ms = 1000.0f / 120.0f);\n  void DispatchTouchPadEvent(FloatPoint from, FloatPoint to, int event_num);\n\n  void Layout();\n\n  // Currently some animations rely on the system time (e.g.\n  // `Scroller::start_time_`). So we should reset the last_frame_time before\n  // starting a new animation.\n  void ResetAnimationTime();\n\n  void DoAnimation(int ms = 1000);\n\n  void InvokeUIMethod(\n      BaseView* view, const std::string& method_name,\n      std::initializer_list<std::pair<std::string, clay::Value&&>> params,\n      std::function<void(LynxUIMethodResult code, const clay::Value& data)>\n          callback);\n\n  fml::RefPtr<fml::TaskRunner> ui_task_runner() {\n    return ui_thread_->GetTaskRunner();\n  }\n\n  // This is convenience for verifying component events.\n  std::function<void(int, const char*, clay::Value::Map)>\n      custom_event_callback_;\n  MOCK_METHOD(void, OnCustomEvent,\n              (std::string event_name, const clay::Value::Map& params), ());\n\n protected:\n  virtual void UISetUp() {}\n  virtual void UITearDown() {}\n  void SetUp() override;\n  void TearDown() override;\n\n  void AsyncStart();\n  void AsyncEnd();\n  void AsyncWait();\n\n  BaseView* ViewForId(const std::string& id_selector);\n  void NavigateByDirection(FocusManager::Direction direction);\n\n  std::unique_ptr<PageView> page_;\n\n private:\n  std::unique_ptr<fml::Thread> ui_thread_;\n  std::unique_ptr<RenderDelegate> delegate_;\n  std::unique_ptr<EventDelegate> event_delegate_;\n  int async_level_ = 0;\n  fml::AutoResetWaitableEvent body_latch_;\n};\n\n#ifndef GTEST_TEST_CLASS_NAME_\n#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \\\n  test_suite_name##_##test_name##_Test\n#endif\n\n#define TEST_F_UI(test_fixture, test_name)                                   \\\n  class GTEST_TEST_CLASS_NAME_(test_fixture, test_name)                      \\\n      : public test_fixture {                                                \\\n   public:                                                                   \\\n    GTEST_TEST_CLASS_NAME_(test_fixture, test_name)() {}                     \\\n                                                                             \\\n   private:                                                                  \\\n    virtual void TestBody() {                                                \\\n      AsyncStart();                                                          \\\n      ui_task_runner()->PostTask([&]() {                                     \\\n        TestBodyUI();                                                        \\\n        AsyncEnd();                                                          \\\n      });                                                                    \\\n      AsyncWait();                                                           \\\n    }                                                                        \\\n    void TestBodyUI();                                                       \\\n    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;    \\\n    BASE_DISALLOW_COPY_AND_ASSIGN(GTEST_TEST_CLASS_NAME_(test_fixture,       \\\n                                                         test_name));        \\\n  };                                                                         \\\n                                                                             \\\n  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_fixture,            \\\n                                                    test_name)::test_info_ = \\\n      ::testing::internal::MakeAndRegisterTestInfo(                          \\\n          #test_fixture, #test_name, nullptr, nullptr,                       \\\n          ::testing::internal::CodeLocation(__FILE__, __LINE__),             \\\n          (::testing::internal::GetTypeId<test_fixture>()),                  \\\n          ::testing::internal::SuiteApiResolver<                             \\\n              test_fixture>::GetSetUpCaseOrSuite(__FILE__, __LINE__),        \\\n          ::testing::internal::SuiteApiResolver<                             \\\n              test_fixture>::GetTearDownCaseOrSuite(__FILE__, __LINE__),     \\\n          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(   \\\n              test_fixture, test_name)>);                                    \\\n  void GTEST_TEST_CLASS_NAME_(test_fixture, test_name)::TestBodyUI()\n\n}  // namespace clay\n#endif  // CLAY_UI_TESTING_UI_TEST_H_\n"
  },
  {
    "path": "clay/ui/ui_rendering_backend.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_UI_RENDERING_BACKEND_H_\n#define CLAY_UI_UI_RENDERING_BACKEND_H_\n\n#ifndef ENABLE_SKITY\n#include \"clay/third_party/txt/src/txt/asset_font_manager_skia.h\"\n#include \"clay/third_party/txt/src/txt/font_collection_skia.h\"\n#include \"clay/ui/resource/asset_manager_font_provider_skia.h\"\n#else\n#include \"clay/gfx/image/animated_image.h\"\n#include \"clay/gfx/image/base_image.h\"\n#include \"clay/gfx/skity/skity_image.h\"\n#include \"clay/third_party/txt/src/txt/asset_font_manager_skity.h\"\n#include \"clay/third_party/txt/src/txt/font_collection_skity.h\"\n#include \"clay/ui/resource/asset_manager_font_provider_skity.h\"\n#endif  // ENABLE_SKITY\n\n#endif  // CLAY_UI_UI_RENDERING_BACKEND_H_\n"
  },
  {
    "path": "clay/ui/window/build.gni",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nui_window_sources = [\n  \"key_data.cc\",\n  \"key_data.h\",\n  \"key_data_helper.cc\",\n  \"key_data_helper.h\",\n  \"key_data_packet.cc\",\n  \"key_data_packet.h\",\n  \"pointer_data.cc\",\n  \"pointer_data.h\",\n  \"pointer_data_helper.cc\",\n  \"pointer_data_helper.h\",\n  \"pointer_data_packet.cc\",\n  \"pointer_data_packet.h\",\n  \"pointer_data_packet_converter.cc\",\n  \"pointer_data_packet_converter.h\",\n  \"viewport_metrics.cc\",\n  \"viewport_metrics.h\",\n]\n"
  },
  {
    "path": "clay/ui/window/key_data.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/window/key_data.h\"\n\n#include <cstring>\n\nnamespace clay {\n\nstatic_assert(sizeof(KeyData) == kBytesPerKeyField * kKeyDataFieldCount,\n              \"KeyData has the wrong size\");\n\nvoid KeyData::Clear() { memset(this, 0, sizeof(KeyData)); }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/key_data.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_WINDOW_KEY_DATA_H_\n#define CLAY_UI_WINDOW_KEY_DATA_H_\n\n#include <cstdint>\n\nnamespace clay {\n\nstatic constexpr int kKeyDataFieldCount = 5;\nstatic constexpr int kBytesPerKeyField = sizeof(int64_t);\n\n// The change of the key event, used by KeyData.\nenum class KeyEventActionType : int64_t {\n  kDown = 0,\n  kUp,\n  kRepeat,\n};\n\n// The fixed-length sections of a KeyDataPacket.\n//\n// KeyData does not contain `character`, for variable-length data are stored in\n// a different way in KeyDataPacket.\nstruct alignas(8) KeyData {\n  // Timestamp in microseconds from an arbitrary and consistent start point\n  uint64_t timestamp;\n  KeyEventActionType type;\n  uint64_t physical;\n  uint64_t logical;\n  // True if the event does not correspond to a native event.\n  //\n  // The value is 1 for true, and 0 for false.\n  uint64_t synthesized;\n\n  // Sets all contents of `Keydata` to 0.\n  void Clear();\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_WINDOW_KEY_DATA_H_\n"
  },
  {
    "path": "clay/ui/window/key_data_helper.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/window/key_data_helper.h\"\n\nnamespace clay {\n\nKeyEventActionType KeyDataHelper::MapKeyEventType(ClayKeyEventType event_kind) {\n  switch (event_kind) {\n    case kClayKeyEventTypeUp:\n      return KeyEventActionType::kUp;\n    case kClayKeyEventTypeDown:\n      return KeyEventActionType::kDown;\n    case kClayKeyEventTypeRepeat:\n      return KeyEventActionType::kRepeat;\n  }\n  return KeyEventActionType::kUp;\n}\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/key_data_helper.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_WINDOW_KEY_DATA_HELPER_H_\n#define CLAY_UI_WINDOW_KEY_DATA_HELPER_H_\n\n#include \"clay/public/clay.h\"\n#include \"clay/ui/window/key_data.h\"\n\nnamespace clay {\nclass KeyDataHelper {\n public:\n  static KeyEventActionType MapKeyEventType(ClayKeyEventType event_kind);\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_WINDOW_KEY_DATA_HELPER_H_\n"
  },
  {
    "path": "clay/ui/window/key_data_packet.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/window/key_data_packet.h\"\n\n#include <cstring>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nKeyDataPacket::KeyDataPacket(const KeyData& event, const char* character) {\n  size_t char_size = character == nullptr ? 0 : strlen(character);\n  uint64_t char_size_64 = char_size;\n  data_.resize(sizeof(uint64_t) + sizeof(KeyData) + char_size);\n  memcpy(CharacterSizeStart(), &char_size_64, sizeof(char_size));\n  memcpy(KeyDataStart(), &event, sizeof(KeyData));\n  if (character != nullptr) {\n    memcpy(CharacterStart(), character, char_size);\n  }\n}\n\nKeyDataPacket::~KeyDataPacket() = default;\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/key_data_packet.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_WINDOW_KEY_DATA_PACKET_H_\n#define CLAY_UI_WINDOW_KEY_DATA_PACKET_H_\n\n#include <functional>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/ui/window/key_data.h\"\n\nnamespace clay {\n\n// A byte stream representing a key event.\nclass KeyDataPacket {\n public:\n  // Build the key data packet by providing information.\n  //\n  // The `character` is a nullable C-string that ends with a '\\0'.\n  KeyDataPacket(const KeyData& event, const char* character);\n  ~KeyDataPacket();\n\n  // Prevent copying.\n  KeyDataPacket(KeyDataPacket const&) = delete;\n  KeyDataPacket& operator=(KeyDataPacket const&) = delete;\n\n  const std::vector<uint8_t>& data() const { return data_; }\n\n private:\n  // Packet structure:\n  // | CharDataSize |     (1 field)\n  // |   Key Data   |     (kKeyDataFieldCount fields)\n  // |   CharData   |     (CharDataSize bits)\n\n  uint8_t* CharacterSizeStart() { return data_.data(); }\n  uint8_t* KeyDataStart() { return CharacterSizeStart() + sizeof(uint64_t); }\n  uint8_t* CharacterStart() { return KeyDataStart() + sizeof(KeyData); }\n\n  std::vector<uint8_t> data_;\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_WINDOW_KEY_DATA_PACKET_H_\n"
  },
  {
    "path": "clay/ui/window/pointer_data.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/window/pointer_data.h\"\n\n#include <cstring>\n\nnamespace clay {\n\nstatic_assert(sizeof(PointerData) == kBytesPerField * kPointerDataFieldCount,\n              \"PointerData has the wrong size\");\n\nvoid PointerData::Clear() { memset(this, 0, sizeof(PointerData)); }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/pointer_data.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_WINDOW_POINTER_DATA_H_\n#define CLAY_UI_WINDOW_POINTER_DATA_H_\n\n#include <cstdint>\n\nnamespace clay {\n\n// If this value changes, update the pointer data unpacking code in\n// platform_dispatcher.dart.\nstatic constexpr int kPointerDataFieldCount = 37;\nstatic constexpr int kBytesPerField = sizeof(int64_t);\n// Must match the button constants in events.dart.\nenum PointerButtonMouse : int64_t {\n  kPointerButtonMousePrimary = 1 << 0,\n  kPointerButtonMouseSecondary = 1 << 1,\n  kPointerButtonMouseMiddle = 1 << 2,\n  kPointerButtonMouseBack = 1 << 3,\n  kPointerButtonMouseForward = 1 << 4,\n};\n\nenum PointerButtonTouch : int64_t {\n  kPointerButtonTouchContact = 1 << 0,\n};\n\nenum PointerButtonStylus : int64_t {\n  kPointerButtonStylusContact = 1 << 0,\n  kPointerButtonStylusPrimary = 1 << 1,\n  kPointerButtonStylusSecondary = 1 << 2,\n};\n\n// This structure is unpacked by hooks.dart.\nstruct alignas(8) PointerData {\n  // Must match the PointerChange enum in pointer.dart.\n  enum class Change : int64_t {\n    kCancel,\n    kAdd,\n    kRemove,\n    kHover,\n    kDown,\n    kMove,\n    kUp,\n    kPanZoomStart,\n    kPanZoomUpdate,\n    kPanZoomEnd,\n  };\n\n  // Must match the PointerDeviceKind enum in pointer.dart.\n  enum class DeviceKind : int64_t {\n    kTouch,\n    kMouse,\n    kStylus,\n    kInvertedStylus,\n    kTrackpad,\n  };\n\n  // Must match the PointerSignalKind enum in pointer.dart.\n  enum class SignalKind : int64_t {\n    kNone,\n    kScroll,\n    kScrollInertiaCancel,\n    kScale,\n  };\n\n  int64_t embedder_id;\n  int64_t time_stamp;\n  Change change;\n  DeviceKind kind;\n  SignalKind signal_kind;\n  int64_t device;\n  int64_t pointer_identifier;\n  double physical_x;\n  double physical_y;\n  double physical_delta_x;\n  double physical_delta_y;\n  int64_t buttons;\n  int64_t obscured;\n  int64_t synthesized;\n  double pressure;\n  double pressure_min;\n  double pressure_max;\n  double distance;\n  double distance_max;\n  double size;\n  double radius_major;\n  double radius_minor;\n  double radius_min;\n  double radius_max;\n  double orientation;\n  double tilt;\n  int64_t platformData;\n  double scroll_delta_x;\n  double scroll_delta_y;\n  double pan_x;\n  double pan_y;\n  double pan_delta_x;\n  double pan_delta_y;\n  double scale;\n  double rotation;\n  int64_t source;\n  int64_t is_precise_scroll = 1;\n\n  void Clear();\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_WINDOW_POINTER_DATA_H_\n"
  },
  {
    "path": "clay/ui/window/pointer_data_helper.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/window/pointer_data_helper.h\"\n\nnamespace clay {\n\n// Returns the PointerData::Change for the given ClayPointerPhase.\nPointerData::Change PointerDataHelper::ToPointerDataChange(\n    ClayPointerPhase phase) {\n  switch (phase) {\n    case kClayPointerPhaseCancel:\n      return PointerData::Change::kCancel;\n    case kClayPointerPhaseUp:\n      return PointerData::Change::kUp;\n    case kClayPointerPhaseDown:\n      return PointerData::Change::kDown;\n    case kClayPointerPhaseMove:\n      return PointerData::Change::kMove;\n    case kClayPointerPhaseAdd:\n      return PointerData::Change::kAdd;\n    case kClayPointerPhaseRemove:\n      return PointerData::Change::kRemove;\n    case kClayPointerPhaseHover:\n      return PointerData::Change::kHover;\n    case kClayPointerPhasePanZoomStart:\n      return PointerData::Change::kPanZoomStart;\n    case kClayPointerPhasePanZoomUpdate:\n      return PointerData::Change::kPanZoomUpdate;\n    case kClayPointerPhasePanZoomEnd:\n      return PointerData::Change::kPanZoomEnd;\n  }\n  return PointerData::Change::kCancel;\n}\n\n// Returns the PointerData::DeviceKind for the given\n// ClayPointerDeviceKind.\nPointerData::DeviceKind PointerDataHelper::ToPointerDataKind(\n    ClayPointerDeviceKind device_kind) {\n  switch (device_kind) {\n    case kClayPointerDeviceKindMouse:\n      return PointerData::DeviceKind::kMouse;\n    case kClayPointerDeviceKindTouch:\n      return PointerData::DeviceKind::kTouch;\n    case kClayPointerDeviceKindStylus:\n      return PointerData::DeviceKind::kStylus;\n    case kClayPointerDeviceKindTrackpad:\n      return PointerData::DeviceKind::kTrackpad;\n  }\n  return PointerData::DeviceKind::kMouse;\n}\n\n// Returns the PointerData::SignalKind for the given\n// ClayPointerSignalKind.\nPointerData::SignalKind PointerDataHelper::ToPointerDataSignalKind(\n    ClayPointerSignalKind kind) {\n  switch (kind) {\n    case kClayPointerSignalKindNone:\n      return PointerData::SignalKind::kNone;\n    case kClayPointerSignalKindScroll:\n      return PointerData::SignalKind::kScroll;\n    case kClayPointerSignalKindScrollInertiaCancel:\n      return PointerData::SignalKind::kScrollInertiaCancel;\n    case kClayPointerSignalKindScale:\n      return PointerData::SignalKind::kScale;\n  }\n  return PointerData::SignalKind::kNone;\n}\n\n// Returns the buttons to synthesize for a PointerData from a\n// ClayPointerEvent with no type or buttons set.\nint64_t PointerDataHelper::PointerDataButtonsForLegacyEvent(\n    PointerData::Change change) {\n  switch (change) {\n    case PointerData::Change::kDown:\n    case PointerData::Change::kMove:\n      // These kinds of change must have a non-zero `buttons`, otherwise\n      // gesture recognizers will ignore these events.\n      return kPointerButtonMousePrimary;\n    case PointerData::Change::kCancel:\n    case PointerData::Change::kAdd:\n    case PointerData::Change::kRemove:\n    case PointerData::Change::kHover:\n    case PointerData::Change::kUp:\n    case PointerData::Change::kPanZoomStart:\n    case PointerData::Change::kPanZoomUpdate:\n    case PointerData::Change::kPanZoomEnd:\n      return 0;\n  }\n  return 0;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/pointer_data_helper.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_WINDOW_POINTER_DATA_HELPER_H_\n#define CLAY_UI_WINDOW_POINTER_DATA_HELPER_H_\n\n#include \"clay/public/clay.h\"\n#include \"clay/ui/window/pointer_data.h\"\n\nnamespace clay {\nclass PointerDataHelper {\n public:\n  static PointerData::Change ToPointerDataChange(ClayPointerPhase phase);\n  static PointerData::DeviceKind ToPointerDataKind(\n      ClayPointerDeviceKind device_kind);\n  static PointerData::SignalKind ToPointerDataSignalKind(\n      ClayPointerSignalKind kind);\n  static int64_t PointerDataButtonsForLegacyEvent(PointerData::Change change);\n};\n}  // namespace clay\n\n#endif  // CLAY_UI_WINDOW_POINTER_DATA_HELPER_H_\n"
  },
  {
    "path": "clay/ui/window/pointer_data_packet.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/window/pointer_data_packet.h\"\n\n#include <cstring>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nPointerDataPacket::PointerDataPacket(size_t count)\n    : data_(count * sizeof(PointerData)) {}\n\nPointerDataPacket::PointerDataPacket(uint8_t* data, size_t num_bytes)\n    : data_(data, data + num_bytes) {}\n\nPointerDataPacket::~PointerDataPacket() = default;\n\nvoid PointerDataPacket::SetPointerData(size_t i, const PointerData& data) {\n  FML_DCHECK(i < GetLength());\n  memcpy(&data_[i * sizeof(PointerData)], &data, sizeof(PointerData));\n}\n\nPointerData PointerDataPacket::GetPointerData(size_t i) const {\n  FML_DCHECK(i < GetLength());\n  PointerData result;\n  memcpy(&result, &data_[i * sizeof(PointerData)], sizeof(PointerData));\n  return result;\n}\n\nsize_t PointerDataPacket::GetLength() const {\n  return data_.size() / sizeof(PointerData);\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/pointer_data_packet.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_WINDOW_POINTER_DATA_PACKET_H_\n#define CLAY_UI_WINDOW_POINTER_DATA_PACKET_H_\n\n#include <cstring>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/ui/window/pointer_data.h\"\n\nnamespace clay {\n\nclass PointerDataPacket {\n public:\n  explicit PointerDataPacket(size_t count);\n  PointerDataPacket(uint8_t* data, size_t num_bytes);\n  ~PointerDataPacket();\n\n  void SetPointerData(size_t i, const PointerData& data);\n  PointerData GetPointerData(size_t i) const;\n  size_t GetLength() const;\n  const std::vector<uint8_t>& data() const { return data_; }\n\n private:\n  std::vector<uint8_t> data_;\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PointerDataPacket);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_WINDOW_POINTER_DATA_PACKET_H_\n"
  },
  {
    "path": "clay/ui/window/pointer_data_packet_converter.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/window/pointer_data_packet_converter.h\"\n\n#include <cmath>\n#include <cstring>\n\n#include \"clay/fml/logging.h\"\n\nnamespace clay {\n\nPointerDataPacketConverter::PointerDataPacketConverter() : pointer_(0) {}\n\nPointerDataPacketConverter::~PointerDataPacketConverter() = default;\n\nstd::unique_ptr<PointerDataPacket> PointerDataPacketConverter::Convert(\n    std::unique_ptr<PointerDataPacket> packet) {\n  std::vector<PointerData> converted_pointers;\n  // Converts each pointer data in the buffer and stores it in the\n  // converted_pointers.\n  for (size_t i = 0; i < packet->GetLength(); i++) {\n    PointerData pointer_data = packet->GetPointerData(i);\n    ConvertPointerData(pointer_data, converted_pointers);\n  }\n\n  // Writes converted_pointers into converted_packet.\n  auto converted_packet =\n      std::make_unique<clay::PointerDataPacket>(converted_pointers.size());\n  size_t count = 0;\n  for (auto& converted_pointer : converted_pointers) {\n    converted_packet->SetPointerData(count++, converted_pointer);\n  }\n\n  return converted_packet;\n}\n\nvoid PointerDataPacketConverter::ConvertPointerData(\n    PointerData pointer_data, std::vector<PointerData>& converted_pointers) {\n  if (pointer_data.signal_kind == PointerData::SignalKind::kNone) {\n    switch (pointer_data.change) {\n      case PointerData::Change::kCancel: {\n        // Android's three finger gesture will send a cancel event\n        // to a non-existing pointer. Drops the cancel if pointer\n        // is not previously added.\n        // https://github.com/flutter/flutter/issues/20517\n        auto iter = states_.find(pointer_data.device);\n        if (iter != states_.end()) {\n          PointerState state = iter->second;\n          FML_DCHECK(state.is_down);\n          UpdatePointerIdentifier(pointer_data, state, false);\n\n          state.is_down = false;\n          states_[pointer_data.device] = state;\n          converted_pointers.push_back(pointer_data);\n        }\n        break;\n      }\n      case PointerData::Change::kAdd: {\n        FML_DCHECK(states_.find(pointer_data.device) == states_.end());\n        EnsurePointerState(pointer_data);\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n      case PointerData::Change::kRemove: {\n        // Makes sure we have an existing pointer\n        auto iter = states_.find(pointer_data.device);\n        FML_DCHECK(iter != states_.end());\n        PointerState state = iter->second;\n\n        if (state.is_down) {\n          // Synthesizes cancel event if the pointer is down.\n          PointerData synthesized_cancel_event = pointer_data;\n          synthesized_cancel_event.change = PointerData::Change::kCancel;\n          synthesized_cancel_event.synthesized = 1;\n          UpdatePointerIdentifier(synthesized_cancel_event, state, false);\n\n          state.is_down = false;\n          states_[synthesized_cancel_event.device] = state;\n          converted_pointers.push_back(synthesized_cancel_event);\n        }\n\n        if (LocationNeedsUpdate(pointer_data, state)) {\n          // Synthesizes a hover event if the location does not match.\n          PointerData synthesized_hover_event = pointer_data;\n          synthesized_hover_event.change = PointerData::Change::kHover;\n          synthesized_hover_event.synthesized = 1;\n\n          UpdateDeltaAndState(synthesized_hover_event, state);\n          converted_pointers.push_back(synthesized_hover_event);\n        }\n\n        states_.erase(pointer_data.device);\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n      case PointerData::Change::kHover: {\n        auto iter = states_.find(pointer_data.device);\n        PointerState state;\n        if (iter == states_.end()) {\n          // Synthesizes add event if the pointer is not previously added.\n          PointerData synthesized_add_event = pointer_data;\n          synthesized_add_event.change = PointerData::Change::kAdd;\n          synthesized_add_event.synthesized = 1;\n          synthesized_add_event.buttons = 0;\n          state = EnsurePointerState(synthesized_add_event);\n          converted_pointers.push_back(synthesized_add_event);\n        } else {\n          state = iter->second;\n        }\n        FML_DCHECK(!state.is_down);\n        state.buttons = pointer_data.buttons;\n        if (LocationNeedsUpdate(pointer_data, state)) {\n          UpdateDeltaAndState(pointer_data, state);\n          converted_pointers.push_back(pointer_data);\n        }\n        break;\n      }\n      case PointerData::Change::kDown: {\n        auto iter = states_.find(pointer_data.device);\n        PointerState state;\n        if (iter == states_.end()) {\n          // Synthesizes a add event if the pointer is not previously added.\n          PointerData synthesized_add_event = pointer_data;\n          synthesized_add_event.change = PointerData::Change::kAdd;\n          synthesized_add_event.synthesized = 1;\n          synthesized_add_event.buttons = 0;\n          state = EnsurePointerState(synthesized_add_event);\n          converted_pointers.push_back(synthesized_add_event);\n        } else {\n          state = iter->second;\n        }\n\n        FML_DCHECK(!state.is_down);\n\n        if (LocationNeedsUpdate(pointer_data, state)) {\n          // Synthesizes a hover event if the location does not match.\n          PointerData synthesized_hover_event = pointer_data;\n          synthesized_hover_event.change = PointerData::Change::kHover;\n          synthesized_hover_event.synthesized = 1;\n          synthesized_hover_event.buttons = 0;\n\n          UpdateDeltaAndState(synthesized_hover_event, state);\n          converted_pointers.push_back(synthesized_hover_event);\n        }\n\n        UpdatePointerIdentifier(pointer_data, state, true);\n        state.is_down = true;\n        state.buttons = pointer_data.buttons;\n        states_[pointer_data.device] = state;\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n      case PointerData::Change::kMove: {\n        // Makes sure we have an existing pointer in down state\n        auto iter = states_.find(pointer_data.device);\n        FML_DCHECK(iter != states_.end());\n        PointerState state = iter->second;\n        FML_DCHECK(state.is_down);\n\n        UpdatePointerIdentifier(pointer_data, state, false);\n        UpdateDeltaAndState(pointer_data, state);\n        state.buttons = pointer_data.buttons;\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n      case PointerData::Change::kUp: {\n        // Makes sure we have an existing pointer in down state\n        auto iter = states_.find(pointer_data.device);\n        FML_DCHECK(iter != states_.end());\n        PointerState state = iter->second;\n        FML_DCHECK(state.is_down);\n\n        UpdatePointerIdentifier(pointer_data, state, false);\n\n        if (LocationNeedsUpdate(pointer_data, state)) {\n          // Synthesizes a move event if the location does not match.\n          PointerData synthesized_move_event = pointer_data;\n          synthesized_move_event.change = PointerData::Change::kMove;\n          synthesized_move_event.buttons = state.buttons;\n          synthesized_move_event.synthesized = 1;\n\n          UpdateDeltaAndState(synthesized_move_event, state);\n          converted_pointers.push_back(synthesized_move_event);\n        }\n\n        state.is_down = false;\n        state.buttons = pointer_data.buttons;\n        states_[pointer_data.device] = state;\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n      case PointerData::Change::kPanZoomStart: {\n        // Makes sure we have an existing pointer\n        auto iter = states_.find(pointer_data.device);\n        PointerState state;\n        if (iter == states_.end()) {\n          // Synthesizes add event if the pointer is not previously added.\n          PointerData synthesized_add_event = pointer_data;\n          synthesized_add_event.change = PointerData::Change::kAdd;\n          synthesized_add_event.synthesized = 1;\n          synthesized_add_event.buttons = 0;\n          state = EnsurePointerState(synthesized_add_event);\n          converted_pointers.push_back(synthesized_add_event);\n        } else {\n          state = iter->second;\n        }\n        FML_DCHECK(!state.is_down);\n        FML_DCHECK(!state.is_pan_zoom_active);\n        if (LocationNeedsUpdate(pointer_data, state)) {\n          // Synthesizes a hover event if the location does not match.\n          PointerData synthesized_hover_event = pointer_data;\n          synthesized_hover_event.change = PointerData::Change::kHover;\n          synthesized_hover_event.synthesized = 1;\n          synthesized_hover_event.buttons = 0;\n\n          UpdateDeltaAndState(synthesized_hover_event, state);\n          converted_pointers.push_back(synthesized_hover_event);\n        }\n\n        UpdatePointerIdentifier(pointer_data, state, true);\n        state.is_pan_zoom_active = true;\n        state.pan_x = 0;\n        state.pan_y = 0;\n        state.scale = 1;\n        state.rotation = 0;\n        states_[pointer_data.device] = state;\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n      case PointerData::Change::kPanZoomUpdate: {\n        // Makes sure we have an existing pointer in pan_zoom_active state\n        auto iter = states_.find(pointer_data.device);\n        FML_DCHECK(iter != states_.end());\n        PointerState state = iter->second;\n        FML_DCHECK(!state.is_down);\n        FML_DCHECK(state.is_pan_zoom_active);\n\n        UpdatePointerIdentifier(pointer_data, state, false);\n        UpdateDeltaAndState(pointer_data, state);\n\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n      case PointerData::Change::kPanZoomEnd: {\n        // Makes sure we have an existing pointer in pan_zoom_active state\n        auto iter = states_.find(pointer_data.device);\n        FML_DCHECK(iter != states_.end());\n        PointerState state = iter->second;\n        FML_DCHECK(state.is_pan_zoom_active);\n\n        UpdatePointerIdentifier(pointer_data, state, false);\n\n        if (LocationNeedsUpdate(pointer_data, state)) {\n          // Synthesizes an update event if the location does not match.\n          PointerData synthesized_move_event = pointer_data;\n          synthesized_move_event.change = PointerData::Change::kPanZoomUpdate;\n          synthesized_move_event.pan_x = state.pan_x;\n          synthesized_move_event.pan_y = state.pan_y;\n          synthesized_move_event.pan_delta_x = 0;\n          synthesized_move_event.pan_delta_y = 0;\n          synthesized_move_event.scale = state.scale;\n          synthesized_move_event.rotation = state.rotation;\n          synthesized_move_event.synthesized = 1;\n\n          UpdateDeltaAndState(synthesized_move_event, state);\n          converted_pointers.push_back(synthesized_move_event);\n        }\n\n        state.is_pan_zoom_active = false;\n        states_[pointer_data.device] = state;\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n      default: {\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n    }\n  } else {\n    switch (pointer_data.signal_kind) {\n      case PointerData::SignalKind::kScroll:\n      case PointerData::SignalKind::kScrollInertiaCancel:\n      case PointerData::SignalKind::kScale: {\n        // Makes sure we have an existing pointer\n        auto iter = states_.find(pointer_data.device);\n        PointerState state;\n\n        if (iter == states_.end()) {\n          // Synthesizes a add event if the pointer is not previously added.\n          PointerData synthesized_add_event = pointer_data;\n          synthesized_add_event.signal_kind = PointerData::SignalKind::kNone;\n          synthesized_add_event.change = PointerData::Change::kAdd;\n          synthesized_add_event.synthesized = 1;\n          synthesized_add_event.buttons = 0;\n          state = EnsurePointerState(synthesized_add_event);\n          converted_pointers.push_back(synthesized_add_event);\n        } else {\n          state = iter->second;\n        }\n\n        if (LocationNeedsUpdate(pointer_data, state)) {\n          if (state.is_down) {\n            // Synthesizes a move event if the pointer is down.\n            PointerData synthesized_move_event = pointer_data;\n            synthesized_move_event.signal_kind = PointerData::SignalKind::kNone;\n            synthesized_move_event.change = PointerData::Change::kMove;\n            synthesized_move_event.buttons = state.buttons;\n            synthesized_move_event.synthesized = 1;\n\n            UpdateDeltaAndState(synthesized_move_event, state);\n            converted_pointers.push_back(synthesized_move_event);\n          } else {\n            // Synthesizes a hover event if the pointer is up.\n            PointerData synthesized_hover_event = pointer_data;\n            synthesized_hover_event.signal_kind =\n                PointerData::SignalKind::kNone;\n            synthesized_hover_event.change = PointerData::Change::kHover;\n            synthesized_hover_event.buttons = 0;\n            synthesized_hover_event.synthesized = 1;\n\n            UpdateDeltaAndState(synthesized_hover_event, state);\n            converted_pointers.push_back(synthesized_hover_event);\n          }\n        }\n\n        converted_pointers.push_back(pointer_data);\n        break;\n      }\n      default: {\n        // Ignores unknown signal kind.\n        break;\n      }\n    }\n  }\n}\n\nPointerState PointerDataPacketConverter::EnsurePointerState(\n    PointerData pointer_data) {\n  PointerState state;\n  state.pointer_identifier = 0;\n  state.is_down = false;\n  state.is_pan_zoom_active = false;\n  state.physical_x = pointer_data.physical_x;\n  state.physical_y = pointer_data.physical_y;\n  state.pan_x = 0;\n  state.pan_y = 0;\n  states_[pointer_data.device] = state;\n  return state;\n}\n\nvoid PointerDataPacketConverter::UpdateDeltaAndState(PointerData& pointer_data,\n                                                     PointerState& state) {\n  pointer_data.physical_delta_x = pointer_data.physical_x - state.physical_x;\n  pointer_data.physical_delta_y = pointer_data.physical_y - state.physical_y;\n  pointer_data.pan_delta_x = pointer_data.pan_x - state.pan_x;\n  pointer_data.pan_delta_y = pointer_data.pan_y - state.pan_y;\n  state.physical_x = pointer_data.physical_x;\n  state.physical_y = pointer_data.physical_y;\n  state.pan_x = pointer_data.pan_x;\n  state.pan_y = pointer_data.pan_y;\n  state.scale = pointer_data.scale;\n  state.rotation = pointer_data.rotation;\n  states_[pointer_data.device] = state;\n}\n\nbool PointerDataPacketConverter::LocationNeedsUpdate(\n    const PointerData pointer_data, const PointerState state) {\n  return state.physical_x != pointer_data.physical_x ||\n         state.physical_y != pointer_data.physical_y;\n}\n\nvoid PointerDataPacketConverter::UpdatePointerIdentifier(\n    PointerData& pointer_data, PointerState& state, bool start_new_pointer) {\n  if (start_new_pointer) {\n    state.pointer_identifier = ++pointer_;\n    states_[pointer_data.device] = state;\n  }\n  pointer_data.pointer_identifier = state.pointer_identifier;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/pointer_data_packet_converter.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_WINDOW_POINTER_DATA_PACKET_CONVERTER_H_\n#define CLAY_UI_WINDOW_POINTER_DATA_PACKET_CONVERTER_H_\n\n#include <cstring>\n#include <map>\n#include <memory>\n#include <vector>\n\n#include \"base/include/fml/macros.h\"\n#include \"clay/ui/window/pointer_data_packet.h\"\n\nnamespace clay {\n\n//------------------------------------------------------------------------------\n/// The current information about a pointer.\n///\n/// This struct is used by PointerDataPacketConverter to fill in necessary\n/// information for the raw pointer packet sent from embedding. This struct also\n/// stores the button state of the last pointer down, up, move, or hover event.\n/// When an embedder issues a pointer up or down event where the pointer's\n/// position has changed since the last move or hover event,\n/// PointerDataPacketConverter generates a synthetic move or hover to notify the\n/// framework. In these cases, these events must be issued with the button state\n/// prior to the pointer up or down.\n///\nstruct PointerState {\n  int64_t pointer_identifier;\n  bool is_down;\n  bool is_pan_zoom_active;\n  double physical_x;\n  double physical_y;\n  double pan_x;\n  double pan_y;\n  double scale;\n  double rotation;\n  int64_t buttons;\n};\n\n//------------------------------------------------------------------------------\n/// Converter to convert the raw pointer data packet from the platforms.\n///\n/// Framework requires certain information to process pointer data. e.g. pointer\n/// identifier and the delta of pointer moment. The converter keeps track each\n/// pointer state and fill in those information appropriately.\n///\n/// The converter is also resposible for providing a clean pointer data stream.\n/// It will attempt to correct the stream if the it contains illegal pointer\n/// transitions.\n///\n/// Example 1 Missing Add:\n///\n///     Down(position x) -> Up(position x)\n///\n///     ###After Conversion###\n///\n///     Synthesized_Add(position x) -> Down(position x) -> Up(position x)\n///\n/// Example 2 Missing another move:\n///\n///     Add(position x) -> Down(position x) -> Move(position y) ->\n///     Up(position z)\n///\n///     ###After Conversion###\n///\n///     Add(position x) -> Down(position x) -> Move(position y) ->\n///     Synthesized_Move(position z) -> Up(position z)\n///\n/// Platform view is the only client that uses this class to convert all the\n/// incoming pointer packet and is responsible for the life cycle of its\n/// instance.\n///\nclass PointerDataPacketConverter {\n public:\n  PointerDataPacketConverter();\n  ~PointerDataPacketConverter();\n\n  //----------------------------------------------------------------------------\n  /// @brief      Converts pointer data packet into a form that framework\n  ///             understands. The raw pointer data packet from embedding does\n  ///             not have sufficient information and may contain illegal\n  ///             pointer transitions. This method will fill out that\n  ///             information and attempt to correct pointer transitions.\n  ///\n  /// @param[in]  packet                   The raw pointer packet sent from\n  ///                                      embedding.\n  ///\n  /// @return     A full converted packet with all the required information\n  /// filled.\n  ///             It may contain synthetic pointer data as the result of\n  ///             converter's attempt to correct illegal pointer transitions.\n  ///\n  std::unique_ptr<PointerDataPacket> Convert(\n      std::unique_ptr<PointerDataPacket> packet);\n\n private:\n  std::map<int64_t, PointerState> states_;\n\n  int64_t pointer_;\n\n  void ConvertPointerData(PointerData pointer_data,\n                          std::vector<PointerData>& converted_pointers);\n\n  PointerState EnsurePointerState(PointerData pointer_data);\n\n  void UpdateDeltaAndState(PointerData& pointer_data, PointerState& state);\n\n  void UpdatePointerIdentifier(PointerData& pointer_data, PointerState& state,\n                               bool start_new_pointer);\n\n  bool LocationNeedsUpdate(const PointerData pointer_data,\n                           const PointerState state);\n\n  BASE_DISALLOW_COPY_AND_ASSIGN(PointerDataPacketConverter);\n};\n\n}  // namespace clay\n\n#endif  // CLAY_UI_WINDOW_POINTER_DATA_PACKET_CONVERTER_H_\n"
  },
  {
    "path": "clay/ui/window/pointer_data_packet_converter_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstring>\n\n#include \"clay/ui/window/pointer_data_packet_converter.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nvoid CreateSimulatedPointerData(PointerData& data,  // NOLINT\n                                PointerData::Change change, int64_t device,\n                                double dx, double dy, int64_t buttons) {\n  data.time_stamp = 0;\n  data.change = change;\n  data.kind = PointerData::DeviceKind::kTouch;\n  data.signal_kind = PointerData::SignalKind::kNone;\n  data.device = device;\n  data.pointer_identifier = 0;\n  data.physical_x = dx;\n  data.physical_y = dy;\n  data.physical_delta_x = 0.0;\n  data.physical_delta_y = 0.0;\n  data.buttons = buttons;\n  data.obscured = 0;\n  data.synthesized = 0;\n  data.pressure = 0.0;\n  data.pressure_min = 0.0;\n  data.pressure_max = 0.0;\n  data.distance = 0.0;\n  data.distance_max = 0.0;\n  data.size = 0.0;\n  data.radius_major = 0.0;\n  data.radius_minor = 0.0;\n  data.radius_min = 0.0;\n  data.radius_max = 0.0;\n  data.orientation = 0.0;\n  data.tilt = 0.0;\n  data.platformData = 0;\n  data.scroll_delta_x = 0.0;\n  data.scroll_delta_y = 0.0;\n}\n\nvoid CreateSimulatedMousePointerData(PointerData& data,  // NOLINT\n                                     PointerData::Change change,\n                                     PointerData::SignalKind signal_kind,\n                                     int64_t device, double dx, double dy,\n                                     double scroll_delta_x,\n                                     double scroll_delta_y, int64_t buttons) {\n  data.time_stamp = 0;\n  data.change = change;\n  data.kind = PointerData::DeviceKind::kMouse;\n  data.signal_kind = signal_kind;\n  data.device = device;\n  data.pointer_identifier = 0;\n  data.physical_x = dx;\n  data.physical_y = dy;\n  data.physical_delta_x = 0.0;\n  data.physical_delta_y = 0.0;\n  data.buttons = buttons;\n  data.obscured = 0;\n  data.synthesized = 0;\n  data.pressure = 0.0;\n  data.pressure_min = 0.0;\n  data.pressure_max = 0.0;\n  data.distance = 0.0;\n  data.distance_max = 0.0;\n  data.size = 0.0;\n  data.radius_major = 0.0;\n  data.radius_minor = 0.0;\n  data.radius_min = 0.0;\n  data.radius_max = 0.0;\n  data.orientation = 0.0;\n  data.tilt = 0.0;\n  data.platformData = 0;\n  data.scroll_delta_x = scroll_delta_x;\n  data.scroll_delta_y = scroll_delta_y;\n}\n\nvoid CreateSimulatedTrackpadGestureData(PointerData& data,  // NOLINT\n                                        PointerData::Change change,\n                                        int64_t device, double dx, double dy,\n                                        double pan_x, double pan_y,\n                                        double scale, double rotation) {\n  data.time_stamp = 0;\n  data.change = change;\n  data.kind = PointerData::DeviceKind::kMouse;\n  data.signal_kind = PointerData::SignalKind::kNone;\n  data.device = device;\n  data.pointer_identifier = 0;\n  data.physical_x = dx;\n  data.physical_y = dy;\n  data.physical_delta_x = 0.0;\n  data.physical_delta_y = 0.0;\n  data.buttons = 0;\n  data.obscured = 0;\n  data.synthesized = 0;\n  data.pressure = 0.0;\n  data.pressure_min = 0.0;\n  data.pressure_max = 0.0;\n  data.distance = 0.0;\n  data.distance_max = 0.0;\n  data.size = 0.0;\n  data.radius_major = 0.0;\n  data.radius_minor = 0.0;\n  data.radius_min = 0.0;\n  data.radius_max = 0.0;\n  data.orientation = 0.0;\n  data.tilt = 0.0;\n  data.platformData = 0;\n  data.scroll_delta_x = 0.0;\n  data.scroll_delta_y = 0.0;\n  data.pan_x = pan_x;\n  data.pan_y = pan_y;\n  data.pan_delta_x = 0.0;\n  data.pan_delta_y = 0.0;\n  data.scale = scale;\n  data.rotation = rotation;\n}\n\nvoid UnpackPointerPacket(std::vector<PointerData>& output,  // NOLINT\n                         std::unique_ptr<PointerDataPacket> packet) {\n  for (size_t i = 0; i < packet->GetLength(); i++) {\n    PointerData pointer_data = packet->GetPointerData(i);\n    output.push_back(pointer_data);\n  }\n  packet.reset();\n}\n\nTEST(PointerDataPacketConverterTest, CanConvertPointerDataPacket) {\n  PointerDataPacketConverter converter;\n  auto packet = std::make_unique<PointerDataPacket>(6);\n  PointerData data;\n  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);\n  packet->SetPointerData(0, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kHover, 0, 3.0, 0.0, 0);\n  packet->SetPointerData(1, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 3.0, 0.0, 1);\n  packet->SetPointerData(2, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 3.0, 4.0, 1);\n  packet->SetPointerData(3, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 4.0, 0);\n  packet->SetPointerData(4, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 4.0,\n                             0);\n  packet->SetPointerData(5, data);\n  auto converted_packet = converter.Convert(std::move(packet));\n\n  std::vector<PointerData> result;\n  UnpackPointerPacket(result, std::move(converted_packet));\n\n  ASSERT_EQ(result.size(), (size_t)6);\n  ASSERT_EQ(result[0].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[0].synthesized, 0);\n\n  ASSERT_EQ(result[1].change, PointerData::Change::kHover);\n  ASSERT_EQ(result[1].synthesized, 0);\n  ASSERT_EQ(result[1].physical_delta_x, 3.0);\n  ASSERT_EQ(result[1].physical_delta_y, 0.0);\n\n  ASSERT_EQ(result[2].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[2].pointer_identifier, 1);\n  ASSERT_EQ(result[2].synthesized, 0);\n\n  ASSERT_EQ(result[3].change, PointerData::Change::kMove);\n  ASSERT_EQ(result[3].pointer_identifier, 1);\n  ASSERT_EQ(result[3].synthesized, 0);\n  ASSERT_EQ(result[3].physical_delta_x, 0.0);\n  ASSERT_EQ(result[3].physical_delta_y, 4.0);\n\n  ASSERT_EQ(result[4].change, PointerData::Change::kUp);\n  ASSERT_EQ(result[4].pointer_identifier, 1);\n  ASSERT_EQ(result[4].synthesized, 0);\n\n  ASSERT_EQ(result[5].change, PointerData::Change::kRemove);\n  ASSERT_EQ(result[5].synthesized, 0);\n}\n\nTEST(PointerDataPacketConverterTest, CanSynthesizeDownAndUp) {\n  PointerDataPacketConverter converter;\n  auto packet = std::make_unique<PointerDataPacket>(4);\n  PointerData data;\n  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);\n  packet->SetPointerData(0, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 3.0, 0.0, 1);\n  packet->SetPointerData(1, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 4.0, 0);\n  packet->SetPointerData(2, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 4.0,\n                             0);\n  packet->SetPointerData(3, data);\n  auto converted_packet = converter.Convert(std::move(packet));\n\n  std::vector<PointerData> result;\n  UnpackPointerPacket(result, std::move(converted_packet));\n\n  ASSERT_EQ(result.size(), (size_t)6);\n  ASSERT_EQ(result[0].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[0].synthesized, 0);\n\n  // A hover should be synthesized.\n  ASSERT_EQ(result[1].change, PointerData::Change::kHover);\n  ASSERT_EQ(result[1].synthesized, 1);\n  ASSERT_EQ(result[1].physical_delta_x, 3.0);\n  ASSERT_EQ(result[1].physical_delta_y, 0.0);\n  ASSERT_EQ(result[1].buttons, 0);\n\n  ASSERT_EQ(result[2].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[2].pointer_identifier, 1);\n  ASSERT_EQ(result[2].synthesized, 0);\n  ASSERT_EQ(result[2].buttons, 1);\n\n  // A move should be synthesized.\n  ASSERT_EQ(result[3].change, PointerData::Change::kMove);\n  ASSERT_EQ(result[3].pointer_identifier, 1);\n  ASSERT_EQ(result[3].synthesized, 1);\n  ASSERT_EQ(result[3].physical_delta_x, 0.0);\n  ASSERT_EQ(result[3].physical_delta_y, 4.0);\n  ASSERT_EQ(result[3].buttons, 1);\n\n  ASSERT_EQ(result[4].change, PointerData::Change::kUp);\n  ASSERT_EQ(result[4].pointer_identifier, 1);\n  ASSERT_EQ(result[4].synthesized, 0);\n  ASSERT_EQ(result[4].buttons, 0);\n\n  ASSERT_EQ(result[5].change, PointerData::Change::kRemove);\n  ASSERT_EQ(result[5].synthesized, 0);\n}\n\nTEST(PointerDataPacketConverterTest, CanUpdatePointerIdentifier) {\n  PointerDataPacketConverter converter;\n  auto packet = std::make_unique<PointerDataPacket>(7);\n  PointerData data;\n  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);\n  packet->SetPointerData(0, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0, 1);\n  packet->SetPointerData(1, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0, 0);\n  packet->SetPointerData(2, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0, 1);\n  packet->SetPointerData(3, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 3.0, 0.0, 1);\n  packet->SetPointerData(4, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 0.0, 0);\n  packet->SetPointerData(5, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 0.0,\n                             0);\n  packet->SetPointerData(6, data);\n  auto converted_packet = converter.Convert(std::move(packet));\n\n  std::vector<PointerData> result;\n  UnpackPointerPacket(result, std::move(converted_packet));\n\n  ASSERT_EQ(result.size(), (size_t)7);\n  ASSERT_EQ(result[0].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[0].synthesized, 0);\n\n  ASSERT_EQ(result[1].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[1].pointer_identifier, 1);\n  ASSERT_EQ(result[1].synthesized, 0);\n\n  ASSERT_EQ(result[2].change, PointerData::Change::kUp);\n  ASSERT_EQ(result[2].pointer_identifier, 1);\n  ASSERT_EQ(result[2].synthesized, 0);\n\n  // Pointer count increase to 2.\n  ASSERT_EQ(result[3].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[3].pointer_identifier, 2);\n  ASSERT_EQ(result[3].synthesized, 0);\n\n  ASSERT_EQ(result[4].change, PointerData::Change::kMove);\n  ASSERT_EQ(result[4].pointer_identifier, 2);\n  ASSERT_EQ(result[4].synthesized, 0);\n  ASSERT_EQ(result[4].physical_delta_x, 3.0);\n  ASSERT_EQ(result[4].physical_delta_y, 0.0);\n\n  ASSERT_EQ(result[5].change, PointerData::Change::kUp);\n  ASSERT_EQ(result[5].pointer_identifier, 2);\n  ASSERT_EQ(result[5].synthesized, 0);\n\n  ASSERT_EQ(result[6].change, PointerData::Change::kRemove);\n  ASSERT_EQ(result[6].synthesized, 0);\n}\n\nTEST(PointerDataPacketConverterTest, AlwaysForwardMoveEvent) {\n  PointerDataPacketConverter converter;\n  auto packet = std::make_unique<PointerDataPacket>(4);\n  PointerData data;\n  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);\n  packet->SetPointerData(0, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0, 1);\n  packet->SetPointerData(1, data);\n  // Creates a move event without a location change.\n  CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 0.0, 0.0, 1);\n  packet->SetPointerData(2, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0, 0);\n  packet->SetPointerData(3, data);\n\n  auto converted_packet = converter.Convert(std::move(packet));\n\n  std::vector<PointerData> result;\n  UnpackPointerPacket(result, std::move(converted_packet));\n\n  ASSERT_EQ(result.size(), (size_t)4);\n  ASSERT_EQ(result[0].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[0].synthesized, 0);\n\n  ASSERT_EQ(result[1].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[1].pointer_identifier, 1);\n  ASSERT_EQ(result[1].synthesized, 0);\n\n  // Does not filter out the move event.\n  ASSERT_EQ(result[2].change, PointerData::Change::kMove);\n  ASSERT_EQ(result[2].pointer_identifier, 1);\n  ASSERT_EQ(result[2].synthesized, 0);\n\n  ASSERT_EQ(result[3].change, PointerData::Change::kUp);\n  ASSERT_EQ(result[3].pointer_identifier, 1);\n  ASSERT_EQ(result[3].synthesized, 0);\n}\n\nTEST(PointerDataPacketConverterTest, CanWorkWithDifferentDevices) {\n  PointerDataPacketConverter converter;\n  auto packet = std::make_unique<PointerDataPacket>(12);\n  PointerData data;\n  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);\n  packet->SetPointerData(0, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0, 1);\n  packet->SetPointerData(1, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kAdd, 1, 0.0, 0.0, 0);\n  packet->SetPointerData(2, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 1, 0.0, 0.0, 1);\n  packet->SetPointerData(3, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0, 0);\n  packet->SetPointerData(4, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0, 1);\n  packet->SetPointerData(5, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kMove, 1, 0.0, 4.0, 1);\n  packet->SetPointerData(6, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 3.0, 0.0, 1);\n  packet->SetPointerData(7, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kUp, 1, 0.0, 4.0, 0);\n  packet->SetPointerData(8, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 0.0, 0);\n  packet->SetPointerData(9, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 0.0,\n                             0);\n  packet->SetPointerData(10, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kRemove, 1, 0.0, 4.0,\n                             0);\n  packet->SetPointerData(11, data);\n  auto converted_packet = converter.Convert(std::move(packet));\n\n  std::vector<PointerData> result;\n  UnpackPointerPacket(result, std::move(converted_packet));\n\n  ASSERT_EQ(result.size(), (size_t)12);\n  ASSERT_EQ(result[0].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[0].device, 0);\n  ASSERT_EQ(result[0].synthesized, 0);\n\n  ASSERT_EQ(result[1].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[1].device, 0);\n  ASSERT_EQ(result[1].pointer_identifier, 1);\n  ASSERT_EQ(result[1].synthesized, 0);\n\n  ASSERT_EQ(result[2].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[2].device, 1);\n  ASSERT_EQ(result[2].synthesized, 0);\n\n  ASSERT_EQ(result[3].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[3].device, 1);\n  ASSERT_EQ(result[3].pointer_identifier, 2);\n  ASSERT_EQ(result[3].synthesized, 0);\n\n  ASSERT_EQ(result[4].change, PointerData::Change::kUp);\n  ASSERT_EQ(result[4].device, 0);\n  ASSERT_EQ(result[4].pointer_identifier, 1);\n  ASSERT_EQ(result[4].synthesized, 0);\n\n  ASSERT_EQ(result[5].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[5].device, 0);\n  ASSERT_EQ(result[5].pointer_identifier, 3);\n  ASSERT_EQ(result[5].synthesized, 0);\n\n  ASSERT_EQ(result[6].change, PointerData::Change::kMove);\n  ASSERT_EQ(result[6].device, 1);\n  ASSERT_EQ(result[6].pointer_identifier, 2);\n  ASSERT_EQ(result[6].synthesized, 0);\n  ASSERT_EQ(result[6].physical_delta_x, 0.0);\n  ASSERT_EQ(result[6].physical_delta_y, 4.0);\n\n  ASSERT_EQ(result[7].change, PointerData::Change::kMove);\n  ASSERT_EQ(result[7].device, 0);\n  ASSERT_EQ(result[7].pointer_identifier, 3);\n  ASSERT_EQ(result[7].synthesized, 0);\n  ASSERT_EQ(result[7].physical_delta_x, 3.0);\n  ASSERT_EQ(result[7].physical_delta_y, 0.0);\n\n  ASSERT_EQ(result[8].change, PointerData::Change::kUp);\n  ASSERT_EQ(result[8].device, 1);\n  ASSERT_EQ(result[8].pointer_identifier, 2);\n  ASSERT_EQ(result[8].synthesized, 0);\n\n  ASSERT_EQ(result[9].change, PointerData::Change::kUp);\n  ASSERT_EQ(result[9].device, 0);\n  ASSERT_EQ(result[9].pointer_identifier, 3);\n  ASSERT_EQ(result[9].synthesized, 0);\n\n  ASSERT_EQ(result[10].change, PointerData::Change::kRemove);\n  ASSERT_EQ(result[10].device, 0);\n  ASSERT_EQ(result[10].synthesized, 0);\n\n  ASSERT_EQ(result[11].change, PointerData::Change::kRemove);\n  ASSERT_EQ(result[11].device, 1);\n  ASSERT_EQ(result[11].synthesized, 0);\n}\n\nTEST(PointerDataPacketConverterTest, CanSynthesizeAdd) {\n  PointerDataPacketConverter converter;\n  auto packet = std::make_unique<PointerDataPacket>(2);\n  PointerData data;\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 330.0, 450.0,\n                             1);\n  packet->SetPointerData(0, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0, 0);\n  packet->SetPointerData(1, data);\n  auto converted_packet = converter.Convert(std::move(packet));\n\n  std::vector<PointerData> result;\n  UnpackPointerPacket(result, std::move(converted_packet));\n\n  ASSERT_EQ(result.size(), (size_t)4);\n  // A add should be synthesized.\n  ASSERT_EQ(result[0].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[0].physical_x, 330.0);\n  ASSERT_EQ(result[0].physical_y, 450.0);\n  ASSERT_EQ(result[0].synthesized, 1);\n  ASSERT_EQ(result[0].buttons, 0);\n\n  ASSERT_EQ(result[1].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[1].physical_x, 330.0);\n  ASSERT_EQ(result[1].physical_y, 450.0);\n  ASSERT_EQ(result[1].synthesized, 0);\n  ASSERT_EQ(result[1].buttons, 1);\n\n  // A move should be synthesized.\n  ASSERT_EQ(result[2].change, PointerData::Change::kMove);\n  ASSERT_EQ(result[2].physical_delta_x, -330.0);\n  ASSERT_EQ(result[2].physical_delta_y, -450.0);\n  ASSERT_EQ(result[2].physical_x, 0.0);\n  ASSERT_EQ(result[2].physical_y, 0.0);\n  ASSERT_EQ(result[2].synthesized, 1);\n  ASSERT_EQ(result[2].buttons, 1);\n\n  ASSERT_EQ(result[3].change, PointerData::Change::kUp);\n  ASSERT_EQ(result[3].physical_x, 0.0);\n  ASSERT_EQ(result[3].physical_y, 0.0);\n  ASSERT_EQ(result[3].synthesized, 0);\n  ASSERT_EQ(result[3].buttons, 0);\n}\n\nTEST(PointerDataPacketConverterTest, CanHandleThreeFingerGesture) {\n  // Regression test https://github.com/flutter/flutter/issues/20517.\n  PointerDataPacketConverter converter;\n  PointerData data;\n  std::vector<PointerData> result;\n  // First finger down.\n  auto packet = std::make_unique<PointerDataPacket>(1);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0, 1);\n  packet->SetPointerData(0, data);\n  auto converted_packet = converter.Convert(std::move(packet));\n  UnpackPointerPacket(result, std::move(converted_packet));\n  // Second finger down.\n  packet = std::make_unique<PointerDataPacket>(1);\n  CreateSimulatedPointerData(data, PointerData::Change::kDown, 1, 33.0, 44.0,\n                             1);\n  packet->SetPointerData(0, data);\n  converted_packet = converter.Convert(std::move(packet));\n  UnpackPointerPacket(result, std::move(converted_packet));\n  // Triggers three cancels.\n  packet = std::make_unique<PointerDataPacket>(3);\n  CreateSimulatedPointerData(data, PointerData::Change::kCancel, 1, 33.0, 44.0,\n                             0);\n  packet->SetPointerData(0, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kCancel, 0, 0.0, 0.0,\n                             0);\n  packet->SetPointerData(1, data);\n  CreateSimulatedPointerData(data, PointerData::Change::kCancel, 2, 40.0, 50.0,\n                             0);\n  packet->SetPointerData(2, data);\n  converted_packet = converter.Convert(std::move(packet));\n  UnpackPointerPacket(result, std::move(converted_packet));\n\n  ASSERT_EQ(result.size(), (size_t)6);\n  ASSERT_EQ(result[0].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[0].device, 0);\n  ASSERT_EQ(result[0].physical_x, 0.0);\n  ASSERT_EQ(result[0].physical_y, 0.0);\n  ASSERT_EQ(result[0].synthesized, 1);\n  ASSERT_EQ(result[0].buttons, 0);\n\n  ASSERT_EQ(result[1].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[1].device, 0);\n  ASSERT_EQ(result[1].physical_x, 0.0);\n  ASSERT_EQ(result[1].physical_y, 0.0);\n  ASSERT_EQ(result[1].synthesized, 0);\n  ASSERT_EQ(result[1].buttons, 1);\n\n  ASSERT_EQ(result[2].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[2].device, 1);\n  ASSERT_EQ(result[2].physical_x, 33.0);\n  ASSERT_EQ(result[2].physical_y, 44.0);\n  ASSERT_EQ(result[2].synthesized, 1);\n  ASSERT_EQ(result[2].buttons, 0);\n\n  ASSERT_EQ(result[3].change, PointerData::Change::kDown);\n  ASSERT_EQ(result[3].device, 1);\n  ASSERT_EQ(result[3].physical_x, 33.0);\n  ASSERT_EQ(result[3].physical_y, 44.0);\n  ASSERT_EQ(result[3].synthesized, 0);\n  ASSERT_EQ(result[3].buttons, 1);\n\n  ASSERT_EQ(result[4].change, PointerData::Change::kCancel);\n  ASSERT_EQ(result[4].device, 1);\n  ASSERT_EQ(result[4].physical_x, 33.0);\n  ASSERT_EQ(result[4].physical_y, 44.0);\n  ASSERT_EQ(result[4].synthesized, 0);\n\n  ASSERT_EQ(result[5].change, PointerData::Change::kCancel);\n  ASSERT_EQ(result[5].device, 0);\n  ASSERT_EQ(result[5].physical_x, 0.0);\n  ASSERT_EQ(result[5].physical_y, 0.0);\n  ASSERT_EQ(result[5].synthesized, 0);\n  // Third cancel should be dropped\n}\n\nTEST(PointerDataPacketConverterTest, CanConvertPointerSignals) {\n  PointerData::SignalKind signal_kinds[] = {\n      PointerData::SignalKind::kScroll,\n      PointerData::SignalKind::kScrollInertiaCancel,\n      PointerData::SignalKind::kScale,\n  };\n  for (const PointerData::SignalKind& kind : signal_kinds) {\n    PointerDataPacketConverter converter;\n    auto packet = std::make_unique<PointerDataPacket>(6);\n    PointerData data;\n    CreateSimulatedMousePointerData(data, PointerData::Change::kAdd,\n                                    PointerData::SignalKind::kNone, 0, 0.0, 0.0,\n                                    0.0, 0.0, 0);\n    packet->SetPointerData(0, data);\n    CreateSimulatedMousePointerData(data, PointerData::Change::kAdd,\n                                    PointerData::SignalKind::kNone, 1, 0.0, 0.0,\n                                    0.0, 0.0, 0);\n    packet->SetPointerData(1, data);\n    CreateSimulatedMousePointerData(data, PointerData::Change::kDown,\n                                    PointerData::SignalKind::kNone, 1, 0.0, 0.0,\n                                    0.0, 0.0, 1);\n    packet->SetPointerData(2, data);\n    CreateSimulatedMousePointerData(data, PointerData::Change::kHover, kind, 0,\n                                    34.0, 34.0, 30.0, 0.0, 0);\n    packet->SetPointerData(3, data);\n    CreateSimulatedMousePointerData(data, PointerData::Change::kHover, kind, 1,\n                                    49.0, 49.0, 50.0, 0.0, 0);\n    packet->SetPointerData(4, data);\n    CreateSimulatedMousePointerData(data, PointerData::Change::kHover, kind, 2,\n                                    10.0, 20.0, 30.0, 40.0, 0);\n    packet->SetPointerData(5, data);\n    auto converted_packet = converter.Convert(std::move(packet));\n\n    std::vector<PointerData> result;\n    UnpackPointerPacket(result, std::move(converted_packet));\n\n    ASSERT_EQ(result.size(), (size_t)9);\n    ASSERT_EQ(result[0].change, PointerData::Change::kAdd);\n    ASSERT_EQ(result[0].signal_kind, PointerData::SignalKind::kNone);\n    ASSERT_EQ(result[0].device, 0);\n    ASSERT_EQ(result[0].physical_x, 0.0);\n    ASSERT_EQ(result[0].physical_y, 0.0);\n    ASSERT_EQ(result[0].synthesized, 0);\n\n    ASSERT_EQ(result[1].change, PointerData::Change::kAdd);\n    ASSERT_EQ(result[1].signal_kind, PointerData::SignalKind::kNone);\n    ASSERT_EQ(result[1].device, 1);\n    ASSERT_EQ(result[1].physical_x, 0.0);\n    ASSERT_EQ(result[1].physical_y, 0.0);\n    ASSERT_EQ(result[1].synthesized, 0);\n\n    ASSERT_EQ(result[2].change, PointerData::Change::kDown);\n    ASSERT_EQ(result[2].signal_kind, PointerData::SignalKind::kNone);\n    ASSERT_EQ(result[2].device, 1);\n    ASSERT_EQ(result[2].physical_x, 0.0);\n    ASSERT_EQ(result[2].physical_y, 0.0);\n    ASSERT_EQ(result[2].synthesized, 0);\n\n    // Converter will synthesize a hover to position for device 0.\n    ASSERT_EQ(result[3].change, PointerData::Change::kHover);\n    ASSERT_EQ(result[3].signal_kind, PointerData::SignalKind::kNone);\n    ASSERT_EQ(result[3].device, 0);\n    ASSERT_EQ(result[3].physical_x, 34.0);\n    ASSERT_EQ(result[3].physical_y, 34.0);\n    ASSERT_EQ(result[3].physical_delta_x, 34.0);\n    ASSERT_EQ(result[3].physical_delta_y, 34.0);\n    ASSERT_EQ(result[3].buttons, 0);\n    ASSERT_EQ(result[3].synthesized, 1);\n\n    ASSERT_EQ(result[4].change, PointerData::Change::kHover);\n    ASSERT_EQ(result[4].signal_kind, kind);\n    ASSERT_EQ(result[4].device, 0);\n    ASSERT_EQ(result[4].physical_x, 34.0);\n    ASSERT_EQ(result[4].physical_y, 34.0);\n    ASSERT_EQ(result[4].scroll_delta_x, 30.0);\n    ASSERT_EQ(result[4].scroll_delta_y, 0.0);\n\n    // Converter will synthesize a move to position for device 1.\n    ASSERT_EQ(result[5].change, PointerData::Change::kMove);\n    ASSERT_EQ(result[5].signal_kind, PointerData::SignalKind::kNone);\n    ASSERT_EQ(result[5].device, 1);\n    ASSERT_EQ(result[5].physical_x, 49.0);\n    ASSERT_EQ(result[5].physical_y, 49.0);\n    ASSERT_EQ(result[5].physical_delta_x, 49.0);\n    ASSERT_EQ(result[5].physical_delta_y, 49.0);\n    ASSERT_EQ(result[5].buttons, 1);\n    ASSERT_EQ(result[5].synthesized, 1);\n\n    ASSERT_EQ(result[6].change, PointerData::Change::kHover);\n    ASSERT_EQ(result[6].signal_kind, kind);\n    ASSERT_EQ(result[6].device, 1);\n    ASSERT_EQ(result[6].physical_x, 49.0);\n    ASSERT_EQ(result[6].physical_y, 49.0);\n    ASSERT_EQ(result[6].scroll_delta_x, 50.0);\n    ASSERT_EQ(result[6].scroll_delta_y, 0.0);\n\n    // Converter will synthesize an add for device 2.\n    ASSERT_EQ(result[7].change, PointerData::Change::kAdd);\n    ASSERT_EQ(result[7].signal_kind, PointerData::SignalKind::kNone);\n    ASSERT_EQ(result[7].device, 2);\n    ASSERT_EQ(result[7].physical_x, 10.0);\n    ASSERT_EQ(result[7].physical_y, 20.0);\n    ASSERT_EQ(result[7].synthesized, 1);\n\n    ASSERT_EQ(result[8].change, PointerData::Change::kHover);\n    ASSERT_EQ(result[8].signal_kind, kind);\n    ASSERT_EQ(result[8].device, 2);\n    ASSERT_EQ(result[8].physical_x, 10.0);\n    ASSERT_EQ(result[8].physical_y, 20.0);\n    ASSERT_EQ(result[8].scroll_delta_x, 30.0);\n    ASSERT_EQ(result[8].scroll_delta_y, 40.0);\n  }\n}\n\nTEST(PointerDataPacketConverterTest, CanConvertTrackpadGesture) {\n  PointerDataPacketConverter converter;\n  auto packet = std::make_unique<PointerDataPacket>(3);\n  PointerData data;\n  CreateSimulatedTrackpadGestureData(data, PointerData::Change::kPanZoomStart,\n                                     0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);\n  packet->SetPointerData(0, data);\n  CreateSimulatedTrackpadGestureData(data, PointerData::Change::kPanZoomUpdate,\n                                     0, 0.0, 0.0, 3.0, 4.0, 1.0, 0.0);\n  packet->SetPointerData(1, data);\n  CreateSimulatedTrackpadGestureData(data, PointerData::Change::kPanZoomEnd, 0,\n                                     0.0, 0.0, 0.0, 0.0, 1.0, 0.0);\n  packet->SetPointerData(2, data);\n  auto converted_packet = converter.Convert(std::move(packet));\n\n  std::vector<PointerData> result;\n  UnpackPointerPacket(result, std::move(converted_packet));\n\n  ASSERT_EQ(result.size(), (size_t)4);\n  ASSERT_EQ(result[0].change, PointerData::Change::kAdd);\n  ASSERT_EQ(result[0].device, 0);\n  ASSERT_EQ(result[0].synthesized, 1);\n\n  ASSERT_EQ(result[1].change, PointerData::Change::kPanZoomStart);\n  ASSERT_EQ(result[1].signal_kind, PointerData::SignalKind::kNone);\n  ASSERT_EQ(result[1].device, 0);\n  ASSERT_EQ(result[1].physical_x, 0.0);\n  ASSERT_EQ(result[1].physical_y, 0.0);\n  ASSERT_EQ(result[1].synthesized, 0);\n\n  ASSERT_EQ(result[2].change, PointerData::Change::kPanZoomUpdate);\n  ASSERT_EQ(result[2].signal_kind, PointerData::SignalKind::kNone);\n  ASSERT_EQ(result[2].device, 0);\n  ASSERT_EQ(result[2].physical_x, 0.0);\n  ASSERT_EQ(result[2].physical_y, 0.0);\n  ASSERT_EQ(result[2].pan_x, 3.0);\n  ASSERT_EQ(result[2].pan_y, 4.0);\n  ASSERT_EQ(result[2].pan_delta_x, 3.0);\n  ASSERT_EQ(result[2].pan_delta_y, 4.0);\n  ASSERT_EQ(result[2].scale, 1.0);\n  ASSERT_EQ(result[2].rotation, 0.0);\n  ASSERT_EQ(result[2].synthesized, 0);\n\n  ASSERT_EQ(result[3].change, PointerData::Change::kPanZoomEnd);\n  ASSERT_EQ(result[3].signal_kind, PointerData::SignalKind::kNone);\n  ASSERT_EQ(result[3].device, 0);\n  ASSERT_EQ(result[3].physical_x, 0.0);\n  ASSERT_EQ(result[3].physical_y, 0.0);\n  ASSERT_EQ(result[3].synthesized, 0);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/pointer_data_packet_unittests.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <cstring>\n\n#include \"clay/ui/window/pointer_data.h\"\n#include \"clay/ui/window/pointer_data_packet.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace clay {\nnamespace testing {\n\nvoid CreateSimpleSimulatedPointerData(PointerData& data,  // NOLINT\n                                      PointerData::Change change,\n                                      int64_t device, double dx, double dy,\n                                      int64_t buttons) {\n  data.time_stamp = 0;\n  data.change = change;\n  data.kind = PointerData::DeviceKind::kTouch;\n  data.signal_kind = PointerData::SignalKind::kNone;\n  data.device = device;\n  data.pointer_identifier = 0;\n  data.physical_x = dx;\n  data.physical_y = dy;\n  data.physical_delta_x = 0.0;\n  data.physical_delta_y = 0.0;\n  data.buttons = buttons;\n  data.obscured = 0;\n  data.synthesized = 0;\n  data.pressure = 0.0;\n  data.pressure_min = 0.0;\n  data.pressure_max = 0.0;\n  data.distance = 0.0;\n  data.distance_max = 0.0;\n  data.size = 0.0;\n  data.radius_major = 0.0;\n  data.radius_minor = 0.0;\n  data.radius_min = 0.0;\n  data.radius_max = 0.0;\n  data.orientation = 0.0;\n  data.tilt = 0.0;\n  data.platformData = 0;\n  data.scroll_delta_x = 0.0;\n  data.scroll_delta_y = 0.0;\n}\n\nTEST(PointerDataPacketTest, CanGetPointerData) {\n  auto packet = std::make_unique<PointerDataPacket>(1);\n  PointerData data;\n  CreateSimpleSimulatedPointerData(data, PointerData::Change::kAdd, 1, 2.0, 3.0,\n                                   4);\n  packet->SetPointerData(0, data);\n\n  PointerData data_recovered = packet->GetPointerData(0);\n  ASSERT_EQ(data_recovered.physical_x, 2.0);\n  ASSERT_EQ(data_recovered.physical_y, 3.0);\n}\n\nTEST(PointerDataPacketTest, CanGetLength) {\n  auto packet = std::make_unique<PointerDataPacket>(6);\n  ASSERT_EQ(packet->GetLength(), (size_t)6);\n}\n\n}  // namespace testing\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/viewport_metrics.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"clay/ui/window/viewport_metrics.h\"\n\nnamespace clay {\n\nconstexpr int32_t kDensityMedium = 160;\n\nViewportMetrics::ViewportMetrics() = default;\n\nViewportMetrics::ViewportMetrics(double p_device_pixel_ratio,\n                                 double p_physical_width,\n                                 double p_physical_height,\n                                 double p_physical_touch_slop)\n    : device_pixel_ratio(p_device_pixel_ratio),\n      device_density_dpi(p_device_pixel_ratio * kDensityMedium),\n      physical_width(p_physical_width),\n      physical_height(p_physical_height),\n      physical_touch_slop(p_physical_touch_slop) {}\n\nViewportMetrics::ViewportMetrics(\n    double p_device_pixel_ratio, double p_device_density_dpi,\n    double p_physical_width, double p_physical_height,\n    double p_physical_padding_top, double p_physical_padding_right,\n    double p_physical_padding_bottom, double p_physical_padding_left,\n    double p_physical_view_inset_top, double p_physical_view_inset_right,\n    double p_physical_view_inset_bottom, double p_physical_view_inset_left,\n    double p_physical_system_gesture_inset_top,\n    double p_physical_system_gesture_inset_right,\n    double p_physical_system_gesture_inset_bottom,\n    double p_physical_system_gesture_inset_left, double p_physical_touch_slop,\n    const std::vector<double>& p_physical_display_features_bounds,\n    const std::vector<int>& p_physical_display_features_type,\n    const std::vector<int>& p_physical_display_features_state,\n    double p_physical_screen_width, double p_physical_screen_height,\n    double p_font_scale, bool p_night_mode)\n    : device_pixel_ratio(p_device_pixel_ratio),\n      device_density_dpi(p_device_density_dpi),\n      physical_width(p_physical_width),\n      physical_height(p_physical_height),\n      physical_padding_top(p_physical_padding_top),\n      physical_padding_right(p_physical_padding_right),\n      physical_padding_bottom(p_physical_padding_bottom),\n      physical_padding_left(p_physical_padding_left),\n      physical_view_inset_top(p_physical_view_inset_top),\n      physical_view_inset_right(p_physical_view_inset_right),\n      physical_view_inset_bottom(p_physical_view_inset_bottom),\n      physical_view_inset_left(p_physical_view_inset_left),\n      physical_system_gesture_inset_top(p_physical_system_gesture_inset_top),\n      physical_system_gesture_inset_right(\n          p_physical_system_gesture_inset_right),\n      physical_system_gesture_inset_bottom(\n          p_physical_system_gesture_inset_bottom),\n      physical_system_gesture_inset_left(p_physical_system_gesture_inset_left),\n      physical_touch_slop(p_physical_touch_slop),\n      physical_display_features_bounds(p_physical_display_features_bounds),\n      physical_display_features_type(p_physical_display_features_type),\n      physical_display_features_state(p_physical_display_features_state),\n      physical_screen_width(p_physical_screen_width),\n      physical_screen_height(p_physical_screen_height),\n      font_scale(p_font_scale),\n      night_mode(p_night_mode) {}\n\nbool NeedsToChangeSize(const ViewportMetrics& old_metrics,\n                       const ViewportMetrics& new_metrics) {\n  return old_metrics.physical_width != new_metrics.physical_width ||\n         old_metrics.physical_height != new_metrics.physical_height ||\n         old_metrics.device_pixel_ratio != new_metrics.device_pixel_ratio ||\n         old_metrics.device_density_dpi != new_metrics.device_density_dpi;\n}\n\nbool NeedsToChangeSystemParameters(const ViewportMetrics& old_metrics,\n                                   const ViewportMetrics& new_metrics) {\n  return old_metrics.font_scale != new_metrics.font_scale ||\n         old_metrics.night_mode != new_metrics.night_mode;\n}\n\nbool operator==(const ViewportMetrics& a, const ViewportMetrics& b) {\n  return a.device_pixel_ratio == b.device_pixel_ratio &&\n         a.device_density_dpi == b.device_density_dpi &&\n         a.physical_width == b.physical_width &&\n         a.physical_height == b.physical_height &&\n         a.physical_padding_top == b.physical_padding_top &&\n         a.physical_padding_right == b.physical_padding_right &&\n         a.physical_padding_bottom == b.physical_padding_bottom &&\n         a.physical_padding_left == b.physical_padding_left &&\n         a.physical_view_inset_top == b.physical_view_inset_top &&\n         a.physical_view_inset_right == b.physical_view_inset_right &&\n         a.physical_view_inset_bottom == b.physical_view_inset_bottom &&\n         a.physical_view_inset_left == b.physical_view_inset_left &&\n         a.physical_system_gesture_inset_top ==\n             b.physical_system_gesture_inset_top &&\n         a.physical_system_gesture_inset_right ==\n             b.physical_system_gesture_inset_right &&\n         a.physical_system_gesture_inset_bottom ==\n             b.physical_system_gesture_inset_bottom &&\n         a.physical_system_gesture_inset_left ==\n             b.physical_system_gesture_inset_left &&\n         a.physical_touch_slop == b.physical_touch_slop &&\n         a.physical_display_features_bounds ==\n             b.physical_display_features_bounds &&\n         a.physical_display_features_type == b.physical_display_features_type &&\n         a.physical_display_features_state ==\n             b.physical_display_features_state &&\n         a.physical_screen_width == b.physical_screen_width &&\n         a.physical_screen_height == b.physical_screen_height &&\n         a.font_scale == b.font_scale && a.night_mode == b.night_mode;\n}\n\nstd::ostream& operator<<(std::ostream& os, const ViewportMetrics& a) {\n  os << \"DPR: \" << a.device_pixel_ratio << \" \"\n     << \"DPI: \" << a.device_density_dpi << \" \"\n     << \"Size: [\" << a.physical_width << \"W \" << a.physical_height << \"H] \"\n     << \"Padding: [\" << a.physical_padding_top << \"T \"\n     << a.physical_padding_right << \"R \" << a.physical_padding_bottom << \"B \"\n     << a.physical_padding_left << \"L] \"\n     << \"Insets: [\" << a.physical_view_inset_top << \"T \"\n     << a.physical_view_inset_right << \"R \" << a.physical_view_inset_bottom\n     << \"B \" << a.physical_view_inset_left << \"L] \"\n     << \"Gesture Insets: [\" << a.physical_system_gesture_inset_top << \"T \"\n     << a.physical_system_gesture_inset_right << \"R \"\n     << a.physical_system_gesture_inset_bottom << \"B \"\n     << a.physical_system_gesture_inset_left << \"L] \"\n     << \"Display Features: \" << a.physical_display_features_type.size()\n     << \"Screen Width: \" << a.physical_screen_width << \" \"\n     << \"Screen Height: \" << a.physical_screen_height << \" \"\n     << \"Font Scale: \" << a.font_scale << \" \"\n     << \"Night Mode: \" << a.night_mode << \" \";\n  return os;\n}\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/ui/window/viewport_metrics.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_UI_WINDOW_VIEWPORT_METRICS_H_\n#define CLAY_UI_WINDOW_VIEWPORT_METRICS_H_\n\n#include <ostream>\n#include <vector>\n\nnamespace clay {\n\nstruct ViewportMetrics {\n  ViewportMetrics();\n  ViewportMetrics(double p_device_pixel_ratio, double p_physical_width,\n                  double p_physical_height, double p_physical_touch_slop);\n  ViewportMetrics(\n      double p_device_pixel_ratio, double p_device_density_dpi,\n      double p_physical_width, double p_physical_height,\n      double p_physical_padding_top, double p_physical_padding_right,\n      double p_physical_padding_bottom, double p_physical_padding_left,\n      double p_physical_view_inset_top, double p_physical_view_inset_right,\n      double p_physical_view_inset_bottom, double p_physical_view_inset_left,\n      double p_physical_system_gesture_inset_top,\n      double p_physical_system_gesture_inset_right,\n      double p_physical_system_gesture_inset_bottom,\n      double p_physical_system_gesture_inset_left, double p_physical_touch_slop,\n      const std::vector<double>& p_physical_display_features_bounds,\n      const std::vector<int>& p_physical_display_features_type,\n      const std::vector<int>& p_physical_display_features_state,\n      double p_physical_screen_width = 0, double p_physical_screen_height = 0,\n      double p_font_scale = 1.0, bool p_night_node = false);\n\n  double device_pixel_ratio = 1.0;\n  double device_density_dpi = 0;\n  double physical_width = 0;\n  double physical_height = 0;\n  double physical_padding_top = 0;\n  double physical_padding_right = 0;\n  double physical_padding_bottom = 0;\n  double physical_padding_left = 0;\n  double physical_view_inset_top = 0;\n  double physical_view_inset_right = 0;\n  double physical_view_inset_bottom = 0;\n  double physical_view_inset_left = 0;\n  double physical_system_gesture_inset_top = 0;\n  double physical_system_gesture_inset_right = 0;\n  double physical_system_gesture_inset_bottom = 0;\n  double physical_system_gesture_inset_left = 0;\n  double physical_touch_slop = -1.0;\n  std::vector<double> physical_display_features_bounds;\n  std::vector<int> physical_display_features_type;\n  std::vector<int> physical_display_features_state;\n  double physical_screen_width = 0;\n  double physical_screen_height = 0;\n  double font_scale = 1.0;\n  bool night_mode = false;\n};\n\nbool NeedsToChangeSize(const ViewportMetrics& old_metrics,\n                       const ViewportMetrics& new_metrics);\nbool NeedsToChangeSystemParameters(const ViewportMetrics& old_metrics,\n                                   const ViewportMetrics& new_metrics);\nbool operator==(const ViewportMetrics& a, const ViewportMetrics& b);\nstd::ostream& operator<<(std::ostream& os, const ViewportMetrics& a);\n\n}  // namespace clay\n\n#endif  // CLAY_UI_WINDOW_VIEWPORT_METRICS_H_\n"
  },
  {
    "path": "clay/version/BUILD.gn",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../common/config.gni\")\nimport(\"version.gni\")\n\n# Use a version.inc file instead of defining macors in the source set\n# since on Android, after gn to cmake process, the belowing macros may leak\n# to other sources files and result in recompile.\naction(\"version_inc\") {\n  script = \"write_file.py\"\n\n  args = [\n    \"--output\",\n    rebase_path(\"$root_gen_dir/clay/version/version.inc\"),\n    \"--content\",\n    string_join(\"\\n\",\n                [\n                  \"// Generated by clay/version:version_inc\",\n                  \"#define ENGINE_VERSION \\\"$engine_version\\\"\",\n                  \"#define SKIA_VERSION \\\"$skia_version\\\"\",\n                  \"#define CLAY_BUILD_VERSION \\\"$build_version\\\"\",\n                  \"#define CLAY_BUILD_NUMBER $build_number\",\n                  \"\",\n                ]),\n  ]\n\n  outputs = [ \"$root_gen_dir/clay/version/version.inc\" ]\n}\n\nsource_set(\"version\") {\n  sources = [\n    \"version.cc\",\n    \"version.h\",\n  ]\n\n  public_configs = [ \"../:config\" ]\n\n  deps = [\n    \":version_inc\",\n    \"../fml:fml\",\n  ]\n}\n"
  },
  {
    "path": "clay/version/version.cc",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// FLUTTER_NOLINT: https://github.com/flutter/flutter/issues/68332\n\n#include \"clay/version/version.h\"\n\n#if !(OS_IOS && ENABLE_SKITY)\n#include \"clay/version/version.inc\"\n#endif\n\nnamespace clay {\n\nconst char* GetEngineVersion() { return ENGINE_VERSION; }\n\nconst char* GetSkiaVersion() { return SKIA_VERSION; }\n\nconst char* GetBuildVersion() { return CLAY_BUILD_VERSION; }\n\nint GetBuildNumber() { return CLAY_BUILD_NUMBER; }\n\n}  // namespace clay\n"
  },
  {
    "path": "clay/version/version.gni",
    "content": "# Copyright 2013 The Flutter Authors. All rights reserved.\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../common/config.gni\")\n\ndeclare_args() {\n  engine_version = \"\"\n\n  skia_version = \"\"\n}\n\n_flutter_root = \"../\"\n\nif (engine_version == \"\") {\n  engine_version_lines =\n      exec_script(\"../build/git_revision.py\",\n                  [\n                    \"--repository\",\n                    rebase_path(_flutter_root, \"\", _flutter_root),\n                  ],\n                  \"list lines\")\n  engine_version = engine_version_lines[0]\n}\n\nif (!enable_skity) {\n  if (skia_version == \"\") {\n    skia_version_lines =\n        exec_script(\"../build/git_revision.py\",\n                    [\n                      \"--repository\",\n                      rebase_path(\"//third_party/skia\", \"\", _flutter_root),\n                    ],\n                    \"list lines\")\n    skia_version = skia_version_lines[0]\n  }\n} else {\n  # When using skity, set a default skia_version to avoid undefined variable issues\n  if (skia_version == \"\") {\n    skia_version = \"skity-enabled\"\n  }\n}\n"
  },
  {
    "path": "clay/version/version.h",
    "content": "// Copyright 2013 The Flutter Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CLAY_VERSION_VERSION_H_\n#define CLAY_VERSION_VERSION_H_\n\n#include \"build/build_config.h\"\n\nnamespace clay {\n\nconst char* GetEngineVersion();\n\nconst char* GetSkiaVersion();\n\nconst char* GetBuildVersion();\n\nint GetBuildNumber();\n\n}  // namespace clay\n\n#endif  // CLAY_VERSION_VERSION_H_\n"
  },
  {
    "path": "clay/version/write_file.py",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\nimport os\nimport argparse\n\ndef main():\n  parser = argparse.ArgumentParser(description='Write content to a file if it has changed.')\n  parser.add_argument('--output', required=True, help='Path to the output file')\n  parser.add_argument('--content', required=True, help='Content to write to the file')\n  args = parser.parse_args()\n\n  content = args.content.replace('\\\\n', '\\n')\n\n  if os.path.exists(args.output):\n    with open(args.output, 'r') as file:\n      existing_content = file.read()\n    if existing_content == content:\n      print(f\"Content in {args.output} is already up to date. No changes made.\")\n      return\n\n  with open(args.output, 'w') as file:\n    file.write(content)\n  print(f\"Content written to {args.output}\")\n\nif __name__ == \"__main__\":\n  main()\n"
  },
  {
    "path": "config.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nif (is_android) {\n  import(\"//build/config/android/config.gni\")\n} else if (is_harmony) {\n  import(\"//build/config/harmony/config.gni\")\n}\nif (target_cpu == \"arm\" || target_cpu == \"arm64\") {\n  import(\"//build/config/arm.gni\")\n}\n\nimport(\"//build_overrides/oliver.gni\")\n\n# global configs\ndeclare_args() {\n  # Whether to enable trace\n  # There are three modes: \"perfetto\", \"systrace\", \"none\"\n  # 1. \"perfetto\" : enable perfetto trace\n  # 2. \"systrace\" : enable system trace, only for Android\n  # 3. \"none\" : disable trace, default value\n  enable_trace = \"none\"\n\n  build_number = \"1\"\n  build_version = \"1.0.0\"\n\n  # Whether to enable unit tests\n  enable_unittests = false\n\n  build_lynx_lepus_node = false\n\n  enable_fuzzertests = false\n\n  # Whether to enable encoder unit tests\n  is_encoder_ut = false\n\n  # Whether enable v8 engine.\n  enable_v8 = true\n\n  # Whether enable frozen mode. Frozen mode is used for ut to disable log printing\n  enable_frozen_mode = false\n\n  # Build lite production\n  enable_lite = false\n  enable_lite_production = false\n\n  # Whether enable recorder.\n  enable_recorder = is_debug\n\n  # Whether enable richtext.\n  enable_richtext = false\n\n  # use napi from primjs with primjs suffix.\n  use_primjs_napi = false\n\n  # enable_lepusng_worklet corresponds the macro of ENABLE_LEPUSNG_WORKLET\n  enable_lepusng_worklet = false\n\n  # enable_inspector corresponds the macro of ENABLE_INSPECTOR\n  enable_inspector = false\n\n  # enable_inspector_test corresponds the macro of INSPECTOR_TEST\n  enable_inspector_test = false\n\n  # disable primjs symbol hidden\n  disable_primjs_symbol_visibility_hidden = false\n\n  # enable primjs prebuilt lib\n  enable_primjs_prebuilt_lib = false\n\n  # Which directory the Node header will be copied into\n  node_headers_dst_dir = node_headers_dst\n\n  # enable lynx clang static checker plugin\n  enable_lynx_clang_plugin = false\n\n  # enable 16kb align for android\n  enable_16kb_align = false\n\n  # compiler optimization level\n  compiler_optimization_level = \"Oz\"\n\n  enable_harmony_shared = false\n\n  # Controls the symbol visibility of the lynx library.\n  # If true, symbols decorated with `LYNX_EXPORT` are exported.\n  # Otherwise, no symbols from this library are exported.\n  lynx_export_symbols = true\n\n  # Whether to enable headless engine with host compiling.\n  is_headless = false\n\n  # Whether to enable embedder layer for desktop.\n  # If true, we can eliminate some unused platform-layer compilation units.\n  desktop_enable_embedder_layer = false\n\n  # Controls whether to use weak_node_api symbols with the _weak suffix. In some cases, this configuration needs to be enabled to avoid conflicts with other NAPI symbols.\n  use_weak_suffix_napi = true\n}\n\ndeclare_args() {\n  v8_deps = [ \"//third_party/NativeScript:NativeScript\" ]\n  if (is_ios || is_mac) {\n    v8_headers_search_path = \"//$lynx_dir/third_party/NativeScript/include\"\n  } else {\n    v8_headers_search_path = rebase_path(\"third_party/v8/11.1.277/include\")\n  }\n}\n\nif (is_android) {\n  enable_inspector = !enable_lite_production\n  enable_inspector_test = !enable_lite_production\n} else if (is_ios || is_mac) {\n  enable_inspector_test = true\n}\n\nif (enable_lite) {\n  # these args are up to enable_lite arg\n  enable_v8 = false\n  enable_lepusng_worklet = false\n}\n"
  },
  {
    "path": "core/AGENTS.md",
    "content": "# AGENTS.md\n\n## Scope\n\nThis directory contains the native engine core of Lynx: animation, event dispatch, renderer, runtime, shell orchestration, shared data, services, style data types, resource loading, template bundle handling, and supporting base utilities.\n\nChild `AGENTS.md` files take precedence when they exist. Use this file to choose the right layer before editing.\n\n## Module Map\n\n- `animation/`: CSS animation primitives, transition/keyframe management, and the shared basic-animation foundation layer.\n- `base/`: core-level threading, VSync, JSON, observer, and platform utility helpers used across the engine.\n- `event/`: platform-agnostic event model, listener matching, and dispatch semantics.\n- `inspector/`: inspector-facing observer and runtime inspection interfaces.\n- `list/`: decoupled list container abstractions and list animation helpers.\n- `parser/`: small parser utilities such as `InputStream`.\n- `public/`: stable proxy and interface headers consumed across core and platform layers.\n- `renderer/`: TASM, CSS, DOM, layout/pipeline, starlight, worklets, and renderer utilities.\n- `resource/`: resource loading and lazy-bundle support.\n- `runtime/`: JS runtime glue, Lepus/LepusNG execution stacks, bindings, and profiling.\n- `services/`: performance, event report, replay, recorder, timing, and other cross-cutting services.\n- `shared_data/`: white-board style shared state between runtime, renderer, and shell.\n- `shell/`: multi-threaded shell orchestration, actor/proxy boundaries, and platform bridges.\n- `style/`: typed style data objects and transform math.\n- `template_bundle/`: template bundle wrappers and binary codec layers.\n- `value_wrapper/`: conversions between public values, Lepus values, NAPI values, and platform representations.\n- `build/`, `include/`: supporting build or umbrella-header directories. These usually do not need directory-local guidance.\n\n## Layer Choice\n\n- If a change is about CSS tokens, DOM nodes, layout invalidation, or page assembly, start in `renderer/`.\n- If a change is about JS execution, native module binding, Lepus bytecode, or runtime lifecycle, start in `runtime/`.\n- If a change is about thread ownership, cross-thread `Act()` calls, native-platform bridges, or queueing work between shell actors, start in `shell/`.\n- If a change is about shared contracts rather than implementations, check `public/` before changing concrete classes.\n- If a change is only a data holder or math helper for style values, start in `style/` rather than `renderer/` or `animation/`.\n\n## Edit Rules\n\n- Prefer the narrowest module that owns the behavior. Do not push renderer logic into `base/` or runtime semantics into `shell/`.\n- Treat `public/` headers as shared API surface. Signature changes there usually require coordinated updates in core and platform code.\n- Keep platform-specific implementations in their platform subdirectories; keep shared semantics in the root of each module.\n- When a directory already has its own `AGENTS.md`, follow the child document instead of generalizing from this file.\n\n## Validate\n\nFor C++ unit tests under `lynx/core`, prefer the `lynx-cpp-test` skill. Resolve the exact exec target from the nearest `BUILD.gn`, then run the smallest relevant target first.\n\nCommon starting points by area:\n\n- `animation_unittests_exec`, `basic_animation_unittests_exec`, `lynx_basic_animator_unittests_exec`\n- `event_unittests_exec`\n- `lynx_base_unittests_exec`\n- `dom_unittest_exec`, `css_test_exec`, `pipeline_test_exec`, `starlight_unittest_exec`\n- `runtime_tests_exec`, `lepus_unittests_exec`, `js_runtime_unittests_exec`\n- `shell_unittests_exec`\n- `shared_data_test_exec`\n- `lazy_bundle_test_exec`\n- `value_wrapper_unittest_exec`\n\n## Notes\n\n- `core/BUILD.gn` assembles many modules together under `lynx_native`, so interface changes often surface outside the directory you touched.\n- Many subdirectories under `lynx/core/` now provide their own `AGENTS.md`. Prefer the nearest child guide over this root summary whenever one exists.\n"
  },
  {
    "path": "core/BUILD.gn",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//build/util/write_cmake_config.gni\")\nimport(\"../config.gni\")\nimport(\"Lynx.gni\")\n\n# This target is used to contain configs that are required globally.\n# This config is added to the basic xxx_source_set template.\n# If you are using the basic xxx_source_set template, you do not need to add this config.\nconfig(\"lynx_public_config\") {\n  ldflags = []\n  include_dirs = []\n  cflags = []\n  defines = []\n  include_dirs += [\n    \"..\",\n    \"../platform\",\n    \"../core\",\n    \"build/gen\",\n    \"../third_party\",\n  ]\n\n  if (!lynx_export_symbols) {\n    defines += [ \"LYNX_NO_EXPORT\" ]\n  }\n\n  cflags += [\n    \"-Wno-c99-designator\",\n    \"-Wno-unknown-warning-option\",\n    \"-Wno-sign-compare\",\n    \"-Wno-unused-but-set-variable\",\n    \"-Wno-unused-parameter\",\n  ]\n  defines += [\n    \"LYNX_ENABLE_E2E_TEST=${enable_e2e_test}\",\n    \"ENABLE_TESTBENCH_REPLAY=${enable_testbench_replay}\",\n    \"ENABLE_TESTBENCH_RECORDER=${enable_testbench_recorder}\",\n    \"INSPECTOR_TEST=${enable_inspector_test}\",\n    \"ENABLE_LEPUSNG_WORKLET=${enable_lepusng_worklet}\",\n    \"ENABLE_NAPI_BINDING=${enable_napi_binding}\",\n    \"DISABLE_NANBOX=${disable_nanbox}\",\n    \"ENABLE_JUST_LEPUSNG=${enable_just_lepusng}\",\n    \"ENABLE_INSPECTOR=${enable_inspector}\",\n  ]\n\n  if (is_android) {\n    if (is_debug) {\n      defines += [ \"DEBUG_MEMORY\" ]\n    }\n    defines += [\n      \"GNU_SUPPORT=1\",\n      \"OS_ANDROID=1\",\n      \"OS_POSIX=1\",\n    ]\n  } else if (is_ios) {\n    defines += [\n      \"OS_IOS=1\",\n      \"OS_POSIX=1\",\n    ]\n  } else if (is_mac) {\n    if (!enable_unittests && !is_oliver && !is_encoder_ut) {\n      defines += [\n        \"OS_OSX=1\",\n        \"OS_POSIX=1\",\n      ]\n    }\n    defines += [ \"lynx_EXPORTS\" ]\n  }\n\n  if (use_primjs_napi) {\n    defines += [ \"USE_PRIMJS_NAPI\" ]\n  }\n  if (use_weak_suffix_napi) {\n    defines += [ \"USE_WEAK_SUFFIX_NAPI=1\" ]\n  }\n\n  if (enable_trace == \"perfetto\") {\n    defines += [ \"ENABLE_TRACE_PERFETTO=1\" ]\n  } else if (enable_trace == \"systrace\") {\n    defines += [ \"ENABLE_TRACE_SYSTRACE=1\" ]\n  }\n\n  if (!is_debug) {\n    defines += [ \"NDEBUG\" ]\n  }\n\n  defines += [ \"RAPIDJSON_HAS_STDSTRING=1\" ]\n\n  if (is_android) {\n    defines += [\n      \"JS_ENGINE_TYPE=2\",\n      \"QUICKJS_VER_LYNX\",\n    ]\n  } else {\n    if (jsengine_type == \"jsc\") {\n      defines += [ \"JS_ENGINE_TYPE=1\" ]\n    } else if (jsengine_type == \"quickjs\") {\n      defines += [\n        \"JS_ENGINE_TYPE=2\",\n        \"QUICKJS_VER_LYNX\",\n      ]\n    } else if (jsengine_type == \"v8\") {\n      defines += [ \"JS_ENGINE_TYPE=0\" ]\n    }\n  }\n\n  if (is_harmony) {\n    defines += [ \"OS_HARMONY=1\" ]\n    if (enable_inspector) {\n      defines += [ \"EXPORT_SYMBOLS_FOR_DEVTOOL=1\" ]\n    }\n\n    if (jsengine_type == \"jsvm\") {\n      defines += [ \"JS_ENGINE_TYPE=3\" ]\n    }\n  }\n\n  # Attach arguments to turn on Lynx Clang static checker plugin.\n  if (enable_lynx_clang_plugin) {\n    if (host_os == \"linux\") {\n      plugin_dylib = rebase_path(\n              \"../../tools/clang/lynx_clang_plugin/release/libLynxClangPlugin.so\")\n    } else if (host_os == \"mac\") {\n      plugin_dylib = rebase_path(\n              \"../../tools/clang/lynx_clang_plugin/release/libLynxClangPlugin.dylib\")\n    } else {\n      print(\n          \"Unable to turn on Lynx Clang plugin on platforms other than MacOS or Linux.\")\n    }\n\n    # Load plugin Clang args.\n    clang_args = [\n      \"-load\",\n      plugin_dylib,\n      \"-add-plugin\",\n      \"lynx_static_analysis\",\n    ]\n\n    # Arguments for plugin.\n    plugin_args = [\n      \"-config\",\n      rebase_path(\n          \"../../tools/clang/lynx_clang_plugin/release/lynx_config.json\"),\n      \"-on_path\",\n      rebase_path(\"//\"),\n      \"-output_dir\",\n      rebase_path(\"../../lynx_clang_plugin_output\"),\n    ]\n\n    # Forward all plugin arguments with plugin prefix.\n    foreach(arg, plugin_args) {\n      clang_args += [\n        \"-plugin-arg-lynx_static_analysis\",\n        arg,\n      ]\n    }\n\n    # Forward all arguments with -Xclang\n    foreach(arg, clang_args) {\n      cflags += [\n        \"-Xclang\",\n        arg,\n      ]\n    }\n  }\n}\n\nconfig(\"config\") {\n  include_dirs = [\n    \".\",\n    \"../core\",\n    \"//third_party\",\n  ]\n\n  cflags = [\n    \"-Wno-c99-designator\",\n    \"-Wno-unknown-warning-option\",\n    \"-Wno-sign-compare\",\n    \"-Wno-unused-but-set-variable\",\n  ]\n\n  defines = [ \"RAPIDJSON_HAS_STDSTRING=1\" ]\n}\n\naction(\"find_node_headers\") {\n  target_dir = rebase_path(node_headers_dst_dir)\n\n  script = \"../oliver/copy_node_headers.py\"\n\n  args = [ target_dir ]\n  outputs = [ \"$root_gen_dir/node\" ]\n}\n\ngroup(\"lynx_core_native\") {\n  deps = [\n    \"base\",\n    \"renderer/dom:dom\",\n  ]\n}\n\ngroup(\"lynx_native\") {\n  deps = [\n    \"../third_party/rapidjson\",\n    \"animation\",\n    \"animation/basic_animation\",\n    \"animation/lynx_basic_animator:lynx_basic_animator\",\n    \"animation/utils:animation_utils\",\n    \"base\",\n    \"build:build\",\n    \"event\",\n    \"renderer:tasm_group\",\n    \"renderer/css\",\n    \"renderer/data:data\",\n    \"renderer/events\",\n    \"renderer/layout_scheduler\",\n    \"renderer/pipeline\",\n    \"renderer/signal:signal\",\n    \"renderer/starlight\",\n    \"renderer/utils:renderer_utils\",\n    \"resource\",\n    \"runtime/common/bindings/event:runtime_common\",\n    \"runtime/common/bindings/modules:native_modules\",\n    \"runtime/common/bindings/resource:promise\",\n    \"runtime/js/bindings/interceptor:interceptor_factory\",\n    \"runtime/lepus:lepus\",\n    \"runtime/lepus/bindings:bindings\",\n    \"services/event_report:event_report\",\n    \"services/feature_count:feature_count\",\n    \"services/fluency:fluency\",\n    \"services/long_task_timing:long_task_timing\",\n    \"services/performance:performance\",\n    \"services/timing_handler:timing_handler\",\n    \"services/watch_dog:watch_dog\",\n    \"//third_party/modp_b64\",\n  ]\n\n  if (is_ios) {\n    deps += [ \"services/fsp_tracing:fsp_tracing\" ]\n  }\n\n  if (is_ios || is_android) {\n    deps += [ \"parser\" ]\n  }\n\n  if (enable_recorder) {\n    deps += [ \"services/recorder:recorder\" ]\n  }\n}\n"
  },
  {
    "path": "core/Lynx.gni",
    "content": "# Copyright 2020 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//${lynx_dir}/core/napi.gni\")\nimport(\"../config.gni\")\nimport(\"base_targets.gni\")\n\n# All of those args one-to-one correspond with the macros in the code of Lynx module.\n# Please do not add other types of flags to this scope.\ndeclare_args() {\n  # build_lepus_compile indicates whether build with template compiler\n  build_lepus_compile = true\n\n  # enable_air corresponds the macro of ENABLE_AIR\n  enable_air = false\n\n  # disable_nanbox corresponds the macro of DISABLE_NANBOX\n  disable_nanbox = false\n\n  # enable_testbench_replay corresponds the macro of ENABLE_TESTBENCH_REPLAY\n  enable_testbench_replay = false\n\n  # enable_testbench_recorder corresponds the macro of ENABLE_TESTBENCH_RECORDER.\n  enable_testbench_recorder = false\n\n  # enable_just_lepusng corresponds the macro of ENABLE_JUST_LEPUSNG\n  # ENABLE_JUST_LEPUSNG only use on ultralite to control package size\n  enable_just_lepusng = false\n\n  # JavaScript engine type. jsengine_type determines the value of macro JS_ENGINE_TYPE\n  jsengine_type = \"none\"\n\n  # enable some logic for e2e test\n  enable_e2e_test = false\n\n  # disable_list_platform_implementation is used to determine whether FE can use c++ list element.\n  disable_list_platform_implementation = true\n}\n\nif (is_win) {\n  build_lepus_compile = false\n} else if (is_android) {\n  declare_args() {\n    # gradle will pass these args when building Android.\n    # enable buid debugMode liblynx.so for testbench.\n    lynx_in_debug = false\n\n    # enable replay in release mode.\n    enable_replay_release = false\n  }\n\n  if (jsengine_type == \"none\") {\n    jsengine_type = \"quickjs\"\n  }\n\n  # these args are up to lynx_in_debug arg,\n  # gradle will not pass these args when building Android.\n  enable_testbench_replay = lynx_in_debug\n  if (enable_replay_release) {\n    enable_testbench_replay = true\n  }\n  enable_testbench_recorder = lynx_in_debug\n} else if (is_ios) {\n  if (jsengine_type == \"none\") {\n    jsengine_type = \"jsc\"\n  }\n}\n\nif (enable_unittests) {\n  enable_air = true\n}\n\nif (enable_lite) {\n  # these args are up to enable_lite arg\n  enable_just_lepusng = true\n  enable_air = false\n  disable_list_platform_implementation = true\n}\n"
  },
  {
    "path": "core/animation/AGENTS.md",
    "content": "# AGENTS.md\n\n## Scope\n\nThis directory contains Lynx core animation code: shared animation primitives, CSS keyframe and transition managers, the platform-agnostic `basic_animation/` layer, the Lynx-facing `lynx_basic_animator/` adapter, and small testing and utility helpers.\n\n## Module Map\n\n- Root files in this directory implement shared animation concepts such as `Animation`, animation curves, keyframe models, and CSS animation/transition management.\n- `basic_animation/` contains reusable animation abstractions such as timelines, effects, keyframes, and interpolatable property values.\n- `lynx_basic_animator/` adapts the basic animation layer to Lynx-facing concepts such as `starlight::AnimationData`, event callbacks, and VSync-backed frame scheduling.\n- `testing/` contains mocks used by animation tests. Do not treat these files as production implementations; changes here normally validate through `animation_unittests_exec`.\n- `utils/` contains low-level timing helpers such as cubic bezier and timing function logic.\n\n## Key Files And Types\n\n- `animation.*`: root animation lifecycle object for CSS-driven animations. Owns play/pause/stop state, frame requests, event dispatch, and the first-frame dummy tick behavior.\n- `css_keyframe_manager.*`: converts CSS keyframe declarations into runtime curves and keyframe models, then ticks all active keyframe animations for an element.\n- `css_transition_manager.*`: expands transition properties, constructs per-property transition animations, and decides when transitions should stop or restart.\n- `keyframe_effect.*`: element-facing effect container for root animation code. It holds the per-curve `KeyframeModel` instances used by CSS animation and transition flows.\n- `keyframe_model.*`: shared root-level model for animation run state and curve progression in the CSS animation path.\n- `transform_animation_curve.*` and `keyframed_animation_curve.*`: property-specific curve implementations used by CSS animations and transitions.\n\n## Layer Choice\n\n- This directory contains two similarly named animation stacks. Root types such as `Animation`, `KeyframeEffect`, and `KeyframeModel` belong to the CSS-driven element animation path in `lynx::animation`.\n- Types under `basic_animation/` such as `basic::Animation`, `basic::KeyframeEffect`, and `basic::KeyframeModel` belong to the platform-agnostic foundation layer in `lynx::animation::basic`.\n- When a change is about CSS property animation, element style updates, transition/keyframe token conversion, or animation events emitted through element infrastructure, prefer the root stack.\n- When a change is about generic timing, keyframe progression, interpolation contracts, or reusable frame-callback abstractions, prefer the `basic_animation/` stack.\n\n## Typical Change Patterns\n\n- If you are changing CSS animation parsing or how CSS keyframes become runtime curves, start from `css_keyframe_manager.*`.\n- If you are changing transition construction, transition restart/stop behavior, or property expansion such as `all`, `margin`, or `padding`, start from `css_transition_manager.*`.\n- If you are changing generic animation lifecycle semantics such as play, pause, stop, dummy-start ticking, or frame scheduling, inspect the root `animation.*` files and compare behavior with `basic_animation/basic_animation.*`.\n\n## Edit Rules\n\n- Put general animation semantics in this directory or `basic_animation/`; keep Lynx-specific adapter logic in `lynx_basic_animator/`.\n- Be careful when editing `css_keyframe_manager.*` or `css_transition_manager.*`; these changes affect CSS animation behavior broadly rather than a single caller.\n- Keep `utils/` focused on small reusable math and timing helpers. Avoid pulling renderer, shell, or platform glue into it.\n- Keep mocks and test doubles under `testing/` only. If production code starts depending on a helper here, that helper likely belongs elsewhere.\n- In the root animation layer, watch for lifecycle-sensitive behavior around `Play`, `Pause`, `Stop`, `Destroy`, `RequestNextFrame`, and the dummy start time. These code paths affect event emission and first-frame style application.\n\n## Invariants And Pitfalls\n\n- Root `animation.cc` uses a dummy start time on first play to force an immediate tick. This is intentionally tied to first-frame style correctness and iOS flicker avoidance; do not remove or change it casually.\n- `css_keyframe_manager.*` constructs curves and models based on property and curve type. Adding a new animatable property usually requires wiring the right keyframe curve and validating the property can be converted into keyframe values.\n- `css_transition_manager.*` expands aggregate transition properties such as `all`, `border-width`, `border-color`, `margin`, and `padding`. A seemingly local change here can fan out into many per-property animations.\n- Transition stop conditions depend on start/end value validity and previous-end-value tracking. Regressions here often show up as animations failing to restart or failing to cancel.\n\n## Common Regression Symptoms\n\n- First-frame style flicker or missing initial animated state often points to lifecycle changes around the dummy start time or first tick path in `animation.*`.\n- Transitions that stop restarting, refuse to cancel, or only animate part of an aggregate property often point to `css_transition_manager.*`.\n- CSS keyframe animations that animate the wrong property set or use the wrong curve type often point to model construction in `css_keyframe_manager.*`.\n\n## Validate\n\nFor C++ unit tests in this directory tree, prefer the `lynx-cpp-test` skill. Use that workflow to prepare the environment, generate GN files, build the right target, and run only the relevant unit tests first.\n\nResolve the exact exec target from this directory's `BUILD.gn`, then use the smallest matching target first.\n\nCommon starting points:\n\n- `animation_unittests_exec`\n- `basic_animation_unittests_exec`\n- `lynx_basic_animator_unittests_exec`\n- `animation_utils_unittests_exec`\n\nExpand validation when appropriate:\n\n- If you changed `css_keyframe_manager.*` or `css_transition_manager.*`, use `lynx-cpp-test` to run `animation_unittests_exec` even if the touched helper seems small.\n- If you changed shared keyframe/timing/value abstractions, use `lynx-cpp-test` to run both `basic_animation_unittests_exec` and `animation_unittests_exec`.\n- If you changed adapter-side callback or VSync behavior, use `lynx-cpp-test` to run `lynx_basic_animator_unittests_exec` and consider whether the shared layer tests should also run.\n- If you changed `utils/*`, easing selection, cubic bezier math, or timing-function behavior, use `lynx-cpp-test` to run `animation_utils_unittests_exec`.\n\n## Notes\n\n- Animation tests are not fully isolated from the rest of core. `BUILD.gn` wires animation tests to renderer, runtime bindings, and shell test helpers, so interface changes can have effects outside this directory.\n- Prefer adding or extending nearby `*_unittest.cc` files instead of creating distant test coverage.\n"
  },
  {
    "path": "core/animation/BUILD.gn",
    "content": "# Copyright 2020 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../testing/test.gni\")\nimport(\"../Lynx.gni\")\n\nanimation_shared_sources = [\n  \"animation.cc\",\n  \"animation.h\",\n  \"animation_curve.cc\",\n  \"animation_curve.h\",\n  \"animation_delegate.h\",\n  \"animation_trace_event_def.h\",\n  \"constants.h\",\n  \"css_keyframe_manager.cc\",\n  \"css_keyframe_manager.h\",\n  \"css_transition_manager.cc\",\n  \"css_transition_manager.h\",\n  \"keyframe_effect.cc\",\n  \"keyframe_effect.h\",\n  \"keyframe_model.cc\",\n  \"keyframe_model.h\",\n  \"keyframed_animation_curve.cc\",\n  \"keyframed_animation_curve.h\",\n  \"transform_animation_curve.cc\",\n  \"transform_animation_curve.h\",\n]\n\nlynx_core_source_set(\"animation\") {\n  sources = animation_shared_sources\n\n  public_deps = [\n    \"../../third_party/rapidjson\",\n    \"../base\",\n    \"../runtime/lepus:lepus\",\n    \"utils:animation_utils\",\n  ]\n}\n\nunittest_set(\"animation_testset\") {\n  testonly = true\n\n  public_deps = [ \":animation\" ]\n\n  deps = [\n    \"../renderer:tasm\",\n    \"../renderer/dom:renderer_dom\",\n    \"../runtime/lepus/bindings:bindings\",\n    \"../shell/testing:mock_tasm_delegate_testset\",\n  ]\n\n  sources = [\n    \"animation_unittest.cc\",\n    \"css_keyframe_manager_unittest.cc\",\n    \"css_transition_manager_unittest.cc\",\n    \"keyframe_effect_unittest.cc\",\n    \"keyframe_model_unittest.cc\",\n    \"keyframed_animation_curve_unittest.cc\",\n    \"testing/mock_animation.cc\",\n    \"testing/mock_css_keyframe_manager.cc\",\n    \"testing/mock_css_transition_manager.cc\",\n    \"transform_animation_curve_unittest.cc\",\n  ]\n}\n\nunittest_exec(\"animation_unittests_exec\") {\n  testonly = true\n  sources = []\n  deps = [\n    \":animation\",\n    \":animation_testset\",\n    \"../renderer/dom:dom\",\n  ]\n}\n\ngroup(\"animation_tests\") {\n  testonly = true\n  deps = [ \":animation_unittests_exec\" ]\n  public_deps = [ \":animation_testset\" ]\n}\n"
  },
  {
    "path": "core/animation/LICENSE",
    "content": "// Copyright 2015 The Chromium Authors. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//    * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//    * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//    * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "core/animation/animation.cc",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/animation.h\"\n\n#include <math.h>\n\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/animation/animation_trace_event_def.h\"\n#include \"core/animation/constants.h\"\n#include \"core/base/lynx_trace_categories.h\"\n#include \"core/base/threading/vsync_monitor.h\"\n#include \"core/renderer/dom/element_manager.h\"\n\nnamespace lynx {\nnamespace animation {\nAnimation::Animation(const base::String& name)\n    : name_(name), keyframe_effect_(nullptr) {}\n\nvoid Animation::Play() {\n  if (state_ == State::kPlay) {\n    return;\n  }\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ANIMATION_PLAY);\n  LOGI(\"Lynx Animation start, name is: \" << name_.str());\n  // Since `DoFrame` may reads and modifies state_, the change of state_ must be\n  // completed before DoFrame is executed.\n  State temp_state = state_;\n  state_ = State::kPlay;\n  // The kIdle flag indicates that the animation has just been created and has\n  // never been ticked before. Here we need to use dummy time to tick the\n  // animation to ensure the style is correct.\n\n  // This is a tricky code used to solve the UI flickering issue in some cases\n  // on iOS. The root cause is that the operation of destroying an old animator\n  // and ticking a newly created animator are not within the same UI operation,\n  // causing them to take effect in different frames, resulting in flickering.\n  // To solve this problem, these two operations need to occur within the same\n  // UI operation. A tricky approach is used here, which involves using a dummy\n  // time to actively tick the newly created animator. The more reasonable\n  // approach is to delay the destruction of the old animator until the next\n  // vsync, and then simultaneously perform the operations of destroying the old\n  // animator and ticking the newly created animator on the next vsync.\n\n  // TODO(WUJINTIAN): Remove these tricky code and defer the destruction of the\n  // animator to the next vsync to solve the aforementioned problem.\n  if (temp_state == State::kIdle) {\n    DoFrame(GetAnimationDummyStartTime());\n    if (animation_delegate_) {\n      animation_delegate_->FlushAnimatedStyle();\n    }\n  } else {\n    RequestNextFrame();\n  }\n}\n\nvoid Animation::Pause() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ANIMATION_PAUSE);\n  LOGI(\"Lynx Animation pause, name is: \" << name_.str());\n  if (state_ == State::kPause) {\n    return;\n  }\n  state_ = State::kPause;\n}\n\nvoid Animation::Stop() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ANIMATION_STOP);\n  state_ = State::kStop;\n}\n\nvoid Animation::Destroy(bool need_clear_effect) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ANIMATION_DESTORY);\n  ClearTransitionPreviousEndValue();\n  if (need_clear_effect) {\n    keyframe_effect_->ClearEffect();\n  }\n  if (state_ == State::kPlay || state_ == State::kPause) {\n    SendCancelEvent();\n    LOGI(\"Lynx Animation cancel, name is: \" << name_.str());\n  }\n  state_ = State::kStop;\n  if (animation_delegate_) {\n    animation_delegate_->FlushAnimatedStyle();\n  }\n}\n\nvoid Animation::CreateEventAndSend(const base::String& event) {\n  if (element_->event_map().find(event) == element_->event_map().end() &&\n      element_->lepus_event_map().find(event) ==\n          element_->lepus_event_map().end() &&\n      element_->global_bind_event_map().find(event) ==\n          element_->global_bind_event_map().end()) {\n    return;\n  }\n  auto dict = lepus::Dictionary::Create();\n  BASE_STATIC_STRING_DECL(kNewAnimator, \"new_animator\");\n  BASE_STATIC_STRING_DECL(kAnimationType, \"animation_type\");\n  BASE_STATIC_STRING_DECL(kAnimationName, \"animation_name\");\n  dict->SetValue(kNewAnimator, true);\n  dict->SetValue(kAnimationType,\n                 is_transition_ ? BASE_STATIC_STRING(kTransitionAnimationName)\n                                : BASE_STATIC_STRING(kKeyframeAnimationName));\n  dict->SetValue(kAnimationName, this->animation_data()->name);\n  element_->element_manager()->SendAnimationEvent(\n      event.str(), element_->impl_id(), lepus::Value(std::move(dict)));\n}\n\nvoid Animation::SetKeyframeEffect(\n    std::unique_ptr<KeyframeEffect> keyframe_effect) {\n  keyframe_effect->SetAnimation(this);\n  keyframe_effect_ = std::move(keyframe_effect);\n}\n\nvoid Animation::Tick(fml::TimePoint& time) {\n  if (!keyframe_effect_) {\n    return;\n  }\n\n  // If start_time_ is uninitialized or is a dummy time, we should update it.\n  if (start_time_ == fml::TimePoint::Min() ||\n      start_time_ == GetAnimationDummyStartTime()) {\n    start_time_ = time;\n    keyframe_effect_->SetStartTime(time);\n  }\n\n  keyframe_effect_->TickKeyframeModel(time);\n}\n\nvoid Animation::BindDelegate(AnimationDelegate* target) {\n  animation_delegate_ = target;\n}\n\nbool Animation::HasFinishedAll(fml::TimePoint& time) {\n  if (!keyframe_effect_ || keyframe_effect_->CheckHasFinished(time)) {\n    return true;\n  }\n  return false;\n}\n\nvoid Animation::RequestNextFrame() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ANIMATION_REQUEST_NEXT_FRAME);\n  if (animation_delegate_) {\n    animation_delegate_->RequestNextFrame(\n        std::weak_ptr<Animation>(shared_from_this()));\n  }\n}\n\nvoid Animation::DoFrame(fml::TimePoint& frame_time) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ANIMATION_DOFRAME,\n              [this](lynx::perfetto::EventContext ctx) {\n                auto* curveTypeInfo = ctx.event()->add_debug_annotations();\n                curveTypeInfo->set_name(\"animationName\");\n                curveTypeInfo->set_string_value(name_.str());\n              });\n  if (frame_time != fml::TimePoint::Min()) {\n    Tick(frame_time);\n    if (HasFinishedAll(frame_time)) {\n      Stop();\n      ClearTransitionPreviousEndValue();\n    }\n  }\n\n  if (state_ == State::kPlay) {\n    RequestNextFrame();\n  } else if (state_ == State::kPause) {\n    keyframe_effect_->SetPauseTime(frame_time);\n  }\n}\n\nvoid Animation::UpdateAnimationData(starlight::AnimationData& data) {\n  animation_data_ = data;\n  if (keyframe_effect_) {\n    keyframe_effect_->UpdateAnimationData(&animation_data_);\n  }\n}\n\nvoid Animation::NotifyElementSizeUpdated() {\n  if (keyframe_effect_) {\n    keyframe_effect_->NotifyElementSizeUpdated();\n  }\n}\n\nvoid Animation::NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern type) {\n  if (keyframe_effect_) {\n    keyframe_effect_->NotifyUnitValuesUpdatedToAnimation(type);\n  }\n}\n\nfml::TimePoint& Animation::GetAnimationDummyStartTime() {\n  static fml::TimePoint kAnimationDummyStartTime = fml::TimePoint();\n  return kAnimationDummyStartTime;\n}\n\nvoid Animation::ClearTransitionPreviousEndValue() {\n  if (is_transition_) {\n    element_->ClearTransitionPreviousEndValue(name());\n  }\n}\n\nvoid Animation::SendStartEvent() {\n  CreateEventAndSend(is_transition_\n                         ? BASE_STATIC_STRING(kTransitionStartEventName)\n                         : BASE_STATIC_STRING(kKeyframeStartEventName));\n}\n\nvoid Animation::SendEndEvent() {\n  CreateEventAndSend(is_transition_\n                         ? BASE_STATIC_STRING(kTransitionEndEventName)\n                         : BASE_STATIC_STRING(kKeyframeEndEventName));\n}\n\nvoid Animation::SendCancelEvent() {\n  CreateEventAndSend(is_transition_\n                         ? BASE_STATIC_STRING(kTransitionCancelEventName)\n                         : BASE_STATIC_STRING(kKeyframeCancelEventName));\n}\n\nvoid Animation::SendIterationEvent() {\n  CreateEventAndSend(BASE_STATIC_STRING(kKeyframeIterationEventName));\n}\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/animation.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_ANIMATION_H_\n#define CORE_ANIMATION_ANIMATION_H_\n\n#include <memory>\n#include <string>\n#include <unordered_set>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/animation/keyframe_effect.h\"\n\nnamespace lynx {\nnamespace base {\nclass VSyncMonitor;\n}\n\nnamespace tasm {\nclass Element;\nclass CSSKeyframesToken;\n}  // namespace tasm\n\nnamespace animation {\nclass KeyframeEffect;\nclass Animation : public std::enable_shared_from_this<Animation> {\n public:\n  // It is a dummy animation start time used to indicate that the starting time\n  // for the animation has not yet been properly set.\n  // Q: Why do we need this dummy time?\n  // A: This dummy time is used to immediately tick the animation when it is\n  // created to ensure the style is correct. When the next vsync arrives, the\n  // correct frame time should be used to update the animation's start time.\n\n  // TODO(wujintian): Mark the fml::TimePoint parameter as const in all\n  // interfaces of animation, and then mark this variable as const.\n  static fml::TimePoint& GetAnimationDummyStartTime();\n\n  enum class State { kIdle = 0, kPlay, kPause, kStop };\n  Animation(const base::String& name);\n  ~Animation() = default;\n  void Play();\n  void Pause();\n  void Stop();\n  void Destroy(bool need_clear_effect = true);\n\n  void DoFrame(fml::TimePoint& frame_time);\n\n  void SendStartEvent();\n\n  void SendEndEvent();\n\n  void SendCancelEvent();\n\n  void SendIterationEvent();\n\n  const base::String& name() { return name_; }\n\n  void BindDelegate(AnimationDelegate* target);\n\n  bool HasFinishedAll(fml::TimePoint& time);\n\n  void SetKeyframeEffect(std::unique_ptr<KeyframeEffect> keyframe_effect);\n\n  KeyframeEffect* keyframe_effect() { return keyframe_effect_.get(); }\n\n  void BindElement(tasm::Element* element) { element_ = element; }\n\n  tasm::Element* GetElement() { return element_; }\n\n  void set_animation_data(starlight::AnimationData& data) {\n    animation_data_ = data;\n  }\n\n  starlight::AnimationData& get_animation_data() { return animation_data_; }\n\n  void UpdateAnimationData(starlight::AnimationData& data);\n\n  starlight::AnimationData* animation_data() { return &animation_data_; }\n\n  void SetRawCssId(tasm::CSSPropertyID id) { raw_style_set_.insert(id); }\n\n  std::unordered_set<tasm::CSSPropertyID>& GetRawStyleSet() {\n    return raw_style_set_;\n  }\n\n  State GetState() { return state_; }\n\n  void SetTransitionFlag() { is_transition_ = true; }\n\n  bool GetTransitionFlag() { return is_transition_; }\n\n  void NotifyElementSizeUpdated();\n\n  void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern);\n\n  void ClearTransitionPreviousEndValue();\n\n protected:\n  fml::TimePoint start_time_{fml::TimePoint::Min()};\n\n private:\n  void CreateEventAndSend(const base::String& event);\n  void Tick(fml::TimePoint& time);\n  void RequestNextFrame();\n  AnimationDelegate* animation_delegate_{nullptr};\n  base::String name_;\n  std::unique_ptr<KeyframeEffect> keyframe_effect_;\n\n  starlight::AnimationData animation_data_;\n\n  tasm::Element* element_{nullptr};\n\n  std::unordered_set<tasm::CSSPropertyID> raw_style_set_{};\n\n  State state_{State::kIdle};\n\n  bool is_transition_ = false;\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_ANIMATION_H_\n"
  },
  {
    "path": "core/animation/animation_curve.cc",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/animation_curve.h\"\n\n#include \"core/animation/keyframed_animation_curve.h\"\n\nnamespace lynx {\nnamespace animation {\n\nvoid AnimationCurve::NotifyElementSizeUpdated() {\n  for (auto& keyframe : keyframes_) {\n    if (keyframe) {\n      keyframe->NotifyElementSizeUpdated();\n    }\n  }\n}\n\nvoid AnimationCurve::NotifyUnitValuesUpdatedToAnimation(\n    tasm::CSSValuePattern type) {\n  for (auto& keyframe : keyframes_) {\n    if (keyframe) {\n      keyframe->NotifyUnitValuesUpdatedToAnimation(type);\n    }\n  }\n}\n\nfml::TimeDelta AnimationCurve::Duration() const {\n  return (keyframes_.back()->Time() - keyframes_.front()->Time()) *\n         scaled_duration();\n}\n\nvoid AnimationCurve::AddKeyframe(std::unique_ptr<Keyframe> keyframe) {\n  // Usually, the keyframes will be added in order, so this loop would be\n  // unnecessary and we should skip it if possible.\n  if (!keyframes_.empty() && keyframe != nullptr &&\n      keyframe->Time() < keyframes_.back()->Time()) {\n    for (size_t i = 0; i < keyframes_.size(); ++i) {\n      if (keyframe->Time() < keyframes_.at(i)->Time()) {\n        keyframes_.insert(keyframes_.begin() + i, std::move(keyframe));\n        return;\n      }\n    }\n  }\n\n  keyframes_.push_back(std::move(keyframe));\n}\n\n// There may be no from(0%) and to(100%) keyframe. If so, we add a empty one.\nvoid AnimationCurve::EnsureFromAndToKeyframe() {\n  static const fml::TimeDelta kFromTimeOffset =\n      fml::TimeDelta::FromSecondsF(0.0f);\n  static const fml::TimeDelta kToTimeOffset =\n      fml::TimeDelta::FromSecondsF(1.0f);\n  if (keyframes_.empty() || (keyframes_.front()->Time() != kFromTimeOffset)) {\n    AddKeyframe(MakeEmptyKeyframe(kFromTimeOffset));\n  }\n  if (keyframes_.empty() || (keyframes_.back()->Time() != kToTimeOffset)) {\n    AddKeyframe(MakeEmptyKeyframe(kToTimeOffset));\n  }\n}\n\nstd::unique_ptr<Keyframe> LayoutAnimationCurve::MakeEmptyKeyframe(\n    const fml::TimeDelta& offset) {\n  return LayoutKeyframe::Create(offset, nullptr);\n}\n\nstd::unique_ptr<Keyframe> OpacityAnimationCurve::MakeEmptyKeyframe(\n    const fml::TimeDelta& offset) {\n  return OpacityKeyframe::Create(offset, nullptr);\n}\n\nstd::unique_ptr<Keyframe> ColorAnimationCurve::MakeEmptyKeyframe(\n    const fml::TimeDelta& offset) {\n  return ColorKeyframe::Create(offset, nullptr);\n}\n\nstd::unique_ptr<Keyframe> FloatAnimationCurve::MakeEmptyKeyframe(\n    const fml::TimeDelta& offset) {\n  return FloatKeyframe::Create(offset, nullptr);\n}\n\nstd::unique_ptr<Keyframe> FilterAnimationCurve::MakeEmptyKeyframe(\n    const fml::TimeDelta& offset) {\n  return FilterKeyframe::Create(offset, nullptr);\n}\n\nstd::unique_ptr<Keyframe> BackgroundPositionAnimationCurve::MakeEmptyKeyframe(\n    const fml::TimeDelta& offset) {\n  return BackgroundPositionKeyframe::Create(offset, nullptr);\n}\n\nstd::unique_ptr<Keyframe> TransformOriginAnimationCurve::MakeEmptyKeyframe(\n    const fml::TimeDelta& offset) {\n  return TransformOriginKeyframe::Create(offset, nullptr);\n}\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/animation_curve.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_ANIMATION_CURVE_H_\n#define CORE_ANIMATION_ANIMATION_CURVE_H_\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"core/animation/utils/timing_function.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\n\nnamespace tasm {\nclass Element;\n}\n\nnamespace animation {\n\nclass OpacityAnimationCurve;\nclass LayoutAnimationCurve;\nclass ColorAnimationCurve;\nclass FloatAnimationCurve;\nclass FilterAnimationCurve;\n\n#define ALL_X_AXIS_CURVE_TYPE                                                 \\\n  AnimationCurve::CurveType::LEFT, AnimationCurve::CurveType::RIGHT,          \\\n      AnimationCurve::CurveType::WIDTH, AnimationCurve::CurveType::MAX_WIDTH, \\\n      AnimationCurve::CurveType::MIN_WIDTH,                                   \\\n      AnimationCurve::CurveType::MARGIN_LEFT,                                 \\\n      AnimationCurve::CurveType::MARGIN_RIGHT,                                \\\n      AnimationCurve::CurveType::PADDING_LEFT,                                \\\n      AnimationCurve::CurveType::PADDING_RIGHT,                               \\\n      AnimationCurve::CurveType::BORDER_LEFT_WIDTH,                           \\\n      AnimationCurve::CurveType::BORDER_RIGHT_WIDTH\n\n#define ALL_LAYOUT_CURVE_TYPE                                               \\\n  ALL_X_AXIS_CURVE_TYPE, AnimationCurve::CurveType::TOP,                    \\\n      AnimationCurve::CurveType::BOTTOM, AnimationCurve::CurveType::HEIGHT, \\\n      AnimationCurve::CurveType::MAX_HEIGHT,                                \\\n      AnimationCurve::CurveType::MIN_HEIGHT,                                \\\n      AnimationCurve::CurveType::PADDING_TOP,                               \\\n      AnimationCurve::CurveType::PADDING_BOTTOM,                            \\\n      AnimationCurve::CurveType::MARGIN_TOP,                                \\\n      AnimationCurve::CurveType::MARGIN_BOTTOM,                             \\\n      AnimationCurve::CurveType::BORDER_TOP_WIDTH,                          \\\n      AnimationCurve::CurveType::BORDER_BOTTOM_WIDTH,                       \\\n      AnimationCurve::CurveType::FLEX_BASIS\n\nclass Keyframe {\n public:\n  Keyframe(const Keyframe&) = delete;\n  Keyframe& operator=(const Keyframe&) = delete;\n\n  fml::TimeDelta Time() const;\n  const TimingFunction* timing_function() const {\n    return timing_function_.get();\n  }\n\n  bool IsEmpty() { return is_empty_; }\n\n  virtual ~Keyframe() = default;\n\n  virtual void NotifyElementSizeUpdated(){};\n\n  virtual void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern){};\n\n  virtual bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                        tasm::Element* element) = 0;\n\n protected:\n  bool is_empty_{true};\n\n  Keyframe(fml::TimeDelta time,\n           std::unique_ptr<TimingFunction> timing_function);\n\n private:\n  fml::TimeDelta time_;\n  std::unique_ptr<TimingFunction> timing_function_;\n};\n\nclass AnimationCurve {\n public:\n  enum class CurveType {\n    UNSUPPORT = 0,\n    LEFT = tasm::kPropertyIDLeft,\n    RIGHT = tasm::kPropertyIDRight,\n    TOP = tasm::kPropertyIDTop,\n    BOTTOM = tasm::kPropertyIDBottom,\n    WIDTH = tasm::kPropertyIDWidth,\n    HEIGHT = tasm::kPropertyIDHeight,\n    OPACITY = tasm::kPropertyIDOpacity,\n    BGCOLOR = tasm::kPropertyIDBackgroundColor,\n    TEXTCOLOR = tasm::kPropertyIDColor,\n    TRANSFORM = tasm::kPropertyIDTransform,\n    MAX_WIDTH = tasm::kPropertyIDMaxWidth,\n    MIN_WIDTH = tasm::kPropertyIDMinWidth,\n    MAX_HEIGHT = tasm::kPropertyIDMaxHeight,\n    MIN_HEIGHT = tasm::kPropertyIDMinHeight,\n    PADDING_LEFT = tasm::kPropertyIDPaddingLeft,\n    PADDING_RIGHT = tasm::kPropertyIDPaddingRight,\n    PADDING_TOP = tasm::kPropertyIDPaddingTop,\n    PADDING_BOTTOM = tasm::kPropertyIDPaddingBottom,\n    MARGIN_LEFT = tasm::kPropertyIDMarginLeft,\n    MARGIN_RIGHT = tasm::kPropertyIDMarginRight,\n    MARGIN_TOP = tasm::kPropertyIDMarginTop,\n    MARGIN_BOTTOM = tasm::kPropertyIDMarginBottom,\n    BORDER_LEFT_WIDTH = tasm::kPropertyIDBorderLeftWidth,\n    BORDER_RIGHT_WIDTH = tasm::kPropertyIDBorderRightWidth,\n    BORDER_TOP_WIDTH = tasm::kPropertyIDBorderTopWidth,\n    BORDER_BOTTOM_WIDTH = tasm::kPropertyIDBorderBottomWidth,\n    BORDER_LEFT_COLOR = tasm::kPropertyIDBorderLeftColor,\n    BORDER_RIGHT_COLOR = tasm::kPropertyIDBorderRightColor,\n    BORDER_TOP_COLOR = tasm::kPropertyIDBorderTopColor,\n    BORDER_BOTTOM_COLOR = tasm::kPropertyIDBorderBottomColor,\n    FLEX_BASIS = tasm::kPropertyIDFlexBasis,\n    FLEX_GROW = tasm::kPropertyIDFlexGrow,\n    FILTER = tasm::kPropertyIDFilter,\n    OFFSET_DISTANCE = tasm::kPropertyIDOffsetDistance,\n    BACKGROUND_POSITION = tasm::kPropertyIDBackgroundPosition,\n    TRANSFORM_ORIGIN = tasm::kPropertyIDTransformOrigin,\n  };\n\n  virtual ~AnimationCurve() = default;\n  CurveType Type() const { return type_; }\n  fml::TimeDelta Duration() const;\n\n  AnimationCurve::CurveType type_;\n  TimingFunction* timing_function() { return timing_function_.get(); }\n  void SetTimingFunction(std::unique_ptr<TimingFunction> timing_function) {\n    timing_function_ = std::move(timing_function);\n  }\n  double scaled_duration() const { return scaled_duration_; }\n  void set_scaled_duration(double scaled_duration) {\n    scaled_duration_ = scaled_duration;\n  }\n\n  size_t get_keyframes_size() { return keyframes_.size(); }\n  void AddKeyframe(std::unique_ptr<Keyframe> keyframe);\n\n  void SetElement(tasm::Element* element) { element_ = element; }\n\n  void EnsureFromAndToKeyframe();\n\n  void NotifyElementSizeUpdated();\n\n  void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern);\n\n  virtual std::unique_ptr<Keyframe> MakeEmptyKeyframe(\n      const fml::TimeDelta& offset) = 0;\n\n  virtual tasm::CSSValue GetValue(fml::TimeDelta& t) const = 0;\n\n protected:\n  std::unique_ptr<TimingFunction> timing_function_;\n  double scaled_duration_{1.0};\n  std::vector<std::unique_ptr<Keyframe>> keyframes_;\n  tasm::Element* element_{nullptr};\n};\n\nclass LayoutAnimationCurve : public AnimationCurve {\n public:\n  ~LayoutAnimationCurve() override = default;\n\n  std::unique_ptr<Keyframe> MakeEmptyKeyframe(\n      const fml::TimeDelta& offset) override;\n};\n\nclass OpacityAnimationCurve : public AnimationCurve {\n public:\n  ~OpacityAnimationCurve() override = default;\n\n  std::unique_ptr<Keyframe> MakeEmptyKeyframe(\n      const fml::TimeDelta& offset) override;\n};\n\nclass ColorAnimationCurve : public AnimationCurve {\n public:\n  ~ColorAnimationCurve() override = default;\n\n  std::unique_ptr<Keyframe> MakeEmptyKeyframe(\n      const fml::TimeDelta& offset) override;\n};\n\nclass FloatAnimationCurve : public AnimationCurve {\n public:\n  ~FloatAnimationCurve() override = default;\n\n  std::unique_ptr<Keyframe> MakeEmptyKeyframe(\n      const fml::TimeDelta& offset) override;\n};\n\nclass FilterAnimationCurve : public AnimationCurve {\n public:\n  ~FilterAnimationCurve() override = default;\n\n  std::unique_ptr<Keyframe> MakeEmptyKeyframe(\n      const fml::TimeDelta& offset) override;\n};\n\nclass BackgroundPositionAnimationCurve : public AnimationCurve {\n public:\n  ~BackgroundPositionAnimationCurve() override = default;\n\n  std::unique_ptr<Keyframe> MakeEmptyKeyframe(\n      const fml::TimeDelta& offset) override;\n};\n\nclass TransformOriginAnimationCurve : public AnimationCurve {\n public:\n  ~TransformOriginAnimationCurve() override = default;\n\n  std::unique_ptr<Keyframe> MakeEmptyKeyframe(\n      const fml::TimeDelta& offset) override;\n};\n\n}  // namespace animation\n}  // namespace lynx\n#endif  // CORE_ANIMATION_ANIMATION_CURVE_H_\n"
  },
  {
    "path": "core/animation/animation_delegate.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_ANIMATION_DELEGATE_H_\n#define CORE_ANIMATION_ANIMATION_DELEGATE_H_\n\n#include <memory>\n#include <queue>\n#include <set>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/linked_hash_map.h\"\n#include \"core/renderer/css/css_property.h\"\n\nnamespace lynx {\nnamespace animation {\n\nclass Animation;\nclass AnimationDelegate {\n public:\n  virtual ~AnimationDelegate() {}\n  virtual void RequestNextFrame(std::weak_ptr<Animation> ptr){};\n  virtual void UpdateFinalStyleMap(const tasm::StyleMap& styles){};\n  virtual void FlushAnimatedStyle(){};\n  virtual void SetNeedsAnimationStyleRecalc(const base::String& name){};\n  virtual void NotifyClientAnimated(tasm::StyleMap& styles,\n                                    tasm::CSSValue value,\n                                    tasm::CSSPropertyID css_id){};\n  tasm::Element* element() { return element_; }\n\n protected:\n  std::vector<std::weak_ptr<Animation>> active_animations_;\n  tasm::Element* element_{nullptr};\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_ANIMATION_DELEGATE_H_\n"
  },
  {
    "path": "core/animation/animation_trace_event_def.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_ANIMATION_TRACE_EVENT_DEF_H_\n#define CORE_ANIMATION_ANIMATION_TRACE_EVENT_DEF_H_\n#if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n\nstatic constexpr const char* const ANIMATION_PLAY = \"Animation::Play\";\n\nstatic constexpr const char* const ANIMATION_PAUSE = \"Animation::Pause\";\n\nstatic constexpr const char* const ANIMATION_STOP = \"Animation::Stop\";\n\nstatic constexpr const char* const ANIMATION_DESTORY = \"Animation::Destroy\";\n\nstatic constexpr const char* const ANIMATION_REQUEST_NEXT_FRAME =\n    \"Animation::RequestNextFrame\";\nstatic constexpr const char* const ANIMATION_DOFRAME = \"Animation::DoFrame\";\n\nstatic constexpr const char* const KEYFRAME_MANAGER_TICK_ALL_ANIMATION =\n    \"CSSKeyframeManager::TickAllAnimation\";\nstatic constexpr const char* const KEYFRAME_MANAGER_NEEDS_ANIMATION_RECALC =\n    \"CSSKeyframeManager::SetNeedsAnimationStyleRecalc\";\n\nstatic constexpr const char* const TRANSITION_MANAGER_NEEDS_ANIMATION_RECALC =\n    \"CSSTransitionManager::TickAllAnimation\";\n\nstatic constexpr const char* const KEYFRAME_EFFECT_TICK_MODEL =\n    \"KeyframeEffect::TickKeyframeModel\";\nstatic constexpr const char* const KEYFRAME_EFFECT_CHECK_HAS_FINISHED =\n    \"KeyframeEffect::CheckHasFinished\";\nstatic constexpr const char* const KEYFRAME_EFFECT_CLEAR_EFFECT =\n    \"KeyframeEffect::ClearEffect\";\n\nstatic constexpr const char* const KEYFRAME_LAYOUT_ANIMATION_CURVE_GET_VALUE =\n    \"KeyframedLayoutAnimationCurve::GetValue\";\nstatic constexpr const char* const KEYFRAME_OPACITY_ANIMATION_CURVE_GET_VALUE =\n    \"KeyframedOpacityAnimationCurve::GetValue\";\nstatic constexpr const char* const KEYFRAME_COLOR_ANIMATION_CURVE_GET_VALUE =\n    \"KeyframedColorAnimationCurve::GetValue\";\nstatic constexpr const char* const KEYFRAME_FONT_ANIMATION_CURVE_GET_VALUE =\n    \"KeyframedFloatAnimationCurve::GetValue\";\nstatic constexpr const char* const KEYFRAME_FILTER_ANIMATION_CURVE_GET_VALUE =\n    \"KeyframedFilterAnimationCurve::GetValue\";\nstatic constexpr const char* const\n    KEYFRAME_TRANSFORM_ANIMATION_CURVE_GET_VALUE =\n        \"KeyframedTransformAnimationCurve::GetValue\";\nstatic constexpr const char* const\n    KEYFRAME_BACKGROUND_POSITION_ANIMATION_CURVE_GET_VALUE =\n        \"KeyframedBackgroundPositionAnimationCurve::GetValue\";\nstatic constexpr const char* const ELEMENT_ANIMATE =\n    \"RendererFunction::ElementAnimate\";\n\n#endif  // #if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n\n#endif  // CORE_ANIMATION_ANIMATION_TRACE_EVENT_DEF_H_\n"
  },
  {
    "path": "core/animation/animation_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"core/animation/css_keyframe_manager.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframe_model.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/animation/testing/mock_animation.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/animation_data.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace testing {\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass AnimationTest : public ::testing::Test {\n public:\n  AnimationTest() {}\n  ~AnimationTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<test::MockTasmDelegate>> tasm_mediator;\n  fml::RefPtr<lynx::tasm::Element> element_;\n\n  void SetUp() override {\n    LynxEnvConfig lynx_env_config(kWidth, kHeight, kDefaultLayoutsUnitPerPx,\n                                  kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<MockPaintingContext>(), tasm_mediator.get(),\n        lynx_env_config);\n    auto config = std::make_shared<PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n  }\n\n  std::shared_ptr<animation::MockAnimation> InitTestAnimation() {\n    auto test_animation =\n        std::make_shared<animation::MockAnimation>(\"test_animation\");\n    auto effect = animation::KeyframeEffect::Create();\n    InitTestEffect(*effect);\n    test_animation->SetKeyframeEffect(std::move(effect));\n    element_ = manager->CreateFiberElement(\"view\");\n    test_animation->BindElement(element_.get());\n    return test_animation;\n  }\n\n  void InitTestEffect(animation::KeyframeEffect& test_effect) {\n    std::unique_ptr<animation::KeyframedOpacityAnimationCurve> test_curve(\n        animation::KeyframedOpacityAnimationCurve::Create());\n\n    // keyframe 0% opacity 1\n    auto test_frame1 =\n        animation::OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n    test_frame1->SetOpacity(0.0f);\n    test_curve->AddKeyframe(std::move(test_frame1));\n    test_curve->type_ = animation::AnimationCurve::CurveType::OPACITY;\n    // keyframe 100% opacity 0\n    auto test_frame2 = animation::OpacityKeyframe::Create(\n        fml::TimeDelta::FromSecondsF(1.0), nullptr);\n    test_frame2->SetOpacity(1.0f);\n    test_curve->AddKeyframe(std::move(test_frame2));\n    std::unique_ptr<animation::KeyframeModel> new_model =\n        animation::KeyframeModel::Create(std::move(test_curve));\n    test_effect.AddKeyframeModel(std::move(new_model));\n  }\n\n  starlight::AnimationData InitAnimationData(\n      const base::String& name, long duration, long delay,\n      starlight::TimingFunctionData timing_func, int iteration_count,\n      starlight::AnimationFillModeType fill_mode,\n      starlight::AnimationDirectionType direction,\n      starlight::AnimationPlayStateType play_state) {\n    starlight::AnimationData data;\n    data.name = name;\n    data.duration = duration;\n    data.delay = delay;\n    data.timing_func = timing_func;\n    data.iteration_count = iteration_count;\n    data.fill_mode = fill_mode;\n    data.direction = direction;\n    data.play_state = play_state;\n    return data;\n  }\n};\n\nTEST_F(AnimationTest, Pause) {\n  auto test_animation = InitTestAnimation();\n  test_animation->Pause();\n  EXPECT_TRUE(test_animation->GetState() ==\n              animation::Animation::State::kPause);\n}\n\nTEST_F(AnimationTest, Stop) {\n  auto test_animation = InitTestAnimation();\n  test_animation->Stop();\n  EXPECT_TRUE(test_animation->GetState() == animation::Animation::State::kStop);\n}\n\nTEST_F(AnimationTest, SetKeyframeEffect) {\n  auto test_animation = InitTestAnimation();\n  auto test_effect = animation::KeyframeEffect::Create();\n  InitTestEffect(*test_effect.get());\n  auto temp_effect = test_effect.get();\n  test_animation->SetKeyframeEffect(std::move(test_effect));\n  EXPECT_TRUE(temp_effect->GetAnimation() == test_animation.get());\n  EXPECT_TRUE(test_animation->keyframe_effect() == temp_effect);\n}\n\nTEST_F(AnimationTest, HasFinishedAll) {\n  {\n    auto test_animation = InitTestAnimation();\n    auto test_animation_data =\n        InitAnimationData(base::String(\"test_animation\"), 3000, 2000,\n                          starlight::TimingFunctionData(), 1,\n                          starlight::AnimationFillModeType::kBoth,\n                          starlight::AnimationDirectionType::kNormal,\n                          starlight::AnimationPlayStateType::kRunning);\n\n    test_animation->UpdateAnimationData(test_animation_data);\n    fml::TimePoint test_time0 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0));\n    test_animation->DoFrame(test_time0);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time0) == false);\n\n    fml::TimePoint test_time1 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.0));\n    test_animation->DoFrame(test_time1);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time1) == false);\n\n    test_time1 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.1));\n    test_animation->DoFrame(test_time1);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time1) == false);\n\n    fml::TimePoint test_time2 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(6.1));\n    test_animation->DoFrame(test_time2);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time2) == true);\n  }\n\n  {\n    auto test_animation = InitTestAnimation();\n    auto test_animation_data =\n        InitAnimationData(base::String(\"test_animation\"), 1000, 0,\n                          starlight::TimingFunctionData(), 3,\n                          starlight::AnimationFillModeType::kBoth,\n                          starlight::AnimationDirectionType::kNormal,\n                          starlight::AnimationPlayStateType::kRunning);\n    test_animation->UpdateAnimationData(test_animation_data);\n    // update start time\n    fml::TimePoint start_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0f));\n    fml::TimePoint test_time0 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.1f));\n    test_animation->DoFrame(start_time);\n    test_animation->DoFrame(test_time0);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time0) == false);\n\n    test_time0 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.0));\n    test_animation->DoFrame(test_time0);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time0) == false);\n\n    test_time0 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.0));\n    test_animation->DoFrame(test_time0);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time0) == false);\n\n    test_time0 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.9));\n    test_animation->DoFrame(test_time0);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time0) == false);\n\n    test_time0 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.1));\n    test_animation->DoFrame(test_time0);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time0) == true);\n\n    test_time0 =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(5.0));\n    test_animation->DoFrame(test_time0);\n    EXPECT_TRUE(test_animation->HasFinishedAll(test_time0) == true);\n  }\n}\n\nTEST_F(AnimationTest, UpdateAnimationData) {\n  auto test_animation = InitTestAnimation();\n  auto test_effect = animation::KeyframeEffect::Create();\n  InitTestEffect(*test_effect.get());\n  auto test_animation_data = starlight::AnimationData();\n  test_animation->UpdateAnimationData(test_animation_data);\n  EXPECT_TRUE(*(test_animation->animation_data()) == test_animation_data);\n}\n\nTEST_F(AnimationTest, CheckStartTime) {\n  auto test_animation = InitTestAnimation();\n  auto test_effect = animation::KeyframeEffect::Create();\n  InitTestEffect(*test_effect.get());\n  test_animation->SetKeyframeEffect(std::move(test_effect));\n  auto test_animation_data = starlight::AnimationData();\n  test_animation_data.name = \"test_animation\";\n  test_animation->UpdateAnimationData(test_animation_data);\n\n  // check start time\n  test_animation->Play();\n  EXPECT_TRUE(test_animation->start_time() ==\n              animation::Animation::GetAnimationDummyStartTime());\n  EXPECT_TRUE(\n      test_animation->keyframe_effect()->keyframe_models()[0]->start_time() ==\n      animation::Animation::GetAnimationDummyStartTime());\n  fml::TimePoint tick_time = fml::TimePoint::FromTicks(100);\n  test_animation->DoFrame(tick_time);\n  EXPECT_TRUE(test_animation->start_time() == tick_time);\n  EXPECT_TRUE(\n      test_animation->keyframe_effect()->keyframe_models()[0]->start_time() ==\n      tick_time);\n}\n\nTEST_F(AnimationTest, SendStartEvent) {\n  auto test_animation = InitTestAnimation();\n  auto test_animation_data = starlight::AnimationData();\n  test_animation_data.name = \"test_animation\";\n  test_animation_data.duration = 1000;\n  test_animation->UpdateAnimationData(test_animation_data);\n  test_animation->GetElement()->data_model()->SetStaticEvent(\n      \"bindEvent\", \"animationstart\", \"onanimationstart\");\n  test_animation->GetElement()->data_model()->SetStaticEvent(\n      \"bindEvent\", \"animationcancel\", \"onanimationcancel\");\n  // keyframe test\n  test_animation->SendStartEvent();\n  EXPECT_TRUE(std::string(tasm_mediator->GetAnimationEventType()) ==\n              \"animationstart\");\n  lepus::Value params = tasm_mediator->GetAnimationEventParams();\n  EXPECT_TRUE(params.IsTable());\n  EXPECT_TRUE(params.Table()->Contains(\"new_animator\") &&\n              params.Table()->GetValue(\"new_animator\").Bool());\n  EXPECT_TRUE(params.Table()->Contains(\"animation_type\") &&\n              params.Table()\n                  ->GetValue(\"animation_type\")\n                  .String()\n                  .IsEqual(\"keyframe-animation\"));\n  EXPECT_TRUE(params.Table()->Contains(\"animation_name\") &&\n              params.Table()\n                  ->GetValue(\"animation_name\")\n                  .String()\n                  .IsEqual(\"test_animation\"));\n\n  test_animation->Play();\n  test_animation->Destroy();\n  EXPECT_TRUE(std::string(tasm_mediator->GetAnimationEventType()) ==\n              \"animationcancel\");\n\n  // transition test\n  test_animation = InitTestAnimation();\n  test_animation->GetElement()->data_model()->SetStaticEvent(\n      \"bindEvent\", \"transitionstart\", \"ontransitionstart\");\n  test_animation->GetElement()->data_model()->SetStaticEvent(\n      \"bindEvent\", \"transitioncancel\", \"ontransitioncancel\");\n  test_animation->SetTransitionFlag();\n  test_animation->UpdateAnimationData(test_animation_data);\n\n  test_animation->SendStartEvent();\n  EXPECT_TRUE(std::string(tasm_mediator->GetAnimationEventType()) ==\n              \"transitionstart\");\n  params = tasm_mediator->GetAnimationEventParams();\n  EXPECT_TRUE(params.IsTable());\n  EXPECT_TRUE(params.Table()->Contains(\"new_animator\") &&\n              params.Table()->GetValue(\"new_animator\").Bool());\n  EXPECT_TRUE(params.Table()->Contains(\"animation_type\") &&\n              params.Table()\n                  ->GetValue(\"animation_type\")\n                  .String()\n                  .IsEqual(\"transition-animation\"));\n  EXPECT_TRUE(params.Table()->Contains(\"animation_name\") &&\n              params.Table()\n                  ->GetValue(\"animation_name\")\n                  .String()\n                  .IsEqual(\"test_animation\"));\n\n  test_animation->Play();\n  test_animation->Destroy();\n  EXPECT_TRUE(std::string(tasm_mediator->GetAnimationEventType()) ==\n              std::string(\"transitioncancel\"));\n}\n\nTEST_F(AnimationTest, SendEndEvent) {\n  auto test_animation = InitTestAnimation();\n  auto test_animation_data = starlight::AnimationData();\n  test_animation_data.name = \"test_animation\";\n  test_animation->UpdateAnimationData(test_animation_data);\n  test_animation->GetElement()->data_model()->SetStaticEvent(\n      \"bindEvent\", \"animationend\", \"onanimationend\");\n\n  // keyframe test\n  test_animation->SendEndEvent();\n  EXPECT_TRUE(std::string(tasm_mediator->GetAnimationEventType()) ==\n              \"animationend\");\n\n  // transition test\n  test_animation = InitTestAnimation();\n  test_animation->SetTransitionFlag();\n  test_animation->UpdateAnimationData(test_animation_data);\n  test_animation->GetElement()->data_model()->SetStaticEvent(\n      \"bindEvent\", \"transitionend\", \"ontransitionend\");\n  test_animation->SendEndEvent();\n  EXPECT_TRUE(std::string(tasm_mediator->GetAnimationEventType()) ==\n              \"transitionend\");\n}\n\nTEST_F(AnimationTest, SendCancelEvent) {\n  auto test_animation = InitTestAnimation();\n  auto test_animation_data = starlight::AnimationData();\n  test_animation_data.name = \"test_animation\";\n  test_animation->UpdateAnimationData(test_animation_data);\n  test_animation->GetElement()->data_model()->SetStaticEvent(\n      \"bindEvent\", \"animationcancel\", \"onanimationcancel\");\n\n  // keyframe test\n  test_animation->SendCancelEvent();\n  EXPECT_TRUE(std::string(tasm_mediator->GetAnimationEventType()) ==\n              \"animationcancel\");\n\n  // transition test\n  test_animation = InitTestAnimation();\n  test_animation->SetTransitionFlag();\n  test_animation->UpdateAnimationData(test_animation_data);\n  test_animation->GetElement()->data_model()->SetStaticEvent(\n      \"bindEvent\", \"transitioncancel\", \"ontransitioncancel\");\n\n  test_animation->SendCancelEvent();\n  EXPECT_TRUE(std::string(tasm_mediator->GetAnimationEventType()) ==\n              \"transitioncancel\");\n}\n\nTEST_F(AnimationTest, SendIterationEvent) {\n  auto test_animation = InitTestAnimation();\n  auto test_animation_data = starlight::AnimationData();\n  test_animation_data.name = \"test_animation\";\n  test_animation->UpdateAnimationData(test_animation_data);\n  test_animation->GetElement()->data_model()->SetStaticEvent(\n      \"bindEvent\", \"animationiteration\", \"onanimationiteration\");\n\n  // keyframe test\n  test_animation->SendIterationEvent();\n  EXPECT_TRUE(std::string(tasm_mediator->GetAnimationEventType()) ==\n              \"animationiteration\");\n}\n\nTEST_F(AnimationTest, TestDelayEvent) {\n  // fill mode forwards\n  {\n    auto test_animation = InitTestAnimation();\n    auto css_keyframe_manager = std::make_unique<animation::CSSKeyframeManager>(\n        test_animation->GetElement());\n    test_animation->BindDelegate(css_keyframe_manager.get());\n    test_animation->keyframe_effect()->BindAnimationDelegate(\n        css_keyframe_manager.get());\n    std::unique_ptr<starlight::AnimationData> animation_data =\n        std::make_unique<starlight::AnimationData>();\n    animation_data->duration = 1000;\n    animation_data->iteration_count = 1;\n    animation_data->delay = 1000;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kForwards;\n    test_animation->UpdateAnimationData(*animation_data);\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationstart\", \"onanimationstart\");\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationend\", \"onanimationend\");\n    // update start time\n    fml::TimePoint start_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0f));\n    test_animation->DoFrame(start_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    // Event test\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.5f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_start_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.2f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_end_event());\n  }\n\n  // fill mode backwards\n  {\n    auto test_animation = InitTestAnimation();\n    auto css_keyframe_manager = std::make_unique<animation::CSSKeyframeManager>(\n        test_animation->GetElement());\n    test_animation->BindDelegate(css_keyframe_manager.get());\n    test_animation->keyframe_effect()->BindAnimationDelegate(\n        css_keyframe_manager.get());\n    std::unique_ptr<starlight::AnimationData> animation_data =\n        std::make_unique<starlight::AnimationData>();\n    animation_data->duration = 1000;\n    animation_data->iteration_count = 3;\n    animation_data->delay = 1000;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kBackwards;\n    test_animation->UpdateAnimationData(*animation_data);\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationstart\", \"onanimationstart\");\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationend\", \"onanimationend\");\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationiteration\", \"onanimationiteration\");\n    // update start time\n    fml::TimePoint start_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(start_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    // Event test\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.5f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_start_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.2f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_iteration_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_iteration_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(5.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_end_event());\n  }\n}\n\nTEST_F(AnimationTest, ShouldSendTwoStartEvent) {\n  {\n    auto test_animation = InitTestAnimation();\n    auto css_keyframe_manager = std::make_unique<animation::CSSKeyframeManager>(\n        test_animation->GetElement());\n    test_animation->BindDelegate(css_keyframe_manager.get());\n    test_animation->keyframe_effect()->BindAnimationDelegate(\n        css_keyframe_manager.get());\n    std::unique_ptr<starlight::AnimationData> animation_data =\n        std::make_unique<starlight::AnimationData>();\n    animation_data->duration = 1000;\n    animation_data->iteration_count = 1;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kBoth;\n    test_animation->UpdateAnimationData(*animation_data);\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationstart\", \"onanimationstart\");\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationend\", \"onanimationend\");\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationcancel\", \"onanimationcancel\");\n\n    // update start time\n    fml::TimePoint start_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(start_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_start_event());\n\n    // Event test\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.8f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    // update a longer duration, since animation has not end, then it should not\n    // have another start event.\n    animation_data->duration = 2000;\n    animation_data->iteration_count = 1;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kForwards;\n    test_animation->UpdateAnimationData(*animation_data);\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_end_event());\n\n    // update a longer duration, since animation has end, then it should have\n    // another start event.\n    animation_data->duration = 3000;\n    animation_data->iteration_count = 1;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kForwards;\n    test_animation->UpdateAnimationData(*animation_data);\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.2f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_start_event());\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_end_event());\n\n    // update a shorter duration\n    animation_data->duration = 2500;\n    animation_data->iteration_count = 1;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kForwards;\n    test_animation->UpdateAnimationData(*animation_data);\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.2f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    // update a zero duration\n    animation_data->duration = 0;\n    animation_data->iteration_count = 1;\n    animation_data->delay = 1000;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kForwards;\n    test_animation->UpdateAnimationData(*animation_data);\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.3f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    // update a zero duration with longer delay\n    animation_data->duration = 0;\n    animation_data->iteration_count = 1;\n    animation_data->delay = 5000;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kForwards;\n    test_animation->UpdateAnimationData(*animation_data);\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(5.5f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(6.5f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->has_received_animation_start_event() &&\n                tasm_mediator->has_received_animation_end_event());\n\n    // longer delay\n    animation_data->duration = 1000;\n    animation_data->iteration_count = 1;\n    animation_data->delay = 7000;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kForwards;\n    test_animation->UpdateAnimationData(*animation_data);\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(7.5f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    // paused at 7.5s, resume at 8.5s, paused time 1s\n    test_animation->Pause();\n    test_animation->DoFrame(tick_time);\n    test_animation->Play();\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(8.5f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(9.5f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->only_received_animation_start_event());\n\n    // cancel at 9.5s\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->Destroy();\n    EXPECT_TRUE(tasm_mediator->only_received_animation_cancel_event());\n  }\n}\n\nTEST_F(AnimationTest, TestDurationZero) {\n  // delay 0\n  {\n    auto test_animation = InitTestAnimation();\n    auto css_keyframe_manager = std::make_unique<animation::CSSKeyframeManager>(\n        test_animation->GetElement());\n    test_animation->BindDelegate(css_keyframe_manager.get());\n    test_animation->keyframe_effect()->BindAnimationDelegate(\n        css_keyframe_manager.get());\n    std::unique_ptr<starlight::AnimationData> animation_data =\n        std::make_unique<starlight::AnimationData>();\n    animation_data->duration = 0;\n    animation_data->iteration_count = 1;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kBackwards;\n    test_animation->UpdateAnimationData(*animation_data);\n    // update start time\n    fml::TimePoint start_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0f));\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationstart\", \"onanimationstart\");\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationend\", \"onanimationend\");\n    test_animation->DoFrame(start_time);\n    EXPECT_TRUE(tasm_mediator->has_received_animation_start_event() &&\n                tasm_mediator->has_received_animation_end_event() &&\n                !tasm_mediator->has_received_animation_cancel_event());\n\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n  }\n\n  // delay 1000\n  {\n    auto test_animation = InitTestAnimation();\n    auto css_keyframe_manager = std::make_unique<animation::CSSKeyframeManager>(\n        test_animation->GetElement());\n    test_animation->BindDelegate(css_keyframe_manager.get());\n    test_animation->keyframe_effect()->BindAnimationDelegate(\n        css_keyframe_manager.get());\n    std::unique_ptr<starlight::AnimationData> animation_data =\n        std::make_unique<starlight::AnimationData>();\n    animation_data->duration = 0;\n    animation_data->iteration_count = 1;\n    animation_data->delay = 1000;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kBackwards;\n    test_animation->UpdateAnimationData(*animation_data);\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationstart\", \"onanimationstart\");\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationend\", \"onanimationend\");\n    // update start time\n    fml::TimePoint start_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(start_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.5f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.1f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->has_received_animation_start_event() &&\n                tasm_mediator->has_received_animation_end_event() &&\n                !tasm_mediator->has_received_animation_cancel_event());\n\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.0f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    // longer delay 3s\n    animation_data->duration = 0;\n    animation_data->iteration_count = 1;\n    animation_data->delay = 3000;\n    animation_data->fill_mode = starlight::AnimationFillModeType::kBackwards;\n    test_animation->UpdateAnimationData(*animation_data);\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationstart\", \"onanimationstart\");\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationend\", \"onanimationend\");\n    test_animation->GetElement()->data_model()->SetStaticEvent(\n        \"bindEvent\", \"animationcancel\", \"onanimationcancel\");\n    tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.5f));\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->Play();\n    test_animation->DoFrame(tick_time);\n    EXPECT_TRUE(tasm_mediator->not_received_any_event());\n\n    tasm_mediator->ClearAnimationEvent();\n    test_animation->Destroy();\n    EXPECT_TRUE(tasm_mediator->only_received_animation_cancel_event());\n  }\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/AGENTS.md",
    "content": "# AGENTS.md\n\n## Scope\n\nThis directory contains the platform-agnostic foundation of Lynx animations: timelines, effects, keyframes, keyframe models, frame callback abstractions, and basic interpolatable property values.\n\n## Module Map\n\n- `basic_animation.*`, `animation_effect.*`, `animation_timeline.*`, and `animation_effect_timing.*` define the core animation lifecycle and timing model.\n- `basic_keyframe_effect.*`, `basic_keyframe_model.*`, and `keyframe.*` implement generic keyframe-based animation behavior.\n- `basic_animatable_values/` contains concrete `PropertyValue` implementations used for interpolation.\n- `thread_local_animation_handler.*` manages frame callback dispatch for animations on the current thread.\n\n## Key Files And Types\n\n- `basic_animation.*`: the shared `basic::Animation` runtime. It coordinates state transitions, requests frames, ticks effects, and emits animation events.\n- `animation_effect.*`: base effect abstraction that owns timing data and the map of per-property keyframe models.\n- `animation_effect_timing.*`: central timing object for duration, delay, fill mode, playback direction, easing, and iteration count.\n- `basic_keyframe_effect.*`: turns generic keyframe tokens into per-property curves and pushes interpolated values back to an `AnimatorTarget`.\n- `basic_keyframe_model.*`: core timing/state machine for a single property animation. It handles phase calculation, pause accounting, active time, iteration trimming, and reverse playback.\n- `property_value.h`: base interpolation contract for value types used by curves and keyframes.\n- `basic_animatable_values/*`: concrete implementations for float, int, and color interpolation.\n- `thread_local_animation_handler.*`: thread-local scheduler used when an animation relies on the shared fallback frame-callback path instead of an injected provider.\n\n## Layer Choice\n\n- The types in this directory live in `lynx::animation::basic` and are not the same as the root `lynx::animation` classes with similar names.\n- If the change is about CSS keyframes, CSS transitions, or element-facing animation events, you are probably in the wrong layer and should inspect the parent `animation/` directory first.\n- If the change is about generic timing, interpolation, run state, or reusable keyframe machinery that should not depend on element/CSS infrastructure, this directory is the right place.\n- If you are unsure which layer owns the behavior, inspect the parent `animation/AGENTS.md` first and choose the layer before editing.\n\n## Typical Change Patterns\n\n- If the change is about state transitions, event emission, or first-frame scheduling, inspect `basic_animation.*`.\n- If the change is about keyframe normalization, per-property keyframe model creation, or target style updates, inspect `basic_keyframe_effect.*`.\n- If the change is about timing math, fill behavior, playback direction, pause accounting, or iteration boundaries, inspect `basic_keyframe_model.*`.\n- If the change is about value interpolation or supported primitive value kinds, inspect `property_value.h` and `basic_animatable_values/`.\n\n## Edit Rules\n\n- Keep this layer reusable and platform-agnostic. Prefer not to introduce Lynx shell, renderer, or product-specific behavior here unless the abstraction genuinely belongs in the shared animation core.\n- Add new interpolatable value types under `basic_animatable_values/` only when they fit the `PropertyValue` contract cleanly: clone, interpolate, and stable type identification.\n- Treat `thread_local_animation_handler.*` changes as lifecycle-sensitive. Small edits here can affect callback ordering, repeated frame requests, and cleanup behavior.\n- If you add new source files, register them in `BUILD.gn` and keep tests close to the implementation they cover.\n- Keep target interaction abstract. `AnimatorTarget` and frame callback provider interfaces are the intended bridge points; avoid sneaking higher-level environment assumptions into core types.\n\n## Invariants And Pitfalls\n\n- `basic_animation.cc` intentionally uses a dummy start time on first play, then switches to the real frame time on first tick. Changes here can alter first-frame behavior and event ordering.\n- `basic_keyframe_effect.cc` normalizes missing or out-of-order offsets before constructing models. Offset cleanup and interpolation are part of correctness, not just preprocessing.\n- `basic_keyframe_model.cc` owns tricky timing rules: pause duration accounting, fill-mode handling, reverse and alternate playback, and iteration trimming. Changes here tend to have wide behavioral impact.\n- `thread_local_animation_handler.cc` clears the pending-request flag before invoking callbacks so callbacks can request another frame safely. Preserve that ordering unless you are deliberately redesigning the scheduler.\n\n## Common Regression Symptoms\n\n- Wrong iteration counts, incorrect reverse playback, or fill-mode surprises usually point to timing logic in `basic_keyframe_model.*`.\n- Pause/resume bugs or animations that stop requesting frames after resuming often point to `basic_animation.*` or `thread_local_animation_handler.*`.\n- Value interpolation glitches that affect only one primitive type usually point to `basic_animatable_values/*` rather than the higher-level timing stack.\n\n## Validate\n\nUse the `lynx-cpp-test` skill for environment setup, GN generation, target build, single-test execution, and coverage workflows.\n\nResolve the exact exec target from this directory's `BUILD.gn`.\n\n- Start with `basic_animation_unittests_exec`.\n- If you change public behavior consumed by the parent animation layer, also run `animation_unittests_exec`.\n\n## Notes\n\n- The current dedicated unit coverage in this directory focuses on `basic_animatable_values/`, so broader timing or scheduler changes may need focused tests added near the affected classes.\n- This directory depends only on animation utilities at build time; keep that narrow dependency surface when possible.\n"
  },
  {
    "path": "core/animation/basic_animation/BUILD.gn",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../testing/test.gni\")\nimport(\"../../Lynx.gni\")\n\nbasic_animation_shared_sources = [\n  \"animation_effect.cc\",\n  \"animation_effect.h\",\n  \"animation_effect_timing.cc\",\n  \"animation_effect_timing.h\",\n  \"animation_event_listener.h\",\n  \"animation_frame_callback.h\",\n  \"animation_frame_callback_provider.h\",\n  \"animation_timeline.cc\",\n  \"animation_timeline.h\",\n  \"animator_target.h\",\n  \"basic_animatable_values/color_property_value.cc\",\n  \"basic_animatable_values/color_property_value.h\",\n  \"basic_animatable_values/float_property_value.cc\",\n  \"basic_animatable_values/float_property_value.h\",\n  \"basic_animatable_values/int_property_value.cc\",\n  \"basic_animatable_values/int_property_value.h\",\n  \"basic_animation.cc\",\n  \"basic_animation.h\",\n  \"basic_animation_curve.cc\",\n  \"basic_animation_curve.h\",\n  \"basic_keyframe_effect.cc\",\n  \"basic_keyframe_effect.h\",\n  \"basic_keyframe_model.cc\",\n  \"basic_keyframe_model.h\",\n  \"keyframe.cc\",\n  \"keyframe.h\",\n  \"property_value.h\",\n  \"thread_local_animation_handler.cc\",\n  \"thread_local_animation_handler.h\",\n]\n\nlynx_core_source_set(\"basic_animation\") {\n  sources = basic_animation_shared_sources\n\n  public_deps = [ \"../utils:animation_utils\" ]\n}\n\nunittest_set(\"basic_animation_testset\") {\n  testonly = true\n  sources = [ \"basic_animatable_values/animatable_values_unittest.cc\" ]\n  deps = []\n  public_deps = [ \":basic_animation\" ]\n}\n\nunittest_exec(\"basic_animation_unittests_exec\") {\n  testonly = true\n  sources = []\n  deps = [\n    \":basic_animation\",\n    \":basic_animation_testset\",\n    \"../../renderer/dom:dom\",\n  ]\n}\n\ngroup(\"basic_animation_tests\") {\n  testonly = true\n  deps = [ \":basic_animation_unittests_exec\" ]\n  public_deps = [ \":basic_animation_testset\" ]\n}\n"
  },
  {
    "path": "core/animation/basic_animation/animation_effect.cc",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/animation_effect.h\"\n\n#include \"core/animation/basic_animation/basic_keyframe_model.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nvoid AnimationEffect::SetStartTime(const fml::TimePoint &time) {\n  for (auto &model : keyframe_models_) {\n    model.second->set_start_time(time);\n  }\n}\n\nvoid AnimationEffect::SetPauseTime(const fml::TimePoint &time) {\n  for (auto &model : keyframe_models_) {\n    model.second->SetRunState(KeyframeModel::PAUSED, time);\n  }\n}\n\nbool AnimationEffect::CheckHasFinished(const fml::TimePoint &monotonic_time) {\n  if (!keyframe_models_.empty()) {\n    if (keyframe_models_.begin()->second->is_finished() &&\n        !keyframe_models_.begin()->second->InEffect(monotonic_time)) {\n      ClearEffect();\n    }\n    return keyframe_models_.begin()->second->is_finished();\n  }\n  return true;\n}\n\nvoid AnimationEffect::ClearEffect() {\n  // TODO(wangyifei.20010605): Will be implemented later.\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/animation_effect.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_EFFECT_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_EFFECT_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"core/animation/basic_animation/animation_effect_timing.h\"\n#include \"core/animation/basic_animation/basic_keyframe_model.h\"\n#include \"core/animation/basic_animation/keyframe.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nclass Animation;\nclass KeyframeModel;\nclass AnimationEffect {\n public:\n  virtual ~AnimationEffect() = default;\n\n  const AnimationEffectTiming& timing() const { return *timing_; }\n\n  void UpdateTiming(std::unique_ptr<OptionalAnimationEffectTiming> timing) {\n    timing_->UpdateTiming(std::move(timing));\n  }\n\n  void SetStartTime(const fml::TimePoint& time);\n\n  void SetPauseTime(const fml::TimePoint& time);\n\n  virtual void TickKeyframeModel(const fml::TimePoint& monotonic_time) = 0;\n\n  void ClearEffect();\n\n  bool CheckHasFinished(const fml::TimePoint& time);\n\n  Animation* HostAnimation() { return animation_; }\n\n  void BindHostAnimation(Animation* animation) { animation_ = animation; }\n\n protected:\n  AnimationEffect() : timing_(AnimationEffectTiming::Create()) {}\n\n  explicit AnimationEffect(std::unique_ptr<AnimationEffectTiming> timing)\n      : timing_(std::move(timing)) {}\n\n  explicit AnimationEffect(\n      std::unique_ptr<OptionalAnimationEffectTiming> timing)\n      : timing_(AnimationEffectTiming::Create(std::move(timing))) {}\n\n  void UpdateAnimationData() {}\n\n protected:\n  // The counter records the current iteration_count of the animation.\n  int current_iteration_count_ = 0;\n\n  std::unique_ptr<AnimationEffectTiming> timing_;\n  std::unordered_map<std::string, std::unique_ptr<basic::KeyframeModel>>\n      keyframe_models_;\n\n private:\n  Animation* animation_{nullptr};\n};\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_EFFECT_H_\n"
  },
  {
    "path": "core/animation/basic_animation/animation_effect_timing.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/animation_effect_timing.h\"\n\nnamespace lynx {\nnamespace animation {\n\nnamespace basic {\n\nvoid AnimationEffectTiming::UpdateTiming(\n    std::unique_ptr<OptionalAnimationEffectTiming> timing) {\n  if (timing->delay_.has_value()) {\n    delay_ = *timing->delay_;\n  }\n  if (timing->fill_.has_value()) {\n    fill_ = *timing->fill_;\n  }\n  if (timing->iterations_.has_value()) {\n    iterations_ = *timing->iterations_;\n  }\n  if (timing->duration_.has_value()) {\n    duration_ = *timing->duration_;\n  }\n  if (timing->direction_.has_value()) {\n    direction_ = *timing->direction_;\n  }\n  if (timing->easing_) {\n    easing_ = std::move(timing->easing_);\n  }\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/animation_effect_timing.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_EFFECT_TIMING_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_EFFECT_TIMING_H_\n\n#include <memory>\n#include <optional>\n#include <utility>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/animation/utils/timing_function.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nstruct OptionalAnimationEffectTiming;\nclass AnimationEffectTiming {\n public:\n  enum class FillMode { kNone = 0, kForwards, kBackwards, kBoth };\n  enum class PlaybackDirection {\n    kNormal = 0,\n    kReverse,\n    kAlternate,\n    kAlternateReverse\n  };\n\n  static std::unique_ptr<AnimationEffectTiming> Create(\n      const fml::TimeDelta& delay, FillMode fill, int64_t iterations,\n      const fml::TimeDelta& duration, PlaybackDirection direction,\n      std::unique_ptr<TimingFunction> easing) {\n    return std::unique_ptr<AnimationEffectTiming>(new AnimationEffectTiming(\n        delay, fill, iterations, duration, direction, std::move(easing)));\n  }\n\n  static std::unique_ptr<AnimationEffectTiming> Create(\n      std::unique_ptr<OptionalAnimationEffectTiming> timing) {\n    return std::unique_ptr<AnimationEffectTiming>(\n        new AnimationEffectTiming(std::move(timing)));\n  }\n\n  static std::unique_ptr<AnimationEffectTiming> Create() {\n    return std::unique_ptr<AnimationEffectTiming>(new AnimationEffectTiming());\n  }\n\n  const fml::TimeDelta& delay() const { return delay_; }\n\n  FillMode fill() const { return fill_; }\n\n  double iterations() const { return iterations_; }\n\n  const fml::TimeDelta& duration() const { return duration_; }\n\n  PlaybackDirection direction() const { return direction_; }\n\n  std::unique_ptr<TimingFunction>& easing() { return easing_; }\n\n  double playback_rate() const { return playback_rate_; };\n\n  void UpdateTiming(std::unique_ptr<OptionalAnimationEffectTiming> timing);\n\n private:\n  AnimationEffectTiming(const fml::TimeDelta& delay, FillMode fill,\n                        int64_t iterations, const fml::TimeDelta& duration,\n                        PlaybackDirection direction,\n                        std::unique_ptr<TimingFunction> easing)\n      : delay_(delay),\n        fill_(fill),\n        iterations_(iterations),\n        duration_(duration),\n        direction_(direction),\n        easing_(std::move(easing)) {}\n\n  explicit AnimationEffectTiming(\n      std::unique_ptr<OptionalAnimationEffectTiming> timing) {\n    UpdateTiming(std::move(timing));\n  }\n\n  AnimationEffectTiming() = default;\n\n  fml::TimeDelta delay_{fml::TimeDelta::Zero()};\n  AnimationEffectTiming::FillMode fill_{FillMode::kNone};\n  double iterations_{1.0};\n  fml::TimeDelta duration_{fml::TimeDelta::Zero()};\n  AnimationEffectTiming::PlaybackDirection direction_{\n      PlaybackDirection::kNormal};\n  std::unique_ptr<TimingFunction> easing_{LinearTimingFunction::Create()};\n  double playback_rate_{1.0};\n};\n\nstruct OptionalAnimationEffectTiming {\n  static std::unique_ptr<OptionalAnimationEffectTiming> Create() {\n    return std::make_unique<OptionalAnimationEffectTiming>();\n  }\n  std::optional<fml::TimeDelta> delay_;\n  std::optional<AnimationEffectTiming::FillMode> fill_;\n  std::optional<double> iterations_;\n  std::optional<fml::TimeDelta> duration_;\n  std::optional<AnimationEffectTiming::PlaybackDirection> direction_;\n  std::unique_ptr<TimingFunction> easing_;\n  double playback_rate_{1.0};\n};\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_EFFECT_TIMING_H_\n"
  },
  {
    "path": "core/animation/basic_animation/animation_event_listener.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_EVENT_LISTENER_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_EVENT_LISTENER_H_\n\n#include <memory>\n\n#include \"core/animation/basic_animation/basic_animation.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nclass AnimationEventListener\n    : public std::enable_shared_from_this<AnimationEventListener> {\n public:\n  virtual ~AnimationEventListener() = default;\n\n  void OnAnimationEvent(const Animation& animation, Animation::EventType type) {\n    switch (type) {\n      case Animation::EventType::Start:\n        OnAnimationStart(animation);\n        break;\n      case Animation::EventType::End:\n        OnAnimationEnd(animation);\n        break;\n      case Animation::EventType::Cancel:\n        OnAnimationCancel(animation);\n        break;\n      case Animation::EventType::Iteration:\n        OnAnimationIteration(animation);\n        break;\n      default:\n        LOGE(\"There is no event of this type\");\n        break;\n    }\n  }\n\n  virtual void OnAnimationStart(const Animation&) = 0;\n  virtual void OnAnimationEnd(const Animation&) = 0;\n  virtual void OnAnimationCancel(const Animation&) = 0;\n  virtual void OnAnimationIteration(const Animation&) = 0;\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_EVENT_LISTENER_H_\n"
  },
  {
    "path": "core/animation/basic_animation/animation_frame_callback.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_FRAME_CALLBACK_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_FRAME_CALLBACK_H_\n\n#include <memory>\n\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nclass AnimationFrameCallback\n    : public std::enable_shared_from_this<AnimationFrameCallback> {\n public:\n  virtual ~AnimationFrameCallback() = default;\n\n  virtual void DoAnimationFrame(const fml::TimePoint& frame_time) = 0;\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_FRAME_CALLBACK_H_\n"
  },
  {
    "path": "core/animation/basic_animation/animation_frame_callback_provider.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_FRAME_CALLBACK_PROVIDER_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_FRAME_CALLBACK_PROVIDER_H_\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/time/time_point.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nclass AnimationFrameCallbackProvider {\n public:\n  virtual ~AnimationFrameCallbackProvider() = default;\n\n  // It must invoke callback async, otherwise it will encounter deadlock.\n  virtual void RequestNextFrame(\n      base::MoveOnlyClosure<void, const fml::TimePoint&> callback) = 0;\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_FRAME_CALLBACK_PROVIDER_H_\n"
  },
  {
    "path": "core/animation/basic_animation/animation_timeline.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/animation_timeline.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/animation_timeline.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_TIMELINE_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_TIMELINE_H_\n\n#include <memory>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/animation/basic_animation/animation_effect_timing.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nclass AnimationTimeLine {\n public:\n  virtual ~AnimationTimeLine() = default;\n  virtual const fml::TimePoint& current_time() = 0;\n\n private:\n  fml::TimePoint clock_time{fml::TimePoint()};\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_ANIMATION_TIMELINE_H_\n"
  },
  {
    "path": "core/animation/basic_animation/animator_target.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_ANIMATOR_TARGET_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_ANIMATOR_TARGET_H_\n\n#include <memory>\n#include <string>\n\n#include \"core/animation/basic_animation/keyframe.h\"\n#include \"core/animation/basic_animation/property_value.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nclass AnimatorTarget : public std::enable_shared_from_this<AnimatorTarget> {\n public:\n  virtual ~AnimatorTarget() = default;\n\n  virtual void UpdateAnimatedStyle(\n      const Keyframe::PropertyValueMap& styles) = 0;\n\n  // This interface is used to retrieve the value of a certain property from a\n  // target. In the W3C standard, if a property's keyframes at the 0% and 100%\n  // stages aren't specified, the current computed value of the property on the\n  // target is used to construct the keyframes for the 0% and 100% stages.\n  virtual std::unique_ptr<PropertyValue> GetStyle(\n      const std::string& property_name) = 0;\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_ANIMATOR_TARGET_H_\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animatable_values/animatable_values_unittest.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include <cmath>\n#include <memory>\n\n#include \"core/animation/basic_animation/basic_animatable_values/color_property_value.h\"\n#include \"core/animation/basic_animation/basic_animatable_values/float_property_value.h\"\n#include \"core/animation/basic_animation/basic_animatable_values/int_property_value.h\"\n#include \"core/animation/basic_animation/property_value.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace testing {\n\nclass BasicAnimatableValueTest : public ::testing::Test {\n public:\n  BasicAnimatableValueTest() {}\n  ~BasicAnimatableValueTest() override {}\n};\n\nTEST_F(BasicAnimatableValueTest, PropertyValueTest1) {\n  //  cover color value\n  auto color_value_1 = std::make_unique<animation::basic::ColorPropertyValue>(\n      static_cast<uint32_t>(0x0));\n  color_value_1->SetColorValue(static_cast<uint32_t>(4294901760));\n  EXPECT_EQ(uint32_t(4294901760), color_value_1->GetColorValue());\n  color_value_1->SetColorSRGBSpace();\n  color_value_1->SetColorLinearSpace();\n  color_value_1->Interpolate(\n      0.5, std::make_unique<animation::basic::ColorPropertyValue>(\n               static_cast<uint32_t>(0x0)));\n  EXPECT_EQ(size_t(animation::basic::BasicPropertyValueType::Color),\n            color_value_1->GetType());\n\n  // cover int value\n  auto int_value_1 = std::make_unique<animation::basic::IntPropertyValue>(0);\n  auto int_value_2 = std::make_unique<animation::basic::IntPropertyValue>(0);\n  int_value_1->SetIntValue(1);\n  int_value_2->SetIntValue(0);\n  EXPECT_EQ(int(1), int_value_1->GetIntValue());\n  EXPECT_EQ(int(0), int_value_2->GetIntValue());\n  int_value_1->Interpolate(\n      0.5, std::make_unique<animation::basic::IntPropertyValue>(0));\n  int_value_2->Interpolate(\n      0.5, std::make_unique<animation::basic::IntPropertyValue>(1));\n  EXPECT_EQ(size_t(animation::basic::BasicPropertyValueType::Int),\n            int_value_1->GetType());\n\n  // cover float value\n  auto float_value_1 =\n      std::make_unique<animation::basic::FloatPropertyValue>(0.0);\n  float_value_1->SetFloatValue(1.0);\n  EXPECT_EQ(float(1.0), float_value_1->GetFloatValue());\n  float_value_1->Interpolate(\n      0.5, std::make_unique<animation::basic::FloatPropertyValue>(0.0));\n  EXPECT_EQ(size_t(animation::basic::BasicPropertyValueType::Float),\n            float_value_1->GetType());\n}\n\n}  // namespace testing\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animatable_values/color_property_value.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/basic_animatable_values/color_property_value.h\"\n\n#include <cmath>\n#include <memory>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nstd::unique_ptr<PropertyValue> ColorPropertyValue::Interpolate(\n    double progress, const std::unique_ptr<PropertyValue> &end_value) const {\n  DCHECK(static_cast<BasicPropertyValueType>(end_value->GetType()) ==\n         BasicPropertyValueType::Color);\n  uint32_t start_color_value = GetColorValue();\n  uint32_t end_color_value =\n      reinterpret_cast<ColorPropertyValue *>(end_value.get())->GetColorValue();\n\n  float startA = ((start_color_value >> 24) & 0xff) / 255.0f;\n  float startR = ((start_color_value >> 16) & 0xff) / 255.0f;\n  float startG = ((start_color_value >> 8) & 0xff) / 255.0f;\n  float startB = ((start_color_value) & 0xff) / 255.0f;\n\n  float endA = ((end_color_value >> 24) & 0xff) / 255.0f;\n  float endR = ((end_color_value >> 16) & 0xff) / 255.0f;\n  float endG = ((end_color_value >> 8) & 0xff) / 255.0f;\n  float endB = ((end_color_value) & 0xff) / 255.0f;\n\n  startR = static_cast<float>(pow(startR, color_space_constant_));\n  startG = static_cast<float>(pow(startG, color_space_constant_));\n  startB = static_cast<float>(pow(startB, color_space_constant_));\n\n  endR = static_cast<float>(pow(endR, color_space_constant_));\n  endG = static_cast<float>(pow(endG, color_space_constant_));\n  endB = static_cast<float>(pow(endB, color_space_constant_));\n\n  float a = startA + progress * (endA - startA);\n  float b = startB + progress * (endB - startB);\n  float r = startR + progress * (endR - startR);\n  float g = startG + progress * (endG - startG);\n\n  a = a * 255.0f;\n  r = static_cast<float>(pow(r, 1.0 / color_space_constant_)) * 255.0f;\n  g = static_cast<float>(pow(g, 1.0 / color_space_constant_)) * 255.0f;\n  b = static_cast<float>(pow(b, 1.0 / color_space_constant_)) * 255.0f;\n  uint32_t result_color_value = static_cast<uint32_t>(round(a)) << 24 |\n                                static_cast<uint32_t>(round(r)) << 16 |\n                                static_cast<uint32_t>(round(g)) << 8 |\n                                static_cast<uint32_t>(round(b));\n\n  return std::make_unique<ColorPropertyValue>(result_color_value);\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animatable_values/color_property_value.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATABLE_VALUES_COLOR_PROPERTY_VALUE_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATABLE_VALUES_COLOR_PROPERTY_VALUE_H_\n\n#include <memory>\n\n#include \"core/animation/basic_animation/property_value.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nclass ColorPropertyValue : public PropertyValue {\n public:\n  explicit ColorPropertyValue(uint32_t value) : value_(value) {}\n\n  std::unique_ptr<PropertyValue> Clone() const override {\n    return std::make_unique<ColorPropertyValue>(value_);\n  }\n\n  ~ColorPropertyValue() override = default;\n\n  uint32_t GetColorValue() const { return value_; }\n\n  void SetColorValue(uint32_t value) { value_ = value; }\n\n  void SetColorSRGBSpace() { color_space_constant_ = 2.2; }\n\n  void SetColorLinearSpace() { color_space_constant_ = 1.0; }\n\n  std::unique_ptr<PropertyValue> Interpolate(\n      double progress,\n      const std::unique_ptr<PropertyValue>& end_value) const override;\n\n  size_t GetType() const override {\n    return static_cast<size_t>(BasicPropertyValueType::Color);\n  }\n\n protected:\n  double color_space_constant_ = 1.0;\n  uint32_t value_ = 0x0;\n};\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATABLE_VALUES_COLOR_PROPERTY_VALUE_H_\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animatable_values/float_property_value.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/basic_animatable_values/float_property_value.h\"\n\n#include <memory>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nstd::unique_ptr<PropertyValue> FloatPropertyValue::Interpolate(\n    double progress, const std::unique_ptr<PropertyValue> &end_value) const {\n  DCHECK(static_cast<BasicPropertyValueType>(end_value->GetType()) ==\n         BasicPropertyValueType::Float);\n  float start_float_value = GetFloatValue();\n  float end_float_value =\n      reinterpret_cast<FloatPropertyValue *>(end_value.get())->GetFloatValue();\n  float res_float_value =\n      start_float_value + (end_float_value - start_float_value) * progress;\n  return std::make_unique<FloatPropertyValue>(res_float_value);\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animatable_values/float_property_value.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATABLE_VALUES_FLOAT_PROPERTY_VALUE_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATABLE_VALUES_FLOAT_PROPERTY_VALUE_H_\n\n#include <memory>\n\n#include \"core/animation/basic_animation/property_value.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nclass FloatPropertyValue : public PropertyValue {\n public:\n  explicit FloatPropertyValue(float value) : value_(value) {}\n\n  std::unique_ptr<PropertyValue> Clone() const override {\n    return std::make_unique<FloatPropertyValue>(value_);\n  }\n\n  ~FloatPropertyValue() override = default;\n\n  float GetFloatValue() const { return value_; }\n\n  void SetFloatValue(float value) { value_ = value; }\n\n  std::unique_ptr<PropertyValue> Interpolate(\n      double progress,\n      const std::unique_ptr<PropertyValue>& end_value) const override;\n\n  size_t GetType() const override {\n    return static_cast<size_t>(BasicPropertyValueType::Float);\n  }\n\n protected:\n  float value_ = 1.0f;\n};\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATABLE_VALUES_FLOAT_PROPERTY_VALUE_H_\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animatable_values/int_property_value.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/basic_animatable_values/int_property_value.h\"\n\n#include <cmath>\n#include <memory>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nstd::unique_ptr<PropertyValue> IntPropertyValue::Interpolate(\n    double progress, const std::unique_ptr<PropertyValue> &end_value) const {\n  DCHECK(static_cast<BasicPropertyValueType>(end_value->GetType()) ==\n         BasicPropertyValueType::Int);\n  int start_int_value = GetIntValue();\n  int end_int_value =\n      reinterpret_cast<IntPropertyValue *>(end_value.get())->GetIntValue();\n  int res_int_value = 0;\n  if (start_int_value == end_int_value) {\n    res_int_value = start_int_value;\n  } else {\n    double delta = static_cast<double>(end_int_value - start_int_value);\n    if (delta < 0) {\n      delta--;\n    } else {\n      delta++;\n    }\n    res_int_value =\n        start_int_value + static_cast<int>(progress * nextafter(delta, 0));\n  }\n\n  return std::make_unique<IntPropertyValue>(res_int_value);\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animatable_values/int_property_value.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATABLE_VALUES_INT_PROPERTY_VALUE_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATABLE_VALUES_INT_PROPERTY_VALUE_H_\n\n#include <memory>\n\n#include \"core/animation/basic_animation/property_value.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nclass IntPropertyValue : public PropertyValue {\n public:\n  explicit IntPropertyValue(int value) : value_(value) {}\n\n  std::unique_ptr<PropertyValue> Clone() const override {\n    return std::make_unique<IntPropertyValue>(value_);\n  }\n\n  ~IntPropertyValue() override = default;\n  int GetIntValue() const { return value_; }\n\n  void SetIntValue(int value) { value_ = value; }\n\n  std::unique_ptr<PropertyValue> Interpolate(\n      double progress,\n      const std::unique_ptr<PropertyValue>& end_value) const override;\n\n  size_t GetType() const override {\n    return static_cast<size_t>(BasicPropertyValueType::Int);\n  }\n\n protected:\n  int value_ = 1;\n};\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATABLE_VALUES_INT_PROPERTY_VALUE_H_\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animation.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/basic_animation.h\"\n\n#include \"core/animation/basic_animation/animation_event_listener.h\"\n#include \"core/animation/basic_animation/animation_frame_callback_provider.h\"\n#include \"core/animation/basic_animation/thread_local_animation_handler.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nvoid Animation::Play() {\n  if (state_ == State::kPlay) {\n    return;\n  }\n  State temp_state = state_;\n  state_ = State::kPlay;\n  if (temp_state == State::kIdle) {\n    DoAnimationFrame(GetAnimationDummyStartTime());\n  } else {\n    RequestNextFrame();\n  }\n}\n\nvoid Animation::Pause() {\n  if (state_ == State::kPause) {\n    return;\n  }\n  state_ = State::kPause;\n}\n\nvoid Animation::Stop() { state_ = State::kStop; }\n\nvoid Animation::Destroy(bool need_clear_effect) {\n  if (need_clear_effect) {\n    effect_->ClearEffect();\n  }\n  if (state_ == State::kPlay || state_ == State::kPause) {\n    // send cancel event\n    SendAnimationEvent(EventType::Cancel);\n  }\n  state_ = State::kStop;\n}\n\nvoid Animation::DoAnimationFrame(const fml::TimePoint &frame_time) {\n  if (frame_time != fml::TimePoint::Min()) {\n    Tick(frame_time);\n    if (HasFinishAll(frame_time)) {\n      Stop();\n    }\n  }\n  if (state_ == State::kPlay) {\n    RequestNextFrame();\n  } else if (state_ == State::kPause) {\n    effect_->SetPauseTime(frame_time);\n  }\n}\n\nvoid Animation::Tick(const fml::TimePoint &time) {\n  if (!effect_) {\n    return;\n  }\n  if (start_time_ == fml::TimePoint::Min() ||\n      start_time_ == GetAnimationDummyStartTime()) {\n    start_time_ = time;\n    effect_->SetStartTime(time);\n  }\n  effect_->TickKeyframeModel(time);\n}\n\nbool Animation::HasFinishAll(const fml::TimePoint &time) {\n  if (!effect_ || effect_->CheckHasFinished(time)) {\n    return true;\n  }\n  return false;\n}\n\nfml::TimePoint &Animation::GetAnimationDummyStartTime() {\n  static fml::TimePoint kAnimationDummyStartTime = fml::TimePoint();\n  return kAnimationDummyStartTime;\n}\n\nvoid Animation::RequestNextFrame() {\n  auto strong_provider = animation_frame_callback_provider_.lock();\n  // If the provider exists in the animation, use it with priority. Otherwise,\n  // use the global provider.\n  if (strong_provider) {\n    strong_provider->RequestNextFrame(\n        [weak_ptr = weak_from_this()](const fml::TimePoint &frame_time) {\n          auto strong_ptr = weak_ptr.lock();\n          if (strong_ptr) {\n            strong_ptr->DoAnimationFrame(frame_time);\n          }\n        });\n    return;\n  }\n  ThreadLocalAnimationHandler::GetInstance().AddAnimationFrameCallback(*this);\n}\n\nvoid Animation::SendAnimationEvent(EventType type) {\n  auto strong_ptr = listener_.lock();\n  if (strong_ptr) {\n    strong_ptr->OnAnimationEvent(*this, type);\n  } else {\n    LOGE(\"Animation already has been destroyed.\");\n  }\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animation.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATION_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATION_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"core/animation/basic_animation/animation_effect.h\"\n#include \"core/animation/basic_animation/animation_frame_callback.h\"\n#include \"core/animation/basic_animation/animation_timeline.h\"\n#include \"core/animation/basic_animation/basic_keyframe_effect.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nclass AnimationFrameCallbackProvider;\nclass AnimationEventListener;\n\nclass Animation : public AnimationFrameCallback {\n public:\n  enum class State { kIdle = 0, kPlay, kPause, kStop };\n  enum PlayState { PAUSED, RUNNING };\n  enum EventType { Start, End, Cancel, Iteration };\n\n  const std::string& animation_name() const { return data_.animation_name(); }\n\n  fml::TimeDelta current_time() const { return data_.current_time(); }\n\n  double playback_rate() const { return data_.playback_rate(); }\n\n  PlayState play_state() const { return data_.play_state(); }\n\n  void set_play_state(PlayState play_state) {\n    data_.set_play_state(play_state);\n  }\n\n public:\n  class Data {\n   public:\n    Data() = default;\n    Data(const std::string& animation_name, const fml::TimeDelta& start_time,\n         const fml::TimeDelta& current_time, PlayState play_state)\n        : animation_name_(animation_name),\n          current_time_(current_time),\n          play_state_(play_state) {}\n\n    const std::string& animation_name() const { return animation_name_; }\n\n    fml::TimeDelta current_time() const { return current_time_; }\n\n    double playback_rate() const { return playback_rate_; }\n\n    PlayState play_state() const { return play_state_; }\n\n    void set_play_state(PlayState play_state) { play_state_ = play_state; }\n\n   private:\n    std::string animation_name_;\n    fml::TimeDelta current_time_;\n    double playback_rate_ = 1.0;\n    PlayState play_state_;\n  };\n\n public:\n  Animation(std::unique_ptr<KeyframeEffect> effect,\n            std::unique_ptr<AnimationTimeLine> timeline)\n      : effect_(std::move(effect)), timeline_(std::move(timeline)) {\n    effect_->BindHostAnimation(this);\n  };\n\n  explicit Animation(std::unique_ptr<KeyframeEffect> effect)\n      : effect_(std::move(effect)) {\n    effect_->BindHostAnimation(this);\n  };\n\n  void AddEventListener(\n      const std::shared_ptr<AnimationEventListener>& listener) {\n    listener_ = std::weak_ptr<AnimationEventListener>(listener);\n  }\n\n  void RegisterAnimationFrameCallbackProvider(\n      const std::weak_ptr<AnimationFrameCallbackProvider>& provider) {\n    animation_frame_callback_provider_ = provider;\n  }\n\n public:\n  void Play();\n  void Pause();\n  void Stop();\n  void Destroy(bool need_clear_effect = true);\n  void DoAnimationFrame(const fml::TimePoint& frame_time) override;\n  void Tick(const fml::TimePoint& time);\n  State GetState() { return state_; }\n\n  const AnimationEffect& GetEffect() { return *effect_; }\n\n  static fml::TimePoint& GetAnimationDummyStartTime();\n\n  void RequestNextFrame();\n\n  bool HasFinishAll(const fml::TimePoint& time);\n\n  void SendAnimationEvent(EventType type);\n\n protected:\n  Data data_;\n  fml::TimePoint start_time_{fml::TimePoint::Min()};\n\n private:\n  State state_{State::kIdle};\n  std::unique_ptr<AnimationEffect> effect_;\n  std::unique_ptr<AnimationTimeLine> timeline_;\n  std::weak_ptr<AnimationEventListener> listener_;\n  std::weak_ptr<AnimationFrameCallbackProvider>\n      animation_frame_callback_provider_;\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATION_H_\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animation_curve.cc",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/basic_animation_curve.h\"\n\n#include <cmath>\n#include <limits>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"core/animation/basic_animation/animation_effect.h\"\n#include \"core/animation/basic_animation/basic_animatable_values/float_property_value.h\"\n#include \"core/animation/basic_animation/keyframe.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nfml::TimeDelta TransformedAnimationTime(\n    const std::vector<std::unique_ptr<Keyframe>>& keyframes,\n    TimingFunction* timing_function, double scaled_duration,\n    fml::TimeDelta time) {\n  if (timing_function) {\n    fml::TimeDelta start_time = keyframes.front()->Time() * scaled_duration;\n    fml::TimeDelta duration =\n        (keyframes.back()->Time() - keyframes.front()->Time()) *\n        scaled_duration;\n    double progress = static_cast<double>(time.ToMicroseconds() -\n                                          start_time.ToMicroseconds()) /\n                      static_cast<double>(duration.ToMicroseconds());\n\n    time = (duration * timing_function->GetValue(progress)) + start_time;\n  }\n\n  return time;\n}\n\nsize_t GetActiveKeyframe(\n    const std::vector<std::unique_ptr<Keyframe>>& keyframes,\n    double scaled_duration, fml::TimeDelta time) {\n  DCHECK(keyframes.size() >= 2);\n  size_t i = 0;\n  for (; i < keyframes.size() - 2; ++i) {  // Last keyframe is never active.\n    if (time < (keyframes[i + 1]->Time() * scaled_duration)) break;\n  }\n\n  return i;\n}\n\ndouble TransformedKeyframeProgress(\n    const std::vector<std::unique_ptr<Keyframe>>& keyframes,\n    double scaled_duration, fml::TimeDelta time, size_t i) {\n  double in_time = time.ToNanosecondsF();\n  double time1 = keyframes[i]->Time().ToNanosecondsF() * scaled_duration;\n  double time2 = keyframes[i + 1]->Time().ToNanosecondsF() * scaled_duration;\n\n  if (std::fabs(time2 - time1) < std::numeric_limits<double>::epsilon()) {\n    return 1.0;\n  }\n  double progress = (in_time - time1) / (time2 - time1);\n\n  if (keyframes[i]->timing_function()) {\n    progress = keyframes[i]->timing_function()->GetValue(progress);\n  }\n\n  return progress;\n}\n\nstd::unique_ptr<AnimationCurve> AnimationCurve::Create(\n    const std::string& property_value_id, basic::AnimationEffect* effect,\n    std::weak_ptr<AnimatorTarget> target) {\n  return std::make_unique<AnimationCurve>(property_value_id, effect, target);\n}\n\nvoid AnimationCurve::EnsureFromAndToKeyframe() {\n  static const double kFromTimeOffset = 0.0f;\n  static const double kToTimeOffset = 1.0f;\n  if (keyframes_.empty() || keyframes_.front()->offset() != kFromTimeOffset) {\n    auto from_keyframe = MakeEmptyKeyframe(kFromTimeOffset);\n    from_keyframe->SetDefaultPropertyValue(curve_type_, target_.lock());\n    AddKeyframe(std::move(from_keyframe));\n  }\n  if (keyframes_.empty() || keyframes_.back()->offset() != kToTimeOffset) {\n    auto to_keyframe = MakeEmptyKeyframe(kFromTimeOffset);\n    to_keyframe->SetDefaultPropertyValue(curve_type_, target_.lock());\n    AddKeyframe(std::move(to_keyframe));\n  }\n}\n\nvoid AnimationCurve::AddKeyframe(std::unique_ptr<Keyframe> keyframe) {\n  if (!keyframes_.empty() && keyframe != nullptr &&\n      keyframe->offset() < keyframes_.back()->offset()) {\n    for (size_t i = 0; i < keyframes_.size(); ++i) {\n      if (keyframe->offset() < keyframes_.at(i)->offset()) {\n        keyframes_.insert(keyframes_.begin() + i, std::move(keyframe));\n        return;\n      }\n    }\n  }\n  keyframes_.push_back(std::move(keyframe));\n}\n\nstd::unique_ptr<Keyframe> AnimationCurve::MakeEmptyKeyframe(double offset) {\n  return std::make_unique<Keyframe>(offset);\n}\n\nstd::unique_ptr<PropertyValue> AnimationCurve::GetValue(fml::TimeDelta& t) {\n  double duration = effect_->timing().duration().ToSecondsF();\n  t = TransformedAnimationTime(keyframes_, timing_function_, duration, t);\n  size_t i = GetActiveKeyframe(keyframes_, duration, t);\n  double progress = TransformedKeyframeProgress(keyframes_, duration, t, i);\n  Keyframe* prev_keyframe = keyframes_[i].get();\n  Keyframe* next_keyframe = keyframes_[i + 1].get();\n  return Keyframe::interpolate(prev_keyframe, next_keyframe, progress);\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/basic_animation_curve.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATION_CURVE_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATION_CURVE_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"core/animation/basic_animation/animation_effect_timing.h\"\n#include \"core/animation/basic_animation/animator_target.h\"\n#include \"core/animation/basic_animation/keyframe.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nclass AnimationEffect;\nclass AnimationCurve {\n public:\n  AnimationCurve() = default;\n  ~AnimationCurve() = default;\n\n  AnimationCurve(const std::string& property_value_id,\n                 basic::AnimationEffect* effect,\n                 std::weak_ptr<AnimatorTarget> target)\n      : property_value_id_(property_value_id),\n        effect_(effect),\n        target_(target) {}\n\n public:\n  static std::unique_ptr<AnimationCurve> Create(\n      const std::string& property_value_id, basic::AnimationEffect* effect,\n      std::weak_ptr<AnimatorTarget> target);\n\n  void EnsureFromAndToKeyframe();\n\n  void AddKeyframe(std::unique_ptr<Keyframe> keyframe);\n\n  std::unique_ptr<Keyframe> MakeEmptyKeyframe(double offset);\n\n  std::unique_ptr<PropertyValue> GetValue(fml::TimeDelta& t);\n\n  TimingFunction* timing_function() { return timing_function_; }\n\n  void SetTimingFunction(TimingFunction* timing_function) {\n    timing_function_ = timing_function;\n  }\n\n  const std::string& property_value_id() { return property_value_id_; }\n\n  void SetPropertyValueId(const std::string& property_value_id) {\n    property_value_id_ = property_value_id;\n  }\n\n  AnimationCurve(const AnimationCurve&) = delete;\n  AnimationCurve& operator=(const AnimationCurve&) = delete;\n\n  void SetCurveType(const std::string& type) { curve_type_ = type; }\n\n  const std::string& GetCurveType() { return curve_type_; }\n\n protected:\n  TimingFunction* timing_function_;\n  std::vector<std::unique_ptr<basic::Keyframe>> keyframes_;\n\n private:\n  std::string property_value_id_;\n  basic::AnimationEffect* effect_;\n  std::string curve_type_;\n  std::weak_ptr<AnimatorTarget> target_;\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_BASIC_ANIMATION_CURVE_H_\n"
  },
  {
    "path": "core/animation/basic_animation/basic_keyframe_effect.cc",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/animation/basic_animation/basic_keyframe_effect.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"core/animation/basic_animation/animation_effect.h\"\n#include \"core/animation/basic_animation/basic_animation.h\"\n#include \"core/animation/basic_animation/keyframe.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nKeyframeEffect::KeyframeEffect(\n    std::vector<std::unique_ptr<KeyframeToken>> keyframes,\n    const std::shared_ptr<AnimatorTarget>& target) {\n  keyframes_token_map_ = std::move(keyframes);\n  target_ = std::weak_ptr<AnimatorTarget>(target);\n}\n\nKeyframeEffect::KeyframeEffect(\n    std::vector<std::unique_ptr<KeyframeToken>> keyframes,\n    const std::shared_ptr<AnimatorTarget>& target,\n    std::unique_ptr<AnimationEffectTiming> timing)\n    : AnimationEffect(std::move(timing)) {\n  keyframes_token_map_ = std::move(keyframes);\n  target_ = std::weak_ptr<AnimatorTarget>(target);\n}\n\nKeyframeEffect::KeyframeEffect(\n    std::vector<std::unique_ptr<KeyframeToken>> keyframes,\n    const std::shared_ptr<AnimatorTarget>& target,\n    std::unique_ptr<OptionalAnimationEffectTiming> timing)\n    : AnimationEffect(std::move(timing)) {\n  keyframes_token_map_ = std::move(keyframes);\n  target_ = std::weak_ptr<AnimatorTarget>(target);\n}\n\nvoid clean_offset_func(std::vector<std::unique_ptr<KeyframeToken>>& vec) {\n  // Get rid of keyframes with out-of-order offset.\n  if (vec.empty()) return;\n\n  bool all_non_null_opt = true;\n  for (const auto& val : vec) {\n    if (!val) {\n      all_non_null_opt = false;\n      break;\n    }\n  }\n  if (all_non_null_opt) return;\n\n  std::vector<std::unique_ptr<KeyframeToken>> cleaned_vec;\n  std::optional<double> previous_value = std::nullopt;\n\n  for (auto& val : vec) {\n    if (val && val->offset()) {\n      if (!previous_value || val->offset() > *previous_value) {\n        previous_value = val->offset();\n        cleaned_vec.push_back(std::move(val));\n      }\n    } else {\n      cleaned_vec.push_back(std::move(val));\n    }\n  }\n\n  vec = std::move(cleaned_vec);\n}\n\nvoid complete_sort_offset_func(\n    std::vector<std::unique_ptr<KeyframeToken>>& vec) {\n  if (vec.empty()) return;\n\n  // Remove the first and last std::nullopt offset.\n  if (!vec.front() && !vec.front()->offset()) {\n    vec.erase(vec.begin());\n  }\n  if (!vec.empty() && !vec.back() && !vec.back()->offset()) {\n    vec.pop_back();\n  }\n\n  if (vec.empty()) return;\n\n  // Interpolate std::nullopt keyframes that lack an offset in the middle.\n  for (size_t i = 0; i < vec.size(); ++i) {\n    if (vec[i] && !vec[i]->offset()) {\n      // Find keyframes that are not std::nullopt before and after.\n      size_t left = i;\n      while (left > 0 && !vec[left]->offset()) {\n        --left;\n      }\n\n      size_t right = i;\n      while (right < vec.size() && !vec[right]->offset()) {\n        ++right;\n      }\n\n      if (left < i && right < vec.size()) {\n        double left_value = vec[left]->offset().value_or(0.0);\n        double right_value = vec[right]->offset().value_or(0.0);\n        for (size_t j = left + 1; j < right; ++j) {\n          vec[j]->set_offset(left_value + (right_value - left_value) *\n                                              (j - left) / (right - left));\n        }\n      }\n    }\n  }\n}\n\nvoid HandleOffsetSequence(std::vector<std::unique_ptr<KeyframeToken>>& vec) {\n  clean_offset_func(vec);\n  complete_sort_offset_func(vec);\n}\n\nvoid KeyframeEffect::MakeKeyframeModel() {\n  HandleOffsetSequence(keyframes_token_map_);\n  auto raw_ptr_timing_function = timing_->easing().release();\n  for (const auto& keyframe_token : keyframes_token_map_) {\n    // caculate offset if option is empty\n    double offset = keyframe_token->offset().value_or(0.0);\n    auto value_map = keyframe_token->property_values();\n    if (!value_map || value_map->empty()) {\n      continue;\n    }\n    auto timing_function = keyframe_token->timing_function();\n    for (auto& value_pair : *value_map) {\n      if (value_pair.first == \"animation-timing-function\") {\n        continue;\n      }\n      std::unique_ptr<Keyframe> keyframe = std::make_unique<Keyframe>(offset);\n      keyframe->set_easing(timing_function);\n      keyframe->AddPropertyValue(std::move(value_pair.second));\n      auto iter = keyframe_models().find(value_pair.first);\n      if (iter == keyframe_models().end()) {\n        std::unique_ptr<AnimationCurve> new_curve =\n            AnimationCurve::Create(value_pair.first, this, target_);\n        new_curve->SetCurveType(value_pair.first);\n        std::unique_ptr<KeyframeModel> model =\n            KeyframeModel::Create(std::move(new_curve), this);\n        iter = keyframe_models()\n                   .insert({value_pair.first, std::move(model)})\n                   .first;\n      }\n      iter->second->curve()->AddKeyframe(std::move(keyframe));\n      iter->second->curve()->SetTimingFunction(raw_ptr_timing_function);\n    }\n  }\n  EnsureFromAndToKeyframe();\n}\n\nvoid KeyframeEffect::TickKeyframeModel(const fml::TimePoint& monotonic_time) {\n  bool should_send_start_event = false;\n  bool should_send_end_event = false;\n  for (auto& model : keyframe_models_) {\n    std::tie(should_send_start_event, should_send_end_event) =\n        model.second->UpdateState(monotonic_time);\n\n    if (!model.second->InEffect(monotonic_time)) {\n      continue;\n    }\n\n    AnimationCurve* curve = model.second->curve();\n\n    int temp_count = current_iteration_count_;\n\n    fml::TimeDelta trimmed = model.second->TrimTimeToCurrentIteration(\n        monotonic_time, current_iteration_count_);\n    if (current_iteration_count_ != temp_count) {\n      //      send iteration event\n      HostAnimation()->SendAnimationEvent(Animation::EventType::Iteration);\n    }\n    std::unique_ptr<PropertyValue> property_value = curve->GetValue(trimmed);\n    property_value_map_.insert_or_assign(curve->property_value_id(),\n                                         std::move(property_value));\n  }\n  if (!property_value_map_.empty()) {\n    auto strong_target = target_.lock();\n    if (strong_target) {\n      strong_target->UpdateAnimatedStyle(property_value_map_);\n    }\n    property_value_map_.clear();\n  }\n\n  if (should_send_start_event) {\n    //    send start event\n    HostAnimation()->SendAnimationEvent(Animation::EventType::Start);\n    LOGI(\"Animation start, name is: \");\n  }\n  if (should_send_end_event) {\n    //    send end event\n    HostAnimation()->SendAnimationEvent(Animation::EventType::End);\n    LOGI(\"Animation end, name is: \");\n  }\n}\n\nvoid KeyframeEffect::EnsureFromAndToKeyframe() {\n  for (auto& keyframe_model : keyframe_models_) {\n    keyframe_model.second->EnsureFromAndToKeyframe();\n  }\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/basic_keyframe_effect.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_BASIC_KEYFRAME_EFFECT_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_BASIC_KEYFRAME_EFFECT_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"core/animation/basic_animation/animation_effect.h\"\n#include \"core/animation/basic_animation/animation_effect_timing.h\"\n#include \"core/animation/basic_animation/animator_target.h\"\n#include \"core/animation/basic_animation/basic_keyframe_model.h\"\n#include \"core/animation/basic_animation/keyframe.h\"\n#include \"core/animation/utils/timing_function.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nclass KeyframeEffect : public AnimationEffect {\n public:\n  ~KeyframeEffect() override = default;\n\n  static std::unique_ptr<KeyframeEffect> Create(\n      std::vector<std::unique_ptr<KeyframeToken>> keyframes,\n      const std::shared_ptr<AnimatorTarget>& target) {\n    auto effect = std::unique_ptr<KeyframeEffect>(\n        new KeyframeEffect(std::move(keyframes), target));\n    effect->MakeKeyframeModel();\n    return effect;\n  }\n\n  static std::unique_ptr<KeyframeEffect> Create(\n      std::vector<std::unique_ptr<KeyframeToken>> keyframes,\n      const std::shared_ptr<AnimatorTarget>& target,\n      std::unique_ptr<AnimationEffectTiming> timing) {\n    auto effect = std::unique_ptr<KeyframeEffect>(\n        new KeyframeEffect(std::move(keyframes), target, std::move(timing)));\n    effect->MakeKeyframeModel();\n    return effect;\n  }\n\n  std::unordered_map<std::string, std::unique_ptr<basic::KeyframeModel>>&\n  keyframe_models() {\n    return keyframe_models_;\n  }\n\n  void MakeKeyframeModel();\n\n  void TickKeyframeModel(const fml::TimePoint& monotonic_time) override;\n\n  void EnsureFromAndToKeyframe();\n\n private:\n  KeyframeEffect(std::vector<std::unique_ptr<KeyframeToken>> keyframes,\n                 const std::shared_ptr<AnimatorTarget>& target);\n\n  KeyframeEffect(std::vector<std::unique_ptr<KeyframeToken>> keyframes,\n                 const std::shared_ptr<AnimatorTarget>& target,\n                 std::unique_ptr<AnimationEffectTiming> timing);\n\n  KeyframeEffect(std::vector<std::unique_ptr<KeyframeToken>> keyframes,\n                 const std::shared_ptr<AnimatorTarget>& target,\n                 std::unique_ptr<OptionalAnimationEffectTiming> timing);\n\n  std::weak_ptr<AnimatorTarget> target_;\n  std::vector<std::unique_ptr<KeyframeToken>> keyframes_token_map_;\n\n  Keyframe::PropertyValueMap property_value_map_;\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_BASIC_KEYFRAME_EFFECT_H_\n"
  },
  {
    "path": "core/animation/basic_animation/basic_keyframe_model.cc",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/basic_keyframe_model.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <limits>\n#include <tuple>\n#include <utility>\n\n#include \"core/animation/basic_animation/animation_effect.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nstd::unique_ptr<KeyframeModel> KeyframeModel::Create(\n    std::unique_ptr<AnimationCurve> curve, basic::AnimationEffect *effect) {\n  return std::make_unique<KeyframeModel>(std::move(curve), effect);\n}\n\nconst AnimationEffectTiming &KeyframeModel::timing() const {\n  return animation_effect_->timing();\n}\n\nvoid KeyframeModel::SetRunState(RunState run_state,\n                                fml::TimePoint monotonic_time) {\n  if ((run_state == STARTING || run_state == RUNNING ||\n       run_state == FINISHED) &&\n      run_state_ == PAUSED) {\n    total_paused_duration_ =\n        total_paused_duration_ + (monotonic_time - pause_time_);\n  } else if (run_state == PAUSED) {\n    pause_time_ = monotonic_time;\n  }\n  run_state_ = run_state;\n}\n\nfml::TimeDelta KeyframeModel::GetRepeatDuration() const {\n  if (timing().iterations() == 0) {\n    return fml::TimeDelta::Zero();\n  }\n  if (timing().duration().ToMillisecondsF() >=\n      (static_cast<double>(std::numeric_limits<int64_t>::max()) /\n       timing().iterations())) {\n    return fml::TimeDelta::Max();\n  }\n  return timing().duration() * timing().iterations();\n}\n\nKeyframeModel::Phase KeyframeModel::CalculatePhase(\n    fml::TimeDelta local_time) const {\n  fml::TimeDelta time_offset = timing().delay() * -1.0;\n  fml::TimeDelta opposite_time_offset = time_offset == fml::TimeDelta::Min()\n                                            ? fml::TimeDelta::Max()\n                                            : fml::TimeDelta() - time_offset;\n  fml::TimeDelta before_active_boundary_time =\n      std::max(opposite_time_offset, fml::TimeDelta());\n  if (local_time < before_active_boundary_time ||\n      (local_time == before_active_boundary_time &&\n       timing().playback_rate() < 0)) {\n    return Phase::BEFORE;\n  }\n  // playback_rate_ here won't be 0, is always 1.0.\n  fml::TimeDelta active_duration =\n      GetRepeatDuration() / std::abs(timing().playback_rate());\n\n  fml::TimeDelta active_after_boundary_time =\n      // Negative iterations_ represents \"infinite iterations\".\n      timing().iterations() >= 0 && ((opposite_time_offset.ToNanoseconds()) <\n                                     std::numeric_limits<int64_t>::max() -\n                                         active_duration.ToNanoseconds())\n          ? std::max(opposite_time_offset + active_duration, fml::TimeDelta())\n          : fml::TimeDelta::Max();\n  if (local_time > active_after_boundary_time ||\n      (local_time == active_after_boundary_time &&\n       timing().playback_rate() > 0)) {\n    return Phase::AFTER;\n  }\n  return Phase::ACTIVE;\n}\n\nfml::TimeDelta KeyframeModel::ConvertMonotonicTimeToLocalTime(\n    fml::TimePoint monotonic_time) const {\n  // If we're paused, time is 'stuck' at the pause time.\n  fml::TimePoint time = (run_state_ == PAUSED) ? pause_time_ : monotonic_time;\n  return time - start_time_ - total_paused_duration_;\n}\n\nfml::TimeDelta KeyframeModel::CalculateActiveTime(\n    fml::TimePoint monotonic_time) const {\n  fml::TimeDelta time_offset = timing().delay() * -1.0;\n  fml::TimeDelta local_time = ConvertMonotonicTimeToLocalTime(monotonic_time);\n\n  Phase phase = CalculatePhase(local_time);\n\n  switch (phase) {\n    case Phase::BEFORE:\n      if (timing().fill() == AnimationEffectTiming::FillMode::kBackwards ||\n          timing().fill() == AnimationEffectTiming::FillMode::kBoth) {\n        return std::max(local_time + time_offset, fml::TimeDelta());\n      }\n      return fml::TimeDelta::Min();\n    case Phase::ACTIVE:\n      return local_time + time_offset;\n    case Phase::AFTER:\n      if (timing().fill() == AnimationEffectTiming::FillMode::kForwards ||\n          timing().fill() == AnimationEffectTiming::FillMode::kBoth) {\n        // playback_rate_ here won't be 0, is always 1.0.\n        fml::TimeDelta active_duration =\n            GetRepeatDuration() / std::abs(timing().playback_rate());\n        return std::max(std::min(local_time + time_offset, active_duration),\n                        fml::TimeDelta());\n      }\n      return fml::TimeDelta::Min();\n    default:\n      return fml::TimeDelta::Min();\n  }\n}\n\nfml::TimeDelta KeyframeModel::TrimTimeToCurrentIteration(\n    fml::TimePoint monotonic_time, int &current_iteration_count) const {\n  fml::TimeDelta active_time = CalculateActiveTime(monotonic_time);\n  fml::TimeDelta start_offset = fml::TimeDelta();\n\n  // Return start offset if we are before the start of the keyframe model\n  if (active_time < fml::TimeDelta()) return start_offset;\n  // Always return zero if we have no iterations.\n  if (!timing().iterations()) return fml::TimeDelta();\n\n  // Don't attempt to trim if we have no duration.\n  if (timing().duration() <= fml::TimeDelta()) return fml::TimeDelta();\n\n  fml::TimeDelta repeated_duration = GetRepeatDuration();\n  // playback_rate_ here won't be 0, is always 1.0.\n  fml::TimeDelta active_duration =\n      repeated_duration / std::abs(timing().playback_rate());\n\n  // Calculate the scaled active time\n  fml::TimeDelta scaled_active_time;\n  if (timing().playback_rate() < 0) {\n    scaled_active_time =\n        ((active_time - active_duration) * timing().playback_rate()) +\n        start_offset;\n  } else {\n    scaled_active_time =\n        (active_time * timing().playback_rate()) + start_offset;\n  }\n\n  // Calculate the iteration time\n  fml::TimeDelta iteration_time;\n  if (scaled_active_time - start_offset == repeated_duration &&\n      fmod(static_cast<double>(timing().iterations()), 1) == 0)\n    iteration_time = timing().duration();\n  else\n    iteration_time = scaled_active_time % timing().duration();\n  // Calculate the current iteration\n  int iteration;\n  if (scaled_active_time <= fml::TimeDelta())\n    iteration = 0;\n  else if (iteration_time == timing().duration())\n    iteration = ceil(static_cast<double>(timing().iterations()) - 1);\n  else\n    iteration = static_cast<int>(scaled_active_time / timing().duration());\n\n  current_iteration_count = iteration;\n  // Check if we are running the keyframe model in reverse direction for the\n  // current iteration\n  bool reverse =\n      (timing().direction() ==\n       AnimationEffectTiming::PlaybackDirection::kReverse) ||\n      (timing().direction() ==\n           AnimationEffectTiming::PlaybackDirection::kAlternate &&\n       iteration % 2 == 1) ||\n      (timing().direction() ==\n           AnimationEffectTiming::PlaybackDirection::kAlternateReverse &&\n       iteration % 2 == 0);\n\n  // If we are running the keyframe model in reverse direction, reverse the\n  // result\n  if (reverse) iteration_time = timing().duration() - iteration_time;\n\n  return iteration_time;\n}\n\nbool KeyframeModel::InEffect(fml::TimePoint monotonic_time) const {\n  return CalculateActiveTime(monotonic_time) != fml::TimeDelta::Min();\n}\n\nvoid KeyframeModel::UpdateAnimationData(AnimationEffectTiming *data) {\n  if (data) {\n    if (curve_) {\n      // TODO(wangyifei.20010605): Will be implemented later.\n    }\n  }\n}\n\nvoid KeyframeModel::EnsureFromAndToKeyframe() {\n  if (curve_) {\n    curve_->EnsureFromAndToKeyframe();\n  }\n}\n\nstd::tuple<bool, bool> KeyframeModel::UpdateState(\n    const fml::TimePoint &monotonic_time) {\n  bool should_send_start_event = false;\n  bool should_send_end_event = false;\n  fml::TimeDelta local_time = ConvertMonotonicTimeToLocalTime(monotonic_time);\n  KeyframeModel::Phase phase = CalculatePhase(local_time);\n  switch (run_state_) {\n    case RunState::STARTING: {\n      if (phase == Phase::ACTIVE) {\n        SetRunState(RunState::RUNNING, monotonic_time);\n        should_send_start_event = true;\n      } else if (phase == Phase::AFTER) {\n        SetRunState(RunState::FINISHED, monotonic_time);\n        should_send_start_event = true;\n        should_send_end_event = true;\n      }\n      break;\n    }\n    case RunState::RUNNING: {\n      if (phase == Phase::BEFORE) {\n        SetRunState(RunState::STARTING, monotonic_time);\n        should_send_end_event = true;\n      } else if (phase == Phase::AFTER) {\n        SetRunState(RunState::FINISHED, monotonic_time);\n        should_send_end_event = true;\n      }\n      break;\n    }\n    case RunState::PAUSED: {\n      if (phase == Phase::BEFORE) {\n        SetRunState(RunState::STARTING, monotonic_time);\n      } else if (phase == Phase::ACTIVE) {\n        SetRunState(RunState::RUNNING, monotonic_time);\n      } else if (phase == Phase::AFTER) {\n        SetRunState(RunState::FINISHED, monotonic_time);\n      }\n      break;\n    }\n    case RunState::FINISHED: {\n      if (phase == Phase::BEFORE) {\n        SetRunState(RunState::STARTING, monotonic_time);\n      } else if (phase == Phase::ACTIVE) {\n        SetRunState(RunState::RUNNING, monotonic_time);\n        should_send_start_event = true;\n      }\n      break;\n    }\n  }\n  return {should_send_start_event, should_send_end_event};\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/basic_keyframe_model.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_BASIC_KEYFRAME_MODEL_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_BASIC_KEYFRAME_MODEL_H_\n\n#include <memory>\n#include <tuple>\n#include <utility>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"core/animation/basic_animation/animation_effect_timing.h\"\n#include \"core/animation/basic_animation/basic_animation_curve.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nclass AnimationEffect;\nclass AnimationCurve;\nclass KeyframeModel {\n public:\n  enum RunState {\n    STARTING = 0,\n    RUNNING,\n    PAUSED,\n    FINISHED,\n  };\n\n  enum class Phase { BEFORE, ACTIVE, AFTER };\n\n public:\n  KeyframeModel() = default;\n  ~KeyframeModel() = default;\n\n  KeyframeModel(std::unique_ptr<AnimationCurve> curve,\n                basic::AnimationEffect* effect)\n      : animation_effect_(effect),\n        run_state_(RunState::STARTING),\n        curve_(std::move(curve)) {}\n\n  static std::unique_ptr<KeyframeModel> Create(\n      std::unique_ptr<AnimationCurve> curve, basic::AnimationEffect* effect);\n\n  const AnimationEffectTiming& timing() const;\n\n  // time point ge/setter start\n  const fml::TimePoint& start_time() const { return start_time_; }\n  const fml::TimePoint& pause_time() const { return pause_time_; }\n  const fml::TimeDelta& total_paused_duration() const {\n    return total_paused_duration_;\n  }\n  void set_start_time(const fml::TimePoint& monotonic_time) {\n    start_time_ = monotonic_time;\n  }\n  bool has_set_start_time() const { return start_time_ != fml::TimePoint(); }\n  void SetRunState(RunState run_state, fml::TimePoint monotonic_time);\n  RunState GetRunState() { return run_state_; }\n  bool is_finished() const { return run_state_ == FINISHED; }\n  // time point ge/setter end\n\n  // timing caculate functions start\n  fml::TimeDelta GetRepeatDuration() const;\n\n  Phase CalculatePhase(fml::TimeDelta local_time) const;\n\n  // LocalTime is relative time\n  fml::TimeDelta ConvertMonotonicTimeToLocalTime(\n      fml::TimePoint monotonic_time) const;\n\n  fml::TimeDelta CalculateActiveTime(fml::TimePoint monotonic_time) const;\n\n  fml::TimeDelta TrimTimeToCurrentIteration(fml::TimePoint monotonic_time,\n                                            int& current_iteration_count) const;\n\n  bool InEffect(fml::TimePoint monotonic_time) const;\n\n  void UpdateAnimationData(AnimationEffectTiming* data);\n\n  void EnsureFromAndToKeyframe();\n\n  std::tuple<bool, bool> UpdateState(const fml::TimePoint& monotonic_time);\n  // timing caculate functions end\n\n  basic::AnimationCurve* curve() { return curve_.get(); }\n\n protected:\n  basic::AnimationEffect* animation_effect_;\n  RunState run_state_;\n  std::unique_ptr<basic::AnimationCurve> curve_;\n  fml::TimePoint start_time_;\n  fml::TimePoint pause_time_;\n  fml::TimeDelta total_paused_duration_{fml::TimeDelta()};\n\n private:\n  KeyframeModel(const KeyframeModel&) = delete;\n  KeyframeModel& operator=(const KeyframeModel&) = delete;\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_BASIC_KEYFRAME_MODEL_H_\n"
  },
  {
    "path": "core/animation/basic_animation/keyframe.cc",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/keyframe.h\"\n\n#include \"core/animation/basic_animation/animator_target.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nvoid Keyframe::SetDefaultPropertyValue(\n    const std::string& type, const std::shared_ptr<AnimatorTarget>& target) {\n  AddPropertyValue(target->GetStyle(type));\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/keyframe.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_KEYFRAME_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_KEYFRAME_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/animation/basic_animation/basic_animatable_values/float_property_value.h\"\n#include \"core/animation/basic_animation/property_value.h\"\n#include \"core/animation/utils/timing_function.h\"\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nclass AnimatorTarget;\nclass Keyframe {\n public:\n  using PropertyValueMap =\n      std::unordered_map<std::string, std::unique_ptr<PropertyValue>>;\n\n  Keyframe(double offset) : offset_(offset) {}\n  virtual ~Keyframe() = default;\n\n  void AddPropertyValue(std::unique_ptr<PropertyValue> property_value) {\n    property_value_ = std::move(property_value);\n  }\n\n  std::optional<double>& offset() { return offset_; }\n  void set_offset(const std::optional<double>& offset) { offset_ = offset; }\n  const fml::TimeDelta Time() {\n    return fml::TimeDelta::FromSecondsF(offset().value_or(0.0));\n  }\n  const TimingFunction& easing() const { return *easing_; }\n  TimingFunction* timing_function() const { return easing_; }\n\n  void set_easing(TimingFunction* easing) { easing_ = easing; }\n\n  void SetDefaultPropertyValue(const std::string& type,\n                               const std::shared_ptr<AnimatorTarget>& target);\n\n  static std::unique_ptr<PropertyValue> interpolate(Keyframe* prev_keyframe,\n                                                    Keyframe* next_keyframe,\n                                                    double progress) {\n    return prev_keyframe->property_value_->Interpolate(\n        progress, next_keyframe->property_value_);\n  };\n\n protected:\n  std::optional<double> offset_;\n  TimingFunction* easing_ = nullptr;\n  std::unique_ptr<PropertyValue> property_value_;\n};\n\nclass KeyframeToken : public Keyframe {\n public:\n  explicit KeyframeToken(double offset) : Keyframe(offset) {}\n\n  ~KeyframeToken() override = default;\n\n  bool AffectsProperty(const std::string& name) {\n    return property_values_->find(name) != property_values_->end();\n  }\n\n  void AddPropertyValueForToken(const std::string& property_name,\n                                std::unique_ptr<PropertyValue> property_value) {\n    if (!property_values_) {\n      property_values_ = std::make_shared<PropertyValueMap>();\n    }\n    property_values_->insert_or_assign(property_name,\n                                       std::move(property_value));\n  }\n\n  std::shared_ptr<PropertyValueMap> property_values() {\n    return property_values_;\n  }\n\n private:\n  std::shared_ptr<PropertyValueMap> property_values_;\n};\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_KEYFRAME_H_\n"
  },
  {
    "path": "core/animation/basic_animation/property_value.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_PROPERTY_VALUE_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_PROPERTY_VALUE_H_\n\n#include <cstddef>\n#include <memory>\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nenum class BasicPropertyValueType { Int = 0, Float, Color, Transform };\n\nclass PropertyValue {\n public:\n  virtual ~PropertyValue() = default;\n\n  virtual std::unique_ptr<PropertyValue> Interpolate(\n      double progress,\n      const std::unique_ptr<PropertyValue>& end_value) const = 0;\n\n  virtual std::unique_ptr<PropertyValue> Clone() const = 0;\n\n  virtual size_t GetType() const = 0;\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_PROPERTY_VALUE_H_\n"
  },
  {
    "path": "core/animation/basic_animation/thread_local_animation_handler.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/basic_animation/thread_local_animation_handler.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nThreadLocalAnimationHandler& ThreadLocalAnimationHandler::GetInstance() {\n  static thread_local ThreadLocalAnimationHandler instance;\n  return instance;\n}\n\nvoid ThreadLocalAnimationHandler::RequestNextFrame() {\n  if (!frame_provider_) {\n    return;\n  }\n\n  if (!has_requested_next_frame_) {\n    frame_provider_->RequestNextFrame([this](const fml::TimePoint& frame_time) {\n      // It must clear `has_requested_next_frame_` before invoking\n      // `DoAnimationFrame`, because  `has_requested_next_frame_` will be set\n      // during invoking `DoAnimationFrame`.\n      has_requested_next_frame_ = false;\n      DoAnimationFrame(frame_time);\n    });\n    has_requested_next_frame_ = true;\n  }\n}\n\nvoid ThreadLocalAnimationHandler::AddAnimationFrameCallback(\n    AnimationFrameCallback& callback) {\n  animation_callbacks_.insert_or_assign(reinterpret_cast<uintptr_t>(&callback),\n                                        callback.weak_from_this());\n  RequestNextFrame();\n}\n\nvoid ThreadLocalAnimationHandler::DoAnimationFrame(\n    const fml::TimePoint& frame_time) {\n  std::unordered_map<uintptr_t, std::weak_ptr<AnimationFrameCallback>>\n      temp_animation_callbacks;\n  temp_animation_callbacks.swap(animation_callbacks_);\n  for (auto& [ptr, weak_callback] : temp_animation_callbacks) {\n    std::shared_ptr<AnimationFrameCallback> callback = weak_callback.lock();\n    if (callback) {\n      callback->DoAnimationFrame(frame_time);\n    }\n  }\n}\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/basic_animation/thread_local_animation_handler.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_BASIC_ANIMATION_THREAD_LOCAL_ANIMATION_HANDLER_H_\n#define CORE_ANIMATION_BASIC_ANIMATION_THREAD_LOCAL_ANIMATION_HANDLER_H_\n\n#include <memory>\n#include <unordered_map>\n#include <utility>\n\n#include \"core/animation/basic_animation/animation_frame_callback.h\"\n#include \"core/animation/basic_animation/animation_frame_callback_provider.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\nclass ThreadLocalAnimationHandler {\n public:\n  static ThreadLocalAnimationHandler& GetInstance();\n\n  ThreadLocalAnimationHandler(const ThreadLocalAnimationHandler&) = delete;\n  ThreadLocalAnimationHandler& operator=(const ThreadLocalAnimationHandler&) =\n      delete;\n  ThreadLocalAnimationHandler(ThreadLocalAnimationHandler&&) = delete;\n  ThreadLocalAnimationHandler& operator=(ThreadLocalAnimationHandler&&) =\n      delete;\n\n  void SetFrameProvider(\n      std::unique_ptr<AnimationFrameCallbackProvider> provider) {\n    frame_provider_ = std::move(provider);\n  }\n\n  void AddAnimationFrameCallback(AnimationFrameCallback& callback);\n\n  void RemoveAnimationFrameCallback(const AnimationFrameCallback* callback) {\n    animation_callbacks_.erase(reinterpret_cast<uintptr_t>(callback));\n  }\n\n  void DoAnimationFrame(const fml::TimePoint& frame_time);\n\n private:\n  ThreadLocalAnimationHandler() = default;\n\n  void RequestNextFrame();\n\n  std::unique_ptr<AnimationFrameCallbackProvider> frame_provider_;\n  std::unordered_map<uintptr_t, std::weak_ptr<AnimationFrameCallback>>\n      animation_callbacks_;\n  bool has_requested_next_frame_{false};\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_BASIC_ANIMATION_THREAD_LOCAL_ANIMATION_HANDLER_H_\n"
  },
  {
    "path": "core/animation/constants.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_CONSTANTS_H_\n#define CORE_ANIMATION_CONSTANTS_H_\n\nnamespace lynx {\nnamespace animation {\n\nstatic constexpr const char kKeyframeAnimationName[] = \"keyframe-animation\";\nstatic constexpr const char kTransitionAnimationName[] = \"transition-animation\";\nstatic constexpr const char kKeyframeStartEventName[] = \"animationstart\";\nstatic constexpr const char kTransitionStartEventName[] = \"transitionstart\";\nstatic constexpr const char kKeyframeEndEventName[] = \"animationend\";\nstatic constexpr const char kTransitionEndEventName[] = \"transitionend\";\nstatic constexpr const char kKeyframeCancelEventName[] = \"animationcancel\";\nstatic constexpr const char kTransitionCancelEventName[] = \"transitioncancel\";\nstatic constexpr const char kKeyframeIterationEventName[] =\n    \"animationiteration\";\n\n}  // namespace animation\n}  // namespace lynx\n#endif  // CORE_ANIMATION_CONSTANTS_H_\n"
  },
  {
    "path": "core/animation/css_keyframe_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/css_keyframe_manager.h\"\n\n#include <algorithm>\n#include <queue>\n#include <utility>\n#include <vector>\n\n#include \"base/include/log/logging.h\"\n#include \"base/include/no_destructor.h\"\n#include \"core/animation/animation.h\"\n#include \"core/animation/animation_delegate.h\"\n#include \"core/animation/animation_trace_event_def.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/animation/transform_animation_curve.h\"\n#include \"core/animation/utils/timing_function.h\"\n#include \"core/renderer/css/css_keyframes_token.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace animation {\n\nconst std::unordered_set<starlight::AnimationPropertyType>&\nGetLayoutPropertyTypeSet() {\n  static const base::NoDestructor<\n      std::unordered_set<starlight::AnimationPropertyType>>\n      layoutPropertyTypeSet({ALL_LAYOUT_ANIMATION_PROPERTY});\n  return *layoutPropertyTypeSet;\n}\n\nconst std::unordered_set<AnimationCurve::CurveType>& GetLayoutCurveTypeSet() {\n  static const base::NoDestructor<std::unordered_set<AnimationCurve::CurveType>>\n      layoutCurveTypeSet({ALL_LAYOUT_CURVE_TYPE});\n  return *layoutCurveTypeSet;\n}\n\nCSSKeyframeManager::CSSKeyframeManager(tasm::Element* element) {\n  element_ = element;\n}\n\nKeyframeModel* CSSKeyframeManager::ConstructModel(\n    std::unique_ptr<AnimationCurve> curve, AnimationCurve::CurveType type,\n    Animation* animation) {\n  curve->SetElement(element_);\n  // add type here\n  curve->type_ = type;\n  curve->SetTimingFunction(\n      TimingFunction::MakeTimingFunction(animation->animation_data()));\n  curve->set_scaled_duration(animation->animation_data()->duration / 1000.0);\n  std::unique_ptr<KeyframeModel> new_keyframe_model =\n      KeyframeModel::Create(std::move(curve));\n  new_keyframe_model->set_animation_data(animation->animation_data());\n  KeyframeModel* keyframe_model = new_keyframe_model.get();\n  animation->keyframe_effect()->AddKeyframeModel(std::move(new_keyframe_model));\n  return keyframe_model;\n}\n\nbool CSSKeyframeManager::InitCurveAndModelAndKeyframe(\n    AnimationCurve::CurveType type, Animation* animation, double offset,\n    std::unique_ptr<TimingFunction> timing_function, tasm::CSSPropertyID id,\n    const tasm::CSSValue& value) {\n  KeyframeModel* keyframe_model =\n      animation->keyframe_effect()->GetKeyframeModelByCurveType(type);\n  bool has_model = (keyframe_model != nullptr);\n  std::unique_ptr<AnimationCurve> new_curve;\n  std::unique_ptr<Keyframe> keyframe;\n  if (GetLayoutCurveTypeSet().count(type) != 0) {\n    if (!has_model) {\n      new_curve = KeyframedLayoutAnimationCurve::Create();\n    }\n    keyframe = LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(offset),\n                                      std::move(timing_function));\n  } else if (type == AnimationCurve::CurveType::OPACITY) {\n    if (!has_model) {\n      new_curve = KeyframedOpacityAnimationCurve::Create();\n    }\n    keyframe = OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(offset),\n                                       std::move(timing_function));\n  } else if (type == AnimationCurve::CurveType::BGCOLOR ||\n             type == AnimationCurve::CurveType::TEXTCOLOR ||\n             type == AnimationCurve::CurveType::BORDER_LEFT_COLOR ||\n             type == AnimationCurve::CurveType::BORDER_RIGHT_COLOR ||\n             type == AnimationCurve::CurveType::BORDER_TOP_COLOR ||\n             type == AnimationCurve::CurveType::BORDER_BOTTOM_COLOR) {\n    if (!has_model) {\n      new_curve = KeyframedColorAnimationCurve::Create(\n          element()->computed_css_style()->new_animator_interpolation());\n    }\n    keyframe = ColorKeyframe::Create(fml::TimeDelta::FromSecondsF(offset),\n                                     std::move(timing_function));\n  } else if (type == AnimationCurve::CurveType::FLEX_GROW ||\n             type == AnimationCurve::CurveType::OFFSET_DISTANCE) {\n    if (!has_model) {\n      new_curve = KeyframedFloatAnimationCurve::Create();\n    }\n    keyframe = FloatKeyframe::Create(fml::TimeDelta::FromSecondsF(offset),\n                                     std::move(timing_function));\n  } else if (type == AnimationCurve::CurveType::FILTER) {\n    if (!has_model) {\n      new_curve = KeyframedFilterAnimationCurve::Create();\n    }\n    keyframe = FilterKeyframe::Create(fml::TimeDelta::FromSecondsF(offset),\n                                      std::move(timing_function));\n  } else if (type == AnimationCurve::CurveType::TRANSFORM) {\n    if (!has_model) {\n      new_curve = KeyframedTransformAnimationCurve::Create();\n    }\n    keyframe = TransformKeyframe::Create(fml::TimeDelta::FromSecondsF(offset),\n                                         std::move(timing_function));\n  } else if (type == AnimationCurve::CurveType::BACKGROUND_POSITION) {\n    if (!has_model) {\n      new_curve = KeyframedBackgroundPositionAnimationCurve::Create();\n    }\n    keyframe = BackgroundPositionKeyframe::Create(\n        fml::TimeDelta::FromSecondsF(offset), std::move(timing_function));\n  } else if (type == AnimationCurve::CurveType::TRANSFORM_ORIGIN) {\n    if (!has_model) {\n      new_curve = KeyframedTransformOriginAnimationCurve::Create();\n    }\n    keyframe = TransformOriginKeyframe::Create(\n        fml::TimeDelta::FromSecondsF(offset), std::move(timing_function));\n  } else {\n    return false;\n  }\n  // construct keyframe_model with AnimationCurve\n  if (!has_model) {\n    keyframe_model = ConstructModel(std::move(new_curve), type, animation);\n  }\n  // set css_value to keyframe\n  if (!keyframe->SetValue(id, value, element_)) {\n    return false;\n  }\n  // add keyframe into AnimationCurve\n  keyframe_model->animation_curve()->AddKeyframe(std::move(keyframe));\n  return true;\n}\n\nvoid CSSKeyframeManager::TickAllAnimation(fml::TimePoint& frame_time) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_MANAGER_TICK_ALL_ANIMATION);\n  auto temp_vec = std::vector<std::weak_ptr<Animation>>();\n  auto& true_vec = active_animations_;\n  temp_vec.swap(true_vec);\n  for (auto& iter : temp_vec) {\n    auto animation_shared_ptr = iter.lock();\n    if (animation_shared_ptr != nullptr) {\n      animation_shared_ptr->DoFrame(frame_time);\n    }\n  }\n  // After traversing the set, the final_animator_maps_ is now assembled.\n}\n\nvoid CSSKeyframeManager::SetAnimationDataAndPlay(\n    base::Vector<starlight::AnimationData>& anim_data) {\n  if (anim_data.size() == animation_data_.size() &&\n      std::equal(anim_data.begin(), anim_data.end(), animation_data_.begin())) {\n    return;\n  }\n  animation_data_ = anim_data;\n  for (auto& data : animation_data_) {\n    if (data.name.empty()) {\n      continue;\n    }\n    // 1. Update data to the existing animation or create a new one, and\n    // temporarily save them to temp_active_animations_map_.\n    auto animation = animations_map_.find(data.name);\n    if (animation != animations_map_.end()) {\n      // Update an existing animation, add it to temp_active_animations_map_ and\n      // delete it from animations_map_;\n      if (animation->second->get_animation_data() != data) {\n        animation->second->UpdateAnimationData(data);\n        temp_active_animations_map_[data.name] = animation->second;\n      } else {\n        temp_keep_animations_map_[data.name] = animation->second;\n      }\n      animations_map_.erase(animation);\n    } else {\n      // Create a new animation, add it to temp_active_animations_map_;\n      auto new_animation = CreateAnimation(data);\n      temp_active_animations_map_[data.name] = new_animation;\n    }\n  }\n  //   2. All animations remaining in animations_map_ need to be destroyed.\n  for (auto& ani_iter : animations_map_) {\n    ani_iter.second->Destroy();\n  }\n\n  for (auto& active_ani_iter : temp_active_animations_map_) {\n    if (active_ani_iter.second->animation_data()->play_state ==\n        starlight::AnimationPlayStateType::kPaused) {\n      active_ani_iter.second->Pause();\n    } else {\n      active_ani_iter.second->Play();\n    }\n  }\n  // 3. Swap active animations to animations_map_.\n  animations_map_.swap(temp_active_animations_map_);\n  animations_map_.merge(temp_keep_animations_map_);\n  temp_keep_animations_map_.clear();\n  temp_active_animations_map_.clear();\n}\n\nstd::shared_ptr<Animation> CSSKeyframeManager::CreateAnimation(\n    starlight::AnimationData& data) {\n  // 1. create animation & keyframe_effect according to animation data\n  auto animation = std::make_shared<Animation>(data.name);\n  animation->set_animation_data(data);\n\n  std::unique_ptr<KeyframeEffect> keyframe_effect = KeyframeEffect::Create();\n  keyframe_effect->BindAnimationDelegate(this);\n  keyframe_effect->BindElement(this->element());\n  animation->SetKeyframeEffect(std::move(keyframe_effect));\n  animation->BindDelegate(this);\n  animation->BindElement(this->element());\n  // 2. create keyframe Models& animation Curves according to CSS keyframe\n  // tokens\n  MakeKeyframeModel(animation.get(), data.name);\n  return animation;\n}\n\nconst tasm::CSSKeyframesContent& CSSKeyframeManager::GetKeyframesStyleMap(\n    const base::String& animation_name) {\n  DCHECK(element() != nullptr);\n  const auto& keyframes_map = element()->keyframes_map();\n  if (keyframes_map.has_value()) {\n    auto iter = keyframes_map->find(animation_name);\n    if (iter != keyframes_map->end()) {\n      return iter->second->GetKeyframesContent();\n    }\n  }\n  tasm::CSSKeyframesToken* tokens =\n      element()->GetCSSKeyframesToken(animation_name);\n  if (tokens) {\n    return tokens->GetKeyframesContent();\n  }\n  return GetEmptyKeyframeMap();\n}\n\nvoid CSSKeyframeManager::MakeKeyframeModel(Animation* animation,\n                                           const base::String& animation_name) {\n  const auto& keyframes_map = GetKeyframesStyleMap(animation_name);\n  for (const auto& keyframe_info : keyframes_map) {\n    double offset = keyframe_info.first;\n    tasm::StyleMap* style_map = keyframe_info.second.get();\n    if (!style_map) {\n      continue;\n    }\n    std::unique_ptr<TimingFunction> timing_function = nullptr;\n    starlight::TimingFunctionData timing_function_for_keyframe;\n    const auto& iter =\n        style_map->find(tasm::kPropertyIDAnimationTimingFunction);\n    if (iter != style_map->end()) {\n      auto timing_function_value = iter->second.GetArray()->get(0);\n      starlight::CSSStyleUtils::ComputeTimingFunction(\n          timing_function_value, false, timing_function_for_keyframe,\n          element_->element_manager()->GetCSSParserConfigs());\n    }\n    for (const auto& css_value_pair : *style_map) {\n      if (css_value_pair.first == tasm::kPropertyIDAnimationTimingFunction) {\n        continue;\n      }\n      timing_function =\n          TimingFunction::MakeTimingFunction(timing_function_for_keyframe);\n      AnimationCurve::CurveType curve_type =\n          static_cast<AnimationCurve::CurveType>(css_value_pair.first);\n      if (GetPropertyIDToAnimationPropertyTypeMap().find(\n              css_value_pair.first) ==\n          GetPropertyIDToAnimationPropertyTypeMap().end()) {\n        LOGE(\"[animation] unsupported animation curve type for css:\"\n             << css_value_pair.first);\n        continue;\n      }\n      bool init_status = InitCurveAndModelAndKeyframe(\n          curve_type, animation, offset, std::move(timing_function),\n          css_value_pair.first, css_value_pair.second);\n      if (!init_status) {\n        continue;\n      }\n      animation->SetRawCssId(css_value_pair.first);\n    }\n  }\n  // There may be no from(0%) and to(100%) keyframe. If so, we add a empty one.\n  animation->keyframe_effect()->EnsureFromAndToKeyframe();\n}\n\nvoid CSSKeyframeManager::RequestNextFrame(std::weak_ptr<Animation> ptr) {\n  active_animations_.push_back(ptr);\n  element_->RequestNextFrame();\n}\n\nvoid CSSKeyframeManager::UpdateFinalStyleMap(const tasm::StyleMap& styles) {\n  element()->UpdateFinalStyleMap(styles);\n}\n\nvoid CSSKeyframeManager::NotifyClientAnimated(tasm::StyleMap& styles,\n                                              tasm::CSSValue value,\n                                              tasm::CSSPropertyID css_id) {\n  if (!element_) {\n    return;\n  }\n  switch (css_id) {\n    case tasm::kPropertyIDTransform: {\n      // If the transform value is empty, we use transform default value to fit\n      // the CSS parsing logic.\n      if (value.IsEmpty() ||\n          (value.IsArray() && value.GetArray()->size() == 0)) {\n        value = GetDefaultValue(starlight::AnimationPropertyType::kTransform);\n      }\n      break;\n    }\n    case tasm::kPropertyIDOpacity: {\n      if (value.IsNumber() && value.GetNumber() < 0.0f) {\n        return;\n      }\n      break;\n    }\n    default: {\n      break;\n    }\n  }\n  if (styles.find(css_id) != styles.end()) {\n    styles.erase(css_id);\n  }\n  styles.insert_or_assign(css_id, std::move(value));\n}\n\nvoid CSSKeyframeManager::SetNeedsAnimationStyleRecalc(\n    const base::String& name) {\n  // clear effect\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_MANAGER_NEEDS_ANIMATION_RECALC);\n  if (element_) {\n    auto iter = animations_map_.find(name);\n    if (iter == animations_map_.end()) {\n      iter = temp_active_animations_map_.find(name);\n      if (iter == temp_active_animations_map_.end()) {\n        return;\n      }\n    }\n    auto animation = iter->second;\n    if (animation) {\n      tasm::StyleMap reset_origin_css_styles;\n      const auto& raw_style_set = animation->GetRawStyleSet();\n      reset_origin_css_styles.reserve(raw_style_set.size());\n      for (tasm::CSSPropertyID key : raw_style_set) {\n        if (!animation->GetTransitionFlag()) {\n          element_->ClearPersistedAnimationFillStyle(key);\n        }\n        std::optional<tasm::CSSValue> value_opt =\n            element_->GetElementStyle(key);\n        if (!value_opt) {\n          reset_origin_css_styles[key] = tasm::CSSValue();\n        } else {\n          reset_origin_css_styles[key] = std::move(*value_opt);\n        }\n      }\n      element()->UpdateFinalStyleMap(reset_origin_css_styles);\n    }\n  }\n}\n\nvoid CSSKeyframeManager::FlushAnimatedStyle() {\n  element()->FlushAnimatedStyle();\n}\n\nconst tasm::CssMeasureContext& CSSKeyframeManager::GetLengthContext(\n    tasm::Element* element) {\n  if (!element || !element->computed_css_style()) {\n    static base::NoDestructor<tasm::CssMeasureContext> sDefaultLengthContext(\n        0.f,\n        element->computed_css_style()->GetMeasureContext().layouts_unit_per_px_,\n        element->computed_css_style()\n            ->GetMeasureContext()\n            .physical_pixels_per_layout_unit_,\n        element->computed_css_style()\n                ->GetMeasureContext()\n                .layouts_unit_per_px_ *\n            DEFAULT_FONT_SIZE_DP,\n        element->computed_css_style()\n                ->GetMeasureContext()\n                .layouts_unit_per_px_ *\n            DEFAULT_FONT_SIZE_DP,\n        starlight::LayoutUnit(), starlight::LayoutUnit());\n    return *sDefaultLengthContext;\n  }\n  return element->computed_css_style()->GetMeasureContext();\n}\n\nconst tasm::CSSKeyframesContent& CSSKeyframeManager::GetEmptyKeyframeMap() {\n  static base::NoDestructor<tasm::CSSKeyframesContent> kEmptyKeyframeMap;\n  return *kEmptyKeyframeMap.get();\n}\n\ntasm::CSSValue CSSKeyframeManager::GetDefaultValue(\n    starlight::AnimationPropertyType type) {\n  if (GetLayoutPropertyTypeSet().count(type) != 0) {\n    // the default values of layout properties are 'auto'.\n    return tasm::CSSValue();\n  } else if (type == starlight::AnimationPropertyType::kOpacity) {\n    return tasm::CSSValue(OpacityKeyframe::kDefaultOpacity,\n                          tasm::CSSValuePattern::NUMBER);\n  } else if (type == starlight::AnimationPropertyType::kBackgroundColor ||\n             (type >= starlight::AnimationPropertyType::kBorderTopColor &&\n              type <= starlight::AnimationPropertyType::kBorderBottomColor)) {\n    return tasm::CSSValue(ColorKeyframe::kDefaultBackgroundColor,\n                          tasm::CSSValuePattern::NUMBER);\n  } else if (type == starlight::AnimationPropertyType::kColor) {\n    return tasm::CSSValue(ColorKeyframe::kDefaultTextColor,\n                          tasm::CSSValuePattern::NUMBER);\n  } else if (type == starlight::AnimationPropertyType::kTransform) {\n    // There are many kinds of identity transforms, we choose one(rotateZ 0\n    // degree) of them.\n    auto items = lepus::CArray::Create();\n    auto item = lepus::CArray::Create();\n    item->emplace_back(static_cast<int>(starlight::TransformType::kRotateZ));\n    item->emplace_back(0.0f);\n    items->emplace_back(std::move(item));\n    return tasm::CSSValue(std::move(items));\n  } else if (type == starlight::AnimationPropertyType::kFlexGrow) {\n    return tasm::CSSValue(FloatKeyframe::kDefaultFloatValue,\n                          tasm::CSSValuePattern::NUMBER);\n  }\n  return tasm::CSSValue();\n}\n\n// TODO:(wujintian) Remove AnimationPropertyType, it is redundant code. Only use\n// AnimationCurve::CurveType and tasm::kPropertyIDxxx in animation code.\nconst std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>&\nGetPropertyIDToAnimationPropertyTypeMap() {\n  static const base::NoDestructor<\n      std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n      kIDPropertyMap({\n#define DECLARE_ID_TYPE_MAP(id, type) \\\n  {tasm::id, starlight::AnimationPropertyType::type},\n          FOREACH_NEW_ANIMATOR_PROPERTY(DECLARE_ID_TYPE_MAP)\n#undef DECLARE_ID_TYPE_MAP\n      });\n  return *kIDPropertyMap;\n}\n\nconst std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>&\nGetPolymericPropertyIDToAnimationPropertyTypeMap(\n    starlight::AnimationPropertyType polymeric_type) {\n  if (polymeric_type == starlight::AnimationPropertyType::kBorderWidth) {\n    static const base::NoDestructor<std::unordered_map<\n        tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n        kIDPropertyBorderWidthMap({\n            {tasm::kPropertyIDBorderTopWidth,\n             starlight::AnimationPropertyType::kBorderTopWidth},\n            {tasm::kPropertyIDBorderLeftWidth,\n             starlight::AnimationPropertyType::kBorderLeftWidth},\n            {tasm::kPropertyIDBorderRightWidth,\n             starlight::AnimationPropertyType::kBorderRightWidth},\n            {tasm::kPropertyIDBorderBottomWidth,\n             starlight::AnimationPropertyType::kBorderBottomWidth},\n        });\n    return *kIDPropertyBorderWidthMap;\n  } else if (polymeric_type == starlight::AnimationPropertyType::kBorderColor) {\n    static const base::NoDestructor<std::unordered_map<\n        tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n        kIDPropertyBorderColorMap({\n            {tasm::kPropertyIDBorderTopColor,\n             starlight::AnimationPropertyType::kBorderTopColor},\n            {tasm::kPropertyIDBorderLeftColor,\n             starlight::AnimationPropertyType::kBorderLeftColor},\n            {tasm::kPropertyIDBorderRightColor,\n             starlight::AnimationPropertyType::kBorderRightColor},\n            {tasm::kPropertyIDBorderBottomColor,\n             starlight::AnimationPropertyType::kBorderBottomColor},\n        });\n    return *kIDPropertyBorderColorMap;\n  } else if (polymeric_type == starlight::AnimationPropertyType::kMargin) {\n    static const base::NoDestructor<std::unordered_map<\n        tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n        kIDPropertyMarginMap({\n            {tasm::kPropertyIDMarginTop,\n             starlight::AnimationPropertyType::kMarginTop},\n            {tasm::kPropertyIDMarginLeft,\n             starlight::AnimationPropertyType::kMarginLeft},\n            {tasm::kPropertyIDMarginRight,\n             starlight::AnimationPropertyType::kMarginRight},\n            {tasm::kPropertyIDMarginBottom,\n             starlight::AnimationPropertyType::kMarginBottom},\n        });\n    return *kIDPropertyMarginMap;\n  } else if (polymeric_type == starlight::AnimationPropertyType::kPadding) {\n    static const base::NoDestructor<std::unordered_map<\n        tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n        kIDPropertyPaddingMap({\n            {tasm::kPropertyIDPaddingTop,\n             starlight::AnimationPropertyType::kPaddingTop},\n            {tasm::kPropertyIDPaddingLeft,\n             starlight::AnimationPropertyType::kPaddingLeft},\n            {tasm::kPropertyIDPaddingRight,\n             starlight::AnimationPropertyType::kPaddingRight},\n            {tasm::kPropertyIDPaddingBottom,\n             starlight::AnimationPropertyType::kPaddingBottom},\n        });\n    return *kIDPropertyPaddingMap;\n  } else {\n    static const base::NoDestructor<std::unordered_map<\n        tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n        kIDPropertyMap({});\n    return *kIDPropertyMap;\n  }\n}\n\nvoid CSSKeyframeManager::NotifyElementSizeUpdated() {\n  for (auto& item : animations_map_) {\n    item.second->NotifyElementSizeUpdated();\n  }\n}\n\nvoid CSSKeyframeManager::NotifyUnitValuesUpdatedToAnimation(\n    tasm::CSSValuePattern type) {\n  for (auto& item : animations_map_) {\n    item.second->NotifyUnitValuesUpdatedToAnimation(type);\n  }\n}\n\nconst std::unordered_set<tasm::CSSPropertyID>& GetAnimatablePropertyIDSet() {\n  static const base::NoDestructor<std::unordered_set<tasm::CSSPropertyID>>\n      animatablePropertyIDSet({ALL_ANIMATABLE_PROPERTY_ID});\n  return *animatablePropertyIDSet;\n}\n\nbool IsAnimatableProperty(tasm::CSSPropertyID css_id) {\n  return GetAnimatablePropertyIDSet().count(css_id) != 0;\n}\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/css_keyframe_manager.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_CSS_KEYFRAME_MANAGER_H_\n#define CORE_ANIMATION_CSS_KEYFRAME_MANAGER_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/animation/animation.h\"\n#include \"core/animation/animation_delegate.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/animation/utils/timing_function.h\"\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/css/css_keyframes_token.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/style/animation_data.h\"\n\nnamespace lynx {\n\nnamespace base {\nclass VSyncMonitor;\n}\n\nnamespace tasm {\nclass Element;\nclass CSSKeyframesToken;\n}  // namespace tasm\nnamespace animation {\n\nconst std::unordered_set<starlight::AnimationPropertyType>&\nGetLayoutPropertyTypeSet();\nconst std::unordered_set<AnimationCurve::CurveType>& GetLayoutCurveTypeSet();\nconst std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>&\nGetPropertyIDToAnimationPropertyTypeMap();\nconst std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>&\nGetPolymericPropertyIDToAnimationPropertyTypeMap(\n    starlight::AnimationPropertyType polymeric_type);\n\nconst std::unordered_set<tasm::CSSPropertyID>& GetAnimatablePropertyIDSet();\n// Check that is this property a animatable property for new animator.\nbool IsAnimatableProperty(tasm::CSSPropertyID css_id);\n\nclass CSSKeyframeManager : public AnimationDelegate {\n public:\n  static const tasm::CssMeasureContext& GetLengthContext(\n      tasm::Element* element);\n\n  CSSKeyframeManager(tasm::Element* element);\n  ~CSSKeyframeManager() = default;\n\n  void AddAnimationDataAndPlay(\n      base::Vector<starlight::AnimationData>& anim_data);\n\n  void SetAnimationDataAndPlay(\n      base::Vector<starlight::AnimationData>& anim_data);\n\n  virtual void TickAllAnimation(fml::TimePoint& time);\n\n  void RequestNextFrame(std::weak_ptr<Animation> ptr) override;\n\n  void UpdateFinalStyleMap(const tasm::StyleMap& styles) override;\n\n  void FlushAnimatedStyle() override;\n\n  void NotifyClientAnimated(tasm::StyleMap& styles, tasm::CSSValue value,\n                            tasm::CSSPropertyID css_id) override;\n  void SetNeedsAnimationStyleRecalc(const base::String& name) override;\n\n  bool InitCurveAndModelAndKeyframe(\n      AnimationCurve::CurveType type, Animation* animation, double offset,\n      std::unique_ptr<TimingFunction> timing_function, tasm::CSSPropertyID id,\n      const tasm::CSSValue& value);\n\n  KeyframeModel* ConstructModel(std::unique_ptr<AnimationCurve> curve,\n                                AnimationCurve::CurveType type,\n                                Animation* animation);\n  bool SetKeyframeValue(\n      const std::pair<tasm::CSSPropertyID, tasm::CSSValue>& css_value_pair);\n\n  virtual const tasm::CSSKeyframesContent& GetKeyframesStyleMap(\n      const base::String& animation_name);\n\n  static const tasm::CSSKeyframesContent& GetEmptyKeyframeMap();\n\n  static tasm::CSSValue GetDefaultValue(starlight::AnimationPropertyType type);\n\n  void NotifyElementSizeUpdated();\n\n  void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern);\n\n protected:\n  std::shared_ptr<Animation> CreateAnimation(starlight::AnimationData& data);\n\n  base::InlineVector<starlight::AnimationData, 1> animation_data_;\n  // The collection of animations running on the current element.\n  base::LinearFlatMap<base::String, std::shared_ptr<Animation>> animations_map_;\n  // The collection of animations that no need to update states during the diff.\n  base::LinearFlatMap<base::String, std::shared_ptr<Animation>>\n      temp_keep_animations_map_;\n  // The collection of animations that need to update states during the diff.\n  base::LinearFlatMap<base::String, std::shared_ptr<Animation>>\n      temp_active_animations_map_;\n\n private:\n  void MakeKeyframeModel(Animation* animation,\n                         const base::String& animation_name);\n\n private:\n  std::shared_ptr<base::VSyncMonitor> vsync_monitor_{nullptr};\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_CSS_KEYFRAME_MANAGER_H_\n"
  },
  {
    "path": "core/animation/css_keyframe_manager_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#define private public\n#define protected public\n\n#include \"core/animation/css_keyframe_manager.h\"\n\n#include <memory>\n\n#include \"core/animation/animation.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframe_model.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/animation/testing/mock_css_keyframe_manager.h\"\n#include \"core/animation/transform_animation_curve.h\"\n#include \"core/animation/utils/timing_function.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/animation_data.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace testing {\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass CSSKeyframeManagerTest : public ::testing::Test {\n public:\n  CSSKeyframeManagerTest() {}\n  ~CSSKeyframeManagerTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<test::MockTasmDelegate>> tasm_mediator;\n  fml::RefPtr<lynx::tasm::Element> element_;\n\n  void SetUp() override {\n    LynxEnvConfig lynx_env_config(kWidth, kHeight, kDefaultLayoutsUnitPerPx,\n                                  kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<MockPaintingContext>(), tasm_mediator.get(),\n        lynx_env_config);\n    auto config = std::make_shared<PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n  }\n\n  std::shared_ptr<animation::Animation> InitTestAnimation() {\n    auto test_animation =\n        std::make_shared<animation::Animation>(\"test_animation\");\n    auto effect = animation::KeyframeEffect::Create();\n    test_animation->SetKeyframeEffect(std::move(effect));\n    element_ = manager->CreateFiberElement(\"view\");\n    test_animation->BindElement(element_.get());\n    return test_animation;\n  }\n\n  void InitTestEffect(animation::KeyframeEffect& test_effect) {\n    std::unique_ptr<animation::KeyframedOpacityAnimationCurve> test_curve(\n        animation::KeyframedOpacityAnimationCurve::Create());\n\n    auto test_frame1 =\n        animation::OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n    test_frame1->SetOpacity(1.0f);\n    test_curve->AddKeyframe(std::move(test_frame1));\n    test_curve->type_ = animation::AnimationCurve::CurveType::OPACITY;\n    auto test_frame2 = animation::OpacityKeyframe::Create(\n        fml::TimeDelta::FromSecondsF(4.0), nullptr);\n    test_frame2->SetOpacity(0.0f);\n    test_curve->AddKeyframe(std::move(test_frame2));\n    std::unique_ptr<animation::KeyframeModel> new_model =\n        animation::KeyframeModel::Create(std::move(test_curve));\n    test_effect.AddKeyframeModel(std::move(new_model));\n  }\n\n  std::unique_ptr<animation::MockCSSKeyframeManager> InitTestKeyframeManager(\n      tasm::Element* element) {\n    auto test_manager =\n        std::make_unique<animation::MockCSSKeyframeManager>(element);\n    return test_manager;\n  }\n\n  starlight::AnimationData InitAnimationData(\n      const base::String& name, long duration, long delay,\n      starlight::TimingFunctionData timing_func, int iteration_count,\n      starlight::AnimationFillModeType fill_mode,\n      starlight::AnimationDirectionType direction,\n      starlight::AnimationPlayStateType play_state) {\n    starlight::AnimationData data;\n    data.name = name;\n    data.duration = duration;\n    data.delay = delay;\n    data.timing_func = timing_func;\n    data.iteration_count = iteration_count;\n    data.fill_mode = fill_mode;\n    data.direction = direction;\n    data.play_state = play_state;\n    return data;\n  }\n\n  fml::RefPtr<Element> InitElement() {\n    auto test_element = manager->CreateFiberElement(\"view\");\n    test_element->SetAttribute(base::String(\"enable-new-animator\"),\n                               lepus::Value(\"true\"));\n    return test_element;\n  }\n};\n\nTEST_F(CSSKeyframeManagerTest, ConstructModel) {\n  auto test_element = manager->CreateFiberElement(\"view\");\n  auto test_manager = InitTestKeyframeManager(test_element.get());\n  auto test_curve = animation::KeyframedOpacityAnimationCurve::Create();\n  auto test_type = animation::AnimationCurve::CurveType::OPACITY;\n  auto test_animation = InitTestAnimation();\n  auto test_model = test_manager->ConstructModel(\n      std::move(test_curve), test_type, test_animation.get());\n  EXPECT_EQ(test_model->animation_curve()->Type(), test_type);\n  EXPECT_EQ(test_model->animation_curve()->timing_function()->GetType(),\n            animation::CubicBezierTimingFunction::MakeTimingFunction(\n                test_animation->animation_data()->timing_func)\n                .get()\n                ->GetType());\n  EXPECT_EQ(test_model->animation_curve()->scaled_duration(),\n            test_animation->animation_data()->duration / 1000.0);\n}\n\nTEST_F(CSSKeyframeManagerTest, InitCurveAndModelAndKeyframe) {\n  auto test_element = manager->CreateFiberElement(\"view\");\n  auto test_manager = InitTestKeyframeManager(test_element.get());\n  auto test_offset = 0.0;\n\n  auto id1 = lynx::tasm::CSSPropertyID::kPropertyIDLeft;\n  lynx::tasm::StyleMap output1;\n  lynx::tasm::CSSParserConfigs configs;\n  auto impl1 = lepus::Value(\"100px\");\n  lynx::tasm::UnitHandler::Process(id1, impl1, output1, configs);\n  auto raw_value1 = output1[id1];\n  auto test_animation1 = InitTestAnimation();\n  auto test_timing_function1 =\n      animation::CubicBezierTimingFunction::MakeTimingFunction(\n          test_animation1->animation_data()->timing_func);\n  auto test_type1 = animation::AnimationCurve::CurveType::LEFT;\n  bool init_success1 = test_manager->InitCurveAndModelAndKeyframe(\n      test_type1, test_animation1.get(), test_offset,\n      std::move(test_timing_function1), id1, raw_value1);\n  EXPECT_EQ(init_success1, true);\n  EXPECT_EQ(*test_animation1->animation_data(),\n            test_animation1->keyframe_effect()\n                ->keyframe_models()[0]\n                ->get_animation_data());\n\n  auto id2 = lynx::tasm::CSSPropertyID::kPropertyIDOpacity;\n  lynx::tasm::StyleMap output2;\n  auto impl2 = lepus::Value(\"1\");\n  lynx::tasm::UnitHandler::Process(id2, impl2, output2, configs);\n  auto raw_value2 = output2[id2];\n  auto test_animation2 = InitTestAnimation();\n  auto test_timing_function2 =\n      animation::CubicBezierTimingFunction::MakeTimingFunction(\n          test_animation2->animation_data()->timing_func);\n  auto test_type2 = animation::AnimationCurve::CurveType::OPACITY;\n  bool init_success2 = test_manager->InitCurveAndModelAndKeyframe(\n      test_type2, test_animation2.get(), test_offset,\n      std::move(test_timing_function2), id2, raw_value2);\n  EXPECT_EQ(init_success2, true);\n  EXPECT_EQ(*test_animation2->animation_data(),\n            test_animation2->keyframe_effect()\n                ->keyframe_models()[0]\n                ->get_animation_data());\n\n  auto id3 = lynx::tasm::CSSPropertyID::kPropertyIDColor;\n  lynx::tasm::StyleMap output3;\n  auto impl3 = lepus::Value(\"blue\");\n  lynx::tasm::UnitHandler::Process(id3, impl3, output3, configs);\n  auto raw_value3 = output3[id3];\n  auto test_animation3 = InitTestAnimation();\n  auto test_timing_function3 =\n      animation::CubicBezierTimingFunction::MakeTimingFunction(\n          test_animation3->animation_data()->timing_func);\n  auto test_type3 = animation::AnimationCurve::CurveType::TEXTCOLOR;\n  bool init_success3 = test_manager->InitCurveAndModelAndKeyframe(\n      test_type3, test_animation3.get(), test_offset,\n      std::move(test_timing_function3), id3, raw_value3);\n  EXPECT_EQ(init_success3, true);\n  EXPECT_EQ(*test_animation3->animation_data(),\n            test_animation3->keyframe_effect()\n                ->keyframe_models()[0]\n                ->get_animation_data());\n\n  auto id4 = lynx::tasm::CSSPropertyID::kPropertyIDColor;\n  lynx::tasm::StyleMap output4;\n  auto impl4 = lepus::Value(\"\");\n  lynx::tasm::UnitHandler::Process(id4, impl4, output4, configs);\n  auto raw_value4 = output4[id4];\n  auto test_animation4 = InitTestAnimation();\n  auto test_timing_function4 =\n      animation::CubicBezierTimingFunction::MakeTimingFunction(\n          test_animation4->animation_data()->timing_func);\n  auto test_type4 = animation::AnimationCurve::CurveType::UNSUPPORT;\n  bool init_success4 = test_manager->InitCurveAndModelAndKeyframe(\n      test_type4, test_animation4.get(), test_offset,\n      std::move(test_timing_function4), id4, raw_value4);\n  EXPECT_EQ(init_success4, false);\n}\n\nTEST_F(CSSKeyframeManagerTest, GetDefaultValue) {\n  auto test_element = InitElement();\n  auto test_manager = InitTestKeyframeManager(test_element.get());\n  auto default_value1 =\n      test_manager->GetDefaultValue(starlight::AnimationPropertyType::kLeft);\n  EXPECT_EQ(default_value1, tasm::CSSValue());\n\n  auto default_value2 =\n      test_manager->GetDefaultValue(starlight::AnimationPropertyType::kOpacity);\n  EXPECT_EQ(default_value2,\n            tasm::CSSValue(animation::OpacityKeyframe::kDefaultOpacity,\n                           tasm::CSSValuePattern::NUMBER));\n\n  auto default_value3 = test_manager->GetDefaultValue(\n      starlight::AnimationPropertyType::kBackgroundColor);\n  EXPECT_EQ(default_value3,\n            tasm::CSSValue(animation::ColorKeyframe::kDefaultBackgroundColor,\n                           tasm::CSSValuePattern::NUMBER));\n\n  auto default_value4 =\n      test_manager->GetDefaultValue(starlight::AnimationPropertyType::kNone);\n  EXPECT_EQ(default_value4, tasm::CSSValue());\n}\n\nTEST_F(CSSKeyframeManagerTest, HasTwoSameAnimation) {\n  auto test_element = InitElement();\n  auto test_manager = InitTestKeyframeManager(test_element.get());\n  base::Vector<starlight::AnimationData> animation_data;\n  animation_data.emplace_back(InitAnimationData(\n      base::String(\"test\"), 2000, 0, starlight::TimingFunctionData(), 1,\n      starlight::AnimationFillModeType::kBoth,\n      starlight::AnimationDirectionType::kNormal,\n      starlight::AnimationPlayStateType::kRunning));\n  animation_data.emplace_back(InitAnimationData(\n      base::String(\"test\"), 3000, 100, starlight::TimingFunctionData(), 1,\n      starlight::AnimationFillModeType::kBoth,\n      starlight::AnimationDirectionType::kNormal,\n      starlight::AnimationPlayStateType::kRunning));\n  test_manager->SetAnimationDataAndPlay(animation_data);\n  EXPECT_TRUE(test_manager->animations_map().count(base::String(\"test\")));\n  EXPECT_TRUE(test_manager->animations_map()[base::String(\"test\")]\n                  ->get_animation_data()\n                  .duration == 3000);\n}\n\nTEST_F(CSSKeyframeManagerTest, ClearEffect) {\n  auto test_element = InitElement();\n  auto test_manager = InitTestKeyframeManager(test_element.get());\n  base::Vector<starlight::AnimationData> animation_data;\n  animation_data.emplace_back(InitAnimationData(\n      base::String(\"test\"), 2000, 0, starlight::TimingFunctionData(), 1,\n      starlight::AnimationFillModeType::kBoth,\n      starlight::AnimationDirectionType::kNormal,\n      starlight::AnimationPlayStateType::kRunning));\n  test_manager->SetAnimationDataAndPlay(animation_data);\n  EXPECT_TRUE(test_manager->animations_map().count(base::String(\"test\")));\n  EXPECT_TRUE(test_manager->animations_map()[base::String(\"test\")]\n                  ->get_animation_data()\n                  .duration == 2000);\n  animation_data.clear();\n  test_manager->SetAnimationDataAndPlay(animation_data);\n  EXPECT_TRUE(test_manager->animations_map().empty());\n  EXPECT_TRUE(test_manager->GetClearEffectAnimationName() == \"test\");\n}\n\nTEST_F(CSSKeyframeManagerTest, DurationZero) {\n  auto test_element = InitElement();\n  auto test_manager = InitTestKeyframeManager(test_element.get());\n  base::Vector<starlight::AnimationData> animation_data;\n  animation_data.emplace_back(InitAnimationData(\n      base::String(\"test\"), 0, 0, starlight::TimingFunctionData(), 1,\n      starlight::AnimationFillModeType::kBoth,\n      starlight::AnimationDirectionType::kNormal,\n      starlight::AnimationPlayStateType::kRunning));\n\n  test_manager->SetAnimationDataAndPlay(animation_data);\n  EXPECT_TRUE(test_manager->has_flush_animated_style());\n  EXPECT_TRUE(\n      test_manager->animations_map()[base::String(\"test\")]->GetState() ==\n      animation::Animation::State::kStop);\n}\n\nTEST_F(CSSKeyframeManagerTest, UpdateAndFlushAnimatedStyle) {\n  auto test_element = InitElement();\n  auto test_manager = InitTestKeyframeManager(test_element.get());\n\n  auto id = lynx::tasm::CSSPropertyID::kPropertyIDLeft;\n  lynx::tasm::StyleMap test_map;\n  lynx::tasm::CSSParserConfigs configs;\n  auto impl = lepus::Value(\"100px\");\n  lynx::tasm::UnitHandler::Process(id, impl, test_map, configs);\n  const auto& final_map = *test_element->final_animator_map_;\n\n  bool update_flag = final_map.empty();\n  EXPECT_TRUE(update_flag);\n\n  auto [flush_flag, has_pending] = test_element->FlushAnimatedStyle();\n  EXPECT_FALSE(flush_flag);\n\n  test_manager->UpdateFinalStyleMap(test_map);\n  update_flag = test_element->final_animator_map_->empty();\n  EXPECT_FALSE(update_flag);\n\n  std::tie(flush_flag, has_pending) = test_element->FlushAnimatedStyle();\n  EXPECT_TRUE(flush_flag);\n}\n\nTEST_F(CSSKeyframeManagerTest, SetNeedsAnimationStyleRecalc) {\n  auto test_animation = InitTestAnimation();\n  auto test_element = InitElement();\n  auto test_manager = InitTestKeyframeManager(test_element.get());\n  test_manager->SetNeedsAnimationStyleRecalc(test_animation->name());\n  const auto& final_map = *test_element->final_animator_map_;\n  bool update_flag = final_map.empty();\n  EXPECT_TRUE(update_flag);\n}\n\nTEST_F(CSSKeyframeManagerTest, GetLayoutPropertyTypeSet) {\n  auto test_set = animation::GetLayoutPropertyTypeSet();\n  static const base::NoDestructor<\n      std::unordered_set<starlight::AnimationPropertyType>>\n      base_set({starlight::AnimationPropertyType::kWidth,\n                starlight::AnimationPropertyType::kHeight,\n                starlight::AnimationPropertyType::kTop,\n                starlight::AnimationPropertyType::kLeft,\n                starlight::AnimationPropertyType::kRight,\n                starlight::AnimationPropertyType::kBottom,\n                starlight::AnimationPropertyType::kBorderLeftWidth,\n                starlight::AnimationPropertyType::kBorderRightWidth,\n                starlight::AnimationPropertyType::kBorderTopWidth,\n                starlight::AnimationPropertyType::kBorderBottomWidth,\n                starlight::AnimationPropertyType::kPaddingLeft,\n                starlight::AnimationPropertyType::kPaddingRight,\n                starlight::AnimationPropertyType::kPaddingTop,\n                starlight::AnimationPropertyType::kPaddingBottom,\n                starlight::AnimationPropertyType::kMarginLeft,\n                starlight::AnimationPropertyType::kMarginRight,\n                starlight::AnimationPropertyType::kMarginTop,\n                starlight::AnimationPropertyType::kMarginBottom,\n                starlight::AnimationPropertyType::kMaxWidth,\n                starlight::AnimationPropertyType::kMinWidth,\n                starlight::AnimationPropertyType::kMaxHeight,\n                starlight::AnimationPropertyType::kMinHeight,\n                starlight::AnimationPropertyType::kFlexBasis});\n  EXPECT_EQ(test_set, *base_set);\n}\n\nTEST_F(CSSKeyframeManagerTest, GetLayoutCurveTypeSet) {\n  auto test_set = animation::GetLayoutCurveTypeSet();\n  static const base::NoDestructor<\n      std::unordered_set<animation::AnimationCurve::CurveType>>\n      base_set({animation::AnimationCurve::CurveType::LEFT,\n                animation::AnimationCurve::CurveType::RIGHT,\n                animation::AnimationCurve::CurveType::TOP,\n                animation::AnimationCurve::CurveType::BOTTOM,\n                animation::AnimationCurve::CurveType::HEIGHT,\n                animation::AnimationCurve::CurveType::WIDTH,\n                animation::AnimationCurve::CurveType::MAX_WIDTH,\n                animation::AnimationCurve::CurveType::MIN_WIDTH,\n                animation::AnimationCurve::CurveType::MAX_HEIGHT,\n                animation::AnimationCurve::CurveType::MIN_HEIGHT,\n                animation::AnimationCurve::CurveType::PADDING_LEFT,\n                animation::AnimationCurve::CurveType::PADDING_RIGHT,\n                animation::AnimationCurve::CurveType::PADDING_TOP,\n                animation::AnimationCurve::CurveType::PADDING_BOTTOM,\n                animation::AnimationCurve::CurveType::MARGIN_LEFT,\n                animation::AnimationCurve::CurveType::MARGIN_RIGHT,\n                animation::AnimationCurve::CurveType::MARGIN_TOP,\n                animation::AnimationCurve::CurveType::MARGIN_BOTTOM,\n                animation::AnimationCurve::CurveType::BORDER_LEFT_WIDTH,\n                animation::AnimationCurve::CurveType::BORDER_RIGHT_WIDTH,\n                animation::AnimationCurve::CurveType::BORDER_TOP_WIDTH,\n                animation::AnimationCurve::CurveType::BORDER_BOTTOM_WIDTH,\n                animation::AnimationCurve::CurveType::FLEX_BASIS});\n  EXPECT_EQ(test_set, *base_set);\n}\n\nTEST_F(CSSKeyframeManagerTest, GetPropertyIDToAnimationPropertyTypeMap) {\n  auto test_map = animation::GetPropertyIDToAnimationPropertyTypeMap();\n  static const base::NoDestructor<\n      std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n      base_map({\n          {tasm::kPropertyIDLeft, starlight::AnimationPropertyType::kLeft},\n          {tasm::kPropertyIDTop, starlight::AnimationPropertyType::kTop},\n          {tasm::kPropertyIDRight, starlight::AnimationPropertyType::kRight},\n          {tasm::kPropertyIDBottom, starlight::AnimationPropertyType::kBottom},\n          {tasm::kPropertyIDWidth, starlight::AnimationPropertyType::kWidth},\n          {tasm::kPropertyIDHeight, starlight::AnimationPropertyType::kHeight},\n          {tasm::kPropertyIDOpacity,\n           starlight::AnimationPropertyType::kOpacity},\n          {tasm::kPropertyIDBackgroundColor,\n           starlight::AnimationPropertyType::kBackgroundColor},\n          {tasm::kPropertyIDColor, starlight::AnimationPropertyType::kColor},\n          {tasm::kPropertyIDMaxWidth,\n           starlight::AnimationPropertyType::kMaxWidth},\n          {tasm::kPropertyIDMinWidth,\n           starlight::AnimationPropertyType::kMinWidth},\n          {tasm::kPropertyIDMaxHeight,\n           starlight::AnimationPropertyType::kMaxHeight},\n          {tasm::kPropertyIDMinHeight,\n           starlight::AnimationPropertyType::kMinHeight},\n          {tasm::kPropertyIDMarginLeft,\n           starlight::AnimationPropertyType::kMarginLeft},\n          {tasm::kPropertyIDMarginRight,\n           starlight::AnimationPropertyType::kMarginRight},\n          {tasm::kPropertyIDMarginTop,\n           starlight::AnimationPropertyType::kMarginTop},\n          {tasm::kPropertyIDMarginBottom,\n           starlight::AnimationPropertyType::kMarginBottom},\n          {tasm::kPropertyIDPaddingLeft,\n           starlight::AnimationPropertyType::kPaddingLeft},\n          {tasm::kPropertyIDPaddingRight,\n           starlight::AnimationPropertyType::kPaddingRight},\n          {tasm::kPropertyIDPaddingTop,\n           starlight::AnimationPropertyType::kPaddingTop},\n          {tasm::kPropertyIDPaddingBottom,\n           starlight::AnimationPropertyType::kPaddingBottom},\n          {tasm::kPropertyIDBorderLeftWidth,\n           starlight::AnimationPropertyType::kBorderLeftWidth},\n          {tasm::kPropertyIDBorderRightWidth,\n           starlight::AnimationPropertyType::kBorderRightWidth},\n          {tasm::kPropertyIDBorderTopWidth,\n           starlight::AnimationPropertyType::kBorderTopWidth},\n          {tasm::kPropertyIDBorderBottomWidth,\n           starlight::AnimationPropertyType::kBorderBottomWidth},\n          {tasm::kPropertyIDBorderLeftColor,\n           starlight::AnimationPropertyType::kBorderLeftColor},\n          {tasm::kPropertyIDBorderRightColor,\n           starlight::AnimationPropertyType::kBorderRightColor},\n          {tasm::kPropertyIDBorderTopColor,\n           starlight::AnimationPropertyType::kBorderTopColor},\n          {tasm::kPropertyIDBorderBottomColor,\n           starlight::AnimationPropertyType::kBorderBottomColor},\n          {tasm::kPropertyIDFlexGrow,\n           starlight::AnimationPropertyType::kFlexGrow},\n          {tasm::kPropertyIDFlexBasis,\n           starlight::AnimationPropertyType::kFlexBasis},\n          {tasm::kPropertyIDFilter, starlight::AnimationPropertyType::kFilter},\n          {tasm::kPropertyIDOffsetDistance,\n           starlight::AnimationPropertyType::kOffsetDistance},\n          {tasm::kPropertyIDTransform,\n           starlight::AnimationPropertyType::kTransform},\n          {tasm::kPropertyIDBackgroundPosition,\n           starlight::AnimationPropertyType::kBackgroundPosition},\n          {tasm::kPropertyIDTransformOrigin,\n           starlight::AnimationPropertyType::kTransformOrigin},\n      });\n  EXPECT_EQ(test_map, *base_map);\n}\n\nTEST_F(CSSKeyframeManagerTest, GetAnimatablePropertyIDSet) {\n  auto test_set = animation::GetAnimatablePropertyIDSet();\n  static const base::NoDestructor<std::unordered_set<tasm::CSSPropertyID>>\n      base_set({\n          tasm::kPropertyIDTop,\n          tasm::kPropertyIDLeft,\n          tasm::kPropertyIDRight,\n          tasm::kPropertyIDBottom,\n          tasm::kPropertyIDWidth,\n          tasm::kPropertyIDHeight,\n          tasm::kPropertyIDBackgroundColor,\n          tasm::kPropertyIDColor,\n          tasm::kPropertyIDOpacity,\n          tasm::kPropertyIDBorderLeftColor,\n          tasm::kPropertyIDBorderRightColor,\n          tasm::kPropertyIDBorderTopColor,\n          tasm::kPropertyIDBorderBottomColor,\n          tasm::kPropertyIDBorderLeftWidth,\n          tasm::kPropertyIDBorderRightWidth,\n          tasm::kPropertyIDBorderTopWidth,\n          tasm::kPropertyIDBorderBottomWidth,\n          tasm::kPropertyIDPaddingLeft,\n          tasm::kPropertyIDPaddingRight,\n          tasm::kPropertyIDPaddingTop,\n          tasm::kPropertyIDPaddingBottom,\n          tasm::kPropertyIDMarginLeft,\n          tasm::kPropertyIDMarginRight,\n          tasm::kPropertyIDMarginTop,\n          tasm::kPropertyIDMarginBottom,\n          tasm::kPropertyIDMaxWidth,\n          tasm::kPropertyIDMinWidth,\n          tasm::kPropertyIDMaxHeight,\n          tasm::kPropertyIDMinHeight,\n          tasm::kPropertyIDFlexGrow,\n          tasm::kPropertyIDFlexBasis,\n          tasm::kPropertyIDTransform,\n          tasm::kPropertyIDFilter,\n          tasm::kPropertyIDOffsetDistance,\n          tasm::kPropertyIDBackgroundPosition,\n          tasm::kPropertyIDTransformOrigin,\n      });\n  EXPECT_EQ(test_set, *base_set);\n  bool test_flag = animation::IsAnimatableProperty(tasm::kPropertyIDOpacity);\n  bool base_flag = *base_set->find(tasm::kPropertyIDOpacity);\n  EXPECT_EQ(test_flag, base_flag);\n}\n\nTEST_F(CSSKeyframeManagerTest,\n       GetPolymericPropertyIDToAnimationPropertyTypeMap) {\n  auto test_map = animation::GetPolymericPropertyIDToAnimationPropertyTypeMap(\n      starlight::AnimationPropertyType::kBorderWidth);\n  static const base::NoDestructor<\n      std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n      kIDPropertyBorderWidthMap({\n          {tasm::kPropertyIDBorderTopWidth,\n           starlight::AnimationPropertyType::kBorderTopWidth},\n          {tasm::kPropertyIDBorderLeftWidth,\n           starlight::AnimationPropertyType::kBorderLeftWidth},\n          {tasm::kPropertyIDBorderRightWidth,\n           starlight::AnimationPropertyType::kBorderRightWidth},\n          {tasm::kPropertyIDBorderBottomWidth,\n           starlight::AnimationPropertyType::kBorderBottomWidth},\n      });\n  EXPECT_EQ(test_map, *kIDPropertyBorderWidthMap);\n\n  test_map = animation::GetPolymericPropertyIDToAnimationPropertyTypeMap(\n      starlight::AnimationPropertyType::kBorderColor);\n  static const base::NoDestructor<\n      std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n      kIDPropertyBorderColorMap({\n          {tasm::kPropertyIDBorderTopColor,\n           starlight::AnimationPropertyType::kBorderTopColor},\n          {tasm::kPropertyIDBorderLeftColor,\n           starlight::AnimationPropertyType::kBorderLeftColor},\n          {tasm::kPropertyIDBorderRightColor,\n           starlight::AnimationPropertyType::kBorderRightColor},\n          {tasm::kPropertyIDBorderBottomColor,\n           starlight::AnimationPropertyType::kBorderBottomColor},\n      });\n  EXPECT_EQ(test_map, *kIDPropertyBorderColorMap);\n\n  test_map = animation::GetPolymericPropertyIDToAnimationPropertyTypeMap(\n      starlight::AnimationPropertyType::kMargin);\n  static const base::NoDestructor<\n      std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n      kIDPropertyMarginMap({\n          {tasm::kPropertyIDMarginTop,\n           starlight::AnimationPropertyType::kMarginTop},\n          {tasm::kPropertyIDMarginLeft,\n           starlight::AnimationPropertyType::kMarginLeft},\n          {tasm::kPropertyIDMarginRight,\n           starlight::AnimationPropertyType::kMarginRight},\n          {tasm::kPropertyIDMarginBottom,\n           starlight::AnimationPropertyType::kMarginBottom},\n      });\n  EXPECT_EQ(test_map, *kIDPropertyMarginMap);\n\n  test_map = animation::GetPolymericPropertyIDToAnimationPropertyTypeMap(\n      starlight::AnimationPropertyType::kPadding);\n  static const base::NoDestructor<\n      std::unordered_map<tasm::CSSPropertyID, starlight::AnimationPropertyType>>\n      kIDPropertyPaddingMap({\n          {tasm::kPropertyIDPaddingTop,\n           starlight::AnimationPropertyType::kPaddingTop},\n          {tasm::kPropertyIDPaddingLeft,\n           starlight::AnimationPropertyType::kPaddingLeft},\n          {tasm::kPropertyIDPaddingRight,\n           starlight::AnimationPropertyType::kPaddingRight},\n          {tasm::kPropertyIDPaddingBottom,\n           starlight::AnimationPropertyType::kPaddingBottom},\n      });\n  EXPECT_EQ(test_map, *kIDPropertyPaddingMap);\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/css_transition_manager.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/css_transition_manager.h\"\n\n#include <utility>\n\n#include \"core/animation/animation_trace_event_def.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n\nnamespace lynx {\nnamespace animation {\n\nconst char* ConvertAnimationPropertyTypeToString(\n    lynx::starlight::AnimationPropertyType type) {\n  switch (type) {\n    case starlight::AnimationPropertyType::kNone:\n      return \"none\";\n    case starlight::AnimationPropertyType::kOpacity:\n      return \"opacity\";\n    case starlight::AnimationPropertyType::kScaleX:\n      return \"scaleX\";\n    case starlight::AnimationPropertyType::kScaleY:\n      return \"scaleY\";\n    case starlight::AnimationPropertyType::kScaleXY:\n      return \"scaleXY\";\n    case starlight::AnimationPropertyType::kWidth:\n      return \"width\";\n    case starlight::AnimationPropertyType::kHeight:\n      return \"height\";\n    case starlight::AnimationPropertyType::kBackgroundColor:\n      return \"background-color\";\n    case starlight::AnimationPropertyType::kColor:\n      return \"color\";\n    case starlight::AnimationPropertyType::kVisibility:\n      return \"visibility\";\n    case starlight::AnimationPropertyType::kLeft:\n      return \"left\";\n    case starlight::AnimationPropertyType::kTop:\n      return \"top\";\n    case starlight::AnimationPropertyType::kRight:\n      return \"right\";\n    case starlight::AnimationPropertyType::kBottom:\n      return \"bottom\";\n    case starlight::AnimationPropertyType::kTransform:\n      return \"transform\";\n    case starlight::AnimationPropertyType::kAll:\n    case starlight::AnimationPropertyType::kLegacyAll_3:\n    case starlight::AnimationPropertyType::kLegacyAll_2:\n    case starlight::AnimationPropertyType::kLegacyAll_1:\n      return \"all\";\n    case starlight::AnimationPropertyType::kMaxWidth:\n      return \"max-width\";\n    case starlight::AnimationPropertyType::kMinWidth:\n      return \"min-width\";\n    case starlight::AnimationPropertyType::kMaxHeight:\n      return \"max-height\";\n    case starlight::AnimationPropertyType::kMinHeight:\n      return \"min-height\";\n    case starlight::AnimationPropertyType::kMarginLeft:\n      return \"margin-left\";\n    case starlight::AnimationPropertyType::kMarginRight:\n      return \"margin-right\";\n    case starlight::AnimationPropertyType::kMarginTop:\n      return \"margin-top\";\n    case starlight::AnimationPropertyType::kMarginBottom:\n      return \"margin-bottom\";\n    case starlight::AnimationPropertyType::kPaddingLeft:\n      return \"padding-left\";\n    case starlight::AnimationPropertyType::kPaddingRight:\n      return \"padding-right\";\n    case starlight::AnimationPropertyType::kPaddingTop:\n      return \"padding-top\";\n    case starlight::AnimationPropertyType::kPaddingBottom:\n      return \"padding-bottom\";\n    case starlight::AnimationPropertyType::kBorderLeftWidth:\n      return \"border-left-width\";\n    case starlight::AnimationPropertyType::kBorderLeftColor:\n      return \"border-left-color\";\n    case starlight::AnimationPropertyType::kBorderRightWidth:\n      return \"border-right-width\";\n    case starlight::AnimationPropertyType::kBorderRightColor:\n      return \"border-right-color\";\n    case starlight::AnimationPropertyType::kBorderTopWidth:\n      return \"border-top-width\";\n    case starlight::AnimationPropertyType::kBorderTopColor:\n      return \"border-top-color\";\n    case starlight::AnimationPropertyType::kBorderBottomWidth:\n      return \"border-bottom-width\";\n    case starlight::AnimationPropertyType::kBorderBottomColor:\n      return \"border-bottom-color\";\n    case starlight::AnimationPropertyType::kFlexBasis:\n      return \"flex-basis\";\n    case starlight::AnimationPropertyType::kFlexGrow:\n      return \"flex-grow\";\n    case starlight::AnimationPropertyType::kBorderWidth:\n      return \"border-width\";\n    case starlight::AnimationPropertyType::kBorderColor:\n      return \"border-color\";\n    case starlight::AnimationPropertyType::kMargin:\n      return \"margin\";\n    case starlight::AnimationPropertyType::kPadding:\n      return \"padding\";\n    case starlight::AnimationPropertyType::kFilter:\n      return \"filter\";\n    case starlight::AnimationPropertyType::kOffsetDistance:\n      return \"offset-distance\";\n    case starlight::AnimationPropertyType::kBoxShadow:\n      return \"box-shadow\";\n    case starlight::AnimationPropertyType::kBackgroundPosition:\n      return \"background-position\";\n    case starlight::AnimationPropertyType::kTransformOrigin:\n      return \"transform-origin\";\n    default:\n      return \"\";\n  }\n}\n\nvoid CSSTransitionManager::setTransitionData(\n    const base::Vector<starlight::TransitionData>& transition_data) {\n  transition_data_.clear();\n  property_types_.clear();\n  base::LinearFlatMap<base::String, std::shared_ptr<Animation>>\n      active_animations_map;\n  for (const auto& data : transition_data) {\n    if (data.property == starlight::AnimationPropertyType::kAll ||\n        data.property == starlight::AnimationPropertyType::kLegacyAll_1 ||\n        data.property == starlight::AnimationPropertyType::kLegacyAll_2 ||\n        data.property == starlight::AnimationPropertyType::kLegacyAll_3) {\n      starlight::TransitionData temp_data(data);\n      const auto& transition_props_map =\n          GetPropertyIDToAnimationPropertyTypeMap();\n      for (const auto& iterator : transition_props_map) {\n        temp_data.property = iterator.second;\n        SetTransitionDataInternal(temp_data, active_animations_map);\n      }\n    } else if (data.property ==\n                   starlight::AnimationPropertyType::kBorderWidth ||\n               data.property ==\n                   starlight::AnimationPropertyType::kBorderColor ||\n               data.property == starlight::AnimationPropertyType::kPadding ||\n               data.property == starlight::AnimationPropertyType::kMargin) {\n      starlight::TransitionData temp_data(data);\n      const auto& poly_transition_props_map =\n          GetPolymericPropertyIDToAnimationPropertyTypeMap(data.property);\n      for (const auto& iterator : poly_transition_props_map) {\n        temp_data.property = iterator.second;\n        SetTransitionDataInternal(temp_data, active_animations_map);\n      }\n    } else {\n      SetTransitionDataInternal(data, active_animations_map);\n    }\n  }\n\n  // 3. All animations remaining in animations_map_ need to be destroyed.\n  for (auto& animation_iterator : animations_map_) {\n    animation_iterator.second->Destroy();\n  }\n  // 4. Swap active animations to animations_map_.\n  animations_map_.swap(active_animations_map);\n}\n\nvoid CSSTransitionManager::SetTransitionDataInternal(\n    const starlight::TransitionData& data,\n    base::LinearFlatMap<base::String, std::shared_ptr<Animation>>&\n        active_animations_map) {\n  // 1. Constructor animation_data according to transition_data\n  property_types_.emplace(static_cast<unsigned int>(data.property));\n  auto& animation_data =\n      transition_data_[static_cast<unsigned int>(data.property)];\n  animation_data.name = ConvertAnimationPropertyTypeToString(data.property);\n  animation_data.duration = data.duration;\n  animation_data.delay = data.delay;\n  animation_data.timing_func = data.timing_func;\n  animation_data.iteration_count = 1;\n  animation_data.fill_mode = starlight::AnimationFillModeType::kForwards;\n  animation_data.direction = starlight::AnimationDirectionType::kNormal;\n  animation_data.play_state = starlight::AnimationPlayStateType::kRunning;\n\n  // 2. Update data to the existing animation, and temporarily save them to\n  // active_animations_map.\n  auto animation = animations_map_.find(animation_data.name);\n  if (animation != animations_map_.end()) {\n    // Add it to active_animations_map and delete it from animations_map_;\n    // Unlike keyframes, transitions do not require updating the animation\n    // parameter of existing animator.\n    active_animations_map[animation_data.name] = animation->second;\n    animations_map_.erase(animation);\n  }\n}\n\nbool CSSTransitionManager::ConsumeCSSProperty(tasm::CSSPropertyID css_id,\n                                              const tasm::CSSValue& end_value) {\n  starlight::AnimationPropertyType property_type =\n      GetAnimationPropertyType(css_id);\n  if (IsShouldTransitionType(property_type)) {\n    // 1. get transition start value and end value\n    tasm::CSSValue start_value_internal;\n    tasm::CSSValue end_value_internal;\n    std::optional<tasm::CSSValue> start_value_opt =\n        element()->GetElementPreviousStyle(css_id);\n    if (!start_value_opt || start_value_opt->IsEmpty()) {\n      // If the start value is empty, we should give it a default value rather\n      // than return directly.\n      start_value_internal = GetDefaultValue(property_type);\n    } else {\n      start_value_internal = std::move(*start_value_opt);\n    }\n\n    if (end_value.IsEmpty()) {\n      // If the end value is empty, we should give it a default value rather\n      // than return directly.\n      end_value_internal = GetDefaultValue(property_type);\n    } else {\n      end_value_internal = end_value;\n    }\n    const auto& configs = element()->element_manager()->GetCSSParserConfigs();\n    if (!IsValueValid(property_type, start_value_internal, configs) ||\n        !IsValueValid(property_type, end_value_internal, configs) ||\n        start_value_internal == end_value_internal ||\n        (previous_end_values_.contains(css_id) &&\n         end_value_internal == previous_end_values_[css_id])) {\n      TryToStopTransitionAnimator(property_type);\n      return false;\n    }\n    previous_end_values_[css_id] = end_value_internal;\n\n    // 2. construct keyframes Map\n    auto start_shared_style_map = std::make_shared<tasm::StyleMap>();\n    start_shared_style_map->insert_or_assign(css_id,\n                                             std::move(start_value_internal));\n\n    auto end_shared_style_map = std::make_shared<tasm::StyleMap>();\n    end_shared_style_map->insert_or_assign(css_id,\n                                           std::move(end_value_internal));\n\n    keyframe_tokens_[ConvertAnimationPropertyTypeToString(property_type)] =\n        tasm::CSSKeyframesContent{{0.f, std::move(start_shared_style_map)},\n                                  {1.f, std::move(end_shared_style_map)}};\n\n    // 3. create transition animation and play\n    const auto& data =\n        transition_data_.find(static_cast<unsigned int>(property_type));\n    if (data != transition_data_.end()) {\n      if (animations_map_.count(data->second.name)) {\n        // If a transition animation is replaced by another identical transition\n        // animation (both animate the same properties), then this transition\n        // animation does not require clearing effect and applying the end\n        // effect.\n        animations_map_[data->second.name]->Destroy(false);\n      }\n      std::shared_ptr<Animation> animation = CreateAnimation(data->second);\n      animation->BindDelegate(this);\n      animation->SetTransitionFlag();\n      animation->Play();\n      animations_map_[data->second.name] = std::move(animation);\n      return true;\n    }\n  }\n  return false;\n}\n\nvoid CSSTransitionManager::TickAllAnimation(fml::TimePoint& frame_time) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, TRANSITION_MANAGER_NEEDS_ANIMATION_RECALC);\n  CSSKeyframeManager::TickAllAnimation(frame_time);\n  // After traversing the set, the final_animator_maps_ is now assembled.\n}\n\nconst tasm::CSSKeyframesContent& CSSTransitionManager::GetKeyframesStyleMap(\n    const base::String& animation_name) {\n  auto it = keyframe_tokens_.find(animation_name);\n  if (it != keyframe_tokens_.end()) {\n    return it->second;\n  }\n\n  return GetEmptyKeyframeMap();\n}\n\nvoid CSSTransitionManager::TryToStopTransitionAnimator(\n    starlight::AnimationPropertyType property_type) {\n  const auto& data =\n      transition_data_.find(static_cast<unsigned int>(property_type));\n  if (data == transition_data_.end()) {\n    return;\n  }\n  const auto& animation_iterator = animations_map_.find(data->second.name);\n  if (animation_iterator == animations_map_.end()) {\n    return;\n  }\n  animation_iterator->second->Destroy();\n  animations_map_.erase(animation_iterator);\n}\n\nbool CSSTransitionManager::IsValueValid(starlight::AnimationPropertyType type,\n                                        const tasm::CSSValue& value,\n                                        const tasm::CSSParserConfigs& configs) {\n  switch (type) {\n    case starlight::AnimationPropertyType::kWidth:\n    case starlight::AnimationPropertyType::kHeight:\n    case starlight::AnimationPropertyType::kLeft:\n    case starlight::AnimationPropertyType::kTop:\n    case starlight::AnimationPropertyType::kRight:\n    case starlight::AnimationPropertyType::kBottom:\n    case starlight::AnimationPropertyType::kMaxWidth:\n    case starlight::AnimationPropertyType::kMinWidth:\n    case starlight::AnimationPropertyType::kMaxHeight:\n    case starlight::AnimationPropertyType::kMinHeight:\n    case starlight::AnimationPropertyType::kPaddingLeft:\n    case starlight::AnimationPropertyType::kPaddingRight:\n    case starlight::AnimationPropertyType::kPaddingTop:\n    case starlight::AnimationPropertyType::kPaddingBottom:\n    case starlight::AnimationPropertyType::kMarginLeft:\n    case starlight::AnimationPropertyType::kMarginRight:\n    case starlight::AnimationPropertyType::kMarginTop:\n    case starlight::AnimationPropertyType::kMarginBottom:\n    case starlight::AnimationPropertyType::kBorderLeftWidth:\n    case starlight::AnimationPropertyType::kBorderRightWidth:\n    case starlight::AnimationPropertyType::kBorderTopWidth:\n    case starlight::AnimationPropertyType::kBorderBottomWidth:\n    case starlight::AnimationPropertyType::kFlexBasis: {\n      auto parse_result = starlight::CSSStyleUtils::ToLength(\n          value, CSSKeyframeManager::GetLengthContext(element()), configs);\n      // return directly if the value of layout property is auto\n      if (!parse_result.second) {\n        return false;\n      }\n      if (!parse_result.first.IsUnit() && !parse_result.first.IsPercent() &&\n          !parse_result.first.IsCalc() && !value.IsVariable()) {\n        return false;\n      }\n      return true;\n    }\n    case starlight::AnimationPropertyType::kOpacity: {\n      if (!value.IsNumber() && !value.IsVariable()) {\n        return false;\n      }\n      if (value.IsNumber()) {\n        auto parse_result = value.GetNumber();\n        if (parse_result < 0 || parse_result > 1) {\n          return false;\n        }\n      }\n      return true;\n    }\n    case starlight::AnimationPropertyType::kFlexGrow:\n    case starlight::AnimationPropertyType::kOffsetDistance: {\n      if (!value.IsNumber() && !value.IsVariable()) {\n        return false;\n      }\n      return true;\n    }\n    case starlight::AnimationPropertyType::kColor:\n    case starlight::AnimationPropertyType::kBackgroundColor:\n    case starlight::AnimationPropertyType::kBorderLeftColor:\n    case starlight::AnimationPropertyType::kBorderRightColor:\n    case starlight::AnimationPropertyType::kBorderTopColor:\n    case starlight::AnimationPropertyType::kBorderBottomColor: {\n      if (!value.IsNumber() && !value.IsVariable()) {\n        return false;\n      }\n      return true;\n    }\n    case starlight::AnimationPropertyType::kTransform: {\n      if (!value.IsArray() && !value.IsVariable()) {\n        return false;\n      }\n      return true;\n    }\n    case starlight::AnimationPropertyType::kFilter: {\n      if (!value.IsArray() && !value.IsVariable()) {\n        return false;\n      }\n      return true;\n    }\n    case starlight::AnimationPropertyType::kBackgroundPosition: {\n      if (!value.IsArray() && !value.IsVariable()) {\n        return false;\n      }\n      return true;\n    }\n    case starlight::AnimationPropertyType::kTransformOrigin: {\n      if (!value.IsArray() && !value.IsVariable()) {\n        return false;\n      }\n      return true;\n    }\n    default: {\n      return false;\n    }\n  }\n}\n\nstarlight::AnimationPropertyType CSSTransitionManager::GetAnimationPropertyType(\n    tasm::CSSPropertyID id) {\n  const auto& transition_props_map = GetPropertyIDToAnimationPropertyTypeMap();\n  const auto& property_it = transition_props_map.find(id);\n  if (property_it != transition_props_map.end()) {\n    return property_it->second;\n  }\n  return starlight::AnimationPropertyType::kNone;\n}\n\nbool CSSTransitionManager::NeedsTransition(tasm::CSSPropertyID css_id) {\n  starlight::AnimationPropertyType property_type =\n      GetAnimationPropertyType(css_id);\n  return IsShouldTransitionType(property_type);\n}\n\nbool CSSTransitionManager::IsShouldTransitionType(\n    starlight::AnimationPropertyType type) {\n  return property_types_.find(static_cast<unsigned int>(type)) !=\n         property_types_.end();\n}\n\nvoid CSSTransitionManager::ClearPreviousEndValue(tasm::CSSPropertyID css_id) {\n  previous_end_values_.erase(css_id);\n}\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/css_transition_manager.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_CSS_TRANSITION_MANAGER_H_\n#define CORE_ANIMATION_CSS_TRANSITION_MANAGER_H_\n\n#include <memory>\n#include <string>\n\n#include \"base/include/value/base_string.h\"\n#include \"base/include/vector.h\"\n#include \"core/animation/css_keyframe_manager.h\"\n#include \"core/style/transition_data.h\"\n\nnamespace lynx {\nnamespace animation {\n\nconst char* ConvertAnimationPropertyTypeToString(\n    lynx::starlight::AnimationPropertyType type);\n\nclass CSSTransitionManager : public CSSKeyframeManager {\n public:\n  CSSTransitionManager(tasm::Element* element) : CSSKeyframeManager(element) {}\n  ~CSSTransitionManager() = default;\n\n  void setTransitionData(\n      const base::Vector<starlight::TransitionData>& transition_data);\n\n  const tasm::CSSKeyframesContent& GetKeyframesStyleMap(\n      const base::String& animation_name) override;\n\n  void TickAllAnimation(fml::TimePoint& time) override;\n\n  bool ConsumeCSSProperty(tasm::CSSPropertyID css_id,\n                          const tasm::CSSValue& end_value);\n\n  bool NeedsTransition(tasm::CSSPropertyID css_id);\n\n  void ClearPreviousEndValue(tasm::CSSPropertyID css_id);\n\n private:\n  void TryToStopTransitionAnimator(\n      starlight::AnimationPropertyType property_type);\n  bool IsValueValid(starlight::AnimationPropertyType type,\n                    const tasm::CSSValue& value,\n                    const tasm::CSSParserConfigs& configs);\n  void SetTransitionDataInternal(\n      const starlight::TransitionData& data,\n      base::LinearFlatMap<base::String, std::shared_ptr<Animation>>&\n          active_animations_map);\n\n  static starlight::AnimationPropertyType GetAnimationPropertyType(\n      tasm::CSSPropertyID id);\n\n  bool IsShouldTransitionType(starlight::AnimationPropertyType type);\n\n protected:\n  base::LinearFlatMap<unsigned int, starlight::AnimationData> transition_data_;\n  base::LinearFlatMap<base::String, tasm::CSSKeyframesContent> keyframe_tokens_;\n  base::LinearFlatSet<unsigned int> property_types_;\n  tasm::StyleMap previous_end_values_;\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_CSS_TRANSITION_MANAGER_H_\n"
  },
  {
    "path": "core/animation/css_transition_manager_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#define private public\n#define protected public\n\n#include \"core/animation/css_transition_manager.h\"\n\n#include <memory>\n\n#include \"core/animation/animation.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframe_model.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/animation/testing/mock_css_transition_manager.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/animation_data.h\"\n#include \"core/style/transition_data.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace testing {\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass CSSTransitionManagerTest : public ::testing::Test {\n public:\n  CSSTransitionManagerTest() {}\n  ~CSSTransitionManagerTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<test::MockTasmDelegate>> tasm_mediator;\n\n  void SetUp() override {\n    LynxEnvConfig lynx_env_config(kWidth, kHeight, kDefaultLayoutsUnitPerPx,\n                                  kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<MockPaintingContext>(), tasm_mediator.get(),\n        lynx_env_config);\n    auto config = std::make_shared<PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n  }\n\n  std::unique_ptr<animation::MockCSSTransitionManager>\n  InitTestTransitionManager(tasm::Element* element) {\n    auto test_manager =\n        std::make_unique<animation::MockCSSTransitionManager>(element);\n    return test_manager;\n  }\n\n  starlight::TransitionData InitTransitionData(\n      starlight::AnimationPropertyType type, long duration, long delay,\n      starlight::TimingFunctionData timing_func) {\n    starlight::TransitionData data;\n    data.property = type;\n    data.duration = duration;\n    data.delay = delay;\n    data.timing_func = timing_func;\n    return data;\n  }\n\n  fml::RefPtr<Element> InitElement() {\n    manager->SetEnableNewAnimatorRadon(true);\n    auto test_element = manager->CreateFiberElement(\"view\");\n    return test_element;\n  }\n};\n\nTEST_F(CSSTransitionManagerTest, setTransitionData) {\n  auto test_element = InitElement();\n  auto test_manager = InitTestTransitionManager(test_element.get());\n  base::Vector<starlight::TransitionData> transition_data;\n  transition_data.emplace_back(\n      InitTransitionData(starlight::AnimationPropertyType::kOpacity, 2000, 100,\n                         starlight::TimingFunctionData()));\n  test_manager->setTransitionData(transition_data);\n  // animation data check\n  EXPECT_TRUE(test_manager->animation_data().size() == 0);\n  // transition data check\n  EXPECT_TRUE(test_manager->transition_data().count(\n      static_cast<unsigned int>(starlight::AnimationPropertyType::kOpacity)));\n\n  // property_type_value check\n  EXPECT_TRUE(test_manager->property_types().find(static_cast<unsigned int>(\n                  starlight::AnimationPropertyType::kOpacity)) !=\n              test_manager->property_types().end());\n\n  // Transition ALL Check\n  transition_data.emplace_back(\n      InitTransitionData(starlight::AnimationPropertyType::kAll, 3000, 500,\n                         starlight::TimingFunctionData()));\n  test_manager->setTransitionData(transition_data);\n  // animation data check\n  EXPECT_TRUE(test_manager->animation_data().size() == 0);\n  // transition data check\n  EXPECT_TRUE(test_manager->transition_data().count(\n      static_cast<unsigned int>(starlight::AnimationPropertyType::kOpacity)));\n  EXPECT_TRUE(test_manager->transition_data().size() ==\n              animation::GetPropertyIDToAnimationPropertyTypeMap().size());\n  const auto& transition_props_map =\n      animation::GetPropertyIDToAnimationPropertyTypeMap();\n  for (const auto& iterator : transition_props_map) {\n    test_manager->property_types().emplace(\n        static_cast<unsigned int>(iterator.second));\n    auto& ani_data =\n        test_manager\n            ->transition_data()[static_cast<unsigned int>(iterator.second)];\n    EXPECT_TRUE(ani_data.name.IsEqual(\n        animation::ConvertAnimationPropertyTypeToString(iterator.second)));\n    EXPECT_TRUE(ani_data.duration == 3000);\n    EXPECT_TRUE(ani_data.delay == 500);\n  }\n  // property_type_value check\n  EXPECT_TRUE(test_manager->property_types().size() ==\n              transition_props_map.size());\n}\n\nTEST_F(CSSTransitionManagerTest, NoNeedUpdateExistingAnimator) {\n  auto test_element = InitElement();\n  auto test_manager = InitTestTransitionManager(test_element.get());\n  base::Vector<starlight::TransitionData> transition_data;\n  transition_data.emplace_back(\n      InitTransitionData(starlight::AnimationPropertyType::kOpacity, 2000, 0,\n                         starlight::TimingFunctionData()));\n  test_manager->setTransitionData(transition_data);\n  test_manager->element()->RecordElementPreviousStyle(\n      tasm::kPropertyIDOpacity, tasm::CSSValue(0.5, CSSValuePattern::NUMBER));\n  test_manager->ConsumeCSSProperty(tasm::kPropertyIDOpacity,\n                                   tasm::CSSValue(1, CSSValuePattern::NUMBER));\n  // Animation map check\n  EXPECT_TRUE(test_manager->animations_map().count(base::String(\"opacity\")));\n  starlight::AnimationData& opacity_animation_data =\n      test_manager->animations_map()[base::String(\"opacity\")]\n          ->get_animation_data();\n  EXPECT_TRUE(opacity_animation_data.name.IsEqual(\"opacity\"));\n  EXPECT_TRUE(opacity_animation_data.duration == 2000);\n  EXPECT_TRUE(opacity_animation_data.delay == 0);\n\n  // Transition ALL Check\n  transition_data.clear();\n  transition_data.emplace_back(\n      InitTransitionData(starlight::AnimationPropertyType::kAll, 3000, 0,\n                         starlight::TimingFunctionData()));\n  test_manager->setTransitionData(transition_data);\n  // Animation map check\n  EXPECT_TRUE(test_manager->animations_map().count(base::String(\"opacity\")));\n  opacity_animation_data =\n      test_manager->animations_map()[base::String(\"opacity\")]\n          ->get_animation_data();\n  EXPECT_TRUE(opacity_animation_data.name.IsEqual(\"opacity\"));\n  EXPECT_TRUE(opacity_animation_data.duration == 2000);\n  EXPECT_TRUE(opacity_animation_data.delay == 0);\n}\n\nTEST_F(CSSTransitionManagerTest, HasTwoSameAnimation) {\n  auto test_element = InitElement();\n  auto test_manager = InitTestTransitionManager(test_element.get());\n  base::Vector<starlight::TransitionData> transition_data;\n  transition_data.emplace_back(\n      InitTransitionData(starlight::AnimationPropertyType::kOpacity, 2000, 0,\n                         starlight::TimingFunctionData()));\n  transition_data.emplace_back(\n      InitTransitionData(starlight::AnimationPropertyType::kOpacity, 3000, 100,\n                         starlight::TimingFunctionData()));\n  test_manager->setTransitionData(transition_data);\n  test_manager->element()->RecordElementPreviousStyle(\n      tasm::kPropertyIDOpacity, tasm::CSSValue(0.5, CSSValuePattern::NUMBER));\n  test_manager->ConsumeCSSProperty(tasm::kPropertyIDOpacity,\n                                   tasm::CSSValue(1, CSSValuePattern::NUMBER));\n  // Animation map check\n  EXPECT_TRUE(test_manager->animations_map().count(base::String(\"opacity\")));\n  starlight::AnimationData& opacity_animation_data =\n      test_manager->animations_map()[base::String(\"opacity\")]\n          ->get_animation_data();\n  EXPECT_TRUE(opacity_animation_data.name.IsEqual(\"opacity\"));\n  EXPECT_TRUE(opacity_animation_data.duration == 3000);\n  EXPECT_TRUE(opacity_animation_data.delay == 100);\n  // transition data check\n  EXPECT_TRUE(test_manager->transition_data().count(\n      static_cast<unsigned int>(starlight::AnimationPropertyType::kOpacity)));\n  auto& opacity_transition_data =\n      test_manager->transition_data()[static_cast<unsigned int>(\n          starlight::AnimationPropertyType::kOpacity)];\n  EXPECT_TRUE(opacity_transition_data.name.IsEqual(\"opacity\"));\n  EXPECT_TRUE(opacity_transition_data.duration == 3000);\n  EXPECT_TRUE(opacity_transition_data.delay == 100);\n}\n\nTEST_F(CSSTransitionManagerTest, ClearEffect) {\n  {\n    // #1\n    auto test_element = InitElement();\n    auto test_manager = InitTestTransitionManager(test_element.get());\n    base::Vector<starlight::TransitionData> transition_data;\n    transition_data.emplace_back(\n        InitTransitionData(starlight::AnimationPropertyType::kOpacity, 2000, 0,\n                           starlight::TimingFunctionData()));\n    test_manager->setTransitionData(transition_data);\n    test_manager->element()->RecordElementPreviousStyle(\n        tasm::kPropertyIDOpacity, tasm::CSSValue(0.5, CSSValuePattern::NUMBER));\n    test_manager->ConsumeCSSProperty(\n        tasm::kPropertyIDOpacity, tasm::CSSValue(1, CSSValuePattern::NUMBER));\n    EXPECT_TRUE(test_manager->animations_map().count(base::String(\"opacity\")));\n    transition_data.clear();\n    test_manager->setTransitionData(transition_data);\n    EXPECT_TRUE(test_manager->GetClearEffectAnimationName() == \"opacity\");\n  }\n\n  {\n    // #2\n    auto test_element = InitElement();\n    auto test_manager = InitTestTransitionManager(test_element.get());\n    base::Vector<starlight::TransitionData> transition_data;\n    transition_data.emplace_back(\n        InitTransitionData(starlight::AnimationPropertyType::kOpacity, 2000, 0,\n                           starlight::TimingFunctionData()));\n    test_manager->setTransitionData(transition_data);\n    test_manager->element()->RecordElementPreviousStyle(\n        tasm::kPropertyIDOpacity, tasm::CSSValue(0.5, CSSValuePattern::NUMBER));\n    test_manager->ConsumeCSSProperty(\n        tasm::kPropertyIDOpacity, tasm::CSSValue(1, CSSValuePattern::NUMBER));\n    EXPECT_TRUE(test_manager->animations_map().count(base::String(\"opacity\")));\n    test_manager->ConsumeCSSProperty(\n        tasm::kPropertyIDOpacity, tasm::CSSValue(0.8, CSSValuePattern::NUMBER));\n    EXPECT_TRUE(test_manager->animations_map().count(base::String(\"opacity\")));\n    // If a transition animation is replaced by another identical transition\n    // animation (both animate the same properties), then this transition\n    // animation does not require applying the end effect.\n    EXPECT_TRUE(test_manager->GetClearEffectAnimationName().empty());\n  }\n\n  {\n    // #3\n    auto test_element = InitElement();\n    auto test_manager = InitTestTransitionManager(test_element.get());\n    base::Vector<starlight::TransitionData> transition_data;\n    transition_data.emplace_back(\n        InitTransitionData(starlight::AnimationPropertyType::kLeft, 2000, 0,\n                           starlight::TimingFunctionData()));\n    test_manager->setTransitionData(transition_data);\n    test_manager->element()->RecordElementPreviousStyle(\n        tasm::kPropertyIDLeft, tasm::CSSValue(0, CSSValuePattern::NUMBER));\n    test_manager->ConsumeCSSProperty(\n        tasm::kPropertyIDLeft, tasm::CSSValue(1, CSSValuePattern::NUMBER));\n    EXPECT_TRUE(test_manager->animations_map().count(base::String(\"left\")));\n    test_manager->ConsumeCSSProperty(tasm::kPropertyIDLeft, tasm::CSSValue());\n    EXPECT_TRUE(!test_manager->animations_map().count(base::String(\"left\")));\n    // If a transition animation is replaced by another identical transition\n    // animation (both animate the same properties), then this transition\n    // animation does not require applying the end effect.\n    EXPECT_TRUE(test_manager->GetClearEffectAnimationName() == \"left\");\n  }\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/keyframe_effect.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/keyframe_effect.h\"\n\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"core/animation/animation.h\"\n#include \"core/animation/animation_curve.h\"\n#include \"core/animation/animation_trace_event_def.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n\nnamespace lynx {\nnamespace animation {\n\nKeyframeEffect::KeyframeEffect() : animation_delegate_(nullptr) {}\n\nstd::unique_ptr<KeyframeEffect> KeyframeEffect::Create() {\n  return std::make_unique<KeyframeEffect>();\n}\n\nvoid KeyframeEffect::SetStartTime(fml::TimePoint& time) {\n  for (auto& keyframe_model : keyframe_models_) {\n    keyframe_model->set_start_time(time);\n  }\n}\n\nvoid KeyframeEffect::SetPauseTime(fml::TimePoint& time) {\n  for (auto& keyframe_model : keyframe_models_) {\n    keyframe_model->SetRunState(KeyframeModel::PAUSED, time);\n  }\n}\n\nvoid KeyframeEffect::AddKeyframeModel(\n    std::unique_ptr<KeyframeModel> keyframe_model) {\n  keyframe_models_.push_back(std::move(keyframe_model));\n}\n\nvoid KeyframeEffect::TickKeyframeModel(fml::TimePoint monotonic_time) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_EFFECT_TICK_MODEL);\n  // Collect animated style of this animation\n  tasm::StyleMap style_map;\n  bool should_send_start_event = false;\n  bool should_send_end_event = false;\n  bool should_persist_fill_styles = false;\n  bool should_clear_fill_styles = false;\n  bool has_checked_over_time = false;\n  style_map.reserve(keyframe_models_.size());\n  const bool is_transition_effect =\n      animation_ != nullptr && animation_->GetTransitionFlag();\n  for (auto& keyframe_model : keyframe_models_) {\n    // #1. Update the model state and collect animation event information.\n    std::tie(should_send_start_event, should_send_end_event) =\n        keyframe_model->UpdateState(monotonic_time);\n    if (!is_transition_effect && should_send_end_event &&\n        keyframe_model->is_finished()) {\n      const auto data = keyframe_model->get_animation_data();\n      if (data.fill_mode == starlight::AnimationFillModeType::kForwards ||\n          data.fill_mode == starlight::AnimationFillModeType::kBoth) {\n        should_persist_fill_styles = true;\n      } else {\n        should_clear_fill_styles = true;\n      }\n    }\n\n    // #2. Collect animation styles\n    if (!keyframe_model->InEffect(monotonic_time)) {\n      continue;\n    }\n    AnimationCurve* curve = keyframe_model->curve();\n    // The counter records whether the iteration_count has changed.\n    int temp_count = current_iteration_count_;\n    // #2.1 Calculate trimmed time to current iteration\n    fml::TimeDelta trimmed;\n    if (has_checked_over_time == false) {\n      trimmed = keyframe_model->TrimTimeToCurrentIteration(\n          monotonic_time, current_iteration_count_, need_report_over_time_);\n      has_checked_over_time = true;\n    } else {\n      bool no_need_report_flag = false;\n      trimmed = keyframe_model->TrimTimeToCurrentIteration(\n          monotonic_time, current_iteration_count_, no_need_report_flag);\n    }\n    if (current_iteration_count_ != temp_count) {\n      animation_->SendIterationEvent();\n    }\n\n    // #2.2 Calculate animation styles according to trimmed time.\n    if (animation_delegate_) {\n      tasm::CSSValue value = curve->GetValue(trimmed);\n      animation_delegate_->NotifyClientAnimated(\n          style_map, value, static_cast<tasm::CSSPropertyID>(curve->Type()));\n    }\n  }\n  // #3. Flush all animation styles to element.\n  if (animation_delegate_ != nullptr && !style_map.empty()) {\n    animation_delegate_->UpdateFinalStyleMap(style_map);\n  }\n  if (!is_transition_effect && should_persist_fill_styles &&\n      element_ != nullptr && !style_map.empty()) {\n    element_->PersistAnimationFillStyles(style_map);\n  }\n  if (!is_transition_effect && should_clear_fill_styles &&\n      element_ != nullptr && animation_ != nullptr) {\n    for (const auto& id : animation_->GetRawStyleSet()) {\n      element_->ClearPersistedAnimationFillStyle(id);\n    }\n  }\n\n  // #4. Send animation event.\n  if (animation_) {\n    if (should_send_start_event) {\n      animation_->SendStartEvent();\n      LOGI(\"Lynx Animation play, name is: \" << animation_->name().str());\n    }\n    if (should_send_end_event) {\n      animation_->SendEndEvent();\n      LOGI(\"Lynx Animation end, name is: \" << animation_->name().str());\n    }\n  }\n}\n\nbool KeyframeEffect::CheckHasFinished(fml::TimePoint& monotonic_time) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_EFFECT_CHECK_HAS_FINISHED);\n  // As all keyframe models share the same animation parameters, once one of\n  // them finishes, all others will also finish. Therefore, here we only need to\n  // check if the first keyframe model has finished.\n  if (!keyframe_models_.empty()) {\n    if (keyframe_models_[0]->is_finished() &&\n        !keyframe_models_[0]->InEffect(monotonic_time)) {\n      ClearEffect();\n    }\n    return keyframe_models_[0]->is_finished();\n  }\n  return true;\n}\n\nvoid KeyframeEffect::ClearEffect() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_EFFECT_CLEAR_EFFECT);\n  if (animation_delegate_) {\n    animation_delegate_->SetNeedsAnimationStyleRecalc(animation_->name());\n  }\n}\n\nKeyframeModel* KeyframeEffect::GetKeyframeModelByCurveType(\n    AnimationCurve::CurveType type) {\n  for (auto& keyframe_model : keyframe_models_) {\n    if (keyframe_model->animation_curve()->Type() == type) {\n      return keyframe_model.get();\n    }\n  }\n  return nullptr;\n}\n\nvoid KeyframeEffect::UpdateAnimationData(starlight::AnimationData* data) {\n  for (auto& keyframe_model : keyframe_models_) {\n    if (keyframe_model) {\n      keyframe_model->UpdateAnimationData(data);\n    }\n  }\n}\n\nvoid KeyframeEffect::EnsureFromAndToKeyframe() {\n  for (auto& keyframe_model : keyframe_models_) {\n    keyframe_model->EnsureFromAndToKeyframe();\n  }\n}\n\nvoid KeyframeEffect::NotifyElementSizeUpdated() {\n  for (auto& keyframe_model : keyframe_models_) {\n    if (keyframe_model) {\n      keyframe_model->NotifyElementSizeUpdated();\n    }\n  }\n}\n\nvoid KeyframeEffect::NotifyUnitValuesUpdatedToAnimation(\n    tasm::CSSValuePattern type) {\n  for (auto& keyframe_model : keyframe_models_) {\n    if (keyframe_model) {\n      keyframe_model->NotifyUnitValuesUpdatedToAnimation(type);\n    }\n  }\n}\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/keyframe_effect.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_KEYFRAME_EFFECT_H_\n#define CORE_ANIMATION_KEYFRAME_EFFECT_H_\n\n#include <memory>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/animation/animation_curve.h\"\n#include \"core/animation/animation_delegate.h\"\n#include \"core/animation/keyframe_model.h\"\n\nnamespace lynx {\n\nnamespace tasm {\nclass Element;\nclass CSSKeyframesToken;\n}  // namespace tasm\n\nnamespace animation {\nclass Animation;\n\nclass KeyframeEffect {\n public:\n  KeyframeEffect();\n  virtual ~KeyframeEffect() = default;\n\n  void TickKeyframeModel(fml::TimePoint monotonic_time);\n\n  void AddKeyframeModel(std::unique_ptr<KeyframeModel> keyframe_model);\n\n  KeyframeModel* GetKeyframeModelByCurveType(AnimationCurve::CurveType type);\n\n  void SetAnimation(Animation* animation) { animation_ = animation; }\n\n  void SetStartTime(fml::TimePoint& time);\n\n  void SetPauseTime(fml::TimePoint& time);\n\n  static std::unique_ptr<KeyframeEffect> Create();\n  void BindAnimationDelegate(AnimationDelegate* target) {\n    animation_delegate_ = target;\n  }\n  void BindElement(tasm::Element* element) { element_ = element; }\n  bool CheckHasFinished(fml::TimePoint& time);\n\n  void ClearEffect();\n\n  void UpdateAnimationData(starlight::AnimationData* data);\n\n  void EnsureFromAndToKeyframe();\n\n  Animation* GetAnimation() { return animation_; }\n\n  std::vector<std::unique_ptr<KeyframeModel>>& keyframe_models() {\n    return keyframe_models_;\n  }\n\n  void NotifyElementSizeUpdated();\n\n  void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern);\n\n private:\n  // The counter records the current iteration_count of the animation.\n  int current_iteration_count_ = 0;\n  tasm::Element* element_{nullptr};\n  std::vector<std::unique_ptr<KeyframeModel>> keyframe_models_;\n  AnimationDelegate* animation_delegate_;\n  Animation* animation_{nullptr};\n  bool need_report_over_time_ = true;\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_KEYFRAME_EFFECT_H_\n"
  },
  {
    "path": "core/animation/keyframe_effect_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/animation/keyframe_effect.h\"\n\n#include <memory>\n\n#include \"core/animation/css_keyframe_manager.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframe_model.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/animation/testing/mock_animation.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/animation_data.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace testing {\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass KeyframeEffectTest : public ::testing::Test {\n public:\n  KeyframeEffectTest() {}\n  ~KeyframeEffectTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<test::MockTasmDelegate>> tasm_mediator;\n\n  fml::RefPtr<lynx::tasm::Element> element_;\n  std::shared_ptr<animation::MockAnimation> animation_;\n\n  void SetUp() override {\n    LynxEnvConfig lynx_env_config(kWidth, kHeight, kDefaultLayoutsUnitPerPx,\n                                  kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<MockPaintingContext>(), tasm_mediator.get(),\n        lynx_env_config);\n    auto config = std::make_shared<PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n  }\n\n  animation::KeyframeEffect* InitTestEffect() {\n    animation_ = std::make_shared<animation::MockAnimation>(\"test_animation\");\n    auto effect = animation::KeyframeEffect::Create();\n    animation::KeyframeEffect* keyframe_effect = effect.get();\n    InitTestEffectInternal(keyframe_effect);\n    animation_->SetKeyframeEffect(std::move(effect));\n    element_ = manager->CreateFiberElement(\"view\");\n    animation_->BindElement(element_.get());\n    return keyframe_effect;\n  }\n\n  void InitTestEffectInternal(animation::KeyframeEffect* test_effect) {\n    // add first model: color model\n    std::unique_ptr<animation::KeyframedColorAnimationCurve> test_curve1(\n        animation::KeyframedColorAnimationCurve::Create(\n            starlight::XAnimationColorInterpolationType::kSRGB));\n    auto test_frame1 =\n        animation::ColorKeyframe::Create(fml::TimeDelta(), nullptr);\n    test_frame1->SetColor(4294901760);\n    test_curve1->AddKeyframe(std::move(test_frame1));\n    test_curve1->type_ = animation::AnimationCurve::CurveType::OPACITY;\n    auto test_frame2 = animation::ColorKeyframe::Create(\n        fml::TimeDelta::FromSecondsF(4.0), nullptr);\n    test_frame2->SetColor(4278255360);\n    test_curve1->AddKeyframe(std::move(test_frame2));\n    std::unique_ptr<animation::KeyframeModel> new_model1 =\n        animation::KeyframeModel::Create(std::move(test_curve1));\n    test_effect->AddKeyframeModel(std::move(new_model1));\n\n    // add second model: layout model\n    std::unique_ptr<animation::KeyframedLayoutAnimationCurve> test_curve2(\n        animation::KeyframedLayoutAnimationCurve::Create());\n\n    auto test_frame3 =\n        animation::LayoutKeyframe::Create(fml::TimeDelta(), nullptr);\n    test_frame3->SetLayout(starlight::NLength::MakeUnitNLength(2.f));\n    test_curve2->AddKeyframe(std::move(test_frame3));\n    test_curve2->type_ = animation::AnimationCurve::CurveType::LEFT;\n    auto test_frame4 = animation::LayoutKeyframe::Create(\n        fml::TimeDelta::FromSecondsF(4.0), nullptr);\n    test_frame4->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n    test_curve2->AddKeyframe(std::move(test_frame4));\n    std::unique_ptr<animation::KeyframeModel> new_model2 =\n        animation::KeyframeModel::Create(std::move(test_curve2));\n    test_effect->AddKeyframeModel(std::move(new_model2));\n\n    // add third model: opacity model\n    std::unique_ptr<animation::KeyframedOpacityAnimationCurve> test_curve3(\n        animation::KeyframedOpacityAnimationCurve::Create());\n\n    auto test_frame5 =\n        animation::OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n    test_frame5->SetOpacity(1.0f);\n    test_curve3->AddKeyframe(std::move(test_frame5));\n    test_curve3->type_ = animation::AnimationCurve::CurveType::BGCOLOR;\n    auto test_frame6 = animation::OpacityKeyframe::Create(\n        fml::TimeDelta::FromSecondsF(4.0), nullptr);\n    test_frame6->SetOpacity(0.0f);\n    test_curve3->AddKeyframe(std::move(test_frame6));\n    std::unique_ptr<animation::KeyframeModel> new_model3 =\n        animation::KeyframeModel::Create(std::move(test_curve3));\n    test_effect->AddKeyframeModel(std::move(new_model3));\n  }\n\n  starlight::AnimationData InitAnimationData(\n      const base::String& name, long duration, long delay,\n      starlight::TimingFunctionData timing_func, int iteration_count,\n      starlight::AnimationFillModeType fill_mode,\n      starlight::AnimationDirectionType direction,\n      starlight::AnimationPlayStateType play_state) {\n    starlight::AnimationData data;\n    data.name = name;\n    data.duration = duration;\n    data.delay = delay;\n    data.timing_func = timing_func;\n    data.iteration_count = iteration_count;\n    data.fill_mode = fill_mode;\n    data.direction = direction;\n    data.play_state = play_state;\n    return data;\n  }\n};\n\nTEST_F(KeyframeEffectTest, AddKeyframeModel) {\n  animation::KeyframeEffect test_effect;\n  EXPECT_TRUE(test_effect.keyframe_models().empty());\n\n  // add first model: color model\n  std::unique_ptr<animation::KeyframedColorAnimationCurve> test_curve1(\n      animation::KeyframedColorAnimationCurve::Create(\n          starlight::XAnimationColorInterpolationType::kSRGB));\n\n  auto test_frame1 =\n      animation::ColorKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetColor(4294901760);\n  test_curve1->AddKeyframe(std::move(test_frame1));\n  test_curve1->type_ = animation::AnimationCurve::CurveType::BGCOLOR;\n  auto test_frame2 = animation::ColorKeyframe::Create(\n      fml::TimeDelta::FromSecondsF(4.0), nullptr);\n  test_frame2->SetColor(4278255360);\n  test_curve1->AddKeyframe(std::move(test_frame2));\n\n  std::unique_ptr<animation::KeyframeModel> new_model1 =\n      animation::KeyframeModel::Create(std::move(test_curve1));\n  test_effect.AddKeyframeModel(std::move(new_model1));\n\n  EXPECT_EQ(1ul, test_effect.keyframe_models().size());\n\n  // add second model: layout model\n  std::unique_ptr<animation::KeyframedLayoutAnimationCurve> test_curve2(\n      animation::KeyframedLayoutAnimationCurve::Create());\n\n  auto test_frame3 =\n      animation::LayoutKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame3->SetLayout(starlight::NLength::MakeUnitNLength(2.f));\n  test_curve2->AddKeyframe(std::move(test_frame3));\n  test_curve2->type_ = animation::AnimationCurve::CurveType::LEFT;\n  auto test_frame4 = animation::LayoutKeyframe::Create(\n      fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame4->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n  test_curve2->AddKeyframe(std::move(test_frame4));\n\n  std::unique_ptr<animation::KeyframeModel> new_model2 =\n      animation::KeyframeModel::Create(std::move(test_curve2));\n  test_effect.AddKeyframeModel(std::move(new_model2));\n\n  EXPECT_EQ(2ul, test_effect.keyframe_models().size());\n\n  // add third model: opacity model\n  std::unique_ptr<animation::KeyframedOpacityAnimationCurve> test_curve3(\n      animation::KeyframedOpacityAnimationCurve::Create());\n\n  auto test_frame5 =\n      animation::OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame5->SetOpacity(1.0f);\n  test_curve3->AddKeyframe(std::move(test_frame5));\n  test_curve3->type_ = animation::AnimationCurve::CurveType::OPACITY;\n  auto test_frame6 = animation::OpacityKeyframe::Create(\n      fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame6->SetOpacity(0.0f);\n  test_curve3->AddKeyframe(std::move(test_frame6));\n\n  std::unique_ptr<animation::KeyframeModel> new_model3 =\n      animation::KeyframeModel::Create(std::move(test_curve3));\n  test_effect.AddKeyframeModel(std::move(new_model3));\n\n  EXPECT_EQ(3ul, test_effect.keyframe_models().size());\n}\n\nTEST_F(KeyframeEffectTest, SetStartTime) {\n  animation::KeyframeEffect* test_effect = InitTestEffect();\n  fml::TimePoint test_start_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0));\n  test_effect->SetStartTime(test_start_time);\n  for (auto& model : test_effect->keyframe_models()) {\n    EXPECT_EQ(model->start_time(), fml::TimePoint::FromEpochDelta(\n                                       fml::TimeDelta::FromSecondsF(1.0)));\n    EXPECT_EQ(model->GetRunState(),\n              animation::KeyframeModel::RunState::STARTING);\n  }\n}\n\nTEST_F(KeyframeEffectTest, SetPauseTime) {\n  animation::KeyframeEffect* test_effect = InitTestEffect();\n  fml::TimePoint test_pause_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.0));\n  test_effect->SetPauseTime(test_pause_time);\n  for (auto& model : test_effect->keyframe_models()) {\n    EXPECT_EQ(model->pause_time(), fml::TimePoint::FromEpochDelta(\n                                       fml::TimeDelta::FromSecondsF(2.0)));\n    EXPECT_EQ(model->GetRunState(), animation::KeyframeModel::RunState::PAUSED);\n  }\n}\n\nTEST_F(KeyframeEffectTest, GetKeyframeModelByCurveType) {\n  animation::KeyframeEffect* test_effect = InitTestEffect();\n  animation::KeyframeModel* opacity_model =\n      test_effect->GetKeyframeModelByCurveType(\n          animation::AnimationCurve::CurveType::OPACITY);\n  EXPECT_NE(nullptr, opacity_model);\n}\n\nTEST_F(KeyframeEffectTest, CheckHasFinished) {\n  animation::KeyframeEffect* test_effect = InitTestEffect();\n\n  std::unique_ptr<starlight::AnimationData> default_data =\n      std::make_unique<starlight::AnimationData>();\n  default_data->duration = 1000;\n  default_data->delay = 0;\n  default_data->iteration_count = 1;\n\n  test_effect->UpdateAnimationData(default_data.get());\n  fml::TimePoint start_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0));\n  test_effect->SetStartTime(start_time);\n\n  fml::TimePoint test_time1 =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.0));\n  test_effect->TickKeyframeModel(test_time1);\n  bool all_finished = test_effect->CheckHasFinished(test_time1);\n  EXPECT_EQ(false, all_finished);\n\n  fml::TimePoint test_time2 =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(5.0));\n  test_effect->TickKeyframeModel(test_time2);\n  all_finished = test_effect->CheckHasFinished(test_time2);\n  EXPECT_EQ(true, all_finished);\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/keyframe_model.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/keyframe_model.h\"\n\n#include <algorithm>\n#include <climits>\n#include <cstdlib>\n#include <limits>\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"core/animation/animation_curve.h\"\n#include \"core/services/event_report/event_tracker.h\"\n\nnamespace lynx {\nnamespace animation {\n\n[[maybe_unused]] static constexpr int64_t kFifteenMinutesInSeconds = 900;\n[[maybe_unused]] static constexpr int64_t kThirtyMinutesInSeconds = 1800;\n[[maybe_unused]] static constexpr int64_t kOneHourInSeconds = 3600;\n\nfml::TimeDelta KeyframeModel::GetRepeatDuration() const {\n  if (animation_data_->iteration_count == 0) {\n    return fml::TimeDelta::Zero();\n  }\n  if (curve_->Duration().ToNanoseconds() >=\n      (static_cast<double>(std::numeric_limits<int64_t>::max()) /\n       static_cast<double>(animation_data_->iteration_count))) {\n    return fml::TimeDelta::Max();\n  }\n  return curve_->Duration() *\n         static_cast<double>(animation_data_->iteration_count);\n}\n\nstd::unique_ptr<KeyframeModel> KeyframeModel::Create(\n    std::unique_ptr<AnimationCurve> curve) {\n  return std::make_unique<KeyframeModel>(std::move(curve));\n}\n\nKeyframeModel::KeyframeModel(std::unique_ptr<AnimationCurve> curve)\n    : run_state_(RunState::STARTING),\n      curve_(std::move(curve)),\n      playback_rate_(1) {}\n\n// This function is a state machine, which updates the model's state based on\n// the monotonic_time and current state, while determining whether to send start\n// or end events.\nstd::tuple<bool, bool> KeyframeModel::UpdateState(\n    const fml::TimePoint& monotonic_time) {\n  bool should_send_start_event = false;\n  bool should_send_end_event = false;\n  fml::TimeDelta local_time = ConvertMonotonicTimeToLocalTime(monotonic_time);\n  KeyframeModel::Phase phase = CalculatePhase(local_time);\n  switch (run_state_) {\n    case RunState::STARTING: {\n      if (phase == Phase::ACTIVE) {\n        SetRunState(RunState::RUNNING, monotonic_time);\n        should_send_start_event = true;\n      } else if (phase == Phase::AFTER) {\n        SetRunState(RunState::FINISHED, monotonic_time);\n        should_send_start_event = true;\n        should_send_end_event = true;\n      }\n      break;\n    }\n    case RunState::RUNNING: {\n      if (phase == Phase::BEFORE) {\n        SetRunState(RunState::STARTING, monotonic_time);\n        should_send_end_event = true;\n      } else if (phase == Phase::AFTER) {\n        SetRunState(RunState::FINISHED, monotonic_time);\n        should_send_end_event = true;\n      }\n      break;\n    }\n    case RunState::PAUSED: {\n      if (phase == Phase::BEFORE) {\n        SetRunState(RunState::STARTING, monotonic_time);\n      } else if (phase == Phase::ACTIVE) {\n        SetRunState(RunState::RUNNING, monotonic_time);\n      } else if (phase == Phase::AFTER) {\n        SetRunState(RunState::FINISHED, monotonic_time);\n      }\n      break;\n    }\n    case RunState::FINISHED: {\n      if (phase == Phase::BEFORE) {\n        SetRunState(RunState::STARTING, monotonic_time);\n      } else if (phase == Phase::ACTIVE) {\n        SetRunState(RunState::RUNNING, monotonic_time);\n        should_send_start_event = true;\n      }\n      break;\n    }\n  }\n  return {should_send_start_event, should_send_end_event};\n}\n\nKeyframeModel::Phase KeyframeModel::CalculatePhase(\n    fml::TimeDelta local_time) const {\n  fml::TimeDelta time_offset =\n      fml::TimeDelta::FromMilliseconds(animation_data_->delay * -1);\n  fml::TimeDelta opposite_time_offset = time_offset == fml::TimeDelta::Min()\n                                            ? fml::TimeDelta::Max()\n                                            : fml::TimeDelta() - time_offset;\n  fml::TimeDelta before_active_boundary_time =\n      std::max(opposite_time_offset, fml::TimeDelta());\n  if (local_time < before_active_boundary_time ||\n      (local_time == before_active_boundary_time && playback_rate_ < 0)) {\n    return KeyframeModel::Phase::BEFORE;\n  }\n  // playback_rate_ here won't be 0, is always 1.0.\n  fml::TimeDelta active_duration =\n      GetRepeatDuration() / std::abs(playback_rate_);\n\n  fml::TimeDelta active_after_boundary_time =\n      // Negative iterations_ represents \"infinite iterations\".\n      animation_data_->iteration_count >= 0 &&\n              ((opposite_time_offset.ToNanoseconds()) <\n               std::numeric_limits<int64_t>::max() -\n                   active_duration.ToNanoseconds())\n          ? std::max(opposite_time_offset + active_duration, fml::TimeDelta())\n          : fml::TimeDelta::Max();\n  if (local_time > active_after_boundary_time ||\n      (local_time == active_after_boundary_time && playback_rate_ > 0)) {\n    return KeyframeModel::Phase::AFTER;\n  }\n  return KeyframeModel::Phase::ACTIVE;\n}\n\nfml::TimeDelta KeyframeModel::ConvertMonotonicTimeToLocalTime(\n    fml::TimePoint monotonic_time) const {\n  // If we're paused, time is 'stuck' at the pause time.\n  fml::TimePoint time = (run_state_ == PAUSED) ? pause_time_ : monotonic_time;\n  return time - start_time_ - total_paused_duration_;\n}\n\nfml::TimeDelta KeyframeModel::CalculateActiveTime(\n    fml::TimePoint monotonic_time) const {\n  fml::TimeDelta time_offset =\n      fml::TimeDelta::FromMilliseconds(animation_data_->delay * -1);\n  fml::TimeDelta local_time = ConvertMonotonicTimeToLocalTime(monotonic_time);\n\n  KeyframeModel::Phase phase = CalculatePhase(local_time);\n\n  switch (phase) {\n    case KeyframeModel::Phase::BEFORE:\n      if (animation_data_->fill_mode ==\n              starlight::AnimationFillModeType::kBackwards ||\n          animation_data_->fill_mode == starlight::AnimationFillModeType::kBoth)\n        return std::max(local_time + time_offset, fml::TimeDelta());\n      return fml::TimeDelta::Min();\n    case KeyframeModel::Phase::ACTIVE:\n      return local_time + time_offset;\n    case KeyframeModel::Phase::AFTER:\n      if (animation_data_->fill_mode ==\n              starlight::AnimationFillModeType::kForwards ||\n          animation_data_->fill_mode ==\n              starlight::AnimationFillModeType::kBoth) {\n        // playback_rate_ here won't be 0, is always 1.0.\n        fml::TimeDelta active_duration =\n            GetRepeatDuration() / std::abs(playback_rate_);\n        return std::max(std::min(local_time + time_offset, active_duration),\n                        fml::TimeDelta());\n      }\n      return fml::TimeDelta::Min();\n    default:\n      return fml::TimeDelta::Min();\n  }\n}\n\nfml::TimeDelta KeyframeModel::TrimTimeToCurrentIteration(\n    fml::TimePoint monotonic_time, int& current_iteration_count,\n    bool& need_report_over_time) const {\n  fml::TimeDelta active_time = CalculateActiveTime(monotonic_time);\n  if (need_report_over_time == true &&\n      active_time.ToSeconds() > kThirtyMinutesInSeconds) {\n    need_report_over_time = false;\n    if (tasm::LynxEnv::GetInstance().EnableAnimationInfoReport()) {\n      tasm::report::EventTracker::OnEvent(\n          [css_animation_name = animation_data_->name.str()](\n              tasm::report::MoveOnlyEvent& event) {\n            event.SetName(\"lynxsdk_animation_report_event\");\n            event.SetProps(\"report_event_type\",\n                           \"css_animation_time_out_30_minutes\");\n            event.SetProps(\"css_animation_name\", css_animation_name);\n            event.SetProps(\"total_duration_seconds\", kThirtyMinutesInSeconds);\n          });\n    }\n  }\n  fml::TimeDelta start_offset = fml::TimeDelta();\n\n  // Return start offset if we are before the start of the keyframe model\n  if (active_time < fml::TimeDelta()) return start_offset;\n  // Always return zero if we have no iterations.\n  if (!animation_data_->iteration_count) return fml::TimeDelta();\n\n  // Don't attempt to trim if we have no duration.\n  if (curve_->Duration() <= fml::TimeDelta()) return fml::TimeDelta();\n\n  fml::TimeDelta repeated_duration = GetRepeatDuration();\n  // playback_rate_ here won't be 0, is always 1.0.\n  fml::TimeDelta active_duration = repeated_duration / std::abs(playback_rate_);\n\n  // Calculate the scaled active time\n  fml::TimeDelta scaled_active_time;\n  if (playback_rate_ < 0) {\n    scaled_active_time =\n        ((active_time - active_duration) * playback_rate_) + start_offset;\n  } else {\n    scaled_active_time = (active_time * playback_rate_) + start_offset;\n  }\n\n  // Calculate the iteration time\n  fml::TimeDelta iteration_time;\n  if (scaled_active_time - start_offset == repeated_duration &&\n      fmod(static_cast<double>(animation_data_->iteration_count), 1) == 0)\n    iteration_time = curve_->Duration();\n  else\n    iteration_time = scaled_active_time % curve_->Duration();\n  //   LOGE(\"[animation]\n  //   scaled_active_time:\"<<scaled_active_time.ToSecondsF()<<\",duration:\"<<curve_->Duration().ToSecondsF());\n\n  // Calculate the current iteration\n  int iteration;\n  if (scaled_active_time <= fml::TimeDelta())\n    iteration = 0;\n  else if (iteration_time == curve_->Duration())\n    iteration = ceil(static_cast<double>(animation_data_->iteration_count) - 1);\n  else\n    iteration = static_cast<int>(scaled_active_time / curve_->Duration());\n\n  current_iteration_count = iteration;\n  // Check if we are running the keyframe model in reverse direction for the\n  // current iteration\n  bool reverse = (animation_data_->direction ==\n                  starlight::AnimationDirectionType::kReverse) ||\n                 (animation_data_->direction ==\n                      starlight::AnimationDirectionType::kAlternate &&\n                  iteration % 2 == 1) ||\n                 (animation_data_->direction ==\n                      starlight::AnimationDirectionType::kAlternateReverse &&\n                  iteration % 2 == 0);\n\n  // If we are running the keyframe model in reverse direction, reverse the\n  // result\n  if (reverse) iteration_time = curve_->Duration() - iteration_time;\n\n  return iteration_time;\n}\n\nbool KeyframeModel::InEffect(fml::TimePoint monotonic_time) const {\n  return CalculateActiveTime(monotonic_time) != fml::TimeDelta::Min();\n}\n\nvoid KeyframeModel::SetRunState(RunState run_state,\n                                fml::TimePoint monotonic_time) {\n  if ((run_state == STARTING || run_state == RUNNING ||\n       run_state == FINISHED) &&\n      run_state_ == PAUSED) {\n    total_paused_duration_ =\n        total_paused_duration_ + (monotonic_time - pause_time_);\n  } else if (run_state == PAUSED) {\n    pause_time_ = monotonic_time;\n  }\n  run_state_ = run_state;\n}\n\nvoid KeyframeModel::UpdateAnimationData(starlight::AnimationData* data) {\n  animation_data_ = data;\n  if (curve_) {\n    // bind timing_function\n    curve_->SetTimingFunction(TimingFunction::MakeTimingFunction(data));\n\n    // scaled_duration's unit is second.\n    curve_->set_scaled_duration(data->duration / 1000.0);\n  }\n}\n\nvoid KeyframeModel::EnsureFromAndToKeyframe() {\n  if (curve_) {\n    curve_->EnsureFromAndToKeyframe();\n  }\n}\n\nvoid KeyframeModel::NotifyElementSizeUpdated() {\n  if (curve_) {\n    curve_->NotifyElementSizeUpdated();\n  }\n}\n\nvoid KeyframeModel::NotifyUnitValuesUpdatedToAnimation(\n    tasm::CSSValuePattern type) {\n  if (curve_) {\n    curve_->NotifyUnitValuesUpdatedToAnimation(type);\n  }\n}\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/keyframe_model.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_KEYFRAME_MODEL_H_\n#define CORE_ANIMATION_KEYFRAME_MODEL_H_\n\n#include <cmath>\n#include <memory>\n#include <string>\n#include <tuple>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/style/animation_data.h\"\n\nnamespace lynx {\nnamespace animation {\n\nclass AnimationCurve;\n\nclass KeyframeModel {\n public:\n  enum RunState {\n    STARTING = 0,\n    RUNNING,\n    PAUSED,\n    FINISHED,\n  };\n\n  enum class Phase { BEFORE, ACTIVE, AFTER };\n\n  static std::unique_ptr<KeyframeModel> Create(\n      std::unique_ptr<AnimationCurve> curve);\n\n  const fml::TimePoint& start_time() const { return start_time_; }\n  const fml::TimePoint& pause_time() const { return pause_time_; }\n  const fml::TimeDelta& total_paused_duration() const {\n    return total_paused_duration_;\n  }\n\n  void set_start_time(fml::TimePoint& monotonic_time) {\n    start_time_ = monotonic_time;\n  }\n  bool has_set_start_time() const { return start_time_ != fml::TimePoint(); }\n\n  double playback_rate() { return playback_rate_; }\n  void set_playback_rate(double playback_rate) {\n    playback_rate_ = playback_rate;\n  }\n\n  fml::TimeDelta GetRepeatDuration() const;\n\n  KeyframeModel::Phase CalculatePhase(fml::TimeDelta local_time) const;\n\n  // LocalTime is relative time\n  fml::TimeDelta ConvertMonotonicTimeToLocalTime(\n      fml::TimePoint monotonic_time) const;\n\n  fml::TimeDelta CalculateActiveTime(fml::TimePoint monotonic_time) const;\n\n  fml::TimeDelta TrimTimeToCurrentIteration(fml::TimePoint monotonic_time,\n                                            int& current_iteration_count,\n                                            bool& need_report_over_time) const;\n\n  AnimationCurve* curve() { return curve_.get(); }\n  const AnimationCurve* curve() const { return curve_.get(); }\n\n  bool InEffect(fml::TimePoint monotonic_time) const;\n\n  void SetRunState(RunState run_state, fml::TimePoint monotonic_time);\n  RunState GetRunState() { return run_state_; }\n  bool is_finished() const { return run_state_ == FINISHED; }\n\n  void set_animation_data(starlight::AnimationData* data) {\n    animation_data_ = data;\n  }\n\n  starlight::AnimationData get_animation_data() { return *animation_data_; }\n\n  AnimationCurve* animation_curve() { return curve_.get(); }\n\n  void UpdateAnimationData(starlight::AnimationData* data);\n\n  void EnsureFromAndToKeyframe();\n\n  void NotifyElementSizeUpdated();\n\n  void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern);\n\n  std::tuple<bool, bool> UpdateState(const fml::TimePoint& monotonic_time);\n\n public:\n  KeyframeModel(std::unique_ptr<AnimationCurve> curve);\n\n private:\n  RunState run_state_;\n  starlight::AnimationData* animation_data_;\n  fml::TimePoint start_time_;\n  std::unique_ptr<AnimationCurve> curve_;\n  double playback_rate_;\n  fml::TimePoint pause_time_;\n  fml::TimeDelta total_paused_duration_{fml::TimeDelta()};\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_KEYFRAME_MODEL_H_\n"
  },
  {
    "path": "core/animation/keyframe_model_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/animation/keyframe_model.h\"\n\n#include <memory>\n\n#include \"core/animation/animation.h\"\n#include \"core/animation/css_keyframe_manager.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace tasm {\nnamespace test {\nstd::unique_ptr<KeyframeModel> InitTestModel() {\n  std::unique_ptr<KeyframedOpacityAnimationCurve> test_curve(\n      KeyframedOpacityAnimationCurve::Create());\n  auto test_frame1 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(0.0), nullptr);\n  test_frame1->SetOpacity(1.0f);\n  test_curve->AddKeyframe(std::move(test_frame1));\n  test_curve->type_ = AnimationCurve::CurveType::OPACITY;\n  auto test_frame2 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetOpacity(0.0f);\n  test_curve->AddKeyframe(std::move(test_frame2));\n  return std::make_unique<KeyframeModel>(std::move(test_curve));\n}\n\nTEST(KeyframeModelTest, GetRepeatDuration) {\n  std::unique_ptr<KeyframeModel> test_model = InitTestModel();\n  starlight::AnimationData default_data = starlight::AnimationData();\n  default_data.duration = 1000;\n  default_data.delay = 1000;\n  default_data.iteration_count = 0;\n  test_model->UpdateAnimationData(&default_data);\n  auto result_1 = test_model->GetRepeatDuration();\n  EXPECT_EQ(result_1, fml::TimeDelta::Zero());\n\n  default_data.duration = 1234;\n  default_data.delay = 1000;\n  default_data.iteration_count = 10;\n  test_model->UpdateAnimationData(&default_data);\n  auto result_3 = test_model->GetRepeatDuration();\n  EXPECT_EQ(result_3, fml::TimeDelta::FromMilliseconds(12340));\n}\n\nTEST(KeyframeModelTest, CalculatePhase) {\n  std::unique_ptr<KeyframeModel> test_model = InitTestModel();\n  starlight::AnimationData default_data = starlight::AnimationData();\n  default_data.duration = 1000;\n  default_data.delay = 1000;\n  default_data.iteration_count = 2;\n  test_model->UpdateAnimationData(&default_data);\n  fml::TimePoint start_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(0.0f));\n  test_model->set_start_time(start_time);\n  test_model->SetRunState(KeyframeModel::STARTING, start_time);\n\n  KeyframeModel::Phase phase =\n      test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(-1.0));\n  EXPECT_EQ(KeyframeModel::Phase::BEFORE, phase);\n\n  phase = test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(0.5));\n  EXPECT_EQ(KeyframeModel::Phase::BEFORE, phase);\n\n  phase = test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(1.5));\n  EXPECT_EQ(KeyframeModel::Phase::ACTIVE, phase);\n\n  phase = test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(2.5));\n  EXPECT_EQ(KeyframeModel::Phase::ACTIVE, phase);\n\n  phase = test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(3.1));\n  EXPECT_EQ(KeyframeModel::Phase::AFTER, phase);\n\n  // Negative iterations_ represents \"infinite iterations\".\n  default_data.iteration_count = -1;\n  test_model->UpdateAnimationData(&default_data);\n\n  phase = test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(3.1));\n  EXPECT_EQ(KeyframeModel::Phase::ACTIVE, phase);\n\n  phase = test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(999999999));\n  EXPECT_EQ(KeyframeModel::Phase::ACTIVE, phase);\n\n  // Negative duration test\n  default_data.duration = 1000;\n  default_data.delay = 0;\n  default_data.iteration_count = 2;\n  test_model->UpdateAnimationData(&default_data);\n\n  phase = test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(2.5));\n  EXPECT_EQ(KeyframeModel::Phase::AFTER, phase);\n\n  default_data.duration = 1000;\n  default_data.delay = 2000;\n  default_data.iteration_count = 1;\n  test_model->UpdateAnimationData(&default_data);\n\n  phase = test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(1.5));\n  EXPECT_EQ(KeyframeModel::Phase::BEFORE, phase);\n\n  phase = test_model->CalculatePhase(fml::TimeDelta::FromSecondsF(3.1));\n  EXPECT_EQ(KeyframeModel::Phase::AFTER, phase);\n}\n\nTEST(KeyframeModelTest, CalculateActiveTime) {\n  std::unique_ptr<KeyframeModel> test_model = InitTestModel();\n  starlight::AnimationData default_data = starlight::AnimationData();\n  default_data.duration = 1000;\n  default_data.delay = 1000;\n  default_data.iteration_count = 1;\n  default_data.fill_mode = starlight::AnimationFillModeType::kBoth;\n  test_model->UpdateAnimationData(&default_data);\n  fml::TimePoint start_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(0));\n  test_model->set_start_time(start_time);\n  test_model->SetRunState(KeyframeModel::STARTING, start_time);\n\n  fml::TimeDelta active_time = test_model->CalculateActiveTime(\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(500)));\n  EXPECT_EQ(active_time, fml::TimeDelta());\n  active_time = test_model->CalculateActiveTime(\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(1100)));\n  EXPECT_EQ(active_time, fml::TimeDelta::FromMilliseconds(100));\n  active_time = test_model->CalculateActiveTime(\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(1500)));\n  EXPECT_EQ(active_time, fml::TimeDelta::FromMilliseconds(500));\n  active_time = test_model->CalculateActiveTime(\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(2100)));\n  EXPECT_EQ(active_time, fml::TimeDelta::FromMilliseconds(1000));\n\n  default_data.fill_mode = starlight::AnimationFillModeType::kNone;\n  test_model->UpdateAnimationData(&default_data);\n  active_time = test_model->CalculateActiveTime(\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(500)));\n  EXPECT_EQ(active_time, fml::TimeDelta::Min());\n  active_time = test_model->CalculateActiveTime(\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(1100)));\n  EXPECT_EQ(active_time, fml::TimeDelta::FromMilliseconds(100));\n  active_time = test_model->CalculateActiveTime(\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(2100)));\n  EXPECT_EQ(active_time, fml::TimeDelta::Min());\n}\n\nTEST(KeyframeModelTest, TrimTimeToCurrentIteration) {\n  std::unique_ptr<KeyframeModel> test_model = InitTestModel();\n  starlight::AnimationData default_data = starlight::AnimationData();\n  default_data.duration = 1000;\n  default_data.delay = 1000;\n  default_data.iteration_count = 3;\n  default_data.fill_mode = starlight::AnimationFillModeType::kBoth;\n  test_model->UpdateAnimationData(&default_data);\n  fml::TimePoint start_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(0));\n  test_model->set_start_time(start_time);\n  test_model->SetRunState(KeyframeModel::STARTING, start_time);\n\n  int iteration_count = 0;\n  fml::TimePoint test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(-1000));\n  bool need_report_flag = false;\n  fml::TimeDelta trimmed_time = test_model->TrimTimeToCurrentIteration(\n      test_time, iteration_count, need_report_flag);\n  EXPECT_EQ(trimmed_time, fml::TimeDelta());\n  EXPECT_EQ(iteration_count, 0);\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(500));\n  trimmed_time = test_model->TrimTimeToCurrentIteration(\n      test_time, iteration_count, need_report_flag);\n  EXPECT_EQ(trimmed_time, fml::TimeDelta());\n  EXPECT_EQ(iteration_count, 0);\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(1500));\n  trimmed_time = test_model->TrimTimeToCurrentIteration(\n      test_time, iteration_count, need_report_flag);\n  EXPECT_EQ(trimmed_time, fml::TimeDelta::FromMilliseconds(500));\n  EXPECT_EQ(iteration_count, 0);\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(2100));\n  trimmed_time = test_model->TrimTimeToCurrentIteration(\n      test_time, iteration_count, need_report_flag);\n  EXPECT_EQ(trimmed_time, fml::TimeDelta::FromMilliseconds(100));\n  EXPECT_EQ(iteration_count, 1);\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(3100));\n  trimmed_time = test_model->TrimTimeToCurrentIteration(\n      test_time, iteration_count, need_report_flag);\n  EXPECT_EQ(trimmed_time, fml::TimeDelta::FromMilliseconds(100));\n  EXPECT_EQ(iteration_count, 2);\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(4100));\n  trimmed_time = test_model->TrimTimeToCurrentIteration(\n      test_time, iteration_count, need_report_flag);\n  EXPECT_EQ(trimmed_time, fml::TimeDelta::FromMilliseconds(1000));\n  EXPECT_EQ(iteration_count, 2);\n}\n\nTEST(KeyframeModelTest, InEffect) {\n  std::unique_ptr<KeyframeModel> test_model = InitTestModel();\n  starlight::AnimationData default_data = starlight::AnimationData();\n  default_data.duration = 1000;\n  default_data.delay = 1000;\n  default_data.iteration_count = 3;\n  default_data.fill_mode = starlight::AnimationFillModeType::kBoth;\n  test_model->UpdateAnimationData(&default_data);\n  fml::TimePoint start_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromMilliseconds(0));\n  test_model->set_start_time(start_time);\n  test_model->SetRunState(KeyframeModel::STARTING, start_time);\n\n  fml::TimePoint test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(-1.0f));\n  EXPECT_EQ(true, test_model->InEffect(test_time));\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(0.5f));\n  EXPECT_EQ(true, test_model->InEffect(test_time));\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.5f));\n  EXPECT_EQ(true, test_model->InEffect(test_time));\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.5f));\n  EXPECT_EQ(true, test_model->InEffect(test_time));\n\n  default_data.fill_mode = starlight::AnimationFillModeType::kNone;\n  test_model->UpdateAnimationData(&default_data);\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(-1.0f));\n  EXPECT_EQ(false, test_model->InEffect(test_time));\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(0.5f));\n  EXPECT_EQ(false, test_model->InEffect(test_time));\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.5f));\n  EXPECT_EQ(true, test_model->InEffect(test_time));\n\n  test_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.5f));\n  EXPECT_EQ(false, test_model->InEffect(test_time));\n}\n\nTEST(KeyframeModelTest, SetRunState) {\n  std::unique_ptr<KeyframeModel> test_model = InitTestModel();\n  std::unique_ptr<starlight::AnimationData> default_data =\n      std::make_unique<starlight::AnimationData>();\n  // default_data->delay=2.0;\n  test_model->set_animation_data(default_data.get());\n\n  fml::TimePoint base_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0f));\n\n  test_model->SetRunState(KeyframeModel::STARTING, base_time);\n  EXPECT_EQ(KeyframeModel::STARTING, test_model->GetRunState());\n\n  test_model->SetRunState(KeyframeModel::RUNNING, base_time);\n  EXPECT_EQ(KeyframeModel::RUNNING, test_model->GetRunState());\n\n  test_model->SetRunState(KeyframeModel::PAUSED, base_time);\n  EXPECT_EQ(KeyframeModel::PAUSED, test_model->GetRunState());\n  EXPECT_EQ(base_time, test_model->pause_time());\n\n  fml::TimePoint run_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.0f));\n  test_model->SetRunState(KeyframeModel::STARTING, run_time);\n  EXPECT_EQ(KeyframeModel::STARTING, test_model->GetRunState());\n  EXPECT_EQ(fml::TimeDelta::FromSecondsF(1.0f),\n            test_model->total_paused_duration());\n\n  fml::TimePoint base_time1 =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.0f));\n\n  test_model->SetRunState(KeyframeModel::PAUSED, base_time1);\n  EXPECT_EQ(KeyframeModel::PAUSED, test_model->GetRunState());\n  EXPECT_EQ(base_time1, test_model->pause_time());\n\n  fml::TimePoint run_time1 =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.0f));\n  test_model->SetRunState(KeyframeModel::RUNNING, run_time1);\n  EXPECT_EQ(fml::TimeDelta::FromSecondsF(2.0f),\n            test_model->total_paused_duration());\n\n  test_model->SetRunState(KeyframeModel::FINISHED, run_time1);\n  EXPECT_EQ(KeyframeModel::FINISHED, test_model->GetRunState());\n\n  fml::TimePoint base_time2 =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(5.0f));\n\n  test_model->SetRunState(KeyframeModel::PAUSED, base_time2);\n  EXPECT_EQ(KeyframeModel::PAUSED, test_model->GetRunState());\n  EXPECT_EQ(base_time2, test_model->pause_time());\n\n  fml::TimePoint run_time2 =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(6.0f));\n  test_model->SetRunState(KeyframeModel::FINISHED, run_time2);\n  EXPECT_EQ(fml::TimeDelta::FromSecondsF(3.0f),\n            test_model->total_paused_duration());\n}\n\nTEST(KeyframeModelTest, UpdateAnimationData) {\n  std::unique_ptr<KeyframeModel> test_model = InitTestModel();\n  std::unique_ptr<starlight::AnimationData> default_data =\n      std::make_unique<starlight::AnimationData>();\n  test_model->set_animation_data(default_data.get());\n  default_data->duration = 2000.0;\n  test_model->UpdateAnimationData(default_data.get());\n\n  EXPECT_EQ(2.0, test_model->animation_curve()->scaled_duration());\n}\n\nTEST(KeyframeModelTest, EnsureFromAndToKeyframe) {\n  std::unique_ptr<KeyframedOpacityAnimationCurve> test_curve(\n      KeyframedOpacityAnimationCurve::Create());\n  auto test_frame1 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(0.3), nullptr);\n  test_frame1->SetOpacity(1.0f);\n  test_curve->AddKeyframe(std::move(test_frame1));\n  test_curve->type_ = AnimationCurve::CurveType::OPACITY;\n  auto test_frame2 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(0.7), nullptr);\n  test_frame2->SetOpacity(0.0f);\n  test_curve->AddKeyframe(std::move(test_frame2));\n  auto test_model = std::make_unique<KeyframeModel>(std::move(test_curve));\n\n  std::unique_ptr<starlight::AnimationData> default_data =\n      std::make_unique<starlight::AnimationData>();\n  test_model->set_animation_data(default_data.get());\n\n  test_model->animation_curve()->EnsureFromAndToKeyframe();\n  EXPECT_EQ(4ul, test_model->animation_curve()->get_keyframes_size());\n}\n\nTEST(KeyframeModelTest, UpdateState) {\n  std::unique_ptr<KeyframeModel> test_model = InitTestModel();\n  starlight::AnimationData default_data = starlight::AnimationData();\n  default_data.duration = 1000;\n  default_data.delay = 1000;\n  default_data.iteration_count = 1;\n  test_model->UpdateAnimationData(&default_data);\n\n  fml::TimePoint start_time =\n      fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(0.0f));\n  test_model->set_start_time(start_time);\n  test_model->SetRunState(KeyframeModel::STARTING, start_time);\n\n  // 0.5s stage: STARTING -> STARTING\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(0.5f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::STARTING, test_model->GetRunState());\n    EXPECT_EQ(false, should_send_start_event);\n    EXPECT_EQ(false, should_send_end_event);\n  }\n\n  // paused on 0.5s stage: STARTING -> PAUSED\n  {\n    fml::TimePoint paused_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(0.5f));\n    test_model->SetRunState(KeyframeModel::PAUSED, paused_time);\n    EXPECT_EQ(KeyframeModel::PAUSED, test_model->GetRunState());\n    EXPECT_EQ(paused_time, test_model->pause_time());\n  }\n\n  // resume on 1.5s stage, paused time = 1.0s: PAUSED -> STARTING\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.5f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::STARTING, test_model->GetRunState());\n    EXPECT_EQ(false, should_send_start_event);\n    EXPECT_EQ(false, should_send_end_event);\n    EXPECT_EQ(fml::TimeDelta::FromSecondsF(1.0f),\n              test_model->total_paused_duration());\n  }\n\n  // 2.1s stage: STARTING -> RUNNING\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.1f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::RUNNING, test_model->GetRunState());\n    EXPECT_EQ(true, should_send_start_event);\n    EXPECT_EQ(false, should_send_end_event);\n  }\n\n  // 2.5s stage: RUNNING -> RUNNING\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.5f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::RUNNING, test_model->GetRunState());\n    EXPECT_EQ(false, should_send_start_event);\n    EXPECT_EQ(false, should_send_end_event);\n  }\n\n  // paused on 2.5s stage: RUNNING -> PAUSED\n  {\n    fml::TimePoint paused_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(2.5f));\n    test_model->SetRunState(KeyframeModel::PAUSED, paused_time);\n    EXPECT_EQ(KeyframeModel::PAUSED, test_model->GetRunState());\n    EXPECT_EQ(paused_time, test_model->pause_time());\n  }\n\n  // resume on 3.5s stage, paused time = 1.0s, total paused time = 2.0s: PAUSED\n  // -> RUNNING\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(3.5f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::RUNNING, test_model->GetRunState());\n    EXPECT_EQ(false, should_send_start_event);\n    EXPECT_EQ(false, should_send_end_event);\n    EXPECT_EQ(fml::TimeDelta::FromSecondsF(2.0f),\n              test_model->total_paused_duration());\n  }\n\n  // 4.1s stage: RUNNING -> FINISHED\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.1f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::FINISHED, test_model->GetRunState());\n    EXPECT_EQ(false, should_send_start_event);\n    EXPECT_EQ(true, should_send_end_event);\n  }\n\n  // 4.5s stage: FINISHED -> FINISHED\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.5f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::FINISHED, test_model->GetRunState());\n    EXPECT_EQ(false, should_send_start_event);\n    EXPECT_EQ(false, should_send_end_event);\n  }\n\n  // paused on 4.5s stage: FINISHED -> PAUSED\n  {\n    fml::TimePoint paused_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(4.5f));\n    test_model->SetRunState(KeyframeModel::PAUSED, paused_time);\n    EXPECT_EQ(KeyframeModel::PAUSED, test_model->GetRunState());\n    EXPECT_EQ(paused_time, test_model->pause_time());\n  }\n\n  // resume on 5.5s stage, paused time = 1.0s, total paused time = 3.0s: PAUSED\n  // -> FINISHED\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(5.5f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::FINISHED, test_model->GetRunState());\n    EXPECT_EQ(false, should_send_start_event);\n    EXPECT_EQ(false, should_send_end_event);\n    EXPECT_EQ(fml::TimeDelta::FromSecondsF(3.0f),\n              test_model->total_paused_duration());\n  }\n\n  // update delay to 5.0s, duration to 0.0s\n  starlight::AnimationData default_data1 = starlight::AnimationData();\n  default_data1.duration = 0;\n  default_data1.delay = 5000;\n  default_data1.iteration_count = 1;\n  test_model->UpdateAnimationData(&default_data1);\n\n  // 6.0s stage: FINISHED -> STARTING\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(6.0f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::STARTING, test_model->GetRunState());\n    EXPECT_EQ(false, should_send_start_event);\n    EXPECT_EQ(false, should_send_end_event);\n  }\n\n  // 8.1s stage: STARTING -> FINISHED\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(8.1f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::FINISHED, test_model->GetRunState());\n    EXPECT_EQ(true, should_send_start_event);\n    EXPECT_EQ(true, should_send_end_event);\n  }\n\n  // update delay to 0.0s, duration to 6.0s\n  starlight::AnimationData default_data2 = starlight::AnimationData();\n  default_data2.duration = 6000;\n  default_data2.delay = 0;\n  default_data2.iteration_count = 1;\n  test_model->UpdateAnimationData(&default_data2);\n\n  // 8.5s stage: FINISHED -> RUNNING\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(8.5f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::RUNNING, test_model->GetRunState());\n    EXPECT_EQ(true, should_send_start_event);\n    EXPECT_EQ(false, should_send_end_event);\n  }\n\n  // update delay to 7.0s, duration to 1.0s\n  starlight::AnimationData default_data3 = starlight::AnimationData();\n  default_data3.duration = 1000;\n  default_data3.delay = 7000;\n  default_data3.iteration_count = 1;\n  test_model->UpdateAnimationData(&default_data3);\n\n  // 9.0s stage: RUNNING -> STARTING\n  {\n    fml::TimePoint tick_time =\n        fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(9.0f));\n    bool should_send_start_event = false;\n    bool should_send_end_event = false;\n    std::tie(should_send_start_event, should_send_end_event) =\n        test_model->UpdateState(tick_time);\n    EXPECT_EQ(KeyframeModel::STARTING, test_model->GetRunState());\n    EXPECT_EQ(false, should_send_start_event);\n    EXPECT_EQ(true, should_send_end_event);\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/keyframed_animation_curve.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/keyframed_animation_curve.h\"\n\n#include <cmath>\n#include <limits>\n\n#include \"base/include/float_comparison.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/animation/animation_trace_event_def.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n\nnamespace lynx {\nnamespace animation {\n\n// keyframe\nfml::TimeDelta Keyframe::Time() const { return time_; }\n\nKeyframe::Keyframe(fml::TimeDelta time,\n                   std::unique_ptr<TimingFunction> timing_function)\n    : time_(time), timing_function_(std::move(timing_function)) {}\n\nfml::TimeDelta TransformedAnimationTime(\n    const std::vector<std::unique_ptr<Keyframe>>& keyframes,\n    const std::unique_ptr<TimingFunction>& timing_function,\n    double scaled_duration, fml::TimeDelta time) {\n  if (timing_function) {\n    fml::TimeDelta start_time = keyframes.front()->Time() * scaled_duration;\n    fml::TimeDelta duration =\n        (keyframes.back()->Time() - keyframes.front()->Time()) *\n        scaled_duration;\n    double progress = static_cast<double>(time.ToMicroseconds() -\n                                          start_time.ToMicroseconds()) /\n                      static_cast<double>(duration.ToMicroseconds());\n\n    time = (duration * timing_function->GetValue(progress)) + start_time;\n  }\n\n  return time;\n}\n\nsize_t GetActiveKeyframe(\n    const std::vector<std::unique_ptr<Keyframe>>& keyframes,\n    double scaled_duration, fml::TimeDelta time) {\n  DCHECK(keyframes.size() >= 2);\n  size_t i = 0;\n  for (; i < keyframes.size() - 2; ++i) {  // Last keyframe is never active.\n    if (time < (keyframes[i + 1]->Time() * scaled_duration)) break;\n  }\n\n  return i;\n}\n\ndouble TransformedKeyframeProgress(\n    const std::vector<std::unique_ptr<Keyframe>>& keyframes,\n    double scaled_duration, fml::TimeDelta time, size_t i) {\n  double in_time = time.ToNanosecondsF();\n  double time1 = keyframes[i]->Time().ToNanosecondsF() * scaled_duration;\n  double time2 = keyframes[i + 1]->Time().ToNanosecondsF() * scaled_duration;\n\n  // Corner case: If time1 is equal to time2, we should return 100% progress\n  // here directly. Otherwise, we will get a progress value of NaN, because the\n  // difference between time1 and time2 will be used as the divisor later.\n  // FIXME(wujintian): Here is a bad case that if duration is 0 and delay is not\n  // 0 and fill mode is backwards and phase is before now, it should return 0.0\n  // instead of return 1.0.\n  if (std::fabs(time2 - time1) < std::numeric_limits<double>::epsilon()) {\n    return 1.0;\n  }\n  double progress = (in_time - time1) / (time2 - time1);\n\n  if (keyframes[i]->timing_function()) {\n    progress = keyframes[i]->timing_function()->GetValue(progress);\n  }\n\n  return progress;\n}\n\ntasm::CSSValue GetStyleInElement(tasm::CSSPropertyID id,\n                                 tasm::Element* element) {\n  std::optional<tasm::CSSValue> value_opt = element->GetElementStyle(id);\n  if (!value_opt) {\n    return tasm::CSSValue();\n  }\n  return std::move(*value_opt);\n}\n\ntasm::CSSValue HandleCSSVariableValueIfNeed(tasm::CSSPropertyID id,\n                                            const tasm::CSSValue& value,\n                                            tasm::Element* element) {\n  const auto& keyframe_value = value;\n  bool is_variable = keyframe_value.IsVariable();\n  if (is_variable) {\n    tasm::StyleMap temp_var_map;\n    temp_var_map.insert_or_assign(id, value);\n    element->HandleCSSVariables(temp_var_map);\n    if (temp_var_map.empty()) {\n      return value;\n    }\n    return temp_var_map.front().second;\n  }\n  return keyframe_value;\n}\n\nconst std::unordered_set<AnimationCurve::CurveType>& GetOnXAxisCurveTypeSet() {\n  static const base::NoDestructor<std::unordered_set<AnimationCurve::CurveType>>\n      onXAxisCurveTypeSet({ALL_X_AXIS_CURVE_TYPE});\n  return *onXAxisCurveTypeSet;\n}\n\n//====== LayoutValueAnimator begin =======\n\nstd::unique_ptr<LayoutKeyframe> LayoutKeyframe::Create(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function) {\n  return std::make_unique<LayoutKeyframe>(time, std::move(timing_function));\n}\n\nLayoutKeyframe::LayoutKeyframe(fml::TimeDelta time,\n                               std::unique_ptr<TimingFunction> timing_function)\n    : Keyframe(time, std::move(timing_function)),\n      value_(starlight::NLength::MakeAutoNLength()) {}\n\n// When view or font size has changed, mark the value 'AutoNLength'.\nvoid LayoutKeyframe::NotifyUnitValuesUpdatedToAnimation(\n    tasm::CSSValuePattern type) {\n  if (css_value_.GetPattern() == type) {\n    value_ = starlight::NLength::MakeAutoNLength();\n  }\n}\n\nstd::pair<starlight::NLength, tasm::CSSValue>\nLayoutKeyframe::GetLayoutKeyframeValue(LayoutKeyframe* keyframe,\n                                       tasm::CSSPropertyID id,\n                                       tasm::Element* element) {\n  // Layout length default value : auto\n  starlight::NLength length = starlight::NLength::MakeAutoNLength();\n  tasm::CSSValue css_value = tasm::CSSValue(starlight::LengthValueType::kAuto);\n  if (keyframe->IsEmpty()) {\n    std::optional<tasm::CSSValue> value_opt = element->GetElementStyle(id);\n    if (!value_opt) {\n      // return default value\n      return std::make_pair(length, css_value);\n    }\n    const auto& configs = element->element_manager()->GetCSSParserConfigs();\n    auto parse_result = starlight::CSSStyleUtils::ToLength(\n        *value_opt, CSSKeyframeManager::GetLengthContext(element), configs);\n    length = parse_result.first;\n    css_value = std::move(*value_opt);\n  } else {\n    length = keyframe->Value();\n    css_value = keyframe->CSSValue();\n  }\n  return std::make_pair(length, css_value);\n}\n\nbool LayoutKeyframe::SetValue(tasm::CSSPropertyID id,\n                              const tasm::CSSValue& value,\n                              tasm::Element* element) {\n  auto keyframe_layout_value = HandleCSSVariableValueIfNeed(id, value, element);\n  auto parse_result = starlight::CSSStyleUtils::ToLength(\n      keyframe_layout_value, CSSKeyframeManager::GetLengthContext(element),\n      element->element_manager()->GetCSSParserConfigs());\n  if (!parse_result.second) {\n    return false;\n  }\n  if (!parse_result.first.IsUnit() && !parse_result.first.IsPercent() &&\n      !parse_result.first.IsCalc() && !parse_result.first.IsAuto()) {\n    return false;\n  }\n  value_ = parse_result.first;\n  css_value_ = value;\n  is_empty_ = false;\n  return true;\n}\n\nstd::unique_ptr<KeyframedLayoutAnimationCurve>\nKeyframedLayoutAnimationCurve::Create() {\n  return std::make_unique<KeyframedLayoutAnimationCurve>();\n}\n\ntasm::CSSValue KeyframedLayoutAnimationCurve::GetValue(\n    fml::TimeDelta& t) const {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_LAYOUT_ANIMATION_CURVE_GET_VALUE,\n              [](lynx::perfetto::EventContext ctx) {\n                auto* curveTypeInfo = ctx.event()->add_debug_annotations();\n                curveTypeInfo->set_name(\"curveType\");\n                curveTypeInfo->set_string_value(\"LayoutAnimation\");\n              });\n\n  t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(),\n                               t);\n  size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t);\n  double progress =\n      TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i);\n\n  LayoutKeyframe* keyframe =\n      reinterpret_cast<LayoutKeyframe*>(keyframes_[i].get());\n  LayoutKeyframe* keyframe_next =\n      reinterpret_cast<LayoutKeyframe*>(keyframes_[i + 1].get());\n\n  auto start_len = keyframe->Value();\n  auto end_len = keyframe_next->Value();\n  // When view or font size has changed, let start_len and end_len be\n  // 'AutoNLength', and then get The actual Nlength based on the updated size.\n  if (start_len.IsAuto() && !keyframe->CSSValue().IsEnum()) {\n    keyframe->SetValue(static_cast<tasm::CSSPropertyID>(Type()),\n                       keyframe->CSSValue(), element_);\n  }\n  if (end_len.IsAuto() && !keyframe_next->CSSValue().IsEnum()) {\n    keyframe_next->SetValue(static_cast<tasm::CSSPropertyID>(Type()),\n                            keyframe_next->CSSValue(), element_);\n  }\n\n  auto start_result = LayoutKeyframe::GetLayoutKeyframeValue(\n      keyframe, static_cast<tasm::CSSPropertyID>(Type()), element_);\n  start_len = start_result.first;\n  auto end_result = LayoutKeyframe::GetLayoutKeyframeValue(\n      keyframe_next, static_cast<tasm::CSSPropertyID>(Type()), element_);\n  end_len = end_result.first;\n\n  if (((!start_len.IsUnit() && !start_len.IsPercent() && !start_len.IsCalc()) ||\n       (!end_len.IsUnit() && !end_len.IsPercent() && !end_len.IsCalc())) ||\n      (std::fabs(progress - 1.0f) < std::numeric_limits<float>::epsilon())) {\n    return end_result.second;\n  }\n  if (std::fabs(progress - 0.0f) < std::numeric_limits<float>::epsilon()) {\n    return start_result.second;\n  }\n\n  float start_value = 0.0f;\n  float end_value = 0.0f;\n  tasm::CSSValuePattern pattern = tasm::CSSValuePattern::NUMBER;\n  if ((start_len.IsUnit() && end_len.IsPercent()) ||\n      (start_len.IsPercent() && end_len.IsUnit()) ||\n      (start_len.IsCalc() || end_len.IsCalc())) {\n    if (!element_ || !element_->parent()) {\n      return tasm::CSSValue(start_len.GetRawValue(),\n                            start_len.IsCalc() ? tasm::CSSValuePattern::CALC\n                            : start_len.IsUnit()\n                                ? tasm::CSSValuePattern::NUMBER\n                                : tasm::CSSValuePattern::PERCENT);\n    }\n    float parent_length = 0;\n    if (GetOnXAxisCurveTypeSet().count(Type()) != 0) {\n      parent_length = element_->parent()->width();\n    } else {\n      parent_length = element_->parent()->height();\n    }\n    start_value = starlight::NLengthToLayoutUnit(\n                      start_len, starlight::LayoutUnit(parent_length))\n                      .ToFloat();\n    end_value = starlight::NLengthToLayoutUnit(\n                    end_len, starlight::LayoutUnit(parent_length))\n                    .ToFloat();\n    pattern = tasm::CSSValuePattern::NUMBER;\n  } else {\n    start_value = start_len.GetRawValue();\n    end_value = end_len.GetRawValue();\n    pattern = start_len.IsUnit() ? tasm::CSSValuePattern::NUMBER\n                                 : tasm::CSSValuePattern::PERCENT;\n  }\n  float new_result = start_value + (end_value - start_value) * progress;\n  return tasm::CSSValue(new_result, pattern);\n}\n\n//====== LayoutValueAnimator end =======\n\n//====== OpacityValueAnimator begin =======\nstd::unique_ptr<OpacityKeyframe> OpacityKeyframe::Create(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function) {\n  return std::make_unique<OpacityKeyframe>(time, std::move(timing_function));\n}\n\nOpacityKeyframe::OpacityKeyframe(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function)\n    : Keyframe(time, std::move(timing_function)) {}\n\nfloat OpacityKeyframe::GetOpacityKeyframeValue(OpacityKeyframe* keyframe,\n                                               tasm::Element* element) {\n  float value = OpacityKeyframe::kDefaultOpacity;\n  if (keyframe->IsEmpty()) {\n    tasm::CSSValue opacity =\n        GetStyleInElement(tasm::kPropertyIDOpacity, element);\n    if (opacity.IsNumber()) {\n      value = static_cast<float>(opacity.AsNumber());\n    }\n  } else {\n    value = static_cast<float>(keyframe->Value());\n  }\n  return value;\n}\n\nbool OpacityKeyframe::SetValue(tasm::CSSPropertyID id,\n                               const tasm::CSSValue& value,\n                               tasm::Element* element) {\n  auto keyframe_opacity_value =\n      HandleCSSVariableValueIfNeed(id, value, element);\n  if (!keyframe_opacity_value.IsNumber()) {\n    return false;\n  }\n  value_ = keyframe_opacity_value.GetNumber();\n  is_empty_ = false;\n  return true;\n}\n\nstd::unique_ptr<KeyframedOpacityAnimationCurve>\nKeyframedOpacityAnimationCurve::Create() {\n  return std::make_unique<KeyframedOpacityAnimationCurve>();\n}\n\ntasm::CSSValue KeyframedOpacityAnimationCurve::GetValue(\n    fml::TimeDelta& t) const {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_OPACITY_ANIMATION_CURVE_GET_VALUE,\n              [](lynx::perfetto::EventContext ctx) {\n                auto* curveTypeInfo = ctx.event()->add_debug_annotations();\n                curveTypeInfo->set_name(\"curveType\");\n                curveTypeInfo->set_string_value(\"OpacityAnimation\");\n              });\n\n  t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(),\n                               t);\n  size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t);\n  double progress =\n      TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i);\n\n  OpacityKeyframe* keyframe =\n      reinterpret_cast<OpacityKeyframe*>(keyframes_[i].get());\n  OpacityKeyframe* keyframe_next =\n      reinterpret_cast<OpacityKeyframe*>(keyframes_[i + 1].get());\n\n  float start_opacity =\n      OpacityKeyframe::GetOpacityKeyframeValue(keyframe, element_);\n  float end_opacity =\n      OpacityKeyframe::GetOpacityKeyframeValue(keyframe_next, element_);\n  float result_value = start_opacity + (end_opacity - start_opacity) * progress;\n\n  if (start_opacity > end_opacity && result_value > 0.0f &&\n      base::FloatsEqual(result_value, 0.0f)) {\n    result_value = 0.0f;\n  } else if (start_opacity < end_opacity && result_value < 1.0f &&\n             base::FloatsEqual(result_value, 1.0f)) {\n    result_value = 1.0f;\n  }\n\n  return tasm::CSSValue(result_value, tasm::CSSValuePattern::NUMBER);\n}\n\n//====== OpacityValueAnimator end =======\n\n//====== ColorValueAnimator begin =======\nstd::unique_ptr<ColorKeyframe> ColorKeyframe::Create(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function) {\n  return std::make_unique<ColorKeyframe>(time, std::move(timing_function));\n}\n\nColorKeyframe::ColorKeyframe(fml::TimeDelta time,\n                             std::unique_ptr<TimingFunction> timing_function)\n    : Keyframe(time, std::move(timing_function)) {}\n\nuint32_t ColorKeyframe::GetColorKeyframeValue(ColorKeyframe* keyframe,\n                                              tasm::CSSPropertyID id,\n                                              tasm::Element* element) {\n  uint32_t value = (id == tasm::kPropertyIDColor)\n                       ? ColorKeyframe::kDefaultTextColor\n                       : ColorKeyframe::kDefaultBackgroundColor;\n  if (keyframe->IsEmpty()) {\n    tasm::CSSValue color = GetStyleInElement(id, element);\n    if (color.IsNumber()) {\n      value = static_cast<uint32_t>(color.AsNumber());\n    }\n  } else {\n    value = static_cast<uint32_t>(keyframe->Value());\n  }\n  return value;\n}\n\nbool ColorKeyframe::SetValue(tasm::CSSPropertyID id,\n                             const tasm::CSSValue& value,\n                             tasm::Element* element) {\n  auto keyframe_color_value = HandleCSSVariableValueIfNeed(id, value, element);\n  if (!keyframe_color_value.IsNumber()) {\n    return false;\n  }\n  value_ = keyframe_color_value.GetNumber();\n  is_empty_ = false;\n  return true;\n}\n\nstd::unique_ptr<KeyframedColorAnimationCurve>\nKeyframedColorAnimationCurve::Create(\n    starlight::XAnimationColorInterpolationType type) {\n  return std::make_unique<KeyframedColorAnimationCurve>(type);\n}\n\ntasm::CSSValue KeyframedColorAnimationCurve::GetValue(fml::TimeDelta& t) const {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_COLOR_ANIMATION_CURVE_GET_VALUE,\n              [](lynx::perfetto::EventContext ctx) {\n                auto* curveTypeInfo = ctx.event()->add_debug_annotations();\n                curveTypeInfo->set_name(\"curveType\");\n                curveTypeInfo->set_string_value(\"ColorAnimation\");\n              });\n  t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(),\n                               t);\n  size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t);\n  double progress =\n      TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i);\n\n  ColorKeyframe* keyframe =\n      reinterpret_cast<ColorKeyframe*>(keyframes_[i].get());\n  ColorKeyframe* keyframe_next =\n      reinterpret_cast<ColorKeyframe*>(keyframes_[i + 1].get());\n\n  uint32_t start_color = ColorKeyframe::GetColorKeyframeValue(\n      keyframe, static_cast<tasm::CSSPropertyID>(Type()), element_);\n  uint32_t end_color = ColorKeyframe::GetColorKeyframeValue(\n      keyframe_next, static_cast<tasm::CSSPropertyID>(Type()), element_);\n\n  double color_space_constant = 1.0;\n  if (interpolate_type_ == starlight::XAnimationColorInterpolationType::kAuto) {\n#if !OS_IOS\n    color_space_constant = 2.2;\n#endif\n  } else {\n    color_space_constant =\n        interpolate_type_ ==\n                starlight::XAnimationColorInterpolationType::kLinearRGB\n            ? 1.0\n            : 2.2;\n  }\n\n  float startA = ((start_color >> 24) & 0xff) / 255.0f;\n  float startR = ((start_color >> 16) & 0xff) / 255.0f;\n  float startG = ((start_color >> 8) & 0xff) / 255.0f;\n  float startB = ((start_color) & 0xff) / 255.0f;\n\n  float endA = ((end_color >> 24) & 0xff) / 255.0f;\n  float endR = ((end_color >> 16) & 0xff) / 255.0f;\n  float endG = ((end_color >> 8) & 0xff) / 255.0f;\n  float endB = ((end_color) & 0xff) / 255.0f;\n\n  // convert RGB to linear\n  startR = static_cast<float>(pow(startR, color_space_constant));\n  startG = static_cast<float>(pow(startG, color_space_constant));\n  startB = static_cast<float>(pow(startB, color_space_constant));\n\n  endR = static_cast<float>(pow(endR, color_space_constant));\n  endG = static_cast<float>(pow(endG, color_space_constant));\n  endB = static_cast<float>(pow(endB, color_space_constant));\n\n  // compute the interpolated color in linear space\n  float a = startA + progress * (endA - startA);\n  float b = startB + progress * (endB - startB);\n  float r = startR + progress * (endR - startR);\n  float g = startG + progress * (endG - startG);\n\n  // convert back to RGB to [0,255] range\n  a = a * 255.0f;\n  r = static_cast<float>(pow(r, 1.0 / color_space_constant)) * 255.0f;\n  g = static_cast<float>(pow(g, 1.0 / color_space_constant)) * 255.0f;\n  b = static_cast<float>(pow(b, 1.0 / color_space_constant)) * 255.0f;\n  uint32_t result_value = static_cast<uint32_t>(round(a)) << 24 |\n                          static_cast<uint32_t>(round(r)) << 16 |\n                          static_cast<uint32_t>(round(g)) << 8 |\n                          static_cast<uint32_t>(round(b));\n  return tasm::CSSValue(result_value, tasm::CSSValuePattern::NUMBER);\n}\n//====== ColorValueAnimator end =======\n\n//====== FloatValueAnimator begin =======\nstd::unique_ptr<FloatKeyframe> FloatKeyframe::Create(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function) {\n  return std::make_unique<FloatKeyframe>(time, std::move(timing_function));\n}\n\nFloatKeyframe::FloatKeyframe(fml::TimeDelta time,\n                             std::unique_ptr<TimingFunction> timing_function)\n    : Keyframe(time, std::move(timing_function)) {}\n\nfloat FloatKeyframe::GetFloatKeyframeValue(FloatKeyframe* keyframe,\n                                           tasm::CSSPropertyID id,\n                                           tasm::Element* element) {\n  float value = FloatKeyframe::kDefaultFloatValue;\n  if (keyframe->IsEmpty()) {\n    tasm::CSSValue float_value =\n        GetStyleInElement(tasm::kPropertyIDFlexGrow, element);\n    if (float_value.IsNumber()) {\n      value = static_cast<float>(float_value.AsNumber());\n    }\n  } else {\n    value = static_cast<float>(keyframe->Value());\n  }\n  return value;\n}\n\nbool FloatKeyframe::SetValue(tasm::CSSPropertyID id,\n                             const tasm::CSSValue& value,\n                             tasm::Element* element) {\n  auto keyframe_float_value = HandleCSSVariableValueIfNeed(id, value, element);\n  if (!keyframe_float_value.IsNumber()) {\n    return false;\n  }\n  value_ = keyframe_float_value.GetNumber();\n  is_empty_ = false;\n  return true;\n}\n\nstd::unique_ptr<KeyframedFloatAnimationCurve>\nKeyframedFloatAnimationCurve::Create() {\n  return std::make_unique<KeyframedFloatAnimationCurve>();\n}\n\ntasm::CSSValue KeyframedFloatAnimationCurve::GetValue(fml::TimeDelta& t) const {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_FONT_ANIMATION_CURVE_GET_VALUE,\n              [](lynx::perfetto::EventContext ctx) {\n                auto* curveTypeInfo = ctx.event()->add_debug_annotations();\n                curveTypeInfo->set_name(\"curveType\");\n                curveTypeInfo->set_string_value(\"FloatAnimation\");\n              });\n\n  t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(),\n                               t);\n  size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t);\n  double progress =\n      TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i);\n\n  FloatKeyframe* keyframe =\n      reinterpret_cast<FloatKeyframe*>(keyframes_[i].get());\n  FloatKeyframe* keyframe_next =\n      reinterpret_cast<FloatKeyframe*>(keyframes_[i + 1].get());\n\n  float start_float = FloatKeyframe::GetFloatKeyframeValue(\n      keyframe, tasm::kPropertyIDFlexGrow, element_);\n  float end_float = FloatKeyframe::GetFloatKeyframeValue(\n      keyframe_next, tasm::kPropertyIDFlexGrow, element_);\n  float result_value = start_float + (end_float - start_float) * progress;\n  return tasm::CSSValue(result_value, tasm::CSSValuePattern::NUMBER);\n}\n\n//====== FloatValueAnimator end =======\n\n//====== FilterValueAnimator begin =======\n\nstd::unique_ptr<FilterKeyframe> FilterKeyframe::Create(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function) {\n  return std::make_unique<FilterKeyframe>(time, std::move(timing_function));\n}\n\nFilterKeyframe::FilterKeyframe(fml::TimeDelta time,\n                               std::unique_ptr<TimingFunction> timing_function)\n    : Keyframe(time, std::move(timing_function)) {}\n\ntasm::CSSValue FilterKeyframe::GetFilterKeyframeValue(FilterKeyframe* keyframe,\n                                                      tasm::CSSPropertyID id,\n                                                      tasm::Element* element) {\n  tasm::CSSValue filter = tasm::CSSValue();\n  if (keyframe->IsEmpty()) {\n    filter = GetStyleInElement(id, element);\n  } else {\n    filter = keyframe->filter_;\n  }\n  return filter;\n}\n\nbool FilterKeyframe::SetValue(tasm::CSSPropertyID id,\n                              const tasm::CSSValue& value,\n                              tasm::Element* element) {\n  auto keyframe_filter_value = HandleCSSVariableValueIfNeed(id, value, element);\n  filter_ = keyframe_filter_value;\n  is_empty_ = false;\n  return true;\n}\n\nstd::unique_ptr<KeyframedFilterAnimationCurve>\nKeyframedFilterAnimationCurve::Create() {\n  return std::make_unique<KeyframedFilterAnimationCurve>();\n}\n\ntasm::CSSValue KeyframedFilterAnimationCurve::GetValue(\n    fml::TimeDelta& t) const {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_FILTER_ANIMATION_CURVE_GET_VALUE,\n              [](lynx::perfetto::EventContext ctx) {\n                auto* curveTypeInfo = ctx.event()->add_debug_annotations();\n                curveTypeInfo->set_name(\"curveType\");\n                curveTypeInfo->set_string_value(\"FilterAnimation\");\n              });\n  t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(),\n                               t);\n  size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t);\n  double progress =\n      TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i);\n  FilterKeyframe* keyframe =\n      reinterpret_cast<FilterKeyframe*>(keyframes_[i].get());\n  FilterKeyframe* keyframe_next =\n      reinterpret_cast<FilterKeyframe*>(keyframes_[i + 1].get());\n\n  tasm::CSSValue start_filter = FilterKeyframe::GetFilterKeyframeValue(\n      keyframe, tasm::kPropertyIDFilter, element_);\n  tasm::CSSValue end_filter = FilterKeyframe::GetFilterKeyframeValue(\n      keyframe_next, tasm::kPropertyIDFilter, element_);\n  if (start_filter == tasm::CSSValue() || end_filter == tasm::CSSValue()) {\n    return start_filter;\n  }\n  double start_filter_value = start_filter.GetArray()->get(1).Double();\n  uint32_t function_type_1 = start_filter.GetArray()->get(0).UInt32();\n  uint32_t pattern_1 = start_filter.GetArray()->get(2).UInt32();\n  double end_filter_value = end_filter.GetArray()->get(1).Double();\n  uint32_t function_type_2 = end_filter.GetArray()->get(0).UInt32();\n  uint32_t pattern_2 = end_filter.GetArray()->get(2).UInt32();\n  if (function_type_1 != function_type_2 || pattern_1 != pattern_2) {\n    return start_filter;\n  }\n  double result_filter_value =\n      start_filter_value + (end_filter_value - start_filter_value) * progress;\n  auto res_arr = lepus::CArray::Create();\n  res_arr->emplace_back(function_type_1);\n  res_arr->emplace_back(result_filter_value);\n  res_arr->emplace_back(pattern_1);\n  return tasm::CSSValue(std::move(res_arr));\n}\n\n//====== FilterValueAnimator end =======\n\n//====== BackgroundPositionAnimator begin =======\n\nBackgroundPositionKeyframe::BackgroundPositionKeyframe(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function)\n    : Keyframe(time, std::move(timing_function)) {}\n\ntasm::CSSValue BackgroundPositionKeyframe::GetBackgroundPositionKeyframeValue(\n    BackgroundPositionKeyframe* keyframe, tasm::CSSPropertyID id,\n    tasm::Element* element) {\n  if (keyframe && !keyframe->IsEmpty()) {\n    return keyframe->GetBackgroundPosition();\n  }\n  return tasm::CSSValue();\n}\n\nstd::unique_ptr<BackgroundPositionKeyframe> BackgroundPositionKeyframe::Create(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function) {\n  return std::make_unique<BackgroundPositionKeyframe>(\n      time, std::move(timing_function));\n}\n\nbool BackgroundPositionKeyframe::SetValue(tasm::CSSPropertyID id,\n                                          const tasm::CSSValue& value,\n                                          tasm::Element* element) {\n  auto keyframe_background_position_value =\n      HandleCSSVariableValueIfNeed(id, value, element);\n  if (!keyframe_background_position_value.IsArray()) {\n    return false;\n  }\n  background_position_ = keyframe_background_position_value;\n  is_empty_ = false;\n  return true;\n}\n\nstd::unique_ptr<KeyframedBackgroundPositionAnimationCurve>\nKeyframedBackgroundPositionAnimationCurve::Create() {\n  return std::make_unique<KeyframedBackgroundPositionAnimationCurve>();\n}\n\nstatic std::pair<uint32_t, double> ProcessPositionValue(uint32_t position_type,\n                                                        double position_value,\n                                                        bool is_x_axis) {\n  if (position_type ==\n      static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter)) {\n    return {static_cast<uint32_t>(tasm::CSSValuePattern::PERCENT), 50.0};\n  }\n\n  if (is_x_axis) {\n    if (position_type ==\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kLeft)) {\n      return {static_cast<uint32_t>(tasm::CSSValuePattern::PERCENT), 0.0};\n    }\n    if (position_type ==\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kRight)) {\n      return {static_cast<uint32_t>(tasm::CSSValuePattern::PERCENT), 100.0};\n    }\n  } else {\n    if (position_type ==\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kTop)) {\n      return {static_cast<uint32_t>(tasm::CSSValuePattern::PERCENT), 0.0};\n    }\n    if (position_type ==\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kBottom)) {\n      return {static_cast<uint32_t>(tasm::CSSValuePattern::PERCENT), 100.0};\n    }\n  }\n\n  return {position_type, position_value};\n}\n\ntasm::CSSValue KeyframedBackgroundPositionAnimationCurve::GetValue(\n    fml::TimeDelta& t) const {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY,\n              KEYFRAME_BACKGROUND_POSITION_ANIMATION_CURVE_GET_VALUE,\n              [](lynx::perfetto::EventContext ctx) {\n                auto* curveTypeInfo = ctx.event()->add_debug_annotations();\n                curveTypeInfo->set_name(\"curveType\");\n                curveTypeInfo->set_string_value(\"BackgroundPositionAnimation\");\n              });\n\n  t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(),\n                               t);\n  size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t);\n  double progress =\n      TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i);\n\n  BackgroundPositionKeyframe* keyframe =\n      static_cast<BackgroundPositionKeyframe*>(keyframes_[i].get());\n  BackgroundPositionKeyframe* keyframe_next =\n      static_cast<BackgroundPositionKeyframe*>(keyframes_[i + 1].get());\n\n  tasm::CSSValue start_background_position =\n      BackgroundPositionKeyframe::GetBackgroundPositionKeyframeValue(\n          keyframe, tasm::kPropertyIDBackgroundPosition, element_);\n  tasm::CSSValue end_background_position =\n      BackgroundPositionKeyframe::GetBackgroundPositionKeyframeValue(\n          keyframe_next, tasm::kPropertyIDBackgroundPosition, element_);\n\n  if (start_background_position == tasm::CSSValue() ||\n      end_background_position == tasm::CSSValue()) {\n    return start_background_position;\n  }\n\n  auto start_background_position_arr =\n      start_background_position.GetArray()->get(0).Array();\n  auto end_background_position_arr =\n      end_background_position.GetArray()->get(0).Array();\n\n  auto [start_x_type, start_x_value] = ProcessPositionValue(\n      static_cast<uint32_t>(start_background_position_arr->get(0).Number()),\n      start_background_position_arr->get(1).Double(), true);\n\n  auto [end_x_type, end_x_value] = ProcessPositionValue(\n      static_cast<uint32_t>(end_background_position_arr->get(0).Number()),\n      end_background_position_arr->get(1).Double(), true);\n\n  auto [start_y_type, start_y_value] = ProcessPositionValue(\n      static_cast<uint32_t>(start_background_position_arr->get(2).Number()),\n      start_background_position_arr->get(3).Double(), false);\n\n  auto [end_y_type, end_y_value] = ProcessPositionValue(\n      static_cast<uint32_t>(end_background_position_arr->get(2).Number()),\n      end_background_position_arr->get(3).Double(), false);\n\n  if (start_x_type != end_x_type || start_y_type != end_y_type) {\n    return start_background_position;\n  }\n\n  double result_x_value =\n      start_x_value + (end_x_value - start_x_value) * progress;\n  double result_y_value =\n      start_y_value + (end_y_value - start_y_value) * progress;\n\n  auto inner_array = lepus::CArray::Create();\n  inner_array->emplace_back(start_x_type);\n  inner_array->emplace_back(result_x_value);\n  inner_array->emplace_back(start_y_type);\n  inner_array->emplace_back(result_y_value);\n\n  auto inner_lepus_value = lepus::Value(std::move(inner_array));\n  auto outer_array = lepus::CArray::Create();\n  outer_array->emplace_back(std::move(inner_lepus_value));\n  return tasm::CSSValue(std::move(outer_array));\n}\n\n//====== BackgroundPositionAnimator end =======\n\n//====== TransformOriginAnimator start =======\nTransformOriginKeyframe::TransformOriginKeyframe(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function)\n    : Keyframe(time, std::move(timing_function)) {}\n\ntasm::CSSValue TransformOriginKeyframe::GetTransformOriginKeyframeValue(\n    TransformOriginKeyframe* keyframe, tasm::CSSPropertyID id,\n    tasm::Element* element) {\n  if (keyframe && !keyframe->IsEmpty()) {\n    return keyframe->GetTransformOrigin();\n  }\n  return tasm::CSSValue();\n}\n\nstd::unique_ptr<TransformOriginKeyframe> TransformOriginKeyframe::Create(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function) {\n  return std::make_unique<TransformOriginKeyframe>(time,\n                                                   std::move(timing_function));\n}\n\nbool TransformOriginKeyframe::SetValue(tasm::CSSPropertyID id,\n                                       const tasm::CSSValue& value,\n                                       tasm::Element* element) {\n  auto keyframe_transform_origin_value =\n      HandleCSSVariableValueIfNeed(id, value, element);\n  if (!keyframe_transform_origin_value.IsArray()) {\n    return false;\n  }\n  transform_origin_ = keyframe_transform_origin_value;\n  is_empty_ = false;\n  return true;\n}\n\nstd::unique_ptr<KeyframedTransformOriginAnimationCurve>\nKeyframedTransformOriginAnimationCurve::Create() {\n  return std::make_unique<KeyframedTransformOriginAnimationCurve>();\n}\n\ntasm::CSSValue KeyframedTransformOriginAnimationCurve::GetValue(\n    fml::TimeDelta& t) const {\n  t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(),\n                               t);\n  size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t);\n  double progress =\n      TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i);\n\n  TransformOriginKeyframe* keyframe =\n      static_cast<TransformOriginKeyframe*>(keyframes_[i].get());\n  TransformOriginKeyframe* keyframe_next =\n      static_cast<TransformOriginKeyframe*>(keyframes_[i + 1].get());\n\n  tasm::CSSValue start_background_position =\n      TransformOriginKeyframe::GetTransformOriginKeyframeValue(\n          keyframe, tasm::kPropertyIDTransformOrigin, element_);\n  tasm::CSSValue end_background_position =\n      TransformOriginKeyframe::GetTransformOriginKeyframeValue(\n          keyframe_next, tasm::kPropertyIDTransformOrigin, element_);\n\n  if (start_background_position == tasm::CSSValue() ||\n      end_background_position == tasm::CSSValue()) {\n    return start_background_position;\n  }\n\n  auto start_background_position_arr = start_background_position.GetArray();\n  auto end_background_position_arr = end_background_position.GetArray();\n\n  auto start_x_value =\n      static_cast<double>(start_background_position_arr->get(0).Number());\n  auto start_x_type = start_background_position_arr->get(1).Number();\n\n  auto end_x_value =\n      static_cast<double>(end_background_position_arr->get(0).Number());\n  auto end_x_type = end_background_position_arr->get(1).Number();\n\n  auto start_y_value =\n      static_cast<double>(start_background_position_arr->get(2).Number());\n  auto start_y_type = start_background_position_arr->get(3).Number();\n\n  auto end_y_value =\n      static_cast<double>(end_background_position_arr->get(2).Number());\n  auto end_y_type = end_background_position_arr->get(3).Number();\n\n  if (start_x_type != end_x_type || start_y_type != end_y_type) {\n    return start_background_position;\n  }\n\n  double result_x_value =\n      start_x_value + (end_x_value - start_x_value) * progress;\n  double result_y_value =\n      start_y_value + (end_y_value - start_y_value) * progress;\n\n  auto inner_array = lepus::CArray::Create();\n\n  inner_array->emplace_back(result_x_value);\n  inner_array->emplace_back(start_x_type);\n  inner_array->emplace_back(result_y_value);\n  inner_array->emplace_back(start_y_type);\n  return tasm::CSSValue(std::move(inner_array));\n}\n//====== TransformOriginAnimator end =======\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/keyframed_animation_curve.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_\n#define CORE_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_\n\n#include <memory>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"core/animation/animation_curve.h\"\n#include \"core/animation/utils/timing_function.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n\nnamespace lynx {\nnamespace animation {\n\nfml::TimeDelta TransformedAnimationTime(\n    const std::vector<std::unique_ptr<Keyframe>>& keyframes,\n    const std::unique_ptr<TimingFunction>& timing_function,\n    double scaled_duration, fml::TimeDelta time);\n\nsize_t GetActiveKeyframe(\n    const std::vector<std::unique_ptr<Keyframe>>& keyframes,\n    double scaled_duration, fml::TimeDelta time);\n\ndouble TransformedKeyframeProgress(\n    const std::vector<std::unique_ptr<Keyframe>>& keyframes,\n    double scaled_duration, fml::TimeDelta time, size_t i);\n\ntasm::CSSValue GetStyleInElement(tasm::CSSPropertyID id,\n                                 tasm::Element* element);\n\ntasm::CSSValue HandleCSSVariableValueIfNeed(tasm::CSSPropertyID id,\n                                            const tasm::CSSValue& value,\n                                            tasm::Element* element);\n\nconst std::unordered_set<AnimationCurve::CurveType>& GetOnXAxisCurveTypeSet();\n\n//====Layout keyframe ====\nclass LayoutKeyframe : public Keyframe {\n public:\n  static std::pair<starlight::NLength, tasm::CSSValue> GetLayoutKeyframeValue(\n      LayoutKeyframe* keyframe, tasm::CSSPropertyID id, tasm::Element* element);\n  static std::unique_ptr<LayoutKeyframe> Create(\n      fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function);\n  ~LayoutKeyframe() override = default;\n\n  void SetLayout(starlight::NLength length) {\n    value_ = length;\n    is_empty_ = false;\n  }\n\n  bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                tasm::Element* element) override;\n\n  void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern) override;\n\n  const starlight::NLength& Value() const { return value_; }\n\n  const tasm::CSSValue CSSValue() const { return css_value_; }\n\n  LayoutKeyframe(fml::TimeDelta time,\n                 std::unique_ptr<TimingFunction> timing_function);\n\n private:\n  starlight::NLength value_;\n  tasm::CSSValue css_value_;\n};\nclass KeyframedLayoutAnimationCurve : public LayoutAnimationCurve {\n public:\n  static std::unique_ptr<KeyframedLayoutAnimationCurve> Create();\n  ~KeyframedLayoutAnimationCurve() override = default;\n\n  tasm::CSSValue GetValue(fml::TimeDelta& t) const override;\n};\n\n//====Opacity keyframe ====\nclass OpacityKeyframe : public Keyframe {\n public:\n  constexpr static float kDefaultOpacity = 1.0f;\n  static float GetOpacityKeyframeValue(OpacityKeyframe* keyframe,\n                                       tasm::Element* element);\n\n  static std::unique_ptr<OpacityKeyframe> Create(\n      fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function);\n  ~OpacityKeyframe() override = default;\n\n  void SetOpacity(float opacity) {\n    value_ = opacity;\n    is_empty_ = false;\n  }\n\n  bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                tasm::Element* element) override;\n\n  float Value() const { return value_; }\n\n  OpacityKeyframe(fml::TimeDelta time,\n                  std::unique_ptr<TimingFunction> timing_function);\n\n private:\n  float value_{kDefaultOpacity};\n};\n\nclass KeyframedOpacityAnimationCurve : public OpacityAnimationCurve {\n public:\n  static std::unique_ptr<KeyframedOpacityAnimationCurve> Create();\n  ~KeyframedOpacityAnimationCurve() override = default;\n\n  tasm::CSSValue GetValue(fml::TimeDelta& t) const override;\n};\n\n//====Color keyframe ====\nclass ColorKeyframe : public Keyframe {\n public:\n  constexpr static uint32_t kDefaultBackgroundColor = 0x0;\n  constexpr static uint32_t kDefaultTextColor = 0xFF000000;\n  static uint32_t GetColorKeyframeValue(ColorKeyframe*, tasm::CSSPropertyID id,\n                                        tasm::Element*);\n  static std::unique_ptr<ColorKeyframe> Create(\n      fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function);\n  ~ColorKeyframe() override = default;\n\n  void SetColor(uint32_t color) {\n    value_ = color;\n    is_empty_ = false;\n  }\n\n  bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                tasm::Element* element) override;\n\n  uint32_t Value() const { return value_; }\n\n  ColorKeyframe(fml::TimeDelta time,\n                std::unique_ptr<TimingFunction> timing_function);\n\n private:\n  uint32_t value_{kDefaultBackgroundColor};\n};\nclass KeyframedColorAnimationCurve : public ColorAnimationCurve {\n public:\n  KeyframedColorAnimationCurve(\n      starlight::XAnimationColorInterpolationType type) {}\n  static std::unique_ptr<KeyframedColorAnimationCurve> Create(\n      starlight::XAnimationColorInterpolationType type);\n  ~KeyframedColorAnimationCurve() override = default;\n\n  tasm::CSSValue GetValue(fml::TimeDelta& t) const override;\n\n  starlight::XAnimationColorInterpolationType get_color_interpolate_type() {\n    return interpolate_type_;\n  }\n\n  void set_color_interpolate_type(\n      starlight::XAnimationColorInterpolationType type) {\n    interpolate_type_ = type;\n  }\n\n private:\n  starlight::XAnimationColorInterpolationType interpolate_type_ =\n      starlight::XAnimationColorInterpolationType::kAuto;\n};\n\n//====Float keyframe ====\nclass FloatKeyframe : public Keyframe {\n public:\n  constexpr static float kDefaultFloatValue = 0.0f;\n  static float GetFloatKeyframeValue(FloatKeyframe*, tasm::CSSPropertyID id,\n                                     tasm::Element*);\n  static std::unique_ptr<FloatKeyframe> Create(\n      fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function);\n  ~FloatKeyframe() override = default;\n\n  void SetFloat(float value) { value_ = value; }\n\n  bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                tasm::Element* element) override;\n\n  uint32_t Value() const { return value_; }\n\n  FloatKeyframe(fml::TimeDelta time,\n                std::unique_ptr<TimingFunction> timing_function);\n\n private:\n  float value_{kDefaultFloatValue};\n};\nclass KeyframedFloatAnimationCurve : public FloatAnimationCurve {\n public:\n  static std::unique_ptr<KeyframedFloatAnimationCurve> Create();\n  ~KeyframedFloatAnimationCurve() override = default;\n\n  tasm::CSSValue GetValue(fml::TimeDelta& t) const override;\n};\n\n//====Filter keyframe ====\nclass FilterKeyframe : public Keyframe {\n public:\n  static tasm::CSSValue GetFilterKeyframeValue(FilterKeyframe* keyframe,\n                                               tasm::CSSPropertyID id,\n                                               tasm::Element* element);\n\n  static std::unique_ptr<FilterKeyframe> Create(\n      fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function);\n  ~FilterKeyframe() override = default;\n\n  void SetFilter(const tasm::CSSValue& filter) { filter_ = filter; }\n\n  bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                tasm::Element* element) override;\n\n  FilterKeyframe(fml::TimeDelta time,\n                 std::unique_ptr<TimingFunction> timing_function);\n\n private:\n  tasm::CSSValue filter_;\n};\n\nclass KeyframedFilterAnimationCurve : public FilterAnimationCurve {\n public:\n  static std::unique_ptr<KeyframedFilterAnimationCurve> Create();\n  ~KeyframedFilterAnimationCurve() override = default;\n\n  tasm::CSSValue GetValue(fml::TimeDelta& t) const override;\n};\n\n//====BackgroundPosition keyframe ====\nclass BackgroundPositionKeyframe : public Keyframe {\n public:\n  static tasm::CSSValue GetBackgroundPositionKeyframeValue(\n      BackgroundPositionKeyframe* keyframe, tasm::CSSPropertyID id,\n      tasm::Element* element);\n\n  static std::unique_ptr<BackgroundPositionKeyframe> Create(\n      fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function);\n  ~BackgroundPositionKeyframe() override = default;\n\n  void SetBackgroundPosition(const tasm::CSSValue& background_position) {\n    background_position_ = background_position;\n  }\n\n  tasm::CSSValue GetBackgroundPosition() const { return background_position_; }\n\n  bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                tasm::Element* element) override;\n\n  BackgroundPositionKeyframe(fml::TimeDelta time,\n                             std::unique_ptr<TimingFunction> timing_function);\n\n private:\n  tasm::CSSValue background_position_;\n};\n\nclass KeyframedBackgroundPositionAnimationCurve\n    : public BackgroundPositionAnimationCurve {\n public:\n  static std::unique_ptr<KeyframedBackgroundPositionAnimationCurve> Create();\n  ~KeyframedBackgroundPositionAnimationCurve() override = default;\n\n  tasm::CSSValue GetValue(fml::TimeDelta& t) const override;\n};\n\n//====transformOrigin keyframe ====\nclass TransformOriginKeyframe : public Keyframe {\n public:\n  static tasm::CSSValue GetTransformOriginKeyframeValue(\n      TransformOriginKeyframe* keyframe, tasm::CSSPropertyID id,\n      tasm::Element* element);\n\n  static std::unique_ptr<TransformOriginKeyframe> Create(\n      fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function);\n  ~TransformOriginKeyframe() override = default;\n\n  tasm::CSSValue GetTransformOrigin() const { return transform_origin_; }\n\n  bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                tasm::Element* element) override;\n\n  TransformOriginKeyframe(fml::TimeDelta time,\n                          std::unique_ptr<TimingFunction> timing_function);\n\n private:\n  tasm::CSSValue transform_origin_;\n};\n\nclass KeyframedTransformOriginAnimationCurve\n    : public TransformOriginAnimationCurve {\n public:\n  static std::unique_ptr<KeyframedTransformOriginAnimationCurve> Create();\n  ~KeyframedTransformOriginAnimationCurve() override = default;\n\n  tasm::CSSValue GetValue(fml::TimeDelta& t) const override;\n};\n\n}  // namespace animation\n}  // namespace lynx\n#endif  // CORE_ANIMATION_KEYFRAMED_ANIMATION_CURVE_H_\n"
  },
  {
    "path": "core/animation/keyframed_animation_curve_unittest.cc",
    "content": "// Copyright 2017 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n#include \"core/animation/keyframed_animation_curve.h\"\n\n#include <memory>\n\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/dom/attribute_holder.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/fiber/view_element.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/animation_data.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\nnamespace lynx {\nnamespace animation {\nnamespace tasm {\nnamespace test {\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass KeyframedAnimationCurveTest : public ::testing::Test {\n public:\n  KeyframedAnimationCurveTest() {}\n  ~KeyframedAnimationCurveTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>\n      tasm_mediator;\n  fml::RefPtr<lynx::tasm::Element> element_;\n\n  void SetUp() override {\n    ::lynx::tasm::LynxEnvConfig lynx_env_config(\n        kWidth, kHeight, kDefaultLayoutsUnitPerPx,\n        kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<::lynx::tasm::MockPaintingContext>(),\n        tasm_mediator.get(), lynx_env_config);\n    auto config = std::make_shared<::lynx::tasm::PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n  }\n\n  fml::RefPtr<::lynx::tasm::FiberElement> InitFiberElement() {\n    manager->SetEnableNewAnimatorRadon(true);\n    auto test_element = manager->CreateFiberView();\n    test_element->SetAttribute(base::String(\"enable-new-animator\"),\n                               lepus::Value(\"true\"));\n    return test_element;\n  }\n};\n\n// Tests that a layout animation with two keyframes works as expected.\nTEST_F(KeyframedAnimationCurveTest, TwoLayoutKeyframe) {\n  std::unique_ptr<KeyframedLayoutAnimationCurve> curve(\n      KeyframedLayoutAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::LEFT;\n  auto test_element = InitFiberElement();\n  curve->SetElement(test_element.get());\n\n  auto test_frame1 = LayoutKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetLayout(starlight::NLength::MakeUnitNLength(2.f));\n  test_frame1->css_value_ =\n      ::lynx::tasm::CSSValue(2.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n  test_frame2->css_value_ =\n      ::lynx::tasm::CSSValue(4.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame2));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  EXPECT_FLOAT_EQ(2.f, curve->GetValue(value1).AsNumber());\n  EXPECT_FLOAT_EQ(3.f, curve->GetValue(value2).AsNumber());\n  EXPECT_FLOAT_EQ(4.f, curve->GetValue(value3).AsNumber());\n}\n\n// Tests that a layout animation with three keyframes works as expected.\nTEST_F(KeyframedAnimationCurveTest, ThreeLayoutKeyframe) {\n  std::unique_ptr<KeyframedLayoutAnimationCurve> curve(\n      KeyframedLayoutAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::LEFT;\n  auto test_element = InitFiberElement();\n  curve->SetElement(test_element.get());\n\n  auto test_frame1 = LayoutKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetLayout(starlight::NLength::MakeUnitNLength(2.f));\n  test_frame1->css_value_ =\n      ::lynx::tasm::CSSValue(2.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n  test_frame2->css_value_ =\n      ::lynx::tasm::CSSValue(4.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_frame3 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  test_frame3->SetLayout(starlight::NLength::MakeUnitNLength(8.f));\n  test_frame3->css_value_ =\n      ::lynx::tasm::CSSValue(8.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame3));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  fml::TimeDelta value4 = fml::TimeDelta::FromSecondsF(1.5f);\n  fml::TimeDelta value5 = fml::TimeDelta::FromSecondsF(2.f);\n  EXPECT_FLOAT_EQ(2.f, curve->GetValue(value1).AsNumber());\n  EXPECT_FLOAT_EQ(3.f, curve->GetValue(value2).AsNumber());\n  EXPECT_FLOAT_EQ(4.f, curve->GetValue(value3).AsNumber());\n  EXPECT_FLOAT_EQ(6.f, curve->GetValue(value4).AsNumber());\n  EXPECT_FLOAT_EQ(8.f, curve->GetValue(value5).AsNumber());\n}\n\n// Tests that a layout animation with multiple keys at a given time works\n// sanely.\nTEST_F(KeyframedAnimationCurveTest, RepeatedLayoutKeyTimes) {\n  std::unique_ptr<KeyframedLayoutAnimationCurve> curve(\n      KeyframedLayoutAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::LEFT;\n  auto test_element = InitFiberElement();\n  curve->SetElement(test_element.get());\n\n  auto test_frame1 = LayoutKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n  test_frame1->css_value_ =\n      ::lynx::tasm::CSSValue(4.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n  test_frame2->css_value_ =\n      ::lynx::tasm::CSSValue(4.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_frame3 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame3->SetLayout(starlight::NLength::MakeUnitNLength(6.f));\n  test_frame3->css_value_ =\n      ::lynx::tasm::CSSValue(6.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame3));\n\n  auto test_frame4 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  test_frame4->SetLayout(starlight::NLength::MakeUnitNLength(6.f));\n  test_frame4->css_value_ =\n      ::lynx::tasm::CSSValue(6.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame4));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n\n  EXPECT_FLOAT_EQ(4.f, curve->GetValue(value1).AsNumber());\n  EXPECT_FLOAT_EQ(4.f, curve->GetValue(value2).AsNumber());\n\n  // There is a discontinuity at 1. Any value between 4 and 6 is valid.\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  float mid_value = curve->GetValue(value3).AsNumber();\n  EXPECT_TRUE(mid_value >= 4 && mid_value <= 6);\n\n  fml::TimeDelta value4 = fml::TimeDelta::FromSecondsF(1.5f);\n  fml::TimeDelta value5 = fml::TimeDelta::FromSecondsF(2.f);\n  EXPECT_FLOAT_EQ(6.f, curve->GetValue(value4).AsNumber());\n  EXPECT_FLOAT_EQ(6.f, curve->GetValue(value5).AsNumber());\n}\n\n// Tests that a opacity animation with two keyframes works as expected.\nTEST_F(KeyframedAnimationCurveTest, TwoOpacityKeyframe) {\n  std::unique_ptr<KeyframedOpacityAnimationCurve> curve(\n      KeyframedOpacityAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::OPACITY;\n  auto test_frame1 = OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetOpacity(1.0f);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetOpacity(0.0f);\n  curve->AddKeyframe(std::move(test_frame2));\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  EXPECT_FLOAT_EQ(1.0f, curve->GetValue(value1).AsNumber());\n  EXPECT_FLOAT_EQ(0.5f, curve->GetValue(value2).AsNumber());\n  EXPECT_FLOAT_EQ(0.0f, curve->GetValue(value3).AsNumber());\n}\n\n// Tests that a opacity animation with three keyframes works as expected.\nTEST_F(KeyframedAnimationCurveTest, ThreeOpacityKeyframe) {\n  std::unique_ptr<KeyframedOpacityAnimationCurve> curve(\n      KeyframedOpacityAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::OPACITY;\n  auto test_frame1 = OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetOpacity(1.0f);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetOpacity(0.4f);\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_frame3 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  test_frame3->SetOpacity(0.0f);\n  curve->AddKeyframe(std::move(test_frame3));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  fml::TimeDelta value4 = fml::TimeDelta::FromSecondsF(1.5f);\n  fml::TimeDelta value5 = fml::TimeDelta::FromSecondsF(2.f);\n  EXPECT_FLOAT_EQ(1.0f, curve->GetValue(value1).AsNumber());\n  EXPECT_FLOAT_EQ(0.7f, curve->GetValue(value2).AsNumber());\n  EXPECT_FLOAT_EQ(0.4f, curve->GetValue(value3).AsNumber());\n  EXPECT_FLOAT_EQ(0.2f, curve->GetValue(value4).AsNumber());\n  EXPECT_FLOAT_EQ(0.0f, curve->GetValue(value5).AsNumber());\n}\n\n// Tests that a layout animation with multiple keys at a given time works\n// sanely.\nTEST_F(KeyframedAnimationCurveTest, RepeatedOpacityKeyTimes) {\n  std::unique_ptr<KeyframedOpacityAnimationCurve> curve(\n      KeyframedOpacityAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::OPACITY;\n  auto test_frame1 = OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetOpacity(0.0f);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetOpacity(0.0f);\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_frame3 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame3->SetOpacity(1.0f);\n  curve->AddKeyframe(std::move(test_frame3));\n\n  auto test_frame4 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  test_frame4->SetOpacity(1.0f);\n  curve->AddKeyframe(std::move(test_frame4));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n\n  EXPECT_FLOAT_EQ(0.0f, curve->GetValue(value1).AsNumber());\n  EXPECT_FLOAT_EQ(0.0f, curve->GetValue(value2).AsNumber());\n\n  // There is a discontinuity at 1. Any value between 4 and 6 is valid.\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  float mid_value = curve->GetValue(value3).AsNumber();\n  EXPECT_TRUE(mid_value >= 0.0f && mid_value <= 1.0f);\n\n  fml::TimeDelta value4 = fml::TimeDelta::FromSecondsF(1.5f);\n  fml::TimeDelta value5 = fml::TimeDelta::FromSecondsF(2.f);\n  EXPECT_FLOAT_EQ(1.0f, curve->GetValue(value4).AsNumber());\n  EXPECT_FLOAT_EQ(1.0f, curve->GetValue(value5).AsNumber());\n}\n\n// Tests that a color animation with two keyframes works as expected.\nTEST_F(KeyframedAnimationCurveTest, TwoColorKeyFrame) {\n  std::unique_ptr<KeyframedColorAnimationCurve> curve(\n      KeyframedColorAnimationCurve::Create(\n          starlight::XAnimationColorInterpolationType::kSRGB));\n  curve->type_ = AnimationCurve::CurveType::BGCOLOR;\n  auto test_frame1 = ColorKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetColor(4294901760);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      ColorKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetColor(4278255360);\n  curve->AddKeyframe(std::move(test_frame2));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  EXPECT_EQ(::lynx::tasm::CSSValue(4294901760u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value1));\n  EXPECT_EQ(::lynx::tasm::CSSValue(4290427392u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value2));\n  EXPECT_EQ(::lynx::tasm::CSSValue(4278255360u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value3));\n}\n\n// Tests that a color animation with three keyframes works as expected.\nTEST_F(KeyframedAnimationCurveTest, ThreeColorKeyFrame) {\n  std::unique_ptr<KeyframedColorAnimationCurve> curve(\n      KeyframedColorAnimationCurve::Create(\n          starlight::XAnimationColorInterpolationType::kSRGB));\n  curve->type_ = AnimationCurve::CurveType::BGCOLOR;\n  auto test_frame1 = ColorKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetColor(4294901760);  // ARGB(255, 255, 0, 0)\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      ColorKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetColor(4278255360);  // ARGB(255, 0, 255, 0)\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_frame3 =\n      ColorKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  test_frame3->SetColor(4278190335);  // ARGB(255, 0, 0, 255)\n  curve->AddKeyframe(std::move(test_frame3));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  fml::TimeDelta value4 = fml::TimeDelta::FromSecondsF(1.5f);\n  fml::TimeDelta value5 = fml::TimeDelta::FromSecondsF(2.f);\n  EXPECT_EQ(::lynx::tasm::CSSValue(4294901760u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value1));\n  EXPECT_EQ(::lynx::tasm::CSSValue(4290427392u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value2));\n  EXPECT_EQ(::lynx::tasm::CSSValue(4278255360u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value3));\n  EXPECT_EQ(::lynx::tasm::CSSValue(4278237882u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value4));\n  EXPECT_EQ(::lynx::tasm::CSSValue(4278190335u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value5));\n}\n\n// Tests that a color animation with multiple keys at a given time works\n// sanely.\nTEST_F(KeyframedAnimationCurveTest, RepeatedColorKeyFrame) {\n  std::unique_ptr<KeyframedColorAnimationCurve> curve(\n      KeyframedColorAnimationCurve::Create(\n          starlight::XAnimationColorInterpolationType::kSRGB));\n  curve->type_ = AnimationCurve::CurveType::BGCOLOR;\n  auto test_frame1 = ColorKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetColor(4282384384);  // ARGB(255, 64, 0, 0)\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      ColorKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetColor(4282384384);  // ARGB(255, 64, 0, 0)\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_frame3 =\n      ColorKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame3->SetColor(4290772992);  // ARGB(255, 192, 0, 0)\n  curve->AddKeyframe(std::move(test_frame3));\n\n  auto test_frame4 =\n      ColorKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  test_frame4->SetColor(4290772992);  // ARGB(255, 192, 0, 0)\n  curve->AddKeyframe(std::move(test_frame4));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n\n  EXPECT_EQ(::lynx::tasm::CSSValue(4282384384u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value1));\n  EXPECT_EQ(::lynx::tasm::CSSValue(4282384384u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value2));\n\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.0f);\n\n  auto mid_value = curve->GetValue(value3);\n  EXPECT_EQ(::lynx::tasm::CSSValue(4290772992u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            mid_value);\n  auto alpha_value = uint32_t(mid_value.AsNumber()) >> 24;\n  EXPECT_EQ(255, alpha_value);\n  auto red_value = uint32_t(mid_value.AsNumber()) << 8 >> 24;\n  EXPECT_LE(64, red_value);\n  EXPECT_GE(192, red_value);\n\n  fml::TimeDelta value4 = fml::TimeDelta::FromSecondsF(1.5f);\n  fml::TimeDelta value5 = fml::TimeDelta::FromSecondsF(2.f);\n  EXPECT_EQ(::lynx::tasm::CSSValue(4290772992u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value4));\n  EXPECT_EQ(::lynx::tasm::CSSValue(4290772992u,\n                                   ::lynx::tasm::CSSValuePattern::NUMBER),\n            curve->GetValue(value5));\n}\n\n// Tests that the keyframes may be added out of order.\nTEST_F(KeyframedAnimationCurveTest, UnsortedKeyframes) {\n  std::unique_ptr<KeyframedLayoutAnimationCurve> curve(\n      KeyframedLayoutAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::LEFT;\n  auto test_element = InitFiberElement();\n  curve->SetElement(test_element.get());\n\n  auto test_frame1 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(2.f), nullptr);\n  test_frame1->SetLayout(starlight::NLength::MakeUnitNLength(8.f));\n  test_frame1->css_value_ =\n      ::lynx::tasm::CSSValue(8.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 = LayoutKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame2->SetLayout(starlight::NLength::MakeUnitNLength(2.f));\n  test_frame2->css_value_ =\n      ::lynx::tasm::CSSValue(2.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_frame3 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(1.f), nullptr);\n  test_frame3->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n  test_frame3->css_value_ =\n      ::lynx::tasm::CSSValue(4.f, ::lynx::tasm::CSSValuePattern::NUMBER);\n  curve->AddKeyframe(std::move(test_frame3));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  fml::TimeDelta value4 = fml::TimeDelta::FromSecondsF(1.5f);\n  fml::TimeDelta value5 = fml::TimeDelta::FromSecondsF(2.f);\n\n  EXPECT_FLOAT_EQ(2.f, curve->GetValue(value1).AsNumber());\n  EXPECT_FLOAT_EQ(3.f, curve->GetValue(value2).AsNumber());\n  EXPECT_FLOAT_EQ(4.f, curve->GetValue(value3).AsNumber());\n  EXPECT_FLOAT_EQ(6.f, curve->GetValue(value4).AsNumber());\n  EXPECT_FLOAT_EQ(8.f, curve->GetValue(value5).AsNumber());\n}\n\nTEST_F(KeyframedAnimationCurveTest, TransformedKeyframeProgress) {\n  {\n    std::vector<std::unique_ptr<Keyframe>> keyframes;\n    auto test_frame1 = OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n    test_frame1->SetOpacity(1.0f);\n    keyframes.emplace_back(std::move(test_frame1));\n\n    auto test_frame2 =\n        OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n    test_frame2->SetOpacity(0.0f);\n    keyframes.emplace_back(std::move(test_frame2));\n    EXPECT_TRUE(TransformedKeyframeProgress(\n                    keyframes, 0, fml::TimeDelta::FromSecondsF(1), 0) == 1.0);\n\n    auto test_frame3 =\n        OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n    test_frame3->SetOpacity(0.0f);\n    keyframes.emplace_back(std::move(test_frame3));\n    EXPECT_TRUE(TransformedKeyframeProgress(\n                    keyframes, 1, fml::TimeDelta::FromSecondsF(1), 1) == 1.0);\n  }\n\n  {\n    std::vector<std::unique_ptr<Keyframe>> keyframes;\n    auto test_frame1 = OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n    test_frame1->SetOpacity(1.0f);\n    keyframes.emplace_back(std::move(test_frame1));\n\n    auto test_frame2 =\n        OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n    test_frame2->SetOpacity(0.0f);\n    keyframes.emplace_back(std::move(test_frame2));\n    EXPECT_TRUE(TransformedKeyframeProgress(keyframes, 2,\n                                            fml::TimeDelta::FromSecondsF(0.5),\n                                            0) == 0.25);\n\n    auto test_frame3 =\n        OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n    test_frame3->SetOpacity(0.0f);\n    keyframes.emplace_back(std::move(test_frame3));\n    EXPECT_TRUE(TransformedKeyframeProgress(\n                    keyframes, 2, fml::TimeDelta::FromSecondsF(2), 1) == 1.0);\n  }\n}\n\nTEST_F(KeyframedAnimationCurveTest, MakeEmptyKeyframe) {\n  auto test_curve1 = KeyframedLayoutAnimationCurve::Create();\n  auto test_frame1 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  auto test_frame2 =\n      test_curve1->KeyframedLayoutAnimationCurve::MakeEmptyKeyframe(\n          fml::TimeDelta::FromSecondsF(2.0));\n  EXPECT_EQ(test_frame1->Time(), test_frame2->Time());\n  EXPECT_EQ(test_frame1->timing_function(), test_frame2->timing_function());\n\n  auto test_curve2 = KeyframedOpacityAnimationCurve::Create();\n  auto test_frame3 =\n      OpacityKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  auto test_frame4 =\n      test_curve2->KeyframedOpacityAnimationCurve::MakeEmptyKeyframe(\n          fml::TimeDelta::FromSecondsF(2.0));\n  EXPECT_EQ(test_frame3->Time(), test_frame4->Time());\n  EXPECT_EQ(test_frame3->timing_function(), test_frame4->timing_function());\n\n  auto test_curve3 = KeyframedColorAnimationCurve::Create(\n      starlight::XAnimationColorInterpolationType::kSRGB);\n  auto test_frame5 =\n      ColorKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  auto test_frame6 =\n      test_curve3->KeyframedColorAnimationCurve::MakeEmptyKeyframe(\n          fml::TimeDelta::FromSecondsF(2.0));\n  EXPECT_EQ(test_frame5->Time(), test_frame6->Time());\n  EXPECT_EQ(test_frame5->timing_function(), test_frame6->timing_function());\n}\n\nTEST_F(KeyframedAnimationCurveTest, HandleCSSVariableValueIfNeed) {\n  ::lynx::tasm::RadonNode node(nullptr, \"my_tag\", 0);\n  manager->SetEnableNewAnimatorRadon(true);\n  auto test_element = manager->CreateFiberElement(\"view\");\n  test_element->SetAttributeHolder(node.attribute_holder());\n  auto test_value =\n      ::lynx::tasm::CSSValue(0.8, ::lynx::tasm::CSSValuePattern::NUMBER);\n  auto result_value_1 = HandleCSSVariableValueIfNeed(\n      ::lynx::tasm::kPropertyIDOpacity, test_value, test_element.get());\n\n  EXPECT_EQ(result_value_1, test_value);\n\n  node.UpdateCSSVariable(\"--height\", \"20px\");\n\n  auto test_value_2 =\n      ::lynx::tasm::CSSValue(\"calc({{--height}} + {{--height}})\",\n                             ::lynx::tasm::CSSValuePattern::STRING,\n                             ::lynx::tasm::CSSValueType::VARIABLE);\n\n  auto var_value = ::lynx::tasm::CSSValue(\"calc(20px + 20px)\",\n                                          ::lynx::tasm::CSSValuePattern::CALC,\n                                          ::lynx::tasm::CSSValueType::VARIABLE);\n  auto result_value_2 = HandleCSSVariableValueIfNeed(\n      ::lynx::tasm::kPropertyIDLeft, test_value_2, test_element.get());\n  EXPECT_EQ(result_value_2, var_value);\n}\n\nTEST_F(KeyframedAnimationCurveTest, FilterInterPolateTest) {\n  std::unique_ptr<KeyframedFilterAnimationCurve> curve(\n      KeyframedFilterAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::FILTER;\n  auto test_element = InitFiberElement();\n  curve->SetElement(test_element.get());\n\n  auto start_arr = lepus::CArray::Create();\n  start_arr->emplace_back(static_cast<uint32_t>(starlight::FilterType::kBlur));\n  start_arr->emplace_back(20.f);\n  start_arr->emplace_back(\n      static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PX));\n  auto start_value = ::lynx::tasm::CSSValue(std::move(start_arr));\n\n  auto end_arr = lepus::CArray::Create();\n  end_arr->emplace_back(static_cast<uint32_t>(starlight::FilterType::kBlur));\n  end_arr->emplace_back(60.f);\n  end_arr->emplace_back(static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PX));\n  auto end_value = ::lynx::tasm::CSSValue(std::move(end_arr));\n\n  auto test_frame1 = FilterKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetFilter(start_value);\n  test_frame1->is_empty_ = false;\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      FilterKeyframe::Create(fml::TimeDelta::FromSecondsF(2.f), nullptr);\n  test_frame2->SetFilter(end_value);\n  test_frame2->is_empty_ = false;\n  curve->AddKeyframe(std::move(test_frame2));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(1.f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(2.f);\n\n  auto result_1 = curve->GetValue(value1).GetArray();\n  auto result_2 = curve->GetValue(value2).GetArray();\n  auto result_3 = curve->GetValue(value3).GetArray();\n\n  EXPECT_FLOAT_EQ(20.f, result_1.get()->get(1).Double());\n  EXPECT_FLOAT_EQ(40.f, result_2.get()->get(1).Double());\n  EXPECT_FLOAT_EQ(60.f, result_3.get()->get(1).Double());\n}\n\nTEST_F(KeyframedAnimationCurveTest, TransformOriginInterPolateTest) {\n  std::unique_ptr<KeyframedTransformOriginAnimationCurve> curve(\n      KeyframedTransformOriginAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::TRANSFORM_ORIGIN;\n  auto test_element = InitFiberElement();\n  curve->SetElement(test_element.get());\n\n  auto start_arr = lepus::CArray::Create();\n  start_arr->emplace_back(50);\n  start_arr->emplace_back(\n      static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT));\n  start_arr->emplace_back(50);\n  start_arr->emplace_back(\n      static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT));\n\n  auto end_arr = lepus::CArray::Create();\n  end_arr->emplace_back(0);\n  end_arr->emplace_back(\n      static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT));\n  end_arr->emplace_back(0);\n  end_arr->emplace_back(\n      static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT));\n\n  auto test_frame1 = TransformOriginKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->transform_origin_ = lynx::tasm::CSSValue(start_arr);\n  test_frame1->is_empty_ = false;\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 = TransformOriginKeyframe::Create(\n      fml::TimeDelta::FromSecondsF(2.f), nullptr);\n  test_frame2->transform_origin_ = lynx::tasm::CSSValue(end_arr);\n  test_frame2->is_empty_ = false;\n  curve->AddKeyframe(std::move(test_frame2));\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(1.f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(2.f);\n\n  auto result_1 = curve->GetValue(value1).GetArray();\n  auto result_2 = curve->GetValue(value2).GetArray();\n  auto result_3 = curve->GetValue(value3).GetArray();\n\n  EXPECT_FLOAT_EQ(50.f, result_1.get()->get(0).Number());\n  EXPECT_FLOAT_EQ(static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT),\n                  result_1.get()->get(1).Number());\n  EXPECT_FLOAT_EQ(50.f, result_1.get()->get(2).Number());\n  EXPECT_FLOAT_EQ(static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT),\n                  result_1.get()->get(3).Number());\n\n  EXPECT_FLOAT_EQ(25.f, result_2.get()->get(0).Number());\n  EXPECT_FLOAT_EQ(static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT),\n                  result_2.get()->get(1).Number());\n  EXPECT_FLOAT_EQ(25.f, result_2.get()->get(2).Number());\n  EXPECT_FLOAT_EQ(static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT),\n                  result_2.get()->get(3).Number());\n\n  EXPECT_FLOAT_EQ(0.f, result_3.get()->get(0).Number());\n  EXPECT_FLOAT_EQ(static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT),\n                  result_3.get()->get(1).Number());\n  EXPECT_FLOAT_EQ(0.f, result_3.get()->get(2).Number());\n  EXPECT_FLOAT_EQ(static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT),\n                  result_3.get()->get(3).Number());\n}\n\nTEST_F(KeyframedAnimationCurveTest, GetOnXAxisCurveTypeSet) {\n  auto test_set = animation::GetOnXAxisCurveTypeSet();\n  static const base::NoDestructor<std::unordered_set<AnimationCurve::CurveType>>\n      base_set({AnimationCurve::CurveType::LEFT,\n                AnimationCurve::CurveType::RIGHT,\n                AnimationCurve::CurveType::WIDTH,\n                AnimationCurve::CurveType::MAX_WIDTH,\n                AnimationCurve::CurveType::MIN_WIDTH,\n                AnimationCurve::CurveType::MARGIN_LEFT,\n                AnimationCurve::CurveType::MARGIN_RIGHT,\n                AnimationCurve::CurveType::PADDING_LEFT,\n                AnimationCurve::CurveType::PADDING_RIGHT,\n                AnimationCurve::CurveType::BORDER_LEFT_WIDTH,\n                AnimationCurve::CurveType::BORDER_RIGHT_WIDTH});\n  EXPECT_EQ(test_set, *base_set);\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/AGENTS.md",
    "content": "# AGENTS.md\n\n## Scope\n\nThis directory adapts the generic `basic_animation/` layer to Lynx-facing animator behavior. It bridges shared animation primitives with `starlight::AnimationData`, VSync-backed frame callbacks, and user-visible animator event hooks.\n\n## Module Map\n\n- `basic_animator.*` is the main Lynx animator entry point and `AnimatorTarget` implementation.\n- `basic_animator_event_listener.*` translates low-level animation events into externally registered callbacks.\n- `basic_animator_frame_callback_provider.*` connects animation frame requests to `base::VSyncMonitor`.\n- `basic_property_value.*` contains Lynx-specific property-value adapters built on top of `basic_animation/property_value.h`.\n\n## Key Files And Types\n\n- `basic_animator.*`: defines `LynxBasicAnimator`, the adapter that builds a shared basic animation from `starlight::AnimationData`, starts and stops it, and converts animated progress back into Lynx-visible callbacks.\n- `basic_animator_event_listener.*`: receives shared animation lifecycle events and maps them to externally registered start/cancel/iteration/end callbacks.\n- `basic_animator_frame_callback_provider.*`: requests frames from `base::VSyncMonitor`, including creating and binding a thread-local monitor when one is not injected.\n- `basic_property_value.*`: adapter-side `PropertyValue` implementation for float progress interpolation used by `LynxBasicAnimator`.\n\n## Layer Choice\n\n- This directory is the Lynx-facing adapter layer on top of `basic_animation/`; it is not the place for generic timing, interpolation, or reusable keyframe machinery.\n- If the issue is specific to `starlight::AnimationData`, callback wiring, adapter-visible progress values, or VSync-backed frame hookup, this directory is the right place.\n- If the issue is about generic timing semantics, iteration trimming, shared frame-callback abstractions, or core interpolation contracts, inspect `../basic_animation/` instead.\n- If you are unsure, inspect the parent `animation/AGENTS.md` first and then compare this directory with `../basic_animation/`.\n\n## Typical Change Patterns\n\n- If the change is about how a Lynx basic animator is initialized from `starlight::AnimationData`, inspect `basic_animator.cc`.\n- If the change is about user callback timing or which animation event maps to which callback, inspect `basic_animator_event_listener.*` and `basic_animator.cc` together.\n- If the change is about frame scheduling, thread affinity, or VSync hookup, inspect `basic_animator_frame_callback_provider.*`.\n- If the change is about the value passed back through `UpdateAnimatedStyle`, inspect `basic_property_value.*` and the `\"BASIC_TYPE_FLOAT\"` path in `basic_animator.cc`.\n\n## Edit Rules\n\n- Keep generic animation semantics in `../basic_animation/`. This directory should stay focused on Lynx-facing adaptation and integration.\n- Changes to `basic_animator.*` can affect start/stop lifecycle, callback timing, and value propagation. Validate event and frame-scheduling behavior together rather than in isolation.\n- Keep `basic_animator_frame_callback_provider.*` aligned with VSync-driven execution. Avoid introducing alternative scheduling semantics here without a strong reason.\n- If you add new adapter-side property value behavior, confirm it still matches the `PropertyValue` type contract defined by the shared animation layer.\n- Treat callback and logging-only branches carefully. Several paths currently log when callbacks are absent; a refactor here can silently change observable event behavior.\n\n## Invariants And Pitfalls\n\n- `basic_animator.cc` hardcodes a two-keyframe `\"BASIC_TYPE_FLOAT\"` animation from `0.0` to `1.0`, then converts progress back through `UpdateAnimatedStyle`. If you change that contract, update both keyframe construction and value consumption together.\n- `InitializeAnimator()` wires together effect timing, keyframes, frame provider, and event listener in one place. Partial refactors here often break either frame callbacks or event delivery.\n- `basic_animator_frame_callback_provider.cc` creates and binds a thread-local `VSyncMonitor` when one is not injected. Changes here can alter thread behavior in tests and in production.\n- `basic_property_value.cc` assumes float-to-float interpolation. If the adapter starts supporting more value kinds, expand the type contract deliberately rather than overloading the existing float path.\n\n## Common Regression Symptoms\n\n- Progress callbacks not firing, firing only once, or returning the wrong value usually point to `basic_animator.cc` and `basic_property_value.*`.\n- Start/cancel/iteration/end callbacks missing or arriving in the wrong order usually point to `basic_animator_event_listener.*` or how it is wired in `InitializeAnimator()`.\n- Animations that appear to initialize correctly but never advance frame-by-frame usually point to `basic_animator_frame_callback_provider.*` and its `VSyncMonitor` path.\n\n## Validate\n\nUse the `lynx-cpp-test` skill for environment setup, GN generation, target build, single-test execution, and coverage workflows.\n\nResolve the exact exec target from this directory's `BUILD.gn`.\n\n- Start with `lynx_basic_animator_unittests_exec`.\n- If your change crosses the shared adapter boundary, also run `basic_animation_unittests_exec`.\n\nExpand validation when appropriate:\n\n- If you touched event callback mapping, confirm start, cancel, iteration, and end behavior in the adapter test coverage.\n- If you touched VSync or frame provider behavior, review whether the change should also be exercised through the shared animation scheduling path.\n\n## Notes\n\n- This adapter depends on shell/base integration points, so seemingly local lifecycle or callback changes may surface as integration issues rather than pure animation failures.\n- `GetStyle` and `UpdateAnimatedStyle` form the adapter boundary between shared animation values and Lynx-visible state updates; review both sides when changing property handling.\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../testing/test.gni\")\nimport(\"../../Lynx.gni\")\n\nlynx_basic_animator_shared_sources = [\n  \"basic_animator.cc\",\n  \"basic_animator.h\",\n  \"basic_animator_event_listener.cc\",\n  \"basic_animator_event_listener.h\",\n  \"basic_animator_frame_callback_provider.cc\",\n  \"basic_animator_frame_callback_provider.h\",\n  \"basic_property_value.cc\",\n  \"basic_property_value.h\",\n]\n\nlynx_core_source_set(\"lynx_basic_animator\") {\n  sources = lynx_basic_animator_shared_sources\n\n  public_deps = [\n    \"../:animation\",\n    \"../../base\",\n    \"../../shell:shell\",\n    \"../utils:animation_utils\",\n  ]\n}\n\nunittest_set(\"lynx_basic_animator_testset\") {\n  testonly = true\n  sources = [ \"lynx_basic_animator_unittest.cc\" ]\n  deps = []\n  public_deps = [ \":lynx_basic_animator\" ]\n}\n\nunittest_exec(\"lynx_basic_animator_unittests_exec\") {\n  testonly = true\n  sources = []\n  deps = [\n    \":lynx_basic_animator\",\n    \":lynx_basic_animator_testset\",\n    \"../../renderer/dom:dom\",\n  ]\n}\n\ngroup(\"lynx_basic_animator_tests\") {\n  testonly = true\n  deps = [ \":lynx_basic_animator_unittests_exec\" ]\n  public_deps = [ \":lynx_basic_animator_testset\" ]\n}\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/basic_animator.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/lynx_basic_animator/basic_animator.h\"\n\n#include <utility>\n#include <vector>\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nvoid LynxBasicAnimator::UpdateAnimatedStyle(\n    const Keyframe::PropertyValueMap &styles) {\n  auto progress_iter = styles.find(\"BASIC_TYPE_FLOAT\");\n  if (progress_iter != styles.end()) {\n    auto progress =\n        static_cast<BasicFloatPropertyValue *>(progress_iter->second.get())\n            ->GetFloatValue();\n    if (custom_callback_) {\n      return custom_callback_(progress);\n    } else {\n      LOGE(\"LynxBasicAnimator has no custom callback! Please check!\")\n    }\n  } else {\n    LOGE(\"A normal progress is not returned here\");\n  }\n}\n\nvoid LynxBasicAnimator::InitializeAnimator() {\n  std::vector<std::unique_ptr<basic::KeyframeToken>> keyframes;\n\n  std::unique_ptr<basic::KeyframeToken> from_keyframe_token =\n      std::make_unique<basic::KeyframeToken>(0.0);\n  std::unique_ptr<basic::PropertyValue> from_property_value =\n      std::make_unique<BasicFloatPropertyValue>(0.0);\n  from_keyframe_token->AddPropertyValueForToken(\"BASIC_TYPE_FLOAT\",\n                                                std::move(from_property_value));\n  keyframes.emplace_back(std::move(from_keyframe_token));\n\n  std::unique_ptr<basic::KeyframeToken> to_keyframe_token =\n      std::make_unique<basic::KeyframeToken>(1.0);\n  std::unique_ptr<basic::PropertyValue> to_property_value =\n      std::make_unique<BasicFloatPropertyValue>(1.0);\n  to_keyframe_token->AddPropertyValueForToken(\"BASIC_TYPE_FLOAT\",\n                                              std::move(to_property_value));\n  keyframes.emplace_back(std::move(to_keyframe_token));\n\n  std::unique_ptr<basic::AnimationEffectTiming> effect_timing =\n      basic::AnimationEffectTiming::Create(\n          fml::TimeDelta::FromMillisecondsF(data_.delay),\n          static_cast<basic::AnimationEffectTiming::FillMode>(data_.fill_mode),\n          data_.iteration_count,\n          fml::TimeDelta::FromMillisecondsF(data_.duration),\n          static_cast<basic::AnimationEffectTiming::PlaybackDirection>(\n              data_.direction),\n          TimingFunction::MakeTimingFunction(data_.timing_func));\n  std::unique_ptr<basic::KeyframeEffect> effect = basic::KeyframeEffect::Create(\n      std::move(keyframes), shared_from_this(), std::move(effect_timing));\n  animation_ = std::make_shared<basic::Animation>(std::move(effect));\n  animation_->RegisterAnimationFrameCallbackProvider(frame_provider_);\n  animation_->AddEventListener(basic_animator_event_listener_);\n};\n\nbool LynxBasicAnimator::IsRunning() const {\n  return animation_ && animation_->GetState() == basic::Animation::State::kPlay;\n}\n\nvoid LynxBasicAnimator::RegisterEventCallback(\n    const BasicAnimatorEventListener::EventCallback &cb,\n    Animation::EventType event_type) {\n  switch (event_type) {\n    case Animation::EventType::Start:\n      basic_animator_event_listener_->RegisterStartCallback(cb);\n      break;\n    case Animation::EventType::Iteration:\n      basic_animator_event_listener_->RegisterIterationCallback(cb);\n      break;\n    case Animation::EventType::Cancel:\n      basic_animator_event_listener_->RegisterCancelCallback(cb);\n      break;\n    case Animation::EventType::End:\n      basic_animator_event_listener_->RegisterEndCallback(cb);\n    default:\n      LOGE(\"There is no event of this type\");\n      break;\n  }\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/basic_animator.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_ANIMATOR_H_\n#define CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_ANIMATOR_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n\n#include \"core/animation/basic_animation/animation_effect.h\"\n#include \"core/animation/basic_animation/animation_effect_timing.h\"\n#include \"core/animation/basic_animation/animation_frame_callback.h\"\n#include \"core/animation/basic_animation/animation_frame_callback_provider.h\"\n#include \"core/animation/basic_animation/basic_animation.h\"\n#include \"core/animation/basic_animation/basic_keyframe_effect.h\"\n#include \"core/animation/lynx_basic_animator/basic_animator_event_listener.h\"\n#include \"core/animation/lynx_basic_animator/basic_animator_frame_callback_provider.h\"\n#include \"core/animation/lynx_basic_animator/basic_property_value.h\"\n#include \"core/style/animation_data.h\"\nnamespace lynx {\n\nnamespace base {\nclass VSyncMonitor;\n}\n\nnamespace animation {\nnamespace basic {\n\nclass BasicAnimatorFrameCallbackProvider;\n\nclass LynxBasicAnimator : public basic::AnimatorTarget {\n public:\n  enum BasicValueType { INT = 0, FLOAT };\n  using Callback = std::function<void(float)>;\n\n  LynxBasicAnimator(starlight::AnimationData data,\n                    std::shared_ptr<base::VSyncMonitor> vsync_monitor = nullptr,\n                    BasicValueType type = BasicValueType::FLOAT)\n      : basic_type_(type), data_(data) {\n    frame_provider_ =\n        std::make_shared<BasicAnimatorFrameCallbackProvider>(vsync_monitor);\n    basic_animator_event_listener_ =\n        std::make_shared<BasicAnimatorEventListener>();\n  };\n\n  void InitializeAnimator();\n\n  void RegisterCustomCallback(const Callback& cb) { custom_callback_ = cb; }\n\n  void RegisterEventCallback(\n      const BasicAnimatorEventListener::EventCallback& cb,\n      Animation::EventType event_type);\n\n  void Start() {\n    this->InitializeAnimator();\n    animation_->Play();\n  }\n\n  bool IsRunning() const;\n\n  void Stop() {\n    if (animation_) {\n      animation_->Stop();\n    }\n  }\n\n  void DestroyAnimation() {\n    if (animation_) {\n      animation_->Destroy();\n    }\n  }\n\n  void UpdateAnimatedStyle(const Keyframe::PropertyValueMap& styles) override;\n\n  std::unique_ptr<basic::PropertyValue> GetStyle(\n      const std::string& property_name) override {\n    // TODO: need return invalid value\n    return std::make_unique<BasicFloatPropertyValue>(1.0);\n  }\n\n  BasicValueType basic_type() { return basic_type_; }\n\n private:\n  BasicValueType basic_type_;\n  starlight::AnimationData data_;\n  std::shared_ptr<basic::Animation> animation_;\n  std::shared_ptr<BasicAnimatorFrameCallbackProvider> frame_provider_;\n  std::shared_ptr<BasicAnimatorEventListener> basic_animator_event_listener_;\n  // The animator executes a callback on each frame, and passed by the user.\n  Callback custom_callback_;\n};\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_ANIMATOR_H_\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/basic_animator_event_listener.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/lynx_basic_animator/basic_animator_event_listener.h\"\n\n#include <utility>\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nvoid BasicAnimatorEventListener::OnAnimationStart(const Animation& animation) {\n  LOGI(\"Basic Animation Start. \");\n  if (start_callback_) {\n    return start_callback_();\n  } else {\n    LOGI(\"LynxBasicAnimator has no start callback! Please check!\")\n  }\n}\n\nvoid BasicAnimatorEventListener::OnAnimationEnd(const Animation& animation) {\n  LOGI(\"Basic Animation End. \");\n  if (end_callback_) {\n    return end_callback_();\n  } else {\n    LOGI(\"LynxBasicAnimator has no start callback! Please check!\")\n  }\n}\n\nvoid BasicAnimatorEventListener::OnAnimationCancel(const Animation& animation) {\n  LOGI(\"Basic Animation Cancel. \");\n  if (cancel_callback_) {\n    return cancel_callback_();\n  } else {\n    LOGI(\"LynxBasicAnimator has no start callback! Please check!\")\n  }\n}\n\nvoid BasicAnimatorEventListener::OnAnimationIteration(\n    const Animation& animation) {\n  LOGI(\"Basic Animation Iteration. \");\n  if (iteration_callback_) {\n    return iteration_callback_();\n  } else {\n    LOGI(\"LynxBasicAnimator has no start callback! Please check!\")\n  }\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/basic_animator_event_listener.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_ANIMATOR_EVENT_LISTENER_H_\n#define CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_ANIMATOR_EVENT_LISTENER_H_\n\n#include <functional>\n#include <memory>\n\n#include \"core/animation/basic_animation/animation_event_listener.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nclass BasicAnimatorEventListener : public AnimationEventListener {\n public:\n  using EventCallback = std::function<void()>;\n\n  BasicAnimatorEventListener() = default;\n  ~BasicAnimatorEventListener() = default;\n\n  void OnAnimationStart(const Animation&) override;\n  void OnAnimationEnd(const Animation&) override;\n  void OnAnimationCancel(const Animation&) override;\n  void OnAnimationIteration(const Animation&) override;\n\n  void RegisterStartCallback(const EventCallback& cb) { start_callback_ = cb; }\n\n  void RegisterCancelCallback(const EventCallback& cb) {\n    cancel_callback_ = cb;\n  }\n\n  void RegisterIterationCallback(const EventCallback& cb) {\n    iteration_callback_ = cb;\n  }\n\n  void RegisterEndCallback(const EventCallback& cb) { end_callback_ = cb; }\n\n private:\n  EventCallback start_callback_;\n  EventCallback cancel_callback_;\n  EventCallback iteration_callback_;\n  EventCallback end_callback_;\n};\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_ANIMATOR_EVENT_LISTENER_H_\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/basic_animator_frame_callback_provider.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/lynx_basic_animator/basic_animator_frame_callback_provider.h\"\n\n#include <utility>\n\n#include \"core/base/threading/vsync_monitor.h\"\n#include \"core/renderer/utils/lynx_env.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace basic {\n\nstd::shared_ptr<base::VSyncMonitor>\nBasicAnimatorFrameCallbackProvider::GetVSyncMonitor() {\n  thread_local std::shared_ptr<base::VSyncMonitor> local_vsync_monitor_;\n  if (!vsync_monitor_) {\n    local_vsync_monitor_ = base::VSyncMonitor::Create(\n        tasm::LynxEnv::GetInstance().EnableAnimationVsyncOnUIThread());\n    local_vsync_monitor_->BindToCurrentThread();\n    local_vsync_monitor_->Init();\n    vsync_monitor_ = local_vsync_monitor_;\n  }\n  return vsync_monitor_;\n}\n\nvoid BasicAnimatorFrameCallbackProvider::RequestNextFrame(\n    base::MoveOnlyClosure<void, const fml::TimePoint&> callback) {\n  GetVSyncMonitor()->ScheduleVSyncSecondaryCallback(\n      reinterpret_cast<uintptr_t>(this),\n      [callback = std::move(callback)](int64_t frame_start, int64_t frame_end) {\n        callback(fml::TimePoint::FromTicks((frame_start)));\n      });\n}\n\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/basic_animator_frame_callback_provider.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_ANIMATOR_FRAME_CALLBACK_PROVIDER_H_\n#define CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_ANIMATOR_FRAME_CALLBACK_PROVIDER_H_\n\n#include <memory>\n\n#include \"core/animation/basic_animation/animation_frame_callback_provider.h\"\n\nnamespace lynx {\n\nnamespace base {\nclass VSyncMonitor;\n}\n\nnamespace animation {\nnamespace basic {\nclass BasicAnimatorFrameCallbackProvider\n    : public AnimationFrameCallbackProvider {\n public:\n  explicit BasicAnimatorFrameCallbackProvider(\n      const std::shared_ptr<base::VSyncMonitor> &vsync_monitor)\n      : vsync_monitor_(vsync_monitor) {}\n\n  ~BasicAnimatorFrameCallbackProvider() override = default;\n\n  void RequestNextFrame(\n      base::MoveOnlyClosure<void, const fml::TimePoint &> callback) override;\n\n  std::shared_ptr<base::VSyncMonitor> GetVSyncMonitor();\n\n private:\n  std::shared_ptr<base::VSyncMonitor> vsync_monitor_{nullptr};\n};\n}  // namespace basic\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_ANIMATOR_FRAME_CALLBACK_PROVIDER_H_\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/basic_property_value.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/lynx_basic_animator/basic_property_value.h\"\n\n#include <memory>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace animation {\n\nstd::unique_ptr<basic::PropertyValue> BasicFloatPropertyValue::Interpolate(\n    double progress, const std::unique_ptr<PropertyValue>& end_value) const {\n  DCHECK(static_cast<basic::BasicPropertyValueType>(end_value->GetType()) ==\n         basic::BasicPropertyValueType::Float);\n  return std::make_unique<BasicFloatPropertyValue>(\n      float_value_ +\n      (reinterpret_cast<BasicFloatPropertyValue*>(end_value.get())\n           ->GetFloatValue() -\n       float_value_) *\n          progress);\n}\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/basic_property_value.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_PROPERTY_VALUE_H_\n#define CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_PROPERTY_VALUE_H_\n\n#include <memory>\n\n#include \"core/animation/basic_animation/property_value.h\"\n\nnamespace lynx {\nnamespace animation {\n\nclass BasicFloatPropertyValue : public basic::PropertyValue {\n public:\n  explicit BasicFloatPropertyValue(float value) : float_value_(value) {}\n\n  std::unique_ptr<PropertyValue> Clone() const override {\n    return std::make_unique<BasicFloatPropertyValue>(float_value_);\n  }\n\n  std::unique_ptr<PropertyValue> Interpolate(\n      double progress,\n      const std::unique_ptr<PropertyValue>& end_value) const override;\n\n  size_t GetType() const override {\n    return static_cast<size_t>(basic::BasicPropertyValueType::Float);\n  }\n\n  float GetFloatValue() const { return float_value_; }\n\n private:\n  float float_value_{1.0};\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_LYNX_BASIC_ANIMATOR_BASIC_PROPERTY_VALUE_H_\n"
  },
  {
    "path": "core/animation/lynx_basic_animator/lynx_basic_animator_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#define private public\n#define protected public\n\n#include <cmath>\n#include <numeric>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"core/animation/lynx_basic_animator/basic_animator.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace testing {\n\nclass LynxBasicAnimatorTest : public ::testing::Test {\n public:\n  LynxBasicAnimatorTest() {}\n  ~LynxBasicAnimatorTest() override {}\n  double calculateMSE(std::vector<double>& data_set_1,\n                      std::vector<double>& data_set_2) {\n    if (data_set_1.empty() || data_set_2.empty()) {\n      return 10.0;\n    }\n    if (data_set_1.size() != data_set_2.size()) {\n      if (data_set_1.size() == 61) {\n        data_set_1.push_back(1.0);\n      } else if (data_set_2.size() == 61) {\n        data_set_2.push_back(2.0);\n      }\n    }\n\n    double mse = 0.0;\n    size_t n = data_set_1.size();\n\n    for (size_t i = 0; i < n; ++i) {\n      double diff = data_set_1[i] - data_set_2[i];\n      mse += diff * diff;\n    }\n\n    return mse / n;\n  }\n\n  double calculatePearsonCorrelation(std::vector<double>& data_set_1,\n                                     std::vector<double>& data_set_2) {\n    if (data_set_1.empty() || data_set_2.empty()) {\n      return 0.0;\n    }\n    if (data_set_1.size() != data_set_2.size()) {\n      if (data_set_1.size() == 61) {\n        data_set_1.push_back(1.0);\n      } else if (data_set_2.size() == 61) {\n        data_set_2.push_back(2.0);\n      }\n    }\n\n    size_t n = data_set_1.size();\n    double sum1 = 0, sum2 = 0, sum1Sq = 0, sum2Sq = 0, pSum = 0;\n\n    for (size_t i = 0; i < n; ++i) {\n      sum1 += data_set_1[i];\n      sum2 += data_set_2[i];\n      sum1Sq += std::pow(data_set_1[i], 2);\n      sum2Sq += std::pow(data_set_2[i], 2);\n      pSum += data_set_1[i] * data_set_2[i];\n    }\n\n    double num = pSum - (sum1 * sum2 / n);\n    double den = std::sqrt((sum1Sq - std::pow(sum1, 2) / n) *\n                           (sum2Sq - std::pow(sum2, 2) / n));\n\n    return den == 0 ? 0 : num / den;\n  }\n\n  std::shared_ptr<animation::basic::LynxBasicAnimator> lynx_basic_animator_1;\n};\n\nTEST_F(LynxBasicAnimatorTest, HowToUseBasicAnimator) {\n  starlight::AnimationData data;\n  data.duration = 2000;\n  data.fill_mode = starlight::AnimationFillModeType::kForwards;\n  starlight::TimingFunctionData timing_function_data;\n  timing_function_data.timing_func = starlight::TimingFunctionType::kLinear;\n  data.timing_func = timing_function_data;\n\n  lynx_basic_animator_1 =\n      std::make_shared<animation::basic::LynxBasicAnimator>(data);\n  std::vector<double> data_set_1;\n  lynx_basic_animator_1->RegisterCustomCallback([&](float progress) {\n    std::cout << \"CallBack called with value: \" << progress << std::endl;\n    data_set_1.push_back(progress);\n  });\n  // lynx_basic_animator_1->Start();\n  lynx_basic_animator_1->InitializeAnimator();\n\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(1.f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(2.f);\n\n  auto model = lynx_basic_animator_1->animation_->effect_->keyframe_models_\n                   .find(\"BASIC_TYPE_FLOAT\")\n                   ->second.get();\n  auto curve = model->curve_.get();\n  auto res1 = static_cast<animation::BasicFloatPropertyValue*>(\n                  curve->GetValue(value1).get())\n                  ->GetFloatValue();\n  auto res2 = static_cast<animation::BasicFloatPropertyValue*>(\n                  curve->GetValue(value2).get())\n                  ->GetFloatValue();\n  auto res3 = static_cast<animation::BasicFloatPropertyValue*>(\n                  curve->GetValue(value3).get())\n                  ->GetFloatValue();\n  EXPECT_FLOAT_EQ(0.0f, res1);\n  EXPECT_FLOAT_EQ(0.5f, res2);\n  EXPECT_FLOAT_EQ(1.0f, res3);\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/testing/mock_animation.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/testing/mock_animation.h\"\n\n#include <math.h>\n\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/base/threading/vsync_monitor.h\"\n\nnamespace lynx {\nnamespace animation {}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/testing/mock_animation.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_TESTING_MOCK_ANIMATION_H_\n#define CORE_ANIMATION_TESTING_MOCK_ANIMATION_H_\n\n#include <memory>\n#include <string>\n#include <unordered_set>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/animation/animation.h\"\n#include \"core/animation/keyframe_effect.h\"\n\nnamespace lynx {\n\nnamespace animation {\n\nclass MockAnimation : public Animation {\n public:\n  MockAnimation(const std::string& name) : Animation(name) {}\n  ~MockAnimation() = default;\n  const fml::TimePoint& start_time() { return start_time_; }\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_TESTING_MOCK_ANIMATION_H_\n"
  },
  {
    "path": "core/animation/testing/mock_css_keyframe_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/testing/mock_css_keyframe_manager.h\"\n\n#include <algorithm>\n#include <queue>\n#include <utility>\n#include <vector>\n\n#include \"base/include/log/logging.h\"\n#include \"core/animation/animation.h\"\n#include \"core/animation/animation_delegate.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/animation/utils/timing_function.h\"\n#include \"core/renderer/css/css_keyframes_token.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace animation {\nvoid MockCSSKeyframeManager::ClearUTStatus() {\n  has_flush_animated_style_ = false;\n  has_request_next_frame_ = false;\n}\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/testing/mock_css_keyframe_manager.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_TESTING_MOCK_CSS_KEYFRAME_MANAGER_H_\n#define CORE_ANIMATION_TESTING_MOCK_CSS_KEYFRAME_MANAGER_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/animation/animation.h\"\n#include \"core/animation/animation_delegate.h\"\n#include \"core/animation/css_keyframe_manager.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/animation/utils/timing_function.h\"\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/css/css_keyframes_token.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/style/animation_data.h\"\n\nnamespace lynx {\n\nnamespace animation {\n\nclass MockCSSKeyframeManager : public CSSKeyframeManager {\n public:\n  MockCSSKeyframeManager(tasm::Element* element)\n      : CSSKeyframeManager(element) {}\n  ~MockCSSKeyframeManager() = default;\n  auto& animations_map() { return animations_map_; }\n\n  void SetNeedsAnimationStyleRecalc(const base::String& name) override {\n    clear_effect_animation_name_ = name.str();\n  }\n\n  const std::string& GetClearEffectAnimationName() {\n    return clear_effect_animation_name_;\n  }\n\n  void RequestNextFrame(std::weak_ptr<Animation> ptr) override {\n    has_request_next_frame_ = true;\n  }\n\n  void FlushAnimatedStyle() override { has_flush_animated_style_ = true; }\n\n  bool has_flush_animated_style() { return has_flush_animated_style_; }\n\n  bool has_request_next_frame() { return has_request_next_frame_; }\n\n  void ClearUTStatus();\n\n private:\n  std::string clear_effect_animation_name_;\n  bool has_flush_animated_style_{false};\n  bool has_request_next_frame_{false};\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_TESTING_MOCK_CSS_KEYFRAME_MANAGER_H_\n"
  },
  {
    "path": "core/animation/testing/mock_css_transition_manager.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/testing/mock_css_transition_manager.h\"\n\n#include <utility>\n\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n\nnamespace lynx {\nnamespace animation {}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/testing/mock_css_transition_manager.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_TESTING_MOCK_CSS_TRANSITION_MANAGER_H_\n#define CORE_ANIMATION_TESTING_MOCK_CSS_TRANSITION_MANAGER_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <vector>\n\n#include \"core/animation/css_transition_manager.h\"\n#include \"core/style/transition_data.h\"\n\nnamespace lynx {\nnamespace animation {\n\nclass MockCSSTransitionManager : public CSSTransitionManager {\n public:\n  MockCSSTransitionManager(tasm::Element* element)\n      : CSSTransitionManager(element) {}\n  ~MockCSSTransitionManager() = default;\n\n  auto& transition_data() { return transition_data_; }\n\n  base::Vector<starlight::AnimationData>& animation_data() {\n    return animation_data_;\n  }\n\n  auto& property_types() { return property_types_; }\n\n  auto& keyframe_tokens() { return keyframe_tokens_; }\n\n  auto& animations_map() { return animations_map_; }\n\n  void NotifyClientAnimated(tasm::StyleMap& styles, tasm::CSSValue value,\n                            tasm::CSSPropertyID css_id) override {\n    has_been_ticked_ = true;\n  }\n\n  void SetNeedsAnimationStyleRecalc(const base::String& name) override {\n    clear_effect_animation_name_ = name.str();\n  }\n\n  bool has_been_ticked() { return has_been_ticked_; }\n\n  const std::string& GetClearEffectAnimationName() {\n    return clear_effect_animation_name_;\n  }\n\n private:\n  bool has_been_ticked_{false};\n  std::string clear_effect_animation_name_;\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_TESTING_MOCK_CSS_TRANSITION_MANAGER_H_\n"
  },
  {
    "path": "core/animation/transform_animation_curve.cc",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/transform_animation_curve.h\"\n\n#include <cmath>\n#include <limits>\n\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/animation/animation_trace_event_def.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/renderer/css/transforms/transform_operations.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n\nnamespace lynx {\nnamespace animation {\n\n//====== TransformValueAnimator begin =======\nstd::unique_ptr<TransformKeyframe> TransformKeyframe::Create(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function) {\n  return std::make_unique<TransformKeyframe>(time, std::move(timing_function));\n}\n\nTransformKeyframe::TransformKeyframe(\n    fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function)\n    : Keyframe(time, std::move(timing_function)) {}\n\nvoid TransformKeyframe::NotifyElementSizeUpdated() {\n  if (value_) {\n    value_->NotifyElementSizeUpdated();\n  }\n}\n\n// When view or font size has changed, mark the value 'AutoNLength'.\nvoid TransformKeyframe::NotifyUnitValuesUpdatedToAnimation(\n    tasm::CSSValuePattern type) {\n  if (value_) {\n    value_->NotifyUnitValuesUpdatedToAnimation(type);\n  }\n}\n\ntransforms::TransformOperations\nTransformKeyframe::GetTransformKeyframeValueInElement(tasm::Element* element) {\n  tasm::CSSValue transform =\n      GetStyleInElement(tasm::kPropertyIDTransform, element);\n  if (transform.IsArray()) {\n    return transforms::TransformOperations(element, transform);\n  } else {\n    return transforms::TransformOperations(element);\n  }\n}\n\nbool TransformKeyframe::SetValue(tasm::CSSPropertyID id,\n                                 const tasm::CSSValue& value,\n                                 tasm::Element* element) {\n  auto keyframe_transform_value =\n      HandleCSSVariableValueIfNeed(id, value, element);\n  if (!keyframe_transform_value.IsArray()) {\n    return false;\n  }\n  auto transform = std::make_unique<transforms::TransformOperations>(\n      element, keyframe_transform_value);\n  value_ = std::move(transform);\n  css_value_ = value;\n  is_empty_ = false;\n  return true;\n}\n\nstd::unique_ptr<KeyframedTransformAnimationCurve>\nKeyframedTransformAnimationCurve::Create() {\n  return std::make_unique<KeyframedTransformAnimationCurve>();\n}\n\n// Using for getting the corresponding transform style value based on the local\n// time passed in. The local time is converted from monotonic time of VSYNC.\n//\n// Details: This method get the active keyframe based on the local time passed\n// in firstly. Then it gets the progress between the active keyframe and the\n// next one. It gets the start transform value from the active keyframe and the\n// end transform value from the keyframe next to the active keyframe. If the\n// keyframe is empty, use the transform value in element instead. Finally, blend\n// the start transform and end transform based on the progress, and return the\n// blend result as the real time style of animation.\ntasm::CSSValue KeyframedTransformAnimationCurve::GetValue(\n    fml::TimeDelta& t) const {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, KEYFRAME_TRANSFORM_ANIMATION_CURVE_GET_VALUE,\n              [](lynx::perfetto::EventContext ctx) {\n                auto* curveTypeInfo = ctx.event()->add_debug_annotations();\n                curveTypeInfo->set_name(\"curveType\");\n                curveTypeInfo->set_string_value(\"TransformAnimation\");\n              });\n  t = TransformedAnimationTime(keyframes_, timing_function_, scaled_duration(),\n                               t);\n  size_t i = GetActiveKeyframe(keyframes_, scaled_duration(), t);\n  double progress =\n      TransformedKeyframeProgress(keyframes_, scaled_duration(), t, i);\n  TransformKeyframe* keyframe =\n      static_cast<TransformKeyframe*>(keyframes_[i].get());\n  TransformKeyframe* keyframe_next =\n      static_cast<TransformKeyframe*>(keyframes_[i + 1].get());\n  auto transform_in_element = transforms::TransformOperations(nullptr);\n\n  if (std::fabs(progress - 0.0f) < std::numeric_limits<float>::epsilon()) {\n    return keyframe->IsEmpty()\n               ? GetStyleInElement(tasm::kPropertyIDTransform, element_)\n               : keyframe->CSSValue();\n  }\n  if (std::fabs(progress - 1.0f) < std::numeric_limits<float>::epsilon()) {\n    return keyframe_next->IsEmpty()\n               ? GetStyleInElement(tasm::kPropertyIDTransform, element_)\n               : keyframe_next->CSSValue();\n  }\n\n  if (keyframe->IsEmpty() || keyframe_next->IsEmpty()) {\n    transform_in_element =\n        TransformKeyframe::GetTransformKeyframeValueInElement(element_);\n  }\n\n  // When view or font size has changed, let start_len and end_len be\n  // 'AutoNLength', and then get The actual Nlength based on the updated size.\n  if (keyframe->Value() && keyframe->Value()->GetOperations().empty()) {\n    keyframe->SetValue(static_cast<tasm::CSSPropertyID>(Type()),\n                       keyframe->CSSValue(), element_);\n  }\n  if (keyframe_next->Value() &&\n      keyframe_next->Value()->GetOperations().empty()) {\n    keyframe_next->SetValue(static_cast<tasm::CSSPropertyID>(Type()),\n                            keyframe_next->CSSValue(), element_);\n  }\n\n  transforms::TransformOperations& start_transform =\n      keyframe->IsEmpty() ? transform_in_element : *keyframe->Value();\n  transforms::TransformOperations& end_transform =\n      keyframe_next->IsEmpty() ? transform_in_element : *keyframe_next->Value();\n\n  transforms::TransformOperations blended_result =\n      end_transform.Blend(start_transform, progress);\n  return blended_result.ToTransformRawValue();\n}\n\n//====== TransformValueAnimator end =======\n\nstd::unique_ptr<Keyframe> TransformAnimationCurve::MakeEmptyKeyframe(\n    const fml::TimeDelta& offset) {\n  return TransformKeyframe::Create(offset, nullptr);\n}\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/transform_animation_curve.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_TRANSFORM_ANIMATION_CURVE_H_\n#define CORE_ANIMATION_TRANSFORM_ANIMATION_CURVE_H_\n\n#include <memory>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/time/time_delta.h\"\n#include \"core/animation/animation_curve.h\"\n#include \"core/animation/utils/timing_function.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/transforms/transform_operations.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n\nnamespace lynx {\n\nnamespace tasm {\nclass Element;\n}\n\nnamespace animation {\n\nclass TransformAnimationCurve : public AnimationCurve {\n public:\n  ~TransformAnimationCurve() override = default;\n\n  std::unique_ptr<Keyframe> MakeEmptyKeyframe(\n      const fml::TimeDelta& offset) override;\n};\n\nclass KeyframedTransformAnimationCurve : public TransformAnimationCurve {\n public:\n  static std::unique_ptr<KeyframedTransformAnimationCurve> Create();\n  ~KeyframedTransformAnimationCurve() override = default;\n\n  tasm::CSSValue GetValue(fml::TimeDelta& t) const override;\n};\n\n//====Transform keyframe ====\nclass TransformKeyframe : public Keyframe {\n public:\n  static transforms::TransformOperations GetTransformKeyframeValueInElement(\n      tasm::Element*);\n  static std::unique_ptr<TransformKeyframe> Create(\n      fml::TimeDelta time, std::unique_ptr<TimingFunction> timing_function);\n  ~TransformKeyframe() override = default;\n\n  void SetTransform(\n      std::unique_ptr<transforms::TransformOperations> transform) {\n    value_ = std::move(transform);\n    is_empty_ = false;\n  }\n\n  bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                tasm::Element* element) override;\n\n  const std::unique_ptr<transforms::TransformOperations>& Value() const {\n    return value_;\n  };\n\n  void NotifyElementSizeUpdated() override;\n\n  void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern) override;\n\n  TransformKeyframe(fml::TimeDelta time,\n                    std::unique_ptr<TimingFunction> timing_function);\n\n  tasm::CSSValue CSSValue() { return css_value_; }\n\n private:\n  std::unique_ptr<transforms::TransformOperations> value_;\n  tasm::CSSValue css_value_;\n};\n\n}  // namespace animation\n}  // namespace lynx\n#endif  // CORE_ANIMATION_TRANSFORM_ANIMATION_CURVE_H_\n"
  },
  {
    "path": "core/animation/transform_animation_curve_unittest.cc",
    "content": "// Copyright 2017 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/transform_animation_curve.h\"\n\n#include <memory>\n\n#include \"base/include/log/logging.h\"\n#include \"core/animation/css_keyframe_manager.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframe_model.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/css/transforms/transform_operations.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/animation_data.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace tasm {\nnamespace testing {\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass MockKeyframedLayoutAnimationCurve : public LayoutAnimationCurve {\n public:\n  MockKeyframedLayoutAnimationCurve() = default;\n  ~MockKeyframedLayoutAnimationCurve() override = default;\n\n  static std::unique_ptr<MockKeyframedLayoutAnimationCurve> Create() {\n    return std::make_unique<MockKeyframedLayoutAnimationCurve>();\n  }\n\n  lynx::tasm::CSSValue GetValue(fml::TimeDelta& t) const override {\n    return lynx::tasm::CSSValue();\n  };\n\n  std::vector<std::unique_ptr<Keyframe>> GetKeyframes() {\n    return std::move(keyframes_);\n  }\n};\n\nclass MockKeyframedTransformAnimationCurve : public TransformAnimationCurve {\n public:\n  MockKeyframedTransformAnimationCurve() = default;\n  ~MockKeyframedTransformAnimationCurve() override = default;\n\n  static std::unique_ptr<MockKeyframedTransformAnimationCurve> Create() {\n    return std::make_unique<MockKeyframedTransformAnimationCurve>();\n  }\n\n  lynx::tasm::CSSValue GetValue(fml::TimeDelta& t) const override {\n    return lynx::tasm::CSSValue();\n  }\n\n  std::vector<std::unique_ptr<Keyframe>> GetKeyframes() {\n    return std::move(keyframes_);\n  }\n};\n\nclass TransformAnimationCurveTest : public ::testing::Test {\n public:\n  TransformAnimationCurveTest() {}\n  ~TransformAnimationCurveTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>\n      tasm_mediator;\n\n  void SetUp() override {\n    ::lynx::tasm::LynxEnvConfig lynx_env_config(\n        kWidth, kHeight, kDefaultLayoutsUnitPerPx,\n        kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<::lynx::tasm::MockPaintingContext>(),\n        tasm_mediator.get(), lynx_env_config);\n    auto config = std::make_shared<::lynx::tasm::PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n  }\n\n  fml::RefPtr<lynx::tasm::Element> InitTestElement() {\n    return manager->CreateFiberElement(\"view\");\n  }\n};\n\nTEST_F(TransformAnimationCurveTest, TransformedAnimationTime) {\n  std::unique_ptr<MockKeyframedLayoutAnimationCurve> curve(\n      MockKeyframedLayoutAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::LEFT;\n  auto test_frame1 = LayoutKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetLayout(starlight::NLength::MakeUnitNLength(2.f));\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_keyframes = curve->GetKeyframes();\n  auto test_scaled_duration = curve->scaled_duration();\n  fml::TimeDelta test_time1 = fml::TimeDelta::FromSecondsF(0.5f);\n  auto transformed_time1 = TransformedAnimationTime(\n      std::move(test_keyframes), nullptr, test_scaled_duration, test_time1);\n  EXPECT_EQ(test_time1, transformed_time1);\n\n  starlight::TimingFunctionData timing_function_data;\n  timing_function_data.timing_func = starlight::TimingFunctionType::kEaseIn;\n  auto test_timing_function =\n      TimingFunction::MakeTimingFunction(timing_function_data);\n  auto transformed_time2 =\n      TransformedAnimationTime(std::move(test_keyframes), test_timing_function,\n                               test_scaled_duration, test_time1);\n  EXPECT_EQ(int64_t(315356734), transformed_time2.ToNanoseconds());\n}\n\nTEST_F(TransformAnimationCurveTest, TransformedKeyframeProgress1) {\n  std::unique_ptr<MockKeyframedLayoutAnimationCurve> curve(\n      MockKeyframedLayoutAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::LEFT;\n  auto test_frame1 = LayoutKeyframe::Create(fml::TimeDelta(), nullptr);\n  test_frame1->SetLayout(starlight::NLength::MakeUnitNLength(2.f));\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_keyframes = curve->GetKeyframes();\n  auto test_scaled_duration = curve->scaled_duration();\n  fml::TimeDelta test_time1 = fml::TimeDelta::FromSecondsF(0.5f);\n  auto progress1 = TransformedKeyframeProgress(\n      std::move(test_keyframes), test_scaled_duration, test_time1, 0);\n  EXPECT_EQ(progress1, 0.5);\n}\n\nTEST_F(TransformAnimationCurveTest, TransformedKeyframeProgress2) {\n  std::unique_ptr<MockKeyframedLayoutAnimationCurve> curve(\n      MockKeyframedLayoutAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::LEFT;\n\n  starlight::TimingFunctionData timing_function_data;\n  timing_function_data.timing_func = starlight::TimingFunctionType::kEaseIn;\n  auto test_timing_function =\n      TimingFunction::MakeTimingFunction(timing_function_data);\n\n  auto test_frame1 =\n      LayoutKeyframe::Create(fml::TimeDelta(), std::move(test_timing_function));\n  test_frame1->SetLayout(starlight::NLength::MakeUnitNLength(2.f));\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      LayoutKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  test_frame2->SetLayout(starlight::NLength::MakeUnitNLength(4.f));\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_keyframes = curve->GetKeyframes();\n  auto test_scaled_duration = curve->scaled_duration();\n  fml::TimeDelta test_time1 = fml::TimeDelta::FromSecondsF(0.5f);\n  auto progress1 = TransformedKeyframeProgress(\n      std::move(test_keyframes), test_scaled_duration, test_time1, 0);\n  EXPECT_EQ(progress1, 0.31535673426536154);\n}\n\nTEST_F(TransformAnimationCurveTest, GetStyleInElement) {\n  auto test_element_ptr = InitTestElement();\n  auto test_element = test_element_ptr.get();\n  std::unique_ptr<KeyframedOpacityAnimationCurve> curve(\n      KeyframedOpacityAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::OPACITY;\n  auto test_frame1 = OpacityKeyframe::Create(fml::TimeDelta(), nullptr);\n  auto raw_value =\n      lynx::tasm::CSSValue(1.0f, lynx::tasm::CSSValuePattern::NUMBER);\n  auto y_id = lynx::tasm::CSSPropertyID::kPropertyIDOpacity;\n  bool set_success1 = test_frame1->SetValue(y_id, raw_value, test_element);\n  EXPECT_EQ(set_success1, true);\n  EXPECT_EQ(test_frame1->IsEmpty(), false);\n  auto w_id = lynx::tasm::CSSPropertyID::kPropertyIDTop;\n  auto result_value2 = GetStyleInElement(w_id, test_element);\n  EXPECT_EQ(result_value2, lynx::tasm::CSSValue());\n}\n\nTEST_F(TransformAnimationCurveTest, GetActiveKeyframe) {\n  //  std::unique_ptr<KeyframedTransformAnimationCurve> curve(\n  //      KeyframedTransformAnimationCurve::Create());\n  std::unique_ptr<MockKeyframedTransformAnimationCurve> curve(\n      MockKeyframedTransformAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::TRANSFORM;\n\n  auto test_frame1 = TransformKeyframe::Create(fml::TimeDelta(), nullptr);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      TransformKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  curve->AddKeyframe(std::move(test_frame2));\n\n  auto test_frame3 =\n      TransformKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  curve->AddKeyframe(std::move(test_frame3));\n\n  auto keyframes = curve->GetKeyframes();\n  fml::TimeDelta value1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta value2 = fml::TimeDelta::FromSecondsF(0.5f);\n  fml::TimeDelta value3 = fml::TimeDelta::FromSecondsF(1.f);\n  fml::TimeDelta value4 = fml::TimeDelta::FromSecondsF(1.5f);\n  EXPECT_EQ(0, GetActiveKeyframe(std::move(keyframes), curve->scaled_duration(),\n                                 value1));\n  EXPECT_EQ(0, GetActiveKeyframe(std::move(keyframes), curve->scaled_duration(),\n                                 value2));\n  EXPECT_EQ(1, GetActiveKeyframe(std::move(keyframes), curve->scaled_duration(),\n                                 value3));\n  EXPECT_EQ(1, GetActiveKeyframe(std::move(keyframes), curve->scaled_duration(),\n                                 value4));\n}\n\nTEST_F(TransformAnimationCurveTest, CreateTransformKeyframe) {\n  auto test_frame = TransformKeyframe::Create(fml::TimeDelta(), nullptr);\n  EXPECT_EQ(test_frame->timing_function(), nullptr);\n  EXPECT_EQ(test_frame->Time(), fml::TimeDelta());\n}\n\nTEST_F(TransformAnimationCurveTest, GetTransformKeyframeValueInElement) {\n  auto element1 = manager->CreateFiberElement(\"view\");\n  auto id = lynx::tasm::CSSPropertyID::kPropertyIDTransform;\n  lynx::tasm::StyleMap output1;\n  lynx::tasm::CSSParserConfigs configs;\n  auto impl1 =\n      lepus::Value(\"translate3D(1rem, 2rem, 3rem) scale(0.1) rotate(10deg)\");\n  bool ret1 = lynx::tasm::UnitHandler::Process(id, impl1, output1, configs);\n  EXPECT_TRUE(ret1);\n  EXPECT_FALSE(output1.empty());\n  auto raw_value = output1[id];\n  element1->ConsumeStyle(output1, nullptr);\n  auto transform_in_element_1 =\n      TransformKeyframe::GetTransformKeyframeValueInElement(element1.get());\n  transforms::TransformOperations operations(element1.get(), raw_value);\n  EXPECT_EQ(operations.size(), static_cast<size_t>(3));\n  EXPECT_EQ(transform_in_element_1.size(), static_cast<size_t>(0));\n}\n\nTEST_F(TransformAnimationCurveTest, SetValue) {\n  auto test_element_ptr = InitTestElement();\n  auto test_element = test_element_ptr.get();\n  auto test_frame = TransformKeyframe::Create(fml::TimeDelta(), nullptr);\n  auto id = lynx::tasm::CSSPropertyID::kPropertyIDTransform;\n  lynx::tasm::StyleMap output;\n  lynx::tasm::CSSParserConfigs configs;\n  auto impl1 = lepus::Value(\"\");\n  lynx::tasm::UnitHandler::Process(id, impl1, output, configs);\n  EXPECT_FALSE(output[id].IsArray());\n  auto raw_value1 = output[id];\n  bool set_success1 = test_frame->SetValue(id, raw_value1, test_element);\n  EXPECT_EQ(set_success1, false);\n  EXPECT_EQ(test_frame->IsEmpty(), true);\n\n  auto impl2 =\n      lepus::Value(\"translate3D(1rem, 2rem, 3rem) scale(0.1) rotate(10deg)\");\n  bool ret2 = lynx::tasm::UnitHandler::Process(id, impl2, output, configs);\n  EXPECT_TRUE(ret2);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  auto raw_value2 = output[id];\n  bool set_success2 = test_frame->SetValue(id, raw_value2, test_element);\n  EXPECT_EQ(set_success2, true);\n  EXPECT_EQ(test_frame->IsEmpty(), false);\n}\n\nTEST_F(TransformAnimationCurveTest, CreateTransformAnimationCurve) {\n  auto test_curve1 = KeyframedTransformAnimationCurve::Create();\n  EXPECT_EQ(test_curve1->Type(), AnimationCurve::CurveType::UNSUPPORT);\n  EXPECT_EQ(test_curve1->scaled_duration(), 1.0);\n  EXPECT_EQ(test_curve1->timing_function(), nullptr);\n  EXPECT_EQ(test_curve1->get_keyframes_size(), 0);\n}\n\nTEST_F(TransformAnimationCurveTest, GetValue) {\n  std::unique_ptr<KeyframedTransformAnimationCurve> curve(\n      KeyframedTransformAnimationCurve::Create());\n  curve->type_ = AnimationCurve::CurveType::TRANSFORM;\n  auto test_element_ptr = InitTestElement();\n  auto test_element = test_element_ptr.get();\n  curve->SetElement(test_element);\n\n  auto test_frame1 = TransformKeyframe::Create(fml::TimeDelta(), nullptr);\n  auto id = lynx::tasm::CSSPropertyID::kPropertyIDTransform;\n  lynx::tasm::CSSParserConfigs configs;\n  auto impl1 = lepus::Value(\"scale(0.1)\");\n  lynx::tasm::StyleMap output1;\n  bool ret1 = lynx::tasm::UnitHandler::Process(id, impl1, output1, configs);\n  EXPECT_TRUE(ret1);\n  EXPECT_FALSE(output1.empty());\n  EXPECT_FALSE(output1.find(id) == output1.end());\n  EXPECT_TRUE(output1[id].IsArray());\n  auto raw_value1 = output1[id];\n  bool set_success1 = test_frame1->SetValue(id, raw_value1, test_element);\n  EXPECT_EQ(set_success1, true);\n  EXPECT_EQ(test_frame1->IsEmpty(), false);\n  curve->AddKeyframe(std::move(test_frame1));\n\n  auto test_frame2 =\n      TransformKeyframe::Create(fml::TimeDelta::FromSecondsF(1.0), nullptr);\n  auto impl2 = lepus::Value(\"scale(1.1)\");\n  lynx::tasm::StyleMap output2;\n  bool ret2 = lynx::tasm::UnitHandler::Process(id, impl2, output2, configs);\n  EXPECT_TRUE(ret2);\n  EXPECT_FALSE(output2.empty());\n  EXPECT_FALSE(output2.find(id) == output2.end());\n  EXPECT_TRUE(output2[id].IsArray());\n  auto raw_value2 = output2[id];\n  bool set_success2 = test_frame2->SetValue(id, raw_value2, test_element);\n  EXPECT_EQ(set_success2, true);\n  EXPECT_EQ(test_frame2->IsEmpty(), false);\n  curve->AddKeyframe(std::move(test_frame2));\n\n  fml::TimeDelta test_time1 = fml::TimeDelta::FromSecondsF(0.f);\n  fml::TimeDelta test_time2 = fml::TimeDelta::FromSecondsF(0.5f);\n  fml::TimeDelta test_time3 = fml::TimeDelta::FromSecondsF(1.f);\n  auto test_value1 = curve->GetValue(test_time1)\n                         .GetValue()\n                         .Array()\n                         ->get(0)\n                         .Array()\n                         .strongify();\n  EXPECT_EQ(test_value1->get(0).Number(),\n            (int)starlight::TransformType::kScale);\n  EXPECT_FLOAT_EQ(test_value1->get(1).Number(), 0.1);\n\n  auto test_value2 = curve->GetValue(test_time2)\n                         .GetValue()\n                         .Array()\n                         ->get(0)\n                         .Array()\n                         .strongify();\n  EXPECT_EQ(test_value2->get(0).Number(),\n            (int)starlight::TransformType::kScale);\n  EXPECT_FLOAT_EQ(test_value2->get(1).Number(), 0.6);\n\n  auto test_value3 = curve->GetValue(test_time3)\n                         .GetValue()\n                         .Array()\n                         ->get(0)\n                         .Array()\n                         .strongify();\n  EXPECT_EQ(test_value3->get(0).Number(),\n            (int)starlight::TransformType::kScale);\n  EXPECT_FLOAT_EQ(test_value3->get(1).Number(), 1.1);\n}\n\nTEST_F(TransformAnimationCurveTest, MakeEmptyKeyframe) {\n  auto test_curve = KeyframedTransformAnimationCurve::Create();\n  auto test_frame1 =\n      TransformKeyframe::Create(fml::TimeDelta::FromSecondsF(2.0), nullptr);\n  auto test_frame2 =\n      test_curve->KeyframedTransformAnimationCurve::MakeEmptyKeyframe(\n          fml::TimeDelta::FromSecondsF(2.0));\n  EXPECT_EQ(test_frame1->Time(), test_frame2->Time());\n  EXPECT_EQ(test_frame1->timing_function(), test_frame2->timing_function());\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/utils/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../testing/test.gni\")\nimport(\"../../Lynx.gni\")\n\nanimation_utils_shared_sources = [\n  \"cubic_bezier.cc\",\n  \"cubic_bezier.h\",\n  \"timing_function.cc\",\n  \"timing_function.h\",\n]\n\nlynx_core_source_set(\"animation_utils\") {\n  sources = animation_utils_shared_sources\n}\n\nunittest_set(\"animation_utils_testset\") {\n  testonly = true\n\n  public_deps = [ \":animation_utils\" ]\n\n  deps = [\n    \"../../renderer:tasm\",\n    \"../../renderer/dom:renderer_dom\",\n    \"../../runtime/lepus/bindings:bindings\",\n    \"../../shell/testing:mock_tasm_delegate_testset\",\n  ]\n\n  sources = [\n    \"cubic_bezier_unittest.cc\",\n    \"timing_function_unittest.cc\",\n  ]\n}\n\nunittest_exec(\"animation_utils_unittests_exec\") {\n  testonly = true\n  sources = []\n  deps = [\n    \":animation_utils\",\n    \":animation_utils_testset\",\n    \"../../renderer/dom:dom\",\n  ]\n}\n\ngroup(\"animation_utils_tests\") {\n  testonly = true\n  deps = [ \":animation_utils_unittests_exec\" ]\n  public_deps = [ \":animation_utils_testset\" ]\n}\n"
  },
  {
    "path": "core/animation/utils/cubic_bezier.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/utils/cubic_bezier.h\"\n\n#include <algorithm>\n#include <cmath>\n\nnamespace lynx {\nnamespace animation {\n\nnamespace {\n\nconst int kMaxNewtonIterations = 4;\n\n}  // namespace\n\nstatic const double kBezierEpsilon = 1e-7;\n\n// To be replaced with std::clamp() from C++17, someday.\ntemplate <class T>\nconstexpr const T& ClampToRange(const T& value, const T& min, const T& max) {\n  return std::min(std::max(value, min), max);\n}\n\ntemplate <typename T>\nconstexpr bool IsApproximatelyEqual(T lhs, T rhs, T tolerance) {\n  static_assert(std::is_arithmetic<T>::value, \"Argument must be arithmetic\");\n  return std::abs(rhs - lhs) <= tolerance;\n}\n\nCubicBezier::CubicBezier(double p1x, double p1y, double p2x, double p2y) {\n  InitCoefficients(p1x, p1y, p2x, p2y);\n  InitGradients(p1x, p1y, p2x, p2y);\n  InitRange(p1y, p2y);\n  InitSpline();\n}\n\nCubicBezier::CubicBezier(const CubicBezier& other) = default;\n\nvoid CubicBezier::InitCoefficients(double p1x, double p1y, double p2x,\n                                   double p2y) {\n  // Calculate the polynomial coefficients, implicit first and last control\n  // points are (0,0) and (1,1).\n  cx_ = 3.0 * p1x;\n  bx_ = 3.0 * (p2x - p1x) - cx_;\n  ax_ = 1.0 - cx_ - bx_;\n\n  cy_ = 3.0 * p1y;\n  by_ = 3.0 * (p2y - p1y) - cy_;\n  ay_ = 1.0 - cy_ - by_;\n\n#ifndef NDEBUG\n  // Bezier curves with x-coordinates outside the range [0,1] for internal\n  // control points may have multiple values for t for a given value of x.\n  // In this case, calls to SolveCurveX may produce ambiguous results.\n  monotonically_increasing_ = p1x >= 0 && p1x <= 1 && p2x >= 0 && p2x <= 1;\n#endif\n}\n\nvoid CubicBezier::InitGradients(double p1x, double p1y, double p2x,\n                                double p2y) {\n  // End-point gradients are used to calculate timing function results\n  // outside the range [0, 1].\n  //\n  // There are four possibilities for the gradient at each end:\n  // (1) the closest control point is not horizontally coincident with regard to\n  //     (0, 0) or (1, 1). In this case the line between the end point and\n  //     the control point is tangent to the bezier at the end point.\n  // (2) the closest control point is coincident with the end point. In\n  //     this case the line between the end point and the far control\n  //     point is tangent to the bezier at the end point.\n  // (3) both internal control points are coincident with an endpoint. There\n  //     are two special case that fall into this category:\n  //     CubicBezier(0, 0, 0, 0) and CubicBezier(1, 1, 1, 1). Both are\n  //     equivalent to linear.\n  // (4) the closest control point is horizontally coincident with the end\n  //     point, but vertically distinct. In this case the gradient at the\n  //     end point is Infinite. However, this causes issues when\n  //     interpolating. As a result, we break down to a simple case of\n  //     0 gradient under these conditions.\n\n  if (p1x > 0)\n    start_gradient_ = p1y / p1x;\n  else if (!p1y && p2x > 0)\n    start_gradient_ = p2y / p2x;\n  else if (!p1y && !p2y)\n    start_gradient_ = 1;\n  else\n    start_gradient_ = 0;\n\n  if (p2x < 1)\n    end_gradient_ = (p2y - 1) / (p2x - 1);\n  else if (p2y == 1 && p1x < 1)\n    end_gradient_ = (p1y - 1) / (p1x - 1);\n  else if (p2y == 1 && p1y == 1)\n    end_gradient_ = 1;\n  else\n    end_gradient_ = 0;\n}\n\n// This works by taking taking the derivative of the cubic bezier, on the y\n// axis. We can then solve for where the derivative is zero to find the min\n// and max distance along the line. We the have to solve those in terms of time\n// rather than distance on the x-axis\nvoid CubicBezier::InitRange(double p1y, double p2y) {\n  range_min_ = 0;\n  range_max_ = 1;\n  if (0 <= p1y && p1y < 1 && 0 <= p2y && p2y <= 1) return;\n\n  const double epsilon = kBezierEpsilon;\n\n  // Represent the function's derivative in the form at^2 + bt + c\n  // as in sampleCurveDerivativeY.\n  // (Technically this is (dy/dt)*(1/3), which is suitable for finding zeros\n  // but does not actually give the slope of the curve.)\n  const double a = 3.0 * ay_;\n  const double b = 2.0 * by_;\n  const double c = cy_;\n\n  // Check if the derivative is constant.\n  if (std::abs(a) < epsilon && std::abs(b) < epsilon) return;\n\n  // Zeros of the function's derivative.\n  double t1 = 0;\n  double t2 = 0;\n\n  if (std::abs(a) < epsilon) {\n    // The function's derivative is linear.\n    t1 = -c / b;\n  } else {\n    // The function's derivative is a quadratic. We find the zeros of this\n    // quadratic using the quadratic formula.\n    double discriminant = b * b - 4 * a * c;\n    if (discriminant < 0) return;\n    double discriminant_sqrt = sqrt(discriminant);\n    t1 = (-b + discriminant_sqrt) / (2 * a);\n    t2 = (-b - discriminant_sqrt) / (2 * a);\n  }\n\n  double sol1 = 0;\n  double sol2 = 0;\n\n  // If the solution is in the range [0,1] then we include it, otherwise we\n  // ignore it.\n\n  // An interesting fact about these beziers is that they are only\n  // actually evaluated in [0,1]. After that we take the tangent at that point\n  // and linearly project it out.\n  if (0 < t1 && t1 < 1) sol1 = SampleCurveY(t1);\n\n  if (0 < t2 && t2 < 1) sol2 = SampleCurveY(t2);\n\n  range_min_ = std::min({range_min_, sol1, sol2});\n  range_max_ = std::max({range_max_, sol1, sol2});\n}\n\nvoid CubicBezier::InitSpline() {\n  double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1);\n  for (int i = 0; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) {\n    spline_samples_[i] = SampleCurveX(i * delta_t);\n  }\n}\n\ndouble CubicBezier::GetDefaultEpsilon() { return kBezierEpsilon; }\n\ndouble CubicBezier::SolveCurveX(double x, double epsilon) const {\n  // assert(x >= 0.0);\n  // assert(x <= 1.0);\n\n  double t0 = 0.0;\n  double t1 = 0.0;\n  double t2 = x;\n  double x2 = 0.0;\n  double d2 = 0.0;\n  int i = 0;\n\n  // Linear interpolation of spline curve for initial guess.\n  double delta_t = 1.0 / (CUBIC_BEZIER_SPLINE_SAMPLES - 1);\n  for (i = 1; i < CUBIC_BEZIER_SPLINE_SAMPLES; i++) {\n    if (x <= spline_samples_[i]) {\n      t1 = delta_t * i;\n      t0 = t1 - delta_t;\n      t2 = t0 + (t1 - t0) * (x - spline_samples_[i - 1]) /\n                    (spline_samples_[i] - spline_samples_[i - 1]);\n      break;\n    }\n  }\n\n  // Perform a few iterations of Newton's method -- normally very fast.\n  // See https://en.wikipedia.org/wiki/Newton%27s_method.\n  double newton_epsilon = std::min(kBezierEpsilon, epsilon);\n  for (i = 0; i < kMaxNewtonIterations; i++) {\n    x2 = SampleCurveX(t2) - x;\n    if (fabs(x2) < newton_epsilon) return t2;\n    d2 = SampleCurveDerivativeX(t2);\n    if (fabs(d2) < kBezierEpsilon) break;\n    t2 = t2 - x2 / d2;\n  }\n  if (fabs(x2) < epsilon) return t2;\n\n  // Fall back to the bisection method for reliability.\n  while (t0 < t1) {\n    x2 = SampleCurveX(t2);\n    if (fabs(x2 - x) < epsilon) return t2;\n    if (x > x2)\n      t0 = t2;\n    else\n      t1 = t2;\n    t2 = (t1 + t0) * .5;\n  }\n\n  // Failure.\n  return t2;\n}\n\ndouble CubicBezier::Solve(double x) const {\n  return SolveWithEpsilon(x, kBezierEpsilon);\n}\n\ndouble CubicBezier::SlopeWithEpsilon(double x, double epsilon) const {\n  x = ClampToRange(x, 0.0, 1.0);\n  double t = SolveCurveX(x, epsilon);\n  double dx = SampleCurveDerivativeX(t);\n  double dy = SampleCurveDerivativeY(t);\n  return dy / dx;\n}\n\ndouble CubicBezier::Slope(double x) const {\n  return SlopeWithEpsilon(x, kBezierEpsilon);\n}\n\ndouble CubicBezier::GetX1() const { return cx_ / 3.0; }\n\ndouble CubicBezier::GetY1() const { return cy_ / 3.0; }\n\ndouble CubicBezier::GetX2() const { return (bx_ + cx_) / 3.0 + GetX1(); }\n\ndouble CubicBezier::GetY2() const { return (by_ + cy_) / 3.0 + GetY1(); }\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/utils/cubic_bezier.h",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_UTILS_CUBIC_BEZIER_H_\n#define CORE_ANIMATION_UTILS_CUBIC_BEZIER_H_\n\nnamespace lynx {\nnamespace animation {\n#define CUBIC_BEZIER_SPLINE_SAMPLES 11\n\nclass CubicBezier {\n public:\n  CubicBezier(double p1x, double p1y, double p2x, double p2y);\n  CubicBezier(const CubicBezier& other);\n\n  double SampleCurveX(double t) const {\n    // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.\n    return ((ax_ * t + bx_) * t + cx_) * t;\n  }\n\n  double SampleCurveY(double t) const {\n    return ((ay_ * t + by_) * t + cy_) * t;\n  }\n\n  double SampleCurveDerivativeX(double t) const {\n    return (3.0 * ax_ * t + 2.0 * bx_) * t + cx_;\n  }\n\n  double SampleCurveDerivativeY(double t) const {\n    return (3.0 * ay_ * t + 2.0 * by_) * t + cy_;\n  }\n\n  static double GetDefaultEpsilon();\n\n  // Given an x value, find a parametric value it came from.\n  // x must be in [0, 1] range. Doesn't use gradients.\n  double SolveCurveX(double x, double epsilon) const;\n\n  // Evaluates y at the given x with default epsilon.\n  double Solve(double x) const;\n  // Evaluates y at the given x. The epsilon parameter provides a hint as to the\n  // required accuracy and is not guaranteed. Uses gradients if x is\n  // out of [0, 1] range.\n  double SolveWithEpsilon(double x, double epsilon) const {\n    if (x < 0.0) return 0.0 + start_gradient_ * x;\n    if (x > 1.0) return 1.0 + end_gradient_ * (x - 1.0);\n    return SampleCurveY(SolveCurveX(x, epsilon));\n  }\n\n  // Returns an approximation of dy/dx at the given x with default epsilon.\n  double Slope(double x) const;\n  // Returns an approximation of dy/dx at the given x.\n  // Clamps x to range [0, 1].\n  double SlopeWithEpsilon(double x, double epsilon) const;\n\n  // These getters are used rarely. We reverse compute them from coefficients.\n  // See CubicBezier::InitCoefficients. The speed has been traded for memory.\n  double GetX1() const;\n  double GetY1() const;\n  double GetX2() const;\n  double GetY2() const;\n\n  // Gets the bezier's minimum y value in the interval [0, 1].\n  double range_min() const { return range_min_; }\n  // Gets the bezier's maximum y value in the interval [0, 1].\n  double range_max() const { return range_max_; }\n\n private:\n  void InitCoefficients(double p1x, double p1y, double p2x, double p2y);\n  void InitGradients(double p1x, double p1y, double p2x, double p2y);\n  void InitRange(double p1y, double p2y);\n  void InitSpline();\n\n  double ax_;\n  double bx_;\n  double cx_;\n\n  double ay_;\n  double by_;\n  double cy_;\n\n  double start_gradient_;\n  double end_gradient_;\n\n  double range_min_;\n  double range_max_;\n\n  double spline_samples_[CUBIC_BEZIER_SPLINE_SAMPLES];\n\n#ifndef NDEBUG\n  // Guard against attempted to solve for t given x in the event that the curve\n  // may have multiple values for t for some values of x in [0, 1].\n  bool monotonically_increasing_;\n#endif\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_UTILS_CUBIC_BEZIER_H_\n"
  },
  {
    "path": "core/animation/utils/cubic_bezier_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/utils/cubic_bezier.h\"\n\n#include <algorithm>\n#include <cmath>\n#include <memory>\n\n#include \"base/include/log/logging.h\"\n#include \"core/animation/css_keyframe_manager.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframe_model.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/css/transforms/transform_operations.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/animation_data.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace tasm {\nnamespace testing {\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass CubicBezierTest : public ::testing::Test {\n public:\n  CubicBezierTest() {}\n  ~CubicBezierTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>\n      tasm_mediator;\n  fml::RefPtr<lynx::tasm::Element> element_;\n\n  void SetUp() override {\n    ::lynx::tasm::LynxEnvConfig lynx_env_config(\n        kWidth, kHeight, kDefaultLayoutsUnitPerPx,\n        kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<::lynx::tasm::MockPaintingContext>(),\n        tasm_mediator.get(), lynx_env_config);\n    auto config = std::make_shared<::lynx::tasm::PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n  }\n\n  starlight::AnimationData InitAnimationData(\n      starlight::TimingFunctionData timing_func) {\n    starlight::AnimationData data;\n    data.name = base::String(\"test\");\n    data.duration = 2000;\n    data.delay = 0;\n    data.timing_func = timing_func;\n    data.iteration_count = 1;\n    data.fill_mode = starlight::AnimationFillModeType::kBoth;\n    data.direction = starlight::AnimationDirectionType::kNormal;\n    data.play_state = starlight::AnimationPlayStateType::kRunning;\n    return data;\n  }\n};\n\nTEST_F(CubicBezierTest, Create) {\n  auto test_cubic = CubicBezier(0.0, 0.5, 1.0, 0.5);\n\n  double epsilon = 1e-7;\n  bool flag = abs(test_cubic.GetX1() - 0.0) < epsilon &&\n              abs(test_cubic.GetY1() - 0.5) < epsilon &&\n              abs(test_cubic.GetX2() - 1.0) < epsilon &&\n              abs(test_cubic.GetY2() - 0.5) < epsilon;\n  EXPECT_TRUE(flag);\n}\n\nTEST_F(CubicBezierTest, GetDefaultEpsilon) {\n  double epsilon = 1e-7;\n  bool flag = abs(CubicBezier::GetDefaultEpsilon() - 1e-7) < epsilon;\n  EXPECT_TRUE(flag);\n}\n\nTEST_F(CubicBezierTest, SolveCurveX) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-7;\n\n  double result1 = test_cubic.SolveCurveX(0.2, 1e-7);\n  bool flag1 = abs(result1 - 0.3269672002845937) - epsilon;\n  EXPECT_TRUE(flag1);\n\n  double result2 = test_cubic.SolveCurveX(0.5, 1e-7);\n  bool flag2 = abs(result2 - 0.6995724416969088) - epsilon;\n  EXPECT_TRUE(flag2);\n\n  double result3 = test_cubic.SolveCurveX(0.8, 1e-7);\n  bool flag3 = abs(result3 - 0.90190872048750559) - epsilon;\n  EXPECT_TRUE(flag3);\n\n  double result4 = test_cubic.SolveCurveX(1.0, 1e-7);\n  bool flag4 = abs(result4 - 1.0) - epsilon;\n  EXPECT_TRUE(flag4);\n}\n\nTEST_F(CubicBezierTest, Solve) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-7;\n\n  double result1 = test_cubic.Solve(0.2);\n  bool flag1 = abs(result1 - 0.2952443342678806) - epsilon;\n  EXPECT_TRUE(flag1);\n\n  double result2 = test_cubic.Solve(0.5);\n  bool flag2 = abs(result2 - 0.80240339105984371) - epsilon;\n  EXPECT_TRUE(flag2);\n\n  double result3 = test_cubic.Solve(0.8);\n  bool flag3 = abs(result3 - 0.97562537385835967) - epsilon;\n  EXPECT_TRUE(flag3);\n\n  double result4 = test_cubic.Solve(1.0);\n  bool flag4 = abs(result4 - 1.0) - epsilon;\n  EXPECT_TRUE(flag4);\n}\n\nTEST_F(CubicBezierTest, SampleCurveX) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-7;\n  double result = test_cubic.SampleCurveX(0.5);\n  bool flag = abs(result - 0.3125) < epsilon;\n  EXPECT_TRUE(flag);\n}\n\nTEST_F(CubicBezierTest, SampleCurveY) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-7;\n  double result = test_cubic.SampleCurveY(0.5);\n  bool flag = abs(result - 0.53750000000000009) < epsilon;\n  EXPECT_TRUE(flag);\n}\n\nTEST_F(CubicBezierTest, SampleCurveDerivativeX) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-7;\n  double result = test_cubic.SampleCurveDerivativeX(0.5);\n  bool flag = abs(result - 0.75) < epsilon;\n  EXPECT_TRUE(flag);\n}\n\nTEST_F(CubicBezierTest, SampleCurveDerivativeY) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-7;\n  double result = test_cubic.SampleCurveDerivativeY(0.5);\n  bool flag = abs(result - 1.425) < epsilon;\n  EXPECT_TRUE(flag);\n}\n\nTEST_F(CubicBezierTest, SolveWithEpsilon) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-7;\n\n  double result1 = test_cubic.SolveWithEpsilon(0.2, 1e-7);\n  bool flag1 = abs(result1 - 0.2952443342678806) - epsilon;\n  EXPECT_TRUE(flag1);\n\n  double result2 = test_cubic.SolveWithEpsilon(0.5, 1e-7);\n  bool flag2 = abs(result2 - 0.80240339105984371) - epsilon;\n  EXPECT_TRUE(flag2);\n\n  double result3 = test_cubic.SolveWithEpsilon(0.8, 1e-7);\n  bool flag3 = abs(result3 - 0.97562537385835967) - epsilon;\n  EXPECT_TRUE(flag3);\n\n  double result4 = test_cubic.SolveWithEpsilon(1.0, 1e-7);\n  bool flag4 = abs(result4 - 1.0) - epsilon;\n  EXPECT_TRUE(flag4);\n}\n\nTEST_F(CubicBezierTest, SlopeWithEpsilon) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-7;\n\n  double result1 = test_cubic.SlopeWithEpsilon(0.2, 1e-7);\n  bool flag1 = abs(result1 - 2.2820580674389523) - epsilon;\n  EXPECT_TRUE(flag1);\n\n  double result2 = test_cubic.SlopeWithEpsilon(0.5, 1e-7);\n  bool flag2 = abs(result2 - 0.99414243958918846) - epsilon;\n  EXPECT_TRUE(flag2);\n\n  double result3 = test_cubic.SlopeWithEpsilon(0.8, 1e-7);\n  bool flag3 = abs(result3 - 0.26156898519284688) - epsilon;\n  EXPECT_TRUE(flag3);\n\n  double result4 = test_cubic.SlopeWithEpsilon(1.0, 1e-7);\n  bool flag4 = abs(result4 - 0.0) - epsilon;\n  EXPECT_TRUE(flag4);\n}\n\nTEST_F(CubicBezierTest, Slope) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-7;\n\n  double result1 = test_cubic.Slope(0.2);\n  bool flag1 = abs(result1 - 2.2820580674389523) - epsilon;\n  EXPECT_TRUE(flag1);\n\n  double result2 = test_cubic.Slope(0.5);\n  bool flag2 = abs(result2 - 0.99414243958918846) - epsilon;\n  EXPECT_TRUE(flag2);\n\n  double result3 = test_cubic.Slope(0.8);\n  bool flag3 = abs(result3 - 0.26156898519284688) - epsilon;\n  EXPECT_TRUE(flag3);\n\n  double result4 = test_cubic.Slope(1.0);\n  bool flag4 = abs(result4 - 0.0) - epsilon;\n  EXPECT_TRUE(flag4);\n}\n\nTEST_F(CubicBezierTest, Get) {\n  auto test_cubic = CubicBezier(0.25, 0.1, 0.25, 1.0);\n\n  double epsilon = 1e-6;\n  bool flag1 = abs(test_cubic.GetX1() - 0.25) < epsilon;\n  bool flag2 = abs(test_cubic.GetY1() - 0.1) < epsilon;\n  bool flag3 = abs(test_cubic.GetX2() - 0.25) < epsilon;\n  bool flag4 = abs(test_cubic.GetY2() - 1.0) < epsilon;\n  EXPECT_TRUE(flag1 && flag2 && flag3 && flag4);\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/utils/timing_function.cc",
    "content": "// Copyright (c) 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/utils/timing_function.h\"\n\n#include <cmath>\n\nnamespace lynx {\nnamespace animation {\n\nTimingFunction::TimingFunction() = default;\n\nTimingFunction::~TimingFunction() = default;\n\nstd::unique_ptr<TimingFunction> TimingFunction::MakeTimingFunction(\n    const starlight::AnimationData* animation_data) {\n  auto timing_function_data = animation_data->timing_func;\n  return MakeTimingFunction(timing_function_data);\n}\n\nstd::unique_ptr<TimingFunction> TimingFunction::MakeTimingFunction(\n    const starlight::TimingFunctionData& timing_function_data) {\n  std::unique_ptr<TimingFunction> timing;\n  auto type = timing_function_data.timing_func;\n  if (type == starlight::TimingFunctionType::kLinear) {\n    timing = std::make_unique<LinearTimingFunction>();\n  } else if (type == starlight::TimingFunctionType::kEaseIn) {\n    timing = CubicBezierTimingFunction::CreatePreset(\n        CubicBezierTimingFunction::EaseType::EASE_IN);\n  } else if (type == starlight::TimingFunctionType::kEaseOut) {\n    timing = CubicBezierTimingFunction::CreatePreset(\n        CubicBezierTimingFunction::EaseType::EASE_OUT);\n  } else if (type == starlight::TimingFunctionType::kEaseInEaseOut) {\n    timing = CubicBezierTimingFunction::CreatePreset(\n        CubicBezierTimingFunction::EaseType::EASE_IN_OUT);\n  } else if (type == starlight::TimingFunctionType::kCubicBezier) {\n    timing = CubicBezierTimingFunction::Create(\n        timing_function_data.x1, timing_function_data.y1,\n        timing_function_data.x2, timing_function_data.y2);\n  } else if (type == starlight::TimingFunctionType::kSteps) {\n    timing = StepsTimingFunction::Create(timing_function_data.x1,\n                                         timing_function_data.steps_type);\n  } else {\n    timing = std::make_unique<LinearTimingFunction>();\n  }\n\n  return timing;\n}\n\nstd::unique_ptr<CubicBezierTimingFunction>\nCubicBezierTimingFunction::CreatePreset(EaseType ease_type) {\n  // These numbers come from\n  // http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag.\n  switch (ease_type) {\n    case EaseType::EASE:\n      return std::unique_ptr<CubicBezierTimingFunction>(\n          new CubicBezierTimingFunction(ease_type, 0.25, 0.1, 0.25, 1.0));\n    case EaseType::EASE_IN:\n      return std::unique_ptr<CubicBezierTimingFunction>(\n          new CubicBezierTimingFunction(ease_type, 0.42, 0.0, 1.0, 1.0));\n    case EaseType::EASE_OUT:\n      return std::unique_ptr<CubicBezierTimingFunction>(\n          new CubicBezierTimingFunction(ease_type, 0.0, 0.0, 0.58, 1.0));\n    case EaseType::EASE_IN_OUT:\n      return std::unique_ptr<CubicBezierTimingFunction>(\n          new CubicBezierTimingFunction(ease_type, 0.42, 0.0, 0.58, 1));\n    default:\n      //      NOTREACHED();\n      return nullptr;\n  }\n}\nstd::unique_ptr<CubicBezierTimingFunction> CubicBezierTimingFunction::Create(\n    double x1, double y1, double x2, double y2) {\n  return std::unique_ptr<CubicBezierTimingFunction>(\n      new CubicBezierTimingFunction(EaseType::CUSTOM, x1, y1, x2, y2));\n}\n\nCubicBezierTimingFunction::CubicBezierTimingFunction(EaseType ease_type,\n                                                     double x1, double y1,\n                                                     double x2, double y2)\n    : bezier_(x1, y1, x2, y2), ease_type_(ease_type) {}\n\nCubicBezierTimingFunction::~CubicBezierTimingFunction() = default;\n\nTimingFunction::Type CubicBezierTimingFunction::GetType() const {\n  return Type::CUBIC_BEZIER;\n}\n\ndouble CubicBezierTimingFunction::GetValue(double x) const {\n  return bezier_.Solve(x);\n}\n\ndouble CubicBezierTimingFunction::Velocity(double x) const {\n  return bezier_.Slope(x);\n}\n\nstd::unique_ptr<TimingFunction> CubicBezierTimingFunction::Clone() const {\n  return std::unique_ptr<TimingFunction>(new CubicBezierTimingFunction(*this));\n}\n\nstd::unique_ptr<StepsTimingFunction> StepsTimingFunction::Create(\n    int steps, starlight::StepsType step_position) {\n  return std::unique_ptr<StepsTimingFunction>(\n      new StepsTimingFunction(steps, step_position));\n}\n\nStepsTimingFunction::StepsTimingFunction(int steps,\n                                         starlight::StepsType step_position)\n    : steps_(steps), step_position_(step_position) {}\n\nStepsTimingFunction::~StepsTimingFunction() = default;\n\nTimingFunction::Type StepsTimingFunction::GetType() const {\n  return Type::STEPS;\n}\n\ndouble StepsTimingFunction::GetValue(double t) const {\n  return GetPreciseValue(t, TimingFunction::LimitDirection::RIGHT);\n}\n\nstd::unique_ptr<TimingFunction> StepsTimingFunction::Clone() const {\n  return std::unique_ptr<TimingFunction>(new StepsTimingFunction(*this));\n}\n\ndouble StepsTimingFunction::Velocity(double x) const { return 0; }\n\ndouble StepsTimingFunction::GetPreciseValue(double t,\n                                            LimitDirection direction) const {\n  const double steps = static_cast<double>(steps_);\n  double current_step = std::floor((steps * t) + GetStepsStartOffset());\n  // Adjust step if using a left limit at a discontinuous step boundary.\n  if (direction == LimitDirection::LEFT &&\n      steps * t - std::floor(steps * t) == 0) {\n    current_step -= 1;\n  }\n  // Jumps may differ from steps based on the number of end-point\n  // discontinuities, which may be 0, 1 or 2.\n  int jumps = NumberOfJumps();\n  if (t >= 0 && current_step < 0) current_step = 0;\n  if (t <= 1 && current_step > jumps) current_step = jumps;\n  return current_step / jumps;\n}\n\nint StepsTimingFunction::NumberOfJumps() const {\n  switch (step_position_) {\n    case starlight::StepsType::kEnd:\n    case starlight::StepsType::kStart:\n      return steps_;\n\n    case starlight::StepsType::kJumpBoth:\n      return steps_ + 1;\n\n    case starlight::StepsType::kJumpNone:\n      // assert(steps_ > 1);\n      return steps_ - 1;\n\n    default:\n      return steps_;\n  }\n}\n\nfloat StepsTimingFunction::GetStepsStartOffset() const {\n  switch (step_position_) {\n    case starlight::StepsType::kJumpBoth:\n    case starlight::StepsType::kStart:\n      return 1;\n\n    case starlight::StepsType::kEnd:\n    case starlight::StepsType::kJumpNone:\n      return 0;\n\n    default:\n      return 1;\n  }\n}\n\nstd::unique_ptr<LinearTimingFunction> LinearTimingFunction::Create() {\n  return std::make_unique<LinearTimingFunction>();\n}\n\nLinearTimingFunction::LinearTimingFunction() = default;\n\nLinearTimingFunction::~LinearTimingFunction() = default;\n\nTimingFunction::Type LinearTimingFunction::GetType() const {\n  return Type::LINEAR;\n}\n\nstd::unique_ptr<TimingFunction> LinearTimingFunction::Clone() const {\n  return std::make_unique<LinearTimingFunction>(*this);\n}\n\ndouble LinearTimingFunction::Velocity(double x) const { return 0; }\n\ndouble LinearTimingFunction::GetValue(double t) const { return t; }\n\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/animation/utils/timing_function.h",
    "content": "// Copyright 2012 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_ANIMATION_UTILS_TIMING_FUNCTION_H_\n#define CORE_ANIMATION_UTILS_TIMING_FUNCTION_H_\n#include <memory>\n\n#include \"core/animation/utils/cubic_bezier.h\"\n#include \"core/style/animation_data.h\"\n\n// See http://www.w3.org/TR/css3-transitions/.\nnamespace lynx {\nnamespace animation {\n\nclass TimingFunction {\n public:\n  virtual ~TimingFunction();\n\n  TimingFunction& operator=(const TimingFunction&) = delete;\n\n  // Note that LINEAR is a nullptr TimingFunction (for now).\n  enum class Type { LINEAR, CUBIC_BEZIER, STEPS };\n\n  // Which limit to apply at a discontinuous boundary.\n  enum class LimitDirection { LEFT, RIGHT };\n\n  virtual Type GetType() const = 0;\n  virtual double GetValue(double t) const = 0;\n  virtual double Velocity(double time) const = 0;\n  virtual std::unique_ptr<TimingFunction> Clone() const = 0;\n\n  static std::unique_ptr<TimingFunction> MakeTimingFunction(\n      const starlight::AnimationData* animation_data);\n\n  static std::unique_ptr<TimingFunction> MakeTimingFunction(\n      const starlight::TimingFunctionData& timing_function_data);\n\n protected:\n  TimingFunction();\n};\n\nclass CubicBezierTimingFunction : public TimingFunction {\n public:\n  enum class EaseType { EASE, EASE_IN, EASE_OUT, EASE_IN_OUT, CUSTOM };\n\n  static std::unique_ptr<CubicBezierTimingFunction> CreatePreset(\n      EaseType ease_type);\n  static std::unique_ptr<CubicBezierTimingFunction> Create(double x1, double y1,\n                                                           double x2,\n                                                           double y2);\n  ~CubicBezierTimingFunction() override;\n\n  CubicBezierTimingFunction& operator=(const CubicBezierTimingFunction&) =\n      delete;\n\n  // TimingFunction implementation.\n  Type GetType() const override;\n  double GetValue(double time) const override;\n  double Velocity(double time) const override;\n  std::unique_ptr<TimingFunction> Clone() const override;\n\n  EaseType ease_type() const { return ease_type_; }\n  const CubicBezier& bezier() const { return bezier_; }\n\n private:\n  CubicBezierTimingFunction(EaseType ease_type, double x1, double y1, double x2,\n                            double y2);\n\n  CubicBezier bezier_;\n  EaseType ease_type_;\n};\n\nclass StepsTimingFunction : public TimingFunction {\n public:\n  static std::unique_ptr<StepsTimingFunction> Create(\n      int steps, starlight::StepsType step_position);\n  ~StepsTimingFunction() override;\n\n  StepsTimingFunction& operator=(const StepsTimingFunction&) = delete;\n\n  // TimingFunction implementation.\n  Type GetType() const override;\n  double GetValue(double t) const override;\n  std::unique_ptr<TimingFunction> Clone() const override;\n  double Velocity(double time) const override;\n\n  int steps() const { return steps_; }\n  starlight::StepsType step_position() const { return step_position_; }\n  double GetPreciseValue(double t, LimitDirection limit_direction) const;\n\n private:\n  StepsTimingFunction(int steps, starlight::StepsType step_position);\n\n  // The number of jumps is the number of discontinuities in the timing\n  // function. There is a subtle distinction between the number of steps and\n  // jumps. The number of steps is the number of intervals in the timing\n  // function. The number of jumps differs from the number of steps when either\n  // both or neither end point has a discontinuity.\n  // https://drafts.csswg.org/css-easing-1/#step-easing-functions\n  int NumberOfJumps() const;\n\n  float GetStepsStartOffset() const;\n\n  int steps_;\n  starlight::StepsType step_position_;\n};\n\nclass LinearTimingFunction : public TimingFunction {\n public:\n  static std::unique_ptr<LinearTimingFunction> Create();\n  LinearTimingFunction();\n  ~LinearTimingFunction() override;\n\n  // TimingFunction implementation.\n  Type GetType() const override;\n  double GetValue(double t) const override;\n  std::unique_ptr<TimingFunction> Clone() const override;\n  double Velocity(double time) const override;\n\n private:\n};\n\n}  // namespace animation\n}  // namespace lynx\n\n#endif  // CORE_ANIMATION_UTILS_TIMING_FUNCTION_H_\n"
  },
  {
    "path": "core/animation/utils/timing_function_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/animation/utils/timing_function.h\"\n\n#include <memory>\n\n#include \"base/include/log/logging.h\"\n#include \"core/animation/css_keyframe_manager.h\"\n#include \"core/animation/keyframe_effect.h\"\n#include \"core/animation/keyframe_model.h\"\n#include \"core/animation/keyframed_animation_curve.h\"\n#include \"core/animation/utils/cubic_bezier.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/css/transforms/transform_operations.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/animation_data.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace animation {\nnamespace tasm {\nnamespace testing {\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass TimingFunctionTest : public ::testing::Test {\n public:\n  TimingFunctionTest() {}\n  ~TimingFunctionTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>\n      tasm_mediator;\n  fml::RefPtr<lynx::tasm::Element> element_;\n\n  void SetUp() override {\n    ::lynx::tasm::LynxEnvConfig lynx_env_config(\n        kWidth, kHeight, kDefaultLayoutsUnitPerPx,\n        kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<::lynx::tasm::MockPaintingContext>(),\n        tasm_mediator.get(), lynx_env_config);\n    auto config = std::make_shared<::lynx::tasm::PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n  }\n\n  starlight::AnimationData InitAnimationData(\n      starlight::TimingFunctionData timing_func) {\n    starlight::AnimationData data;\n    data.name = base::String(\"test\");\n    data.duration = 2000;\n    data.delay = 0;\n    data.timing_func = timing_func;\n    data.iteration_count = 1;\n    data.fill_mode = starlight::AnimationFillModeType::kBoth;\n    data.direction = starlight::AnimationDirectionType::kNormal;\n    data.play_state = starlight::AnimationPlayStateType::kRunning;\n    return data;\n  }\n};\n\nTEST_F(TimingFunctionTest, MakeTimingFunction1) {\n  auto test_timing_function_data = starlight::TimingFunctionData();\n  auto timing_function_0 =\n      TimingFunction::MakeTimingFunction(test_timing_function_data);\n  auto type0 = timing_function_0->GetType();\n  EXPECT_EQ(type0, TimingFunction::Type::LINEAR);\n\n  auto test_func_type_1 = starlight::TimingFunctionType::kEaseIn;\n  test_timing_function_data.timing_func = test_func_type_1;\n  auto timing_function_1 =\n      TimingFunction::MakeTimingFunction(test_timing_function_data);\n  auto type1 = timing_function_1->GetType();\n  EXPECT_EQ(type1, TimingFunction::Type::CUBIC_BEZIER);\n\n  auto test_func_type_2 = starlight::TimingFunctionType::kEaseOut;\n  test_timing_function_data.timing_func = test_func_type_2;\n  auto timing_function_2 =\n      TimingFunction::MakeTimingFunction(test_timing_function_data);\n  auto type2 = timing_function_2->GetType();\n  EXPECT_EQ(type2, TimingFunction::Type::CUBIC_BEZIER);\n\n  auto test_func_type_3 = starlight::TimingFunctionType::kEaseInEaseOut;\n  test_timing_function_data.timing_func = test_func_type_3;\n  auto timing_function_3 =\n      TimingFunction::MakeTimingFunction(test_timing_function_data);\n  auto type3 = timing_function_3->GetType();\n  EXPECT_EQ(type3, TimingFunction::Type::CUBIC_BEZIER);\n\n  auto test_func_type_4 = starlight::TimingFunctionType::kCubicBezier;\n  test_timing_function_data.timing_func = test_func_type_4;\n  auto timing_function_4 =\n      TimingFunction::MakeTimingFunction(test_timing_function_data);\n  auto type4 = timing_function_4->GetType();\n  EXPECT_EQ(type4, TimingFunction::Type::CUBIC_BEZIER);\n\n  auto test_func_type_5 = starlight::TimingFunctionType::kSteps;\n  test_timing_function_data.timing_func = test_func_type_5;\n  auto timing_function_5 =\n      TimingFunction::MakeTimingFunction(test_timing_function_data);\n  auto type5 = timing_function_5->GetType();\n  EXPECT_EQ(type5, TimingFunction::Type::STEPS);\n}\n\nTEST_F(TimingFunctionTest, MakeTimingFunction2) {\n  auto test_timing_function_data = starlight::TimingFunctionData();\n  auto test_animation_data = InitAnimationData(test_timing_function_data);\n  auto timing_function_0 =\n      TimingFunction::MakeTimingFunction(&test_animation_data);\n  auto type0 = timing_function_0->GetType();\n  EXPECT_EQ(type0, TimingFunction::Type::LINEAR);\n\n  auto test_func_type_1 = starlight::TimingFunctionType::kEaseIn;\n  test_timing_function_data.timing_func = test_func_type_1;\n  test_animation_data = InitAnimationData(test_timing_function_data);\n  auto timing_function_1 =\n      TimingFunction::MakeTimingFunction(&test_animation_data);\n  auto type1 = timing_function_1->GetType();\n  EXPECT_EQ(type1, TimingFunction::Type::CUBIC_BEZIER);\n\n  auto test_func_type_2 = starlight::TimingFunctionType::kEaseOut;\n  test_timing_function_data.timing_func = test_func_type_2;\n  test_animation_data = InitAnimationData(test_timing_function_data);\n  auto timing_function_2 =\n      TimingFunction::MakeTimingFunction(&test_animation_data);\n  auto type2 = timing_function_2->GetType();\n  EXPECT_EQ(type2, TimingFunction::Type::CUBIC_BEZIER);\n\n  auto test_func_type_3 = starlight::TimingFunctionType::kEaseInEaseOut;\n  test_timing_function_data.timing_func = test_func_type_3;\n  test_animation_data = InitAnimationData(test_timing_function_data);\n  auto timing_function_3 =\n      TimingFunction::MakeTimingFunction(&test_animation_data);\n  auto type3 = timing_function_3->GetType();\n  EXPECT_EQ(type3, TimingFunction::Type::CUBIC_BEZIER);\n\n  auto test_func_type_4 = starlight::TimingFunctionType::kCubicBezier;\n  test_timing_function_data.timing_func = test_func_type_4;\n  test_animation_data = InitAnimationData(test_timing_function_data);\n  auto timing_function_4 =\n      TimingFunction::MakeTimingFunction(&test_animation_data);\n  auto type4 = timing_function_4->GetType();\n  EXPECT_EQ(type4, TimingFunction::Type::CUBIC_BEZIER);\n\n  auto test_func_type_5 = starlight::TimingFunctionType::kSteps;\n  test_timing_function_data.timing_func = test_func_type_5;\n  test_animation_data = InitAnimationData(test_timing_function_data);\n  auto timing_function_5 =\n      TimingFunction::MakeTimingFunction(&test_animation_data);\n  auto type5 = timing_function_5->GetType();\n  EXPECT_EQ(type5, TimingFunction::Type::STEPS);\n}\n\nTEST_F(TimingFunctionTest, CreatePreset) {\n  auto cubic_timing_function_0 = CubicBezierTimingFunction::CreatePreset(\n      CubicBezierTimingFunction::EaseType::CUSTOM);\n  EXPECT_TRUE(cubic_timing_function_0 == nullptr);\n\n  double epsilon = 1e-7;\n  auto cubic_timing_function_1 = CubicBezierTimingFunction::CreatePreset(\n      CubicBezierTimingFunction::EaseType::EASE);\n  EXPECT_TRUE(cubic_timing_function_1 != nullptr);\n  auto bezier1 = cubic_timing_function_1->bezier();\n  bool flag1 = abs(bezier1.GetX1() - 0.25) < epsilon &&\n               abs(bezier1.GetY1() - 0.1) < epsilon &&\n               abs(bezier1.GetX2() - 0.25) < epsilon &&\n               abs(bezier1.GetY2() - 1.0) < epsilon;\n  EXPECT_TRUE(flag1);\n\n  auto cubic_timing_function_2 = CubicBezierTimingFunction::CreatePreset(\n      CubicBezierTimingFunction::EaseType::EASE_IN);\n  EXPECT_TRUE(cubic_timing_function_2 != nullptr);\n  auto bezier2 = cubic_timing_function_2->bezier();\n  bool flag2 = abs(bezier2.GetX1() - 0.42) < epsilon &&\n               abs(bezier2.GetY1() - 0.0) < epsilon &&\n               abs(bezier2.GetX2() - 1.0) < epsilon &&\n               abs(bezier2.GetY2() - 1.0) < epsilon;\n  EXPECT_TRUE(flag2);\n\n  auto cubic_timing_function_3 = CubicBezierTimingFunction::CreatePreset(\n      CubicBezierTimingFunction::EaseType::EASE_OUT);\n  EXPECT_TRUE(cubic_timing_function_3 != nullptr);\n  auto bezier3 = cubic_timing_function_3->bezier();\n  bool flag3 = abs(bezier3.GetX1() - 0.0) < epsilon &&\n               abs(bezier3.GetY1() - 0.0) < epsilon &&\n               abs(bezier3.GetX2() - 0.58) < epsilon &&\n               abs(bezier3.GetY2() - 1.0) < epsilon;\n  EXPECT_TRUE(flag3);\n\n  auto cubic_timing_function_4 = CubicBezierTimingFunction::CreatePreset(\n      CubicBezierTimingFunction::EaseType::EASE_IN_OUT);\n  EXPECT_TRUE(cubic_timing_function_4 != nullptr);\n  auto bezier4 = cubic_timing_function_4->bezier();\n  bool flag4 = abs(bezier4.GetX1() - 0.42) < epsilon &&\n               abs(bezier4.GetY1() - 0.0) < epsilon &&\n               abs(bezier4.GetX2() - 0.58) < epsilon &&\n               abs(bezier4.GetY2() - 1.0) < epsilon;\n  EXPECT_TRUE(flag4);\n}\n\nTEST_F(TimingFunctionTest, CubicBezierTimingFunctionCreate) {\n  auto test_cubic_timing_function =\n      CubicBezierTimingFunction::Create(0.0, 0.25, 0.5, 1.0);\n  double epsilon = 1e-7;\n  auto bezier = test_cubic_timing_function->bezier();\n  bool flag = abs(bezier.GetX1() - 0.0) < epsilon &&\n              abs(bezier.GetY1() - 0.25) < epsilon &&\n              abs(bezier.GetX2() - 0.5) < epsilon &&\n              abs(bezier.GetY2() - 1.0) < epsilon;\n  EXPECT_TRUE(flag);\n  EXPECT_EQ(test_cubic_timing_function->ease_type(),\n            CubicBezierTimingFunction::EaseType::CUSTOM);\n  EXPECT_TRUE(test_cubic_timing_function->GetType() ==\n              TimingFunction::Type::CUBIC_BEZIER);\n}\n\nTEST_F(TimingFunctionTest, LinearTimingFunctionCreate) {\n  auto test_linear_timing_function = LinearTimingFunction::Create();\n  EXPECT_TRUE(test_linear_timing_function->GetType() ==\n              TimingFunction::Type::LINEAR);\n}\n\nTEST_F(TimingFunctionTest, StepsTimingFunctionCreate) {\n  auto test_steps_timing_function =\n      StepsTimingFunction::Create(2, starlight::StepsType::kStart);\n  EXPECT_EQ(test_steps_timing_function->steps(), 2);\n  EXPECT_EQ(test_steps_timing_function->step_position(),\n            starlight::StepsType::kStart);\n  EXPECT_TRUE(test_steps_timing_function->GetType() ==\n              TimingFunction::Type::STEPS);\n}\n\nTEST_F(TimingFunctionTest, Clone) {\n  auto test_linear_timing_function = LinearTimingFunction::Create();\n  auto clone_linear_timing_function = test_linear_timing_function->Clone();\n  EXPECT_EQ(test_linear_timing_function->GetType(),\n            clone_linear_timing_function->GetType());\n\n  auto test_cubic_timing_function =\n      CubicBezierTimingFunction::Create(0.0, 0.25, 0.5, 1.0);\n  auto clone_cubic_timing_function = test_cubic_timing_function->Clone();\n  EXPECT_EQ(test_cubic_timing_function->GetType(),\n            clone_cubic_timing_function->GetType());\n\n  auto test_steps_timing_function =\n      StepsTimingFunction::Create(2, starlight::StepsType::kStart);\n  auto clone_steps_timing_function = test_steps_timing_function->Clone();\n  EXPECT_EQ(test_steps_timing_function->GetType(),\n            clone_steps_timing_function->GetType());\n}\n\nTEST_F(TimingFunctionTest, GetType) {\n  auto test_linear_timing_function = LinearTimingFunction::Create();\n  EXPECT_EQ(test_linear_timing_function->GetType(),\n            TimingFunction::Type::LINEAR);\n\n  auto test_cubic_timing_function =\n      CubicBezierTimingFunction::Create(0.0, 0.25, 0.5, 1.0);\n  EXPECT_EQ(test_cubic_timing_function->GetType(),\n            TimingFunction::Type::CUBIC_BEZIER);\n\n  auto test_steps_timing_function =\n      StepsTimingFunction::Create(2, starlight::StepsType::kStart);\n  EXPECT_EQ(test_steps_timing_function->GetType(), TimingFunction::Type::STEPS);\n}\n\nTEST_F(TimingFunctionTest, GetValue) {\n  double epsilon = 0.0001;\n  auto test_linear_timing_function = LinearTimingFunction::Create();\n  EXPECT_TRUE(abs(test_linear_timing_function->GetValue(0.5) - 0.5) < epsilon);\n\n  auto test_cubic_timing_function =\n      CubicBezierTimingFunction::Create(0.0, 0.25, 0.5, 1.0);\n  EXPECT_TRUE(abs(test_cubic_timing_function->GetValue(0.5) - 0.7809) <\n              epsilon);\n\n  auto test_steps_timing_function =\n      StepsTimingFunction::Create(2, starlight::StepsType::kStart);\n  EXPECT_TRUE(abs(test_steps_timing_function->GetValue(0.5) - 1.0) < epsilon);\n}\n\nTEST_F(TimingFunctionTest, Velocity) {\n  double epsilon = 0.0001;\n  auto test_linear_timing_function = LinearTimingFunction::Create();\n  EXPECT_TRUE(abs(test_linear_timing_function->Velocity(0.5) - 0.0) < epsilon);\n\n  auto test_cubic_timing_function =\n      CubicBezierTimingFunction::Create(0.0, 0.25, 0.5, 1.0);\n  EXPECT_TRUE(abs(test_cubic_timing_function->Velocity(0.5) - 0.8418) <\n              epsilon);\n\n  auto test_steps_timing_function =\n      StepsTimingFunction::Create(2, starlight::StepsType::kStart);\n  EXPECT_TRUE(abs(test_steps_timing_function->Velocity(0.5) - 0.0) < epsilon);\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace animation\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/BUILD.gn",
    "content": "# Copyright 2020 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//${lynx_dir}/build_overrides/bindings_files.gni\")\nimport(\"../../testing/test.gni\")\nimport(\"../Lynx.gni\")\n\n# base_shared_sources\nbase_shared_sources = [\n                        \"js_constants.h\",\n                        \"lynx_export.h\",\n                        \"memory/memory_pressure_callback.cc\",\n                        \"memory/memory_pressure_callback.h\",\n                        \"thread/once_task.h\",\n                        \"thread/thread_utils.cc\",\n                        \"thread/thread_utils.h\",\n                        \"threading/js_thread_config_getter.h\",\n                        \"threading/task_runner_manufactor.cc\",\n                        \"threading/task_runner_manufactor.h\",\n                        \"threading/thread_merger.cc\",\n                        \"threading/thread_merger.h\",\n                        \"threading/vsync_monitor.cc\",\n                        \"threading/vsync_monitor.h\",\n                        \"utils/any.h\",\n                      ] + trace_extend_sources\n\nif (is_android) {\n  base_shared_sources += [\n    \"android/android_jni.cc\",\n    \"android/android_jni.h\",\n    \"android/callstack_util_android.cc\",\n    \"android/callstack_util_android.h\",\n    \"android/device_utils_android.cc\",\n    \"android/device_utils_android.h\",\n    \"android/fresco_blur_filter.c\",\n    \"android/fresco_blur_filter.h\",\n    \"android/java_only_array.cc\",\n    \"android/java_only_array.h\",\n    \"android/java_only_map.cc\",\n    \"android/java_only_map.h\",\n    \"android/java_value.cc\",\n    \"android/java_value.h\",\n    \"android/jni_helper.cc\",\n    \"android/jni_helper.h\",\n    \"android/lynx_android_blur.cc\",\n    \"android/lynx_error_android.cc\",\n    \"android/lynx_error_android.h\",\n    \"android/lynx_white_board_android.cc\",\n    \"android/message_loop_android_vsync.cc\",\n    \"android/message_loop_android_vsync.h\",\n    \"android/piper_data.cc\",\n    \"android/piper_data.h\",\n    \"android/vsync_monitor_android.cc\",\n    \"android/vsync_monitor_android.h\",\n    \"thread/atomic_lifecycle.cc\",\n    \"thread/atomic_lifecycle.h\",\n    \"threading/js_thread_config_getter.cc\",\n    \"threading/task_runner_vsync.cc\",\n    \"threading/task_runner_vsync.h\",\n  ]\n} else if (is_harmony) {\n  base_shared_sources += [\n    \"harmony/harmony_function_loader.cc\",\n    \"harmony/harmony_function_loader.h\",\n    \"harmony/harmony_napi_env_holder.cc\",\n    \"harmony/harmony_napi_env_holder.h\",\n    \"harmony/harmony_trace_event_def.h\",\n    \"harmony/napi_convert_helper.cc\",\n    \"harmony/napi_convert_helper.h\",\n    \"harmony/props_constant.h\",\n    \"harmony/threading/js_thread_config_getter_harmony.cc\",\n    \"harmony/vsync_monitor_harmony.cc\",\n    \"harmony/vsync_monitor_harmony.h\",\n  ]\n} else if (is_apple) {\n  if (!enable_unittests && !is_oliver_node_lynx && !is_oliver_tasm &&\n      (!is_mac || !desktop_enable_embedder_layer)) {\n    base_shared_sources += [\n      \"darwin/lynx_env_darwin.h\",\n      \"darwin/lynx_env_darwin.mm\",\n      \"darwin/lynx_trail_hub_impl_darwin.h\",\n      \"darwin/lynx_trail_hub_impl_darwin.mm\",\n    ]\n  }\n  if (is_ios) {\n    base_shared_sources += [\n      \"darwin/message_loop_darwin_vsync.cc\",\n      \"darwin/message_loop_darwin_vsync.h\",\n      \"darwin/vsync_monitor_darwin.h\",\n      \"darwin/vsync_monitor_darwin.mm\",\n      \"threading/task_runner_vsync.cc\",\n      \"threading/task_runner_vsync.h\",\n    ]\n  }\n  if (is_mac) {\n    base_shared_sources += [\n      \"utils/paths_mac.h\",\n      \"utils/paths_mac.mm\",\n    ]\n  }\n  base_shared_sources += [ \"threading/js_thread_config_getter.cc\" ]\n} else if (is_win) {\n  base_shared_sources += [\n    \"threading/js_thread_config_getter.cc\",\n    \"utils/paths_win.cc\",\n    \"utils/paths_win.h\",\n  ]\n} else if (is_linux) {\n  base_shared_sources += [ \"threading/js_thread_config_getter.cc\" ]\n}\n\nif (enable_unittests) {\n  base_shared_sources += [ \"threading/vsync_monitor_default.cc\" ]\n  if (!is_android) {\n    base_shared_sources += [\n      \"threading/task_runner_vsync.cc\",\n      \"threading/task_runner_vsync.h\",\n    ]\n  }\n}\n\n# base_shared_sources end\n\nlynx_core_source_set(\"base\") {\n  libs = []\n  sources = []\n  deps = []\n  public_deps = [ \"../build:build\" ]\n  if (is_oliver_ssr) {\n    not_needed(base_shared_sources)\n    sources += [\n      \"threading/task_runner_manufactor.cc\",\n      \"threading/task_runner_manufactor.h\",\n    ]\n    deps += [\n      \":json_utils\",\n      \":observer\",\n    ]\n  } else {\n    sources += base_shared_sources\n    public_deps += [ \"../../base/trace/native:trace_public_headers\" ]\n    if (is_android) {\n      deps += [ \"../../third_party/rapidjson:rapidjson\" ]\n    }\n    if (is_win) {\n      libs += [ \"user32.lib\" ]\n    }\n\n    if (is_headless) {\n      defines = [ \"ENABLE_HEADLESS\" ]\n    }\n\n    deps += [\n      \":json_utils\",\n      \":observer\",\n    ]\n  }\n}\n\nlynx_core_source_set(\"json_utils\") {\n  sources = [\n    \"json/json_util.h\",\n    \"json/json_utils.cc\",\n  ]\n\n  public_deps = [ \"../../third_party/rapidjson\" ]\n  deps = [ \"../../base/src:base_log_headers\" ]\n}\n\nlynx_core_source_set(\"observer\") {\n  sources = [\n    \"observer/observer.h\",\n    \"observer/observer_list.cc\",\n    \"observer/observer_list.h\",\n  ]\n}\n\nlynx_core_source_set(\"gtest_lynx_base_android\") {\n  check_includes = false\n  sources = []\n  deps = []\n  if (is_android) {\n    sources = [\n      \"android/android_jni_unittest.cc\",\n      \"android/java_value_unittest.cc\",\n      \"android/jni_helper_unittest.cc\",\n    ]\n    deps += [ \"../value_wrapper:value_impl_android_unittest\" ]\n  }\n  deps += [ \"//third_party/googletest:gtest_sources\" ]\n}\n\nunittest_set(\"base_testset\") {\n  sources = [\n    \"json/json_utils_unittests.cc\",\n    \"memory/memory_pressure_callback_unittest.cc\",\n    \"thread/blocking_queue_unittest.cc\",\n    \"thread/once_task_unittest.cc\",\n    \"thread/thread_utils_unittest.cc\",\n    \"threading/task_runner_manufactor_unittest.cc\",\n    \"threading/task_runner_vsync_unittest.cc\",\n    \"threading/thread_merger_unittest.cc\",\n    \"threading/vsync_monitor_unittest.cc\",\n  ]\n  public_deps = [ \"../base\" ]\n}\n\nunittest_exec(\"lynx_base_unittests_exec\") {\n  sources = []\n  deps = [\n    \":base_testset\",\n    \"../base\",\n    \"../renderer/utils:lynx_env\",\n  ]\n}\n\ngroup(\"base_tests\") {\n  testonly = true\n  deps = [ \":lynx_base_unittests_exec\" ]\n  public_deps = [ \":base_testset\" ]\n}\n"
  },
  {
    "path": "core/base/android/android_jni.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/android/android_jni.h\"\n\n#include <sys/prctl.h>\n\n#include <string>\n#include <utility>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/log/logging.h\"\n#include \"core/base/android/callstack_util_android.h\"\n#include \"core/build/gen/lynx_sub_error_code.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nvoid GetExceptionInfo(\n    JNIEnv *env, lynx::base::android::ScopedLocalJavaRef<jthrowable> &throwable,\n    std::string &error_msg, std::string &error_stack) {\n  if (!throwable.Get()) {\n    return;\n  }\n  error_msg.append(\n      CallStackUtilAndroid::GetMessageOfCauseChain(env, throwable));\n  error_stack.append(\n      CallStackUtilAndroid::GetStackTraceStringWithLineTrimmed(env, throwable));\n}\n\nvoid CheckException(JNIEnv *env) {\n  HasJNIException() = false;\n  if (!HasException(env)) {\n    return;\n  }\n\n  static thread_local bool is_reentering = false;\n\n  // Exception has been found, might as well tell BreakPad about it.\n  lynx::base::android::ScopedLocalJavaRef<jthrowable> throwable(\n      env, env->ExceptionOccurred());\n  if (throwable.Get()) {\n    // Clear the pending exception, since a local reference is now held.\n    env->ExceptionDescribe();\n    env->ExceptionClear();\n\n    // If is reentering, ignore the exception thrown by\n    // GetExceptionInfo to avoid infinite recursion\n    if (!is_reentering) {\n      is_reentering = true;\n      std::string error_message;\n      std::string error_stack;\n      GetExceptionInfo(env, throwable, error_message, error_stack);\n\n      lynx::base::LynxError error{error::E_EXCEPTION_JNI,\n                                  std::move(error_message)};\n      error.custom_info_.emplace(\"error_stack\", std::move(error_stack));\n      lynx::base::ErrorStorage::GetInstance().SetError(std::move(error));\n      is_reentering = false;\n    }\n    HasJNIException() = true;\n  }\n\n  // Now, feel good about it and die.\n  // CHECK(false) << \"Please include Java exception stack in crash report\";\n}\n\n// Used to indicate whether there is\n// an jni exception after a jni call\nbool &HasJNIException() {\n  static thread_local bool has_jni_exception = false;\n  return has_jni_exception;\n}\n\nint GetJNILocalFrameCapacity() {\n  JNIEnv *env = base::android::AttachCurrentThread();\n  int capacity = 1;\n  for (; capacity < 512; capacity++) {\n    auto pushResult = env->PushLocalFrame(capacity);\n\n    if (pushResult < 0) {\n      jthrowable java_throwable = env->ExceptionOccurred();\n      if (java_throwable) {\n        // Clear the pending exception, since a local reference is now held.\n        // env->ExceptionDescribe();\n        env->ExceptionClear();\n        env->DeleteLocalRef(java_throwable);\n      }\n\n      env->PopLocalFrame(nullptr);\n      return capacity - 1;\n    }\n  }\n\n  return capacity;\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/android_jni.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_ANDROID_ANDROID_JNI_H_\n#define CORE_BASE_ANDROID_ANDROID_JNI_H_\n\n#include <jni.h>\n\n#include <cstdint>\n#include <string>\n\n#include \"base/include/platform/android/jni_utils.h\"\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"core/base/lynx_export.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nLYNX_EXPORT void CheckException(JNIEnv *env);\n\n// Used to indicate whether there is an jni exception after a jni call.\n// It should be noted that only the result checked by calling this method\n// immediately after JNI invocation is valid.\nbool &HasJNIException();\n\n// Get message and stack of a throwable, and append the results to\n// parameters error_msg and error_stack respectively\nvoid GetExceptionInfo(\n    JNIEnv *env, lynx::base::android::ScopedLocalJavaRef<jthrowable> &throwable,\n    std::string &error_msg, std::string &error_stack);\n\nint GetJNILocalFrameCapacity();\nclass JniLocalScope {\n public:\n  JniLocalScope(JNIEnv *env, jint capacity = 256) : env_(env) {\n    hasFrame_ = false;\n\n    for (size_t i = capacity; i > 0; i /= 2) {\n      auto pushResult = env->PushLocalFrame(i);\n      if (pushResult == 0) {\n        hasFrame_ = true;\n        break;\n      }\n\n      // if failed, clear the exception and try again with less capacity\n      if (pushResult < 0) {\n        jthrowable java_throwable = env->ExceptionOccurred();\n        if (java_throwable) {\n          env->ExceptionClear();\n          env->DeleteLocalRef(java_throwable);\n        }\n      }\n    }\n  }\n\n  ~JniLocalScope() {\n    if (hasFrame_) {\n      env_->PopLocalFrame(nullptr);\n    }\n  }\n\n private:\n  JNIEnv *env_;\n  bool hasFrame_;\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_ANDROID_JNI_H_\n"
  },
  {
    "path": "core/base/android/android_jni_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/base/android/android_jni.h\"\n\n#include \"base/include/debug/lynx_error.h\"\n#include \"core/base/android/java_only_map.h\"\n#include \"core/base/android/jni_helper.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nTEST(AndroidJNITest, HasJNIException) {\n  JNIEnv* env = AttachCurrentThread();\n  auto key = JNIConvertHelper::ConvertToJNIStringUTF(env, \"key1\");\n  JavaOnlyMap j_map;\n\n  // The HasJNIException flag is intially set to false\n  ASSERT_FALSE(HasJNIException());\n\n  // Get value from a empty JavaOnlyMap will throw a jni exception\n  bool bool_value = JavaOnlyMap::JavaOnlyMapGetBooleanAtIndex(\n      env, j_map.jni_object(), key.Get());\n  ASSERT_TRUE(HasJNIException());\n\n  // Successfully calling a JNI method resets the HasJNIException flag\n  j_map.PushBoolean(\"key1\", false);\n  bool_value = JavaOnlyMap::JavaOnlyMapGetBooleanAtIndex(\n      env, j_map.jni_object(), key.Get());\n  ASSERT_FALSE(HasJNIException());\n}\n\nTEST(AndroidJNITest, GetExceptionInfo) {\n  JNIEnv* env = AttachCurrentThread();\n  auto key = JNIConvertHelper::ConvertToJNIStringUTF(env, \"key1\");\n  JavaOnlyMap j_map;\n\n  // Get value from a empty JavaOnlyMap will throw a jni exception\n  JavaOnlyMap::JavaOnlyMapGetBooleanAtIndex(env, j_map.jni_object(), key.Get());\n  ASSERT_TRUE(HasJNIException());\n\n  const auto& error = ErrorStorage::GetInstance().GetError();\n  ASSERT_NE(\n      error->error_message_.find(\"java.lang.NullPointerException: key: key1\"),\n      std::string::npos);\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/callstack_util_android.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/base/android/callstack_util_android.h\"\n\n#include \"platform/android/lynx_android/src/main/jni/gen/CallStackUtil_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/CallStackUtil_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForCallStackUtil(JNIEnv *env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynx\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nconstexpr char kErrorMsgGetJavaExceptionFailed[] =\n    \"Another JNI exception occurred when get java exception info, \"\n    \"please ask Lynx for help\";\n\nstd::string CallStackUtilAndroid::GetMessageOfCauseChain(\n    JNIEnv *env, const ScopedLocalJavaRef<jthrowable> &throwable) {\n  if (!throwable.Get()) {\n    return \"\";\n  }\n  ScopedLocalJavaRef<jstring> j_message =\n      Java_CallStackUtil_getMessageOfCauseChain(env, throwable.Get());\n  if (j_message.Get()) {\n    std::string message;\n    const char *str = env->GetStringUTFChars(j_message.Get(), JNI_FALSE);\n    if (str) {\n      message.append(str);\n    }\n    env->ReleaseStringUTFChars(j_message.Get(), str);\n    return message;\n  } else {\n    return kErrorMsgGetJavaExceptionFailed;\n  }\n}\n\nstd::string CallStackUtilAndroid::GetStackTraceStringWithLineTrimmed(\n    JNIEnv *env, const ScopedLocalJavaRef<jthrowable> &throwable) {\n  if (!throwable.Get()) {\n    return \"\";\n  }\n  ScopedLocalJavaRef<jstring> j_stack =\n      Java_CallStackUtil_getStackTraceStringWithLineTrimmed(env,\n                                                            throwable.Get());\n  if (j_stack.Get()) {\n    std::string stack;\n    const char *str = env->GetStringUTFChars(j_stack.Get(), JNI_FALSE);\n    if (str) {\n      stack.append(str);\n    }\n    env->ReleaseStringUTFChars(j_stack.Get(), str);\n    return stack;\n  } else {\n    return kErrorMsgGetJavaExceptionFailed;\n  }\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/callstack_util_android.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_BASE_ANDROID_CALLSTACK_UTIL_ANDROID_H_\n#define CORE_BASE_ANDROID_CALLSTACK_UTIL_ANDROID_H_\n#include <jni.h>\n\n#include <string>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"core/base/lynx_export.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nclass CallStackUtilAndroid {\n public:\n  LYNX_EXPORT_FOR_DEVTOOL static std::string GetMessageOfCauseChain(\n      JNIEnv* env, const ScopedLocalJavaRef<jthrowable>& throwable);\n  LYNX_EXPORT_FOR_DEVTOOL static std::string GetStackTraceStringWithLineTrimmed(\n      JNIEnv* env, const ScopedLocalJavaRef<jthrowable>& throwable);\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_CALLSTACK_UTIL_ANDROID_H_\n"
  },
  {
    "path": "core/base/android/device_utils_android.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/android/device_utils_android.h\"\n\n#include \"platform/android/lynx_android/src/main/jni/gen/DeviceUtils_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/DeviceUtils_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForDeviceUtils(JNIEnv* env) { return RegisterNativesImpl(env); }\n}  // namespace jni\n}  // namespace lynx\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nbool DeviceUtilsAndroid::Is64BitDevice() {\n  return Java_DeviceUtils_is64BitDevice(android::AttachCurrentThread());\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/device_utils_android.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_ANDROID_DEVICE_UTILS_ANDROID_H_\n#define CORE_BASE_ANDROID_DEVICE_UTILS_ANDROID_H_\n\n#include <jni.h>\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nclass DeviceUtilsAndroid {\n public:\n  DeviceUtilsAndroid() = delete;\n  ~DeviceUtilsAndroid() = delete;\n\n  static bool Is64BitDevice();\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_DEVICE_UTILS_ANDROID_H_\n"
  },
  {
    "path": "core/base/android/fresco_blur_filter.c",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n// copy from\n// https://github.com/facebook/fresco/blob/v2.4.0/native-filters/src/main/jni/filters/blur_filter.c\n#include \"core/base/android/fresco_blur_filter.h\"  // add by lynx\n\n#include <android/bitmap.h>\n#include <jni.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\ntypedef struct {\n  uint8_t b, g, r, a;\n} pixel_t;\n\n#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))\n#define UNUSED(expr) ((void)(expr));\n\n// These values are chosen arbitrarily but small enough to avoid\n// integer-overflows\n#define BITMAP_MAX_DIMENSION 65536\n#define BLUR_MAX_ITERATIONS 65536\n#define BLUR_MAX_RADIUS 65536\n\nstatic inline int max(int a, int b) { return a > b ? a : b; }\nstatic inline int bound(int x, int l, int h) {\n  return x < l ? l : (x > h ? h : x);\n}\n\nstatic void safe_throw_exception(JNIEnv* env, const char* msg) {\n  if (!(*env)->ExceptionCheck(env)) {\n    // modify by lynx\n    jclass runtime_exception =\n        (*env)->FindClass(env, \"java/lang/RuntimeException\");\n    if (!runtime_exception) {\n      return;\n    }\n    (*env)->ThrowNew(env, runtime_exception, msg);\n  }\n}\n\n/**\n * Creates a blurred version of the given `row` of `pixelsIn`. It uses a moving\n * average algorithm such that it reads every pixel of the row just once. The\n * edge pixels are repeated to avoid artifacts.\n *\n * Requires a pre-computed `div` table of size `255 * diameter` that maps `x ->\n * x / diameter` (can be rounded)\n */\nstatic inline void internalHorizontalBlur(pixel_t* pixelsIn, pixel_t* outRow,\n                                          int w, int row, int diameter,\n                                          uint8_t* div) {\n  const int firstInByte = w * row;\n  const int lastInByte = w * (row + 1) - 1;\n  const int radius = diameter >> 1;\n\n  int a = 0, r = 0, g = 0, b = 0;\n  pixel_t p;\n\n  // iterate over relative position to first pixel of row\n  for (int i = -radius; i < w + radius; i++) {\n    const int ii = bound(firstInByte + i, firstInByte, lastInByte);\n    p = pixelsIn[ii];\n    a += p.a;\n    r += p.r;\n    g += p.g;\n    b += p.b;\n\n    if (i >= radius) {\n      const int outOffset = i - radius;\n      p.a = div[a];\n      p.r = div[r];\n      p.g = div[g];\n      p.b = div[b];\n      outRow[outOffset] = p;\n\n      const int j = i - (diameter - 1);\n      const int jj = bound(firstInByte + j, firstInByte, lastInByte);\n      p = pixelsIn[jj];\n      a -= p.a;\n      r -= p.r;\n      g -= p.g;\n      b -= p.b;\n    }\n  }\n}\n\n/**\n * Creates a blurred version of the given `col` of `pixelsIn`. It uses a moving\n * average algorithm such that it reads every pixel of the column just once. The\n * edge pixels are repeated to avoid artifacts.\n *\n * [ 0        ] [          ] [          ] [          ] [ col      ] [          ]\n * [ w-1      ] [          ] [          ] [          ] [          ] [ col+1w   ]\n * [          ] [          ] [          ] [          ] [          ] [          ]\n * [          ] [          ] [          ] [          ] [          ] [          ]\n * [          ] [          ] [          ] [          ] [          ] [          ]\n * [          ] [          ] [          ] [          ] [          ] [          ]\n * [          ] [          ] [          ] [col+(h-1)w] [          ] [          ]\n *\n * Requires a pre-computed `div` table of size `255 * diameter` that maps `x ->\n * x / diameter` (can be rounded)\n */\nstatic inline void internalVerticalBlur(pixel_t* pixelsIn, pixel_t* outCol,\n                                        int w, int h, int col, int diameter,\n                                        uint8_t* div) {\n  const int firstInByte = col;\n  const int lastInByte = w * (h - 1) + col;\n  const int radiusTimesW = (diameter >> 1) * w;\n  const int diameterMinusOneTimesW = (diameter - 1) * w;\n\n  int a = 0, r = 0, g = 0, b = 0;\n  pixel_t p;\n\n  // iterate over absolute positions in `pixelsIn`; `w` is the step width for\n  // moving down one row\n  for (int i = firstInByte - radiusTimesW; i <= lastInByte + radiusTimesW;\n       i += w) {\n    const int ii = bound(i, firstInByte, lastInByte);\n    p = pixelsIn[ii];\n    a += p.a;\n    r += p.r;\n    g += p.g;\n    b += p.b;\n\n    const int outPos = i - radiusTimesW;\n    if (outPos >= firstInByte) {\n      p.a = div[a];\n      p.r = div[r];\n      p.g = div[g];\n      p.b = div[b];\n      *(outCol++) = p;\n\n      const int j = i - diameterMinusOneTimesW;\n      const int jj = bound(j, firstInByte, lastInByte);\n      p = pixelsIn[jj];\n      a -= p.a;\n      r -= p.r;\n      g -= p.g;\n      b -= p.b;\n    }\n  }\n}\n\n/**\n * A native implementation of an iterative box blur algorithm that runs fast and\n * with little extra memory. It requires bitmap's format to be ARGB_8888 and\n * works in-place (see actual memory usage below).\n *\n * The individual box blurs are split up in vertical and horizontal direction.\n * That allows us to use a moving average implementation for blurring individual\n * rows and columns.\n *\n * The runtime is: O(iterations * width * height) and therefore linear in the\n * number of pixels\n *\n * The required memory is: 2 * radius * 256 * 1 Bytes + max(width, height) * 4\n * Bytes (+constant)\n */\nstatic void BlurFilter_iterativeBoxBlur(JNIEnv* env, jclass clazz,\n                                        jobject bitmap, jint iterations,\n                                        jint radius) {\n  UNUSED(clazz);\n\n  if (iterations <= 0 || iterations > BLUR_MAX_ITERATIONS) {\n    safe_throw_exception(\n        env, \"BlurFilter_iterativeBoxBlur: Iterations argument out of bounds\");\n    return;\n  }\n\n  if (radius <= 0 || radius > BLUR_MAX_RADIUS) {\n    safe_throw_exception(\n        env, \"BlurFilter_iterativeBoxBlur: Blur radius argument out of bounds\");\n    return;\n  }\n\n  AndroidBitmapInfo bitmapInfo;\n\n  int rc = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo);\n  if (rc != ANDROID_BITMAP_RESULT_SUCCESS) {\n    safe_throw_exception(\n        env, \"BlurFilter_iterativeBoxBlur: Failed to get Bitmap info\");\n    return;\n  }\n\n  if (bitmapInfo.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {\n    safe_throw_exception(\n        env, \"BlurFilter_iterativeBoxBlur: Unexpected bitmap format\");\n    return;\n  }\n\n  pixel_t* pixelPtr;\n\n  const int w = bitmapInfo.width;\n  const int h = bitmapInfo.height;\n\n  if (w > BITMAP_MAX_DIMENSION || h > BITMAP_MAX_DIMENSION) {\n    safe_throw_exception(\n        env, \"BlurFilter_iterativeBoxBlur: Bitmap dimensions too large\");\n    return;\n  }\n\n  // locking pixels such that they will not get moved around during processing\n  rc = AndroidBitmap_lockPixels(env, bitmap, (void*)&pixelPtr);\n  if (rc != ANDROID_BITMAP_RESULT_SUCCESS) {\n    safe_throw_exception(\n        env, \"BlurFilter_iterativeBoxBlur: Failed to lock Bitmap pixels\");\n    return;\n  }\n\n  // the information written to an output pixels `x` are from `[x-radius,\n  // x+radius]` (inclusive)\n  const int diameter = radius + 1 + radius;\n\n  // pre-compute division table: speed-up by factor 5(!)\n  uint8_t* div = (uint8_t*)malloc(256 * diameter * sizeof(uint8_t));\n  if (!div) {\n    safe_throw_exception(\n        env, \"BlurFilter_iterativeBoxBlur: Failed to allocate memory: div\");\n    return;\n  }\n\n  // the following lines will fill-up at least the first `255 * diameter`\n  // entries with the mapping `div[x] = (x + r) / d` (i.e. division of x by d\n  // rounded to the nearest number).\n  uint8_t* ptr = div;\n  for (int r = 0; r <= radius; r++) {\n    *(ptr++) = 0;\n  }\n  for (int b = 1; b <= 255; b++) {\n    for (int d = 0; d < diameter; d++) {\n      *(ptr++) = b;\n    }\n  }\n\n  // temporary array for the output of the currently blurred row OR column\n  pixel_t* tempRowOrColumn = (pixel_t*)malloc(max(w, h) * sizeof(pixel_t));\n  if (!tempRowOrColumn) {\n    free(div);\n    safe_throw_exception(env,\n                         \"BlurFilter_iterativeBoxBlur: Failed to allocate \"\n                         \"memory: tempRowOrColumn\");\n    return;\n  }\n\n  for (int i = 0; i < iterations; i++) {\n    // blur rows one-by-one\n    for (int row = 0; row < h; row++) {\n      internalHorizontalBlur(pixelPtr, tempRowOrColumn, w, row, diameter, div);\n\n      // copy output row pixels back to bitmap\n      memcpy(pixelPtr + w * row, tempRowOrColumn, w * sizeof(pixel_t));\n    }\n\n    // blur columns one-by-one\n    for (int col = 0; col < w; col++) {\n      internalVerticalBlur(pixelPtr, tempRowOrColumn, w, h, col, diameter, div);\n\n      // copy output column pixels back to bitmap\n      pixel_t* ptr = pixelPtr + col;\n      for (int row = 0; row < h; row++) {\n        *ptr = tempRowOrColumn[row];\n        ptr += w;\n      }\n    }\n  }\n\n  free(tempRowOrColumn);\n  free(div);\n\n  rc = AndroidBitmap_unlockPixels(env, bitmap);\n  if (rc != ANDROID_BITMAP_RESULT_SUCCESS) {\n    safe_throw_exception(\n        env, \"BlurFilter_iterativeBoxBlur: Failed to unlock Bitmap pixels\");\n  }\n}\n\nvoid fresco_iterativeBoxBlur(JNIEnv* env, jclass clazz, jobject bitmap,\n                             jint iterations, jint radius) {\n  BlurFilter_iterativeBoxBlur(env, clazz, bitmap, iterations, radius);\n}\n"
  },
  {
    "path": "core/base/android/fresco_blur_filter.h",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <jni.h>\n#ifndef CORE_BASE_ANDROID_FRESCO_BLUR_FILTER_H_\n#define CORE_BASE_ANDROID_FRESCO_BLUR_FILTER_H_\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\nvoid fresco_iterativeBoxBlur(JNIEnv* env, jclass clazz, jobject bitmap,\n                             jint iterations, jint radius);\n#ifdef __cplusplus\n}\n#endif\n\n#endif  // CORE_BASE_ANDROID_FRESCO_BLUR_FILTER_H_\n"
  },
  {
    "path": "core/base/android/java_only_array.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/android/java_only_array.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"core/base/android/android_jni.h\"\n#include \"core/base/android/java_only_map.h\"\n#include \"core/base/android/java_value.h\"\n#include \"core/base/android/jni_helper.h\"\n#include \"core/base/android/piper_data.h\"\n#include \"core/base/js_constants.h\"\n#include \"core/base/json/json_util.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/JavaOnlyArray_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/JavaOnlyArray_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForJavaOnlyArray(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynx\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nJavaOnlyArray::JavaOnlyArray() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  jni_object_.Reset(env, Java_JavaOnlyArray_create(env).Get());\n}\n\nstd::unique_ptr<base::android::JavaOnlyArray> JavaOnlyArray::ShallowCopy() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  auto array = Java_JavaOnlyArray_shallowCopy(env, jni_object_.Get());\n  return std::make_unique<base::android::JavaOnlyArray>(env, array);\n}\n\nvoid JavaOnlyArray::PushString(const std::string& value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n\n  static const int api_level = android_get_device_api_level();\n  // Emoji will make App crash when use `NewStringUTF` API in Android 5.x\n  if (api_level < __ANDROID_API_M__) {\n    base::android::ScopedLocalJavaRef<jbyteArray> jni_value =\n        JNIConvertHelper::ConvertToJNIByteArray(env, value);\n    Java_JavaOnlyArray_pushByteArrayAsString(env, jni_object_.Get(),\n                                             jni_value.Get());\n  } else {\n    base::android::ScopedLocalJavaRef<jstring> jni_value =\n        JNIConvertHelper::ConvertToJNIStringUTF(env, value);\n    Java_JavaOnlyArray_pushString(env, jni_object_.Get(), jni_value.Get());\n  }\n}\n\nvoid JavaOnlyArray::PushBoolean(bool value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_JavaOnlyArray_pushBoolean(env, jni_object_.Get(), value);\n}\n\nvoid JavaOnlyArray::PushInt64(int64_t value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_JavaOnlyArray_pushLong(env, jni_object_.Get(), value);\n}\n\nvoid JavaOnlyArray::PushInt(int value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_JavaOnlyArray_pushInt(env, jni_object_.Get(), value);\n}\n\nvoid JavaOnlyArray::PushDouble(double value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_JavaOnlyArray_pushDouble(env, jni_object_.Get(), value);\n}\n\nvoid JavaOnlyArray::PushArray(JavaOnlyArray* value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_JavaOnlyArray_pushArray(env, jni_object_.Get(), value->jni_object());\n}\n\nvoid JavaOnlyArray::PushByteArray(uint8_t* buffer, int length) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jbyteArray> jni_byte_array_ref;\n  if (buffer && length > 0) {\n    jni_byte_array_ref = base::android::JNIConvertHelper::ConvertToJNIByteArray(\n        env, std::string(reinterpret_cast<const char*>(buffer), length));\n  }\n  Java_JavaOnlyArray_pushByteArray(env, jni_object_.Get(),\n                                   jni_byte_array_ref.Get());\n}\n\nvoid JavaOnlyArray::PushMap(JavaOnlyMap* value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_JavaOnlyArray_pushMap(env, jni_object_.Get(), value->jni_object());\n}\n\nvoid JavaOnlyArray::PushNull() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_JavaOnlyArray_pushNull(env, jni_object_.Get());\n}\n\nvoid JavaOnlyArray::PushJavaValue(const JavaValue& value) {\n  JavaValue::JavaValueType type = value.type();\n  switch (type) {\n    case JavaValue::JavaValueType::Null:\n      PushNull();\n      break;\n    case JavaValue::JavaValueType::Boolean:\n      PushBoolean(value.Bool());\n      break;\n    case JavaValue::JavaValueType::Double:\n      PushDouble(value.Double());\n      break;\n    case JavaValue::JavaValueType::Int32:\n      PushInt(value.Int32());\n      break;\n    case JavaValue::JavaValueType::Int64:\n      PushInt64(value.Int64());\n      break;\n    case JavaValue::JavaValueType::String: {\n      JNIEnv* env = base::android::AttachCurrentThread();\n      static const int api_level = android_get_device_api_level();\n      // Emoji will make App crash when use `NewStringUTF` API in Android 5.x\n      if (api_level < __ANDROID_API_M__) {\n        PushString(value.String());\n      } else {\n        Java_JavaOnlyArray_pushString(env, jni_object_.Get(), value.JString());\n      }\n      break;\n    }\n    case JavaValue::JavaValueType::ByteArray: {\n      JNIEnv* env = base::android::AttachCurrentThread();\n      Java_JavaOnlyArray_pushByteArray(env, jni_object_.Get(),\n                                       value.JByteArray());\n      break;\n    }\n    case JavaValue::JavaValueType::Array:\n      PushArray(value.Array().get());\n      break;\n    case JavaValue::JavaValueType::Map:\n      PushMap(value.Map().get());\n      break;\n    default:\n      break;\n  }\n}\n\nvoid JavaOnlyArray::Clear() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_JavaOnlyArray_clear(env, jni_object());\n}\n\nvoid JavaOnlyArray::Reset() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  jni_object_.Reset(env, Java_JavaOnlyArray_create(env).Get());\n}\n\nint32_t JavaOnlyArray::JavaOnlyArrayGetSize(JNIEnv* env, jobject array) {\n  return Java_JavaOnlyArray_size(env, array);\n}\n\nReadableType JavaOnlyArray::JavaOnlyArrayGetTypeAtIndex(JNIEnv* env,\n                                                        jobject array,\n                                                        int32_t index) {\n  return static_cast<ReadableType>(\n      Java_JavaOnlyArray_getTypeIndex(env, array, index));\n}\n\nstd::string JavaOnlyArray::JavaOnlyArrayGetStringAtIndex(JNIEnv* env,\n                                                         jobject array,\n                                                         int32_t index) {\n  return base::android::JNIConvertHelper::ConvertToString(\n      env, Java_JavaOnlyArray_getString(env, array, index).Get());\n}\n\nbool JavaOnlyArray::JavaOnlyArrayGetBooleanAtIndex(JNIEnv* env, jobject array,\n                                                   int32_t index) {\n  return Java_JavaOnlyArray_getBoolean(env, array, index);\n}\n\nint32_t JavaOnlyArray::JavaOnlyArrayGetIntAtIndex(JNIEnv* env, jobject array,\n                                                  int32_t index) {\n  return Java_JavaOnlyArray_getInt(env, array, index);\n}\n\nint64_t JavaOnlyArray::JavaOnlyArrayGetLongAtIndex(JNIEnv* env, jobject array,\n                                                   int32_t index) {\n  return Java_JavaOnlyArray_getLong(env, array, index);\n}\n\ndouble JavaOnlyArray::JavaOnlyArrayGetDoubleAtIndex(JNIEnv* env, jobject array,\n                                                    int32_t index) {\n  return Java_JavaOnlyArray_getDouble(env, array, index);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jobject>\nJavaOnlyArray::JavaOnlyArrayGetMapAtIndex(JNIEnv* env, jobject array,\n                                          int32_t index) {\n  return Java_JavaOnlyArray_getMap(env, array, index);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jobject>\nJavaOnlyArray::JavaOnlyArrayGetObjectAtIndex(JNIEnv* env, jobject array,\n                                             int32_t index) {\n  return Java_JavaOnlyArray_getObject(env, array, index);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jobject>\nJavaOnlyArray::JavaOnlyArrayGetArrayAtIndex(JNIEnv* env, jobject array,\n                                            int32_t index) {\n  return Java_JavaOnlyArray_getArray(env, array, index);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jbyteArray>\nJavaOnlyArray::JavaOnlyArrayGetByteArrayAtIndex(JNIEnv* env, jobject array,\n                                                int32_t index) {\n  return Java_JavaOnlyArray_getByteArray(env, array, index);\n}\n\nstd::string jstring2string(JNIEnv* env, jstring jStr) {\n  if (!jStr) return \"\";\n  const char* str = env->GetStringUTFChars(jStr, JNI_FALSE);\n  std::string result = str;\n  env->ReleaseStringUTFChars(jStr, str);\n  return str;\n}\nJavaValue JavaOnlyArray::JavaOnlyArrayGetJavaValueAtIndex(JNIEnv* env,\n                                                          jobject array,\n                                                          size_t index) {\n  ReadableType type = JavaOnlyArrayGetTypeAtIndex(env, array, index);\n  switch (type) {\n    case Null: {\n      return JavaValue();\n    }\n    case Boolean: {\n      return JavaValue(JavaOnlyArrayGetBooleanAtIndex(env, array, index));\n    }\n    case Int: {\n      return JavaValue(JavaOnlyArrayGetIntAtIndex(env, array, index));\n    }\n    case Number: {\n      return JavaValue(JavaOnlyArrayGetDoubleAtIndex(env, array, index));\n    }\n    case String: {\n      return JavaValue(JavaOnlyArrayGetStringAtIndex(env, array, index));\n    }\n    case Map: {\n      return JavaValue(std::make_shared<base::android::JavaOnlyMap>(\n          env, JavaOnlyArrayGetMapAtIndex(env, array, index)));\n    }\n    case Array: {\n      return JavaValue(std::make_shared<base::android::JavaOnlyArray>(\n          env, JavaOnlyArrayGetArrayAtIndex(env, array, index)));\n    }\n    case Long: {\n      return JavaValue(JavaOnlyArrayGetLongAtIndex(env, array, index));\n    }\n    case ByteArray: {\n      lynx::base::android::ScopedLocalJavaRef<jbyteArray> j_byte_array =\n          JavaOnlyArrayGetByteArrayAtIndex(env, array, index);\n      auto* array_ptr =\n          env->GetByteArrayElements(j_byte_array.Get(), JNI_FALSE);\n      size_t len = env->GetArrayLength(j_byte_array.Get());\n      JavaValue ret =\n          JavaValue(reinterpret_cast<const uint8_t*>(array_ptr), len);\n      env->ReleaseByteArrayElements(j_byte_array.Get(), array_ptr, JNI_FALSE);\n      return ret;\n    }\n    case LynxObject: {\n      return JavaValue(JavaOnlyArrayGetObjectAtIndex(env, array, index),\n                       JavaValue::JavaValueType::LynxObject);\n    }\n    case PiperData: {\n      return JavaValue(JavaOnlyArrayGetObjectAtIndex(env, array, index),\n                       JavaValue::JavaValueType::Transfer);\n    }\n    case ByteBuffer: {\n      return JavaValue();\n    }\n    case TemplateData: {\n      return JavaValue(JavaOnlyArrayGetObjectAtIndex(env, array, index),\n                       JavaValue::JavaValueType::TemplateData);\n    }\n  }\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/java_only_array.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_ANDROID_JAVA_ONLY_ARRAY_H_\n#define CORE_BASE_ANDROID_JAVA_ONLY_ARRAY_H_\n\n#include <memory>\n#include <string>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"third_party/rapidjson/document.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nenum ReadableType {\n  Null,\n  Boolean,\n  Int,\n  Number,\n  String,\n  Map,\n  Array,\n  Long,\n  ByteArray,\n  PiperData,\n  LynxObject,\n  ByteBuffer,\n  TemplateData,\n};\n\nstd::string jstring2string(JNIEnv* env, jstring jStr);\n\nclass JavaOnlyMap;\nclass JavaValue;\nclass JavaOnlyArray {\n public:\n  JavaOnlyArray();\n  JavaOnlyArray(JNIEnv* env,\n                const lynx::base::android::ScopedLocalJavaRef<jobject>& ref)\n      : jni_object_(env, ref.Get()) {}\n\n  std::unique_ptr<base::android::JavaOnlyArray> ShallowCopy();\n  ~JavaOnlyArray() = default;\n\n  void PushString(const std::string& value);\n  void PushBoolean(bool value);\n  void PushInt(int value);\n  void PushInt64(int64_t value);\n  void PushDouble(double value);\n  void PushArray(JavaOnlyArray* value);\n  void PushByteArray(uint8_t* buffer, int length);\n  void PushMap(JavaOnlyMap* value);\n  void PushNull();\n  void PushJavaValue(const JavaValue& value);\n  void Clear();\n  void Reset();\n\n  inline jobject jni_object() { return jni_object_.Get(); }\n\n  JavaOnlyArray(JNIEnv* env, jobject jni_object)\n      : jni_object_(env, jni_object) {}\n  explicit JavaOnlyArray(base::android::ScopedGlobalJavaRef<jobject> ref)\n      : jni_object_(ref) {}\n\n  static int32_t JavaOnlyArrayGetSize(JNIEnv* env, jobject array);\n  static ReadableType JavaOnlyArrayGetTypeAtIndex(JNIEnv* env, jobject array,\n                                                  int32_t index);\n  static std::string JavaOnlyArrayGetStringAtIndex(JNIEnv* env, jobject array,\n                                                   int32_t index);\n  static bool JavaOnlyArrayGetBooleanAtIndex(JNIEnv* env, jobject array,\n                                             int32_t index);\n  static int32_t JavaOnlyArrayGetIntAtIndex(JNIEnv* env, jobject array,\n                                            int32_t index);\n  static int64_t JavaOnlyArrayGetLongAtIndex(JNIEnv* env, jobject array,\n                                             int32_t index);\n  static double JavaOnlyArrayGetDoubleAtIndex(JNIEnv* env, jobject array,\n                                              int32_t index);\n  static lynx::base::android::ScopedLocalJavaRef<jobject>\n  JavaOnlyArrayGetMapAtIndex(JNIEnv* env, jobject array, int32_t index);\n  static lynx::base::android::ScopedLocalJavaRef<jobject>\n  JavaOnlyArrayGetArrayAtIndex(JNIEnv* env, jobject array, int32_t index);\n\n  static lynx::base::android::ScopedLocalJavaRef<jobject>\n  JavaOnlyArrayGetObjectAtIndex(JNIEnv* env, jobject array, int32_t index);\n  static lynx::base::android::ScopedLocalJavaRef<jbyteArray>\n  JavaOnlyArrayGetByteArrayAtIndex(JNIEnv* env, jobject array, int32_t index);\n\n  static JavaValue JavaOnlyArrayGetJavaValueAtIndex(JNIEnv* env, jobject array,\n                                                    size_t index);\n\n private:\n  base::android::ScopedGlobalJavaRef<jobject> jni_object_;\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_JAVA_ONLY_ARRAY_H_\n"
  },
  {
    "path": "core/base/android/java_only_map.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/android/java_only_map.h\"\n\n#include <android/api-level.h>\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"core/base/android/android_jni.h\"\n#include \"core/base/android/java_only_array.h\"\n#include \"core/base/android/java_value.h\"\n#include \"core/base/android/jni_helper.h\"\n#include \"core/base/android/piper_data.h\"\n#include \"core/base/js_constants.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/JavaOnlyMap_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/JavaOnlyMap_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForJavaOnlyMap(JNIEnv* env) { return RegisterNativesImpl(env); }\n}  // namespace jni\n}  // namespace lynx\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nJavaOnlyMap::JavaOnlyMap() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  jni_object_.Reset(env, Java_JavaOnlyMap_create(env).Get());\n}\n\nstd::unique_ptr<base::android::JavaOnlyMap> JavaOnlyMap::ShallowCopy() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  auto map = Java_JavaOnlyMap_shallowCopy(env, jni_object_.Get());\n  return std::make_unique<base::android::JavaOnlyMap>(env, map);\n}\n\nvoid JavaOnlyMap::PushString(const std::string& key, const std::string& value) {\n  PushString(key.c_str(), value.c_str());\n}\n\nvoid JavaOnlyMap::PushString(const char* key, const char* value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n\n  static const int api_level = android_get_device_api_level();\n  // Emoji will make App crash when use `NewStringUTF` API in Android 5.x\n  if (api_level < __ANDROID_API_M__) {  // Build.VERSION_CODES.M\n    base::android::ScopedLocalJavaRef<jbyteArray> jni_key =\n        JNIConvertHelper::ConvertToJNIByteArray(env, key);\n    base::android::ScopedLocalJavaRef<jbyteArray> jni_value =\n        JNIConvertHelper::ConvertToJNIByteArray(env, value);\n    Java_JavaOnlyMap_putByteArrayAsString(env, jni_object_.Get(), jni_key.Get(),\n                                          jni_value.Get());\n  } else {\n    base::android::ScopedLocalJavaRef<jstring> jni_key =\n        JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n    base::android::ScopedLocalJavaRef<jstring> jni_value =\n        JNIConvertHelper::ConvertToJNIStringUTF(env, value);\n    Java_JavaOnlyMap_putString(env, jni_object_.Get(), jni_key.Get(),\n                               jni_value.Get());\n  }\n}\n\nvoid JavaOnlyMap::PushNull(const char* key) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  Java_JavaOnlyMap_putNull(env, jni_object_.Get(), jni_key.Get());\n}\n\nvoid JavaOnlyMap::PushBoolean(const std::string& key, bool value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  Java_JavaOnlyMap_putBoolean(env, jni_object_.Get(), jni_key.Get(), value);\n}\n\nvoid JavaOnlyMap::PushInt(const std::string& key, int value) {\n  PushInt(key.c_str(), value);\n}\n\nvoid JavaOnlyMap::PushInt(const char* key, int value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  Java_JavaOnlyMap_putInt(env, jni_object_.Get(), jni_key.Get(), value);\n}\n\nvoid JavaOnlyMap::PushInt64(const char* key, int64_t value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  Java_JavaOnlyMap_putLong(env, jni_object_.Get(), jni_key.Get(), value);\n}\n\nvoid JavaOnlyMap::PushDouble(const std::string& key, double value) {\n  PushDouble(key.c_str(), value);\n}\n\nvoid JavaOnlyMap::PushDouble(const char* key, double value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  Java_JavaOnlyMap_putDouble(env, jni_object_.Get(), jni_key.Get(), value);\n}\n\nvoid JavaOnlyMap::PushMap(const std::string& key, JavaOnlyMap* value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  Java_JavaOnlyMap_putMap(env, jni_object_.Get(), jni_key.Get(),\n                          value->jni_object());\n}\n\nvoid JavaOnlyMap::PushArray(const std::string& key, JavaOnlyArray* value) {\n  PushArray(key.c_str(), value);\n}\n\nvoid JavaOnlyMap::PushArray(const char* key, JavaOnlyArray* value) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  Java_JavaOnlyMap_putArray(env, jni_object_.Get(), jni_key.Get(),\n                            value->jni_object());\n}\n\nvoid JavaOnlyMap::PushByteArray(const std::string& key, uint8_t* buffer,\n                                int length) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jbyteArray> jni_byte_array_ref;\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      base::android::JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  if (buffer && length > 0) {\n    jni_byte_array_ref = base::android::JNIConvertHelper::ConvertToJNIByteArray(\n        env, std::string(reinterpret_cast<const char*>(buffer), length));\n  }\n  Java_JavaOnlyMap_putByteArray(env, jni_object_.Get(), jni_key.Get(),\n                                jni_byte_array_ref.Get());\n}\n\nvoid JavaOnlyMap::PushByteBuffer(const std::string& key, jobject byte_buffer) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jobject> jni_byte_buffer_ref(env,\n                                                                 byte_buffer);\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      base::android::JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  Java_JavaOnlyMap_putByteBuffer(env, jni_object_.Get(), jni_key.Get(),\n                                 jni_byte_buffer_ref.Get());\n}\n\nvoid JavaOnlyMap::PushJavaValue(const std::string& key,\n                                const JavaValue& value) {\n  JavaValue::JavaValueType type = value.type();\n  switch (type) {\n    case JavaValue::JavaValueType::Null:\n      PushNull(key.c_str());\n      break;\n    case JavaValue::JavaValueType::Boolean:\n      PushBoolean(key, value.Bool());\n      break;\n    case JavaValue::JavaValueType::Double:\n      PushDouble(key, value.Double());\n      break;\n    case JavaValue::JavaValueType::Int32:\n      PushInt(key, value.Int32());\n      break;\n    case JavaValue::JavaValueType::Int64:\n      PushInt64(key.c_str(), value.Int64());\n      break;\n    case JavaValue::JavaValueType::String: {\n      JNIEnv* env = base::android::AttachCurrentThread();\n      base::android::ScopedLocalJavaRef<jstring> jni_key =\n          JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n      Java_JavaOnlyMap_putString(env, jni_object_.Get(), jni_key.Get(),\n                                 value.JString());\n      break;\n    }\n    case JavaValue::JavaValueType::ByteArray: {\n      JNIEnv* env = base::android::AttachCurrentThread();\n      base::android::ScopedLocalJavaRef<jstring> jni_key =\n          JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n      Java_JavaOnlyMap_putByteArray(env, jni_object_.Get(), jni_key.Get(),\n                                    value.JByteArray());\n      break;\n    }\n    case JavaValue::JavaValueType::Array:\n      PushArray(key, value.Array().get());\n      break;\n    case JavaValue::JavaValueType::Map:\n      PushMap(key, value.Map().get());\n      break;\n    default:\n      break;\n  }\n}\n\nint JavaOnlyMap::Size() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n\n  return static_cast<int>(Java_JavaOnlyMap_size(env, jni_object_.Get()));\n}\n\nbool JavaOnlyMap::Contains(const char* key) const {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedLocalJavaRef<jstring> jni_key =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  return static_cast<ReadableType>(Java_JavaOnlyMap_getTypeIndex(\n             env, jni_object_.Get(), jni_key.Get())) !=\n         lynx::base::android::ReadableType::Null;\n}\n\nvoid JavaOnlyMap::Reset() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  jni_object_.Reset(env, Java_JavaOnlyMap_create(env).Get());\n}\n\nlynx::base::android::ScopedLocalJavaRef<jobject>\nJavaOnlyMap::JavaOnlyMapGetKeys(JNIEnv* env, jobject map) {\n  return Java_JavaOnlyMap_getKeys(env, map);\n}\n\nReadableType JavaOnlyMap::JavaOnlyMapGetTypeAtIndex(JNIEnv* env, jobject map,\n                                                    jstring key) {\n  return static_cast<ReadableType>(\n      Java_JavaOnlyMap_getTypeIndex(env, map, key));\n}\n\nstd::string JavaOnlyMap::JavaOnlyMapGetStringAtIndex(JNIEnv* env, jobject map,\n                                                     jstring key) {\n  return base::android::JNIConvertHelper::ConvertToString(\n      env, Java_JavaOnlyMap_getString(env, map, key).Get());\n}\n\nbool JavaOnlyMap::JavaOnlyMapGetBooleanAtIndex(JNIEnv* env, jobject map,\n                                               jstring key) {\n  return Java_JavaOnlyMap_getBoolean(env, map, key);\n}\n\nint32_t JavaOnlyMap::JavaOnlyMapGetIntAtIndex(JNIEnv* env, jobject map,\n                                              jstring key) {\n  return Java_JavaOnlyMap_getInt(env, map, key);\n}\n\nint64_t JavaOnlyMap::JavaOnlyMapGetLongAtIndex(JNIEnv* env, jobject map,\n                                               jstring key) {\n  return Java_JavaOnlyMap_getLong(env, map, key);\n}\n\ndouble JavaOnlyMap::JavaOnlyMapGetDoubleAtIndex(JNIEnv* env, jobject map,\n                                                jstring key) {\n  return Java_JavaOnlyMap_getDouble(env, map, key);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jobject>\nJavaOnlyMap::JavaOnlyMapGetTemplateDataAtIndex(JNIEnv* env, jobject map,\n                                               jstring key) {\n  return Java_JavaOnlyMap_getTemplateData(env, map, key);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jobject>\nJavaOnlyMap::JavaOnlyMapGetMapAtIndex(JNIEnv* env, jobject map, jstring key) {\n  return Java_JavaOnlyMap_getMap(env, map, key);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jobject>\nJavaOnlyMap::JavaOnlyMapGetArrayAtIndex(JNIEnv* env, jobject map, jstring key) {\n  return Java_JavaOnlyMap_getArray(env, map, key);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jbyteArray>\nJavaOnlyMap::JavaOnlyMapGetByteArrayAtIndex(JNIEnv* env, jobject map,\n                                            jstring key) {\n  return Java_JavaOnlyMap_getByteArray(env, map, key);\n}\n\nlynx::base::android::ScopedLocalJavaRef<jobject>\nJavaOnlyMap::JavaOnlyMapGetPiperDataAtIndex(JNIEnv* env, jobject map,\n                                            jstring key) {\n  return Java_JavaOnlyMap_getPiperData(env, map, key);\n}\n\nvoid JavaOnlyMap::ForEach(JNIEnv* env, jobject map, ForEachClosure closure,\n                          OnSizeAwareClosure size_aware_closure) {\n  auto keys = base::android::JavaOnlyMap::JavaOnlyMapGetKeys(env, map);\n\n  jclass cls_arraylist = env->GetObjectClass(keys.Get());\n  base::android::ScopedGlobalJavaRef<jclass> cls_arraylist_global(\n      env, cls_arraylist);\n  env->DeleteLocalRef(cls_arraylist);\n\n  jmethodID arraylist_get = env->GetMethodID(cls_arraylist_global.Get(), \"get\",\n                                             \"(I)Ljava/lang/Object;\");\n  jmethodID arraylist_size =\n      env->GetMethodID(cls_arraylist_global.Get(), \"size\", \"()I\");\n  jint module_len = env->CallIntMethod(keys.Get(), arraylist_size);\n\n  if (size_aware_closure) {\n    // Let caller do reserve.\n    size_aware_closure(module_len);\n  }\n\n  for (int i = 0; i < module_len; i++) {\n    jstring key_str = static_cast<jstring>(\n        env->CallObjectMethod(keys.Get(), arraylist_get, i));\n    base::android::ScopedLocalJavaRef<jstring> key_jstring(env, key_str);\n    std::string key = base::android::JNIConvertHelper::ConvertToString(\n        env, key_jstring.Get());\n\n    closure(env, map, key_jstring.Get(), key);\n  }\n}\n\nJavaValue JavaOnlyMap::JavaOnlyMapGetJavaValueAtIndex(JNIEnv* env, jobject map,\n                                                      jstring key) {\n  base::android::ReadableType type =\n      base::android::JavaOnlyMap::JavaOnlyMapGetTypeAtIndex(env, map, key);\n  switch (type) {\n    case Null: {\n      return JavaValue();\n    }\n    case Boolean: {\n      return JavaValue(JavaOnlyMapGetBooleanAtIndex(env, map, key));\n    }\n    case Int: {\n      return JavaValue(JavaOnlyMapGetIntAtIndex(env, map, key));\n    }\n    case Number: {\n      return JavaValue(JavaOnlyMapGetDoubleAtIndex(env, map, key));\n    }\n    case String: {\n      return JavaValue(JavaOnlyMapGetStringAtIndex(env, map, key));\n    }\n    case Map: {\n      return JavaValue(std::make_shared<base::android::JavaOnlyMap>(\n          env, JavaOnlyMapGetMapAtIndex(env, map, key)));\n    }\n    case Array: {\n      return JavaValue(std::make_shared<base::android::JavaOnlyArray>(\n          env, JavaOnlyMapGetArrayAtIndex(env, map, key)));\n    }\n    case Long: {\n      return JavaValue(JavaOnlyMapGetLongAtIndex(env, map, key));\n    }\n    case ByteArray: {\n      lynx::base::android::ScopedLocalJavaRef<jbyteArray> j_byte_array =\n          JavaOnlyMapGetByteArrayAtIndex(env, map, key);\n      auto* array_ptr =\n          env->GetByteArrayElements(j_byte_array.Get(), JNI_FALSE);\n      size_t len = env->GetArrayLength(j_byte_array.Get());\n      JavaValue ret =\n          JavaValue(reinterpret_cast<const uint8_t*>(array_ptr), len);\n      env->ReleaseByteArrayElements(j_byte_array.Get(), array_ptr, JNI_FALSE);\n      return ret;\n    }\n    case PiperData: {\n      return JavaValue(JavaOnlyMapGetPiperDataAtIndex(env, map, key),\n                       JavaValue::JavaValueType::Transfer);\n    }\n    case LynxObject: {\n      // LynxObject should not be operated in JavaOnlyMap\n      return JavaValue();\n    }\n    case ByteBuffer: {\n      return JavaValue();\n    }\n    case TemplateData: {\n      return JavaValue(JavaOnlyMapGetTemplateDataAtIndex(env, map, key),\n                       JavaValue::JavaValueType::TemplateData);\n    }\n  }\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/java_only_map.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_ANDROID_JAVA_ONLY_MAP_H_\n#define CORE_BASE_ANDROID_JAVA_ONLY_MAP_H_\n#include <memory>\n#include <string>\n\n#include \"base/include/closure.h\"\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"base/include/value/base_value.h\"\n#include \"core/base/android/java_only_array.h\"\n#include \"third_party/rapidjson/document.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\nclass JavaOnlyArray;\nclass JavaValue;\nclass JavaOnlyMap {\n public:\n  // return type: void\n  // arguments: JNIENV, JavaOnlyMap, jstring key, std::string key\n  using ForEachClosure = base::MoveOnlyClosure<void, JNIEnv*, jobject, jstring,\n                                               const std::string&>;\n  using OnSizeAwareClosure = base::MoveOnlyClosure<void, jint>;\n\n  JavaOnlyMap();\n\n  JavaOnlyMap(JNIEnv* env,\n              const lynx::base::android::ScopedLocalJavaRef<jobject>& ref)\n      : jni_object_(env, ref.Get()) {}\n\n  std::unique_ptr<base::android::JavaOnlyMap> ShallowCopy();\n\n  ~JavaOnlyMap() = default;\n\n  void PushString(const std::string& key, const std::string& value);\n  void PushString(const char* key, const char* value);\n  void PushNull(const char* key);\n  void PushBoolean(const std::string& key, bool value);\n  void PushInt(const std::string& key, int value);\n  void PushInt(const char* key, int value);\n  void PushInt64(const char* key, int64_t value);\n  void PushDouble(const std::string& key, double value);\n  void PushDouble(const char* key, double value);\n  void PushMap(const std::string& key, JavaOnlyMap* value);\n  void PushArray(const std::string& key, JavaOnlyArray* value);\n  void PushArray(const char* key, JavaOnlyArray* value);\n  void PushByteArray(const std::string& key, uint8_t* buffer, int length);\n  void PushJavaValue(const std::string& key, const JavaValue& value);\n  void PushByteBuffer(const std::string& key, jobject byte_buffer);\n\n  bool Contains(const char* key) const;\n\n  int Size();\n\n  void Reset();\n\n  inline jobject jni_object() { return jni_object_.Get(); }\n\n  static lynx::base::android::ScopedLocalJavaRef<jobject> JavaOnlyMapGetKeys(\n      JNIEnv* env, jobject map);\n  static ReadableType JavaOnlyMapGetTypeAtIndex(JNIEnv* env, jobject map,\n                                                jstring key);\n  static std::string JavaOnlyMapGetStringAtIndex(JNIEnv* env, jobject map,\n                                                 jstring key);\n  static bool JavaOnlyMapGetBooleanAtIndex(JNIEnv* env, jobject map,\n                                           jstring key);\n  static int32_t JavaOnlyMapGetIntAtIndex(JNIEnv* env, jobject map,\n                                          jstring key);\n  static int64_t JavaOnlyMapGetLongAtIndex(JNIEnv* env, jobject map,\n                                           jstring key);\n  static double JavaOnlyMapGetDoubleAtIndex(JNIEnv* env, jobject map,\n                                            jstring key);\n  static lynx::base::android::ScopedLocalJavaRef<jobject>\n  JavaOnlyMapGetTemplateDataAtIndex(JNIEnv* env, jobject map, jstring key);\n  static lynx::base::android::ScopedLocalJavaRef<jobject>\n  JavaOnlyMapGetMapAtIndex(JNIEnv* env, jobject map, jstring key);\n  static lynx::base::android::ScopedLocalJavaRef<jobject>\n  JavaOnlyMapGetArrayAtIndex(JNIEnv* env, jobject map, jstring key);\n  static lynx::base::android::ScopedLocalJavaRef<jbyteArray>\n  JavaOnlyMapGetByteArrayAtIndex(JNIEnv* env, jobject map, jstring key);\n  static lynx::base::android::ScopedLocalJavaRef<jobject>\n  JavaOnlyMapGetPiperDataAtIndex(JNIEnv* env, jobject map, jstring key);\n\n  static JavaValue JavaOnlyMapGetJavaValueAtIndex(JNIEnv* env, jobject map,\n                                                  jstring key);\n\n  static void ForEach(JNIEnv* env, jobject map, ForEachClosure closure,\n                      OnSizeAwareClosure size_aware_closure = nullptr);\n\n private:\n  base::android::ScopedGlobalJavaRef<jobject> jni_object_;\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_JAVA_ONLY_MAP_H_\n"
  },
  {
    "path": "core/base/android/java_value.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/android/java_value.h\"\n\n#include \"base/include/platform/android/jni_convert_helper.h\"\n#include \"core/base/android/java_only_array.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nnamespace converter {\n\ntemplate <typename T>\nstatic jobject valueOf(JNIEnv* env, jclass c, const char* signature,\n                       const T& value) {\n  static jmethodID valueOfMethod =\n      env->GetStaticMethodID(c, \"valueOf\", signature);\n  return env->CallStaticObjectMethod(c, valueOfMethod, value);\n}\n\ntemplate <typename T>\nstatic double doubleValue(JNIEnv* env, jclass c, const T& value) {\n  static jmethodID doubleValueMethod =\n      env->GetMethodID(c, \"doubleValue\", \"()D\");\n  return env->CallDoubleMethod(value, doubleValueMethod);\n}\n\njobject charValueOf(JNIEnv* env, jchar value) {\n  static auto cls = base::android::GetGlobalClass(env, \"java/lang/Character\");\n  return valueOf(env, cls.Get(), \"(C)Ljava/lang/Character;\", value);\n}\n\njobject byteValueOf(JNIEnv* env, jbyte value) {\n  static auto cls = base::android::GetGlobalClass(env, \"java/lang/Byte\");\n  return valueOf(env, cls.Get(), \"(B)Ljava/lang/Byte;\", value);\n}\n\njobject booleanValueOf(JNIEnv* env, jboolean value) {\n  static auto cls = base::android::GetGlobalClass(env, \"java/lang/Boolean\");\n  return valueOf(env, cls.Get(), \"(Z)Ljava/lang/Boolean;\", value);\n}\n\njobject shortValueOf(JNIEnv* env, jshort value) {\n  static auto cls = base::android::GetGlobalClass(env, \"java/lang/Short\");\n  return valueOf(env, cls.Get(), \"(S)Ljava/lang/Short;\", value);\n}\n\njobject integerValueOf(JNIEnv* env, jint value) {\n  static auto cls = base::android::GetGlobalClass(env, \"java/lang/Integer\");\n  return valueOf(env, cls.Get(), \"(I)Ljava/lang/Integer;\", value);\n}\n\njobject longValueOf(JNIEnv* env, jlong value) {\n  static auto cls = base::android::GetGlobalClass(env, \"java/lang/Long\");\n  return valueOf(env, cls.Get(), \"(J)Ljava/lang/Long;\", value);\n}\njobject floatValueOf(JNIEnv* env, jfloat value) {\n  static auto cls = base::android::GetGlobalClass(env, \"java/lang/Float\");\n  return valueOf(env, cls.Get(), \"(F)Ljava/lang/Float;\", value);\n}\n\njobject doubleValueOf(JNIEnv* env, jdouble value) {\n  static auto cls = base::android::GetGlobalClass(env, \"java/lang/Double\");\n  return valueOf(env, cls.Get(), \"(D)Ljava/lang/Double;\", value);\n}\n\n}  // namespace converter\n\nJavaValue::JavaValue(bool value) : type_(JavaValueType::Boolean) {\n  jvalue j_value;\n  j_value.z = static_cast<jboolean>(value);\n  j_variant_value_ = j_value;\n}\n\nJavaValue::JavaValue(double value) : type_(JavaValueType::Double) {\n  jvalue j_value;\n  j_value.d = static_cast<jdouble>(value);\n  j_variant_value_ = j_value;\n}\n\nJavaValue::JavaValue(float value) : type_(JavaValueType::Float) {\n  jvalue j_value;\n  j_value.f = static_cast<jfloat>(value);\n  j_variant_value_ = j_value;\n}\n\nJavaValue::JavaValue(int32_t value) : type_(JavaValueType::Int32) {\n  jvalue j_value;\n  j_value.i = static_cast<jint>(value);\n  j_variant_value_ = j_value;\n}\n\nJavaValue::JavaValue(int64_t value) : type_(JavaValueType::Int64) {\n  jvalue j_value;\n  j_value.j = static_cast<jlong>(value);\n  j_variant_value_ = j_value;\n}\n\nJavaValue::JavaValue(const std::string& value) : type_(JavaValueType::String) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_variant_value_ = lynx::base::android::ScopedGlobalJavaRef<jobject>(\n      env, JNIConvertHelper::ConvertToJNIStringUTF(env, value).Get());\n}\n\nJavaValue::JavaValue(jstring str) : type_(JavaValueType::String) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_variant_value_ =\n      lynx::base::android::ScopedGlobalJavaRef<jobject>(env, str);\n}\n\nJavaValue::JavaValue(const uint8_t* value, size_t length)\n    : type_(JavaValueType::ByteArray) {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_variant_value_ = lynx::base::android::ScopedGlobalJavaRef<jobject>(\n      env, JNIConvertHelper::ConvertToJNIByteArray(\n               env, std::string(reinterpret_cast<const char*>(value), length))\n               .Get());\n}\n\nbool JavaValue::Bool() const {\n  if (IsBool()) {\n    return std::get<jvalue>(j_variant_value_).z;\n  }\n  return false;\n}\n\nint32_t JavaValue::Int32() const {\n  if (IsInt32()) {\n    return std::get<jvalue>(j_variant_value_).i;\n  }\n  return 0;\n}\n\nint64_t JavaValue::Int64() const {\n  if (IsInt64()) {\n    return std::get<jvalue>(j_variant_value_).j;\n  }\n  return 0;\n}\n\nfloat JavaValue::Float() const {\n  if (IsFloat()) {\n    return std::get<jvalue>(j_variant_value_).f;\n  }\n  return 0;\n}\n\ndouble JavaValue::Double() const {\n  if (IsDouble()) {\n    return std::get<jvalue>(j_variant_value_).d;\n  }\n  return 0;\n}\n\nconst std::string& JavaValue::String() const {\n  if (IsString()) {\n    if (string_cache_) {\n      return *string_cache_;\n    }\n    jstring java_str_ref = reinterpret_cast<jstring>(\n        std::get<base::android::ScopedGlobalJavaRef<jobject>>(j_variant_value_)\n            .Get());\n    if (java_str_ref == nullptr) {\n      return base::String().str();\n    }\n    JNIEnv* env = base::android::AttachCurrentThread();\n    const char* str = env->GetStringUTFChars(java_str_ref, NULL);\n    if (str) {\n      string_cache_ = std::make_optional<std::string>(str);\n    }\n    env->ReleaseStringUTFChars(java_str_ref, str);\n    return *string_cache_;\n  }\n  return base::String().str();\n}\nJavaValue JavaValue::GetValueForKey(const std::string& key) const {\n  if (!IsMap()) {\n    return JavaValue();\n  }\n  JNIEnv* env = base::android::AttachCurrentThread();\n  base::android::ScopedGlobalJavaRef<jstring> j_key =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, key);\n  return JavaOnlyMap::JavaOnlyMapGetJavaValueAtIndex(\n      env,\n      std::get<std::shared_ptr<base::android::JavaOnlyMap>>(j_variant_value_)\n          ->jni_object(),\n      j_key.Get());\n}\nuint8_t* JavaValue::ArrayBuffer() const {\n  if (!IsArrayBuffer()) {\n    return nullptr;\n  }\n  if (!array_buffer_ptr_cache_.empty()) {\n    return array_buffer_ptr_cache_.data();\n  }\n  JNIEnv* env = base::android::AttachCurrentThread();\n  jbyte* j_byte = env->GetByteArrayElements(JByteArray(), JNI_FALSE);\n  if (j_byte == nullptr) {\n    return nullptr;\n  }\n  array_buffer_ptr_cache_ =\n      std::vector<uint8_t>(j_byte, j_byte + env->GetArrayLength(JByteArray()));\n  env->ReleaseByteArrayElements(JByteArray(), j_byte, 0);\n  return array_buffer_ptr_cache_.data();\n}\nJavaValue JavaValue::GetValueForIndex(uint32_t index) const {\n  if (!IsArray()) {\n    return JavaValue();\n  }\n  JNIEnv* env = base::android::AttachCurrentThread();\n  return JavaOnlyArray::JavaOnlyArrayGetJavaValueAtIndex(\n      env,\n      std::get<std::shared_ptr<base::android::JavaOnlyArray>>(j_variant_value_)\n          ->jni_object(),\n      index);\n}\n\njvalue JavaValue::JByte() const {\n  jvalue j_byte;\n  // No exception is thrown, truncated from the last 8 bits\n  if (IsInt32()) {\n    j_byte.b = static_cast<jbyte>(Int32());\n  } else if (IsDouble()) {\n    j_byte.b = static_cast<jbyte>(Double());\n  } else {\n    j_byte.b = 0;\n  }\n  return j_byte;\n}\n\njvalue JavaValue::WrapperJByte() const {\n  jvalue j_wrapper_byte;\n  JNIEnv* env = base::android::AttachCurrentThread();\n  // No exception is thrown, truncated from the last 8 bits\n  j_wrapper_byte.l = converter::byteValueOf(env, JByte().b);\n  return j_wrapper_byte;\n}\n\njvalue JavaValue::JChar() const {\n  jvalue j_char;\n  char c_result = '\\0';\n  if (!String().empty()) {\n    c_result = String()[0];\n  }\n  j_char.c = static_cast<jchar>(c_result);\n  return j_char;\n}\n\njvalue JavaValue::WrapperJChar() const {\n  jvalue j_wrapper_char;\n  char c_result = '\\0';\n  if (!String().empty()) {\n    c_result = String()[0];\n  }\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_wrapper_char.l = converter::charValueOf(env, static_cast<jchar>(c_result));\n  return j_wrapper_char;\n}\n\njvalue JavaValue::JBoolean() const {\n  jvalue j_boolean;\n  j_boolean.z = static_cast<jboolean>(Bool());\n  return j_boolean;\n}\n\njvalue JavaValue::WrapperJBoolean() const {\n  jvalue j_wrapper_boolean;\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_wrapper_boolean.l =\n      converter::booleanValueOf(env, static_cast<jboolean>(Bool()));\n  return j_wrapper_boolean;\n}\n\njvalue JavaValue::JShort() const {\n  jvalue j_short;\n  j_short.s = static_cast<jshort>(Number());\n  return j_short;\n}\n\njvalue JavaValue::WrapperJShort() const {\n  jvalue j_wrapper_short;\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_wrapper_short.l = converter::shortValueOf(env, JShort().s);\n  return j_wrapper_short;\n}\n\njvalue JavaValue::JInt() const {\n  jvalue j_int;\n  j_int.i = static_cast<jint>(Number());\n  return j_int;\n}\n\njvalue JavaValue::WrapperJInt() const {\n  jvalue j_wrapper_int;\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_wrapper_int.l = converter::integerValueOf(env, JInt().i);\n  return j_wrapper_int;\n}\n\njvalue JavaValue::JLong() const {\n  jvalue j_long;\n  if (IsInt64()) {\n    j_long.j = static_cast<jlong>(Int64());\n  } else {\n    j_long.j = static_cast<jlong>(Double());\n  }\n  return j_long;\n}\n\njvalue JavaValue::WrapperJLong() const {\n  jvalue j_wrapper_long;\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_wrapper_long.l = converter::longValueOf(env, JLong().j);\n  return j_wrapper_long;\n}\n\njvalue JavaValue::JFloat() const {\n  jvalue j_float;\n  j_float.f = static_cast<jfloat>(Number());\n  return j_float;\n}\n\njvalue JavaValue::WrapperJFloat() const {\n  jvalue j_wrapper_float;\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_wrapper_float.l =\n      converter::floatValueOf(env, static_cast<jfloat>(JFloat().f));\n  return j_wrapper_float;\n}\n\njvalue JavaValue::JDouble() const {\n  jvalue j_double;\n  j_double.d = static_cast<jdouble>(Number());\n  return j_double;\n}\n\njvalue JavaValue::WrapperJDouble() const {\n  jvalue j_wrapper_double;\n  JNIEnv* env = base::android::AttachCurrentThread();\n  j_wrapper_double.l =\n      converter::doubleValueOf(env, static_cast<jdouble>(JDouble().d));\n  return j_wrapper_double;\n}\n\njvalue JavaValue::JNull() const {\n  jvalue j_null;\n  j_null.l = nullptr;\n  return j_null;\n}\nint JavaValue::Length() const {\n  if (IsArray()) {\n    JNIEnv* env = base::android::AttachCurrentThread();\n    return JavaOnlyArray::JavaOnlyArrayGetSize(env, Array()->jni_object());\n  } else if (IsArrayBuffer()) {\n    JNIEnv* env = base::android::AttachCurrentThread();\n    return static_cast<int>(env->GetArrayLength(JByteArray()));\n  } else {\n    return 0;\n  }\n}\n\ndouble JavaValue::Number() const {\n  if (IsDouble()) {\n    return Double();\n  } else if (IsInt32()) {\n    return Int32();\n  } else if (IsInt64()) {\n    return Int64();\n  } else if (IsFloat()) {\n    return Float();\n  }\n  return 0;\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/java_value.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_ANDROID_JAVA_VALUE_H_\n#define CORE_BASE_ANDROID_JAVA_VALUE_H_\n\n#include <climits>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/table.h\"\n#include \"core/base/android/android_jni.h\"\n#include \"core/base/android/java_only_array.h\"\n#include \"core/base/android/java_only_map.h\"\n#include \"core/base/js_constants.h\"\n#include \"core/public/jsb/lynx_module_callback.h\"\n#include \"core/public/pub_value.h\"\n#include \"core/renderer/utils/value_utils.h\"\n#include \"core/runtime/js/jsi/jsi.h\"\nnamespace lynx {\nnamespace base {\nnamespace android {\n\n// TODO(zhangqun.29) remove convert function in callback_invoker_.cc\nnamespace converter {\ntemplate <typename T>\nstatic jobject valueOf(JNIEnv* env, jclass c, const char* signature,\n                       const T& value);\ntemplate <typename T>\nstatic double doubleValue(JNIEnv* env, jclass c, const T& value);\njobject charValueOf(JNIEnv* env, jchar value);\njobject byteValueOf(JNIEnv* env, jbyte value);\njobject booleanValueOf(JNIEnv* env, jboolean value);\njobject shortValueOf(JNIEnv* env, jshort value);\njobject integerValueOf(JNIEnv* env, jint value);\njobject longValueOf(JNIEnv* env, jlong value);\njobject floatValueOf(JNIEnv* env, jfloat value);\njobject doubleValueOf(JNIEnv* env, jdouble value);\n}  // namespace converter\n\nclass JavaValue {\n public:\n  enum class JavaValueType {\n    Null = 0,\n    Undefined,\n    Boolean,\n    Float,\n    Double,\n    Int32,\n    Int64,\n    String,\n    ByteArray,\n    Array,\n    Map,\n    // Only use for Java returnType:piperdata\n    Transfer,\n    // Only use for Java returnType:LynxObject\n    LynxObject,\n\n    TemplateData\n  };\n\n  JavaValue() : type_(JavaValueType::Null) {}\n  JavaValue(bool value);\n  JavaValue(double value);\n  JavaValue(float value);\n  JavaValue(int32_t value);\n  JavaValue(int64_t value);\n  JavaValue(const std::string& value);\n  JavaValue(jstring str);\n  JavaValue(const uint8_t* value, size_t length);\n\n  JavaValue(std::shared_ptr<base::android::JavaOnlyArray>&& value)\n      : type_(JavaValueType::Array), j_variant_value_(std::move(value)) {}\n\n  JavaValue(std::shared_ptr<base::android::JavaOnlyMap>&& value)\n      : type_(JavaValueType::Map), j_variant_value_(std::move(value)) {}\n  JavaValue(ScopedGlobalJavaRef<jobject>&& value, JavaValueType type)\n      : type_(type), j_variant_value_(std::move(value)) {}\n\n  JavaValue(const JavaValue&) = default;\n  JavaValue& operator=(const JavaValue&) = default;\n  JavaValue(JavaValue&&) = default;\n  JavaValue& operator=(JavaValue&&) = default;\n\n  ~JavaValue() = default;\n\n  bool IsPrimitiveType() {\n    return type_ == JavaValueType::Null || type_ == JavaValueType::Boolean ||\n           type_ == JavaValueType::Double || type_ == JavaValueType::Int32 ||\n           type_ == JavaValueType::Int64 || type_ == JavaValueType::Float ||\n           type_ == JavaValueType::Undefined;\n  }\n\n  bool IsBool() const { return type_ == JavaValueType::Boolean; }\n  bool IsInt32() const { return type_ == JavaValueType::Int32; }\n  bool IsInt64() const { return type_ == JavaValueType::Int64; }\n  bool IsDouble() const { return type_ == JavaValueType::Double; }\n  bool IsFloat() const { return type_ == JavaValueType::Float; }\n  bool IsNumber() const {\n    return IsInt32() || IsInt64() || IsDouble() || IsFloat();\n  }\n\n  bool IsNull() const { return type_ == JavaValueType::Null; }\n  bool IsUndefined() const { return type_ == JavaValueType::Undefined; }\n  bool IsString() const { return type_ == JavaValueType::String; }\n  bool IsArray() const { return type_ == JavaValueType::Array; }\n  bool IsArrayBuffer() const { return type_ == JavaValueType::ByteArray; }\n  bool IsMap() const { return type_ == JavaValueType::Map; }\n  bool IsTransfer() const { return type_ == JavaValueType::Transfer; }\n  bool IsLynxObject() const { return type_ == JavaValueType::LynxObject; }\n  bool IsTemplateData() const { return type_ == JavaValueType::TemplateData; }\n\n  bool Bool() const;\n  int32_t Int32() const;\n  int64_t Int64() const;\n  double Double() const;\n  float Float() const;\n  // When using the Number() method on a JavaValue of type Int64, the return\n  // value will be cast to double. This will result in a loss of precision when\n  // the absolute value exceeds 2^53. If you ensure that the data is within a\n  // reasonable range, you can use Number() directly. But it is strongly\n  // recommended to use IsInt64() and Int64() when applicable.\n  // e.g.\n  // if (IsInt64()) {\n  //    ...\n  // } else if (IsNumber()) {\n  //    ...\n  // }\n  // In this interface , int64 is supported merely to align the behavior in the\n  // hierarchy.\n  // TODO(zhangqun.29): When Number() in pub::Value is deleted, this interface\n  // will be deleted at the same time\n  double Number() const;\n  uint8_t* ArrayBuffer() const;\n  const std::string& String() const;\n  const std::shared_ptr<base::android::JavaOnlyArray>& Array() const {\n    return std::get<std::shared_ptr<base::android::JavaOnlyArray>>(\n        j_variant_value_);\n  }\n  const std::shared_ptr<base::android::JavaOnlyMap>& Map() const {\n    return std::get<std::shared_ptr<base::android::JavaOnlyMap>>(\n        j_variant_value_);\n  }\n  jbyteArray JByteArray() const {\n    return static_cast<jbyteArray>(\n        std::get<base::android::ScopedGlobalJavaRef<jobject>>(j_variant_value_)\n            .Get());\n  }\n  jstring JString() const {\n    return static_cast<jstring>(\n        std::get<base::android::ScopedGlobalJavaRef<jobject>>(j_variant_value_)\n            .Get());\n  }\n\n  jobject TransferData() const {\n    return std::get<base::android::ScopedGlobalJavaRef<jobject>>(\n               j_variant_value_)\n        .Get();\n  }\n\n  jobject LynxObject() const {\n    return std::get<base::android::ScopedGlobalJavaRef<jobject>>(\n               j_variant_value_)\n        .Get();\n  }\n\n  jobject TemplateData() const {\n    return std::get<base::android::ScopedGlobalJavaRef<jobject>>(\n               j_variant_value_)\n        .Get();\n  }\n\n  // Map Getter\n  JavaValue GetValueForKey(const std::string& key) const;\n  // Array Getter\n  JavaValue GetValueForIndex(uint32_t index) const;\n\n  JavaValueType type() const { return type_; }\n\n  // Wrapper JValue\n  jvalue JByte() const;\n  jvalue WrapperJByte() const;\n  jvalue JChar() const;\n  jvalue WrapperJChar() const;\n  jvalue JBoolean() const;\n  jvalue WrapperJBoolean() const;\n  jvalue JShort() const;\n  jvalue WrapperJShort() const;\n  jvalue JInt() const;\n  jvalue WrapperJInt() const;\n  jvalue JLong() const;\n  jvalue WrapperJLong() const;\n  jvalue JFloat() const;\n  jvalue WrapperJFloat() const;\n  jvalue JDouble() const;\n  jvalue WrapperJDouble() const;\n  jvalue JNull() const;\n\n  int Length() const;\n\n  static JavaValue Undefined() { return JavaValue(JavaValueType::Undefined); }\n\n private:\n  JavaValue(JavaValueType type) : type_(type) {}\n\n  JavaValueType type_{JavaValueType::Null};\n  std::variant<jvalue, base::android::ScopedGlobalJavaRef<jobject>,\n               std::shared_ptr<base::android::JavaOnlyArray>,\n               std::shared_ptr<base::android::JavaOnlyMap>>\n      j_variant_value_;\n  mutable std::optional<std::string> string_cache_;\n  mutable std::vector<uint8_t> array_buffer_ptr_cache_;\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_JAVA_VALUE_H_\n"
  },
  {
    "path": "core/base/android/java_value_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/base/android/java_value.h\"\n\n#include \"base/include/platform/android/jni_convert_helper.h\"\n#include \"core/base/android/android_jni.h\"\n#include \"core/base/android/java_only_map.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nTEST(JavaValueTest, ConstructorAndGetterAndSetter) {\n  JNIEnv* env = AttachCurrentThread();\n  {\n    JavaValue val = JavaValue();\n    ASSERT_TRUE(val.IsNull());\n    ASSERT_TRUE(val.IsPrimitiveType());\n  }\n  {\n    JavaValue val = JavaValue::Undefined();\n    ASSERT_FALSE(val.IsNull());\n    ASSERT_TRUE(val.IsUndefined());\n    ASSERT_TRUE(val.IsPrimitiveType());\n  }\n  {\n    JavaValue val = JavaValue(true);\n    ASSERT_TRUE(val.IsBool());\n    ASSERT_TRUE(val.IsPrimitiveType());\n    ASSERT_TRUE(val.Bool());\n  }\n  {\n    JavaValue val = JavaValue(0.01);\n    ASSERT_TRUE(val.IsNumber());\n    ASSERT_TRUE(val.IsDouble());\n    ASSERT_TRUE(val.Double() == 0.01);\n  }\n  {\n    JavaValue val = JavaValue(static_cast<int32_t>(1));\n    ASSERT_TRUE(val.IsNumber());\n    ASSERT_TRUE(val.IsInt32());\n    ASSERT_TRUE(val.Int32() == 1);\n  }\n  {\n    JavaValue val = JavaValue(static_cast<int64_t>(12345678));\n    ASSERT_TRUE(val.IsNumber());\n    ASSERT_TRUE(val.IsInt64());\n    ASSERT_TRUE(val.Int64() == 12345678);\n  }\n  {\n    JavaValue val = JavaValue(std::string(\"hello world\"));\n    ASSERT_TRUE(val.IsString());\n    ASSERT_TRUE(val.String() == \"hello world\");\n  }\n  {\n    JavaValue val = JavaValue(\n        JNIConvertHelper::ConvertToJNIStringUTF(env, \"hello world\").Get());\n    ASSERT_TRUE(val.IsString());\n    // get the string twice to test if cache is valid in JavaValue.\n    ASSERT_TRUE(val.String() == \"hello world\");\n    ASSERT_TRUE(val.String() == \"hello world\");\n    ASSERT_TRUE(val.JString() != nullptr);\n  }\n  {\n    std::string str = \"hello world\";\n    JavaValue val =\n        JavaValue(reinterpret_cast<const uint8_t*>(str.c_str()), str.length());\n    ASSERT_TRUE(val.IsArrayBuffer());\n    ASSERT_TRUE(val.JByteArray() != nullptr);\n  }\n  {\n    JavaValue val = JavaValue(std::make_shared<base::android::JavaOnlyArray>());\n    ASSERT_TRUE(val.IsArray());\n    ASSERT_TRUE(val.Array()->jni_object() != nullptr);\n  }\n  {\n    JavaValue val = JavaValue(std::make_shared<base::android::JavaOnlyMap>());\n    ASSERT_TRUE(val.IsMap());\n    ASSERT_TRUE(val.Map()->jni_object() != nullptr);\n  }\n}\n\nTEST(JavaValueTest, JavaDataConvertor) {\n  JNIEnv* env = AttachCurrentThread();\n  {\n    JavaValue byte_java_value(10);\n    ASSERT_TRUE(byte_java_value.IsInt32());\n    jvalue common_j_value = byte_java_value.JByte();\n    ASSERT_EQ(common_j_value.b, 10);\n\n    JavaValue out_of_range_1(128);\n    ASSERT_TRUE(out_of_range_1.IsInt32());\n    jvalue out_of_range_j_value_1 = out_of_range_1.JByte();\n    ASSERT_EQ(out_of_range_j_value_1.b, -128);\n\n    JavaValue out_of_range_2(-129);\n    ASSERT_TRUE(out_of_range_2.IsInt32());\n    jvalue out_of_range_j_value_2 = out_of_range_2.JByte();\n    ASSERT_EQ(out_of_range_j_value_2.b, static_cast<uint8_t>(127));\n\n    JavaValue byte_wrapper_java_value(10);\n    ASSERT_TRUE(byte_wrapper_java_value.IsInt32());\n    jvalue common_wrapper_j_value = byte_wrapper_java_value.WrapperJByte();\n    auto cls = base::android::GetGlobalClass(env, \"java/lang/Byte\");\n    jmethodID byte_value_method =\n        env->GetMethodID(cls.Get(), \"byteValue\", \"()B\");\n    int8_t result =\n        env->CallByteMethod(common_wrapper_j_value.l, byte_value_method);\n    ASSERT_EQ(result, 10);\n  }\n\n  {\n    const std::string str = \"foo\";\n    JavaValue char_java_value(str);\n    ASSERT_TRUE(char_java_value.IsString());\n    jvalue char_j_value = char_java_value.JChar();\n    ASSERT_EQ(char_j_value.c, 'f');\n\n    JavaValue char_java_value_null = JavaValue();\n    ASSERT_FALSE(char_java_value_null.IsString());\n    jvalue char_j_value_null = char_java_value_null.JChar();\n    ASSERT_EQ(char_j_value_null.c, '\\0');\n\n    JavaValue char_wrapper_java_value(str);\n    ASSERT_TRUE(char_wrapper_java_value.IsString());\n    jvalue char_wrapper_j_value = char_wrapper_java_value.WrapperJChar();\n    auto cls = base::android::GetGlobalClass(env, \"java/lang/Character\");\n    jmethodID char_value_method =\n        env->GetMethodID(cls.Get(), \"charValue\", \"()C\");\n    char result =\n        env->CallCharMethod(char_wrapper_j_value.l, char_value_method);\n    ASSERT_EQ(result, 'f');\n  }\n\n  {\n    JavaValue bool_java_value(false);\n    ASSERT_TRUE(bool_java_value.IsBool());\n    jvalue bool_j_value = bool_java_value.JBoolean();\n    ASSERT_FALSE(bool_j_value.z);\n\n    JavaValue bool_wrapper_java_value(false);\n    ASSERT_TRUE(bool_wrapper_java_value.IsBool());\n    jvalue bool_wrapper_j_value = bool_wrapper_java_value.WrapperJBoolean();\n    auto cls = base::android::GetGlobalClass(env, \"java/lang/Boolean\");\n    jmethodID bool_value_method =\n        env->GetMethodID(cls.Get(), \"booleanValue\", \"()Z\");\n    char result =\n        env->CallBooleanMethod(bool_wrapper_j_value.l, bool_value_method);\n    ASSERT_EQ(result, false);\n  }\n\n  {\n    JavaValue short_java_value(10);\n    ASSERT_TRUE(short_java_value.IsInt32());\n    jvalue short_j_value = short_java_value.JShort();\n    ASSERT_EQ(short_j_value.s, 10);\n\n    JavaValue short_wrapper_java_value(10);\n    ASSERT_TRUE(short_wrapper_java_value.IsInt32());\n    jvalue short_wrapper_j_value = short_wrapper_java_value.WrapperJShort();\n    auto cls = base::android::GetGlobalClass(env, \"java/lang/Short\");\n    jmethodID short_value_method =\n        env->GetMethodID(cls.Get(), \"shortValue\", \"()S\");\n    short result =\n        env->CallShortMethod(short_wrapper_j_value.l, short_value_method);\n    ASSERT_EQ(result, 10);\n  }\n\n  {\n    JavaValue int_java_value(10);\n    ASSERT_TRUE(int_java_value.IsInt32());\n    jvalue int_j_value = int_java_value.JInt();\n    ASSERT_EQ(int_j_value.i, 10);\n\n    JavaValue int_wrapper_java_value(10);\n    ASSERT_TRUE(int_wrapper_java_value.IsInt32());\n    jvalue int_wrapper_j_value = int_wrapper_java_value.WrapperJInt();\n    auto cls = base::android::GetGlobalClass(env, \"java/lang/Integer\");\n    jmethodID int_value_method = env->GetMethodID(cls.Get(), \"intValue\", \"()I\");\n    int32_t result =\n        env->CallIntMethod(int_wrapper_j_value.l, int_value_method);\n    ASSERT_EQ(result, 10);\n  }\n\n  {\n    JavaValue long_java_value(9223372036854775806);\n    ASSERT_TRUE(long_java_value.IsInt64());\n    jvalue long_j_value = long_java_value.JLong();\n    ASSERT_EQ(long_j_value.j, 9223372036854775806);\n\n    JavaValue long_wrapper_java_value(9223372036854775806);\n    ASSERT_TRUE(long_wrapper_java_value.IsInt64());\n    jvalue long_wrapper_j_value = long_wrapper_java_value.WrapperJLong();\n    auto cls = base::android::GetGlobalClass(env, \"java/lang/Long\");\n    jmethodID long_value_method =\n        env->GetMethodID(cls.Get(), \"longValue\", \"()J\");\n    int64_t result =\n        env->CallLongMethod(long_wrapper_j_value.l, long_value_method);\n    ASSERT_EQ(result, 9223372036854775806);\n  }\n\n  {\n    JavaValue float_java_value(3.14f);\n    ASSERT_TRUE(float_java_value.IsFloat());\n    jvalue float_j_value = float_java_value.JFloat();\n    ASSERT_EQ(float_j_value.f, 3.14f);\n\n    JavaValue float_wrapper_java_value(3.14f);\n    ASSERT_TRUE(float_wrapper_java_value.IsFloat());\n    jvalue float_wrapper_j_value = float_wrapper_java_value.WrapperJFloat();\n    auto cls = base::android::GetGlobalClass(env, \"java/lang/Float\");\n    jmethodID float_value_method =\n        env->GetMethodID(cls.Get(), \"floatValue\", \"()F\");\n    float result =\n        env->CallFloatMethod(float_wrapper_j_value.l, float_value_method);\n    ASSERT_EQ(result, 3.14f);\n  }\n\n  {\n    JavaValue double_java_value(3.1415926);\n    ASSERT_TRUE(double_java_value.IsDouble());\n    jvalue double_j_value = double_java_value.JDouble();\n    ASSERT_EQ(double_j_value.d, 3.1415926);\n\n    JavaValue double_wrapper_java_value(3.1415926);\n    ASSERT_TRUE(double_wrapper_java_value.IsDouble());\n    jvalue double_wrapper_j_value = double_wrapper_java_value.WrapperJDouble();\n    auto cls = base::android::GetGlobalClass(env, \"java/lang/Double\");\n    jmethodID double_value_method =\n        env->GetMethodID(cls.Get(), \"doubleValue\", \"()D\");\n    double result =\n        env->CallDoubleMethod(double_wrapper_j_value.l, double_value_method);\n    ASSERT_EQ(result, 3.1415926);\n  }\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/jni_helper.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/base/android/jni_helper.h\"\n\n#include <unordered_set>\n#include <vector>\n\n#include \"base/include/log/logging.h\"\n#include \"core/base/android/java_only_array.h\"\n#include \"core/base/android/java_only_map.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nruntime::js::ArrayBuffer JNIHelper::ConvertToJSIArrayBuffer(\n    JNIEnv* env, runtime::js::Runtime* rt, jbyteArray j_obj) {\n  jsize length = env->GetArrayLength(j_obj);\n  jboolean is_copy = 0;\n  jbyte* bytes = env->GetByteArrayElements(j_obj, &is_copy);\n  runtime::js::ArrayBuffer array_buffer = runtime::js::ArrayBuffer(\n      *rt, reinterpret_cast<const uint8_t*>(bytes), length);\n  env->ReleaseByteArrayElements(j_obj, bytes, 0);\n  return array_buffer;\n}\n\nlynx::base::android::ScopedLocalJavaRef<jbyteArray>\nJNIHelper::ConvertToJNIByteArray(JNIEnv* env, runtime::js::Runtime* rt,\n                                 const runtime::js::ArrayBuffer& array_buffer) {\n  size_t length = array_buffer.length(*rt);\n  uint8_t* buffer = array_buffer.data(*rt);\n  if (!buffer || length <= 0) {\n    return ScopedLocalJavaRef<jbyteArray>();\n  }\n  jbyteArray jni_byte_array = env->NewByteArray(length);  // NOLINT\n  env->SetByteArrayRegion(jni_byte_array, 0, length,\n                          reinterpret_cast<jbyte*>(buffer));\n  return ScopedLocalJavaRef(env, jni_byte_array);\n}\n\nScopedLocalJavaRef<jobject> JNIHelper::ConvertSTLStringMapToJavaMap(\n    JNIEnv* env, const std::unordered_map<std::string, std::string>& map) {\n  if (map.empty()) {\n    return ScopedLocalJavaRef<jobject>();\n  }\n\n  JavaOnlyMap j_map;\n  for (const auto& [key, value] : map) {\n    j_map.PushString(key.c_str(), value.c_str());\n  }\n  // j_map.jni_object_ is a ScopedGlobalJavaRef object, it will be delete when\n  // j_map released. So we need to call NewLocalRef to get its local reference.\n  ScopedLocalJavaRef<jobject> scoped_j_map;\n  scoped_j_map.Reset(env, j_map.jni_object());\n  return scoped_j_map;\n}\n\nvoid JNIHelper::PushByteArrayToJavaArray(runtime::js::Runtime* rt,\n                                         const runtime::js::ArrayBuffer& buf,\n                                         JavaOnlyArray* jarray) {\n  if (jarray == nullptr) {\n    return;\n  }\n  size_t length = buf.length(*rt);\n  uint8_t* buffer = buf.data(*rt);\n  jarray->PushByteArray(buffer, static_cast<int>(length));\n}\n\nvoid JNIHelper::PushByteArrayToJavaMap(runtime::js::Runtime* rt,\n                                       const std::string& key,\n                                       const runtime::js::ArrayBuffer& buf,\n                                       JavaOnlyMap* jmap) {\n  if (jmap == nullptr) {\n    return;\n  }\n  size_t length = buf.length(*rt);\n  uint8_t* buffer = buf.data(*rt);\n  jmap->PushByteArray(key, buffer, static_cast<int>(length));\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/jni_helper.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_BASE_ANDROID_JNI_HELPER_H_\n#define CORE_BASE_ANDROID_JNI_HELPER_H_\n\n#include <jni.h>\n\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <vector>\n\n#include \"base/include/platform/android/java_type.h\"\n#include \"base/include/platform/android/jni_convert_helper.h\"\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"core/base/lynx_export.h\"\n#include \"core/runtime/js/jsi/jsi.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\nclass JavaOnlyArray;\nclass JavaOnlyMap;\n\nclass JNIHelper {\n public:\n  static lynx::base::android::ScopedLocalJavaRef<jbyteArray>\n  ConvertToJNIByteArray(JNIEnv* env, runtime::js::Runtime* rt,\n                        const runtime::js::ArrayBuffer& buf);\n\n  static runtime::js::ArrayBuffer ConvertToJSIArrayBuffer(\n      JNIEnv* env, runtime::js::Runtime* rt, jbyteArray j_obj);\n\n  static ScopedLocalJavaRef<jobject> ConvertSTLStringMapToJavaMap(\n      JNIEnv* env, const std::unordered_map<std::string, std::string>& map);\n\n  static void PushByteArrayToJavaArray(runtime::js::Runtime* rt,\n                                       const runtime::js::ArrayBuffer& buf,\n                                       JavaOnlyArray* jarray);\n  static void PushByteArrayToJavaMap(runtime::js::Runtime* rt,\n                                     const std::string& key,\n                                     const runtime::js::ArrayBuffer& buf,\n                                     JavaOnlyMap* jmap);\n};\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_JNI_HELPER_H_\n"
  },
  {
    "path": "core/base/android/jni_helper_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/base/android/jni_helper.h\"\n\n#include \"core/base/android/android_jni.h\"\n#include \"core/base/android/java_only_map.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nTEST(ConvertSTLStringMapToJavaMapTest, ConvertEmptyMap) {\n  JNIEnv* env = AttachCurrentThread();\n\n  std::unordered_map<std::string, std::string> empty_map;\n  ScopedLocalJavaRef<jobject> j_empty_map =\n      JNIHelper::ConvertSTLStringMapToJavaMap(env, empty_map);\n  ASSERT_EQ(j_empty_map.Get(), nullptr);\n}\n\nTEST(ConvertSTLStringMapToJavaMapTest, ConvertNonEmptyMap) {\n  JNIEnv* env = AttachCurrentThread();\n\n  std::unordered_map<std::string, std::string> map;\n  map.emplace(\"key1\", \"value1\");\n  map.emplace(\"key2\", \"value2\");\n  map.emplace(\"key3\", \"value3\");\n  ScopedLocalJavaRef<jobject> j_map =\n      JNIHelper::ConvertSTLStringMapToJavaMap(env, map);\n  auto key = JNIConvertHelper::ConvertToJNIStringUTF(env, \"key1\");\n  auto value =\n      JavaOnlyMap::JavaOnlyMapGetStringAtIndex(env, j_map.Get(), key.Get());\n  ASSERT_EQ(value, \"value1\");\n  key = JNIConvertHelper::ConvertToJNIStringUTF(env, \"key2\");\n  value = JavaOnlyMap::JavaOnlyMapGetStringAtIndex(env, j_map.Get(), key.Get());\n  ASSERT_EQ(value, \"value2\");\n  key = JNIConvertHelper::ConvertToJNIStringUTF(env, \"key3\");\n  value = JavaOnlyMap::JavaOnlyMapGetStringAtIndex(env, j_map.Get(), key.Get());\n  ASSERT_EQ(value, \"value3\");\n  key = JNIConvertHelper::ConvertToJNIStringUTF(env, \"key4\");\n  value = JavaOnlyMap::JavaOnlyMapGetStringAtIndex(env, j_map.Get(), key.Get());\n  ASSERT_EQ(value, \"\");\n}\n\nTEST(ConvertSTLStringMapToJavaMapTest, ConvertMapWithEmptyKey) {\n  JNIEnv* env = AttachCurrentThread();\n\n  std::unordered_map<std::string, std::string> map;\n  map.emplace(\"\", \"value\");\n  ScopedLocalJavaRef<jobject> j_map =\n      JNIHelper::ConvertSTLStringMapToJavaMap(env, map);\n  auto key = JNIConvertHelper::ConvertToJNIStringUTF(env, \"\");\n  auto value =\n      JavaOnlyMap::JavaOnlyMapGetStringAtIndex(env, j_map.Get(), key.Get());\n  ASSERT_EQ(value, \"value\");\n}\n\nTEST(ConvertSTLStringMapToJavaMapTest, ConvertMapWithEmptyValue) {\n  JNIEnv* env = AttachCurrentThread();\n\n  std::unordered_map<std::string, std::string> map;\n  map.emplace(\"key\", \"\");\n  ScopedLocalJavaRef<jobject> j_map =\n      JNIHelper::ConvertSTLStringMapToJavaMap(env, map);\n  auto key = JNIConvertHelper::ConvertToJNIStringUTF(env, \"key\");\n  auto value =\n      JavaOnlyMap::JavaOnlyMapGetStringAtIndex(env, j_map.Get(), key.Get());\n  ASSERT_EQ(value, \"\");\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/lynx_android_blur.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include <jni.h>\n\n#include \"core/base/android/fresco_blur_filter.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/BlurUtils_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/BlurUtils_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForBlurUtils(JNIEnv* env) { return RegisterNativesImpl(env); }\n}  // namespace jni\n}  // namespace lynx\n\nvoid IterativeBoxBlur(JNIEnv* env, jclass jcaller, jobject bitmap,\n                      jint iterations, jint radius) {\n  fresco_iterativeBoxBlur(env, jcaller, bitmap, iterations, radius);\n}\n"
  },
  {
    "path": "core/base/android/lynx_error_android.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/android/lynx_error_android.h\"\n\n#include \"base/include/debug/lynx_error.h\"\n#include \"core/base/android/jni_helper.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/LynxError_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/LynxError_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForLynxError(JNIEnv* env) { return RegisterNativesImpl(env); }\n}  // namespace jni\n}  // namespace lynx\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nLynxErrorAndroid::LynxErrorAndroid(\n    int error_code, const std::string& error_message,\n    const std::string& fix_suggestion, base::LynxErrorLevel level,\n    const std::unordered_map<std::string, std::string>& custom_info,\n    bool is_logbox_only) {\n  JNIEnv* env = AttachCurrentThread();\n  auto j_msg = JNIConvertHelper::ConvertToJNIStringUTF(env, error_message);\n  auto j_suggestion =\n      JNIConvertHelper::ConvertToJNIStringUTF(env, fix_suggestion);\n  auto j_level = JNIConvertHelper::ConvertToJNIStringUTF(\n      env, LynxError::GetLevelString(static_cast<int32_t>(level)));\n  auto j_map = JNIHelper::ConvertSTLStringMapToJavaMap(env, custom_info);\n\n  jni_object_.Reset(env, Java_LynxError_createLynxError(\n                             env, error_code, j_msg.Get(), j_suggestion.Get(),\n                             j_level.Get(), j_map.Get(), is_logbox_only)\n                             .Get());\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/lynx_error_android.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_BASE_ANDROID_LYNX_ERROR_ANDROID_H_\n#define CORE_BASE_ANDROID_LYNX_ERROR_ANDROID_H_\n\n#include <string>\n#include <unordered_map>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n\nnamespace lynx {\nnamespace base {\n\nenum class LynxErrorLevel;\n\nnamespace android {\n\nclass LynxErrorAndroid {\n public:\n  LynxErrorAndroid(\n      int error_code, const std::string& error_message,\n      const std::string& fix_suggestion, base::LynxErrorLevel level,\n      const std::unordered_map<std::string, std::string>& custom_info,\n      bool is_logbox_only);\n  ~LynxErrorAndroid() = default;\n\n  jobject jni_object() { return jni_object_.Get(); }\n\n private:\n  ScopedGlobalJavaRef<jobject> jni_object_;\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_LYNX_ERROR_ANDROID_H_\n"
  },
  {
    "path": "core/base/android/lynx_white_board_android.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"core/base/android/android_jni.h\"\n#include \"core/shared_data/lynx_white_board.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/LynxWhiteBoard_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/LynxWhiteBoard_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForLynxWhiteBoard(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynx\n\njlong Create(JNIEnv* env, jobject jcaller) {\n  auto* white_board = new lynx::tasm::WhiteBoard();\n  auto* sp_white_board =\n      new std::shared_ptr<lynx::tasm::WhiteBoard>(white_board);\n  return reinterpret_cast<int64_t>(sp_white_board);\n}\n\nvoid Destroy(JNIEnv* env, jobject jcaller, jlong ptr) {\n  std::shared_ptr<lynx::tasm::WhiteBoard>* white_board =\n      reinterpret_cast<std::shared_ptr<lynx::tasm::WhiteBoard>*>(ptr);\n  delete white_board;\n}\n"
  },
  {
    "path": "core/base/android/message_loop_android_vsync.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/base/android/message_loop_android_vsync.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"base/include/timer/time_utils.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/base/trace/trace_event_def.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\nMessageLoopAndroidVSync::MessageLoopAndroidVSync() {\n  vsync_monitor_ = std::make_shared<base::VSyncMonitorAndroid>();\n  vsync_monitor_->BindToCurrentThread();\n  vsync_monitor_->Init();\n}\n\nvoid MessageLoopAndroidVSync::WakeUp(fml::TimePoint time_point) {\n  if (fml::TimePoint::Now() < time_point || WaitForVSyncTimeOut()) {\n    // Scenario 1: The execution time of the task has not yet arrived. Use the\n    // epoll to wake up the looper at the specified time.\n\n    // Scenario 2: When app goes into the background, the platform layer may no\n    // longer provides VSync callbacks to the application. In this case, we need\n    // to use epoll to wake up the looper to flush tasks.\n    MessageLoopAndroid::WakeUp(time_point);\n  } else if (!HasPendingVSyncRequest()) {\n    // No pending VSync request, a new VSync request should be sent.\n    request_vsync_time_millis_ = base::CurrentSystemTimeMilliseconds();\n    vsync_monitor_->RequestVSyncOnUIThread(\n        [this](int64_t frame_start_time_ns, int64_t frame_target_time_ns) {\n          request_vsync_time_millis_ = 0;\n          max_execute_time_ms_ = static_cast<uint64_t>(\n              (frame_target_time_ns - frame_start_time_ns) *\n              kTraversalProportion / kNSecPerMSec);\n          RunExpiredTasksNow();\n        });\n  }\n}\n\nbool MessageLoopAndroidVSync::WaitForVSyncTimeOut() {\n  auto now = base::CurrentSystemTimeMilliseconds();\n  // There is a pending VSync request, and the waiting time has already exceeded\n  // the threshold.\n  return HasPendingVSyncRequest() &&\n         (now - request_vsync_time_millis_ >= kWaitingVSyncTimeoutMillis);\n}\n\nbool MessageLoopAndroidVSync::HasPendingVSyncRequest() {\n  return request_vsync_time_millis_ > 0;\n}\n\nvoid MessageLoopAndroidVSync::FlushTasks(fml::FlushType type) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, MESSAGE_LOOP_ANDROID_VASYNC_FLUSH_TASKS);\n  const auto now = fml::TimePoint::Now();\n  auto begin = base::CurrentSystemTimeMilliseconds();\n  std::vector<const base::closure*> observers;\n  do {\n    auto invocation =\n        task_queue_->GetNextTaskToRun(queue_ids_, now, &observers);\n    if (!invocation) {\n      break;\n    }\n    invocation();\n    if (!observers.empty()) {\n      for (const auto& observer : observers) {\n        (*observer)();\n      }\n      observers.clear();\n    }\n    if (type == fml::FlushType::kSingle) {\n      break;\n    }\n    if (base::CurrentSystemTimeMilliseconds() - begin > max_execute_time_ms_) {\n      // The maximum execution time has been reached, no further execution.\n      return;\n    }\n  } while (true);\n}\n\nbool MessageLoopAndroidVSync::CanRunNow() {\n  // TODO(heshan): For now, a workaround is in place.\n  // Currently, there are two message loops on the UI thread,\n  // so special handling is required when making calls from the UI thread.\n  // This code will be removed once the MessageLoopVsync is used as default.\n  if (UIThread::GetRunner()->GetLoop() ==\n      fml::MessageLoop::GetCurrent().GetLoopImpl()) {\n    return UIThread::GetRunner(true)->GetLoop().get() == this;\n  }\n\n  return MessageLoopImpl::CanRunNow();\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/message_loop_android_vsync.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_ANDROID_MESSAGE_LOOP_ANDROID_VSYNC_H_\n#define CORE_BASE_ANDROID_MESSAGE_LOOP_ANDROID_VSYNC_H_\n\n#include <memory>\n\n#include \"base/include/fml/platform/android/message_loop_android.h\"\n#include \"core/base/android/vsync_monitor_android.h\"\nnamespace lynx {\nnamespace base {\nnamespace android {\nclass MessageLoopAndroidVSync : public fml::MessageLoopAndroid {\n public:\n  MessageLoopAndroidVSync();\n\n  ~MessageLoopAndroidVSync() override = default;\n\n  bool CanRunNow() override;\n\n private:\n  void FlushTasks(fml::FlushType type) override;\n\n  void WakeUp(fml::TimePoint time_point) override;\n\n  bool WaitForVSyncTimeOut();\n\n  bool HasPendingVSyncRequest();\n\n  // The max execution time, its value is determined by the screen refresh rate.\n  // Will be set inside the vsync callback.\n  uint64_t max_execute_time_ms_ = 16;\n  static constexpr uint64_t kNSecPerMSec = 1000000;\n  // This is an estimated value. It indicates the proportion of FlushTasks in\n  // the entire vsync cycle.\n  static constexpr float kTraversalProportion = 0.75;\n  // TODO(qiuxian): VSyncMonitor now locates in shell, needs to be moved to base\n  std::shared_ptr<lynx::base::VSyncMonitorAndroid> vsync_monitor_;\n\n  // Used to record the time for requesting vsync, it will be reset to 0 when\n  // the vsync callback is executed.\n  uint64_t request_vsync_time_millis_ = 0;\n  // The maximum timeout for waiting for the vsync callback.\n  static constexpr uint64_t kWaitingVSyncTimeoutMillis = 5000;\n};\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n#endif  // CORE_BASE_ANDROID_MESSAGE_LOOP_ANDROID_VSYNC_H_\n"
  },
  {
    "path": "core/base/android/piper_data.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/android/piper_data.h\"\n\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"core/base/android/android_jni.h\"\n#include \"core/base/android/jni_helper.h\"\n#include \"core/base/js_constants.h\"\n#include \"core/renderer/dom/android/lepus_message_consumer.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/PiperData_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/PiperData_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForPiperData(JNIEnv* env) { return RegisterNativesImpl(env); }\n}  // namespace jni\n}  // namespace lynx\n\njlong ParseStringData(JNIEnv* env, jclass jcaller, jstring data) {\n  const char* temp = env->GetStringUTFChars(data, JNI_FALSE);\n\n  rapidjson::Document* document = new rapidjson::Document();\n  document->Parse(temp);\n  env->ReleaseStringUTFChars(data, temp);\n  if (document->HasParseError()) {\n    LOGE(\"PiperData Error: source is not valid json format! Error Msg:\"\n         << document->GetParseErrorMsg()\n         << \", position: \" << document->GetErrorOffset());\n    delete document;\n    return 0;\n  }\n  return reinterpret_cast<jlong>(document);\n}\n\nvoid ReleaseData(JNIEnv* env, jclass jcaller, jlong data) {\n  LOGI(\"ReleaseData:\" << data);\n  auto json_data = reinterpret_cast<rapidjson::Document*>(data);\n  delete json_data;\n}\n\nnamespace lynx {\nnamespace base {\nnamespace android {\nnamespace {\nenum PiperDataType { Empty, String, Map };\n\nstd::optional<runtime::js::Value> jsonValueToJSValue(\n    const rapidjson::Value& rap_value, runtime::js::Runtime* rt) {\n  rapidjson::Type type = rap_value.GetType();\n  switch (type) {\n    case rapidjson::Type::kNullType:\n      return runtime::js::Value(nullptr);\n    case rapidjson::Type::kFalseType:\n      return runtime::js::Value(false);\n    case rapidjson::Type::kTrueType:\n      return runtime::js::Value(true);\n    case rapidjson::Type::kNumberType: {\n      if (rap_value.IsInt()) {\n        return runtime::js::Value(rap_value.GetInt());\n      }\n      if (rap_value.IsInt64()) {\n        // In JavaScript,  the max safe integer is 9007199254740991 and the min\n        // safe integer is -9007199254740991, so when integer beyond limit, use\n        // BigInt Object to define it. More information from\n        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number\n        int64_t num = rap_value.GetInt64();\n        if (num < runtime::js::kMinJavaScriptNumber ||\n            num > runtime::js::kMaxJavaScriptNumber) {\n          auto bigint_opt =\n              runtime::js::BigInt::createWithString(*rt, std::to_string(num));\n          if (!bigint_opt) {\n            return std::optional<runtime::js::Value>();\n          }\n          return runtime::js::Value(std::move(*bigint_opt));\n        }\n        // cast to double\n        return runtime::js::Value(rap_value.GetDouble());\n      }\n      return runtime::js::Value(rap_value.GetDouble());\n    }\n    case rapidjson::Type::kStringType: {\n      return runtime::js::Value(\n          runtime::js::String::createFromUtf8(*rt, rap_value.GetString()));\n    }\n    case rapidjson::Type::kArrayType: {\n      auto array_opt = runtime::js::Array::createWithLength(\n          *rt, static_cast<size_t>(rap_value.Size()));\n      if (!array_opt) {\n        return std::optional<runtime::js::Value>();\n      }\n      for (rapidjson::SizeType i = 0; i < rap_value.Size(); i++) {\n        auto js_value_opt = jsonValueToJSValue(rap_value[i], rt);\n        if (!js_value_opt) {\n          return std::optional<runtime::js::Value>();\n        }\n        array_opt->setValueAtIndex(*rt, i, std::move(*js_value_opt));\n      }\n      return runtime::js::Value(std::move(*array_opt));\n    }\n    case rapidjson::Type::kObjectType: {\n      runtime::js::Object obj = runtime::js::Object(*rt);\n      for (rapidjson::Value::ConstMemberIterator itr = rap_value.MemberBegin();\n           itr != rap_value.MemberEnd(); ++itr) {\n        auto js_value_opt = jsonValueToJSValue(itr->value, rt);\n        if (!js_value_opt) {\n          return std::optional<runtime::js::Value>();\n        }\n        obj.setProperty(*rt, itr->name.GetString(), std::move(*js_value_opt));\n      }\n      return runtime::js::Value(obj);\n    }\n    default:\n      break;\n  }\n  return runtime::js::Value();\n}\n}  // namespace\n\nstd::optional<runtime::js::Value> PiperData::jsObjectFromPiperData(\n    JNIEnv* env, runtime::js::Runtime* rt, jobject piper_data) {\n  // Notice: You should make sure Java object `PiperData` alive when you\n  // call JNI method `getNativePtr`. Otherwise Java object may be dealloc in\n  // other thread and the raw native ptr will be free in other thread. Use\n  // ScopedLocalJavaRef to ensure `PiperData` alive here.\n  std::optional<lynx::runtime::js::Value> ret = runtime::js::Value();\n  PiperDataType data_type =\n      static_cast<PiperDataType>(Java_PiperData_getDataType(env, piper_data));\n  if (data_type == String) {\n    jlong json_data = Java_PiperData_getNativePtr(env, piper_data);\n    if (!json_data) {\n      return runtime::js::Value();\n    }\n    ret = jsonValueToJSValue(*reinterpret_cast<rapidjson::Document*>(json_data),\n                             rt);\n  }\n\n  if (data_type == Map) {\n    size_t len =\n        static_cast<size_t>(Java_PiperData_getBufferPosition(env, piper_data));\n    if (len == 0) {\n      return runtime::js::Value();\n    }\n    auto buffer = Java_PiperData_getBuffer(env, piper_data);\n\n    char* message_data =\n        static_cast<char*>(env->GetDirectBufferAddress(buffer.Get()));\n    lynx::tasm::LepusDecoder decoder;\n    ret = decoder.DecodeJSMessage(*rt, message_data, len);\n  }\n\n  // If piper data is disposable, recycle it immediately.\n  if (data_type != Empty) {\n    Java_PiperData_recycleIfIsDisposable(env, piper_data);\n  }\n  return ret;\n}\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/piper_data.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_ANDROID_PIPER_DATA_H_\n#define CORE_BASE_ANDROID_PIPER_DATA_H_\n#include <string>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"core/runtime/js/jsi/jsi.h\"\n#include \"third_party/rapidjson/document.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace android {\n\nclass PiperData {\n public:\n  static std::optional<runtime::js::Value> jsObjectFromPiperData(\n      JNIEnv* env, runtime::js::Runtime* rt, jobject json);\n};\n\n}  // namespace android\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_PIPER_DATA_H_\n"
  },
  {
    "path": "core/base/android/vsync_monitor_android.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/android/vsync_monitor_android.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"core/base/android/android_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/VSyncMonitor_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/VSyncMonitor_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForVSyncMonitor(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynx\n\nvoid OnVSync(JNIEnv* env, jclass jcaller, jlong nativePtr,\n             jlong frameStartTimeNS, jlong frameEndTimeNS) {\n  auto* weak_ptr =\n      reinterpret_cast<std::weak_ptr<lynx::base::VSyncMonitorAndroid>*>(\n          nativePtr);\n  auto shared_ptr = weak_ptr->lock();\n  if (shared_ptr) {\n    shared_ptr->OnVSync(frameStartTimeNS, frameEndTimeNS);\n  }\n  delete weak_ptr;\n}\n\nnamespace lynx {\nnamespace base {\n\nstd::shared_ptr<VSyncMonitor> VSyncMonitor::Create(bool is_on_ui_thread) {\n  return std::make_shared<lynx::base::VSyncMonitorAndroid>(is_on_ui_thread);\n}\n\nvoid VSyncMonitorAndroid::RequestVSync() {\n  auto* weak_self = new std::weak_ptr<VSyncMonitor>(shared_from_this());\n  JNIEnv* env = base::android::AttachCurrentThread();\n  if (is_vsync_triggered_in_ui_thread_) {\n    Java_VSyncMonitor_requestOnUIThread(env,\n                                        reinterpret_cast<jlong>(weak_self));\n  } else {\n    Java_VSyncMonitor_request(env, reinterpret_cast<jlong>(weak_self));\n  }\n}\n\nvoid VSyncMonitorAndroid::RequestVSyncOnUIThread(Callback callback) {\n  if (callback_) {\n    // request during a frame interval, just return\n    return;\n  }\n\n  callback_ = std::move(callback);\n\n  auto* weak_self = new std::weak_ptr<VSyncMonitor>(shared_from_this());\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_VSyncMonitor_requestOnUIThread(env, reinterpret_cast<jlong>(weak_self));\n}\n\nvoid VSyncMonitorAndroid::RequestVSyncOnUIThread() {\n  auto* weak_self = new std::weak_ptr<VSyncMonitor>(shared_from_this());\n  JNIEnv* env = base::android::AttachCurrentThread();\n  Java_VSyncMonitor_requestOnUIThread(env, reinterpret_cast<jlong>(weak_self));\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/android/vsync_monitor_android.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_ANDROID_VSYNC_MONITOR_ANDROID_H_\n#define CORE_BASE_ANDROID_VSYNC_MONITOR_ANDROID_H_\n\n#include <jni.h>\n\n#include \"core/base/threading/vsync_monitor.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass VSyncMonitorAndroid : public VSyncMonitor {\n public:\n  VSyncMonitorAndroid() : VSyncMonitor(false) {}\n\n  VSyncMonitorAndroid(bool is_on_ui_thread) : VSyncMonitor(is_on_ui_thread) {}\n\n  ~VSyncMonitorAndroid() override = default;\n\n  void RequestVSyncOnUIThread(Callback callback) override;\n\n  void RequestVSyncOnUIThread() override;\n\n protected:\n  void RequestVSync() override;\n\n private:\n  bool is_vsync_triggered_in_ui_thread_{false};\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_ANDROID_VSYNC_MONITOR_ANDROID_H_\n"
  },
  {
    "path": "core/base/darwin/config_darwin.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_DARWIN_CONFIG_DARWIN_H_\n#define CORE_BASE_DARWIN_CONFIG_DARWIN_H_\n\n#include <optional>\n#include <string>\n\nnamespace lynx {\nnamespace tasm {\n\nclass LynxConfigDarwin {\n public:\n  LynxConfigDarwin() = delete;\n  ~LynxConfigDarwin() = delete;\n\n  static std::optional<std::string> stringFromExternalEnv(\n      const std::string& key);\n};\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_BASE_DARWIN_CONFIG_DARWIN_H_\n"
  },
  {
    "path": "core/base/darwin/config_darwin.mm",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"core/base/darwin/config_darwin.h\"\n#import <Foundation/Foundation.h>\n#if OS_IOS\n#import \"LynxEnv+Internal.h\"\n#endif\n\nnamespace lynx {\nnamespace tasm {\n\nstd::optional<std::string> LynxConfigDarwin::stringFromExternalEnv(const std::string &key) {\n#if OS_IOS\n  NSString *value =\n      [[LynxEnv sharedInstance] _stringFromExternalEnv:[NSString stringWithUTF8String:key.c_str()]];\n  if (value) {\n    return [value UTF8String];\n  }\n#endif\n  return std::nullopt;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/darwin/lynx_env_darwin.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_DARWIN_LYNX_ENV_DARWIN_H_\n#define CORE_BASE_DARWIN_LYNX_ENV_DARWIN_H_\n\n#import <Foundation/Foundation.h>\n#include <string>\n\nnamespace lynx {\nnamespace tasm {\n\nclass LynxEnvDarwin {\n public:\n  LynxEnvDarwin() = delete;\n  ~LynxEnvDarwin() = delete;\n\n  static void onPiperInvoked(const std::string& module_name, const std::string& method_name,\n                             const std::string& param_str, const std::string& url,\n                             const std::string& invoke_session);\n  static void onPiperResponsed(const std::string& module_name, const std::string& method_name,\n                               const std::string& url, NSDictionary* response,\n                               const std::string& invoke_session);\n\n  static void initNativeUIThread();\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_BASE_DARWIN_LYNX_ENV_DARWIN_H_\n"
  },
  {
    "path": "core/base/darwin/lynx_env_darwin.mm",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#import \"core/base/darwin/lynx_env_darwin.h\"\n\n#include \"core/base/threading/task_runner_manufactor.h\"\n// TODO(zhengsenyao): move lynx_env_darwin.mm out of //lynx/core/base/\n#import <Lynx/LynxEnv.h>  // nogncheck\n\nnamespace lynx {\nnamespace tasm {\n\nvoid LynxEnvDarwin::onPiperInvoked(const std::string& module_name, const std::string& method_name,\n                                   const std::string& param_str, const std::string& url,\n                                   const std::string& invoke_session) {\n  [[LynxEnv sharedInstance] onPiperInvoked:[NSString stringWithUTF8String:module_name.c_str()]\n                                    method:[NSString stringWithUTF8String:method_name.c_str()]\n                                  paramStr:[NSString stringWithUTF8String:param_str.c_str()]\n                                       url:[NSString stringWithUTF8String:url.c_str()]\n                                 sessionID:[NSString stringWithUTF8String:invoke_session.c_str()]];\n}\n\nvoid LynxEnvDarwin::onPiperResponsed(const std::string& module_name, const std::string& method_name,\n                                     const std::string& url, NSDictionary* response,\n                                     const std::string& invoke_session) {\n  [[LynxEnv sharedInstance]\n      onPiperResponsed:module_name.empty() ? @\"\"\n                                           : [NSString stringWithUTF8String:module_name.c_str()]\n                method:method_name.empty() ? @\"\"\n                                           : [NSString stringWithUTF8String:method_name.c_str()]\n                   url:url.empty() ? @\"\" : [NSString stringWithUTF8String:url.c_str()]\n              response:response ?: @{}\n             sessionID:invoke_session.empty()\n                           ? @\"\"\n                           : [NSString stringWithUTF8String:invoke_session.c_str()]];\n}\n\nvoid LynxEnvDarwin::initNativeUIThread() {\n  // init ui thread native loop\n  if (NSThread.isMainThread) {\n    base::UIThread::Init();\n  } else {\n    dispatch_async(dispatch_get_main_queue(), ^{\n      base::UIThread::Init();\n    });\n  }\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/darwin/lynx_trail_hub_impl_darwin.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_DARWIN_LYNX_TRAIL_HUB_IMPL_DARWIN_H_\n#define CORE_BASE_DARWIN_LYNX_TRAIL_HUB_IMPL_DARWIN_H_\n\n#include <optional>\n#include <string>\n\n#include \"core/renderer/utils/lynx_trail_hub.h\"  // nogncheck\n\nnamespace lynx {\nnamespace tasm {\n\nclass LynxTrailHubImplDarwin : public LynxTrailHub::TrailImpl {\n public:\n  LynxTrailHubImplDarwin() = default;\n  ~LynxTrailHubImplDarwin() override = default;\n\n  std::optional<std::string> GetStringForTrailKey(\n      const std::string& key) override;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_BASE_DARWIN_LYNX_TRAIL_HUB_IMPL_DARWIN_H_\n"
  },
  {
    "path": "core/base/darwin/lynx_trail_hub_impl_darwin.mm",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/darwin/lynx_trail_hub_impl_darwin.h\"\n\n#include <memory>\n#include <optional>\n#include <string>\n\n#include \"core/base/darwin/config_darwin.h\"  // nogncheck\n\nnamespace lynx {\nnamespace tasm {\n\nstd::optional<std::string> LynxTrailHubImplDarwin::GetStringForTrailKey(const std::string& key) {\n  return tasm::LynxConfigDarwin::stringFromExternalEnv(key);\n}\n\nstd::unique_ptr<LynxTrailHub::TrailImpl> LynxTrailHub::TrailImpl::Create() {\n  return std::make_unique<LynxTrailHubImplDarwin>();\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/darwin/message_loop_darwin_vsync.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/base/darwin/message_loop_darwin_vsync.h\"\n\n#include <utility>\n\n#include \"base/include/timer/time_utils.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace darwin {\n// TODO(huangweiwu): this class will merge with android vsync loop.\nMessageLoopDarwinVSync::MessageLoopDarwinVSync() {\n  vsync_monitor_ = std::make_shared<base::VSyncMonitorIOS>(true, false);\n  // Will be removed after refactoring VSyncMonitor.\n  vsync_monitor_->BindToCurrentThread();\n  vsync_monitor_->Init();\n}\n\nvoid MessageLoopDarwinVSync::WakeUp(fml::TimePoint time_point) {\n  if (fml::TimePoint::Now() < time_point) {\n    MessageLoopDarwin::WakeUp(time_point);\n  } else if (!HasPendingVSyncRequest()) {\n    // No pending VSync request, a new VSync request should be sent.\n    request_vsync_time_millis_ = base::CurrentSystemTimeMilliseconds();\n    vsync_monitor_->RequestVSyncOnUIThread(\n        [this](int64_t frame_start_time_ns, int64_t frame_target_time_ns) {\n          request_vsync_time_millis_ = 0;\n          max_execute_time_ms_ = static_cast<uint64_t>(\n              (frame_target_time_ns - frame_start_time_ns) * kNSecPerMSec);\n          SetRestrictionDuration(\n              fml::TimeDelta::FromMilliseconds(max_execute_time_ms_));\n          RunExpiredTasksNow();\n        });\n  }\n}\n\nbool MessageLoopDarwinVSync::HasPendingVSyncRequest() {\n  return request_vsync_time_millis_ > 0;\n}\n\nbool MessageLoopDarwinVSync::CanRunNow() {\n  // TODO(heshan): For now, a workaround is in place.\n  // Currently, there are two message loops on the UI thread,\n  // so special handling is required when making calls from the UI thread.\n  // This code will be removed once the MessageLoopVsync is used as default.\n  if (UIThread::GetRunner()->GetLoop() ==\n      fml::MessageLoop::GetCurrent().GetLoopImpl()) {\n    return UIThread::GetRunner(true)->GetLoop().get() == this;\n  }\n\n  return MessageLoopImpl::CanRunNow();\n}\n\n}  // namespace darwin\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/darwin/message_loop_darwin_vsync.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_DARWIN_MESSAGE_LOOP_DARWIN_VSYNC_H_\n#define CORE_BASE_DARWIN_MESSAGE_LOOP_DARWIN_VSYNC_H_\n\n#include <memory>\n\n#include \"base/include/fml/platform/darwin/message_loop_darwin.h\"\n#include \"core/base/darwin/vsync_monitor_darwin.h\"\nnamespace lynx {\nnamespace base {\nnamespace darwin {\nclass MessageLoopDarwinVSync : public fml::MessageLoopDarwin {\n public:\n  MessageLoopDarwinVSync();\n\n  ~MessageLoopDarwinVSync() override = default;\n\n  bool CanRunNow() override;\n\n private:\n  void WakeUp(fml::TimePoint time_point) override;\n\n  bool HasPendingVSyncRequest();\n  uint64_t max_execute_time_ms_ = 16;\n  static constexpr uint64_t kNSecPerMSec = 1000000;\n  // TODO(qiuxian): VSyncMonitor now locates in shell, needs to be moved to base\n  std::shared_ptr<lynx::base::VSyncMonitorIOS> vsync_monitor_;\n  // Used to record the time for requesting vsync, it will be reset to 0 when\n  // the vsync callback is executed.\n  uint64_t request_vsync_time_millis_ = 0;\n};\n}  // namespace darwin\n}  // namespace base\n}  // namespace lynx\n#endif  // CORE_BASE_DARWIN_MESSAGE_LOOP_DARWIN_VSYNC_H_\n"
  },
  {
    "path": "core/base/darwin/vsync_monitor_darwin.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_DARWIN_VSYNC_MONITOR_DARWIN_H_\n#define CORE_BASE_DARWIN_VSYNC_MONITOR_DARWIN_H_\n\n#include <memory>\n\n#include \"core/base/threading/vsync_monitor.h\"\n\nnamespace lynx {\nnamespace base {\nclass LynxVSyncPulsePuppet;\n\nclass VSyncMonitorIOS : public VSyncMonitor {\n public:\n  VSyncMonitorIOS(bool init_in_current_loop = true,\n                  bool is_on_ui_thread = false,\n                  bool is_vsync_post_task_by_emergency = true);\n  ~VSyncMonitorIOS() override;\n\n  void Init() override;\n\n  void SetHighRefreshRate() override;\n\n  void RequestVSync() override;\n\n  void RequestVSyncOnUIThread(Callback callback) override;\n\n  void RequestVSyncOnUIThread() override;\n\n private:\n  std::unique_ptr<LynxVSyncPulsePuppet> delegate_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_DARWIN_VSYNC_MONITOR_DARWIN_H_\n"
  },
  {
    "path": "core/base/darwin/vsync_monitor_darwin.mm",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/darwin/vsync_monitor_darwin.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/base/trace/trace_event_def.h\"\n#include \"core/renderer/utils/lynx_env.h\"\n\n#import <Foundation/Foundation.h>\n#import <QuartzCore/CADisplayLink.h>\n\n@interface LynxVSyncPulse : NSObject\n@property(atomic) CADisplayLink *displayLink;\n@property(atomic) BOOL isInBackground;\n\n- (instancetype)initWithCallback:(lynx::base::VSyncMonitor::Callback)callback;\n\n- (void)requestPulse;\n\n- (void)invalidate;\n\n@end\n\n@implementation LynxVSyncPulse {\n  lynx::base::VSyncMonitor::Callback _callback;\n  CADisplayLink *_displayLink;\n}\n\n- (instancetype)initWithCallback:(lynx::base::VSyncMonitor::Callback)callback {\n  self = [super init];\n  if (self) {\n    _callback = std::move(callback);\n    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onMainDisplay:)];\n    [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];\n    _displayLink.paused = YES;\n\n    [[NSNotificationCenter defaultCenter] addObserver:self\n                                             selector:@selector(appWillEnterForeground:)\n                                                 name:UIApplicationWillEnterForegroundNotification\n                                               object:nil];\n    [[NSNotificationCenter defaultCenter] addObserver:self\n                                             selector:@selector(appDidEnterBackground:)\n                                                 name:UIApplicationDidEnterBackgroundNotification\n                                               object:nil];\n  }\n  return self;\n}\n\n- (void)appWillEnterForeground:(UIApplication *)application {\n  _isInBackground = NO;\n}\n\n- (void)appDidEnterBackground:(UIApplication *)application {\n  _isInBackground = YES;\n}\n\n- (void)requestPulse {\n  _displayLink.paused = NO;\n}\n\n- (void)SetHighRefreshRate {\n  if (@available(iOS 15.0, tvOS 15.0, *)) {\n    CAFrameRateRange frameRateRange = CAFrameRateRangeMake(30, 120, 120);\n    _displayLink.preferredFrameRateRange = frameRateRange;\n  } else if (@available(iOS 10.0, tvOS 10.0, *)) {\n    _displayLink.preferredFramesPerSecond = 120;\n  }\n}\n\n- (void)onMainDisplay:(CADisplayLink *)link {\n  // TODO: This is a temporary solution, and a more reasonable solution should only stop GL related\n  // operations.\n  if (_isInBackground) {\n    return;\n  }\n  link.paused = YES;\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, VSYNC_MONITOR_DARWIN_ON_MAIN_DISPLAY);\n  if (_callback) {\n    CFTimeInterval timestamp = _displayLink.timestamp;\n    _callback(timestamp * 1e+9, (timestamp + _displayLink.duration) * 1e+9);\n  }\n}\n\n- (void)invalidate {\n  [_displayLink invalidate];\n}\n\n- (void)dealloc {\n  [_displayLink invalidate];\n  _displayLink = nil;\n  [[NSNotificationCenter defaultCenter] removeObserver:self];\n}\n@end\n\nnamespace lynx {\nnamespace base {\n\nstd::shared_ptr<VSyncMonitor> VSyncMonitor::Create(bool is_on_ui_thread) {\n  return std::make_shared<lynx::base::VSyncMonitorIOS>(false, is_on_ui_thread);\n}\n\nclass LynxVSyncPulsePuppet {\n public:\n  LynxVSyncPulsePuppet(VSyncMonitorIOS *vsync_monitor_ios) {\n    if (!delegate) {\n      delegate = [[LynxVSyncPulse alloc]\n          initWithCallback:std::bind(&VSyncMonitor::OnVSync, vsync_monitor_ios,\n                                     std::placeholders::_1, std::placeholders::_2)];\n    }\n  }\n  ~LynxVSyncPulsePuppet() { [delegate invalidate]; }\n  void RequestPulse() { [delegate requestPulse]; }\n  void SetHighRefreshRate() { [delegate SetHighRefreshRate]; }\n\n private:\n  LynxVSyncPulse *delegate = nullptr;\n};\n\nVSyncMonitorIOS::VSyncMonitorIOS(bool init_in_current_loop, bool is_on_ui_thread,\n                                 bool is_vsync_post_task_by_emergency)\n    : VSyncMonitor(is_on_ui_thread, is_vsync_post_task_by_emergency) {\n  if (init_in_current_loop) {\n    Init();\n  }\n}\n\nvoid VSyncMonitorIOS::Init() {\n  if (!delegate_) {\n    delegate_ = std::make_unique<LynxVSyncPulsePuppet>(this);\n  }\n}\n\nvoid VSyncMonitorIOS::SetHighRefreshRate() {\n  if (!delegate_) {\n    Init();\n  }\n  delegate_->SetHighRefreshRate();\n}\n\nVSyncMonitorIOS::~VSyncMonitorIOS() {}\n\nvoid VSyncMonitorIOS::RequestVSync() {\n  if (!delegate_) {\n    Init();\n  }\n  delegate_->RequestPulse();\n}\n\nvoid VSyncMonitorIOS::RequestVSyncOnUIThread(Callback callback) {\n  if (callback_) {\n    // request during a frame interval, just return\n    return;\n  }\n  callback_ = std::move(callback);\n  if ([NSThread isMainThread]) {\n    RequestVSync();\n  } else {\n    std::weak_ptr<VSyncMonitorIOS> weak_self =\n        std::static_pointer_cast<VSyncMonitorIOS>(shared_from_this());\n    dispatch_async(dispatch_get_main_queue(), ^{\n      auto strong_self = weak_self.lock();\n      if (strong_self) {\n        strong_self->RequestVSync();\n      }\n    });\n  }\n}\n\nvoid VSyncMonitorIOS::RequestVSyncOnUIThread() {\n  if ([NSThread isMainThread]) {\n    RequestVSync();\n  } else {\n    std::weak_ptr<VSyncMonitorIOS> weak_self =\n        std::static_pointer_cast<VSyncMonitorIOS>(shared_from_this());\n    dispatch_async(dispatch_get_main_queue(), ^{\n      auto strong_self = weak_self.lock();\n      if (strong_self) {\n        strong_self->RequestVSync();\n      }\n    });\n  }\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/debug/BUILD.gn",
    "content": "# Copyright 2022 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../Lynx.gni\")\n\nlynx_core_source_set(\"memory_tracer_src\") {\n  sources = [\n    \"memory_tracer.cc\",\n    \"memory_tracer.h\",\n    \"memory_tracer_android.cc\",\n  ]\n  deps = [ \"//third_party/xhook:xhook\" ]\n}\n"
  },
  {
    "path": "core/base/debug/memory_tracer.cc",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/debug/memory_tracer.h\"\n\n#include <dlfcn.h>\n#include <unwind.h>\n\n#include <fstream>\n\n#include \"base/include/log/logging.h\"\n#include \"third_party/xhook/libxhook/jni/xhook.h\"\n\nvoid* (*real_malloc)(size_t size);\nvoid (*real_free)(void* ptr);\nvoid* (*real_realloc)(void* ptr, size_t size);\nvoid* (*real_calloc)(size_t nmemb, size_t size);\n\nvoid* operator new(size_t size) {\n  void* p;\n  lynx::base::MemoryTracer::SetupRealFunctions();\n  p = real_malloc(size);\n  lynx::base::MemoryTracer::Instance().RecordAllocation(p, size);\n  return p;\n}\n\nvoid* operator new[](size_t size) {\n  void* p;\n  lynx::base::MemoryTracer::SetupRealFunctions();\n  p = real_malloc(size);\n  lynx::base::MemoryTracer::Instance().RecordAllocation(p, size);\n  return p;\n}\n\nvoid operator delete(void* p) noexcept {\n  lynx::base::MemoryTracer::SetupRealFunctions();\n  lynx::base::MemoryTracer::Instance().RecordRelease(p);\n  real_free(p);\n}\n\nvoid operator delete[](void* p) noexcept {\n  lynx::base::MemoryTracer::SetupRealFunctions();\n  lynx::base::MemoryTracer::Instance().RecordRelease(p);\n  real_free(p);\n}\n\nnamespace lynx {\nnamespace base {\n\nconstexpr size_t kMaxStackDepth = 16;\nstatic Dl_info so_dl_info_;\n\nstatic void* local_malloc(size_t size) {\n  void* p;\n  p = real_malloc(size);\n  lynx::base::MemoryTracer::Instance().RecordAllocation(p, size);\n  return p;\n}\n\nstatic void local_free(void* ptr) {\n  lynx::base::MemoryTracer::Instance().RecordRelease(ptr);\n  real_free(ptr);\n}\n\nstatic void* local_realloc(void* ptr, size_t size) {\n  void* p;\n  p = real_realloc(ptr, size);\n  lynx::base::MemoryTracer::Instance().RecordRelease(ptr);\n  lynx::base::MemoryTracer::Instance().RecordAllocation(p, size);\n  return p;\n}\n\nstatic void* local_calloc(size_t nmemb, size_t size) {\n  void* p;\n  p = real_calloc(nmemb, size);\n  lynx::base::MemoryTracer::Instance().RecordAllocation(p, nmemb * size);\n  return p;\n}\n\nusing LibcFunc = struct {\n  const char* name;\n  void* local_func;\n  void** real_func;\n};\n\nstatic const LibcFunc libc_funcs[] = {\n    {\"malloc\", reinterpret_cast<void*>(local_malloc),\n     reinterpret_cast<void**>(&real_malloc)},\n    {\"realloc\", reinterpret_cast<void*>(local_realloc),\n     reinterpret_cast<void**>(&real_realloc)},\n    {\"calloc\", reinterpret_cast<void*>(local_calloc),\n     reinterpret_cast<void**>(&real_calloc)},\n    {\"free\", reinterpret_cast<void*>(local_free),\n     reinterpret_cast<void**>(&real_free)}};\n\nconst char kLynxLibraryPathRegex[] = \".*/liblynx\\\\.so$\";\n\n_Unwind_Reason_Code UnwindCallback(_Unwind_Context* context, void* stack) {\n  _Unwind_Word ip = _Unwind_GetIP(context);\n  auto* s =\n      reinterpret_cast<std::vector<uintptr_t, InternalAllocator<uintptr_t>>*>(\n          stack);\n  s->reserve(kMaxStackDepth);\n  if (s->size() < kMaxStackDepth) {\n    //    auto offset = reinterpret_cast<uintptr_t>(ip -\n    //    (_Unwind_Word)so_dl_info_.dli_fbase); s->push_back(offset);\n    s->push_back(ip);\n    return _URC_NO_REASON;\n  }\n  return _URC_END_OF_STACK;\n}\n\nMemoryTracer& MemoryTracer::Instance() {\n  static std::unique_ptr<MemoryTracer> instance_;\n  static std::once_flag instance_once_flag;\n  std::call_once(instance_once_flag, [&]() {\n    SetupRealFunctions();\n    instance_.reset(\n        static_cast<MemoryTracer*>(real_malloc(sizeof(MemoryTracer))));\n    ::new (instance_.get()) MemoryTracer();\n  });\n  return *instance_;\n}\n\nint MemoryTracer::AddrToBufferIndex(void* addr) {\n  return (reinterpret_cast<uintptr_t>(addr) >> 8) & (kBufferCount - 1);\n}\n\nvoid MemoryTracer::SetupRealFunctions() {\n  static std::once_flag setup_once_flag;\n  std::call_once(setup_once_flag, [] {\n    for (auto func : libc_funcs) {\n      *(func.real_func) = dlsym(RTLD_NEXT, func.name);\n      if (*(func.real_func) == nullptr) {\n        LOGF(\"failed to get symbol:\" << func.name);\n      }\n    }\n    dladdr(reinterpret_cast<const void*>(SetupRealFunctions), &so_dl_info_);\n  });\n}\n\nvoid MemoryTracer::InstallLibcFunctionsHook() {\n  for (auto func : libc_funcs) {\n    xhook_register(kLynxLibraryPathRegex, func.name, func.local_func, nullptr);\n    if (*(func.real_func) == nullptr) {\n      LOGF(\"failed to get symbol:\" << func.name);\n    }\n  }\n  xhook_refresh(1);\n}\n\nvoid MemoryTracer::UnInstallLibcFunctionsHook() {\n  SetupRealFunctions();\n  for (auto func : libc_funcs) {\n    xhook_register(kLynxLibraryPathRegex, func.name, *(func.real_func),\n                   nullptr);\n  }\n  xhook_refresh(1);\n}\n\nvoid MemoryTracer::InitBuffer() {\n  for (unsigned int i = 0; i < kBufferCount; i++) {\n    record_buffers_[i];\n  }\n}\n\nvoid MemoryTracer::RecordAllocation(void* ptr, size_t size) {\n  if (!enable_.load() || !ptr ||\n      size < static_cast<size_t>(min_watched_size_)) {\n    return;\n  }\n  int buffer_index = AddrToBufferIndex(ptr);\n  auto& buffer = record_buffers_.at(buffer_index);\n  Record record{};\n  record.addr = reinterpret_cast<uintptr_t>(ptr);\n  record.size = size;\n  _Unwind_Backtrace(UnwindCallback, &record.stack);\n  buffer.AddRecord(record);\n}\n\nvoid MemoryTracer::RecordRelease(void* ptr) {\n  if (!enable_.load() || !ptr) {\n    return;\n  }\n  int buffer_index = AddrToBufferIndex(ptr);\n  auto& buffer = record_buffers_.at(buffer_index);\n  buffer.RemoveRecord(reinterpret_cast<uintptr_t>(ptr));\n}\n\nvoid MemoryTracer::StartTracing(int min_watched_size) {\n  if (enable_.load()) {\n    LOGW(\"MemoryTracer: tracing already started.\");\n    return;\n  }\n  LOGI(\"MemoryTracer: start tracing.\");\n  if (record_buffers_.size() < kBufferCount) {\n    InitBuffer();\n  }\n  min_watched_size_ = min_watched_size;\n  InstallLibcFunctionsHook();\n  enable_.store(true);\n}\n\nvoid MemoryTracer::StopTracing() {\n  if (!enable_.load()) {\n    LOGW(\"MemoryTracer: tracing not started.\");\n  }\n  LOGI(\"MemoryTracer: stop tracing.\");\n  enable_.store(false);\n  UnInstallLibcFunctionsHook();\n  // buffer.Clear holds the lock to wait for all the recording operations\n  // finishing.\n  for (auto& buffer : record_buffers_) {\n    buffer.second.Clear();\n  }\n  record_buffers_.clear();\n}\n\nvoid MemoryTracer::WriteRecordsToFile(const std::string& file_path) {\n  if (!enable_.load()) {\n    LOGW(\"MemoryTracer: tracing not started.\");\n    return;\n  }\n  std::ofstream output(file_path);\n  if (!output.is_open()) {\n    LOGE(\"MemoryTracer: failed to create file(\" << strerror(errno)\n                                                << \"): \" << file_path);\n    return;\n  }\n  output << \"Lynx Memory Report:\" << std::endl;\n  for (auto& pair : record_buffers_) {\n    pair.second.DumpRecordsToStream(output);\n  }\n  LOGI(\"MemoryTracer: dump memory to \" << file_path);\n}\n\nvoid MemoryTracer::Enable() { enable_.store(true); }\n\nvoid MemoryTracer::Disable() { enable_.store(false); }\n\nMemoryTracer::~MemoryTracer() {\n  record_buffers_.clear();\n  DlInfo::Instance().ClearCache();\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/debug/memory_tracer.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_DEBUG_MEMORY_TRACER_H_\n#define CORE_BASE_DEBUG_MEMORY_TRACER_H_\n\n#include <dlfcn.h>\n\n#include <fstream>\n#include <functional>\n#include <iostream>\n#include <map>\n#include <memory>\n#include <mutex>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\nextern void* (*real_malloc)(size_t size);\nextern void (*real_free)(void* ptr);\nextern void* (*real_realloc)(void* ptr, size_t size);\nextern void* (*real_calloc)(size_t nmemb, size_t size);\n\nnamespace lynx {\nnamespace base {\n\ntemplate <typename T>\nclass InternalAllocator {\n public:\n  typedef T value_type;\n  typedef T* pointer;\n  typedef const T* const_pointer;\n  typedef T& reference;\n  typedef const T& const_reference;\n  typedef size_t size_type;\n  typedef ptrdiff_t difference_type;\n\n  template <typename U>\n  struct rebind {\n    typedef InternalAllocator<U> other;\n  };\n\n  pointer allocate(size_type n, const void* hint = 0) {\n    return static_cast<pointer>(real_malloc(sizeof(T) * n));\n  }\n\n  void deallocate(pointer p, size_type n) { real_free(p); }\n\n  template <typename U, typename... Args>\n  void construct(U* p, Args&&... args) {\n    ::new (p) U(std::forward<Args>(args)...);\n  }\n\n  template <typename U>\n  void destroy(U* p) {\n    p->~U();\n  }\n\n  pointer address(reference x) { return (pointer)&x; }\n\n  const_pointer address(const_reference x) { return (const_pointer)&x; }\n\n  size_type max_size() const { return size_type(UINTMAX_MAX / sizeof(T)); }\n};\n\nstruct Record {\n  uintptr_t addr;\n  size_t size;\n  std::vector<uintptr_t, InternalAllocator<uintptr_t>> stack;\n};\n\nstruct AddrHash {\n  size_t operator()(const uintptr_t addr) const {\n    return static_cast<size_t>(addr);\n  }\n};\n\nstruct AddrEq {\n  bool operator()(const uintptr_t lhs, const uintptr_t rhs) const {\n    return lhs == rhs;\n  }\n};\n\nclass DlInfo {\n public:\n  static DlInfo& Instance() {\n    static std::unique_ptr<DlInfo> instance_;\n    static std::once_flag instance_once_flag;\n    std::call_once(instance_once_flag, [&]() {\n      instance_.reset((DlInfo*)real_malloc(sizeof(DlInfo)));\n      ::new (instance_.get()) DlInfo();\n    });\n    return *instance_;\n  }\n\n  Dl_info* GetDlInfo(uintptr_t addr) {\n    auto& info = dl_infos_[addr];\n    if (!info.dli_fname) {\n      dladdr(reinterpret_cast<void*>(addr), &info);\n    }\n    return &info;\n  }\n\n  void ClearCache() { dl_infos_.clear(); }\n\n private:\n  using DlInfoAllocator =\n      InternalAllocator<std::pair<const uintptr_t, Dl_info>>;\n  std::map<uintptr_t, Dl_info, std::less<int>, DlInfoAllocator> dl_infos_;\n};\n\nclass RecordBuffer {\n public:\n  RecordBuffer() = default;\n  virtual ~RecordBuffer() { records_.clear(); };\n  inline void AddRecord(Record& record) {\n    std::lock_guard<std::mutex> lock(lock_);\n    records_[record.addr] = record;\n  }\n\n  inline void RemoveRecord(uintptr_t addr) {\n    std::lock_guard<std::mutex> lock(lock_);\n    auto iter = records_.find(addr);\n    if (iter != records_.end()) {\n      records_.erase(iter);\n    }\n  }\n\n  // before this function be called, we need to ensure that\n  // all the recording operations are finished or in progress.\n  void Clear() {\n    std::lock_guard<std::mutex> lock(lock_);\n    records_.clear();\n  }\n\n  void DumpRecordsToStream(std::ostream& os) {\n    std::lock_guard<std::mutex> lock(lock_);\n    for (auto& pair : records_) {\n      os << \"object: addr=\" << std::hex << \"0x\" << pair.first\n         << \" size=\" << std::dec << pair.second.size << std::endl;\n      for (uintptr_t frame : pair.second.stack) {\n        Dl_info* info = DlInfo::Instance().GetDlInfo(frame);\n        os << \"0x\" << std::hex\n           << frame - reinterpret_cast<uintptr_t>(info->dli_fbase) << \" \"\n           << (info->dli_fname ? info->dli_fname : \"\") << std::endl;\n      }\n      os << std::endl;\n    }\n  }\n\n private:\n  std::mutex lock_;\n  std::unordered_map<uintptr_t, Record, AddrHash, AddrEq,\n                     InternalAllocator<std::pair<const uintptr_t, Record>>>\n      records_;\n};\n\nclass MemoryTracer {\n public:\n  static MemoryTracer& Instance();\n  static void SetupRealFunctions();\n  static void InstallLibcFunctionsHook();\n  static void UnInstallLibcFunctionsHook();\n\n  MemoryTracer() : enable_(false), min_watched_size_(0){};\n  ~MemoryTracer();\n  void StartTracing(int min_watch_size);\n  void StopTracing();\n  inline void RecordAllocation(void* ptr, size_t size);\n  inline void RecordRelease(void* ptr);\n  void WriteRecordsToFile(const std::string& file_path);\n  void Enable();\n  void Disable();\n\n private:\n  using RecordBufferAllocator =\n      InternalAllocator<std::pair<const int, RecordBuffer>>;\n  using BufferIndexAllocator =\n      InternalAllocator<std::pair<const int, const int>>;\n\n  static inline int AddrToBufferIndex(void* addr);\n  void InitBuffer();\n\n  static constexpr unsigned int kBufferCount = 8;\n  std::map<int, RecordBuffer, std::less<int>, RecordBufferAllocator>\n      record_buffers_;\n  std::atomic_bool enable_;\n  int min_watched_size_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_DEBUG_MEMORY_TRACER_H_\n"
  },
  {
    "path": "core/base/debug/memory_tracer_android.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/debug/memory_tracer.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/LynxNativeMemoryTracer_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/LynxNativeMemoryTracer_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForLynxNativeMemoryTracer(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynx\n\n// static\nvoid StartTracing(JNIEnv* env, jclass clazz, jint min_watched_size) {\n  lynx::base::MemoryTracer::Instance().StartTracing(min_watched_size);\n}\n\n// static\nvoid StopTracing(JNIEnv* env, jclass jcaller) {\n  lynx::base::MemoryTracer::Instance().StopTracing();\n}\n\n// static\nvoid WriteRecordsToFile(JNIEnv* env, jclass jcaller, jstring filePath) {\n  const char* file_path_str = env->GetStringUTFChars(filePath, nullptr);\n  lynx::base::MemoryTracer::Instance().WriteRecordsToFile(file_path_str);\n  env->ReleaseStringUTFChars(filePath, file_path_str);\n}\n"
  },
  {
    "path": "core/base/harmony/harmony_function_loader.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/harmony/harmony_function_loader.h\"\n\n#include <dlfcn.h>\n\n#include <mutex>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace harmony {\n\nvoid* GetSharedObjectHandler(const char* so_name) {\n  if (strcmp(so_name, kAceNdkSoName) == 0) {\n    static void* shared_object_handler = nullptr;\n    static std::once_flag once_flag;\n    std::call_once(once_flag, []() {\n      void* handle = dlopen(kAceNdkSoName, RTLD_NOW | RTLD_LOCAL);\n      shared_object_handler = handle;\n    });\n    return shared_object_handler;\n  }\n\n  if (strcmp(so_name, kNativeDrawingSoName) == 0) {\n    static void* native_drawing_object_handler = nullptr;\n    static std::once_flag native_drawing_once_flag;\n    std::call_once(native_drawing_once_flag, []() {\n      void* native_drawing_handle =\n          dlopen(kNativeDrawingSoName, RTLD_NOW | RTLD_LOCAL);\n      native_drawing_object_handler = native_drawing_handle;\n    });\n    return native_drawing_object_handler;\n  }\n\n  return nullptr;\n}\n\nHarmonyCompatFunctionsHandler* DlSymAllSymbolNeeded(void* handler) {\n  HarmonyCompatFunctionsHandler* funcs = new HarmonyCompatFunctionsHandler();\n  bool success = true;\n#define X(ret, name, args, symbol)                     \\\n  funcs->name = (ret(*) args)dlsym(handler, symbol);   \\\n  if (!funcs->name) {                                  \\\n    LOGE(\"Failed to load harmony symbol::\" << symbol); \\\n    success = false;                                   \\\n  }\n\n  HARMONY_COMPAT_FUNCTIONS\n#undef X\n  if (!success) {\n    delete funcs;\n    return nullptr;\n  }\n  return funcs;\n}\n\nHarmonyCompatFunctionsHandler* GetHarmonyCompatFunctionsHandler() {\n  static std::once_flag once_flag;\n  static HarmonyCompatFunctionsHandler* harmony_compat_handler = nullptr;\n  std::call_once(once_flag, []() {\n    void* handle = GetSharedObjectHandler(kAceNdkSoName);\n    if (handle == nullptr) {\n      return;\n    }\n    auto* func_handler = DlSymAllSymbolNeeded(handle);\n    if (func_handler != nullptr) {\n      harmony_compat_handler = func_handler;\n    } else {\n      LOGW(\"Failed to load ace_ndk symbol\");\n    }\n  });\n  return harmony_compat_handler;\n}\n\n// get native drawing functions\nNativeDrawingFunctionsHandler* DlSymAllNativeDrawingSymbolNeeded(\n    void* handler) {\n  NativeDrawingFunctionsHandler* funcs = new NativeDrawingFunctionsHandler();\n  bool success = true;\n#define X(ret, name, args, symbol)                     \\\n  funcs->name = (ret(*) args)dlsym(handler, symbol);   \\\n  if (!funcs->name) {                                  \\\n    LOGE(\"Failed to load harmony symbol::\" << symbol); \\\n    success = false;                                   \\\n  }\n\n  NATIVE_DRAWING_FUNCTIONS\n#undef X\n  if (!success) {\n    delete funcs;\n    return nullptr;\n  }\n  return funcs;\n}\n\nNativeDrawingFunctionsHandler* GetNativeDrawingFunctionsHandler() {\n  static std::once_flag once_flag;\n  static NativeDrawingFunctionsHandler* native_drawing_handler = nullptr;\n  std::call_once(once_flag, []() {\n    void* handler = GetSharedObjectHandler(kNativeDrawingSoName);\n    if (handler == nullptr) {\n      return;\n    }\n    auto* func_handler = DlSymAllNativeDrawingSymbolNeeded(handler);\n    if (func_handler != nullptr) {\n      native_drawing_handler = func_handler;\n    } else {\n      LOGW(\"Failed to load native_drawing symbol\");\n    }\n  });\n  return native_drawing_handler;\n}\n\n}  // namespace harmony\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/harmony/harmony_function_loader.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_HARMONY_HARMONY_FUNCTION_LOADER_H_\n#define CORE_BASE_HARMONY_HARMONY_FUNCTION_LOADER_H_\n\n#include <arkui/native_node.h>\n#include <native_drawing/drawing_font_collection.h>\n\nnamespace lynx {\nnamespace base {\nnamespace harmony {\n\nconstexpr char kAceNdkSoName[] = \"libace_ndk.z.so\";\nconstexpr char kNativeDrawingSoName[] = \"libnative_drawing.so\";\n\nvoid* GetSharedObjectHandler(const char* so_name);\n\n#define HARMONY_COMPAT_FUNCTIONS                                               \\\n  X(ArkUI_SnapshotOptions*, oh_create_snapshot_option_func, (),                \\\n    \"OH_ArkUI_CreateSnapshotOptions\")                                          \\\n  X(ArkUI_ErrorCode, oh_destroy_snapshot_option_func,                          \\\n    (ArkUI_SnapshotOptions*), \"OH_ArkUI_DestroySnapshotOptions\")               \\\n  X(ArkUI_ErrorCode, oh_snapshot_option_set_scale_func,                        \\\n    (ArkUI_SnapshotOptions*, float), \"OH_ArkUI_SnapshotOptions_SetScale\")      \\\n  X(ArkUI_ErrorCode, oh_get_node_snapshot_func,                                \\\n    (ArkUI_NodeHandle, ArkUI_SnapshotOptions*, OH_PixelmapNative**),           \\\n    \"OH_ArkUI_GetNodeSnapshot\")                                                \\\n  X(ArkUI_CrossLanguageOption*, oh_create_cross_language_option_func, (),      \\\n    \"OH_ArkUI_CrossLanguageOption_Create\")                                     \\\n  X(void, oh_destroy_cross_language_option_func, (ArkUI_CrossLanguageOption*), \\\n    \"OH_ArkUI_CrossLanguageOption_Destroy\")                                    \\\n  X(ArkUI_ErrorCode, oh_set_node_utils_set_cross_language_option_func,         \\\n    (ArkUI_NodeHandle, ArkUI_CrossLanguageOption*),                            \\\n    \"OH_ArkUI_NodeUtils_SetCrossLanguageOption\")                               \\\n  X(void, oh_set_cross_language_option_set_attribute_setting_status_func,      \\\n    (ArkUI_CrossLanguageOption*, bool),                                        \\\n    \"OH_ArkUI_CrossLanguageOption_SetAttributeSettingStatus\")                  \\\n  X(bool, oh_get_cross_language_option_get_attribute_setting_status_func,      \\\n    (ArkUI_CrossLanguageOption*),                                              \\\n    \"OH_ArkUI_CrossLanguageOption_GetAttributeSettingStatus\")\n\nstruct HarmonyCompatFunctionsHandler {\n#define X(ret, name, args, symbol) ret(*name) args = nullptr;\n  HARMONY_COMPAT_FUNCTIONS\n#undef X\n};\n\nHarmonyCompatFunctionsHandler* GetHarmonyCompatFunctionsHandler();\n\n// native_drawing APIs from libnative_drawing.so\n#define NATIVE_DRAWING_FUNCTIONS                        \\\n  X(OH_Drawing_FontCollection*,                         \\\n    oh_drawing_get_font_collection_global_instance, (), \\\n    \"OH_Drawing_GetFontCollectionGlobalInstance\")\n\nstruct NativeDrawingFunctionsHandler {\n#define X(ret, name, args, symbol) ret(*name) args = nullptr;\n  NATIVE_DRAWING_FUNCTIONS\n#undef X\n};\n\nNativeDrawingFunctionsHandler* GetNativeDrawingFunctionsHandler();\n\n}  // namespace harmony\n}  // namespace base\n}  // namespace lynx\n#endif  // CORE_BASE_HARMONY_HARMONY_FUNCTION_LOADER_H_\n"
  },
  {
    "path": "core/base/harmony/harmony_napi_env_holder.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/harmony/harmony_napi_env_holder.h\"\n\n#include <napi/native_api.h>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace harmony {\nnamespace {\nnapi_env& Holder() {\n  static thread_local napi_env env = nullptr;\n  return env;\n}\n}  // namespace\n\nnapi_env GetJSThreadNapiEnv() {\n  DCHECK(Holder());\n  return Holder();\n}\n\nvoid InitializationNapiEnvForCurrentThread(napi_env env) {\n  LOGI(\"InitializationNapiEnvForCurrentThread with \" << env);\n  Holder() = env;\n}\n\n}  // namespace harmony\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/harmony/harmony_napi_env_holder.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_HARMONY_HARMONY_NAPI_ENV_HOLDER_H_\n#define CORE_BASE_HARMONY_HARMONY_NAPI_ENV_HOLDER_H_\n\n#include <napi/native_api.h>\n\n#include \"core/base/lynx_export.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace harmony {\n/**\n * Get the NapiEnv of the js thread, only can be called in js thread.\n * then you can use the NapiEnv to call napi functions from system lib or other\n * sdk\n * @return NapiEnv of the js thread\n * @warning non-sendable object is always different instance & type in different\n * thread / ark runtime\n * @example\n * the code below shows an example that use the http api from arkts to load a\n * resource in js thread\n * @code\n * namespace {\n * std::string NapiValueToString(napi_env env, napi_value value) {\n *  napi_value str_value = nullptr;\n *  napi_coerce_to_string(env, value, &str_value);\n *\n * size_t str_length = 0;\n * napi_get_value_string_utf8(env, str_value, nullptr, 0, &str_length);\n * char* value_str_value = new char[str_length + 1];\n * napi_get_value_string_utf8(env, str_value, value_str_value, str_length + 1,\n *                           &str_length);\n * std::string result(value_str_value);\n * delete[] value_str_value;\n * return result;\n * }\n *\n * void PrintProperties(napi_env env, napi_value object, const std::string\n * &object_name) {\n *  // list properties\n *  LOGI(\"start to print properties of the object: \" << object_name);\n *  napi_value properties_list = nullptr;\n *  napi_status ret = napi_get_property_names(env , object, &properties_list);\n *  DCHECK(ret == napi_ok);\n *\n *  bool is_array = false;\n *  napi_is_array(env, properties_list, &is_array);\n *\n *  if (is_array) {\n *    uint32_t property_count = 0;\n *    napi_get_array_length(env, properties_list, &property_count);\n *    for (size_t i = 0; i < property_count; i++) {\n *      napi_value property_name = nullptr;\n *      napi_get_element(env, properties_list, i, &property_name);\n *\n *      auto name_str_value = NapiValueToString(env, property_name);\n *\n *      // now get property value\n *      napi_value property_value = nullptr;\n *      napi_get_property(env, object, property_name, &property_value);\n *\n *      auto value_str_value = NapiValueToString(env, property_value);\n *\n *      LOGI(\"property_name: \" << name_str_value << \" property_value: \" <<\n * value_str_value);\n *    }\n *  }\n * }\n * }\n *\n * void LoadBitmap(const std::string& path, LoadBitmapCallback callback)\n * { LOGI(\"LoadBitmap start\")\n *\n * auto env = lynx::base::harmony::GetJSThreadNapiEnv();\n * napi_value http_module = nullptr;\n * auto ret = napi_load_module(env, \"@ohos.net.http\", &http_module);\n * DCHECK(ret == napi_ok);\n *\n * PrintProperties(env, http_module, \"http_module\");\n *\n * napi_value create_http_func = nullptr;\n * napi_value create_http_str = nullptr;\n * napi_create_string_latin1(env, \"createHttp\", NAPI_AUTO_LENGTH,\n *                          &create_http_str);\n * napi_get_property(env, http_module, create_http_str, &create_http_func);\n *\n * napi_value http_request = nullptr;\n * napi_value napi_undefined_instance = nullptr;\n * napi_get_undefined(env, &napi_undefined_instance);\n * ret = napi_call_function(env, napi_undefined_instance, create_http_func, 0,\n *                         nullptr, &http_request);\n * DCHECK(ret == napi_ok);\n *\n * // setup the httpRequest.request params\n * // url\n * napi_value napi_value_url = nullptr;\n * napi_create_string_latin1(env, path.c_str(), NAPI_AUTO_LENGTH,\n *                          &napi_value_url);\n *\n * // options\n * napi_value request_option = nullptr;\n * // get option constructor is too slow, so just use a raw object.\n * napi_create_object(env, &request_option);\n *\n * // get HttpDataType\n * napi_value http_data_type = nullptr;\n * napi_get_named_property(env, http_module, \"HttpDataType\", &http_data_type);\n * napi_value http_data_type_array_buffer = nullptr;\n * napi_get_named_property(env, http_data_type, \"ARRAY_BUFFER\",\n *                        &http_data_type_array_buffer);\n * napi_set_named_property(env, request_option, \"expectDataType\",\n *                        http_data_type_array_buffer);\n *\n * // create the callback function\n * auto napi_callback_func = [](napi_env env, napi_callback_info info) {\n *  size_t argc = 2;\n *  napi_value args[2] = {nullptr};\n *  void* data = nullptr;\n *  napi_get_cb_info(env, info, &argc, args, nullptr, &data);\n *\n *  // handle error\n *  napi_valuetype type;\n *  napi_typeof(env, args[0], &type);\n *  if (type == napi_object) {\n *    PrintProperties(env, args[0], \"args[0]\");\n *    napi_value error_code = nullptr;\n *    napi_get_named_property(env, args[0], \"code\", &error_code);\n *    int32_t error_code_value = 0;\n *    napi_get_value_int32(env, error_code, &error_code_value);\n *  }\n *\n *  // handle array buffer\n *  PrintProperties(env, args[1], \"args[1]\");\n *  napi_value array_buffer = nullptr;\n *  napi_get_named_property(env, args[1], \"result\", &array_buffer);\n *  napi_typeof(env, array_buffer, &type);\n *\n *  bool is_array_buffer = false;\n *  napi_is_arraybuffer(env, array_buffer, &is_array_buffer);\n *\n *  if (is_array_buffer) {\n *    size_t buffer_size;\n *    void* buffer_data;\n *    napi_get_arraybuffer_info(env, array_buffer, &buffer_data, &buffer_size);\n *\n *    auto data = DataHolder::MakeWithCopy(buffer_data, buffer_size);\n *  }\n *  return napi_value();\n * };\n * napi_value callback_func = nullptr;\n * napi_create_function(env, nullptr, NAPI_AUTO_LENGTH, napi_callback_func,\n * this, &callback_func);\n *\n * size_t argc = 3;\n * napi_value argv[3] = {napi_value_url, request_option, callback_func};\n *\n * napi_value request_func = nullptr;\n * ret = napi_get_named_property(env, http_request, \"request\", &request_func);\n * DCHECK(ret == napi_ok);\n *\n * napi_valuetype type;\n * napi_typeof(env, request_func, &type);\n *\n * ret =\n *    napi_call_function(env, http_request, request_func, argc, argv, nullptr);\n * DCHECK(ret == napi_ok);\n * }\n */\nLYNX_EXPORT napi_env GetJSThreadNapiEnv();\n\nvoid InitializationNapiEnvForCurrentThread(napi_env env);\n\n}  // namespace harmony\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_HARMONY_HARMONY_NAPI_ENV_HOLDER_H_\n"
  },
  {
    "path": "core/base/harmony/harmony_trace_event_def.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_HARMONY_HARMONY_TRACE_EVENT_DEF_H_\n#define CORE_BASE_HARMONY_HARMONY_TRACE_EVENT_DEF_H_\n\n#include \"core/base/lynx_trace_categories.h\"\n\n#if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n\ninline constexpr const char* const IMAGE_HELPER_DECODE_IMAGE_SYNC =\n    \"LynxImageHelper::DecodeImageSync\";\ninline constexpr const char* const NAPI_CONVERT_HELPER_JSON_TO_LEPUS_VALUE =\n    \"NapiConvertHelper::JSONToLepusValue\";\ninline constexpr const char* const SETUP_ARK_TS_RUNTIME = \"SetupArkTSRuntime\";\ninline constexpr const char* const FONT_FACE_PARSE_AND_ADD_SRC =\n    \"FontFace::ParseAndAddSrc\";\ninline constexpr const char* const LEPUS_VALUE_TO_NAPI_VALUE =\n    \"LepusValueToNapiValue\";\ninline constexpr const char* const CALL_JSB_ON_ARK_TS = \"CallJSBOnArkTS\";\ninline constexpr const char* const PUB_VALUE_TO_LEPUS_VALUE =\n    \"PubValueToLepusValue\";\n/**\n * @history_name{NativeModule::Invoke}\n */\ninline constexpr const char* const HARMONY_NATIVE_MODULE_INVOKE =\n    \"CallPlatformImplementation\";\ninline constexpr const char* const INVOKE_CALLBACK_ON_UI_THREAD =\n    \"InvokeCallbackOnUIThread\";\ninline constexpr const char* const NAPI_VALUE_TO_LEPUS_VALUE =\n    \"NapiValueToLepusValue\";\n/**\n * @history_name{JSBTiming::jsb_callback_thread_switch_start}\n */\ninline constexpr const char* const\n    NATIVE_MODULE_HARMONY_CALLBACK_THREAD_SWITCH_START =\n        \"NativeModule::PlatformCallbackStart\";\ninline constexpr const char* const LOAD_JS_SOURCE = \"LoadJSSource\";\ninline constexpr const char* const BASE_TEXT_SHADOW_NODE_LOAD_FONT_FAMILY =\n    \"BaseTextShadowNode::LoadFontFamily\";\ninline constexpr const char* const IMAGE_SHADOW_NODE_MEASURE =\n    \"ImageShadowNode::Measure\";\ninline constexpr const char* const IMAGE_SHADOW_NODE_JUST_SIZE =\n    \"ImageShadowNode::JustSize\";\ninline constexpr const char* const INPUT_SHADOW_NODE_MEASURE =\n    \"InputShadowNode::Measure\";\ninline constexpr const char* const JS_SHADOW_NODE_REQUEST_LAYOUT =\n    \"JSShadowNode::RequestLayout\";\ninline constexpr const char* const JS_SHADOW_NODE_MEASURE =\n    \"JSShadowNode::Measure\";\ninline constexpr const char* const OVERLAY_SHADOW_NODE_MEASURE =\n    \"OverlayShadowNode::Measure\";\ninline constexpr const char* const SHADOW_NODE_OWNER_CREATE_SHADOW_NODE =\n    \"ShadowNodeOwner::CreateShadowNode.\";\ninline constexpr const char* const TEXT_SHADOW_NODE_MEASURE =\n    \"TextShadowNode::Measure\";\ninline constexpr const char* const TEXT_SHADOW_NODE_LAYOUT =\n    \"TextShadowNode::Layout\";\ninline constexpr const char* const\n    TEXT_SHADOW_NODE_HANDLE_TEXT_OVERFLOW_AND_TRUNCATION =\n        \"TextShadowNode::HandleTextOverflowAndTruncation\";\ninline constexpr const char* const UIBASE_INPUT_ON_MEASURE =\n    \"UIBaseInput::OnMeasure\";\ninline constexpr const char* const UIBASE_UPDATE_LAYOUT =\n    \"UIBase::UpdateLayout\";\ninline constexpr const char* const UIBASE_APPLY_TRANSFORM =\n    \"UIBase::ApplyTransform\";\ninline constexpr const char* const UIBASE_CREATE_OR_UPDATE_BACKGROUND =\n    \"UIBase::CreateOrUpdateBackground\";\ninline constexpr const char* const UIBASE_ON_DRAW = \"UIBase::OnDraw\";\ninline constexpr const char* const UIBASE_ON_DRAW_BEHIND =\n    \"UIBase::OnDrawBehind\";\ninline constexpr const char* const UIBASE_ON_OVERLAY_DRAW =\n    \"UIBase::OnOverlayDraw\";\ninline constexpr const char* const UIBASE_INIT_DRAW_NODE =\n    \"UIBase::InitDrawNode\";\ninline constexpr const char* const UI_EXPOSURE_EXEC = \"UIExposure::Exec\";\ninline constexpr const char* const UI_FOLD_VIEW_UPDATE_LAYOUT =\n    \"UIFoldView::UpdateLayout\";\ninline constexpr const char* const UI_FOLD_VIEW_UPDATE_FOLD_VIEW_LAYOUT =\n    \"UIFoldView::UpdateFoldViewLayout\";\ninline constexpr const char* const UI_IMAGE_UPDATE_LAYOUT =\n    \"UIImage::UpdateLayout\";\ninline constexpr const char* const UI_LIST_ON_MEASURE = \"UIList::OnMeasure\";\ninline constexpr const char* const UI_LIST_UPDATE_CONTENT_SIZE =\n    \"UIList::UpdateContentSize\";\ninline constexpr const char* const UI_OWNER_CREATE_JS_UI =\n    \"UIOwner::CreateJSUI\";\ninline constexpr const char* const UI_OWNER_CREATE_UI = \"UIOwner::CreateUI.\";\ninline constexpr const char* const UI_OWNER_UPDATE_UI = \"UIOwner::UpdateUI\";\ninline constexpr const char* const UI_OWNER_ON_NODE_READY =\n    \"UIOwner::OnNodeReady\";\ninline constexpr const char* const UI_OWNER_UPDATE_LAYOUT =\n    \"UIOwner::UpdateLayout\";\ninline constexpr const char* const UI_OWNER_ON_LAYOUT_FINISH =\n    \"UIOwner::OnLayoutFinish\";\ninline constexpr const char* const UI_OWNER_ATTACH_PAGE_ROOT =\n    \"UIOwner::AttachPageRoot\";\ninline constexpr const char* const UI_OWNER_START_FLUENCY_TRACE =\n    \"StartFluencyTrace\";\ninline constexpr const char* const UI_OWNER_STOP_FLUENCY_TRACE =\n    \"StopFluencyTrace\";\ninline constexpr const char* const UI_ROOT_CONSTRUCTOR = \"UIRoot::UIRoot\";\ninline constexpr const char* const UI_SCROLL_ON_MEASURE = \"UIScroll::OnMeasure\";\ninline constexpr const char* const UI_SCROLL_UPDATE_CONTENT_SIZE =\n    \"UIScroll::UpdateContentSize\";\ninline constexpr const char* const UI_SWIPER_ON_PROP_UPDATE =\n    \"UISwiper::OnPropUpdate\";\ninline constexpr const char* const UI_TEXT_AREA_ON_PROP_UPDATE =\n    \"UITextArea::OnPropUpdate\";\ninline constexpr const char* const NODE_MANAGER_MEASURE_NODE =\n    \"NodeManager::MeasureNode\";\ninline constexpr const char* const NODE_MANAGER_LAYOUT_NODE =\n    \"NodeManager::LayoutNode\";\n#endif  // #if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n\n#endif  // CORE_BASE_HARMONY_HARMONY_TRACE_EVENT_DEF_H_\n"
  },
  {
    "path": "core/base/harmony/napi_convert_helper.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/harmony/napi_convert_helper.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/platform/harmony/napi_util.h\"\n#include \"base/include/value/byte_array.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/base/harmony/harmony_trace_event_def.h\"\n#include \"core/base/js_constants.h\"\n#include \"core/renderer/utils/value_utils.h\"\n#include \"core/runtime/lepus/json_parser.h\"\n\nnamespace lynx {\nnamespace base {\n\nstatic constexpr int kMaxDepth = 100;\n\nvoid create_lepus_value_from_js_object(napi_env env, napi_value obj,\n                                       lepus_value& result, int depth) {\n  napi_valuetype type;\n  napi_typeof(env, obj, &type);\n  switch (type) {\n    case napi_undefined: {\n      result.SetUndefined();\n    } break;\n    case napi_null: {\n      result.SetNil();\n    } break;\n    case napi_bigint: {\n      int64_t i = base::NapiUtil::ConvertToBigInt64(env, obj);\n      result = lepus_value(i);\n    } break;\n    case napi_number: {\n      double d = base::NapiUtil::ConvertToDouble(env, obj);\n      result = lepus_value(d);\n    } break;\n    case napi_string: {\n      std::string s = base::NapiUtil::ConvertToString(env, obj);\n      result = lepus_value(s);\n    } break;\n    case napi_boolean: {\n      bool b = base::NapiUtil::ConvertToBoolean(env, obj);\n      result = lepus_value(b);\n    } break;\n    case napi_object: {\n      if (base::NapiUtil::IsArrayBuffer(env, obj)) {\n        std::unique_ptr<uint8_t[]> buffer{nullptr};\n        size_t length{0};\n        bool ok =\n            base::NapiUtil::ConvertToArrayBuffer(env, obj, buffer, length);\n        if (ok) {\n          result =\n              lepus_value(lepus::ByteArray::Create(std::move(buffer), length));\n        } else {\n          LOGE(\"Fail to convert array buffer\");\n          result.SetUndefined();\n        }\n      } else {\n        if (depth >= kMaxDepth) {\n          napi_value object_keys;\n          napi_get_all_property_names(\n              env, obj, napi_key_own_only, napi_key_enumerable,\n              napi_key_numbers_to_strings, &object_keys);\n          std::vector<std::string> array_string;\n          NapiUtil::ConvertToArrayString(env, object_keys, array_string);\n          std::string output;\n          for (const auto& item : array_string) {\n            output.append(item + \", \");\n          }\n          LOGE(\"Maybe circular structure: \" << output);\n#if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n          abort();\n#endif\n        }\n        if (base::NapiUtil::IsArray(env, obj)) {\n          auto arr = lepus::CArray::Create();\n          uint32_t length;\n          napi_get_array_length(env, obj, &length);\n          for (uint32_t i = 0; i < length; i++) {\n            napi_value item;\n            napi_get_element(env, obj, i, &item);\n            lepus_value value;\n            create_lepus_value_from_js_object(env, item, value, depth + 1);\n            arr->push_back(value);\n          }\n          result = lepus_value(arr);\n        } else {\n          auto dict = lepus::Dictionary::Create();\n          napi_value object_keys;\n          napi_get_all_property_names(\n              env, obj, napi_key_own_only, napi_key_enumerable,\n              napi_key_numbers_to_strings, &object_keys);\n          uint32_t length;\n          napi_get_array_length(env, object_keys, &length);\n          dict->reserve(length);\n          for (uint32_t i = 0; i < length; i++) {\n            napi_value k;\n            napi_get_element(env, object_keys, i, &k);\n            napi_value v;\n            napi_get_property(env, obj, k, &v);\n            std::string key = NapiUtil::ConvertToString(env, k);\n            lepus_value value;\n            create_lepus_value_from_js_object(env, v, value, depth + 1);\n            dict->SetValue(key, value);\n          }\n          result = lepus_value(dict);\n        }\n      }\n    } break;\n    default:\n      result.SetUndefined();\n      break;\n  }\n}\n\nlepus_value NapiConvertHelper::JSONToLepusValue(napi_env env, napi_value obj) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, NAPI_CONVERT_HELPER_JSON_TO_LEPUS_VALUE);\n  napi_valuetype type;\n  napi_typeof(env, obj, &type);\n  if (type == napi_valuetype::napi_string) {\n    std::string s = base::NapiUtil::ConvertToString(env, obj);\n    return lepus::jsonValueTolepusValue(s.c_str());\n  } else if (type == napi_valuetype::napi_object) {\n    lepus_value result;\n    create_lepus_value_from_js_object(env, obj, result, 0);\n    return result;\n  } else {\n    LOGD(\"JSONToLepusValue:: type should be string or object\");\n  }\n  return lepus_value();\n}\n\nlepus_value NapiConvertHelper::ConvertToLepusValue(napi_env env,\n                                                   napi_value obj) {\n  lepus_value result;\n  create_lepus_value_from_js_object(env, obj, result, 0);\n  return result;\n}\n\nvoid NapiConvertHelper::AssembleArray(napi_env env, napi_value array,\n                                      uint32_t index,\n                                      const lepus::Value& value) {\n  napi_value result = NapiConvertHelper::CreateNapiValue(env, value);\n  napi_set_element(env, array, index, result);\n}\n\nvoid NapiConvertHelper::AssembleMap(napi_env env, napi_value object,\n                                    const char* key,\n                                    const lepus::Value& value) {\n  napi_value k;\n  napi_create_string_latin1(env, key, NAPI_AUTO_LENGTH, &k);\n  napi_value result = NapiConvertHelper::CreateNapiValue(env, value);\n  napi_set_property(env, object, k, result);\n}\n\nnapi_value NapiConvertHelper::CreateNapiValue(napi_env env,\n                                              const lepus::Value& value) {\n  napi_value result = nullptr;\n  if (value.IsNil()) {\n    napi_get_null(env, &result);\n  } else if (value.IsString()) {\n    napi_create_string_utf8(env, value.CString(), NAPI_AUTO_LENGTH, &result);\n  } else if (value.IsInt32()) {\n    napi_create_int32(env, value.Int32(), &result);\n  } else if (value.IsInt64()) {\n    int64_t i = value.Int64();\n    // When integer beyond limit, use BigInt Object to define it\n    if (i < runtime::js::kMinJavaScriptNumber ||\n        i > runtime::js::kMaxJavaScriptNumber) {\n      napi_create_bigint_int64(env, i, &result);\n    } else {\n      napi_create_int64(env, i, &result);\n    }\n  } else if (value.IsUInt32()) {\n    napi_create_uint32(env, value.UInt32(), &result);\n  } else if (value.IsUInt64()) {\n    uint64_t u = value.UInt64();\n    if (u > runtime::js::kMaxJavaScriptNumber) {\n      napi_create_bigint_int64(env, u, &result);\n    } else {\n      napi_create_int64(env, u, &result);\n    }\n  } else if (value.IsNumber()) {\n    napi_create_double(env, value.Number(), &result);\n  } else if (value.IsArrayOrJSArray()) {\n    napi_create_array(env, &result);\n    tasm::ForEachLepusValue(\n        value, [&env, &result](const lepus::Value& i, const lepus::Value& v) {\n          AssembleArray(env, result, i.Int64(), v);\n        });\n  } else if (value.IsObject()) {\n    napi_create_object(env, &result);\n    tasm::ForEachLepusValue(\n        value, [&env, &result](const lepus::Value& k, const lepus::Value& v) {\n          AssembleMap(env, result, k.ToString().c_str(), v);\n        });\n  } else if (value.IsByteArray()) {\n    const auto& buffer = value.ByteArray();\n    void* result_data;\n    napi_create_buffer_copy(env, buffer->GetLength(), buffer->GetPtr(),\n                            &result_data, &result);\n  } else if (value.IsBool()) {\n    napi_get_boolean(env, value.Bool(), &result);\n  } else if (value.IsUndefined()) {\n    napi_get_undefined(env, &result);\n  } else {\n    LOGE(\"NapiConvertHelper, unknown type :\" << value.Type());\n  }\n  return result;\n}\n\nvoid copy_napi_value(napi_env env, napi_value obj, napi_value& result) {\n  napi_valuetype type;\n  napi_typeof(env, obj, &type);\n  switch (type) {\n    case napi_undefined: {\n      napi_get_undefined(env, &result);\n    } break;\n    case napi_null: {\n      napi_get_null(env, &result);\n    } break;\n    case napi_number: {\n      double d = base::NapiUtil::ConvertToDouble(env, obj);\n      napi_create_double(env, d, &result);\n    } break;\n    case napi_bigint: {\n      int64_t i = base::NapiUtil::ConvertToBigInt64(env, obj);\n      napi_create_bigint_int64(env, i, &result);\n    } break;\n    case napi_string: {\n      std::string s = base::NapiUtil::ConvertToString(env, obj);\n      napi_create_string_utf8(env, s.c_str(), s.length(), &result);\n    } break;\n    case napi_boolean: {\n      bool b = base::NapiUtil::ConvertToBoolean(env, obj);\n      napi_get_boolean(env, b, &result);\n    } break;\n    case napi_object: {\n      if (base::NapiUtil::IsArrayBuffer(env, obj)) {\n        size_t length{0};\n        void* data{nullptr};\n        napi_get_arraybuffer_info(env, obj, &data, &length);\n        void* result_data;\n        napi_create_buffer_copy(env, length, data, &result_data, &result);\n      } else if (base::NapiUtil::IsArray(env, obj)) {\n        uint32_t length;\n        napi_get_array_length(env, obj, &length);\n        napi_create_array_with_length(env, length, &result);\n        for (uint32_t i = 0; i < length; i++) {\n          napi_value item;\n          napi_get_element(env, obj, i, &item);\n          napi_value value;\n          copy_napi_value(env, item, value);\n          napi_set_element(env, result, i, value);\n        }\n      } else {\n        napi_create_object(env, &result);\n        napi_value object_keys;\n        napi_get_all_property_names(env, obj, napi_key_own_only,\n                                    napi_key_enumerable,\n                                    napi_key_numbers_to_strings, &object_keys);\n        uint32_t length;\n        napi_get_array_length(env, object_keys, &length);\n        for (uint32_t i = 0; i < length; i++) {\n          napi_value k;\n          napi_get_element(env, object_keys, i, &k);\n          napi_value v;\n          napi_get_property(env, obj, k, &v);\n          napi_value key;\n          copy_napi_value(env, k, key);\n          napi_value value;\n          copy_napi_value(env, v, value);\n          napi_set_property(env, result, key, value);\n        }\n      }\n    } break;\n    default:\n      napi_get_undefined(env, &result);\n      break;\n  }\n}\n\nnapi_value NapiConvertHelper::CloneNapiValue(napi_env env, napi_value obj) {\n  napi_value result;\n  copy_napi_value(env, obj, result);\n  return result;\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/harmony/napi_convert_helper.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_HARMONY_NAPI_CONVERT_HELPER_H_\n#define CORE_BASE_HARMONY_NAPI_CONVERT_HELPER_H_\n\n#include <node_api.h>\n\n#include <string>\n#include <unordered_set>\n#include <vector>\n\n#include \"base/include/value/base_value.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass NapiConvertHelper {\n public:\n  static lepus_value JSONToLepusValue(napi_env env, napi_value obj);\n\n  static lepus_value ConvertToLepusValue(napi_env env, napi_value obj);\n\n  static void AssembleArray(napi_env env, napi_value array, uint32_t index,\n                            const lepus::Value& value);\n\n  static void AssembleMap(napi_env env, napi_value object, const char* key,\n                          const lepus::Value& value);\n\n  static napi_value CreateNapiValue(napi_env env, const lepus::Value& value);\n\n  static napi_value CloneNapiValue(napi_env env, napi_value obj);\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_HARMONY_NAPI_CONVERT_HELPER_H_\n"
  },
  {
    "path": "core/base/harmony/props_constant.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_HARMONY_PROPS_CONSTANT_H_\n#define CORE_BASE_HARMONY_PROPS_CONSTANT_H_\n\nnamespace lynx {\nnamespace tasm {\nnamespace harmony {\n// props\nstatic constexpr const char* kTextAttr = \"text\";\nstatic constexpr const char* kColor = \"color\";\nstatic constexpr const char* kFontSize = \"font-size\";\nstatic constexpr const char* kLineHeight = \"line-height\";\nstatic constexpr const char* kTextAlign = \"text-align\";\nstatic constexpr const char* kFontFamily = \"font-family\";\nstatic constexpr const char* kTextMaxLine = \"text-maxline\";\nstatic constexpr const char* kTextIndent = \"text-indent\";\nstatic constexpr const char* kTextOverflow = \"text-overflow\";\nstatic constexpr const char* kDirection = \"direction\";\nstatic constexpr const char* kVerticalAlign = \"vertical-align\";\nstatic constexpr const char* kFontWeight = \"font-weight\";\nstatic constexpr const char* kFontStyle = \"font-style\";\nstatic constexpr const char* kLetterSpacing = \"letter-spacing\";\nstatic constexpr const char* kWhiteSpace = \"white-space\";\nstatic constexpr const char* kTextDecoration = \"text-decoration\";\nstatic constexpr const char* kWordBreak = \"word-break\";\nstatic constexpr const char* kTextStrokeWidth = \"text-stroke-width\";\nstatic constexpr const char* kTextStrokeColor = \"text-stroke-color\";\nstatic constexpr const char* kTextShadow = \"text-shadow\";\nstatic constexpr const char* kEventThrough = \"event-through\";\nstatic constexpr const char* kIgnoreFocus = \"ignore-focus\";\nstatic constexpr const char* kPointerEvents = \"pointer-events\";\nstatic constexpr const char* kIdSelector = \"idSelector\";\n}  // namespace harmony\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_BASE_HARMONY_PROPS_CONSTANT_H_\n"
  },
  {
    "path": "core/base/harmony/threading/js_thread_config_getter_harmony.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include <napi/native_api.h>\n#include <uv.h>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/thread.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/base/harmony/harmony_napi_env_holder.h\"\n#include \"core/base/harmony/harmony_trace_event_def.h\"\n#include \"core/base/threading/js_thread_config_getter.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace {\nvoid SetupArkTSRuntime() {\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, SETUP_ARK_TS_RUNTIME);\n  LOGI(\"Start setup arkts runtime.\")\n\n  napi_env js_thread_env;\n  napi_status status = napi_create_ark_runtime(&js_thread_env);\n  DCHECK(status == napi_ok);\n  LOGI(\"Create arkts runtime with result \" << status);\n\n  uv_loop_t* loop;\n  napi_get_uv_event_loop(js_thread_env, &loop);\n\n  fml::MessageLoop::EnsureInitializedForCurrentThread(loop);\n\n  harmony::InitializationNapiEnvForCurrentThread(js_thread_env);\n\n  LOGI(\"Setup arkts runtime done.\")\n}\n}  // namespace\n\nfml::Thread::ThreadConfig GetJSThreadConfig(const std::string& worker_name) {\n  return fml::Thread::ThreadConfig{\n      worker_name, fml::Thread::ThreadPriority::HIGH,\n      std::make_shared<base::closure>(\n          static_cast<void (*)()>(SetupArkTSRuntime))};\n}\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/harmony/vsync_monitor_harmony.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/harmony/vsync_monitor_harmony.h\"\n\n#include <memory>\n\n#include \"base/include/log/logging.h\"\n#include \"base/include/platform/harmony/harmony_vsync_manager.h\"\n\nnamespace lynx {\nnamespace base {\n\nnamespace {\n\n// nano seconds\nstatic std::atomic_uint g_refresh_rate_ = 16666700;\n\n}  // namespace\n\nstd::shared_ptr<VSyncMonitor> VSyncMonitor::Create(bool is_on_ui_thread) {\n  return std::make_shared<lynx::base::VSyncMonitorHarmony>();\n}\n\nvoid VSyncMonitorHarmony::OnUpdateRefreshRate(int64_t refresh_rate) {\n  DCHECK(refresh_rate > 0);\n  g_refresh_rate_ = (1.0f / refresh_rate) * (1000.0 * 1000.0 * 1000.0);\n}\n\nvoid VSyncMonitorHarmony::RequestVSync() {\n  // Request the vsync signal from global vsync manager.\n  base::HarmonyVsyncManager::GetInstance().RequestVSync(\n      [weak_this = weak_from_this()](long long timestamp) {\n        auto shared_this = weak_this.lock();\n        if (shared_this) {\n          int64_t frame_nanos = static_cast<int64_t>(timestamp);\n          auto frame_time = fml::TimePoint::FromEpochDelta(\n              fml::TimeDelta::FromNanoseconds(frame_nanos));\n          auto now = fml::TimePoint::Now();\n          if (frame_time > now) {\n            frame_time = now;\n          }\n          auto target_time =\n              frame_time + fml::TimeDelta::FromNanoseconds(g_refresh_rate_);\n          auto frame_start_time = frame_time.ToEpochDelta().ToNanoseconds();\n          auto frame_target_time = target_time.ToEpochDelta().ToNanoseconds();\n          shared_this->OnVSync(frame_start_time, frame_target_time);\n        }\n      });\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/harmony/vsync_monitor_harmony.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_HARMONY_VSYNC_MONITOR_HARMONY_H_\n#define CORE_BASE_HARMONY_VSYNC_MONITOR_HARMONY_H_\n\n#include \"base/include/fml/time/time_point.h\"\n#include \"core/base/threading/vsync_monitor.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass VSyncMonitorHarmony : public base::VSyncMonitor {\n public:\n  VSyncMonitorHarmony() = default;\n  ~VSyncMonitorHarmony() override = default;\n\n  static void OnUpdateRefreshRate(int64_t refresh_rate);\n\n private:\n  void RequestVSync() override;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_HARMONY_VSYNC_MONITOR_HARMONY_H_\n"
  },
  {
    "path": "core/base/js_constants.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_JS_CONSTANTS_H_\n#define CORE_BASE_JS_CONSTANTS_H_\n#include <string>\n\nnamespace lynx {\nnamespace runtime {\nnamespace js {\n/*\n * https://www.npmjs.com/package/jsbi\n */\n\nstatic constexpr const char* const CONSTRUCTOR_BIG_INT = \"BigInt\";\nstatic constexpr const char* const BIG_INT_VAL = \"__lynx_val__\";\nstatic constexpr const char* const TO_JSON = \"toJSON\";\nstatic constexpr const char* const OPERATOR_ADD = \"add\";\nstatic constexpr const char* const OPERATOR_SUBTRACT = \"subtract\";\nstatic constexpr const char* const OPERATOR_MULTIPLY = \"multiply\";\nstatic constexpr const char* const OPERATOR_DIVIDE = \"divide\";\nstatic constexpr const char* const OPERATOR_REMAINDER = \"remainder\";\nstatic constexpr const char* const OPERATOR_EQUAL = \"equal\";\nstatic constexpr const char* const OPERATOR_NOT_EQUAL = \"notEqual\";\nstatic constexpr const char* const OPERATOR_LESS_THAN = \"lessThan\";\nstatic constexpr const char* const OPERATOR_LESS_THAN_OR_EQUAL =\n    \"lessThanOrEqual\";\nstatic constexpr const char* const OPERATOR_GREATER_THAN = \"greaterThan\";\nstatic constexpr const char* const OPERATOR_GREATER_THAN_OR_EQUAL =\n    \"greaterThanOrEqual\";\nstatic constexpr const int BIG_INT_OBJ_PARAMS_COUNT = 4;\nstatic constexpr const int64_t kMaxJavaScriptNumber = 9007199254740991;\nstatic constexpr const int64_t kMinJavaScriptNumber = -9007199254740991;\n\n// Temporary key for transferring NAPI value to Piper value via global object\nstatic constexpr const char* const kLynxNapiValueToPiperValueTempObject =\n    \"__lynx_napi_value_to_piper_value_temp_object__\";\n\n// TODO: add more\n\n}  // namespace js\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_BASE_JS_CONSTANTS_H_\n"
  },
  {
    "path": "core/base/json/json_util.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_BASE_JSON_JSON_UTIL_H_\n#define CORE_BASE_JSON_JSON_UTIL_H_\n\n#include <string>\n\n#include \"core/base/lynx_export.h\"\n#include \"third_party/rapidjson/document.h\"\n#include \"third_party/rapidjson/error/en.h\"\n#include \"third_party/rapidjson/reader.h\"\n#include \"third_party/rapidjson/stringbuffer.h\"\n#include \"third_party/rapidjson/writer.h\"\nnamespace lynx {\nnamespace base {\n\nextern rapidjson::MemoryPoolAllocator<>* global_allocate_;\n\n//   rapidjson::Value ParseJson(const std::string& json);\nLYNX_EXPORT_FOR_DEVTOOL std::string ToJson(const rapidjson::Value& json);\n\nconst char* TypeName(const rapidjson::Value& value);\nbool IsNumber(const rapidjson::Value& value);\nbool IsArray(const rapidjson::Value& value);\nbool IsNull(const rapidjson::Value& value);\nLYNX_EXPORT_FOR_DEVTOOL rapidjson::Document strToJson(const char* json);\n\n//  const rapidjson::Value& GetDefault(const rapidjson::Value& json, const\n//  std::string& key, rapidjson::Value&& value);\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_JSON_JSON_UTIL_H_\n"
  },
  {
    "path": "core/base/json/json_utils.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"base/include/log/logging.h\"\n#include \"core/base/json/json_util.h\"\n#include \"third_party/rapidjson/document.h\"\n#include \"third_party/rapidjson/error/en.h\"\n#include \"third_party/rapidjson/reader.h\"\n#include \"third_party/rapidjson/stringbuffer.h\"\n#include \"third_party/rapidjson/writer.h\"\n\nnamespace lynx {\nnamespace base {\n\nrapidjson::MemoryPoolAllocator<>* global_allocate_ =\n    new rapidjson::MemoryPoolAllocator<>();\n\nstd::string ToJson(const rapidjson::Value& json) {\n  rapidjson::Value msg(rapidjson::kObjectType);\n  rapidjson::StringBuffer buffer;\n  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);\n  json.Accept(writer);\n  std::string str = buffer.GetString();\n\n  return str;\n}\n\nbool IsNumber(const rapidjson::Value& value) { return value.IsNumber(); }\n\nbool IsArray(const rapidjson::Value& value) { return value.IsArray(); }\n\nbool IsNull(const rapidjson::Value& value) { return value.IsNull(); }\n\nconst char* TypeName(const rapidjson::Value& value) {\n  switch (value.GetType()) {\n    case rapidjson::kNullType:\n      return \"null\";\n    case rapidjson::kNumberType:\n      return \"number\";\n    case rapidjson::kStringType:\n      return \"string\";\n    case rapidjson::kTrueType:\n    case rapidjson::kFalseType:\n      return \"bool\";\n    case rapidjson::kArrayType:\n      return \"array\";\n    case rapidjson::kObjectType:\n      return \"object\";\n    default:\n      return \"\";\n  }\n  return \"\";\n}\n\nrapidjson::Document strToJson(const char* json) {\n  rapidjson::Document document;\n\n  if (document.Parse(json).HasParseError()) {\n    printf(\" parse json str error: %s\\n\", json);\n    return document;\n  }\n\n  return document;\n}\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/json/json_utils_unittests.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/json/json_util.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/rapidjson/document.h\"\n\nnamespace lynx {\nnamespace base {\n\nTEST(JSON, all) {\n  {\n    std::string json_str = \"Is an illegal json string\";\n    rapidjson::Document value = strToJson(json_str.c_str());\n    EXPECT_TRUE(value.IsNull());\n    EXPECT_TRUE(IsNull(value));\n    EXPECT_FALSE(IsArray(value));\n    EXPECT_FALSE(IsNumber(value));\n    EXPECT_STREQ(TypeName(value), \"null\");\n    std::string raw_str = ToJson(value);\n    EXPECT_STREQ(raw_str.c_str(), \"null\");\n  }\n\n  {\n    std::string json_str = \"[1,2]\";\n    rapidjson::Document value = strToJson(json_str.c_str());\n    EXPECT_TRUE(value.IsArray());\n    EXPECT_STREQ(TypeName(value), \"array\");\n    int target_result[] = {1, 2};\n    for (unsigned int index = 0; index < value.GetArray().Size(); index++) {\n      EXPECT_TRUE(IsNumber((value.GetArray())[index]));\n      EXPECT_STREQ(TypeName((value.GetArray())[index]), \"number\");\n      EXPECT_TRUE((value.GetArray())[index].IsNumber());\n      EXPECT_EQ((value.GetArray())[index].GetInt(), target_result[index]);\n    }\n    EXPECT_FALSE(IsNull(value));\n    EXPECT_TRUE(IsArray(value));\n    EXPECT_FALSE(IsNumber(value));\n    std::string raw_str = ToJson(value);\n    EXPECT_STREQ(raw_str.c_str(), json_str.c_str());\n  }\n\n  {\n    std::string json_str = \"{\\\"name\\\":\\\"lynx\\\",\\\"age\\\":10000,\\\"enable\\\":true}\";\n    rapidjson::Document value = strToJson(json_str.c_str());\n    EXPECT_FALSE(IsNull(value));\n    EXPECT_FALSE(IsArray(value));\n    EXPECT_FALSE(IsNumber(value));\n    EXPECT_TRUE(value.IsObject());\n    EXPECT_STREQ(TypeName(value), \"object\");\n    EXPECT_TRUE(value.GetObject().MemberCount() == 3);\n    EXPECT_TRUE(value.GetObject().HasMember(\"name\"));\n    EXPECT_TRUE(value.GetObject()[\"name\"].IsString());\n    EXPECT_STREQ(TypeName(value.GetObject()[\"name\"]), \"string\");\n    EXPECT_STREQ(value.GetObject()[\"name\"].GetString(), \"lynx\");\n    EXPECT_TRUE(value.GetObject().HasMember(\"age\"));\n    EXPECT_TRUE(IsNumber(value.GetObject()[\"age\"]));\n    EXPECT_EQ(value.GetObject()[\"age\"].GetInt(), 10000);\n\n    EXPECT_TRUE(value.GetObject().HasMember(\"enable\"));\n    EXPECT_TRUE(value.GetObject()[\"enable\"].IsBool());\n    EXPECT_STREQ(TypeName(value.GetObject()[\"enable\"]), \"bool\");\n    EXPECT_TRUE(value.GetObject()[\"enable\"].GetBool());\n\n    std::string raw_str = ToJson(value);\n    EXPECT_STREQ(raw_str.c_str(), json_str.c_str());\n  }\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/lynx_export.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_LYNX_EXPORT_H_\n#define CORE_BASE_LYNX_EXPORT_H_\n\n// Symbols in the lynx engine are not exported by default due to\n// -fvisibility=hidden. We provide two macros to help you export some symbols:\n// 1. LYNX_EXPORT. If certain symbols need to be accessed externally, they\n// should be marked with the LYNX_EXPORT.\n// 2. LYNX_EXPORT_FOR_DEVTOOL. If symbols need to be exported solely for use by\n// the devtool, the LYNX_EXPORT_FOR_DEVTOOL should be applied.\n//\n// If you need stricter symbol export rules from the Lynx engine for purposes\n// such as better package management, you can provide LYNX_NO_EXPORT to gn\n// script or customize the export rules using --version-script. Importantly, if\n// a symbol is not marked with LYNX_EXPORT or LYNX_EXPORT_FOR_DEVTOOL, it can\n// not be exported, even if you use a\n// --version-script to customize the export rules.\n#ifdef LYNX_NO_EXPORT\n#define LYNX_EXPORT\n#define LYNX_EXPORT_FOR_DEVTOOL\n#else  // LYNX_NO_EXPORT\n#define LYNX_EXPORT __attribute__((visibility(\"default\")))\n\n#if EXPORT_SYMBOLS_FOR_DEVTOOL\n#define LYNX_EXPORT_FOR_DEVTOOL __attribute__((visibility(\"default\")))\n#else\n#define LYNX_EXPORT_FOR_DEVTOOL\n#endif\n#endif  // LYNX_NO_EXPORT\n\n#define LYNX_HIDE __attribute__((visibility(\"hidden\")))\n\n#endif  // CORE_BASE_LYNX_EXPORT_H_\n"
  },
  {
    "path": "core/base/lynx_trace_categories.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_LYNX_TRACE_CATEGORIES_H_\n#define CORE_BASE_LYNX_TRACE_CATEGORIES_H_\n\n#include \"base/trace/native/trace_defines.h\"\n\nstatic constexpr const char* const LYNX_TRACE_CATEGORY_VITALS = \"vitals\";\nstatic constexpr const char* const LYNX_TRACE_CATEGORY_JAVASCRIPT =\n    \"javascript\";\nstatic constexpr const char* const LYNX_TRACE_CATEGORY_SCREENSHOTS =\n    \"disabled-by-default-devtools.screenshot\";\nstatic constexpr const char* const LYNX_TRACE_CATEGORY_FPS =\n    \"disabled-by-default-devtools.timeline.frame\";\nstatic constexpr const char* const LYNX_TRACE_CATEGORY_DEVTOOL_TIMELINE =\n    \"disabled-by-default-devtools.timeline\";\nstatic constexpr const char* const LYNX_TRACE_CATEGORY_JSB = \"jsb\";\nstatic constexpr const char* const LYNX_TRACE_CATEGORY_ATRACE = \"system\";\nstatic constexpr const char* const INSTANCE_ID = \"instance_id\";\nstatic constexpr const char* const PIPELINE_ID = \"pipeline_id\";\nstatic constexpr const char* const CALLBACK_ID = \"callback_id\";\n\n#endif  // CORE_BASE_LYNX_TRACE_CATEGORIES_H_\n"
  },
  {
    "path": "core/base/memory/memory_pressure_callback.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/memory/memory_pressure_callback.h\"\n\n#include <algorithm>\n#include <list>\n#include <mutex>\n#include <utility>\n\n#include \"base/include/no_destructor.h\"\n#include \"base/trace/native/trace_event.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace {\nclass MemoryPressureObserver {\n public:\n  MemoryPressureObserver() = default;\n  ~MemoryPressureObserver() = default;\n\n  void AddObserver(MemoryPressureCallback* listener) {\n    std::scoped_lock<std::mutex> lock(observers_mutex_);\n    observers_.push_back(listener);\n  }\n\n  void RemoveObserver(MemoryPressureCallback* listener) {\n    std::scoped_lock<std::mutex> lock(observers_mutex_);\n    auto it = std::find(observers_.begin(), observers_.end(), listener);\n    if (it != observers_.end()) {\n      observers_.erase(it);\n    }\n  }\n\n  void Notify(MemoryPressureLevel memory_pressure_level) {\n    std::scoped_lock<std::mutex> lock(observers_mutex_);\n    for (auto* listener : observers_) {\n      listener->OnLowMemory(memory_pressure_level);\n    }\n  }\n\n private:\n  std::mutex observers_mutex_;\n  std::list<MemoryPressureCallback*> observers_;\n};\n\nMemoryPressureObserver& GetMemoryPressureObserver() {\n  static NoDestructor<MemoryPressureObserver> observer{};\n  return *observer;\n}\n}  // namespace\n\nvoid MemoryPressureCallback::NotifyMemoryPressure(\n    MemoryPressureLevel memory_pressure_level) {\n  if (memory_pressure_level ==\n      MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE) {\n    return;\n  }\n  GetMemoryPressureObserver().Notify(memory_pressure_level);\n}\n\nMemoryPressureCallback::MemoryPressureCallback(Callback callback)\n    : callback_(std::move(callback)) {\n  GetMemoryPressureObserver().AddObserver(this);\n}\n\nMemoryPressureCallback::~MemoryPressureCallback() {\n  GetMemoryPressureObserver().RemoveObserver(this);\n}\n\nvoid MemoryPressureCallback::OnLowMemory(\n    MemoryPressureLevel memory_pressure_level) {\n  TRACE_EVENT(\"MemoryPressureCallback\", \"OnLowMemory\");\n  callback_(memory_pressure_level);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/memory/memory_pressure_callback.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_MEMORY_MEMORY_PRESSURE_CALLBACK_H_\n#define CORE_BASE_MEMORY_MEMORY_PRESSURE_CALLBACK_H_\n\n#include <functional>\n\n#include \"base/include/memory/memory_pressure_level.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass MemoryPressureCallback {\n public:\n  using Callback = std::function<void(MemoryPressureLevel)>;\n\n  static void NotifyMemoryPressure(MemoryPressureLevel memory_pressure_level);\n\n  explicit MemoryPressureCallback(Callback callback);\n\n  ~MemoryPressureCallback();\n  void OnLowMemory(MemoryPressureLevel memory_pressure_level);\n\n private:\n  Callback callback_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_MEMORY_MEMORY_PRESSURE_CALLBACK_H_\n"
  },
  {
    "path": "core/base/memory/memory_pressure_callback_unittest.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/memory/memory_pressure_callback.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nTEST(MemoryPressureCallbackTest, NotifyMemoryPressure) {\n  int count = 0;\n  MemoryPressureCallback listener(\n      [&count](MemoryPressureLevel level) { ++count; });\n\n  MemoryPressureCallback::NotifyMemoryPressure(\n      MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE);\n  EXPECT_EQ(1, count);\n\n  MemoryPressureCallback::NotifyMemoryPressure(\n      MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_NONE);\n  EXPECT_EQ(1, count);\n\n  MemoryPressureCallback::NotifyMemoryPressure(\n      MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL);\n  EXPECT_EQ(2, count);\n}\n\nTEST(MemoryPressureCallbackTest, UnregisterOnDestruction) {\n  int count = 0;\n  {\n    MemoryPressureCallback listener(\n        [&count](MemoryPressureLevel level) { ++count; });\n    MemoryPressureCallback::NotifyMemoryPressure(\n        MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE);\n  }\n\n  EXPECT_EQ(1, count);\n\n  MemoryPressureCallback::NotifyMemoryPressure(\n      MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_CRITICAL);\n  EXPECT_EQ(1, count);\n}\n\nTEST(MemoryPressureCallbackTest, MultipleListeners) {\n  int count1 = 0;\n  int count2 = 0;\n  MemoryPressureCallback listener1(\n      [&count1](MemoryPressureLevel level) { ++count1; });\n  MemoryPressureCallback listener2(\n      [&count2](MemoryPressureLevel level) { ++count2; });\n\n  MemoryPressureCallback::NotifyMemoryPressure(\n      MemoryPressureLevel::MEMORY_PRESSURE_LEVEL_MODERATE);\n  EXPECT_EQ(1, count1);\n  EXPECT_EQ(1, count2);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/observer/observer.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_OBSERVER_OBSERVER_H_\n#define CORE_BASE_OBSERVER_OBSERVER_H_\n\nnamespace lynx {\nnamespace base {\nclass Observer {\n public:\n  Observer() : previous_(nullptr), next_(nullptr) {}\n  virtual ~Observer() {}\n  virtual void Update() = 0;\n  friend class ObserverList;\n\n private:\n  Observer* previous_;\n  Observer* next_;\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_OBSERVER_OBSERVER_H_\n"
  },
  {
    "path": "core/base/observer/observer_list.cc",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/observer/observer_list.h\"\n\n#include \"core/base/observer/observer.h\"\n\nnamespace lynx {\nnamespace base {\n\nvoid ObserverList::AddObserver(Observer* obs) { list_.emplace_back(obs); }\n\nvoid ObserverList::RemoveObserver(Observer* obs) { list_.remove(obs); }\n\nvoid ObserverList::ForEachObserver() {\n  for (auto it = list_.begin(); it != list_.end();) {\n    Observer* obs = *it;\n    it = list_.erase(it);\n    obs->Update();\n  }\n}\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/observer/observer_list.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_OBSERVER_OBSERVER_LIST_H_\n#define CORE_BASE_OBSERVER_OBSERVER_LIST_H_\n\n#include <list>\n\n#include \"core/base/lynx_export.h\"\n\nnamespace lynx {\nnamespace base {\nclass Observer;\nclass ObserverList {\n public:\n  ObserverList() {}\n\n  LYNX_EXPORT_FOR_DEVTOOL void AddObserver(Observer* obs);\n  LYNX_EXPORT_FOR_DEVTOOL void RemoveObserver(Observer* obs);\n  void Clear();\n  LYNX_EXPORT_FOR_DEVTOOL void ForEachObserver();\n\n private:\n  std::list<Observer*> list_;\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_OBSERVER_OBSERVER_LIST_H_\n"
  },
  {
    "path": "core/base/thread/atomic_lifecycle.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/thread/atomic_lifecycle.h\"\n\n#include <thread>\n\n#include \"base/include/log/logging.h\"\n\nnamespace {\nconstexpr int32_t STATE_FREE = 0;\nconstexpr int32_t STATE_LOCKED = 1;\nconstexpr int32_t STATE_TERMINATED = 2;\n}  // namespace\n\nnamespace lynx {\nnamespace base {\n\nbool AtomicLifecycle::TryLock(AtomicLifecycle* ptr) {\n  if (ptr == nullptr) {\n    return false;\n  }\n\n  int32_t previous = STATE_FREE;\n  bool locked = ptr->state_.compare_exchange_strong(previous, STATE_LOCKED) ||\n                (previous == STATE_LOCKED);\n\n  if (!locked) {\n    LOGW(\"AtomicLifecycle TryLock after TryTerminate\");\n    return false;\n  }\n\n  ++ptr->lock_count_;\n  return true;\n}\n\nbool AtomicLifecycle::TryFree(AtomicLifecycle* ptr) {\n  if (ptr == nullptr) {\n    return false;\n  }\n\n  int32_t lock_count = --ptr->lock_count_;\n  if (lock_count > 0) {\n    return false;\n  } else if (lock_count < 0) {\n    LOGW(\"AtomicLifecycle TryFree more than TryLock\");\n  }\n  ptr->state_ = STATE_FREE;\n  return true;\n}\n\nbool AtomicLifecycle::TryTerminate(AtomicLifecycle* ptr) {\n  if (ptr == nullptr) {\n    return false;\n  }\n\n  int32_t previous = STATE_FREE;\n  return ptr->state_.compare_exchange_strong(previous, STATE_TERMINATED);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/thread/atomic_lifecycle.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_THREAD_ATOMIC_LIFECYCLE_H_\n#define CORE_BASE_THREAD_ATOMIC_LIFECYCLE_H_\n\n#include <atomic>\n\nnamespace lynx {\nnamespace base {\n\nclass AtomicLifecycle {\n public:\n  static bool TryLock(AtomicLifecycle*);\n  static bool TryFree(AtomicLifecycle*);\n  static bool TryTerminate(AtomicLifecycle*);\n\n  AtomicLifecycle() = default;\n  ~AtomicLifecycle() = default;\n\n private:\n  std::atomic<std::int32_t> state_ = 0;\n  std::atomic<std::int32_t> lock_count_ = 0;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_THREAD_ATOMIC_LIFECYCLE_H_\n"
  },
  {
    "path": "core/base/thread/blocking_queue.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_THREAD_BLOCKING_QUEUE_H_\n#define CORE_BASE_THREAD_BLOCKING_QUEUE_H_\n\n#include <condition_variable>\n#include <iostream>\n#include <mutex>\n#include <random>\n#include <thread>\n#include <vector>\n\nnamespace lynx {\nnamespace base {\n\ntemplate <typename T>\nclass BlockingQueue {\n  std::mutex mutex_;\n  std::condition_variable not_full_;\n  std::condition_variable not_empty_;\n  size_t start_;\n  size_t end_;\n  size_t capacity_;\n  std::vector<T> vt_;\n\n public:\n  BlockingQueue(const BlockingQueue<T>& other) = delete;\n  BlockingQueue<T>& operator=(const BlockingQueue<T>& other) = delete;\n  explicit BlockingQueue(size_t capacity)\n      : start_(0), end_(0), capacity_(capacity), vt_(capacity + 1) {}\n\n  bool IsEmpty() { return end_ == start_; }\n\n  bool IsFull() { return (start_ + capacity_ - end_) % (capacity_ + 1) == 0; }\n\n  /**\n   * Add a new object to the queue.\n   * Blocks execution while queue is full.\n   */\n  void Push(const T& e) {\n    std::unique_lock<std::mutex> lock(mutex_);\n    while (IsFull()) {\n      not_full_.wait(lock);\n    }\n\n    vt_[end_++] = e;\n    end_ %= (capacity_ + 1);\n    not_empty_.notify_one();\n  }\n\n  /**\n   * Retrieve and remove the oldest object.\n   * Blocks execution while queue is empty.\n   */\n  T Pop() {\n    std::unique_lock<std::mutex> lock(mutex_);\n    while (IsEmpty()) {\n      not_empty_.wait(lock);\n    }\n\n    auto res = vt_[start_++];\n    start_ %= (capacity_ + 1);\n    not_full_.notify_one();\n    return res;\n  }\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_THREAD_BLOCKING_QUEUE_H_\n"
  },
  {
    "path": "core/base/thread/blocking_queue_unittest.cc",
    "content": "/*\n * Copyright (C) 2019 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/services/inputflinger/tests/BlockingQueue_test.cpp\n\n// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/thread/blocking_queue.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nTEST(BlockingQueueTest, Queue_AddAndRemove) {\n  constexpr size_t capacity = 10;\n  BlockingQueue<int> queue(capacity);\n\n  queue.Push(1);\n  ASSERT_EQ(queue.Pop(), 1);\n}\n\nTEST(BlockingQueueTest, Queue_isFIFO) {\n  constexpr size_t capacity = 10;\n  BlockingQueue<int> queue(capacity);\n\n  for (size_t i = 0; i < capacity; i++) {\n    queue.Push(static_cast<int>(i));\n  }\n  for (size_t i = 0; i < capacity; i++) {\n    ASSERT_EQ(queue.Pop(), static_cast<int>(i));\n  }\n}\n\nTEST(BlockingQueueTest, Queue_AllowsMultipleThreads) {\n  constexpr size_t capacity =\n      100;  // large capacity to increase likelihood that threads overlap\n  BlockingQueue<int> queue(capacity);\n\n  // Fill queue from a different thread\n  std::thread fillQueue([&queue]() {\n    for (size_t i = 0; i < capacity; i++) {\n      queue.Push(static_cast<int>(i));\n    }\n  });\n\n  // Make sure all elements are received in correct order\n  for (size_t i = 0; i < capacity; i++) {\n    ASSERT_EQ(queue.Pop(), static_cast<int>(i));\n  }\n\n  fillQueue.join();\n}\n\nTEST(BlockingQueueTest, Queue_BlocksWhileWaitingForElements) {\n  constexpr size_t capacity = 1;\n  BlockingQueue<int> queue(capacity);\n\n  std::atomic_bool hasReceivedElement = false;\n\n  // fill queue from a different thread\n  std::thread waitUntilHasElements([&queue, &hasReceivedElement]() {\n    queue.Pop();  // This should block until an element has been added\n    hasReceivedElement = true;\n  });\n\n  ASSERT_FALSE(hasReceivedElement);\n  queue.Push(1);\n  waitUntilHasElements.join();\n  ASSERT_TRUE(hasReceivedElement);\n}\n\nTEST(BlockingQueueTest, Queue_BlocksWhileWaitingForSpace) {\n  constexpr size_t capacity = 1;\n  BlockingQueue<int> queue(capacity);\n\n  queue.Push(0);  // fill queue\n\n  std::atomic_bool hasPushedElement = false;\n\n  // push queue from a different thread\n  std::thread waitUntilHasSpace([&queue, &hasPushedElement]() {\n    queue.Push(1);  // This should block until queue is not full\n    hasPushedElement = true;\n  });\n\n  ASSERT_FALSE(hasPushedElement);\n  queue.Pop();\n  waitUntilHasSpace.join();\n  ASSERT_TRUE(hasPushedElement);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/thread/once_task.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_THREAD_ONCE_TASK_H_\n#define CORE_BASE_THREAD_ONCE_TASK_H_\n\n#include <functional>\n#include <future>\n#include <mutex>\n#include <utility>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n\nnamespace lynx {\nnamespace base {\n\n// For now, the OnceTask class is a key class to impl the parallel flush of\n// elements. At present, when exec the parallel flush of elements, each element\n// will post a task into the thread pool. After completion, some reduce tasks\n// will be returned to be executed in the tasm thread. In addition, after all\n// tasks have been thrown into the thread pool, the tasm thread will try to\n// transfer the accumulated tasks to the tasm thread for execution. Since this\n// logic is not a general thread pool logic, we need to encapsulate a OnceTask\n// class to implement this capability. In the future, other parallel scenarios\n// can also use this class to perform similar operations. Based on this class\n// and thread pool, a simple code example is shown in the once_task_unittest.cc\ntemplate <typename T, typename... Args>\nclass OnceTask : public fml::RefCountedThreadSafeStorage {\n public:\n  OnceTask(base::MoveOnlyClosure<void, Args...> task, std::future<T> future)\n      : started_(false), task_(std::move(task)), future_(std::move(future)) {}\n  ~OnceTask() override = default;\n\n  std::future<T>& GetFuture() { return future_; }\n\n  void ReleaseSelf() const override { delete this; };\n\n  // Returning true indicates that run task in the current thread, while\n  // returning false indicates that the task has already been started in another\n  // thread.\n  bool Run(Args... arguments) {\n    bool expected_run = false;\n    if (started_.compare_exchange_strong(expected_run, true)) {\n      task_(std::forward<Args>(arguments)...);\n      return true;\n    }\n    return false;\n  }\n\n private:\n  std::atomic_bool started_;\n  base::MoveOnlyClosure<void, Args...> task_;\n  std::future<T> future_;\n};\n\ntemplate <typename T, typename... Args>\nusing OnceTaskRefptr = fml::RefPtr<OnceTask<T, Args...>>;\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_THREAD_ONCE_TASK_H_\n"
  },
  {
    "path": "core/base/thread/once_task_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/thread/once_task.h\"\n\n#include <functional>\n#include <vector>\n\n#include \"base/include/log/logging.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass OnceTaskTest : public ::testing::Test {\n protected:\n  OnceTaskTest() {}\n  ~OnceTaskTest() {}\n\n  void SetUp() override {}\n\n  void TearDown() override {}\n\n  std::vector<std::function<void()>> GenerateTask(int* parallelArray,\n                                                  int* reduceArray, int index) {\n    *(parallelArray + index) += 1;\n\n    return std::vector<std::function<void()>>{\n        [reduceArray, index]() { *(reduceArray + index) = index; }};\n  }\n};\n\nTEST_F(OnceTaskTest, TestOnceTask0) {\n  using TestOnceTaskRefptr = OnceTaskRefptr<std::vector<std::function<void()>>>;\n\n  int index = 10000;\n  int* parallelArray = new int[index];\n  std::fill(parallelArray, parallelArray + index, 0);\n\n  int* reduceArray = new int[index];\n  std::fill(reduceArray, reduceArray + index, 0);\n\n  std::vector<TestOnceTaskRefptr> task_vec{};\n\n  for (int i = 0; i < index; ++i) {\n    std::promise<std::vector<std::function<void()>>> promise;\n    std::future<std::vector<std::function<void()>>> future =\n        promise.get_future();\n\n    base::closure task_with_promise =\n        base::closure([this, parallelArray, reduceArray, i,\n                       promise = std::move(promise)]() mutable {\n          promise.set_value(GenerateTask(parallelArray, reduceArray, i));\n        });\n\n    TestOnceTaskRefptr task_info_ptr = fml::AdoptRef(\n        new OnceTask(std::move(task_with_promise), std::move(future)));\n\n    base::closure task_with_guard =\n        base::closure([task_info_ptr]() { task_info_ptr->Run(); });\n\n    base::TaskRunnerManufactor::PostTaskToConcurrentLoop(\n        std::move(task_with_guard), base::ConcurrentTaskType::HIGH_PRIORITY);\n    task_vec.emplace_back(std::move(task_info_ptr));\n  }\n\n  for (auto iter = task_vec.rbegin(); iter != task_vec.rend(); ++iter) {\n    if (!(*iter)->Run()) {\n      break;\n    }\n  }\n\n  for (auto& task : task_vec) {\n    if (task->GetFuture().valid()) {\n      std::vector<std::function<void()>> operations = task->GetFuture().get();\n      for (auto it = operations.begin(); it != operations.end(); it++) {\n        (*it)();\n      }\n    }\n  }\n\n  for (int i = 0; i < index; ++i) {\n    EXPECT_EQ(*(parallelArray + i), 1);\n    EXPECT_EQ(*(reduceArray + i), i);\n  }\n\n  delete[] parallelArray;\n  delete[] reduceArray;\n}\n\nTEST_F(OnceTaskTest, TestOnceTask1) {\n  using TestOnceTaskRefptr =\n      OnceTaskRefptr<std::vector<std::function<void()>>, int>;\n\n  int index = 10000;\n  int* parallelArray = new int[index];\n  std::fill(parallelArray, parallelArray + index, 0);\n\n  int* reduceArray = new int[index];\n  std::fill(reduceArray, reduceArray + index, 0);\n\n  std::vector<TestOnceTaskRefptr> task_vec{};\n\n  for (int i = 0; i < index; ++i) {\n    std::promise<std::vector<std::function<void()>>> promise;\n    std::future<std::vector<std::function<void()>>> future =\n        promise.get_future();\n\n    base::MoveOnlyClosure<void, int> task_with_promise(\n        [this, parallelArray, reduceArray,\n         promise = std::move(promise)](int index) mutable {\n          promise.set_value(GenerateTask(parallelArray, reduceArray, index));\n        });\n\n    TestOnceTaskRefptr task_info_ptr = fml::AdoptRef(\n        new OnceTask(std::move(task_with_promise), std::move(future)));\n\n    base::closure task_with_guard = base::closure(\n        [task_info_ptr, i]() mutable { task_info_ptr->Run(std::move(i)); });\n\n    base::TaskRunnerManufactor::PostTaskToConcurrentLoop(\n        std::move(task_with_guard), base::ConcurrentTaskType::HIGH_PRIORITY);\n    task_vec.emplace_back(std::move(task_info_ptr));\n  }\n\n  for (auto& task : task_vec) {\n    if (task->GetFuture().valid()) {\n      std::vector<std::function<void()>> operations = task->GetFuture().get();\n      for (auto it = operations.begin(); it != operations.end(); it++) {\n        (*it)();\n      }\n    }\n  }\n\n  for (int i = 0; i < index; ++i) {\n    EXPECT_EQ(*(parallelArray + i), 1);\n    EXPECT_EQ(*(reduceArray + i), i);\n  }\n\n  delete[] parallelArray;\n  delete[] reduceArray;\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/thread/thread_utils.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/thread/thread_utils.h\"\n\n#if defined(OS_ANDROID)\n#include <sys/prctl.h>\n#include <unistd.h>\n#elif defined(OS_WIN)\n#include <Windows.h>\n#else\n#include <pthread.h>\n#include <unistd.h>\n#endif\n\nnamespace lynx {\nnamespace base {\n\n#if defined(OS_WIN)\n// The GetThreadDescription API was brought in version 1607 of Windows 10.\ntypedef HRESULT(WINAPI* GetThreadDescription)(HANDLE hThread,\n                                              PWSTR* ppszThreadDescription);\n#endif\n\nstd::string GetCurrentThreadName() {\n  char buf[64] = {};\n#if defined(OS_ANDROID)\n  if (prctl(PR_GET_NAME, buf) == 0) {\n    return buf;\n  }\n#elif defined(OS_WIN)\n  static auto get_thread_description_func =\n      reinterpret_cast<GetThreadDescription>(\n          reinterpret_cast<void*>(::GetProcAddress(\n              ::GetModuleHandleA(\"Kernel32.dll\"), \"GetThreadDescription\")));\n  if (get_thread_description_func) {\n    PWSTR data;\n    HRESULT hr = get_thread_description_func(GetCurrentThread(), &data);\n    if (!FAILED(hr)) {\n      wcstombs(buf, data, sizeof(buf));\n      LocalFree(data);\n      return buf;\n    }\n  }\n#else\n  if (pthread_getname_np(pthread_self(), buf, sizeof(buf)) == 0) {\n    return buf;\n  }\n#endif\n  return \"unknow\";  // If thread name is not set, return unknow\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/thread/thread_utils.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_THREAD_THREAD_UTILS_H_\n#define CORE_BASE_THREAD_THREAD_UTILS_H_\n\n#include <string>\n\nnamespace lynx {\nnamespace base {\n\n/**\n * @brief Get the current thread's name.\n *\n * @return std::string The name of the current thread as a C++ string. If the\n * thread name cannot be retrieved, the function returns \"unknown\".\n */\nstd::string GetCurrentThreadName();\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_THREAD_THREAD_UTILS_H_\n"
  },
  {
    "path": "core/base/thread/thread_utils_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/thread/thread_utils.h\"\n\n#include <cstdbool>\n#include <thread>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#if defined(OS_WIN)\n#include <windows.h>\n#else\n#include <pthread.h>\n\n#include <algorithm>\n#endif\n\nnamespace lynx {\nnamespace base {\n\nbool SetThreadName(const std::string& name) {\n  char buf[16] = {};\n  size_t sz = std::min(name.size(), static_cast<size_t>(15));\n  strncpy(buf, name.c_str(), sz);\n\n#if defined(OS_IOS) || defined(OS_OSX)\n  return pthread_setname_np(buf) == 0;\n#elif defined(OS_WIN)\n  wchar_t wstr[128];\n  mbstowcs(wstr, buf, sizeof(buf));\n  HRESULT hr = SetThreadDescription(GetCurrentThread(), wstr);\n  return SUCCEEDED(hr);\n#else\n  return pthread_setname_np(pthread_self(), buf) == 0;\n#endif\n}\n\nTEST(ThreadUtilsTest, GetCurrentThreadName) {\n  std::string threadName = GetCurrentThreadName();\n  // Test case 1: Set the thread name and verify it's retrieved correctly\n  std::string testThreadName = \"MyTestThread\";\n  SetThreadName(testThreadName);\n  EXPECT_EQ(GetCurrentThreadName(), testThreadName);\n\n  // Test case 2: Verify the function returns origin thread name.\n  SetThreadName(threadName);\n  EXPECT_EQ(GetCurrentThreadName(), threadName);\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/js_thread_config_getter.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/threading/js_thread_config_getter.h\"\n\n#include <string>\n\n#include \"base/include/fml/thread.h\"\n\nnamespace lynx {\nnamespace base {\n\nfml::Thread::ThreadConfig GetJSThreadConfig(const std::string& worker_name) {\n  return fml::Thread::ThreadConfig{worker_name,\n                                   fml::Thread::ThreadPriority::HIGH, nullptr};\n}\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/js_thread_config_getter.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_THREADING_JS_THREAD_CONFIG_GETTER_H_\n#define CORE_BASE_THREADING_JS_THREAD_CONFIG_GETTER_H_\n\n#include <string>\n\n#include \"base/include/fml/thread.h\"\n\nnamespace lynx {\nnamespace base {\nfml::Thread::ThreadConfig GetJSThreadConfig(const std::string& worker_name);\n}\n}  // namespace lynx\n\n#endif  // CORE_BASE_THREADING_JS_THREAD_CONFIG_GETTER_H_\n"
  },
  {
    "path": "core/base/threading/task_runner_manufactor.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/threading/task_runner_manufactor.h\"\n\n#include <algorithm>\n#include <condition_variable>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n// TODO(zhengsenyao): move it to tasm source_set\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/fml/concurrent_message_loop.h\"\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/include/no_destructor.h\"\n#include \"core/base/threading/js_thread_config_getter.h\"\n#include \"core/base/threading/vsync_monitor.h\"\n#include \"core/renderer/utils/lynx_env.h\"  // nogncheck\n\n#ifdef OS_ANDROID\n#include \"core/base/android/device_utils_android.h\"\n#endif\n\n#ifdef OS_WIN\n#include \"base/include/fml/platform/win/task_runner_win32.h\"\n#endif\n\nnamespace lynx {\nnamespace base {\n\nnamespace {\n\ninline size_t GetMaxThreadsAllowed() {\n  // Reserve two threads for the UI thread and the JS thread.\n  constexpr size_t kReservedThreadCount = 2;\n  constexpr size_t kMinThreadCount = 2;\n\n  static size_t cpu_cores =\n      static_cast<size_t>(std::thread::hardware_concurrency());\n\n  static size_t remaining_thread_count =\n      cpu_cores >= kReservedThreadCount ? cpu_cores - kReservedThreadCount : 0;\n\n  static size_t max_thread_count =\n      std::max(remaining_thread_count, kMinThreadCount);\n\n  return max_thread_count;\n}\n\ninline bool& HasInit() {\n  static bool has_init = false;\n  return has_init;\n}\n\ninline std::condition_variable& GetUIInitCV() {\n  static base::NoDestructor<std::condition_variable> ui_thread_init_cv_;\n  return *ui_thread_init_cv_;\n}\n\n#if !defined(OS_WIN)\n// fix for unused function error.\ninline std::mutex& GetUIThreadMutex() {\n  static base::NoDestructor<std::mutex> ui_thread_init_mutex;\n  return *ui_thread_init_mutex;\n}\n#endif\n\ninline fml::RefPtr<fml::TaskRunner>& GetUITaskRunner() {\n// win need use fml::TaskRunnerWin32\n#ifdef OS_WIN\n  static base::NoDestructor<fml::RefPtr<fml::TaskRunner>> runner(\n      fml::TaskRunnerWin32::Create());\n#else\n  // other platform, UIThread::Init init ui thread loop and set runner here\n  static base::NoDestructor<fml::RefPtr<fml::TaskRunner>> runner;\n#endif\n  return *runner;\n}\n\nclass ThreadGroup {\n public:\n  fml::RefPtr<fml::TaskRunner> GetTaskRunner(size_t thread_index) {\n    size_t index = thread_index % max_count_;\n    {\n      std::lock_guard<std::mutex> lock(thread_group_mutex_);\n      auto it = thread_groups_.find(index);\n      if (it == thread_groups_.end()) {\n        auto new_thread = std::make_unique<fml::Thread>(\n            GetJSThreadConfig(prefix_name_ + std::to_string(index)));\n        auto task_runner =\n            fml::MakeRefCounted<fml::TaskRunner>(new_thread->GetLoop());\n        thread_groups_.emplace(index, std::move(new_thread));\n        LOGI(\"ThreadGroup for \" << prefix_name_ << \", max_count:\" << max_count_\n                                << \", new thread for index:\" << index);\n        return task_runner;\n      }\n      return fml::MakeRefCounted<fml::TaskRunner>(it->second->GetLoop());\n    }\n  }\n\n private:\n  friend class base::NoDestructor<ThreadGroup>;\n  size_t max_count_;\n  std::string prefix_name_;\n  std::unordered_map<size_t, std::unique_ptr<fml::Thread>> thread_groups_;\n  std::mutex thread_group_mutex_;\n\n  ThreadGroup(const std::string& prefix_name, size_t max_count) {\n    max_count_ = max_count;\n    prefix_name_ = prefix_name;\n  }\n};\n\ninline size_t GetMultiTASMThreadCacheSize() {\n  static size_t max_thread_count = GetMaxThreadsAllowed();\n  static size_t multi_tasm_thread_cache_size =\n      std::min(static_cast<size_t>(tasm::LynxEnv::GetInstance().GetLongEnv(\n                   tasm::LynxEnv::Key::MULTI_TASM_THREAD_SIZE,\n                   static_cast<int>(max_thread_count))),\n               max_thread_count);\n  return multi_tasm_thread_cache_size;\n}\n\ninline size_t GetMultiLayoutThreadCacheSize() {\n  // The Layout process takes less time in most scenarios, so it is not\n  // necessary to set a high number of threads\n  static const size_t kDefaultLayoutThreadCacheMaxSize = 3;\n  static size_t max_thread_count =\n      std::min(kDefaultLayoutThreadCacheMaxSize, GetMaxThreadsAllowed());\n\n  static size_t multi_layout_thread_cache_size =\n      std::min(static_cast<size_t>(tasm::LynxEnv::GetInstance().GetLongEnv(\n                   tasm::LynxEnv::Key::MULTI_LAYOUT_THREAD_SIZE,\n                   static_cast<int>(max_thread_count))),\n               max_thread_count);\n  return multi_layout_thread_cache_size;\n}\n\ninline ThreadGroup& GetJSGroupThreadCache(const std::string& prefix_name,\n                                          size_t max_count) {\n  static base::NoDestructor<ThreadGroup> js_thread_cache{\n      prefix_name,\n      max_count > 0 ? max_count : std::thread::hardware_concurrency()};\n  return *js_thread_cache;\n}\n\ninline ThreadGroup& GetTASMThreadCache(const std::string& prefix_name) {\n  static size_t tasm_thread_max_count = GetMultiTASMThreadCacheSize();\n  static base::NoDestructor<ThreadGroup> tasm_thread_cache{\n      prefix_name, tasm_thread_max_count};\n  return *tasm_thread_cache;\n}\n\ninline ThreadGroup& GetLayoutThreadCache(const std::string& prefix_name) {\n  static size_t layout_thread_max_count = GetMultiLayoutThreadCacheSize();\n  static base::NoDestructor<ThreadGroup> layout_thread_cache{\n      prefix_name, layout_thread_max_count};\n  return *layout_thread_cache;\n}\n\nstatic size_t GetConcurrentLoopHighPriorityWorkerCount() {\n  constexpr size_t min_count = 1;\n  const size_t max_count =\n      std::max(min_count, size_t(std::thread::hardware_concurrency()));\n  size_t count = max_count;\n#ifdef OS_ANDROID\n  size_t percent =\n      std::clamp(tasm::LynxEnv::GetInstance().GetLongEnv(\n                     tasm::LynxEnv::Key::\n                         CONCURRENT_LOOP_HIGH_PRIORITY_WORKER_COUNT_PERCENT,\n                     0),\n                 0L, 100L);\n  if (percent > 0) {\n    count = max_count * percent / 100;\n  } else if (!android::DeviceUtilsAndroid::Is64BitDevice()) {\n    count = max_count / 2;\n  }\n#endif\n  return std::clamp(count, min_count, max_count);\n}\n\n}  // namespace\n\ninline fml::RefPtr<fml::TaskRunner>& GetUIVSyncTaskRunner() {\n  static base::NoDestructor<fml::RefPtr<fml::TaskRunner>> runner;\n  return *runner;\n}\n\nfml::RefPtr<fml::TaskRunner>& UIThread::GetRunner(\n    bool enable_vsync_aligned_msg_loop) {\n#if !defined(OS_WIN)\n  if (!HasInit()) {\n    LOGI(\"Waiting for UIThread to initialize.\");\n    std::unique_lock<std::mutex> local_lock(GetUIThreadMutex());\n    GetUIInitCV().wait(local_lock);\n  }\n#endif\n  return enable_vsync_aligned_msg_loop ? GetUIVSyncTaskRunner()\n                                       : GetUITaskRunner();\n}\n\nvoid UIThread::Init(void* platform_loop) {\n  if (HasInit()) {\n    return;\n  }\n  GetUITaskRunner() =\n      fml::MessageLoop::EnsureInitializedForCurrentThread(platform_loop)\n          .GetTaskRunner();\n\n#if defined(OS_ANDROID) || (OS_IOS)\n  auto vsync_monitor = base::VSyncMonitor::Create();\n  vsync_monitor->BindToCurrentThread();\n  vsync_monitor->Init();\n  GetUITaskRunner()->GetLoop()->SetVSyncRequest(\n      [vsync_monitor](fml::VSyncCallback vsync_callback) {\n        vsync_monitor->RequestVSyncOnUIThread(\n            [vsync_callback = std::move(vsync_callback)](\n                int64_t frame_start_time_ns, int64_t frame_target_time_ns) {\n              vsync_callback(frame_start_time_ns, frame_target_time_ns);\n            });\n      });\n  GetUIVSyncTaskRunner() =\n      fml::MakeRefCounted<fml::TaskRunner>(GetUITaskRunner()->GetLoop(), true);\n#endif\n  HasInit() = true;\n  GetUIInitCV().notify_all();\n}\n\nvoid UIThread::InitTaskRunner(fml::TaskRunnerDelegate* task_runner_delegate) {\n  if (HasInit()) {\n    return;\n  }\n  if (task_runner_delegate) {\n    GetUITaskRunner() = fml::MakeRefCounted<fml::TaskRunner>(nullptr);\n    GetUITaskRunner()->SetDelegate(task_runner_delegate);\n    HasInit() = true;\n    GetUIInitCV().notify_all();\n  } else {\n    Init();\n  }\n}\n\nTaskRunnerManufactor::TaskRunnerManufactor(ThreadStrategyForRendering strategy,\n                                           bool enable_multi_tasm_thread,\n                                           bool enable_multi_layout_thread,\n                                           bool enable_vsync_aligned_msg_loop,\n                                           bool enable_async_thread_cache,\n                                           std::string js_group_thread_name)\n    : thread_strategy_(strategy),\n      enable_multi_tasm_thread_(enable_multi_tasm_thread),\n      js_group_thread_name_(std::move(js_group_thread_name)) {\n  LOGI(\"TaskRunnerManufactor setThreadStrategy:\"\n       << strategy << \", multi_tasm:\" << enable_multi_tasm_thread\n       << \", async_thread_cache:\" << enable_async_thread_cache);\n  static size_t current_label = 0;\n  label_ = ++current_label;\n\n  switch (strategy) {\n    case ALL_ON_UI: {\n      StartUIThread(enable_vsync_aligned_msg_loop);\n      StartJSThread();\n      CreateTASMRunner(ui_task_runner_->GetLoop(),\n                       enable_vsync_aligned_msg_loop);\n      layout_task_runner_ = tasm_task_runner_;\n    } break;\n    case MOST_ON_TASM: {\n      StartUIThread(enable_vsync_aligned_msg_loop);\n      StartJSThread();\n      CreateTASMRunner(StartTASMThread(), enable_vsync_aligned_msg_loop);\n      layout_task_runner_ = tasm_task_runner_;\n    } break;\n    case PART_ON_LAYOUT: {\n      StartUIThread(enable_vsync_aligned_msg_loop);\n      StartJSThread();\n      CreateTASMRunner(ui_task_runner_->GetLoop(),\n                       enable_vsync_aligned_msg_loop);\n      StartLayoutThread(enable_multi_layout_thread);\n    } break;\n    case MULTI_THREADS: {\n      StartUIThread(enable_vsync_aligned_msg_loop);\n      StartJSThread();\n      CreateTASMRunner(StartTASMThread(), enable_vsync_aligned_msg_loop);\n      StartLayoutThread(enable_multi_layout_thread);\n    } break;\n    default:\n      break;\n  }\n}\n\nfml::RefPtr<fml::TaskRunner> TaskRunnerManufactor::GetJSRunner(\n    const std::string& js_group_thread_name) {\n  static constexpr const char* js_thread_name = \"Lynx_JS\";\n  if (js_group_thread_name.empty()) {\n    static base::NoDestructor<fml::Thread> js_thread(\n        GetJSThreadConfig(js_thread_name));\n    return js_thread->GetTaskRunner();\n  } else {\n    unsigned char group_thread_name_last_char =\n        static_cast<unsigned char>(js_group_thread_name.back());\n    static auto js_thread_count = tasm::LynxEnv::GetInstance().GetLongEnv(\n        tasm::LynxEnv::Key::MULTI_JS_THREAD_COUNT,\n        std::thread::hardware_concurrency());\n    return GetJSGroupThreadCache(js_thread_name, js_thread_count)\n        .GetTaskRunner(group_thread_name_last_char);\n  }\n}\n\nfml::RefPtr<fml::TaskRunner> TaskRunnerManufactor::GetTASMTaskRunner() {\n  return tasm_task_runner_;\n}\n\nfml::RefPtr<fml::TaskRunner> TaskRunnerManufactor::GetLayoutTaskRunner() {\n  return layout_task_runner_;\n}\n\nfml::RefPtr<fml::TaskRunner> TaskRunnerManufactor::GetUITaskRunner() {\n  return ui_task_runner_;\n}\n\nfml::RefPtr<fml::TaskRunner> TaskRunnerManufactor::GetJSTaskRunner() {\n  return js_task_runner_;\n}\n\nThreadStrategyForRendering TaskRunnerManufactor::GetManufactorStrategy() {\n  return thread_strategy_;\n}\n\nvoid TaskRunnerManufactor::OnThreadStrategyUpdated(\n    ThreadStrategyForRendering new_strategy) {\n  thread_strategy_ = new_strategy;\n}\n\nvoid TaskRunnerManufactor::StartUIThread(bool enable_vsync_aligned_msg_loop) {\n  ui_task_runner_ = UIThread::GetRunner(enable_vsync_aligned_msg_loop);\n}\n\nvoid TaskRunnerManufactor::CreateTASMRunner(\n    fml::RefPtr<fml::MessageLoopImpl> loop,\n    bool enable_vsync_aligned_msg_loop) {\n#if defined(OS_WIN)\n  tasm_task_runner_ = ui_task_runner_;\n  return;\n#elif defined(OS_LINUX) && !ENABLE_UNITTESTS\n  tasm_task_runner_ = ui_task_runner_;\n  return;\n#elif defined(OS_HARMONY) && defined(ENABLE_HEADLESS)\n  tasm_task_runner_ = ui_task_runner_;\n  return;\n#endif\n\n  tasm_task_runner_ = fml::MakeRefCounted<fml::TaskRunner>(\n      std::move(loop), enable_vsync_aligned_msg_loop);\n}\n\nfml::RefPtr<fml::MessageLoopImpl> TaskRunnerManufactor::StartTASMThread() {\n  static constexpr const char* tasm_thread_name = \"Lynx_TASM\";\n  if (enable_multi_tasm_thread_) {\n    tasm_loop_ =\n        GetTASMThreadCache(tasm_thread_name).GetTaskRunner(label_)->GetLoop();\n  } else {\n    static base::NoDestructor<fml::Thread> tasm_thread(\n        fml::Thread::ThreadConfig(tasm_thread_name,\n                                  fml::Thread::ThreadPriority::HIGH, nullptr));\n    tasm_loop_ = tasm_thread->GetLoop();\n  }\n\n  return tasm_loop_;\n}\n\nfml::RefPtr<fml::MessageLoopImpl> TaskRunnerManufactor::GetTASMLoop() {\n  return tasm_loop_ ? tasm_loop_ : StartTASMThread();\n}\n\nvoid TaskRunnerManufactor::StartLayoutThread(bool enable_multi_layout_thread) {\n  static constexpr const char* layout_thread_name = \"Lynx_Layout\";\n  if (enable_multi_layout_thread) {\n    layout_task_runner_ = fml::MakeRefCounted<fml::TaskRunner>(\n        GetLayoutThreadCache(layout_thread_name)\n            .GetTaskRunner(label_)\n            ->GetLoop());\n  } else {\n    static base::NoDestructor<fml::Thread> layout_thread(\n        fml::Thread::ThreadConfig(layout_thread_name,\n                                  fml::Thread::ThreadPriority::HIGH, nullptr));\n\n    layout_task_runner_ = fml::MakeRefCounted<fml::TaskRunner>(\n        layout_thread->GetTaskRunner()->GetLoop());\n  }\n}\n\nvoid TaskRunnerManufactor::StartJSThread() {\n#if LYNX_ENABLE_FROZEN_MODE\n  js_task_runner_ = ui_task_runner_;\n#else\n  js_task_runner_ = GetJSRunner(js_group_thread_name_);\n#endif\n}\n\nfml::Thread TaskRunnerManufactor::CreateJSWorkerThread(\n    const std::string& worker_name) {\n  std::string thread_name = std::string(\"Lynx_JS_Worker-\") + worker_name;\n  return fml::Thread(thread_name);\n}\n\nvoid TaskRunnerManufactor::PostTaskToConcurrentLoop(base::closure task,\n                                                    ConcurrentTaskType type) {\n  GetConcurrentLoop(type).PostTask(std::move(task));\n}\n\nbool TaskRunnerManufactor::IsOnConcurrentLoopWorker(ConcurrentTaskType type) {\n  return GetConcurrentLoop(type).RunsTasksOnCurrentThreadWorker();\n}\n\nfml::ConcurrentMessageLoop& TaskRunnerManufactor::GetConcurrentLoop(\n    ConcurrentTaskType type) {\n  switch (type) {\n    case ConcurrentTaskType::HIGH_PRIORITY: {\n      static base::NoDestructor<fml::ConcurrentMessageLoop> high_priority_loop(\n          \"LynxHighTask\", fml::Thread::ThreadPriority::HIGH,\n          GetConcurrentLoopHighPriorityWorkerCount());\n      return *high_priority_loop;\n    }\n    case ConcurrentTaskType::NORMAL_PRIORITY: {\n      constexpr size_t normal_worker_count = 1;\n      static base::NoDestructor<fml::ConcurrentMessageLoop>\n          normal_priority_loop(\"LynxNormalTask\",\n                               fml::Thread::ThreadPriority::NORMAL,\n                               normal_worker_count);\n      return *normal_priority_loop;\n    }\n  }\n  LOGF(\"Unknown concurrent task type: \" << static_cast<int>(type));\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/task_runner_manufactor.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_THREADING_TASK_RUNNER_MANUFACTOR_H_\n#define CORE_BASE_THREADING_TASK_RUNNER_MANUFACTOR_H_\n\n#include <memory>\n#include <mutex>\n#include <string>\n\n#include \"base/include/fml/concurrent_message_loop.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/thread.h\"\n#include \"base/include/no_destructor.h\"\n#include \"core/base/lynx_export.h\"\n\nnamespace lynx {\nnamespace base {\n\nenum ThreadStrategyForRendering {\n  ALL_ON_UI = 0,\n  MOST_ON_TASM = 1,\n  PART_ON_LAYOUT = 2,\n  MULTI_THREADS = 3,\n};\n\nenum class ConcurrentTaskType : int32_t {\n  HIGH_PRIORITY = 0,\n  NORMAL_PRIORITY,\n};\n\ninline bool IsEngineAsync(base::ThreadStrategyForRendering strategy) {\n  return strategy == base::ThreadStrategyForRendering::MULTI_THREADS ||\n         strategy == base::ThreadStrategyForRendering::MOST_ON_TASM;\n}\n\ninline ThreadStrategyForRendering ToAsyncEngineStrategy(\n    base::ThreadStrategyForRendering strategy) {\n  if (strategy == ALL_ON_UI) {\n    return MOST_ON_TASM;\n  } else if (strategy == PART_ON_LAYOUT) {\n    return MULTI_THREADS;\n  }\n  return strategy;\n}\n\nclass UIThread {\n public:\n  LYNX_EXPORT_FOR_DEVTOOL static fml::RefPtr<fml::TaskRunner>& GetRunner(\n      bool enable_vsync_aligned_msg_loop = false);\n\n  // ensure call on ui thread.\n  static void Init(void* platform_loop = nullptr);\n  static void InitTaskRunner(fml::TaskRunnerDelegate* task_runner_delegate);\n\n private:\n  UIThread() = delete;\n  ~UIThread() = delete;\n};\n\nclass TaskRunnerManufactor {\n public:\n  // Should be created on UI thread\n  TaskRunnerManufactor(ThreadStrategyForRendering strategy,\n                       bool enable_multi_tasm_thread,\n                       bool enable_multi_layout_thread,\n                       bool enable_vsync_aligned_msg_loop = false,\n                       bool enable_async_thread_cache = false,\n                       std::string js_group_thread_name = \"\");\n\n  virtual ~TaskRunnerManufactor() = default;\n\n  TaskRunnerManufactor(const TaskRunnerManufactor&) = delete;\n  TaskRunnerManufactor& operator=(const TaskRunnerManufactor&) = delete;\n  TaskRunnerManufactor(TaskRunnerManufactor&&) = default;\n  TaskRunnerManufactor& operator=(TaskRunnerManufactor&&) = default;\n\n  LYNX_EXPORT static fml::RefPtr<fml::TaskRunner> GetJSRunner(\n      const std::string& js_group_thread_name);\n\n  LYNX_EXPORT_FOR_DEVTOOL fml::RefPtr<fml::TaskRunner> GetTASMTaskRunner();\n\n  fml::RefPtr<fml::TaskRunner> GetLayoutTaskRunner();\n\n  LYNX_EXPORT_FOR_DEVTOOL fml::RefPtr<fml::TaskRunner> GetUITaskRunner();\n\n  LYNX_EXPORT_FOR_DEVTOOL fml::RefPtr<fml::TaskRunner> GetJSTaskRunner();\n\n  fml::RefPtr<fml::MessageLoopImpl> GetTASMLoop();\n\n  ThreadStrategyForRendering GetManufactorStrategy();\n\n  void OnThreadStrategyUpdated(ThreadStrategyForRendering new_strategy);\n\n  static fml::Thread CreateJSWorkerThread(const std::string& worker_name);\n\n  static void PostTaskToConcurrentLoop(base::closure, ConcurrentTaskType type);\n\n  // Returns true if the calling thread is one of the worker threads owned by\n  // the concurrent message loop of the specified priority type.\n  static bool IsOnConcurrentLoopWorker(ConcurrentTaskType type);\n\n private:\n  void StartUIThread(bool enable_vsync_aligned_msg_loop);\n\n  fml::RefPtr<fml::MessageLoopImpl> StartTASMThread();\n\n  void StartLayoutThread(bool enable_multi_layout_thread);\n\n  void StartJSThread();\n\n  void CreateTASMRunner(fml::RefPtr<fml::MessageLoopImpl> loop,\n                        bool enable_vsync_aligned_msg_loop);\n\n  static fml::ConcurrentMessageLoop& GetConcurrentLoop(ConcurrentTaskType type);\n\n  fml::RefPtr<fml::TaskRunner> tasm_task_runner_;\n  fml::RefPtr<fml::TaskRunner> layout_task_runner_;\n  fml::RefPtr<fml::TaskRunner> ui_task_runner_;\n\n  fml::RefPtr<fml::TaskRunner> js_task_runner_;\n\n  fml::RefPtr<fml::MessageLoopImpl> tasm_loop_;\n\n  // Can only be used when multiple TASM thread is enabled\n  std::unique_ptr<fml::Thread> tasm_thread_;\n  // Can only be used when multiple Layout thread is enabled\n  std::unique_ptr<fml::Thread> layout_thread_;\n\n  ThreadStrategyForRendering thread_strategy_;\n  bool enable_multi_tasm_thread_;\n\n  std::string js_group_thread_name_;\n\n  size_t label_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_THREADING_TASK_RUNNER_MANUFACTOR_H_\n"
  },
  {
    "path": "core/base/threading/task_runner_manufactor_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/base/threading/task_runner_manufactor.h\"\n\n#include \"base/include/fml/synchronization/count_down_latch.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass TaskRunnerManufactorTest : public ::testing::Test {\n protected:\n  TaskRunnerManufactorTest() = default;\n  ~TaskRunnerManufactorTest() override = default;\n\n  void SetUp() override { UIThread::Init(); }\n};\n\nTEST_F(TaskRunnerManufactorTest, AllOnUIThreadMode) {\n  TaskRunnerManufactor ui_mode_manufactor =\n      TaskRunnerManufactor(ALL_ON_UI, false, false);\n  ASSERT_EQ(ui_mode_manufactor.GetTASMTaskRunner()->GetLoop(),\n            ui_mode_manufactor.GetLayoutTaskRunner()->GetLoop());\n}\n\nTEST_F(TaskRunnerManufactorTest, PartOnLayoutMode) {\n  TaskRunnerManufactor part_on_layout_manufactor =\n      TaskRunnerManufactor(PART_ON_LAYOUT, false, false);\n  ASSERT_NE(part_on_layout_manufactor.GetLayoutTaskRunner()->GetLoop(),\n            part_on_layout_manufactor.GetTASMTaskRunner()->GetLoop());\n}\n\nTEST_F(TaskRunnerManufactorTest, MostOnTASMMode) {\n  TaskRunnerManufactor most_on_tasm_mode_manufactor =\n      TaskRunnerManufactor(MOST_ON_TASM, false, false);\n  ASSERT_EQ(most_on_tasm_mode_manufactor.GetLayoutTaskRunner()->GetLoop(),\n            most_on_tasm_mode_manufactor.GetTASMTaskRunner()->GetLoop());\n}\n\nTEST_F(TaskRunnerManufactorTest, DefaultMultiThreadMode) {\n  TaskRunnerManufactor multi_thread_mode_manufactor =\n      TaskRunnerManufactor(MULTI_THREADS, false, false);\n  ASSERT_NE(multi_thread_mode_manufactor.GetLayoutTaskRunner()->GetLoop(),\n            multi_thread_mode_manufactor.GetTASMTaskRunner()->GetLoop());\n}\n\nTEST_F(TaskRunnerManufactorTest, MultiTASMThreadMode) {\n  TaskRunnerManufactor multi_tasm_manufactor_1 =\n      TaskRunnerManufactor(MULTI_THREADS, true, false);\n  TaskRunnerManufactor multi_tasm_manufactor_2 =\n      TaskRunnerManufactor(MULTI_THREADS, true, false);\n  ASSERT_NE(multi_tasm_manufactor_1.GetTASMTaskRunner()->GetLoop(),\n            multi_tasm_manufactor_2.GetTASMTaskRunner()->GetLoop());\n  ASSERT_EQ(multi_tasm_manufactor_1.GetLayoutTaskRunner()->GetLoop(),\n            multi_tasm_manufactor_2.GetLayoutTaskRunner()->GetLoop());\n}\n\nTEST_F(TaskRunnerManufactorTest, MultiLayoutThreadMode) {\n  TaskRunnerManufactor multi_layout_manufactor_1 =\n      TaskRunnerManufactor(MULTI_THREADS, false, true);\n  TaskRunnerManufactor multi_layout_manufactor_2 =\n      TaskRunnerManufactor(MULTI_THREADS, false, true);\n  ASSERT_EQ(multi_layout_manufactor_1.GetTASMTaskRunner()->GetLoop(),\n            multi_layout_manufactor_2.GetTASMTaskRunner()->GetLoop());\n  ASSERT_NE(multi_layout_manufactor_1.GetLayoutTaskRunner()->GetLoop(),\n            multi_layout_manufactor_2.GetLayoutTaskRunner()->GetLoop());\n}\n\nTEST_F(TaskRunnerManufactorTest, MultiLayoutThreadModeAndCache) {\n  TaskRunnerManufactor multi_layout_manufactor_1 =\n      TaskRunnerManufactor(MULTI_THREADS, false, true, false, true);\n  TaskRunnerManufactor multi_layout_manufactor_2 =\n      TaskRunnerManufactor(MULTI_THREADS, false, true, false, true);\n  TaskRunnerManufactor multi_layout_manufactor_3 =\n      TaskRunnerManufactor(MULTI_THREADS, false, true, false, true);\n  TaskRunnerManufactor multi_layout_manufactor_4 =\n      TaskRunnerManufactor(MULTI_THREADS, false, true, false, true);\n  ASSERT_EQ(multi_layout_manufactor_1.GetTASMTaskRunner()->GetLoop(),\n            multi_layout_manufactor_2.GetTASMTaskRunner()->GetLoop());\n  ASSERT_NE(multi_layout_manufactor_1.GetLayoutTaskRunner()->GetLoop(),\n            multi_layout_manufactor_2.GetLayoutTaskRunner()->GetLoop());\n  ASSERT_NE(multi_layout_manufactor_1.GetLayoutTaskRunner()->GetLoop(),\n            multi_layout_manufactor_3.GetLayoutTaskRunner()->GetLoop());\n  ASSERT_EQ(multi_layout_manufactor_1.GetLayoutTaskRunner()->GetLoop(),\n            multi_layout_manufactor_4.GetLayoutTaskRunner()->GetLoop());\n}\n\nTEST_F(TaskRunnerManufactorTest, MultiJSGroupThreadMode) {\n  TaskRunnerManufactor single_js_thread =\n      TaskRunnerManufactor(ALL_ON_UI, false, false, false, false, \"\");\n  TaskRunnerManufactor multi_js_thread_1 =\n      TaskRunnerManufactor(ALL_ON_UI, false, false, false, false, \"Group1\");\n  TaskRunnerManufactor multi_js_thread_2 =\n      TaskRunnerManufactor(ALL_ON_UI, false, false, false, false, \"Group2\");\n  TaskRunnerManufactor multi_js_thread_3 =\n      TaskRunnerManufactor(ALL_ON_UI, false, false, false, false, \"Group3\");\n  TaskRunnerManufactor multi_js_thread_temp_1 =\n      TaskRunnerManufactor(ALL_ON_UI, false, false, false, false, \"Group1\");\n  ASSERT_NE(single_js_thread.GetJSTaskRunner()->GetLoop(),\n            multi_js_thread_1.GetLayoutTaskRunner()->GetLoop());\n  ASSERT_NE(single_js_thread.GetJSTaskRunner()->GetLoop(),\n            multi_js_thread_2.GetLayoutTaskRunner()->GetLoop());\n  ASSERT_NE(single_js_thread.GetJSTaskRunner()->GetLoop(),\n            multi_js_thread_3.GetLayoutTaskRunner()->GetLoop());\n  ASSERT_EQ(single_js_thread.GetJSTaskRunner()->GetLoop(),\n            TaskRunnerManufactor::GetJSRunner(\"\")->GetLoop());\n  ASSERT_EQ(multi_js_thread_1.GetJSTaskRunner()->GetLoop(),\n            TaskRunnerManufactor::GetJSRunner(\"Group1\")->GetLoop());\n  ASSERT_EQ(multi_js_thread_1.GetJSTaskRunner()->GetLoop(),\n            multi_js_thread_temp_1.GetJSTaskRunner()->GetLoop());\n}\n\nTEST_F(TaskRunnerManufactorTest, IsOnConcurrentLoopWorker) {\n  EXPECT_FALSE(TaskRunnerManufactor::IsOnConcurrentLoopWorker(\n      ConcurrentTaskType::HIGH_PRIORITY));\n  EXPECT_FALSE(TaskRunnerManufactor::IsOnConcurrentLoopWorker(\n      ConcurrentTaskType::NORMAL_PRIORITY));\n\n  fml::CountDownLatch high_priority_latch(1);\n  fml::CountDownLatch normal_priority_latch(1);\n\n  TaskRunnerManufactor::PostTaskToConcurrentLoop(\n      [&]() {\n        EXPECT_TRUE(TaskRunnerManufactor::IsOnConcurrentLoopWorker(\n            ConcurrentTaskType::HIGH_PRIORITY));\n        EXPECT_FALSE(TaskRunnerManufactor::IsOnConcurrentLoopWorker(\n            ConcurrentTaskType::NORMAL_PRIORITY));\n        high_priority_latch.CountDown();\n      },\n      ConcurrentTaskType::HIGH_PRIORITY);\n\n  TaskRunnerManufactor::PostTaskToConcurrentLoop(\n      [&]() {\n        EXPECT_TRUE(TaskRunnerManufactor::IsOnConcurrentLoopWorker(\n            ConcurrentTaskType::NORMAL_PRIORITY));\n        EXPECT_FALSE(TaskRunnerManufactor::IsOnConcurrentLoopWorker(\n            ConcurrentTaskType::HIGH_PRIORITY));\n        normal_priority_latch.CountDown();\n      },\n      ConcurrentTaskType::NORMAL_PRIORITY);\n\n  high_priority_latch.Wait();\n  normal_priority_latch.Wait();\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/task_runner_vsync.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/threading/task_runner_vsync.h\"\n\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n\nnamespace lynx {\nnamespace base {\nTaskRunnerVSync::TaskRunnerVSync(fml::RefPtr<fml::MessageLoopImpl> loop)\n    : TaskRunner(loop) {}\n\nbool TaskRunnerVSync::RunsTasksOnCurrentThread() {\n  // The reference of vsync message loop.\n  static auto kUILoopVSync = base::UIThread::GetRunner(true)->GetLoop();\n  // The reference of non-vsync task runner.\n  static auto kUIRunnerNonVSync = base::UIThread::GetRunner(false);\n\n  // Since the bound loop could be change dynamically, we should check if the\n  // current bound loop is vsync message loop. If it is, the non-vsync task\n  // runner should be used to check RunsTasksOnCurrentThread because we didn't\n  // save the vsync message loop to tls. Otherwise, we just call\n  // TaskRunner::RunsTasksOnCurrentThread().\n  return (loop_ == kUILoopVSync) ? kUIRunnerNonVSync->RunsTasksOnCurrentThread()\n                                 : fml::TaskRunner::RunsTasksOnCurrentThread();\n}\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/task_runner_vsync.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_THREADING_TASK_RUNNER_VSYNC_H_\n#define CORE_BASE_THREADING_TASK_RUNNER_VSYNC_H_\n#include \"base/include/fml/task_runner.h\"\n\nnamespace lynx {\nnamespace base {\nclass TaskRunnerVSync : public fml::TaskRunner {\n public:\n  ~TaskRunnerVSync() override = default;\n\n  bool RunsTasksOnCurrentThread() override;\n\n  explicit TaskRunnerVSync(fml::RefPtr<fml::MessageLoopImpl> loop);\n\n private:\n  FML_FRIEND_MAKE_REF_COUNTED(TaskRunnerVSync);\n  FML_FRIEND_REF_COUNTED_THREAD_SAFE(TaskRunnerVSync);\n  BASE_DISALLOW_COPY_AND_ASSIGN(TaskRunnerVSync);\n};\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_THREADING_TASK_RUNNER_VSYNC_H_\n"
  },
  {
    "path": "core/base/threading/task_runner_vsync_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/threading/task_runner_vsync.h\"\n\n#include <memory>\n#include <thread>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/message_loop_impl.h\"\n#include \"base/include/fml/platform/linux/message_loop_linux.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace threading {\n\nclass TaskRunnerVSyncTest : public ::testing::Test {\n protected:\n  TaskRunnerVSyncTest() = default;\n  ~TaskRunnerVSyncTest() override = default;\n\n  static void SetUpTestSuite() { base::UIThread::Init(); }\n};\n\nTEST_F(TaskRunnerVSyncTest, RunsTasksOnCurrentThread) {\n  const auto original_ui_runner = base::UIThread::GetRunner(false);\n  const auto original_vsync_runner = base::UIThread::GetRunner(true);\n\n  const auto ui_thread = std::make_unique<fml::Thread>(\"MOCK_UI_THREAD\");\n  const auto bg_thread = std::make_unique<fml::Thread>(\"BACKGROUND_THREAD\");\n\n  // Although on real enviroment the vsync loop thread is actually the platform\n  // ui thread, the unit test is running on Linux, and it seems that the\n  // MessageLoopLinux does not support multiple loops on one thread like\n  // MessageLoopAndroid and MessageLoopDarwin do. So we mock vsync thread here\n  // to make sure this unit test can run. The focus of this unit test is to\n  // verify whether TaskRunnerVSync work correctly when its bound loop changes.\n  const auto vsync_thread = std::make_unique<fml::Thread>(\"MOCK_VSYNC_THREAD\");\n\n  base::UIThread::GetRunner(false) =\n      fml::MakeRefCounted<fml::TaskRunner>(ui_thread->GetLoop());\n  base::UIThread::GetRunner(true) =\n      fml::MakeRefCounted<base::TaskRunnerVSync>(vsync_thread->GetLoop());\n\n  const auto engine_runner =\n      fml::MakeRefCounted<base::TaskRunnerVSync>(vsync_thread->GetLoop());\n\n  ui_thread->GetTaskRunner()->PostSyncTask([&engine_runner]() {\n    ASSERT_TRUE(engine_runner->RunsTasksOnCurrentThread());\n  });\n\n  bg_thread->GetTaskRunner()->PostSyncTask(\n      [&engine_runner, &bg_loop = bg_thread->GetLoop()]() {\n        ASSERT_FALSE(engine_runner->RunsTasksOnCurrentThread());\n        engine_runner->Bind(bg_loop);\n        ASSERT_TRUE(engine_runner->RunsTasksOnCurrentThread());\n      });\n\n  ui_thread->GetTaskRunner()->PostSyncTask([&engine_runner]() {\n    ASSERT_FALSE(engine_runner->RunsTasksOnCurrentThread());\n  });\n\n  vsync_thread->GetTaskRunner()->PostSyncTask([&engine_runner]() {\n    ASSERT_FALSE(engine_runner->RunsTasksOnCurrentThread());\n  });\n\n  base::UIThread::GetRunner(false) = original_ui_runner;\n  base::UIThread::GetRunner(true) = original_vsync_runner;\n}\n}  // namespace threading\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/thread_merger.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/threading/thread_merger.h\"\n\n#include \"base/include/fml/message_loop_task_queues.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/base/trace/trace_event_def.h\"\n\nnamespace lynx {\nnamespace base {\n\nvoid ThreadMerger::Merge(fml::TaskRunner* owner, fml::TaskRunner* subsumed) {\n  DCHECK(owner);\n  DCHECK(subsumed);\n\n  if (owner == subsumed) {\n    return;\n  }\n\n  // ensure on owner's thread.\n  DCHECK(owner->RunsTasksOnCurrentThread());\n\n  if (subsumed->RunsTasksOnCurrentThread()) {\n    return;\n  }\n\n  fml::AutoResetWaitableEvent arwe;\n  subsumed->PostEmergencyTask([owner_id = owner->GetTaskQueueId(),\n                               subsumed_id = subsumed->GetTaskQueueId(),\n                               &arwe]() {\n    fml::MessageLoopTaskQueues::GetInstance()->Merge(owner_id, subsumed_id);\n    arwe.Signal();\n  });\n  arwe.Wait();\n}\n\nThreadMerger::ThreadMerger(fml::TaskRunner* owner, fml::TaskRunner* subsumed)\n    : owner_(owner), subsumed_(subsumed) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, THREAD_MERGER_CONSTRUCTOR);\n\n  Merge(owner_, subsumed_);\n}\n\nThreadMerger::~ThreadMerger() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, THREAD_MERGER_DECONSTRUCTOR);\n  if (owner_ == subsumed_) {\n    return;\n  }\n\n  // ensure on owner's thread.\n  DCHECK(owner_->RunsTasksOnCurrentThread());\n\n  fml::MessageLoopTaskQueues::GetInstance()->Unmerge(\n      owner_->GetTaskQueueId(), subsumed_->GetTaskQueueId());\n}\n\nThreadMerger::ThreadMerger(ThreadMerger&& other)\n    : owner_(other.owner_), subsumed_(other.subsumed_) {\n  other.owner_ = nullptr;\n  other.subsumed_ = nullptr;\n}\n\nThreadMerger& ThreadMerger::operator=(ThreadMerger&& other) {\n  owner_ = other.owner_;\n  subsumed_ = other.subsumed_;\n  other.owner_ = nullptr;\n  other.subsumed_ = nullptr;\n  return *this;\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/thread_merger.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_THREADING_THREAD_MERGER_H_\n#define CORE_BASE_THREADING_THREAD_MERGER_H_\n\n#include \"base/include/fml/task_runner.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass ThreadMerger {\n public:\n  ThreadMerger(fml::TaskRunner* owner, fml::TaskRunner* subsumed);\n  ~ThreadMerger();\n\n  static void Merge(fml::TaskRunner* owner, fml::TaskRunner* subsumed);\n  ThreadMerger(const ThreadMerger&) = delete;\n  ThreadMerger& operator=(const ThreadMerger&) = delete;\n  ThreadMerger(ThreadMerger&&);\n  ThreadMerger& operator=(ThreadMerger&&);\n\n private:\n  fml::TaskRunner* owner_;\n\n  fml::TaskRunner* subsumed_;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_THREADING_THREAD_MERGER_H_\n"
  },
  {
    "path": "core/base/threading/thread_merger_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/threading/thread_merger.h\"\n\n#include <memory>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/fml/synchronization/waitable_event.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"base/include/fml/thread.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass ThreadMergerTest : public ::testing::Test {\n protected:\n  ThreadMergerTest() = default;\n  ~ThreadMergerTest() override = default;\n\n  void SetUp() override { UIThread::Init(); }\n};\n\nTEST_F(ThreadMergerTest, SameRunner) {\n  TaskRunnerManufactor manufactor(ThreadStrategyForRendering::MULTI_THREADS,\n                                  true, true);\n  auto* runner = manufactor.GetTASMTaskRunner().get();\n  fml::AutoResetWaitableEvent arwe;\n\n  fml::MessageLoop* looper = nullptr;\n  runner->PostTask([&arwe, &looper]() {\n    looper = &(fml::MessageLoop::GetCurrent());\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  runner->PostTask([&arwe, runner, looper]() {\n    auto merger = std::make_unique<ThreadMerger>(runner, runner);\n    ASSERT_EQ(looper, &(fml::MessageLoop::GetCurrent()));\n\n    merger = nullptr;\n    ASSERT_EQ(looper, &(fml::MessageLoop::GetCurrent()));\n\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n}\n\nTEST_F(ThreadMergerTest, DifferentRunners) {\n  TaskRunnerManufactor manufactor(ThreadStrategyForRendering::MULTI_THREADS,\n                                  true, true);\n  auto* owner_runner = manufactor.GetTASMTaskRunner().get();\n  auto* subsumed_runner = manufactor.GetLayoutTaskRunner().get();\n\n  fml::AutoResetWaitableEvent arwe;\n\n  fml::MessageLoop* owner_looper = nullptr;\n  fml::MessageLoop* subsumed_looper = nullptr;\n\n  owner_runner->PostTask([&arwe, &owner_looper]() {\n    owner_looper = &(fml::MessageLoop::GetCurrent());\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  subsumed_runner->PostTask([&arwe, &subsumed_looper]() {\n    subsumed_looper = &(fml::MessageLoop::GetCurrent());\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  ThreadMerger* merger = nullptr;\n  owner_runner->PostTask([&merger, &arwe, owner_runner, subsumed_runner]() {\n    merger = new ThreadMerger(owner_runner, subsumed_runner);\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  subsumed_runner->PostTask([&arwe, owner_looper, subsumed_looper]() {\n    ASSERT_EQ(owner_looper, &(fml::MessageLoop::GetCurrent()));\n    ASSERT_NE(subsumed_looper, &(fml::MessageLoop::GetCurrent()));\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  owner_runner->PostTask([&merger, &arwe]() {\n    delete merger;\n    merger = nullptr;\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  subsumed_runner->PostTask([&arwe, owner_looper, subsumed_looper]() {\n    ASSERT_NE(owner_looper, &(fml::MessageLoop::GetCurrent()));\n    ASSERT_EQ(subsumed_looper, &(fml::MessageLoop::GetCurrent()));\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n}\n\nTEST_F(ThreadMergerTest, RvalueMove) {\n  TaskRunnerManufactor manufactor(ThreadStrategyForRendering::MULTI_THREADS,\n                                  true, true);\n  auto* owner_runner = manufactor.GetTASMTaskRunner().get();\n  auto* subsumed_runner = manufactor.GetLayoutTaskRunner().get();\n\n  fml::AutoResetWaitableEvent arwe;\n\n  fml::MessageLoop* owner_looper = nullptr;\n  fml::MessageLoop* subsumed_looper = nullptr;\n\n  owner_runner->PostTask([&arwe, &owner_looper]() {\n    owner_looper = &(fml::MessageLoop::GetCurrent());\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  subsumed_runner->PostTask([&arwe, &subsumed_looper]() {\n    subsumed_looper = &(fml::MessageLoop::GetCurrent());\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  ThreadMerger* merger = nullptr;\n  owner_runner->PostTask([&merger, &arwe, owner_runner, subsumed_runner]() {\n    merger = new ThreadMerger(owner_runner, subsumed_runner);\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  subsumed_runner->PostTask([&arwe, owner_looper, subsumed_looper]() {\n    ASSERT_EQ(owner_looper, &(fml::MessageLoop::GetCurrent()));\n    ASSERT_NE(subsumed_looper, &(fml::MessageLoop::GetCurrent()));\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  owner_runner->PostTask([&merger, &arwe]() {\n    ThreadMerger moved_merger(std::move(*merger));\n    *merger = std::move(moved_merger);\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  owner_runner->PostTask([&merger, &arwe]() {\n    delete merger;\n    merger = nullptr;\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n  arwe.Reset();\n\n  subsumed_runner->PostTask([&arwe, owner_looper, subsumed_looper]() {\n    ASSERT_NE(owner_looper, &(fml::MessageLoop::GetCurrent()));\n    ASSERT_EQ(subsumed_looper, &(fml::MessageLoop::GetCurrent()));\n    arwe.Signal();\n  });\n\n  arwe.Wait();\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/vsync_monitor.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/threading/vsync_monitor.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/message_loop.h\"\n#include \"base/include/log/logging.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n\nnamespace lynx {\nnamespace base {\n\nVSyncMonitor::VSyncMonitor(bool is_on_ui_thread,\n                           bool is_vsync_post_task_by_emergency)\n    : is_vsync_post_task_by_emergency_(is_vsync_post_task_by_emergency),\n      is_on_ui_thread_(is_on_ui_thread),\n      platform_impl_(nullptr) {\n  LOGI(\"VSyncMonitor created with is_vsync_post_task_by_emergency_ \"\n       << is_vsync_post_task_by_emergency_);\n}\n\nVSyncMonitor::VSyncMonitor(\n    const std::shared_ptr<VSyncMonitorPlatformImpl> &platform_impl,\n    bool is_on_ui_thread, bool is_vsync_post_task_by_emergency)\n    : is_vsync_post_task_by_emergency_(is_vsync_post_task_by_emergency),\n      is_on_ui_thread_(is_on_ui_thread),\n      platform_impl_(platform_impl) {\n  LOGI(\"VSyncMonitor created with is_vsync_post_task_by_emergency_ \"\n       << is_vsync_post_task_by_emergency_);\n}\n\nvoid VSyncMonitor::AsyncRequestVSync(Callback callback) {\n  // take care: do not call AsyncRequestVSync in multiple threads, or add a\n  // mutex to protect\n  if (callback_) {\n    // request during a frame interval, just return\n    return;\n  }\n\n  DCHECK(runner_->RunsTasksOnCurrentThread());\n\n  callback_ = std::move(callback);\n\n  if (is_on_ui_thread_) {\n    RequestVSyncOnUIThread();\n  } else {\n    RequestVSync();\n  }\n}\n\nvoid VSyncMonitor::ScheduleVSyncSecondaryCallback(uintptr_t id,\n                                                  Callback callback) {\n  if (!callback) {\n    return;\n  }\n\n  DCHECK(runner_->RunsTasksOnCurrentThread());\n\n  // take care: do not call AsyncRequestVSync in multiple threads\n  auto &&[iter, inserted] =\n      secondary_callbacks_.emplace(id, std::move(callback));\n  if (!inserted) {\n    // the same callback already post, ignore\n    return;\n  }\n\n  if (!requested_) {\n    if (is_on_ui_thread_) {\n      RequestVSyncOnUIThread();\n    } else {\n      RequestVSync();\n    }\n    requested_ = true;\n  }\n}\n\nvoid VSyncMonitor::StopVSync() { stop_vsync_ = true; }\n\nvoid VSyncMonitor::OnVSync(int64_t frame_start_time,\n                           int64_t frame_target_time) {\n  if (stop_vsync_) {\n    callback_ = nullptr;\n    secondary_callbacks_.clear();\n    return;\n  }\n\n  if (runner_->RunsTasksOnCurrentThread()) {\n    OnVSyncInternal(frame_start_time, frame_target_time);\n    return;\n  }\n\n  auto task = [weak_self = weak_from_this(), frame_start_time,\n               frame_target_time]() {\n    auto self = weak_self.lock();\n    if (self != nullptr) {\n      self->OnVSyncInternal(frame_start_time, frame_target_time);\n    }\n  };\n  if (is_vsync_post_task_by_emergency_) {\n    runner_->PostEmergencyTask(std::move(task));\n  } else {\n    runner_->PostTask(std::move(task));\n  }\n}\n\nvoid VSyncMonitor::OnVSyncInternal(int64_t frame_start_time,\n                                   int64_t frame_target_time) {\n  requested_ = false;\n  // if necessary, add callback mutex\n  if (callback_) {\n    Callback callback = std::move(callback_);\n    callback(frame_start_time, frame_target_time);\n  }\n\n  if (!secondary_callbacks_.empty()) {\n    std::vector<Callback> callback_vec;\n    for (auto &&[id, callback] : secondary_callbacks_) {\n      callback_vec.push_back(std::move(callback));\n    }\n    secondary_callbacks_.clear();\n\n    for (auto &cb : callback_vec) {\n      cb(frame_start_time, frame_target_time);\n    }\n  }\n}\n\nvoid VSyncMonitor::BindTaskRunner(const fml::RefPtr<fml::TaskRunner> &runner) {\n  runner_ = std::move(runner);\n}\n\nvoid VSyncMonitor::BindToCurrentThread() {\n  if (runner_) {\n    return;\n  }\n  // TODO(qiuxian): The macro will be removed after refactoring VSyncMonitor.\n  // While creating MessageLoopAndroidVSync in UIThread::Init() on Android\n  // platform, calling UIThread::GetRunner here can block current thread.\n  // This piece of code fixes crash on Window platform, it's safe to delete\n  // it from other platforms.\n#if defined(OS_WIN) || defined(ENABLE_HEADLESS) || defined(OS_LINUX)\n  auto ui_runner = base::UIThread::GetRunner();\n  if (ui_runner->RunsTasksOnCurrentThread()) {\n    runner_ = ui_runner;\n    return;\n  }\n#endif\n\n  runner_ = fml::MessageLoop::GetCurrent().GetTaskRunner();\n}\n\nvoid VSyncMonitor::RequestVSync() {\n  if (!platform_impl_) {\n    LOGE(\"VSyncMonitorPlatformImpl is null, skip RequestVSync\");\n    return;\n  }\n  platform_impl_->RequestVSync(\n      [weak_self = weak_from_this()](int64_t frame_start_time,\n                                     int64_t frame_target_time) {\n        auto self = weak_self.lock();\n        if (self != nullptr) {\n          self->OnVSync(frame_start_time, frame_target_time);\n        }\n      });\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/vsync_monitor.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_THREADING_VSYNC_MONITOR_H_\n#define CORE_BASE_THREADING_VSYNC_MONITOR_H_\n\n#include <functional>\n#include <memory>\n#include <unordered_map>\n\n#include \"base/include/closure.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"core/public/vsync_monitor_platform_impl.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass VSyncMonitor : public std::enable_shared_from_this<VSyncMonitor> {\n public:\n  using Callback = base::MoveOnlyClosure<void, int64_t, int64_t>;\n\n  VSyncMonitor(bool is_on_ui_thread = false,\n               bool is_vsync_post_task_by_emergency = true);\n  explicit VSyncMonitor(\n      const std::shared_ptr<VSyncMonitorPlatformImpl> &platform_impl,\n      bool is_on_ui_thread = false,\n      bool is_vsync_post_task_by_emergency = true);\n  virtual ~VSyncMonitor() = default;\n\n  static std::shared_ptr<VSyncMonitor> Create(bool is_on_ui_thread = false);\n\n  virtual void Init() {}\n\n  virtual void SetHighRefreshRate() {}\n\n  // TODO(heshan):invoke this method in Init.\n  // after initialization, VSyncMonitor needs to bind\n  // to MessageLoop of current thread.\n  void BindToCurrentThread();\n\n  // the callback only be set once on one frame\n  void AsyncRequestVSync(Callback callback);\n\n  // the callback is unique per id\n  void ScheduleVSyncSecondaryCallback(uintptr_t id, Callback callback);\n\n  // frame_start_time/frame_target_time is in nanoseconds\n  void OnVSync(int64_t frame_start_time, int64_t frame_target_time);\n\n  void BindTaskRunner(const fml::RefPtr<fml::TaskRunner> &runner);\n\n  void StopVSync();\n\n  virtual void RequestVSyncOnUIThread(Callback callback){};\n\n  virtual void RequestVSyncOnUIThread(){};\n\n protected:\n  virtual void RequestVSync();\n\n  Callback callback_;\n\n  fml::RefPtr<fml::TaskRunner> runner_;\n\n private:\n  void OnVSyncInternal(int64_t frame_start_time, int64_t frame_target_time);\n\n  bool is_vsync_post_task_by_emergency_{false};\n  bool is_on_ui_thread_{false};\n  bool requested_{false};\n  bool stop_vsync_{false};\n  // additional callbacks required to invoke when VSync is requested\n  std::unordered_map<uintptr_t, Callback> secondary_callbacks_;\n  std::shared_ptr<VSyncMonitorPlatformImpl> platform_impl_;\n\n  // disallow copy&assign\n  VSyncMonitor(const VSyncMonitor &) = delete;\n  VSyncMonitor &operator==(const VSyncMonitor &) = delete;\n};\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_THREADING_VSYNC_MONITOR_H_\n"
  },
  {
    "path": "core/base/threading/vsync_monitor_default.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"core/base/threading/vsync_monitor.h\"\n\nnamespace lynx {\nnamespace base {\n\nstd::shared_ptr<VSyncMonitor> VSyncMonitor::Create(bool is_on_ui_thread) {\n  return nullptr;\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/threading/vsync_monitor_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/threading/vsync_monitor.h\"\n\n#include <algorithm>\n\n#include \"base/include/fml/thread.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace base {\nnamespace testing {\n\nnamespace {\n\nconstexpr int64_t kFrameDuration = 16;  // ms\n\nconstexpr int64_t kLoop = 10;\n\n}  // namespace\n\nclass TestVSyncMonitor : public VSyncMonitor {\n public:\n  TestVSyncMonitor() = default;\n  ~TestVSyncMonitor() override = default;\n\n  void RequestVSync() override {}\n\n  void TriggerVsync() {\n    OnVSync(current_, current_ + kFrameDuration);\n    current_ += kFrameDuration;\n  }\n\n private:\n  int64_t current_ = kFrameDuration;\n};\n\nclass VsyncMonitorTest : public ::testing::Test {\n protected:\n  VsyncMonitorTest() = default;\n  ~VsyncMonitorTest() override = default;\n\n  static void SetUpTestSuite() { base::UIThread::Init(); }\n};\n\nTEST_F(VsyncMonitorTest, AsyncRequestVSync) {\n  auto monitor = std::make_shared<TestVSyncMonitor>();\n  monitor->BindToCurrentThread();\n\n  int64_t start_time = 0;\n  int64_t target_time = 0;\n\n  for (int64_t i = 1; i <= kLoop; ++i) {\n    monitor->AsyncRequestVSync(\n        [&start_time, &target_time](int64_t frame_start_time,\n                                    int64_t frame_target_time) {\n          start_time = frame_start_time;\n          target_time = frame_target_time;\n        });\n    monitor->TriggerVsync();\n    ASSERT_EQ(start_time, i * kFrameDuration);\n    ASSERT_EQ(target_time, (i + 1) * kFrameDuration);\n  }\n}\n\nTEST_F(VsyncMonitorTest, AsyncRequestVSyncDuplicately) {\n  auto monitor = std::make_shared<TestVSyncMonitor>();\n  monitor->BindToCurrentThread();\n\n  int64_t start_time = 0;\n  int64_t target_time = 0;\n\n  monitor->AsyncRequestVSync(\n      [&start_time, &target_time](int64_t frame_start_time,\n                                  int64_t frame_target_time) {\n        start_time = frame_start_time;\n        target_time = frame_target_time;\n      });\n\n  // in one vsync duration, AsyncRequestVSync Duplicately will not be dropped\n  monitor->AsyncRequestVSync(\n      [&start_time, &target_time](int64_t frame_start_time,\n                                  int64_t frame_target_time) {\n        start_time = 0;\n        target_time = 0;\n      });\n\n  monitor->TriggerVsync();\n  ASSERT_EQ(start_time, kFrameDuration);\n  ASSERT_EQ(target_time, start_time + kFrameDuration);\n}\n\nTEST_F(VsyncMonitorTest, AsyncRequestVSyncWithId) {\n  auto monitor = std::make_shared<TestVSyncMonitor>();\n  monitor->BindToCurrentThread();\n\n  std::vector<int64_t> ids;\n  int64_t start_time = 0;\n  int64_t target_time = 0;\n\n  for (int64_t i = 0; i < kLoop; ++i) {\n    monitor->ScheduleVSyncSecondaryCallback(\n        static_cast<uintptr_t>(i),\n        [&start_time, &target_time, &ids, i](int64_t frame_start_time,\n                                             int64_t frame_target_time) {\n          start_time = frame_start_time;\n          target_time = frame_target_time;\n          ids.emplace_back(i);\n        });\n  }\n\n  monitor->TriggerVsync();\n  ASSERT_EQ(start_time, kFrameDuration);\n  ASSERT_EQ(target_time, start_time + kFrameDuration);\n\n  // the callbacks called as the order in a std::unordered_map,\n  // not the ascending order.\n  std::sort(ids.begin(), ids.end());\n  for (int64_t i = 0; i < kLoop; ++i) {\n    ASSERT_EQ(ids[static_cast<size_t>(i)], i);\n  }\n}\n\nTEST_F(VsyncMonitorTest, AsyncRequestVSyncWithDuplicateId) {\n  auto monitor = std::make_shared<TestVSyncMonitor>();\n  monitor->BindToCurrentThread();\n\n  int64_t start_time = 0;\n  int64_t target_time = 0;\n\n  monitor->ScheduleVSyncSecondaryCallback(\n      1, [&start_time, &target_time](int64_t frame_start_time,\n                                     int64_t frame_target_time) {\n        start_time = frame_start_time;\n        target_time = frame_target_time;\n      });\n\n  // in one vsync duration, AsyncRequestVSync with duplicate id will not be\n  // dropped\n  monitor->ScheduleVSyncSecondaryCallback(\n      1, [&start_time, &target_time](int64_t frame_start_time,\n                                     int64_t frame_target_time) {\n        start_time = 0;\n        target_time = 0;\n      });\n\n  monitor->TriggerVsync();\n  ASSERT_EQ(start_time, kFrameDuration);\n  ASSERT_EQ(target_time, start_time + kFrameDuration);\n}\n\nTEST_F(VsyncMonitorTest, AsyncRequestVSyncWithNullCallback) {\n  auto monitor = std::make_shared<TestVSyncMonitor>();\n  monitor->BindToCurrentThread();\n\n  int64_t start_time = 0;\n  int64_t target_time = 0;\n\n  // the null callback will not be dropped\n  monitor->ScheduleVSyncSecondaryCallback(1, nullptr);\n\n  monitor->ScheduleVSyncSecondaryCallback(\n      1, [&start_time, &target_time](int64_t frame_start_time,\n                                     int64_t frame_target_time) {\n        start_time = frame_start_time;\n        target_time = frame_target_time;\n      });\n\n  monitor->TriggerVsync();\n  ASSERT_EQ(start_time, kFrameDuration);\n  ASSERT_EQ(target_time, start_time + kFrameDuration);\n}\n\nTEST_F(VsyncMonitorTest, OnVsyncOnMergedThread) {\n  auto monitor = std::make_shared<TestVSyncMonitor>();\n  int64_t start_time = 0;\n  int64_t target_time = 0;\n\n  fml::Thread owner_thread;\n  fml::Thread subsumed_thread;\n  auto owner = owner_thread.GetTaskRunner();\n  auto subsumed = subsumed_thread.GetTaskRunner();\n\n  std::condition_variable cv_;\n  std::mutex mutex_;\n  bool has_trigger_finished{false};\n\n  subsumed->PostSyncTask([&monitor, &owner, &cv_, &mutex_,\n                          &has_trigger_finished, &start_time, &target_time,\n                          owner_id = owner->GetTaskQueueId(),\n                          subsumed_id = subsumed->GetTaskQueueId()]() {\n    monitor->BindToCurrentThread();\n    monitor->AsyncRequestVSync(\n        [&start_time, &target_time](int64_t frame_start_time,\n                                    int64_t frame_target_time) {\n          start_time = frame_start_time;\n          target_time = frame_target_time;\n        });\n    monitor->TriggerVsync();\n\n    // OnVsync on current thread\n    ASSERT_EQ(start_time, kFrameDuration);\n    ASSERT_EQ(target_time, start_time + kFrameDuration);\n\n    start_time = 0;\n    target_time = 0;\n    monitor->AsyncRequestVSync(\n        [&start_time, &target_time](int64_t frame_start_time,\n                                    int64_t frame_target_time) {\n          start_time = frame_start_time;\n          target_time = frame_target_time;\n        });\n\n    fml::MessageLoopTaskQueues::GetInstance()->Merge(owner_id, subsumed_id);\n    // Sometimes the owner thread's task execution is even earlier than the task\n    // directly scheduled in the subsumed(on current thread). This should be\n    // related to the allocation of system thread resources. so we can post a\n    // task to wait trigger call, to avoid this situation from happening.\n    owner->PostTask([&has_trigger_finished, &cv_, &mutex_]() {\n      std::unique_lock<std::mutex> locker(mutex_);\n      if (has_trigger_finished) {\n        return;\n      }\n      cv_.wait(locker);\n    });\n    monitor->TriggerVsync();\n\n    // OnVsync not on current thread\n    ASSERT_EQ(start_time, 0);\n    ASSERT_EQ(target_time, 0);\n    {\n      std::unique_lock<std::mutex> locker(mutex_);\n      has_trigger_finished = true;\n    }\n    cv_.notify_all();\n  });\n\n  owner->PostSyncTask([owner_id = owner->GetTaskQueueId(),\n                       subsumed_id = subsumed->GetTaskQueueId()]() {\n    fml::MessageLoopTaskQueues::GetInstance()->Unmerge(owner_id, subsumed_id);\n  });\n\n  ASSERT_EQ(start_time, kFrameDuration + kFrameDuration);\n  ASSERT_EQ(target_time, start_time + kFrameDuration);\n}\n\n}  // namespace testing\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/trace/trace_event_def.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_TRACE_TRACE_EVENT_DEF_H_\n#define CORE_BASE_TRACE_TRACE_EVENT_DEF_H_\n\n#include \"core/base/lynx_trace_categories.h\"\n\n#if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n\nstatic constexpr const char* const THREAD_MERGER_CONSTRUCTOR =\n    \"ThreadMerger::ThreadMerger\";\nstatic constexpr const char* const THREAD_MERGER_DECONSTRUCTOR =\n    \"ThreadMerger::~ThreadMerger\";\nstatic constexpr const char* const MESSAGE_LOOP_ANDROID_VASYNC_FLUSH_TASKS =\n    \"MessageLoopAndroidVSync::FlushTasks\";\nstatic constexpr const char* const VSYNC_MONITOR_DARWIN_ON_MAIN_DISPLAY =\n    \"LynxVSyncPulse::onMainDisplay\";\nstatic constexpr const char* const JSI_OBJECT_GET =\n    \"LynxPlatformJSIObjectAndroid::get\";\nstatic constexpr const char* const JSI_OBJECT_GET_DESCRIPTOR =\n    \"LynxPlatformJSIObjectAndroid::GetJSIObjectDescriptor\";\nstatic constexpr const char* const LYNX_ENV_GET_BOOL_ENV = \"GetBoolEnv\";\nstatic constexpr const char* const LYNX_ENV_GET_LONG_ENV = \"GetLongEnv\";\nstatic constexpr const char* const LYNX_ENV_GET_STRING_ENV = \"GetStringEnv\";\nstatic constexpr const char* const LYNX_ENV_GET_EXTERNAL_ENV = \"GetExternalEnv\";\n\n#endif  // #if ENABLE_TRACE_PERFETTO || ENABLE_TRACE_SYSTRACE\n\n#endif  // CORE_BASE_TRACE_TRACE_EVENT_DEF_H_\n"
  },
  {
    "path": "core/base/utils/any.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_UTILS_ANY_H_\n#define CORE_BASE_UTILS_ANY_H_\n\n#include <utility>\n\nnamespace lynx {\nnamespace base {\n\nclass BaseErasure {\n public:\n  BaseErasure() = default;\n  BaseErasure(const BaseErasure&) = delete;\n  BaseErasure& operator=(const BaseErasure&) = delete;\n  virtual ~BaseErasure() {}\n\n public:\n  virtual BaseErasure* clone() = 0;\n};\n\ntemplate <typename T>\nclass ValueHolder : public BaseErasure {\n public:\n  ValueHolder(T t) : value_(t) {}\n  ValueHolder* clone() override { return new ValueHolder<T>(value_); }\n\n public:\n  T value_;\n};\n\nclass any {\n public:\n  any() : data_ptr_(nullptr) {}\n  ~any() { delete data_ptr_; }\n\n  template <typename T>\n  any(T t) : data_ptr_(new ValueHolder<T>(t)) {}\n\n  any(const any& a) : data_ptr_(a.data_ptr_->clone()) {}\n\n  any(any&& a) noexcept : data_ptr_(a.data_ptr_) { a.data_ptr_ = nullptr; }\n\n  any& operator=(any a) {\n    std::swap(data_ptr_, a.data_ptr_);\n    return *this;\n  }\n\n  template <class T>\n  friend T any_cast(const any& a);\n\n private:\n  BaseErasure* data_ptr_;\n};\n\ntemplate <class T>\nT any_cast(const any& a) {\n  return static_cast<ValueHolder<T>*>(a.data_ptr_)->value_;\n}\n\n}  // namespace base\n}  // namespace lynx\n\n#endif  // CORE_BASE_UTILS_ANY_H_\n"
  },
  {
    "path": "core/base/utils/paths_mac.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_UTILS_PATHS_MAC_H_\n#define CORE_BASE_UTILS_PATHS_MAC_H_\n#include <string>\n#include <utility>\n\nnamespace lynx {\nnamespace common {\nstd::pair<bool, std::string> GetResourceDirectoryPath();\n}  // namespace common\n}  // namespace lynx\n#endif  // CORE_BASE_UTILS_PATHS_MAC_H_\n"
  },
  {
    "path": "core/base/utils/paths_mac.mm",
    "content": "\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/utils/paths_mac.h\"\n\n#import <Foundation/Foundation.h>\n\nnamespace lynx {\nnamespace common {\n\nstd::pair<bool, std::string> GetResourceDirectoryPath() {\n  const char *exe_path =\n      [[[[NSBundle mainBundle] resourcePath] stringByAppendingString:@\"/Resources\"] UTF8String];\n  return {true, std::string(exe_path)};\n}\n\n}  // namespace common\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/utils/paths_win.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/base/utils/paths_win.h\"\n\n#include <windows.h>\n\n#include <chrono>\n#include <sstream>\n\n#include \"base/include/string/string_conversion_win.h\"\n\nextern \"C\" IMAGE_DOS_HEADER __ImageBase;\n\n// Returns the HMODULE of the dll the macro was expanded in.\n// Only use in cc files, not in h files.\n#define CURRENT_MODULE() reinterpret_cast<HMODULE>(&__ImageBase)\n\nnamespace lynx {\nnamespace base {\n\nnamespace {\nstruct FileInfo {\n  // The size of the file in bytes.  Undefined when is_directory is true.\n  int64_t size = 0;\n  // True if the file corresponds to a directory.\n  bool is_directory = false;\n  // True if the file corresponds to a symbolic link.  For Windows currently\n  // not supported and thus always false.\n  bool is_symbolic_link = false;\n  // The last modified time of a file.\n  std::chrono::system_clock::time_point last_modified;\n  // The last accessed time of a file.\n  std::chrono::system_clock::time_point last_accessed;\n  // The creation time of a file.\n  std::chrono::system_clock::time_point creation_time;\n};\n}  // namespace\n\nsize_t RootLength(const std::string& path) {\n  if (path.empty()) return 0;\n  if (path[0] == '/') return 1;\n  if (path[0] == '\\\\') {\n    if (path.size() < 2 || path[1] != '\\\\') return 1;\n    // The path is a network share. Search for up to two '\\'s, as they are\n    // the server and share - and part of the root part.\n    size_t index = path.find('\\\\', 2);\n    if (index > 0) {\n      index = path.find('\\\\', index + 1);\n      if (index > 0) return index;\n    }\n    return path.size();\n  }\n  // If the path is of the form 'C:/' or 'C:\\', with C being any letter, it's\n  // a root part.\n  if (path.length() >= 2 && path[1] == ':' &&\n      (path[2] == '/' || path[2] == '\\\\') &&\n      ((path[0] >= 'A' && path[0] <= 'Z') ||\n       (path[0] >= 'a' && path[0] <= 'z'))) {\n    return 3;\n  }\n  return 0;\n}\n\nsize_t LastSeparator(const std::string& path) {\n  return path.find_last_of(\"/\\\\\");\n}\n\nstd::string GetDirectoryName(const std::string& path) {\n  size_t root_length = RootLength(path);\n  size_t separator = LastSeparator(path);\n  if (separator < root_length) separator = root_length;\n  if (separator == std::string::npos) return {};\n  return path.substr(0, separator);\n}\n\nstd::pair<bool, std::string> GetExecutableDirectoryPath() {\n  HMODULE module = GetModuleHandle(nullptr);\n  if (module == nullptr) {\n    return {false, \"\"};\n  }\n  wchar_t path[MAX_PATH];\n  DWORD read_size = GetModuleFileName(module, path, MAX_PATH);\n  if (read_size == 0 || read_size == MAX_PATH) {\n    return {false, \"\"};\n  }\n  return {true, GetDirectoryName(Utf8FromUtf16(std::wstring{path, read_size}))};\n}\n\nstd::pair<bool, std::string> GetModuleDirectoryPath() {\n  wchar_t system_buffer[MAX_PATH];\n  system_buffer[0] = 0;\n  if (GetModuleFileName(CURRENT_MODULE(), system_buffer, MAX_PATH) == 0) {\n    return {false, \"\"};\n  }\n  return {true, GetDirectoryName(Utf8FromUtf16(std::wstring(system_buffer)))};\n}\n\nbool DirectoryExists(const std::string& path) {\n  DWORD fileattr = GetFileAttributes(Utf16FromUtf8(std::string(path)).c_str());\n  if (fileattr != INVALID_FILE_ATTRIBUTES)\n    return (fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0;\n  return false;\n}\n\nstd::string JoinPaths(std::initializer_list<std::string> components) {\n  std::stringstream stream;\n  size_t i = 0;\n  const size_t size = components.size();\n  constexpr static char SEPARATOR = '\\\\';\n  for (const auto& component : components) {\n    i++;\n    stream << component;\n    if (component.back() == SEPARATOR) {\n      continue;\n    }\n    if (i != size) {\n      stream << SEPARATOR;\n    }\n  }\n  return stream.str();\n}\n\nbool CreateDir(const std::string& path) {\n  // If the path exists, we've succeeded if it's a directory, failed otherwise.\n  const DWORD fileattr = ::GetFileAttributes(Utf16FromUtf8(path).c_str());\n  if (fileattr != INVALID_FILE_ATTRIBUTES) {\n    if ((fileattr & FILE_ATTRIBUTE_DIRECTORY) != 0) {\n      return true;\n    }\n    return false;\n  }\n\n  // Attempt to create the parent recursively.  This will immediately return\n  // true if it already exists, otherwise will create all required parent\n  // directories starting with the highest-level missing parent.\n  auto parent_path = GetDirectoryName(std::string(path));\n  if (parent_path == path) {\n    return false;\n  }\n  if (!CreateDir(parent_path)) {\n    return false;\n  }\n\n  if (::CreateDirectory(Utf16FromUtf8(path).c_str(), nullptr)) {\n    return true;\n  }\n  const DWORD error_code = ::GetLastError();\n  if (error_code == ERROR_ALREADY_EXISTS && DirectoryExists(path)) {\n    // This error code ERROR_ALREADY_EXISTS doesn't indicate whether we were\n    // racing with someone creating the same directory, or a file with the same\n    // path.  If DirectoryExists() returns true, we lost the race to create the\n    // same directory.\n    return true;\n  }\n  return false;\n}\n\nstd::chrono::system_clock::time_point FileTime2TimePoint(const FILETIME& ft) {\n  SYSTEMTIME st = {0};\n  if (!FileTimeToSystemTime(&ft, &st)) {\n    return std::chrono::system_clock::time_point(\n        (std::chrono::system_clock::time_point::min)());\n  }\n\n  // number of seconds\n  ULARGE_INTEGER ull;\n  ull.LowPart = ft.dwLowDateTime;\n  ull.HighPart = ft.dwHighDateTime;\n\n  time_t secs = ull.QuadPart / 10000000ULL - 11644473600ULL;\n  std::chrono::milliseconds ms((ull.QuadPart / 10000ULL) % 1000);\n  auto tp = std::chrono::system_clock::from_time_t(secs);\n  tp += ms;\n  return tp;\n}\n\nbool GetFileInfo(const std::string& file_path, FileInfo& results) {\n  WIN32_FILE_ATTRIBUTE_DATA attr;\n  if (!GetFileAttributesEx(Utf16FromUtf8(std::string(file_path)).c_str(),\n                           GetFileExInfoStandard, &attr)) {\n    return false;\n  }\n\n  ULARGE_INTEGER size;\n  size.HighPart = attr.nFileSizeHigh;\n  size.LowPart = attr.nFileSizeLow;\n  results.size = size.QuadPart;\n\n  results.is_directory =\n      (attr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;\n  results.last_modified = FileTime2TimePoint(attr.ftLastWriteTime);\n  results.last_accessed = FileTime2TimePoint(attr.ftLastAccessTime);\n  results.creation_time = FileTime2TimePoint(attr.ftCreationTime);\n  return true;\n}\n\nbool GetFileSize(const std::string& file_path, int64_t& file_size) {\n  FileInfo info{};\n  if (!GetFileInfo(file_path, info)) {\n    return false;\n  }\n  file_size = info.size;\n  return true;\n}\n\n}  // namespace base\n}  // namespace lynx\n"
  },
  {
    "path": "core/base/utils/paths_win.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_BASE_UTILS_PATHS_WIN_H_\n#define CORE_BASE_UTILS_PATHS_WIN_H_\n#include <string>\n#include <utility>\n\nnamespace lynx {\nnamespace base {\nstd::pair<bool, std::string> GetExecutableDirectoryPath();\nstd::pair<bool, std::string> GetModuleDirectoryPath();\nbool DirectoryExists(const std::string& path);\nstd::string JoinPaths(std::initializer_list<std::string> components);\nbool CreateDir(const std::string& path);\nbool GetFileSize(const std::string& file_path, int64_t& file_size);\n}  // namespace base\n}  // namespace lynx\n#endif  // CORE_BASE_UTILS_PATHS_WIN_H_\n"
  },
  {
    "path": "core/base_targets.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//${lynx_dir}/build_overrides/darwin.gni\")\n\n# Do not include this file directly, include //core/Lynx.gni instead\n#\nimport(\"../config.gni\")\nimport(\"../oliver/oliver.gni\")\n\n# This target defines a source_set which contains some common configs and deps needed by\n# all targets in the source tree of Lynx\n\nquickjs_public_config =\n    rebase_path(\"../third_party/quickjs:quickjs_public_config\")\nlynx_oliver_config = rebase_path(\"../oliver:lynx_oliver_config\")\nlynx_android_public_config =\n    rebase_path(\"../platform/android/lynx_android:lynx_android_public_config\")\nlynx_unit_test_config = rebase_path(\"../testing:lynx_unit_test_config\")\nfind_node_headers = rebase_path(\":find_node_headers\")\nlynx_base_config = rebase_path(\"../base/src:base_public_config\")\n\ntemplate(\"lynx_core_source_set\") {\n  source_set(target_name) {\n    forward_variables_from(invoker,\n                           \"*\",\n                           [\n                             \"configs\",\n                             \"public_configs\",\n                           ])\n\n    if (!defined(deps)) {\n      deps = []\n    }\n\n    if (!defined(configs)) {\n      configs = []\n    }\n    if (defined(invoker.configs)) {\n      configs += invoker.configs\n    }\n\n    if (!defined(public_configs)) {\n      public_configs = []\n    }\n    if (defined(invoker.public_configs)) {\n      public_configs += invoker.public_configs\n    }\n    public_configs += [\n      \"//${lynx_dir}/core:lynx_public_config\",\n      quickjs_public_config,\n      lynx_base_config,\n    ]\n\n    if (enable_unittests) {\n      configs += [ lynx_unit_test_config ]\n    }\n\n    if (is_oliver || is_encoder_ut) {\n      configs += [ lynx_oliver_config ]\n    } else if (is_android) {\n      if (!defined(libs)) {\n        libs = []\n      }\n      libs += [ \"log\" ]\n      configs += [ lynx_android_public_config ]\n    } else if (is_apple) {\n      configs += darwin_config\n    }\n\n    if (is_oliver) {\n      deps += [ find_node_headers ]\n    }\n\n    if (defined(exclude_configs)) {\n      configs -= exclude_configs\n    }\n\n    if (defined(exclude_deps)) {\n      deps -= exclude_deps\n    }\n\n    if (defined(exclude_public_configs)) {\n      public_configs -= exclude_public_configs\n    }\n  }\n}\n"
  },
  {
    "path": "core/build/AGENTS.md",
    "content": "# AGENTS.md\n\n## Scope\n\nThis directory is a very small build-support layer for generated core build artifacts. Today it mainly exposes generated sub-error-code sources through the `build` target.\n\n## Key Files And Boundaries\n\n- `BUILD.gn`: the only meaningful ownership file here. Treat it as target wiring for generated build artifacts rather than as a place for engine behavior.\n- Generated sub-error-code sources that flow through this target are downstream artifacts. If their contents are wrong, the source definition or generator is usually the real owner.\n\n## Typical Change Patterns\n\n- If you are only exposing or wiring generated sources into a consumer target, this directory is the right place.\n- If you are changing how error codes are defined, named, or generated, stop here and inspect the upstream generator or definition source instead of patching the generated build layer.\n\n## Edit Rules\n\n- Treat files here as generated or generation-adjacent build artifacts, not hand-authored business logic.\n- If a change is really about error-code definitions or generation rules, the owning generator or upstream definition is usually the better place to edit.\n- Avoid introducing unrelated core logic into this directory.\n\n## Common Regression Symptoms\n\n- Build wiring succeeds locally but generated error-code symbols disappear from downstream targets.\n- A build-only edit appears to fix one consumer while silently disconnecting another shared consumer of the generated artifact.\n\n## Validate\n\nThis directory does not define a standalone unit-test exec. Validate through the nearest consumer build path if you change generated artifact wiring.\n\n## Notes\n\n- A correct change here is usually small. Large edits in this directory are a signal that the real ownership may be elsewhere.\n"
  },
  {
    "path": "core/build/BUILD.gn",
    "content": "# Copyright 2020 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\nimport(\"../Lynx.gni\")\n\nlynx_core_source_set(\"build\") {\n  sources = [\n    \"gen/lynx_sub_error_code.cc\",\n    \"gen/lynx_sub_error_code.h\",\n  ]\n}\n"
  },
  {
    "path": "core/event/AGENTS.md",
    "content": "# AGENTS.md\n\n## Scope\n\nThis directory contains Lynx Core’s event system: the event object model (`Event` and its subclasses), event targets (`EventTarget`), listeners and listener maps (`EventListener*`), and the event dispatcher (`EventDispatcher`).\n\nThis layer defines platform-agnostic “core event dispatch semantics”. Platform layers (iOS/Android/Harmony, etc.) typically collect input, build the corresponding core events at a higher layer, and then rely on the dispatch logic here to deliver events through capture/target/bubble phases.\n\n## Module Map\n\n- `event.*`: Base event type. Holds event metadata, propagation state, `detail`, and `event_path`.\n- `event_dispatcher.*`: Event dispatcher. Fires listeners in Capture → Target → Bubble order (and includes Global).\n- `event_target.*`: Event target abstraction. Provides parent/child relationship, listener container, and hooks for building `event_path`.\n- `event_listener.*`: Listener abstraction and options (capture/catch/global/once/passive, etc.).\n- `event_listener_map.*`: Container that stores listener vectors by event name (add/remove/find).\n- `event_dispatch_result.h`: Dispatch result and cancel reasons (`DispatchEventResult` / `EventCancelType`).\n- `touch_event.*` / `keyboard_event.*` / `custom_event.*`: Core event subclasses that populate custom detail or handle conflicts/params.\n- `*_test.*`: Unit tests for the event system in this directory.\n\n## Key Files And Types\n\n- [Event](file:./event.h)：\n  - Propagation phases: `PhaseType::{kCapturingPhase,kAtTarget,kBubblingPhase,kGlobal}`.\n  - Propagation control: `is_stop_propagation_` / `is_stop_immediate_propagation_`.\n  - Event path: `InitEventPath()` builds `event_path_` upwards via `EventTarget::GetParentTarget()`.\n  - Detail payload: `detail_` (`lepus::Value`) is populated by `HandleEventBaseDetail()` and subclass `HandleEventCustomDetail()`.\n  - Compatibility/conflicts: `HandleEventConflictAndParam()` may cancel/short-circuit before dispatch (implemented by subclasses).\n  \n- [EventDispatcher](file:./event_dispatcher.cc)：\n  - `DispatchEvent(target, event)` is the entry point; it calls `InitEventPath()` and then `Dispatch()`.\n  - `Dispatch()` ordering and cancel rules: conflict/param handling → set target/currentTarget → global → capture → at-target → bubble.\n  \n- [EventTarget](file:./event_target.h)：\n  - Event tree: `GetParentTarget()` defines the parent chain used by `event_path`.\n  - Path pruning: `IsEventPathSkip()` and `IsEventPathCatch()` can skip/truncate `event_path` construction.\n  - Target metadata: `GetEventTargetInfo()` / `GetEventControlInfo()` provide `target/currentTarget` info for `detail` and additional dispatch-control info.\n\n- [EventListener](file:./event_listener.h)：\n  - `Options` encodes listener semantics via bit flags (capture/catch/global, etc.).\n  - `IsMatchEvent()` decides whether to fire based on the current event phase and listener options (especially capture/catch/global).\n\n## Dispatch Semantics And Notes\n\n- **Phase semantics**\n  - Capture: reverse-iterate from root toward target (`path.rbegin → path.rend`), but does not re-dispatch the target itself in capture.\n  - Target: dispatched on the target node; listeners are stably sorted so capture listeners run before bubble listeners (see [event_target.cc](./event_target.cc)).\n  - Bubble: iterate from target upward (`path` forward), and likewise does not re-dispatch the target itself.\n  - Global: triggered via `target->HandleGlobalEvent()`; global listener matching is controlled by `EventListener::Options::kGlobalBit`.\n\n- **Cancelation and short-circuit**\n  - If `stopImmediatePropagation` is set during listener invocation, subsequent listeners on the same target are skipped.\n  - If `stopPropagation` is set or the listener options indicate catch, the `DispatchEventResult` is marked as `kCanceledByEventHandler` and `EventDispatcher` stops further propagation.\n  - `HandleEventConflictAndParam()` may cancel before dispatch begins (used for event conflicts/parameter adjustments).\n\n- **Weak pointers and lifecycle**\n  - `event_path` uses `fml::WeakPtr<EventTarget>`; if a node becomes invalid during dispatch it will be skipped and logged as an error.\n  - `EventDispatcher` also holds a weak pointer to the target; if the target is already released it returns `kCanceledBeforeDispatch`.\n\n## Typical Change Patterns\n\n- **Add/extend event types**\n  - Extend `Event::EventType` and add a corresponding `Event` subclass (or extend an existing subclass).\n  - Implement `HandleEventCustomDetail()` (populate `detail`) and/or `HandleEventConflictAndParam()` (handle conflicts and parameter compatibility) in the subclass.\n\n- **Adjust propagation behavior**\n  - Ordering, short-circuit conditions, and global/capture/bubble semantics: start from [event_dispatcher.cc](./event_dispatcher.cc).\n  - Pruning/truncating `event_path`: start from `Event::InitEventPath()` and `EventTarget::{IsEventPathSkip,IsEventPathCatch}`.\n\n- **Adjust listener matching/priority**\n  - Phase/option matching: start from [event_listener.cc](./event_listener.cc).\n  - Stable sorting of listeners at target phase: start from [event_target.cc](./event_target.cc).\n\n## Invariants And Pitfalls\n\n- `EventTarget::DispatchEvent()` copies the listener vector to avoid instability when listeners are added/removed during dispatch; do not break this isolation behavior.\n- `TouchEvent::long_press_consumed_` is a static cross-dispatch state (written based on whether `longpress` was consumed). Be careful when changing trigger/cancel logic so this state is not updated incorrectly.\n\n## Validate\n\n- Unit test targets for this directory are in [BUILD.gn](./BUILD.gn):\n  - `event_unittests_exec`\n  - `event_tests` (group)\n\n- If you changed propagation semantics (short-circuit behavior, phase matching, `event_path` construction), run `event_unittests_exec` first and verify:\n  - listener firing order (capture/target/bubble/global)\n  - short-circuit behavior for stopPropagation / stopImmediatePropagation / catch\n  - robustness when weak pointers in the event path become invalid\n"
  },
  {
    "path": "core/event/BUILD.gn",
    "content": "# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../testing/test.gni\")\nimport(\"../Lynx.gni\")\n\nevent_shared_sources = [\n  \"custom_event.cc\",\n  \"custom_event.h\",\n  \"event.cc\",\n  \"event.h\",\n  \"event_dispatch_result.h\",\n  \"event_dispatcher.cc\",\n  \"event_dispatcher.h\",\n  \"event_listener.cc\",\n  \"event_listener.h\",\n  \"event_listener_map.cc\",\n  \"event_listener_map.h\",\n  \"event_target.cc\",\n  \"event_target.h\",\n  \"keyboard_event.cc\",\n  \"keyboard_event.h\",\n  \"touch_event.cc\",\n  \"touch_event.h\",\n]\n\nlynx_core_source_set(\"event\") {\n  sources = event_shared_sources\n}\n\nunittest_set(\"event_testset\") {\n  sources = [\n    \"event_listener_map_test.cc\",\n    \"event_listener_map_test.h\",\n    \"event_listener_test.cc\",\n    \"event_listener_test.h\",\n    \"event_target_test.cc\",\n    \"event_target_test.h\",\n  ]\n  public_deps = [\n    \"../../base/src:base_values\",\n    \"../event\",\n  ]\n  data_deps = []\n}\n\nunittest_exec(\"event_unittests_exec\") {\n  sources = []\n  deps = [\n    \":event_testset\",\n    \"../../base/src:base_values_extended\",\n  ]\n}\n\ngroup(\"event_tests\") {\n  testonly = true\n  deps = [ \":event_unittests_exec\" ]\n}\n"
  },
  {
    "path": "core/event/custom_event.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/custom_event.h\"\n\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace event {\n\nCustomEvent::CustomEvent(const std::string& event_name,\n                         const lepus::Value& event_param,\n                         const std::string& param_name, int64_t time_stamp,\n                         Capture capture, Bubbles bubbles,\n                         Cancelable cancelable, ComposedMode composed_mode,\n                         PhaseType phase_type)\n    : Event(event_name, time_stamp, EventType::kCustomEvent, capture, bubbles,\n            cancelable, composed_mode, phase_type),\n      event_param_(event_param),\n      param_name_(param_name) {\n  event_type_ = EventType::kCustomEvent;\n}\n\nvoid CustomEvent::HandleEventCustomDetail() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, CUSTOM_EVENT_CUSTOM_DETAIL, \"name\", type_);\n  BASE_STATIC_STRING_DECL(kTimestamp, \"timestamp\");\n\n  auto dict = detail_.Table();\n  int64_t time_stamp = 0;\n  if (event_param_.IsTable() && event_param_.Table()->Contains(kTimestamp)) {\n    time_stamp = event_param_.Table()->GetValue(kTimestamp).Number();\n    // TODO(hexionghui): In order to prevent the e2e test from failing, the\n    // timestamp will be deleted here, and the e2e test will be modified later\n    // to avoid this.\n    event_param_.Table()->Erase(kTimestamp);\n  } else {\n    time_stamp = std::chrono::duration_cast<std::chrono::milliseconds>(\n                     std::chrono::system_clock::now().time_since_epoch())\n                     .count();\n  }\n  time_stamp_ = time_stamp;\n  dict->SetValue(kTimestamp, time_stamp);\n  dict->SetValue(param_name_, event_param_);\n  if (param_name_ == \"params\") {\n    BASE_STATIC_STRING_DECL(kDetail, \"detail\");\n    dict->SetValue(kDetail, event_param_);\n  }\n}\n\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/custom_event.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_EVENT_CUSTOM_EVENT_H_\n#define CORE_EVENT_CUSTOM_EVENT_H_\n\n#include <string>\n\n#include \"core/event/event.h\"\n\nnamespace lynx {\nnamespace event {\n\nclass CustomEvent : public Event {\n public:\n  CustomEvent(const std::string& event_name, const lepus::Value& event_param,\n              const std::string& param_name, int64_t time_stamp = 0,\n              Capture capture = Capture::kNo, Bubbles bubbles = Bubbles::kNo,\n              Cancelable cancelable = Cancelable::kYes,\n              ComposedMode composed_mode = ComposedMode::kScoped,\n              PhaseType phase_type = PhaseType::kNone);\n\n  void HandleEventCustomDetail() override;\n\n private:\n  lepus::Value event_param_{lepus::Dictionary::Create()};\n  std::string param_name_{\"\"};\n};\n\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_CUSTOM_EVENT_H_\n"
  },
  {
    "path": "core/event/event.cc",
    "content": "/*\n * Copyright (C) 2001 Peter Kelly (pmk@post.com)\n * Copyright (C) 2001 Tobias Anton (anton@stud.fbi.fh-darmstadt.de)\n * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)\n * Copyright (C) 2003, 2005, 2006, 2008 Apple Inc. All rights reserved.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Library General Public\n * License as published by the Free Software Foundation; either\n * version 2 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Library General Public License for more details.\n *\n * You should have received a copy of the GNU Library General Public License\n * along with this library; see the file COPYING.LIB.  If not, write to\n * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n * Boston, MA 02110-1301, USA.\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/event.h\"\n\n#include <chrono>\n#include <utility>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"core/event/event_dispatcher.h\"\n#include \"core/event/event_target.h\"\n\nnamespace lynx {\nnamespace event {\n\nEvent::Event(const std::string& type, int64_t time_stamp, EventType event_type,\n             Capture capture, Bubbles bubbles, Cancelable cancelable,\n             ComposedMode composed_mode, PhaseType phase_type)\n    : event_type_(event_type),\n      time_stamp_(time_stamp),\n      type_(type),\n      capture_(capture == Capture::kYes),\n      bubbles_(bubbles == Bubbles::kYes),\n      cancelable_(cancelable == Cancelable::kYes),\n      composed_(composed_mode == ComposedMode::kScoped),\n      event_phase_(phase_type) {\n  BASE_STATIC_STRING_DECL(kType, \"type\");\n  BASE_STATIC_STRING_DECL(kTimestamp, \"timestamp\");\n  detail_.Table()->SetValue(kType, type_);\n  detail_.Table()->SetValue(kTimestamp, time_stamp_);\n}\n\nEvent::Event(const std::string& type, EventType event_type, Capture capture,\n             Bubbles bubbles, Cancelable cancelable, ComposedMode composed_mode,\n             PhaseType phase_type)\n    : Event(type,\n            std::chrono::duration_cast<std::chrono::milliseconds>(\n                std::chrono::system_clock::now().time_since_epoch())\n                .count(),\n            event_type, capture, bubbles, cancelable, composed_mode,\n            phase_type) {}\n\nEvent::Event(const std::string& type, EventType event_type, Capture capture,\n             Bubbles bubbles, Cancelable cancelable, ComposedMode composed_mode)\n    : Event(type,\n            std::chrono::duration_cast<std::chrono::milliseconds>(\n                std::chrono::system_clock::now().time_since_epoch())\n                .count(),\n            event_type, capture, bubbles, cancelable, composed_mode,\n            PhaseType::kNone) {}\n\nEvent::Event(const std::string& type, int64_t time_stamp, EventType event_type,\n             Capture capture, Bubbles bubbles, Cancelable cancelable,\n             ComposedMode composed_mode, PhaseType phase_type,\n             const lepus::Value& detail)\n    : Event(type, time_stamp, event_type, capture, bubbles, cancelable,\n            composed_mode, phase_type) {\n  detail_ = detail;\n}\n\nvoid Event::InitEventPath(EventTarget& target) {\n  EventTarget* event_target = &target;\n  if (!event_target) {\n    LOGE(\"Event::InitEventPath error: the target is null.\")\n    return;\n  }\n  while (event_target && !event_target->IsEventPathCatch(&target, this)) {\n    if (event_target->IsEventPathSkip(&target, this)) {\n      event_target = event_target->GetParentTarget();\n      continue;\n    }\n    event_path_.push_back(event_target->GetWeakTarget());\n    event_target = event_target->GetParentTarget();\n  }\n}\n\nstd::vector<fml::WeakPtr<EventTarget>>& Event::event_path() {\n  return event_path_;\n}\n\nDispatchEventResult Event::DispatchEvent(EventDispatcher& dispatcher) {\n  return dispatcher.Dispatch();\n}\n\nvoid Event::HandleEventBaseDetail(bool is_core_event) {\n  if (!target_ || !current_target_) {\n    LOGE(\n        \"Event::HandleEventBaseDetail error: the target or current_target is \"\n        \"null.\")\n    return;\n  }\n  BASE_STATIC_STRING_DECL(kTarget, \"target\");\n  BASE_STATIC_STRING_DECL(kCurrentTarget, \"currentTarget\");\n  detail_.SetProperty(kTarget, target_->GetEventTargetInfo(is_core_event));\n  detail_.SetProperty(kCurrentTarget,\n                      current_target_->GetEventTargetInfo(is_core_event));\n}\n\nvoid Event::HandleEventCustomDetail() {}\n\nbool Event::IsCaptureBubbleEvent() {\n  return event_type_ == EventType::kTouchEvent ||\n         event_type_ == EventType::kCustomEvent ||\n         event_type_ == EventType::kMouseEvent ||\n         event_type_ == EventType::kWheelEvent ||\n         event_type_ == EventType::kKeyboardEvent;\n}\n\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/event.h",
    "content": "/*\n * Copyright (C) 2001 Peter Kelly (pmk@post.com)\n * Copyright (C) 2001 Tobias Anton (anton@stud.fbi.fh-darmstadt.de)\n * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)\n * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights\n * reserved.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Library General Public\n * License as published by the Free Software Foundation; either\n * version 2 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Library General Public License for more details.\n *\n * You should have received a copy of the GNU Library General Public License\n * along with this library; see the file COPYING.LIB.  If not, write to\n * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n * Boston, MA 02110-1301, USA.\n *\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_EVENT_EVENT_H_\n#define CORE_EVENT_EVENT_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/include/value/table.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/event/event_dispatch_result.h\"\n\n#define EVENT_TYPE_CAPTURE \"capture-bind\"\n#define EVENT_TYPE_CAPTURE_CATCH \"capture-catch\"\n#define EVENT_TYPE_CATCH \"catchEvent\"\n#define EVENT_TYPE_GLOBAL \"global-bindEvent\"\n\nnamespace lynx {\nnamespace event {\n\nclass EventTarget;\nclass EventDispatcher;\n\nclass Event : public lepus::RefCounted {\n public:\n  enum class Capture {\n    kNo,\n    kYes,\n  };\n\n  enum class Bubbles {\n    kNo,\n    kYes,\n  };\n\n  enum class Cancelable {\n    kNo,\n    kYes,\n  };\n\n  enum class PhaseType {\n    kNone = 0,\n    kCapturingPhase = 1,\n    kAtTarget = 2,\n    kBubblingPhase = 3,\n    kGlobal = 4,\n  };\n\n  // If need extend a new bind type for Event, a new enumeration should be added\n  // in BindType\n  enum class BindType {\n    kNone,\n    kBubble,\n    kCapture,\n    kCaptureCatch,\n    kBubbleCatch,\n    kGlobalBind,\n  };\n\n  enum class ComposedMode {\n    kComposed,\n    kScoped,\n  };\n\n  // If need extend a new Event subclass, a new enumeration should be added in\n  // EventType.\n  enum class EventType {\n    kNone,\n    kTouchEvent,\n    kKeyboardEvent,\n    kWheelEvent,\n    kPointerEvent,\n    kUIEvent,\n    kMouseEvent,\n    kMessageEvent,\n    kCustomEvent,\n  };\n\n  Event(const std::string&, int64_t, EventType, Capture, Bubbles, Cancelable,\n        ComposedMode, PhaseType);\n\n  Event(const std::string&, EventType, Capture, Bubbles, Cancelable,\n        ComposedMode, PhaseType);\n\n  Event(const std::string&, EventType, Capture, Bubbles, Cancelable,\n        ComposedMode);\n\n  Event(const std::string&, int64_t, EventType, Capture, Bubbles, Cancelable,\n        ComposedMode, PhaseType, const lepus::Value&);\n\n  virtual ~Event() = default;\n\n  lepus::RefType GetRefType() const override { return lepus::RefType::kEvent; };\n\n  EventType event_type() { return event_type_; }\n  void set_event_type(EventType event_type) { event_type_ = event_type; }\n  bool from_frontend() { return from_frontend_; }\n  void set_from_frontend(bool from_frontend) { from_frontend_ = from_frontend; }\n  int64_t time_stamp() const { return time_stamp_; };\n  const std::string& type() const { return type_; }\n  bool capture() const { return capture_; }\n  void set_capture(bool capture) { capture_ = capture; }\n  bool bubbles() const { return bubbles_; }\n  void set_bubbles(bool bubbles) { bubbles_ = bubbles; }\n  bool cancelable() const { return cancelable_; }\n  bool composed() const { return composed_; }\n\n  PhaseType event_phase() const { return event_phase_; }\n  void set_event_phase(PhaseType event_phase) { event_phase_ = event_phase; }\n\n  bool is_stop_propagation() const { return is_stop_propagation_; }\n  void set_is_stop_propagation(bool is_stop_propagation) {\n    is_stop_propagation_ = is_stop_propagation;\n  }\n\n  bool is_stop_immediate_propagation() const {\n    return is_stop_immediate_propagation_;\n  }\n  void set_is_stop_immediate_propagation(bool is_stop_immediate_propagation) {\n    is_stop_immediate_propagation_ = is_stop_immediate_propagation;\n  }\n\n  fml::WeakPtr<EventTarget> target() const { return target_; }\n  void set_target(fml::WeakPtr<EventTarget> target) { target_ = target; }\n\n  fml::WeakPtr<EventTarget> current_target() const { return current_target_; }\n  void set_current_target(fml::WeakPtr<EventTarget> current_target) {\n    current_target_ = current_target;\n  }\n\n  lepus::Value& detail() { return detail_; }\n  void set_detail(const lepus::Value detail) { detail_ = detail; }\n\n  std::vector<fml::WeakPtr<EventTarget>>& event_path();\n\n  void SetTraceFlowId(uint64_t trace_flow_id) {\n    trace_flow_id_ = trace_flow_id;\n  }\n  uint64_t TraceFlowId() const { return trace_flow_id_; }\n\n  void InitEventPath(EventTarget& target);\n  virtual DispatchEventResult DispatchEvent(EventDispatcher&);\n\n  // Called before triggering (invoke the listener) an event to get the base\n  // part of detail_.\n  virtual void HandleEventBaseDetail(bool is_core_event = false);\n  // Called before dispatching an event to get the custom part of detail_.\n  virtual void HandleEventCustomDetail();\n  // Called before dispatching an event to handle the conflic and param.\n  virtual bool HandleEventConflictAndParam() { return false; }\n  bool IsCaptureBubbleEvent();\n\n protected:\n  EventType event_type_{EventType::kNone};\n  int64_t time_stamp_{0};\n  std::string type_;\n  bool from_frontend_{false};\n\n  bool capture_ : 1;\n  bool bubbles_ : 1;\n  bool cancelable_ : 1;\n  bool composed_ : 1;\n\n  PhaseType event_phase_{PhaseType::kNone};\n\n  bool is_stop_propagation_{false};\n  bool is_stop_immediate_propagation_{false};\n\n  fml::WeakPtr<EventTarget> current_target_;\n  fml::WeakPtr<EventTarget> target_;\n\n  // Save the event parameters passed to the listener's closure.\n  lepus::Value detail_{lepus::Dictionary::Create()};\n\n  std::vector<fml::WeakPtr<EventTarget>> event_path_;\n\n  uint64_t trace_flow_id_{TRACE_FLOW_ID()};\n};\n\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_EVENT_H_\n"
  },
  {
    "path": "core/event/event_dispatch_result.h",
    "content": "// Copyright 2016 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_EVENT_EVENT_DISPATCH_RESULT_H_\n#define CORE_EVENT_EVENT_DISPATCH_RESULT_H_\n\n#include <stdint.h>\n\nnamespace lynx {\nnamespace event {\n\nenum class EventCancelType : uint8_t {\n  // Event was not canceled by event handler or default event handler.\n  kNotCanceled = 0,\n  // Event was canceled by event handler; i.e. a script handler calling\n  // preventDefault.\n  kCanceledByEventHandler,\n  // Event was canceled by the default event handler; i.e. executing the default\n  // action.  This result should be used sparingly as it deviates from the DOM\n  // Event Dispatch model. Default event handlers really shouldn't be invoked\n  // inside of dispatch.\n  kCanceledByDefaultEventHandler,\n  // Event was canceled but suppressed before dispatched to event handler.  This\n  // result should be used sparingly; and its usage likely indicates there is\n  // potential for a bug. Trusted events may return this code; but untrusted\n  // events likely should always execute the event handler the developer intends\n  // to execute.\n  kCanceledBeforeDispatch,\n};\n\nstruct DispatchEventResult {\n  EventCancelType cancel_type;\n  // Whether the event was consumed by front-end.\n  bool consumed;\n\n  bool IsCanceled() const {\n    return cancel_type != EventCancelType::kNotCanceled;\n  }\n};\n\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_EVENT_DISPATCH_RESULT_H_\n"
  },
  {
    "path": "core/event/event_dispatcher.cc",
    "content": "/*\n * Copyright (C) 1999 Lars Knoll (knoll@kde.org)\n *           (C) 1999 Antti Koivisto (koivisto@kde.org)\n *           (C) 2001 Dirk Mueller (mueller@kde.org)\n * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All\n * rights reserved.\n * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)\n * Copyright (C) 2009 Torch Mobile Inc. All rights reserved.\n * (http://www.torchmobile.com/)\n * Copyright (C) 2011 Google Inc. All rights reserved.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Library General Public\n * License as published by the Free Software Foundation; either\n * version 2 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Library General Public License for more details.\n *\n * You should have received a copy of the GNU Library General Public License\n * along with this library; see the file COPYING.LIB.  If not, write to\n * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n * Boston, MA 02110-1301, USA.\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/event_dispatcher.h\"\n\n#include <utility>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/event/event.h\"\n#include \"core/event/event_target.h\"\n#include \"core/event/touch_event.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace event {\n\nDispatchEventResult EventDispatcher::DispatchEvent(EventTarget& target,\n                                                   fml::RefPtr<Event> event) {\n  EventDispatcher dispatcher(target, event);\n  return event->DispatchEvent(dispatcher);\n}\n\nEventDispatcher::EventDispatcher(EventTarget& target, fml::RefPtr<Event> event)\n    : target_(target.GetWeakTarget()), event_(std::move(event)) {\n  event_->InitEventPath(*target_);\n}\n\nDispatchEventResult EventDispatcher::Dispatch() {\n  if (!target_) {\n    LOGE(\"EventDispatcher::Dispatch error: the target is null.\")\n    return {EventCancelType::kCanceledBeforeDispatch, false};\n  }\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, EVENT_DISPATCHER_DISPATCH,\n              [this, target = target_](lynx::perfetto::EventContext ctx) {\n                ctx.event()->add_debug_annotations(\"name\", event_->type());\n                ctx.event()->add_debug_annotations(\"target\",\n                                                   target->GetUniqueID());\n              });\n  LOGI(\"EventDispatcher::Dispatch name: \" << event_->type() << \" target: \"\n                                          << target_->GetUniqueID())\n  // handle conflic and param\n  if (event_->HandleEventConflictAndParam()) {\n    return {EventCancelType::kCanceledByEventHandler, false};\n  }\n  event_->set_target(target_->GetWeakTarget());\n  event_->HandleEventCustomDetail();\n  bool consumed = false;\n  auto path = event_->event_path();\n\n  // trigger global event, eg: trigger-global-event attribute or global-bind\n  // event\n  target_->HandleGlobalEvent(event_);\n\n  // capture, eg: capture-bindtap\n  if (event_->capture()) {\n    for (auto item = path.rbegin(); item != path.rend(); ++item) {\n      fml::WeakPtr<EventTarget> target = *item;\n      if (!target) {\n        LOGE(\n            \"EventDispatcher::Dispatch capture error: the target of event path \"\n            \"is null.\")\n        continue;\n      }\n      if (event_->target() == target) {\n        // target is handled by target phase.\n        continue;\n      }\n      event_->set_event_phase(Event::PhaseType::kCapturingPhase);\n      event_->set_current_target(*item);\n      auto result = (*item)->DispatchEvent(event_);\n      consumed |= result.consumed;\n      if (result.IsCanceled()) {\n        return result;\n      }\n    }\n  }\n\n  // at target\n  {\n    event_->set_event_phase(Event::PhaseType::kAtTarget);\n    event_->set_current_target(target_->GetWeakTarget());\n    auto result = target_->DispatchEvent(event_);\n    consumed |= result.consumed;\n    if (result.IsCanceled()) {\n      return result;\n    }\n  }\n\n  // bubble, eg: bindtap\n  if (event_->bubbles()) {\n    for (auto& item : path) {\n      if (!item) {\n        LOGE(\n            \"EventDispatcher::Dispatch bubble error: the target of event path \"\n            \"is null.\")\n        continue;\n      }\n      if (event_->target() == item) {\n        // target is handled by target phase.\n        continue;\n      }\n      event_->set_event_phase(Event::PhaseType::kBubblingPhase);\n      event_->set_current_target(item);\n      auto result = item->DispatchEvent(event_);\n      consumed |= result.consumed;\n      if (result.IsCanceled()) {\n        return result;\n      }\n    }\n  }\n\n  return {EventCancelType::kNotCanceled, consumed};\n}\n\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/event_dispatcher.h",
    "content": "/*\n * Copyright (C) 1999 Lars Knoll (knoll@kde.org)\n *           (C) 1999 Antti Koivisto (koivisto@kde.org)\n *           (C) 2001 Dirk Mueller (mueller@kde.org)\n * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights\n * reserved.\n * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)\n * Copyright (C) 2009 Torch Mobile Inc. All rights reserved.\n * (http://www.torchmobile.com/)\n * Copyright (C) 2011 Google Inc. All rights reserved.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Library General Public\n * License as published by the Free Software Foundation; either\n * version 2 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Library General Public License for more details.\n *\n * You should have received a copy of the GNU Library General Public License\n * along with this library; see the file COPYING.LIB.  If not, write to\n * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n * Boston, MA 02110-1301, USA.\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"core/event/event_dispatch_result.h\"\n\n#ifndef CORE_EVENT_EVENT_DISPATCHER_H_\n#define CORE_EVENT_EVENT_DISPATCHER_H_\n\nnamespace lynx {\nnamespace event {\n\nclass Event;\nclass EventTarget;\n\nclass EventDispatcher {\n public:\n  static DispatchEventResult DispatchEvent(EventTarget&, fml::RefPtr<Event>);\n\n  DispatchEventResult Dispatch();\n  EventTarget& GetTarget() const { return *target_; }\n  fml::RefPtr<Event> GetEvent() const { return event_; }\n\n private:\n  EventDispatcher(EventTarget&, fml::RefPtr<Event>);\n\n  fml::WeakPtr<EventTarget> target_;\n  fml::RefPtr<Event> event_;\n};\n\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_EVENT_DISPATCHER_H_\n"
  },
  {
    "path": "core/event/event_listener.cc",
    "content": "/*\n * Copyright (C) 2006, 2008, 2009 Apple Inc. All rights reserved.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Library General Public\n * License as published by the Free Software Foundation; either\n * version 2 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Library General Public License for more details.\n *\n * You should have received a copy of the GNU Library General Public License\n * along with this library; see the file COPYING.LIB.  If not, write to\n * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n * Boston, MA 02110-1301, USA.\n *\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/event_listener.h\"\n\n#include \"core/event/event.h\"\n\nnamespace lynx {\nnamespace event {\n\nEventListener::EventListener(Type type, const EventListener::Options& options)\n    : type_(type), options_(options) {}\n\nbool EventListener::IsMatchEvent(fml::RefPtr<Event> event) const {\n  bool is_capture_bubble_event = event->IsCaptureBubbleEvent();\n  if (is_capture_bubble_event) {\n    bool is_capture_listener =\n        event->event_phase() == Event::PhaseType::kCapturingPhase &&\n        GetOptions().IsCapture() && !GetOptions().IsGlobal();\n    // Align the logic of capturePhase:false of miniapp.\n    bool is_good_capture = GetOptions().IsCapture() && event->capture();\n    bool is_target_listener =\n        event->event_phase() == Event::PhaseType::kAtTarget &&\n        (is_good_capture || !GetOptions().IsCapture()) &&\n        !GetOptions().IsGlobal();\n    bool is_bubble_listener =\n        event->event_phase() == Event::PhaseType::kBubblingPhase &&\n        !GetOptions().IsCapture() && !GetOptions().IsGlobal();\n    bool is_global_listener =\n        event->event_phase() == Event::PhaseType::kGlobal &&\n        GetOptions().IsGlobal();\n    return is_capture_listener || is_target_listener || is_bubble_listener ||\n           is_global_listener;\n  }\n  return true;\n}\n\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/event_listener.h",
    "content": "/*\n * Copyright (C) 2006, 2008, 2009 Apple Inc. All rights reserved.\n *\n * This library is free software; you can redistribute it and/or\n * modify it under the terms of the GNU Library General Public\n * License as published by the Free Software Foundation; either\n * version 2 of the License, or (at your option) any later version.\n *\n * This library is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n * Library General Public License for more details.\n *\n * You should have received a copy of the GNU Library General Public License\n * along with this library; see the file COPYING.LIB.  If not, write to\n * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\n * Boston, MA 02110-1301, USA.\n *\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_EVENT_EVENT_LISTENER_H_\n#define CORE_EVENT_EVENT_LISTENER_H_\n\n#include <string>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n\nnamespace lynx {\nnamespace event {\n\nclass Event;\n\nclass EventListener {\n public:\n  // If need extend a new EventListener subclass, a new enumeration should be\n  // added in Type.\n  enum class Type {\n    kLepusClosureEventListener,\n    kJSClosureEventListener,\n    kClosureEventListener\n  };\n\n  struct Options {\n    static const int kCaptureBit = 1 << 0;\n    static const int kOnceBit = 1 << 1;\n    static const int kPassiveBit = 1 << 2;\n    static const int kSignalBit = 1 << 3;\n    // Indicates whether to intercept the event. Compatible with capture-catch\n    // and catch.\n    static const int kCatchBit = 1 << 4;\n    // Indicates whether to global bind the event. Compatible with global-bind.\n    static const int kGlobalBit = 1 << 5;\n\n    int64_t flags;\n\n    Options(bool capture = false, bool once = false, bool passive = false,\n            bool signal = false, bool is_catch = false, bool is_global = false)\n        : flags((capture ? kCaptureBit : 0) | (once ? kOnceBit : 0) |\n                (passive ? kPassiveBit : 0) | (signal ? kSignalBit : 0) |\n                (is_catch ? kCatchBit : 0) | (is_global ? kGlobalBit : 0)) {}\n\n    int64_t Flags() const { return flags; }\n\n    bool IsCapture() const { return (flags & kCaptureBit) == kCaptureBit; }\n\n    bool IsCatch() const { return (flags & kCatchBit) == kCatchBit; }\n\n    bool IsGlobal() const { return (flags & kGlobalBit) == kGlobalBit; }\n  };\n\n  explicit EventListener(Type type, const Options& options = Options());\n  virtual ~EventListener() = default;\n\n  // make EventListener move only\n  EventListener(const EventListener&) = delete;\n  EventListener& operator=(const EventListener&) = delete;\n  EventListener(EventListener&&) = default;\n  EventListener& operator=(EventListener&&) = default;\n\n  void set_removed(bool value) { removed_ = value; }\n  bool removed() { return removed_; }\n\n  Type type() const { return type_; }\n\n  const Options& GetOptions() const { return options_; }\n\n  virtual void Invoke(fml::RefPtr<Event> event) = 0;\n\n  virtual bool Matches(EventListener* listener) = 0;\n\n  bool IsMatchEvent(fml::RefPtr<Event> event) const;\n\n protected:\n  bool removed_{false};\n  Type type_;\n  Options options_;\n};\n\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_EVENT_LISTENER_H_\n"
  },
  {
    "path": "core/event/event_listener_map.cc",
    "content": "/*\n * Copyright (C) 1999 Lars Knoll (knoll@kde.org)\n *           (C) 1999 Antti Koivisto (koivisto@kde.org)\n *           (C) 2001 Dirk Mueller (mueller@kde.org)\n * Copyright (C) 2004, 2005, 2006, 2007, 2012 Apple Inc. All rights reserved.\n * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)\n *           (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>\n * Copyright (C) 2011 Andreas Kling (kling@webkit.org)\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/event_listener_map.h\"\n\n#include <algorithm>\n#include <tuple>\n\nnamespace lynx {\nnamespace event {\n\nvoid EventListenerMap::Clear() { map_.clear(); }\n\nbool EventListenerMap::IsEmpty() const {\n  if (map_.empty()) {\n    return true;\n  }\n  for (const auto& pair : map_) {\n    if (!pair.second.empty()) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool EventListenerMap::Contains(const std::string& type) const {\n  for (const auto& pair : map_) {\n    if (pair.first == type) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool EventListenerMap::Add(const std::string& type,\n                           std::shared_ptr<EventListener> listener) {\n  EventListenerVector* vector = Find(type);\n  if (vector == nullptr) {\n    vector = &map_.emplace_back(std::piecewise_construct,\n                                std::forward_as_tuple(type), std::tuple<>())\n                  .second;\n  }\n  vector->emplace_back(std::move(listener));\n  return true;\n}\n\nbool EventListenerMap::Remove(const std::string& type,\n                              std::shared_ptr<EventListener> listener) {\n  EventListenerVector* vector = Find(type);\n  if (!vector || vector->empty()) {\n    return false;\n  }\n  auto iter = std::remove_if(vector->begin(), vector->end(),\n                             [ptr = listener.get()](const auto& item) {\n                               if (ptr->Matches(item.get())) {\n                                 item->set_removed(true);\n                                 return true;\n                               }\n                               return false;\n                             });\n  if (iter == vector->end()) {\n    return false;\n  }\n  vector->erase(iter, vector->end());\n  return true;\n}\n\nbool EventListenerMap::Remove(const std::string& type) {\n  EventListenerVector* vector = Find(type);\n  if (!vector || vector->empty()) {\n    return false;\n  }\n  vector->clear_and_shrink();\n  return true;\n}\n\nEventListenerVector* EventListenerMap::Find(const std::string& type) {\n  for (auto& pair : map_) {\n    if (pair.first == type) {\n      return &(pair.second);\n    }\n  }\n  return nullptr;\n}\n\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/event_listener_map.h",
    "content": "/*\n * Copyright (C) 1999 Lars Knoll (knoll@kde.org)\n *           (C) 1999 Antti Koivisto (koivisto@kde.org)\n *           (C) 2001 Dirk Mueller (mueller@kde.org)\n * Copyright (C) 2004, 2005, 2006, 2007, 2012 Apple Inc. All rights reserved.\n * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)\n *           (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>\n * Copyright (C) 2011 Andreas Kling (kling@webkit.org)\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/vector.h\"\n#include \"core/event/event_listener.h\"\n\n#ifndef CORE_EVENT_EVENT_LISTENER_MAP_H_\n#define CORE_EVENT_EVENT_LISTENER_MAP_H_\n\nnamespace lynx {\nnamespace event {\n\nclass EventListener;\nusing EventListenerVector =\n    base::InlineVector<std::shared_ptr<EventListener>, 2>;\n\nclass EventListenerMap {\n public:\n  void Clear();\n  bool IsEmpty() const;\n  bool Contains(const std::string& type) const;\n\n  bool Add(const std::string& type, std::shared_ptr<EventListener> listener);\n  bool Remove(const std::string& type, std::shared_ptr<EventListener> listener);\n  bool Remove(const std::string& type);\n\n  EventListenerVector* Find(const std::string& type);\n\n private:\n  base::InlineVector<std::pair<std::string, EventListenerVector>, 2> map_;\n};\n\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_EVENT_LISTENER_MAP_H_\n"
  },
  {
    "path": "core/event/event_listener_map_test.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/event_listener_map_test.h\"\n\n#include \"core/event/event_listener_test.h\"\n\nnamespace lynx {\nnamespace event {\nnamespace test {\n\nTEST_F(EventListenerMapTest, TestEventListenerMapTest0) {\n  // init event listener map\n  EXPECT_TRUE(map_->IsEmpty());\n  EXPECT_FALSE(map_->Contains(\"test\"));\n  auto* vec_ptr = map_->Find(\"test\");\n  EXPECT_EQ(vec_ptr, nullptr);\n  EXPECT_FALSE(map_->Remove(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\")));\n\n  // add {\"test\": \"1\"}\n  map_->Add(\"test\", std::make_unique<MockEventListener>(\n                        EventListener::Type::kJSClosureEventListener, \"1\"));\n  EXPECT_FALSE(map_->IsEmpty());\n  vec_ptr = map_->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 1);\n  EXPECT_TRUE(map_->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n\n  // add {\"test\": \"2\"}\n  map_->Add(\"test\", std::make_unique<MockEventListener>(\n                        EventListener::Type::kJSClosureEventListener, \"2\"));\n  EXPECT_FALSE(map_->IsEmpty());\n  vec_ptr = map_->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 2);\n  EXPECT_TRUE(map_->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"2\");\n\n  // add {\"test\": \"3\"}\n  map_->Add(\"test\", std::make_unique<MockEventListener>(\n                        EventListener::Type::kJSClosureEventListener, \"3\"));\n  EXPECT_FALSE(map_->IsEmpty());\n  vec_ptr = map_->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 3);\n  EXPECT_TRUE(map_->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"2\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetContent(),\n            \"3\");\n\n  EXPECT_FALSE(map_->Remove(\n      \"test1\", std::make_unique<MockEventListener>(\n                   EventListener::Type::kJSClosureEventListener, \"1\")));\n\n  // remove {\"test\": \"1\"}\n  EXPECT_TRUE(map_->Remove(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\")));\n  EXPECT_FALSE(map_->IsEmpty());\n  vec_ptr = map_->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 2);\n  EXPECT_TRUE(map_->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"2\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"3\");\n\n  // clear\n  map_->Clear();\n  EXPECT_TRUE(map_->IsEmpty());\n  EXPECT_FALSE(map_->Contains(\"test\"));\n  vec_ptr = map_->Find(\"test\");\n  EXPECT_EQ(vec_ptr, nullptr);\n  EXPECT_FALSE(map_->Remove(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\")));\n}\n\n}  // namespace test\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/event_listener_map_test.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"core/event/event_listener_map.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#ifndef CORE_EVENT_EVENT_LISTENER_MAP_TEST_H_\n#define CORE_EVENT_EVENT_LISTENER_MAP_TEST_H_\n\nnamespace lynx {\nnamespace event {\nnamespace test {\n\nclass EventListenerMapTest : public ::testing::Test {\n public:\n  EventListenerMapTest() = default;\n  ~EventListenerMapTest() override = default;\n\n  void SetUp() override { map_ = std::make_unique<EventListenerMap>(); }\n\n  void TearDown() override {}\n\n  std::unique_ptr<EventListenerMap> map_;\n};\n\n}  // namespace test\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_EVENT_LISTENER_MAP_TEST_H_\n"
  },
  {
    "path": "core/event/event_listener_test.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/event_listener_test.h\"\n\n#include \"core/event/event_target.h\"\n\nnamespace lynx {\nnamespace event {\nnamespace test {\n\nMockEventListener::MockEventListener(EventListener::Type type,\n                                     const std::string& content,\n                                     const std::string& event_name,\n                                     const std::string& erase_content,\n                                     EventTarget* target,\n                                     const Options& options)\n    : EventListener(type, options),\n      content_(content),\n      event_name_(event_name),\n      erase_content_(erase_content),\n      target_(target) {}\n\nvoid MockEventListener::Invoke(fml::RefPtr<Event> event) {\n  count_++;\n  if (target_ != nullptr) {\n    target_->RemoveEventListener(\n        event_name_,\n        std::make_unique<MockEventListener>(type(), erase_content_));\n  }\n}\n\nbool MockEventListener::Matches(EventListener* listener) {\n  if (type() != listener->type()) {\n    return false;\n  }\n  return GetContent() ==\n         static_cast<MockEventListener*>(listener)->GetContent();\n}\n\n}  // namespace test\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/event_listener_test.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string>\n\n#include \"core/event/event_listener.h\"\n\n#ifndef CORE_EVENT_EVENT_LISTENER_TEST_H_\n#define CORE_EVENT_EVENT_LISTENER_TEST_H_\n\nnamespace lynx {\nnamespace event {\n\nclass EventTarget;\n\nnamespace test {\n\nclass MockEventListener : public EventListener {\n public:\n  MockEventListener(EventListener::Type, const std::string&,\n                    const std::string& event_name = \"\",\n                    const std::string& erase_content = \"\",\n                    EventTarget* target = nullptr,\n                    const Options& options = Options());\n\n  ~MockEventListener() override = default;\n\n  virtual void Invoke(fml::RefPtr<Event> event) override;\n  virtual bool Matches(EventListener* listener) override;\n\n  int32_t GetCount() { return count_; }\n  std::string GetContent() { return content_; }\n\n private:\n  int32_t count_{0};\n  std::string content_;\n\n  std::string event_name_;\n  std::string erase_content_;\n  EventTarget* target_{nullptr};\n};\n\n}  // namespace test\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_EVENT_LISTENER_TEST_H_\n"
  },
  {
    "path": "core/event/event_target.cc",
    "content": "/*\n * Copyright (C) 1999 Lars Knoll (knoll@kde.org)\n *           (C) 1999 Antti Koivisto (koivisto@kde.org)\n *           (C) 2001 Dirk Mueller (mueller@kde.org)\n * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.\n * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)\n *           (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/event_target.h\"\n\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"core/event/touch_event.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace event {\n\nEventTarget::EventTarget()\n    : event_listener_map_(std::make_unique<EventListenerMap>()) {}\n\nDispatchEventResult EventTarget::DispatchEvent(fml::RefPtr<Event> event) {\n  auto vector = event_listener_map_->Find(event->type());\n  if (vector == nullptr) {\n    return {EventCancelType::kNotCanceled, false};\n  }\n\n  // Fire all listeners registered for this event. Don't fire listeners removed\n  // during event dispatch. Also, don't fire event listeners added during event\n  // dispatch. Conveniently, all new event listeners will be added after or at\n  // index |size|, so iterating up to (but not including) |size| naturally\n  // excludes new event listeners.\n  EventListenerVector copy = *vector;\n  bool consumed = false;\n  bool is_catch = false;\n  // When in the target phase, the listeners need to be sorted in a stable order\n  // based on the capture order.\n  if (event->event_phase() == Event::PhaseType::kAtTarget) {\n    std::stable_sort(copy.begin(), copy.end(),\n                     [](const std::shared_ptr<EventListener>& a,\n                        const std::shared_ptr<EventListener>& b) {\n                       return a->GetOptions().IsCapture() &&\n                              !b->GetOptions().IsCapture();\n                     });\n  }\n  for (auto& listener : copy) {\n    if (!listener->IsMatchEvent(event)) {\n      continue;\n    }\n    if (listener->removed()) {\n      continue;\n    }\n\n    TRACE_EVENT(\n        LYNX_TRACE_CATEGORY, EVENT_TARGET_DISPATCHEVENT,\n        [&event, &listener, target = this](lynx::perfetto::EventContext ctx) {\n          ctx.event()->add_debug_annotations(\"name\", event->type());\n          ctx.event()->add_debug_annotations(\n              \"phase\", std::to_string(static_cast<int>(event->event_phase())));\n          ctx.event()->add_debug_annotations(\"target\", target->GetUniqueID());\n          ctx.event()->add_debug_annotations(\n              \"listener_options\",\n              std::to_string(listener->GetOptions().Flags()));\n          ctx.event()->add_flow_ids(event->TraceFlowId());\n        });\n    LOGI(\"EventTarget::DispatchEvent name: \"\n         << event->type()\n         << \", phase: \" << static_cast<int>(event->event_phase())\n         << \", target: \" << GetUniqueID()\n         << \", listener options: \" << listener->GetOptions().Flags())\n\n    is_catch |= listener->GetOptions().IsCatch();\n    listener->Invoke(event);\n    consumed = true;\n\n    if (event->is_stop_immediate_propagation()) {\n      break;\n    }\n  }\n\n  if (consumed && event->event_type() == Event::EventType::kTouchEvent &&\n      event->type() == EVENT_LONG_PRESS) {\n    TouchEvent::long_press_consumed_ = consumed;\n  }\n\n  if (event->is_stop_propagation() || event->is_stop_immediate_propagation() ||\n      is_catch) {\n    return {EventCancelType::kCanceledByEventHandler, consumed};\n  } else {\n    return {EventCancelType::kNotCanceled, consumed};\n  }\n}\n\nbool EventTarget::AddEventListener(const std::string& type,\n                                   std::shared_ptr<EventListener> listener) {\n  return event_listener_map_->Add(type, std::move(listener));\n}\n\nbool EventTarget::RemoveEventListener(const std::string& type,\n                                      std::shared_ptr<EventListener> listener) {\n  return event_listener_map_->Remove(type, std::move(listener));\n}\n\nbool EventTarget::RemoveEventListeners(const std::string& type) {\n  return event_listener_map_->Remove(type);\n}\n\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/event_target.h",
    "content": "/*\n * Copyright (C) 1999 Lars Knoll (knoll@kde.org)\n *           (C) 1999 Antti Koivisto (koivisto@kde.org)\n *           (C) 2001 Dirk Mueller (mueller@kde.org)\n * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.\n * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)\n *           (C) 2007, 2008 Nikolas Zimmermann <zimmermann@kde.org>\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n */\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"core/event/event.h\"\n#include \"core/event/event_listener.h\"\n#include \"core/event/event_listener_map.h\"\n\n#ifndef CORE_EVENT_EVENT_TARGET_H_\n#define CORE_EVENT_EVENT_TARGET_H_\n\nnamespace lynx {\nnamespace event {\n\nclass EventTarget {\n public:\n  enum class EventTargetType {\n    kNone,\n    kElement,\n    kContextProxy,\n  };\n\n  EventTarget();\n  virtual ~EventTarget() = default;\n\n  // Returns the parent EventTarget in the event-target tree.\n  virtual EventTarget* GetParentTarget() = 0;\n  virtual fml::WeakPtr<EventTarget> GetWeakTarget() {\n    return fml::WeakPtr<EventTarget>();\n  }\n\n  virtual DispatchEventResult DispatchEvent(fml::RefPtr<Event>);\n  virtual bool AddEventListener(const std::string& type,\n                                std::shared_ptr<EventListener> listener);\n  virtual bool RemoveEventListener(const std::string& type,\n                                   std::shared_ptr<EventListener> listener);\n  virtual bool RemoveEventListeners(const std::string& type);\n\n  EventListenerMap* GetEventListenerMap() const {\n    return event_listener_map_.get();\n  }\n\n  bool HasEventListener(const std::string& event_type) {\n    return event_listener_map_->Contains(event_type);\n  }\n\n  EventTargetType target_type() { return target_type_; }\n\n  // Check whether catch the event path.\n  virtual bool IsEventPathCatch(EventTarget* target, Event* event) {\n    return false;\n  }\n\n  // Check whether skip the event path.\n  virtual bool IsEventPathSkip(EventTarget* target, Event* event) {\n    return false;\n  }\n\n  // Get the information about target.\n  virtual lepus::Value GetEventTargetInfo(bool is_core_event = false) {\n    return lepus::Value();\n  };\n\n  // Get information used to control event sending, compatible with events\n  // bound through __AddEvent.\n  virtual lepus::Value GetEventControlInfo(bool is_core_event = false) {\n    return lepus::Value();\n  }\n\n  // Handle the trigger-global-event attribute and the global-bind event.\n  virtual void HandleGlobalEvent(fml::RefPtr<Event> event) {}\n  // Get the switch which controls whether compatible multi touch params.\n  virtual bool GetEnableMultiTouchParamsCompatible() { return false; }\n  // Get unit to calculate the position.\n  virtual float GetLayoutsUnitPerPx() { return 1.f; }\n  // Get the unique id for target.\n  virtual std::string GetUniqueID() { return std::to_string(0); }\n\n protected:\n  EventTargetType target_type_{EventTargetType::kNone};\n  std::unique_ptr<EventListenerMap> event_listener_map_;\n};\n\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_EVENT_TARGET_H_\n"
  },
  {
    "path": "core/event/event_target_test.cc",
    "content": "// Copyright 2016 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/event_target_test.h\"\n\n#include \"core/event/custom_event.h\"\n#include \"core/event/event_listener_map.h\"\n#include \"core/event/event_listener_test.h\"\n#include \"core/event/touch_event.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace event {\nnamespace test {\n\nEventTarget* MockEventTarget::GetParentTarget() { return nullptr; }\n\nTEST_F(EventTargetTest, TestEventTargetTest0) {\n  auto map = mock_target_->GetEventListenerMap();\n\n  // init event listener map\n  EXPECT_TRUE(map->IsEmpty());\n  EXPECT_FALSE(map->Contains(\"test\"));\n  auto* vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(vec_ptr, nullptr);\n  EXPECT_FALSE(mock_target_->RemoveEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\")));\n\n  auto mock_event = fml::MakeRefCounted<Event>(\n      \"test\", Event::EventType::kNone, Event::Capture::kNo, Event::Bubbles::kNo,\n      Event::Cancelable::kYes, Event::ComposedMode::kComposed,\n      Event::PhaseType::kNone);\n\n  // add {\"test\": \"1\"}\n  mock_target_->AddEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\"));\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 1);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            1);\n\n  // add {\"test\": \"2\"}\n  mock_target_->AddEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"2\"));\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 2);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"2\");\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            1);\n\n  // add {\"test\": \"3\"}\n  mock_target_->AddEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"3\"));\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 3);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"2\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetContent(),\n            \"3\");\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            3);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            1);\n\n  EXPECT_FALSE(mock_target_->RemoveEventListener(\n      \"test1\", std::make_unique<MockEventListener>(\n                   EventListener::Type::kJSClosureEventListener, \"1\")));\n\n  // remove {\"test\": \"1\"}\n  EXPECT_TRUE(mock_target_->RemoveEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\")));\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 2);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"2\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"3\");\n\n  // clear\n  map->Clear();\n  EXPECT_TRUE(map->IsEmpty());\n  EXPECT_FALSE(map->Contains(\"test\"));\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(vec_ptr, nullptr);\n  EXPECT_FALSE(map->Remove(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\")));\n}\n\nTEST_F(EventTargetTest, TestEventTargetEraseBefore) {\n  auto map = mock_target_->GetEventListenerMap();\n\n  // init event listener map\n  EXPECT_TRUE(map->IsEmpty());\n  EXPECT_FALSE(map->Contains(\"test\"));\n  auto* vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(vec_ptr, nullptr);\n  EXPECT_FALSE(mock_target_->RemoveEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\")));\n\n  auto mock_event = fml::MakeRefCounted<Event>(\n      \"test\", Event::EventType::kNone, Event::Capture::kNo, Event::Bubbles::kNo,\n      Event::Cancelable::kYes, Event::ComposedMode::kComposed,\n      Event::PhaseType::kNone);\n\n  // add {\"test\": \"1\"}\n  mock_target_->AddEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\"));\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 1);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            1);\n\n  // add {\"test\": \"2\"}\n  mock_target_->AddEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"2\", \"test\",\n                  \"1\", mock_target_.get()));\n  mock_target_->AddEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"3\", \"test\",\n                  \"1\", mock_target_.get()));\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 3);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"2\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetContent(),\n            \"3\");\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"2\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"3\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            1);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            1);\n}\n\nTEST_F(EventTargetTest, TestEventTargetEraseCurrent) {\n  auto map = mock_target_->GetEventListenerMap();\n\n  // init event listener map\n  EXPECT_TRUE(map->IsEmpty());\n  EXPECT_FALSE(map->Contains(\"test\"));\n  auto* vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(vec_ptr, nullptr);\n  EXPECT_FALSE(mock_target_->RemoveEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\")));\n\n  auto mock_event = fml::MakeRefCounted<Event>(\n      \"test\", Event::EventType::kNone, Event::Capture::kNo, Event::Bubbles::kNo,\n      Event::Cancelable::kYes, Event::ComposedMode::kComposed,\n      Event::PhaseType::kNone);\n\n  // add {\"test\": \"1\"}\n  mock_target_->AddEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\"));\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 1);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            1);\n\n  // add {\"test\": \"2\"}\n  auto event_l_2 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"2\", \"test\", \"2\",\n      mock_target_.get());\n  mock_target_->AddEventListener(\"test\", event_l_2);\n  auto event_l_3 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"3\", \"test\", \"3\",\n      mock_target_.get());\n  mock_target_->AddEventListener(\"test\", event_l_3);\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 3);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"2\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetContent(),\n            \"3\");\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n\n  EXPECT_EQ(static_cast<MockEventListener*>(event_l_2.get())->GetCount(), 1);\n  EXPECT_EQ(static_cast<MockEventListener*>(event_l_3.get())->GetCount(), 1);\n}\n\nTEST_F(EventTargetTest, TestEventTargetEraseAfter) {\n  auto map = mock_target_->GetEventListenerMap();\n\n  // init event listener map\n  EXPECT_TRUE(map->IsEmpty());\n  EXPECT_FALSE(map->Contains(\"test\"));\n  auto* vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(vec_ptr, nullptr);\n  EXPECT_FALSE(mock_target_->RemoveEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\")));\n\n  auto mock_event = fml::MakeRefCounted<Event>(\n      \"test\", Event::EventType::kNone, Event::Capture::kNo, Event::Bubbles::kNo,\n      Event::Cancelable::kYes, Event::ComposedMode::kComposed,\n      Event::PhaseType::kNone);\n\n  // add {\"test\": \"1\"}\n  mock_target_->AddEventListener(\n      \"test\", std::make_unique<MockEventListener>(\n                  EventListener::Type::kJSClosureEventListener, \"1\"));\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 1);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            1);\n\n  // add {\"test\": \"2\"}\n  auto event_l_2 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"2\", \"test\", \"3\",\n      mock_target_.get());\n  mock_target_->AddEventListener(\"test\", event_l_2);\n  auto event_l_3 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"3\", \"test\", \"0\",\n      mock_target_.get());\n  mock_target_->AddEventListener(\"test\", event_l_3);\n  EXPECT_FALSE(map->IsEmpty());\n  vec_ptr = map->Find(\"test\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 3);\n  EXPECT_TRUE(map->Contains(\"test\"));\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetContent(),\n            \"2\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetContent(),\n            \"3\");\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetContent(),\n            \"1\");\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n\n  EXPECT_EQ(static_cast<MockEventListener*>(event_l_2.get())->GetCount(), 1);\n  EXPECT_EQ(static_cast<MockEventListener*>(event_l_3.get())->GetCount(), 0);\n}\n\nTEST_F(EventTargetTest, TestEventTargetDispatchCaptureBubbleEvent) {\n  auto map = mock_target_->GetEventListenerMap();\n  auto mock_event = fml::MakeRefCounted<TouchEvent>(\"tap\");\n\n  // add {\"tap\": \"1\"} with capture flag\n  auto event_options_1 = event::EventListener::Options(\n      true /* capture */, false, false, false, false, false);\n  auto event_l_1 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"1\", \"tap\", \"0\",\n      mock_target_.get(), event_options_1);\n  mock_target_->AddEventListener(\"tap\", event_l_1);\n\n  // add {\"tap\": \"2\"} with bubble flag\n  auto event_options_2 = event::EventListener::Options(\n      false /* bubble */, false, false, false, false, false);\n  auto event_l_2 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"2\", \"tap\", \"0\",\n      mock_target_.get(), event_options_2);\n  mock_target_->AddEventListener(\"tap\", event_l_2);\n\n  // add {\"tap\": \"3\"} with global flag\n  auto event_options_3 = event::EventListener::Options(\n      false, false, false, false, false, true /* global */);\n  auto event_l_3 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"3\", \"tap\", \"0\",\n      mock_target_.get(), event_options_3);\n  mock_target_->AddEventListener(\"tap\", event_l_3);\n\n  EXPECT_FALSE(map->IsEmpty());\n  auto vec_ptr = map->Find(\"tap\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 3);\n\n  mock_event->set_event_phase(Event::PhaseType::kCapturingPhase);\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            1);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            0);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            0);\n\n  mock_event->set_event_phase(Event::PhaseType::kAtTarget);\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            1);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            0);\n\n  mock_event->set_event_phase(Event::PhaseType::kBubblingPhase);\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            0);\n\n  mock_event->set_event_phase(Event::PhaseType::kGlobal);\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            1);\n}\n\nTEST_F(EventTargetTest, TestEventTargetDispatchNoCaptureBubbleEvent) {\n  auto map = mock_target_->GetEventListenerMap();\n  auto mock_event =\n      fml::MakeRefCounted<CustomEvent>(\"custom\", lepus::Value(), \"detail\");\n\n  // add {\"custom\": \"1\"} with capture flag\n  auto event_options_1 = event::EventListener::Options(\n      true /* capture */, false, false, false, false, false);\n  auto event_l_1 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"1\", \"custom\", \"0\",\n      mock_target_.get(), event_options_1);\n  mock_target_->AddEventListener(\"custom\", event_l_1);\n\n  // add {\"custom\": \"2\"} with bubble flag\n  auto event_options_2 = event::EventListener::Options(\n      false /* bubble */, false, false, false, false, false);\n  auto event_l_2 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"2\", \"custom\", \"0\",\n      mock_target_.get(), event_options_2);\n  mock_target_->AddEventListener(\"custom\", event_l_2);\n\n  // add {\"custom\": \"3\"} with global flag\n  auto event_options_3 = event::EventListener::Options(\n      false, false, false, false, false, true /* global */);\n  auto event_l_3 = std::make_shared<MockEventListener>(\n      EventListener::Type::kJSClosureEventListener, \"3\", \"custom\", \"0\",\n      mock_target_.get(), event_options_3);\n  mock_target_->AddEventListener(\"custom\", event_l_3);\n\n  EXPECT_FALSE(map->IsEmpty());\n  auto vec_ptr = map->Find(\"custom\");\n  EXPECT_EQ(static_cast<int32_t>(vec_ptr->size()), 3);\n\n  mock_event->set_capture(true);\n  mock_event->set_bubbles(true);\n  mock_event->set_event_phase(Event::PhaseType::kCapturingPhase);\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            1);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            0);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            0);\n\n  mock_event->set_event_phase(Event::PhaseType::kAtTarget);\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            1);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            0);\n\n  mock_event->set_event_phase(Event::PhaseType::kBubblingPhase);\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            0);\n\n  mock_event->set_event_phase(Event::PhaseType::kGlobal);\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            1);\n\n  mock_event->set_capture(false);\n  mock_event->set_event_phase(Event::PhaseType::kAtTarget);\n  mock_target_->DispatchEvent(mock_event);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[0].get())->GetCount(),\n            2);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[1].get())->GetCount(),\n            3);\n  EXPECT_EQ(static_cast<MockEventListener*>((*vec_ptr)[2].get())->GetCount(),\n            1);\n}\n\n}  // namespace test\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/event_target_test.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"core/event/event_target.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\n#ifndef CORE_EVENT_EVENT_TARGET_TEST_H_\n#define CORE_EVENT_EVENT_TARGET_TEST_H_\n\nnamespace lynx {\nnamespace event {\nnamespace test {\n\nclass MockEventTarget : public EventTarget {\n public:\n  MockEventTarget() = default;\n  virtual ~MockEventTarget() override = default;\n\n  virtual EventTarget* GetParentTarget() override;\n};\n\nclass EventTargetTest : public ::testing::Test {\n public:\n  EventTargetTest() = default;\n  ~EventTargetTest() override = default;\n\n  void SetUp() override { mock_target_ = std::make_unique<MockEventTarget>(); }\n\n  void TearDown() override {}\n\n  std::unique_ptr<MockEventTarget> mock_target_;\n};\n\n}  // namespace test\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_EVENT_TARGET_TEST_H_\n"
  },
  {
    "path": "core/event/keyboard_event.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/keyboard_event.h\"\n\nnamespace lynx {\nnamespace event {\n\nKeyboardEvent::KeyboardEvent(const std::string& event_name,\n                             const std::string& key_code)\n    : Event(event_name, Event::EventType::kKeyboardEvent, Event::Capture::kYes,\n            Event::Bubbles::kYes, Event::Cancelable::kNo,\n            Event::ComposedMode::kComposed) {\n  key_code_ = key_code;\n}\n\nKeyboardEvent::~KeyboardEvent() = default;\n\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/keyboard_event.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_EVENT_KEYBOARD_EVENT_H_\n#define CORE_EVENT_KEYBOARD_EVENT_H_\n\n#include <string>\n\n#include \"core/event/event.h\"\n\nnamespace lynx {\nnamespace event {\n\nclass KeyboardEvent : public Event {\n public:\n  KeyboardEvent(const std::string& event_name, const std::string& key_code);\n  ~KeyboardEvent();\n\n  const std::string& key_code() const { return key_code_; }\n\n private:\n  std::string key_code_;\n};\n\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_KEYBOARD_EVENT_H_\n"
  },
  {
    "path": "core/event/touch_event.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/event/touch_event.h\"\n\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"core/event/event_target.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace event {\n\nstatic int64_t g_unique_touch_event_id_ = 0;\nstatic int64_t GetNextUniqueTouchEventID() {\n  return g_unique_touch_event_id_++;\n}\n\nbool TouchEvent::long_press_consumed_ = false;\n\nTouchEvent::TouchEvent(const std::string& event_name, float x, float y,\n                       float page_x, float page_y, float client_x,\n                       float client_y, int64_t time_stamp)\n    : Event(event_name, time_stamp, EventType::kTouchEvent, Capture::kYes,\n            Bubbles::kYes, Cancelable::kYes, ComposedMode::kScoped,\n            PhaseType::kNone),\n      x_(x),\n      y_(y),\n      page_x_(page_x),\n      page_y_(page_y),\n      client_x_(client_x),\n      client_y_(client_y),\n      identifier_(GetNextUniqueTouchEventID()) {\n  event_type_ = EventType::kTouchEvent;\n}\n\nTouchEvent::TouchEvent(const std::string& event_name,\n                       const lepus::Value& targets_touches, int64_t time_stamp)\n    : Event(event_name, time_stamp, EventType::kTouchEvent, Capture::kYes,\n            Bubbles::kYes, Cancelable::kYes, ComposedMode::kScoped,\n            PhaseType::kNone),\n      is_multi_touch_(true),\n      targets_touches_(targets_touches) {\n  event_type_ = EventType::kTouchEvent;\n}\n\nvoid TouchEvent::HandleEventCustomDetail() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, TOUCH_EVENT_CUSTOM_DETAIL, \"name\", type_);\n  if (!target_) {\n    return;\n  }\n  BASE_STATIC_STRING_DECL(kX, \"x\");\n  BASE_STATIC_STRING_DECL(kY, \"y\");\n  BASE_STATIC_STRING_DECL(kPageX, \"pageX\");\n  BASE_STATIC_STRING_DECL(kPageY, \"pageY\");\n  BASE_STATIC_STRING_DECL(kClientX, \"clientX\");\n  BASE_STATIC_STRING_DECL(kClientY, \"clientY\");\n  BASE_STATIC_STRING_DECL(kIdentifier, \"identifier\");\n  BASE_STATIC_STRING_DECL(kDetail, \"detail\");\n  BASE_STATIC_STRING_DECL(kTouches, \"touches\");\n  BASE_STATIC_STRING_DECL(kChangedTouches, \"changedTouches\");\n\n  auto dict = detail_.Table();\n  float layouts_unit_per_px = target_->GetLayoutsUnitPerPx();\n  auto detail = lepus::Dictionary::Create();\n\n  if (is_multi_touch_) {\n    bool enable_multi_touch_params_compatible =\n        target_->GetEnableMultiTouchParamsCompatible();\n\n    if (type_ == EVENT_TOUCH_CANCEL) {\n      if (enable_multi_touch_params_compatible) {\n        dict.get()->SetValue(kChangedTouches,\n                             lepus::Value::Clone(GetCurrentTouches()));\n        dict.get()->SetValue(kTouches,\n                             lepus::Value::Clone(GetCurrentTouches()));\n      } else {\n        dict.get()->SetValue(kChangedTouches,\n                             lepus::Value::Clone(GetCurrentTouches()));\n        dict.get()->SetValue(kTouches, lepus::CArray::Create());\n      }\n      return;\n    }\n\n    float detail_x = 0.f, detail_y = 0.f;\n    auto changed_touches = lepus::CArray::Create();\n    auto touches_with_deleted = lepus::Value::Clone(GetCurrentTouches());\n    for (const auto& target_touches : *(targets_touches_.Table())) {\n      if (target_touches.second.IsArray()) {\n        // touches: includes all touches on the same target.\n        auto touches = target_touches.second.Array();\n        for (size_t i = 0; i < touches->size(); ++i) {\n          const auto& touch_info = touches->get(i).Array();\n\n          int64_t identifier =\n              static_cast<int64_t>(touch_info->get(0).Number());\n          float client_x = touch_info->get(1).Number();\n          float client_y = touch_info->get(2).Number();\n          float page_x = touch_info->get(3).Number();\n          float page_y = touch_info->get(4).Number();\n          float x = touch_info->get(5).Number();\n          float y = touch_info->get(6).Number();\n\n          if (identifier == 0) {\n            detail_x = page_x / layouts_unit_per_px;\n            detail_y = page_y / layouts_unit_per_px;\n          }\n\n          auto touch = lepus::Dictionary::Create();\n          touch->SetValue(kPageX, page_x / layouts_unit_per_px);\n          touch->SetValue(kPageY, page_y / layouts_unit_per_px);\n          touch->SetValue(kClientX, client_x / layouts_unit_per_px);\n          touch->SetValue(kClientY, client_y / layouts_unit_per_px);\n          touch->SetValue(kX, x / layouts_unit_per_px);\n          touch->SetValue(kY, y / layouts_unit_per_px);\n          touch->SetValue(kIdentifier, identifier);\n\n          auto touch_value = lepus_value(std::move(touch));\n          changed_touches->push_back(touch_value);\n\n          // Find whether this touch's identifier is in current_touches_. If\n          // it isn't in current_touches_, then insert it, or modify or delete\n          // it\n          bool in_current_touches = false;\n          const auto& current_touches = GetCurrentTouches().Array();\n          for (size_t j = 0; j < current_touches->size(); ++j) {\n            if (current_touches->get(j)\n                    .Table()\n                    ->GetValue(kIdentifier)\n                    .Number() == identifier) {\n              in_current_touches = true;\n              // delete this touch when touchend\n              if (type_ == EVENT_TOUCH_END) {\n                current_touches->Erase(static_cast<uint32_t>(j));\n                break;\n              }\n              // modify this touch when touchmove\n              current_touches->set(j, touch_value);\n              break;\n            }\n          }\n          // current_touches_ doesn't include this touch, add it into\n          // current_touches_\n          if (type_ == EVENT_TOUCH_START && !in_current_touches) {\n            current_touches->emplace_back(std::move(touch_value));\n          }\n        }\n      }\n    }\n\n    detail.get()->SetValue(kX, detail_x);\n    detail.get()->SetValue(kY, detail_y);\n    dict.get()->SetValue(kDetail, std::move(detail));\n    dict.get()->SetValue(kChangedTouches, std::move(changed_touches));\n\n    // When the event type is touchend, the touches parameter needs to be\n    // compatible.\n    if (enable_multi_touch_params_compatible && type_ == EVENT_TOUCH_END) {\n      dict.get()->SetValue(kTouches, std::move(touches_with_deleted));\n    } else {\n      dict.get()->SetValue(kTouches, lepus::Value::Clone(GetCurrentTouches()));\n    }\n  } else {\n    detail.get()->SetValue(kX, page_x_ / layouts_unit_per_px);\n    detail.get()->SetValue(kY, page_y_ / layouts_unit_per_px);\n    dict.get()->SetValue(kDetail, std::move(detail));\n\n    auto touch = lepus::Dictionary::Create();\n    int64_t identifier = reinterpret_cast<int64_t>(&touch);\n    touch.get()->SetValue(kIdentifier, identifier);\n    touch.get()->SetValue(kX, x_ / layouts_unit_per_px);\n    touch.get()->SetValue(kY, y_ / layouts_unit_per_px);\n    touch.get()->SetValue(kPageX, page_x_ / layouts_unit_per_px);\n    touch.get()->SetValue(kPageY, page_y_ / layouts_unit_per_px);\n    touch.get()->SetValue(kClientX, client_x_ / layouts_unit_per_px);\n    touch.get()->SetValue(kClientY, client_y_ / layouts_unit_per_px);\n\n    auto touch_value = lepus_value(std::move(touch));\n    auto touches = lepus::CArray::Create();\n    touches.get()->push_back(touch_value);\n    dict.get()->SetValue(kTouches, std::move(touches));\n\n    auto changed_touches = lepus::CArray::Create();\n    changed_touches.get()->emplace_back(std::move(touch_value));\n    dict.get()->SetValue(kChangedTouches, std::move(changed_touches));\n  }\n}\n\nbool TouchEvent::HandleEventConflictAndParam() {\n  // TODO(hexionghui): Check whether it is the first finger.\n  if (type_ == EVENT_TOUCH_START) {\n    TouchEvent::long_press_consumed_ = false;\n  }\n  if (TouchEvent::long_press_consumed_ && type_ == EVENT_TAP) {\n    return true;\n  }\n  if (type_ == EVENT_TOUCH_CANCEL) {\n    GetCurrentTouches() = lepus::Value(lepus::CArray::Create());\n  }\n  return false;\n}\n\nlepus::Value& TouchEvent::GetCurrentTouches() {\n  // Save the finger information on the screen before the event is distributed.\n  static lepus::Value current_touches = lepus::Value(lepus::CArray::Create());\n  return current_touches;\n}\n\n}  // namespace event\n}  // namespace lynx\n"
  },
  {
    "path": "core/event/touch_event.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_EVENT_TOUCH_EVENT_H_\n#define CORE_EVENT_TOUCH_EVENT_H_\n\n#include <string>\n\n#include \"core/event/event.h\"\n\n#define EVENT_TOUCH_START \"touchstart\"\n#define EVENT_TOUCH_MOVE \"touchmove\"\n#define EVENT_TOUCH_END \"touchend\"\n#define EVENT_TOUCH_CANCEL \"touchcancel\"\n#define EVENT_TAP \"tap\"\n#define EVENT_LONG_PRESS \"longpress\"\n\nnamespace lynx {\nnamespace event {\n\nclass TouchEvent : public Event {\n public:\n  // Indicates whether a long press event is triggered and consumed.\n  static bool long_press_consumed_;\n\n  TouchEvent(const std::string& event_name, float x = 0, float y = 0,\n             float page_x = 0, float page_y = 0, float client_x = 0,\n             float client_y = 0, int64_t time_stamp = 0);\n  TouchEvent(const std::string& event_name, const lepus::Value& targets_touches,\n             int64_t time_stamp = 0);\n\n  float x() const { return x_; }\n  float y() const { return y_; }\n  float page_x() const { return page_x_; }\n  float page_y() const { return page_y_; }\n  float client_x() const { return client_x_; }\n  float client_y() const { return client_y_; }\n  int64_t identifier() const { return identifier_; }\n  bool is_multi_touch() const { return is_multi_touch_; }\n\n  void HandleEventCustomDetail() override;\n\n  bool HandleEventConflictAndParam() override;\n\n  lepus::Value& GetCurrentTouches();\n\n private:\n  float x_;\n  float y_;\n  float page_x_;\n  float page_y_;\n  float client_x_;\n  float client_y_;\n  int64_t identifier_;\n  // Indicates whether the touch event contains multi-finger information.\n  bool is_multi_touch_{false};\n  // Save the touched object and its touch information.\n  lepus::Value targets_touches_{};\n};\n\n}  // namespace event\n}  // namespace lynx\n\n#endif  // CORE_EVENT_TOUCH_EVENT_H_\n"
  },
  {
    "path": "core/include/AGENTS.md",
    "content": "# AGENTS.md\n\n## Scope\n\nThis directory contains exported standalone headers for `starlight_standalone`.\n\n## Module Map\n\n- `starlight_standalone/starlight.h`: umbrella include for standalone consumers.\n- `starlight_standalone/starlight_config.h`: standalone configuration-facing definitions.\n- `starlight_standalone/starlight_enums.h`: exported enum surface used by standalone callers.\n- `starlight_standalone/starlight_standalone.h`: higher-level standalone entry header.\n- `starlight_standalone/starlight_value.h`: exported value/container-facing standalone types.\n\n## Key Files And Types\n\n- These headers are exported surface, not internal convenience wrappers. Changing one can affect downstream standalone consumers that are not otherwise visible from `lynx/core`.\n- `starlight.h` and `starlight_standalone.h` are aggregation points. Dependency creep or include-order surprises often surface here first.\n\n## Typical Change Patterns\n\n- If a change is about standalone-facing type shape, config names, or exported enums, this directory is the right surface.\n- If a change only matters to one implementation module, prefer editing that module and keep these headers as thin contracts.\n\n## Edit Rules\n\n- Treat these headers as public include surface. Keep them stable and dependency-light.\n- Header changes here can affect external or standalone consumers even when internal core code still compiles.\n- If a change is implementation-specific rather than interface-specific, prefer editing the owning standalone implementation module or other source directories instead.\n\n## Common Regression Symptoms\n\n- Standalone builds compile differently from in-tree core builds after a header-only change here.\n- Enums or value types still compile but downstream callers interpret them differently because the exported contract drifted.\n\n## Validate\n\nThis directory does not define standalone unit tests. Validate through the owning standalone/starlight consumers when header contracts change.\n\n## Notes\n\n- Favor additive edits and dependency minimization here. This directory is a contract surface, not an implementation layer.\n"
  },
  {
    "path": "core/include/starlight_standalone/starlight.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_H_\n#define CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_H_\n\n#include <stdint.h>\n\n#include \"starlight_config.h\"\n#include \"starlight_value.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// a wrapper of LayoutObject.\nstruct StarlightNode;\ntypedef struct StarlightNode* SLNodeRef;\nstruct StarlightSize;\nstruct StarlightConfig;\n\n/**\n *  1. When `widthMode` or `heightMode` is `SLNodeMeasureModeUndefined`:\n *  The corresponding `width` or `height` value is meaningless, as if setting\n * `width: max-content` or `height: max-content` in Web.\n *\n *  2. When `widthMode` or `heightMode` is `SLNodeMeasureModeExactly`:\n *  The `width` or `height` value specifies the exact size constraint (content\n * bounds) for the node.\n *\n *  3. When `widthMode` or `heightMode` is `SLNodeMeasureModeAtMost`:\n *  The `width` or `height` value specifies the maximum allowable size (content\n * bounds) for the node.\n *\n *  The `width`, `width_mode`, `height`, and `height_mode` parameters\n *  respectively define the width and height constraints (content bounds) of the\n *  node. This function is only required to return the content size of\n *  the node, excluding margins, borders, and padding.\n */\ntypedef StarlightSize (*StarlightMeasureFunc)(void* manager_node, float width,\n                                              SLNodeMeasureMode width_mode,\n                                              float height,\n                                              SLNodeMeasureMode height_mode);\n\ntypedef float (*StarlightBaselineFunc)(void* manager_node, float width,\n                                       float height);\n\ntypedef struct StarlightMeasureDelegate {\n  // measure_func_ is used to measure the node.\n  StarlightMeasureFunc measure_func_;\n  // When baseline_func_ is set, it will be called to get the distance from the\n  // top edge of the content box to the baseline of the node.\n  StarlightBaselineFunc baseline_func_;\n  // When the measure_func_ or baseline_func_ need to access the manager_node_,\n  // set the manager_node_.\n  void* manager_node_;\n} StarlightMeasureDelegate;\n\nSLNodeRef SLNodeNew();\n// User need to manage the memory of config through calling `SLConfigFree` when\n// not using.\nSLNodeRef SLNodeNewWithConfig(StarlightConfig* config);\n\nvoid SLNodeInsertChild(const SLNodeRef parent, const SLNodeRef child,\n                       int32_t index);\n// Recommended to use this function rather than SLNodeInsertChild.\nvoid SLNodeInsertChildBefore(const SLNodeRef parent, const SLNodeRef child,\n                             const SLNodeRef reference);\nvoid SLNodeRemoveChild(const SLNodeRef parent, const SLNodeRef child);\nvoid SLNodeRemoveAllChildren(const SLNodeRef parent);\n// Nearly resets all of the node, including removing children, reseting layout\n// results, reseting CSS styles, and so on.\nvoid SLNodeReset(const SLNodeRef node);\nSLNodeRef SLNodeGetChild(const SLNodeRef node, int32_t index);\nint32_t SLNodeGetChildCount(const SLNodeRef node);\nSLNodeRef SLNodeGetParent(const SLNodeRef node);\n\nvoid SLNodeFree(const SLNodeRef node);\nvoid SLNodeFreeRecursive(const SLNodeRef node);\n\nbool SLNodeIsDirty(const SLNodeRef node);\nvoid SLNodeMarkDirty(const SLNodeRef node);\n\n// TODO(yuanzhiwen): currently unavailable\n// bool SLNodeGetHasNewLayout(const SLNodeRef node);\n// void SLNodeSetHasNewLayout(const SLNodeRef node, bool has_new_layout);\n\nbool SLNodeIsRTL(const SLNodeRef node);\n\n// owner_width, owner_height define the containing block for the root node, and\n// the percentage for width, height, max-width, max-height, padding, margin will\n// refer to this containing block. If width, height are not set, the root node's\n// outer box (margin box) will be limited by the owner_width and owner_height.\nvoid SLNodeCalculateLayout(const SLNodeRef node, float owner_width,\n                           float owner_height, SLDirection owner_direction);\n\n/**\n * @brief Set the measurement delegate of a node.\n *\n * @param delegate The measurement delegate object.\n *\n * This function sets the measurement delegate of a node. The measurement\n * delegate object should inject functions to measure the node:\n *   - StarlightMeasureFunc measure_func_ (optional): for measuring the node.\n *   - StarlightBaselineFunc baseline_func_ (optional): for calculating the\nbaseline of the node.\n *   - void* manager_node_ (optional): the manager node of the StarlightNode, it\n * usually holds the StarlightNode.\n *\n * Users need to manage the lifecycle of the measurement delegate object\n * themselves to ensure that it is released before the node is destroyed.\n */\nvoid SLNodeSetMeasureDelegate(const SLNodeRef node,\n                              StarlightMeasureDelegate* const delegate);\nStarlightMeasureDelegate* SLNodeGetMeasureDelegate(const SLNodeRef node);\nbool SLNodeHasMeasureFunc(const SLNodeRef node);\n\n// Styles\nvoid SLNodeStyleSetDirection(const SLNodeRef node, SLDirection type);\nvoid SLNodeStyleSetFlexDirection(const SLNodeRef node, SLFlexDirection value);\n\n// alignment\nvoid SLNodeStyleSetJustifyContent(const SLNodeRef node, SLJustifyContent value);\nvoid SLNodeStyleSetAlignContent(const SLNodeRef node, SLAlignContent value);\nvoid SLNodeStyleSetAlignItems(const SLNodeRef node, SLFlexAlign value);\nvoid SLNodeStyleSetAlignSelf(const SLNodeRef node, SLFlexAlign value);\n\n// only supports `absoulte` and `relative`. Defaults to `relative`.\nvoid SLNodeStyleSetPositionType(const SLNodeRef node, SLPositionType value);\n\n// Defaults to `no-wrap`.\nvoid SLNodeStyleSetFlexWrap(const SLNodeRef node, SLFlexWrap value);\n\n// Defaults to `flex`.\nvoid SLNodeStyleSetDisplay(const SLNodeRef node, SLDisplay value);\n\n// Defaults to `border-box`. Different from Web (`content-box`).\nvoid SLNodeStyleSetBoxSizing(const SLNodeRef node, SLBoxSizing value);\n\n// Defaults to undefined.\nvoid SLNodeStyleSetAspectRatio(const SLNodeRef node, float value);\n\n// Defaults to 0.\nvoid SLNodeStyleSetOrder(const SLNodeRef node, int32_t value);\n\n// flex: 1 is equivalent to flex-grow: 1, flex-shrink: 1, flex-basis: 0 (differs\n// from 0% in Web).\nvoid SLNodeStyleSetFlex(const SLNodeRef node, float value);\n// Defaults to 0.\nvoid SLNodeStyleSetFlexGrow(const SLNodeRef node, float value);\n// Defaults to 1.\nvoid SLNodeStyleSetFlexShrink(const SLNodeRef node, float value);\n// flex-basis: defaults to `auto`.\nvoid SLNodeStyleSetFlexBasis(const SLNodeRef node, float value);\nvoid SLNodeStyleSetFlexBasisPercent(const SLNodeRef node, float value);\nvoid SLNodeStyleSetFlexBasisAuto(const SLNodeRef node);\n\n// top, bottom, left, right: defaults to `auto`.\nvoid SLNodeStyleSetPosition(const SLNodeRef node, SLEdge edge, float position);\nvoid SLNodeStyleSetPositionPercent(const SLNodeRef node, SLEdge edge,\n                                   float position);\nvoid SLNodeStyleSetPositionAuto(const SLNodeRef node, SLEdge edge);\n\n// margin\n// Defaults to 0.\nvoid SLNodeStyleSetMargin(const SLNodeRef node, SLEdge edge, float value);\nvoid SLNodeStyleSetMarginPercent(const SLNodeRef node, SLEdge edge,\n                                 float value);\nvoid SLNodeStyleSetMarginAuto(const SLNodeRef node, SLEdge edge);\n\n// padding\n// Defaults to 0.\nvoid SLNodeStyleSetPadding(const SLNodeRef node, SLEdge edge, float value);\nvoid SLNodeStyleSetPaddingPercent(const SLNodeRef node, SLEdge edge,\n                                  float value);\n\n// border width\n// Defaults to 0.\nvoid SLNodeStyleSetBorder(const SLNodeRef node, SLEdge edge, float value);\n\n// gap\n// Defaults to 0.\nvoid SLNodeStyleSetGap(const SLNodeRef node, SLGap gutter, float value);\nvoid SLNodeStyleSetGapPercent(const SLNodeRef node, SLGap gutter, float value);\n\n// width properties\n// width: defaults to `auto`.\nvoid SLNodeStyleSetWidth(const SLNodeRef node, float value);\nvoid SLNodeStyleSetWidthPercent(const SLNodeRef node, float value);\nvoid SLNodeStyleSetWidthAuto(const SLNodeRef node);\nvoid SLNodeStyleSetWidthMaxContent(const SLNodeRef node);\nvoid SLNodeStyleSetWidthFitContent(const SLNodeRef node);\n// min-width: defaults to `0`.\nvoid SLNodeStyleSetMinWidth(const SLNodeRef node, float value);\nvoid SLNodeStyleSetMinWidthPercent(const SLNodeRef node, float value);\n// max-width: defaults to no limit.\nvoid SLNodeStyleSetMaxWidth(const SLNodeRef node, float value);\nvoid SLNodeStyleSetMaxWidthPercent(const SLNodeRef node, float value);\n\n// height properties\n// height: defaults to `auto`.\nvoid SLNodeStyleSetHeight(const SLNodeRef node, float value);\nvoid SLNodeStyleSetHeightPercent(const SLNodeRef node, float value);\nvoid SLNodeStyleSetHeightAuto(const SLNodeRef node);\nvoid SLNodeStyleSetHeightMaxContent(const SLNodeRef node);\nvoid SLNodeStyleSetHeightFitContent(const SLNodeRef node);\n// min-height: defaults to `0`.\nvoid SLNodeStyleSetMinHeight(const SLNodeRef node, float value);\nvoid SLNodeStyleSetMinHeightPercent(const SLNodeRef node, float value);\n// max-height: defaults to no limit.\nvoid SLNodeStyleSetMaxHeight(const SLNodeRef node, float value);\nvoid SLNodeStyleSetMaxHeightPercent(const SLNodeRef node, float value);\n\n// get style\nSLFlexDirection SLNodeStyleGetFlexDirection(const SLNodeRef node);\n// When setting to SLJustifyContentStart/SLJustifyContentEnd, we will get\n// SLJustifyContentFlexStart/SLJustifyContentFlexEnd.\nSLJustifyContent SLNodeStyleGetJustifyContent(const SLNodeRef node);\nSLAlignContent SLNodeStyleGetAlignContent(const SLNodeRef node);\nSLFlexAlign SLNodeStyleGetAlignItems(const SLNodeRef node);\nSLFlexAlign SLNodeStyleGetAlignSelf(const SLNodeRef node);\nSLPositionType SLNodeStyleGetPositionType(const SLNodeRef node);\nSLFlexWrap SLNodeStyleGetFlexWrap(const SLNodeRef node);\nSLDisplay SLNodeStyleGetDisplay(const SLNodeRef node);\nSLBoxSizing SLNodeStyleGetBoxSizing(const SLNodeRef node);\nfloat SLNodeStyleGetAspectRatio(const SLNodeRef node);\nint32_t SLNodeStyleGetOrder(const SLNodeRef node);\nfloat SLNodeStyleGetFlexGrow(const SLNodeRef node);\nfloat SLNodeStyleGetFlexShrink(const SLNodeRef node);\nfloat SLNodeStyleGetBorder(const SLNodeRef node, SLEdge edge);\nStarlightValue SLNodeStyleGetFlexBasis(const SLNodeRef node);\nStarlightValue SLNodeStyleGetPosition(const SLNodeRef node, SLEdge edge);\nStarlightValue SLNodeStyleGetMargin(const SLNodeRef node, SLEdge edge);\nStarlightValue SLNodeStyleGetPadding(const SLNodeRef node, SLEdge edge);\nStarlightValue SLNodeStyleGetGap(const SLNodeRef node, SLGap gutter);\nStarlightValue SLNodeStyleGetWidth(const SLNodeRef node);\nStarlightValue SLNodeStyleGetHeight(const SLNodeRef node);\nStarlightValue SLNodeStyleGetMinWidth(const SLNodeRef node);\nStarlightValue SLNodeStyleGetMaxWidth(const SLNodeRef node);\nStarlightValue SLNodeStyleGetMinHeight(const SLNodeRef node);\nStarlightValue SLNodeStyleGetMaxHeight(const SLNodeRef node);\n\n// layout result\n// the distance from the upper left corner of the current node's border box to\n// the left edge of the parent node's border box.\nfloat SLNodeLayoutGetLeft(const SLNodeRef node);\n// the distance from the upper left corner of the current node's border box to\n// the top edge of the parent node's border box.\nfloat SLNodeLayoutGetTop(const SLNodeRef node);\n\n// TODO(yuanzhiwen): offset_right, and offset_bottom\n// float SLNodeLayoutGetRight(const SLNodeRef node);\n// float SLNodeLayoutGetBottom(const SLNodeRef node);\n\n// return the width and height of border box.\nfloat SLNodeLayoutGetWidth(const SLNodeRef node);\nfloat SLNodeLayoutGetHeight(const SLNodeRef node);\nfloat SLNodeLayoutGetMargin(const SLNodeRef node, SLEdge edge);\nfloat SLNodeLayoutGetPadding(const SLNodeRef node, SLEdge edge);\nfloat SLNodeLayoutGetBorder(const SLNodeRef node, SLEdge edge);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  // CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_H_\n"
  },
  {
    "path": "core/include/starlight_standalone/starlight_config.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_CONFIG_H_\n#define CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_CONFIG_H_\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct StarlightConfig StarlightConfig;\n\nStarlightConfig* SLConfigCreate();\n\nvoid SLConfigSetPhysicalPixelsPerLayoutUnit(\n    StarlightConfig* const config, float physical_pixels_per_layout_unit);\n\nvoid SLConfigFree(StarlightConfig* const config);\n\nfloat SLConfigGetPhysicalPixelsPerLayoutUnit(StarlightConfig* const config);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  // CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_CONFIG_H_\n"
  },
  {
    "path": "core/include/starlight_standalone/starlight_enums.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_ENUMS_H_\n#define CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_ENUMS_H_\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef enum SLDisplay {\n  SLDisplayNone = 0,\n  // default value\n  SLDisplayFlex = 1,\n} SLDisplay;\n\n// for align-self: auto is default value\n// for align-items: stretch is default value\ntypedef enum SLFlexAlign {\n  // auto is not supported in align-items\n  SLFlexAlignAuto = 0,\n  SLFlexAlignStretch = 1,\n  SLFlexAlignFlexStart = 2,\n  SLFlexAlignFlexEnd = 3,\n  SLFlexAlignCenter = 4,\n  SLFlexAlignBaseline = 5,\n  SLFlexAlignStart = 6,\n  SLFlexAlignEnd = 7,\n} SLFlexAlign;\n\n// stretch is default value.\ntypedef enum SLAlignContent {\n  SLAlignContentFlexStart = 0,\n  SLAlignContentFlexEnd = 1,\n  SLAlignContentCenter = 2,\n  SLAlignContentStretch = 3,\n  SLAlignContentSpaceBetween = 4,\n  SLAlignContentSpaceAround = 5,\n} SLAlignContent;\n\ntypedef enum SLJustifyContent {\n  SLJustifyContentFlexStart = 0,\n  SLJustifyContentCenter = 1,\n  SLJustifyContentFlexEnd = 2,\n  SLJustifyContentSpaceBetween = 3,\n  SLJustifyContentSpaceAround = 4,\n  SLJustifyContentSpaceEvenly = 5,\n  SLJustifyContentStretch = 6,\n  SLJustifyContentStart = 7,\n  SLJustifyContentEnd = 8,\n} SLJustifyContent;\n\ntypedef enum SLFlexDirection {\n  SLFlexDirectionColumn = 0,\n  SLFlexDirectionRow = 1,\n  SLFlexDirectionRowReverse = 2,\n  SLFlexDirectionColumnReverse = 3,\n} SLFlexDirection;\n\ntypedef enum SLFlexWrap {\n  SLFlexWrapWrap = 0,\n  SLFlexWrapNowrap = 1,\n  SLFlexWrapWrapReverse = 2,\n} SLFlexWrap;\n\ntypedef enum SLDirection {\n  SLDirectionRTL = 2,\n  SLDirectionLTR = 3,\n} SLDirection;\n\ntypedef enum SLPositionType {\n  SLPositionTypeAbsolute = 0,\n  SLPositionTypeRelative = 1,\n} SLPositionType;\n\ntypedef enum SLBoxSizing {\n  SLBoxSizingBorderBox = 0,\n  SLBoxSizingContentBox = 1,\n} SLBoxSizing;\n\ntypedef enum SLEdge {\n  SLEdgeLeft = 0,\n  SLEdgeRight = 1,\n  SLEdgeTop = 2,\n  SLEdgeBottom = 3,\n  SLEdgeStart = 4,\n  SLEdgeEnd = 5,\n  SLEdgeHorizontal = 6,\n  SLEdgeVertical = 7,\n  SLEdgeAll = 8,\n} SLEdge;\n\ntypedef enum SLGap { SLGapColumn = 0, SLGapRow = 1, SLGapAll = 2 } SLGap;\n\ntypedef enum SLDimension {\n  SLHorizontal = 0,\n  SLVertical = 1,\n  SLDimensionCount = 2\n} SLDimension;\n\ntypedef enum SLNodeMeasureMode {\n  SLNodeMeasureModeUndefined = 0,\n  SLNodeMeasureModeExactly = 1,\n  SLNodeMeasureModeAtMost = 2,\n} SLNodeMeasureMode;\n\ntypedef enum SLUnit {\n  SLUnitPoint = 0,\n  SLUnitPercent = 1,\n  SLUnitAuto = 2,\n  SLUnitMaxContent = 3,\n  SLUnitFitContent = 4,\n} SLUnit;\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  // CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_ENUMS_H_\n"
  },
  {
    "path": "core/include/starlight_standalone/starlight_standalone.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_STANDALONE_H_\n#define CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_STANDALONE_H_\n\n#include \"starlight.h\"\n#include \"starlight_config.h\"\n#include \"starlight_enums.h\"\n#include \"starlight_value.h\"\n\n#endif  // CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_STANDALONE_H_\n"
  },
  {
    "path": "core/include/starlight_standalone/starlight_value.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_VALUE_H_\n#define CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_VALUE_H_\n\n#include \"starlight_enums.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// return type of measure function\ntypedef struct StarlightSize {\n  float width_;\n  float height_;\n} StarlightSize;\n\n// used to get the value of a style getter\ntypedef struct StarlightValue {\n  float value_;\n  SLUnit unit_;\n} StarlightValue;\n\n#define SLUndefined (10E20f)\n#define kDefaultPhysicalPixelsPerLayoutUnit (1.f)\n#define kStarlightDefaultTargetSDKVersion \"3.2\"\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif  // CORE_INCLUDE_STARLIGHT_STANDALONE_STARLIGHT_VALUE_H_\n"
  },
  {
    "path": "core/inspector/BUILD.gn",
    "content": "# Copyright 2020 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../testing/test.gni\")\nimport(\"../Lynx.gni\")\n\ninspector_shared_sources = [\n  \"console_message_postman.h\",\n  \"lepus_inspector_manager.h\",\n  \"observer/inspector_common_observer.h\",\n  \"observer/inspector_element_observer.h\",\n  \"observer/inspector_lepus_observer.h\",\n  \"observer/inspector_runtime_observer_ng.h\",\n  \"runtime_inspector_manager.h\",\n  \"style_sheet.h\",\n]\n\nlynx_core_source_set(\"inspector\") {\n  sources = inspector_shared_sources\n  public_deps = [ \"../../third_party/rapidjson\" ]\n}\n\nunittest_set(\"inspector_testset\") {\n  public_configs = [ \"../:lynx_public_config\" ]\n  sources = [\n    \"runtime_inspector_manager_unittest.cc\",\n    \"runtime_inspector_manager_unittest.h\",\n  ]\n}\n\nunittest_exec(\"inspector_test_exec\") {\n  sources = []\n  deps = [ \":inspector_testset\" ]\n}\n\ngroup(\"inspector_group\") {\n  testonly = true\n  deps = [\n    \":inspector_test_exec\",\n    \":inspector_testset\",\n  ]\n}\n"
  },
  {
    "path": "core/inspector/console_message_postman.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INSPECTOR_CONSOLE_MESSAGE_POSTMAN_H_\n#define CORE_INSPECTOR_CONSOLE_MESSAGE_POSTMAN_H_\n\n#include <memory>\n#include <string>\n\nnamespace lynx {\nnamespace runtime {\nnamespace js {\nclass InspectorRuntimeObserverNG;\n\nstruct ConsoleMessage {\n  ConsoleMessage(const std::string& text, int32_t level, int64_t timestamp)\n      : text_(text), level_(level), timestamp_(timestamp){};\n  std::string text_;\n  int32_t level_;\n  int64_t timestamp_;\n};\n\nclass ConsoleMessagePostMan {\n public:\n  ConsoleMessagePostMan() = default;\n  virtual ~ConsoleMessagePostMan() = default;\n\n  virtual void OnMessagePosted(const ConsoleMessage& message) = 0;\n  virtual void InsertRuntimeObserver(\n      const std::shared_ptr<InspectorRuntimeObserverNG>& observer) = 0;\n};\n\n}  // namespace js\n\n}  // namespace runtime\n}  // namespace lynx\n#endif  // CORE_INSPECTOR_CONSOLE_MESSAGE_POSTMAN_H_\n"
  },
  {
    "path": "core/inspector/lepus_inspector_manager.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INSPECTOR_LEPUS_INSPECTOR_MANAGER_H_\n#define CORE_INSPECTOR_LEPUS_INSPECTOR_MANAGER_H_\n\n#include <memory>\n#include <string>\n\nnamespace lynx {\nnamespace runtime {\nclass MTSContext;\n}  // namespace runtime\n\nnamespace lepus {\nclass InspectorLepusObserver;\n\nclass LepusInspectorManager {\n public:\n  virtual ~LepusInspectorManager() = default;\n\n  virtual void InitInspector(\n      runtime::MTSContext* context,\n      const std::shared_ptr<InspectorLepusObserver>& observer,\n      const std::string& context_name) = 0;\n  virtual void SetDebugInfo(const std::string& debug_info_url,\n                            const std::string& file_name) = 0;\n  virtual void DestroyInspector() = 0;\n};\n\n}  // namespace lepus\n}  // namespace lynx\n\n#endif  // CORE_INSPECTOR_LEPUS_INSPECTOR_MANAGER_H_\n"
  },
  {
    "path": "core/inspector/observer/inspector_common_observer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INSPECTOR_OBSERVER_INSPECTOR_COMMON_OBSERVER_H_\n#define CORE_INSPECTOR_OBSERVER_INSPECTOR_COMMON_OBSERVER_H_\n\n#include <string>\n\nnamespace lynx {\nnamespace tasm {\n\nclass InspectorCommonObserver {\n public:\n  virtual ~InspectorCommonObserver() noexcept = default;\n\n  virtual void EndReplayTest(const std::string& file_path) = 0;\n  virtual void SendLayoutTree() = 0;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_INSPECTOR_OBSERVER_INSPECTOR_COMMON_OBSERVER_H_\n"
  },
  {
    "path": "core/inspector/observer/inspector_element_observer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INSPECTOR_OBSERVER_INSPECTOR_ELEMENT_OBSERVER_H_\n#define CORE_INSPECTOR_OBSERVER_INSPECTOR_ELEMENT_OBSERVER_H_\n\n#include <map>\n#include <string>\n\n#include \"core/base/utils/any.h\"\n#include \"core/renderer/dom/element.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass Element;\n\nclass InspectorElementObserver {\n public:\n  virtual void OnDocumentUpdated() = 0;\n\n  // OnElementNodeAdded handles notifications for new element nodes.\n  // 1. It can notify each node individually as they are added,\n  //    or notify the devtool once with the root of a newly added subtree,\n  //    avoiding the need to notify every single node.\n  // 2. When the page root node is notified, it sets element_root_ on the\n  // devtool side,\n  //    which is used by the devtool to find element nodes by nodeId.\n  virtual void OnElementNodeAdded(Element *ptr) = 0;\n  virtual void OnElementNodeRemoved(Element *ptr) = 0;\n  virtual void OnCharacterDataModified(Element *ptr) = 0;\n  virtual void OnElementDataModelSet(Element *ptr) = 0;\n\n  virtual void OnElementManagerWillDestroy() = 0;\n\n  virtual void OnCSSStyleSheetAdded(Element *ptr) = 0;\n\n  virtual void OnComponentUselessUpdate(const std::string &component_name,\n                                        const lepus::Value &properties) = 0;\n  virtual void OnSetNativeProps(Element *ptr, const std::string &name,\n                                const std::string &value, bool is_style) = 0;\n  virtual std::map<lynx::devtool::DevToolFunction,\n                   std::function<void(const base::any &)>>\n  GetDevToolFunction() = 0;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_INSPECTOR_OBSERVER_INSPECTOR_ELEMENT_OBSERVER_H_\n"
  },
  {
    "path": "core/inspector/observer/inspector_lepus_observer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INSPECTOR_OBSERVER_INSPECTOR_LEPUS_OBSERVER_H_\n#define CORE_INSPECTOR_OBSERVER_INSPECTOR_LEPUS_OBSERVER_H_\n\n#include <memory>\n#include <string>\n\n#include \"core/inspector/lepus_inspector_manager.h\"\n\nnamespace lynx {\n\nnamespace devtool {\nclass InspectorClientNG;\n}\n\nnamespace lepus {\n\nclass InspectorLepusObserver {\n public:\n  virtual ~InspectorLepusObserver() = default;\n\n  virtual std::unique_ptr<LepusInspectorManager> CreateLepusInspectorManager() {\n    return nullptr;\n  }\n  virtual bool ShouldFetchDebugInfo() { return false; }\n  virtual std::string GetDebugInfo(const std::string& url) { return \"\"; }\n  virtual void SetDebugInfoUrl(const std::string& url,\n                               const std::string& file_name) = 0;\n\n  virtual void OnInspectorInited(\n      const std::string& vm_type, const std::string& name,\n      const std::shared_ptr<devtool::InspectorClientNG>& client) = 0;\n  virtual void OnContextDestroyed(const std::string& name) = 0;\n\n  virtual void OnConsoleEvent(const std::string& func_name,\n                              const std::string& args) = 0;\n\n  virtual void PrepareForScriptEval(const std::string& name) = 0;\n};\n\n}  // namespace lepus\n}  // namespace lynx\n\n#endif  // CORE_INSPECTOR_OBSERVER_INSPECTOR_LEPUS_OBSERVER_H_\n"
  },
  {
    "path": "core/inspector/observer/inspector_runtime_observer_ng.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INSPECTOR_OBSERVER_INSPECTOR_RUNTIME_OBSERVER_NG_H_\n#define CORE_INSPECTOR_OBSERVER_INSPECTOR_RUNTIME_OBSERVER_NG_H_\n\n#include <memory>\n#include <string>\n\n#include \"core/inspector/console_message_postman.h\"\n#include \"core/inspector/runtime_inspector_manager.h\"\n\nnamespace lynx {\nnamespace runtime {\nclass RuntimeManagerDelegate;\n}\n\nnamespace tasm {\nclass WhiteBoardDelegate;\n}\n\nnamespace devtool {\nclass InspectorClientNG;\n}\nnamespace runtime {\nnamespace js {\nenum class JSRuntimeType;\n\n// Only works for js runtime.\n// Create some instances which implemented in LynxDevtool and observe the js\n// runtime.\nclass InspectorRuntimeObserverNG {\n public:\n  virtual ~InspectorRuntimeObserverNG() = default;\n\n  virtual int GetViewId() { return -1; }\n\n  // The following functions are used to create some instances which implemented\n  // in LynxDevtool.\n  virtual std::unique_ptr<runtime::RuntimeManagerDelegate>\n  CreateRuntimeManagerDelegate() {\n    return nullptr;\n  }\n  virtual std::unique_ptr<RuntimeInspectorManager>\n  CreateRuntimeInspectorManager(const std::string& vm_type) {\n    return nullptr;\n  }\n  virtual std::shared_ptr<ConsoleMessagePostMan> CreateConsoleMessagePostMan() {\n    return nullptr;\n  }\n  virtual void InitWhiteBoardInspector(\n      const std::shared_ptr<tasm::WhiteBoardDelegate>& delegate) = 0;\n\n  // The following functions are used to observe the js runtime and notify\n  // LynxDevtool.\n  virtual void OnInspectorInited(\n      const std::string& vm_type, int64_t runtime_id,\n      const std::string& group_id, bool single_group,\n      const std::shared_ptr<devtool::InspectorClientNG>& client) = 0;\n  virtual void OnRuntimeCreated(JSRuntimeType type) = 0;\n  virtual void OnRuntimeDestroyed(int64_t runtime_id) = 0;\n\n  virtual void PrepareForScriptEval() = 0;\n\n  virtual std::string GetTag() { return \"\"; }\n};\n\n}  // namespace js\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_INSPECTOR_OBSERVER_INSPECTOR_RUNTIME_OBSERVER_NG_H_\n"
  },
  {
    "path": "core/inspector/runtime_inspector_manager.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INSPECTOR_RUNTIME_INSPECTOR_MANAGER_H_\n#define CORE_INSPECTOR_RUNTIME_INSPECTOR_MANAGER_H_\n\n#include <memory>\n#include <string>\n\nnamespace lynx {\nnamespace runtime {\nnamespace js {\nclass Runtime;\nclass InspectorRuntimeObserverNG;\n\nclass RuntimeInspectorManager {\n public:\n  virtual ~RuntimeInspectorManager() = default;\n\n  virtual void InitInspector(\n      Runtime* runtime,\n      const std::shared_ptr<InspectorRuntimeObserverNG>& observer) = 0;\n  virtual void DestroyInspector() = 0;\n\n  std::string BuildInspectorUrl(const std::string& filename) {\n    static constexpr char kUrlPrefixShared[] = \"file://shared\";\n    static constexpr char kUrlPrefixView[] = \"file://view\";\n    static constexpr char kUrlLynxCore[] = \"lynx_core\";\n    static constexpr char kUrlSeparator = '/';\n\n    std::string url = filename;\n    if (url.front() != kUrlSeparator) {\n      url = kUrlSeparator + url;\n    }\n    if (filename.find(kUrlLynxCore) != std::string::npos) {\n      url = kUrlPrefixShared + url;\n    } else {\n      url = kUrlPrefixView + std::to_string(instance_id_) + url;\n    }\n    return url;\n  }\n\n  virtual void PrepareForScriptEval() = 0;\n\n protected:\n  int instance_id_{-1};\n};\n\n}  // namespace js\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_INSPECTOR_RUNTIME_INSPECTOR_MANAGER_H_\n"
  },
  {
    "path": "core/inspector/runtime_inspector_manager_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/inspector/runtime_inspector_manager.h\"\n\n#include \"core/inspector/runtime_inspector_manager_unittest.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace runtime {\nnamespace js {\nnamespace testing {\nclass RuntimeInspectorManagerTest : public ::testing::Test {\n public:\n  RuntimeInspectorManagerTest() {}\n  ~RuntimeInspectorManagerTest() override {}\n  void SetUp() override {}\n};\n\nTEST_F(RuntimeInspectorManagerTest, BuildInspectorUrl) {\n  auto manager = new MockRuntimeInspectorManager(1);\n\n  EXPECT_TRUE(manager->BuildInspectorUrl(\"/app-service.js\") ==\n              \"file://view1/app-service.js\");\n  EXPECT_TRUE(manager->BuildInspectorUrl(\"app-service.js\") ==\n              \"file://view1/app-service.js\");\n  EXPECT_TRUE(manager->BuildInspectorUrl(\"/test.js\") == \"file://view1/test.js\");\n  EXPECT_TRUE(manager->BuildInspectorUrl(\"test.js\") == \"file://view1/test.js\");\n  EXPECT_TRUE(manager->BuildInspectorUrl(\"/lynx_core.js\") ==\n              \"file://shared/lynx_core.js\");\n  EXPECT_TRUE(manager->BuildInspectorUrl(\"lynx_core.js\") ==\n              \"file://shared/lynx_core.js\");\n}\n}  // namespace testing\n}  // namespace js\n}  // namespace runtime\n}  // namespace lynx\n"
  },
  {
    "path": "core/inspector/runtime_inspector_manager_unittest.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INSPECTOR_RUNTIME_INSPECTOR_MANAGER_UNITTEST_H_\n#define CORE_INSPECTOR_RUNTIME_INSPECTOR_MANAGER_UNITTEST_H_\n\n#include <memory>\n\n#include \"core/inspector/runtime_inspector_manager.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace runtime {\nnamespace js {\nnamespace testing {\n\nclass MockRuntimeInspectorManager : public RuntimeInspectorManager {\n public:\n  MockRuntimeInspectorManager(int instance_id) { instance_id_ = instance_id; }\n\n  void InitInspector(\n      Runtime* runtime,\n      const std::shared_ptr<InspectorRuntimeObserverNG>& observer) {}\n  void DestroyInspector() {}\n  void PrepareForScriptEval() {}\n};\n\n}  // namespace testing\n}  // namespace js\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_INSPECTOR_RUNTIME_INSPECTOR_MANAGER_UNITTEST_H_\n"
  },
  {
    "path": "core/inspector/style_sheet.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_INSPECTOR_STYLE_SHEET_H_\n#define CORE_INSPECTOR_STYLE_SHEET_H_\n\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\nnamespace lynx {\nnamespace devtool {\n\nusing LynxDoubleMapString =\n    std::unordered_map<std::string,\n                       std::unordered_map<std::string, std::string>>;\n\nusing LynxTripleMapString = std::unordered_map<\n    std::string,\n    std::unordered_map<std::string,\n                       std::unordered_map<std::string, std::string>>>;\n\nusing LynxAttributePair =\n    std::pair<std::vector<std::string>,\n              std::unordered_map<std::string, std::string>>;\n\nconstexpr intptr_t kElementPtr = -1;\n\nstruct Range {\n  int start_line_;\n  int end_line_;\n  int start_column_;\n  int end_column_;\n};\n\nstruct CSSPropertyDetail {\n  std::string name_;\n  std::string value_;\n  std::string text_;\n  bool disabled_;\n  bool implicit_;\n  bool important_;\n  bool looped_;\n  bool parsed_ok_;\n  Range property_range_;\n};\n\nstruct InspectorStyleSheet {\n  std::string style_sheet_id_;\n  std::string style_name_;\n  std::string origin_;\n  std::string css_text_;\n  std::unordered_multimap<std::string, CSSPropertyDetail> css_properties_;\n  std::unordered_map<std::string, CSSPropertyDetail> shorthand_entries_;\n  std::vector<std::string> property_order_;\n  Range style_value_range_;\n  Range style_name_range_;\n  bool empty = true;\n  uint64_t position_;\n};\n\nstruct InspectorKeyframe {\n  std::string key_text_;\n  InspectorStyleSheet style_;\n};\n\nstruct InspectorSelectorList {\n  std::string text_;\n  std::vector<std::string> selectors_order_;\n  std::unordered_map<std::string, Range> selectors_;\n};\n\nstruct InspectorCSSRule {\n  std::string style_sheet_id_;\n  std::string origin_;\n  InspectorStyleSheet style_;\n  InspectorSelectorList selector_list_;\n};\n\nenum class InspectorElementType {\n  DOCUMENT = 0,\n  STYLEVALUE,\n  ELEMENT,\n  COMPONENT\n};\n\nenum class InspectorNodeType : int {\n  ElementNode = 1,\n  kTextNode = 3,\n  kDocumentNode = 9\n};\n\nenum Function {\n  Index = 0,\n  Parent,             // 1\n  Impl,               // 2\n  Type,               // 3\n  ComponentName,      // 4\n  SlotName,           // 5\n  Tag,                // 6\n  ID,                 // 7\n  Children,           // 8\n  ClassOrder,         // 9\n  SlotFillers,        // 10\n  InlineStyle,        // 11\n  Attr,               // 12\n  DataSet,            // 13\n  EventMap,           // 14\n  RootCss,            // 15\n  RootAnimation,      // 16\n  ThisManager,        // 17\n  MessageToJSEngine,  // 18\n  OnClose,            // 19\n  OnTASMCreated,      // 20\n  DefaultCSS,         // 21\n  Density,            // 22\n  BoxModel,           // 23\n  ImplID,             // 24\n  RemoveNode,         // 25\n  SetAttribute,       // 26\n  Reset,              // 27\n  SetStyle,           // 28\n  SetFontSize,        // 29\n  FlushProps,         // 30\n  Component,          // 31\n  ProcessCSS,         // 32\n  ProcessRootCss      // 33\n};\n\nenum class DevToolFunction : int {\n  InitForInspector,\n  InitPlugForInspector,\n  InitStyleValueElement,\n  InitStyleRoot,\n  SetDocElement,\n  SetStyleValueElement,\n  SetStyleRoot,\n};\n\n}  // namespace devtool\n}  // namespace lynx\n\n#endif  // CORE_INSPECTOR_STYLE_SHEET_H_\n"
  },
  {
    "path": "core/list/BUILD.gn",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../testing/test.gni\")\nimport(\"../Lynx.gni\")\n\nlist_container_shared_sources = [\n  \"decoupled_list_types.h\",\n  \"list_container_delegate.h\",\n  \"list_element_delegate.h\",\n  \"list_item_element_delegate.h\",\n]\n\nif (disable_list_platform_implementation) {\n  list_container_shared_sources += [\n    \"decoupled_adapter_helper.cc\",\n    \"decoupled_adapter_helper.h\",\n    \"decoupled_batch_list_adapter.cc\",\n    \"decoupled_batch_list_adapter.h\",\n    \"decoupled_default_list_adapter.cc\",\n    \"decoupled_default_list_adapter.h\",\n    \"decoupled_grid_layout_manager.cc\",\n    \"decoupled_grid_layout_manager.h\",\n    \"decoupled_item_holder.cc\",\n    \"decoupled_item_holder.h\",\n    \"decoupled_linear_layout_manager.cc\",\n    \"decoupled_linear_layout_manager.h\",\n    \"decoupled_list_adapter.cc\",\n    \"decoupled_list_adapter.h\",\n    \"decoupled_list_anchor_manager.cc\",\n    \"decoupled_list_anchor_manager.h\",\n    \"decoupled_list_children_helper.cc\",\n    \"decoupled_list_children_helper.h\",\n    \"decoupled_list_container_impl.cc\",\n    \"decoupled_list_container_impl.h\",\n    \"decoupled_list_event_manager.cc\",\n    \"decoupled_list_event_manager.h\",\n    \"decoupled_list_layout_manager.cc\",\n    \"decoupled_list_layout_manager.h\",\n    \"decoupled_list_orientation_helper.cc\",\n    \"decoupled_list_orientation_helper.h\",\n    \"decoupled_list_types.h\",\n    \"decoupled_staggered_grid_layout_manager.cc\",\n    \"decoupled_staggered_grid_layout_manager.h\",\n    \"list_animation_manager.cc\",\n    \"list_animation_manager.h\",\n  ]\n  if (!defined(enable_list_container_animation) ||\n      enable_list_container_animation) {\n    list_container_shared_sources += [\n      \"list_animation_manager_impl.cc\",\n      \"list_animation_manager_impl.h\",\n    ]\n  } else {\n    list_container_shared_sources += [\n      \"list_animation_manager_default.cc\",\n      \"list_animation_manager_default.h\",\n    ]\n  }\n} else {\n  list_container_shared_sources += [\n    \"decoupled_list_container_default.cc\",\n    \"decoupled_list_container_default.h\",\n  ]\n}\n\nlynx_core_source_set(\"list_container\") {\n  sources = list_container_shared_sources\n  public_deps = [\n    \"../build:build\",\n    \"../renderer/trace:renderer_trace\",\n  ]\n  if (!defined(enable_list_container_animation) ||\n      enable_list_container_animation) {\n    deps = [ \"../animation/lynx_basic_animator:lynx_basic_animator\" ]\n  }\n}\n\nlist_container_testset_shared_sources = []\n\nif (disable_list_platform_implementation) {\n  list_container_testset_shared_sources += [\n    \"decoupled_default_list_adapter_unittest.cc\",\n    \"decoupled_list_adapter_unittest.cc\",\n    \"decoupled_list_anchor_manager_unittest.cc\",\n    \"decoupled_list_children_helper_unittest.cc\",\n    \"decoupled_list_container_impl_unittest.cc\",\n    \"decoupled_list_event_manager_unittest.cc\",\n    \"decoupled_list_layout_manager_unittest.cc\",\n    \"decoupled_staggered_grid_layout_manager_unittest.cc\",\n    \"testing/fiber_diff_info.h\",\n    \"testing/mock_list_element.h\",\n    \"testing/radon_data_source.h\",\n    \"testing/utils.h\",\n  ]\n}\n\nunittest_set(\"list_container_testset\") {\n  sources = list_container_testset_shared_sources\n  public_deps = [ \":list_container\" ]\n  deps = [\n    \"../public\",\n    \"../renderer/dom:dom\",\n  ]\n}\n\nunittest_exec(\"list_container_testset_exec\") {\n  sources = []\n  deps = [ \":list_container_testset\" ]\n}\n\ngroup(\"list_container_testset_group\") {\n  testonly = true\n  deps = [\n    \":list_container_testset\",\n    \":list_container_testset_exec\",\n  ]\n}\n"
  },
  {
    "path": "core/list/decoupled_adapter_helper.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_adapter_helper.h\"\n\n#include <algorithm>\n#include <sstream>\n#include <utility>\n\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/table.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/build/gen/lynx_sub_error_code.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\nstd::string AdapterHelper::DiffResult::ToString() const {\n  std::ostringstream oss;\n  oss << \"DiffResult: item_keys:[\";\n  for (const auto& item_key : item_keys_) {\n    oss << item_key << \",\";\n  }\n  oss << \"],\";\n  auto diff_action_to_string = [&oss](const std::string& key,\n                                      const std::vector<int32_t>& array) {\n    oss << key << \":[\";\n    for (int32_t index : array) {\n      oss << index << \",\";\n    }\n    oss << \"],\";\n  };\n  diff_action_to_string(kRadonDataInsertions, insertions_);\n  diff_action_to_string(kRadonDataRemovals, removals_);\n  diff_action_to_string(kRadonDataUpdateFrom, update_from_);\n  diff_action_to_string(kRadonDataUpdateTo, update_to_);\n  diff_action_to_string(kRadonDataMoveFrom, move_from_);\n  diff_action_to_string(kRadonDataMoveTo, move_to_);\n  return oss.str();\n}\n\n//  update \"diff-result\" info  on radon_diff architecture\nbool AdapterHelper::UpdateRadonDiffResult(const pub::Value& diff_result) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_DIFF_RESULT);\n  bool has_update = false;\n  if (diff_result.IsMap()) {\n    diff_result.ForeachMap(\n        [this, &has_update](const pub::Value& key, const pub::Value& value) {\n          if (key.IsString()) {\n            const std::string& key_str = key.str();\n            if (key_str == kRadonDataInsertions) {\n              UpdateInsertions(value);\n              has_update = true;\n            } else if (key_str == kRadonDataRemovals) {\n              UpdateRemovals(value);\n              has_update = true;\n            } else if (key_str == kRadonDataUpdateFrom) {\n              UpdateUpdateFrom(value);\n              has_update = true;\n            } else if (key_str == kRadonDataUpdateTo) {\n              UpdateUpdateTo(value);\n              has_update = true;\n            } else if (key_str == kRadonDataMoveFrom) {\n              UpdateMoveFrom(value);\n              has_update = true;\n            } else if (key_str == kRadonDataMoveTo) {\n              UpdateMoveTo(value);\n              has_update = true;\n            }\n          }\n        });\n  }\n  return has_update;\n}\n\nvoid AdapterHelper::UpdateInsertions(const pub::Value& diff_insertions) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_INSERTIONS);\n  diff_result_.insertions_.clear();\n  if (diff_insertions.IsArray()) {\n    diff_insertions.ForeachArray(\n        [this](int64_t index, const pub::Value& value) {\n          if (value.IsInt32() && value.Int32() >= 0) {\n            diff_result_.insertions_.emplace_back(value.Int32());\n          }\n        });\n  }\n}\n\nvoid AdapterHelper::UpdateRemovals(const pub::Value& diff_removals) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_REMOVALS);\n  diff_result_.removals_.clear();\n  if (diff_removals.IsArray()) {\n    diff_removals.ForeachArray([this](int64_t index, const pub::Value& value) {\n      if (value.IsInt32() && value.Int32() >= 0) {\n        diff_result_.removals_.emplace_back(value.Int32());\n      }\n    });\n  }\n}\n\nvoid AdapterHelper::UpdateUpdateFrom(const pub::Value& diff_update_from) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_FROM);\n  diff_result_.update_from_.clear();\n  if (diff_update_from.IsArray()) {\n    diff_update_from.ForeachArray(\n        [this](int64_t index, const pub::Value& value) {\n          if (value.IsInt32() && value.Int32() >= 0) {\n            diff_result_.update_from_.emplace_back(value.Int32());\n          }\n        });\n  }\n}\n\nvoid AdapterHelper::UpdateUpdateTo(const pub::Value& diff_update_to) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_TO);\n  diff_result_.update_to_.clear();\n  if (diff_update_to.IsArray()) {\n    diff_update_to.ForeachArray([this](int64_t index, const pub::Value& value) {\n      if (value.IsInt32() && value.Int32() >= 0) {\n        diff_result_.update_to_.emplace_back(value.Int32());\n      }\n    });\n  }\n}\n\nvoid AdapterHelper::UpdateMoveTo(const pub::Value& diff_move_to) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_MOVE_TO);\n  diff_result_.move_to_.clear();\n  if (diff_move_to.IsArray()) {\n    diff_move_to.ForeachArray([this](int64_t index, const pub::Value& value) {\n      if (value.IsInt32() && value.Int32() >= 0) {\n        diff_result_.move_to_.emplace_back(value.Int32());\n      }\n    });\n  }\n}\n\nvoid AdapterHelper::UpdateMoveFrom(const pub::Value& diff_move_from) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_MOVE_FROM);\n  diff_result_.move_from_.clear();\n  if (diff_move_from.IsArray()) {\n    diff_move_from.ForeachArray([this](int64_t index, const pub::Value& value) {\n      if (value.IsInt32() && value.Int32() >= 0) {\n        diff_result_.move_from_.emplace_back(value.Int32());\n      }\n    });\n  }\n}\n\n// update \"item-key\" info on radon architecture\nvoid AdapterHelper::UpdateItemKeys(const pub::Value& item_keys_value) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_ITEM_KEYS);\n  auto& item_keys = diff_result_.item_keys_;\n  item_keys.clear();\n  item_key_map_.clear();\n  bool has_illegal_item_key = false;\n  bool has_duplicated_item_key = false;\n  if (item_keys_value.IsArray()) {\n    item_keys_value.ForeachArray(\n        [this, &has_illegal_item_key, &has_duplicated_item_key, &item_keys](\n            int64_t index, const pub::Value& value) {\n          if (value.IsString()) {\n            const std::string& item_key = value.str();\n            has_duplicated_item_key =\n                has_duplicated_item_key ||\n                item_key_map_.find(item_key) != item_key_map_.end();\n            item_key_map_[item_key] = static_cast<int>(item_keys.size());\n            item_keys.emplace_back(item_key);\n          } else {\n            has_illegal_item_key = true;\n          }\n        });\n  }\n  if (has_illegal_item_key && delegate_) {\n    std::string error_msg = \"Error for illegal list item-key.\";\n    std::string suggestion = \"Please check the legality of the item-key.\";\n    auto error = lynx::base::LynxError(\n        error::E_COMPONENT_LIST_ILLEGAL_ITEM_KEY, std::move(error_msg),\n        std::move(suggestion), base::LynxErrorLevel::Error);\n    delegate_->OnErrorOccurred(std::move(error));\n  }\n  if (has_duplicated_item_key && delegate_) {\n    std::string error_msg = \"Error for duplicated list item-key.\";\n    std::string suggestion = \"Please check the legality of the item-key.\";\n    auto error = lynx::base::LynxError(\n        error::E_COMPONENT_LIST_DUPLICATE_ITEM_KEY, std::move(error_msg),\n        std::move(suggestion), base::LynxErrorLevel::Error);\n    delegate_->OnErrorOccurred(std::move(error));\n  }\n}\n\n// update \"estimated-height-px\" info on radon architecture\nvoid AdapterHelper::UpdateEstimatedHeightsPx(\n    const pub::Value& estimated_heights_px) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_ESTIMATED_HEIGHT);\n  estimated_heights_px_.clear();\n  if (estimated_heights_px.IsArray()) {\n    estimated_heights_px.ForeachArray(\n        [this](int64_t index, const pub::Value& value) {\n          if (value.IsInt32()) {\n            // Note: In radon arch, if not set estimated_heights_px, the value\n            // will be -1.\n            estimated_heights_px_.emplace_back(value.Int32());\n          }\n        });\n  }\n}\n\n// update \"estimated-main-axis-size-px\" info on radon architecture\nvoid AdapterHelper::UpdateEstimatedSizesPx(\n    const pub::Value& estimated_sizes_px) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_ESTIMATED_SIZE);\n  estimated_sizes_px_.clear();\n  if (estimated_sizes_px.IsArray()) {\n    estimated_sizes_px.ForeachArray(\n        [this](int64_t index, const pub::Value& value) {\n          if (value.IsInt32()) {\n            // Note: In radon arch, if not set estimated_sizes_px, the value\n            // will be -1.\n            estimated_sizes_px_.emplace_back(value.Int32());\n          }\n        });\n  }\n}\n\n// update \"full-span\" info on radon architecture\nvoid AdapterHelper::UpdateFullSpans(const pub::Value& full_spans) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_FULL_SPANS);\n  full_spans_.clear();\n  if (full_spans.IsArray()) {\n    full_spans.ForeachArray([this](int64_t index, const pub::Value& value) {\n      if (value.IsInt32() && value.Int32() >= 0) {\n        full_spans_.insert(value.Int32());\n      }\n    });\n  }\n}\n\n// update \"sticky-bottom\" info on radon architecture\nvoid AdapterHelper::UpdateStickyBottoms(const pub::Value& sticky_bottoms) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_STICKY_BOTTOMS);\n  sticky_bottoms_.clear();\n  if (sticky_bottoms.IsArray()) {\n    sticky_bottoms.ForeachArray([this](int64_t index, const pub::Value& value) {\n      if (value.IsInt32() && value.Int32() >= 0) {\n        sticky_bottoms_.emplace_back(value.Int32());\n      }\n    });\n  }\n}\n\n// update \"sticky-top\" info on radon architecture\nvoid AdapterHelper::UpdateStickyTops(const pub::Value& sticky_tops) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_STICKY_TOPS);\n  sticky_tops_.clear();\n  if (sticky_tops.IsArray()) {\n    sticky_tops.ForeachArray([this](int64_t index, const pub::Value& value) {\n      if (value.IsInt32() && value.Int32() >= 0) {\n        sticky_tops_.emplace_back(value.Int32());\n      }\n    });\n  }\n}\n\n// update \"insert-action\" on fiber architecture\nvoid AdapterHelper::UpdateFiberInsertAction(\n    const std::unique_ptr<pub::Value>& insert_action,\n    bool only_parse_insertions /* = true */) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_FIBER_INSERT_ACTION);\n  if (!insert_action || !insert_action->IsArray()) {\n    return;\n  }\n  auto& insertions = diff_result_.insertions_;\n  auto& item_keys = diff_result_.item_keys_;\n  if (only_parse_insertions) {\n    insertions.clear();\n  }\n  std::ostringstream oss;\n  insert_action->ForeachArray([this, only_parse_insertions, &oss, &insertions,\n                               &item_keys](int64_t index,\n                                           const pub::Value& value) {\n    if (value.IsMap()) {\n      const auto& position = value.GetValueForKey(kFiberDataPosition);\n      const auto& item_key = value.GetValueForKey(kFiberDataItemKey);\n      if (!position || !item_key || !position->IsNumber()) {\n        return;\n      }\n      int index = static_cast<int>(position->Number());\n      if (item_key->IsString() && !item_key->str().empty()) {\n        const auto& item_key_str = item_key->str();\n        if (index >= 0) {\n          if (only_parse_insertions) {\n            insertions.emplace_back(index);\n            return;\n          }\n          if (index <= static_cast<int>(item_keys.size())) {\n            const auto& is_full_span = value.GetValueForKey(kFiberDataFullSpan);\n            const auto& is_sticky_top =\n                value.GetValueForKey(kFiberDataStickyTop);\n            const auto& is_sticky_bottom =\n                value.GetValueForKey(kFiberDataStickyBottom);\n            const auto& estimated_height_px =\n                value.GetValueForKey(kFiberDataEstimatedHeightPx);\n            const auto& estimated_size_px =\n                value.GetValueForKey(kFiberDataEstimatedMainAxisSizePx);\n            const auto& recyclable = value.GetValueForKey(kFiberDataRecyclable);\n            item_keys.insert(item_keys.begin() + index, item_key_str);\n            if (is_full_span && is_full_span->IsBool() &&\n                is_full_span->Bool()) {\n              fiber_full_spans_.insert(item_key_str);\n            }\n            if (is_sticky_top && is_sticky_top->IsBool() &&\n                is_sticky_top->Bool()) {\n              fiber_sticky_tops_.insert(item_key_str);\n            }\n            if (is_sticky_bottom && is_sticky_bottom->IsBool() &&\n                is_sticky_bottom->Bool()) {\n              fiber_sticky_bottoms_.insert(item_key_str);\n            }\n            if (estimated_height_px && estimated_height_px->IsNumber()) {\n              fiber_estimated_heights_px_[item_key_str] =\n                  static_cast<int32_t>(estimated_height_px->Number());\n            }\n            if (estimated_size_px && estimated_size_px->IsNumber()) {\n              fiber_estimated_sizes_px_[item_key_str] =\n                  static_cast<int32_t>(estimated_size_px->Number());\n            }\n            if (recyclable && recyclable->IsBool() && !recyclable->Bool()) {\n              fiber_unrecyclable_.insert(item_key_str);\n            }\n          }\n        }\n      } else if (!only_parse_insertions) {\n        // Parse illegal item-key\n        if (oss.str().empty()) {\n          oss << \"indexes: [\";\n        }\n        oss << index << \", \";\n      }\n    }\n  });\n  if (!only_parse_insertions && delegate_ && !oss.str().empty()) {\n    std::string error_msg =\n        \"Error for illegal list item-key in parse insert with \" + oss.str() +\n        \"]\";\n    std::string suggestion = \"Please check the legality of the item-key.\";\n    auto error = lynx::base::LynxError(\n        error::E_COMPONENT_LIST_ILLEGAL_ITEM_KEY, std::move(error_msg),\n        std::move(suggestion), base::LynxErrorLevel::Error);\n    delegate_->OnErrorOccurred(std::move(error));\n  }\n}\n\n// update \"remove-action\" on  fiber architecture\nvoid AdapterHelper::UpdateFiberRemoveAction(\n    const std::unique_ptr<pub::Value>& remove_action,\n    bool only_parse_removals /* = true */) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_FIBER_REMOVE_ACTION);\n  if (!remove_action || !remove_action->IsArray()) {\n    return;\n  }\n  auto& removals = diff_result_.removals_;\n  auto& item_keys = diff_result_.item_keys_;\n  if (only_parse_removals) {\n    removals.clear();\n  }\n  remove_action->ForeachArray([this, only_parse_removals, &removals,\n                               &item_keys](int64_t array_index,\n                                           const pub::Value& value) {\n    if (!value.IsNumber()) {\n      return;\n    }\n    int index = static_cast<int>(value.Number());\n    if (index >= 0 && index < static_cast<int>(item_keys.size())) {\n      if (only_parse_removals) {\n        removals.emplace_back(index);\n        return;\n      }\n      // Note: item_keys_ is a vector, and can not remove element from it in the\n      // forward direction.\n      const auto& item_key_str = item_keys[index];\n      auto it = item_key_map_.end();\n      if (item_key_map_.end() != (it = item_key_map_.find(item_key_str))) {\n        item_key_map_.erase(it);\n      }\n      if (fiber_full_spans_.end() != fiber_full_spans_.find(item_key_str)) {\n        fiber_full_spans_.erase(item_key_str);\n      }\n      if (fiber_sticky_tops_.end() != fiber_sticky_tops_.find(item_key_str)) {\n        fiber_sticky_tops_.erase(item_key_str);\n      }\n      if (fiber_sticky_bottoms_.end() !=\n          fiber_sticky_bottoms_.find(item_key_str)) {\n        fiber_sticky_bottoms_.erase(item_key_str);\n      }\n      if (fiber_estimated_heights_px_.end() !=\n          fiber_estimated_heights_px_.find(item_key_str)) {\n        fiber_estimated_heights_px_.erase(item_key_str);\n      }\n      if (fiber_estimated_sizes_px_.end() !=\n          fiber_estimated_sizes_px_.find(item_key_str)) {\n        fiber_estimated_sizes_px_.erase(item_key_str);\n      }\n      if (fiber_unrecyclable_.end() != fiber_unrecyclable_.find(item_key_str)) {\n        fiber_unrecyclable_.erase(item_key_str);\n      }\n    }\n  });\n  if (!only_parse_removals) {\n    item_keys.clear();\n    std::vector<std::pair<std::string, int>> remaining_item_keys(\n        item_key_map_.begin(), item_key_map_.end());\n\n    std::sort(remaining_item_keys.begin(), remaining_item_keys.end(),\n              [](const std::pair<std::string, int>& l,\n                 const std::pair<std::string, int>& r) {\n                return l.second < r.second;\n              });\n    for (const auto& it : remaining_item_keys) {\n      item_keys.emplace_back(it.first);\n    }\n  }\n}\n\n// update \"update-action\" on fiber architecture\nvoid AdapterHelper::UpdateFiberUpdateAction(\n    const std::unique_ptr<pub::Value>& update_action,\n    bool only_parse_update /* = true */) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_FIBER_UPDATE_ACTION);\n  if (!update_action || !update_action->IsArray()) {\n    return;\n  }\n  auto& update_from = diff_result_.update_from_;\n  auto& update_to = diff_result_.update_to_;\n  auto& item_keys = diff_result_.item_keys_;\n  if (only_parse_update) {\n    update_from.clear();\n    update_to.clear();\n  }\n  std::ostringstream oss;\n  update_action->ForeachArray([this, only_parse_update, &update_from,\n                               &update_to, &item_keys,\n                               &oss](int64_t index, const pub::Value& value) {\n    if (value.IsMap()) {\n      const auto& from_position = value.GetValueForKey(kFiberDataFrom);\n      const auto& to_position = value.GetValueForKey(kFiberDataTo);\n      const auto& item_key = value.GetValueForKey(kFiberDataItemKey);\n      const auto& flush = value.GetValueForKey(kFiberDataFlush);\n      if (!from_position || !to_position || !flush || !item_key ||\n          !from_position->IsNumber() || !to_position->IsNumber() ||\n          !flush->IsBool()) {\n        return;\n      }\n      int from = static_cast<int>(from_position->Number());\n      int to = static_cast<int>(to_position->Number());\n      if (item_key->IsString() && !item_key->str().empty()) {\n        if (from >= 0 && to >= 0) {\n          if (only_parse_update) {\n            if (flush->Bool()) {\n              update_from.emplace_back(from);\n              update_to.emplace_back(to);\n            }\n            // Note: if only_parse_update == true, we should return here to\n            // avoid updating item_keys_ repeatedly.\n            return;\n          }\n          const std::string& item_key_str = item_key->str();\n          if (from < static_cast<int>(item_keys.size())) {\n            item_keys[from] = item_key_str;\n            const auto& is_full_span = value.GetValueForKey(kFiberDataFullSpan);\n            const auto& is_sticky_top =\n                value.GetValueForKey(kFiberDataStickyTop);\n            const auto& is_sticky_bottom =\n                value.GetValueForKey(kFiberDataStickyBottom);\n            const auto& estimated_height_px =\n                value.GetValueForKey(kFiberDataEstimatedHeightPx);\n            const auto& estimated_size_px =\n                value.GetValueForKey(kFiberDataEstimatedMainAxisSizePx);\n            const auto& recyclable = value.GetValueForKey(kFiberDataRecyclable);\n            if (is_full_span && is_full_span->IsBool()) {\n              if (is_full_span->Bool()) {\n                fiber_full_spans_.insert(item_key_str);\n              } else {\n                fiber_full_spans_.erase(item_key_str);\n              }\n            }\n            if (is_sticky_top && is_sticky_top->IsBool()) {\n              if (is_sticky_top->Bool()) {\n                fiber_sticky_tops_.insert(item_key_str);\n              } else {\n                fiber_sticky_tops_.erase(item_key_str);\n              }\n            }\n            if (is_sticky_bottom && is_sticky_bottom->IsBool()) {\n              if (is_sticky_bottom->Bool()) {\n                fiber_sticky_bottoms_.insert(item_key_str);\n              } else {\n                fiber_sticky_bottoms_.erase(item_key_str);\n              }\n            }\n            if (estimated_height_px && estimated_height_px->IsNumber() &&\n                fiber_estimated_heights_px_.end() !=\n                    fiber_estimated_heights_px_.find(item_key_str)) {\n              fiber_estimated_heights_px_[item_key_str] =\n                  static_cast<int32_t>(estimated_height_px->Number());\n            }\n            if (estimated_size_px && estimated_size_px->IsNumber() &&\n                fiber_estimated_sizes_px_.end() !=\n                    fiber_estimated_sizes_px_.find(item_key_str)) {\n              fiber_estimated_sizes_px_[item_key_str] =\n                  static_cast<int32_t>(estimated_size_px->Number());\n            }\n            if (recyclable && recyclable->IsBool()) {\n              if (!recyclable->Bool()) {\n                fiber_unrecyclable_.insert(item_key_str);\n              } else {\n                fiber_unrecyclable_.erase(item_key_str);\n              }\n            }\n          }\n        }\n      } else if (!only_parse_update) {\n        // Parse illegal item-key\n        if (oss.str().empty()) {\n          oss << \"indexes: [\";\n        }\n        oss << from << \", \";\n      }\n    }\n  });\n  if (!only_parse_update && delegate_ && !oss.str().empty()) {\n    std::string error_msg =\n        \"Error for illegal list item-key in parse update with \" + oss.str() +\n        \"]\";\n    std::string suggestion = \"Please check the legality of the item-key.\";\n    auto error = lynx::base::LynxError(\n        error::E_COMPONENT_LIST_ILLEGAL_ITEM_KEY, std::move(error_msg),\n        std::move(suggestion), base::LynxErrorLevel::Error);\n    delegate_->OnErrorOccurred(std::move(error));\n  }\n}\n\n// update extra info such as sticky、full-span on fiber architecture\nvoid AdapterHelper::UpdateFiberExtraInfo() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, ADAPTER_HELPER_UPDATE_FIBER_EXTRA_INFO);\n  // update item_key_map_ from item_keys_ vector after parse insert / remove /\n  // update actions\n  auto& item_keys = diff_result_.item_keys_;\n  bool has_duplicated_item_key = false;\n  item_key_map_.clear();\n  for (int i = 0; i < static_cast<int>(item_keys.size()); ++i) {\n    const std::string& item_key = item_keys[i];\n    has_duplicated_item_key =\n        has_duplicated_item_key ||\n        item_key_map_.find(item_key) != item_key_map_.end();\n    item_key_map_[item_key] = i;\n  }\n\n  if (has_duplicated_item_key && delegate_) {\n    std::string error_msg = \"Error for duplicated list item-key. \";\n    error_msg += \"Last diff result is \" + last_diff_result_.ToString();\n    error_msg += \"Current diff result is \" + diff_result_.ToString();\n    std::string suggestion = \"Please check the legality of the item-key.\";\n    auto error = lynx::base::LynxError(\n        error::E_COMPONENT_LIST_DUPLICATE_ITEM_KEY, std::move(error_msg),\n        std::move(suggestion), base::LynxErrorLevel::Error);\n    delegate_->OnErrorOccurred(std::move(error));\n  }\n  // update estimated height px from fiber\n  estimated_heights_px_.resize(item_keys.size(), -1);\n  for (const auto& pair : fiber_estimated_heights_px_) {\n    auto it = item_key_map_.find(pair.first);\n    if (item_key_map_.end() != it && it->second >= 0 &&\n        it->second < static_cast<int>(item_keys.size())) {\n      estimated_heights_px_[it->second] = pair.second;\n    }\n  }\n  // update estimated main axis size px from fiber\n  estimated_sizes_px_.resize(item_keys.size(), -1);\n  for (const auto& pair : fiber_estimated_sizes_px_) {\n    auto it = item_key_map_.find(pair.first);\n    if (item_key_map_.end() != it && it->second >= 0 &&\n        it->second < static_cast<int>(item_keys.size())) {\n      estimated_sizes_px_[it->second] = pair.second;\n    }\n  }\n  // update full span from fiber\n  full_spans_.clear();\n  for (const auto& item_key_str : fiber_full_spans_) {\n    auto it = item_key_map_.find(item_key_str);\n    if (item_key_map_.end() != it && it->second >= 0 &&\n        it->second < static_cast<int>(item_keys.size())) {\n      full_spans_.insert(it->second);\n    }\n  }\n  // update sticky top from fiber\n  sticky_tops_.clear();\n  for (const auto& item_key_str : fiber_sticky_tops_) {\n    auto it = item_key_map_.find(item_key_str);\n    if (item_key_map_.end() != it && it->second >= 0 &&\n        it->second < static_cast<int>(item_keys.size())) {\n      sticky_tops_.emplace_back(it->second);\n    }\n  }\n  std::sort(sticky_tops_.begin(), sticky_tops_.end());\n  // update sticky bottom from fiber\n  sticky_bottoms_.clear();\n  for (const auto& item_key_str : fiber_sticky_bottoms_) {\n    auto it = item_key_map_.find(item_key_str);\n    if (item_key_map_.end() != it && it->second >= 0 &&\n        it->second < static_cast<int>(item_keys.size())) {\n      sticky_bottoms_.emplace_back(it->second);\n    }\n  }\n  std::sort(sticky_bottoms_.begin(), sticky_bottoms_.end());\n  // update recyclable from fiber\n  unrecyclable_.clear();\n  for (const auto& item_key_str : fiber_unrecyclable_) {\n    auto it = item_key_map_.find(item_key_str);\n    if (item_key_map_.end() != it && it->second >= 0 &&\n        it->second < static_cast<int>(item_keys.size())) {\n      unrecyclable_.insert(it->second);\n    }\n  }\n  // save last diff result.\n  last_diff_result_ = diff_result_;\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_adapter_helper.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_ADAPTER_HELPER_H_\n#define CORE_LIST_DECOUPLED_ADAPTER_HELPER_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <vector>\n\n#include \"base/include/debug/lynx_error.h\"\n#include \"core/list/decoupled_list_types.h\"\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass AdapterHelper {\n public:\n  class Delegate {\n   public:\n    virtual void OnErrorOccurred(lynx::base::LynxError error) = 0;\n  };\n\n  class DiffResult {\n   public:\n    std::vector<std::string> item_keys_;\n    std::vector<int32_t> insertions_;\n    std::vector<int32_t> removals_;\n    std::vector<int32_t> update_from_;\n    std::vector<int32_t> update_to_;\n    std::vector<int32_t> move_from_;\n    std::vector<int32_t> move_to_;\n\n    bool HasValidDiff() const {\n      return insertions_.size() > 0 || removals_.size() > 0 ||\n             move_to_.size() > 0 || move_from_.size() > 0 ||\n             update_to_.size() > 0 || update_from_.size() > 0;\n    }\n    std::string ToString() const;\n  };\n  // Update diff info for Radon arch.\n  bool UpdateRadonDiffResult(const pub::Value& diff_result);\n\n  void UpdateItemKeys(const pub::Value& item_keys_value);\n\n  void UpdateEstimatedHeightsPx(const pub::Value& estimated_heights_px);\n\n  void UpdateEstimatedSizesPx(const pub::Value& estimated_sizes_px);\n\n  void UpdateFullSpans(const pub::Value& full_spans);\n\n  void UpdateStickyBottoms(const pub::Value& sticky_bottoms);\n\n  void UpdateStickyTops(const pub::Value& sticky_tops);\n\n  // Update diff info for Fiber arch.\n  void UpdateFiberInsertAction(const std::unique_ptr<pub::Value>& insert_action,\n                               bool only_parse_insertions = true);\n\n  void UpdateFiberRemoveAction(const std::unique_ptr<pub::Value>& remove_action,\n                               bool only_parse_removals = true);\n\n  void UpdateFiberUpdateAction(const std::unique_ptr<pub::Value>& update_action,\n                               bool only_parse_update = true);\n\n  void UpdateFiberExtraInfo();\n\n  void SetDelegate(AdapterHelper::Delegate* delegate) { delegate_ = delegate; }\n\n  int32_t GetDateCount() const {\n    return static_cast<int32_t>(diff_result_.item_keys_.size());\n  }\n\n  std::optional<std::string> GetItemKeyForIndex(int index) const {\n    if (index >= 0 && index < GetDateCount()) {\n      return std::make_optional<std::string>(diff_result_.item_keys_[index]);\n    }\n    return std::nullopt;\n  }\n\n  bool HasValidDiff() const { return diff_result_.HasValidDiff(); }\n\n  int GetIndexForItemKey(const std::string& item_key) const {\n    auto it = item_key_map_.end();\n    if (item_key_map_.end() != (it = item_key_map_.find(item_key))) {\n      return it->second;\n    }\n    return kInvalidIndex;\n  }\n\n  bool HasExpectedDiffAnimation() const {\n    return !(insertions().size() == item_keys().size() &&\n             update_from().empty() && update_to().empty() &&\n             move_from().empty() && move_to().empty() && removals().empty());\n  }\n\n  void ClearDiffResult() {\n    diff_result_.insertions_.clear();\n    diff_result_.removals_.clear();\n    diff_result_.update_from_.clear();\n    diff_result_.update_to_.clear();\n    diff_result_.move_from_.clear();\n    diff_result_.move_to_.clear();\n  }\n\n  inline const std::vector<std::string>& item_keys() const {\n    return diff_result_.item_keys_;\n  }\n  inline const std::vector<int32_t>& estimated_heights_px() const {\n    return estimated_heights_px_;\n  }\n  inline const std::vector<int32_t>& estimated_sizes_px() const {\n    return estimated_sizes_px_;\n  }\n  inline const std::unordered_map<std::string, int>& item_key_map() const {\n    return item_key_map_;\n  }\n  inline const std::unordered_set<int32_t>& full_spans() const {\n    return full_spans_;\n  }\n  inline const std::vector<int32_t>& sticky_bottoms() const {\n    return sticky_bottoms_;\n  }\n  inline const std::vector<int32_t>& sticky_tops() const {\n    return sticky_tops_;\n  }\n  inline const std::unordered_set<int32_t>& unrecyclable() const {\n    return unrecyclable_;\n  }\n  inline const std::vector<int32_t>& insertions() const {\n    return diff_result_.insertions_;\n  }\n  inline const std::vector<int32_t>& removals() const {\n    return diff_result_.removals_;\n  }\n  inline const std::vector<int32_t>& update_from() const {\n    return diff_result_.update_from_;\n  }\n  inline const std::vector<int32_t>& update_to() const {\n    return diff_result_.update_to_;\n  }\n  inline const std::vector<int32_t>& move_from() const {\n    return diff_result_.move_from_;\n  }\n  inline const std::vector<int32_t>& move_to() const {\n    return diff_result_.move_to_;\n  }\n\n private:\n  void UpdateInsertions(const pub::Value& diff_insertions);\n\n  void UpdateRemovals(const pub::Value& diff_removals);\n\n  void UpdateUpdateFrom(const pub::Value& diff_update_from);\n\n  void UpdateUpdateTo(const pub::Value& diff_update_to);\n\n  void UpdateMoveTo(const pub::Value& diff_move_to);\n\n  void UpdateMoveFrom(const pub::Value& diff_move_from);\n\n private:\n  AdapterHelper::Delegate* delegate_{nullptr};\n  DiffResult diff_result_;\n  DiffResult last_diff_result_;\n  // the data structure of the map is the <item-key,position>\n  std::unordered_map<std::string, int> item_key_map_;\n  std::unordered_set<int32_t> full_spans_;\n  std::vector<int32_t> sticky_tops_;\n  std::vector<int32_t> sticky_bottoms_;\n  // Deprecated: using estimated_sizes_px_\n  std::vector<int32_t> estimated_heights_px_;\n  std::vector<int32_t> estimated_sizes_px_;\n  std::unordered_set<int32_t> unrecyclable_;\n  // Fiber\n  // Deprecated: using fiber_estimated_sizes_px_\n  std::unordered_map<std::string, int32_t> fiber_estimated_heights_px_;\n  std::unordered_map<std::string, int32_t> fiber_estimated_sizes_px_;\n  std::unordered_set<std::string> fiber_full_spans_;\n  std::unordered_set<std::string> fiber_sticky_tops_;\n  std::unordered_set<std::string> fiber_sticky_bottoms_;\n  std::unordered_set<std::string> fiber_unrecyclable_;\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_ADAPTER_HELPER_H_\n"
  },
  {
    "path": "core/list/decoupled_batch_list_adapter.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_batch_list_adapter.h\"\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\nconst uint32_t ItemStatus::kNeverBind = 0x01 << 1;\nconst uint32_t ItemStatus::kUpdated = 0x01 << 2;\nconst uint32_t ItemStatus::kRemoved = 0x01 << 3;\nconst uint32_t ItemStatus::kInBinding = 0x01 << 4;\nconst uint32_t ItemStatus::kFinishedBinding = 0x01 << 5;\nconst uint32_t ItemStatus::kRecycled = 0x01 << 6;\n\nvoid BatchListAdapter::OnItemHolderInserted(ItemHolder* item_holder) {\n  if (!item_holder->item_key().empty()) {\n    const std::string& item_key = item_holder->item_key();\n    if (item_status_map_->find(item_key) != item_status_map_->end()) {\n      DLIST_LOGE(\"[\" << list_container_\n                     << \"] BatchListAdapter::OnItemHolderInserted: \"\n                     << \"repeat insert item key: \" << item_key);\n    }\n    (*item_status_map_)[item_key] = ItemStatus();\n  }\n}\n\nvoid BatchListAdapter::OnItemHolderRemoved(ItemHolder* item_holder) {\n  MarkItemStatus(item_holder->item_key(), ItemStatus::kRemoved);\n}\n\nvoid BatchListAdapter::OnItemHolderUpdateTo(ItemHolder* item_holder,\n                                            bool fiber_flush) {\n  const std::string& item_key = item_holder->item_key();\n  auto it = item_status_map_->find(item_key);\n  if (it != item_status_map_->end() && !IsNeverBind(it->second)) {\n    MarkItemStatus(item_key, ItemStatus::kUpdated);\n    // TODO(dingwang.wxx): remove this logic.\n    if (fiber_flush && GetItemElementDelegate(item_holder)) {\n      fiber_flush_item_holder_set_.insert(item_holder);\n    }\n  }\n}\n\nvoid BatchListAdapter::OnItemHolderReInsert(ItemHolder* item_holder) {\n  MarkItemStatus(item_holder->item_key(), ItemStatus::kNeverBind);\n}\n\nvoid BatchListAdapter::OnEnqueueElement(ItemHolder* item_holder) {\n  if (item_holder) {\n    // Note: This is only chance to erase list item element from map.\n    list_item_delegate_map_->erase(item_holder->item_key());\n  }\n}\n\nvoid BatchListAdapter::OnDataSetChanged() {\n  if (item_holder_map_) {\n    for (const auto& pair : *item_holder_map_) {\n      const std::string& item_key = pair.second->item_key();\n      auto it = item_status_map_->find(item_key);\n      if (it != item_status_map_->end() && !IsRemoved(it->second)) {\n        MarkItemStatus(item_key, ItemStatus::kNeverBind);\n      }\n    }\n  }\n}\n\nbool BatchListAdapter::BindItemHolder(ItemHolder* item_holder, int index,\n                                      bool preload_section /* = false */) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_BIND_ITEM_HOLDER,\n              [this, item_holder](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event(), item_holder);\n              });\n  if (!item_holder || index != item_holder->index() || preload_section) {\n    // Note: not supports preload section when using component cache.\n    return false;\n  }\n  return BindItemHolderInternal(item_holder, index) != kInvalidIndex;\n}\n\nvoid BatchListAdapter::BindItemHolders(const ItemHolderSet& item_holder_set) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_BIND_ITEM_HOLDERS,\n              \"batch_item_number\", item_holder_set.size());\n  const auto& value_factory = list_container_->value_factory();\n  if (value_factory) {\n    std::unique_ptr<pub::Value> index_array;\n    std::unique_ptr<pub::Value> operation_id_array;\n    if ((index_array = value_factory->CreateArray()) &&\n        (operation_id_array = value_factory->CreateArray())) {\n      for (ItemHolder* item_holder : item_holder_set) {\n        if (item_holder) {\n          const int index = item_holder->index();\n          int64_t operation_id =\n              BindItemHolderInternal(item_holder, index, false);\n          if (operation_id != kInvalidIndex) {\n            index_array->PushInt32ToArray(index);\n            operation_id_array->PushInt64ToArray(operation_id);\n          }\n        }\n      }\n      list_container_->list_delegate()->ComponentAtIndexes(\n          std::move(index_array), std::move(operation_id_array));\n    }\n  }\n}\n\nint64_t BatchListAdapter::BindItemHolderInternal(\n    ItemHolder* item_holder, int index, bool invoke_bind /* = true */) {\n  const std::string& item_key = item_holder->item_key();\n  auto it = item_status_map_->find(item_key);\n  if (it != item_status_map_->end()) {\n    const auto& item_status = it->second;\n    if (IsDirty(item_status) || IsRecycled(item_status)) {\n      ElementDelegate* list_delegate = list_container_->list_delegate();\n      // Generate binding key.\n      int64_t operation_id = GenerateOperationId();\n      // Check if the element is already in the component map. If it exists,\n      // it needs to be recycled before invoking ComponentAtIndex(). This is\n      // primarily for adapting to the Fiber architecture, where recycling\n      // must occur before re-rendering.\n      if (list_delegate->IsFiberArch() && GetItemElementDelegate(item_holder)) {\n        DLIST_LOGI(\"[\" << list_container_\n                       << \"] BatchListAdapter::BindItemHolderInternal: enqueue \"\n                          \"component before render with item_key = \"\n                       << item_holder->item_key() << \", index = \" << index);\n        RecycleItemHolder(item_holder);\n        list_container_->list_children_helper()->EraseFromLastBindingChildren(\n            item_holder);\n      }\n      // Mark status kInBinding.\n      (it->second).status_ = ItemStatus::kInBinding;\n      (it->second).operation_id_ = operation_id;\n      // Note: Update binding key map and operation_id_.\n      binding_key_map_->insert(std::make_pair(operation_id, item_key));\n      if (invoke_bind) {\n        DLIST_LOGI(\n            \"[\"\n            << list_container_\n            << \"] BatchListAdapter::BindItemHolderInternal: with item_key = \"\n            << item_key << \", index = \" << index << \", operation_id = \"\n            << operation_id << \", item_state = \" << it->second.ToString());\n        // TODO(dingwang.wxx): impl native state storage\n        list_delegate->ComponentAtIndex(index, operation_id);\n      }\n      return operation_id;\n    }\n  }\n  return kInvalidIndex;\n}\n\nvoid BatchListAdapter::OnFinishBindItemHolder(\n    ItemElementDelegate* list_item_delegate,\n    const std::shared_ptr<tasm::PipelineOptions>& options) {\n  int valid_bind_index =\n      OnFinishBindInternal(list_item_delegate, options->operation_id);\n  if (valid_bind_index != kInvalidIndex) {\n    // Note: Mark should_flush_finish_layout_ to determine whether needs to\n    // invoke FinishLayoutOperation().\n    list_container_->MarkShouldFlushFinishLayout(options->has_layout);\n    if (list_container_->intercept_depth() == 0) {\n      // Note: In MULTI_THREAD mode, if the list item has been rendered async,\n      // we should invoke list OnLayoutChildren. But in ALL_ON_UI mode, we\n      // should check intercept_depth_ value to make use that list will not\n      // invoke new layout pass in one layout.\n      list_container_->list_layout_manager()->OnLayoutChildren(\n          true, valid_bind_index);\n    }\n  }\n}\n\nvoid BatchListAdapter::OnFinishBindItemHolders(\n    const std::vector<ItemElementDelegate*>& list_item_delegate_array,\n    const std::shared_ptr<tasm::PipelineOptions>& options) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_FINISH_BIND_ITEM_HOLDERS,\n              \"batch_item_number\", list_item_delegate_array.size());\n\n  if (list_item_delegate_array.empty() || options->operation_ids_.empty() ||\n      list_item_delegate_array.size() != options->operation_ids_.size()) {\n    return;\n  }\n  bool has_valid_bind = false;\n  const size_t list_item_size = list_item_delegate_array.size();\n  // Traverse list items and operation ids array.\n  for (size_t i = 0; i < list_item_size; ++i) {\n    int valid_bind_index = OnFinishBindInternal(list_item_delegate_array[i],\n                                                options->operation_ids_[i]);\n    has_valid_bind |= valid_bind_index != kInvalidIndex;\n  }\n  if (has_valid_bind) {\n    // Note: Mark should_flush_finish_layout_ to determine whether needs to\n    // invoke FinishLayoutOperation().\n    list_container_->MarkShouldFlushFinishLayout(options->has_layout);\n    if (list_container_->intercept_depth() == 0) {\n      // Note: In MULTI_THREAD mode, if the list items have been rendered async,\n      // we should invoke list OnBatchLayoutChildren. But in ALL_ON_UI mode, we\n      // should check intercept_depth_ value to make use that list will not\n      // invoke new layout pass in one layout.\n      list_container_->list_layout_manager()->OnBatchLayoutChildren();\n    }\n  }\n}\n\nint BatchListAdapter::OnFinishBindInternal(\n    ItemElementDelegate* list_item_delegate, int64_t operation_id) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_FINISH_BIND_INTERNAL,\n              \"operation_id\", operation_id);\n  int valid_bind_index = kInvalidIndex;\n  if (!list_item_delegate) {\n    DLIST_LOGE(\"[\" << list_container_\n                   << \"] BatchListAdapter::OnFinishBindInternal: \"\n                   << \"null list item with operation_id = \" << operation_id);\n  } else {\n    ElementDelegate* list_delegate = list_container_->list_delegate();\n    auto binding_key_it = binding_key_map_->find(operation_id);\n    if (binding_key_it != binding_key_map_->end()) {\n      // Get item_key according to operation_id from binding_key_map_.\n      const std::string& item_key = binding_key_it->second;\n      // Note: The ItemStatus has the same lifecycle with ItemHolder, so it can\n      // avoid the case that the ItemHolder is destroyed.\n      auto it = item_status_map_->find(item_key);\n      if (it != item_status_map_->end() &&\n          it->second.operation_id_ == operation_id) {\n        // case 1. Found item_status and check operation_id is equal.\n        const auto& item_status = it->second;\n        if (IsBinding(item_status) && !IsDirty(item_status)) {\n          // case 1.1. This is a valid bind.\n          valid_bind_index = OnFinishValidBind(item_key, list_item_delegate);\n        } else {\n          // case 1.2. Other status, for example, if the item holder is marked\n          // dirty again, we can enqueue this component directly.\n          DLIST_LOGI(\"[\" << list_container_\n                         << \"] BatchListAdapter::OnFinishBindInternal: other \"\n                            \"state with item_key = \"\n                         << item_key\n                         << \", item_status = \" << item_status.ToString());\n          list_delegate->EnqueueComponent(list_item_delegate->GetImplId());\n        }\n        // Note: Need reset operation_id_.\n        it->second.operation_id_ = 0;\n      } else if (it != item_status_map_->end()) {\n        // case 2. The Operation_id is not the latest one in item_status_map_,\n        // the component can be recycled.\n        DLIST_LOGI(\"[\" << list_container_\n                       << \"] BatchListAdapter::OnFinishBindInternal: not \"\n                          \"latest binding with operation_id = \"\n                       << operation_id\n                       << \", item_status = \" << it->second.ToString());\n        list_delegate->EnqueueComponent(list_item_delegate->GetImplId());\n      } else {\n        // case 3. Not found item_status.\n        DLIST_LOGI(\"[\" << list_container_\n                       << \"] BatchListAdapter::OnFinishBindInternal: not found \"\n                          \"item_status with operation_id = \"\n                       << operation_id);\n        list_delegate->EnqueueComponent(list_item_delegate->GetImplId());\n      }\n      binding_key_map_->erase(binding_key_it);\n    } else {\n      DLIST_LOGE(\"[\" << list_container_\n                     << \"] BatchListAdapter::OnFinishBindInternal: \"\n                     << \"not in binding_key_map_ with operation_id = \"\n                     << operation_id);\n      list_delegate->EnqueueComponent(list_item_delegate->GetImplId());\n    }\n  }\n  return valid_bind_index;\n}\n\nint BatchListAdapter::OnFinishValidBind(\n    const std::string& item_key, ItemElementDelegate* list_item_delegate) {\n  DLIST_LOGI(\n      \"[\" << list_container_\n          << \"] BatchListAdapter::HandleValidBinding: valid with item_key = \"\n          << item_key << \", impl_id = \" << list_item_delegate->GetImplId());\n  // Note: This is only chance to insert list item element to element map.\n  list_item_delegate_map_->insert(std::make_pair(item_key, list_item_delegate));\n  MarkItemStatus(item_key, ItemStatus::kFinishedBinding);\n  // Note: here using item key to find ItemHolder is just for the rationality of\n  // code logic.\n  auto it = item_holder_map_->find(item_key);\n  if (it != item_holder_map_->end() && it->second) {\n    const auto& item_holder = it->second;\n    if (item_holder) {\n      // Update layout info from component to ItemHolder.\n      item_holder->UpdateLayoutFromItemDelegate(list_item_delegate);\n      // Add item_holder to attach_children_set.\n      list_container_->list_children_helper()->AttachChild(item_holder.get(),\n                                                           list_item_delegate);\n      return item_holder->index();\n    }\n  }\n  return kInvalidIndex;\n}\n\nvoid BatchListAdapter::RecycleItemHolder(ItemHolder* item_holder) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_BIND_ITEM_HOLDER,\n              [this, item_holder](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event(), item_holder);\n              });\n  if (item_holder) {\n    const std::string& item_key = item_holder->item_key();\n    auto it = item_status_map_->find(item_key);\n    if (it != item_status_map_->end()) {\n      const auto& item_status = it->second;\n      if (IsRemoved(item_status)) {\n        // If the data is removed, we need erase it from item_status_map_.\n        item_status_map_->erase(item_key);\n      } else if (IsFinishedBinding(item_status)) {\n        // If the data is finish binding, we set it to bound.\n        MarkItemStatus(item_key, ItemStatus::kRecycled);\n      }\n      EnqueueElement(item_holder);\n    }\n  }\n}\n\nbool BatchListAdapter::CheckItemStatus(const std::string& item_key,\n                                       uint32_t item_status) const {\n  auto it = item_status_map_->find(item_key);\n  if (it == item_status_map_->end()) {\n    DLIST_LOGE(\"[\" << list_container_ << \"] BatchListAdapter::CheckItemStatus: \"\n                   << \"not found item_key = \" << item_key);\n    return false;\n  }\n  return it->second == item_status;\n}\n\nvoid BatchListAdapter::MarkItemStatus(const std::string& item_key,\n                                      uint32_t item_status) {\n  auto it = item_status_map_->end();\n  if (item_key.empty() ||\n      ((it = item_status_map_->find(item_key)) == item_status_map_->end())) {\n    return;\n  }\n  (it->second).status_ = item_status;\n}\n\n#if ENABLE_TRACE_PERFETTO\nvoid BatchListAdapter::UpdateTraceDebugInfo(TraceEvent* event,\n                                            ItemHolder* item_holder) const {\n  ListAdapter::UpdateTraceDebugInfo(event, item_holder);\n  auto* adapter_type_info = event->add_debug_annotations();\n  adapter_type_info->set_name(\"adapter_type\");\n  adapter_type_info->set_string_value(\"batch\");\n}\n#endif\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_batch_list_adapter.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_BATCH_LIST_ADAPTER_H_\n#define CORE_LIST_DECOUPLED_BATCH_LIST_ADAPTER_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/include/string/string_utils.h\"\n#include \"core/list/decoupled_list_adapter.h\"\n#include \"core/list/list_item_element_delegate.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ItemStatus {\n public:\n  static const uint32_t kNeverBind;\n  static const uint32_t kUpdated;\n  static const uint32_t kRemoved;\n  static const uint32_t kInBinding;\n  static const uint32_t kFinishedBinding;\n  static const uint32_t kRecycled;\n\n  ItemStatus(uint32_t status = kNeverBind)\n      : status_(status), operation_id_(0) {}\n\n  std::string ToString() const {\n    return base::FormatString(\"ItemStatus[status_=%d, operation_id_=%ld]\",\n                              status_, operation_id_);\n  }\n\n  bool operator==(const ItemStatus& rhs) const {\n    return status_ == rhs.status_;\n  }\n\n  bool operator!=(const ItemStatus& rhs) const { return !operator==(rhs); }\n\n  uint32_t status_{kNeverBind};\n  int64_t operation_id_{0};\n};\n\nusing ListItemDelegateMap =\n    std::unordered_map<std::string, ItemElementDelegate*>;\nusing ItemStatusMap = std::unordered_map<std::string, ItemStatus>;\nusing BindingKeyMap = std::unordered_map<int64_t, std::string>;\n\nclass BatchListAdapter : public ListAdapter {\n public:\n  BatchListAdapter(ListContainerImpl* list_container_impl)\n      : ListAdapter(list_container_impl),\n        list_item_delegate_map_(std::make_unique<ListItemDelegateMap>()),\n        item_status_map_(std::make_unique<ItemStatusMap>()),\n        binding_key_map_(std::make_unique<BindingKeyMap>()) {}\n\n  ~BatchListAdapter() override = default;\n\n  BatchListAdapter(const BatchListAdapter& rhs) = delete;\n\n  BatchListAdapter& operator=(const BatchListAdapter& rhs) = delete;\n\n  BatchListAdapter(ListAdapter&& rhs) noexcept : ListAdapter(std::move(rhs)) {\n    list_item_delegate_map_ = std::make_unique<ListItemDelegateMap>();\n    item_status_map_ = std::make_unique<ItemStatusMap>();\n    binding_key_map_ = std::make_unique<BindingKeyMap>();\n  }\n\n  BatchListAdapter& operator=(ListAdapter&& rhs) noexcept {\n    if (this != &rhs) {\n      ListAdapter::operator=(std::move(rhs));\n      list_item_delegate_map_ = std::make_unique<ListItemDelegateMap>();\n      item_status_map_ = std::make_unique<ItemStatusMap>();\n      binding_key_map_ = std::make_unique<BindingKeyMap>();\n    }\n    return *this;\n  }\n\n protected:\n  // Handle diff insert.\n  void OnItemHolderInserted(ItemHolder* item_holder) override;\n\n  // Handle diff removed.\n  void OnItemHolderRemoved(ItemHolder* item_holder) override;\n\n  // Handle diff update from\n  void OnItemHolderUpdateFrom(ItemHolder* item_holder) override {}\n\n  // Handle diff update to\n  void OnItemHolderUpdateTo(ItemHolder* item_holder, bool fiber_flush) override;\n\n  // Handle diff moved from\n  void OnItemHolderMovedFrom(ItemHolder* item_holder) override {}\n\n  // Handle diff moved from\n  void OnItemHolderMovedTo(ItemHolder* item_holder) override {}\n\n  // Handle diff remove and insert again.\n  void OnItemHolderReInsert(ItemHolder* item_holder) override;\n\n  void OnEnqueueElement(ItemHolder* item_holder) override;\n\n#if ENABLE_TRACE_PERFETTO\n  void UpdateTraceDebugInfo(TraceEvent* event,\n                            ItemHolder* item_holder) const override;\n#endif\n\n public:\n  // Handle full data updated.\n  void OnDataSetChanged() override;\n\n  // Bind the item holder with index.\n  bool BindItemHolder(ItemHolder* item_holder, int index,\n                      bool preload_section = false) override;\n\n  // Bind item holders in the set.\n  void BindItemHolders(const ItemHolderSet& item_holder_set) override;\n\n  // Finish bind item holder with element.\n  void OnFinishBindItemHolder(\n      ItemElementDelegate* list_item_delegate,\n      const std::shared_ptr<tasm::PipelineOptions>& options) override;\n\n  // Finish bind item holders with elements. Note: no need to implement.\n  void OnFinishBindItemHolders(\n      const std::vector<ItemElementDelegate*>& list_item_delegate_array,\n      const std::shared_ptr<tasm::PipelineOptions>& options) override;\n\n  // Recycle ItemHolder.\n  void RecycleItemHolder(ItemHolder* item_holder) override;\n\n  // Return whether the ItemHolder has already been bound, if return true, it\n  // means the ItemHolder is a no dirty node, but with no valid list item\n  // element.\n  bool IsRecycled(const ItemHolder* item_holder) override {\n    return CheckItemStatus(item_holder->item_key(), ItemStatus::kRecycled);\n  }\n\n  // Return whether the ItemHolder is in binding.\n  bool IsBinding(const ItemHolder* item_holder) override {\n    auto it = item_status_map_->find(item_holder->item_key());\n    if (it != item_status_map_->end()) {\n      return (it->second).operation_id_ != 0;\n    }\n    return false;\n  }\n\n  // Return whether the ItemHolder is in finish binding, if return true, it\n  // means the ItemHolder is a no dirty node with valid list item element.\n  bool IsFinishedBinding(const ItemHolder* item_holder) override {\n    return CheckItemStatus(item_holder->item_key(),\n                           ItemStatus::kFinishedBinding);\n  }\n\n  // Return whether the ItemHolder is dirty\n  bool IsDirty(const ItemHolder* item_holder) override {\n    return CheckItemStatus(item_holder->item_key(), ItemStatus::kNeverBind) ||\n           CheckItemStatus(item_holder->item_key(), ItemStatus::kUpdated);\n  }\n\n  // Return whether the ItemHolder is update_to\n  bool IsUpdated(const ItemHolder* item_holder) override {\n    return CheckItemStatus(item_holder->item_key(), ItemStatus::kUpdated);\n  }\n\n  // Return whether the ItemHolder is removed\n  bool IsRemoved(const ItemHolder* item_holder) override {\n    return CheckItemStatus(item_holder->item_key(), ItemStatus::kRemoved);\n  }\n\n  ItemElementDelegate* GetItemElementDelegate(\n      const ItemHolder* item_holder) override {\n    return GetItemElementDelegate(item_holder->item_key());\n  }\n\n private:\n  int64_t BindItemHolderInternal(ItemHolder* item_holder, int index,\n                                 bool invoke_bind = true);\n\n  int OnFinishBindInternal(ItemElementDelegate* list_item_delegate,\n                           int64_t operation_id);\n\n  int OnFinishValidBind(const std::string& item_key,\n                        ItemElementDelegate* list_item_delegate);\n\n  void MarkItemStatus(const std::string& item_key, uint32_t item_status);\n\n  bool CheckItemStatus(const std::string& item_key, uint32_t item_status) const;\n\n  ItemElementDelegate* GetItemElementDelegate(\n      const std::string& item_key) const {\n    auto it = list_item_delegate_map_->find(item_key);\n    if (it != list_item_delegate_map_->end()) {\n      return it->second;\n    }\n    return nullptr;\n  }\n\n  bool IsBinding(const std::string& item_key) const {\n    auto it = item_status_map_->find(item_key);\n    if (it != item_status_map_->end()) {\n      return (it->second).operation_id_ != 0;\n    }\n    return false;\n  }\n\n  bool IsBinding(const ItemStatus& item_status) const {\n    return item_status.operation_id_ != 0;\n  }\n\n  bool IsFinishedBinding(const ItemStatus& item_status) const {\n    return item_status == ItemStatus::kFinishedBinding;\n  }\n\n  bool IsDirty(const ItemStatus& item_status) const {\n    return item_status == ItemStatus::kNeverBind ||\n           item_status == ItemStatus::kUpdated;\n  }\n\n  bool IsRecycled(const ItemStatus& item_status) const {\n    return item_status == ItemStatus::kRecycled;\n  }\n\n  bool IsRemoved(const ItemStatus& item_status) const {\n    return item_status == ItemStatus::kRemoved;\n  }\n\n  bool IsNeverBind(const ItemStatus& item_status) const {\n    return item_status == ItemStatus::kNeverBind;\n  }\n\n private:\n  // <item-key, ItemDelegate*> map\n  std::unique_ptr<ListItemDelegateMap> list_item_delegate_map_;\n  // <item-key, ItemStatus> map\n  std::unique_ptr<ItemStatusMap> item_status_map_;\n  // <operation-id, item-key> map\n  std::unique_ptr<BindingKeyMap> binding_key_map_;\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_BATCH_LIST_ADAPTER_H_\n"
  },
  {
    "path": "core/list/decoupled_default_list_adapter.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_default_list_adapter.h\"\n\n#include <memory>\n#include <string>\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\nvoid DefaultListAdapter::OnItemHolderRemoved(ItemHolder* item_holder) {\n  if (item_holder) {\n    item_holder->MarkDirty(true);\n    item_holder->MarkRemoved(true);\n  }\n}\n\nvoid DefaultListAdapter::OnItemHolderUpdateFrom(ItemHolder* item_holder) {\n  if (item_holder) {\n    item_holder->MarkDirty(true);\n  }\n}\n\nvoid DefaultListAdapter::OnItemHolderUpdateTo(ItemHolder* item_holder,\n                                              bool fiber_flush) {\n  if (item_holder) {\n    item_holder->MarkDirty(true);\n    item_holder->MarkDiffStatus(DiffStatus::kUpdateTo);\n    // TODO(dingwang.wxx): remove this logic\n    if (fiber_flush && GetItemElementDelegate(item_holder)) {\n      fiber_flush_item_holder_set_.insert(item_holder);\n    }\n  }\n}\n\nvoid DefaultListAdapter::OnItemHolderMovedFrom(ItemHolder* item_holder) {\n  if (item_holder) {\n    item_holder->MarkDirty(true);\n  }\n}\n\nvoid DefaultListAdapter::OnItemHolderMovedTo(ItemHolder* item_holder) {\n  if (item_holder) {\n    item_holder->MarkDirty(true);\n  }\n}\n\nvoid DefaultListAdapter::OnItemHolderReInsert(ItemHolder* item_holder) {\n  if (item_holder) {\n    item_holder->MarkDirty(true);\n    item_holder->MarkRemoved(false);\n  }\n}\n\nvoid DefaultListAdapter::OnDataSetChanged() {\n  if (item_holder_map_) {\n    for (const auto& pair : *item_holder_map_) {\n      if (pair.second && !(pair.second->removed())) {\n        pair.second->MarkDirty(true);\n      }\n    }\n  }\n}\n\nvoid DefaultListAdapter::OnEnqueueElement(ItemHolder* item_holder) {\n  if (item_holder) {\n    if (list_container_->list_event_manager()) {\n      list_container_->list_event_manager()->SendExposureEvent(\n          kEventNodeDisappear, item_holder);\n    }\n    item_holder->SetItemDelegate(nullptr);\n  }\n}\n\n// Bind ItemHolder for the specified index. For each invoke\n// ComponentAtIndex() to render a child element, a unique operation-id is\n// generated and the pair <operation-id, ItemHolder> is added to a map.\nbool DefaultListAdapter::BindItemHolder(ItemHolder* item_holder, int index,\n                                        bool preload_section /* = false */) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_BIND_ITEM_HOLDER,\n              [this, item_holder](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event(), item_holder);\n              });\n  if (!item_holder || index != item_holder->index() ||\n      (preload_section && item_holder->virtual_dom_preloaded())) {\n    return false;\n  }\n  if (IsDirty(item_holder) || IsRecycled(item_holder)) {\n    ElementDelegate* list_delegate = list_container_->list_delegate();\n    int64_t operation_id = GenerateOperationId();\n    (*binding_item_holder_weak_map_)[operation_id] =\n        item_holder->WeakFromThis();\n    // In ReactLynx 3.0, binding item_holder twice without enqueuing will result\n    // in cloning of the old element. This MR aims to avoid this scenario by\n    // mandating enqueuing before binding.\n    if (list_delegate->IsFiberArch() && GetItemElementDelegate(item_holder)) {\n      DLIST_LOGI(\"[\" << list_container_\n                     << \"] DefaultListAdapter::BindItemHolder: enqueue \"\n                        \"component before render with item_key = \"\n                     << item_holder->item_key() << \", index = \" << index);\n      RecycleItemHolder(item_holder);\n    }\n    item_holder->MarkDirty(false);\n    item_holder->MarkDiffStatus(DiffStatus::kValid);\n    item_holder->SetOperationId(operation_id);\n    // Note: before invoking ComponentAtIndex(), we use GetItemElementDelegate()\n    // to get latest item delegate from ItemHolder, and if got nullptr, we\n    // should send exposure event after invoking ComponentAtIndex().\n    ItemElementDelegate* list_item_delegate =\n        GetItemElementDelegate(item_holder);\n    bool should_send_exposure_event = list_item_delegate == nullptr;\n    DLIST_LOGI(\"[\" << list_container_\n                   << \"] DefaultListAdapter::BindItemHolder: with index = \"\n                   << index << \", item_key = \" << item_holder->item_key()\n                   << \", operation_id = \" << operation_id);\n    bool should_request_state_restore =\n        list_container_->should_request_state_restore();\n    if (list_delegate->ComponentAtIndex(index, operation_id,\n                                        should_request_state_restore)) {\n      item_holder->MarkVirtualDomPreloaded(true);\n      // TODO(dingwang.wxx): Move the events invocations in finishing bind.\n      list_item_delegate = GetItemElementDelegate(item_holder);\n      if (should_send_exposure_event && list_item_delegate) {\n        list_item_delegate->OnListItemWillAppear(item_holder->item_key());\n      }\n      if (should_send_exposure_event) {\n        list_container_->list_event_manager()->SendExposureEvent(\n            kEventNodeAppear, item_holder);\n      }\n      return true;\n    }\n  }\n  return false;\n}\n\n// When the rendering of the list's child node is complete, this method will\n// be invoked.\nvoid DefaultListAdapter::OnFinishBindItemHolder(\n    ItemElementDelegate* list_item_delegate,\n    const std::shared_ptr<tasm::PipelineOptions>& option) {\n  if (!list_item_delegate) {\n    DLIST_LOGE(\"[\" << list_container_\n                   << \"] DefaultListAdapter::OnFinishBindItemHolder: \"\n                   << \"list_item_delegate is nullptr\");\n    return;\n  }\n  int64_t operation_id = option->operation_id;\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, DEFAULT_LIST_ADAPTER_FINISH_BIND_ITEM_HOLDER,\n              \"operation_id\", operation_id);\n  ElementDelegate* list_delegate = list_container_->list_delegate();\n  // Find the corresponding ItemHolder based on the operation_id and bind the\n  // ItemHolder to the child element.\n  if (auto it = binding_item_holder_weak_map_->find(operation_id);\n      it != binding_item_holder_weak_map_->end()) {\n    ItemHolder* binding_item_holder = nullptr;\n    if ((binding_item_holder = it->second.get()) &&\n        binding_item_holder->operation_id() == operation_id) {\n      binding_item_holder_weak_map_->erase(operation_id);\n      int index = binding_item_holder->index();\n      const std::string& item_key = binding_item_holder->item_key();\n      TRACE_EVENT(LYNX_TRACE_CATEGORY,\n                  DEFAULT_LIST_ADAPTER_FINISH_BIND_ITEM_HOLDER_FINISH, \"index\",\n                  index);\n      DLIST_LOGI(\n          \"[\" << list_container_\n              << \"] DefaultListAdapter::OnFinishBindItemHolder: with index = \"\n              << index << \", item_key = \" << item_key << \", operation_id = \"\n              << operation_id << \", list item element impl_id = \"\n              << list_item_delegate->GetImplId());\n      // Update item holder's info with list_item_delegate.\n      binding_item_holder->SetItemDelegate(list_item_delegate);\n      binding_item_holder->UpdateLayoutFromItemDelegate();\n      binding_item_holder->SetOrientation(\n          list_container_->list_layout_manager()->orientation());\n      list_delegate->CheckZIndex(list_item_delegate);\n      // Reset operation id.\n      binding_item_holder->SetOperationId(0);\n\n      // Add ItemHolder to attach_children_.\n      list_container_->list_children_helper()->AttachChild(binding_item_holder,\n                                                           list_item_delegate);\n      // Note: Mark should_flush_finish_layout_ to determine whether needs to\n      // invoke FinishLayoutOperation().\n      list_container_->MarkShouldFlushFinishLayout(option->has_layout);\n      if (list_container_->intercept_depth() == 0) {\n        list_container_->list_layout_manager()->OnLayoutChildren(true, index);\n      }\n      list_delegate->ReportListItemLifecycleStatistic(option, item_key);\n    } else {\n      DLIST_LOGI(\"[\" << list_container_\n                     << \"] DefaultListAdapter::OnFinishBindItemHolder: \"\n                        \"ItemHolder is destroy with operation_id = \"\n                     << operation_id);\n      binding_item_holder_weak_map_->erase(operation_id);\n      list_delegate->EnqueueComponent(list_item_delegate->GetImplId());\n    }\n  } else {\n    DLIST_LOGE(\n        \"[\" << list_container_\n            << \"] DefaultListAdapter::OnFinishBindItemHolder: \"\n            << \"not in binding_item_holder_weak_map_ with operation_id = \"\n            << operation_id);\n    const auto& attached_delegate_item_holder_map =\n        list_container_->list_children_helper()\n            ->attached_delegate_item_holder_map();\n    if (attached_delegate_item_holder_map.find(list_item_delegate) !=\n        attached_delegate_item_holder_map.end()) {\n      // The component is not attached to any item holder, so we can enqueue\n      // this component directly.\n      list_delegate->EnqueueComponent(list_item_delegate->GetImplId());\n    }\n  }\n}\n\n// Recycle ItemHolder.\nvoid DefaultListAdapter::RecycleItemHolder(ItemHolder* item_holder) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_RECYCLE_ITEM_HOLDER,\n              [this, item_holder](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event(), item_holder);\n              });\n  if (item_holder) {\n    EnqueueElement(item_holder);\n    list_container_->list_delegate()->FlushImmediately();\n  }\n}\n\n#if ENABLE_TRACE_PERFETTO\nvoid DefaultListAdapter::UpdateTraceDebugInfo(TraceEvent* event,\n                                              ItemHolder* item_holder) const {\n  ListAdapter::UpdateTraceDebugInfo(event, item_holder);\n  auto* adapter_type_info = event->add_debug_annotations();\n  adapter_type_info->set_name(\"adapter_type\");\n  adapter_type_info->set_string_value(\"default\");\n}\n#endif\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_default_list_adapter.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_DEFAULT_LIST_ADAPTER_H_\n#define CORE_LIST_DECOUPLED_DEFAULT_LIST_ADAPTER_H_\n\n#include <memory>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"core/list/decoupled_list_adapter.h\"\n\nnamespace lynx {\nnamespace list {\n\nusing BindingItemHolderWeakMap =\n    std::unordered_map<int64_t, fml::WeakPtr<ItemHolder>>;\n\nclass DefaultListAdapter : public ListAdapter {\n public:\n  DefaultListAdapter(ListContainerImpl* list_container_impl)\n      : ListAdapter(list_container_impl),\n        binding_item_holder_weak_map_(\n            std::make_unique<BindingItemHolderWeakMap>()) {}\n\n  ~DefaultListAdapter() override = default;\n\n  DefaultListAdapter(const DefaultListAdapter& rhs) = delete;\n\n  DefaultListAdapter& operator=(const DefaultListAdapter& rhs) = delete;\n\n  DefaultListAdapter(ListAdapter&& rhs) noexcept\n      : ListAdapter(std::move(rhs)) {}\n\n  DefaultListAdapter& operator=(ListAdapter&& rhs) noexcept {\n    if (this != &rhs) {\n      ListAdapter::operator=(std::move(rhs));\n    }\n    return *this;\n  }\n\n protected:\n  // Handle diff insert.\n  void OnItemHolderInserted(ItemHolder* item_holder) override {}\n\n  // Handle diff removed.\n  void OnItemHolderRemoved(ItemHolder* item_holder) override;\n\n  // Handle diff update from\n  void OnItemHolderUpdateFrom(ItemHolder* item_holder) override;\n\n  // Handle diff update to\n  void OnItemHolderUpdateTo(ItemHolder* item_holder, bool fiber_flush) override;\n\n  // Handle diff moved from\n  void OnItemHolderMovedFrom(ItemHolder* item_holder) override;\n\n  // Handle diff moved to\n  void OnItemHolderMovedTo(ItemHolder* item_holder) override;\n\n  // Handle diff remove and insert again.\n  void OnItemHolderReInsert(ItemHolder* item_holder) override;\n\n  void OnEnqueueElement(ItemHolder* item_holder) override;\n\n#if ENABLE_TRACE_PERFETTO\n  void UpdateTraceDebugInfo(TraceEvent* event,\n                            ItemHolder* item_holder) const override;\n#endif\n\n public:\n  // Handle full data updated.\n  void OnDataSetChanged() override;\n\n  // Bind the item holder with index.\n  bool BindItemHolder(ItemHolder* item_holder, int index,\n                      bool preload_section = false) override;\n\n  // Bind item holders in the set. Note: no need to implement.\n  void BindItemHolders(const ItemHolderSet& item_holder_set) override {}\n\n  // Finish bind item holder with element.\n  void OnFinishBindItemHolder(\n      ItemElementDelegate* list_item_delegate,\n      const std::shared_ptr<tasm::PipelineOptions>& options) override;\n\n  // Finish bind item holders with elements. Note: no need to implement.\n  void OnFinishBindItemHolders(\n      const std::vector<ItemElementDelegate*>& list_item_delegate_array,\n      const std::shared_ptr<tasm::PipelineOptions>& options) override {}\n\n  // Recycle ItemHolder.\n  void RecycleItemHolder(ItemHolder* item_holder) override;\n\n  // Return whether the ItemHolder has already been bound, if return true, it\n  // means the ItemHolder is a no dirty node, but with no valid list item\n  // element.\n  bool IsRecycled(const ItemHolder* item_holder) override {\n    return !item_holder->dirty_ && item_holder->operation_id_ == 0 &&\n           !item_holder->item_delegate_;\n  }\n\n  // Return whether the ItemHolder is in binding.\n  bool IsBinding(const ItemHolder* item_holder) override {\n    return item_holder->operation_id_ != 0;\n  }\n\n  // Return whether the ItemHolder is in finish binding, if return true, it\n  // means the ItemHolder is a no dirty node with valid list item element.\n  bool IsFinishedBinding(const ItemHolder* item_holder) override {\n    return !item_holder->dirty_ && item_holder->operation_id_ == 0 &&\n           item_holder->item_delegate_;\n  }\n\n  // Return whether the ItemHolder is dirty\n  bool IsDirty(const ItemHolder* item_holder) override {\n    return item_holder->dirty_;\n  }\n\n  // Return whether the ItemHolder is update_to\n  bool IsUpdated(const ItemHolder* item_holder) override {\n    return item_holder->dirty_ && item_holder->is_updated();\n  }\n\n  // Return whether the ItemHolder is removed\n  bool IsRemoved(const ItemHolder* item_holder) override {\n    return item_holder->removed_;\n  }\n\n  ItemElementDelegate* GetItemElementDelegate(\n      const ItemHolder* item_holder) override {\n    return item_holder->item_delegate_;\n  }\n\n private:\n  std::unique_ptr<BindingItemHolderWeakMap> binding_item_holder_weak_map_{\n      nullptr};\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_DEFAULT_LIST_ADAPTER_H_\n"
  },
  {
    "path": "core/list/decoupled_default_list_adapter_unittest.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/list/decoupled_default_list_adapter.h\"\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/value_wrapper/value_impl_lepus.h\"\n#include \"testing/fiber_data_source.h\"\n#include \"testing/mock_list_element.h\"\n#include \"testing/mock_list_item_element.h\"\n#include \"testing/radon_data_source.h\"\n#include \"testing/utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace list {\n\nusing ::testing::_;\nusing ::testing::Return;\n\nclass DefaultListAdapterTest : public ::testing::Test {\n public:\n  DefaultListAdapterTest() = default;\n  ~DefaultListAdapterTest() override = default;\n\n  std::unique_ptr<MockListElement> mock_list_element_{nullptr};\n  std::unique_ptr<ListContainerImpl> list_container_impl_{nullptr};\n  std::shared_ptr<pub::PubValueFactoryDefault> value_factory_{nullptr};\n  ListLayoutManager* list_layout_manager_{nullptr};\n  ListAdapter* list_adapter_{nullptr};\n  AdapterHelper* list_adapter_helper_{nullptr};\n\n  void SetUp() override {\n    value_factory_ = std::make_shared<pub::PubValueFactoryDefault>();\n    mock_list_element_ = std::make_unique<MockListElement>();\n    list_container_impl_ = std::make_unique<ListContainerImpl>(\n        mock_list_element_.get(), value_factory_);\n    list_layout_manager_ = list_container_impl_->list_layout_manager();\n    list_adapter_ = list_container_impl_->list_adapter();\n    list_adapter_helper_ = list_adapter_->list_adapter_helper();\n  }\n\n  void InitFiberDataSource() {\n    testing::InsertAction insert_action{\n        .insert_ops_ = {\n            {.position_ = 0, \"A_0\", 100, false, false, false, false},\n            {.position_ = 1, \"B_1\", 100, false, false, false, false},\n            {.position_ = 2, \"C_2\", 100, false, false, false, false},\n            {.position_ = 3, \"D_3\", 100, false, false, false, false},\n            {.position_ = 4, \"E_4\", 100, false, false, false, false},\n            {.position_ = 5, \"F_5\", 100, false, false, false, false},\n            {.position_ = 6, \"G_6\", 100, false, false, false, false},\n            {.position_ = 7, \"H_7\", 100, false, false, false, false},\n            {.position_ = 8, \"I_8\", 100, false, false, false, false},\n            {.position_ = 9, \"J_9\", 100, false, false, false, false},\n        }};\n    testing::FiberDataSource fiber_data_source{\n        .insert_action_ = insert_action,\n    };\n    LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n        FiberUpdateListInfo, lepus::Value(fiber_data_source.Resolve()));\n    list_container_impl_->ResolveAttribute(*key, value);\n    list_container_impl_->PropsUpdateFinish();\n  }\n};\n\nTEST_F(DefaultListAdapterTest, OnItemHolderRemoved) {\n  std::unique_ptr<ItemHolder> item_holder = std::make_unique<ItemHolder>(\n      0, \"A_0\", list_container_impl_->list_animation_manager());\n  item_holder->MarkDirty(false);\n  item_holder->MarkRemoved(false);\n  list_adapter_->OnItemHolderRemoved(item_holder.get());\n  EXPECT_TRUE(item_holder->dirty());\n  EXPECT_TRUE(item_holder->removed());\n}\n\nTEST_F(DefaultListAdapterTest, OnItemHolderUpdateFrom) {\n  std::unique_ptr<ItemHolder> item_holder = std::make_unique<ItemHolder>(\n      0, \"A_0\", list_container_impl_->list_animation_manager());\n  item_holder->MarkDirty(false);\n  list_adapter_->OnItemHolderUpdateFrom(item_holder.get());\n  EXPECT_TRUE(item_holder->dirty());\n}\n\nTEST_F(DefaultListAdapterTest, OnItemHolderUpdateTo) {\n  std::unique_ptr<ItemHolder> item_holder = std::make_unique<ItemHolder>(\n      0, \"A_0\", list_container_impl_->list_animation_manager());\n  item_holder->MarkDirty(false);\n  item_holder->MarkDiffStatus(DiffStatus::kValid);\n  list_adapter_->OnItemHolderUpdateTo(item_holder.get(), false);\n  EXPECT_TRUE(item_holder->dirty());\n  EXPECT_TRUE(item_holder->is_updated());\n}\n\nTEST_F(DefaultListAdapterTest, OnItemHolderMovedFrom) {\n  std::unique_ptr<ItemHolder> item_holder = std::make_unique<ItemHolder>(\n      0, \"A_0\", list_container_impl_->list_animation_manager());\n  item_holder->MarkDirty(false);\n  list_adapter_->OnItemHolderMovedFrom(item_holder.get());\n  EXPECT_TRUE(item_holder->dirty());\n}\n\nTEST_F(DefaultListAdapterTest, OnItemHolderMovedTo) {\n  std::unique_ptr<ItemHolder> item_holder = std::make_unique<ItemHolder>(\n      0, \"A_0\", list_container_impl_->list_animation_manager());\n  item_holder->MarkDirty(false);\n  list_adapter_->OnItemHolderMovedTo(item_holder.get());\n  EXPECT_TRUE(item_holder->dirty());\n}\n\nTEST_F(DefaultListAdapterTest, OnItemHolderReInsert) {\n  std::unique_ptr<ItemHolder> item_holder = std::make_unique<ItemHolder>(\n      0, \"A_0\", list_container_impl_->list_animation_manager());\n  item_holder->MarkRemoved(true);\n  item_holder->MarkDirty(true);\n  list_adapter_->OnItemHolderReInsert(item_holder.get());\n  EXPECT_TRUE(item_holder->dirty());\n  EXPECT_FALSE(item_holder->removed());\n}\n\nTEST_F(DefaultListAdapterTest, OnDataSetChanged) {\n  InitFiberDataSource();\n  for (int i = 0; i < list_adapter_->GetDataCount(); ++i) {\n    ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(i);\n    item_holder->MarkDirty(false);\n  }\n  list_adapter_->OnDataSetChanged();\n  for (int i = 0; i < list_adapter_->GetDataCount(); ++i) {\n    ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(i);\n    EXPECT_TRUE(list_adapter_->IsDirty(item_holder));\n  }\n}\n\nTEST_F(DefaultListAdapterTest, BindItemHolder) {\n  InitFiberDataSource();\n  list_container_impl_->StartInterceptListElementUpdated();\n  int index = 0;\n  ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(index);\n  // Check Bind Item Holder\n  EXPECT_CALL(*mock_list_element_, ComponentAtIndex(_, _, _))\n      .WillOnce(Return(true));\n  EXPECT_TRUE(list_adapter_->BindItemHolder(item_holder, index, false));\n  EXPECT_FALSE(list_adapter_->IsDirty(item_holder));\n  EXPECT_TRUE(list_adapter_->IsBinding(item_holder));\n  EXPECT_FALSE(list_adapter_->IsRecycled(item_holder));\n  EXPECT_FALSE(list_adapter_->IsFinishedBinding(item_holder));\n  EXPECT_EQ(list_adapter_->GetItemElementDelegate(item_holder), nullptr);\n  list_container_impl_->StopInterceptListElementUpdated();\n}\n\nTEST_F(DefaultListAdapterTest, OnFinishBindItemHolder) {\n  InitFiberDataSource();\n  list_container_impl_->StartInterceptListElementUpdated();\n  int index = 0;\n  ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(index);\n  EXPECT_CALL(*mock_list_element_, ComponentAtIndex(_, _, _))\n      .WillOnce(Return(true));\n  EXPECT_TRUE(list_adapter_->BindItemHolder(item_holder, index, false));\n  EXPECT_FALSE(list_adapter_->IsDirty(item_holder));\n  EXPECT_TRUE(list_adapter_->IsBinding(item_holder));\n  EXPECT_FALSE(list_adapter_->IsRecycled(item_holder));\n  EXPECT_FALSE(list_adapter_->IsFinishedBinding(item_holder));\n  EXPECT_EQ(list_adapter_->GetItemElementDelegate(item_holder), nullptr);\n  // Check Finish Bind Item Holder.\n  auto pipeline = std::make_shared<tasm::PipelineOptions>();\n  pipeline->operation_id = item_holder->operation_id_;\n  const auto& item_key = item_holder->item_key();\n  mock_list_element_->AddListItemElement(\n      item_key, std::make_unique<MockListItemElement>(\n                    mock_list_element_->GetImplId() + 1));\n  list_adapter_->OnFinishBindItemHolder(\n      mock_list_element_->GetListItemElement(item_key), pipeline);\n  EXPECT_FALSE(list_adapter_->IsBinding(item_holder));\n  EXPECT_FALSE(list_adapter_->IsRecycled(item_holder));\n  EXPECT_TRUE(list_adapter_->IsFinishedBinding(item_holder));\n  EXPECT_FALSE(list_adapter_->IsDirty(item_holder));\n  EXPECT_TRUE(list_adapter_->GetItemElementDelegate(item_holder) != nullptr);\n  list_container_impl_->StopInterceptListElementUpdated();\n}\n\nTEST_F(DefaultListAdapterTest, RadonDiffCase0) {\n  // Before\n  testing::RadonDataSource radon_data_source{\n      .item_keys_ = {\"A_0\", \"B_1\", \"C_2\", \"D_3\", \"E_4\", \"F_5\", \"G_6\", \"H_7\",\n                     \"I_8\"},\n      .insertion_ = {0, 1, 2, 3, 4, 5, 6, 7, 8},\n      .estimated_main_axis_size_pxs_ = {100, 100, 100, 100, 100, 100, 100, 100,\n                                        100},\n  };\n  auto result = list_adapter_->UpdateRadonDataSource(pub::ValueImplLepus(\n      lepus::Value(radon_data_source.GenerateDataSource())));\n  EXPECT_EQ(result.first, ListAdapterDiffResult::kInsert);\n  list_adapter_->UpdateItemHolderToLatest(\n      list_container_impl_->list_children_helper());\n\n  list_container_impl_->StartInterceptListElementUpdated();\n  for (int i = 0; i < static_cast<int>(radon_data_source.GetItemCount()); ++i) {\n    ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(i);\n    EXPECT_NE(item_holder, nullptr);\n    EXPECT_FALSE(list_adapter_->IsRecycled(item_holder));\n    EXPECT_FALSE(list_adapter_->IsBinding(item_holder));\n    EXPECT_FALSE(list_adapter_->IsFinishedBinding(item_holder));\n    EXPECT_TRUE(list_adapter_->IsDirty(item_holder));\n    EXPECT_FALSE(list_adapter_->IsUpdated(item_holder));\n    EXPECT_FALSE(list_adapter_->IsRemoved(item_holder));\n    EXPECT_TRUE(list_adapter_->GetItemElementDelegate(item_holder) == nullptr);\n  }\n  list_container_impl_->StopInterceptListElementUpdated();\n}\n\nTEST_F(DefaultListAdapterTest, RadonDiffCase1) {\n  // Before\n  testing::RadonDataSource radon_data_source{\n      .item_keys_ = {\"A_0\", \"B_1\", \"C_2\", \"D_3\", \"E_4\", \"F_5\", \"G_6\", \"H_7\",\n                     \"I_8\"},\n      .insertion_ = {0, 1, 2, 3, 4, 5, 6, 7, 8},\n      .estimated_main_axis_size_pxs_ = {100, 100, 100, 100, 100, 100, 100, 100,\n                                        100},\n  };\n  list_adapter_->UpdateRadonDataSource(pub::ValueImplLepus(\n      lepus::Value(radon_data_source.GenerateDataSource())));\n  list_adapter_->UpdateItemHolderToLatest(\n      list_container_impl_->list_children_helper());\n  int list_item_impl_id = mock_list_element_->GetImplId() + 1;\n\n  {\n    list_container_impl_->StartInterceptListElementUpdated();\n    for (int i = 0; i < static_cast<int>(radon_data_source.GetItemCount());\n         ++i) {\n      ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(i);\n      // Bind.\n      EXPECT_CALL(*mock_list_element_, ComponentAtIndex(_, _, _))\n          .WillOnce(Return(true));\n      EXPECT_TRUE(list_adapter_->BindItemHolder(item_holder, i, false));\n      // Finish bind.\n      auto pipeline = std::make_shared<tasm::PipelineOptions>();\n      pipeline->operation_id = item_holder->operation_id_;\n      const auto& item_key = item_holder->item_key();\n      mock_list_element_->AddListItemElement(\n          item_key, std::make_unique<MockListItemElement>(list_item_impl_id++));\n      list_adapter_->OnFinishBindItemHolder(\n          mock_list_element_->GetListItemElement(item_key), pipeline);\n      EXPECT_TRUE(list_adapter_->GetItemElementDelegate(item_holder) !=\n                  nullptr);\n    }\n    list_container_impl_->StopInterceptListElementUpdated();\n  }\n\n  // after\n  testing::RadonDataSource radon_data_source_1{\n      .item_keys_ = {\"New_A_0\", \"New_B_1\", \"New_C_2\", \"New_D_3\", \"E_4\",\n                     \"New_F_5\", \"New_G_6\", \"New_H_7\", \"New_I_8\"},\n      .insertion_ = {0, 1, 2, 3, 5, 6, 7, 8},\n      .removal_ = {0, 1, 2, 3, 5, 6, 7, 8},\n  };\n  list_adapter_->UpdateRadonDataSource(pub::ValueImplLepus(\n      lepus::Value(radon_data_source_1.GenerateDataSource())));\n  list_adapter_->UpdateItemHolderToLatest(\n      list_container_impl_->list_children_helper());\n\n  {\n    list_container_impl_->StartInterceptListElementUpdated();\n    for (int i = 0; i < static_cast<int>(radon_data_source_1.GetItemCount());\n         ++i) {\n      ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(i);\n      if (i == 4) {\n        // No need to bind\n        EXPECT_FALSE(list_adapter_->BindItemHolder(item_holder, i));\n      } else {\n        // Need to bind\n        EXPECT_CALL(*mock_list_element_, ComponentAtIndex(_, _, _))\n            .WillOnce(Return(true));\n        EXPECT_TRUE(list_adapter_->BindItemHolder(item_holder, i));\n        auto pipeline = std::make_shared<tasm::PipelineOptions>();\n        pipeline->operation_id = item_holder->operation_id_;\n        const auto& item_key = item_holder->item_key();\n        mock_list_element_->AddListItemElement(\n            item_key,\n            std::make_unique<MockListItemElement>(list_item_impl_id++));\n        list_adapter_->OnFinishBindItemHolder(\n            mock_list_element_->GetListItemElement(item_key), pipeline);\n        EXPECT_TRUE(list_adapter_->GetItemElementDelegate(item_holder) !=\n                    nullptr);\n      }\n    }\n    list_adapter_->RecycleRemovedItemHolders();\n    EXPECT_EQ(list_adapter_->item_holder_map_->size(),\n              radon_data_source_1.GetItemCount());\n    list_container_impl_->StopInterceptListElementUpdated();\n  }\n}\n\nTEST_F(DefaultListAdapterTest, RadonDiffCase2) {\n  // Before\n  testing::RadonDataSource radon_data_source{\n      .item_keys_ = {\"A_0\", \"B_1\", \"C_2\", \"D_3\", \"E_4\", \"F_5\", \"G_6\", \"H_7\",\n                     \"I_8\"},\n      .insertion_ = {0, 1, 2, 3, 4, 5, 6, 7, 8},\n      .estimated_main_axis_size_pxs_ = {100, 100, 100, 100, 100, 100, 100, 100,\n                                        100},\n  };\n  list_adapter_->UpdateRadonDataSource(pub::ValueImplLepus(\n      lepus::Value(radon_data_source.GenerateDataSource())));\n  list_adapter_->UpdateItemHolderToLatest(\n      list_container_impl_->list_children_helper());\n  int list_item_impl_id = mock_list_element_->GetImplId() + 1;\n  int raw_data_count = static_cast<int>(radon_data_source.GetItemCount());\n\n  {\n    // Trigger all item holders to bind.\n    list_container_impl_->StartInterceptListElementUpdated();\n    for (int i = 0; i < raw_data_count; ++i) {\n      ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(i);\n      // Bind.\n      EXPECT_CALL(*mock_list_element_, ComponentAtIndex(_, _, _))\n          .WillOnce(Return(true));\n      EXPECT_TRUE(list_adapter_->BindItemHolder(item_holder, i, false));\n    }\n    list_container_impl_->StopInterceptListElementUpdated();\n  }\n\n  std::vector<std::shared_ptr<tasm::PipelineOptions>> pipeline_options;\n  {\n    // The item holder with index < raw_data_count / 2 is finished bind.\n    list_container_impl_->StartInterceptListElementUpdated();\n    for (int i = 0; i < raw_data_count; ++i) {\n      ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(i);\n      auto pipeline = std::make_shared<tasm::PipelineOptions>();\n      pipeline->operation_id = item_holder->operation_id_;\n      pipeline_options.emplace_back(pipeline);\n      const auto& item_key = item_holder->item_key();\n      mock_list_element_->AddListItemElement(\n          item_key, std::make_unique<MockListItemElement>(list_item_impl_id++));\n      if (i < raw_data_count / 2) {\n        list_adapter_->OnFinishBindItemHolder(\n            mock_list_element_->GetListItemElement(item_key), pipeline);\n        EXPECT_TRUE(list_adapter_->GetItemElementDelegate(item_holder) !=\n                    nullptr);\n      }\n    }\n    list_container_impl_->StopInterceptListElementUpdated();\n  }\n\n  int remaining_count = raw_data_count - raw_data_count / 2;\n  EXPECT_EQ(static_cast<DefaultListAdapter*>(list_adapter_)\n                ->binding_item_holder_weak_map_->size(),\n            remaining_count);\n\n  // after\n  testing::RadonDataSource radon_data_source_1{\n      .item_keys_ = {\"New_A_0\", \"New_B_1\", \"New_C_2\", \"New_D_3\", \"New_E_4\",\n                     \"New_F_5\", \"New_G_6\", \"New_H_7\", \"New_I_8\"},\n      .insertion_ = {0, 1, 2, 3, 4, 5, 6, 7, 8},\n      .removal_ = {0, 1, 2, 3, 4, 5, 6, 7, 8},\n  };\n  list_adapter_->UpdateRadonDataSource(pub::ValueImplLepus(\n      lepus::Value(radon_data_source_1.GenerateDataSource())));\n  list_adapter_->UpdateItemHolderToLatest(\n      list_container_impl_->list_children_helper());\n  // Simulate destroy removed item holders.\n  list_adapter_->RecycleRemovedItemHolders();\n\n  {\n    // The item holder with index >= raw_data_count / 2 is finished bind.\n    for (int i = raw_data_count / 2; i < raw_data_count; ++i) {\n      auto pipeline = pipeline_options[i];\n      const auto& item_key = radon_data_source.item_keys_[i];\n      EXPECT_CALL(*mock_list_element_, EnqueueComponent(_)).Times(1);\n      list_adapter_->OnFinishBindItemHolder(\n          mock_list_element_->GetListItemElement(item_key), pipeline);\n    }\n  }\n}\n\nTEST_F(DefaultListAdapterTest, FiberDiffCase0) {\n  testing::InsertAction insert_action{\n      .insert_ops_ = {\n          {.position_ = 0, \"A_0\", 100, true, true, false, false},\n          {.position_ = 1, \"B_1\", 100, true, true, false, false},\n          {.position_ = 2, \"C_2\", 100, true, true, false, false},\n          {.position_ = 3, \"D_3\", 100, true, true, false, false},\n          {.position_ = 4, \"E_4\", 100, true, true, false, false},\n          {.position_ = 5, \"F_5\", 100, true, true, false, false},\n          {.position_ = 6, \"G_6\", 100, true, true, false, false},\n          {.position_ = 7, \"H_7\", 100, true, true, false, false},\n          {.position_ = 8, \"I_8\", 100, true, true, false, false},\n          {.position_ = 9, \"J_9\", 100, true, true, false, false},\n      }};\n  testing::FiberDataSource fiber_data_source{\n      .insert_action_ = insert_action,\n  };\n  auto result = list_adapter_->UpdateFiberDataSource(\n      pub::ValueImplLepus(lepus::Value(fiber_data_source.Resolve())));\n  EXPECT_EQ(result.first, ListAdapterDiffResult::kInsert);\n  list_adapter_->UpdateItemHolderToLatest(\n      list_container_impl_->list_children_helper());\n  for (int i = 0; i < 10; ++i) {\n    ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(i);\n    EXPECT_NE(item_holder, nullptr);\n    EXPECT_FALSE(list_adapter_->IsRecycled(item_holder));\n    EXPECT_FALSE(list_adapter_->IsBinding(item_holder));\n    EXPECT_FALSE(list_adapter_->IsFinishedBinding(item_holder));\n    EXPECT_TRUE(list_adapter_->IsDirty(item_holder));\n    EXPECT_FALSE(list_adapter_->IsUpdated(item_holder));\n    EXPECT_FALSE(list_adapter_->IsRemoved(item_holder));\n    EXPECT_TRUE(list_adapter_->GetItemElementDelegate(item_holder) == nullptr);\n  }\n}\n\nTEST_F(DefaultListAdapterTest, FiberDiffCase1) {\n  InitFiberDataSource();\n  testing::RemoveAction remove_action{\n      .remove_ops_ = {0, 1, 2, 3, 4},\n  };\n  testing::UpdateAction update_action{\n      .update_ops_ =\n          {\n              {.from_ = 0, .to_ = 0, false, \"F_5\", 50, true, true, true, true},\n              {.from_ = 1, .to_ = 1, false, \"G_6\", 50, true, true, true, true},\n              {.from_ = 2, .to_ = 2, false, \"H_7\", 50, true, true, true, true},\n              {.from_ = 3, .to_ = 3, false, \"I_8\", 50, true, true, true, true},\n              {.from_ = 4, .to_ = 4, false, \"J_9\", 50, true, true, true, true},\n          },\n  };\n  testing::FiberDataSource fiber_data_source{\n      .remove_action_ = remove_action,\n      .update_action_ = update_action,\n  };\n  auto result = list_adapter_->UpdateFiberDataSource(\n      pub::ValueImplLepus(lepus::Value(fiber_data_source.Resolve())));\n  EXPECT_EQ(result.first, ListAdapterDiffResult::kRemove);\n  list_adapter_->UpdateItemHolderToLatest(\n      list_container_impl_->list_children_helper());\n  for (int i = 0; i < list_adapter_->GetDataCount(); ++i) {\n    ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(i);\n    EXPECT_TRUE(item_holder->item_full_span());\n    EXPECT_TRUE(item_holder->sticky_top());\n    EXPECT_TRUE(item_holder->sticky_bottom());\n    EXPECT_TRUE(base::FloatsEqual(item_holder->height(), 50));\n    EXPECT_TRUE(item_holder->recyclable());\n  }\n}\n\nTEST_F(DefaultListAdapterTest, FiberDiffCase2) {\n  // test no illegal item key\n  testing::InsertAction insert_action{\n      .insert_ops_ = {\n          {.position_ = 0, \"A_0\", 100, false, false, false},\n          {.position_ = 1, \"B_1\", 100, false, false, false},\n          {.position_ = 2, \"C_2\", 100, false, false, false},\n          {.position_ = 3, \"D_3\", 100, false, false, false},\n          {.position_ = 4, \"E_4\", 100, false, false, false},\n      }};\n  testing::FiberDataSource fiber_data_source{\n      .insert_action_ = insert_action,\n  };\n  EXPECT_CALL(*mock_list_element_, OnErrorOccurred(_)).Times(0);\n  list_adapter_->UpdateFiberDataSource(\n      pub::ValueImplLepus(lepus::Value(fiber_data_source.Resolve())));\n  list_adapter_->UpdateItemHolderToLatest(\n      list_container_impl_->list_children_helper());\n\n  // test illegal item key\n  testing::InsertAction insert_action_1{\n      .insert_ops_ = {\n          {.position_ = 0, \"\", 100, false, false, false},\n      }};\n  testing::FiberDataSource fiber_data_source_1{\n      .insert_action_ = insert_action_1,\n  };\n  EXPECT_CALL(*mock_list_element_, OnErrorOccurred(_)).Times(1);\n  list_adapter_->UpdateFiberDataSource(\n      pub::ValueImplLepus(lepus::Value(fiber_data_source_1.Resolve())));\n}\n\nTEST_F(DefaultListAdapterTest, FiberDiffCase3) {\n  // test duplicated item key\n  testing::InsertAction insert_action{\n      .insert_ops_ = {\n          {.position_ = 0, \"A_0\", 100, false, false, false},\n          {.position_ = 1, \"B_1\", 100, false, false, false},\n          {.position_ = 2, \"C_2\", 100, false, false, false},\n          {.position_ = 3, \"D_3\", 100, false, false, false},\n          {.position_ = 4, \"D_3\", 100, false, false, false},\n      }};\n  testing::FiberDataSource fiber_data_source{\n      .insert_action_ = insert_action,\n  };\n  EXPECT_CALL(*mock_list_element_, OnErrorOccurred(_)).Times(1);\n  list_adapter_->UpdateFiberDataSource(\n      pub::ValueImplLepus(lepus::Value(fiber_data_source.Resolve())));\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_grid_layout_manager.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_grid_layout_manager.h\"\n\n#include <algorithm>\n#include <vector>\n\n#include \"core/list/decoupled_item_holder.h\"\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\nGridLayoutManager::GridLayoutManager(ListContainerImpl* list_container_impl)\n    : LinearLayoutManager(list_container_impl) {}\n\nvoid GridLayoutManager::UpdateLayoutStateToFillPreloadBuffer(\n    LayoutState& layout_state, int index, float offset,\n    LayoutDirection layout_direction) {\n  ItemHolder* item_holder = list_container_->GetItemHolderForIndex(index);\n  // calculate the index of the first column in this row\n  if (item_holder && item_holder->item_col_index() > 0) {\n    layout_state.next_bind_index_ = index - item_holder->item_col_index();\n  } else {\n    layout_state.next_bind_index_ = index;\n  }\n  layout_state.next_layout_offset_ = offset;\n  layout_state.layout_direction_ = layout_direction;\n}\n\nint GridLayoutManager::GetTargetIndexForPreloadBuffer(\n    int start_index, LayoutDirection layout_direction) {\n  if (!ValidPreloadBufferCount()) {\n    return kInvalidIndex;\n  }\n  const int data_count = list_container_->GetDataCount();\n  int target_index = kInvalidIndex;\n  if (layout_direction == LayoutDirection::kLayoutToEnd) {\n    // Layout to end\n    for (int i = start_index;\n         i < data_count && i < start_index + preload_buffer_count_; ++i) {\n      ItemHolder* item_holder = list_container_->GetItemHolderForIndex(i);\n      if (item_holder) {\n        if (item_holder->item_full_span()) {\n          target_index = std::max(target_index, item_holder->index());\n        } else if (item_holder->item_col_index() >= 0) {\n          // Note: here target_index may out of data count.\n          target_index =\n              std::max(target_index, item_holder->index() -\n                                         item_holder->item_col_index() +\n                                         span_count_ - 1);\n        }\n      }\n    }\n    if (target_index == kInvalidIndex) {\n      return kInvalidIndex;\n    }\n  } else {\n    // Layout to start\n    target_index = data_count;\n    for (int i = start_index; i >= 0 && i > start_index - preload_buffer_count_;\n         --i) {\n      ItemHolder* item_holder = list_container_->GetItemHolderForIndex(i);\n      if (item_holder) {\n        if (item_holder->item_full_span()) {\n          target_index = std::min(target_index, item_holder->index());\n        } else if (item_holder->item_col_index() >= 0) {\n          target_index =\n              std::min(target_index,\n                       item_holder->index() - item_holder->item_col_index());\n        }\n      }\n    }\n    if (target_index == data_count) {\n      return kInvalidIndex;\n    }\n  }\n  // clamp target_index to range\n  return std::max(0, std::min(target_index, data_count - 1));\n}\n\nvoid GridLayoutManager::LayoutChunk(LayoutChunkResult& result,\n                                    LayoutState& layout_state,\n                                    bool preload_section) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_LAYOUT_CHUNK, \"index\",\n              base::FormatString(\"%d\", layout_state.next_bind_index_));\n  if (!list_container_ || !list_children_helper_ || !list_orientation_helper_) {\n    result.consumed_ = 0.f;\n    result.finished_ = true;\n    return;\n  }\n  ItemHolder* current_item_holder =\n      list_container_->GetItemHolderForIndex(layout_state.next_bind_index_);\n  if (!current_item_holder || current_item_holder->item_col_index() != 0) {\n    DLIST_LOGE(\n        \"GridLayoutManager::LayoutChunk: item holder is nullptr or it's column \"\n        \"index is not 0\");\n    result.consumed_ = 0.f;\n    result.finished_ = true;\n    return;\n  }\n  const int data_count = list_container_->GetDataCount();\n  int remainingSpan = span_count_;\n  int count = 0;\n  std::vector<ItemHolder*> item_holder_vector;\n  // should fill a row or column during the layout chunk.\n  while (count < span_count_ && remainingSpan > 0) {\n    int index = layout_state.next_bind_index_ + count;\n    int item_span_size = getSpanSize(index);\n    if (item_span_size > span_count_) {\n      DLIST_LOGE(\"GridLayoutManager::LayoutChunk: invalid item span size = \"\n                 << item_span_size);\n      result.consumed_ = 0.f;\n      result.finished_ = true;\n      return;\n    }\n    remainingSpan -= item_span_size;\n    // item did not fit into this row or column\n    if (remainingSpan < 0) {\n      break;\n    }\n    // if the index here exceeds the count, should break the loop.\n    if (index >= data_count) {\n      break;\n    }\n    ItemHolder* item_holder = list_container_->GetItemHolderForIndex(index);\n    if (!item_holder) {\n      DLIST_LOGE(\n          \"GridLayoutManager::LayoutChunk: item holder is nullptr with \"\n          \"index = \"\n          << index);\n    } else {\n      list_container_->list_adapter()->BindItemHolder(item_holder, index,\n                                                      preload_section);\n      if (item_holder->item_full_span()) {\n        remainingSpan = 0;\n      }\n      item_holder_vector.push_back(item_holder);\n    }\n    count++;\n  }\n  if (count == 0 || item_holder_vector.empty()) {\n    // If the count of this line's items is 0, mark result.finished_ and\n    // directly return.\n    result.finished_ = true;\n    return;\n  } else if (count != static_cast<int32_t>(item_holder_vector.size())) {\n    DLIST_LOGE(\n        \"GridLayoutManager::LayoutChunk: fail to get all item holders in this \"\n        \"line, directly return.\");\n    result.consumed_ = 0.f;\n    result.finished_ = true;\n    return;\n  }\n  // Update current index of layout state.\n  int start_index_of_next_row = GetStartIndexOfNextRow(\n      layout_state.layout_direction_, layout_state.next_bind_index_);\n  if (start_index_of_next_row != kInvalidIndex) {\n    layout_state.next_bind_index_ = start_index_of_next_row;\n  } else {\n    layout_state.next_bind_index_ +=\n        static_cast<int32_t>(layout_state.layout_direction_) * count;\n  }\n  // Layout item holders in one line.\n  // Note: item_holder_vector should be checked not empty when using\n  // std::max_element\n  ItemHolder* max_item_holder = *std::max_element(\n      item_holder_vector.begin(), item_holder_vector.end(),\n      [this](ItemHolder* a, ItemHolder* b) {\n        return list_orientation_helper_->GetDecoratedMeasurement(a) <\n               list_orientation_helper_->GetDecoratedMeasurement(b);\n      });\n  float max_size =\n      list_orientation_helper_->GetDecoratedMeasurement(max_item_holder);\n  result.consumed_ = max_size;\n  // Calculate item holder's main offset.\n  float main_offset = 0.f, cross_offset = 0.f;\n  if (LayoutDirection::kLayoutToStart == layout_state.layout_direction_) {\n    main_offset = layout_state.next_layout_offset_ - max_size +\n                  max_item_holder->top_inset();\n  } else {\n    main_offset = layout_state.next_layout_offset_;\n  }\n\n  // Calculate item holder's cross offset.\n  for (ItemHolder* item_holder : item_holder_vector) {\n    if (item_holder) {\n      int item_col_index = item_holder->item_col_index();\n      float item_cross_size =\n          list_orientation_helper_->GetDecoratedMeasurementInOther(item_holder);\n      if (base::FloatsLargerOrEqual(0.f, item_cross_size)) {\n        // If get invalid item size in cross axis.\n        float list_cross_size =\n            list_orientation_helper_->GetMeasurementInOtherWithoutPadding();\n        float total_cross_gap_size = (span_count_ - 1) * cross_axis_gap_;\n        item_cross_size =\n            (list_cross_size - total_cross_gap_size) / span_count_;\n      }\n      cross_offset = list_orientation_helper_->GetStartAfterPaddingInOther() +\n                     item_col_index * (item_cross_size + cross_axis_gap_);\n      if (orientation_ == Orientation::kVertical) {\n        item_holder->UpdateLayoutFromManager(cross_offset, main_offset);\n      } else {\n        item_holder->UpdateLayoutFromManager(main_offset, cross_offset);\n      }\n    }\n  }\n}\n\nint GridLayoutManager::GetStartIndexOfNextRow(LayoutDirection direction,\n                                              const int start_index) const {\n  if (!list_container_) {\n    return kInvalidIndex;\n  }\n  int index = kInvalidIndex;\n  ItemHolder* item_holder = nullptr;\n  if (direction == LayoutDirection::kLayoutToEnd) {\n    const int data_count = list_container_->GetDataCount();\n    for (int i = start_index + 1; i < data_count; ++i) {\n      item_holder = list_container_->GetItemHolderForIndex(i);\n      if (!item_holder) {\n        DLIST_LOGE(\"GridLayoutManager::GetStartIndexOfNextRow \"\n                   << \"null item holder\");\n        return kInvalidIndex;\n      } else if (item_holder->item_full_span() ||\n                 item_holder->item_col_index() == 0) {\n        // If iterate to the first item holder in the next line, break directly.\n        index = i;\n        break;\n      }\n    }\n  } else {\n    for (int i = start_index - 1; i >= 0; --i) {\n      item_holder = list_container_->GetItemHolderForIndex(i);\n      if (!item_holder) {\n        DLIST_LOGE(\"GridLayoutManager::GetStartIndexOfNextRow \"\n                   << \"null item holder\");\n        return kInvalidIndex;\n      } else if (item_holder->item_full_span() ||\n                 item_holder->item_col_index() == 0) {\n        // If iterate to the first item holder in the prev line, break directly.\n        index = i;\n        break;\n      }\n    }\n  }\n  return index;\n}\n\nvoid GridLayoutManager::UpdateLayoutStateToFillStart(\n    LayoutState& layout_state,\n    const ListAnchorManager::AnchorInfo& anchor_info) {\n  // anchor_info.coordinate_ is the top value of anchor item holder, which is\n  // including main axis gap. For example, item_holder's height == 100 and\n  // main_axis_gap == 10, the top value of item_holder_1 is 110.\n  float top_inset =\n      anchor_info.item_holder_ ? anchor_info.item_holder_->top_inset() : 0.f;\n  float offset = anchor_info.start_offset_ - top_inset;\n  // calculate the index of the first column in the row of anchor.\n  int first_col_index = anchor_info.index_;\n  ItemHolder* item_holder =\n      list_container_->GetItemHolderForIndex(first_col_index);\n  if (item_holder && item_holder->item_col_index() > 0) {\n    first_col_index -= item_holder->item_col_index();\n  }\n  // calculate the index of  the first column in the pre row.\n  int next_bind_index =\n      first_col_index + static_cast<int32_t>(LayoutDirection::kLayoutToStart);\n  item_holder = list_container_->GetItemHolderForIndex(next_bind_index);\n  if (item_holder && item_holder->item_col_index() > 0) {\n    next_bind_index -= item_holder->item_col_index();\n  }\n  layout_state.next_bind_index_ = next_bind_index;\n  layout_state.available_ = offset - content_offset_ -\n                            list_orientation_helper_->GetStartAfterPadding();\n  layout_state.next_layout_offset_ = offset;\n  layout_state.layout_direction_ = LayoutDirection::kLayoutToStart;\n}\n\nvoid GridLayoutManager::UpdateLayoutStateToFillEnd(\n    LayoutState& layout_state,\n    const ListAnchorManager::AnchorInfo& anchor_info) {\n  int index = anchor_info.index_;\n  float offset = anchor_info.start_offset_;\n  ItemHolder* item_holder = list_container_->GetItemHolderForIndex(index);\n  // calculate the index of the first column in this row\n  if (item_holder && item_holder->item_col_index() > 0) {\n    layout_state.next_bind_index_ = index - item_holder->item_col_index();\n  } else {\n    layout_state.next_bind_index_ = index;\n  }\n  layout_state.available_ =\n      list_orientation_helper_->GetEndAfterPadding() + content_offset_ - offset;\n  layout_state.next_layout_offset_ = offset;\n  layout_state.layout_direction_ = LayoutDirection::kLayoutToEnd;\n}\n\nvoid GridLayoutManager::LayoutInvalidItemHolder(int first_invalid_index) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_LAYOUT_INVALID_ITEM,\n              \"first_invalid_index\", std::to_string(first_invalid_index));\n  if (!list_container_ || !list_children_helper_ || first_invalid_index < 0 ||\n      first_invalid_index >= list_container_->GetDataCount()) {\n    return;\n  }\n  // Update span size and column index.\n  // Note: we should update column index and span size firstly because the span\n  // size is important info in calculate item holder's layout offset.\n  list_children_helper_->ForEachChild(\n      [this, first_invalid_index](ItemHolder* item_holder) {\n        const int index = item_holder->index();\n        if (index >= first_invalid_index) {\n          if (item_holder->item_full_span()) {\n            item_holder->SetItemSpanSize(span_count_);\n            item_holder->SetItemColIndex(0);\n          } else {\n            ItemHolder* prev_item_holder =\n                list_container_->GetItemHolderForIndex(index - 1);\n            ItemHolder* next_item_holder =\n                list_container_->GetItemHolderForIndex(index + 1);\n            if (prev_item_holder) {\n              if (prev_item_holder->item_full_span() ||\n                  prev_item_holder->item_col_index() == span_count_ - 1) {\n                // If the last item holder is full span or the last one in it's\n                // line, the current item holder's column index is 0.\n                item_holder->SetItemColIndex(0);\n              } else {\n                item_holder->SetItemColIndex(\n                    prev_item_holder->item_col_index() + 1);\n              }\n            } else {\n              // The current item holder is the first one.\n              item_holder->SetItemColIndex(0);\n            }\n            if (next_item_holder) {\n              if (next_item_holder->item_full_span()) {\n                // If the next item holder is full span, the span size of\n                // current item holder should be (span_count - col_index)\n                item_holder->SetItemSpanSize(span_count_ -\n                                             item_holder->item_col_index());\n              } else {\n                item_holder->SetItemSpanSize(1);\n              }\n            } else {\n              // The current item holder is the last one.\n              item_holder->SetItemSpanSize(span_count_ -\n                                           item_holder->item_col_index());\n            }\n          }\n        }\n        return false;\n      });\n  list_children_helper_->ForEachChild([this, first_invalid_index](\n                                          ItemHolder* item_holder) {\n    item_holder->SetOrientation(orientation());\n    int index = item_holder->index();\n    if (index >= first_invalid_index) {\n      float main_axis = 0.f, cross_axis = 0.f;\n      ItemHolder* prev_item_holder = nullptr;\n      if (index > 0) {\n        prev_item_holder = list_container_->GetItemHolderForIndex(index - 1);\n      }\n      if (prev_item_holder) {\n        if (prev_item_holder->item_full_span() ||\n            prev_item_holder->item_col_index() == span_count_ - 1 ||\n            item_holder->item_full_span()) {\n          // previous item_holder already hit the end\n          // current item_holder should layout from begin\n          main_axis =\n              LargestMainSizeInRowWithItemHolder(prev_item_holder) +\n              main_axis_gap_ +\n              list_orientation_helper_->GetItemHolderMainMargin(item_holder);\n          // add main_axis_gap to item_holder\n          item_holder->SetTopInset(main_axis_gap_);\n          cross_axis =\n              list_orientation_helper_->GetStartAfterPaddingInOther() +\n              list_orientation_helper_->GetItemHolderCrossMargin(item_holder);\n        } else {\n          // previous item holder is not the last one in the current row.\n          if (orientation() == Orientation::kVertical) {\n            main_axis = prev_item_holder->top();\n            cross_axis =\n                list_orientation_helper_->GetDecoratedMeasurementInOther(\n                    prev_item_holder) +\n                prev_item_holder->left();\n          } else {\n            main_axis = prev_item_holder->left();\n            cross_axis =\n                list_orientation_helper_->GetDecoratedMeasurementInOther(\n                    prev_item_holder) +\n                prev_item_holder->top();\n          }\n          cross_axis += cross_axis_gap_;\n          item_holder->SetTopInset(prev_item_holder->top_inset());\n        }\n      } else {\n        // this is the first item_holder\n        main_axis =\n            list_orientation_helper_->GetStartAfterPadding() +\n            list_orientation_helper_->GetItemHolderMainMargin(item_holder);\n        cross_axis =\n            list_orientation_helper_->GetStartAfterPaddingInOther() +\n            list_orientation_helper_->GetItemHolderCrossMargin(item_holder);\n      }\n      if (orientation_ == Orientation::kVertical) {\n        item_holder->UpdateLayoutFromManager(cross_axis, main_axis);\n      } else {\n        item_holder->UpdateLayoutFromManager(main_axis, cross_axis);\n      }\n    }\n    return false;\n  });\n}\n\nbool GridLayoutManager::ShouldRecycleItemHolder(\n    const ItemHolder* item_holder) const {\n  if (!item_holder || !item_holder->recyclable()) {\n    return false;\n  }\n  return LargestMainSizeInRowWithItemHolder(item_holder) < content_offset_ ||\n         list_orientation_helper_->GetDecoratedStart(item_holder) >\n             content_offset_ + list_orientation_helper_->GetMeasurement();\n}\n\nint GridLayoutManager::getSpanSize(int index) {\n  if (list_container_) {\n    // full_span item should fill the whole row, so its size should be equal to\n    // the span_count.\n    ListAdapter* adapter = list_container_->list_adapter();\n    if (adapter && adapter->IsFullSpanAtIndex(index)) {\n      return span_count_;\n    }\n    ItemHolder* item_holder = list_container_->GetItemHolderForIndex(index);\n    if (item_holder) {\n      return item_holder->item_span_size();\n    }\n  }\n  return 1;\n}\n\nfloat GridLayoutManager::GetTargetContentSize() {\n  // Note: content size == padding top + sum of children's height + padding\n  // bottom\n  if (!list_children_helper_ || !list_orientation_helper_) {\n    return 0.f;\n  }\n  int last_element_index = list_container_->list_adapter()->GetDataCount() - 1;\n  return list_container_->RoundValueToPixelGrid(\n      LargestMainSizeInRowWithItemHolder(\n          list_container_->GetItemHolderForIndex(last_element_index)) +\n      list_orientation_helper_->GetEndPadding());\n}\n\nfloat GridLayoutManager::LargestMainSizeInRowWithItemHolder(\n    const ItemHolder* item_holder) const {\n  if (!item_holder || !list_container_ || !list_orientation_helper_) {\n    return 0.f;\n  }\n  if (item_holder->item_full_span()) {\n    return list_orientation_helper_->GetDecoratedEnd(item_holder);\n  }\n  const int data_count = list_container_->GetDataCount();\n  int start_index = item_holder->index();\n  if (item_holder->item_col_index() > 0) {\n    start_index = start_index - item_holder->item_col_index();\n  }\n  if (start_index < 0 || start_index >= data_count - 1) {\n    DLIST_LOGE(\"GridLayoutManager::LargestMainSizeInRowWithItemHolder \"\n               << \"invalid start index \" << start_index);\n    return list_orientation_helper_->GetDecoratedEnd(item_holder);\n  }\n  float largest_main_size = 0.f;\n  // Iterate all item holders in the current line.\n  for (int i = start_index; i < data_count; ++i) {\n    ItemHolder* current_item_holder = list_container_->GetItemHolderForIndex(i);\n    if (!current_item_holder) {\n      DLIST_LOGE(\"GridLayoutManager::LargestMainSizeInRowWithItemHolder \"\n                 << \"null item holder\");\n      break;\n    } else if (i != start_index &&\n               (current_item_holder->item_full_span() ||\n                current_item_holder->item_col_index() == 0)) {\n      // If iterate to the first item holder in the next line, break directly.\n      break;\n    }\n    largest_main_size = std::max(\n        largest_main_size,\n        list_orientation_helper_->GetDecoratedEnd(current_item_holder));\n  }\n  return largest_main_size;\n}\n\n#if ENABLE_TRACE_PERFETTO\nvoid GridLayoutManager::UpdateTraceDebugInfo(TraceEvent* event) const {\n  ListLayoutManager::UpdateTraceDebugInfo(event);\n  auto* list_type_info = event->add_debug_annotations();\n  list_type_info->set_name(\"list_type\");\n  list_type_info->set_string_value(\"flow\");\n}\n#endif\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_grid_layout_manager.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_GRID_LAYOUT_MANAGER_H_\n#define CORE_LIST_DECOUPLED_GRID_LAYOUT_MANAGER_H_\n\n#include \"core/list/decoupled_linear_layout_manager.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass GridLayoutManager : public LinearLayoutManager {\n public:\n  GridLayoutManager(ListContainerImpl* list_container_impl);\n  ~GridLayoutManager() override = default;\n\n protected:\n  void LayoutChunk(LayoutChunkResult& result, LayoutState& layout_state,\n                   bool preload_section = false) override;\n  void UpdateLayoutStateToFillStart(\n      LayoutState& layout_state,\n      const ListAnchorManager::AnchorInfo& anchor_info) override;\n  void UpdateLayoutStateToFillEnd(\n      LayoutState& layout_state,\n      const ListAnchorManager::AnchorInfo& anchor_info) override;\n  void LayoutInvalidItemHolder(int first_invalid_index) override;\n  bool ShouldRecycleItemHolder(const ItemHolder* item_holder) const override;\n  float GetTargetContentSize() override;\n  void UpdateLayoutStateToFillPreloadBuffer(\n      LayoutState& layout_state, int index, float offset,\n      LayoutDirection layout_direction) override;\n  int GetTargetIndexForPreloadBuffer(int start_index,\n                                     LayoutDirection layout_direction) override;\n#if ENABLE_TRACE_PERFETTO\n  void UpdateTraceDebugInfo(TraceEvent* event) const override;\n#endif\n\n private:\n  // get the column size which the item holder occupies\n  int getSpanSize(int index);\n  // calculate the largest itemHolder‘s index in this row.\n  float LargestMainSizeInRowWithItemHolder(const ItemHolder* item_holder) const;\n  int GetStartIndexOfNextRow(LayoutDirection direction,\n                             const int start_index) const;\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_GRID_LAYOUT_MANAGER_H_\n"
  },
  {
    "path": "core/list/decoupled_item_holder.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_item_holder.h\"\n\n#include <algorithm>\n\n#include \"base/include/float_comparison.h\"\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/list/decoupled_list_orientation_helper.h\"\n\nnamespace lynx {\nnamespace list {\n\nItemHolder::ItemHolder(int index, const std::string& item_key,\n                       AnimationDelegate* delegate)\n    : index_(index), item_key_(item_key), animation_delegate_(delegate) {\n  DCHECK(delegate);\n}\n\nvoid ItemHolder::UpdateLayoutFromItemDelegate() {\n  UpdateLayoutFromItemDelegate(item_delegate_);\n}\n\nvoid ItemHolder::UpdateLayoutFromItemDelegate(\n    ItemElementDelegate* item_delegate) {\n  if (item_delegate) {\n    // Update layout info from starlight. Here we don't update left and top's\n    // value which are always zero for list's child element.\n    width_ = item_delegate->GetWidth();\n    height_ = item_delegate->GetHeight();\n    borders_ = item_delegate->GetBorders();\n    paddings_ = item_delegate->GetPaddings();\n    margins_ = item_delegate->GetMargins();\n  }\n}\n\nvoid ItemHolder::UpdateLayoutToPlatform(float content_size,\n                                        float container_width) {\n  UpdateLayoutToPlatform(content_size, container_width, item_delegate_);\n}\n\nvoid ItemHolder::UpdateLayoutToPlatform(float content_size,\n                                        float container_width,\n                                        ItemElementDelegate* item_delegate) {\n  if (item_delegate) {\n    if (animation_delegate_->UpdateAnimation() &&\n        animation_type_ == ItemHolderAnimationType::kTransform) {\n      // NOTE: In the remove animation, a new item holder is created, and we\n      // need the new item holder to also run the transform animation.\n    } else {\n      if (direction_ == Direction::kRTL) {\n        item_delegate->UpdateLayoutToPlatform(\n            GetRTLLeft(content_size, container_width, left_, width_), top_);\n      } else {\n        item_delegate->UpdateLayoutToPlatform(left_, top_);\n      }\n    }\n    // Record current content size and container width.\n    content_size_ = content_size;\n    container_width_ = container_width;\n  }\n}\n\nvoid ItemHolder::UpdateLayoutFromManager(float left, float top) {\n  // Update left and top's value from list's layout manager.\n  if (base::FloatsEqual(left, left_) && base::FloatsEqual(top, top_)) {\n    return;\n  }\n  if (animation_delegate_->UpdateAnimation() &&\n      animation_delegate_->AnimationType() != ListAnimationType::kNone &&\n      std::isnan(animation_origin_left_) && std::isnan(animation_origin_top_)) {\n    animation_origin_left_ = left_;\n    animation_origin_top_ = top_;\n    animation_type_ = ItemHolderAnimationType::kTransform;\n    // NOTE: In an insert animation, the item could be set with opacity\n    // animation first, currently we cover it with a transform animation.\n    animation_origin_opacity_ = std::numeric_limits<float>::quiet_NaN();\n  }\n  // NOTE: When the coordinates are assigned multiple times during the\n  // animation, we take the last assignment as the animation’s target position.\n  left_ = left;\n  top_ = top;\n}\n\nvoid ItemHolder::DoAnimationFrame(float progress) {\n  ItemElementDelegate* item_delegate =\n      animation_delegate_->GetItemElementDelegate(this);\n  if (item_delegate) {\n    if (animation_type_ == ItemHolderAnimationType::kNone) {\n      return;\n    }\n    DCHECK((animation_type_ == ItemHolderAnimationType::kTransform) ==\n               !std::isnan(animation_origin_left_) &&\n           (animation_type_ == ItemHolderAnimationType::kTransform) ==\n               !std::isnan(animation_origin_top_));\n    DCHECK((animation_type_ == ItemHolderAnimationType::kOpacity) ==\n           !std::isnan(animation_origin_opacity_));\n    if (animation_type_ == ItemHolderAnimationType::kTransform &&\n        !std::isnan(animation_origin_left_) &&\n        !std::isnan(animation_origin_top_)) {\n      float l =\n          animation_origin_left_ + (left_ - animation_origin_left_) * progress;\n      const float t =\n          animation_origin_top_ + (top_ - animation_origin_top_) * progress;\n      if (direction_ == Direction::kRTL) {\n        l = GetRTLLeft(content_size_, container_width_, l, width_);\n      }\n      item_delegate->UpdateLayoutToPlatform(l, t);\n      item_delegate->FlushPatching();\n    } else if (animation_type_ == ItemHolderAnimationType::kOpacity &&\n               !std::isnan(animation_origin_opacity_)) {\n      item_delegate->FlushAnimatedStyle(\n          tasm::CSSPropertyID::kPropertyIDOpacity,\n          tasm::CSSValue(std::fabs(animation_origin_opacity_ - progress),\n                         tasm::CSSValuePattern::NUMBER));\n    }\n  }\n}\n\nvoid ItemHolder::EndAnimation() {\n  ItemElementDelegate* item_delegate =\n      animation_delegate_->GetItemElementDelegate(this);\n  if (animation_type_ == ItemHolderAnimationType::kTransform) {\n    if (item_delegate && (left_ != item_delegate->GetLeft() ||\n                          top_ != item_delegate->GetTop())) {\n      if (direction_ == Direction::kRTL) {\n        item_delegate->UpdateLayoutToPlatform(\n            GetRTLLeft(content_size_, container_width_, left_, width_), top_);\n      } else {\n        item_delegate->UpdateLayoutToPlatform(left_, top_);\n      }\n    }\n    animation_origin_left_ = std::numeric_limits<float>::quiet_NaN();\n    animation_origin_top_ = std::numeric_limits<float>::quiet_NaN();\n    content_size_ = std::numeric_limits<float>::quiet_NaN();\n    container_width_ = std::numeric_limits<float>::quiet_NaN();\n  } else if (animation_type_ == ItemHolderAnimationType::kOpacity) {\n    if (item_delegate) {\n      item_delegate->FlushAnimatedStyle(\n          tasm::CSSPropertyID::kPropertyIDOpacity,\n          tasm::CSSValue(1, tasm::CSSValuePattern::NUMBER));\n    }\n    if (animation_origin_opacity_ == 1.f) {\n      animation_delegate_->RecycleItemHolder(this);\n    }\n    animation_origin_opacity_ = std::numeric_limits<float>::quiet_NaN();\n  }\n  DCHECK(std::isnan(animation_origin_top_));\n  DCHECK(std::isnan(animation_origin_left_));\n  DCHECK(std::isnan(animation_origin_opacity_));\n  animation_type_ = ItemHolderAnimationType::kNone;\n}\n\n// Animation here means the `animation` of *this* item holder, nor all the list\n// animations.\nvoid ItemHolder::RecycleAfterAnimation(ItemHolderAnimationType type) {\n  if (!animation_delegate_->UpdateAnimation()) {\n    return;\n  }\n  animation_type_ = type;\n  if (type == ItemHolderAnimationType::kOpacity) {\n    animation_origin_opacity_ = 1.f;\n  }\n  animation_delegate_->DeferredDestroyItemHolder(this);\n}\n\nvoid ItemHolder::MarkInsertOpacity() {\n  DCHECK(animation_type_ == ItemHolderAnimationType::kNone);\n  animation_type_ = ItemHolderAnimationType::kOpacity;\n  animation_origin_opacity_ = 0.f;\n}\n\nfloat ItemHolder::height() const {\n  if (orientation_ == Orientation::kHorizontal) {\n    return base::FloatsLargerOrEqual(height_, 0.f) ? height_ : 0.f;\n  }\n  return GetSizeInMainAxis();\n}\n\nfloat ItemHolder::width() const {\n  if (orientation_ == Orientation::kVertical) {\n    return base::FloatsLargerOrEqual(width_, 0.f) ? width_ : 0.f;\n  }\n  return GetSizeInMainAxis();\n}\n\nfloat ItemHolder::GetSizeInMainAxis() const {\n  // If the ItemHolder is never bound, we use estimated size or container size.\n  float main_axis_size =\n      orientation_ == Orientation::kVertical ? height_ : width_;\n  return base::FloatsLargerOrEqual(main_axis_size, 0.f)\n             ? main_axis_size\n             : (base::FloatsLargerOrEqual(estimated_size_, 0.f)\n                    ? estimated_size_\n                    : (base::FloatsLarger(container_size_, 0.f)\n                           ? container_size_\n                           : kDefaultMainAxisItemSize));\n}\n\nfloat ItemHolder::GetBorder(FrameDirection frame_direction) const {\n  return borders_[static_cast<uint32_t>(frame_direction)];\n}\n\nfloat ItemHolder::GetPadding(FrameDirection frame_direction) const {\n  return paddings_[static_cast<uint32_t>(frame_direction)];\n}\n\nfloat ItemHolder::GetMargin(FrameDirection frame_direction) const {\n  return margins_[static_cast<uint32_t>(frame_direction)];\n}\n\nfloat ItemHolder::GetRTLLeft(float content_size, float container_width,\n                             float left, float width) const {\n  if (orientation_ == Orientation::kHorizontal) {\n    return std::max(content_size, container_width) - left - width;\n  }\n  return container_width - left - width;\n}\n\nbool ItemHolder::IsAtStickyPosition(float content_offset, float list_height,\n                                    float content_size, float sticky_offset,\n                                    float start, float end) const {\n  if (sticky_top_ && start < content_offset + sticky_offset) {\n    return true;\n  } else if (sticky_bottom_ &&\n             end >= std::min(content_offset + list_height - sticky_offset,\n                             content_size)) {\n    return true;\n  } else {\n    return false;\n  }\n}\n\nbool ItemHolder::VisibleInList(ListOrientationHelper* orientation_helper,\n                               float content_offset) const {\n  if (!orientation_helper) {\n    return false;\n  }\n  float container_size = orientation_helper->GetMeasurement();\n  float list_start = content_offset;\n  float list_end = list_start + container_size;\n  float start = orientation_helper->GetDecoratedStart(this);\n  float end = orientation_helper->GetDecoratedEnd(this);\n  return ((base::FloatsLarger(list_start, start) &&\n           base::FloatsLarger(end, list_start)) ||\n          (base::FloatsLarger(list_end, start) &&\n           base::FloatsLarger(end, list_end)) ||\n          (base::FloatsLargerOrEqual(start, list_start) &&\n           base::FloatsLargerOrEqual(list_end, end)));\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_item_holder.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_ITEM_HOLDER_H_\n#define CORE_LIST_DECOUPLED_ITEM_HOLDER_H_\n\n#include <array>\n#include <limits>\n#include <string>\n\n#include \"base/include/flex_optional.h\"\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"core/list/decoupled_list_types.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ItemElementDelegate;\nclass ListOrientationHelper;\n\nclass ItemHolder : public fml::EnableWeakFromThis<ItemHolder> {\n public:\n  class AnimationDelegate {\n   public:\n    virtual ~AnimationDelegate() = default;\n    // Animation running currently.\n    virtual ListAnimationType AnimationType() const = 0;\n    virtual void DeferredDestroyItemHolder(ItemHolder* holder) = 0;\n    virtual void RecycleItemHolder(ItemHolder* holder) = 0;\n    virtual ItemElementDelegate* GetItemElementDelegate(ItemHolder* holder) = 0;\n    // Whether enable update animation in frontend, distinguish this from\n    // `AnimationType()` plz.\n    virtual bool UpdateAnimation() const = 0;\n  };\n\n  // Note: The comparator of ItemHolder should allow objects with the same\n  // index but different addresses to exist and it should meet the\n  // requirements that the comparator of std::set should possess, specifically,\n  // it needs to determine that there's a strict weak ordering among elements.\n  class Compare {\n   public:\n    bool operator()(const ItemHolder* lhs, const ItemHolder* rhs) const {\n      if (lhs && rhs) {\n        if (lhs == rhs) {\n          return false;\n        } else if (lhs->index_ == rhs->index_) {\n          return lhs < rhs;\n        } else {\n          return lhs->index_ < rhs->index_;\n        }\n      } else if (!lhs) {  // nullptr is less than any non-null pointer\n        return rhs != nullptr;\n      } else {  // any non-null pointer is not less than nullptr\n        return false;\n      }\n    }\n  };\n\n  class WeakCompare {\n   public:\n    bool operator()(const fml::WeakPtr<ItemHolder>& lhs,\n                    const fml::WeakPtr<ItemHolder>& rhs) const {\n      return Compare()(lhs.get(), rhs.get());\n    }\n  };\n\n  ItemHolder(int index, const std::string& item_key,\n             AnimationDelegate* delegate);\n  virtual ~ItemHolder() = default;\n\n  void UpdateLayoutFromItemDelegate();\n  void UpdateLayoutFromItemDelegate(ItemElementDelegate* item_delegate);\n  void UpdateLayoutToPlatform(float content_size, float container_width);\n  void UpdateLayoutToPlatform(float content_size, float container_width,\n                              ItemElementDelegate* item_delegate);\n  void UpdateLayoutFromManager(float left, float top);\n  bool IsAtStickyPosition(float content_offset, float list_height,\n                          float content_size, float sticky_offset, float start,\n                          float end) const;\n  bool VisibleInList(ListOrientationHelper* orientation_helper,\n                     float content_offset) const;\n\n  // Animation\n  void DoAnimationFrame(float progress);\n  void EndAnimation();\n  void RecycleAfterAnimation(ItemHolderAnimationType type);\n  void MarkInsertOpacity();\n\n  float GetBorder(FrameDirection frame_direction) const;\n  float GetPadding(FrameDirection frame_direction) const;\n  float GetMargin(FrameDirection frame_direction) const;\n  void SetIndex(int index) { index_ = index; }\n  void SetItemKey(const std::string& item_key) { item_key_ = item_key; }\n  void SetItemSpanSize(int item_span_size) { item_span_size_ = item_span_size; }\n  void SetItemColIndex(int item_col_index) { item_col_index_ = item_col_index; }\n  void SetItemFullSpan(bool item_full_span) {\n    item_full_span_ = item_full_span;\n  }\n  void SetTopInset(float top_inset) { top_inset_ = top_inset; }\n  void SetTop(float top) { top_ = top; }\n  void SetLeft(float left) { left_ = left; }\n  void SetEstimatedSize(float estimated_size) {\n    estimated_size_ = estimated_size;\n  }\n  void SetContainerSize(float container_size) {\n    container_size_ = container_size;\n  }\n  void SetOrientation(Orientation orientation) { orientation_ = orientation; }\n  void SetDirection(Direction direction) { direction_ = direction; }\n  void SetSticky(bool sticky_top, bool sticky_bottom) {\n    sticky_top_ = sticky_top;\n    sticky_bottom_ = sticky_bottom;\n  }\n  void SetWeakAnchorRef(ItemHolder* item_holder) {\n    weak_anchor_ref_ = base::make_flex_optional(\n        item_holder ? item_holder->WeakFromThis() : fml::WeakPtr<ItemHolder>());\n  }\n  void ResetWeakAnchorRef() { weak_anchor_ref_.reset(); }\n  ItemHolder* GetAnchorRefHolder() const {\n    return weak_anchor_ref_ && (*weak_anchor_ref_) ? (*weak_anchor_ref_).get()\n                                                   : nullptr;\n  }\n  void SetRecyclable(bool recyclable) { recyclable_ = recyclable; }\n\n  const std::string& item_key() const { return item_key_; }\n  int index() const { return index_; }\n  int item_col_index() const { return item_col_index_; }\n  int item_span_size() const { return item_span_size_; }\n  bool item_full_span() const { return item_full_span_; }\n  float top_inset() const { return top_inset_; }\n  float left() const { return left_; }\n  float top() const { return top_; }\n  float height() const;\n  float width() const;\n  bool sticky() const { return sticky_top_ || sticky_bottom_; }\n  bool sticky_top() const { return sticky_top_; }\n  bool sticky_bottom() const { return sticky_bottom_; }\n  base::flex_optional<fml::WeakPtr<ItemHolder>> weak_anchor_ref() const {\n    return weak_anchor_ref_;\n  }\n  bool recyclable() const { return recyclable_; }\n\n private:\n  float GetRTLLeft(float content_size, float container_width, float left,\n                   float width) const;\n  float GetSizeInMainAxis() const;\n  void SetItemDelegate(ItemElementDelegate* item_delegate) {\n    item_delegate_ = item_delegate;\n  }\n  void SetOperationId(int64_t operation_id) { operation_id_ = operation_id; }\n  void MarkDiffStatus(DiffStatus status) { diff_status_ = status; }\n  void MarkRemoved(bool value) { removed_ = value; }\n  void MarkDirty(bool value) { dirty_ = value; }\n  void MarkVirtualDomPreloaded(bool value) { virtual_dom_preloaded_ = value; }\n  bool removed() const { return removed_; }\n  bool is_updated() const { return diff_status_ == DiffStatus::kUpdateTo; }\n  bool BoundWithItemDelegate() {\n    return !dirty_ && item_delegate_ && operation_id_ == 0;\n  }\n  bool BoundWithoutItemDelegate() {\n    return !dirty_ && !item_delegate_ && operation_id_ == 0;\n  }\n  bool dirty() const { return dirty_; }\n  int64_t operation_id() const { return operation_id_; }\n  bool virtual_dom_preloaded() const { return virtual_dom_preloaded_; }\n\n private:\n  friend class DefaultListAdapter;\n  // All status Info of item holder.\n  // Whether the ItemHolder is need to bind in the next layout. If the\n  // ItemHolder is bound, the dirty_ will be set to false.\n  bool dirty_{true};\n  // Whether the ItemHolder is removed.\n  bool removed_{false};\n  // kValid / kRemoved / kUpdateTo / kUpdatedFrom / kMoveTo / kMoveFrom\n  DiffStatus diff_status_{DiffStatus::kValid};\n  // The id of every binding operation, which is used to determine the binding\n  // relationship with ItemHolder and child element.\n  int64_t operation_id_{0};\n  // The ItemElementDelegate's ptr bound with ItemHolder. If the ItemHolder is\n  // recycled, the element_ will be reset to nullptr;\n  ItemElementDelegate* item_delegate_{nullptr};\n  bool virtual_dom_preloaded_{false};\n\n  // All layout Info of item holder.\n  // The ItemHolder's index in data source.\n  int index_{kInvalidIndex};\n  base::flex_optional<fml::WeakPtr<ItemHolder>> weak_anchor_ref_;\n  // The ItemHold's key.\n  std::string item_key_;\n  // Whether the ItemHolder can be recycled.\n  bool recyclable_{true};\n  // Whether the ItemHolder is a sticky top item.\n  bool sticky_top_{false};\n  // Whether the ItemHolder is a sticky bottom item.\n  bool sticky_bottom_{false};\n  // Whether the ItemHolder is full span item.\n  bool item_full_span_{false};\n  // The ItemHolder's column index.\n  int item_col_index_{0};\n  // The number of columns occupied by each ItemHolder. For example, a full-span\n  // ItemHolder, the span_size is equal to the list's column count.\n  int item_span_size_{1};\n  float left_{0.f};\n  float top_{0.f};\n  float width_{kInvalidDimensionSize};\n  float height_{kInvalidDimensionSize};\n  // The ItemHolder's main axis-gap.\n  float top_inset_{0.f};\n  // The container's size in main axis.\n  float container_size_{kInvalidDimensionSize};\n  // The ItemHolder's estimated size (ppx).\n  float estimated_size_{kInvalidDimensionSize};\n  // The layout's orientation. (kVertical / kHorizontal)\n  Orientation orientation_{Orientation::kVertical};\n  // The layout's direction. (kNormal / kRTL)\n  Direction direction_{Direction::kNormal};\n  std::array<float, 4> paddings_{};\n  std::array<float, 4> borders_{};\n  std::array<float, 4> margins_{};\n\n  // Animation\n  AnimationDelegate* animation_delegate_{nullptr};\n  float animation_origin_left_{std::numeric_limits<float>::quiet_NaN()};\n  float animation_origin_top_{std::numeric_limits<float>::quiet_NaN()};\n  // Note: 1.f means the opacity animation of 1 -> 0, and the 0.f is the\n  // opposite.\n  float animation_origin_opacity_{std::numeric_limits<float>::quiet_NaN()};\n  float content_size_{std::numeric_limits<float>::quiet_NaN()};\n  float container_width_{std::numeric_limits<float>::quiet_NaN()};\n  ItemHolderAnimationType animation_type_{ItemHolderAnimationType::kNone};\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_ITEM_HOLDER_H_\n"
  },
  {
    "path": "core/list/decoupled_linear_layout_manager.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_linear_layout_manager.h\"\n\n#include <algorithm>\n#include <unordered_set>\n#include <vector>\n\n#include \"base/include/float_comparison.h\"\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\nLinearLayoutManager::LinearLayoutManager(ListContainerImpl* list_container_impl)\n    : ListLayoutManager(list_container_impl) {}\n\nvoid LinearLayoutManager::OnBatchLayoutChildren() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_BATCH_CHILDREN,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n\n  OnPrepareForLayoutChildren();\n\n  // Note: To avoid nested invoking OnBatchLayoutChildren,\n  // StartInterceptListElementUpdated() and StopInterceptListElementUpdated()\n  // need to be invoked at the begin or end of OnBatchLayoutChildren().\n  list_container_->StartInterceptListElementUpdated();\n\n  LayoutState layout_state;\n  layout_state.latest_updated_content_offset_ = content_offset_;\n\n  // step 1. Update anchor info and layout all item_holders\n  ListAnchorManager::AnchorInfo anchor_info;\n  InitLayoutAndAnchor(anchor_info, kInvalidIndex);\n  list_container_->list_event_manager()->SendAnchorDebugInfoIfNeeded(\n      anchor_info);\n\n  // step 2. Invoke batch render.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_BATCH_RENDER);\n  LayoutInvalidItemHolder(0);\n  list_children_helper_->UpdateOnScreenChildren(list_orientation_helper_.get(),\n                                                content_offset_);\n  list_container_->list_adapter()->BindItemHolders(\n      list_children_helper_->on_screen_children());\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 3. Invoke OnLayoutChildren after batch render.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    LINEAR_LAYOUT_MANAGER_LAYOUT_CHILDREN_INTERNAL);\n  OnLayoutChildrenInternal(anchor_info, layout_state);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 4. Handle layout result: recycle and update layout to platform.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_LAYOUT_AFTER);\n  OnLayoutAfter(layout_state);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n}\n\nvoid LinearLayoutManager::OnLayoutChildren(\n    bool is_component_finished /* = false */, int component_index /* = -1 */) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_LAYOUT_CHILDREN,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n\n  if (!list_container_ || !list_children_helper_) {\n    return;\n  }\n\n  OnPrepareForLayoutChildren();\n\n  // Note: To avoid nested invoking OnLayoutChildren,\n  // StartInterceptListElementUpdated() and StopInterceptListElementUpdated()\n  // need to be invoked at the begin or end of OnLayoutChildren().\n  list_container_->StartInterceptListElementUpdated();\n\n  LayoutState layout_state;\n  layout_state.latest_updated_content_offset_ = content_offset_;\n\n  // step 1. Update anchor info and layout all item_holders\n  ListAnchorManager::AnchorInfo anchor_info;\n  InitLayoutAndAnchor(anchor_info, component_index);\n  list_container_->list_event_manager()->SendAnchorDebugInfoIfNeeded(\n      anchor_info);\n\n  // step 2. Fill after find anchor.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    LINEAR_LAYOUT_MANAGER_LAYOUT_CHILDREN_INTERNAL);\n  OnLayoutChildrenInternal(anchor_info, layout_state);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 3. Handle layout result: recycle and update layout to platform.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_LAYOUT_AFTER);\n  OnLayoutAfter(layout_state);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n}\n\nvoid LinearLayoutManager::OnLayoutChildrenInternal(\n    ListAnchorManager::AnchorInfo& anchor_info, LayoutState& layout_state) {\n  // Handle empty data source.\n  if (list_container_->GetDataCount() == 0) {\n    content_size_ = GetTargetContentSize();\n    // Reset content offset to 0.\n    SetContentOffset(0.f);\n    FlushContentSizeAndOffsetToPlatform(\n        layout_state.latest_updated_content_offset_, true);\n    layout_state.latest_updated_content_offset_ = content_offset_;\n    // Note: need update on screen children.\n    list_children_helper_->UpdateOnScreenChildren(\n        list_orientation_helper_.get(), content_offset_);\n    return;\n  }\n\n  // step 1. Fill from anchor.\n  if (anchor_info.valid_) {\n    FillWithAnchor(layout_state, anchor_info);\n  }\n\n  // step 2. Update content size and offset\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    LINEAR_LAYOUT_MANAGER_HANDLE_CONTENT_SIZE_AND_OFFSET);\n  LayoutInvalidItemHolder(0);\n  content_size_ = GetTargetContentSize();\n  list_anchor_manager_->AdjustContentOffsetWithAnchor(anchor_info,\n                                                      content_offset_);\n\n  // step 2.5 Update sticky items\n  UpdateStickyItemsAfterLayout(anchor_info);\n  FlushContentSizeAndOffsetToPlatform(\n      layout_state.latest_updated_content_offset_, true);\n  layout_state.latest_updated_content_offset_ = content_offset_;\n\n  // The previous AdjustOffsetWithAnchor was called twice(the second one is\n  // caused by sticky), so the scrolled value should be set only when both of\n  // these calls have finished\n  list_anchor_manager_->MarkScrolledInitialScrollIndex();\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 3. Handle Preload.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    LINEAR_LAYOUT_MANAGER_HANDLE_PRELOAD_IF_NEED);\n  list_children_helper_->UpdateOnScreenChildren(list_orientation_helper_.get(),\n                                                content_offset_);\n  if (enable_preload_section_) {\n    PreloadSectionOnNextFrame();\n  } else {\n    HandlePreloadIfNeeded(layout_state, anchor_info, true);\n  }\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n}\n\nvoid LinearLayoutManager::HandlePreloadIfNeeded(\n    LayoutState& layout_state, ListAnchorManager::AnchorInfo& anchor_info,\n    bool from_layout) {\n  if (ValidPreloadBufferCount() && Preload(layout_state)) {\n    LayoutInvalidItemHolder(0);\n    content_size_ = GetTargetContentSize();\n    list_anchor_manager_->AdjustContentOffsetWithAnchor(anchor_info,\n                                                        content_offset_);\n    FlushContentSizeAndOffsetToPlatform(\n        layout_state.latest_updated_content_offset_, from_layout);\n    layout_state.latest_updated_content_offset_ = content_offset_;\n    // Note: need re-update on screen children after preload\n    list_children_helper_->UpdateOnScreenChildren(\n        list_orientation_helper_.get(), content_offset_);\n  }\n}\n\nvoid LinearLayoutManager::OnLayoutAfter(LayoutState& layout_state) {\n  HandleLayoutOrScrollResult(layout_state, true);\n  // Send layout events.\n  // Note: Events has to be called after StopInterceptListElementUpdated to\n  // avoid reenter in worklet\n  list_container_->StopInterceptListElementUpdated();\n  float scroll_delta = content_offset_ - last_content_offset_;\n  last_content_offset_ = content_offset_;\n  list_container_->list_event_manager()->RecordVisibleItemIfNeeded(false);\n  EventSource event_source = list_container_->has_valid_diff()\n                                 ? EventSource::kDiff\n                                 : EventSource::kLayout;\n  SendLayoutCompleteEvent();\n  SendScrollEvents(scroll_delta, content_offset_, event_source);\n  list_container_->ClearValidDiff();\n}\n\nvoid LinearLayoutManager::HandleLayoutOrScrollResult(LayoutState& layout_state,\n                                                     bool is_layout) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY,\n              LINEAR_LAYOUT_MANAGER_HANDLE_LAYOUT_OR_SCROLL,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  if (list_container_->enable_batch_render() ||\n      list_container_->enable_insert_platform_view_operation()) {\n    ListLayoutManager::HandleLayoutOrScrollResult(is_layout);\n  } else {\n    // No Batch Render.\n    ListAdapter* list_adapter = list_container_->list_adapter();\n    // 1. Recycle off-screen or off-preload's item holder.\n    if (!ValidPreloadBufferCount()) {\n      // No preload case.\n      RecycleOffScreenItemHolders();\n    } else if (layout_state.ValidPreload()) {\n      // valid preload case.\n      if (layout_state.preload_min_index_ != kInvalidIndex) {\n        RecycleOffPreloadItemHolders(false, layout_state.preload_min_index_);\n      }\n      if (layout_state.preload_max_index_ != kInvalidIndex) {\n        RecycleOffPreloadItemHolders(true, layout_state.preload_max_index_);\n      }\n    }\n    if (is_layout) {\n      // 2. Recycle all removed child.\n      list_adapter->RecycleRemovedItemHolders();\n    }\n    // 3. Update layout info to platform.\n    list_children_helper_->ForEachChild(\n        [this, list_adapter](ItemHolder* item_holder) {\n          item_holder->UpdateLayoutToPlatform(\n              content_size_, GetWidth(),\n              list_adapter->GetItemElementDelegate(item_holder));\n          return false;\n        });\n    list_container_->FlushPatching();\n  }\n}\n\nvoid LinearLayoutManager::PreloadSectionOnNextFrame() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_PRELOAD_SECTION);\n  if (list_container_ &&\n      list_container_->need_preload_section_on_next_frame()) {\n    list_container_->list_delegate()->RequestNextFrame();\n  }\n}\n\nvoid LinearLayoutManager::PreloadSection() {\n  if (list_container_ &&\n      list_container_->need_preload_section_on_next_frame()) {\n    LayoutState layout_state;\n    PreloadSection(layout_state);\n  }\n}\n\n/**\n * @description: The main linear layout fill steps are as follows:\n *\n * step 1. Fill to end: From anchor's index and coordinate, calculate\n * available space to end, then try to render children to fill all available\n * space.\n *\n * step 2. Fill to start: From anchor's index and coordinate,\n * calculate available space to start. Considering if has remaining space that\n * not be filled in step2, we also add it to available space, then try to render\n * children to fill.\n *\n * step 3. Fill extra: Considering if has remaining space in\n * step3 but not in step2, we also needs render children to end again to fill\n * all remaining space.\n */\nvoid LinearLayoutManager::FillWithAnchor(\n    LayoutState& layout_state,\n    const ListAnchorManager::AnchorInfo& anchor_info) {\n  // step1. Fill to end from anchor_info's index\n  float extra_for_start = list_orientation_helper_->GetStartAfterPadding();\n  float extra_for_end = list_orientation_helper_->GetEndPadding();\n  UpdateLayoutStateToFillEnd(layout_state, anchor_info);\n  layout_state.extra_ = extra_for_end;\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_FILL_ANCHOR_END,\n                    \"anchor_index\", std::to_string(anchor_info.index_));\n  Fill(layout_state);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step2. Fill to start from anchor_info's index - 1\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    LINEAR_LAYOUT_MANAGER_FILL_ANCHOR_START);\n  if (layout_state.available_ > 0.f) {\n    extra_for_start += layout_state.available_;\n  }\n  layout_state.extra_ = extra_for_start;\n  UpdateLayoutStateToFillStart(layout_state, anchor_info);\n  Fill(layout_state);\n  // Record the min laid out index after this fill.\n  layout_state.min_layout_chunk_index_ = layout_state.next_bind_index_;\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step3. Fill extra from anchor index instead of the index which is recorded\n  // after filling to end, which can avoid the situation that the available\n  // space calculated incorrectly.\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_FILL_ANCHOR_EXTRA);\n  if (layout_state.available_ > 0.f) {\n    layout_state.extra_ = layout_state.available_;\n    UpdateLayoutStateToFillEnd(layout_state, anchor_info);\n    Fill(layout_state);\n  }\n}\n\n/**\n * @description: This function can be used in OnLayoutChildren() or\n * ScrollByInternal(), and the main steps of preloading are as follows:\n *\n * step 1. Find first or last visible item holder according to on screen\n * children.\n *\n * step 2. Update layout state, calculate target index and preload to start\n * or end.\n */\nbool LinearLayoutManager::Preload(LayoutState& layout_state) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_PRELOAD,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  const auto& on_screen_children = list_children_helper_->on_screen_children();\n  layout_state.ResetPreloadIndex();\n  list_children_helper_->ClearInPreloadChildren();\n  if (on_screen_children.empty()) {\n    DLIST_LOGE(\"LinearLayoutManager::Preload: empty on screen children\");\n  } else {\n    const auto* first_visible_item_holder = *(on_screen_children.cbegin());\n    const auto* last_visible_item_holder = *(on_screen_children.crbegin());\n    if (!first_visible_item_holder || !last_visible_item_holder) {\n      DLIST_LOGE(\n          \"LinearLayoutManager::Preload: visible item holder is nullptr\");\n    } else {\n      const ItemHolderSet& in_preload_children =\n          list_children_helper_->in_preload_children();\n      int first_visible_index = first_visible_item_holder->index();\n      int last_visible_index = last_visible_item_holder->index();\n      int start_index = first_visible_index +\n                        static_cast<int32_t>(LayoutDirection::kLayoutToStart);\n      int end_index = last_visible_index +\n                      static_cast<int32_t>(LayoutDirection::kLayoutToEnd);\n      int target_start_index = GetTargetIndexForPreloadBuffer(\n          start_index, LayoutDirection::kLayoutToStart);\n      int target_end_index = GetTargetIndexForPreloadBuffer(\n          end_index, LayoutDirection::kLayoutToEnd);\n      // Fill to end for preload\n      TRACE_EVENT_BEGIN(\n          LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_HANDLE_PRELOAD_TO_END,\n          \"info\",\n          base::FormatString(\"[%d -> %d]\", end_index, target_end_index));\n      DLIST_LOGD(\n          \"LinearLayoutManager::Preload: preload to end, \"\n          \"last_visible_index = \"\n          << last_visible_index << \", preload range = [\" << end_index << \" -> \"\n          << target_end_index << \"]\");\n      if (end_index != kInvalidIndex && target_end_index != kInvalidIndex &&\n          end_index <= target_end_index) {\n        UpdateLayoutStateToFillPreloadBuffer(\n            layout_state, end_index,\n            list_orientation_helper_->GetDecoratedEnd(last_visible_item_holder),\n            LayoutDirection::kLayoutToEnd);\n        // Fill preload buffer item holders\n        PreloadInternal(layout_state, target_end_index);\n        layout_state.preload_max_index_ = target_end_index;\n        for (int i = end_index; i <= target_end_index; ++i) {\n          list_children_helper_->AddChild(\n              in_preload_children, list_container_->GetItemHolderForIndex(i));\n        }\n      }\n      TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n      // Fill to start for preload\n      TRACE_EVENT(\n          LYNX_TRACE_CATEGORY, LIST_PRE_RENDER_OFF_SCREEN_UPPER_ELEMENT, \"info\",\n          base::FormatString(\"[%d -> %d]\", start_index, target_start_index));\n      DLIST_LOGD(\n          \"LinearLayoutManager::Preload: preload to start, \"\n          \"first_visible_index = \"\n          << first_visible_index << \", preload range = [\" << start_index\n          << \" -> \" << target_start_index << \"]\");\n      if (start_index != kInvalidIndex && target_start_index != kInvalidIndex &&\n          target_start_index <= start_index) {\n        UpdateLayoutStateToFillPreloadBuffer(\n            layout_state, start_index,\n            list_orientation_helper_->GetDecoratedStart(\n                first_visible_item_holder),\n            LayoutDirection::kLayoutToStart);\n        // Fill preload buffer item holders\n        PreloadInternal(layout_state, target_start_index);\n        // Record the min laid out index after this fill.\n        layout_state.min_layout_chunk_index_ = layout_state.next_bind_index_;\n        layout_state.preload_min_index_ = target_start_index;\n        for (int i = target_start_index; i <= start_index; ++i) {\n          list_children_helper_->AddChild(\n              in_preload_children, list_container_->GetItemHolderForIndex(i));\n        }\n      }\n    }\n  }\n  return layout_state.preload_max_index_ != kInvalidIndex ||\n         layout_state.preload_min_index_ != kInvalidIndex;\n}\n\n/**\n * @description: Preload item holders to target index.\n */\nvoid LinearLayoutManager::PreloadInternal(LayoutState& layout_state,\n                                          int target_index,\n                                          bool preload_section /* = false */) {\n  static LayoutChunkResult result;\n  while (HasMore(layout_state, target_index)) {\n    result.Reset();\n    LayoutChunk(result, layout_state, preload_section);\n    if (result.finished_) {\n      break;\n    }\n    layout_state.next_layout_offset_ +=\n        result.consumed_ * static_cast<int32_t>(layout_state.layout_direction_);\n  }\n}\n\n/**\n * @description: Recycle all item holders out of preload buffer\n */\nvoid LinearLayoutManager::RecycleOffPreloadItemHolders(bool recycle_to_end,\n                                                       int target_index) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_RECYCLE_PRELOAD_ITEM);\n  if (target_index != kInvalidIndex) {\n    list_children_helper_->ForEachChild(\n        [this, target_index, recycle_to_end](ItemHolder* item_holder) {\n          if (item_holder) {\n            int index = item_holder->index();\n            if (recycle_to_end && index > target_index &&\n                ShouldRecycleStickyItemHolder(item_holder)) {\n              list_container_->list_adapter()->RecycleItemHolder(item_holder);\n            } else if (!recycle_to_end && index < target_index &&\n                       ShouldRecycleStickyItemHolder(item_holder)) {\n              list_container_->list_adapter()->RecycleItemHolder(item_holder);\n            }\n          }\n          return false;\n        });\n  }\n}\n\n/**\n * @description: Calculate target index with start index and preload\n * buffer count\n */\nint LinearLayoutManager::GetTargetIndexForPreloadBuffer(\n    int start_index, LayoutDirection layout_direction) {\n  const int data_count = list_container_->GetDataCount();\n  int target_index = kInvalidIndex;\n  if (start_index >= 0 && start_index < data_count) {\n    target_index = layout_direction == LayoutDirection::kLayoutToEnd\n                       ? start_index + preload_buffer_count_ - 1\n                       : start_index - preload_buffer_count_ + 1;\n    target_index = std::max(0, std::min(target_index, data_count));\n  }\n  return target_index;\n}\n\n/**\n * @description: The main handling scroll steps are as follows:\n * step 1. Update anchor info in scroll using the latest content offset.\n *\n * step 2. Fill from anchor item holder.\n *\n * step 3. Update content offset and size: After filling, we\n * need calculate new content offset and content size.\n *\n * step 4. Handle sticky /\n * Recycle / Flush: Handle sticky, recycle ItemHolder out of list's visible\n * range, and flush children's layout infos to platform in OnLayoutCompleted.\n */\nvoid LinearLayoutManager::ScrollByInternal(float content_offset,\n                                           float original_offset,\n                                           bool from_platform) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_SCROLL_BY_INTERNAL,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  if (!list_container_ || !list_children_helper_) {\n    return;\n  }\n  list_container_->StartInterceptListElementUpdated();\n\n  // step1. Update anchor info in scroll.\n  LayoutState layout_state;\n  layout_state.latest_updated_content_offset_ =\n      from_platform ? content_offset : content_offset_;\n  content_offset_ = content_offset;\n\n  // Note: use the latest content offset to update on screen children first.\n  list_children_helper_->UpdateOnScreenChildren(list_orientation_helper_.get(),\n                                                content_offset_);\n  const auto& on_screen_children = list_children_helper_->on_screen_children();\n  if (on_screen_children.empty()) {\n    DLIST_LOGE(\n        \"LinearLayoutManager::ScrollByInternal: empty on screen children\");\n    list_container_->StopInterceptListElementUpdated();\n    return;\n  }\n  ListAnchorManager::AnchorInfo anchor_info;\n  UpdateScrollAnchorInfo(anchor_info, on_screen_children, content_offset_);\n  if (!anchor_info.valid_) {\n    DLIST_LOGE(\n        \"LinearLayoutManager::ScrollByInternal: null anchor item holder\");\n    list_container_->StopInterceptListElementUpdated();\n    return;\n  }\n\n  // step 2. Fill\n  FillWithAnchor(layout_state, anchor_info);\n\n  // step 3. Update content size and offset\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    FLUSH_CONTENT_SIZE_AND_OFFSET_TO_PLATFORM);\n  LayoutInvalidItemHolder(\n      layout_state.min_layout_chunk_index_ -\n      static_cast<int32_t>(LayoutDirection::kLayoutToStart));\n  content_size_ = GetTargetContentSize();\n  list_anchor_manager_->AdjustContentOffsetWithAnchor(\n      anchor_info, layout_state.latest_updated_content_offset_);\n  FlushContentSizeAndOffsetToPlatform(\n      layout_state.latest_updated_content_offset_, false);\n  layout_state.latest_updated_content_offset_ = content_offset_;\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 3.5. Handle sticky.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    LINEAR_LAYOUT_MANAGER_UPDATE_STICKY_ITEMS);\n  UpdateStickyItems();\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 4. Handle preload\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    LINEAR_LAYOUT_MANAGER_HANDLE_PRELOAD_IF_NEED);\n  list_children_helper_->UpdateOnScreenChildren(list_orientation_helper_.get(),\n                                                content_offset_);\n  HandlePreloadIfNeeded(layout_state, anchor_info, false);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 5. Handle scroll result.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_ON_SCROLL_AFTER);\n  OnScrollAfter(layout_state, original_offset);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n}\n\nvoid LinearLayoutManager::OnScrollAfter(LayoutState& layout_state,\n                                        float original_offset) {\n  HandleLayoutOrScrollResult(layout_state, false);\n  // Send scroll event\n  // Events has to be called after StopInterceptListElementUpdated to avoid\n  // reenter in worklet\n  list_container_->StopInterceptListElementUpdated();\n  float scroll_delta = content_offset_ - last_content_offset_;\n  last_content_offset_ = content_offset_;\n  SendScrollEvents(scroll_delta, original_offset, EventSource::kScroll);\n}\n\nvoid LinearLayoutManager::UpdateScrollAnchorInfo(\n    ListAnchorManager::AnchorInfo& anchor_info,\n    const ItemHolderSet& on_screen_children, const float content_offset) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY,\n              LINEAR_LAYOUT_MANAGER_UPDATE_SCROLL_ANCHOR_INFO);\n  ItemHolder* first_visible_item_holder = list_children_helper_->GetFirstChild(\n      on_screen_children,\n      [this, list_adapter = list_container_->list_adapter()](\n          const ItemHolder* item_holder) {\n        return !list_adapter->IsDirty(item_holder) &&\n               list_adapter->GetItemElementDelegate(item_holder) &&\n               IsItemHolderNotAtStickyPosition(item_holder);\n      });\n  ItemHolder* last_visible_item_holder = list_children_helper_->GetLastChild(\n      on_screen_children,\n      [this, list_adapter = list_container_->list_adapter()](\n          const ItemHolder* item_holder) {\n        return !list_adapter->IsDirty(item_holder) &&\n               list_adapter->GetItemElementDelegate(item_holder) &&\n               IsItemHolderNotAtStickyPosition(item_holder);\n      });\n  ItemHolder* anchor_item_holder = nullptr;\n  if (!first_visible_item_holder || !last_visible_item_holder) {\n    anchor_item_holder = list_children_helper_->GetFirstChild(\n        on_screen_children, [this](const ItemHolder* item_holder) {\n          return base::FloatsLargerOrEqual(\n                     list_orientation_helper_->GetStart(item_holder),\n                     content_offset_) &&\n                 IsItemHolderNotAtStickyPosition(item_holder);\n        });\n    if (!anchor_item_holder) {\n      anchor_item_holder = *(on_screen_children.begin());\n    }\n  } else {\n    anchor_item_holder = first_visible_item_holder;\n  }\n  if (anchor_item_holder) {\n    anchor_info.item_holder_ = anchor_item_holder;\n    anchor_info.index_ = anchor_item_holder->index();\n    anchor_info.start_offset_ =\n        list_orientation_helper_->GetStart(anchor_item_holder);\n    anchor_info.start_alignment_delta_ =\n        anchor_info.start_offset_ - content_offset;\n    anchor_info.valid_ = true;\n  } else {\n    anchor_info.valid_ = false;\n  }\n}\n\nvoid LinearLayoutManager::LayoutInvalidItemHolder(int first_invalid_index) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_LAYOUT_INVALID_ITEM,\n              \"first_invalid_index\", std::to_string(first_invalid_index));\n  if (!list_container_ || !list_children_helper_ || first_invalid_index < 0 ||\n      first_invalid_index >= list_container_->GetDataCount()) {\n    return;\n  }\n  float offset = 0.f;\n  list_children_helper_->ForEachChild(\n      [this, &offset, first_invalid_index](ItemHolder* item_holder) {\n        if (item_holder->index() >= first_invalid_index) {\n          item_holder->SetOrientation(orientation());\n          if (item_holder->index() > 0) {\n            item_holder->SetTopInset(main_axis_gap_);\n          } else {\n            offset += list_orientation_helper_->GetStartAfterPadding();\n          }\n          offset += item_holder->top_inset();\n          float main_axis = 0.f, cross_axis = 0.f;\n          main_axis =\n              offset +\n              list_orientation_helper_->GetItemHolderMainMargin(item_holder);\n          cross_axis = list_orientation_helper_->GetStartAfterPaddingInOther();\n          if (orientation_ == Orientation::kVertical) {\n            item_holder->UpdateLayoutFromManager(cross_axis, main_axis);\n          } else {\n            item_holder->UpdateLayoutFromManager(main_axis, cross_axis);\n          }\n        }\n        offset = list_orientation_helper_->GetDecoratedEnd(item_holder);\n        return false;\n      });\n}\n\nfloat LinearLayoutManager::GetTargetContentSize() {\n  // Note: content size == padding top + sum of children's height + padding\n  // bottom\n  if (!list_container_ || !list_orientation_helper_) {\n    return 0.f;\n  }\n  if (list_container_->list_adapter()->GetDataCount() == 0) {\n    return list_container_->RoundValueToPixelGrid(\n        list_orientation_helper_->GetStartAfterPadding() +\n        list_orientation_helper_->GetEndPadding());\n  } else {\n    int last_element_index =\n        list_container_->list_adapter()->GetDataCount() - 1;\n    // Last ItemHolder's end + list's padding-bottom or padding-right\n    return list_container_->RoundValueToPixelGrid(\n        list_orientation_helper_->GetDecoratedEnd(\n            list_container_->GetItemHolderForIndex(last_element_index)) +\n        list_orientation_helper_->GetEndPadding());\n  }\n}\n\n// Render and layout one ItemHolder, GridLayoutManager overrides this function\n// to render column-count ItemHolders or a full-span ItemHolder.\nvoid LinearLayoutManager::LayoutChunk(LayoutChunkResult& result,\n                                      LayoutState& layout_state,\n                                      bool preload_section) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_LAYOUT_CHUNK, \"index\",\n              base::FormatString(\"%d\", layout_state.next_bind_index_));\n  if (!list_container_ || !list_children_helper_ || !list_orientation_helper_) {\n    result.consumed_ = 0.f;\n    return;\n  }\n  int index = layout_state.next_bind_index_;\n  ItemHolder* item_holder = list_container_->GetItemHolderForIndex(index);\n  if (item_holder) {\n    list_container_->list_adapter()->BindItemHolder(item_holder, index,\n                                                    preload_section);\n    result.consumed_ =\n        list_orientation_helper_->GetDecoratedMeasurement(item_holder);\n    float left = 0.f, top = 0.f;\n    if (orientation_ == Orientation::kVertical) {\n      // vertical\n      left = list_orientation_helper_->GetStartAfterPaddingInOther() +\n             item_holder->GetMargin(FrameDirection::kLeft);\n      if (layout_state.layout_direction_ == LayoutDirection::kLayoutToEnd) {\n        // fill to end\n        top = layout_state.next_layout_offset_ +\n              item_holder->GetMargin(FrameDirection::kTop);\n      } else {\n        // fill to start\n        top = layout_state.next_layout_offset_ - result.consumed_ +\n              item_holder->top_inset() +\n              item_holder->GetMargin(FrameDirection::kTop);\n      }\n    } else {\n      // horizontal\n      top = list_orientation_helper_->GetStartAfterPaddingInOther() +\n            item_holder->GetMargin(FrameDirection::kTop);\n      if (layout_state.layout_direction_ == LayoutDirection::kLayoutToEnd) {\n        // fill to end\n        left = layout_state.next_layout_offset_ +\n               item_holder->GetMargin(FrameDirection::kLeft);\n      } else {\n        // fill to start\n        left = layout_state.next_layout_offset_ - result.consumed_ +\n               item_holder->top_inset() +\n               item_holder->GetMargin(FrameDirection::kLeft);\n      }\n    }\n    item_holder->UpdateLayoutFromManager(left, top);\n  }\n  layout_state.next_bind_index_ +=\n      static_cast<int32_t>(layout_state.layout_direction_);\n}\n\n// Update layout state to fill to start, GridLayoutManager overrides this\n// function to handle LayoutState by it self.\nvoid LinearLayoutManager::UpdateLayoutStateToFillStart(\n    LayoutState& layout_state,\n    const ListAnchorManager::AnchorInfo& anchor_info) {\n  // anchor_info.coordinate_ is the top value of anchor item holder, which is\n  // including main axis gap. For example, item_holder's height == 100 and\n  // main_axis_gap == 10, the top value of item_holder_1 is 110.\n  float top_inset =\n      anchor_info.item_holder_ ? anchor_info.item_holder_->top_inset() : 0.f;\n  float offset = anchor_info.start_offset_ - top_inset;\n  int index = anchor_info.index_ +\n              static_cast<int32_t>(LayoutDirection::kLayoutToStart);\n  // Update layout state.\n  layout_state.available_ = offset - content_offset_ -\n                            list_orientation_helper_->GetStartAfterPadding();\n  layout_state.next_bind_index_ = index;\n  layout_state.next_layout_offset_ = offset;\n  layout_state.layout_direction_ = LayoutDirection::kLayoutToStart;\n}\n\n// Update layout state to fill to end, GridLayoutManager overrides this function\n// to handle LayoutState by it self.\nvoid LinearLayoutManager::UpdateLayoutStateToFillEnd(\n    LayoutState& layout_state,\n    const ListAnchorManager::AnchorInfo& anchor_info) {\n  float offset = anchor_info.start_offset_;\n  int index = anchor_info.index_;\n  // Update layout state.\n  layout_state.available_ =\n      list_orientation_helper_->GetEndAfterPadding() + content_offset_ - offset;\n  layout_state.next_bind_index_ = index;\n  layout_state.next_layout_offset_ = offset;\n  layout_state.layout_direction_ = LayoutDirection::kLayoutToEnd;\n}\n\n// Update layout state to fill preload buffer\nvoid LinearLayoutManager::UpdateLayoutStateToFillPreloadBuffer(\n    LayoutState& layout_state, int index, float offset,\n    LayoutDirection layout_direction) {\n  layout_state.next_bind_index_ = index;\n  layout_state.next_layout_offset_ = offset;\n  layout_state.layout_direction_ = layout_direction;\n}\n\n// Try to render as many ItemHolders as possible to fill the specified available\n// area. The filling's direction, start index, start coordinate and the size\n// of the available area are specified by the LayoutState.\nvoid LinearLayoutManager::Fill(LayoutState& layout_state) {\n  float remaining = layout_state.available_ + layout_state.extra_;\n  static LayoutChunkResult result;\n  while (HasMore(layout_state.next_bind_index_, remaining)) {\n    result.Reset();\n    LayoutChunk(result, layout_state);\n    if (result.finished_) {\n      break;\n    }\n    layout_state.next_layout_offset_ +=\n        result.consumed_ * static_cast<int32_t>(layout_state.layout_direction_);\n    layout_state.available_ -= result.consumed_;\n    remaining -= result.consumed_;\n  }\n}\n\n// Return whether list has more available space and data source to fill.\nbool LinearLayoutManager::HasMore(int next_bind_index, float remaining) const {\n  return base::FloatsLarger(remaining, 0.f) && list_container_ &&\n         next_bind_index >= 0 &&\n         next_bind_index < list_container_->GetDataCount();\n}\n\n// Return whether list has more data source to fill.\nbool LinearLayoutManager::HasMore(const LayoutState& layout_state,\n                                  int target_index) const {\n  int next_bind_index = layout_state.next_bind_index_;\n  if (layout_state.layout_direction_ == LayoutDirection::kLayoutToEnd) {\n    return list_container_ && next_bind_index >= 0 &&\n           next_bind_index < list_container_->GetDataCount() &&\n           next_bind_index <= target_index;\n  } else {\n    return list_container_ && next_bind_index >= 0 &&\n           next_bind_index < list_container_->GetDataCount() &&\n           next_bind_index >= target_index;\n  }\n}\n\nvoid LinearLayoutManager::PreloadSection(LayoutState& layout_state) {\n  if (!enable_preload_section_) {\n    return;\n  }\n  const auto& on_screen_children = list_children_helper_->on_screen_children();\n  if (on_screen_children.empty()) {\n    DLIST_LOGE(\"LinearLayoutManager::PreloadSection: empty on screen children\");\n  } else {\n    const auto* first_visible_item_holder = *(on_screen_children.cbegin());\n    const auto* last_visible_item_holder = *(on_screen_children.crbegin());\n    if (!first_visible_item_holder || !last_visible_item_holder) {\n      DLIST_LOGE(\n          \"LinearLayoutManager::PreloadSection: visible item holder is \"\n          \"nullptr\");\n    } else {\n      int first_visible_index = first_visible_item_holder->index();\n      int last_visible_index = last_visible_item_holder->index();\n      int start_index = first_visible_index +\n                        static_cast<int32_t>(LayoutDirection::kLayoutToStart);\n      int end_index = last_visible_index +\n                      static_cast<int32_t>(LayoutDirection::kLayoutToEnd);\n      const int data_count = list_container_->GetDataCount();\n      int target_end_index = end_index;\n      int section_count = last_visible_index - first_visible_index + 1;\n      if (section_count <= 0) {\n        DLIST_LOGE(\"LinearLayoutManager::PreloadSection: invalid section count \"\n                   << section_count);\n        return;\n      }\n      while (end_index >= 0 && end_index < data_count &&\n             last_visible_item_holder) {\n        target_end_index = std::min(end_index + section_count, data_count - 1);\n        TRACE_EVENT(\n            LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_PRELOAD_SECTION_TO_END,\n            \"info\",\n            base::FormatString(\"[%d -> %d]\", end_index, target_end_index));\n        UpdateLayoutStateToFillPreloadBuffer(\n            layout_state, end_index,\n            list_orientation_helper_->GetDecoratedEnd(last_visible_item_holder),\n            LayoutDirection::kLayoutToEnd);\n        // Fill preload section item holders\n        PreloadInternal(layout_state, target_end_index, true);\n        // Recycle\n        RecycleOffScreenItemHolders();\n        last_visible_item_holder =\n            list_container_->GetItemHolderForIndex(target_end_index);\n        end_index = target_end_index + 1;\n      }\n      int target_start_index = start_index;\n      while (start_index >= 0 && start_index < data_count &&\n             first_visible_item_holder) {\n        target_start_index = std::max(start_index - section_count, 0);\n        TRACE_EVENT(\n            LYNX_TRACE_CATEGORY, LINEAR_LAYOUT_MANAGER_PRELOAD_SECTION_TO_START,\n            \"info\",\n            base::FormatString(\"[%d -> %d]\", start_index, target_start_index));\n        UpdateLayoutStateToFillPreloadBuffer(\n            layout_state, start_index,\n            list_orientation_helper_->GetDecoratedStart(\n                first_visible_item_holder),\n            LayoutDirection::kLayoutToStart);\n        // Fill preload section item holders\n        PreloadInternal(layout_state, target_start_index, true);\n        // Recycle\n        RecycleOffScreenItemHolders();\n        first_visible_item_holder =\n            list_container_->GetItemHolderForIndex(target_start_index);\n        start_index = target_start_index - 1;\n      }\n    }\n  }\n}\n\n#if ENABLE_TRACE_PERFETTO\nvoid LinearLayoutManager::UpdateTraceDebugInfo(TraceEvent* event) const {\n  ListLayoutManager::UpdateTraceDebugInfo(event);\n  auto* list_type_info = event->add_debug_annotations();\n  list_type_info->set_name(\"list_type\");\n  list_type_info->set_string_value(\"single\");\n}\n#endif\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_linear_layout_manager.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LINEAR_LAYOUT_MANAGER_H_\n#define CORE_LIST_DECOUPLED_LINEAR_LAYOUT_MANAGER_H_\n\n#include <string>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"core/list/decoupled_list_layout_manager.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass LinearLayoutManager : public ListLayoutManager {\n protected:\n  class LayoutState {\n   public:\n    bool ValidPreload() const {\n      return preload_min_index_ != kInvalidIndex ||\n             preload_max_index_ != kInvalidIndex;\n    }\n\n    void ResetPreloadIndex() {\n      preload_min_index_ = kInvalidIndex;\n      preload_max_index_ = kInvalidIndex;\n    }\n\n    std::string ToString() const {\n      return base::FormatString(\n          \"LayoutState: index=%d \"\n          \"layout_direction=%d, offset=%f, available=%f, extra=%f\",\n          next_bind_index_, static_cast<int32_t>(layout_direction_),\n          next_layout_offset_, available_, extra_);\n    }\n\n    // Min index to bind after filling to start.\n    int min_layout_chunk_index_{-1};\n    // Next index to bind\n    int next_bind_index_{0};\n    // The min bind index in preload.\n    int preload_min_index_{kInvalidIndex};\n    // The max bind index in preload.\n    int preload_max_index_{kInvalidIndex};\n    // Layout direction\n    LayoutDirection layout_direction_{LayoutDirection::kLayoutToEnd};\n    // Layout start coordinate.\n    float next_layout_offset_{0.f};\n    // Number of pixels that we should fill, in the layout direction.\n    float available_{0.f};\n    // Extra available area.\n    float extra_{0.f};\n    // The latest updated content offset.\n    float latest_updated_content_offset_{0.f};\n  };\n\n  class LayoutChunkResult {\n   public:\n    void Reset() {\n      finished_ = false;\n      consumed_ = 0.f;\n    }\n\n    // Whether it is necessary to interrupt this fill.\n    bool finished_{false};\n    // The size of the remaining space for this fill.\n    float consumed_{0.f};\n  };\n\n public:\n  LinearLayoutManager(ListContainerImpl* list_container_impl);\n  ~LinearLayoutManager() override = default;\n\n  void OnBatchLayoutChildren() override;\n  void OnLayoutChildren(bool is_component_finished = false,\n                        int component_index = -1) override;\n\n protected:\n  void ScrollByInternal(float content_offset, float original_offset,\n                        bool from_platform) override;\n  void LayoutInvalidItemHolder(int first_invalid_index) override;\n  float GetTargetContentSize() override;\n  void PreloadSection() override;\n  // Render and layout one ItemHolder, GridLayoutManager overrides this function\n  // to render column-count ItemHolders or a full-span ItemHolder.\n  virtual void LayoutChunk(LayoutChunkResult& result, LayoutState& layout_state,\n                           bool preload_section = false);\n  // Update layout state to fill to start, GridLayoutManager overrides this\n  // function to handle LayoutState by it self.\n  virtual void UpdateLayoutStateToFillStart(\n      LayoutState& layout_state,\n      const ListAnchorManager::AnchorInfo& anchor_info);\n  // Update layout state to fill to end, GridLayoutManager overrides this\n  // function to handle LayoutState by it self.\n  virtual void UpdateLayoutStateToFillEnd(\n      LayoutState& layout_state,\n      const ListAnchorManager::AnchorInfo& anchor_info);\n  virtual void UpdateLayoutStateToFillPreloadBuffer(\n      LayoutState& layout_state, int index, float offset,\n      LayoutDirection layout_direction);\n  virtual int GetTargetIndexForPreloadBuffer(int start_index,\n                                             LayoutDirection layout_direction);\n#if ENABLE_TRACE_PERFETTO\n  void UpdateTraceDebugInfo(TraceEvent* event) const override;\n#endif\n\n private:\n  void OnLayoutChildrenInternal(ListAnchorManager::AnchorInfo& anchor_info,\n                                LayoutState& layout_state);\n  void OnLayoutAfter(LayoutState& layout_state);\n  void OnScrollAfter(LayoutState& layout_state, float original_offset);\n  void FillWithAnchor(LayoutState& layout_state,\n                      const ListAnchorManager::AnchorInfo& anchor_info);\n  void Fill(LayoutState& layout_state);\n  bool HasMore(int current_index, float remaining) const;\n  bool HasMore(const LayoutState& layout_state, int target_index) const;\n  void UpdateScrollAnchorInfo(ListAnchorManager::AnchorInfo& anchor_info,\n                              const ItemHolderSet& on_screen_children,\n                              const float content_offset);\n  void HandleLayoutOrScrollResult(LayoutState& layout_state, bool is_layout);\n\n  // Implement preload.\n  void HandlePreloadIfNeeded(LayoutState& layout_state,\n                             ListAnchorManager::AnchorInfo& anchor_info,\n                             bool from_layout);\n  bool Preload(LayoutState& layout_state);\n  void PreloadInternal(LayoutState& layout_state, int target_index,\n                       bool preload_section = false);\n  void RecycleOffPreloadItemHolders(bool recycle_to_end, int target_index);\n  void PreloadSectionOnNextFrame();\n  void PreloadSection(LayoutState& layout_state);\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LINEAR_LAYOUT_MANAGER_H_\n"
  },
  {
    "path": "core/list/decoupled_list_adapter.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_list_adapter.h\"\n\n#include <unordered_set>\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\nListAdapter::ListAdapter(ListContainerImpl* list_container_impl)\n    : list_container_(list_container_impl),\n      item_holder_map_(std::make_unique<ItemHolderMap>()),\n      adapter_helper_(std::make_unique<AdapterHelper>()) {\n  if (!list_container_) {\n    DLIST_LOGE(\"ListAdapter::ListAdapter: list_container_ is nullptr\");\n  }\n  adapter_helper_->SetDelegate(this);\n}\n\nvoid ListAdapter::OnErrorOccurred(lynx::base::LynxError error) {\n  list_container_->list_delegate()->OnErrorOccurred(std::move(error));\n}\n\n// Update data source for radon diff arch.\nstd::pair<ListAdapterDiffResult, bool> ListAdapter::UpdateRadonDataSource(\n    const pub::Value& radon_data_source) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_UPDATE_DATA_SOURCE);\n  if (!radon_data_source.IsMap()) {\n    return {ListAdapterDiffResult::kNone, false};\n  }\n  bool has_updated = false;\n  // Parse diff result.\n  radon_data_source.ForeachMap(\n      [this, &has_updated](const pub::Value& key, const pub::Value& value) {\n        if (key.IsString() && key.str() == kRadonDataDiffResult) {\n          has_updated = adapter_helper_->UpdateRadonDiffResult(value);\n        }\n      });\n  // TODO(@hujing.1):refactor the code according to the corresponding vector\n  // if the diffResult:insert[0,1,2,3],update from:[0,1,2,3],update\n  // To:[4,5,6,7].\n  // Firstly: should handle the removed actions and inserted actions,to keep the\n  // data size is right.\n  for (const auto& cur : adapter_helper_->removals()) {\n    ItemHolder* child_holder = GetItemHolderForIndex(cur);\n    if (child_holder) {\n      OnItemHolderRemoved(child_holder);\n    }\n  }\n  // Secondly: according to the old data,to keep itemHolder status right.\n  for (const auto& cur : adapter_helper_->move_from()) {\n    ItemHolder* child_holder = GetItemHolderForIndex(cur);\n    if (child_holder) {\n      OnItemHolderMovedFrom(child_holder);\n    }\n  }\n  for (const auto& cur : adapter_helper_->update_from()) {\n    ItemHolder* child_holder = GetItemHolderForIndex(cur);\n    if (child_holder) {\n      OnItemHolderUpdateFrom(child_holder);\n    }\n  }\n  // init list_container_info\n  const auto& value_factory = list_container_->value_factory();\n  std::unique_ptr<pub::Value> list_container_info;\n  if (value_factory) {\n    list_container_info = value_factory->CreateMap();\n  }\n  radon_data_source.ForeachMap(\n      [this, &list_container_info](const pub::Value& key,\n                                   const pub::Value& value) {\n        if (key.IsString()) {\n          const std::string& key_str = key.str();\n          if (key_str == kRadonDataEstimatedHeightPx) {\n            adapter_helper_->UpdateEstimatedHeightsPx(value);\n          } else if (key_str == kRadonDataEstimatedMainAxisSizePx) {\n            adapter_helper_->UpdateEstimatedSizesPx(value);\n          } else if (key_str == kRadonDataFullSpan) {\n            adapter_helper_->UpdateFullSpans(value);\n          } else if (key_str == kRadonDataStickyTop) {\n            adapter_helper_->UpdateStickyTops(value);\n            if (list_container_info) {\n              list_container_info->PushValueToMap(kListContainerInfoStickyStart,\n                                                  value);\n            }\n          } else if (key_str == kRadonDataStickyBottom) {\n            adapter_helper_->UpdateStickyBottoms(value);\n            if (list_container_info) {\n              list_container_info->PushValueToMap(kListContainerInfoStickyEnd,\n                                                  value);\n            }\n          } else if (key_str == kRadonDataItemKeys) {\n            adapter_helper_->UpdateItemKeys(value);\n            if (list_container_info) {\n              list_container_info->PushValueToMap(kListContainerInfoItemKeys,\n                                                  value);\n            }\n          }\n        }\n      });\n  // Thirdly: update the item holder status according to the new diffResult.\n  for (const auto& cur : adapter_helper_->move_to()) {\n    ItemHolder* child_holder = GetItemHolderForIndex(cur);\n    if (child_holder) {\n      OnItemHolderMovedTo(child_holder);\n    }\n  }\n  for (const auto& cur : adapter_helper_->update_to()) {\n    ItemHolder* child_holder = GetItemHolderForIndex(cur);\n    if (child_holder) {\n      OnItemHolderUpdateTo(child_holder, false);\n    }\n  }\n  // Flush list-container-info\n  if (list_container_info) {\n    list_container_->list_delegate()->FlushListContainerInfo(\n        kListContainerInfo, std::move(list_container_info), false);\n  }\n  // For output list diff info before clear\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_OUTPUT_DATA_SOURCE_DIFF_INFO,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  auto result = ListAdapterDiffResult::kNone;\n  if (has_updated) {\n    if (!adapter_helper_->removals().empty()) {\n      result |= ListAdapterDiffResult::kRemove;\n    }\n    if (!adapter_helper_->insertions().empty()) {\n      result |= ListAdapterDiffResult::kInsert;\n    }\n    if (!adapter_helper_->update_from().empty() &&\n        !adapter_helper_->update_to().empty()) {\n      result |= ListAdapterDiffResult::kUpdate;\n    }\n    if (!adapter_helper_->move_from().empty() &&\n        !adapter_helper_->move_to().empty()) {\n      result |= ListAdapterDiffResult::kMove;\n    }\n  }\n  return {result, HasExpectedDiffAnimation()};\n}\n\nbool ListAdapter::HasExpectedDiffAnimation() const {\n  return !(adapter_helper_->insertions().size() ==\n               adapter_helper_->item_keys().size() &&\n           adapter_helper_->update_from().empty() &&\n           adapter_helper_->update_to().empty() &&\n           adapter_helper_->move_from().empty() &&\n           adapter_helper_->move_to().empty() &&\n           adapter_helper_->removals().empty());\n}\n\n// Update data source for fiber arch.\nstd::pair<ListAdapterDiffResult, bool> ListAdapter::UpdateFiberDataSource(\n    const pub::Value& fiber_data_source) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_UPDATE_FIVER_DATA_SOURCE);\n  if (!fiber_data_source.IsMap()) {\n    return {ListAdapterDiffResult::kNone, false};\n  }\n  std::unique_ptr<pub::Value> insert_action =\n      fiber_data_source.GetValueForKey(kFiberDataInsertAction);\n  std::unique_ptr<pub::Value> remove_action =\n      fiber_data_source.GetValueForKey(kFiberDataRemoveAction);\n  std::unique_ptr<pub::Value> update_action =\n      fiber_data_source.GetValueForKey(kFiberDataUpdateAction);\n  // Firstly only generate insert / remove / update arrays.\n  adapter_helper_->UpdateFiberRemoveAction(remove_action);\n  adapter_helper_->UpdateFiberInsertAction(insert_action);\n  adapter_helper_->UpdateFiberUpdateAction(update_action);\n  // Mark dirty based on index.\n  MarkChildHolderDirty();\n  // Parse extra info from insert / remove / update actions.\n  adapter_helper_->UpdateFiberRemoveAction(remove_action, false);\n  adapter_helper_->UpdateFiberInsertAction(insert_action, false);\n  adapter_helper_->UpdateFiberUpdateAction(update_action, false);\n  // Update extra info.\n  adapter_helper_->UpdateFiberExtraInfo();\n  // Generate and flush list-container-info for fiber.\n  GenerateAndFlushListContainerInfo();\n  // For output list diff info before clear\n  TRACE_EVENT(LYNX_TRACE_CATEGORY,\n              LIST_ADAPTER_OUTPUT_FIBER_DATA_SOURCE_DIFF_INFO,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  if (adapter_helper_->HasValidDiff()) {\n    auto result = ListAdapterDiffResult::kNone;\n    if (!adapter_helper_->removals().empty()) {\n      result |= ListAdapterDiffResult::kRemove;\n    }\n    if (!adapter_helper_->insertions().empty()) {\n      result |= ListAdapterDiffResult::kInsert;\n    }\n    if (!adapter_helper_->update_from().empty() &&\n        !adapter_helper_->update_to().empty()) {\n      result |= ListAdapterDiffResult::kUpdate;\n    }\n    if (!adapter_helper_->move_from().empty() &&\n        !adapter_helper_->move_to().empty()) {\n      result |= ListAdapterDiffResult::kMove;\n    }\n    return {result, HasExpectedDiffAnimation()};\n  } else {\n    return {ListAdapterDiffResult::kNone, false};\n  }\n}\n\nvoid ListAdapter::GenerateAndFlushListContainerInfo() {\n  const auto& value_factory = list_container_->value_factory();\n  if (value_factory) {\n    std::unique_ptr<pub::Value> list_container_info =\n        value_factory->CreateMap();\n    std::unique_ptr<pub::Value> sticky_start_indexes =\n        value_factory->CreateArray();\n    std::unique_ptr<pub::Value> sticky_end_indexes =\n        value_factory->CreateArray();\n    std::unique_ptr<pub::Value> item_keys = value_factory->CreateArray();\n    if (list_container_info) {\n      if (sticky_start_indexes) {\n        for (const auto& index : adapter_helper_->sticky_tops()) {\n          sticky_start_indexes->PushInt32ToArray(index);\n        }\n        list_container_info->PushValueToMap(kListContainerInfoStickyStart,\n                                            *sticky_start_indexes);\n      }\n      if (sticky_end_indexes) {\n        for (const auto& index : adapter_helper_->sticky_bottoms()) {\n          sticky_end_indexes->PushInt32ToArray(index);\n        }\n        list_container_info->PushValueToMap(kListContainerInfoStickyEnd,\n                                            *sticky_end_indexes);\n      }\n      if (item_keys) {\n        for (const auto& item_key : adapter_helper_->item_keys()) {\n          item_keys->PushStringToArray(item_key);\n        }\n        list_container_info->PushValueToMap(kListContainerInfoItemKeys,\n                                            *item_keys);\n      }\n      list_container_->list_delegate()->FlushListContainerInfo(\n          kListContainerInfo, std::move(list_container_info), true);\n    }\n  }\n}\n\n// Update the latest data source to the ItemHolder and add updated ItemHolders\n// to children_ set in ChildrenHelper. If has new insertions, create ItemHolders\n// and add them to the ItemHolder map.\nvoid ListAdapter::UpdateItemHolderToLatest(\n    ListChildrenHelper* list_children_helper) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_UPDATE_ITEM_TO_LATEST);\n  if (!list_children_helper) {\n    return;\n  }\n  // Update anchor ref for removed on screen children.\n  // Note: This logic should be invoked before latest diff info being updated to\n  // all ItemHolders, so here UpdateAnchorRefItem() is invoked in the begin of\n  // UpdateItemHolderToLatest()\n  UpdateAnchorRefItem(list_children_helper);\n\n  const auto& children = list_children_helper->children();\n  const auto& attached_children = list_children_helper->attached_children();\n  const auto& last_binding_children =\n      list_children_helper->last_binding_children();\n  std::unordered_set<ItemHolder*> attached_children_set(\n      attached_children.begin(), attached_children.end());\n  std::unordered_set<ItemHolder*> last_binding_children_set(\n      last_binding_children.begin(), last_binding_children.end());\n  list_children_helper->ClearChildren();\n  // Note: If has diff info, the attached children set needs to be rebuilt to\n  // delete removed item holders.\n  list_children_helper->ClearAttachedChildren();\n  list_children_helper->ClearLastBindingChildren();\n  ItemHolder* item_holder = nullptr;\n  auto it = item_holder_map_->end();\n  for (const auto& pair : adapter_helper_->item_key_map()) {\n    const auto& item_key = pair.first;\n    int new_index = pair.second;\n    it = item_holder_map_->find(item_key);\n    if (item_holder_map_->end() != it) {\n      item_holder = it->second.get();\n      // One component with item-key == 'x' removed from 5 and inserted to 10,\n      // here the item_holder is marked removed. We reuse this item_holder for\n      // index 10, so we should clear removed flag and mark dirty.\n      if (IsRemoved(item_holder)) {\n        OnItemHolderReInsert(item_holder);\n      }\n    } else {\n      ListAnimationManager* list_animation_manager =\n          list_container_->list_animation_manager();\n      (*item_holder_map_)[item_key] = std::make_unique<ItemHolder>(\n          new_index, item_key, list_animation_manager);\n      item_holder = (*item_holder_map_)[item_key].get();\n      if (list_animation_manager->AnimationType() != ListAnimationType::kNone) {\n        item_holder->MarkInsertOpacity();\n      }\n      OnItemHolderInserted(item_holder);\n    }\n    CheckSticky(item_holder, new_index);\n    item_holder->SetIndex(new_index);\n    item_holder->SetItemFullSpan(IsFullSpanAtIndex(new_index));\n    item_holder->SetEstimatedSize(GetEstimatedSizeForIndex(new_index));\n    item_holder->SetRecyclable(IsRecyclableAtIndex(new_index));\n    list_children_helper->AddChild(children, item_holder);\n    if (attached_children_set.find(item_holder) !=\n        attached_children_set.end()) {\n      // Add item holder to attached children.\n      ItemElementDelegate* list_item_delegate =\n          GetItemElementDelegate(item_holder);\n      list_children_helper->AttachChild(item_holder, list_item_delegate);\n    }\n    if (last_binding_children_set.find(item_holder) !=\n        last_binding_children_set.end()) {\n      // Add item holder to last binding children.\n      list_children_helper->AddChild(last_binding_children, item_holder);\n    }\n  }\n}\n\nvoid ListAdapter::UpdateAnchorRefItem(\n    ListChildrenHelper* list_children_helper) {\n  const auto& children = list_children_helper->children();\n  const auto& on_screen_children = list_children_helper->on_screen_children();\n  bool has_valid_diff = list_adapter_helper()->HasValidDiff();\n  bool should_search_ref_anchor = list_container_->ShouldSearchRefAnchor();\n  SearchRefAnchorStrategy search_strategy =\n      list_container_->search_ref_anchor_strategy();\n  std::unordered_map<ItemHolder*, ItemHolder*> tmp_anchor_ref_map;\n  if (has_valid_diff && should_search_ref_anchor &&\n      !on_screen_children.empty() && !children.empty()) {\n    list_children_helper->ForEachChild(\n        on_screen_children,\n        [this, list_children_helper, &children, &tmp_anchor_ref_map,\n         search_strategy](ItemHolder* on_screen_child) {\n          // We only need to update removed on screen child.\n          if (IsRemoved(on_screen_child)) {\n            auto weak_anchor_ref = on_screen_child->weak_anchor_ref();\n            if (!weak_anchor_ref) {\n              // (1) Removed child's weak_anchor_ref is never set.\n              if (auto it = children.find(on_screen_child);\n                  it != children.end()) {\n                ItemHolder* anchor_ref_child =\n                    list_children_helper->GetFirstChildFrom(\n                        children, on_screen_child,\n                        [this, on_screen_child](const ItemHolder* item_holder) {\n                          return item_holder != on_screen_child &&\n                                 !IsRemoved(item_holder);\n                        },\n                        search_strategy == SearchRefAnchorStrategy::kToStart);\n                on_screen_child->SetWeakAnchorRef(anchor_ref_child);\n              } else {\n                // Unexpected case: child is not in children set.\n                DLIST_LOGE(\"[\" << list_container_\n                               << \"] ListAdapter::UpdateItemHolderToLatest: \"\n                                  \"on_screen_child is not at children set and \"\n                                  \"it's weak_anchor_ref is never set: index=\"\n                               << on_screen_child->index()\n                               << \", item-key=\" << on_screen_child->item_key());\n                on_screen_child->SetWeakAnchorRef(nullptr);\n              }\n            } else if (weak_anchor_ref && (*weak_anchor_ref)) {\n              // (2) Update weak_anchor_ref if needed.\n              ItemHolder* current_anchor_ref_child = (*weak_anchor_ref).get();\n              if (IsRemoved(current_anchor_ref_child)) {\n                // Current anchor ref child is removed, need to find a new\n                // anchor ref.\n                if (auto it = tmp_anchor_ref_map.find(current_anchor_ref_child);\n                    it != tmp_anchor_ref_map.end()) {\n                  // Fast find new ref anchor child from tmp_anchor_ref_map\n                  on_screen_child->SetWeakAnchorRef(it->second);\n                } else {\n                  if (auto it = children.find(current_anchor_ref_child);\n                      it != children.end()) {\n                    ItemHolder* new_anchor_ref_child =\n                        list_children_helper->GetFirstChildFrom(\n                            children, current_anchor_ref_child,\n                            [this, current_anchor_ref_child](\n                                const ItemHolder* item_holder) {\n                              return item_holder != current_anchor_ref_child &&\n                                     !IsRemoved(item_holder);\n                            },\n                            search_strategy ==\n                                SearchRefAnchorStrategy::kToStart);\n                    tmp_anchor_ref_map[current_anchor_ref_child] =\n                        new_anchor_ref_child;\n                    on_screen_child->SetWeakAnchorRef(new_anchor_ref_child);\n                  } else {\n                    tmp_anchor_ref_map[current_anchor_ref_child] = nullptr;\n                    on_screen_child->SetWeakAnchorRef(nullptr);\n                  }\n                }\n              }\n            }\n          }\n          return false;\n        });\n  }\n}\n\n// Mark all child ItemHolders's diff status.\nvoid ListAdapter::MarkChildHolderDirty() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_MARK_CHILD_DIRTY);\n  if (!adapter_helper_) {\n    return;\n  }\n  ItemHolder* child_holder = nullptr;\n  for (const auto& cur : adapter_helper_->removals()) {\n    if ((child_holder = GetItemHolderForIndex(cur))) {\n      OnItemHolderRemoved(child_holder);\n    }\n  }\n  for (const auto& cur : adapter_helper_->move_to()) {\n    child_holder = GetItemHolderForIndex(cur);\n    if (child_holder) {\n      OnItemHolderMovedTo(child_holder);\n    }\n  }\n  for (const auto& cur : adapter_helper_->move_from()) {\n    child_holder = GetItemHolderForIndex(cur);\n    if (child_holder) {\n      OnItemHolderMovedFrom(child_holder);\n    }\n  }\n  for (const auto& cur : adapter_helper_->update_to()) {\n    child_holder = GetItemHolderForIndex(cur);\n    if (child_holder) {\n      OnItemHolderUpdateTo(child_holder, true);\n    }\n  }\n  for (const auto& cur : adapter_helper_->update_from()) {\n    child_holder = GetItemHolderForIndex(cur);\n    if (child_holder) {\n      OnItemHolderUpdateFrom(child_holder);\n    }\n  }\n}\n\n// Get the ItemHolder for the specified index.\nItemHolder* ListAdapter::GetItemHolderForIndex(int index) {\n  if (adapter_helper_ && index >= 0 && index < GetDataCount()) {\n    auto item_key = adapter_helper_->GetItemKeyForIndex(index);\n    if (item_key &&\n        item_holder_map_->end() != item_holder_map_->find(*item_key)) {\n      return ((*item_holder_map_)[*item_key]).get();\n    }\n  }\n  return nullptr;\n}\n\n// Get whether the ItemHolder is full span for the specified index.\nbool ListAdapter::IsFullSpanAtIndex(int index) const {\n  return adapter_helper_ ? adapter_helper_->full_spans().end() !=\n                               adapter_helper_->full_spans().find(index)\n                         : false;\n}\n\nstd::unique_ptr<pub::Value> ListAdapter::GenerateDiffResult() const {\n  std::unique_ptr<pub::Value> diff_info;\n  const auto& value_factory = list_container_->value_factory();\n  if (value_factory && (diff_info = value_factory->CreateMap())) {\n    GenerateDiffArray(kDiffInfoInsertion, adapter_helper_->insertions(),\n                      diff_info);\n    GenerateDiffArray(kDiffInfoRemoval, adapter_helper_->removals(), diff_info);\n    GenerateDiffArray(kDiffInfoUpdateFrom, adapter_helper_->update_from(),\n                      diff_info);\n    GenerateDiffArray(kDiffInfoUpdateTo, adapter_helper_->update_to(),\n                      diff_info);\n    GenerateDiffArray(kDiffInfoMoveFrom, adapter_helper_->move_from(),\n                      diff_info);\n    GenerateDiffArray(kDiffInfoMoveTo, adapter_helper_->move_to(), diff_info);\n  }\n  return diff_info;\n}\n\nvoid ListAdapter::ClearDiffResult() { adapter_helper_->ClearDiffResult(); }\n\nvoid ListAdapter::GenerateDiffArray(\n    const std::string& diff_key, const std::vector<int32_t>& diff_array,\n    const std::unique_ptr<pub::Value>& diff_result) const {\n  std::unique_ptr<pub::Value> array_value;\n  const auto& value_factory = list_container_->value_factory();\n  if (value_factory && diff_result &&\n      (array_value = value_factory->CreateArray())) {\n    for (auto index : diff_array) {\n      array_value->PushInt32ToArray(index);\n    }\n    diff_result->PushValueToMap(diff_key, *array_value);\n  }\n}\n\n// Get estimated height for the specified index.\nfloat ListAdapter::GetEstimatedSizeForIndex(int index) {\n  float estimated_size = kInvalidDimensionSize;\n  const float layouts_unit_per_px =\n      list_container_->list_delegate()->GetLayoutsUnitPerPx();\n  // Note: Taking into account compatibility with lower versions, developers\n  // might set both estimated-main-axis-size-px and estimated-height-px,\n  // therefore, using estimated-main-axis-size-px in priority.\n  if (adapter_helper_ && index >= 0 &&\n      index < static_cast<int>(adapter_helper_->estimated_sizes_px().size())) {\n    estimated_size =\n        adapter_helper_->estimated_sizes_px()[index] * layouts_unit_per_px;\n    if (estimated_size > 0.f) {\n      return estimated_size;\n    }\n  }\n  if (adapter_helper_ && index >= 0 &&\n      index <\n          static_cast<int>(adapter_helper_->estimated_heights_px().size())) {\n    estimated_size =\n        adapter_helper_->estimated_heights_px()[index] * layouts_unit_per_px;\n    if (estimated_size > 0.f) {\n      return estimated_size;\n    }\n  }\n  return kInvalidDimensionSize;\n}\n\n// Get whether the ItemHolder is recyclable for the specified index.\nbool ListAdapter::IsRecyclableAtIndex(int index) const {\n  if (!adapter_helper_) {\n    return true;\n  }\n  const auto& unrecyclable_set = adapter_helper_->unrecyclable();\n  return unrecyclable_set.find(index) == unrecyclable_set.end();\n}\n\n// Check whether the ItemHolder is sticky item with the specified index.\nvoid ListAdapter::CheckSticky(ItemHolder* item_holder, int32_t index) {\n  if (!adapter_helper_) {\n    return;\n  }\n  bool sticky_top = false;\n  if (std::find(adapter_helper_->sticky_tops().begin(),\n                adapter_helper_->sticky_tops().end(),\n                (int32_t)index) != adapter_helper_->sticky_tops().end()) {\n    sticky_top = true;\n  }\n  bool sticky_bottom = false;\n  if (std::find(adapter_helper_->sticky_bottoms().begin(),\n                adapter_helper_->sticky_bottoms().end(),\n                (int32_t)index) != adapter_helper_->sticky_bottoms().end()) {\n    sticky_bottom = true;\n  }\n  item_holder->SetSticky(sticky_top, sticky_bottom);\n}\n\nint64_t ListAdapter::GenerateOperationId() const {\n  static int32_t base_operation_id = 0;\n  return (static_cast<int64_t>(list_container_->list_delegate()->GetImplId())\n          << 32) +\n         base_operation_id++;\n}\n\nvoid ListAdapter::RecycleAllItemHolders() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_RECYCLE_ALL_ITEM_HOLDER);\n  for (auto it = item_holder_map_->begin(); it != item_holder_map_->end();) {\n    const auto& item_holder = it->second;\n    if (item_holder) {\n      RecycleItemHolder(item_holder.get());\n    }\n    ++it;\n  }\n}\n\nvoid ListAdapter::RecycleRemovedItemHolders() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_RECYCLE_REMOVED_ITEM_HOLDER);\n  ListAnimationManager* list_animation_manager =\n      list_container_->list_animation_manager();\n  for (auto it = item_holder_map_->begin(); it != item_holder_map_->end();) {\n    const auto& item_holder = it->second.get();\n    if (item_holder && IsRemoved(item_holder)) {\n      RecycleItemHolder(item_holder);\n      if (list_animation_manager->UpdateAnimation() &&\n          list_animation_manager->AnimationType() != ListAnimationType::kNone) {\n        ++it;\n      } else {\n        it = item_holder_map_->erase(it);\n      }\n    } else {\n      ++it;\n    }\n  }\n}\n\nvoid ListAdapter::UpdateLayoutInfoToItemHolder(\n    ItemElementDelegate* list_item_delegate, ItemHolder* item_holder) {\n  if (list_item_delegate && item_holder && IsFinishedBinding(item_holder) &&\n      GetItemElementDelegate(item_holder) == list_item_delegate) {\n    item_holder->UpdateLayoutFromItemDelegate(list_item_delegate);\n  }\n}\n\nvoid ListAdapter::EnqueueElementsIfNeeded() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_ADAPTER_ENQUEUE_ELEMENTS);\n  bool need_flush = !fiber_flush_item_holder_set_.empty();\n  if (!fiber_flush_item_holder_set_.empty()) {\n    list_container_->list_children_helper()->ForEachChild(\n        fiber_flush_item_holder_set_, [this](ItemHolder* item_holder) {\n          EnqueueElement(item_holder);\n          return false;\n        });\n    fiber_flush_item_holder_set_.clear();\n  }\n  for (auto& it : *item_holder_map_) {\n    const auto& item_holder = it.second;\n    // Note: If the ItemHolder is removed, we only try to enqueue element, not\n    // erase it from item_holder_map_.\n    if (item_holder && IsRemoved(item_holder.get())) {\n      EnqueueElement(item_holder.get());\n      need_flush = true;\n    }\n  }\n  if (need_flush) {\n    list_container_->list_delegate()->FlushImmediately();\n  }\n}\n\n// It will invoked list's EnqueueComponent() to recycle component\n// bound with ItemHolder and remove platform view from parent.\nvoid ListAdapter::EnqueueElement(ItemHolder* item_holder) {\n  if (!item_holder) {\n    DLIST_LOGE(\"[\" << list_container_\n                   << \"] ListAdapter::EnqueueElement: null item holder\");\n    return;\n  }\n  if (auto type = list_container_->list_animation_manager()->AnimationType();\n      type != ListAnimationType::kNone) {\n    if (type == ListAnimationType::kRemove) {\n      item_holder->RecycleAfterAnimation(ItemHolderAnimationType::kOpacity);\n    } else if (type == ListAnimationType::kInsert) {\n      // The insert animation in a list may push a child off the screen, but at\n      // that moment we still need a transform animation, so deferred destroy is\n      // still necessary.\n      item_holder->RecycleAfterAnimation(ItemHolderAnimationType::kTransform);\n    }\n    return;\n  }\n  ItemElementDelegate* list_item_delegate = GetItemElementDelegate(item_holder);\n  if (list_item_delegate) {\n    int32_t list_item_id = list_item_delegate->GetImplId();\n    // Remove platform view and enqueue list item.\n    list_container_->list_delegate()->RemoveListItemPaintingNode(list_item_id);\n    list_container_->list_delegate()->EnqueueComponent(list_item_id);\n    // Detach child.\n    if (list_container_->list_children_helper()) {\n      list_container_->list_children_helper()->DetachChild(item_holder,\n                                                           list_item_delegate);\n    }\n    OnEnqueueElement(item_holder);\n  }\n}\n\n#if ENABLE_TRACE_PERFETTO\nvoid ListAdapter::UpdateTraceDebugInfo(TraceEvent* event) const {\n  // item-key\n  auto* item_keys = event->add_debug_annotations();\n  item_keys->set_name(\"item-keys\");\n  std::string item_keys_str = \"\";\n  int index = 0;\n  for (const auto& item_key : adapter_helper_->item_keys()) {\n    item_keys_str += \"(\" + std::to_string(index++) + \") \" + item_key + \"\\n\";\n  }\n  item_keys->set_string_value(item_keys_str);\n\n  bool has_update = false;\n  // update-from\n  auto* update_from = event->add_debug_annotations();\n  update_from->set_name(\"update-from\");\n  std::string update_from_str = \"\";\n  for (const auto& index : adapter_helper_->update_from()) {\n    has_update = true;\n    update_from_str += std::to_string(index) + \"\\n\";\n  }\n  update_from->set_string_value(update_from_str);\n\n  // update-to\n  auto* update_to = event->add_debug_annotations();\n  update_to->set_name(\"update-to\");\n  std::string update_to_str = \"\";\n  for (const auto& index : adapter_helper_->update_to()) {\n    has_update = true;\n    update_to_str += std::to_string(index) + \"\\n\";\n  }\n  update_to->set_string_value(update_to_str);\n\n  // insert\n  auto* insert = event->add_debug_annotations();\n  insert->set_name(\"insert\");\n  std::string insert_str = \"\";\n  for (const auto& index : adapter_helper_->insertions()) {\n    has_update = true;\n    insert_str += std::to_string(index) + \"\\n\";\n  }\n  insert->set_string_value(insert_str);\n\n  // remove\n  auto* remove = event->add_debug_annotations();\n  remove->set_name(\"remove\");\n  std::string remove_str = \"\";\n  for (const auto& index : adapter_helper_->removals()) {\n    has_update = true;\n    remove_str += std::to_string(index) + \"\\n\";\n  }\n  remove->set_string_value(remove_str);\n\n  // has update\n  auto* has_update_annotations = event->add_debug_annotations();\n  has_update_annotations->set_name(\"has_update\");\n  has_update_annotations->set_string_value(std::to_string(has_update));\n\n  // sticky top\n  auto* sticky_top = event->add_debug_annotations();\n  sticky_top->set_name(\"sticky-top\");\n  std::string sticky_top_str = \"\";\n  for (const auto& index : adapter_helper_->sticky_tops()) {\n    sticky_top_str += std::to_string(index) + \"\\n\";\n  }\n  sticky_top->set_string_value(sticky_top_str);\n\n  // sticky bottom\n  auto* sticky_bottom = event->add_debug_annotations();\n  sticky_bottom->set_name(\"sticky-bottom\");\n  std::string sticky_bottom_str = \"\";\n  for (const auto& index : adapter_helper_->sticky_bottoms()) {\n    sticky_bottom_str += std::to_string(index) + \"\\n\";\n  }\n  sticky_bottom->set_string_value(sticky_bottom_str);\n\n  // full span\n  auto* full_span = event->add_debug_annotations();\n  full_span->set_name(\"full-span\");\n  std::string full_span_str = \"\";\n  for (const auto& index : adapter_helper_->full_spans()) {\n    full_span_str += std::to_string(index) + \"\\n\";\n  }\n  full_span->set_string_value(full_span_str);\n\n  // estimated_heights_px\n  auto* estimated_heights_px = event->add_debug_annotations();\n  estimated_heights_px->set_name(\"estimated-heights-px\");\n  std::string estimated_heights_px_str = \"\";\n  for (const auto& value : adapter_helper_->estimated_heights_px()) {\n    estimated_heights_px_str += std::to_string(value) + \"\\n\";\n  }\n  estimated_heights_px->set_string_value(estimated_heights_px_str);\n\n  // estimated_sizes_px\n  auto* estimated_sizes_px = event->add_debug_annotations();\n  estimated_sizes_px->set_name(\"estimated-sizes-px\");\n  std::string estimated_sizes_px_str = \"\";\n  for (const auto& value : adapter_helper_->estimated_sizes_px()) {\n    estimated_sizes_px_str += std::to_string(value) + \"\\n\";\n  }\n  estimated_sizes_px->set_string_value(estimated_sizes_px_str);\n\n  // recyclable\n  auto* unrecyclable = event->add_debug_annotations();\n  unrecyclable->set_name(\"unrecyclable\");\n  std::string unrecyclable_info_str = \"\";\n  for (const auto& index : adapter_helper_->unrecyclable()) {\n    unrecyclable_info_str += std::to_string(index) + \"\\n\";\n  }\n  unrecyclable->set_string_value(unrecyclable_info_str);\n}\n\nvoid ListAdapter::UpdateTraceDebugInfo(TraceEvent* event,\n                                       ItemHolder* item_holder) const {\n  if (item_holder) {\n    auto* index_info = event->add_debug_annotations();\n    index_info->set_name(\"index\");\n    index_info->set_string_value(std::to_string(item_holder->index()));\n\n    auto* item_key_info = event->add_debug_annotations();\n    item_key_info->set_name(\"item-key\");\n    item_key_info->set_string_value(item_holder->item_key());\n  }\n}\n#endif\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_adapter.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LIST_ADAPTER_H_\n#define CORE_LIST_DECOUPLED_LIST_ADAPTER_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"core/list/decoupled_adapter_helper.h\"\n#include \"core/list/decoupled_item_holder.h\"\n#include \"core/list/decoupled_list_children_helper.h\"\n#include \"core/public/pipeline_option.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListContainerImpl;\n\nclass ListAdapter : public AdapterHelper::Delegate {\n public:\n  ListAdapter(ListContainerImpl* list_container_impl);\n\n  virtual ~ListAdapter() = default;\n\n  ListAdapter(const ListAdapter& rhs) = delete;\n\n  ListAdapter& operator=(const ListAdapter& rhs) = delete;\n\n  ListAdapter(ListAdapter&& rhs) noexcept\n      : list_container_(rhs.list_container_),\n        item_holder_map_(std::move(rhs.item_holder_map_)),\n        adapter_helper_(std::move(rhs.adapter_helper_)) {\n    adapter_helper_->SetDelegate(this);\n    rhs.Release();\n  }\n\n  ListAdapter& operator=(ListAdapter&& rhs) noexcept {\n    if (this != &rhs) {\n      list_container_ = rhs.list_container_;\n      item_holder_map_ = std::move(rhs.item_holder_map_);\n      adapter_helper_ = std::move(rhs.adapter_helper_);\n      adapter_helper_->SetDelegate(this);\n      rhs.Release();\n    }\n    return *this;\n  }\n\n  void OnErrorOccurred(lynx::base::LynxError error) override;\n\n protected:\n  // Handle diff insert.\n  virtual void OnItemHolderInserted(ItemHolder* item_holder) = 0;\n\n  // Handle diff removed.\n  virtual void OnItemHolderRemoved(ItemHolder* item_holder) = 0;\n\n  // Handle diff update from\n  virtual void OnItemHolderUpdateFrom(ItemHolder* item_holder) = 0;\n\n  // Handle diff update to\n  virtual void OnItemHolderUpdateTo(ItemHolder* item_holder,\n                                    bool fiber_flush) = 0;\n\n  // Handle diff moved from\n  virtual void OnItemHolderMovedFrom(ItemHolder* item_holder) = 0;\n\n  // Handle diff moved from\n  virtual void OnItemHolderMovedTo(ItemHolder* item_holder) = 0;\n\n  // Handle diff remove and insert again.\n  virtual void OnItemHolderReInsert(ItemHolder* item_holder) = 0;\n\n  virtual void OnEnqueueElement(ItemHolder* item_holder) = 0;\n\n#if ENABLE_TRACE_PERFETTO\n  void UpdateTraceDebugInfo(TraceEvent* event) const;\n\n  virtual void UpdateTraceDebugInfo(TraceEvent* event,\n                                    ItemHolder* item_holder) const;\n#endif\n\n public:\n  // Handle full data updated.\n  virtual void OnDataSetChanged() = 0;\n\n  // Bind the item holder with index.\n  virtual bool BindItemHolder(ItemHolder* item_holder, int index,\n                              bool preload_section = false) = 0;\n\n  // Bind item holders in the set.\n  virtual void BindItemHolders(const ItemHolderSet& item_holder_set) = 0;\n\n  // Finish bind item holder with element.\n  virtual void OnFinishBindItemHolder(\n      ItemElementDelegate* list_item_delegate,\n      const std::shared_ptr<tasm::PipelineOptions>& options) = 0;\n\n  // Finish bind item holders with elements\n  virtual void OnFinishBindItemHolders(\n      const std::vector<ItemElementDelegate*>& list_items,\n      const std::shared_ptr<tasm::PipelineOptions>& options) = 0;\n\n  // Recycle ItemHolder.\n  virtual void RecycleItemHolder(ItemHolder* item_holder) = 0;\n\n  // Return whether the ItemHolder has already been bound, if return true, it\n  // means the ItemHolder is a no dirty node, but with no valid list item\n  // element.\n  virtual bool IsRecycled(const ItemHolder* item_holder) = 0;\n\n  // Return whether the ItemHolder is in binding.\n  virtual bool IsBinding(const ItemHolder* item_holder) = 0;\n\n  // Return whether the ItemHolder is in finish binding, if return true, it\n  // means the ItemHolder is a no dirty node with valid list item element.\n  virtual bool IsFinishedBinding(const ItemHolder* item_holder) = 0;\n\n  // Return whether the ItemHolder is dirty\n  virtual bool IsDirty(const ItemHolder* item_holder) = 0;\n\n  // Return whether the ItemHolder is update_to\n  virtual bool IsUpdated(const ItemHolder* item_holder) = 0;\n\n  // Return whether the ItemHolder is removed\n  virtual bool IsRemoved(const ItemHolder* item_holder) = 0;\n\n  virtual ItemElementDelegate* GetItemElementDelegate(\n      const ItemHolder* item_holder) = 0;\n\n  virtual void RecycleItemHolder(const ItemHolder* item_holder) {}\n\n  // Return diff result and whether to use animation in this diff process,\n  // animation will not be enabled in first screen.\n  std::pair<ListAdapterDiffResult, bool> UpdateRadonDataSource(\n      const pub::Value& radon_data_source);\n\n  std::pair<ListAdapterDiffResult, bool> UpdateFiberDataSource(\n      const pub::Value& fiber_data_source);\n\n  // Return parsed diff result.\n  std::unique_ptr<pub::Value> GenerateDiffResult() const;\n\n  void ClearDiffResult();\n\n  void UpdateItemHolderToLatest(ListChildrenHelper* list_children_helper);\n\n  // Recycle all itemHolders when basic list props changed such as\n  // column-count/list-type.\n  void RecycleAllItemHolders();\n\n  // Recycle all removed ItemHolders.\n  void RecycleRemovedItemHolders();\n\n  // If the list item is self-layout-updated, we invoke the method to update\n  // layout info to the ItemHolder.\n  void UpdateLayoutInfoToItemHolder(ItemElementDelegate* list_item_delegate,\n                                    ItemHolder* item_holder);\n\n  void EnqueueElementsIfNeeded();\n\n  ItemHolder* GetItemHolderForIndex(int index);\n\n  bool IsFullSpanAtIndex(int index) const;\n\n  void Release() {\n    list_container_ = nullptr;\n    adapter_helper_ = nullptr;\n    item_holder_map_ = nullptr;\n  }\n\n  int GetDataCount() const {\n    return adapter_helper_ ? adapter_helper_->GetDateCount() : 0;\n  }\n\n  bool HasFullSpanItems() { return !adapter_helper_->full_spans().empty(); }\n\n  const std::vector<int32_t>& GetStickyTops() const {\n    return adapter_helper_->sticky_tops();\n  }\n\n  const std::vector<int32_t>& GetStickyBottoms() const {\n    return adapter_helper_->sticky_bottoms();\n  }\n\n  bool IsRecyclableAtIndex(int index) const;\n\n  AdapterHelper* list_adapter_helper() const { return adapter_helper_.get(); }\n\n  const std::unique_ptr<ItemHolderMap>& item_holder_map() const {\n    return item_holder_map_;\n  }\n\n protected:\n  int64_t GenerateOperationId() const;\n\n  // It will invoked list's EnqueueComponent() to recycle component\n  // bound with ItemHolder and remove platform view from parent.\n  void EnqueueElement(ItemHolder* item_holder);\n\n private:\n  float GetEstimatedSizeForIndex(int index);\n\n  void MarkChildHolderDirty();\n\n  void CheckSticky(ItemHolder* item_holder, int32_t index);\n\n  bool HasExpectedDiffAnimation() const;\n\n  void UpdateAnchorRefItem(ListChildrenHelper* list_children_helper);\n\n  void GenerateAndFlushListContainerInfo();\n\n  void GenerateDiffArray(const std::string& diff_key,\n                         const std::vector<int32_t>& diff_array,\n                         const std::unique_ptr<pub::Value>& diff_result) const;\n\n protected:\n  ListContainerImpl* list_container_{nullptr};\n  std::unique_ptr<ItemHolderMap> item_holder_map_;\n  ItemHolderSet fiber_flush_item_holder_set_;\n\n private:\n  std::unique_ptr<AdapterHelper> adapter_helper_;\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LIST_ADAPTER_H_\n"
  },
  {
    "path": "core/list/decoupled_list_adapter_unittest.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/list/decoupled_list_adapter.h\"\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/value_wrapper/value_impl_lepus.h\"\n#include \"testing/fiber_data_source.h\"\n#include \"testing/mock_list_element.h\"\n#include \"testing/mock_list_item_element.h\"\n#include \"testing/radon_data_source.h\"\n#include \"testing/utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListAdapterTest : public ::testing::Test {\n public:\n  ListAdapterTest() = default;\n  ~ListAdapterTest() override = default;\n\n  std::unique_ptr<MockListElement> mock_list_element_{nullptr};\n  std::unique_ptr<ListContainerImpl> list_container_impl_{nullptr};\n  std::shared_ptr<pub::PubValueFactoryDefault> value_factory_{nullptr};\n  ListLayoutManager* list_layout_manager_{nullptr};\n  ListAdapter* list_adapter_{nullptr};\n  AdapterHelper* list_adapter_helper_{nullptr};\n\n  void SetUp() override {\n    value_factory_ = std::make_shared<pub::PubValueFactoryDefault>();\n    mock_list_element_ = std::make_unique<MockListElement>();\n    list_container_impl_ = std::make_unique<ListContainerImpl>(\n        mock_list_element_.get(), value_factory_);\n    list_layout_manager_ = list_container_impl_->list_layout_manager();\n    list_adapter_ = list_container_impl_->list_adapter();\n    list_adapter_helper_ = list_adapter_->list_adapter_helper();\n  }\n};\n\nTEST_F(ListAdapterTest, UpdateRadonDataSource) {\n  testing::RadonDataSource radon_data_source{\n      .item_keys_ = {\"A_0\", \"B_1\", \"C_2\", \"D_3\", \"E_4\", \"F_5\", \"G_6\", \"H_7\",\n                     \"I_8\"},\n      .insertion_ = {0, 1, 2, 3, 4, 5, 6, 7, 8},\n      .estimated_height_pxs_ = {100, 100, 100, 100, 100, 100, 100, 100, 100},\n      .estimated_main_axis_size_pxs_ = {100, 100, 100, 100, 100, 100, 100, 100,\n                                        100},\n      .sticky_tops_ = {0, 1},\n      .sticky_bottoms_ = {7, 8},\n      .full_spans_ = {0, 1, 7, 8},\n  };\n  auto result = list_adapter_->UpdateRadonDataSource(pub::ValueImplLepus(\n      lepus::Value(radon_data_source.GenerateDataSource())));\n  EXPECT_EQ(result.first, ListAdapterDiffResult::kInsert);\n  EXPECT_EQ(list_adapter_helper_->item_keys().size(),\n            radon_data_source.GetItemCount());\n  EXPECT_EQ(list_adapter_helper_->item_key_map().size(),\n            radon_data_source.GetItemCount());\n  EXPECT_EQ(list_adapter_->GetDataCount(), radon_data_source.GetItemCount());\n  EXPECT_EQ(list_adapter_helper_->estimated_heights_px().size(),\n            radon_data_source.GetItemCount());\n  EXPECT_EQ(list_adapter_helper_->estimated_sizes_px().size(),\n            radon_data_source.GetItemCount());\n  EXPECT_EQ(list_adapter_helper_->full_spans().size(),\n            radon_data_source.full_spans_.size());\n  EXPECT_EQ(list_adapter_helper_->sticky_tops().size(),\n            radon_data_source.sticky_tops_.size());\n  EXPECT_EQ(list_adapter_helper_->sticky_bottoms().size(),\n            radon_data_source.sticky_bottoms_.size());\n}\n\nTEST_F(ListAdapterTest, UpdateFiberDataSource) {\n  testing::InsertAction insert_action{\n      .insert_ops_ = {\n          {.position_ = 0, \"A_0\", 100, true, true, false, false},\n          {.position_ = 1, \"B_1\", 100, true, true, false, false},\n          {.position_ = 2, \"C_2\", 100, true, true, false, false},\n          {.position_ = 3, \"D_3\", 100, true, true, false, false},\n          {.position_ = 4, \"E_4\", 100, true, true, false, false},\n          {.position_ = 5, \"F_5\", 100, true, true, false, false},\n          {.position_ = 6, \"G_6\", 100, true, true, false, false},\n          {.position_ = 7, \"H_7\", 100, true, true, false, false},\n          {.position_ = 8, \"I_8\", 100, true, true, false, false},\n          {.position_ = 9, \"J_9\", 100, true, true, false, false},\n      }};\n  testing::FiberDataSource fiber_data_source{\n      .insert_action_ = insert_action,\n  };\n  auto result = list_adapter_->UpdateFiberDataSource(\n      pub::ValueImplLepus(lepus::Value(fiber_data_source.Resolve())));\n  EXPECT_EQ(result.first, ListAdapterDiffResult::kInsert);\n  EXPECT_EQ(list_adapter_helper_->item_keys().size(), 10);\n  EXPECT_EQ(list_adapter_helper_->item_key_map().size(), 10);\n  EXPECT_EQ(list_adapter_->GetDataCount(), 10);\n  EXPECT_EQ(list_adapter_helper_->estimated_sizes_px().size(), 10);\n  EXPECT_EQ(list_adapter_helper_->full_spans().size(), 10);\n  EXPECT_EQ(list_adapter_helper_->sticky_tops().size(), 10);\n  EXPECT_EQ(list_adapter_helper_->sticky_bottoms().size(), 0);\n  EXPECT_EQ(list_adapter_helper_->unrecyclable().size(), 10);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_anchor_manager.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_list_anchor_manager.h\"\n\n#include <algorithm>\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/list/decoupled_list_layout_manager.h\"\n\nnamespace lynx {\nnamespace list {\n\nListAnchorManager::ListAnchorManager(ListLayoutManager* list_layout_manager)\n    : list_layout_manager_(list_layout_manager) {}\n\n// Update anchor info when layout children.\nvoid ListAnchorManager::RetrieveAnchorInfoBeforeLayout(\n    AnchorInfo& anchor_info, int finishing_binding_index) {\n  // 1. Try find anchor from pending data, include initial-scroll-index or\n  // non-smooth scroll target.\n  if (FindAnchorFromPendingData(anchor_info)) {\n    DLIST_LOGI(\"[\" << list_container_\n                   << \"] Find anchor info from pending data.\");\n    return;\n  }\n  // 2. Try find anchor from children.\n  DLIST_LOGI(\"[\" << list_container_\n                 << \"] Find anchor info from existing children.\");\n  FindAnchorFromChildren(anchor_info, finishing_binding_index);\n}\n\nbool ListAnchorManager::FindAnchorFromPendingData(AnchorInfo& anchor_info) {\n  int anchor_index = kInvalidIndex;\n  bool valid_init_scroll_index = IsValidInitialScrollIndex();\n  bool valid_non_smooth_scroll_target =\n      scrolling_info_.IsValidNonSmoothScrollTarget();\n  if (valid_init_scroll_index) {\n    anchor_index = initial_scroll_index_;\n  } else if (valid_non_smooth_scroll_target) {\n    anchor_index = scrolling_info_.scrolling_target_;\n  }\n  ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(anchor_index);\n  if (item_holder) {\n    // Note: Use initial-scroll-index or no-smooth scroll target as anchor and\n    // fill a screen size area, and since item_holders' layout not finished, do\n    // not set coordinate here.\n    anchor_info.valid_ = true;\n    anchor_info.index_ = anchor_index;\n    anchor_info.item_holder_ = item_holder;\n    anchor_info.start_alignment_delta_ = 0;\n  }\n  return valid_init_scroll_index || valid_non_smooth_scroll_target;\n}\n\nvoid ListAnchorManager::FindAnchorFromChildren(AnchorInfo& anchor_info,\n                                               int finishing_binding_index) {\n  // TODO(dingwang.wxx): impl another find anchor logic for layout from end.\n  FindAnchor(anchor_info, !anchor_priority_from_begin_,\n             finishing_binding_index);\n  ClearDiffReference();\n}\n\nvoid ListAnchorManager::AdjustAnchorInfoAfterLayout(AnchorInfo& anchor_info) {\n  if (IsValidInitialScrollIndex() ||\n      scrolling_info_.IsValidNonSmoothScrollTarget() ||\n      anchor_info.is_removed_child_ref_) {\n    // For cases:\n    // a. valid initial-scroll-index\n    // b. non-smooth scroll\n    // c. anchor is from removed child ref.\n    float start =\n        list_orientation_helper_->GetDecoratedStart(anchor_info.item_holder_);\n    list_layout_manager_->SetContentOffset(start, false);\n    anchor_info.start_offset_ = start;\n    if (anchor_info.is_removed_child_ref_) {\n      if (anchor_info.align_start_) {\n        anchor_info.start_alignment_delta_ = 0;\n      } else {\n        anchor_info.start_alignment_delta_ =\n            list_orientation_helper_->GetMeasurement() +\n            anchor_info.item_holder_->top_inset();\n        float adjusted_content_offset =\n            list_orientation_helper_->GetStart(anchor_info.item_holder_) -\n            anchor_info.start_alignment_delta_;\n        list_layout_manager_->SetContentOffset(adjusted_content_offset, false);\n      }\n    }\n  } else {\n    // Note: The anchor_info.start_offset_ is firstly updated in FindAnchor()\n    // which is invoked before LayoutInvalidItemHolder(). Here need update\n    // anchor layout coordinate(start_offset_) after invoking\n    // LayoutInvalidItemHolder(0) because it is possible to insert or delete\n    // some items before the anchor.\n    float coordinate =\n        list_orientation_helper_->GetDecoratedStart(anchor_info.item_holder_);\n    float content_offset = list_layout_manager_->content_offset();\n    list_layout_manager_->SetContentOffset(\n        content_offset + coordinate - anchor_info.start_offset_, false);\n    // Rest anchor's coordinate\n    anchor_info.start_offset_ = coordinate;\n  }\n}\n\nvoid ListAnchorManager::AdjustContentOffsetWithAnchor(\n    ListAnchorManager::AnchorInfo& anchor_info, float content_offset) {\n  if (anchor_info.valid_) {\n    list_layout_manager_->SetContentOffset(\n        list_orientation_helper_->GetStart(anchor_info.item_holder_) -\n        anchor_info.start_alignment_delta_);\n  } else {\n    // Note: due to the update in content_size, here needs invoke\n    // SetContentOffset() again to trim the content_offset.\n    list_layout_manager_->SetContentOffset(content_offset);\n  }\n}\n\nvoid ListAnchorManager::UpdateAnchorWithItemHolder(AnchorInfo& anchor_info,\n                                                   ItemHolder* item_holder) {\n  anchor_info.valid_ = true;\n  anchor_info.index_ = item_holder->index();\n  anchor_info.item_holder_ = item_holder;\n  AdjustAnchorAlignment(anchor_info);\n}\n\nvoid ListAnchorManager::FindAnchor(AnchorInfo& anchor_info, bool from_end,\n                                   int finishing_binding_index) {\n  if (list_container_->GetDataCount() == 0) {\n    anchor_info.Reset();\n    return;\n  }\n  // 1. Get anchor candidates from children.\n  const auto& anchor_candidates =\n      GetAnchorCandidates(finishing_binding_index, from_end);\n  // The priority of all anchor candidates:\n  //   no_dirty_item_holder > updated_item_holder >\n  //   binding_item_holder > finishing_binding_item_holder\n  auto it = std::find_if(anchor_candidates.begin(), anchor_candidates.end(),\n                         [](ItemHolder* item_holder) { return item_holder; });\n  if (!anchor_info.valid_ && it != anchor_candidates.end()) {\n    UpdateAnchorWithItemHolder(anchor_info, *it);\n  }\n  // 2. Get anchor from outside children.\n  if (!anchor_info.valid_) {\n    if (list_container_->ShouldSearchRefAnchor()) {\n      FindAnchorFromRef(anchor_info);\n    } else {\n      ItemHolder* outside_bounds_item_holder =\n          from_end ? first_valid_item_holder_below_screen_\n                   : last_valid_item_holder_up_screen_;\n      if (outside_bounds_item_holder) {\n        anchor_info.valid_ = true;\n        anchor_info.index_ = outside_bounds_item_holder->index();\n        anchor_info.item_holder_ = outside_bounds_item_holder;\n        anchor_info.start_offset_ = list_orientation_helper_->GetDecoratedStart(\n            outside_bounds_item_holder);\n        anchor_visibility_ = AnchorVisibility::kAnchorVisibilityHide;\n        AdjustAnchorAlignment(anchor_info);\n      }\n    }\n  }\n  // 3. No anchor found. layout from list padding top\n  DLIST_LOGI(\"[\" << list_container_ << \"] Find anchor info from begin or end.\");\n  if (!anchor_info.valid_) {\n    anchor_info.valid_ = true;\n    anchor_info.index_ = 0;\n    anchor_info.item_holder_ = list_adapter_->GetItemHolderForIndex(0);\n    anchor_info.AssignCoordinateFromPadding(list_orientation_helper_);\n    anchor_info.start_alignment_delta_ = anchor_info.start_offset_;\n  }\n}\n\nvoid ListAnchorManager::FindAnchorFromRef(AnchorInfo& anchor_info) {\n  const auto& children = list_children_helper_->children();\n  const auto& on_screen_children = list_children_helper_->on_screen_children();\n  SearchRefAnchorStrategy search_strategy =\n      list_container_->search_ref_anchor_strategy();\n  if (!children.empty() && !on_screen_children.empty()) {\n    ItemHolder* target_ref_child = nullptr;\n    list_children_helper_->ForEachChild(\n        on_screen_children,\n        [this, &target_ref_child, &children](ItemHolder* on_screen_child) {\n          if (!list_adapter_->IsRemoved(on_screen_child)) {\n            return false;\n          }\n          if (ItemHolder* ref_holder = on_screen_child->GetAnchorRefHolder();\n              ref_holder && children.find(ref_holder) != children.end()) {\n            target_ref_child = ref_holder;\n            return true;\n          }\n          return false;\n        },\n        search_strategy == SearchRefAnchorStrategy::kToEnd);\n    // update anchor info.\n    anchor_info.valid_ = true;\n    anchor_info.is_removed_child_ref_ = true;\n    if (search_strategy == SearchRefAnchorStrategy::kToStart) {\n      anchor_info.align_start_ = true;\n      if (target_ref_child) {\n        //        col-0            col-1\n        //                   +______________+\n        //                   |              |\n        //   +--------------+|              |  ----- content offset\n        //   |              ||  target ref  |\n        //   |    Item1     ||              |\n        //   | (next fill)  |+______________+\n        //   |              |+______________+\n        //   +______________+|              |\n        //                   |    Item2     |\n        //                   |              |\n        //                   |              |\n        //                   +______________+\n        anchor_info.index_ =\n            std::min(target_ref_child->index() +\n                         static_cast<int>(LayoutDirection::kLayoutToEnd),\n                     static_cast<int>(children.size()) - 1);\n      } else {\n        anchor_info.index_ = 0;\n      }\n    } else if (search_strategy == SearchRefAnchorStrategy::kToEnd) {\n      anchor_info.align_start_ = false;\n      if (target_ref_child) {\n        //        col-0            col-1\n        //                   +______________+\n        //                   |              |\n        //   +--------------+|              |\n        //   |              ||    Item2     |\n        //   |    Item1     ||              |\n        //   | (next fill)  |+______________+\n        //   |              |+______________+\n        //   +______________+|              | ----- list end\n        //                   |  target ref  |\n        //                   |              |\n        //                   |              |\n        //                   +______________+\n        anchor_info.index_ = target_ref_child->index();\n      } else {\n        // Note: here we want to layout from the last one, so need to reset\n        // anchor_info.align_start_ to true.\n        anchor_info.align_start_ = true;\n        anchor_info.index_ = static_cast<int>(children.size()) - 1;\n      }\n    }\n    anchor_info.item_holder_ =\n        list_container_->GetItemHolderForIndex(anchor_info.index_);\n  }\n}\n\nbase::InlineVector<ItemHolder*, kAnchorCandidateSize>\nListAnchorManager::GetAnchorCandidates(int finishing_binding_index,\n                                       bool from_end) {\n  // The priority level is:\n  // 1. No dirty item_holder\n  // 2. No dirty but binding item_holder(multi-thread)\n  // 3. The item_holder finishing binding which cause this layout\n  // 4. Updated item_holder (Not removed or newly inserted)\n  // Find these item_holders can be chosen from.\n  ItemHolder* no_dirty_item_holder = nullptr;\n  ItemHolder* updated_item_holder = nullptr;\n  ItemHolder* binding_item_holder = nullptr;\n  ItemHolder* finishing_binding_item_holder = nullptr;\n  const auto& on_screen_children = list_children_helper_->on_screen_children();\n  list_children_helper_->ForEachChild(\n      on_screen_children,\n      [this, &no_dirty_item_holder, &updated_item_holder, &binding_item_holder,\n       &finishing_binding_item_holder,\n       finishing_binding_index](ItemHolder* item_holder) {\n        // Sticky item_holder can not be choosen as anchor\n        if (!list_layout_manager_->IsItemHolderNotAtStickyPosition(\n                item_holder)) {\n          return false;\n        }\n        int index = item_holder->index();\n        if ((list_adapter_->IsFinishedBinding(item_holder) ||\n             list_adapter_->IsRecycled(item_holder)) &&\n            index != finishing_binding_index) {\n          no_dirty_item_holder = item_holder;\n          return true;\n        } else if (!list_adapter_->IsDirty(item_holder) &&\n                   list_adapter_->IsBinding(item_holder) &&\n                   !binding_item_holder) {\n          binding_item_holder = item_holder;\n        } else if (!list_adapter_->IsDirty(item_holder) &&\n                   index == finishing_binding_index) {\n          // Only when this finishing_binding_item_holder is in\n          // on_screen_children can it be anchor.\n          finishing_binding_item_holder = item_holder;\n        } else if (list_adapter_->IsDirty(item_holder) &&\n                   list_adapter_->IsUpdated(item_holder) &&\n                   !updated_item_holder) {\n          updated_item_holder = item_holder;\n        }\n        return false;\n      },\n      from_end);\n  return {no_dirty_item_holder, updated_item_holder, binding_item_holder,\n          finishing_binding_item_holder};\n}\n\nvoid ListAnchorManager::InitScrollToPositionParam(ItemHolder* item_holder,\n                                                  int index, float offset,\n                                                  int align, bool smooth) {\n  scrolling_info_.scrolling_target_ = index;\n  scrolling_info_.scrolling_offset_ = offset;\n  scrolling_info_.scrolling_align_ = (ScrollingInfoAlignment)align;\n  scrolling_info_.scrolling_smooth_ = smooth;\n  scrolling_info_.item_holder_ = item_holder;\n}\n\nfloat ListAnchorManager::CalculateTargetScrollingOffset(\n    ItemHolder* item_holder) {\n  return scrolling_info_.CalcScrollingOffset(\n      list_orientation_helper_->GetMeasurement(),\n      list_layout_manager_->content_size(),\n      list_orientation_helper_->GetStart(item_holder),\n      list_orientation_helper_->GetDecoratedMeasurement(item_holder));\n}\n\nvoid ListAnchorManager::AdjustAnchorAlignment(AnchorInfo& anchor_info) {\n  switch (anchor_visibility_) {\n    case AnchorVisibility::kAnchorVisibilityNoAdjustment:\n      if (anchor_align_to_bottom_) {\n        anchor_info.start_offset_ = list_orientation_helper_->GetDecoratedStart(\n            anchor_info.item_holder_);\n        anchor_info.start_alignment_delta_ =\n            list_orientation_helper_->GetDecoratedEnd(\n                anchor_info.item_holder_) -\n            list_layout_manager_->content_offset();\n      } else {\n        anchor_info.start_offset_ = list_orientation_helper_->GetDecoratedStart(\n            anchor_info.item_holder_);\n        anchor_info.start_alignment_delta_ =\n            list_orientation_helper_->GetStart(anchor_info.item_holder_) -\n            list_layout_manager_->content_offset();\n      }\n      break;\n    case AnchorVisibility::kAnchorVisibilityHide:\n      anchor_info.start_offset_ =\n          list_orientation_helper_->GetDecoratedStart(anchor_info.item_holder_);\n      anchor_info.start_alignment_delta_ =\n          -list_orientation_helper_->GetDecoratedMeasurement(\n              anchor_info.item_holder_);\n      break;\n    case AnchorVisibility::kAnchorVisibilityShow:\n      anchor_info.start_offset_ = 0;\n      anchor_info.start_alignment_delta_ = 0;\n    default:\n      break;\n  }\n}\n\nvoid ListAnchorManager::UpdateDiffAnchorReference() {\n  ClearDiffReference();\n  const ItemHolderSet& on_screen_children =\n      list_children_helper_->on_screen_children();\n  ItemHolder* first_visible_item_holder = list_children_helper_->GetFirstChild(\n      on_screen_children, [this](const ItemHolder* item_holder) {\n        return !list_adapter_->IsRemoved(item_holder) &&\n               list_layout_manager_->IsItemHolderNotAtStickyPosition(\n                   item_holder);\n      });\n  ItemHolder* last_visible_item_holder = list_children_helper_->GetLastChild(\n      on_screen_children, [this](const ItemHolder* item_holder) {\n        return !list_adapter_->IsRemoved(item_holder) &&\n               list_layout_manager_->IsItemHolderNotAtStickyPosition(\n                   item_holder);\n      });\n  if (!first_visible_item_holder || !last_visible_item_holder ||\n      !list_children_helper_) {\n    return;\n  }\n  // TODO(fangzhou) provide more options here\n  list_children_helper_->ForEachChild([this, first_visible_item_holder,\n                                       last_visible_item_holder](\n                                          ItemHolder* item_holder) {\n    if (!list_adapter_->IsDirty(item_holder)) {\n      if (item_holder->index() < first_visible_item_holder->index() &&\n          (!last_valid_item_holder_up_screen_ ||\n           item_holder->index() > last_valid_item_holder_up_screen_->index())) {\n        last_valid_item_holder_up_screen_ = item_holder;\n      }\n      if (item_holder->index() > last_visible_item_holder->index() &&\n          (!first_valid_item_holder_below_screen_ ||\n           item_holder->index() <\n               first_valid_item_holder_below_screen_->index())) {\n        first_valid_item_holder_below_screen_ = item_holder;\n      }\n    }\n    return false;\n  });\n}\n\nbool ListAnchorManager::IsValidInitialScrollIndex() const {\n  return initial_scroll_index_ >= 0 &&\n         initial_scroll_index_ < list_adapter_->GetDataCount();\n}\n\nvoid ListAnchorManager::MarkScrolledInitialScrollIndex() {\n  if (IsValidInitialScrollIndex()) {\n    initial_scroll_index_ = -1;\n  }\n}\n\n// Calculate scroll offset by alignment\nfloat ListAnchorManager::ScrollingInfo::CalcScrollingOffset(\n    float list_size, float list_content_size, float item_offset,\n    float item_size) {\n  float estimated_offset = 0;\n  if (scrolling_align_ == ScrollingInfoAlignment::kTop) {\n    estimated_offset = item_offset - scrolling_offset_;\n  } else if (scrolling_align_ == ScrollingInfoAlignment::kMiddle) {\n    estimated_offset =\n        item_offset - (list_size - item_size) / 2.0 - scrolling_offset_;\n  } else if (scrolling_align_ == ScrollingInfoAlignment::kBottom) {\n    estimated_offset = item_offset - list_size + item_size - scrolling_offset_;\n  }\n  // Clamp estimated_offset to [0.f, max content offset].\n  if (base::FloatsLargerOrEqual(0.f, estimated_offset) ||\n      base::FloatsLargerOrEqual(list_size, list_content_size)) {\n    estimated_offset = 0.f;\n  } else if (estimated_offset > list_content_size - list_size) {\n    estimated_offset = list_content_size - list_size;\n  }\n  return estimated_offset;\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_anchor_manager.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LIST_ANCHOR_MANAGER_H_\n#define CORE_LIST_DECOUPLED_LIST_ANCHOR_MANAGER_H_\n\n#include \"base/include/vector.h\"\n#include \"core/list/decoupled_list_adapter.h\"\n#include \"core/list/decoupled_list_children_helper.h\"\n#include \"core/list/decoupled_list_orientation_helper.h\"\n#include \"core/list/decoupled_list_types.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListLayoutManager;\nclass ListContainerImpl;\n\nclass ListAnchorManager {\n public:\n  class AnchorInfo {\n   public:\n    void AssignCoordinateFromPadding(\n        const ListOrientationHelper* list_orientation_helper) {\n      start_offset_ = list_orientation_helper\n                          ? list_orientation_helper->GetStartAfterPadding()\n                          : 0.f;\n    }\n\n    void Reset() {\n      valid_ = false;\n      index_ = kInvalidIndex;\n      start_offset_ = 0.f;\n      start_alignment_delta_ = 0.f;\n      item_holder_ = nullptr;\n    }\n\n    bool valid_{false};\n    int index_{kInvalidIndex};\n    // The top of anchor item_holder when this anchor_info first generated.\n    float start_offset_{0.f};\n    // The delta between anchor item_holder's top and content_offset when this\n    // anchor_info first generated.\n    float start_alignment_delta_{0.f};\n    bool is_removed_child_ref_{false};\n    bool align_start_{true};\n    ItemHolder* item_holder_{nullptr};\n  };\n\n  class ScrollingInfo {\n   public:\n    float CalcScrollingOffset(float list_size, float list_content_size,\n                              float item_offset, float item_size);\n    void Reset() {\n      scrolling_target_ = kInvalidIndex;\n      scrolling_align_ = ScrollingInfoAlignment::kTop;\n      scrolling_offset_ = 0;\n      scrolling_smooth_ = false;\n      item_holder_ = nullptr;\n    };\n    void InvalidatePosition() { scrolling_target_ = kInvalidIndex; }\n    bool IsValidNonSmoothScrollTarget() const {\n      return scrolling_target_ != kInvalidIndex && !scrolling_smooth_;\n    }\n\n    int scrolling_target_{kInvalidIndex};\n    ScrollingInfoAlignment scrolling_align_{ScrollingInfoAlignment::kTop};\n    float scrolling_offset_{0.f};\n    bool scrolling_smooth_{false};\n    ItemHolder* item_holder_{nullptr};\n  };\n\n public:\n  ListAnchorManager(ListLayoutManager* list_layout_manager);\n\n  void SetListOrientationHelper(\n      ListOrientationHelper* list_orientation_helper) {\n    list_orientation_helper_ = list_orientation_helper;\n  }\n  void ClearDiffReference() {\n    first_valid_item_holder_below_screen_ = nullptr;\n    last_valid_item_holder_up_screen_ = nullptr;\n  }\n  void SetAnchorAlignToBottom(bool anchor_align_to_bottom) {\n    anchor_align_to_bottom_ = anchor_align_to_bottom;\n  }\n  void SetAnchorVisibility(AnchorVisibility anchor_visibility) {\n    anchor_visibility_ = anchor_visibility;\n  }\n  void SetAnchorPriorityFromBegin(bool anchor_priority_from_begin) {\n    anchor_priority_from_begin_ = anchor_priority_from_begin;\n  }\n  void SetListContainer(ListContainerImpl* list_container) {\n    list_container_ = list_container;\n  }\n  void SetListAdapter(ListAdapter* list_adapter) {\n    list_adapter_ = list_adapter;\n  }\n  void SetListChildrenHelper(ListChildrenHelper* children_helper) {\n    list_children_helper_ = children_helper;\n  }\n  void SetInitialScrollIndex(int initial_scroll_index) {\n    initial_scroll_index_ = initial_scroll_index;\n  }\n  void MarkScrolledInitialScrollIndex();\n  void RetrieveAnchorInfoBeforeLayout(AnchorInfo& anchor_info,\n                                      int finishing_binding_index);\n  void AdjustAnchorInfoAfterLayout(AnchorInfo& anchor_info);\n  void UpdateDiffAnchorReference();\n  bool IsValidInitialScrollIndex() const;\n  int initial_scroll_index() const { return initial_scroll_index_; }\n  void InitScrollToPositionParam(ItemHolder* item_holder, int index,\n                                 float offset, int align, bool smooth);\n  float CalculateTargetScrollingOffset(ItemHolder* item_holder);\n  void InvalidateScrollInfoPosition() { scrolling_info_.InvalidatePosition(); }\n  void ResetScrollInfo() { scrolling_info_.Reset(); }\n  bool IsValidSmoothScrollInfo() {\n    return scrolling_info_.scrolling_target_ != kInvalidIndex &&\n           scrolling_info_.scrolling_smooth_;\n  }\n  void AdjustContentOffsetWithAnchor(AnchorInfo& anchor_info,\n                                     float content_offset);\n  const ScrollingInfo& scrolling_info() const { return scrolling_info_; }\n\n private:\n  void FindAnchor(AnchorInfo& anchor_info, bool from_begin,\n                  int finishing_binding_index);\n  void FindAnchorFromRef(AnchorInfo& anchor_info);\n  bool FindAnchorFromPendingData(AnchorInfo& anchor_info);\n  void FindAnchorFromChildren(AnchorInfo& anchor_info,\n                              int finishing_binding_index);\n  base::InlineVector<ItemHolder*, kAnchorCandidateSize> GetAnchorCandidates(\n      int finishing_binding_index, bool from_end);\n  void UpdateAnchorWithItemHolder(AnchorInfo& anchor_info,\n                                  ItemHolder* item_holder);\n  void AdjustAnchorAlignment(AnchorInfo& anchor_info);\n\n private:\n  bool anchor_align_to_bottom_{false};\n  bool anchor_priority_from_begin_{true};\n  AnchorVisibility anchor_visibility_{\n      AnchorVisibility::kAnchorVisibilityNoAdjustment};\n  int initial_scroll_index_{-1};\n  ItemHolder* first_valid_item_holder_below_screen_{nullptr};\n  ItemHolder* last_valid_item_holder_up_screen_{nullptr};\n  ListContainerImpl* list_container_{nullptr};\n  ListAdapter* list_adapter_{nullptr};\n  ListLayoutManager* list_layout_manager_{nullptr};\n  ListChildrenHelper* list_children_helper_{nullptr};\n  ListOrientationHelper* list_orientation_helper_{nullptr};\n  ScrollingInfo scrolling_info_;\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LIST_ANCHOR_MANAGER_H_\n"
  },
  {
    "path": "core/list/decoupled_list_anchor_manager_unittest.cc",
    "content": "\n// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/list/decoupled_list_anchor_manager.h\"\n\n#include \"base/include/string/string_utils.h\"\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/value_wrapper/value_impl_lepus.h\"\n#include \"testing/fiber_data_source.h\"\n#include \"testing/mock_list_element.h\"\n#include \"testing/mock_list_item_element.h\"\n#include \"testing/radon_data_source.h\"\n#include \"testing/utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace list {\n\nusing ::testing::_;\nusing ::testing::Return;\n\nclass ListAnchorManagerTest : public ::testing::Test {\n public:\n  ListAnchorManagerTest() = default;\n  ~ListAnchorManagerTest() override = default;\n\n  std::unique_ptr<MockListElement> mock_list_element_{nullptr};\n  std::unique_ptr<ListContainerImpl> list_container_impl_{nullptr};\n  std::shared_ptr<pub::PubValueFactoryDefault> value_factory_{nullptr};\n  ListAdapter* list_adapter_{nullptr};\n  ListLayoutManager* list_layout_manager_{nullptr};\n  ListAnchorManager* list_anchor_manager_{nullptr};\n\n  void SetUp() override {\n    value_factory_ = std::make_shared<pub::PubValueFactoryDefault>();\n    mock_list_element_ = std::make_unique<MockListElement>();\n    list_container_impl_ = std::make_unique<ListContainerImpl>(\n        mock_list_element_.get(), value_factory_);\n    list_adapter_ = list_container_impl_->list_adapter();\n    list_layout_manager_ = list_container_impl_->list_layout_manager();\n    list_anchor_manager_ = list_layout_manager_->list_anchor_manager_.get();\n  }\n\n  ListOrientationHelper* GetOrientationHelper() {\n    return list_layout_manager_->list_orientation_helper_.get();\n  }\n\n  bool IsFullSpan(int index) {\n    static int full_span_flag = 5;\n    return !(index % full_span_flag);\n  }\n\n  void InitLayoutAttrs(std::string list_type, int span_count,\n                       std::string scroll_orientation, float main_axis_gap,\n                       float cross_axis_gap, float list_main_size,\n                       float list_cross_size) {\n    {\n      LIST_CONTAINER_DEFINE_PROP_VALUE(ListType, String, list_type);\n      list_container_impl_->ResolveAttribute(*key, *value);\n      list_layout_manager_ = list_container_impl_->list_layout_manager();\n    }\n    {\n      LIST_CONTAINER_DEFINE_PROP_VALUE(SpanCount, Number, span_count);\n      list_container_impl_->ResolveAttribute(*key, *value);\n    }\n    {\n      LIST_CONTAINER_DEFINE_PROP_VALUE(ScrollOrientation, String,\n                                       scroll_orientation);\n      list_container_impl_->ResolveAttribute(*key, *value);\n    }\n    list_container_impl_->ResolveListAxisGap(\n        tasm::CSSPropertyID::kPropertyIDListMainAxisGap, main_axis_gap);\n    list_container_impl_->ResolveListAxisGap(\n        tasm::CSSPropertyID::kPropertyIDListCrossAxisGap, cross_axis_gap);\n    if (scroll_orientation == kPropValueScrollOrientationVertical) {\n      mock_list_element_->height_ = list_main_size;\n      mock_list_element_->width_ = list_cross_size;\n    } else {\n      mock_list_element_->height_ = list_cross_size;\n      mock_list_element_->width_ = list_main_size;\n    }\n    list_container_impl_->PropsUpdateFinish();\n  }\n\n  void InitFiberDataSource(int data_count, std::vector<int> estimated_sizes,\n                           bool mock_full_span) {\n    testing::InsertAction insert_action;\n    for (int i = 0; i < data_count; ++i) {\n      insert_action.insert_ops_.push_back(\n          {i, base::FormatString(\"list-item-%d\", i), estimated_sizes[i],\n           mock_full_span && IsFullSpan(i), false, false, true});\n    }\n    testing::FiberDataSource fiber_data_source{\n        .insert_action_ = insert_action,\n    };\n    LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n        FiberUpdateListInfo, lepus::Value(fiber_data_source.Resolve()));\n    list_container_impl_->ResolveAttribute(*key, value);\n    list_container_impl_->PropsUpdateFinish();\n  }\n\n  void InitRadonDataSource(int data_count) {\n    testing::RadonDataSource radon_data_source;\n    for (int i = 0; i < data_count; ++i) {\n      radon_data_source.item_keys_.emplace_back(\n          base::FormatString(\"list-item-%d\", i));\n      radon_data_source.insertion_.emplace_back(i);\n      radon_data_source.estimated_main_axis_size_pxs_.emplace_back(100);\n    }\n    LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n        RadonListPlatformInfo,\n        lepus::Value(radon_data_source.GenerateDataSource()));\n    list_container_impl_->ResolveAttribute(*key, value);\n    list_container_impl_->PropsUpdateFinish();\n  }\n\n  void OnLayoutChildren() {\n    static int list_item_impl_id = mock_list_element_->GetImplId() + 1;\n    auto pipeline = std::make_shared<tasm::PipelineOptions>();\n    EXPECT_CALL(*mock_list_element_, ComponentAtIndex(_, _, _))\n        .WillRepeatedly([&](uint32_t index, int64_t operationId,\n                            bool enable_reuse_notification) {\n          ItemHolder* item_holder =\n              list_container_impl_->GetItemHolderForIndex(index);\n          const auto& item_key = item_holder->item_key();\n          // mock pipeline when list item finish rendering.\n          auto pipeline = std::make_shared<tasm::PipelineOptions>();\n          pipeline->operation_id = operationId;\n          pipeline->has_layout = true;\n          mock_list_element_->AddListItemElement(\n              item_key,\n              std::make_unique<MockListItemElement>(list_item_impl_id++));\n          // set list item's real height.\n          MockListItemElement* mock_list_item_element =\n              mock_list_element_->GetListItemElement(item_key);\n          mock_list_item_element->height_ = 100.f;\n          // invoke OnFinishBindItemHolder.\n          list_adapter_->OnFinishBindItemHolder(mock_list_item_element,\n                                                pipeline);\n          return true;\n        });\n    list_container_impl_->OnLayoutChildren(pipeline);\n  }\n\n  void CheckAnchorInfo(const ListAnchorManager::AnchorInfo& target,\n                       const ListAnchorManager::AnchorInfo& basic) {\n    EXPECT_TRUE(target.valid_ == basic.valid_ &&\n                target.index_ == basic.index_ &&\n                base::FloatsEqual(target.start_offset_, basic.start_offset_) &&\n                base::FloatsEqual(target.start_alignment_delta_,\n                                  basic.start_alignment_delta_) &&\n                target.is_removed_child_ref_ == basic.is_removed_child_ref_ &&\n                target.align_start_ == basic.align_start_ &&\n                target.item_holder_ == basic.item_holder_);\n  }\n};\n\nTEST_F(ListAnchorManagerTest, IsValidInitialScrollIndex) {\n  int data_count = 100;\n  InitFiberDataSource(data_count, std::vector<int>(data_count, 50), false);\n  list_anchor_manager_->SetInitialScrollIndex(1);\n  EXPECT_TRUE(list_anchor_manager_->IsValidInitialScrollIndex());\n  list_anchor_manager_->SetInitialScrollIndex(101);\n  EXPECT_FALSE(list_anchor_manager_->IsValidInitialScrollIndex());\n}\n\nTEST_F(ListAnchorManagerTest, FindAnchor1) {\n  int data_count = 100;\n  InitFiberDataSource(data_count, std::vector<int>(data_count, 50), false);\n  float padding_top = 10.f;\n  mock_list_element_->paddings_ = {0.f, padding_top, 0.f, 0.f};\n  ListAnchorManager::AnchorInfo anchor_info;\n  list_anchor_manager_->FindAnchor(anchor_info, false, kInvalidIndex);\n  CheckAnchorInfo(\n      anchor_info,\n      {.valid_ = true,\n       .index_ = 0,\n       .start_offset_ = padding_top,\n       .start_alignment_delta_ = padding_top,\n       .item_holder_ = list_container_impl_->GetItemHolderForIndex(0)});\n}\n\nTEST_F(ListAnchorManagerTest, FindAnchor2) {\n  int data_count = 100;\n  InitFiberDataSource(data_count, std::vector<int>(data_count, 50), false);\n  // Clear all data\n  testing::RemoveAction remove_action;\n  for (int i = 0; i < data_count; ++i) {\n    remove_action.remove_ops_.emplace_back(i);\n  }\n  testing::FiberDataSource fiber_data_source{\n      .remove_action_ = remove_action,\n  };\n  LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n      FiberUpdateListInfo, lepus::Value(fiber_data_source.Resolve()));\n  list_container_impl_->ResolveAttribute(*key, value);\n  list_container_impl_->PropsUpdateFinish();\n\n  ListAnchorManager::AnchorInfo anchor_info;\n  list_anchor_manager_->FindAnchor(anchor_info, false, kInvalidIndex);\n  CheckAnchorInfo(\n      anchor_info,\n      {.valid_ = false, .index_ = kInvalidIndex, .item_holder_ = nullptr});\n}\n\nTEST_F(ListAnchorManagerTest, FindAnchor3) {\n  int data_count = 100;\n  InitLayoutAttrs(\"single\", 1, \"vertical\", 0.f, 0.f, 2000.f, 500.f);\n  InitRadonDataSource(data_count);\n  OnLayoutChildren();\n\n  // update from: [0, ... 9]\n  // update to: [0, ... 9]\n  int first_valid_index = 10;\n  ItemHolder* first_valid_item_holder =\n      list_container_impl_->GetItemHolderForIndex(first_valid_index);\n  testing::RadonDataSource radon_data_source;\n  for (int i = 0; i < data_count; ++i) {\n    radon_data_source.item_keys_.emplace_back(\n        base::FormatString(\"list-item-%d\", i));\n    radon_data_source.estimated_main_axis_size_pxs_.emplace_back(100);\n    if (i < first_valid_index) {\n      radon_data_source.update_from_.emplace_back(i);\n      radon_data_source.update_to_.emplace_back(i);\n    }\n  }\n  LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n      RadonListPlatformInfo,\n      lepus::Value(radon_data_source.GenerateDataSource()));\n  list_container_impl_->ResolveAttribute(*key, value);\n  list_container_impl_->PropsUpdateFinish();\n\n  ListAnchorManager::AnchorInfo anchor_info;\n  list_anchor_manager_->FindAnchor(anchor_info, false, kInvalidIndex);\n  CheckAnchorInfo(\n      anchor_info,\n      {.valid_ = true,\n       .index_ = first_valid_index,\n       .start_offset_ =\n           GetOrientationHelper()->GetDecoratedStart(first_valid_item_holder),\n       .start_alignment_delta_ =\n           GetOrientationHelper()->GetStart(first_valid_item_holder) -\n           list_layout_manager_->content_offset(),\n       .item_holder_ = first_valid_item_holder});\n}\n\nTEST_F(ListAnchorManagerTest, GetAnchorCandidates) {\n  int data_count = 100;\n  InitLayoutAttrs(\"single\", 1, \"vertical\", 0.f, 0.f, 2000.f, 500.f);\n  InitRadonDataSource(data_count);\n  OnLayoutChildren();\n\n  // case1. No update\n  {\n    testing::RadonDataSource radon_data_source;\n    for (int i = 0; i < data_count; ++i) {\n      radon_data_source.item_keys_.emplace_back(\n          base::FormatString(\"list-item-%d\", i));\n      radon_data_source.estimated_main_axis_size_pxs_.emplace_back(100);\n    }\n    LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n        RadonListPlatformInfo,\n        lepus::Value(radon_data_source.GenerateDataSource()));\n    list_container_impl_->ResolveAttribute(*key, value);\n    list_container_impl_->PropsUpdateFinish();\n\n    const auto& anchor_candidates =\n        list_anchor_manager_->GetAnchorCandidates(kInvalidIndex, false);\n    EXPECT_EQ(anchor_candidates.size(), kAnchorCandidateSize);\n    EXPECT_TRUE(anchor_candidates[0] != nullptr);\n    EXPECT_TRUE(anchor_candidates[1] == nullptr);\n    EXPECT_TRUE(anchor_candidates[2] == nullptr);\n    EXPECT_TRUE(anchor_candidates[3] == nullptr);\n  }\n\n  // case2. Update\n  {\n    testing::RadonDataSource radon_data_source;\n    for (int i = 0; i < data_count; ++i) {\n      radon_data_source.item_keys_.emplace_back(\n          base::FormatString(\"list-item-%d\", i));\n      radon_data_source.estimated_main_axis_size_pxs_.emplace_back(100);\n    }\n    // remove: [0 - 9]\n    // insert: [0 - 9]\n    // update: [10 - 99]\n    int first_updated_index = 10;\n    ItemHolder* first_updated_item_holder =\n        list_container_impl_->GetItemHolderForIndex(first_updated_index);\n    for (int i = 0; i < data_count; ++i) {\n      if (i < first_updated_index) {\n        radon_data_source.removal_.emplace_back(i);\n        radon_data_source.insertion_.emplace_back(i);\n        radon_data_source.item_keys_.emplace_back(\n            base::FormatString(\"new-list-item-%d\", i));\n      } else {\n        radon_data_source.update_from_.emplace_back(i);\n        radon_data_source.update_to_.emplace_back(i);\n        radon_data_source.item_keys_.emplace_back(\n            base::FormatString(\"list-item-%d\", i));\n        radon_data_source.estimated_main_axis_size_pxs_.emplace_back(100);\n      }\n    }\n    LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n        RadonListPlatformInfo,\n        lepus::Value(radon_data_source.GenerateDataSource()));\n    list_container_impl_->ResolveAttribute(*key, value);\n    list_container_impl_->PropsUpdateFinish();\n\n    const auto& anchor_candidates =\n        list_anchor_manager_->GetAnchorCandidates(kInvalidIndex, false);\n    EXPECT_EQ(anchor_candidates.size(), kAnchorCandidateSize);\n    EXPECT_TRUE(anchor_candidates[0] == nullptr);\n    EXPECT_TRUE(anchor_candidates[1] == first_updated_item_holder);\n    EXPECT_TRUE(anchor_candidates[2] == nullptr);\n    EXPECT_TRUE(anchor_candidates[3] == nullptr);\n  }\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_children_helper.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_list_children_helper.h\"\n\n#include <string>\n\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/list/decoupled_list_orientation_helper.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\n// Insert ItemHolder to children_ set. It will be invoked by\n// ListAdapter::UpdateItemHolderToLatest() when list's data source has new inset\n// child.\nvoid ListChildrenHelper::AddChild(const ItemHolderSet& children,\n                                  ItemHolder* item_holder) {\n  if (item_holder) {\n    const_cast<ItemHolderSet&>(children).insert(item_holder);\n  }\n}\n\nvoid ListChildrenHelper::AddChild(const WeakItemHolderSet& children,\n                                  ItemHolder* item_holder) {\n  if (item_holder) {\n    const_cast<WeakItemHolderSet&>(children).insert(\n        item_holder->WeakFromThis());\n  }\n}\n\n// Insert ItemHolder to attached_children_ set. It will be invoked by\n// ListContainer::OnComponentFinished() when the ItemHolder is bound with\n// element.\nvoid ListChildrenHelper::AttachChild(ItemHolder* item_holder,\n                                     ItemElementDelegate* item_delegate) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_CHILDREN_ATTACH_CHILD, \"index\",\n              std::to_string(item_holder ? item_holder->index() : -1),\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  if (!item_holder) {\n    return;\n  }\n  attached_children_.insert(item_holder);\n  if (item_delegate) {\n    attached_delegate_item_holder_map_[item_delegate] = item_holder;\n  }\n}\n\n// Delete ItemHolder from attached_children_ set. It will be invoked by\n// ListContainer::RecycleChild() when the ItemHolder is recycled.\nvoid ListChildrenHelper::DetachChild(ItemHolder* item_holder,\n                                     ItemElementDelegate* item_delegate) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_CHILDREN_DETACH_CHILD, \"index\",\n              std::to_string(item_holder ? item_holder->index() : -1),\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  if (!item_holder) {\n    return;\n  }\n  if (!attached_children_.erase(item_holder)) {\n    DLIST_LOGE(\"Fail to erase item holder at pos = \" << item_holder->index());\n  }\n  if (item_delegate) {\n    attached_delegate_item_holder_map_.erase(item_delegate);\n  }\n}\n\n// Traverse child nodes. When reverse == true, traverse in backward order\nvoid ListChildrenHelper::ForEachChild(\n    const ItemHolderSet& children, const std::function<bool(ItemHolder*)>& func,\n    bool reverse) const {\n  if (!reverse) {\n    for (auto* item_holder : children) {\n      if (item_holder && func(item_holder)) {\n        return;\n      }\n    }\n  } else {\n    for (auto it = children.rbegin(); it != children.rend(); ++it) {\n      if (*it && func(*it)) {\n        return;\n      }\n    }\n  }\n}\n\n// Traverse child nodes. When reverse == true, traverse in backward order\nvoid ListChildrenHelper::ForEachChild(\n    const WeakItemHolderSet& children,\n    const std::function<bool(ItemHolder*)>& func, bool reverse) const {\n  if (!reverse) {\n    for (const auto& item_holder : children) {\n      if (item_holder && func(item_holder.get())) {\n        return;\n      }\n    }\n  } else {\n    for (auto it = children.rbegin(); it != children.rend(); ++it) {\n      if (const auto& item_holder = *it;\n          item_holder && func(item_holder.get())) {\n        return;\n      }\n    }\n  }\n}\n\n// Traverse child nodes. When reverse == true, traverse in backward order\nvoid ListChildrenHelper::ForEachChild(\n    const std::function<bool(ItemHolder*)>& func, bool reverse) const {\n  ForEachChild(children_, func, reverse);\n}\n\nItemHolder* ListChildrenHelper::GetFirstChild(\n    const std::function<bool(const ItemHolder*)>& func) const {\n  return GetFirstChild(children_, func);\n}\n\nItemHolder* ListChildrenHelper::GetLastChild(\n    const std::function<bool(const ItemHolder*)>& func) const {\n  return GetLastChild(children_, func);\n}\n\n//(TODO)fangzhou.fz: After implementing preload, this first child is incorrect.\nItemHolder* ListChildrenHelper::GetFirstChild(\n    const ItemHolderSet& children,\n    const std::function<bool(const ItemHolder*)>& func) const {\n  ItemHolder* res = nullptr;\n  ForEachChild(children, [&res, &func](ItemHolder* item_holder) {\n    if (item_holder && func(item_holder)) {\n      res = item_holder;\n      return true;\n    }\n    return false;\n  });\n  return res;\n}\n\nItemHolder* ListChildrenHelper::GetLastChild(\n    const ItemHolderSet& children,\n    const std::function<bool(const ItemHolder*)>& func) const {\n  ItemHolder* res = nullptr;\n  ForEachChild(\n      children,\n      [&res, &func](ItemHolder* item_holder) {\n        if (item_holder && func(item_holder)) {\n          res = item_holder;\n          return true;\n        }\n        return false;\n      },\n      true);\n  return res;\n}\n\nvoid ListChildrenHelper::InitStickyItemHolderSet(int thread_mode) {\n  in_sticky_top_children_.Clear();\n  in_sticky_bottom_children_.Clear();\n  if (use_default_sticky_buffer_count_) {\n    int capacity = kStickyItemSetCapacityForSyncMode;\n    if (thread_mode > base::ThreadStrategyForRendering::ALL_ON_UI &&\n        thread_mode <= base::ThreadStrategyForRendering::MULTI_THREADS) {\n      capacity = kStickyItemSetCapacityForASyncMode;\n    }\n    in_sticky_top_children_.SetCapacity(capacity);\n    in_sticky_bottom_children_.SetCapacity(capacity);\n  }\n}\n\nbool ListChildrenHelper::AddToStickyItemHolderSet(ItemHolder* item_holder) {\n  if (item_holder->sticky_top()) {\n    return in_sticky_top_children_.AddItemHolder(item_holder);\n  } else if (item_holder->sticky_bottom()) {\n    return in_sticky_bottom_children_.AddItemHolder(item_holder);\n  }\n  return false;\n}\n\nbool ListChildrenHelper::InStickyItemHolderSet(\n    const ItemHolder* item_holder) const {\n  if (item_holder->sticky_top()) {\n    return in_sticky_top_children_.Contain(item_holder);\n  } else if (item_holder->sticky_bottom()) {\n    return in_sticky_bottom_children_.Contain(item_holder);\n  }\n  return false;\n}\n\nvoid ListChildrenHelper::UpdateOnScreenChildren(\n    ListOrientationHelper* orientation_helper, float content_offset) {\n  if (!orientation_helper) {\n    return;\n  }\n  on_screen_children_.clear();\n  ForEachChild(\n      [this, content_offset, &orientation_helper](ItemHolder* item_holder) {\n        if (item_holder &&\n            item_holder->VisibleInList(orientation_helper, content_offset)) {\n          on_screen_children_.insert(item_holder);\n        }\n        return false;\n      });\n  // This trace event is used to output the debug info.\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_CHILDREN_UPDATE_ON_SCREEN_CHILD,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n}\n\nvoid ListChildrenHelper::UpdateInStickyChildren(\n    ListOrientationHelper* orientation_helper, float content_offset,\n    float content_size, float sticky_offset) {\n  if (!orientation_helper || !orientation_helper->IsVertical()) {\n    // Not support sticky in horizontal direction.\n    return;\n  }\n  in_sticky_children_.clear();\n  ForEachChild([this, orientation_helper, content_offset, content_size,\n                sticky_offset](ItemHolder* item_holder) {\n    if (item_holder &&\n        item_holder->IsAtStickyPosition(\n            content_offset, orientation_helper->GetMeasurement(), content_size,\n            sticky_offset, orientation_helper->GetStart(item_holder),\n            orientation_helper->GetEnd(item_holder))) {\n      in_sticky_children_.insert(item_holder);\n    }\n    return false;\n  });\n}\n\nvoid ListChildrenHelper::EraseFromLastBindingChildren(ItemHolder* item_holder) {\n  if (item_holder) {\n    last_binding_children_.erase(item_holder);\n  }\n}\n\nvoid ListChildrenHelper::HandleLayoutOrScrollResult(\n    const std::function<bool(ItemHolder*)>& insert_handler,\n    const std::function<bool(ItemHolder*)>& recycle_handler,\n    const std::function<bool(ItemHolder*)>& update_handler) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY,\n              LIST_CHILDREN_HANDLE_LAYOUT_OR_SCROLL_RESULT);\n  ItemHolderSet new_binding_children;\n  ItemHolderSet new_added_children;\n  // Merge all need binding children from on_screen_children_ /\n  // in_preload_children_ / in_sticky_children_.\n  ForEachChild(attached_children_, [this, &new_binding_children](\n                                       ItemHolder* item_holder) {\n    bool in_sticky = recycle_item_holder_\n                         ? (in_sticky_top_children_.Contain(item_holder) ||\n                            in_sticky_bottom_children_.Contain(item_holder))\n                         : (in_sticky_children_.find(item_holder) !=\n                            in_sticky_children_.end());\n    if (!item_holder->recyclable() || in_sticky ||\n        on_screen_children_.find(item_holder) != on_screen_children_.end() ||\n        in_preload_children_.find(item_holder) != in_preload_children_.end()) {\n      new_binding_children.insert(item_holder);\n    }\n    return false;\n  });\n  // Update new added ItemHolders.\n  ForEachChild(new_binding_children,\n               [this, &new_added_children](ItemHolder* item_holder) {\n                 if (last_binding_children_.find(item_holder) ==\n                     last_binding_children_.end()) {\n                   new_added_children.insert(item_holder);\n                 }\n                 return false;\n               });\n  // Update new removed ItemHolders.\n  ForEachChild(new_binding_children, [this](ItemHolder* item_holder) {\n    last_binding_children_.erase(item_holder);\n    return false;\n  });\n  // Handle insert.\n  ForEachChild(new_added_children, insert_handler);\n  // Handle recycle.\n  ForEachChild(last_binding_children_, recycle_handler);\n  // Update layout info to platform.\n  last_binding_children_.clear();\n  last_binding_children_.insert(new_binding_children.begin(),\n                                new_binding_children.end());\n  ForEachChild(last_binding_children_, update_handler);\n}\n\nItemHolder* ListChildrenHelper::GetFirstChildFrom(\n    const ItemHolderSet& children, ItemHolder* start_child,\n    const std::function<bool(const ItemHolder*)>& condition_func,\n    bool reverse /* = false */) const {\n  if (auto start_it = children.find(start_child); start_it != children.end()) {\n    if (!reverse) {\n      auto target_it =\n          std::find_if(start_it, children.end(),\n                       [&condition_func](const ItemHolder* item_holder) {\n                         return condition_func(item_holder);\n                       });\n      return target_it != children.end() ? *target_it : nullptr;\n    } else {\n      auto reverse_start_it = std::make_reverse_iterator(std::next(start_it));\n      auto reverse_target_it =\n          std::find_if(reverse_start_it, children.rend(),\n                       [&condition_func](const ItemHolder* item_holder) {\n                         return condition_func(item_holder);\n                       });\n      return reverse_target_it != children.rend() ? *reverse_target_it\n                                                  : nullptr;\n    }\n  } else {\n    // start_child is not in children or children is empty.\n    return nullptr;\n  }\n}\n\n#if ENABLE_TRACE_PERFETTO\nvoid ListChildrenHelper::UpdateTraceDebugInfo(TraceEvent* event) {\n  // attached children\n  auto* attached_children = event->add_debug_annotations();\n  attached_children->set_name(\"attached_children\");\n  std::string attached_children_str = \"\";\n  int index = 0;\n  for (const auto* item_holder : attached_children_) {\n    attached_children_str += \"(\" + std::to_string(index++) + \") \";\n    if (item_holder) {\n      attached_children_str += \"[\" + std::to_string(item_holder->index()) +\n                               \", \" + item_holder->item_key() + \"]\\n\";\n    } else {\n      attached_children_str += \"[-1, nullptr]\\n\";\n    }\n  }\n  attached_children->set_string_value(attached_children_str);\n\n  // on screen children\n  auto* on_screen_children = event->add_debug_annotations();\n  on_screen_children->set_name(\"on_screen_children\");\n  std::string on_screen_children_str = \"\";\n  index = 0;\n  for (const auto* item_holder : on_screen_children_) {\n    on_screen_children_str += \"(\" + std::to_string(index++) + \") \";\n    if (item_holder) {\n      on_screen_children_str += \"[\" + std::to_string(item_holder->index()) +\n                                \", \" + item_holder->item_key() + \"]\\n\";\n    } else {\n      on_screen_children_str += \"[-1, nullptr]\\n\";\n    }\n  }\n  on_screen_children->set_string_value(on_screen_children_str);\n\n  // children\n  auto* children = event->add_debug_annotations();\n  children->set_name(\"children\");\n  std::string children_str = \"\";\n  index = 0;\n  for (const auto* item_holder : children_) {\n    children_str += \"(\" + std::to_string(index++) + \") \";\n    if (item_holder) {\n      children_str += \"[\" + std::to_string(item_holder->index()) + \", \" +\n                      item_holder->item_key() + \"]\\n\";\n    } else {\n      children_str += \"[-1, nullptr]\\n\";\n    }\n  }\n  children->set_string_value(children_str);\n}\n#endif\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_children_helper.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LIST_CHILDREN_HELPER_H_\n#define CORE_LIST_DECOUPLED_LIST_CHILDREN_HELPER_H_\n\n#include <functional>\n#include <set>\n#include <unordered_map>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"core/list/decoupled_item_holder.h\"\n\nnamespace lynx {\nnamespace list {\n\nusing ItemHolderSet = std::set<ItemHolder*, ItemHolder::Compare>;\nusing WeakItemHolderSet =\n    std::set<fml::WeakPtr<ItemHolder>, ItemHolder::WeakCompare>;\n\n// Utility class for traversing all child nodes.\nclass ListChildrenHelper {\n public:\n  class StickyItemHolderSet {\n   public:\n    bool AddItemHolder(ItemHolder* item_holder) {\n      if (item_holder &&\n          static_cast<int>(inline_item_holders_.size()) < capacity_ &&\n          inline_item_holders_.find(item_holder) ==\n              inline_item_holders_.end()) {\n        inline_item_holders_.insert(item_holder);\n        count_ += 1;\n      }\n      return count_ < capacity_;\n    }\n\n    void SetCapacity(int capacity) { capacity_ = capacity; }\n\n    void Clear() {\n      count_ = 0;\n      inline_item_holders_.clear();\n    }\n\n    bool Contain(const ItemHolder* item_holder) const {\n      return inline_item_holders_.find(const_cast<ItemHolder*>(item_holder)) !=\n             inline_item_holders_.end();\n    }\n\n   private:\n    int capacity_{1};\n    int count_{0};\n    ItemHolderSet inline_item_holders_;\n  };\n\n#if ENABLE_TRACE_PERFETTO\n  void UpdateTraceDebugInfo(TraceEvent* event);\n#endif\n  void AddChild(const ItemHolderSet& children, ItemHolder* item_holder);\n  void AddChild(const WeakItemHolderSet& children, ItemHolder* item_holder);\n  void AttachChild(ItemHolder* item_holder, ItemElementDelegate* item_delegate);\n  void DetachChild(ItemHolder* item_holder, ItemElementDelegate* item_delegate);\n  void ForEachChild(const std::function<bool(ItemHolder*)>& func,\n                    bool reverse = false) const;\n  void ForEachChild(const ItemHolderSet& children,\n                    const std::function<bool(ItemHolder*)>& func,\n                    bool reverse = false) const;\n  void ForEachChild(const WeakItemHolderSet& children,\n                    const std::function<bool(ItemHolder*)>& func,\n                    bool reverse = false) const;\n  const ItemHolderSet& children() const { return children_; }\n  const ItemHolderSet& attached_children() const { return attached_children_; }\n  const std::unordered_map<ItemElementDelegate*, ItemHolder*>&\n  attached_delegate_item_holder_map() const {\n    return attached_delegate_item_holder_map_;\n  }\n  int GetChildCount() const { return static_cast<int>(children_.size()); }\n  void ClearChildren() { children_.clear(); }\n  void ClearLastBindingChildren() { last_binding_children_.clear(); }\n  void ClearAttachedChildren() {\n    attached_children_.clear();\n    attached_delegate_item_holder_map_.clear();\n  }\n  ItemHolder* GetFirstChild(\n      const ItemHolderSet& children,\n      const std::function<bool(const ItemHolder*)>& func) const;\n  ItemHolder* GetLastChild(\n      const ItemHolderSet& children,\n      const std::function<bool(const ItemHolder*)>& func) const;\n  ItemHolder* GetFirstChild(\n      const std::function<bool(const ItemHolder*)>& func) const;\n  ItemHolder* GetLastChild(\n      const std::function<bool(const ItemHolder*)>& func) const;\n  void UpdateOnScreenChildren(ListOrientationHelper* orientation_helper,\n                              float content_offset);\n  void UpdateInStickyChildren(ListOrientationHelper* orientation_helper,\n                              float content_offset, float content_size,\n                              float sticky_offset);\n  void ClearOnScreenChildren() { on_screen_children_.clear(); }\n  void ClearInPreloadChildren() { in_preload_children_.clear(); }\n  void ClearInStickyChildren() { in_sticky_children_.clear(); }\n  void ClearDeferredDestroyItemHolder() { deferred_destroy_children_.clear(); }\n  const ItemHolderSet& on_screen_children() const {\n    return on_screen_children_;\n  }\n  const ItemHolderSet& in_preload_children() const {\n    return in_preload_children_;\n  }\n  const ItemHolderSet& in_sticky_children() const {\n    return in_sticky_children_;\n  }\n  const ItemHolderSet& last_binding_children() const {\n    return last_binding_children_;\n  }\n  const WeakItemHolderSet& deferred_destroy_children() const {\n    return deferred_destroy_children_;\n  }\n  void EraseFromLastBindingChildren(ItemHolder* item_holder);\n  void HandleLayoutOrScrollResult(\n      const std::function<bool(ItemHolder*)>& insert_handler,\n      const std::function<bool(ItemHolder*)>& recycle_handler,\n      const std::function<bool(ItemHolder*)>& update_handler);\n  ItemHolder* GetFirstChildFrom(\n      const ItemHolderSet& children, ItemHolder* start_child,\n      const std::function<bool(const ItemHolder*)>& condition_func,\n      bool reverse = false) const;\n  void InitStickyItemHolderSet(int thread_mode);\n  bool AddToStickyItemHolderSet(ItemHolder* item_holder);\n  bool InStickyItemHolderSet(const ItemHolder* item_holder) const;\n  void SetRecycleStickyItem(bool recycle_sticky_item) {\n    recycle_item_holder_ = recycle_sticky_item;\n  }\n  void SetCustomStickyItemHolderCapacity(int capacity) {\n    if (capacity > 0) {\n      use_default_sticky_buffer_count_ = false;\n      in_sticky_top_children_.SetCapacity(capacity);\n      in_sticky_bottom_children_.SetCapacity(capacity);\n    }\n  }\n\n private:\n  bool recycle_item_holder_{false};\n  bool use_default_sticky_buffer_count_{true};\n  StickyItemHolderSet in_sticky_top_children_;\n  StickyItemHolderSet in_sticky_bottom_children_;\n  std::unordered_map<ItemElementDelegate*, ItemHolder*>\n      attached_delegate_item_holder_map_;\n  ItemHolderSet children_;\n  ItemHolderSet attached_children_;\n  ItemHolderSet last_binding_children_;\n  ItemHolderSet on_screen_children_;\n  ItemHolderSet in_preload_children_;\n  ItemHolderSet in_sticky_children_;\n  // NOTE: there are maybe some item holders which cant be destroyed in diff\n  // process because of animation. And because they are managed by unique_ptr,\n  // they cant be managed by themselves.\n  // And we need to ensure the children's safety during animation process.\n  WeakItemHolderSet deferred_destroy_children_;\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LIST_CHILDREN_HELPER_H_\n"
  },
  {
    "path": "core/list/decoupled_list_children_helper_unittest.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/list/decoupled_list_children_helper.h\"\n\n#include \"core/list/decoupled_list_adapter.h\"\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/value_wrapper/value_impl_lepus.h\"\n#include \"testing/fiber_data_source.h\"\n#include \"testing/mock_list_element.h\"\n#include \"testing/mock_list_item_element.h\"\n#include \"testing/radon_data_source.h\"\n#include \"testing/utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListChildrenHelperTest : public ::testing::Test {\n public:\n  ListChildrenHelperTest() = default;\n  ~ListChildrenHelperTest() override = default;\n\n  std::unique_ptr<MockListElement> mock_list_element_{nullptr};\n  std::unique_ptr<ListContainerImpl> list_container_impl_{nullptr};\n  std::shared_ptr<pub::PubValueFactoryDefault> value_factory_{nullptr};\n  ListLayoutManager* list_layout_manager_{nullptr};\n  ListAdapter* list_adapter_{nullptr};\n  ListChildrenHelper* list_children_helper_{nullptr};\n\n  void SetUp() override {\n    value_factory_ = std::make_shared<pub::PubValueFactoryDefault>();\n    mock_list_element_ = std::make_unique<MockListElement>();\n    list_container_impl_ = std::make_unique<ListContainerImpl>(\n        mock_list_element_.get(), value_factory_);\n    list_layout_manager_ = list_container_impl_->list_layout_manager();\n    list_adapter_ = list_container_impl_->list_adapter();\n    list_children_helper_ = list_container_impl_->list_children_helper();\n  }\n\n  void InitFiberDataSource() {\n    testing::InsertAction insert_action{\n        .insert_ops_ = {\n            {.position_ = 0, \"A_0\", 100, false, false, false, false},\n            {.position_ = 1, \"B_1\", 100, false, false, false, false},\n            {.position_ = 2, \"C_2\", 100, false, false, false, false},\n            {.position_ = 3, \"D_3\", 100, false, false, false, false},\n            {.position_ = 4, \"E_4\", 100, false, false, false, false},\n            {.position_ = 5, \"F_5\", 100, false, false, false, false},\n            {.position_ = 6, \"G_6\", 100, false, false, false, false},\n            {.position_ = 7, \"H_7\", 100, false, false, false, false},\n            {.position_ = 8, \"I_8\", 100, false, false, false, false},\n            {.position_ = 9, \"J_9\", 100, false, false, false, false},\n        }};\n    testing::FiberDataSource fiber_data_source{\n        .insert_action_ = insert_action,\n    };\n    LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n        FiberUpdateListInfo, lepus::Value(fiber_data_source.Resolve()));\n    list_container_impl_->ResolveAttribute(*key, value);\n    list_container_impl_->PropsUpdateFinish();\n  }\n\n  ItemHolder* GetItemHolderForKey(const std::string& item_key) {\n    const auto& item_holder_map = list_adapter_->item_holder_map_;\n    if (item_holder_map->end() != item_holder_map->find(item_key)) {\n      return ((*item_holder_map)[item_key]).get();\n    }\n    return nullptr;\n  }\n\n  void Init(const std::vector<std::string>& on_screen_keys,\n            const std::vector<std::string>& in_preload_keys,\n            const std::vector<std::pair<std::string, bool>>& in_sticky_keys,\n            const std::vector<std::string>& attached_keys) {\n    list_children_helper_->ClearOnScreenChildren();\n    list_children_helper_->ClearInPreloadChildren();\n    list_children_helper_->ClearInStickyChildren();\n    list_children_helper_->ClearAttachedChildren();\n    const ItemHolderSet& on_screen_children =\n        list_children_helper_->on_screen_children();\n    const ItemHolderSet& in_preload_children =\n        list_children_helper_->in_preload_children();\n    const ItemHolderSet& in_sticky_children =\n        list_children_helper_->in_sticky_children();\n    for (const std::string& item_key : on_screen_keys) {\n      list_children_helper_->AddChild(on_screen_children,\n                                      GetItemHolderForKey(item_key));\n    }\n    for (const std::string& item_key : in_preload_keys) {\n      list_children_helper_->AddChild(in_preload_children,\n                                      GetItemHolderForKey(item_key));\n    }\n    if (list_children_helper_->recycle_item_holder_) {\n      list_children_helper_->InitStickyItemHolderSet(\n          base::ThreadStrategyForRendering::ALL_ON_UI);\n    }\n    for (const auto& item : in_sticky_keys) {\n      if (list_children_helper_->recycle_item_holder_) {\n        if (item.second) {\n          list_children_helper_->in_sticky_top_children_.AddItemHolder(\n              GetItemHolderForKey(item.first));\n        } else {\n          list_children_helper_->in_sticky_bottom_children_.AddItemHolder(\n              GetItemHolderForKey(item.first));\n        }\n      } else {\n        list_children_helper_->AddChild(in_sticky_children,\n                                        GetItemHolderForKey(item.first));\n      }\n    }\n    mock_list_element_->ClearListItemElements();\n    int list_item_element_id = mock_list_element_->GetImplId() + 1;\n    for (const std::string& item_key : attached_keys) {\n      ItemHolder* item_holder = GetItemHolderForKey(item_key);\n      mock_list_element_->AddListItemElement(\n          item_key,\n          std::make_unique<MockListItemElement>(list_item_element_id++));\n      list_children_helper_->AttachChild(\n          item_holder, mock_list_element_->GetListItemElement(item_key));\n    }\n  }\n};\n\n// Case 1. Has no attached children.\nTEST_F(ListChildrenHelperTest, HandleLayoutOrScrollResult0) {\n  InitFiberDataSource();\n  std::vector<std::string> on_screen_keys = {\"A_0\", \"B_1\", \"C_2\", \"D_3\"};\n  std::vector<std::string> in_preload_keys = {\"E_4\"};\n  std::vector<std::pair<std::string, bool>> in_sticky_keys = {{\"I_8\", false}};\n  Init(on_screen_keys, in_preload_keys, in_sticky_keys, {});\n  int insert_times = 0;\n  int recycle_times = 0;\n  int update_times = 0;\n  auto insert_handler = [&insert_times](ItemHolder* item_holder) {\n    insert_times += 1;\n    return false;\n  };\n  auto recycle_handler = [&recycle_times](ItemHolder* item_holder) {\n    recycle_times += 1;\n    return false;\n  };\n  auto update_handler = [&update_times](ItemHolder* item_holder) {\n    update_times += 1;\n    return false;\n  };\n  list_children_helper_->HandleLayoutOrScrollResult(\n      insert_handler, recycle_handler, update_handler);\n  EXPECT_EQ(list_children_helper_->last_binding_children().size(), 0);\n  EXPECT_EQ(insert_times, 0);\n  EXPECT_EQ(recycle_times, 0);\n  EXPECT_EQ(update_times, 0);\n}\n\n// Case 1. Has attached children.\nTEST_F(ListChildrenHelperTest, HandleLayoutOrScrollResult1) {\n  InitFiberDataSource();\n  std::vector<std::string> on_screen_keys = {\"A_0\", \"B_1\", \"C_2\", \"D_3\"};\n  std::vector<std::string> in_preload_keys = {\"E_4\"};\n  std::vector<std::pair<std::string, bool>> in_sticky_keys = {{\"I_8\", false}};\n  std::vector<std::string> attached_keys = {\"A_0\", \"B_1\", \"C_2\",\n                                            \"D_3\", \"E_4\", \"I_8\"};\n  Init(on_screen_keys, in_preload_keys, in_sticky_keys, attached_keys);\n  int insert_times = 0;\n  int recycle_times = 0;\n  int update_times = 0;\n  auto insert_handler = [&insert_times](ItemHolder* item_holder) {\n    insert_times += 1;\n    return false;\n  };\n  auto recycle_handler = [&recycle_times](ItemHolder* item_holder) {\n    recycle_times += 1;\n    return false;\n  };\n  auto update_handler = [&update_times](ItemHolder* item_holder) {\n    update_times += 1;\n    return false;\n  };\n  list_children_helper_->HandleLayoutOrScrollResult(\n      insert_handler, recycle_handler, update_handler);\n  EXPECT_EQ(list_children_helper_->last_binding_children().size(),\n            attached_keys.size());\n  EXPECT_EQ(insert_times, 6);\n  EXPECT_EQ(recycle_times, 0);\n  EXPECT_EQ(update_times, 6);\n}\n\n// Case2: Test scroll\nTEST_F(ListChildrenHelperTest, HandleLayoutOrScrollResult2) {\n  InitFiberDataSource();\n  // Before scroll\n  std::vector<std::string> on_screen_keys = {\"A_0\", \"B_1\", \"C_2\", \"D_3\"};\n  std::vector<std::string> in_preload_keys = {\"E_4\"};\n  std::vector<std::pair<std::string, bool>> in_sticky_keys = {{\"I_8\", false}};\n  std::vector<std::string> attached_keys = {\"A_0\", \"B_1\", \"C_2\",\n                                            \"D_3\", \"E_4\", \"I_8\"};\n  Init(on_screen_keys, in_preload_keys, in_sticky_keys, attached_keys);\n  int insert_times = 0;\n  int recycle_times = 0;\n  int update_times = 0;\n  auto insert_handler = [&insert_times](ItemHolder* item_holder) {\n    insert_times += 1;\n    return false;\n  };\n  auto recycle_handler = [&recycle_times](ItemHolder* item_holder) {\n    recycle_times += 1;\n    return false;\n  };\n  auto update_handler = [&update_times](ItemHolder* item_holder) {\n    update_times += 1;\n    return false;\n  };\n  list_children_helper_->HandleLayoutOrScrollResult(\n      insert_handler, recycle_handler, update_handler);\n  EXPECT_EQ(list_children_helper_->last_binding_children().size(),\n            attached_keys.size());\n  EXPECT_EQ(insert_times, 6);\n  EXPECT_EQ(recycle_times, 0);\n  EXPECT_EQ(update_times, 6);\n\n  // After scroll\n  on_screen_keys = {\"C_2\", \"D_3\", \"E_4\", \"F_5\"};\n  in_preload_keys = {\"B_1\", \"G_6\"};\n  in_sticky_keys = {{\"I_8\", false}, {\"J_9\", false}};\n  attached_keys = {\"B_1\", \"C_2\", \"D_3\", \"E_4\", \"F_5\", \"G_6\", \"I_8\", \"J_9\"};\n  Init(on_screen_keys, in_preload_keys, in_sticky_keys, attached_keys);\n  insert_times = 0;\n  recycle_times = 0;\n  update_times = 0;\n  list_children_helper_->HandleLayoutOrScrollResult(\n      insert_handler, recycle_handler, update_handler);\n  EXPECT_EQ(list_children_helper_->last_binding_children().size(),\n            attached_keys.size());\n  EXPECT_EQ(insert_times, 3);\n  EXPECT_EQ(recycle_times, 1);\n  EXPECT_EQ(update_times, 8);\n}\n\n// Case3: Enable recycle item holder\nTEST_F(ListChildrenHelperTest, HandleLayoutOrScrollResult3) {\n  InitFiberDataSource();\n  // Before scroll\n  std::vector<std::string> on_screen_keys = {\"B_1\", \"C_2\", \"D_3\", \"E_4\"};\n  std::vector<std::string> in_preload_keys = {};\n  std::vector<std::pair<std::string, bool>> in_sticky_keys = {{\"A_0\", true}};\n  std::vector<std::string> attached_keys = {\"A_0\", \"B_1\", \"C_2\", \"D_3\", \"E_4\"};\n  Init(on_screen_keys, in_preload_keys, in_sticky_keys, attached_keys);\n  int insert_times = 0;\n  int recycle_times = 0;\n  int update_times = 0;\n  auto insert_handler = [&insert_times](ItemHolder* item_holder) {\n    insert_times += 1;\n    return false;\n  };\n  auto recycle_handler = [&recycle_times](ItemHolder* item_holder) {\n    recycle_times += 1;\n    return false;\n  };\n  auto update_handler = [&update_times](ItemHolder* item_holder) {\n    update_times += 1;\n    return false;\n  };\n  list_children_helper_->HandleLayoutOrScrollResult(\n      insert_handler, recycle_handler, update_handler);\n  EXPECT_EQ(list_children_helper_->last_binding_children().size(),\n            attached_keys.size());\n  EXPECT_EQ(insert_times, 5);\n  EXPECT_EQ(recycle_times, 0);\n  EXPECT_EQ(update_times, 5);\n\n  // After scroll\n  on_screen_keys = {\"D_3\", \"E_4\", \"F_5\", \"G_6\"};\n  in_preload_keys = {};\n  in_sticky_keys = {{\"C_2\", true}};\n  attached_keys = {\"C_2\", \"D_3\", \"E_4\", \"F_5\", \"G_6\"};\n  Init(on_screen_keys, in_preload_keys, in_sticky_keys, attached_keys);\n  insert_times = 0;\n  recycle_times = 0;\n  update_times = 0;\n  list_children_helper_->HandleLayoutOrScrollResult(\n      insert_handler, recycle_handler, update_handler);\n  EXPECT_EQ(list_children_helper_->last_binding_children().size(),\n            attached_keys.size());\n  EXPECT_EQ(insert_times, 2);\n  EXPECT_EQ(recycle_times, 2);\n  EXPECT_EQ(update_times, 5);\n}\n\nTEST_F(ListChildrenHelperTest, AddChild) {\n  InitFiberDataSource();\n  EXPECT_EQ(list_children_helper_->children().size(),\n            list_container_impl_->GetDataCount());\n}\n\nTEST_F(ListChildrenHelperTest, AttachChild) {\n  InitFiberDataSource();\n  int list_item_element_id = mock_list_element_->GetImplId() + 1;\n  for (int i = 0; i < list_container_impl_->GetDataCount(); ++i) {\n    ItemHolder* item_holder = list_container_impl_->GetItemHolderForIndex(i);\n    const std::string& item_key = item_holder->item_key();\n    mock_list_element_->AddListItemElement(\n        item_key,\n        std::make_unique<MockListItemElement>(list_item_element_id++));\n    list_children_helper_->AttachChild(\n        item_holder, mock_list_element_->GetListItemElement(item_key));\n  }\n  EXPECT_EQ(list_children_helper_->attached_children().size(),\n            list_container_impl_->GetDataCount());\n  EXPECT_EQ(list_children_helper_->attached_delegate_item_holder_map().size(),\n            list_container_impl_->GetDataCount());\n}\n\nTEST_F(ListChildrenHelperTest, DetachChild) {\n  InitFiberDataSource();\n  int list_item_element_id = mock_list_element_->GetImplId() + 1;\n  for (int i = 0; i < list_container_impl_->GetDataCount(); ++i) {\n    ItemHolder* item_holder = list_container_impl_->GetItemHolderForIndex(i);\n    const std::string& item_key = item_holder->item_key();\n    mock_list_element_->AddListItemElement(\n        item_key,\n        std::make_unique<MockListItemElement>(list_item_element_id++));\n    list_children_helper_->AttachChild(\n        item_holder, mock_list_element_->GetListItemElement(item_key));\n  }\n  EXPECT_EQ(list_children_helper_->attached_children().size(),\n            list_container_impl_->GetDataCount());\n  EXPECT_EQ(list_children_helper_->attached_delegate_item_holder_map().size(),\n            list_container_impl_->GetDataCount());\n  for (int i = 0; i < list_container_impl_->GetDataCount(); ++i) {\n    ItemHolder* item_holder = list_container_impl_->GetItemHolderForIndex(i);\n    const std::string& item_key = item_holder->item_key();\n    list_children_helper_->DetachChild(\n        item_holder, mock_list_element_->GetListItemElement(item_key));\n  }\n  EXPECT_EQ(list_children_helper_->attached_children().size(), 0);\n  EXPECT_EQ(list_children_helper_->attached_delegate_item_holder_map().size(),\n            0);\n}\n\nTEST_F(ListChildrenHelperTest, ClearAttachedChildren) {\n  InitFiberDataSource();\n  int list_item_element_id = mock_list_element_->GetImplId() + 1;\n  for (int i = 0; i < list_container_impl_->GetDataCount(); ++i) {\n    ItemHolder* item_holder = list_container_impl_->GetItemHolderForIndex(i);\n    const std::string& item_key = item_holder->item_key();\n    mock_list_element_->AddListItemElement(\n        item_key,\n        std::make_unique<MockListItemElement>(list_item_element_id++));\n    list_children_helper_->AttachChild(\n        item_holder, mock_list_element_->GetListItemElement(item_key));\n  }\n  list_children_helper_->ClearAttachedChildren();\n  EXPECT_EQ(list_children_helper_->attached_children().size(), 0);\n  EXPECT_EQ(list_children_helper_->attached_delegate_item_holder_map().size(),\n            0);\n}\n\nTEST_F(ListChildrenHelperTest, GetFirstChild) {\n  InitFiberDataSource();\n  ItemHolder* item_holder =\n      list_children_helper_->GetFirstChild([](const ItemHolder* item_holder) {\n        return item_holder->item_key() == \"C_2\";\n      });\n  EXPECT_TRUE(item_holder != nullptr);\n  EXPECT_TRUE(item_holder->item_key() == \"C_2\");\n\n  item_holder = list_children_helper_->GetFirstChild(\n      list_children_helper_->children(), [](const ItemHolder* item_holder) {\n        return item_holder->item_key() == \"D_3\";\n      });\n  EXPECT_TRUE(item_holder != nullptr);\n  EXPECT_TRUE(item_holder->item_key() == \"D_3\");\n}\n\nTEST_F(ListChildrenHelperTest, GetLastChild) {\n  InitFiberDataSource();\n  ItemHolder* item_holder =\n      list_children_helper_->GetLastChild([](const ItemHolder* item_holder) {\n        return item_holder->item_key() == \"G_6\";\n      });\n  EXPECT_TRUE(item_holder != nullptr);\n  EXPECT_TRUE(item_holder->item_key() == \"G_6\");\n\n  item_holder = list_children_helper_->GetLastChild(\n      list_children_helper_->children(), [](const ItemHolder* item_holder) {\n        return item_holder->item_key() == \"H_7\";\n      });\n  EXPECT_TRUE(item_holder != nullptr);\n  EXPECT_TRUE(item_holder->item_key() == \"H_7\");\n}\n\nTEST_F(ListChildrenHelperTest, GetFirstChildFrom) {\n  InitFiberDataSource();\n  const ItemHolderSet& children = list_children_helper_->children();\n  // 1. Forward search\n  // Find an element with valid item key that exists after the starting point.\n  ItemHolder* start_child = GetItemHolderForKey(\"C_2\");\n  ItemHolder* result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) {\n        return !item->item_key().empty() && item->item_key() != \"C_2\";\n      },\n      false);\n  EXPECT_NE(result, nullptr);\n  EXPECT_EQ(result->item_key(), \"D_3\");\n\n  // Try to find an element that only exists before the starting point.\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) { return item->item_key() == \"A_0\"; }, false);\n  EXPECT_EQ(result, nullptr);\n\n  // The condition is met by the starting element itself.\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) {\n        return !item->item_key().empty() && item->item_key() == \"C_2\";\n      },\n      false);\n  EXPECT_NE(result, nullptr);\n  EXPECT_EQ(result->item_key(), \"C_2\");\n\n  // No element meets the condition.\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) { return item->item_key() == \"Z_25\"; }, false);\n  EXPECT_EQ(result, nullptr);\n\n  // 2. Reverse search\n  // Find an element that exists before the starting point.\n  start_child = GetItemHolderForKey(\"G_6\");\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) {\n        return !item->item_key().empty() && item->item_key() != \"G_6\";\n      },\n      true);\n  EXPECT_NE(result, nullptr);\n  EXPECT_EQ(result->item_key(), \"F_5\");\n\n  // Try to find an element that only exists after the starting point.\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) { return item->item_key() == \"I_8\"; }, true);\n  EXPECT_EQ(result, nullptr);\n\n  // The condition is met by the starting element itself.\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) {\n        return !item->item_key().empty() && item->item_key() == \"G_6\";\n      },\n      true);\n  EXPECT_NE(result, nullptr);\n  EXPECT_EQ(result->item_key(), \"G_6\");\n\n  // No element meets the condition.\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) { return item->item_key() == \"Z_25\"; }, true);\n  EXPECT_EQ(result, nullptr);\n\n  // 3. Edge cases\n  // start_child is not in children.\n  auto temp_holder = std::make_unique<ItemHolder>(\n      99, \"NotInSet\", list_container_impl_->list_animation_manager());\n  result = list_children_helper_->GetFirstChildFrom(\n      children, temp_holder.get(),\n      [](const ItemHolder* item) { return !item->item_key().empty(); }, false);\n  EXPECT_EQ(result, nullptr);\n\n  // 4. The set is empty.\n  start_child = GetItemHolderForKey(\"F_5\");\n  list_children_helper_->ClearChildren();\n  EXPECT_EQ(list_children_helper_->children().size(), 0);\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) { return !item->item_key().empty(); }, false);\n  EXPECT_EQ(result, nullptr);\n\n  // 5. The set only has one element.\n  start_child = GetItemHolderForKey(\"F_5\");\n  list_children_helper_->ClearChildren();\n  list_children_helper_->AddChild(children, start_child);\n  EXPECT_EQ(list_children_helper_->children().size(), 1);\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) {\n        return !item->item_key().empty() && item->item_key() == \"F_5\";\n      },\n      false);\n  EXPECT_NE(result, nullptr);\n  EXPECT_EQ(result->item_key(), \"F_5\");\n\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) {\n        return !item->item_key().empty() && item->item_key() != \"F_5\";\n      },\n      false);\n  EXPECT_EQ(result, nullptr);\n\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) {\n        return !item->item_key().empty() && item->item_key() == \"F_5\";\n      },\n      true);\n  EXPECT_NE(result, nullptr);\n  EXPECT_EQ(result->item_key(), \"F_5\");\n\n  result = list_children_helper_->GetFirstChildFrom(\n      children, start_child,\n      [](const ItemHolder* item) {\n        return !item->item_key().empty() && item->item_key() != \"F_5\";\n      },\n      true);\n  EXPECT_EQ(result, nullptr);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_container_default.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_list_container_default.h\"\n\nnamespace lynx {\nnamespace list {\n\nstd::unique_ptr<ContainerDelegate> CreateListContainerDelegate(\n    ElementDelegate* list_delegate,\n    const std::shared_ptr<pub::PubValueFactory>& value_factory) {\n  return std::make_unique<ListContainerDefault>(list_delegate, value_factory);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_container_default.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LIST_CONTAINER_DEFAULT_H_\n#define CORE_LIST_DECOUPLED_LIST_CONTAINER_DEFAULT_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"core/list/list_container_delegate.h\"\n#include \"core/list/list_element_delegate.h\"\n#include \"core/list/list_item_element_delegate.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListContainerDefault : public ContainerDelegate {\n public:\n  ListContainerDefault(\n      ElementDelegate* list_delegate,\n      const std::shared_ptr<pub::PubValueFactory>& value_factory) {}\n\n  ~ListContainerDefault() override {}\n\n  void OnAttachToElementManager() override {}\n\n  bool ResolveAttribute(const pub::Value& key,\n                        const pub::Value& value) override {}\n\n  void ResolveListAxisGap(tasm::CSSPropertyID id, float gap) override {}\n\n  void PropsUpdateFinish() override {}\n\n  void OnLayoutChildren(\n      const std::shared_ptr<tasm::PipelineOptions>& options) override {}\n\n  void FinishBindItemHolder(\n      ItemElementDelegate* list_item_delegate,\n      const std::shared_ptr<tasm::PipelineOptions>& options) override {}\n\n  void FinishBindItemHolders(\n      const std::vector<ItemElementDelegate*>& list_item_delegate_array,\n      const std::shared_ptr<tasm::PipelineOptions>& options) override {}\n\n  void ScrollByPlatformContainer(float content_offset_x, float content_offset_y,\n                                 float original_x, float original_y) override {}\n\n  void OnListItemLayoutUpdated(\n      ItemElementDelegate* list_item_delegate) override {}\n\n  void OnAttachedToElementManager() override {}\n\n  void ScrollToPosition(int index, float offset, int align,\n                        bool smooth) override {}\n\n  void ScrollStopped() override {}\n\n  void EnableInsertPlatformView() override {}\n\n  void OnNextFrame() override {}\n\n  void SetEnableBatchRender(bool enable_batch_render) override {}\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LIST_CONTAINER_DEFAULT_H_\n"
  },
  {
    "path": "core/list/decoupled_list_container_impl.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_list_container_impl.h\"\n\n#include <algorithm>\n#include <utility>\n\n#include \"base/trace/native/trace_event.h\"\n#include \"core/build/gen/lynx_sub_error_code.h\"\n#include \"core/list/decoupled_batch_list_adapter.h\"\n#include \"core/list/decoupled_default_list_adapter.h\"\n#include \"core/list/decoupled_grid_layout_manager.h\"\n#include \"core/list/decoupled_linear_layout_manager.h\"\n#include \"core/list/decoupled_staggered_grid_layout_manager.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\nListContainerImpl::ListContainerImpl(\n    ElementDelegate* list_delegate,\n    const std::shared_ptr<pub::PubValueFactory>& value_factory)\n    : list_delegate_(list_delegate),\n      list_layout_manager_(std::make_unique<LinearLayoutManager>(this)),\n      list_adapter_(std::make_unique<DefaultListAdapter>(this)),\n      list_children_helper_(std::make_unique<ListChildrenHelper>()),\n      list_event_manager_(std::make_unique<ListEventManager>(this)),\n      list_animation_manager_(CreateListAnimationManager(this)),\n      value_factory_(value_factory) {\n  DLIST_LOGI(\"ListContainerImpl::ListContainerImpl() this=\" << this);\n  list_layout_manager_->InitLayoutManager(list_children_helper_.get(),\n                                          Orientation::kVertical);\n  if (!list_delegate->IsAttachToElementManager()) {\n    return;\n  }\n  physical_pixels_per_layout_unit_ =\n      list_delegate_->GetPhysicalPixelsPerLayoutUnit();\n  if (base::FloatsEqual(physical_pixels_per_layout_unit_, 0.f)) {\n    physical_pixels_per_layout_unit_ = 1.f;\n  }\n}\n\nListContainerImpl::~ListContainerImpl() {\n  DLIST_LOGI(\"ListContainerImpl::~ListContainerImpl this=\" << this);\n}\n\nvoid ListContainerImpl::OnAttachToElementManager() {\n  physical_pixels_per_layout_unit_ =\n      list_delegate_->GetPhysicalPixelsPerLayoutUnit();\n  if (base::FloatsEqual(physical_pixels_per_layout_unit_, 0.f)) {\n    physical_pixels_per_layout_unit_ = 1.f;\n  }\n}\n\nvoid ListContainerImpl::FinishBindItemHolder(\n    ItemElementDelegate* list_item_delegate,\n    const std::shared_ptr<tasm::PipelineOptions>& options) {\n  if (list_adapter_) {\n    list_adapter_->OnFinishBindItemHolder(list_item_delegate, options);\n  }\n}\n\nvoid ListContainerImpl::FinishBindItemHolders(\n    const std::vector<ItemElementDelegate*>& list_item_delegate_array,\n    const std::shared_ptr<tasm::PipelineOptions>& options) {\n  if (list_adapter_) {\n    list_adapter_->OnFinishBindItemHolders(list_item_delegate_array, options);\n  }\n}\n\nvoid ListContainerImpl::OnNextFrame() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_CONTAINER_ON_NEXT_FRAME);\n  list_layout_manager_->PreloadSection();\n  need_preload_section_on_next_frame_ = false;\n}\n\nvoid ListContainerImpl::OnListItemLayoutUpdated(\n    ItemElementDelegate* list_item_delegate) {\n  if (list_item_delegate) {\n    const auto& attached_delegate_item_holder_map =\n        list_children_helper_->attached_delegate_item_holder_map();\n    if (auto it = attached_delegate_item_holder_map.find(list_item_delegate);\n        it != attached_delegate_item_holder_map.end()) {\n      list_adapter_->UpdateLayoutInfoToItemHolder(list_item_delegate,\n                                                  it->second);\n    }\n  }\n}\n\nfloat ListContainerImpl::RoundValueToPixelGrid(const float value) {\n  return std::roundf(value * physical_pixels_per_layout_unit_) /\n         physical_pixels_per_layout_unit_;\n}\n\n// Get count of data source.\nint ListContainerImpl::GetDataCount() const {\n  return list_adapter_ ? list_adapter_->GetDataCount() : 0;\n}\n\n// Get the ItemHolder for the specified index.\nItemHolder* ListContainerImpl::GetItemHolderForIndex(int index) {\n  return list_adapter_ ? list_adapter_->GetItemHolderForIndex(index) : nullptr;\n}\n\n// Flush all children's layout info patching to plaform.\nvoid ListContainerImpl::FlushPatching() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_CONTAINER_FLUSH_PATCHING);\n  bool should_flush_finish_layout = should_flush_finish_layout_;\n  should_flush_finish_layout_ = false;\n  list_delegate_->FlushPatching(should_flush_finish_layout);\n}\n\n// Update content offset and size to platform view.\nvoid ListContainerImpl::UpdateContentOffsetAndSizeToPlatform(\n    float content_size, float delta_x, float delta_y,\n    bool is_init_scroll_offset, bool from_layout) {\n  list_delegate_->UpdateContentOffsetAndSizeToPlatform(\n      content_size, delta_x, delta_y, is_init_scroll_offset, from_layout);\n}\n\n// Update scroll info to platform view.\nvoid ListContainerImpl::UpdateScrollInfo(float estimated_offset, bool smooth,\n                                         bool scrolling) {\n  if (smooth) {\n    list_delegate_->UpdateScrollInfo(estimated_offset, smooth, scrolling);\n  }\n}\n\n// This function should be called before any code that may trigger list's\n// OnListElementUpdated() to enable list avoid reacting to additional redundant\n// OnListElementUpdated() calls.\nvoid ListContainerImpl::StartInterceptListElementUpdated() {\n  intercept_depth_++;\n}\n\n// This method should be called after any code that may trigger list's\n// OnListElementUpdated().\nvoid ListContainerImpl::StopInterceptListElementUpdated() {\n  if (intercept_depth_ < 1) {\n    intercept_depth_ = 1;\n  }\n  intercept_depth_--;\n}\n\nvoid ListContainerImpl::UpdateListLayoutManager(LayoutType layout_type) {\n  int span_count = list_layout_manager_->span_count();\n  Orientation orientation = list_layout_manager_->orientation();\n  float main_axis_gap = list_layout_manager_->main_axis_gap();\n  float cross_axis_gap = list_layout_manager_->cross_axis_gap();\n  float preload_buffer_count = list_layout_manager_->preload_buffer_count();\n  float content_size = list_layout_manager_->content_size();\n  // Store the previous content_offset_ or the delta calculation may be\n  // incorrect\n  float content_offset = list_layout_manager_->content_offset();\n  bool enable_preload_section = list_layout_manager_->enable_preload_section();\n  if (layout_type == LayoutType::kSingle) {\n    list_layout_manager_ = std::make_unique<LinearLayoutManager>(this);\n  } else if (layout_type == LayoutType::kFlow) {\n    list_layout_manager_ = std::make_unique<GridLayoutManager>(this);\n  } else if (layout_type == LayoutType::kWaterFall) {\n    list_layout_manager_ = std::make_unique<StaggeredGridLayoutManager>(this);\n  }\n  list_layout_manager_->InitLayoutManager(list_children_helper_.get(),\n                                          orientation);\n  list_layout_manager_->SetSpanCount(span_count);\n  list_layout_manager_->SetMainAxisGap(main_axis_gap);\n  list_layout_manager_->SetCrossAxisGap(cross_axis_gap);\n  list_layout_manager_->ResetContentOffsetAndContentSize(content_offset,\n                                                         content_size);\n  list_layout_manager_->SetPreloadBufferCount(preload_buffer_count);\n  list_layout_manager_->SetEnablePreloadSection(enable_preload_section);\n  list_adapter_->OnDataSetChanged();\n  need_recycle_all_item_holders_before_layout_ = true;\n}\n\nbool ListContainerImpl::ResolveAttribute(const pub::Value& key,\n                                         const pub::Value& value) {\n  if (!key.IsString()) {\n    DLIST_LOGE(\"[\" << this\n                   << \"] ListContainerImpl::ResolveAttribute: non string key\");\n    return true;\n  }\n  const std::string& key_str = key.str();\n  // TODO(dingwang.wxx): using more save trace event.\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_CONTAINER_RESOLVE_ATTRIBUTE, \"key\",\n              key_str.c_str());\n  bool should_set_props = true;\n  bool should_mark_layout_dirty = false;\n  if (key_str == kPropCustomListName && value.IsString()) {\n    // custom-list-container\n    if (value.str() == kPropValueListContainer) {\n      list_delegate_->UpdateListLayoutNodeAttribute();\n    }\n  } else if (key_str == kPropVerticalOrientation) {\n    // vertical-orientation\n    // TODO: @deprecated vertical-orientation\n    Orientation orientation = Orientation::kVertical;\n    if (value.IsBool()) {\n      orientation =\n          value.Bool() ? Orientation::kVertical : Orientation::kHorizontal;\n    } else if (value.IsString()) {\n      orientation = value.str() == kPropValueTrue ? Orientation::kVertical\n                                                  : Orientation::kHorizontal;\n      std::string error_msg = \"Error type for attribute vertical-orientation\";\n      std::string suggestion =\n          \"Please use bool value or use scroll-orientation\";\n      auto error = lynx::base::LynxError(\n          error::E_COMPONENT_LIST_INVALID_PROPS_ARG, std::move(error_msg),\n          std::move(suggestion), base::LynxErrorLevel::Warn);\n      list_delegate_->OnErrorOccurred(std::move(error));\n    }\n    list_layout_manager_->SetOrientation(orientation);\n    list_layout_manager_->CreateOrUpdateListAnchorManager();\n  } else if (key_str == kPropScrollOrientation && value.IsString()) {\n    // scroll-orientation\n    // Note: if value is any illegal string, using vertical by default.\n    Orientation orientation =\n        value.str() == kPropValueScrollOrientationHorizontal\n            ? Orientation::kHorizontal\n            : Orientation::kVertical;\n    list_layout_manager_->SetOrientation(orientation);\n    list_layout_manager_->CreateOrUpdateListAnchorManager();\n  } else if (key_str == kPropEnableDynamicSpanCount && value.IsBool()) {\n    // enable-dynamic-span-count\n    enable_dynamic_span_count_ = value.Bool();\n    should_set_props = false;\n  } else if ((key_str == kPropSpanCount || key_str == kPropColumnCount) &&\n             value.IsNumber()) {\n    // span-count / column-count\n    // TODO: @deprecated column-count\n    int span_count = static_cast<int>(value.Number());\n    if (span_count <= 0) {\n      span_count = 1;\n    }\n    if (list_layout_manager_->span_count() != span_count) {\n      span_count_changed_ = true;\n    }\n    list_layout_manager_->SetSpanCount(span_count);\n    should_mark_layout_dirty = true;\n    should_set_props = false;\n  } else if (key_str == kPropAnchorPriority && value.IsString()) {\n    // anchor-priority\n    list_layout_manager_->SetAnchorPriorityFromBegin(\n        value.str() == kPropValueAnchorPriorityFromBegin);\n    should_set_props = false;\n  } else if (key_str == kPropAnchorAlign && value.IsString()) {\n    // anchor-align\n    list_layout_manager_->SetAnchorAlignToBottom(value.str() ==\n                                                 kPropValueAnchorAlignToBottom);\n    should_set_props = false;\n  } else if (key_str == kPropAnchorVisibility && value.IsString()) {\n    // anchor-visibility\n    const std::string& value_str = value.str();\n    if (value_str == kPropValueAnchorVisibilityHide) {\n      list_layout_manager_->SetAnchorVisibility(\n          AnchorVisibility::kAnchorVisibilityHide);\n    } else if (value_str == kPropValueAnchorVisibilityShow) {\n      list_layout_manager_->SetAnchorVisibility(\n          AnchorVisibility::kAnchorVisibilityShow);\n    } else {\n      list_layout_manager_->SetAnchorVisibility(\n          AnchorVisibility::kAnchorVisibilityNoAdjustment);\n    }\n    should_set_props = false;\n  } else if ((key_str == kPropRadonListPlatformInfo ||\n              key_str == kPropFiberUpdateListInfo) &&\n             value.IsMap()) {\n    // list-platform-info / update-list-info\n    std::pair<ListAdapterDiffResult, bool> result;\n    if (key_str == kPropRadonListPlatformInfo) {\n      result = list_adapter_->UpdateRadonDataSource(value);\n    } else if (key_str == kPropFiberUpdateListInfo) {\n      result = list_adapter_->UpdateFiberDataSource(value);\n    }\n    if (result.first != ListAdapterDiffResult::kNone) {\n      should_mark_layout_dirty = true;\n    }\n    has_valid_diff_ = should_mark_layout_dirty;\n    need_preload_section_on_next_frame_ = should_mark_layout_dirty;\n    if (should_mark_layout_dirty) {\n      list_layout_manager_->UpdateDiffAnchorReference();\n    }\n    should_set_props = false;\n    need_update_item_holders_ = true;\n    animation_diff_result_ =\n        result.second ? result.first : ListAdapterDiffResult::kNone;\n  } else if (key_str == kPropUpdateAnimation && value.IsString()) {\n    // update-animation\n    update_animation_ = value.str() == kPropValueUpdateAnimationDefault;\n    should_set_props = false;\n  } else if (key_str == kPropListType && value.IsString()) {\n    // list-type\n    LayoutType last_layout_type = layout_type_;\n    const std::string& value_str = value.str();\n    if (value_str == kPropValueListTypeSingle) {\n      layout_type_ = LayoutType::kSingle;\n    } else if (value_str == kPropValueListTypeFlow) {\n      layout_type_ = LayoutType::kFlow;\n    } else if (value_str == kPropValueListTypeWaterFall) {\n      layout_type_ = LayoutType::kWaterFall;\n    } else {\n      layout_type_ = LayoutType::kSingle;\n    }\n    if (layout_type_ != last_layout_type) {\n      UpdateListLayoutManager(layout_type_);\n    }\n    should_mark_layout_dirty = true;\n    should_set_props = false;\n  } else if (key_str == kPropInitialScrollIndex && value.IsNumber()) {\n    // initial-scroll-index\n    initial_scroll_index_ = static_cast<int>(value.Number());\n    should_set_props = false;\n  } else if (key_str == kPropUpperThresholdItemCount && value.IsNumber()) {\n    // upper-threshold-item-count\n    list_event_manager_->SetUpperThresholdItemCount(\n        static_cast<int>(value.Number()));\n    should_set_props = false;\n  } else if (key_str == kPropLowerThresholdItemCount && value.IsNumber()) {\n    // lower-threshold-item-count\n    list_event_manager_->SetLowerThresholdItemCount(\n        static_cast<int>(value.Number()));\n    should_set_props = false;\n  } else if (key_str == kPropNeedLayoutCompleteInfo && value.IsBool()) {\n    // need-layout-complete-info\n    list_event_manager_->SetNeedLayoutCompleteInfo(value.Bool());\n    should_set_props = false;\n  } else if (key_str == kPropLayoutId && value.IsNumber()) {\n    // layout-id\n    layout_id_ = static_cast<int>(value.Number());\n    should_set_props = false;\n  } else if (key_str == kPropScrollEventThrottle && value.IsNumber()) {\n    // scroll-event-throttle\n    list_event_manager_->SetScrollEventThrottleMS(\n        static_cast<int>(value.Number()));\n    should_set_props = false;\n  } else if ((key_str == kPropNeedsVisibleCells ||\n              key_str == kPropNeedVisibleItemInfo) &&\n             value.IsBool()) {\n    // need-visible-item-info / needs-visible-cells\n    // TODO: @deprecated needs-visible-cells\n    list_event_manager_->SetVisibleCell(value.Bool());\n  } else if (key_str == kPropShouldRequestStateRestore && value.IsBool()) {\n    // should-request-state-restore\n    should_request_state_restore_ = value.Bool();\n    should_set_props = false;\n  } else if (key_str == kPropStickyOffset && value.IsNumber()) {\n    // sticky-offset\n    sticky_offset_ = value.Number();\n  } else if (key_str == kPropSticky && value.IsBool()) {\n    // sticky\n    sticky_enabled_ = value.Bool();\n  } else if (key_str == kPropExperimentalRecycleStickyItem && value.IsBool()) {\n    // experimental-recycle-sticky-item\n    // TODO(dingwang.wxx): experimental prop, the default value is true in\n    // release3.4.\n    recycle_sticky_item_ = value.Bool();\n  } else if (key_str == kPropStickyBufferCount && value.IsNumber()) {\n    // sticky-buffer-count\n    sticky_buffer_count_ = static_cast<int>(value.Number());\n  } else if (key_str == kPropEnablePreloadSection && value.IsBool()) {\n    // experimental-enable-preload-section\n    list_layout_manager_->SetEnablePreloadSection(value.Bool());\n    should_set_props = false;\n  } else if (key_str == kPropPreloadBufferCount && value.IsNumber()) {\n    // preload-buffer-count\n    should_mark_layout_dirty = list_layout_manager_->SetPreloadBufferCount(\n        static_cast<int>(value.Number()));\n    should_set_props = false;\n  } else if (key_str == kPropEnableInsertPlatformViewOperation &&\n             value.IsBool()) {\n    // enable-insert-platform-view-operation\n    enable_insert_platform_view_operation_ = value.Bool();\n  } else if (key_str == kPropExperimentalBatchRenderStrategy) {\n    // experimental-batch-render-strategy\n    // Note: If parse experimental-batch-render-strategy in list property, we\n    // should block flush this property to platform because before parsing all\n    // properties of list element, we has pushed this property to prop_bundle.\n    should_set_props = false;\n  } else if (key_str == kPropListDebugInfoLevel && value.IsNumber()) {\n    // list-debug-info-level\n    list_event_manager_->SetListDebugInfoLevel(\n        std::min(ListDebugInfoLevel::kListDebugInfoLevelVerbose,\n                 static_cast<ListDebugInfoLevel>(value.Number())));\n    should_set_props = false;\n  } else if (key_str == kPropExperimentalRecycleAvailableItemBeforeLayout &&\n             value.IsBool()) {\n    // experimental-recycle-available-item-before-layout\n    recycle_available_item_before_layout_ = value.Bool();\n    should_set_props = false;\n  } else if (key_str == kPropExperimentalSearchRefAnchorStrategy &&\n             value.IsNumber()) {\n    // experimental-search-ref-anchor-strategy\n    search_ref_anchor_strategy_ =\n        static_cast<SearchRefAnchorStrategy>(static_cast<int>(value.Number()));\n    should_set_props = false;\n  }\n  if (should_mark_layout_dirty) {\n    list_delegate_->MarkListElementLayoutDirty();\n  }\n  return should_set_props;\n};\n\nvoid ListContainerImpl::OnLayoutChildren(\n    const std::shared_ptr<tasm::PipelineOptions>& options) {\n  if (update_animation_ != list_animation_manager_->UpdateAnimation()) {\n    list_animation_manager_->SetUpdateAnimation(update_animation_);\n  }\n  if (list_animation_manager_->UpdateAnimation() &&\n      animation_diff_result_ != ListAdapterDiffResult::kNone) {\n    list_animation_manager_->UpdateDiffResult(animation_diff_result_);\n  }\n  animation_diff_result_ = ListAdapterDiffResult::kNone;\n  if (list_layout_manager_) {\n    if (options->need_timestamps) {\n      list_delegate_->MarkTiming(ListTiming::kRenderChildrenStart);\n    }\n    if (need_recycle_all_item_holders_before_layout_) {\n      list_adapter_->RecycleAllItemHolders();\n      need_recycle_all_item_holders_before_layout_ = false;\n    }\n    if (intercept_depth_ == 0) {\n      // Note: we should reset should_flush_finish_layout_ to\n      // options->has_layout to make sure invoke FinishLayoutOperation() to\n      // trigger layoutDidFinished lifecycle of all list's children.\n      should_flush_finish_layout_ = options->has_layout;\n      // Try to enqueue all available items before layout.\n      if (recycle_available_item_before_layout_) {\n        list_adapter_->EnqueueElementsIfNeeded();\n      }\n      if (!enable_batch_render()) {\n        list_layout_manager_->OnLayoutChildren();\n      } else {\n        list_layout_manager_->OnBatchLayoutChildren();\n      }\n    }\n    if (options->need_timestamps) {\n      list_delegate_->MarkTiming(ListTiming::kRenderChildrenEnd);\n      const float list_main_size = list_layout_manager_->main_axis_size();\n      const float content_size = list_layout_manager_->content_size();\n      if (GetDataCount() > 0 && base::FloatsLarger(list_main_size, 0.f) &&\n          base::FloatsLargerOrEqual(content_size, list_main_size)) {\n        list_delegate_->MarkTiming(ListTiming::kFullFillRenderChildrenEnd);\n      }\n    }\n  }\n}\n\nvoid ListContainerImpl::PropsUpdateFinish() {\n  // Handle initial-scroll-index attr.\n  if ((initial_scroll_index_ >= 0 && initial_scroll_index_ < GetDataCount()) &&\n      initial_scroll_index_status_ == InitialScrollIndexStatus::kUnset) {\n    initial_scroll_index_status_ = InitialScrollIndexStatus::kSet;\n    list_layout_manager_->SetInitialScrollIndex(initial_scroll_index_);\n  }\n\n  // Handle update-animation attr.\n  if (layout_type_ == LayoutType::kWaterFall) {\n    // Consider the order of resolving list-type and update-animation is not\n    // fixed, we should move this logic in PropsUpdateFinish().\n    // TODO(dongjiajian): support update animation in waterfall.\n    update_animation_ = false;\n  }\n  if (update_animation_ != list_animation_manager_->UpdateAnimation()) {\n    list_delegate_->MarkListElementLayoutDirty();\n  }\n\n  // Handle enable-dynamic-span-count attr and reset span_count_changed_.\n  if (span_count_changed_) {\n    span_count_changed_ = false;\n    if (!enable_dynamic_span_count_) {\n      // Note: OnDataSetChanged() should be invoked before\n      // UpdateItemHolderToLatest() if needed.\n      list_adapter_->OnDataSetChanged();\n      need_recycle_all_item_holders_before_layout_ = true;\n    }\n  }\n\n  // Record diff result in PropsUpdateFinish() because we need to parse\n  // need-layout-complete-info.\n  list_event_manager_->RecordDiffResultIfNeeded();\n  list_event_manager_->SendDiffDebugEventIfNeeded();\n\n  // Handle experimental-batch-render-strategy attr.\n  // Note: need to move from DefaultListAdapter to BatchListAdapter before\n  // invoke UpdateItemHolderToLatest().\n  if (enable_batch_render() && !batch_adapter_initialized_) {\n    // Move construct from DefaultListAdapter to BatchListAdapter.\n    list_adapter_ =\n        std::make_unique<BatchListAdapter>(std::move(*list_adapter_));\n    // Note: set new list adapter to AnchorManager.\n    list_layout_manager_->CreateOrUpdateListAnchorManager();\n    batch_adapter_initialized_ = true;\n  }\n\n  // Update all item holders if needed.\n  if (need_update_item_holders_) {\n    list_adapter_->UpdateItemHolderToLatest(list_children_helper_.get());\n    need_update_item_holders_ = false;\n  }\n\n  // Handle sticky-buffer-count attr.\n  if (sticky_buffer_count_ > kInvalidItemCount) {\n    if (!recycle_sticky_item_) {\n      // Note: A valid sticky buffer count means need to recycle sticky\n      // item.\n      recycle_sticky_item_ = true;\n    }\n    list_children_helper_->SetCustomStickyItemHolderCapacity(\n        sticky_buffer_count_);\n  }\n  list_children_helper_->SetRecycleStickyItem(recycle_sticky_item_);\n\n  // Clear diff result.\n  list_adapter_->ClearDiffResult();\n}\n\nvoid ListContainerImpl::ScrollByPlatformContainer(float content_offset_x,\n                                                  float content_offset_y,\n                                                  float original_x,\n                                                  float original_y) {\n  if (list_layout_manager_) {\n    // reset should_flush_finish_layout_ flag to false.\n    should_flush_finish_layout_ = false;\n    list_layout_manager_->ScrollByPlatformContainer(\n        content_offset_x, content_offset_y, original_x, original_y);\n  }\n}\n\nvoid ListContainerImpl::ScrollToPosition(int index, float offset, int align,\n                                         bool smooth) {\n  if (list_layout_manager_) {\n    list_layout_manager_->ScrollToPosition(index, offset, align, smooth);\n  }\n}\n\nvoid ListContainerImpl::ScrollStopped() {\n  if (list_layout_manager_) {\n    list_layout_manager_->ScrollStopped();\n  }\n}\n\nvoid ListContainerImpl::OnAttachedToElementManager() {\n  physical_pixels_per_layout_unit_ =\n      list_delegate_->GetPhysicalPixelsPerLayoutUnit();\n  if (base::FloatsEqual(physical_pixels_per_layout_unit_, 0.f)) {\n    physical_pixels_per_layout_unit_ = 1.f;\n  }\n}\n\nvoid ListContainerImpl::ResolveListAxisGap(tasm::CSSPropertyID id, float gap) {\n  if (tasm::CSSPropertyID::kPropertyIDListMainAxisGap == id) {\n    if (base::FloatsNotEqual(gap, list_layout_manager_->main_axis_gap())) {\n      list_layout_manager_->SetMainAxisGap(gap);\n      list_delegate_->MarkListElementLayoutDirty();\n    }\n  } else if (tasm::CSSPropertyID::kPropertyIDListCrossAxisGap == id) {\n    if (base::FloatsNotEqual(gap, list_layout_manager_->cross_axis_gap())) {\n      list_layout_manager_->SetCrossAxisGap(gap);\n      list_delegate_->MarkListElementLayoutDirty();\n    }\n  }\n}\n\nstd::unique_ptr<ContainerDelegate> CreateListContainerDelegate(\n    ElementDelegate* list_delegate,\n    const std::shared_ptr<pub::PubValueFactory>& value_factory) {\n  return std::make_unique<ListContainerImpl>(list_delegate, value_factory);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_container_impl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LIST_CONTAINER_IMPL_H_\n#define CORE_LIST_DECOUPLED_LIST_CONTAINER_IMPL_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"core/list/decoupled_list_adapter.h\"\n#include \"core/list/decoupled_list_children_helper.h\"\n#include \"core/list/decoupled_list_event_manager.h\"\n#include \"core/list/decoupled_list_layout_manager.h\"\n#include \"core/list/decoupled_list_types.h\"\n#include \"core/list/list_animation_manager.h\"\n#include \"core/list/list_container_delegate.h\"\n#include \"core/list/list_element_delegate.h\"\n#include \"core/list/list_item_element_delegate.h\"\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListContainerImpl : public ContainerDelegate {\n public:\n  ListContainerImpl(ElementDelegate* list_delegate,\n                    const std::shared_ptr<pub::PubValueFactory>& value_factory);\n  ~ListContainerImpl() override;\n\n  // Implement ContainerDelegate\n  void OnAttachToElementManager() override;\n  bool ResolveAttribute(const pub::Value& key,\n                        const pub::Value& value) override;\n  void ResolveListAxisGap(tasm::CSSPropertyID id, float gap) override;\n  void PropsUpdateFinish() override;\n  void OnLayoutChildren(\n      const std::shared_ptr<tasm::PipelineOptions>& options) override;\n  void FinishBindItemHolder(\n      ItemElementDelegate* list_item_delegate,\n      const std::shared_ptr<tasm::PipelineOptions>& options) override;\n  void FinishBindItemHolders(\n      const std::vector<ItemElementDelegate*>& list_item_delegate_array,\n      const std::shared_ptr<tasm::PipelineOptions>& options) override;\n  void ScrollByPlatformContainer(float content_offset_x, float content_offset_y,\n                                 float original_x, float original_y) override;\n  void OnListItemLayoutUpdated(\n      ItemElementDelegate* list_item_delegate) override;\n  void OnAttachedToElementManager() override;\n  void ScrollToPosition(int index, float offset, int align,\n                        bool smooth) override;\n  void ScrollStopped() override;\n  void EnableInsertPlatformView() override {\n    enable_insert_platform_view_operation_ = true;\n  }\n  void OnNextFrame() override;\n  void SetEnableBatchRender(bool enable_batch_render) override {\n    enable_batch_render_ = enable_batch_render;\n  }\n\n  int GetDataCount() const;\n  ItemHolder* GetItemHolderForIndex(int index);\n  void FlushPatching();\n  void UpdateContentOffsetAndSizeToPlatform(float content_size,\n                                            float target_content_offset_x,\n                                            float target_content_offset_y,\n                                            bool is_init_scroll_offset,\n                                            bool from_layout);\n  void UpdateScrollInfo(float estimated_offset, bool smooth, bool scrolling);\n  void StartInterceptListElementUpdated();\n  void StopInterceptListElementUpdated();\n  float RoundValueToPixelGrid(const float value);\n  void ClearValidDiff() { has_valid_diff_ = false; }\n  void MarkShouldFlushFinishLayout(bool has_layout) {\n    should_flush_finish_layout_ |= has_layout;\n  }\n  void ResetLayoutID() { layout_id_ = -1; }\n  bool ShouldSearchRefAnchor() const {\n    return search_ref_anchor_strategy_ == SearchRefAnchorStrategy::kToStart ||\n           search_ref_anchor_strategy_ == SearchRefAnchorStrategy::kToEnd;\n  }\n\n  // Getter\n  ListAdapter* list_adapter() const { return list_adapter_.get(); }\n  ListLayoutManager* list_layout_manager() const {\n    return list_layout_manager_.get();\n  }\n  ListEventManager* list_event_manager() const {\n    return list_event_manager_.get();\n  }\n  ListChildrenHelper* list_children_helper() const {\n    return list_children_helper_.get();\n  }\n  ListAnimationManager* list_animation_manager() const {\n    return list_animation_manager_.get();\n  }\n  bool IsRTL() const { return list_delegate_->IsRTL(); }\n  int intercept_depth() const { return intercept_depth_; }\n  ElementDelegate* list_delegate() { return list_delegate_; }\n  int layout_id() const { return layout_id_; }\n  bool sticky_enabled() const { return sticky_enabled_; }\n  bool recycle_sticky_item() const { return recycle_sticky_item_; }\n  float sticky_offset() const { return sticky_offset_; }\n  bool should_request_state_restore() const {\n    return should_request_state_restore_;\n  }\n  bool enable_batch_render() const { return enable_batch_render_; }\n  bool enable_insert_platform_view_operation() const {\n    return enable_insert_platform_view_operation_;\n  }\n  bool has_valid_diff() const { return has_valid_diff_; }\n  const std::shared_ptr<pub::PubValueFactory>& value_factory() {\n    return value_factory_;\n  }\n  bool need_preload_section_on_next_frame() const {\n    return need_preload_section_on_next_frame_;\n  }\n  bool should_flush_finish_layout() const {\n    return should_flush_finish_layout_;\n  }\n  SearchRefAnchorStrategy search_ref_anchor_strategy() const {\n    return search_ref_anchor_strategy_;\n  }\n\n protected:\n  // Currently, the list container does not copy any member variables and is an\n  // empty implementation.\n  ListContainerImpl(const ListContainerImpl& list_container_impl) = delete;\n\n private:\n  void UpdateListLayoutManager(LayoutType layout_type);\n  //  fml::RefPtr<lepus::CArray> GenerateVisibleItemInfo() const;\n\n private:\n  bool enable_dynamic_span_count_{true};\n  bool enable_insert_platform_view_operation_{false};\n  bool span_count_changed_{false};\n  bool batch_adapter_initialized_{false};\n  bool recycle_available_item_before_layout_{false};\n  bool sticky_enabled_{false};\n  bool recycle_sticky_item_{true};\n  SearchRefAnchorStrategy search_ref_anchor_strategy_{\n      SearchRefAnchorStrategy::kNone};\n  int sticky_buffer_count_{kInvalidItemCount};\n  float sticky_offset_{0.f};\n  int intercept_depth_{0};\n  bool should_flush_finish_layout_{false};\n  LayoutType layout_type_{LayoutType::kSingle};\n  ElementDelegate* list_delegate_{nullptr};\n  float physical_pixels_per_layout_unit_{1.f};\n  int initial_scroll_index_{kInvalidIndex};\n  InitialScrollIndexStatus initial_scroll_index_status_{\n      InitialScrollIndexStatus::kUnset};\n  std::unique_ptr<ListLayoutManager> list_layout_manager_;\n  std::unique_ptr<ListAdapter> list_adapter_;\n  std::unique_ptr<ListChildrenHelper> list_children_helper_;\n  std::unique_ptr<ListEventManager> list_event_manager_;\n  std::unique_ptr<ListAnimationManager> list_animation_manager_;\n  bool need_recycle_all_item_holders_before_layout_{false};\n  bool need_update_item_holders_{false};\n  int layout_id_{kInvalidIndex};\n  bool should_request_state_restore_{false};\n  bool has_valid_diff_{false};\n  bool update_animation_{false};\n  bool need_preload_section_on_next_frame_{false};\n  bool enable_batch_render_{false};\n  ListAdapterDiffResult animation_diff_result_{ListAdapterDiffResult::kNone};\n  std::shared_ptr<pub::PubValueFactory> value_factory_;\n};\n\nstd::unique_ptr<ContainerDelegate> CreateListContainerDelegate(\n    ElementDelegate* list_delegate,\n    const std::shared_ptr<pub::PubValueFactory>& value_factory);\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LIST_CONTAINER_IMPL_H_\n"
  },
  {
    "path": "core/list/decoupled_list_container_impl_unittest.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/list/decoupled_list_container_impl.h\"\n\n#include \"core/value_wrapper/value_impl_lepus.h\"\n#include \"testing/fiber_data_source.h\"\n#include \"testing/mock_list_element.h\"\n#include \"testing/radon_data_source.h\"\n#include \"testing/utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListContainerImplTest : public ::testing::Test {\n public:\n  ListContainerImplTest() = default;\n  ~ListContainerImplTest() override = default;\n\n  std::unique_ptr<MockListElement> mock_list_element_{nullptr};\n  std::unique_ptr<ListContainerImpl> list_container_impl_{nullptr};\n  std::shared_ptr<pub::PubValueFactoryDefault> value_factory_{nullptr};\n  ListLayoutManager* list_layout_manager_{nullptr};\n  ListAdapter* list_adapter_{nullptr};\n  ListEventManager* list_event_manager_{nullptr};\n\n  void SetUp() override {\n    value_factory_ = std::make_shared<pub::PubValueFactoryDefault>();\n    mock_list_element_ = std::make_unique<MockListElement>();\n    list_container_impl_ = std::make_unique<ListContainerImpl>(\n        mock_list_element_.get(), value_factory_);\n    list_layout_manager_ = list_container_impl_->list_layout_manager();\n    list_adapter_ = list_container_impl_->list_adapter();\n    list_event_manager_ = list_container_impl_->list_event_manager();\n  }\n};\n\nTEST_F(ListContainerImplTest, Constructor) {\n  EXPECT_EQ(list_container_impl_->list_delegate(), mock_list_element_.get());\n  EXPECT_TRUE(\n      base::FloatsEqual(list_container_impl_->physical_pixels_per_layout_unit_,\n                        kDefaultPhysicalPixelsPerLayoutUnit));\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(CustomListName) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(CustomListName, String,\n                                   kPropValueListContainer);\n  EXPECT_CALL(*mock_list_element_, UpdateListLayoutNodeAttribute()).Times(1);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(VerticalOrientation) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(VerticalOrientation, Bool, true);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_layout_manager_->orientation(), Orientation::kVertical);\n  EXPECT_TRUE(list_layout_manager_->list_orientation_helper_->IsVertical());\n  value = value_factory_->CreateBool(false);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_layout_manager_->orientation(), Orientation::kHorizontal);\n  EXPECT_FALSE(list_layout_manager_->list_orientation_helper_->IsVertical());\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ScrollOrientation) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(ScrollOrientation, String,\n                                   kPropValueScrollOrientationVertical);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_layout_manager_->orientation(), Orientation::kVertical);\n  EXPECT_TRUE(list_layout_manager_->list_orientation_helper_->IsVertical());\n  value = value_factory_->CreateString(kPropValueScrollOrientationHorizontal);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_layout_manager_->orientation(), Orientation::kHorizontal);\n  EXPECT_FALSE(list_layout_manager_->list_orientation_helper_->IsVertical());\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(EnableDynamicSpanCount) {\n  EXPECT_TRUE(list_container_impl_->enable_dynamic_span_count_);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(EnableDynamicSpanCount, Bool, false);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_FALSE(list_container_impl_->enable_dynamic_span_count_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(SpanCount) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(SpanCount, Number, 2);\n  EXPECT_CALL(*mock_list_element_, MarkListElementLayoutDirty()).Times(1);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_layout_manager_->span_count_, 2);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ColumnCount) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(ColumnCount, Number, 2);\n  EXPECT_CALL(*mock_list_element_, MarkListElementLayoutDirty()).Times(1);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_layout_manager_->span_count_, 2);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(AnchorPriority) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(AnchorPriority, String,\n                                   kPropValueAnchorPriorityFromBegin);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(\n      list_layout_manager_->list_anchor_manager_->anchor_priority_from_begin_);\n  value = value_factory_->CreateString(kPropValueAnchorPriorityFromEnd);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_FALSE(\n      list_layout_manager_->list_anchor_manager_->anchor_priority_from_begin_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(AnchorAlign) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(AnchorAlign, String,\n                                   kPropValueAnchorAlignToBottom);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(\n      list_layout_manager_->list_anchor_manager_->anchor_align_to_bottom_);\n  value = value_factory_->CreateString(kPropValueAnchorAlignToTop);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_FALSE(\n      list_layout_manager_->list_anchor_manager_->anchor_align_to_bottom_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(AnchorVisibility) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(AnchorVisibility, String,\n                                   kPropValueAnchorVisibilityHide);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_layout_manager_->list_anchor_manager_->anchor_visibility_,\n            AnchorVisibility::kAnchorVisibilityHide);\n  value = value_factory_->CreateString(kPropValueAnchorVisibilityShow);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_layout_manager_->list_anchor_manager_->anchor_visibility_,\n            AnchorVisibility::kAnchorVisibilityShow);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(RadonListPlatformInfo) {\n  EXPECT_FALSE(list_container_impl_->has_valid_diff_);\n  EXPECT_FALSE(list_container_impl_->need_preload_section_on_next_frame_);\n  EXPECT_FALSE(list_container_impl_->need_update_item_holders_);\n  testing::RadonDataSource radon_data_source{\n      .item_keys_ = {\"A_0\", \"B_1\", \"C_2\", \"D_3\", \"E_4\", \"F_5\", \"G_6\", \"H_7\",\n                     \"I_8\"},\n      .insertion_ = {0, 1, 2, 3, 4, 5, 6, 7, 8},\n      .estimated_height_pxs_ = {100, 100, 100, 100, 100, 100, 100, 100, 100},\n  };\n  LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n      RadonListPlatformInfo,\n      lepus::Value(radon_data_source.GenerateDataSource()));\n  EXPECT_CALL(*mock_list_element_, MarkListElementLayoutDirty()).Times(1);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, value));\n  EXPECT_TRUE(list_container_impl_->has_valid_diff_);\n  EXPECT_TRUE(list_container_impl_->need_preload_section_on_next_frame_);\n  EXPECT_TRUE(list_container_impl_->need_update_item_holders_);\n  EXPECT_EQ(list_container_impl_->GetDataCount(),\n            radon_data_source.GetItemCount());\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(FiberUpdateListInfo) {\n  EXPECT_FALSE(list_container_impl_->has_valid_diff_);\n  EXPECT_FALSE(list_container_impl_->need_preload_section_on_next_frame_);\n  EXPECT_FALSE(list_container_impl_->need_update_item_holders_);\n  testing::InsertAction insert_action{\n      .insert_ops_ = {\n          {.position_ = 0, \"A_0\", 100, false, false, false, false},\n          {.position_ = 1, \"B_1\", 100, false, false, false, false},\n          {.position_ = 2, \"C_2\", 100, false, false, false, false},\n          {.position_ = 3, \"D_3\", 100, false, false, false, false},\n          {.position_ = 4, \"E_4\", 100, false, false, false, false},\n          {.position_ = 5, \"F_5\", 100, false, false, false, false},\n          {.position_ = 6, \"G_6\", 100, false, false, false, false},\n          {.position_ = 7, \"H_7\", 100, false, false, false, false},\n          {.position_ = 8, \"I_8\", 100, false, false, false, false},\n          {.position_ = 9, \"J_9\", 100, false, false, false, false},\n      }};\n  testing::FiberDataSource fiber_data_source{\n      .insert_action_ = insert_action,\n  };\n  LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n      FiberUpdateListInfo, lepus::Value(fiber_data_source.Resolve()));\n  EXPECT_CALL(*mock_list_element_, MarkListElementLayoutDirty()).Times(1);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, value));\n  EXPECT_TRUE(list_container_impl_->has_valid_diff_);\n  EXPECT_TRUE(list_container_impl_->need_preload_section_on_next_frame_);\n  EXPECT_TRUE(list_container_impl_->need_update_item_holders_);\n  EXPECT_EQ(list_container_impl_->GetDataCount(), 10);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(UpdateAnimation) {\n  EXPECT_FALSE(list_container_impl_->update_animation_);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(UpdateAnimation, String,\n                                   kPropValueUpdateAnimationDefault);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(list_container_impl_->update_animation_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ListType) {\n  EXPECT_EQ(list_container_impl_->layout_type_, LayoutType::kSingle);\n  EXPECT_CALL(*mock_list_element_, MarkListElementLayoutDirty()).Times(3);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(ListType, String, kPropValueListTypeSingle);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_container_impl_->layout_type_, LayoutType::kSingle);\n  value = value_factory_->CreateString(kPropValueListTypeFlow);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_container_impl_->layout_type_, LayoutType::kFlow);\n  value = value_factory_->CreateString(kPropValueListTypeWaterFall);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_container_impl_->layout_type_, LayoutType::kWaterFall);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(InitialScrollIndex) {\n  EXPECT_EQ(list_container_impl_->initial_scroll_index_, kInvalidIndex);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(InitialScrollIndex, Number, 1);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_container_impl_->initial_scroll_index_, 1);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(UpperThresholdItemCount) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(UpperThresholdItemCount, Number, 1);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_event_manager_->upper_threshold_item_count_, 1);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(LowerThresholdItemCount) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(LowerThresholdItemCount, Number, 1);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_event_manager_->lower_threshold_item_count_, 1);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(NeedLayoutCompleteInfo) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(NeedLayoutCompleteInfo, Bool, true);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(list_event_manager_->need_layout_complete_info_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(LayoutId) {\n  EXPECT_EQ(list_container_impl_->layout_id_, kInvalidIndex);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(LayoutId, Number, 0);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_container_impl_->layout_id_, 0);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ScrollEventThrottle) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(ScrollEventThrottle, Number, 16);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_event_manager_->scroll_event_throttle_ms_, 16);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(NeedsVisibleCells) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(NeedsVisibleCells, Bool, true);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(list_event_manager_->need_visible_cell_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(NeedVisibleItemInfo) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(NeedVisibleItemInfo, Bool, true);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(list_event_manager_->need_visible_cell_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ShouldRequestStateRestore) {\n  EXPECT_FALSE(list_container_impl_->should_request_state_restore_);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(ShouldRequestStateRestore, Bool, true);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(list_container_impl_->should_request_state_restore_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(StickyOffset) {\n  EXPECT_EQ(list_container_impl_->sticky_offset_, 0.f);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(StickyOffset, Number, 100.f);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_container_impl_->sticky_offset_, 100.f);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(Sticky) {\n  EXPECT_FALSE(list_container_impl_->sticky_enabled_);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(Sticky, Bool, true);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(list_container_impl_->sticky_enabled_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ExperimentalRecycleStickyItem) {\n  EXPECT_TRUE(list_container_impl_->recycle_sticky_item_);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(ExperimentalRecycleStickyItem, Bool, false);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_FALSE(list_container_impl_->recycle_sticky_item_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(StickyBufferCount) {\n  EXPECT_EQ(list_container_impl_->sticky_buffer_count_, kInvalidItemCount);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(StickyBufferCount, Number, 10);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_container_impl_->sticky_buffer_count_, 10);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(EnablePreloadSection) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(EnablePreloadSection, Bool, true);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(list_layout_manager_->enable_preload_section_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(PreloadBufferCount) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(PreloadBufferCount, Number, 10);\n  EXPECT_CALL(*mock_list_element_, MarkListElementLayoutDirty()).Times(1);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_layout_manager_->preload_buffer_count_, 10);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(EnableInsertPlatformViewOperation) {\n  EXPECT_FALSE(list_container_impl_->enable_insert_platform_view_operation_);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(EnableInsertPlatformViewOperation, Bool,\n                                   true);\n  EXPECT_TRUE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(list_container_impl_->enable_insert_platform_view_operation_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ExperimentalBatchRenderStrategy) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(\n      ExperimentalBatchRenderStrategy, Number,\n      static_cast<int>(\n          BatchRenderStrategy::kAsyncResolvePropertyAndElementTree));\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ListDebugInfoLevel) {\n  LIST_CONTAINER_DEFINE_PROP_VALUE(\n      ListDebugInfoLevel, Number,\n      static_cast<int>(ListDebugInfoLevel::kListDebugInfoLevelInfo));\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_event_manager_->debug_info_level_,\n            ListDebugInfoLevel::kListDebugInfoLevelInfo);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ExperimentalRecycleAvailableItemBeforeLayout) {\n  EXPECT_FALSE(list_container_impl_->recycle_available_item_before_layout_);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(ExperimentalRecycleAvailableItemBeforeLayout,\n                                   Bool, true);\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_TRUE(list_container_impl_->recycle_available_item_before_layout_);\n}\n\nTEST_LIST_CONTAINER_RESOLVE_PROP(ExperimentalSearchRefAnchorStrategy) {\n  EXPECT_EQ(list_container_impl_->search_ref_anchor_strategy_,\n            SearchRefAnchorStrategy::kNone);\n  LIST_CONTAINER_DEFINE_PROP_VALUE(\n      ExperimentalSearchRefAnchorStrategy, Number,\n      static_cast<int>(SearchRefAnchorStrategy::kToEnd));\n  EXPECT_FALSE(list_container_impl_->ResolveAttribute(*key, *value));\n  EXPECT_EQ(list_container_impl_->search_ref_anchor_strategy_,\n            SearchRefAnchorStrategy::kToEnd);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_event_manager.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_list_event_manager.h\"\n\n#include <algorithm>\n#include <utility>\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/list/decoupled_list_types.h\"\n\nnamespace lynx {\nnamespace list {\n\nListEventManager::ListEventManager(ListContainerImpl* list_container_impl)\n    : list_container_(list_container_impl) {\n  if (!list_container_) {\n    DLIST_LOGE(\n        \"ListEventManager::ListEventManager: list_container_ is nullptr\");\n  }\n}\n\nvoid ListEventManager::SendLayoutCompleteEvent() {\n  if (!list_container_ || !list_container_->value_factory() ||\n      !list_container_->list_delegate()->HasBoundEvent(kEventLayoutComplete)) {\n    return;\n  }\n  CreateLayoutCompleteInfoIfNeeded();\n  if (layout_complete_info_) {\n    // Move and clear layout_complete_info_\n    std::unique_ptr<pub::Value> layout_complete_info =\n        std::move(layout_complete_info_);\n    // push layout id\n    layout_complete_info->PushInt32ToMap(kLayoutInfoLayoutId,\n                                         list_container_->layout_id());\n    // push scroll info if needed\n    std::unique_ptr<pub::Value> scroll_info;\n    if (need_layout_complete_info_ &&\n        (scroll_info = GenerateScrollInfo(0.f, 0.f))) {\n      layout_complete_info->PushValueToMap(kLayoutInfoScrollInfo, *scroll_info);\n    }\n    list_container_->ResetLayoutID();\n    list_container_->list_delegate()->SendCustomEvent(\n        kEventLayoutComplete, kEventParamDetail,\n        std::move(layout_complete_info));\n  }\n}\n\nvoid ListEventManager::RecordVisibleItemIfNeeded(bool is_layout_before) {\n  if (!need_layout_complete_info_ || !list_container_->value_factory()) {\n    return;\n  }\n  CreateLayoutCompleteInfoIfNeeded();\n  if (layout_complete_info_) {\n    // push eventUnit\n    layout_complete_info_->PushStringToMap(kLayoutInfoEventUnit,\n                                           kLayoutInfoEventUnitValuePx);\n    // push visibleItemBeforeUpdate or visibleItemAfterUpdate\n    std::unique_ptr<pub::Value> visible_cells_info;\n    if ((visible_cells_info = GenerateVisibleCellsInfo(0.f, 0.f, false))) {\n      layout_complete_info_->PushValueToMap(\n          is_layout_before ? kLayoutInfoVisibleItemBeforeUpdate\n                           : kLayoutInfoVisibleItemAfterUpdate,\n          *visible_cells_info);\n    }\n  }\n}\n\nvoid ListEventManager::RecordDiffResultIfNeeded() {\n  if (!need_layout_complete_info_ || !list_container_->value_factory()) {\n    return;\n  }\n  CreateLayoutCompleteInfoIfNeeded();\n  if (layout_complete_info_) {\n    std::unique_ptr<pub::Value> diff_result;\n    if ((diff_result = list_container_->list_adapter()->GenerateDiffResult())) {\n      layout_complete_info_->PushValueToMap(kLayoutInfoDiffResult,\n                                            *diff_result);\n    }\n  }\n}\n\nvoid ListEventManager::SendScrollEvent(float distance,\n                                       EventSource event_source) {\n  if (base::IsZero(distance)) {\n    return;\n  }\n  // sendScrollEvent\n  auto now = std::chrono::steady_clock::now();\n  auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(\n                      now - last_scroll_event_time_)\n                      .count();\n  if (duration > scroll_event_throttle_ms_) {\n    SendCustomScrollEvent(kEventScroll, distance, event_source);\n    last_scroll_event_time_ = now;\n  }\n}\n\nvoid ListEventManager::DetectScrollToThresholdAndSend(\n    float distance, float original_offset, list::EventSource event_source) {\n  if (!list_container_) {\n    return;\n  }\n  ListLayoutManager* list_layout_manager =\n      list_container_->list_layout_manager();\n  ListChildrenHelper* list_children_helper =\n      list_container_->list_children_helper();\n  bool is_upper = false;\n  bool is_lower = false;\n  bool is_lower_edge = false;\n  bool is_upper_edge = false;\n\n  // calculate the firstItemIndex & lastItemIndex\n  int first_index = INT_MAX;\n  int end_index = INT_MIN;\n  auto item_holder_list = list_children_helper->on_screen_children();\n  for (auto it = item_holder_list.begin(); it != item_holder_list.end(); ++it) {\n    auto item_holder = *it;\n    if (item_holder) {\n      first_index = std::min(item_holder->index(), first_index);\n      end_index = std::max(item_holder->index(), end_index);\n    }\n  }\n  float content_offset = list_layout_manager->content_offset();\n  float content_size = list_layout_manager->content_size();\n  float list_size = list_layout_manager->main_axis_size();\n\n  // sendUpperScrollEvent\n  if (first_index < upper_threshold_item_count_) {\n    is_upper = true;\n  }\n  if (upper_threshold_item_count_ == 0 &&\n      base::FloatsLargerOrEqual(0, content_offset)) {\n    // come to the top edge\n    is_upper = true;\n  }\n  if (base::FloatsLarger(list_size, content_size)) {\n    is_upper_edge = true;\n    is_lower_edge = true;\n  } else {\n    if (base::FloatsLargerOrEqual(content_offset + list_size, content_size)) {\n      is_lower_edge = true;\n    }\n    if (base::FloatsLargerOrEqual(0, content_offset)) {\n      is_upper_edge = true;\n    }\n  }\n\n  // sendLowerScrollEvent\n  int bottom_border_item_index =\n      list_children_helper->GetChildCount() - lower_threshold_item_count_ - 1;\n  if (end_index > bottom_border_item_index) {\n    is_lower = true;\n  }\n  if (lower_threshold_item_count_ == 0 &&\n      base::FloatsLargerOrEqual(content_offset + list_size, content_size)) {\n    // come to the bottom edge\n    is_lower = true;\n  }\n\n  // Special case. The content can not fill the list\n  if (base::FloatsLargerOrEqual(list_size, content_size)) {\n    is_lower = true;\n    is_upper = true;\n  }\n\n  // Send scroll to upper/lower event.\n  if (event_source == EventSource::kDiff ||\n      event_source == EventSource::kLayout) {\n    // 1. Force sending lower/upper event after diff or layout\n    if (is_upper) {\n      SendCustomScrollEvent(kEventScrollToUpper, distance, event_source);\n    }\n    if (is_lower) {\n      SendCustomScrollEvent(kEventScrollToLower, distance, event_source);\n    }\n  } else if (event_source == EventSource::kScroll) {\n    // 2. Handle event from scroll.\n    ScrollPositionState previous_state = previous_scroll_pos_state_;\n    if (is_upper && (previous_state != ScrollPositionState::kUpper &&\n                     previous_state != ScrollPositionState::kBothEdge)) {\n      // Update previous_status and valid_diff flag before sending event to\n      // avoid reenter in worklet.\n      UpdatePreviousScrollState(is_lower, is_upper);\n      SendCustomScrollEvent(list::kEventScrollToUpper, distance, event_source);\n    }\n    if (is_lower && (previous_state != ScrollPositionState::kLower &&\n                     previous_state != ScrollPositionState::kBothEdge)) {\n      // Update previous_status and valid_diff flag before sending event to\n      // avoid reenter in worklet.\n      UpdatePreviousScrollState(is_lower, is_upper);\n      SendCustomScrollEvent(list::kEventScrollToLower, distance, event_source);\n    }\n    UpdatePreviousScrollState(is_lower, is_upper);\n  }\n\n  // Send scroll to upper/lower edge event.\n  if (is_upper_edge &&\n      NotAtBouncesArea(original_offset, content_size, list_size)) {\n    SendCustomScrollEvent(kEventScrollToUpperEdge, 0, event_source);\n  }\n  if (is_lower_edge &&\n      NotAtBouncesArea(original_offset, content_size, list_size)) {\n    SendCustomScrollEvent(kEventScrollToLowerEdge, 0, event_source);\n  }\n  if (!is_lower_edge && !is_upper_edge) {\n    SendCustomScrollEvent(kEventScrollToNormalState, 0, event_source);\n  }\n}\n\nbool ListEventManager::NotAtBouncesArea(float original_offset,\n                                        float content_size, float list_size) {\n  // original_offset is smaller than 0\n  if (base::FloatsLarger(0, original_offset)) {\n    return false;\n  }\n  // list can not be scrolled and content_offset is not zero\n  if (base::FloatsLargerOrEqual(list_size, content_size) &&\n      base::FloatsLarger(original_offset, 0)) {\n    return false;\n  }\n  // list is scrollable and original_offset is beyond end edge\n  if (base::FloatsLarger(content_size, list_size) &&\n      base::FloatsLarger(original_offset + list_size, content_size)) {\n    return false;\n  }\n  return true;\n}\n\nvoid ListEventManager::UpdatePreviousScrollState(bool is_lower, bool is_upper) {\n  if (is_lower && is_upper) {\n    previous_scroll_pos_state_ = ScrollPositionState::kBothEdge;\n  } else if (is_lower) {\n    previous_scroll_pos_state_ = ScrollPositionState::kLower;\n  } else if (is_upper) {\n    previous_scroll_pos_state_ = ScrollPositionState::kUpper;\n  } else {\n    previous_scroll_pos_state_ = ScrollPositionState::kMiddle;\n  }\n}\n\nvoid ListEventManager::SendCustomScrollEvent(const std::string& event_name,\n                                             float distance,\n                                             EventSource event_source) {\n  if (!list_container_ || !list_container_->value_factory() ||\n      !list_container_->list_delegate()->HasBoundEvent(event_name)) {\n    return;\n  }\n  ListLayoutManager* list_layout_manager =\n      list_container_->list_layout_manager();\n  bool is_vertical = list_layout_manager->CanScrollVertically();\n  float content_offset = list_layout_manager->content_offset();\n  float scroll_left = !is_vertical ? content_offset : 0.f;\n  float scroll_top = is_vertical ? content_offset : 0.f;\n  float dx = !is_vertical ? distance : 0.f;\n  float dy = is_vertical ? distance : 0.f;\n  float layouts_unit_per_px =\n      list_container_->list_delegate()->GetLayoutsUnitPerPx();\n  if (base::FloatsLarger(layouts_unit_per_px, 0.f)) {\n    std::unique_ptr<pub::Value> scroll_info = GenerateScrollInfo(dx, dy);\n    if (scroll_info) {\n      // push eventSource\n      scroll_info->PushInt32ToMap(kScrollInfoEventSource,\n                                  static_cast<int>(event_source));\n      std::unique_ptr<pub::Value> visible_cells_info;\n      // push attachedCells if needed.\n      if (need_visible_cell_ && (visible_cells_info = GenerateVisibleCellsInfo(\n                                     scroll_left, scroll_top, true))) {\n        scroll_info->PushValueToMap(kScrollInfoAttachedCells,\n                                    *visible_cells_info);\n      }\n      list_container_->list_delegate()->SendCustomEvent(\n          event_name, kEventParamDetail, std::move(scroll_info));\n    }\n  }\n}\n\nvoid ListEventManager::SendDiffDebugEventIfNeeded() {\n  const auto& value_factory = list_container_->value_factory();\n  if (value_factory &&\n      ShouldGenerateDebugInfo(ListDebugInfoLevel::kListDebugInfoLevelInfo)) {\n    std::unique_ptr<pub::Value> debug_info;\n    std::unique_ptr<pub::Value> diff_result;\n    if ((debug_info = value_factory->CreateMap()) &&\n        (diff_result = list_container_->list_adapter()->GenerateDiffResult())) {\n      // push diff result\n      debug_info->PushValueToMap(kDebugInfoDiffResult, *diff_result);\n      list_container_->list_delegate()->SendCustomEvent(\n          kEventListDebugInfo, kEventParamDetail, std::move(debug_info));\n    }\n  }\n}\n\nvoid ListEventManager::SendAnchorDebugInfoIfNeeded(\n    const ListAnchorManager::AnchorInfo& anchor_info) {\n  const auto& value_factory = list_container_->value_factory();\n  if (value_factory &&\n      ShouldGenerateDebugInfo(ListDebugInfoLevel::kListDebugInfoLevelInfo)) {\n    std::unique_ptr<pub::Value> debug_info;\n    std::unique_ptr<pub::Value> anchor_info_map;\n    if ((debug_info = value_factory->CreateMap()) &&\n        (anchor_info_map = value_factory->CreateMap())) {\n      if (!anchor_info.valid_) {\n        anchor_info_map->PushInt32ToMap(kDebugInfoAnchorIndex, kInvalidIndex);\n      } else {\n        anchor_info_map->PushInt32ToMap(kDebugInfoAnchorIndex,\n                                        anchor_info.index_);\n        anchor_info_map->PushDoubleToMap(kDebugInfoAnchorStartOffset,\n                                         anchor_info.start_offset_);\n        anchor_info_map->PushDoubleToMap(kDebugInfoAnchorStartAlignmentDelta,\n                                         anchor_info.start_alignment_delta_);\n        anchor_info_map->PushBoolToMap(\n            kDebugInfoAnchorDirty,\n            list_container_->list_adapter()->IsDirty(anchor_info.item_holder_));\n        anchor_info_map->PushBoolToMap(\n            kDebugInfoAnchorBinding, list_container_->list_adapter()->IsBinding(\n                                         anchor_info.item_holder_));\n      }\n      debug_info->PushValueToMap(kDebugInfoAnchorInfo, *anchor_info_map);\n      list_container_->list_delegate()->SendCustomEvent(\n          kEventListDebugInfo, kEventParamDetail, std::move(debug_info));\n    }\n  }\n}\n\nvoid ListEventManager::SendExposureEvent(const std::string& event_name,\n                                         const ItemHolder* item_holder) {\n  ItemElementDelegate* list_item_delegate =\n      list_container_->list_adapter()->GetItemElementDelegate(item_holder);\n  if (!list_item_delegate || !list_item_delegate->HasBoundEvent(event_name)) {\n    return;\n  }\n  std::unique_ptr<pub::Value> exposure_info =\n      GenerateNodeExposureInfo(item_holder);\n  if (exposure_info) {\n    list_item_delegate->SendCustomEvent(event_name, kEventParamDetail,\n                                        std::move(exposure_info));\n  }\n}\n\nstd::unique_ptr<pub::Value> ListEventManager::GenerateNodeExposureInfo(\n    const ItemHolder* item_holder) const {\n  std::unique_ptr<pub::Value> exposure_info;\n  const auto& value_factory = list_container_->value_factory();\n  if (value_factory && (exposure_info = value_factory->CreateMap())) {\n    // push index\n    exposure_info->PushInt32ToMap(kCellInfoIndex, item_holder->index());\n    // push key\n    exposure_info->PushStringToMap(kCellInfoItemKey, item_holder->item_key());\n  }\n  return exposure_info;\n}\n\nstd::unique_ptr<pub::Value> ListEventManager::GenerateScrollInfo(\n    float deltaX, float deltaY) const {\n  std::unique_ptr<pub::Value> scroll_info;\n  const auto& value_factory = list_container_->value_factory();\n  if (value_factory) {\n    scroll_info = value_factory->CreateMap();\n    float layouts_unit_per_px =\n        list_container_->list_delegate()->GetLayoutsUnitPerPx();\n    if (scroll_info && base::FloatsLarger(layouts_unit_per_px, 0.f)) {\n      ListLayoutManager* list_layout_manager =\n          list_container_->list_layout_manager();\n      bool is_vertical = list_layout_manager->CanScrollVertically();\n      float content_offset =\n          list_layout_manager->content_offset() / layouts_unit_per_px;\n      float content_size =\n          list_layout_manager->content_size() / layouts_unit_per_px;\n      float list_width =\n          list_container_->list_delegate()->GetWidth() / layouts_unit_per_px;\n      float list_height =\n          list_container_->list_delegate()->GetHeight() / layouts_unit_per_px;\n\n      scroll_info->PushDoubleToMap(kScrollInfoScrollLeft,\n                                   is_vertical ? 0.f : content_offset);\n      scroll_info->PushDoubleToMap(kScrollInfoScrollTop,\n                                   !is_vertical ? 0.f : content_offset);\n      scroll_info->PushDoubleToMap(kScrollInfoScrollWidth,\n                                   is_vertical ? list_width : content_size);\n      scroll_info->PushDoubleToMap(kScrollInfoScrollHeight,\n                                   !is_vertical ? list_height : content_size);\n      scroll_info->PushDoubleToMap(kScrollInfoListWidth, list_width);\n      scroll_info->PushDoubleToMap(kScrollInfoListHeight, list_height);\n      scroll_info->PushDoubleToMap(kScrollInfoDeltaX,\n                                   deltaX / layouts_unit_per_px);\n      scroll_info->PushDoubleToMap(kScrollInfoDeltaY,\n                                   deltaY / layouts_unit_per_px);\n    }\n  }\n  return scroll_info;\n}\n\n/**\n * For scroll event:\n *   (1) left / top / right / bottom Relative to the list, in px\n *   (2) position is legacy.\n *   [{\n *     \"id\": string,\n *     \"itemKey\": string,\n *     \"index\": number,\n *     \"position\": number,\n *     \"left\": number,\n *     \"top\": number,\n *     \"right\": number,\n *     \"bottom\": number,\n *   }]\n *\n * For layout complete info:\n *   (1) originX and originY is the position of child node relative to the\n * entire scroll area.\n *   [{\n *     \"height\": number;\n *     \"width\": number;\n *     \"itemKey\": string;\n *     \"isBinding\": boolean;\n *     \"originX\": number;\n *     \"originY\": number;\n *     \"updated\": boolean;\n *    }]\n */\nstd::unique_ptr<pub::Value> ListEventManager::GenerateVisibleCellsInfo(\n    float scroll_left, float scroll_top, bool for_scroll_info) const {\n  std::unique_ptr<pub::Value> visible_cells_info;\n  const auto& value_factory = list_container_->value_factory();\n  if (value_factory) {\n    visible_cells_info = value_factory->CreateArray();\n    float layouts_unit_per_px =\n        list_container_->list_delegate()->GetLayoutsUnitPerPx();\n    if (visible_cells_info && base::FloatsLarger(layouts_unit_per_px, 0.f)) {\n      ListAdapter* list_adapter = list_container_->list_adapter();\n      ListChildrenHelper* list_children_helper =\n          list_container_->list_children_helper();\n      list_children_helper->ForEachChild(\n          list_children_helper->on_screen_children(),\n          [list_adapter, layouts_unit_per_px, scroll_left, scroll_top,\n           for_scroll_info, &visible_cells_info,\n           &value_factory](ItemHolder* item_holder) {\n            ItemElementDelegate* list_item_delegate =\n                list_adapter->GetItemElementDelegate(item_holder);\n            if (list_item_delegate) {\n              auto item_info = value_factory->CreateMap();\n              if (item_info) {\n                item_info->PushStringToMap(kCellInfoItemKey,\n                                           item_holder->item_key());\n                item_info->PushInt32ToMap(kCellInfoIndex, item_holder->index());\n                float left = item_holder->left();\n                float top = item_holder->top();\n                if (for_scroll_info) {\n                  // scroll info\n                  item_info->PushStringToMap(\n                      kCellInfoIdSelector, list_item_delegate->GetIdSelector());\n                  item_info->PushDoubleToMap(\n                      kCellInfoTop, (top - scroll_top) / layouts_unit_per_px);\n                  item_info->PushDoubleToMap(\n                      kCellInfoBottom,\n                      (top + item_holder->height()) / layouts_unit_per_px);\n                  item_info->PushDoubleToMap(\n                      kCellInfoLeft,\n                      (left - scroll_left) / layouts_unit_per_px);\n                  item_info->PushDoubleToMap(\n                      kCellInfoRight,\n                      (left + item_holder->width()) / layouts_unit_per_px);\n                  // for legacy API\n                  item_info->PushInt32ToMap(kCellInfoPosition,\n                                            item_holder->index());\n                } else {\n                  // layout info\n                  item_info->PushDoubleToMap(kCellInfoOriginX,\n                                             left / layouts_unit_per_px);\n                  item_info->PushDoubleToMap(kCellInfoOriginY,\n                                             top / layouts_unit_per_px);\n                  item_info->PushDoubleToMap(\n                      kCellInfoWidth,\n                      item_holder->width() / layouts_unit_per_px);\n                  item_info->PushDoubleToMap(\n                      kCellInfoHeight,\n                      item_holder->height() / layouts_unit_per_px);\n                  item_info->PushBoolToMap(\n                      kCellInfoIsBinding, list_adapter->IsBinding(item_holder));\n                  item_info->PushBoolToMap(\n                      kCellInfoUpdated, list_adapter->IsUpdated(item_holder));\n                }\n                visible_cells_info->PushValueToArray(*item_info);\n              }\n            }\n            return false;\n          });\n    }\n  }\n  return visible_cells_info;\n}\n\nvoid ListEventManager::CreateLayoutCompleteInfoIfNeeded() {\n  if (!layout_complete_info_ && list_container_->value_factory()) {\n    layout_complete_info_ = list_container_->value_factory()->CreateMap();\n  }\n}\n\nbool ListEventManager::ShouldGenerateDebugInfo(\n    ListDebugInfoLevel target_level) const {\n  ElementDelegate* list_delegate = list_container_->list_delegate();\n  return list_delegate->IsInDebugMode() &&\n         list_delegate->HasBoundEvent(kEventListDebugInfo) &&\n         debug_info_level_ >= target_level;\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_event_manager.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LIST_EVENT_MANAGER_H_\n#define CORE_LIST_DECOUPLED_LIST_EVENT_MANAGER_H_\n\n#include <chrono>\n#include <ctime>\n#include <memory>\n#include <string>\n#include <unordered_set>\n\n#include \"core/list/decoupled_list_anchor_manager.h\"\n#include \"core/list/decoupled_list_children_helper.h\"\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListContainerImpl;\n\nclass ListEventManager {\n public:\n  ListEventManager(ListContainerImpl* list_container_impl);\n\n  void SetVisibleCell(bool visible_cell) { need_visible_cell_ = visible_cell; }\n\n  void SetScrollEventThrottleMS(int scroll_event_throttle_ms) {\n    scroll_event_throttle_ms_ = scroll_event_throttle_ms;\n  }\n\n  void SetLowerThresholdItemCount(int lower_threshold_item_count) {\n    lower_threshold_item_count_ = lower_threshold_item_count;\n  }\n\n  void SetUpperThresholdItemCount(int upper_threshold_item_count) {\n    upper_threshold_item_count_ = upper_threshold_item_count;\n  }\n\n  void SetListDebugInfoLevel(ListDebugInfoLevel debug_info_level) {\n    debug_info_level_ = debug_info_level;\n  }\n\n  void SendLayoutCompleteEvent();\n\n  void SendScrollEvent(float distance, EventSource event_source);\n\n  void SendExposureEvent(const std::string& event_name,\n                         const ItemHolder* item_holder);\n\n  void DetectScrollToThresholdAndSend(float distance, float original_offset,\n                                      EventSource event_source);\n\n  void RecordVisibleItemIfNeeded(bool is_layout_before);\n\n  void RecordDiffResultIfNeeded();\n\n  void SendDiffDebugEventIfNeeded();\n\n  void SendAnchorDebugInfoIfNeeded(\n      const ListAnchorManager::AnchorInfo& anchor_info);\n\n  void SetNeedLayoutCompleteInfo(bool need_layout_complete_info) {\n    need_layout_complete_info_ = need_layout_complete_info;\n  }\n\n private:\n  void SendCustomScrollEvent(const std::string& event_name, float distance,\n                             EventSource event_source);\n\n  void CreateLayoutCompleteInfoIfNeeded();\n\n  std::unique_ptr<pub::Value> GenerateScrollInfo(float deltaX,\n                                                 float deltaY) const;\n\n  std::unique_ptr<pub::Value> GenerateVisibleCellsInfo(\n      float scroll_left, float scroll_top, bool for_scroll_info) const;\n\n  std::unique_ptr<pub::Value> GenerateNodeExposureInfo(\n      const ItemHolder* item_holder) const;\n\n  void UpdatePreviousScrollState(bool is_lower, bool is_upper);\n\n  bool NotAtBouncesArea(float content_offset, float content_size,\n                        float list_size);\n\n  bool ShouldGenerateDebugInfo(ListDebugInfoLevel target_level) const;\n\n private:\n  ListContainerImpl* list_container_{nullptr};\n  std::unique_ptr<pub::Value> layout_complete_info_;\n  bool need_visible_cell_{false};\n  bool need_layout_complete_info_{false};\n  int scroll_event_throttle_ms_{200};\n  int lower_threshold_item_count_{0};\n  int upper_threshold_item_count_{0};\n  ListDebugInfoLevel debug_info_level_{\n      ListDebugInfoLevel::kListDebugInfoLevelInfo};\n  std::chrono::time_point<std::chrono::steady_clock,\n                          std::chrono::steady_clock::duration>\n      last_scroll_event_time_ = std::chrono::steady_clock::now();\n  ScrollPositionState previous_scroll_pos_state_{ScrollPositionState::kMiddle};\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LIST_EVENT_MANAGER_H_\n"
  },
  {
    "path": "core/list/decoupled_list_event_manager_unittest.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/list/decoupled_list_event_manager.h\"\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/value_wrapper/value_impl_lepus.h\"\n#include \"testing/fiber_data_source.h\"\n#include \"testing/mock_list_element.h\"\n#include \"testing/mock_list_item_element.h\"\n#include \"testing/radon_data_source.h\"\n#include \"testing/utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace list {\n\nusing ::testing::_;\nusing ::testing::Return;\n\nclass ListEventManagerTest : public ::testing::Test {\n public:\n  ListEventManagerTest() = default;\n  ~ListEventManagerTest() override = default;\n\n  std::unique_ptr<MockListElement> mock_list_element_{nullptr};\n  std::unique_ptr<ListContainerImpl> list_container_impl_{nullptr};\n  std::shared_ptr<pub::PubValueFactoryDefault> value_factory_{nullptr};\n  ListLayoutManager* list_layout_manager_{nullptr};\n  ListEventManager* list_event_manager_{nullptr};\n  ListChildrenHelper* list_children_helper_{nullptr};\n  ListAdapter* list_adapter_{nullptr};\n\n  void SetUp() override {\n    value_factory_ = std::make_shared<pub::PubValueFactoryDefault>();\n    mock_list_element_ = std::make_unique<MockListElement>();\n    list_container_impl_ = std::make_unique<ListContainerImpl>(\n        mock_list_element_.get(), value_factory_);\n    list_layout_manager_ = list_container_impl_->list_layout_manager();\n    list_event_manager_ = list_container_impl_->list_event_manager();\n    list_children_helper_ = list_container_impl_->list_children_helper();\n    list_adapter_ = list_container_impl_->list_adapter();\n  }\n\n  void InitFiberDataSourceAndLayout(int layout_id,\n                                    bool need_layout_complete_info,\n                                    bool need_visible_item_info,\n                                    int scroll_event_throttle_ms = 16) {\n    testing::InsertAction insert_action{\n        .insert_ops_ = {\n            {.position_ = 0, \"A_0\", 100, false, false, false, false},\n            {.position_ = 1, \"B_1\", 100, false, false, false, false},\n            {.position_ = 2, \"C_2\", 100, false, false, false, false},\n            {.position_ = 3, \"D_3\", 100, false, false, false, false},\n            {.position_ = 4, \"E_4\", 100, false, false, false, false},\n            {.position_ = 5, \"F_5\", 100, false, false, false, false},\n            {.position_ = 6, \"G_6\", 100, false, false, false, false},\n            {.position_ = 7, \"H_7\", 100, false, false, false, false},\n            {.position_ = 8, \"I_8\", 100, false, false, false, false},\n            {.position_ = 9, \"J_9\", 100, false, false, false, false},\n        }};\n    testing::FiberDataSource fiber_data_source{\n        .insert_action_ = insert_action,\n    };\n    auto update_list_info_key =\n        value_factory_->CreateString(kPropFiberUpdateListInfo);\n    auto update_list_info_value =\n        pub::ValueImplLepus(lepus::Value(fiber_data_source.Resolve()));\n    list_container_impl_->ResolveAttribute(*update_list_info_key,\n                                           update_list_info_value);\n    auto layout_id_key = value_factory_->CreateString(kPropLayoutId);\n    auto layout_id_value = value_factory_->CreateNumber(layout_id);\n    list_container_impl_->ResolveAttribute(*layout_id_key, *layout_id_value);\n    auto need_layout_complete_info_key =\n        value_factory_->CreateString(kPropNeedLayoutCompleteInfo);\n    auto need_layout_complete_info_value =\n        value_factory_->CreateBool(need_layout_complete_info);\n    list_container_impl_->ResolveAttribute(*need_layout_complete_info_key,\n                                           *need_layout_complete_info_value);\n    auto need_visible_item_info_key =\n        value_factory_->CreateString(kPropNeedVisibleItemInfo);\n    auto need_visible_item_info_value =\n        value_factory_->CreateBool(need_visible_item_info);\n    list_container_impl_->ResolveAttribute(*need_visible_item_info_key,\n                                           *need_visible_item_info_value);\n    auto scroll_event_throttle_ms_key =\n        value_factory_->CreateString(kPropScrollEventThrottle);\n    auto scroll_event_throttle_ms_value =\n        value_factory_->CreateNumber(scroll_event_throttle_ms);\n    list_container_impl_->ResolveAttribute(*scroll_event_throttle_ms_key,\n                                           *scroll_event_throttle_ms_value);\n    list_container_impl_->PropsUpdateFinish();\n    list_layout_manager_->LayoutInvalidItemHolder(0);\n    list_layout_manager_->content_size_ =\n        list_layout_manager_->GetTargetContentSize();\n    list_children_helper_->UpdateOnScreenChildren(\n        list_layout_manager_->list_orientation_helper_.get(),\n        list_layout_manager_->content_offset_);\n    {\n      // Note: because all item's real size is equal to estimated size, so we\n      // can bind all visible item holders directly.\n      list_container_impl_->StartInterceptListElementUpdated();\n      int list_item_impl_id = mock_list_element_->GetImplId() + 1;\n      list_children_helper_->ForEachChild(\n          list_children_helper_->on_screen_children(),\n          [this, &list_item_impl_id](ItemHolder* item_holder) {\n            EXPECT_CALL(*mock_list_element_, ComponentAtIndex(_, _, _))\n                .WillOnce(Return(true));\n            list_adapter_->BindItemHolder(item_holder, item_holder->index());\n            auto pipeline = std::make_shared<tasm::PipelineOptions>();\n            pipeline->operation_id = item_holder->operation_id_;\n            const auto& item_key = item_holder->item_key();\n            mock_list_element_->AddListItemElement(\n                item_key,\n                std::make_unique<MockListItemElement>(list_item_impl_id++));\n            list_adapter_->OnFinishBindItemHolder(\n                mock_list_element_->GetListItemElement(item_key), pipeline);\n            return false;\n          });\n      list_container_impl_->StopInterceptListElementUpdated();\n    }\n  }\n\n  void CheckScrollInfo(const std::unique_ptr<pub::Value>& scroll_info_value,\n                       float scroll_left, float scroll_top, float scroll_width,\n                       float scroll_height, float list_width, float list_height,\n                       float delta_x, float delta_y) {\n    EXPECT_TRUE(scroll_info_value && scroll_info_value->IsMap());\n    auto scroll_left_value =\n        scroll_info_value->GetValueForKey(kScrollInfoScrollLeft);\n    EXPECT_TRUE(\n        scroll_left_value && scroll_left_value->IsNumber() &&\n        base::FloatsEqual(static_cast<double>(scroll_left_value->Number()),\n                          scroll_left));\n    auto scroll_top_value =\n        scroll_info_value->GetValueForKey(kScrollInfoScrollTop);\n    EXPECT_TRUE(\n        scroll_top_value && scroll_top_value->IsNumber() &&\n        base::FloatsEqual(static_cast<double>(scroll_top_value->Number()),\n                          scroll_top));\n    auto scroll_width_value =\n        scroll_info_value->GetValueForKey(kScrollInfoScrollWidth);\n    EXPECT_TRUE(\n        scroll_width_value && scroll_width_value->IsNumber() &&\n        base::FloatsEqual(static_cast<double>(scroll_width_value->Number()),\n                          scroll_width));\n    auto scroll_height_value =\n        scroll_info_value->GetValueForKey(kScrollInfoScrollHeight);\n    EXPECT_TRUE(\n        scroll_height_value && scroll_height_value->IsNumber() &&\n        base::FloatsEqual(static_cast<double>(scroll_height_value->Number()),\n                          scroll_height));\n    auto list_width_value =\n        scroll_info_value->GetValueForKey(kScrollInfoListWidth);\n    EXPECT_TRUE(\n        list_width_value && list_width_value->IsNumber() &&\n        base::FloatsEqual(static_cast<double>(list_width_value->Number()),\n                          list_width));\n    auto list_height_value =\n        scroll_info_value->GetValueForKey(kScrollInfoListHeight);\n    EXPECT_TRUE(\n        list_height_value && list_height_value->IsNumber() &&\n        base::FloatsEqual(static_cast<double>(list_height_value->Number()),\n                          list_height));\n    auto scroll_delta_x_value =\n        scroll_info_value->GetValueForKey(kScrollInfoDeltaX);\n    EXPECT_TRUE(\n        scroll_delta_x_value && scroll_delta_x_value->IsNumber() &&\n        base::FloatsEqual(static_cast<double>(scroll_delta_x_value->Number()),\n                          delta_x));\n    auto scroll_delta_y_value =\n        scroll_info_value->GetValueForKey(kScrollInfoDeltaY);\n    EXPECT_TRUE(\n        scroll_delta_y_value && scroll_delta_y_value->IsNumber() &&\n        base::FloatsEqual(static_cast<double>(scroll_delta_y_value->Number()),\n                          delta_y));\n  }\n\n  void CheckAllVisibleCellInfo(\n      const std::unique_ptr<pub::Value>& visible_cell_info, float scroll_left,\n      float scroll_top, bool for_scroll_info) {\n    EXPECT_TRUE(visible_cell_info && visible_cell_info->IsArray());\n    EXPECT_EQ(visible_cell_info->Length(),\n              list_children_helper_->on_screen_children_.size());\n    auto it = list_children_helper_->on_screen_children_.begin();\n    visible_cell_info->ForeachArray(\n        [this, scroll_left, scroll_top, for_scroll_info, &it](\n            int64_t index, const pub::Value& cell_info) {\n          ItemHolder* item_holder = *(it++);\n          CheckCellInfo(cell_info, item_holder, scroll_left, scroll_top,\n                        for_scroll_info);\n        });\n  }\n\n  void CheckCellInfo(const pub::Value& cell_info, ItemHolder* item_holder,\n                     float scroll_left, float scroll_top,\n                     bool for_scroll_info) {\n    EXPECT_TRUE(cell_info.IsMap());\n    auto index_value = cell_info.GetValueForKey(kCellInfoIndex);\n    EXPECT_TRUE(index_value && index_value->IsInt32() &&\n                index_value->Int32() == item_holder->index());\n    auto item_key_value = cell_info.GetValueForKey(kCellInfoItemKey);\n    EXPECT_TRUE(item_key_value && item_key_value->IsString() &&\n                item_key_value->str() == item_holder->item_key());\n    ItemElementDelegate* list_item_delegate =\n        list_adapter_->GetItemElementDelegate(item_holder);\n    float layouts_unit_per_px =\n        list_container_impl_->list_delegate()->GetLayoutsUnitPerPx();\n    if (for_scroll_info) {\n      auto id_value = cell_info.GetValueForKey(kCellInfoIdSelector);\n      EXPECT_TRUE(id_value && id_value->IsString() &&\n                  id_value->str() == list_item_delegate->GetIdSelector());\n      auto top_value = cell_info.GetValueForKey(kCellInfoTop);\n      EXPECT_TRUE(top_value && top_value->IsNumber() &&\n                  base::FloatsEqual(\n                      static_cast<double>(top_value->Number()),\n                      (item_holder->top() - scroll_top) / layouts_unit_per_px));\n      auto bottom_value = cell_info.GetValueForKey(kCellInfoBottom);\n      EXPECT_TRUE(\n          bottom_value && bottom_value->IsNumber() &&\n          base::FloatsEqual(static_cast<double>(bottom_value->Number()),\n                            (item_holder->top() + item_holder->height()) /\n                                layouts_unit_per_px));\n      auto left_value = cell_info.GetValueForKey(kCellInfoLeft);\n      EXPECT_TRUE(left_value && left_value->IsNumber() &&\n                  base::FloatsEqual(static_cast<double>(left_value->Number()),\n                                    (item_holder->left() - scroll_left) /\n                                        layouts_unit_per_px));\n      auto right_value = cell_info.GetValueForKey(kCellInfoRight);\n      EXPECT_TRUE(\n          right_value && right_value->IsNumber() &&\n          base::FloatsEqual(static_cast<double>(right_value->Number()),\n                            (item_holder->left() + item_holder->width()) /\n                                layouts_unit_per_px));\n      auto position_value = cell_info.GetValueForKey(kCellInfoPosition);\n      EXPECT_TRUE(position_value && position_value->IsInt32() &&\n                  position_value->Int32() == item_holder->index());\n    } else {\n      auto origin_x_value = cell_info.GetValueForKey(kCellInfoOriginX);\n      EXPECT_TRUE(\n          origin_x_value && origin_x_value->IsNumber() &&\n          base::FloatsEqual(static_cast<double>(origin_x_value->Number()),\n                            item_holder->left() / layouts_unit_per_px));\n      auto origin_y_value = cell_info.GetValueForKey(kCellInfoOriginY);\n      EXPECT_TRUE(\n          origin_y_value && origin_y_value->IsNumber() &&\n          base::FloatsEqual(static_cast<double>(origin_y_value->Number()),\n                            item_holder->top() / layouts_unit_per_px));\n      auto width_value = cell_info.GetValueForKey(kCellInfoWidth);\n      EXPECT_TRUE(\n          width_value && width_value->IsNumber() &&\n          base::FloatsEqual(static_cast<double>(width_value->Number()),\n                            item_holder->width() / layouts_unit_per_px));\n      auto height_value = cell_info.GetValueForKey(kCellInfoHeight);\n      EXPECT_TRUE(\n          height_value && height_value->IsNumber() &&\n          base::FloatsEqual(static_cast<double>(height_value->Number()),\n                            item_holder->height() / layouts_unit_per_px));\n      auto is_binding_value = cell_info.GetValueForKey(kCellInfoIsBinding);\n      EXPECT_TRUE(is_binding_value && is_binding_value->IsBool() &&\n                  is_binding_value->Bool() ==\n                      list_adapter_->IsBinding(item_holder));\n      auto is_updated_value = cell_info.GetValueForKey(kCellInfoUpdated);\n      EXPECT_TRUE(is_updated_value && is_updated_value->IsBool() &&\n                  is_updated_value->Bool() ==\n                      list_adapter_->IsUpdated(item_holder));\n    }\n  }\n};\n\nTEST_F(ListEventManagerTest, HasBoundEvent) {\n  mock_list_element_->events_ = {\"scroll\",\n                                 \"layoutcomplete\",\n                                 \"scrolltoupper\",\n                                 \"scrolltolower\",\n                                 \"scrolltoupperedge\",\n                                 \"scrolltoloweredge\",\n                                 \"scrolltonormalstate\",\n                                 \"listdebuginfo\"};\n  EXPECT_TRUE(mock_list_element_->HasBoundEvent(kEventScroll));\n  EXPECT_TRUE(mock_list_element_->HasBoundEvent(kEventLayoutComplete));\n  EXPECT_TRUE(mock_list_element_->HasBoundEvent(kEventScrollToUpper));\n  EXPECT_TRUE(mock_list_element_->HasBoundEvent(kEventScrollToLower));\n  EXPECT_TRUE(mock_list_element_->HasBoundEvent(kEventScrollToUpperEdge));\n  EXPECT_TRUE(mock_list_element_->HasBoundEvent(kEventScrollToLowerEdge));\n  EXPECT_TRUE(mock_list_element_->HasBoundEvent(kEventScrollToNormalState));\n  EXPECT_TRUE(mock_list_element_->HasBoundEvent(kEventListDebugInfo));\n}\n\nTEST_F(ListEventManagerTest, SendLayoutCompleteEvent0) {\n  // 1. No bind layout complete event.\n  InitFiberDataSourceAndLayout(1, false, false);\n  mock_list_element_->events_ = {};\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _)).Times(0);\n  list_event_manager_->SendLayoutCompleteEvent();\n}\n\nTEST_F(ListEventManagerTest, SendLayoutCompleteEvent1) {\n  // 2. Bind layout complete event.\n  int layout_id = 1;\n  InitFiberDataSourceAndLayout(layout_id, true, false);\n  mock_list_element_->events_ = {\"layoutcomplete\"};\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventLayoutComplete);\n        EXPECT_EQ(param_name, kEventParamDetail);\n        EXPECT_TRUE(param->IsMap());\n        // check layout id\n        auto layout_id_value = param->GetValueForKey(kLayoutInfoLayoutId);\n        EXPECT_TRUE(layout_id_value && layout_id_value->IsNumber() &&\n                    static_cast<int>(layout_id_value->Number()) == layout_id);\n        // check scroll info\n        auto scroll_info_value = param->GetValueForKey(kLayoutInfoScrollInfo);\n        float scroll_left = 0.f;\n        float scroll_top = list_layout_manager_->content_offset();\n        float scroll_width = mock_list_element_->GetWidth();\n        float scroll_height = list_layout_manager_->content_size();\n        float list_width = mock_list_element_->GetWidth();\n        float list_height = mock_list_element_->GetHeight();\n        float delta_x = 0.f;\n        float delta_y = 0.f;\n        CheckScrollInfo(scroll_info_value, scroll_left, scroll_top,\n                        scroll_width, scroll_height, list_width, list_height,\n                        delta_x, delta_y);\n      });\n  list_event_manager_->SendLayoutCompleteEvent();\n}\n\nTEST_F(ListEventManagerTest, SendCustomScrollEvent) {\n  int layout_id = 1;\n  int scroll_event_threshold = 20;\n  float distance = 10.f;\n  InitFiberDataSourceAndLayout(layout_id, false, true, scroll_event_threshold);\n  mock_list_element_->events_ = {\"scroll\"};\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScroll);\n        EXPECT_EQ(param_name, kEventParamDetail);\n        EXPECT_TRUE(param->IsMap());\n        // check event source\n        auto event_source_value = param->GetValueForKey(kScrollInfoEventSource);\n        EXPECT_TRUE(event_source_value && event_source_value->IsNumber() &&\n                    static_cast<int>(event_source_value->Number()) ==\n                        static_cast<int>(EventSource::kScroll));\n        // check scroll info\n        float scroll_left = 0.f;\n        float scroll_top = list_layout_manager_->content_offset();\n        float scroll_width = mock_list_element_->GetWidth();\n        float scroll_height = list_layout_manager_->content_size();\n        float list_width = mock_list_element_->GetWidth();\n        float list_height = mock_list_element_->GetHeight();\n        float delta_x = 0.f;\n        float delta_y = distance;\n        CheckScrollInfo(param, scroll_left, scroll_top, scroll_width,\n                        scroll_height, list_width, list_height, delta_x,\n                        delta_y);\n        // check visible cell info\n        auto visible_cell_info =\n            param->GetValueForKey(kScrollInfoAttachedCells);\n        CheckAllVisibleCellInfo(visible_cell_info, scroll_left, scroll_top,\n                                true);\n      });\n  list_event_manager_->SendCustomScrollEvent(kEventScroll, distance,\n                                             EventSource::kScroll);\n}\n\nTEST_F(ListEventManagerTest, SendAnchorDebugInfoIfNeeded) {\n  int layout_id = 1;\n  InitFiberDataSourceAndLayout(layout_id, true, false);\n  mock_list_element_->is_debug_mode_ = true;\n  mock_list_element_->events_ = {kEventListDebugInfo};\n  // Construct anchor info.\n  ListAnchorManager::AnchorInfo anchor_info;\n  int anchor_index = 1;\n  ItemHolder* item_holder = list_adapter_->GetItemHolderForIndex(anchor_index);\n  anchor_info.valid_ = true;\n  anchor_info.index_ = anchor_index;\n  anchor_info.start_offset_ = 100.f;\n  anchor_info.start_alignment_delta_ = 10.f;\n  anchor_info.item_holder_ = item_holder;\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventListDebugInfo);\n        EXPECT_EQ(param_name, kEventParamDetail);\n        EXPECT_TRUE(param->IsMap());\n        auto anchor_info_map = param->GetValueForKey(kDebugInfoAnchorInfo);\n        EXPECT_TRUE(anchor_info_map && anchor_info_map->IsMap());\n        auto index_value =\n            anchor_info_map->GetValueForKey(kDebugInfoAnchorIndex);\n        EXPECT_TRUE(index_value && index_value->IsInt32() &&\n                    index_value->Int32() == anchor_index);\n        auto start_offset =\n            anchor_info_map->GetValueForKey(kDebugInfoAnchorStartOffset);\n        EXPECT_TRUE(\n            start_offset && start_offset->IsNumber() &&\n            base::FloatsEqual(static_cast<double>(start_offset->Number()),\n                              anchor_info.start_offset_));\n        auto delta_value = anchor_info_map->GetValueForKey(\n            kDebugInfoAnchorStartAlignmentDelta);\n        EXPECT_TRUE(\n            delta_value && delta_value->IsNumber() &&\n            base::FloatsEqual(static_cast<double>(delta_value->Number()),\n                              anchor_info.start_alignment_delta_));\n        auto dirty_value =\n            anchor_info_map->GetValueForKey(kDebugInfoAnchorDirty);\n        EXPECT_TRUE(dirty_value && dirty_value->IsBool() &&\n                    dirty_value->Bool() == list_adapter_->IsDirty(item_holder));\n        auto binding_value =\n            anchor_info_map->GetValueForKey(kDebugInfoAnchorBinding);\n        EXPECT_TRUE(binding_value && binding_value->IsBool() &&\n                    binding_value->Bool() ==\n                        list_adapter_->IsBinding(item_holder));\n      });\n  list_event_manager_->SendAnchorDebugInfoIfNeeded(anchor_info);\n}\n\nTEST_F(ListEventManagerTest, SendExposureEvent) {\n  int layout_id = 1;\n  InitFiberDataSourceAndLayout(layout_id, true, false);\n  ItemHolder* appear_item_holder = list_adapter_->GetItemHolderForIndex(0);\n  ItemHolder* disappear_item_holder = list_adapter_->GetItemHolderForIndex(1);\n  ItemElementDelegate* appear_item_element =\n      list_adapter_->GetItemElementDelegate(appear_item_holder);\n  ItemElementDelegate* disappear_item_element =\n      list_adapter_->GetItemElementDelegate(disappear_item_holder);\n  TO_MOCK_LIST_ITEM_ELEMENT(appear_item_element)->events_ = {\n      kEventNodeAppear, kEventNodeDisappear};\n  TO_MOCK_LIST_ITEM_ELEMENT(disappear_item_element)->events_ = {\n      kEventNodeAppear, kEventNodeDisappear};\n  EXPECT_CALL(*TO_MOCK_LIST_ITEM_ELEMENT(appear_item_element),\n              SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventNodeAppear);\n        EXPECT_EQ(param_name, kEventParamDetail);\n        EXPECT_TRUE(param->IsMap());\n        auto index_value = param->GetValueForKey(kCellInfoIndex);\n        EXPECT_TRUE(index_value && index_value->IsInt32() &&\n                    index_value->Int32() == appear_item_holder->index());\n        auto item_key_value = param->GetValueForKey(kCellInfoItemKey);\n        EXPECT_TRUE(item_key_value && item_key_value->IsString() &&\n                    item_key_value->str() == appear_item_holder->item_key());\n      });\n  list_event_manager_->SendExposureEvent(kEventNodeAppear, appear_item_holder);\n  EXPECT_CALL(*TO_MOCK_LIST_ITEM_ELEMENT(disappear_item_element),\n              SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventNodeDisappear);\n        EXPECT_EQ(param_name, kEventParamDetail);\n        EXPECT_TRUE(param->IsMap());\n        auto index_value = param->GetValueForKey(kCellInfoIndex);\n        EXPECT_TRUE(index_value && index_value->IsInt32() &&\n                    index_value->Int32() == disappear_item_holder->index());\n        auto item_key_value = param->GetValueForKey(kCellInfoItemKey);\n        EXPECT_TRUE(item_key_value && item_key_value->IsString() &&\n                    item_key_value->str() == disappear_item_holder->item_key());\n      });\n  list_event_manager_->SendExposureEvent(kEventNodeDisappear,\n                                         disappear_item_holder);\n}\n\nTEST_F(ListEventManagerTest, RecordDiffResultIfNeeded) {\n  int layout_id = 1;\n  InitFiberDataSourceAndLayout(layout_id, true, false);\n  const auto& layout_complete_info = list_event_manager_->layout_complete_info_;\n  EXPECT_TRUE(layout_complete_info && layout_complete_info->IsMap());\n  auto diff_result_value =\n      layout_complete_info->GetValueForKey(kLayoutInfoDiffResult);\n  EXPECT_TRUE(diff_result_value && diff_result_value->IsMap());\n  auto insert_value = diff_result_value->GetValueForKey(kDiffInfoInsertion);\n  EXPECT_TRUE(insert_value && insert_value->IsArray());\n  auto remove_value = diff_result_value->GetValueForKey(kDiffInfoRemoval);\n  EXPECT_TRUE(remove_value && remove_value->IsArray());\n  auto update_from_value =\n      diff_result_value->GetValueForKey(kDiffInfoUpdateFrom);\n  EXPECT_TRUE(update_from_value && update_from_value->IsArray());\n  auto update_to_value = diff_result_value->GetValueForKey(kDiffInfoUpdateTo);\n  EXPECT_TRUE(update_to_value && update_to_value->IsArray());\n  auto move_from_value = diff_result_value->GetValueForKey(kDiffInfoMoveFrom);\n  EXPECT_TRUE(move_from_value && move_from_value->IsArray());\n  auto move_to_value = diff_result_value->GetValueForKey(kDiffInfoMoveTo);\n  EXPECT_TRUE(move_to_value && move_to_value->IsArray());\n}\n\nTEST_F(ListEventManagerTest, SendDiffDebugEventIfNeeded) {\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventListDebugInfo);\n        EXPECT_EQ(param_name, kEventParamDetail);\n        EXPECT_TRUE(param->IsMap());\n        auto diff_result_value = param->GetValueForKey(kDebugInfoDiffResult);\n        EXPECT_TRUE(diff_result_value && diff_result_value->IsMap());\n      });\n  int layout_id = 1;\n  mock_list_element_->is_debug_mode_ = true;\n  mock_list_element_->events_ = {kEventListDebugInfo};\n  InitFiberDataSourceAndLayout(layout_id, true, false);\n}\n\nTEST_F(ListEventManagerTest, DetectScrollToThresholdAndSend0) {\n  int layout_id = 1;\n  // let list_size > content_size\n  mock_list_element_->height_ = 5000;\n  mock_list_element_->events_ = {kEventScrollToUpper, kEventScrollToLower,\n                                 kEventScrollToUpperEdge,\n                                 kEventScrollToLowerEdge};\n  InitFiberDataSourceAndLayout(layout_id, true, false);\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToUpper);\n      })\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToLower);\n      })\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToUpperEdge);\n      })\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToLowerEdge);\n      });\n  list_event_manager_->DetectScrollToThresholdAndSend(0.f, 0.f,\n                                                      EventSource::kScroll);\n  EXPECT_TRUE(list_event_manager_->previous_scroll_pos_state_ ==\n              ScrollPositionState::kBothEdge);\n}\n\nTEST_F(ListEventManagerTest, DetectScrollToThresholdAndSend1) {\n  int layout_id = 1;\n  mock_list_element_->events_ = {kEventScrollToUpper, kEventScrollToLower,\n                                 kEventScrollToUpperEdge,\n                                 kEventScrollToLowerEdge};\n  InitFiberDataSourceAndLayout(layout_id, true, false);\n\n  // list_size: 500, content_size: 1000, content_offset: 0, original_offset: 0\n  mock_list_element_->height_ = 500;\n  list_layout_manager_->content_offset_ = 0.f;\n  float original_offset = 0.f;\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToUpper);\n      })\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToUpperEdge);\n      });\n  list_event_manager_->DetectScrollToThresholdAndSend(0.f, original_offset,\n                                                      EventSource::kScroll);\n  EXPECT_TRUE(list_event_manager_->previous_scroll_pos_state_ ==\n              ScrollPositionState::kUpper);\n\n  // list_size: 500, content_size: 1000, content_offset: 500, original_offset:\n  // 500\n  mock_list_element_->height_ = 500.f;\n  list_layout_manager_->content_offset_ = 500.f;\n  original_offset = 500.f;\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToLower);\n      })\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToLowerEdge);\n      });\n  list_event_manager_->DetectScrollToThresholdAndSend(0.f, original_offset,\n                                                      EventSource::kScroll);\n  EXPECT_TRUE(list_event_manager_->previous_scroll_pos_state_ ==\n              ScrollPositionState::kLower);\n\n  // list_size: 500, content_size: 1000, content_offset: 0, original_offset: -20\n  // at bounce area\n  mock_list_element_->height_ = 500.f;\n  list_layout_manager_->content_offset_ = 0.f;\n  original_offset = -20.f;\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToUpper);\n      });\n  list_event_manager_->DetectScrollToThresholdAndSend(0.f, original_offset,\n                                                      EventSource::kScroll);\n  EXPECT_TRUE(list_event_manager_->previous_scroll_pos_state_ ==\n              ScrollPositionState::kUpper);\n  // bounce back to 0\n  list_layout_manager_->content_offset_ = 0.f;\n  original_offset = 0.f;\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToUpperEdge);\n      });\n  list_event_manager_->DetectScrollToThresholdAndSend(0.f, original_offset,\n                                                      EventSource::kScroll);\n  EXPECT_TRUE(list_event_manager_->previous_scroll_pos_state_ ==\n              ScrollPositionState::kUpper);\n\n  // list_size: 500, content_size: 1000, content_offset: 500, original_offset:\n  // 520 at bounce area\n  mock_list_element_->height_ = 500.f;\n  list_layout_manager_->content_offset_ = 500.f;\n  original_offset = 520.f;\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToLower);\n      });\n  list_event_manager_->DetectScrollToThresholdAndSend(0.f, original_offset,\n                                                      EventSource::kScroll);\n  EXPECT_TRUE(list_event_manager_->previous_scroll_pos_state_ ==\n              ScrollPositionState::kLower);\n  // bounce back to 500\n  list_layout_manager_->content_offset_ = 500.f;\n  original_offset = 500.f;\n  EXPECT_CALL(*mock_list_element_, SendCustomEvent(_, _, _))\n      .WillOnce([&](const std::string& event_name,\n                    const std::string& param_name,\n                    std::unique_ptr<pub::Value> param) {\n        EXPECT_EQ(event_name, kEventScrollToLowerEdge);\n      });\n  list_event_manager_->DetectScrollToThresholdAndSend(0.f, original_offset,\n                                                      EventSource::kScroll);\n  EXPECT_TRUE(list_event_manager_->previous_scroll_pos_state_ ==\n              ScrollPositionState::kLower);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_layout_manager.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_list_layout_manager.h\"\n\n#include <algorithm>\n#include <vector>\n\n#include \"base/include/log/logging.h\"\n#include \"core/list/decoupled_list_anchor_manager.h\"\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\nListLayoutManager::ListLayoutManager(ListContainerImpl* list_container_impl)\n    : list_container_(list_container_impl) {\n  if (!list_container_) {\n    DLIST_LOGE(\n        \"ListLayoutManager::ListLayoutManager: list_container_ is nullptr\");\n  }\n}\n\n// When create ListContainerImpl or change list-type prop, we will invoke this\n// function to init LayoutManager.\nvoid ListLayoutManager::InitLayoutManager(\n    ListChildrenHelper* list_children_helper, Orientation list_orientation) {\n  list_children_helper_ = list_children_helper;\n  // create list_orientation_helper_.\n  SetOrientation(list_orientation);\n  // create list_anchor_manager_.\n  CreateOrUpdateListAnchorManager();\n}\n\n// Set layout orientation, and if list_orientation_helper_ == nullptr or\n// orientation changed, create new list_orientation_helper_.\nvoid ListLayoutManager::SetOrientation(Orientation orientation) {\n  if (orientation_ == orientation && list_orientation_helper_ != nullptr) {\n    return;\n  }\n  orientation_ = orientation;\n  list_orientation_helper_ =\n      ListOrientationHelper::CreateListOrientationHelper(this, orientation);\n}\n\nvoid ListLayoutManager::CreateOrUpdateListAnchorManager() {\n  if (!list_anchor_manager_) {\n    list_anchor_manager_ = std::make_unique<ListAnchorManager>(this);\n  }\n  list_anchor_manager_->SetListOrientationHelper(\n      list_orientation_helper_.get());\n  list_anchor_manager_->SetListAdapter(list_container_->list_adapter());\n  list_anchor_manager_->SetListChildrenHelper(list_children_helper_);\n  list_anchor_manager_->SetListContainer(list_container_);\n}\n\nfloat ListLayoutManager::GetWidth() const {\n  ElementDelegate* list_delegate = nullptr;\n  if (list_container_ && (list_delegate = list_container_->list_delegate())) {\n    return list_delegate->GetWidth() -\n           list_delegate\n               ->GetBorders()[static_cast<uint32_t>(FrameDirection::kLeft)] -\n           list_delegate\n               ->GetBorders()[static_cast<uint32_t>(FrameDirection::kRight)];\n  }\n  return 0.f;\n}\n\nfloat ListLayoutManager::GetHeight() const {\n  ElementDelegate* list_delegate = nullptr;\n  if (list_container_ && (list_delegate = list_container_->list_delegate())) {\n    return list_delegate->GetHeight() -\n           list_delegate\n               ->GetBorders()[static_cast<uint32_t>(FrameDirection::kTop)] -\n           list_delegate\n               ->GetBorders()[static_cast<uint32_t>(FrameDirection::kBottom)];\n  }\n  return 0.f;\n}\n\nfloat ListLayoutManager::GetPaddingLeft() const {\n  if (list_container_ && list_container_->list_delegate()) {\n    return list_container_->list_delegate()\n        ->GetPaddings()[static_cast<uint32_t>(FrameDirection::kLeft)];\n  }\n  return 0.f;\n}\n\nfloat ListLayoutManager::GetPaddingRight() const {\n  if (list_container_ && list_container_->list_delegate()) {\n    return list_container_->list_delegate()\n        ->GetPaddings()[static_cast<uint32_t>(FrameDirection::kRight)];\n  }\n  return 0.f;\n}\n\nfloat ListLayoutManager::GetPaddingTop() const {\n  if (list_container_ && list_container_->list_delegate()) {\n    return list_container_->list_delegate()\n        ->GetPaddings()[static_cast<uint32_t>(FrameDirection::kTop)];\n  }\n  return 0.f;\n}\n\nfloat ListLayoutManager::GetPaddingBottom() const {\n  if (list_container_ && list_container_->list_delegate()) {\n    return list_container_->list_delegate()\n        ->GetPaddings()[static_cast<uint32_t>(FrameDirection::kBottom)];\n  }\n  return 0.f;\n}\n\nvoid ListLayoutManager::InitLayoutAndAnchor(\n    ListAnchorManager::AnchorInfo& anchor_info, int finishing_binding_index) {\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    LIST_LAYOUT_MANAGER_RETRIEVE_ANCHOR_INFO);\n  // Record the current anchor information BEFORE laying out the item_holders as\n  // the layout result should be connected to the previous onScreen status.\n  list_anchor_manager_->RetrieveAnchorInfoBeforeLayout(anchor_info,\n                                                       finishing_binding_index);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n  if (!anchor_info.valid_) {\n    return;\n  }\n  LayoutInvalidItemHolder(0);\n  content_size_ = GetTargetContentSize();\n  // After LayoutInvalidItemHolder, the anchor item_holder's top or left may\n  // changed so it has to be adjusted.\n  list_anchor_manager_->AdjustAnchorInfoAfterLayout(anchor_info);\n}\n\nvoid ListLayoutManager::SetListLayoutInfoToAllItemHolders() {\n  if (!list_children_helper_ || !list_orientation_helper_) {\n    DLIST_LOGE(\n        \"ListLayoutManager::SetListLayoutInfoToAllItemHolders: \"\n        \"list_children_helper_ or list_orientation_helper_ is nullptr\");\n    return;\n  }\n  float container_size = list_orientation_helper_->GetMeasurement();\n  if (!base::FloatsLarger(container_size, 0.f)) {\n    DLIST_LOGE(\n        \"ListLayoutManager::SetListLayoutInfoToAllItemHolders: invalid list \"\n        \"container's size.\");\n  }\n  list_children_helper_->ForEachChild([container_size,\n                                       is_rtl = list_container_->IsRTL()](\n                                          ItemHolder* item_holder) {\n    item_holder->SetContainerSize(container_size);\n    item_holder->SetDirection(is_rtl ? Direction::kRTL : Direction::kNormal);\n    return false;\n  });\n}\n\nvoid ListLayoutManager::SetSpanCount(int span_count) {\n  span_count_ = span_count;\n  InitLayoutState();\n}\n\n// Receives scrolling events from the platform.\nvoid ListLayoutManager::ScrollByPlatformContainer(float content_offset_x,\n                                                  float content_offset_y,\n                                                  float original_x,\n                                                  float original_y) {\n  ScrollByInternal(\n      orientation_ == Orientation::kHorizontal ? content_offset_x\n                                               : content_offset_y,\n      orientation_ == Orientation::kHorizontal ? original_x : original_y, true);\n}\n\n// Platform UI will invoke this function when scrollToPosition UI method is\n// invoked and pass parameters to ListLayoutManager.\nvoid ListLayoutManager::ScrollToPosition(int index, float offset, int align,\n                                         bool smooth) {\n  ItemHolder* item_holder = nullptr;\n  if (!list_container_ || !list_orientation_helper_ ||\n      !(item_holder = list_container_->GetItemHolderForIndex(index)) ||\n      !list_anchor_manager_) {\n    return;\n  }\n  list_anchor_manager_->InitScrollToPositionParam(item_holder, index, offset,\n                                                  align, smooth);\n  DLIST_LOGI(\"[\" << list_container_ << \"] ScrollToPosition: \" << item_holder\n                 << \", \" << index << \", \" << offset << \", \" << align << \", \"\n                 << smooth);\n  if (smooth) {\n    float target_offset =\n        list_anchor_manager_->CalculateTargetScrollingOffset(item_holder);\n    list_container_->list_delegate()->UpdateScrollInfo(target_offset, smooth,\n                                                       false);\n  } else {\n    // scroll to index by layout, by initial-scroll-index\n    // is_non_smooth_scroll_ will block layout_complete event\n    is_non_smooth_scroll_ = true;\n    OnLayoutChildren();\n    // Invalidate consumed index to avoid double calculation\n    list_anchor_manager_->InvalidateScrollInfoPosition();\n    float target_offset =\n        list_anchor_manager_->CalculateTargetScrollingOffset(item_holder);\n    // scroll to additional offset\n    if (base::FloatsNotEqual(0, offset) ||\n        align != static_cast<int>(ScrollingInfoAlignment::kTop)) {\n      ScrollByInternal(target_offset, target_offset, false);\n    }\n    is_non_smooth_scroll_ = false;\n  }\n}\n\n// Platform UI will invoke this function when scrollToPosition UI method is\n// finished to clear ListLayoutManager's related scrolling info.\nvoid ListLayoutManager::ScrollStopped() {\n  DLIST_LOGI(\"[\" << list_container_ << \"] ScrollStopped\");\n  list_anchor_manager_->ResetScrollInfo();\n}\n\n// Determine whether the current ItemHolder needs to be recycled.\nbool ListLayoutManager::ShouldRecycleItemHolder(\n    const ItemHolder* item_holder) const {\n  if (!item_holder || !item_holder->recyclable() || !list_orientation_helper_) {\n    return false;\n  }\n  return !ItemHolderVisibleInList(item_holder);\n}\n\nbool ListLayoutManager::ItemHolderVisibleInList(\n    const ItemHolder* item_holder) const {\n  if (!item_holder) {\n    return false;\n  }\n  return item_holder->VisibleInList(list_orientation_helper_.get(),\n                                    content_offset_);\n}\n\n// Recycle all off-screen ItemHolders. It will be invoked after layouting\n// children or handling scroll events.\nvoid ListLayoutManager::RecycleOffScreenItemHolders() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, LIST_LAYOUT_MANAGER_RECYCLE_OFF_SCREEN_ITEM);\n  if (!list_children_helper_) {\n    return;\n  }\n  std::vector<ItemHolder*> off_screen_item_holders;\n  list_children_helper_->ForEachChild(\n      list_children_helper_->attached_children(),\n      [this, &off_screen_item_holders](ItemHolder* item_holder) {\n        if (item_holder && ShouldRecycleItemHolder(item_holder) &&\n            ShouldRecycleStickyItemHolder(item_holder)) {\n          off_screen_item_holders.push_back(item_holder);\n        }\n        return false;\n      });\n  ListAdapter* list_adapter = list_container_->list_adapter();\n  ItemElementDelegate* list_item_delegate = nullptr;\n  bool should_request_state_restore =\n      list_container_->should_request_state_restore();\n  for (auto item_holder : off_screen_item_holders) {\n    list_item_delegate = list_adapter->GetItemElementDelegate(item_holder);\n    if (list_item_delegate && should_request_state_restore) {\n      list_item_delegate->OnListItemDisappear(true, item_holder->item_key());\n    }\n    list_container_->list_adapter()->RecycleItemHolder(item_holder);\n  }\n}\n\n// Update content size and content offset and flush to platform by invoking\n// ListContainer::UpdateContentOffsetAndSizeToPlatform().\nvoid ListLayoutManager::FlushContentSizeAndOffsetToPlatform(\n    float content_offset_before_adjustment, bool from_layout) {\n  content_offset_ = ClampContentOffsetToEdge(content_offset_, content_size_);\n  float delta_x = CanScrollVertically()\n                      ? 0.f\n                      : content_offset_ - content_offset_before_adjustment;\n  float delta_y = CanScrollVertically()\n                      ? content_offset_ - content_offset_before_adjustment\n                      : 0.f;\n  if (list_container_) {\n    list_container_->UpdateContentOffsetAndSizeToPlatform(\n        content_size_, delta_x, delta_y,\n        list_anchor_manager_->IsValidInitialScrollIndex(),\n        is_non_smooth_scroll_ || from_layout);\n  }\n  FlushScrollInfoToPlatformIfNeeded();\n}\n\nvoid ListLayoutManager::FlushScrollInfoToPlatformIfNeeded() {\n  if (list_container_ && list_anchor_manager_->IsValidSmoothScrollInfo()) {\n    const ListAnchorManager::ScrollingInfo& scrolling_info =\n        list_anchor_manager_->scrolling_info();\n    ItemHolder* item_holder = list_container_->GetItemHolderForIndex(\n        scrolling_info.scrolling_target_);\n    if (item_holder) {\n      if (item_holder != scrolling_info.item_holder_) {\n        DLIST_LOGE(\n            \"[\" << list_container_\n                << \"] FlushScrollInfoToPlatformIfNeeded: target item holder in \"\n                   \"scrolling_info_ is not exist: \"\n                << scrolling_info.item_holder_ << \", \" << item_holder);\n      }\n      float target_offset =\n          list_anchor_manager_->CalculateTargetScrollingOffset(item_holder);\n      list_container_->list_delegate()->UpdateScrollInfo(target_offset, true,\n                                                         true);\n    } else {\n      list_anchor_manager_->ResetScrollInfo();\n    }\n  }\n}\n\n// Callback before layout.\nvoid ListLayoutManager::OnPrepareForLayoutChildren() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY,\n              LIST_LAYOUT_MANAGER_PREPARE_FOR_LAYOUT_CHILDREN);\n  SetListLayoutInfoToAllItemHolders();\n  list_container_->list_event_manager()->RecordVisibleItemIfNeeded(true);\n}\n\nvoid ListLayoutManager::SendLayoutCompleteEvent() {\n  // The bindlayoutcomplete event always works with a worklet to ensure\n  // immediate operation. Since a worklet may change a component's size and\n  // trigger another layout process, this event should be sent after the\n  // StopInterceptListElementUpdated to ensure that the layout inside it goes\n  // without blocking.\n  ListEventManager* event_manager = list_container_->list_event_manager();\n  if (event_manager && !is_non_smooth_scroll_) {\n    event_manager->SendLayoutCompleteEvent();\n  }\n}\n\nvoid ListLayoutManager::SendScrollEvents(float scroll_delta,\n                                         float original_offset,\n                                         EventSource event_source) {\n  list_container_->list_event_manager()->SendScrollEvent(scroll_delta,\n                                                         event_source);\n  list_container_->list_event_manager()->DetectScrollToThresholdAndSend(\n      scroll_delta, original_offset, event_source);\n}\n\n// Render sticky nodes if needed.\nint ListLayoutManager::UpdateStickyItems() {\n  if (!list_container_ || !list_container_->list_adapter() ||\n      !list_container_->sticky_enabled()) {\n    return kInvalidIndex;\n  }\n  int minimum_layout_changed_item_holder_index = kInvalidIndex;\n  float sticky_offset = list_container_->sticky_offset();\n  // If recycle sticky item, clear sticky item holder set firstly.\n  if (list_container_->recycle_sticky_item()) {\n    list_children_helper_->InitStickyItemHolderSet(\n        list_container_->list_delegate()->GetThreadStrategy());\n  }\n  // Enumerate from end to begin, find the first visible sticky-top item.\n  const std::vector<int32_t>& sticky_top_items =\n      list_container_->list_adapter()->GetStickyTops();\n  for (auto iter = sticky_top_items.rbegin(); iter != sticky_top_items.rend();\n       iter++) {\n    int index = *iter;\n    if (UpdateStickyItemsInternal(minimum_layout_changed_item_holder_index,\n                                  sticky_offset, index)) {\n      break;\n    }\n  }\n  // Enumerate from begin to end, find the first visible sticky-bottom item.\n  const std::vector<int32_t>& sticky_bottom_items =\n      list_container_->list_adapter()->GetStickyBottoms();\n  for (auto iter = sticky_bottom_items.begin();\n       iter != sticky_bottom_items.end(); iter++) {\n    int index = *iter;\n    if (UpdateStickyItemsInternal(minimum_layout_changed_item_holder_index,\n                                  sticky_offset, index)) {\n      break;\n    }\n  }\n  return minimum_layout_changed_item_holder_index;\n}\n\nbool ListLayoutManager::UpdateStickyItemsInternal(int& layout_changed_position,\n                                                  float sticky_offset,\n                                                  int index) {\n  ItemHolder* item_holder = list_container_->GetItemHolderForIndex(index);\n  if (item_holder->IsAtStickyPosition(\n          content_offset_, GetHeight(), content_size_, sticky_offset,\n          list_orientation_helper_->GetStart(item_holder),\n          list_orientation_helper_->GetEnd(item_holder))) {\n    float size_before_bind =\n        list_orientation_helper_->GetDecoratedMeasurement(item_holder);\n    // Bind item_holder if needed.\n    list_container_->list_adapter()->BindItemHolder(item_holder, index);\n    // Check if size changed.\n    if (base::FloatsNotEqual(\n            list_orientation_helper_->GetDecoratedMeasurement(item_holder),\n            size_before_bind)) {\n      if (layout_changed_position == kInvalidIndex ||\n          layout_changed_position > item_holder->index()) {\n        layout_changed_position = item_holder->index();\n      }\n    }\n    // If recycle sticky item, we need to add item holder to sticky item set and\n    // return whether need to bind next sticky item holder.\n    return list_container_->recycle_sticky_item()\n               ? !list_children_helper_->AddToStickyItemHolderSet(item_holder)\n               : true;\n  }\n  return false;\n}\n\nvoid ListLayoutManager::UpdateStickyItemsAfterLayout(\n    ListAnchorManager::AnchorInfo& anchor_info) {\n  // If the list has sticky items, the sticky items should be updated after\n  // the first adjustment to obtain information about which sticky items will\n  // enter their sticky mode. Since new sticky items may trigger extra\n  // bindings and cause additional layout changes, which requires an update\n  // to the layout afterwards.\n  if (list_container_->sticky_enabled()) {\n    int minimum_layout_updated_index = UpdateStickyItems();\n    minimum_layout_updated_index = std::max(minimum_layout_updated_index, 0);\n    // Layout and adjust scroll status again\n    LayoutInvalidItemHolder(minimum_layout_updated_index);\n    content_size_ = GetTargetContentSize();\n    list_anchor_manager_->AdjustContentOffsetWithAnchor(anchor_info,\n                                                        content_offset_);\n  }\n}\n\nvoid ListLayoutManager::HandleLayoutOrScrollResult(bool is_layout) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY,\n              LIST_LAYOUT_MANAGER_HANDLE_PLATFORM_OPERATION);\n  // The Handler to update layout info to platform.\n  auto update_layout_handler = [this](ItemHolder* item_holder) {\n    item_holder->UpdateLayoutToPlatform(\n        content_size_, GetWidth(),\n        list_container_->list_adapter()->GetItemElementDelegate(item_holder));\n    return false;\n  };\n  if (list_container_->sticky_enabled() &&\n      !list_container_->recycle_sticky_item()) {\n    list_children_helper_->UpdateInStickyChildren(\n        list_orientation_helper_.get(), content_offset_, content_size_,\n        list_container_->sticky_offset());\n  }\n  // The Handler to insert platform view and update layout info to platform ui\n  // including:\n  //   (1) on screen children\n  //   (2) in preload children\n  //   (3) sticky children\n  auto insert_handler = [this](ItemHolder* item_holder) {\n    ItemElementDelegate* list_item_delegate =\n        list_container_->list_adapter()->GetItemElementDelegate(item_holder);\n    if (list_item_delegate) {\n      list_container_->list_delegate()->InsertListItemPaintingNode(\n          list_item_delegate->GetImplId());\n    }\n    return false;\n  };\n  // The Handler to recycle off-screen or off-preload's item holder.\n  auto recycle_handler = [this](ItemHolder* item_holder) {\n    list_container_->list_adapter()->RecycleItemHolder(item_holder);\n    return false;\n  };\n  list_children_helper_->HandleLayoutOrScrollResult(\n      insert_handler, recycle_handler, update_layout_handler);\n  // Recycle all removed child.\n  if (is_layout) {\n    list_container_->list_adapter()->RecycleRemovedItemHolders();\n  }\n  list_container_->FlushPatching();\n}\n\n// Clamp content offset within scrollable range.\nfloat ListLayoutManager::ClampContentOffsetToEdge(float content_offset,\n                                                  float content_size) {\n  if (!list_orientation_helper_) {\n    return content_offset;\n  }\n  float scroll_range =\n      content_size - list_orientation_helper_->GetMeasurement();\n  return std::max(0.f, std::min(content_offset, scroll_range));\n}\n\nbool ListLayoutManager::ShouldRecycleStickyItemHolder(\n    const ItemHolder* item_holder) const {\n  if (list_container_->recycle_sticky_item()) {\n    return !list_container_->sticky_enabled() || !item_holder->sticky() ||\n           !list_children_helper_->InStickyItemHolderSet(item_holder);\n  } else {\n    return IsItemHolderNotAtStickyPosition(item_holder);\n  }\n}\n\nbool ListLayoutManager::IsItemHolderNotAtStickyPosition(\n    const ItemHolder* item_holder) const {\n  int sticky_offset = list_container_->sticky_offset();\n  bool sticky_enabled = list_container_->sticky_enabled();\n  return !sticky_enabled || !item_holder->sticky() ||\n         !item_holder->IsAtStickyPosition(\n             content_offset_, GetHeight(), content_size_, sticky_offset,\n             list_orientation_helper_->GetStart(item_holder),\n             list_orientation_helper_->GetEnd(item_holder));\n}\n\n#if ENABLE_TRACE_PERFETTO\nvoid ListLayoutManager::UpdateTraceDebugInfo(TraceEvent* event) const {\n  auto* content_size_info = event->add_debug_annotations();\n  content_size_info->set_name(\"content_size\");\n  content_size_info->set_string_value(std::to_string(content_size_));\n\n  auto* content_offset_info = event->add_debug_annotations();\n  content_offset_info->set_name(\"content_offset\");\n  content_offset_info->set_string_value(std::to_string(content_offset_));\n}\n#endif\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_layout_manager.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LIST_LAYOUT_MANAGER_H_\n#define CORE_LIST_DECOUPLED_LIST_LAYOUT_MANAGER_H_\n\n#include <algorithm>\n#include <memory>\n\n#include \"base/include/float_comparison.h\"\n#include \"core/list/decoupled_list_anchor_manager.h\"\n#include \"core/list/decoupled_list_children_helper.h\"\n#include \"core/list/decoupled_list_orientation_helper.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListContainerImpl;\n\n// Basic list layout manager, inherited by LinearLayoutManager /\n// StaggeredGridLayoutManager.\nclass ListLayoutManager {\n public:\n  ListLayoutManager(ListContainerImpl* list_container_impl);\n  virtual ~ListLayoutManager() = default;\n\n  // Init layout state.\n  virtual void InitLayoutState() {}\n  // Render and layout child nodes. This function will be invoked within\n  // OnListElementUpdated() if list has new diff result or list self-updated. In\n  // PART_ON_LAYOUT or MULTI_THREAD, this function will also be invoked within\n  // OnComponentFinished().\n  virtual void OnLayoutChildren(bool is_component_finished = false,\n                                int component_index = -1) = 0;\n  virtual void OnBatchLayoutChildren() {}\n  virtual void PreloadSection() {}\n\n  void InitLayoutManager(ListChildrenHelper* list_children_helper,\n                         Orientation list_orientation);\n  void SetOrientation(Orientation orientation);\n  void CreateOrUpdateListAnchorManager();\n  void ScrollByPlatformContainer(float content_offset_x, float content_offset_y,\n                                 float original_x, float original_y);\n  void ScrollToPosition(int index, float offset, int align, bool smooth);\n  void ScrollStopped();\n  float GetWidth() const;\n  float GetHeight() const;\n  float GetPaddingLeft() const;\n  float GetPaddingRight() const;\n  float GetPaddingTop() const;\n  float GetPaddingBottom() const;\n  bool CanScrollHorizontally() const {\n    return orientation_ == Orientation::kHorizontal;\n  }\n  bool CanScrollVertically() const {\n    return orientation_ == Orientation::kVertical;\n  }\n  bool ItemHolderVisibleInList(const ItemHolder* item_holder) const;\n  bool IsItemHolderNotAtStickyPosition(const ItemHolder* item_holder) const;\n  void UpdateDiffAnchorReference() {\n    list_anchor_manager_->UpdateDiffAnchorReference();\n  }\n\n  void SetMainAxisGap(float main_axis_gap) { main_axis_gap_ = main_axis_gap; }\n  void SetCrossAxisGap(float cross_axis_gap) {\n    cross_axis_gap_ = cross_axis_gap;\n  }\n  void SetInitialScrollIndex(int initial_scroll_index) {\n    list_anchor_manager_->SetInitialScrollIndex(initial_scroll_index);\n  }\n  void SetAnchorPriorityFromBegin(bool anchor_priority_from_begin) {\n    list_anchor_manager_->SetAnchorPriorityFromBegin(\n        anchor_priority_from_begin);\n  }\n  void SetAnchorAlignToBottom(bool anchor_align_to_bottom) {\n    list_anchor_manager_->SetAnchorAlignToBottom(anchor_align_to_bottom);\n  }\n  void SetAnchorVisibility(AnchorVisibility anchor_visibility) {\n    list_anchor_manager_->SetAnchorVisibility(anchor_visibility);\n  }\n  bool SetPreloadBufferCount(int count) {\n    bool count_changed = false;\n    if (count < 0) {\n      DLIST_LOGE(\n          \"ListLayoutManager::SetPreloadBufferCount: invalid preload buffer \"\n          \"count = \"\n          << count);\n      count_changed = preload_buffer_count_ != 0;\n      preload_buffer_count_ = 0;\n    } else {\n      count_changed = preload_buffer_count_ != count;\n      preload_buffer_count_ = count;\n    }\n    return count_changed;\n  }\n  void SetEnablePreloadSection(bool value) { enable_preload_section_ = value; }\n  void SetSpanCount(int span_count);\n  void SetContentOffset(float content_offset, bool with_clamp = true) {\n    content_offset_ =\n        with_clamp ? ClampContentOffsetToEdge(content_offset, content_size_)\n                   : std::max(content_offset, 0.f);\n  }\n  void ResetContentOffsetAndContentSize(float content_offset,\n                                        float content_size) {\n    content_size_ = content_size;\n    content_offset_ = content_offset;\n  }\n\n  int initial_scroll_index() const {\n    return list_anchor_manager_->initial_scroll_index();\n  }\n  int preload_buffer_count() const { return preload_buffer_count_; }\n  int span_count() const { return span_count_; }\n  float content_size() const { return content_size_; }\n  Orientation orientation() const { return orientation_; }\n  float main_axis_gap() const { return main_axis_gap_; }\n  float cross_axis_gap() const { return cross_axis_gap_; }\n  float content_offset() const { return content_offset_; }\n  bool enable_preload_section() const { return enable_preload_section_; }\n  float main_axis_size() const {\n    return list_orientation_helper_ ? list_orientation_helper_->GetMeasurement()\n                                    : 0.f;\n  }\n\n protected:\n  // Handle scrolling events from the platform.\n  virtual void ScrollByInternal(float content_offset, float original_offset,\n                                bool from_platform) = 0;\n  // Layout ItemHolder from specified index to end.\n  virtual void LayoutInvalidItemHolder(int first_invalid_index) = 0;\n  // Get list's content size.\n  virtual float GetTargetContentSize() = 0;\n  virtual bool ShouldRecycleItemHolder(const ItemHolder* item_holder) const;\n#if ENABLE_TRACE_PERFETTO\n  virtual void UpdateTraceDebugInfo(TraceEvent* event) const;\n#endif\n\n  // Init AnchorInfo and layout all item_holders.\n  void InitLayoutAndAnchor(ListAnchorManager::AnchorInfo& anchor_info,\n                           int finishing_binding_index);\n  void RecycleOffScreenItemHolders();\n  void FlushContentSizeAndOffsetToPlatform(\n      float content_offset_before_adjustment, bool from_layout);\n  void SendLayoutCompleteEvent();\n  void SendScrollEvents(float scroll_delta, float original_offset,\n                        EventSource event_source);\n  // Sticky methods\n  int UpdateStickyItems();\n  bool ShouldRecycleStickyItemHolder(const ItemHolder* item_holder) const;\n  void UpdateStickyItemsAfterLayout(ListAnchorManager::AnchorInfo& anchor_info);\n  // Calculated content_offset must be clamped to [0, content_size], or Android\n  // may stop at an over-edge index\n  float ClampContentOffsetToEdge(float content_offset, float content_size);\n  bool ValidPreloadBufferCount() const {\n    return preload_buffer_count_ > 0 && !enable_preload_section_;\n  }\n  void OnPrepareForLayoutChildren();\n  void HandleLayoutOrScrollResult(bool is_layout);\n\n private:\n  bool UpdateStickyItemsInternal(int& layout_changed_position,\n                                 float sticky_offset, int index);\n  void FlushScrollInfoToPlatformIfNeeded();\n  void SetListLayoutInfoToAllItemHolders();\n\n protected:\n  bool is_non_smooth_scroll_{false};\n  Orientation orientation_{Orientation::kVertical};\n  int span_count_{1};\n  float content_size_{0.f};\n  float content_offset_{0.f};\n  float last_content_offset_{0.f};\n  float main_axis_gap_{0.f};\n  float cross_axis_gap_{0.f};\n  int preload_buffer_count_{0};\n  bool enable_preload_section_{false};\n  std::unique_ptr<ListAnchorManager> list_anchor_manager_;\n  std::unique_ptr<ListOrientationHelper> list_orientation_helper_;\n  ListContainerImpl* list_container_{nullptr};\n  ListChildrenHelper* list_children_helper_{nullptr};\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LIST_LAYOUT_MANAGER_H_\n"
  },
  {
    "path": "core/list/decoupled_list_layout_manager_unittest.cc",
    "content": "\n// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/list/decoupled_list_layout_manager.h\"\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/value_wrapper/value_impl_lepus.h\"\n#include \"testing/fiber_data_source.h\"\n#include \"testing/mock_list_element.h\"\n#include \"testing/mock_list_item_element.h\"\n#include \"testing/radon_data_source.h\"\n#include \"testing/utils.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace list {\n\nusing ::testing::_;\nusing ::testing::Return;\n\nclass ListLayoutManagerTest : public ::testing::Test {\n public:\n  ListLayoutManagerTest() = default;\n  ~ListLayoutManagerTest() override = default;\n\n  std::unique_ptr<MockListElement> mock_list_element_{nullptr};\n  std::unique_ptr<ListContainerImpl> list_container_impl_{nullptr};\n  std::shared_ptr<pub::PubValueFactoryDefault> value_factory_{nullptr};\n  ListAdapter* list_adapter_{nullptr};\n  ListLayoutManager* list_layout_manager_{nullptr};\n\n  void SetUp() override {\n    value_factory_ = std::make_shared<pub::PubValueFactoryDefault>();\n    mock_list_element_ = std::make_unique<MockListElement>();\n    list_container_impl_ = std::make_unique<ListContainerImpl>(\n        mock_list_element_.get(), value_factory_);\n    list_adapter_ = list_container_impl_->list_adapter();\n    list_layout_manager_ = list_container_impl_->list_layout_manager();\n  }\n\n  void InitFiberDataSource() {\n    testing::InsertAction insert_action{\n        .insert_ops_ = {\n            {.position_ = 0, \"A_0\", 100, false, false, false, false},\n            {.position_ = 1, \"B_1\", 100, false, false, false, false},\n            {.position_ = 2, \"C_2\", 100, false, false, false, false},\n            {.position_ = 3, \"D_3\", 100, false, false, false, false},\n            {.position_ = 4, \"E_4\", 100, false, false, false, false},\n            {.position_ = 5, \"F_5\", 100, false, false, false, false},\n            {.position_ = 6, \"G_6\", 100, false, false, false, false},\n            {.position_ = 7, \"H_7\", 100, false, false, false, false},\n            {.position_ = 8, \"I_8\", 100, false, false, false, false},\n            {.position_ = 9, \"J_9\", 100, false, false, false, false},\n        }};\n    testing::FiberDataSource fiber_data_source{\n        .insert_action_ = insert_action,\n    };\n    LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n        FiberUpdateListInfo, lepus::Value(fiber_data_source.Resolve()));\n    list_container_impl_->ResolveAttribute(*key, value);\n    list_container_impl_->PropsUpdateFinish();\n  }\n\n  void InitLayoutAttrs(std::string list_type, int span_count,\n                       std::string scroll_orientation, float main_axis_gap,\n                       float cross_axis_gap, float list_main_size,\n                       float list_cross_size) {\n    {\n      LIST_CONTAINER_DEFINE_PROP_VALUE(ListType, String, list_type);\n      list_container_impl_->ResolveAttribute(*key, *value);\n      list_layout_manager_ = list_container_impl_->list_layout_manager();\n    }\n    {\n      LIST_CONTAINER_DEFINE_PROP_VALUE(SpanCount, Number, span_count);\n      list_container_impl_->ResolveAttribute(*key, *value);\n    }\n    {\n      LIST_CONTAINER_DEFINE_PROP_VALUE(ScrollOrientation, String,\n                                       scroll_orientation);\n      list_container_impl_->ResolveAttribute(*key, *value);\n    }\n    list_container_impl_->ResolveListAxisGap(\n        tasm::CSSPropertyID::kPropertyIDListMainAxisGap, main_axis_gap);\n    list_container_impl_->ResolveListAxisGap(\n        tasm::CSSPropertyID::kPropertyIDListCrossAxisGap, cross_axis_gap);\n    if (scroll_orientation == kPropValueScrollOrientationVertical) {\n      mock_list_element_->height_ = list_main_size;\n      mock_list_element_->width_ = list_cross_size;\n    } else {\n      mock_list_element_->height_ = list_cross_size;\n      mock_list_element_->width_ = list_main_size;\n    }\n    list_container_impl_->PropsUpdateFinish();\n  }\n\n  ListOrientationHelper* GetOrientationHelper() {\n    return list_layout_manager_->list_orientation_helper_.get();\n  }\n};\n\nTEST_F(ListLayoutManagerTest, InitLayoutAttrs) {\n  InitLayoutAttrs(\"waterfall\", 3, \"horizontal\", 10.f, 5.f, 2000.f, 1000.f);\n  EXPECT_TRUE(list_layout_manager_->CanScrollHorizontally());\n  EXPECT_EQ(list_layout_manager_->span_count(), 3);\n  EXPECT_TRUE(base::FloatsEqual(list_layout_manager_->main_axis_gap(), 10.f));\n  EXPECT_TRUE(base::FloatsEqual(list_layout_manager_->cross_axis_gap(), 5.f));\n  EXPECT_TRUE(base::FloatsEqual(list_layout_manager_->content_offset(), 0.f));\n  EXPECT_TRUE(base::FloatsEqual(list_layout_manager_->content_size(), 0.f));\n  EXPECT_TRUE(\n      base::FloatsEqual(GetOrientationHelper()->GetMeasurement(), 2000.f));\n  EXPECT_TRUE(base::FloatsEqual(GetOrientationHelper()->GetMeasurementInOther(),\n                                1000.f));\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_orientation_helper.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_list_orientation_helper.h\"\n\n#include <algorithm>\n\n#include \"core/list/decoupled_list_layout_manager.h\"\n\nnamespace lynx {\nnamespace list {\n\n/**\n * VerticalOrientationHelper\n */\nclass VerticalOrientationHelper : public ListOrientationHelper {\n public:\n  explicit VerticalOrientationHelper(ListLayoutManager* list_layout_manager)\n      : ListOrientationHelper(list_layout_manager) {}\n\n  ~VerticalOrientationHelper() override = default;\n\n  bool IsVertical() const override { return true; }\n\n  float GetStartAfterPadding() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetPaddingTop() : 0.f;\n  }\n\n  float GetEndAfterPadding() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetHeight() -\n                                      list_layout_manager_->GetPaddingBottom()\n                                : 0.f;\n  }\n\n  float GetMeasurement() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetHeight() : 0.f;\n  }\n\n  float GetMeasurementInOther() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetWidth() : 0.f;\n  }\n\n  float GetMeasurementInOtherWithoutPadding() const override {\n    return list_layout_manager_->GetWidth() -\n           list_layout_manager_->GetPaddingLeft() -\n           list_layout_manager_->GetPaddingRight();\n  }\n\n  float GetStartAfterPaddingInOther() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetPaddingLeft() : 0.f;\n  }\n\n  float GetEndPadding() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetPaddingBottom()\n                                : 0.f;\n  }\n\n  float GetDecoratedMeasurement(const ItemHolder* item_holder) const override {\n    return item_holder\n               ? item_holder->height() +\n                     item_holder->GetMargin(list::FrameDirection::kTop) +\n                     item_holder->GetMargin(list::FrameDirection::kBottom) +\n                     item_holder->top_inset()\n               : 0.f;\n  }\n\n  float GetDecoratedMeasurementInOther(\n      const ItemHolder* item_holder) const override {\n    return item_holder\n               ? item_holder->width() +\n                     item_holder->GetMargin(list::FrameDirection::kLeft) +\n                     item_holder->GetMargin(list::FrameDirection::kRight)\n               : 0.f;\n  }\n\n  float GetDecoratedStart(const ItemHolder* item_holder) const override {\n    return item_holder\n               ? item_holder->top() -\n                     item_holder->GetMargin(list::FrameDirection::kTop) -\n                     item_holder->top_inset()\n               : 0.f;\n  }\n\n  float GetDecoratedEnd(const ItemHolder* item_holder) const override {\n    // Note: GetHeight() includes the border-width in vertical direction\n    return item_holder\n               ? item_holder->top() + item_holder->height() +\n                     // item_holder->GetBorder(LazyDirection::kBottom) +\n                     item_holder->GetMargin(list::FrameDirection::kBottom)\n               : 0.f;\n  }\n\n  float GetStart(const ItemHolder* item_holder) const override {\n    return item_holder ? item_holder->top() -\n                             item_holder->GetMargin(list::FrameDirection::kTop)\n                       : 0.f;\n  }\n\n  float GetEnd(const ItemHolder* item_holder) const override {\n    return item_holder ? item_holder->top() + item_holder->height() : 0.f;\n  }\n\n  float GetItemHolderCrossMargin(const ItemHolder* item_holder) const override {\n    return item_holder ? item_holder->GetMargin(list::FrameDirection::kLeft)\n                       : 0.f;\n  }\n\n  float GetItemHolderMainMargin(const ItemHolder* item_holder) const override {\n    return item_holder ? item_holder->GetMargin(list::FrameDirection::kTop)\n                       : 0.f;\n  }\n};\n\n/**\n * HorizontalOrientationHelper\n */\nclass HorizontalOrientationHelper : public ListOrientationHelper {\n public:\n  explicit HorizontalOrientationHelper(ListLayoutManager* list_layout_manager)\n      : ListOrientationHelper(list_layout_manager) {}\n\n  ~HorizontalOrientationHelper() override = default;\n\n  bool IsVertical() const override { return false; }\n\n  float GetStartAfterPadding() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetPaddingLeft() : 0.f;\n  }\n\n  float GetEndAfterPadding() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetWidth() -\n                                      list_layout_manager_->GetPaddingRight()\n                                : 0.f;\n  }\n\n  float GetStartAfterPaddingInOther() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetPaddingTop() : 0.f;\n  }\n\n  float GetMeasurement() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetWidth() : 0.f;\n  }\n\n  float GetMeasurementInOther() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetHeight() : 0.f;\n  }\n\n  float GetMeasurementInOtherWithoutPadding() const override {\n    return list_layout_manager_->GetHeight() -\n           list_layout_manager_->GetPaddingTop() -\n           list_layout_manager_->GetPaddingBottom();\n  }\n\n  float GetEndPadding() const override {\n    return list_layout_manager_ ? list_layout_manager_->GetPaddingRight() : 0.f;\n  }\n\n  float GetDecoratedMeasurement(const ItemHolder* item_holder) const override {\n    return item_holder\n               ? item_holder->width() +\n                     item_holder->GetMargin(list::FrameDirection::kLeft) +\n                     item_holder->GetMargin(list::FrameDirection::kRight) +\n                     item_holder->top_inset()\n               : 0.f;\n  }\n\n  float GetDecoratedMeasurementInOther(\n      const ItemHolder* item_holder) const override {\n    return item_holder\n               ? item_holder->height() +\n                     item_holder->GetMargin(list::FrameDirection::kTop) +\n                     item_holder->GetMargin(list::FrameDirection::kBottom)\n               : 0.f;\n  }\n\n  float GetDecoratedStart(const ItemHolder* item_holder) const override {\n    return item_holder ? item_holder->left() -\n                             item_holder->GetMargin(list::FrameDirection::kLeft)\n                       : 0.f;\n  }\n\n  float GetDecoratedEnd(const ItemHolder* item_holder) const override {\n    // Note: GetWidth() includes the border-width in horizontal direction\n    return item_holder\n               ? item_holder->left() + item_holder->width() +\n                     item_holder->GetMargin(list::FrameDirection::kRight)\n               : 0.f;\n  }\n\n  float GetItemHolderCrossMargin(const ItemHolder* item_holder) const override {\n    return item_holder ? item_holder->GetMargin(list::FrameDirection::kTop)\n                       : 0.f;\n  }\n\n  float GetItemHolderMainMargin(const ItemHolder* item_holder) const override {\n    return item_holder ? item_holder->GetMargin(list::FrameDirection::kLeft)\n                       : 0.f;\n  }\n\n  float GetStart(const ItemHolder* item_holder) const override {\n    return item_holder ? item_holder->left() -\n                             item_holder->GetMargin(list::FrameDirection::kLeft)\n                       : 0.f;\n  }\n\n  float GetEnd(const ItemHolder* item_holder) const override {\n    return item_holder ? item_holder->left() + item_holder->width() : 0.f;\n  }\n};\n\n/**\n * ListOrientationHelper\n */\nListOrientationHelper::ListOrientationHelper(\n    ListLayoutManager* list_layout_manager)\n    : list_layout_manager_(list_layout_manager) {}\n\nListOrientationHelper::~ListOrientationHelper() = default;\n\nstd::unique_ptr<ListOrientationHelper>\nListOrientationHelper::CreateListOrientationHelper(\n    ListLayoutManager* list_layout_manager, list::Orientation orientation) {\n  if (orientation == list::Orientation::kHorizontal) {\n    return std::make_unique<HorizontalOrientationHelper>(list_layout_manager);\n  }\n  return std::make_unique<VerticalOrientationHelper>(list_layout_manager);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_list_orientation_helper.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LIST_ORIENTATION_HELPER_H_\n#define CORE_LIST_DECOUPLED_LIST_ORIENTATION_HELPER_H_\n\n#include <memory>\n\n#include \"core/list/decoupled_item_holder.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListLayoutManager;\n\nclass ListOrientationHelper {\n public:\n  static std::unique_ptr<ListOrientationHelper> CreateListOrientationHelper(\n      ListLayoutManager* list_layout_manager, list::Orientation orientation);\n  virtual ~ListOrientationHelper();\n\n  virtual bool IsVertical() const = 0;\n  // Get list size in main axis.\n  virtual float GetMeasurement() const = 0;\n  // Get list size in cross axis.\n  virtual float GetMeasurementInOther() const = 0;\n  // Get List content area.\n  virtual float GetMeasurementInOtherWithoutPadding() const = 0;\n  // Get list size port's start offset in main axis.\n  virtual float GetStartAfterPadding() const = 0;\n  // Get list size port's end offset in main axis.\n  virtual float GetEndAfterPadding() const = 0;\n  // Get list size port's start offset in cross axis.\n  virtual float GetStartAfterPaddingInOther() const = 0;\n  // Get list's end padding in main axis (padding-right / padding-bottom).\n  virtual float GetEndPadding() const = 0;\n\n  // Get child ItemHolder size in main axis.\n  virtual float GetDecoratedMeasurement(\n      const ItemHolder* item_holder) const = 0;\n  // Get child ItemHolder size in cross axis.\n  virtual float GetDecoratedMeasurementInOther(\n      const ItemHolder* item_holder) const = 0;\n  // Get child ItemHolder start layout offset in main axis.\n  virtual float GetDecoratedStart(const ItemHolder* item_holder) const = 0;\n  // Get child ItemHolder end layout offset in main axis.\n  virtual float GetDecoratedEnd(const ItemHolder* item_holder) const = 0;\n  // Get child ItemHolder start layout offset without main-axis-gap and\n  // margin-top in main axis.\n  virtual float GetStart(const ItemHolder* item_holder) const = 0;\n  // Get child ItemHolder end layout offset without margin-bottom in main axis.\n  virtual float GetEnd(const ItemHolder* item_holder) const = 0;\n  // Get child ItemHolder's margin in main axis.\n  virtual float GetItemHolderMainMargin(\n      const ItemHolder* item_holder) const = 0;\n  // Get child ItemHolder's margin in cross axis.\n  virtual float GetItemHolderCrossMargin(\n      const ItemHolder* item_holder) const = 0;\n\n protected:\n  explicit ListOrientationHelper(ListLayoutManager* list_layout_manager);\n  ListLayoutManager* list_layout_manager_{nullptr};\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LIST_ORIENTATION_HELPER_H_\n"
  },
  {
    "path": "core/list/decoupled_list_types.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_LIST_TYPES_H_\n#define CORE_LIST_DECOUPLED_LIST_TYPES_H_\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace list {\n\n#define DLIST_LOGD(msg) LOGD(\"[List] \" << msg)\n#define DLIST_LOGV(msg) LOGV(\"[List] \" << msg)\n#define DLIST_LOGI(msg) LOGI(\"[List] \" << msg)\n#define DLIST_LOGW(msg) LOGW(\"[List] \" << msg)\n#define DLIST_LOGE(msg) LOGE(\"[List] \" << msg)\n\nclass ItemHolder;\n\nusing ItemKeySet = std::unordered_set<std::string>;\nusing ItemHolderMap =\n    std::unordered_map<std::string, std::unique_ptr<list::ItemHolder>>;\nusing ItemHolderPtrMap = std::unordered_map<std::string, list::ItemHolder*>;\n\n// props\nstatic constexpr const char* const kPropCustomListName = \"custom-list-name\";\nstatic constexpr const char kPropEnableDecoupledList[] =\n    \"enable-decoupled-list\";\nstatic constexpr const char* const kPropScrollOrientation =\n    \"scroll-orientation\";\nstatic constexpr const char* const kPropVerticalOrientation =\n    \"vertical-orientation\";\nstatic constexpr const char* const kPropUpdateAnimation = \"update-animation\";\nstatic constexpr const char* const kPropListType = \"list-type\";\nstatic constexpr const char* const kPropSpanCount = \"span-count\";\nstatic constexpr const char* const kPropColumnCount = \"column-count\";\nstatic constexpr const char* const kPropListMainAxisGap = \"list-main-axis-gap\";\nstatic constexpr const char* const kPropListCrossAxisGap =\n    \"list-cross-axis-gap\";\nstatic constexpr const char* const kPropSticky = \"sticky\";\nstatic constexpr const char* const kPropStickyOffset = \"sticky-offset\";\nstatic constexpr const char* const kPropEnableNestedScroll =\n    \"enable-nested-scroll\";\nstatic constexpr const char* const kPropEnableScroll = \"enable-scroll\";\nstatic constexpr const char* const kPropRadonListPlatformInfo =\n    \"list-platform-info\";\nstatic constexpr const char* const kPropFiberUpdateListInfo =\n    \"update-list-info\";\nstatic constexpr const char* const kPropPreloadBufferCount =\n    \"preload-buffer-count\";\nstatic constexpr const char* const kPropEnableInsertPlatformViewOperation =\n    \"enable-insert-platform-view-operation\";\nstatic constexpr const char* const kPropBounces = \"bounces\";\nstatic constexpr const char* const kPropEnableDynamicSpanCount =\n    \"enable-dynamic-span-count\";\nstatic constexpr const char* const kPropStickyBufferCount =\n    \"sticky-buffer-count\";\nstatic constexpr const char* const kPropNeedVisibleItemInfo =\n    \"need-visible-item-info\";\nstatic constexpr const char* const kPropNeedsVisibleCells =\n    \"needs-visible-cells\";\nstatic constexpr const char* const kPropLowerThresholdItemCount =\n    \"lower-threshold-item-count\";\nstatic constexpr const char* const kPropUpperThresholdItemCount =\n    \"upper-threshold-item-count\";\nstatic constexpr const char* const kPropScrollEventThrottle =\n    \"scroll-event-throttle\";\nstatic constexpr const char* const kPropInitialScrollIndex =\n    \"initial-scroll-index\";\nstatic constexpr const char* const kPropNeedLayoutCompleteInfo =\n    \"need-layout-complete-info\";\nstatic constexpr const char* const kPropLayoutId = \"layout-id\";\nstatic constexpr const char* const kPropShouldRequestStateRestore =\n    \"should-request-state-restore\";\nstatic constexpr const char* const kPropExperimentalRecycleStickyItem =\n    \"experimental-recycle-sticky-item\";\nstatic constexpr const char* const kPropExperimentalUpdateStickyForDiff =\n    \"experimental-update-sticky-for-diff\";\nstatic constexpr const char* const\n    kPropExperimentalRecycleAvailableItemBeforeLayout =\n        \"experimental-recycle-available-item-before-layout\";\nstatic constexpr const char* const kPropExperimentalBatchRenderStrategy =\n    \"experimental-batch-render-strategy\";\nstatic constexpr const char* const kPropAnchorPriority = \"anchor-priority\";\nstatic constexpr const char* const kPropAnchorVisibility = \"anchor-visibility\";\nstatic constexpr const char* const kPropAnchorAlign = \"anchor-align\";\nstatic constexpr const char* const kPropEnablePreloadSection =\n    \"experimental-enable-preload-section\";\nstatic constexpr const char* const kPropExperimentalContinuousResolveTree =\n    \"experimental-continuous-resolve-tree\";\nstatic constexpr const char* const kPropListDebugInfoLevel =\n    \"list-debug-info-level\";\nstatic constexpr const char* const kPropExperimentalSearchRefAnchorStrategy =\n    \"experimental-search-ref-anchor-strategy\";\n\n// prop value\nstatic constexpr const char* const kPropValueTrue = \"true\";\nstatic constexpr const char* const kPropValueListTypeSingle = \"single\";\nstatic constexpr const char* const kPropValueListTypeFlow = \"flow\";\nstatic constexpr const char* const kPropValueListTypeWaterFall = \"waterfall\";\nstatic constexpr const char* const kPropValueListContainer = \"list-container\";\nstatic constexpr const char* const kPropValueUpdateAnimationDefault = \"default\";\nstatic constexpr const char* const kPropValueScrollOrientationHorizontal =\n    \"horizontal\";\nstatic constexpr const char* const kPropValueScrollOrientationVertical =\n    \"vertical\";\nstatic constexpr const char* const kPropValueAnchorPriorityFromBegin =\n    \"fromBegin\";\nstatic constexpr const char* const kPropValueAnchorPriorityFromEnd = \"fromEnd\";\nstatic constexpr const char* const kPropValueAnchorAlignToBottom = \"toBottom\";\nstatic constexpr const char* const kPropValueAnchorAlignToTop = \"toTop\";\nstatic constexpr const char* const kPropValueAnchorVisibilityHide = \"hide\";\nstatic constexpr const char* const kPropValueAnchorVisibilityShow = \"show\";\n\n// list-container-info\nstatic constexpr const char* kListContainerInfo = \"list-container-info\";\nstatic constexpr const char* kListContainerInfoStickyStart = \"stickyStart\";\nstatic constexpr const char* kListContainerInfoStickyEnd = \"stickyEnd\";\nstatic constexpr const char* kListContainerInfoItemKeys = \"itemkeys\";\n\n// event\nstatic constexpr const char* const kEventScroll = \"scroll\";\nstatic constexpr const char* const kEventScrollToUpper = \"scrolltoupper\";\nstatic constexpr const char* const kEventScrollToUpperEdge =\n    \"scrolltoupperedge\";\nstatic constexpr const char* const kEventScrollToLower = \"scrolltolower\";\nstatic constexpr const char* const kEventScrollToLowerEdge =\n    \"scrolltoloweredge\";\nstatic constexpr const char* const kEventScrollToNormalState =\n    \"scrolltonormalstate\";\nstatic constexpr const char* const kEventLayoutComplete = \"layoutcomplete\";\nstatic constexpr const char* const kEventNodeAppear = \"nodeappear\";\nstatic constexpr const char* const kEventNodeDisappear = \"nodedisappear\";\nstatic constexpr const char* const kEventScrollStateChange =\n    \"scrollstatechange\";\nstatic constexpr const char* const kEventListDebugInfo = \"listdebuginfo\";\nstatic constexpr const char* const kEventParamDetail = \"detail\";\n\n// event layout complete info\nstatic constexpr const char* const kLayoutInfoLayoutId = \"layout-id\";\nstatic constexpr const char* const kLayoutInfoScrollInfo = \"scrollInfo\";\nstatic constexpr const char* const kLayoutInfoDiffResult = \"diffResult\";\nstatic constexpr const char* const kLayoutInfoEventUnit = \"eventUnit\";\nstatic constexpr const char* const kLayoutInfoVisibleItemBeforeUpdate =\n    \"visibleItemBeforeUpdate\";\nstatic constexpr const char* const kLayoutInfoVisibleItemAfterUpdate =\n    \"visibleItemAfterUpdate\";\nstatic constexpr const char* const kLayoutInfoEventUnitValuePx = \"px\";\n\n// event scroll info\nstatic constexpr const char* const kScrollInfoEventSource = \"eventSource\";\nstatic constexpr const char* const kScrollInfoAttachedCells = \"attachedCells\";\nstatic constexpr const char* const kScrollInfoScrollLeft = \"scrollLeft\";\nstatic constexpr const char* const kScrollInfoScrollTop = \"scrollTop\";\nstatic constexpr const char* const kScrollInfoScrollWidth = \"scrollWidth\";\nstatic constexpr const char* const kScrollInfoScrollHeight = \"scrollHeight\";\nstatic constexpr const char* const kScrollInfoListWidth = \"listWidth\";\nstatic constexpr const char* const kScrollInfoListHeight = \"listHeight\";\nstatic constexpr const char* const kScrollInfoDeltaX = \"deltaX\";\nstatic constexpr const char* const kScrollInfoDeltaY = \"deltaY\";\n\n// event cell info\nstatic constexpr const char* const kCellInfoIdSelector = \"id\";\nstatic constexpr const char* const kCellInfoItemKey = \"itemKey\";\nstatic constexpr const char* const kCellInfoIndex = \"index\";\nstatic constexpr const char* const kCellInfoPosition = \"position\";\nstatic constexpr const char* const kCellInfoTop = \"top\";\nstatic constexpr const char* const kCellInfoLeft = \"left\";\nstatic constexpr const char* const kCellInfoBottom = \"bottom\";\nstatic constexpr const char* const kCellInfoRight = \"right\";\nstatic constexpr const char* const kCellInfoOriginX = \"originX\";\nstatic constexpr const char* const kCellInfoOriginY = \"originY\";\nstatic constexpr const char* const kCellInfoWidth = \"width\";\nstatic constexpr const char* const kCellInfoHeight = \"height\";\nstatic constexpr const char* const kCellInfoIsBinding = \"isBinding\";\nstatic constexpr const char* const kCellInfoUpdated = \"updated\";\n\n// event diff info\nstatic constexpr const char* const kDiffInfoInsertion = \"insertions\";\nstatic constexpr const char* const kDiffInfoRemoval = \"removals\";\nstatic constexpr const char* const kDiffInfoUpdateFrom = \"update_from\";\nstatic constexpr const char* const kDiffInfoUpdateTo = \"update_to\";\nstatic constexpr const char* const kDiffInfoMoveFrom = \"move_from\";\nstatic constexpr const char* const kDiffInfoMoveTo = \"move_to\";\n\n// event debug info\nstatic constexpr const char* const kDebugInfoDiffResult = \"diffResult\";\nstatic constexpr const char* const kDebugInfoAnchorInfo = \"anchor_info\";\nstatic constexpr const char* const kDebugInfoAnchorIndex = \"anchor_index\";\nstatic constexpr const char* const kDebugInfoAnchorStartOffset = \"start_offset\";\nstatic constexpr const char* const kDebugInfoAnchorStartAlignmentDelta =\n    \"start_alignment_delta\";\nstatic constexpr const char* const kDebugInfoAnchorDirty = \"dirty\";\nstatic constexpr const char* const kDebugInfoAnchorBinding = \"binding\";\n\n// radon arch data\nstatic constexpr const char* const kRadonDataDiffResult = \"diffResult\";\nstatic constexpr const char* const kRadonDataInsertions = \"insertions\";\nstatic constexpr const char* const kRadonDataRemovals = \"removals\";\nstatic constexpr const char* const kRadonDataUpdateFrom = \"updateFrom\";\nstatic constexpr const char* const kRadonDataUpdateTo = \"updateTo\";\nstatic constexpr const char* const kRadonDataMoveFrom = \"moveFrom\";\nstatic constexpr const char* const kRadonDataMoveTo = \"moveTo\";\nstatic constexpr const char* const kRadonDataEstimatedHeightPx =\n    \"estimatedHeightPx\";\nstatic constexpr const char* const kRadonDataEstimatedMainAxisSizePx =\n    \"estimatedMainAxisSizePx\";\nstatic constexpr const char* const kRadonDataFullSpan = \"fullspan\";\nstatic constexpr const char* const kRadonDataStickyTop = \"stickyTop\";\nstatic constexpr const char* const kRadonDataStickyBottom = \"stickyBottom\";\nstatic constexpr const char* const kRadonDataItemKeys = \"itemkeys\";\n\n// fiber arch data\nstatic constexpr const char* const kFiberDataInsertAction = \"insertAction\";\nstatic constexpr const char* const kFiberDataRemoveAction = \"removeAction\";\nstatic constexpr const char* const kFiberDataUpdateAction = \"updateAction\";\nstatic constexpr const char* const kFiberDataPosition = \"position\";\nstatic constexpr const char* const kFiberDataItemKey = \"item-key\";\nstatic constexpr const char* const kFiberDataFullSpan = \"full-span\";\nstatic constexpr const char* const kFiberDataStickyTop = \"sticky-top\";\nstatic constexpr const char* const kFiberDataStickyBottom = \"sticky-bottom\";\nstatic constexpr const char* const kFiberDataEstimatedHeightPx =\n    \"estimated-height-px\";\nstatic constexpr const char* const kFiberDataEstimatedMainAxisSizePx =\n    \"estimated-main-axis-size-px\";\nstatic constexpr const char* const kFiberDataRecyclable = \"recyclable\";\nstatic constexpr const char* const kFiberDataFrom = \"from\";\nstatic constexpr const char* const kFiberDataTo = \"to\";\nstatic constexpr const char* const kFiberDataFlush = \"flush\";\n\n// constant value\nstatic constexpr int kInvalidIndex = -1;\nstatic constexpr int kInvalidItemCount = -1;\nstatic constexpr int kInvalidDimensionSize = -1.f;\nstatic constexpr int kStickyItemSetCapacityForSyncMode = 1;\nstatic constexpr int kStickyItemSetCapacityForASyncMode = 2;\nstatic constexpr int kDefaultMainAxisItemSize = 200;\nstatic constexpr int kAnchorCandidateSize = 4;\n\nenum class LayoutDirection : int32_t {\n  kLayoutToStart = -1,\n  kLayoutToEnd = 1,\n};\n\nenum class FrameDirection : uint32_t {\n  kLeft = 0,\n  kTop,\n  kRight,\n  kBottom,\n};\n\nenum class LayoutType {\n  kSingle = 0,\n  kFlow,\n  kWaterFall,\n};\n\nenum class Orientation { kHorizontal = 0, kVertical };\n\nenum class Direction { kNormal = 0, kRTL };\n\nenum class InitialScrollIndexStatus {\n  kUnset = 0,\n  kSet,\n  kScrolled,\n};\n\nenum class DiffStatus {\n  kValid = 0,\n  kRemoved,\n  kUpdateTo,\n  kUpdatedFrom,\n  kMoveTo,\n  kMoveFrom\n};\n\nenum class AnchorVisibility {\n  kAnchorVisibilityNoAdjustment,\n  kAnchorVisibilityShow,\n  kAnchorVisibilityHide\n};\n\nenum class ScrollingInfoAlignment { kTop, kMiddle, kBottom };\n\nenum class EventSource {\n  kDiff = 0,\n  kLayout,\n  kScroll,\n};\n\nenum class ScrollPositionState {\n  kMiddle = 0,  // not upper and not lower\n  kUpper,\n  kLower,\n  kBothEdge,  // on upper and lower\n};\n\nenum class BatchRenderStrategy {\n  kDefault = 0,\n  kBatchRender = 1,\n  kAsyncResolveProperty = 2,\n  kAsyncResolvePropertyAndElementTree = 3\n};\n\nenum class ScrollState {\n  kNone = 0,\n  kIdle,\n  kDragging,\n  kFling,\n  kScrollAnimation\n};\n\nenum class ListDebugInfoLevel {\n  kListDebugInfoLevelNone = 0,\n  kListDebugInfoLevelError,\n  kListDebugInfoLevelInfo,\n  kListDebugInfoLevelVerbose,\n};\n\nenum class ListTiming {\n  kRenderChildrenStart = 0,\n  kRenderChildrenEnd,\n  kFullFillRenderChildrenEnd\n};\n\nenum class SearchRefAnchorStrategy {\n  kNone = 0,\n  kToStart,\n  kToEnd,\n};\n\nenum class ItemHolderAnimationType { kNone, kTransform, kOpacity };\n\nenum class ListAnimationType { kNone, kRemove, kInsert, kUpdate };\n\nenum class ListAdapterDiffResult {\n  kNone = 0,\n  kMove = 1,\n  kInsert = 1 << 1,\n  kRemove = 1 << 2,\n  kUpdate = 1 << 3,\n};\n\nconstexpr inline ListAdapterDiffResult operator|(ListAdapterDiffResult a,\n                                                 ListAdapterDiffResult b) {\n  return static_cast<ListAdapterDiffResult>(static_cast<unsigned int>(a) |\n                                            static_cast<unsigned int>(b));\n}\n\nconstexpr inline ListAdapterDiffResult operator&(ListAdapterDiffResult a,\n                                                 ListAdapterDiffResult b) {\n  return static_cast<ListAdapterDiffResult>(static_cast<unsigned int>(a) &\n                                            static_cast<unsigned int>(b));\n}\n\nconstexpr inline ListAdapterDiffResult& operator|=(ListAdapterDiffResult& a,\n                                                   ListAdapterDiffResult b) {\n  a = a | b;\n  return a;\n}\n\nconstexpr inline ListAdapterDiffResult& operator&=(ListAdapterDiffResult& a,\n                                                   ListAdapterDiffResult b) {\n  a = a & b;\n  return a;\n}\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_LIST_TYPES_H_\n"
  },
  {
    "path": "core/list/decoupled_staggered_grid_layout_manager.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/decoupled_staggered_grid_layout_manager.h\"\n\n#include <algorithm>\n#include <cmath>\n\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace list {\n\nStaggeredGridLayoutManager::StaggeredGridLayoutManager(\n    ListContainerImpl* list_container_impl)\n    : ListLayoutManager(list_container_impl) {}\n\nvoid StaggeredGridLayoutManager::InitLayoutState() {\n  column_indexes_.resize(span_count_);\n  std::fill(column_indexes_.begin(), column_indexes_.begin() + span_count_,\n            std::vector<int>());\n}\n\nvoid StaggeredGridLayoutManager::UpdateStartAndEndLinesStatus(\n    LayoutState& layout_state) {\n  // Use attached_children to calculate start/end lines\n  // 1. Calculate start_item_holders and end_item_holders from attached_children\n  std::vector<ItemHolder*> start_item_holders(\n      span_count_, list_container_->GetItemHolderForIndex(\n                       list_container_->GetDataCount() - 1));\n  std::vector<ItemHolder*> end_item_holders(\n      span_count_, list_container_->GetItemHolderForIndex(0));\n  if (list_children_helper_->attached_children().size() > 0) {\n    list_children_helper_->ForEachChild(\n        list_children_helper_->attached_children(),\n        [this, &start_item_holders, &end_item_holders,\n         list_adapter =\n             list_container_->list_adapter()](ItemHolder* item_holder) {\n          // Note: When updating layout status, we should exclude the item\n          // holder which is removed or in sticky but not intersected with\n          // visible area.\n          if (!IntersectVisibleArea(item_holder) ||\n              list_adapter->IsRemoved(item_holder)) {\n            return false;\n          }\n          // span index of current item holder.\n          int span_index = item_holder->item_col_index();\n          int item_index = item_holder->index();\n          int current_start_item_index =\n              start_item_holders[span_index]->index();\n          int current_end_item_index = end_item_holders[span_index]->index();\n          // 1. Update start_item_holders vector.\n          if (item_holder->item_full_span()) {\n            // 1.1 If item holder is full span.\n            for (int i = 0; i < span_count_; ++i) {\n              // If index of item holder in start_item_holders larger than\n              // item_index, we should set item_holder to the position i in\n              // start_item_holders.\n              if (start_item_holders[i]->index() > item_index) {\n                start_item_holders[i] = item_holder;\n              }\n            }\n          } else if (current_start_item_index > item_index) {\n            // 1.2 If item holder is not full span and has less index, we just\n            // need to set it to start_item_holders.\n            start_item_holders[span_index] = item_holder;\n          }\n          // 2. Update end_item_holders vector.\n          if (item_holder->item_full_span()) {\n            // 2.1 If item holder is full span.\n            for (int i = 0; i < span_count_; ++i) {\n              // If index of item holder in end_item_holders smaller than\n              // item_index, we should set item_holder to the position i in\n              // end_item_holders.\n              if (end_item_holders[i]->index() < item_index) {\n                end_item_holders[i] = item_holder;\n              }\n            }\n          } else if (current_end_item_index < item_index) {\n            // 2.2 If item holder is not full span and has larger index, we\n            // just need to set it to end_item_holders.\n            end_item_holders[span_index] = item_holder;\n          }\n          return false;\n        });\n    layout_state.is_start_full_span_ = true;\n    layout_state.is_end_full_span_ = true;\n    ItemHolder* item_holder = nullptr;\n    // 2. Use start_item_holders update start lines and start indexes.\n    for (int i = 0; i < span_count_; ++i) {\n      item_holder = start_item_holders[i];\n      layout_state.start_index[i] = item_holder->index();\n      layout_state.start_lines[i] =\n          list_orientation_helper_->GetDecoratedStart(item_holder);\n      // Note: if all items in start_item_holders are full span, we mark\n      // is_start_full_span_ to true.\n      layout_state.is_start_full_span_ =\n          item_holder->item_full_span() && layout_state.is_start_full_span_;\n    }\n    // 3. Use end_item_holders update end lines and end indexes.\n    for (int i = 0; i < span_count_; ++i) {\n      item_holder = end_item_holders[i];\n      layout_state.end_index[i] = item_holder->index();\n      layout_state.end_lines[i] =\n          list_orientation_helper_->GetDecoratedEnd(item_holder);\n      // Note: if all items in end_item_holders are full span, we mark\n      // is_end_full_span_ to true.\n      layout_state.is_end_full_span_ =\n          item_holder->item_full_span() && layout_state.is_end_full_span_;\n    }\n  }\n}\n\nvoid StaggeredGridLayoutManager::OnBatchLayoutChildren() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_BATCH_CHILDREN,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  OnPrepareForLayoutChildren();\n\n  // Note: To avoid nested invoking OnBatchLayoutChildren,\n  // StartInterceptListElementUpdated() and StopInterceptListElementUpdated()\n  // need to be invoked at the begin or end of OnBatchLayoutChildren().\n  list_container_->StartInterceptListElementUpdated();\n\n  LayoutState layout_state(span_count_);\n  layout_state.latest_updated_content_offset_ = content_offset_;\n\n  // step 1. Update anchor info and layout all item_holders\n  ListAnchorManager::AnchorInfo anchor_info;\n  InitLayoutAndAnchor(anchor_info, kInvalidIndex);\n  list_container_->list_event_manager()->SendAnchorDebugInfoIfNeeded(\n      anchor_info);\n\n  // step 2. Invoke batch render.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_BATCH_RENDER);\n  LayoutInvalidItemHolder(0);\n  list_children_helper_->UpdateOnScreenChildren(list_orientation_helper_.get(),\n                                                content_offset_);\n  list_container_->list_adapter()->BindItemHolders(\n      list_children_helper_->on_screen_children());\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 3. Invoke OnLayoutChildren after batch render.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    GRID_LAYOUT_MANAGER_LAYOUT_CHILDREN_INTERNAL);\n  OnLayoutChildrenInternal(anchor_info, layout_state);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 4. Handle layout result: recycle and update layout to platform.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_LAYOUT_AFTER);\n  OnLayoutAfter();\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n}\n\nvoid StaggeredGridLayoutManager::OnLayoutChildren(\n    bool is_component_finished /* = false */, int component_index /* = -1 */) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_LAYOUT_CHILDREN,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  if (!list_container_ || !list_children_helper_) {\n    return;\n  }\n\n  OnPrepareForLayoutChildren();\n\n  list_container_->StartInterceptListElementUpdated();\n  LayoutState layout_state(span_count_);\n  layout_state.latest_updated_content_offset_ = content_offset_;\n\n  // step 1. Update anchor info and layout all item_holders\n  ListAnchorManager::AnchorInfo anchor_info;\n  InitLayoutAndAnchor(anchor_info, component_index);\n  list_container_->list_event_manager()->SendAnchorDebugInfoIfNeeded(\n      anchor_info);\n\n  // step 2. Fill after find anchor.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    GRID_LAYOUT_MANAGER_HANDLE_CONTENT_SIZE_AND_OFFSET);\n  OnLayoutChildrenInternal(anchor_info, layout_state);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // step 3. Handle layout result: recycle and update layout to platform.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_LAYOUT_AFTER);\n  OnLayoutAfter();\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n}\n\nvoid StaggeredGridLayoutManager::OnLayoutChildrenInternal(\n    ListAnchorManager::AnchorInfo& anchor_info, LayoutState& layout_state) {\n  // Handle empty data source.\n  if (list_container_->GetDataCount() == 0) {\n    content_size_ = GetTargetContentSize();\n    SetContentOffset(0.f);\n    FlushContentSizeAndOffsetToPlatform(\n        layout_state.latest_updated_content_offset_, true);\n    layout_state.latest_updated_content_offset_ = content_offset_;\n    // Note: need update on screen children.\n    list_children_helper_->UpdateOnScreenChildren(\n        list_orientation_helper_.get(), content_offset_);\n    return;\n  }\n\n  // step 1. Bind all visible ItemHolders.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    GRID_LAYOUT_MANAGER_BIND_ALL_VISIBLE_ITEM, \"anchor_index\",\n                    anchor_info.index_);\n  bool should_fill = BindAllVisibleItemHolders();\n  while (should_fill) {\n    LayoutInvalidItemHolder(0);\n    content_size_ = GetTargetContentSize();\n    list_anchor_manager_->AdjustContentOffsetWithAnchor(anchor_info,\n                                                        content_offset_);\n    should_fill = BindAllVisibleItemHolders();\n  }\n  LayoutInvalidItemHolder(0);\n  content_size_ = GetTargetContentSize();\n  list_anchor_manager_->AdjustContentOffsetWithAnchor(anchor_info,\n                                                      content_offset_);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n  if (!list_container_->enable_batch_render()) {\n    // TODO(dingwang.wxx): If use initial-scroll-index, this may lead to fill\n    // from the item 0 to initial-scroll-index. So we can remove this logic.\n    TRACE_EVENT(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_FILL);\n    layout_state.Reset(span_count_);\n    layout_state.layout_direction_ = LayoutDirection::kLayoutToStart;\n    UpdateStartAndEndLinesStatus(layout_state);\n    Fill(layout_state);\n  }\n\n  // step 2. Update content size and content offset.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    GRID_LAYOUT_MANAGER_HANDLE_CONTENT_SIZE_AND_OFFSET);\n  LayoutInvalidItemHolder(0);\n  content_size_ = GetTargetContentSize();\n  list_anchor_manager_->AdjustContentOffsetWithAnchor(anchor_info,\n                                                      content_offset_);\n  // step 2.5 Update sticky items.\n  UpdateStickyItemsAfterLayout(anchor_info);\n  FlushContentSizeAndOffsetToPlatform(\n      layout_state.latest_updated_content_offset_, true);\n  layout_state.latest_updated_content_offset_ = content_offset_;\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n\n  // The previous AdjustOffsetWithAnchor was called twice(the second one is\n  // caused by sticky), so the scrolled value should be set only when both of\n  // these calls have finished\n  list_anchor_manager_->MarkScrolledInitialScrollIndex();\n\n  // step 3. Handle Preload.\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    GRID_LAYOUT_MANAGER_HANDLE_PRELOAD_IF_NEED);\n  // Note: need update on screen children.\n  list_children_helper_->UpdateOnScreenChildren(list_orientation_helper_.get(),\n                                                content_offset_);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n}\n\nvoid StaggeredGridLayoutManager::OnLayoutAfter() {\n  HandleLayoutOrScrollResult(true);\n\n  // Send layout events.\n  // Note: Events has to be called after StopInterceptListElementUpdated to\n  // avoid reenter in worklet\n  list_container_->StopInterceptListElementUpdated();\n  float scroll_delta = content_offset_ - last_content_offset_;\n  last_content_offset_ = content_offset_;\n  list_container_->list_event_manager()->RecordVisibleItemIfNeeded(false);\n  EventSource event_source = list_container_->has_valid_diff()\n                                 ? EventSource::kDiff\n                                 : EventSource::kLayout;\n  SendLayoutCompleteEvent();\n  SendScrollEvents(scroll_delta, content_offset_, event_source);\n  list_container_->ClearValidDiff();\n}\n\nvoid StaggeredGridLayoutManager::HandleLayoutOrScrollResult(bool is_layout) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_HANDLE_LAYOUT_OR_SCROLL,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  if (list_container_->enable_batch_render() ||\n      list_container_->enable_insert_platform_view_operation()) {\n    // batch render.\n    ListLayoutManager::HandleLayoutOrScrollResult(is_layout);\n  } else {\n    // No batch render.\n    ListAdapter* list_adapter = list_container_->list_adapter();\n    // 1. Recycle off-screen or off-preload's item holder.\n    RecycleOffScreenItemHolders();\n    if (is_layout) {\n      // 2. Recycle all removed child.\n      list_adapter->RecycleRemovedItemHolders();\n    }\n    // 3. Update layout info to platform.\n    list_children_helper_->ForEachChild(\n        [this, list_adapter](ItemHolder* item_holder) {\n          item_holder->UpdateLayoutToPlatform(\n              content_size_, GetWidth(),\n              list_adapter->GetItemElementDelegate(item_holder));\n          return false;\n        });\n    list_container_->FlushPatching();\n  }\n}\n\n/**\n Fill\n This algorithm first searches for a valid layout trunk where all item_holders\n are either bound or binding in multi-thread mode. Afterward, we calculate the\n top and bottom positions to determine if there is a need to fill any blank\n space. If the top section needs to be filled, we call FillToStart. If there is\n a gap in the bottom section, we call FillToEnd. Note that calling the\n FillToStart function may cause layout changes since the layout always starts\n from index 0. Binding may also cause height changes, which can result in\n column-index and offset adjustments. Therefore, after calling FillToStart, we\n must always call FillToEnd to ensure that no extra gaps are created during the\n FillToStart process.\n */\nvoid StaggeredGridLayoutManager::Fill(LayoutState& layout_state) {\n  if (layout_state.layout_direction_ == LayoutDirection::kLayoutToEnd) {\n    // If layoutToEnd, only need to fill to end.\n    FillToEnd(layout_state);\n    LayoutInvalidItemHolder(0);\n  } else {\n    FillToStart(layout_state);\n    // Fill to end if end is not completely filled.\n    layout_state.layout_direction_ = LayoutDirection::kLayoutToEnd;\n    UpdateStartAndEndLinesStatus(layout_state);\n    FillToEnd(layout_state);\n    // Need layout all item_holder to avoid discontinuous layout which may cause\n    // white space\n    LayoutInvalidItemHolder(0);\n  }\n}\n\nvoid StaggeredGridLayoutManager::FillToEnd(LayoutState& layout_state) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, STAGGERED_GRID_LAYOUT_MANAGER_FILL_TO_END);\n  int next_item_index = *std::max_element(layout_state.end_index.begin(),\n                                          layout_state.end_index.end());\n  next_item_index += static_cast<int>(layout_state.layout_direction_);\n  const int data_count = list_container_->GetDataCount();\n  bool need_to_fill = next_item_index >= 0 && next_item_index < data_count &&\n                      HasRemainSpaceToFillEnd(next_item_index, layout_state);\n  while (need_to_fill) {\n    LayoutChunkToEnd(next_item_index, layout_state, false);\n    next_item_index += static_cast<int>(layout_state.layout_direction_);\n    need_to_fill = next_item_index >= 0 && next_item_index < data_count &&\n                   HasRemainSpaceToFillEnd(next_item_index, layout_state);\n  }\n}\n\nvoid StaggeredGridLayoutManager::FillToStart(LayoutState& layout_state) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, STAGGERED_GRID_LAYOUT_MANAGER_FILL_TO_START);\n  // If layoutToStart, fill to start then fill to end\n  LayoutInvalidItemHolder(0);\n  // Fill to start first\n  UpdateStartAndEndLinesStatus(layout_state);\n  auto min_start_lines_height = std::min_element(\n      layout_state.start_lines.begin(), layout_state.start_lines.end());\n  int min_start_col = static_cast<int>(\n      std::distance(layout_state.start_lines.begin(), min_start_lines_height));\n  int min_start_index = layout_state.start_index[min_start_col];\n  // Store the delta before layout\n  ItemHolder* scroll_anchor_item_holder =\n      list_container_->GetItemHolderForIndex(min_start_index);\n  float start_align_delta =\n      list_orientation_helper_->GetDecoratedStart(scroll_anchor_item_holder) -\n      content_offset_;\n  int next_item_index = kInvalidIndex;\n  bool need_fill = HasRemainSpaceToFillStart(layout_state) &&\n                   (next_item_index = FindNextIndexToFillStart(layout_state)) !=\n                       kInvalidIndex;\n  while (need_fill) {\n    bool item_size_changed = false;\n    ItemHolder* item_holder =\n        list_container_->GetItemHolderForIndex(next_item_index);\n    if (item_holder) {\n      float before_size =\n          list_orientation_helper_->GetDecoratedMeasurement(item_holder);\n      list_container_->list_adapter()->BindItemHolder(item_holder,\n                                                      next_item_index);\n      item_size_changed = base::FloatsNotEqual(\n          before_size,\n          list_orientation_helper_->GetDecoratedMeasurement(item_holder));\n    }\n    if (item_size_changed) {\n      // If item_holder really bound and size did changed, trigger the layout.\n      LayoutInvalidItemHolder(0);\n      UpdateStartAndEndLinesStatus(layout_state);\n    } else {\n      // If size didn't change, only update start lines and start indexes\n      if (item_holder->item_full_span()) {\n        std::fill(layout_state.start_lines.begin(),\n                  layout_state.start_lines.end(),\n                  list_orientation_helper_->GetDecoratedStart(item_holder));\n        std::fill(layout_state.start_index.begin(),\n                  layout_state.start_index.end(), next_item_index);\n        // Note: need to update is_start_full_span_ after update start_lines and\n        // start_index.\n        layout_state.is_start_full_span_ = true;\n      } else {\n        int span_index = item_holder->item_col_index();\n        layout_state.start_lines[span_index] =\n            list_orientation_helper_->GetDecoratedStart(item_holder);\n        layout_state.start_index[span_index] = next_item_index;\n        // Note: need to update is_start_full_span_ after update start_lines and\n        // start_index.\n        layout_state.is_start_full_span_ = false;\n      }\n    }\n    float target_content_offset =\n        list_orientation_helper_->GetDecoratedStart(scroll_anchor_item_holder) -\n        start_align_delta;\n    content_size_ = GetTargetContentSize();\n    SetContentOffset(target_content_offset);\n    // If still not filled, trigger next fill.\n    need_fill = HasRemainSpaceToFillStart(layout_state) &&\n                (next_item_index = FindNextIndexToFillStart(layout_state)) !=\n                    kInvalidIndex;\n  }\n}\n\nvoid StaggeredGridLayoutManager::LayoutChunkToEnd(int current_index,\n                                                  LayoutState& layout_state,\n                                                  bool skip_bind) {\n  if (!list_container_ || !list_children_helper_ || !list_orientation_helper_) {\n    return;\n  }\n  // Init current layout item_holder\n  ItemHolder* item_holder =\n      list_container_->GetItemHolderForIndex(current_index);\n  if (!item_holder || list_container_->list_adapter()->IsRemoved(item_holder)) {\n    return;\n  }\n  item_holder->SetOrientation(orientation());\n  if (!skip_bind) {\n    list_container_->list_adapter()->BindItemHolder(item_holder, current_index);\n  }\n  float main_axis_position =\n      CalculateMainAxisPosition(item_holder, layout_state);\n  float cross_axis_position = CalculateCrossAxisPosition(item_holder);\n  if (orientation_ == Orientation::kVertical) {\n    item_holder->UpdateLayoutFromManager(cross_axis_position,\n                                         main_axis_position);\n  } else {\n    item_holder->UpdateLayoutFromManager(main_axis_position,\n                                         cross_axis_position);\n  }\n}\n\nbool StaggeredGridLayoutManager::BindAllVisibleItemHolders() {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_BIND_ALL_VISIBLE_ITEM);\n  // Bind all visible item_holders\n  bool should_fill = false;\n  list_children_helper_->ForEachChild(\n      [this, &should_fill](ItemHolder* item_holder) {\n        if (IntersectVisibleArea(item_holder)) {\n          should_fill = list_container_->list_adapter()->BindItemHolder(\n                            item_holder, item_holder->index()) ||\n                        should_fill;\n        }\n        return false;\n      });\n  return should_fill;\n}\n\nvoid StaggeredGridLayoutManager::LayoutInvalidItemHolder(\n    int first_invalid_index) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY,\n              STAGGERED_GRID_LAYOUT_MANAGER_LAYOUT_INVALID_ITEM,\n              \"first_invalid_index\", first_invalid_index);\n  if (list_container_->GetDataCount() == 0) {\n    for (auto& column_index : column_indexes_) {\n      column_index.clear();\n    }\n    return;\n  }\n  // (TODO)fangzhou.fz: here should not be default first_invalid_index 0.\n  // Optimize it to improve performance.\n  const int data_count = list_container_->GetDataCount();\n  if (first_invalid_index < 0 || first_invalid_index > data_count - 1) {\n    return;\n  }\n  LayoutState layout_state(span_count_, LayoutDirection::kLayoutToEnd);\n  for (int i = 0; i < span_count_; ++i) {\n    auto& column_index = column_indexes_[i];\n    if (!column_index.empty()) {\n      int erase_index = static_cast<int>(column_index.size());\n      for (int j = erase_index - 1; j >= 0; --j) {\n        if (column_index[j] < first_invalid_index) {\n          break;\n        }\n        erase_index = j;\n      }\n      column_index.erase(column_index.begin() + erase_index,\n                         column_index.end());\n      if (!column_index.empty()) {\n        int end_index = *(column_index.rbegin());\n        layout_state.end_index[i] = end_index;\n        layout_state.end_lines[i] = list_orientation_helper_->GetDecoratedEnd(\n            list_container_->GetItemHolderForIndex(end_index));\n      }\n    }\n  }\n  for (int i = first_invalid_index; i < data_count; ++i) {\n    LayoutChunkToEnd(i, layout_state, true);\n  }\n}\n\nfloat StaggeredGridLayoutManager::CalculateMainAxisPosition(\n    ItemHolder* item_holder, LayoutState& layout_state) {\n  // Use min and max elements to layout current item_holder and update end_lines\n  auto& end_lines = layout_state.end_lines;\n  auto& end_indexes = layout_state.end_index;\n  if (end_lines.empty() || end_indexes.empty()) {\n    return 0.f;\n  }\n  float item_size = 0.f;\n  float main_axis_position = 0.f;\n  int item_index = item_holder->index();\n  if (item_holder->item_full_span()) {\n    // Handle full_span items\n    main_axis_position = *std::max_element(end_lines.begin(), end_lines.end());\n    float top_inset = 0.f;\n\n    if (item_index > 0) {\n      top_inset = main_axis_gap_;\n    } else {\n      main_axis_position += list_orientation_helper_->GetStartAfterPadding();\n    }\n    // update top inset\n    item_holder->SetTopInset(top_inset);\n    // Note: After updating top_inset, we can get new item_size because\n    // item_size contains the top_inset of item holder.\n    item_size = list_orientation_helper_->GetDecoratedMeasurement(item_holder);\n    // Update end_lines and end_indexes.\n    std::fill(end_lines.begin(), end_lines.end(),\n              main_axis_position + item_size);\n    std::fill(end_indexes.begin(), end_indexes.end(), item_index);\n    layout_state.is_end_full_span_ = true;\n    // Note: don't forget to add top_inset to main_axis_position.\n    main_axis_position += top_inset;\n    item_holder->SetItemColIndex(0);\n    for (auto& info : column_indexes_) {\n      info.push_back(item_index);\n    }\n  } else {\n    int min_span_index =\n        GetMinEndSpanItemInfoForEndLines(layout_state).span_index_;\n    main_axis_position = end_lines[min_span_index];\n    // Only add topInset to non_zero item_holder\n    // When layout_manager changed, the previous top_inset setting should be\n    // overrided\n    float top_inset = 0;\n    if (end_lines[min_span_index] > 0) {\n      top_inset = main_axis_gap_;\n    } else {\n      main_axis_position += list_orientation_helper_->GetStartAfterPadding();\n    }\n    // update top inset\n    item_holder->SetTopInset(top_inset);\n    // Note: After updating top_inset, we can get new item_size because\n    // item_size contains the top_inset of item holder.\n    item_size = list_orientation_helper_->GetDecoratedMeasurement(item_holder);\n    // Update end_lines and end_indexes.\n    end_lines[min_span_index] = main_axis_position + item_size;\n    end_indexes[min_span_index] = item_index;\n    layout_state.is_end_full_span_ = false;\n    // Note: don't forget to add top_inset to main_axis_position.\n    main_axis_position += top_inset;\n    item_holder->SetItemColIndex(min_span_index);\n    column_indexes_[min_span_index].push_back(item_index);\n  }\n  return main_axis_position;\n}\n\nfloat StaggeredGridLayoutManager::CalculateCrossAxisPosition(\n    const ItemHolder* item_holder) {\n  // padding + column_index * (column_size + cross_axis_gap)\n  float column_size =\n      list_orientation_helper_->GetDecoratedMeasurementInOther(item_holder) > 0\n          ? list_orientation_helper_->GetDecoratedMeasurementInOther(\n                item_holder)\n          : list_orientation_helper_->GetMeasurementInOther() / span_count_;\n  return list_orientation_helper_->GetStartAfterPaddingInOther() +\n         item_holder->item_col_index() * (column_size + cross_axis_gap_);\n}\n\n/**\n scroll\n */\nvoid StaggeredGridLayoutManager::ScrollByInternal(float content_offset,\n                                                  float original_offset,\n                                                  bool from_platform) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_SCROLL_BY_INTERNAL,\n              [this](lynx::perfetto::EventContext ctx) {\n                UpdateTraceDebugInfo(ctx.event());\n              });\n  float delta = content_offset - last_content_offset_;\n  if (!list_container_ || fabs(delta) < 10e-6) {\n    FlushContentSizeAndOffsetToPlatform(content_offset, false);\n    last_content_offset_ = content_offset_;\n    return;\n  }\n  float content_offset_before_adjustment =\n      from_platform ? content_offset : content_offset_;\n  list_container_->StartInterceptListElementUpdated();\n  // Fill\n  LayoutState layout_state(span_count_, delta > 0\n                                            ? LayoutDirection::kLayoutToEnd\n                                            : LayoutDirection::kLayoutToStart);\n  UpdateStartAndEndLinesStatus(layout_state);\n  SetContentOffset(content_offset);\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY,\n                    GRID_LAYOUT_MANAGER_SCROLL_BY_INTERNAL_FILL);\n  Fill(layout_state);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n  content_size_ = GetTargetContentSize();\n  UpdateStickyItems();\n  // TODO(fangzhou.fz) adjust offset for sticky-item\n  FlushContentSizeAndOffsetToPlatform(content_offset_before_adjustment, false);\n  list_children_helper_->UpdateOnScreenChildren(list_orientation_helper_.get(),\n                                                content_offset_);\n\n  TRACE_EVENT_BEGIN(LYNX_TRACE_CATEGORY, GRID_LAYOUT_MANAGER_ON_SCROLL_AFTER);\n  OnScrollAfter(original_offset);\n  TRACE_EVENT_END(LYNX_TRACE_CATEGORY);\n}\n\nvoid StaggeredGridLayoutManager::OnScrollAfter(float original_offset) {\n  HandleLayoutOrScrollResult(false);\n  // Send scroll event\n  // Events has to be called after StopInterceptListElementUpdated to avoid\n  // reenter in worklet\n  list_container_->StopInterceptListElementUpdated();\n  float scroll_delta = content_offset_ - last_content_offset_;\n  last_content_offset_ = content_offset_;\n  SendScrollEvents(scroll_delta, original_offset, EventSource::kScroll);\n}\n\nfloat StaggeredGridLayoutManager::GetTargetContentSize() {\n  // Note: content size == padding top + sum of children's height + padding\n  // bottom\n  float content_size = 0.f;\n  for (const auto& column_index : column_indexes_) {\n    if (!column_index.empty()) {\n      ItemHolder* item_holder =\n          list_container_->GetItemHolderForIndex(column_index.back());\n      content_size = std::max(\n          content_size,\n          list_orientation_helper_->GetDecoratedStart(item_holder) +\n              list_orientation_helper_->GetDecoratedMeasurement(item_holder));\n    }\n  }\n  // Note: end padding in main axis should be considered.\n  return list_container_->RoundValueToPixelGrid(\n      content_size + list_orientation_helper_->GetEndPadding());\n}\n\nbool StaggeredGridLayoutManager::IntersectVisibleArea(\n    const ItemHolder* item_holder) const {\n  float container_size = list_orientation_helper_->GetMeasurement();\n  return base::FloatsLargerOrEqual(\n             content_offset_ + container_size,\n             list_orientation_helper_->GetDecoratedStart(item_holder)) &&\n         base::FloatsLargerOrEqual(\n             list_orientation_helper_->GetDecoratedEnd(item_holder),\n             content_offset_);\n}\n\nbool StaggeredGridLayoutManager::CurrentLineHasRemainSpaceToFillEnd(\n    float end_line) const {\n  return end_line <\n         std::min(content_offset_ + list_orientation_helper_->GetMeasurement(),\n                  content_size_ - list_orientation_helper_->GetEndPadding());\n}\n\nbool StaggeredGridLayoutManager::HasRemainSpaceToFillEnd(\n    int next_valid_item_index, LayoutState& layout_state) const {\n  const auto& end_indexes = layout_state.end_index;\n  const auto& end_lines = layout_state.end_lines;\n  if (end_indexes.empty() || end_lines.empty()) {\n    return false;\n  }\n  if (layout_state.is_end_full_span_) {\n    // Case 1: end index is full span.\n    //        col-0            col-1          col-2\n    //   +--------------++--------------++--------------+\n    //   | Item0(200px) || Item1(100px) || Item2(150px) |\n    //   |              ||              ||              |\n    //   |              |+______________+|              |\n    //   |              |                +______________+\n    //   +______________+                               |\n    //   +----------------------------------------------+\n    //   |          FullSpanItem3(end index)            |\n    //   +----------------------------------------------+\n    return CurrentLineHasRemainSpaceToFillEnd(end_lines[0]);\n  } else {\n    // Case 2: item in end indexes is not full span.\n    ListAdapter* list_adapter = list_container_->list_adapter();\n    if (list_adapter->IsFullSpanAtIndex(next_valid_item_index)) {\n      // Case 2.1 the next item is full span.\n      //        col-0            col-1          col-2\n      //   +--------------++--------------++--------------+\n      //   | Item0(200px) || Item1(100px) || Item2(150px) |\n      //   |              ||              ||              |\n      //   |              |+______________+|              |\n      //   |              |                +______________+\n      //   +______________+                               |\n      //   +----------------------------------------------+\n      //   |          FullSpanItem3(next item)            |\n      //   +----------------------------------------------+\n      float max_end_line = *std::max_element(layout_state.end_lines.begin(),\n                                             layout_state.end_lines.end());\n      return CurrentLineHasRemainSpaceToFillEnd(max_end_line);\n    } else {\n      // Case 2.2 the next item is not full span.\n      //        col-0            col-1          col-2\n      //   +--------------++--------------++--------------+\n      //   | Item0(200px) || Item1(100px) || Item2(200px) |\n      //   |              ||              ||              |\n      //   |              |+______________+|              |\n      //   |              |+______________+|              |\n      //   +______________+|              |+______________+\n      //                   |    Item3     |\n      //                   | (next item)  |\n      //                   |              |\n      //                   +______________+\n      float min_end_line = *std::min_element(layout_state.end_lines.begin(),\n                                             layout_state.end_lines.end());\n      return CurrentLineHasRemainSpaceToFillEnd(min_end_line);\n    }\n  }\n}\n\nbool StaggeredGridLayoutManager::HasRemainSpaceToFillStart(\n    LayoutState& layout_state) const {\n  const auto& start_indexes = layout_state.start_index;\n  const auto& start_lines = layout_state.start_lines;\n  if (start_indexes.empty() || start_lines.empty()) {\n    return false;\n  }\n  const float start_after_padding =\n      list_orientation_helper_->GetStartAfterPadding();\n  if (layout_state.is_start_full_span_) {\n    // Case 1: start index is full span\n    float start_full_span_line = start_lines[0];\n    return start_full_span_line > content_offset_ &&\n           start_full_span_line > start_after_padding;\n  } else {\n    // Case 2: start indexes has no full span item\n    for (int i = 0; i < static_cast<int>(start_lines.size()) &&\n                    i < list_container_->GetDataCount();\n         ++i) {\n      if (start_lines[i] > content_offset_ &&\n          start_lines[i] > start_after_padding) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n\nint StaggeredGridLayoutManager::FindNextIndexToFillStart(\n    LayoutState& layout_state) const {\n  const auto& start_indexes = layout_state.start_index;\n  const auto& start_lines = layout_state.start_lines;\n  if (start_indexes.empty() || start_lines.empty()) {\n    return kInvalidIndex;\n  }\n  ListAdapter* list_adapter = list_container_->list_adapter();\n  if (layout_state.is_start_full_span_) {\n    // Case 1 start index is full span\n    int start_full_span_index = start_indexes[0];\n    if (start_full_span_index == 0) {\n      // Case 1.1 start index is first item.\n      return kInvalidIndex;\n    } else if (list_adapter->IsFullSpanAtIndex(start_full_span_index - 1)) {\n      // Case 1.2 start index - 1 is full span item.\n      return start_full_span_index - 1;\n    } else {\n      // Case 1.3. Find max end item index before start_full_span_index\n      //        col-0            col-1          col-2\n      //   +--------------++--------------++--------------+\n      //   | Item0(200px) || Item1(100px) || Item2(150px) |\n      //   |              ||              ||              |\n      //   | (next item)  |+______________+|              |\n      //   |              |                +______________+\n      //   +______________+                               |\n      //   +----------------------------------------------+\n      //   |              FullSpanItem3(50)               |\n      //   +----------------------------------------------+\n      for (const auto& column_index : column_indexes_) {\n        int item_index =\n            GetItemIndexBeforeTargetIndex(column_index, start_full_span_index);\n        if (item_index != kInvalidIndex) {\n          return item_index;\n        }\n      }\n    }\n  } else {\n    // Case 2. start index is not full span.\n    // Here consider the case:\n    //\n    //        col-0            col-1          col-2\n    //   +--------------++--------------++--------------+\n    //   | Item0(200px) || Item1(100px) || Item2(150px) |\n    //   |              ||              || (next item)  |\n    //   |              |+______________+|              |\n    //   |              |                +______________+\n    //   +______________+                               |\n    //   +----------------------------------------------+\n    //   |              FullSpanItem3(50)               |\n    //   +----------------------------------------------+\n    //\n    // If fill to start, the correct bind order should be: Item0, Item2, Item1.\n    // After bind Item0, we find that col-1 and col-2 has the same start line\n    // that larger than col-0. If we invoke max_element(start_lines) directly,\n    // we will get the col-1 is the max start line and use col-1 to fill, but in\n    // this case the correct col is col-2.\n    std::vector<SpanItemInfo> span_item_infos =\n        GetMaxEndSpanItemInfoForStartLines(layout_state);\n    if (!span_item_infos.empty()) {\n      for (const auto& span_item_info : span_item_infos) {\n        if (span_item_info.IsValid()) {\n          const auto& column_index =\n              column_indexes_[span_item_info.span_index_];\n          int next_item_index = GetItemIndexBeforeTargetIndex(\n              column_index, span_item_info.item_index_);\n          if (next_item_index != kInvalidIndex) {\n            return next_item_index;\n          }\n        }\n      }\n    }\n  }\n  return kInvalidIndex;\n}\n\nint StaggeredGridLayoutManager::GetItemIndexBeforeTargetIndex(\n    const std::vector<int>& span_indexes, int target_index) const {\n  if (!span_indexes.empty()) {\n    auto it = std::find(span_indexes.begin(), span_indexes.end(), target_index);\n    auto reverse_it = span_indexes.rend();\n    if (it != span_indexes.end() &&\n        (reverse_it = std::make_reverse_iterator(it)) != span_indexes.rend()) {\n      ItemHolder* next_item_holder =\n          list_container_->GetItemHolderForIndex(*reverse_it);\n      if (next_item_holder &&\n          base::FloatsLargerOrEqual(\n              list_orientation_helper_->GetDecoratedEnd(next_item_holder),\n              content_offset_)) {\n        return *reverse_it;\n      }\n    }\n  }\n  return kInvalidIndex;\n}\n\nstd::vector<StaggeredGridLayoutManager::SpanItemInfo>\nStaggeredGridLayoutManager::GetMaxEndSpanItemInfoForStartLines(\n    LayoutState& layout_state) const {\n  const auto& start_indexes = layout_state.start_index;\n  const auto& start_lines = layout_state.start_lines;\n  if (start_lines.empty()) {\n    return {{kInvalidIndex, kInvalidIndex}};\n  }\n  if (layout_state.is_start_full_span_) {\n    return {{0, start_indexes[0]}};\n  }\n  std::vector<SpanItemInfo> span_item_infos;\n  auto max_line_it = std::max_element(start_lines.begin(), start_lines.end());\n  for (int i = 0; i < static_cast<int>(start_lines.size()); ++i) {\n    if (base::FloatsEqual(start_lines[i], *max_line_it)) {\n      span_item_infos.emplace_back(i, start_indexes[i]);\n    }\n  }\n  return span_item_infos;\n}\n\nStaggeredGridLayoutManager::SpanItemInfo\nStaggeredGridLayoutManager::GetMinEndSpanItemInfoForEndLines(\n    LayoutState& layout_state) const {\n  const auto& end_indexes = layout_state.end_index;\n  const auto& end_lines = layout_state.end_lines;\n  if (end_lines.empty()) {\n    return {kInvalidIndex, kInvalidIndex};\n  }\n  if (layout_state.is_end_full_span_) {\n    return {0, end_indexes[0]};\n  }\n  auto it = std::min_element(end_lines.begin(), end_lines.end());\n  int span_index = static_cast<int>(std::distance(end_lines.begin(), it));\n  return {span_index, end_indexes[span_index]};\n}\n\n#if ENABLE_TRACE_PERFETTO\nvoid StaggeredGridLayoutManager::UpdateTraceDebugInfo(TraceEvent* event) const {\n  ListLayoutManager::UpdateTraceDebugInfo(event);\n  auto* list_type_info = event->add_debug_annotations();\n  list_type_info->set_name(\"list_type\");\n  list_type_info->set_string_value(\"waterfall\");\n}\n#endif\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/decoupled_staggered_grid_layout_manager.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_DECOUPLED_STAGGERED_GRID_LAYOUT_MANAGER_H_\n#define CORE_LIST_DECOUPLED_STAGGERED_GRID_LAYOUT_MANAGER_H_\n\n#include <vector>\n\n#include \"core/list/decoupled_list_layout_manager.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass StaggeredGridLayoutManager : public ListLayoutManager {\n public:\n  class SpanItemInfo {\n   public:\n    SpanItemInfo(int span_index = kInvalidIndex, int item_index = kInvalidIndex)\n        : span_index_(span_index), item_index_(item_index) {}\n\n    bool IsValid() const {\n      return span_index_ != kInvalidIndex && item_index_ != kInvalidIndex;\n    }\n\n    int span_index_{kInvalidIndex};\n    int item_index_{kInvalidIndex};\n  };\n\n  class LayoutState {\n   public:\n    LayoutState(int span_count, LayoutDirection layout_direction =\n                                    LayoutDirection::kLayoutToEnd)\n        : layout_direction_(layout_direction) {\n      start_lines.resize(span_count);\n      start_index.resize(span_count);\n      end_lines.resize(span_count);\n      end_index.resize(span_count);\n    }\n\n    void Reset(int span_count) {\n      layout_direction_ = LayoutDirection::kLayoutToEnd;\n      is_start_full_span_ = false;\n      is_end_full_span_ = false;\n      start_lines.assign(span_count, 0.f);\n      start_index.assign(span_count, 0);\n      end_lines.assign(span_count, 0.f);\n      end_index.assign(span_count, 0);\n    }\n\n    // Layout direction\n    LayoutDirection layout_direction_{LayoutDirection::kLayoutToEnd};\n    // The latest updated content offset.\n    float latest_updated_content_offset_{0.f};\n\n    bool is_start_full_span_{false};\n    bool is_end_full_span_{false};\n    // Top item_holds at each columns\n    std::vector<float> start_lines{};\n    std::vector<int> start_index{};\n    // Bottom item_holds at each columns\n    std::vector<float> end_lines{};\n    std::vector<int> end_index{};\n  };\n\n  StaggeredGridLayoutManager(ListContainerImpl* list_container_impl);\n  ~StaggeredGridLayoutManager() override = default;\n\n  void InitLayoutState() override;\n  void OnBatchLayoutChildren() override;\n  void OnLayoutChildren(bool is_component_finished = false,\n                        int component_index = -1) override;\n\n protected:\n  void ScrollByInternal(float content_offset, float original_offset,\n                        bool from_platform) override;\n  void LayoutInvalidItemHolder(int first_invalid_index) override;\n  float GetTargetContentSize() override;\n#if ENABLE_TRACE_PERFETTO\n  void UpdateTraceDebugInfo(TraceEvent* event) const override;\n#endif\n\n private:\n  // Update layout state\n  void UpdateStartAndEndLinesStatus(LayoutState& layout_state);\n\n  void OnLayoutChildrenInternal(ListAnchorManager::AnchorInfo& anchor_info,\n                                LayoutState& layout_state);\n\n  // Bind all visible item_holders to create a valid trunk for fill. Return true\n  // if a 'componentAtIndex' really called.\n  bool BindAllVisibleItemHolders();\n\n  void OnLayoutAfter();\n  void OnScrollAfter(float original_offset);\n  void HandleLayoutOrScrollResult(bool is_layout);\n\n  // Fill\n  void Fill(LayoutState& layout_state);\n  void FillToEnd(LayoutState& layout_state);\n  void FillToStart(LayoutState& layout_state);\n  void LayoutChunkToEnd(int current_index, LayoutState& layout_state,\n                        bool skip_bind);\n  float CalculateCrossAxisPosition(const ItemHolder* item_holder);\n  float CalculateMainAxisPosition(ItemHolder* item_holder,\n                                  LayoutState& layout_state);\n  // Detect whether this item_holder intersects current visible area.\n  bool IntersectVisibleArea(const ItemHolder* item_holder) const;\n  // Detect whether current trunk left empty space to fill. Both toStart and\n  // toEnd.\n  int FindNextIndexToFillStart(LayoutState& layout_state) const;\n  bool HasRemainSpaceToFillStart(LayoutState& layout_state) const;\n  int GetItemIndexBeforeTargetIndex(const std::vector<int>& span_indexes,\n                                    int target_index) const;\n  bool HasRemainSpaceToFillEnd(int next_valid_item_index,\n                               LayoutState& layout_state) const;\n  bool CurrentLineHasRemainSpaceToFillEnd(float end_line) const;\n  std::vector<SpanItemInfo> GetMaxEndSpanItemInfoForStartLines(\n      LayoutState& layout_state) const;\n  SpanItemInfo GetMinEndSpanItemInfoForEndLines(\n      LayoutState& layout_state) const;\n\n private:\n  std::vector<std::vector<int>> column_indexes_{};\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_DECOUPLED_STAGGERED_GRID_LAYOUT_MANAGER_H_\n"
  },
  {
    "path": "core/list/decoupled_staggered_grid_layout_manager_unittest.cc",
    "content": "\n// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/list/decoupled_staggered_grid_layout_manager.h\"\n\n#include <algorithm>\n#include <random>\n\n#include \"base/include/string/string_utils.h\"\n#include \"core/list/decoupled_list_container_impl.h\"\n#include \"core/value_wrapper/value_impl_lepus.h\"\n#include \"testing/fiber_data_source.h\"\n#include \"testing/mock_list_element.h\"\n#include \"testing/mock_list_item_element.h\"\n#include \"testing/utils.h\"\n\nnamespace lynx {\nnamespace list {\n\nusing ::testing::_;\nusing ::testing::Return;\n\nusing LayoutState = StaggeredGridLayoutManager::LayoutState;\n\nclass StaggeredGridLayoutManagerTest : public ::testing::Test {\n public:\n  StaggeredGridLayoutManagerTest() = default;\n  ~StaggeredGridLayoutManagerTest() override = default;\n\n  std::unique_ptr<MockListElement> mock_list_element_{nullptr};\n  std::unique_ptr<ListContainerImpl> list_container_impl_{nullptr};\n  std::shared_ptr<pub::PubValueFactoryDefault> value_factory_{nullptr};\n  ListLayoutManager* list_layout_manager_{nullptr};\n  ListAdapter* list_adapter_{nullptr};\n\n  class BaseLayoutResult {\n   public:\n    BaseLayoutResult(int data_count, int span_count)\n        : data_count_(data_count), span_count_(span_count) {\n      item_sizes_.resize(data_count, 0.f);\n      span_indexes_.resize(span_count, {});\n    }\n\n    void ReLayout() {\n      // Reset layout result.\n      span_indexes_.assign(span_count_, {});\n      content_size_ = 0.f;\n      // Re-layout\n      std::vector<float> span_sizes(span_count_, 0.f);\n      for (int i = 0; i < data_count_; ++i) {\n        int item_size = item_sizes_[i];\n        if (IsItemFullSpan(i)) {\n          float max_size =\n              *std::max_element(span_sizes.begin(), span_sizes.end());\n          for (int j = 0; j < span_count_; ++j) {\n            span_sizes[j] = max_size + item_size;\n            span_indexes_[j].push_back(i);\n          }\n        } else {\n          auto min_size_it =\n              std::min_element(span_sizes.begin(), span_sizes.end());\n          int min_span_index = std::distance(span_sizes.begin(), min_size_it);\n          span_sizes[min_span_index] += item_size;\n          span_indexes_[min_span_index].push_back(i);\n        }\n      }\n      content_size_ = *std::max_element(span_sizes.begin(), span_sizes.end());\n    }\n\n    bool IsItemFullSpan(int index) const {\n      return full_span_indexes_.find(index) != full_span_indexes_.end();\n    }\n\n    int data_count_{0};\n    int span_count_{0};\n    float content_offset_{0.f};\n    float content_size_{0.f};\n    std::vector<int> item_sizes_;\n    std::unordered_set<int> full_span_indexes_;\n    std::vector<std::vector<int>> span_indexes_;\n  };\n\n  void SetUp() override {\n    value_factory_ = std::make_shared<pub::PubValueFactoryDefault>();\n    mock_list_element_ = std::make_unique<MockListElement>();\n    list_container_impl_ = std::make_unique<ListContainerImpl>(\n        mock_list_element_.get(), value_factory_);\n    list_layout_manager_ = list_container_impl_->list_layout_manager();\n    list_adapter_ = list_container_impl_->list_adapter();\n  }\n\n  StaggeredGridLayoutManager* GetStaggeredGridLayoutManager() {\n    return static_cast<StaggeredGridLayoutManager*>(list_layout_manager_);\n  }\n\n  bool IsFullSpan(int index) {\n    static int full_span_flag = 5;\n    return !(index % full_span_flag);\n  }\n\n  ListOrientationHelper* GetOrientationHelper() {\n    return list_layout_manager_->list_orientation_helper_.get();\n  }\n\n  void InitFiberDataSource(int data_count, std::vector<int> estimated_sizes,\n                           bool mock_full_span) {\n    testing::InsertAction insert_action;\n    for (int i = 0; i < data_count; ++i) {\n      insert_action.insert_ops_.push_back(\n          {i, base::FormatString(\"list-item-%d\", i), estimated_sizes[i],\n           mock_full_span && IsFullSpan(i), false, false, true});\n    }\n    testing::FiberDataSource fiber_data_source{\n        .insert_action_ = insert_action,\n    };\n    LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(\n        FiberUpdateListInfo, lepus::Value(fiber_data_source.Resolve()));\n    list_container_impl_->ResolveAttribute(*key, value);\n    list_container_impl_->PropsUpdateFinish();\n  }\n\n  void InitLayoutAttrs(std::string list_type, int span_count,\n                       std::string scroll_orientation, float main_axis_gap,\n                       float cross_axis_gap, float list_main_size,\n                       float list_cross_size) {\n    {\n      LIST_CONTAINER_DEFINE_PROP_VALUE(ListType, String, list_type);\n      list_container_impl_->ResolveAttribute(*key, *value);\n      list_layout_manager_ = list_container_impl_->list_layout_manager();\n    }\n    {\n      LIST_CONTAINER_DEFINE_PROP_VALUE(SpanCount, Number, span_count);\n      list_container_impl_->ResolveAttribute(*key, *value);\n    }\n    {\n      LIST_CONTAINER_DEFINE_PROP_VALUE(ScrollOrientation, String,\n                                       scroll_orientation);\n      list_container_impl_->ResolveAttribute(*key, *value);\n    }\n    list_container_impl_->ResolveListAxisGap(\n        tasm::CSSPropertyID::kPropertyIDListMainAxisGap, main_axis_gap);\n    list_container_impl_->ResolveListAxisGap(\n        tasm::CSSPropertyID::kPropertyIDListCrossAxisGap, cross_axis_gap);\n    if (scroll_orientation == kPropValueScrollOrientationVertical) {\n      mock_list_element_->height_ = list_main_size;\n      mock_list_element_->width_ = list_cross_size;\n    } else {\n      mock_list_element_->height_ = list_cross_size;\n      mock_list_element_->width_ = list_main_size;\n    }\n    list_container_impl_->PropsUpdateFinish();\n  }\n\n  BaseLayoutResult GenerateBaseLayoutResult(int data_count, int span_count,\n                                            int min_item_size,\n                                            int max_item_size,\n                                            bool mock_full_span = false) {\n    BaseLayoutResult base_layout_result(data_count, span_count);\n    auto& item_sizes = base_layout_result.item_sizes_;\n    auto& span_indexes = base_layout_result.span_indexes_;\n    auto& full_span_indexes = base_layout_result.full_span_indexes_;\n    std::vector<float> span_sizes(span_count, 0.f);\n    std::mt19937 gen(0);\n    std::uniform_int_distribution<> dis(min_item_size, max_item_size);\n    for (int i = 0; i < data_count; ++i) {\n      bool is_full_span = mock_full_span && IsFullSpan(i);\n      int item_size = dis(gen);\n      if (is_full_span) {\n        float max_size =\n            *std::max_element(span_sizes.begin(), span_sizes.end());\n        for (int j = 0; j < span_count; ++j) {\n          span_sizes[j] = max_size + item_size;\n          span_indexes[j].push_back(i);\n        }\n        full_span_indexes.insert(i);\n      } else {\n        auto min_size_it =\n            std::min_element(span_sizes.begin(), span_sizes.end());\n        int min_span_index = std::distance(span_sizes.begin(), min_size_it);\n        span_sizes[min_span_index] += item_size;\n        span_indexes[min_span_index].push_back(i);\n      }\n      item_sizes[i] = item_size;\n    }\n    base_layout_result.content_size_ =\n        *std::max_element(span_sizes.begin(), span_sizes.end());\n    return base_layout_result;\n  }\n};\n\nTEST_F(StaggeredGridLayoutManagerTest, LayoutInvalidItemHolder) {\n  int data_count = 1000;\n  int span_count = 3;\n  float min_item_size = 50;\n  float max_item_size = 100;\n  bool mock_full_span = true;\n  BaseLayoutResult base_layout_result = GenerateBaseLayoutResult(\n      data_count, span_count, min_item_size, max_item_size, mock_full_span);\n  InitLayoutAttrs(\"waterfall\", span_count, \"vertical\", 0.f, 0.f, 2000.f,\n                  1000.f);\n  InitFiberDataSource(data_count, base_layout_result.item_sizes_,\n                      mock_full_span);\n\n  auto* waterfall_layout_manager = GetStaggeredGridLayoutManager();\n  waterfall_layout_manager->LayoutInvalidItemHolder(0);\n  EXPECT_TRUE(\n      base::FloatsEqual(waterfall_layout_manager->GetTargetContentSize(),\n                        base_layout_result.content_size_));\n  for (int i = 0; i < span_count; ++i) {\n    auto& basic_span_index = base_layout_result.span_indexes_[i];\n    auto& test_span_index = waterfall_layout_manager->column_indexes_[i];\n    EXPECT_EQ(test_span_index.size(), basic_span_index.size());\n    for (int j = 0; j < static_cast<int>(basic_span_index.size()); ++j) {\n      EXPECT_EQ(test_span_index[j], basic_span_index[j]);\n    }\n  }\n}\n\nTEST_F(StaggeredGridLayoutManagerTest, LayoutInvalidItemHolder1) {\n  int data_count = 1000;\n  int span_count = 3;\n  float min_item_size = 50;\n  float max_item_size = 100;\n  bool mock_full_span = true;\n  BaseLayoutResult base_layout_result = GenerateBaseLayoutResult(\n      data_count, span_count, min_item_size, max_item_size, mock_full_span);\n  InitLayoutAttrs(\"waterfall\", span_count, \"vertical\", 0.f, 0.f, 2000.f,\n                  1000.f);\n  InitFiberDataSource(data_count, base_layout_result.item_sizes_,\n                      mock_full_span);\n\n  auto* waterfall_layout_manager = GetStaggeredGridLayoutManager();\n  waterfall_layout_manager->LayoutInvalidItemHolder(0);\n\n  // Change item size of index 4 and 9.\n  list_container_impl_->GetItemHolderForIndex(4)->SetEstimatedSize(200);\n  list_container_impl_->GetItemHolderForIndex(9)->SetEstimatedSize(500);\n  base_layout_result.item_sizes_[4] = 200;\n  base_layout_result.item_sizes_[9] = 500;\n  base_layout_result.ReLayout();\n  // Layout from index 4.\n  waterfall_layout_manager->LayoutInvalidItemHolder(4);\n  EXPECT_TRUE(\n      base::FloatsEqual(waterfall_layout_manager->GetTargetContentSize(),\n                        base_layout_result.content_size_));\n\n  // Change item size of index 5 and 15 (full span).\n  list_container_impl_->GetItemHolderForIndex(5)->SetEstimatedSize(300);\n  list_container_impl_->GetItemHolderForIndex(15)->SetEstimatedSize(300);\n  base_layout_result.item_sizes_[5] = 300;\n  base_layout_result.item_sizes_[15] = 300;\n  base_layout_result.ReLayout();\n  // Layout from 7.\n  waterfall_layout_manager->LayoutInvalidItemHolder(5);\n  EXPECT_TRUE(\n      base::FloatsEqual(waterfall_layout_manager->GetTargetContentSize(),\n                        base_layout_result.content_size_));\n  // Check span index\n  for (int i = 0; i < span_count; ++i) {\n    auto& basic_span_index = base_layout_result.span_indexes_[i];\n    auto& test_span_index = waterfall_layout_manager->column_indexes_[i];\n    EXPECT_EQ(test_span_index.size(), basic_span_index.size());\n    for (int j = 0; j < static_cast<int>(basic_span_index.size()); ++j) {\n      EXPECT_EQ(test_span_index[j], basic_span_index[j]);\n    }\n  }\n}\n\nTEST_F(StaggeredGridLayoutManagerTest, HasRemainSpaceToFillEnd) {\n  int data_count = 100;\n  int span_count = 3;\n  float list_main_size = 2000.f;\n  float list_cross_size = 1000.f;\n  float content_offset = 1000.f;\n  float content_size = 10000.f;\n  bool mock_full_span = true;\n  InitLayoutAttrs(\"waterfall\", span_count, \"vertical\", 0.f, 0.f, list_main_size,\n                  list_cross_size);\n  InitFiberDataSource(data_count, std::vector<int>(data_count, 100),\n                      mock_full_span);\n  auto* waterfall_layout_manager = GetStaggeredGridLayoutManager();\n  waterfall_layout_manager->content_offset_ = content_offset;\n  waterfall_layout_manager->content_size_ = content_size;\n\n  // case 1. current end line is full span\n  // end_lines: 2999, 2999, 2999\n  // end_indexes: 10, 10, 10\n  LayoutState layout_state(span_count, LayoutDirection::kLayoutToEnd);\n  auto& end_lines = layout_state.end_lines;\n  auto& end_indexes = layout_state.end_index;\n  layout_state.is_end_full_span_ = true;\n  std::fill(end_lines.begin(), end_lines.end(),\n            list_main_size + content_offset - 1.f);\n  std::fill(end_indexes.begin(), end_indexes.end(), 10);\n  EXPECT_TRUE(\n      waterfall_layout_manager->HasRemainSpaceToFillEnd(0, layout_state));\n  layout_state.Reset(span_count);\n  layout_state.is_end_full_span_ = true;\n  // end_lines: 3001, 3001, 3001\n  // end_indexes: 10, 10, 10\n  std::fill(end_lines.begin(), end_lines.end(),\n            list_main_size + content_offset + 1.f);\n  std::fill(end_indexes.begin(), end_indexes.end(), 10);\n  EXPECT_FALSE(\n      waterfall_layout_manager->HasRemainSpaceToFillEnd(0, layout_state));\n\n  // case 2. current end line is not full span but next item is full span.\n  int next_bind_item_index = 10;\n  list_adapter_->adapter_helper_->full_spans_.insert(next_bind_item_index);\n  list_container_impl_->GetItemHolderForIndex(next_bind_item_index)\n      ->item_full_span_ = true;\n  // end_lines: 2950, 3000, 3050\n  // end_indexes: 7, 8, 9\n  layout_state.Reset(span_count);\n  for (int i = 0; i < span_count; ++i) {\n    end_lines[i] = list_main_size + content_offset + (i - 1) * 50.f;\n    end_indexes[i] = next_bind_item_index - span_count + i;\n  }\n  EXPECT_FALSE(waterfall_layout_manager->HasRemainSpaceToFillEnd(\n      next_bind_item_index, layout_state));\n  // end_lines: 2850, 2900, 2950\n  // end_indexes: 7, 8, 9\n  layout_state.Reset(span_count);\n  for (int i = 0; i < span_count; ++i) {\n    end_lines[i] = list_main_size + content_offset - (i + 1) * 50.f;\n    end_indexes[i] = next_bind_item_index - span_count + i;\n  }\n  EXPECT_TRUE(waterfall_layout_manager->HasRemainSpaceToFillEnd(\n      next_bind_item_index, layout_state));\n\n  // case 3. current end line is not full span and next item is not full span.\n  list_adapter_->adapter_helper_->full_spans_.erase(next_bind_item_index);\n  list_container_impl_->GetItemHolderForIndex(next_bind_item_index)\n      ->item_full_span_ = false;\n  // end_lines: 2950, 3000, 3050\n  // end_indexes: 7, 8, 9\n  layout_state.Reset(span_count);\n  for (int i = 0; i < span_count; ++i) {\n    end_lines[i] = list_main_size + content_offset + (i - 1) * 50.f;\n    end_indexes[i] = next_bind_item_index - span_count + i;\n  }\n  EXPECT_TRUE(waterfall_layout_manager->HasRemainSpaceToFillEnd(\n      next_bind_item_index, layout_state));\n  // end_lines: 2850, 2900, 2950\n  // end_indexes: 7, 8, 9\n  layout_state.Reset(span_count);\n  for (int i = 0; i < span_count; ++i) {\n    // 2850, 2900, 2950\n    end_lines[i] = list_main_size + content_offset - (i + 1) * 50.f;\n    end_indexes[i] = next_bind_item_index - span_count + i;\n  }\n  EXPECT_TRUE(waterfall_layout_manager->HasRemainSpaceToFillEnd(\n      next_bind_item_index, layout_state));\n  // end_lines: 3050, 3100, 3150\n  // end_indexes: 7, 8, 9\n  layout_state.Reset(span_count);\n  for (int i = 0; i < span_count; ++i) {\n    end_lines[i] = list_main_size + content_offset + (i + 1) * 50.f;\n    end_indexes[i] = next_bind_item_index - span_count + i;\n  }\n  EXPECT_FALSE(waterfall_layout_manager->HasRemainSpaceToFillEnd(\n      next_bind_item_index, layout_state));\n}\n\nTEST_F(StaggeredGridLayoutManagerTest, HasRemainSpaceToFillStart) {\n  int data_count = 100;\n  int span_count = 3;\n  float list_main_size = 2000.f;\n  float list_cross_size = 1000.f;\n  float content_offset = 1000.f;\n  float content_size = 10000.f;\n  bool mock_full_span = true;\n  InitLayoutAttrs(\"waterfall\", span_count, \"vertical\", 0.f, 0.f, list_main_size,\n                  list_cross_size);\n  InitFiberDataSource(data_count, std::vector<int>(data_count, 100),\n                      mock_full_span);\n  auto* waterfall_layout_manager = GetStaggeredGridLayoutManager();\n  waterfall_layout_manager->content_offset_ = content_offset;\n  waterfall_layout_manager->content_size_ = content_size;\n\n  // case 1. current start line is full span\n  LayoutState layout_state(span_count, LayoutDirection::kLayoutToStart);\n  auto& start_lines = layout_state.start_lines;\n  auto& start_indexes = layout_state.start_index;\n  // start_lines: 999, 999, 999\n  // start_indexes: 10, 10, 10\n  layout_state.is_start_full_span_ = true;\n  std::fill(start_lines.begin(), start_lines.end(), content_offset - 1.f);\n  std::fill(start_indexes.begin(), start_indexes.end(), 10);\n  EXPECT_FALSE(\n      waterfall_layout_manager->HasRemainSpaceToFillStart(layout_state));\n  // start_lines: 1001, 1001, 1001\n  // start_indexes: 10, 10, 10\n  layout_state.Reset(span_count);\n  layout_state.is_start_full_span_ = true;\n  std::fill(start_lines.begin(), start_lines.end(), content_offset + 1.f);\n  std::fill(start_indexes.begin(), start_indexes.end(), 10);\n  EXPECT_TRUE(\n      waterfall_layout_manager->HasRemainSpaceToFillStart(layout_state));\n\n  // case 2. current lines has no full span\n  // start_lines: 950, 1000, 1050\n  // start_indexes: 11, 12, 13\n  int next_bind_item_index = 10;\n  layout_state.Reset(span_count);\n  for (int i = 0; i < span_count; ++i) {\n    start_lines[i] = content_offset + (i - 1) * 50.f;\n    start_indexes[i] = next_bind_item_index + i + 1;\n  }\n  EXPECT_TRUE(\n      waterfall_layout_manager->HasRemainSpaceToFillStart(layout_state));\n  // start_lines: 850, 900, 950\n  // start_indexes: 11, 12, 13\n  layout_state.Reset(span_count);\n  for (int i = 0; i < span_count; ++i) {\n    start_lines[i] = content_offset - i * 50.f;\n    start_indexes[i] = next_bind_item_index + i + 1;\n  }\n  EXPECT_FALSE(\n      waterfall_layout_manager->HasRemainSpaceToFillStart(layout_state));\n}\n\nTEST_F(StaggeredGridLayoutManagerTest, GetItemIndexBeforeTargetIndex) {\n  int data_count = 1000;\n  int span_count = 3;\n  float min_item_size = 50;\n  float max_item_size = 100;\n  float list_main_size = 2000.f;\n  float list_cross_size = 1000.f;\n  bool mock_full_span = true;\n  float content_offset = 1000.f;\n  BaseLayoutResult base_layout_result = GenerateBaseLayoutResult(\n      data_count, span_count, min_item_size, max_item_size, mock_full_span);\n  InitLayoutAttrs(\"waterfall\", span_count, \"vertical\", 0.f, 0.f, list_main_size,\n                  list_cross_size);\n  InitFiberDataSource(data_count, base_layout_result.item_sizes_,\n                      mock_full_span);\n\n  auto* waterfall_layout_manager = GetStaggeredGridLayoutManager();\n  waterfall_layout_manager->LayoutInvalidItemHolder(0);\n  waterfall_layout_manager->content_offset_ = content_offset;\n  waterfall_layout_manager->content_size_ =\n      waterfall_layout_manager->GetTargetContentSize();\n  for (auto& span_indexes : waterfall_layout_manager->column_indexes_) {\n    for (int i = 0; i < static_cast<int>(span_indexes.size()); ++i) {\n      int target_index = span_indexes[i];\n      int result_index =\n          waterfall_layout_manager->GetItemIndexBeforeTargetIndex(span_indexes,\n                                                                  target_index);\n      if (target_index == 0 || i == 0) {\n        EXPECT_EQ(result_index, kInvalidIndex);\n      } else {\n        ItemHolder* next_item_holder =\n            list_container_impl_->GetItemHolderForIndex(span_indexes[i - 1]);\n        EXPECT_TRUE(next_item_holder != nullptr);\n        if (base::FloatsLargerOrEqual(\n                GetOrientationHelper()->GetDecoratedEnd(next_item_holder),\n                content_offset)) {\n          EXPECT_EQ(result_index, next_item_holder->index());\n        } else {\n          EXPECT_EQ(result_index, kInvalidIndex);\n        }\n      }\n    }\n  }\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/list_animation_manager.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/list_animation_manager.h\"\n\n#include \"core/list/decoupled_list_container_impl.h\"\n\nnamespace lynx {\nnamespace list {\n\nListAnimationManager::ListAnimationManager(\n    ListContainerImpl* list_container_impl)\n    : list_container_impl_(list_container_impl) {}\n\nItemElementDelegate* ListAnimationManager::GetItemElementDelegate(\n    ItemHolder* item_holder) {\n  return list_container_impl_->list_adapter()->GetItemElementDelegate(\n      item_holder);\n}\n\nvoid ListAnimationManager::DeferredDestroyItemHolder(ItemHolder* item_holder) {\n  ListChildrenHelper* list_children_helper =\n      list_container_impl_->list_children_helper();\n  list_children_helper->AddChild(\n      list_children_helper->deferred_destroy_children(), item_holder);\n}\n\nvoid ListAnimationManager::RecycleItemHolder(ItemHolder* item_holder) {\n  list_container_impl_->list_adapter()->RecycleItemHolder(item_holder);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/list_animation_manager.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_LIST_ANIMATION_MANAGER_H_\n#define CORE_LIST_LIST_ANIMATION_MANAGER_H_\n\n#include <memory>\n\n#include \"core/list/decoupled_item_holder.h\"\n#include \"core/list/decoupled_list_types.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListContainerImpl;\n\nclass ListAnimationManager : public ItemHolder::AnimationDelegate {\n public:\n  ListAnimationManager(ListContainerImpl* list_container_impl);\n\n  ~ListAnimationManager() override = default;\n\n  virtual void UpdateDiffResult(ListAdapterDiffResult result) = 0;\n\n  virtual void SetUpdateAnimation(bool update_animation) = 0;\n\n  ListAnimationType AnimationType() const override { return animation_type_; }\n\n  ItemElementDelegate* GetItemElementDelegate(ItemHolder* item_holder) override;\n\n  void DeferredDestroyItemHolder(ItemHolder* item_holder) override;\n\n  void RecycleItemHolder(ItemHolder* item_holder) override;\n\n  bool UpdateAnimation() const override { return update_animation_; }\n\n protected:\n  bool update_animation_{false};\n  ListAnimationType animation_type_{ListAnimationType::kNone};\n  ListContainerImpl* list_container_impl_{nullptr};\n};\n\nstd::unique_ptr<ListAnimationManager> CreateListAnimationManager(\n    ListContainerImpl* list_container_impl);\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_LIST_ANIMATION_MANAGER_H_\n"
  },
  {
    "path": "core/list/list_animation_manager_default.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/list_animation_manager_default.h\"\n\n#include <memory>\n\n#include \"core/list/decoupled_list_container_impl.h\"\n\nnamespace lynx {\nnamespace list {\n\nListAnimationManagerDefault::ListAnimationManagerDefault(\n    ListContainerImpl* list_container_impl)\n    : ListAnimationManager(list_container_impl) {}\n\nstd::unique_ptr<ListAnimationManager> CreateListAnimationManager(\n    ListContainerImpl* list_container_impl) {\n  return std::make_unique<ListAnimationManagerDefault>(list_container_impl);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/list_animation_manager_default.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_LIST_ANIMATION_MANAGER_DEFAULT_H_\n#define CORE_LIST_LIST_ANIMATION_MANAGER_DEFAULT_H_\n\n#include \"core/list/list_animation_manager.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListAnimationManagerDefault : public ListAnimationManager {\n public:\n  ListAnimationManagerDefault(ListContainerImpl* list_container_impl);\n  ~ListAnimationManagerDefault() override = default;\n\n  void UpdateDiffResult(ListAdapterDiffResult result) override {}\n\n  void SetUpdateAnimation(bool update_animation) override {}\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_LIST_ANIMATION_MANAGER_DEFAULT_H_\n"
  },
  {
    "path": "core/list/list_animation_manager_impl.cc",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/list/list_animation_manager_impl.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"core/list/decoupled_list_container_impl.h\"\n\nnamespace lynx {\nnamespace list {\n\nListAnimationManagerImpl::ListAnimationManagerImpl(\n    ListContainerImpl* list_container_impl)\n    : ListAnimationManager(list_container_impl) {}\n\nListAnimationManagerImpl::~ListAnimationManagerImpl() {\n  if (animator_ && animator_->IsRunning()) {\n    animator_->Stop();\n  }\n}\n\nvoid ListAnimationManagerImpl::UpdateDiffResult(ListAdapterDiffResult result) {\n  if (result == ListAdapterDiffResult::kNone) {\n    return;\n  }\n  if (animator_ && animator_->IsRunning()) {\n    animator_->Stop();\n  }\n  if (result ==\n      (ListAdapterDiffResult::kRemove | ListAdapterDiffResult::kUpdate)) {\n    animation_type_ = ListAnimationType::kRemove;\n  } else if (result == (ListAdapterDiffResult::kInsert |\n                        ListAdapterDiffResult::kUpdate) ||\n             result == ListAdapterDiffResult::kUpdate) {\n    animation_type_ = ListAnimationType::kInsert;\n  } else {\n    animation_type_ = ListAnimationType::kUpdate;\n  }\n  if (!animator_) {\n    InitializeAnimator();\n    InitializeAnimatorEvent();\n  } else if (animator_->IsRunning()) {\n    // TODO(dongjiajian): using lynx animator instead of the basic animator.\n    // the basic animator won't handle the case of the repeat `start()`.\n    // The destroy of the animation will trigger the cancel event of the\n    // animation.\n    animator_->DestroyAnimation();\n    InitializeAnimator();\n    InitializeAnimatorEvent();\n  }\n  animator_->Start();\n}\n\nvoid ListAnimationManagerImpl::SetUpdateAnimation(bool update_animation) {\n  if (update_animation_ && !update_animation && animator_ &&\n      animator_->IsRunning()) {\n    animator_->Stop();\n  }\n  update_animation_ = update_animation;\n}\n\nvoid ListAnimationManagerImpl::InitializeAnimator() {\n  starlight::AnimationData data;\n  data.duration = 300;\n  data.fill_mode = starlight::AnimationFillModeType::kForwards;\n  starlight::TimingFunctionData timing_function_data;\n  timing_function_data.timing_func = starlight::TimingFunctionType::kEaseIn;\n  data.timing_func = timing_function_data;\n  // basic animator requires a shared_ptr.\n  animator_ =\n      std::make_shared<animation::basic::LynxBasicAnimator>(std::move(data));\n}\n\nvoid ListAnimationManagerImpl::InitializeAnimatorEvent() {\n  animator_->RegisterCustomCallback(\n      [weak_ptr = WeakFromThis()](float progress) {\n        if (auto ptr = weak_ptr.get()) {\n          ptr->DoAnimationFrame(progress);\n        }\n      });\n  animator_->RegisterEventCallback(\n      [weak_ptr = WeakFromThis()]() {\n        if (auto ptr = weak_ptr.get()) {\n          ptr->EndAnimation();\n        }\n      },\n      animation::basic::Animation::EventType::End);\n  // The destroy of the animation will trigger the cancel event of the\n  // animation.\n  animator_->RegisterEventCallback(\n      [weak_ptr = WeakFromThis()]() {\n        if (auto ptr = weak_ptr.get()) {\n          ptr->EndAnimation();\n        }\n      },\n      animation::basic::Animation::EventType::Cancel);\n}\n\nvoid ListAnimationManagerImpl::DoAnimationFrame(float progress) {\n  ListChildrenHelper* list_children_helper =\n      list_container_impl_->list_children_helper();\n  const auto& on_screen_children = list_children_helper->on_screen_children();\n  std::vector<fml::WeakPtr<ItemHolder>> on_screen_children_snapshot;\n  on_screen_children_snapshot.reserve(on_screen_children.size());\n  for (auto* child : on_screen_children) {\n    if (child) {\n      on_screen_children_snapshot.emplace_back(child->WeakFromThis());\n    }\n  }\n  for (auto& weak_child : on_screen_children_snapshot) {\n    if (auto child = weak_child.get()) {\n      child->DoAnimationFrame(progress);\n    }\n  }\n  list_children_helper->ForEachChild(\n      list_children_helper->deferred_destroy_children(),\n      [progress](ItemHolder* item_holder) {\n        item_holder->DoAnimationFrame(progress);\n        return false;\n      });\n}\n\nvoid ListAnimationManagerImpl::EndAnimation() {\n  // 0. reset `animation_type_` first to avoid recursion of item destroy.\n  animation_type_ = ListAnimationType::kNone;\n\n  // 1. Need to destroy child after the animation.\n  ListChildrenHelper* list_children_helper =\n      list_container_impl_->list_children_helper();\n  list_children_helper->ForEachChild(\n      list_children_helper->deferred_destroy_children(),\n      [](ItemHolder* item_holder) {\n        item_holder->EndAnimation();\n        return false;\n      });\n  list_children_helper->ClearDeferredDestroyItemHolder();\n\n  // 2. Because we can't know exactly how many item holders we will create\n  // during animation, so we need to save layout infos in advance. Now clean\n  list_children_helper->ForEachChild([](ItemHolder* item_holder) {\n    item_holder->EndAnimation();\n    return false;\n  });\n}\n\nstd::unique_ptr<ListAnimationManager> CreateListAnimationManager(\n    ListContainerImpl* list_container_impl) {\n  return std::make_unique<ListAnimationManagerImpl>(list_container_impl);\n}\n\n}  // namespace list\n}  // namespace lynx\n"
  },
  {
    "path": "core/list/list_animation_manager_impl.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_LIST_ANIMATION_MANAGER_IMPL_H_\n#define CORE_LIST_LIST_ANIMATION_MANAGER_IMPL_H_\n\n#include <memory>\n\n#include \"base/include/fml/memory/weak_ptr.h\"\n#include \"core/animation/lynx_basic_animator/basic_animator.h\"\n#include \"core/list/list_animation_manager.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ListAnimationManagerImpl\n    : public fml::EnableWeakFromThis<ListAnimationManagerImpl>,\n      public ListAnimationManager {\n public:\n  ListAnimationManagerImpl(ListContainerImpl* list_container_impl);\n\n  ~ListAnimationManagerImpl() override;\n\n  void UpdateDiffResult(ListAdapterDiffResult result) override;\n\n  void SetUpdateAnimation(bool update_animation) override;\n\n private:\n  void InitializeAnimator();\n\n  void InitializeAnimatorEvent();\n\n  void DoAnimationFrame(float progress);\n\n  void EndAnimation();\n\n private:\n  std::shared_ptr<animation::basic::LynxBasicAnimator> animator_{nullptr};\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_LIST_ANIMATION_MANAGER_IMPL_H_\n"
  },
  {
    "path": "core/list/list_container_delegate.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_LIST_CONTAINER_DELEGATE_H_\n#define CORE_LIST_LIST_CONTAINER_DELEGATE_H_\n\n#include <memory>\n#include <vector>\n\n#include \"core/list/list_element_delegate.h\"\n#include \"core/list/list_item_element_delegate.h\"\n#include \"core/public/pipeline_option.h\"\n#include \"core/public/pub_value.h\"\n#include \"core/renderer/css/css_property_id.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ContainerDelegate {\n public:\n  virtual ~ContainerDelegate() = default;\n\n  virtual void OnAttachToElementManager() = 0;\n  virtual bool ResolveAttribute(const pub::Value& key,\n                                const pub::Value& value) = 0;\n  virtual void ResolveListAxisGap(tasm::CSSPropertyID id, float gap) = 0;\n  virtual void PropsUpdateFinish() = 0;\n  virtual void OnLayoutChildren(\n      const std::shared_ptr<tasm::PipelineOptions>& options) = 0;\n  virtual void FinishBindItemHolder(\n      ItemElementDelegate* list_item_delegate,\n      const std::shared_ptr<tasm::PipelineOptions>& options) = 0;\n  virtual void FinishBindItemHolders(\n      const std::vector<ItemElementDelegate*>& list_item_delegate_array,\n      const std::shared_ptr<tasm::PipelineOptions>& options) = 0;\n  virtual void ScrollByPlatformContainer(float content_offset_x,\n                                         float content_offset_y,\n                                         float original_x,\n                                         float original_y) = 0;\n  virtual void OnListItemLayoutUpdated(ItemElementDelegate* list_item) = 0;\n  virtual void OnAttachedToElementManager() = 0;\n  virtual void ScrollToPosition(int index, float offset, int align,\n                                bool smooth) = 0;\n  virtual void ScrollStopped() = 0;\n  virtual void EnableInsertPlatformView() = 0;\n  virtual void OnNextFrame() = 0;\n  virtual void SetEnableBatchRender(bool enable_batch_render) = 0;\n};\n\nstd::unique_ptr<ContainerDelegate> CreateListContainerDelegate(\n    ElementDelegate* list_delegate,\n    const std::shared_ptr<pub::PubValueFactory>& value_factory);\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_LIST_CONTAINER_DELEGATE_H_\n"
  },
  {
    "path": "core/list/list_element_delegate.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_LIST_ELEMENT_DELEGATE_H_\n#define CORE_LIST_LIST_ELEMENT_DELEGATE_H_\n\n#include <array>\n#include <memory>\n#include <string>\n\n#include \"base/include/debug/lynx_error.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/list/decoupled_list_types.h\"\n#include \"core/list/list_item_element_delegate.h\"\n#include \"core/public/pipeline_option.h\"\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ElementDelegate {\n public:\n  virtual ~ElementDelegate() = default;\n\n  virtual int32_t GetImplId() const = 0;\n  virtual float GetPhysicalPixelsPerLayoutUnit() const = 0;\n  virtual float GetLayoutsUnitPerPx() const = 0;\n  virtual void MarkListElementLayoutDirty() = 0;\n  virtual bool IsRTL() const = 0;\n  virtual float GetWidth() const = 0;\n  virtual float GetHeight() const = 0;\n  virtual const std::array<float, 4>& GetPaddings() const = 0;\n  virtual const std::array<float, 4>& GetMargins() const = 0;\n  virtual const std::array<float, 4>& GetBorders() const = 0;\n  virtual void FlushListContainerInfo(\n      const std::string& list_container_info_str,\n      std::unique_ptr<pub::Value> list_container_info,\n      bool from_fiber_data_source) = 0;\n  virtual void UpdateListLayoutNodeAttribute() = 0;\n  virtual bool ComponentAtIndex(uint32_t index, int64_t operationId = 0,\n                                bool enable_reuse_notification = false) = 0;\n  virtual void EnqueueComponent(int32_t list_item_id) = 0;\n  virtual void RemoveListItemPaintingNode(int32_t list_item_id) = 0;\n  virtual void InsertListItemPaintingNode(int32_t list_item_id) = 0;\n  virtual void FlushPatching(bool should_flush_finish_layout) = 0;\n  virtual void FlushImmediately() = 0;\n  virtual void UpdateContentOffsetAndSizeToPlatform(float content_size,\n                                                    float delta_x,\n                                                    float delta_y,\n                                                    bool is_init_scroll_offset,\n                                                    bool from_layout) = 0;\n  virtual bool HasBoundEvent(const std::string& event_name) const = 0;\n  virtual void SendCustomEvent(const std::string& event_name,\n                               const std::string& param_name,\n                               std::unique_ptr<pub::Value> param) = 0;\n  virtual void UpdateScrollInfo(float estimated_offset, bool smooth,\n                                bool scrolling) = 0;\n\n  virtual void ComponentAtIndexes(\n      std::unique_ptr<pub::Value> index_array,\n      std::unique_ptr<pub::Value> operation_id_array,\n      bool enable_reuse_notification = false) {}\n  virtual int GetThreadStrategy() const {\n    return base::ThreadStrategyForRendering::ALL_ON_UI;\n  }\n  virtual bool IsFiberArch() const { return false; }\n  virtual void CheckZIndex(ItemElementDelegate* list_item_delegate) const {}\n  virtual void ReportListItemLifecycleStatistic(\n      const std::shared_ptr<tasm::PipelineOptions>& option,\n      const std::string& item_key) const {}\n  virtual void OnErrorOccurred(base::LynxError error) const {}\n  virtual bool IsAttachToElementManager() const { return true; }\n  virtual void MarkTiming(ListTiming flag) {}\n  virtual void RequestNextFrame() {}\n  virtual bool IsInDebugMode() const { return false; }\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_LIST_ELEMENT_DELEGATE_H_\n"
  },
  {
    "path": "core/list/list_item_element_delegate.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_LIST_ITEM_ELEMENT_DELEGATE_H_\n#define CORE_LIST_LIST_ITEM_ELEMENT_DELEGATE_H_\n\n#include <array>\n#include <memory>\n#include <string>\n\n#include \"core/public/pub_value.h\"\n#include \"core/renderer/css/css_property_id.h\"\n#include \"core/renderer/css/css_value.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass ItemElementDelegate {\n public:\n  virtual ~ItemElementDelegate() = default;\n\n  virtual int32_t GetImplId() const = 0;\n  virtual std::string GetIdSelector() const = 0;\n  virtual float GetWidth() const = 0;\n  virtual float GetHeight() const = 0;\n  virtual float GetLeft() const = 0;\n  virtual float GetTop() const = 0;\n  virtual const std::array<float, 4>& GetPaddings() const = 0;\n  virtual const std::array<float, 4>& GetMargins() const = 0;\n  virtual const std::array<float, 4>& GetBorders() const = 0;\n  virtual void UpdateLayoutToPlatform(float left, float top) = 0;\n  virtual bool HasBoundEvent(const std::string& event_name) const = 0;\n  virtual void SendCustomEvent(const std::string& event_name,\n                               const std::string& param_name,\n                               std::unique_ptr<pub::Value> param) = 0;\n  virtual void OnListItemWillAppear(const std::string& item_key) = 0;\n  virtual void OnListItemDisappear(bool is_exist,\n                                   const std::string& item_key) = 0;\n  virtual void FlushPatching() {}\n  virtual void FlushAnimatedStyle(tasm::CSSPropertyID id,\n                                  tasm::CSSValue value) {}\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_LIST_ITEM_ELEMENT_DELEGATE_H_\n"
  },
  {
    "path": "core/list/testing/fiber_data_source.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_TESTING_FIBER_DATA_SOURCE_H_\n#define CORE_LIST_TESTING_FIBER_DATA_SOURCE_H_\n\n#include <string>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/list/decoupled_list_types.h\"\n\nnamespace lynx {\nnamespace list {\nnamespace testing {\n\nclass InsertOp {\n public:\n  int position_{0};\n  std::string item_key_;\n  int estimated_main_axis_size_px_{0};\n  bool full_span_{false};\n  bool sticky_top_{false};\n  bool sticky_bottom_{false};\n  bool recyclable_{true};\n\n  fml::RefPtr<lepus::Dictionary> ToMap() const {\n    auto insert_action = lepus::Dictionary::Create();\n    insert_action->SetValue(kFiberDataPosition, lepus::Value(position_));\n    insert_action->SetValue(kFiberDataItemKey, lepus::Value(item_key_));\n    insert_action->SetValue(kFiberDataEstimatedMainAxisSizePx,\n                            lepus::Value(estimated_main_axis_size_px_));\n    insert_action->SetValue(kFiberDataFullSpan, lepus::Value(full_span_));\n    insert_action->SetValue(kFiberDataStickyTop, lepus::Value(sticky_top_));\n    insert_action->SetValue(kFiberDataStickyBottom,\n                            lepus::Value(sticky_bottom_));\n    insert_action->SetValue(kFiberDataRecyclable, lepus::Value(recyclable_));\n    return insert_action;\n  }\n};\n\nclass UpdateOp {\n public:\n  int from_{kInvalidIndex};\n  int to_{kInvalidIndex};\n  bool flush_{false};\n  std::string item_key_;\n  int estimated_main_axis_size_px_{0};\n  bool full_span_{false};\n  bool sticky_top_{false};\n  bool sticky_bottom_{false};\n  bool recyclable_{true};\n\n  fml::RefPtr<lepus::Dictionary> ToMap() const {\n    auto update_action = lepus::Dictionary::Create();\n    update_action->SetValue(kFiberDataFrom, lepus::Value(from_));\n    update_action->SetValue(kFiberDataTo, lepus::Value(to_));\n    update_action->SetValue(kFiberDataFlush, lepus::Value(flush_));\n    update_action->SetValue(kFiberDataItemKey, lepus::Value(item_key_));\n    update_action->SetValue(kFiberDataEstimatedMainAxisSizePx,\n                            lepus::Value(estimated_main_axis_size_px_));\n    update_action->SetValue(kFiberDataFullSpan, lepus::Value(full_span_));\n    update_action->SetValue(kFiberDataStickyTop, lepus::Value(sticky_top_));\n    update_action->SetValue(kFiberDataStickyBottom,\n                            lepus::Value(sticky_bottom_));\n    update_action->SetValue(kFiberDataRecyclable, lepus::Value(recyclable_));\n    return update_action;\n  }\n};\n\nclass InsertAction {\n public:\n  std::vector<InsertOp> insert_ops_;\n\n  fml::RefPtr<lepus::CArray> ToArray() const {\n    auto insert_action = lepus::CArray::Create();\n    for (const auto& insert_op : insert_ops_) {\n      insert_action->emplace_back(insert_op.ToMap());\n    }\n    return insert_action;\n  }\n};\n\nclass UpdateAction {\n public:\n  std::vector<UpdateOp> update_ops_;\n\n  fml::RefPtr<lepus::CArray> ToArray() const {\n    auto update_action = lepus::CArray::Create();\n    for (const auto& update_op : update_ops_) {\n      update_action->emplace_back(update_op.ToMap());\n    }\n    return update_action;\n  }\n};\n\nclass RemoveAction {\n public:\n  std::vector<int> remove_ops_;\n\n  fml::RefPtr<lepus::CArray> ToArray() const {\n    auto remove_action = lepus::CArray::Create();\n    for (const auto& remove_op : remove_ops_) {\n      remove_action->emplace_back(lepus::Value(remove_op));\n    }\n    return remove_action;\n  }\n};\n\nclass FiberDataSource {\n public:\n  InsertAction insert_action_;\n  RemoveAction remove_action_;\n  UpdateAction update_action_;\n\n  fml::RefPtr<lepus::Dictionary> Resolve() const {\n    auto diff_result = lepus::Dictionary::Create();\n    diff_result->SetValue(kFiberDataInsertAction, insert_action_.ToArray());\n    diff_result->SetValue(kFiberDataRemoveAction, remove_action_.ToArray());\n    diff_result->SetValue(kFiberDataUpdateAction, update_action_.ToArray());\n    return diff_result;\n  }\n};\n\n}  // namespace testing\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_TESTING_FIBER_DATA_SOURCE_H_\n"
  },
  {
    "path": "core/list/testing/mock_list_element.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_TESTING_MOCK_LIST_ELEMENT_H_\n#define CORE_LIST_TESTING_MOCK_LIST_ELEMENT_H_\n\n#include <array>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n\n#include \"core/list/list_element_delegate.h\"\n#include \"core/list/testing/mock_list_item_element.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n\nnamespace lynx {\nnamespace list {\n\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 2.f;\n\nclass MockListElement : public ElementDelegate {\n public:\n  MockListElement() = default;\n  ~MockListElement() override = default;\n\n  int32_t GetImplId() const override { return impl_id_; }\n  float GetPhysicalPixelsPerLayoutUnit() const override {\n    return kDefaultPhysicalPixelsPerLayoutUnit;\n  }\n  float GetLayoutsUnitPerPx() const override {\n    return kDefaultLayoutsUnitPerPx;\n  }\n  MOCK_METHOD(void, MarkListElementLayoutDirty, (), (override));\n  bool IsRTL() const override { return is_rtl_; }\n  float GetWidth() const override { return width_; }\n  float GetHeight() const override { return height_; }\n  const std::array<float, 4>& GetPaddings() const override { return paddings_; }\n  const std::array<float, 4>& GetMargins() const override { return margins_; }\n  const std::array<float, 4>& GetBorders() const override { return borders_; }\n  void FlushListContainerInfo(const std::string& list_container_info_str,\n                              std::unique_ptr<pub::Value> list_container_info,\n                              bool from_fiber_data_source) override {}\n  MOCK_METHOD(void, UpdateListLayoutNodeAttribute, (), (override));\n  MOCK_METHOD(bool, ComponentAtIndex,\n              (uint32_t index, int64_t operationId,\n               bool enable_reuse_notification),\n              (override));\n  MOCK_METHOD(void, EnqueueComponent, (int32_t list_item_id), (override));\n  void RemoveListItemPaintingNode(int32_t list_item_id) override {}\n  void InsertListItemPaintingNode(int32_t list_item_id) override {}\n  void FlushPatching(bool should_flush_finish_layout) override {}\n  void FlushImmediately() override {}\n  void UpdateContentOffsetAndSizeToPlatform(float content_size, float delta_x,\n                                            float delta_y,\n                                            bool is_init_scroll_offset,\n                                            bool from_layout) override {}\n  bool HasBoundEvent(const std::string& event_name) const override {\n    return events_.find(event_name) != events_.end();\n  }\n  MOCK_METHOD(void, SendCustomEvent,\n              (const std::string& event_name, const std::string& param_name,\n               std::unique_ptr<pub::Value> param),\n              (override));\n  void UpdateScrollInfo(float estimated_offset, bool smooth,\n                        bool scrolling) override {}\n  void ComponentAtIndexes(std::unique_ptr<pub::Value> index_array,\n                          std::unique_ptr<pub::Value> operation_id_array,\n                          bool enable_reuse_notification = false) {}\n  int GetThreadStrategy() const {\n    return base::ThreadStrategyForRendering::ALL_ON_UI;\n  }\n  bool IsFiberArch() const { return is_fiber_arch_; }\n  void CheckZIndex(ItemElementDelegate* list_item_delegate) const {}\n  void ReportListItemLifecycleStatistic(\n      const std::shared_ptr<tasm::PipelineOptions>& option,\n      const std::string& item_key) const {}\n  MOCK_METHOD(void, OnErrorOccurred, (base::LynxError error),\n              (const, override));\n  bool IsAttachToElementManager() const { return is_attached_; }\n  void MarkTiming(ListTiming flag) {}\n  void RequestNextFrame() {}\n  bool IsInDebugMode() const { return is_debug_mode_; }\n\n  void ClearListItemElements() { list_item_elements_.clear(); }\n  void AddListItemElement(\n      const std::string& item_key,\n      std::unique_ptr<MockListItemElement> list_item_element) {\n    list_item_elements_[item_key] = std::move(list_item_element);\n  }\n  MockListItemElement* GetListItemElement(const std::string& item_key) {\n    if (auto it = list_item_elements_.find(item_key);\n        it != list_item_elements_.end()) {\n      return it->second.get();\n    }\n    return nullptr;\n  }\n\n private:\n  int32_t impl_id_{1};\n  bool is_fiber_arch_{false};\n  bool is_rtl_{false};\n  bool is_attached_{true};\n  bool is_debug_mode_{false};\n  float width_{500.f};\n  float height_{500.f};\n  std::array<float, 4> paddings_{0.f, 0.f, 0.f, 0.f};\n  std::array<float, 4> margins_{0.f, 0.f, 0.f, 0.f};\n  std::array<float, 4> borders_{0.f, 0.f, 0.f, 0.f};\n  std::unordered_set<std::string> events_;\n  std::unordered_map<std::string, std::unique_ptr<MockListItemElement>>\n      list_item_elements_;\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_TESTING_MOCK_LIST_ELEMENT_H_\n"
  },
  {
    "path": "core/list/testing/mock_list_item_element.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_TESTING_MOCK_LIST_ITEM_ELEMENT_H_\n#define CORE_LIST_TESTING_MOCK_LIST_ITEM_ELEMENT_H_\n\n#include <array>\n#include <memory>\n#include <string>\n#include <unordered_set>\n\n#include \"core/list/list_item_element_delegate.h\"\n#include \"third_party/googletest/googlemock/include/gmock/gmock.h\"\n\nnamespace lynx {\nnamespace list {\n\nclass MockListItemElement : public ItemElementDelegate {\n public:\n  MockListItemElement(int32_t impl_id) : impl_id_(impl_id) {}\n  ~MockListItemElement() override = default;\n\n  int32_t GetImplId() const override { return impl_id_; }\n  std::string GetIdSelector() const {\n    return std::string(\"mock_list_item_element_\") + std::to_string(impl_id_);\n  }\n  float GetWidth() const override { return width_; }\n  float GetHeight() const override { return height_; }\n  float GetLeft() const override { return left_; }\n  float GetTop() const override { return top_; }\n  const std::array<float, 4>& GetPaddings() const override { return paddings_; }\n  const std::array<float, 4>& GetMargins() const override { return margins_; }\n  const std::array<float, 4>& GetBorders() const override { return borders_; }\n  void UpdateLayoutToPlatform(float left, float top) override {\n    left_ = left;\n    top_ = top;\n  }\n  bool HasBoundEvent(const std::string& event_name) const override {\n    return events_.find(event_name) != events_.end();\n  }\n  MOCK_METHOD(void, SendCustomEvent,\n              (const std::string& event_name, const std::string& param_name,\n               std::unique_ptr<pub::Value> param),\n              (override));\n  void OnListItemWillAppear(const std::string& item_key) override {}\n  void OnListItemDisappear(bool is_exist,\n                           const std::string& item_key) override {}\n\n private:\n  int32_t impl_id_{-1};\n  float left_{0.f};\n  float top_{0.f};\n  float width_{500.f};\n  float height_{100.f};\n  std::array<float, 4> paddings_{0.f, 0.f, 0.f, 0.f};\n  std::array<float, 4> margins_{0.f, 0.f, 0.f, 0.f};\n  std::array<float, 4> borders_{0.f, 0.f, 0.f, 0.f};\n  std::unordered_set<std::string> events_;\n};\n\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_TESTING_MOCK_LIST_ITEM_ELEMENT_H_\n"
  },
  {
    "path": "core/list/testing/radon_data_source.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_TESTING_RADON_DATA_SOURCE_H_\n#define CORE_LIST_TESTING_RADON_DATA_SOURCE_H_\n\n#include <string>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/list/decoupled_list_types.h\"\n\nnamespace lynx {\nnamespace list {\nnamespace testing {\n\nclass RadonDataSource {\n public:\n  std::vector<std::string> item_keys_;\n  std::vector<int> insertion_;\n  std::vector<int> removal_;\n  std::vector<int> update_from_;\n  std::vector<int> update_to_;\n  std::vector<int> estimated_height_pxs_;\n  std::vector<int> estimated_main_axis_size_pxs_;\n  std::vector<int> sticky_tops_;\n  std::vector<int> sticky_bottoms_;\n  std::vector<int> full_spans_;\n\n  int GetItemCount() const { return static_cast<int>(item_keys_.size()); }\n\n  fml::RefPtr<lepus::Dictionary> GenerateDataSource() const {\n    // item keys\n    auto item_keys_array = lepus::CArray::Create();\n    for (const auto& item_key : item_keys_) {\n      item_keys_array->push_back(lepus_value(item_key));\n    }\n    // estimated_height_px\n    auto estimated_height_px_array = lepus::CArray::Create();\n    for (const auto& height : estimated_height_pxs_) {\n      estimated_height_px_array->push_back(lepus_value(height));\n    }\n    // estimated_main_axis_size_px\n    auto estimated_main_axis_px_array = lepus::CArray::Create();\n    for (const auto& height : estimated_main_axis_size_pxs_) {\n      estimated_main_axis_px_array->push_back(lepus_value(height));\n    }\n    // sticky_tops\n    auto sticky_tops_array = lepus::CArray::Create();\n    for (int32_t index : sticky_tops_) {\n      sticky_tops_array->push_back(lepus_value(index));\n    }\n    // sticky_bottoms\n    auto sticky_bottoms_array = lepus::CArray::Create();\n    for (int32_t index : sticky_bottoms_) {\n      sticky_bottoms_array->push_back(lepus_value(index));\n    }\n    // full_spans\n    auto full_spans_array = lepus::CArray::Create();\n    for (int32_t index : full_spans_) {\n      full_spans_array->push_back(lepus_value(index));\n    }\n    // insertions\n    auto insertion_array = lepus::CArray::Create();\n    for (const auto& index : insertion_) {\n      insertion_array->push_back(lepus_value(index));\n    }\n    // removals\n    auto removal_array = lepus::CArray::Create();\n    for (const auto& index : removal_) {\n      removal_array->push_back(lepus_value(index));\n    }\n    // update_from\n    auto update_from_array = lepus::CArray::Create();\n    for (const auto& index : update_from_) {\n      update_from_array->push_back(lepus_value(index));\n    }\n    // update_to\n    auto update_to_array = lepus::CArray::Create();\n    for (const auto& index : update_to_) {\n      update_to_array->push_back(lepus_value(index));\n    }\n    auto diff_result = lepus::Dictionary::Create();\n    diff_result->SetValue(kRadonDataInsertions, lepus_value(insertion_array));\n    diff_result->SetValue(kRadonDataRemovals, lepus_value(removal_array));\n    diff_result->SetValue(kRadonDataUpdateFrom, lepus_value(update_from_array));\n    diff_result->SetValue(kRadonDataUpdateTo, lepus_value(update_to_array));\n    auto data_source = lepus::Dictionary::Create();\n    data_source->SetValue(kRadonDataDiffResult, lepus_value(diff_result));\n    data_source->SetValue(kRadonDataItemKeys, lepus_value(item_keys_array));\n    data_source->SetValue(kRadonDataEstimatedHeightPx,\n                          lepus_value(estimated_height_px_array));\n    data_source->SetValue(kRadonDataEstimatedMainAxisSizePx,\n                          lepus_value(estimated_main_axis_px_array));\n    data_source->SetValue(kRadonDataFullSpan, lepus_value(full_spans_array));\n    data_source->SetValue(kRadonDataStickyTop, lepus_value(sticky_tops_array));\n    data_source->SetValue(kRadonDataStickyBottom,\n                          lepus_value(sticky_bottoms_array));\n    return data_source;\n  }\n};\n\n}  // namespace testing\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_TESTING_RADON_DATA_SOURCE_H_\n"
  },
  {
    "path": "core/list/testing/utils.h",
    "content": "// Copyright 2026 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_LIST_TESTING_UTILS_H_\n#define CORE_LIST_TESTING_UTILS_H_\n\nnamespace lynx {\nnamespace list {\n\n#ifndef TEST_LIST_CONTAINER_RESOLVE_PROP\n#define TEST_LIST_CONTAINER_RESOLVE_PROP(name) \\\n  TEST_F(ListContainerImplTest, ResolveProp##name)\n#endif\n\n#ifndef LIST_CONTAINER_DEFINE_PROP_VALUE\n#define LIST_CONTAINER_DEFINE_PROP_VALUE(name, value_type, init_value) \\\n  auto key = value_factory_->CreateString(kProp##name);                \\\n  auto value = value_factory_->Create##value_type(init_value);\n#endif\n\n#ifndef LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE\n#define LIST_CONTAINER_DEFINE_PROP_LEPUS_VALUE(name, init_lepus_value) \\\n  auto key = value_factory_->CreateString(kProp##name);                \\\n  auto value = pub::ValueImplLepus(init_lepus_value);\n#endif\n\n#ifndef TO_MOCK_LIST_ITEM_ELEMENT\n#define TO_MOCK_LIST_ITEM_ELEMENT(item_delegate) \\\n  (static_cast<MockListItemElement*>(item_delegate))\n#endif\n}  // namespace list\n}  // namespace lynx\n\n#endif  // CORE_LIST_TESTING_UTILS_H_\n"
  },
  {
    "path": "core/napi.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../config.gni\")\n\n# All of those args one-to-one correspond with the macros in the code of Lynx module.\n# Please do not add other types of flags to this scope.\ndeclare_args() {\n  # enable_napi_binding corresponds the macro of ENABLE_NAPI_BINDING\n  enable_napi_binding = false\n}\n\nif (is_android) {\n  # the arg is up to enable_lite arg,\n  # gradle will not pass this arg when building Android.\n  enable_just_lepusng = enable_lite\n}\n\nenable_napi_binding = enable_lepusng_worklet\n"
  },
  {
    "path": "core/parser/BUILD.gn",
    "content": "# Copyright 2020 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../Lynx.gni\")\n\nparser_shared_sources = [\n  \"input_stream.cc\",\n  \"input_stream.h\",\n]\n\nlynx_core_source_set(\"parser\") {\n  sources = parser_shared_sources\n}\n"
  },
  {
    "path": "core/parser/input_stream.cc",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/parser/input_stream.h\"\n#define TOKEN_NUM_PRINT_EACH_SIDE 8\n\nnamespace lynx {\nnamespace parser {\nInputStream::InputStream() : cursor_(0), source_() {}\n\nInputStream::~InputStream() {}\n\nvoid InputStream::Write(const std::string& source) { source_.append(source); }\n\nbool InputStream::HasNext() { return source_[cursor_] != 0; }\n\nchar InputStream::Next() { return HasNext() ? source_[cursor_++] : 0; }\n\nvoid InputStream::Back() {\n  if (cursor_ > 0) --cursor_;\n}\n\nvoid InputStream::Back(int step) {\n  if (cursor_ > 0) {\n    cursor_ -= step;\n  }\n}\n\n// return source code around error position, use ' ' as seperator\nstd::string InputStream::GetPartStr(int32_t& line, int32_t& col) {\n  int32_t line_index = 1, column_index = 0;\n  size_t length = source_.length();\n\n  while (line_index < line) {\n    while (static_cast<size_t>(column_index) < length &&\n           source_[column_index++] != '\\n')\n      ;\n    line_index++;\n  }\n  // start: start position of the error line\n  int32_t start = column_index;\n  // error_column_index: position of the error\n  int32_t error_column_index = column_index + col;\n  while (static_cast<size_t>(column_index) < length &&\n         source_[column_index++] != '\\n')\n    ;\n  // end: end position of the error line\n  int32_t end = column_index;\n\n  int32_t token_number = 0;\n  int32_t index = -1;\n  // find ahead and after TOKEN_NUM_PRINT_EACH_SIDE token around error position\n  for (index = error_column_index; index < end; index++) {\n    if (source_[index] == ' ') {\n      token_number++;\n      if (token_number == TOKEN_NUM_PRINT_EACH_SIDE) {\n        break;\n      }\n    }\n  }\n  int32_t end_pos = index;\n\n  token_number = 0;\n  for (index = error_column_index; index > start; index--) {\n    if (source_[index] == ' ') {\n      token_number++;\n      if (token_number == TOKEN_NUM_PRINT_EACH_SIDE) {\n        break;\n      }\n    }\n  }\n  int32_t start_pos = index;\n\n  return source_.substr(start_pos, end_pos - start_pos);\n}\n}  // namespace parser\n}  // namespace lynx\n"
  },
  {
    "path": "core/parser/input_stream.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PARSER_INPUT_STREAM_H_\n#define CORE_PARSER_INPUT_STREAM_H_\n\n#include <string>\n\nnamespace lynx {\nnamespace parser {\nclass InputStream {\n public:\n  InputStream();\n  ~InputStream();\n  void Write(const std::string& source);\n  bool HasNext();\n  char Next();\n  void Back();\n  void Back(int step);\n  std::string GetPartStr(int32_t& line, int32_t& col);\n\n private:\n  int cursor_;\n  std::string source_;\n};\n}  // namespace parser\n}  // namespace lynx\n\n#endif  // CORE_PARSER_INPUT_STREAM_H_\n"
  },
  {
    "path": "core/public/BUILD.gn",
    "content": "# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../Lynx.gni\")\n\nlynx_core_source_set(\"public\") {\n  sources = [\n    \"box_model.h\",\n    \"devtool/lynx_devtool_proxy.h\",\n    \"devtool/lynx_inspector_owner.h\",\n    \"jsb/extension_module_factory.h\",\n    \"jsb/lynx_extension_module.h\",\n    \"jsb/lynx_module_callback.h\",\n    \"jsb/lynx_native_module.h\",\n    \"jsb/native_module_factory.h\",\n    \"layout_ctx_platform_impl.h\",\n    \"layout_node_manager.h\",\n    \"layout_node_value.h\",\n    \"list_container_proxy.h\",\n    \"list_data.h\",\n    \"list_engine_proxy.h\",\n    \"lynx_engine_proxy.h\",\n    \"lynx_extension_delegate.h\",\n    \"lynx_layout_proxy.h\",\n    \"lynx_resource_loader.h\",\n    \"lynx_runtime_proxy.h\",\n    \"page_options.h\",\n    \"painting_ctx_platform_impl.h\",\n    \"perf_controller_proxy.h\",\n    \"performance_controller_platform_impl.h\",\n    \"pipeline_option.h\",\n    \"platform_extra_bundle.h\",\n    \"platform_renderer_type.h\",\n    \"prop_bundle.h\",\n    \"pub_value.h\",\n    \"runtime_lifecycle_observer.h\",\n    \"text_layout_impl.h\",\n    \"text_utils.h\",\n    \"timing_key.h\",\n    \"ui_delegate.h\",\n    \"ui_operation_queue_interface.h\",\n    \"vsync_monitor_platform_impl.h\",\n    \"vsync_observer_interface.h\",\n  ]\n}\n"
  },
  {
    "path": "core/public/box_model.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_BOX_MODEL_H_\n#define CORE_PUBLIC_BOX_MODEL_H_\n\nnamespace lynx {\nnamespace tasm {\nenum BoxModelOffset {\n  PAD_LEFT,\n  PAD_TOP,\n  PAD_RIGHT,\n  PAD_BOTTOM,\n  BORDER_LEFT,\n  BORDER_TOP,\n  BORDER_RIGHT,\n  BORDER_BOTTOM,\n  MARGIN_LEFT,\n  MARGIN_TOP,\n  MARGIN_RIGHT,\n  MARGIN_BOTTOM,\n  LAYOUT_LEFT,\n  LAYOUT_TOP,\n  LAYOUT_RIGHT,\n  LAYOUT_BOTTOM\n};\n\n}\n}  // namespace lynx\n#endif  // CORE_PUBLIC_BOX_MODEL_H_\n"
  },
  {
    "path": "core/public/devtool/lynx_devtool_proxy.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_DEVTOOL_LYNX_DEVTOOL_PROXY_H_\n#define CORE_PUBLIC_DEVTOOL_LYNX_DEVTOOL_PROXY_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"core/public/devtool/lynx_inspector_owner.h\"\n#include \"core/public/ui_delegate.h\"\n\nnamespace lynx {\n\nnamespace tasm {\nclass TemplateData;\n}  // namespace tasm\n\nnamespace devtool {\nclass LynxDevToolProxy {\n public:\n  virtual void ReloadTemplate(\n      const std::string& url, const std::vector<uint8_t>& source,\n      const std::shared_ptr<tasm::TemplateData>& data) = 0;\n  virtual void LoadTemplateFromURL(\n      const std::string& url,\n      const std::shared_ptr<tasm::TemplateData> data = nullptr) = 0;\n  virtual double GetScreenScaleFactor() = 0;\n  virtual void TakeSnapshot(\n      size_t max_width, size_t max_height, int quality,\n      float screen_scale_factor,\n      const lynx::fml::RefPtr<lynx::fml::TaskRunner>& screenshot_runner,\n      tasm::TakeSnapshotCompletedCallback callback) = 0;\n  virtual int GetNodeForLocation(int x, int y) = 0;\n  virtual std::vector<float> GetTransformValue(\n      int id, const std::vector<float>& pad_border_margin_layout) = 0;\n  virtual std::string GetLynxUITree() = 0;\n  virtual std::string GetUINodeInfo(int id) = 0;\n  virtual int SetUIStyle(int id, const std::string& name,\n                         const std::string& content) = 0;\n  virtual void SetInspectorOwner(LynxInspectorOwner* owner) = 0;\n  virtual void EmulateTouch(const std::string& event_type, int x, int y,\n                            const std::string& button, float delta_x,\n                            float delta_y, int modifiers, int click_count) = 0;\n};\n}  // namespace devtool\n}  // namespace lynx\n#endif  // CORE_PUBLIC_DEVTOOL_LYNX_DEVTOOL_PROXY_H_\n"
  },
  {
    "path": "core/public/devtool/lynx_inspector_owner.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_DEVTOOL_LYNX_INSPECTOR_OWNER_H_\n#define CORE_PUBLIC_DEVTOOL_LYNX_INSPECTOR_OWNER_H_\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/task_runner.h\"\n\nnamespace lynx {\nnamespace runtime {\nnamespace js {\nclass InspectorRuntimeObserverNG;\n}\n\n}  // namespace runtime\nnamespace tasm {\nclass TemplateData;\n}  // namespace tasm\n\nnamespace devtool {\nclass LynxDevToolProxy;\n\nclass LynxInspectorOwner {\n public:\n  virtual ~LynxInspectorOwner() = default;\n  virtual void SetUITaskRunner(\n      const fml::RefPtr<fml::TaskRunner>& task_runner) {}\n  virtual void Init(LynxDevToolProxy* proxy,\n                    const std::shared_ptr<LynxInspectorOwner>& shared_self) = 0;\n  // life cycle\n  virtual void OnTemplateAssemblerCreated(intptr_t ptr) = 0;\n  virtual void OnLoaded(const std::string& url) = 0;\n  virtual void OnLoadTemplate(\n      const std::string& url, const std::vector<uint8_t>& tem,\n      const std::shared_ptr<tasm::TemplateData>& data) = 0;\n  virtual void OnShow() = 0;\n  virtual void OnHide() = 0;\n  virtual void InvokeCDPFromSDK(\n      const std::string& cdp_msg,\n      std::function<void(const std::string&)>&& callback) = 0;\n  virtual std::shared_ptr<lynx::runtime::js::InspectorRuntimeObserverNG>\n  OnBackgroundRuntimeCreated(const std::string& group_thread_name) = 0;\n};\n\n}  // namespace devtool\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_DEVTOOL_LYNX_INSPECTOR_OWNER_H_\n"
  },
  {
    "path": "core/public/jsb/extension_module_factory.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_JSB_EXTENSION_MODULE_FACTORY_H_\n#define CORE_PUBLIC_JSB_EXTENSION_MODULE_FACTORY_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"core/public/jsb/lynx_extension_module.h\"\n#include \"core/public/jsb/native_module_factory.h\"\n\nnamespace lynx {\nnamespace runtime {\nusing ExtensionModuleCreator =\n    std::function<std::shared_ptr<LynxExtensionModule>()>;\n\nstruct ModuleCreatorInfo {\n  ExtensionModuleCreator creator;\n  bool lazy_create;\n};\n\nclass ExtensionModuleFactory : public NativeModuleFactory {\n public:\n  ExtensionModuleFactory()\n      : opaque_env_(nullptr),\n        vsync_observer_(nullptr),\n        task_runner_(nullptr),\n        ui_delegate_(nullptr) {}\n  virtual ~ExtensionModuleFactory() = default;\n\n  // Called on the main thread\n  virtual void OnLynxViewCreate(tasm::UIDelegate* ui_delegate) {\n    ui_delegate_ = ui_delegate;\n  }\n\n  // Called on the main thread\n  virtual void OnLynxViewDestroy() {\n    std::lock_guard<std::mutex> lock(mutex_);\n    for (const auto& pair : module_map_) {\n      pair.second->SetLynxViewDestroyedState();\n    }\n    ui_delegate_ = nullptr;\n  }\n\n  void OnRuntimeInit(const fml::RefPtr<fml::TaskRunner>& task_runner) {\n    for (const auto& pair : module_map_) {\n      pair.second->SetRuntimeInitState(task_runner);\n    }\n    task_runner_ = task_runner;\n  }\n\n  // Called on the BTS thread\n  void OnRuntimeAttach(void* opaque_env,\n                       const std::shared_ptr<IVSyncObserver>& vsync_observer) {\n    for (const auto& pair : module_map_) {\n      pair.second->SetRuntimeAttachedState(opaque_env, vsync_observer);\n    }\n    opaque_env_ = opaque_env;\n    vsync_observer_ = vsync_observer;\n  }\n\n  // Called on the BTS thread\n  void OnRuntimeReady(void* env, void* lynx, const std::string& url) {\n    for (const auto& pair : module_map_) {\n      pair.second->SetRuntimeReadyState(env, lynx, url);\n    }\n  }\n\n  // Called on the BTS thread\n  void OnRuntimeDetach() {\n    std::lock_guard<std::mutex> lock(mutex_);\n    for (const auto& pair : module_map_) {\n      pair.second->SetRuntimeDetachedState();\n    }\n  }\n\n  void OnEnterForeground() {\n    std::lock_guard<std::mutex> lock(mutex_);\n    for (const auto& pair : module_map_) {\n      pair.second->SetEnteringForegroundState();\n    }\n  }\n\n  void OnEnterBackground() {\n    std::lock_guard<std::mutex> lock(mutex_);\n    for (const auto& pair : module_map_) {\n      pair.second->SetEnteringBackgroundState();\n    }\n  }\n\n  // LynxExtensionModule is created using lazy loading by default. If lazy\n  // loading is not used, it is created when OnLynxViewCreate is called.\n  virtual void RegisterExtensionModule(const std::string& name,\n                                       ExtensionModuleCreator creator,\n                                       bool lazy_create = true) {\n    std::lock_guard<std::mutex> lock(mutex_);\n    ModuleCreatorInfo info{std::move(creator), lazy_create};\n    module_creators_.emplace(name, std::move(info));\n  }\n\n protected:\n  std::unordered_map<std::string, std::shared_ptr<LynxExtensionModule>>\n      module_map_;\n  std::unordered_map<std::string, ModuleCreatorInfo> module_creators_;\n  // The opaque_env_ Only accessible in BTS thread\n  void* opaque_env_;\n  std::shared_ptr<IVSyncObserver> vsync_observer_;\n  fml::RefPtr<fml::TaskRunner> task_runner_;\n  tasm::UIDelegate* ui_delegate_;\n};\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_JSB_EXTENSION_MODULE_FACTORY_H_\n"
  },
  {
    "path": "core/public/jsb/lynx_extension_module.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_JSB_LYNX_EXTENSION_MODULE_H_\n#define CORE_PUBLIC_JSB_LYNX_EXTENSION_MODULE_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"core/public/jsb/lynx_native_module.h\"\n#include \"core/public/ui_delegate.h\"\n#include \"core/public/vsync_observer_interface.h\"\n\nnamespace lynx {\nnamespace runtime {\nclass LynxExtensionModule : public LynxNativeModule {\n public:\n  virtual void SetLynxViewCreatedState(tasm::UIDelegate* ui_delegate) = 0;\n  virtual void SetLynxViewDestroyedState() = 0;\n  virtual void SetRuntimeInitState(\n      const fml::RefPtr<fml::TaskRunner>& task_runner) = 0;\n  virtual void SetRuntimeAttachedState(\n      void* opaque_env,\n      const std::shared_ptr<IVSyncObserver>& vsync_observer) = 0;\n  virtual void SetRuntimeReadyState(void* opaque_env, void* opaque_lynx,\n                                    const std::string& url) = 0;\n  virtual void SetRuntimeDetachedState() = 0;\n\n  virtual void SetEnteringForegroundState() = 0;\n  virtual void SetEnteringBackgroundState() = 0;\n\n  void RegisterMethod(const NativeModuleMethod& method,\n                      LynxNativeModule::NativeModuleInvocation invocation) {\n    methods_.emplace(method.name, method);\n    invocations_.emplace(method.name, std::move(invocation));\n  }\n\n protected:\n  std::unordered_map<std::string, LynxNativeModule::NativeModuleInvocation>\n      invocations_;\n};\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_JSB_LYNX_EXTENSION_MODULE_H_\n"
  },
  {
    "path": "core/public/jsb/lynx_module_callback.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_JSB_LYNX_MODULE_CALLBACK_H_\n#define CORE_PUBLIC_JSB_LYNX_MODULE_CALLBACK_H_\n\n#include <memory>\n#include <unordered_map>\n\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace runtime {\nclass LynxModuleCallback {\n public:\n  enum class Type {\n    NATIVE,\n    JSI,\n    LEPUS,\n    NAPI,\n  };\n  explicit LynxModuleCallback(int64_t callback_id)\n      : callback_id_(callback_id) {}\n\n  virtual ~LynxModuleCallback() = default;\n\n  // Set callback args before invoking callback\n  virtual void SetArgs(std::unique_ptr<pub::Value> args) = 0;\n  void SetCallbackFlowId(uint64_t flow_id) { callback_flow_id_ = flow_id; }\n  uint64_t CallbackFlowId() const { return callback_flow_id_; }\n  uint64_t CallbackId() const { return callback_id_; }\n  virtual Type GetType() const { return Type::NATIVE; }\n\n protected:\n  const int64_t callback_id_;\n  // We need callback flow id to bind CallJSB and InvokeCallback in tracing.\n  uint64_t callback_flow_id_ = 0;\n};\n\n// The key of CallbackMap represents the index at which the callback is located\n// among all parameters.\nusing CallbackMap =\n    std::unordered_map<int64_t, std::shared_ptr<LynxModuleCallback>>;\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_JSB_LYNX_MODULE_CALLBACK_H_\n"
  },
  {
    "path": "core/public/jsb/lynx_native_module.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_JSB_LYNX_NATIVE_MODULE_H_\n#define CORE_PUBLIC_JSB_LYNX_NATIVE_MODULE_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/closure.h\"\n#include \"base/include/debug/lynx_error.h\"\n#include \"base/include/expected.h\"\n#include \"core/base/lynx_export.h\"\n#include \"core/public/jsb/lynx_module_callback.h\"\n#include \"core/public/lynx_runtime_proxy.h\"\n#include \"core/public/pub_value.h\"\n\n// TODO(liyanbo.monster): after remove native promise, delete this.\n#if OS_IOS || OS_TVOS || OS_OSX || OS_ANDROID\n#include \"core/runtime/js/bindings/modules/lynx_module_timing.h\"\n#include \"core/runtime/js/bindings/modules/module_delegate.h\"\n#include \"core/runtime/js/jsi/jsi.h\"\n#endif\n\nnamespace lynx {\nnamespace runtime {\nstruct NativeModuleMethod {\n  std::string name;\n  size_t args_count;\n  NativeModuleMethod(const std::string& method_name, size_t count)\n      : name(method_name), args_count(count) {}\n};\n\nusing NativeModuleMethods = std::unordered_map<std::string, NativeModuleMethod>;\n\n/*\n * Upper-level modules can inherit from LynxNativeModule to register their own\n * JSB.\n */\nclass LYNX_EXPORT_FOR_DEVTOOL LynxNativeModule {\n public:\n  class Delegate {\n   public:\n    virtual void InvokeCallback(\n        const std::shared_ptr<LynxModuleCallback>& callback,\n        base::MoveOnlyClosure<bool> invoke_pre_func = nullptr) = 0;\n    virtual void RunOnJSThread(base::closure func) = 0;\n    virtual void RunOnPlatformThread(base::closure func) = 0;\n    virtual const std::shared_ptr<pub::PubValueFactory>& GetValueFactory() = 0;\n    virtual void OnErrorOccurred(const std::string& module_name,\n                                 const std::string& method_name,\n                                 base::LynxError error) = 0;\n  };\n\n  using NativeModuleInvocation = std::unique_ptr<pub::Value> (\n      LynxNativeModule::*)(std::unique_ptr<pub::Value>, const CallbackMap&);\n\n  LynxNativeModule(std::shared_ptr<pub::PubValueFactory> value_factory)\n      : value_factory_(std::move(value_factory)) {}\n\n  LynxNativeModule() {}\n\n  virtual ~LynxNativeModule() = default;\n\n  // Find invocation you registered and call invocation with args and callbacks\n  virtual base::expected<std::unique_ptr<pub::Value>, std::string> InvokeMethod(\n      const std::string& method_name, std::unique_ptr<pub::Value> args,\n      size_t count, const CallbackMap& callbacks) = 0;\n\n  virtual std::unique_ptr<pub::Value> GetAttributeValue(\n      const std::string& attribute_name) {\n    return nullptr;\n  }\n\n  // Returns a list of NativeModuleMethod you registered\n  virtual const NativeModuleMethods& GetMethodList() const { return methods_; }\n\n  virtual void SetDelegate(std::weak_ptr<Delegate> delegate) {\n    delegate_ = delegate;\n  }\n  void SetRuntimeProxy(std::weak_ptr<shell::LynxRuntimeProxy> proxy) {\n    runtime_proxy_ = proxy;\n  }\n\n  // The `context_id_` is used to associate with `LynxContext`, which is linked\n  // to the corresponding `Context` through `findContext` in\n  // `LynxModuleWrapper`.\n  void SetContextID(int64_t context_id) { context_id_ = context_id; }\n\n  virtual void Destroy() {}\n\n  std::shared_ptr<pub::PubValueFactory> GetValueFactory() {\n    return value_factory_;\n  }\n\n// TODO(liyanbo.monster): after remove native promise, delete this.\n#if OS_IOS || OS_TVOS || OS_OSX || OS_ANDROID\n  virtual void EnterInvokeScope(\n      js::Runtime* rt, std::shared_ptr<js::ModuleDelegate> module_delegate) {}\n  virtual void ExitInvokeScope() {}\n  virtual std::optional<js::Value> TryGetPromiseRet() { return std::nullopt; }\n#endif\n\n protected:\n  // The `context_id_` is used to associate with `LynxContext`, which is linked\n  // to the corresponding `Context` through `findContext` in\n  // `LynxModuleWrapper`.\n  int64_t context_id_ = -1;\n  std::weak_ptr<Delegate> delegate_;\n  std::weak_ptr<shell::LynxRuntimeProxy> runtime_proxy_;\n  NativeModuleMethods methods_;\n  std::shared_ptr<pub::PubValueFactory> value_factory_;\n};\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_JSB_LYNX_NATIVE_MODULE_H_\n"
  },
  {
    "path": "core/public/jsb/native_module_factory.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_JSB_NATIVE_MODULE_FACTORY_H_\n#define CORE_PUBLIC_JSB_NATIVE_MODULE_FACTORY_H_\n\n#include <functional>\n#include <memory>\n#include <mutex>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"core/base/lynx_export.h\"\n#include \"core/public/jsb/lynx_native_module.h\"\n\nnamespace lynx {\nnamespace runtime {\nusing ModuleCreator = std::function<std::shared_ptr<LynxNativeModule>()>;\n\nclass LYNX_EXPORT_FOR_DEVTOOL NativeModuleFactory {\n public:\n  virtual ~NativeModuleFactory() = default;\n\n  // Default implementation support to register and create C++ module.\n  // Different platforms can implement subclasses to register the platform\n  // Module\n  virtual std::shared_ptr<LynxNativeModule> CreateModule(\n      const std::string& name) {\n    std::lock_guard<std::mutex> lock(mutex_);\n    auto itr = creators_.find(name);\n    if (itr == creators_.end()) {\n      return nullptr;\n    }\n    return itr->second();\n  };\n\n  virtual void Register(const std::string& name, ModuleCreator creator) {\n    std::lock_guard<std::mutex> lock(mutex_);\n    creators_.emplace(name, std::move(creator));\n  }\n\n protected:\n  std::mutex mutex_;\n  std::unordered_map<std::string, ModuleCreator> creators_;\n};\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_JSB_NATIVE_MODULE_FACTORY_H_\n"
  },
  {
    "path": "core/public/layout_ctx_platform_impl.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_LAYOUT_CTX_PLATFORM_IMPL_H_\n#define CORE_PUBLIC_LAYOUT_CTX_PLATFORM_IMPL_H_\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"base/include/closure.h\"\n#include \"core/public/layout_node_manager.h\"\n#include \"core/public/platform_extra_bundle.h\"\n#include \"core/public/prop_bundle.h\"\n\nnamespace lynx {\nnamespace shell {\nclass LynxShell;\n}  // namespace shell\nnamespace tasm {\n\nusing CSSFontFaceAttrsMap = std::unordered_map<std::string, std::string>;\n\nusing CSSFontFaceRule = std::pair<std::string, CSSFontFaceAttrsMap>;\n\nusing CSSFontFaceRuleMap =\n    std::unordered_map<std::string,\n                       std::vector<std::shared_ptr<CSSFontFaceRule>>>;\n\nclass LayoutCtxPlatformImpl {\n public:\n  virtual ~LayoutCtxPlatformImpl() = default;\n  virtual int CreateLayoutNode(int id, const std::string& tag,\n                               PropBundle* props, bool allow_inline) = 0;\n  virtual void UpdateLayoutNode(int id, PropBundle* props) = 0;\n  virtual void InsertLayoutNode(int parent, int child, int index) = 0;\n  virtual void RemoveLayoutNode(int parent, int child, int index) = 0;\n  virtual void DestroyLayoutNodes(const std::unordered_set<int>& ids) = 0;\n  virtual void ScheduleLayout() = 0;\n  virtual void OnLayoutBefore(int id) = 0;\n  virtual void OnLayout(int id, float left, float top, float width,\n                        float height, const std::array<float, 4>& paddings,\n                        const std::array<float, 4>& borders) = 0;\n  virtual void Destroy() = 0;\n\n  virtual void SetFontFaces(const CSSFontFaceRuleMap& fontfaces) = 0;\n  virtual void SetLynxShell(shell::LynxShell* shell) {}\n  virtual void UpdateRootSize(float width, float height) {}\n  virtual std::unique_ptr<PlatformExtraBundle> GetPlatformExtraBundle(\n      int32_t id) {\n    return std::unique_ptr<PlatformExtraBundle>();\n  }\n  virtual void SetLayoutNodeManager(LayoutNodeManager* layout_node_manager) = 0;\n  virtual std::unique_ptr<PlatformExtraBundleHolder>\n  ReleasePlatformBundleHolder() {\n    return std::unique_ptr<PlatformExtraBundleHolder>();\n  }\n};\n\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LAYOUT_CTX_PLATFORM_IMPL_H_\n"
  },
  {
    "path": "core/public/layout_node_manager.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_PUBLIC_LAYOUT_NODE_MANAGER_H_\n#define CORE_PUBLIC_LAYOUT_NODE_MANAGER_H_\n\n#include <memory>\n\n#include \"core/public/layout_node_value.h\"\n\nnamespace lynx {\n\nnamespace tasm {\n\nclass LayoutNodeManager {\n public:\n  LayoutNodeManager() = default;\n  virtual ~LayoutNodeManager() = default;\n  virtual void SetMeasureFunc(int32_t id,\n                              std::unique_ptr<MeasureFunc> measure_func) = 0;\n  virtual void MarkDirtyAndRequestLayout(int32_t id) = 0;\n  virtual void MarkDirtyAndForceLayout(int32_t id) = 0;\n  virtual bool IsDirty(int32_t id) = 0;\n  virtual FlexDirection GetFlexDirection(int32_t id) = 0;\n  virtual float GetWidth(int32_t id) = 0;\n  virtual float GetHeight(int32_t id) = 0;\n  virtual float GetMinWidth(int32_t id) = 0;\n  virtual float GetMaxWidth(int32_t id) = 0;\n  virtual float GetMinHeight(int32_t id) = 0;\n  virtual float GetMaxHeight(int32_t id) = 0;\n  virtual float GetPaddingLeft(int32_t id) = 0;\n  virtual float GetPaddingTop(int32_t id) = 0;\n  virtual float GetPaddingRight(int32_t id) = 0;\n  virtual float GetPaddingBottom(int32_t id) = 0;\n  virtual float GetMarginLeft(int32_t id) = 0;\n  virtual float GetMarginTop(int32_t id) = 0;\n  virtual float GetMarginRight(int32_t id) = 0;\n  virtual float GetMarginBottom(int32_t id) = 0;\n  virtual LayoutResult UpdateMeasureByPlatform(int32_t id, float width,\n                                               int32_t width_mode, float height,\n                                               int32_t height_mode,\n                                               bool final_measure) = 0;\n  virtual void AlignmentByPlatform(int32_t id, float offset_top,\n                                   float offset_left) = 0;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LAYOUT_NODE_MANAGER_H_\n"
  },
  {
    "path": "core/public/layout_node_value.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_PUBLIC_LAYOUT_NODE_VALUE_H_\n#define CORE_PUBLIC_LAYOUT_NODE_VALUE_H_\n#include <memory>\n\nnamespace lynx {\nnamespace tasm {\n\nstruct LayoutResult {\n  LayoutResult() : width_(0.f), height_(0.f), baseline_(0.f) {}\n  LayoutResult(float width, float height)\n      : width_(width), height_(height), baseline_(0.f) {}\n  LayoutResult(float width, float height, float baseline)\n      : width_(width), height_(height), baseline_(baseline) {}\n  float width_;\n  float height_;\n  float baseline_;\n};\n\nenum class FlexDirection : unsigned {\n  kColumn = 0,\n  kRow = 1,\n  kRowReverse = 2,\n  kColumnReverse = 3,\n};\n\nstruct LayoutNodeStyle {\n  static constexpr float UNDEFINED_MIN_SIZE = 0.f;\n  static constexpr float UNDEFINED_MAX_SIZE = static_cast<float>(0x7FFFFFF);\n};\n\n// FIXME(zhixuan): Layout type flags is a mess now, we should refactor it\nenum LayoutNodeType : unsigned char {\n  // Default is UNKNOWN, indicating that the LayoutNodeType corresponding to the\n  // current tag is still unknown.\n  UNKNOWN = 0,\n  // Common node will not have corresponding platform layout node\n  COMMON = 1,\n  // The layout of virtual node will be handle by its parent which has custom\n  // layout instead of layout engine\n  VIRTUAL = 1 << 1,\n  // Node has custom layout\n  CUSTOM = 1 << 2,\n  // Node is inline and should be measured by native\n  INLINE = 1 << 5\n};\n\nclass MeasureFunc {\n public:\n  virtual ~MeasureFunc() = default;\n  virtual LayoutResult Measure(float width, int32_t width_mode, float height,\n                               int32_t height_mode, bool final_measure) = 0;\n  virtual void Alignment() = 0;\n};\n\n}  // namespace tasm\n\n}  // namespace lynx\n#endif  // CORE_PUBLIC_LAYOUT_NODE_VALUE_H_\n"
  },
  {
    "path": "core/public/list_container_proxy.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_LIST_CONTAINER_PROXY_H_\n#define CORE_PUBLIC_LIST_CONTAINER_PROXY_H_\n#include \"core/base/lynx_export.h\"\n#include \"core/public/list_engine_proxy.h\"\n\nnamespace lynx {\nnamespace shell {\n\nclass LYNX_EXPORT ListContainerProxy {\n public:\n  explicit ListContainerProxy(ListEngineProxy* list_engine_proxy)\n      : list_engine_proxy_(list_engine_proxy){};\n\n  void ScrollByListContainer(int32_t tag, float offset_x, float offset_y,\n                             float original_x, float original_y);\n\n  void ScrollToPosition(int32_t tag, int index, float offset, int align,\n                        bool smooth);\n\n  void ScrollStopped(int32_t tag);\n\n private:\n  ListEngineProxy* list_engine_proxy_;\n};\n\n}  // namespace shell\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LIST_CONTAINER_PROXY_H_\n"
  },
  {
    "path": "core/public/list_data.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_LIST_DATA_H_\n#define CORE_PUBLIC_LIST_DATA_H_\n\n#include <algorithm>\n#include <string>\n#include <vector>\n\nnamespace lynx {\nnamespace tasm {\n/*\n * `ListData` provides the necessary information for lazy-loading of the\n * `<list>`, such as diff, full-span, sticky, etc. This information is generated\n * by the FE framework. It has been deprecated under Fiber.\n */\nclass ListData {\n public:\n  void SetViewTypeNames(const std::vector<std::string>& names) {\n    for (const std::string& name : names) {\n      view_type_names_.emplace_back(name.c_str());\n    }\n  }\n\n  void SetNewArch(bool new_arch) { new_arch_ = new_arch; }\n\n  void SetDiffable(bool diffable) { diffable_ = diffable; }\n\n  void SetFullSpan(const std::vector<uint32_t>& indices) {\n    full_span_.clear();\n    full_span_.insert(full_span_.end(), indices.begin(), indices.end());\n    std::sort(full_span_.begin(), full_span_.end());\n  }\n\n  void SetStickyTop(const std::vector<uint32_t>& indices) {\n    sticky_top_.clear();\n    sticky_top_.insert(sticky_top_.end(), indices.begin(), indices.end());\n    std::sort(sticky_top_.begin(), sticky_top_.end());\n  }\n\n  void SetStickyBottom(const std::vector<uint32_t>& indices) {\n    sticky_bottom_.clear();\n    sticky_bottom_.insert(sticky_bottom_.end(), indices.begin(), indices.end());\n    std::sort(sticky_bottom_.begin(), sticky_bottom_.end());\n  }\n\n  template <class VECTOR_LIKE>\n  void SetInsertions(const VECTOR_LIKE& indices) {\n    insertions_.clear();\n    insertions_.insert(insertions_.end(), indices.begin(), indices.end());\n  }\n\n  template <class VECTOR_LIKE>\n  void SetRemovals(const VECTOR_LIKE& indices) {\n    removals_.clear();\n    removals_.insert(removals_.end(), indices.begin(), indices.end());\n  }\n\n  template <class VECTOR_LIKE>\n  void SetUpdateFrom(const VECTOR_LIKE& indices) {\n    update_from_.clear();\n    update_from_.insert(update_from_.end(), indices.begin(), indices.end());\n  }\n\n  template <class VECTOR_LIKE>\n  void SetUpdateTo(const VECTOR_LIKE& indices) {\n    update_to_.clear();\n    update_to_.insert(update_to_.end(), indices.begin(), indices.end());\n  }\n\n  template <class VECTOR_LIKE>\n  void SetMoveFrom(const VECTOR_LIKE& indices) {\n    move_from_.clear();\n    move_from_.insert(move_from_.end(), indices.begin(), indices.end());\n  }\n\n  template <class VECTOR_LIKE>\n  void SetMoveTo(const VECTOR_LIKE& indices) {\n    move_to_.clear();\n    move_to_.insert(move_to_.end(), indices.begin(), indices.end());\n  }\n\n  const std::vector<std::string>& GetViewTypeNames() const {\n    return view_type_names_;\n  }\n  bool GetNewArch() const { return new_arch_; }\n  bool GetDiffable() const { return diffable_; }\n  const std::vector<int32_t>& GetFullSpan() const { return full_span_; }\n  const std::vector<int32_t>& GetStickyTop() const { return sticky_top_; }\n  const std::vector<int32_t>& GetStickyBottom() const { return sticky_bottom_; }\n  const std::vector<int32_t>& GetInsertions() const { return insertions_; }\n  const std::vector<int32_t>& GetRemovals() const { return removals_; }\n  const std::vector<int32_t>& GetUpdateFrom() const { return update_from_; }\n  const std::vector<int32_t>& GetUpdateTo() const { return update_to_; }\n  const std::vector<int32_t>& GetMoveFrom() const { return move_from_; }\n  const std::vector<int32_t>& GetMoveTo() const { return move_to_; }\n\n private:\n  std::vector<std::string> view_type_names_;\n  bool new_arch_ = false;\n  bool diffable_ = false;\n  std::vector<int32_t> full_span_;\n  std::vector<int32_t> sticky_top_;\n  std::vector<int32_t> sticky_bottom_;\n  std::vector<int32_t> insertions_;\n  std::vector<int32_t> removals_;\n  std::vector<int32_t> update_from_;\n  std::vector<int32_t> update_to_;\n  std::vector<int32_t> move_from_;\n  std::vector<int32_t> move_to_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LIST_DATA_H_\n"
  },
  {
    "path": "core/public/list_engine_proxy.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_LIST_ENGINE_PROXY_H_\n#define CORE_PUBLIC_LIST_ENGINE_PROXY_H_\n\n#include <memory>\n#include <utility>\n\nnamespace lynx {\nnamespace shell {\n\nclass ListEngineProxy {\n public:\n  virtual void ScrollByListContainer(int32_t tag, float offset_x,\n                                     float offset_y, float original_x,\n                                     float original_y) = 0;\n\n  virtual void ScrollToPosition(int32_t tag, int index, float offset, int align,\n                                bool smooth) = 0;\n\n  virtual void ScrollStopped(int32_t tag) = 0;\n};\n\n}  // namespace shell\n\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LIST_ENGINE_PROXY_H_\n"
  },
  {
    "path": "core/public/lynx_engine_proxy.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_LYNX_ENGINE_PROXY_H_\n#define CORE_PUBLIC_LYNX_ENGINE_PROXY_H_\n\n#include <list>\n#include <memory>\n#include <string>\n\n#include \"base/include/closure.h\"\n#include \"core/public/list_data.h\"\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace shell {\n\nclass LynxEngineProxy {\n public:\n  virtual ~LynxEngineProxy() = default;\n\n  virtual void DispatchTaskToLynxEngine(base::closure task) = 0;\n\n  // Event\n  virtual bool SendTouchEvent(const std::string& name, int32_t tag, float x,\n                              float y, float client_x, float client_y,\n                              float page_x, float page_y,\n                              int64_t timestamp = 0) = 0;\n\n  virtual bool SendTouchEvent(const std::string& name, const pub::Value& params,\n                              int64_t timestamp = 0) = 0;\n\n  virtual void SendCustomEvent(const std::string& name, int32_t tag,\n                               const pub::Value& params,\n                               const std::string& params_name) = 0;\n\n  virtual void SendGestureEvent(int tag, int gesture_id, std::string name,\n                                const pub::Value& params) = 0;\n\n  virtual void SendBubbleEvent(const std::string& name, int32_t tag,\n                               const pub::Value& dict) = 0;\n\n  virtual void OnPseudoStatusChanged(int32_t id, int32_t pre_status,\n                                     int32_t current_status) = 0;\n\n  virtual void StartEventGenerate(const pub::Value& event_params) = 0;\n\n  virtual void StartEventCapture(int64_t event_id) = 0;\n\n  virtual void StartEventBubble(int64_t event_id) = 0;\n\n  virtual void StartEventFire(bool is_stop, int64_t event_id) = 0;\n\n  // List\n  // TODO(chenyouhui): Split the list interface into its own public API.\n  virtual void ScrollByListContainer(int32_t tag, float x, float y,\n                                     float original_x, float original_y) = 0;\n\n  virtual void ScrollToPosition(int32_t tag, int index, float offset, int align,\n                                bool smooth) = 0;\n\n  virtual void ScrollStopped(int32_t tag) = 0;\n\n  virtual int32_t ObtainListChild(int32_t tag, uint32_t index,\n                                  int64_t operation_id,\n                                  bool enable_reuse_notification) = 0;\n\n  virtual void RecycleListChild(int32_t tag, uint32_t sign) = 0;\n\n  virtual void RenderListChild(int32_t tag, uint32_t index,\n                               int64_t operation_id) = 0;\n\n  virtual void UpdateListChild(int32_t tag, uint32_t sign, uint32_t index,\n                               int64_t operation_id) = 0;\n\n  virtual tasm::ListData GetListData(int view_id) = 0;\n\n  // This function constructs and returns a list of element tags synchronized\n  // that represent the specified element and its ancestor elements in the DOM\n  // hierarchy. The elements should not affected by the z-index attribute.\n  // Do not call this function when using async tasm.\n  virtual std::list<int32_t> GetAncestorElements(int32_t tag) = 0;\n\n  virtual void MarkLayoutDirty(int sign) = 0;\n\n  // Animation\n  virtual bool EnableRasterAnimation() = 0;\n\n  virtual float GetDensity() const = 0;\n\n  virtual void OnFirstMeaningfulPaint() = 0;\n\n  virtual void TriggerLayout() = 0;\n};\n\n}  // namespace shell\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LYNX_ENGINE_PROXY_H_\n"
  },
  {
    "path": "core/public/lynx_extension_delegate.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n/*\n * This is a experimental API, it is unstable and may break at any time.\n */\n\n#ifndef CORE_PUBLIC_LYNX_EXTENSION_DELEGATE_H_\n#define CORE_PUBLIC_LYNX_EXTENSION_DELEGATE_H_\n\n#include <memory>\n\n#include \"core/public/jsb/native_module_factory.h\"\n#include \"core/public/runtime_lifecycle_observer.h\"\n\nnamespace lynx {\nnamespace shell {\ntemplate <typename T>\nclass LynxActor;\nclass BTSRuntime;\n}  // namespace shell\n\nnamespace pub {\n\nclass LynxExtensionDelegate {\n public:\n  virtual ~LynxExtensionDelegate() = default;\n  virtual void OnDevicePixelRatioChanged(float device_pixel_ratio){};\n  virtual void SetRuntimeActor(\n      std::shared_ptr<shell::LynxActor<shell::BTSRuntime>> actor) = 0;\n};\n\n}  // namespace pub\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LYNX_EXTENSION_DELEGATE_H_\n"
  },
  {
    "path": "core/public/lynx_layout_proxy.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_LYNX_LAYOUT_PROXY_H_\n#define CORE_PUBLIC_LYNX_LAYOUT_PROXY_H_\n\n#include <memory>\n\n#include \"base/include/closure.h\"\n\nnamespace lynx {\n\nnamespace shell {\n\nclass LynxLayoutProxy {\n public:\n  virtual ~LynxLayoutProxy() = default;\n\n  virtual void DispatchTaskToLynxLayout(base::closure task) = 0;\n  virtual void TriggerLayout() = 0;\n};\n\n}  // namespace shell\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LYNX_LAYOUT_PROXY_H_\n"
  },
  {
    "path": "core/public/lynx_resource_loader.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_LYNX_RESOURCE_LOADER_H_\n#define CORE_PUBLIC_LYNX_RESOURCE_LOADER_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/closure.h\"\n#include \"core/base/lynx_export.h\"\n\nnamespace lynx {\nnamespace pub {\n\n// TODO(zhoupeng.z): generated by IDL\nenum class LynxResourceType : int32_t {\n  kGeneric = 0,\n  kImage = 1,\n  kFont = 2,\n  kLottie = 3,\n  kVideo = 4,\n  kSvg = 5,\n  kTemplate = 6,\n  kLazyBundle = 7,  // LazyBundle from js\n  kLynxCoreJs = 8,\n  kExternalJs = 9,\n  kAssets = 11,\n  kI18nText = 12,\n  kGraphics = 13,\n  kTheme = 14,\n  kFrame = 15,\n  kExternalByteCode = 16,  // external byteCode loaded from outside;\n};\n\nstruct ResourceLoadTiming {\n  // all microseconds\n  uint64_t request_start;                    // receive the request from client\n  uint64_t request_internal_prepare_finish;  // internal prepare done, like\n                                             // check url, fallback logic etc.\n  uint64_t\n      request_prepare_to_call_fetcher;      // start to prepare to call fetcher,\n                                            // mostly it is the same as\n                                            // request_internal_prepare_finish\n  uint64_t request_send_to_fetcher;         // actually send request to fetcher\n  uint64_t response_received_from_fetcher;  // actually receive response from\n                                            // fetcher\n  uint64_t response_trigger_callback;       // trigger callback from client\n};\n\nstruct LynxResourceRequest {\n  std::string url;\n  LynxResourceType type;\n\n  // TODO(nihao.royal): used by lazy bundle now, need to be removed in long\n  // term.\n  bool request_in_current_thread = true;\n};\n\nstruct LynxResourceResponse {\n  std::vector<uint8_t> data;\n\n  // used to represent template_bundle type that can be returned by platform.\n  // TODO(nihao.royal): make LynxTemplateBundle a public class.\n  void* bundle = nullptr;\n  int32_t err_code = 0;\n  std::string err_msg;\n  ResourceLoadTiming timing;\n\n  bool Success() const { return err_code == 0; }\n};\n\nstruct LynxPathResponse {\n  std::string path;\n  int32_t err_code = 0;\n  std::string err_msg;\n  ResourceLoadTiming timing;\n\n  bool Success() const { return err_code == 0; }\n};\n\nclass LYNX_EXPORT LynxStreamDelegate {\n public:\n  virtual ~LynxStreamDelegate() = default;\n  virtual void OnStart(size_t size) = 0;\n  virtual void OnData(std::vector<uint8_t> data) = 0;\n  virtual void OnEnd() = 0;\n  virtual void OnError(std::string error_msg) = 0;\n};\n\nclass LYNX_EXPORT LynxResourceLoader\n    : public std::enable_shared_from_this<LynxResourceLoader> {\n public:\n  virtual ~LynxResourceLoader() = default;\n\n  void LoadResource(\n      const LynxResourceRequest& request,\n      base::MoveOnlyClosure<void, LynxResourceResponse&> callback);\n\n  void LoadResourcePath(\n      const LynxResourceRequest& request,\n      base::MoveOnlyClosure<void, LynxPathResponse&> path_callback);\n\n  virtual void LoadStream(\n      const LynxResourceRequest& request,\n      const std::shared_ptr<LynxStreamDelegate>& stream_delegate) {}\n\n  virtual void LoadBytecode(\n      const LynxResourceRequest& request,\n      base::MoveOnlyClosure<void, LynxResourceResponse&> callback) {\n    pub::LynxResourceResponse resp;\n    resp.err_code = -1;\n    resp.err_msg = \"LoadBytecode is not supported.\";\n    callback(resp);\n  }\n\n  virtual bool IsLocalResource(const std::string& url) { return false; }\n\n  virtual std::string ShouldRedirectUrl(const LynxResourceRequest& request) {\n    return request.url;\n  }\n\n protected:\n  virtual void LoadResourceInternal(\n      const LynxResourceRequest& request,\n      base::MoveOnlyClosure<void, LynxResourceResponse&> callback) = 0;\n\n  virtual void LoadResourcePathInternal(\n      const LynxResourceRequest& request,\n      base::MoveOnlyClosure<void, LynxPathResponse&> path_callback){};\n};\n\n}  // namespace pub\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LYNX_RESOURCE_LOADER_H_\n"
  },
  {
    "path": "core/public/lynx_runtime_proxy.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_LYNX_RUNTIME_PROXY_H_\n#define CORE_PUBLIC_LYNX_RUNTIME_PROXY_H_\n\n#include <memory>\n#include <string>\n\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace shell {\n\nclass LynxRuntimeProxy {\n public:\n  virtual ~LynxRuntimeProxy() = default;\n\n  virtual void CallJSFunction(std::string module_id, std::string method_id,\n                              std::unique_ptr<pub::Value> params) = 0;\n\n  virtual void CallJSApiCallbackWithValue(\n      int32_t callback_id, std::unique_ptr<pub::Value> params) = 0;\n\n  virtual void CallJSIntersectionObserver(\n      int32_t observer_id, int32_t callback_id,\n      std::unique_ptr<pub::Value> params) = 0;\n\n  virtual void EvaluateScript(const std::string& url, std::string script,\n                              int32_t callback_id) = 0;\n\n  virtual void RejectDynamicComponentLoad(const std::string& url,\n                                          int32_t callback_id, int32_t err_code,\n                                          const std::string& err_msg) = 0;\n};\n\n}  // namespace shell\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_LYNX_RUNTIME_PROXY_H_\n"
  },
  {
    "path": "core/public/page_options.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_PAGE_OPTIONS_H_\n#define CORE_PUBLIC_PAGE_OPTIONS_H_\n\nnamespace lynx {\nnamespace tasm {\n\n/**\n * Embedded mode is an experimental switch\n * When embeddedMode is set, we offer optimal performance for embedded\n * scenarios. But it will restrict business flexibility. Embedded mode\n * configuration options using bitwise operations for multiple selections\n *\n * Usage:\n * 1. Basic usage:\n *    - Use UNSET for no options selected\n *    - Use EMBEDDED_MODE_BASE for basic optimizations\n *    - Use EMBEDDED_MODE_ALL for all optimizations\n *\n * 2. Combine options:\n *    - Use bitwise OR (|) to combine options\n *    - Example: EMBEDDED_MODE_BASE | ENGINE_POOL\n *\n * 3. Check options:\n *    - Use bitwise AND (&) to check if an option is enabled\n *    - Example: (mode & ENGINE_POOL) != 0\n */\nenum EmbeddedMode {\n  /**\n   * No optimization options selected\n   */\n  UNSET = 0,\n\n  /**\n   * Basic embedded mode with minimal optimizations\n   */\n  EMBEDDED_MODE_BASE = 1 << 0,\n\n  /**\n   * Engine pool optimization for better instance reuse\n   */\n  ENGINE_POOL = 1 << 1,\n\n  /**\n   *  Integrate Layout with Element\n   */\n  LAYOUT_IN_ELEMENT = 1 << 2,\n\n  /**\n   * Generate render content based native `Fragment` layer.\n   */\n  FRAGMENT_LAYER_RENDER = 1 << 3,\n\n  /**\n   * Use TextService to layout and render text.\n   */\n  USE_TEXT_SERVICE = 1 << 4,\n\n  /**\n   * Combination of all optimization options\n   *\n   * Note: When adding new optimization options, update this value\n   */\n  EMBEDDED_MODE_ALL = EMBEDDED_MODE_BASE | ENGINE_POOL | LAYOUT_IN_ELEMENT\n};\n\n/// Common options shared by components within a Lynx page.\n/// Unlike PageConfig, the options are dynamic and can be updated on-the-flight\n/// by calling LynxShell::SetPageOptions\nstruct PageOptions {\n  static constexpr int32_t kUnknownInstanceID = -1;\n\n  PageOptions() = default;\n\n  explicit PageOptions(int32_t instance_id, bool debuggable = false)\n      : instance_id_(instance_id), debuggable_(debuggable) {}\n\n  bool GetDebuggable() const { return debuggable_; }\n\n  void SetDebuggable(bool debuggable) { debuggable_ = debuggable; }\n\n  void SetInstanceID(int32_t instance_id) { instance_id_ = instance_id; }\n\n  int32_t GetInstanceID() const { return instance_id_; }\n\n  // Set long task monitoring explicitly disabled for this instance.\n  // If true, long task monitoring will always be disabled.\n  // If false, long task monitoring will respect the default behavior defined by\n  // lynx::tasm::timing::LongTaskMonitor\n  void SetLongTaskMonitorDisabled(bool disabled) {\n    long_task_disabled_ = disabled;\n  }\n\n  // Get long task monitoring disabled status for this instance.\n  bool GetLongTaskMonitorDisabled() const { return long_task_disabled_; }\n\n  void SetEmbeddedMode(EmbeddedMode mode) { embedded_mode_ = mode; }\n\n  EmbeddedMode GetEmbeddedMode() const { return embedded_mode_; }\n\n  bool IsEmbeddedModeOn() const {\n    return embedded_mode_ & EmbeddedMode::EMBEDDED_MODE_BASE;\n  }\n\n  bool IsLayoutInElementModeOn() const {\n    return (embedded_mode_ & (EmbeddedMode::LAYOUT_IN_ELEMENT |\n                              EmbeddedMode::FRAGMENT_LAYER_RENDER)) > 0;\n  }\n\n  // When USE_TEXT_SERVICE or FRAGMENT_LAYER_RENDER is on, text layout and\n  // render will be done by TextService.\n  bool IsUsingTextService() const {\n    return (embedded_mode_ & (EmbeddedMode::USE_TEXT_SERVICE |\n                              EmbeddedMode::FRAGMENT_LAYER_RENDER)) > 0;\n  }\n\n  bool IsFragmentLayerRender() const {\n    return (embedded_mode_ & EmbeddedMode::FRAGMENT_LAYER_RENDER) > 0;\n  }\n\n  void SetHasLogicExecutor(bool enable) { has_logic_executor_ = enable; }\n\n  bool HasLogicExecutor() const { return has_logic_executor_; }\n\n private:\n  int32_t instance_id_{kUnknownInstanceID};\n  bool debuggable_{false};\n  bool long_task_disabled_{false};\n  EmbeddedMode embedded_mode_{UNSET};\n  bool has_logic_executor_{false};\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_PAGE_OPTIONS_H_\n"
  },
  {
    "path": "core/public/painting_ctx_platform_impl.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_PAINTING_CTX_PLATFORM_IMPL_H_\n#define CORE_PUBLIC_PAINTING_CTX_PLATFORM_IMPL_H_\n\n#include <atomic>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/lynx_actor.h\"\n#include \"core/public/layout_node_value.h\"\n#include \"core/public/pipeline_option.h\"\n#include \"core/public/platform_extra_bundle.h\"\n#include \"core/public/platform_renderer_type.h\"\n#include \"core/public/prop_bundle.h\"\n#include \"core/public/text_layout_impl.h\"\n#include \"core/public/timing_key.h\"\n#include \"core/public/ui_operation_queue_interface.h\"\n\nnamespace lynx {\n\nnamespace shell {\nclass DynamicUIOperationQueue;\n}  // namespace shell\n\nnamespace tasm {\n\nclass LynxTemplateBundle;\nclass NativePaintingContext;\n\n// painting context platform object ref.\nclass PaintingCtxPlatformRef {\n public:\n  virtual ~PaintingCtxPlatformRef() = default;\n\n  virtual void InsertPaintingNode(int parent, int child, int index) {}\n  virtual void RemovePaintingNode(int parent, int child, int index,\n                                  bool is_move) {}\n  virtual void DestroyPaintingNode(int parent, int child, int index) {}\n\n  // update the data of the method of \"scrolling\"\n  virtual void UpdateScrollInfo(int32_t container_id, bool smooth,\n                                float estimated_offset, bool scrolling) {}\n\n  virtual void SetGestureDetectorState(int64_t id, int32_t gesture_id,\n                                       int32_t state) {}\n\n  virtual void UpdateNodeReadyPatching(std::vector<int32_t> ready_ids,\n                                       std::vector<int32_t> remove_ids) {}\n  virtual void UpdateNodeReloadPatching(std::vector<int32_t> reload_ids) {}\n  virtual void UpdateEventInfo(bool has_touch_pseudo) {}\n  virtual void UpdateFlattenStatus(int id, bool flatten) {}\n\n  virtual void ListReusePaintingNode(int id, const std::string& item_key){};\n  virtual void ListCellWillAppear(int sign, const std::string& item_key){};\n  virtual void ListCellDisappear(int sign, bool isExist,\n                                 const std::string& item_key){};\n  // insert the child's painting node of the list\n  virtual void InsertListItemPaintingNode(int32_t list_id, int32_t child_id) {}\n  // remove the child's painting node of the list\n  virtual void RemoveListItemPaintingNode(int32_t list_id, int32_t child_id) {}\n  // update the listContainer's contentOffset and contentSize\n  virtual void UpdateContentOffsetForListContainer(int32_t container_id,\n                                                   float content_size,\n                                                   float delta_x, float delta_y,\n                                                   bool is_init_scroll_offset,\n                                                   bool from_layout) {}\n  virtual void SetNeedMarkPaintEndTiming(const tasm::PipelineID& pipeline_id) {}\n\n  virtual void MarkUIOperationQueueFlushForRecreateEngine(bool enable) {}\n\n  virtual bool IsNativePaintingCtxPlatformRef() { return false; }\n};\n\nstruct PaintingCtxPlatformImplConfig {\n  bool enable_native_schedule_create_view_async;\n};\n\nclass PaintingCtxPlatformImpl {\n public:\n  virtual ~PaintingCtxPlatformImpl() {}\n\n  void SetConfig(PaintingCtxPlatformImplConfig&& config) {\n    config_ = std::move(config);\n  }\n\n  virtual void SetUIOperationQueue(\n      const std::shared_ptr<shell::UIOperationQueueInterface>& queue){};\n  virtual void SetInstanceId(const int32_t instance_id){};\n  virtual void CreatePaintingNode(int id, const std::string& tag,\n                                  const fml::RefPtr<PropBundle>& painting_data,\n                                  bool flatten, bool create_node_async,\n                                  uint32_t node_index = 0) = 0;\n  virtual void InsertPaintingNode(int parent, int child, int index) {}\n  virtual void RemovePaintingNode(int parent, int child, int index,\n                                  bool is_move){};\n  virtual void DestroyPaintingNode(int parent, int child, int index){};\n  virtual void UpdatePaintingNode(\n      int id, bool tend_to_flatten,\n      const fml::RefPtr<PropBundle>& painting_data) = 0;\n\n  virtual std::unique_ptr<pub::Value> GetTextInfo(const std::string& content,\n                                                  const pub::Value& info) = 0;\n\n  virtual void StopExposure(const pub::Value& options) = 0;\n  virtual void ResumeExposure() = 0;\n\n  virtual void SetFrameAppBundle(\n      int tag, const std::shared_ptr<LynxTemplateBundle>& bundle){};\n\n  virtual void UpdateLayout(int tag, float x, float y, float width,\n                            float height, const float* paddings,\n                            const float* margins, const float* borders,\n                            const float* bounds, const float* sticky,\n                            float max_height, uint32_t node_index = 0) = 0;\n  virtual void UpdatePlatformExtraBundle(int32_t id,\n                                         PlatformExtraBundle* bundle) {}\n\n  virtual void SetKeyframes(fml::RefPtr<PropBundle> keyframes_data) = 0;\n  virtual void Flush() = 0;\n  virtual void FlushImmediately() { Flush(); };\n  virtual void HandleValidate(int tag) = 0;\n  virtual void FinishTasmOperation(\n      const std::shared_ptr<PipelineOptions>& options) = 0;\n  virtual void FinishLayoutOperation(\n      const std::shared_ptr<PipelineOptions>& options) = 0;\n\n  virtual std::vector<float> getBoundingClientOrigin(int id) = 0;\n  virtual std::vector<float> getWindowSize(int id) = 0;\n  virtual std::vector<float> GetRectToWindow(int id) = 0;\n\n  virtual std::vector<float> GetRectToLynxView(int64_t id) = 0;\n  virtual std::vector<float> ScrollBy(int64_t id, float width,\n                                      float height) = 0;\n  // TODO(liting.src): remove later.\n  virtual void ConsumeGesture(int64_t id, int32_t gesture_id,\n                              const pub::Value& params){};\n  virtual void Invoke(\n      int64_t id, const std::string& method, const pub::Value& params,\n      const std::function<void(int32_t code, const pub::Value& data)>&\n          callback) = 0;\n\n  virtual int32_t GetTagInfo(const std::string& tag_name) = 0;\n  virtual bool IsFlatten(base::MoveOnlyClosure<bool, bool> func) = 0;\n\n  virtual bool NeedAnimationProps() = 0;\n\n  virtual void UpdateLayoutPatching() {}\n  virtual void OnFirstMeaningfulLayout() {}\n\n  // TODO(liting.src): remove this method after ui operation queue refactor.\n  virtual void UpdateNodeReadyPatching(std::vector<int32_t> ready_ids,\n                                       std::vector<int32_t> remove_ids) {}\n\n  virtual void SetContextHasAttached() {}\n  virtual void SetEnableVsyncAlignedFlush(bool enabled) {}\n\n  virtual void InvokeUIMethod(int32_t view_id, const std::string& method,\n                              fml::RefPtr<tasm::PropBundle> args,\n                              int32_t callback_id) {}\n  virtual void getAbsolutePosition(int id, float* position) {}\n\n  virtual void EnableUIOperationBatching(){};\n\n  virtual bool DefaultOverflowAlwaysVisible() { return false; }\n\n  // TODO(chenyouhui): remove this method after ui operation queue refactor.\n  virtual bool EnableParallelElement() { return true; }\n\n  // TODO(liting.src): remove this method after ui operation queue refactor.\n  virtual bool HasEnableUIOperationBatching() { return false; }\n\n  virtual bool EnableUIOperationQueue() { return false; }\n\n  // platform object ref.\n  const std::shared_ptr<PaintingCtxPlatformRef>& GetPlatformRef() {\n    return platform_ref_;\n  }\n\n  // TODO(liting.src): remove this method after ui operation queue refactor.\n  virtual base::closure ExecuteOperationSafely(base::closure op) { return op; }\n\n  virtual LayoutResult MeasureText(Element* element, float width,\n                                   int width_mode, float height,\n                                   int height_mode) {\n    return text_layout_impl_->Measure(element, width, width_mode, height,\n                                      height_mode);\n  }\n\n  virtual void DispatchLayoutBefore(Element* element) {\n    text_layout_impl_->DispatchLayoutBefore(element);\n  }\n\n  virtual void AlignText(Element* element) {\n    text_layout_impl_->Align(element);\n  }\n\n  virtual void DestroyText(Element* element) {\n    if (text_layout_impl_) {\n      text_layout_impl_->Destroy(element);\n    }\n  }\n\n  virtual NativePaintingContext* CastToNativeCtx() { return nullptr; }\n\n  void MarkUIOperationQueueFlushForRecreateEngine(bool enable);\n\n  bool IsLayoutFinish() { return is_layout_finish_; }\n\n  void ResetLayoutStatus() { is_layout_finish_ = false; }\n\n  void MarkLayoutFinish() { is_layout_finish_ = true; }\n\n protected:\n  std::atomic<bool> is_layout_finish_ = {false};\n\n  std::shared_ptr<PaintingCtxPlatformRef> platform_ref_;\n  std::unique_ptr<TextLayoutImpl> text_layout_impl_;\n  PaintingCtxPlatformImplConfig config_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_PAINTING_CTX_PLATFORM_IMPL_H_\n"
  },
  {
    "path": "core/public/perf_controller_proxy.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_PERF_CONTROLLER_PROXY_H_\n#define CORE_PUBLIC_PERF_CONTROLLER_PROXY_H_\n#include <memory>\n#include <string>\n#include <unordered_map>\n\n#include \"base/include/closure.h\"\n#include \"core/public/pipeline_option.h\"\n#include \"core/public/timing_key.h\"\n\nnamespace lynx {\nnamespace shell {\n\n/**\n * @brief Event struct containing different types of property\n */\nstruct ReportEvent {\n  std::string event_name;\n  std::unordered_map<std::string, std::string> string_props;\n  std::unordered_map<std::string, int> int_props;\n  std::unordered_map<std::string, double> double_props;\n};\n\nclass PerfControllerProxy {\n public:\n  virtual ~PerfControllerProxy() = default;\n\n  /**\n   * @brief Support setting HostPlatformType field (such as 'windowsClay'),\n   * which will affect the platform type tag in HostPlatformTiming data in\n   * PipelineEntry\n   * @param type The specific host platform type.\n   */\n  virtual void SetHostPlatformType(const std::string& type) = 0;\n\n  /**\n   * @brief Marks a timing event with the specified key and pipeline ID.\n   * @param timing_key The key that uniquely identifies the timing event.\n   * @param pipeline_id The optional pipeline ID associated with the timing\n   * event.\n   */\n  virtual void MarkTiming(tasm::TimingKey timing_key,\n                          const tasm::PipelineID& pipeline_id) = 0;\n  virtual void SetTiming(tasm::TimingKey timing_key, uint64_t timestamp_us,\n                         const tasm::PipelineID& pipeline_id) = 0;\n\n  /**\n   * @brief Set a host-platform-timing event with the specified key and\n   * pipeline ID.\n   * @param timing_key The key that uniquely identifies the timing event.\n   * @param timestamp_us Timestamp of the timing event occurrence\n   * @param pipeline_id The optional pipeline ID associated with the timing\n   * event.\n   */\n  virtual void SetHostPlatformTiming(tasm::TimingKey timing_key,\n                                     uint64_t timestamp_us,\n                                     const tasm::PipelineID& pipeline_id) = 0;\n\n  /**\n   * @brief Get the currently running platform\n   * @return eg. 'windows' 'macOS' 'iOS' 'Android' 'Linux'\n   */\n  virtual std::string GetPlatform() const = 0;\n\n  /**\n   * @brief Post an async task to the report thread\n   * @param task The async task\n   */\n  virtual void RunTaskInReportThread(base::closure task) = 0;\n\n  /**\n   * @brief Interface to report an event\n   * @param instance_id The instanceId of a lynx view\n   * @param event The event to be reported\n   */\n  virtual void OnEvent(int32_t instance_id, ReportEvent& event) = 0;\n};\n\n}  // namespace shell\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_PERF_CONTROLLER_PROXY_H_\n"
  },
  {
    "path": "core/public/performance_controller_platform_impl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_PERFORMANCE_CONTROLLER_PLATFORM_IMPL_H_\n#define CORE_PUBLIC_PERFORMANCE_CONTROLLER_PLATFORM_IMPL_H_\n\n#include <memory>\n\n#include \"base/include/lynx_actor.h\"\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace performance {\nclass PerformanceController;\nclass PerformanceControllerPlatformImpl {\n public:\n  virtual ~PerformanceControllerPlatformImpl() = default;\n  virtual void SetActor(\n      const std::shared_ptr<shell::LynxActor<PerformanceController>>&\n          actor) = 0;\n  virtual void OnPerformanceEvent(const std::unique_ptr<pub::Value>& entry) = 0;\n};\n}  // namespace performance\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_PERFORMANCE_CONTROLLER_PLATFORM_IMPL_H_\n"
  },
  {
    "path": "core/public/pipeline_option.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_PIPELINE_OPTION_H_\n#define CORE_PUBLIC_PIPELINE_OPTION_H_\n\n#include <sys/types.h>\n\n#include <sstream>\n#include <string>\n#include <thread>\n#include <vector>\n\n#if ENABLE_TRACE_PERFETTO\n#include \"base/trace/native/trace_event.h\"\n#endif\n\n#include \"base/include/timer/time_utils.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nusing PipelineID = std::string;\nusing PipelineOrigin = std::string;\n\nstruct ListItemLifeOption {\n  double update_duration() const {\n    if (end_update_time_ != 0 && start_update_time_ != 0) {\n      return (end_update_time_ - start_update_time_) / 1000.f;\n    }\n    return 0;\n  }\n\n  double render_duration() const {\n    if (end_render_time_ != 0 && start_render_time_ != 0) {\n      return (end_render_time_ - start_render_time_) / 1000.f;\n    }\n    return 0;\n  }\n\n  double dispatch_duration() const {\n    if (end_dispatch_time_ != 0 && start_dispatch_time_ != 0) {\n      return (end_dispatch_time_ - start_dispatch_time_) / 1000.f;\n    }\n    return 0;\n  }\n\n  double layout_duration() const {\n    if (end_layout_time_ != 0 && start_layout_time_ != 0) {\n      return (end_layout_time_ - start_layout_time_) / 1000.f;\n    }\n    return 0;\n  }\n\n  uint64_t start_render_time_{0};\n  uint64_t end_render_time_{0};\n  uint64_t start_dispatch_time_{0};\n  uint64_t end_dispatch_time_{0};\n  uint64_t start_update_time_{0};\n  uint64_t end_update_time_{0};\n  uint64_t start_layout_time_{0};\n  uint64_t end_layout_time_{0};\n};\n\nclass Element;\nclass PipelineVersion;\n\nstruct PipelineOptions {\n  // TODO(songshourui.null): The platform layer's LoadTemplate can accept these\n  // options, which are directly mapped to the platform-layer LynxLoadOption\n  // enums. A centralized source of truth should be maintained, with other\n  // configurations generated from this source.\n  static const int32_t kDumpElement = 0x01 << 1;\n  static const int32_t kRecycleTemplateBundle = 0x01 << 2;\n  static const int32_t kProcessLayoutWithoutUIFlush = 0x01 << 3;\n  static const int32_t kRenderForRecreateEngine = 0x01 << 4;\n\n  // TODO(kechenglong): impl ToLepusValue here.\n  // Default constructor that generates a unique PipelineID\n  explicit PipelineOptions() {\n    pipeline_id =\n        PipelineIDGenerator::Instance()->GenerateThreadTimestampPipelineID();\n    pipeline_start_timestamp = base::CurrentSystemTimeMicroseconds();\n  }\n\n  void ResetListRelatedFlag() {\n    list_id_ = 0;\n    list_comp_id_ = 0;\n    operation_id = 0;\n    list_item_ids_.clear();\n    operation_ids_.clear();\n    updated_list_elements_.clear();\n  }\n\n  PipelineID pipeline_id;\n  PipelineOrigin pipeline_origin;\n  uint64_t pipeline_start_timestamp;\n\n  int64_t operation_id = 0;\n\n  bool need_timestamps{false};\n\n  bool is_first_screen = false;\n\n  bool is_reuse_engine{false};\n  // true if triggered by reloadTemplate, used to mark setup timing\n  bool is_reload_template = false;\n  // true if has layout\n  // TODO(heshan):put to a new struct like LayoutResultBundle\n  // which may just consumed by FinishLayoutOperation\n  bool has_layout = false;\n  // true if need call DispatchLayoutUpdates\n  bool trigger_layout_ = true;\n  // Whether mark entire tree dirty or not.\n  bool force_resolve_style_ = false;\n  // Whether mark entire tree dirty and reset style sheet or not.\n  bool force_update_style_sheet_ = false;\n  // Indicate this option is created in on scripting start\n  bool created_in_on_scripting_start_ = false;\n  // Indicate this pipeline is executed in pre painting mode.\n  bool enable_pre_painting = false;\n  // Indicate this pipeline need recycle template bundle.\n  bool enable_recycle_template_bundle = false;\n  // Indicate this pipeline need dump element tree.\n  bool enable_dump_element_tree = false;\n  // Indicate this pipeline is executed for a 'recreate' engine.\n  bool render_for_recreate_engine = false;\n\n  // This variable records the order of native update data. Used for syncFlush\n  // only.\n  uint32_t native_update_data_order_ = 0;\n  // the component id of list\n  int32_t list_comp_id_ = 0;\n  // The id of list.\n  int32_t list_id_ = 0;\n  // The array of operation id in list batch render\n  std::vector<int64_t> operation_ids_;\n  // The array of list item id in list batch render\n  std::vector<int32_t> list_item_ids_;\n  // The ids of layout updated list elements.\n  mutable std::vector<int32_t> updated_list_elements_;\n  mutable ListItemLifeOption list_item_life_option_;\n  bool enable_report_list_item_life_statistic_{false};\n  // Return true if this pipeline is triggered by render list item.\n  bool IsRenderListItem() const {\n    return operation_id != 0 && list_id_ != 0 && list_comp_id_ != 0;\n  }\n#if ENABLE_TRACE_PERFETTO\n  void UpdateTraceDebugInfo(TraceEvent* event) const {\n    auto* debug_pipeline_id = event->add_debug_annotations();\n    debug_pipeline_id->set_name(\"pipeline_id\");\n    debug_pipeline_id->set_string_value(pipeline_id);\n    auto* debug_pipeline_origin = event->add_debug_annotations();\n    debug_pipeline_origin->set_name(\"pipeline_origin\");\n    debug_pipeline_origin->set_string_value(pipeline_origin);\n    auto* debug_need_timestamps = event->add_debug_annotations();\n    debug_need_timestamps->set_name(\"need_timestamps\");\n    debug_need_timestamps->set_string_value(need_timestamps ? \"true\" : \"false\");\n    auto* debug_operation_id = event->add_debug_annotations();\n    debug_operation_id->set_name(\"operation_id\");\n    debug_operation_id->set_string_value(std::to_string(operation_id));\n    auto* debug_is_first_screen = event->add_debug_annotations();\n    debug_is_first_screen->set_name(\"is_first_screen\");\n    debug_is_first_screen->set_string_value(is_first_screen ? \"true\" : \"false\");\n    auto* debug_has_layout = event->add_debug_annotations();\n    debug_has_layout->set_name(\"has_layout\");\n    debug_has_layout->set_string_value(has_layout ? \"true\" : \"false\");\n  }\n#endif\n  bool HeldByContext() const { return version != nullptr; }\n\n  bool need_trigger_data_updated_{false};\n\n  // flag for unified pixel pipeline\n  bool enable_unified_pixel_pipeline{false};\n\n  // Switches for pipeline stage.\n  bool resolve_requested{false};\n  bool layout_requested{false};\n  bool flush_ui_requested{false};\n  Element* target_node{nullptr};\n  // Whether the current template has been reloaded.\n  bool reload{false};\n  const PipelineVersion* version{nullptr};\n\n private:\n  // Helper class to generate pipelineID\n  class PipelineIDGenerator {\n   public:\n    static PipelineIDGenerator* Instance() {\n      static thread_local PipelineIDGenerator instance_;\n      return &instance_;\n    }\n    PipelineID GenerateThreadTimestampPipelineID() {\n      if (thread_id_prefix_.empty()) {\n        std::stringstream ss;\n        ss << std::this_thread::get_id();\n        thread_id_prefix_ = ss.str() + \"_\";\n      }\n      auto pipeline_id =\n          thread_id_prefix_ + std::to_string(GetNextPipelineID());\n      return pipeline_id;\n    };\n\n   private:\n    std::string thread_id_prefix_;\n    uint64_t pipeline_id_generator_{0};\n    uint64_t GetNextPipelineID() { return ++pipeline_id_generator_; }\n  };\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_PIPELINE_OPTION_H_\n"
  },
  {
    "path": "core/public/platform_extra_bundle.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_PLATFORM_EXTRA_BUNDLE_H_\n#define CORE_PUBLIC_PLATFORM_EXTRA_BUNDLE_H_\n\n#include <cstdint>\n\nnamespace lynx {\nnamespace tasm {\n\nclass PlatformExtraBundle;\n\nclass PlatformExtraBundleHolder {\n public:\n  PlatformExtraBundleHolder() = default;\n  virtual ~PlatformExtraBundleHolder() = default;\n};\n\nclass PlatformExtraBundle {\n public:\n  PlatformExtraBundle(int32_t signature, PlatformExtraBundleHolder *holder)\n      : signature_(signature), holder_(holder) {}\n\n  virtual ~PlatformExtraBundle() = default;\n\n  int32_t Signature() const { return signature_; }\n\n  PlatformExtraBundleHolder *Holder() const { return holder_; }\n\n private:\n  int32_t signature_;\n  PlatformExtraBundleHolder *holder_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_PLATFORM_EXTRA_BUNDLE_H_\n"
  },
  {
    "path": "core/public/platform_renderer_type.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_PLATFORM_RENDERER_TYPE_H_\n#define CORE_PUBLIC_PLATFORM_RENDERER_TYPE_H_\n#include <cstdint>\n\nenum class PlatformRendererType : uint8_t {\n  kUnknown = 0,\n  kView = 1,\n  kPage = 2,\n  kScroll = 3,\n  kText = 4,\n  kImage = 5,\n  kList = 6,\n  kListItem = 7,\n};\n\n#endif  // CORE_PUBLIC_PLATFORM_RENDERER_TYPE_H_\n"
  },
  {
    "path": "core/public/prop_bundle.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_PUBLIC_PROP_BUNDLE_H_\n#define CORE_PUBLIC_PROP_BUNDLE_H_\n\n#include <sys/types.h>\n\n#include <memory>\n#include <string>\n#include <string_view>\n#include <type_traits>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/vector.h\"\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace tasm {\n\n// TODO(chenyouhui): Remove GestureDetector from prop_bundle completely\nclass GestureDetector;\n\nenum CSSPropertyID : int32_t;\n\nclass PropBundle : public fml::RefCountedThreadSafeStorage {\n public:\n  PropBundle() = default;\n\n  virtual ~PropBundle() = default;\n  virtual void SetNullProps(const char* key) = 0;\n  virtual void SetProps(const char* key, unsigned int value) = 0;\n  virtual void SetProps(const char* key, int value) = 0;\n  virtual void SetProps(const char* key, const char* value) = 0;\n  virtual void SetProps(const char* key, bool value) = 0;\n  virtual void SetProps(const char* key, double value) = 0;\n  virtual void SetProps(const char* key, const pub::Value& value) = 0;\n  virtual void SetProps(const pub::Value& value) = 0;\n  virtual void SetEventHandler(const pub::Value& event) = 0;\n  virtual void SetGestureDetector(const GestureDetector& detector) = 0;\n  virtual void ResetEventHandler() = 0;\n  virtual bool Contains(const char* key) const = 0;\n\n  // styles.\n  virtual void SetNullPropsByID(CSSPropertyID id) = 0;\n  virtual void SetPropsByID(CSSPropertyID id, unsigned int value) = 0;\n  virtual void SetPropsByID(CSSPropertyID id, int value) = 0;\n  virtual void SetPropsByID(CSSPropertyID id, const char* value) = 0;\n  virtual void SetPropsByID(CSSPropertyID id, bool value) = 0;\n  virtual void SetPropsByID(CSSPropertyID id, double value) = 0;\n  virtual void SetPropsByID(CSSPropertyID id, const pub::Value& value) = 0;\n  virtual void SetPropsByID(CSSPropertyID id, const uint8_t* data,\n                            size_t size) = 0;\n  virtual void SetPropsByID(CSSPropertyID id, const uint32_t* data,\n                            size_t size) = 0;\n\n  template <typename T>\n  void SetPropsByID(CSSPropertyID id, const base::Vector<T>& value) {\n    SetPropsByIDInner(id, value);\n  }\n\n  template <typename T>\n  void SetPropsByID(CSSPropertyID id, const std::vector<T>& value) {\n    SetPropsByIDInner(id, value);\n  }\n\n  virtual bool IsNative() const { return false; }\n\n  // TODO(wujintian): Currently, the copy of the element depends on the shallow\n  // copy optimization of the prop bundle to improve performance. In the future,\n  // when we implement the ability to update multiple prop bundles in a LynxUI\n  // at once, the copied element can choose to create a new prop bundle for\n  // updating styles instead of modifying a const prop bundle. At that time, the\n  // copy of the element will no longer depend on the shallow copy of the prop\n  // bundle, and the related code for the shallow copy of the prop bundle can be\n  // removed.\n\n  // This function is used to perform a shallow copy of the prop bundle. The\n  // prop bundle is a map, and in this context, a shallow copy means that only\n  // the first-level keys and values of the prop bundle are copied.\n  virtual fml::RefPtr<PropBundle> ShallowCopy() = 0;\n\n  void ReleaseSelf() const override { delete this; }\n\n private:\n  // TODO: remove the friend later\n  friend class RichTextNode;\n\n  template <typename T>\n  void SetPropsByIDInner(CSSPropertyID id, const T& value) {\n    if constexpr (std::is_same_v<typename T::value_type, uint8_t> ||\n                  std::is_same_v<typename T::value_type, uint32_t>) {\n      SetPropsByID(id, value.data(), value.size());\n    } else {\n      uint32_t buffer[value.size()];\n      for (size_t i = 0; i < value.size(); i++) {\n        buffer[i] = static_cast<uint32_t>(value[i]);\n      }\n      SetPropsByID(id, buffer, value.size());\n    }\n  }\n};\n\nclass PropBundleCreator {\n public:\n  PropBundleCreator() = default;\n  virtual ~PropBundleCreator() = default;\n\n  virtual fml::RefPtr<PropBundle> CreatePropBundle() = 0;\n\n  /**\n   * create prop bundle using mapBuffer or not. Only supported in Android by\n   * now.\n   */\n  virtual fml::RefPtr<PropBundle> CreatePropBundle(bool use_map_buffer) {\n    return CreatePropBundle();\n  };\n\n  /*\n   * Create a lepus::Value based prop bundle named NativePropBundle when\n   * use_native_value is true. And currently for fragment_layer_render only.\n   */\n  virtual fml::RefPtr<PropBundle> CreatePropBundle(bool use_map_buffer,\n                                                   bool use_native_value) {\n    return CreatePropBundle(use_map_buffer);\n  }\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_PROP_BUNDLE_H_\n"
  },
  {
    "path": "core/public/pub_value.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_PUB_VALUE_H_\n#define CORE_PUBLIC_PUB_VALUE_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/closure.h\"\n\nnamespace lynx {\nnamespace pub {\n\nenum class ValueBackendType {\n  ValueBackendTypeInvalid,\n  ValueBackendTypeLepus,\n  ValueBackendTypePiper,\n  ValueBackendTypeCustom,\n  ValueBackendTypeDarwin,\n  ValueBackendTypeJava,\n  ValueBackendTypeNapi,\n  ValueBackendTypeNapiPrimJS,\n};\n\nclass PubValueFactory;\nclass Value;\n\nusing ForeachMapFunc =\n    base::MoveOnlyClosure<void, const pub::Value&, const pub::Value&>;\nusing ForeachArrayFunc =\n    base::MoveOnlyClosure<void, int64_t, const pub::Value&>;\n\nclass ScopedCircleChecker;\n\nclass Value {\n public:\n  virtual ~Value() = default;\n\n  ValueBackendType backend_type() const { return backend_type_; }\n\n  // Type\n  virtual int64_t Type() const = 0;\n  virtual bool IsUndefined() const = 0;\n  virtual bool IsBool() const = 0;\n  virtual bool IsInt32() const = 0;\n  virtual bool IsInt64() const = 0;\n  virtual bool IsUInt32() const = 0;\n  virtual bool IsUInt64() const = 0;\n  virtual bool IsDouble() const = 0;\n  virtual bool IsNumber() const = 0;\n\n  virtual bool IsNil() const = 0;\n  virtual bool IsString() const = 0;\n  virtual bool IsArray() const = 0;\n  virtual bool IsArrayBuffer() const { return false; }\n  virtual bool IsMap() const = 0;\n  virtual bool IsFunction() const = 0;\n  // Transfer type: If a type is a transfer type, it must be parsed through\n  // ParseTransferValue\n  virtual bool IsTransfer() const { return false; };\n  // LynxObject type: if a type is a LynxObject type, it must be parsed through\n  // ParseLynxObject\n  virtual bool IsLynxObject() const { return false; };\n\n  // Indicates whether a templateData inside;\n  virtual bool IsTemplateData() const { return false; }\n\n  // Getter\n  virtual bool Bool() const = 0;\n  virtual double Double() const = 0;\n  virtual int32_t Int32() const = 0;\n  virtual uint32_t UInt32() const = 0;\n  virtual int64_t Int64() const = 0;\n  virtual uint64_t UInt64() const = 0;\n  // TODO(zhangqun.29): The double return value cannot represent Int64,Delete\n  // the interface\n  virtual double Number() const = 0;\n  virtual uint8_t* ArrayBuffer() const { return nullptr; }\n  virtual const std::string& str() const = 0;\n  virtual int Length() const = 0;\n  virtual bool IsEqual(const Value& value) const { return false; }\n\n  // Iterator\n  virtual void ForeachArray(pub::ForeachArrayFunc func) const = 0;\n  virtual void ForeachMap(pub::ForeachMapFunc func) const = 0;\n\n  // Find\n  virtual std::unique_ptr<Value> GetValueAtIndex(uint32_t idx) const = 0;\n  virtual bool Erase(uint32_t idx) const = 0;\n  virtual std::unique_ptr<Value> GetValueForKey(\n      const std::string& key) const = 0;\n  virtual bool Erase(const std::string& key) const = 0;\n  virtual bool Contains(const std::string& key) const = 0;\n\n  // Setter\n  virtual bool PushValueToArray(const Value& value) { return false; }\n  virtual bool PushValueToArray(std::unique_ptr<Value> value) { return false; }\n  virtual bool PushNullToArray() { return false; }\n  virtual bool PushArrayBufferToArray(std::unique_ptr<uint8_t[]> value,\n                                      size_t length) {\n    return false;\n  }\n  virtual bool PushStringToArray(const std::string& value) { return false; }\n  virtual bool PushBigIntToArray(const std::string& value) { return false; }\n  virtual bool PushBoolToArray(bool value) { return false; }\n  virtual bool PushDoubleToArray(double value) { return false; }\n  virtual bool PushInt32ToArray(int32_t value) { return false; }\n  virtual bool PushUInt32ToArray(uint32_t value) { return false; }\n  virtual bool PushInt64ToArray(int64_t value) { return false; }\n  virtual bool PushUInt64ToArray(uint64_t value) { return false; }\n\n  virtual bool PushValueToMap(const std::string& key, const Value& value) {\n    return false;\n  }\n  virtual bool PushValueToMap(const std::string& key,\n                              std::unique_ptr<Value> value) {\n    return false;\n  }\n  virtual bool PushNullToMap(const std::string& key) { return false; }\n  virtual bool PushArrayBufferToMap(const std::string& key,\n                                    std::unique_ptr<uint8_t[]> value,\n                                    size_t length) {\n    return false;\n  }\n  virtual bool PushStringToMap(const std::string& key,\n                               const std::string& value) {\n    return false;\n  }\n\n  virtual bool PushBigIntToMap(const std::string& key,\n                               const std::string& value) {\n    return false;\n  }\n  virtual bool PushBoolToMap(const std::string& key, bool value) {\n    return false;\n  }\n  virtual bool PushDoubleToMap(const std::string& key, double value) {\n    return false;\n  }\n  virtual bool PushInt32ToMap(const std::string& key, int32_t value) {\n    return false;\n  }\n  virtual bool PushUInt32ToMap(const std::string& key, uint32_t value) {\n    return false;\n  }\n  virtual bool PushInt64ToMap(const std::string& key, int64_t value) {\n    return false;\n  }\n  virtual bool PushUInt64ToMap(const std::string& key, uint64_t value) {\n    return false;\n  }\n\n  // Verify\n  virtual bool CheckCircle(\n      std::vector<std::unique_ptr<pub::Value>>* prev_value_vector,\n      int depth) const {\n    return false;\n  };\n  virtual std::unique_ptr<Value> Clone() const { return nullptr; }\n\n  // Transfer\n  virtual std::unique_ptr<pub::Value> ParseTransferValue(\n      std::shared_ptr<PubValueFactory> value_factory) const {\n    return nullptr;\n  }\n\n  // LynxObject\n  virtual std::unique_ptr<pub::Value> ParseLynxObject(\n      std::shared_ptr<PubValueFactory> value_factory) const {\n    return nullptr;\n  }\n\n  virtual std::unique_ptr<pub::Value> ParseTemplateData(\n      std::shared_ptr<PubValueFactory> value_factory) const {\n    return nullptr;\n  }\n\n protected:\n  explicit Value(ValueBackendType backend_type) : backend_type_(backend_type) {}\n  ValueBackendType backend_type_;\n};\n\n/*\n * PubValueFactory is the factory used to create a concrete implementation of\n * pub Value. Different pub Value data backends can provide different Factory\n * implementations.\n */\nclass PubValueFactory {\n public:\n  enum class FactoryType {\n    kDefault,\n    kPiper,\n    kLepus,\n    kJava,\n    kDarwin,\n    kNapiPrimJS,\n    kCustom\n  };\n  virtual std::unique_ptr<Value> CreateArray() = 0;\n  virtual std::unique_ptr<Value> CreateMap() = 0;\n  virtual std::unique_ptr<Value> CreateBool(bool value) = 0;\n  virtual std::unique_ptr<Value> CreateNumber(double value) = 0;\n  virtual std::unique_ptr<Value> CreateString(const std::string& value) = 0;\n  virtual std::unique_ptr<Value> CreateArrayBuffer(\n      std::unique_ptr<uint8_t[]> value, size_t length) = 0;\n  virtual FactoryType GetFactoryType() const { return FactoryType::kDefault; };\n  virtual ~PubValueFactory() {}\n};\n\nclass ScopedCircleChecker {\n public:\n  ScopedCircleChecker() = default;\n  ~ScopedCircleChecker() {\n    if (!scoped_value_vector_ || scoped_value_vector_->empty()) {\n      return;\n    }\n    scoped_value_vector_->pop_back();\n  }\n\n  static std::unique_ptr<std::vector<std::unique_ptr<pub::Value>>>\n  InitVectorIfNecessary(const pub::Value& value) {\n    if (value.backend_type() != pub::ValueBackendType::ValueBackendTypePiper) {\n      return nullptr;\n    }\n    auto prev_value_vector =\n        std::make_unique<std::vector<std::unique_ptr<pub::Value>>>();\n    prev_value_vector->push_back(value.Clone());\n    return prev_value_vector;\n  }\n\n  bool CheckCircleOrCacheValue(\n      std::vector<std::unique_ptr<pub::Value>>* prev_value_vector,\n      const pub::Value& value, int depth) {\n    if (prev_value_vector == nullptr ||\n        value.backend_type() != ValueBackendType::ValueBackendTypePiper) {\n      return false;\n    }\n    if (value.CheckCircle(prev_value_vector, depth)) {\n      return true;\n    }\n    prev_value_vector->push_back(value.Clone());\n    scoped_value_vector_ = prev_value_vector;\n    return false;\n  }\n\n private:\n  std::vector<std::unique_ptr<pub::Value>>* scoped_value_vector_ = nullptr;\n};\n}  // namespace pub\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_PUB_VALUE_H_\n"
  },
  {
    "path": "core/public/runtime_lifecycle_observer.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_RUNTIME_LIFECYCLE_OBSERVER_H_\n#define CORE_PUBLIC_RUNTIME_LIFECYCLE_OBSERVER_H_\n\n#include <memory>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/fml/task_runner.h\"\n#include \"core/base/lynx_export.h\"\n#include \"core/public/vsync_observer_interface.h\"\n\nnamespace lynx {\nnamespace runtime {\n\n// Runtime lifecycle observer used to listen the events of lynx runtime.\n// Triggered on runtime thread\nclass LYNX_EXPORT RuntimeLifecycleObserver {\n public:\n  RuntimeLifecycleObserver() = default;\n  virtual ~RuntimeLifecycleObserver() = default;\n\n  virtual void OnRuntimeCreate(std::shared_ptr<IVSyncObserver> observer) = 0;\n  virtual void OnRuntimeInit(int64_t runtime_id) = 0;\n  virtual void OnAppEnterForeground() = 0;\n  virtual void OnAppEnterBackground() = 0;\n  virtual void OnRuntimeAttach(void* env) = 0;\n  virtual void OnRuntimeDetach() = 0;\n};\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_RUNTIME_LIFECYCLE_OBSERVER_H_\n"
  },
  {
    "path": "core/public/text_layout_impl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_TEXT_LAYOUT_IMPL_H_\n#define CORE_PUBLIC_TEXT_LAYOUT_IMPL_H_\n\n#include \"core/public/layout_node_value.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass Element;\n\nclass TextLayoutImpl {\n public:\n  virtual ~TextLayoutImpl() = default;\n\n  virtual void DispatchLayoutBefore(Element* element) = 0;\n\n  virtual void Destroy(Element* element) {}\n\n  virtual LayoutResult Measure(Element* element, float width, int width_mode,\n                               float height, int height_mode) = 0;\n\n  virtual void Align(Element* element) = 0;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n#endif  // CORE_PUBLIC_TEXT_LAYOUT_IMPL_H_\n"
  },
  {
    "path": "core/public/text_utils.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_TEXT_UTILS_H_\n#define CORE_PUBLIC_TEXT_UTILS_H_\n\n#include <memory>\n#include <string>\n\n#include \"core/public/pub_value.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass TextUtils {\n public:\n  // For now, only support px and rpx, and do not support font scale, screen\n  // matrix.\n  static std::unique_ptr<pub::Value> GetTextInfo(const std::string& content,\n                                                 const pub::Value& info);\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_TEXT_UTILS_H_\n"
  },
  {
    "path": "core/public/timing_key.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_TIMING_KEY_H_\n#define CORE_PUBLIC_TIMING_KEY_H_\n\n#include <string>\n\nnamespace lynx {\nnamespace tasm {\n\n// TODO (kechenglong): move TimingKey to timing.h\nusing TimingKey = std::string;\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_TIMING_KEY_H_\n"
  },
  {
    "path": "core/public/ui_delegate.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_UI_DELEGATE_H_\n#define CORE_PUBLIC_UI_DELEGATE_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"base/include/fml/task_runner.h\"\n#include \"core/public/box_model.h\"\n#include \"core/public/jsb/native_module_factory.h\"\n#include \"core/public/layout_ctx_platform_impl.h\"\n#include \"core/public/list_engine_proxy.h\"\n#include \"core/public/lynx_engine_proxy.h\"\n#include \"core/public/lynx_layout_proxy.h\"\n#include \"core/public/lynx_resource_loader.h\"\n#include \"core/public/lynx_runtime_proxy.h\"\n#include \"core/public/painting_ctx_platform_impl.h\"\n#include \"core/public/perf_controller_proxy.h\"\n\nnamespace lynx {\nnamespace tasm {\nusing TakeSnapshotCompletedCallback =\n    std::function<void(const std::string&, float timestamp, float device_width,\n                       float device_height, float page_scale_factor)>;\nclass PageConfig;\n/*\n * UIDelegate is used for communication between LynxShell and UI rendering\n * module.\n *\n * It can take some initialization parameters from the UI rendering module and\n * pass some objects to the UI rendering module after initialization.\n */\nclass UIDelegate {\n public:\n  virtual ~UIDelegate() = default;\n  virtual std::unique_ptr<PaintingCtxPlatformImpl> CreatePaintingContext() = 0;\n  virtual std::unique_ptr<LayoutCtxPlatformImpl> CreateLayoutContext() = 0;\n  virtual std::unique_ptr<PropBundleCreator> CreatePropBundleCreator() = 0;\n  virtual std::unique_ptr<runtime::NativeModuleFactory>\n  GetCustomModuleFactory() = 0;\n\n  // Indicates whether to use logical pixels as the layout unit on current\n  // platform.\n  // If true, the layout unit is logical pixels, otherwise it is physical\n  // pixels.\n  virtual bool UsesLogicalPixels() const = 0;\n  // Get real device pixel ratio of the screen which LynxView current\n  // displaying\n  virtual double GetScreenScaleFactor() const { return 1.0; }\n  virtual void OnLynxCreate(\n      const std::shared_ptr<shell::ListEngineProxy>& list_engine_proxy,\n      const std::shared_ptr<shell::LynxEngineProxy>& engine_proxy,\n      const std::shared_ptr<shell::LynxRuntimeProxy>& runtime_proxy,\n      const std::shared_ptr<shell::LynxLayoutProxy>& layout_proxy,\n      const std::shared_ptr<shell::PerfControllerProxy>& perf_controller_proxy,\n      const std::shared_ptr<pub::LynxResourceLoader>& resource_loader,\n      const fml::RefPtr<fml::TaskRunner>& ui_task_runner,\n      const fml::RefPtr<fml::TaskRunner>& layout_task_runner,\n      int32_t instance_id, bool is_embedded_mode = false) = 0;\n\n  virtual void OnUpdateScreenMetrics(float width, float height,\n                                     float device_pixel_ratio) {}\n\n  void SetInstanceId(int32_t id) { instance_id_ = id; }\n  int32_t GetInstanceId() const { return instance_id_; }\n  virtual void OnPageConfigDecoded(const std::shared_ptr<PageConfig>& config) {}\n  virtual void TakeSnapshot(\n      size_t max_width, size_t max_height, int quality,\n      float screen_scale_factor,\n      const lynx::fml::RefPtr<lynx::fml::TaskRunner>& screenshot_runner,\n      TakeSnapshotCompletedCallback callback) {}\n  virtual int GetNodeForLocation(int x, int y) { return -1; }\n  virtual std::vector<float> GetTransformValue(\n      int id, const std::vector<float>& pad_border_margin_layout) {\n    return {};\n  }\n  virtual std::string GetLynxUITree() { return std::string(); }\n  virtual std::string GetUINodeInfo(int id) { return std::string(); }\n  virtual int SetUIStyle(int id, const std::string& name,\n                         const std::string& content) {\n    return 0;\n  }\n\n private:\n  // Represents an unknown instance ID. Typically set proactively during event\n  // reporting, indicating that the current event does not need to distinguish\n  // the LynxShell runtime environment and does not need to associate common\n  // parameters.\n  int32_t instance_id_ = -1;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_UI_DELEGATE_H_\n"
  },
  {
    "path": "core/public/ui_operation_queue_interface.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_PUBLIC_UI_OPERATION_QUEUE_INTERFACE_H_\n#define CORE_PUBLIC_UI_OPERATION_QUEUE_INTERFACE_H_\n\n// TODO(chenyouhui): Remove this interface after migrating all Enqueue logic to\n// PaintingContext\n\n#include \"base/include/closure.h\"\n\nnamespace lynx {\nnamespace shell {\n\nclass UIOperationQueueInterface {\n public:\n  virtual ~UIOperationQueueInterface() = default;\n  virtual void Enqueue(base::closure operation) = 0;\n  virtual void Flush() = 0;\n};\n\n}  // namespace shell\n}  // namespace lynx\n#endif  // CORE_PUBLIC_UI_OPERATION_QUEUE_INTERFACE_H_\n"
  },
  {
    "path": "core/public/vsync_monitor_platform_impl.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_VSYNC_MONITOR_PLATFORM_IMPL_H_\n#define CORE_PUBLIC_VSYNC_MONITOR_PLATFORM_IMPL_H_\n\n#include \"base/include/closure.h\"\n\nnamespace lynx {\nnamespace base {\n\nclass VSyncMonitorPlatformImpl {\n public:\n  using Callback = base::MoveOnlyClosure<void, int64_t, int64_t>;\n  virtual ~VSyncMonitorPlatformImpl() {}\n\n  virtual void RequestVSync(Callback callback) = 0;\n};\n\n}  // namespace base\n}  // namespace lynx\n#endif  // CORE_PUBLIC_VSYNC_MONITOR_PLATFORM_IMPL_H_\n"
  },
  {
    "path": "core/public/vsync_observer_interface.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_PUBLIC_VSYNC_OBSERVER_INTERFACE_H_\n#define CORE_PUBLIC_VSYNC_OBSERVER_INTERFACE_H_\n\n#include <stdio.h>\n\n#include <functional>\n\n#include \"base/include/closure.h\"\n#include \"core/base/lynx_export.h\"\n\nnamespace lynx {\nnamespace runtime {\n\n// C++ VsyncObserver interface, exported from lynx.so.\nclass LYNX_EXPORT IVSyncObserver {\n public:\n  IVSyncObserver() = default;\n  virtual ~IVSyncObserver() = default;\n\n  virtual void RequestAnimationFrame(\n      uintptr_t id, base::MoveOnlyClosure<void, int64_t, int64_t> callback) = 0;\n\n  virtual void RequestBeforeAnimationFrame(\n      uintptr_t id, base::MoveOnlyClosure<void, int64_t, int64_t> callback) = 0;\n\n  virtual void RegisterAfterAnimationFrameListener(\n      base::MoveOnlyClosure<void, int64_t, int64_t> callback) = 0;\n};\n\n}  // namespace runtime\n}  // namespace lynx\n\n#endif  // CORE_PUBLIC_VSYNC_OBSERVER_INTERFACE_H_\n"
  },
  {
    "path": "core/renderer/BUILD.gn",
    "content": "# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../testing/test.gni\")\nimport(\"../Lynx.gni\")\nimport(\"build.gni\")\n\ngroup(\"tasm_group\") {\n  deps = [\n    \":tasm\",\n    \"../services/event_report:event_report\",\n    \"../services/long_task_timing:long_task_timing\",\n    \"../services/performance:performance\",\n    \"../services/ssr\",\n    \"../shell:shell\",\n    \"dom:dom\",\n    \"dom/selector:element_selector\",\n  ]\n}\n\nlepus_compile = is_android && build_lepus_compile\n\nlynx_core_source_set(\"tasm\") {\n  include_dirs = [ \"../../third_party\" ]\n\n  # TODO(zhengsenyao): break tasm to into three targets: tasm, tasm_config & vdom_radon\n  allow_circular_includes_from = [ \"dom:dom_headers\" ]\n\n  sources = lynx_tasm_shared_sources_path\n  if (!is_oliver_ssr) {\n    sources += lynx_recorder_sources_path\n  }\n\n  deps = [ \"dom:dom_headers\" ]\n  public_deps = [\n    \"../animation\",\n    \"../animation/utils:animation_utils\",\n    \"../base\",\n    \"../runtime/lepus:lepus\",\n    \"../runtime/lepus/bindings:bindings\",\n    \"../services/event_report\",\n    \"../services/long_task_timing\",\n    \"../services/performance\",\n    \"../template_bundle:template_bundle\",\n    \"css\",\n    \"data\",\n    \"dom/vdom/radon\",\n    \"events\",\n    \"layout_scheduler\",\n    \"pipeline\",\n    \"signal:signal\",\n    \"starlight\",\n    \"trace:renderer_trace\",\n    \"utils:renderer_utils\",\n  ]\n\n  if (!is_oliver_ssr) {\n    public_deps += [\n      \"../animation/basic_animation:basic_animation\",\n      \"../animation/lynx_basic_animator:lynx_basic_animator\",\n      \"../resource\",\n      \"../runtime\",\n      \"../shared_data\",\n      \"../shell\",\n    ]\n  }\n\n  if (enable_unittests) {\n    sources += [\n      \"tasm/testing/event_tracker_mock.cc\",\n      \"tasm/testing/event_tracker_mock.h\",\n    ]\n    configs = [\n      \"//build/config/compiler:cxx_version_17\",\n      \"../runtime/common/napi:napi_config\",\n    ]\n    deps += [\n      \"../../third_party/quickjs\",\n      \"../../third_party/rapidjson:rapidjson\",\n      \"../base\",\n      \"../runtime/common/napi:napi_binding_quickjs\",\n      \"../runtime/lepusng/napi/test:test_module\",\n    ]\n    deps += [\n      \"../../third_party/napi:env\",\n      \"../../third_party/napi:quickjs\",\n      \"../../third_party/quickjs:quickjs_libc\",\n    ]\n    if (is_mac) {\n      # On MacOS, NapiBindingCore source files create JSC runtime by default.\n      # And consequently all unittests should link with JSC framework and\n      # depends on NapiBindingJSC.\n      deps += [ \"../runtime/common/napi:napi_binding_jsc\" ]\n    }\n  }\n  if (is_mac && !enable_unittests && !desktop_enable_embedder_layer) {\n    deps += [\n      \"../../platform/darwin/ios/lynx_service_api:lynx_service_api_sources\",\n    ]\n  }\n\n  if (lepus_compile) {\n    public_deps += [ \"../template_bundle/template_codec:lepus_compile\" ]\n  }\n}\n\nunittest_exec(\"page_config_unittests_exec\") {\n  testonly = true\n\n  sources = [ \"page_config_unittests.cc\" ]\n\n  deps = [\n    \":tasm\",\n    \"dom:dom\",\n  ]\n}\n"
  },
  {
    "path": "core/renderer/build.gni",
    "content": "# Copyright 2023 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"//${lynx_dir}/build_overrides/renderer_utils_files.gni\")\nimport(\"../../config.gni\")\n\n# TODO(chenyouhui): Remove this\nimport(\"../../oliver/oliver.gni\")\nlynx_tasm_shared_sources = [\n                             \"../services/replay/replay_controller.cc\",\n                             \"../services/replay/replay_controller.h\",\n                             \"element_manager_delegate_impl.cc\",\n                             \"element_manager_delegate_impl.h\",\n                             \"js_bundle_holder_impl.cc\",\n                             \"js_bundle_holder_impl.h\",\n                             \"lynx_env_config.cc\",\n                             \"lynx_env_config.h\",\n                             \"lynx_global_pool.cc\",\n                             \"lynx_global_pool.h\",\n                             \"page_proxy.cc\",\n                             \"page_proxy.h\",\n                             \"tasm/config.cc\",\n                             \"tasm/config.h\",\n                             \"tasm/i18n/i18n.cc\",\n                             \"tasm/i18n/i18n.h\",\n                             \"tasm_runtime_bundle.h\",\n                             \"template_assembler.cc\",\n                             \"template_assembler.h\",\n                             \"template_entry.cc\",\n                             \"template_entry.h\",\n                             \"template_entry_holder.cc\",\n                             \"template_entry_holder.h\",\n                             \"template_themed.h\",\n                           ] + lynx_tasm_extend_shared_sources\n\nif (is_ios) {\n  lynx_tasm_shared_sources += [ \"tasm/sysinfo/sysinfo_ios.cc\" ]\n} else if (is_harmony) {\n  lynx_tasm_shared_sources += [ \"tasm/sysinfo/sysinfo_harmony.cc\" ]\n} else if (is_android) {\n  lynx_tasm_shared_sources += [\n    \"tasm/i18n/i18n_binder_android.cc\",\n    \"tasm/i18n/i18n_binder_android.h\",\n    \"tasm/sysinfo/sysinfo_android.cc\",\n  ]\n}\n\nif (is_apple && !enable_unittests && !is_oliver_ssr && !is_oliver_node_lynx &&\n    (!is_mac || !desktop_enable_embedder_layer)) {\n  lynx_tasm_shared_sources += [\n    \"../base/darwin/config_darwin.h\",\n    \"../base/darwin/config_darwin.mm\",\n    \"tasm/i18n/i18n_binder_darwin.h\",\n    \"tasm/i18n/i18n_binder_darwin.mm\",\n  ]\n}\n\nif (is_oliver_ssr) {\n  lynx_tasm_shared_sources += [ \"tasm/sysinfo/sysinfo_default.cc\" ]\n}\nif (enable_unittests) {\n  lynx_tasm_shared_sources += [ \"tasm/sysinfo/sysinfo_default.cc\" ]\n}\n\nlynx_tasm_shared_sources_path =\n    get_path_info(lynx_tasm_shared_sources, \"abspath\")\n\nlynx_recorder_sources = [\n  \"../services/recorder/recorder_controller.cc\",\n  \"../services/recorder/recorder_controller.h\",\n]\n\nlynx_recorder_sources_path = get_path_info(lynx_recorder_sources, \"abspath\")\n\nlynx_tasm_config_sources = [\n  \"tasm/config.cc\",\n  \"tasm/config.h\",\n  \"tasm/sysinfo/sysinfo_headless.cc\",\n]\n\nlynx_tasm_config_sources_path =\n    get_path_info(lynx_tasm_config_sources, \"abspath\")\n"
  },
  {
    "path": "core/renderer/css/.gitignore",
    "content": "auto_gen_css_decoder.h\nauto_gen_css_decoder.cc\ncss_property_id.h\ncss_property_auto_gen_unittest.cc\nparser/animation_property_handler.h\nparser/bool_handler.h\nparser/border_style_handler.h\nparser/border_width_handler.h\nparser/color_handler.h\nparser/length_handler.h\nparser/number_handler.h\nparser/string_handler.h\nparser/time_handler.h\nparser/timing_function_handler.h\nlayout_property.h\nlayout_property.h.hashes"
  },
  {
    "path": "core/renderer/css/BUILD.gn",
    "content": "# Copyright 2020 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../testing/test.gni\")\nimport(\"../../Lynx.gni\")\nimport(\"build.gni\")\n\nlynx_core_source_set(\"css\") {\n  check_includes = false\n\n  sources = lynx_css_core_sources_path + lynx_css_runtime_sources_path\n\n  exec_script(\"../../../tools/css_generator/css_parser_generator.py\")\n\n  exec_script(\"../../../tools/css_generator/generate_layout_property.py\",\n              [\n                rebase_path(\"../../../tools/css_generator/css_defines\"),\n                rebase_path(\"./layout_property.h\"),\n                rebase_path(\n                    \"../../../tools/css_generator/css_defines_sha256.txt\"),\n              ],\n              \"list lines\")\n\n  public_deps = [\n    \"../../../third_party/rapidjson\",\n    \"../../base\",\n    \"../../runtime/lepus:lepus\",\n    \"../../style\",\n    \"../trace:renderer_trace\",\n  ]\n\n  deps = [\n    \"../../renderer/dom:dom_headers\",\n    \"../../renderer/starlight:starlight\",\n  ]\n\n  if (enable_unittests) {\n    public_deps += [\n      \":css_parser\",\n      \"../../../third_party/double-conversion:double_conversion\",\n    ]\n  }\n}\n\nlynx_core_source_set(\"css_dom\") {\n  sources = lynx_css_dom_sources_path\n  deps = [ \"../../../base/src:base_log_headers\" ]\n}\n\nlynx_core_source_set(\"css_decoder\") {\n  sources = lynx_css_decoder_sources_path\n  deps = [ \"../../../base/src:base_log_headers\" ]\n}\n\nlynx_core_source_set(\"css_parser\") {\n  sources = lynx_css_parser_sources_path\n}\n\nunittest_set(\"css_test_testset\") {\n  check_includes = false\n  testonly = true\n  public_deps = [ \":css\" ]\n\n  deps = [\n    \"../:tasm\",\n    \"../../../third_party/rapidjson:rapidjson\",\n    \"../../renderer:tasm_group\",\n    \"../../runtime/lepus/bindings:bindings\",\n    \"../../shell/testing:mock_tasm_delegate_testset\",\n    \"../dom:renderer_dom\",\n  ]\n\n  sources = [\n    \"../../style/transform/quaternion_unittest.cc\",\n    \"../dom/style_resolver_unittest.cc\",\n    \"computed_css_style_css_text_helper_unittest.cc\",\n    \"css_color_unittest.cc\",\n    \"css_font_face_token_unittest.cc\",\n    \"css_fragment_unittest.cc\",\n    \"css_keyframes_token_unittest.cc\",\n    \"css_keywords_unittest.cc\",\n    \"css_parser_token_unittest.cc\",\n    \"css_property_auto_gen_unittest.cc\",\n    \"css_property_bitset_unittest.cc\",\n    \"css_property_unittest.cc\",\n    \"css_style_sheet_manager_unittest.cc\",\n    \"css_style_utils_unittest.cc\",\n    \"css_utils_unittest.cc\",\n    \"css_value_unittest.cc\",\n    \"css_variable_handler_unittest.cc\",\n    \"shared_css_fragment_unittest.cc\",\n    \"transforms/transform_operation_unittest.cc\",\n    \"transforms/transform_operations_unittest.cc\",\n    \"unit_handler_unittest.cc\",\n  ]\n}\n\nunittest_set(\"css_ng_testset\") {\n  public_deps = [ \":css\" ]\n\n  deps = [\n    \"../:tasm\",\n    \"../../runtime/lepus/bindings:bindings\",\n    \"../../shell/testing:mock_tasm_delegate_testset\",\n    \"../dom:renderer_dom\",\n  ]\n\n  sources = [\n    \"ng/invalidation/invalidation_set_test.cc\",\n    \"ng/invalidation/rule_invalidation_set_test.cc\",\n    \"ng/matcher/selector_matcher_test.cc\",\n    \"ng/parser/css_parser_token_stream_test.cc\",\n    \"ng/parser/css_tokenizer_test.cc\",\n    \"ng/selector/css_selector_parser_test.cc\",\n    \"ng/selector/lynx_css_selector_test.cc\",\n    \"ng/style/rule_set_unittest.cc\",\n  ]\n}\n\nunittest_exec(\"css_test_exec\") {\n  testonly = true\n  sources = []\n  deps = [\n    \":css_ng_testset\",\n    \":css_test_testset\",\n    \"../dom:dom\",\n  ]\n}\n\ngroup(\"css_tests\") {\n  testonly = true\n  deps = [\n    \":css_test_exec\",\n    \"parser:css_parser_test_exec\",\n  ]\n  public_deps = [\n    \":css_ng_testset\",\n    \":css_test_testset\",\n    \"parser:css_parser_testset\",\n  ]\n}\n"
  },
  {
    "path": "core/renderer/css/android/css_color_utils.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <string>\n\n#include \"core/base/android/jni_helper.h\"\n#include \"core/renderer/css/css_color.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/ColorUtils_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/ColorUtils_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForColorUtils(JNIEnv* env) { return RegisterNativesImpl(env); }\n}  // namespace jni\n}  // namespace lynx\n\njint Parse(JNIEnv* env, jclass jcaller, jstring color) {\n  std::string str_color =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, color);\n  lynx::tasm::CSSColor hex_color;\n  lynx::tasm::CSSColor::Parse(str_color, hex_color);\n  return hex_color.Cast();\n}\n\njboolean Validate(JNIEnv* env, jclass jcaller, jstring color) {\n  std::string str_color =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, color);\n  lynx::tasm::CSSColor hex_color;\n  return lynx::tasm::CSSColor::Parse(str_color, hex_color);\n}\n"
  },
  {
    "path": "core/renderer/css/android/css_gradient_utils.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/platform/android/jni_convert_helper.h\"\n#include \"core/base/android/jni_helper.h\"\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/css/css_utils.h\"\n#include \"core/renderer/css/measure_context.h\"\n#include \"core/renderer/tasm/react/android/mapbuffer/map_buffer_builder.h\"\n#include \"core/renderer/tasm/react/android/mapbuffer/readable_map_buffer.h\"\n#include \"core/renderer/ui_wrapper/common/android/prop_bundle_android.h\"\n#include \"core/value_wrapper/value_impl_lepus.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/GradientUtils_jni.h\"\n#include \"platform/android/lynx_android/src/main/jni/gen/GradientUtils_register_jni.h\"\n\nnamespace lynx {\nnamespace jni {\nbool RegisterJNIForGradientUtils(JNIEnv* env) {\n  return RegisterNativesImpl(env);\n}\n}  // namespace jni\n}  // namespace lynx\n\njfloatArray GetRadialRadius(JNIEnv* env, jclass jcaller, jint shape,\n                            jint shapeSize, jfloat cx, jfloat cy, jfloat sx,\n                            jfloat sy) {\n  auto radius = lynx::tasm::GetRadialGradientRadius(\n      static_cast<lynx::starlight::RadialGradientShapeType>(shape),\n      static_cast<lynx::starlight::RadialGradientSizeType>(shapeSize), cx, cy,\n      sx, sy);\n  auto arr = env->NewFloatArray(2);\n  jfloat ret[] = {radius.first, radius.second};\n  env->SetFloatArrayRegion(arr, 0, 2, ret);\n  return arr;\n}\n\njobject GetGradientArray(JNIEnv* env, jclass jcaller, jstring gradientDef,\n                         jfloat screen_width, jfloat layouts_unit_per_px,\n                         jfloat physical_pixels_per_layout_unit,\n                         jfloat root_node_font_size, jfloat cur_node_font_size,\n                         jfloat font_scale, jfloat viewport_width,\n                         jfloat viewport_height) {\n  std::string gradient_def =\n      lynx::base::android::JNIConvertHelper::ConvertToString(env, gradientDef);\n  auto gradient_data =\n      lynx::starlight::CSSStyleUtils::GetGradientArrayFromString(\n          gradient_def.c_str(), gradient_def.length(),\n          lynx::tasm::CssMeasureContext(\n              screen_width, layouts_unit_per_px,\n              physical_pixels_per_layout_unit, root_node_font_size,\n              cur_node_font_size, lynx::starlight::LayoutUnit(viewport_width),\n              lynx::starlight::LayoutUnit(viewport_height)),\n          lynx::tasm::CSSParserConfigs());\n\n  if (!gradient_data.IsArray()) {\n    return nullptr;\n  }\n  lynx::base::android::MapBufferBuilder builder{};\n  lynx::pub::ValueImplLepus value{std::move(gradient_data)};\n  lynx::tasm::PropBundleAndroid::AssembleMapBuffer(builder, 0, value);\n  auto map_buffer =\n      std::make_unique<lynx::base::android::MapBuffer>(builder.build());\n  auto map_buffer_jobject =\n      lynx::base::android::JReadableMapBuffer::CreateReadableMapBuffer(\n          *map_buffer);\n  return env->NewLocalRef(map_buffer_jobject.Get());  // NOLINT\n}\n"
  },
  {
    "path": "core/renderer/css/build.gni",
    "content": "# Copyright 2020 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../config.gni\")\n\nlynx_css_core_sources = [\n  \"css_color.cc\",\n  \"css_color.h\",\n  \"css_content_data.cc\",\n  \"css_content_data.h\",\n  \"css_debug_msg.h\",\n  \"css_font_face_token.cc\",\n  \"css_font_face_token.h\",\n  \"css_fragment.cc\",\n  \"css_fragment.h\",\n  \"css_fragment_decorator.cc\",\n  \"css_fragment_decorator.h\",\n  \"css_keyframes_token.cc\",\n  \"css_keyframes_token.h\",\n  \"css_keywords.cc\",\n  \"css_keywords.h\",\n  \"css_parser_token.cc\",\n  \"css_parser_token.h\",\n  \"css_property.cc\",\n  \"css_property.h\",\n  \"css_property_bitset.h\",\n  \"css_property_id.h\",\n  \"css_selector_constants.cc\",\n  \"css_selector_constants.h\",\n  \"css_sheet.cc\",\n  \"css_sheet.h\",\n  \"css_style_sheet_manager.cc\",\n  \"css_style_sheet_manager.h\",\n  \"css_utils.cc\",\n  \"css_utils.h\",\n  \"css_value.cc\",\n  \"css_value.h\",\n  \"css_variable_handler.cc\",\n  \"css_variable_handler.h\",\n  \"layout_property.cc\",\n  \"layout_property.h\",\n  \"ng/css_ng_utils.cc\",\n  \"ng/css_ng_utils.h\",\n  \"ng/invalidation/invalidation_set.cc\",\n  \"ng/invalidation/invalidation_set.h\",\n  \"ng/invalidation/invalidation_set_feature.h\",\n  \"ng/invalidation/rule_invalidation_set.cc\",\n  \"ng/invalidation/rule_invalidation_set.h\",\n  \"ng/matcher/selector_matcher.cc\",\n  \"ng/matcher/selector_matcher.h\",\n  \"ng/selector/lynx_css_selector.cc\",\n  \"ng/selector/lynx_css_selector.h\",\n  \"ng/selector/lynx_css_selector_extra_data.cc\",\n  \"ng/selector/lynx_css_selector_extra_data.h\",\n  \"ng/selector/lynx_css_selector_list.cc\",\n  \"ng/selector/lynx_css_selector_list.h\",\n  \"ng/style/rule_data.h\",\n  \"ng/style/rule_set.cc\",\n  \"ng/style/rule_set.h\",\n  \"ng/style/style_rule.h\",\n  \"parser/animation_direction_handler.cc\",\n  \"parser/animation_direction_handler.h\",\n  \"parser/animation_fill_mode_handler.cc\",\n  \"parser/animation_fill_mode_handler.h\",\n  \"parser/animation_iteration_count_handler.cc\",\n  \"parser/animation_iteration_count_handler.h\",\n  \"parser/animation_name_handler.cc\",\n  \"parser/animation_name_handler.h\",\n  \"parser/animation_play_state_handler.cc\",\n  \"parser/animation_play_state_handler.h\",\n  \"parser/animation_property_handler.cc\",\n  \"parser/animation_property_handler.h\",\n  \"parser/animation_shorthand_handler.cc\",\n  \"parser/animation_shorthand_handler.h\",\n  \"parser/aspect_ratio_handler.cc\",\n  \"parser/aspect_ratio_handler.h\",\n  \"parser/auto_font_size_handler.cc\",\n  \"parser/auto_font_size_handler.h\",\n  \"parser/auto_font_size_preset_sizes_handler.cc\",\n  \"parser/auto_font_size_preset_sizes_handler.h\",\n  \"parser/background_box_handler.cc\",\n  \"parser/background_box_handler.h\",\n  \"parser/background_clip_handler.cc\",\n  \"parser/background_clip_handler.h\",\n  \"parser/background_image_handler.cc\",\n  \"parser/background_image_handler.h\",\n  \"parser/background_position_handler.cc\",\n  \"parser/background_position_handler.h\",\n  \"parser/background_repeat_handler.cc\",\n  \"parser/background_repeat_handler.h\",\n  \"parser/background_shorthand_handler.cc\",\n  \"parser/background_shorthand_handler.h\",\n  \"parser/background_size_handler.cc\",\n  \"parser/background_size_handler.h\",\n  \"parser/bool_handler.cc\",\n  \"parser/bool_handler.h\",\n  \"parser/border_handler.cc\",\n  \"parser/border_handler.h\",\n  \"parser/border_radius_handler.cc\",\n  \"parser/border_radius_handler.h\",\n  \"parser/border_style_handler.cc\",\n  \"parser/border_style_handler.h\",\n  \"parser/border_width_handler.cc\",\n  \"parser/border_width_handler.h\",\n  \"parser/clip_path_handler.cc\",\n  \"parser/clip_path_handler.h\",\n  \"parser/color_handler.cc\",\n  \"parser/color_handler.h\",\n  \"parser/css_parser_configs.h\",\n  \"parser/css_string_parser.cc\",\n  \"parser/css_string_parser.h\",\n  \"parser/css_string_scanner.cc\",\n  \"parser/css_string_scanner.h\",\n  \"parser/cursor_handler.cc\",\n  \"parser/cursor_handler.h\",\n  \"parser/enum_handler.cc\",\n  \"parser/enum_handler.h\",\n  \"parser/filter_handler.cc\",\n  \"parser/filter_handler.h\",\n  \"parser/flex_flow_handler.cc\",\n  \"parser/flex_flow_handler.h\",\n  \"parser/flex_handler.cc\",\n  \"parser/flex_handler.h\",\n  \"parser/font_feature_settings_handler.cc\",\n  \"parser/font_feature_settings_handler.h\",\n  \"parser/font_length_handler.cc\",\n  \"parser/font_length_handler.h\",\n  \"parser/font_variation_settings_handler.cc\",\n  \"parser/font_variation_settings_handler.h\",\n  \"parser/four_sides_shorthand_handler.cc\",\n  \"parser/four_sides_shorthand_handler.h\",\n  \"parser/gap_handler.cc\",\n  \"parser/gap_handler.h\",\n  \"parser/grid_position_handler.cc\",\n  \"parser/grid_position_handler.h\",\n  \"parser/grid_template_handler.cc\",\n  \"parser/grid_template_handler.h\",\n  \"parser/handler_defines.h\",\n  \"parser/length_handler.cc\",\n  \"parser/length_handler.h\",\n  \"parser/list_gap_handler.cc\",\n  \"parser/list_gap_handler.h\",\n  \"parser/mask_shorthand_handler.cc\",\n  \"parser/mask_shorthand_handler.h\",\n  \"parser/number_handler.cc\",\n  \"parser/number_handler.h\",\n  \"parser/offset_rotate_handler.cc\",\n  \"parser/offset_rotate_handler.h\",\n  \"parser/relative_align_handler.cc\",\n  \"parser/relative_align_handler.h\",\n  \"parser/shadow_handler.cc\",\n  \"parser/shadow_handler.h\",\n  \"parser/string_handler.cc\",\n  \"parser/string_handler.h\",\n  \"parser/text_decoration_handler.cc\",\n  \"parser/text_decoration_handler.h\",\n  \"parser/text_stroke_handler.cc\",\n  \"parser/text_stroke_handler.h\",\n  \"parser/time_handler.cc\",\n  \"parser/time_handler.h\",\n  \"parser/timing_function_handler.cc\",\n  \"parser/timing_function_handler.h\",\n  \"parser/transform_handler.cc\",\n  \"parser/transform_handler.h\",\n  \"parser/transform_origin_handler.cc\",\n  \"parser/transform_origin_handler.h\",\n  \"parser/transition_shorthand_handler.cc\",\n  \"parser/transition_shorthand_handler.h\",\n  \"parser/vertical_align_handler.cc\",\n  \"parser/vertical_align_handler.h\",\n  \"shared_css_fragment.cc\",\n  \"shared_css_fragment.h\",\n  \"style_node.cc\",\n  \"style_node.h\",\n  \"unit_handler.cc\",\n  \"unit_handler.h\",\n]\n\nif (!enable_unittests) {\n  lynx_css_core_sources += [ \"ng/selector/lynx_css_selector_empty.cc\" ]\n}\n\nif (is_android) {\n  lynx_css_core_sources += [\n    \"android/css_color_utils.cc\",\n    \"android/css_gradient_utils.cc\",\n  ]\n}\n\nlynx_css_core_sources_path = get_path_info(lynx_css_core_sources, \"abspath\")\n\nlynx_css_runtime_sources = [\n  \"computed_css_style.cc\",\n  \"computed_css_style.h\",\n  \"computed_css_style_css_text_helper.cc\",\n  \"computed_css_style_css_text_helper.h\",\n  \"css_style_utils.cc\",\n  \"css_style_utils.h\",\n  \"measure_context.cc\",\n  \"measure_context.h\",\n  \"text_attributes.cc\",\n  \"text_attributes.h\",\n  \"transforms/transform_operation.cc\",\n  \"transforms/transform_operation.h\",\n  \"transforms/transform_operations.cc\",\n  \"transforms/transform_operations.h\",\n]\nlynx_css_runtime_sources_path =\n    get_path_info(lynx_css_runtime_sources, \"abspath\")\n\nlynx_css_dom_sources = [\n  \"dynamic_css_configs.h\",\n  \"dynamic_css_styles_manager.cc\",\n  \"dynamic_css_styles_manager.h\",\n  \"dynamic_direction_styles_manager.cc\",\n  \"dynamic_direction_styles_manager.h\",\n  \"select_element_token.cc\",\n  \"select_element_token.h\",\n]\nlynx_css_dom_sources_path = get_path_info(lynx_css_dom_sources, \"abspath\")\n\nlynx_css_parser_sources = [\n  \"ng/parser/css_parser_idioms.cc\",\n  \"ng/parser/css_parser_idioms.h\",\n  \"ng/parser/css_parser_token.cc\",\n  \"ng/parser/css_parser_token.h\",\n  \"ng/parser/css_parser_token_range.cc\",\n  \"ng/parser/css_parser_token_stream.cc\",\n  \"ng/parser/css_tokenizer.cc\",\n  \"ng/parser/css_tokenizer.h\",\n  \"ng/parser/css_tokenizer_input_stream.cc\",\n  \"ng/parser/css_tokenizer_input_stream.h\",\n  \"ng/parser/string_to_number.cc\",\n  \"ng/selector/css_parser_context.h\",\n  \"ng/selector/css_selector_parser.cc\",\n  \"ng/selector/css_selector_parser.h\",\n  \"ng/selector/lynx_css_selector_impl.cc\",\n]\nlynx_css_parser_sources_path = get_path_info(lynx_css_parser_sources, \"abspath\")\n\nlynx_css_decoder_sources = [\n  \"auto_gen_css_decoder.cc\",\n  \"auto_gen_css_decoder.h\",\n  \"css_decoder.cc\",\n  \"css_decoder.h\",\n]\nlynx_css_decoder_sources_path =\n    get_path_info(lynx_css_decoder_sources, \"abspath\")\n\nlynx_css_extra_sources = [\n  \"css_fragment_decorator.cc\",\n  \"css_fragment_decorator.h\",\n  \"css_variable_handler.cc\",\n  \"css_variable_handler.h\",\n  \"ng/selector/lynx_css_selector_empty.cc\",\n]\n\nlynx_css_extra_sources_path = get_path_info(lynx_css_extra_sources, \"abspath\")\n"
  },
  {
    "path": "core/renderer/css/computed_css_style.cc",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/computed_css_style.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"base/include/algorithm.h\"\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"base/include/vector.h\"\n#include \"core/build/gen/lynx_sub_error_code.h\"\n#include \"core/renderer/css/css_debug_msg.h\"\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/style/color.h\"\n\nnamespace lynx {\n\nusing base::FloatsEqual;\n\nnamespace starlight {\n\nusing CSSValuePattern = tasm::CSSValuePattern;\n\nconst base::InlineOrderedFlatSet<tasm::CSSPropertyID, 3>&\nComputedCSSStyle::GetPlatformInheritableProperty() {\n  static const base::NoDestructor<\n      base::InlineOrderedFlatSet<tasm::CSSPropertyID, 3>>\n      kPlatformInheritableProperty({\n          tasm::kPropertyIDLineHeight,\n          tasm::kPropertyIDLetterSpacing,\n          tasm::kPropertyIDLineSpacing,\n      });\n  return *kPlatformInheritableProperty;\n}\n\nconst ComputedCSSStyle::StyleFunc* ComputedCSSStyle::FuncMap() {\n  static const StyleFunc* func_map_ = []() {\n    static StyleFunc style_funcs[tasm::CSSPropertyID::kPropertyEnd] = {nullptr};\n#define DECLARE_PROPERTY_SETTER(name, c, value) \\\n  style_funcs[tasm::kPropertyID##name] = &ComputedCSSStyle::Set##name;\n    FOREACH_ALL_PROPERTY(DECLARE_PROPERTY_SETTER)\n#undef DECLARE_PROPERTY_SETTER\n    return style_funcs;\n  }();\n  return func_map_;\n}\n\nconst ComputedCSSStyle::StyleGetterFunc* ComputedCSSStyle::GetterFuncMap() {\n  static const StyleGetterFunc* getter_func_map_ = []() {\n    static StyleGetterFunc map[tasm::CSSPropertyID::kPropertyEnd] = {nullptr};\n#define DECLARE_PLATFORM_PROPERTY_GETTER(name) \\\n  map[tasm::kPropertyID##name] = &ComputedCSSStyle::name##ToLepus;\n    FOREACH_PLATFORM_PROPERTY(DECLARE_PLATFORM_PROPERTY_GETTER)\n#undef DECLARE_PLATFORM_PROPERTY_GETTER\n#undef FOREACH_PLATFORM_PROPERTY\n    return map;\n  }();\n  return getter_func_map_;\n}\n\nconst ComputedCSSStyle::StyleInheritFuncMap&\nComputedCSSStyle::InheritFuncMap() {\n  static base::NoDestructor<ComputedCSSStyle::StyleInheritFuncMap>\n      inherit_func_map_{{\n#define DECLARE_PLATFORM_PROPERTY_INHERIT_FUNC(name) \\\n  {tasm::kPropertyID##name, &ComputedCSSStyle::Inherit##name},\n          FOREACH_PLATFORM_COMPLEX_INHERITABLE_PROPERTY(\n              DECLARE_PLATFORM_PROPERTY_INHERIT_FUNC)\n#undef DECLARE_PLATFORM_PROPERTY_INHERIT_FUNC\n#undef FOREACH_PLATFORM_COMPLEX_INHERITABLE_PROPERTY\n      }};\n  return *inherit_func_map_;\n}\n\nnamespace {\nbool CalculateFromBorderWidthStringToFloat(\n    const tasm::CSSValue& value, float& result,\n    const tasm::CssMeasureContext& context, const bool reset,\n    bool css_align_with_legacy_w3c, const tasm::CSSParserConfigs& configs) {\n  if (reset) {\n    result = DEFAULT_CSS_VALUE(css_align_with_legacy_w3c, BORDER);\n    return true;\n  }\n\n  auto parse_result = CSSStyleUtils::ToLength(value, context, configs);\n  if (!parse_result.second ||\n      (!parse_result.first.IsUnit() && !parse_result.first.IsCalc()) ||\n      (parse_result.first.IsCalc() &&\n       parse_result.first.NumericLength().ContainsPercentage())) {\n    return false;\n  }\n  result = CSSStyleUtils::GetBorderWidthFromLengthToFloat(parse_result.first,\n                                                          context);\n  return true;\n}\n\nbool CalculateCSSValueToFloat(const tasm::CSSValue& value, float& result,\n                              const tasm::CssMeasureContext& context,\n                              const tasm::CSSParserConfigs& configs,\n                              bool is_font_relevant = false) {\n  auto parse_result =\n      CSSStyleUtils::ToLength(value, context, configs, is_font_relevant);\n  if (!parse_result.second) {\n    return false;\n  }\n\n  if (parse_result.first.IsCalc()) {\n    result = CSSStyleUtils::RoundValueToPixelGrid(\n        parse_result.first.NumericLength().GetFixedPart(),\n        context.physical_pixels_per_layout_unit_);\n  } else {\n    // FIXME(zhixuan): The function has the legact bug to return percentage\n    // value as fixed value for non calc length. Will fix later.\n    result = CSSStyleUtils::RoundValueToPixelGrid(\n        parse_result.first.GetRawValue(),\n        context.physical_pixels_per_layout_unit_);\n  }\n  return true;\n}\n\nlepus::Value ShadowDataToLepus(const base::Vector<ShadowData>& shadows) {\n  auto group = lepus::CArray::Create();\n  for (const auto& shadow_data : shadows) {\n    auto item = lepus::CArray::Create();\n    item->emplace_back(shadow_data.h_offset);\n    item->emplace_back(shadow_data.v_offset);\n    item->emplace_back(shadow_data.blur);\n    item->emplace_back(shadow_data.spread);\n    item->emplace_back(static_cast<int>(shadow_data.option));\n    item->emplace_back(shadow_data.color);\n    group->emplace_back(std::move(item));\n  }\n  return lepus::Value(std::move(group));\n}\n\nbool SetLayoutAnimationTimingFunctionInternal(\n    const tasm::CSSValue& value, const bool reset,\n    TimingFunctionData& timing_function,\n    const tasm::CSSParserConfigs& configs) {\n  const lepus::Value& lepus_val = value.GetValue();\n  // TimingFunction's input value must be a non-empty array , if not we will\n  // reset it.\n  bool reset_internal =\n      (reset || !lepus_val.IsArray() || lepus_val.Array()->size() <= 0);\n  const lepus::Value& param =\n      reset_internal ? lepus_val : lepus_val.Array()->get(0);\n  return CSSStyleUtils::ComputeTimingFunction(param, reset_internal,\n                                              timing_function, configs);\n}\n\nbool SetBorderWidthHelper(bool cssAlignWithLegacyW3C,\n                          const tasm::CssMeasureContext& context, float& width,\n                          const tasm::CSSValue& value,\n                          const tasm::CSSParserConfigs& configs,\n                          const bool reset) {\n  float old_value = width;\n  if (UNLIKELY(!CalculateFromBorderWidthStringToFloat(\n          value, width, context, reset, cssAlignWithLegacyW3C, configs))) {\n    return false;\n  }\n  return width != old_value;\n}\n\nbool SetBorderRadiusHelper(NLength& radiusX, NLength& radiusY,\n                           const lynx::tasm::CssMeasureContext& context,\n                           tasm::CSSPropertyID cssID,\n                           const tasm::CSSValue& value, const bool reset,\n                           const tasm::CSSParserConfigs& configs) {\n  if (reset) {\n    radiusX = radiusY = DefaultLayoutStyle::SL_DEFAULT_RADIUS();\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsArray(), configs.enable_css_strict_mode, tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(cssID).c_str(), tasm::ARRAY_TYPE)\n\n    auto arr = value.GetArray();\n    auto parse_result = CSSStyleUtils::ToLength(\n        tasm::CSSValue(arr->get(0),\n                       static_cast<CSSValuePattern>(arr->get(1).Number())),\n        context, configs);\n\n    CSS_HANDLER_FAIL_IF_NOT(parse_result.second, configs.enable_css_strict_mode,\n                            tasm::SET_PROPERTY_ERROR,\n                            tasm::CSSProperty::GetPropertyName(cssID).c_str())\n\n    radiusX = std::move(parse_result.first);\n\n    parse_result = CSSStyleUtils::ToLength(\n        tasm::CSSValue(arr->get(2),\n                       static_cast<CSSValuePattern>(arr->get(3).Number())),\n        context, configs);\n\n    CSS_HANDLER_FAIL_IF_NOT(parse_result.second, configs.enable_css_strict_mode,\n                            tasm::SET_PROPERTY_ERROR,\n                            tasm::CSSProperty::GetPropertyName(cssID).c_str())\n\n    radiusY = std::move(parse_result.first);\n  }\n  return true;\n}\n\nlepus_value LayoutAnimationTimingFunctionToLepusHelper(\n    const TimingFunctionData& timingFunction) {\n  auto array = lepus::CArray::Create();\n  array->emplace_back(static_cast<int>(timingFunction.timing_func));\n  array->emplace_back(static_cast<int>(timingFunction.steps_type));\n  array->emplace_back(timingFunction.x1);\n  array->emplace_back(timingFunction.y1);\n  array->emplace_back(timingFunction.x2);\n  array->emplace_back(timingFunction.y2);\n  return lepus_value(std::move(array));\n}\n\nbool SetBackgroundOrMaskImage(base::flex_optional<BackgroundData>& data,\n                              const tasm::CSSValue& value, bool reset) {\n  CSSStyleUtils::PrepareOptional(data);\n  CSSStyleUtils::PrepareOptional(data->image_data);\n  auto& image_data = data->image_data;\n  auto old_value = image_data->image;\n  image_data->image_count = DefaultComputedStyle::DEFAULT_LONG;\n  image_data->image = lepus::Value();\n  if (!reset) {\n    if (!value.IsArray()) {\n      return false;\n    }\n    auto array = value.GetArray();\n    for (size_t i = 0; i < array->size(); i++) {\n      const auto& img = array->get(i);\n      if (img.IsNumber()) {\n        ++image_data->image_count;\n      }\n    }\n    image_data->image = value.GetValue();\n    image_data->clone_image = false;\n  }\n  return old_value != image_data->image;\n}\n\nbool SetBackgroundOrMaskPosition(base::flex_optional<BackgroundData>& data,\n                                 const tasm::CssMeasureContext& context,\n                                 const tasm::CSSParserConfigs& configs,\n                                 const tasm::CSSValue& value, bool reset) {\n  CSSStyleUtils::PrepareOptional(data);\n  CSSStyleUtils::PrepareOptional(data->image_data);\n  auto& image_data = data->image_data;\n  auto old_value = image_data->position;\n  image_data->position.clear();\n  if (!reset) {\n    if (!value.IsArray()) {\n      return false;\n    }\n    auto pos_arr = value.GetArray();\n    for (size_t i = 0; i != pos_arr->size(); ++i) {\n      auto array = pos_arr->get(i).Array();\n      uint32_t pos_x_type = static_cast<uint32_t>(array->get(0).Number());\n      uint32_t pos_y_type = static_cast<uint32_t>(array->get(2).Number());\n      // position x\n      if (pos_x_type ==\n          static_cast<uint32_t>(BackgroundPositionType::kCenter)) {\n        image_data->position.emplace_back(NLength::MakePercentageNLength(50.f));\n      } else if (pos_x_type ==\n                 static_cast<uint32_t>(BackgroundPositionType::kLeft)) {\n        image_data->position.emplace_back(NLength::MakePercentageNLength(0.f));\n      } else if (pos_x_type ==\n                 static_cast<uint32_t>(BackgroundPositionType::kRight)) {\n        image_data->position.emplace_back(\n            NLength::MakePercentageNLength(100.f));\n      } else {\n        auto pattern = static_cast<uint32_t>(array->get(0).Number());\n        image_data->position.emplace_back(\n            CSSStyleUtils::ToLength(\n                tasm::CSSValue{array->get(1),\n                               static_cast<CSSValuePattern>(pattern)},\n                context, configs)\n                .first);\n      }\n\n      // position y\n      if (pos_y_type ==\n          static_cast<uint32_t>(BackgroundPositionType::kCenter)) {\n        image_data->position.emplace_back(NLength::MakePercentageNLength(50.f));\n      } else if (pos_y_type ==\n                 static_cast<uint32_t>(BackgroundPositionType::kTop)) {\n        image_data->position.emplace_back(NLength::MakePercentageNLength(0.f));\n      } else if (pos_y_type ==\n                 static_cast<uint32_t>(BackgroundPositionType::kBottom)) {\n        image_data->position.emplace_back(\n            NLength::MakePercentageNLength(100.f));\n      } else {\n        auto pattern = static_cast<uint32_t>(array->get(2).Number());\n        image_data->position.emplace_back(\n            CSSStyleUtils::ToLength(\n                tasm::CSSValue{array->get(3),\n                               static_cast<CSSValuePattern>(pattern)},\n                context, configs)\n                .first);\n      }\n    }\n  }\n  return old_value != image_data->position;\n}\n\nbool SetBackgroundOrMaskSize(base::flex_optional<BackgroundData>& data,\n                             const tasm::CssMeasureContext& context,\n                             const tasm::CSSParserConfigs& configs,\n                             const tasm::CSSValue& value, bool reset) {\n  CSSStyleUtils::PrepareOptional(data);\n  CSSStyleUtils::PrepareOptional(data->image_data);\n  auto& image_data = data->image_data;\n  auto old_value = image_data->size;\n  image_data->size.clear();\n  if (!reset) {\n    if (!value.IsArray()) {\n      return false;\n    }\n    auto size_arr = value.GetArray();\n    for (size_t i = 0; i != size_arr->size(); ++i) {\n      auto array = size_arr->get(i).Array();\n      auto pattern = static_cast<uint32_t>(array->get(0).Number());\n      image_data->size.emplace_back(\n          CSSStyleUtils::ToLength(\n              tasm::CSSValue(array->get(1),\n                             static_cast<CSSValuePattern>(pattern)),\n              context, configs)\n              .first);\n      pattern = static_cast<uint32_t>(array->get(2).Number());\n      image_data->size.emplace_back(\n          CSSStyleUtils::ToLength(\n              tasm::CSSValue(array->get(3),\n                             static_cast<CSSValuePattern>(pattern)),\n              context, configs)\n              .first);\n    }\n  }\n  return old_value != image_data->size;\n}\n\nbool SetBackgroundOrMaskClip(base::flex_optional<BackgroundData>& data,\n                             const tasm::CSSValue& value, bool reset) {\n  CSSStyleUtils::PrepareOptional(data);\n  CSSStyleUtils::PrepareOptional(data->image_data);\n  auto& image_data = data->image_data;\n  auto old_value = image_data->clip;\n  image_data->clip.clear();\n  if (!reset) {\n    if (!value.IsArray()) {\n      return false;\n    }\n    auto clip_arr = value.GetArray();\n    for (size_t i = 0; i < clip_arr->size(); i++) {\n      auto clip_type = static_cast<uint32_t>(clip_arr->get(i).Number());\n      image_data->clip.emplace_back(static_cast<BackgroundClipType>(clip_type));\n    }\n  }\n  return old_value != image_data->clip;\n}\n\nbool SetBackgroundOrMaskOrigin(base::flex_optional<BackgroundData>& data,\n                               const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(data);\n  CSSStyleUtils::PrepareOptional(data->image_data);\n  auto& image_data = data->image_data;\n  auto old_value = image_data->origin;\n  image_data->origin.clear();\n  if (!reset) {\n    if (!value.IsArray()) {\n      return false;\n    }\n    auto origin_arr = value.GetArray();\n    for (size_t i = 0; i < origin_arr->size(); i++) {\n      auto origin_type = static_cast<uint32_t>(origin_arr->get(i).Number());\n      image_data->origin.emplace_back(\n          static_cast<BackgroundOriginType>(origin_type));\n    }\n  }\n  return old_value != image_data->origin;\n}\n\nbool SetBackgroundOrMaskRepeat(base::flex_optional<BackgroundData>& data,\n                               const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(data);\n  CSSStyleUtils::PrepareOptional(data->image_data);\n  auto& image_data = data->image_data;\n  auto old_value = image_data->repeat;\n  image_data->repeat.clear();\n  if (!reset) {\n    if (!value.IsArray()) {\n      return false;\n    }\n    auto repeat_arr = value.GetArray();\n    for (size_t i = 0; i < repeat_arr->size(); i++) {\n      auto repeat_type =\n          static_cast<uint32_t>(repeat_arr->get(i).Array()->get(0).Number());\n      image_data->repeat.emplace_back(\n          static_cast<BackgroundRepeatType>(repeat_type));\n      repeat_type =\n          static_cast<uint32_t>(repeat_arr->get(i).Array()->get(1).Number());\n      image_data->repeat.emplace_back(\n          static_cast<BackgroundRepeatType>(repeat_type));\n    }\n  }\n  return old_value != image_data->repeat;\n}\n\n}  // namespace\n\nlepus::Value ComputedCSSStyleUtilsMethod::BackgroundOrMaskClipToLepus(\n    const base::flex_optional<BackgroundData>& data) {\n  if (data && data->image_data && !data->image_data->clip.empty()) {\n    auto array = lepus::CArray::Create();\n    for (const auto& clip : data->image_data->clip) {\n      array->emplace_back(static_cast<int32_t>(clip));\n    }\n    return lepus::Value{std::move(array)};\n  } else {\n    return lepus::Value{lepus::CArray::Create()};\n  }\n}\n\nlepus::Value ComputedCSSStyleUtilsMethod::BackgroundOrMaskImageToLepus(\n    base::flex_optional<BackgroundData>& data,\n    const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  if (data && data->image_data && data->image_data->image.IsArray()) {\n    std::vector<std::pair<size_t, BackgroundImageType>> items;\n    {\n      auto array = data->image_data->image.Array();\n      // TODO(renzhongyue): optimize the background's parse logic later, wuch\n      // that we can avoid clone data here.\n\n      // The BackgroundData->data->image is an array where image type and image\n      // data are stored alternately. This code specifically handles\n      // radial-gradient data. First, find the indices of all radial-gradient\n      // types.\n      for (size_t i = 0; i < array->size(); i++) {\n        const auto& img = array->get(i);\n        if (!img.IsNumber()) {\n          continue;\n        }\n        if (img.Number() >=\n            static_cast<uint32_t>(BackgroundImageType::kRadialGradient)) {\n          items.emplace_back(i, static_cast<BackgroundImageType>(img.Number()));\n        }\n      }\n    }\n\n    if (!items.empty()) {\n      // Clone the image data to avoid modifying the original data during\n      // computation.\n      if (!data->image_data->clone_image) {\n        data->image_data->clone_image = true;\n        data->image_data->image = lepus::Value::Clone(data->image_data->image);\n      }\n      auto new_array = data->image_data->image.Array();\n      // For each radial-gradient type, the next element in the array is the\n      // gradient data. Use `ComputeRadialGradient` to process the gradient\n      // data.\n      for (const auto& item : items) {\n        if (item.first + 1 >= new_array->size()) {\n          continue;\n        }\n        const auto& gradient_data = new_array->get(item.first + 1);\n        if (item.second == BackgroundImageType::kRadialGradient) {\n          CSSStyleUtils::ComputeRadialGradient(gradient_data, context, configs);\n        } else if ((item.second == BackgroundImageType::kConicGradient)) {\n          CSSStyleUtils::ComputeConicGradient(gradient_data, context, configs);\n        }\n      }\n    }\n\n    return data->image_data->image;\n  } else {\n    return lepus::Value{lepus::CArray::Create()};\n  }\n}\n\nlepus::Value ComputedCSSStyleUtilsMethod::BackgroundOrMaskOriginToLepus(\n    const base::flex_optional<BackgroundData>& data) {\n  if (data && data->image_data && !data->image_data->origin.empty()) {\n    auto array = lepus::CArray::Create();\n    for (const auto& origin : data->image_data->origin) {\n      array->emplace_back(static_cast<int32_t>(origin));\n    }\n    return lepus::Value{std::move(array)};\n  } else {\n    return lepus::Value{lepus::CArray::Create()};\n  }\n}\n\nlepus::Value ComputedCSSStyleUtilsMethod::BackgroundOrMaskPositionToLepus(\n    const base::flex_optional<BackgroundData>& data) {\n  if (data && data->image_data && !data->image_data->position.empty()) {\n    auto array = lepus::CArray::Create();\n    for (const auto& pos : data->image_data->position) {\n      CSSStyleUtils::AddLengthToArray(array, pos);\n    }\n    return lepus::Value{std::move(array)};\n  }\n  return lepus::Value{lepus::CArray::Create()};\n}\n\nlepus::Value ComputedCSSStyleUtilsMethod::BackgroundOrMaskRepeatToLepus(\n    const base::flex_optional<BackgroundData>& data) {\n  if (data && data->image_data && !data->image_data->repeat.empty()) {\n    auto array = lepus::CArray::Create();\n    for (const auto& repeat : data->image_data->repeat) {\n      array->emplace_back(static_cast<int32_t>(repeat));\n    }\n    return lepus_value{std::move(array)};\n  } else {\n    return lepus::Value{lepus::CArray::Create()};\n  }\n}\n\nlepus::Value ComputedCSSStyleUtilsMethod::BackgroundOrMaskSizeToLepus(\n    const base::flex_optional<BackgroundData>& data) {\n  if (data && data->image_data && !data->image_data->size.empty()) {\n    auto array = lepus::CArray::Create();\n    for (const auto& size : data->image_data->size) {\n      CSSStyleUtils::AddLengthToArray(array, size);\n    }\n    return lepus::Value{std::move(array)};\n  } else {\n    return lepus::Value{lepus::CArray::Create()};\n  }\n}\n\nfloat ComputedCSSStyle::SAFE_AREA_INSET_TOP_ = 0;\nfloat ComputedCSSStyle::SAFE_AREA_INSET_BOTTOM_ = 0;\nfloat ComputedCSSStyle::SAFE_AREA_INSET_LEFT_ = 0;\nfloat ComputedCSSStyle::SAFE_AREA_INSET_RIGHT_ = 0;\n\nComputedCSSStyle::ComputedCSSStyle(float layouts_unit_per_px,\n                                   double physical_pixels_per_layout_unit)\n    : layout_computed_style_(physical_pixels_per_layout_unit),\n      length_context_(0.f, layouts_unit_per_px, physical_pixels_per_layout_unit,\n                      layouts_unit_per_px * DEFAULT_FONT_SIZE_DP,\n                      layouts_unit_per_px * DEFAULT_FONT_SIZE_DP, LayoutUnit(),\n                      LayoutUnit()) {}\n\nComputedCSSStyle::ComputedCSSStyle(const ComputedCSSStyle& o)\n    : layout_computed_style_(o.layout_computed_style_),\n      length_context_(o.length_context_) {}\n\nvoid ComputedCSSStyle::Reset() {\n  layout_computed_style_.Reset();\n\n  opacity_ = DefaultComputedStyle::DEFAULT_OPACITY;\n  z_index_ = DefaultComputedStyle::DEFAULT_LONG;\n\n  ResetOverflow();\n\n  text_attributes_.reset();\n  transform_raw_.reset();\n  transform_origin_.reset();\n  animation_data_.reset();\n  transition_data_.reset();\n  layout_animation_data_.reset();\n  enter_transition_data_.reset();\n  exit_transition_data_.reset();\n  pause_transition_data_.reset();\n  resume_transition_data_.reset();\n  filter_.reset();\n  visibility_ = DefaultComputedStyle::DEFAULT_VISIBILITY;\n  caret_color_ = base::String();\n  const float default_font_size =\n      length_context_.layouts_unit_per_px_ * DEFAULT_FONT_SIZE_DP;\n  SetFontSize(default_font_size, default_font_size);\n}\n\nbool ComputedCSSStyle::SetValue(tasm::CSSPropertyID id,\n                                const tasm::CSSValue& value, bool reset) {\n  const auto* funcMap = FuncMap();\n  if (id > tasm::CSSPropertyID::kPropertyStart &&\n      id < tasm::CSSPropertyID::kPropertyEnd) {\n    if (StyleFunc func = funcMap[id]) {\n      if (auto changed = (this->*func)(value, reset); changed) {\n        MarkChanged(id);\n        return true;\n      }\n\n      return false;\n    }\n  }\n  LynxWarning(false, error::E_CSS_COMPUTED_CSS_VALUE_UNKNOWN_SETTER,\n              \"SetValue can't find style func id:%d\", id);\n  return false;\n}\n\nbool ComputedCSSStyle::AppendAnimatedAnimationValue(tasm::StyleMap animate_data,\n                                                    bool reset) {\n  auto name =\n      animate_data[tasm::CSSPropertyID::kPropertyIDAnimationName].AsString();\n  auto temp_animate_data = AnimationData();\n  if (!animation_data_) {\n    animation_data_.emplace();\n  }\n\n  auto iter = std::find_if(animation_data_->begin(), animation_data_->end(),\n                           [&](const AnimationData& animation_data) {\n                             return animation_data.name == name;\n                           });\n\n  bool new_animation_data = (iter == animation_data_->end());\n  AnimationData* target_animation_data =\n      new_animation_data ? &temp_animate_data : &(*iter);\n\n  for (auto [id, value] : animate_data) {\n    switch (id) {\n      case tasm::kPropertyIDAnimationName:\n        target_animation_data->name = value.AsString();\n        break;\n      case tasm::kPropertyIDAnimationDuration:\n        target_animation_data->duration = value.AsNumber();\n        break;\n      case tasm::kPropertyIDAnimationDelay:\n        target_animation_data->delay = value.AsNumber();\n        break;\n      case tasm::kPropertyIDAnimationFillMode:\n        target_animation_data->fill_mode =\n            value.GetEnum<AnimationFillModeType>();\n        break;\n      case tasm::kPropertyIDAnimationIterationCount:\n        target_animation_data->iteration_count = value.AsNumber();\n        break;\n      case tasm::kPropertyIDAnimationTimingFunction:\n        CSSStyleUtils::ComputeTimingFunction(value.GetArray()->get(0), reset,\n                                             target_animation_data->timing_func,\n                                             parser_configs_);\n        break;\n      case tasm::kPropertyIDAnimationDirection:\n        target_animation_data->direction =\n            value.GetEnum<AnimationDirectionType>();\n        break;\n      case tasm::kPropertyIDAnimationPlayState:\n        target_animation_data->play_state =\n            value.GetEnum<AnimationPlayStateType>();\n        break;\n      default:\n        break;\n    }\n  }\n\n  if (new_animation_data) {\n    animation_data_->emplace_back(temp_animate_data);\n  }\n\n  return true;\n}\nbool ComputedCSSStyle::ResetValue(tasm::CSSPropertyID id) {\n  const auto* funcMap = FuncMap();\n  if (id > tasm::CSSPropertyID::kPropertyStart &&\n      id < tasm::CSSPropertyID::kPropertyEnd) {\n    if (StyleFunc func = funcMap[id]) {\n      if (auto reset = (this->*func)(tasm::CSSValue(), true); reset) {\n        MarkReset(id);\n        return true;\n      }\n\n      return false;\n    }\n  }\n  LynxWarning(false, error::E_CSS_COMPUTED_CSS_VALUE_UNKNOWN_SETTER,\n              \"ResetValue can't find style func id:%d\", id);\n  return false;\n}\n\nvoid ComputedCSSStyle::SetOverflowDefaultVisible(\n    bool default_overflow_visible) {\n  default_overflow_visible_ = default_overflow_visible;\n  ResetOverflow();\n}\n\nvoid ComputedCSSStyle::ResetOverflow() {\n  const auto& overflow = default_overflow_visible_ ? OverflowType::kVisible\n                                                   : OverflowType::kHidden;\n  overflow_ = overflow;\n  overflow_x_ = overflow;\n  overflow_y_ = overflow;\n  origin_overflow_ = default_overflow_visible_ ? OVERFLOW_XY : OVERFLOW_HIDDEN;\n}\n\nbool ComputedCSSStyle::InheritValue(tasm::CSSPropertyID id,\n                                    const ComputedCSSStyle& from) {\n  const auto& inheritFuncMap = InheritFuncMap();\n  auto iter = inheritFuncMap.find(id);\n  if (iter == inheritFuncMap.end()) {\n    LynxWarning(false, error::E_CSS_COMPUTED_CSS_VALUE_UNSUPPORTED_INHERITANCE,\n                \"Inherit is not supported for style: \\\"%s\\\"\",\n                tasm::CSSProperty::GetPropertyName(id).c_str());\n    return false;\n  }\n  StyleInheritFunc func = iter->second;\n  if (auto inherit = (this->*func)(from); inherit) {\n    MarkChanged(id);\n    return true;\n  }\n\n  return false;\n}\n\n#define SUPPORTED_LENGTH_PROPERTY(V)                                           \\\n  V(Width, NLength, layout_computed_style_.box_data_.Access()->width_, WIDTH)  \\\n  V(Height, NLength, layout_computed_style_.box_data_.Access()->height_,       \\\n    HEIGHT)                                                                    \\\n  V(MinWidth, NLength, layout_computed_style_.box_data_.Access()->min_width_,  \\\n    MIN_WIDTH)                                                                 \\\n  V(MinHeight, NLength,                                                        \\\n    layout_computed_style_.box_data_.Access()->min_height_, MIN_HEIGHT)        \\\n  V(MaxWidth, NLength, layout_computed_style_.box_data_.Access()->max_width_,  \\\n    MAX_WIDTH)                                                                 \\\n  V(MaxHeight, NLength,                                                        \\\n    layout_computed_style_.box_data_.Access()->max_height_, MAX_HEIGHT)        \\\n  V(FlexBasis, NLength,                                                        \\\n    layout_computed_style_.flex_data_.Access()->flex_basis_, FLEX_BASIS)       \\\n  V(Left, NLength, layout_computed_style_.surround_data_.left_, FOUR_POSITION) \\\n  V(Right, NLength, layout_computed_style_.surround_data_.right_,              \\\n    FOUR_POSITION)                                                             \\\n  V(Top, NLength, layout_computed_style_.surround_data_.top_, FOUR_POSITION)   \\\n  V(Bottom, NLength, layout_computed_style_.surround_data_.bottom_,            \\\n    FOUR_POSITION)                                                             \\\n  V(PaddingLeft, NLength, layout_computed_style_.surround_data_.padding_left_, \\\n    PADDING)                                                                   \\\n  V(PaddingRight, NLength,                                                     \\\n    layout_computed_style_.surround_data_.padding_right_, PADDING)             \\\n  V(PaddingTop, NLength, layout_computed_style_.surround_data_.padding_top_,   \\\n    PADDING)                                                                   \\\n  V(PaddingBottom, NLength,                                                    \\\n    layout_computed_style_.surround_data_.padding_bottom_, PADDING)            \\\n  V(MarginLeft, NLength, layout_computed_style_.surround_data_.margin_left_,   \\\n    MARGIN)                                                                    \\\n  V(MarginRight, NLength, layout_computed_style_.surround_data_.margin_right_, \\\n    MARGIN)                                                                    \\\n  V(MarginTop, NLength, layout_computed_style_.surround_data_.margin_top_,     \\\n    MARGIN)                                                                    \\\n  V(MarginBottom, NLength,                                                     \\\n    layout_computed_style_.surround_data_.margin_bottom_, MARGIN)\n\nbool ComputedCSSStyle::SetFlexGrow(const tasm::CSSValue& value,\n                                   const bool reset) {\n  return CSSStyleUtils::ComputeFloatStyle(\n      value, reset, layout_computed_style_.flex_data_.Access()->flex_grow_,\n      DefaultLayoutStyle::SL_DEFAULT_FLEX_GROW, \"flex-grow must be a number!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetFlexShrink(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return CSSStyleUtils::ComputeFloatStyle(\n      value, reset, layout_computed_style_.flex_data_.Access()->flex_shrink_,\n      DefaultLayoutStyle::SL_DEFAULT_FLEX_SHRINK,\n      \"flex-shrink must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetOrder(const tasm::CSSValue& value, const bool reset) {\n  return CSSStyleUtils::ComputeFloatStyle(\n      value, reset, layout_computed_style_.flex_data_.Access()->order_,\n      DefaultLayoutStyle::SL_DEFAULT_ORDER, \"order must be a number!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLinearWeightSum(const tasm::CSSValue& value,\n                                          const bool reset) {\n  return CSSStyleUtils::ComputeFloatStyle(\n      value, reset,\n      layout_computed_style_.linear_data_.Access()->linear_weight_sum_,\n      DefaultLayoutStyle::SL_DEFAULT_LINEAR_WEIGHT_SUM,\n      \"linear-weight-sum must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLinearWeight(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return CSSStyleUtils::ComputeFloatStyle(\n      value, reset,\n      layout_computed_style_.linear_data_.Access()->linear_weight_,\n      DefaultLayoutStyle::SL_DEFAULT_LINEAR_WEIGHT,\n      \"linear-weight must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAspectRatio(const tasm::CSSValue& value,\n                                      const bool reset) {\n  return CSSStyleUtils::ComputeFloatStyle(\n      value, reset, layout_computed_style_.box_data_.Access()->aspect_ratio_,\n      DefaultLayoutStyle::SL_DEFAULT_ASPECT_RATIO,\n      \"aspect-ratio must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderLeftWidth(const tasm::CSSValue& value,\n                                          const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_,\n      css_align_with_legacy_w3c_);\n  return SetBorderWidthHelper(\n      css_align_with_legacy_w3c_, length_context_,\n      layout_computed_style_.surround_data_.border_data_->width_left, value,\n      parser_configs_, reset);\n}\n\nbool ComputedCSSStyle::SetBorderTopWidth(const tasm::CSSValue& value,\n                                         const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_,\n      css_align_with_legacy_w3c_);\n  return SetBorderWidthHelper(\n      css_align_with_legacy_w3c_, length_context_,\n      layout_computed_style_.surround_data_.border_data_->width_top, value,\n      parser_configs_, reset);\n}\n\nbool ComputedCSSStyle::SetBorderRightWidth(const tasm::CSSValue& value,\n                                           const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_,\n      css_align_with_legacy_w3c_);\n  return SetBorderWidthHelper(\n      css_align_with_legacy_w3c_, length_context_,\n      layout_computed_style_.surround_data_.border_data_->width_right, value,\n      parser_configs_, reset);\n}\n\nbool ComputedCSSStyle::SetBorderBottomWidth(const tasm::CSSValue& value,\n                                            const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_,\n      css_align_with_legacy_w3c_);\n  return SetBorderWidthHelper(\n      css_align_with_legacy_w3c_, length_context_,\n      layout_computed_style_.surround_data_.border_data_->width_bottom, value,\n      parser_configs_, reset);\n}\n\nbool ComputedCSSStyle::SetBorder(const tasm::CSSValue& value,\n                                 const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetTextStroke(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetTextStrokeColor(const tasm::CSSValue& value,\n                                          const bool reset) {\n  PrepareOptionalForTextAttributes();\n  auto old_value_color = text_attributes_->text_stroke_color;\n  if (reset) {\n    text_attributes_->text_stroke_color = DefaultColor::DEFAULT_COLOR;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsNumber(), parser_configs_.enable_css_strict_mode,\n        tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDTextStrokeColor)\n            .c_str(),\n        tasm::NUMBER_TYPE)\n\n    if (value.IsNumber()) {\n      text_attributes_->text_stroke_color =\n          static_cast<unsigned int>(value.GetNumber());\n    }\n  }\n  return old_value_color != text_attributes_->text_stroke_color;\n}\n\nbool ComputedCSSStyle::SetTextStrokeWidth(const tasm::CSSValue& value,\n                                          const bool reset) {\n  PrepareOptionalForTextAttributes();\n  auto old_value = text_attributes_->text_stroke_width;\n  if (reset) {\n    text_attributes_->text_stroke_width = DefaultComputedStyle::DEFAULT_FLOAT;\n  } else {\n    auto parse_result =\n        CSSStyleUtils::ToLength(value, length_context_, parser_configs_, false);\n    if (!parse_result.second) {\n      return false;\n    }\n    if (parse_result.first.IsUnit()) {\n      text_attributes_->text_stroke_width = parse_result.first.GetRawValue();\n    } else {\n      return false;\n    }\n  }\n  return base::FloatsNotEqual(text_attributes_->text_stroke_width, old_value);\n}\n\nbool ComputedCSSStyle::SetBorderTop(const tasm::CSSValue& value,\n                                    const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderRight(const tasm::CSSValue& value,\n                                      const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderBottom(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetFontScale(float font_scale) {\n  if (font_scale == length_context_.font_scale_) {\n    return false;\n  }\n  length_context_.font_scale_ = font_scale;\n  return true;\n}\nbool ComputedCSSStyle::SetBorderLeft(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetMarginInlineStart(const tasm::CSSValue& value,\n                                            const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetMarginInlineEnd(const tasm::CSSValue& value,\n                                          const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetPaddingInlineStart(const tasm::CSSValue& value,\n                                             const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetPaddingInlineEnd(const tasm::CSSValue& value,\n                                           const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderInlineStartWidth(const tasm::CSSValue& value,\n                                                 const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderInlineEndWidth(const tasm::CSSValue& value,\n                                               const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderInlineStartColor(const tasm::CSSValue& value,\n                                                 const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderInlineEndColor(const tasm::CSSValue& value,\n                                               const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderInlineStartStyle(const tasm::CSSValue& value,\n                                                 const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderInlineEndStyle(const tasm::CSSValue& value,\n                                               const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderStartStartRadius(const tasm::CSSValue& value,\n                                                 const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderEndStartRadius(const tasm::CSSValue& value,\n                                               const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderStartEndRadius(const tasm::CSSValue& value,\n                                               const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderEndEndRadius(const tasm::CSSValue& value,\n                                             const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetRelativeAlignInlineStart(const tasm::CSSValue& value,\n                                                   const bool reset) {\n  NOTREACHED();\n  return false;\n}\n\nbool ComputedCSSStyle::SetRelativeAlignInlineEnd(const tasm::CSSValue& value,\n                                                 const bool reset) {\n  NOTREACHED();\n  return false;\n}\n\nbool ComputedCSSStyle::SetRelativeInlineStartOf(const tasm::CSSValue& value,\n                                                const bool reset) {\n  NOTREACHED();\n  return false;\n}\n\nbool ComputedCSSStyle::SetRelativeInlineEndOf(const tasm::CSSValue& value,\n                                              const bool reset) {\n  NOTREACHED();\n  return false;\n}\n\n#define SET_LENGTH_PROPERTY(type_name, length, css_type, default_type)     \\\n  bool ComputedCSSStyle::Set##type_name(const tasm::CSSValue& value,       \\\n                                        const bool reset) {                \\\n    return CSSStyleUtils::ComputeLengthStyle(                              \\\n        value, reset, length_context_, css_type,                           \\\n        DefaultLayoutStyle::SL_DEFAULT_##default_type(), parser_configs_); \\\n  }\nSUPPORTED_LENGTH_PROPERTY(SET_LENGTH_PROPERTY)\n#undef SET_LENGTH_PROPERTY\n\nbool ComputedCSSStyle::SetFlexDirection(const tasm::CSSValue& value,\n                                        const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<FlexDirectionType>(\n      value, reset, layout_computed_style_.flex_data_.Access()->flex_direction_,\n      DefaultLayoutStyle::SL_DEFAULT_FLEX_DIRECTION,\n      \"flex-direction must be a enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetJustifyContent(const tasm::CSSValue& value,\n                                         const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<JustifyContentType>(\n      value, reset,\n      layout_computed_style_.flex_data_.Access()->justify_content_,\n      DefaultLayoutStyle::SL_DEFAULT_JUSTIFY_CONTENT,\n      \"justify-content must be a enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetFlexWrap(const tasm::CSSValue& value,\n                                   const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<FlexWrapType>(\n      value, reset, layout_computed_style_.flex_data_.Access()->flex_wrap_,\n      DefaultLayoutStyle::SL_DEFAULT_FLEX_WRAP, \"flex-warp must be a enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAlignItems(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<FlexAlignType>(\n      value, reset, layout_computed_style_.flex_data_.Access()->align_items_,\n      DefaultLayoutStyle::SL_DEFAULT_ALIGN_ITEMS, \"align-items must be a enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAlignSelf(const tasm::CSSValue& value,\n                                    const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<FlexAlignType>(\n      value, reset, layout_computed_style_.flex_data_.Access()->align_self_,\n      DefaultLayoutStyle::SL_DEFAULT_ALIGN_SELF, \"align-self must be a enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAlignContent(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<AlignContentType>(\n      value, reset, layout_computed_style_.flex_data_.Access()->align_content_,\n      DefaultLayoutStyle::SL_DEFAULT_ALIGN_CONTENT,\n      \"align-content must be a enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetPosition(const tasm::CSSValue& value,\n                                   const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<PositionType>(\n      value, reset, layout_computed_style_.position_,\n      DefaultLayoutStyle::SL_DEFAULT_POSITION, \"position must be a enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetDirection(const tasm::CSSValue& value,\n                                    const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<DirectionType>(\n      value, reset, layout_computed_style_.direction_,\n      DefaultLayoutStyle::SL_DEFAULT_DIRECTION, \"direction must be a enum!\",\n      parser_configs_);\n}\n\nvoid ComputedCSSStyle::SetOriginOverflowMask(tasm::CSSPropertyID id) {\n#define CHECK_OVERFLOW_VAL(value, mask)             \\\n  if (value == starlight::OverflowType::kVisible) { \\\n    origin_overflow_ |= (mask);                     \\\n  } else {                                          \\\n    origin_overflow_ &= ~(mask);                    \\\n  }\n\n  switch (id) {\n    case tasm::CSSPropertyID::kPropertyIDOverflow:\n      CHECK_OVERFLOW_VAL(overflow_, OVERFLOW_XY)\n      break;\n    case tasm::CSSPropertyID::kPropertyIDOverflowX:\n      CHECK_OVERFLOW_VAL(overflow_x_, OVERFLOW_X)\n      break;\n    case tasm::CSSPropertyID::kPropertyIDOverflowY:\n      CHECK_OVERFLOW_VAL(overflow_y_, OVERFLOW_Y)\n      break;\n    default:\n      break;\n  }\n}\n\nbool ComputedCSSStyle::SetOverflow(const tasm::CSSValue& value,\n                                   const bool reset) {\n  bool result = CSSStyleUtils::ComputeEnumStyle<OverflowType>(\n      value, reset, overflow_, GetDefaultOverflowType(),\n      \"overflow must be a enum!\", parser_configs_);\n\n  SetOriginOverflowMask(tasm::CSSPropertyID::kPropertyIDOverflow);\n  return result;\n}\n\nbool ComputedCSSStyle::SetDisplay(const tasm::CSSValue& value,\n                                  const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<DisplayType>(\n      value, reset, layout_computed_style_.display_,\n      DefaultLayoutStyle::SL_DEFAULT_DISPLAY, \"display must be a enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLinearOrientation(const tasm::CSSValue& value,\n                                            const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<LinearOrientationType>(\n      value, reset,\n      layout_computed_style_.linear_data_.Access()->linear_orientation_,\n      DefaultLayoutStyle::SL_DEFAULT_LINEAR_ORIENTATION,\n      \"linear-orientation must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLinearDirection(const tasm::CSSValue& value,\n                                          const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<LinearOrientationType>(\n      value, reset,\n      layout_computed_style_.linear_data_.Access()->linear_orientation_,\n      DefaultLayoutStyle::SL_DEFAULT_LINEAR_ORIENTATION,\n      \"linear-direction must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLinearLayoutGravity(const tasm::CSSValue& value,\n                                              const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<LinearLayoutGravityType>(\n      value, reset,\n      layout_computed_style_.linear_data_.Access()->linear_layout_gravity_,\n      DefaultLayoutStyle::SL_DEFAULT_LINEAR_LAYOUT_GRAVITY,\n      \"linear-layout-gravity must be a enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLinearGravity(const tasm::CSSValue& value,\n                                        const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<LinearGravityType>(\n      value, reset,\n      layout_computed_style_.linear_data_.Access()->linear_gravity_,\n      DefaultLayoutStyle::SL_DEFAULT_LINEAR_GRAVITY,\n      \"linear-gravity must be a enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLinearCrossGravity(const tasm::CSSValue& value,\n                                             const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<LinearCrossGravityType>(\n      value, reset,\n      layout_computed_style_.linear_data_.Access()->linear_cross_gravity_,\n      DefaultLayoutStyle::SL_DEFAULT_LINEAR_CROSS_GRAVITY,\n      \"linear-cross-gravity must be a enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBoxSizing(const tasm::CSSValue& value,\n                                    const bool reset) {\n  auto old_value = layout_computed_style_.box_sizing_;\n  if (reset) {\n    layout_computed_style_.box_sizing_ = BoxSizingType::kAuto;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsEnum(), parser_configs_.enable_css_strict_mode,\n        tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDBoxSizing).c_str(),\n        tasm::ENUM_TYPE)\n\n    layout_computed_style_.box_sizing_ = value.GetEnum<BoxSizingType>();\n  }\n  return old_value != layout_computed_style_.box_sizing_;\n}\n\nbool ComputedCSSStyle::SetRelativeId(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_id_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_ID, \"relative-id must be a int!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeAlignTop(const tasm::CSSValue& value,\n                                           const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_align_top_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_ALIGN_TOP,\n      \"relative-align-top must be a int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeAlignRight(const tasm::CSSValue& value,\n                                             const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_align_right_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_ALIGN_RIGHT,\n      \"relative-align-right must be a int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeAlignBottom(const tasm::CSSValue& value,\n                                              const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_align_bottom_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_ALIGN_BOTTOM,\n      \"relative-align-bottom must be a int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeAlignLeft(const tasm::CSSValue& value,\n                                            const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_align_left_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_ALIGN_LEFT,\n      \"relative-align-left must be a int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeTopOf(const tasm::CSSValue& value,\n                                        const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_top_of_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_TOP_OF,\n      \"relative-top-of must be a int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeRightOf(const tasm::CSSValue& value,\n                                          const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_right_of_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_RIGHT_OF,\n      \"relative-right-of must be a int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeBottomOf(const tasm::CSSValue& value,\n                                           const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_bottom_of_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_BOTTOM_OF,\n      \"relative-bottom-of must be a int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeLeftOf(const tasm::CSSValue& value,\n                                         const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_left_of_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_LEFT_OF,\n      \"relative-left-of must be a int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeLayoutOnce(const tasm::CSSValue& value,\n                                             const bool reset) {\n  return CSSStyleUtils::ComputeBoolStyle(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_layout_once_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_LAYOUT_ONCE,\n      \"relative-layout-once must be a bool!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetRelativeCenter(const tasm::CSSValue& value,\n                                         const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<RelativeCenterType>(\n      value, reset,\n      layout_computed_style_.relative_data_.Access()->relative_center_,\n      DefaultLayoutStyle::SL_DEFAULT_RELATIVE_CENTER,\n      \"relative-center must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridTemplateColumns(const tasm::CSSValue& value,\n                                              const bool reset) {\n  return CSSStyleUtils::ComputeGridTrackSizing(\n      value, reset, length_context_,\n      layout_computed_style_.grid_data_.Access()\n          ->grid_template_columns_min_track_sizing_function_,\n      layout_computed_style_.grid_data_.Access()\n          ->grid_template_columns_max_track_sizing_function_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_TRACK(),\n      \"grid-template-columns must be an array!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridTemplateRows(const tasm::CSSValue& value,\n                                           const bool reset) {\n  return CSSStyleUtils::ComputeGridTrackSizing(\n      value, reset, length_context_,\n      layout_computed_style_.grid_data_.Access()\n          ->grid_template_rows_min_track_sizing_function_,\n      layout_computed_style_.grid_data_.Access()\n          ->grid_template_rows_max_track_sizing_function_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_TRACK(),\n      \"grid-template-rows must be an array!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridAutoColumns(const tasm::CSSValue& value,\n                                          const bool reset) {\n  return CSSStyleUtils::ComputeGridTrackSizing(\n      value, reset, length_context_,\n      layout_computed_style_.grid_data_.Access()\n          ->grid_auto_columns_min_track_sizing_function_,\n      layout_computed_style_.grid_data_.Access()\n          ->grid_auto_columns_max_track_sizing_function_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_TRACK(),\n      \"grid-auto-columns must be an array!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridAutoRows(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return CSSStyleUtils::ComputeGridTrackSizing(\n      value, reset, length_context_,\n      layout_computed_style_.grid_data_.Access()\n          ->grid_auto_rows_min_track_sizing_function_,\n      layout_computed_style_.grid_data_.Access()\n          ->grid_auto_rows_max_track_sizing_function_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_TRACK(),\n      \"grid-auto-rows must be an array!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridColumnSpan(const tasm::CSSValue& value,\n                                         const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.grid_data_.Access()->grid_column_span_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_SPAN,\n      \"grid-column-span must be an int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridRowSpan(const tasm::CSSValue& value,\n                                      const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset, layout_computed_style_.grid_data_.Access()->grid_row_span_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_SPAN, \"grid-row-span must be an int!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridRowStart(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset, layout_computed_style_.grid_data_.Access()->grid_row_start_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_ITEM_POSITION,\n      \"grid-row-start must be an int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridRowEnd(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset, layout_computed_style_.grid_data_.Access()->grid_row_end_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_ITEM_POSITION,\n      \"grid-row-end must be an int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridColumnStart(const tasm::CSSValue& value,\n                                          const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.grid_data_.Access()->grid_column_start_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_ITEM_POSITION,\n      \"grid-column-start must be an int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridColumnEnd(const tasm::CSSValue& value,\n                                        const bool reset) {\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset,\n      layout_computed_style_.grid_data_.Access()->grid_column_end_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_ITEM_POSITION,\n      \"grid-column-end must be an int!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGap(const tasm::CSSValue& value, const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\n// Reuse the grid gap setting, because flex also supports using\n// grid-column-gap/grid-row-gap\nbool ComputedCSSStyle::SetColumnGap(const tasm::CSSValue& value,\n                                    const bool reset) {\n  return SetGridColumnGap(value, reset);\n}\n\nbool ComputedCSSStyle::SetRowGap(const tasm::CSSValue& value,\n                                 const bool reset) {\n  return SetGridRowGap(value, reset);\n}\n\nbool ComputedCSSStyle::SetGridColumnGap(const tasm::CSSValue& value,\n                                        const bool reset) {\n  return CSSStyleUtils::ComputeLengthStyle(\n      value, reset, length_context_,\n      layout_computed_style_.grid_data_.Access()->grid_column_gap_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_GAP(), parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridRowGap(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return CSSStyleUtils::ComputeLengthStyle(\n      value, reset, length_context_,\n      layout_computed_style_.grid_data_.Access()->grid_row_gap_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_GAP(), parser_configs_);\n}\n\nbool ComputedCSSStyle::SetJustifyItems(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<JustifyType>(\n      value, reset, layout_computed_style_.grid_data_.Access()->justify_items_,\n      DefaultLayoutStyle::SL_DEFAULT_JUSTIFY_ITEMS,\n      \"justify-items must be a enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetJustifySelf(const tasm::CSSValue& value,\n                                      const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<JustifyType>(\n      value, reset, layout_computed_style_.grid_data_.Access()->justify_self_,\n      DefaultLayoutStyle::SL_DEFAULT_JUSTIFY_SELF,\n      \"justify-self must be a enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetGridAutoFlow(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<GridAutoFlowType>(\n      value, reset, layout_computed_style_.grid_data_.Access()->grid_auto_flow_,\n      DefaultLayoutStyle::SL_DEFAULT_GRID_AUTO_FLOW,\n      \"grid-auto-flow must be a enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetFlex(const tasm::CSSValue& value, const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetFlexFlow(const tasm::CSSValue& value,\n                                   const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetPadding(const tasm::CSSValue& value,\n                                  const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetMargin(const tasm::CSSValue& value,\n                                 const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\nbool ComputedCSSStyle::SetInsetInlineStart(const tasm::CSSValue& value,\n                                           const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetInsetInlineEnd(const tasm::CSSValue& value,\n                                         const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBorderWidth(const tasm::CSSValue& value,\n                                      const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\n// setter\n\n// deprecated. Here is only a Placeholder.\nbool ComputedCSSStyle::SetImplicitAnimation(const tasm::CSSValue& value,\n                                            const bool reset) {\n  return false;\n}\n\nbool ComputedCSSStyle::HasOpacity() const {\n  // TODO(songshourui.null): Previously, an element was considered to have\n  // opacity if the property was set, but opacity == 1.0f should not count. The\n  // Android flatten check depended on the old logic (`origin_has_opacity_`),\n  // causing performance issues by incorrectly preventing flattening. To avoid\n  // breaking changes, a new `EnableOptimizeHasOpacity` setting is introduced.\n  // This setting will be removed after verifying stability in production.\n  if (tasm::LynxEnv::GetInstance().EnableOptimizeHasOpacity()) {\n    return base::FloatsNotEqual(opacity_, 1.0f);\n  }\n  return origin_has_opacity_;\n}\n\nbool ComputedCSSStyle::SetOpacity(const tasm::CSSValue& value,\n                                  const bool reset) {\n  bool result = CSSStyleUtils::ComputeFloatStyle(\n      value, reset, opacity_, DefaultComputedStyle::DEFAULT_OPACITY,\n      \"opacity must be a float!\", parser_configs_);\n\n  origin_has_opacity_ = !reset;\n  return result;\n}\n\nbool ComputedCSSStyle::SetOverflowX(const tasm::CSSValue& value,\n                                    const bool reset) {\n  bool result = CSSStyleUtils::ComputeEnumStyle<OverflowType>(\n      value, reset, overflow_x_, GetDefaultOverflowType(),\n      \"overflow-x must be an enum!\", parser_configs_);\n\n  SetOriginOverflowMask(tasm::CSSPropertyID::kPropertyIDOverflowX);\n\n  return result;\n}\n\nbool ComputedCSSStyle::SetOverflowY(const tasm::CSSValue& value,\n                                    const bool reset) {\n  bool result = CSSStyleUtils::ComputeEnumStyle<OverflowType>(\n      value, reset, overflow_y_, GetDefaultOverflowType(),\n      \"overflow-y must be an enum!\", parser_configs_);\n\n  SetOriginOverflowMask(tasm::CSSPropertyID::kPropertyIDOverflowY);\n\n  return result;\n}\n\nbool ComputedCSSStyle::SetFontSize(const tasm::CSSValue& value,\n                                   const bool reset) {\n  PrepareOptionalForTextAttributes();\n  const float default_font_size =\n      DEFAULT_FONT_SIZE_DP * length_context_.layouts_unit_per_px_;\n  auto old_value = text_attributes_->font_size;\n  if (reset) {\n    text_attributes_->font_size = default_font_size;\n  } else {\n    text_attributes_->font_size = length_context_.cur_node_font_size_;\n  }\n  return base::FloatsNotEqual(text_attributes_->font_size, old_value);\n}\n\nbool ComputedCSSStyle::SetXPlaceholderFontSize(const tasm::CSSValue& value,\n                                               const bool reset) {\n  PrepareOptionalForPlaceholderTextAttributes();\n  const float default_font_size =\n      DEFAULT_FONT_SIZE_DP * length_context_.layouts_unit_per_px_;\n  auto old_value = placeholder_text_attributes_->font_size;\n  if (reset) {\n    placeholder_text_attributes_->font_size = default_font_size;\n  } else {\n    CalculateCSSValueToFloat(value, placeholder_text_attributes_->font_size,\n                             length_context_, parser_configs_);\n  }\n  return base::FloatsNotEqual(placeholder_text_attributes_->font_size,\n                              old_value);\n}\n\nbool ComputedCSSStyle::SetLineHeight(const tasm::CSSValue& value,\n                                     const bool reset) {\n  PrepareOptionalForTextAttributes();\n  auto old_value = text_attributes_->computed_line_height;\n  auto old_factor = text_attributes_->line_height_factor;\n  if (reset) {\n    text_attributes_->computed_line_height =\n        DefaultComputedStyle::DEFAULT_LINE_HEIGHT;\n    text_attributes_->line_height_factor =\n        DefaultComputedStyle::DEFAULT_LINE_HEIGHT_FACTOR;\n\n    return base::FloatsNotEqual(text_attributes_->computed_line_height,\n                                old_value) ||\n           base::FloatsNotEqual(old_factor,\n                                text_attributes_->line_height_factor);\n  } else {\n    if (value.IsNumber() || value.IsPercent()) {\n      text_attributes_->line_height_factor =\n          value.GetNumber() / (value.IsPercent() ? 100 : 1);\n      text_attributes_->computed_line_height =\n          text_attributes_->line_height_factor *\n          length_context_.cur_node_font_size_;\n      // Either the computed line height or the line height factor changes may\n      // affect how the line height behaves.\n      return base::FloatsNotEqual(text_attributes_->computed_line_height,\n                                  old_value) ||\n             base::FloatsNotEqual(old_factor,\n                                  text_attributes_->line_height_factor);\n    } else {\n      if (UNLIKELY(!CalculateCSSValueToFloat(\n              value, text_attributes_->computed_line_height, length_context_,\n              parser_configs_, true))) {\n        return false;\n      }\n      text_attributes_->line_height_factor =\n          DefaultComputedStyle::DEFAULT_LINE_HEIGHT_FACTOR;\n      return base::FloatsNotEqual(text_attributes_->computed_line_height,\n                                  old_value);\n    }\n  }\n  return false;\n}\n\nbool ComputedCSSStyle::SetXAutoFontSize(const tasm::CSSValue& value,\n                                        const bool reset) {\n  PrepareOptionalForTextAttributes();\n  auto old_is_auto_font_size = text_attributes_->is_auto_font_size;\n  auto old_auto_font_size_min_size = text_attributes_->auto_font_size_min_size;\n  auto old_is_auto_font_size_max_size =\n      text_attributes_->auto_font_size_max_size;\n  auto old_auto_font_size_step_granularity =\n      text_attributes_->auto_font_size_step_granularity;\n  if (reset) {\n    text_attributes_->is_auto_font_size =\n        DefaultComputedStyle::DEFAULT_AUTO_FONT_SIZE;\n    text_attributes_->auto_font_size_min_size =\n        DefaultComputedStyle::DEFAULT_FLOAT;\n    text_attributes_->auto_font_size_max_size =\n        DefaultComputedStyle::DEFAULT_FLOAT;\n    text_attributes_->auto_font_size_step_granularity =\n        DefaultComputedStyle::DEFAULT_AUTO_FONT_SIZE_STEP_GRANULARITY;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsArray(), parser_configs_.enable_css_strict_mode,\n        tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDXAutoFontSize)\n            .c_str(),\n        tasm::ARRAY_TYPE)\n\n    auto arr = value.GetArray();\n    CSS_HANDLER_FAIL_IF_NOT(\n        arr->size() == 7, parser_configs_.enable_css_strict_mode,\n        tasm::SIZE_ERROR,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDXAutoFontSize)\n            .c_str(),\n        arr->size())\n\n    text_attributes_->is_auto_font_size = arr->get(0).Bool();\n    if (UNLIKELY(!CalculateCSSValueToFloat(\n            tasm::CSSValue(arr->get(1), static_cast<tasm::CSSValuePattern>(\n                                            arr->get(2).Number())),\n            text_attributes_->auto_font_size_min_size, length_context_,\n            parser_configs_, true))) {\n      return false;\n    }\n    if (UNLIKELY(!CalculateCSSValueToFloat(\n            tasm::CSSValue(arr->get(3), static_cast<tasm::CSSValuePattern>(\n                                            arr->get(4).Number())),\n            text_attributes_->auto_font_size_max_size, length_context_,\n            parser_configs_, true))) {\n      return false;\n    }\n    if (UNLIKELY(!CalculateCSSValueToFloat(\n            tasm::CSSValue(arr->get(5), static_cast<tasm::CSSValuePattern>(\n                                            arr->get(6).Number())),\n            text_attributes_->auto_font_size_step_granularity, length_context_,\n            parser_configs_, true))) {\n      return false;\n    }\n  }\n  return old_is_auto_font_size != text_attributes_->is_auto_font_size ||\n         old_auto_font_size_min_size !=\n             text_attributes_->auto_font_size_min_size ||\n         old_is_auto_font_size_max_size !=\n             text_attributes_->auto_font_size_max_size ||\n         old_auto_font_size_step_granularity !=\n             text_attributes_->auto_font_size_step_granularity;\n}\n\nbool ComputedCSSStyle::SetXAutoFontSizePresetSizes(const tasm::CSSValue& value,\n                                                   const bool reset) {\n  PrepareOptionalForTextAttributes();\n\n  auto old_value =\n      text_attributes_->auto_font_size_preset_sizes\n          ? *text_attributes_->auto_font_size_preset_sizes\n          : DefaultComputedStyle::DEFAULT_AUTO_FONT_SIZE_PRESET_SIZES();\n  if (reset) {\n    text_attributes_->auto_font_size_preset_sizes.reset();\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(value.IsArray(),\n                            parser_configs_.enable_css_strict_mode,\n                            tasm::TYPE_MUST_BE,\n                            tasm::CSSProperty::GetPropertyName(\n                                tasm::kPropertyIDXAutoFontSizePresetSizes)\n                                .c_str(),\n                            tasm::ARRAY_TYPE)\n\n    auto arr = value.GetArray();\n    CSS_HANDLER_FAIL_IF_NOT(arr->size() % 2 == 0,\n                            parser_configs_.enable_css_strict_mode,\n                            tasm::SIZE_ERROR,\n                            tasm::CSSProperty::GetPropertyName(\n                                tasm::kPropertyIDXAutoFontSizePresetSizes)\n                                .c_str(),\n                            arr->size())\n\n    base::InlineVector<float, 4> dest;\n    for (size_t i = 0; i < arr->size() / 2; i++) {\n      float preset_size;\n      if (UNLIKELY(!CalculateCSSValueToFloat(\n              tasm::CSSValue(arr->get(2 * i),\n                             static_cast<tasm::CSSValuePattern>(\n                                 arr->get(2 * i + 1).Number())),\n              preset_size, length_context_, parser_configs_, true))) {\n        return false;\n      }\n      dest.push_back(preset_size);\n    }\n    if (dest.size() > 0) {\n      text_attributes_->auto_font_size_preset_sizes = dest;\n    } else {\n      text_attributes_->auto_font_size_preset_sizes.reset();\n    }\n  }\n\n  return old_value !=\n         (text_attributes_->auto_font_size_preset_sizes\n              ? *text_attributes_->auto_font_size_preset_sizes\n              : DefaultComputedStyle::DEFAULT_AUTO_FONT_SIZE_PRESET_SIZES());\n}\n\nbool ComputedCSSStyle::SetPerspective(const tasm::CSSValue& value,\n                                      const bool reset) {\n  CSSStyleUtils::PrepareOptional(perspective_data_);\n  auto old_value = perspective_data_;\n  if (reset) {\n    perspective_data_.reset();\n  } else {\n    auto length =\n        CSSStyleUtils::ToLength(value, length_context_, parser_configs_);\n    if (length.second) {\n      perspective_data_->length_ = length.first;\n      perspective_data_->pattern_ = value.GetPattern();\n    }\n  }\n  return !(old_value == perspective_data_);\n}\n\nbool ComputedCSSStyle::SetLetterSpacing(const tasm::CSSValue& value,\n                                        const bool reset) {\n  PrepareOptionalForTextAttributes();\n  auto old_value = text_attributes_->letter_spacing;\n  if (reset) {\n    text_attributes_->letter_spacing =\n        DefaultComputedStyle::DEFAULT_LETTER_SPACING;\n  } else if (UNLIKELY(!CalculateCSSValueToFloat(\n                 value, text_attributes_->letter_spacing, length_context_,\n                 parser_configs_, true))) {\n    return false;\n  }\n  return base::FloatsNotEqual(text_attributes_->letter_spacing, old_value);\n}\n// transform\n\nbool ComputedCSSStyle::SetTransform(const tasm::CSSValue& value,\n                                    const bool reset) {\n  return CSSStyleUtils::ComputeTransform(value, reset, transform_raw_,\n                                         length_context_, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetTransformOrigin(const tasm::CSSValue& value,\n                                          const bool reset) {\n  auto old_value = transform_origin_\n                       ? *transform_origin_\n                       : DefaultComputedStyle::DEFAULT_TRANSFORM_ORIGIN();\n  if (reset) {\n    transform_origin_.reset();\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsArray(), parser_configs_.enable_css_strict_mode,\n        tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDTransformOrigin)\n            .c_str(),\n        tasm::ARRAY_TYPE)\n\n    auto arr = value.GetArray();\n    CSS_HANDLER_FAIL_IF_NOT(\n        arr->size() >= 2, parser_configs_.enable_css_strict_mode,\n        tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDTransformOrigin)\n            .c_str(),\n        tasm::ARRAY_TYPE)\n\n    CSSStyleUtils::PrepareOptional(transform_origin_);\n    auto parse_result_x = CSSStyleUtils::ToLength(\n        tasm::CSSValue(\n            arr->get(TransformOriginData::INDEX_X),\n            static_cast<CSSValuePattern>(\n                arr->get(TransformOriginData::INDEX_X_UNIT).Number())),\n        length_context_, parser_configs_);\n    if (parse_result_x.second) {\n      transform_origin_->x = parse_result_x.first;\n    }\n    if (arr->size() == 4) {\n      auto parse_result_y = CSSStyleUtils::ToLength(\n          tasm::CSSValue(\n              arr->get(TransformOriginData::INDEX_Y),\n              static_cast<CSSValuePattern>(\n                  arr->get(TransformOriginData::INDEX_Y_UNIT).Number())),\n          length_context_, parser_configs_);\n      if (parse_result_y.second) {\n        transform_origin_->y = parse_result_y.first;\n      }\n    }\n  }\n  return !(old_value == transform_origin_);\n}\n\n// animation\nbool ComputedCSSStyle::SetAnimation(const tasm::CSSValue& value,\n                                    const bool reset) {\n  auto old_value = animation_data_;\n  if (reset) {\n    animation_data_.reset();\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsArray() || value.IsMap(),\n        parser_configs_.enable_css_strict_mode, tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDAnimation).c_str(),\n        tasm::ARRAY_OR_MAP_TYPE)\n\n    if (!animation_data_) {\n      animation_data_.emplace();\n    }\n    animation_data_->clear();\n    if (value.IsArray()) {\n      auto group = value.GetArray();\n      for (size_t i = 0; i < group->size(); i++) {\n        if (animation_data_->size() < i + 1) {\n          animation_data_->push_back(AnimationData());\n        }\n        CSSStyleUtils::ComputeAnimation(group->get(i), animation_data_->at(i),\n                                        \"animation must is invalid.\",\n                                        parser_configs_);\n      }\n    } else {\n      animation_data_->push_back(AnimationData());\n      CSSStyleUtils::ComputeAnimation(\n          value.GetValue(), animation_data_->front(),\n          \"animation must is invalid.\", parser_configs_);\n    }\n  }\n  return old_value != animation_data_;\n}\n\nbool ComputedCSSStyle::SetAnimationName(const tasm::CSSValue& value,\n                                        const bool reset) {\n  auto reset_func = [](AnimationData& anim) { anim.name = base::String(); };\n  auto compute_func = [this](const lepus::Value& value, AnimationData& anim,\n                             bool reset) -> bool {\n    return CSSStyleUtils::ComputeStringStyle(\n        tasm::CSSValue(value, CSSValuePattern::STRING), reset, anim.name,\n        base::String(), \"animation-name must be a string!\",\n        this->parser_configs_);\n  };\n  return CSSStyleUtils::SetAnimationProperty(\n      animation_data_, value, reset_func, compute_func, reset, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAnimationTimingFunction(const tasm::CSSValue& value,\n                                                  const bool reset) {\n  auto reset_func = [](AnimationData& anim) { anim.timing_func.Reset(); };\n  auto compute_func = [this](const lepus::Value& value, AnimationData& anim,\n                             bool reset) -> bool {\n    return CSSStyleUtils::ComputeTimingFunction(value, reset, anim.timing_func,\n                                                this->parser_configs_);\n  };\n  return CSSStyleUtils::SetAnimationProperty(\n      animation_data_, value, reset_func, compute_func, reset, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAnimationIterationCount(const tasm::CSSValue& value,\n                                                  const bool reset) {\n  auto reset_func = [](AnimationData& anim) { anim.iteration_count = 1; };\n  auto compute_func = [this](const lepus::Value& value, AnimationData& anim,\n                             bool reset) -> bool {\n    return CSSStyleUtils::ComputeIntStyle(\n        tasm::CSSValue(value, CSSValuePattern::NUMBER), reset,\n        anim.iteration_count, 0, \"animation-iteration-count must be a number!\",\n        this->parser_configs_);\n  };\n  return CSSStyleUtils::SetAnimationProperty(\n      animation_data_, value, reset_func, compute_func, reset, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAnimationDuration(const tasm::CSSValue& value,\n                                            const bool reset) {\n  auto reset_func = [](AnimationData& anim) {\n    anim.duration = DefaultComputedStyle::DEFAULT_LONG;\n  };\n  auto compute_func = [this](const lepus::Value& value, AnimationData& anim,\n                             bool reset) -> bool {\n    return CSSStyleUtils::ComputeLongStyle(\n        tasm::CSSValue(value, CSSValuePattern::NUMBER), reset, anim.duration,\n        DefaultComputedStyle::DEFAULT_LONG,\n        \"animation-duration must be a long!\", this->parser_configs_);\n  };\n  return CSSStyleUtils::SetAnimationProperty(\n      animation_data_, value, reset_func, compute_func, reset, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAnimationFillMode(const tasm::CSSValue& value,\n                                            const bool reset) {\n  auto reset_func = [](AnimationData& anim) {\n    anim.fill_mode = AnimationFillModeType::kNone;\n  };\n  auto compute_func = [this](const lepus::Value& value, AnimationData& anim,\n                             bool reset) -> bool {\n    return CSSStyleUtils::ComputeEnumStyle<AnimationFillModeType>(\n        tasm::CSSValue(value, CSSValuePattern::ENUM), reset, anim.fill_mode,\n        AnimationFillModeType::kNone, \"animation-fill-mode must be a string!\",\n        this->parser_configs_);\n  };\n  return CSSStyleUtils::SetAnimationProperty(\n      animation_data_, value, reset_func, compute_func, reset, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAnimationDelay(const tasm::CSSValue& value,\n                                         const bool reset) {\n  auto reset_func = [](AnimationData& anim) {\n    anim.delay = DefaultComputedStyle::DEFAULT_LONG;\n  };\n  auto compute_func = [this](const lepus::Value& value, AnimationData& anim,\n                             bool reset) -> bool {\n    return CSSStyleUtils::ComputeLongStyle(\n        tasm::CSSValue(value, CSSValuePattern::NUMBER), reset, anim.delay,\n        DefaultComputedStyle::DEFAULT_LONG, \"animation-delay must be a float!\",\n        this->parser_configs_);\n  };\n  return CSSStyleUtils::SetAnimationProperty(\n      animation_data_, value, reset_func, compute_func, reset, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAnimationDirection(const tasm::CSSValue& value,\n                                             const bool reset) {\n  auto reset_func = [](AnimationData& anim) {\n    anim.direction = AnimationDirectionType::kNormal;\n  };\n  auto compute_func = [this](const lepus::Value& value, AnimationData& anim,\n                             bool reset) -> bool {\n    return CSSStyleUtils::ComputeEnumStyle<AnimationDirectionType>(\n        tasm::CSSValue(value, CSSValuePattern::ENUM), reset, anim.direction,\n        AnimationDirectionType::kNormal, \"animation-direction must be a !\",\n        this->parser_configs_);\n  };\n  return CSSStyleUtils::SetAnimationProperty(\n      animation_data_, value, reset_func, compute_func, reset, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetAnimationPlayState(const tasm::CSSValue& value,\n                                             const bool reset) {\n  auto reset_func = [](AnimationData& anim) {\n    anim.play_state = AnimationPlayStateType::kRunning;\n  };\n  auto compute_func = [this](const lepus::Value& value, AnimationData& anim,\n                             bool reset) -> bool {\n    return CSSStyleUtils::ComputeEnumStyle<AnimationPlayStateType>(\n        tasm::CSSValue(value, CSSValuePattern::ENUM), reset, anim.play_state,\n        AnimationPlayStateType::kRunning,\n        \"animation-play-state must be a enum!\", this->parser_configs_);\n  };\n  return CSSStyleUtils::SetAnimationProperty(\n      animation_data_, value, reset_func, compute_func, reset, parser_configs_);\n}\n\n// layout animation\n\nbool ComputedCSSStyle::SetLayoutAnimationCreateDelay(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return CSSStyleUtils::ComputeLongStyle(\n      value, reset, layout_animation_data_->create_ani.delay,\n      DefaultComputedStyle::DEFAULT_LONG,\n      \"layout-animation-create-delay must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationCreateDuration(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return CSSStyleUtils::ComputeLongStyle(\n      value, reset, layout_animation_data_->create_ani.duration,\n      DefaultComputedStyle::DEFAULT_LONG,\n      \"layout-animation-create-duration must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationCreateProperty(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return CSSStyleUtils::ComputeEnumStyle<starlight::AnimationPropertyType>(\n      value, reset, layout_animation_data_->create_ani.property,\n      starlight::AnimationPropertyType::kOpacity,\n      \"layout-animation-create-property must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationCreateTimingFunction(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return SetLayoutAnimationTimingFunctionInternal(\n      value, reset, layout_animation_data_->create_ani.timing_function,\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationUpdateDelay(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return CSSStyleUtils::ComputeLongStyle(\n      value, reset, layout_animation_data_->update_ani.delay,\n      DefaultComputedStyle::DEFAULT_LONG,\n      \"layout-animation-update-delay must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationUpdateDuration(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return CSSStyleUtils::ComputeLongStyle(\n      value, reset, layout_animation_data_->update_ani.duration,\n      DefaultComputedStyle::DEFAULT_LONG,\n      \"layout-animation-update-duration must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationUpdateTimingFunction(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return SetLayoutAnimationTimingFunctionInternal(\n      value, reset, layout_animation_data_->update_ani.timing_function,\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationDeleteDuration(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return CSSStyleUtils::ComputeLongStyle(\n      value, reset, layout_animation_data_->delete_ani.duration,\n      DefaultComputedStyle::DEFAULT_LONG,\n      \"layout-animation-delete-duration must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationDeleteDelay(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return CSSStyleUtils::ComputeLongStyle(\n      value, reset, layout_animation_data_->delete_ani.delay,\n      DefaultComputedStyle::DEFAULT_LONG,\n      \"layout-animation-delete-delay must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationDeleteProperty(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return CSSStyleUtils::ComputeEnumStyle<starlight::AnimationPropertyType>(\n      value, reset, layout_animation_data_->delete_ani.property,\n      starlight::AnimationPropertyType::kOpacity,\n      \"layout-animation-delete-property must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLayoutAnimationDeleteTimingFunction(\n    const tasm::CSSValue& value, const bool reset) {\n  CSSStyleUtils::PrepareOptional(layout_animation_data_);\n  return SetLayoutAnimationTimingFunctionInternal(\n      value, reset, layout_animation_data_->delete_ani.timing_function,\n      parser_configs_);\n}\n\n// TODO(baiqiang): Remove Transition shorthand setter\nbool ComputedCSSStyle::SetTransition(const tasm::CSSValue& value,\n                                     const bool reset) {\n  auto old_value = transition_data_;\n  if (reset) {\n    transition_data_.reset();\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsArray(), parser_configs_.enable_css_strict_mode,\n        tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDTransition).c_str(),\n        tasm::ARRAY_TYPE)\n\n    if (!transition_data_) {\n      transition_data_.emplace();\n    }\n    transition_data_->clear();\n    auto group = value.GetArray();\n    BASE_STATIC_STRING_DECL(kProperty, \"property\");\n    BASE_STATIC_STRING_DECL(kDuration, \"duration\");\n    BASE_STATIC_STRING_DECL(kTiming, \"timing\");\n    BASE_STATIC_STRING_DECL(kDelay, \"delay\");\n    for (size_t i = 0; i < group->size(); i++) {\n      if (transition_data_->size() < i + 1) {\n        transition_data_->push_back(TransitionData());\n      }\n      auto dict = group->get(i).Table();\n      (*transition_data_)[i].property = static_cast<AnimationPropertyType>(\n          dict->GetValue(kProperty).Number());\n      (*transition_data_)[i].duration = dict->GetValue(kDuration).Number();\n      if (dict->Contains(kTiming)) {\n        DCHECK(dict->GetValue(kTiming).IsArray());\n        CSSStyleUtils::ComputeTimingFunction(\n            dict->GetValue(kTiming).Array()->get(0), reset,\n            (*transition_data_)[i].timing_func, parser_configs_);\n      }\n      if (dict->Contains(kDelay)) {\n        (*transition_data_)[i].delay = dict->GetValue(kDelay).Number();\n      }\n    }\n  }\n  return old_value != transition_data_;\n}\n\nbool ComputedCSSStyle::SetTransitionProperty(const tasm::CSSValue& value,\n                                             const bool reset) {\n  auto reset_func = [](TransitionData& transition) {\n    transition.property = AnimationPropertyType::kNone;\n  };\n  auto compute_func = [](const lepus::Value& value, TransitionData& transition,\n                         bool reset) {\n    AnimationPropertyType old = transition.property;\n    transition.property = static_cast<AnimationPropertyType>(value.Number());\n    return old != transition.property;\n  };\n  return CSSStyleUtils::SetAnimationProperty(transition_data_, value,\n                                             reset_func, compute_func, reset,\n                                             parser_configs_);\n}\n\nbool ComputedCSSStyle::SetTransitionTimingFunction(const tasm::CSSValue& value,\n                                                   const bool reset) {\n  auto reset_func = [](TransitionData& transition) {\n    transition.timing_func.Reset();\n  };\n  auto compute_func = [this](const lepus::Value& value,\n                             TransitionData& transition, bool reset) {\n    return CSSStyleUtils::ComputeTimingFunction(\n        value, reset, transition.timing_func, this->parser_configs_);\n  };\n  return CSSStyleUtils::SetAnimationProperty(transition_data_, value,\n                                             reset_func, compute_func, reset,\n                                             parser_configs_);\n}\n\nbool ComputedCSSStyle::SetTransitionDuration(const tasm::CSSValue& value,\n                                             const bool reset) {\n  auto reset_func = [](TransitionData& transition) { transition.duration = 0; };\n  auto compute_func = [](const lepus::Value& value, TransitionData& transition,\n                         bool reset) {\n    long old = transition.duration;\n    transition.duration = value.Number();\n    return old != transition.duration;\n  };\n  return CSSStyleUtils::SetAnimationProperty(transition_data_, value,\n                                             reset_func, compute_func, reset,\n                                             parser_configs_);\n}\n\nbool ComputedCSSStyle::SetTransitionDelay(const tasm::CSSValue& value,\n                                          const bool reset) {\n  auto reset_func = [](TransitionData& transition) { transition.delay = 0; };\n  auto compute_func = [](const lepus::Value& value, TransitionData& transition,\n                         bool reset) {\n    long old = transition.delay;\n    transition.delay = value.Number();\n    return old != transition.delay;\n  };\n  return CSSStyleUtils::SetAnimationProperty(transition_data_, value,\n                                             reset_func, compute_func, reset,\n                                             parser_configs_);\n}\n\nbool ComputedCSSStyle::SetEnterTransitionName(const lynx::tasm::CSSValue& value,\n                                              bool reset) {\n  return CSSStyleUtils::ComputeHeroAnimation(\n      value, reset, enter_transition_data_,\n      \"enter-transition-name must is invalid.\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetExitTransitionName(lynx::tasm::CSSValue const& value,\n                                             bool reset) {\n  return CSSStyleUtils::ComputeHeroAnimation(\n      value, reset, exit_transition_data_,\n      \"exit-transition-name must is invalid.\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetPauseTransitionName(lynx::tasm::CSSValue const& value,\n                                              bool reset) {\n  return CSSStyleUtils::ComputeHeroAnimation(\n      value, reset, pause_transition_data_,\n      \"pause-transition-name must is invalid.\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetResumeTransitionName(\n    lynx::tasm::CSSValue const& value, bool reset) {\n  return CSSStyleUtils::ComputeHeroAnimation(\n      value, reset, resume_transition_data_,\n      \"resume-transition-name must is invalid.\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetLineSpacing(const tasm::CSSValue& value,\n                                      const bool reset) {\n  PrepareOptionalForTextAttributes();\n  auto old_value = text_attributes_->line_spacing;\n  if (reset) {\n    text_attributes_->line_spacing = DefaultComputedStyle::DEFAULT_LINE_SPACING;\n  } else if (UNLIKELY(!CalculateCSSValueToFloat(\n                 value, text_attributes_->line_spacing, length_context_,\n                 parser_configs_, true))) {\n    return false;\n  }\n  return base::FloatsNotEqual(text_attributes_->line_spacing, old_value);\n}\n\nbool ComputedCSSStyle::SetColor(const tasm::CSSValue& value, const bool reset) {\n  PrepareOptionalForTextAttributes();\n  auto old_value_color = text_attributes_->color;\n  auto old_value_gradient = text_attributes_->text_gradient;\n  if (reset) {\n    text_attributes_->color = DefaultColor::DEFAULT_TEXT_COLOR;\n    text_attributes_->text_gradient.reset();\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsNumber() || value.IsArray(),\n        parser_configs_.enable_css_strict_mode, tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDColor).c_str(),\n        tasm::ARRAY_OR_NUMBER_TYPE)\n\n    if (value.IsNumber()) {\n      text_attributes_->color = static_cast<uint32_t>(value.GetNumber());\n      text_attributes_->text_gradient.reset();\n    } else {\n      text_attributes_->color = DefaultColor::DEFAULT_TEXT_COLOR;\n      text_attributes_->text_gradient = value.GetValue();\n      text_attributes_->clone_text_gradient = false;\n    }\n  }\n  return old_value_color != text_attributes_->color ||\n         old_value_gradient != text_attributes_->text_gradient;\n}\n\nbool ComputedCSSStyle::SetXPlaceholderColor(const tasm::CSSValue& value,\n                                            const bool reset) {\n  PrepareOptionalForPlaceholderTextAttributes();\n  auto old_value_color = placeholder_text_attributes_->color;\n  auto old_value_gradient = placeholder_text_attributes_->text_gradient;\n  if (reset) {\n    placeholder_text_attributes_->color = DefaultColor::DEFAULT_TEXT_COLOR;\n    placeholder_text_attributes_->text_gradient.reset();\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsNumber() || value.IsArray(),\n        parser_configs_.enable_css_strict_mode, tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDXPlaceholderColor)\n            .c_str(),\n        tasm::ARRAY_OR_NUMBER_TYPE)\n\n    if (value.IsNumber()) {\n      placeholder_text_attributes_->color =\n          static_cast<uint32_t>(value.GetNumber());\n      placeholder_text_attributes_->text_gradient.reset();\n    } else {\n      placeholder_text_attributes_->color = DefaultColor::DEFAULT_TEXT_COLOR;\n      placeholder_text_attributes_->text_gradient = value.GetValue();\n    }\n  }\n  return old_value_color != placeholder_text_attributes_->color ||\n         old_value_gradient != placeholder_text_attributes_->text_gradient;\n}\n\nbool ComputedCSSStyle::SetBackground(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetBackgroundColor(const tasm::CSSValue& value,\n                                          const bool reset) {\n  CSSStyleUtils::PrepareOptional(background_data_);\n  return CSSStyleUtils::ComputeUIntStyle(\n      value, reset, background_data_->color, DefaultColor::DEFAULT_COLOR,\n      \"background-color must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBackgroundImage(const tasm::CSSValue& value,\n                                          const bool reset) {\n  return SetBackgroundOrMaskImage(background_data_, value, reset);\n}\n\nbool ComputedCSSStyle::SetBackgroundSize(const tasm::CSSValue& value,\n                                         const bool reset) {\n  return SetBackgroundOrMaskSize(background_data_, length_context_,\n                                 parser_configs_, value, reset);\n}\n\nbool ComputedCSSStyle::SetBackgroundClip(const lynx::tasm::CSSValue& value,\n                                         bool reset) {\n  return SetBackgroundOrMaskClip(background_data_, value, reset);\n}\n\nbool ComputedCSSStyle::SetBackgroundPosition(const tasm::CSSValue& value,\n                                             const bool reset) {\n  return SetBackgroundOrMaskPosition(background_data_, length_context_,\n                                     parser_configs_, value, reset);\n}\n\nbool ComputedCSSStyle::SetBackgroundRepeat(const tasm::CSSValue& value,\n                                           const bool reset) {\n  return SetBackgroundOrMaskRepeat(background_data_, value, reset);\n}\n\nbool ComputedCSSStyle::SetBackgroundOrigin(const tasm::CSSValue& value,\n                                           const bool reset) {\n  return SetBackgroundOrMaskOrigin(background_data_, value, reset);\n}\n\nbool ComputedCSSStyle::SetFilter(const tasm::CSSValue& value,\n                                 const bool reset) {\n  if (!value.IsArray()) {\n    // CSSParser enabled, only grayscale supported.\n    auto last_filter = filter_;\n    if (reset) {\n      filter_.reset();\n    } else {\n      CSSStyleUtils::PrepareOptional(filter_);\n      (*filter_).type = FilterType::kGrayscale;\n      (*filter_).amount =\n          NLength::MakePercentageNLength(value.GetNumber() * 100);\n    }\n    return last_filter != filter_;\n  }\n\n  return CSSStyleUtils::ComputeFilter(value, reset, filter_, length_context_,\n                                      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderTopColor(const tasm::CSSValue& value,\n                                         const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_);\n  return CSSStyleUtils::ComputeUIntStyle(\n      value, reset,\n      layout_computed_style_.surround_data_.border_data_->color_top,\n      DefaultColor::DEFAULT_BORDER_COLOR, \"border-top-color must be a number\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderRightColor(const tasm::CSSValue& value,\n                                           const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_);\n  return CSSStyleUtils::ComputeUIntStyle(\n      value, reset,\n      layout_computed_style_.surround_data_.border_data_->color_right,\n      DefaultColor::DEFAULT_BORDER_COLOR, \"border-right-color must be a number\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderBottomColor(const tasm::CSSValue& value,\n                                            const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_);\n  return CSSStyleUtils::ComputeUIntStyle(\n      value, reset,\n      layout_computed_style_.surround_data_.border_data_->color_bottom,\n      DefaultColor::DEFAULT_BORDER_COLOR,\n      \"border-bottom-color must be a number\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderLeftColor(const tasm::CSSValue& value,\n                                          const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_);\n  return CSSStyleUtils::ComputeUIntStyle(\n      value, reset,\n      layout_computed_style_.surround_data_.border_data_->color_left,\n      DefaultColor::DEFAULT_BORDER_COLOR, \"border-left-color must be a number\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderTopStyle(const tasm::CSSValue& value,\n                                         const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_,\n      css_align_with_legacy_w3c_);\n  return CSSStyleUtils::ComputeEnumStyle<BorderStyleType>(\n      value, reset,\n      layout_computed_style_.surround_data_.border_data_->style_top,\n      DEFAULT_CSS_VALUE(css_align_with_legacy_w3c_, BORDER_STYLE),\n      \"border-top-style must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderRightStyle(const tasm::CSSValue& value,\n                                           const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_,\n      css_align_with_legacy_w3c_);\n  return CSSStyleUtils::ComputeEnumStyle<BorderStyleType>(\n      value, reset,\n      layout_computed_style_.surround_data_.border_data_->style_right,\n      DEFAULT_CSS_VALUE(css_align_with_legacy_w3c_, BORDER_STYLE),\n      \"border-right-style must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderBottomStyle(const tasm::CSSValue& value,\n                                            const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_,\n      css_align_with_legacy_w3c_);\n  return CSSStyleUtils::ComputeEnumStyle<BorderStyleType>(\n      value, reset,\n      layout_computed_style_.surround_data_.border_data_->style_bottom,\n      DEFAULT_CSS_VALUE(css_align_with_legacy_w3c_, BORDER_STYLE),\n      \"border-bottom-style must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderLeftStyle(const tasm::CSSValue& value,\n                                          const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_,\n      css_align_with_legacy_w3c_);\n  return CSSStyleUtils::ComputeEnumStyle<BorderStyleType>(\n      value, reset,\n      layout_computed_style_.surround_data_.border_data_->style_left,\n      DEFAULT_CSS_VALUE(css_align_with_legacy_w3c_, BORDER_STYLE),\n      \"border-left-style must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetOutlineColor(const lynx::tasm::CSSValue& value,\n                                       bool reset) {\n  CSSStyleUtils::PrepareOptional(outline_);\n  return CSSStyleUtils::ComputeUIntStyle(\n      value, reset, outline_->color, DefaultColor::DEFAULT_OUTLINE_COLOR,\n      \"outline-color must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetOutlineStyle(const lynx::tasm::CSSValue& value,\n                                       bool reset) {\n  CSSStyleUtils::PrepareOptional(outline_);\n  return CSSStyleUtils::ComputeEnumStyle(\n      value, reset, outline_->style,\n      DEFAULT_CSS_VALUE(css_align_with_legacy_w3c_, BORDER_STYLE),\n      \"outline-style must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetOutlineWidth(const lynx::tasm::CSSValue& value,\n                                       bool reset) {\n  CSSStyleUtils::PrepareOptional(outline_);\n  float old_value = outline_->width;\n  if (reset) {\n    outline_->width = DefaultComputedStyle::DEFAULT_FLOAT;\n  } else {\n    if (UNLIKELY(!CSSStyleUtils::CalculateLength(\n            value, outline_->width, length_context_, parser_configs_))) {\n      return false;\n    }\n  }\n  return base::FloatsNotEqual(old_value, outline_->width);\n}\n\nbool ComputedCSSStyle::SetVisibility(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<VisibilityType>(\n      value, reset, visibility_, DefaultComputedStyle::DEFAULT_VISIBILITY,\n      \"visibility must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBoxShadow(const tasm::CSSValue& value,\n                                    const bool reset) {\n  return CSSStyleUtils::ComputeShadowStyle(value, reset, box_shadow_,\n                                           length_context_, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderColor(const tasm::CSSValue& value,\n                                      const bool reset) {\n  return tasm::UnitHandler::CSSWarning(false,\n                                       parser_configs_.enable_css_strict_mode,\n                                       \"Set Border Color will never be called\");\n}\n\nbool ComputedCSSStyle::SetFontFamily(const tasm::CSSValue& value,\n                                     const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeStringStyle(\n      value, reset, text_attributes_->font_family, base::String(),\n      \"font family must be a string!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetXPlaceholderFontFamily(const tasm::CSSValue& value,\n                                                 const bool reset) {\n  PrepareOptionalForPlaceholderTextAttributes();\n  return CSSStyleUtils::ComputeStringStyle(\n      value, reset, placeholder_text_attributes_->font_family, base::String(),\n      \"font family must be a string!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetCaretColor(const lynx::tasm::CSSValue& value,\n                                     bool reset) {\n  return CSSStyleUtils::ComputeStringStyle(\n      value, reset, caret_color_, base::String(),\n      \"caret-color must be a string!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetTextShadow(const tasm::CSSValue& value,\n                                     const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeShadowStyle(value, reset,\n                                           text_attributes_->text_shadow,\n                                           length_context_, parser_configs_);\n}\n\nbool ComputedCSSStyle::SetWhiteSpace(const tasm::CSSValue& value,\n                                     const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<WhiteSpaceType>(\n      value, reset, text_attributes_->white_space,\n      DefaultComputedStyle::DEFAULT_WHITE_SPACE, \"white-space must be an enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetFontWeight(const tasm::CSSValue& value,\n                                     const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<starlight::FontWeightType>(\n      value, reset, text_attributes_->font_weight,\n      DefaultComputedStyle::DEFAULT_FONT_WEIGHT, \"font weight must be an enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetXPlaceholderFontWeight(const tasm::CSSValue& value,\n                                                 const bool reset) {\n  PrepareOptionalForPlaceholderTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<starlight::FontWeightType>(\n      value, reset, placeholder_text_attributes_->font_weight,\n      DefaultComputedStyle::DEFAULT_FONT_WEIGHT, \"font weight must be an enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetWordBreak(const tasm::CSSValue& value,\n                                    const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<WordBreakType>(\n      value, reset, text_attributes_->word_break,\n      DefaultComputedStyle::DEFAULT_WORD_BREAK, \"word-break must be an enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetFontStyle(const tasm::CSSValue& value,\n                                    const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<starlight::FontStyleType>(\n      value, reset, text_attributes_->font_style,\n      DefaultComputedStyle::DEFAULT_FONT_STYLE, \"font style must be an enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetXPlaceholderFontStyle(const tasm::CSSValue& value,\n                                                const bool reset) {\n  PrepareOptionalForPlaceholderTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<starlight::FontStyleType>(\n      value, reset, placeholder_text_attributes_->font_style,\n      DefaultComputedStyle::DEFAULT_FONT_STYLE, \"font style must be an enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetTextAlign(const tasm::CSSValue& value,\n                                    const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<starlight::TextAlignType>(\n      value, reset, text_attributes_->text_align,\n      DefaultComputedStyle::DEFAULT_TEXT_ALIGN, \"text align must be an enum!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetTextOverflow(const tasm::CSSValue& value,\n                                       const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<starlight::TextOverflowType>(\n      value, reset, text_attributes_->text_overflow,\n      DefaultComputedStyle::DEFAULT_TEXT_OVERFLOW,\n      \"text overflow must be an enum!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetTextDecoration(const tasm::CSSValue& value,\n                                         const bool reset) {\n  PrepareOptionalForTextAttributes();\n\n  const auto get_flags = [this]() {\n    uint32_t flags = 0;\n    if (text_attributes_->underline_decoration) {\n      flags |= static_cast<int>(TextDecorationType::kUnderLine);\n    }\n    if (text_attributes_->line_through_decoration) {\n      flags |= static_cast<int>(TextDecorationType::kLineThrough);\n    }\n    if (text_attributes_->text_decoration_style) {\n      flags |= static_cast<uint32_t>(text_attributes_->text_decoration_style);\n    }\n    return flags;\n  };\n  uint32_t old_flags = get_flags();\n  auto old_color = text_attributes_->text_decoration_color;\n\n  if (reset) {\n    text_attributes_->underline_decoration =\n        DefaultComputedStyle::DEFAULT_BOOLEAN;\n    text_attributes_->line_through_decoration =\n        DefaultComputedStyle::DEFAULT_BOOLEAN;\n    text_attributes_->text_decoration_style =\n        static_cast<uint8_t>(TextDecorationType::kSolid);\n    text_attributes_->text_decoration_color = DefaultColor::DEFAULT_COLOR;\n  } else {\n    text_attributes_->underline_decoration =\n        DefaultComputedStyle::DEFAULT_BOOLEAN;\n    text_attributes_->line_through_decoration =\n        DefaultComputedStyle::DEFAULT_BOOLEAN;\n    text_attributes_->text_decoration_style =\n        DefaultComputedStyle::DEFAULT_TEXT_DECORATION_STYLE;\n    text_attributes_->text_decoration_color = DefaultColor::DEFAULT_COLOR;\n    auto result = value.GetArray();\n    for (size_t i = 0; i < result->size(); i++) {\n      uint32_t decoration = static_cast<uint32_t>(result->get(i).Number());\n      if (decoration & static_cast<uint32_t>(TextDecorationType::kColor)) {\n        text_attributes_->text_decoration_color =\n            static_cast<uint32_t>(result->get(i + 1).Number());\n        i++;\n        continue;\n      }\n      if (decoration & static_cast<uint32_t>(TextDecorationType::kUnderLine)) {\n        text_attributes_->underline_decoration = true;\n      } else if (decoration &\n                 static_cast<uint32_t>(TextDecorationType::kLineThrough)) {\n        text_attributes_->line_through_decoration = true;\n      } else if (decoration) {\n        text_attributes_->text_decoration_style =\n            static_cast<uint8_t>(decoration);\n      }\n    }\n  }\n  uint32_t new_flags = get_flags();\n  auto new_color = text_attributes_->text_decoration_color;\n  return (old_flags != new_flags) || (old_color != new_color);\n}\n\nbool ComputedCSSStyle::SetTextDecorationColor(const tasm::CSSValue& value,\n                                              const bool reset) {\n  PrepareOptionalForTextAttributes();\n  auto old_value_color = text_attributes_->decoration_color;\n  if (reset) {\n    text_attributes_->decoration_color = DefaultColor::DEFAULT_COLOR;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsNumber(), parser_configs_.enable_css_strict_mode,\n        tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDTextDecorationColor)\n            .c_str(),\n        tasm::NUMBER_TYPE)\n\n    if (value.IsNumber()) {\n      text_attributes_->decoration_color =\n          static_cast<unsigned int>(value.GetNumber());\n    }\n  }\n  return old_value_color != text_attributes_->decoration_color;\n}\n\nbool ComputedCSSStyle::SetZIndex(const tasm::CSSValue& value,\n                                 const bool reset) {\n  // If not enable z-index, return false default.\n  if (!enable_z_index_) {\n    return false;\n  }\n\n  has_z_index_ = !reset;\n\n  // TODO(songshourui.null): A z-index can be 0, but an initial value of 0\n  // currently means no z-index is set, which conflicts with a developer\n  // explicitly setting it to 0. To optimize this, consider using an optional to\n  // store the z-index. If the optional has no value, it would mean z-index is\n  // not set. This change would also require modifications at the platform\n  // layer.\n  return CSSStyleUtils::ComputeIntStyle(\n      value, reset, z_index_, 0, \"z-index must be a number!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetBorderRadius(const tasm::CSSValue& value,\n                                       const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_);\n  auto old_value = layout_computed_style_.surround_data_.border_data_;\n  if (reset) {\n    layout_computed_style_.surround_data_.border_data_->radius_x_top_left =\n        DefaultLayoutStyle::SL_DEFAULT_RADIUS();\n    layout_computed_style_.surround_data_.border_data_->radius_y_top_left =\n        DefaultLayoutStyle::SL_DEFAULT_RADIUS();\n    layout_computed_style_.surround_data_.border_data_->radius_x_top_right =\n        DefaultLayoutStyle::SL_DEFAULT_RADIUS();\n    layout_computed_style_.surround_data_.border_data_->radius_y_top_right =\n        DefaultLayoutStyle::SL_DEFAULT_RADIUS();\n    layout_computed_style_.surround_data_.border_data_->radius_x_bottom_left =\n        DefaultLayoutStyle::SL_DEFAULT_RADIUS();\n    layout_computed_style_.surround_data_.border_data_->radius_y_bottom_left =\n        DefaultLayoutStyle::SL_DEFAULT_RADIUS();\n    layout_computed_style_.surround_data_.border_data_->radius_x_bottom_right =\n        DefaultLayoutStyle::SL_DEFAULT_RADIUS();\n    layout_computed_style_.surround_data_.border_data_->radius_y_bottom_right =\n        DefaultLayoutStyle::SL_DEFAULT_RADIUS();\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsArray(), parser_configs_.enable_css_strict_mode,\n        tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDBorderRadius)\n            .c_str(),\n        tasm::ARRAY_TYPE)\n\n    auto container = value.GetArray();\n    for (int i = 0; i < 4; i++) {\n      auto parse_result = CSSStyleUtils::ToLength(\n          tasm::CSSValue(\n              container->get(i * 4),\n              static_cast<CSSValuePattern>(container->get(i * 4 + 1).Number())),\n          length_context_, parser_configs_);\n\n      CSS_HANDLER_FAIL_IF_NOT(\n          parse_result.second, parser_configs_.enable_css_strict_mode,\n          tasm::SET_PROPERTY_ERROR,\n          tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDBorderRadius)\n              .c_str())\n\n      if (i == 0) {\n        layout_computed_style_.surround_data_.border_data_->radius_x_top_left =\n            std::move(parse_result.first);\n      } else if (i == 1) {\n        layout_computed_style_.surround_data_.border_data_->radius_x_top_right =\n            std::move(parse_result.first);\n      } else if (i == 2) {\n        layout_computed_style_.surround_data_.border_data_\n            ->radius_x_bottom_right = std::move(parse_result.first);\n      } else if (i == 3) {\n        layout_computed_style_.surround_data_.border_data_\n            ->radius_x_bottom_left = std::move(parse_result.first);\n      }\n\n      parse_result = CSSStyleUtils::ToLength(\n          tasm::CSSValue(\n              container->get(i * 4 + 2),\n              static_cast<CSSValuePattern>(container->get(i * 4 + 3).Number())),\n          length_context_, parser_configs_);\n\n      CSS_HANDLER_FAIL_IF_NOT(\n          parse_result.second, parser_configs_.enable_css_strict_mode,\n          tasm::SET_PROPERTY_ERROR,\n          tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDBorderRadius)\n              .c_str())\n\n      if (i == 0) {\n        layout_computed_style_.surround_data_.border_data_->radius_y_top_left =\n            std::move(parse_result.first);\n      } else if (i == 1) {\n        layout_computed_style_.surround_data_.border_data_->radius_y_top_right =\n            std::move(parse_result.first);\n      } else if (i == 2) {\n        layout_computed_style_.surround_data_.border_data_\n            ->radius_y_bottom_right = std::move(parse_result.first);\n      } else if (i == 3) {\n        layout_computed_style_.surround_data_.border_data_\n            ->radius_y_bottom_left = std::move(parse_result.first);\n      }\n    }\n  }\n  return old_value->radius_x_top_left != layout_computed_style_.surround_data_\n                                             .border_data_->radius_x_top_left ||\n         old_value->radius_y_top_left != layout_computed_style_.surround_data_\n                                             .border_data_->radius_y_top_left ||\n         old_value->radius_x_top_right !=\n             layout_computed_style_.surround_data_.border_data_\n                 ->radius_x_top_right ||\n         old_value->radius_y_top_right !=\n             layout_computed_style_.surround_data_.border_data_\n                 ->radius_y_top_right ||\n         old_value->radius_x_bottom_left !=\n             layout_computed_style_.surround_data_.border_data_\n                 ->radius_x_bottom_left ||\n         old_value->radius_y_bottom_left !=\n             layout_computed_style_.surround_data_.border_data_\n                 ->radius_y_bottom_left ||\n         old_value->radius_x_bottom_right !=\n             layout_computed_style_.surround_data_.border_data_\n                 ->radius_x_bottom_right ||\n         old_value->radius_y_bottom_right !=\n             layout_computed_style_.surround_data_.border_data_\n                 ->radius_y_bottom_right;\n}\n\nbool ComputedCSSStyle::SetBorderTopLeftRadius(const tasm::CSSValue& value,\n                                              const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_);\n  auto old_value = layout_computed_style_.surround_data_.border_data_;\n\n  return SetBorderRadiusHelper(layout_computed_style_.surround_data_\n                                   .border_data_->radius_x_top_left,\n                               layout_computed_style_.surround_data_\n                                   .border_data_->radius_y_top_left,\n                               length_context_,\n                               tasm::kPropertyIDBorderTopLeftRadius, value,\n                               reset, parser_configs_) &&\n         (old_value->radius_x_top_left !=\n              layout_computed_style_.surround_data_.border_data_\n                  ->radius_x_top_left ||\n          old_value->radius_y_top_left != layout_computed_style_.surround_data_\n                                              .border_data_->radius_y_top_left);\n}\n\nbool ComputedCSSStyle::SetBorderTopRightRadius(const tasm::CSSValue& value,\n                                               const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_);\n  auto old_value = layout_computed_style_.surround_data_.border_data_;\n\n  return SetBorderRadiusHelper(layout_computed_style_.surround_data_\n                                   .border_data_->radius_x_top_right,\n                               layout_computed_style_.surround_data_\n                                   .border_data_->radius_y_top_right,\n                               length_context_,\n                               tasm::kPropertyIDBorderTopRightRadius, value,\n                               reset, parser_configs_) &&\n         (old_value->radius_x_top_right !=\n              layout_computed_style_.surround_data_.border_data_\n                  ->radius_x_top_right ||\n          old_value->radius_y_top_right !=\n              layout_computed_style_.surround_data_.border_data_\n                  ->radius_y_top_right);\n}\n\nbool ComputedCSSStyle::SetBorderBottomRightRadius(const tasm::CSSValue& value,\n                                                  const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_);\n  auto old_value = layout_computed_style_.surround_data_.border_data_;\n\n  return SetBorderRadiusHelper(layout_computed_style_.surround_data_\n                                   .border_data_->radius_x_bottom_right,\n                               layout_computed_style_.surround_data_\n                                   .border_data_->radius_y_bottom_right,\n                               length_context_,\n                               tasm::kPropertyIDBorderBottomRightRadius, value,\n                               reset, parser_configs_) &&\n         (old_value->radius_x_bottom_right !=\n              layout_computed_style_.surround_data_.border_data_\n                  ->radius_x_bottom_right ||\n          old_value->radius_y_bottom_right !=\n              layout_computed_style_.surround_data_.border_data_\n                  ->radius_y_bottom_right);\n}\n\nbool ComputedCSSStyle::SetBorderBottomLeftRadius(const tasm::CSSValue& value,\n                                                 const bool reset) {\n  CSSStyleUtils::PrepareOptional(\n      layout_computed_style_.surround_data_.border_data_);\n  auto old_value = layout_computed_style_.surround_data_.border_data_;\n\n  return SetBorderRadiusHelper(layout_computed_style_.surround_data_\n                                   .border_data_->radius_x_bottom_left,\n                               layout_computed_style_.surround_data_\n                                   .border_data_->radius_y_bottom_left,\n                               length_context_,\n                               tasm::kPropertyIDBorderBottomLeftRadius, value,\n                               reset, parser_configs_) &&\n         (old_value->radius_x_bottom_left !=\n              layout_computed_style_.surround_data_.border_data_\n                  ->radius_x_bottom_left ||\n          old_value->radius_y_bottom_left !=\n              layout_computed_style_.surround_data_.border_data_\n                  ->radius_y_bottom_left);\n}\n\nbool ComputedCSSStyle::SetBorderStyle(const tasm::CSSValue& value,\n                                      const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetAdaptFontSize(const tasm::CSSValue& value,\n                                        const bool reset) {\n  return CSSStyleUtils::ComputeStringStyle(\n      value, reset, adapt_font_size_, base::String(),\n      \"adapt-font-size must be a string!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetOutline(const tasm::CSSValue& value,\n                                  const bool reset) {\n  return tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n}\n\nbool ComputedCSSStyle::SetVerticalAlign(const tasm::CSSValue& value,\n                                        const bool reset) {\n  PrepareOptionalForTextAttributes();\n  auto old_value_type = text_attributes_->vertical_align;\n  auto old_value_length = text_attributes_->vertical_align_length;\n  if (reset) {\n    text_attributes_->vertical_align =\n        DefaultComputedStyle::DEFAULT_VERTICAL_ALIGN;\n    text_attributes_->vertical_align_length =\n        DefaultComputedStyle::DEFAULT_FLOAT;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(\n        value.IsArray(), parser_configs_.enable_css_strict_mode,\n        tasm::TYPE_MUST_BE,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDVerticalAlign)\n            .c_str(),\n        tasm::ARRAY_TYPE)\n\n    auto arr = value.GetArray();\n    CSS_HANDLER_FAIL_IF_NOT(\n        arr->size() == 4, parser_configs_.enable_css_strict_mode,\n        tasm::SIZE_ERROR,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDVerticalAlign)\n            .c_str(),\n        arr->size())\n\n    CSS_HANDLER_FAIL_IF_NOT(\n        static_cast<CSSValuePattern>(arr->get(1).Number()) ==\n            CSSValuePattern::ENUM,\n        parser_configs_.enable_css_strict_mode, tasm::TYPE_ERROR,\n        tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDVerticalAlign)\n            .c_str())\n\n    text_attributes_->vertical_align =\n        static_cast<starlight::VerticalAlignType>(arr->get(0).Number());\n    if (text_attributes_->vertical_align == VerticalAlignType::kLength) {\n      auto pattern = static_cast<tasm::CSSValuePattern>(arr->get(3).Number());\n      std::pair<NLength, bool> result =\n          CSSStyleUtils::ToLength(tasm::CSSValue(arr->get(2), pattern),\n                                  length_context_, parser_configs_);\n      CSS_HANDLER_FAIL_IF_NOT(\n          (result.second && result.first.IsUnit()),\n          parser_configs_.enable_css_strict_mode, tasm::TYPE_ERROR,\n          tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDVerticalAlign)\n              .c_str())\n\n      text_attributes_->vertical_align_length = result.first.GetRawValue();\n    } else if (text_attributes_->vertical_align ==\n               VerticalAlignType::kPercent) {\n      CSS_HANDLER_FAIL_IF_NOT(\n          arr->get(2).IsNumber(), parser_configs_.enable_css_strict_mode,\n          tasm::TYPE_ERROR,\n          tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDVerticalAlign)\n              .c_str())\n\n      text_attributes_->vertical_align_length = arr->get(2).Number();\n    } else {\n      text_attributes_->vertical_align_length = 0.0f;\n    }\n  }\n  return old_value_type != text_attributes_->vertical_align ||\n         old_value_length != text_attributes_->vertical_align_length;\n}\n\nbool ComputedCSSStyle::SetContent(const tasm::CSSValue& value,\n                                  const bool reset) {\n  return CSSStyleUtils::ComputeStringStyle(\n      value, reset, content_, base::String(), \"content must be a string!\",\n      parser_configs_);\n}\n\nbool ComputedCSSStyle::SetListMainAxisGap(const tasm::CSSValue& value,\n                                          const bool reset) {\n  return CSSStyleUtils::ComputeLengthStyle(\n      value, reset, length_context_,\n      layout_computed_style_.linear_data_.Access()->list_main_axis_gap_,\n      DefaultLayoutStyle::SL_DEFAULT_ZEROLENGTH(), parser_configs_);\n}\n\nbool ComputedCSSStyle::SetListCrossAxisGap(const tasm::CSSValue& value,\n                                           const bool reset) {\n  return CSSStyleUtils::ComputeLengthStyle(\n      value, reset, length_context_,\n      layout_computed_style_.linear_data_.Access()->list_cross_axis_gap_,\n      DefaultLayoutStyle::SL_DEFAULT_ZEROLENGTH(), parser_configs_);\n}\n\nbool ComputedCSSStyle::SetClipPath(const tasm::CSSValue& value,\n                                   const bool reset) {\n  // ref to old array\n  fml::RefPtr<lepus::CArray> last_path = clip_path_;\n  clip_path_ = lepus::CArray::Create();\n\n  fml::RefPtr<lepus::CArray> raw_array;\n  BasicShapeType type = BasicShapeType::kUnknown;\n  if (reset || !value.IsArray() || value.GetArray()->size() == 0) {\n    // if not reset, it means value is invalid and launch warning.\n    LynxWarning(reset, error::E_CSS_COMPUTED_CSS_VALUE_UNKNOWN_SETTER,\n                \"clip-path must be an array\")\n  } else {\n    raw_array = value.GetArray();\n    type = static_cast<starlight::BasicShapeType>(raw_array->get(0).Number());\n  }\n\n  switch (type) {\n    case BasicShapeType::kUnknown:\n      // Unknown type, reset the clip-path.\n      break;\n    case BasicShapeType::kCircle:\n      if (raw_array->size() != 7) {\n        LOGW(\"Error in parsing basic shape circle.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeBasicShapeCircle(raw_array, reset, clip_path_,\n                                             length_context_, parser_configs_);\n      break;\n    case BasicShapeType::kEllipse:\n      if (raw_array->size() != 9) {\n        LOGW(\"Error in parsing basic shape circle.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeBasicShapeEllipse(raw_array, reset, clip_path_,\n                                              length_context_, parser_configs_);\n      break;\n    case BasicShapeType::kPath:\n      if (raw_array->size() != 2) {\n        LOGW(\"Error in parsing basic shape path.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeBasicShapePath(raw_array, reset, clip_path_);\n      break;\n    case BasicShapeType::kSuperEllipse:\n      if (raw_array->size() != 11) {\n        LOGW(\"Error in parsing super ellipse.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeSuperEllipse(raw_array, reset, clip_path_,\n                                         length_context_, parser_configs_);\n      break;\n    case BasicShapeType::kInset:\n      constexpr int INSET_ARRAY_LENGTH_RECT = 9;\n      constexpr int INSET_ARRAY_LENGTH_ROUND = 25;\n      constexpr int INSET_ARRAY_LENGTH_SUPER_ELLIPSE = 27;\n      if (raw_array->size() != INSET_ARRAY_LENGTH_RECT &&\n          raw_array->size() != INSET_ARRAY_LENGTH_ROUND &&\n          raw_array->size() != INSET_ARRAY_LENGTH_SUPER_ELLIPSE) {\n        LOGW(\"Error in parsing basic shape inset.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeBasicShapeInset(raw_array, reset, clip_path_,\n                                            length_context_, parser_configs_);\n      break;\n  }\n  // Check last path equals to current path.\n  return last_path.get() == nullptr || *last_path != *clip_path_;\n}\n\nbool ComputedCSSStyle::SetOffsetDistance(const tasm::CSSValue& value,\n                                         const bool reset) {\n  return CSSStyleUtils::ComputeFloatStyle(\n      value, reset, offset_distance_,\n      DefaultComputedStyle::DEFAULT_OFFSET_DISTANCE,\n      \"offset_distance must be a float!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetOffsetRotate(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return CSSStyleUtils::ComputeFloatStyle(\n      value, reset, offset_rotate_, DefaultComputedStyle::DEFAULT_OFFSET_ROTATE,\n      \"offset_rotate must be a float!\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetOffsetPath(const tasm::CSSValue& value,\n                                     const bool reset) {\n  // ref to old array\n  fml::RefPtr<lepus::CArray> last_path = offset_path_;\n  offset_path_ = lepus::CArray::Create();\n\n  fml::RefPtr<lepus::CArray> raw_array;\n  BasicShapeType type = BasicShapeType::kUnknown;\n  if (reset || !value.IsArray() || value.GetArray()->size() == 0) {\n    // if not reset, it means value is invalid and launch warning.\n    LynxWarning(reset, error::E_CSS_COMPUTED_CSS_VALUE_UNKNOWN_SETTER,\n                \"clip-path must be an array\")\n  } else {\n    raw_array = value.GetArray();\n    type = static_cast<starlight::BasicShapeType>(raw_array->get(0).Number());\n  }\n\n  switch (type) {\n    case BasicShapeType::kUnknown:\n      // Unknown type, reset the clip-path.\n      break;\n    case BasicShapeType::kCircle:\n      if (raw_array->size() != 7) {\n        LOGW(\"Error in parsing basic shape circle.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeBasicShapeCircle(raw_array, reset, offset_path_,\n                                             length_context_, parser_configs_);\n      break;\n    case BasicShapeType::kEllipse:\n      if (raw_array->size() != 9) {\n        LOGW(\"Error in parsing basic shape circle.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeBasicShapeEllipse(raw_array, reset, offset_path_,\n                                              length_context_, parser_configs_);\n      break;\n    case BasicShapeType::kPath:\n      if (raw_array->size() != 2) {\n        LOGW(\"Error in parsing basic shape path.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeBasicShapePath(raw_array, reset, offset_path_);\n      break;\n    case BasicShapeType::kSuperEllipse:\n      if (raw_array->size() != 11) {\n        LOGW(\"Error in parsing super ellipse.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeSuperEllipse(raw_array, reset, offset_path_,\n                                         length_context_, parser_configs_);\n      break;\n    case BasicShapeType::kInset:\n      constexpr int INSET_ARRAY_LENGTH_RECT = 9;\n      constexpr int INSET_ARRAY_LENGTH_ROUND = 25;\n      constexpr int INSET_ARRAY_LENGTH_SUPER_ELLIPSE = 27;\n      if (raw_array->size() != INSET_ARRAY_LENGTH_RECT &&\n          raw_array->size() != INSET_ARRAY_LENGTH_ROUND &&\n          raw_array->size() != INSET_ARRAY_LENGTH_SUPER_ELLIPSE) {\n        LOGW(\"Error in parsing basic shape inset.\");\n        return false;\n      }\n      CSSStyleUtils::ComputeBasicShapeInset(raw_array, reset, offset_path_,\n                                            length_context_, parser_configs_);\n      break;\n  }\n  // Check last path equals to current path.\n  return last_path.get() == nullptr || *last_path != *offset_path_;\n}\n\n// TODO(liyanbo): this will replace by drawInfo.\n// getter\n\nlepus_value ComputedCSSStyle::OpacityToLepus() { return lepus_value(opacity_); }\n\nlepus_value ComputedCSSStyle::PositionToLepus() {\n  return lepus_value(static_cast<int>(layout_computed_style_.position_));\n}\n\nlepus_value ComputedCSSStyle::OverflowToLepus() {\n  return lepus_value(static_cast<int>(overflow_));\n}\n\nlepus_value ComputedCSSStyle::OverflowXToLepus() {\n  return lepus_value(static_cast<int>(overflow_x_));\n}\nlepus_value ComputedCSSStyle::OverflowYToLepus() {\n  return lepus_value(static_cast<int>(overflow_y_));\n}\n\nlepus_value ComputedCSSStyle::FontSizeToLepus() {\n  if (text_attributes_) {\n    return lepus_value(text_attributes_->font_size);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::XPlaceholderFontSizeToLepus() {\n  if (placeholder_text_attributes_) {\n    return lepus_value(placeholder_text_attributes_->font_size);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::LineHeightToLepus() {\n  if (text_attributes_) {\n    return lepus_value(text_attributes_->computed_line_height);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::XAutoFontSizeToLepus() {\n  if (text_attributes_) {\n    auto arr = lepus::CArray::Create();\n    arr->emplace_back(text_attributes_->is_auto_font_size);\n    arr->emplace_back(text_attributes_->auto_font_size_min_size);\n    arr->emplace_back(text_attributes_->auto_font_size_max_size);\n    arr->emplace_back(text_attributes_->auto_font_size_step_granularity);\n    return lepus::Value(std::move(arr));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::XAutoFontSizePresetSizesToLepus() {\n  if (text_attributes_ && text_attributes_->auto_font_size_preset_sizes) {\n    base::InsertionSort((*text_attributes_->auto_font_size_preset_sizes).data(),\n                        (*text_attributes_->auto_font_size_preset_sizes).size(),\n                        [](float a, float b) { return a < b; });\n    auto arr = lepus::CArray::Create();\n    for (const auto& preset_size :\n         *text_attributes_->auto_font_size_preset_sizes) {\n      arr->emplace_back(preset_size);\n    }\n    return lepus::Value(std::move(arr));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::PerspectiveToLepus() {\n  auto array = lepus::CArray::Create();\n  NLength& length = perspective_data_->length_;\n  tasm::CSSValuePattern pattern = perspective_data_->pattern_;\n  array->emplace_back(length.GetRawValue());\n  if (length.IsUnit()) {\n    if (pattern == tasm::CSSValuePattern::NUMBER) {\n      array->emplace_back(static_cast<int>(PerspectiveLengthUnit::NUMBER));\n    } else {\n      array->emplace_back(static_cast<int>(PerspectiveLengthUnit::PX));\n    }\n  } else {\n    array->emplace_back(static_cast<int>(PerspectiveLengthUnit::DEFAULT));\n  }\n  return lepus_value(std::move(array));\n}\n\nlepus_value ComputedCSSStyle::LetterSpacingToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<double>(text_attributes_->letter_spacing));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::LineSpacingToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<double>(text_attributes_->line_spacing));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::ColorToLepus() {\n  if (text_attributes_) {\n    if (text_attributes_->text_gradient.has_value() &&\n        text_attributes_->text_gradient->IsArray()) {\n      return *text_attributes_->text_gradient;\n    } else {\n      return lepus_value(text_attributes_->color.has_value()\n                             ? *text_attributes_->color\n                             : DefaultColor::DEFAULT_TEXT_COLOR);\n    }\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::XPlaceholderColorToLepus() {\n  if (placeholder_text_attributes_) {\n    if (placeholder_text_attributes_->text_gradient.has_value() &&\n        placeholder_text_attributes_->text_gradient->IsArray()) {\n      return *placeholder_text_attributes_->text_gradient;\n    } else {\n      return lepus_value(placeholder_text_attributes_->color.has_value()\n                             ? *placeholder_text_attributes_->color\n                             : DefaultColor::DEFAULT_TEXT_COLOR);\n    }\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BackgroundToLepus() { return lepus_value(); }\n\nlepus_value ComputedCSSStyle::BackgroundColorToLepus() {\n  if (background_data_) {\n    return lepus_value(background_data_->color);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BackgroundImageToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskImageToLepus(\n      background_data_, length_context_, parser_configs_);\n}\n\nlepus_value ComputedCSSStyle::BackgroundSizeToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskSizeToLepus(\n      background_data_);\n}\n\nlepus_value ComputedCSSStyle::BackgroundClipToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskClipToLepus(\n      background_data_);\n}\n\nlepus_value ComputedCSSStyle::BackgroundOriginToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskOriginToLepus(\n      background_data_);\n}\n\nlepus_value ComputedCSSStyle::BackgroundPositionToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskPositionToLepus(\n      background_data_);\n}\n\nlepus_value ComputedCSSStyle::BackgroundRepeatToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskRepeatToLepus(\n      background_data_);\n}\n\nlepus_value ComputedCSSStyle::FilterToLepus() {\n  return CSSStyleUtils::FilterToLepus(filter_);\n}\n\nlepus_value ComputedCSSStyle::BorderTopColorToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    return lepus_value(\n        layout_computed_style_.surround_data_.border_data_->color_top);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderRightColorToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    return lepus_value(\n        layout_computed_style_.surround_data_.border_data_->color_right);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderBottomColorToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    return lepus_value(\n        layout_computed_style_.surround_data_.border_data_->color_bottom);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderLeftColorToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    return lepus_value(\n        layout_computed_style_.surround_data_.border_data_->color_left);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderTopWidthToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_computed_style_.GetBorderTopWidth()));\n}\n\nlepus_value ComputedCSSStyle::BorderRightWidthToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_computed_style_.GetBorderRightWidth()));\n}\n\nlepus_value ComputedCSSStyle::BorderBottomWidthToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_computed_style_.GetBorderBottomWidth()));\n}\n\nlepus_value ComputedCSSStyle::BorderLeftWidthToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_computed_style_.GetBorderLeftWidth()));\n}\n\nlepus_value ComputedCSSStyle::ListMainAxisGapToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_computed_style_.GetListMainAxisGap()));\n}\n\nlepus_value ComputedCSSStyle::ListCrossAxisGapToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_computed_style_.GetListCrossAxisGap()));\n}\n\nlepus_value ComputedCSSStyle::TransformToLepus() {\n  if (transform_raw_) {\n    return CSSStyleUtils::TransformToLepus(*transform_raw_);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::TransformOriginToLepus() {\n  auto array = lepus::CArray::Create();\n  CSSStyleUtils::AddLengthToArray(array, transform_origin_->x);\n  CSSStyleUtils::AddLengthToArray(array, transform_origin_->y);\n  return lepus_value(std::move(array));\n}\n\nlepus_value ComputedCSSStyle::AnimationToLepus() {\n  if (!animation_data_) {\n    return lepus::Value();\n  }\n  auto array_wrap = lepus::CArray::Create();\n  std::for_each(\n      animation_data_->begin(), animation_data_->end(),\n      [&array_wrap](AnimationData& anim) {\n        array_wrap->emplace_back(CSSStyleUtils::AnimationDataToLepus(anim));\n      });\n  return lepus_value(std::move(array_wrap));\n}\n\nlepus_value ComputedCSSStyle::AnimationNameToLepus() {\n  if (!animation_data_) {\n    return lepus::Value();\n  }\n  auto array_wrap = lepus::CArray::Create();\n  std::for_each(animation_data_->begin(), animation_data_->end(),\n                [&array_wrap](AnimationData& anim) {\n                  array_wrap->emplace_back(anim.name);\n                });\n  return lepus_value(std::move(array_wrap));\n}\n\nlepus_value ComputedCSSStyle::AnimationDurationToLepus() {\n  DCHECK(animation_data_ && !animation_data_->empty());\n  return lepus_value(static_cast<double>(animation_data_->front().duration));\n}\n\nlepus_value ComputedCSSStyle::AnimationTimingFunctionToLepus() {\n  DCHECK(animation_data_ && !animation_data_->empty());\n  auto array = lepus::CArray::Create();\n  array->emplace_back(\n      static_cast<int>(animation_data_->front().timing_func.timing_func));\n  array->emplace_back(\n      static_cast<int>(animation_data_->front().timing_func.steps_type));\n  array->emplace_back(animation_data_->front().timing_func.x1);\n  array->emplace_back(animation_data_->front().timing_func.y1);\n  array->emplace_back(animation_data_->front().timing_func.x2);\n  array->emplace_back(animation_data_->front().timing_func.y2);\n  return lepus_value(std::move(array));\n}\n\nlepus_value ComputedCSSStyle::AnimationDelayToLepus() {\n  DCHECK(animation_data_ && !animation_data_->empty());\n  return lepus_value(static_cast<double>(animation_data_->front().delay));\n}\n\nlepus_value ComputedCSSStyle::AnimationIterationCountToLepus() {\n  DCHECK(animation_data_ && !animation_data_->empty());\n  return lepus_value(animation_data_->front().iteration_count);\n}\n\nlepus_value ComputedCSSStyle::AnimationDirectionToLepus() {\n  DCHECK(animation_data_ && !animation_data_->empty());\n  return lepus_value(static_cast<int>(animation_data_->front().direction));\n}\n\nlepus_value ComputedCSSStyle::AnimationFillModeToLepus() {\n  DCHECK(animation_data_ && !animation_data_->empty());\n  return lepus_value(static_cast<int>(animation_data_->front().fill_mode));\n}\n\nlepus_value ComputedCSSStyle::AnimationPlayStateToLepus() {\n  DCHECK(animation_data_ && !animation_data_->empty());\n  return lepus_value(static_cast<int>(animation_data_->front().play_state));\n}\n\nlepus_value ComputedCSSStyle::LayoutAnimationCreateDurationToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_animation_data_->create_ani.duration));\n}\n\nlepus_value ComputedCSSStyle::LayoutAnimationCreateTimingFunctionToLepus() {\n  return LayoutAnimationTimingFunctionToLepusHelper(\n      layout_animation_data_->create_ani.timing_function);\n}\nlepus_value ComputedCSSStyle::LayoutAnimationCreateDelayToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_animation_data_->create_ani.delay));\n}\nlepus_value ComputedCSSStyle::LayoutAnimationCreatePropertyToLepus() {\n  return lepus_value(\n      static_cast<int>(layout_animation_data_->create_ani.property));\n}\nlepus_value ComputedCSSStyle::LayoutAnimationDeleteDurationToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_animation_data_->delete_ani.duration));\n}\nlepus_value ComputedCSSStyle::LayoutAnimationDeleteTimingFunctionToLepus() {\n  return LayoutAnimationTimingFunctionToLepusHelper(\n      layout_animation_data_->delete_ani.timing_function);\n}\nlepus_value ComputedCSSStyle::LayoutAnimationDeleteDelayToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_animation_data_->delete_ani.delay));\n}\nlepus_value ComputedCSSStyle::LayoutAnimationDeletePropertyToLepus() {\n  return lepus_value(\n      static_cast<int>(layout_animation_data_->delete_ani.property));\n}\nlepus_value ComputedCSSStyle::LayoutAnimationUpdateDurationToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_animation_data_->update_ani.duration));\n}\nlepus_value ComputedCSSStyle::LayoutAnimationUpdateTimingFunctionToLepus() {\n  return LayoutAnimationTimingFunctionToLepusHelper(\n      layout_animation_data_->update_ani.timing_function);\n}\nlepus_value ComputedCSSStyle::LayoutAnimationUpdateDelayToLepus() {\n  return lepus_value(\n      static_cast<double>(layout_animation_data_->update_ani.delay));\n}\n\nlepus_value ComputedCSSStyle::TransitionToLepus() {\n  if (!transition_data_) {\n    return lepus::Value();\n  }\n  auto array_wrap = lepus::CArray::Create();\n  for (const auto& it : *transition_data_) {\n    auto array = lepus::CArray::Create();\n    if (it.property == AnimationPropertyType::kNone) {\n      break;\n    }\n    array->reserve(9);\n    array->emplace_back(static_cast<int>(it.property));\n    array->emplace_back(static_cast<double>(it.duration));\n    array->emplace_back(static_cast<int>(it.timing_func.timing_func));\n    array->emplace_back(static_cast<int>(it.timing_func.steps_type));\n    array->emplace_back(static_cast<double>(it.timing_func.x1));\n    array->emplace_back(static_cast<double>(it.timing_func.y1));\n    array->emplace_back(static_cast<double>(it.timing_func.x2));\n    array->emplace_back(static_cast<double>(it.timing_func.y2));\n    array->emplace_back(static_cast<double>(it.delay));\n    array_wrap->emplace_back(std::move(array));\n  }\n  return lepus::Value(std::move(array_wrap));\n}\n\nlepus_value ComputedCSSStyle::TransitionPropertyToLepus() {\n  tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n  return lepus_value();\n}\nlepus_value ComputedCSSStyle::TransitionDurationToLepus() {\n  tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n  return lepus_value();\n}\nlepus_value ComputedCSSStyle::TransitionDelayToLepus() {\n  tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n  return lepus_value();\n}\nlepus_value ComputedCSSStyle::TransitionTimingFunctionToLepus() {\n  tasm::UnitHandler::CSSMethodUnreachable(\n      parser_configs_.enable_css_strict_mode);\n  return lepus_value();\n}\n\nlepus_value ComputedCSSStyle::EnterTransitionNameToLepus() {\n  return CSSStyleUtils::AnimationDataToLepus(*enter_transition_data_);\n}\n\nlepus_value ComputedCSSStyle::ExitTransitionNameToLepus() {\n  return CSSStyleUtils::AnimationDataToLepus(*exit_transition_data_);\n}\nlepus_value ComputedCSSStyle::PauseTransitionNameToLepus() {\n  return CSSStyleUtils::AnimationDataToLepus(*pause_transition_data_);\n}\n\nlepus_value ComputedCSSStyle::ResumeTransitionNameToLepus() {\n  return CSSStyleUtils::AnimationDataToLepus(*resume_transition_data_);\n}\n\nlepus_value ComputedCSSStyle::VisibilityToLepus() {\n  return lepus_value(static_cast<int>(visibility_));\n}\n\nlepus_value ComputedCSSStyle::BorderTopStyleToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    return lepus_value(static_cast<int>(\n        layout_computed_style_.surround_data_.border_data_->style_top));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderRightStyleToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    return lepus_value(static_cast<int>(\n        layout_computed_style_.surround_data_.border_data_->style_right));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderBottomStyleToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    return lepus_value(static_cast<int>(\n        layout_computed_style_.surround_data_.border_data_->style_bottom));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderLeftStyleToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    return lepus_value(static_cast<int>(\n        layout_computed_style_.surround_data_.border_data_->style_left));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::OutlineColorToLepus() {\n  return lepus_value(outline_->color);\n}\nlepus_value ComputedCSSStyle::OutlineStyleToLepus() {\n  return lepus_value(static_cast<int>(outline_->style));\n}\nlepus_value ComputedCSSStyle::OutlineWidthToLepus() {\n  return lepus_value(static_cast<int>(outline_->width));\n}\n\nlepus_value ComputedCSSStyle::BorderColorToLepus() { return lepus_value(); }\n\nlepus_value ComputedCSSStyle::FontFamilyToLepus() {\n  if (text_attributes_) {\n    return lepus_value(text_attributes_->font_family);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::XPlaceholderFontFamilyToLepus() {\n  if (placeholder_text_attributes_) {\n    return lepus_value(placeholder_text_attributes_->font_family);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::CaretColorToLepus() {\n  return lepus::Value(caret_color_);\n}\n\nlepus_value ComputedCSSStyle::DirectionToLepus() {\n  return lepus::Value(static_cast<int>(\n      layout_computed_style_.direction_ == DirectionType::kLynxRtl\n          ? DirectionType::kRtl\n          : layout_computed_style_.direction_));\n}\n\nlepus_value ComputedCSSStyle::WhiteSpaceToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<int>(text_attributes_->white_space));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::WordBreakToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<int>(text_attributes_->word_break));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BoxShadowToLepus() {\n  if (box_shadow_) {\n    return ShadowDataToLepus(*box_shadow_);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::TextShadowToLepus() {\n  if (text_attributes_ && text_attributes_->text_shadow) {\n    return ShadowDataToLepus(*text_attributes_->text_shadow);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::FontWeightToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<int>(text_attributes_->font_weight));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::XPlaceholderFontWeightToLepus() {\n  if (placeholder_text_attributes_) {\n    return lepus_value(\n        static_cast<int>(placeholder_text_attributes_->font_weight));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::FontStyleToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<int>(text_attributes_->font_style));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::XPlaceholderFontStyleToLepus() {\n  if (placeholder_text_attributes_) {\n    return lepus_value(\n        static_cast<int>(placeholder_text_attributes_->font_style));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::TextAlignToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<int>(text_attributes_->text_align));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::TextOverflowToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<int>(text_attributes_->text_overflow));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::TextDecorationToLepus() {\n  if (text_attributes_) {\n    auto array = lepus::CArray::Create();\n    int flags = 0;\n    if (text_attributes_->underline_decoration) {\n      flags |= static_cast<int>(TextDecorationType::kUnderLine);\n    }\n    if (text_attributes_->line_through_decoration) {\n      flags |= static_cast<int>(TextDecorationType::kLineThrough);\n    }\n    array->emplace_back(static_cast<int32_t>(flags));\n    array->emplace_back(\n        static_cast<int32_t>(text_attributes_->text_decoration_style));\n    // if not set the text-decoration-color, use the color as the default value\n    array->emplace_back(\n        static_cast<int32_t>(text_attributes_->text_decoration_color.has_value()\n                                 ? *text_attributes_->text_decoration_color\n                                 : DefaultColor::DEFAULT_TEXT_COLOR));\n    return lepus_value{std::move(array)};\n  } else {\n    return lepus::Value{lepus::CArray::Create()};\n  }\n}\n\nlepus_value ComputedCSSStyle::TextDecorationColorToLepus() {\n  if (text_attributes_) {\n    return lepus_value(text_attributes_->decoration_color.has_value()\n                           ? *text_attributes_->decoration_color\n                           : DefaultColor::DEFAULT_TEXT_COLOR);\n  } else {\n    return lepus_value();\n  }\n}\n\nbool ComputedCSSStyle::InheritLineHeight(const ComputedCSSStyle& from) {\n  DCHECK(from.text_attributes_.has_value());\n\n  PrepareOptionalForTextAttributes();\n  bool factor_different =\n      base::FloatsNotEqual(text_attributes_->line_height_factor,\n                           from.text_attributes_->line_height_factor);\n  text_attributes_->line_height_factor =\n      from.text_attributes_->line_height_factor;\n\n  auto old_computed_value = text_attributes_->computed_line_height;\n  if (text_attributes_->line_height_factor !=\n      DefaultComputedStyle::DEFAULT_LINE_HEIGHT_FACTOR) {\n    // Inherit the factor, when the line height is in factor form.\n    text_attributes_->computed_line_height =\n        text_attributes_->line_height_factor *\n        length_context_.cur_node_font_size_;\n  } else {\n    text_attributes_->computed_line_height =\n        from.text_attributes_->computed_line_height;\n  }\n  return factor_different ||\n         base::FloatsNotEqual(text_attributes_->computed_line_height,\n                              old_computed_value);\n}\n\nbool ComputedCSSStyle::InheritLineSpacing(const ComputedCSSStyle& from) {\n  DCHECK(from.text_attributes_.has_value());\n  if (!from.text_attributes_.has_value() ||\n      (text_attributes_.has_value() &&\n       text_attributes_->line_spacing == from.text_attributes_->line_spacing)) {\n    return false;\n  }\n  PrepareOptionalForTextAttributes();\n  text_attributes_->line_spacing = from.text_attributes_->line_spacing;\n  return true;\n}\n\nbool ComputedCSSStyle::InheritLetterSpacing(const ComputedCSSStyle& from) {\n  DCHECK(from.text_attributes_.has_value());\n  if (!from.text_attributes_.has_value() ||\n      (text_attributes_.has_value() &&\n       text_attributes_->letter_spacing ==\n           from.text_attributes_->letter_spacing)) {\n    return false;\n  }\n  PrepareOptionalForTextAttributes();\n  text_attributes_->letter_spacing = from.text_attributes_->letter_spacing;\n  return true;\n}\n\nlepus_value ComputedCSSStyle::ZIndexToLepus() { return lepus_value(z_index_); }\n\nlepus_value ComputedCSSStyle::VerticalAlignToLepus() {\n  if (text_attributes_) {\n    auto arr = lepus::CArray::Create();\n    arr->emplace_back(static_cast<int>(text_attributes_->vertical_align));\n    arr->emplace_back(text_attributes_->vertical_align_length);\n    return lepus::Value(std::move(arr));\n  } else {\n    return lepus::Value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderRadiusToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    auto container = lepus::CArray::Create();\n    container->reserve(16);\n    CSSStyleUtils::AddLengthToArray(\n        container,\n        layout_computed_style_.surround_data_.border_data_->radius_x_top_left);\n    CSSStyleUtils::AddLengthToArray(\n        container,\n        layout_computed_style_.surround_data_.border_data_->radius_y_top_left);\n    CSSStyleUtils::AddLengthToArray(\n        container,\n        layout_computed_style_.surround_data_.border_data_->radius_x_top_right);\n    CSSStyleUtils::AddLengthToArray(\n        container,\n        layout_computed_style_.surround_data_.border_data_->radius_y_top_right);\n    CSSStyleUtils::AddLengthToArray(\n        container, layout_computed_style_.surround_data_.border_data_\n                       ->radius_x_bottom_right);\n    CSSStyleUtils::AddLengthToArray(\n        container, layout_computed_style_.surround_data_.border_data_\n                       ->radius_y_bottom_right);\n    CSSStyleUtils::AddLengthToArray(\n        container, layout_computed_style_.surround_data_.border_data_\n                       ->radius_x_bottom_left);\n    CSSStyleUtils::AddLengthToArray(\n        container, layout_computed_style_.surround_data_.border_data_\n                       ->radius_y_bottom_left);\n    return lepus::Value(std::move(container));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderTopLeftRadiusToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    auto array = lepus::CArray::Create();\n    CSSStyleUtils::AddLengthToArray(\n        array,\n        layout_computed_style_.surround_data_.border_data_->radius_x_top_left);\n    CSSStyleUtils::AddLengthToArray(\n        array,\n        layout_computed_style_.surround_data_.border_data_->radius_y_top_left);\n    return lepus::Value(std::move(array));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderTopRightRadiusToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    auto array = lepus::CArray::Create();\n    CSSStyleUtils::AddLengthToArray(\n        array,\n        layout_computed_style_.surround_data_.border_data_->radius_x_top_right);\n    CSSStyleUtils::AddLengthToArray(\n        array,\n        layout_computed_style_.surround_data_.border_data_->radius_y_top_right);\n    return lepus::Value(std::move(array));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderBottomRightRadiusToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    auto array = lepus::CArray::Create();\n    CSSStyleUtils::AddLengthToArray(\n        array, layout_computed_style_.surround_data_.border_data_\n                   ->radius_x_bottom_right);\n    CSSStyleUtils::AddLengthToArray(\n        array, layout_computed_style_.surround_data_.border_data_\n                   ->radius_y_bottom_right);\n    return lepus::Value(std::move(array));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::BorderBottomLeftRadiusToLepus() {\n  if (layout_computed_style_.surround_data_.border_data_) {\n    auto array = lepus::CArray::Create();\n    CSSStyleUtils::AddLengthToArray(\n        array, layout_computed_style_.surround_data_.border_data_\n                   ->radius_x_bottom_left);\n    CSSStyleUtils::AddLengthToArray(\n        array, layout_computed_style_.surround_data_.border_data_\n                   ->radius_y_bottom_left);\n    return lepus::Value(std::move(array));\n  } else {\n    return lepus_value();\n  }\n}\n\nbool ComputedCSSStyle::SetCursor(const tasm::CSSValue& value,\n                                 const bool reset) {\n  auto old_value = cursor_;\n  if (reset) {\n    cursor_.reset();\n    return true;\n  } else {\n    CSSStyleUtils::PrepareOptional(cursor_);\n    cursor_ = value.GetValue();\n    return old_value != cursor_;\n  }\n}\n\nlepus_value ComputedCSSStyle::CursorToLepus() {\n  if (cursor_ && cursor_->IsArray()) {\n    return *cursor_;\n  } else {\n    return lepus_value();\n  }\n}\n\nbool ComputedCSSStyle::SetTextIndent(const tasm::CSSValue& value,\n                                     const bool reset) {\n  PrepareOptionalForTextAttributes();\n  if (reset) {\n    text_attributes_->text_indent = DefaultLayoutStyle::SL_DEFAULT_ZEROLENGTH();\n    return true;\n  } else {\n    auto old_value = text_attributes_->text_indent;\n    auto ret = CSSStyleUtils::ToLength(value, length_context_, parser_configs_);\n    if (!ret.second) {\n      return false;\n    }\n    text_attributes_->text_indent = ret.first;\n    return old_value != text_attributes_->text_indent;\n  }\n}\n\nlepus_value ComputedCSSStyle::TextIndentToLepus() {\n  if (text_attributes_) {\n    auto array = lepus::CArray::Create();\n    CSSStyleUtils::AddLengthToArray(array, text_attributes_->text_indent);\n    return lepus_value(std::move(array));\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::TextStrokeToLepus() {\n  // Parse failed or reset, return an empty array.\n  return lepus_value();\n}\n\nlepus_value ComputedCSSStyle::TextStrokeColorToLepus() {\n  if (text_attributes_) {\n    return lepus_value(text_attributes_->text_stroke_color.has_value()\n                           ? *text_attributes_->text_stroke_color\n                           : DefaultColor::DEFAULT_TEXT_COLOR);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus_value ComputedCSSStyle::TextStrokeWidthToLepus() {\n  if (text_attributes_) {\n    return lepus_value(text_attributes_->text_stroke_width);\n  } else {\n    return lepus_value();\n  }\n}\n\nlepus::Value ComputedCSSStyle::ClipPathToLepus() {\n  // Parse failed or reset, return an empty array.\n  return clip_path_.get() ? lepus::Value(clip_path_)\n                          : lepus::Value(lepus::CArray::Create());\n}\n\nlepus::Value ComputedCSSStyle::OffsetPathToLepus() {\n  // Parse failed or reset, return an empty array.\n  return offset_path_.get() ? lepus::Value(offset_path_)\n                            : lepus::Value(lepus::CArray::Create());\n}\n\nlepus::Value ComputedCSSStyle::OffsetDistanceToLepus() {\n  return lepus::Value(offset_distance_);\n}\n\nlepus::Value ComputedCSSStyle::OffsetRotateToLepus() {\n  return lepus::Value(offset_rotate_);\n}\n\nbool ComputedCSSStyle::SetMaskImage(const tasm::CSSValue& value,\n                                    const bool reset) {\n  return SetBackgroundOrMaskImage(mask_data_, value, reset);\n}\n\nbool ComputedCSSStyle::SetMaskSize(const tasm::CSSValue& value,\n                                   const bool reset) {\n  return SetBackgroundOrMaskSize(mask_data_, length_context_, parser_configs_,\n                                 value, reset);\n}\n\nbool ComputedCSSStyle::SetMaskOrigin(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return SetBackgroundOrMaskOrigin(mask_data_, value, reset);\n}\n\nbool ComputedCSSStyle::SetMaskClip(const tasm::CSSValue& value,\n                                   const bool reset) {\n  return SetBackgroundOrMaskClip(mask_data_, value, reset);\n}\n\nbool ComputedCSSStyle::SetMaskPosition(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return SetBackgroundOrMaskPosition(mask_data_, length_context_,\n                                     parser_configs_, value, reset);\n}\n\nbool ComputedCSSStyle::SetMaskRepeat(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return SetBackgroundOrMaskRepeat(mask_data_, value, reset);\n}\n\nbool ComputedCSSStyle::SetMask(const tasm::CSSValue& value, const bool reset) {\n  return false;\n}\n\nlepus_value ComputedCSSStyle::MaskImageToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskImageToLepus(\n      mask_data_, length_context_, parser_configs_);\n}\n\nlepus_value ComputedCSSStyle::MaskSizeToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskSizeToLepus(mask_data_);\n}\n\nlepus_value ComputedCSSStyle::MaskClipToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskClipToLepus(mask_data_);\n}\n\nlepus_value ComputedCSSStyle::MaskOriginToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskOriginToLepus(mask_data_);\n}\n\nlepus_value ComputedCSSStyle::MaskPositionToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskPositionToLepus(\n      mask_data_);\n}\n\nlepus_value ComputedCSSStyle::MaskRepeatToLepus() {\n  return ComputedCSSStyleUtilsMethod::BackgroundOrMaskRepeatToLepus(mask_data_);\n}\n\nbool ComputedCSSStyle::SetImageRendering(const tasm::CSSValue& value,\n                                         const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<ImageRenderingType>(\n      value, reset, image_rendering_, ImageRenderingType::kAuto,\n      \"image-rendering must be a number!\", parser_configs_);\n}\n\nlepus_value ComputedCSSStyle::ImageRenderingToLepus() {\n  return lepus_value(static_cast<int>(image_rendering_));\n}\n\nbool ComputedCSSStyle::SetHyphens(const tasm::CSSValue& value,\n                                  const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<HyphensType>(\n      value, reset, text_attributes_->hyphens,\n      DefaultComputedStyle::DEFAULT_HYPHENS, \"hyphens must be an enum!\",\n      parser_configs_);\n}\n\nlepus_value ComputedCSSStyle::HyphensToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<int>(text_attributes_->hyphens));\n  } else {\n    return lepus_value();\n  }\n}\n\nbool ComputedCSSStyle::SetXAppRegion(const tasm::CSSValue& value,\n                                     const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<XAppRegionType>(\n      value, reset, app_region_, XAppRegionType::kNone,\n      \"app region must be an enum!\", parser_configs_);\n}\n\nlepus_value ComputedCSSStyle::XAppRegionToLepus() {\n  return lepus_value(static_cast<int>(app_region_));\n}\n\nbool ComputedCSSStyle::SetXAnimationColorInterpolation(\n    const tasm::CSSValue& value, const bool reset) {\n  return CSSStyleUtils::ComputeEnumStyle<XAnimationColorInterpolationType>(\n      value, reset, new_animator_interpolation_,\n      XAnimationColorInterpolationType::kAuto,\n      \"-x-animation-color-interpolation must be an enum\", parser_configs_);\n}\n\nbool ComputedCSSStyle::SetXHandleColor(const tasm::CSSValue& value,\n                                       const bool reset) {\n  return CSSStyleUtils::ComputeUIntStyle(\n      value, reset, handle_color_, DefaultColor::DEFAULT_COLOR,\n      \"-x-handle-color must be a number!\", parser_configs_);\n}\n\nlepus_value ComputedCSSStyle::XHandleColorToLepus() {\n  return lepus_value(handle_color_);\n}\n\nbool ComputedCSSStyle::SetXHandleSize(const tasm::CSSValue& value,\n                                      const bool reset) {\n  float old_value = handle_size_;\n  if (reset) {\n    handle_size_ = DefaultComputedStyle::DEFAULT_FLOAT;\n  } else {\n    if (UNLIKELY(!CalculateCSSValueToFloat(value, handle_size_, length_context_,\n                                           parser_configs_))) {\n      return false;\n    }\n  }\n  return base::FloatsNotEqual(old_value, handle_size_);\n}\n\nlepus_value ComputedCSSStyle::XHandleSizeToLepus() {\n  return lepus_value(handle_size_);\n}\n\nbool ComputedCSSStyle::SetFontVariationSettings(const tasm::CSSValue& value,\n                                                const bool reset) {\n  PrepareOptionalForTextAttributes();\n\n  auto old_value = text_attributes_->font_variation_settings;\n  if (reset) {\n    if (old_value) {\n      old_value = nullptr;\n      return true;\n    }\n    return false;\n  }\n\n  CSS_HANDLER_FAIL_IF_NOT(\n      value.IsArray(), parser_configs_.enable_css_strict_mode,\n      tasm::TYPE_MUST_BE,\n      tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDFontVariationSettings)\n          .c_str(),\n      tasm::ARRAY_TYPE)\n  text_attributes_->font_variation_settings = value.GetArray();\n\n  return old_value.get() == nullptr ||\n         *old_value != *text_attributes_->font_variation_settings;\n}\n\nlepus_value ComputedCSSStyle::FontVariationSettingsToLepus() {\n  if (text_attributes_ && text_attributes_->font_variation_settings.get()) {\n    return lepus::Value(text_attributes_->font_variation_settings);\n  }\n  return lepus::Value(lepus::CArray::Create());\n}\n\nbool ComputedCSSStyle::SetFontFeatureSettings(const tasm::CSSValue& value,\n                                              const bool reset) {\n  PrepareOptionalForTextAttributes();\n\n  auto old_value = text_attributes_->font_feature_settings;\n  if (reset) {\n    if (old_value) {\n      old_value = nullptr;\n      return true;\n    }\n    return false;\n  }\n\n  CSS_HANDLER_FAIL_IF_NOT(\n      value.IsArray(), parser_configs_.enable_css_strict_mode,\n      tasm::TYPE_MUST_BE,\n      tasm::CSSProperty::GetPropertyName(tasm::kPropertyIDFontFeatureSettings)\n          .c_str(),\n      tasm::ARRAY_TYPE)\n  text_attributes_->font_feature_settings = value.GetArray();\n\n  return old_value.get() == nullptr ||\n         *old_value != *text_attributes_->font_feature_settings;\n}\n\nlepus_value ComputedCSSStyle::FontFeatureSettingsToLepus() {\n  if (text_attributes_ && text_attributes_->font_feature_settings.get()) {\n    return lepus::Value(text_attributes_->font_feature_settings);\n  }\n  return lepus::Value(lepus::CArray::Create());\n}\n\nbool ComputedCSSStyle::SetFontOpticalSizing(const tasm::CSSValue& value,\n                                            const bool reset) {\n  PrepareOptionalForTextAttributes();\n  return CSSStyleUtils::ComputeEnumStyle<FontOpticalSizingType>(\n      value, reset, text_attributes_->font_optical_sizing,\n      DefaultComputedStyle::DEFAULT_FONT_OPTICAL_SIZING,\n      \"font-optical-sizing must be an enum!\", parser_configs_);\n}\n\nlepus_value ComputedCSSStyle::FontOpticalSizingToLepus() {\n  if (text_attributes_) {\n    return lepus_value(static_cast<int>(text_attributes_->font_optical_sizing));\n  } else {\n    return lepus_value();\n  }\n}\n\nbool ComputedCSSStyle::SetPointerEvents(const tasm::CSSValue& value,\n                                        const bool reset) {\n  bool flush = CSSStyleUtils::ComputeEnumStyle<PointerEventsType>(\n      value, reset, pointer_events_, PointerEventsType::kAuto,\n      \"pointer-events must be an enum!\", parser_configs_);\n  return pointer_events_ == PointerEventsType::kAuto ? true : flush;\n}\n\nlepus_value ComputedCSSStyle::PointerEventsToLepus() {\n  return lepus_value(static_cast<int>(pointer_events_));\n}\n\n}  // namespace starlight\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/computed_css_style.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_COMPUTED_CSS_STYLE_H_\n#define CORE_RENDERER_CSS_COMPUTED_CSS_STYLE_H_\n\n#include <unordered_map>\n\n#include \"base/include/flex_optional.h\"\n#include \"base/include/vector.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/css_property_bitset.h\"\n#include \"core/renderer/css/css_property_id.h\"\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/css/measure_context.h\"\n#include \"core/renderer/css/text_attributes.h\"\n#include \"core/renderer/starlight/style/layout_computed_style.h\"\n#include \"core/renderer/starlight/style/surround_data.h\"\n#include \"core/renderer/starlight/types/layout_types.h\"\n#include \"core/renderer/tasm/config.h\"\n#include \"core/style/animation_data.h\"\n#include \"core/style/background_data.h\"\n#include \"core/style/default_computed_style.h\"\n#include \"core/style/filter_data.h\"\n#include \"core/style/layout_animation_data.h\"\n#include \"core/style/outline_data.h\"\n#include \"core/style/perspective_data.h\"\n#include \"core/style/shadow_data.h\"\n#include \"core/style/transform_origin_data.h\"\n#include \"core/style/transform_raw_data.h\"\n#include \"core/style/transition_data.h\"\n\nnamespace lynx {\nnamespace tasm {\n// TODO(songshourui.null): remove this when all the ##nameToLepus methods are\n// deleted. In the long term, the PropBundleStyleWriter will optimize all Write\n// methods, at which point all ##nameToLepus methods of ComputedCSSStyle can be\n// deleted, and this forward declaration. will also be removed.\nclass PropBundleStyleWriter;\nclass ComputedCSSStyleCssTextHelper;\nclass PseudoElement;\nclass Element;\n}  // namespace tasm\n\nnamespace starlight {\n/** CSSStyle stores the specified values of all CSS properties.\n * Specified values are the values assigned to CSS properties when they are set,\n * including px, %, auto, and various enumerated properties. All CSS properties\n * are grouped. */\n\nclass ComputedCSSStyleUtilsMethod {\n public:\n  static lepus::Value BackgroundOrMaskClipToLepus(\n      const base::flex_optional<starlight::BackgroundData>& data);\n  static lepus::Value BackgroundOrMaskImageToLepus(\n      base::flex_optional<BackgroundData>& data,\n      const tasm::CssMeasureContext& context,\n      const tasm::CSSParserConfigs& configs);\n  static lepus::Value BackgroundOrMaskOriginToLepus(\n      const base::flex_optional<BackgroundData>& data);\n  static lepus::Value BackgroundOrMaskPositionToLepus(\n      const base::flex_optional<BackgroundData>& data);\n  static lepus::Value BackgroundOrMaskRepeatToLepus(\n      const base::flex_optional<BackgroundData>& data);\n  static lepus::Value BackgroundOrMaskSizeToLepus(\n      const base::flex_optional<BackgroundData>& data);\n};\n\nclass ComputedCSSStyle {\n public:\n  LYNX_EXPORT ComputedCSSStyle(float layouts_unit_per_px,\n                               double physical_pixels_per_layout_unit);\n  ComputedCSSStyle(const ComputedCSSStyle& o);\n  ~ComputedCSSStyle() = default;\n\n  LYNX_EXPORT bool SetValue(tasm::CSSPropertyID id, const tasm::CSSValue& value,\n                            bool reset = false);\n\n  bool AppendAnimatedAnimationValue(tasm::StyleMap animate_data,\n                                    bool reset = false);\n\n  double GetFontSize() const { return length_context_.cur_node_font_size_; }\n\n  double GetRootFontSize() const {\n    return length_context_.root_node_font_size_;\n  }\n\n  void SetScreenWidth(float screen_width) {\n    length_context_.screen_width_ = screen_width;\n  }\n\n  bool SetFontScale(float font_scale);\n\n  void SetFontScaleOnlyEffectiveOnSp(bool on_sp) {\n    length_context_.font_scale_sp_only_ = on_sp;\n  }\n\n  void SetViewportWidth(const LayoutUnit& width) {\n    length_context_.viewport_width_ = width;\n  }\n\n  void SetViewportHeight(const LayoutUnit& height) {\n    length_context_.viewport_height_ = height;\n  }\n\n  bool SetFontSize(double cur_node_font_size, double root_node_font_size) {\n    if (length_context_.cur_node_font_size_ == cur_node_font_size &&\n        length_context_.root_node_font_size_ == root_node_font_size) {\n      return false;\n    }\n    length_context_.cur_node_font_size_ = cur_node_font_size;\n    length_context_.root_node_font_size_ = root_node_font_size;\n    return true;\n  }\n\n  void SetLayoutUnit(float physical_pixels_per_layout_unit,\n                     float layouts_unit_per_px) {\n    length_context_.physical_pixels_per_layout_unit_ =\n        physical_pixels_per_layout_unit;\n    length_context_.layouts_unit_per_px_ = layouts_unit_per_px;\n    layout_computed_style_.SetPhysicalPixelsPerLayoutUnit(\n        physical_pixels_per_layout_unit);\n  }\n\n  const tasm::CssMeasureContext& GetMeasureContext() { return length_context_; }\n\n  void Reset();\n  bool ResetValue(tasm::CSSPropertyID id);\n  void SetOverflowDefaultVisible(bool default_overflow_visible);\n  OverflowType GetDefaultOverflowType() const {\n    return default_overflow_visible_ ? OverflowType::kVisible\n                                     : OverflowType::kHidden;\n  }\n\n  bool InheritValue(tasm::CSSPropertyID id, const ComputedCSSStyle& from);\n\n  bool HasAnimation() const { return animation_data_.has_value(); }\n\n  base::Vector<AnimationData>& animation_data() {\n    CSSStyleUtils::PrepareOptional(animation_data_);\n    return *animation_data_;\n  }\n\n  bool HasTransform() const { return transform_raw_.has_value(); }\n\n  bool HasTransformOrigin() const { return transform_origin_.has_value(); }\n\n  bool TransformChanged() const {\n    return changed_bitset_.Has(tasm::kPropertyIDTransform) ||\n           changed_bitset_.Has(tasm::kPropertyIDTransformOrigin) ||\n           reset_bitset_.Has(tasm::kPropertyIDTransform) ||\n           reset_bitset_.Has(tasm::kPropertyIDTransformOrigin);\n  }\n\n  bool HasTransition() const { return transition_data_.has_value(); }\n\n  bool HasBorder() const {\n    return layout_computed_style_.surround_data_.border_data_ &&\n           (layout_computed_style_.surround_data_.border_data_->width_top +\n            layout_computed_style_.surround_data_.border_data_->width_right +\n            layout_computed_style_.surround_data_.border_data_->width_bottom +\n            layout_computed_style_.surround_data_.border_data_->width_left) > 0;\n  }\n\n  bool HasBorderRadius() const {\n    return layout_computed_style_.surround_data_.border_data_ &&\n           (layout_computed_style_.surround_data_.border_data_\n                ->radius_x_top_left.GetRawValue() +\n            layout_computed_style_.surround_data_.border_data_\n                ->radius_x_top_right.GetRawValue() +\n            layout_computed_style_.surround_data_.border_data_\n                ->radius_x_bottom_right.GetRawValue() +\n            layout_computed_style_.surround_data_.border_data_\n                ->radius_x_bottom_left.GetRawValue() +\n            layout_computed_style_.surround_data_.border_data_\n                ->radius_y_top_left.GetRawValue() +\n            layout_computed_style_.surround_data_.border_data_\n                ->radius_y_top_right.GetRawValue() +\n            layout_computed_style_.surround_data_.border_data_\n                ->radius_y_bottom_right.GetRawValue() +\n            layout_computed_style_.surround_data_.border_data_\n                ->radius_y_bottom_left.GetRawValue()) > 0;\n  }\n\n  const NLength& GetSimpleBorderTopLeftRadius() {\n    DCHECK(\n        layout_computed_style_.surround_data_.border_data_->radius_x_top_left ==\n        layout_computed_style_.surround_data_.border_data_->radius_y_top_left);\n    return layout_computed_style_.surround_data_.border_data_\n        ->radius_x_top_left;\n  }\n  const NLength& GetSimpleBorderTopRightRadius() {\n    DCHECK(\n        layout_computed_style_.surround_data_.border_data_\n            ->radius_x_top_right ==\n        layout_computed_style_.surround_data_.border_data_->radius_y_top_right);\n    return layout_computed_style_.surround_data_.border_data_\n        ->radius_x_top_right;\n  }\n  const NLength& GetSimpleBorderBottomLeftRadius() {\n    DCHECK(layout_computed_style_.surround_data_.border_data_\n               ->radius_x_bottom_left ==\n           layout_computed_style_.surround_data_.border_data_\n               ->radius_y_bottom_left);\n    return layout_computed_style_.surround_data_.border_data_\n        ->radius_x_bottom_left;\n  }\n  const NLength& GetSimpleBorderBottomRightRadius() {\n    DCHECK(layout_computed_style_.surround_data_.border_data_\n               ->radius_x_bottom_right ==\n           layout_computed_style_.surround_data_.border_data_\n               ->radius_y_bottom_right);\n    return layout_computed_style_.surround_data_.border_data_\n        ->radius_x_bottom_right;\n  }\n\n  base::Vector<TransitionData>& transition_data() {\n    CSSStyleUtils::PrepareOptional(transition_data_);\n    return *transition_data_;\n  }\n\n  void SetCssAlignLegacyWithW3c(bool value) {\n    css_align_with_legacy_w3c_ = value;\n  }\n\n  void SetCSSParserConfigs(const tasm::CSSParserConfigs& configs) {\n    parser_configs_ = configs;\n  }\n\n  tasm::CSSParserConfigs& GetCSSParserConfigs() { return parser_configs_; }\n\n  void SetEnableZIndex(bool enable) { enable_z_index_ = enable; }\n\n  int GetZIndex() const { return z_index_; }\n\n  bool HasZIndex() const { return has_z_index_; }\n\n  ImageRenderingType GetImageRendering() { return image_rendering_; }\n\n  float GetOpacity() const { return opacity_; }\n\n  PositionType GetPosition() const { return layout_computed_style_.position_; }\n\n  OverflowType GetOverflow() const { return overflow_; }\n\n  OverflowType GetOverflowX() const { return overflow_x_; }\n\n  OverflowType GetOverflowY() const { return overflow_y_; }\n\n  int32_t GetOverflowType() const {\n    return (static_cast<int32_t>(overflow_) << 16) |\n           (static_cast<int32_t>(overflow_x_) << 8) |\n           static_cast<int32_t>(overflow_y_);\n  }\n\n  bool IsOverflowXY() const { return origin_overflow_ == OVERFLOW_XY; }\n\n  bool IsOverflowX() const { return origin_overflow_ & OVERFLOW_X; }\n\n  bool IsOverflowY() const { return origin_overflow_ & OVERFLOW_Y; }\n\n  bool IsOverflowHidden() const { return origin_overflow_ == OVERFLOW_HIDDEN; }\n\n  base::flex_optional<TextAttributes>& GetTextAttributes() {\n    return text_attributes_;\n  }\n\n  base::flex_optional<TextAttributes>& GetPlaceholderTextAttributes() {\n    return placeholder_text_attributes_;\n  }\n\n  base::flex_optional<BackgroundData>& GetBackgroundData() {\n    return background_data_;\n  }\n\n  base::flex_optional<FilterData>& GetFilterData() { return filter_; }\n\n  base::flex_optional<BackgroundData>& GetMaskData() { return mask_data_; }\n\n  auto& GetTransformData() { return transform_raw_; }\n\n  base::flex_optional<TransformOriginData>& GetTransformOriginData() {\n    return transform_origin_;\n  }\n\n  auto& GetAnimationData() { return animation_data_; }\n\n  base::flex_optional<LayoutAnimationData>& GetLayoutAnimationData() {\n    return layout_animation_data_;\n  }\n\n  auto& GetTransitionData() { return transition_data_; }\n\n  base::flex_optional<AnimationData>& GetEnterTransitionData() {\n    return enter_transition_data_;\n  }\n\n  base::flex_optional<AnimationData>& GetExitTransitionData() {\n    return exit_transition_data_;\n  }\n\n  base::flex_optional<AnimationData>& GetPauseTransitionData() {\n    return pause_transition_data_;\n  }\n\n  base::flex_optional<AnimationData>& GetResumeTransitionData() {\n    return resume_transition_data_;\n  }\n\n  VisibilityType GetVisibilityData() { return visibility_; }\n\n  base::flex_optional<OutLineData>& GetOutLineData() { return outline_; }\n\n  auto& GetBoxShadowData() { return box_shadow_; }\n\n  base::String& GetCaretColor() { return caret_color_; }\n\n  base::flex_optional<PerspectiveData>& GetPerspectiveData() {\n    return perspective_data_;\n  }\n\n  base::flex_optional<lepus::Value>& GetCursor() { return cursor_; }\n\n  fml::RefPtr<lepus::CArray>& GetClipPath() { return clip_path_; }\n\n  XAppRegionType GetAppRegion() { return app_region_; }\n\n  float GetHandleSize() { return handle_size_; }\n\n  uint32_t GetHandleColor() { return handle_color_; }\n\n  bool HasOpacity() const;\n\n  bool OpacityChanged() const {\n    return changed_bitset_.Has(tasm::kPropertyIDOpacity) ||\n           reset_bitset_.Has(tasm::kPropertyIDOpacity);\n  }\n\n  const LayoutComputedStyle* GetConstLayoutComputedStyle() const {\n    return &layout_computed_style_;\n  }\n\n  LayoutComputedStyle* GetLayoutComputedStyle() {\n    return &layout_computed_style_;\n  }\n\n  void PrepareOptionalForTextAttributes() {\n    const float default_font_size =\n        DEFAULT_FONT_SIZE_DP * length_context_.layouts_unit_per_px_;\n    CSSStyleUtils::PrepareOptionalForTextAttributes(text_attributes_,\n                                                    default_font_size);\n  }\n\n  void PrepareOptionalForPlaceholderTextAttributes() {\n    const float default_font_size =\n        DEFAULT_FONT_SIZE_DP * length_context_.layouts_unit_per_px_;\n    CSSStyleUtils::PrepareOptionalForTextAttributes(\n        placeholder_text_attributes_, default_font_size);\n  }\n\n  XAnimationColorInterpolationType new_animator_interpolation() {\n    return new_animator_interpolation_;\n  }\n\n  const fml::RefPtr<lepus::CArray>& GetOffsetPath() const {\n    return offset_path_;\n  }\n\n  float GetOffsetDistance() const { return offset_distance_; }\n\n  float GetOffsetRotate() const { return offset_rotate_; }\n\n  PointerEventsType GetPointerEvents() { return pointer_events_; }\n\n  DirectionType GetDirection() { return layout_computed_style_.GetDirection(); }\n\n  static bool IsPlatformInheritableProperty(const tasm::CSSPropertyID id) {\n    return GetPlatformInheritableProperty().contains(id);\n  }\n\n  static float SAFE_AREA_INSET_TOP_;\n  static float SAFE_AREA_INSET_BOTTOM_;\n  static float SAFE_AREA_INSET_LEFT_;\n  static float SAFE_AREA_INSET_RIGHT_;\n\n  bool IsDirty() { return changed_bitset_.HasAny() || reset_bitset_.HasAny(); }\n\n  bool IsClean() { return !IsDirty(); }\n\n  void MarkChanged(tasm::CSSPropertyID id) {\n    changed_bitset_.Set(id);\n    reset_bitset_.Reset(id);\n  }\n\n private:\n  void SetOriginOverflowMask(tasm::CSSPropertyID id);\n\n  friend class tasm::ComputedCSSStyleCssTextHelper;\n  using StyleFunc = bool (ComputedCSSStyle::*)(const tasm::CSSValue&,\n                                               const bool reset);\n  using StyleGetterFunc = lepus_value (ComputedCSSStyle::*)();\n  using StyleInheritFunc = bool (ComputedCSSStyle::*)(const ComputedCSSStyle&);\n  using StyleInheritFuncMap =\n      std::unordered_map<tasm::CSSPropertyID, StyleInheritFunc>;\n\n  static const StyleFunc* FuncMap();\n  static const StyleGetterFunc* GetterFuncMap();\n  const StyleInheritFuncMap& InheritFuncMap();\n\n  // calc style parameters.\n  LayoutComputedStyle layout_computed_style_;\n  tasm::CssMeasureContext length_context_;\n\n  /***************** css style property ***************************/\n\n  // this should not in css. But here is only compact old version.\n  base::String caret_color_;\n  base::String adapt_font_size_;\n  base::String content_;\n  base::flex_optional<AnimationData> enter_transition_data_;\n  base::flex_optional<AnimationData> exit_transition_data_;\n  base::flex_optional<AnimationData> pause_transition_data_;\n  base::flex_optional<AnimationData> resume_transition_data_;\n  base::flex_optional<BackgroundData> background_data_;\n  base::flex_optional<BackgroundData> mask_data_;\n  base::flex_optional<LayoutAnimationData> layout_animation_data_;\n  base::flex_optional<OutLineData> outline_;\n  base::flex_optional<base::InlineVector<AnimationData, 1>> animation_data_;\n  base::flex_optional<base::InlineVector<TransformRawData, 1>> transform_raw_;\n  base::flex_optional<base::InlineVector<TransitionData, 1>> transition_data_;\n  base::flex_optional<base::InlineVector<ShadowData, 1>> box_shadow_;\n  base::flex_optional<TextAttributes> text_attributes_;\n  base::flex_optional<TextAttributes> placeholder_text_attributes_;\n  base::flex_optional<TransformOriginData> transform_origin_;\n  base::flex_optional<FilterData> filter_;\n  base::flex_optional<PerspectiveData> perspective_data_;\n  // [type, [url, x, y], type, keyword ]\n  base::flex_optional<lepus_value> cursor_;\n  // clip-path array [type, args..]\n  fml::RefPtr<lepus::CArray> clip_path_{nullptr};\n  // offset-path array [type, args..]\n  fml::RefPtr<lepus::CArray> offset_path_{nullptr};\n\n  int z_index_{DefaultComputedStyle::DEFAULT_LONG};\n  bool enable_z_index_{false};\n  bool has_z_index_{false};\n\n  unsigned int handle_color_{0};\n  float handle_size_{0.f};\n\n  bool origin_has_opacity_{false};\n  float opacity_{DefaultComputedStyle::DEFAULT_OPACITY};\n\n  float offset_distance_{DefaultComputedStyle::DEFAULT_OFFSET_DISTANCE};\n  float offset_rotate_ = {DefaultComputedStyle::DEFAULT_OFFSET_ROTATE};\n\n  ImageRenderingType image_rendering_ = ImageRenderingType::kAuto;\n  XAppRegionType app_region_ = XAppRegionType::kNone;\n  XAnimationColorInterpolationType new_animator_interpolation_ =\n      XAnimationColorInterpolationType::kAuto;\n\n  static constexpr int32_t OVERFLOW_HIDDEN = 0x00;\n  static constexpr int32_t OVERFLOW_X = 0x01;\n  static constexpr int32_t OVERFLOW_Y = 0x02;\n  static constexpr int32_t OVERFLOW_XY = (OVERFLOW_X | OVERFLOW_Y);\n  // TODO(songshourui.null): Currently, `overflow` is not treated as a shorthand\n  // property; `overflow`, `overflow-x`, and `overflow-y` are handled\n  // individually. To avoid breaking changes, the original logic from `Element`\n  // is retained. This can be optimized later by treating `overflow` as a\n  // shorthand property.\n  int32_t origin_overflow_{0};\n\n  OverflowType overflow_{DefaultComputedStyle::DEFAULT_OVERFLOW};\n  OverflowType overflow_x_{DefaultComputedStyle::DEFAULT_OVERFLOW};\n  OverflowType overflow_y_{DefaultComputedStyle::DEFAULT_OVERFLOW};\n\n  VisibilityType visibility_{DefaultComputedStyle::DEFAULT_VISIBILITY};\n  PointerEventsType pointer_events_ = PointerEventsType::kAuto;\n\n  /************ css style property end ***************************/\n\n  tasm::CSSParserConfigs parser_configs_;\n  bool default_overflow_visible_ = false;\n  bool css_align_with_legacy_w3c_ = false;\n\n  void ResetOverflow();\n\n// style setter by CSSValue\n#define SET_WITH_CSS_VALUE(name, css_name, default_value) \\\n  bool Set##name(const tasm::CSSValue& value, const bool reset = false);\n  FOREACH_ALL_PROPERTY(SET_WITH_CSS_VALUE)\n#undef SET_WITH_CSS_VALUE\n\n// platform style getter\n#define FOREACH_PLATFORM_PROPERTY(V)     \\\n  V(Opacity)                             \\\n  V(Position)                            \\\n  V(Overflow)                            \\\n  V(OverflowX)                           \\\n  V(OverflowY)                           \\\n  V(FontSize)                            \\\n  V(LineHeight)                          \\\n  V(LetterSpacing)                       \\\n  V(LineSpacing)                         \\\n  V(Color)                               \\\n  V(Background)                          \\\n  V(BackgroundClip)                      \\\n  V(BackgroundColor)                     \\\n  V(BackgroundImage)                     \\\n  V(BackgroundOrigin)                    \\\n  V(BackgroundPosition)                  \\\n  V(BackgroundRepeat)                    \\\n  V(BackgroundSize)                      \\\n  V(MaskImage)                           \\\n  V(MaskSize)                            \\\n  V(MaskOrigin)                          \\\n  V(MaskClip)                            \\\n  V(MaskPosition)                        \\\n  V(MaskRepeat)                          \\\n  V(Filter)                              \\\n  V(BorderLeftColor)                     \\\n  V(BorderRightColor)                    \\\n  V(BorderTopColor)                      \\\n  V(BorderBottomColor)                   \\\n  V(BorderLeftWidth)                     \\\n  V(BorderRightWidth)                    \\\n  V(BorderTopWidth)                      \\\n  V(BorderBottomWidth)                   \\\n  V(Transform)                           \\\n  V(TransformOrigin)                     \\\n  V(Animation)                           \\\n  V(AnimationName)                       \\\n  V(AnimationDuration)                   \\\n  V(AnimationTimingFunction)             \\\n  V(AnimationDelay)                      \\\n  V(AnimationIterationCount)             \\\n  V(AnimationDirection)                  \\\n  V(AnimationFillMode)                   \\\n  V(AnimationPlayState)                  \\\n  V(LayoutAnimationCreateDuration)       \\\n  V(LayoutAnimationCreateTimingFunction) \\\n  V(LayoutAnimationCreateDelay)          \\\n  V(LayoutAnimationCreateProperty)       \\\n  V(LayoutAnimationDeleteDuration)       \\\n  V(LayoutAnimationDeleteTimingFunction) \\\n  V(LayoutAnimationDeleteDelay)          \\\n  V(LayoutAnimationDeleteProperty)       \\\n  V(LayoutAnimationUpdateDuration)       \\\n  V(LayoutAnimationUpdateTimingFunction) \\\n  V(LayoutAnimationUpdateDelay)          \\\n  V(Transition)                          \\\n  V(TransitionProperty)                  \\\n  V(TransitionDuration)                  \\\n  V(TransitionDelay)                     \\\n  V(TransitionTimingFunction)            \\\n  V(EnterTransitionName)                 \\\n  V(ExitTransitionName)                  \\\n  V(PauseTransitionName)                 \\\n  V(ResumeTransitionName)                \\\n  V(Visibility)                          \\\n  V(BorderLeftStyle)                     \\\n  V(BorderRightStyle)                    \\\n  V(BorderTopStyle)                      \\\n  V(BorderBottomStyle)                   \\\n  V(OutlineColor)                        \\\n  V(OutlineStyle)                        \\\n  V(OutlineWidth)                        \\\n  V(BoxShadow)                           \\\n  V(BorderColor)                         \\\n  V(FontFamily)                          \\\n  V(CaretColor)                          \\\n  V(TextShadow)                          \\\n  V(Direction)                           \\\n  V(WhiteSpace)                          \\\n  V(FontWeight)                          \\\n  V(WordBreak)                           \\\n  V(FontStyle)                           \\\n  V(TextAlign)                           \\\n  V(TextOverflow)                        \\\n  V(TextDecoration)                      \\\n  V(TextDecorationColor)                 \\\n  V(ZIndex)                              \\\n  V(ImageRendering)                      \\\n  V(VerticalAlign)                       \\\n  V(BorderRadius)                        \\\n  V(BorderTopLeftRadius)                 \\\n  V(BorderTopRightRadius)                \\\n  V(BorderBottomRightRadius)             \\\n  V(BorderBottomLeftRadius)              \\\n  V(ListMainAxisGap)                     \\\n  V(ListCrossAxisGap)                    \\\n  V(Perspective)                         \\\n  V(Cursor)                              \\\n  V(TextIndent)                          \\\n  V(ClipPath)                            \\\n  V(TextStroke)                          \\\n  V(TextStrokeWidth)                     \\\n  V(TextStrokeColor)                     \\\n  V(XAutoFontSize)                       \\\n  V(XAutoFontSizePresetSizes)            \\\n  V(Hyphens)                             \\\n  V(XAppRegion)                          \\\n  V(XHandleSize)                         \\\n  V(XHandleColor)                        \\\n  V(OffsetDistance)                      \\\n  V(OffsetPath)                          \\\n  V(OffsetRotate)                        \\\n  V(FontVariationSettings)               \\\n  V(FontFeatureSettings)                 \\\n  V(FontOpticalSizing)                   \\\n  V(XPlaceholderColor)                   \\\n  V(XPlaceholderFontFamily)              \\\n  V(XPlaceholderFontSize)                \\\n  V(XPlaceholderFontWeight)              \\\n  V(XPlaceholderFontStyle)               \\\n  V(PointerEvents)\n\n#define GETTER_STYLE_STRING(name) lepus_value name##ToLepus();\n  FOREACH_PLATFORM_PROPERTY(GETTER_STYLE_STRING)\n#undef GETTER_STYLE_STRING\n\n// style inherit.\n#define FOREACH_PLATFORM_COMPLEX_INHERITABLE_PROPERTY(V) \\\n  V(LineHeight)                                          \\\n  V(LetterSpacing)                                       \\\n  V(LineSpacing)\n\n  static const base::InlineOrderedFlatSet<tasm::CSSPropertyID, 3>&\n  GetPlatformInheritableProperty();\n\n#define INHERIT_CSS_VALUE(name) \\\n  bool Inherit##name(const ComputedCSSStyle& from);\n  FOREACH_PLATFORM_COMPLEX_INHERITABLE_PROPERTY(INHERIT_CSS_VALUE)\n#undef INHERIT_CSS_VALUE\n\n private:\n  float GetBorderFinalWidth(float width, BorderStyleType style) const {\n    return (style != BorderStyleType::kNone && style != BorderStyleType::kHide)\n               ? width\n               : 0.f;\n  }\n\n  tasm::CSSIDBitset& GetChangedBitset() { return changed_bitset_; }\n  tasm::CSSIDBitset& GetResetBitset() { return reset_bitset_; }\n\n  void MarkReset(tasm::CSSPropertyID id) {\n    changed_bitset_.Reset(id);\n    reset_bitset_.Set(id);\n  }\n\n  void ClearChanged() { changed_bitset_.Reset(); }\n\n  void ClearReset() { reset_bitset_.Reset(); }\n\n  tasm::CSSIDBitset changed_bitset_;\n  tasm::CSSIDBitset reset_bitset_;\n\n  // TODO(songshourui.null): remove this when all the ##nameToLepus methods are\n  // deleted. In the long term, the PropBundleStyleWriter will optimize all\n  // Write methods, at which point all ##nameToLepus methods of ComputedCSSStyle\n  // can be deleted, and this friend class will also be removed.\n  friend class tasm::PropBundleStyleWriter;\n  // TODO(songshourui.null): Ditto,  remove this when all the ##nameToLepus\n  // methods are deleted.\n  friend class tasm::PseudoElement;\n};  // ComputedCSSStyle\n\n}  // namespace starlight\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_COMPUTED_CSS_STYLE_H_\n"
  },
  {
    "path": "core/renderer/css/computed_css_style_css_text_helper.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/computed_css_style_css_text_helper.h\"\n\n#include <cstdint>\n#include <sstream>\n#include <string>\n\n#include \"base/include/value/base_string.h\"\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/css/css_decoder.h\"\n#include \"core/renderer/css/transforms/transform_operations.h\"\n#include \"core/renderer/starlight/style/default_layout_style.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nnamespace {\nconstexpr const char kDefaultZIndex[] = \"0\";\nconstexpr const char kDefaultFilter[] = \"none\";\nconstexpr const char kDirectionLtr[] = \"ltr\";\nconstexpr const char kDirectionRtl[] = \"rtl\";\nconstexpr const char kDefaultBackgroundPosition[] = \"0% 0%\";\nconstexpr const char kDefaultBackgroundPositionDimensionValue[] = \"50%\";\nconstexpr const char* kPxMark = \"px\";\nconstexpr const char* kPercentageMark = \"%\";\nconstexpr const char kDefaultBorderRadius[] = \"0px 0px\";\nconstexpr CSSPropertyID kMarginIDs[] = {\n    kPropertyIDMarginTop, kPropertyIDMarginRight, kPropertyIDMarginBottom,\n    kPropertyIDMarginLeft};\nconstexpr CSSPropertyID kPaddingsIDs[] = {\n    kPropertyIDPaddingTop, kPropertyIDPaddingRight, kPropertyIDPaddingBottom,\n    kPropertyIDPaddingLeft};\nconstexpr CSSPropertyID kBorderWidthIDs[] = {\n    kPropertyIDBorderTopWidth, kPropertyIDBorderRightWidth,\n    kPropertyIDBorderBottomWidth, kPropertyIDBorderLeftWidth};\nconstexpr CSSPropertyID kBorderColorIDs[] = {\n    kPropertyIDBorderTopColor, kPropertyIDBorderRightColor,\n    kPropertyIDBorderBottomColor, kPropertyIDBorderLeftColor};\n\nstd::string NumericLengthToString(\n    const starlight::NLength::BaseLength& length) {\n  if (!length.HasValue()) {\n    return std::string(\"0px\");\n  } else if (length.ContainsFixedValue() && !length.ContainsPercentage()) {\n    return CSSDecoder::NumberToString(length.GetFixedPart()) + kPxMark;\n  } else if (!length.ContainsFixedValue() && length.ContainsPercentage()) {\n    return CSSDecoder::NumberToString(length.GetPercentagePart()) +\n           kPercentageMark;\n  } else {\n    // Ignore scenario when both fixed part and percentage part exists.\n  }\n\n  return \"0px\";\n}\n}  // namespace\n\nconst ComputedCSSStyleCssTextHelper::StyleStringValueGetter*\nComputedCSSStyleCssTextHelper::GetterFuncMap() {\n  static const StyleStringValueGetter* getter_func_map = []() {\n    static StyleStringValueGetter map[tasm::CSSPropertyID::kPropertyEnd] = {\n        nullptr};\n#define DECLARE_GET_COMPUTED_VALUE_SUPPORTED_GETTER(name) \\\n  map[tasm::CSSPropertyID::kPropertyID##name] =           \\\n      &ComputedCSSStyleCssTextHelper::name##CSSText;\n    FOREACH_GET_COMPUTED_VALUE_SUPPORTED_PROPERTY(\n        DECLARE_GET_COMPUTED_VALUE_SUPPORTED_GETTER)\n#undef DECLARE_GET_COMPUTED_VALUE_SUPPORTED_GETTER\n#undef FOREACH_GET_COMPUTED_VALUE_SUPPORTED_PROPERTY\n    return map;\n  }();\n\n  return getter_func_map;\n}\n\nbase::String ComputedCSSStyleCssTextHelper::ColorCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->text_attributes_) {\n    if (computed_css_style->text_attributes_->text_gradient.has_value() &&\n        computed_css_style->text_attributes_->text_gradient->IsArray()) {\n      return base::String();\n    } else {\n      return Uint32ToRGBString(\n          computed_css_style->text_attributes_->color.has_value()\n              ? *computed_css_style->text_attributes_->color\n              : starlight::DefaultColor::DEFAULT_TEXT_COLOR);\n    }\n  }\n\n  return base::String();\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BackgroundColorCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->background_data_) {\n    return Uint32ToRGBString(computed_css_style->background_data_->color);\n  } else {\n    return base::String();\n  }\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderColorCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  base::Vector<base::String> border_colors;\n  border_colors.reserve(4);\n  for (auto border_color_id : kBorderColorIDs) {\n    border_colors.emplace_back(\n        ComputedCSSStyleCssTextHelper::GetComputedStyleByPropertyID(\n            border_color_id, computed_css_style, ref_layout_result));\n  }\n\n  return ConcatStringsWithWhitespace(border_colors);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderTopColorCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return Uint32ToRGBString(computed_css_style->layout_computed_style_\n                                 .surround_data_.border_data_->color_top);\n  }\n  return Uint32ToRGBString(starlight::DefaultColor::DEFAULT_BORDER_COLOR);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderRightColorCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return Uint32ToRGBString(computed_css_style->layout_computed_style_\n                                 .surround_data_.border_data_->color_right);\n  }\n  return Uint32ToRGBString(starlight::DefaultColor::DEFAULT_BORDER_COLOR);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderBottomColorCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return Uint32ToRGBString(computed_css_style->layout_computed_style_\n                                 .surround_data_.border_data_->color_bottom);\n  }\n  return Uint32ToRGBString(starlight::DefaultColor::DEFAULT_BORDER_COLOR);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderLeftColorCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return Uint32ToRGBString(computed_css_style->layout_computed_style_\n                                 .surround_data_.border_data_->color_left);\n  }\n  return Uint32ToRGBString(starlight::DefaultColor::DEFAULT_BORDER_COLOR);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::LeftCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(ref_layout_result.offset_.X());\n}\n\nbase::String ComputedCSSStyleCssTextHelper::TopCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(ref_layout_result.offset_.Y());\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BottomCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(ref_layout_result.offset_.Y() +\n                            ref_layout_result.size_.height_);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::RightCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(ref_layout_result.offset_.X() +\n                            ref_layout_result.size_.width_);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::WidthCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(ref_layout_result.size_.width_);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::HeightCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(ref_layout_result.size_.height_);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::TransformCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->transform_raw_) {\n    auto transform_operations = transforms::TransformOperations(\n        ref_layout_result, *(computed_css_style->transform_raw_));\n    return transform_operations.CssText();\n  } else {\n    return base::String();\n  }\n}\n\nbase::String ComputedCSSStyleCssTextHelper::TransformOriginCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  base::Vector<base::String> tokens;\n  tokens.reserve(2);\n  tokens.emplace_back(FloatToPixelString(\n      starlight::NLengthToLayoutUnit(\n          computed_css_style->transform_origin_->x,\n          starlight::LayoutUnit(ref_layout_result.size_.width_))\n          .ToFloat()));\n  tokens.emplace_back(FloatToPixelString(\n      starlight::NLengthToLayoutUnit(\n          computed_css_style->transform_origin_->y,\n          starlight::LayoutUnit(ref_layout_result.size_.height_))\n          .ToFloat()));\n  return ConcatStringsWithWhitespace(tokens);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::OpacityCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return base::String(CSSDecoder::NumberToString(computed_css_style->opacity_));\n}\n\nbase::String ComputedCSSStyleCssTextHelper::ZIndexCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->HasZIndex()) {\n    return base::String(\n        CSSDecoder::NumberToString(computed_css_style->GetZIndex()));\n  }\n  return BASE_STATIC_STRING(kDefaultZIndex);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::FilterCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (!computed_css_style->filter_.has_value()) {\n    return BASE_STATIC_STRING(kDefaultFilter);\n  }\n\n  std::ostringstream filter_sstream;\n  if (computed_css_style->filter_->type == starlight::FilterType::kBlur) {\n    filter_sstream << \"blur(\";\n    filter_sstream << CSSDecoder::NumberToString(\n        starlight::NLengthToLayoutUnit(computed_css_style->filter_->amount,\n                                       starlight::LayoutUnit::Indefinite())\n            .ToFloat());\n    filter_sstream << \"px)\";\n  } else if (computed_css_style->filter_->type ==\n             starlight::FilterType::kGrayscale) {\n    filter_sstream << \"grayscale(\";\n    filter_sstream << computed_css_style->filter_->amount.NumericLength()\n                              .GetPercentagePart() /\n                          100;\n    filter_sstream << \")\";\n  } else {\n    filter_sstream << \"none\";\n  }\n\n  return base::String(filter_sstream.str());\n}\n\nbase::String ComputedCSSStyleCssTextHelper::DirectionCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return computed_css_style->GetDirection() == starlight::DirectionType::kRtl ||\n                 computed_css_style->GetDirection() ==\n                     starlight::DirectionType::kLynxRtl\n             ? BASE_STATIC_STRING(kDirectionRtl)\n             : BASE_STATIC_STRING(kDirectionLtr);\n}\n\nbase::Vector<base::String>\nComputedCSSStyleCssTextHelper::ParseBackgroundPositionArray(\n    starlight::ComputedCSSStyle* computed_css_style) {\n  base::Vector<base::String> position_tokens;\n  const auto& position =\n      computed_css_style->background_data_->image_data->position;\n  uint32_t token_count = static_cast<uint32_t>(position.size()) / 2;\n  position_tokens.reserve(token_count);\n\n  for (uint32_t idx = 0; idx < token_count; idx++) {\n    base::Vector<base::String> tokens;\n    const auto& x_length = position[idx * 2];\n    const auto& y_length = position[idx * 2 + 1];\n\n    if (x_length.IsUnit() || x_length.IsPercent()) {\n      tokens.emplace_back(\n          base::String(NumericLengthToString(x_length.NumericLength())));\n    } else {\n      tokens.emplace_back(\n          BASE_STATIC_STRING(kDefaultBackgroundPositionDimensionValue));\n    }\n\n    if (y_length.IsUnit() || y_length.IsPercent()) {\n      tokens.emplace_back(\n          base::String(NumericLengthToString(y_length.NumericLength())));\n    } else {\n      tokens.emplace_back(\n          BASE_STATIC_STRING(kDefaultBackgroundPositionDimensionValue));\n    }\n\n    position_tokens.emplace_back(ConcatStringsWithWhitespace(tokens));\n  }\n\n  return position_tokens;\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BackgroundPositionCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (!computed_css_style->background_data_.has_value() ||\n      !computed_css_style->background_data_->image_data.has_value() ||\n      computed_css_style->background_data_->image_data->image_count == 0) {\n    return BASE_STATIC_STRING(kDefaultBackgroundPosition);\n  }\n\n  const auto& image_data = *computed_css_style->background_data_->image_data;\n  auto image_count = image_data.image_count;\n\n  if (image_data.position.empty()) {\n    std::ostringstream position_stream;\n    for (uint32_t idx = 0; idx < image_count; idx++) {\n      position_stream << kDefaultBackgroundPosition;\n      if (idx < image_count - 1) {\n        position_stream << \", \";\n      }\n    }\n    return base::String(position_stream.str());\n  }\n\n  auto position_tokens = ParseBackgroundPositionArray(computed_css_style);\n  std::ostringstream position_stream;\n  const size_t token_count = position_tokens.size();\n\n  if (token_count == 0) {\n    for (uint32_t idx = 0; idx < image_count; idx++) {\n      position_stream << kDefaultBackgroundPosition;\n      if (idx < image_count - 1) {\n        position_stream << \", \";\n      }\n    }\n  } else {\n    for (uint32_t idx = 0; idx < image_count; idx++) {\n      position_stream << position_tokens[idx % token_count].str();\n      if (idx < image_count - 1) {\n        position_stream << \", \";\n      }\n    }\n  }\n\n  return base::String(position_stream.str());\n}\n\nbase::String ComputedCSSStyleCssTextHelper::PaddingCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  base::Vector<base::String> padding_tokens;\n  padding_tokens.reserve(4);\n  for (auto id : kPaddingsIDs) {\n    padding_tokens.emplace_back(GetComputedStyleByPropertyID(\n        id, computed_css_style, ref_layout_result));\n  }\n  return ConcatStringsWithWhitespace(padding_tokens);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::PaddingLeftCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(\n      ref_layout_result.padding_[starlight::Direction::kLeft]);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::PaddingRightCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(\n      ref_layout_result.padding_[starlight::Direction::kRight]);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::PaddingTopCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(\n      ref_layout_result.padding_[starlight::Direction::kTop]);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::PaddingBottomCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(\n      ref_layout_result.padding_[starlight::Direction::kBottom]);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::MarginCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  base::Vector<base::String> margin_tokens;\n  margin_tokens.reserve(4);\n  for (auto id : kMarginIDs) {\n    margin_tokens.emplace_back(GetComputedStyleByPropertyID(\n        id, computed_css_style, ref_layout_result));\n  }\n  return ConcatStringsWithWhitespace(margin_tokens);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::MarginTopCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(\n      ref_layout_result.margin_[starlight::Direction::kTop]);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::MarginBottomCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(\n      ref_layout_result.margin_[starlight::Direction::kBottom]);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::MarginLeftCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(\n      ref_layout_result.margin_[starlight::Direction::kLeft]);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::MarginRightCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  return FloatToPixelString(\n      ref_layout_result.margin_[starlight::Direction::kRight]);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderWidthCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  base::Vector<base::String> border_width_tokens;\n  border_width_tokens.reserve(4);\n  for (auto id : kBorderWidthIDs) {\n    border_width_tokens.emplace_back(GetComputedStyleByPropertyID(\n        id, computed_css_style, ref_layout_result));\n  }\n  return ConcatStringsWithWhitespace(border_width_tokens);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderLeftWidthCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return FloatToPixelString(computed_css_style->layout_computed_style_\n                                  .surround_data_.border_data_->width_left);\n  }\n  return FloatToPixelString(GetDefaultBorderWidth(computed_css_style));\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderRightWidthCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return FloatToPixelString(computed_css_style->layout_computed_style_\n                                  .surround_data_.border_data_->width_right);\n  }\n  return FloatToPixelString(GetDefaultBorderWidth(computed_css_style));\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderTopWidthCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return FloatToPixelString(computed_css_style->layout_computed_style_\n                                  .surround_data_.border_data_->width_top);\n  }\n  return FloatToPixelString(GetDefaultBorderWidth(computed_css_style));\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderBottomWidthCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return FloatToPixelString(computed_css_style->layout_computed_style_\n                                  .surround_data_.border_data_->width_bottom);\n  }\n  return FloatToPixelString(GetDefaultBorderWidth(computed_css_style));\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderRadiusCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  std::ostringstream border_radius_stream;\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    auto [top_left_rx, top_left_ry] = BorderRadiusPairCSSTextComponents(\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_x_top_left,\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_y_top_left,\n        ref_layout_result);\n    auto [top_right_rx, top_right_ry] = BorderRadiusPairCSSTextComponents(\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_x_top_right,\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_y_top_right,\n        ref_layout_result);\n    auto [bottom_right_rx, bottom_right_ry] = BorderRadiusPairCSSTextComponents(\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_x_bottom_right,\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_y_bottom_right,\n        ref_layout_result);\n    auto [bottom_left_rx, bottom_left_ry] = BorderRadiusPairCSSTextComponents(\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_x_bottom_left,\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_y_bottom_left,\n        ref_layout_result);\n    border_radius_stream << top_left_rx.str() << \" \" << top_right_rx.str()\n                         << \" \" << bottom_right_rx.str() << \" \"\n                         << bottom_left_rx.str() << \" / \" << top_left_ry.str()\n                         << \" \" << top_right_ry.str() << \" \"\n                         << bottom_right_ry.str() << \" \"\n                         << bottom_left_ry.str();\n  } else {\n    border_radius_stream << \"0px 0px 0px 0px / 0px 0px 0px 0px\";\n  }\n\n  return border_radius_stream.str();\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderTopRightRadiusCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return BorderRadiusPairCSSText(\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_x_top_right,\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_y_top_right,\n        ref_layout_result);\n  }\n\n  return BASE_STATIC_STRING(kDefaultBorderRadius);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderTopLeftRadiusCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return BorderRadiusPairCSSText(\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_x_top_left,\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_y_top_left,\n        ref_layout_result);\n  }\n\n  return BASE_STATIC_STRING(kDefaultBorderRadius);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderBottomLeftRadiusCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return BorderRadiusPairCSSText(\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_x_bottom_left,\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_y_bottom_left,\n        ref_layout_result);\n  }\n\n  return BASE_STATIC_STRING(kDefaultBorderRadius);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderBottomRightRadiusCSSText(\n    starlight::ComputedCSSStyle* computed_css_style,\n    starlight::LayoutResultForRendering ref_layout_result) {\n  if (computed_css_style->layout_computed_style_.surround_data_.border_data_\n          .has_value()) {\n    return BorderRadiusPairCSSText(\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_x_bottom_right,\n        computed_css_style->layout_computed_style_.surround_data_.border_data_\n            ->radius_y_bottom_right,\n        ref_layout_result);\n  }\n\n  return BASE_STATIC_STRING(kDefaultBorderRadius);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::BorderRadiusPairCSSText(\n    const starlight::NLength& rx, const starlight::NLength& ry,\n    const starlight::LayoutResultForRendering& ref_layout_result) {\n  base::Vector<base::String> tokens;\n  tokens.reserve(2);\n  auto [rx_str, ry_str] =\n      BorderRadiusPairCSSTextComponents(rx, ry, ref_layout_result);\n  tokens.emplace_back(rx_str);\n  tokens.emplace_back(ry_str);\n  return ConcatStringsWithWhitespace(tokens);\n}\n\nstd::tuple<base::String, base::String>\nComputedCSSStyleCssTextHelper::BorderRadiusPairCSSTextComponents(\n    const starlight::NLength& rx, const starlight::NLength& ry,\n    const starlight::LayoutResultForRendering& ref_layout_result) {\n  return std::make_tuple(\n      FloatToPixelString(\n          starlight::NLengthToLayoutUnit(\n              rx, starlight::LayoutUnit(ref_layout_result.size_.width_))\n              .ToFloat()),\n      FloatToPixelString(\n          starlight::NLengthToLayoutUnit(\n              ry, starlight::LayoutUnit(ref_layout_result.size_.height_))\n              .ToFloat()));\n}\n\nbase::String ComputedCSSStyleCssTextHelper::FloatToPixelString(float value) {\n  std::string num_value = CSSDecoder::NumberToString(value);\n\n  return base::String(num_value + \"px\");\n}\n\nbase::String ComputedCSSStyleCssTextHelper::Uint32ToRGBString(\n    uint32_t color_value) {\n  std::string result;\n  result.reserve(24);\n  uint32_t alpha = (color_value >> 24) & 0xFF;\n  uint32_t red = (color_value >> 16) & 0xFF;\n  uint32_t green = (color_value >> 8) & 0xFF;\n  uint32_t blue = color_value & 0xFF;\n\n  if (alpha != 255) {\n    result += \"argb(\";\n  } else {\n    result += \"rgb(\";\n  }\n  result += std::to_string(red);\n  result += \", \";\n  result += std::to_string(green);\n  result += \", \";\n  result += std::to_string(blue);\n  if (alpha != 255) {\n    result += \", \";\n    result += std::to_string(alpha / 255);\n  }\n  result += ')';\n  return base::String(result);\n}\n\nbase::String ComputedCSSStyleCssTextHelper::ConcatStringsWithWhitespace(\n    const base::Vector<base::String>& strings) {\n  if (strings.empty()) {\n    return base::String();\n  }\n  if (strings.size() == 1) {\n    return strings[0];\n  }\n  size_t total_length = 0;\n  for (const auto& str : strings) {\n    total_length += str.length();\n  }\n  total_length += strings.size() - 1;\n  std::string result;\n  result.reserve(total_length);\n  for (size_t i = 0; i < strings.size(); ++i) {\n    if (i > 0) {\n      result += ' ';\n    }\n    result += strings[i].c_str();\n  }\n  return base::String(result);\n}\n\nfloat ComputedCSSStyleCssTextHelper::GetDefaultBorderWidth(\n    starlight::ComputedCSSStyle* computed_css_style) {\n  return computed_css_style->css_align_with_legacy_w3c_\n             ? starlight::DefaultLayoutStyle::W3C_DEFAULT_BORDER\n             : starlight::DefaultLayoutStyle::SL_DEFAULT_BORDER;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/computed_css_style_css_text_helper.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_COMPUTED_CSS_STYLE_CSS_TEXT_HELPER_H_\n#define CORE_RENDERER_CSS_COMPUTED_CSS_STYLE_CSS_TEXT_HELPER_H_\n\n#include <cstdint>\n#include <tuple>\n\n#include \"base/include/value/base_string.h\"\n#include \"base/include/vector.h\"\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/starlight/types/layout_result.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass ComputedCSSStyleCssTextHelper {\n public:\n  ComputedCSSStyleCssTextHelper() = default;\n  ~ComputedCSSStyleCssTextHelper() = default;\n\n  base::String GetComputedStyleByPropertyID(\n      tasm::CSSPropertyID id, starlight::ComputedCSSStyle* computed_css_style,\n      starlight::LayoutResultForRendering ref_layout_result) {\n    const auto* funcMap = GetterFuncMap();\n    if (id > tasm::CSSPropertyID::kPropertyStart &&\n        id < tasm::CSSPropertyID::kPropertyEnd) {\n      if (StyleStringValueGetter func = funcMap[id]) {\n        return (this->*func)(computed_css_style, ref_layout_result);\n      }\n    }\n    return base::String();\n  }\n\n private:\n  using StyleStringValueGetter = base::String (\n      ComputedCSSStyleCssTextHelper::*)(starlight::ComputedCSSStyle*,\n                                        starlight::LayoutResultForRendering);\n  static const StyleStringValueGetter* GetterFuncMap();\n\n#define FOREACH_GET_COMPUTED_VALUE_SUPPORTED_PROPERTY(V) \\\n  V(Top)                                                 \\\n  V(Bottom)                                              \\\n  V(Left)                                                \\\n  V(Right)                                               \\\n  V(Width)                                               \\\n  V(Height)                                              \\\n  V(Transform)                                           \\\n  V(TransformOrigin)                                     \\\n  V(Opacity)                                             \\\n  V(Color)                                               \\\n  V(BackgroundColor)                                     \\\n  V(ZIndex)                                              \\\n  V(Filter)                                              \\\n  V(Direction)                                           \\\n  V(BackgroundPosition)                                  \\\n  V(Padding)                                             \\\n  V(PaddingTop)                                          \\\n  V(PaddingBottom)                                       \\\n  V(PaddingLeft)                                         \\\n  V(PaddingRight)                                        \\\n  V(Margin)                                              \\\n  V(MarginTop)                                           \\\n  V(MarginBottom)                                        \\\n  V(MarginLeft)                                          \\\n  V(MarginRight)                                         \\\n  V(BorderWidth)                                         \\\n  V(BorderTopWidth)                                      \\\n  V(BorderBottomWidth)                                   \\\n  V(BorderLeftWidth)                                     \\\n  V(BorderRightWidth)                                    \\\n  V(BorderRadius)                                        \\\n  V(BorderTopLeftRadius)                                 \\\n  V(BorderTopRightRadius)                                \\\n  V(BorderBottomRightRadius)                             \\\n  V(BorderBottomLeftRadius)                              \\\n  V(BorderColor)                                         \\\n  V(BorderTopColor)                                      \\\n  V(BorderRightColor)                                    \\\n  V(BorderBottomColor)                                   \\\n  V(BorderLeftColor)\n\n#define GETTER_STYLE_STRING_VALUE(name)                \\\n  base::String name##CSSText(                          \\\n      starlight::ComputedCSSStyle* computed_css_style, \\\n      starlight::LayoutResultForRendering ref_layout_result);\n  FOREACH_GET_COMPUTED_VALUE_SUPPORTED_PROPERTY(GETTER_STYLE_STRING_VALUE)\n#undef GETTER_STYLE_STRING_VALUE\n\n  base::Vector<base::String> ParseBackgroundPositionArray(\n      starlight::ComputedCSSStyle* computed_css_style);\n\n  base::String FloatToPixelString(float value);\n\n  base::String Uint32ToRGBString(uint32_t color_value);\n\n  base::String ConcatStringsWithWhitespace(\n      const base::Vector<base::String>& strings);\n\n  base::String BorderRadiusPairCSSText(\n      const starlight::NLength& rx, const starlight::NLength& ry,\n      const starlight::LayoutResultForRendering& ref_layout_result);\n\n  std::tuple<base::String, base::String> BorderRadiusPairCSSTextComponents(\n      const starlight::NLength& rx, const starlight::NLength& ry,\n      const starlight::LayoutResultForRendering& ref_layout_result);\n\n  float GetDefaultBorderWidth(starlight::ComputedCSSStyle* computed_css_style);\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_COMPUTED_CSS_STYLE_CSS_TEXT_HELPER_H_\n"
  },
  {
    "path": "core/renderer/css/computed_css_style_css_text_helper_unittest.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/renderer/css/computed_css_style_css_text_helper.h\"\n\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/text_attributes.h\"\n#include \"core/renderer/starlight/types/layout_result.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nnamespace {\nCSSValue parseBackgroundPositionStringValue(const lepus::Value& value_str,\n                                            const CSSParserConfigs& configs) {\n  CSSStringParser parser = CSSStringParser::FromLepusString(value_str, configs);\n  return parser.ParseBackgroundPosition();\n}\n}  // namespace\n\nTEST(ComputedCSSStyleCssTextHelperTest, floatToPixelString) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  EXPECT_EQ(helper.FloatToPixelString(1.0f), \"1px\");\n  EXPECT_EQ(helper.FloatToPixelString(1.00000000000f), \"1px\");\n  EXPECT_EQ(helper.FloatToPixelString(1.56f), \"1.56px\");\n  EXPECT_EQ(helper.FloatToPixelString(0.0f), \"0px\");\n  EXPECT_EQ(helper.FloatToPixelString(-1.0f), \"-1px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, Uint32ToRGBString) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  // uint32_t red = 0xFFFF0000;\n  EXPECT_EQ(helper.Uint32ToRGBString(0xFFFF0000), \"rgb(255, 0, 0)\");\n  EXPECT_EQ(helper.Uint32ToRGBString(0xFF0000FF), \"rgb(0, 0, 255)\");\n  EXPECT_EQ(helper.Uint32ToRGBString(0xFF00FF00), \"rgb(0, 255, 0)\");\n  EXPECT_EQ(helper.Uint32ToRGBString(0xFFFFFFFF), \"rgb(255, 255, 255)\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, ConcatStringsWithWhitespace) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  EXPECT_EQ(helper.ConcatStringsWithWhitespace({}), \"\");\n  EXPECT_EQ(helper.ConcatStringsWithWhitespace({\"a\"}), \"a\");\n  EXPECT_EQ(helper.ConcatStringsWithWhitespace({\"a\", \"b\", \"c\"}), \"a b c\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, OpacityCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  computed_css_style.opacity_ = 0.5f;\n  EXPECT_EQ(helper.OpacityCSSText(&computed_css_style,\n                                  starlight::LayoutResultForRendering()),\n            \"0.5\");\n  computed_css_style.opacity_ = 0.900000f;\n  EXPECT_EQ(helper.OpacityCSSText(&computed_css_style,\n                                  starlight::LayoutResultForRendering()),\n            \"0.9\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, TransformOriginCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.transform_origin_);\n  computed_css_style.transform_origin_->x =\n      starlight::NLength::MakeUnitNLength(100.f);\n  computed_css_style.transform_origin_->y =\n      starlight::NLength::MakeUnitNLength(100.f);\n  EXPECT_EQ(helper.TransformOriginCSSText(\n                &computed_css_style, starlight::LayoutResultForRendering()),\n            \"100px 100px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, SizeCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result = {};\n  layout_result.size_ = FloatSize(100.f, 100.f);\n  EXPECT_EQ(helper.HeightCSSText(&computed_css_style, layout_result), \"100px\");\n  EXPECT_EQ(helper.WidthCSSText(&computed_css_style, layout_result), \"100px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, FourSidesCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result = {};\n  layout_result.size_ = FloatSize(100.f, 100.f);\n  layout_result.offset_ = starlight::FloatPoint(20.f, 10.f);\n  EXPECT_EQ(helper.LeftCSSText(&computed_css_style, layout_result), \"20px\");\n  EXPECT_EQ(helper.TopCSSText(&computed_css_style, layout_result), \"10px\");\n  EXPECT_EQ(helper.RightCSSText(&computed_css_style, layout_result), \"120px\");\n  EXPECT_EQ(helper.BottomCSSText(&computed_css_style, layout_result), \"110px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BackgroundColorCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.background_data_);\n  computed_css_style.background_data_->color = 0xFF0000FF;\n  EXPECT_EQ(helper.BackgroundColorCSSText(\n                &computed_css_style, starlight::LayoutResultForRendering()),\n            \"rgb(0, 0, 255)\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, ColorCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  computed_css_style.text_attributes_ = starlight::TextAttributes(14);\n  computed_css_style.text_attributes_->color = 0xFF0000FF;\n  EXPECT_EQ(helper.ColorCSSText(&computed_css_style,\n                                starlight::LayoutResultForRendering()),\n            \"rgb(0, 0, 255)\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, UnsupportedCSSProperty) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  EXPECT_EQ(helper.GetComputedStyleByPropertyID(\n                tasm::CSSPropertyID::kPropertyEnd, &computed_css_style,\n                starlight::LayoutResultForRendering()),\n            \"\");\n  EXPECT_EQ(helper.GetComputedStyleByPropertyID(\n                tasm::CSSPropertyID::kPropertyStart, &computed_css_style,\n                starlight::LayoutResultForRendering()),\n            \"\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, GetComputedStyleByPropertyID) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  computed_css_style.opacity_ = 0.5f;\n  EXPECT_EQ(helper.GetComputedStyleByPropertyID(\n                tasm::CSSPropertyID::kPropertyIDOpacity, &computed_css_style,\n                starlight::LayoutResultForRendering()),\n            \"0.5\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, ZIndexCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  computed_css_style.SetEnableZIndex(true);\n  computed_css_style.SetZIndex(\n      CSSValue(lepus::Value(100), CSSValuePattern::NUMBER,\n               CSSValueType::DEFAULT),\n      false);\n  EXPECT_EQ(helper.GetComputedStyleByPropertyID(\n                tasm::CSSPropertyID::kPropertyIDZIndex, &computed_css_style,\n                starlight::LayoutResultForRendering()),\n            \"100\");\n\n  computed_css_style.SetZIndex(\n      CSSValue(lepus::Value(0), CSSValuePattern::NUMBER, CSSValueType::DEFAULT),\n      true);\n  EXPECT_EQ(helper.GetComputedStyleByPropertyID(\n                tasm::CSSPropertyID::kPropertyIDZIndex, &computed_css_style,\n                starlight::LayoutResultForRendering()),\n            \"0\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, FilterCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  EXPECT_EQ(helper.GetComputedStyleByPropertyID(\n                tasm::CSSPropertyID::kPropertyIDFilter, &computed_css_style,\n                starlight::LayoutResultForRendering()),\n            \"none\");\n\n  starlight::CSSStyleUtils::PrepareOptional(computed_css_style.filter_);\n  (*(computed_css_style.filter_)).type = starlight::FilterType::kBlur;\n  (*(computed_css_style.filter_)).amount =\n      starlight::NLength::MakeUnitNLength(10);\n  EXPECT_EQ(helper.GetComputedStyleByPropertyID(\n                tasm::CSSPropertyID::kPropertyIDFilter, &computed_css_style,\n                starlight::LayoutResultForRendering()),\n            \"blur(10px)\");\n\n  (*(computed_css_style.filter_)).type = starlight::FilterType::kGrayscale;\n  (*(computed_css_style.filter_)).amount =\n      starlight::NLength::MakePercentageNLength(28);\n  EXPECT_EQ(helper.GetComputedStyleByPropertyID(\n                tasm::CSSPropertyID::kPropertyIDFilter, &computed_css_style,\n                starlight::LayoutResultForRendering()),\n            \"grayscale(0.28)\");\n\n  computed_css_style.SetFilter(\n      CSSValue(lepus::Value(0), CSSValuePattern::NUMBER, CSSValueType::DEFAULT),\n      true);\n  EXPECT_EQ(helper.GetComputedStyleByPropertyID(\n                tasm::CSSPropertyID::kPropertyIDFilter, &computed_css_style,\n                starlight::LayoutResultForRendering()),\n            \"none\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, DirectionCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n\n  // Test kNormal (should return \"ltr\")\n  computed_css_style.SetValue(tasm::CSSPropertyID::kPropertyIDDirection,\n                              CSSValue(starlight::DirectionType::kNormal),\n                              false);\n  EXPECT_EQ(helper.DirectionCSSText(&computed_css_style,\n                                    starlight::LayoutResultForRendering()),\n            \"ltr\");\n\n  // Test kLtr (should return \"ltr\")\n  computed_css_style.SetValue(tasm::CSSPropertyID::kPropertyIDDirection,\n                              CSSValue(starlight::DirectionType::kLtr), false);\n  EXPECT_EQ(helper.DirectionCSSText(&computed_css_style,\n                                    starlight::LayoutResultForRendering()),\n            \"ltr\");\n\n  // Test kLynxRtl (should return \"rtl\")\n  computed_css_style.SetValue(tasm::CSSPropertyID::kPropertyIDDirection,\n                              CSSValue(starlight::DirectionType::kLynxRtl),\n                              false);\n  EXPECT_EQ(helper.DirectionCSSText(&computed_css_style,\n                                    starlight::LayoutResultForRendering()),\n            \"rtl\");\n\n  // Test kRtl (should return \"rtl\")\n  computed_css_style.SetValue(tasm::CSSPropertyID::kPropertyIDDirection,\n                              CSSValue(starlight::DirectionType::kRtl), false);\n  EXPECT_EQ(helper.DirectionCSSText(&computed_css_style,\n                                    starlight::LayoutResultForRendering()),\n            \"rtl\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest,\n     BackgroundPositionCSSTextOneValueSyntax) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n  CSSParserConfigs configs;\n\n  // Prepare background data and image data\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.background_data_);\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.background_data_->image_data);\n  auto& image_data = computed_css_style.background_data_->image_data.value();\n\n  // Default value as center\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 0%\");\n\n  image_data.image_count = 1;\n\n  // Test 1-value syntax with `top` keyword (x should default to 50%, y = 0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"top\"), configs), false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 0%\");\n\n  // Test 1-value syntax with `bottom` keyword (x=should default to 50%, y =\n  // 100%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"bottom\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 100%\");\n\n  // Test 1-value syntax with `left` keyword (x=0%, y should default to 50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"left\"), configs), false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 50%\");\n\n  // Test 1-value syntax with `right` keyword (x=100%, y should default to 50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"right\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"100% 50%\");\n\n  // Test 1-value syntax with `center` keyword\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"center\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 50%\");\n\n  // Test 1-value syntax with percentage value(x = assigned percentage value, y\n  // should default to 50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"25%\"), configs), false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"25% 50%\");\n\n  // Test 1-value syntax with numeric value(x = assigned numeric value, y should\n  // default to 50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"25px\"), configs), false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"25px 50%\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest,\n     BackgroundPositionCSSTextTwoValuesSyntax) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n  CSSParserConfigs configs;\n\n  // Prepare background data and image data\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.background_data_);\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.background_data_->image_data);\n  auto& image_data = computed_css_style.background_data_->image_data.value();\n\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 0%\");\n\n  image_data.image_count = 1;\n\n  // Test 2-value syntax: top left (x=0%, y=0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"top left\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 0%\");\n\n  // Test 2-value syntax: left top (x=0%, y=0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"left top\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 0%\");\n\n  // Test 2-value syntax: bottom left (x=0%, y=100%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"bottom left\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 100%\");\n\n  // Test 2-value syntax: left bottom (x=0%, y=0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"left bottom\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 100%\");\n\n  // Test 2-value syntax: top right (x=100%, y=0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"top right\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"100% 0%\");\n\n  // Test 2-value syntax: right top (x=100%, y=0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"right top\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"100% 0%\");\n\n  // Test 2-value syntax: bottom right (x=100%, y=100%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"bottom right\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"100% 100%\");\n\n  // Test 2-value syntax: right bottom (x=100%, y=100%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"right bottom\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"100% 100%\");\n\n  // Test 2-value syntax: center center (x=50%, y=50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"center center\"),\n                                         configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 50%\");\n\n  // Test 2-value syntax: center top (x=50%, y=0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"center top\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 0%\");\n\n  // Test 2-value syntax: top center (x=50%, y=0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"top center\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 0%\");\n\n  // Test 2-value syntax: center bottom (x=50%, y=100%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"center bottom\"),\n                                         configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 100%\");\n\n  // Test 2-value syntax: bottom center (x=50%, y=100%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"bottom center\"),\n                                         configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 100%\");\n\n  // Test 2-value syntax: center right (x=100%, y=50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"center right\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"100% 50%\");\n\n  // Test 2-value syntax: right center (x=100%, y=50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"right center\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"100% 50%\");\n\n  // Test 2-value syntax: center left (x=0%, y=50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"center left\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 50%\");\n\n  // Test 2-value syntax: left center (x=0%, y=50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"left center\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 50%\");\n\n  // Test 2-value syntax: 25% center (x=25%, y=50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"25% center\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"25% 50%\");\n\n  // Test 2-value syntax: 25px center (x=25px, y=50%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"25px center\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"25px 50%\");\n\n  // Test 2-value syntax: 25% top (x=25%, y=0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"25% top\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"25% 0%\");\n\n  // Test 2-value syntax: 25px top (x=25px, y=0%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"25px top\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"25px 0%\");\n\n  // Test 2-value syntax: 25% bottom (x=25%, y=100%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"25% bottom\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"25% 100%\");\n\n  // Test 2-value syntax: 25px bottom (x=25px, y=100%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"25px bottom\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"25px 100%\");\n\n  // Test 2-value syntax: center 25% (x=50%, y=25%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"center 25%\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 25%\");\n\n  // Test 2-value syntax: center 25px (x=50%, y=25px)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"center 25px\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"50% 25px\");\n\n  // Test 2-value syntax: left 25% (x=0%, y=25%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"left 25%\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 25%\");\n\n  // Test 2-value syntax: left 25px (x=0%, y=25px)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"left 25px\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 25px\");\n\n  // Test 2-value syntax: right 25% (x=100%, y=25%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"right 25%\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"100% 25%\");\n\n  // Test 2-value syntax: right 25px (x=100%, y=25px)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"right 25px\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"100% 25px\");\n\n  // Test 2-value syntax: 25px 25% (x=25px, y=25%)\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"25px 25%\"), configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"25px 25%\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest,\n     BackgroundPositionCSSTextMultiBackgroundImages) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n  CSSParserConfigs configs;\n\n  // Prepare background data and image data\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.background_data_);\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.background_data_->image_data);\n  auto& image_data = computed_css_style.background_data_->image_data.value();\n\n  // Set multiple images\n  image_data.image_count = 4;\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"top left, right bottom\"),\n                                         configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 0%, 100% 100%, 0% 0%, 100% 100%\");\n\n  image_data.image_count = 3;\n\n  image_data.position.clear();\n  computed_css_style.SetBackgroundPosition(\n      parseBackgroundPositionStringValue(lepus::Value(\"top left, right bottom\"),\n                                         configs),\n      false);\n  EXPECT_EQ(\n      helper.BackgroundPositionCSSText(&computed_css_style, layout_result),\n      \"0% 0%, 100% 100%, 0% 0%\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, PaddingCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n\n  layout_result.padding_[starlight::Direction::kLeft] = 10.0f;\n  layout_result.padding_[starlight::Direction::kRight] = 20.0f;\n  layout_result.padding_[starlight::Direction::kTop] = 5.0f;\n  layout_result.padding_[starlight::Direction::kBottom] = 15.0f;\n\n  EXPECT_EQ(helper.PaddingLeftCSSText(&computed_css_style, layout_result),\n            \"10px\");\n  EXPECT_EQ(helper.PaddingRightCSSText(&computed_css_style, layout_result),\n            \"20px\");\n  EXPECT_EQ(helper.PaddingTopCSSText(&computed_css_style, layout_result),\n            \"5px\");\n  EXPECT_EQ(helper.PaddingBottomCSSText(&computed_css_style, layout_result),\n            \"15px\");\n\n  // top right bottom left order\n  EXPECT_EQ(helper.PaddingCSSText(&computed_css_style, layout_result),\n            \"5px 20px 15px 10px\");\n\n  layout_result.padding_[starlight::Direction::kLeft] = 10.0f;\n  layout_result.padding_[starlight::Direction::kRight] = 10.0f;\n  layout_result.padding_[starlight::Direction::kTop] = 10.0f;\n  layout_result.padding_[starlight::Direction::kBottom] = 10.0f;\n  EXPECT_EQ(helper.PaddingCSSText(&computed_css_style, layout_result),\n            \"10px 10px 10px 10px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, MarginCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n\n  layout_result.margin_[starlight::Direction::kLeft] = 10.0f;\n  layout_result.margin_[starlight::Direction::kRight] = 20.0f;\n  layout_result.margin_[starlight::Direction::kTop] = 5.0f;\n  layout_result.margin_[starlight::Direction::kBottom] = 15.0f;\n\n  EXPECT_EQ(helper.MarginLeftCSSText(&computed_css_style, layout_result),\n            \"10px\");\n  EXPECT_EQ(helper.MarginRightCSSText(&computed_css_style, layout_result),\n            \"20px\");\n  EXPECT_EQ(helper.MarginTopCSSText(&computed_css_style, layout_result), \"5px\");\n  EXPECT_EQ(helper.MarginBottomCSSText(&computed_css_style, layout_result),\n            \"15px\");\n\n  // top right bottom left order\n  EXPECT_EQ(helper.MarginCSSText(&computed_css_style, layout_result),\n            \"5px 20px 15px 10px\");\n\n  layout_result.margin_[starlight::Direction::kLeft] = -10.0f;\n  layout_result.margin_[starlight::Direction::kRight] = -20.0f;\n  layout_result.margin_[starlight::Direction::kTop] = -5.0f;\n  layout_result.margin_[starlight::Direction::kBottom] = -15.0f;\n  EXPECT_EQ(helper.MarginLeftCSSText(&computed_css_style, layout_result),\n            \"-10px\");\n  EXPECT_EQ(helper.MarginCSSText(&computed_css_style, layout_result),\n            \"-5px -20px -15px -10px\");\n\n  layout_result.margin_[starlight::Direction::kLeft] = 0.0f;\n  layout_result.margin_[starlight::Direction::kRight] = 0.0f;\n  layout_result.margin_[starlight::Direction::kTop] = 0.0f;\n  layout_result.margin_[starlight::Direction::kBottom] = 0.0f;\n  EXPECT_EQ(helper.MarginCSSText(&computed_css_style, layout_result),\n            \"0px 0px 0px 0px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BorderWidthCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n\n  // Test case: no border data, should return default values\n  EXPECT_EQ(helper.BorderTopWidthCSSText(&computed_css_style, layout_result),\n            \"0px\");\n  EXPECT_EQ(helper.BorderBottomWidthCSSText(&computed_css_style, layout_result),\n            \"0px\");\n  EXPECT_EQ(helper.BorderLeftWidthCSSText(&computed_css_style, layout_result),\n            \"0px\");\n  EXPECT_EQ(helper.BorderRightWidthCSSText(&computed_css_style, layout_result),\n            \"0px\");\n  EXPECT_EQ(helper.BorderWidthCSSText(&computed_css_style, layout_result),\n            \"0px 0px 0px 0px\");\n\n  computed_css_style.css_align_with_legacy_w3c_ = true;\n  // Test case: no border data, should return default values\n  EXPECT_EQ(helper.BorderTopWidthCSSText(&computed_css_style, layout_result),\n            \"3px\");\n  EXPECT_EQ(helper.BorderBottomWidthCSSText(&computed_css_style, layout_result),\n            \"3px\");\n  EXPECT_EQ(helper.BorderLeftWidthCSSText(&computed_css_style, layout_result),\n            \"3px\");\n  EXPECT_EQ(helper.BorderRightWidthCSSText(&computed_css_style, layout_result),\n            \"3px\");\n  EXPECT_EQ(helper.BorderWidthCSSText(&computed_css_style, layout_result),\n            \"3px 3px 3px 3px\");\n\n  // Test case: with border data\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.layout_computed_style_.surround_data_.border_data_);\n  auto& border_data = computed_css_style.layout_computed_style_.surround_data_\n                          .border_data_.value();\n\n  border_data.width_top = 10.0f;\n  border_data.width_right = 20.0f;\n  border_data.width_bottom = 30.0f;\n  border_data.width_left = 40.0f;\n\n  EXPECT_EQ(helper.BorderTopWidthCSSText(&computed_css_style, layout_result),\n            \"10px\");\n  EXPECT_EQ(helper.BorderRightWidthCSSText(&computed_css_style, layout_result),\n            \"20px\");\n  EXPECT_EQ(helper.BorderBottomWidthCSSText(&computed_css_style, layout_result),\n            \"30px\");\n  EXPECT_EQ(helper.BorderLeftWidthCSSText(&computed_css_style, layout_result),\n            \"40px\");\n  EXPECT_EQ(helper.BorderWidthCSSText(&computed_css_style, layout_result),\n            \"10px 20px 30px 40px\");\n\n  // Test case: equal border widths\n  border_data.width_top = 5.0f;\n  border_data.width_right = 5.0f;\n  border_data.width_bottom = 5.0f;\n  border_data.width_left = 5.0f;\n\n  EXPECT_EQ(helper.BorderWidthCSSText(&computed_css_style, layout_result),\n            \"5px 5px 5px 5px\");\n\n  // Test case: floating point values\n  border_data.width_top = 2.5f;\n  border_data.width_right = 3.75f;\n  border_data.width_bottom = 1.25f;\n  border_data.width_left = 0.5f;\n\n  EXPECT_EQ(helper.BorderTopWidthCSSText(&computed_css_style, layout_result),\n            \"2.5px\");\n  EXPECT_EQ(helper.BorderRightWidthCSSText(&computed_css_style, layout_result),\n            \"3.75px\");\n  EXPECT_EQ(helper.BorderBottomWidthCSSText(&computed_css_style, layout_result),\n            \"1.25px\");\n  EXPECT_EQ(helper.BorderLeftWidthCSSText(&computed_css_style, layout_result),\n            \"0.5px\");\n  EXPECT_EQ(helper.BorderWidthCSSText(&computed_css_style, layout_result),\n            \"2.5px 3.75px 1.25px 0.5px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BorderCornerRadiusCSSText_Defaults) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n  layout_result.size_ = FloatSize(200.f, 100.f);\n\n  EXPECT_EQ(\n      helper.BorderTopLeftRadiusCSSText(&computed_css_style, layout_result),\n      \"0px 0px\");\n  EXPECT_EQ(\n      helper.BorderTopRightRadiusCSSText(&computed_css_style, layout_result),\n      \"0px 0px\");\n  EXPECT_EQ(\n      helper.BorderBottomRightRadiusCSSText(&computed_css_style, layout_result),\n      \"0px 0px\");\n  EXPECT_EQ(\n      helper.BorderBottomLeftRadiusCSSText(&computed_css_style, layout_result),\n      \"0px 0px\");\n  EXPECT_EQ(helper.BorderRadiusCSSText(&computed_css_style, layout_result),\n            \"0px 0px 0px 0px / 0px 0px 0px 0px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest,\n     BorderCornerRadiusCSSText_ValuesAndSlashCombine) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n  layout_result.size_ = FloatSize(200.f, 100.f);\n\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.layout_computed_style_.surround_data_.border_data_);\n  auto& border_data = computed_css_style.layout_computed_style_.surround_data_\n                          .border_data_.value();\n\n  border_data.radius_x_top_left = starlight::NLength::MakeUnitNLength(10.f);\n  border_data.radius_y_top_left = starlight::NLength::MakeUnitNLength(5.f);\n\n  border_data.radius_x_top_right =\n      starlight::NLength::MakePercentageNLength(25.f);\n  border_data.radius_y_top_right = starlight::NLength::MakeUnitNLength(6.f);\n\n  border_data.radius_x_bottom_right = starlight::NLength::MakeUnitNLength(30.f);\n  border_data.radius_y_bottom_right =\n      starlight::NLength::MakePercentageNLength(20.f);\n\n  border_data.radius_x_bottom_left =\n      starlight::NLength::MakePercentageNLength(11.f);\n  border_data.radius_y_bottom_left = starlight::NLength::MakeUnitNLength(11.f);\n\n  EXPECT_EQ(\n      helper.BorderTopLeftRadiusCSSText(&computed_css_style, layout_result),\n      \"10px 5px\");\n  EXPECT_EQ(\n      helper.BorderTopRightRadiusCSSText(&computed_css_style, layout_result),\n      \"50px 6px\");\n  EXPECT_EQ(\n      helper.BorderBottomRightRadiusCSSText(&computed_css_style, layout_result),\n      \"30px 20px\");\n  EXPECT_EQ(\n      helper.BorderBottomLeftRadiusCSSText(&computed_css_style, layout_result),\n      \"22px 11px\");\n\n  EXPECT_EQ(helper.BorderRadiusCSSText(&computed_css_style, layout_result),\n            \"10px 50px 30px 22px / 5px 6px 20px 11px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BorderRadiusPairCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::LayoutResultForRendering layout_result;\n  layout_result.size_ = FloatSize(100.f, 80.f);\n\n  auto rx = starlight::NLength::MakePercentageNLength(50.f);\n  auto ry = starlight::NLength::MakeUnitNLength(24.f);\n  EXPECT_EQ(helper.BorderRadiusPairCSSText(rx, ry, layout_result), \"50px 24px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BorderRadiusPairCSSTextComponents) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::LayoutResultForRendering layout_result;\n  layout_result.size_ = FloatSize(100.f, 80.f);\n\n  auto rx = starlight::NLength::MakePercentageNLength(50.f);\n  auto ry = starlight::NLength::MakeUnitNLength(24.f);\n  auto tuple = helper.BorderRadiusPairCSSTextComponents(rx, ry, layout_result);\n  EXPECT_EQ(std::get<0>(tuple), \"50px\");\n  EXPECT_EQ(std::get<1>(tuple), \"24px\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BorderTopColorCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n\n  EXPECT_EQ(helper.BorderTopColorCSSText(&computed_css_style, layout_result),\n            \"rgb(0, 0, 0)\");\n\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.layout_computed_style_.surround_data_.border_data_);\n  computed_css_style.layout_computed_style_.surround_data_.border_data_\n      ->color_top = 0xFFFF0000;\n  EXPECT_EQ(helper.BorderTopColorCSSText(&computed_css_style, layout_result),\n            \"rgb(255, 0, 0)\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BorderRightColorCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n\n  EXPECT_EQ(helper.BorderRightColorCSSText(&computed_css_style, layout_result),\n            \"rgb(0, 0, 0)\");\n\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.layout_computed_style_.surround_data_.border_data_);\n  computed_css_style.layout_computed_style_.surround_data_.border_data_\n      ->color_right = 0xFF0000FF;\n  EXPECT_EQ(helper.BorderRightColorCSSText(&computed_css_style, layout_result),\n            \"rgb(0, 0, 255)\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BorderBottomColorCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n\n  EXPECT_EQ(helper.BorderBottomColorCSSText(&computed_css_style, layout_result),\n            \"rgb(0, 0, 0)\");\n\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.layout_computed_style_.surround_data_.border_data_);\n  computed_css_style.layout_computed_style_.surround_data_.border_data_\n      ->color_bottom = 0xFF00FF00;\n  EXPECT_EQ(helper.BorderBottomColorCSSText(&computed_css_style, layout_result),\n            \"rgb(0, 255, 0)\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BorderLeftColorCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n\n  EXPECT_EQ(helper.BorderLeftColorCSSText(&computed_css_style, layout_result),\n            \"rgb(0, 0, 0)\");\n\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.layout_computed_style_.surround_data_.border_data_);\n  computed_css_style.layout_computed_style_.surround_data_.border_data_\n      ->color_left = 0xFFFFFFFF;\n  EXPECT_EQ(helper.BorderLeftColorCSSText(&computed_css_style, layout_result),\n            \"rgb(255, 255, 255)\");\n}\n\nTEST(ComputedCSSStyleCssTextHelperTest, BorderColorCSSText) {\n  auto helper = ComputedCSSStyleCssTextHelper();\n  starlight::ComputedCSSStyle computed_css_style{1.f, 1.f};\n  starlight::LayoutResultForRendering layout_result;\n\n  EXPECT_EQ(helper.BorderColorCSSText(&computed_css_style, layout_result),\n            \"rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0)\");\n\n  starlight::CSSStyleUtils::PrepareOptional(\n      computed_css_style.layout_computed_style_.surround_data_.border_data_);\n  auto& border =\n      *computed_css_style.layout_computed_style_.surround_data_.border_data_;\n  border.color_top = 0xFFFF0000;\n  border.color_right = 0xFF0000FF;\n  border.color_bottom = 0xFF00FF00;\n  border.color_left = 0xFFFFFFFF;\n\n  EXPECT_EQ(helper.BorderColorCSSText(&computed_css_style, layout_result),\n            \"rgb(255, 0, 0) rgb(0, 0, 255) rgb(0, 255, 0) rgb(255, 255, 255)\");\n}\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_color.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_color.h\"\n\n#include <ctype.h>\n\n#include <algorithm>\n#include <cmath>\n#include <cstdint>\n#include <sstream>\n#include <vector>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/include/string/string_number_convert.h\"\n#include \"core/renderer/css/css_keywords.h\"\n\nnamespace lynx {\nnamespace tasm {\n\n#define ARRAY_SIZE(a) static_cast<int>(sizeof(a) / sizeof((a)[0]))\n\ntemplate <typename T>\nuint8_t clamp_css_byte(T i) {  // Clamp to integer 0 .. 255.\n  i = round(i);\n  return i < 0 ? 0 : i > 255 ? 255 : static_cast<uint8_t>(i);\n}\n\ntemplate <typename T>\nfloat clamp_css_float(T f) {  // Clamp to float 0.0 .. 1.0.\n  return f < 0 ? 0 : f > 1 ? 1 : static_cast<float>(f);\n}\nbool parse_css_int(const std::string& str,\n                   uint8_t& output) {  // int or percentage.\n  int64_t i = 0;\n  if (str.length() && str[str.length() - 1] == '%') {\n    if (UNLIKELY(!base::StringToInt(str.substr(0, str.length() - 1), i, 10))) {\n      return false;\n    }\n    output = clamp_css_byte(i / 100.0f * 255.0f);\n    return true;\n  } else {\n    if (UNLIKELY(!base::StringToInt(str.substr(0, str.length()), i, 10))) {\n      return false;\n    }\n    output = clamp_css_byte(i);\n    return true;\n  }\n}\n\nbool parse_css_float(const std::string& str,\n                     float& output) {  // float or percentage.\n  double d = 0;\n  if (str.length() && str[str.length() - 1] == '%') {\n    if (UNLIKELY(\n            !base::StringToDouble(str.substr(0, str.length() - 1), d, true))) {\n      return false;\n    }\n    output = clamp_css_float(d / 100.0f);\n    return true;\n  } else {\n    if (UNLIKELY(!base::StringToDouble(str.substr(0, str.length()), d, true))) {\n      return false;\n    }\n    output = clamp_css_float(d);\n    return true;\n  }\n}\n\nfloat css_hue_to_rgb(float m1, float m2, float h) {\n  // Wrap hue to [0, 1] range using fmod for efficiency with large values.\n  // Using fmodf for float precision and handling negative values correctly.\n  h = fmodf(h, 1.0f);\n  if (h < 0.0f) {\n    h += 1.0f;\n  }\n\n  if (h * 6.0f < 1.0f) {\n    return m1 + (m2 - m1) * h * 6.0f;\n  }\n  if (h * 2.0f < 1.0f) {\n    return m2;\n  }\n  if (h * 3.0f < 2.0f) {\n    return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0f;\n  }\n  return m1;\n}\n\nstd::vector<std::string> split(const std::string& s, char delim) {\n  std::vector<std::string> elems;\n  std::stringstream ss(s);\n  std::string item;\n  while (std::getline(ss, item, delim)) {\n    elems.push_back(item);\n  }\n  return elems;\n}\n\nbool CSSColor::Parse(const std::string& color_str, CSSColor& color) {\n  if (color_str.empty()) return false;\n  std::string str = color_str;\n\n  // Remove all whitespace, not compliant, but should just be more accepting.\n  str.erase(std::remove(str.begin(), str.end(), ' '), str.end());\n\n  // Convert to lowercase.\n  std::transform(str.begin(), str.end(), str.begin(), ::tolower);\n\n  // #abc and #abc123 syntax.\n  if (str.length() && str[0] == '#') {\n    if (str.length() == 4) {\n      int64_t iv = 0;\n      if (UNLIKELY(!base::StringToInt(str.substr(1), iv, 16))) {\n        return false;\n      }\n      if (!(iv >= 0 && iv <= 0xfff)) {\n        return false;\n      } else {\n        color = {\n            static_cast<uint8_t>(((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8)),\n            static_cast<uint8_t>((iv & 0xf0) | ((iv & 0xf0) >> 4)),\n            static_cast<uint8_t>((iv & 0xf) | ((iv & 0xf) << 4)), 1};\n        return true;\n      }\n    } else if (str.length() == 5) {\n      // format like #abcd will be parserd as #aabbccdd\n      int64_t iv = 0;\n      if (UNLIKELY(!base::StringToInt(str.substr(1), iv, 16))) {\n        return false;\n      }\n      if (!(iv >= 0 && iv <= 0xffff)) {\n        return false;\n      } else {\n        color = {\n            static_cast<uint8_t>(((iv & 0xf000) >> 8) | ((iv & 0xf000) >> 12)),\n            static_cast<uint8_t>(((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8)),\n            static_cast<uint8_t>((iv & 0xf0) | ((iv & 0xf0) >> 4)),\n            ((iv & 0xf) | ((iv & 0xf) << 4)) / 255.0f};\n        return true;\n      }\n    } else if (str.length() == 7) {\n      int64_t iv = 0;\n      if (UNLIKELY(!base::StringToInt(str.substr(1), iv, 16))) {\n        return false;\n      }\n      if (!(iv >= 0 && iv <= 0xffffff)) {\n        return false;  // Covers NaN.\n      } else {\n        color = {static_cast<uint8_t>((iv & 0xff0000) >> 16),\n                 static_cast<uint8_t>((iv & 0xff00) >> 8),\n                 static_cast<uint8_t>(iv & 0xff), 1};\n        return true;\n      }\n    } else if (str.length() == 9) {\n      int64_t iv = 0;\n      if (UNLIKELY(!base::StringToInt(str.substr(1), iv, 16))) {\n        return false;\n      }\n      if (!(iv >= 0 && iv <= 0xffffffff)) {\n        return false;  // Covers NaN.\n      } else {\n        color = {static_cast<uint8_t>((iv & 0xff000000) >> 24),\n                 static_cast<uint8_t>((iv & 0xff0000) >> 16),\n                 static_cast<uint8_t>((iv & 0xff00) >> 8),\n                 static_cast<uint8_t>(iv & 0xff) / 255.0f};\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  size_t op = str.find_first_of('('), ep = str.find_first_of(')');\n  if (op != std::string::npos && ep + 1 == str.length()) {\n    const std::string fname = str.substr(0, op);\n    const std::vector<std::string> params =\n        split(str.substr(op + 1, ep - (op + 1)), ',');\n\n    float alpha = 1.0f;\n\n    if (fname == \"rgba\" || fname == \"rgb\") {\n      if (fname == \"rgba\") {\n        if (params.size() != 4) {\n          return false;\n        }\n        if (UNLIKELY(!parse_css_float(params.back(), alpha))) {\n          return false;\n        }\n      } else {\n        if (params.size() != 3) {\n          return false;\n        }\n      }\n      uint8_t r = 0;\n      uint8_t g = 0;\n      uint8_t b = 0;\n      if (UNLIKELY(!parse_css_int(params[0], r) ||\n                   !parse_css_int(params[1], g) ||\n                   !parse_css_int(params[2], b))) {\n        return false;\n      }\n      color = {r, g, b, alpha};\n      return true;\n\n    } else if (fname == \"hsla\" || fname == \"hsl\") {\n      if (fname == \"hsla\") {\n        if (params.size() != 4) {\n          return false;\n        }\n        if (UNLIKELY(!parse_css_float(params.back(), alpha))) {\n          return false;\n        }\n      } else {\n        if (params.size() != 3) {\n          return false;\n        }\n      }\n      double h = 0;\n      if (UNLIKELY(!base::StringToDouble(params[0], h, true))) {\n        return false;\n      }\n\n      // NOTE(deanm): According to the CSS spec s/l should only be\n      // percentages, but we don't bother and let float or percentage.\n      float s = 0.f;\n      float l = 0.f;\n      if (UNLIKELY(!parse_css_float(params[1], s) ||\n                   !parse_css_float(params[2], l))) {\n        return false;\n      }\n\n      color = CreateFromHSLA(h, s * 100, l * 100, alpha);\n      return true;\n    }\n  }\n  // Check in named colors\n  return ParseNamedColor(str, color);\n}\n\nbool CSSColor::ParseNamedColor(const std::string& color_str, CSSColor& color) {\n  auto iter = GetTokenValue(color_str.c_str(),\n                            static_cast<unsigned>(color_str.length()));\n  if (iter != nullptr && IsColorIdentifier(iter->type)) {\n    color = CreateFromKeyword(iter->type);\n    return true;\n  }\n  return false;\n}\n\nCSSColor CSSColor::CreateFromHSLA(float h, float s, float l, float a) {\n  h /= 360.0f;\n  s /= 100.0f;\n  l /= 100.0f;\n  // Note: hue wrapping is handled efficiently in css_hue_to_rgb using fmodf\n\n  float m2 = l <= 0.5f ? l * (s + 1.0f) : l + s - l * s;\n  float m1 = l * 2.0f - m2;\n\n  return CSSColor{\n      clamp_css_byte(css_hue_to_rgb(m1, m2, h + 1.0f / 3.0f) * 255.0f),\n      clamp_css_byte(css_hue_to_rgb(m1, m2, h) * 255.0f),\n      clamp_css_byte(css_hue_to_rgb(m1, m2, h - 1.0f / 3.0f) * 255.0f),\n      clamp_css_float(a)};\n}\n\nCSSColor CSSColor::CreateFromRGBA(float r, float g, float b, float a) {\n  return CSSColor{clamp_css_byte(r), clamp_css_byte(g), clamp_css_byte(b),\n                  clamp_css_float(a)};\n}\n\nCSSColor CSSColor::CreateFromKeyword(TokenType type) {\n  int index = static_cast<int>(type) - static_cast<int>(TokenType::TRANSPARENT);\n  static const CSSColor colors[] =\n      // [TokenType::TRANSPARENT, ..., TokenType::YELLOWGREEN]\n      {{0, 0, 0, 0},       {240, 248, 255, 1}, {250, 235, 215, 1},\n       {0, 255, 255, 1},   {127, 255, 212, 1}, {240, 255, 255, 1},\n       {245, 245, 220, 1}, {255, 228, 196, 1}, {0, 0, 0, 1},\n       {255, 235, 205, 1}, {0, 0, 255, 1},     {138, 43, 226, 1},\n       {165, 42, 42, 1},   {222, 184, 135, 1}, {95, 158, 160, 1},\n       {127, 255, 0, 1},   {210, 105, 30, 1},  {255, 127, 80, 1},\n       {100, 149, 237, 1}, {255, 248, 220, 1}, {220, 20, 60, 1},\n       {0, 255, 255, 1},   {0, 0, 139, 1},     {0, 139, 139, 1},\n       {184, 134, 11, 1},  {169, 169, 169, 1}, {0, 100, 0, 1},\n       {169, 169, 169, 1}, {189, 183, 107, 1}, {139, 0, 139, 1},\n       {85, 107, 47, 1},   {255, 140, 0, 1},   {153, 50, 204, 1},\n       {139, 0, 0, 1},     {233, 150, 122, 1}, {143, 188, 143, 1},\n       {72, 61, 139, 1},   {47, 79, 79, 1},    {47, 79, 79, 1},\n       {0, 206, 209, 1},   {148, 0, 211, 1},   {255, 20, 147, 1},\n       {0, 191, 255, 1},   {105, 105, 105, 1}, {105, 105, 105, 1},\n       {30, 144, 255, 1},  {178, 34, 34, 1},   {255, 250, 240, 1},\n       {34, 139, 34, 1},   {255, 0, 255, 1},   {220, 220, 220, 1},\n       {248, 248, 255, 1}, {255, 215, 0, 1},   {218, 165, 32, 1},\n       {128, 128, 128, 1}, {0, 128, 0, 1},     {173, 255, 47, 1},\n       {128, 128, 128, 1}, {240, 255, 240, 1}, {255, 105, 180, 1},\n       {205, 92, 92, 1},   {75, 0, 130, 1},    {255, 255, 240, 1},\n       {240, 230, 140, 1}, {230, 230, 250, 1}, {255, 240, 245, 1},\n       {124, 252, 0, 1},   {255, 250, 205, 1}, {173, 216, 230, 1},\n       {240, 128, 128, 1}, {224, 255, 255, 1}, {250, 250, 210, 1},\n       {211, 211, 211, 1}, {144, 238, 144, 1}, {211, 211, 211, 1},\n       {255, 182, 193, 1}, {255, 160, 122, 1}, {32, 178, 170, 1},\n       {135, 206, 250, 1}, {119, 136, 153, 1}, {119, 136, 153, 1},\n       {176, 196, 222, 1}, {255, 255, 224, 1}, {0, 255, 0, 1},\n       {50, 205, 50, 1},   {250, 240, 230, 1}, {255, 0, 255, 1},\n       {128, 0, 0, 1},     {102, 205, 170, 1}, {0, 0, 205, 1},\n       {186, 85, 211, 1},  {147, 112, 219, 1}, {60, 179, 113, 1},\n       {123, 104, 238, 1}, {0, 250, 154, 1},   {72, 209, 204, 1},\n       {199, 21, 133, 1},  {25, 25, 112, 1},   {245, 255, 250, 1},\n       {255, 228, 225, 1}, {255, 228, 181, 1}, {255, 222, 173, 1},\n       {0, 0, 128, 1},     {253, 245, 230, 1}, {128, 128, 0, 1},\n       {107, 142, 35, 1},  {255, 165, 0, 1},   {255, 69, 0, 1},\n       {218, 112, 214, 1}, {238, 232, 170, 1}, {152, 251, 152, 1},\n       {175, 238, 238, 1}, {219, 112, 147, 1}, {255, 239, 213, 1},\n       {255, 218, 185, 1}, {205, 133, 63, 1},  {255, 192, 203, 1},\n       {221, 160, 221, 1}, {176, 224, 230, 1}, {128, 0, 128, 1},\n       {255, 0, 0, 1},     {188, 143, 143, 1}, {65, 105, 225, 1},\n       {139, 69, 19, 1},   {250, 128, 114, 1}, {244, 164, 96, 1},\n       {46, 139, 87, 1},   {255, 245, 238, 1}, {160, 82, 45, 1},\n       {192, 192, 192, 1}, {135, 206, 235, 1}, {106, 90, 205, 1},\n       {112, 128, 144, 1}, {112, 128, 144, 1}, {255, 250, 250, 1},\n       {0, 255, 127, 1},   {70, 130, 180, 1},  {210, 180, 140, 1},\n       {0, 128, 128, 1},   {216, 191, 216, 1}, {255, 99, 71, 1},\n       {64, 224, 208, 1},  {238, 130, 238, 1}, {245, 222, 179, 1},\n       {255, 255, 255, 1}, {245, 245, 245, 1}, {255, 255, 0, 1},\n       {154, 205, 50, 1}};\n  DCHECK(index >= 0 && index < ARRAY_SIZE(colors));\n  return colors[index];\n}\n\nunsigned int CSSColor::Cast() const {\n  unsigned int color =\n      (0xffffffff & b_) | ((0xffffffff & g_) << 8) | ((0xffffffff & r_) << 16) |\n      ((0xffffffff & (static_cast<unsigned char>(a_ * 255))) << 24);\n  return color;\n}\n\nbool CSSColor::IsColorIdentifier(TokenType type) {\n  return type >= TokenType::TRANSPARENT && type <= TokenType::YELLOWGREEN;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_color.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_COLOR_H_\n#define CORE_RENDERER_CSS_CSS_COLOR_H_\n\n#include <string>\n\nnamespace lynx {\nnamespace tasm {\nenum class TokenType;\n\nclass CSSColor {\n public:\n  CSSColor() : r_(0), g_(0), b_(0), a_(1.0f) {}\n  CSSColor(unsigned char r, unsigned char g, unsigned char b, float a)\n      : r_(r), g_(g), b_(b), a_(a) {}\n  static bool Parse(const std::string& color_str, CSSColor& color);\n  static bool ParseNamedColor(const std::string& color_str, CSSColor& color);\n  static CSSColor CreateFromHSLA(float h, float s, float l, float a);\n  static CSSColor CreateFromRGBA(float r, float g, float b, float a);\n  static CSSColor CreateFromKeyword(TokenType);\n  static bool IsColorIdentifier(TokenType type);\n\n  unsigned int Cast() const;\n\n  bool operator==(const CSSColor& other) const {\n    return r_ == other.r_ && g_ == other.g_ && b_ == other.b_ && a_ == other.a_;\n  }\n\n  static constexpr unsigned int Black = 0xFF000000;\n  static constexpr unsigned int White = 0xFFFFFFFF;\n  static constexpr unsigned int Gray = 0xFF808080;\n  static constexpr unsigned int Transparent = 0x00000000;\n\n  unsigned char r_;\n  unsigned char g_;\n  unsigned char b_;\n  float a_;\n};\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_COLOR_H_\n"
  },
  {
    "path": "core/renderer/css/css_color_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_color.h\"\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nconst std::unordered_map<std::string, CSSColor>& NamedColors() {\n  static const base::NoDestructor<std::unordered_map<std::string, CSSColor>>\n      colors{{\n          {\"transparent\", {0, 0, 0, 0}},\n          {\"aliceblue\", {240, 248, 255, 1}},\n          {\"antiquewhite\", {250, 235, 215, 1}},\n          {\"aqua\", {0, 255, 255, 1}},\n          {\"aquamarine\", {127, 255, 212, 1}},\n          {\"azure\", {240, 255, 255, 1}},\n          {\"beige\", {245, 245, 220, 1}},\n          {\"bisque\", {255, 228, 196, 1}},\n          {\"black\", {0, 0, 0, 1}},\n          {\"blanchedalmond\", {255, 235, 205, 1}},\n          {\"blue\", {0, 0, 255, 1}},\n          {\"blueviolet\", {138, 43, 226, 1}},\n          {\"brown\", {165, 42, 42, 1}},\n          {\"burlywood\", {222, 184, 135, 1}},\n          {\"cadetblue\", {95, 158, 160, 1}},\n          {\"chartreuse\", {127, 255, 0, 1}},\n          {\"chocolate\", {210, 105, 30, 1}},\n          {\"coral\", {255, 127, 80, 1}},\n          {\"cornflowerblue\", {100, 149, 237, 1}},\n          {\"cornsilk\", {255, 248, 220, 1}},\n          {\"crimson\", {220, 20, 60, 1}},\n          {\"cyan\", {0, 255, 255, 1}},\n          {\"darkblue\", {0, 0, 139, 1}},\n          {\"darkcyan\", {0, 139, 139, 1}},\n          {\"darkgoldenrod\", {184, 134, 11, 1}},\n          {\"darkgray\", {169, 169, 169, 1}},\n          {\"darkgreen\", {0, 100, 0, 1}},\n          {\"darkgrey\", {169, 169, 169, 1}},\n          {\"darkkhaki\", {189, 183, 107, 1}},\n          {\"darkmagenta\", {139, 0, 139, 1}},\n          {\"darkolivegreen\", {85, 107, 47, 1}},\n          {\"darkorange\", {255, 140, 0, 1}},\n          {\"darkorchid\", {153, 50, 204, 1}},\n          {\"darkred\", {139, 0, 0, 1}},\n          {\"darksalmon\", {233, 150, 122, 1}},\n          {\"darkseagreen\", {143, 188, 143, 1}},\n          {\"darkslateblue\", {72, 61, 139, 1}},\n          {\"darkslategray\", {47, 79, 79, 1}},\n          {\"darkslategrey\", {47, 79, 79, 1}},\n          {\"darkturquoise\", {0, 206, 209, 1}},\n          {\"darkviolet\", {148, 0, 211, 1}},\n          {\"deeppink\", {255, 20, 147, 1}},\n          {\"deepskyblue\", {0, 191, 255, 1}},\n          {\"dimgray\", {105, 105, 105, 1}},\n          {\"dimgrey\", {105, 105, 105, 1}},\n          {\"dodgerblue\", {30, 144, 255, 1}},\n          {\"firebrick\", {178, 34, 34, 1}},\n          {\"floralwhite\", {255, 250, 240, 1}},\n          {\"forestgreen\", {34, 139, 34, 1}},\n          {\"fuchsia\", {255, 0, 255, 1}},\n          {\"gainsboro\", {220, 220, 220, 1}},\n          {\"ghostwhite\", {248, 248, 255, 1}},\n          {\"gold\", {255, 215, 0, 1}},\n          {\"goldenrod\", {218, 165, 32, 1}},\n          {\"gray\", {128, 128, 128, 1}},\n          {\"green\", {0, 128, 0, 1}},\n          {\"greenyellow\", {173, 255, 47, 1}},\n          {\"grey\", {128, 128, 128, 1}},\n          {\"honeydew\", {240, 255, 240, 1}},\n          {\"hotpink\", {255, 105, 180, 1}},\n          {\"indianred\", {205, 92, 92, 1}},\n          {\"indigo\", {75, 0, 130, 1}},\n          {\"ivory\", {255, 255, 240, 1}},\n          {\"khaki\", {240, 230, 140, 1}},\n          {\"lavender\", {230, 230, 250, 1}},\n          {\"lavenderblush\", {255, 240, 245, 1}},\n          {\"lawngreen\", {124, 252, 0, 1}},\n          {\"lemonchiffon\", {255, 250, 205, 1}},\n          {\"lightblue\", {173, 216, 230, 1}},\n          {\"lightcoral\", {240, 128, 128, 1}},\n          {\"lightcyan\", {224, 255, 255, 1}},\n          {\"lightgoldenrodyellow\", {250, 250, 210, 1}},\n          {\"lightgray\", {211, 211, 211, 1}},\n          {\"lightgreen\", {144, 238, 144, 1}},\n          {\"lightgrey\", {211, 211, 211, 1}},\n          {\"lightpink\", {255, 182, 193, 1}},\n          {\"lightsalmon\", {255, 160, 122, 1}},\n          {\"lightseagreen\", {32, 178, 170, 1}},\n          {\"lightskyblue\", {135, 206, 250, 1}},\n          {\"lightslategray\", {119, 136, 153, 1}},\n          {\"lightslategrey\", {119, 136, 153, 1}},\n          {\"lightsteelblue\", {176, 196, 222, 1}},\n          {\"lightyellow\", {255, 255, 224, 1}},\n          {\"lime\", {0, 255, 0, 1}},\n          {\"limegreen\", {50, 205, 50, 1}},\n          {\"linen\", {250, 240, 230, 1}},\n          {\"magenta\", {255, 0, 255, 1}},\n          {\"maroon\", {128, 0, 0, 1}},\n          {\"mediumaquamarine\", {102, 205, 170, 1}},\n          {\"mediumblue\", {0, 0, 205, 1}},\n          {\"mediumorchid\", {186, 85, 211, 1}},\n          {\"mediumpurple\", {147, 112, 219, 1}},\n          {\"mediumseagreen\", {60, 179, 113, 1}},\n          {\"mediumslateblue\", {123, 104, 238, 1}},\n          {\"mediumspringgreen\", {0, 250, 154, 1}},\n          {\"mediumturquoise\", {72, 209, 204, 1}},\n          {\"mediumvioletred\", {199, 21, 133, 1}},\n          {\"midnightblue\", {25, 25, 112, 1}},\n          {\"mintcream\", {245, 255, 250, 1}},\n          {\"mistyrose\", {255, 228, 225, 1}},\n          {\"moccasin\", {255, 228, 181, 1}},\n          {\"navajowhite\", {255, 222, 173, 1}},\n          {\"navy\", {0, 0, 128, 1}},\n          {\"oldlace\", {253, 245, 230, 1}},\n          {\"olive\", {128, 128, 0, 1}},\n          {\"olivedrab\", {107, 142, 35, 1}},\n          {\"orange\", {255, 165, 0, 1}},\n          {\"orangered\", {255, 69, 0, 1}},\n          {\"orchid\", {218, 112, 214, 1}},\n          {\"palegoldenrod\", {238, 232, 170, 1}},\n          {\"palegreen\", {152, 251, 152, 1}},\n          {\"paleturquoise\", {175, 238, 238, 1}},\n          {\"palevioletred\", {219, 112, 147, 1}},\n          {\"papayawhip\", {255, 239, 213, 1}},\n          {\"peachpuff\", {255, 218, 185, 1}},\n          {\"peru\", {205, 133, 63, 1}},\n          {\"pink\", {255, 192, 203, 1}},\n          {\"plum\", {221, 160, 221, 1}},\n          {\"powderblue\", {176, 224, 230, 1}},\n          {\"purple\", {128, 0, 128, 1}},\n          {\"red\", {255, 0, 0, 1}},\n          {\"rosybrown\", {188, 143, 143, 1}},\n          {\"royalblue\", {65, 105, 225, 1}},\n          {\"saddlebrown\", {139, 69, 19, 1}},\n          {\"salmon\", {250, 128, 114, 1}},\n          {\"sandybrown\", {244, 164, 96, 1}},\n          {\"seagreen\", {46, 139, 87, 1}},\n          {\"seashell\", {255, 245, 238, 1}},\n          {\"sienna\", {160, 82, 45, 1}},\n          {\"silver\", {192, 192, 192, 1}},\n          {\"skyblue\", {135, 206, 235, 1}},\n          {\"slateblue\", {106, 90, 205, 1}},\n          {\"slategray\", {112, 128, 144, 1}},\n          {\"slategrey\", {112, 128, 144, 1}},\n          {\"snow\", {255, 250, 250, 1}},\n          {\"springgreen\", {0, 255, 127, 1}},\n          {\"steelblue\", {70, 130, 180, 1}},\n          {\"tan\", {210, 180, 140, 1}},\n          {\"teal\", {0, 128, 128, 1}},\n          {\"thistle\", {216, 191, 216, 1}},\n          {\"tomato\", {255, 99, 71, 1}},\n          {\"turquoise\", {64, 224, 208, 1}},\n          {\"violet\", {238, 130, 238, 1}},\n          {\"wheat\", {245, 222, 179, 1}},\n          {\"white\", {255, 255, 255, 1}},\n          {\"whitesmoke\", {245, 245, 245, 1}},\n          {\"yellow\", {255, 255, 0, 1}},\n          {\"yellowgreen\", {154, 205, 50, 1}},\n      }};\n  return *colors;\n}\n\nTEST(CSSColor, Keywords) {\n  for (const auto& it : NamedColors()) {\n    CSSColor color;\n    EXPECT_TRUE(CSSColor::ParseNamedColor(it.first, color));\n    EXPECT_EQ(color, it.second);\n  }\n}\n\nTEST(CSSColor, Parse) {\n  auto color_str = \"not color\";\n  CSSColor color;\n  EXPECT_FALSE(CSSColor::Parse(color_str, color));\n\n  color_str = \"red\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xffff0000);\n\n  color_str = \"#00ff00\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xff00ff00);\n\n  color_str = \"#056b\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xbb005566);\n\n  color_str = \"#090a\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xaa009900);\n\n  color_str = \"#abcd\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xddaabbcc);\n\n  color_str = \"#hello0\";\n  EXPECT_FALSE(CSSColor::Parse(color_str, color));\n\n  color_str = \"#00ff00ee\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xee00ff00);\n\n  color_str = \"rgb(0,0,255)\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xff0000ff);\n\n  color_str = \"rgb(2000,-1,255)\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xffff00ff);\n\n  color_str = \"rgb(0, 0, 255)\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xff0000ff);\n\n  color_str = \"rgb(100%, 0, 100%)\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xffff00ff);\n\n  color_str = \"rgba(0, 0, 255, 100)\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xff0000ff);\n\n  color_str = \"hsl(240, 100%, 50%)\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0xff0000ff);\n\n  color_str = \"hsla(240, 100%, 50%, 0.3)\";\n  EXPECT_TRUE(CSSColor::Parse(color_str, color));\n  EXPECT_EQ(color.Cast(), 0x4c0000ff);\n\n  color_str = \"#errors\";\n  EXPECT_FALSE(CSSColor::Parse(color_str, color));\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_content_data.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_content_data.h\"\n\n#include \"core/renderer/dom/attribute_holder.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nContentData* ContentData::createTextContent(const base::String& text) {\n  return new TextContentData(text);\n}\n\nContentData* ContentData::createImageContent(const std::string& url) {\n  return new ImageContentData(url);\n}\n\nContentData* ContentData::createAttrContent(const AttributeHolder* node,\n                                            const base::String& attr) {\n  return new AttrContentData(node, attr);\n}\n\nAttrContentData::AttrContentData(const AttributeHolder* owner,\n                                 const base::String& attr)\n    : attr_owner_(owner), attr_key_(attr) {}\n\nconst lepus::Value& AttrContentData::attr_content() {\n  static lepus::Value kTempLepusValue = lepus::Value();\n  if (attr_owner_ == nullptr) return kTempLepusValue;\n\n  auto& styles = attr_owner_->attributes();\n  auto iter = styles.find(attr_key_);\n  if (iter != styles.end()) return iter->second;\n\n  return kTempLepusValue;\n}\n\n// TODO: another type\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_content_data.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_RENDERER_CSS_CSS_CONTENT_DATA_H_\n#define CORE_RENDERER_CSS_CSS_CONTENT_DATA_H_\n\n#include <string>\n\n#include \"base/include/value/base_string.h\"\n#include \"base/include/value/base_value.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass AttributeHolder;\n\nclass ContentData {\n public:\n  static ContentData* createTextContent(const base::String&);\n  static ContentData* createImageContent(const std::string&);\n  static ContentData* createAttrContent(const AttributeHolder*,\n                                        const base::String&);\n\n  virtual ~ContentData() {\n    if (next_) delete next_;\n  }\n  virtual bool isText() const { return false; }\n  virtual bool isImage() const { return false; }\n  virtual bool isAttr() const { return false; }\n\n  ContentData* next() { return next_; }\n  void set_next(ContentData* next) { next_ = next; }\n\n private:\n  ContentData* next_ = nullptr;\n};\n\nclass TextContentData : public ContentData {\n  friend class ContentData;\n\n public:\n  TextContentData(const base::String& text) : text_(text) {}\n\n  const base::String& text() const { return text_; }\n  void set_text(const base::String& text) { text_ = text; }\n\n  bool isText() const override { return true; }\n\n private:\n  base::String text_;\n};\n\nclass ImageContentData : public ContentData {\n public:\n  ImageContentData(const std::string& url) : url_(url) {}\n\n  const std::string& url() const { return url_; }\n  void set_url(const std::string& url) { url_ = url; }\n\n  bool isImage() const override { return true; }\n\n private:\n  std::string url_;\n};\n\nclass AttrContentData : public ContentData {\n public:\n  AttrContentData(const AttributeHolder* owner, const base::String& text);\n\n  const lepus::Value& attr_content();\n  bool isAttr() const override { return true; }\n\n private:\n  const AttributeHolder* attr_owner_;\n  const base::String attr_key_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_CONTENT_DATA_H_\n"
  },
  {
    "path": "core/renderer/css/css_debug_msg.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_DEBUG_MSG_H_\n#define CORE_RENDERER_CSS_CSS_DEBUG_MSG_H_\n\nnamespace lynx {\nnamespace tasm {\n// css attr and style type\nconstexpr char ANIMATION_PROPERTY[] = \"animation-property\";\nconstexpr char BOOL_TYPE[] = \"bool-type\";\nconstexpr char CUBIC_BEZIER[] = \"cubic-bezier\";\nconstexpr char FLOAT_TYPE[] = \"float-type\";\nconstexpr char SQUARE_BEZIER[] = \"square-bezier\";\nconstexpr char STEP_VALUE[] = \"step-value\";\nconstexpr char TIMING_FUNCTION[] = \"timing-function\";\nconstexpr char TIME_VALUE[] = \"time-value\";\n\n// css warning info\nconstexpr char TYPE_UNSUPPORTED[] = \"%s don't support type:%s\";\nconstexpr char TYPE_MUST_BE[] = \"%s must be %s\";\nconstexpr char STRING_TYPE[] = \"a string!\";\nconstexpr char NUMBER_TYPE[] = \"a number!\";\nconstexpr char ARRAY_TYPE[] = \"an array!\";\nconstexpr char ARRAY_OR_MAP_TYPE[] = \"an array or a map!\";\nconstexpr char ARRAY_OR_NUMBER_TYPE[] = \"an array or a number!\";\nconstexpr char ENUM_TYPE[] = \"an enum!\";\nconstexpr char INT_TYPE[] = \"an int!\";\nconstexpr char STRING_OR_NUMBER_TYPE[] = \"a string or number!\";\nconstexpr char STRING_OR_BOOL_TYPE[] = \"a string or bool!\";\nconstexpr char FORMAT_ERROR[] = \"%s format error:%s\";\nconstexpr char EMPTY_ERROR[] = \"%s is empty!\";\nconstexpr char SIZE_ERROR[] = \"%s size error:%d\";\nconstexpr char TYPE_ERROR[] = \"%s type error\";\nconstexpr char CANNOT_REACH_METHOD[] = \"method unreachable.\";\nconstexpr char SET_PROPERTY_ERROR[] = \"set %s error.\";\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_DEBUG_MSG_H_\n"
  },
  {
    "path": "core/renderer/css/css_decoder.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_decoder.h\"\n\n#include <sstream>\n#include <utility>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/build/gen/lynx_sub_error_code.h\"\n#include \"core/renderer/css/css_color.h\"\n#include \"core/style/timing_function_data.h\"\n#include \"core/style/transform_raw_data.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nusing namespace lynx::starlight;  // NOLINT\nusing namespace lynx::tasm;       // NOLINT\n\nnamespace {\n// remove\nstd::string NumberToRGBAColorString(double number) {\n  auto color = static_cast<unsigned int>(number);\n  std::stringstream stream;\n  stream << std::hex << color;\n  auto str = stream.str();\n  if (str.length() < 3) {\n    LOGE(\"str format is invalid:\" << str);\n    return str;\n  }\n  // if the current length is seven or six, which means the hex alpha value is\n  // only digit or zero, should insert a zero ahead to make it more easy to\n  // understand\n  if (str.length() < 8) {\n    str.insert(str.begin(), static_cast<size_t>(8 - str.length()), '0');\n  }\n\n  auto a = str.substr(0, 2);\n  if (a == \"ff\") {\n    str = std::string(\"#\") + str.substr(2, str.length());\n  } else {\n    str = std::string(\"#\") + str.substr(2, str.length()) + a;\n  }\n  return str;\n}\n\nstd::string NumberToLineHeightString(double number) {\n  if (number > 10E19) {\n    return \"normal\";\n  } else {\n    return CSSDecoder::NumberToString(number);\n  }\n}\n\n}  // namespace\n\nstd::string CSSDecoder::NumberToString(double number) {\n  auto ret = std::to_string(number);\n  ret = ret.erase(ret.find_last_not_of('0') + 1, std::string::npos);\n  if (ret.back() == '.') {\n    ret.pop_back();\n  }\n  return ret;\n}\n\nstd::string CSSDecoder::CSSValueToString(const CSSPropertyID id,\n                                         const lynx::tasm::CSSValue &value,\n                                         bool map_key_ordered) {\n  if (value.IsEmpty()) {\n    return \"\";\n  } else if (value.IsString()) {\n    return value.AsStdString();\n  } else if (value.IsNumber()) {\n    return CSSValueNumberToString(id, value);\n  } else if (value.IsPx()) {\n    return CSSDecoder::NumberToString(value.AsNumber()) + std::string(\"px\");\n  } else if (value.IsRpx()) {\n    return CSSDecoder::NumberToString(value.AsNumber()) + std::string(\"rpx\");\n  } else if (value.IsPPx()) {\n    return CSSDecoder::NumberToString(value.AsNumber()) + std::string(\"ppx\");\n  } else if (value.IsRem()) {\n    return CSSDecoder::NumberToString(value.AsNumber()) + std::string(\"rem\");\n  } else if (value.IsEm()) {\n    return CSSDecoder::NumberToString(value.AsNumber()) + std::string(\"em\");\n  } else if (value.IsVh()) {\n    return CSSDecoder::NumberToString(value.AsNumber()) + std::string(\"vh\");\n  } else if (value.IsVw()) {\n    return CSSDecoder::NumberToString(value.AsNumber()) + std::string(\"vw\");\n  } else if (value.IsPercent()) {\n    return CSSDecoder::NumberToString(value.AsNumber()) + std::string(\"%\");\n  } else if (value.IsSp()) {\n    return CSSDecoder::NumberToString(value.AsNumber()) + std::string(\"sp\");\n  } else if (value.IsCalc()) {\n    return value.AsStdString();\n  } else if (value.IsIntrinsic()) {\n    return value.AsStdString();\n  } else if (value.IsEnv()) {\n    return value.AsStdString();\n  } else if (value.IsArray()) {\n    return CSSValueArrayToString(id, value);\n  } else if (value.IsMap()) {\n    // TODO(liyanbo): de parser map type.\n    return value.AsJsonString(map_key_ordered);\n  } else if (value.IsEnum()) {\n    return CSSValueEnumToString(id, value);\n  } else if (value.IsBoolean()) {\n    return value.AsBool() ? \"true\" : \"false\";\n  } else {\n    LynxWarning(false, error::E_CSS_UNSUPPORTED_VALUE,\n                \"lynx css decoder unknown value type: %s\",\n                CSSProperty::GetPropertyName(id).c_str());\n    return \"\";\n  }\n}\n\nstd::string CSSDecoder::CSSValueEnumToString(\n    const CSSPropertyID id, const lynx::tasm::CSSValue &value) {\n  switch (id) {\n    case lynx::tasm::kPropertyIDTop:\n    case lynx::tasm::kPropertyIDLeft:\n    case lynx::tasm::kPropertyIDRight:\n    case lynx::tasm::kPropertyIDBottom:\n    case lynx::tasm::kPropertyIDBorderWidth:\n    case lynx::tasm::kPropertyIDBorderLeftWidth:\n    case lynx::tasm::kPropertyIDBorderRightWidth:\n    case lynx::tasm::kPropertyIDBorderTopWidth:\n    case lynx::tasm::kPropertyIDBorderBottomWidth:\n    case lynx::tasm::kPropertyIDBorderInlineStartWidth:\n    case lynx::tasm::kPropertyIDBorderInlineEndWidth:\n    case lynx::tasm::kPropertyIDHeight:\n    case lynx::tasm::kPropertyIDWidth:\n    case lynx::tasm::kPropertyIDMaxWidth:\n    case lynx::tasm::kPropertyIDMinWidth:\n    case lynx::tasm::kPropertyIDMaxHeight:\n    case lynx::tasm::kPropertyIDMinHeight:\n    case lynx::tasm::kPropertyIDPadding:\n    case lynx::tasm::kPropertyIDPaddingLeft:\n    case lynx::tasm::kPropertyIDPaddingRight:\n    case lynx::tasm::kPropertyIDPaddingTop:\n    case lynx::tasm::kPropertyIDPaddingBottom:\n    case lynx::tasm::kPropertyIDPaddingInlineStart:\n    case lynx::tasm::kPropertyIDPaddingInlineEnd:\n    case lynx::tasm::kPropertyIDMargin:\n    case lynx::tasm::kPropertyIDMarginLeft:\n    case lynx::tasm::kPropertyIDMarginRight:\n    case lynx::tasm::kPropertyIDMarginTop:\n    case lynx::tasm::kPropertyIDMarginBottom:\n    case lynx::tasm::kPropertyIDMarginInlineStart:\n    case lynx::tasm::kPropertyIDMarginInlineEnd:\n    case lynx::tasm::kPropertyIDFlexBasis:\n    case lynx::tasm::kPropertyIDFontSize:\n    case lynx::tasm::kPropertyIDLetterSpacing:\n    case lynx::tasm::kPropertyIDLineHeight:\n    case lynx::tasm::kPropertyIDLineSpacing:\n    case lynx::tasm::kPropertyIDAdaptFontSize:\n    case lynx::tasm::kPropertyIDOutlineWidth:\n      return ToLengthType(value.GetEnum<LengthValueType>());\n    case lynx::tasm::kPropertyIDDisplay:\n      return ToDisplayType(value.GetEnum<DisplayType>());\n    case lynx::tasm::kPropertyIDOverflow:\n    case lynx::tasm::kPropertyIDOverflowX:\n    case lynx::tasm::kPropertyIDOverflowY:\n      return ToOverflowType(value.GetEnum<OverflowType>());\n    case lynx::tasm::kPropertyIDFlexDirection:\n      return ToFlexDirectionType(value.GetEnum<FlexDirectionType>());\n    case lynx::tasm::kPropertyIDFlexWrap:\n      return ToFlexWrapType(value.GetEnum<FlexWrapType>());\n    case lynx::tasm::kPropertyIDAlignItems:\n    case lynx::tasm::kPropertyIDAlignSelf:\n      return ToFlexAlignType(value.GetEnum<FlexAlignType>());\n    case lynx::tasm::kPropertyIDAlignContent:\n      return ToAlignContentType(value.GetEnum<AlignContentType>());\n    case lynx::tasm::kPropertyIDJustifyContent:\n      return ToJustifyContentType(value.GetEnum<JustifyContentType>());\n    case lynx::tasm::kPropertyIDLinearOrientation:\n      return ToLinearOrientationType(value.GetEnum<LinearOrientationType>());\n    case lynx::tasm::kPropertyIDLinearGravity:\n      return ToLinearGravityType(value.GetEnum<LinearGravityType>());\n    case lynx::tasm::kPropertyIDLinearLayoutGravity:\n      return ToLinearLayoutGravityType(\n          value.GetEnum<LinearLayoutGravityType>());\n    case lynx::tasm::kPropertyIDLayoutAnimationCreateTimingFunction:\n    case lynx::tasm::kPropertyIDLayoutAnimationDeleteTimingFunction:\n    case lynx::tasm::kPropertyIDLayoutAnimationUpdateTimingFunction:\n    case lynx::tasm::kPropertyIDTransitionTimingFunction:\n      return ToTimingFunctionType(value.GetEnum<TimingFunctionType>());\n    case lynx::tasm::kPropertyIDLayoutAnimationCreateProperty:\n    case lynx::tasm::kPropertyIDLayoutAnimationDeleteProperty:\n    case lynx::tasm::kPropertyIDTransitionProperty:\n      return ToAnimationPropertyType(value.GetEnum<AnimationPropertyType>());\n    case lynx::tasm::kPropertyIDVisibility:\n      return ToVisibilityType(value.GetEnum<VisibilityType>());\n    case lynx::tasm::kPropertyIDBorderLeftStyle:\n    case lynx::tasm::kPropertyIDBorderRightStyle:\n    case lynx::tasm::kPropertyIDBorderTopStyle:\n    case lynx::tasm::kPropertyIDBorderBottomStyle:\n    case lynx::tasm::kPropertyIDBorderInlineStartStyle:\n    case lynx::tasm::kPropertyIDBorderInlineEndStyle:\n    case lynx::tasm::kPropertyIDOutlineStyle:\n      return ToBorderStyleType(value.GetEnum<BorderStyleType>());\n    case lynx::tasm::kPropertyIDDirection:\n      return ToDirectionType(value.GetEnum<DirectionType>());\n    case lynx::tasm::kPropertyIDRelativeCenter:\n      return ToRelativeCenterType(value.GetEnum<RelativeCenterType>());\n    case lynx::tasm::kPropertyIDPosition:\n      return ToPositionType(value.GetEnum<PositionType>());\n    case lynx::tasm::kPropertyIDAnimationFillMode:\n      return ToAnimationFillModeType(value.GetEnum<AnimationFillModeType>());\n    case lynx::tasm::kPropertyIDAnimationDirection:\n      return ToAnimationDirectionType(value.GetEnum<AnimationDirectionType>());\n    case lynx::tasm::kPropertyIDAnimationPlayState:\n      return ToAnimationPlayStateType(value.GetEnum<AnimationPlayStateType>());\n    case lynx::tasm::kPropertyIDWhiteSpace:\n      return ToWhiteSpaceType(value.GetEnum<WhiteSpaceType>());\n      break;\n    case lynx::tasm::kPropertyIDFontWeight:\n      return ToFontWeightType(value.GetEnum<FontWeightType>());\n      break;\n    case lynx::tasm::kPropertyIDXPlaceholderFontWeight:\n      return ToXPlaceholderFontWeightType(\n          value.GetEnum<XPlaceholderFontWeightType>());\n    case lynx::tasm::kPropertyIDWordBreak:\n      return ToWordBreakType(value.GetEnum<WordBreakType>());\n    case lynx::tasm::kPropertyIDFontStyle:\n      return ToFontStyleType(value.GetEnum<FontStyleType>());\n      break;\n    case lynx::tasm::kPropertyIDXPlaceholderFontStyle:\n      return ToXPlaceholderFontStyleType(\n          value.GetEnum<XPlaceholderFontStyleType>());\n    case lynx::tasm::kPropertyIDTextAlign:\n      return ToTextAlignType(value.GetEnum<TextAlignType>());\n      break;\n    case lynx::tasm::kPropertyIDTextOverflow:\n      return ToTextOverflowType(value.GetEnum<TextOverflowType>());\n      break;\n    case lynx::tasm::kPropertyIDAnimationTimingFunction:\n      return ToTimingFunctionType(value.GetEnum<TimingFunctionType>());\n    case lynx::tasm::kPropertyIDBoxSizing:\n      return ToBoxSizingType(value.GetEnum<BoxSizingType>());\n    case lynx::tasm::kPropertyIDLinearCrossGravity:\n      return ToLinearCrossGravityType(value.GetEnum<LinearCrossGravityType>());\n    case lynx::tasm::kPropertyIDLinearDirection:\n      return ToLinearOrientationType(value.GetEnum<LinearOrientationType>());\n    case lynx::tasm::kPropertyIDJustifyItems:\n    case lynx::tasm::kPropertyIDJustifySelf:\n      return ToJustifyType(value.GetEnum<JustifyType>());\n    case lynx::tasm::kPropertyIDGridAutoFlow:\n      return ToGridAutoFlowType(value.GetEnum<GridAutoFlowType>());\n    case lynx::tasm::kPropertyIDImageRendering:\n      return ToImageRenderingType(value.GetEnum<ImageRenderingType>());\n    case lynx::tasm::kPropertyIDHyphens:\n      return ToHyphensType(value.GetEnum<HyphensType>());\n    case lynx::tasm::kPropertyIDXAppRegion:\n      return ToXAppRegionType(value.GetEnum<XAppRegionType>());\n    case lynx::tasm::kPropertyIDXAnimationColorInterpolation:\n      return ToXAnimationColorInterpolationType(\n          value.GetEnum<XAnimationColorInterpolationType>());\n    case lynx::tasm::kPropertyIDFontOpticalSizing:\n      return ToFontOpticalSizingType(value.GetEnum<FontOpticalSizingType>());\n      // TODO(liyanbo): this will support when parser support.\n      //    case lynx::tasm::kPropertyIDBackgroundPosition:\n      //      break;\n      //    case lynx::tasm::kPropertyIDBackgroundOrigin:\n      //      break;\n      //    case lynx::tasm::kPropertyIDBackgroundRepeat:\n      //      break;\n      //    case lynx::tasm::kPropertyIDBackgroundSize:\n      //      break;\n      //    case lynx::tasm::kPropertyIDBackgroundClip:\n      //      break;\n    case lynx::tasm::kPropertyIDPointerEvents:\n      return ToPointerEventsType(value.GetEnum<PointerEventsType>());\n    default:\n      LynxWarning(false, error::E_CSS_UNSUPPORTED_VALUE,\n                  \"lynx css decoder no such enum for css_id: %s\",\n                  CSSProperty::GetPropertyName(id).c_str());\n      return \"\";\n  }\n}\n\nstd::string CSSDecoder::CSSValueNumberToString(\n    const CSSPropertyID id, const lynx::tasm::CSSValue &value) {\n  switch (id) {\n    case lynx::tasm::kPropertyIDBackgroundColor:\n    case lynx::tasm::kPropertyIDBorderLeftColor:\n    case lynx::tasm::kPropertyIDBorderRightColor:\n    case lynx::tasm::kPropertyIDBorderTopColor:\n    case lynx::tasm::kPropertyIDBorderBottomColor:\n    case lynx::tasm::kPropertyIDBorderInlineStartColor:\n    case lynx::tasm::kPropertyIDBorderInlineEndColor:\n    case lynx::tasm::kPropertyIDColor:\n    case lynx::tasm::kPropertyIDOutlineColor:\n    case lynx::tasm::kPropertyIDTextStrokeColor:\n    case lynx::tasm::kPropertyIDXHandleColor:\n      return NumberToRGBAColorString(value.AsNumber());\n    case lynx::tasm::kPropertyIDLineHeight:\n      return NumberToLineHeightString(value.AsNumber());\n    default:\n      return CSSDecoder::NumberToString(value.AsNumber());\n  }\n}\n\nnamespace {\nvoid RemoveRedundantZeros(std::string &paramsValue) {\n  if (paramsValue.length() <= 1) {\n    return;\n  }\n  while (true) {\n    if (paramsValue.back() != '0') {\n      break;\n    }\n    paramsValue.pop_back();\n  }\n  if (paramsValue.back() == '.') {\n    paramsValue.pop_back();\n  }\n  return;\n}\n\nstd::string ToLengthWithUnit(const lepus_value &value,\n                             lynx::tasm::CSSValuePattern pattern) {\n  std::string res;\n  std::string paramsValue = std::to_string(static_cast<float>(value.Number()));\n  std::string paramsUnit;\n  switch (pattern) {\n    case lynx::tasm::CSSValuePattern::NUMBER:\n      paramsUnit = \"\";\n      break;\n    case lynx::tasm::CSSValuePattern::PX:\n      paramsUnit = \"px\";\n      break;\n    case lynx::tasm::CSSValuePattern::RPX:\n      paramsUnit = \"rpx\";\n      break;\n    case lynx::tasm::CSSValuePattern::PPX:\n      paramsUnit = \"ppx\";\n      break;\n    case lynx::tasm::CSSValuePattern::REM:\n      paramsUnit = \"rem\";\n      break;\n    case lynx::tasm::CSSValuePattern::EM:\n      paramsUnit = \"em\";\n      break;\n    case lynx::tasm::CSSValuePattern::PERCENT:\n      paramsUnit = \"%\";\n      break;\n    case lynx::tasm::CSSValuePattern::VH:\n      paramsUnit = \"vh\";\n      break;\n    case lynx::tasm::CSSValuePattern::VW:\n      paramsUnit = \"vw\";\n      break;\n    case lynx::tasm::CSSValuePattern::CALC:\n    case lynx::tasm::CSSValuePattern::INTRINSIC:\n      paramsValue = value.StdString();\n      paramsUnit = \"\";\n      break;\n    case lynx::tasm::CSSValuePattern::FR:\n      paramsUnit = \"fr\";\n      break;\n    default:\n      LynxWarning(false, error::E_CSS_UNSUPPORTED_VALUE,\n                  \"%d length type pattern is invalid.\", (int)pattern);\n      break;\n  }\n  RemoveRedundantZeros(paramsValue);\n  res = paramsValue + paramsUnit;\n  return res;\n}\n\nvoid GetTransformParams(std::string &length, const lepus_value &value,\n                        const lepus_value &unit) {\n  auto pattern = static_cast<lynx::tasm::CSSValuePattern>(unit.Number());\n  length = ToLengthWithUnit(value, pattern);\n  return;\n}\n\nstd::string GetTransformMatrixParams(const fml::RefPtr<lepus::CArray> &arr,\n                                     int count) {\n  std::ostringstream oss;\n  for (int i = 1; i < count; ++i) {\n    if (i > 1) {\n      oss << \", \";\n    }\n    oss << arr->get(i).Number();\n  }\n  return oss.str();\n};\n}  // namespace\n\nstd::string CSSDecoder::ToTransformType(lynx::starlight::TransformType type) {\n  switch (type) {\n    case TransformType::kNone:\n      return \"none\";\n    case TransformType::kTranslate:\n      return \"translate\";\n    case TransformType::kTranslateX:\n      return \"translateX\";\n    case TransformType::kTranslateY:\n      return \"translateY\";\n    case TransformType::kTranslateZ:\n      return \"translateZ\";\n    case TransformType::kTranslate3d:\n      return \"translate3d\";\n    case TransformType::kRotate:\n      return \"rotate\";\n    case TransformType::kRotateX:\n      return \"rotateX\";\n    case TransformType::kRotateY:\n      return \"rotateY\";\n    case TransformType::kRotateZ:\n      return \"rotateZ\";\n    case TransformType::kScale:\n      return \"scale\";\n    case TransformType::kScaleX:\n      return \"scaleX\";\n    case TransformType::kScaleY:\n      return \"scaleY\";\n    case TransformType::kSkew:\n      return \"skew\";\n    case TransformType::kSkewX:\n      return \"skewX\";\n    case TransformType::kSkewY:\n      return \"skewY\";\n    case TransformType::kMatrix:\n      return \"matrix\";\n    case TransformType::kMatrix3d:\n      return \"matrix3d\";\n    default:\n      LynxWarning(false, error::E_CSS_UNSUPPORTED_VALUE,\n                  \"%d transform type is invalid.\", (int)type);\n      return \"\";\n  }\n}\n\nnamespace {\nstd::string ToTransformProperty(const lynx::tasm::CSSValue &value) {\n  std::string res;\n  auto items = value.GetArray();\n  for (size_t i = 0; i < items->size(); i++) {\n    auto arr = items->get(i).Array();\n    TransformType type = static_cast<TransformType>(\n        arr->get(TransformRawData::INDEX_FUNC).Number());\n    std::string func = CSSDecoder::ToTransformType(type);\n    std::string param1 = \"0\";\n    std::string param2 = \"0\";\n    std::string param3 = \"0\";\n    switch (type) {\n      case TransformType::kTranslate:\n        GetTransformParams(param1,\n                           arr->get(TransformRawData::INDEX_TRANSLATE_0),\n                           arr->get(TransformRawData::INDEX_TRANSLATE_0_UNIT));\n        if (arr->size() <= TransformRawData::INDEX_TRANSLATE_1) {\n          param2 = param1;\n        } else {\n          GetTransformParams(\n              param2, arr->get(TransformRawData::INDEX_TRANSLATE_1),\n              arr->get(TransformRawData::INDEX_TRANSLATE_1_UNIT));\n        }\n        func += std::string(\"(\" + param1 + \",\" + param2 + \")\");\n        break;\n      case TransformType::kTranslateX:\n      case TransformType::kTranslateY:\n      case TransformType::kTranslateZ:\n        GetTransformParams(param1,\n                           arr->get(TransformRawData::INDEX_TRANSLATE_0),\n                           arr->get(TransformRawData::INDEX_TRANSLATE_0_UNIT));\n        func += std::string(\"(\" + param1 + \")\");\n        break;\n      case TransformType::kTranslate3d:\n        GetTransformParams(param1,\n                           arr->get(TransformRawData::INDEX_TRANSLATE_0),\n                           arr->get(TransformRawData::INDEX_TRANSLATE_0_UNIT));\n        GetTransformParams(param2,\n                           arr->get(TransformRawData::INDEX_TRANSLATE_1),\n                           arr->get(TransformRawData::INDEX_TRANSLATE_1_UNIT));\n        GetTransformParams(param3,\n                           arr->get(TransformRawData::INDEX_TRANSLATE_2),\n                           arr->get(TransformRawData::INDEX_TRANSLATE_2_UNIT));\n        func += std::string(\"(\" + param1 + \",\" + param2 + \",\" + param3 + \")\");\n        break;\n      case TransformType::kRotate:\n      case TransformType::kRotateX:\n      case TransformType::kRotateY:\n      case TransformType::kRotateZ:\n      case TransformType::kSkewX:\n      case TransformType::kSkewY:\n        param1 = std::to_string(\n            arr->get(TransformRawData::INDEX_ROTATE_ANGLE).Number());\n        RemoveRedundantZeros(param1);\n        func += std::string(\"(\" + param1 + \"deg\" + \")\");\n        break;\n      case TransformType::kSkew:\n        param1 =\n            std::to_string(arr->get(TransformRawData::INDEX_SKEW_0).Number());\n        RemoveRedundantZeros(param1);\n        if (arr->size() <= TransformRawData::INDEX_SKEW_1) {\n          param2 = param1;\n        } else {\n          param2 =\n              std::to_string(arr->get(TransformRawData::INDEX_SKEW_1).Number());\n        }\n        RemoveRedundantZeros(param2);\n        func += std::string(\"(\" + param1 + \"deg, \" + param2 + \"deg)\");\n        break;\n      case TransformType::kScale:\n        param1 =\n            std::to_string(arr->get(TransformRawData::INDEX_SCALE_0).Number());\n        if (arr->size() <= TransformRawData::INDEX_SCALE_1) {\n          param2 = param1;\n        } else {\n          param2 = std::to_string(\n              arr->get(TransformRawData::INDEX_SCALE_1).Number());\n        }\n        RemoveRedundantZeros(param1);\n        RemoveRedundantZeros(param2);\n        func += std::string(\"(\" + param1 + \",\" + param2 + \")\");\n        break;\n      case TransformType::kScaleX:\n      case TransformType::kScaleY:\n        param1 =\n            std::to_string(arr->get(TransformRawData::INDEX_SCALE_0).Number());\n        RemoveRedundantZeros(param1);\n        func += std::string(\"(\" + param1 + \")\");\n        break;\n      case TransformType::kMatrix:\n      case TransformType::kMatrix3d:\n        func += std::string(\n            \"(\" +\n            GetTransformMatrixParams(\n                arr, type == starlight::TransformType::kMatrix ? 7 : 17) +\n            \")\");\n        break;\n      default:\n        break;\n    }\n    res += func;\n    res += \" \";\n  }\n  if (!res.empty()) {\n    res.pop_back();\n  }\n  return res;\n}\n\nstd::string TimingFunctionToString(const lynx::lepus::Value &value) {\n  std::string time_func = \"\";\n  std::string comma = \",\";\n  std::string func_params = \"\";\n  if (value.IsNumber()) {\n    TimingFunctionType timing_func =\n        static_cast<TimingFunctionType>(value.Number());\n    time_func = CSSDecoder::ToTimingFunctionType(timing_func);\n  } else {\n    auto arr = value.Array();\n    TimingFunctionType timing_func = static_cast<TimingFunctionType>(\n        arr->get(TimingFunctionData::INDEX_TYPE).Number());\n    time_func = CSSDecoder::ToTimingFunctionType(timing_func);\n    if (timing_func == TimingFunctionType::kSquareBezier) {\n      float x1 = arr->get(TimingFunctionData::INDEX_X1).Number();\n      float y1 = arr->get(TimingFunctionData::INDEX_Y1).Number();\n      std::string paramx1 = std::to_string(x1);\n      std::string paramy1 = std::to_string(y1);\n      RemoveRedundantZeros(paramx1);\n      RemoveRedundantZeros(paramy1);\n      func_params = \"(\" + paramx1 + comma + paramy1 + \")\";\n    } else if (timing_func == TimingFunctionType::kCubicBezier) {\n      float x1 = arr->get(TimingFunctionData::INDEX_X1).Number();\n      float y1 = arr->get(TimingFunctionData::INDEX_Y1).Number();\n      float x2 = arr->get(TimingFunctionData::INDEX_X2).Number();\n      float y2 = arr->get(TimingFunctionData::INDEX_Y2).Number();\n      std::string paramx1 = std::to_string(x1);\n      std::string paramy1 = std::to_string(y1);\n      std::string paramx2 = std::to_string(x2);\n      std::string paramy2 = std::to_string(y2);\n      RemoveRedundantZeros(paramx1);\n      RemoveRedundantZeros(paramy1);\n      RemoveRedundantZeros(paramx2);\n      RemoveRedundantZeros(paramy2);\n      func_params = \"(\" + paramx1 + comma + paramy1 + comma + paramx2 + comma +\n                    paramy2 + \")\";\n    } else {\n      std::string error_msg =\n          std::string(\"does not support such time func implementation\") +\n          std::to_string(static_cast<int>(timing_func));\n      LynxWarning(false, error::E_CSS_UNSUPPORTED_VALUE, error_msg.c_str());\n    }\n    time_func += func_params;\n  }\n  return time_func;\n}\n\nstd::string ToTransitionProperties(const lynx::tasm::CSSValue &value) {\n  std::string res = \"[\";\n  std::string split = \", \";\n  auto group = value.GetArray();\n  for (size_t i = 0; i < group->size(); ++i) {\n    AnimationPropertyType transition_property =\n        static_cast<AnimationPropertyType>(group->get(i).Number());\n    std::string property_string =\n        CSSDecoder::ToAnimationPropertyType(transition_property);\n    res += property_string;\n    if (i != group->size() - 1) {\n      res += split;\n    }\n  }\n  res += \"]\";\n  return res;\n}\n\nstd::string ToTimingFunctions(const lynx::tasm::CSSValue &value) {\n  std::string res = \"[\";\n  std::string split = \", \";\n  auto group = value.GetArray();\n  for (size_t i = 0; i < group->size(); ++i) {\n    std::string timing_function_string = TimingFunctionToString(group->get(i));\n    res += timing_function_string;\n    if (i != group->size() - 1) {\n      res += split;\n    }\n  }\n  res += \"]\";\n  return res;\n}\n\nstd::string ToTransitionProperty(const lynx::tasm::CSSValue &value) {\n  std::string res = \"\";\n  std::string space = \" \";\n  auto group = value.GetArray();\n  BASE_STATIC_STRING_DECL(kProperty, \"property\");\n  BASE_STATIC_STRING_DECL(kDuration, \"duration\");\n  BASE_STATIC_STRING_DECL(kTiming, \"timing\");\n  BASE_STATIC_STRING_DECL(kDelay, \"delay\");\n  for (size_t i = 0; i < group->size(); i++) {\n    auto dict = group->get(i).Table();\n    AnimationPropertyType animation_property =\n        static_cast<AnimationPropertyType>(dict->GetValue(kProperty).Number());\n    std::string property =\n        CSSDecoder::ToAnimationPropertyType(animation_property);\n    res += property;\n\n    long duration = dict->GetValue(kDuration).Number();\n    res += space;\n    std::string duration_time = std::to_string(duration) + \"ms\";\n    res += duration_time;\n\n    if (dict->Contains(kTiming)) {\n      DCHECK(dict->GetValue(kTiming).IsArray());\n      std::string time_func =\n          TimingFunctionToString(dict->GetValue(kTiming).Array()->get(0));\n      res += space;\n      res += time_func;\n    }\n\n    if (dict->Contains(kDelay)) {\n      long delay = dict->GetValue(kDelay).Number();\n      std::string delay_time = std::to_string(delay) + \"ms\";\n      res += space;\n      res += delay_time;\n    }\n    res += \",\";\n  }\n  if (!res.empty()) {\n    res.pop_back();\n  }\n  return res;\n}\n\nstd::string ToVerticalAlignProperty(const lynx::tasm::CSSValue &value) {\n  auto vertical_align_array = value.GetArray();\n  VerticalAlignType type =\n      static_cast<VerticalAlignType>(vertical_align_array->get(0).Int32());\n  switch (type) {\n    case VerticalAlignType::kBaseline:\n      return \"baseline\";\n    case VerticalAlignType::kSub:\n      return \"sub\";\n    case VerticalAlignType::kSuper:\n      return \"super\";\n    case VerticalAlignType::kTop:\n      return \"top\";\n    case VerticalAlignType::kBottom:\n      return \"bottom\";\n    case VerticalAlignType::kTextTop:\n      return \"text-top\";\n    case VerticalAlignType::kTextBottom:\n      return \"text-bottom\";\n    case VerticalAlignType::kMiddle:\n      return \"middle\";\n    case VerticalAlignType::kCenter:\n      return \"center\";\n    case VerticalAlignType::kPercent:\n    case VerticalAlignType::kLength:\n      return ToLengthWithUnit(\n          vertical_align_array->get(2),\n          static_cast<CSSValuePattern>(vertical_align_array->get(3).Int32()));\n    default:\n      return \"0px\";\n  }\n}\n\nstd::string ToXAutoFontSizeProperty(const lynx::tasm::CSSValue &value) {\n  std::string res;\n  auto auto_font_size_array = value.GetArray();\n  bool is_auto_font_size = auto_font_size_array->get(0).Bool();\n  res += is_auto_font_size ? \"true\" : \"false\";\n\n  double min_font_size = auto_font_size_array->get(1).Number();\n  if (min_font_size != 0.f) {\n    res += \" \";\n    res += ToLengthWithUnit(\n        auto_font_size_array->get(1),\n        static_cast<CSSValuePattern>(auto_font_size_array->get(2).Int32()));\n  }\n\n  double max_font_size = auto_font_size_array->get(3).Number();\n  if (max_font_size != 0.f) {\n    res += \" \";\n    res += ToLengthWithUnit(\n        auto_font_size_array->get(3),\n        static_cast<CSSValuePattern>(auto_font_size_array->get(4).Int32()));\n  }\n\n  double step_granularity = auto_font_size_array->get(5).Number();\n  if (step_granularity != 1.f) {\n    res += \" \";\n    res += ToLengthWithUnit(\n        auto_font_size_array->get(5),\n        static_cast<CSSValuePattern>(auto_font_size_array->get(6).Int32()));\n  }\n\n  return res;\n}\n\nstd::string ToXAutoFontSizePresetSizesProperty(\n    const lynx::tasm::CSSValue &value) {\n  std::string res;\n  auto preset_sizes_array = value.GetArray();\n  for (size_t i = 0; i < preset_sizes_array->size(); i = i + 2) {\n    res += CSSDecoder::CSSValueToString(\n        kPropertyIDXAutoFontSizePresetSizes,\n        CSSValue(preset_sizes_array->get(i),\n                 static_cast<CSSValuePattern>(\n                     preset_sizes_array->get(i + 1).Int32())));\n    if (i != preset_sizes_array->size() - 2) {\n      res += \" \";\n    }\n  }\n  return res;\n}\n\nvoid ComputeShadowStyle(std::string &prop_result, const std::string &key,\n                        const fml::RefPtr<lynx::lepus::Dictionary> &dict) {\n  auto prop_arr = dict->GetValue(key).Array();\n  GetTransformParams(prop_result, prop_arr->get(0), prop_arr->get(1));\n}\n\nstd::string ToBoxShadowProperty(const lynx::tasm::CSSValue &value) {\n  std::string res = \"\";\n  std::string space = \" \";\n  std::string comma = \",\";\n  BASE_STATIC_STRING_DECL(kEnable, \"enable\");\n  BASE_STATIC_STRING_DECL(kBlur, \"blur\");\n  BASE_STATIC_STRING_DECL(kSpread, \"spread\");\n  BASE_STATIC_STRING_DECL(kOption, \"option\");\n  BASE_STATIC_STRING_DECL(kColor, \"color\");\n  auto group = value.GetArray();\n  for (size_t i = 0; i < group->size(); i++) {\n    auto dict = group->get(i).Table();\n    bool enable = true;\n    if (dict->Contains(kEnable)) {\n      enable = dict->GetValue(kEnable).Bool();\n    }\n    if (enable) {\n      std::string h_length = \"0\";\n      std::string v_length = \"0\";\n      ComputeShadowStyle(h_length, \"h_offset\", dict);\n      ComputeShadowStyle(v_length, \"v_offset\", dict);\n      res += (h_length + space + v_length + space);\n\n      if (dict->Contains(kBlur)) {\n        std::string blur_length = \"0\";\n        ComputeShadowStyle(blur_length, kBlur.str(), dict);\n        res += (blur_length + space);\n      }\n\n      if (dict->Contains(kSpread)) {\n        std::string spread_length = \"0\";\n        ComputeShadowStyle(spread_length, kSpread.str(), dict);\n        res += (spread_length + space);\n      }\n\n      if (dict->Contains(kOption)) {\n        auto option = dict->GetValue(kOption).Number();\n        std::string option_type =\n            CSSDecoder::ToShadowOption(static_cast<ShadowOption>(option));\n        res += (option_type + space);\n      }\n\n      if (dict->Contains(kColor)) {\n        auto color = dict->GetValue(kColor).Number();\n        res += (NumberToRGBAColorString(color) + space);\n      }\n\n      if (res.length() == 0) {\n        res += space;\n      } else {\n        res.pop_back();\n      }\n    } else {\n      res += (\"none\" + space);\n    }\n    res += comma;\n  }\n  if (!res.empty()) {\n    res.pop_back();\n  }\n  return res;\n}\n\nstd::string ToBorderRadiusProperty(const lynx::tasm::CSSValue &value) {\n  auto container = value.GetArray();\n  std::string radius_result = \"\";\n  std::string space = \" \";\n  std::string res = \"\";\n  std::string horizontal_res = \"\";\n  std::string vertical_res = \"\";\n  bool is_all_same = true;\n\n  for (int i = 0; i < 4; i++) {\n    auto parse_result = ToLengthWithUnit(\n        container->get(i * 4),\n        static_cast<CSSValuePattern>(container->get(i * 4 + 1).Number()));\n    std::string temp = std::move(parse_result);\n    if (radius_result.empty()) {\n      radius_result = temp;\n    } else {\n      is_all_same &= (radius_result == temp);\n    }\n    horizontal_res += temp + space;\n\n    parse_result = ToLengthWithUnit(\n        container->get(i * 4 + 2),\n        static_cast<CSSValuePattern>(container->get(i * 4 + 3).Number()));\n    temp = std::move(parse_result);\n    if (radius_result.empty()) {\n      radius_result = temp;\n    } else {\n      is_all_same &= (radius_result == temp);\n    }\n    vertical_res += temp + space;\n  }\n  if (horizontal_res == vertical_res) {\n    res = horizontal_res;\n  } else {\n    res = horizontal_res + \"/\" + vertical_res;\n  }\n  if (is_all_same) {\n    res = radius_result;\n  } else if (!res.empty()) {\n    res.pop_back();\n  }\n  return res;\n}\n\nstd::string ToSingleBorderRadiusProperty(const lynx::tasm::CSSValue &value) {\n  auto arr = value.GetArray();\n  std::string res = \"\";\n  std::string space = \" \";\n  auto parse_result = ToLengthWithUnit(\n      arr->get(0), static_cast<CSSValuePattern>(arr->get(1).Number()));\n  std::string radiusX = std::move(parse_result);\n  res += radiusX + space;\n  parse_result = ToLengthWithUnit(\n      arr->get(2), static_cast<CSSValuePattern>(arr->get(3).Number()));\n  std::string radiusY = std::move(parse_result);\n  if (radiusX == radiusY) {\n    res.pop_back();\n  } else {\n    res += radiusY;\n  }\n  return res;\n}\n\nstd::string ToBackgroundSizeEnumString(int32_t val) {\n  constexpr int32_t BACKGROUND_SIZE_AUTO =\n      -static_cast<int>(starlight::BackgroundSizeType::kAuto);\n  constexpr int32_t BACKGROUND_SIZE_COVER =\n      -static_cast<int>(starlight::BackgroundSizeType::kCover);\n  constexpr int32_t BACKGROUND_SIZE_CONTAIN =\n      -static_cast<int>(starlight::BackgroundSizeType::kContain);\n  if (val == BACKGROUND_SIZE_AUTO) {\n    return \"auto\";\n  } else if (val == BACKGROUND_SIZE_COVER) {\n    return \"cover\";\n  } else if (val == BACKGROUND_SIZE_CONTAIN) {\n    return \"contain\";\n  } else {\n    return \"\";\n  }\n}\n\nstd::string ToBackgroundSizeProperty(const lynx::tasm::CSSValue &value) {\n  auto arr = value.GetArray();\n  std::string res = \"\";\n  std::string space = \" \";\n  std::string comma = \",\";\n  for (size_t i = 0; i != arr->size(); ++i) {\n    auto array = arr->get(i).Array();\n    if (array->size() != 4) {\n      return res;\n    }\n    std::string parse_result = \"\";\n    auto pattern = static_cast<CSSValuePattern>(array->get(0).Number());\n    if (pattern != CSSValuePattern::NUMBER) {\n      parse_result = ToLengthWithUnit(array->get(1), pattern);\n    } else {\n      auto val = static_cast<int32_t>(array->get(1).Number());\n      parse_result = ToBackgroundSizeEnumString(val);\n    }\n    // contain and cover can only be setted once for one background-size\n    if (parse_result != \"contain\" && parse_result != \"cover\") {\n      res += parse_result;\n      res += space;\n    }\n\n    pattern = static_cast<CSSValuePattern>(array->get(2).Number());\n    if (pattern != CSSValuePattern::NUMBER) {\n      parse_result = ToLengthWithUnit(array->get(3), pattern);\n    } else {\n      auto val = static_cast<int32_t>(array->get(3).Number());\n      parse_result = ToBackgroundSizeEnumString(val);\n    }\n    res += parse_result;\n    res += comma;\n  }\n  if (!res.empty()) {\n    res.pop_back();\n  }\n  return res;\n}\n\nstd::string ToBackgroundOriginProperty(const lynx::tasm::CSSValue &value) {\n  std::string res = \"\";\n  auto arr = value.GetArray();\n  for (size_t i = 0; i < arr->size(); i++) {\n    if (i != 0) {\n      res += \" ,\";\n    }\n    int32_t raw_value = static_cast<int32_t>(arr->get(i).Number());\n    auto enum_value = static_cast<starlight::BackgroundOriginType>(raw_value);\n    switch (enum_value) {\n      case starlight::BackgroundOriginType::kPaddingBox:\n        res += \"padding-box\";\n        break;\n      case starlight::BackgroundOriginType::kContentBox:\n        res += \"content-box\";\n        break;\n      case starlight::BackgroundOriginType::kBorderBox:\n        res += \"border-box\";\n        break;\n    }\n  }\n\n  return res;\n}\n\nstd::string BackgroundPositionEnumToString(uint32_t value) {\n  auto type = static_cast<starlight::BackgroundPositionType>(value);\n  switch (type) {\n    case starlight::BackgroundPositionType::kTop:\n      return \"top\";\n    case starlight::BackgroundPositionType::kRight:\n      return \"right\";\n    case starlight::BackgroundPositionType::kBottom:\n      return \"bottom\";\n    case starlight::BackgroundPositionType::kLeft:\n      return \"left\";\n    case starlight::BackgroundPositionType::kCenter:\n      return \"center\";\n    default:\n      return \"\";\n  }\n}\n\nstd::string ToBackgroundPositionProperty(const lynx::tasm::CSSValue &value) {\n  std::string res = \"\";\n  auto arr = value.GetArray();\n  for (size_t i = 0; i < arr->size(); i++) {\n    if (i != 0) {\n      res += \", \";\n    }\n\n    if (!arr->get(i).IsArray()) {\n      // some thing is error\n      break;\n    }\n\n    auto pos_arr = arr->get(i).Array();\n    if (pos_arr->size() != 4) {\n      break;\n    }\n\n    uint32_t enum_begin =\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kTop);\n    uint32_t enum_end =\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter);\n\n    // position x\n    uint32_t x_pattern = pos_arr->get(0).UInt32();\n    if (x_pattern >= enum_begin && x_pattern <= enum_end) {\n      res += BackgroundPositionEnumToString(x_pattern);\n    } else {\n      res += ToLengthWithUnit(pos_arr->get(1),\n                              static_cast<CSSValuePattern>(x_pattern));\n    }\n    // space split\n    res += \" \";\n    // position y\n    uint32_t y_pattern = pos_arr->get(2).UInt32();\n    if (y_pattern >= enum_begin && y_pattern <= enum_end) {\n      res += BackgroundPositionEnumToString(y_pattern);\n    } else {\n      res += ToLengthWithUnit(pos_arr->get(3),\n                              static_cast<CSSValuePattern>(y_pattern));\n    }\n  }\n\n  return res;\n}\n\nstd::string BackgroundRepeatEnumToString(uint32_t raw_value) {\n  auto rep = static_cast<starlight::BackgroundRepeatType>(raw_value);\n  switch (rep) {\n    case starlight::BackgroundRepeatType::kRepeat:\n      return \"repeat\";\n    case starlight::BackgroundRepeatType::kNoRepeat:\n      return \"no-repeat\";\n    case starlight::BackgroundRepeatType::kRepeatX:\n      return \"repeat-x\";\n    case starlight::BackgroundRepeatType::kRepeatY:\n      return \"repeat-y\";\n    case starlight::BackgroundRepeatType::kRound:\n      return \"round\";\n    case starlight::BackgroundRepeatType::kSpace:\n      return \"space\";\n    default:\n      return \"\";\n  }\n}\n\nstd::string ToBackgroundRepeatProperty(const lynx::tasm::CSSValue &value) {\n  std::string res = \"\";\n  auto arr = value.GetArray();\n  for (size_t i = 0; i < arr->size(); i++) {\n    if (i != 0) {\n      res += \", \";\n    }\n\n    if (!arr->get(i).IsArray()) {\n      // some thing is error\n      break;\n    }\n\n    auto rep_arr = arr->get(i).Array();\n    if (rep_arr->size() != 2) {\n      break;\n    }\n\n    // repeat x\n    res += BackgroundRepeatEnumToString(rep_arr->get(0).UInt32());\n    if (rep_arr->get(0).UInt32() <=\n        3) {  // space and round only have one keyword\n      // split\n      res += \" \";\n      res += BackgroundRepeatEnumToString(rep_arr->get(1).UInt32());\n    }\n  }\n\n  return res;\n}\n\nstd::string BackgroundUrlToString(const lynx::lepus::Value &value) {\n  if (!value.IsString()) {\n    return \"\";\n  }\n\n  std::string res = \"url(\\\"\";\n\n  res += value.StdString();\n\n  res += \"\\\")\";\n\n  return res;\n}\n\nstd::string GradientColorStopToString(const lynx::lepus::CArray *colors,\n                                      const lynx::lepus::CArray *pos) {\n  std::string res = \"\";\n\n  if (pos->size() > 0 && pos->size() != colors->size()) {\n    return res;\n  }\n\n  for (size_t i = 0; i < colors->size(); i++) {\n    auto number = colors->get(i).Number();\n    if (number != 0.0) {\n      res += NumberToRGBAColorString(number);\n    } else {\n      res += \"transparent\";\n    }\n\n    if (pos->size() > 0) {\n      res += \" \";\n      res += CSSDecoder::NumberToString(pos->get(i).Number());\n      res += \"%\";\n    }\n\n    if (i < colors->size() - 1) {\n      res += \" ,\";\n    }\n  }\n\n  return res;\n}\n\nstd::string BackgroundLinearGradientToString(const lynx::lepus::Value &value) {\n  if (!value.IsArray()) {\n    return \"\";\n  }\n\n  auto arr = value.Array();\n  if (arr->size() < 3) {\n    return \"\";\n  }\n\n  auto angle = arr->get(0);\n  auto colors = arr->get(1);\n  auto pos = arr->get(2);\n\n  if (!colors.IsArray() || !pos.IsArray()) {\n    return \"\";\n  }\n\n  std::string res = \"linear-gradient(\";\n  // parse angle\n  res += CSSDecoder::NumberToString(angle.Number());\n  res += \"deg ,\";\n\n  res += GradientColorStopToString(colors.Array().get(), pos.Array().get());\n\n  res += \")\";\n\n  return res;\n}\n\nstd::string BackgroundRadialGradientToString(const lynx::lepus::Value &value) {\n  if (!value.IsArray()) {\n    return \"\";\n  }\n\n  auto arr = value.Array();\n  if (arr->size() != 3) {\n    return \"\";\n  }\n\n  auto shape_size = arr->get(0);\n  auto colors = arr->get(1);\n  auto pos = arr->get(2);\n\n  if (!shape_size.IsArray() || !colors.IsArray() || !pos.IsArray()) {\n    return \"\";\n  }\n\n  std::string res = \"radial-gradient(\";\n\n  // shape size\n  auto shape_arr = shape_size.Array();\n  auto shape_type =\n      static_cast<RadialGradientShapeType>(shape_arr->get(0).UInt32());\n  if (shape_type == starlight::RadialGradientShapeType::kEllipse) {\n    res += \"ellipse \";\n  } else {\n    res += \"circle \";\n  }\n\n  auto shape_size_type =\n      static_cast<RadialGradientSizeType>(shape_arr->get(1).UInt32());\n  switch (shape_size_type) {\n    case starlight::RadialGradientSizeType::kClosestCorner:\n      res += \"closest-corner \";\n      break;\n    case starlight::RadialGradientSizeType::kClosestSide:\n      res += \"closest-side \";\n      break;\n    case starlight::RadialGradientSizeType::kFarthestCorner:\n      res += \"farthest-corner \";\n      break;\n    case starlight::RadialGradientSizeType::kFarthestSide:\n      res += \"farthest-side \";\n      break;\n    case starlight::RadialGradientSizeType::kLength:\n      res += ToLengthWithUnit(shape_arr->get(7),\n                              static_cast<lynx::tasm::CSSValuePattern>(\n                                  shape_arr->get(6).Number())) +\n             \" \";\n      res += ToLengthWithUnit(shape_arr->get(9),\n                              static_cast<lynx::tasm::CSSValuePattern>(\n                                  shape_arr->get(8).Number())) +\n             \" \";\n      break;\n    default:\n      res += \" \";\n      break;\n  }\n\n  // center position\n  res += \"at \";\n  auto pos_x_type = shape_arr->get(2).UInt32();\n  auto pos_x_value = shape_arr->get(3);\n  auto pos_y_type = shape_arr->get(4).UInt32();\n  auto pos_y_value = shape_arr->get(5);\n\n  uint32_t enum_begin =\n      static_cast<uint32_t>(starlight::BackgroundPositionType::kTop);\n  uint32_t enum_end =\n      static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter);\n\n  if (pos_x_type >= enum_begin && pos_x_type <= enum_end) {\n    res += BackgroundPositionEnumToString(pos_x_type);\n  } else {\n    res +=\n        ToLengthWithUnit(pos_x_value, static_cast<CSSValuePattern>(pos_x_type));\n  }\n\n  res += \" \";\n  if (pos_y_type >= enum_begin && pos_y_type <= enum_end) {\n    res += BackgroundPositionEnumToString(pos_y_type);\n  } else {\n    res +=\n        ToLengthWithUnit(pos_y_value, static_cast<CSSValuePattern>(pos_y_type));\n  }\n\n  res += \" ,\";\n\n  res += GradientColorStopToString(colors.Array().get(), pos.Array().get());\n\n  res += \")\";\n\n  return res;\n}\n\nstd::string BackgroundConicGradientToString(const lynx::lepus::Value &value) {\n  if (!value.IsArray()) {\n    return \"\";\n  }\n\n  auto arr = value.Array();\n  if (arr->size() != 4) {\n    return \"\";\n  }\n\n  auto angle = arr->get(0);\n  auto center = arr->get(1).Array();\n  auto colors = arr->get(2);\n  auto pos = arr->get(3);\n\n  std::string res = \"conic-gradient(from \";\n\n  // parse angle\n  res += CSSDecoder::NumberToString(angle.Number());\n  res += \"deg at \";\n\n  auto pos_x_value = center->get(0);\n  auto pos_x_type = center->get(1).UInt32();\n  res +=\n      ToLengthWithUnit(pos_x_value, static_cast<CSSValuePattern>(pos_x_type));\n  res += \" \";\n\n  auto pos_y_value = center->get(2);\n  auto pos_y_type = center->get(3).UInt32();\n  res +=\n      ToLengthWithUnit(pos_y_value, static_cast<CSSValuePattern>(pos_y_type));\n  res += \",\";\n\n  res += GradientColorStopToString(colors.Array().get(), pos.Array().get());\n\n  res += \")\";\n  return res;\n}\n\nstd::string ToBackgroundImageProperty(const lynx::tasm::CSSValue &value) {\n  std::string res = \"\";\n\n  auto arr = value.GetArray();\n\n  for (size_t i = 0; i < arr->size(); i++) {\n    auto type = arr->get(i);\n    if (!type.IsUInt32()) {\n      continue;\n    }\n\n    i++;\n\n    auto image_value = arr->get(i);\n\n    auto e_type = static_cast<BackgroundImageType>(type.UInt32());\n\n    switch (e_type) {\n      case starlight::BackgroundImageType::kNone:\n        res += \"none\";\n        break;\n      case starlight::BackgroundImageType::kUrl:\n        res += BackgroundUrlToString(image_value);\n        break;\n      case starlight::BackgroundImageType::kLinearGradient:\n        res += BackgroundLinearGradientToString(image_value);\n        break;\n      case starlight::BackgroundImageType::kRadialGradient:\n        res += BackgroundRadialGradientToString(image_value);\n        break;\n      case starlight::BackgroundImageType::kConicGradient:\n        res += BackgroundConicGradientToString(image_value);\n        break;\n    }\n\n    if (i > 0 && i < arr->size() - 1) {\n      res += \" ,\";\n    }\n  }\n\n  return res;\n}\n\nstd::string GradientColorToString(const lynx::tasm::CSSValue &value) {\n  if (!value.IsArray()) {\n    return \"\";\n  }\n\n  auto arr = value.GetArray();\n  if (arr->size() != 2) {\n    return \"\";\n  }\n\n  auto type = static_cast<starlight::BackgroundImageType>(arr->get(0).UInt32());\n  if (type == starlight::BackgroundImageType::kLinearGradient) {\n    return BackgroundLinearGradientToString(arr->get(1));\n  } else if (type == starlight::BackgroundImageType::kRadialGradient) {\n    return BackgroundRadialGradientToString(arr->get(1));\n  } else {\n    return \"\";\n  }\n}\n\nstd::string ToGridTrackSizingProperty(const lynx::tasm::CSSValue &value) {\n  if (!value.IsArray()) {\n    return \"\";\n  }\n\n  auto items = value.GetArray();\n  std::string res;\n\n  for (size_t i = 0; i < items->size(); i += 2) {\n    if (i != 0) {\n      res += \" \";\n    }\n\n    auto length_value = items->get(i);\n    auto pattern_value =\n        static_cast<CSSValuePattern>(items->get(i + 1).Number());\n    // handle minmax function.\n    if (pattern_value == tasm::CSSValuePattern::ENUM &&\n        static_cast<tasm::CSSFunctionType>(length_value.Number()) ==\n            tasm::CSSFunctionType::MINMAX) {\n      res += \"minmax(\";\n      i += 2;\n      if (i + 3 >= items->size()) {\n        return res;\n      }\n      res +=\n          (static_cast<CSSValuePattern>(items->get(i + 1).Number()) ==\n           lynx::tasm::CSSValuePattern::ENUM)\n              ? \"auto\"\n              : ToLengthWithUnit(\n                    items->get(i),\n                    static_cast<CSSValuePattern>(items->get(i + 1).Number()));\n      res += \",\";\n      i += 2;\n      res +=\n          (static_cast<CSSValuePattern>(items->get(i + 1).Number()) ==\n           lynx::tasm::CSSValuePattern::ENUM)\n              ? \"auto\"\n              : ToLengthWithUnit(\n                    items->get(i),\n                    static_cast<CSSValuePattern>(items->get(i + 1).Number()));\n      res += \")\";\n    } else {\n      res += (pattern_value == lynx::tasm::CSSValuePattern::ENUM)\n                 ? \"auto\"\n                 : ToLengthWithUnit(length_value, pattern_value);\n    }\n  }\n  return res;\n}\n\nstd::string ToFilterProperty(const lynx::tasm::CSSValue &value) {\n  if (!value.IsArray()) {\n    return \"\";\n  }\n  auto arr = value.GetArray();\n  std::string res;\n  starlight::FilterType type =\n      static_cast<starlight::FilterType>(arr->get(0).Number());\n  switch (type) {\n    case starlight::FilterType::kGrayscale:\n      res =\n          \"grayscale(\" +\n          ToLengthWithUnit(\n              arr->get(1),\n              static_cast<lynx::tasm::CSSValuePattern>(arr->get(2).Number())) +\n          \")\";\n      break;\n    case starlight::FilterType::kBlur:\n      res +=\n          \"blur(\" +\n          ToLengthWithUnit(\n              arr->get(1),\n              static_cast<lynx::tasm::CSSValuePattern>(arr->get(2).Number())) +\n          \")\";\n      break;\n    case starlight::FilterType::kNone:\n      res = \"none\";\n    default:\n      res = \"not support\";\n      break;\n  }\n  return res;\n}\n\nstd::string ToTextDecorationProperty(const lynx::tasm::CSSValue &value) {\n  if (!value.IsArray()) {\n    return \"\";\n  }\n  auto arr = value.GetArray();\n  std::string res = \"\";\n  for (size_t i = 0; i < arr->size(); i++) {\n    int decoration = static_cast<int>(arr->get(i).Number());\n    if (decoration & static_cast<int>(starlight::TextDecorationType::kColor)) {\n      uint32_t textDecorationColor =\n          static_cast<uint32_t>(arr->get(i + 1).Number());\n      lynx::tasm::CSSColor color;\n      color.a_ = (textDecorationColor & 0xff000000) >> 24;\n      color.r_ = (textDecorationColor & 0x00ff0000) >> 16;\n      color.g_ = (textDecorationColor & 0x0000ff00) >> 8;\n      color.b_ = textDecorationColor & 0x000000ff;\n\n      std::stringstream color_string;\n      if ((static_cast<unsigned int>(color.a_)) != 255) {\n        color_string << \"rgba(\" << static_cast<unsigned int>(color.r_) << \",\"\n                     << static_cast<unsigned int>(color.g_) << \",\"\n                     << static_cast<unsigned int>(color.b_) << \",\"\n                     << color.a_ / 255 << \")\";\n      } else {\n        color_string << \"rgb(\" << static_cast<unsigned int>(color.r_) << \",\"\n                     << static_cast<unsigned int>(color.g_) << \",\"\n                     << static_cast<unsigned int>(color.b_) << \")\";\n      }\n      res += \" \";\n      res += color_string.str();\n      i++;\n      continue;\n    }\n    if (decoration &\n        static_cast<int>(starlight::TextDecorationType::kLineThrough)) {\n      res += \" line-through\";\n    } else if (decoration &\n               static_cast<int>(starlight::TextDecorationType::kUnderLine)) {\n      res += \" underline\";\n    } else if (decoration) {\n      switch (decoration) {\n        case static_cast<int>(starlight::TextDecorationType::kSolid):\n          res += \" solid\";\n          break;\n        case static_cast<int>(starlight::TextDecorationType::kDouble):\n          res += \" double\";\n          break;\n        case static_cast<int>(starlight::TextDecorationType::kDotted):\n          res += \" dotted\";\n          break;\n        case static_cast<int>(starlight::TextDecorationType::kDashed):\n          res += \" dashed\";\n          break;\n        case static_cast<int>(starlight::TextDecorationType::kWavy):\n          res += \" wavy\";\n          break;\n        default:\n          break;\n      }\n    }\n  }\n  if (res.empty()) {\n    res = \"none\";\n  } else {\n    res = res.substr(1);  // trim the first whitespace\n  }\n  return res;\n}\n}  // namespace\n\nstd::string CSSDecoder::CSSValueArrayToString(\n    const CSSPropertyID id, const lynx::tasm::CSSValue &value) {\n  switch (id) {\n    case kPropertyIDAnimationTimingFunction:\n    case kPropertyIDTransitionTimingFunction:\n      return ToTimingFunctions(value);\n    case kPropertyIDTransform:\n      return ToTransformProperty(value);\n    case kPropertyIDTransition:\n      return ToTransitionProperty(value);\n    case kPropertyIDTransitionProperty:\n      return ToTransitionProperties(value);\n    case kPropertyIDBoxShadow:\n    case kPropertyIDTextShadow:\n      return ToBoxShadowProperty(value);\n    case kPropertyIDBorderRadius:\n      return ToBorderRadiusProperty(value);\n    case kPropertyIDBorderTopLeftRadius:\n    case kPropertyIDBorderTopRightRadius:\n    case kPropertyIDBorderBottomLeftRadius:\n    case kPropertyIDBorderBottomRightRadius:\n    case kPropertyIDBorderStartStartRadius:\n    case kPropertyIDBorderStartEndRadius:\n    case kPropertyIDBorderEndStartRadius:\n    case kPropertyIDBorderEndEndRadius:\n      return ToSingleBorderRadiusProperty(value);\n    case kPropertyIDBackgroundSize:\n      return ToBackgroundSizeProperty(value);\n    case kPropertyIDBackgroundOrigin:\n      return ToBackgroundOriginProperty(value);\n    case kPropertyIDBackgroundPosition:\n      return ToBackgroundPositionProperty(value);\n    case kPropertyIDBackgroundRepeat:\n      return ToBackgroundRepeatProperty(value);\n    case kPropertyIDBackgroundImage:\n      return ToBackgroundImageProperty(value);\n    case kPropertyIDColor:\n      return GradientColorToString(value);\n    case kPropertyIDTextDecoration:\n      return ToTextDecorationProperty(value);\n    case kPropertyIDFilter:\n      return ToFilterProperty(value);\n    case kPropertyIDGridTemplateColumns:\n    case kPropertyIDGridTemplateRows:\n    case kPropertyIDGridAutoColumns:\n    case kPropertyIDGridAutoRows:\n      return ToGridTrackSizingProperty(value);\n    case kPropertyIDVerticalAlign:\n      return ToVerticalAlignProperty(value);\n    case kPropertyIDXAutoFontSize:\n      return ToXAutoFontSizeProperty(value);\n    case kPropertyIDXAutoFontSizePresetSizes:\n      return ToXAutoFontSizePresetSizesProperty(value);\n    case kPropertyIDFontVariationSettings:\n      return value.AsJsonString();\n    case kPropertyIDFontFeatureSettings:\n      return value.AsJsonString();\n    default:\n      return value.AsJsonString();\n  }\n}\n\nstd::string CSSDecoder::ToFlexAlignType(FlexAlignType type) {\n  switch (type) {\n    case FlexAlignType::kAuto:\n      return \"auto\";\n    case FlexAlignType::kStretch:\n      return \"stretch\";\n    case FlexAlignType::kFlexStart:\n      return \"flex-start\";\n    case FlexAlignType::kFlexEnd:\n      return \"flex-end\";\n    case FlexAlignType::kCenter:\n      return \"center\";\n    case FlexAlignType::kBaseline:\n      return \"baseline\";\n    case FlexAlignType::kStart:\n      return \"start\";\n    case FlexAlignType::kEnd:\n      return \"end\";\n  }\n}\n\nstd::string CSSDecoder::ToLengthType(lynx::starlight::LengthValueType type) {\n  if (type == LengthValueType::kAuto) {\n    return \"auto\";\n  }\n  LynxWarning(false, error::E_CSS_UNSUPPORTED_VALUE,\n              \"no such enum for type: %d\", (int)type);\n  return \"\";\n}\n\nstd::string CSSDecoder::ToOverflowType(lynx::starlight::OverflowType type) {\n  switch (type) {\n    case OverflowType::kVisible:\n      return \"visible\";\n    case OverflowType::kHidden:\n      return \"hidden\";\n    case OverflowType::kScroll:\n      return \"scroll\";\n  }\n}\n\nstd::string CSSDecoder::ToTimingFunctionType(\n    lynx::starlight::TimingFunctionType type) {\n  switch (type) {\n    case TimingFunctionType::kLinear:\n      return \"linear\";\n    case TimingFunctionType::kEaseIn:\n      return \"ease-in\";\n    case TimingFunctionType::kEaseOut:\n      return \"ease-out\";\n    case TimingFunctionType::kEaseInEaseOut:\n      return \"ease-in-out\";\n    case TimingFunctionType::kSquareBezier:\n      return \"square-bezier\";\n    case TimingFunctionType::kCubicBezier:\n      return \"cubic-bezier\";\n    default:\n      LynxWarning(false, error::E_CSS_UNSUPPORTED_VALUE,\n                  \"%d timing function type is invalid.\", (int)type);\n      return \"\";\n  }\n}\n\nstd::string CSSDecoder::ToAnimationPropertyType(\n    lynx::starlight::AnimationPropertyType type) {\n  switch (type) {\n    case AnimationPropertyType::kNone:\n      return \"none\";\n    case AnimationPropertyType::kOpacity:\n      return \"opacity\";\n    case AnimationPropertyType::kScaleX:\n      return \"scaleX\";\n    case AnimationPropertyType::kScaleY:\n      return \"scaleY\";\n    case AnimationPropertyType::kScaleXY:\n      return \"scaleXY\";\n    case AnimationPropertyType::kWidth:\n      return \"width\";\n    case AnimationPropertyType::kHeight:\n      return \"height\";\n    case AnimationPropertyType::kBackgroundColor:\n      return \"background-color\";\n    case AnimationPropertyType::kColor:\n      return \"color\";\n    case AnimationPropertyType::kVisibility:\n      return \"visibility\";\n    case AnimationPropertyType::kLeft:\n      return \"left\";\n    case AnimationPropertyType::kTop:\n      return \"top\";\n    case AnimationPropertyType::kRight:\n      return \"right\";\n    case AnimationPropertyType::kBottom:\n      return \"bottom\";\n    case AnimationPropertyType::kTransform:\n      return \"transform\";\n    case AnimationPropertyType::kAll:\n    case AnimationPropertyType::kLegacyAll_3:\n    case AnimationPropertyType::kLegacyAll_2:\n    case AnimationPropertyType::kLegacyAll_1:\n      return \"all\";\n    case starlight::AnimationPropertyType::kMaxWidth:\n      return \"max-width\";\n    case starlight::AnimationPropertyType::kMinWidth:\n      return \"min-width\";\n    case starlight::AnimationPropertyType::kMaxHeight:\n      return \"max-height\";\n    case starlight::AnimationPropertyType::kMinHeight:\n      return \"min-height\";\n    case starlight::AnimationPropertyType::kMarginLeft:\n      return \"margin-left\";\n    case starlight::AnimationPropertyType::kMarginRight:\n      return \"margin-right\";\n    case starlight::AnimationPropertyType::kMarginTop:\n      return \"margin-top\";\n    case starlight::AnimationPropertyType::kMarginBottom:\n      return \"margin-bottom\";\n    case starlight::AnimationPropertyType::kPaddingLeft:\n      return \"padding-left\";\n    case starlight::AnimationPropertyType::kPaddingRight:\n      return \"padding-right\";\n    case starlight::AnimationPropertyType::kPaddingTop:\n      return \"padding-top\";\n    case starlight::AnimationPropertyType::kPaddingBottom:\n      return \"padding-bottom\";\n    case starlight::AnimationPropertyType::kBorderLeftWidth:\n      return \"border-left-width\";\n    case starlight::AnimationPropertyType::kBorderLeftColor:\n      return \"border-left-color\";\n    case starlight::AnimationPropertyType::kBorderRightWidth:\n      return \"border-right-width\";\n    case starlight::AnimationPropertyType::kBorderRightColor:\n      return \"border-right-color\";\n    case starlight::AnimationPropertyType::kBorderTopWidth:\n      return \"border-top-width\";\n    case starlight::AnimationPropertyType::kBorderTopColor:\n      return \"border-top-color\";\n    case starlight::AnimationPropertyType::kBorderBottomWidth:\n      return \"border-bottom-width\";\n    case starlight::AnimationPropertyType::kBorderBottomColor:\n      return \"border-bottom-color\";\n    case starlight::AnimationPropertyType::kFlexBasis:\n      return \"flex-basis\";\n    case starlight::AnimationPropertyType::kFlexGrow:\n      return \"flex-grow\";\n    case starlight::AnimationPropertyType::kBorderWidth:\n      return \"border-width\";\n    case starlight::AnimationPropertyType::kBorderColor:\n      return \"border-color\";\n    case starlight::AnimationPropertyType::kMargin:\n      return \"margin\";\n    case starlight::AnimationPropertyType::kPadding:\n      return \"padding\";\n    case starlight::AnimationPropertyType::kFilter:\n      return \"filter\";\n    case starlight::AnimationPropertyType::kOffsetDistance:\n      return \"offset-distance\";\n    case starlight::AnimationPropertyType::kBoxShadow:\n      return \"box-shadow\";\n    case starlight::AnimationPropertyType::kBackgroundPosition:\n      return \"background-position\";\n    case starlight::AnimationPropertyType::kTransformOrigin:\n      return \"transform-origin\";\n  }\n}\n\nstd::string CSSDecoder::ToBorderStyleType(\n    lynx::starlight::BorderStyleType type) {\n  switch (type) {\n    case BorderStyleType::kSolid:\n      return \"solid\";\n    case BorderStyleType::kDashed:\n      return \"dashed\";\n    case BorderStyleType::kDotted:\n      return \"dotted\";\n    case BorderStyleType::kDouble:\n      return \"double\";\n    case BorderStyleType::kGroove:\n      return \"groove\";\n    case BorderStyleType::kRidge:\n      return \"ridge\";\n    case BorderStyleType::kInset:\n      return \"inset\";\n    case BorderStyleType::kOutset:\n      return \"outset\";\n    case BorderStyleType::kHide:\n      return \"hide\";\n    case BorderStyleType::kNone:\n      return \"none\";\n    case BorderStyleType::kUndefined:\n      return \"undefined\";\n  }\n}\n\nstd::string CSSDecoder::ToShadowOption(lynx::starlight::ShadowOption option) {\n  switch (option) {\n    case ShadowOption::kNone:\n      return \"none\";\n    case ShadowOption::kInset:\n      return \"inset\";\n    case ShadowOption::kInitial:\n      return \"initial\";\n    case ShadowOption::kInherit:\n      return \"inherit\";\n  }\n}\n\nstd::string CSSDecoder::ToAnimationDirectionType(AnimationDirectionType type) {\n  switch (type) {\n    case AnimationDirectionType::kNormal:\n      return \"normal\";\n    case AnimationDirectionType::kReverse:\n      return \"reverse\";\n    case AnimationDirectionType::kAlternate:\n      return \"alternate\";\n    case AnimationDirectionType::kAlternateReverse:\n      return \"alternate-reverse\";\n  }\n}\n\nstd::string CSSDecoder::ToAnimationFillModeType(AnimationFillModeType type) {\n  switch (type) {\n    case AnimationFillModeType::kNone:\n      return \"none\";\n    case AnimationFillModeType::kForwards:\n      return \"forwards\";\n    case AnimationFillModeType::kBackwards:\n      return \"backwards\";\n    case AnimationFillModeType::kBoth:\n      return \"both\";\n  }\n}\n\nstd::string CSSDecoder::ToAnimationPlayStateType(AnimationPlayStateType type) {\n  switch (type) {\n    case AnimationPlayStateType::kPaused:\n      return \"paused\";\n    case AnimationPlayStateType::kRunning:\n      return \"running\";\n  }\n}\n\nstd::string CSSDecoder::ToJustifyType(JustifyType type) {\n  switch (type) {\n    case JustifyType::kAuto:\n      return \"auto\";\n    case JustifyType::kStretch:\n      return \"stretch\";\n    case JustifyType::kStart:\n      return \"start\";\n    case JustifyType::kEnd:\n      return \"end\";\n    case JustifyType::kCenter:\n      return \"center\";\n  }\n}\n\nstd::string CSSDecoder::ToGridAutoFlowType(GridAutoFlowType type) {\n  switch (type) {\n    case GridAutoFlowType::kRow:\n      return \"row\";\n    case GridAutoFlowType::kColumn:\n      return \"column\";\n    case GridAutoFlowType::kDense:\n      return \"dense\";\n    case GridAutoFlowType::kRowDense:\n      return \"row dense\";\n    case GridAutoFlowType::kColumnDense:\n      return \"column dense\";\n  }\n}\n\nstd::string CSSDecoder::ToRgbaFromColorValue(const std::string &color_value) {\n  lynx::tasm::CSSColor color;\n  std::string real_color_value;\n  if (lynx::tasm::CSSColor::Parse(color_value, color)) {\n    real_color_value = std::to_string(color.Cast());\n  } else {\n    real_color_value = color_value;\n  }\n\n  std::stringstream ss(real_color_value);\n  unsigned int color_val;\n  ss >> color_val;\n  color.a_ = (color_val & 0xff000000) >> 24;\n  color.r_ = (color_val & 0x00ff0000) >> 16;\n  color.g_ = (color_val & 0x0000ff00) >> 8;\n  color.b_ = color_val & 0x000000ff;\n\n  std::stringstream color_string;\n  if ((static_cast<unsigned int>(color.a_)) != 255) {\n    color_string << \"rgba(\" << static_cast<unsigned int>(color.r_) << \",\"\n                 << static_cast<unsigned int>(color.g_) << \",\"\n                 << static_cast<unsigned int>(color.b_) << \",\" << color.a_ / 255\n                 << \")\";\n  } else {\n    color_string << \"rgb(\" << static_cast<unsigned int>(color.r_) << \",\"\n                 << static_cast<unsigned int>(color.g_) << \",\"\n                 << static_cast<unsigned int>(color.b_) << \")\";\n  }\n  return color_string.str();\n}\n\nstd::string CSSDecoder::ToRgbaFromRgbaValue(const std::string &r,\n                                            const std::string &g,\n                                            const std::string &b,\n                                            const std::string &a) {\n  std::stringstream color_string;\n  color_string << \"rgba(\" << r << \",\" << g << \",\" << b << \",\" << a.substr(0, 5)\n               << \")\";\n  return color_string.str();\n}\n\nstd::string CSSDecoder::ToPxValue(double px_value) {\n  std::ostringstream oss;\n  // remove last zero && add px suffix\n  oss << px_value << \"px\";\n  return oss.str();\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_decoder.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_DECODER_H_\n#define CORE_RENDERER_CSS_CSS_DECODER_H_\n\n#include <string>\n\n#include \"core/renderer/css/auto_gen_css_decoder.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass CSSDecoder : public AutoGenCSSDecoder {\n public:\n  static std::string CSSValueToString(const lynx::tasm::CSSPropertyID id,\n                                      const lynx::tasm::CSSValue& value,\n                                      bool map_key_ordered = false);\n  static std::string CSSValueEnumToString(const lynx::tasm::CSSPropertyID id,\n                                          const lynx::tasm::CSSValue& value);\n\n  static std::string CSSValueNumberToString(const lynx::tasm::CSSPropertyID id,\n                                            const lynx::tasm::CSSValue& value);\n\n  static std::string CSSValueArrayToString(const lynx::tasm::CSSPropertyID id,\n                                           const lynx::tasm::CSSValue& value);\n\n  static std::string ToFlexAlignType(lynx::starlight::FlexAlignType type);\n\n  static std::string ToLengthType(lynx::starlight::LengthValueType type);\n\n  static std::string ToOverflowType(lynx::starlight::OverflowType type);\n\n  static std::string ToTimingFunctionType(\n      lynx::starlight::TimingFunctionType type);\n\n  static std::string ToAnimationPropertyType(\n      lynx::starlight::AnimationPropertyType type);\n\n  static std::string ToBorderStyleType(lynx::starlight::BorderStyleType type);\n\n  static std::string ToTransformType(lynx::starlight::TransformType type);\n\n  static std::string ToShadowOption(lynx::starlight::ShadowOption option);\n\n  static std::string ToAnimationDirectionType(\n      lynx::starlight::AnimationDirectionType type);\n\n  static std::string ToAnimationFillModeType(\n      lynx::starlight::AnimationFillModeType type);\n\n  static std::string ToAnimationPlayStateType(\n      lynx::starlight::AnimationPlayStateType type);\n\n  static std::string ToRgbaFromColorValue(const std::string& color_value);\n\n  static std::string ToRgbaFromRgbaValue(const std::string& r,\n                                         const std::string& g,\n                                         const std::string& b,\n                                         const std::string& a);\n\n  static std::string ToPxValue(double px_value);\n\n  static std::string ToJustifyType(lynx::starlight::JustifyType type);\n  static std::string ToGridAutoFlowType(lynx::starlight::GridAutoFlowType type);\n\n  static std::string NumberToString(double number);\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_DECODER_H_\n"
  },
  {
    "path": "core/renderer/css/css_font_face_token.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_font_face_token.h\"\n\n#include <utility>\n\n#include \"core/renderer/utils/value_utils.h\"\n\nconstexpr const static char* FONT_FAMILY = \"font-family\";\n\nnamespace lynx {\nnamespace tasm {\n\ninline static std::string _innerTrTrim(const std::string& str) {\n  static const std::string chs = \"' \\t\\v\\r\\n\\\"\";\n  size_t first = str.find_first_not_of(chs);\n  size_t last = str.find_last_not_of(chs);\n  if (first == std::string::npos || last == std::string::npos) {\n    return \"\";\n  }\n  return str.substr(first, (last - first + 1));\n}\n\nCSSFontFaceRule* MakeCSSFontFaceToken(lepus::Value value) {\n  CSSFontFaceRule* rule = new CSSFontFaceRule();\n  ForEachLepusValue(\n      value, [rule](const lepus::Value& k, const lepus::Value& v) {\n        if (k.IsString() && v.IsString()) {\n          CSSFontTokenAddAttribute(rule, k.StdString(), v.StdString());\n        }\n      });\n  return rule;\n}\n\nvoid CSSFontTokenAddAttribute(CSSFontFaceRule* rule, const std::string& name,\n                              const std::string& val) {\n  std::string newName = _innerTrTrim(name);\n  std::string newVal = _innerTrTrim(val);\n  if (name == FONT_FAMILY) {\n    rule->first = newVal;\n  }\n  rule->second[std::move(newName)] = std::move(newVal);\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_font_face_token.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_FONT_FACE_TOKEN_H_\n#define CORE_RENDERER_CSS_CSS_FONT_FACE_TOKEN_H_\n\n#include <cstdint>\n#include <string>\n\n#include \"base/include/value/base_value.h\"\n#include \"core/public/layout_ctx_platform_impl.h\"\n\nstatic constexpr uint8_t CSS_BINARY_FONT_FACE_TYPE = 0x01;\n\nnamespace lynx {\nnamespace tasm {\n\nCSSFontFaceRule* MakeCSSFontFaceToken(lepus::Value value);\nvoid CSSFontTokenAddAttribute(CSSFontFaceRule* token, const std::string& name,\n                              const std::string& val);\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_FONT_FACE_TOKEN_H_\n"
  },
  {
    "path": "core/renderer/css/css_font_face_token_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/renderer/css/css_font_face_token.h\"\n\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/table.h\"\n#include \"core/base/json/json_util.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/rapidjson/document.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nclass CSSFontFaceTokenTest : public ::testing::Test {\n protected:\n  CSSFontFaceTokenTest() {}\n  ~CSSFontFaceTokenTest() {}\n\n  void SetUp() override {}\n\n  void TearDown() override {}\n};\n\nTEST_F(CSSFontFaceTokenTest, TestMakeCSSFontFaceToken) {\n  lepus::Value obj(lepus::Dictionary::Create());\n  obj.SetProperty(\"key1\", lepus::Value(\"value1\"));\n  obj.SetProperty(\"key2\", lepus::Value(\"value2\"));\n  obj.SetProperty(\"key3\", lepus::Value(\"value3\"));\n  obj.SetProperty(\"key4\", lepus::Value(\"       \"));\n  obj.SetProperty(\"font-family\", lepus::Value(\"font-family-value\"));\n\n  CSSFontFaceRule* rule = MakeCSSFontFaceToken(obj);\n  EXPECT_EQ(rule->second.size(), 5);\n  EXPECT_EQ(rule->first, \"font-family-value\");\n\n  EXPECT_EQ(rule->second[\"key1\"], \"value1\");\n  EXPECT_EQ(rule->second[\"key2\"], \"value2\");\n  EXPECT_EQ(rule->second[\"key3\"], \"value3\");\n  EXPECT_EQ(rule->second[\"key4\"], \"\");\n  EXPECT_EQ(rule->second[\"font-family\"], \"font-family-value\");\n  delete rule;\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_fragment.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/css_fragment.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nconst CSSKeyframesTokenMap& CSSFragment::GetKeyframesRuleMap() {\n  return keyframes_;\n}\n\nconst CSSFontFaceRuleMap& CSSFragment::GetFontFaceRuleMap() {\n  return fontfaces_;\n}\n\nCSSKeyframesToken* CSSFragment::GetKeyframesRule(const base::String& key) {\n  auto it = keyframes_.find(key);\n  if (it != keyframes_.end()) {\n    return it->second.get();\n  }\n  return nullptr;\n}\n\nconst std::vector<std::shared_ptr<CSSFontFaceRule>>&\nCSSFragment::GetFontFaceRule(const std::string& key) {\n  auto it = fontfaces_.find(key);\n  if (it != fontfaces_.end()) {\n    return it->second;\n  }\n  return GetDefaultFontFaceList();\n}\n\nconst std::vector<std::shared_ptr<CSSFontFaceRule>>&\nCSSFragment::GetDefaultFontFaceList() {\n  static base::NoDestructor<std::vector<std::shared_ptr<CSSFontFaceRule>>>\n      fontfaces{};\n  return *fontfaces;\n}\n\nvoid CSSFragment::CollectIdChangedInvalidation(CSSFragment* style_sheet,\n                                               css::InvalidationLists& lists,\n                                               const std::string& old_id,\n                                               const std::string& new_id) {\n  // We know the style_sheet is not empty\n  if (!old_id.empty()) style_sheet->CollectInvalidationSetsForId(lists, old_id);\n  if (!new_id.empty()) style_sheet->CollectInvalidationSetsForId(lists, new_id);\n}\n\nvoid CSSFragment::CollectClassChangedInvalidation(\n    CSSFragment* style_sheet, css::InvalidationLists& lists,\n    const ClassList& old_classes, const ClassList& new_classes) {\n  if (old_classes.empty()) {\n    for (auto& class_name : new_classes) {\n      style_sheet->CollectInvalidationSetsForClass(lists, class_name.str());\n    }\n  } else {\n    base::InlineVector<bool, ClassList::kInlinedSize> remaining_class_bits(\n        old_classes.size());\n    for (auto& class_name : new_classes) {\n      bool found = false;\n      for (unsigned j = 0; j < old_classes.size(); ++j) {\n        if (class_name == old_classes[j]) {\n          // Mark each class that is still in the newClasses, so we can skip\n          // doing a n^2 search below when looking for removals. We can't\n          // break from this loop early since a class can appear more than\n          // once.\n          remaining_class_bits[j] = true;\n          found = true;\n        }\n      }\n      // Class was added.\n      if (!found) {\n        style_sheet->CollectInvalidationSetsForClass(lists, class_name.str());\n      }\n    }\n\n    for (unsigned i = 0; i < old_classes.size(); ++i) {\n      if (remaining_class_bits[i]) continue;\n      // Class was removed.\n      style_sheet->CollectInvalidationSetsForClass(lists, old_classes[i].str());\n    }\n  }\n}\n\nvoid CSSFragment::CollectPseudoChangedInvalidation(\n    CSSFragment* style_sheet, css::InvalidationLists& lists, PseudoState prev,\n    PseudoState curr) {\n  if ((prev ^ curr) & kPseudoStateFocus) {\n    style_sheet->CollectInvalidationSetsForPseudoClass(\n        lists, css::LynxCSSSelector::kPseudoFocus);\n  }\n  if ((prev ^ curr) & kPseudoStateActive) {\n    style_sheet->CollectInvalidationSetsForPseudoClass(\n        lists, css::LynxCSSSelector::kPseudoActive);\n  }\n  if ((prev ^ curr) & kPseudoStateHover) {\n    style_sheet->CollectInvalidationSetsForPseudoClass(\n        lists, css::LynxCSSSelector::kPseudoHover);\n  }\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_fragment.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_FRAGMENT_H_\n#define CORE_RENDERER_CSS_CSS_FRAGMENT_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"base/include/value/base_string.h\"\n#include \"base/include/vector.h\"\n#include \"core/renderer/css/css_font_face_token.h\"\n#include \"core/renderer/css/css_keyframes_token.h\"\n#include \"core/renderer/css/css_parser_token.h\"\n#include \"core/renderer/css/ng/invalidation/invalidation_set.h\"\n#include \"core/renderer/css/ng/style/rule_set.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nstruct PseudoNotContent {\n  CSSSheet::SheetType scope_type;\n  std::string selector_key;\n  std::string scope;\n};\n\nusing PseudoClassStyleMap = std::unordered_map<std::string, PseudoNotContent>;\n\nusing CSSParserTokenMap =\n    std::unordered_map<std::string, fml::RefPtr<CSSParseToken>>;\n\nusing CSSKeyframesTokenMap =\n    base::LinearFlatMap<base::String, fml::RefPtr<CSSKeyframesToken>>;\n\nstruct PseudoNotStyle {\n  PseudoClassStyleMap pseudo_not_for_tag;\n  PseudoClassStyleMap pseudo_not_for_class;\n  PseudoClassStyleMap pseudo_not_for_id;\n  std::unordered_map<int, PseudoClassStyleMap> pseudo_not_global_map;\n};\n\n// TODO(songshourui.null): rename this class to StyleSheet.\nclass CSSFragment {\n public:\n  CSSFragment() = default;\n\n  CSSFragment(CSSKeyframesTokenMap keyframes, CSSFontFaceRuleMap fontfaces)\n      : keyframes_(std::move(keyframes)), fontfaces_(std::move(fontfaces)){};\n\n  virtual ~CSSFragment() = default;\n\n  virtual const CSSParserTokenMap& pseudo_map() = 0;\n  virtual const CSSParserTokenMap& child_pseudo_map() = 0;\n  virtual const CSSParserTokenMap& cascade_map() = 0;\n  virtual const CSSParserTokenMap& css() = 0;\n  virtual css::RuleSet* rule_set() = 0;\n  virtual const PseudoNotStyle& pseudo_not_style() = 0;\n\n  virtual CSSParseToken* GetCSSStyle(const std::string& key) = 0;\n  virtual CSSParseToken* GetPseudoStyle(const std::string& key) = 0;\n  virtual CSSParseToken* GetCascadeStyle(const std::string& key) = 0;\n  virtual CSSParseToken* GetIdStyle(const std::string& key) = 0;\n  virtual CSSParseToken* GetTagStyle(const std::string& key) = 0;\n  virtual CSSParseToken* GetUniversalStyle(const std::string& key) = 0;\n\n  virtual bool HasPseudoNotStyle() = 0;\n  virtual void InitPseudoNotStyle() = 0;\n  virtual bool HasIdSelector() { return true; }\n\n  virtual bool enable_css_selector() = 0;\n  virtual bool enable_css_invalidation() = 0;\n\n  virtual void CollectInvalidationSetsForId(css::InvalidationLists& lists,\n                                            const std::string& id) = 0;\n  virtual void CollectInvalidationSetsForClass(\n      css::InvalidationLists& lists, const std::string& class_name) = 0;\n  virtual void CollectInvalidationSetsForPseudoClass(\n      css::InvalidationLists& lists,\n      css::LynxCSSSelector::PseudoType pseudo) = 0;\n\n  virtual fml::RefPtr<CSSParseToken> GetSharedCSSStyle(\n      const std::string& key) = 0;\n\n  virtual const CSSKeyframesTokenMap& GetKeyframesRuleMap();\n\n  virtual const CSSFontFaceRuleMap& GetFontFaceRuleMap();\n\n  virtual CSSKeyframesToken* GetKeyframesRule(const base::String& key);\n  virtual const std::vector<std::shared_ptr<CSSFontFaceRule>>& GetFontFaceRule(\n      const std::string& key);\n\n  virtual bool HasCSSStyle() = 0;\n\n  bool HasPseudoStyle() { return !pseudo_map().empty(); }\n\n  bool HasCascadeStyle() { return !cascade_map().empty(); }\n\n  bool HasFontFacesResolved() const { return has_font_faces_resolved_; }\n\n  void MarkFontFacesResolved(bool resolved) {\n    has_font_faces_resolved_ = resolved;\n  }\n\n  void MarkHasTouchPseudoToken() { has_touch_pseudo_token_ = true; }\n  bool HasTouchPseudoToken() const { return has_touch_pseudo_token_; }\n  const std::vector<std::shared_ptr<CSSFontFaceRule>>& GetDefaultFontFaceList();\n\n  void SetKeyFramesRuleMap(CSSKeyframesTokenMap map) {\n    keyframes_ = std::move(map);\n  }\n  void SetFontFaceRuleMap(CSSFontFaceRuleMap map) {\n    fontfaces_ = std::move(map);\n  }\n\n  bool GetEnableCSSLazyImport() { return enable_css_lazy_import_; }\n\n  void SetEnableCSSLazyImport(bool enable) { enable_css_lazy_import_ = enable; }\n\n  static void CollectIdChangedInvalidation(CSSFragment*,\n                                           css::InvalidationLists&,\n                                           const std::string&,\n                                           const std::string&);\n\n  static void CollectClassChangedInvalidation(CSSFragment*,\n                                              css::InvalidationLists&,\n                                              const ClassList&,\n                                              const ClassList&);\n\n  static void CollectPseudoChangedInvalidation(CSSFragment*,\n                                               css::InvalidationLists&,\n                                               PseudoState, PseudoState);\n\n protected:\n  bool has_touch_pseudo_token_{false};\n  // FIXME(linxs): it's better to flush related fontface or keyframe only when\n  // any element has font-family or animation indicated the font faces has been\n  // resolved or not\n  bool has_font_faces_resolved_{false};\n\n  // enableCSSLazyImport's default value is false now.\n  bool enable_css_lazy_import_ = false;\n\n  std::optional<bool> has_css_style_;\n\n  CSSKeyframesTokenMap keyframes_;\n  CSSFontFaceRuleMap fontfaces_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_FRAGMENT_H_\n"
  },
  {
    "path": "core/renderer/css/css_fragment_decorator.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_fragment_decorator.h\"\n\n#include <utility>\n\n#include \"base/include/no_destructor.h\"\n#include \"base/trace/native/trace_event.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nCSSFragmentDecorator::~CSSFragmentDecorator() = default;\n\nconst CSSParserTokenMap& CSSFragmentDecorator::pseudo_map() {\n  if (!intrinsic_style_sheets_) {\n    static base::NoDestructor<CSSParserTokenMap> fake_pseudo{};\n    return *fake_pseudo;\n  }\n  return intrinsic_style_sheets_->pseudo_map();\n}\n\nconst CSSParserTokenMap& CSSFragmentDecorator::child_pseudo_map() {\n  if (!intrinsic_style_sheets_) {\n    static base::NoDestructor<CSSParserTokenMap> fake_child_pseudo{};\n    return *fake_child_pseudo;\n  }\n  return intrinsic_style_sheets_->child_pseudo_map();\n}\n\nconst PseudoNotStyle& CSSFragmentDecorator::pseudo_not_style() {\n  if (!intrinsic_style_sheets_) {\n    static base::NoDestructor<PseudoNotStyle> fake_pseudo_not_style{};\n    return *fake_pseudo_not_style;\n  }\n  return intrinsic_style_sheets_->pseudo_not_style();\n}\n\nconst CSSParserTokenMap& CSSFragmentDecorator::css() {\n  // TODO(wangyifei.20010605): only for unittest, don't use it please, I will\n  // remove it later.\n  if (intrinsic_style_sheets_ &&\n      intrinsic_style_sheets_->enable_css_selector()) {\n    for (auto& ext_css : external_css_) {\n      intrinsic_style_sheets_->rule_set()->AddToRuleSet(ext_css.first,\n                                                        ext_css.second);\n    }\n  }\n  if (!external_css_.empty()) {\n    return external_css_;\n  } else if (intrinsic_style_sheets_) {\n    return intrinsic_style_sheets_->css();\n  }\n  return external_css_;\n}\n\nconst CSSParserTokenMap& CSSFragmentDecorator::cascade_map() {\n  if (!intrinsic_style_sheets_) {\n    static base::NoDestructor<CSSParserTokenMap> fake_cascade{};\n    return *fake_cascade;\n  }\n  return intrinsic_style_sheets_->cascade_map();\n}\n\nconst CSSKeyframesTokenMap& CSSFragmentDecorator::GetKeyframesRuleMap() {\n  if (!intrinsic_style_sheets_) {\n    static base::NoDestructor<CSSKeyframesTokenMap> fake_keyframes{};\n    return *fake_keyframes;\n  }\n  return intrinsic_style_sheets_->GetKeyframesRuleMap();\n}\n\ncss::RuleSet* CSSFragmentDecorator::rule_set() {\n  if (!intrinsic_style_sheets_) {\n    return nullptr;\n  }\n  return intrinsic_style_sheets_->rule_set();\n}\n\nconst CSSFontFaceRuleMap& CSSFragmentDecorator::GetFontFaceRuleMap() {\n  if (!intrinsic_style_sheets_) {\n    static base::NoDestructor<CSSFontFaceRuleMap> fake_fontfaces{};\n    return *fake_fontfaces;\n  }\n  return intrinsic_style_sheets_->GetFontFaceRuleMap();\n}\n\nbool CSSFragmentDecorator::HasCSSStyle() {\n  if (has_css_style_.has_value()) {\n    return has_css_style_.value_or(false);\n  }\n  if (enable_css_lazy_import_) {\n    if (!external_css_.empty() ||\n        (intrinsic_style_sheets_ && intrinsic_style_sheets_->HasCSSStyle())) {\n      has_css_style_ = true;\n      return true;\n    }\n    has_css_style_ = false;\n    return false;\n  } else {\n    has_css_style_ = !css().empty();\n    return !css().empty();\n  }\n}\n\nCSSParseToken* CSSFragmentDecorator::GetCSSStyle(const std::string& key) {\n  if (intrinsic_style_sheets_ &&\n      intrinsic_style_sheets_->enable_css_selector()) {\n    for (auto& ext_css : external_css_) {\n      intrinsic_style_sheets_->rule_set()->AddToRuleSet(ext_css.first,\n                                                        ext_css.second);\n    }\n  }\n  if (!external_css_.empty()) {\n    auto it = external_css_.find(key);\n    if (it != external_css_.end()) {\n      return it->second.get();\n    }\n  }\n  if (intrinsic_style_sheets_) {\n    return intrinsic_style_sheets_->GetCSSStyle(key);\n  }\n  return nullptr;\n}\n\nCSSKeyframesToken* CSSFragmentDecorator::GetKeyframesRule(\n    const base::String& key) {\n  if (!intrinsic_style_sheets_) {\n    return nullptr;\n  }\n  return intrinsic_style_sheets_->GetKeyframesRule(key);\n}\n\nconst std::vector<std::shared_ptr<CSSFontFaceRule>>&\nCSSFragmentDecorator::GetFontFaceRule(const std::string& key) {\n  if (!intrinsic_style_sheets_) {\n    return GetDefaultFontFaceList();\n  }\n  return intrinsic_style_sheets_->GetFontFaceRule(key);\n}\n\nfml::RefPtr<CSSParseToken> CSSFragmentDecorator::GetSharedCSSStyle(\n    const std::string& key) {\n  if (intrinsic_style_sheets_ &&\n      intrinsic_style_sheets_->enable_css_selector()) {\n    for (auto& ext_css : external_css_) {\n      intrinsic_style_sheets_->rule_set()->AddToRuleSet(ext_css.first,\n                                                        ext_css.second);\n    }\n  }\n  if (!external_css_.empty()) {\n    auto it = external_css_.find(key);\n    if (it != external_css_.end()) {\n      return it->second;\n    }\n  }\n  if (intrinsic_style_sheets_) {\n    return intrinsic_style_sheets_->GetSharedCSSStyle(key);\n  }\n  return nullptr;\n}\n\nvoid CSSFragmentDecorator::AddExternalStyle(const std::string& key,\n                                            fml::RefPtr<CSSParseToken> value) {\n  // A new independent attribute map is needed for each component instance, as\n  // multiple external tokens may merge to become the new token.\n  if (external_css_.find(key) == external_css_.end()) {\n    external_css_[key] = std::move(value);\n    return;\n  }\n\n  auto& target = external_css_[key];\n  for (auto it : value->GetAttributes()) {\n    target->SetAttribute(it.first, it.second);\n  }\n  // Resolve raw_attributes and mark target token is already parsed.\n  target->GetAttributes();\n}\n\nbool CSSFragmentDecorator::HasPseudoNotStyle() {\n  if (intrinsic_style_sheets_) {\n    return intrinsic_style_sheets_->HasPseudoNotStyle();\n  }\n  return false;\n}\n\nvoid CSSFragmentDecorator::InitPseudoNotStyle() {\n  if (intrinsic_style_sheets_) {\n    intrinsic_style_sheets_->InitPseudoNotStyle();\n  }\n}\n\n#define GET_PARSER_TOKEN_STYLE(name)                         \\\n  CSSParseToken* CSSFragmentDecorator::Get##name##Style(     \\\n      const std::string& key) {                              \\\n    if (intrinsic_style_sheets_) {                           \\\n      return intrinsic_style_sheets_->Get##name##Style(key); \\\n    }                                                        \\\n    return nullptr;                                          \\\n  }\n\nGET_PARSER_TOKEN_STYLE(Pseudo)\nGET_PARSER_TOKEN_STYLE(Cascade)\nGET_PARSER_TOKEN_STYLE(Id)\nGET_PARSER_TOKEN_STYLE(Tag)\nGET_PARSER_TOKEN_STYLE(Universal)\n#undef GET_PARSER_TOKEN_STYLE\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_fragment_decorator.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_FRAGMENT_DECORATOR_H_\n#define CORE_RENDERER_CSS_CSS_FRAGMENT_DECORATOR_H_\n\n#include <memory>\n#include <string>\n#include <vector>\n\n#include \"core/renderer/css/css_fragment.h\"\n\nnamespace lynx {\nnamespace tasm {\n\n// A decorator that lives in each component and takes into account both\n// intra-component styles and external classes.\nclass CSSFragmentDecorator : public CSSFragment {\n public:\n  CSSFragmentDecorator(CSSFragment* intrinsic_style_sheets)\n      : intrinsic_style_sheets_(intrinsic_style_sheets) {\n    if (intrinsic_style_sheets) {\n      enable_css_lazy_import_ =\n          intrinsic_style_sheets->GetEnableCSSLazyImport();\n    }\n  }\n  ~CSSFragmentDecorator() override;\n\n  const CSSParserTokenMap& css() override;\n  const CSSParserTokenMap& pseudo_map() override;\n  const CSSParserTokenMap& child_pseudo_map() override;\n  const CSSParserTokenMap& cascade_map() override;\n  css::RuleSet* rule_set() override;\n  const PseudoNotStyle& pseudo_not_style() override;\n\n  bool HasCSSStyle() override;\n  CSSParseToken* GetCSSStyle(const std::string& key) override;\n  CSSParseToken* GetPseudoStyle(const std::string& key) override;\n  CSSParseToken* GetCascadeStyle(const std::string& key) override;\n  CSSParseToken* GetIdStyle(const std::string& key) override;\n  CSSParseToken* GetTagStyle(const std::string& key) override;\n  CSSParseToken* GetUniversalStyle(const std::string& key) override;\n\n  bool HasPseudoNotStyle() override;\n  void InitPseudoNotStyle() override;\n\n  fml::RefPtr<CSSParseToken> GetSharedCSSStyle(const std::string& key) override;\n  const CSSKeyframesTokenMap& GetKeyframesRuleMap() override;\n  const CSSFontFaceRuleMap& GetFontFaceRuleMap() override;\n  CSSKeyframesToken* GetKeyframesRule(const base::String& key) override;\n  const std::vector<std::shared_ptr<CSSFontFaceRule>>& GetFontFaceRule(\n      const std::string& key) override;\n\n  void AddExternalStyle(const std::string& key,\n                        fml::RefPtr<CSSParseToken> value);\n\n  inline bool enable_css_selector() override {\n    return intrinsic_style_sheets_ &&\n           intrinsic_style_sheets_->enable_css_selector();\n  }\n\n  inline bool enable_css_invalidation() override {\n    return intrinsic_style_sheets_ &&\n           intrinsic_style_sheets_->enable_css_invalidation();\n  }\n\n  void CollectInvalidationSetsForId(css::InvalidationLists& lists,\n                                    const std::string& id) override {\n    if (intrinsic_style_sheets_)\n      intrinsic_style_sheets_->CollectInvalidationSetsForId(lists, id);\n  }\n\n  void CollectInvalidationSetsForClass(css::InvalidationLists& lists,\n                                       const std::string& class_name) override {\n    if (intrinsic_style_sheets_)\n      intrinsic_style_sheets_->CollectInvalidationSetsForClass(lists,\n                                                               class_name);\n  }\n\n  void CollectInvalidationSetsForPseudoClass(\n      css::InvalidationLists& lists,\n      css::LynxCSSSelector::PseudoType pseudo) override {\n    if (intrinsic_style_sheets_)\n      intrinsic_style_sheets_->CollectInvalidationSetsForPseudoClass(lists,\n                                                                     pseudo);\n  }\n\n  bool IntrinsicStyleSheetHasTouchPseudoToken() {\n    if (intrinsic_style_sheets_) {\n      return intrinsic_style_sheets_->HasTouchPseudoToken();\n    }\n    return HasTouchPseudoToken();\n  }\n\n private:\n  CSSFragment* intrinsic_style_sheets_ = nullptr;\n  CSSParserTokenMap external_css_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_FRAGMENT_DECORATOR_H_\n"
  },
  {
    "path": "core/renderer/css/css_fragment_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/renderer/css/css_fragment.h\"\n\n#include \"core/renderer/css/shared_css_fragment.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nclass CSSFragmentTest : public ::testing::Test {\n protected:\n  CSSFragmentTest() {}\n  ~CSSFragmentTest() {}\n\n  void SetUp() override {}\n\n  void TearDown() override {}\n};\n\nTEST_F(CSSFragmentTest, TestGetFontFaceRule) {\n  SharedCSSFragment fragment(0, {}, {}, {}, {});\n\n  const auto& font_face_token_map = fragment.GetFontFaceRuleMap();\n  EXPECT_TRUE(font_face_token_map.empty());\n\n  const auto& font_face_rule = fragment.GetFontFaceRule(\"test\");\n  EXPECT_TRUE(font_face_rule.empty());\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_keyframes_token.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_keyframes_token.h\"\n\nnamespace lynx {\nnamespace tasm {}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_keyframes_token.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_KEYFRAMES_TOKEN_H_\n#define CORE_RENDERER_CSS_CSS_KEYFRAMES_TOKEN_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/css_parser_token.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/template_bundle/template_codec/compile_options.h\"\n\nnamespace lynx {\n\nnamespace starlight {\nclass CSSStyleUtils;\n}  // namespace starlight\n\nnamespace tasm {\n\n// Used by encoder\ntypedef std::unordered_map<std::string, std::shared_ptr<StyleMap>>\n    CSSKeyframesMap;\ntypedef std::unordered_map<std::string, std::shared_ptr<RawStyleMap>>\n    CSSRawKeyframesMap;\n\ntypedef base::LinearFlatMap<float, std::shared_ptr<StyleMap>>\n    CSSKeyframesContent;\ntypedef base::LinearFlatMap<float, std::shared_ptr<RawStyleMap>>\n    CSSRawKeyframesContent;\n\nclass CSSKeyframesToken : public fml::RefCountedThreadSafeStorage {\n public:\n  CSSKeyframesToken(const CSSParserConfigs& parser_configs)\n      : parser_configs_(parser_configs) {}\n\n  virtual ~CSSKeyframesToken() = default;\n\n  void ReleaseSelf() const override { delete this; }\n\n  void SetKeyframesContent(CSSKeyframesContent&& content) {\n    content_ = std::move(content);\n  }\n  void SetRawKeyframesContent(CSSRawKeyframesContent&& content) {\n    raw_content_ = std::move(content);\n  }\n\n  static float ParseKeyStr(const std::string& key_text,\n                           bool enableCSSStrictMode = false) {\n    float key = 0;\n    if (key_text == \"from\") {\n      key = 0;\n    } else if (key_text == \"to\") {\n      key = 1;\n    } else {\n      key = atof(key_text.c_str()) / 100.0;\n    }\n    if (key > 1 || key < 0) {\n      UnitHandler::CSSWarning(false, enableCSSStrictMode,\n                              \"key frames must >=0 && <=0. error input:%s\",\n                              key_text.c_str());\n      return 0;\n    }\n    return key;\n  }\n\n  CSSKeyframesContent& GetKeyframesContent() {\n    if (!raw_content_.empty()) {\n      for (auto keyframe = raw_content_.begin(); keyframe != raw_content_.end();\n           keyframe++) {\n        auto key = keyframe->first;\n        StyleMap* temp_map = content_[key].get();\n        if (temp_map == nullptr) {\n          continue;\n        }\n        auto& raw_style_map = keyframe->second;\n        for (auto style : *raw_style_map) {\n          UnitHandler::ProcessCSSValue(style.first, style.second, *temp_map,\n                                       parser_configs_);\n        }\n      }\n      raw_content_.clear();\n    }\n    return content_;\n  }\n\n protected:\n  // for decode css.\n  friend class LynxBinaryBaseCSSReader;\n\n  CSSKeyframesContent content_;\n  CSSRawKeyframesContent raw_content_;\n\n  const CSSParserConfigs parser_configs_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_KEYFRAMES_TOKEN_H_\n"
  },
  {
    "path": "core/renderer/css/css_keyframes_token_unittest.cc",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#define private public\n#define protected public\n\n#include \"core/renderer/css/css_keyframes_token.h\"\n\n#include <cmath>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nbool NearlyEqual(float a, float b, float epsilon = 1e-5) {\n  return std::fabs(a - b) < epsilon;\n}\n\nTEST(CSSKeyframesToken, ParseKeyStr0) {\n  std::string input = \"from\";\n\n  EXPECT_EQ(CSSKeyframesToken::ParseKeyStr(input), 0);\n}\n\nTEST(CSSKeyframesToken, ParseKeyStr1) {\n  std::string input = \"to\";\n\n  EXPECT_EQ(CSSKeyframesToken::ParseKeyStr(input), 1);\n}\n\nTEST(CSSKeyframesToken, ParseKeyStr2) {\n  std::string input = \"99\";\n\n  EXPECT_TRUE(NearlyEqual(CSSKeyframesToken::ParseKeyStr(input), 0.99));\n}\n\nTEST(CSSKeyframesToken, ParseKeyStr3) {\n  std::string input = \"-1\";\n\n  EXPECT_EQ(CSSKeyframesToken::ParseKeyStr(input), 0);\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_keywords.cc",
    "content": "/* C++ code produced by gperf version 3.3 */\n/* Command-line: gperf -D -t --output-file css_keywords.cc css_keywords.tmpl  */\n/* Computed positions: -k'1-4,6-8,12-13,$' */\n\n#if !(                                                                         \\\n    (' ' == 32) && ('!' == 33) && ('\"' == 34) && ('#' == 35) && ('%' == 37) && \\\n    ('&' == 38) && ('\\'' == 39) && ('(' == 40) && (')' == 41) &&               \\\n    ('*' == 42) && ('+' == 43) && (',' == 44) && ('-' == 45) && ('.' == 46) && \\\n    ('/' == 47) && ('0' == 48) && ('1' == 49) && ('2' == 50) && ('3' == 51) && \\\n    ('4' == 52) && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) && \\\n    ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) && ('=' == 61) && \\\n    ('>' == 62) && ('?' == 63) && ('A' == 65) && ('B' == 66) && ('C' == 67) && \\\n    ('D' == 68) && ('E' == 69) && ('F' == 70) && ('G' == 71) && ('H' == 72) && \\\n    ('I' == 73) && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) && \\\n    ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) && ('R' == 82) && \\\n    ('S' == 83) && ('T' == 84) && ('U' == 85) && ('V' == 86) && ('W' == 87) && \\\n    ('X' == 88) && ('Y' == 89) && ('Z' == 90) && ('[' == 91) &&                \\\n    ('\\\\' == 92) && (']' == 93) && ('^' == 94) && ('_' == 95) &&               \\\n    ('a' == 97) && ('b' == 98) && ('c' == 99) && ('d' == 100) &&               \\\n    ('e' == 101) && ('f' == 102) && ('g' == 103) && ('h' == 104) &&            \\\n    ('i' == 105) && ('j' == 106) && ('k' == 107) && ('l' == 108) &&            \\\n    ('m' == 109) && ('n' == 110) && ('o' == 111) && ('p' == 112) &&            \\\n    ('q' == 113) && ('r' == 114) && ('s' == 115) && ('t' == 116) &&            \\\n    ('u' == 117) && ('v' == 118) && ('w' == 119) && ('x' == 120) &&            \\\n    ('y' == 121) && ('z' == 122) && ('{' == 123) && ('|' == 124) &&            \\\n    ('}' == 125) && ('~' == 126))\n/* The character set is not based on ISO-646.  */\n#error \\\n    \"gperf generated tables don't work with this execution character set. Please report a bug to <bug-gperf@gnu.org>.\"\n#endif\n\n#line 7 \"css_keywords.tmpl\"\n\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/css_keywords.h\"\n\n#include <cstring>\n\n#define size_t unsigned\n// NOLINTBEGIN(modernize-use-nullptr)\nnamespace lynx {\nnamespace tasm {\n#line 20 \"css_keywords.tmpl\"\nstruct TokenValue;\n/* maximum key range = 1543, duplicates = 0 */\n\nclass CSSKeywordsHash {\n private:\n  static inline unsigned int hash(const char *str, size_t len);\n\n public:\n  static const struct TokenValue *GetTokenValue(const char *str, size_t len);\n};\n\ninline unsigned int CSSKeywordsHash::hash(const char *str, size_t len) {\n  static const unsigned short asso_values[] = {\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 215,  1545, 1545,\n      1545, 1545, 1545, 5,    1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 5,    120,  295,  15,   0,    475,  155,  90,   20,   1545, 310,\n      0,    45,   5,    0,    75,   220,  20,   55,   0,    110,  378,  405,\n      480,  285,  0,    1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545, 1545,\n      1545, 1545, 1545, 1545};\n  unsigned int hval = len;\n\n  switch (hval) {\n    default:\n      hval += asso_values[static_cast<unsigned char>(str[12])];\n#if (defined __cplusplus &&                                     \\\n     (__cplusplus >= 201703L ||                                 \\\n      (__cplusplus >= 201103L && defined __clang__ &&           \\\n       __clang_major__ + (__clang_minor__ >= 9) > 3))) ||       \\\n    (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && \\\n     ((defined __GNUC__ && __GNUC__ >= 10) ||                   \\\n      (defined __clang__ && __clang_major__ >= 9)))\n      [[fallthrough]];\n#elif (defined __GNUC__ && __GNUC__ >= 7) || \\\n    (defined __clang__ && __clang_major__ >= 10)\n      __attribute__((__fallthrough__));\n#endif\n    /*FALLTHROUGH*/\n    case 12:\n      hval += asso_values[static_cast<unsigned char>(str[11])];\n#if (defined __cplusplus &&                                     \\\n     (__cplusplus >= 201703L ||                                 \\\n      (__cplusplus >= 201103L && defined __clang__ &&           \\\n       __clang_major__ + (__clang_minor__ >= 9) > 3))) ||       \\\n    (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && \\\n     ((defined __GNUC__ && __GNUC__ >= 10) ||                   \\\n      (defined __clang__ && __clang_major__ >= 9)))\n      [[fallthrough]];\n#elif (defined __GNUC__ && __GNUC__ >= 7) || \\\n    (defined __clang__ && __clang_major__ >= 10)\n      __attribute__((__fallthrough__));\n#endif\n    /*FALLTHROUGH*/\n    case 11:\n    case 10:\n    case 9:\n    case 8:\n      hval += asso_values[static_cast<unsigned char>(str[7])];\n#if (defined __cplusplus &&                                     \\\n     (__cplusplus >= 201703L ||                                 \\\n      (__cplusplus >= 201103L && defined __clang__ &&           \\\n       __clang_major__ + (__clang_minor__ >= 9) > 3))) ||       \\\n    (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && \\\n     ((defined __GNUC__ && __GNUC__ >= 10) ||                   \\\n      (defined __clang__ && __clang_major__ >= 9)))\n      [[fallthrough]];\n#elif (defined __GNUC__ && __GNUC__ >= 7) || \\\n    (defined __clang__ && __clang_major__ >= 10)\n      __attribute__((__fallthrough__));\n#endif\n    /*FALLTHROUGH*/\n    case 7:\n      hval += asso_values[static_cast<unsigned char>(str[6])];\n#if (defined __cplusplus &&                                     \\\n     (__cplusplus >= 201703L ||                                 \\\n      (__cplusplus >= 201103L && defined __clang__ &&           \\\n       __clang_major__ + (__clang_minor__ >= 9) > 3))) ||       \\\n    (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && \\\n     ((defined __GNUC__ && __GNUC__ >= 10) ||                   \\\n      (defined __clang__ && __clang_major__ >= 9)))\n      [[fallthrough]];\n#elif (defined __GNUC__ && __GNUC__ >= 7) || \\\n    (defined __clang__ && __clang_major__ >= 10)\n      __attribute__((__fallthrough__));\n#endif\n    /*FALLTHROUGH*/\n    case 6:\n      hval += asso_values[static_cast<unsigned char>(str[5])];\n#if (defined __cplusplus &&                                     \\\n     (__cplusplus >= 201703L ||                                 \\\n      (__cplusplus >= 201103L && defined __clang__ &&           \\\n       __clang_major__ + (__clang_minor__ >= 9) > 3))) ||       \\\n    (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && \\\n     ((defined __GNUC__ && __GNUC__ >= 10) ||                   \\\n      (defined __clang__ && __clang_major__ >= 9)))\n      [[fallthrough]];\n#elif (defined __GNUC__ && __GNUC__ >= 7) || \\\n    (defined __clang__ && __clang_major__ >= 10)\n      __attribute__((__fallthrough__));\n#endif\n    /*FALLTHROUGH*/\n    case 5:\n    case 4:\n      hval += asso_values[static_cast<unsigned char>(str[3])];\n#if (defined __cplusplus &&                                     \\\n     (__cplusplus >= 201703L ||                                 \\\n      (__cplusplus >= 201103L && defined __clang__ &&           \\\n       __clang_major__ + (__clang_minor__ >= 9) > 3))) ||       \\\n    (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && \\\n     ((defined __GNUC__ && __GNUC__ >= 10) ||                   \\\n      (defined __clang__ && __clang_major__ >= 9)))\n      [[fallthrough]];\n#elif (defined __GNUC__ && __GNUC__ >= 7) || \\\n    (defined __clang__ && __clang_major__ >= 10)\n      __attribute__((__fallthrough__));\n#endif\n    /*FALLTHROUGH*/\n    case 3:\n      hval += asso_values[static_cast<unsigned char>(str[2])];\n#if (defined __cplusplus &&                                     \\\n     (__cplusplus >= 201703L ||                                 \\\n      (__cplusplus >= 201103L && defined __clang__ &&           \\\n       __clang_major__ + (__clang_minor__ >= 9) > 3))) ||       \\\n    (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && \\\n     ((defined __GNUC__ && __GNUC__ >= 10) ||                   \\\n      (defined __clang__ && __clang_major__ >= 9)))\n      [[fallthrough]];\n#elif (defined __GNUC__ && __GNUC__ >= 7) || \\\n    (defined __clang__ && __clang_major__ >= 10)\n      __attribute__((__fallthrough__));\n#endif\n    /*FALLTHROUGH*/\n    case 2:\n      hval += asso_values[static_cast<unsigned char>(str[1])];\n#if (defined __cplusplus &&                                     \\\n     (__cplusplus >= 201703L ||                                 \\\n      (__cplusplus >= 201103L && defined __clang__ &&           \\\n       __clang_major__ + (__clang_minor__ >= 9) > 3))) ||       \\\n    (defined __STDC_VERSION__ && __STDC_VERSION__ >= 202000L && \\\n     ((defined __GNUC__ && __GNUC__ >= 10) ||                   \\\n      (defined __clang__ && __clang_major__ >= 9)))\n      [[fallthrough]];\n#elif (defined __GNUC__ && __GNUC__ >= 7) || \\\n    (defined __clang__ && __clang_major__ >= 10)\n      __attribute__((__fallthrough__));\n#endif\n    /*FALLTHROUGH*/\n    case 1:\n      hval += asso_values[static_cast<unsigned char>(str[0])];\n      break;\n  }\n  return hval + asso_values[static_cast<unsigned char>(str[len - 1])];\n}\n\nconst struct TokenValue *CSSKeywordsHash::GetTokenValue(const char *str,\n                                                        size_t len) {\n  enum {\n    TOTAL_KEYWORDS = 318,\n    MIN_WORD_LENGTH = 1,\n    MAX_WORD_LENGTH = 20,\n    MIN_HASH_VALUE = 2,\n    MAX_HASH_VALUE = 1544\n  };\n\n#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 6) > 4) || \\\n    (defined __clang__ && __clang_major__ >= 3)\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wmissing-field-initializers\"\n#endif\n  static const struct TokenValue token_list[] = {\n#line 28 \"css_keywords.tmpl\"\n      {\"to\", TokenType::TO},\n#line 93 \"css_keywords.tmpl\"\n      {\"toleft\", TokenType::TOLEFT},\n#line 71 \"css_keywords.tmpl\"\n      {\"at\", TokenType::AT},\n#line 281 \"css_keywords.tmpl\"\n      {\"all\", TokenType::ALL},\n#line 246 \"css_keywords.tmpl\"\n      {\"teal\", TokenType::TEAL},\n#line 337 \"css_keywords.tmpl\"\n      {\"on\", TokenType::ON},\n#line 27 \"css_keywords.tmpl\"\n      {\"none\", TokenType::NONE},\n#line 245 \"css_keywords.tmpl\"\n      {\"tan\", TokenType::TAN},\n#line 326 \"css_keywords.tmpl\"\n      {\"alternate\", TokenType::ALTERNATE},\n#line 256 \"css_keywords.tmpl\"\n      {\"rotate\", TokenType::ROTATE},\n#line 259 \"css_keywords.tmpl\"\n      {\"rotatez\", TokenType::ROTATE_Z},\n#line 72 \"css_keywords.tmpl\"\n      {\"data\", TokenType::DATA},\n#line 193 \"css_keywords.tmpl\"\n      {\"linen\", TokenType::LINEN},\n#line 214 \"css_keywords.tmpl\"\n      {\"orange\", TokenType::ORANGE},\n#line 260 \"css_keywords.tmpl\"\n      {\"translate\", TokenType::TRANSLATE},\n#line 264 \"css_keywords.tmpl\"\n      {\"translatez\", TokenType::TRANSLATE_Z},\n#line 77 \"css_keywords.tmpl\"\n      {\"dotted\", TokenType::DOTTED},\n#line 228 \"css_keywords.tmpl\"\n      {\"red\", TokenType::RED},\n#line 248 \"css_keywords.tmpl\"\n      {\"tomato\", TokenType::TOMATO},\n#line 45 \"css_keywords.tmpl\"\n      {\"rad\", TokenType::RAD},\n#line 261 \"css_keywords.tmpl\"\n      {\"translate3d\", TokenType::TRANSLATE_3D},\n#line 316 \"css_keywords.tmpl\"\n      {\"ease\", TokenType::EASE},\n#line 169 \"css_keywords.tmpl\"\n      {\"indigo\", TokenType::INDIGO},\n#line 191 \"css_keywords.tmpl\"\n      {\"lime\", TokenType::LIME},\n#line 312 \"css_keywords.tmpl\"\n      {\"linear\", TokenType::LINEAR},\n#line 215 \"css_keywords.tmpl\"\n      {\"orangered\", TokenType::ORANGERED},\n#line 90 \"css_keywords.tmpl\"\n      {\"normal\", TokenType::NORMAL},\n#line 95 \"css_keywords.tmpl\"\n      {\"totop\", TokenType::TOTOP},\n#line 68 \"css_keywords.tmpl\"\n      {\"ellipse\", TokenType::ELLIPSE},\n#line 83 \"css_keywords.tmpl\"\n      {\"inset\", TokenType::INSET},\n#line 195 \"css_keywords.tmpl\"\n      {\"maroon\", TokenType::MAROON},\n#line 37 \"css_keywords.tmpl\"\n      {\"em\", TokenType::EM},\n#line 79 \"css_keywords.tmpl\"\n      {\"solid\", TokenType::SOLID},\n#line 236 \"css_keywords.tmpl\"\n      {\"sienna\", TokenType::SIENNA},\n#line 313 \"css_keywords.tmpl\"\n      {\"ease-in\", TokenType::EASE_IN},\n#line 192 \"css_keywords.tmpl\"\n      {\"limegreen\", TokenType::LIMEGREEN},\n#line 52 \"css_keywords.tmpl\"\n      {\"repeat\", TokenType::REPEAT},\n#line 168 \"css_keywords.tmpl\"\n      {\"indianred\", TokenType::INDIANRED},\n#line 324 \"css_keywords.tmpl\"\n      {\"s\", TokenType::SECOND},\n#line 36 \"css_keywords.tmpl\"\n      {\"rem\", TokenType::REM},\n#line 218 \"css_keywords.tmpl\"\n      {\"palegreen\", TokenType::PALEGREEN},\n#line 47 \"css_keywords.tmpl\"\n      {\"auto\", TokenType::AUTO},\n#line 232 \"css_keywords.tmpl\"\n      {\"salmon\", TokenType::SALMON},\n#line 235 \"css_keywords.tmpl\"\n      {\"seashell\", TokenType::SEASHELL},\n#line 73 \"css_keywords.tmpl\"\n      {\"thin\", TokenType::THIN},\n#line 26 \"css_keywords.tmpl\"\n      {\"url\", TokenType::URL},\n#line 334 \"css_keywords.tmpl\"\n      {\"true\", TokenType::TOKEN_TRUE},\n#line 94 \"css_keywords.tmpl\"\n      {\"toright\", TokenType::TORIGHT},\n#line 217 \"css_keywords.tmpl\"\n      {\"palegoldenrod\", TokenType::PALEGOLDENROD},\n#line 113 \"css_keywords.tmpl\"\n      {\"azure\", TokenType::AZURE},\n#line 108 \"css_keywords.tmpl\"\n      {\"transparent\", TokenType::TRANSPARENT},\n#line 220 \"css_keywords.tmpl\"\n      {\"palevioletred\", TokenType::PALEVIOLETRED},\n#line 46 \"css_keywords.tmpl\"\n      {\"turn\", TokenType::TURN},\n#line 24 \"css_keywords.tmpl\"\n      {\"hsl\", TokenType::HSL},\n#line 206 \"css_keywords.tmpl\"\n      {\"mintcream\", TokenType::MINTCREAM},\n#line 30 \"css_keywords.tmpl\"\n      {\"top\", TokenType::TOP},\n#line 91 \"css_keywords.tmpl\"\n      {\"bold\", TokenType::BOLD},\n#line 55 \"css_keywords.tmpl\"\n      {\"round\", TokenType::ROUND},\n#line 76 \"css_keywords.tmpl\"\n      {\"hidden\", TokenType::HIDDEN},\n#line 323 \"css_keywords.tmpl\"\n      {\"ms\", TokenType::MILLISECOND},\n#line 25 \"css_keywords.tmpl\"\n      {\"hsla\", TokenType::HSLA},\n#line 85 \"css_keywords.tmpl\"\n      {\"underline\", TokenType::UNDERLINE},\n#line 84 \"css_keywords.tmpl\"\n      {\"outset\", TokenType::OUTSET},\n#line 247 \"css_keywords.tmpl\"\n      {\"thistle\", TokenType::THISTLE},\n#line 319 \"css_keywords.tmpl\"\n      {\"step-end\", TokenType::STEP_END},\n#line 74 \"css_keywords.tmpl\"\n      {\"medium\", TokenType::MEDIUM},\n#line 314 \"css_keywords.tmpl\"\n      {\"ease-out\", TokenType::EASE_OUT},\n#line 81 \"css_keywords.tmpl\"\n      {\"groove\", TokenType::GROOVE},\n#line 103 \"css_keywords.tmpl\"\n      {\"saturate\", TokenType::SATURATE},\n#line 163 \"css_keywords.tmpl\"\n      {\"green\", TokenType::GREEN},\n#line 198 \"css_keywords.tmpl\"\n      {\"mediumorchid\", TokenType::MEDIUMORCHID},\n#line 160 \"css_keywords.tmpl\"\n      {\"gold\", TokenType::GOLD},\n#line 322 \"css_keywords.tmpl\"\n      {\"steps\", TokenType::STEPS},\n#line 200 \"css_keywords.tmpl\"\n      {\"mediumseagreen\", TokenType::MEDIUMSEAGREEN},\n#line 318 \"css_keywords.tmpl\"\n      {\"step-start\", TokenType::STEP_START},\n#line 78 \"css_keywords.tmpl\"\n      {\"dashed\", TokenType::DASHED},\n#line 207 \"css_keywords.tmpl\"\n      {\"mistyrose\", TokenType::MISTYROSE},\n#line 40 \"css_keywords.tmpl\"\n      {\"sp\", TokenType::SP},\n#line 44 \"css_keywords.tmpl\"\n      {\"grad\", TokenType::GRAD},\n#line 82 \"css_keywords.tmpl\"\n      {\"ridge\", TokenType::RIDGE},\n#line 32 \"css_keywords.tmpl\"\n      {\"bottom\", TokenType::BOTTOM},\n#line 92 \"css_keywords.tmpl\"\n      {\"tobottom\", TokenType::TOBOTTOM},\n#line 161 \"css_keywords.tmpl\"\n      {\"goldenrod\", TokenType::GOLDENROD},\n#line 194 \"css_keywords.tmpl\"\n      {\"magenta\", TokenType::MAGENTA},\n#line 234 \"css_keywords.tmpl\"\n      {\"seagreen\", TokenType::SEAGREEN},\n#line 118 \"css_keywords.tmpl\"\n      {\"blue\", TokenType::BLUE},\n#line 233 \"css_keywords.tmpl\"\n      {\"sandybrown\", TokenType::SANDYBROWN},\n#line 306 \"css_keywords.tmpl\"\n      {\"margin\", TokenType::MARGIN},\n#line 231 \"css_keywords.tmpl\"\n      {\"saddlebrown\", TokenType::SADDLEBROWN},\n#line 80 \"css_keywords.tmpl\"\n      {\"double\", TokenType::DOUBLE},\n#line 197 \"css_keywords.tmpl\"\n      {\"mediumblue\", TokenType::MEDIUMBLUE},\n#line 119 \"css_keywords.tmpl\"\n      {\"blueviolet\", TokenType::BLUEVIOLET},\n#line 96 \"css_keywords.tmpl\"\n      {\"path\", TokenType::PATH},\n#line 117 \"css_keywords.tmpl\"\n      {\"blanchedalmond\", TokenType::BLANCHEDALMOND},\n#line 203 \"css_keywords.tmpl\"\n      {\"mediumturquoise\", TokenType::MEDIUMTURQUOISE},\n#line 276 \"css_keywords.tmpl\"\n      {\"height\", TokenType::HEIGHT},\n#line 106 \"css_keywords.tmpl\"\n      {\"blur\", TokenType::BLUR},\n#line 225 \"css_keywords.tmpl\"\n      {\"plum\", TokenType::PLUM},\n#line 332 \"css_keywords.tmpl\"\n      {\"paused\", TokenType::PAUSED},\n#line 227 \"css_keywords.tmpl\"\n      {\"purple\", TokenType::PURPLE},\n#line 31 \"css_keywords.tmpl\"\n      {\"right\", TokenType::RIGHT},\n#line 244 \"css_keywords.tmpl\"\n      {\"steelblue\", TokenType::STEELBLUE},\n#line 239 \"css_keywords.tmpl\"\n      {\"slateblue\", TokenType::SLATEBLUE},\n#line 114 \"css_keywords.tmpl\"\n      {\"beige\", TokenType::BEIGE},\n#line 330 \"css_keywords.tmpl\"\n      {\"both\", TokenType::BOTH},\n#line 89 \"css_keywords.tmpl\"\n      {\"local\", TokenType::LOCAL},\n#line 23 \"css_keywords.tmpl\"\n      {\"rgba\", TokenType::RGBA},\n#line 317 \"css_keywords.tmpl\"\n      {\"ease-in-out\", TokenType::EASE_IN_OUT},\n#line 201 \"css_keywords.tmpl\"\n      {\"mediumslateblue\", TokenType::MEDIUMSLATEBLUE},\n#line 211 \"css_keywords.tmpl\"\n      {\"oldlace\", TokenType::OLDLACE},\n#line 223 \"css_keywords.tmpl\"\n      {\"peru\", TokenType::PERU},\n#line 278 \"css_keywords.tmpl\"\n      {\"color\", TokenType::COLOR},\n#line 199 \"css_keywords.tmpl\"\n      {\"mediumpurple\", TokenType::MEDIUMPURPLE},\n#line 125 \"css_keywords.tmpl\"\n      {\"coral\", TokenType::CORAL},\n#line 86 \"css_keywords.tmpl\"\n      {\"line-through\", TokenType::LINE_THROUGH},\n#line 43 \"css_keywords.tmpl\"\n      {\"deg\", TokenType::DEG},\n#line 53 \"css_keywords.tmpl\"\n      {\"no-repeat\", TokenType::NO_REPEAT},\n#line 263 \"css_keywords.tmpl\"\n      {\"translatey\", TokenType::TRANSLATE_Y},\n#line 158 \"css_keywords.tmpl\"\n      {\"gainsboro\", TokenType::GAINSBORO},\n#line 153 \"css_keywords.tmpl\"\n      {\"dodgerblue\", TokenType::DODGERBLUE},\n#line 49 \"css_keywords.tmpl\"\n      {\"contain\", TokenType::CONTAIN},\n#line 184 \"css_keywords.tmpl\"\n      {\"lightsalmon\", TokenType::LIGHTSALMON},\n#line 205 \"css_keywords.tmpl\"\n      {\"midnightblue\", TokenType::MIDNIGHTBLUE},\n#line 33 \"css_keywords.tmpl\"\n      {\"center\", TokenType::CENTER},\n#line 185 \"css_keywords.tmpl\"\n      {\"lightseagreen\", TokenType::LIGHTSEAGREEN},\n#line 111 \"css_keywords.tmpl\"\n      {\"aqua\", TokenType::AQUA},\n#line 265 \"css_keywords.tmpl\"\n      {\"scale\", TokenType::SCALE},\n#line 102 \"css_keywords.tmpl\"\n      {\"contrast\", TokenType::CONTRAST},\n#line 70 \"css_keywords.tmpl\"\n      {\"polygon\", TokenType::POLYGON},\n#line 148 \"css_keywords.tmpl\"\n      {\"darkviolet\", TokenType::DARKVIOLET},\n#line 134 \"css_keywords.tmpl\"\n      {\"darkgreen\", TokenType::DARKGREEN},\n#line 101 \"css_keywords.tmpl\"\n      {\"brightness\", TokenType::BRIGHTNESS},\n#line 141 \"css_keywords.tmpl\"\n      {\"darkred\", TokenType::DARKRED},\n#line 139 \"css_keywords.tmpl\"\n      {\"darkorange\", TokenType::DARKORANGE},\n#line 196 \"css_keywords.tmpl\"\n      {\"mediumaquamarine\", TokenType::MEDIUMAQUAMARINE},\n#line 112 \"css_keywords.tmpl\"\n      {\"aquamarine\", TokenType::AQUAMARINE},\n#line 128 \"css_keywords.tmpl\"\n      {\"crimson\", TokenType::CRIMSON},\n#line 212 \"css_keywords.tmpl\"\n      {\"olive\", TokenType::OLIVE},\n#line 250 \"css_keywords.tmpl\"\n      {\"violet\", TokenType::VIOLET},\n#line 132 \"css_keywords.tmpl\"\n      {\"darkgoldenrod\", TokenType::DARKGOLDENROD},\n#line 327 \"css_keywords.tmpl\"\n      {\"alternate-reverse\", TokenType::ALTERNATE_REVERSE},\n#line 60 \"css_keywords.tmpl\"\n      {\"border-area\", TokenType::BORDER_AREA},\n#line 142 \"css_keywords.tmpl\"\n      {\"darksalmon\", TokenType::DARKSALMON},\n#line 22 \"css_keywords.tmpl\"\n      {\"rgb\", TokenType::RGB},\n#line 284 \"css_keywords.tmpl\"\n      {\"min-width\", TokenType::MIN_WIDTH},\n#line 115 \"css_keywords.tmpl\"\n      {\"bisque\", TokenType::BISQUE},\n#line 105 \"css_keywords.tmpl\"\n      {\"var\", TokenType::VAR},\n#line 104 \"css_keywords.tmpl\"\n      {\"hue-rotate\", TokenType::HUE_ROTATE},\n#line 307 \"css_keywords.tmpl\"\n      {\"padding\", TokenType::PADDING},\n#line 249 \"css_keywords.tmpl\"\n      {\"turquoise\", TokenType::TURQUOISE},\n#line 54 \"css_keywords.tmpl\"\n      {\"space\", TokenType::SPACE},\n#line 216 \"css_keywords.tmpl\"\n      {\"orchid\", TokenType::ORCHID},\n#line 189 \"css_keywords.tmpl\"\n      {\"lightsteelblue\", TokenType::LIGHTSTEELBLUE},\n#line 172 \"css_keywords.tmpl\"\n      {\"lavender\", TokenType::LAVENDER},\n#line 174 \"css_keywords.tmpl\"\n      {\"lawngreen\", TokenType::LAWNGREEN},\n#line 61 \"css_keywords.tmpl\"\n      {\"linear-gradient\", TokenType::LINEAR_GRADIENT},\n#line 181 \"css_keywords.tmpl\"\n      {\"lightgreen\", TokenType::LIGHTGREEN},\n#line 294 \"css_keywords.tmpl\"\n      {\"margin-left\", TokenType::MARGIN_LEFT},\n#line 325 \"css_keywords.tmpl\"\n      {\"reverse\", TokenType::REVERSE},\n#line 333 \"css_keywords.tmpl\"\n      {\"running\", TokenType::RUNNING},\n#line 62 \"css_keywords.tmpl\"\n      {\"radial-gradient\", TokenType::RADIAL_GRADIENT},\n#line 130 \"css_keywords.tmpl\"\n      {\"darkblue\", TokenType::DARKBLUE},\n#line 285 \"css_keywords.tmpl\"\n      {\"min-height\", TokenType::MIN_HEIGHT},\n#line 175 \"css_keywords.tmpl\"\n      {\"lemonchiffon\", TokenType::LEMONCHIFFON},\n#line 295 \"css_keywords.tmpl\"\n      {\"margin-right\", TokenType::MARGIN_RIGHT},\n#line 144 \"css_keywords.tmpl\"\n      {\"darkslateblue\", TokenType::DARKSLATEBLUE},\n#line 29 \"css_keywords.tmpl\"\n      {\"left\", TokenType::LEFT},\n#line 59 \"css_keywords.tmpl\"\n      {\"text\", TokenType::TEXT},\n#line 298 \"css_keywords.tmpl\"\n      {\"padding-left\", TokenType::PADDING_LEFT},\n#line 219 \"css_keywords.tmpl\"\n      {\"paleturquoise\", TokenType::PALETURQUOISE},\n#line 237 \"css_keywords.tmpl\"\n      {\"silver\", TokenType::SILVER},\n#line 176 \"css_keywords.tmpl\"\n      {\"lightblue\", TokenType::LIGHTBLUE},\n#line 251 \"css_keywords.tmpl\"\n      {\"wheat\", TokenType::WHEAT},\n#line 243 \"css_keywords.tmpl\"\n      {\"springgreen\", TokenType::SPRINGGREEN},\n#line 336 \"css_keywords.tmpl\"\n      {\"fr\", TokenType::FR},\n#line 252 \"css_keywords.tmpl\"\n      {\"white\", TokenType::WHITE},\n#line 97 \"css_keywords.tmpl\"\n      {\"super-ellipse\", TokenType::SUPER_ELLIPSE},\n#line 262 \"css_keywords.tmpl\"\n      {\"translatex\", TokenType::TRANSLATE_X},\n#line 137 \"css_keywords.tmpl\"\n      {\"darkmagenta\", TokenType::DARKMAGENTA},\n#line 241 \"css_keywords.tmpl\"\n      {\"slategrey\", TokenType::SLATEGREY},\n#line 296 \"css_keywords.tmpl\"\n      {\"margin-top\", TokenType::MARGIN_TOP},\n#line 315 \"css_keywords.tmpl\"\n      {\"ease-in-ease-out\", TokenType::EASE_IN_EASE_OUT},\n#line 143 \"css_keywords.tmpl\"\n      {\"darkseagreen\", TokenType::DARKSEAGREEN},\n#line 240 \"css_keywords.tmpl\"\n      {\"slategray\", TokenType::SLATEGRAY},\n#line 275 \"css_keywords.tmpl\"\n      {\"width\", TokenType::WIDTH},\n#line 335 \"css_keywords.tmpl\"\n      {\"false\", TokenType::TOKEN_FALSE},\n#line 308 \"css_keywords.tmpl\"\n      {\"filter\", TokenType::FILTER},\n#line 301 \"css_keywords.tmpl\"\n      {\"padding-bottom\", TokenType::PADDING_BOTTOM},\n#line 88 \"css_keywords.tmpl\"\n      {\"format\", TokenType::FORMAT},\n#line 331 \"css_keywords.tmpl\"\n      {\"infinite\", TokenType::INFINITE},\n#line 230 \"css_keywords.tmpl\"\n      {\"royalblue\", TokenType::ROYALBLUE},\n#line 123 \"css_keywords.tmpl\"\n      {\"chartreuse\", TokenType::CHARTREUSE},\n#line 122 \"css_keywords.tmpl\"\n      {\"cadetblue\", TokenType::CADETBLUE},\n#line 120 \"css_keywords.tmpl\"\n      {\"brown\", TokenType::BROWN},\n#line 110 \"css_keywords.tmpl\"\n      {\"antiquewhite\", TokenType::ANTIQUEWHITE},\n#line 109 \"css_keywords.tmpl\"\n      {\"aliceblue\", TokenType::ALICEBLUE},\n#line 39 \"css_keywords.tmpl\"\n      {\"vh\", TokenType::VH},\n#line 213 \"css_keywords.tmpl\"\n      {\"olivedrab\", TokenType::OLIVEDRAB},\n#line 300 \"css_keywords.tmpl\"\n      {\"padding-top\", TokenType::PADDING_TOP},\n#line 204 \"css_keywords.tmpl\"\n      {\"mediumvioletred\", TokenType::MEDIUMVIOLETRED},\n#line 280 \"css_keywords.tmpl\"\n      {\"transform\", TokenType::TRANSFORM},\n#line 66 \"css_keywords.tmpl\"\n      {\"farthest-side\", TokenType::FARTHEST_SIDE},\n#line 310 \"css_keywords.tmpl\"\n      {\"transform-origin\", TokenType::TRANSFORM_ORIGIN},\n#line 202 \"css_keywords.tmpl\"\n      {\"mediumspringgreen\", TokenType::MEDIUMSPRINGGREEN},\n#line 299 \"css_keywords.tmpl\"\n      {\"padding-right\", TokenType::PADDING_RIGHT},\n#line 339 \"css_keywords.tmpl\"\n      {\"from\", TokenType::FROM},\n#line 177 \"css_keywords.tmpl\"\n      {\"lightcoral\", TokenType::LIGHTCORAL},\n#line 272 \"css_keywords.tmpl\"\n      {\"matrix3d\", TokenType::MATRIX_3D},\n#line 293 \"css_keywords.tmpl\"\n      {\"border-bottom-color\", TokenType::BORDER_BOTTOM_COLOR},\n#line 129 \"css_keywords.tmpl\"\n      {\"cyan\", TokenType::CYAN},\n#line 258 \"css_keywords.tmpl\"\n      {\"rotatey\", TokenType::ROTATE_Y},\n#line 67 \"css_keywords.tmpl\"\n      {\"farthest-corner\", TokenType::FARTHEST_CORNER},\n#line 253 \"css_keywords.tmpl\"\n      {\"whitesmoke\", TokenType::WHITESMOKE},\n#line 64 \"css_keywords.tmpl\"\n      {\"closest-side\", TokenType::CLOSEST_SIDE},\n#line 69 \"css_keywords.tmpl\"\n      {\"circle\", TokenType::CIRCLE},\n#line 188 \"css_keywords.tmpl\"\n      {\"lightslategrey\", TokenType::LIGHTSLATEGREY},\n#line 226 \"css_keywords.tmpl\"\n      {\"powderblue\", TokenType::POWDERBLUE},\n#line 187 \"css_keywords.tmpl\"\n      {\"lightslategray\", TokenType::LIGHTSLATEGRAY},\n#line 131 \"css_keywords.tmpl\"\n      {\"darkcyan\", TokenType::DARKCYAN},\n#line 65 \"css_keywords.tmpl\"\n      {\"closest-corner\", TokenType::CLOSEST_CORNER},\n#line 291 \"css_keywords.tmpl\"\n      {\"border-right-color\", TokenType::BORDER_RIGHT_COLOR},\n#line 289 \"css_keywords.tmpl\"\n      {\"border-bottom-width\", TokenType::BORDER_BOTTOM_WIDTH},\n#line 173 \"css_keywords.tmpl\"\n      {\"lavenderblush\", TokenType::LAVENDERBLUSH},\n#line 297 \"css_keywords.tmpl\"\n      {\"margin-bottom\", TokenType::MARGIN_BOTTOM},\n#line 121 \"css_keywords.tmpl\"\n      {\"burlywood\", TokenType::BURLYWOOD},\n#line 183 \"css_keywords.tmpl\"\n      {\"lightpink\", TokenType::LIGHTPINK},\n#line 156 \"css_keywords.tmpl\"\n      {\"forestgreen\", TokenType::FORESTGREEN},\n#line 124 \"css_keywords.tmpl\"\n      {\"chocolate\", TokenType::CHOCOLATE},\n#line 48 \"css_keywords.tmpl\"\n      {\"cover\", TokenType::COVER},\n#line 170 \"css_keywords.tmpl\"\n      {\"ivory\", TokenType::IVORY},\n#line 75 \"css_keywords.tmpl\"\n      {\"thick\", TokenType::THICK},\n#line 292 \"css_keywords.tmpl\"\n      {\"border-top-color\", TokenType::BORDER_TOP_COLOR},\n#line 224 \"css_keywords.tmpl\"\n      {\"pink\", TokenType::PINK},\n#line 107 \"css_keywords.tmpl\"\n      {\"fit-content\", TokenType::FIT_CONTENT},\n#line 208 \"css_keywords.tmpl\"\n      {\"moccasin\", TokenType::MOCCASIN},\n#line 63 \"css_keywords.tmpl\"\n      {\"conic-gradient\", TokenType::CONIC_GRADIENT},\n#line 287 \"css_keywords.tmpl\"\n      {\"border-right-width\", TokenType::BORDER_RIGHT_WIDTH},\n#line 182 \"css_keywords.tmpl\"\n      {\"lightgrey\", TokenType::LIGHTGREY},\n#line 116 \"css_keywords.tmpl\"\n      {\"black\", TokenType::BLACK},\n#line 305 \"css_keywords.tmpl\"\n      {\"border-color\", TokenType::BORDER_COLOR},\n#line 180 \"css_keywords.tmpl\"\n      {\"lightgray\", TokenType::LIGHTGRAY},\n#line 171 \"css_keywords.tmpl\"\n      {\"khaki\", TokenType::KHAKI},\n#line 149 \"css_keywords.tmpl\"\n      {\"deeppink\", TokenType::DEEPPINK},\n#line 221 \"css_keywords.tmpl\"\n      {\"papayawhip\", TokenType::PAPAYAWHIP},\n#line 165 \"css_keywords.tmpl\"\n      {\"grey\", TokenType::GREY},\n#line 162 \"css_keywords.tmpl\"\n      {\"gray\", TokenType::GRAY},\n#line 42 \"css_keywords.tmpl\"\n      {\"max-content\", TokenType::MAX_CONTENT},\n#line 99 \"css_keywords.tmpl\"\n      {\"env\", TokenType::ENV},\n#line 138 \"css_keywords.tmpl\"\n      {\"darkolivegreen\", TokenType::DARKOLIVEGREEN},\n#line 147 \"css_keywords.tmpl\"\n      {\"darkturquoise\", TokenType::DARKTURQUOISE},\n#line 100 \"css_keywords.tmpl\"\n      {\"grayscale\", TokenType::GRAYSCALE},\n#line 320 \"css_keywords.tmpl\"\n      {\"square-bezier\", TokenType::SQUARE_BEZIER},\n#line 140 \"css_keywords.tmpl\"\n      {\"darkorchid\", TokenType::DARKORCHID},\n#line 136 \"css_keywords.tmpl\"\n      {\"darkkhaki\", TokenType::DARKKHAKI},\n#line 167 \"css_keywords.tmpl\"\n      {\"hotpink\", TokenType::HOTPINK},\n#line 229 \"css_keywords.tmpl\"\n      {\"rosybrown\", TokenType::ROSYBROWN},\n#line 279 \"css_keywords.tmpl\"\n      {\"visibility\", TokenType::VISIBILITY},\n#line 152 \"css_keywords.tmpl\"\n      {\"dimgrey\", TokenType::DIMGREY},\n#line 150 \"css_keywords.tmpl\"\n      {\"deepskyblue\", TokenType::DEEPSKYBLUE},\n#line 151 \"css_keywords.tmpl\"\n      {\"dimgray\", TokenType::DIMGRAY},\n#line 159 \"css_keywords.tmpl\"\n      {\"ghostwhite\", TokenType::GHOSTWHITE},\n#line 329 \"css_keywords.tmpl\"\n      {\"backwards\", TokenType::BACKWARDS},\n#line 126 \"css_keywords.tmpl\"\n      {\"cornflowerblue\", TokenType::CORNFLOWERBLUE},\n#line 178 \"css_keywords.tmpl\"\n      {\"lightcyan\", TokenType::LIGHTCYAN},\n#line 179 \"css_keywords.tmpl\"\n      {\"lightgoldenrodyellow\", TokenType::LIGHTGOLDENRODYELLOW},\n#line 242 \"css_keywords.tmpl\"\n      {\"snow\", TokenType::SNOW},\n#line 164 \"css_keywords.tmpl\"\n      {\"greenyellow\", TokenType::GREENYELLOW},\n#line 282 \"css_keywords.tmpl\"\n      {\"max-width\", TokenType::MAX_WIDTH},\n#line 255 \"css_keywords.tmpl\"\n      {\"yellowgreen\", TokenType::YELLOWGREEN},\n#line 238 \"css_keywords.tmpl\"\n      {\"skyblue\", TokenType::SKYBLUE},\n#line 51 \"css_keywords.tmpl\"\n      {\"repeat-y\", TokenType::REPEAT_Y},\n#line 98 \"css_keywords.tmpl\"\n      {\"calc\", TokenType::CALC},\n#line 209 \"css_keywords.tmpl\"\n      {\"navajowhite\", TokenType::NAVAJOWHITE},\n#line 288 \"css_keywords.tmpl\"\n      {\"border-top-width\", TokenType::BORDER_TOP_WIDTH},\n#line 186 \"css_keywords.tmpl\"\n      {\"lightskyblue\", TokenType::LIGHTSKYBLUE},\n#line 166 \"css_keywords.tmpl\"\n      {\"honeydew\", TokenType::HONEYDEW},\n#line 283 \"css_keywords.tmpl\"\n      {\"max-height\", TokenType::MAX_HEIGHT},\n#line 267 \"css_keywords.tmpl\"\n      {\"scaley\", TokenType::SCALE_Y},\n#line 321 \"css_keywords.tmpl\"\n      {\"cubic-bezier\", TokenType::CUBIC_BEZIER},\n#line 290 \"css_keywords.tmpl\"\n      {\"border-left-color\", TokenType::BORDER_LEFT_COLOR},\n#line 146 \"css_keywords.tmpl\"\n      {\"darkslategrey\", TokenType::DARKSLATEGREY},\n#line 145 \"css_keywords.tmpl\"\n      {\"darkslategray\", TokenType::DARKSLATEGRAY},\n#line 135 \"css_keywords.tmpl\"\n      {\"darkgrey\", TokenType::DARKGREY},\n#line 273 \"css_keywords.tmpl\"\n      {\"opacity\", TokenType::OPACITY},\n#line 133 \"css_keywords.tmpl\"\n      {\"darkgray\", TokenType::DARKGRAY},\n#line 309 \"css_keywords.tmpl\"\n      {\"background-position\", TokenType::BACKGROUND_POSITION},\n#line 210 \"css_keywords.tmpl\"\n      {\"navy\", TokenType::NAVY},\n#line 190 \"css_keywords.tmpl\"\n      {\"lightyellow\", TokenType::LIGHTYELLOW},\n#line 127 \"css_keywords.tmpl\"\n      {\"cornsilk\", TokenType::CORNSILK},\n#line 57 \"css_keywords.tmpl\"\n      {\"padding-box\", TokenType::PADDING_BOX},\n#line 304 \"css_keywords.tmpl\"\n      {\"border-width\", TokenType::BORDER_WIDTH},\n#line 257 \"css_keywords.tmpl\"\n      {\"rotatex\", TokenType::ROTATE_X},\n#line 56 \"css_keywords.tmpl\"\n      {\"border-box\", TokenType::BORDER_BOX},\n#line 155 \"css_keywords.tmpl\"\n      {\"floralwhite\", TokenType::FLORALWHITE},\n#line 157 \"css_keywords.tmpl\"\n      {\"fuchsia\", TokenType::FUCHSIA},\n#line 58 \"css_keywords.tmpl\"\n      {\"content-box\", TokenType::CONTENT_BOX},\n#line 271 \"css_keywords.tmpl\"\n      {\"matrix\", TokenType::MATRIX},\n#line 34 \"css_keywords.tmpl\"\n      {\"px\", TokenType::PX},\n#line 328 \"css_keywords.tmpl\"\n      {\"forwards\", TokenType::FORWARDS},\n#line 35 \"css_keywords.tmpl\"\n      {\"rpx\", TokenType::RPX},\n#line 270 \"css_keywords.tmpl\"\n      {\"skewy\", TokenType::SKEW_Y},\n#line 254 \"css_keywords.tmpl\"\n      {\"yellow\", TokenType::YELLOW},\n#line 41 \"css_keywords.tmpl\"\n      {\"ppx\", TokenType::PPX},\n#line 286 \"css_keywords.tmpl\"\n      {\"border-left-width\", TokenType::BORDER_LEFT_WIDTH},\n#line 154 \"css_keywords.tmpl\"\n      {\"firebrick\", TokenType::FIREBRICK},\n#line 268 \"css_keywords.tmpl\"\n      {\"skew\", TokenType::SKEW},\n#line 38 \"css_keywords.tmpl\"\n      {\"vw\", TokenType::VW},\n#line 277 \"css_keywords.tmpl\"\n      {\"background-color\", TokenType::BACKGROUND_COLOR},\n#line 302 \"css_keywords.tmpl\"\n      {\"flex-basis\", TokenType::FLEX_BASIS},\n#line 269 \"css_keywords.tmpl\"\n      {\"skewx\", TokenType::SKEW_X},\n#line 311 \"css_keywords.tmpl\"\n      {\"offset-distance\", TokenType::OFFSET_DISTANCE},\n#line 50 \"css_keywords.tmpl\"\n      {\"repeat-x\", TokenType::REPEAT_X},\n#line 266 \"css_keywords.tmpl\"\n      {\"scalex\", TokenType::SCALE_X},\n#line 87 \"css_keywords.tmpl\"\n      {\"wavy\", TokenType::WAVY},\n#line 274 \"css_keywords.tmpl\"\n      {\"scalexy\", TokenType::SCALE_XY},\n#line 338 \"css_keywords.tmpl\"\n      {\"off\", TokenType::OFF},\n#line 222 \"css_keywords.tmpl\"\n      {\"peachpuff\", TokenType::PEACHPUFF},\n#line 303 \"css_keywords.tmpl\"\n      {\"flex-grow\", TokenType::FLEX_GROW}};\n#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 6) > 4) || \\\n    (defined __clang__ && __clang_major__ >= 3)\n#pragma GCC diagnostic pop\n#endif\n\n  static const short lookup[] = {\n      -1,  -1,  0,   -1,  -1,  -1,  1,   2,   3,   4,   -1,  -1,  5,   -1,\n      6,   -1,  -1,  -1,  7,   -1,  -1,  -1,  -1,  -1,  8,   -1,  -1,  -1,\n      -1,  -1,  -1,  9,   10,  -1,  11,  12,  13,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  14,  15,  -1,  -1,  -1,  -1,  -1,  16,  -1,  17,  -1,  -1,\n      18,  -1,  19,  -1,  -1,  20,  -1,  -1,  21,  -1,  22,  -1,  -1,  23,\n      -1,  24,  -1,  -1,  25,  -1,  26,  -1,  -1,  -1,  27,  -1,  28,  -1,\n      -1,  29,  30,  -1,  -1,  -1,  -1,  -1,  31,  -1,  -1,  32,  33,  34,\n      -1,  35,  -1,  36,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  37,  -1,  38,\n      -1,  39,  40,  -1,  -1,  -1,  -1,  41,  -1,  42,  -1,  43,  44,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  45,  46,  -1,  -1,  47,  48,  -1,\n      49,  50,  -1,  51,  52,  -1,  -1,  -1,  53,  54,  -1,  -1,  -1,  55,\n      56,  57,  58,  59,  -1,  60,  -1,  -1,  -1,  -1,  61,  -1,  -1,  -1,\n      -1,  -1,  -1,  62,  63,  64,  -1,  -1,  65,  -1,  66,  -1,  -1,  67,\n      -1,  68,  -1,  69,  -1,  70,  -1,  71,  72,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  73,  74,  75,  -1,  -1,  76,  -1,  -1,  77,  -1,  -1,\n      -1,  -1,  -1,  -1,  78,  79,  80,  -1,  81,  82,  -1,  -1,  83,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  84,  85,  86,  -1,  -1,\n      -1,  -1,  -1,  87,  -1,  -1,  -1,  -1,  88,  -1,  -1,  -1,  -1,  89,\n      -1,  -1,  -1,  90,  -1,  -1,  -1,  -1,  91,  -1,  -1,  -1,  92,  -1,\n      -1,  -1,  -1,  93,  94,  95,  -1,  -1,  96,  -1,  -1,  -1,  -1,  97,\n      -1,  98,  -1,  -1,  -1,  -1,  99,  -1,  -1,  -1,  100, -1,  -1,  -1,\n      101, -1,  -1,  -1,  -1,  102, 103, -1,  -1,  -1,  104, 105, -1,  -1,\n      -1,  106, -1,  107, -1,  -1,  -1,  108, -1,  109, -1,  110, 111, -1,\n      112, -1,  -1,  113, -1,  114, 115, 116, 117, -1,  -1,  -1,  118, 119,\n      -1,  120, -1,  -1,  -1,  121, 122, -1,  -1,  -1,  123, -1,  124, 125,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  126, -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  127, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  128,\n      -1,  -1,  129, -1,  -1,  -1,  130, 131, -1,  132, -1,  -1,  133, 134,\n      -1,  -1,  -1,  135, -1,  136, -1,  -1,  -1,  -1,  -1,  137, 138, -1,\n      -1,  -1,  139, -1,  140, 141, -1,  -1,  -1,  142, -1,  -1,  143, 144,\n      -1,  145, -1,  -1,  -1,  -1,  146, -1,  -1,  -1,  147, -1,  148, -1,\n      149, 150, -1,  -1,  -1,  -1,  -1,  151, -1,  -1,  152, -1,  153, -1,\n      -1,  154, 155, -1,  -1,  -1,  -1,  156, 157, -1,  -1,  -1,  158, -1,\n      159, -1,  -1,  160, -1,  -1,  161, -1,  162, -1,  163, -1,  -1,  -1,\n      -1,  164, 165, 166, -1,  -1,  -1,  -1,  167, -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  168, 169, 170, -1,  -1,  -1,  -1,\n      171, 172, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  173, 174,\n      -1,  -1,  175, -1,  -1,  176, -1,  177, 178, -1,  -1,  179, 180, 181,\n      182, -1,  183, 184, -1,  -1,  -1,  -1,  185, 186, -1,  -1,  187, -1,\n      188, -1,  189, 190, 191, -1,  -1,  -1,  192, 193, -1,  194, -1,  195,\n      196, -1,  -1,  -1,  -1,  -1,  -1,  197, -1,  -1,  -1,  198, -1,  199,\n      -1,  -1,  -1,  -1,  -1,  200, -1,  -1,  -1,  201, -1,  -1,  202, 203,\n      204, 205, 206, -1,  -1,  207, 208, -1,  -1,  -1,  -1,  209, -1,  -1,\n      210, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  211,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  212, -1,  -1,  -1,  -1,\n      -1,  -1,  213, -1,  -1,  -1,  214, -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      215, 216, -1,  -1,  -1,  217, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      218, 219, -1,  -1,  -1,  220, 221, -1,  222, -1,  223, -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  224, -1,  -1,  -1,  -1,  225, -1,\n      226, -1,  -1,  -1,  -1,  -1,  -1,  -1,  227, -1,  -1,  -1,  228, -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  229, -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  230, 231, -1,  -1,  232, -1,  233, -1,\n      234, 235, -1,  -1,  -1,  236, 237, 238, -1,  239, -1,  240, 241, -1,\n      -1,  242, -1,  243, -1,  -1,  -1,  244, -1,  -1,  -1,  -1,  245, -1,\n      -1,  -1,  -1,  -1,  -1,  246, -1,  -1,  247, -1,  -1,  248, 249, -1,\n      -1,  -1,  -1,  -1,  250, -1,  -1,  -1,  251, -1,  252, -1,  -1,  -1,\n      253, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  254,\n      -1,  255, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  256, -1,  -1,  -1,\n      257, -1,  -1,  -1,  258, 259, -1,  -1,  -1,  -1,  -1,  -1,  -1,  260,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  261, -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  262, -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  263, 264, -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  265, -1,  266, -1,  -1,  267, -1,  268,\n      -1,  -1,  -1,  -1,  -1,  269, 270, -1,  -1,  -1,  -1,  -1,  271, -1,\n      -1,  -1,  -1,  272, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  273, -1,  -1,\n      -1,  -1,  -1,  274, 275, -1,  276, 277, 278, -1,  -1,  -1,  -1,  279,\n      280, -1,  -1,  -1,  -1,  281, -1,  -1,  -1,  -1,  282, -1,  -1,  -1,\n      283, 284, -1,  -1,  -1,  -1,  -1,  285, -1,  -1,  286, -1,  -1,  -1,\n      287, -1,  288, -1,  -1,  -1,  -1,  -1,  -1,  -1,  289, -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  290, -1,  -1,  -1,  -1,  291, -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  292, 293, -1,  -1,  -1,  -1,  -1,  294,\n      -1,  -1,  -1,  295, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      296, 297, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  298, -1,  -1,  -1,  -1,  299, -1,  300, -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  301, -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  302, -1,  -1,  -1,  303, -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  304, -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  305, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      306, 307, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  308, -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  309, -1,  -1,  -1,  -1,\n      310, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  311, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  312, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  313, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  314, -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      315, -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  316, -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,\n      -1,  -1,  -1,  -1,  317};\n\n  if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) {\n    unsigned int key = hash(str, len);\n\n    if (key <= MAX_HASH_VALUE) {\n      int index = lookup[key];\n\n      if (index >= 0) {\n        const char *s = token_list[index].name;\n\n        if (*str == *s && !strcmp(str + 1, s + 1)) return &token_list[index];\n      }\n    }\n  }\n  return nullptr;\n}\n#line 340 \"css_keywords.tmpl\"\n\nconst struct TokenValue *GetTokenValue(const char *str, unsigned len) {\n  return CSSKeywordsHash::GetTokenValue(str, len);\n}\n}  // namespace tasm\n}  // namespace lynx\n// NOLINTEND(modernize-use-nullptr)\n"
  },
  {
    "path": "core/renderer/css/css_keywords.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_KEYWORDS_H_\n#define CORE_RENDERER_CSS_CSS_KEYWORDS_H_\n\nnamespace lynx {\nnamespace tasm {\n\nenum class TokenType {\n  /* tokens begin */\n  LEFT_PAREN,   // (\n  RIGHT_PAREN,  // )\n  COMMA,        // ,\n  COLON,        // :\n  DOT,          // .\n  MINUS,        // -\n  PLUS,         // +\n  SEMICOLON,    // ;\n  SLASH,        // /\n  SHARP,        // #\n  PERCENTAGE,   // %\n  EQUAL,        // =\n  IDENTIFIER,   // {a-z, A-Z, or _}, { a-z A-Z 0-9 _ }\n  STRING,       // ''\n  NUMBER,       // number\n  DIMENSION,    // number + ident\n  HEX,          // #\n  ERROR,\n  TOKEN_EOF,\n  WHITESPACE,\n  UNKNOWN,\n  /* tokens end */\n\n  // Keywords below are ident-like tokens, as new token type for compatibility\n  RGB,              // rgb\n  RGBA,             // rgba\n  HSL,              // hsl\n  HSLA,             // hsla\n  URL,              // url\n  NONE,             // none\n  TO,               // to\n  CENTER,           // center\n  PX,               // px\n  RPX,              // rpx\n  REM,              // rem\n  EM,               // em\n  VW,               // vw\n  VH,               // vh\n  PPX,              // ppx\n  FR,               // fr\n  SP,               // sp\n  MAX_CONTENT,      // max-content\n  DEG,              // deg\n  GRAD,             // GRAD\n  RAD,              // rad\n  TURN,             // turn\n  AUTO,             // auto\n  COVER,            // cover\n  CONTAIN,          // contain\n  REPEAT_X,         // repeat-x\n  REPEAT_Y,         // repeat-y\n  REPEAT,           // repeat\n  NO_REPEAT,        // no-repeat\n  SPACE,            // space\n  ROUND,            // round\n  BORDER_BOX,       // border-box\n  PADDING_BOX,      // padding-box\n  CONTENT_BOX,      // content-box\n  TEXT,             // text\n  BORDER_AREA,      // border-area\n  LINEAR_GRADIENT,  // linear-gradient\n  RADIAL_GRADIENT,  // radial-gradient\n  CONIC_GRADIENT,   // conic-gradient\n  CLOSEST_SIDE,     // closest-side\n  CLOSEST_CORNER,   // closest-corner\n  FARTHEST_SIDE,    // farthest-side\n  FARTHEST_CORNER,  // farthest-corner\n  ELLIPSE,          // ellipse\n  CIRCLE,           // circle\n  POLYGON,          // polygon\n  SUPER_ELLIPSE,    // super-ellipse\n  PATH,             // path\n  AT,               // at\n  DATA,             // data\n  /* border keywords */\n  THIN,    // thin\n  MEDIUM,  // medium\n  THICK,   // thick\n  HIDDEN,  // hidden\n  DOTTED,  // dotted\n  DASHED,  // dashed\n  SOLID,   // solid\n  DOUBLE,  // double\n  GROOVE,  // groove\n  RIDGE,   // ridge\n  INSET,   // inset\n  OUTSET,  // outset\n  /* text-decoration */\n  UNDERLINE,\n  LINE_THROUGH,\n  WAVY,\n  /* font-face */\n  FORMAT,\n  LOCAL,\n  NORMAL,\n  BOLD,\n  /* compatible history keywords */\n  TOBOTTOM,  // tobottom\n  TOLEFT,    // toleft\n  TORIGHT,   // toright\n  TOTOP,     // totop\n  /* function begin */\n  CALC,         // calc(1px + 12px)\n  ENV,          // env(safe-area-inset-right)\n  FIT_CONTENT,  // fit-content(20px)\n  BLUR,         // blur(2px)\n\n  /* transform begin */\n  ROTATE,\n  ROTATE_X,\n  ROTATE_Y,\n  ROTATE_Z,\n  TRANSLATE,\n  TRANSLATE_3D,\n  TRANSLATE_X,\n  TRANSLATE_Y,\n  TRANSLATE_Z,\n  SCALE,\n  SCALE_X,\n  SCALE_Y,\n  SKEW,\n  SKEW_X,\n  SKEW_Y,\n  MATRIX,\n  MATRIX_3D,\n  /* transform end */\n\n  SQUARE_BEZIER,  // square-bezier\n  CUBIC_BEZIER,   // cubic-bezier\n  STEPS,          // steps\n\n  GRAYSCALE,   // grayscale(10%)\n  BRIGHTNESS,  // brightness(10%)\n  CONTRAST,    // contrast(10%)\n  SATURATE,    // saturate(10%)\n  HUE_ROTATE,  // hue-rotate(10deg)\n\n  VAR,\n  /* function end */\n\n  /* color begin, the order is associated with CSSColor */\n  TRANSPARENT,\n  ALICEBLUE,\n  ANTIQUEWHITE,\n  AQUA,\n  AQUAMARINE,\n  AZURE,\n  BEIGE,\n  BISQUE,\n  BLACK,\n  BLANCHEDALMOND,\n  BLUE,\n  BLUEVIOLET,\n  BROWN,\n  BURLYWOOD,\n  CADETBLUE,\n  CHARTREUSE,\n  CHOCOLATE,\n  CORAL,\n  CORNFLOWERBLUE,\n  CORNSILK,\n  CRIMSON,\n  CYAN,\n  DARKBLUE,\n  DARKCYAN,\n  DARKGOLDENROD,\n  DARKGRAY,\n  DARKGREEN,\n  DARKGREY,\n  DARKKHAKI,\n  DARKMAGENTA,\n  DARKOLIVEGREEN,\n  DARKORANGE,\n  DARKORCHID,\n  DARKRED,\n  DARKSALMON,\n  DARKSEAGREEN,\n  DARKSLATEBLUE,\n  DARKSLATEGRAY,\n  DARKSLATEGREY,\n  DARKTURQUOISE,\n  DARKVIOLET,\n  DEEPPINK,\n  DEEPSKYBLUE,\n  DIMGRAY,\n  DIMGREY,\n  DODGERBLUE,\n  FIREBRICK,\n  FLORALWHITE,\n  FORESTGREEN,\n  FUCHSIA,\n  GAINSBORO,\n  GHOSTWHITE,\n  GOLD,\n  GOLDENROD,\n  GRAY,\n  GREEN,\n  GREENYELLOW,\n  GREY,\n  HONEYDEW,\n  HOTPINK,\n  INDIANRED,\n  INDIGO,\n  IVORY,\n  KHAKI,\n  LAVENDER,\n  LAVENDERBLUSH,\n  LAWNGREEN,\n  LEMONCHIFFON,\n  LIGHTBLUE,\n  LIGHTCORAL,\n  LIGHTCYAN,\n  LIGHTGOLDENRODYELLOW,\n  LIGHTGRAY,\n  LIGHTGREEN,\n  LIGHTGREY,\n  LIGHTPINK,\n  LIGHTSALMON,\n  LIGHTSEAGREEN,\n  LIGHTSKYBLUE,\n  LIGHTSLATEGRAY,\n  LIGHTSLATEGREY,\n  LIGHTSTEELBLUE,\n  LIGHTYELLOW,\n  LIME,\n  LIMEGREEN,\n  LINEN,\n  MAGENTA,\n  MAROON,\n  MEDIUMAQUAMARINE,\n  MEDIUMBLUE,\n  MEDIUMORCHID,\n  MEDIUMPURPLE,\n  MEDIUMSEAGREEN,\n  MEDIUMSLATEBLUE,\n  MEDIUMSPRINGGREEN,\n  MEDIUMTURQUOISE,\n  MEDIUMVIOLETRED,\n  MIDNIGHTBLUE,\n  MINTCREAM,\n  MISTYROSE,\n  MOCCASIN,\n  NAVAJOWHITE,\n  NAVY,\n  OLDLACE,\n  OLIVE,\n  OLIVEDRAB,\n  ORANGE,\n  ORANGERED,\n  ORCHID,\n  PALEGOLDENROD,\n  PALEGREEN,\n  PALETURQUOISE,\n  PALEVIOLETRED,\n  PAPAYAWHIP,\n  PEACHPUFF,\n  PERU,\n  PINK,\n  PLUM,\n  POWDERBLUE,\n  PURPLE,\n  RED,\n  ROSYBROWN,\n  ROYALBLUE,\n  SADDLEBROWN,\n  SALMON,\n  SANDYBROWN,\n  SEAGREEN,\n  SEASHELL,\n  SIENNA,\n  SILVER,\n  SKYBLUE,\n  SLATEBLUE,\n  SLATEGRAY,\n  SLATEGREY,\n  SNOW,\n  SPRINGGREEN,\n  STEELBLUE,\n  TAN,\n  TEAL,\n  THISTLE,\n  TOMATO,\n  TURQUOISE,\n  VIOLET,\n  WHEAT,\n  WHITE,\n  WHITESMOKE,\n  YELLOW,\n  YELLOWGREEN,\n  /* color end */\n\n  /* time */\n  SECOND,\n  MILLISECOND,\n\n  /* transition begin */\n  // NONE,\n  OPACITY,  // opacity\n  // SCALE_X,\n  // SCALE_Y,\n  SCALE_XY,             // scaleXY\n  WIDTH,                // width\n  HEIGHT,               // height\n  BACKGROUND_COLOR,     // background-color\n  COLOR,                // color\n  VISIBILITY,           // visibility\n  LEFT,                 // left\n  TOP,                  // top\n  RIGHT,                // right\n  BOTTOM,               // bottom\n  TRANSFORM,            // transform\n  ALL,                  // all\n  MAX_WIDTH,            // max-width\n  MAX_HEIGHT,           // max-height\n  MIN_WIDTH,            // min-width\n  MIN_HEIGHT,           // min-height\n  PADDING_LEFT,         // padding-left\n  PADDING_RIGHT,        // padding-right\n  PADDING_TOP,          // padding-top\n  PADDING_BOTTOM,       // padding-bottom\n  MARGIN_LEFT,          // margin-left\n  MARGIN_RIGHT,         // margin-right\n  MARGIN_TOP,           // margin-top\n  MARGIN_BOTTOM,        // margin-bottom\n  BORDER_LEFT_COLOR,    // border-left-color\n  BORDER_RIGHT_COLOR,   // border-right-color\n  BORDER_TOP_COLOR,     // border-top-color\n  BORDER_BOTTOM_COLOR,  // border-bottom-color\n  BORDER_LEFT_WIDTH,    // border-left-width\n  BORDER_RIGHT_WIDTH,   // border-right-width\n  BORDER_TOP_WIDTH,     // border-top-width\n  BORDER_BOTTOM_WIDTH,  // border-bottom-width\n  FLEX_BASIS,           // flex-basis\n  FLEX_GROW,            // flex-grow\n  BORDER_WIDTH,         // border-width\n  BORDER_COLOR,         // border-color\n  PADDING,              // padding\n  MARGIN,               // margin\n  FILTER,               // filter\n  OFFSET_DISTANCE,      // offset-distance\n  BACKGROUND_POSITION,  // background-position\n  TRANSFORM_ORIGIN,     // transform-origin\n  /* transition end*/\n\n  /* timing function begin */\n  LINEAR,            // linear\n  EASE_IN,           // ease-in\n  EASE_OUT,          // ease-out\n  EASE_IN_EASE_OUT,  // ease-in-ease-out\n  EASE,              // ease\n  EASE_IN_OUT,       // ease-in-out\n  STEP_START,        // step-start\n  STEP_END,          // step-end\n\n  /* timing function end */\n  REVERSE,\n  ALTERNATE,\n  ALTERNATE_REVERSE,\n\n  FORWARDS,\n  BACKWARDS,\n  BOTH,\n\n  INFINITE,\n\n  PAUSED,\n  RUNNING,\n\n  TOKEN_TRUE,\n  TOKEN_FALSE,\n\n  ON,\n  OFF,\n  FROM,\n};\n\nstruct TokenValue {\n  const char* name;\n  TokenType type;\n};\n\nconst struct TokenValue* GetTokenValue(const char* str, unsigned len);\n\n}  // namespace tasm\n}  // namespace lynx\n#endif  // CORE_RENDERER_CSS_CSS_KEYWORDS_H_\n"
  },
  {
    "path": "core/renderer/css/css_keywords.tmpl",
    "content": "%language=C++\n%define class-name CSSKeywordsHash\n%define word-array-name token_list\n%define lookup-function-name GetTokenValue\n%enum\n%readonly-tables\n%{\n// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/css_keywords.h\"\n\n#include <cstring>\n\n#define size_t unsigned\n// NOLINTBEGIN(modernize-use-nullptr)\nnamespace lynx{\nnamespace tasm{\n%}\nstruct TokenValue;\n%%\nrgb, TokenType::RGB\nrgba, TokenType::RGBA\nhsl, TokenType::HSL\nhsla, TokenType::HSLA\nurl, TokenType::URL\nnone, TokenType::NONE\nto, TokenType::TO\nleft, TokenType::LEFT\ntop, TokenType::TOP\nright, TokenType::RIGHT\nbottom, TokenType::BOTTOM\ncenter, TokenType::CENTER\npx, TokenType::PX\nrpx, TokenType::RPX\nrem, TokenType::REM\nem, TokenType::EM\nvw, TokenType::VW\nvh, TokenType::VH\nsp, TokenType::SP\nppx, TokenType::PPX\nmax-content, TokenType::MAX_CONTENT\ndeg, TokenType::DEG\ngrad, TokenType::GRAD\nrad, TokenType::RAD\nturn, TokenType::TURN\nauto, TokenType::AUTO\ncover, TokenType::COVER\ncontain, TokenType::CONTAIN\nrepeat-x, TokenType::REPEAT_X\nrepeat-y, TokenType::REPEAT_Y\nrepeat, TokenType::REPEAT\nno-repeat, TokenType::NO_REPEAT\nspace, TokenType::SPACE\nround, TokenType::ROUND\nborder-box, TokenType::BORDER_BOX\npadding-box, TokenType::PADDING_BOX\ncontent-box, TokenType::CONTENT_BOX\ntext, TokenType::TEXT\nborder-area, TokenType::BORDER_AREA\nlinear-gradient, TokenType::LINEAR_GRADIENT\nradial-gradient, TokenType::RADIAL_GRADIENT\nconic-gradient, TokenType::CONIC_GRADIENT\nclosest-side, TokenType::CLOSEST_SIDE\nclosest-corner, TokenType::CLOSEST_CORNER\nfarthest-side, TokenType::FARTHEST_SIDE\nfarthest-corner, TokenType::FARTHEST_CORNER\nellipse, TokenType::ELLIPSE\ncircle, TokenType::CIRCLE\npolygon, TokenType::POLYGON\nat, TokenType::AT\ndata, TokenType::DATA\nthin, TokenType::THIN\nmedium, TokenType::MEDIUM\nthick, TokenType::THICK\nhidden, TokenType::HIDDEN\ndotted, TokenType::DOTTED\ndashed, TokenType::DASHED\nsolid, TokenType::SOLID\ndouble, TokenType::DOUBLE\ngroove, TokenType::GROOVE\nridge, TokenType::RIDGE\ninset, TokenType::INSET\noutset, TokenType::OUTSET\nunderline, TokenType::UNDERLINE\nline-through, TokenType::LINE_THROUGH\nwavy, TokenType::WAVY\nformat, TokenType::FORMAT\nlocal, TokenType::LOCAL\nnormal, TokenType::NORMAL\nbold, TokenType::BOLD\ntobottom, TokenType::TOBOTTOM\ntoleft, TokenType::TOLEFT\ntoright, TokenType::TORIGHT\ntotop, TokenType::TOTOP\npath, TokenType::PATH\nsuper-ellipse, TokenType::SUPER_ELLIPSE\ncalc, TokenType::CALC\nenv, TokenType::ENV\ngrayscale, TokenType::GRAYSCALE\nbrightness, TokenType::BRIGHTNESS\ncontrast, TokenType::CONTRAST\nsaturate, TokenType::SATURATE\nhue-rotate, TokenType::HUE_ROTATE\nvar, TokenType::VAR\nblur, TokenType::BLUR\nfit-content, TokenType::FIT_CONTENT\ntransparent, TokenType::TRANSPARENT\naliceblue, TokenType::ALICEBLUE\nantiquewhite, TokenType::ANTIQUEWHITE\naqua, TokenType::AQUA\naquamarine, TokenType::AQUAMARINE\nazure, TokenType::AZURE\nbeige, TokenType::BEIGE\nbisque, TokenType::BISQUE\nblack, TokenType::BLACK\nblanchedalmond, TokenType::BLANCHEDALMOND\nblue, TokenType::BLUE\nblueviolet, TokenType::BLUEVIOLET\nbrown, TokenType::BROWN\nburlywood, TokenType::BURLYWOOD\ncadetblue, TokenType::CADETBLUE\nchartreuse, TokenType::CHARTREUSE\nchocolate, TokenType::CHOCOLATE\ncoral, TokenType::CORAL\ncornflowerblue, TokenType::CORNFLOWERBLUE\ncornsilk, TokenType::CORNSILK\ncrimson, TokenType::CRIMSON\ncyan, TokenType::CYAN\ndarkblue, TokenType::DARKBLUE\ndarkcyan, TokenType::DARKCYAN\ndarkgoldenrod, TokenType::DARKGOLDENROD\ndarkgray, TokenType::DARKGRAY\ndarkgreen, TokenType::DARKGREEN\ndarkgrey, TokenType::DARKGREY\ndarkkhaki, TokenType::DARKKHAKI\ndarkmagenta, TokenType::DARKMAGENTA\ndarkolivegreen, TokenType::DARKOLIVEGREEN\ndarkorange, TokenType::DARKORANGE\ndarkorchid, TokenType::DARKORCHID\ndarkred, TokenType::DARKRED\ndarksalmon, TokenType::DARKSALMON\ndarkseagreen, TokenType::DARKSEAGREEN\ndarkslateblue, TokenType::DARKSLATEBLUE\ndarkslategray, TokenType::DARKSLATEGRAY\ndarkslategrey, TokenType::DARKSLATEGREY\ndarkturquoise, TokenType::DARKTURQUOISE\ndarkviolet, TokenType::DARKVIOLET\ndeeppink, TokenType::DEEPPINK\ndeepskyblue, TokenType::DEEPSKYBLUE\ndimgray, TokenType::DIMGRAY\ndimgrey, TokenType::DIMGREY\ndodgerblue, TokenType::DODGERBLUE\nfirebrick, TokenType::FIREBRICK\nfloralwhite, TokenType::FLORALWHITE\nforestgreen, TokenType::FORESTGREEN\nfuchsia, TokenType::FUCHSIA\ngainsboro, TokenType::GAINSBORO\nghostwhite, TokenType::GHOSTWHITE\ngold, TokenType::GOLD\ngoldenrod, TokenType::GOLDENROD\ngray, TokenType::GRAY\ngreen, TokenType::GREEN\ngreenyellow, TokenType::GREENYELLOW\ngrey, TokenType::GREY\nhoneydew, TokenType::HONEYDEW\nhotpink, TokenType::HOTPINK\nindianred, TokenType::INDIANRED\nindigo, TokenType::INDIGO\nivory, TokenType::IVORY\nkhaki, TokenType::KHAKI\nlavender, TokenType::LAVENDER\nlavenderblush, TokenType::LAVENDERBLUSH\nlawngreen, TokenType::LAWNGREEN\nlemonchiffon, TokenType::LEMONCHIFFON\nlightblue, TokenType::LIGHTBLUE\nlightcoral, TokenType::LIGHTCORAL\nlightcyan, TokenType::LIGHTCYAN\nlightgoldenrodyellow, TokenType::LIGHTGOLDENRODYELLOW\nlightgray, TokenType::LIGHTGRAY\nlightgreen, TokenType::LIGHTGREEN\nlightgrey, TokenType::LIGHTGREY\nlightpink, TokenType::LIGHTPINK\nlightsalmon, TokenType::LIGHTSALMON\nlightseagreen, TokenType::LIGHTSEAGREEN\nlightskyblue, TokenType::LIGHTSKYBLUE\nlightslategray, TokenType::LIGHTSLATEGRAY\nlightslategrey, TokenType::LIGHTSLATEGREY\nlightsteelblue, TokenType::LIGHTSTEELBLUE\nlightyellow, TokenType::LIGHTYELLOW\nlime, TokenType::LIME\nlimegreen, TokenType::LIMEGREEN\nlinen, TokenType::LINEN\nmagenta, TokenType::MAGENTA\nmaroon, TokenType::MAROON\nmediumaquamarine, TokenType::MEDIUMAQUAMARINE\nmediumblue, TokenType::MEDIUMBLUE\nmediumorchid, TokenType::MEDIUMORCHID\nmediumpurple, TokenType::MEDIUMPURPLE\nmediumseagreen, TokenType::MEDIUMSEAGREEN\nmediumslateblue, TokenType::MEDIUMSLATEBLUE\nmediumspringgreen, TokenType::MEDIUMSPRINGGREEN\nmediumturquoise, TokenType::MEDIUMTURQUOISE\nmediumvioletred, TokenType::MEDIUMVIOLETRED\nmidnightblue, TokenType::MIDNIGHTBLUE\nmintcream, TokenType::MINTCREAM\nmistyrose, TokenType::MISTYROSE\nmoccasin, TokenType::MOCCASIN\nnavajowhite, TokenType::NAVAJOWHITE\nnavy, TokenType::NAVY\noldlace, TokenType::OLDLACE\nolive, TokenType::OLIVE\nolivedrab, TokenType::OLIVEDRAB\norange, TokenType::ORANGE\norangered, TokenType::ORANGERED\norchid, TokenType::ORCHID\npalegoldenrod, TokenType::PALEGOLDENROD\npalegreen, TokenType::PALEGREEN\npaleturquoise, TokenType::PALETURQUOISE\npalevioletred, TokenType::PALEVIOLETRED\npapayawhip, TokenType::PAPAYAWHIP\npeachpuff, TokenType::PEACHPUFF\nperu, TokenType::PERU\npink, TokenType::PINK\nplum, TokenType::PLUM\npowderblue, TokenType::POWDERBLUE\npurple, TokenType::PURPLE\nred, TokenType::RED\nrosybrown, TokenType::ROSYBROWN\nroyalblue, TokenType::ROYALBLUE\nsaddlebrown, TokenType::SADDLEBROWN\nsalmon, TokenType::SALMON\nsandybrown, TokenType::SANDYBROWN\nseagreen, TokenType::SEAGREEN\nseashell, TokenType::SEASHELL\nsienna, TokenType::SIENNA\nsilver, TokenType::SILVER\nskyblue, TokenType::SKYBLUE\nslateblue, TokenType::SLATEBLUE\nslategray, TokenType::SLATEGRAY\nslategrey, TokenType::SLATEGREY\nsnow, TokenType::SNOW\nspringgreen, TokenType::SPRINGGREEN\nsteelblue, TokenType::STEELBLUE\ntan, TokenType::TAN\nteal, TokenType::TEAL\nthistle, TokenType::THISTLE\ntomato, TokenType::TOMATO\nturquoise, TokenType::TURQUOISE\nviolet, TokenType::VIOLET\nwheat, TokenType::WHEAT\nwhite, TokenType::WHITE\nwhitesmoke, TokenType::WHITESMOKE\nyellow, TokenType::YELLOW\nyellowgreen, TokenType::YELLOWGREEN\nrotate, TokenType::ROTATE\nrotatex, TokenType::ROTATE_X\nrotatey, TokenType::ROTATE_Y\nrotatez, TokenType::ROTATE_Z\ntranslate, TokenType::TRANSLATE\ntranslate3d, TokenType::TRANSLATE_3D\ntranslatex, TokenType::TRANSLATE_X\ntranslatey, TokenType::TRANSLATE_Y\ntranslatez, TokenType::TRANSLATE_Z\nscale, TokenType::SCALE\nscalex, TokenType::SCALE_X\nscaley, TokenType::SCALE_Y\nskew, TokenType::SKEW\nskewx, TokenType::SKEW_X\nskewy, TokenType::SKEW_Y\nmatrix, TokenType::MATRIX\nmatrix3d, TokenType::MATRIX_3D\nopacity, TokenType::OPACITY\nscalexy, TokenType::SCALE_XY\nwidth, TokenType::WIDTH\nheight, TokenType::HEIGHT\nbackground-color, TokenType::BACKGROUND_COLOR\ncolor, TokenType::COLOR\nvisibility, TokenType::VISIBILITY\ntransform, TokenType::TRANSFORM\nall, TokenType::ALL\nmax-width, TokenType::MAX_WIDTH\nmax-height, TokenType::MAX_HEIGHT\nmin-width, TokenType::MIN_WIDTH\nmin-height, TokenType::MIN_HEIGHT\nborder-left-width, TokenType::BORDER_LEFT_WIDTH\nborder-right-width, TokenType::BORDER_RIGHT_WIDTH\nborder-top-width, TokenType::BORDER_TOP_WIDTH\nborder-bottom-width, TokenType::BORDER_BOTTOM_WIDTH\nborder-left-color, TokenType::BORDER_LEFT_COLOR\nborder-right-color, TokenType::BORDER_RIGHT_COLOR\nborder-top-color, TokenType::BORDER_TOP_COLOR\nborder-bottom-color, TokenType::BORDER_BOTTOM_COLOR\nmargin-left, TokenType::MARGIN_LEFT\nmargin-right, TokenType::MARGIN_RIGHT\nmargin-top, TokenType::MARGIN_TOP\nmargin-bottom, TokenType::MARGIN_BOTTOM\npadding-left, TokenType::PADDING_LEFT\npadding-right, TokenType::PADDING_RIGHT\npadding-top, TokenType::PADDING_TOP\npadding-bottom, TokenType::PADDING_BOTTOM\nflex-basis, TokenType::FLEX_BASIS\nflex-grow, TokenType::FLEX_GROW\nborder-width, TokenType::BORDER_WIDTH\nborder-color, TokenType::BORDER_COLOR\nmargin, TokenType::MARGIN\npadding, TokenType::PADDING\nfilter, TokenType::FILTER\nbackground-position, TokenType::BACKGROUND_POSITION\ntransform-origin, TokenType::TRANSFORM_ORIGIN\noffset-distance, TokenType::OFFSET_DISTANCE\nlinear, TokenType::LINEAR\nease-in, TokenType::EASE_IN\nease-out, TokenType::EASE_OUT\nease-in-ease-out, TokenType::EASE_IN_EASE_OUT\nease, TokenType::EASE\nease-in-out, TokenType::EASE_IN_OUT\nstep-start, TokenType::STEP_START\nstep-end, TokenType::STEP_END\nsquare-bezier, TokenType::SQUARE_BEZIER\ncubic-bezier, TokenType::CUBIC_BEZIER\nsteps, TokenType::STEPS\nms, TokenType::MILLISECOND\ns, TokenType::SECOND\nreverse, TokenType::REVERSE\nalternate, TokenType::ALTERNATE\nalternate-reverse, TokenType::ALTERNATE_REVERSE\nforwards, TokenType::FORWARDS\nbackwards, TokenType::BACKWARDS\nboth, TokenType::BOTH\ninfinite, TokenType::INFINITE\npaused, TokenType::PAUSED\nrunning, TokenType::RUNNING\ntrue, TokenType::TOKEN_TRUE\nfalse, TokenType::TOKEN_FALSE\nfr, TokenType::FR\non, TokenType::ON\noff, TokenType::OFF\nfrom, TokenType::FROM\n%%\nconst struct TokenValue* GetTokenValue(const char *str, unsigned len) {\n  return CSSKeywordsHash::GetTokenValue(str, len);\n}\n}  // namespace tasm\n}  // namespace lynx\n// NOLINTEND(modernize-use-nullptr)\n"
  },
  {
    "path": "core/renderer/css/css_keywords_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_keywords.h\"\n\n#include <map>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nTEST(CSSKeywords, TokenTypeCheck) {\n  static const std::map<std::string, TokenType> ret{\n      {\"rgb\", TokenType::RGB},\n      {\"rgba\", TokenType::RGBA},\n      {\"hsl\", TokenType::HSL},\n      {\"hsla\", TokenType::HSLA},\n      {\"url\", TokenType::URL},\n      {\"none\", TokenType::NONE},\n      {\"to\", TokenType::TO},\n      {\"left\", TokenType::LEFT},\n      {\"top\", TokenType::TOP},\n      {\"right\", TokenType::RIGHT},\n      {\"bottom\", TokenType::BOTTOM},\n      {\"center\", TokenType::CENTER},\n      {\"px\", TokenType::PX},\n      {\"rpx\", TokenType::RPX},\n      {\"rem\", TokenType::REM},\n      {\"em\", TokenType::EM},\n      {\"vw\", TokenType::VW},\n      {\"vh\", TokenType::VH},\n      {\"sp\", TokenType::SP},\n      {\"ppx\", TokenType::PPX},\n      {\"fr\", TokenType::FR},\n      {\"max-content\", TokenType::MAX_CONTENT},\n      {\"deg\", TokenType::DEG},\n      {\"grad\", TokenType::GRAD},\n      {\"rad\", TokenType::RAD},\n      {\"turn\", TokenType::TURN},\n      {\"auto\", TokenType::AUTO},\n      {\"cover\", TokenType::COVER},\n      {\"contain\", TokenType::CONTAIN},\n      {\"repeat-x\", TokenType::REPEAT_X},\n      {\"repeat-y\", TokenType::REPEAT_Y},\n      {\"repeat\", TokenType::REPEAT},\n      {\"no-repeat\", TokenType::NO_REPEAT},\n      {\"space\", TokenType::SPACE},\n      {\"round\", TokenType::ROUND},\n      {\"border-box\", TokenType::BORDER_BOX},\n      {\"padding-box\", TokenType::PADDING_BOX},\n      {\"content-box\", TokenType::CONTENT_BOX},\n      {\"text\", TokenType::TEXT},\n      {\"border-area\", TokenType::BORDER_AREA},\n      {\"linear-gradient\", TokenType::LINEAR_GRADIENT},\n      {\"radial-gradient\", TokenType::RADIAL_GRADIENT},\n      {\"conic-gradient\", TokenType::CONIC_GRADIENT},\n      {\"closest-side\", TokenType::CLOSEST_SIDE},\n      {\"closest-corner\", TokenType::CLOSEST_CORNER},\n      {\"farthest-side\", TokenType::FARTHEST_SIDE},\n      {\"farthest-corner\", TokenType::FARTHEST_CORNER},\n      {\"ellipse\", TokenType::ELLIPSE},\n      {\"circle\", TokenType::CIRCLE},\n      {\"polygon\", TokenType::POLYGON},\n      {\"at\", TokenType::AT},\n      {\"data\", TokenType::DATA},\n      {\"thin\", TokenType::THIN},\n      {\"medium\", TokenType::MEDIUM},\n      {\"thick\", TokenType::THICK},\n      {\"hidden\", TokenType::HIDDEN},\n      {\"dotted\", TokenType::DOTTED},\n      {\"dashed\", TokenType::DASHED},\n      {\"solid\", TokenType::SOLID},\n      {\"double\", TokenType::DOUBLE},\n      {\"groove\", TokenType::GROOVE},\n      {\"ridge\", TokenType::RIDGE},\n      {\"inset\", TokenType::INSET},\n      {\"outset\", TokenType::OUTSET},\n      {\"underline\", TokenType::UNDERLINE},\n      {\"line-through\", TokenType::LINE_THROUGH},\n      {\"wavy\", TokenType::WAVY},\n      {\"format\", TokenType::FORMAT},\n      {\"local\", TokenType::LOCAL},\n      {\"normal\", TokenType::NORMAL},\n      {\"bold\", TokenType::BOLD},\n      {\"tobottom\", TokenType::TOBOTTOM},\n      {\"toleft\", TokenType::TOLEFT},\n      {\"toright\", TokenType::TORIGHT},\n      {\"totop\", TokenType::TOTOP},\n      {\"path\", TokenType::PATH},\n      {\"super-ellipse\", TokenType::SUPER_ELLIPSE},\n      {\"calc\", TokenType::CALC},\n      {\"env\", TokenType::ENV},\n      {\"grayscale\", TokenType::GRAYSCALE},\n      {\"brightness\", TokenType::BRIGHTNESS},\n      {\"contrast\", TokenType::CONTRAST},\n      {\"saturate\", TokenType::SATURATE},\n      {\"hue-rotate\", TokenType::HUE_ROTATE},\n      {\"var\", TokenType::VAR},\n      {\"blur\", TokenType::BLUR},\n      {\"fit-content\", TokenType::FIT_CONTENT},\n      {\"transparent\", TokenType::TRANSPARENT},\n      {\"aliceblue\", TokenType::ALICEBLUE},\n      {\"antiquewhite\", TokenType::ANTIQUEWHITE},\n      {\"aqua\", TokenType::AQUA},\n      {\"aquamarine\", TokenType::AQUAMARINE},\n      {\"azure\", TokenType::AZURE},\n      {\"beige\", TokenType::BEIGE},\n      {\"bisque\", TokenType::BISQUE},\n      {\"black\", TokenType::BLACK},\n      {\"blanchedalmond\", TokenType::BLANCHEDALMOND},\n      {\"blue\", TokenType::BLUE},\n      {\"blueviolet\", TokenType::BLUEVIOLET},\n      {\"brown\", TokenType::BROWN},\n      {\"burlywood\", TokenType::BURLYWOOD},\n      {\"cadetblue\", TokenType::CADETBLUE},\n      {\"chartreuse\", TokenType::CHARTREUSE},\n      {\"chocolate\", TokenType::CHOCOLATE},\n      {\"coral\", TokenType::CORAL},\n      {\"cornflowerblue\", TokenType::CORNFLOWERBLUE},\n      {\"cornsilk\", TokenType::CORNSILK},\n      {\"crimson\", TokenType::CRIMSON},\n      {\"cyan\", TokenType::CYAN},\n      {\"darkblue\", TokenType::DARKBLUE},\n      {\"darkcyan\", TokenType::DARKCYAN},\n      {\"darkgoldenrod\", TokenType::DARKGOLDENROD},\n      {\"darkgray\", TokenType::DARKGRAY},\n      {\"darkgreen\", TokenType::DARKGREEN},\n      {\"darkgrey\", TokenType::DARKGREY},\n      {\"darkkhaki\", TokenType::DARKKHAKI},\n      {\"darkmagenta\", TokenType::DARKMAGENTA},\n      {\"darkolivegreen\", TokenType::DARKOLIVEGREEN},\n      {\"darkorange\", TokenType::DARKORANGE},\n      {\"darkorchid\", TokenType::DARKORCHID},\n      {\"darkred\", TokenType::DARKRED},\n      {\"darksalmon\", TokenType::DARKSALMON},\n      {\"darkseagreen\", TokenType::DARKSEAGREEN},\n      {\"darkslateblue\", TokenType::DARKSLATEBLUE},\n      {\"darkslategray\", TokenType::DARKSLATEGRAY},\n      {\"darkslategrey\", TokenType::DARKSLATEGREY},\n      {\"darkturquoise\", TokenType::DARKTURQUOISE},\n      {\"darkviolet\", TokenType::DARKVIOLET},\n      {\"deeppink\", TokenType::DEEPPINK},\n      {\"deepskyblue\", TokenType::DEEPSKYBLUE},\n      {\"dimgray\", TokenType::DIMGRAY},\n      {\"dimgrey\", TokenType::DIMGREY},\n      {\"dodgerblue\", TokenType::DODGERBLUE},\n      {\"firebrick\", TokenType::FIREBRICK},\n      {\"floralwhite\", TokenType::FLORALWHITE},\n      {\"forestgreen\", TokenType::FORESTGREEN},\n      {\"fuchsia\", TokenType::FUCHSIA},\n      {\"gainsboro\", TokenType::GAINSBORO},\n      {\"ghostwhite\", TokenType::GHOSTWHITE},\n      {\"gold\", TokenType::GOLD},\n      {\"goldenrod\", TokenType::GOLDENROD},\n      {\"gray\", TokenType::GRAY},\n      {\"green\", TokenType::GREEN},\n      {\"greenyellow\", TokenType::GREENYELLOW},\n      {\"grey\", TokenType::GREY},\n      {\"honeydew\", TokenType::HONEYDEW},\n      {\"hotpink\", TokenType::HOTPINK},\n      {\"indianred\", TokenType::INDIANRED},\n      {\"indigo\", TokenType::INDIGO},\n      {\"ivory\", TokenType::IVORY},\n      {\"khaki\", TokenType::KHAKI},\n      {\"lavender\", TokenType::LAVENDER},\n      {\"lavenderblush\", TokenType::LAVENDERBLUSH},\n      {\"lawngreen\", TokenType::LAWNGREEN},\n      {\"lemonchiffon\", TokenType::LEMONCHIFFON},\n      {\"lightblue\", TokenType::LIGHTBLUE},\n      {\"lightcoral\", TokenType::LIGHTCORAL},\n      {\"lightcyan\", TokenType::LIGHTCYAN},\n      {\"lightgoldenrodyellow\", TokenType::LIGHTGOLDENRODYELLOW},\n      {\"lightgray\", TokenType::LIGHTGRAY},\n      {\"lightgreen\", TokenType::LIGHTGREEN},\n      {\"lightgrey\", TokenType::LIGHTGREY},\n      {\"lightpink\", TokenType::LIGHTPINK},\n      {\"lightsalmon\", TokenType::LIGHTSALMON},\n      {\"lightseagreen\", TokenType::LIGHTSEAGREEN},\n      {\"lightskyblue\", TokenType::LIGHTSKYBLUE},\n      {\"lightslategray\", TokenType::LIGHTSLATEGRAY},\n      {\"lightslategrey\", TokenType::LIGHTSLATEGREY},\n      {\"lightsteelblue\", TokenType::LIGHTSTEELBLUE},\n      {\"lightyellow\", TokenType::LIGHTYELLOW},\n      {\"lime\", TokenType::LIME},\n      {\"limegreen\", TokenType::LIMEGREEN},\n      {\"linen\", TokenType::LINEN},\n      {\"magenta\", TokenType::MAGENTA},\n      {\"maroon\", TokenType::MAROON},\n      {\"mediumaquamarine\", TokenType::MEDIUMAQUAMARINE},\n      {\"mediumblue\", TokenType::MEDIUMBLUE},\n      {\"mediumorchid\", TokenType::MEDIUMORCHID},\n      {\"mediumpurple\", TokenType::MEDIUMPURPLE},\n      {\"mediumseagreen\", TokenType::MEDIUMSEAGREEN},\n      {\"mediumslateblue\", TokenType::MEDIUMSLATEBLUE},\n      {\"mediumspringgreen\", TokenType::MEDIUMSPRINGGREEN},\n      {\"mediumturquoise\", TokenType::MEDIUMTURQUOISE},\n      {\"mediumvioletred\", TokenType::MEDIUMVIOLETRED},\n      {\"midnightblue\", TokenType::MIDNIGHTBLUE},\n      {\"mintcream\", TokenType::MINTCREAM},\n      {\"mistyrose\", TokenType::MISTYROSE},\n      {\"moccasin\", TokenType::MOCCASIN},\n      {\"navajowhite\", TokenType::NAVAJOWHITE},\n      {\"navy\", TokenType::NAVY},\n      {\"oldlace\", TokenType::OLDLACE},\n      {\"olive\", TokenType::OLIVE},\n      {\"olivedrab\", TokenType::OLIVEDRAB},\n      {\"orange\", TokenType::ORANGE},\n      {\"orangered\", TokenType::ORANGERED},\n      {\"orchid\", TokenType::ORCHID},\n      {\"palegoldenrod\", TokenType::PALEGOLDENROD},\n      {\"palegreen\", TokenType::PALEGREEN},\n      {\"paleturquoise\", TokenType::PALETURQUOISE},\n      {\"palevioletred\", TokenType::PALEVIOLETRED},\n      {\"papayawhip\", TokenType::PAPAYAWHIP},\n      {\"peachpuff\", TokenType::PEACHPUFF},\n      {\"peru\", TokenType::PERU},\n      {\"pink\", TokenType::PINK},\n      {\"plum\", TokenType::PLUM},\n      {\"powderblue\", TokenType::POWDERBLUE},\n      {\"purple\", TokenType::PURPLE},\n      {\"red\", TokenType::RED},\n      {\"rosybrown\", TokenType::ROSYBROWN},\n      {\"royalblue\", TokenType::ROYALBLUE},\n      {\"saddlebrown\", TokenType::SADDLEBROWN},\n      {\"salmon\", TokenType::SALMON},\n      {\"sandybrown\", TokenType::SANDYBROWN},\n      {\"seagreen\", TokenType::SEAGREEN},\n      {\"seashell\", TokenType::SEASHELL},\n      {\"sienna\", TokenType::SIENNA},\n      {\"silver\", TokenType::SILVER},\n      {\"skyblue\", TokenType::SKYBLUE},\n      {\"slateblue\", TokenType::SLATEBLUE},\n      {\"slategray\", TokenType::SLATEGRAY},\n      {\"slategrey\", TokenType::SLATEGREY},\n      {\"snow\", TokenType::SNOW},\n      {\"springgreen\", TokenType::SPRINGGREEN},\n      {\"steelblue\", TokenType::STEELBLUE},\n      {\"tan\", TokenType::TAN},\n      {\"teal\", TokenType::TEAL},\n      {\"thistle\", TokenType::THISTLE},\n      {\"tomato\", TokenType::TOMATO},\n      {\"turquoise\", TokenType::TURQUOISE},\n      {\"violet\", TokenType::VIOLET},\n      {\"wheat\", TokenType::WHEAT},\n      {\"white\", TokenType::WHITE},\n      {\"whitesmoke\", TokenType::WHITESMOKE},\n      {\"yellow\", TokenType::YELLOW},\n      {\"yellowgreen\", TokenType::YELLOWGREEN},\n      {\"rotate\", TokenType::ROTATE},\n      {\"rotatex\", TokenType::ROTATE_X},\n      {\"rotatey\", TokenType::ROTATE_Y},\n      {\"rotatez\", TokenType::ROTATE_Z},\n      {\"translate\", TokenType::TRANSLATE},\n      {\"translate3d\", TokenType::TRANSLATE_3D},\n      {\"translatex\", TokenType::TRANSLATE_X},\n      {\"translatey\", TokenType::TRANSLATE_Y},\n      {\"translatez\", TokenType::TRANSLATE_Z},\n      {\"scale\", TokenType::SCALE},\n      {\"scalex\", TokenType::SCALE_X},\n      {\"scaley\", TokenType::SCALE_Y},\n      {\"skew\", TokenType::SKEW},\n      {\"skewx\", TokenType::SKEW_X},\n      {\"skewy\", TokenType::SKEW_Y},\n      {\"matrix\", TokenType::MATRIX},\n      {\"matrix3d\", TokenType::MATRIX_3D},\n      {\"opacity\", TokenType::OPACITY},\n      {\"scalexy\", TokenType::SCALE_XY},\n      {\"width\", TokenType::WIDTH},\n      {\"height\", TokenType::HEIGHT},\n      {\"background-color\", TokenType::BACKGROUND_COLOR},\n      {\"background-position\", TokenType::BACKGROUND_POSITION},\n      {\"color\", TokenType::COLOR},\n      {\"visibility\", TokenType::VISIBILITY},\n      {\"transform\", TokenType::TRANSFORM},\n      {\"transform-origin\", TokenType::TRANSFORM_ORIGIN},\n      {\"all\", TokenType::ALL},\n      {\"max-width\", TokenType::MAX_WIDTH},\n      {\"max-height\", TokenType::MAX_HEIGHT},\n      {\"min-width\", TokenType::MIN_WIDTH},\n      {\"min-height\", TokenType::MIN_HEIGHT},\n      {\"margin-left\", TokenType::MARGIN_LEFT},\n      {\"margin-right\", TokenType::MARGIN_RIGHT},\n      {\"margin-top\", TokenType::MARGIN_TOP},\n      {\"margin-bottom\", TokenType::MARGIN_BOTTOM},\n      {\"padding-left\", TokenType::PADDING_LEFT},\n      {\"padding-right\", TokenType::PADDING_RIGHT},\n      {\"padding-top\", TokenType::PADDING_TOP},\n      {\"padding-bottom\", TokenType::PADDING_BOTTOM},\n      {\"border-left-color\", TokenType::BORDER_LEFT_COLOR},\n      {\"border-right-color\", TokenType::BORDER_RIGHT_COLOR},\n      {\"border-top-color\", TokenType::BORDER_TOP_COLOR},\n      {\"border-bottom-color\", TokenType::BORDER_BOTTOM_COLOR},\n      {\"border-left-width\", TokenType::BORDER_LEFT_WIDTH},\n      {\"border-right-width\", TokenType::BORDER_RIGHT_WIDTH},\n      {\"border-top-width\", TokenType::BORDER_TOP_WIDTH},\n      {\"border-bottom-width\", TokenType::BORDER_BOTTOM_WIDTH},\n      {\"flex-basis\", TokenType::FLEX_BASIS},\n      {\"flex-grow\", TokenType::FLEX_GROW},\n      {\"border-width\", TokenType::BORDER_WIDTH},\n      {\"border-color\", TokenType::BORDER_COLOR},\n      {\"margin\", TokenType::MARGIN},\n      {\"padding\", TokenType::PADDING},\n      {\"filter\", TokenType::FILTER},\n      {\"offset-distance\", TokenType::OFFSET_DISTANCE},\n      {\"linear\", TokenType::LINEAR},\n      {\"ease-in\", TokenType::EASE_IN},\n      {\"ease-out\", TokenType::EASE_OUT},\n      {\"ease-in-ease-out\", TokenType::EASE_IN_EASE_OUT},\n      {\"ease\", TokenType::EASE},\n      {\"ease-in-out\", TokenType::EASE_IN_OUT},\n      {\"step-start\", TokenType::STEP_START},\n      {\"step-end\", TokenType::STEP_END},\n      {\"square-bezier\", TokenType::SQUARE_BEZIER},\n      {\"cubic-bezier\", TokenType::CUBIC_BEZIER},\n      {\"steps\", TokenType::STEPS},\n      {\"ms\", TokenType::MILLISECOND},\n      {\"s\", TokenType::SECOND},\n      {\"reverse\", TokenType::REVERSE},\n      {\"alternate\", TokenType::ALTERNATE},\n      {\"alternate-reverse\", TokenType::ALTERNATE_REVERSE},\n      {\"forwards\", TokenType::FORWARDS},\n      {\"backwards\", TokenType::BACKWARDS},\n      {\"both\", TokenType::BOTH},\n      {\"infinite\", TokenType::INFINITE},\n      {\"paused\", TokenType::PAUSED},\n      {\"running\", TokenType::RUNNING},\n      {\"true\", TokenType::TOKEN_TRUE},\n      {\"false\", TokenType::TOKEN_FALSE},\n      {\"on\", TokenType::ON},\n      {\"off\", TokenType::OFF},\n      {\"from\", TokenType::FROM},\n  };\n\n  EXPECT_EQ(\n      static_cast<int>(TokenType::FROM) - static_cast<int>(TokenType::UNKNOWN),\n      ret.size());\n  for (const auto& [s, t] : ret) {\n    EXPECT_EQ(GetTokenValue(s.c_str(), s.length())->type, t);\n  }\n}\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_parser_token.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"core/renderer/css/css_parser_token.h\"\n\n#include <cstddef>\n\n#include \"base/include/log/logging.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nbool CSSParseToken::IsPseudoStyleToken() const {\n  const auto& target_sheet_ptr = TargetSheet();\n  if (target_sheet_ptr) {\n    return target_sheet_ptr->GetType() > CSSSheet::NAME_SELECT &&\n           target_sheet_ptr->GetType() != CSSSheet::ALL_SELECT;\n  } else {\n    return false;\n  }\n}\n\nbool CSSParseToken::IsCascadeSelectorStyleToken() const {\n  return sheets_.size() > 1;\n}\n\nconst StyleMap& CSSParseToken::GetAttributes() {\n  if (parser_state_.load(std::memory_order_acquire) == ParseState::kParsed) {\n    // If token is already parsed, return the parsed attributes.\n    return attributes_;\n  }\n\n  StyleMap css_attribute;\n  size_t total_pool_capacity = 0;\n\n  // If raw attributes is not empty, process raw attributes.\n  if (!raw_attributes_.empty()) {\n    TRACE_EVENT(LYNX_TRACE_CATEGORY, CSS_PATCH_PROCESS_RAW);\n    // Get the total pool capacity of the parsed attributes from raw_attributes.\n    total_pool_capacity =\n        CSSProperty::GetTotalParsedStyleCountFromMap(raw_attributes_);\n    css_attribute.reserve(total_pool_capacity);\n    // Process raw attributes and store them in local variable css_attribute.\n    raw_attributes_.for_each([&](const CSSPropertyID& k, const CSSValue& v) {\n      UnitHandler::ProcessCSSValue(k, v, css_attribute, parser_configs_);\n    });\n  }\n\n  int expected = ParseState::kNotParsed;\n  // Try to set the parser state to kParsing.\n  while (\n      !parser_state_.compare_exchange_strong(expected, ParseState::kParsing) &&\n      parser_state_.load(std::memory_order_acquire) != ParseState::kParsed) {\n    expected = ParseState::kNotParsed;\n  }\n\n  // If the parser state is already kParsed, return the parsed attributes.\n  if (parser_state_.load(std::memory_order_acquire) == ParseState::kParsed) {\n    return attributes_;\n  }\n\n  attributes_ = std::move(css_attribute);\n  // Set the parser state to kParsed.\n  parser_state_.store(ParseState::kParsed, std::memory_order_release);\n  return attributes_;\n}\n\nint CSSParseToken::GetStyleTokenType() const {\n  if (sheets_.empty()) {\n    return 0;\n  }\n  const auto& target_sheet_ptr = TargetSheet();\n  return target_sheet_ptr ? target_sheet_ptr->GetType() : 0;\n}\n\nvoid CSSParseToken::MarkAsTouchPseudoToken() { is_touch_pseudo_ = true; }\n\nbool CSSParseToken::IsTouchPseudoToken() const { return is_touch_pseudo_; }\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_parser_token.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef CORE_RENDERER_CSS_CSS_PARSER_TOKEN_H_\n#define CORE_RENDERER_CSS_CSS_PARSER_TOKEN_H_\n\n#include <atomic>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/vector.h\"\n#include \"core/base/lynx_export.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/css_sheet.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass CSSParseToken : public fml::RefCountedThreadSafeStorage {\n public:\n  CSSParseToken(const CSSParserConfigs& parser_configs)\n      : parser_configs_(parser_configs) {}\n\n  virtual ~CSSParseToken() = default;\n\n  void ReleaseSelf() const override { delete this; }\n\n  auto& sheets() { return sheets_; }\n  const std::shared_ptr<CSSSheet>& TargetSheet() const {\n    return sheets_.back();\n  }\n\n  auto& style_variables() { return style_variables_; }\n  const auto& GetStyleVariables() const { return style_variables_; }\n\n  void SetStyleVariables(CSSVariableMap&& vars) {\n    style_variables_ = std::move(vars);\n  }\n\n  void SetAttribute(CSSPropertyID id, const CSSValue& value) {\n    attributes_[id] = value;\n  }\n\n  virtual void SetAttributes(StyleMap&& attributes) {\n    attributes_ = std::move(attributes);\n    MarkParsed();\n  }\n\n  auto& attributes() { return attributes_; }\n  LYNX_EXPORT_FOR_DEVTOOL virtual const StyleMap& GetAttributes();\n\n  auto& raw_attributes() { return raw_attributes_; }\n\n  bool IsPseudoStyleToken() const;\n  bool IsCascadeSelectorStyleToken() const;\n  int GetStyleTokenType() const;\n\n  void MarkAsTouchPseudoToken();\n  bool IsTouchPseudoToken() const;\n\n  const CSSParserConfigs& GetCSSParserConfigs() { return parser_configs_; }\n\n  inline void MarkParsed() { parser_state_ = ParseState::kParsed; }\n\n protected:\n  bool is_touch_pseudo_{false};\n  base::InlineVector<std::shared_ptr<CSSSheet>, 4> sheets_;\n\n  StyleMap attributes_;\n  RawStyleMap raw_attributes_;\n  CSSVariableMap style_variables_;\n  CSSParserConfigs parser_configs_;\n\n private:\n  enum ParseState : int {\n    kNotParsed = 0,\n    kParsing,\n    kParsed,\n  };\n\n  std::atomic_int parser_state_{ParseState::kNotParsed};\n};\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_PARSER_TOKEN_H_\n"
  },
  {
    "path": "core/renderer/css/css_parser_token_unittest.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/renderer/css/css_parser_token.h\"\n\n#include <tuple>\n\n#include \"base/include/value/table.h\"\n#include \"core/base/json/json_util.h\"\n#include \"core/base/thread/once_task.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n#include \"third_party/rapidjson/document.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nTEST(CSSParseToken, GetAttributes) {\n  CSSParserConfigs parser_configs;\n  auto tokens = fml::MakeRefCounted<CSSParseToken>(parser_configs);\n  tokens->raw_attributes().insert_or_assign(CSSPropertyID::kPropertyIDFontSize,\n                                            CSSValue::MakePlainString(\"18px\"));\n\n  EXPECT_FALSE(tokens->raw_attributes().empty());\n\n  const auto& attrs = tokens->GetAttributes();\n\n  StyleMap expected;\n  UnitHandler::Process(CSSPropertyID::kPropertyIDFontSize, lepus::Value(\"18px\"),\n                       expected, parser_configs);\n  EXPECT_EQ(attrs.find(CSSPropertyID::kPropertyIDFontSize)->second,\n            expected.find(CSSPropertyID::kPropertyIDFontSize)->second);\n}\n\nTEST(CSSParseToken, ParallelGetAttributes) {\n  CSSParserConfigs parser_configs;\n  StyleMap expected;\n  UnitHandler::Process(CSSPropertyID::kPropertyIDFontSize, lepus::Value(\"18px\"),\n                       expected, parser_configs);\n  UnitHandler::Process(CSSPropertyID::kPropertyIDHeight, lepus::Value(\"18px\"),\n                       expected, parser_configs);\n  UnitHandler::Process(CSSPropertyID::kPropertyIDWidth, lepus::Value(\"18px\"),\n                       expected, parser_configs);\n\n  auto tokens = fml::MakeRefCounted<CSSParseToken>(parser_configs);\n  tokens->SetAttributes(std::move(expected));\n\n  using TestOnceTaskRefptr = base::OnceTaskRefptr<void>;\n  std::vector<TestOnceTaskRefptr> task_vec{};\n\n  for (int i = 0; i < 10000; ++i) {\n    std::promise<void> promise;\n    std::future<void> future = promise.get_future();\n\n    auto task = fml::MakeRefCounted<base::OnceTask<void>>(\n        [tokens, promise = std::move(promise)]() mutable {\n          tokens->GetAttributes();\n          promise.set_value();\n        },\n        std::move(future));\n\n    base::TaskRunnerManufactor::PostTaskToConcurrentLoop(\n        [task]() { task->Run(); }, base::ConcurrentTaskType::HIGH_PRIORITY);\n\n    task_vec.emplace_back(std::move(task));\n  }\n\n  for (auto item : task_vec) {\n    item->GetFuture().get();\n  }\n}\n\nTEST(CSSParseToken, ParallelProcessAttributes) {\n  CSSParserConfigs parser_configs;\n  StyleMap expected;\n  UnitHandler::Process(CSSPropertyID::kPropertyIDFontSize, lepus::Value(\"18px\"),\n                       expected, parser_configs);\n  CSSValue font_size_expected_value =\n      expected.find(CSSPropertyID::kPropertyIDFontSize)->second;\n  UnitHandler::Process(CSSPropertyID::kPropertyIDHeight, lepus::Value(\"18px\"),\n                       expected, parser_configs);\n  CSSValue height_expected_value =\n      expected.find(CSSPropertyID::kPropertyIDHeight)->second;\n  UnitHandler::Process(CSSPropertyID::kPropertyIDWidth, lepus::Value(\"18px\"),\n                       expected, parser_configs);\n  CSSValue width_expected_value =\n      expected.find(CSSPropertyID::kPropertyIDWidth)->second;\n\n  RawStyleMap raw_attributes;\n  raw_attributes.insert_or_assign(CSSPropertyID::kPropertyIDFontSize,\n                                  CSSValue::MakePlainString(\"18px\"));\n  raw_attributes.insert_or_assign(CSSPropertyID::kPropertyIDHeight,\n                                  CSSValue::MakePlainString(\"18px\"));\n  raw_attributes.insert_or_assign(CSSPropertyID::kPropertyIDWidth,\n                                  CSSValue::MakePlainString(\"18px\"));\n\n  auto tokens = fml::MakeRefCounted<CSSParseToken>(parser_configs);\n  tokens->raw_attributes_ = std::move(raw_attributes);\n\n  using TestOnceTaskRefptr = base::OnceTaskRefptr<std::tuple<bool, bool, bool>>;\n  std::vector<TestOnceTaskRefptr> task_vec{};\n\n  for (int i = 0; i < 10000; ++i) {\n    std::promise<std::tuple<bool, bool, bool>> promise;\n    std::future<std::tuple<bool, bool, bool>> future = promise.get_future();\n\n    auto task =\n        fml::MakeRefCounted<base::OnceTask<std::tuple<bool, bool, bool>>>(\n            [tokens, font_size_expected_value, width_expected_value,\n             height_expected_value, promise = std::move(promise)]() mutable {\n              const auto& attributes = tokens->GetAttributes();\n\n              promise.set_value(std::make_tuple(\n                  attributes.find(CSSPropertyID::kPropertyIDFontSize)->second ==\n                      font_size_expected_value,\n                  attributes.find(CSSPropertyID::kPropertyIDHeight)->second ==\n                      height_expected_value,\n                  attributes.find(CSSPropertyID::kPropertyIDWidth)->second ==\n                      width_expected_value));\n            },\n            std::move(future));\n\n    base::TaskRunnerManufactor::PostTaskToConcurrentLoop(\n        [task]() { task->Run(); }, base::ConcurrentTaskType::HIGH_PRIORITY);\n\n    task_vec.emplace_back(std::move(task));\n  }\n\n  for (auto item : task_vec) {\n    auto compare_result = item->GetFuture().get();\n    EXPECT_TRUE(std::get<0>(compare_result));\n    EXPECT_TRUE(std::get<1>(compare_result));\n    EXPECT_TRUE(std::get<2>(compare_result));\n  }\n}\n\nTEST(CSSParseToken, TestTouchPseudoToken) {\n  CSSParserConfigs parser_configs;\n  auto tokens = fml::MakeRefCounted<CSSParseToken>(parser_configs);\n\n  tokens->MarkAsTouchPseudoToken();\n  EXPECT_TRUE(tokens->IsTouchPseudoToken());\n}\n\nTEST(CSSParseToken, TestGetStyleTokenTypeCornerCase) {\n  CSSParserConfigs parser_configs;\n  auto tokens = fml::MakeRefCounted<CSSParseToken>(parser_configs);\n  EXPECT_EQ(tokens->GetStyleTokenType(), 0);\n\n  tokens->sheets_.push_back(nullptr);\n  EXPECT_EQ(tokens->GetStyleTokenType(), 0);\n}\n\nTEST(CSSParseToken, TestIsPseudoStyleTokenCornerCase) {\n  CSSParserConfigs parser_configs;\n  auto tokens = fml::MakeRefCounted<CSSParseToken>(parser_configs);\n  tokens->sheets_.push_back(nullptr);\n\n  EXPECT_FALSE(tokens->IsPseudoStyleToken());\n}\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_property.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_property.h\"\n\n#include <array>\n#include <mutex>\n#include <set>\n#include <unordered_set>\n\n#include \"base/include/no_destructor.h\"\n#include \"core/public/prop_bundle.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nconst char* GetPropertyNameCStr(CSSPropertyID id) {\n  return CSSProperty::GetPropertyNameCStr(id);\n}\n\nconst std::set<CSSPropertyID> inspectorFilteredProperties{\n    kPropertyIDBorder,\n    kPropertyIDBorderTop,\n    kPropertyIDBorderRight,\n    kPropertyIDBorderBottom,\n    kPropertyIDBorderLeft,\n    kPropertyIDMarginInlineStart,\n    kPropertyIDMarginInlineEnd,\n    kPropertyIDPaddingInlineStart,\n    kPropertyIDPaddingInlineEnd,\n    kPropertyIDBorderInlineStartWidth,\n    kPropertyIDBorderInlineEndWidth,\n    kPropertyIDBorderInlineStartColor,\n    kPropertyIDBorderInlineEndColor,\n    kPropertyIDBorderInlineStartStyle,\n    kPropertyIDBorderInlineEndStyle,\n    kPropertyIDBorderStartStartRadius,\n    kPropertyIDBorderEndStartRadius,\n    kPropertyIDBorderStartEndRadius,\n    kPropertyIDBorderEndEndRadius,\n    kPropertyIDFlex,\n    kPropertyIDFlexFlow,\n    kPropertyIDPadding,\n    kPropertyIDMargin,\n    kPropertyIDInsetInlineStart,\n    kPropertyIDInsetInlineEnd,\n    kPropertyIDBorderWidth,\n    kPropertyIDBackground,\n    kPropertyIDBorderColor,\n    kPropertyIDBorderStyle,\n    kPropertyIDOutline};\n\nusing CSSPropertyNameToIDMap =\n    std::unordered_map<base::static_string::GenericCacheKey, CSSPropertyID>;\n\nCSSPropertyID CSSProperty::GetPropertyID(\n    const base::static_string::GenericCacheKey& key) {\n  const static base::NoDestructor<CSSPropertyNameToIDMap> kPropertyNameMapping{{\n#define DECLARE_PROPERTY_NAME(name, c, value) \\\n  {kPropertyName##name, kPropertyID##name},\n      FOREACH_ALL_PROPERTY(DECLARE_PROPERTY_NAME)\n#undef DECLARE_PROPERTY_NAME\n  }};\n  auto it = kPropertyNameMapping->find(key);\n  return it != kPropertyNameMapping->end() ? it->second : kPropertyEnd;\n}\n\nconst base::static_string::GenericCache& CSSProperty::GetPropertyName(\n    CSSPropertyID id) {\n  // The reason using std::call_once is that GenericCache is not copy\n  // constructible and `NoDestructor<PropertyIDMappingArray> xx{{}}`\n  // won't compile\n  using PropertyIDMappingArray = std::array<base::static_string::GenericCache,\n                                            CSSPropertyID::kPropertyEnd + 1>;\n  static PropertyIDMappingArray* kPropertyIdMapping;\n  static std::once_flag prop_id_mapping_init_flag;\n  std::call_once(prop_id_mapping_init_flag, []() {\n    kPropertyIdMapping = new PropertyIDMappingArray{\n        \"\",  // start\n#define DECLARE_PROPERTY_ID(name, c, value) kPropertyName##name,\n        FOREACH_ALL_PROPERTY(DECLARE_PROPERTY_ID)\n#undef DECLARE_PROPERTY_ID\n            \"\"  // end\n    };\n  });\n  if (id >= kPropertyStart && id <= kPropertyEnd) {\n    return (*kPropertyIdMapping)[id];\n  }\n  return (*kPropertyIdMapping)[kPropertyStart];  // Empty string\n}\n\n// TODO: Need to auto generate from css define\nsize_t CSSProperty::GetShorthandExpand(CSSPropertyID id) {\n  using PropertyIDShorthandArray =\n      std::array<uint8_t, CSSPropertyID::kPropertyEnd + 1>;\n  static PropertyIDShorthandArray* kPropertyIdShorthand;\n  static std::once_flag prop_id_mapping_init_flag;\n  std::call_once(prop_id_mapping_init_flag, []() {\n    kPropertyIdShorthand = new PropertyIDShorthandArray();\n    std::memset(kPropertyIdShorthand->data(), 0,\n                sizeof(uint8_t) * kPropertyIdShorthand->size());\n    (*kPropertyIdShorthand)[kPropertyIDPadding] = 4;\n    (*kPropertyIdShorthand)[kPropertyIDMargin] = 4;\n    (*kPropertyIdShorthand)[kPropertyIDFlex] = 3;\n    (*kPropertyIdShorthand)[kPropertyIDBackground] = 8;\n    (*kPropertyIdShorthand)[kPropertyIDBorder] = 12;\n    (*kPropertyIdShorthand)[kPropertyIDBorderWidth] = 4;\n    (*kPropertyIdShorthand)[kPropertyIDBorderRadius] = 4;\n    (*kPropertyIdShorthand)[kPropertyIDBorderColor] = 4;\n    (*kPropertyIdShorthand)[kPropertyIDBorderStyle] = 4;\n    (*kPropertyIdShorthand)[kPropertyIDBorderRight] = 3;\n    (*kPropertyIdShorthand)[kPropertyIDBorderLeft] = 3;\n    (*kPropertyIdShorthand)[kPropertyIDBorderTop] = 3;\n    (*kPropertyIdShorthand)[kPropertyIDBorderBottom] = 3;\n    (*kPropertyIdShorthand)[kPropertyIDOutline] = 3;\n    (*kPropertyIdShorthand)[kPropertyIDFlexFlow] = 2;\n    (*kPropertyIdShorthand)[kPropertyIDTransition] = 5;\n    (*kPropertyIdShorthand)[kPropertyIDMask] = 8;\n    (*kPropertyIdShorthand)[kPropertyIDAnimation] = 9;\n  });\n  if (id >= kPropertyStart && id <= kPropertyEnd) {\n    return (*kPropertyIdShorthand)[id];\n  }\n  return 0;\n}\n\n// TODO: Need to auto generate from css define\nconst std::unordered_set<CSSPropertyID> shorthandCSSProperties{\n    kPropertyIDPadding,      kPropertyIDMargin,      kPropertyIDFlex,\n    kPropertyIDBackground,   kPropertyIDBorder,      kPropertyIDBorderWidth,\n    kPropertyIDBorderRadius, kPropertyIDBorderColor, kPropertyIDBorderStyle,\n    kPropertyIDBorderRight,  kPropertyIDBorderLeft,  kPropertyIDBorderTop,\n    kPropertyIDBorderBottom, kPropertyIDOutline,     kPropertyIDFlexFlow,\n    kPropertyIDTransition,   kPropertyIDMask,        kPropertyIDAnimation};\n\nbool CSSProperty::IsShorthandProperty(CSSPropertyID id) {\n  return shorthandCSSProperties.find(id) != shorthandCSSProperties.end();\n}\n\nCSSPropertyID CSSProperty::GetTimingOptionsPropertyID(\n    const base::static_string::GenericCacheKey& key) {\n#define DECLARE_ANIMATIONAPI_PROPERTY_NAME(name, alias) \\\n  static constexpr const char kAnimationAPIPropertyName##name[] = alias;\n  FOREACH_ALL_ANIMATIONAPI_PROPERTY(DECLARE_ANIMATIONAPI_PROPERTY_NAME)\n#undef DECLARE_ANIMATIONAPI_PROPERTY_NAME\n\n  const static base::NoDestructor<CSSPropertyNameToIDMap>\n      kAnimationPropertyNameMapping{{\n#define DECLARE_PROPERTY_NAME(name, alias) \\\n  {kAnimationAPIPropertyName##name, kPropertyID##name},\n          FOREACH_ALL_ANIMATIONAPI_PROPERTY(DECLARE_PROPERTY_NAME)\n#undef DECLARE_PROPERTY_NAME\n      }};\n  auto it = kAnimationPropertyNameMapping->find(key);\n  return it != kAnimationPropertyNameMapping->end() ? it->second : kPropertyEnd;\n}\n\nconst std::unordered_map<std::string, std::string>&\nCSSProperty::GetComputeStyleMap() {\n  static const base::NoDestructor<std::unordered_map<std::string, std::string>>\n      kComputeStyleMap{{\n#define DECLARE_PROPERTY_ID(name, c, value) {c, value},\n          FOREACH_ALL_PROPERTY(DECLARE_PROPERTY_ID)\n#undef DECLARE_PROPERTY_ID\n              {\"\", \"\"}}};\n  return *kComputeStyleMap;\n}\n\nbool CSSProperty::IsInspectorFilteredProperty(CSSPropertyID id) {\n  return inspectorFilteredProperties.find(id) !=\n         inspectorFilteredProperties.end();\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_property.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_PROPERTY_H_\n#define CORE_RENDERER_CSS_CSS_PROPERTY_H_\n\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n\n#include \"base/include/linked_hash_map.h\"\n#include \"base/include/no_destructor.h\"\n#include \"base/include/value/base_string.h\"\n#include \"base/include/vector.h\"\n#include \"core/renderer/css/css_property_id.h\"\n#include \"core/renderer/css/css_value.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nusing PseudoState = uint32_t;\nstatic constexpr PseudoState kPseudoStateNone = 0;\nstatic constexpr PseudoState kPseudoStateHover = 1;\nstatic constexpr PseudoState kPseudoStateHoverTransition = 1 << 1;\nstatic constexpr PseudoState kPseudoStateActive = 1 << 3;\nstatic constexpr PseudoState kPseudoStateActiveTransition = 1 << 4;\nstatic constexpr PseudoState kPseudoStateFocus = 1 << 6;\nstatic constexpr PseudoState kPseudoStateFocusTransition = 1 << 7;\nstatic constexpr PseudoState kPseudoStatePlaceHolder = 1 << 8;\nstatic constexpr PseudoState kPseudoStateBefore = 1 << 9;\nstatic constexpr PseudoState kPseudoStateAfter = 1 << 10;\nstatic constexpr PseudoState kPseudoStateSelection = 1 << 11;\n\n#define FOREACH_ALL_ANIMATIONAPI_PROPERTY(V) \\\n  V(AnimationDuration, \"duration\")           \\\n  V(AnimationDelay, \"delay\")                 \\\n  V(AnimationIterationCount, \"iterations\")   \\\n  V(AnimationFillMode, \"fill\")               \\\n  V(AnimationTimingFunction, \"easing\")       \\\n  V(AnimationDirection, \"direction\")         \\\n  V(AnimationPlayState, \"play-state\")\n\n#define ALL_ANIMATABLE_PROPERTY_ID                                          \\\n  tasm::kPropertyIDTop, tasm::kPropertyIDLeft, tasm::kPropertyIDRight,      \\\n      tasm::kPropertyIDBottom, tasm::kPropertyIDWidth,                      \\\n      tasm::kPropertyIDHeight, tasm::kPropertyIDBackgroundColor,            \\\n      tasm::kPropertyIDColor, tasm::kPropertyIDOpacity,                     \\\n      tasm::kPropertyIDBorderLeftColor, tasm::kPropertyIDBorderRightColor,  \\\n      tasm::kPropertyIDBorderTopColor, tasm::kPropertyIDBorderBottomColor,  \\\n      tasm::kPropertyIDBorderLeftWidth, tasm::kPropertyIDBorderRightWidth,  \\\n      tasm::kPropertyIDBorderTopWidth, tasm::kPropertyIDBorderBottomWidth,  \\\n      tasm::kPropertyIDPaddingLeft, tasm::kPropertyIDPaddingRight,          \\\n      tasm::kPropertyIDPaddingTop, tasm::kPropertyIDPaddingBottom,          \\\n      tasm::kPropertyIDMarginLeft, tasm::kPropertyIDMarginRight,            \\\n      tasm::kPropertyIDMarginTop, tasm::kPropertyIDMarginBottom,            \\\n      tasm::kPropertyIDMaxWidth, tasm::kPropertyIDMinWidth,                 \\\n      tasm::kPropertyIDMaxHeight, tasm::kPropertyIDMinHeight,               \\\n      tasm::kPropertyIDFlexGrow, tasm::kPropertyIDFlexBasis,                \\\n      tasm::kPropertyIDFilter, tasm::kPropertyIDTransform,                  \\\n      tasm::kPropertyIDOffsetDistance, tasm::kPropertyIDBackgroundPosition, \\\n      tasm::kPropertyIDTransformOrigin\n\n#define FOREACH_NEW_ANIMATOR_PROPERTY(V)              \\\n  V(kPropertyIDLeft, kLeft)                           \\\n  V(kPropertyIDTop, kTop)                             \\\n  V(kPropertyIDRight, kRight)                         \\\n  V(kPropertyIDBottom, kBottom)                       \\\n  V(kPropertyIDWidth, kWidth)                         \\\n  V(kPropertyIDHeight, kHeight)                       \\\n  V(kPropertyIDOpacity, kOpacity)                     \\\n  V(kPropertyIDBackgroundColor, kBackgroundColor)     \\\n  V(kPropertyIDColor, kColor)                         \\\n  V(kPropertyIDMaxWidth, kMaxWidth)                   \\\n  V(kPropertyIDMinWidth, kMinWidth)                   \\\n  V(kPropertyIDMaxHeight, kMaxHeight)                 \\\n  V(kPropertyIDMinHeight, kMinHeight)                 \\\n  V(kPropertyIDMarginLeft, kMarginLeft)               \\\n  V(kPropertyIDMarginRight, kMarginRight)             \\\n  V(kPropertyIDMarginTop, kMarginTop)                 \\\n  V(kPropertyIDMarginBottom, kMarginBottom)           \\\n  V(kPropertyIDPaddingLeft, kPaddingLeft)             \\\n  V(kPropertyIDPaddingRight, kPaddingRight)           \\\n  V(kPropertyIDPaddingTop, kPaddingTop)               \\\n  V(kPropertyIDPaddingBottom, kPaddingBottom)         \\\n  V(kPropertyIDBorderLeftWidth, kBorderLeftWidth)     \\\n  V(kPropertyIDBorderRightWidth, kBorderRightWidth)   \\\n  V(kPropertyIDBorderTopWidth, kBorderTopWidth)       \\\n  V(kPropertyIDBorderBottomWidth, kBorderBottomWidth) \\\n  V(kPropertyIDBorderLeftColor, kBorderLeftColor)     \\\n  V(kPropertyIDBorderRightColor, kBorderRightColor)   \\\n  V(kPropertyIDBorderTopColor, kBorderTopColor)       \\\n  V(kPropertyIDBorderBottomColor, kBorderBottomColor) \\\n  V(kPropertyIDFlexGrow, kFlexGrow)                   \\\n  V(kPropertyIDFlexBasis, kFlexBasis)                 \\\n  V(kPropertyIDFilter, kFilter)                       \\\n  V(kPropertyIDTransform, kTransform)                 \\\n  V(kPropertyIDTransformOrigin, kTransformOrigin)     \\\n  V(kPropertyIDOffsetDistance, kOffsetDistance)       \\\n  V(kPropertyIDBackgroundPosition, kBackgroundPosition)\n\n// Define macro for direction aware property, {css_id, is_logic_style, css_id in\n// ltr mode, css_id in rtl mode}\n// TODO(zhouzhitao): unify logic with radon element, use this macro to replace\n// RTLDirectionMapping defined in dynamic_css_style_manager\n#define FOREACH_DIRECTION_MAPPING_PROPERTY(V)                                  \\\n  V(MarginInlineStart, true, kPropertyIDMarginLeft, kPropertyIDMarginRight)    \\\n  V(MarginInlineEnd, true, kPropertyIDMarginRight, kPropertyIDMarginLeft)      \\\n  V(PaddingInlineStart, true, kPropertyIDPaddingLeft, kPropertyIDPaddingRight) \\\n  V(PaddingInlineEnd, true, kPropertyIDPaddingRight, kPropertyIDPaddingLeft)   \\\n  V(BorderInlineStartWidth, true, kPropertyIDBorderLeftWidth,                  \\\n    kPropertyIDBorderRightWidth)                                               \\\n  V(BorderInlineEndWidth, true, kPropertyIDBorderRightWidth,                   \\\n    kPropertyIDBorderLeftWidth)                                                \\\n  V(BorderInlineStartStyle, true, kPropertyIDBorderLeftStyle,                  \\\n    kPropertyIDBorderRightStyle)                                               \\\n  V(BorderInlineEndStyle, true, kPropertyIDBorderRightStyle,                   \\\n    kPropertyIDBorderLeftStyle)                                                \\\n  V(BorderInlineStartColor, true, kPropertyIDBorderLeftColor,                  \\\n    kPropertyIDBorderRightColor)                                               \\\n  V(BorderInlineEndColor, true, kPropertyIDBorderRightColor,                   \\\n    kPropertyIDBorderLeftColor)                                                \\\n  V(BorderStartStartRadius, true, kPropertyIDBorderTopLeftRadius,              \\\n    kPropertyIDBorderTopRightRadius)                                           \\\n  V(BorderStartEndRadius, true, kPropertyIDBorderTopRightRadius,               \\\n    kPropertyIDBorderTopLeftRadius)                                            \\\n  V(BorderEndStartRadius, true, kPropertyIDBorderBottomLeftRadius,             \\\n    kPropertyIDBorderBottomRightRadius)                                        \\\n  V(BorderEndEndRadius, true, kPropertyIDBorderBottomRightRadius,              \\\n    kPropertyIDBorderBottomLeftRadius)                                         \\\n  V(RelativeAlignInlineStart, true, kPropertyIDRelativeAlignLeft,              \\\n    kPropertyIDRelativeAlignRight)                                             \\\n  V(RelativeAlignInlineEnd, true, kPropertyIDRelativeAlignRight,               \\\n    kPropertyIDRelativeAlignLeft)                                              \\\n  V(RelativeInlineStartOf, true, kPropertyIDRelativeLeftOf,                    \\\n    kPropertyIDRelativeRightOf)                                                \\\n  V(RelativeInlineEndOf, true, kPropertyIDRelativeRightOf,                     \\\n    kPropertyIDRelativeLeftOf)                                                 \\\n  V(InsetInlineStart, true, kPropertyIDLeft, kPropertyIDRight)                 \\\n  V(InsetInlineEnd, true, kPropertyIDRight, kPropertyIDLeft)                   \\\n  V(MarginLeft, false, kPropertyIDMarginLeft, kPropertyIDMarginRight)          \\\n  V(MarginRight, false, kPropertyIDMarginRight, kPropertyIDMarginLeft)         \\\n  V(Left, false, kPropertyIDLeft, kPropertyIDRight)                            \\\n  V(Right, false, kPropertyIDRight, kPropertyIDLeft)                           \\\n  V(PaddingLeft, false, kPropertyIDPaddingLeft, kPropertyIDPaddingRight)       \\\n  V(PaddingRight, false, kPropertyIDPaddingRight, kPropertyIDPaddingLeft)      \\\n  V(BorderLeftWidth, false, kPropertyIDBorderLeftWidth,                        \\\n    kPropertyIDBorderRightWidth)                                               \\\n  V(BorderRightWidth, false, kPropertyIDBorderRightWidth,                      \\\n    kPropertyIDBorderLeftWidth)                                                \\\n  V(BorderLeftStyle, false, kPropertyIDBorderLeftStyle,                        \\\n    kPropertyIDBorderRightStyle)                                               \\\n  V(BorderRightStyle, false, kPropertyIDBorderRightStyle,                      \\\n    kPropertyIDBorderLeftStyle)                                                \\\n  V(BorderLeftColor, false, kPropertyIDBorderLeftColor,                        \\\n    kPropertyIDBorderRightColor)                                               \\\n  V(BorderRightColor, false, kPropertyIDBorderRightColor,                      \\\n    kPropertyIDBorderLeftColor)                                                \\\n  V(BorderTopLeftRadius, false, kPropertyIDBorderTopLeftRadius,                \\\n    kPropertyIDBorderTopRightRadius)                                           \\\n  V(BorderTopRightRadius, false, kPropertyIDBorderTopRightRadius,              \\\n    kPropertyIDBorderTopLeftRadius)                                            \\\n  V(BorderBottomLeftRadius, false, kPropertyIDBorderBottomLeftRadius,          \\\n    kPropertyIDBorderBottomRightRadius)                                        \\\n  V(BorderBottomRightRadius, false, kPropertyIDBorderBottomRightRadius,        \\\n    kPropertyIDBorderBottomLeftRadius)                                         \\\n  V(RelativeAlignLeft, false, kPropertyIDRelativeAlignLeft,                    \\\n    kPropertyIDRelativeAlignRight)                                             \\\n  V(RelativeAlignRight, false, kPropertyIDRelativeAlignRight,                  \\\n    kPropertyIDRelativeAlignLeft)                                              \\\n  V(RelativeLeftOf, false, kPropertyIDRelativeLeftOf,                          \\\n    kPropertyIDRelativeRightOf)                                                \\\n  V(RelativeRightOf, false, kPropertyIDRelativeRightOf,                        \\\n    kPropertyIDRelativeLeftOf)\n\n// This key-policy class specifies that the key-value storage method for\n// `StyleMap` is in the form `kkkvvv...`, meaning the key is stored\n// independently and used as a hash value to speed up lookups. Simultaneously,\n// `assign_existing_for_merge` configures the `Merge()` interface to assign\n// values to existing keys. This is because, by default, the `Merge()`\n// interface of `base::LinearFlatMap` behaves the same as `std::unordered_map`,\n// skipping existing keys.\nstruct MapKeyPolicyCSSPropertyID\n    : public base::MapKeyPolicyConsecutiveIntegers<CSSPropertyID> {\n  static constexpr auto assign_existing_for_merge = true;\n};\n\nusing StyleMap =\n    base::LinearFlatMap<CSSPropertyID, CSSValue, MapKeyPolicyCSSPropertyID>;\nstatic_assert(StyleMap::container_type::is_trivially_relocatable);\n\nusing CSSVariableMap = base::LinearFlatMap<base::String, base::String>;\nusing CSSValueMap = base::LinearFlatMap<base::String, tasm::CSSValue>;\nusing ParsedStyles = std::pair<StyleMap, CSSVariableMap>;\n// TODO(yuyang), choose proper map type\nusing ParsedStylesMap =\n    std::unordered_map<std::string, std::shared_ptr<ParsedStyles>>;\n\nusing AirCompStylesMap =\n    std::unordered_map<std::string, std::shared_ptr<StyleMap>>;\nusing AirParsedStylesMap = std::unordered_map<std::string, AirCompStylesMap>;\n\nusing RawStyleMap = StyleMap;\nusing RawLepusStyleMap =\n    base::LinearFlatMap<CSSPropertyID, lepus::Value, MapKeyPolicyCSSPropertyID>;\nstatic_assert(RawLepusStyleMap::container_type::is_trivially_relocatable);\n\nconstexpr int kCSSPropertyCount = kPropertyEnd;\n\n/* Sometimes, for example, when setting inline styles on nodes one by one\n through the render function, we cannot get the exact number of styles, so we\n provide a fuzzy initial capacity for the StyleMap that stores these styles.\n For most scenarios, we can ensure that the StyleMap stores data in the same\n continuous memory without wasting too much memory.\n This is just a magic number balanced between memory usage and performance.\n */\nconstexpr size_t kCSSStyleMapFuzzyAllocationSize = 6;\n\nclass LYNX_EXPORT_FOR_DEVTOOL CSSProperty {\n public:\n  // base::String, const char* and std::string could be implicitly converted\n  // to base::static_string::GenericCacheKey very cheaply.\n  static CSSPropertyID GetPropertyID(\n      const base::static_string::GenericCacheKey& key);\n\n  static const base::static_string::GenericCache& GetPropertyName(\n      CSSPropertyID id);\n\n  static const char* GetPropertyNameCStr(CSSPropertyID id) {\n    return GetPropertyName(id).c_str();\n  }\n\n  // Get total count of properties after parsing [id] if [id] is a shorthand\n  // one. If [id] is not a shorthand property this function returns 0.\n  static size_t GetShorthandExpand(CSSPropertyID id);\n\n  // Determine whether [id] is a shorthand property.\n  static bool IsShorthandProperty(CSSPropertyID id);\n\n  // Input map may contain shorthand properties. This function calculates total\n  // count of properties after parsing the whole map.\n  template <class Map>\n  static typename std::enable_if<\n      std::is_same_v<typename Map::key_type, CSSPropertyID>, size_t>::type\n  GetTotalParsedStyleCountFromMap(const Map& map) {\n    // Shorthand raw styles are decomposed to multiple ones and precalculate\n    // reserving count for target map from source map.\n    size_t reserve_count = map.size();\n    for (const auto& kv_pair : map) {\n      if (auto expand = GetShorthandExpand(kv_pair.first); expand > 0) {\n        reserve_count += expand - 1;\n      }\n    }\n    return reserve_count;\n  }\n\n  template <class Array, class Trait = typename Array::TraitID>\n  static size_t GetTotalParsedStyleCountFromArray(const Array* array,\n                                                  size_t size) {\n    size_t reserve_count = size;\n    for (size_t i = 0; i < size; i++) {\n      if (auto expand = GetShorthandExpand(Trait::GetPropertyID(array[i]));\n          expand > 0) {\n        reserve_count += expand - 1;\n      }\n    }\n    return reserve_count;\n  }\n\n  static size_t GetTotalParsedStyleCountFromArray(const CSSPropertyID* array,\n                                                  size_t size) {\n    size_t reserve_count = size;\n    for (size_t i = 0; i < size; i++) {\n      if (auto expand = GetShorthandExpand(array[i]); expand > 0) {\n        reserve_count += expand - 1;\n      }\n    }\n    return reserve_count;\n  }\n\n  static inline bool IsPropertyValid(\n      const base::static_string::GenericCacheKey& name) {\n    return IsPropertyValid(GetPropertyID(name));\n  }\n\n  static inline bool IsPropertyValid(CSSPropertyID id) {\n    return id > kPropertyStart && id < kPropertyEnd;\n  }\n\n  static inline bool IsCustomProperty(const char* name, uint32_t len) {\n    return len > 2 && name[0] == '-' && name[1] == '-';\n  }\n\n  // When using Element animation api, the timing options's keys are not\n  // standard css expressions. In order to get the corresponding PropertyID, add\n  // the following function.\n  static CSSPropertyID GetTimingOptionsPropertyID(\n      const base::static_string::GenericCacheKey& key);\n\n  static inline bool IsTransitionProps(CSSPropertyID id) {\n    return id >= CSSPropertyID::kPropertyIDTransition &&\n           id <= CSSPropertyID::kPropertyIDTransitionTimingFunction;\n  }\n\n  static inline bool IsKeyframeProps(CSSPropertyID id) {\n    return id >= CSSPropertyID::kPropertyIDAnimation &&\n           id <= CSSPropertyID::kPropertyIDAnimationPlayState;\n  }\n\n private:\n  CSSProperty() = delete;\n\n public:\n  static const std::unordered_map<std::string, std::string>&\n  GetComputeStyleMap();\n  static bool IsInspectorFilteredProperty(CSSPropertyID id);\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\nnamespace std {\ntemplate <>\nstruct hash<lynx::tasm::CSSPropertyID> {\n  std::size_t operator()(const lynx::tasm::CSSPropertyID& k) const {\n    return static_cast<std::size_t>(k);\n  }\n};\n}  // namespace std\n\n#endif  // CORE_RENDERER_CSS_CSS_PROPERTY_H_\n"
  },
  {
    "path": "core/renderer/css/css_property_bitset.h",
    "content": "// Copyright 2020 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_PROPERTY_BITSET_H_\n#define CORE_RENDERER_CSS_CSS_PROPERTY_BITSET_H_\n\n#include <algorithm>\n#include <array>\n#include <cassert>\n#include <cstdint>\n#include <iostream>\n\n#include \"base/include/log/logging.h\"\n#include \"core/renderer/css/css_property_id.h\"\n\n// clang-format off\n#if defined(__clang__) && __has_feature(unsafe_buffer_usage)\n// Disable hwasan checking for unsafe buffer accesses.\n// Disabling `clang-format` allows each `_Pragma` to be on its own line, as\n// recommended by https://gcc.gnu.org/onlinedocs/cpp/Pragmas.html.\n#define UNSAFE_BUFFERS(...)                  \\\n  _Pragma(\"clang unsafe_buffer_usage begin\") \\\n      __VA_ARGS__                            \\\n  _Pragma(\"clang unsafe_buffer_usage end\")\n#else\n#define UNSAFE_BUFFERS(...) __VA_ARGS__\n#endif\n// clang-format on\n\n#if !defined(UNSAFE_TODO)\n#define UNSAFE_TODO(...) UNSAFE_BUFFERS(__VA_ARGS__)\n#endif\n\n#if __cplusplus >= 202002L\n#include <bit>\n#elif defined(__GNUC__) || defined(__clang__)\n#elif defined(_MSC_VER)\n#include <intrin.h>\n#endif\n\nnamespace lynx {\nnamespace tasm {\n\n#if __cplusplus >= 202002L\ninline int CountrZero(uint64_t n) { return std::countr_zero(n); }\n#elif defined(__GNUC__) || defined(__clang__)\ninline int CountrZero(uint64_t n) { return __builtin_ctzll(n); }\n#elif defined(_MSC_VER)\ninline int CountrZero(uint64_t n) {\n  unsigned long index;\n  _BitScanForward64(&index, n);\n  return static_cast<int>(index);\n}\n#else\n#error \"Compiler not supported for CountrZero. Please add a fallback.\"\n#endif\n\n// A bitset designed for CSSPropertyIDs.\ntemplate <size_t kBits>\nclass CSSBitsetBase {\n public:\n  static_assert(kBits <= kPropertyEnd,\n                \"Bit count must not exceed kPropertyEnd\");\n  static_assert(kBits > 0, \"Iterator assumes at least one chunk.\");\n\n  static const size_t kChunks = (kBits + 63) / 64;\n\n  CSSBitsetBase() : chunks_() {}\n  CSSBitsetBase(const CSSBitsetBase<kBits>& o) { *this = o; }\n\n  // This slightly weird construction helps Clang make an actual\n  // compile-time static value, until we have constinit.\n  template <int N>\n  explicit constexpr CSSBitsetBase(const CSSPropertyID (&list)[N])\n      : chunks_(CreateChunks(list)) {}\n\n  CSSBitsetBase& operator=(const CSSBitsetBase& o) = default;\n\n  bool operator==(const CSSBitsetBase& o) const { return chunks_ == o.chunks_; }\n  bool operator!=(const CSSBitsetBase& o) const { return !(*this == o); }\n\n  inline void Set(CSSPropertyID id) {\n    size_t bit = static_cast<size_t>(static_cast<unsigned>(id));\n    UNSAFE_TODO(chunks_.data()[bit / 64]) |= (1ull << (bit % 64));\n  }\n\n  inline void Reset(CSSPropertyID id) {\n    size_t bit = static_cast<size_t>(static_cast<unsigned>(id));\n    UNSAFE_TODO(chunks_.data()[bit / 64]) &= ~(1ull << (bit % 64));\n  }\n\n  inline void Or(CSSPropertyID id, bool v) {\n    size_t bit = static_cast<size_t>(static_cast<unsigned>(id));\n    UNSAFE_TODO(chunks_.data()[bit / 64]) |=\n        (static_cast<uint64_t>(v) << (bit % 64));\n  }\n\n  void And(const CSSBitsetBase& other) {\n    for (size_t i = 0; i < kChunks; ++i) {\n      UNSAFE_TODO(chunks_.data()[i] &= other.chunks_.data()[i]);\n    }\n  }\n\n  void Xor(const CSSBitsetBase& other) {\n    for (size_t i = 0; i < kChunks; ++i) {\n      UNSAFE_TODO(chunks_.data()[i] ^= other.chunks_.data()[i]);\n    }\n  }\n\n  void Or(const CSSBitsetBase& other) {\n    for (size_t i = 0; i < kChunks; ++i) {\n      UNSAFE_TODO(chunks_.data()[i] |= other.chunks_.data()[i]);\n    }\n  }\n\n  CSSBitsetBase& operator&=(const CSSBitsetBase& other) {\n    And(other);\n    return *this;\n  }\n\n  CSSBitsetBase& operator^=(const CSSBitsetBase& other) {\n    Xor(other);\n    return *this;\n  }\n\n  CSSBitsetBase& operator|=(const CSSBitsetBase& other) {\n    Or(other);\n    return *this;\n  }\n\n  inline bool Has(CSSPropertyID id) const {\n    size_t bit = static_cast<size_t>(static_cast<unsigned>(id));\n    return UNSAFE_TODO(chunks_.data()[bit / 64]) & (1ull << (bit % 64));\n  }\n\n  inline bool HasAny() const {\n    for (uint64_t chunk : chunks_) {\n      if (chunk) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  inline void Reset() {\n    UNSAFE_TODO(std::memset(chunks_.data(), 0, sizeof(chunks_)));\n  }\n\n  // Yields the CSSPropertyIDs which are set.\n  class Iterator {\n   public:\n    // Only meant for internal use (from begin() or end()).\n    Iterator(const uint64_t* chunks, size_t chunk_index, size_t index)\n        : chunks_(chunks),\n          index_(index),\n          chunk_index_(chunk_index),\n          chunk_(chunks_[0]) {\n      DCHECK(index == 0 || index == kBits);\n      if (index < kBits) {\n        ++*this;  // Go to the first set bit.\n      }\n    }\n\n    inline void operator++() {\n      // If there are no more bits set in this chunk,\n      // skip to the next nonzero chunk (if any exists).\n      while (!chunk_) {\n        if (++chunk_index_ >= kChunks) {\n          index_ = kBits;\n          return;\n        }\n        chunk_ = UNSAFE_TODO(chunks_[chunk_index_]);\n      }\n      index_ = chunk_index_ * 64 + CountrZero(chunk_);\n      chunk_ &= chunk_ - 1;  // Clear the lowest bit.\n    }\n\n    inline CSSPropertyID operator*() const {\n      return static_cast<CSSPropertyID>(index_);\n    }\n\n    inline bool operator==(const Iterator& o) const {\n      return index_ == o.index_;\n    }\n    inline bool operator!=(const Iterator& o) const {\n      return index_ != o.index_;\n    }\n\n   private:\n    const uint64_t* chunks_;\n    // The current bit index this Iterator is pointing to. Note that this is\n    // the \"global\" index, i.e. it has the range [0, kBits]. (It is not a local\n    // index with range [0, 64]).\n    //\n    // Never exceeds kBits.\n    size_t index_ = 0;\n    // The current chunk index this Iterator is pointing to.\n    // Points to kChunks if we are done.\n    size_t chunk_index_ = 0;\n    // The iterator works by \"pre-fetching\" the current chunk (corresponding\n    // (to the current index), and removing its bits one by one.\n    // This is not used (contains junk) for the end() iterator.\n    uint64_t chunk_ = 0;\n  };\n\n  Iterator begin() const { return Iterator(chunks_.data(), 0, 0); }\n  Iterator end() const { return Iterator(chunks_.data(), kChunks, kBits); }\n\n private:\n  std::array<uint64_t, kChunks> chunks_;\n\n  template <int N>\n  static constexpr std::array<uint64_t, kChunks> CreateChunks(\n      const CSSPropertyID (&list)[N]) {\n    std::array<uint64_t, kChunks> chunks{};\n    for (CSSPropertyID id : list) {\n      unsigned bit = static_cast<unsigned>(id);\n      chunks[bit / 64] |= uint64_t{1} << (bit % 64);\n    }\n    return chunks;\n  }\n};\n\ntemplate <size_t kBits>\ninline CSSBitsetBase<kBits> operator&(CSSBitsetBase<kBits> lhs,\n                                      const CSSBitsetBase<kBits>& rhs) {\n  lhs &= rhs;\n  return lhs;\n}\n\ntemplate <size_t kBits>\ninline CSSBitsetBase<kBits> operator^(CSSBitsetBase<kBits> lhs,\n                                      const CSSBitsetBase<kBits>& rhs) {\n  lhs ^= rhs;\n  return lhs;\n}\n\ntemplate <size_t kBits>\ninline CSSBitsetBase<kBits> operator|(CSSBitsetBase<kBits> lhs,\n                                      const CSSBitsetBase<kBits>& rhs) {\n  lhs |= rhs;\n  return lhs;\n}\n\nusing CSSIDBitset = CSSBitsetBase<kPropertyEnd>;\n}  // namespace tasm\n}  // namespace lynx\n#endif  // CORE_RENDERER_CSS_CSS_PROPERTY_BITSET_H_\n"
  },
  {
    "path": "core/renderer/css/css_property_bitset_unittest.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_property_bitset.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(CSSIDBitsetTest, SetAndHas) {\n  CSSIDBitset bits;\n  EXPECT_FALSE(bits.Has(kPropertyIDTop));\n  bits.Set(kPropertyIDTop);\n  EXPECT_TRUE(bits.Has(kPropertyIDTop));\n}\n\nTEST(CSSIDBitsetTest, HasAny) {\n  CSSIDBitset bits;\n  EXPECT_FALSE(bits.HasAny());\n  bits.Set(kPropertyIDTop);\n  EXPECT_TRUE(bits.HasAny());\n}\n\nTEST(CSSIDBitsetTest, Reset) {\n  CSSIDBitset bits;\n  EXPECT_FALSE(bits.HasAny());\n  bits.Set(kPropertyIDTop);\n  EXPECT_TRUE(bits.HasAny());\n  EXPECT_TRUE(bits.Has(kPropertyIDTop));\n  bits.Reset();\n  EXPECT_FALSE(bits.HasAny());\n  EXPECT_FALSE(bits.Has(kPropertyIDTop));\n}\n\nTEST(CSSIDBitsetTest, ResetById) {\n  CSSIDBitset bits;\n  bits.Set(kPropertyIDTop);\n  bits.Set(kPropertyIDLeft);\n  EXPECT_TRUE(bits.Has(kPropertyIDTop));\n  EXPECT_TRUE(bits.Has(kPropertyIDLeft));\n\n  bits.Reset(kPropertyIDTop);\n  EXPECT_FALSE(bits.Has(kPropertyIDTop));\n  EXPECT_TRUE(bits.Has(kPropertyIDLeft));\n}\n\nTEST(CSSIDBitsetTest, Iterator) {\n  CSSIDBitset bits;\n  bits.Set(kPropertyIDTop);\n  bits.Set(kPropertyIDLeft);\n  bits.Set(kPropertyIDFontSize);\n  bits.Set(kPropertyIDColor);\n\n  std::set<CSSPropertyID> expected_ids = {\n      kPropertyIDTop, kPropertyIDLeft, kPropertyIDFontSize, kPropertyIDColor};\n\n  for (CSSPropertyID id : bits) {\n    EXPECT_EQ(expected_ids.erase(id), 1);\n  }\n  EXPECT_TRUE(expected_ids.empty());\n}\n\nTEST(CSSIDBitsetTest, BitwiseOperators) {\n  CSSIDBitset bits1;\n  bits1.Set(kPropertyIDTop);\n  bits1.Set(kPropertyIDLeft);\n  bits1.Set(kPropertyIDColor);\n\n  CSSIDBitset bits2;\n  bits2.Set(kPropertyIDLeft);\n  bits2.Set(kPropertyIDFontSize);\n  bits2.Set(kPropertyIDColor);\n\n  // Test OR (|)\n  CSSIDBitset or_result = bits1 | bits2;\n  EXPECT_TRUE(or_result.Has(kPropertyIDTop));\n  EXPECT_TRUE(or_result.Has(kPropertyIDLeft));\n  EXPECT_TRUE(or_result.Has(kPropertyIDColor));\n  EXPECT_TRUE(or_result.Has(kPropertyIDFontSize));\n  EXPECT_FALSE(or_result.Has(kPropertyIDWidth));\n\n  // Test AND (&)\n  CSSIDBitset and_result = bits1 & bits2;\n  EXPECT_FALSE(and_result.Has(kPropertyIDTop));\n  EXPECT_TRUE(and_result.Has(kPropertyIDLeft));\n  EXPECT_TRUE(and_result.Has(kPropertyIDColor));\n  EXPECT_FALSE(and_result.Has(kPropertyIDFontSize));\n\n  // Test XOR (^)\n  CSSIDBitset xor_result = bits1 ^ bits2;\n  EXPECT_TRUE(xor_result.Has(kPropertyIDTop));\n  EXPECT_FALSE(xor_result.Has(kPropertyIDLeft));\n  EXPECT_FALSE(xor_result.Has(kPropertyIDColor));\n  EXPECT_TRUE(xor_result.Has(kPropertyIDFontSize));\n\n  // Test assignment operators\n  CSSIDBitset bits_copy = bits1;\n  bits_copy |= bits2;\n  EXPECT_EQ(bits_copy, or_result);\n\n  bits_copy = bits1;\n  bits_copy &= bits2;\n  EXPECT_EQ(bits_copy, and_result);\n\n  bits_copy = bits1;\n  bits_copy ^= bits2;\n  EXPECT_EQ(bits_copy, xor_result);\n}\n\nTEST(CSSIDBitsetTest, ConstexprConstructor) {\n  constexpr CSSPropertyID initial_properties[] = {\n      kPropertyIDTop, kPropertyIDLeft, kPropertyIDFontSize, kPropertyIDColor};\n\n  constexpr CSSIDBitset bits(initial_properties);\n\n  EXPECT_TRUE(bits.Has(kPropertyIDTop));\n  EXPECT_TRUE(bits.Has(kPropertyIDLeft));\n  EXPECT_TRUE(bits.Has(kPropertyIDFontSize));\n  EXPECT_TRUE(bits.Has(kPropertyIDColor));\n  EXPECT_FALSE(bits.Has(kPropertyIDWidth));\n  EXPECT_FALSE(bits.Has(kPropertyIDHeight));\n\n  // Verify with iterator\n  std::set<CSSPropertyID> expected_ids(std::begin(initial_properties),\n                                       std::end(initial_properties));\n  std::set<CSSPropertyID> actual_ids;\n  for (CSSPropertyID id : bits) {\n    actual_ids.insert(id);\n  }\n\n  EXPECT_EQ(expected_ids, actual_ids);\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_property_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/css_property.h\"\n\n#include \"core/renderer/css/css_property_id.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nextern const char* GetPropertyNameCStr(CSSPropertyID id);\n\nnamespace test {\n\nTEST(CSSProperty, GetComputeStyleMap) {\n  auto map = CSSProperty::GetComputeStyleMap();\n  EXPECT_EQ(map.size(), 226);\n}\n\nTEST(CSSProperty, GetPropertyID) {\n  EXPECT_EQ(CSSProperty::GetPropertyID(\"justify-content\"),\n            kPropertyIDJustifyContent);\n\n  std::string name = \"justify-content\";\n  EXPECT_EQ(CSSProperty::GetPropertyID(name), kPropertyIDJustifyContent);\n\n  base::String name_lepus(\"justify-content\");\n  EXPECT_EQ(CSSProperty::GetPropertyID(name_lepus), kPropertyIDJustifyContent);\n}\n\nTEST(CSSProperty, GetPropertyName) {\n  EXPECT_TRUE(\n      strcmp(CSSProperty::GetPropertyName(kPropertyIDJustifyContent).c_str(),\n             \"justify-content\") == 0);\n  EXPECT_EQ(CSSProperty::GetPropertyName(kPropertyIDJustifyContent).str(),\n            \"justify-content\");\n\n  base::String lepus_name =\n      CSSProperty::GetPropertyName(kPropertyIDJustifyContent);\n  EXPECT_TRUE(lepus_name == \"justify-content\");\n\n  EXPECT_TRUE(\n      strcmp(CSSProperty::GetPropertyNameCStr(kPropertyIDJustifyContent),\n             \"justify-content\") == 0);\n\n  EXPECT_TRUE(strcmp(GetPropertyNameCStr(kPropertyIDJustifyContent),\n                     \"justify-content\") == 0);\n\n  EXPECT_EQ(\n      CSSProperty::GetPropertyName(static_cast<CSSPropertyID>(0xFFFF)).str(),\n      \"\");\n}\n\nTEST(CSSProperty, IsInspectorFilteredProperty) {\n  const CSSPropertyID inspectorFilteredProperties[] = {\n      kPropertyIDBorder,\n      kPropertyIDBorderTop,\n      kPropertyIDBorderRight,\n      kPropertyIDBorderBottom,\n      kPropertyIDBorderLeft,\n      kPropertyIDMarginInlineStart,\n      kPropertyIDMarginInlineEnd,\n      kPropertyIDPaddingInlineStart,\n      kPropertyIDPaddingInlineEnd,\n      kPropertyIDBorderInlineStartWidth,\n      kPropertyIDBorderInlineEndWidth,\n      kPropertyIDBorderInlineStartColor,\n      kPropertyIDBorderInlineEndColor,\n      kPropertyIDBorderInlineStartStyle,\n      kPropertyIDBorderInlineEndStyle,\n      kPropertyIDBorderStartStartRadius,\n      kPropertyIDBorderEndStartRadius,\n      kPropertyIDBorderStartEndRadius,\n      kPropertyIDBorderEndEndRadius,\n      kPropertyIDFlex,\n      kPropertyIDFlexFlow,\n      kPropertyIDPadding,\n      kPropertyIDMargin,\n      kPropertyIDInsetInlineStart,\n      kPropertyIDInsetInlineEnd,\n      kPropertyIDBorderWidth,\n      kPropertyIDBackground,\n      kPropertyIDBorderColor,\n      kPropertyIDBorderStyle,\n      kPropertyIDOutline};\n  for (size_t i = 0;\n       i < sizeof(inspectorFilteredProperties) / sizeof(CSSPropertyID); i++) {\n    EXPECT_TRUE(CSSProperty::IsInspectorFilteredProperty(\n        inspectorFilteredProperties[i]));\n  }\n}\n\nTEST(CSSProperty, IsShorthandProperty) {\n  const CSSPropertyID shorthands[] = {\n      kPropertyIDPadding,      kPropertyIDMargin,      kPropertyIDFlex,\n      kPropertyIDBackground,   kPropertyIDBorder,      kPropertyIDBorderWidth,\n      kPropertyIDBorderRadius, kPropertyIDBorderColor, kPropertyIDBorderStyle,\n      kPropertyIDBorderRight,  kPropertyIDBorderLeft,  kPropertyIDBorderTop,\n      kPropertyIDBorderBottom, kPropertyIDOutline,     kPropertyIDFlexFlow,\n      kPropertyIDTransition,   kPropertyIDMask,        kPropertyIDAnimation};\n  for (size_t i = 0; i < sizeof(shorthands) / sizeof(CSSPropertyID); i++) {\n    EXPECT_TRUE(CSSProperty::IsShorthandProperty(shorthands[i]));\n  }\n}\n\nTEST(CSSProperty, IsCustomProperty) {\n  EXPECT_FALSE(CSSProperty::IsCustomProperty(\"\", 0));\n  EXPECT_FALSE(CSSProperty::IsCustomProperty(\"--\", 2));\n  EXPECT_TRUE(CSSProperty::IsCustomProperty(\"--x\", 3));\n  EXPECT_FALSE(CSSProperty::IsCustomProperty(\"custom-property\", 15));\n  EXPECT_TRUE(CSSProperty::IsCustomProperty(\"--custom-property\", 17));\n}\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_selector_constants.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_selector_constants.h\"\n\nnamespace lynx {\nnamespace tasm {}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_selector_constants.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_SELECTOR_CONSTANTS_H_\n#define CORE_RENDERER_CSS_CSS_SELECTOR_CONSTANTS_H_\n\nnamespace lynx {\nnamespace tasm {\n\n/*\n *  selectors            Example               Example Description\n *  .class               .intro      Select All Elements with class=\"intro\"\n *  #id                 #first       Select Elements with id=\"first\"\n *  element               view                Select All view Elements\n *  element, element   view, checkbox   Select All view, checkbox Elements\n *  :not           #foo:not(.bar)  Select Elements with id=\"foo\" but class!=bar\n *  ::placeholder    input::placeholder   Placeholder Text Style of input\n * Elements More Selectors to Expand\n */\n\nstatic constexpr const char kCSSSelectorClass[] = \".\";\nstatic constexpr const char kCSSSelectorID[] = \"#\";\nstatic constexpr const char kCSSSelectorSelection[] = \"::selection\";\nstatic constexpr const char kCSSSelectorNot[] = \":not\";\nstatic constexpr const char kCSSSelectorPlaceholder[] = \"::placeholder\";\nstatic constexpr const char kCSSSelectorAll[] = \"*\";\nstatic constexpr const char kCSSSelectorFirstChild[] = \":first-child\";\nstatic constexpr const char kCSSSelectorLastChild[] = \":last-child\";\nstatic constexpr const char kCSSSelectorPseudoFocus[] = \":focus\";\nstatic constexpr const char kCSSSelectorPseudoActive[] = \":active\";\nstatic constexpr const char kCSSSelectorPseudoHover[] = \":hover\";\n\n// TODO: add more\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_SELECTOR_CONSTANTS_H_\n"
  },
  {
    "path": "core/renderer/css/css_sheet.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_sheet.h\"\n\n#include \"core/renderer/css/css_selector_constants.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nCSSSheet::CSSSheet(const std::string& str)\n    : type_(0), selector_(str), parent_(nullptr) {\n  ConfirmType();\n}\n\nvoid CSSSheet::ConfirmType() {\n  if (selector_.empty()) return;\n\n  const auto& name = selector_.str();\n  constexpr const static char* kColon = \":\";\n\n  // TODO(yuyang), optimize this.\n  // If it is a pseudo-class or pseudo-element, it must contain ':'\n  if (name.find(kColon) != std::string::npos) {\n    if (name.find(kCSSSelectorPlaceholder) != std::string::npos) {\n      type_ |= PLACEHOLDER_SELECT;\n    } else if (name.find(kCSSSelectorFirstChild) != std::string::npos) {\n      type_ |= FIRST_CHILD_SELECT;\n    } else if (name.find(kCSSSelectorLastChild) != std::string::npos) {\n      type_ |= LAST_CHILD_SELECT;\n    } else if (name.find(kCSSSelectorNot) != std::string::npos) {\n      type_ |= NOT_SELECT;\n    } else if (name.find(kCSSSelectorSelection) != std::string::npos) {\n      type_ |= SELECTION_SELECT;\n    } else if (name.find(kCSSSelectorPseudoActive) != std::string::npos) {\n      type_ |= PSEUDO_ACTIVE_SELECT;\n    } else if (name.find(kCSSSelectorPseudoFocus) != std::string::npos) {\n      type_ |= PSEUDO_FOCUS_SELECT;\n    } else if (name.find(kCSSSelectorPseudoHover) != std::string::npos) {\n      type_ |= PSEUDO_HOVER_SELECT;\n    }\n  }\n\n  const auto& type = name.substr(0, 1);\n  if (type == kCSSSelectorClass) {\n    type_ |= CLASS_SELECT;\n    name_ = name.substr(1);\n  } else if (type == kCSSSelectorID) {\n    type_ |= ID_SELECT;\n    name_ = name.substr(1);\n  } else if (type == kCSSSelectorAll) {\n    type_ |= ALL_SELECT;\n    name_ = BASE_STATIC_STRING(kCSSSelectorAll);\n  } else {\n    type_ |= NAME_SELECT;\n    name_ = name;\n  }\n}\n\nbool CSSSheet::IsTouchPseudo() const {\n  return type_ &\n         (PSEUDO_ACTIVE_SELECT | PSEUDO_FOCUS_SELECT | PSEUDO_HOVER_SELECT);\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_sheet.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_SHEET_H_\n#define CORE_RENDERER_CSS_CSS_SHEET_H_\n\n#include <memory>\n#include <string>\n\n#include \"base/include/value/base_string.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass CSSSheet {\n public:\n  enum SheetType {\n    CLASS_SELECT = 1,\n    ID_SELECT = 1 << 1,\n    NAME_SELECT = 1 << 2,\n    // deprecated\n    AFTER_SELECT = 1 << 3,\n    // deprecated\n    BEFORE_SELECT = 1 << 4,\n    NOT_SELECT = 1 << 5,\n    PLACEHOLDER_SELECT = 1 << 6,\n    ALL_SELECT = 1 << 7,\n    FIRST_CHILD_SELECT = 1 << 8,\n    LAST_CHILD_SELECT = 1 << 9,\n    PSEUDO_FOCUS_SELECT = 1 << 10,\n    SELECTION_SELECT = 1 << 11,\n    PSEUDO_ACTIVE_SELECT = 1 << 12,\n    PSEUDO_HOVER_SELECT = 1 << 13,\n  };\n\n  CSSSheet(const std::string& str);\n  ~CSSSheet() {}\n\n  int GetType() { return type_; }\n\n  const base::String& GetSelector() { return selector_; }\n\n  const base::String& GetName() { return name_; }\n\n  void SetParent(std::shared_ptr<CSSSheet> ptr) { parent_ = ptr.get(); }\n\n  CSSSheet* GetParent() { return parent_; }\n\n  bool IsTouchPseudo() const;\n\n private:\n  // for desirialize\n  CSSSheet() {}\n  friend class TemplateBinaryWriter;\n  friend class TemplateBinaryReader;\n  friend class TemplateBinaryReaderSSR;\n  friend class LynxBinaryBaseCSSReader;\n\n  void ConfirmType();\n\n  int type_;\n  // Single Rule, like .info, view\n  base::String selector_;\n\n  // Characters after removing the rules, such as \"view\" and \"info\"\n  base::String name_;\n  // std::shared_ptr<CSSSheet> parent_;\n  CSSSheet* parent_;\n};\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_SHEET_H_\n"
  },
  {
    "path": "core/renderer/css/css_style_sheet_manager.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/css_style_sheet_manager.h\"\n\n#include \"base/trace/native/trace_event.h\"\n#include \"core/renderer/css/css_fragment.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace tasm {\n// app.ttss\nstatic uint32_t sBasicCSSId = 0;\n\nSharedCSSFragment* CSSStyleSheetManager::GetCSSStyleSheetForComponent(\n    int32_t id) {\n  // Actually this function can be fully replaced by\n  // GetCSSStyleSheet(id). And component_fragments_ can be deleted.\n  // No need to import self.\n  return GetCSSStyleSheet(id);\n}\n\nSharedCSSFragment* CSSStyleSheetManager::GetCSSStyleSheetForPage(int32_t id) {\n  if (enable_css_lazy_import_) {\n    return GetCSSStyleSheet(id);\n  } else {\n    if (enable_new_import_rule_) {\n      return GetCSSStyleSheet(id);\n    }\n    auto it = page_fragments_.find(id);\n    if (it != page_fragments_.end() && it->second->is_baked()) {\n      return it->second.get();\n    }\n    auto fragment = std::make_unique<SharedCSSFragment>(id, this);\n    fragment->ImportOtherFragment(GetCSSStyleSheet(sBasicCSSId));\n    if (id > 0) {\n      fragment->ImportOtherFragment(GetCSSStyleSheet(id));\n    }\n    fragment->MarkBaked();\n    auto ptr = fragment.get();\n    page_fragments_[id] = std::move(fragment);\n    return ptr;\n  }\n}\n\nSharedCSSFragment* CSSStyleSheetManager::GetCSSStyleSheet(int32_t id) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, STYLE_SHEET_MANAGER_GET_STYLE_SHEET);\n  SharedCSSFragment* fragment = GetSharedCSSFragmentById(id);\n  if (fragment == nullptr) {\n    if (delegate_ && delegate_->DecodeCSSFragmentById(id)) {\n      fragment = GetSharedCSSFragmentById(id);\n    } else {\n      return nullptr;\n    }\n  }\n  if (fragment == nullptr || fragment->is_baked()) {\n    return fragment;\n  }\n  FlatDependentCSS(fragment);\n  return fragment;\n}\n\nvoid CSSStyleSheetManager::FlatDependentCSS(SharedCSSFragment* fragment) {\n  const auto& dependents = fragment->dependent_ids();\n  if (fragment->enable_css_selector() && fix_css_import_rule_order_) {\n    std::for_each(dependents.begin(), dependents.end(), [&](int32_t id) {\n      fragment->ImportOtherFragment(GetCSSStyleSheet(id));\n    });\n  } else {\n    // FIXME(linxs:) Retaining the logic below to avoid breaking changes,\n    // although it is incorrect...\n    std::for_each(dependents.rbegin(), dependents.rend(), [&](int32_t id) {\n      fragment->ImportOtherFragment(GetCSSStyleSheet(id));\n    });\n  }\n  fragment->MarkBaked();\n}\n\nvoid CSSStyleSheetManager::FlattenAllCSSFragment() {\n  std::for_each(raw_fragments_->begin(), raw_fragments_->end(),\n                [this](const auto& fragment) {\n                  this->FlatDependentCSS(fragment.second.get());\n                });\n}\n\nvoid CSSStyleSheetManager::CopyFrom(const CSSStyleSheetManager& other) {\n  raw_fragments_ = other.raw_fragments_;\n  enable_new_import_rule_ = other.enable_new_import_rule_;\n}\n\nconst std::shared_ptr<CSSStyleSheetManager::CSSFragmentMap>&\nCSSStyleSheetManager::GetCSSFragmentMap() const {\n  return raw_fragments_;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_style_sheet_manager.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_STYLE_SHEET_MANAGER_H_\n#define CORE_RENDERER_CSS_CSS_STYLE_SHEET_MANAGER_H_\n\n#include <memory>\n#include <mutex>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n\n#include \"core/renderer/css/shared_css_fragment.h\"\n#include \"core/template_bundle/template_codec/binary_decoder/page_config.h\"\n#include \"core/template_bundle/template_codec/moulds.h\"\n#include \"core/template_bundle/template_codec/template_binary.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass CSSStyleSheetDelegate {\n public:\n  CSSStyleSheetDelegate() = default;\n  virtual ~CSSStyleSheetDelegate() = default;\n  virtual bool DecodeCSSFragmentById(int32_t fragmentId) = 0;\n};\n\nclass CSSStyleSheetManager {\n public:\n  using CSSFragmentMap =\n      std::unordered_map<int32_t, std::unique_ptr<SharedCSSFragment>>;\n\n  CSSStyleSheetManager(CSSStyleSheetDelegate* delegate)\n      : raw_fragments_(std::make_shared<CSSFragmentMap>()),\n        delegate_(delegate){};\n\n  SharedCSSFragment* GetCSSStyleSheetForComponent(int32_t id);\n  SharedCSSFragment* GetCSSStyleSheetForPage(int32_t id);\n  const CSSFragmentMap& raw_fragments() const { return *raw_fragments_; }\n  std::atomic_bool GetStopThread() const { return stop_thread_; }\n\n  void SetThreadStopFlag(bool stop_thread) { stop_thread_ = stop_thread; }\n\n  SharedCSSFragment* GetSharedCSSFragmentById(int32_t id) {\n    std::lock_guard<std::mutex> g_lock(fragment_mutex_);\n    decoded_fragment_.emplace(id);\n    auto fragment_iter = raw_fragments_->find(id);\n    return fragment_iter != raw_fragments_->end() ? fragment_iter->second.get()\n                                                  : nullptr;\n  }\n\n  bool IsSharedCSSFragmentDecoded(int32_t id) {\n    std::lock_guard<std::mutex> g_lock(fragment_mutex_);\n    if (decoded_fragment_.find(id) != decoded_fragment_.end()) {\n      return true;\n    }\n    return false;\n  }\n\n  void AddSharedCSSFragment(std::unique_ptr<SharedCSSFragment> fragment) {\n    std::lock_guard<std::mutex> g_lock(fragment_mutex_);\n    raw_fragments_->emplace(fragment->id(), std::move(fragment));\n  }\n\n  void ReplaceSharedCSSFragment(std::unique_ptr<SharedCSSFragment> fragment) {\n    std::lock_guard<std::mutex> g_lock(fragment_mutex_);\n    raw_fragments_->insert_or_assign(fragment->id(), std::move(fragment));\n  }\n\n  void RemoveSharedCSSFragment(int32_t id) {\n    std::lock_guard<std::mutex> g_lock(fragment_mutex_);\n    raw_fragments_->erase(id);\n  }\n\n  void SetEnableNewImportRule(bool enable) { enable_new_import_rule_ = enable; }\n\n  // Flatten all the css fragments, so that they will be read-only\n  void FlattenAllCSSFragment();\n\n  // only copy raw_fragments_\n  void CopyFrom(const CSSStyleSheetManager& other);\n\n  const std::shared_ptr<CSSFragmentMap>& GetCSSFragmentMap() const;\n\n  SharedCSSFragment* GetCSSStyleSheet(int32_t id);\n\n  void SetEnableCSSLazyImport(bool enable) { enable_css_lazy_import_ = enable; }\n\n  bool GetEnableCSSLazyImport() { return enable_css_lazy_import_; }\n\n  void SetFixCSSImportRuleOrder(bool enable) {\n    fix_css_import_rule_order_ = enable;\n  }\n\n  bool GetFixCSSImportRuleOrder() const { return fix_css_import_rule_order_; }\n\n private:\n  friend class TemplateBinaryReader;\n  friend class TemplateBinaryReaderSSR;\n  friend class LynxBinaryBaseCSSReader;\n  friend class LynxBinaryReader;\n\n  void FlatDependentCSS(SharedCSSFragment* fragment);\n\n  CSSRoute route_;\n  CSSFragmentMap page_fragments_;\n  // shared in pre-decoding\n  std::shared_ptr<CSSFragmentMap> raw_fragments_;\n  CSSStyleSheetDelegate* delegate_ = nullptr;\n  std::unordered_set<int> decoded_fragment_;\n  volatile std::atomic_bool stop_thread_ = false;\n  std::mutex fragment_mutex_;\n  bool enable_new_import_rule_ = false;\n\n  // enableCSSLazyImport default value is false.\n  bool enable_css_lazy_import_ = false;\n\n  bool fix_css_import_rule_order_{true};\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_STYLE_SHEET_MANAGER_H_\n"
  },
  {
    "path": "core/renderer/css/css_style_sheet_manager_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_style_sheet_manager.h\"\n\n#include <fstream>\n\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace testing {\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass CSSStyleSheetManagerTest : public ::testing::Test {\n public:\n  CSSStyleSheetManagerTest() = default;\n  ~CSSStyleSheetManagerTest() override = default;\n\n  void SetUp() override {\n    base::UIThread::Init();\n    LynxEnvConfig lynx_env_config(kWidth, kHeight, kDefaultLayoutsUnitPerPx,\n                                  kDefaultPhysicalPixelsPerLayoutUnit);\n    delegate = std::make_unique<::testing::NiceMock<test::MockTasmDelegate>>();\n    auto manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<MockPaintingContext>(), delegate.get(),\n        lynx_env_config);\n    tasm = std::make_unique<lynx::tasm::TemplateAssembler>(\n        *delegate.get(), std::move(manager), delegate.get(), 0);\n  }\n\n  std::unique_ptr<::testing::NiceMock<test::MockTasmDelegate>> delegate;\n  std::unique_ptr<TemplateAssembler> tasm;\n};\n\nTEST_F(CSSStyleSheetManagerTest, LoadTemplate) {\n  std::string path = \"../../core/renderer/css/testing/counter.js\";\n  std::ifstream istream(path, std::ios::in | std::ios::binary);\n  EXPECT_TRUE(istream.good());\n  std::vector<uint8_t> data((std::istreambuf_iterator<char>(istream)),\n                            std::istreambuf_iterator<char>());\n  auto pipeline_options = std::make_shared<PipelineOptions>();\n  tasm->LoadTemplate(path, std::move(data), nullptr, pipeline_options);\n  auto entry = tasm->FindEntry(DEFAULT_ENTRY_NAME);\n\n  auto manager = entry->GetStyleSheetManager();\n  EXPECT_EQ(manager->raw_fragments().size(), static_cast<size_t>(3));\n  // pm->css_id();\n  auto pf = manager->GetCSSStyleSheetForPage(2);\n  EXPECT_TRUE(pf);\n  EXPECT_TRUE(pf->is_baked());\n  // The fragment has merged with common.ttss\n  EXPECT_TRUE(pf->css().find(\".common\") != pf->css().end());\n  // cm->css_id()\n  auto cf = manager->GetCSSStyleSheetForComponent(1);\n  EXPECT_TRUE(cf);\n  EXPECT_TRUE(cf->is_baked());\n  // The component has no common selector\n  EXPECT_TRUE(cf->css().find(\".common\") == cf->css().end());\n}\n\nTEST_F(CSSStyleSheetManagerTest, Pseudo) {\n  // testing/pseudo\n  std::string path = \"../../core/renderer/css/testing/pseudo.js\";\n  std::ifstream istream(path, std::ios::in | std::ios::binary);\n  EXPECT_TRUE(istream.good());\n  std::vector<uint8_t> data((std::istreambuf_iterator<char>(istream)),\n                            std::istreambuf_iterator<char>());\n  auto pipeline_options = std::make_shared<PipelineOptions>();\n  tasm->LoadTemplate(path, std::move(data), nullptr, pipeline_options);\n  auto entry = tasm->FindEntry(DEFAULT_ENTRY_NAME);\n\n  auto manager = entry->GetStyleSheetManager();\n  EXPECT_TRUE(manager);\n  // cm->css_id()\n  auto cf = manager->GetCSSStyleSheetForPage(1);\n  cf = manager->GetCSSStyleSheetForComponent(1);\n  EXPECT_TRUE(cf);\n  EXPECT_TRUE(cf->HasPseudoStyle());\n  EXPECT_TRUE(cf->HasPseudoNotStyle());\n\n  EXPECT_EQ(cf->pseudo_map().size(), static_cast<size_t>(4));\n  EXPECT_TRUE(cf->GetPseudoStyle(\":not(.xxx)\"));\n  // FIXME: The cascade style should be '.box .container'\n  EXPECT_TRUE(cf->GetCascadeStyle(\".box.container\"));\n  EXPECT_TRUE(cf->GetFontFaceRule(\"roboto\")[0]);\n  EXPECT_TRUE(cf->GetKeyframesRule(\"opacity-ani\"));\n}\n\n// TODO(songshourui.null): enable this test after replace fontfaces.js\nTEST_F(CSSStyleSheetManagerTest, DISABLED_Fontfaces) {\n  // testing/fontfaces\n  std::string path = \"../../core/renderer/css/testing/fontfaces.js\";\n  std::ifstream istream(path, std::ios::in | std::ios::binary);\n  EXPECT_TRUE(istream.good());\n  std::vector<uint8_t> data((std::istreambuf_iterator<char>(istream)),\n                            std::istreambuf_iterator<char>());\n  auto pipeline_options = std::make_shared<PipelineOptions>();\n  tasm->LoadTemplate(path, std::move(data), nullptr, pipeline_options);\n  auto entry = tasm->FindEntry(DEFAULT_ENTRY_NAME);\n\n  auto manager = entry->GetStyleSheetManager();\n  EXPECT_TRUE(manager);\n  // cm->css_id()\n  auto cf = manager->GetCSSStyleSheetForPage(1);\n  EXPECT_TRUE(cf);\n  EXPECT_FALSE(cf->HasPseudoStyle());\n  EXPECT_FALSE(cf->HasPseudoNotStyle());\n\n  auto token_list = cf->GetFontFaceRule(\"DroidSerif\");\n  EXPECT_EQ(token_list.size(), static_cast<size_t>(4));\n  auto token = token_list[0];\n  auto attrs = token->second;\n  EXPECT_EQ(attrs.size(), static_cast<size_t>(4));\n  EXPECT_EQ(attrs[\"font-weight\"], \"normal\");\n  EXPECT_EQ(attrs[\"font-style\"], \"normal\");\n  EXPECT_EQ(attrs[\"font-family\"], \"DroidSerif\");\n  EXPECT_EQ(attrs[\"src\"],\n            \"url('DroidSerif-Regular-webfont.ttf') format('truetype')\");\n\n  token = token_list[1];\n  attrs = token->second;\n  EXPECT_EQ(attrs.size(), static_cast<size_t>(4));\n  EXPECT_EQ(attrs[\"font-weight\"], \"normal\");\n  EXPECT_EQ(attrs[\"font-style\"], \"italic\");\n  EXPECT_EQ(attrs[\"font-family\"], \"DroidSerif\");\n  EXPECT_EQ(attrs[\"src\"],\n            \"url('DroidSerif-Italic-webfont.ttf') format('truetype')\");\n\n  token = token_list[2];\n  attrs = token->second;\n  EXPECT_EQ(attrs.size(), static_cast<size_t>(4));\n  EXPECT_EQ(attrs[\"font-weight\"], \"bold\");\n  EXPECT_EQ(attrs[\"font-style\"], \"normal\");\n  EXPECT_EQ(attrs[\"font-family\"], \"DroidSerif\");\n  EXPECT_EQ(attrs[\"src\"],\n            \"url('DroidSerif-Bold-webfont.ttf') format('truetype')\");\n\n  token = token_list[3];\n  attrs = token->second;\n  EXPECT_EQ(attrs.size(), static_cast<size_t>(4));\n  EXPECT_EQ(attrs[\"font-weight\"], \"bold\");\n  EXPECT_EQ(attrs[\"font-style\"], \"italic\");\n  EXPECT_EQ(attrs[\"font-family\"], \"DroidSerif\");\n  EXPECT_EQ(attrs[\"src\"],\n            \"url('DroidSerif-BoldItalic-webfont.ttf') format('truetype')\");\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_style_utils.cc",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_style_utils.h\"\n\n#include <cmath>\n#include <vector>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/float_comparison.h\"\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"base/include/vector.h\"\n#include \"core/build/gen/lynx_sub_error_code.h\"\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/css/css_keyframes_token.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/css/measure_context.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/parser/length_handler.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/default_layout_style.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/ui_wrapper/layout/layout_context.h\"\n#include \"core/renderer/utils/value_utils.h\"\n#include \"core/style/animation_data.h\"\n#include \"core/style/color.h\"\n#include \"core/style/default_computed_style.h\"\n#include \"core/style/filter_data.h\"\n#include \"core/style/layout_animation_data.h\"\n#include \"core/style/transform_origin_data.h\"\n#include \"core/style/transform_raw_data.h\"\n#include \"core/style/transition_data.h\"\n\nnamespace lynx {\nnamespace starlight {\n#define TRUE_RETURN(result) \\\n  do {                      \\\n    type = result;          \\\n    return true;            \\\n  } while (0);\n\n#define FALSE_RETURN(css_name)                               \\\n  do {                                                       \\\n    LOGE(\"invalid value for \" << css_name << \": \" << value); \\\n    return false;                                            \\\n  } while (0);\n\nnamespace {\nnamespace {\nconstexpr const char* kViewWidth = \"view_width\";\nconstexpr const char* kViewHeight = \"view_height\";\nconstexpr const char* k100VH = \"100vh\";\nconstexpr const char* k100VW = \"100vw\";\n}  // namespace\nconstexpr float kRpxRatio = 750.0f;\nstruct CalcValue {\n  float unit_value = 0.0;\n  float per_value = 0.0;\n  float number_value = 0.0;\n\n  bool is_number = true;\n};\n\nstd::pair<NLength, bool> TryMakeIntrinsicNLength(\n    const std::string& value_str, const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  if (value_str == \"max-content\") {\n    return std::pair<NLength, bool>(NLength::MakeMaxContentNLength(), true);\n  } else if (value_str == \"fit-content\") {\n    return std::pair<NLength, bool>(NLength::MakeFitContentNLength(), true);\n  } else if (base::BeginsWith(value_str, \"fit-content(\") &&\n             base::EndsWith(value_str, \")\")) {\n    // get xxx from fit-content(xxx);\n    std::string sub_value = value_str.substr(12, value_str.length() - 13);\n    tasm::CSSValue css_value;\n    lynx::tasm::LengthHandler::Process(lepus::Value(std::move(sub_value)),\n                                       css_value, configs);\n    auto result = CSSStyleUtils::ToLength(css_value, context, configs);\n    return std::pair<NLength, bool>(\n        NLength::MakeFitContentNLength(result.first.NumericLength()), true);\n  }\n\n  return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n}\n\nstd::pair<int, bool> GetEnvValue(const std::string& env_name) {\n  if (env_name == \"safe-area-inset-top\") {\n    return std::make_pair(\n        lynx::starlight::ComputedCSSStyle::SAFE_AREA_INSET_TOP_, true);\n  } else if (env_name == \"safe-area-inset-bottom\") {\n    return std::make_pair(\n        lynx::starlight::ComputedCSSStyle::SAFE_AREA_INSET_BOTTOM_, true);\n  } else if (env_name == \"safe-area-inset-left\") {\n    return std::make_pair(\n        lynx::starlight::ComputedCSSStyle::SAFE_AREA_INSET_LEFT_, true);\n  } else if (env_name == \"safe-area-inset-right\") {\n    return std::make_pair(\n        lynx::starlight::ComputedCSSStyle::SAFE_AREA_INSET_RIGHT_, true);\n  }\n\n  return std::make_pair(0, false);\n}\n\nconst size_t kExprCalcInlineStackSize = 16;\nusing CalcValueStack = base::InlineStack<CalcValue, kExprCalcInlineStackSize>;\n\nbool CalculationTopTwoData(CalcValueStack& data_stack, char operation) {\n  if (data_stack.size() < 2) {\n    return false;\n  }\n\n  // stack\n  CalcValue data2 = data_stack.top();\n  data_stack.pop();\n  CalcValue data1 = data_stack.top();\n  data_stack.pop();\n\n  if (operation == '+') {\n    if (data1.is_number != data2.is_number) {\n      return false;\n    }\n    data1.unit_value += data2.unit_value;\n    data1.per_value += data2.per_value;\n    data1.number_value += data2.number_value;\n    data_stack.push(std::move(data1));\n  } else if (operation == '-') {\n    if (data1.is_number != data2.is_number) {\n      return false;\n    }\n    if (data1.is_number) {\n      data1.number_value -= data2.number_value;\n    } else {\n      data1.unit_value -= data2.unit_value;\n      data1.per_value -= data2.per_value;\n    }\n    data_stack.push(std::move(data1));\n  } else if (operation == '*') {\n    // One data should be a number\n    if (!data1.is_number && !data2.is_number) {\n      return false;\n    }\n    if (data1.is_number && data2.is_number) {\n      data1.number_value *= data2.number_value;\n      data_stack.push(std::move(data1));\n    } else {\n      float number = data1.is_number ? data1.number_value : data2.number_value;\n      CalcValue& length = data1.is_number ? data2 : data1;\n      length.unit_value *= number;\n      length.per_value *= number;\n      data_stack.push(std::move(length));\n    }\n  } else if (operation == '/') {\n    if (!data2.is_number || data2.number_value == 0) {\n      return false;\n    }\n\n    if (data1.is_number) {\n      data1.number_value /= data2.number_value;\n    } else {\n      data1.unit_value /= data2.number_value;\n      data1.per_value /= data2.number_value;\n    }\n    data_stack.push(std::move(data1));\n  } else {\n    return false;\n  }\n\n  return true;\n}\n\nstd::pair<NLength, bool> TryMakeCalcNLength(\n    const std::string& value_str, const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs, bool is_font_relevant) {\n  if (value_str.find(\"auto\") != std::string::npos ||\n      !base::BeginsWith(value_str, \"calc\")) {\n    return std::pair<NLength, bool>(NLength::MakeAutoNLength(), true);\n  }\n\n  size_t value_len = value_str.length();\n  // Operations include + - * / ( )\n  base::InlineStack<char, kExprCalcInlineStackSize> op_stack;\n  // Data stack\n  CalcValueStack data_stack;\n  // value cache\n  std::string sub_value;\n\n  // skip \"calc\"\n  for (size_t i = 4; i < value_len; ++i) {\n    char tchar = value_str[i];\n    bool is_operation = (tchar == '*' || tchar == '/' || tchar == '(' ||\n                         tchar == ')' || tchar == '+' || tchar == '-');\n    // parse sub length\n    if ((is_operation || tchar == ' ') && !sub_value.empty()) {\n      tasm::CSSValue css_value;\n      if (sub_value == kViewWidth) {\n        sub_value = k100VW;\n\n      } else if (sub_value == kViewHeight) {\n        sub_value = k100VH;\n      }\n      if (!lynx::tasm::LengthHandler::Process(lepus::Value(sub_value),\n                                              css_value, configs)) {\n        return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n      }\n      CalcValue value;\n      if (css_value.GetPattern() == tasm::CSSValuePattern::NUMBER) {\n        value.is_number = true;\n        value.number_value = static_cast<float>(css_value.GetNumber());\n      } else {\n        std::pair<NLength, bool> result = CSSStyleUtils::ToLength(\n            css_value, context, configs, is_font_relevant);\n        if (!result.second) {\n          return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n        }\n        value.is_number = false;\n        if (result.first.GetType() == NLengthType::kNLengthUnit) {\n          value.unit_value = result.first.GetRawValue();\n        } else if (result.first.GetType() == NLengthType::kNLengthPercentage) {\n          value.per_value = result.first.GetRawValue();\n        } else {\n          return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n        }\n      }\n      data_stack.push(std::move(value));\n      sub_value.clear();\n    }\n\n    if (tchar == ' ') {\n      continue;\n    }\n\n    // The four basic operations.\n    // There are two space around add and sub sign.\n    if (is_operation &&\n        (!(tchar == '+' || tchar == '-') ||\n         (i > 0 && i < value_len - 1 && value_str[i - 1] == ' ' &&\n          value_str[i + 1] == ' '))) {\n      if (tchar == '+' || tchar == '-') {\n        while (!op_stack.empty() && op_stack.top() != '(') {\n          if (!CalculationTopTwoData(data_stack, op_stack.top())) {\n            return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n          }\n          op_stack.pop();\n        }\n        op_stack.push(tchar);\n      } else if (tchar == '*' || tchar == '/') {\n        while (!op_stack.empty() &&\n               (op_stack.top() == '*' || op_stack.top() == '/')) {\n          if (!CalculationTopTwoData(data_stack, op_stack.top())) {\n            return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n          }\n          op_stack.pop();\n        }\n        op_stack.push(tchar);\n      } else if (tchar == '(') {\n        op_stack.push(tchar);\n      } else if (tchar == ')') {\n        while (!op_stack.empty() && op_stack.top() != '(') {\n          if (!CalculationTopTwoData(data_stack, op_stack.top())) {\n            return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n          }\n          op_stack.pop();\n        }\n        // remove kLeftBracket\n        if (op_stack.empty()) {\n          return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n        }\n        op_stack.pop();\n      }\n      continue;\n    }\n\n    sub_value += tchar;\n\n    if (sub_value == \"env\") {\n      size_t env_end_index = i + 1;\n      while (env_end_index < value_len && value_str[env_end_index] != ')') {\n        ++env_end_index;\n      }\n      if (env_end_index == value_len) {\n        return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n      }\n\n      std::string env_func =\n          sub_value + value_str.substr(i + 1, env_end_index - i);\n      tasm::CSSValue css_value;\n      if (!lynx::tasm::LengthHandler::Process(lepus::Value(std::move(env_func)),\n                                              css_value, configs)) {\n        return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n      }\n      std::pair<NLength, bool> result =\n          CSSStyleUtils::ToLength(css_value, context, configs);\n      if (result.second) {\n        CalcValue env_value;\n        env_value.is_number = false;\n        env_value.unit_value = result.first.GetRawValue();\n        data_stack.push(CalcValue(env_value));\n      } else {\n        return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n      }\n      // skip env function\n      sub_value.clear();\n      i = env_end_index;\n      continue;\n    }\n  }\n  if (op_stack.size() != 0 || data_stack.size() != 1) {\n    return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n  }\n\n  // FIXME(zhixuan): calc(0%) should behave differrent from calc(0)\n  if (!base::FloatsEqual(data_stack.top().per_value, 0.0f)) {\n    return std::pair<NLength, bool>(\n        NLength::MakeCalcNLength(data_stack.top().unit_value,\n                                 data_stack.top().per_value),\n        true);\n  } else {\n    return std::pair<NLength, bool>(\n        NLength::MakeCalcNLength(data_stack.top().unit_value), true);\n  }\n}\n}  // namespace\n\nnamespace {\n\nstd::pair<NLength, bool> ToLengthHelper(const tasm::CSSValue& value,\n                                        const float& factor) {\n  float float_value = static_cast<float>(value.GetNumber()) * factor;\n  return std::pair<NLength, bool>(NLength::MakeUnitNLength(float_value), true);\n}\n\nvoid ComputeShadowStyleHelper(float& prop_result, const base::String& key,\n                              const fml::RefPtr<lepus::Dictionary>& dict,\n                              const tasm::CssMeasureContext& context,\n                              const tasm::CSSParserConfigs& configs) {\n  auto prop_arr = dict->GetValue(key).Array();\n  auto prop = tasm::CSSValue(\n      prop_arr->get(0),\n      static_cast<tasm::CSSValuePattern>(prop_arr->get(1).Number()));\n  CSSStyleUtils::CalculateLength(prop, prop_result, context, configs);\n}\n\nvoid SetX1Y1(TimingFunctionData& timing_function,\n             const fml::RefPtr<lepus::CArray>& arr) {\n  timing_function.x1 = arr->get(TimingFunctionData::INDEX_X1).Number();\n  timing_function.y1 = arr->get(TimingFunctionData::INDEX_Y1).Number();\n}\n\nvoid UpdateAnimationProp(long& dest, const tasm::CSSPropertyID anim_id,\n                         const fml::RefPtr<lepus::Dictionary>& map) {\n  auto p = map->GetValue(std::to_string(anim_id));\n  if (p.IsNumber()) {\n    dest = static_cast<long>(p.Number());\n  }\n}\n}  // namespace\n\n// TODO(zhixuan): return base length here.\nstd::pair<NLength, bool> CSSStyleUtils::ToLength(\n    const tasm::CSSValue& value, const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs, bool is_font_relevant) {\n  auto pattern = value.GetPattern();\n  const float non_sp_font_scale =\n      (is_font_relevant && !context.font_scale_sp_only_) ? context.font_scale_\n                                                         : 1.f;\n\n  if (pattern == tasm::CSSValuePattern::NUMBER) {\n    float float_value =\n        static_cast<float>(value.GetNumber()) * non_sp_font_scale;\n    return std::pair<NLength, bool>(NLength::MakeUnitNLength(float_value),\n                                    true);\n  } else if (pattern == tasm::CSSValuePattern::PX) {\n    float float_value = static_cast<float>(value.GetNumber()) *\n                        context.layouts_unit_per_px_ * non_sp_font_scale;\n    return std::pair<NLength, bool>(NLength::MakeUnitNLength(float_value),\n                                    true);\n  } else if (pattern == tasm::CSSValuePattern::RPX) {\n    float float_value = static_cast<float>(value.GetNumber()) *\n                        context.screen_width_ / kRpxRatio * non_sp_font_scale;\n    return std::pair<NLength, bool>(NLength::MakeUnitNLength(float_value),\n                                    true);\n  } else if (pattern == tasm::CSSValuePattern::PPX) {\n    float float_value = static_cast<float>(value.GetNumber()) /\n                        context.physical_pixels_per_layout_unit_ *\n                        non_sp_font_scale;\n    return std::pair<NLength, bool>(NLength::MakeUnitNLength(float_value),\n                                    true);\n  } else if (pattern == tasm::CSSValuePattern::REM) {\n    return ToLengthHelper(value, context.root_node_font_size_);\n  } else if (pattern == tasm::CSSValuePattern::EM) {\n    return ToLengthHelper(value, context.cur_node_font_size_);\n  } else if (pattern == tasm::CSSValuePattern::PERCENT) {\n    float float_value = static_cast<float>(value.GetNumber());\n    return std::pair<NLength, bool>(NLength::MakePercentageNLength(float_value),\n                                    true);\n  } else if (pattern == tasm::CSSValuePattern::VH) {\n    return context.viewport_height_.IsDefinite()\n               ? ToLengthHelper(value,\n                                context.viewport_height_.ToFloat() / 100.f)\n               : std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n  } else if (pattern == tasm::CSSValuePattern::VW) {\n    return context.viewport_width_.IsDefinite()\n               ? ToLengthHelper(value,\n                                context.viewport_width_.ToFloat() / 100.f)\n               : std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n  } else if (pattern == tasm::CSSValuePattern::CALC) {\n    const auto& value_old = value.AsStdString();\n    return TryMakeCalcNLength(value_old, context, configs, is_font_relevant);\n  } else if (pattern == tasm::CSSValuePattern::INTRINSIC) {\n    const auto& value_old = value.AsStdString();\n    return TryMakeIntrinsicNLength(value_old, context, configs);\n  } else if (pattern == tasm::CSSValuePattern::ENV) {\n    const auto& value_old = value.AsStdString();\n    size_t len = value_old.length();\n    auto env_name = value_old.substr(4, len - 5);\n    auto found = env_name.find_first_not_of(' ');\n    if (found != std::string::npos) env_name.erase(0, found);\n    found = env_name.find_last_not_of(' ');\n    if (found != std::string::npos) env_name.erase(found + 1);\n    std::pair<int, bool> result = GetEnvValue(env_name);\n    if (!result.second) {\n      return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n    }\n    return std::pair<NLength, bool>(NLength::MakeUnitNLength(result.first),\n                                    true);\n  } else if (pattern == tasm::CSSValuePattern::ENUM) {\n    return std::pair<NLength, bool>(NLength::MakeAutoNLength(), true);\n  } else if (pattern == tasm::CSSValuePattern::SP) {\n    float float_value = static_cast<float>(value.GetNumber()) *\n                        context.layouts_unit_per_px_ * context.font_scale_;\n    return std::pair<NLength, bool>(NLength::MakeUnitNLength(float_value),\n                                    true);\n  } else if (pattern == tasm::CSSValuePattern::FR) {\n    float float_value = static_cast<float>(value.GetNumber());\n    return std::pair<NLength, bool>(NLength::MakeFrNLength(float_value), true);\n  } else {\n    tasm::UnitHandler::CSSWarning(false, configs.enable_css_strict_mode,\n                                  (std::string(\"no such type length:\") +\n                                   std::to_string(static_cast<int>(pattern)))\n                                      .c_str());\n    return std::pair<NLength, bool>(NLength::MakeAutoNLength(), false);\n  }\n}\n\nbase::flex_optional<float> CSSStyleUtils::ResolveFontSize(\n    const tasm::CSSValue& value, const tasm::LynxEnvConfig& env_config,\n    const starlight::LayoutUnit& vw_base, const starlight::LayoutUnit& vh_base,\n    double cur_node_font_size, double root_node_font_size,\n    const tasm::CSSParserConfigs& configs) {\n  tasm::CssMeasureContext css_context(env_config, root_node_font_size,\n                                      cur_node_font_size);\n  css_context.viewport_width_ = vw_base;\n  css_context.viewport_height_ = vh_base;\n  css_context.font_scale_sp_only_ = env_config.FontScaleSpOnly();\n\n  base::flex_optional<float> result;\n  const auto resolved_result = ToLength(value, css_context, configs, true);\n\n  if (resolved_result.second) {\n    const auto resolved_unit = starlight::NLengthToLayoutUnit(\n        resolved_result.first,\n        starlight::LayoutUnit(css_context.cur_node_font_size_));\n    if (resolved_unit.IsDefinite()) {\n      result = resolved_unit.ToFloat();\n    }\n  }\n  return result;\n}\n\nfloat CSSStyleUtils::RoundValueToPixelGrid(\n    const float value, const float physical_pixels_per_layout_unit) {\n  return std::roundf(value * physical_pixels_per_layout_unit) /\n         physical_pixels_per_layout_unit;\n}\n\nlepus::Value CSSStyleUtils::ResolveCSSKeyframesStyle(\n    tasm::StyleMap* attrs, const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  fml::RefPtr<lepus::Dictionary> dict = lepus::Dictionary::Create();\n  for (const auto& [key, value] : *attrs) {\n    if (key == tasm::kPropertyIDBackgroundColor ||\n        key == tasm::kPropertyIDColor) {\n      unsigned int color = 0;\n      ComputeUIntStyle(value, false, color, DefaultColor::DEFAULT_COLOR,\n                       \"background-color must be a number!\", configs);\n      dict->SetValue(tasm::CSSProperty::GetPropertyName(key), color);\n    } else if (key == tasm::kPropertyIDOpacity) {\n      float opacity = 1.0f;\n      ComputeFloatStyle(value, false, opacity,\n                        DefaultComputedStyle::DEFAULT_FLOAT,\n                        \"opacity must be a float!\", configs);\n      dict->SetValue(tasm::CSSProperty::GetPropertyName(key), opacity);\n    } else if (key == tasm::kPropertyIDTransform) {\n      // transform\n      auto raw =\n          base::make_flex_optional(base::InlineVector<TransformRawData, 1>());\n      ComputeTransform(value, false, raw, context, configs);\n      dict->SetValue(tasm::CSSProperty::GetPropertyName(key),\n                     TransformToLepus(*raw));\n    } else if (key == tasm::kPropertyIDLeft || key == tasm::kPropertyIDTop ||\n               key == tasm::kPropertyIDWidth ||\n               key == tasm::kPropertyIDHeight) {\n      dict->SetValue(tasm::CSSProperty::GetPropertyName(key), value.GetValue());\n    } else {\n      tasm::UnitHandler::CSSWarning(false, configs.enable_css_strict_mode,\n                                    \"keyframe don't support id:%d\", key);\n    }\n  }\n  return lepus::Value(std::move(dict));\n}\n\nlepus::Value CSSStyleUtils::ResolveCSSKeyframesToken(\n    tasm::CSSKeyframesToken* token, const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  auto dict = lepus::Dictionary::Create();\n  for (const auto& [key, value] : token->GetKeyframesContent()) {\n    dict->SetValue(std::to_string(key),\n                   ResolveCSSKeyframesStyle(value.get(), context, configs));\n  }\n  return lepus::Value(std::move(dict));\n}\n\nlepus::Value CSSStyleUtils::ResolveCSSKeyframes(\n    const tasm::CSSKeyframesTokenMap& frames,\n    const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  auto dict = lepus::Dictionary::Create();\n  for (const auto& [key, value] : frames) {\n    dict->SetValue(key,\n                   ResolveCSSKeyframesToken(value.get(), context, configs));\n  }\n  return lepus::Value(std::move(dict));\n}\n\nbool CSSStyleUtils::ComputeBoolStyle(const tasm::CSSValue& value,\n                                     const bool reset, bool& dest,\n                                     const bool default_value, const char* msg,\n                                     const tasm::CSSParserConfigs& configs) {\n  auto old_value = dest;\n  if (reset) {\n    dest = default_value;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(value.IsBoolean(), configs.enable_css_strict_mode,\n                            msg)\n    dest = value.GetBool();\n  }\n  return old_value != dest;\n}\n\nnamespace {\n\ntemplate <typename T>\ninline bool ComputeNumberStyle(const tasm::CSSValue& value, const bool reset,\n                               T& dest, const T default_value, const char* msg,\n                               const tasm::CSSParserConfigs& configs) {\n  auto old_value = dest;\n  if (reset) {\n    dest = default_value;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(value.IsNumber(), configs.enable_css_strict_mode,\n                            msg)\n    dest = static_cast<T>(value.GetNumber());\n  }\n  // Use double for comparison to avoid precision issue.\n  if (!base::DoublesEqual(old_value, dest)) {\n    return true;\n  }\n  dest = old_value;\n  return false;\n}\n\ntemplate <>\ninline bool ComputeNumberStyle(const tasm::CSSValue& value, const bool reset,\n                               uint32_t& dest, const uint32_t default_value,\n                               const char* msg,\n                               const tasm::CSSParserConfigs& configs) {\n  auto old_value = dest;\n  if (reset) {\n    dest = default_value;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(value.IsNumber(), configs.enable_css_strict_mode,\n                            msg)\n    dest = static_cast<uint32_t>(value.GetNumber());\n  }\n  return old_value != dest;\n}\n\n}  // namespace\n\nbool CSSStyleUtils::ComputeFloatStyle(const tasm::CSSValue& value,\n                                      const bool reset, float& dest,\n                                      const float default_value,\n                                      const char* msg,\n                                      const tasm::CSSParserConfigs& configs) {\n  return ComputeNumberStyle<float>(value, reset, dest, default_value, msg,\n                                   configs);\n}\n\nbool CSSStyleUtils::ComputeIntStyle(const tasm::CSSValue& value,\n                                    const bool reset, int& dest,\n                                    const int default_value, const char* msg,\n                                    const tasm::CSSParserConfigs& configs) {\n  return ComputeNumberStyle<int>(value, reset, dest, default_value, msg,\n                                 configs);\n}\n\nbool CSSStyleUtils::ComputeUIntStyle(const tasm::CSSValue& value,\n                                     const bool reset, unsigned int& dest,\n                                     const unsigned int default_value,\n                                     const char* msg,\n                                     const tasm::CSSParserConfigs& configs) {\n  return ComputeNumberStyle<unsigned int>(value, reset, dest, default_value,\n                                          msg, configs);\n}\n\nbool CSSStyleUtils::ComputeGridTrackSizing(\n    const tasm::CSSValue& value, const bool reset,\n    const tasm::CssMeasureContext& context, std::vector<NLength>& min_dest,\n    std::vector<NLength>& max_dest, const std::vector<NLength>& default_value,\n    const char* msg, const tasm::CSSParserConfigs& configs) {\n  auto old_min_value = min_dest;\n  auto old_max_value = max_dest;\n  if (reset) {\n    min_dest = default_value;\n    max_dest = default_value;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(value.IsArray(), configs.enable_css_strict_mode,\n                            msg)\n    auto length_array = value.GetArray();\n    std::vector<NLength> length_arr_min_result;\n    std::vector<NLength> length_arr_max_result;\n\n    for (size_t idx = 0; idx < length_array->size(); idx += 2) {\n      tasm::CSSValue css_value(length_array->get(idx),\n                               static_cast<tasm::CSSValuePattern>(\n                                   length_array->get(idx + 1).Number()));\n      // Handle minmax function.\n      // e.g., minmax(10px, max-content) -->\n      // (CSSFunctionType::MINMAX, CSSValuePattern::ENUM),\n      // (10, CSSValuePattern::PX),\n      // (\"max-content\", CSSValuePattern::INTRINSIC)\n      if (css_value.IsEnum() && css_value.GetEnum<tasm::CSSFunctionType>() ==\n                                    tasm::CSSFunctionType::MINMAX) {\n        idx += 2;\n        if (idx + 3 >= length_array->size()) {\n          return false;\n        }\n        tasm::CSSValue css_value(length_array->get(idx),\n                                 static_cast<tasm::CSSValuePattern>(\n                                     length_array->get(idx + 1).Number()));\n        std::pair<NLength, bool> result =\n            CSSStyleUtils::ToLength(css_value, context, configs);\n        length_arr_min_result.emplace_back(result.first);\n        idx += 2;\n        css_value = tasm::CSSValue(length_array->get(idx),\n                                   static_cast<tasm::CSSValuePattern>(\n                                       length_array->get(idx + 1).Number()));\n        result = CSSStyleUtils::ToLength(css_value, context, configs);\n        length_arr_max_result.emplace_back(result.first);\n      } else {\n        std::pair<NLength, bool> result =\n            CSSStyleUtils::ToLength(css_value, context, configs);\n        length_arr_min_result.emplace_back(result.first);\n        length_arr_max_result.emplace_back(result.first);\n      }\n    }\n    min_dest = std::move(length_arr_min_result);\n    max_dest = std::move(length_arr_max_result);\n  }\n\n  return old_min_value != min_dest || old_max_value != max_dest;\n}\n\nbool CSSStyleUtils::ComputeLengthStyle(const tasm::CSSValue& value,\n                                       const bool reset,\n                                       const tasm::CssMeasureContext& context,\n                                       NLength& dest,\n                                       const NLength& default_value,\n                                       const tasm::CSSParserConfigs& configs) {\n  NLength old_value = dest;\n  if (reset) {\n    dest = default_value;\n  } else {\n    auto parse_result = CSSStyleUtils::ToLength(value, context, configs);\n    if (!parse_result.second) {\n      return false;\n    }\n    dest = std::move(parse_result.first);\n  }\n  return old_value != dest;\n}\n\nbool CSSStyleUtils::CalculateLength(const tasm::CSSValue& value, float& result,\n                                    const tasm::CssMeasureContext& context,\n                                    const tasm::CSSParserConfigs& configs) {\n  auto parse_result = CSSStyleUtils::ToLength(value, context, configs);\n  if (!parse_result.second) {\n    return false;\n  }\n\n  const auto& length = parse_result.first;\n  result = CSSStyleUtils::RoundValueToPixelGrid(\n      length.GetRawValue(), context.physical_pixels_per_layout_unit_);\n  return true;\n}\n\nvoid CSSStyleUtils::ConvertCSSValueToNumber(\n    const tasm::CSSValue& value, float& result, PlatformLengthUnit& unit,\n    const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  if (value.IsPercent()) {\n    result = value.AsNumber() / 100.0f;\n    unit = PlatformLengthUnit::PERCENTAGE;\n  } else {\n    CalculateLength(value, result, context, configs);\n  }\n}\n\nvoid GetLengthData(NLength& length, const lepus_value& value,\n                   const lepus_value& unit,\n                   const tasm::CssMeasureContext& context,\n                   const tasm::CSSParserConfigs& configs) {\n  auto pattern = static_cast<tasm::CSSValuePattern>(unit.Number());\n  auto parse_result =\n      CSSStyleUtils::ToLength(tasm::CSSValue(value, pattern), context, configs);\n  if (parse_result.second) {\n    length = parse_result.first;\n    if (length.IsUnit()) {\n      length = NLength::MakeUnitNLength(CSSStyleUtils::RoundValueToPixelGrid(\n          length.GetRawValue(), context.physical_pixels_per_layout_unit_));\n    }\n  }\n}\n\nbool CSSStyleUtils::ComputeFilter(const tasm::CSSValue& value, bool reset,\n                                  base::flex_optional<FilterData>& filter,\n                                  const tasm::CssMeasureContext context,\n                                  const tasm::CSSParserConfigs& configs) {\n  auto last_filter = filter;\n  if (reset) {\n    // reset the optional directly, if optional is std::nullopt, filterToLepus\n    // will return an empty array.\n    filter.reset();\n  } else {\n    PrepareOptional(filter);\n    CSS_HANDLER_FAIL_IF_NOT(value.IsArray(), configs.enable_css_strict_mode,\n                            \"filter must be an array! [type, length, unit]\")\n    FilterData item;\n    auto attributes = value.GetArray();\n    // Check attr size\n    CSS_HANDLER_FAIL_IF_NOT(attributes->size() != 0,\n                            configs.enable_css_strict_mode,\n                            \"filter array must have attributes\")\n    item.type = static_cast<FilterType>(\n        attributes->get(FilterData::kIndexType).Number());\n\n    // Check param size.\n    CSS_HANDLER_FAIL_IF_NOT(\n        item.type == FilterType::kNone || attributes->size() == 3,\n        configs.enable_css_strict_mode, \"filter function should has a param\")\n    // Compose unit and number value into NLength.\n    if (item.type == FilterType::kBrightness ||\n        item.type == FilterType::kContrast ||\n        item.type == FilterType::kSaturate) {\n      item.amount = NLength(NLength::MakeUnitNLength(\n          attributes->get(FilterData::kIndexAmount).Number()));\n    } else if (item.type == FilterType::kGrayscale ||\n               item.type == FilterType::kBlur) {\n      GetLengthData(item.amount, attributes->get(FilterData::kIndexAmount),\n                    attributes->get(FilterData::kIndexUnit), context, configs);\n    }\n    *filter = item;\n  }\n  return last_filter != filter;\n}\n\nnamespace {\nvoid GetTransformMatrix(TransformType matrix_type,\n                        const fml::RefPtr<lepus::CArray>& arr,\n                        TransformRawData& transform_raw_data,\n                        const tasm::CssMeasureContext& context,\n                        const tasm::CSSParserConfigs& configs) {\n  if (matrix_type == TransformType::kMatrix) {\n    for (int i = 0; i < 6; ++i) {\n      if (i == 4 || i == 5) {\n        // handle translate pixel unit.\n        transform_raw_data\n            .matrix[TransformRawData::INDEX_2D_TO_3D_MATRIX_ID[i]] =\n            CSSStyleUtils::ToLength(\n                tasm::CSSValue(arr->get(i + 1), tasm::CSSValuePattern::PX),\n                context, configs)\n                .first.GetRawValue();\n      } else {\n        transform_raw_data\n            .matrix[TransformRawData::INDEX_2D_TO_3D_MATRIX_ID[i]] =\n            arr->get(i + 1).Number();\n      }\n    }\n  } else if (matrix_type == TransformType::kMatrix3d) {\n    for (int i = 0; i < 16; ++i) {\n      if (i == 12 || i == 13 || i == 14) {\n        // handle translate pixel unit.\n        transform_raw_data.matrix[TransformRawData::INDEX_3D_MATRIX_ID[i]] =\n            CSSStyleUtils::ToLength(\n                tasm::CSSValue(arr->get(i + 1), tasm::CSSValuePattern::PX),\n                context, configs)\n                .first.GetRawValue();\n      } else {\n        transform_raw_data.matrix[TransformRawData::INDEX_3D_MATRIX_ID[i]] =\n            arr->get(i + 1).Number();\n      }\n    }\n  }\n  transform_raw_data.matrix_empty = false;\n}\n}  // namespace\n\nbool CSSStyleUtils::ComputeTransform(\n    const tasm::CSSValue& value, bool reset,\n    base::flex_optional<base::InlineVector<TransformRawData, 1>>& raw,\n    const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  auto old_raw = raw;\n  if (reset) {\n    raw.reset();\n  } else {\n    PrepareOptional(raw);\n    raw->clear();\n    CSS_HANDLER_FAIL_IF_NOT(value.IsArray(), configs.enable_css_strict_mode,\n                            \"transform must be a array!\")\n    auto items = value.GetArray();\n    CSS_HANDLER_FAIL_IF_NOT(items->size() > 0, configs.enable_css_strict_mode,\n                            \"transform's array size must > 0\")\n    raw->reserve(items->size());\n    for (size_t i = 0; i < items->size(); i++) {\n      CSS_HANDLER_FAIL_IF_NOT(items->get(i).IsArray(),\n                              configs.enable_css_strict_mode,\n                              \"transform's items must be an array\")\n      auto arr = items->get(i).Array();\n      CSS_HANDLER_FAIL_IF_NOT(items->size() > 0, configs.enable_css_strict_mode,\n                              \"transform's array size must > 0\")\n      TransformRawData item;\n      item.type = static_cast<TransformType>(\n          arr->get(TransformRawData::INDEX_FUNC).Number());\n      switch (item.type) {\n        case TransformType::kTranslate:\n          GetLengthData(item.p0, arr->get(TransformRawData::INDEX_TRANSLATE_0),\n                        arr->get(TransformRawData::INDEX_TRANSLATE_0_UNIT),\n                        context, configs);\n          item.unit_type0 = static_cast<tasm::CSSValuePattern>(\n              arr->get(TransformRawData::INDEX_TRANSLATE_0_UNIT).Number());\n          if (arr->size() > TransformRawData::INDEX_TRANSLATE_1) {\n            GetLengthData(item.p1,\n                          arr->get(TransformRawData::INDEX_TRANSLATE_1),\n                          arr->get(TransformRawData::INDEX_TRANSLATE_1_UNIT),\n                          context, configs);\n            item.unit_type1 = static_cast<tasm::CSSValuePattern>(\n                arr->get(TransformRawData::INDEX_TRANSLATE_1_UNIT).Number());\n          }\n          break;\n        case TransformType::kTranslateX:\n        case TransformType::kTranslateY:\n        case TransformType::kTranslateZ:\n          GetLengthData(item.p0, arr->get(TransformRawData::INDEX_TRANSLATE_0),\n                        arr->get(TransformRawData::INDEX_TRANSLATE_0_UNIT),\n                        context, configs);\n          item.unit_type0 = static_cast<tasm::CSSValuePattern>(\n              arr->get(TransformRawData::INDEX_TRANSLATE_0_UNIT).Number());\n          break;\n        case TransformType::kTranslate3d:\n          GetLengthData(item.p0, arr->get(TransformRawData::INDEX_TRANSLATE_0),\n                        arr->get(TransformRawData::INDEX_TRANSLATE_0_UNIT),\n                        context, configs);\n          item.unit_type0 = static_cast<tasm::CSSValuePattern>(\n              arr->get(TransformRawData::INDEX_TRANSLATE_0_UNIT).Number());\n          GetLengthData(item.p1, arr->get(TransformRawData::INDEX_TRANSLATE_1),\n                        arr->get(TransformRawData::INDEX_TRANSLATE_1_UNIT),\n                        context, configs);\n          item.unit_type1 = static_cast<tasm::CSSValuePattern>(\n              arr->get(TransformRawData::INDEX_TRANSLATE_1_UNIT).Number());\n          GetLengthData(item.p2, arr->get(TransformRawData::INDEX_TRANSLATE_2),\n                        arr->get(TransformRawData::INDEX_TRANSLATE_2_UNIT),\n                        context, configs);\n          item.unit_type2 = static_cast<tasm::CSSValuePattern>(\n              arr->get(TransformRawData::INDEX_TRANSLATE_2_UNIT).Number());\n          break;\n        case TransformType::kRotate:\n        case TransformType::kRotateX:\n        case TransformType::kRotateY:\n        case TransformType::kRotateZ:\n          item.p0 = NLength::MakeUnitNLength(\n              arr->get(TransformRawData::INDEX_ROTATE_ANGLE).Number());\n          break;\n        case TransformType::kScale:\n          item.p0 = NLength::MakeUnitNLength(\n              arr->get(TransformRawData::INDEX_SCALE_0).Number());\n          if (arr->size() <= TransformRawData::INDEX_SCALE_1) {\n            item.p1 = item.p0;\n          } else {\n            item.p1 = NLength::MakeUnitNLength(\n                arr->get(TransformRawData::INDEX_SCALE_1).Number());\n          }\n          break;\n        case TransformType::kScaleX:\n        case TransformType::kScaleY:\n          item.p0 = NLength::MakeUnitNLength(\n              arr->get(TransformRawData::INDEX_SCALE_0).Number());\n          break;\n        case TransformType::kSkew:\n          item.p0 = NLength::MakeUnitNLength(\n              arr->get(TransformRawData::INDEX_SKEW_0).Number());\n          if (arr->size() <= TransformRawData::INDEX_SKEW_1) {\n            item.p1 = NLength::MakeUnitNLength(.0f);\n          } else {\n            item.p1 = NLength::MakeUnitNLength(\n                arr->get(TransformRawData::INDEX_SKEW_1).Number());\n          }\n          break;\n        case TransformType::kSkewX:\n        case TransformType::kSkewY:\n          item.p0 = NLength::MakeUnitNLength(\n              arr->get(TransformRawData::INDEX_SKEW_0).Number());\n          break;\n        case TransformType::kMatrix:\n        case TransformType::kMatrix3d:\n          GetTransformMatrix(item.type, arr, item, context, configs);\n          break;\n        default:\n          LynxWarning(false, error::E_CSS_COMPUTED_CSS_VALUE_UNKNOWN_SETTER,\n                      \"can't reach here, no such instance:%d\", (int)item.type);\n          break;\n      }\n      raw->push_back(item);\n    }\n  }\n  return old_raw != raw;\n}\n\n/// Generate a lepus array to platform according to the computed `FilterData`.\n/// - Parameter filter: Current filter state.\n/// - Returns: A lepus array, [int, double, int], indicates [FilterType, Amount,\n/// Unit]. Empty if `filter` is nullopt, which typically occurs when the value\n/// is reset.\nlepus_value CSSStyleUtils::FilterToLepus(\n    const base::flex_optional<FilterData>& filter) {\n  auto result = lepus::CArray::Create();\n  if (filter) {\n    result->emplace_back(static_cast<int>(filter->type));\n    // Transfer NLength into platform unit value.\n    AddLengthToArray(result, filter->amount);\n  }\n  return lepus_value(std::move(result));\n}\n\nlepus_value CSSStyleUtils::TransformToLepus(\n    const base::Vector<TransformRawData>& transform_raw) {\n  auto items = lepus::CArray::Create();\n  for (const auto& tr : transform_raw) {\n    auto item = lepus::CArray::Create();\n    // estimate the capacity and reserve in advance\n    const bool is_matrix = (tr.type == TransformType::kMatrix ||\n                            tr.type == TransformType::kMatrix3d);\n    // Estimate the required capacity for item to avoid multiple reallocations:\n    // - 1: for tr.type\n    // - 16: for 16 matrix values if the type is matrix or matrix3d\n    // - 3: for 3 parameters (p0, p1, p2) for other types\n    // - 4: each AddLengthToArray call can add up to 4 elements (see\n    // AddLengthToArray implementation)\n    if (is_matrix) {\n      // type (1) + 16 matrix values\n      item->reserve(1 + 16);\n    } else {\n      // type (1) + up to 4 elements for each of p0, p1, p2\n      item->reserve(1 + 3 * 4);\n    }\n    item->emplace_back(static_cast<int>(tr.type));\n    if (is_matrix) {\n      for (int i = 0; i < 16; ++i) {\n        item->emplace_back(tr.matrix[i]);\n      }\n    } else {\n      AddLengthToArray(item, tr.p0);\n      AddLengthToArray(item, tr.p1);\n      AddLengthToArray(item, tr.p2);\n    }\n    items->emplace_back(std::move(item));\n  }\n  return lepus_value(std::move(items));\n}\n\nbool CSSStyleUtils::IsLayoutRelatedTransform(tasm::CSSPropertyID id,\n                                             const tasm::CSSValue& value) {\n  if (id == tasm::kPropertyIDTransform) {\n    auto array = value.GetArray();\n    for (size_t idx = 0; idx < array->size(); ++idx) {\n      auto transform = array->get(idx).Array();\n      auto transform_func =\n          static_cast<starlight::TransformType>(transform->get(0).Int32());\n      if (transform_func == starlight::TransformType::kTranslate ||\n          transform_func == starlight::TransformType::kTranslateX ||\n          transform_func == starlight::TransformType::kTranslateY ||\n          transform_func == starlight::TransformType::kTranslateZ ||\n          transform_func == starlight::TransformType::kTranslate3d) {\n        for (size_t param_idx = 2; param_idx < transform->size();\n             param_idx += 2) {\n          auto pattern = static_cast<tasm::CSSValuePattern>(\n              transform->get(param_idx).Int32());\n          if (pattern == tasm::CSSValuePattern::RPX ||\n              pattern == tasm::CSSValuePattern::EM ||\n              pattern == tasm::CSSValuePattern::REM ||\n              pattern == tasm::CSSValuePattern::VW ||\n              pattern == tasm::CSSValuePattern::VH) {\n            return true;\n          }\n        }\n      }\n    }\n  }\n  return false;\n}\n\nbool CSSStyleUtils::ComputeStringStyle(const tasm::CSSValue& value,\n                                       const bool reset, base::String& dest,\n                                       const base::String& default_value,\n                                       const char* msg,\n                                       const tasm::CSSParserConfigs& configs) {\n  auto old_value = dest;\n  if (reset) {\n    dest = default_value;\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(value.IsString(), configs.enable_css_strict_mode,\n                            msg)\n    dest = value.AsString();\n  }\n  return !old_value.IsEqual(dest);\n}\n\nbool CSSStyleUtils::ComputeTimingFunction(\n    const lepus::Value& value, const bool reset,\n    TimingFunctionData& timing_function,\n    const tasm::CSSParserConfigs& configs) {\n  auto old_value = timing_function;\n  if (reset) {\n    timing_function.Reset();\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(value.IsNumber() || value.IsArray(),\n                            configs.enable_css_strict_mode,\n                            \"timing-function must be a enum or a array!\")\n    if (value.IsNumber()) {\n      timing_function.timing_func =\n          static_cast<TimingFunctionType>(value.Number());\n    } else {\n      auto arr = value.Array();\n      timing_function.timing_func = static_cast<TimingFunctionType>(\n          arr->get(TimingFunctionData::INDEX_TYPE).Number());\n      if (timing_function.timing_func == TimingFunctionType::kSquareBezier) {\n        SetX1Y1(timing_function, arr);\n      } else if (timing_function.timing_func ==\n                 TimingFunctionType::kCubicBezier) {\n        SetX1Y1(timing_function, arr);\n        timing_function.x2 = arr->get(TimingFunctionData::INDEX_X2).Number();\n        timing_function.y2 = arr->get(TimingFunctionData::INDEX_Y2).Number();\n      } else if (timing_function.timing_func == TimingFunctionType::kSteps) {\n        timing_function.x1 = arr->get(TimingFunctionData::INDEX_X1).Number();\n        timing_function.steps_type = static_cast<StepsType>(\n            arr->get(TimingFunctionData::INDEX_STEPS_TYPE).Number());\n      } else {\n        std::string error_msg =\n            std::string(\"no such bezier implementation\") +\n            std::to_string(static_cast<int>(timing_function.timing_func));\n        LynxWarning(false, error::E_CSS_COMPUTED_CSS_VALUE_UNKNOWN_SETTER,\n                    error_msg.c_str());\n      }\n    }\n  }\n  return !(old_value == timing_function);\n}\n\nbool CSSStyleUtils::ComputeLongStyle(const tasm::CSSValue& value,\n                                     const bool reset, long& dest,\n                                     const long default_value, const char* msg,\n                                     const tasm::CSSParserConfigs& configs) {\n  return ComputeNumberStyle<long>(value, reset, dest, default_value, msg,\n                                  configs);\n}\n\nbool CSSStyleUtils::ComputeHeroAnimation(\n    const tasm::CSSValue& value, const bool reset,\n    base::flex_optional<AnimationData>& anim, const char* msg,\n    const tasm::CSSParserConfigs& configs) {\n  auto old_value = anim ? *anim : DefaultComputedStyle::DEFAULT_ANIMATION();\n  if (reset) {\n    anim.reset();\n  } else {\n    if (value.IsEmpty()) {\n      return false;\n    }\n    CSS_HANDLER_FAIL_IF_NOT(value.IsArray() || value.IsMap(),\n                            configs.enable_css_strict_mode, msg)\n    PrepareOptional(anim);\n    if (value.IsArray()) {\n      auto array = value.GetArray();\n      if (array->size() == 0) {\n        return false;\n      }\n      ComputeAnimation(array->get(0), *anim, msg, configs);\n    } else {\n      ComputeAnimation(value.GetValue(), *anim, msg, configs);\n    }\n  }\n\n  return old_value != anim;\n}\n\nbool CSSStyleUtils::ComputeAnimation(const lepus::Value& value,\n                                     AnimationData& anim, const char* msg,\n                                     const tasm::CSSParserConfigs& configs) {\n  CSS_HANDLER_FAIL_IF_NOT(value.IsObject(), configs.enable_css_strict_mode, msg)\n  auto map = value.Table();\n  auto p_name = map->GetValue(std::to_string(tasm::kPropertyIDAnimationName));\n  if (p_name.IsString()) {\n    anim.name = p_name.String();\n  }\n\n  UpdateAnimationProp(anim.duration, tasm::kPropertyIDAnimationDuration, map);\n\n  auto p_timing =\n      map->GetValue(std::to_string(tasm::kPropertyIDAnimationTimingFunction));\n  if (p_timing.IsArray()) {\n    ComputeTimingFunction(p_timing.Array()->get(0), false, anim.timing_func,\n                          configs);\n  }\n\n  auto p_fill_mode =\n      map->GetValue(std::to_string(tasm::kPropertyIDAnimationFillMode));\n  if (p_fill_mode.IsNumber()) {\n    anim.fill_mode =\n        static_cast<starlight::AnimationFillModeType>(p_fill_mode.Number());\n  }\n\n  UpdateAnimationProp(anim.delay, tasm::kPropertyIDAnimationDelay, map);\n\n  auto p_direction =\n      map->GetValue(std::to_string(tasm::kPropertyIDAnimationDirection));\n  if (p_direction.IsNumber()) {\n    anim.direction =\n        static_cast<starlight::AnimationDirectionType>(p_direction.Number());\n  }\n\n  auto p_iteration_count =\n      map->GetValue(std::to_string(tasm::kPropertyIDAnimationIterationCount));\n  if (p_iteration_count.IsNumber()) {\n    anim.iteration_count = static_cast<int>(p_iteration_count.Number());\n  }\n\n  auto p_play_state =\n      map->GetValue(std::to_string(tasm::kPropertyIDAnimationPlayState));\n  if (p_play_state.IsNumber()) {\n    anim.play_state =\n        static_cast<starlight::AnimationPlayStateType>(p_play_state.Number());\n  }\n\n  return true;\n}\n\nlepus_value CSSStyleUtils::AnimationDataToLepus(AnimationData& anim) {\n  auto array = lepus::CArray::Create();\n  array->emplace_back(anim.name);\n  array->emplace_back(static_cast<double>(anim.duration));\n  array->emplace_back(static_cast<int>(anim.timing_func.timing_func));\n  array->emplace_back(static_cast<int>(anim.timing_func.steps_type));\n  array->emplace_back(static_cast<float>(anim.timing_func.x1));\n  array->emplace_back(static_cast<float>(anim.timing_func.y1));\n  array->emplace_back(static_cast<float>(anim.timing_func.x2));\n  array->emplace_back(static_cast<float>(anim.timing_func.y2));\n  array->emplace_back(static_cast<double>(anim.delay));\n  array->emplace_back(static_cast<int>(anim.iteration_count));\n  array->emplace_back(static_cast<int>(anim.direction));\n  array->emplace_back(static_cast<int>(anim.fill_mode));\n  array->emplace_back(static_cast<int>(anim.play_state));\n  return lepus::Value(std::move(array));\n}\n\nbool CSSStyleUtils::ComputeShadowStyle(\n    const tasm::CSSValue& value, const bool reset,\n    base::flex_optional<base::InlineVector<ShadowData, 1>>& shadow,\n    const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  if (reset) {\n    shadow.reset();\n    return true;\n  }\n  auto old_value =\n      shadow ? *shadow : DefaultComputedStyle::DEFAULT_BOX_SHADOW();\n  CSS_HANDLER_FAIL_IF_NOT(value.IsArray(), configs.enable_css_strict_mode,\n                          \"shadow must be an array!\")\n  auto group = value.GetArray();\n  base::InlineVector<ShadowData, 1> dest;\n  BASE_STATIC_STRING_DECL(kEnable, \"enable\");\n  for (size_t i = 0; i < group->size(); i++) {\n    auto dict = group->get(i).Table();\n    bool enable = true;\n    if (dict->Contains(kEnable)) {\n      enable = dict->GetValue(kEnable).Bool();\n    }\n    if (enable) {\n      CSS_HANDLER_FAIL_IF_NOT(dict->size() > 2, configs.enable_css_strict_mode,\n                              \"shadow must have h_offset and v_offset !\")\n      if (dest.size() < (i + 1)) {\n        dest.emplace_back(ShadowData());\n      }\n      auto& shadow_ele = dest.at(i);\n\n      BASE_STATIC_STRING_DECL(kHOffset, \"h_offset\");\n      ComputeShadowStyleHelper(shadow_ele.h_offset, kHOffset, dict, context,\n                               configs);\n\n      BASE_STATIC_STRING_DECL(kVOffset, \"v_offset\");\n      ComputeShadowStyleHelper(shadow_ele.v_offset, kVOffset, dict, context,\n                               configs);\n\n      BASE_STATIC_STRING_DECL(kBlur, \"blur\");\n      if (dict->Contains(kBlur)) {\n        ComputeShadowStyleHelper(shadow_ele.blur, kBlur, dict, context,\n                                 configs);\n      }\n      BASE_STATIC_STRING_DECL(kSpread, \"spread\");\n      if (dict->Contains(kSpread)) {\n        ComputeShadowStyleHelper(shadow_ele.spread, kSpread, dict, context,\n                                 configs);\n      }\n      BASE_STATIC_STRING_DECL(kOption, \"option\");\n      if (dict->Contains(kOption)) {\n        auto option = dict->GetValue(kOption).Number();\n        shadow_ele.option = static_cast<ShadowOption>(option);\n      }\n      BASE_STATIC_STRING_DECL(kColor, \"color\");\n      if (dict->Contains(kColor)) {\n        auto color = static_cast<uint32_t>(dict->GetValue(kColor).Number());\n        shadow_ele.color = color;\n      }\n    }\n  }\n  if (dest.size() > 0) {\n    shadow = dest;\n  } else {\n    shadow.reset();\n  }\n  return old_value !=\n         (shadow ? *shadow : DefaultComputedStyle::DEFAULT_BOX_SHADOW());\n}\n\nstd::shared_ptr<tasm::StyleMap> CSSStyleUtils::ProcessCSSAttrsMap(\n    const lepus::Value& value, const tasm::CSSParserConfigs& configs) {\n  auto map = std::make_shared<tasm::StyleMap>();\n  if (!value.IsObject()) {\n    return map;\n  }\n  const auto& table = value.Table();\n  map->reserve(table->size());\n  for (const auto& [key, value] : *table) {\n    tasm::CSSPropertyID id = tasm::CSSProperty::GetPropertyID(key);\n    if (!tasm::CSSProperty::IsPropertyValid(id)) {\n      continue;\n    }\n    tasm::UnitHandler::Process(id, value, *(map.get()), configs);\n  }\n  return map;\n}\n\nvoid CSSStyleUtils::UpdateCSSKeyframes(\n    tasm::CSSKeyframesTokenMap& keyframes_map, const base::String& name,\n    const lepus::Value& keyframes, const tasm::CSSParserConfigs& configs) {\n  if (!keyframes.IsTable()) {\n    if (!keyframes.IsArray() || keyframes.Array()->size() < 2) {\n      return;\n    }\n    keyframes_map[name] = fml::MakeRefCounted<tasm::CSSKeyframesToken>(configs);\n    const auto& ary = keyframes.Array();\n    float interval = 1.0f / (ary->size() - 1);\n    for (size_t i = 0; i < ary->size(); ++i) {\n      keyframes_map[name]->GetKeyframesContent().insert(std::make_pair(\n          i * interval, ProcessCSSAttrsMap(ary->get(i), configs)));\n    }\n    return;\n  }\n  if (keyframes.Table()->size() < 2) {\n    return;\n  }\n  keyframes_map[name] = fml::MakeRefCounted<tasm::CSSKeyframesToken>(configs);\n  auto table = keyframes.Table();\n  for (auto& iter : *table) {\n    const std::string& per = iter.first.str();\n    if (per.empty()) {\n      continue;\n    }\n    char* endptr = nullptr;\n    float interval = strtof(per.c_str(), &endptr) / 100.0;\n    keyframes_map[name]->GetKeyframesContent().insert(std::make_pair(\n        interval,\n        starlight::CSSStyleUtils::ProcessCSSAttrsMap(iter.second, configs)));\n  }\n}\n\nfloat CSSStyleUtils::GetBorderWidthFromLengthToFloat(\n    const NLength& value, const tasm::CssMeasureContext& context) {\n  float raw_value = value.NumericLength().GetFixedPart();\n  return CSSStyleUtils::RoundValueToPixelGrid(\n      raw_value, context.physical_pixels_per_layout_unit_);\n}\n\n/**\n * Add a NLength value to a lepus::CArray, append the value and unit to the\n * target array. Convert the NLength to [value, unit] for the platform.\n * We don't know the parent length value, so need to add the sub lengths to\n * array for calc length.\n * @param array target array, to which append the value and unit.\n * @param length the length to be added\n */\nvoid CSSStyleUtils::AddLengthToArray(const fml::RefPtr<lepus::CArray>& array,\n                                     const NLength& length) {\n  const auto push_length = [](const fml::RefPtr<lepus::CArray>& array,\n                              const NLength& length) {\n    if (length.NumericLength().ContainsPercentage()) {\n      array->emplace_back(length.NumericLength().GetPercentagePart() / 100.f);\n      array->emplace_back(static_cast<int>(PlatformLengthUnit::PERCENTAGE));\n    }\n    if (length.NumericLength().ContainsFixedValue() ||\n        !length.NumericLength().ContainsPercentage()) {\n      array->emplace_back(length.NumericLength().GetFixedPart());\n      array->emplace_back(static_cast<int>(PlatformLengthUnit::NUMBER));\n    }\n  };\n  if (length.IsCalc() && length.NumericLength().ContainsFixedValue() &&\n      length.NumericLength().ContainsPercentage()) {\n    auto calc = lepus::CArray::Create();\n    push_length(calc, length);\n    array->emplace_back(std::move(calc));\n    array->emplace_back(static_cast<int>(PlatformLengthUnit::CALC));\n  } else {\n    push_length(array, length);\n  }\n}\n\n/**\n *  Compute the basic shape ellipse function to lepus::array.\n *  [type ellipse, radiusX, platformUnit, radiusY,  platformUnit, centerX,\n * platformUnit, centerY, platformUnit]\n * @param raw array, [type, radiusX, unit, radiusY, unit, centerX, unit,\n * centerY, unit]\n * @param out output array, empty array created outside the function.\n * @param context length context\n */\nvoid CSSStyleUtils::ComputeBasicShapeEllipse(\n    const fml::RefPtr<lepus::CArray>& raw, bool reset,\n    fml::RefPtr<lepus::CArray>& out, const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  if (reset) {\n    // Keep the array empty.\n    return;\n  }\n  constexpr int INDEX_ELLIPSE_TYPE = 0;\n  constexpr int INDEX_ELLIPSE_RADIUS_X = 1;\n  constexpr int INDEX_ELLIPSE_RADIUS_X_UNIT = 2;\n  constexpr int INDEX_ELLIPSE_RADIUS_Y = 3;\n  constexpr int INDEX_ELLIPSE_RADIUS_Y_UNIT = 4;\n  constexpr int INDEX_ELLIPSE_CENTER_X = 5;\n  constexpr int INDEX_ELLIPSE_CENTER_X_UNIT = 6;\n  constexpr int INDEX_ELLIPSE_CENTER_Y = 7;\n  constexpr int INDEX_ELLIPSE_CENTER_Y_UNIT = 8;\n\n  out->push_back(raw->get(INDEX_ELLIPSE_TYPE));\n  NLength radius_x = NLength::MakeAutoNLength(),\n          radius_y = NLength::MakeAutoNLength(),\n          center_x = NLength::MakeAutoNLength(),\n          center_y = NLength::MakeAutoNLength();\n  // Compute the CSSValue to NLength according to unit and length context.\n  GetLengthData(radius_x, raw->get(INDEX_ELLIPSE_RADIUS_X),\n                raw->get(INDEX_ELLIPSE_RADIUS_X_UNIT), context, configs);\n  GetLengthData(radius_y, raw->get(INDEX_ELLIPSE_RADIUS_Y),\n                raw->get(INDEX_ELLIPSE_RADIUS_Y_UNIT), context, configs);\n  GetLengthData(center_x, raw->get(INDEX_ELLIPSE_CENTER_X),\n                raw->get(INDEX_ELLIPSE_CENTER_X_UNIT), context, configs);\n  GetLengthData(center_y, raw->get(INDEX_ELLIPSE_CENTER_Y),\n                raw->get(INDEX_ELLIPSE_CENTER_Y_UNIT), context, configs);\n\n  // Change the unit to platform unit and append to target array.\n  AddLengthToArray(out, radius_x);\n  AddLengthToArray(out, radius_y);\n  AddLengthToArray(out, center_x);\n  AddLengthToArray(out, center_y);\n}\n\n/**\n * Compute the radius and position in basic shape circle array.\n * @param raw array contains radius, centerX and centerY\n * @param out output array, 1-D lepus array stores the [type, radius,\n * platformUnit, centerX, platformUnit, centerY, platformUnit]\n */\nvoid CSSStyleUtils::ComputeBasicShapeCircle(\n    const fml::RefPtr<lepus::CArray>& raw, bool reset,\n    fml::RefPtr<lepus::CArray>& out, const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  if (reset) {\n    // Keep the array empty.\n    return;\n  }\n\n  constexpr int INDEX_CIRCLE_TYPE = 0;\n  constexpr int INDEX_CIRCLE_RADIUS = 1;\n  constexpr int INDEX_CIRCLE_RADIUS_UNIT = 2;\n  constexpr int INDEX_CIRCLE_CENTER_X = 3;\n  constexpr int INDEX_CIRCLE_CENTER_X_UNIT = 4;\n  constexpr int INDEX_CIRCLE_CENTER_Y = 5;\n  constexpr int INDEX_CIRCLE_CENTER_Y_UNIT = 6;\n\n  out->push_back(raw->get(INDEX_CIRCLE_TYPE));\n  NLength radius = NLength::MakeAutoNLength(),\n          center_x = NLength::MakeAutoNLength(),\n          center_y = NLength::MakeAutoNLength();\n  GetLengthData(radius, raw->get(INDEX_CIRCLE_RADIUS),\n                raw->get(INDEX_CIRCLE_RADIUS_UNIT), context, configs);\n  GetLengthData(center_x, raw->get(INDEX_CIRCLE_CENTER_X),\n                raw->get(INDEX_CIRCLE_CENTER_X_UNIT), context, configs);\n  GetLengthData(center_y, raw->get(INDEX_CIRCLE_CENTER_Y),\n                raw->get(INDEX_CIRCLE_CENTER_Y_UNIT), context, configs);\n\n  // Convert unit to platformUnit and append to output array.\n  AddLengthToArray(out, radius);\n  AddLengthToArray(out, center_x);\n  AddLengthToArray(out, center_y);\n}\n\n/**\n * Set basic shape path, input is [type, string].\n * @param raw [type, string] array.\n */\nvoid CSSStyleUtils::ComputeBasicShapePath(const fml::RefPtr<lepus::CArray>& raw,\n                                          bool reset,\n                                          fml::RefPtr<lepus::CArray>& out) {\n  if (reset) {\n    // Keep the array empty.\n    return;\n  }\n  // Don't need change anything in BasicShapePath.\n  // [typePath, dataString]\n  out = raw;\n}\n\n/**\n *  Convert the parse result `raw` to array with platform unit `out`.\n * @param raw parsed css value\n * @param reset need reset\n * @param out output array, all units are converted to platform units\n */\nvoid CSSStyleUtils::ComputeSuperEllipse(const fml::RefPtr<lepus::CArray>& raw,\n                                        bool reset,\n                                        fml::RefPtr<lepus::CArray>& out,\n                                        const tasm::CssMeasureContext& context,\n                                        const tasm::CSSParserConfigs& configs) {\n  if (reset) {\n    // Keep the array empty.\n    return;\n  }\n  constexpr int INDEX_SUPER_ELLIPSE_TYPE = 0;\n  constexpr int INDEX_SUPER_ELLIPSE_RADIUS_X = 1;\n  constexpr int INDEX_SUPER_ELLIPSE_RADIUS_X_UNIT = 2;\n  constexpr int INDEX_SUPER_ELLIPSE_RADIUS_Y = 3;\n  constexpr int INDEX_SUPER_ELLIPSE_RADIUS_Y_UNIT = 4;\n  constexpr int INDEX_SUPER_ELLIPSE_EXPONENT_X = 5;\n  constexpr int INDEX_SUPER_ELLIPSE_EXPONENT_Y = 6;\n  constexpr int INDEX_SUPER_ELLIPSE_CENTER_X = 7;\n  constexpr int INDEX_SUPER_ELLIPSE_CENTER_X_UNIT = 8;\n  constexpr int INDEX_SUPER_ELLIPSE_CENTER_Y = 9;\n  constexpr int INDEX_SUPER_ELLIPSE_CENTER_Y_UNIT = 10;\n\n  // Append type\n  out->push_back(raw->get(INDEX_SUPER_ELLIPSE_TYPE));\n\n  // Convert style length to platform length\n  NLength radius_x = NLength::MakeAutoNLength();\n  NLength radius_y = NLength::MakeAutoNLength();\n  NLength center_x = NLength::MakeAutoNLength();\n  NLength center_y = NLength::MakeAutoNLength();\n\n  GetLengthData(radius_x, raw->get(INDEX_SUPER_ELLIPSE_RADIUS_X),\n                raw->get(INDEX_SUPER_ELLIPSE_RADIUS_X_UNIT), context, configs);\n  GetLengthData(radius_y, raw->get(INDEX_SUPER_ELLIPSE_RADIUS_Y),\n                raw->get(INDEX_SUPER_ELLIPSE_RADIUS_Y_UNIT), context, configs);\n  GetLengthData(center_x, raw->get(INDEX_SUPER_ELLIPSE_CENTER_X),\n                raw->get(INDEX_SUPER_ELLIPSE_CENTER_X_UNIT), context, configs);\n  GetLengthData(center_y, raw->get(INDEX_SUPER_ELLIPSE_CENTER_Y),\n                raw->get(INDEX_SUPER_ELLIPSE_CENTER_Y_UNIT), context, configs);\n\n  // re-build array, [type, rx, urx, ry, ury, ex, ey, cx, ucx, cy, ucy]\n  AddLengthToArray(out, radius_x);\n  AddLengthToArray(out, radius_y);\n  out->push_back(raw->get(INDEX_SUPER_ELLIPSE_EXPONENT_X));\n  out->push_back(raw->get(INDEX_SUPER_ELLIPSE_EXPONENT_Y));\n  AddLengthToArray(out, center_x);\n  AddLengthToArray(out, center_y);\n}\n\nvoid CSSStyleUtils::ComputeBasicShapeInset(\n    const fml::RefPtr<lepus::CArray>& raw, bool reset,\n    const fml::RefPtr<lepus::CArray>& dst,\n    const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  if (reset) {\n    // keep the dst array empty.\n    return;\n  }\n  constexpr int INDEX_INSET_TYPE = 0;\n  dst->push_back(raw->get(INDEX_INSET_TYPE));\n  NLength length = NLength::MakeAutoNLength();\n\n  // Get inset for the four sides.\n  constexpr int ARRAY_LENGTH_INSET_RECT = 8;\n  for (int i = 1; i < ARRAY_LENGTH_INSET_RECT; i += 2) {\n    GetLengthData(length, raw->get(i), raw->get(i + 1), context, configs);\n    AddLengthToArray(dst, length);\n  }\n  constexpr int ARRAY_LENGTH_INSET_ROUNDED = 25;\n  // raw array is arranged [type, top, unit, right, unit, bottom, unit, left,\n  // unit, top-left-x, unit, top-left-y, unit, top-right-x, unit, top-right-y,\n  // unit, bottom-right-x, unit, bottom-right-y, unit, bottom-left-x, unit,\n  // bottom-left-y, unit]\n  if (raw->size() == ARRAY_LENGTH_INSET_ROUNDED) {\n    // Get <border-radius> for the four sides.\n    for (int i = ARRAY_LENGTH_INSET_RECT + 1; i < ARRAY_LENGTH_INSET_ROUNDED;\n         i += 2) {\n      GetLengthData(length, raw->get(i), raw->get(i + 1), context, configs);\n      AddLengthToArray(dst, length);\n    }\n  }\n  constexpr int ARRAY_LENGTH_INSET_SUPER_ELLIPSE = 27;\n  // raw array is arranged [type, top, unit, right, unit, bottom, unit, left,\n  // unit, ex, ey, top-left-x, unit, top-left-y, unit, top-right-x, unit,\n  // top-right-y, unit, bottom-right-x, unit, bottom-right-y, unit,\n  // bottom-left-x, unit, bottom-left-y, unit]\n  if (raw->size() == ARRAY_LENGTH_INSET_SUPER_ELLIPSE) {\n    // get exponent for [ex, ey]\n    dst->push_back(raw->get(ARRAY_LENGTH_INSET_RECT + 1));\n    dst->push_back(raw->get(ARRAY_LENGTH_INSET_RECT + 2));\n    // Get <border-radius> for the four sides.\n    for (int i = ARRAY_LENGTH_INSET_RECT + 3;\n         i < ARRAY_LENGTH_INSET_SUPER_ELLIPSE; i += 2) {\n      GetLengthData(length, raw->get(i), raw->get(i + 1), context, configs);\n      AddLengthToArray(dst, length);\n    }\n  }\n}\n\nbool CSSStyleUtils::IsBorderLengthLegal(std::string value) {\n  return value == \"thick\" || value == \"medium\" || value == \"thin\" ||\n         base::EndsWith(value, \"px\") || base::EndsWith(value, \"rpx\") ||\n         base::EndsWith(value, \"em\") || base::EndsWith(value, \"rem\") ||\n         base::EndsWith(value, \"%\");\n}\n\nvoid CSSStyleUtils::ComputeRadialGradient(\n    const lepus::Value& gradient_data, const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  // Gradient shape array ref:\n  // [shape, shape_size, pos_x, pos_x_value, pos_y, pos_y_value, ...]\n  auto shape_arr = gradient_data.Array()->get(0).Array();\n  // Convert the CSS value to platform value\n  if (shape_arr->get(1).Number() ==\n      static_cast<uint32_t>(starlight::RadialGradientSizeType::kLength)) {\n    while (shape_arr->size() > 10) {\n      shape_arr->pop_back();\n    }\n    // Ignore the result of to length\n    CSSStyleUtils::AddLengthToArray(\n        shape_arr, CSSStyleUtils::ToLength(\n                       tasm::CSSValue(shape_arr->get(7),\n                                      static_cast<tasm::CSSValuePattern>(\n                                          shape_arr->get(6).Number())),\n                       context, configs)\n                       .first);\n    CSSStyleUtils::AddLengthToArray(\n        shape_arr, CSSStyleUtils::ToLength(\n                       tasm::CSSValue(shape_arr->get(9),\n                                      static_cast<tasm::CSSValuePattern>(\n                                          shape_arr->get(8).Number())),\n                       context, configs)\n                       .first);\n  }\n}\n\nvoid CSSStyleUtils::ComputeConicGradient(\n    const lepus::Value& gradient_data, const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  // [center_x, center_x_unit, center_y, center_y_unit]\n  auto center_arr = gradient_data.Array()->get(1).Array();\n  auto center_result = lepus::CArray::Create();\n  AddLengthToArray(center_result,\n                   ToLength(tasm::CSSValue(center_arr->get(0),\n                                           static_cast<tasm::CSSValuePattern>(\n                                               center_arr->get(1).Number())),\n                            context, configs)\n                       .first);\n  AddLengthToArray(center_result,\n                   ToLength(tasm::CSSValue(center_arr->get(2),\n                                           static_cast<tasm::CSSValuePattern>(\n                                               center_arr->get(3).Number())),\n                            context, configs)\n                       .first);\n  gradient_data.Array()->set(1, lepus::Value(center_result));\n}\n\nlepus::Value CSSStyleUtils::GetGradientArrayFromString(\n    const char* gradient_def, size_t gradient_def_length,\n    const tasm::CssMeasureContext& context,\n    const tasm::CSSParserConfigs& configs) {\n  auto parser = lynx::tasm::CSSStringParser(\n      gradient_def, static_cast<uint32_t>(gradient_def_length), configs);\n  auto value = parser.ParseTextColor();\n  if (!value.IsArray()) {\n    return lepus::Value();\n  }\n  const auto& arr = value.GetArray();\n  auto type = arr->get(0).Number();\n\n  if (type == static_cast<int32_t>(BackgroundImageType::kRadialGradient)) {\n    ComputeRadialGradient(arr->get(1), context, configs);\n  } else if (type ==\n             static_cast<int32_t>(BackgroundImageType::kConicGradient)) {\n    ComputeConicGradient(arr->get(1), context, configs);\n  }\n  return value.GetValue();\n}\n\n}  // namespace starlight\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_style_utils.h",
    "content": "// Copyright 2017 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_STYLE_UTILS_H_\n#define CORE_RENDERER_CSS_CSS_STYLE_UTILS_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/flex_optional.h\"\n#include \"base/include/value/array.h\"\n#include \"core/base/lynx_export.h\"\n#include \"core/renderer/css/css_font_face_token.h\"\n#include \"core/renderer/css/css_fragment.h\"\n#include \"core/renderer/css/css_keyframes_token.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/css/parser/css_parser_configs.h\"\n#include \"core/renderer/css/text_attributes.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/renderer/starlight/types/layout_unit.h\"\n#include \"core/style/shadow_data.h\"\n#include \"core/style/transform_raw_data.h\"\n#include \"core/style/transition_data.h\"\n\nnamespace lynx {\nnamespace tasm {\nclass LayoutContext;\nclass LynxEnvConfig;\nclass CssMeasureContext;\n}  // namespace tasm\nnamespace starlight {\n\nstruct AnimationData;\nstruct TimingFunctionData;\nstruct FilterData;\n\nclass NLength;\n\nclass CSSStyleUtils {\n public:\n  template <typename T>\n  static inline void PrepareOptional(T& optional) {\n    if (!optional) {\n      optional.emplace();\n    }\n  }\n\n  template <typename T>\n  static inline void PrepareOptional(T& optional,\n                                     bool css_align_with_legacy_w3c) {\n    if (!optional) {\n      optional.emplace(css_align_with_legacy_w3c);\n    }\n  }\n\n  static inline void PrepareOptionalForTextAttributes(\n      base::flex_optional<starlight::TextAttributes>& optional,\n      float default_font_size) {\n    if (!optional) {\n      optional = starlight::TextAttributes(default_font_size);\n    }\n  }\n\n  static std::pair<NLength, bool> ToLength(\n      const tasm::CSSValue& value, const tasm::CssMeasureContext& context,\n      const tasm::CSSParserConfigs& configs, bool is_font_relevant = false);\n\n  static base::flex_optional<float> ResolveFontSize(\n      const tasm::CSSValue& value, const tasm::LynxEnvConfig& config,\n      const starlight::LayoutUnit& vw_base,\n      const starlight::LayoutUnit& vh_base, double cur_node_font_size,\n      double root_node_font_size, const tasm::CSSParserConfigs& configs);\n\n  static float RoundValueToPixelGrid(\n      const float value, const float physical_pixels_per_layout_unit);\n\n  // Only air element is using this method now. After air element completes the\n  // optimization that flush keyframes by names, this method can be removed.\n  static lepus::Value ResolveCSSKeyframes(\n      const tasm::CSSKeyframesTokenMap& frames,\n      const tasm::CssMeasureContext& context,\n      const tasm::CSSParserConfigs& configs);\n\n  static lepus::Value ResolveCSSKeyframesToken(\n      tasm::CSSKeyframesToken* token, const tasm::CssMeasureContext& context,\n      const tasm::CSSParserConfigs& configs);\n\n  static bool ComputeBoolStyle(const tasm::CSSValue& value, const bool reset,\n                               bool& dest, const bool default_value,\n                               const char* msg,\n                               const tasm::CSSParserConfigs& configs);\n  static bool ComputeFloatStyle(const tasm::CSSValue& value, const bool reset,\n                                float& dest, const float default_value,\n                                const char* msg,\n                                const tasm::CSSParserConfigs& configs);\n\n  static bool ComputeIntStyle(const tasm::CSSValue& value, const bool reset,\n                              int& dest, const int default_value,\n                              const char* msg,\n                              const tasm::CSSParserConfigs& configs);\n  static bool ComputeLengthStyle(const tasm::CSSValue& value, const bool reset,\n                                 const tasm::CssMeasureContext& context,\n                                 NLength& dest, const NLength& default_value,\n                                 const tasm::CSSParserConfigs& configs);\n  static bool ComputeGridTrackSizing(\n      const tasm::CSSValue& value, const bool reset,\n      const tasm::CssMeasureContext& context, std::vector<NLength>& min_dest,\n      std::vector<NLength>& max_dest, const std::vector<NLength>& default_value,\n      const char* msg, const tasm::CSSParserConfigs& configs);\n\n  template <typename T>\n  static bool ComputeEnumStyle(const tasm::CSSValue& value, bool reset, T& dest,\n                               const T default_value, const char* msg,\n                               const tasm::CSSParserConfigs& configs) {\n    auto old_value = dest;\n    if (reset) {\n      dest = default_value;\n    } else {\n      CSS_HANDLER_FAIL_IF_NOT(value.IsEnum(), configs.enable_css_strict_mode,\n                              msg)\n      dest = static_cast<T>(value.GetNumber());\n    }\n    return old_value != dest;\n  }\n\n  static bool CalculateLength(const tasm::CSSValue& value, float& result,\n                              const tasm::CssMeasureContext& context,\n                              const tasm::CSSParserConfigs& configs);\n\n  static void ConvertCSSValueToNumber(const tasm::CSSValue& value,\n                                      float& result, PlatformLengthUnit& unit,\n                                      const tasm::CssMeasureContext& context,\n                                      const tasm::CSSParserConfigs& configs);\n\n  static bool ComputeUIntStyle(const tasm::CSSValue& value, const bool reset,\n                               unsigned int& dest,\n                               const unsigned int default_value,\n                               const char* msg,\n                               const tasm::CSSParserConfigs& configs);\n  static bool ComputeShadowStyle(\n      const tasm::CSSValue& value, const bool reset,\n      base::flex_optional<base::InlineVector<ShadowData, 1>>& shadow,\n      const tasm::CssMeasureContext& context,\n      const tasm::CSSParserConfigs& configs);\n\n  static bool ComputeTransform(\n      const tasm::CSSValue& value, bool reset,\n      base::flex_optional<base::InlineVector<TransformRawData, 1>>& raw,\n      const tasm::CssMeasureContext& context,\n      const tasm::CSSParserConfigs& configs);\n\n  static bool IsLayoutRelatedTransform(tasm::CSSPropertyID id,\n                                       const tasm::CSSValue& value);\n\n  static lepus_value TransformToLepus(\n      const base::Vector<TransformRawData>& items);\n\n  static bool ComputeFilter(const tasm::CSSValue& value, bool reset,\n                            base::flex_optional<FilterData>& filter,\n                            const tasm::CssMeasureContext,\n                            const tasm::CSSParserConfigs& configs);\n\n  static lepus_value FilterToLepus(\n      const base::flex_optional<FilterData>& filter);\n\n  static bool ComputeStringStyle(const tasm::CSSValue& value, const bool reset,\n                                 base::String& dest,\n                                 const base::String& default_value,\n                                 const char* msg,\n                                 const tasm::CSSParserConfigs& configs);\n  static bool ComputeTimingFunction(const lepus::Value& value, const bool reset,\n                                    TimingFunctionData& timing_function,\n                                    const tasm::CSSParserConfigs& configs);\n  static bool ComputeLongStyle(const tasm::CSSValue& value, const bool reset,\n                               long& dest, const long default_value,\n                               const char* msg,\n                               const tasm::CSSParserConfigs& configs);\n\n  template <typename T, typename F0, typename F1>\n  static bool SetAnimationProperty(T& anim, const tasm::CSSValue& value,\n                                   F0 const& reset_func, F1 const& compute_func,\n                                   const bool reset,\n                                   const tasm::CSSParserConfigs& configs) {\n    if (reset) {\n      if (anim) {\n        for (auto& it : *anim) {\n          reset_func(it);\n        }\n      }\n      return true;\n    }\n    CSS_HANDLER_FAIL_IF_NOT(value.IsEnum() || value.IsNumber() ||\n                                value.IsString() || value.IsArray(),\n                            configs.enable_css_strict_mode,\n                            \"Animation or Transition property must \"\n                            \"be enum, number, string or array!\")\n    CSSStyleUtils::PrepareOptional(anim);\n    if (anim->empty()) {\n      anim->emplace_back();\n    }\n    bool changed = false;\n    size_t input_size;\n    if (value.IsArray()) {\n      auto arr = value.GetArray();\n      input_size = arr->size();\n      for (size_t i = 0; i < arr->size(); i++) {\n        if (anim->size() < i + 1) {\n          anim->emplace_back();\n        }\n        changed |= compute_func(arr->get(i), (*anim)[i], reset);\n      }\n    } else {\n      input_size = 1;\n      changed = compute_func(value.GetValue(), anim->front(), reset);\n    }\n    changed = changed || input_size != anim->size();\n    // Reset the remaining values\n    for (size_t i = input_size; i < anim->size(); ++i) {\n      reset_func((*anim)[i]);\n    }\n    return changed;\n  }\n\n  static bool ComputeHeroAnimation(const tasm::CSSValue& value,\n                                   const bool reset,\n                                   base::flex_optional<AnimationData>& anim,\n                                   const char* msg,\n                                   const tasm::CSSParserConfigs& configs);\n  static bool ComputeAnimation(const lepus::Value& value, AnimationData& anim,\n                               const char* msg,\n                               const tasm::CSSParserConfigs& configs);\n  static lepus_value AnimationDataToLepus(AnimationData& anim);\n\n  static std::shared_ptr<tasm::StyleMap> ProcessCSSAttrsMap(\n      const lepus::Value& value, const tasm::CSSParserConfigs& configs);\n  static void UpdateCSSKeyframes(tasm::CSSKeyframesTokenMap& keyframes_map,\n                                 const base::String& name,\n                                 const lepus::Value& keyframes,\n                                 const tasm::CSSParserConfigs& configs);\n  static float GetBorderWidthFromLengthToFloat(\n      const NLength& value, const tasm::CssMeasureContext& context);\n\n  static void AddLengthToArray(const fml::RefPtr<lepus::CArray>& array,\n                               const NLength& length);\n  static void ComputeBasicShapeEllipse(const fml::RefPtr<lepus::CArray>& raw,\n                                       bool reset,\n                                       fml::RefPtr<lepus::CArray>& out,\n                                       const tasm::CssMeasureContext& context,\n                                       const tasm::CSSParserConfigs& configs);\n  static void ComputeBasicShapeCircle(const fml::RefPtr<lepus::CArray>& raw,\n                                      bool reset,\n                                      fml::RefPtr<lepus::CArray>& out,\n                                      const tasm::CssMeasureContext& context,\n                                      const tasm::CSSParserConfigs& configs);\n  static void ComputeBasicShapePath(const fml::RefPtr<lepus::CArray>& raw,\n                                    bool reset,\n                                    fml::RefPtr<lepus::CArray>& out);\n  static void ComputeSuperEllipse(const fml::RefPtr<lepus::CArray>& raw,\n                                  bool reset, fml::RefPtr<lepus::CArray>& out,\n                                  const tasm::CssMeasureContext& context,\n                                  const tasm::CSSParserConfigs& configs);\n  static void ComputeBasicShapeInset(const fml::RefPtr<lepus::CArray>& raw,\n                                     bool reset,\n                                     const fml::RefPtr<lepus::CArray>& dst,\n                                     const tasm::CssMeasureContext& context,\n                                     const tasm::CSSParserConfigs& configs);\n\n  static bool IsBorderLengthLegal(std::string value);\n\n  static void ComputeRadialGradient(const lepus::Value& gradient_data,\n                                    const tasm::CssMeasureContext& context,\n                                    const tasm::CSSParserConfigs& configs);\n\n  static void ComputeConicGradient(const lepus::Value& gradient_data,\n                                   const tasm::CssMeasureContext& context,\n                                   const tasm::CSSParserConfigs& configs);\n\n  static LYNX_EXPORT lepus::Value GetGradientArrayFromString(\n      const char* gradient_def, size_t gradient_def_length,\n      const tasm::CssMeasureContext& context,\n      const tasm::CSSParserConfigs& configs);\n\n private:\n  static lepus::Value ResolveCSSKeyframesStyle(\n      tasm::StyleMap* attrs, const tasm::CssMeasureContext& context,\n      const tasm::CSSParserConfigs& configs);\n};\n\n}  // namespace starlight\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_STYLE_UTILS_H_\n"
  },
  {
    "path": "core/renderer/css/css_style_utils_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/css_style_utils.h\"\n\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/style/filter_data.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace starlight {\n\nTEST(CssStyleUtils, SetZIndex) {\n  ComputedCSSStyle computed_css_style(1.f, 1.f);\n\n  computed_css_style.SetEnableZIndex(false);\n\n  tasm::CSSParserConfigs configs;\n  lepus::Value z_index_raw_value = lepus::Value(\"1\");\n  auto z_index = tasm::UnitHandler::Process(tasm::kPropertyIDZIndex,\n                                            z_index_raw_value, configs);\n\n  EXPECT_FALSE(computed_css_style.SetValue(tasm::kPropertyIDZIndex,\n                                           z_index[tasm::kPropertyIDZIndex]));\n  EXPECT_FALSE(computed_css_style.HasZIndex());\n\n  computed_css_style.SetEnableZIndex(true);\n\n  z_index_raw_value = lepus::Value(\"0\");\n  z_index = tasm::UnitHandler::Process(tasm::kPropertyIDZIndex,\n                                       z_index_raw_value, configs);\n  EXPECT_FALSE(computed_css_style.SetValue(tasm::kPropertyIDZIndex,\n                                           z_index[tasm::kPropertyIDZIndex]));\n  EXPECT_TRUE(computed_css_style.HasZIndex());\n\n  z_index_raw_value = lepus::Value(\"1\");\n  z_index = tasm::UnitHandler::Process(tasm::kPropertyIDZIndex,\n                                       z_index_raw_value, configs);\n  EXPECT_TRUE(computed_css_style.SetValue(tasm::kPropertyIDZIndex,\n                                          z_index[tasm::kPropertyIDZIndex]));\n  EXPECT_TRUE(computed_css_style.HasZIndex());\n\n  EXPECT_TRUE(computed_css_style.ResetValue(tasm::kPropertyIDZIndex));\n  EXPECT_FALSE(computed_css_style.HasZIndex());\n}\n\nTEST(CssStyleUtils, ComputeFilter) {\n  ComputedCSSStyle computedCssStyle(1.f, 1.f);\n  tasm::CSSParserConfigs configs;\n  tasm::CssMeasureContext length_context = computedCssStyle.GetMeasureContext();\n  base::flex_optional<FilterData> output;\n  auto arr = lepus::CArray::Create();\n  bool ret = false;\n  // CSSValue to data\n  arr->push_back(lepus::Value(static_cast<int>(FilterType::kNone)));\n  auto input = tasm::CSSValue(lepus::Value(arr), tasm::CSSValuePattern::ARRAY);\n\n  ret = CSSStyleUtils::ComputeFilter(input, false, output, length_context,\n                                     configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ((*output).type, FilterType::kNone);\n\n  arr->Erase(0);\n  arr->push_back(lepus::Value(static_cast<int>(FilterType::kGrayscale)));\n\n  ret = CSSStyleUtils::ComputeFilter(input, false, output, length_context,\n                                     configs);\n  EXPECT_FALSE(ret);\n\n  // grayscale(80%)\n  arr->push_back(lepus::Value(80));\n  arr->push_back(\n      lepus::Value(static_cast<int>(tasm::CSSValuePattern::PERCENT)));\n  ret = CSSStyleUtils::ComputeFilter(input, false, output, length_context,\n                                     configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ((*output).type, FilterType::kGrayscale);\n  EXPECT_EQ((*output).amount.GetType(), NLengthType::kNLengthPercentage);\n  EXPECT_EQ((*output).amount.GetRawValue(), 80.0f);\n\n  arr->Erase(0, 3);\n  // blur(20px)\n  arr->push_back(lepus::Value(static_cast<int>(FilterType::kBlur)));\n  arr->push_back(lepus::Value(20));\n  arr->push_back(lepus::Value(static_cast<int>(tasm::CSSValuePattern::PX)));\n  ret = CSSStyleUtils::ComputeFilter(input, false, output, length_context,\n                                     configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ((*output).type, FilterType::kBlur);\n  EXPECT_EQ((*output).amount.GetType(), NLengthType::kNLengthUnit);\n  EXPECT_EQ((*output).amount.GetRawValue(), 20);\n\n  // data to lepus\n  auto lepus_value = CSSStyleUtils::FilterToLepus(output);\n  EXPECT_TRUE(lepus_value.IsArray());\n  arr = lepus_value.Array();\n  EXPECT_EQ(arr->size(), 3);\n  EXPECT_EQ(arr->get(0).Number(), static_cast<int>(FilterType::kBlur));\n  EXPECT_EQ(arr->get(1).Number(), 20);\n  EXPECT_EQ(arr->get(2).Number(), static_cast<int>(PlatformLengthUnit::NUMBER));\n\n  // data cmp ==\n  FilterData target;\n  target.Reset();\n  EXPECT_FALSE(output == target);\n\n  // reset\n  ret = CSSStyleUtils::ComputeFilter(input, true, output, length_context,\n                                     configs);\n  EXPECT_TRUE(ret);\n  // data cmp !=\n  EXPECT_TRUE(output == std::nullopt);\n  CSSStyleUtils::PrepareOptional(output);\n  EXPECT_FALSE(output != target);\n}\n\nTEST(CssStyleUtils, ComputeIntStyle) {\n  tasm::CSSParserConfigs configs;\n  int dest = 1;\n\n  // Non-reset: assign positive integer.\n  tasm::CSSValue positive_value(10, tasm::CSSValuePattern::NUMBER);\n  bool changed = CSSStyleUtils::ComputeIntStyle(\n      positive_value, /*reset=*/false, dest, /*default_value=*/0,\n      \"int style must be number!\", configs);\n  EXPECT_TRUE(changed);\n  EXPECT_EQ(dest, 10);\n\n  // Non-reset: assign negative integer.\n  tasm::CSSValue negative_value(-5, tasm::CSSValuePattern::NUMBER);\n  changed = CSSStyleUtils::ComputeIntStyle(\n      negative_value, /*reset=*/false, dest, /*default_value=*/0,\n      \"int style must be number!\", configs);\n  EXPECT_TRUE(changed);\n  EXPECT_EQ(dest, -5);\n\n  // Non-reset: assign zero and then keep the same value.\n  tasm::CSSValue zero_value(0, tasm::CSSValuePattern::NUMBER);\n  changed = CSSStyleUtils::ComputeIntStyle(\n      zero_value, /*reset=*/false, dest, /*default_value=*/0,\n      \"int style must be number!\", configs);\n  EXPECT_TRUE(changed);\n  EXPECT_EQ(dest, 0);\n\n  changed = CSSStyleUtils::ComputeIntStyle(\n      zero_value, /*reset=*/false, dest, /*default_value=*/0,\n      \"int style must be number!\", configs);\n  EXPECT_FALSE(changed);\n  EXPECT_EQ(dest, 0);\n\n  // Reset: no-op when default equals current value.\n  changed = CSSStyleUtils::ComputeIntStyle(\n      zero_value, /*reset=*/true, dest, /*default_value=*/0,\n      \"int style must be number!\", configs);\n  EXPECT_FALSE(changed);\n  EXPECT_EQ(dest, 0);\n\n  // Reset: change to a new default value.\n  changed = CSSStyleUtils::ComputeIntStyle(\n      zero_value, /*reset=*/true, dest, /*default_value=*/100,\n      \"int style must be number!\", configs);\n  EXPECT_TRUE(changed);\n  EXPECT_EQ(dest, 100);\n\n  // Non-number input should not modify dest and returns false.\n  tasm::CSSValue invalid_value(\"not_number\", tasm::CSSValuePattern::STRING,\n                               tasm::CSSValueType::DEFAULT);\n  changed = CSSStyleUtils::ComputeIntStyle(\n      invalid_value, /*reset=*/false, dest, /*default_value=*/100,\n      \"int style must be number!\", configs);\n  EXPECT_FALSE(changed);\n  EXPECT_EQ(dest, 100);\n\n  // Beyond FLT_MAX value.\n  dest = 99999999;\n  tasm::CSSValue new_value(100000000, tasm::CSSValuePattern::NUMBER);\n  changed = CSSStyleUtils::ComputeIntStyle(\n      new_value, /*reset=*/false, dest, /*default_value=*/0,\n      \"int style must be number!\", configs);\n  EXPECT_TRUE(changed);\n  EXPECT_EQ(dest, 100000000);\n}\n\nTEST(CssStyleUtils, GetLengthData) {\n  ComputedCSSStyle computedCssStyle(1.0f, 3.0f);\n  tasm::CSSParserConfigs configs;\n  tasm::CssMeasureContext length_context = computedCssStyle.GetMeasureContext();\n  // test 0.25px\n  NLength len = NLength::MakeAutoNLength();\n  lepus::Value unit = lepus::Value(static_cast<int>(tasm::CSSValuePattern::PX));\n  lepus::Value value = lepus::Value(0.25);\n  extern void GetLengthData(NLength & length, const lepus_value& value,\n                            const lepus_value& unit,\n                            const tasm::CssMeasureContext& context,\n                            const tasm::CSSParserConfigs& configs);\n  GetLengthData(len, value, unit, length_context, configs);\n  EXPECT_TRUE(len.IsUnit());\n  EXPECT_FLOAT_EQ(len.GetRawValue(), 1.0f / 3.0f);\n\n  // test 25%\n  unit = lepus::Value(static_cast<int>(tasm::CSSValuePattern::PERCENT));\n  value = lepus::Value(0.25);\n  extern void GetLengthData(NLength & length, const lepus_value& value,\n                            const lepus_value& unit,\n                            const tasm::CssMeasureContext& context,\n                            const tasm::CSSParserConfigs& configs);\n  GetLengthData(len, value, unit, length_context, configs);\n  EXPECT_TRUE(len.IsPercent());\n  EXPECT_FLOAT_EQ(len.GetRawValue(), 0.25);\n\n  // test 0.25ppx\n  unit = lepus::Value(static_cast<int>(tasm::CSSValuePattern::PPX));\n  value = lepus::Value(0.25);\n  extern void GetLengthData(NLength & length, const lepus_value& value,\n                            const lepus_value& unit,\n                            const tasm::CssMeasureContext& context,\n                            const tasm::CSSParserConfigs& configs);\n  GetLengthData(len, value, unit, length_context, configs);\n  EXPECT_TRUE(len.IsUnit());\n  EXPECT_FLOAT_EQ(len.GetRawValue(), 0);\n\n  // test 0.55ppx\n  unit = lepus::Value(static_cast<int>(tasm::CSSValuePattern::PPX));\n  value = lepus::Value(0.55);\n  extern void GetLengthData(NLength & length, const lepus_value& value,\n                            const lepus_value& unit,\n                            const tasm::CssMeasureContext& context,\n                            const tasm::CSSParserConfigs& configs);\n  GetLengthData(len, value, unit, length_context, configs);\n  EXPECT_TRUE(len.IsUnit());\n  EXPECT_FLOAT_EQ(len.GetRawValue(), 1.0f / 3.0f);\n}\n\nTEST(CSSStyleUtils, ComputeBasicShapes) {\n  tasm::CSSParserConfigs configs;\n  ComputedCSSStyle computed_css_style(1.0f, 1.0f);\n  tasm::CssMeasureContext context = computed_css_style.GetMeasureContext();\n  // Test compute path\n  auto path =\n      lepus::Value(R\"(path('M 0 200 L 0,75 A 5,5 0,0,1 150,75 L 200 200 z')\");\n  auto input_arr = lepus::CArray::Create();\n  input_arr->push_back(\n      lepus::Value(static_cast<uint32_t>(starlight::BasicShapeType::kPath)));\n  input_arr->push_back(path);\n  fml::RefPtr<lepus::CArray> out_arr{nullptr};\n  CSSStyleUtils::ComputeBasicShapePath(input_arr, false, out_arr);\n  EXPECT_EQ(out_arr->size(), 2);\n  EXPECT_EQ(out_arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kPath));\n  out_arr = lepus::CArray::Create();\n  CSSStyleUtils::ComputeBasicShapePath(input_arr, true, out_arr);\n  EXPECT_EQ(out_arr->size(), 0);\n\n  // Test compute circle\n  lepus::Value circle = lepus::Value(R\"(circle(20px at left bottom))\");\n  auto circle_value =\n      tasm::UnitHandler::Process(tasm::kPropertyIDClipPath, circle, configs);\n  auto circle_raw = circle_value[tasm::kPropertyIDClipPath].GetArray();\n  out_arr = lepus::CArray::Create();\n  CSSStyleUtils::ComputeBasicShapeCircle(circle_raw, false, out_arr, context,\n                                         configs);\n  EXPECT_EQ(out_arr->size(), 7);\n  EXPECT_EQ(out_arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kCircle));\n  out_arr = lepus::CArray::Create();\n  CSSStyleUtils::ComputeBasicShapeCircle(circle_raw, true, out_arr, context,\n                                         configs);\n  EXPECT_EQ(out_arr->size(), 0);\n\n  // Test compute ellipse\n  lepus::Value ellipse = lepus::Value(R\"(ellipse(20px 30px at left bottom))\");\n  auto ellipse_value =\n      tasm::UnitHandler::Process(tasm::kPropertyIDClipPath, ellipse, configs);\n  auto ellipse_raw = ellipse_value[tasm::kPropertyIDClipPath].GetArray();\n  out_arr = lepus::CArray::Create();\n  CSSStyleUtils::ComputeBasicShapeEllipse(ellipse_raw, false, out_arr, context,\n                                          configs);\n  EXPECT_EQ(out_arr->size(), 9);\n  EXPECT_EQ(out_arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kEllipse));\n\n  out_arr = lepus::CArray::Create();\n  CSSStyleUtils::ComputeBasicShapeEllipse(ellipse_raw, true, out_arr, context,\n                                          configs);\n  EXPECT_TRUE(out_arr->size() == 0);\n}\n\nTEST(CSSStyleUtils, ComputeSuperEllipse) {\n#define UNIT_PX static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PX)\n#define UNIT_PERCENT static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT)\n#define PLATFORM_NUMBER \\\n  static_cast<uint32_t>(lynx::starlight::PlatformLengthUnit::NUMBER)\n#define PLATFORM_PERCENT \\\n  static_cast<uint32_t>(lynx::starlight::PlatformLengthUnit::PERCENTAGE)\n#define BUILD_ELLIPSE_ARR(arr, rx, urx, ry, ury, ex, ey, cx, ucx, cy, ucy) \\\n  arr->push_back(lynx::lepus::Value(rx));                                  \\\n  arr->push_back(lynx::lepus::Value(urx));                                 \\\n  arr->push_back(lynx::lepus::Value(ry));                                  \\\n  arr->push_back(lynx::lepus::Value(ury));                                 \\\n  arr->push_back(lynx::lepus::Value(ex));                                  \\\n  arr->push_back(lynx::lepus::Value(ey));                                  \\\n  arr->push_back(lynx::lepus::Value(cx));                                  \\\n  arr->push_back(lynx::lepus::Value(ucx));                                 \\\n  arr->push_back(lynx::lepus::Value(cy));                                  \\\n  arr->push_back(lynx::lepus::Value(ucy));\n\n  auto arr = lepus::CArray::Create();\n  arr->push_back(lepus::Value(\n      static_cast<uint32_t>(starlight::BasicShapeType::kSuperEllipse)));\n\n#define CHECK_COMPUTED_SUPER_ELLIPSE(arr, rx, urx, ry, ury, ex, ey, cx, ucx, \\\n                                     cy, ucy)                                \\\n  EXPECT_FLOAT_EQ(arr->get(1).Number(), rx);                                 \\\n  EXPECT_EQ(arr->get(2).Number(), urx);                                      \\\n  EXPECT_FLOAT_EQ(arr->get(3).Number(), ry);                                 \\\n  EXPECT_EQ(arr->get(4).Number(), ury);                                      \\\n  EXPECT_FLOAT_EQ(arr->get(5).Number(), ex);                                 \\\n  EXPECT_EQ(arr->get(6).Number(), ey);                                       \\\n  EXPECT_FLOAT_EQ(arr->get(7).Number(), cx);                                 \\\n  EXPECT_EQ(arr->get(8).Number(), ucx);                                      \\\n  EXPECT_FLOAT_EQ(arr->get(9).Number(), cy);                                 \\\n  EXPECT_EQ(arr->get(10).Number(), ucy);\n\n  BUILD_ELLIPSE_ARR(arr, 30, UNIT_PX, 40, UNIT_PERCENT, 5, 9, 100, UNIT_PERCENT,\n                    20, UNIT_PX);\n\n  auto out = lepus::CArray::Create();\n  ComputedCSSStyle computedCssStyle(1.0f, 1.0f);\n  tasm::CSSParserConfigs configs;\n\n  // Test normal compute\n  tasm::CssMeasureContext length_context = computedCssStyle.GetMeasureContext();\n  CSSStyleUtils::ComputeSuperEllipse(arr, false, out, length_context, configs);\n  CHECK_COMPUTED_SUPER_ELLIPSE(out, 30, PLATFORM_NUMBER, 0.4, PLATFORM_PERCENT,\n                               5, 9, 1, PLATFORM_PERCENT, 20, PLATFORM_NUMBER)\n\n  // test reset\n  out = lepus::CArray::Create();\n  arr = lepus::CArray::Create();\n  arr->push_back(lepus::Value(\n      static_cast<uint32_t>(starlight::BasicShapeType::kSuperEllipse)));\n  BUILD_ELLIPSE_ARR(arr, 30, UNIT_PX, 40, UNIT_PERCENT, 5, 9, 100, UNIT_PERCENT,\n                    20, UNIT_PX);\n  CSSStyleUtils::ComputeSuperEllipse(arr, true, out, length_context, configs);\n  EXPECT_EQ(out->size(), 0);\n\n#undef CHECK_COMPUTED_SUPER_ELLIPSE\n#undef BUILD_ELLIPSE_ARR\n#undef UNIT_PX\n#undef UNIT_PERCENT\n#undef PLATFORM_PERCENT\n#undef PLATFORM_NUMBER\n}\n\nTEST(CSSStyleUtils, SetBasicShapeInset) {\n  ComputedCSSStyle computedCssStyle(1.0f, 1.0f);\n  tasm::CSSParserConfigs configs;\n  tasm::CssMeasureContext context = computedCssStyle.GetMeasureContext();\n\n  {  // Test compute inset\n    lepus::Value inset =\n        lepus::Value(R\"(inset(20px 30% super-ellipse 2 3 10px))\");\n    auto inset_value =\n        tasm::UnitHandler::Process(tasm::kPropertyIDClipPath, inset, configs);\n    auto inset_raw = inset_value[tasm::kPropertyIDClipPath].GetArray();\n    auto out_arr = lepus::CArray::Create();\n    CSSStyleUtils::ComputeBasicShapeInset(inset_raw, false, out_arr, context,\n                                          configs);\n    EXPECT_EQ(out_arr->size(), 27);\n    EXPECT_EQ(out_arr->get(0).Number(),\n              static_cast<uint32_t>(starlight::BasicShapeType::kInset));\n    // top\n    EXPECT_EQ(out_arr->get(1).Number(), 20);\n    EXPECT_EQ(out_arr->get(2).Number(),\n              static_cast<uint32_t>(PlatformLengthUnit::NUMBER));\n    // right\n    EXPECT_FLOAT_EQ(out_arr->get(3).Number(), 0.3);\n    EXPECT_EQ(out_arr->get(4).Number(),\n              static_cast<uint32_t>(PlatformLengthUnit::PERCENTAGE));\n    // bottom\n    EXPECT_EQ(out_arr->get(5).Number(), 20);\n    EXPECT_EQ(out_arr->get(6).Number(),\n              static_cast<uint32_t>(PlatformLengthUnit::NUMBER));\n    // left\n    EXPECT_FLOAT_EQ(out_arr->get(7).Number(), 0.3);\n    EXPECT_EQ(out_arr->get(8).Number(),\n              static_cast<uint32_t>(PlatformLengthUnit::PERCENTAGE));\n    // exponent x & y\n    EXPECT_EQ(out_arr->get(9).Number(), 2);\n    EXPECT_EQ(out_arr->get(10).Number(), 3);\n\n    for (int i = 11; i < 27; i += 2) {\n      EXPECT_EQ(out_arr->get(i).Number(), 10);\n      EXPECT_EQ(out_arr->get(i + 1).Number(),\n                static_cast<uint32_t>(PlatformLengthUnit::NUMBER));\n    }\n\n    // test reset.\n    out_arr = lepus::CArray::Create();\n    CSSStyleUtils::ComputeBasicShapeInset(inset_raw, true, out_arr, context,\n                                          configs);\n    EXPECT_TRUE(out_arr->size() == 0);\n  }\n\n  {\n    // Test compute inset\n    lepus::Value ellipse = lepus::Value(R\"(inset(20px 30% round 45%))\");\n    auto ellipse_value =\n        tasm::UnitHandler::Process(tasm::kPropertyIDClipPath, ellipse, configs);\n    auto ellipse_raw = ellipse_value[tasm::kPropertyIDClipPath].GetArray();\n    auto out_arr = lepus::CArray::Create();\n    CSSStyleUtils::ComputeBasicShapeInset(ellipse_raw, false, out_arr, context,\n                                          configs);\n    EXPECT_EQ(out_arr->size(), 25);\n    EXPECT_EQ(out_arr->get(0).Number(),\n              static_cast<uint32_t>(starlight::BasicShapeType::kInset));\n    // top\n    EXPECT_EQ(out_arr->get(1).Number(), 20);\n    EXPECT_EQ(out_arr->get(2).Number(),\n              static_cast<uint32_t>(PlatformLengthUnit::NUMBER));\n    // right\n    EXPECT_FLOAT_EQ(out_arr->get(3).Number(), 0.3);\n    EXPECT_EQ(out_arr->get(4).Number(),\n              static_cast<uint32_t>(PlatformLengthUnit::PERCENTAGE));\n    // bottom\n    EXPECT_EQ(out_arr->get(5).Number(), 20);\n    EXPECT_EQ(out_arr->get(6).Number(),\n              static_cast<uint32_t>(PlatformLengthUnit::NUMBER));\n    // left\n    EXPECT_FLOAT_EQ(out_arr->get(7).Number(), 0.3);\n    EXPECT_EQ(out_arr->get(8).Number(),\n              static_cast<uint32_t>(PlatformLengthUnit::PERCENTAGE));\n\n    for (int i = 9; i < 25; i += 2) {\n      EXPECT_FLOAT_EQ(0.45, out_arr->get(i).Number());\n      EXPECT_EQ(out_arr->get(i + 1).Number(),\n                static_cast<uint32_t>(PlatformLengthUnit::PERCENTAGE));\n    }\n  }\n}\n\n}  // namespace starlight\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_utils.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/css_utils.h\"\n\n#include <cmath>\n\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace {\n\nusing vec2 = std::pair<float, float>;\n\n// Compute the radius to the closest/farthest side (depending on the compare\n// functor).\nvec2 RadiusToSide(float px, float py, float sx, float sy,\n                  RadialGradientShapeType shape,\n                  bool (*compare)(float, float)) {\n  float dx1 = fabs(px);\n  float dy1 = fabs(py);\n  float dx2 = fabs(px - sx);\n  float dy2 = fabs(py - sy);\n\n  float dx = compare(dx1, dx2) ? dx1 : dx2;\n  float dy = compare(dy1, dy2) ? dy1 : dy2;\n\n  if (shape == RadialGradientShapeType::kCircle)\n    return compare(dx, dy) ? vec2(dx, dx) : vec2(dy, dy);\n\n  return vec2(dx, dy);\n}\n\n// Compute the radius of an ellipse\ninline vec2 EllipseRadius(float offset_x, float offset_y, float aspect_ratio) {\n  // If the aspect_ratio is 0 or infinite, the ellipse is completely flat.\n  if (aspect_ratio == 0 || std::isinf(aspect_ratio) ||\n      std::isnan(aspect_ratio)) {\n    return vec2(0, 0);\n  }\n\n  float a = sqrtf(offset_x * offset_x +\n                  offset_y * offset_y * aspect_ratio * aspect_ratio);\n  return vec2(a, a / aspect_ratio);\n}\n\n// Compute the radius to the closest/farthest corner (depending on the compare\n// functor).\nvec2 RadiusToCorner(float px, float py, float sx, float sy,\n                    RadialGradientShapeType shape,\n                    bool (*compare)(float, float)) {\n  vec2 corners[] = {{0.f, 0.f}, {sx, 0}, {sx, sy}, {0, sy}};\n\n  unsigned corner_index = 0;\n  float distance = hypotf(px - corners[corner_index].first,\n                          py - corners[corner_index].second);\n  for (unsigned i = 1; i < std::size(corners); ++i) {\n    float new_distance = hypotf(px - corners[i].first, py - corners[i].second);\n    if (compare(new_distance, distance)) {\n      corner_index = i;\n      distance = new_distance;\n    }\n  }\n\n  if (shape == RadialGradientShapeType::kCircle) {\n    return vec2(distance, distance);\n  }\n\n  // If the end shape is an ellipse, the gradient-shape has the same ratio of\n  // width to height that it would if closest-side or farthest-side were\n  // specified, as appropriate.\n  const vec2 side_radius =\n      RadiusToSide(px, py, sx, sy, RadialGradientShapeType::kEllipse, compare);\n\n  return EllipseRadius(corners[corner_index].first - px,\n                       corners[corner_index].second - py,\n                       side_radius.first / side_radius.second);\n}\n}  // namespace\n\nvec2 GetRadialGradientRadius(RadialGradientShapeType shape,\n                             RadialGradientSizeType shape_size, float cx,\n                             float cy, float sx, float sy) {\n  vec2 radius = {0, 0};\n  switch (shape_size) {\n    case RadialGradientSizeType::kClosestSide:\n      radius = RadiusToSide(cx, cy, sx, sy, shape,\n                            [](float a, float b) { return a < b; });\n      break;\n    case RadialGradientSizeType::kClosestCorner:\n      radius = RadiusToCorner(cx, cy, sx, sy, shape,\n                              [](float a, float b) { return a < b; });\n      break;\n    case RadialGradientSizeType::kFarthestSide:\n      radius = RadiusToSide(cx, cy, sx, sy, shape,\n                            [](float a, float b) { return a > b; });\n      break;\n    case RadialGradientSizeType::kFarthestCorner:\n      radius = RadiusToCorner(cx, cy, sx, sy, shape,\n                              [](float a, float b) { return a > b; });\n      break;\n    default:\n      break;\n  }\n  return radius;\n}\n\nbool ParseStyleDeclarationList(const char* content, uint32_t content_length,\n                               DeclarationListConsumeFunction consume_func) {\n  uint32_t current{0};\n  const char kLeftBracket = '(';\n  const char kRightBracket = ')';\n  const char kSemiColon = ';';\n  const char kColon = ':';\n  const char kWhiteSpace = ' ';\n\n  while (current < content_length) {\n    // key\n    while (current < content_length) {\n      char c = content[current];\n      if (c != kSemiColon && c != kWhiteSpace) {\n        break;\n      }\n      current++;\n    }\n\n    auto key_start = current;\n    auto key_end = key_start;\n\n    //':', advance to colon or whitespace or semicolon(bad case)\n    while (current < content_length) {\n      char c = content[current];\n      if ((c == kColon || c == kWhiteSpace || c == kSemiColon)) {\n        if (key_end == key_start) {\n          key_end = current;\n        }\n        if (c != kWhiteSpace) {\n          break;\n        }\n      }\n      current++;\n    }\n\n    //\n    if (content[current] != kColon) {\n      // a bad case like: \"background;red;width:1px\"\n      continue;\n    }\n\n    // Advance ':'\n    current++;\n\n    // value\n    // skip whitespace after ':', for example: \"background: red\"\n    auto value_start = 0;\n\n    while (current < content_length) {\n      char c = content[current];\n      if (value_start == 0 && c != kWhiteSpace) {\n        value_start = current;\n      }\n\n      // handle '('\n      if (c == kLeftBracket) {\n        base::InlineStack<char, 16> brackets;\n        brackets.push(kLeftBracket);\n        current++;\n        while (current < content_length && !brackets.empty()) {\n          if (content[current] == kLeftBracket) {\n            brackets.push(kLeftBracket);\n          }\n          if (content[current] == kRightBracket) {\n            brackets.pop();\n          }\n          if (brackets.empty()) {\n            break;\n          }\n          current++;\n        }\n        if (!brackets.empty()) {\n          return false;\n        }\n\n        continue;\n      }\n\n      // handle '\\'', '\\\"'\n      if (c == '\\'' || c == '\\\"') {\n        char boundary = c;\n        current++;\n        while (current < content_length && content[current] != boundary) {\n          current++;\n        }\n\n        if (current >= content_length) {\n          // Unterminated string without `'` at end , like 'xxxxx\n          return false;\n        } else if (content[current] == boundary) {\n          // content[current] == boundary,just skip the end boundary,like `'`\n          current++;\n        }\n        continue;\n      }\n\n      // end of semicolon\n      if (c == kSemiColon) {\n        break;\n      }\n      current++;\n    }\n\n    auto value_end = current;\n\n    if (key_end < content_length && value_end <= content_length) {\n      consume_func(content + key_start, key_end - key_start,\n                   content + value_start, value_end - value_start);\n    }\n  }\n  return true;\n}\n\nClassList SplitClasses(const char* content, size_t length) {\n  size_t i = 0, len = length, start = i;\n  ClassList output;\n  while (i < len) {\n    char current_char = content[i];\n    if (current_char == ' ' && start == i) {\n      i++;\n      start = i;\n      // skip whitespace\n      continue;\n    }\n\n    size_t end = 0;\n    if (current_char == ' ' || current_char == '\\0') {\n      end = i;\n    } else if (i == len - 1) {\n      // end of the string\n      end = len;\n    }\n\n    if (end > start) {\n      output.emplace_back(content + start, end - start);\n      start = i + 1;\n    }\n    i++;\n  }\n  return output;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_utils.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_UTILS_H_\n#define CORE_RENDERER_CSS_CSS_UTILS_H_\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/closure.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/renderer/utils/base/base_def.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nusing RadialGradientSizeType = starlight::RadialGradientSizeType;\nusing RadialGradientShapeType = starlight::RadialGradientShapeType;\nusing DeclarationListConsumeFunction =\n    base::MoveOnlyClosure<void, const char* /*key_start*/,\n                          uint32_t /* key_length*/, const char* /*value_start*/,\n                          uint32_t /*value_length*/>;\n\nstd::pair<float, float> GetRadialGradientRadius(\n    RadialGradientShapeType shape, RadialGradientSizeType shape_size, float cx,\n    float cy, float sx, float sy);\n\nbool ParseStyleDeclarationList(const char* content, uint32_t content_length,\n                               DeclarationListConsumeFunction consume_func);\n\nClassList SplitClasses(const char* content, size_t length);\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_UTILS_H_\n"
  },
  {
    "path": "core/renderer/css/css_utils_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_utils.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace testing {\n\nstruct RadialGradientRadiusTestCase {\n  RadialGradientShapeType shape;\n  RadialGradientSizeType shape_size;\n  float cx;\n  float cy;\n  float sx;\n  float sy;\n\n  std::pair<float, float> expected;\n};\n\nclass RadialGradientRadiusTest\n    : public ::testing::TestWithParam<RadialGradientRadiusTestCase> {};\n\nTEST_P(RadialGradientRadiusTest, Radius) {\n  auto param = GetParam();\n  auto radius = GetRadialGradientRadius(param.shape, param.shape_size, param.cx,\n                                        param.cy, param.sx, param.sy);\n  EXPECT_EQ(static_cast<int>(radius.first), param.expected.first);\n  EXPECT_EQ(static_cast<int>(radius.second), param.expected.second);\n}\n\nstatic const RadialGradientRadiusTestCase data[] = {\n    {RadialGradientShapeType::kEllipse,\n     RadialGradientSizeType::kClosestSide,\n     10,\n     0,\n     200,\n     100,\n     {10, 0}},\n\n    {RadialGradientShapeType::kEllipse,\n     RadialGradientSizeType::kClosestCorner,\n     0,\n     0,\n     200,\n     100,\n     {0, 0}},\n\n    {RadialGradientShapeType::kEllipse,\n     RadialGradientSizeType::kFarthestCorner,\n     100,\n     50,\n     200,\n     100,\n     {141, 70}},\n\n    {RadialGradientShapeType::kEllipse,\n     RadialGradientSizeType::kFarthestCorner,\n     0,\n     0,\n     200,\n     100,\n     {282, 141}},\n\n    {RadialGradientShapeType::kEllipse,\n     RadialGradientSizeType::kFarthestSide,\n     0,\n     0,\n     200,\n     100,\n     {200, 100}},\n\n    {RadialGradientShapeType::kCircle,\n     RadialGradientSizeType::kFarthestCorner,\n     100,\n     50,\n     200,\n     100,\n     {111, 111}},\n\n    {RadialGradientShapeType::kCircle,\n     RadialGradientSizeType::kFarthestSide,\n     0,\n     0,\n     200,\n     100,\n     {200, 200}},\n\n    {RadialGradientShapeType::kCircle,\n     RadialGradientSizeType::kClosestSide,\n     0,\n     0,\n     200,\n     100,\n     {0, 0}},\n\n    {RadialGradientShapeType::kCircle,\n     RadialGradientSizeType::kFarthestCorner,\n     0,\n     0,\n     200,\n     100,\n     {223, 223}},\n\n    {RadialGradientShapeType::kCircle,\n     RadialGradientSizeType::kClosestCorner,\n     0,\n     0,\n     200,\n     100,\n     {0, 0}},\n};\n\nINSTANTIATE_TEST_SUITE_P(Radius, RadialGradientRadiusTest,\n                         ::testing::ValuesIn(data));\n\nTEST(CSSUtils, ParseStyleDeclarationList) {\n  const char* input = \"background : url('xxx;);;;border:1px solid red \";\n  std::unordered_map<std::string, std::string> values;\n  auto ret = ParseStyleDeclarationList(\n      input, static_cast<uint32_t>(strlen(input)),\n      [&values](const char* key_start, uint32_t key_len,\n                const char* value_start, uint32_t value_len) {\n        auto key = std::string(key_start, key_start + key_len);\n        auto value = std::string(value_start, value_start + value_len);\n        values.insert(std::make_pair(key, value));\n      });\n\n  EXPECT_TRUE(ret);\n\n  EXPECT_TRUE(values[\"background\"] == \"url('xxx;)\");\n  EXPECT_TRUE(values[\"border\"] == \"1px solid red \");\n\n  const char* input2 = \"background ; url('xxx;');;;border:1px solid red \";\n\n  std::unordered_map<std::string, std::string> values2;\n  ret = ParseStyleDeclarationList(\n      input2, static_cast<uint32_t>(strlen(input2)),\n      [&values2](const char* key_start, uint32_t key_len,\n                 const char* value_start, uint32_t value_len) {\n        auto key = std::string(key_start, key_start + key_len);\n        auto value = std::string(value_start, value_start + value_len);\n        values2.insert(std::make_pair(key, value));\n      });\n\n  EXPECT_TRUE(ret);\n  EXPECT_TRUE(values2[\"border\"] == \"1px solid red \");\n\n  const char* input3 = \"height : 3px ; width:2px;border:1px solid red\";\n\n  std::unordered_map<std::string, std::string> values3;\n  ret = ParseStyleDeclarationList(\n      input3, static_cast<uint32_t>(strlen(input3)),\n      [&values3](const char* key_start, uint32_t key_len,\n                 const char* value_start, uint32_t value_len) {\n        auto key = std::string(key_start, key_start + key_len);\n        auto value = std::string(value_start, value_start + value_len);\n        values3.insert(std::make_pair(key, value));\n      });\n\n  EXPECT_TRUE(ret);\n  EXPECT_TRUE(values3[\"border\"] == \"1px solid red\");\n  EXPECT_TRUE(values3[\"width\"] == \"2px\");\n  EXPECT_TRUE(values3[\"height\"] == \"3px \");\n\n  const char* input4 = \"background;url('xxx;');width:1px\";\n  std::unordered_map<std::string, std::string> values4;\n  ret = ParseStyleDeclarationList(\n      input4, static_cast<uint32_t>(strlen(input4)),\n      [&values4](const char* key_start, uint32_t key_len,\n                 const char* value_start, uint32_t value_len) {\n        auto key = std::string(key_start, key_start + key_len);\n        auto value = std::string(value_start, value_start + value_len);\n        values4.insert(std::make_pair(key, value));\n      });\n\n  EXPECT_TRUE(ret);\n  EXPECT_TRUE(values4[\"width\"] == \"1px\");\n\n  const char* input5 = \"background : url('xxx;';;;border:1px solid red \";\n\n  std::unordered_map<std::string, std::string> values5;\n  ret = ParseStyleDeclarationList(\n      input5, static_cast<uint32_t>(strlen(input5)),\n      [&values5](const char* key_start, uint32_t key_len,\n                 const char* value_start, uint32_t value_len) {\n        auto key = std::string(key_start, key_start + key_len);\n        auto value = std::string(value_start, value_start + value_len);\n        values5.insert(std::make_pair(key, value));\n      });\n\n  EXPECT_TRUE(!ret);\n\n  const char* input6 = \"background : 'xxxxx;;;border:1px solid red \";\n\n  std::unordered_map<std::string, std::string> values6;\n  ret = ParseStyleDeclarationList(\n      input6, static_cast<uint32_t>(strlen(input6)),\n      [&values6](const char* key_start, uint32_t key_len,\n                 const char* value_start, uint32_t value_len) {\n        auto key = std::string(key_start, key_start + key_len);\n        auto value = std::string(value_start, value_start + value_len);\n        values6.insert(std::make_pair(key, value));\n      });\n\n  EXPECT_TRUE(!ret);\n}\n\nTEST(CSSUtils, ParseStyleDeclarationListCase2) {\n  const char* input = \"color:red;font-family: 'TIKTOK LATIN'\";\n  std::unordered_map<std::string, std::string> values;\n  auto ret = ParseStyleDeclarationList(\n      input, static_cast<uint32_t>(strlen(input)),\n      [&values](const char* key_start, uint32_t key_len,\n                const char* value_start, uint32_t value_len) {\n        auto key = std::string(key_start, key_start + key_len);\n        auto value = std::string(value_start, value_start + value_len);\n        values.insert(std::make_pair(key, value));\n      });\n\n  EXPECT_TRUE(ret);\n\n  EXPECT_TRUE(values[\"color\"] == \"red\");\n  EXPECT_TRUE(values[\"font-family\"] == \"'TIKTOK LATIN'\");\n}\n\nTEST(CSSUtils, SplitClasses) {\n  const char* input0 = \"dark blue\";\n\n  ClassList out0 = SplitClasses(input0, strlen(input0));\n\n  EXPECT_TRUE(out0.size() == 2);\n  EXPECT_TRUE(out0[0] == \"dark\");\n  EXPECT_TRUE(out0[1] == \"blue\");\n\n  const char* input1 = \"   dark  blue  black   \";\n\n  ClassList out1 = SplitClasses(input1, strlen(input1));\n\n  EXPECT_TRUE(out1.size() == 3);\n  EXPECT_TRUE(out1[0] == \"dark\");\n  EXPECT_TRUE(out1[1] == \"blue\");\n  EXPECT_TRUE(out1[2] == \"black\");\n\n  const char* input2 = \"   dark  \";\n  ClassList out2 = SplitClasses(input2, strlen(input2));\n\n  EXPECT_TRUE(out2.size() == 1);\n  EXPECT_TRUE(out2[0] == \"dark\");\n\n  const char* input3 = \"dark \";\n  ClassList out3 = SplitClasses(input3, strlen(input3));\n\n  EXPECT_TRUE(out3.size() == 1);\n  EXPECT_TRUE(out3[0] == \"dark\");\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_value.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/css_value.h\"\n\n#include <algorithm>\n#include <cmath>\n\n#include \"base/include/value/table.h\"\n#include \"base/include/vector.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/runtime/lepus/json_parser.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nCSSValue::CSSValue(const CSSValue& other)\n    : val_uint64(other.val_uint64),\n      __attr__(other.__attr__),\n      optionals_(other.optionals_) {\n  static_assert(\n      offsetof(CSSValue, optionals_) - offsetof(CSSValue, __attr__) ==\n          sizeof(CSSValue::__attr__),\n      \"When modifying the attributes union, the type of the `__attr__` \"\n      \"variable needs to be adjusted simultaneously to ensure that its {0} \"\n      \"initialization can clear the entire structure bits to zero.\");\n\n  if (IsValueStorageReference()) {\n    reinterpret_cast<fml::RefCountedThreadSafeStorage*>(val_ptr)->AddRef();\n  }\n}\n\nCSSValue& CSSValue::operator=(const CSSValue& other) {\n  if (unlikely(this == &other)) {\n    return *this;\n  }\n  FreeValueStorage();\n  val_uint64 = other.val_uint64;\n  __attr__ = other.__attr__;\n  optionals_ = other.optionals_;\n  if (IsValueStorageReference()) {\n    reinterpret_cast<fml::RefCountedThreadSafeStorage*>(val_ptr)->AddRef();\n  }\n  return *this;\n}\n\nCSSValue::CSSValue(CSSValue&& other) noexcept\n    : val_uint64(other.val_uint64),\n      __attr__(other.__attr__),\n      optionals_(std::move(other.optionals_)) {\n  other.__attr__ = 0;\n}\n\nCSSValue& CSSValue::operator=(CSSValue&& other) noexcept {\n  if (likely(this != &other)) {\n    this->~CSSValue();\n    new (this) CSSValue(std::move(other));\n  }\n  return *this;\n}\n\nCSSValue::CSSValue(const base::String& value, CSSValuePattern pattern,\n                   CSSValueType value_type) {\n  // Firstly let `__attr__` be set to 0 and then assign other fields.\n  type_ = lynx_value_string;\n  pattern_ = pattern;\n  is_variable_ = value_type == CSSValueType::VARIABLE;\n  auto* str = base::String::Unsafe::GetUntaggedStringRawRef(value);\n  val_ptr = reinterpret_cast<lynx_value_ptr>(str);\n  str->AddRef();\n}\n\nCSSValue::CSSValue(base::String&& value, CSSValuePattern pattern,\n                   CSSValueType value_type) {\n  // Firstly let `__attr__` be set to 0 and then assign other fields.\n  type_ = lynx_value_string;\n  pattern_ = pattern;\n  is_variable_ = value_type == CSSValueType::VARIABLE;\n  auto* str = base::String::Unsafe::GetUntaggedStringRawRef(value);\n  val_ptr = reinterpret_cast<lynx_value_ptr>(str);\n  if (str != base::String::Unsafe::GetStringRawRef(value)) {\n    str->AddRef();\n  }\n  base::String::Unsafe::SetStringToEmpty(value);\n}\n\nCSSValue::CSSValue(const char* value, CSSValuePattern pattern,\n                   CSSValueType value_type) {\n  // Firstly let `__attr__` be set to 0 and then assign other fields.\n  type_ = lynx_value_string;\n  pattern_ = pattern;\n  is_variable_ = value_type == CSSValueType::VARIABLE;\n  auto* str = base::RefCountedStringImpl::Unsafe::RawCreate(value);\n  val_ptr = reinterpret_cast<lynx_value_ptr>(str);\n}\n\nCSSValue::CSSValue(const std::string& value, CSSValuePattern pattern,\n                   CSSValueType value_type) {\n  // Firstly let `__attr__` be set to 0 and then assign other fields.\n  type_ = lynx_value_string;\n  pattern_ = pattern;\n  is_variable_ = value_type == CSSValueType::VARIABLE;\n  auto* ptr = base::RefCountedStringImpl::Unsafe::RawCreate(value);\n  val_ptr = reinterpret_cast<lynx_value_ptr>(ptr);\n}\n\nCSSValue::CSSValue(std::string&& value, CSSValuePattern pattern,\n                   CSSValueType value_type) {\n  // Firstly let `__attr__` be set to 0 and then assign other fields.\n  type_ = lynx_value_string;\n  pattern_ = pattern;\n  is_variable_ = value_type == CSSValueType::VARIABLE;\n  auto* ptr = base::RefCountedStringImpl::Unsafe::RawCreate(std::move(value));\n  val_ptr = reinterpret_cast<lynx_value_ptr>(ptr);\n}\n\nCSSValue::CSSValue(const fml::RefPtr<lepus::CArray>& array)\n    : val_ptr(reinterpret_cast<lynx_value_ptr>(array.get())) {\n  // Firstly let `__attr__` be set to 0 and then assign other fields.\n  type_ = lynx_value_array;\n  pattern_ = CSSValuePattern::ARRAY;\n  array.get()->AddRef();\n}\n\nCSSValue::CSSValue(fml::RefPtr<lepus::CArray>&& array)\n    : val_ptr(reinterpret_cast<lynx_value_ptr>(array.AbandonRef())) {\n  // Firstly let `__attr__` be set to 0 and then assign other fields.\n  type_ = lynx_value_array;\n  pattern_ = CSSValuePattern::ARRAY;\n}\n\nCSSValue::CSSValue(const lepus::Value& value, CSSValuePattern pattern,\n                   CSSValueType value_type)\n    : val_uint64(value.value().val_uint64) {\n  // Firstly let `__attr__` be set to 0 and then assign other fields.\n  type_ = value.value().type;\n  pattern_ = pattern;\n  DCHECK(type_ <= lynx_value_map);\n  is_variable_ = value_type == CSSValueType::VARIABLE;\n  if (IsValueStorageReference()) {\n    reinterpret_cast<fml::RefCountedThreadSafeStorage*>(val_ptr)->AddRef();\n  }\n}\n\nCSSValue::CSSValue(lepus::Value&& value, CSSValuePattern pattern,\n                   CSSValueType value_type)\n    : val_uint64(value.value().val_uint64) {\n  // Firstly let `__attr__` be set to 0 and then assign other fields.\n  type_ = value.value().type;\n  pattern_ = pattern;\n  DCHECK(type_ <= lynx_value_map);\n  is_variable_ = value_type == CSSValueType::VARIABLE;\n  const_cast<lynx_value&>(value.value()).type = lynx_value_null;\n}\n\nlepus::Value CSSValue::GetValue() const {\n  lepus::Value result(lepus::Value::kUnsafeCreateAsUninitialized);\n  const_cast<lynx_value&>(result.value()).val_uint64 = val_uint64;\n  const_cast<lynx_value&>(result.value()).type = type_;\n  if (IsValueStorageReference()) {\n    reinterpret_cast<fml::RefCountedThreadSafeStorage*>(val_ptr)->AddRef();\n  }\n  return result;\n}\n\nbase::String CSSValue::GetDefaultValue() const {\n  if (optionals_.HasValue<DefaultValueField>()) {\n    return optionals_.Get<DefaultValueField>();\n  } else {\n    return base::String();\n  }\n}\n\nlepus::Value CSSValue::GetDefaultValueMapOpt() const {\n  if (optionals_.HasValue<DefaultValueMapField>()) {\n    return optionals_.Get<DefaultValueMapField>();\n  } else {\n    return lepus::Value();\n  }\n}\n\nvoid CSSValue::SetDefaultValue(base::String default_val) {\n  if (default_val.empty()) {\n    // Release instead of assignment to prevent the memory of dynamic property\n    // parts from being created unexpectedly.\n    optionals_.Release<DefaultValueField>();\n  } else {\n    optionals_.Get<DefaultValueField>() = std::move(default_val);\n  }\n}\n\nvoid CSSValue::SetDefaultValueMap(lepus::Value default_value_map) {\n  if (default_value_map.IsTable() && !default_value_map.Table()->empty()) {\n    optionals_.Get<DefaultValueMapField>() = std::move(default_value_map);\n  } else {\n    optionals_.Release<DefaultValueMapField>();\n  }\n}\n\nvoid CSSValue::SetVarReferences(base::Vector<VarReference> var_references) {\n  if (var_references.empty()) {\n    optionals_.Release<VarReferenceField>();\n  } else {\n    optionals_.Get<VarReferenceField>() = std::move(var_references);\n  }\n  is_variable_ = true;\n  needs_variable_resolution_ = true;\n}\n\nvoid CSSValue::SetValueAndPattern(const lepus::Value& value,\n                                  CSSValuePattern pattern) {\n  FreeValueStorage();\n  val_uint64 = value.value().val_uint64;\n  type_ = value.value().type;\n  pattern_ = pattern;\n  if (IsValueStorageReference()) {\n    reinterpret_cast<fml::RefCountedThreadSafeStorage*>(val_ptr)->AddRef();\n  }\n}\n\nfml::WeakRefPtr<lepus::CArray> CSSValue::GetArray() const& {\n  return fml::WeakRefPtr<lepus::CArray>(\n      val_ptr != nullptr && type_ == lynx_value_array\n          ? reinterpret_cast<lepus::CArray*>(val_ptr)\n          : lepus::Value::DummyArray());\n}\n\nfml::RefPtr<lepus::CArray> CSSValue::GetArray() && {\n  if (val_ptr != nullptr && type_ == lynx_value_array) {\n    return fml::RefPtr<lepus::CArray>(\n        reinterpret_cast<lepus::CArray*>(val_ptr));\n  }\n  return lepus::CArray::Create();\n}\n\ndouble CSSValue::GetNumber() const {\n  switch (type_) {\n    case lynx_value_double:\n      return val_double;\n    case lynx_value_int32:\n      return val_int32;\n    case lynx_value_uint32:\n      return val_uint32;\n    case lynx_value_int64:\n      return val_int64;\n    case lynx_value_uint64:\n      return val_uint64;\n    default:\n      return 0.f;\n  }\n}\n\nbool CSSValue::GetBool() const {\n  if (type_ == lynx_value_bool) {\n    return val_bool;\n  } else {\n    return GetValue().Bool();\n  }\n}\n\nbase::String CSSValue::AsString() const& {\n  if (type_ == lynx_value_string) {\n    return base::String::Unsafe::ConstructWeakRefStringFromRawRef(\n        reinterpret_cast<base::RefCountedStringImpl*>(val_ptr));\n  } else {\n    return base::String();\n  }\n}\n\nbase::String CSSValue::AsString() && {\n  if (type_ == lynx_value_string) {\n    return base::String::Unsafe::ConstructStringFromRawRef(\n        reinterpret_cast<base::RefCountedStringImpl*>(val_ptr));\n  } else {\n    return base::String();\n  }\n}\n\nconst std::string& CSSValue::AsStdString() const {\n  if (type_ == lynx_value_string) {\n    return reinterpret_cast<base::RefCountedStringImpl*>(val_ptr)->str();\n  } else {\n    return base::RefCountedStringImpl::Unsafe::kEmptyString.str();\n  }\n}\n\nvoid CSSValue::SetArray(fml::RefPtr<lepus::CArray>&& array) {\n  FreeValueStorage();\n  val_ptr = reinterpret_cast<lynx_value_ptr>(array.AbandonRef());\n  type_ = lynx_value_array;\n  pattern_ = CSSValuePattern::ARRAY;\n  is_variable_ = false;\n}\n\nvoid CSSValue::SetBoolean(bool value) {\n  FreeValueStorage();\n  val_bool = value;\n  type_ = lynx_value_bool;\n  pattern_ = CSSValuePattern::BOOLEAN;\n  is_variable_ = false;\n}\n\nvoid CSSValue::SetNumber(double val, CSSValuePattern pattern) {\n  FreeValueStorage();\n  val_double = val;\n  type_ = lynx_value_double;\n  pattern_ = pattern;\n  is_variable_ = false;\n}\n\nvoid CSSValue::SetNumber(int32_t val, CSSValuePattern pattern) {\n  FreeValueStorage();\n  val_int32 = val;\n  type_ = lynx_value_int32;\n  pattern_ = pattern;\n  is_variable_ = false;\n}\n\nvoid CSSValue::SetNumber(uint32_t val, CSSValuePattern pattern) {\n  FreeValueStorage();\n  val_uint32 = val;\n  type_ = lynx_value_uint32;\n  pattern_ = pattern;\n  is_variable_ = false;\n}\n\nvoid CSSValue::SetNumber(int64_t val, CSSValuePattern pattern) {\n  FreeValueStorage();\n  val_int64 = val;\n  type_ = lynx_value_int64;\n  pattern_ = pattern;\n  is_variable_ = false;\n}\n\nvoid CSSValue::SetNumber(uint64_t val, CSSValuePattern pattern) {\n  FreeValueStorage();\n  val_uint64 = val;\n  type_ = lynx_value_uint64;\n  pattern_ = pattern;\n  is_variable_ = false;\n}\n\nvoid CSSValue::SetString(const base::String& value, CSSValuePattern pattern) {\n  FreeValueStorage();\n  auto* ptr = base::String::Unsafe::GetUntaggedStringRawRef(value);\n  ptr->AddRef();\n  val_ptr = reinterpret_cast<lynx_value_ptr>(ptr);\n  type_ = lynx_value_string;\n  pattern_ = pattern;\n  is_variable_ = false;\n}\n\nvoid CSSValue::SetString(base::String&& value, CSSValuePattern pattern) {\n  FreeValueStorage();\n  auto* ptr = base::String::Unsafe::GetUntaggedStringRawRef(value);\n  if (ptr != base::String::Unsafe::GetStringRawRef(value)) {\n    ptr->AddRef();\n  }\n  val_ptr = reinterpret_cast<lynx_value_ptr>(ptr);\n  type_ = lynx_value_string;\n  base::String::Unsafe::SetStringToEmpty(value);\n  pattern_ = pattern;\n  is_variable_ = false;\n}\n\nbool operator==(const CSSValue& left, const CSSValue& right) {\n  if (left.pattern_ != right.pattern_) {\n    return false;\n  }\n\n  if (left.IsValueStorageNumber()) {\n    if (right.IsValueStorageNumber()) {\n      return std::fabs(left.GetNumber() - right.GetNumber()) < 0.000001;\n    } else {\n      return false;\n    }\n  }\n\n  if (left.type_ != right.type_) {\n    return false;\n  }\n\n  switch (left.type_) {\n    case lynx_value_null:\n    case lynx_value_undefined:\n      return true;\n    case lynx_value_bool:\n      return left.val_bool == right.val_bool;\n    case lynx_value_nan:\n      return false;  // Two NAN values are not equal? The same logic as\n                     // lepus::Value.\n    case lynx_value_string:\n      return left.AsStdString() == right.AsStdString();\n    case lynx_value_map:\n      return *reinterpret_cast<lepus::Dictionary*>(left.val_ptr) ==\n             *reinterpret_cast<lepus::Dictionary*>(right.val_ptr);\n    case lynx_value_array:\n      return *reinterpret_cast<lepus::CArray*>(left.val_ptr) ==\n             *reinterpret_cast<lepus::CArray*>(right.val_ptr);\n    default:\n      break;\n  }\n  return false;\n}\n\n// Optimized structure for Tarjan's SCC algorithm\nstruct alignas(4) SCCNode {\n  int32_t index = -1;\n  int32_t low_link = -1;\n  bool on_stack = false;\n  uint8_t padding[3] = {0};  // Padding for cache alignment\n};\n\nclass CSSValue::CycleDetector {\n public:\n  explicit CycleDetector(const CustomPropertiesMap& variables)\n      : variables_(variables) {\n    // Process all variables when used_vars is empty\n    FindAllSCCs();\n  }\n\n  bool IsInCycle(const std::string& var_name) const {\n    return cycles_.contains(var_name);\n  }\n\n private:\n  void FindAllSCCs() {\n    index_ = 0;\n    nodes_.reserve(variables_.size());\n\n    for (const auto& [var_name, _] : variables_) {\n      const std::string& var_name_str = var_name.str();\n      if (nodes_[var_name_str].index == -1) {\n        StrongConnect(var_name_str);\n      }\n    }\n  }\n\n  void StrongConnect(const std::string& var_name) {\n    SCCNode& node = nodes_[var_name];\n    node.index = index_++;\n    node.low_link = node.index;\n    stack_.push(var_name);\n    node.on_stack = true;\n\n    // Optimized dependency traversal\n    auto var_it = variables_.find(var_name);\n    if (var_it != variables_.end() && var_it->second.IsVariable() &&\n        var_it->second.optionals_.HasValue<VarReferenceField>()) {\n      const auto& refs = var_it->second.optionals_.Get<VarReferenceField>();\n      for (const auto& var_ref : refs) {\n        const std::string dep_name(var_ref.Name(var_it->second.AsStdString()));\n\n        if (variables_.find(dep_name) != variables_.end()) {\n          auto& dep_node = nodes_[dep_name];\n          if (dep_node.index == -1) {\n            StrongConnect(dep_name);\n            node.low_link = std::min(node.low_link, dep_node.low_link);\n          } else if (dep_node.on_stack) {\n            node.low_link = std::min(node.low_link, dep_node.index);\n          }\n        }\n      }\n    }\n\n    // Efficient SCC extraction\n    if (node.low_link == node.index) {\n      bool has_cycle = false;\n      std::string w;\n      do {\n        w = stack_.top();\n        stack_.pop();\n        nodes_[w].on_stack = false;\n\n        if (w != var_name) {\n          has_cycle = true;\n        } else {\n          // Check self-loop\n          auto var_it = variables_.find(w);\n          if (var_it != variables_.end() && var_it->second.IsVariable() &&\n              var_it->second.optionals_.HasValue<VarReferenceField>()) {\n            for (const auto& var_ref :\n                 var_it->second.optionals_.Get<VarReferenceField>()) {\n              if (var_ref.Name(var_it->second.AsStdString()) == w) {\n                has_cycle = true;\n                break;\n              }\n            }\n          }\n        }\n\n        if (has_cycle) {\n          cycles_.insert(w);\n        }\n      } while (w != var_name);\n    }\n  }\n\n  const CustomPropertiesMap& variables_;\n  base::InlineLinearFlatMap<std::string, SCCNode, 32> nodes_;\n  base::InlineLinearFlatSet<std::string, 8> cycles_;\n  base::InlineStack<std::string, 24> stack_;\n  int index_ = 0;\n};\n\nstd::string CSSValue::AsJsonString(bool map_key_ordered) const {\n  return lepus::lepusValueToString(GetValue(), map_key_ordered);\n}\n\nstd::string CSSValue::ResolveVariable(\n    const std::string& var_name, const CustomPropertiesMap& custom_properties,\n    const CycleDetector& detector, int max_depth,\n    const HandleCustomPropertyFunc& handle_func) {\n  if (max_depth <= 0) {\n    return \"\";\n  }\n\n  auto value = custom_properties.find(var_name);\n  if (value == custom_properties.end()) {\n    return \"\";\n  }\n\n  const CSSValue& css_value = value->second;\n  // If it's a simple string value (no variables), return it directly\n  if (!css_value.IsVariable()) {\n    return css_value.AsStdString();\n  }\n\n  return CSSValue::Substitution(css_value, custom_properties, detector,\n                                max_depth - 1, handle_func);\n  ;\n}\n\nvoid CSSValue::SubstituteAll(CustomPropertiesMap& custom_properties,\n                             int max_depth,\n                             const HandleCustomPropertyFunc& handle_func) {\n  CycleDetector detector(custom_properties);\n  for (auto& [name, value] : custom_properties) {\n    if (value.NeedsVariableResolution()) {\n      auto property = CSSValue::Substitution(value, custom_properties, detector,\n                                             max_depth, handle_func);\n      custom_properties[name] = CSSValue(\n          std::move(property), CSSValuePattern::STRING, CSSValueType::DEFAULT);\n    }\n  }\n}\n\nstd::string CSSValue::Substitution(\n    const CSSValue& css_value, const CustomPropertiesMap& custom_properties,\n    int max_depth, const HandleCustomPropertyFunc& handle_func) {\n  // Build cycle detector for all variables (optimized for performance)\n  CycleDetector detector(custom_properties);\n\n  return Substitution(css_value, custom_properties, detector, max_depth,\n                      handle_func);\n}\n\nstd::string CSSValue::Substitution(\n    const CSSValue& css_value, const CustomPropertiesMap& custom_properties,\n    const CycleDetector& detector, int max_depth,\n    const HandleCustomPropertyFunc& handle_func) {\n  if (!css_value.IsVariable() ||\n      !css_value.optionals_.HasValue<VarReferenceField>()) {\n    return css_value.AsStdString();\n  }\n\n  const std::string& raw_value = css_value.AsStdString();\n  const auto& var_refs = css_value.optionals_.Get<VarReferenceField>();\n\n  if (var_refs.empty()) {\n    return css_value.AsStdString();\n  }\n\n  std::string result;\n\n  // Smart size estimation with move semantics\n  size_t estimated_size = raw_value.size() << 2;\n  result.reserve(std::min(estimated_size, size_t{4096}));\n\n  size_t last_pos = 0;\n\n  for (const auto& var_ref : var_refs) {\n    // Append text before this variable reference\n    result.append(raw_value, last_pos, var_ref.start - last_pos);\n\n    const std::string dep_name(var_ref.Name(raw_value));\n\n    auto var_it = custom_properties.find(dep_name);\n    if (handle_func) {\n      if (var_it != custom_properties.end()) {\n        handle_func(dep_name, var_it->second.AsString());\n      } else {\n        handle_func(dep_name, base::String());\n      }\n    }\n\n    // Fast path: check if variable exists and not in cycle\n    if (var_it != custom_properties.end() && !detector.IsInCycle(dep_name)) {\n      // Variable exists and is not in a cycle - resolve it normally\n      std::string resolved_value = ResolveVariable(\n          dep_name, custom_properties, detector, max_depth, handle_func);\n\n      if (!resolved_value.empty()) {\n        result.append(std::move(resolved_value));\n      } else {\n        result.append(CSSValue::ResolveFallback(\n            var_ref, custom_properties, detector, max_depth, handle_func));\n      }\n    } else if (!var_ref.fallback.empty()) {\n      // Variable not found or in cycle - use fallback\n      result.append(CSSValue::ResolveFallback(\n          var_ref, custom_properties, detector, max_depth, handle_func));\n    }\n\n    last_pos = var_ref.end;\n  }\n\n  // Append remaining text after last variable reference\n  if (last_pos < raw_value.size()) {\n    result.append(raw_value, last_pos);\n  }\n\n  // Return by move to avoid copy\n  return result;\n}\n\nstd::string CSSValue::SubstitutionResolved(\n    const CSSValue& css_value, const CustomPropertiesMap& custom_properties,\n    const HandleCustomPropertyFunc& handle_func) {\n  if (!css_value.IsVariable() ||\n      !css_value.optionals_.HasValue<VarReferenceField>()) {\n    return css_value.AsStdString();\n  }\n\n  const std::string& raw_value = css_value.AsStdString();\n  const auto& var_refs = css_value.optionals_.Get<VarReferenceField>();\n\n  if (var_refs.empty()) {\n    return css_value.AsStdString();\n  }\n\n  std::string result;\n\n  // Smart size estimation with move semantics\n  size_t estimated_size = raw_value.size() << 2;\n  result.reserve(std::min(estimated_size, size_t{4096}));\n\n  size_t last_pos = 0;\n\n  for (const auto& var_ref : var_refs) {\n    // Append text before this variable reference\n    result.append(raw_value, last_pos, var_ref.start - last_pos);\n\n    const std::string dep_name(var_ref.Name(raw_value));\n\n    auto var_it = custom_properties.find(dep_name);\n    if (handle_func) {\n      if (var_it != custom_properties.end()) {\n        handle_func(dep_name, var_it->second.AsString());\n      } else {\n        handle_func(dep_name, base::String());\n      }\n    }\n\n    if (var_it != custom_properties.end()) {\n      // Variable exists - resolve it normally\n      // Since custom_properties are already resolved in\n      // CollectCustomProperties, we can directly use the string value.\n      result.append(var_it->second.AsStdString());\n    } else if (!var_ref.fallback.empty()) {\n      // Variable not found - use fallback\n      result.append(CSSValue::ResolveFallbackResolved(\n          var_ref, custom_properties, handle_func));\n    }\n\n    last_pos = var_ref.end;\n  }\n\n  // Append remaining text after last variable reference\n  if (last_pos < raw_value.size()) {\n    result.append(raw_value, last_pos);\n  }\n\n  // Return by move to avoid copy\n  return result;\n}\n\nstd::string CSSValue::ResolveFallback(\n    const VarReference& ref, const CustomPropertiesMap& variable_map,\n    const CycleDetector& detector, int max_depth,\n    const HandleCustomPropertyFunc& handle_func) {\n  if (ref.fallback.empty()) {\n    return \"\";\n  }\n  CSSStringParser parser(ref.fallback.c_str(),\n                         static_cast<uint32_t>(ref.fallback.length()),\n                         ref.parser_configs);\n  CSSValue css_value = parser.ParseVariable();\n  return CSSValue::Substitution(css_value, variable_map, detector,\n                                max_depth - 1, handle_func);\n}\n\nstd::string CSSValue::ResolveFallbackResolved(\n    const VarReference& ref, const CustomPropertiesMap& variable_map,\n    const HandleCustomPropertyFunc& handle_func) {\n  if (ref.fallback.empty()) {\n    return \"\";\n  }\n  CSSStringParser parser(ref.fallback.c_str(),\n                         static_cast<uint32_t>(ref.fallback.length()),\n                         ref.parser_configs);\n  CSSValue css_value = parser.ParseVariable();\n  return CSSValue::SubstitutionResolved(css_value, variable_map, handle_func);\n}\n\nstd::string_view VarReference::Name(const std::string& raw_value) const {\n  if (name_start >= raw_value.size() || name_end > raw_value.size() ||\n      name_start >= name_end) {\n    return \"\";\n  }\n  return std::string_view{raw_value}.substr(start + offset + name_start,\n                                            name_end - name_start);\n}\n\nbool CSSValue::ToVarReference() {\n  if (!IsVariable() || optionals_.HasValue<VarReferenceField>()) {\n    // Not a variable value or already a reference value.\n    return false;\n  }\n\n  // First, call ReleaseTransfer to retrieve the possible default_value and\n  // default_value_map, which will release the entire buffer. Then, call\n  // Get<VarReferenceField>, and the newly created buffer will only store the\n  // VarReference field.\n  auto default_value = optionals_.ReleaseTransfer<DefaultValueField>();\n  auto default_value_map = optionals_.ReleaseTransfer<DefaultValueMapField>();\n\n  const auto& format = AsStdString();\n  auto& var_references = optionals_.Get<VarReferenceField>();\n  const auto* default_value_map_pointer =\n      default_value_map.IsTable() ? default_value_map.Table().get() : nullptr;\n\n  // Look for {{variable}} patterns\n  size_t pos = 0;\n  while (pos < format.size()) {\n    // Find opening {{\n    size_t open_start = format.find(\"{{\", pos);\n    if (open_start == std::string::npos) break;\n\n    // Find closing }}\n    size_t close_end = format.find(\"}}\", open_start + 2);\n    if (close_end == std::string::npos) break;\n\n    VarReference ref;\n    // For {{--color}} format, we need to set positions to work with Name()\n    // method For {{--color}}, we want the variable name \"--color\" to be\n    // extracted correctly Use offset = 2 for {{}} format (vs default 4 for\n    // var() format)\n    ref.start = open_start;   // Base offset (position of {{)\n    ref.end = close_end + 2;  // End of string (position after }})\n    ref.offset =\n        2;  // Use 2 for {{}} format instead of default 4 for var() format\n    ref.name_start = 0;  // Variable name starts at position 2 in the string\n    ref.name_end =\n        close_end - open_start - 2;  // length of variable name (excluding }})\n\n    if (default_value_map_pointer) {\n      auto name = ref.Name(format);\n      if (auto it = default_value_map_pointer->find(std::string(name));\n          it != default_value_map_pointer->end()) {\n        ref.fallback = it->second.String();\n      }\n    } else if (!default_value.empty()) {\n      ref.fallback = default_value;\n    }\n    var_references.emplace_back(std::move(ref));\n\n    // Move past this match\n    pos = close_end + 2;\n  }\n\n  needs_variable_resolution_ = true;\n  return true;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_value.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_VALUE_H_\n#define CORE_RENDERER_CSS_CSS_VALUE_H_\n\n#include <memory>\n#include <string>\n#include <type_traits>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n\n#include \"base/include/base_defines.h\"\n#include \"base/include/bundled_optional.h\"\n#include \"base/include/flex_optional.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/base_value.h\"\n#include \"core/base/lynx_export.h\"\n#include \"core/renderer/css/parser/css_parser_configs.h\"\n#include \"core/renderer/starlight/style/copyable.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/renderer/starlight/style/data_ref.h\"\n\nnamespace lynx {\nnamespace tasm {\nenum class CSSValuePattern : uint8_t {\n  EMPTY = 0,\n  STRING = 1,\n  NUMBER = 2,\n  BOOLEAN = 3,\n  ENUM = 4,\n  PX = 5,\n  RPX = 6,\n  EM = 7,\n  REM = 8,\n  VH = 9,\n  VW = 10,\n  PERCENT = 11,\n  CALC = 12,\n  ENV = 13,\n  ARRAY = 14,\n  MAP = 15,\n  PPX = 16,\n  INTRINSIC = 17,\n  SP = 18,\n  FR = 19,\n  COUNT = 20,\n};\n\nenum class CSSValueType : uint8_t {\n  DEFAULT = 0,\n  VARIABLE = 1,\n};\n\nenum class CSSFunctionType : uint8_t {\n  DEFAULT = 0,\n  REPEAT = 1,\n  MINMAX = 2,\n};\n\nclass CSSValue;\nstruct VarReference {\n  size_t name_start;\n  size_t name_end;\n  size_t start;\n  size_t end;\n  size_t offset = 4;  // Default offset for var() format, use 2 for {{}} format\n  base::String fallback;\n  CSSParserConfigs parser_configs;\n  std::string_view Name(const std::string& raw_value) const;\n};\n\nusing CustomPropertiesMap = base::LinearFlatMap<base::String, CSSValue>;\nusing CustomPropertiesMapRef =\n    starlight::DataRef<starlight::Copyable<CustomPropertiesMap>>;\n\nclass LYNX_EXPORT_FOR_DEVTOOL CSSValue {\n public:\n  CSSValue() {}  // __attr__ initialized to zero.\n  ~CSSValue() { FreeValueStorage(); }\n\n  CSSValue(const CSSValue& other);\n  CSSValue& operator=(const CSSValue& other);\n  CSSValue(CSSValue&& other) noexcept;\n  CSSValue& operator=(CSSValue&& other) noexcept;\n\n  CSSValue(const base::String& value, CSSValuePattern pattern,\n           CSSValueType value_type);\n  CSSValue(base::String&& value, CSSValuePattern pattern,\n           CSSValueType value_type);\n  CSSValue(const char* value, CSSValuePattern pattern, CSSValueType value_type);\n  CSSValue(const std::string& value, CSSValuePattern pattern,\n           CSSValueType value_type);\n  CSSValue(std::string&& value, CSSValuePattern pattern,\n           CSSValueType value_type);\n\n  template <typename EnumT, typename = std::enable_if_t<std::is_enum_v<EnumT>>>\n  explicit CSSValue(EnumT value) : val_int32(static_cast<int32_t>(value)) {\n    // Firstly let `__attr__` be set to 0 and then assign other fields.\n    type_ = lynx_value_int32;\n    pattern_ = CSSValuePattern::ENUM;\n  }\n\n  explicit CSSValue(bool value) : val_bool(value) {\n    // Firstly let `__attr__` be set to 0 and then assign other fields.\n    type_ = lynx_value_bool;\n    pattern_ = CSSValuePattern::BOOLEAN;\n  }\n\n  CSSValue(double value, CSSValuePattern pattern) : val_double(value) {\n    // Firstly let `__attr__` be set to 0 and then assign other fields.\n    type_ = lynx_value_double;\n    pattern_ = pattern;\n  }\n\n  CSSValue(int32_t value, CSSValuePattern pattern) : val_int32(value) {\n    // Firstly let `__attr__` be set to 0 and then assign other fields.\n    type_ = lynx_value_int32;\n    pattern_ = pattern;\n  }\n\n  CSSValue(uint32_t value, CSSValuePattern pattern) : val_uint32(value) {\n    // Firstly let `__attr__` be set to 0 and then assign other fields.\n    type_ = lynx_value_uint32;\n    pattern_ = pattern;\n  }\n\n  CSSValue(int64_t value, CSSValuePattern pattern) : val_int64(value) {\n    // Firstly let `__attr__` be set to 0 and then assign other fields.\n    type_ = lynx_value_int64;\n    pattern_ = pattern;\n  }\n\n  CSSValue(uint64_t value, CSSValuePattern pattern) : val_uint64(value) {\n    // Firstly let `__attr__` be set to 0 and then assign other fields.\n    type_ = lynx_value_uint64;\n    pattern_ = pattern;\n  }\n\n  explicit CSSValue(const fml::RefPtr<lepus::CArray>& array);\n  explicit CSSValue(fml::RefPtr<lepus::CArray>&& array);\n\n  // It is not recommended to construct CSSValue from opaque lepus::Value,\n  // prefer other constructors.\n  CSSValue(const lepus::Value& value, CSSValuePattern pattern,\n           CSSValueType value_type = CSSValueType::DEFAULT);\n  CSSValue(lepus::Value&& value, CSSValuePattern pattern,\n           CSSValueType value_type = CSSValueType::DEFAULT);\n\n  lepus::Value GetValue() const;\n  CSSValuePattern GetPattern() const { return pattern_; }\n  CSSValueType GetValueType() const {\n    return is_variable_ ? CSSValueType::VARIABLE : CSSValueType::DEFAULT;\n  }\n  base::String GetDefaultValue() const;\n  lepus::Value GetDefaultValueMapOpt() const;\n\n  void SetPattern(CSSValuePattern pattern) { pattern_ = pattern; }\n  void SetValueAndPattern(const lepus::Value& value, CSSValuePattern pattern);\n  void SetType(CSSValueType type) {\n    is_variable_ = type == CSSValueType::VARIABLE;\n  }\n  void SetDefaultValue(base::String default_val);\n  void SetDefaultValueMap(lepus::Value default_value_map);\n\n  template <typename T>\n  T GetEnum() const {\n    return (T)AsNumber();\n  }\n\n  fml::WeakRefPtr<lepus::CArray> GetArray() const&;\n  fml::RefPtr<lepus::CArray> GetArray() &&;\n\n  double GetNumber() const;\n  double AsNumber() const { return GetNumber(); }\n  bool GetBool() const;\n  bool AsBool() const { return GetBool(); }\n\n  base::String AsString() const&;\n  base::String AsString() &&;\n  const std::string& AsStdString() const;\n\n  void SetArray(fml::RefPtr<lepus::CArray>&& array);\n\n  void SetBoolean(bool value);\n  void SetNumber(double val, CSSValuePattern pattern);\n  void SetNumber(int32_t val, CSSValuePattern pattern);\n  void SetNumber(uint32_t val, CSSValuePattern pattern);\n  void SetNumber(int64_t val, CSSValuePattern pattern);\n  void SetNumber(uint64_t val, CSSValuePattern pattern);\n\n  void SetString(const base::String& value, CSSValuePattern pattern);\n  void SetString(base::String&& value, CSSValuePattern pattern);\n\n  template <typename EnumT, typename = std::enable_if_t<std::is_enum_v<EnumT>>>\n  void SetEnum(EnumT value) {\n    FreeValueStorage();\n    val_int32 = static_cast<int32_t>(value);\n    type_ = lynx_value_int32;\n    pattern_ = CSSValuePattern::ENUM;\n    is_variable_ = false;\n  }\n\n  void SetVarReferences(base::Vector<VarReference> var_references);\n  bool NeedsVariableResolution() const { return needs_variable_resolution_; }\n\n  bool IsVariable() const { return is_variable_; }\n  bool IsString() const { return pattern_ == CSSValuePattern::STRING; }\n  bool IsNumber() const { return pattern_ == CSSValuePattern::NUMBER; }\n  bool IsBoolean() const { return pattern_ == CSSValuePattern::BOOLEAN; }\n  bool IsEnum() const { return pattern_ == CSSValuePattern::ENUM; }\n  bool IsPx() const { return pattern_ == CSSValuePattern::PX; }\n  bool IsPPx() const { return pattern_ == CSSValuePattern::PPX; }\n  bool IsRpx() const { return pattern_ == CSSValuePattern::RPX; }\n  bool IsEm() const { return pattern_ == CSSValuePattern::EM; }\n  bool IsRem() const { return pattern_ == CSSValuePattern::REM; }\n  bool IsVh() const { return pattern_ == CSSValuePattern::VH; }\n  bool IsVw() const { return pattern_ == CSSValuePattern::VW; }\n  bool IsPercent() const { return pattern_ == CSSValuePattern::PERCENT; }\n  bool IsCalc() const { return pattern_ == CSSValuePattern::CALC; }\n  bool IsArray() const { return pattern_ == CSSValuePattern::ARRAY; }\n  bool IsMap() const { return pattern_ == CSSValuePattern::MAP; }\n  bool IsEmpty() const { return pattern_ == CSSValuePattern::EMPTY; }\n  bool IsEnv() const { return pattern_ == CSSValuePattern::ENV; }\n  bool IsIntrinsic() const { return pattern_ == CSSValuePattern::INTRINSIC; }\n  bool IsSp() const { return pattern_ == CSSValuePattern::SP; }\n\n  std::string AsJsonString(bool map_key_ordered = false) const;\n\n  friend bool operator==(const CSSValue& left, const CSSValue& right);\n  friend bool operator!=(const CSSValue& left, const CSSValue& right) {\n    return !(left == right);\n  }\n\n  using HandleCustomPropertyFunc =\n      std::function<void(const base::String&, const base::String&)>;\n  static void SubstituteAll(\n      CustomPropertiesMap& custom_properties, int max_depth = 10,\n      const HandleCustomPropertyFunc& handle_func = nullptr);\n  static std::string Substitution(\n      const CSSValue& css_value, const CustomPropertiesMap& variable_map,\n      int max_depth = 10,\n      const HandleCustomPropertyFunc& handle_func = nullptr);\n  static std::string SubstitutionResolved(\n      const CSSValue& css_value, const CustomPropertiesMap& variable_map,\n      const HandleCustomPropertyFunc& handle_func = nullptr);\n\n  // Change the legacy css variable value which in a format with {{ variable }}\n  // into the new VarReference based variables.\n  // TODO(renzhongyue): reference values can be directly encode into template to\n  // reduce runtime overheads.\n  bool ToVarReference();\n\n  // A flag telling base containers such as `base::Vector<Value>` and maps\n  // built on vector to optimize for reallocate, insert and erase.\n  using TriviallyRelocatable = bool;\n\n private:\n  class CycleDetector;\n  friend class LynxBinaryBaseCSSReader;\n\n  // Make a CSSValue of default type and string value. Normally for unit-tests.\n  static CSSValue MakePlainString(const char* value) {\n    return CSSValue(value, CSSValuePattern::STRING, CSSValueType::DEFAULT);\n  }\n\n  static std::string Substitution(\n      const CSSValue& css_value, const CustomPropertiesMap& variable_map,\n      const CycleDetector& detector, int max_depth = 10,\n      const HandleCustomPropertyFunc& handle_func = nullptr);\n  static std::string ResolveVariable(\n      const std::string& var_name, const CustomPropertiesMap& custom_properties,\n      const CycleDetector& detector, int max_depth = 10,\n      const HandleCustomPropertyFunc& handle_func = nullptr);\n  static std::string ResolveFallback(\n      const VarReference& ref, const CustomPropertiesMap& variable_map,\n      const CycleDetector& detector, int max_depth = 10,\n      const HandleCustomPropertyFunc& handle_func = nullptr);\n  static std::string ResolveFallbackResolved(\n      const VarReference& ref, const CustomPropertiesMap& variable_map,\n      const HandleCustomPropertyFunc& handle_func = nullptr);\n\n  BASE_INLINE bool IsValueStorageNumber() const {\n    return type_ >= lynx_value_double && type_ <= lynx_value_uint64;\n  }\n  BASE_INLINE bool IsValueStorageReference() const {\n    return type_ >= lynx_value_string && type_ <= lynx_value_object;\n  }\n  BASE_INLINE void FreeValueStorage() {\n    if (IsValueStorageReference()) {\n      reinterpret_cast<fml::RefCountedThreadSafeStorage*>(val_ptr)->Release();\n    }\n  }\n\n  LYNX_VALUE_BASE_STORAGE_DEFINITION\n  union {\n    // All attributes initialized to 0 by single instruction.\n    uintptr_t __attr__{0};\n\n    struct {\n      lynx_value_type type_;\n      CSSValuePattern pattern_;\n\n      struct {\n        bool is_variable_ : 1;\n\n        // TODO(yangguangzhao.solace): Currently, `needs_variable_resolution_`\n        // serves a dual purpose: it indicates both whether the current\n        // CSSValue is handled by the new parsing logic and whether it\n        // requires further resolution. After the old CSSVariable parsing\n        // logic is deprecated, we will update this flag to solely represent\n        // its single, intended meaning: whether the CSSValue needs further\n        // resolution.\n        bool needs_variable_resolution_ : 1;\n      };\n    };\n  };\n\n  struct DefaultValueField {\n    using Type = base::String;\n  };\n  struct DefaultValueMapField {\n    using Type = lepus::Value;\n  };\n  struct VarReferenceField {\n    using Type = base::Vector<VarReference>;\n  };\n  mutable base::BundledOptionals<DefaultValueField, DefaultValueMapField,\n                                 VarReferenceField>\n      optionals_;\n};\n\nstatic_assert(\n    sizeof(CSSValue) <= 32,\n    \"The CSSValue struct has been precisely optimized to no more \"\n    \"than 32 bytes. If you need to add fields and this assertion is \"\n    \"broken, please contact the code owners of the CSS module first.\");\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_VALUE_H_\n"
  },
  {
    "path": "core/renderer/css/css_value_lldb.py",
    "content": "# Copyright 2025 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport lldb\nfrom enum import IntEnum\n\n\nclass LynxValueType(IntEnum):\n    NULL = 0\n    UNDEFINED = 1\n    BOOL = 2\n    DOUBLE = 3\n    INT32 = 4\n    UINT32 = 5\n    INT64 = 6\n    UINT64 = 7\n    NAN = 8\n    STRING = 9\n    ARRAY = 10\n    DICTIONARY = 11\n    ARRAY_BUFFER = 12\n    OBJECT = 13\n    FUNCTION = 14\n    EXTERNAL = 15\n    EXTENDED = 16\n\nclass CSSValueSyntheticProvider:\n  def __init__(self, val_obj, internal_dict):\n    self.val_obj = val_obj\n\n  def update(self):\n    try:\n      # get type field in lynx_value\n      self.type_field = self.val_obj.GetChildMemberWithName('type_')\n      self.type_value = self.type_field.GetValueAsUnsigned()\n      \n      # get val_ptr field in lynx_value\n      self.val_ptr_field = self.val_obj.GetChildMemberWithName('val_ptr')\n      \n      self.child_count = 7\n      self.load_addr = self.val_obj.GetLoadAddress()\n    except:\n      self.type_value = 0\n      self.child_count = 0\n\n  def num_children(self, max_children):\n    return self.child_count\n\n  def get_child_at_index(self,index):\n    if index < 0 or index >= self.child_count:\n      return None\n    \n    try:\n      if index == 0:\n        # If val_ptr is a RefCountedStringImpl raw pointer\n        if self.type_value == LynxValueType.STRING:\n          string_type = self.val_obj.GetTarget().FindFirstType(\n              'lynx::base::RefCountedStringImpl').GetPointerType()\n          return self.val_ptr_field.Cast(string_type)\n        # If val_ptr is a CArray raw pointer\n        elif self.type_value == LynxValueType.ARRAY:\n          array_type = self.val_obj.GetTarget().FindFirstType(\n              'lynx::lepus::CArray').GetPointerType()\n          return self.val_ptr_field.Cast(array_type)\n        # If val_ptr is a Dictionary raw pointer\n        elif self.type_value == LynxValueType.DICTIONARY:\n          dict_type = self.val_obj.GetTarget().FindFirstType(\n              'lynx::lepus::Dictionary').GetPointerType()\n          return self.val_ptr_field.Cast(dict_type)\n        else:\n          return self.get_union_value()\n\n      if index == 1:\n        return self.val_obj.GetChildMemberWithName('__attr__')\n      if index == 2:\n        return self.type_field\n      if index == 3:\n        return self.val_obj.GetChildMemberWithName('pattern_')\n      if index == 4:\n        return self.val_obj.GetChildMemberWithName('is_variable_')\n      if index == 5:\n        return self.val_obj.GetChildMemberWithName('needs_variable_resolution_')\n      if index == 6:\n        return self.val_obj.GetChildMemberWithName('optionals_')\n    except:\n      return None\n\n  def has_children(self):\n    return True\n\n  def get_union_value(self):\n    try:\n      if self.type_value in [LynxValueType.BOOL, LynxValueType.NAN]:\n        return self.val_obj.GetChildMemberWithName('val_bool')\n      elif self.type_value == LynxValueType.DOUBLE:\n        return self.val_obj.GetChildMemberWithName('val_double')\n      else:\n        return self.val_obj.GetChildMemberWithName('val_int64')\n    except:\n      return None\n\ndef __lldb_init_module(debugger, internal_dict):\n    debugger.HandleCommand(\n      'type synthetic add -x \"^lynx::tasm::CSSValue$\" -l css_value_lldb.CSSValueSyntheticProvider -w liblynx'\n    )\n    debugger.HandleCommand('type category enable liblynx')"
  },
  {
    "path": "core/renderer/css/css_value_unittest.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#define protected public\n\n#include \"core/renderer/css/css_value.h\"\n\n#include <gtest/gtest.h>\n\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass CSSValueSubstitutionTest : public ::testing::Test {\n protected:\n  void SetUp() override {\n    // Setup common test data\n  }\n  CSSParserConfigs configs_;\n};\n\nclass CSSValueToVarReferenceTest : public ::testing::Test {\n protected:\n  void SetUp() override {\n    // Setup common test data for ToVarReference tests\n  }\n  CSSParserConfigs configs_;\n};\n\nTEST(CSSValueTest, DefaultConstruct) {\n  CSSValue v;\n  EXPECT_TRUE(v.type_ == lynx_value_null);\n  EXPECT_TRUE(v.IsEmpty());\n  EXPECT_TRUE(v.GetValueType() == CSSValueType::DEFAULT);\n}\n\nTEST(CSSValueTest, ConstructFromEnum) {\n  enum TempEnum { e1, e2, e3 = 1000, e4 = 9999 };\n  CSSValue v1(e1);\n  CSSValue v3(e3);\n  CSSValue v4(e4);\n  EXPECT_TRUE(v1.IsEnum());\n  EXPECT_TRUE(v3.IsEnum());\n  EXPECT_TRUE(v4.IsEnum());\n  EXPECT_TRUE(v1.GetEnum<TempEnum>() == e1);\n  EXPECT_TRUE(v3.GetEnum<TempEnum>() == e3);\n  EXPECT_TRUE(v4.GetEnum<TempEnum>() == e4);\n}\n\nTEST(CSSValueTest, ConstructFromNumber) {\n  {\n    bool b = true;\n    CSSValue v(b);\n    EXPECT_TRUE(v.IsBoolean());\n    EXPECT_TRUE(v.GetBool());\n    EXPECT_TRUE(v.GetValue().IsBool());\n    EXPECT_TRUE(v.GetValue().Bool());\n  }\n\n  {\n    double d = 3.14;\n    CSSValue v(d, CSSValuePattern::PX);\n    EXPECT_TRUE(v.GetPattern() == CSSValuePattern::PX);\n    EXPECT_TRUE(v.IsPx());\n    EXPECT_TRUE(v.GetNumber() == d);\n    EXPECT_TRUE(v.GetValue().IsNumber());\n    EXPECT_TRUE(v.GetValue().IsDouble());\n    EXPECT_TRUE(v.GetValue().Number() == d);\n  }\n\n  {\n    int32_t i = 99;\n    CSSValue v(i, CSSValuePattern::PERCENT);\n    EXPECT_TRUE(v.GetPattern() == CSSValuePattern::PERCENT);\n    EXPECT_TRUE(v.IsPercent());\n    EXPECT_TRUE(v.GetNumber() == (double)i);\n    EXPECT_TRUE(v.GetValue().IsNumber());\n    EXPECT_TRUE(v.GetValue().IsInt32());\n    EXPECT_TRUE(v.GetValue().Number() == (double)i);\n  }\n\n  {\n    uint32_t i = 99;\n    CSSValue v(i, CSSValuePattern::PERCENT);\n    EXPECT_TRUE(v.GetPattern() == CSSValuePattern::PERCENT);\n    EXPECT_TRUE(v.IsPercent());\n    EXPECT_TRUE(v.GetNumber() == (double)i);\n    EXPECT_TRUE(v.GetValue().IsNumber());\n    EXPECT_TRUE(v.GetValue().IsUInt32());\n    EXPECT_TRUE(v.GetValue().Number() == (double)i);\n  }\n\n  {\n    int64_t i = 99;\n    CSSValue v(i, CSSValuePattern::PERCENT);\n    EXPECT_TRUE(v.GetPattern() == CSSValuePattern::PERCENT);\n    EXPECT_TRUE(v.IsPercent());\n    EXPECT_TRUE(v.GetNumber() == (double)i);\n    EXPECT_TRUE(v.GetValue().IsNumber());\n    EXPECT_TRUE(v.GetValue().IsInt64());\n    EXPECT_TRUE(v.GetValue().Number() == (double)i);\n  }\n\n  {\n    uint64_t i = 99;\n    CSSValue v(i, CSSValuePattern::PERCENT);\n    EXPECT_TRUE(v.GetPattern() == CSSValuePattern::PERCENT);\n    EXPECT_TRUE(v.IsPercent());\n    EXPECT_TRUE(v.GetNumber() == (double)i);\n    EXPECT_TRUE(v.GetValue().IsNumber());\n    EXPECT_TRUE(v.GetValue().IsUInt64());\n    EXPECT_TRUE(v.GetValue().Number() == (double)i);\n  }\n}\n\nTEST_F(CSSValueSubstitutionTest, SimpleVariableSubstitution) {\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--color\", CSSValue::MakePlainString(\"red\"));\n\n  lepus::Value variable = lepus::Value(\"var(--color)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"red\");\n}\n\nTEST_F(CSSValueSubstitutionTest, VariableWithFallback) {\n  CustomPropertiesMap variables;\n  // --primary is not defined, should use fallback\n\n  lepus::Value variable = lepus::Value(\"var(--primary, blue)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \" blue\");\n}\n\nTEST_F(CSSValueSubstitutionTest, NestedVariableReferences) {\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--primary\", CSSValue::MakePlainString(\"red\"));\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--primary)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue var_primary = parser.ParseVariable();\n\n    variables.insert_or_assign(\"--secondary\", var_primary);\n  }\n\n  lepus::Value variable = lepus::Value(\"var(--secondary)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"red\");\n}\n\nTEST_F(CSSValueSubstitutionTest, DeepNestedVariableReferences) {\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--a\", CSSValue::MakePlainString(\"red\"));\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a = parser.ParseVariable();\n    variables.insert_or_assign(\"--b\", ref_to_a);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--b)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_b = parser.ParseVariable();\n    variables.insert_or_assign(\"--c\", ref_to_b);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--c)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_c = parser.ParseVariable();\n    variables.insert_or_assign(\"--d\", ref_to_c);\n  }\n\n  lepus::Value variable = lepus::Value(\"var(--d)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"red\");\n}\n\nTEST_F(CSSValueSubstitutionTest, CycleDetection) {\n  CustomPropertiesMap variables;\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a = parser.ParseVariable();\n    variables.insert_or_assign(\"--b\", ref_to_a);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--b)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_b = parser.ParseVariable();\n    variables.insert_or_assign(\"--a\", ref_to_b);\n  }\n\n  lepus::Value variable = lepus::Value(\"var(--a)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"\");  // Should return empty due to cycle\n}\n\nTEST_F(CSSValueSubstitutionTest, DepthLimit) {\n  CustomPropertiesMap variables;\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a2)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a2 = parser.ParseVariable();\n    variables.insert_or_assign(\"--a1\", ref_to_a2);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a3)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a3 = parser.ParseVariable();\n    variables.insert_or_assign(\"--a2\", ref_to_a3);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a4)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a4 = parser.ParseVariable();\n    variables.insert_or_assign(\"--a3\", ref_to_a4);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a5)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a5 = parser.ParseVariable();\n    variables.insert_or_assign(\"--a4\", ref_to_a5);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a6)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a6 = parser.ParseVariable();\n    variables.insert_or_assign(\"--a5\", ref_to_a6);\n  }\n\n  variables.insert_or_assign(\"--a6\", CSSValue::MakePlainString(\"red\"));\n\n  lepus::Value variable = lepus::Value(\"var(--a1)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  // Test with depth limit 3 (should hit limit)\n  std::string result = CSSValue::Substitution(css_value, variables, 3);\n  EXPECT_EQ(result, \"\");  // Should return empty due to depth limit\n\n  // Test with sufficient depth\n  result = CSSValue::Substitution(css_value, variables, 10);\n  EXPECT_EQ(result, \"red\");\n}\n\nTEST_F(CSSValueSubstitutionTest, MultipleVariablesInOneString) {\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--color\", CSSValue::MakePlainString(\"red\"));\n  variables.insert_or_assign(\"--size\", CSSValue::MakePlainString(\"16px\"));\n\n  lepus::Value variable =\n      lepus::Value(\"color: var(--color); font-size: var(--size)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"color: red; font-size: 16px\");\n}\n\nTEST_F(CSSValueSubstitutionTest, NonVariableValue) {\n  CustomPropertiesMap variables;\n\n  auto css_value = CSSValue::MakePlainString(\"red\");\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"red\");\n}\n\nTEST_F(CSSValueSubstitutionTest, EmptyVariableMap) {\n  CustomPropertiesMap variables;\n\n  lepus::Value variable = lepus::Value(\"var(--undefined)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"\");  // Should return empty for undefined variable\n}\n\nTEST_F(CSSValueSubstitutionTest, MultiVariableCycleDetection) {\n  CustomPropertiesMap variables;\n\n  // Create a cycle: --a -> --b -> --c -> --a\n  {\n    lepus::Value variable = lepus::Value(\"var(--b)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_b = parser.ParseVariable();\n    variables.insert_or_assign(\"--a\", ref_to_b);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--c)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_c = parser.ParseVariable();\n    variables.insert_or_assign(\"--b\", ref_to_c);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a = parser.ParseVariable();\n    variables.insert_or_assign(\"--c\", ref_to_a);\n  }\n\n  lepus::Value variable = lepus::Value(\"var(--a)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"\");  // Should return empty due to cycle\n}\n\nTEST_F(CSSValueSubstitutionTest, SelfReferencingVariable) {\n  CustomPropertiesMap variables;\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--self)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_self = parser.ParseVariable();\n    variables.insert_or_assign(\"--self\", ref_to_self);\n  }\n\n  lepus::Value variable = lepus::Value(\"var(--self)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"\");  // Should return empty due to self-reference\n}\n\nTEST_F(CSSValueSubstitutionTest, MultipleVariablesWithCycle) {\n  CustomPropertiesMap variables;\n\n  // Create variables with a cycle\n  variables.insert_or_assign(\"--valid\", CSSValue::MakePlainString(\"blue\"));\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--cycle1)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_cycle1 = parser.ParseVariable();\n    variables.insert_or_assign(\"--cycle2\", ref_to_cycle1);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--cycle2)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_cycle2 = parser.ParseVariable();\n    variables.insert_or_assign(\"--cycle1\", ref_to_cycle2);\n  }\n\n  // Test multiple variables in one string where one has a cycle\n  lepus::Value variable =\n      lepus::Value(\"color: var(--valid); border: var(--cycle1)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"color: blue; border: \");  // --cycle1 should resolve to\n                                               // empty due to cycle\n}\n\nTEST_F(CSSValueSubstitutionTest, ComplexCycleWithFallback) {\n  CustomPropertiesMap variables;\n\n  // Create a cycle with fallback values\n  {\n    lepus::Value variable = lepus::Value(\"var(--cyclic, fallback)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_cyclic = parser.ParseVariable();\n    variables.insert_or_assign(\"--cyclic\", ref_to_cyclic);\n  }\n\n  lepus::Value variable = lepus::Value(\"var(--cyclic)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"\");  // Should return empty due to cycle, not fallback\n}\n\nTEST_F(CSSValueSubstitutionTest, CrossReferenceCycleDetection) {\n  CustomPropertiesMap variables;\n\n  // Create variables that reference each other in a complex way\n  {\n    lepus::Value variable = lepus::Value(\"var(--x) var(--y)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue complex_ref = parser.ParseVariable();\n    variables.insert_or_assign(\"--z\", complex_ref);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--z)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_z = parser.ParseVariable();\n    variables.insert_or_assign(\"--x\", ref_to_z);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--x)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_x = parser.ParseVariable();\n    variables.insert_or_assign(\"--y\", ref_to_x);\n  }\n\n  lepus::Value variable = lepus::Value(\"var(--x)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"\");  // Should detect cycle across multiple references\n}\n\nTEST_F(CSSValueSubstitutionTest, CycleWithFallbackCorrectBehavior) {\n  CustomPropertiesMap variables;\n\n  // Create a 3-element cycle: --a -> --b -> --c -> --a\n  {\n    lepus::Value variable = lepus::Value(\"var(--b, fallback-b)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_b = parser.ParseVariable();\n    variables.insert_or_assign(\"--a\", ref_to_b);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--c, fallback-c)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_c = parser.ParseVariable();\n    variables.insert_or_assign(\"--b\", ref_to_c);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a, fallback-a)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a = parser.ParseVariable();\n    variables.insert_or_assign(\"--c\", ref_to_a);\n  }\n\n  // Test that variables in the cycle return empty (undefined) instead of\n  // fallback\n  lepus::Value variable_a = lepus::Value(\"var(--a)\");\n  CSSStringParser parser_a =\n      CSSStringParser::FromLepusString(variable_a, configs_);\n  CSSValue css_value_a = parser_a.ParseVariable();\n  std::string result_a = CSSValue::Substitution(css_value_a, variables);\n  EXPECT_EQ(result_a, \"\");  // Should return empty due to cycle, not fallback\n\n  lepus::Value variable_b = lepus::Value(\"var(--b)\");\n  CSSStringParser parser_b =\n      CSSStringParser::FromLepusString(variable_b, configs_);\n  CSSValue css_value_b = parser_b.ParseVariable();\n  std::string result_b = CSSValue::Substitution(css_value_b, variables);\n  EXPECT_EQ(result_b, \"\");  // Should return empty due to cycle, not fallback\n\n  lepus::Value variable_c = lepus::Value(\"var(--c)\");\n  CSSStringParser parser_c =\n      CSSStringParser::FromLepusString(variable_c, configs_);\n  CSSValue css_value_c = parser_c.ParseVariable();\n  std::string result_c = CSSValue::Substitution(css_value_c, variables);\n  EXPECT_EQ(result_c, \"\");  // Should return empty due to cycle, not fallback\n\n  // Test that non-cyclic variables can use fallback correctly\n  variables.insert_or_assign(\"--valid\", CSSValue::MakePlainString(\"blue\"));\n\n  lepus::Value variable_d = lepus::Value(\"var(--nonexistent, fallback-value)\");\n  CSSStringParser parser_d =\n      CSSStringParser::FromLepusString(variable_d, configs_);\n  CSSValue css_value_d = parser_d.ParseVariable();\n  std::string result_d = CSSValue::Substitution(css_value_d, variables);\n  EXPECT_EQ(result_d,\n            \" fallback-value\");  // Should use fallback for non-cyclic undefined\n}\n\nTEST_F(CSSValueSubstitutionTest, NonCycleFallbackBehavior) {\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--valid\", CSSValue::MakePlainString(\"blue\"));\n\n  // Test that variables not in cycles can use fallback correctly\n  lepus::Value variable = lepus::Value(\"var(--undefined, fallback)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(\n      result,\n      \" fallback\");  // Should use fallback for non-cyclic undefined variable\n}\n\nTEST_F(CSSValueSubstitutionTest, MixedCycleAndValidVariables) {\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--valid\", CSSValue::MakePlainString(\"green\"));\n\n  // Create a cycle\n  {\n    lepus::Value variable = lepus::Value(\"var(--cyclic)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_cyclic = parser.ParseVariable();\n    variables.insert_or_assign(\"--a\", ref_to_cyclic);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--a)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a = parser.ParseVariable();\n    variables.insert_or_assign(\"--cyclic\", ref_to_a);\n  }\n\n  // Test mixed usage: valid variable + cyclic variable\n  lepus::Value variable =\n      lepus::Value(\"color: var(--valid); border: var(--cyclic, fallback)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result,\n            \"color: green; border:  fallback\");  // --cyclic is on circle, but\n                                                 // caller can use fallback\n}\n\nTEST_F(CSSValueSubstitutionTest, MixedCycleAndValidVariables2) {\n  CustomPropertiesMap variables;\n  /**\n   *\n   *  background: var(--cyclic-a, blue);\n   *  --cyclic: var(--cyclic-b, red);\n   *  --cyclic-a: var(--cyclic-b, yellow);\n   *  --cyclic-b: var(--cyclic, pink);\n   * */\n\n  // Create a cycle\n  {\n    lepus::Value variable = lepus::Value(\"var(--cyclic-b, red)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_cyclic = parser.ParseVariable();\n    variables.insert_or_assign(\"--cyclic\", ref_to_cyclic);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--cyclic-b, yellow)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a = parser.ParseVariable();\n    variables.insert_or_assign(\"--cyclic-a\", ref_to_a);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--cyclic, pink)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_b = parser.ParseVariable();\n    variables.insert_or_assign(\"--cyclic-b\", ref_to_b);\n  }\n\n  // Test mixed usage: valid variable + cyclic variable\n  lepus::Value variable = lepus::Value(\"var(--cyclic-a, blue)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \" yellow\");\n}\n\nTEST_F(CSSValueSubstitutionTest, SubstitutionConsumeProperty1) {\n  CustomPropertiesMap custom_properties;\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--b, red)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_cyclic = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--a\", ref_to_cyclic);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"var(--c, yellow)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--b\", ref_to_a);\n  }\n\n  {\n    lepus::Value variable = lepus::Value(\"blue\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_b = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--c\", ref_to_b);\n  }\n\n  CSSVariableMap result_css_related_variables{};\n  CSSVariableMap target_css_related_variables{};\n  target_css_related_variables.insert_or_assign(\"--d\", \"\");\n  target_css_related_variables.insert_or_assign(\"--a\", \"var(--b, red)\");\n  target_css_related_variables.insert_or_assign(\"--b\", \"var(--c, yellow)\");\n  target_css_related_variables.insert_or_assign(\"--c\", \"blue\");\n\n  lepus::Value variable = lepus::Value(\"var(--d, var(--a))\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  CSSValue::HandleCustomPropertyFunc handle_func =\n      [&result_css_related_variables](const base::String& name,\n                                      const base::String& value) {\n        result_css_related_variables.insert_or_assign(name, value);\n      };\n\n  std::string value_str =\n      CSSValue::Substitution(css_value, custom_properties, 10, handle_func);\n  EXPECT_EQ(value_str, \" blue\");\n  EXPECT_EQ(result_css_related_variables, target_css_related_variables);\n}\n\nTEST_F(CSSValueSubstitutionTest, SubstitutionNestedVariable) {\n  CustomPropertiesMap custom_properties;\n  {\n    lepus::Value variable = lepus::Value(\"var(--b, red)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_cyclic = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--a\", ref_to_cyclic);\n  }\n  {\n    lepus::Value variable = lepus::Value(\"var(--c, yellow)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--b\", ref_to_a);\n  }\n  {\n    lepus::Value variable = lepus::Value(\"blue\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_b = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--c\", ref_to_b);\n  }\n\n  lepus::Value variable = lepus::Value(\n      \"var(--d, var(--invalid-name, var(--invalid-name2, var(--a))))\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n  std::string result =\n      CSSValue::Substitution(css_value, custom_properties, 10, nullptr);\n  EXPECT_EQ(result, \"   blue\");\n}\n\nTEST_F(CSSValueSubstitutionTest, SubstituteAll) {\n  CustomPropertiesMap custom_properties;\n  {\n    lepus::Value variable = lepus::Value(\"var(--b, red)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_cyclic = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--a\", ref_to_cyclic);\n    EXPECT_TRUE(ref_to_cyclic.NeedsVariableResolution());\n    EXPECT_TRUE(custom_properties[\"--a\"].NeedsVariableResolution());\n  }\n  {\n    lepus::Value variable = lepus::Value(\"var(--c, yellow)\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_a = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--b\", ref_to_a);\n  }\n  {\n    lepus::Value variable = lepus::Value(\"blue\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue ref_to_b = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--c\", ref_to_b);\n  }\n  {\n    lepus::Value variable = lepus::Value(\n        \"var(--d, var(--invalid-name, var(--invalid-name2, var(--a))))\");\n    CSSStringParser parser =\n        CSSStringParser::FromLepusString(variable, configs_);\n    CSSValue css_value = parser.ParseVariable();\n    custom_properties.insert_or_assign(\"--d\", css_value);\n  }\n\n  CSSValue::SubstituteAll(custom_properties, 10, nullptr);\n\n  EXPECT_EQ(custom_properties[\"--a\"].AsStdString(), \"blue\");\n  EXPECT_EQ(custom_properties[\"--b\"].AsStdString(), \"blue\");\n  EXPECT_EQ(custom_properties[\"--c\"].AsStdString(), \"blue\");\n  EXPECT_EQ(custom_properties[\"--d\"].AsStdString(), \"   blue\");\n}\n\n// Tests for ToVarReference function using Substitution method\nTEST_F(CSSValueToVarReferenceTest, SimpleVariableToVarReference) {\n  // Test simple {{--variable}} format conversion\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--color\", CSSValue::MakePlainString(\"red\"));\n\n  CSSValue css_value(\"{{--color}}\", CSSValuePattern::STRING,\n                     CSSValueType::VARIABLE);\n  css_value.SetDefaultValue(\"blue\");\n\n  // Convert to VarReference\n  EXPECT_TRUE(css_value.ToVarReference());\n  EXPECT_TRUE(css_value.NeedsVariableResolution());\n  EXPECT_TRUE(css_value.IsVariable());\n\n  // Test that substitution works with the converted VarReference\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"red\");  // Should resolve to the variable value\n}\n\nTEST_F(CSSValueToVarReferenceTest, MultipleVariablesToVarReference) {\n  // Test multiple variables in one value: {{--color}} {{--size}}\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--color\", CSSValue::MakePlainString(\"red\"));\n  variables.insert_or_assign(\"--size\", CSSValue::MakePlainString(\"16px\"));\n\n  CSSValue css_value(\"{{--color}} {{--size}}\", CSSValuePattern::STRING,\n                     CSSValueType::VARIABLE);\n\n  EXPECT_TRUE(css_value.ToVarReference());\n  EXPECT_TRUE(css_value.NeedsVariableResolution());\n\n  // Test that substitution works with multiple variables\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"red 16px\");  // Should resolve both variables\n}\n\nTEST_F(CSSValueToVarReferenceTest, VariableWithFallbackMapToVarReference) {\n  // Test variable with fallback from default value map\n  CustomPropertiesMap variables;\n  // --primary is not defined, should use fallback\n\n  // Create a CSS value with default value map\n  lepus::Value default_map(lepus::Dictionary::Create());\n  default_map.Table()->SetValue(\"--primary\", lepus::Value(\"red\"));\n\n  CSSValue css_value(\"{{--primary}}\", CSSValuePattern::STRING,\n                     CSSValueType::VARIABLE);\n  css_value.SetDefaultValueMap(default_map);\n\n  EXPECT_TRUE(css_value.ToVarReference());\n\n  // Test that substitution works with fallback from default map\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"red\");  // Should use fallback from default map\n}\n\nTEST_F(CSSValueToVarReferenceTest, NoVariableConversionForNonVariable) {\n  // Test that non-variable CSS values are not converted\n  auto css_value = CSSValue::MakePlainString(\"red\");\n\n  EXPECT_FALSE(css_value.ToVarReference());\n  EXPECT_FALSE(css_value.NeedsVariableResolution());\n  EXPECT_FALSE(css_value.IsVariable());\n}\n\nTEST_F(CSSValueToVarReferenceTest, NoDoubleConversion) {\n  // Test that already converted variables are not converted again\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--color\", CSSValue::MakePlainString(\"red\"));\n\n  CSSValue css_value(\"{{--color}}\", CSSValuePattern::STRING,\n                     CSSValueType::VARIABLE);\n\n  // First conversion\n  EXPECT_TRUE(css_value.ToVarReference());\n  EXPECT_TRUE(css_value.NeedsVariableResolution());\n\n  // Second conversion should fail\n  EXPECT_FALSE(css_value.ToVarReference());\n\n  // Verify substitution still works after attempted double conversion\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"red\");\n}\n\nTEST_F(CSSValueToVarReferenceTest, EmptyVariableName) {\n  // Test edge case with empty variable name: {{}}\n  CustomPropertiesMap variables;\n\n  CSSValue css_value(\"{{}}\", CSSValuePattern::STRING, CSSValueType::VARIABLE);\n\n  EXPECT_TRUE(css_value.ToVarReference());\n\n  // Test substitution with empty variable name\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"\");  // Should return empty for undefined variable\n}\n\nTEST_F(CSSValueToVarReferenceTest, ComplexVariableWithCalc) {\n  // Test complex variable usage: calc({{--size}} * 2)\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--size\", CSSValue::MakePlainString(\"10px\"));\n\n  CSSValue css_value(\"calc({{--size}} * 2)\", CSSValuePattern::STRING,\n                     CSSValueType::VARIABLE);\n\n  EXPECT_TRUE(css_value.ToVarReference());\n\n  // Test that substitution works with complex expressions\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result,\n            \"calc(10px * 2)\");  // Should resolve variable in calc expression\n}\n\nTEST_F(CSSValueToVarReferenceTest, VariableWithSimpleFallback) {\n  // Test variable with simple fallback value\n  CustomPropertiesMap variables;\n  // --undefined-color is not in variables, should use fallback\n\n  CSSValue css_value(\"{{--undefined-color}}\", CSSValuePattern::STRING,\n                     CSSValueType::VARIABLE);\n  css_value.SetDefaultValue(\"black\");\n\n  EXPECT_TRUE(css_value.ToVarReference());\n\n  // Test that substitution works with simple fallback\n  std::string result = CSSValue::Substitution(css_value, variables);\n  EXPECT_EQ(result, \"black\");  // Should use the fallback value\n}\n\nTEST_F(CSSValueSubstitutionTest, SubstitutionResolvedSimple) {\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--color\", CSSValue::MakePlainString(\"red\"));\n  variables.insert_or_assign(\"--size\", CSSValue::MakePlainString(\"16px\"));\n\n  lepus::Value variable =\n      lepus::Value(\"color: var(--color); font-size: var(--size)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result =\n      CSSValue::SubstitutionResolved(css_value, variables, nullptr);\n  EXPECT_EQ(result, \"color: red; font-size: 16px\");\n}\n\nTEST_F(CSSValueSubstitutionTest, SubstitutionResolvedFallback) {\n  CustomPropertiesMap variables;\n  // --primary is not defined, should use fallback\n\n  lepus::Value variable = lepus::Value(\"var(--primary, blue)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result =\n      CSSValue::SubstitutionResolved(css_value, variables, nullptr);\n  EXPECT_EQ(result, \" blue\");\n}\n\nTEST_F(CSSValueSubstitutionTest, SubstitutionResolvedNoRecursive) {\n  CustomPropertiesMap variables;\n  // Intentionally put a raw var string in map. SubstitutionResolved should just\n  // use it as is.\n  variables.insert_or_assign(\"--color\",\n                             CSSValue::MakePlainString(\"var(--other)\"));\n\n  lepus::Value variable = lepus::Value(\"var(--color)\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result =\n      CSSValue::SubstitutionResolved(css_value, variables, nullptr);\n  // It should NOT resolve --other, just return the string \"var(--other)\"\n  EXPECT_EQ(result, \"var(--other)\");\n}\n\nTEST_F(CSSValueSubstitutionTest, SubstitutionResolvedWithFallbackResolved) {\n  CustomPropertiesMap variables;\n  variables.insert_or_assign(\"--inner\", CSSValue::MakePlainString(\"green\"));\n\n  // Outer var not found, fallback contains inner var which SHOULD be resolved\n  lepus::Value variable = lepus::Value(\"var(--missing, var(--inner))\");\n  CSSStringParser parser = CSSStringParser::FromLepusString(variable, configs_);\n  CSSValue css_value = parser.ParseVariable();\n\n  std::string result =\n      CSSValue::SubstitutionResolved(css_value, variables, nullptr);\n  EXPECT_EQ(result, \" green\");\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_variable_handler.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/css_variable_handler.h\"\n\n#include <utility>\n\n#include \"base/include/no_destructor.h\"\n#include \"base/include/value/table.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nbool CSSVariableHandler::HandleCSSVariables(StyleMap& map,\n                                            AttributeHolder* holder,\n                                            const CSSParserConfigs& configs) {\n  if (map.empty()) {\n    return false;\n  }\n\n  auto first_variable_iter = map.end();\n  for (auto it = map.begin(); it != map.end(); ++it) {\n    if (it->second.IsVariable()) {\n      first_variable_iter = it;\n      break;\n    }\n  }\n\n  if (first_variable_iter == map.end()) {\n    return false;\n  }\n  // the CSSVariable order need to be kept.\n  StyleMap style_map;\n  style_map.reserve(CSSProperty::GetTotalParsedStyleCountFromMap(map));\n\n  for (auto it = map.begin(); it != first_variable_iter; ++it) {\n    style_map[it->first] = std::move(it->second);\n  }\n\n  for (auto it = first_variable_iter; it != map.end(); ++it) {\n    if (it->second.IsVariable()) {\n      const auto& id = it->first;\n      const auto& css_value = it->second;\n      if (css_value.NeedsVariableResolution()) {\n        ResolveCSSVariables(id, css_value, style_map, holder, configs);\n        continue;\n      }\n\n      const auto& value_expr = css_value.GetValue();\n      if (value_expr.IsString()) {\n        auto default_value_map_opt = css_value.GetDefaultValueMapOpt();\n        auto property = GetCSSVariableByRule(value_expr.StdString(), holder,\n                                             css_value.GetDefaultValue(),\n                                             default_value_map_opt);\n        UnitHandler::Process(id, lepus::Value(std::move(property)), style_map,\n                             configs);\n      } else {\n        UnitHandler::Process(id, lepus::Value(css_value.GetDefaultValue()),\n                             style_map, configs);\n      }\n    } else {\n      style_map[it->first] = std::move(it->second);\n    }\n  }\n\n  map = std::move(style_map);\n  return true;\n}\n\nbool CSSVariableHandler::HasCSSVariableInStyleMap(const StyleMap& map) {\n  for (const auto& [_, css_value] : map) {\n    if (css_value.IsVariable()) {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool CSSVariableHandler::HasCSSVariableInHolder(const AttributeHolder* holder) {\n  if (!holder) {\n    return false;\n  }\n  return !(holder->css_variables_map().empty() &&\n           holder->GetCSSInlineVariables().empty());\n}\n\n//    \"The food taste {{ feeling }} !\"\n//    => rule: {{\"feeling\", \"delicious\"}}\n//    => result: \"The food taste delicious !\"\nbase::String CSSVariableHandler::GetCSSVariableByRule(\n    const std::string& format,\n    base::MoveOnlyClosure<base::String, const std::string&> rule_matcher) {\n  std::string variable_value;\n  std::string maybe_key;\n  int brace_start = -1;\n  int brace_end = -1;\n  int pre_brace_end = 0;\n  for (int i = 0; static_cast<size_t>(i) < format.size(); ++i) {\n    char c = format[i];\n    switch (c) {\n      case '{':\n        brace_start = i;\n        break;\n      case '}':\n        brace_end = brace_start != -1 ? i : -1;\n        break;\n      default:\n        break;\n    }\n    if (brace_start != -1 && brace_end != -1) {\n      variable_value.append(format, pre_brace_end,\n                            brace_start - pre_brace_end - 1);\n      maybe_key =\n          std::string(&format[brace_start + 1], brace_end - brace_start - 1);\n      base::String value = rule_matcher(maybe_key);\n\n      // if rule_matcher finds nothings, we should just use defaultValue.\n      if (value.empty()) {\n        return value;\n      }\n\n      variable_value.append(value.str());\n      // skip addition bracket characters\n      pre_brace_end = brace_end + 2;\n      brace_start = -1;\n      brace_end = -1;\n    }\n  }\n  if (static_cast<size_t>(pre_brace_end) < format.size()) {\n    variable_value.append(format, pre_brace_end, format.size() - pre_brace_end);\n  }\n  return std::move(variable_value);\n}\n\nbase::String CSSVariableHandler::GetCSSVariableByRule(\n    const std::string& format, AttributeHolder* holder,\n    const base::String& default_props, const lepus::Value& default_value_map) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, CSS_HANDLER_GET_VARIABLE_BY_RULE, \"format\",\n              format);\n  auto css_variable_value = GetCSSVariableByRule(\n      format,\n      [holder, self = this, &default_value_map](const std::string& maybe_key) {\n        auto value = holder->GetCSSVariableValue(maybe_key);\n        // If the default_value_map exists, look for possible default css var\n        // values, and if we can't find them, change the css value to\n        // default_props.\n        if (value.empty()) {\n          if (default_value_map.IsTable()) {\n            auto table = default_value_map.Table().get();\n            auto iter = table->find(maybe_key);\n            if (iter != table->end()) {\n              value = iter->second.String();\n            }\n          }\n        }\n        if (self->enable_fiber_arch_) {\n          // In FiberArch, relating node with it's related css variables for\n          // optimization.\n          holder->AddCSSVariableRelated(maybe_key, value);\n        }\n        return value;\n      });\n\n  if (css_variable_value.empty()) {\n    css_variable_value = default_props;\n  }\n  return css_variable_value;\n}\n\nstatic CustomPropertiesMap* EmptyCustomPropertyMap() {\n  static base::NoDestructor<CustomPropertiesMap> map;\n  return map.get();\n}\n\nvoid CSSVariableHandler::ResolveCSSVariables(CSSPropertyID id,\n                                             const CSSValue& value,\n                                             StyleMap& style_map,\n                                             AttributeHolder* holder,\n                                             const CSSParserConfigs& configs) {\n  if (!enable_fiber_arch_) {\n    // Resolve CSS variables only in FiberArch.\n    return;\n  }\n  if (!holder) {\n    LOGE(\"ResolveCSSVariables: holder is null\");\n    return;\n  }\n\n  const CustomPropertiesMap* custom_properties = holder->GetCustomProperties();\n  if (!custom_properties) {\n    custom_properties = EmptyCustomPropertyMap();\n  }\n\n  const auto handle_custom_property_func = [holder](const base::String& name,\n                                                    const base::String& value) {\n    holder->AddCSSVariableRelated(name, value);\n  };\n  auto property = CSSValue::SubstitutionResolved(value, *custom_properties,\n                                                 handle_custom_property_func);\n  UnitHandler::Process(id, lepus::Value(std::move(property)), style_map,\n                       configs);\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/css_variable_handler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_CSS_VARIABLE_HANDLER_H_\n#define CORE_RENDERER_CSS_CSS_VARIABLE_HANDLER_H_\n\n#include <string>\n\n#include \"base/include/closure.h\"\n#include \"core/base/lynx_export.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/dom/attribute_holder.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass CSSVariableHandler {\n public:\n  explicit CSSVariableHandler(bool enable_fiber_arch = false)\n      : enable_fiber_arch_(enable_fiber_arch) {}\n\n  // method to handle css variables in style map.\n  // If no CSS variables are present in the style map, return false; otherwise,\n  // return true.\n  bool HandleCSSVariables(StyleMap& map, AttributeHolder* holder,\n                          const CSSParserConfigs& configs);\n\n  // method to get variable value by DOM structure.\n  // if value not found, return default_props.\n  LYNX_EXPORT_FOR_DEVTOOL base::String GetCSSVariableByRule(\n      const std::string& format, AttributeHolder* holder,\n      const base::String& default_props, const lepus::Value& default_value_map);\n\n  bool HasCSSVariableInStyleMap(const StyleMap& map);\n\n  bool HasCSSVariableInHolder(const AttributeHolder* holder);\n\n private:\n  void ResolveCSSVariables(CSSPropertyID id, const CSSValue& value,\n                           StyleMap& style_map, AttributeHolder* holder,\n                           const CSSParserConfigs& configs);\n  static base::String GetCSSVariableByRule(\n      const std::string& format,\n      base::MoveOnlyClosure<base::String, const std::string&> rule_matcher);\n\n  bool enable_fiber_arch_;\n};\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_CSS_VARIABLE_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/css_variable_handler_unittest.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/css_variable_handler.h\"\n\n#include \"core/renderer/dom/vdom/radon/radon_node.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace testing {\n\nclass CSSVariableHandlerTest : public ::testing::Test {\n public:\n  CSSVariableHandlerTest() {}\n  ~CSSVariableHandlerTest() override {}\n\n  void SetUp() override {}\n};\n\nTEST_F(CSSVariableHandlerTest, FormatStringWithRule0) {\n  CSSVariableHandler handler;\n  RadonNode node(nullptr, \"my_tag\", 0);\n  node.UpdateCSSVariable(\"--main-bg-color\", \"red\");\n  base::String default_props;\n  lepus::Value default_value_map;\n  std::string result =\n      handler\n          .GetCSSVariableByRule(\"{{--main-bg-color}}\",\n                                node.attribute_holder().get(), default_props,\n                                default_value_map)\n          .c_str();\n  ASSERT_EQ(result, \"red\");\n}\n\nTEST_F(CSSVariableHandlerTest, FormatStringWithRule1) {\n  CSSVariableHandler handler;\n  RadonNode node(nullptr, \"my_tag\", 0);\n  node.UpdateCSSVariable(\"--height\", \"20px\");\n  base::String default_props;\n  lepus::Value default_value_map;\n  std::string result =\n      handler\n          .GetCSSVariableByRule(\"calc({{--height}} + {{--height}})\",\n                                node.attribute_holder().get(), default_props,\n                                default_value_map)\n          .c_str();\n  ASSERT_EQ(result, \"calc(20px + 20px)\");\n}\n\nTEST_F(CSSVariableHandlerTest, FormatStringWithRule2) {\n  CSSVariableHandler handler;\n  RadonNode node(nullptr, \"my_tag\", 0);\n  node.UpdateCSSVariable(\"--height\", \"20px\");\n  base::String default_props;\n  lepus::Value default_value_map;\n  std::string result =\n      handler\n          .GetCSSVariableByRule(\"calc({{--height}} - {{--height}})\",\n                                node.attribute_holder().get(), default_props,\n                                default_value_map)\n          .c_str();\n  ASSERT_EQ(result, \"calc(20px - 20px)\");\n}\n\nTEST_F(CSSVariableHandlerTest, FormatStringWithRule3) {\n  CSSVariableHandler handler;\n  RadonNode node(nullptr, \"my_tag\", 0);\n  node.UpdateCSSVariable(\"--height\", \"20px\");\n  base::String default_props;\n  lepus::Value default_value_map;\n  std::string result =\n      handler\n          .GetCSSVariableByRule(\"calc({{--height}} * {{--height}})\",\n                                node.attribute_holder().get(), default_props,\n                                default_value_map)\n          .c_str();\n  ASSERT_EQ(result, \"calc(20px * 20px)\");\n}\n\nTEST_F(CSSVariableHandlerTest, FormatStringWithRule4) {\n  CSSVariableHandler handler;\n  RadonNode node(nullptr, \"my_tag\", 0);\n  node.UpdateCSSVariable(\"--height\", \"20px\");\n  base::String default_props;\n  lepus::Value default_value_map;\n  std::string result =\n      handler\n          .GetCSSVariableByRule(\"calc({{--height}} / {{--height}})\",\n                                node.attribute_holder().get(), default_props,\n                                default_value_map)\n          .c_str();\n  ASSERT_EQ(result, \"calc(20px / 20px)\");\n}\n\nTEST_F(CSSVariableHandlerTest, FormatStringWithRule5) {\n  CSSVariableHandler handler;\n  RadonNode node(nullptr, \"my_tag\", 0);\n  node.UpdateCSSVariable(\"--height\", \"20px\");\n  node.UpdateCSSVariable(\"--width\", \"40px\");\n  base::String default_props;\n  lepus::Value default_value_map;\n  std::string result =\n      handler\n          .GetCSSVariableByRule(\"calc({{--height}} + {{--width}})\",\n                                node.attribute_holder().get(), default_props,\n                                default_value_map)\n          .c_str();\n  ASSERT_EQ(result, \"calc(20px + 40px)\");\n}\n\nTEST_F(CSSVariableHandlerTest, FormatStringWithRule6) {\n  CSSVariableHandler handler;\n  RadonNode node(nullptr, \"my_tag\", 0);\n  node.UpdateCSSVariable(\"--height\", \"20px\");\n  node.UpdateCSSVariable(\"--width\", \"40px\");\n  base::String default_props;\n  lepus::Value default_value_map;\n  std::string result =\n      handler\n          .GetCSSVariableByRule(\n              \"calc({{--height}} + {{--width}} * {{--height}})\",\n              node.attribute_holder().get(), default_props, default_value_map)\n          .c_str();\n  ASSERT_EQ(result, \"calc(20px + 40px * 20px)\");\n}\n\nTEST_F(CSSVariableHandlerTest, FormatStringWithRule7) {\n  CSSVariableHandler handler;\n  RadonNode node(nullptr, \"my_tag\", 0);\n  node.UpdateCSSVariable(\"--height\", \"20px\");\n  node.UpdateCSSVariable(\"--width\", \"40px\");\n  base::String default_props = \"calc(300px - 100px)\";\n  lepus::Value default_value_map;\n  std::string result =\n      handler\n          .GetCSSVariableByRule(\"calc({{--main-height}} - 100px)\",\n                                node.attribute_holder().get(), default_props,\n                                default_value_map)\n          .c_str();\n  ASSERT_EQ(result, \"calc(300px - 100px)\");\n}\n\nTEST_F(CSSVariableHandlerTest, FormatStringWithRule8) {\n  CSSVariableHandler handler;\n  RadonNode node(nullptr, \"my_tag\", 0);\n  node.UpdateCSSVariable(\"--var-style\", \"solid\");\n  //   node.UpdateCSSVariable(\"--var-color\", \"black\");\n  base::String default_props = \"2px double red\";\n  auto table = lepus::Dictionary::Create();\n  table->SetValue(base::String(\"--var-style\"), lepus::Value(\"double\"));\n  table->SetValue(base::String(\"--var-color\"), lepus::Value(\"red\"));\n  lepus::Value default_value_map = lepus::Value(table);\n  std::string result;\n  result = handler\n               .GetCSSVariableByRule(\"2px {{--var-style}} {{--var-color}}\",\n                                     node.attribute_holder().get(),\n                                     default_props, default_value_map)\n               .c_str();\n  ASSERT_EQ(result, \"2px solid red\");\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/dynamic_css_configs.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_DYNAMIC_CSS_CONFIGS_H_\n#define CORE_RENDERER_CSS_DYNAMIC_CSS_CONFIGS_H_\n\n#include <unordered_set>\n\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/tasm/config.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nstruct DynamicCSSConfigs {\n  static tasm::DynamicCSSConfigs& GetDefaultDynamicCSSConfigs() {\n    static base::NoDestructor<tasm::DynamicCSSConfigs> kDefaultCSSConfigs;\n    return *kDefaultCSSConfigs;\n  }\n\n  // TODO(wangzhixuan.0821): Remove the following setting when once inheritance\n  // is proved to be stable.\n  bool OnceInheritanceDisabled() const {\n    // cache the setting.\n    // static bool disable = tasm::LynxEnv::GetInstance().GetBoolEnv(\n    //     tasm::LynxEnv::Key::DISABLE_ONCE_DYNAMIC_CSS, false);\n    // return disable;\n    // TODO(zhixuan): Enable it after bugs are fixed.\n    return once_inheritance_disabled_;\n  }\n\n  bool enable_css_inheritance_ = false;\n  std::unordered_set<CSSPropertyID> custom_inherit_list_;\n  // Hack to keep the old behavior that vw is resolved against screen metrics\n  // only for font size if viweport size is specified as definite value.\n  bool unify_vw_vh_behavior_ = false;\n  bool font_scale_sp_only = false;\n  bool once_inheritance_disabled_{true};\n  bool enable_css_inline_variables_{false};\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_DYNAMIC_CSS_CONFIGS_H_\n"
  },
  {
    "path": "core/renderer/css/dynamic_css_styles_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/dynamic_css_styles_manager.h\"\n\n#include <memory>\n#include <string>\n#include <unordered_set>\n\n#include \"base/include/no_destructor.h\"\n#include \"core/build/gen/lynx_sub_error_code.h\"\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/css/layout_property.h\"\n#include \"core/renderer/css/measure_context.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/starlight/style/default_layout_style.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\nnamespace lynx {\nnamespace tasm {\nusing starlight::DirectionType;\nusing starlight::TextAlignType;\n\nnamespace {\n\n// Not that kPropertyIDFontSize, kPropertyIDLineSpacing,\n// kPropertyIDLetterSpacing, kPropertyIDLineHeight are not simple inheritable\n// props, should inherit computed style value for them.\nconst std::unordered_set<CSSPropertyID>& GetDefaultInheritableProps() {\n  const static base::NoDestructor<std::unordered_set<CSSPropertyID>>\n      kDefaultInheritableProps(std::unordered_set<CSSPropertyID>{\n          kPropertyIDFontSize, kPropertyIDFontFamily, kPropertyIDTextAlign,\n          kPropertyIDLineSpacing, kPropertyIDLetterSpacing,\n          kPropertyIDLineHeight, kPropertyIDFontStyle, kPropertyIDFontWeight,\n          kPropertyIDColor, kPropertyIDTextDecoration, kPropertyIDTextShadow,\n          kPropertyIDDirection, kPropertyIDCursor});\n  return *kDefaultInheritableProps;\n}\n\nconst std::unordered_set<CSSPropertyID>& GetComplexDynamicProps() {\n  const static base::NoDestructor<std::unordered_set<CSSPropertyID>>\n      kComplexDynamicProps(std::unordered_set<CSSPropertyID>{\n          kPropertyIDTransformOrigin,\n          kPropertyIDBackgroundSize,\n          kPropertyIDBackgroundPosition,\n          kPropertyIDBorderRadius,\n          kPropertyIDBorderTopLeftRadius,\n          kPropertyIDBorderTopRightRadius,\n          kPropertyIDBorderBottomLeftRadius,\n          kPropertyIDBorderBottomRightRadius,\n          kPropertyIDBorderStartStartRadius,\n          kPropertyIDBorderStartEndRadius,\n          kPropertyIDBorderEndEndRadius,\n          kPropertyIDBorderEndStartRadius,\n          kPropertyIDTransform,\n          kPropertyIDBoxShadow,\n          kPropertyIDTextShadow,\n          kPropertyIDGridAutoRows,\n          kPropertyIDGridAutoColumns,\n          kPropertyIDGridTemplateRows,\n          kPropertyIDGridTemplateColumns,\n      });\n  return *kComplexDynamicProps;\n}\n\ninline DynamicCSSStylesManager::StyleUpdateFlags GetPercentDependency(\n    CSSPropertyID id) {\n  // Currently, only line-height and font size are supported to have behavior of\n  // percentage unit\n  if (id == kPropertyIDLineHeight) {\n    return DynamicCSSStylesManager::kUpdateEm;\n  }\n  return DynamicCSSStylesManager::kNoUpdate;\n}\n\ninline DynamicCSSStylesManager::StyleUpdateFlags CheckFontScaleRelevance(\n    CSSPropertyID id) {\n  // Currently, only line-height and font size are supported to have behavior of\n  // percentage unit\n  if (id == kPropertyIDFontSize || id == kPropertyIDLineHeight ||\n      id == kPropertyIDLetterSpacing) {\n    return DynamicCSSStylesManager::kUpdateFontScale;\n  }\n  return DynamicCSSStylesManager::kNoUpdate;\n}\n\n}  // namespace\n\nconst std::unordered_set<CSSPropertyID>&\nDynamicCSSStylesManager::GetInheritableProps() {\n  return GetDefaultInheritableProps();\n}\n\n// static\nCSSPropertyID DynamicCSSStylesManager::ResolveDirectionAwarePropertyID(\n    CSSPropertyID id, starlight::DirectionType direction) {\n  return ResolveDirectionAwareProperty(id, direction);\n}\n\n// static\n// TODO(zhouzhitao): unify logic with radon element, remove this overwritten\n// version of UpdateDirectionAwareDefaultStyles\nvoid DynamicCSSStylesManager::UpdateDirectionAwareDefaultStyles(\n    Element* element, starlight::DirectionType direction,\n    const CSSValue& text_align_value) {\n  // Currently, only text-align has direction aware default property.\n  auto default_align_value = text_align_value;\n\n  if (default_align_value.IsEmpty()) {\n    default_align_value = CSSValue(TextAlignType::kStart);\n  }\n  const auto default_align =\n      ResolveTextAlign(kPropertyIDTextAlign, default_align_value, direction);\n  element->SetStyleInternal(kPropertyIDTextAlign, default_align.second);\n}\n\nDynamicCSSStylesManager::StyleUpdateFlags\nDynamicCSSStylesManager::GetValueFlags(CSSPropertyID id, const CSSValue& value,\n                                       bool unify_vw_vh_behavior) {\n  DynamicCSSStylesManager::StyleUpdateFlags flags =\n      DynamicCSSStylesManager::kNoUpdate;\n\n  switch (value.GetPattern()) {\n    case CSSValuePattern::EMPTY:\n    case CSSValuePattern::ENUM:\n      break;\n    case CSSValuePattern::RPX:\n      flags = DynamicCSSStylesManager::kUpdateScreenMetrics;\n    case CSSValuePattern::PX:\n    case CSSValuePattern::PPX:\n      flags |= CheckFontScaleRelevance(id);\n      break;\n    case CSSValuePattern::PERCENT:\n    case CSSValuePattern::NUMBER:\n      flags = GetPercentDependency(id);\n      flags |= CheckFontScaleRelevance(id);\n      break;\n    case CSSValuePattern::REM:\n      flags = DynamicCSSStylesManager::kUpdateRem;\n      break;\n    case CSSValuePattern::EM:\n      flags = DynamicCSSStylesManager::kUpdateEm;\n      break;\n    case CSSValuePattern::VW:\n    case CSSValuePattern::VH:\n      flags = DynamicCSSStylesManager::kUpdateViewport;\n      break;\n    case CSSValuePattern::CALC: {\n      const auto& calc_str = value.AsStdString();\n      if (calc_str.find(\"rpx\") != std::string::npos) {\n        flags |= DynamicCSSStylesManager::kUpdateScreenMetrics;\n        flags |= CheckFontScaleRelevance(id);\n      }\n      if (calc_str.find(\"em\") != std::string::npos) {\n        flags |= DynamicCSSStylesManager::kUpdateEm;\n      }\n      if (calc_str.find(\"rem\") != std::string::npos) {\n        flags |= DynamicCSSStylesManager::kUpdateRem;\n      }\n      if (calc_str.find(\"%\") != std::string::npos) {\n        flags |= GetPercentDependency(id);\n      }\n      if (calc_str.find(\"px\") != std::string::npos ||\n          calc_str.find(\"ppx\") != std::string::npos) {\n        flags |= CheckFontScaleRelevance(id);\n      }\n      if (calc_str.find(\"vw\") != std::string::npos ||\n          calc_str.find(\"vh\") != std::string::npos ||\n          calc_str.find(\"view_width\") != std::string::npos ||\n          calc_str.find(\"view_height\") != std::string::npos) {\n        flags |= DynamicCSSStylesManager::kUpdateViewport;\n      }\n      if (calc_str.find(\"sp\") != std::string::npos) {\n        flags |= DynamicCSSStylesManager::kUpdateFontScale;\n      }\n      break;\n    }\n    case CSSValuePattern::ENV:\n      // TODO:\n      break;\n    case CSSValuePattern::SP:\n      flags |= DynamicCSSStylesManager::kUpdateFontScale;\n    default:\n      // TODO: Currently always recompute complex properties, we can\n      // struturelize the properties before passing it to computed style in\n      // future.\n      if (GetComplexDynamicProps().count(id)) {\n        flags = DynamicCSSStylesManager::kUpdateScreenMetrics |\n                DynamicCSSStylesManager::kUpdateEm |\n                DynamicCSSStylesManager::kUpdateRem |\n                DynamicCSSStylesManager::kUpdateFontScale;\n        if (unify_vw_vh_behavior) {\n          flags = (flags | DynamicCSSStylesManager::kUpdateViewport);\n        }\n      }\n      break;\n  }\n  if (IsDirectionAwareStyle(id)) {\n    flags |= DynamicCSSStylesManager::kUpdateDirectionStyle;\n  }\n\n  return flags;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/dynamic_css_styles_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_DYNAMIC_CSS_STYLES_MANAGER_H_\n#define CORE_RENDERER_CSS_DYNAMIC_CSS_STYLES_MANAGER_H_\n\n#include <array>\n#include <functional>\n#include <map>\n#include <memory>\n#include <optional>\n#include <set>\n#include <unordered_set>\n#include <utility>\n\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/css/dynamic_css_configs.h\"\n#include \"core/renderer/css/dynamic_direction_styles_manager.h\"\n#include \"core/renderer/css/measure_context.h\"\n#include \"core/renderer/starlight/style/default_layout_style.h\"\n#include \"core/renderer/starlight/types/layout_configs.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nstruct PseudoPlaceHolderStyles {\n  std::optional<CSSValue> font_size_;\n  std::optional<CSSValue> color_;\n  std::optional<CSSValue> font_weight_;\n  std::optional<CSSValue> font_family_;\n};\n\nclass Element;\nclass LayoutNode;\n\nstruct PropertiesResolvingStatus {\n  // per page status\n  struct PageStatus {\n    float root_font_size_;\n    float font_scale_ = Config::DefaultFontScale();\n    starlight::LayoutUnit viewport_width_;\n    starlight::LayoutUnit viewport_height_;\n    float screen_width_ = 0.f;\n  };\n\n  PageStatus page_status_;\n\n  // per element status\n  float computed_font_size_;\n  starlight::DirectionType direction_type_ =\n      starlight::DefaultLayoutStyle::SL_DEFAULT_DIRECTION;\n\n  void ApplyPageStatus(const PropertiesResolvingStatus& status) {\n    page_status_ = status.page_status_;\n  }\n};\n\nclass DynamicCSSStylesManager {\n private:\n  enum StyleDynamicType : uint32_t {\n    kEmType = 0,\n    kRemType = 1,\n    kScreenMetricsType = 2,\n    kDirectionStyleType = 3,\n    kFontScaleType = 4,\n    kViewportType = 5,\n    kDynamicTypeCount = 6\n  };\n\n public:\n  DynamicCSSStylesManager(){};\n\n  enum StyleUpdateFlag : uint32_t {\n    kUpdateEm = 1 << kEmType,\n    kUpdateRem = 1 << kRemType,\n    kUpdateScreenMetrics = 1 << kScreenMetricsType,\n    kUpdateDirectionStyle = 1 << kDirectionStyleType,\n    kUpdateFontScale = 1 << kFontScaleType,\n    kUpdateViewport = 1 << kViewportType,\n  };\n\n  static constexpr uint32_t kAllStyleUpdate =\n      kUpdateEm | kUpdateRem | kUpdateScreenMetrics | kUpdateDirectionStyle |\n      kUpdateFontScale | kUpdateViewport;\n\n  static constexpr uint32_t kNoUpdate = 0;\n\n  using StyleUpdateFlags = uint32_t;\n\n  static const std::unordered_set<CSSPropertyID>& GetInheritableProps();\n\n  static CSSPropertyID ResolveDirectionAwarePropertyID(\n      CSSPropertyID id, starlight::DirectionType direction);\n\n  static void UpdateDirectionAwareDefaultStyles(\n      Element* element, starlight::DirectionType direction,\n      const CSSValue& text_align_value);\n\n  static StyleUpdateFlags GetValueFlags(CSSPropertyID id, const CSSValue& value,\n                                        bool unify_vw_vh_behavior);\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_DYNAMIC_CSS_STYLES_MANAGER_H_\n"
  },
  {
    "path": "core/renderer/css/dynamic_direction_styles_manager.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/dynamic_direction_styles_manager.h\"\n\n#include <unordered_map>\n\n#include \"base/include/no_destructor.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/starlight/style/default_layout_style.h\"\n\nnamespace lynx {\nnamespace tasm {\nusing starlight::DirectionType;\nusing starlight::TextAlignType;\n\nnamespace {\n// For logic style\nconst std::unordered_map<CSSPropertyID, CSSPropertyID>& GetLogicStyleMapping() {\n  static base::NoDestructor<std::unordered_map<CSSPropertyID, CSSPropertyID>>\n      kDefaultDirectionAwareProps{{\n          {kPropertyIDMarginInlineStart, kPropertyIDMarginLeft},\n          {kPropertyIDMarginInlineEnd, kPropertyIDMarginRight},\n          {kPropertyIDPaddingInlineStart, kPropertyIDPaddingLeft},\n          {kPropertyIDPaddingInlineEnd, kPropertyIDPaddingRight},\n          {kPropertyIDBorderInlineStartWidth, kPropertyIDBorderLeftWidth},\n          {kPropertyIDBorderInlineEndWidth, kPropertyIDBorderRightWidth},\n          {kPropertyIDBorderInlineStartStyle, kPropertyIDBorderLeftStyle},\n          {kPropertyIDBorderInlineEndStyle, kPropertyIDBorderRightStyle},\n          {kPropertyIDBorderInlineStartColor, kPropertyIDBorderLeftColor},\n          {kPropertyIDBorderInlineEndColor, kPropertyIDBorderRightColor},\n          {kPropertyIDBorderStartStartRadius, kPropertyIDBorderTopLeftRadius},\n          {kPropertyIDBorderStartEndRadius, kPropertyIDBorderTopRightRadius},\n          {kPropertyIDBorderEndStartRadius, kPropertyIDBorderBottomLeftRadius},\n          {kPropertyIDBorderEndEndRadius, kPropertyIDBorderBottomRightRadius},\n          {kPropertyIDRelativeAlignInlineStart, kPropertyIDRelativeAlignLeft},\n          {kPropertyIDRelativeAlignInlineEnd, kPropertyIDRelativeAlignRight},\n          {kPropertyIDRelativeInlineStartOf, kPropertyIDRelativeLeftOf},\n          {kPropertyIDRelativeInlineEndOf, kPropertyIDRelativeRightOf},\n          {kPropertyIDInsetInlineStart, kPropertyIDLeft},\n          {kPropertyIDInsetInlineEnd, kPropertyIDRight},\n      }};\n  return *kDefaultDirectionAwareProps;\n}\n\n// For lynx-rtl. first ==rtl==> second\nconst std::unordered_map<CSSPropertyID, CSSPropertyID>&\nGetRTLDirectionMapping() {\n  static base::NoDestructor<std::unordered_map<CSSPropertyID, CSSPropertyID>>\n      kDefaultDirectionAwareProps{{\n          {kPropertyIDLeft, kPropertyIDRight},\n          {kPropertyIDRight, kPropertyIDLeft},\n          {kPropertyIDMarginLeft, kPropertyIDMarginRight},\n          {kPropertyIDMarginRight, kPropertyIDMarginLeft},\n          {kPropertyIDPaddingLeft, kPropertyIDPaddingRight},\n          {kPropertyIDPaddingRight, kPropertyIDPaddingLeft},\n          {kPropertyIDBorderLeftWidth, kPropertyIDBorderRightWidth},\n          {kPropertyIDBorderRightWidth, kPropertyIDBorderLeftWidth},\n          {kPropertyIDBorderLeftStyle, kPropertyIDBorderRightStyle},\n          {kPropertyIDBorderRightStyle, kPropertyIDBorderLeftStyle},\n          {kPropertyIDBorderLeftColor, kPropertyIDBorderRightColor},\n          {kPropertyIDBorderRightColor, kPropertyIDBorderLeftColor},\n          {kPropertyIDBorderTopLeftRadius, kPropertyIDBorderTopRightRadius},\n          {kPropertyIDBorderTopRightRadius, kPropertyIDBorderTopLeftRadius},\n          {kPropertyIDBorderBottomLeftRadius,\n           kPropertyIDBorderBottomRightRadius},\n          {kPropertyIDBorderBottomRightRadius,\n           kPropertyIDBorderBottomLeftRadius},\n          {kPropertyIDRelativeAlignLeft, kPropertyIDRelativeAlignRight},\n          {kPropertyIDRelativeAlignRight, kPropertyIDRelativeAlignLeft},\n          {kPropertyIDRelativeLeftOf, kPropertyIDRelativeRightOf},\n          {kPropertyIDRelativeRightOf, kPropertyIDRelativeLeftOf},\n      }};\n  return *kDefaultDirectionAwareProps;\n}\n}  // namespace\n\n// static\nbool IsLogicalDirectionStyle(CSSPropertyID css_id) {\n  return css_id == kPropertyIDTextAlign || GetLogicStyleMapping().count(css_id);\n}\n\n// static\nbool IsDirectionAwareStyle(CSSPropertyID css_id) {\n  return IsLogicalDirectionStyle(css_id) ||\n         GetRTLDirectionMapping().count(css_id);\n}\n\n// static\nstd::pair<CSSPropertyID, IsLogic> ResolveLogicStyleID(CSSPropertyID css_id) {\n  CSSPropertyID trans_id = css_id;\n  bool is_logic_style = false;\n  // start --> left/right\n  const auto& trans_map = GetLogicStyleMapping();\n  const auto& trans_entry = trans_map.find(css_id);\n  if (trans_entry != trans_map.end()) {\n    is_logic_style = true;\n    trans_id = trans_entry->second;\n  }\n  return {trans_id, is_logic_style};\n}\n\n// static\nCSSPropertyID ResolveDirectionRelatedStyleID(CSSPropertyID trans_id,\n                                             DirectionType direction,\n                                             bool is_logic_style) {\n  // is direction aware props\n  const auto& rlt_map = GetRTLDirectionMapping();\n  const auto& rlt_entry = rlt_map.find(trans_id);\n  if (rlt_entry != rlt_map.end()) {\n    if ((IsRTL(direction) && is_logic_style) || IsLynxRTL(direction)) {\n      return rlt_entry->second;\n    }\n  }\n  return trans_id;\n}\n\nCSSPropertyID ResolveDirectionAwareProperty(CSSPropertyID css_id,\n                                            DirectionType direction) {\n  auto ret_pair = ResolveLogicStyleID(css_id);\n  CSSPropertyID trans_id = ret_pair.first;\n  bool is_logic_style = ret_pair.second;\n\n  if (!IsAnyRTL(direction)) {\n    return trans_id;\n  }\n\n  // is direction aware props\n  trans_id =\n      ResolveDirectionRelatedStyleID(trans_id, direction, is_logic_style);\n\n  return trans_id;\n}\n\nCSSStyleValue ResolveTextAlign(CSSPropertyID css_id,\n                               const tasm::CSSValue& value,\n                               DirectionType direction) {\n  TextAlignType align_type = value.GetEnum<TextAlignType>();\n  TextAlignType result_type;\n  switch (align_type) {\n    case TextAlignType::kStart:\n      result_type = direction == DirectionType::kNormal\n                        ? TextAlignType::kStart\n                        : (IsAnyRTL(direction) ? TextAlignType::kRight\n                                               : TextAlignType::kLeft);\n      break;\n    case TextAlignType::kEnd:\n      result_type =\n          IsAnyRTL(direction) ? TextAlignType::kLeft : TextAlignType::kRight;\n      break;\n    case TextAlignType::kLeft:\n      result_type =\n          IsLynxRTL(direction) ? TextAlignType::kRight : TextAlignType::kLeft;\n      break;\n    case TextAlignType::kRight:\n      result_type =\n          IsLynxRTL(direction) ? TextAlignType::kLeft : TextAlignType::kRight;\n      break;\n    default:\n      result_type = align_type;\n      break;\n  }\n  return CSSStyleValue(css_id, CSSValue(result_type));\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/dynamic_direction_styles_manager.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_DYNAMIC_DIRECTION_STYLES_MANAGER_H_\n#define CORE_RENDERER_CSS_DYNAMIC_DIRECTION_STYLES_MANAGER_H_\n\n#include <map>\n#include <utility>\n\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace tasm {\nusing CSSStyleValue = std::pair<CSSPropertyID, CSSValue>;\nusing IsLogic = bool;\nbool IsDirectionAwareStyle(CSSPropertyID css_id);\n\nbool IsLogicalDirectionStyle(CSSPropertyID css_id);\n\ninline bool IsRTL(starlight::DirectionType direction) {\n  return direction == starlight::DirectionType::kRtl;\n}\n\ninline bool IsLynxRTL(starlight::DirectionType direction) {\n  return direction == starlight::DirectionType::kLynxRtl;\n}\n\ninline bool IsAnyRTL(starlight::DirectionType direction) {\n  return IsRTL(direction) || IsLynxRTL(direction);\n}\n\nCSSPropertyID ResolveDirectionAwareProperty(CSSPropertyID css_id,\n                                            starlight::DirectionType direction);\nCSSStyleValue ResolveTextAlign(CSSPropertyID css_id,\n                               const tasm::CSSValue& value,\n                               starlight::DirectionType direction);\n\nstd::pair<CSSPropertyID, IsLogic> ResolveLogicStyleID(CSSPropertyID css_id);\nCSSPropertyID ResolveDirectionRelatedStyleID(CSSPropertyID trans_id,\n                                             starlight::DirectionType direction,\n                                             bool is_logic_style);\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_DYNAMIC_DIRECTION_STYLES_MANAGER_H_\n"
  },
  {
    "path": "core/renderer/css/layout_node_unittest.cc",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/ui_wrapper/layout/layout_node.h\"\n\n#include <memory>\n#include <unordered_map>\n\n#include \"core/renderer/css/layout_property.h\"\n#include \"core/renderer/lynx_env_config.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass LayoutNodeTests : public ::testing::Test {\n protected:\n  LayoutNodeTests() = default;\n  ~LayoutNodeTests() override = default;\n\n  int id_ = 0;\n  std::unordered_map<int, LayoutNode> nodes_;\n\n  std::unique_ptr<starlight::ComputedCSSStyle> layout_computed_css_;\n  LayoutNode* root_;\n  starlight::LayoutConfigs layout_configs_;\n  LynxEnvConfig layout_env_config_{0, 0, 1.f, 1.f};\n\n  LayoutNode* CommonNode() {\n    auto id = id_++;\n    auto result =\n        &nodes_\n             .emplace(\n                 std::piecewise_construct, std::forward_as_tuple(id),\n                 std::forward_as_tuple(id, layout_configs_, layout_env_config_,\n                                       *layout_computed_css_))\n             .first->second;\n    result->set_type(LayoutNodeType::COMMON);\n    return result;\n  }\n\n  LayoutNode* VirtualNode() {\n    auto id = id_++;\n    auto result =\n        &nodes_\n             .emplace(\n                 std::piecewise_construct, std::forward_as_tuple(id),\n                 std::forward_as_tuple(id, layout_configs_, layout_env_config_,\n                                       *layout_computed_css_))\n             .first->second;\n    result->set_type(LayoutNodeType::VIRTUAL);\n    return result;\n  }\n\n  void SetUp() override {\n    layout_computed_css_ =\n        std::make_unique<starlight::ComputedCSSStyle>(1.f, 1.f);\n    root_ = CommonNode();\n  }\n\n  void AppendSomeCommonNodes(int number) {\n    for (int i = 0; i < number; ++i) {\n      root_->InsertNode(CommonNode());\n    }\n  }\n\n  void AppendSomeVirtualNodes(int number) {\n    for (int i = 0; i < number; ++i) {\n      root_->InsertNode(VirtualNode());\n    }\n  }\n};\n\nTEST_F(LayoutNodeTests, InsertCommon) {\n  auto child = CommonNode();\n  root_->InsertNode(child);\n  EXPECT_EQ(1, root_->slnode()->GetChildCount());\n  EXPECT_EQ(child->slnode(), root_->slnode()->FirstChild());\n}\n\nTEST_F(LayoutNodeTests, InsertVirtual) {\n  root_->InsertNode(VirtualNode());\n  EXPECT_EQ(0, root_->slnode()->GetChildCount());\n}\n\nTEST_F(LayoutNodeTests, InsertCommonWithinCommon) {\n  auto child0 = CommonNode();\n  auto child1 = CommonNode();\n  auto child2 = CommonNode();\n  AppendSomeCommonNodes(10);\n  root_->InsertNode(child0);\n  root_->InsertNode(child2);\n  AppendSomeCommonNodes(10);\n\n  root_->InsertNode(child1, 11);\n  EXPECT_EQ(child1->slnode(), child2->slnode()->Previous());\n  EXPECT_EQ(child1->slnode(), child0->slnode()->Next());\n}\n\nTEST_F(LayoutNodeTests, InsertCommonBeforeVirtual) {\n  auto child0 = CommonNode();\n  auto child1 = CommonNode();\n  auto child2 = CommonNode();\n  AppendSomeCommonNodes(10);\n  root_->InsertNode(child0);\n  AppendSomeVirtualNodes(10);\n  root_->InsertNode(child2);\n  AppendSomeCommonNodes(10);\n\n  root_->InsertNode(child1, 11);\n  EXPECT_EQ(child1->slnode(), child2->slnode()->Previous());\n  EXPECT_EQ(child1->slnode(), child0->slnode()->Next());\n}\n\nTEST_F(LayoutNodeTests, RemoveCommonBeforeVirtual) {\n  auto child0 = CommonNode();\n  auto child1 = CommonNode();\n  auto child2 = CommonNode();\n  AppendSomeCommonNodes(10);\n  root_->InsertNode(child0);\n  root_->InsertNode(child1);\n  AppendSomeVirtualNodes(10);\n  root_->InsertNode(child2);\n  AppendSomeCommonNodes(10);\n\n  root_->RemoveNodeAtIndex(11);\n  EXPECT_EQ(child0->slnode(), child2->slnode()->Previous());\n}\n\nTEST_F(LayoutNodeTests, InsertCommonAfterVirtual) {\n  auto child0 = CommonNode();\n  auto child1 = CommonNode();\n  auto child2 = CommonNode();\n  AppendSomeCommonNodes(10);\n  root_->InsertNode(child0);\n  AppendSomeVirtualNodes(10);\n  root_->InsertNode(child2);\n  AppendSomeCommonNodes(10);\n\n  root_->InsertNode(child1, 21);\n  EXPECT_EQ(child1->slnode(), child2->slnode()->Previous());\n  EXPECT_EQ(child1->slnode(), child0->slnode()->Next());\n}\n\nTEST_F(LayoutNodeTests, RemoveCommonAfterVirtual) {\n  auto child0 = CommonNode();\n  auto child1 = CommonNode();\n  auto child2 = CommonNode();\n  AppendSomeCommonNodes(10);\n  root_->InsertNode(child0);\n  AppendSomeVirtualNodes(10);\n  root_->InsertNode(child1);\n  root_->InsertNode(child2);\n  AppendSomeCommonNodes(10);\n\n  root_->RemoveNodeAtIndex(21);\n  EXPECT_EQ(child0->slnode(), child2->slnode()->Previous());\n}\n\nTEST_F(LayoutNodeTests, InsertCommonWithinVirtual) {\n  auto child0 = CommonNode();\n  auto child1 = CommonNode();\n  auto child2 = CommonNode();\n  AppendSomeCommonNodes(10);\n  root_->InsertNode(child0);\n  AppendSomeVirtualNodes(10);\n  root_->InsertNode(child2);\n  AppendSomeCommonNodes(10);\n\n  root_->InsertNode(child1, 15);\n  EXPECT_EQ(child1->slnode(), child2->slnode()->Previous());\n  EXPECT_EQ(child1->slnode(), child0->slnode()->Next());\n}\n\nTEST_F(LayoutNodeTests, RemoveCommonWithinVirtual) {\n  auto child0 = CommonNode();\n  auto child1 = CommonNode();\n  auto child2 = CommonNode();\n  AppendSomeCommonNodes(10);\n  root_->InsertNode(child0);\n  AppendSomeVirtualNodes(10);\n  root_->InsertNode(child1);\n  AppendSomeVirtualNodes(10);\n  root_->InsertNode(child2);\n  AppendSomeCommonNodes(10);\n\n  root_->RemoveNodeAtIndex(21);\n  EXPECT_EQ(child0->slnode(), child2->slnode()->Previous());\n}\n\n#define TEST_DECLARE_WANTED_PROPERTY(name, type) \\\n  EXPECT_EQ(LayoutProperty::ConsumptionTest(kPropertyID##name), type);\n\nstruct ThreadArgs {\n  std::atomic<bool>* flag;\n  const int* wanted_prop;\n};\n\nTEST_F(LayoutNodeTests, ConcurrentAccess) {\n  constexpr int kThreadCount = 16;\n  constexpr int kLoopTimes = 10000;\n  std::vector<std::thread> threads;\n  std::atomic<bool> start_flag{false};\n\n  static const auto& kWantedProperty = []() -> const int(&)[kPropertyEnd] {\n    static int arr[kPropertyEnd];\n    std::fill(std::begin(arr), std::end(arr), ConsumptionStatus::SKIP);\n\n#define DECLARE_WANTED_PROPERTY(name, type) arr[kPropertyID##name] = type;\n    FOREACH_LAYOUT_PROPERTY(DECLARE_WANTED_PROPERTY)\n#undef DECLARE_WANTED_PROPERTY\n\n    return arr;\n  }();\n\n  auto test_task = [](ThreadArgs args) {\n    while (!(*args.flag))\n      ;\n    for (int i = 0; i < kLoopTimes; ++i) {\n      FOREACH_LAYOUT_PROPERTY(TEST_DECLARE_WANTED_PROPERTY);\n    }\n  };\n\n  for (int i = 0; i < kThreadCount; ++i) {\n    threads.emplace_back(test_task, ThreadArgs{&start_flag, kWantedProperty});\n  }\n\n  start_flag = true;\n  for (auto& t : threads) t.join();\n}\n\nTEST_F(LayoutNodeTests, BasicFunction) {\n  FOREACH_LAYOUT_PROPERTY(TEST_DECLARE_WANTED_PROPERTY);\n}\n\n#undef TEST_DECLARE_WANTED_PROPERTY\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/layout_property.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/layout_property.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nConsumptionStatus LayoutProperty::ConsumptionTest(CSSPropertyID id) {\n  static const auto& kWantedProperty = []() -> const int(&)[kPropertyEnd] {\n    static int arr[kPropertyEnd];\n    std::fill(std::begin(arr), std::end(arr), ConsumptionStatus::SKIP);\n\n#define DECLARE_WANTED_PROPERTY(name, type) arr[kPropertyID##name] = type;\n    FOREACH_LAYOUT_PROPERTY(DECLARE_WANTED_PROPERTY)\n#undef DECLARE_WANTED_PROPERTY\n\n    return arr;\n  }();\n\n  return static_cast<ConsumptionStatus>(kWantedProperty[id]);\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/measure_context.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/measure_context.h\"\n\n#include \"core/renderer/css/computed_css_style.h\"\n#include \"core/renderer/lynx_env_config.h\"\n\nnamespace lynx {\nnamespace tasm {\nCssMeasureContext::CssMeasureContext(\n    float screen_width, float layouts_unit_per_px,\n    float physical_pixels_per_layout_unit, float root_font_size,\n    float cur_font_size, const starlight::LayoutUnit& viewport_width,\n    const starlight::LayoutUnit& viewport_height)\n    : screen_width_(screen_width),\n      layouts_unit_per_px_(layouts_unit_per_px),\n      physical_pixels_per_layout_unit_(physical_pixels_per_layout_unit),\n      root_node_font_size_(root_font_size),\n      cur_node_font_size_(cur_font_size),\n      viewport_width_(viewport_width),\n      viewport_height_(viewport_height) {}\n\nCssMeasureContext::CssMeasureContext(const tasm::LynxEnvConfig& config,\n                                     float root_font_size, float cur_font_size)\n    : screen_width_(config.ScreenWidth()),\n      layouts_unit_per_px_(config.LayoutsUnitPerPx()),\n      physical_pixels_per_layout_unit_(config.PhysicalPixelsPerLayoutUnit()),\n      root_node_font_size_(root_font_size),\n      cur_node_font_size_(cur_font_size),\n      font_scale_(config.FontScale()),\n      viewport_width_(config.ViewportWidth()),\n      viewport_height_(config.ViewportHeight()),\n      font_scale_sp_only_(config.FontScaleSpOnly()) {}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/measure_context.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_MEASURE_CONTEXT_H_\n#define CORE_RENDERER_CSS_MEASURE_CONTEXT_H_\n\n#include \"core/base/lynx_export.h\"\n#include \"core/renderer/starlight/layout/layout_global.h\"\n#include \"core/renderer/starlight/types/layout_constraints.h\"\n#include \"core/renderer/tasm/config.h\"\n\n// TODO(liting):move to .cc\n#include \"core/renderer/starlight/types/nlength.h\"\n\nnamespace lynx {\nnamespace tasm {\nclass LynxEnvConfig;\nstruct PropertiesResolvingStatus;\n\nclass CssMeasureContext {\n public:\n  CssMeasureContext(const tasm::LynxEnvConfig& config, float root_font_size,\n                    float cur_font_size);\n  LYNX_EXPORT CssMeasureContext(float screen_width, float layouts_unit_per_px,\n                                float physical_pixels_per_layout_unit,\n                                float root_font_size, float cur_font_size,\n                                const starlight::LayoutUnit& viewport_width_,\n                                const starlight::LayoutUnit& viewport_height_);\n  ~CssMeasureContext() {}\n  float screen_width_;\n  float layouts_unit_per_px_;\n  float physical_pixels_per_layout_unit_;\n  float root_node_font_size_;\n  float cur_node_font_size_;\n  float font_scale_ = tasm::Config::DefaultFontScale();\n  starlight::LayoutUnit viewport_width_;\n  starlight::LayoutUnit viewport_height_;\n  bool font_scale_sp_only_ = false;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_MEASURE_CONTEXT_H_\n"
  },
  {
    "path": "core/renderer/css/ng/LICENSE",
    "content": "// Copyright 2015 The Chromium Authors. All rights reserved.\n//\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are\n// met:\n//\n//    * Redistributions of source code must retain the above copyright\n// notice, this list of conditions and the following disclaimer.\n//    * Redistributions in binary form must reproduce the above\n// copyright notice, this list of conditions and the following disclaimer\n// in the documentation and/or other materials provided with the\n// distribution.\n//    * Neither the name of Google Inc. nor the names of its\n// contributors may be used to endorse or promote products derived from\n// this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n// \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "core/renderer/css/ng/css_ng_utils.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n\n#include \"base/include/no_destructor.h\"\n\nnamespace lynx {\nnamespace css {\n\nconst std::string& CSSGlobalEmptyString() {\n  static const base::NoDestructor<std::string> str;\n  return *str;\n}\n\nconst std::u16string& CSSGlobalEmptyU16String() {\n  static const base::NoDestructor<std::u16string> str;\n  return *str;\n}\n\nconst std::string& CSSGlobalStarString() {\n  static const base::NoDestructor<std::string> str(\"*\");\n  return *str;\n}\n\nconst std::u16string& CSSGlobalStarU16String() {\n  static const base::NoDestructor<std::u16string> str(u\"*\");\n  return *str;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/css_ng_utils.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_CSS_NG_UTILS_H_\n#define CORE_RENDERER_CSS_NG_CSS_NG_UTILS_H_\n\n#include <algorithm>\n#include <cassert>\n#include <cstdint>\n#include <string>\n\n#include \"base/include/compiler_specific.h\"\n#include \"base/include/log/logging.h\"\n#include \"base/include/string/string_utils.h\"\n\nnamespace lynx {\nnamespace css {\nusing UChar = base::UChar;\nusing LChar = base::LChar;\nusing UChar32 = base::UChar32;\n\n#define U16_LEAD(supplementary) (UChar)(((supplementary) >> 10) + 0xd7c0)\n#define U16_TRAIL(supplementary) (UChar)(((supplementary) & 0x3ff) | 0xdc00)\n\nnamespace ustring_helper {\n\ninline void append(std::u16string& str, const UChar32 c) {\n  if (((uint32_t)(c) <= 0xffff)) {  // U_IS_BMP\n    str.push_back(static_cast<UChar>(c));\n    return;\n  }\n  str.push_back(U16_LEAD(c));\n  str.push_back(U16_TRAIL(c));\n}\n\ninline std::u16string from_string(const std::string& str) {\n  return base::U8StringToU16(str);\n}\n\ninline std::u16string from_string(const char* str, size_t len) {\n  return base::U8StringToU16(std::string(str, len));\n}\n\ninline std::string to_string(const std::u16string& str) {\n  return base::U16StringToU8(str);\n}\n\n}  // namespace ustring_helper\n\nconstexpr LChar kEndOfFileMarker = 0;\n\n// https://html.spec.whatwg.org/C/#parse-error-unexpected-null-character\nconstexpr UChar kReplacementCharacter = 0xFFFD;\nconst std::string& CSSGlobalEmptyString();\nconst std::u16string& CSSGlobalEmptyU16String();\nconst std::string& CSSGlobalStarString();\nconst std::u16string& CSSGlobalStarU16String();\n\ntemplate <class T>\ninline T ToLower(const T& input) {\n  T result;\n  result.resize(input.size());\n  std::transform(input.begin(), input.end(), result.begin(), ::tolower);\n  return result;\n}\n\ninline std::string ToLowerASCII(const std::u16string& input) {\n  std::u16string result;\n  result.resize(input.size());\n  std::transform(input.begin(), input.end(), result.begin(), ::tolower);\n  return lynx::css::ustring_helper::to_string(result);\n}\n\ninline bool EqualIgnoringASCIICase(const std::u16string& lfs,\n                                   const std::u16string& rhs) {\n  return ToLower(lfs) == rhs;\n}\n\ninline bool EqualIgnoringASCIICase(const std::string& lfs,\n                                   const std::string& rhs) {\n  return ToLower(lfs) == rhs;\n}\n\n}  // namespace css\n}  // namespace lynx\n#endif  // CORE_RENDERER_CSS_NG_CSS_NG_UTILS_H_\n"
  },
  {
    "path": "core/renderer/css/ng/invalidation/invalidation_set.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n/*\n * Copyright (C) 2014 Google Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *     * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *     * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *     * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"core/renderer/css/ng/invalidation/invalidation_set.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"core/renderer/dom/attribute_holder.h\"\n\nnamespace lynx {\nnamespace css {\n\nInvalidationSet::InvalidationSet(InvalidationType type)\n    : type_(static_cast<unsigned>(type)),\n      invalidates_self_(false),\n      is_alive_(true) {}\n\nbool InvalidationSet::InvalidatesElement(\n    const tasm::AttributeHolder& element) const {\n  if (WholeSubtreeInvalid()) return true;\n\n  if (HasTagNames() && HasTagName(element.tag().str())) {\n    return true;\n  }\n\n  if (element.HasID() && HasIds() && HasId(element.idSelector().str())) {\n    return true;\n  }\n\n  if (element.HasClass() && HasClasses()) {\n    if (FindAnyClass(element)) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nvoid InvalidationSet::Combine(const InvalidationSet& other) {\n  DCHECK(is_alive_);\n  DCHECK(other.is_alive_);\n  DCHECK_EQ(GetType(), other.GetType());\n\n  if (IsSelfInvalidationSet()) {\n    // We should never modify the SelfInvalidationSet singleton. When\n    // aggregating the contents from another invalidation set into an\n    // invalidation set which only invalidates self, we instantiate a new\n    // DescendantInvalidation set before calling Combine(). We still may end up\n    // here if we try to combine two references to the singleton set.\n    DCHECK(other.IsSelfInvalidationSet());\n    return;\n  }\n\n  DCHECK(&other != this);\n\n  if (other.InvalidatesSelf()) {\n    SetInvalidatesSelf();\n    if (other.IsSelfInvalidationSet()) {\n      return;\n    }\n  }\n\n  // No longer bother combining data structures, since the whole subtree is\n  // deemed invalid.\n  if (WholeSubtreeInvalid()) {\n    return;\n  }\n\n  if (other.WholeSubtreeInvalid()) {\n    SetWholeSubtreeInvalid();\n    return;\n  }\n\n  for (const auto& class_name : other.Classes()) {\n    AddClass(class_name);\n  }\n\n  for (const auto& id : other.Ids()) {\n    AddId(id);\n  }\n\n  for (const auto& tag_name : other.TagNames()) {\n    AddTagName(tag_name);\n  }\n}\n\nbool InvalidationSet::HasEmptyBackings() const {\n  return classes_.IsEmpty(backing_flags_) && ids_.IsEmpty(backing_flags_) &&\n         tag_names_.IsEmpty(backing_flags_);\n}\n\nconst std::string* InvalidationSet::FindAnyClass(\n    const tasm::AttributeHolder& element) const {\n  const auto& class_names = element.classes();\n  size_t size = class_names.size();\n  if (std::string* string_impl = classes_.GetStringImpl(backing_flags_)) {\n    for (size_t i = 0; i < size; ++i) {\n      if (*string_impl == class_names[i].str()) {\n        return string_impl;\n      }\n    }\n  }\n  if (const std::unordered_set<std::string>* set =\n          classes_.GetHashSet(backing_flags_)) {\n    for (size_t i = 0; i < size; ++i) {\n      auto it = set->find(class_names[i].str());\n      if (it != set->end()) {\n        return &*it;\n      }\n    }\n  }\n  return nullptr;\n}\n\nvoid InvalidationSet::AddClass(const std::string& class_name) {\n  if (WholeSubtreeInvalid()) return;\n  DCHECK(!class_name.empty());\n  classes_.Add(backing_flags_, class_name);\n}\n\nvoid InvalidationSet::AddId(const std::string& id) {\n  if (WholeSubtreeInvalid()) return;\n  DCHECK(!id.empty());\n  ids_.Add(backing_flags_, id);\n}\n\nvoid InvalidationSet::AddTagName(const std::string& tag_name) {\n  if (WholeSubtreeInvalid()) return;\n  DCHECK(!tag_name.empty());\n  tag_names_.Add(backing_flags_, tag_name);\n}\n\nvoid InvalidationSet::SetWholeSubtreeInvalid() {\n  if (whole_subtree_invalid_) {\n    return;\n  }\n  whole_subtree_invalid_ = true;\n  ClearAllBackings();\n}\n\nnamespace {\nDescendantInvalidationSet* CreateSelfInvalidationSet() {\n  auto* new_set = new DescendantInvalidationSet();\n  new_set->SetInvalidatesSelf();\n  return new_set;\n}\n}  // namespace\n\nInvalidationSet* InvalidationSet::SelfInvalidationSet() {\n  static InvalidationSet* singleton_ = CreateSelfInvalidationSet();\n  return singleton_;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/invalidation/invalidation_set.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n/*\n * Copyright (C) 2014 Google Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *     * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *     * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *     * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef CORE_RENDERER_CSS_NG_INVALIDATION_INVALIDATION_SET_H_\n#define CORE_RENDERER_CSS_NG_INVALIDATION_INVALIDATION_SET_H_\n\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace tasm {\nclass AttributeHolder;\n}\nnamespace css {\n\nenum class InvalidationType {\n  kInvalidateDescendants,\n};\n\nclass InvalidationSet;\nclass DescendantInvalidationSet;\n\n// Tracks data to determine which descendants in a DOM subtree need to have\n// style recalculated.\n//\n// Some example invalidation sets:\n//\n// .z {}\n//   For class z we will have a DescendantInvalidationSet with invalidatesSelf\n//   (the element itself is invalidated).\n//\n// .y .z {}\n//   For class y we will have a DescendantInvalidationSet containing class z.\n//\n// .v * {}\n//   For class v we will have a DescendantInvalidationSet with\n//   wholeSubtreeInvalid.\n//\n// Avoid virtual functions to minimize space consumption.\nclass InvalidationSet {\n public:\n  struct Deleter {\n    void operator()(InvalidationSet* set);\n  };\n\n  InvalidationSet(const InvalidationSet&) = delete;\n  InvalidationSet& operator=(const InvalidationSet&) = delete;\n\n  ~InvalidationSet() {\n    DCHECK(is_alive_);\n    is_alive_ = false;\n    ClearAllBackings();\n  }\n\n  InvalidationType GetType() const {\n    return static_cast<InvalidationType>(type_);\n  }\n  bool IsDescendantInvalidationSet() const {\n    return GetType() == InvalidationType::kInvalidateDescendants;\n  }\n\n  bool InvalidatesElement(const tasm::AttributeHolder&) const;\n\n  void AddClass(const std::string& class_name);\n  void AddId(const std::string& id);\n  void AddTagName(const std::string& tag_name);\n\n  void SetWholeSubtreeInvalid();\n  bool WholeSubtreeInvalid() const { return whole_subtree_invalid_; }\n  void SetInvalidatesSelf() { invalidates_self_ = true; }\n  bool InvalidatesSelf() const { return invalidates_self_; }\n\n  bool IsEmpty() const { return HasEmptyBackings(); }\n\n  bool IsAlive() const { return is_alive_; }\n\n  void Combine(const InvalidationSet& other);\n\n  // Returns a singleton DescendantInvalidationSet which only has\n  // InvalidatesSelf() set and is otherwise empty. As this is a common\n  // invalidation set for features only found in rightmost compounds,\n  // sharing this singleton between such features saves a lot of memory on\n  // sites with a big number of style rules.\n  static InvalidationSet* SelfInvalidationSet();\n  bool IsSelfInvalidationSet() const { return this == SelfInvalidationSet(); }\n\n  enum class BackingType {\n    kClasses,\n    kIds,\n    kTagNames,\n    kAttributes\n    // These values are used as bit-indices, and must be smaller than 8.\n    // See Backing::GetMask.\n  };\n\n  template <BackingType>\n  union Backing;\n\n  // Each BackingType has a corresponding bit in an instance of this class. A\n  // set bit indicates that the Backing at that position is a\n  // std::unordered_set. An unset bit indicates a std::string (which may be\n  // nullptr).\n  class BackingFlags {\n   private:\n    uint8_t bits_ = 0;\n    template <BackingType>\n    friend union Backing;\n  };\n\n  // WARNING: Backings must be cleared manually in ~InvalidationSet, otherwise\n  //          a std::string or std::unordered_set will leak.\n  template <BackingType type>\n  union Backing {\n    using Flags = BackingFlags;\n    static_assert(static_cast<size_t>(type) < sizeof(BackingFlags::bits_) * 8,\n                  \"Enough bits in BackingFlags\");\n\n    // Adds a std::string to the associated Backing. If the Backing is\n    // currently empty, we simply AddRef the std::string of the incoming\n    // std::string. If the Backing already has one item, we first \"upgrade\"\n    // to a std::unordered_set, and add the std::string.\n    void Add(Flags&, const std::string&);\n    // Clears the associated Backing. If the Backing is a std::string, it is\n    // released. If the Backing is a std::unordered_set, it is deleted.\n    void Clear(Flags&);\n    bool Contains(const Flags&, const std::string&) const;\n    bool IsEmpty(const Flags&) const;\n    size_t Size(const Flags&) const;\n    bool IsHashSet(const Flags& flags) const { return flags.bits_ & GetMask(); }\n\n    std::string* GetStringImpl(const Flags& flags) const {\n      return IsHashSet(flags) ? nullptr : string_impl_;\n    }\n    const std::unordered_set<std::string>* GetHashSet(\n        const Flags& flags) const {\n      return IsHashSet(flags) ? hash_set_ : nullptr;\n    }\n\n    // A simple forward iterator, which can either \"iterate\" over a single\n    // std::string, or act as a wrapper for\n    // std::unordered_set<std::string>::iterator.\n    class Iterator {\n     public:\n      enum class Type { kString, kHashSet };\n\n      explicit Iterator(std::string* string_impl)\n          : type_(Type::kString),\n            string_(string_impl == nullptr ? \"\" : *string_impl) {}\n      explicit Iterator(std::unordered_set<std::string>::iterator iterator)\n          : type_(Type::kHashSet), hash_set_iterator_(iterator) {}\n\n      bool operator==(const Iterator& other) const {\n        if (type_ != other.type_) return false;\n        if (type_ == Type::kString) return string_ == other.string_;\n        return hash_set_iterator_ == other.hash_set_iterator_;\n      }\n      bool operator!=(const Iterator& other) const { return !(*this == other); }\n      void operator++() {\n        if (type_ == Type::kString)\n          string_ = \"\";\n        else\n          ++hash_set_iterator_;\n      }\n\n      const std::string& operator*() const {\n        return type_ == Type::kString ? string_ : *hash_set_iterator_;\n      }\n\n     private:\n      Type type_;\n      // Used when type_ is kString.\n      std::string string_;\n      // Used when type_ is kHashSet.\n      std::unordered_set<std::string>::iterator hash_set_iterator_;\n    };\n\n    class Range {\n     public:\n      Range(Iterator begin, Iterator end) : begin_(begin), end_(end) {}\n      Iterator begin() const { return begin_; }\n      Iterator end() const { return end_; }\n\n     private:\n      Iterator begin_;\n      Iterator end_;\n    };\n\n    Range Items(const Flags& flags) const {\n      Iterator begin = IsHashSet(flags) ? Iterator(hash_set_->begin())\n                                        : Iterator(string_impl_);\n      Iterator end =\n          IsHashSet(flags) ? Iterator(hash_set_->end()) : Iterator(nullptr);\n      return Range(begin, end);\n    }\n\n   private:\n    uint8_t GetMask() const { return 1u << static_cast<size_t>(type); }\n    void SetIsString(Flags& flags) { flags.bits_ &= ~GetMask(); }\n    void SetIsHashSet(Flags& flags) { flags.bits_ |= GetMask(); }\n\n    std::string* string_impl_ = nullptr;\n    std::unordered_set<std::string>* hash_set_;\n  };\n\n protected:\n  explicit InvalidationSet(InvalidationType);\n\n private:\n  void ClearAllBackings() {\n    classes_.Clear(backing_flags_);\n    ids_.Clear(backing_flags_);\n    tag_names_.Clear(backing_flags_);\n  }\n  bool HasEmptyBackings() const;\n\n  bool HasClasses() const { return !classes_.IsEmpty(backing_flags_); }\n  bool HasIds() const { return !ids_.IsEmpty(backing_flags_); }\n  bool HasTagNames() const { return !tag_names_.IsEmpty(backing_flags_); }\n\n  bool HasId(const std::string& string) const {\n    return ids_.Contains(backing_flags_, string);\n  }\n\n  bool HasTagName(const std::string& string) const {\n    return tag_names_.Contains(backing_flags_, string);\n  }\n\n  Backing<BackingType::kClasses>::Range Classes() const {\n    return classes_.Items(backing_flags_);\n  }\n\n  Backing<BackingType::kIds>::Range Ids() const {\n    return ids_.Items(backing_flags_);\n  }\n\n  Backing<BackingType::kTagNames>::Range TagNames() const {\n    return tag_names_.Items(backing_flags_);\n  }\n\n  const std::string* FindAnyClass(const tasm::AttributeHolder&) const;\n\n  Backing<BackingType::kClasses> classes_;\n  Backing<BackingType::kIds> ids_;\n  Backing<BackingType::kTagNames> tag_names_;\n\n  bool whole_subtree_invalid_{false};\n  BackingFlags backing_flags_;\n\n  unsigned type_ : 2;\n\n  // If true, the element itself is invalid.\n  unsigned invalidates_self_ : 1;\n\n  // If true, the instance is alive and can be used.\n  unsigned is_alive_ : 1;\n  friend class RuleInvalidationSetTest;\n};\n\nusing InvalidationSetPtr =\n    std::unique_ptr<InvalidationSet, InvalidationSet::Deleter>;\nusing DescendantInvalidationSetPtr =\n    std::unique_ptr<DescendantInvalidationSet, InvalidationSet::Deleter>;\n\nclass DescendantInvalidationSet : public InvalidationSet {\n public:\n  static DescendantInvalidationSetPtr Create() {\n    return DescendantInvalidationSetPtr(new DescendantInvalidationSet());\n  }\n\n  DescendantInvalidationSet()\n      : InvalidationSet(InvalidationType::kInvalidateDescendants) {}\n};\n\nusing InvalidationSetVector = base::Vector<InvalidationSet*>;\n\nstruct InvalidationLists {\n  InvalidationSetVector descendants;\n};\n\ntemplate <typename InvalidationSet::BackingType type>\nvoid InvalidationSet::Backing<type>::Add(InvalidationSet::BackingFlags& flags,\n                                         const std::string& string) {\n  if (IsHashSet(flags)) {\n    hash_set_->insert(string);\n  } else if (string_impl_) {\n    if (*string_impl_ == string) {\n      return;\n    }\n    // The string_impl_ needs to be inserted into hash_set later\n    std::string string_impl(*string_impl_);\n    delete string_impl_;\n    string_impl_ = nullptr;\n    hash_set_ = new std::unordered_set<std::string>();\n    hash_set_->insert(string_impl);\n    hash_set_->insert(string);\n    SetIsHashSet(flags);\n  } else {\n    string_impl_ = new std::string(string);\n  }\n}\n\ntemplate <typename InvalidationSet::BackingType type>\nvoid InvalidationSet::Backing<type>::Clear(\n    InvalidationSet::BackingFlags& flags) {\n  if (IsHashSet(flags)) {\n    if (hash_set_) {\n      delete hash_set_;\n      string_impl_ = nullptr;\n    }\n  } else {\n    if (string_impl_) {\n      delete string_impl_;\n      string_impl_ = nullptr;\n    }\n  }\n  SetIsString(flags);\n}\n\ntemplate <typename InvalidationSet::BackingType type>\nbool InvalidationSet::Backing<type>::Contains(\n    const InvalidationSet::BackingFlags& flags,\n    const std::string& string) const {\n  if (IsHashSet(flags)) {\n    return hash_set_->find(string) != hash_set_->end();\n  }\n  if (string_impl_) {\n    return *string_impl_ == string;\n  }\n  return false;\n}\n\ntemplate <typename InvalidationSet::BackingType type>\nbool InvalidationSet::Backing<type>::IsEmpty(\n    const InvalidationSet::BackingFlags& flags) const {\n  return !IsHashSet(flags) && !string_impl_;\n}\n\ntemplate <typename InvalidationSet::BackingType type>\nsize_t InvalidationSet::Backing<type>::Size(\n    const InvalidationSet::BackingFlags& flags) const {\n  if (const std::unordered_set<std::string>* set = GetHashSet(flags)) {\n    return set->size();\n  }\n  if (GetStringImpl(flags)) {\n    return 1;\n  }\n  return 0;\n}\n\ninline void InvalidationSet::Deleter::operator()(InvalidationSet* set) {\n  // Workaround Linux build issue\n  if (set == InvalidationSet::SelfInvalidationSet()) {\n    // Static variables cannot be deleted\n    return;\n  }\n  if (set->IsDescendantInvalidationSet()) {\n    delete static_cast<DescendantInvalidationSet*>(set);\n  }\n}\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_INVALIDATION_INVALIDATION_SET_H_\n"
  },
  {
    "path": "core/renderer/css/ng/invalidation/invalidation_set_feature.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_RENDERER_CSS_NG_INVALIDATION_INVALIDATION_SET_FEATURE_H_\n#define CORE_RENDERER_CSS_NG_INVALIDATION_INVALIDATION_SET_FEATURE_H_\n\n#include <string>\n\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace css {\n\nclass InvalidationSetFeature {\n public:\n  bool HasFeature() const {\n    return !classes.empty() || !ids.empty() || !tag_names.empty();\n  }\n\n  void SetClass(const std::string& class_name) {\n    if (Size() == 1 && (!ids.empty() || !classes.empty())) return;\n    Clear();\n    classes.push_back(class_name);\n  }\n  void SetId(const std::string& id) {\n    if (Size() == 1 && !ids.empty()) return;\n    Clear();\n    ids.push_back(id);\n  }\n  void SetTag(const std::string& tag_name) {\n    if (Size() == 1) return;\n    Clear();\n    tag_names.push_back(tag_name);\n  }\n\n  bool FullInvalid() const { return full_invalid_; }\n  void SetFullInvalid(bool value) { full_invalid_ = value; }\n\n  void Clear() {\n    classes.clear();\n    ids.clear();\n    tag_names.clear();\n  }\n  size_t Size() const { return classes.size() + ids.size() + tag_names.size(); }\n\n  base::InlineVector<std::string, 4> classes;\n  base::InlineVector<std::string, 2> ids;\n  base::InlineVector<std::string, 2> tag_names;\n\n private:\n  bool full_invalid_{false};\n};\n\n}  // namespace css\n}  // namespace lynx\n#endif  // CORE_RENDERER_CSS_NG_INVALIDATION_INVALIDATION_SET_FEATURE_H_\n"
  },
  {
    "path": "core/renderer/css/ng/invalidation/invalidation_set_test.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2014 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"core/renderer/css/ng/invalidation/invalidation_set.h\"\n\n#include <vector>\n\n#include \"core/renderer/tasm/testing/mock_attribute_holder.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\n\nnamespace css {\nnamespace {\n\nusing BackingType = InvalidationSet::BackingType;\nusing BackingFlags = InvalidationSet::BackingFlags;\ntemplate <BackingType type>\nusing Backing = InvalidationSet::Backing<type>;\n\ntemplate <BackingType type>\nbool HasAny(const Backing<type>& backing, const BackingFlags& flags,\n            std::initializer_list<const char*> args) {\n  for (const char* str : args) {\n    if (backing.Contains(flags, str)) return true;\n  }\n  return false;\n}\n\ntemplate <BackingType type>\nbool HasAll(const Backing<type>& backing, const BackingFlags& flags,\n            std::initializer_list<const char*> args) {\n  for (const char* str : args) {\n    if (!backing.Contains(flags, str)) return false;\n  }\n  return true;\n}\n\nTEST(InvalidationSetTest, Backing_Create) {\n  BackingFlags flags;\n  Backing<BackingType::kClasses> backing;\n\n  ASSERT_FALSE(backing.IsHashSet(flags));\n}\n\nTEST(InvalidationSetTest, Backing_Add) {\n  BackingFlags flags;\n  Backing<BackingType::kClasses> backing;\n\n  ASSERT_FALSE(backing.IsHashSet(flags));\n  backing.Add(flags, std::string(\"test1\"));\n  ASSERT_FALSE(backing.IsHashSet(flags));\n  backing.Add(flags, std::string(\"test2\"));\n  ASSERT_TRUE(backing.IsHashSet(flags));\n  backing.Clear(flags);\n}\n\nTEST(InvalidationSetTest, Backing_AddSame) {\n  BackingFlags flags;\n  Backing<BackingType::kClasses> backing;\n\n  ASSERT_FALSE(backing.IsHashSet(flags));\n  backing.Add(flags, std::string(\"test1\"));\n  ASSERT_FALSE(backing.IsHashSet(flags));\n  backing.Add(flags, std::string(\"test1\"));\n  // No need to upgrade to HashSet if we're adding the item we already have.\n  ASSERT_FALSE(backing.IsHashSet(flags));\n  backing.Clear(flags);\n}\n\nTEST(InvalidationSetTest, Backing_Independence) {\n  BackingFlags flags;\n\n  Backing<BackingType::kClasses> classes;\n  Backing<BackingType::kIds> ids;\n  Backing<BackingType::kTagNames> tag_names;\n  Backing<BackingType::kAttributes> attributes;\n\n  classes.Add(flags, \"test1\");\n  ids.Add(flags, \"test2\");\n  tag_names.Add(flags, \"test3\");\n  attributes.Add(flags, \"test4\");\n\n  // Adding to set does not affect other backings:\n  ASSERT_TRUE(classes.Contains(flags, \"test1\"));\n  ASSERT_FALSE(HasAny(classes, flags, {\"test2\", \"test3\", \"test4\"}));\n\n  ASSERT_TRUE(ids.Contains(flags, \"test2\"));\n  ASSERT_FALSE(HasAny(ids, flags, {\"test1\", \"test3\", \"test4\"}));\n\n  ASSERT_TRUE(tag_names.Contains(flags, \"test3\"));\n  ASSERT_FALSE(HasAny(tag_names, flags, {\"test1\", \"test2\", \"test4\"}));\n\n  ASSERT_TRUE(attributes.Contains(flags, \"test4\"));\n  ASSERT_FALSE(HasAny(attributes, flags, {\"test1\", \"test2\", \"test3\"}));\n\n  // Adding additional items to one set does not affect others:\n  classes.Add(flags, \"test5\");\n  tag_names.Add(flags, \"test6\");\n\n  ASSERT_TRUE(HasAll(classes, flags, {\"test1\", \"test5\"}));\n  ASSERT_FALSE(HasAny(classes, flags, {\"test2\", \"test3\", \"test4\", \"test6\"}));\n\n  ASSERT_TRUE(ids.Contains(flags, \"test2\"));\n  ASSERT_FALSE(\n      HasAny(ids, flags, {\"test1\", \"test3\", \"test4\", \"test5\", \"test6\"}));\n\n  ASSERT_TRUE(HasAll(tag_names, flags, {\"test3\", \"test6\"}));\n  ASSERT_FALSE(HasAny(tag_names, flags, {\"test1\", \"test2\", \"test4\", \"test5\"}));\n\n  ASSERT_TRUE(attributes.Contains(flags, \"test4\"));\n  ASSERT_FALSE(HasAny(attributes, flags, {\"test1\", \"test2\", \"test3\"}));\n\n  // Clearing one set does not clear others:\n\n  classes.Clear(flags);\n  ids.Clear(flags);\n  attributes.Clear(flags);\n\n  auto all_test_strings = {\"test1\", \"test2\", \"test3\",\n                           \"test4\", \"test5\", \"test6\"};\n\n  ASSERT_FALSE(HasAny(classes, flags, all_test_strings));\n  ASSERT_FALSE(HasAny(ids, flags, all_test_strings));\n  ASSERT_FALSE(HasAny(attributes, flags, all_test_strings));\n\n  ASSERT_FALSE(classes.IsHashSet(flags));\n  ASSERT_FALSE(ids.IsHashSet(flags));\n  ASSERT_FALSE(attributes.IsHashSet(flags));\n\n  ASSERT_TRUE(tag_names.IsHashSet(flags));\n  ASSERT_TRUE(HasAll(tag_names, flags, {\"test3\", \"test6\"}));\n  ASSERT_FALSE(HasAny(tag_names, flags, {\"test1\", \"test2\", \"test4\", \"test5\"}));\n  tag_names.Clear(flags);\n}\n\nTEST(InvalidationSetTest, Backing_ClearContains) {\n  BackingFlags flags;\n  Backing<BackingType::kClasses> backing;\n\n  // Clearing an empty set:\n  ASSERT_FALSE(backing.Contains(flags, \"test1\"));\n  ASSERT_FALSE(backing.IsHashSet(flags));\n  backing.Clear(flags);\n  ASSERT_FALSE(backing.IsHashSet(flags));\n\n  // Add one element to the set, and clear it:\n  backing.Add(flags, \"test1\");\n  ASSERT_FALSE(backing.IsHashSet(flags));\n  ASSERT_TRUE(backing.Contains(flags, \"test1\"));\n  backing.Clear(flags);\n  ASSERT_FALSE(backing.Contains(flags, \"test1\"));\n  ASSERT_FALSE(backing.IsHashSet(flags));\n\n  // Add two elements to the set, and clear them:\n  backing.Add(flags, \"test1\");\n  ASSERT_FALSE(backing.IsHashSet(flags));\n  ASSERT_TRUE(backing.Contains(flags, \"test1\"));\n  ASSERT_FALSE(backing.Contains(flags, \"test2\"));\n  backing.Add(flags, \"test2\");\n  ASSERT_TRUE(backing.IsHashSet(flags));\n  ASSERT_TRUE(backing.Contains(flags, \"test1\"));\n  ASSERT_TRUE(backing.Contains(flags, \"test2\"));\n  backing.Clear(flags);\n  ASSERT_FALSE(backing.Contains(flags, \"test1\"));\n  ASSERT_FALSE(backing.Contains(flags, \"test2\"));\n  ASSERT_FALSE(backing.IsHashSet(flags));\n}\n\nTEST(InvalidationSetTest, Backing_BackingIsEmpty) {\n  BackingFlags flags;\n  Backing<BackingType::kClasses> backing;\n\n  ASSERT_TRUE(backing.IsEmpty(flags));\n  backing.Add(flags, \"test1\");\n  ASSERT_FALSE(backing.IsEmpty(flags));\n  backing.Add(flags, \"test2\");\n  backing.Clear(flags);\n  ASSERT_TRUE(backing.IsEmpty(flags));\n}\n\nTEST(InvalidationSetTest, Backing_IsEmpty) {\n  BackingFlags flags;\n  Backing<BackingType::kClasses> backing;\n\n  ASSERT_TRUE(backing.IsEmpty(flags));\n\n  backing.Add(flags, \"test1\");\n  ASSERT_FALSE(backing.IsEmpty(flags));\n\n  backing.Clear(flags);\n  ASSERT_TRUE(backing.IsEmpty(flags));\n}\n\nTEST(InvalidationSetTest, Backing_Iterator) {\n  // Iterate over empty set.\n  {\n    BackingFlags flags;\n    Backing<BackingType::kClasses> backing;\n\n    std::vector<std::string> strings;\n    for (const std::string& str : backing.Items(flags)) strings.push_back(str);\n    ASSERT_EQ(0u, strings.size());\n  }\n\n  // Iterate over set with one item.\n  {\n    BackingFlags flags;\n    Backing<BackingType::kClasses> backing;\n\n    backing.Add(flags, \"test1\");\n    std::vector<std::string> strings;\n    for (const std::string& str : backing.Items(flags)) strings.push_back(str);\n    ASSERT_EQ(1u, strings.size());\n    ASSERT_TRUE(std::find(strings.begin(), strings.end(), \"test1\") !=\n                strings.end());\n    backing.Clear(flags);\n  }\n\n  // Iterate over set with multiple items.\n  {\n    BackingFlags flags;\n    Backing<BackingType::kClasses> backing;\n\n    backing.Add(flags, \"test1\");\n    backing.Add(flags, \"test2\");\n    backing.Add(flags, \"test3\");\n    std::vector<std::string> strings;\n    for (const std::string& str : backing.Items(flags)) strings.push_back(str);\n    ASSERT_EQ(3u, strings.size());\n    ASSERT_TRUE(std::find(strings.begin(), strings.end(), \"test1\") !=\n                strings.end());\n    ASSERT_TRUE(std::find(strings.begin(), strings.end(), \"test2\") !=\n                strings.end());\n    ASSERT_TRUE(std::find(strings.begin(), strings.end(), \"test3\") !=\n                strings.end());\n    backing.Clear(flags);\n  }\n}\n\nTEST(InvalidationSetTest, Backing_GetStringImpl) {\n  BackingFlags flags;\n  Backing<BackingType::kClasses> backing;\n  EXPECT_FALSE(backing.GetStringImpl(flags));\n  backing.Add(flags, \"a\");\n  EXPECT_EQ(\"a\", *backing.GetStringImpl(flags));\n  backing.Add(flags, \"b\");\n  EXPECT_FALSE(backing.GetStringImpl(flags));\n  backing.Clear(flags);\n}\n\nTEST(InvalidationSetTest, Backing_GetHashSet) {\n  BackingFlags flags;\n  Backing<BackingType::kClasses> backing;\n  EXPECT_FALSE(backing.GetHashSet(flags));\n  backing.Add(flags, \"a\");\n  EXPECT_FALSE(backing.GetHashSet(flags));\n  backing.Add(flags, \"b\");\n  EXPECT_TRUE(backing.GetHashSet(flags));\n  backing.Clear(flags);\n}\n\nTEST(InvalidationSetTest, ClassInvalidatesElement) {\n  auto set = DescendantInvalidationSet::Create();\n\n  auto element = std::make_unique<tasm::MockAttributeHolder>(\"div\");\n  element->SetIdSelector(\"test\");\n  element->AddClass(\"a\");\n  element->AddClass(\"b\");\n\n  EXPECT_FALSE(set->InvalidatesElement(*element));\n  // Adding one string sets the string_impl_ of the classes_ Backing.\n  set->AddClass(\"a\");\n  EXPECT_TRUE(set->InvalidatesElement(*element));\n  // Adding another upgrades to a HashSet.\n  set->AddClass(\"c\");\n  EXPECT_TRUE(set->InvalidatesElement(*element));\n\n  // These sets should not cause invalidation.\n  set = DescendantInvalidationSet::Create();\n  set->AddClass(\"c\");\n  EXPECT_FALSE(set->InvalidatesElement(*element));\n  set->AddClass(\"d\");\n  EXPECT_FALSE(set->InvalidatesElement(*element));\n}\n\n// TEST(InvalidationSetTest, AttributeInvalidatesElement) {\n//   auto element = std::make_unique<tasm::MockAttributeHolder>(\"div\");\n//   element->SetIdSelector(\"test\");\n//   element->SetStaticAttribute(\"a\", lepus_value());\n//   element->SetStaticAttribute(\"b\", lepus_value());\n//\n//   ASSERT_TRUE(element);\n//\n//   auto set = DescendantInvalidationSet::Create();\n//   EXPECT_FALSE(set->InvalidatesElement(*element));\n//   // Adding one string sets the string_impl_ of the classes_ Backing.\n//   set->AddAttribute(\"a\");\n//   EXPECT_TRUE(set->InvalidatesElement(*element));\n//   // Adding another upgrades to a HashSet.\n//   set->AddAttribute(\"c\");\n//   EXPECT_TRUE(set->InvalidatesElement(*element));\n//\n//   // These sets should not cause invalidation.\n//   set = DescendantInvalidationSet::Create();\n//   set->AddAttribute(\"c\");\n//   EXPECT_FALSE(set->InvalidatesElement(*element));\n//   set->AddAttribute(\"d\");\n//   EXPECT_FALSE(set->InvalidatesElement(*element));\n// }\n\n// Once we setWholeSubtreeInvalid, we should not keep the HashSets.\nTEST(InvalidationSetTest, SubtreeInvalid_AddBefore) {\n  auto set = DescendantInvalidationSet::Create();\n  set->AddClass(\"a\");\n  set->SetWholeSubtreeInvalid();\n\n  ASSERT_TRUE(set->IsEmpty());\n}\n\n// Don't (re)create HashSets if we've already setWholeSubtreeInvalid.\nTEST(InvalidationSetTest, SubtreeInvalid_AddAfter) {\n  auto set = DescendantInvalidationSet::Create();\n  set->SetWholeSubtreeInvalid();\n  set->AddTagName(\"a\");\n\n  ASSERT_TRUE(set->IsEmpty());\n}\n\n// No need to keep the HashSets when combining with a wholeSubtreeInvalid set.\nTEST(InvalidationSetTest, SubtreeInvalid_Combine_1) {\n  auto set1 = DescendantInvalidationSet::Create();\n  auto set2 = DescendantInvalidationSet::Create();\n\n  set1->AddId(\"a\");\n  set2->SetWholeSubtreeInvalid();\n\n  set1->Combine(*set2);\n\n  ASSERT_TRUE(set1->WholeSubtreeInvalid());\n  ASSERT_TRUE(set1->IsEmpty());\n}\n\n// No need to add HashSets from combining set when we already have\n// wholeSubtreeInvalid.\n// TEST(InvalidationSetTest, SubtreeInvalid_Combine_2) {\n//   auto set1 = DescendantInvalidationSet::Create();\n//   auto set2 = DescendantInvalidationSet::Create();\n//\n//   set1->SetWholeSubtreeInvalid();\n//   set2->AddAttribute(\"a\");\n//\n//   set1->Combine(*set2);\n//\n//   ASSERT_TRUE(set1->WholeSubtreeInvalid());\n//   ASSERT_TRUE(set1->IsEmpty());\n// }\n\nTEST(InvalidationSetTest, SelfInvalidationSet_Combine) {\n  InvalidationSet* self_set = InvalidationSet::SelfInvalidationSet();\n\n  EXPECT_TRUE(self_set->IsSelfInvalidationSet());\n  self_set->Combine(*self_set);\n  EXPECT_TRUE(self_set->IsSelfInvalidationSet());\n\n  auto set = DescendantInvalidationSet::Create();\n  EXPECT_FALSE(set->InvalidatesSelf());\n  set->Combine(*self_set);\n  EXPECT_TRUE(set->InvalidatesSelf());\n}\n\n}  // namespace\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/invalidation/rule_invalidation_set.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/invalidation/rule_invalidation_set.h\"\n\n#include <algorithm>\n#include <utility>\n\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n\nnamespace lynx {\nnamespace css {\n\nstatic inline bool SupportedRelation(LynxCSSSelector::RelationType relation) {\n  return relation == LynxCSSSelector::kSubSelector ||\n         relation == LynxCSSSelector::kDescendant ||\n         relation == LynxCSSSelector::kChild ||\n         relation == LynxCSSSelector::kUAShadow;\n}\n\nInvalidationSet& RuleInvalidationSet::GetInvalidationSet(\n    PositionType position, InvalidationSetPtr& invalidation_set) {\n  if (!invalidation_set) {\n    invalidation_set =\n        position == kSubject\n            ? InvalidationSetPtr(InvalidationSet::SelfInvalidationSet())\n            : DescendantInvalidationSet::Create();\n    return *invalidation_set;\n  }\n\n  if (invalidation_set->IsSelfInvalidationSet() && position == kSubject) {\n    return *invalidation_set;\n  }\n\n  // For example, '. a' creates a SelfInvalidationSet, then '.a .b' needs to\n  // change the invalidation_set\n  if (invalidation_set->IsSelfInvalidationSet()) {\n    invalidation_set = DescendantInvalidationSet::Create();\n    invalidation_set->SetInvalidatesSelf();\n  }\n\n  return *invalidation_set;\n}\n\nInvalidationSet& RuleInvalidationSet::GetInvalidationSet(\n    InvalidationSetMap& map, const std::string& key, PositionType position) {\n  InvalidationSetPtr& invalidation_set = map[key];\n  return GetInvalidationSet(position, invalidation_set);\n}\n\nInvalidationSet& RuleInvalidationSet::GetInvalidationSet(\n    PseudoTypeInvalidationSetMap& map, LynxCSSSelector::PseudoType key,\n    PositionType position) {\n  InvalidationSetPtr& invalidation_set = map[key];\n  return GetInvalidationSet(position, invalidation_set);\n}\n\n// For example, '.a.b' will return '.a', '.a#b' will return '#b', 'div.a' will\n// return '.a'\nvoid RuleInvalidationSet::ExtractSimpleSelector(\n    const LynxCSSSelector& selector, InvalidationSetFeature& feature) {\n  if (selector.Match() == LynxCSSSelector::kTag &&\n      selector.Value() != CSSGlobalStarString()) {\n    feature.SetTag(selector.Value());\n    return;\n  }\n  if (selector.Match() == LynxCSSSelector::kId) {\n    feature.SetId(selector.Value());\n    return;\n  }\n  if (selector.Match() == LynxCSSSelector::kClass) {\n    feature.SetClass(selector.Value());\n  }\n}\n\nInvalidationSet* RuleInvalidationSet::GetInvalidationSetForSimpleSelector(\n    const LynxCSSSelector& selector, PositionType position) {\n  if (selector.Match() == LynxCSSSelector::kClass) {\n    return &GetInvalidationSet(class_invalidation_sets_, selector.Value(),\n                               position);\n  }\n  if (selector.Match() == LynxCSSSelector::kId) {\n    return &GetInvalidationSet(id_invalidation_sets_, selector.Value(),\n                               position);\n  }\n  if (selector.Match() == LynxCSSSelector::kPseudoClass) {\n    switch (selector.GetPseudoType()) {\n      case LynxCSSSelector::kPseudoHover:\n      case LynxCSSSelector::kPseudoFocus:\n      case LynxCSSSelector::kPseudoActive:\n        return &GetInvalidationSet(pseudo_invalidation_sets_,\n                                   selector.GetPseudoType(), position);\n      default:\n        break;\n    }\n  }\n  return nullptr;\n}\n\nvoid RuleInvalidationSet::UpdateInvalidationSets(\n    const LynxCSSSelector& complex, InvalidationSetFeature& feature,\n    PositionType position) {\n  // For example, '.a' will return a 'feature' object with classes containing\n  // 'a', and the last_in_compound is '.a' too.\n  // Another example, '.a#b' will return a 'feature' object with ids containing\n  // 'b' because id is more important than class, and the last_in_compound is\n  // '.b' too.\n  const LynxCSSSelector* last_in_compound =\n      ExtractCompound(complex, feature, position);\n\n  bool was_full_invalid = feature.FullInvalid();\n\n  if (!feature.HasFeature()) {\n    feature.SetFullInvalid(true);\n  }\n\n  const LynxCSSSelector* next_compound = last_in_compound->TagHistory();\n  // For example, '.a .b', only support descendants\n  if (next_compound && SupportedRelation(last_in_compound->Relation())) {\n    // Add next_compound in *_invalidation_sets_\n    AddSelectorToInvalidationSets(*next_compound, feature);\n  }\n\n  if (!next_compound) {\n    return;\n  }\n\n  feature.SetFullInvalid(was_full_invalid);\n}\n\nvoid RuleInvalidationSet::ExtractSelectorList(\n    const LynxCSSSelector& simple_selector, PositionType position) {\n  const LynxCSSSelector* selector_list = simple_selector.SelectorListSelector();\n  if (!selector_list) {\n    return;\n  }\n\n  for (; selector_list;\n       selector_list = LynxCSSSelectorList::Next(*selector_list)) {\n    InvalidationSetFeature complex_feature;\n    UpdateInvalidationSets(*selector_list, complex_feature, position);\n  }\n}\n\nconst LynxCSSSelector* RuleInvalidationSet::ExtractCompound(\n    const LynxCSSSelector& compound, InvalidationSetFeature& feature,\n    PositionType position) {\n  // NOTE: This loop stops once we are at the end of the compound, i.e., we see\n  // a relation that is not a sub-selector. So for e.g. .a .b.c#d, we will see\n  // .b, .c, #d and then stop, returning a pointer to #d.\n  const LynxCSSSelector* simple_selector = &compound;\n  for (;; simple_selector = simple_selector->TagHistory()) {\n    ExtractSimpleSelector(*simple_selector, feature);\n    // Create and add invalidation-set to *_invalidation_sets_\n    if (InvalidationSet* invalidation_set =\n            GetInvalidationSetForSimpleSelector(*simple_selector, position)) {\n      if (position == kSubject) {\n        invalidation_set->SetInvalidatesSelf();\n      }\n    }\n\n    // For the :not pseudo class\n    ExtractSelectorList(*simple_selector, position);\n\n    // Next should be another compound selector or null\n    if (!simple_selector->TagHistory() ||\n        simple_selector->Relation() != LynxCSSSelector::kSubSelector) {\n      return simple_selector;\n    }\n  }\n}\n\nvoid RuleInvalidationSet::AddFeatureToInvalidationSet(\n    InvalidationSet& invalidation_set, const InvalidationSetFeature& feature) {\n  if (feature.FullInvalid()) {\n    invalidation_set.SetWholeSubtreeInvalid();\n    return;\n  }\n\n  for (const auto& id : feature.ids) {\n    invalidation_set.AddId(id);\n  }\n  for (const auto& tag_name : feature.tag_names) {\n    invalidation_set.AddTagName(tag_name);\n  }\n  for (const auto& class_name : feature.classes) {\n    invalidation_set.AddClass(class_name);\n  }\n}\n\nvoid RuleInvalidationSet::AddSimpleSelectorToInvalidationSets(\n    const LynxCSSSelector& simple_selector,\n    InvalidationSetFeature& descendant_feature) {\n  // Add invalidation-set to *_invalidation_sets_ with type kAncestor\n  if (InvalidationSet* invalidation_set =\n          GetInvalidationSetForSimpleSelector(simple_selector, kAncestor)) {\n    // For example, if we have a selector, that is '.m .p', for class m we\n    // only have a descendant containing class p\n    AddFeatureToInvalidationSet(*invalidation_set, descendant_feature);\n  }\n}\n\nconst LynxCSSSelector*\nRuleInvalidationSet::AddCompoundSelectorToInvalidationSets(\n    const LynxCSSSelector& compound,\n    InvalidationSetFeature& descendant_feature) {\n  // For example, for selector '.m .n.x .p' we will add feature to\n  // InvalidationSets, the result is '.m .p', '.n .p', '.x .p',\n  // '.p'(SelfInvalidationSet).\n  const LynxCSSSelector* simple_selector = &compound;\n  for (; simple_selector; simple_selector = simple_selector->TagHistory()) {\n    AddSimpleSelectorToInvalidationSets(*simple_selector, descendant_feature);\n    if (simple_selector->Relation() != LynxCSSSelector::kSubSelector) {\n      break;\n    }\n    if (!simple_selector->TagHistory()) {\n      break;\n    }\n  }\n\n  return simple_selector;\n}\n\nvoid RuleInvalidationSet::AddSelectorToInvalidationSets(\n    const LynxCSSSelector& selector,\n    InvalidationSetFeature& descendant_feature) {\n  // The 'selector' is the selector immediately to the left of the rightmost\n  // combinator. descendant_feature has the feature of the rightmost compound\n  // selector.\n  const LynxCSSSelector* compound = &selector;\n  while (compound) {\n    // NOTE: We only support descendants\n    if (!SupportedRelation(compound->Relation())) {\n      return;\n    }\n\n    // For example, for selector '.m .n.x .p' the loop is '.n' and '.m'\n    const LynxCSSSelector* last_in_compound =\n        AddCompoundSelectorToInvalidationSets(*compound, descendant_feature);\n    DCHECK(last_in_compound);\n    compound = last_in_compound->TagHistory();\n  }\n}\n\nvoid RuleInvalidationSet::AddSelector(const LynxCSSSelector& selector) {\n  InvalidationSetFeature feature;\n  UpdateInvalidationSets(selector, feature, kSubject);\n}\n\nvoid RuleInvalidationSet::CombineInvalidationSet(\n    InvalidationSetMap& map, const std::string& key,\n    InvalidationSet* invalidation_set) {\n  DCHECK(invalidation_set);\n  InvalidationSetPtr& value = map[key];\n  GetInvalidationSet(\n      invalidation_set->IsSelfInvalidationSet() ? kSubject : kAncestor, value)\n      .Combine(*invalidation_set);\n}\n\nvoid RuleInvalidationSet::CombineInvalidationSet(\n    PseudoTypeInvalidationSetMap& map, LynxCSSSelector::PseudoType key,\n    InvalidationSet* invalidation_set) {\n  DCHECK(invalidation_set);\n  InvalidationSetPtr& value = map[key];\n  GetInvalidationSet(\n      invalidation_set->IsSelfInvalidationSet() ? kSubject : kAncestor, value)\n      .Combine(*invalidation_set);\n}\n\nvoid RuleInvalidationSet::Merge(const RuleInvalidationSet& other) {\n  for (const auto& entry : other.class_invalidation_sets_)\n    CombineInvalidationSet(class_invalidation_sets_, entry.first,\n                           entry.second.get());\n  for (const auto& entry : other.id_invalidation_sets_)\n    CombineInvalidationSet(id_invalidation_sets_, entry.first,\n                           entry.second.get());\n  for (const auto& entry : other.pseudo_invalidation_sets_) {\n    auto key = static_cast<LynxCSSSelector::PseudoType>(entry.first);\n    CombineInvalidationSet(pseudo_invalidation_sets_, key, entry.second.get());\n  }\n}\n\nvoid RuleInvalidationSet::Clear() {\n  class_invalidation_sets_.clear();\n  id_invalidation_sets_.clear();\n  pseudo_invalidation_sets_.clear();\n}\n\n#define COLLECT_INVALIDATION_SETS(field, name, key_type)                  \\\n  void RuleInvalidationSet::Collect##name(                                \\\n      InvalidationLists& invalidation_lists, const key_type& key) const { \\\n    auto it = field.find(key);                                            \\\n    if (it == field.end()) {                                              \\\n      return;                                                             \\\n    }                                                                     \\\n    if (!it->second->IsAlive()) {                                         \\\n      return;                                                             \\\n    }                                                                     \\\n    DescendantInvalidationSet* descendants =                              \\\n        static_cast<DescendantInvalidationSet*>(it->second.get());        \\\n    if (descendants) {                                                    \\\n      invalidation_lists.descendants.push_back(descendants);              \\\n    }                                                                     \\\n  }\n\nCOLLECT_INVALIDATION_SETS(id_invalidation_sets_, Id, std::string)\nCOLLECT_INVALIDATION_SETS(class_invalidation_sets_, Class, std::string)\nCOLLECT_INVALIDATION_SETS(pseudo_invalidation_sets_, PseudoClass,\n                          LynxCSSSelector::PseudoType)\n#undef COLLECT_INVALIDATION_SETS\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/invalidation/rule_invalidation_set.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_RENDERER_CSS_NG_INVALIDATION_RULE_INVALIDATION_SET_H_\n#define CORE_RENDERER_CSS_NG_INVALIDATION_RULE_INVALIDATION_SET_H_\n\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"core/renderer/css/ng/invalidation/invalidation_set.h\"\n#include \"core/renderer/css/ng/invalidation/invalidation_set_feature.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector.h\"\n\nnamespace lynx {\nnamespace css {\n\nstruct InvalidationLists;\n\nclass RuleInvalidationSet {\n public:\n  RuleInvalidationSet() = default;\n  RuleInvalidationSet(const RuleInvalidationSet&) = delete;\n  RuleInvalidationSet& operator=(const RuleInvalidationSet&) = delete;\n\n  void Merge(const RuleInvalidationSet& other);\n\n  void Clear();\n\n  void AddSelector(const LynxCSSSelector&);\n\n  void CollectClass(InvalidationLists&, const std::string& class_name) const;\n  void CollectId(InvalidationLists&, const std::string& id) const;\n  void CollectPseudoClass(InvalidationLists&,\n                          const LynxCSSSelector::PseudoType&) const;\n\n private:\n  enum PositionType { kSubject, kAncestor };\n  InvalidationSet* GetInvalidationSetForSimpleSelector(const LynxCSSSelector&,\n                                                       PositionType);\n\n  using InvalidationSetMap =\n      std::unordered_map<std::string, InvalidationSetPtr>;\n  using PseudoTypeInvalidationSetMap =\n      std::unordered_map<LynxCSSSelector::PseudoType, InvalidationSetPtr>;\n\n  void UpdateInvalidationSets(const LynxCSSSelector&, InvalidationSetFeature&,\n                              PositionType);\n\n  static void ExtractSimpleSelector(const LynxCSSSelector&,\n                                    InvalidationSetFeature&);\n  const LynxCSSSelector* ExtractCompound(const LynxCSSSelector&,\n                                         InvalidationSetFeature&, PositionType);\n  void ExtractSelectorList(const LynxCSSSelector&, PositionType);\n  void AddFeatureToInvalidationSet(InvalidationSet&,\n                                   const InvalidationSetFeature&);\n  void AddSelectorToInvalidationSets(\n      const LynxCSSSelector&, InvalidationSetFeature& descendant_feature);\n  const LynxCSSSelector* AddCompoundSelectorToInvalidationSets(\n      const LynxCSSSelector&, InvalidationSetFeature& descendant_feature);\n  void AddSimpleSelectorToInvalidationSets(\n      const LynxCSSSelector& simple_selector,\n      InvalidationSetFeature& descendant_feature);\n\n  static InvalidationSet& GetInvalidationSet(\n      PositionType position, InvalidationSetPtr& invalidation_set);\n\n  static InvalidationSet& GetInvalidationSet(InvalidationSetMap&,\n                                             const std::string& key,\n                                             PositionType);\n  static InvalidationSet& GetInvalidationSet(PseudoTypeInvalidationSetMap&,\n                                             LynxCSSSelector::PseudoType key,\n                                             PositionType);\n\n  static void CombineInvalidationSet(InvalidationSetMap&,\n                                     const std::string& key, InvalidationSet*);\n  static void CombineInvalidationSet(PseudoTypeInvalidationSetMap&,\n                                     LynxCSSSelector::PseudoType key,\n                                     InvalidationSet*);\n\n  InvalidationSetMap class_invalidation_sets_;\n  InvalidationSetMap id_invalidation_sets_;\n  PseudoTypeInvalidationSetMap pseudo_invalidation_sets_;\n\n  friend class RuleInvalidationSetTest;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_INVALIDATION_RULE_INVALIDATION_SET_H_\n"
  },
  {
    "path": "core/renderer/css/ng/invalidation/rule_invalidation_set_test.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2015 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"core/renderer/css/ng/invalidation/rule_invalidation_set.h\"\n\n#include <memory>\n#include <string>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#include \"core/renderer/css/ng/parser/css_parser_token_range.h\"\n#include \"core/renderer/css/ng/parser/css_tokenizer.h\"\n#include \"core/renderer/css/ng/selector/css_parser_context.h\"\n#include \"core/renderer/css/ng/selector/css_selector_parser.h\"\n#include \"core/renderer/css/ng/style/style_rule.h\"\n#include \"core/renderer/tasm/testing/mock_attribute_holder.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nusing testing::AssertionFailure;\nusing testing::AssertionResult;\nusing testing::AssertionSuccess;\n\nnamespace lynx {\nnamespace css {\n\nclass RuleInvalidationSetTest : public testing::Test {\n public:\n  RuleInvalidationSetTest() = default;\n\n  void SetUp() override {\n    document_ = std::make_unique<tasm::MockAttributeHolder>(\"html\");\n    auto first = std::make_unique<tasm::MockAttributeHolder>(\"b\");\n    auto first_ptr = first.get();\n    document_->AddChild(std::move(first));\n    auto inner = std::make_unique<tasm::MockAttributeHolder>(\"i\");\n    first_ptr->AddChild(std::move(inner));\n  }\n\n  void MergeInto(RuleInvalidationSet& rule_feature_set) {\n    rule_feature_set.Merge(rule_invalidation_set_);\n  }\n\n  RuleInvalidationSet& GetRuleInvalidationSet() {\n    return rule_invalidation_set_;\n  }\n\n  void AddSelector(const std::string& selector_text) {\n    return AddSelector(selector_text, rule_invalidation_set_);\n  }\n\n  static void AddSelector(std::unique_ptr<css::LynxCSSSelector[]> selector_arr,\n                          RuleInvalidationSet& set) {\n    auto style_rule =\n        std::make_unique<StyleRule>(std::move(selector_arr), nullptr);\n    return AddSelector(std::move(style_rule), set);\n  }\n\n  static void AddSelector(std::unique_ptr<StyleRule> style_rule,\n                          RuleInvalidationSet& set) {\n    for (const LynxCSSSelector* s = style_rule->FirstSelector(); s;\n         s = LynxCSSSelectorList::Next(*s)) {\n      set.AddSelector(*s);\n    }\n  }\n\n  static void AddSelector(const std::string& selector_text,\n                          RuleInvalidationSet& set) {\n    std::vector<LynxCSSSelector> arena;\n    CSSParserContext context;\n    CSSTokenizer tokenizer(selector_text);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    LynxCSSSelectorVector selector_vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    size_t flattened_size = CSSSelectorParser::FlattenedSize(selector_vector);\n    if (!flattened_size) {\n      return;\n    }\n\n    auto selector_array = std::make_unique<LynxCSSSelector[]>(flattened_size);\n    CSSSelectorParser::AdoptSelectorVector(\n        selector_vector, selector_array.get(), flattened_size);\n    return AddSelector(std::move(selector_array), set);\n  }\n\n  void ClearInvalidations() { rule_invalidation_set_.Clear(); }\n\n  void CollectClass(InvalidationLists& invalidation_lists,\n                    const std::string& class_name) const {\n    rule_invalidation_set_.CollectClass(invalidation_lists, class_name);\n  }\n\n  void CollectId(InvalidationLists& invalidation_lists,\n                 const std::string& id) const {\n    rule_invalidation_set_.CollectId(invalidation_lists, id);\n  }\n\n  void CollectPseudoClass(InvalidationLists& invalidation_lists,\n                          LynxCSSSelector::PseudoType pseudo) const {\n    rule_invalidation_set_.CollectPseudoClass(invalidation_lists, pseudo);\n  }\n\n  using BackingType = InvalidationSet::BackingType;\n\n  template <BackingType type>\n  std::unordered_set<std::string> ToHashSet(\n      typename InvalidationSet::Backing<type>::Range range) {\n    std::unordered_set<std::string> hash_set;\n    for (auto& str : range) hash_set.insert(str);\n    return hash_set;\n  }\n\n  std::unordered_set<std::string> ClassSet(\n      const InvalidationSet& invalidation_set) {\n    return ToHashSet<BackingType::kClasses>(invalidation_set.Classes());\n  }\n\n  std::unordered_set<std::string> IdSet(\n      const InvalidationSet& invalidation_set) {\n    return ToHashSet<BackingType::kIds>(invalidation_set.Ids());\n  }\n\n  std::unordered_set<std::string> TagNameSet(\n      const InvalidationSet& invalidation_set) {\n    return ToHashSet<BackingType::kTagNames>(invalidation_set.TagNames());\n  }\n\n  AssertionResult HasNoInvalidation(InvalidationSetVector& invalidation_sets) {\n    if (!invalidation_sets.empty()) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 0\";\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasSelfInvalidation(\n      InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    if (!invalidation_sets[0]->InvalidatesSelf()) {\n      return AssertionFailure() << \"should invalidate self\";\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasNoSelfInvalidation(\n      InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    if (invalidation_sets[0]->InvalidatesSelf()) {\n      return AssertionFailure() << \"should not invalidate self\";\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasSelfInvalidationSet(\n      InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    if (!invalidation_sets[0]->IsSelfInvalidationSet()) {\n      return AssertionFailure() << \"should be the self-invalidation set\";\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasNotSelfInvalidationSet(\n      InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    if (invalidation_sets[0]->IsSelfInvalidationSet()) {\n      return AssertionFailure() << \"should not be the self-invalidation set\";\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasWholeSubtreeInvalidation(\n      InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    if (!invalidation_sets[0]->WholeSubtreeInvalid()) {\n      return AssertionFailure() << \"should invalidate whole subtree\";\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasClassInvalidation(\n      const std::string& class_name, InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    std::unordered_set<std::string> classes = ClassSet(*invalidation_sets[0]);\n    if (classes.size() != 1u) {\n      return AssertionFailure() << classes.size() << \" should be 1\";\n    }\n    if (classes.find(class_name) == classes.end()) {\n      return AssertionFailure() << \"should invalidate class \" << class_name;\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasClassInvalidation(\n      const std::string& first_class_name, const std::string& second_class_name,\n      InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    std::unordered_set<std::string> classes = ClassSet(*invalidation_sets[0]);\n    if (classes.size() != 2u) {\n      return AssertionFailure() << classes.size() << \" should be 2\";\n    }\n    if (classes.find(first_class_name) == classes.end()) {\n      return AssertionFailure()\n             << \"should invalidate class \" << first_class_name;\n    }\n    if (classes.find(second_class_name) == classes.end()) {\n      return AssertionFailure()\n             << \"should invalidate class \" << second_class_name;\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasClassInvalidation(\n      const std::string& first_class_name, const std::string& second_class_name,\n      const std::string& third_class_name,\n      InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    std::unordered_set<std::string> classes = ClassSet(*invalidation_sets[0]);\n    if (classes.size() != 3u) {\n      return AssertionFailure() << classes.size() << \" should be 3\";\n    }\n    if (classes.find(first_class_name) == classes.end()) {\n      return AssertionFailure()\n             << \"should invalidate class \" << first_class_name;\n    }\n    if (classes.find(second_class_name) == classes.end()) {\n      return AssertionFailure()\n             << \"should invalidate class \" << second_class_name;\n    }\n    if (classes.find(third_class_name) == classes.end()) {\n      return AssertionFailure()\n             << \"should invalidate class \" << third_class_name;\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasIdInvalidation(const std::string& id,\n                                    InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    std::unordered_set<std::string> ids = IdSet(*invalidation_sets[0]);\n    if (ids.size() != 1u) {\n      return AssertionFailure() << ids.size() << \" should be 1\";\n    }\n    if (ids.find(id) == ids.end()) {\n      return AssertionFailure() << \"should invalidate id \" << id;\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasIdInvalidation(const std::string& first_id,\n                                    const std::string& second_id,\n                                    InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    std::unordered_set<std::string> ids = IdSet(*invalidation_sets[0]);\n    if (ids.size() != 2u) {\n      return AssertionFailure() << ids.size() << \" should be 2\";\n    }\n    if (ids.find(first_id) == ids.end()) {\n      return AssertionFailure() << \"should invalidate id \" << first_id;\n    }\n    if (ids.find(second_id) == ids.end()) {\n      return AssertionFailure() << \"should invalidate id \" << second_id;\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasTagNameInvalidation(\n      const std::string& tag_name, InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    std::unordered_set<std::string> tag_names =\n        TagNameSet(*invalidation_sets[0]);\n    if (tag_names.size() != 1u) {\n      return AssertionFailure() << tag_names.size() << \" should be 1\";\n    }\n    if (tag_names.find(tag_name) == tag_names.end()) {\n      return AssertionFailure() << \"should invalidate tag \" << tag_name;\n    }\n    return AssertionSuccess();\n  }\n\n  AssertionResult HasTagNameInvalidation(\n      const std::string& first_tag_name, const std::string& second_tag_name,\n      InvalidationSetVector& invalidation_sets) {\n    if (invalidation_sets.size() != 1u) {\n      return AssertionFailure() << \"has \" << invalidation_sets.size()\n                                << \" invalidation set(s), should have 1\";\n    }\n    std::unordered_set<std::string> tag_names =\n        TagNameSet(*invalidation_sets[0]);\n    if (tag_names.size() != 2u) {\n      return AssertionFailure() << tag_names.size() << \" should be 2\";\n    }\n    if (tag_names.find(first_tag_name) == tag_names.end()) {\n      return AssertionFailure() << \"should invalidate tag \" << first_tag_name;\n    }\n    if (tag_names.find(second_tag_name) == tag_names.end()) {\n      return AssertionFailure() << \"should invalidate tag \" << second_tag_name;\n    }\n    return AssertionSuccess();\n  }\n\n private:\n  RuleInvalidationSet rule_invalidation_set_;\n  std::unique_ptr<tasm::MockAttributeHolder> document_;\n};\n\nTEST_F(RuleInvalidationSetTest, interleavedDescendantSibling1) {\n  AddSelector(\".p\");\n\n  InvalidationLists invalidation_lists;\n  CollectClass(invalidation_lists, \"p\");\n  EXPECT_TRUE(HasSelfInvalidation(invalidation_lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, id) {\n  AddSelector(\"#a #b\");\n\n  InvalidationLists invalidation_lists;\n  CollectId(invalidation_lists, \"a\");\n  EXPECT_TRUE(HasIdInvalidation(\"b\", invalidation_lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, pseudoClass) {\n  AddSelector(\":focus\");\n\n  InvalidationLists invalidation_lists;\n  CollectPseudoClass(invalidation_lists, LynxCSSSelector::kPseudoFocus);\n  EXPECT_TRUE(HasSelfInvalidation(invalidation_lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, tagName) {\n  AddSelector(\":focus e\");\n\n  InvalidationLists invalidation_lists;\n  CollectPseudoClass(invalidation_lists, LynxCSSSelector::kPseudoFocus);\n  EXPECT_TRUE(HasTagNameInvalidation(\"e\", invalidation_lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, Whole) {\n  AddSelector(\".a *\");\n\n  InvalidationLists invalidation_lists;\n  CollectClass(invalidation_lists, \"a\");\n  EXPECT_TRUE(HasWholeSubtreeInvalidation(invalidation_lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, SelfInvalidationSet) {\n  AddSelector(\".a\");\n  AddSelector(\"div .b\");\n  AddSelector(\"#c\");\n  AddSelector(\"[d]\");\n  AddSelector(\":hover\");\n\n  InvalidationLists invalidation_lists;\n  CollectClass(invalidation_lists, \"a\");\n  EXPECT_TRUE(HasSelfInvalidation(invalidation_lists.descendants));\n  EXPECT_TRUE(HasSelfInvalidationSet(invalidation_lists.descendants));\n\n  invalidation_lists.descendants.clear();\n  CollectClass(invalidation_lists, \"b\");\n  EXPECT_TRUE(HasSelfInvalidation(invalidation_lists.descendants));\n  EXPECT_TRUE(HasSelfInvalidationSet(invalidation_lists.descendants));\n\n  invalidation_lists.descendants.clear();\n  CollectId(invalidation_lists, \"c\");\n  EXPECT_TRUE(HasSelfInvalidation(invalidation_lists.descendants));\n  EXPECT_TRUE(HasSelfInvalidationSet(invalidation_lists.descendants));\n\n  invalidation_lists.descendants.clear();\n  CollectPseudoClass(invalidation_lists, LynxCSSSelector::kPseudoHover);\n  EXPECT_TRUE(HasSelfInvalidation(invalidation_lists.descendants));\n  EXPECT_TRUE(HasSelfInvalidationSet(invalidation_lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, ReplaceSelfInvalidationSet) {\n  AddSelector(\".a\");\n\n  InvalidationLists invalidation_lists;\n  CollectClass(invalidation_lists, \"a\");\n  EXPECT_TRUE(HasSelfInvalidation(invalidation_lists.descendants));\n  EXPECT_TRUE(HasSelfInvalidationSet(invalidation_lists.descendants));\n\n  AddSelector(\".a div\");\n\n  invalidation_lists.descendants.clear();\n  CollectClass(invalidation_lists, \"a\");\n  EXPECT_TRUE(HasSelfInvalidation(invalidation_lists.descendants));\n  EXPECT_TRUE(HasNotSelfInvalidationSet(invalidation_lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, Not) {\n  AddSelector(\".b:not(.a) .c\");\n  auto& s = GetRuleInvalidationSet();\n  InvalidationLists lists;\n  s.CollectClass(lists, \"b\");\n  EXPECT_TRUE(HasClassInvalidation(\"c\", lists.descendants));\n\n  lists = InvalidationLists();\n  s.CollectClass(lists, \"d\");\n  EXPECT_TRUE(HasNoInvalidation(lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, EnsureMutableInvalidationSet2) {\n  AddSelector(\".a div\");\n  AddSelector(\"div\");\n  auto& s = GetRuleInvalidationSet();\n  InvalidationLists lists;\n  s.CollectClass(lists, \"a\");\n  EXPECT_TRUE(HasTagNameInvalidation(\"div\", lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, EnsureMutableInvalidationSet3) {\n  AddSelector(\".a ~ *\");\n  AddSelector(\".a div\");\n  auto& s = GetRuleInvalidationSet();\n  InvalidationLists lists;\n  s.CollectClass(lists, \"a\");\n  EXPECT_FALSE(HasWholeSubtreeInvalidation(lists.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, Merge) {\n  RuleInvalidationSet local;\n  AddSelector(\".a div\");\n  AddSelector(\":hover *\");\n  MergeInto(local);\n  ClearInvalidations();\n  InvalidationLists lists;\n  local.CollectClass(lists, \"a\");\n  EXPECT_FALSE(HasWholeSubtreeInvalidation(lists.descendants));\n\n  InvalidationLists lists_pseudo;\n  local.CollectPseudoClass(lists_pseudo,\n                           LynxCSSSelector::PseudoType::kPseudoHover);\n  EXPECT_TRUE(HasWholeSubtreeInvalidation(lists_pseudo.descendants));\n}\n\nTEST_F(RuleInvalidationSetTest, IgnoreSibling) {\n  AddSelector(\".a ~ div\");\n  AddSelector(\".a ~ div .b\");\n  AddSelector(\".a ~ div *\");\n  InvalidationLists lists;\n  CollectClass(lists, \"a\");\n  EXPECT_TRUE(HasNoInvalidation(lists.descendants));\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/matcher/selector_matcher.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/matcher/selector_matcher.h\"\n\n#include <string>\n\n#include \"base/include/auto_reset.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n\nnamespace lynx {\nnamespace css {\n\nstatic StyleNode* Parent(\n    const SelectorMatcher::SelectorMatchingContext& context) {\n  return context.holder->SelectorMatchingParent();\n}\n\nbool SelectorMatcher::Match(const SelectorMatchingContext& context) const {\n  base::AutoReset<bool> reset_in_match(&in_match_, true);\n\n  return MatchSelector(context) == kMatches;\n}\n\nSelectorMatcher::MatchResult SelectorMatcher::MatchSelector(\n    const SelectorMatchingContext& context) const {\n  if (!MatchSimple(context)) {\n    return kFailsLocally;\n  }\n  if (context.selector->IsLastInTagHistory()) {\n    return kMatches;\n  }\n\n  MatchResult match;\n  if (context.selector->Relation() != LynxCSSSelector::kSubSelector) {\n    match = MatchForRelation(context);\n  } else {\n    match = MatchForSubSelector(context);\n  }\n  return match;\n}\n\nstatic inline SelectorMatcher::SelectorMatchingContext NextContext(\n    const SelectorMatcher::SelectorMatchingContext& context) {\n  SelectorMatcher::SelectorMatchingContext next_context(context);\n  DCHECK(context.selector->TagHistory());\n  next_context.selector = context.selector->TagHistory();\n  return next_context;\n}\n\nSelectorMatcher::MatchResult SelectorMatcher::MatchForSubSelector(\n    const SelectorMatchingContext& context) const {\n  SelectorMatchingContext next_context = NextContext(context);\n  return MatchSelector(next_context);\n}\n\nSelectorMatcher::MatchResult SelectorMatcher::MatchForRelation(\n    const SelectorMatchingContext& context) const {\n  SelectorMatchingContext next_context = NextContext(context);\n  LynxCSSSelector::RelationType relation = context.selector->Relation();\n\n  switch (relation) {\n    case LynxCSSSelector::kDescendant: {\n      for (next_context.holder = Parent(next_context); next_context.holder;\n           next_context.holder = Parent(next_context)) {\n        MatchResult match = MatchSelector(next_context);\n        if (match == kMatches || match == kFailsCompletely) {\n          return match;\n        }\n      }\n      return kFailsCompletely;\n    }\n    case LynxCSSSelector::kChild: {\n      next_context.holder = Parent(next_context);\n      if (!next_context.holder) {\n        return kFailsCompletely;\n      }\n      return MatchSelector(next_context);\n    }\n    case LynxCSSSelector::kDirectAdjacent: {\n      next_context.holder = context.holder->PreviousSibling();\n      if (!next_context.holder) {\n        return kFailsAllSiblings;\n      }\n      return MatchSelector(next_context);\n    }\n    case LynxCSSSelector::kIndirectAdjacent: {\n      next_context.holder = context.holder->PreviousSibling();\n      for (; next_context.holder;\n           next_context.holder = next_context.holder->PreviousSibling()) {\n        MatchResult match = MatchSelector(next_context);\n        if (match == kMatches || match == kFailsAllSiblings ||\n            match == kFailsCompletely) {\n          return match;\n        }\n      }\n      return kFailsAllSiblings;\n    }\n    case LynxCSSSelector::kUAShadow: {\n      next_context.holder = context.holder->PseudoElementOwner();\n      return MatchSelector(next_context);\n    }\n    case LynxCSSSelector::kSubSelector:\n    default:\n      break;\n  }\n  return kFailsCompletely;\n}\n\nbool SelectorMatcher::MatchSimple(\n    const SelectorMatchingContext& context) const {\n  DCHECK(context.holder);\n  auto& element = *context.holder;\n  DCHECK(context.selector);\n  const LynxCSSSelector& selector = *context.selector;\n\n  switch (selector.Match()) {\n    case LynxCSSSelector::kTag:\n      return selector.Value() == CSSGlobalStarString() ||\n             element.ContainsTagSelector(selector.Value());\n    case LynxCSSSelector::kClass:\n      return element.ContainsClassSelector(selector.Value());\n    case LynxCSSSelector::kId:\n      return element.ContainsIdSelector(selector.Value());\n    case LynxCSSSelector::kPseudoClass:\n      return MatchPseudoClass(context);\n    case LynxCSSSelector::kPseudoElement:\n      return MatchPseudoElement(context);\n    default:\n      return false;\n  }\n}\n\nbool SelectorMatcher::MatchPseudoNot(\n    const SelectorMatchingContext& context) const {\n  const LynxCSSSelector& selector = *context.selector;\n  DCHECK(selector.SelectorList());\n  SelectorMatchingContext sub_context(context);\n  for (sub_context.selector = selector.SelectorList()->First();\n       sub_context.selector; sub_context.selector = LynxCSSSelectorList::Next(\n                                 *sub_context.selector)) {\n    if (MatchSelector(sub_context) == kMatches) {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool SelectorMatcher::MatchPseudoClass(\n    const SelectorMatchingContext& context) const {\n  auto& element = *context.holder;\n  const LynxCSSSelector& selector = *context.selector;\n\n  switch (selector.GetPseudoType()) {\n    case LynxCSSSelector::kPseudoNot:\n      return MatchPseudoNot(context);\n    case LynxCSSSelector::kPseudoHover:\n      return element.HasPseudoState(tasm::kPseudoStateHover);\n    case LynxCSSSelector::kPseudoActive:\n      return element.HasPseudoState(tasm::kPseudoStateActive);\n    case LynxCSSSelector::kPseudoFocus:\n      return element.HasPseudoState(tasm::kPseudoStateFocus);\n    case LynxCSSSelector::kPseudoRoot:\n      return element.tag().str() == \"page\";\n    case LynxCSSSelector::kPseudoUnknown:\n    default:\n      break;\n  }\n  return false;\n}\n\nbool SelectorMatcher::MatchPseudoElement(\n    const SelectorMatchingContext& context) const {\n  auto& holder = *context.holder;\n  const LynxCSSSelector& selector = *context.selector;\n\n  switch (selector.GetPseudoType()) {\n    case LynxCSSSelector::PseudoType::kPseudoPlaceholder:\n      return holder.HasPseudoState(tasm::kPseudoStatePlaceHolder);\n    case LynxCSSSelector::PseudoType::kPseudoSelection:\n      return holder.HasPseudoState(tasm::kPseudoStateSelection);\n    default:\n      break;\n  }\n  return false;\n}\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/matcher/selector_matcher.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_MATCHER_SELECTOR_MATCHER_H_\n#define CORE_RENDERER_CSS_NG_MATCHER_SELECTOR_MATCHER_H_\n\n#include <limits>\n\n#include \"core/renderer/css/ng/selector/lynx_css_selector.h\"\n#include \"core/renderer/css/style_node.h\"\n\nnamespace lynx {\nnamespace css {\n\nclass SelectorMatcher {\n public:\n  SelectorMatcher() = default;\n\n  SelectorMatcher(const SelectorMatcher&) = delete;\n  SelectorMatcher& operator=(const SelectorMatcher&) = delete;\n\n  struct SelectorMatchingContext {\n   public:\n    explicit SelectorMatchingContext(StyleNode* holder) : holder(holder) {}\n    const LynxCSSSelector* selector = nullptr;\n    StyleNode* holder = nullptr;\n  };\n\n  bool Match(const SelectorMatchingContext& context) const;\n\n private:\n  bool MatchSimple(const SelectorMatchingContext&) const;\n\n  enum MatchResult {\n    kMatches,\n    kFailsLocally,\n    kFailsAllSiblings,\n    kFailsCompletely\n  };\n\n  MatchResult MatchSelector(const SelectorMatchingContext&) const;\n  MatchResult MatchForSubSelector(const SelectorMatchingContext&) const;\n  MatchResult MatchForRelation(const SelectorMatchingContext&) const;\n  bool MatchPseudoClass(const SelectorMatchingContext&) const;\n  bool MatchPseudoElement(const SelectorMatchingContext&) const;\n  bool MatchPseudoNot(const SelectorMatchingContext&) const;\n\n  mutable bool in_match_ = false;\n};\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_MATCHER_SELECTOR_MATCHER_H_\n"
  },
  {
    "path": "core/renderer/css/ng/matcher/selector_matcher_test.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/matcher/selector_matcher.h\"\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"core/renderer/css/ng/parser/css_parser_token_range.h\"\n#include \"core/renderer/css/ng/parser/css_tokenizer.h\"\n#include \"core/renderer/css/ng/selector/css_parser_context.h\"\n#include \"core/renderer/css/ng/selector/css_selector_parser.h\"\n#include \"core/renderer/tasm/testing/mock_attribute_holder.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace css {\n\nTEST(CSSMatcherTest, CheckSimple) {\n  const char* test_case = \"div .a:focus\";\n\n  SCOPED_TRACE(test_case);\n\n  CSSParserContext context;\n  CSSTokenizer tokenizer(test_case);\n  const auto tokens = tokenizer.TokenizeToEOF();\n  CSSParserTokenRange range(tokens);\n  LynxCSSSelectorVector vector =\n      CSSSelectorParser::ParseSelector(range, &context);\n  auto list = CSSSelectorParser::AdoptSelectorVector(vector);\n  auto parent = std::make_unique<tasm::MockAttributeHolder>(\"div\");\n  auto child = std::make_unique<tasm::MockAttributeHolder>(\"div\");\n  auto child_ptr = child.get();\n  child->SetClass(\"a\");\n  child->SetPseudoState(tasm::kPseudoStateFocus);\n  parent->AddChild(std::move(child));\n\n  SelectorMatcher matcher;\n  SelectorMatcher::SelectorMatchingContext matchingContext(child_ptr);\n  matchingContext.selector = list.First();\n  auto ret = matcher.Match(matchingContext);\n\n  EXPECT_TRUE(ret);\n}\n\nstruct MatchStatusTestData {\n  const char* selector;\n  bool status;\n};\n\nMatchStatusTestData matcher_test_data[] = {\n    {\"*\", true},\n    {\"view\", true},\n    {\":root view\", true},\n    {\".foo\", true},\n    {\".bar\", false},\n    {\"#main\", true},\n    {\"#test\", false},\n    {\":focus\", true},\n    {\":active\", false},\n\n    // We never evaluate :hover, since :active fails to match.\n    {\":active:hover\", false},\n\n    // Non-rightmost compound:\n    {\":focus *\", true},\n    {\":focus > *\", true},\n    {\"text + .foo\", true},\n    {\"view + .foo\", false},\n    {\"text ~ .foo\", true},\n    {\"view ~ .foo\", false},\n\n    // Within pseudo-classes:\n    {\":not(text)\", true},\n    {\":not(view)\", false},\n    {\":not(:active, :hover)\", true},\n    {\":not(:active, :focus)\", false},\n};\n\nclass MatchStatusTest\n    : public testing::Test,\n      public testing::WithParamInterface<MatchStatusTestData> {};\n\nINSTANTIATE_TEST_SUITE_P(SelectorChecker, MatchStatusTest,\n                         testing::ValuesIn(matcher_test_data));\n\nTEST_P(MatchStatusTest, All) {\n  MatchStatusTestData param = GetParam();\n\n  CSSParserContext context;\n  CSSTokenizer tokenizer(param.selector);\n  const auto tokens = tokenizer.TokenizeToEOF();\n  CSSParserTokenRange range(tokens);\n  LynxCSSSelectorVector vector =\n      CSSSelectorParser::ParseSelector(range, &context);\n  auto list = CSSSelectorParser::AdoptSelectorVector(vector);\n\n  auto parent = std::make_unique<tasm::MockAttributeHolder>(\"page\");\n  parent->SetPseudoState(tasm::kPseudoStateFocus);\n  auto first = std::make_unique<tasm::MockAttributeHolder>(\"text\");\n  parent->AddChild(std::move(first));\n\n  auto target = std::make_unique<tasm::MockAttributeHolder>(\"view\");\n  auto target_ptr = target.get();\n  target->SetIdSelector(\"main\");\n  target->SetClass(\"foo\");\n  target->SetStaticAttribute(\"flatten\", lepus_value(\"true\"));\n  target->SetStaticAttribute(\"color\", lepus_value(\"red green blue\"));\n  target->SetStaticAttribute(\"lang\", lepus_value(\"zh-CN\"));\n  target->SetPseudoState(tasm::kPseudoStateFocus);\n  parent->AddChild(std::move(target));\n\n  auto next = std::make_unique<tasm::MockAttributeHolder>(\"view\");\n  parent->AddChild(std::move(next));\n\n  auto last = std::make_unique<tasm::MockAttributeHolder>(\"view\");\n  parent->AddChild(std::move(last));\n\n  SelectorMatcher matcher;\n  SelectorMatcher::SelectorMatchingContext matchingContext(target_ptr);\n  matchingContext.selector = list.First();\n  bool ret = matcher.Match(matchingContext);\n  SCOPED_TRACE(param.selector);\n  EXPECT_EQ(param.status, ret);\n}\n\nTEST(CSSMatcherTest, CheckPseudoElement) {\n  const char* test_case = \"div .a::placeholder, div::selection\";\n\n  SCOPED_TRACE(test_case);\n\n  CSSParserContext context;\n  CSSTokenizer tokenizer(test_case);\n  const auto tokens = tokenizer.TokenizeToEOF();\n  CSSParserTokenRange range(tokens);\n  LynxCSSSelectorVector vector =\n      CSSSelectorParser::ParseSelector(range, &context);\n  auto list = CSSSelectorParser::AdoptSelectorVector(vector);\n  auto parent = std::make_unique<tasm::MockAttributeHolder>(\"div\");\n  auto child = std::make_unique<tasm::MockAttributeHolder>(\"div\");\n  auto child_ptr = child.get();\n  child->SetClass(\"a\");\n  parent->AddChild(std::move(child));\n\n  tasm::MockAttributeHolder placeholder(\"view\");\n  placeholder.AddPseudoState(tasm::kPseudoStatePlaceHolder);\n  placeholder.SetPseudoElementOwner(child_ptr);\n\n  tasm::MockAttributeHolder selection(\"view\");\n  selection.AddPseudoState(tasm::kPseudoStateSelection);\n  selection.SetPseudoElementOwner(child_ptr);\n\n  {\n    SelectorMatcher matcher;\n    SelectorMatcher::SelectorMatchingContext matchingContext(child_ptr);\n    matchingContext.selector = list.First();\n    auto ret = matcher.Match(matchingContext);\n    EXPECT_FALSE(ret);\n  }\n  {\n    SelectorMatcher matcher;\n    SelectorMatcher::SelectorMatchingContext matchingContext(&placeholder);\n    matchingContext.selector = list.First();\n    auto ret = matcher.Match(matchingContext);\n    EXPECT_TRUE(ret);\n  }\n  {\n    SelectorMatcher matcher;\n    SelectorMatcher::SelectorMatchingContext matchingContext(&selection);\n    matchingContext.selector = list.Next(*list.First());\n    auto ret = matcher.Match(matchingContext);\n    EXPECT_TRUE(ret);\n  }\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_parser_idioms.cc",
    "content": "// Copyright 2019 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/parser/css_parser_idioms.h\"\n\n#include \"core/renderer/css/ng/parser/css_tokenizer_input_stream.h\"\n#include \"core/renderer/css/ng/parser/string_to_number.h\"\n\nnamespace lynx {\nnamespace css {\n\nvoid ConsumeSingleWhitespaceIfNext(CSSTokenizerInputStream& input) {\n  // We check for \\r\\n and HTML spaces since we don't do preprocessing\n  UChar next = input.PeekWithoutReplacement(0);\n  if (next == '\\r' && input.PeekWithoutReplacement(1) == '\\n')\n    input.Advance(2);\n  else if (base::IsHTMLSpace(next))\n    input.Advance();\n}\n\n// https://drafts.csswg.org/css-syntax/#consume-an-escaped-code-point\nUChar32 ConsumeEscape(CSSTokenizerInputStream& input) {\n  UChar cc = input.NextInputChar();\n  input.Advance();\n  DCHECK(!IsCSSNewLine(cc));\n  if (base::IsASCIIHexNumber(cc)) {\n    unsigned consumed_hex_digits = 1;\n    std::u16string hex_chars;\n    hex_chars.push_back(cc);\n    while (consumed_hex_digits < 6 &&\n           base::IsASCIIHexNumber(input.PeekWithoutReplacement(0))) {\n      cc = input.NextInputChar();\n      input.Advance();\n      hex_chars.push_back(cc);\n      consumed_hex_digits++;\n    };\n    ConsumeSingleWhitespaceIfNext(input);\n    bool ok = false;\n    UChar32 code_point =\n        HexCharactersToUInt(hex_chars.c_str(), hex_chars.length(),\n                            NumberParsingOptions::kStrict, &ok);\n    DCHECK(ok);\n    if (code_point == 0 || (0xD800 <= code_point && code_point <= 0xDFFF) ||\n        code_point > 0x10FFFF)\n      return kReplacementCharacter;\n    return code_point;\n  }\n\n  if (cc == kEndOfFileMarker) return kReplacementCharacter;\n  return cc;\n}\n\n// http://www.w3.org/TR/css3-syntax/#consume-a-name\nstd::u16string ConsumeName(CSSTokenizerInputStream& input) {\n  std::u16string result;\n  while (true) {\n    UChar cc = input.NextInputChar();\n    input.Advance();\n    if (IsNameCodePoint(cc)) {\n      result.push_back(cc);\n      continue;\n    }\n    if (TwoCharsAreValidEscape(cc, input.PeekWithoutReplacement(0))) {\n      result.push_back(ConsumeEscape(input));\n      continue;\n    }\n    input.PushBack(cc);\n    return result;\n  }\n}\n\n// https://drafts.csswg.org/css-syntax/#would-start-an-identifier\nbool NextCharsAreIdentifier(UChar first, const CSSTokenizerInputStream& input) {\n  UChar second = input.PeekWithoutReplacement(0);\n  if (IsNameStartCodePoint(first) || TwoCharsAreValidEscape(first, second))\n    return true;\n\n  if (first == '-') {\n    return IsNameStartCodePoint(second) || second == '-' ||\n           TwoCharsAreValidEscape(second, input.PeekWithoutReplacement(1));\n  }\n\n  return false;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_parser_idioms.h",
    "content": "\n/*\n * Copyright (C) 2013 Google Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are\n * met:\n *\n *     * Redistributions of source code must retain the above copyright\n * notice, this list of conditions and the following disclaimer.\n *     * Redistributions in binary form must reproduce the above\n * copyright notice, this list of conditions and the following disclaimer\n * in the documentation and/or other materials provided with the\n * distribution.\n *     * Neither the name of Google Inc. nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_IDIOMS_H_\n#define CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_IDIOMS_H_\n\n#include <string>\n\n#include \"base/include/string/string_utils.h\"\n\nnamespace lynx {\nnamespace css {\n\nclass CSSTokenizerInputStream;\n\n// Space characters as defined by the CSS specification.\n// http://www.w3.org/TR/css3-syntax/#whitespace\ninline bool IsCSSSpace(base::UChar c) {\n  return c == ' ' || c == '\\t' || c == '\\n';\n}\n\ninline bool IsCSSNewLine(base::UChar cc) {\n  // We check \\r and \\f here, since we have no preprocessing stage\n  return (cc == '\\r' || cc == '\\n' || cc == '\\f');\n}\n\n// https://drafts.csswg.org/css-syntax/#name-start-code-point\ntemplate <typename CharacterType>\nbool IsNameStartCodePoint(CharacterType c) {\n  return ((c | 0x20) >= 'a' && (c | 0x20) <= 'z') || c == '_' ||\n         !base::IsASCII(c);\n}\n\n// https://drafts.csswg.org/css-syntax/#name-code-point\ntemplate <typename CharacterType>\nbool IsNameCodePoint(CharacterType c) {\n  return IsNameStartCodePoint(c) || base::IsASCIINumber(c) || c == '-';\n}\n\n// https://drafts.csswg.org/css-syntax/#check-if-two-code-points-are-a-valid-escape\ninline bool TwoCharsAreValidEscape(base::UChar first, base::UChar second) {\n  return first == '\\\\' && !IsCSSNewLine(second);\n}\n\n// Consumes a single whitespace, if the stream is currently looking at a\n// whitespace. Note that \\r\\n counts as a single whitespace, as we don't do\n// input preprocessing as a separate step.\n//\n// See https://drafts.csswg.org/css-syntax-3/#input-preprocessing\nvoid ConsumeSingleWhitespaceIfNext(CSSTokenizerInputStream&);\n\n// https://drafts.csswg.org/css-syntax/#consume-an-escaped-code-point\nbase::UChar32 ConsumeEscape(CSSTokenizerInputStream&);\n\n// http://www.w3.org/TR/css3-syntax/#consume-a-name\nstd::u16string ConsumeName(CSSTokenizerInputStream&);\n\n// https://drafts.csswg.org/css-syntax/#would-start-an-identifier\nbool NextCharsAreIdentifier(base::UChar, const CSSTokenizerInputStream&);\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_IDIOMS_H_\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_parser_token.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/parser/css_parser_token.h\"\n\n#include <algorithm>\n#include <limits>\n\nnamespace lynx {\nnamespace css {\n\n// Just a helper used for Delimiter tokens.\nCSSParserToken::CSSParserToken(CSSParserTokenType type, UChar c)\n    : type_(type), block_type_(kNotBlock), delimiter_(c) {\n  DCHECK_EQ(type_, static_cast<unsigned>(kDelimiterToken));\n}\n\nCSSParserToken::CSSParserToken(CSSParserTokenType type, double numeric_value,\n                               NumericValueType numeric_value_type,\n                               NumericSign sign)\n    : type_(type),\n      block_type_(kNotBlock),\n      numeric_value_type_(numeric_value_type),\n      numeric_sign_(sign) /*,\n       unit_(static_cast<unsigned>(CSSPrimitiveValue::UnitType::kNumber))*/\n{\n  DCHECK_EQ(type, kNumberToken);\n  numeric_value_ =\n      std::clamp<double>(numeric_value, -std::numeric_limits<float>::max(),\n                         std::numeric_limits<float>::max());\n}\n\nCSSParserToken::CSSParserToken(CSSParserTokenType type, UChar32 start,\n                               UChar32 end)\n    : type_(kUnicodeRangeToken), block_type_(kNotBlock) {\n  DCHECK_EQ(type, kUnicodeRangeToken);\n  unicode_range_.start = start;\n  unicode_range_.end = end;\n}\n\nCSSParserToken::CSSParserToken(HashTokenType type, const std::u16string& value)\n    : type_(kHashToken), block_type_(kNotBlock), hash_token_type_(type) {\n  InitValueFromStringView(value);\n}\n\nvoid CSSParserToken::ConvertToDimensionWithUnit(const std::u16string& unit) {\n  DCHECK_EQ(type_, static_cast<unsigned>(kNumberToken));\n  type_ = kDimensionToken;\n  InitValueFromStringView(unit);\n  // unit_ = static_cast<unsigned>(CSSPrimitiveValue::StringToUnitType(unit));\n}\n\nvoid CSSParserToken::ConvertToPercentage() {\n  DCHECK_EQ(type_, static_cast<unsigned>(kNumberToken));\n  type_ = kPercentageToken;\n  // unit_ = static_cast<unsigned>(CSSPrimitiveValue::UnitType::kPercentage);\n}\n\nUChar CSSParserToken::Delimiter() const {\n  DCHECK_EQ(type_, static_cast<unsigned>(kDelimiterToken));\n  return delimiter_;\n}\n\nNumericSign CSSParserToken::GetNumericSign() const {\n  // This is valid for DimensionToken and PercentageToken, but only used\n  // in <an+b> parsing on NumberTokens.\n  DCHECK_EQ(type_, static_cast<unsigned>(kNumberToken));\n  return static_cast<NumericSign>(numeric_sign_);\n}\n\nNumericValueType CSSParserToken::GetNumericValueType() const {\n  DCHECK(type_ == kNumberToken || type_ == kPercentageToken ||\n         type_ == kDimensionToken);\n  return static_cast<NumericValueType>(numeric_value_type_);\n}\n\ndouble CSSParserToken::NumericValue() const {\n  DCHECK(type_ == kNumberToken || type_ == kPercentageToken ||\n         type_ == kDimensionToken);\n  return numeric_value_;\n}\n\n// CSSPropertyID CSSParserToken::ParseAsUnresolvedCSSPropertyID(\n//     const ExecutionContext* execution_context,\n//     CSSParserMode mode) const {\n//   DCHECK_EQ(type_, static_cast<unsigned>(kIdentToken));\n//   return UnresolvedCSSPropertyID(execution_context, Value(), mode);\n// }\n//\n// AtRuleDescriptorID CSSParserToken::ParseAsAtRuleDescriptorID() const {\n//   DCHECK_EQ(type_, static_cast<unsigned>(kIdentToken));\n//   return AsAtRuleDescriptorID(Value());\n// }\n\n// CSSValueID CSSParserToken::Id() const {\n//   if (type_ != kIdentToken)\n//     return CSSValueID::kInvalid;\n//   if (id_ < 0)\n//     id_ = static_cast<int>(CssValueKeywordID(Value()));\n//   return static_cast<CSSValueID>(id_);\n// }\n\n// CSSValueID CSSParserToken::FunctionId() const {\n//   if (type_ != kFunctionToken)\n//     return CSSValueID::kInvalid;\n//   if (id_ < 0)\n//     id_ = static_cast<int>(CssValueKeywordID(Value()));\n//   return static_cast<CSSValueID>(id_);\n// }\n\n// bool CSSParserToken::HasStringBacking() const {\n//   CSSParserTokenType token_type = GetType();\n//   return token_type == kIdentToken || token_type == kFunctionToken ||\n//          token_type == kAtKeywordToken || token_type == kHashToken ||\n//          token_type == kUrlToken || token_type == kDimensionToken ||\n//          token_type == kStringToken;\n// }\n\n// CSSParserToken CSSParserToken::CopyWithUpdatedString(\n//     const std::u16string& string) const {\n//   CSSParserToken copy(*this);\n//   copy.InitValueFromStringView(string);\n//   return copy;\n// }\n\nbool CSSParserToken::ValueDataCharRawEqual(const CSSParserToken& other) const {\n  if (value_length_ != other.value_length_) return false;\n\n  return value_data_char_raw_ == other.value_data_char_raw_;\n}\n\nbool CSSParserToken::operator==(const CSSParserToken& other) const {\n  if (type_ != other.type_) return false;\n  switch (type_) {\n    case kDelimiterToken:\n      return Delimiter() == other.Delimiter();\n    case kHashToken:\n      if (hash_token_type_ != other.hash_token_type_) return false;\n      [[fallthrough]];\n    case kIdentToken:\n    case kFunctionToken:\n    case kStringToken:\n    case kUrlToken:\n      return ValueDataCharRawEqual(other);\n    case kDimensionToken:\n      if (!ValueDataCharRawEqual(other)) return false;\n      [[fallthrough]];\n    case kNumberToken:\n    case kPercentageToken:\n      return numeric_sign_ == other.numeric_sign_ &&\n             numeric_value_ == other.numeric_value_ &&\n             numeric_value_type_ == other.numeric_value_type_;\n    case kUnicodeRangeToken:\n      return unicode_range_.start == other.unicode_range_.start &&\n             unicode_range_.end == other.unicode_range_.end;\n    default:\n      return true;\n  }\n}\n\nvoid CSSParserToken::Serialize(std::string& builder) const {\n  // This is currently only used for @supports CSSOM. To keep our implementation\n  // simple we handle some of the edge cases incorrectly (see comments below).\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_parser_token.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_TOKEN_H_\n#define CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_TOKEN_H_\n\n#include <string>\n#include <vector>\n\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n\nnamespace lynx {\nnamespace css {\n\nenum CSSParserTokenType {\n  kIdentToken = 0,\n  kFunctionToken,\n  kAtKeywordToken,\n  kHashToken,\n  kUrlToken,\n  kBadUrlToken,\n  kDelimiterToken,\n  kNumberToken,\n  kPercentageToken,\n  kDimensionToken,\n  kIncludeMatchToken,\n  kDashMatchToken,\n  kPrefixMatchToken,\n  kSuffixMatchToken,\n  kSubstringMatchToken,\n  kColumnToken,\n  kUnicodeRangeToken,\n  kWhitespaceToken,\n  kCDOToken,\n  kCDCToken,\n  kColonToken,\n  kSemicolonToken,\n  kCommaToken,\n  kLeftParenthesisToken,\n  kRightParenthesisToken,\n  kLeftBracketToken,\n  kRightBracketToken,\n  kLeftBraceToken,\n  kRightBraceToken,\n  kStringToken,\n  kBadStringToken,\n  kEOFToken,\n  kCommentToken,\n};\n\nenum NumericSign {\n  kNoSign,\n  kPlusSign,\n  kMinusSign,\n};\n\nenum NumericValueType {\n  kIntegerValueType,\n  kNumberValueType,\n};\n\nenum HashTokenType {\n  kHashTokenId,\n  kHashTokenUnrestricted,\n};\n\nclass CSSParserToken {\n public:\n  enum BlockType {\n    kNotBlock,\n    kBlockStart,\n    kBlockEnd,\n  };\n\n  CSSParserToken(CSSParserTokenType type, BlockType block_type = kNotBlock)\n      : type_(type), block_type_(block_type) {}\n  CSSParserToken(CSSParserTokenType type, const std::u16string& value,\n                 BlockType block_type = kNotBlock)\n      : type_(type), block_type_(block_type) {\n    InitValueFromStringView(value);\n    id_ = -1;\n  }\n\n  CSSParserToken(CSSParserTokenType, UChar);  // for DelimiterToken\n  CSSParserToken(CSSParserTokenType, double, NumericValueType,\n                 NumericSign);  // for NumberToken\n  CSSParserToken(CSSParserTokenType, UChar32,\n                 UChar32);  // for UnicodeRangeToken\n\n  CSSParserToken(HashTokenType, const std::u16string&);\n\n  bool operator==(const CSSParserToken& other) const;\n  bool operator!=(const CSSParserToken& other) const {\n    return !(*this == other);\n  }\n\n  // Converts NumberToken to DimensionToken.\n  void ConvertToDimensionWithUnit(const std::u16string&);\n\n  // Converts NumberToken to PercentageToken.\n  void ConvertToPercentage();\n\n  CSSParserTokenType GetType() const {\n    return static_cast<CSSParserTokenType>(type_);\n  }\n  const std::u16string& Value() const { return value_data_char_raw_; }\n\n  bool IsEOF() const { return type_ == static_cast<unsigned>(kEOFToken); }\n\n  UChar Delimiter() const;\n  NumericSign GetNumericSign() const;\n  NumericValueType GetNumericValueType() const;\n  double NumericValue() const;\n  HashTokenType GetHashTokenType() const {\n    DCHECK_EQ(type_, static_cast<unsigned>(kHashToken));\n    return hash_token_type_;\n  }\n  BlockType GetBlockType() const { return static_cast<BlockType>(block_type_); }\n  //  CSSPrimitiveValue::UnitType GetUnitType() const {\n  //    return static_cast<CSSPrimitiveValue::UnitType>(unit_);\n  //  }\n  UChar32 UnicodeRangeStart() const {\n    DCHECK_EQ(type_, static_cast<unsigned>(kUnicodeRangeToken));\n    return unicode_range_.start;\n  }\n  UChar32 UnicodeRangeEnd() const {\n    DCHECK_EQ(type_, static_cast<unsigned>(kUnicodeRangeToken));\n    return unicode_range_.end;\n  }\n  // CSSValueID Id() const;\n  // CSSValueID FunctionId() const;\n\n  // bool HasStringBacking() const;\n\n  void Serialize(std::string&) const;\n\n  // CSSParserToken CopyWithUpdatedString(const std::u16string&) const;\n\n  static CSSParserTokenType ClosingTokenType(CSSParserTokenType opening_type) {\n    switch (opening_type) {\n      case kFunctionToken:\n      case kLeftParenthesisToken:\n        return kRightParenthesisToken;\n      case kLeftBracketToken:\n        return kRightBracketToken;\n      case kLeftBraceToken:\n        return kRightBraceToken;\n      default:\n        // NOTREACHED();\n        return kEOFToken;\n    }\n  }\n\n private:\n  void InitValueFromStringView(const std::u16string& string) {\n    value_length_ = string.length();\n    value_data_char_raw_ = string;\n  }\n  bool ValueDataCharRawEqual(const CSSParserToken& other) const;\n\n  unsigned type_ : 6;                // CSSParserTokenType\n  unsigned block_type_ : 2;          // BlockType\n  unsigned numeric_value_type_ : 1;  // NumericValueType\n  unsigned numeric_sign_ : 2;        // NumericSign\n  // unsigned unit_ : 7;                // CSSPrimitiveValue::UnitType\n  // value_... is an unpacked std::u16string so that we can pack it\n  // tightly with the rest of this object for a smaller object size.\n  size_t value_length_;\n  std::u16string value_data_char_raw_;  // Either LChar* or UChar*.\n\n  union {\n    UChar delimiter_;\n    HashTokenType hash_token_type_;\n    double numeric_value_;\n    mutable int id_;\n\n    struct {\n      UChar32 start;\n      UChar32 end;\n    } unicode_range_;\n  };\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_TOKEN_H_\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_parser_token_range.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/parser/css_parser_token_range.h\"\n\n#include <string>\n\nnamespace lynx {\nnamespace css {\n\nCSSParserToken CSSParserTokenRange::g_static_eof_token =\n    CSSParserToken(kEOFToken);\n\nCSSParserTokenRange CSSParserTokenRange::MakeSubRange(\n    const CSSParserToken* first, const CSSParserToken* last) const {\n  if (first == &g_static_eof_token) first = last_;\n  if (last == &g_static_eof_token) last = last_;\n  DCHECK(first <= last);\n  return CSSParserTokenRange(first, last);\n}\n\nCSSParserTokenRange CSSParserTokenRange::ConsumeBlock() {\n  DCHECK_EQ(Peek().GetBlockType(), CSSParserToken::kBlockStart);\n  const CSSParserToken* start = &Peek() + 1;\n  unsigned nesting_level = 0;\n  do {\n    const CSSParserToken& token = Consume();\n    if (token.GetBlockType() == CSSParserToken::kBlockStart)\n      nesting_level++;\n    else if (token.GetBlockType() == CSSParserToken::kBlockEnd)\n      nesting_level--;\n  } while (nesting_level && first_ < last_);\n\n  if (nesting_level) return MakeSubRange(start, first_);  // Ended at EOF\n  return MakeSubRange(start, first_ - 1);\n}\n\nvoid CSSParserTokenRange::ConsumeComponentValue() {\n  // FIXME: This is going to do multiple passes over large sections of a\n  // stylesheet. We should consider optimising this by precomputing where each\n  // block ends.\n  unsigned nesting_level = 0;\n  do {\n    const CSSParserToken& token = Consume();\n    if (token.GetBlockType() == CSSParserToken::kBlockStart)\n      nesting_level++;\n    else if (token.GetBlockType() == CSSParserToken::kBlockEnd)\n      nesting_level--;\n  } while (nesting_level && first_ < last_);\n}\n\nstd::string CSSParserTokenRange::Serialize() const {\n  // We're supposed to insert comments between certain pairs of token types\n  // as per spec, but since this is currently only used for @supports CSSOM\n  // and CSS Paint API arguments we just get these cases wrong and avoid the\n  // additional complexity.\n  std::string builder;\n  for (const CSSParserToken* it = first_; it != last_; ++it)\n    it->Serialize(builder);\n  return builder;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_parser_token_range.h",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_TOKEN_RANGE_H_\n#define CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_TOKEN_RANGE_H_\n\n#include <string>\n#include <vector>\n\n#include \"core/renderer/css/ng/parser/css_parser_token.h\"\n\nnamespace lynx {\nnamespace css {\n\n// A CSSParserTokenRange is an iterator over a subrange of a vector of\n// CSSParserTokens. Accessing outside of the range will return an endless stream\n// of EOF tokens. This class refers to half-open intervals [first, last).\nclass CSSParserTokenRange {\n  static CSSParserToken g_static_eof_token;\n\n public:\n  CSSParserTokenRange(const std::vector<CSSParserToken>& vector)\n      : first_(vector.data()),\n        last_(vector.data() + std::distance(vector.begin(), vector.end())) {}\n\n  // This should be called on a range with tokens returned by that range.\n  CSSParserTokenRange MakeSubRange(const CSSParserToken* first,\n                                   const CSSParserToken* last) const;\n\n  bool AtEnd() const { return first_ == last_; }\n  const CSSParserToken* end() const { return last_; }\n\n  const CSSParserToken& Peek(size_t offset = 0) const {\n    if (first_ + offset >= last_) return g_static_eof_token;\n    return *(first_ + offset);\n  }\n\n  const CSSParserToken& Consume() {\n    if (first_ == last_) return g_static_eof_token;\n    return *first_++;\n  }\n\n  const CSSParserToken& ConsumeIncludingWhitespace() {\n    const CSSParserToken& result = Consume();\n    ConsumeWhitespace();\n    return result;\n  }\n\n  // The returned range doesn't include the brackets\n  CSSParserTokenRange ConsumeBlock();\n\n  void ConsumeComponentValue();\n\n  void ConsumeWhitespace() {\n    while (Peek().GetType() == kWhitespaceToken) ++first_;\n  }\n\n  std::string Serialize() const;\n\n  const CSSParserToken* begin() const { return first_; }\n\n private:\n  CSSParserTokenRange(const CSSParserToken* first, const CSSParserToken* last)\n      : first_(first), last_(last) {}\n\n  const CSSParserToken* first_;\n  const CSSParserToken* last_;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_TOKEN_RANGE_H_\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_parser_token_stream.cc",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/parser/css_parser_token_stream.h\"\n\nnamespace lynx {\nnamespace css {\n\nstd::u16string CSSParserTokenStream::StringRangeAt(size_t start,\n                                                   size_t length) const {\n  return tokenizer_.StringRangeAt(start, length);\n}\n\nvoid CSSParserTokenStream::ConsumeWhitespace() {\n  while (Peek().GetType() == kWhitespaceToken) UncheckedConsume();\n}\n\nCSSParserToken CSSParserTokenStream::ConsumeIncludingWhitespace() {\n  CSSParserToken result = Consume();\n  ConsumeWhitespace();\n  return result;\n}\n\nbool CSSParserTokenStream::ConsumeCommentOrNothing() {\n  DCHECK(!HasLookAhead());\n  const auto token = tokenizer_.TokenizeSingleWithComments();\n  if (token.GetType() != kCommentToken) {\n    next_ = token;\n    has_look_ahead_ = true;\n    return false;\n  }\n\n  has_look_ahead_ = false;\n  offset_ = tokenizer_.Offset();\n  return true;\n}\n\nvoid CSSParserTokenStream::UncheckedConsumeComponentValue() {\n  DCHECK(HasLookAhead());\n\n  // Have to use internal consume/peek in here because they can read past\n  // start/end of blocks\n  unsigned nesting_level = 0;\n  do {\n    const CSSParserToken& token = UncheckedConsumeInternal();\n    if (token.GetBlockType() == CSSParserToken::kBlockStart)\n      nesting_level++;\n    else if (token.GetBlockType() == CSSParserToken::kBlockEnd)\n      nesting_level--;\n  } while (!PeekInternal().IsEOF() && nesting_level);\n}\n\nvoid CSSParserTokenStream::UncheckedSkipToEndOfBlock() {\n  DCHECK(HasLookAhead());\n  // Have to use internal consume/peek in here because they can read past\n  // start/end of blocks\n  unsigned nesting_level = 1;\n  do {\n    const CSSParserToken& token = UncheckedConsumeInternal();\n    if (token.GetBlockType() == CSSParserToken::kBlockStart)\n      nesting_level++;\n    else if (token.GetBlockType() == CSSParserToken::kBlockEnd)\n      nesting_level--;\n  } while (nesting_level && !PeekInternal().IsEOF());\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_parser_token_stream.h",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_TOKEN_STREAM_H_\n#define CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_TOKEN_STREAM_H_\n\n#include <vector>\n\n#include \"base/include/auto_reset.h\"\n#include \"core/renderer/css/ng/parser/css_parser_token_range.h\"\n#include \"core/renderer/css/ng/parser/css_tokenizer.h\"\n\nnamespace lynx {\nnamespace css {\n\nnamespace detail {\n\ntemplate <typename...>\nbool IsTokenTypeOneOf(CSSParserTokenType t) {\n  return false;\n}\n\ntemplate <CSSParserTokenType Head, CSSParserTokenType... Tail>\nbool IsTokenTypeOneOf(CSSParserTokenType t) {\n  return t == Head || IsTokenTypeOneOf<Tail...>(t);\n}\n\n}  // namespace detail\n\n// A streaming interface to CSSTokenizer that tokenizes on demand.\n// Abstractly, the stream ends at either EOF or the beginning/end of a block.\n// To consume a block, a BlockGuard must be created first to ensure that\n// we finish consuming a block even if there was an error.\n//\n// Methods prefixed with \"Unchecked\" can only be called after calls to Peek(),\n// EnsureLookAhead(), or AtEnd() with no subsequent modifications to the stream\n// such as a consume.\nclass CSSParserTokenStream {\n public:\n  // Instantiate this to start reading from a block. When the guard is out of\n  // scope, the rest of the block is consumed.\n  class BlockGuard {\n   public:\n    explicit BlockGuard(CSSParserTokenStream& stream) : stream_(stream) {\n      const CSSParserToken next = stream.ConsumeInternal();\n      DCHECK_EQ(next.GetBlockType(), CSSParserToken::kBlockStart);\n    }\n\n    ~BlockGuard() {\n      stream_.EnsureLookAhead();\n      stream_.UncheckedSkipToEndOfBlock();\n    }\n\n   private:\n    CSSParserTokenStream& stream_;\n  };\n\n  static constexpr uint64_t FlagForTokenType(CSSParserTokenType token_type) {\n    return 1ull << static_cast<uint64_t>(token_type);\n  }\n\n  // The specified token type will be treated as kEOF while the Boundary is on\n  // the stack.\n  class Boundary {\n   public:\n    Boundary(CSSParserTokenStream& stream, CSSParserTokenType boundary_type)\n        : auto_reset_(&stream.boundaries_,\n                      stream.boundaries_ | FlagForTokenType(boundary_type)) {}\n    ~Boundary() = default;\n\n   private:\n    lynx::base::AutoReset<uint64_t> auto_reset_;\n  };\n\n  // We found that this value works well empirically by printing out the\n  // maximum buffer size for a few top alexa websites. It should be slightly\n  // above the expected number of tokens in the prelude of an at rule and\n  // the number of tokens in a declaration.\n  // TODO(crbug.com/661854): Can we streamify at rule parsing so that this is\n  // only needed for declarations which are easier to think about?\n  static constexpr size_t InitialBufferSize() { return 128; }\n\n  explicit CSSParserTokenStream(CSSTokenizer& tokenizer)\n      : tokenizer_(tokenizer), next_(kEOFToken) {\n    buffer_.reserve(InitialBufferSize());\n  }\n\n  CSSParserTokenStream(CSSParserTokenStream&&) = default;\n  CSSParserTokenStream(const CSSParserTokenStream&) = delete;\n  CSSParserTokenStream& operator=(const CSSParserTokenStream&) = delete;\n\n  inline void EnsureLookAhead() {\n    if (!HasLookAhead()) {\n      has_look_ahead_ = true;\n      next_ = tokenizer_.TokenizeSingle();\n    }\n  }\n\n  // Forcibly read a lookahead token.\n  inline void LookAhead() {\n    DCHECK(!HasLookAhead());\n    next_ = tokenizer_.TokenizeSingle();\n    has_look_ahead_ = true;\n  }\n\n  inline bool HasLookAhead() const { return has_look_ahead_; }\n\n  inline const CSSParserToken& Peek() {\n    EnsureLookAhead();\n    return next_;\n  }\n\n  inline const CSSParserToken& UncheckedPeek() const {\n    DCHECK(HasLookAhead());\n    return next_;\n  }\n\n  inline const CSSParserToken& Consume() {\n    EnsureLookAhead();\n    return UncheckedConsume();\n  }\n\n  const CSSParserToken& UncheckedConsume() {\n    DCHECK(HasLookAhead());\n    DCHECK(next_.GetBlockType() != CSSParserToken::kBlockStart);\n    DCHECK(next_.GetBlockType() != CSSParserToken::kBlockEnd);\n    has_look_ahead_ = false;\n    offset_ = tokenizer_.Offset();\n    return next_;\n  }\n\n  inline bool AtEnd() {\n    EnsureLookAhead();\n    return UncheckedAtEnd();\n  }\n\n  inline bool UncheckedAtEnd() const {\n    DCHECK(HasLookAhead());\n    return (boundaries_ & FlagForTokenType(next_.GetType())) ||\n           next_.GetBlockType() == CSSParserToken::kBlockEnd;\n  }\n\n  // Get the index of the character in the original string to be consumed next.\n  size_t Offset() const { return offset_; }\n\n  // Get the index of the starting character of the look-ahead token.\n  size_t LookAheadOffset() const {\n    DCHECK(HasLookAhead());\n    return tokenizer_.PreviousOffset();\n  }\n\n  // Returns a view on a range of characters in the original string.\n  std::u16string StringRangeAt(size_t start, size_t length) const;\n\n  void ConsumeWhitespace();\n  CSSParserToken ConsumeIncludingWhitespace();\n  void UncheckedConsumeComponentValue();\n\n  // Either consumes a comment token and returns true, or peeks at the next\n  // token and return false.\n  bool ConsumeCommentOrNothing();\n\n  // Invalidates any ranges created by previous calls to\n  // ConsumeUntilPeekedTypeIs()\n  template <CSSParserTokenType... Types>\n  CSSParserTokenRange ConsumeUntilPeekedTypeIs() {\n    EnsureLookAhead();\n\n    buffer_.clear();\n    while (!UncheckedAtEnd() &&\n           !detail::IsTokenTypeOneOf<Types...>(UncheckedPeek().GetType())) {\n      ConsumeTokenOrBlockAndAppendToBuffer();\n    }\n\n    return CSSParserTokenRange(buffer_);\n  }\n\n private:\n  inline void ConsumeTokenOrBlockAndAppendToBuffer() {\n    // Have to use internal consume/peek in here because they can read past\n    // start/end of blocks\n    unsigned nesting_level = 0;\n    do {\n      const CSSParserToken& token = UncheckedConsumeInternal();\n      buffer_.push_back(token);\n\n      if (token.GetBlockType() == CSSParserToken::kBlockStart)\n        nesting_level++;\n      else if (token.GetBlockType() == CSSParserToken::kBlockEnd)\n        nesting_level--;\n    } while (!PeekInternal().IsEOF() && nesting_level);\n  }\n\n  const CSSParserToken& PeekInternal() {\n    EnsureLookAhead();\n    return UncheckedPeekInternal();\n  }\n\n  const CSSParserToken& UncheckedPeekInternal() const {\n    DCHECK(HasLookAhead());\n    return next_;\n  }\n\n  const CSSParserToken& ConsumeInternal() {\n    EnsureLookAhead();\n    return UncheckedConsumeInternal();\n  }\n\n  const CSSParserToken& UncheckedConsumeInternal() {\n    DCHECK(HasLookAhead());\n    has_look_ahead_ = false;\n    offset_ = tokenizer_.Offset();\n    return next_;\n  }\n\n  void UncheckedSkipToEndOfBlock();\n\n  std::vector<CSSParserToken> buffer_;\n  CSSTokenizer& tokenizer_;\n  CSSParserToken next_;\n  size_t offset_ = 0;\n  bool has_look_ahead_ = false;\n  uint64_t boundaries_ = FlagForTokenType(kEOFToken);\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_PARSER_CSS_PARSER_TOKEN_STREAM_H_\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_parser_token_stream_test.cc",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/parser/css_parser_token_stream.h\"\n\n#include <string>\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace css {\n\nnamespace {\n\nTEST(CSSParserTokenStreamTest, EmptyStream) {\n  CSSTokenizer tokenizer(\"\");\n  CSSParserTokenStream stream(tokenizer);\n  EXPECT_TRUE(stream.Consume().IsEOF());\n  EXPECT_TRUE(stream.Peek().IsEOF());\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, PeekThenConsume) {\n  CSSTokenizer tokenizer(\"A\");  // kIdent\n  CSSParserTokenStream stream(tokenizer);\n  EXPECT_EQ(kIdentToken, stream.Peek().GetType());\n  EXPECT_EQ(kIdentToken, stream.Consume().GetType());\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, ConsumeThenPeek) {\n  CSSTokenizer tokenizer(\"A\");  // kIdent\n  CSSParserTokenStream stream(tokenizer);\n  EXPECT_EQ(kIdentToken, stream.Consume().GetType());\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, ConsumeMultipleTokens) {\n  CSSTokenizer tokenizer(\"A 1\");  // kIdent kWhitespace kNumber\n  CSSParserTokenStream stream(tokenizer);\n  EXPECT_EQ(kIdentToken, stream.Consume().GetType());\n  EXPECT_EQ(kWhitespaceToken, stream.Consume().GetType());\n  EXPECT_EQ(kNumberToken, stream.Consume().GetType());\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, UncheckedPeekAndConsumeAfterPeek) {\n  CSSTokenizer tokenizer(\"A\");  // kIdent\n  CSSParserTokenStream stream(tokenizer);\n  EXPECT_EQ(kIdentToken, stream.Peek().GetType());\n  EXPECT_EQ(kIdentToken, stream.UncheckedPeek().GetType());\n  EXPECT_EQ(kIdentToken, stream.UncheckedConsume().GetType());\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, UncheckedPeekAndConsumeAfterAtEnd) {\n  CSSTokenizer tokenizer(\"A\");  // kIdent\n  CSSParserTokenStream stream(tokenizer);\n  EXPECT_FALSE(stream.AtEnd());\n  EXPECT_EQ(kIdentToken, stream.UncheckedPeek().GetType());\n  EXPECT_EQ(kIdentToken, stream.UncheckedConsume().GetType());\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, UncheckedConsumeComponentValue) {\n  CSSTokenizer tokenizer(\"A{1}{2{3}}B\");\n  CSSParserTokenStream stream(tokenizer);\n\n  EXPECT_EQ(kIdentToken, stream.Peek().GetType());\n  stream.UncheckedConsumeComponentValue();\n  EXPECT_EQ(kLeftBraceToken, stream.Peek().GetType());\n  stream.UncheckedConsumeComponentValue();\n  EXPECT_EQ(kLeftBraceToken, stream.Peek().GetType());\n  stream.UncheckedConsumeComponentValue();\n  EXPECT_EQ(kIdentToken, stream.Peek().GetType());\n  stream.UncheckedConsumeComponentValue();\n\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, ConsumeWhitespace) {\n  CSSTokenizer tokenizer(\" \\t\\n\");  // kWhitespace\n  CSSParserTokenStream stream(tokenizer);\n\n  EXPECT_EQ(kWhitespaceToken, stream.Consume().GetType());\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, ConsumeIncludingWhitespace) {\n  CSSTokenizer tokenizer(\"A \\t\\n\");  // kIdent kWhitespace\n  CSSParserTokenStream stream(tokenizer);\n\n  EXPECT_EQ(kIdentToken, stream.ConsumeIncludingWhitespace().GetType());\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, RangesDoNotGetInvalidatedWhenConsuming) {\n  std::string s;\n  s.append(\"1 \");\n  for (int i = 0; i < 100; i++) s.append(\"A \");\n\n  CSSTokenizer tokenizer(s);\n  CSSParserTokenStream stream(tokenizer);\n\n  // Consume a single token range.\n  auto range = stream.ConsumeUntilPeekedTypeIs<kIdentToken>();\n\n  EXPECT_EQ(kNumberToken, range.Peek().GetType());\n\n  // Consume remaining tokens to try to invalidate the range.\n  while (!stream.AtEnd()) stream.ConsumeIncludingWhitespace();\n\n  EXPECT_EQ(kNumberToken, range.ConsumeIncludingWhitespace().GetType());\n  EXPECT_TRUE(range.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, BlockErrorRecoveryConsumesRestOfBlock) {\n  CSSTokenizer tokenizer(\"{B }1\");\n  CSSParserTokenStream stream(tokenizer);\n\n  {\n    CSSParserTokenStream::BlockGuard guard(stream);\n    EXPECT_EQ(kIdentToken, stream.Consume().GetType());\n    EXPECT_FALSE(stream.AtEnd());\n  }  // calls destructor\n\n  EXPECT_EQ(kNumberToken, stream.Consume().GetType());\n}\n\nTEST(CSSParserTokenStreamTest, BlockErrorRecoveryOnSuccess) {\n  CSSTokenizer tokenizer(\"{B }1\");\n  CSSParserTokenStream stream(tokenizer);\n\n  {\n    CSSParserTokenStream::BlockGuard guard(stream);\n    EXPECT_EQ(kIdentToken, stream.Consume().GetType());\n    EXPECT_EQ(kWhitespaceToken, stream.Consume().GetType());\n    EXPECT_TRUE(stream.AtEnd());\n  }  // calls destructor\n\n  EXPECT_EQ(kNumberToken, stream.Consume().GetType());\n}\n\nTEST(CSSParserTokenStreamTest, BlockErrorRecoveryConsumeComponentValue) {\n  CSSTokenizer tokenizer(\"{{B} C}1\");\n  CSSParserTokenStream stream(tokenizer);\n\n  {\n    CSSParserTokenStream::BlockGuard guard(stream);\n    stream.EnsureLookAhead();\n    stream.UncheckedConsumeComponentValue();\n  }  // calls destructor\n\n  EXPECT_EQ(kNumberToken, stream.Consume().GetType());\n}\n\nTEST(CSSParserTokenStreamTest, OffsetAfterPeek) {\n  CSSTokenizer tokenizer(\"ABC\");\n  CSSParserTokenStream stream(tokenizer);\n\n  EXPECT_EQ(0U, stream.Offset());\n  EXPECT_EQ(kIdentToken, stream.Peek().GetType());\n  EXPECT_EQ(0U, stream.Offset());\n}\n\nTEST(CSSParserTokenStreamTest, OffsetAfterConsumes) {\n  CSSTokenizer tokenizer(\"ABC 1 {23 }\");\n  CSSParserTokenStream stream(tokenizer);\n\n  EXPECT_EQ(0U, stream.Offset());\n  EXPECT_EQ(kIdentToken, stream.Consume().GetType());\n  EXPECT_EQ(3U, stream.Offset());\n  EXPECT_EQ(kWhitespaceToken, stream.Consume().GetType());\n  EXPECT_EQ(4U, stream.Offset());\n  EXPECT_EQ(kNumberToken, stream.ConsumeIncludingWhitespace().GetType());\n  EXPECT_EQ(6U, stream.Offset());\n  stream.EnsureLookAhead();\n  stream.UncheckedConsumeComponentValue();\n  EXPECT_EQ(11U, stream.Offset());\n}\n\nTEST(CSSParserTokenStreamTest, LookAheadOffset) {\n  CSSTokenizer tokenizer(\"ABC/* *//* */1\");\n  CSSParserTokenStream stream(tokenizer);\n\n  stream.EnsureLookAhead();\n  EXPECT_EQ(0U, stream.Offset());\n  EXPECT_EQ(0U, stream.LookAheadOffset());\n  EXPECT_EQ(kIdentToken, stream.Consume().GetType());\n\n  stream.EnsureLookAhead();\n  EXPECT_EQ(3U, stream.Offset());\n  EXPECT_EQ(13U, stream.LookAheadOffset());\n}\n\nTEST(CSSParserTokenStreamTest, ConsumeUntilPeekedTypeIsEmpty) {\n  CSSTokenizer tokenizer(\"{23 }\");\n  CSSParserTokenStream stream(tokenizer);\n\n  auto range = stream.ConsumeUntilPeekedTypeIs<>();\n  EXPECT_TRUE(stream.AtEnd());\n\n  EXPECT_EQ(kLeftBraceToken, range.Consume().GetType());\n  EXPECT_EQ(kNumberToken, range.Consume().GetType());\n  EXPECT_EQ(kWhitespaceToken, range.Consume().GetType());\n  EXPECT_EQ(kRightBraceToken, range.Consume().GetType());\n  EXPECT_TRUE(range.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, Boundary) {\n  CSSTokenizer tokenizer(\"foo:red;bar:blue;asdf\");\n  CSSParserTokenStream stream(tokenizer);\n\n  {\n    CSSParserTokenStream::Boundary boundary(stream, kSemicolonToken);\n    CSSParserTokenRange range = stream.ConsumeUntilPeekedTypeIs<>();\n    EXPECT_EQ(u\"foo\", range.Consume().Value());\n    EXPECT_EQ(kColonToken, range.Consume().GetType());\n    EXPECT_EQ(u\"red\", range.Consume().Value());\n    EXPECT_TRUE(stream.AtEnd());\n  }\n\n  EXPECT_FALSE(stream.AtEnd());\n  EXPECT_EQ(kSemicolonToken, stream.Consume().GetType());\n\n  {\n    CSSParserTokenStream::Boundary boundary(stream, kSemicolonToken);\n    CSSParserTokenRange range = stream.ConsumeUntilPeekedTypeIs<>();\n    EXPECT_EQ(u\"bar\", range.Consume().Value());\n    EXPECT_EQ(kColonToken, range.Consume().GetType());\n    EXPECT_EQ(u\"blue\", range.Consume().Value());\n    EXPECT_TRUE(stream.AtEnd());\n  }\n\n  EXPECT_FALSE(stream.AtEnd());\n  EXPECT_EQ(kSemicolonToken, stream.Consume().GetType());\n\n  EXPECT_EQ(u\"asdf\", stream.Consume().Value());\n  EXPECT_TRUE(stream.AtEnd());\n}\n\nTEST(CSSParserTokenStreamTest, MultipleBoundaries) {\n  CSSTokenizer tokenizer(\"a:b,c;d:,;e\");\n  CSSParserTokenStream stream(tokenizer);\n\n  {\n    CSSParserTokenStream::Boundary boundary_semicolon(stream, kSemicolonToken);\n\n    {\n      CSSParserTokenStream::Boundary boundary_comma(stream, kCommaToken);\n\n      {\n        CSSParserTokenStream::Boundary boundary_colon(stream, kColonToken);\n        CSSParserTokenRange range = stream.ConsumeUntilPeekedTypeIs<>();\n        EXPECT_EQ(u\"a\", range.Consume().Value());\n        EXPECT_TRUE(range.AtEnd());\n        EXPECT_TRUE(stream.AtEnd());\n      }\n\n      EXPECT_FALSE(stream.AtEnd());\n      EXPECT_EQ(kColonToken, stream.Consume().GetType());\n\n      CSSParserTokenRange range = stream.ConsumeUntilPeekedTypeIs<>();\n      EXPECT_EQ(u\"b\", range.Consume().Value());\n      EXPECT_TRUE(range.AtEnd());\n      EXPECT_TRUE(stream.AtEnd());\n    }\n\n    EXPECT_FALSE(stream.AtEnd());\n    EXPECT_EQ(kCommaToken, stream.Consume().GetType());\n\n    CSSParserTokenRange range = stream.ConsumeUntilPeekedTypeIs<>();\n    EXPECT_EQ(u\"c\", range.Consume().Value());\n    EXPECT_TRUE(range.AtEnd());\n    EXPECT_TRUE(stream.AtEnd());\n  }\n\n  EXPECT_FALSE(stream.AtEnd());\n  EXPECT_EQ(kSemicolonToken, stream.Consume().GetType());\n\n  CSSParserTokenRange range = stream.ConsumeUntilPeekedTypeIs<>();\n  EXPECT_TRUE(stream.AtEnd());\n\n  EXPECT_EQ(u\"d\", range.Consume().Value());\n  EXPECT_EQ(kColonToken, range.Consume().GetType());\n  EXPECT_EQ(kCommaToken, range.Consume().GetType());\n  EXPECT_EQ(kSemicolonToken, range.Consume().GetType());\n  EXPECT_EQ(u\"e\", range.Consume().Value());\n}\n\nTEST(CSSParserTokenStreamTest, IneffectiveBoundary) {\n  CSSTokenizer tokenizer(\"a:b|\");\n  CSSParserTokenStream stream(tokenizer);\n\n  {\n    CSSParserTokenStream::Boundary boundary_colon(stream, kColonToken);\n\n    {\n      // It's valid to add another boundary, but it has no affect in this\n      // case, since kColonToken appears first.\n      CSSParserTokenStream::Boundary boundary_semicolon(stream,\n                                                        kSemicolonToken);\n\n      CSSParserTokenRange range = stream.ConsumeUntilPeekedTypeIs<>();\n      EXPECT_EQ(u\"a\", range.Consume().Value());\n      EXPECT_TRUE(range.AtEnd());\n\n      EXPECT_EQ(kColonToken, stream.Peek().GetType());\n      EXPECT_TRUE(stream.AtEnd());\n    }\n\n    EXPECT_TRUE(stream.AtEnd());\n  }\n\n  EXPECT_FALSE(stream.AtEnd());\n}\n\n}  // namespace\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_tokenizer.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/parser/css_tokenizer.h\"\n\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/ng/parser/css_parser_idioms.h\"\n\nnamespace lynx {\nnamespace css {\n#include \"core/renderer/css/ng/parser/css_tokenizer_codepoints.cc\"\n}\n}  // namespace lynx\n\nnamespace lynx {\nnamespace css {\nCSSTokenizer::CSSTokenizer(const std::string& string)\n    : CSSTokenizer(ustring_helper::from_string(string)) {}\n\nCSSTokenizer::CSSTokenizer(const std::u16string& string, size_t offset)\n    : input_(string) {\n  // According to the spec, we should perform preprocessing here.\n  // See: https://drafts.csswg.org/css-syntax/#input-preprocessing\n  //\n  // However, we can skip this step since:\n  // * We're using HTML spaces (which accept \\r and \\f as a valid white space)\n  // * Do not count white spaces\n  // * CSSTokenizerInputStream::NextInputChar() replaces NULLs for replacement\n  //   characters\n  input_.Advance(offset);\n}\n\nstd::vector<CSSParserToken> CSSTokenizer::TokenizeToEOF() {\n  // To avoid resizing we err on the side of reserving too much space.\n  // Most strings we tokenize have about 3.5 to 5 characters per token.\n  std::vector<CSSParserToken> tokens;\n  tokens.reserve((input_.length() - Offset()) / 3);\n\n  while (true) {\n    const CSSParserToken token = NextToken();\n    switch (token.GetType()) {\n      case kCommentToken:\n        continue;\n      case kEOFToken:\n        return tokens;\n      default:\n        tokens.push_back(token);\n        break;\n    }\n  }\n}\n\nstd::u16string CSSTokenizer::StringRangeAt(size_t start, size_t length) const {\n  return input_.RangeAt(start, length);\n}\n\nCSSParserToken CSSTokenizer::TokenizeSingle() {\n  while (true) {\n    prev_offset_ = input_.Offset();\n    const CSSParserToken token = NextToken();\n    if (token.GetType() == kCommentToken) continue;\n    return token;\n  }\n}\n\nCSSParserToken CSSTokenizer::TokenizeSingleWithComments() {\n  prev_offset_ = input_.Offset();\n  return NextToken();\n}\n\nsize_t CSSTokenizer::TokenCount() { return token_count_; }\n\nvoid CSSTokenizer::Reconsume(UChar c) { input_.PushBack(c); }\n\nUChar CSSTokenizer::Consume() {\n  UChar current = input_.NextInputChar();\n  input_.Advance();\n  return current;\n}\n\nCSSParserToken CSSTokenizer::WhiteSpace(UChar cc) {\n  input_.AdvanceUntilNonWhitespace();\n  return CSSParserToken(kWhitespaceToken);\n}\n\nCSSParserToken CSSTokenizer::BlockStart(CSSParserTokenType type) {\n  block_stack_.push_back(type);\n  return CSSParserToken(type, CSSParserToken::kBlockStart);\n}\n\nCSSParserToken CSSTokenizer::BlockStart(CSSParserTokenType block_type,\n                                        CSSParserTokenType type,\n                                        const std::u16string& name) {\n  block_stack_.push_back(block_type);\n  return CSSParserToken(type, name, CSSParserToken::kBlockStart);\n}\n\nCSSParserToken CSSTokenizer::BlockEnd(CSSParserTokenType type,\n                                      CSSParserTokenType start_type) {\n  if (!block_stack_.empty() && block_stack_.back() == start_type) {\n    block_stack_.pop_back();\n    return CSSParserToken(type, CSSParserToken::kBlockEnd);\n  }\n  return CSSParserToken(type);\n}\n\nCSSParserToken CSSTokenizer::LeftParenthesis(UChar cc) {\n  return BlockStart(kLeftParenthesisToken);\n}\n\nCSSParserToken CSSTokenizer::RightParenthesis(UChar cc) {\n  return BlockEnd(kRightParenthesisToken, kLeftParenthesisToken);\n}\n\nCSSParserToken CSSTokenizer::LeftBracket(UChar cc) {\n  return BlockStart(kLeftBracketToken);\n}\n\nCSSParserToken CSSTokenizer::RightBracket(UChar cc) {\n  return BlockEnd(kRightBracketToken, kLeftBracketToken);\n}\n\nCSSParserToken CSSTokenizer::LeftBrace(UChar cc) {\n  return BlockStart(kLeftBraceToken);\n}\n\nCSSParserToken CSSTokenizer::RightBrace(UChar cc) {\n  return BlockEnd(kRightBraceToken, kLeftBraceToken);\n}\n\nCSSParserToken CSSTokenizer::PlusOrFullStop(UChar cc) {\n  if (NextCharsAreNumber(cc)) {\n    Reconsume(cc);\n    return ConsumeNumericToken();\n  }\n  return CSSParserToken(kDelimiterToken, cc);\n}\n\nCSSParserToken CSSTokenizer::Asterisk(UChar cc) {\n  DCHECK_EQ(cc, '*');\n  if (ConsumeIfNext('=')) return CSSParserToken(kSubstringMatchToken);\n  return CSSParserToken(kDelimiterToken, '*');\n}\n\nCSSParserToken CSSTokenizer::LessThan(UChar cc) {\n  DCHECK_EQ(cc, '<');\n  if (input_.PeekWithoutReplacement(0) == '!' &&\n      input_.PeekWithoutReplacement(1) == '-' &&\n      input_.PeekWithoutReplacement(2) == '-') {\n    input_.Advance(3);\n    return CSSParserToken(kCDOToken);\n  }\n  return CSSParserToken(kDelimiterToken, '<');\n}\n\nCSSParserToken CSSTokenizer::Comma(UChar cc) {\n  return CSSParserToken(kCommaToken);\n}\n\nCSSParserToken CSSTokenizer::HyphenMinus(UChar cc) {\n  if (NextCharsAreNumber(cc)) {\n    Reconsume(cc);\n    return ConsumeNumericToken();\n  }\n  if (input_.PeekWithoutReplacement(0) == '-' &&\n      input_.PeekWithoutReplacement(1) == '>') {\n    input_.Advance(2);\n    return CSSParserToken(kCDCToken);\n  }\n  if (NextCharsAreIdentifier(cc)) {\n    Reconsume(cc);\n    return ConsumeIdentLikeToken();\n  }\n  return CSSParserToken(kDelimiterToken, cc);\n}\n\nCSSParserToken CSSTokenizer::Solidus(UChar cc) {\n  if (ConsumeIfNext('*')) {\n    // These get ignored, but we need a value to return.\n    ConsumeUntilCommentEndFound();\n    return CSSParserToken(kCommentToken);\n  }\n\n  return CSSParserToken(kDelimiterToken, cc);\n}\n\nCSSParserToken CSSTokenizer::Colon(UChar cc) {\n  return CSSParserToken(kColonToken);\n}\n\nCSSParserToken CSSTokenizer::SemiColon(UChar cc) {\n  return CSSParserToken(kSemicolonToken);\n}\n\nCSSParserToken CSSTokenizer::Hash(UChar cc) {\n  UChar next_char = input_.PeekWithoutReplacement(0);\n  if (IsNameCodePoint(next_char) ||\n      TwoCharsAreValidEscape(next_char, input_.PeekWithoutReplacement(1))) {\n    HashTokenType type =\n        NextCharsAreIdentifier() ? kHashTokenId : kHashTokenUnrestricted;\n    return CSSParserToken(type, ConsumeName());\n  }\n\n  return CSSParserToken(kDelimiterToken, cc);\n}\n\nCSSParserToken CSSTokenizer::CircumflexAccent(UChar cc) {\n  DCHECK_EQ(cc, '^');\n  if (ConsumeIfNext('=')) return CSSParserToken(kPrefixMatchToken);\n  return CSSParserToken(kDelimiterToken, '^');\n}\n\nCSSParserToken CSSTokenizer::DollarSign(UChar cc) {\n  DCHECK_EQ(cc, '$');\n  if (ConsumeIfNext('=')) return CSSParserToken(kSuffixMatchToken);\n  return CSSParserToken(kDelimiterToken, '$');\n}\n\nCSSParserToken CSSTokenizer::VerticalLine(UChar cc) {\n  DCHECK_EQ(cc, '|');\n  if (ConsumeIfNext('=')) return CSSParserToken(kDashMatchToken);\n  if (ConsumeIfNext('|')) return CSSParserToken(kColumnToken);\n  return CSSParserToken(kDelimiterToken, '|');\n}\n\nCSSParserToken CSSTokenizer::Tilde(UChar cc) {\n  DCHECK_EQ(cc, '~');\n  if (ConsumeIfNext('=')) return CSSParserToken(kIncludeMatchToken);\n  return CSSParserToken(kDelimiterToken, '~');\n}\n\nCSSParserToken CSSTokenizer::CommercialAt(UChar cc) {\n  DCHECK_EQ(cc, '@');\n  if (NextCharsAreIdentifier())\n    return CSSParserToken(kAtKeywordToken, ConsumeName());\n  return CSSParserToken(kDelimiterToken, '@');\n}\n\nCSSParserToken CSSTokenizer::ReverseSolidus(UChar cc) {\n  if (TwoCharsAreValidEscape(cc, input_.PeekWithoutReplacement(0))) {\n    Reconsume(cc);\n    return ConsumeIdentLikeToken();\n  }\n  return CSSParserToken(kDelimiterToken, cc);\n}\n\nCSSParserToken CSSTokenizer::AsciiDigit(UChar cc) {\n  Reconsume(cc);\n  return ConsumeNumericToken();\n}\n\nCSSParserToken CSSTokenizer::LetterU(UChar cc) {\n  if (input_.PeekWithoutReplacement(0) == '+' &&\n      (base::IsASCIIHexNumber(input_.PeekWithoutReplacement(1)) ||\n       input_.PeekWithoutReplacement(1) == '?')) {\n    input_.Advance();\n    return ConsumeUnicodeRange();\n  }\n  Reconsume(cc);\n  return ConsumeIdentLikeToken();\n}\n\nCSSParserToken CSSTokenizer::NameStart(UChar cc) {\n  Reconsume(cc);\n  return ConsumeIdentLikeToken();\n}\n\nCSSParserToken CSSTokenizer::StringStart(UChar cc) {\n  return ConsumeStringTokenUntil(cc);\n}\n\nCSSParserToken CSSTokenizer::EndOfFile(UChar cc) {\n  return CSSParserToken(kEOFToken);\n}\n\nCSSParserToken CSSTokenizer::NextToken() {\n  // Unlike the HTMLTokenizer, the CSS Syntax spec is written\n  // as a stateless, (fixed-size) look-ahead tokenizer.\n  // We could move to the stateful model and instead create\n  // states for all the \"next 3 codepoints are X\" cases.\n  // State-machine tokenizers are easier to write to handle\n  // incremental tokenization of partial sources.\n  // However, for now we follow the spec exactly.\n  UChar cc = Consume();\n  CodePoint code_point_func = nullptr;\n\n  if (base::IsASCII(cc)) {\n    DCHECK(cc < codePointsNumber);\n    code_point_func = kCodePoints[cc];\n  } else {\n    code_point_func = &CSSTokenizer::NameStart;\n  }\n\n  ++token_count_;\n  if (code_point_func) return ((this)->*(code_point_func))(cc);\n  return CSSParserToken(kDelimiterToken, cc);\n}\n\n// This method merges the following spec sections for efficiency\n// http://www.w3.org/TR/css3-syntax/#consume-a-number\n// http://www.w3.org/TR/css3-syntax/#convert-a-string-to-a-number\nCSSParserToken CSSTokenizer::ConsumeNumber() {\n  DCHECK(NextCharsAreNumber());\n\n  NumericValueType type = kIntegerValueType;\n  NumericSign sign = kNoSign;\n  unsigned number_length = 0;\n\n  UChar next = input_.PeekWithoutReplacement(0);\n  if (next == '+') {\n    ++number_length;\n    sign = kPlusSign;\n  } else if (next == '-') {\n    ++number_length;\n    sign = kMinusSign;\n  }\n\n  number_length = input_.SkipWhilePredicate<base::IsASCIINumber>(number_length);\n  next = input_.PeekWithoutReplacement(number_length);\n  if (next == '.' &&\n      base::IsASCIINumber(input_.PeekWithoutReplacement(number_length + 1))) {\n    type = kNumberValueType;\n    number_length =\n        input_.SkipWhilePredicate<base::IsASCIINumber>(number_length + 2);\n    next = input_.PeekWithoutReplacement(number_length);\n  }\n\n  if (next == 'E' || next == 'e') {\n    next = input_.PeekWithoutReplacement(number_length + 1);\n    if (base::IsASCIINumber(next)) {\n      type = kNumberValueType;\n      number_length =\n          input_.SkipWhilePredicate<base::IsASCIINumber>(number_length + 1);\n    } else if ((next == '+' || next == '-') &&\n               base::IsASCIINumber(\n                   input_.PeekWithoutReplacement(number_length + 2))) {\n      type = kNumberValueType;\n      number_length =\n          input_.SkipWhilePredicate<base::IsASCIINumber>(number_length + 3);\n    }\n  }\n\n  double value = input_.GetDouble(0, number_length);\n  input_.Advance(number_length);\n\n  return CSSParserToken(kNumberToken, value, type, sign);\n}\n\n// http://www.w3.org/TR/css3-syntax/#consume-a-numeric-token\nCSSParserToken CSSTokenizer::ConsumeNumericToken() {\n  CSSParserToken token = ConsumeNumber();\n  if (NextCharsAreIdentifier())\n    token.ConvertToDimensionWithUnit(ConsumeName());\n  else if (ConsumeIfNext('%'))\n    token.ConvertToPercentage();\n  return token;\n}\n\n// https://drafts.csswg.org/css-syntax/#consume-ident-like-token\nCSSParserToken CSSTokenizer::ConsumeIdentLikeToken() {\n  std::u16string name = ConsumeName();\n  if (ConsumeIfNext('(')) {\n    if (EqualIgnoringASCIICase(name, u\"url\")) {\n      // The spec is slightly different so as to avoid dropping whitespace\n      // tokens, but they wouldn't be used and this is easier.\n      input_.AdvanceUntilNonWhitespace();\n      UChar next = input_.PeekWithoutReplacement(0);\n      if (next != '\"' && next != '\\'') return ConsumeUrlToken();\n    }\n    return BlockStart(kLeftParenthesisToken, kFunctionToken, name);\n  }\n  return CSSParserToken(kIdentToken, name);\n}\n\n// https://drafts.csswg.org/css-syntax/#consume-a-string-token\nCSSParserToken CSSTokenizer::ConsumeStringTokenUntil(UChar ending_code_point) {\n  // Strings without escapes get handled without allocations\n  for (unsigned size = 0;; size++) {\n    UChar cc = input_.PeekWithoutReplacement(size);\n    if (cc == ending_code_point) {\n      unsigned start_offset = input_.Offset();\n      input_.Advance(size + 1);\n      return CSSParserToken(kStringToken, input_.RangeAt(start_offset, size));\n    }\n    if (IsCSSNewLine(cc)) {\n      input_.Advance(size);\n      return CSSParserToken(kBadStringToken);\n    }\n    if (cc == '\\0' || cc == '\\\\') break;\n  }\n\n  std::u16string output;\n  while (true) {\n    UChar cc = Consume();\n    if (cc == ending_code_point || cc == kEndOfFileMarker) {\n      return CSSParserToken(kStringToken, RegisterString(output));\n    }\n    if (IsCSSNewLine(cc)) {\n      Reconsume(cc);\n      return CSSParserToken(kBadStringToken);\n    }\n    if (cc == '\\\\') {\n      if (input_.NextInputChar() == kEndOfFileMarker) continue;\n      if (IsCSSNewLine(input_.PeekWithoutReplacement(0)))\n        ConsumeSingleWhitespaceIfNext();  // This handles \\r\\n for us\n      else\n        output.push_back(ConsumeEscape());\n    } else {\n      output.push_back(cc);\n    }\n  }\n}\n\nCSSParserToken CSSTokenizer::ConsumeUnicodeRange() {\n  DCHECK(base::IsASCIIHexNumber(input_.PeekWithoutReplacement(0)) ||\n         input_.PeekWithoutReplacement(0) == '?');\n  int length_remaining = 6;\n  UChar32 start = 0;\n\n  while (length_remaining &&\n         base::IsASCIIHexNumber(input_.PeekWithoutReplacement(0))) {\n    start = start * 16 + base::ToASCIIHexValue(Consume());\n    --length_remaining;\n  }\n\n  UChar32 end = start;\n  if (length_remaining && ConsumeIfNext('?')) {\n    do {\n      start *= 16;\n      end = end * 16 + 0xF;\n      --length_remaining;\n    } while (length_remaining && ConsumeIfNext('?'));\n  } else if (input_.PeekWithoutReplacement(0) == '-' &&\n             base::IsASCIIHexNumber(input_.PeekWithoutReplacement(1))) {\n    input_.Advance();\n    length_remaining = 6;\n    end = 0;\n    do {\n      end = end * 16 + base::ToASCIIHexValue(Consume());\n      --length_remaining;\n    } while (length_remaining &&\n             base::IsASCIIHexNumber(input_.PeekWithoutReplacement(0)));\n  }\n\n  return CSSParserToken(kUnicodeRangeToken, start, end);\n}\n\n// https://drafts.csswg.org/css-syntax/#non-printable-code-point\nstatic bool IsNonPrintableCodePoint(UChar cc) {\n  return (cc >= '\\0' && cc <= '\\x8') || cc == '\\xb' ||\n         (cc >= '\\xe' && cc <= '\\x1f') || cc == '\\x7f';\n}\n\n// https://drafts.csswg.org/css-syntax/#consume-url-token\nCSSParserToken CSSTokenizer::ConsumeUrlToken() {\n  input_.AdvanceUntilNonWhitespace();\n\n  // URL tokens without escapes get handled without allocations\n  for (unsigned size = 0;; size++) {\n    UChar cc = input_.PeekWithoutReplacement(size);\n    if (cc == ')') {\n      unsigned start_offset = input_.Offset();\n      input_.Advance(size + 1);\n      return CSSParserToken(kUrlToken, input_.RangeAt(start_offset, size));\n    }\n    if (cc <= ' ' || cc == '\\\\' || cc == '\"' || cc == '\\'' || cc == '(' ||\n        cc == '\\x7f')\n      break;\n  }\n\n  std::u16string result;\n  while (true) {\n    UChar cc = Consume();\n    if (cc == ')' || cc == kEndOfFileMarker)\n      return CSSParserToken(kUrlToken, RegisterString(result));\n\n    if (base::IsHTMLSpace(cc)) {\n      input_.AdvanceUntilNonWhitespace();\n      if (ConsumeIfNext(')') || input_.NextInputChar() == kEndOfFileMarker) {\n        return CSSParserToken(kUrlToken, RegisterString(result));\n      }\n      break;\n    }\n\n    if (cc == '\"' || cc == '\\'' || cc == '(' || IsNonPrintableCodePoint(cc))\n      break;\n\n    if (cc == '\\\\') {\n      if (TwoCharsAreValidEscape(cc, input_.PeekWithoutReplacement(0))) {\n        result.push_back(ConsumeEscape());\n        continue;\n      }\n      break;\n    }\n\n    result.push_back(cc);\n  }\n\n  ConsumeBadUrlRemnants();\n  return CSSParserToken(kBadUrlToken);\n}\n\n// https://drafts.csswg.org/css-syntax/#consume-the-remnants-of-a-bad-url\nvoid CSSTokenizer::ConsumeBadUrlRemnants() {\n  while (true) {\n    UChar cc = Consume();\n    if (cc == ')' || cc == kEndOfFileMarker) return;\n    if (TwoCharsAreValidEscape(cc, input_.PeekWithoutReplacement(0)))\n      ConsumeEscape();\n  }\n}\n\nvoid CSSTokenizer::ConsumeSingleWhitespaceIfNext() {\n  lynx::css::ConsumeSingleWhitespaceIfNext(input_);\n}\n\nvoid CSSTokenizer::ConsumeUntilCommentEndFound() {\n  UChar c = Consume();\n  while (true) {\n    if (c == kEndOfFileMarker) return;\n    if (c != '*') {\n      c = Consume();\n      continue;\n    }\n    c = Consume();\n    if (c == '/') return;\n  }\n}\n\nbool CSSTokenizer::ConsumeIfNext(UChar character) {\n  // Since we're not doing replacement we can't tell the difference from\n  // a NUL in the middle and the kEndOfFileMarker, so character must not be\n  // NUL.\n  DCHECK(character);\n  if (input_.PeekWithoutReplacement(0) == character) {\n    input_.Advance();\n    return true;\n  }\n  return false;\n}\n\n// http://www.w3.org/TR/css3-syntax/#consume-a-name\nstd::u16string CSSTokenizer::ConsumeName() {\n  // Names without escapes get handled without allocations\n  for (unsigned size = 0;; ++size) {\n    UChar cc = input_.PeekWithoutReplacement(size);\n    if (IsNameCodePoint(cc)) continue;\n    // peekWithoutReplacement will return NUL when we hit the end of the\n    // input. In that case we want to still use the rangeAt() fast path\n    // below.\n    if (cc == '\\0' && input_.Offset() + size < input_.length()) break;\n    if (cc == '\\\\') break;\n    unsigned start_offset = input_.Offset();\n    input_.Advance(size);\n    return input_.RangeAt(start_offset, size);\n  }\n\n  return RegisterString(lynx::css::ConsumeName(input_));\n}\n\n// https://drafts.csswg.org/css-syntax/#consume-an-escaped-code-point\nUChar32 CSSTokenizer::ConsumeEscape() {\n  return lynx::css::ConsumeEscape(input_);\n}\n\nbool CSSTokenizer::NextTwoCharsAreValidEscape() {\n  return TwoCharsAreValidEscape(input_.PeekWithoutReplacement(0),\n                                input_.PeekWithoutReplacement(1));\n}\n\n// http://www.w3.org/TR/css3-syntax/#starts-with-a-number\nbool CSSTokenizer::NextCharsAreNumber(UChar first) {\n  UChar second = input_.PeekWithoutReplacement(0);\n  if (base::IsASCIINumber(first)) return true;\n  if (first == '+' || first == '-')\n    return ((base::IsASCIINumber(second)) ||\n            (second == '.' &&\n             base::IsASCIINumber(input_.PeekWithoutReplacement(1))));\n  if (first == '.') return (base::IsASCIINumber(second));\n  return false;\n}\n\nbool CSSTokenizer::NextCharsAreNumber() {\n  UChar first = Consume();\n  bool are_number = NextCharsAreNumber(first);\n  Reconsume(first);\n  return are_number;\n}\n\n// https://drafts.csswg.org/css-syntax/#would-start-an-identifier\nbool CSSTokenizer::NextCharsAreIdentifier(UChar first) {\n  return lynx::css::NextCharsAreIdentifier(first, input_);\n}\n\nbool CSSTokenizer::NextCharsAreIdentifier() {\n  UChar first = Consume();\n  bool are_identifier = NextCharsAreIdentifier(first);\n  Reconsume(first);\n  return are_identifier;\n}\n\nconst std::u16string& CSSTokenizer::RegisterString(\n    const std::u16string& string) {\n  string_pool_.push_back(string);\n  return string;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_tokenizer.h",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_RENDERER_CSS_NG_PARSER_CSS_TOKENIZER_H_\n#define CORE_RENDERER_CSS_NG_PARSER_CSS_TOKENIZER_H_\n\n#include <climits>\n#include <string>\n#include <vector>\n\n#include \"core/renderer/css/ng/parser/css_parser_token.h\"\n#include \"core/renderer/css/ng/parser/css_tokenizer_input_stream.h\"\n\nnamespace lynx {\nnamespace css {\n\nclass CSSTokenizerInputStream;\n\nclass CSSTokenizer {\n public:\n  CSSTokenizer(const std::u16string&, size_t offset = 0);\n  CSSTokenizer(const std::string&);\n  CSSTokenizer(const CSSTokenizer&) = delete;\n  CSSTokenizer& operator=(const CSSTokenizer&) = delete;\n\n  std::vector<CSSParserToken> TokenizeToEOF();\n  size_t TokenCount();\n\n  size_t Offset() const { return input_.Offset(); }\n  size_t PreviousOffset() const { return prev_offset_; }\n  std::u16string StringRangeAt(size_t start, size_t length) const;\n\n private:\n  CSSParserToken TokenizeSingle();\n  CSSParserToken TokenizeSingleWithComments();\n\n  CSSParserToken NextToken();\n\n  UChar Consume();\n  void Reconsume(UChar);\n\n  CSSParserToken ConsumeNumericToken();\n  CSSParserToken ConsumeIdentLikeToken();\n  CSSParserToken ConsumeNumber();\n  CSSParserToken ConsumeStringTokenUntil(UChar);\n  CSSParserToken ConsumeUnicodeRange();\n  CSSParserToken ConsumeUrlToken();\n\n  void ConsumeBadUrlRemnants();\n  void ConsumeSingleWhitespaceIfNext();\n  void ConsumeUntilCommentEndFound();\n\n  bool ConsumeIfNext(UChar);\n  std::u16string ConsumeName();\n  UChar32 ConsumeEscape();\n\n  bool NextTwoCharsAreValidEscape();\n  bool NextCharsAreNumber(UChar);\n  bool NextCharsAreNumber();\n  bool NextCharsAreIdentifier(UChar);\n  bool NextCharsAreIdentifier();\n\n  CSSParserToken BlockStart(CSSParserTokenType);\n  CSSParserToken BlockStart(CSSParserTokenType block_type, CSSParserTokenType,\n                            const std::u16string&);\n  CSSParserToken BlockEnd(CSSParserTokenType, CSSParserTokenType start_type);\n\n  CSSParserToken WhiteSpace(UChar);\n  CSSParserToken LeftParenthesis(UChar);\n  CSSParserToken RightParenthesis(UChar);\n  CSSParserToken LeftBracket(UChar);\n  CSSParserToken RightBracket(UChar);\n  CSSParserToken LeftBrace(UChar);\n  CSSParserToken RightBrace(UChar);\n  CSSParserToken PlusOrFullStop(UChar);\n  CSSParserToken Comma(UChar);\n  CSSParserToken HyphenMinus(UChar);\n  CSSParserToken Asterisk(UChar);\n  CSSParserToken LessThan(UChar);\n  CSSParserToken Solidus(UChar);\n  CSSParserToken Colon(UChar);\n  CSSParserToken SemiColon(UChar);\n  CSSParserToken Hash(UChar);\n  CSSParserToken CircumflexAccent(UChar);\n  CSSParserToken DollarSign(UChar);\n  CSSParserToken VerticalLine(UChar);\n  CSSParserToken Tilde(UChar);\n  CSSParserToken CommercialAt(UChar);\n  CSSParserToken ReverseSolidus(UChar);\n  CSSParserToken AsciiDigit(UChar);\n  CSSParserToken LetterU(UChar);\n  CSSParserToken NameStart(UChar);\n  CSSParserToken StringStart(UChar);\n  CSSParserToken EndOfFile(UChar);\n\n  const std::u16string& RegisterString(const std::u16string&);\n\n  using CodePoint = CSSParserToken (CSSTokenizer::*)(UChar);\n  static const CodePoint kCodePoints[];\n\n  CSSTokenizerInputStream input_;\n  std::vector<CSSParserTokenType> block_stack_;\n\n  // We only allocate strings when escapes are used.\n  std::vector<std::u16string> string_pool_;\n\n  friend class CSSParserTokenStream;\n\n  size_t prev_offset_ = 0;\n  size_t token_count_ = 0;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_PARSER_CSS_TOKENIZER_H_\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_tokenizer_codepoints.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n// Auto-generated by make_css_tokenizer_codepoints.py\n\nconst CSSTokenizer::CodePoint CSSTokenizer::kCodePoints[128] = {\n    &CSSTokenizer::EndOfFile,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    &CSSTokenizer::WhiteSpace,\n    &CSSTokenizer::WhiteSpace,\n    0,\n    &CSSTokenizer::WhiteSpace,\n    &CSSTokenizer::WhiteSpace,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    &CSSTokenizer::WhiteSpace,\n    0,\n    &CSSTokenizer::StringStart,\n    &CSSTokenizer::Hash,\n    &CSSTokenizer::DollarSign,\n    0,\n    0,\n    &CSSTokenizer::StringStart,\n    &CSSTokenizer::LeftParenthesis,\n    &CSSTokenizer::RightParenthesis,\n    &CSSTokenizer::Asterisk,\n    &CSSTokenizer::PlusOrFullStop,\n    &CSSTokenizer::Comma,\n    &CSSTokenizer::HyphenMinus,\n    &CSSTokenizer::PlusOrFullStop,\n    &CSSTokenizer::Solidus,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::AsciiDigit,\n    &CSSTokenizer::Colon,\n    &CSSTokenizer::SemiColon,\n    &CSSTokenizer::LessThan,\n    0,\n    0,\n    0,\n    &CSSTokenizer::CommercialAt,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::LetterU,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::LeftBracket,\n    &CSSTokenizer::ReverseSolidus,\n    &CSSTokenizer::RightBracket,\n    &CSSTokenizer::CircumflexAccent,\n    &CSSTokenizer::NameStart,\n    0,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::LetterU,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::NameStart,\n    &CSSTokenizer::LeftBrace,\n    &CSSTokenizer::VerticalLine,\n    &CSSTokenizer::RightBrace,\n    &CSSTokenizer::Tilde,\n    0,\n};\nconst unsigned codePointsNumber = 128;\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_tokenizer_input_stream.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/parser/css_tokenizer_input_stream.h\"\n\n#include \"core/renderer/css/ng/parser/string_to_number.h\"\n\nnamespace lynx {\nnamespace css {\n\nCSSTokenizerInputStream::CSSTokenizerInputStream(const std::u16string& input)\n    : offset_(0), string_length_(input.length()), string_(input) {}\n\nvoid CSSTokenizerInputStream::AdvanceUntilNonWhitespace() {\n  // Using HTML space here rather than CSS space since we don't do preprocessing\n  while (offset_ < string_length_ && base::IsHTMLSpace(string_[offset_]))\n    ++offset_;\n}\n\ndouble CSSTokenizerInputStream::GetDouble(unsigned start, unsigned end) const {\n  DCHECK(start <= end && ((offset_ + end) <= string_length_));\n  bool is_result_ok = false;\n  double result = 0.0;\n  if (start < end) {\n    result = CharactersToDouble(string_.data() + offset_ + start, end - start,\n                                &is_result_ok);\n  }\n  // FIXME: It looks like callers ensure we have a valid number\n  return is_result_ok ? result : 0.0;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_tokenizer_input_stream.h",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_PARSER_CSS_TOKENIZER_INPUT_STREAM_H_\n#define CORE_RENDERER_CSS_NG_PARSER_CSS_TOKENIZER_INPUT_STREAM_H_\n\n#include <algorithm>\n#include <string>\n#include <vector>\n\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n\nnamespace lynx {\nnamespace css {\n\nclass CSSTokenizerInputStream {\n public:\n  explicit CSSTokenizerInputStream(const std::u16string& input);\n  CSSTokenizerInputStream(const CSSTokenizerInputStream&) = delete;\n  CSSTokenizerInputStream& operator=(const CSSTokenizerInputStream&) = delete;\n\n  // Gets the char in the stream replacing NUL characters with a unicode\n  // replacement character. Will return (NUL) kEndOfFileMarker when at the\n  // end of the stream.\n  UChar NextInputChar() const {\n    if (offset_ >= string_length_) return '\\0';\n    UChar result = string_[offset_];\n    return result ? result : 0xFFFD;\n  }\n\n  // Gets the char at lookaheadOffset from the current stream position. Will\n  // return NUL (kEndOfFileMarker) if the stream position is at the end.\n  // NOTE: This may *also* return NUL if there's one in the input! Never\n  // compare the return value to '\\0'.\n  UChar PeekWithoutReplacement(unsigned lookahead_offset) const {\n    if ((offset_ + lookahead_offset) >= string_length_) return '\\0';\n    return string_[offset_ + lookahead_offset];\n  }\n\n  void Advance(unsigned offset = 1) { offset_ += offset; }\n  void PushBack(UChar cc) {\n    --offset_;\n    // DCHECK(NextInputChar() == cc);\n  }\n\n  double GetDouble(unsigned start, unsigned end) const;\n\n  template <bool characterPredicate(UChar)>\n  unsigned SkipWhilePredicate(unsigned offset) {\n    while ((offset_ + offset) < string_length_ &&\n           characterPredicate(string_[offset_ + offset]))\n      ++offset;\n    return offset;\n  }\n\n  void AdvanceUntilNonWhitespace();\n\n  size_t length() const { return string_length_; }\n  size_t Offset() const { return std::min(offset_, string_length_); }\n\n  std::u16string RangeAt(unsigned start, unsigned length) const {\n    DCHECK(start + length <= string_length_);\n    return string_.substr(start, length);\n  }\n\n private:\n  size_t offset_;\n  const size_t string_length_;\n  const std::u16string string_;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_PARSER_CSS_TOKENIZER_INPUT_STREAM_H_\n"
  },
  {
    "path": "core/renderer/css/ng/parser/css_tokenizer_test.cc",
    "content": "// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/parser/css_tokenizer.h\"\n\n#include <string>\n#include <vector>\n\n#include \"core/renderer/css/ng/parser/css_parser_token_range.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace css {\n\n// This let's us see the line numbers of failing tests\n#define TEST_TOKENS(string, ...) \\\n  { TestTokens(string, __VA_ARGS__); }\n\nvoid CompareTokens(const CSSParserToken& expected,\n                   const CSSParserToken& actual) {\n  ASSERT_EQ(expected.GetType(), actual.GetType());\n  switch (expected.GetType()) {\n    case kDelimiterToken:\n      ASSERT_EQ(expected.Delimiter(), actual.Delimiter());\n      break;\n    case kIdentToken:\n    case kFunctionToken:\n    case kStringToken:\n    case kUrlToken:\n      ASSERT_EQ(expected.Value(), actual.Value());\n      break;\n    case kDimensionToken:\n      ASSERT_EQ(expected.Value(), actual.Value());\n      ASSERT_EQ(expected.GetNumericValueType(), actual.GetNumericValueType());\n      ASSERT_DOUBLE_EQ(expected.NumericValue(), actual.NumericValue());\n      break;\n    case kNumberToken:\n      ASSERT_EQ(expected.GetNumericSign(), actual.GetNumericSign());\n      [[fallthrough]];\n    case kPercentageToken:\n      ASSERT_EQ(expected.GetNumericValueType(), actual.GetNumericValueType());\n      if (expected.NumericValue() != actual.NumericValue()) {\n        ASSERT_EQ(expected.GetNumericValueType(), actual.GetNumericValueType());\n      }\n      ASSERT_DOUBLE_EQ(expected.NumericValue(), actual.NumericValue());\n      break;\n    case kUnicodeRangeToken:\n      ASSERT_EQ(expected.UnicodeRangeStart(), actual.UnicodeRangeStart());\n      ASSERT_EQ(expected.UnicodeRangeEnd(), actual.UnicodeRangeEnd());\n      break;\n    case kHashToken:\n      ASSERT_EQ(expected.Value(), actual.Value());\n      ASSERT_EQ(expected.GetHashTokenType(), actual.GetHashTokenType());\n      break;\n    default:\n      break;\n  }\n}\n\nvoid TestTokens(const std::u16string& string, const CSSParserToken& token1,\n                const CSSParserToken& token2 = CSSParserToken(kEOFToken),\n                const CSSParserToken& token3 = CSSParserToken(kEOFToken)) {\n  std::vector<CSSParserToken> expected_tokens;\n  expected_tokens.push_back(token1);\n  if (token2.GetType() != kEOFToken) {\n    expected_tokens.push_back(token2);\n    if (token3.GetType() != kEOFToken) expected_tokens.push_back(token3);\n  }\n\n  CSSParserTokenRange expected(expected_tokens);\n\n  CSSTokenizer tokenizer(string);\n  const auto tokens = tokenizer.TokenizeToEOF();\n  CSSParserTokenRange actual(tokens);\n\n  // Just check that serialization doesn't hit any asserts\n  actual.Serialize();\n\n  while (!expected.AtEnd() || !actual.AtEnd())\n    CompareTokens(expected.Consume(), actual.Consume());\n}\nvoid TestTokens(const std::string& string, const CSSParserToken& token1,\n                const CSSParserToken& token2 = CSSParserToken(kEOFToken),\n                const CSSParserToken& token3 = CSSParserToken(kEOFToken)) {\n  TestTokens(ustring_helper::from_string(string), token1, token2, token3);\n}\n\nstatic CSSParserToken Ident(const std::string& string) {\n  return CSSParserToken(kIdentToken, ustring_helper::from_string(string));\n}\n\nstatic CSSParserToken Ident(const std::u16string& string) {\n  return CSSParserToken(kIdentToken, string);\n}\n\nstatic CSSParserToken AtKeyword(const std::string& string) {\n  return CSSParserToken(kAtKeywordToken, ustring_helper::from_string(string));\n}\nstatic CSSParserToken GetString(const std::string& string) {\n  return CSSParserToken(kStringToken, ustring_helper::from_string(string));\n}\n\nstatic CSSParserToken GetString(const std::u16string& string) {\n  return CSSParserToken(kStringToken, string);\n}\nstatic CSSParserToken Func(const std::string& string) {\n  return CSSParserToken(kFunctionToken, ustring_helper::from_string(string));\n}\nstatic CSSParserToken Url(const std::string& string) {\n  return CSSParserToken(kUrlToken, ustring_helper::from_string(string));\n}\nstatic CSSParserToken GetHash(const std::string& string, HashTokenType type) {\n  return CSSParserToken(type, ustring_helper::from_string(string));\n}\n\nstatic CSSParserToken Delim(char c) {\n  return CSSParserToken(kDelimiterToken, c);\n}\n\nstatic CSSParserToken UnicodeRng(UChar32 start, UChar32 end) {\n  return CSSParserToken(kUnicodeRangeToken, start, end);\n}\n\nstatic CSSParserToken Number(NumericValueType type, double value,\n                             NumericSign sign) {\n  return CSSParserToken(kNumberToken, value, type, sign);\n}\n\nstatic CSSParserToken Dimension(NumericValueType type, double value,\n                                const std::string& string) {\n  CSSParserToken token = Number(type, value, kNoSign);  // sign ignored\n  token.ConvertToDimensionWithUnit(ustring_helper::from_string(string));\n  return token;\n}\n\nstatic CSSParserToken Percentage(NumericValueType type, double value) {\n  CSSParserToken token = Number(type, value, kNoSign);  // sign ignored\n  token.ConvertToPercentage();\n  return token;\n}\n\n// We need to initialize PartitionAlloc before creating CSSParserTokens\n// because CSSParserToken depends on PartitionAlloc. It is safe to call\n// WTF::Partitions::initialize() multiple times.\n#define DEFINE_TOKEN(name, argument)      \\\n  static CSSParserToken& name() {         \\\n    static CSSParserToken name(argument); \\\n    return name;                          \\\n  }\n\nDEFINE_TOKEN(Whitespace, (kWhitespaceToken))\nDEFINE_TOKEN(Colon, (kColonToken))\nDEFINE_TOKEN(Semicolon, (kSemicolonToken))\nDEFINE_TOKEN(Comma, (kCommaToken))\nDEFINE_TOKEN(IncludeMatch, (kIncludeMatchToken))\nDEFINE_TOKEN(DashMatch, (kDashMatchToken))\nDEFINE_TOKEN(PrefixMatch, (kPrefixMatchToken))\nDEFINE_TOKEN(SuffixMatch, (kSuffixMatchToken))\nDEFINE_TOKEN(SubstringMatch, (kSubstringMatchToken))\nDEFINE_TOKEN(Column, (kColumnToken))\nDEFINE_TOKEN(Cdo, (kCDOToken))\nDEFINE_TOKEN(Cdc, (kCDCToken))\nDEFINE_TOKEN(LeftParenthesis, (kLeftParenthesisToken))\nDEFINE_TOKEN(RightParenthesis, (kRightParenthesisToken))\nDEFINE_TOKEN(LeftBracket, (kLeftBracketToken))\nDEFINE_TOKEN(RightBracket, (kRightBracketToken))\nDEFINE_TOKEN(LeftBrace, (kLeftBraceToken))\nDEFINE_TOKEN(RightBrace, (kRightBraceToken))\nDEFINE_TOKEN(BadString, (kBadStringToken))\nDEFINE_TOKEN(BadUrl, (kBadUrlToken))\n\n#undef DEFINE_TOKEN\n\nstd::u16string FromUChar32(UChar32 c) {\n  std::u16string input;\n  input.push_back(c);\n  return input;\n}\n\nTEST(CSSTokenizerTest, SingleCharacterTokens) {\n  TEST_TOKENS(\"(\", LeftParenthesis());\n  TEST_TOKENS(\")\", RightParenthesis());\n  TEST_TOKENS(\"[\", LeftBracket());\n  TEST_TOKENS(\"]\", RightBracket());\n  TEST_TOKENS(\",\", Comma());\n  TEST_TOKENS(\":\", Colon());\n  TEST_TOKENS(\";\", Semicolon());\n  TEST_TOKENS(\")[\", RightParenthesis(), LeftBracket());\n  TEST_TOKENS(\"[)\", LeftBracket(), RightParenthesis());\n  TEST_TOKENS(\"{}\", LeftBrace(), RightBrace());\n  TEST_TOKENS(\",,\", Comma(), Comma());\n}\n\nTEST(CSSTokenizerTest, MultipleCharacterTokens) {\n  TEST_TOKENS(\"~=\", IncludeMatch());\n  TEST_TOKENS(\"|=\", DashMatch());\n  TEST_TOKENS(\"^=\", PrefixMatch());\n  TEST_TOKENS(\"$=\", SuffixMatch());\n  TEST_TOKENS(\"*=\", SubstringMatch());\n  TEST_TOKENS(\"||\", Column());\n  TEST_TOKENS(\"|||\", Column(), Delim('|'));\n  TEST_TOKENS(\"<!--\", Cdo());\n  TEST_TOKENS(\"<!---\", Cdo(), Delim('-'));\n  TEST_TOKENS(\"-->\", Cdc());\n}\n\nTEST(CSSTokenizerTest, DelimiterToken) {\n  TEST_TOKENS(\"^\", Delim('^'));\n  TEST_TOKENS(\"*\", Delim('*'));\n  TEST_TOKENS(\"%\", Delim('%'));\n  TEST_TOKENS(\"~\", Delim('~'));\n  TEST_TOKENS(\"&\", Delim('&'));\n  TEST_TOKENS(\"|\", Delim('|'));\n  TEST_TOKENS(\"\\x7f\", Delim('\\x7f'));\n  TEST_TOKENS(\"\\1\", Delim('\\x1'));\n  TEST_TOKENS(\"~-\", Delim('~'), Delim('-'));\n  TEST_TOKENS(\"^|\", Delim('^'), Delim('|'));\n  TEST_TOKENS(\"$~\", Delim('$'), Delim('~'));\n  TEST_TOKENS(\"*^\", Delim('*'), Delim('^'));\n}\n\nTEST(CSSTokenizerTest, WhitespaceTokens) {\n  TEST_TOKENS(\"   \", Whitespace());\n  TEST_TOKENS(\"\\n\\rS\", Whitespace(), Ident(\"S\"));\n  TEST_TOKENS(\"   *\", Whitespace(), Delim('*'));\n  TEST_TOKENS(\"\\r\\n\\f\\t2\", Whitespace(), Number(kIntegerValueType, 2, kNoSign));\n}\n\nTEST(CSSTokenizerTest, Escapes) {\n  TEST_TOKENS(\"hel\\\\6Co\", Ident(\"hello\"));\n  TEST_TOKENS(\"\\\\26 B\", Ident(\"&B\"));\n  TEST_TOKENS(\"'hel\\\\6c o'\", GetString(\"hello\"));\n  TEST_TOKENS(\"'spac\\\\65\\r\\ns'\", GetString(\"spaces\"));\n  TEST_TOKENS(\"spac\\\\65\\r\\ns\", Ident(\"spaces\"));\n  TEST_TOKENS(\"spac\\\\65\\n\\rs\", Ident(\"space\"), Whitespace(), Ident(\"s\"));\n  TEST_TOKENS(\"sp\\\\61\\tc\\\\65\\fs\", Ident(\"spaces\"));\n  TEST_TOKENS(\"hel\\\\6c  o\", Ident(\"hell\"), Whitespace(), Ident(\"o\"));\n  TEST_TOKENS(\"test\\\\\\n\", Ident(\"test\"), Delim('\\\\'), Whitespace());\n  TEST_TOKENS(\"test\\\\D799\", Ident(u\"test\" + FromUChar32(0xD799)));\n  TEST_TOKENS(\"\\\\E000\", Ident(FromUChar32(0xE000)));\n  TEST_TOKENS(\"te\\\\s\\\\t\", Ident(\"test\"));\n  TEST_TOKENS(\"spaces\\\\ in\\\\\\tident\", Ident(\"spaces in\\tident\"));\n  TEST_TOKENS(\"\\\\.\\\\,\\\\:\\\\!\", Ident(\".,:!\"));\n  TEST_TOKENS(\"\\\\\\r\", Delim('\\\\'), Whitespace());\n  TEST_TOKENS(\"\\\\\\f\", Delim('\\\\'), Whitespace());\n  TEST_TOKENS(\"\\\\\\r\\n\", Delim('\\\\'), Whitespace());\n  std::u16string replacement = FromUChar32(0xFFFD);\n  TEST_TOKENS(ustring_helper::from_string(\"null\\\\\\0\", 6u),\n              Ident(u\"null\" + replacement));\n  TEST_TOKENS(ustring_helper::from_string(\"null\\\\\\0\\0\", 7u),\n              Ident(u\"null\" + replacement + replacement));\n  TEST_TOKENS(\"null\\\\0\", Ident(u\"null\" + replacement));\n  TEST_TOKENS(\"null\\\\0000\", Ident(u\"null\" + replacement));\n  TEST_TOKENS(\"large\\\\110000\", Ident(u\"large\" + replacement));\n  TEST_TOKENS(\"large\\\\23456a\", Ident(u\"large\" + replacement));\n  TEST_TOKENS(\"surrogate\\\\D800\", Ident(u\"surrogate\" + replacement));\n  TEST_TOKENS(\"surrogate\\\\0DABC\", Ident(u\"surrogate\" + replacement));\n  TEST_TOKENS(\"\\\\00DFFFsurrogate\", Ident(replacement + u\"surrogate\"));\n  TEST_TOKENS(\"\\\\10fFfF\", Ident(FromUChar32(0x10ffff)));\n  TEST_TOKENS(\"\\\\10fFfF0\", Ident(FromUChar32(0x10ffff) + u\"0\"));\n  TEST_TOKENS(\"\\\\10000000\", Ident(FromUChar32(0x100000) + u\"00\"));\n  TEST_TOKENS(\"eof\\\\\", Ident(u\"eof\" + replacement));\n}\n\nTEST(CSSTokenizerTest, IdentToken) {\n  TEST_TOKENS(\"simple-ident\", Ident(\"simple-ident\"));\n  TEST_TOKENS(\"testing123\", Ident(\"testing123\"));\n  TEST_TOKENS(\"hello!\", Ident(\"hello\"), Delim('!'));\n  TEST_TOKENS(\"world\\5\", Ident(\"world\"), Delim('\\5'));\n  TEST_TOKENS(\"_under score\", Ident(\"_under\"), Whitespace(), Ident(\"score\"));\n  TEST_TOKENS(\"-_underscore\", Ident(\"-_underscore\"));\n  TEST_TOKENS(\"-text\", Ident(\"-text\"));\n  TEST_TOKENS(\"-\\\\6d\", Ident(\"-m\"));\n  TEST_TOKENS(\"--abc\", Ident(\"--abc\"));\n  TEST_TOKENS(\"--\", Ident(\"--\"));\n  TEST_TOKENS(\"--11\", Ident(\"--11\"));\n  TEST_TOKENS(\"---\", Ident(\"---\"));\n  TEST_TOKENS(FromUChar32(0x2003), Ident(FromUChar32(0x2003)));  // em-space\n  TEST_TOKENS(FromUChar32(0xA0),\n              Ident(FromUChar32(0xA0)));  // non-breaking space\n  TEST_TOKENS(FromUChar32(0x1234), Ident(FromUChar32(0x1234)));\n  TEST_TOKENS(FromUChar32(0x12345), Ident(FromUChar32(0x12345)));\n  TEST_TOKENS(ustring_helper::from_string(\"\\0\", 1u),\n              Ident(FromUChar32(0xFFFD)));\n  TEST_TOKENS(ustring_helper::from_string(\"ab\\0c\", 4u),\n              Ident(u\"ab\" + FromUChar32(0xFFFD) + u\"c\"));\n  TEST_TOKENS(ustring_helper::from_string(\"ab\\0c\", 4u),\n              Ident(u\"ab\" + FromUChar32(0xFFFD) + u\"c\"));\n}\n\nTEST(CSSTokenizerTest, FunctionToken) {\n  TEST_TOKENS(\"scale(2)\", Func(\"scale\"), Number(kIntegerValueType, 2, kNoSign),\n              RightParenthesis());\n  TEST_TOKENS(\"foo-bar\\\\ baz(\", Func(\"foo-bar baz\"));\n  TEST_TOKENS(\"fun\\\\(ction(\", Func(\"fun(ction\"));\n  TEST_TOKENS(\"-foo(\", Func(\"-foo\"));\n  TEST_TOKENS(\"url(\\\"foo.gif\\\"\", Func(\"url\"), GetString(\"foo.gif\"));\n  TEST_TOKENS(\"foo(  \\'bar.gif\\'\", Func(\"foo\"), Whitespace(),\n              GetString(\"bar.gif\"));\n  // To simplify implementation we drop the whitespace in\n  // function(url),whitespace,string()\n  TEST_TOKENS(\"url(  \\'bar.gif\\'\", Func(\"url\"), GetString(\"bar.gif\"));\n}\n\nTEST(CSSTokenizerTest, AtKeywordToken) {\n  TEST_TOKENS(\"@at-keyword\", AtKeyword(\"at-keyword\"));\n  TEST_TOKENS(\"@testing123\", AtKeyword(\"testing123\"));\n  TEST_TOKENS(\"@hello!\", AtKeyword(\"hello\"), Delim('!'));\n  TEST_TOKENS(\"@-text\", AtKeyword(\"-text\"));\n  TEST_TOKENS(\"@--abc\", AtKeyword(\"--abc\"));\n  TEST_TOKENS(\"@--\", AtKeyword(\"--\"));\n  TEST_TOKENS(\"@--11\", AtKeyword(\"--11\"));\n  TEST_TOKENS(\"@---\", AtKeyword(\"---\"));\n  TEST_TOKENS(\"@\\\\ \", AtKeyword(\" \"));\n  TEST_TOKENS(\"@-\\\\ \", AtKeyword(\"- \"));\n  TEST_TOKENS(\"@@\", Delim('@'), Delim('@'));\n  TEST_TOKENS(\"@2\", Delim('@'), Number(kIntegerValueType, 2, kNoSign));\n  TEST_TOKENS(\"@-1\", Delim('@'), Number(kIntegerValueType, -1, kMinusSign));\n}\n\nTEST(CSSTokenizerTest, UrlToken) {\n  TEST_TOKENS(\"url(foo.gif)\", Url(\"foo.gif\"));\n  TEST_TOKENS(\"urL(https://example.com/cats.png)\",\n              Url(\"https://example.com/cats.png\"));\n  TEST_TOKENS(\"uRl(what-a.crazy^URL~this\\\\ is!)\",\n              Url(\"what-a.crazy^URL~this is!\"));\n  TEST_TOKENS(\"uRL(123#test)\", Url(\"123#test\"));\n  TEST_TOKENS(\"Url(escapes\\\\ \\\\\\\"\\\\'\\\\)\\\\()\", Url(\"escapes \\\"')(\"));\n  TEST_TOKENS(\"UrL(   whitespace   )\", Url(\"whitespace\"));\n  TEST_TOKENS(\"URl( whitespace-eof \", Url(\"whitespace-eof\"));\n  TEST_TOKENS(\"URL(eof\", Url(\"eof\"));\n  TEST_TOKENS(\"url(not/*a*/comment)\", Url(\"not/*a*/comment\"));\n  TEST_TOKENS(\"urL()\", Url(\"\"));\n  TEST_TOKENS(\"uRl(white space),\", BadUrl(), Comma());\n  TEST_TOKENS(\"Url(b(ad),\", BadUrl(), Comma());\n  TEST_TOKENS(\"uRl(ba'd):\", BadUrl(), Colon());\n  TEST_TOKENS(\"urL(b\\\"ad):\", BadUrl(), Colon());\n  TEST_TOKENS(\"uRl(b\\\"ad):\", BadUrl(), Colon());\n  TEST_TOKENS(\"Url(b\\\\\\rad):\", BadUrl(), Colon());\n  TEST_TOKENS(\"url(b\\\\\\nad):\", BadUrl(), Colon());\n  TEST_TOKENS(\"url(/*'bad')*/\", BadUrl(), Delim('*'), Delim('/'));\n  TEST_TOKENS(\"url(ba'd\\\\\\\\))\", BadUrl(), RightParenthesis());\n}\n\nTEST(CSSTokenizerTest, StringToken) {\n  TEST_TOKENS(\"'text'\", GetString(\"text\"));\n  TEST_TOKENS(\"\\\"text\\\"\", GetString(\"text\"));\n  TEST_TOKENS(\"'testing, 123!'\", GetString(\"testing, 123!\"));\n  TEST_TOKENS(\"'es\\\\'ca\\\\\\\"pe'\", GetString(\"es'ca\\\"pe\"));\n  TEST_TOKENS(\"'\\\"quotes\\\"'\", GetString(\"\\\"quotes\\\"\"));\n  TEST_TOKENS(\"\\\"'quotes'\\\"\", GetString(\"'quotes'\"));\n  TEST_TOKENS(\"\\\"mismatch'\", GetString(\"mismatch'\"));\n  TEST_TOKENS(\"'text\\5\\t\\13'\", GetString(\"text\\5\\t\\13\"));\n  TEST_TOKENS(\"\\\"end on eof\", GetString(\"end on eof\"));\n  TEST_TOKENS(\"'esca\\\\\\nped'\", GetString(\"escaped\"));\n  TEST_TOKENS(\"\\\"esc\\\\\\faped\\\"\", GetString(\"escaped\"));\n  TEST_TOKENS(\"'new\\\\\\rline'\", GetString(\"newline\"));\n  TEST_TOKENS(\"\\\"new\\\\\\r\\nline\\\"\", GetString(\"newline\"));\n  TEST_TOKENS(\"'bad\\nstring\", BadString(), Whitespace(), Ident(\"string\"));\n  TEST_TOKENS(\"'bad\\rstring\", BadString(), Whitespace(), Ident(\"string\"));\n  TEST_TOKENS(\"'bad\\r\\nstring\", BadString(), Whitespace(), Ident(\"string\"));\n  TEST_TOKENS(\"'bad\\fstring\", BadString(), Whitespace(), Ident(\"string\"));\n  TEST_TOKENS(ustring_helper::from_string(\"'\\0'\", 3u),\n              GetString(FromUChar32(0xFFFD)));\n  TEST_TOKENS(ustring_helper::from_string(\"'hel\\0lo'\", 8u),\n              GetString(u\"hel\" + FromUChar32(0xFFFD) + u\"lo\"));\n  TEST_TOKENS(ustring_helper::from_string(\"'h\\\\65l\\0lo'\", 10u),\n              GetString(u\"hel\" + FromUChar32(0xFFFD) + u\"lo\"));\n}\n\nTEST(CSSTokenizerTest, HashToken) {\n  TEST_TOKENS(\"#id-selector\", GetHash(\"id-selector\", kHashTokenId));\n  TEST_TOKENS(\"#FF7700\", GetHash(\"FF7700\", kHashTokenId));\n  TEST_TOKENS(\"#3377FF\", GetHash(\"3377FF\", kHashTokenUnrestricted));\n  TEST_TOKENS(\"#\\\\ \", GetHash(\" \", kHashTokenId));\n  TEST_TOKENS(\"# \", Delim('#'), Whitespace());\n  TEST_TOKENS(\"#\\\\\\n\", Delim('#'), Delim('\\\\'), Whitespace());\n  TEST_TOKENS(\"#\\\\\\r\\n\", Delim('#'), Delim('\\\\'), Whitespace());\n  TEST_TOKENS(\"#!\", Delim('#'), Delim('!'));\n}\n\nTEST(CSSTokenizerTest, NumberToken) {\n  TEST_TOKENS(\"10\", Number(kIntegerValueType, 10, kNoSign));\n  TEST_TOKENS(\"12.0\", Number(kNumberValueType, 12, kNoSign));\n  TEST_TOKENS(\"+45.6\", Number(kNumberValueType, 45.6, kPlusSign));\n  TEST_TOKENS(\"-7\", Number(kIntegerValueType, -7, kMinusSign));\n  TEST_TOKENS(\"010\", Number(kIntegerValueType, 10, kNoSign));\n  TEST_TOKENS(\"10e0\", Number(kNumberValueType, 10, kNoSign));\n  TEST_TOKENS(\"12e3\", Number(kNumberValueType, 12000, kNoSign));\n  TEST_TOKENS(\"3e+1\", Number(kNumberValueType, 30, kNoSign));\n  TEST_TOKENS(\"12E-1\", Number(kNumberValueType, 1.2, kNoSign));\n  TEST_TOKENS(\".7\", Number(kNumberValueType, 0.7, kNoSign));\n  TEST_TOKENS(\"-.3\", Number(kNumberValueType, -0.3, kMinusSign));\n  TEST_TOKENS(\"+637.54e-2\", Number(kNumberValueType, 6.3754, kPlusSign));\n  TEST_TOKENS(\"-12.34E+2\", Number(kNumberValueType, -1234, kMinusSign));\n\n  TEST_TOKENS(\"+ 5\", Delim('+'), Whitespace(),\n              Number(kIntegerValueType, 5, kNoSign));\n  TEST_TOKENS(\"-+12\", Delim('-'), Number(kIntegerValueType, 12, kPlusSign));\n  TEST_TOKENS(\"+-21\", Delim('+'), Number(kIntegerValueType, -21, kMinusSign));\n  TEST_TOKENS(\"++22\", Delim('+'), Number(kIntegerValueType, 22, kPlusSign));\n  TEST_TOKENS(\"13.\", Number(kIntegerValueType, 13, kNoSign), Delim('.'));\n  TEST_TOKENS(\"1.e2\", Number(kIntegerValueType, 1, kNoSign), Delim('.'),\n              Ident(\"e2\"));\n  TEST_TOKENS(\"2e3.5\", Number(kNumberValueType, 2000, kNoSign),\n              Number(kNumberValueType, 0.5, kNoSign));\n  TEST_TOKENS(\"2e3.\", Number(kNumberValueType, 2000, kNoSign), Delim('.'));\n  TEST_TOKENS(\"1000000000000000000000000\",\n              Number(kIntegerValueType, 1e24, kNoSign));\n}\n\nTEST(CSSTokenizerTest, DimensionToken) {\n  TEST_TOKENS(\"10px\", Dimension(kIntegerValueType, 10, \"px\"));\n  TEST_TOKENS(\"12.0em\", Dimension(kNumberValueType, 12, \"em\"));\n  TEST_TOKENS(\"-12.0em\", Dimension(kNumberValueType, -12, \"em\"));\n  TEST_TOKENS(\"+45.6__qem\", Dimension(kNumberValueType, 45.6, \"__qem\"));\n  TEST_TOKENS(\"5e\", Dimension(kIntegerValueType, 5, \"e\"));\n  TEST_TOKENS(\"5px-2px\", Dimension(kIntegerValueType, 5, \"px-2px\"));\n  TEST_TOKENS(\"5e-\", Dimension(kIntegerValueType, 5, \"e-\"));\n  TEST_TOKENS(\"5\\\\ \", Dimension(kIntegerValueType, 5, \" \"));\n  TEST_TOKENS(\"40\\\\70\\\\78\", Dimension(kIntegerValueType, 40, \"px\"));\n  TEST_TOKENS(\"4e3e2\", Dimension(kNumberValueType, 4000, \"e2\"));\n  TEST_TOKENS(\"0x10px\", Dimension(kIntegerValueType, 0, \"x10px\"));\n  TEST_TOKENS(\"4unit \", Dimension(kIntegerValueType, 4, \"unit\"), Whitespace());\n  TEST_TOKENS(\"5e+\", Dimension(kIntegerValueType, 5, \"e\"), Delim('+'));\n  TEST_TOKENS(\"2e.5\", Dimension(kIntegerValueType, 2, \"e\"),\n              Number(kNumberValueType, 0.5, kNoSign));\n  TEST_TOKENS(\"2e+.5\", Dimension(kIntegerValueType, 2, \"e\"),\n              Number(kNumberValueType, 0.5, kPlusSign));\n}\n\nTEST(CSSTokenizerTest, PercentageToken) {\n  TEST_TOKENS(\"10%\", Percentage(kIntegerValueType, 10));\n  TEST_TOKENS(\"+12.0%\", Percentage(kNumberValueType, 12));\n  TEST_TOKENS(\"-48.99%\", Percentage(kNumberValueType, -48.99));\n  TEST_TOKENS(\"6e-1%\", Percentage(kNumberValueType, 0.6));\n  TEST_TOKENS(\"5%%\", Percentage(kIntegerValueType, 5), Delim('%'));\n}\n\nTEST(CSSTokenizerTest, UnicodeRangeToken) {\n  TEST_TOKENS(\"u+012345-123456\", UnicodeRng(0x012345, 0x123456));\n  TEST_TOKENS(\"U+1234-2345\", UnicodeRng(0x1234, 0x2345));\n  TEST_TOKENS(\"u+222-111\", UnicodeRng(0x222, 0x111));\n  TEST_TOKENS(\"U+CafE-d00D\", UnicodeRng(0xcafe, 0xd00d));\n  TEST_TOKENS(\"U+2??\", UnicodeRng(0x200, 0x2ff));\n  TEST_TOKENS(\"U+ab12??\", UnicodeRng(0xab1200, 0xab12ff));\n  TEST_TOKENS(\"u+??????\", UnicodeRng(0x000000, 0xffffff));\n  TEST_TOKENS(\"u+??\", UnicodeRng(0x00, 0xff));\n\n  TEST_TOKENS(\"u+222+111\", UnicodeRng(0x222, 0x222),\n              Number(kIntegerValueType, 111, kPlusSign));\n  TEST_TOKENS(\"u+12345678\", UnicodeRng(0x123456, 0x123456),\n              Number(kIntegerValueType, 78, kNoSign));\n  TEST_TOKENS(\"u+123-12345678\", UnicodeRng(0x123, 0x123456),\n              Number(kIntegerValueType, 78, kNoSign));\n  TEST_TOKENS(\"u+cake\", UnicodeRng(0xca, 0xca), Ident(\"ke\"));\n  TEST_TOKENS(\"u+1234-gggg\", UnicodeRng(0x1234, 0x1234), Ident(\"-gggg\"));\n  TEST_TOKENS(\"U+ab12???\", UnicodeRng(0xab1200, 0xab12ff), Delim('?'));\n  TEST_TOKENS(\"u+a1?-123\", UnicodeRng(0xa10, 0xa1f),\n              Number(kIntegerValueType, -123, kMinusSign));\n  TEST_TOKENS(\"u+1??4\", UnicodeRng(0x100, 0x1ff),\n              Number(kIntegerValueType, 4, kNoSign));\n  TEST_TOKENS(\"u+z\", Ident(\"u\"), Delim('+'), Ident(\"z\"));\n  TEST_TOKENS(\"u+\", Ident(\"u\"), Delim('+'));\n  TEST_TOKENS(\"u+-543\", Ident(\"u\"), Delim('+'),\n              Number(kIntegerValueType, -543, kMinusSign));\n}\n\nTEST(CSSTokenizerTest, CommentToken) {\n  TEST_TOKENS(\"/*comment*/a\", Ident(\"a\"));\n  TEST_TOKENS(\"/**\\\\2f**//\", Delim('/'));\n  TEST_TOKENS(\"/**y*a*y**/ \", Whitespace());\n  TEST_TOKENS(\",/* \\n :) \\n */)\", Comma(), RightParenthesis());\n  TEST_TOKENS(\":/*/*/\", Colon());\n  TEST_TOKENS(\"/**/*\", Delim('*'));\n  TEST_TOKENS(\";/******\", Semicolon());\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/number_parsing_options.h",
    "content": "// Copyright 2017 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_PARSER_NUMBER_PARSING_OPTIONS_H_\n#define CORE_RENDERER_CSS_NG_PARSER_NUMBER_PARSING_OPTIONS_H_\n\n#include <ostream>\n\n#include \"base/include/log/logging.h\"\n\nnamespace lynx {\nnamespace css {\n\n// Copyable and immutable object representing number parsing flags.\nclass NumberParsingOptions {\n public:\n  static constexpr unsigned kNone = 0;\n  static constexpr unsigned kAcceptTrailingGarbage = 1;\n  static constexpr unsigned kAcceptLeadingPlus = 1 << 1;\n  static constexpr unsigned kAcceptLeadingTrailingWhitespace = 1 << 2;\n  static constexpr unsigned kAcceptMinusZeroForUnsigned = 1 << 3;\n\n  // 'Strict' behavior for WTF::String.\n  static constexpr unsigned kStrict =\n      kAcceptLeadingPlus | kAcceptLeadingTrailingWhitespace;\n  // Non-'Strict' behavior for WTF::String.\n  static constexpr unsigned kLoose = kStrict | kAcceptTrailingGarbage;\n\n  // This constructor allows implicit conversion from unsigned.\n  NumberParsingOptions(unsigned options) : options_(options) {\n    DCHECK(options < 1u << 4);\n  }\n\n  bool AcceptTrailingGarbage() const {\n    return options_ & kAcceptTrailingGarbage;\n  }\n  bool AcceptLeadingPlus() const { return options_ & kAcceptLeadingPlus; }\n  bool AcceptWhitespace() const {\n    return options_ & kAcceptLeadingTrailingWhitespace;\n  }\n  bool AcceptMinusZeroForUnsigned() const {\n    return options_ & kAcceptMinusZeroForUnsigned;\n  }\n\n private:\n  unsigned options_;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_PARSER_NUMBER_PARSING_OPTIONS_H_\n"
  },
  {
    "path": "core/renderer/css/ng/parser/string_to_number.cc",
    "content": "// Copyright 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/parser/string_to_number.h\"\n\n#include <cmath>\n#include <limits>\n#include <string>\n\n#include \"third_party/double-conversion/double-conversion/double-conversion.h\"\n\nnamespace lynx {\nnamespace css {\n\ntemplate <int base>\nbool IsCharacterAllowedInBase(UChar);\n\ntemplate <>\nbool IsCharacterAllowedInBase<10>(UChar c) {\n  return base::IsASCIINumber(c);\n}\n\ntemplate <>\nbool IsCharacterAllowedInBase<16>(UChar c) {\n  return base::IsASCIIHexNumber(c);\n}\n\ntemplate <typename IntegralType, typename CharType, int base>\nstatic inline IntegralType ToIntegralType(const CharType* data, size_t length,\n                                          NumberParsingOptions options,\n                                          NumberParsingResult* parsing_result) {\n  static_assert(std::is_integral<IntegralType>::value,\n                \"IntegralType must be an integral type.\");\n  static constexpr IntegralType kIntegralMax =\n      std::numeric_limits<IntegralType>::max();\n  static constexpr IntegralType kIntegralMin =\n      std::numeric_limits<IntegralType>::min();\n  static constexpr bool kIsSigned =\n      std::numeric_limits<IntegralType>::is_signed;\n  DCHECK(parsing_result);\n\n  IntegralType value = 0;\n  NumberParsingResult result = NumberParsingResult::kError;\n  bool is_negative = false;\n  bool overflow = false;\n  const bool accept_minus = kIsSigned || options.AcceptMinusZeroForUnsigned();\n\n  if (!data) goto bye;\n\n  if (options.AcceptWhitespace()) {\n    while (length && base::IsSpaceOrNewline(*data)) {\n      --length;\n      ++data;\n    }\n  }\n\n  if (accept_minus && length && *data == '-') {\n    --length;\n    ++data;\n    is_negative = true;\n  } else if (length && options.AcceptLeadingPlus() && *data == '+') {\n    --length;\n    ++data;\n  }\n\n  if (!length || !IsCharacterAllowedInBase<base>(*data)) goto bye;\n\n  while (length && IsCharacterAllowedInBase<base>(*data)) {\n    --length;\n    IntegralType digit_value;\n    CharType c = *data;\n    if (base::IsASCIINumber(c))\n      digit_value = c - '0';\n    else if (c >= 'a')\n      digit_value = c - 'a' + 10;\n    else\n      digit_value = c - 'A' + 10;\n\n    if (is_negative) {\n      if (!kIsSigned && options.AcceptMinusZeroForUnsigned()) {\n        if (digit_value != 0) {\n          result = NumberParsingResult::kError;\n          overflow = true;\n        }\n      } else {\n        // Overflow condition:\n        //       value * base - digit_value < kIntegralMin\n        //   <=> value < (kIntegralMin + digit_value) / base\n        // We must be careful of rounding errors here, but the default rounding\n        // mode (round to zero) works well, so we can use this formula as-is.\n        if (value < (kIntegralMin + digit_value) / base) {\n          result = NumberParsingResult::kOverflowMin;\n          overflow = true;\n        }\n      }\n    } else {\n      // Overflow condition:\n      //       value * base + digit_value > kIntegralMax\n      //   <=> value > (kIntegralMax + digit_value) / base\n      // Ditto regarding rounding errors.\n      if (value > (kIntegralMax - digit_value) / base) {\n        result = NumberParsingResult::kOverflowMax;\n        overflow = true;\n      }\n    }\n\n    if (!overflow) {\n      if (is_negative)\n        value = base * value - digit_value;\n      else\n        value = base * value + digit_value;\n    }\n    ++data;\n  }\n\n  if (options.AcceptWhitespace()) {\n    while (length && base::IsSpaceOrNewline(*data)) {\n      --length;\n      ++data;\n    }\n  }\n\n  if (length == 0 || options.AcceptTrailingGarbage()) {\n    if (!overflow) result = NumberParsingResult::kSuccess;\n  } else {\n    // Even if we detected overflow, we return kError for trailing garbage.\n    result = NumberParsingResult::kError;\n  }\nbye:\n  *parsing_result = result;\n  return result == NumberParsingResult::kSuccess ? value : 0;\n}\n\ntemplate <typename IntegralType, typename CharType, int base>\nstatic inline IntegralType ToIntegralType(const CharType* data, size_t length,\n                                          NumberParsingOptions options,\n                                          bool* ok) {\n  NumberParsingResult result;\n  IntegralType value = ToIntegralType<IntegralType, CharType, base>(\n      data, length, options, &result);\n  if (ok) *ok = result == NumberParsingResult::kSuccess;\n  return value;\n}\n\nunsigned CharactersToUInt(const LChar* data, size_t length,\n                          NumberParsingOptions options,\n                          NumberParsingResult* result) {\n  return ToIntegralType<unsigned, LChar, 10>(data, length, options, result);\n}\n\nunsigned CharactersToUInt(const UChar* data, size_t length,\n                          NumberParsingOptions options,\n                          NumberParsingResult* result) {\n  return ToIntegralType<unsigned, UChar, 10>(data, length, options, result);\n}\n\nunsigned HexCharactersToUInt(const LChar* data, size_t length,\n                             NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<unsigned, LChar, 16>(data, length, options, ok);\n}\n\nunsigned HexCharactersToUInt(const UChar* data, size_t length,\n                             NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<unsigned, UChar, 16>(data, length, options, ok);\n}\n\nuint64_t HexCharactersToUInt64(const LChar* data, size_t length,\n                               NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<uint64_t, LChar, 16>(data, length, options, ok);\n}\n\nuint64_t HexCharactersToUInt64(const UChar* data, size_t length,\n                               NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<uint64_t, UChar, 16>(data, length, options, ok);\n}\n\nint CharactersToInt(const LChar* data, size_t length,\n                    NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<int, LChar, 10>(data, length, options, ok);\n}\n\nint CharactersToInt(const UChar* data, size_t length,\n                    NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<int, UChar, 10>(data, length, options, ok);\n}\n\nunsigned CharactersToUInt(const LChar* data, size_t length,\n                          NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<unsigned, LChar, 10>(data, length, options, ok);\n}\n\nunsigned CharactersToUInt(const UChar* data, size_t length,\n                          NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<unsigned, UChar, 10>(data, length, options, ok);\n}\n\nint64_t CharactersToInt64(const LChar* data, size_t length,\n                          NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<int64_t, LChar, 10>(data, length, options, ok);\n}\n\nint64_t CharactersToInt64(const UChar* data, size_t length,\n                          NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<int64_t, UChar, 10>(data, length, options, ok);\n}\n\nuint64_t CharactersToUInt64(const LChar* data, size_t length,\n                            NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<uint64_t, LChar, 10>(data, length, options, ok);\n}\n\nuint64_t CharactersToUInt64(const UChar* data, size_t length,\n                            NumberParsingOptions options, bool* ok) {\n  return ToIntegralType<uint64_t, UChar, 10>(data, length, options, ok);\n}\n\nenum TrailingJunkPolicy { kDisallowTrailingJunk, kAllowTrailingJunk };\n\ninline double ParseDouble(const LChar* string, size_t length,\n                          size_t& parsed_length) {\n  static double_conversion::StringToDoubleConverter converter(\n      double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES |\n          double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK,\n      0.0, 0, nullptr, nullptr);\n  int int_parsed_length = 0;\n  double d =\n      converter.StringToDouble(reinterpret_cast<const char*>(string),\n                               static_cast<int>(length), &int_parsed_length);\n  parsed_length = int_parsed_length;\n  return d;\n}\n\ninline double ParseDouble(const UChar* string, size_t length,\n                          size_t& parsed_length) {\n  LChar conversion_buffer[length];\n  for (size_t i = 0; i < length; ++i)\n    conversion_buffer[i] =\n        base::IsASCII(string[i]) ? static_cast<LChar>(string[i]) : 0;\n  return ParseDouble(conversion_buffer, length, parsed_length);\n}\n\ntemplate <typename CharType, TrailingJunkPolicy policy>\nstatic inline double ToDoubleType(const CharType* data, size_t length, bool* ok,\n                                  size_t& parsed_length) {\n  size_t leading_spaces_length = 0;\n  while (leading_spaces_length < length &&\n         base::IsASCIISpace(data[leading_spaces_length]))\n    ++leading_spaces_length;\n\n  double number = ParseDouble(data + leading_spaces_length,\n                              length - leading_spaces_length, parsed_length);\n  if (!parsed_length) {\n    if (ok) *ok = false;\n    return 0.0;\n  }\n\n  parsed_length += leading_spaces_length;\n  if (ok) *ok = policy == kAllowTrailingJunk || parsed_length == length;\n  return number;\n}\n\ndouble CharactersToDouble(const LChar* data, size_t length, bool* ok) {\n  size_t parsed_length;\n  return ToDoubleType<LChar, kDisallowTrailingJunk>(data, length, ok,\n                                                    parsed_length);\n}\n\ndouble CharactersToDouble(const UChar* data, size_t length, bool* ok) {\n  size_t parsed_length;\n  return ToDoubleType<UChar, kDisallowTrailingJunk>(data, length, ok,\n                                                    parsed_length);\n}\n\ndouble CharactersToDouble(const LChar* data, size_t length,\n                          size_t& parsed_length) {\n  return ToDoubleType<LChar, kAllowTrailingJunk>(data, length, nullptr,\n                                                 parsed_length);\n}\n\ndouble CharactersToDouble(const UChar* data, size_t length,\n                          size_t& parsed_length) {\n  return ToDoubleType<UChar, kAllowTrailingJunk>(data, length, nullptr,\n                                                 parsed_length);\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/parser/string_to_number.h",
    "content": "// Copyright 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_PARSER_STRING_TO_NUMBER_H_\n#define CORE_RENDERER_CSS_NG_PARSER_STRING_TO_NUMBER_H_\n\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/ng/parser/number_parsing_options.h\"\n\nnamespace lynx {\nnamespace css {\n\nusing UChar = base::UChar;\nusing LChar = base::LChar;\nusing UChar32 = base::UChar32;\n\nenum class NumberParsingResult {\n  kSuccess,\n  kError,\n  // For UInt functions, kOverflowMin never happens. Negative numbers are\n  // treated as kError. This behavior matches to the HTML standard.\n  // https://html.spec.whatwg.org/C/#rules-for-parsing-non-negative-integers\n  kOverflowMin,\n  kOverflowMax,\n};\n\n// string -> int.\nint CharactersToInt(const LChar*, size_t, NumberParsingOptions, bool* ok);\nint CharactersToInt(const UChar*, size_t, NumberParsingOptions, bool* ok);\n\n// string -> unsigned.\nunsigned HexCharactersToUInt(const LChar*, size_t, NumberParsingOptions,\n                             bool* ok);\nunsigned HexCharactersToUInt(const UChar*, size_t, NumberParsingOptions,\n                             bool* ok);\nuint64_t HexCharactersToUInt64(const UChar*, size_t, NumberParsingOptions,\n                               bool* ok);\nuint64_t HexCharactersToUInt64(const LChar*, size_t, NumberParsingOptions,\n                               bool* ok);\nunsigned CharactersToUInt(const LChar*, size_t, NumberParsingOptions, bool* ok);\nunsigned CharactersToUInt(const UChar*, size_t, NumberParsingOptions, bool* ok);\n\n// NumberParsingResult versions of CharactersToUInt. They can detect\n// overflow. |NumberParsingResult*| should not be nullptr;\nunsigned CharactersToUInt(const LChar*, size_t, NumberParsingOptions,\n                          NumberParsingResult*);\nunsigned CharactersToUInt(const UChar*, size_t, NumberParsingOptions,\n                          NumberParsingResult*);\n\n// string -> int64_t.\nint64_t CharactersToInt64(const LChar*, size_t, NumberParsingOptions, bool* ok);\nint64_t CharactersToInt64(const UChar*, size_t, NumberParsingOptions, bool* ok);\n\n// string -> uint64_t.\nuint64_t CharactersToUInt64(const LChar*, size_t, NumberParsingOptions,\n                            bool* ok);\nuint64_t CharactersToUInt64(const UChar*, size_t, NumberParsingOptions,\n                            bool* ok);\n\n// FIXME: Like the strict functions above, these give false for \"ok\" when there\n// is trailing garbage.  Like the non-strict functions above, these return the\n// value when there is trailing garbage.  It would be better if these were more\n// consistent with the above functions instead.\n\n// string -> double.\n//\n// These functions accepts:\n//  - leading '+'\n//  - numbers without leading zeros such as \".5\"\n//  - numbers ending with \".\" such as \"3.\"\n//  - scientific notation\n//  - leading whitespace (base::IsASCIISpace, not base::IsHTMLSpace)\n//  - no trailing whitespace\n//  - no trailing garbage\n//  - no numbers such as \"NaN\" \"Infinity\"\n//\n// A huge absolute number which a double can't represent is accepted, and\n// +Infinity or -Infinity is returned.\n//\n// A small absolute numbers which a double can't represent is accepted, and\n// 0 is returned\ndouble CharactersToDouble(const LChar*, size_t, bool* ok);\ndouble CharactersToDouble(const UChar*, size_t, bool* ok);\n\n// |parsed_length| will have the length of characters which was parsed as a\n// double number. It will be 0 if the input string isn't a number. It will be\n// smaller than |length| if the input string contains trailing\n// whiespace/garbage.\ndouble CharactersToDouble(const LChar*, size_t length, size_t& parsed_length);\ndouble CharactersToDouble(const UChar*, size_t length, size_t& parsed_length);\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_PARSER_STRING_TO_NUMBER_H_\n"
  },
  {
    "path": "core/renderer/css/ng/selector/css_parser_context.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2016 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef CORE_RENDERER_CSS_NG_SELECTOR_CSS_PARSER_CONTEXT_H_\n#define CORE_RENDERER_CSS_NG_SELECTOR_CSS_PARSER_CONTEXT_H_\n\nnamespace lynx {\nnamespace css {\n\nclass CSSParserContext final {};\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_SELECTOR_CSS_PARSER_CONTEXT_H_\n"
  },
  {
    "path": "core/renderer/css/ng/selector/css_selector_parser.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"core/renderer/css/ng/selector/css_selector_parser.h\"\n\n#include <iostream>\n#include <limits>\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"core/renderer/css/ng/parser/css_parser_token_stream.h\"\n#include \"core/renderer/css/ng/parser/string_to_number.h\"\n\nnamespace lynx {\nnamespace css {\n\nnamespace {\n\nvoid AppendTagHistory(LynxCSSParserSelector* target,\n                      LynxCSSSelector::RelationType relation,\n                      std::unique_ptr<LynxCSSParserSelector> selector) {\n  LynxCSSParserSelector* end = target;\n  while (end->tag_history) {\n    end = end->tag_history.get();\n  }\n  end->selector->SetRelation(relation);\n  end->tag_history = std::move(selector);\n}\n\nLynxCSSSelector::RelationType GetImplicitShadowCombinatorForMatching(\n    LynxCSSParserSelector* selector) {\n  switch (selector->selector->GetPseudoType()) {\n    case LynxCSSSelector::PseudoType::kPseudoPlaceholder:\n    case LynxCSSSelector::PseudoType::kPseudoSelection:\n      return LynxCSSSelector::RelationType::kUAShadow;\n    default:\n      return LynxCSSSelector::RelationType::kSubSelector;\n  }\n}\n\nbool NeedsImplicitShadowCombinatorForMatching(LynxCSSParserSelector* selector) {\n  return GetImplicitShadowCombinatorForMatching(selector) !=\n         LynxCSSSelector::RelationType::kSubSelector;\n}\n\n}  // namespace\n\n// static\nLynxCSSSelectorVector CSSSelectorParser::ParseSelector(\n    CSSParserTokenRange range, const CSSParserContext* context) {\n  CSSSelectorParser parser(context);\n  range.ConsumeWhitespace();\n  LynxCSSSelectorVector result = parser.ConsumeComplexSelectorList(range);\n  if (!range.AtEnd()) return {};\n\n  return result;\n}\n\nCSSSelectorParser::CSSSelectorParser(const CSSParserContext* context) {}\n\nLynxCSSSelectorVector CSSSelectorParser::ConsumeComplexSelectorList(\n    CSSParserTokenRange& range) {\n  LynxCSSSelectorVector selector_list;\n  std::unique_ptr<LynxCSSParserSelector> selector =\n      ConsumeComplexSelector(range);\n  if (!selector) return {};\n  selector_list.push_back(std::move(selector));\n  while (!range.AtEnd() && range.Peek().GetType() == kCommaToken) {\n    range.ConsumeIncludingWhitespace();\n    selector = ConsumeComplexSelector(range);\n    if (!selector) return {};\n    selector_list.push_back(std::move(selector));\n  }\n\n  if (failed_parsing_) return {};\n\n  return selector_list;\n}\n\nLynxCSSSelectorList CSSSelectorParser::ConsumeNestedSelectorList(\n    CSSParserTokenRange& range) {\n  LynxCSSSelectorVector result = ConsumeComplexSelectorList(range);\n  if (result.empty()) return {};\n  return AdoptSelectorVector(result);\n}\n\nnamespace {\n\nenum CompoundSelectorFlags {\n  kHasPseudoElementForRightmostCompound = 1 << 0,\n};\n\nunsigned ExtractCompoundFlags(const LynxCSSParserSelector& simple_selector) {\n  if (simple_selector.selector->Match() != LynxCSSSelector::kPseudoElement)\n    return 0;\n  // We don't restrict what follows custom ::-webkit-* pseudo elements in UA\n  // sheets. We currently use selectors in mediaControls.css like this:\n  return kHasPseudoElementForRightmostCompound;\n}\n\n}  // namespace\n\nstd::unique_ptr<LynxCSSParserSelector>\nCSSSelectorParser::ConsumeComplexSelector(CSSParserTokenRange& range) {\n  std::unique_ptr<LynxCSSParserSelector> selector =\n      ConsumeCompoundSelector(range);\n  if (!selector) return nullptr;\n\n  unsigned previous_compound_flags = 0;\n\n  for (LynxCSSParserSelector* simple = selector.get();\n       simple && !previous_compound_flags; simple = simple->tag_history.get())\n    previous_compound_flags |= ExtractCompoundFlags(*simple);\n\n  if (LynxCSSSelector::RelationType combinator = ConsumeCombinator(range)) {\n    return ConsumePartialComplexSelector(range, combinator, std::move(selector),\n                                         previous_compound_flags);\n  }\n\n  return selector;\n}\n\nstd::unique_ptr<LynxCSSParserSelector>\nCSSSelectorParser::ConsumePartialComplexSelector(\n    CSSParserTokenRange& range, LynxCSSSelector::RelationType& combinator,\n    std::unique_ptr<LynxCSSParserSelector> selector,\n    unsigned& previous_compound_flags) {\n  do {\n    std::unique_ptr<LynxCSSParserSelector> next_selector =\n        ConsumeCompoundSelector(range);\n    if (!next_selector)\n      return combinator == LynxCSSSelector::kDescendant ? std::move(selector)\n                                                        : nullptr;\n    if (previous_compound_flags & kHasPseudoElementForRightmostCompound)\n      return nullptr;\n    LynxCSSParserSelector* end = next_selector.get();\n    unsigned compound_flags = ExtractCompoundFlags(*end);\n    while (end->tag_history) {\n      end = end->tag_history.get();\n      compound_flags |= ExtractCompoundFlags(*end);\n    }\n    end->selector->SetRelation(combinator);\n    previous_compound_flags = compound_flags;\n    end->tag_history = std::move(selector);\n\n    selector = std::move(next_selector);\n  } while ((combinator = ConsumeCombinator(range)));\n\n  return selector;\n}\n\n// Could be made smaller and faster by replacing pointer with an\n// offset into a string buffer and making the bit fields smaller but\n// that could not be maintained by hand.\nstruct NameToPseudoStruct {\n  const char* string;\n  unsigned type : 8;\n};\n\n// These tables should be kept sorted.\nconst static NameToPseudoStruct kPseudoTypeWithoutArgumentsMap[] = {\n    {\"active\", LynxCSSSelector::kPseudoActive},\n    {\"after\", LynxCSSSelector::kPseudoAfter},\n    {\"backdrop\", LynxCSSSelector::kPseudoBackdrop},\n    {\"before\", LynxCSSSelector::kPseudoBefore},\n    {\"checked\", LynxCSSSelector::kPseudoChecked},\n    {\"default\", LynxCSSSelector::kPseudoDefault},\n    {\"disabled\", LynxCSSSelector::kPseudoDisabled},\n    {\"empty\", LynxCSSSelector::kPseudoEmpty},\n    {\"enabled\", LynxCSSSelector::kPseudoEnabled},\n    {\"first-child\", LynxCSSSelector::kPseudoFirstChild},\n    {\"first-letter\", LynxCSSSelector::kPseudoFirstLetter},\n    {\"first-line\", LynxCSSSelector::kPseudoFirstLine},\n    {\"first-of-type\", LynxCSSSelector::kPseudoFirstOfType},\n    {\"focus\", LynxCSSSelector::kPseudoFocus},\n    {\"hover\", LynxCSSSelector::kPseudoHover},\n    {\"last-child\", LynxCSSSelector::kPseudoLastChild},\n    {\"last-of-type\", LynxCSSSelector::kPseudoLastOfType},\n    {\"link\", LynxCSSSelector::kPseudoLink},\n    {\"only-child\", LynxCSSSelector::kPseudoOnlyChild},\n    {\"only-of-type\", LynxCSSSelector::kPseudoOnlyOfType},\n    {\"placeholder\", LynxCSSSelector::kPseudoPlaceholder},\n    {\"root\", LynxCSSSelector::kPseudoRoot},\n    {\"selection\", LynxCSSSelector::kPseudoSelection},\n};\n\nconst static NameToPseudoStruct kPseudoTypeWithArgumentsMap[] = {\n    {\"dir\", LynxCSSSelector::kPseudoDir},\n    {\"has\", LynxCSSSelector::kPseudoHas},\n    {\"is\", LynxCSSSelector::kPseudoIs},\n    {\"lang\", LynxCSSSelector::kPseudoLang},\n    {\"not\", LynxCSSSelector::kPseudoNot},\n    {\"nth-child\", LynxCSSSelector::kPseudoNthChild},\n    {\"nth-last-child\", LynxCSSSelector::kPseudoNthLastChild},\n    {\"nth-last-of-type\", LynxCSSSelector::kPseudoNthLastOfType},\n    {\"nth-of-type\", LynxCSSSelector::kPseudoNthOfType},\n    {\"where\", LynxCSSSelector::kPseudoWhere},\n};\n\nstatic LynxCSSSelector::PseudoType NameToPseudoType(const std::string& name,\n                                                    bool has_arguments) {\n  if (name.empty()) return LynxCSSSelector::kPseudoUnknown;\n\n  const NameToPseudoStruct* pseudo_type_map;\n  const NameToPseudoStruct* pseudo_type_map_end;\n  if (has_arguments) {\n    pseudo_type_map = kPseudoTypeWithArgumentsMap;\n    pseudo_type_map_end =\n        kPseudoTypeWithArgumentsMap + std::size(kPseudoTypeWithArgumentsMap);\n  } else {\n    pseudo_type_map = kPseudoTypeWithoutArgumentsMap;\n    pseudo_type_map_end = kPseudoTypeWithoutArgumentsMap +\n                          std::size(kPseudoTypeWithoutArgumentsMap);\n  }\n  const NameToPseudoStruct* match = std::lower_bound(\n      pseudo_type_map, pseudo_type_map_end, name,\n      [](const NameToPseudoStruct& entry, const std::string& name) -> bool {\n        DCHECK(entry.string);\n        // If strncmp returns 0, then either the keys are equal, or |name| sorts\n        // before |entry|.\n        return strncmp(entry.string, reinterpret_cast<const char*>(name.data()),\n                       name.length()) < 0;\n      });\n  if (match == pseudo_type_map_end || match->string != name)\n    return LynxCSSSelector::kPseudoUnknown;\n\n  return static_cast<LynxCSSSelector::PseudoType>(match->type);\n}\n\nLynxCSSSelector::PseudoType CSSSelectorParser::ParsePseudoType(\n    const std::u16string& name, bool has_arguments) {\n  auto key = ustring_helper::to_string(name);\n  LynxCSSSelector::PseudoType pseudo_type =\n      NameToPseudoType(key, has_arguments);\n  if (pseudo_type != LynxCSSSelector::PseudoType::kPseudoUnknown)\n    return pseudo_type;\n\n  return LynxCSSSelector::PseudoType::kPseudoUnknown;\n}\n\nnamespace {\n\nbool IsSimpleSelectorValidAfterPseudoElement(\n    const LynxCSSParserSelector& simple_selector,\n    LynxCSSSelector::PseudoType compound_pseudo_element) {\n  switch (compound_pseudo_element) {\n    case LynxCSSSelector::kPseudoUnknown:\n      return true;\n    case LynxCSSSelector::kPseudoAfter:\n    case LynxCSSSelector::kPseudoBefore:\n      break;\n    default:\n      break;\n  }\n  if (simple_selector.selector->Match() != LynxCSSSelector::kPseudoClass)\n    return false;\n  LynxCSSSelector::PseudoType pseudo =\n      simple_selector.selector->GetPseudoType();\n  switch (pseudo) {\n    case LynxCSSSelector::kPseudoNot:\n      // These pseudo-classes are themselves always valid.\n      // CSSSelectorParser::restricting_pseudo_element_ ensures that invalid\n      // nested selectors will be dropped if they are invalid according to\n      // this function.\n      return true;\n    default:\n      break;\n  }\n  return false;\n}\n\n}  // namespace\n\nstd::unique_ptr<LynxCSSParserSelector>\nCSSSelectorParser::ConsumeCompoundSelector(CSSParserTokenRange& range) {\n  base::AutoReset<LynxCSSSelector::PseudoType> reset_restricting(\n      &restricting_pseudo_element_, restricting_pseudo_element_);\n\n  std::unique_ptr<LynxCSSParserSelector> compound_selector;\n  std::u16string element_name;\n  const bool has_tag_name = ConsumeName(range, element_name);\n  if (!has_tag_name) {\n    compound_selector = ConsumeSimpleSelector(range);\n    if (!compound_selector) return nullptr;\n    if (compound_selector->selector->Match() == LynxCSSSelector::kPseudoElement)\n      restricting_pseudo_element_ =\n          compound_selector->selector->GetPseudoType();\n  }\n  element_name = ToLower(element_name);\n\n  while (std::unique_ptr<LynxCSSParserSelector> simple_selector =\n             ConsumeSimpleSelector(range)) {\n    if (simple_selector->selector->Match() == LynxCSSSelector::kPseudoElement)\n      restricting_pseudo_element_ = simple_selector->selector->GetPseudoType();\n\n    if (compound_selector) {\n      compound_selector = AddSimpleSelectorToCompound(\n          std::move(compound_selector), std::move(simple_selector));\n    } else {\n      compound_selector = std::move(simple_selector);\n    }\n  }\n\n  if (!compound_selector) {\n    return std::make_unique<LynxCSSParserSelector>(\n        ustring_helper::to_string(element_name));\n  }\n\n  // Prepending a type selector to the compound is\n  // unnecessary if this compound is an argument to a pseudo selector like\n  // :not(), since a type selector will be prepended at the top level of the\n  // selector if necessary. We need to propagate that context information here\n  // to tell if we are at the top level.\n  PrependTypeSelectorIfNeeded(has_tag_name, element_name,\n                              compound_selector.get());\n  return SplitCompoundAtImplicitShadowCrossingCombinator(\n      std::move(compound_selector));\n}\n\nstd::unique_ptr<LynxCSSParserSelector> CSSSelectorParser::ConsumeSimpleSelector(\n    CSSParserTokenRange& range) {\n  const CSSParserToken& token = range.Peek();\n  std::unique_ptr<LynxCSSParserSelector> selector;\n  if (token.GetType() == kHashToken)\n    selector = ConsumeId(range);\n  else if (token.GetType() == kDelimiterToken && token.Delimiter() == '.')\n    selector = ConsumeClass(range);\n  else if (token.GetType() == kLeftBracketToken)\n    selector = ConsumeAttribute(range);\n  else if (token.GetType() == kColonToken)\n    selector = ConsumePseudo(range);\n  else\n    return nullptr;\n  // TODO(futhark@chromium.org): crbug.com/578131\n  // The UASheetMode check is a work-around to allow this selector in\n  // mediaControls(New).css:\n  // video::-webkit-media-text-track-region-container.scrolling\n  if (!selector || (!IsSimpleSelectorValidAfterPseudoElement(\n                       *selector.get(), restricting_pseudo_element_))) {\n    failed_parsing_ = true;\n  }\n  return selector;\n}\n\nbool CSSSelectorParser::ConsumeName(CSSParserTokenRange& range,\n                                    std::u16string& name) {\n  name = CSSGlobalEmptyU16String();\n\n  const CSSParserToken& first_token = range.Peek();\n  if (first_token.GetType() == kIdentToken) {\n    name = first_token.Value();\n    range.Consume();\n  } else if (first_token.GetType() == kDelimiterToken &&\n             first_token.Delimiter() == '*') {\n    name = CSSGlobalStarU16String();\n    range.Consume();\n  } else if (first_token.GetType() == kDelimiterToken &&\n             first_token.Delimiter() == '|') {\n    // This is an empty namespace, which'll get assigned this value below\n    name = CSSGlobalEmptyU16String();\n  } else {\n    return false;\n  }\n\n  if (range.Peek().GetType() != kDelimiterToken ||\n      range.Peek().Delimiter() != '|')\n    return true;\n\n  if (range.Peek(1).GetType() == kIdentToken) {\n    range.Consume();\n    name = range.Consume().Value();\n  } else if (range.Peek(1).GetType() == kDelimiterToken &&\n             range.Peek(1).Delimiter() == '*') {\n    range.Consume();\n    range.Consume();\n    name = CSSGlobalStarU16String();\n  } else {\n    name = CSSGlobalEmptyU16String();\n    return false;\n  }\n\n  return true;\n}\n\nstd::unique_ptr<LynxCSSParserSelector> CSSSelectorParser::ConsumeId(\n    CSSParserTokenRange& range) {\n  DCHECK_EQ(range.Peek().GetType(), kHashToken);\n  if (range.Peek().GetHashTokenType() != kHashTokenId) return nullptr;\n  std::unique_ptr<LynxCSSParserSelector> selector =\n      std::make_unique<LynxCSSParserSelector>();\n  selector->selector->SetMatch(LynxCSSSelector::kId);\n  selector->selector->SetValue(\n      ustring_helper::to_string(range.Consume().Value()));\n  return selector;\n}\n\nstd::unique_ptr<LynxCSSParserSelector> CSSSelectorParser::ConsumeClass(\n    CSSParserTokenRange& range) {\n  DCHECK_EQ(range.Peek().GetType(), kDelimiterToken);\n  DCHECK_EQ(range.Peek().Delimiter(), '.');\n  range.Consume();\n  if (range.Peek().GetType() != kIdentToken) return nullptr;\n  std::unique_ptr<LynxCSSParserSelector> selector =\n      std::make_unique<LynxCSSParserSelector>();\n  selector->selector->SetMatch(LynxCSSSelector::kClass);\n  std::u16string value = range.Consume().Value();\n  selector->selector->SetValue(ustring_helper::to_string(value));\n  return selector;\n}\n\nstd::unique_ptr<LynxCSSParserSelector> CSSSelectorParser::ConsumeAttribute(\n    CSSParserTokenRange& range) {\n  DCHECK_EQ(range.Peek().GetType(), kLeftBracketToken);\n  CSSParserTokenRange block = range.ConsumeBlock();\n  block.ConsumeWhitespace();\n\n  std::u16string attribute_name;\n  if (!ConsumeName(block, attribute_name)) return nullptr;\n  if (attribute_name == CSSGlobalStarU16String() || attribute_name.empty())\n    return nullptr;\n  block.ConsumeWhitespace();\n\n  attribute_name = ToLower(attribute_name);\n  std::unique_ptr<LynxCSSParserSelector> selector =\n      std::make_unique<LynxCSSParserSelector>();\n\n  if (block.AtEnd()) {\n    selector->selector->SetAttribute(\n        ustring_helper::to_string(attribute_name),\n        LynxCSSSelector::AttributeMatchType::kCaseSensitive);\n    selector->selector->SetMatch(LynxCSSSelector::kAttributeSet);\n    return selector;\n  }\n\n  selector->selector->SetMatch(ConsumeAttributeMatch(block));\n\n  const CSSParserToken& attribute_value = block.ConsumeIncludingWhitespace();\n  if (attribute_value.GetType() != kIdentToken &&\n      attribute_value.GetType() != kStringToken)\n    return nullptr;\n  selector->selector->SetValue(\n      ustring_helper::to_string(attribute_value.Value()));\n  selector->selector->SetAttribute(ustring_helper::to_string(attribute_name),\n                                   ConsumeAttributeFlags(block));\n\n  if (!block.AtEnd()) return nullptr;\n  return selector;\n}\n\nstd::unique_ptr<LynxCSSParserSelector> CSSSelectorParser::ConsumePseudo(\n    CSSParserTokenRange& range) {\n  DCHECK_EQ(range.Peek().GetType(), kColonToken);\n  range.Consume();\n\n  int colons = 1;\n  if (range.Peek().GetType() == kColonToken) {\n    range.Consume();\n    colons++;\n  }\n\n  const CSSParserToken& token = range.Peek();\n  if (token.GetType() != kIdentToken && token.GetType() != kFunctionToken)\n    return nullptr;\n\n  std::unique_ptr<LynxCSSParserSelector> selector =\n      std::make_unique<LynxCSSParserSelector>();\n  selector->selector->SetMatch(colons == 1 ? LynxCSSSelector::kPseudoClass\n                                           : LynxCSSSelector::kPseudoElement);\n\n  const std::u16string& value = token.Value();\n  bool has_arguments = token.GetType() == kFunctionToken;\n  std::string lower_value = ToLowerASCII(value);\n  LynxCSSSelector::PseudoType pseudo_type =\n      CSSSelectorParser::ParsePseudoType(value, has_arguments);\n  selector->selector->SetValue(pseudo_type == LynxCSSSelector::kPseudoState\n                                   ? ustring_helper::to_string(value)\n                                   : lower_value);\n  selector->selector->UpdatePseudoType(pseudo_type);\n\n  if (selector->selector->Match() == LynxCSSSelector::kPseudoElement) {\n    switch (selector->selector->GetPseudoType()) {\n      case LynxCSSSelector::kPseudoBefore:\n      case LynxCSSSelector::kPseudoAfter:\n        break;\n      default:\n        break;\n    }\n  }\n\n  if (selector->selector->Match() == LynxCSSSelector::kPseudoElement &&\n      disallow_pseudo_elements_)\n    return nullptr;\n\n  if (token.GetType() == kIdentToken) {\n    range.Consume();\n    if (selector->selector->GetPseudoType() == LynxCSSSelector::kPseudoUnknown)\n      return nullptr;\n    return selector;\n  }\n\n  CSSParserTokenRange block = range.ConsumeBlock();\n  block.ConsumeWhitespace();\n  if (selector->selector->GetPseudoType() == LynxCSSSelector::kPseudoUnknown)\n    return nullptr;\n\n  switch (selector->selector->GetPseudoType()) {\n    case LynxCSSSelector::kPseudoNot: {\n      DisallowPseudoElementsScope scope(this);\n\n      std::unique_ptr<LynxCSSSelectorList> selector_list =\n          std::make_unique<LynxCSSSelectorList>();\n      *selector_list = ConsumeNestedSelectorList(block);\n      if (!selector_list->IsValid() || !block.AtEnd()) return nullptr;\n\n      selector->selector->SetSelectorList(std::move(selector_list));\n      return selector;\n    }\n    case LynxCSSSelector::kPseudoDir: {\n      const CSSParserToken& ident = block.ConsumeIncludingWhitespace();\n      if (ident.GetType() != kIdentToken || !block.AtEnd()) return nullptr;\n      selector->selector->SetArgument(ustring_helper::to_string(ident.Value()));\n      return selector;\n    }\n    case LynxCSSSelector::kPseudoLang: {\n      // FIXME: CSS Selectors Level 4 allows :lang(*-foo)\n      const CSSParserToken& ident = block.ConsumeIncludingWhitespace();\n      if (ident.GetType() != kIdentToken || !block.AtEnd()) return nullptr;\n      selector->selector->SetArgument(ustring_helper::to_string(ident.Value()));\n      return selector;\n    }\n    case LynxCSSSelector::kPseudoNthChild:\n    case LynxCSSSelector::kPseudoNthLastChild:\n    case LynxCSSSelector::kPseudoNthOfType:\n    case LynxCSSSelector::kPseudoNthLastOfType: {\n      std::pair<int, int> ab;\n      if (!ConsumeANPlusB(block, ab)) return nullptr;\n      block.ConsumeWhitespace();\n      if (!block.AtEnd()) return nullptr;\n      selector->selector->SetNth(ab.first, ab.second);\n      return selector;\n    }\n    default:\n      break;\n  }\n\n  return nullptr;\n}\n\nLynxCSSSelector::RelationType CSSSelectorParser::ConsumeCombinator(\n    CSSParserTokenRange& range) {\n  LynxCSSSelector::RelationType fallback_result = LynxCSSSelector::kSubSelector;\n  while (range.Peek().GetType() == kWhitespaceToken) {\n    range.Consume();\n    fallback_result = LynxCSSSelector::kDescendant;\n  }\n\n  if (range.Peek().GetType() != kDelimiterToken) return fallback_result;\n\n  switch (range.Peek().Delimiter()) {\n    case '+':\n      range.ConsumeIncludingWhitespace();\n      return LynxCSSSelector::kDirectAdjacent;\n\n    case '~':\n      range.ConsumeIncludingWhitespace();\n      return LynxCSSSelector::kIndirectAdjacent;\n\n    case '>':\n      range.ConsumeIncludingWhitespace();\n      return LynxCSSSelector::kChild;\n\n    default:\n      break;\n  }\n  return fallback_result;\n}\n\nLynxCSSSelector::MatchType CSSSelectorParser::ConsumeAttributeMatch(\n    CSSParserTokenRange& range) {\n  const CSSParserToken& token = range.ConsumeIncludingWhitespace();\n  switch (token.GetType()) {\n    case kIncludeMatchToken:\n      return LynxCSSSelector::kAttributeList;\n    case kDashMatchToken:\n      return LynxCSSSelector::kAttributeHyphen;\n    case kPrefixMatchToken:\n      return LynxCSSSelector::kAttributeBegin;\n    case kSuffixMatchToken:\n      return LynxCSSSelector::kAttributeEnd;\n    case kSubstringMatchToken:\n      return LynxCSSSelector::kAttributeContain;\n    case kDelimiterToken:\n      if (token.Delimiter() == '=') return LynxCSSSelector::kAttributeExact;\n      [[fallthrough]];\n    default:\n      failed_parsing_ = true;\n      return LynxCSSSelector::kAttributeExact;\n  }\n}\n\nLynxCSSSelector::AttributeMatchType CSSSelectorParser::ConsumeAttributeFlags(\n    CSSParserTokenRange& range) {\n  if (range.Peek().GetType() != kIdentToken)\n    return LynxCSSSelector::AttributeMatchType::kCaseSensitive;\n  const CSSParserToken& flag = range.ConsumeIncludingWhitespace();\n  if (EqualIgnoringASCIICase(flag.Value(), u\"i\"))\n    return LynxCSSSelector::AttributeMatchType::kCaseInsensitive;\n  else if (EqualIgnoringASCIICase(flag.Value(), u\"s\")/* &&\n           RuntimeEnabledFeatures::CSSCaseSensitiveSelectorEnabled()*/)\n    return LynxCSSSelector::AttributeMatchType::kCaseSensitiveAlways;\n  failed_parsing_ = true;\n  return LynxCSSSelector::AttributeMatchType::kCaseSensitive;\n}\n\nbool CSSSelectorParser::ConsumeANPlusB(CSSParserTokenRange& range,\n                                       std::pair<int, int>& result) {\n  const CSSParserToken& token = range.Consume();\n  if (token.GetType() == kNumberToken &&\n      token.GetNumericValueType() == kIntegerValueType) {\n    result = std::make_pair(0, token.NumericValue());\n    return true;\n  }\n  if (token.GetType() == kIdentToken) {\n    if (EqualIgnoringASCIICase(token.Value(), u\"odd\")) {\n      result = std::make_pair(2, 1);\n      return true;\n    }\n    if (EqualIgnoringASCIICase(token.Value(), u\"even\")) {\n      result = std::make_pair(2, 0);\n      return true;\n    }\n  }\n\n  // The 'n' will end up as part of an ident or dimension. For a valid <an+b>,\n  // this will store a string of the form 'n', 'n-', or 'n-123'.\n  std::u16string n_string;\n\n  if (token.GetType() == kDelimiterToken && token.Delimiter() == '+' &&\n      range.Peek().GetType() == kIdentToken) {\n    result.first = 1;\n    n_string = range.Consume().Value();\n  } else if (token.GetType() == kDimensionToken &&\n             token.GetNumericValueType() == kIntegerValueType) {\n    result.first = std::clamp<int64_t>(\n        static_cast<int64_t>(token.NumericValue()),\n        std::numeric_limits<int>::lowest(), std::numeric_limits<int>::max());\n    n_string = token.Value();\n  } else if (token.GetType() == kIdentToken) {\n    if (token.Value()[0] == '-') {\n      result.first = -1;\n      n_string = token.Value().substr(1);\n    } else {\n      result.first = 1;\n      n_string = token.Value();\n    }\n  }\n\n  range.ConsumeWhitespace();\n\n  if (n_string.empty() || !base::IsASCIIAlphaCaselessEqual(n_string[0], 'n'))\n    return false;\n  if (n_string.length() > 1 && n_string[1] != '-') return false;\n\n  if (n_string.length() > 2) {\n    bool valid;\n    std::u16string s = n_string.substr(1);\n    result.second = CharactersToInt(s.data(), s.length(),\n                                    NumberParsingOptions::kStrict, &valid);\n    return valid;\n  }\n\n  NumericSign sign = n_string.length() == 1 ? kNoSign : kMinusSign;\n  if (sign == kNoSign && range.Peek().GetType() == kDelimiterToken) {\n    char delimiter_sign = range.ConsumeIncludingWhitespace().Delimiter();\n    if (delimiter_sign == '+')\n      sign = kPlusSign;\n    else if (delimiter_sign == '-')\n      sign = kMinusSign;\n    else\n      return false;\n  }\n\n  if (sign == kNoSign && range.Peek().GetType() != kNumberToken) {\n    result.second = 0;\n    return true;\n  }\n\n  const CSSParserToken& b = range.Consume();\n  if (b.GetType() != kNumberToken ||\n      b.GetNumericValueType() != kIntegerValueType)\n    return false;\n  if ((b.GetNumericSign() == kNoSign) == (sign == kNoSign)) return false;\n  result.second = std::clamp<int64_t>(static_cast<int64_t>(b.NumericValue()),\n                                      std::numeric_limits<int>::lowest(),\n                                      std::numeric_limits<int>::max());\n  if (sign == kMinusSign) {\n    // Negating minimum integer returns itself, instead return max integer.\n    if (UNLIKELY(result.second == std::numeric_limits<int>::min()))\n      result.second = std::numeric_limits<int>::max();\n    else\n      result.second = -result.second;\n  }\n  return true;\n}\n\nvoid CSSSelectorParser::PrependTypeSelectorIfNeeded(\n    bool has_q_name, const std::u16string& element_name,\n    LynxCSSParserSelector* compound_selector) {\n  if (!has_q_name &&\n      !NeedsImplicitShadowCombinatorForMatching(compound_selector))\n    return;\n\n  std::u16string determined_element_name =\n      !has_q_name ? CSSGlobalStarU16String() : element_name;\n  std::u16string tag = determined_element_name;\n\n  if (tag != CSSGlobalStarU16String() ||\n      NeedsImplicitShadowCombinatorForMatching(compound_selector)) {\n    std::unique_ptr<LynxCSSParserSelector> second =\n        std::make_unique<LynxCSSParserSelector>();\n    second->selector = std::move(compound_selector->selector);\n    second->tag_history = std::move(compound_selector->tag_history);\n    compound_selector->tag_history = std::move(second);\n    compound_selector->selector = std::make_unique<LynxCSSSelector>(\n        ustring_helper::to_string(tag),\n        determined_element_name == CSSGlobalStarU16String());\n  }\n}\n\nstd::unique_ptr<LynxCSSParserSelector>\nCSSSelectorParser::AddSimpleSelectorToCompound(\n    std::unique_ptr<LynxCSSParserSelector> compound_selector,\n    std::unique_ptr<LynxCSSParserSelector> simple_selector) {\n  AppendTagHistory(compound_selector.get(), LynxCSSSelector::kSubSelector,\n                   std::move(simple_selector));\n  return compound_selector;\n}\n\nstd::unique_ptr<LynxCSSParserSelector>\nCSSSelectorParser::SplitCompoundAtImplicitShadowCrossingCombinator(\n    std::unique_ptr<LynxCSSParserSelector> compound_selector) {\n  // The tagHistory is a linked list that stores combinator separated compound\n  // selectors from right-to-left. Yet, within a single compound selector,\n  // stores the simple selectors from left-to-right.\n  //\n  // \".a.b > div#id\" is stored in a tagHistory as [div, #id, .a, .b], each\n  // element in the list stored with an associated relation (combinator or\n  // SubSelector).\n  //\n  // ::cue, ::shadow, and custom pseudo elements have an implicit ShadowPseudo\n  // combinator to their left, which really makes for a new compound selector,\n  // yet it's consumed by the selector parser as a single compound selector.\n  //\n  // Example:\n  //\n  // input#x::-webkit-clear-button -> [ ::-webkit-clear-button, input, #x ]\n  //\n  // Likewise, ::slotted() pseudo element has an implicit ShadowSlot combinator\n  // to its left for finding matching slot element in other TreeScope.\n  //\n  // ::part has a implicit ShadowPart combinator to it's left finding the host\n  // element in the scope of the style rule.\n  //\n  // Example:\n  //\n  // slot[name=foo]::slotted(div) -> [ ::slotted(div), slot, [name=foo] ]\n  LynxCSSParserSelector* split_after = compound_selector.get();\n\n  while (split_after->tag_history && !NeedsImplicitShadowCombinatorForMatching(\n                                         split_after->tag_history.get())) {\n    split_after = split_after->tag_history.get();\n  }\n\n  if (!split_after->tag_history.get()) {\n    return compound_selector;\n  }\n\n  std::unique_ptr<LynxCSSParserSelector> remaining =\n      std::move(split_after->tag_history);\n  LynxCSSSelector::RelationType relation =\n      GetImplicitShadowCombinatorForMatching(remaining.get());\n  // We might need to split the compound twice since ::placeholder is allowed\n  // after ::slotted and they both need an implicit combinator for matching.\n  remaining =\n      SplitCompoundAtImplicitShadowCrossingCombinator(std::move(remaining));\n  AppendTagHistory(remaining.get(), relation, std::move(compound_selector));\n  return remaining;\n}\n\nsize_t CSSSelectorParser::FlattenedSize(\n    const LynxCSSSelectorVector& selector_vector) {\n  size_t flattened_size = 0;\n  for (const std::unique_ptr<LynxCSSParserSelector>& selector_ptr :\n       selector_vector) {\n    for (LynxCSSParserSelector* selector = selector_ptr.get(); selector;\n         selector = selector->tag_history.get())\n      ++flattened_size;\n  }\n  // DCHECK(flattened_size);\n  return flattened_size;\n}\n\nstatic size_t SelectorIndex(LynxCSSSelector* selector_array,\n                            const LynxCSSSelector& selector) {\n  return static_cast<size_t>(&selector - selector_array);\n}\n\nstatic const LynxCSSSelector& SelectorAt(LynxCSSSelector* selector_array,\n                                         size_t index) {\n  return selector_array[index];\n}\n\nstatic size_t IndexOfNextSelectorAfter(LynxCSSSelector* selector_array,\n                                       size_t index) {\n  const LynxCSSSelector& current = SelectorAt(selector_array, index);\n  const LynxCSSSelector* next = LynxCSSSelectorList::Next(current);\n  if (!next) return UINT_MAX;\n  return SelectorIndex(selector_array, *next);\n}\n\nvoid CSSSelectorParser::AdoptSelectorVector(\n    LynxCSSSelectorVector& selector_vector, LynxCSSSelector* selector_array,\n    size_t flattened_size) {\n  DCHECK_EQ(flattened_size, FlattenedSize(selector_vector));\n  size_t array_index = 0;\n  for (const std::unique_ptr<LynxCSSParserSelector>& selector_ptr :\n       selector_vector) {\n    LynxCSSParserSelector* current = selector_ptr.get();\n    while (current) {\n      selector_array[array_index] = std::move(*current->selector);\n      current = current->tag_history.get();\n      DCHECK(!selector_array[array_index].IsLastInSelectorList());\n      if (current) {\n        selector_array[array_index].SetLastInTagHistory(false);\n      }\n      ++array_index;\n    }\n    DCHECK(selector_array[array_index - 1].IsLastInTagHistory());\n  }\n  DCHECK_EQ(flattened_size, array_index);\n  selector_array[array_index - 1].SetLastInSelectorList(true);\n  for (size_t selector_index = 0; selector_index != UINT_MAX;\n       selector_index =\n           IndexOfNextSelectorAfter(selector_array, selector_index)) {\n    selector_array[selector_index].UpdateSpecificity(\n        selector_array[selector_index].CalcSpecificity());\n  }\n\n  selector_vector.clear();\n}\n\nLynxCSSSelectorList CSSSelectorParser::AdoptSelectorVector(\n    LynxCSSSelectorVector& selector_vector) {\n  if (selector_vector.empty()) {\n    return {};\n  }\n\n  size_t flattened_size = FlattenedSize(selector_vector);\n\n  auto selector_array = std::make_unique<LynxCSSSelector[]>(flattened_size);\n  AdoptSelectorVector(selector_vector, selector_array.get(), flattened_size);\n  return {std::move(selector_array)};\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/selector/css_selector_parser.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2014 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#ifndef CORE_RENDERER_CSS_NG_SELECTOR_CSS_SELECTOR_PARSER_H_\n#define CORE_RENDERER_CSS_NG_SELECTOR_CSS_SELECTOR_PARSER_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"core/renderer/css/ng/parser/css_parser_token_range.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n\nnamespace lynx {\nnamespace css {\n\nclass CSSParserContext;\nclass CSSParserTokenStream;\nclass LynxCSSSelectorList;\n\nstruct LynxCSSParserSelector {\n  LynxCSSParserSelector() : selector(std::make_unique<LynxCSSSelector>()) {}\n  explicit LynxCSSParserSelector(const std::string& tag_name)\n      : selector(std::make_unique<LynxCSSSelector>(tag_name)) {}\n  std::unique_ptr<LynxCSSSelector> selector;\n  std::unique_ptr<LynxCSSParserSelector> tag_history;\n};\n\n// SelectorVector is the list of CSS selectors as it is parsed,\n// where each selector can contain others (in a tree). Typically,\n// before actual use, you would convert it into a flattened list using\n// LynxCSSParserSelector::AdoptSelectorVector(), but it can be useful to have\n// this temporary form to find out e.g. how many bytes it will occupy (e.g. in\n// StyleRule::Create) before you actually make that allocation.\nusing LynxCSSSelectorVector =\n    std::vector<std::unique_ptr<LynxCSSParserSelector>>;\n\n// FIXME: We should consider building CSSSelectors directly instead of using\n// the intermediate LynxCSSParserSelector.\nclass CSSSelectorParser {\n public:\n  // Both ParseSelector() and ConsumeSelector() return an empty list\n  // on error.\n  static LynxCSSSelectorVector ParseSelector(CSSParserTokenRange,\n                                             const CSSParserContext*);\n\n  static bool ConsumeANPlusB(CSSParserTokenRange&, std::pair<int, int>&);\n\n  static LynxCSSSelector::PseudoType ParsePseudoType(const std::u16string&,\n                                                     bool has_arguments);\n  // Finds out how many elements one would need to allocate for\n  // AdoptSelectorVector(), ie., storing the selector tree as a flattened list.\n  // The returned count is in LynxCSSSelector elements, not bytes.\n  static size_t FlattenedSize(const LynxCSSSelectorVector& selector_vector);\n  static LynxCSSSelectorList AdoptSelectorVector(\n      LynxCSSSelectorVector& selector_vector);\n  static void AdoptSelectorVector(LynxCSSSelectorVector& selector_vector,\n                                  LynxCSSSelector* selector_array,\n                                  size_t flattened_size);\n\n private:\n  CSSSelectorParser(const CSSParserContext*);\n\n  // These will all consume trailing comments if successful\n\n  LynxCSSSelectorVector ConsumeComplexSelectorList(CSSParserTokenRange&);\n  // Consumes a complex selector list if inside_compound_pseudo_ is false,\n  // otherwise consumes a compound selector list.\n  LynxCSSSelectorList ConsumeNestedSelectorList(CSSParserTokenRange&);\n\n  std::unique_ptr<LynxCSSParserSelector> ConsumeComplexSelector(\n      CSSParserTokenRange&);\n\n  // ConsumePartialComplexSelector() method provides the common logic of\n  // consuming a complex selector and consuming a relative selector.\n  //\n  // After consuming the left-most combinator of a relative selector, we can\n  // consume the remaining selectors with the common logic.\n  // For example, after consuming the left-most combinator '~' of the relative\n  // selector '~ .a ~ .b', we can consume remaining selectors '.a ~ .b'\n  // with this method.\n  //\n  // After consuming the left-most compound selector and a combinator of a\n  // complex selector, we can also use this method to consume the remaining\n  // selectors of the complex selector.\n  std::unique_ptr<LynxCSSParserSelector> ConsumePartialComplexSelector(\n      CSSParserTokenRange&,\n      LynxCSSSelector::RelationType& /* current combinator */,\n      std::unique_ptr<LynxCSSParserSelector> /* previous compound selector */,\n      unsigned& /* previous compound flags */);\n\n  std::unique_ptr<LynxCSSParserSelector> ConsumeCompoundSelector(\n      CSSParserTokenRange&);\n  // This doesn't include element names, since they're handled specially\n  std::unique_ptr<LynxCSSParserSelector> ConsumeSimpleSelector(\n      CSSParserTokenRange&);\n\n  bool ConsumeName(CSSParserTokenRange&, std::u16string& name);\n\n  // These will return nullptr when the selector is invalid\n  std::unique_ptr<LynxCSSParserSelector> ConsumeId(CSSParserTokenRange&);\n  std::unique_ptr<LynxCSSParserSelector> ConsumeClass(CSSParserTokenRange&);\n  std::unique_ptr<LynxCSSParserSelector> ConsumePseudo(CSSParserTokenRange&);\n  std::unique_ptr<LynxCSSParserSelector> ConsumeAttribute(CSSParserTokenRange&);\n\n  LynxCSSSelector::RelationType ConsumeCombinator(CSSParserTokenRange&);\n  LynxCSSSelector::MatchType ConsumeAttributeMatch(CSSParserTokenRange&);\n  LynxCSSSelector::AttributeMatchType ConsumeAttributeFlags(\n      CSSParserTokenRange&);\n\n  void PrependTypeSelectorIfNeeded(bool has_element_name,\n                                   const std::u16string& element_name,\n                                   LynxCSSParserSelector*);\n  static std::unique_ptr<LynxCSSParserSelector> AddSimpleSelectorToCompound(\n      std::unique_ptr<LynxCSSParserSelector> compound_selector,\n      std::unique_ptr<LynxCSSParserSelector> simple_selector);\n  static std::unique_ptr<LynxCSSParserSelector>\n  SplitCompoundAtImplicitShadowCrossingCombinator(\n      std::unique_ptr<LynxCSSParserSelector> compound_selector);\n\n  bool failed_parsing_ = false;\n  bool disallow_pseudo_elements_ = false;\n  // When parsing a compound which includes a pseudo-element, the simple\n  // selectors permitted to follow that pseudo-element may be restricted.\n  // If this is the case, then restricting_pseudo_element_ will be set to the\n  // PseudoType of the pseudo-element causing the restriction.\n  LynxCSSSelector::PseudoType restricting_pseudo_element_ =\n      LynxCSSSelector::kPseudoUnknown;\n\n  class DisallowPseudoElementsScope {\n   public:\n    explicit DisallowPseudoElementsScope(CSSSelectorParser* parser)\n        : parser_(parser), was_disallowed_(parser_->disallow_pseudo_elements_) {\n      parser_->disallow_pseudo_elements_ = true;\n    }\n    DisallowPseudoElementsScope(const DisallowPseudoElementsScope&) = delete;\n    DisallowPseudoElementsScope& operator=(const DisallowPseudoElementsScope&) =\n        delete;\n\n    ~DisallowPseudoElementsScope() {\n      parser_->disallow_pseudo_elements_ = was_disallowed_;\n    }\n\n   private:\n    CSSSelectorParser* parser_;\n    bool was_disallowed_;\n  };\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_SELECTOR_CSS_SELECTOR_PARSER_H_\n"
  },
  {
    "path": "core/renderer/css/ng/selector/css_selector_parser_test.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n// Copyright 2015 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n#include \"core/renderer/css/ng/selector/css_selector_parser.h\"\n\n#include <limits>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n#include \"core/renderer/css/ng/parser/css_tokenizer.h\"\n#include \"core/renderer/css/ng/selector/css_parser_context.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace css {\n\ntypedef struct {\n  const char* input;\n  const int a;\n  const int b;\n} ANPlusBTestCase;\n\nstruct SelectorTestCase {\n  // The input string to parse as a selector list.\n  const char* input;\n\n  // The expected serialization of the parsed selector list. If nullptr, then\n  // the expected serialization is the same as the input value.\n  //\n  // For selector list that are expected to fail parsing, use the empty\n  // string \"\".\n  const char* expected = nullptr;\n};\n\nclass SelectorParseTest : public ::testing::TestWithParam<SelectorTestCase> {};\n\nTEST_P(SelectorParseTest, Parse) {\n  auto param = GetParam();\n  SCOPED_TRACE(param.input);\n  CSSParserContext context;\n  CSSTokenizer tokenizer(param.input);\n  const auto tokens = tokenizer.TokenizeToEOF();\n  CSSParserTokenRange range(tokens);\n  LynxCSSSelectorVector vector =\n      CSSSelectorParser::ParseSelector(range, &context);\n  auto list = CSSSelectorParser::AdoptSelectorVector(vector);\n  const char* expected = param.expected ? param.expected : param.input;\n  EXPECT_EQ(std::string(expected), list.SelectorsText());\n}\n\nTEST(CSSSelectorParserTest, SimpleSelector) {\n  const char* test_cases[] = {\n      \"div .a:hover\",\n  };\n\n  for (auto* test_case : test_cases) {\n    SCOPED_TRACE(test_case);\n\n    CSSParserContext context;\n    CSSTokenizer tokenizer(test_case);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    auto list = CSSSelectorParser::AdoptSelectorVector(vector);\n  }\n}\n\nTEST(CSSSelectorParserTest, ValidANPlusB) {\n  ANPlusBTestCase test_cases[] = {\n      {\"odd\", 2, 1},\n      {\"OdD\", 2, 1},\n      {\"even\", 2, 0},\n      {\"EveN\", 2, 0},\n      {\"0\", 0, 0},\n      {\"8\", 0, 8},\n      {\"+12\", 0, 12},\n      {\"-14\", 0, -14},\n\n      {\"0n\", 0, 0},\n      {\"16N\", 16, 0},\n      {\"-19n\", -19, 0},\n      {\"+23n\", 23, 0},\n      {\"n\", 1, 0},\n      {\"N\", 1, 0},\n      {\"+n\", 1, 0},\n      {\"-n\", -1, 0},\n      {\"-N\", -1, 0},\n\n      {\"6n-3\", 6, -3},\n      {\"-26N-33\", -26, -33},\n      {\"n-18\", 1, -18},\n      {\"+N-5\", 1, -5},\n      {\"-n-7\", -1, -7},\n\n      {\"0n+0\", 0, 0},\n      {\"10n+5\", 10, 5},\n      {\"10N +5\", 10, 5},\n      {\"10n -5\", 10, -5},\n      {\"N+6\", 1, 6},\n      {\"n +6\", 1, 6},\n      {\"+n -7\", 1, -7},\n      {\"-N -8\", -1, -8},\n      {\"-n+9\", -1, 9},\n\n      {\"33N- 22\", 33, -22},\n      {\"+n- 25\", 1, -25},\n      {\"N- 46\", 1, -46},\n      {\"n- 0\", 1, 0},\n      {\"-N- 951\", -1, -951},\n      {\"-n- 951\", -1, -951},\n\n      {\"29N + 77\", 29, 77},\n      {\"29n - 77\", 29, -77},\n      {\"+n + 61\", 1, 61},\n      {\"+N - 63\", 1, -63},\n      {\"+n/**/- 48\", 1, -48},\n      {\"-n + 81\", -1, 81},\n      {\"-N - 88\", -1, -88},\n\n      {\"3091970736n + 1\", std::numeric_limits<int>::max(), 1},\n      {\"-3091970736n + 1\", std::numeric_limits<int>::min(), 1},\n      // B is calculated as +ve first, then negated.\n      {\"N- 3091970736\", 1, -std::numeric_limits<int>::max()},\n      {\"N+ 3091970736\", 1, std::numeric_limits<int>::max()},\n  };\n\n  for (auto test_case : test_cases) {\n    SCOPED_TRACE(test_case.input);\n\n    std::pair<int, int> ab;\n    CSSTokenizer tokenizer(test_case.input);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    bool passed = CSSSelectorParser::ConsumeANPlusB(range, ab);\n    EXPECT_TRUE(passed);\n    EXPECT_EQ(test_case.a, ab.first);\n    EXPECT_EQ(test_case.b, ab.second);\n  }\n}\n\nTEST(CSSSelectorParserTest, InvalidANPlusB) {\n  // Some of these have token range prefixes which are valid <an+b> and could\n  // in theory be valid in consumeANPlusB, but this behaviour isn't needed\n  // anywhere and not implemented.\n  const char* test_cases[] = {\n      \" odd\",     \"+ n\",     \"3m+4\",  \"12n--34\",  \"12n- -34\",\n      \"12n- +34\", \"23n-+43\", \"10n 5\", \"10n + +5\", \"10n + -5\",\n  };\n\n  for (auto* test_case : test_cases) {\n    SCOPED_TRACE(test_case);\n\n    std::pair<int, int> ab;\n    CSSTokenizer tokenizer(test_case);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    bool passed = CSSSelectorParser::ConsumeANPlusB(range, ab);\n    EXPECT_FALSE(passed);\n  }\n}\n\nTEST(CSSSelectorParserTest, PseudoElementsInCompoundLists) {\n  const char* test_cases[] = {\":not(::before)\",\n                              \":not(::content)\",\n                              \":host(::before)\",\n                              \":host(::content)\",\n                              \":host-context(::before)\",\n                              \":host-context(::content)\",\n                              \":-webkit-any(::after, ::before)\",\n                              \":-webkit-any(::content, span)\"};\n\n  CSSParserContext context;\n\n  for (auto* test_case : test_cases) {\n    CSSTokenizer tokenizer(test_case);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    EXPECT_EQ(vector.size(), 0u);\n  }\n}\n\nTEST(CSSSelectorParserTest, InvalidSimpleAfterPseudoElementInCompound) {\n  const char* test_cases[] = {\n      \"::before#id\",\n      \"::after:hover\",\n      \".class::content::before\",\n      \"::shadow.class\",\n      \"::selection:window-inactive::before\",\n      \"::-webkit-volume-slider.class\",\n      \"::before:not(.a)\",\n      \"::shadow:not(::after)\",\n      \"::-webkit-scrollbar:vertical:not(:first-child)\",\n      \"video::-webkit-media-text-track-region-container.scrolling\",\n      \"div ::before.a\",\n      \"::slotted(div):hover\",\n      \"::slotted(div)::slotted(span)\",\n      \"::slotted(div)::before:hover\",\n      \"::slotted(div)::before::slotted(span)\",\n      \"::slotted(*)::first-letter\",\n      \"::slotted(.class)::first-line\",\n      \"::slotted([attr])::-webkit-scrollbar\"};\n\n  for (auto* test_case : test_cases) {\n    CSSTokenizer tokenizer(test_case);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    CSSParserContext context;\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    EXPECT_EQ(vector.size(), 0u);\n  }\n}\n\nTEST(CSSSelectorParserTest, InvalidPseudoElementInNonRightmostCompound) {\n  const char* test_cases[] = {\"::-webkit-volume-slider *\", \"::before *\",\n                              \"::-webkit-scrollbar *\", \"::cue *\",\n                              \"::selection *\"};\n\n  for (auto* test_case : test_cases) {\n    CSSTokenizer tokenizer(test_case);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    CSSParserContext context;\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    EXPECT_EQ(vector.size(), 0u);\n  }\n}\n\nTEST(CSSSelectorParserTest, UnexpectedPipe) {\n  const char* test_cases[] = {\"div | .c\", \"| div\", \" | div\"};\n\n  CSSParserContext context;\n\n  for (auto* test_case : test_cases) {\n    CSSTokenizer tokenizer(test_case);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    EXPECT_EQ(vector.size(), 0u);\n  }\n}\n\nTEST(CSSSelectorParserTest, SerializedUniversal) {\n  const char* test_cases[][2] = {\n      {\"*\", \"*\"},\n  };\n\n  CSSParserContext context;\n\n  for (auto** test_case : test_cases) {\n    SCOPED_TRACE(test_case[0]);\n    CSSTokenizer tokenizer(test_case[0]);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    LynxCSSSelectorList list = CSSSelectorParser::AdoptSelectorVector(vector);\n    EXPECT_TRUE(list.IsValid());\n    EXPECT_EQ(test_case[1], list.SelectorsText());\n  }\n}\n\nTEST(CSSSelectorParserTest, AttributeSelectorUniversalInvalid) {\n  const char* test_cases[] = {\"[*]\", \"[*|*]\"};\n\n  CSSParserContext context;\n  for (auto* test_case : test_cases) {\n    SCOPED_TRACE(test_case);\n    CSSTokenizer tokenizer(test_case);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    EXPECT_EQ(vector.size(), 0u);\n  }\n}\n\nnamespace {\n\nconst auto TagLocalName = [](const LynxCSSSelector* selector) {\n  return selector->Value();\n};\n\nconst auto AttributeLocalName = [](const LynxCSSSelector* selector) {\n  return selector->Attribute();\n};\n\nconst auto SelectorValue = [](const LynxCSSSelector* selector) {\n  return selector->Value();\n};\n\nstruct ASCIILowerTestCase {\n  const char* input;\n  const char16_t* expected;\n  std::function<std::string(const LynxCSSSelector*)> getter;\n};\n\n}  // namespace\n\nTEST(CSSSelectorParserTest, ASCIILowerHTMLStrict) {\n  const ASCIILowerTestCase test_cases[] = {\n      {\"\\\\212a bd\", u\"\\u212abd\", TagLocalName},\n      {\"[\\\\212al-ass]\", u\"\\u212al-ass\", AttributeLocalName},\n      {\".\\\\212al-ass\", u\"\\u212al-ass\", SelectorValue},\n      {\"#\\\\212al-ass\", u\"\\u212al-ass\", SelectorValue}};\n\n  CSSParserContext context;\n\n  for (auto test_case : test_cases) {\n    SCOPED_TRACE(test_case.input);\n    CSSTokenizer tokenizer(test_case.input);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    EXPECT_GT(vector.size(), 0u);\n    LynxCSSSelectorList list = CSSSelectorParser::AdoptSelectorVector(vector);\n    EXPECT_TRUE(list.IsValid());\n    const LynxCSSSelector* selector = list.First();\n    ASSERT_TRUE(selector);\n    EXPECT_EQ(test_case.expected,\n              ustring_helper::from_string(test_case.getter(selector)));\n  }\n}\n\nTEST(CSSSelectorParserTest, ImplicitShadowCrossingCombinators) {\n  struct ShadowCombinatorTest {\n    const char* input;\n    std::vector<std::pair<std::string, LynxCSSSelector::RelationType>>\n        expectation;\n  };\n\n  const ShadowCombinatorTest test_cases[] = {\n      {\n          \"*::placeholder\",\n          {\n              {\"placeholder\", LynxCSSSelector::kUAShadow},\n              {CSSGlobalStarString(), LynxCSSSelector::kSubSelector},\n          },\n      },\n      {\n          \"::selection\",\n          {\n              {\"selection\", LynxCSSSelector::kUAShadow},\n              {CSSGlobalStarString(), LynxCSSSelector::kSubSelector},\n          },\n      },\n  };\n\n  CSSParserContext context;\n\n  for (auto test_case : test_cases) {\n    SCOPED_TRACE(test_case.input);\n    CSSTokenizer tokenizer(test_case.input);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    LynxCSSSelectorList list = CSSSelectorParser::AdoptSelectorVector(vector);\n    EXPECT_TRUE(list.IsValid());\n    const LynxCSSSelector* selector = list.First();\n    for (auto sub_expectation : test_case.expectation) {\n      ASSERT_TRUE(selector);\n      std::string selector_value = selector->Value();\n      EXPECT_EQ(sub_expectation.first, selector_value);\n      EXPECT_EQ(sub_expectation.second, selector->Relation());\n      selector = selector->TagHistory();\n    }\n    EXPECT_FALSE(selector);\n  }\n}\n\nstatic const SelectorTestCase lynx_css_selector_parser_test_data[] = {\n    {\"div\", \"div\"},\n    {\":active\", \":active\"},\n    {R\"CSS(.\\[item\\])CSS\", \".[item]\"},\n    {\".\\\\[item\\\\]\", \".[item]\"},\n    {\"div.class\", \"div.class\"},\n    {\"div:active\", \"div:active\"},\n    {\"#list:focus\", \"#list:focus\"},\n    {\"div:hover\", \"div:hover\"},\n    {\"input::placeholder\", \"input::placeholder\"},\n    {\"::selection\", \"::selection\"},\n    {\"#text::selection\", \"#text::selection\"},\n    {\"#main\", \"#main\"},\n    {\".a.b.c\", \".a.b.c\"},\n    {\"#main div\", \"#main div\"},\n    {\"#main div .a\", \"#main div .a\"},\n    {\"div .a.b\", \"div .a.b\"},\n    {\"view text\", \"view text\"},\n    {\"div *\", \"div *\"},\n    {\"div text *\", \"div text *\"},\n    // {\"view[flatten=\\\"false\\\"]\", \"view[flatten=false]\"},\n    // {\"a[title]\", \"a[title]\"},\n    {\"div text *\", \"div text *\"},\n    {\"div text *\", \"div text *\"},\n    {\"*\", \"*\"},\n};\n\nINSTANTIATE_TEST_SUITE_P(LynxCSSSelectorParserTest, SelectorParseTest,\n                         testing::ValuesIn(lynx_css_selector_parser_test_data));\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/selector/lynx_css_selector.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/selector/lynx_css_selector.h\"\n\n#include <limits>\n#include <memory>\n#include <utility>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n\nnamespace lynx {\nnamespace css {\n\nvoid LynxCSSSelector::FromLepus(LynxCSSSelector& s, const lepus_value& value) {\n  if (!value.IsArray()) {\n    return;\n  }\n  const auto& arr = value.Array();\n  uint32_t bit = arr->get(0).UInt32();\n  s.relation_ = bit & 0xf;\n  s.match_ = bit >> 4 & 0xf;\n  s.pseudo_type_ = bit >> 8 & 0xff;\n  s.is_last_in_selector_list_ = bit >> 16 & 1;\n  s.is_last_in_tag_history_ = bit >> 17 & 1;\n  s.has_extra_data_ = bit >> 18 & 1;\n  s.tag_is_implicit_ = bit >> 19 & 1;\n\n  s.specificity_ = arr->get(1).UInt32();\n  if (s.has_extra_data_) {\n    const auto& extra_arr = arr->get(2).Array();\n    const auto& v = extra_arr->get(0).StdString();\n    s.extra_data_ = std::make_unique<LynxCSSSelectorExtraData>(v);\n    s.extra_data_->match_type_ =\n        static_cast<LynxCSSSelectorExtraData::MatchType>(\n            extra_arr->get(1).UInt32());\n    const auto& bits = extra_arr->get(2).Array();\n    if (s.extra_data_->match_type_ ==\n        LynxCSSSelectorExtraData::MatchType::kNth) {\n      s.extra_data_->bits_.nth_.a_ = bits->get(0).Int32();\n      s.extra_data_->bits_.nth_.b_ = bits->get(1).Int32();\n    } else if (s.extra_data_->match_type_ ==\n               LynxCSSSelectorExtraData::MatchType::kAttr) {\n      s.extra_data_->bits_.attr_.attribute_match_ =\n          static_cast<AttributeMatchType>(bits->get(0).UInt32());\n      s.extra_data_->bits_.attr_.is_case_sensitive_attribute_ =\n          bits->get(1).Bool();\n    }\n\n    s.extra_data_->attribute_ = extra_arr->get(3).StdString();\n    s.extra_data_->argument_ = extra_arr->get(4).StdString();\n    bool has_selector_list = extra_arr->get(5).IsArray();\n    if (has_selector_list) {\n      const auto& selector_list_arr = extra_arr->get(5).Array();\n      auto selector_array =\n          selector_list_arr->size() == 0\n              ? nullptr\n              : std::make_unique<LynxCSSSelector[]>(selector_list_arr->size());\n      for (size_t i = 0; i < selector_list_arr->size(); ++i) {\n        FromLepus(selector_array[i], selector_list_arr->get(i));\n      }\n      s.extra_data_->selector_list_ =\n          std::make_unique<LynxCSSSelectorList>(std::move(selector_array));\n    }\n  } else {\n    s.value_ = arr->get(2).StdString();\n  }\n}\n\nlepus_value LynxCSSSelector::ToLepus() const {\n  auto arr = lepus::CArray::Create();\n\n  uint32_t bit = 0;\n  bit |= relation_;\n  bit |= match_ << 4;\n  bit |= pseudo_type_ << 8;\n  bit |= is_last_in_selector_list_ << 16;\n  bit |= is_last_in_tag_history_ << 17;\n  bit |= has_extra_data_ << 18;\n  bit |= tag_is_implicit_ << 19;\n\n  arr->emplace_back(bit);\n  arr->emplace_back(specificity_);\n  if (has_extra_data_) {\n    auto extra_arr = lepus::CArray::Create();\n    extra_arr->emplace_back(extra_data_->value_);\n    extra_arr->emplace_back(static_cast<uint32_t>(extra_data_->match_type_));\n\n    {\n      auto bits = lepus::CArray::Create();\n      if (extra_data_->match_type_ ==\n          LynxCSSSelectorExtraData::MatchType::kNth) {\n        bits->emplace_back(extra_data_->NthAValue());\n        bits->emplace_back(extra_data_->NthBValue());\n      } else if (extra_data_->match_type_ ==\n                 LynxCSSSelectorExtraData::MatchType::kAttr) {\n        bits->emplace_back(\n            static_cast<uint32_t>(extra_data_->bits_.attr_.attribute_match_));\n        bits->emplace_back(\n            extra_data_->bits_.attr_.is_case_sensitive_attribute_);\n      } else if (extra_data_->match_type_ ==\n                 LynxCSSSelectorExtraData::MatchType::kHas) {\n        bits->emplace_back(extra_data_->bits_.has_.contains_pseudo_);\n        bits->emplace_back(\n            extra_data_->bits_.has_.contains_complex_logical_combinations_);\n      }\n      extra_arr->emplace_back(std::move(bits));\n    }\n\n    extra_arr->emplace_back(extra_data_->attribute_);\n    extra_arr->emplace_back(extra_data_->argument_);\n    if (SelectorList()) {\n      auto selector_list_arr = lepus::CArray::Create();\n      auto current = SelectorList()->First();\n      while (current) {\n        selector_list_arr->emplace_back(current->ToLepus());\n        if (current->IsLastInTagHistory() && current->IsLastInSelectorList()) {\n          break;\n        }\n        current++;\n      }\n      extra_arr->emplace_back(std::move(selector_list_arr));\n    } else {\n      extra_arr->emplace_back(false);\n    }\n\n    arr->emplace_back(std::move(extra_arr));\n  } else {\n    arr->emplace_back(value_);\n  }\n\n  return lepus_value(std::move(arr));\n}\n\nvoid LynxCSSSelector::CreateExtraData() {\n  if (has_extra_data_) return;\n  extra_data_ = std::make_unique<LynxCSSSelectorExtraData>(value_);\n  value_.clear();\n  has_extra_data_ = true;\n}\n\nvoid LynxCSSSelector::SetAttribute(const std::string& value,\n                                   AttributeMatchType match_type) {\n  CreateExtraData();\n  extra_data_->attribute_ = value;\n  extra_data_->match_type_ = LynxCSSSelectorExtraData::MatchType::kAttr;\n  extra_data_->bits_.attr_.attribute_match_ = match_type;\n  extra_data_->bits_.attr_.is_case_sensitive_attribute_ = true;\n}\n\nvoid LynxCSSSelector::SetArgument(const std::string& value) {\n  CreateExtraData();\n  extra_data_->argument_ = value;\n}\n\nvoid LynxCSSSelector::SetSelectorList(\n    std::unique_ptr<LynxCSSSelectorList> selector_list) {\n  CreateExtraData();\n  extra_data_->selector_list_ = std::move(selector_list);\n}\n\nvoid LynxCSSSelector::SetNth(int a, int b) {\n  CreateExtraData();\n  extra_data_->match_type_ = LynxCSSSelectorExtraData::MatchType::kNth;\n  extra_data_->bits_.nth_.a_ = a;\n  extra_data_->bits_.nth_.b_ = b;\n}\n\nbool LynxCSSSelector::MatchNth(unsigned count) const {\n  DCHECK(has_extra_data_);\n  return extra_data_->MatchNth(count);\n}\n\nconst std::string& LynxCSSSelector::Argument() const {\n  return has_extra_data_ ? extra_data_->argument_ : CSSGlobalEmptyString();\n}\n\nconst LynxCSSSelector* LynxCSSSelector::SelectorListSelector() const {\n  if (has_extra_data_ && extra_data_->selector_list_) {\n    return extra_data_->selector_list_->First();\n  } else {\n    return nullptr;\n  }\n}\n\nconst LynxCSSSelector* LynxCSSSelector::SerializeCompound(\n    std::string& result) const {\n  if (Match() == kTag && !tag_is_implicit_) {\n    result += Value();\n  }\n\n  for (const LynxCSSSelector* selector = this; selector;\n       selector = selector->TagHistory()) {\n    if (selector->Match() == kId) {\n      result.push_back('#');\n      result += selector->Value();\n    } else if (selector->Match() == kClass) {\n      result.push_back('.');\n      result += selector->Value();\n    } else if (selector->Match() == kPseudoClass) {\n      result.push_back(':');\n      result += selector->Value();\n    } else if (selector->Match() == kPseudoElement) {\n      result += \"::\";\n      result += selector->Value();\n    } else if (selector->IsAttributeSelector()) {\n      // Attribute selectors are not supported\n    }\n\n    if (selector->SelectorList()) {\n      result.push_back('(');\n      const LynxCSSSelector* first_selector = selector->SelectorList()->First();\n      for (const LynxCSSSelector* sub_selector = first_selector; sub_selector;\n           sub_selector = LynxCSSSelectorList::Next(*sub_selector)) {\n        if (sub_selector != first_selector) result += \", \";\n        result += sub_selector->ToString();\n      }\n      result.push_back(')');\n    }\n\n    if (selector->Relation() != kSubSelector) return selector;\n  }\n  return nullptr;\n}\n\nstd::string LynxCSSSelector::ToString() const {\n  std::string result;\n  for (const LynxCSSSelector* compound = this; compound;\n       compound = compound->TagHistory()) {\n    std::string compound_result;\n    compound = compound->SerializeCompound(compound_result);\n    if (!compound) return compound_result + result;\n\n    DCHECK(compound->Relation() != kSubSelector);\n    switch (compound->Relation()) {\n      case kDescendant:\n        result = \" \" + compound_result + result;\n        break;\n      case kChild:\n        result = \" > \" + compound_result + result;\n        break;\n      case kDirectAdjacent:\n        result = \" + \" + compound_result + result;\n        break;\n      case kIndirectAdjacent:\n        result = \" ~ \" + compound_result + result;\n        break;\n      case kSubSelector:\n        NOTREACHED();\n        break;\n      case kUAShadow:\n        result = compound_result + result;\n        break;\n      default:\n        break;\n    }\n  }\n  return result;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/selector/lynx_css_selector.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_SELECTOR_LYNX_CSS_SELECTOR_H_\n#define CORE_RENDERER_CSS_NG_SELECTOR_LYNX_CSS_SELECTOR_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\n#include \"base/include/value/base_value.h\"\n#include \"core/base/lynx_export.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_extra_data.h\"\n\nnamespace lynx {\nnamespace css {\n\nclass CSSParserContext;\nclass LynxCSSSelectorList;\n\nenum PseudoId : uint8_t {\n  kPseudoIdNone,\n  kPseudoIdFirstLine,\n  kPseudoIdFirstLetter,\n  kPseudoIdBefore,\n  kPseudoIdAfter,\n  kPseudoIdBackdrop,\n  kPseudoIdSelection,\n};\n\nclass LynxCSSSelector {\n public:\n  using AttributeMatchType = LynxCSSSelectorExtraData::AttributeMatchType;\n\n  LynxCSSSelector();\n  LynxCSSSelector(const LynxCSSSelector&) = delete;\n  explicit LynxCSSSelector(const std::string&, bool tag_is_implicit = false);\n\n  ~LynxCSSSelector() = default;\n\n  LYNX_EXPORT_FOR_DEVTOOL std::string ToString() const;\n\n  bool operator==(const LynxCSSSelector&) const = delete;\n  bool operator!=(const LynxCSSSelector&) const = delete;\n\n  unsigned CalcSpecificity() const;\n\n  enum MatchType {\n    kUnknown,\n    kTag,\n    kId,\n    kClass,\n    kPseudoClass,\n    kPseudoElement,\n    kAttributeExact,\n    kAttributeSet,\n    kAttributeHyphen,\n    kAttributeList,\n    kAttributeContain,\n    kAttributeBegin,\n    kAttributeEnd,\n    kFirstAttributeSelectorMatch = kAttributeExact,\n  };\n\n  enum RelationType {\n    kSubSelector,\n    kDescendant,\n    kChild,\n    kDirectAdjacent,\n    kIndirectAdjacent,\n    kUAShadow,\n    kRelativeDescendant,\n    kRelativeChild,\n    kRelativeDirectAdjacent,\n    kRelativeIndirectAdjacent\n  };\n\n  enum PseudoType {\n    kPseudoUnknown,\n    kPseudoEmpty,\n    kPseudoFirstChild,\n    kPseudoFirstOfType,\n    kPseudoLastChild,\n    kPseudoLastOfType,\n    kPseudoOnlyChild,\n    kPseudoOnlyOfType,\n    kPseudoFirstLine,\n    kPseudoFirstLetter,\n    kPseudoNthChild,\n    kPseudoNthOfType,\n    kPseudoNthLastChild,\n    kPseudoNthLastOfType,\n    kPseudoState,\n    kPseudoLink,\n    kPseudoVisited,\n    kPseudoIs,\n    kPseudoWhere,\n    kPseudoHover,\n    kPseudoFocus,\n    kPseudoActive,\n    kPseudoChecked,\n    kPseudoEnabled,\n    kPseudoDefault,\n    kPseudoDisabled,\n    kPseudoBefore,\n    kPseudoAfter,\n    kPseudoBackdrop,\n    kPseudoLang,\n    kPseudoNot,\n    kPseudoPlaceholder,\n    kPseudoRoot,\n    kPseudoSelection,\n    kPseudoDir,\n    kPseudoHas,\n    kPseudoRelativeAnchor\n  };\n\n  PseudoType GetPseudoType() const {\n    return static_cast<PseudoType>(pseudo_type_);\n  }\n\n  void UpdatePseudoType(PseudoType pseudo_type);\n\n  void UpdateSpecificity(uint32_t v) { specificity_ = v; }\n\n  const LynxCSSSelector* TagHistory() const {\n    return is_last_in_tag_history_ ? nullptr : this + 1;\n  }\n\n  const std::string& Value() const;\n\n  const std::string& Attribute() const;\n  LynxCSSSelectorExtraData::AttributeMatchType AttributeMatch() const;\n  const std::string& Argument() const;\n  const LynxCSSSelectorList* SelectorList() const {\n    return has_extra_data_ ? extra_data_->selector_list_.get() : nullptr;\n  }\n\n  uint32_t Specificity() const { return specificity_; }\n\n  void SetValue(const std::string&);\n  void SetAttribute(const std::string&, AttributeMatchType);\n  void SetArgument(const std::string&);\n  void SetSelectorList(std::unique_ptr<LynxCSSSelectorList>);\n\n  void SetNth(int a, int b);\n  bool MatchNth(unsigned count) const;\n\n  bool IsAttributeSelector() const {\n    return match_ >= kFirstAttributeSelectorMatch;\n  }\n\n  static void FromLepus(LynxCSSSelector&, const lepus_value&);\n  lepus_value ToLepus() const;\n\n  MatchType Match() const { return static_cast<MatchType>(match_); }\n  void SetMatch(MatchType match) { match_ = match; }\n\n  RelationType Relation() const { return static_cast<RelationType>(relation_); }\n  void SetRelation(RelationType relation) { relation_ = relation; }\n\n  bool IsLastInTagHistory() const { return is_last_in_tag_history_; }\n  void SetLastInTagHistory(bool is_last) { is_last_in_tag_history_ = is_last; }\n\n  bool IsLastInSelectorList() const { return is_last_in_selector_list_; }\n  void SetLastInSelectorList(bool is_last) {\n    is_last_in_selector_list_ = is_last;\n  }\n\n  void SetPseudoType(PseudoType pseudo_type) {\n    pseudo_type_ = pseudo_type;\n    DCHECK_EQ(static_cast<PseudoType>(pseudo_type_),\n              pseudo_type);  // using a bitfield.\n  }\n\n  unsigned CalcSpecificityForSimple() const;\n  const LynxCSSSelector* SerializeCompound(std::string&) const;\n\n  const LynxCSSSelector* SelectorListSelector() const;\n\n  LynxCSSSelector& operator=(LynxCSSSelector&&) = default;\n  LynxCSSSelector& operator=(const LynxCSSSelector&) = delete;\n\n private:\n  void CreateExtraData();\n\n  unsigned relation_ : 4;     // enum RelationType\n  unsigned match_ : 4;        // enum MatchType\n  unsigned pseudo_type_ : 8;  // enum PseudoType\n  unsigned is_last_in_selector_list_ : 1;\n  unsigned is_last_in_tag_history_ : 1;\n  unsigned has_extra_data_ : 1;\n  unsigned tag_is_implicit_ : 1;\n  uint32_t specificity_;\n  std::string value_;\n  std::unique_ptr<LynxCSSSelectorExtraData> extra_data_;\n};\n\ninline const std::string& LynxCSSSelector::Attribute() const {\n  DCHECK(IsAttributeSelector());\n  DCHECK(has_extra_data_);\n  return extra_data_->attribute_;\n}\n\ninline LynxCSSSelectorExtraData::AttributeMatchType\nLynxCSSSelector::AttributeMatch() const {\n  DCHECK(IsAttributeSelector());\n  DCHECK(has_extra_data_);\n  return extra_data_->bits_.attr_.attribute_match_;\n}\n\ninline void LynxCSSSelector::SetValue(const std::string& value) {\n  if (!has_extra_data_) {\n    value_ = value;\n    return;\n  }\n  extra_data_->value_ = value;\n}\n\ninline LynxCSSSelector::LynxCSSSelector()\n    : relation_(kSubSelector),\n      match_(kUnknown),\n      pseudo_type_(kPseudoUnknown),\n      is_last_in_selector_list_(false),\n      is_last_in_tag_history_(true),\n      has_extra_data_(false),\n      tag_is_implicit_(false),\n      specificity_(0),\n      value_() {}\n\ninline LynxCSSSelector::LynxCSSSelector(const std::string& tag_name,\n                                        bool tag_is_implicit)\n    : relation_(kSubSelector),\n      match_(kTag),\n      pseudo_type_(kPseudoUnknown),\n      is_last_in_selector_list_(false),\n      is_last_in_tag_history_(true),\n      has_extra_data_(false),\n      tag_is_implicit_(tag_is_implicit),\n      specificity_(0),\n      value_(tag_name) {}\n\ninline const std::string& LynxCSSSelector::Value() const {\n  if (has_extra_data_) return extra_data_->value_;\n  return value_;\n}\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_SELECTOR_LYNX_CSS_SELECTOR_H_\n"
  },
  {
    "path": "core/renderer/css/ng/selector/lynx_css_selector_empty.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"core/renderer/css/ng/selector/lynx_css_selector.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n\nnamespace lynx {\nnamespace css {\n\nvoid LynxCSSSelector::UpdatePseudoType(PseudoType pseudo_type) {}\n\nunsigned LynxCSSSelector::CalcSpecificity() const { return 0; }\n\nunsigned LynxCSSSelector::CalcSpecificityForSimple() const { return 0; }\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/selector/lynx_css_selector_extra_data.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/selector/lynx_css_selector_extra_data.h\"\n\n#include <limits>\n\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n\nnamespace lynx {\nnamespace css {\n\nLynxCSSSelectorExtraData::LynxCSSSelectorExtraData(const std::string& value)\n    : value_(value),\n      match_type_(LynxCSSSelectorExtraData::MatchType::kUnknown),\n      bits_(),\n      attribute_(CSSGlobalStarString()),\n      argument_(CSSGlobalEmptyString()) {}\n\nLynxCSSSelectorExtraData::~LynxCSSSelectorExtraData() = default;\n\nbool LynxCSSSelectorExtraData::MatchNth(unsigned unsigned_count) const {\n  int max_value = std::numeric_limits<int>::max() / 2;\n  int min_value = std::numeric_limits<int>::min() / 2;\n  if (UNLIKELY(unsigned_count > static_cast<unsigned>(max_value) ||\n               NthAValue() > max_value || NthAValue() < min_value ||\n               NthBValue() > max_value || NthBValue() < min_value))\n    return false;\n\n  int count = static_cast<int>(unsigned_count);\n  if (!NthAValue()) return count == NthBValue();\n  if (NthAValue() > 0) {\n    if (count < NthBValue()) return false;\n    return (count - NthBValue()) % NthAValue() == 0;\n  }\n  if (count > NthBValue()) return false;\n  return (NthBValue() - count) % (-NthAValue()) == 0;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/selector/lynx_css_selector_extra_data.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_SELECTOR_LYNX_CSS_SELECTOR_EXTRA_DATA_H_\n#define CORE_RENDERER_CSS_NG_SELECTOR_LYNX_CSS_SELECTOR_EXTRA_DATA_H_\n\n#include <memory>\n#include <optional>\n#include <string>\n#include <vector>\n\nnamespace lynx {\nnamespace css {\n\nclass LynxCSSSelectorList;\n\nstruct LynxCSSSelectorExtraData {\n  enum class AttributeMatchType : int {\n    kCaseSensitive,\n    kCaseInsensitive,\n    kCaseSensitiveAlways,\n  };\n\n  enum class MatchType : uint8_t {\n    kUnknown,\n    kNth,\n    kAttr,\n    kHas,\n  };\n\n  explicit LynxCSSSelectorExtraData(const std::string& value);\n  ~LynxCSSSelectorExtraData();\n\n  bool MatchNth(unsigned count) const;\n  int NthAValue() const { return bits_.nth_.a_; }\n  int NthBValue() const { return bits_.nth_.b_; }\n\n  std::string value_;\n  MatchType match_type_;\n  union {\n    struct {\n      int a_;  // Used for :nth-*\n      int b_;  // Used for :nth-*\n    } nth_;\n\n    struct {\n      AttributeMatchType\n          attribute_match_;  // used for attribute selector (with value)\n      bool is_case_sensitive_attribute_;\n    } attr_;\n\n    struct {\n      bool contains_pseudo_;\n\n      bool contains_complex_logical_combinations_;\n    } has_;\n  } bits_;\n\n  std::string attribute_;\n  std::string argument_;\n  std::unique_ptr<LynxCSSSelectorList> selector_list_;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_SELECTOR_LYNX_CSS_SELECTOR_EXTRA_DATA_H_\n"
  },
  {
    "path": "core/renderer/css/ng/selector/lynx_css_selector_impl.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <memory>\n\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n\nnamespace lynx {\nnamespace css {\n\nstatic constexpr unsigned kIdSpecificity = 0x010000;\nstatic constexpr unsigned kClassSpecificity = 0x000100;\nstatic constexpr unsigned kTagSpecificity = 0x000001;\n\nvoid LynxCSSSelector::UpdatePseudoType(PseudoType pseudo_type) {\n  DCHECK(match_ == kPseudoClass || match_ == kPseudoElement);\n  SetPseudoType(pseudo_type);\n\n  switch (GetPseudoType()) {\n    case kPseudoAfter:\n    case kPseudoBefore:\n    case kPseudoFirstLetter:\n    case kPseudoFirstLine:\n      if (match_ == kPseudoClass) match_ = kPseudoElement;\n      [[fallthrough]];\n    // For pseudo elements\n    case kPseudoPlaceholder:\n    case kPseudoSelection:\n      if (match_ != kPseudoElement) pseudo_type_ = kPseudoUnknown;\n      break;\n    // For pseudo classes\n    case kPseudoActive:\n    case kPseudoChecked:\n    case kPseudoDefault:\n    case kPseudoDir:\n    case kPseudoDisabled:\n    case kPseudoEmpty:\n    case kPseudoEnabled:\n    case kPseudoFirstChild:\n    case kPseudoFirstOfType:\n    case kPseudoFocus:\n    case kPseudoHas:\n    case kPseudoHover:\n    case kPseudoIs:\n    case kPseudoLang:\n    case kPseudoLastChild:\n    case kPseudoLastOfType:\n    case kPseudoLink:\n    case kPseudoNot:\n    case kPseudoNthChild:\n    case kPseudoNthLastChild:\n    case kPseudoNthLastOfType:\n    case kPseudoNthOfType:\n    case kPseudoOnlyChild:\n    case kPseudoOnlyOfType:\n    case kPseudoRoot:\n    case kPseudoState:\n    case kPseudoUnknown:\n    case kPseudoVisited:\n    case kPseudoWhere:\n      if (match_ != kPseudoClass) pseudo_type_ = kPseudoUnknown;\n      break;\n    default:\n      pseudo_type_ = kPseudoUnknown;\n      break;\n  }\n}\n\nunsigned LynxCSSSelector::CalcSpecificity() const {\n  // make sure the result doesn't overflow\n  static const unsigned kIdMask = 0xff0000;\n  static const unsigned kClassMask = 0x00ff00;\n  static const unsigned kElementMask = 0x0000ff;\n\n  unsigned total = 0;\n  unsigned temp = 0;\n\n  for (const LynxCSSSelector* selector = this; selector;\n       selector = selector->TagHistory()) {\n    temp = total + selector->CalcSpecificityForSimple();\n    // Clamp each component to its max in the case of overflow.\n    if ((temp & kIdMask) < (total & kIdMask))\n      total |= kIdMask;\n    else if ((temp & kClassMask) < (total & kClassMask))\n      total |= kClassMask;\n    else if ((temp & kElementMask) < (total & kElementMask))\n      total |= kElementMask;\n    else\n      total = temp;\n  }\n  return total;\n}\n\nunsigned LynxCSSSelector::CalcSpecificityForSimple() const {\n  switch (Match()) {\n    case kId:\n      return kIdSpecificity;\n    case kPseudoClass:\n      switch (GetPseudoType()) {\n        case kPseudoWhere:\n          return 0;\n        case kPseudoNot:\n          DCHECK(SelectorList());\n          [[fallthrough]];\n        case kPseudoIs:\n          return SelectorList() ? SelectorList()->CalcSpecificity() : 0;\n        case kPseudoHas:\n          return SelectorList() ? SelectorList()->CalcSpecificity() : 0;\n        default:\n          break;\n      }\n      return kClassSpecificity;\n    case kPseudoElement:\n      return kClassSpecificity;\n    case kClass:\n    case kAttributeExact:\n    case kAttributeSet:\n    case kAttributeList:\n    case kAttributeHyphen:\n    case kAttributeContain:\n    case kAttributeBegin:\n    case kAttributeEnd:\n      return kClassSpecificity;\n    case kTag:\n      if (Value() == CSSGlobalEmptyString()) return 0;\n      return kTagSpecificity;\n    case kUnknown:\n      return 0;\n  }\n  NOTREACHED();\n  return 0;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/selector/lynx_css_selector_list.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n/*\n * Copyright (C) 2008, 2012 Apple Inc. All rights reserved.\n * Copyright (C) 2009 Google Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n\n#include <algorithm>\n#include <memory>\n#include <string>\n\nnamespace lynx {\nnamespace css {\n\nunsigned LynxCSSSelectorList::CalcSpecificity() const {\n  unsigned specificity = 0;\n\n  for (const LynxCSSSelector* s = First(); s; s = Next(*s))\n    specificity = std::max(specificity, s->CalcSpecificity());\n\n  return specificity;\n}\n\nstd::string LynxCSSSelectorList::SelectorsText(const LynxCSSSelector* first) {\n  std::string result;\n\n  for (const LynxCSSSelector* s = first; s; s = Next(*s)) {\n    if (s != first) result.append(\", \");\n    result.append(s->ToString());\n  }\n\n  return result;\n}\n\nconst LynxCSSSelector* LynxCSSSelectorList::Next(\n    const LynxCSSSelector& current) {\n  // Skip subparts of compound selectors.\n  const LynxCSSSelector* last = &current;\n  while (!last->IsLastInTagHistory()) last++;\n  return last->IsLastInSelectorList() ? nullptr : last + 1;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/selector/lynx_css_selector_list.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n/*\n * Copyright (C) 2008, 2012 Apple Inc. All rights reserved.\n * Copyright (C) 2009 Google Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY\n * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#ifndef CORE_RENDERER_CSS_NG_SELECTOR_LYNX_CSS_SELECTOR_LIST_H_\n#define CORE_RENDERER_CSS_NG_SELECTOR_LYNX_CSS_SELECTOR_LIST_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"core/renderer/css/ng/selector/lynx_css_selector.h\"\n\nnamespace lynx {\nnamespace css {\n\n// This class represents a CSS selector, i.e. a pattern of one or more\n// simple selectors. https://www.w3.org/TR/css3-selectors/\n\n// More specifically, a CSS selector is a chain of one or more sequences\n// of simple selectors separated by combinators.\n//\n// For example, \"div.c1 > span.c2 + .c3#ident\" is represented as a\n// LynxCSSSelectorList that owns six LynxCSSSelector instances.\n//\n// The simple selectors are stored in memory in the following order:\n// .c3, #ident, span, .c2, div, .c1\n// (See LynxCSSSelector.h for more information.)\n//\n// First() and Next() can be used to traverse from right to left through\n// the chain of sequences: .c3#ident then span.c2 then div.c1\n//\n// SelectorAt and IndexOfNextSelectorAfter provide an equivalent API:\n// size_t index = 0;\n// do {\n//   const LynxCSSSelector& sequence = selectorList.SelectorAt(index);\n//   ...\n//   index = IndexOfNextSelectorAfter(index);\n// } while (index != UINT_MAX);\n//\n// Use LynxCSSSelector::TagHistory() and LynxCSSSelector::IsLastInTagHistory()\n// to traverse through each sequence of simple selectors,\n// from .c3 to #ident; from span to .c2; from div to .c1\n//\n// StyleRule stores its selectors in an identical memory layout,\n// but not as part of a LynxCSSSelectorList (see its class comments).\n// It reuses many of the exposed static member functions from\n// LynxCSSSelectorList to provide a subset of its API.\nclass LynxCSSSelectorList {\n public:\n  LynxCSSSelectorList() = default;\n\n  LynxCSSSelectorList(LynxCSSSelectorList&& o)\n      : selector_array_(std::move(o.selector_array_)) {}\n\n  LynxCSSSelectorList(std::unique_ptr<LynxCSSSelector[]> selector_array)\n      : selector_array_(std::move(selector_array)) {}\n\n  LynxCSSSelectorList& operator=(LynxCSSSelectorList&& o) {\n    DCHECK(this != &o);\n    selector_array_ = std::move(o.selector_array_);\n    return *this;\n  }\n\n  ~LynxCSSSelectorList() = default;\n\n  bool IsValid() const { return !!selector_array_; }\n  const LynxCSSSelector* First() const { return selector_array_.get(); }\n  static const LynxCSSSelector* Next(const LynxCSSSelector&);\n\n  // The CSS selector represents a single sequence of simple selectors.\n  bool HasOneSelector() const { return selector_array_ && !Next(*First()); }\n\n  std::string SelectorsText() const { return SelectorsText(First()); }\n  static std::string SelectorsText(const LynxCSSSelector* first);\n\n  // Selector lists don't know their length, computing it is O(n) and should be\n  // avoided when possible. Instead iterate from first() and using next().\n  // unsigned ComputeLength() const;\n\n  // Return the specificity of the selector with the highest specificity.\n  unsigned CalcSpecificity() const;\n\n  LynxCSSSelectorList(const LynxCSSSelectorList&) = delete;\n  LynxCSSSelectorList& operator=(const LynxCSSSelectorList&) = delete;\n\n private:\n  // End of a multipart selector is indicated by is_last_in_tag_history_ bit in\n  // the last item. End of the array is indicated by is_last_in_selector_list_\n  // bit in the last item.\n  std::unique_ptr<LynxCSSSelector[]> selector_array_;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_SELECTOR_LYNX_CSS_SELECTOR_LIST_H_\n"
  },
  {
    "path": "core/renderer/css/ng/selector/lynx_css_selector_test.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/selector/lynx_css_selector.h\"\n\n#include <memory>\n#include <utility>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/ng/parser/css_tokenizer.h\"\n#include \"core/renderer/css/ng/selector/css_parser_context.h\"\n#include \"core/renderer/css/ng/selector/css_selector_parser.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace css {\n\nstruct LynxSelectorTestCase {\n  // The input string to parse as a selector list.\n  const char* input;\n};\n\nclass LynxSelectorTest : public ::testing::TestWithParam<LynxSelectorTestCase> {\n};\n\nTEST_P(LynxSelectorTest, Parse) {\n  auto param = GetParam();\n  SCOPED_TRACE(param.input);\n  CSSParserContext context;\n  CSSTokenizer tokenizer(param.input);\n  const auto tokens = tokenizer.TokenizeToEOF();\n  CSSParserTokenRange range(tokens);\n  LynxCSSSelectorVector vector =\n      CSSSelectorParser::ParseSelector(range, &context);\n  auto list = CSSSelectorParser::AdoptSelectorVector(vector);\n  EXPECT_TRUE(list.IsValid());\n  // Encode\n  auto array = lepus::CArray::Create();\n  auto current = list.First();\n  while (current) {\n    array->push_back(current->ToLepus());\n    if (current->IsLastInTagHistory() && current->IsLastInSelectorList()) {\n      break;\n    }\n    current++;\n  }\n\n  // Decode\n  int flattened_size = array->size();\n  auto selector_array = std::make_unique<LynxCSSSelector[]>(flattened_size);\n  for (int i = 0; i < flattened_size; ++i) {\n    LynxCSSSelector::FromLepus(selector_array[i], array->get(i));\n  }\n  auto result = LynxCSSSelectorList(std::move(selector_array));\n\n  // EXPECT_EQ(param.input, list.SelectorsText());\n  EXPECT_EQ(result.SelectorsText(), list.SelectorsText());\n}\n\nstatic const LynxSelectorTestCase lynx_css_selector_test_data[] = {\n    {\"div\"},\n    {\":active\"},\n    {R\"CSS(.\\[item\\])CSS\"},\n    {\".\\\\[item\\\\]\"},\n    {\"div.class\"},\n    {\"div:active\"},\n    {\"#list:focus\"},\n    {\"div:hover\"},\n    {\"input::placeholder\"},\n    {\"*::placeholder\"},\n    {\"::selection\"},\n    {\"#text::selection\"},\n    {\"#main\"},\n    {\".a.b.c\"},\n    {\"#main div\"},\n    {\"#main div .a\"},\n    {\"div .a.b\"},\n    {\"view text\"},\n    {\"div *\"},\n    {\"div text *\"},\n    {\"view[flatten=\\\"false\\\"]\"},\n    {\"a[title]\"},\n    {\"div text *\"},\n    {\"div text *\"},\n    {\"*\"},\n};\n\nINSTANTIATE_TEST_SUITE_P(LynxCSSSelectorTest, LynxSelectorTest,\n                         testing::ValuesIn(lynx_css_selector_test_data));\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/style/rule_data.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_STYLE_RULE_DATA_H_\n#define CORE_RENDERER_CSS_NG_STYLE_RULE_DATA_H_\n\n#include <memory>\n\n#include \"core/renderer/css/ng/style/style_rule.h\"\n\nnamespace lynx {\nnamespace css {\n\nstruct RuleData {\n  static constexpr size_t kSelectorIndexBits = 13;\n\n  static constexpr size_t kPositionBits = 19;\n\n  RuleData(const fml::RefPtr<StyleRule>& rule, unsigned selector_index,\n           unsigned position)\n      : rule_(rule),\n        selector_index_(selector_index),\n        position_(position),\n        specificity_(Selector().Specificity()) {}\n\n  const LynxCSSSelector& Selector() const {\n    return rule_->SelectorAt(selector_index_);\n  }\n\n  StyleRule* Rule() const { return rule_.get(); }\n\n  unsigned Position() const { return position_; }\n\n  unsigned SelectorIndex() const { return selector_index_; }\n\n  unsigned Specificity() const { return specificity_; }\n\n private:\n  fml::RefPtr<StyleRule> rule_;\n  unsigned selector_index_ : kSelectorIndexBits;\n  unsigned position_ : kPositionBits;\n  unsigned specificity_;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_STYLE_RULE_DATA_H_\n"
  },
  {
    "path": "core/renderer/css/ng/style/rule_set.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/style/rule_set.h\"\n\n#include <iostream>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/ng/css_ng_utils.h\"\n#include \"core/renderer/css/ng/invalidation/rule_invalidation_set.h\"\n#include \"core/renderer/css/ng/matcher/selector_matcher.h\"\n#include \"core/renderer/css/shared_css_fragment.h\"\n\nnamespace lynx {\nnamespace css {\n\nstatic void MatchKey(StyleNode* node, const CompactRuleDataVector& list,\n                     unsigned level, base::Vector<MatchedRule>& matched) {\n  for (const auto& rule : list) {\n    SelectorMatcher matcher;\n    SelectorMatcher::SelectorMatchingContext context(node);\n    context.selector = &rule.Selector();\n    auto ret = matcher.Match(context);\n    if (ret) {\n      // Avoid copying the RuleData, only used in the local scope\n      matched.emplace_back(&rule, level);\n    }\n  }\n}\n\nstatic void MatchKey(\n    StyleNode* node, const std::string& key,\n    const std::unordered_map<std::string, CompactRuleDataVector>& map,\n    unsigned level, base::Vector<MatchedRule>& matched) {\n  if (key.empty()) return;\n\n  auto list = map.find(key);\n  if (list != map.end()) {\n    MatchKey(node, list->second, level, matched);\n  }\n}\n\nvoid RuleSet::AddToRuleSet(const std::string& text,\n                           const fml::RefPtr<tasm::CSSParseToken>& token) {\n  auto selector_array = std::make_unique<LynxCSSSelector[]>(1);\n  selector_array[0].SetValue(text);\n  selector_array[0].SetMatch(LynxCSSSelector::MatchType::kClass);\n  selector_array[0].SetLastInTagHistory(true);\n  selector_array[0].SetLastInSelectorList(true);\n  AddStyleRule(\n      fml::MakeRefCounted<StyleRule>(std::move(selector_array), token));\n}\n\nvoid RuleSet::MatchStyles(StyleNode* node, unsigned& level,\n                          base::Vector<MatchedRule>& output) const {\n  for (const auto& dep : deps_) {\n    dep.MatchStyles(node, level, output);\n  }\n  ++level;\n  MatchKey(node, universal_rules_, level, output);\n  if (node->GetPseudoState() != tasm::kPseudoStateNone) {\n    MatchKey(node, pseudo_rules_, level, output);\n  }\n  MatchKey(node, node->tag().str(), tag_rules_, level, output);\n  for (const auto& c : node->classes()) {\n    MatchKey(node, c.str(), class_rules_, level, output);\n  }\n  MatchKey(node, node->idSelector().str(), id_rules_, level, output);\n}\n\nvoid RuleSet::AddStyleRule(const fml::RefPtr<StyleRule>& rule) {\n  if (rule == nullptr) return;\n\n  for (unsigned selector_index = 0; selector_index != UINT_MAX;\n       selector_index = rule->IndexOfNextSelectorAfter(selector_index)) {\n    RuleData rule_data(rule, selector_index, rule_count_);\n    ++rule_count_;\n    AddToRuleSetInternal(rule->SelectorAt(selector_index), rule_data);\n    if (!fragment_) continue;\n    RuleInvalidationSet* set = fragment_->GetRuleInvalidationSet();\n    if (!set) continue;\n    set->AddSelector(rule->SelectorAt(selector_index));\n  }\n}\n\nvoid RuleSet::AddToRuleSet(\n    const std::string& key,\n    std::unordered_map<std::string, CompactRuleDataVector>& map,\n    const RuleData& rule) {\n  map[key].push_back(rule);\n}\n\nstatic void ExtractSelector(const LynxCSSSelector* selector, std::string& id,\n                            std::string& class_name, std::string& attr_name,\n                            std::string& attr_value, std::string& tag_name,\n                            LynxCSSSelector::PseudoType& pseudo_type) {\n  switch (selector->Match()) {\n    case LynxCSSSelector::kId:\n      id = selector->Value();\n      break;\n    case LynxCSSSelector::kClass:\n      class_name = selector->Value();\n      break;\n    case LynxCSSSelector::kTag:\n      if (selector->Value() != CSSGlobalStarString())\n        tag_name = selector->Value();\n      break;\n    case LynxCSSSelector::kPseudoClass:\n    case LynxCSSSelector::kPseudoElement:\n      switch (selector->GetPseudoType()) {\n        case LynxCSSSelector::kPseudoActive:\n        case LynxCSSSelector::kPseudoFocus:\n        case LynxCSSSelector::kPseudoHover:\n        case LynxCSSSelector::kPseudoPlaceholder:\n        case LynxCSSSelector::kPseudoSelection:\n          pseudo_type = selector->GetPseudoType();\n          break;\n        default:\n          break;\n      }\n      break;\n    case LynxCSSSelector::kAttributeExact:\n    case LynxCSSSelector::kAttributeSet:\n    case LynxCSSSelector::kAttributeHyphen:\n    case LynxCSSSelector::kAttributeList:\n    case LynxCSSSelector::kAttributeContain:\n    case LynxCSSSelector::kAttributeBegin:\n    case LynxCSSSelector::kAttributeEnd:\n      attr_name = selector->Attribute();\n      attr_value = selector->Value();\n      break;\n    default:\n      break;\n  }\n}\n\nstatic const LynxCSSSelector* ExtractBestSelector(\n    const LynxCSSSelector& selector, std::string& id, std::string& class_name,\n    std::string& attr_name, std::string& attr_value, std::string& tag_name,\n    LynxCSSSelector::PseudoType& pseudo_type) {\n  const LynxCSSSelector* it = &selector;\n  for (; it && it->Relation() == LynxCSSSelector::kSubSelector;\n       it = it->TagHistory()) {\n    ExtractSelector(it, id, class_name, attr_name, attr_value, tag_name,\n                    pseudo_type);\n  }\n  if (it) {\n    ExtractSelector(it, id, class_name, attr_name, attr_value, tag_name,\n                    pseudo_type);\n  }\n  return it;\n}\n\nbool RuleSet::AddToRuleSetInternal(const LynxCSSSelector& selector,\n                                   const RuleData& rule_data) {\n  std::string id;\n  std::string class_name;\n  std::string attr_name;\n  std::string attr_value;\n  std::string tag_name;\n  LynxCSSSelector::PseudoType pseudo_type = LynxCSSSelector::kPseudoUnknown;\n\n  ExtractBestSelector(selector, id, class_name, attr_name, attr_value, tag_name,\n                      pseudo_type);\n\n  // Prefer rule sets in order of most likely to apply infrequently.\n  if (!id.empty()) {\n    AddToRuleSet(id, id_rules_, rule_data);\n    return true;\n  }\n\n  if (!class_name.empty()) {\n    AddToRuleSet(class_name, class_rules_, rule_data);\n    return true;\n  }\n\n  if (!attr_name.empty()) {\n    AddToRuleSet(attr_name, attr_rules_, rule_data);\n    return true;\n  }\n\n  if (pseudo_type != LynxCSSSelector::kPseudoUnknown) {\n    pseudo_rules_.push_back(rule_data);\n    return true;\n  }\n\n  if (!tag_name.empty()) {\n    AddToRuleSet(tag_name, tag_rules_, rule_data);\n    return true;\n  }\n\n  universal_rules_.push_back(rule_data);\n  return false;\n}\n\nfml::RefPtr<tasm::CSSParseToken> RuleSet::GetRootToken() {\n  auto iter = std::find_if(universal_rules_.begin(), universal_rules_.end(),\n                           [](const RuleData& rd) {\n                             return rd.Selector().GetPseudoType() ==\n                                    LynxCSSSelector::PseudoType::kPseudoRoot;\n                           });\n  if (iter != universal_rules_.end()) {\n    return iter->Rule()->Token();\n  }\n  return nullptr;\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/style/rule_set.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_STYLE_RULE_SET_H_\n#define CORE_RENDERER_CSS_NG_STYLE_RULE_SET_H_\n\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"base/include/vector.h\"\n#include \"core/renderer/css/ng/style/rule_data.h\"\n#include \"core/renderer/css/style_node.h\"\n\nnamespace lynx {\n\nnamespace tasm {\nclass CSSParseToken;\nclass SharedCSSFragment;\n}  // namespace tasm\n\nnamespace css {\n\nclass RuleInvalidationSet;\n\nstruct MatchedRule {\n  MatchedRule(const RuleData* rule_data, unsigned index)\n      : rule_data_(rule_data) {\n    position_ = (static_cast<uint64_t>(index) << RuleData::kPositionBits) +\n                rule_data_->Position();\n  }\n\n  const RuleData* Data() const { return rule_data_; }\n  uint64_t Position() const { return position_; }\n  unsigned Specificity() const { return rule_data_->Specificity(); }\n\n private:\n  const RuleData* rule_data_;\n  uint64_t position_;\n};\n\nusing CompactRuleDataVector = base::InlineVector<RuleData, 2>;\n\nclass RuleSet {\n public:\n  explicit RuleSet(tasm::SharedCSSFragment* fragment) : fragment_(fragment) {}\n\n  void MatchStyles(StyleNode* node, unsigned& level,\n                   base::Vector<MatchedRule>& output) const;\n\n  void AddToRuleSet(const std::string& text,\n                    const fml::RefPtr<tasm::CSSParseToken>& token);\n\n  void Merge(const RuleSet& rule_set) { deps_.push_back(rule_set); }\n\n  void AddStyleRule(const fml::RefPtr<StyleRule>& r);\n\n  fml::RefPtr<tasm::CSSParseToken> GetRootToken();\n\n  const auto& id_rules(const std::string& key) { return id_rules_[key]; }\n\n  const auto& class_rules(const std::string& key) { return class_rules_[key]; }\n\n  const auto& attr_rules(const std::string& key) { return attr_rules_[key]; }\n\n  const auto& tag_rules(const std::string& key) { return tag_rules_[key]; }\n\n  const auto& pseudo_rules() { return pseudo_rules_; }\n\n  const auto& universal_rules() { return universal_rules_; }\n\n private:\n  bool AddToRuleSetInternal(const LynxCSSSelector& component,\n                            const RuleData& rule);\n\n  static void AddToRuleSet(\n      const std::string& key,\n      std::unordered_map<std::string, CompactRuleDataVector>& map,\n      const RuleData& rule);\n\n  std::unordered_map<std::string, CompactRuleDataVector> id_rules_;\n  std::unordered_map<std::string, CompactRuleDataVector> class_rules_;\n  std::unordered_map<std::string, CompactRuleDataVector> attr_rules_;\n  std::unordered_map<std::string, CompactRuleDataVector> tag_rules_;\n  CompactRuleDataVector pseudo_rules_;\n  CompactRuleDataVector universal_rules_;\n\n  std::vector<RuleSet> deps_;\n  tasm::SharedCSSFragment* fragment_ = nullptr;\n  unsigned rule_count_ = 0;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_STYLE_RULE_SET_H_\n"
  },
  {
    "path": "core/renderer/css/ng/style/rule_set_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/ng/style/rule_set.h\"\n\n#include \"core/renderer/css/css_parser_token.h\"\n#include \"core/renderer/css/ng/parser/css_parser_token_range.h\"\n#include \"core/renderer/css/ng/parser/css_tokenizer.h\"\n#include \"core/renderer/css/ng/selector/css_parser_context.h\"\n#include \"core/renderer/css/ng/selector/css_selector_parser.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace css {\n\nstruct TestFragment {\n  TestFragment() : rule_set_(nullptr) {}\n  void AddCSSRules(const std::string& input) {\n    CSSParserContext context;\n    CSSTokenizer tokenizer(input);\n    const auto tokens = tokenizer.TokenizeToEOF();\n    CSSParserTokenRange range(tokens);\n    LynxCSSSelectorVector vector =\n        CSSSelectorParser::ParseSelector(range, &context);\n    size_t flattened_size = CSSSelectorParser::FlattenedSize(vector);\n    auto selector_array = std::make_unique<LynxCSSSelector[]>(flattened_size);\n    CSSSelectorParser::AdoptSelectorVector(vector, selector_array.get(),\n                                           flattened_size);\n    rule_set_.AddStyleRule(\n        fml::MakeRefCounted<StyleRule>(std::move(selector_array), nullptr));\n  }\n  RuleSet& GetRuleSet() { return rule_set_; }\n  RuleSet rule_set_;\n};\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_Id) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\"#main\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  std::string str(\"main\");\n  auto rules = rule_set.id_rules(str);\n  ASSERT_EQ(1u, rules.size());\n  ASSERT_EQ(str, rules.front().Selector().Value());\n}\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_Nth) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\"view:nth-child(1)\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  std::string str(\"view\");\n  auto rules = rule_set.tag_rules(str);\n  ASSERT_EQ(1u, rules.size());\n  ASSERT_EQ(str, rules.front().Selector().Value());\n}\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_Pseudo) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\"view:hover\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  std::string str(\"view\");\n  auto rules = rule_set.pseudo_rules();\n  ASSERT_EQ(1u, rules.size());\n  ASSERT_EQ(str, rules.front().Selector().Value());\n}\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_ClassAndId) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\".item#main\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  std::string str(\"main\");\n  auto rules = rule_set.id_rules(str);\n  ASSERT_EQ(1u, rules.size());\n  std::string class_str(\"item\");\n  ASSERT_EQ(class_str, rules.front().Selector().Value());\n}\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_IdAndClass) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\"#main.item\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  std::string str(\"main\");\n  auto rules = rule_set.id_rules(str);\n  ASSERT_EQ(1u, rules.size());\n  ASSERT_EQ(str, rules.front().Selector().Value());\n}\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_AttrAndId) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\"[attr]#main\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  std::string str(\"main\");\n  auto rules = rule_set.id_rules(str);\n  ASSERT_EQ(1u, rules.size());\n  std::string attr_str(\"attr\");\n  ASSERT_EQ(attr_str, rules.front().Selector().Attribute());\n}\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_TagAndAttrAndId) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\"view[attr]#main\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  std::string str(\"main\");\n  auto rules = rule_set.id_rules(str);\n  ASSERT_EQ(1u, rules.size());\n  std::string tag_str(\"view\");\n  ASSERT_EQ(tag_str, rules.front().Selector().Value());\n}\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_TagAndAttr) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\"view[attr]\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  ASSERT_EQ(1u, rule_set.attr_rules(\"attr\").size());\n  ASSERT_TRUE(rule_set.tag_rules(\"view\").empty());\n}\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_AttrAndClass) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\"[attr].item\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  ASSERT_TRUE(rule_set.attr_rules(\"attr\").empty());\n  ASSERT_EQ(1u, rule_set.class_rules(\"item\").size());\n}\n\nTEST(RuleSetTest, FindBestRuleSetAndAdd_PlaceholderPseudo) {\n  TestFragment fragment;\n\n  fragment.AddCSSRules(\"::placeholder\");\n  fragment.AddCSSRules(\"input::placeholder\");\n  RuleSet& rule_set = fragment.GetRuleSet();\n  auto rules = rule_set.pseudo_rules();\n  ASSERT_EQ(2u, rules.size());\n}\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/ng/style/style_rule.h",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_NG_STYLE_STYLE_RULE_H_\n#define CORE_RENDERER_CSS_NG_STYLE_STYLE_RULE_H_\n\n#include <limits.h>\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/fml/memory/ref_counted.h\"\n#include \"core/renderer/css/ng/selector/lynx_css_selector_list.h\"\n\nnamespace lynx {\n\nnamespace tasm {\nclass CSSParseToken;\n}\n\nnamespace css {\n\nclass StyleRule : public fml::RefCountedThreadSafeStorage {\n public:\n  StyleRule(std::unique_ptr<LynxCSSSelector[]> selector_array,\n            fml::RefPtr<tasm::CSSParseToken> token)\n      : selector_array_(std::move(selector_array)), token_(std::move(token)) {}\n\n  void ReleaseSelf() const override { delete this; }\n\n  unsigned IndexOfNextSelectorAfter(size_t index) const {\n    const LynxCSSSelector& current = SelectorAt(index);\n    const LynxCSSSelector* next = LynxCSSSelectorList::Next(current);\n    if (!next) return UINT_MAX;\n    return SelectorIndex(*next);\n  }\n\n  const LynxCSSSelector* FirstSelector() const { return selector_array_.get(); }\n  const LynxCSSSelector& SelectorAt(size_t index) const {\n    return selector_array_[index];\n  }\n  unsigned SelectorIndex(const LynxCSSSelector& selector) const {\n    return static_cast<unsigned>(&selector - FirstSelector());\n  }\n\n  const auto& Token() { return token_; }\n\n private:\n  std::unique_ptr<LynxCSSSelector[]> selector_array_;\n  fml::RefPtr<tasm::CSSParseToken> token_;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_NG_STYLE_STYLE_RULE_H_\n"
  },
  {
    "path": "core/renderer/css/parser/BUILD.gn",
    "content": "# Copyright 2021 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../../testing/test.gni\")\n\nunittest_set(\"css_parser_testset\") {\n  public_deps = [\n    \"..:css\",\n    \"../../../../third_party/rapidjson\",\n  ]\n\n  sources = [\n    \"animation_direction_handler_unittest.cc\",\n    \"animation_fill_mode_handler_unittest.cc\",\n    \"animation_iteration_count_handler_unittest.cc\",\n    \"animation_property_handler_unittest.cc\",\n    \"animation_shorthand_handler_unittest.cc\",\n    \"aspect_ratio_handler_unittest.cc\",\n    \"auto_font_size_handler_unittest.cc\",\n    \"auto_font_size_preset_sizes_handler_unittest.cc\",\n    \"background_box_handler_unittest.cc\",\n    \"background_clip_handler_unittest.cc\",\n    \"background_image_handler_unittest.cc\",\n    \"background_position_handler_unittest.cc\",\n    \"background_repeat_handler_unittest.cc\",\n    \"background_shorthand_handler_unittest.cc\",\n    \"background_size_handler_unittest.cc\",\n    \"bool_handler_unittest.cc\",\n    \"border_handler_unittest.cc\",\n    \"border_radius_handler_unittest.cc\",\n    \"clip_path_handler_unittest.cc\",\n    \"color_handler_unittest.cc\",\n    \"css_string_parser_unittest.cc\",\n    \"css_string_scanner_unittest.cc\",\n    \"enum_handler_unittest.cc\",\n    \"filter_handler_unittest.cc\",\n    \"flex_flow_handler_unittest.cc\",\n    \"flex_handler_unittest.cc\",\n    \"font_length_handler_unittest.cc\",\n    \"four_sides_shorthand_handler_unittest.cc\",\n    \"grid_template_handler_unittest.cc\",\n    \"length_handler_unittest.cc\",\n    \"list_gap_handler_unittest.cc\",\n    \"number_handler_unittest.cc\",\n    \"shadow_handler_unittest.cc\",\n    \"string_handler_unittest.cc\",\n    \"text_decoration_handler_unittest.cc\",\n    \"text_stroke_handler_unittest.cc\",\n    \"time_handler_unittest.cc\",\n    \"timing_function_handler_unittest.cc\",\n    \"transform_handler_unittest.cc\",\n    \"transform_origin_handler_unittest.cc\",\n    \"transition_shorthand_handler_unittest.cc\",\n  ]\n}\n\nunittest_exec(\"css_parser_test_exec\") {\n  sources = []\n  deps = [\n    \":css_parser_testset\",\n    \"../../dom:dom\",\n  ]\n}\n"
  },
  {
    "path": "core/renderer/css/parser/animation_direction_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/animation_direction_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationDirectionHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue css_value;\n  if (parser.ParseSingleOrMultipleValuePreview<\n          &CSSStringParser::ParseAnimationDirection>(css_value)) {\n    output.insert_or_assign(key, std::move(css_value));\n    return true;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDAnimationDirection] = &Handle; }\n\n}  // namespace AnimationDirectionHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_direction_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_ANIMATION_DIRECTION_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_ANIMATION_DIRECTION_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationDirectionHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace AnimationDirectionHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_ANIMATION_DIRECTION_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/animation_direction_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/animation_direction_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(AnimationDirectionHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDAnimationDirection;\n\n  std::vector<std::pair<std::string, starlight::AnimationDirectionType>> cases =\n      {\n          {\"normal\", starlight::AnimationDirectionType::kNormal},\n          {\"reverse\", starlight::AnimationDirectionType::kReverse},\n          {\"alternate\", starlight::AnimationDirectionType::kAlternate},\n          {\"alternate-reverse\",\n           starlight::AnimationDirectionType::kAlternateReverse},\n      };\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  for (const auto& s : cases) {\n    EXPECT_TRUE(\n        UnitHandler::Process(id, lepus::Value(s.first), output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output[id].IsEnum());\n    EXPECT_EQ(output[id].GetNumber(), static_cast<int>(s.second));\n  }\n}\n\nTEST(AnimationDirectionHandler, Multi) {\n  auto id = CSSPropertyID::kPropertyIDAnimationDirection;\n  auto impl = lepus::Value(\"normal, reverse\");\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  EXPECT_TRUE(output[id].GetValue().IsArray());\n  const auto& arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<int>(starlight::AnimationDirectionType::kNormal));\n  EXPECT_EQ(arr->get(1).Number(),\n            static_cast<int>(starlight::AnimationDirectionType::kReverse));\n}\n\nTEST(AnimationDirectionHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDAnimationDirection;\n  std::vector<std::string> cases = {\"invalid,\", \"2\", \"2s\"};\n  StyleMap output;\n  CSSParserConfigs configs;\n  for (const auto& s : cases) {\n    EXPECT_FALSE(UnitHandler::Process(id, lepus::Value(s), output, configs));\n    EXPECT_TRUE(output.empty());\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_fill_mode_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/animation_fill_mode_handler.h\"\n\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationFillModeHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue css_value;\n  if (parser.ParseSingleOrMultipleValuePreview<\n          &CSSStringParser::ParseAnimationFillMode>(css_value)) {\n    output.insert_or_assign(key, std::move(css_value));\n    return true;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDAnimationFillMode] = &Handle; }\n\n}  // namespace AnimationFillModeHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_fill_mode_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_ANIMATION_FILL_MODE_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_ANIMATION_FILL_MODE_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationFillModeHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace AnimationFillModeHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_ANIMATION_FILL_MODE_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/animation_fill_mode_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/animation_fill_mode_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(AnimationFillModeHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDAnimationFillMode;\n\n  std::vector<std::pair<std::string, starlight::AnimationFillModeType>> cases =\n      {\n          {\"none\", starlight::AnimationFillModeType::kNone},\n          {\"forwards\", starlight::AnimationFillModeType::kForwards},\n          {\"backwards\", starlight::AnimationFillModeType::kBackwards},\n          {\"both\", starlight::AnimationFillModeType::kBoth},\n      };\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  for (const auto& s : cases) {\n    EXPECT_TRUE(\n        UnitHandler::Process(id, lepus::Value(s.first), output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output[id].IsEnum());\n    EXPECT_EQ(output[id].GetNumber(), static_cast<int>(s.second));\n  }\n}\n\nTEST(AnimationFillModeHandler, Multi) {\n  auto id = CSSPropertyID::kPropertyIDAnimationFillMode;\n  auto impl = lepus::Value(\"forwards, backwards\");\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  EXPECT_TRUE(output[id].GetValue().IsArray());\n  const auto& arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<int>(starlight::AnimationFillModeType::kForwards));\n  EXPECT_EQ(arr->get(1).Number(),\n            static_cast<int>(starlight::AnimationFillModeType::kBackwards));\n}\n\nTEST(AnimationFillModeHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDAnimationFillMode;\n  std::vector<std::string> cases = {\"invalid,\", \"2\", \"2s\"};\n  StyleMap output;\n  CSSParserConfigs configs;\n  for (const auto& s : cases) {\n    EXPECT_FALSE(UnitHandler::Process(id, lepus::Value(s), output, configs));\n    EXPECT_TRUE(output.empty());\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_iteration_count_handler.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/animation_iteration_count_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimIterCountHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue css_value;\n  if (parser.ParseSingleOrMultipleValuePreview<\n          &CSSStringParser::ParseAnimationIterCount>(css_value)) {\n    output.insert_or_assign(key, std::move(css_value));\n    return true;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDAnimationIterationCount] = &Handle; }\n\n}  // namespace AnimIterCountHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_iteration_count_handler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_ANIMATION_ITERATION_COUNT_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_ANIMATION_ITERATION_COUNT_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimIterCountHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace AnimIterCountHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_ANIMATION_ITERATION_COUNT_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/animation_iteration_count_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/animation_iteration_count_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(AnimIterCountHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDAnimationIterationCount;\n\n  std::vector<std::pair<std::string, double>> cases = {\n      {\"1000\", 1000}, {\"infinite\", 10E8}, {\"1\", 1}};\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  for (const auto& s : cases) {\n    EXPECT_TRUE(\n        UnitHandler::Process(id, lepus::Value(s.first), output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output[id].IsNumber());\n    EXPECT_EQ(output[id].GetNumber(), static_cast<int>(s.second));\n  }\n}\n\nTEST(AnimIterCountHandler, Multi) {\n  auto id = CSSPropertyID::kPropertyIDAnimationIterationCount;\n  auto impl = lepus::Value(\"1000, infinite\");\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  EXPECT_TRUE(output[id].GetValue().IsArray());\n  const auto& arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(), 1000);\n  EXPECT_EQ(arr->get(1).Number(), 10E8);\n}\n\nTEST(AnimIterCountHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDAnimationIterationCount;\n  std::vector<std::string> cases = {\"invalid,\", \"2s\", \"-1\"};\n  StyleMap output;\n  CSSParserConfigs configs;\n  for (const auto& s : cases) {\n    EXPECT_FALSE(UnitHandler::Process(id, lepus::Value(s), output, configs));\n    EXPECT_TRUE(output.empty());\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_name_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/animation_name_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationNameHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue css_value;\n  if (parser.ParseSingleOrMultipleValuePreview<\n          &CSSStringParser::ParseAnimationName>(css_value)) {\n    output.insert_or_assign(key, std::move(css_value));\n    return true;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDAnimationName] = &Handle; }\n\n}  // namespace AnimationNameHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_name_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_ANIMATION_NAME_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_ANIMATION_NAME_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationNameHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace AnimationNameHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_ANIMATION_NAME_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/animation_play_state_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/animation_play_state_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationPlayStateHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue css_value;\n  if (parser.ParseSingleOrMultipleValuePreview<\n          &CSSStringParser::ParseAnimationPlayState>(css_value)) {\n    output.insert_or_assign(key, std::move(css_value));\n    return true;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDAnimationPlayState] = &Handle; }\n\n}  // namespace AnimationPlayStateHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_play_state_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_ANIMATION_PLAY_STATE_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_ANIMATION_PLAY_STATE_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationPlayStateHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace AnimationPlayStateHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_ANIMATION_PLAY_STATE_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/animation_property_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/animation_property_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationPropertyHandler {\n\nHANDLER_IMPL() {\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue css_value;\n  if (parser.ParseSingleOrMultipleValuePreview<\n          &CSSStringParser::ParseTransitionProperty>(css_value)) {\n    output.insert_or_assign(key, std::move(css_value));\n    return true;\n  }\n  return false;\n}\n\n}  // namespace AnimationPropertyHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_property_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/animation_property_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(AnimationPropertyHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDTransitionProperty;\n\n  std::vector<std::pair<std::string, starlight::AnimationPropertyType>> cases =\n      {{\"hello\", starlight::AnimationPropertyType::kNone},\n       {\"width\", starlight::AnimationPropertyType::kWidth},\n       {\"all\", starlight::AnimationPropertyType::kAll}};\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  for (const auto& s : cases) {\n    EXPECT_TRUE(\n        UnitHandler::Process(id, lepus::Value(s.first), output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output[id].IsEnum());\n    EXPECT_EQ(output[id].GetNumber(), static_cast<int>(s.second));\n  }\n}\n\nTEST(AnimationPropertyHandler, Multi) {\n  auto id = CSSPropertyID::kPropertyIDTransitionProperty;\n  auto impl = lepus::Value(\n      \"none, opacity, scaleX, scaleY, scaleXY, width, height, \"\n      \"background-color, color,visibility, left, top, right,bottom,transform, \"\n      \"all, max-width, max-height, min-width, min-height\");\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  EXPECT_TRUE(output[id].GetArray());\n  const auto& arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kNone));\n  EXPECT_EQ(arr->get(1).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kOpacity));\n  EXPECT_EQ(arr->get(2).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kScaleX));\n  EXPECT_EQ(arr->get(3).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kScaleY));\n  EXPECT_EQ(arr->get(4).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kScaleXY));\n  EXPECT_EQ(arr->get(5).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kWidth));\n  EXPECT_EQ(arr->get(6).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kHeight));\n  EXPECT_EQ(\n      arr->get(7).Number(),\n      static_cast<int>(starlight::AnimationPropertyType::kBackgroundColor));\n  EXPECT_EQ(arr->get(8).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kColor));\n  EXPECT_EQ(arr->get(9).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kVisibility));\n  EXPECT_EQ(arr->get(10).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kLeft));\n  EXPECT_EQ(arr->get(11).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kTop));\n  EXPECT_EQ(arr->get(12).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kRight));\n  EXPECT_EQ(arr->get(13).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kBottom));\n  EXPECT_EQ(arr->get(14).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kTransform));\n  EXPECT_EQ(arr->get(15).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kAll));\n  EXPECT_EQ(arr->get(16).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kMaxWidth));\n  EXPECT_EQ(arr->get(17).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kMaxHeight));\n  EXPECT_EQ(arr->get(18).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kMinWidth));\n  EXPECT_EQ(arr->get(19).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kMinHeight));\n}\n\nTEST(AnimationPropertyHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDTransitionProperty;\n  std::vector<std::string> cases = {\"invalid,\", \"2\", \"2s\"};\n  StyleMap output;\n  CSSParserConfigs configs;\n  for (const auto& s : cases) {\n    EXPECT_FALSE(UnitHandler::Process(id, lepus::Value(s), output, configs));\n    EXPECT_TRUE(output.empty());\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_shorthand_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/animation_shorthand_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationShorthandHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  bool single = std::strchr(parser.content(), ',') == nullptr;\n  // [name, duration, delay, timing, count, direction, fill_mode, play_state]\n  lepus::Value arr[8];\n  if (!parser.ParseAnimation(single, arr)) {\n    return false;\n  }\n  // shorthand -> longhand\n  // [name, duration, delay, timing, count, direction, fill_mode, play_state]\n  if (key == kPropertyIDAnimation) {\n    output.emplace_or_assign(\n        CSSPropertyID::kPropertyIDAnimationName, arr[0],\n        single ? CSSValuePattern::STRING : CSSValuePattern::ARRAY);\n    output.emplace_or_assign(\n        CSSPropertyID::kPropertyIDAnimationDuration, arr[1],\n        single ? CSSValuePattern::NUMBER : CSSValuePattern::ARRAY);\n    output.emplace_or_assign(\n        CSSPropertyID::kPropertyIDAnimationDelay, arr[2],\n        single ? CSSValuePattern::NUMBER : CSSValuePattern::ARRAY);\n    output.emplace_or_assign(CSSPropertyID::kPropertyIDAnimationTimingFunction,\n                             arr[3].Array());\n    output.emplace_or_assign(\n        CSSPropertyID::kPropertyIDAnimationIterationCount, arr[4],\n        single ? CSSValuePattern::NUMBER : CSSValuePattern::ARRAY);\n    output.emplace_or_assign(\n        CSSPropertyID::kPropertyIDAnimationDirection, arr[5],\n        single ? CSSValuePattern::ENUM : CSSValuePattern::ARRAY);\n    output.emplace_or_assign(\n        CSSPropertyID::kPropertyIDAnimationFillMode, arr[6],\n        single ? CSSValuePattern::ENUM : CSSValuePattern::ARRAY);\n    output.emplace_or_assign(\n        CSSPropertyID::kPropertyIDAnimationPlayState, arr[7],\n        single ? CSSValuePattern::ENUM : CSSValuePattern::ARRAY);\n  } else {\n    // [name, duration, delay, timing, count, direction, fill_mode, play_state]\n    static const CSSPropertyID keys[8] = {\n        kPropertyIDAnimationName,           kPropertyIDAnimationDuration,\n        kPropertyIDAnimationDelay,          kPropertyIDAnimationTimingFunction,\n        kPropertyIDAnimationIterationCount, kPropertyIDAnimationDirection,\n        kPropertyIDAnimationFillMode,       kPropertyIDAnimationPlayState};\n    auto map = lepus::Dictionary::Create();\n    for (size_t i = 0; i < 8; i++) {\n      map->SetValue(std::to_string(keys[i]), arr[i]);\n    }\n    output.emplace_or_assign(key, lepus::Value(std::move(map)),\n                             CSSValuePattern::MAP);\n  }\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDAnimation] = &Handle;\n  array[kPropertyIDEnterTransitionName] = &Handle;\n  array[kPropertyIDExitTransitionName] = &Handle;\n  array[kPropertyIDPauseTransitionName] = &Handle;\n  array[kPropertyIDResumeTransitionName] = &Handle;\n}\n\n}  // namespace AnimationShorthandHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/animation_shorthand_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_ANIMATION_SHORTHAND_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_ANIMATION_SHORTHAND_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AnimationShorthandHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace AnimationShorthandHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_ANIMATION_SHORTHAND_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/animation_shorthand_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/animation_shorthand_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(AnimationShorthandHandler, Animation) {\n  auto id = CSSPropertyID::kPropertyIDAnimation;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value(\"rotate 10s ease 1s 10 forwards\");\n  bool ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output[kPropertyIDAnimationName].GetValue().StringView(), \"rotate\");\n  EXPECT_EQ(output[kPropertyIDAnimationDuration].GetNumber(), 10000);\n  auto timing_function =\n      output[kPropertyIDAnimationTimingFunction].GetArray().strongify();\n  EXPECT_EQ(timing_function->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kEaseInEaseOut));\n  EXPECT_EQ(output[kPropertyIDAnimationDelay].GetNumber(), 1000);\n  EXPECT_EQ(output[kPropertyIDAnimationIterationCount].GetNumber(), 10);\n  EXPECT_EQ(output[kPropertyIDAnimationFillMode].GetNumber(),\n            static_cast<int>(starlight::AnimationFillModeType::kForwards));\n\n  output.clear();\n  impl = lepus::Value(\"10\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output[kPropertyIDAnimationName].GetValue().StringView(), \"none\");\n  EXPECT_EQ(output[kPropertyIDAnimationDuration].GetNumber(), 0);\n  EXPECT_TRUE(output[kPropertyIDAnimationTimingFunction].GetValue().IsArray());\n  timing_function = output[kPropertyIDAnimationTimingFunction].GetArray();\n  EXPECT_EQ(timing_function->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kLinear));\n  EXPECT_EQ(output[kPropertyIDAnimationDelay].GetNumber(), 0);\n  EXPECT_EQ(output[kPropertyIDAnimationIterationCount].GetNumber(), 10);\n\n  output.clear();\n  impl = lepus::Value(\"10s 10 test\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output[kPropertyIDAnimationName].GetValue().StringView(), \"test\");\n  EXPECT_EQ(output[kPropertyIDAnimationDuration].GetNumber(), 10000);\n  EXPECT_TRUE(output[kPropertyIDAnimationTimingFunction].GetValue().IsArray());\n  timing_function = output[kPropertyIDAnimationTimingFunction].GetArray();\n  EXPECT_EQ(timing_function->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kLinear));\n  EXPECT_EQ(output[kPropertyIDAnimationDelay].GetNumber(), 0);\n  EXPECT_EQ(output[kPropertyIDAnimationIterationCount].GetNumber(), 10);\n\n  output.clear();\n  impl = lepus::Value(\"10s ease 1s forwards 10 item1-ani-frames\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output[kPropertyIDAnimationName].GetValue().StringView(),\n            \"item1-ani-frames\");\n  EXPECT_EQ(output[kPropertyIDAnimationDuration].GetNumber(), 10000);\n  EXPECT_TRUE(output[kPropertyIDAnimationTimingFunction].GetValue().IsArray());\n  timing_function = output[kPropertyIDAnimationTimingFunction].GetArray();\n  EXPECT_EQ(timing_function->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kEaseInEaseOut));\n  EXPECT_EQ(output[kPropertyIDAnimationDelay].GetNumber(), 1000);\n  EXPECT_EQ(output[kPropertyIDAnimationIterationCount].GetNumber(), 10);\n  EXPECT_EQ(output[kPropertyIDAnimationFillMode].GetNumber(),\n            static_cast<int>(starlight::AnimationFillModeType::kForwards));\n\n  output.clear();\n  impl = lepus::Value(\"10s ease 1ms forwards infinite test paused reverse\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) == output.end());\n\n  EXPECT_EQ(output[kPropertyIDAnimationName].GetValue().StringView(), \"test\");\n  EXPECT_EQ(output[kPropertyIDAnimationDuration].GetNumber(), 10000);\n  EXPECT_TRUE(output[kPropertyIDAnimationTimingFunction].GetValue().IsArray());\n  timing_function = output[kPropertyIDAnimationTimingFunction].GetArray();\n  EXPECT_EQ(timing_function->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kEaseInEaseOut));\n  EXPECT_EQ(output[kPropertyIDAnimationDelay].GetNumber(), 1);\n  EXPECT_EQ(output[kPropertyIDAnimationIterationCount].GetNumber(), 10E8);\n  EXPECT_EQ(output[kPropertyIDAnimationFillMode].GetNumber(),\n            static_cast<int>(starlight::AnimationFillModeType::kForwards));\n  EXPECT_EQ(output[kPropertyIDAnimationPlayState].GetNumber(),\n            static_cast<int>(starlight::AnimationPlayStateType::kPaused));\n  EXPECT_EQ(output[kPropertyIDAnimationDirection].GetNumber(),\n            static_cast<int>(starlight::AnimationDirectionType::kReverse));\n}\n\nTEST(AnimationShorthandHandler, Default) {\n  auto id = CSSPropertyID::kPropertyIDAnimation;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value(\"test\");\n  bool ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) == output.end());\n\n  EXPECT_EQ(output[kPropertyIDAnimationName].GetValue().StringView(), \"test\");\n  EXPECT_EQ(output[kPropertyIDAnimationDuration].GetNumber(), 0);\n  EXPECT_TRUE(output[kPropertyIDAnimationTimingFunction].GetValue().IsArray());\n  auto timing_function = output[kPropertyIDAnimationTimingFunction].GetArray();\n  EXPECT_EQ(timing_function->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kLinear));\n  EXPECT_EQ(output[kPropertyIDAnimationDelay].GetNumber(), 0);\n  EXPECT_EQ(output[kPropertyIDAnimationIterationCount].GetNumber(), 1);\n  EXPECT_EQ(output[kPropertyIDAnimationFillMode].GetNumber(),\n            static_cast<int>(starlight::AnimationFillModeType::kNone));\n  EXPECT_EQ(output[kPropertyIDAnimationPlayState].GetNumber(),\n            static_cast<int>(starlight::AnimationPlayStateType::kRunning));\n  EXPECT_EQ(output[kPropertyIDAnimationDirection].GetNumber(),\n            static_cast<int>(starlight::AnimationDirectionType::kNormal));\n}\n\nTEST(AnimationShorthandHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDAnimation;\n  StyleMap output;\n  CSSParserConfigs configs;\n  std::vector<std::string> cases = {\"test test\", \"12s 12s 10ms\", \"ease ease\",\n                                    \"test, \"};\n\n  for (const auto& s : cases) {\n    output.clear();\n    EXPECT_FALSE(UnitHandler::Process(id, lepus::Value(s), output, configs));\n    EXPECT_TRUE(output.empty());\n  }\n}\n\nTEST(AnimationShorthandHandler, Legacy) {\n  auto id = CSSPropertyID::kPropertyIDEnterTransitionName;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value(true);\n  bool ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  impl = lepus::Value(\"test 10s ease 1s infinite forwards\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationName) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationDuration) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationTimingFunction) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationFillMode) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationIterationCount) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationDelay) == output.end());\n  EXPECT_EQ(output[id]\n                .GetValue()\n                .Table()\n                ->GetValue(std::to_string(tasm::kPropertyIDAnimationName))\n                .StringView(),\n            \"test\");\n  EXPECT_EQ(output[id]\n                .GetValue()\n                .Table()\n                ->GetValue(std::to_string(tasm::kPropertyIDAnimationDuration))\n                .Number(),\n            10000);\n  EXPECT_EQ(\n      (starlight::TimingFunctionType)output[id]\n          .GetValue()\n          .Table()\n          ->GetValue(std::to_string(tasm::kPropertyIDAnimationTimingFunction))\n          .Array()\n          ->get(0)\n          .Number(),\n      starlight::TimingFunctionType::kEaseInEaseOut);\n  EXPECT_EQ(output[id]\n                .GetValue()\n                .Table()\n                ->GetValue(std::to_string(tasm::kPropertyIDAnimationDelay))\n                .Number(),\n            1000);\n  EXPECT_EQ(\n      output[id]\n          .GetValue()\n          .Table()\n          ->GetValue(std::to_string(tasm::kPropertyIDAnimationIterationCount))\n          .Number(),\n      10E8);\n  EXPECT_EQ((starlight::AnimationFillModeType)output[id]\n                .GetValue()\n                .Table()\n                ->GetValue(std::to_string(tasm::kPropertyIDAnimationFillMode))\n                .Number(),\n            starlight::AnimationFillModeType::kForwards);\n\n  output.clear();\n  impl = lepus::Value(\"10s ease 1s forwards infinite test\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationName) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationDuration) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationTimingFunction) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationFillMode) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationIterationCount) == output.end());\n  EXPECT_TRUE(output.find(kPropertyIDAnimationDelay) == output.end());\n  EXPECT_EQ(output[id]\n                .GetValue()\n                .Table()\n                ->GetValue(std::to_string(tasm::kPropertyIDAnimationName))\n                .StringView(),\n            \"test\");\n  EXPECT_EQ(output[id]\n                .GetValue()\n                .Table()\n                ->GetValue(std::to_string(tasm::kPropertyIDAnimationDuration))\n                .Number(),\n            10000);\n  EXPECT_EQ(\n      output[id]\n          .GetValue()\n          .Table()\n          ->GetValue(std::to_string(tasm::kPropertyIDAnimationTimingFunction))\n          .Array()\n          ->get(0)\n          .Number(),\n      static_cast<int>(starlight::TimingFunctionType::kEaseInEaseOut));\n  EXPECT_EQ(output[id]\n                .GetValue()\n                .Table()\n                ->GetValue(std::to_string(tasm::kPropertyIDAnimationDelay))\n                .Number(),\n            1000);\n  EXPECT_EQ(\n      output[id]\n          .GetValue()\n          .Table()\n          ->GetValue(std::to_string(tasm::kPropertyIDAnimationIterationCount))\n          .Number(),\n      10E8);\n  EXPECT_EQ(output[id]\n                .GetValue()\n                .Table()\n                ->GetValue(std::to_string(tasm::kPropertyIDAnimationFillMode))\n                .Number(),\n            static_cast<int>(starlight::AnimationFillModeType::kForwards));\n\n  output.clear();\n  impl = lepus::Value(\"10s ease 1s forwards infinite test\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsMap());\n  auto table = output[id].GetValue().Table();\n  auto holder =\n      table->GetValue(base::String(std::to_string(kPropertyIDAnimationName)));\n  EXPECT_EQ(holder.StringView(), \"test\");\n  auto holder2 = table->GetValue(\n      base::String(std::to_string(kPropertyIDAnimationDuration)));\n  EXPECT_EQ(holder2.Number(), 10000);\n  auto holder3 =\n      table->GetValue(base::String(std::to_string(kPropertyIDAnimationDelay)));\n  EXPECT_EQ(holder3.Number(), 1000);\n  auto holder4 = table->GetValue(\n      base::String(std::to_string(kPropertyIDAnimationTimingFunction)));\n  EXPECT_TRUE(holder4.IsArray());\n  EXPECT_EQ(holder4.Array()->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kEaseInEaseOut));\n  auto holder5 = table->GetValue(\n      base::String(std::to_string(kPropertyIDAnimationFillMode)));\n  EXPECT_EQ(holder5.Number(),\n            static_cast<int>(starlight::AnimationFillModeType::kForwards));\n  auto holder6 = table->GetValue(\n      base::String(std::to_string(kPropertyIDAnimationIterationCount)));\n  EXPECT_EQ(holder6.Number(), 10E8);\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/aspect_ratio_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/aspect_ratio_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AspectRatioHandler {\n\nHANDLER_IMPL() {\n  if (input.IsNumber()) {\n    output.emplace_or_assign(key, input, CSSValuePattern::NUMBER);\n    return true;\n  }\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto ret = parser.ParseAspectRatio();\n  if (!ret.IsEmpty()) {\n    output.insert_or_assign(key, std::move(ret));\n    return true;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDAspectRatio] = &Handle; }\n\n}  // namespace AspectRatioHandler\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/aspect_ratio_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_ASPECT_RATIO_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_ASPECT_RATIO_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AspectRatioHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace AspectRatioHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_ASPECT_RATIO_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/aspect_ratio_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/aspect_ratio_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(AspectRatioHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDAspectRatio;\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"10/100\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_FLOAT_EQ(output[id].GetNumber(), 0.1);\n\n  output.clear();\n  impl = lepus::Value(\"10\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 10);\n\n  output.clear();\n  impl = lepus::Value(0.25);\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 0.25);\n\n  output.clear();\n  impl = lepus::Value(-0.75);\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), -0.75);\n}\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/auto_font_size_handler.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/auto_font_size_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/parser/length_handler.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AutoFontSizeHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  auto array = lepus::CArray::Create();\n\n  CSSValue is_auto_font_size = CSSValue(false);\n  CSSValue auto_font_size_min_size = CSSValue(0, CSSValuePattern::PX);\n  CSSValue auto_font_size_max_size = CSSValue(0, CSSValuePattern::PX);\n  CSSValue auto_font_size_step_granularity = CSSValue(1, CSSValuePattern::PX);\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto res = parser.ParseAutoFontSize(\n      is_auto_font_size, auto_font_size_min_size, auto_font_size_max_size,\n      auto_font_size_step_granularity);\n  if (!res) {\n    return false;\n  }\n\n  array->emplace_back(is_auto_font_size.GetValue());\n  array->emplace_back(auto_font_size_min_size.GetValue());\n  array->emplace_back(\n      static_cast<int32_t>(auto_font_size_min_size.GetPattern()));\n  array->emplace_back(auto_font_size_max_size.GetValue());\n  array->emplace_back(\n      static_cast<int32_t>(auto_font_size_max_size.GetPattern()));\n  array->emplace_back(auto_font_size_step_granularity.GetValue());\n  array->emplace_back(\n      static_cast<int32_t>(auto_font_size_step_granularity.GetPattern()));\n\n  output.emplace_or_assign(key, std::move(array));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDXAutoFontSize] = &Handle; }\n\n}  // namespace AutoFontSizeHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/auto_font_size_handler.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_AUTO_FONT_SIZE_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_AUTO_FONT_SIZE_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AutoFontSizeHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace AutoFontSizeHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_AUTO_FONT_SIZE_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/auto_font_size_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/auto_font_size_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(AutoFontSizeHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDXAutoFontSize;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr0 = output[id].GetArray();\n  EXPECT_FALSE(arr0->get(0).Bool());\n\n  output.clear();\n  impl = lepus::Value(\"false\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr1 = output[id].GetArray();\n  EXPECT_FALSE(arr1->get(0).Bool());\n\n  output.clear();\n  impl = lepus::Value(\"true 8px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr2 = output[id].GetArray();\n  EXPECT_TRUE(arr2->get(0).Bool());\n  EXPECT_EQ(arr2->get(1).Number(), 8.f);\n  EXPECT_EQ(static_cast<CSSValuePattern>(arr2->get(2).Number()),\n            CSSValuePattern::PX);\n\n  output.clear();\n  impl = lepus::Value(\"true 8px 20.8px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr3 = output[id].GetArray();\n  EXPECT_TRUE(arr3->get(0).Bool());\n  EXPECT_EQ(arr3->get(1).Number(), 8.f);\n  EXPECT_EQ(static_cast<CSSValuePattern>(arr3->get(2).Number()),\n            CSSValuePattern::PX);\n  EXPECT_EQ(arr3->get(3).Number(), 20.8);\n  EXPECT_EQ(static_cast<CSSValuePattern>(arr3->get(4).Number()),\n            CSSValuePattern::PX);\n\n  output.clear();\n  impl = lepus::Value(\"true 8px 20.8px 3px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr4 = output[id].GetArray();\n  EXPECT_TRUE(arr4->get(0).Bool());\n  EXPECT_EQ(arr4->get(5).Number(), 3.f);\n  EXPECT_EQ(static_cast<CSSValuePattern>(arr4->get(6).Number()),\n            CSSValuePattern::PX);\n\n  output.clear();\n  impl = lepus::Value(\"true 8px 20.8px 3px 88\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  output.clear();\n  impl = lepus::Value(\"true px 20.8px\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  output.clear();\n  impl = lepus::Value(\"true px 20.8px\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  output.clear();\n  impl = lepus::Value(10);\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/auto_font_size_preset_sizes_handler.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/auto_font_size_preset_sizes_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AutoFontSizePresetSizesHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto arr = lepus::CArray::Create();\n  if (!parser.ParseAutoFontSizePresetSize(arr)) {\n    return false;\n  }\n  output.emplace_or_assign(key, std::move(arr));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDXAutoFontSizePresetSizes] = &Handle;\n}\n\n}  // namespace AutoFontSizePresetSizesHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/auto_font_size_preset_sizes_handler.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_AUTO_FONT_SIZE_PRESET_SIZES_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_AUTO_FONT_SIZE_PRESET_SIZES_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace AutoFontSizePresetSizesHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace AutoFontSizePresetSizesHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_AUTO_FONT_SIZE_PRESET_SIZES_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/auto_font_size_preset_sizes_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/auto_font_size_preset_sizes_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(XAutoFontSizePresetSizesHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDXAutoFontSizePresetSizes;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr0 = output[id].GetArray();\n  EXPECT_TRUE(arr0->size() == 0);\n\n  output.clear();\n  impl = lepus::Value(\"10px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr1 = output[id].GetArray();\n  EXPECT_TRUE(arr1->size() == 2);\n  EXPECT_EQ(static_cast<CSSValuePattern>(arr1->get(1).Number()),\n            CSSValuePattern::PX);\n\n  output.clear();\n  impl = lepus::Value(\"10rpx 8px 2em\");\n  UnitHandler::Process(id, impl, output, configs);\n  auto arr2 = output[id].GetArray();\n  EXPECT_TRUE(arr2->size() == 6);\n  EXPECT_EQ(static_cast<CSSValuePattern>(arr2->get(1).Number()),\n            CSSValuePattern::RPX);\n  EXPECT_EQ(static_cast<CSSValuePattern>(arr2->get(3).Number()),\n            CSSValuePattern::PX);\n  EXPECT_EQ(static_cast<CSSValuePattern>(arr2->get(5).Number()),\n            CSSValuePattern::EM);\n  EXPECT_EQ(arr2->get(2).Number(), 8.f);\n\n  output.clear();\n  impl = lepus::Value(\"true\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  output.clear();\n  impl = lepus::Value(\"10px dd 30px\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_box_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/background_box_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundBoxHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto box = parser.ParseBackgroundBox();\n  if (box.IsEmpty()) {\n    return false;\n  }\n  output.insert_or_assign(key, std::move(box));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDBackgroundClip] = &Handle;\n  array[kPropertyIDBackgroundOrigin] = &Handle;\n  array[kPropertyIDMaskClip] = &Handle;\n  array[kPropertyIDMaskOrigin] = &Handle;\n}\n}  // namespace BackgroundBoxHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_box_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_BACKGROUND_BOX_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_BACKGROUND_BOX_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundBoxHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace BackgroundBoxHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_BACKGROUND_BOX_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/background_box_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/background_box_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(BackgroundOriginHandler, Valid) {\n  CSSParserConfigs configs;\n  {\n    // content-box\n    auto raw = R\"(content-box)\";\n    CSSStringParser parser{raw, static_cast<uint32_t>(strlen(raw)), configs};\n    CSSValue box = parser.ParseBackgroundBox();\n    EXPECT_TRUE(box.IsArray());\n    EXPECT_NE(box.GetArray()->size(), 0);\n    EXPECT_EQ(\n        box.GetArray()->get(0).Number(),\n        static_cast<uint32_t>(starlight::BackgroundOriginType::kContentBox));\n  }\n  {\n    // padding-box\n    auto raw = R\"(padding-box)\";\n    CSSStringParser parser{raw, static_cast<uint32_t>(strlen(raw)), configs};\n    CSSValue box = parser.ParseBackgroundBox();\n    EXPECT_TRUE(box.IsArray());\n    EXPECT_NE(box.GetArray()->size(), 0);\n    EXPECT_EQ(\n        box.GetArray()->get(0).Number(),\n        static_cast<uint32_t>(starlight::BackgroundOriginType::kPaddingBox));\n  }\n  {\n    // border-box\n    auto raw = R\"(border-box)\";\n    CSSStringParser parser{raw, static_cast<uint32_t>(strlen(raw)), configs};\n    CSSValue box = parser.ParseBackgroundBox();\n    EXPECT_TRUE(box.IsArray());\n    EXPECT_NE(box.GetArray()->size(), 0);\n    EXPECT_EQ(\n        box.GetArray()->get(0).Number(),\n        static_cast<uint32_t>(starlight::BackgroundOriginType::kBorderBox));\n  }\n  {\n    // multiple value\n    auto raw = R\"(border-box, padding-box, content-box)\";\n    CSSStringParser parser{raw, static_cast<uint32_t>(strlen(raw)), configs};\n    CSSValue box = parser.ParseBackgroundBox();\n    EXPECT_TRUE(box.IsArray());\n    EXPECT_NE(box.GetArray()->size(), 0);\n    EXPECT_EQ(\n        box.GetArray()->get(0).Number(),\n        static_cast<uint32_t>(starlight::BackgroundOriginType::kBorderBox));\n    EXPECT_EQ(\n        box.GetArray()->get(1).Number(),\n        static_cast<uint32_t>(starlight::BackgroundOriginType::kPaddingBox));\n    EXPECT_EQ(\n        box.GetArray()->get(2).Number(),\n        static_cast<uint32_t>(starlight::BackgroundOriginType::kContentBox));\n  }\n}\n\nTEST(BackgroundBoxHandler, Invalid) {\n  CSSParserConfigs configs;\n  auto test_invalid = [configs](const char* val) {\n    CSSStringParser parser{val, static_cast<uint32_t>(strlen(val)), configs};\n    CSSValue box = parser.ParseBackgroundBox();\n    EXPECT_TRUE(box.IsEmpty());\n  };\n  const char* values[4] = {\"fill-box\", \"margin-box\", \"stroke-box\", \"view-box\"};\n  for (auto* val : values) {\n    test_invalid(val);\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_clip_handler.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/background_clip_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundClipHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto clip = parser.ParseBackgroundClip();\n  if (clip.IsEmpty()) {\n    return false;\n  }\n  output.insert_or_assign(key, std::move(clip));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDBackgroundClip] = &Handle; }\n}  // namespace BackgroundClipHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_clip_handler.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_BACKGROUND_CLIP_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_BACKGROUND_CLIP_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundClipHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace BackgroundClipHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_BACKGROUND_CLIP_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/background_clip_handler_unittest.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/background_clip_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(BackgroundClipHandler, Valid) {\n  CSSParserConfigs configs;\n  {\n    // content-box\n    auto raw = R\"(content-box)\";\n    CSSStringParser parser{raw, static_cast<uint32_t>(strlen(raw)), configs};\n    CSSValue clip = parser.ParseBackgroundClip();\n    EXPECT_TRUE(clip.IsArray());\n    EXPECT_NE(clip.GetArray()->size(), 0);\n    EXPECT_EQ(\n        clip.GetArray()->get(0).Number(),\n        static_cast<uint32_t>(starlight::BackgroundClipType::kContentBox));\n  }\n  {\n    // padding-box\n    auto raw = R\"(padding-box)\";\n    CSSStringParser parser{raw, static_cast<uint32_t>(strlen(raw)), configs};\n    CSSValue clip = parser.ParseBackgroundClip();\n    EXPECT_TRUE(clip.IsArray());\n    EXPECT_NE(clip.GetArray()->size(), 0);\n    EXPECT_EQ(\n        clip.GetArray()->get(0).Number(),\n        static_cast<uint32_t>(starlight::BackgroundClipType::kPaddingBox));\n  }\n  {\n    // border-box\n    auto raw = R\"(border-box)\";\n    CSSStringParser parser{raw, static_cast<uint32_t>(strlen(raw)), configs};\n    CSSValue clip = parser.ParseBackgroundClip();\n    EXPECT_TRUE(clip.IsArray());\n    EXPECT_NE(clip.GetArray()->size(), 0);\n    EXPECT_EQ(clip.GetArray()->get(0).Number(),\n              static_cast<uint32_t>(starlight::BackgroundClipType::kBorderBox));\n  }\n  {\n    // text\n    auto raw = R\"(text)\";\n    CSSStringParser parser{raw, static_cast<uint32_t>(strlen(raw)), configs};\n    CSSValue clip = parser.ParseBackgroundClip();\n    EXPECT_TRUE(clip.IsArray());\n    EXPECT_NE(clip.GetArray()->size(), 0);\n    EXPECT_EQ(clip.GetArray()->get(0).Number(),\n              static_cast<uint32_t>(starlight::BackgroundClipType::kText));\n  }\n  {\n    // multiple value\n    auto raw = R\"(border-box, padding-box, content-box, text)\";\n    CSSStringParser parser{raw, static_cast<uint32_t>(strlen(raw)), configs};\n    CSSValue clip = parser.ParseBackgroundClip();\n    EXPECT_TRUE(clip.IsArray());\n    EXPECT_NE(clip.GetArray()->size(), 0);\n    EXPECT_EQ(clip.GetArray()->get(0).Number(),\n              static_cast<uint32_t>(starlight::BackgroundClipType::kBorderBox));\n    EXPECT_EQ(\n        clip.GetArray()->get(1).Number(),\n        static_cast<uint32_t>(starlight::BackgroundClipType::kPaddingBox));\n    EXPECT_EQ(\n        clip.GetArray()->get(2).Number(),\n        static_cast<uint32_t>(starlight::BackgroundClipType::kContentBox));\n    EXPECT_EQ(clip.GetArray()->get(3).Number(),\n              static_cast<uint32_t>(starlight::BackgroundClipType::kText));\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_image_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/background_image_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundImageHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto image = parser.ParseBackgroundImage();\n  if (image.IsEmpty()) {\n    return false;\n  }\n  output.insert_or_assign(key, std::move(image));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDBackgroundImage] = &Handle;\n  array[kPropertyIDMaskImage] = &Handle;\n}\n\n}  // namespace BackgroundImageHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_image_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_BACKGROUND_IMAGE_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_BACKGROUND_IMAGE_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundImageHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace BackgroundImageHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_BACKGROUND_IMAGE_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/background_image_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/background_image_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/css_color.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\ntemplate <typename T, typename Enable = void>\nstruct LepusCheck {};\n\ntemplate <>\nstruct LepusCheck<uint32_t> {\n  static void Check(lepus::Value const& value, uint32_t t) {\n    ASSERT_TRUE(value.IsUInt32());\n    EXPECT_EQ(value.UInt32(), t);\n  }\n};\n\ntemplate <>\nstruct LepusCheck<int32_t> {\n  static void Check(lepus::Value const& value, int32_t t) {\n    ASSERT_TRUE(value.IsNumber());\n    EXPECT_EQ(static_cast<int32_t>(value.Number()), t);\n  }\n};\n\ntemplate <>\nstruct LepusCheck<float> {\n  static void Check(lepus::Value const& value, float t) {\n    ASSERT_TRUE(value.IsNumber());\n    EXPECT_NEAR(value.Number(), t, 0.01);\n  }\n};\n\ntemplate <>\nstruct LepusCheck<std::string> {\n  static void Check(lepus::Value const& value, std::string const& t) {\n    ASSERT_TRUE(value.IsString());\n    EXPECT_EQ(value.StdString(), t);\n  }\n};\n\ntemplate <>\nstruct LepusCheck<const char*> {\n  static void Check(lepus::Value const& value, const char* t) {\n    ASSERT_TRUE(value.IsString());\n    EXPECT_EQ(value.StdString(), std::string(t));\n  }\n};\n\ntemplate <typename T>\nstruct LepusCheck<T, typename std::enable_if_t<std::is_enum<T>::value>> {\n  static void Check(lepus::Value const& value, T t) {\n    LepusCheck<uint32_t>::Check(value, static_cast<uint32_t>(t));\n  }\n};\n\ntemplate <typename T>\nstruct LepusCheck<std::vector<T>> {\n  static void Check(lepus::Value const& value, std::vector<T> const& t) {\n    ASSERT_TRUE(value.IsArray());\n    auto array = value.Array();\n    for (size_t i = 0; i < t.size(); i++) {\n      LepusCheck<T>::Check(array->get(i), t[i]);\n    }\n  }\n};\n\ntemplate <typename T, typename F, size_t... I>\nvoid TupleForEach(T&& t, F f, std::integer_sequence<size_t, I...>) {\n  auto l = {(f(I, std::get<I>(t)), 0)...};\n  // prevent unused warnning.\n  LOGI(&l);\n}\n\ntemplate <typename T>\nvoid LepusCheckFunc(lepus::Value const& value, T const& t) {\n  LepusCheck<T>::Check(value, t);\n}\n\ntemplate <typename... Args>\nstruct LepusCheck<std::tuple<Args...>> {\n  using tuple_type = typename std::tuple<Args...>;\n  static void Check(lepus::Value const& value, tuple_type const& t) {\n    ASSERT_TRUE(value.IsArray());\n    auto array = value.Array();\n    ASSERT_EQ(array->size(), std::tuple_size<tuple_type>::value);\n\n    TupleForEach(\n        t, [&array](size_t i, auto f) { LepusCheckFunc(array->get(i), f); },\n        std::make_integer_sequence<size_t, sizeof...(Args)>());\n  }\n};\n\ntemplate <typename... Args>\nvoid LepusCheckEach(lepus::Value const& value, Args&&... args) {\n  std::tuple<Args...> tuple{args...};\n  LepusCheck<std::tuple<Args...>>::Check(value, tuple);\n}\n\nTEST(BackgroundImageHandler, parse_url_image) {\n  std::string url_list =\n      \"url('https://yyy/i/bg_flower.gif'), \"\n      \"url('https://tttt/files/7693/catfront.png'),url('https:/\"\n      \"/xxxx/ee/lynx-home/static/img/\"\n      \"zh-logo-color.7c750dd6.png');\";\n  CSSParserConfigs configs;\n  CSSStringParser url_list_parser{\n      url_list.c_str(), static_cast<uint32_t>(url_list.size()), configs};\n\n  CSSValue result = url_list_parser.ParseBackgroundImage();\n\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  EXPECT_TRUE(result.IsArray());\n  auto url_array = result.GetArray();\n  EXPECT_EQ(url_array->size(), static_cast<size_t>(6));\n\n  LepusCheckEach(result.GetValue(), starlight::BackgroundImageType::kUrl,\n                 \"https://yyy/i/bg_flower.gif\",\n                 starlight::BackgroundImageType::kUrl,\n                 \"https://tttt/files/7693/catfront.png\",\n                 starlight::BackgroundImageType::kUrl,\n                 \"https://xxxx/ee/lynx-home/static/img/\"\n                 \"zh-logo-color.7c750dd6.png\");\n\n  std::string url_list2 = \"url(\\\"https://yyy/i/bg_flower.gif\\\")\";\n  CSSStringParser url_list_parser2{\n      url_list2.c_str(), static_cast<uint32_t>(url_list2.size()), configs};\n\n  CSSValue result2 = url_list_parser2.ParseBackgroundImage();\n  EXPECT_EQ(result2.GetPattern(), CSSValuePattern::ARRAY);\n  LepusCheckEach(result2.GetValue(), starlight::BackgroundImageType::kUrl,\n                 \"https://yyy/i/bg_flower.gif\");\n}\n\nTEST(BackgroundImageHandler, parse_url_data) {\n  std::string url_list = \"url(\\\"data:image/png;base64,\\\") \";\n  CSSParserConfigs configs;\n  CSSStringParser url_list_parser{\n      url_list.c_str(), static_cast<uint32_t>(url_list.size()), configs};\n\n  CSSValue result = url_list_parser.ParseBackgroundImage();\n\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  EXPECT_TRUE(result.IsArray());\n  auto url_array = result.GetArray();\n  EXPECT_EQ(url_array->size(), static_cast<size_t>(2));\n\n  LepusCheckEach(result.GetValue(), starlight::BackgroundImageType::kUrl,\n                 \"data:image/png;base64,\");\n}\n\nstatic void ExpectLinearGradientEQ(const lepus::Value& value, float deg,\n                                   const std::vector<uint32_t>& colors,\n                                   const std::vector<float>& stops,\n                                   int direction) {\n  LepusCheckEach(value, deg, colors, stops, direction);\n}\n\nTEST(BackgroundImageHandler, parse_linear_gradient) {\n  CSSParserConfigs configs;\n  std::string bg_image = \"linear-gradient(to left, red, blue 30%, green 0.9);\";\n  CSSStringParser parser{bg_image.c_str(),\n                         static_cast<uint32_t>(bg_image.size()), configs};\n\n  CSSValue result = parser.ParseBackgroundImage();\n\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  auto url_array = result.GetArray();\n  EXPECT_EQ(url_array->size(), static_cast<size_t>(2));\n  EXPECT_TRUE(url_array->get(0).IsUInt32());\n  EXPECT_EQ(\n      url_array->get(0).UInt32(),\n      static_cast<uint32_t>(starlight::BackgroundImageType::kLinearGradient));\n\n  ExpectLinearGradientEQ(url_array->get(1), 270.f,\n                         {0xffff0000, 0xff0000ff, 0xff008000},\n                         {0.f, 30.f, 90.f}, 3);\n\n  std::string bg_image2 =\n      \"linear-gradient(rgba(0, 0, 255, 0.5), rgba(255, 255, 0, 0.5));\";\n  CSSStringParser parser2{bg_image2.c_str(),\n                          static_cast<uint32_t>(bg_image2.size()), configs};\n\n  CSSValue value2 = parser2.ParseBackgroundImage();\n\n  ASSERT_TRUE(value2.IsArray());\n  LepusCheckEach(\n      value2.GetValue(), starlight::BackgroundImageType::kLinearGradient,\n      std::make_tuple(180.f,\n                      std::vector<uint32_t>{CSSColor(0, 0, 255, 0.5f).Cast(),\n                                            CSSColor{255, 255, 0, 0.5f}.Cast()},\n                      std::vector<float>{}, 2));\n}\n\nTEST(BackgroundImageHandler, parse_radial_gradient) {\n  std::string radial_gradient =\n      \"radial-gradient(ellipse at top, red, transparent)\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{radial_gradient.c_str(),\n                         static_cast<uint32_t>(radial_gradient.size()),\n                         configs};\n  CSSValue value = parser.ParseBackgroundImage();\n\n  ASSERT_TRUE(value.IsArray());\n  auto array = value.GetArray();\n  LepusCheckFunc(array->get(0),\n                 starlight::BackgroundImageType::kRadialGradient);\n  LepusCheckEach(\n      array->get(1),\n      std::make_tuple(starlight::RadialGradientShapeType::kEllipse,\n                      starlight::RadialGradientSizeType::kFarthestCorner,\n                      starlight::BackgroundPositionType::kCenter,\n                      -1.f * static_cast<uint32_t>(\n                                 starlight::BackgroundPositionType::kCenter),\n                      starlight::BackgroundPositionType::kTop, -32),\n      std::vector<uint32_t>{0xffff0000, 0x0}, std::vector<float>{});\n}\n\nTEST(BackgroundImageHandler, parse_radial_gradient_with_array) {\n  std::string radial_gradient =\n      \"radial-gradient(ellipse at top, red, transparent), \"\n      \"radial-gradient(ellipse at right, blue, transparent)\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{radial_gradient.c_str(),\n                         static_cast<uint32_t>(radial_gradient.size()),\n                         configs};\n  CSSValue value = parser.ParseBackgroundImage();\n\n  ASSERT_TRUE(value.IsArray());\n  auto array = value.GetArray();\n  LepusCheckFunc(array->get(0),\n                 starlight::BackgroundImageType::kRadialGradient);\n  LepusCheckEach(\n      array->get(1),\n      std::make_tuple(starlight::RadialGradientShapeType::kEllipse,\n                      starlight::RadialGradientSizeType::kFarthestCorner,\n                      starlight::BackgroundPositionType::kCenter,\n                      -1.f * static_cast<uint32_t>(\n                                 starlight::BackgroundPositionType::kCenter),\n                      starlight::BackgroundPositionType::kTop,\n                      -1.f * static_cast<uint32_t>(\n                                 starlight::BackgroundPositionType::kTop)),\n      std::vector<uint32_t>{0xffff0000, 0x0}, std::vector<float>{});\n  LepusCheckFunc(array->get(2),\n                 starlight::BackgroundImageType::kRadialGradient);\n  LepusCheckEach(\n      array->get(3),\n      std::make_tuple(starlight::RadialGradientShapeType::kEllipse,\n                      starlight::RadialGradientSizeType::kFarthestCorner,\n                      starlight::BackgroundPositionType::kRight,\n                      -1.f * static_cast<uint32_t>(\n                                 starlight::BackgroundPositionType::kRight),\n                      starlight::BackgroundPositionType::kCenter,\n                      -1.f * static_cast<uint32_t>(\n                                 starlight::BackgroundPositionType::kCenter)),\n      std::vector<uint32_t>{0xff0000ff, 0x0}, std::vector<float>{});\n}\n\nTEST(BackgroundImageHandler, parse_radial_gradient_with_size) {\n  std::string radial_gradient =\n      \"radial-gradient(ellipse 10px 5px at top, red, transparent)\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{radial_gradient.c_str(),\n                         static_cast<uint32_t>(radial_gradient.size()),\n                         configs};\n  CSSValue value = parser.ParseBackgroundImage();\n\n  ASSERT_TRUE(value.IsArray());\n  auto array = value.GetArray();\n  LepusCheckFunc(array->get(0),\n                 starlight::BackgroundImageType::kRadialGradient);\n  LepusCheckEach(\n      array->get(1),\n      std::make_tuple(starlight::RadialGradientShapeType::kEllipse,\n                      starlight::RadialGradientSizeType::kLength,\n                      starlight::BackgroundPositionType::kCenter,\n                      -1.f * static_cast<uint32_t>(\n                                 starlight::BackgroundPositionType::kCenter),\n                      starlight::BackgroundPositionType::kTop, -32,\n                      CSSValuePattern::PX, 10, CSSValuePattern::PX, 5),\n      std::vector<uint32_t>{0xffff0000, 0x0}, std::vector<float>{});\n}\n\nTEST(BackgroundImageHandler, parse_radial_gradient_with_circle) {\n  std::string radial_gradient =\n      \"radial-gradient(circle 10px at top, red, transparent)\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{radial_gradient.c_str(),\n                         static_cast<uint32_t>(radial_gradient.size()),\n                         configs};\n  CSSValue value = parser.ParseBackgroundImage();\n\n  ASSERT_TRUE(value.IsArray());\n  auto array = value.GetArray();\n  LepusCheckFunc(array->get(0),\n                 starlight::BackgroundImageType::kRadialGradient);\n  LepusCheckEach(\n      array->get(1),\n      std::make_tuple(starlight::RadialGradientShapeType::kCircle,\n                      starlight::RadialGradientSizeType::kLength,\n                      starlight::BackgroundPositionType::kCenter,\n                      -1.f * static_cast<uint32_t>(\n                                 starlight::BackgroundPositionType::kCenter),\n                      starlight::BackgroundPositionType::kTop, -32,\n                      CSSValuePattern::PX, 10, CSSValuePattern::PX, 10),\n      std::vector<uint32_t>{0xffff0000, 0x0}, std::vector<float>{});\n}\n\nTEST(BackgroundImageHandler, parse_radial_gradient_with_shape_size) {\n  std::string radial_gradients[] = {\n      \"radial-gradient(farthest-corner at center, red, transparent)\",\n      \"radial-gradient(farthest-side at center, red, transparent)\",\n      \"radial-gradient(closest-corner at center, red, transparent)\",\n      \"radial-gradient(closest-side at center, red, transparent)\",\n  };\n\n  for (int i = 0; i < std::size(radial_gradients); i++) {\n    const auto& g = radial_gradients[i];\n    CSSParserConfigs configs;\n    CSSStringParser parser{g.c_str(), static_cast<uint32_t>(g.size()), configs};\n    CSSValue value = parser.ParseBackgroundImage();\n\n    auto array = value.GetArray();\n    LepusCheckFunc(array->get(0),\n                   starlight::BackgroundImageType::kRadialGradient);\n    LepusCheckEach(\n        array->get(1),\n        std::make_tuple(starlight::RadialGradientShapeType::kEllipse,\n                        static_cast<starlight::RadialGradientSizeType>(i),\n                        starlight::BackgroundPositionType::kCenter,\n                        -1.f * static_cast<uint32_t>(\n                                   starlight::BackgroundPositionType::kCenter),\n                        starlight::BackgroundPositionType::kCenter,\n                        -1.f * static_cast<uint32_t>(\n                                   starlight::BackgroundPositionType::kCenter)),\n        std::vector<uint32_t>{0xffff0000, 0x0}, std::vector<float>{});\n  }\n}\n\nTEST(BackgroundImageHandler, parse_radial_gradient_with_sizes) {\n  {\n    // The shape should be circle\n    std::string g1 = \"radial-gradient(10px at top, red, transparent)\";\n    std::string g2 = \"radial-gradient(circle 10px at top, red, transparent)\";\n    CSSParserConfigs configs;\n    CSSStringParser p1{g1.c_str(), static_cast<uint32_t>(g2.size()), configs};\n    CSSValue v1 = p1.ParseBackgroundImage();\n\n    CSSStringParser p2{g2.c_str(), static_cast<uint32_t>(g2.size()), configs};\n    CSSValue v2 = p2.ParseBackgroundImage();\n    EXPECT_EQ(v1, v2);\n  }\n\n  {\n    // The shape should be ellipse\n    std::string g3 = \"radial-gradient(10px 5px at top, red, transparent)\";\n    std::string g4 =\n        \"radial-gradient(ellipse 10px 5px at top, red, transparent)\";\n    CSSParserConfigs configs;\n    CSSStringParser p3{g3.c_str(), static_cast<uint32_t>(g3.size()), configs};\n    CSSValue v3 = p3.ParseBackgroundImage();\n\n    CSSStringParser p4{g4.c_str(), static_cast<uint32_t>(g4.size()), configs};\n    CSSValue v4 = p4.ParseBackgroundImage();\n    EXPECT_EQ(v3, v4);\n  }\n}\n\nTEST(BackgroundImageHandler, parse_radial_gradient_invalid) {\n  std::string radial_gradients[] = {\n      \"radial-gradient(ellipse farthest-corner 10px at top, red, transparent)\",\n      \"radial-gradient(circle 10px 5px at top, red, transparent)\",\n      \"radial-gradient(ellipse 10px at top, red, transparent)\",\n      \"radial-gradient(ellipse ellipse, red, transparent)\",\n      \"radial-gradient(farthest-corner at center)\",\n  };\n\n  for (const auto& g : radial_gradients) {\n    SCOPED_TRACE(g);\n    CSSParserConfigs configs;\n    CSSStringParser parser{g.c_str(), static_cast<uint32_t>(g.size()), configs};\n    CSSValue value = parser.ParseBackgroundImage();\n\n    ASSERT_TRUE(value.IsEmpty());\n  }\n}\n\nTEST(BackgroundImageHandler, parse_linear_gradient_option_stop) {\n  std::string bg_image =\n      \"linear-gradient(to left, red, blue, green 0.9, blue, black 150%);\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{bg_image.c_str(),\n                         static_cast<uint32_t>(bg_image.size()), configs};\n\n  CSSValue result = parser.ParseBackgroundImage();\n\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  auto url_array = result.GetArray();\n  EXPECT_EQ(url_array->size(), static_cast<size_t>(2));\n  EXPECT_TRUE(url_array->get(0).IsUInt32());\n  EXPECT_EQ(\n      url_array->get(0).UInt32(),\n      static_cast<uint32_t>(starlight::BackgroundImageType::kLinearGradient));\n  EXPECT_TRUE(url_array->get(1).IsArray());\n\n  uint32_t red = 0xffff0000;\n  uint32_t blue = 0xff0000ff;\n  uint32_t green = 0xff008000;\n  uint32_t mix = CSSStringParser::LerpColor(green, blue, 0.9f, 1.2, 1.f);\n\n  ExpectLinearGradientEQ(url_array->get(1), 270.f, {red, blue, green, mix},\n                         {0.f, 45.f, 90.f, 100.f}, 3);\n}\n\nTEST(BackgroundImageHandler, parse_linear_gradient_option_start) {\n  std::string bg_image = \"linear-gradient(to left, red -10%, blue 10%, green);\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{bg_image.c_str(),\n                         static_cast<uint32_t>(bg_image.size()), configs};\n\n  CSSValue result = parser.ParseBackgroundImage();\n\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  auto url_array = result.GetArray();\n  EXPECT_EQ(url_array->size(), static_cast<size_t>(2));\n  EXPECT_TRUE(url_array->get(0).IsUInt32());\n  EXPECT_EQ(\n      url_array->get(0).UInt32(),\n      static_cast<uint32_t>(starlight::BackgroundImageType::kLinearGradient));\n  EXPECT_TRUE(url_array->get(1).IsArray());\n\n  uint32_t red = 0xffff0000;\n  uint32_t blue = 0xff0000ff;\n  uint32_t green = 0xff008000;\n  uint32_t mix = CSSStringParser::LerpColor(red, blue, -0.1f, 0.1f, 0.f);\n\n  ExpectLinearGradientEQ(url_array->get(1), 270.f, {mix, blue, green},\n                         {0.f, 10.f, 100.f}, 3);\n}\n\nTEST(BackgroundImageHandler, linear_gradient_angle_valid_value) {\n  typedef struct LinearGradientAngleKV {\n    const char* key;\n    double value;\n  } LinearGradientAngleKV;\n  const LinearGradientAngleKV values[] = {\n      {.key = \"linear-gradient(green, green)\", .value = 180.f},\n      {.key = \"linear-gradient(90DeG, green, green)\", .value = 90.f},\n      {.key = \"linear-gradient(100gRaD, green, green)\", .value = 90.f},\n      {.key = \"linear-gradient(1.57rAd, green, green)\", .value = 89.954373f},\n      {.key = \"linear-gradient(0.25tUrN, green, green)\", .value = 90.f},\n      {nullptr}};\n  CSSParserConfigs configs;\n  for (const LinearGradientAngleKV* it = values; it->key; it++) {\n    const char* key = it->key;\n    CSSStringParser parser{key, static_cast<uint32_t>(strlen(key)), configs};\n    CSSValue result = parser.ParseBackgroundImage();\n    EXPECT_TRUE(result.IsArray());\n    EXPECT_EQ(\n        result.GetArray()->get(0).Number(),\n        static_cast<uint32_t>(starlight::BackgroundImageType::kLinearGradient));\n    lepus::CArray& gradient_array = *(result.GetArray()->get(1).Array().get());\n    EXPECT_FLOAT_EQ(gradient_array.get(0).Number(), it->value);\n  }\n}\n\nTEST(BackgroundImageHandler, linear_gradient_value_invalid) {\n  const char* invalid_values[] = {\"linear-gradient(90degree, red, red)\",\n                                  \"linear-gradient(100gradian, red, red)\",\n                                  \"linear-gradient(1.57radian, red, red)\",\n                                  \"linear-gradient(0.25turns, red, red)\",\n                                  nullptr};\n  CSSParserConfigs configs;\n  for (const char** it = invalid_values; *it; it++) {\n    CSSStringParser parser{*it, static_cast<uint32_t>(strlen(*it)), configs};\n    CSSValue gradient = parser.ParseBackgroundImage();\n    EXPECT_TRUE(gradient.IsEmpty());\n  }\n}\n\nTEST(BackgroundImageHandler, parse_conic_gradient) {\n  std::string bg_image = \"conic-gradient(red, blue);\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{bg_image.c_str(),\n                         static_cast<uint32_t>(bg_image.size()), configs};\n\n  CSSValue result = parser.ParseBackgroundImage();\n\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  auto array = result.GetArray();\n  EXPECT_EQ(array->size(), static_cast<size_t>(2));\n\n  LepusCheckFunc(array->get(0), starlight::BackgroundImageType::kConicGradient);\n\n  LepusCheckEach(array->get(1), 0.f,\n                 std::make_tuple(50.f, CSSValuePattern::PERCENT, 50.f,\n                                 CSSValuePattern::PERCENT),\n                 std::vector<uint32_t>{0xffff0000, 0xff0000ff},\n                 std::vector<float>{});\n}\n\nTEST(BackgroundImageHandler, parse_conic_gradient_angle) {\n  std::string bg_image = \"conic-gradient(from 30deg, red, blue);\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{bg_image.c_str(),\n                         static_cast<uint32_t>(bg_image.size()), configs};\n\n  CSSValue result = parser.ParseBackgroundImage();\n\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  auto array = result.GetArray();\n  EXPECT_EQ(array->size(), static_cast<size_t>(2));\n\n  LepusCheckFunc(array->get(0), starlight::BackgroundImageType::kConicGradient);\n\n  LepusCheckEach(array->get(1), 30.f,\n                 std::make_tuple(50.f, CSSValuePattern::PERCENT, 50.f,\n                                 CSSValuePattern::PERCENT),\n                 std::vector<uint32_t>{0xffff0000, 0xff0000ff},\n                 std::vector<float>{});\n}\n\nTEST(BackgroundImageHandler, parse_conic_gradient_angle_at) {\n  std::string bg_image =\n      \"conic-gradient(from 50deg at top right, red 0%, blue 90%);\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{bg_image.c_str(),\n                         static_cast<uint32_t>(bg_image.size()), configs};\n\n  CSSValue result = parser.ParseBackgroundImage();\n\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  auto array = result.GetArray();\n  EXPECT_EQ(array->size(), static_cast<size_t>(2));\n\n  LepusCheckFunc(array->get(0), starlight::BackgroundImageType::kConicGradient);\n\n  LepusCheckEach(array->get(1), 50.f,\n                 std::make_tuple(100.f, CSSValuePattern::PERCENT, 0.f,\n                                 CSSValuePattern::PERCENT),\n                 std::vector<uint32_t>{0xffff0000, 0xff0000ff},\n                 std::vector<float>{0.f, 90.f});\n}\n\nTEST(BackgroundImageHandler, conic_gradient_value_invalid) {\n  const char* invalid_values[] = {\"conic-gradient(90deg, red, red)\",\n                                  \"conic-gradient(from 90deg at red, red)\",\n                                  \"conic-gradient(at, red, red)\",\n                                  \"conic-gradient(at 10px red, red)\", nullptr};\n  CSSParserConfigs configs;\n  for (const char** it = invalid_values; *it; it++) {\n    CSSStringParser parser{*it, static_cast<uint32_t>(strlen(*it)), configs};\n    CSSValue gradient = parser.ParseBackgroundImage();\n    EXPECT_TRUE(gradient.IsEmpty());\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_position_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/background_position_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundPositionHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto ret = parser.ParseBackgroundPosition();\n  if (ret.IsEmpty()) {\n    return false;\n  }\n  output.insert_or_assign(key, std::move(ret));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDBackgroundPosition] = &Handle;\n  array[kPropertyIDMaskPosition] = &Handle;\n}\n\n}  // namespace BackgroundPositionHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_position_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_BACKGROUND_POSITION_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_BACKGROUND_POSITION_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundPositionHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace BackgroundPositionHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_BACKGROUND_POSITION_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/background_position_handler_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/background_position_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(BackgroundPositionHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDBackgroundPosition;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  // input invalid.\n  auto impl = lepus::Value(111);\n  bool ret;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"center\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  auto background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  auto pos = background_position.GetArray().strongify();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(1));\n  auto arr = pos->get(0).Array().strongify();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  for (size_t i = 0; i < arr->size(); i += 2) {\n    EXPECT_EQ(\n        (uint32_t)arr->get(i).Number(),\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter));\n    EXPECT_EQ((float)arr->get(i + 1).Number(),\n              -1.f * static_cast<uint32_t>(\n                         starlight::BackgroundPositionType::kCenter));\n  }\n\n  output.clear();\n  impl = lepus::Value(\"left\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  pos = background_position.GetArray();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(1));\n  arr = pos->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((uint32_t)arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BackgroundPositionType::kLeft));\n  EXPECT_EQ(\n      (float)arr->get(1).Number(),\n      -1.f * static_cast<uint32_t>(starlight::BackgroundPositionType::kLeft));\n  EXPECT_EQ((uint32_t)arr->get(2).Number(),\n            static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter));\n  EXPECT_EQ(\n      (float)arr->get(3).Number(),\n      -1.f * static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter));\n\n  output.clear();\n  impl = lepus::Value(\"bottom\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  pos = background_position.GetArray();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(1));\n  arr = pos->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((uint32_t)arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter));\n  EXPECT_EQ(\n      (float)arr->get(1).Number(),\n      -1.f * static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter));\n  EXPECT_EQ((uint32_t)arr->get(2).Number(),\n            static_cast<uint32_t>(starlight::BackgroundPositionType::kBottom));\n  EXPECT_EQ(\n      (float)arr->get(3).Number(),\n      -1.f * static_cast<uint32_t>(starlight::BackgroundPositionType::kBottom));\n\n  output.clear();\n  impl = lepus::Value(\"bottom, right bottom\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  pos = background_position.GetArray();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(2));\n  {\n    arr = pos->get(0).Array();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n    EXPECT_EQ(\n        (uint32_t)arr->get(0).Number(),\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter));\n    EXPECT_EQ((float)arr->get(1).Number(),\n              -1.f * static_cast<uint32_t>(\n                         starlight::BackgroundPositionType::kCenter));\n    EXPECT_EQ(\n        (uint32_t)arr->get(2).Number(),\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kBottom));\n    EXPECT_EQ((float)arr->get(3).Number(),\n              -1.f * static_cast<uint32_t>(\n                         starlight::BackgroundPositionType::kBottom));\n  }\n  {\n    arr = pos->get(1).Array();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n    EXPECT_EQ((uint32_t)arr->get(0).Number(),\n              static_cast<uint32_t>(starlight::BackgroundPositionType::kRight));\n    EXPECT_EQ((float)arr->get(1).Number(),\n              -1.f * static_cast<uint32_t>(\n                         starlight::BackgroundPositionType::kRight));\n    EXPECT_EQ(\n        (uint32_t)arr->get(2).Number(),\n        static_cast<uint32_t>(starlight::BackgroundPositionType::kBottom));\n    EXPECT_EQ((float)arr->get(3).Number(),\n              -1.f * static_cast<uint32_t>(\n                         starlight::BackgroundPositionType::kBottom));\n  }\n\n  output.clear();\n  impl = lepus::Value(\"right bottom\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  pos = background_position.GetArray();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(1));\n  arr = pos->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((uint32_t)arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BackgroundPositionType::kRight));\n  EXPECT_EQ(\n      (float)arr->get(1).Number(),\n      -1.f * static_cast<uint32_t>(starlight::BackgroundPositionType::kRight));\n  EXPECT_EQ((uint32_t)arr->get(2).Number(),\n            static_cast<uint32_t>(starlight::BackgroundPositionType::kBottom));\n  EXPECT_EQ(\n      (float)arr->get(3).Number(),\n      -1.f * static_cast<uint32_t>(starlight::BackgroundPositionType::kBottom));\n\n  output.clear();\n  impl = lepus::Value(\"50px 40px\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  pos = background_position.GetArray();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(1));\n  arr = pos->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::PX);\n  EXPECT_EQ((float)arr->get(1).Number(), 50);\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::PX);\n  EXPECT_EQ((float)arr->get(3).Number(), 40);\n\n  output.clear();\n  impl = lepus::Value(\"50px 40%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  pos = background_position.GetArray();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(1));\n  arr = pos->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::PX);\n  EXPECT_EQ((float)arr->get(1).Number(), 50);\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::PERCENT);\n  EXPECT_EQ((float)arr->get(3).Number(), 40);\n}\n\nTEST(BackgroundPositionHandler, Swap) {\n  std::vector<std::pair<std::string, std::string>> cases = {\n      {\"top left\", \"left top\"},           {\"top right\", \"right top\"},\n      {\"bottom right\", \"right bottom\"},   {\"bottom left\", \"left bottom\"},\n      {\"bottom center\", \"center bottom\"}, {\"top center\", \"center top\"},\n      {\"left center\", \"center left\"},     {\"right center\", \"center right\"},\n  };\n  CSSParserConfigs configs;\n  for (const auto& s : cases) {\n    CSSStringParser first{s.first.c_str(),\n                          static_cast<uint32_t>(s.first.size()), configs};\n    CSSStringParser second{s.second.c_str(),\n                           static_cast<uint32_t>(s.second.size()), configs};\n    EXPECT_FALSE(first.ParseBackgroundPosition().IsEmpty());\n    EXPECT_FALSE(second.ParseBackgroundPosition().IsEmpty());\n    EXPECT_EQ(first.ParseBackgroundPosition(),\n              second.ParseBackgroundPosition());\n  }\n}\n\nTEST(BackgroundPositionHandler, Calc) {\n  auto id = CSSPropertyID::kPropertyIDBackgroundPosition;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  // input invalid.\n  output.clear();\n  auto impl = lepus::Value(\"calc(100% - 20px)  40px\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  auto background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  auto pos = background_position.GetArray().strongify();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(1));\n  auto arr = pos->get(0).Array().strongify();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::CALC);\n  EXPECT_EQ(arr->get(1).StringView(), \"calc(100% - 20px)\");\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::PX);\n  EXPECT_EQ(arr->get(3).Number(), 40);\n\n  output.clear();\n  impl = lepus::Value(\"calc(20px + 50%)  calc(30px * 2)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  pos = background_position.GetArray();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(1));\n  arr = pos->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::CALC);\n  EXPECT_EQ(arr->get(1).StringView(), \"calc(20px + 50%)\");\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::CALC);\n  EXPECT_EQ(arr->get(3).StringView(), \"calc(30px * 2)\");\n\n  output.clear();\n  impl = lepus::Value(\"calc(20px + (20px * 2)) calc(50% + (10px * 2 * 50%))\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_position = output[id];\n  EXPECT_TRUE(background_position.IsArray());\n  pos = background_position.GetArray();\n  EXPECT_EQ(pos->size(), static_cast<size_t>(1));\n  arr = pos->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::CALC);\n  EXPECT_EQ(arr->get(1).StringView(), \"calc(20px + (20px * 2))\");\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::CALC);\n  EXPECT_EQ(arr->get(3).StringView(), \"calc(50% + (10px * 2 * 50%))\");\n}\n\nTEST(BackgroundPositionHandler, Invalid) {\n  CSSParserConfigs configs;\n  std::vector<std::string> cases = {\n      \"left hello\", \"top 10%\",  \"top top\",\n      \"right left\", \"10% left\", \"50px right\",\n  };\n  for (const auto& s : cases) {\n    CSSStringParser parser{s.c_str(), static_cast<uint32_t>(s.size()), configs};\n    CSSValue pos = parser.ParseBackgroundPosition();\n    EXPECT_TRUE(pos.IsEmpty());\n    EXPECT_FALSE(!pos.IsEmpty());\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_repeat_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/background_repeat_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundRepeatHandler {\n\nusing starlight::BackgroundRepeatType;\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto repeat = parser.ParseBackgroundRepeat();\n  if (repeat.IsEmpty()) {\n    return false;\n  }\n  output.insert_or_assign(key, std::move(repeat));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDBackgroundRepeat] = &Handle;\n  array[kPropertyIDMaskRepeat] = &Handle;\n}\n}  // namespace BackgroundRepeatHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_repeat_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_BACKGROUND_REPEAT_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_BACKGROUND_REPEAT_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundRepeatHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace BackgroundRepeatHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_BACKGROUND_REPEAT_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/background_repeat_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/background_repeat_handler.h\"\n\n#include <string>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(BackgroundRepeatHandler, One) {\n  auto id = CSSPropertyID::kPropertyIDBackgroundRepeat;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  std::vector<\n      std::pair<std::string, std::pair<starlight::BackgroundRepeatType,\n                                       starlight::BackgroundRepeatType>>>\n      cases = {{\"repeat\",\n                {starlight::BackgroundRepeatType::kRepeat,\n                 starlight::BackgroundRepeatType::kRepeat}},\n               {\"no-repeat\",\n                {starlight::BackgroundRepeatType::kNoRepeat,\n                 starlight::BackgroundRepeatType::kNoRepeat}},\n               {\"repeat-x\",\n                {starlight::BackgroundRepeatType::kRepeat,\n                 starlight::BackgroundRepeatType::kNoRepeat}},\n               {\"repeat-y\",\n                {starlight::BackgroundRepeatType::kNoRepeat,\n                 starlight::BackgroundRepeatType::kRepeat}},\n               {\"round\",\n                {starlight::BackgroundRepeatType::kRound,\n                 starlight::BackgroundRepeatType::kRound}},\n               {\"space\",\n                {starlight::BackgroundRepeatType::kSpace,\n                 starlight::BackgroundRepeatType::kSpace}}};\n\n  for (const auto& item : cases) {\n    output.clear();\n    auto impl = lepus::Value(item.first);\n    EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output.find(id) != output.end());\n    EXPECT_EQ(output.size(), static_cast<size_t>(1));\n    auto background_size = output[id];\n    EXPECT_TRUE(background_size.IsArray());\n    auto size = background_size.GetArray();\n    EXPECT_EQ(size->size(), static_cast<size_t>(1));\n    auto arr = size->get(0).Array();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n    EXPECT_EQ(arr->get(0).Number(), static_cast<int>(item.second.first));\n    EXPECT_EQ(arr->get(1).Number(), static_cast<int>(item.second.second));\n  }\n}\n\nTEST(BackgroundRepeatHandler, Two) {\n  auto id = CSSPropertyID::kPropertyIDBackgroundRepeat;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  std::vector<\n      std::pair<std::string, std::pair<starlight::BackgroundRepeatType,\n                                       starlight::BackgroundRepeatType>>>\n      cases = {{\"repeat no-repeat\",\n                {starlight::BackgroundRepeatType::kRepeat,\n                 starlight::BackgroundRepeatType::kNoRepeat}},\n               {\"no-repeat repeat\",\n                {starlight::BackgroundRepeatType::kNoRepeat,\n                 starlight::BackgroundRepeatType::kRepeat}},\n               {\"round round\",\n                {starlight::BackgroundRepeatType::kRound,\n                 starlight::BackgroundRepeatType::kRound}},\n               {\"space space\",\n                {starlight::BackgroundRepeatType::kSpace,\n                 starlight::BackgroundRepeatType::kSpace}}};\n\n  for (const auto& item : cases) {\n    output.clear();\n    auto impl = lepus::Value(item.first);\n    EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output.find(id) != output.end());\n    EXPECT_EQ(output.size(), static_cast<size_t>(1));\n    auto background_size = output[id];\n    EXPECT_TRUE(background_size.IsArray());\n    auto size = background_size.GetArray();\n    EXPECT_EQ(size->size(), static_cast<size_t>(1));\n    auto arr = size->get(0).Array();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n    EXPECT_EQ(arr->get(0).Number(), static_cast<int>(item.second.first));\n    EXPECT_EQ(arr->get(1).Number(), static_cast<int>(item.second.second));\n  }\n}\n\nstatic void CheckLepusArrayNumberValue(fml::RefPtr<lepus::CArray> left,\n                                       fml::RefPtr<lepus::CArray> right) {\n  EXPECT_EQ(left->size(), right->size());\n  for (int i = 0; i < left->size(); i++) {\n    if (left->get(i).IsArray()) {\n      EXPECT_TRUE(right->get(i).IsArray());\n      CheckLepusArrayNumberValue(left->get(i).Array(), right->get(i).Array());\n    } else {\n      EXPECT_EQ(left->get(i).Number(), right->get(i).Number());\n    }\n  }\n}\n\nTEST(BackgroundRepeatHandler, Valid) {\n  const char* values[] = {\n      \"repeat-x\",\n      \"repeat-y\",\n      \"repeat\",\n      \"space\",\n      \"round\",\n      \"no-repeat\",\n      \"repeat space\",\n      \"round no-repeat\",\n      \"repeat repeat, repeat\",\n      \"repeat-x, repeat-y, repeat\",\n      nullptr,\n  };\n  constexpr int test_value_num = sizeof(values) / sizeof(char*) - 1;\n#define make_a_repeat(x, y)                                               \\\n  ((Repeat){static_cast<uint32_t>(starlight::BackgroundRepeatType::k##x), \\\n            static_cast<uint32_t>(starlight::BackgroundRepeatType::k##y)})\n\n  typedef struct Repeat {\n    uint32_t x;\n    uint32_t y;\n  } Repeat;\n\n  auto build_expected_repeat_array = [](Repeat* expected,\n                                        int num) -> fml::RefPtr<lepus::CArray> {\n    fml::RefPtr<lepus::CArray> res = lepus::CArray::Create();\n    for (int i = 0; i < num; i++) {\n      auto array = lepus::CArray::Create();\n      array->push_back(lepus::Value((expected + i)->x));\n      array->push_back(lepus::Value((expected + i)->y));\n      res->push_back(lepus::Value(array));\n    }\n    return res;\n  };\n  fml::RefPtr<lepus::CArray> expected[test_value_num];\n  // repeat-x\n  Repeat repeat = make_a_repeat(Repeat, NoRepeat);\n  expected[0] = build_expected_repeat_array(&repeat, 1);\n  // repeat-y\n  repeat = make_a_repeat(NoRepeat, Repeat);\n  // repeat\n  expected[1] = build_expected_repeat_array(&repeat, 1);\n  repeat = make_a_repeat(Repeat, Repeat);\n  expected[2] = build_expected_repeat_array(&repeat, 1);\n  // space\n  repeat = make_a_repeat(Space, Space);\n  expected[3] = build_expected_repeat_array(&repeat, 1);\n  // round\n  repeat = make_a_repeat(Round, Round);\n  expected[4] = build_expected_repeat_array(&repeat, 1);\n  //\"no-repeat\",\n  repeat = make_a_repeat(NoRepeat, NoRepeat);\n  expected[5] = build_expected_repeat_array(&repeat, 1);\n  //      \"repeat space\",\n  repeat = make_a_repeat(Repeat, Space);\n  expected[6] = build_expected_repeat_array(&repeat, 1);\n  //      \"round no-repeat\",\n  repeat = make_a_repeat(Round, NoRepeat);\n  expected[7] = build_expected_repeat_array(&repeat, 1);\n  //      \"repeat repeat, repeat\",\n  Repeat repeats[] = {make_a_repeat(Repeat, Repeat),\n                      make_a_repeat(Repeat, Repeat)};\n\n  expected[8] = build_expected_repeat_array(repeats, 2);\n  //      \"repeat-x, repeat-y, repeat\",\n  Repeat repeats_1[] = {make_a_repeat(Repeat, NoRepeat),\n                        make_a_repeat(NoRepeat, Repeat),\n                        make_a_repeat(Repeat, Repeat)};\n  expected[9] = build_expected_repeat_array(repeats_1, 3);\n#undef make_a_repeat\n\n  // test loop\n  CSSParserConfigs configs;\n  int i = 0;\n  for (const char** it = values + i; *it; it = ++i + values) {\n    CSSStringParser parser = {*it, static_cast<uint32_t>(strlen(*it)), configs};\n    CSSValue repeat_array = parser.ParseBackgroundRepeat();\n    EXPECT_TRUE(repeat_array.IsArray());\n    CheckLepusArrayNumberValue(repeat_array.GetArray(), expected[i]);\n  }\n}\n\nTEST(BackgroundRepeatHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDBackgroundRepeat;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto cases = {\"repeat-y repeat-x\", \"repeat-x no-repeat\"};\n\n  for (const auto& item : cases) {\n    output.clear();\n    auto impl = lepus::Value(item);\n    EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n  }\n}\n\nTEST(BackgroundRepeatHandler, Invalid2) {\n  const char* invalid_values[] = {\"repeat-y round\", nullptr};\n  CSSParserConfigs configs;\n  for (const char** it = invalid_values; *it; it++) {\n    CSSStringParser parser{*it, static_cast<uint32_t>(strlen(*it)), configs};\n    CSSValue repeat = parser.ParseBackgroundRepeat();\n    EXPECT_TRUE(repeat.IsEmpty());\n  }\n\n  {  // Invalid value, but in Lynx it can be parsed with first two values.\n    const char* invalid_value = \"repeat space round\";\n    CSSStringParser parser{\n        invalid_value, static_cast<uint32_t>(strlen(invalid_value)), configs};\n    CSSValue repeat = parser.ParseBackgroundRepeat();\n    EXPECT_TRUE(repeat.IsEmpty());\n  }\n\n  // invalid value should be [repeat, repeat] in lynx\n  {\n    const char* invalid_value = \"repeat repeat-x\";\n    CSSStringParser parser{\n        invalid_value, static_cast<uint32_t>(strlen(invalid_value)), configs};\n    CSSValue repeat = parser.ParseBackgroundRepeat();\n    EXPECT_TRUE(repeat.IsEmpty());\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_shorthand_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/background_shorthand_handler.h\"\n\n#include <string>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundShorthandHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  parser.SetIsLegacyParser(configs.enable_legacy_parser);\n  auto ret = parser.ParseBackgroundOrMask(false);\n  if (ret.IsEmpty()) {\n    return false;\n  }\n  const auto& background = ret.GetArray();\n  output.emplace_or_assign(kPropertyIDBackgroundColor, background->get(0),\n                           CSSValuePattern::NUMBER);\n  output.emplace_or_assign(kPropertyIDBackgroundImage,\n                           background->get(1).Array());\n  // CSS parser and background compatible with old version\n  if (background->size() == 7) {\n    output.emplace_or_assign(kPropertyIDBackgroundPosition,\n                             background->get(2).Array());\n    output.emplace_or_assign(kPropertyIDBackgroundSize,\n                             background->get(3).Array());\n    output.emplace_or_assign(kPropertyIDBackgroundRepeat,\n                             background->get(4).Array());\n    output.emplace_or_assign(kPropertyIDBackgroundOrigin,\n                             background->get(5).Array());\n    output.emplace_or_assign(kPropertyIDBackgroundClip,\n                             background->get(6).Array());\n  }\n  return true;\n}\nHANDLER_REGISTER_IMPL() { array[kPropertyIDBackground] = &Handle; }\n}  // namespace BackgroundShorthandHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_shorthand_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_BACKGROUND_SHORTHAND_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_BACKGROUND_SHORTHAND_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundShorthandHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace BackgroundShorthandHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_BACKGROUND_SHORTHAND_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/background_shorthand_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include <utility>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/css_color.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\ntemplate <typename T, typename Enable = void>\nstruct LepusCheck {};\n\ntemplate <>\nstruct LepusCheck<uint32_t> {\n  static void Check(lepus::Value const& value, uint32_t t) {\n    ASSERT_TRUE(value.IsUInt32());\n    EXPECT_EQ(value.UInt32(), t);\n  }\n};\n\ntemplate <>\nstruct LepusCheck<int32_t> {\n  static void Check(lepus::Value const& value, int32_t t) {\n    ASSERT_TRUE(value.IsNumber());\n    EXPECT_EQ(static_cast<int32_t>(value.Number()), t);\n  }\n};\n\ntemplate <>\nstruct LepusCheck<float> {\n  static void Check(lepus::Value const& value, float t) {\n    ASSERT_TRUE(value.IsNumber());\n    EXPECT_NEAR(value.Number(), t, 0.01);\n  }\n};\n\ntemplate <>\nstruct LepusCheck<std::string> {\n  static void Check(lepus::Value const& value, std::string const& t) {\n    ASSERT_TRUE(value.IsString());\n    EXPECT_EQ(value.StdString(), t);\n  }\n};\n\ntemplate <>\nstruct LepusCheck<const char*> {\n  static void Check(lepus::Value const& value, const char* t) {\n    ASSERT_TRUE(value.IsString());\n    EXPECT_EQ(value.StdString(), std::string(t));\n  }\n};\n\ntemplate <typename T>\nstruct LepusCheck<T, typename std::enable_if_t<std::is_enum<T>::value>> {\n  static void Check(lepus::Value const& value, T t) {\n    LepusCheck<uint32_t>::Check(value, static_cast<uint32_t>(t));\n  }\n};\n\ntemplate <typename T>\nstruct LepusCheck<std::vector<T>> {\n  static void Check(lepus::Value const& value, std::vector<T> const& t) {\n    ASSERT_TRUE(value.IsArray());\n    auto array = value.Array();\n    for (size_t i = 0; i < t.size(); i++) {\n      LepusCheck<T>::Check(array->get(i), t[i]);\n    }\n  }\n};\n\ntemplate <typename T, typename F, size_t... I>\nvoid TupleForEach(T&& t, F f, std::integer_sequence<size_t, I...>) {\n  auto l = {(f(I, std::get<I>(t)), 0)...};\n  // prevent unused warnning.\n  LOGI(&l);\n}\n\ntemplate <typename T>\nvoid LepusCheckFunc(lepus::Value const& value, T const& t) {\n  LepusCheck<T>::Check(value, t);\n}\n\ntemplate <typename... Args>\nstruct LepusCheck<std::tuple<Args...>> {\n  using tuple_type = typename std::tuple<Args...>;\n  static void Check(lepus::Value const& value, tuple_type const& t) {\n    ASSERT_TRUE(value.IsArray());\n    auto array = value.Array();\n    ASSERT_EQ(array->size(), std::tuple_size<tuple_type>::value);\n\n    TupleForEach(\n        t, [&array](size_t i, auto f) { LepusCheckFunc(array->get(i), f); },\n        std::make_integer_sequence<size_t, sizeof...(Args)>());\n  }\n};\n\ntemplate <typename... Args>\nvoid LepusCheckEach(lepus::Value const& value, Args&&... args) {\n  std::tuple<Args...> tuple{args...};\n  LepusCheck<std::tuple<Args...>>::Check(value, tuple);\n}\n\nstruct ImageOrGradient {\n  uint32_t type;\n  std::string url;\n  float angle;\n  std::vector<uint32_t> color;\n  std::vector<float> stop;\n\n  explicit ImageOrGradient(const std::string& str)\n      : type(static_cast<uint32_t>(starlight::BackgroundImageType::kUrl)),\n        url(str),\n        angle(0.f) {}\n\n  ImageOrGradient(float angle, std::vector<uint32_t> const& c,\n                  std::vector<float> const& s)\n      : type(static_cast<uint32_t>(\n            starlight::BackgroundImageType::kLinearGradient)),\n        color(c),\n        stop(s) {}\n\n  void CheckValue(lepus::CArray* array, size_t index) const {\n    ASSERT_TRUE(array->size() > index);\n\n    auto value = array->get(index);\n    ASSERT_TRUE(value.IsUInt32());\n    switch (value.UInt32()) {\n      case static_cast<uint32_t>(starlight::BackgroundImageType::kUrl):\n        ASSERT_TRUE(array->size() > index + 1);\n        ASSERT_TRUE(array->get(index + 1).IsString());\n        ASSERT_EQ(url, array->get(index + 1).StdString());\n        break;\n      case static_cast<uint32_t>(\n          starlight::BackgroundImageType::kLinearGradient):\n        ASSERT_TRUE(array->size() > index + 1);\n        ASSERT_TRUE(array->get(index + 1).IsArray());\n        auto ga = array->get(index + 1).Array();\n        CheckLinearGradient(ga.get());\n        break;\n    }\n  }\n  void CheckLinearGradient(lepus::CArray* array) const {\n    if (stop.empty()) {\n      LepusCheckEach(lepus::Value(angle), color);\n    } else {\n      LepusCheckEach(lepus::Value(angle), color, stop);\n    }\n  }\n};\n\ntemplate <>\nstruct LepusCheck<std::vector<ImageOrGradient>> {\n  static void Check(lepus::Value const& value,\n                    std::vector<ImageOrGradient> const& t) {\n    ASSERT_TRUE(value.IsArray());\n    auto array = value.Array();\n    size_t image_index = 0;\n    for (size_t i = 0; i < array->size(); i++) {\n      ASSERT_TRUE(array->get(i).IsUInt32());\n      ASSERT_TRUE(t.size() > image_index);\n      switch (array->get(i).UInt32()) {\n        case static_cast<uint32_t>(starlight::BackgroundImageType::kNone):\n          // TODO parse this\n          continue;\n        default:\n          t[image_index++].CheckValue(array.get(), i++);\n          break;\n      }\n    }\n  }\n};\n\nusing BGPosition = std::array<std::pair<uint32_t, float>, 2>;\nusing BGSize = BGPosition;\nusing BGRepeat = std::array<uint32_t, 2>;\n\ntemplate <>\nstruct LepusCheck<std::array<std::pair<uint32_t, float>, 2>> {\n  using value_type = std::array<std::pair<uint32_t, float>, 2>;\n  static void Check(lepus::Value const& value, value_type const& t) {\n    LepusCheckEach(value, t[0].first, t[0].second, t[1].first, t[1].second);\n  }\n};\n\ntemplate <>\nstruct LepusCheck<std::array<uint32_t, 2>> {\n  using value_type = std::array<uint32_t, 2>;\n\n  static void Check(lepus::Value const& value, value_type const& t) {\n    LepusCheckEach(value, t[0], t[1]);\n  }\n};\n\ntemplate <class T>\nstd::pair<uint32_t, float> MakeLengthT(T t, float value) {\n  return std::make_pair(static_cast<uint32_t>(t), value);\n}\n\ntemplate <class T>\nBGRepeat MakeRepeat(T t1, T t2) {\n  return BGRepeat{static_cast<uint32_t>(t1), static_cast<uint32_t>(t2)};\n}\n\ntemplate <class T>\nstd::vector<uint32_t> MakeBoxList(T t) {\n  std::vector<uint32_t> result;\n  result.emplace_back(static_cast<uint32_t>(t));\n  return result;\n}\n\ntemplate <class T, class... Args>\nstd::vector<uint32_t> MakeBoxList(T t, Args&&... args) {\n  std::vector<uint32_t> result;\n  result.emplace_back(static_cast<uint32_t>(t));\n  auto sub_list = MakeBoxList(args...);\n  result.insert(result.end(), sub_list.begin(), sub_list.end());\n  return result;\n}\n\nstatic void check_bg_array(const lepus::Value& value, uint32_t color,\n                           std::vector<ImageOrGradient> const& image,\n                           std::vector<BGPosition> const& position,\n                           std::vector<BGSize> const& size,\n                           std::vector<BGRepeat> const& repeat,\n                           std::vector<uint32_t> const& origin,\n                           std::vector<uint32_t> const& clip) {\n  LepusCheckEach(value, color, image, position, size, repeat, origin, clip);\n}\n\nTEST(BackgroundHandler, Normal) {\n  std::string bg_str = \"url('https://yyy/i/bg_flower.gif')\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{bg_str.c_str(), static_cast<uint32_t>(bg_str.size()),\n                         configs};\n  // Open full feature\n  parser.SetIsLegacyParser(false);\n\n  CSSValue result = parser.ParseBackgroundOrMask(false);\n\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n\n  check_bg_array(\n      result.GetValue(), 0, {ImageOrGradient{\"https://yyy/i/bg_flower.gif\"}},\n      // position\n      {{MakeLengthT(tasm::CSSValuePattern::PERCENT, 0.f),\n        MakeLengthT(tasm::CSSValuePattern::PERCENT, 0.f)}},\n      // size\n      {{MakeLengthT(\n            tasm::CSSValuePattern::NUMBER,\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto)),\n        MakeLengthT(\n            tasm::CSSValuePattern::NUMBER,\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto))}},\n      // repeat\n      {\n          MakeRepeat(starlight::BackgroundRepeatType::kRepeat,\n                     starlight::BackgroundRepeatType::kRepeat),\n      },\n      MakeBoxList(starlight::BackgroundOriginType::kPaddingBox),\n      MakeBoxList(starlight::BackgroundOriginType::kPaddingBox));\n\n  std::string bg_str_complex =\n      \"url('https://yyy/i/bg_flower.gif') left 5% / 15% 60% \"\n      \"repeat-x \"\n      \",url('https://xxxx/ee/lynx-home/static/img/\"\n      \"zh-logo-color.7c750dd6.png') red\";\n\n  CSSStringParser parser2{bg_str_complex.c_str(),\n                          static_cast<uint32_t>(bg_str_complex.size()),\n                          configs};\n\n  parser2.SetIsLegacyParser(false);\n  CSSValue result2 = parser2.ParseBackgroundOrMask(false);\n\n  EXPECT_EQ(result2.GetPattern(), CSSValuePattern::ARRAY);\n  check_bg_array(\n      result2.GetValue(), 0xffff0000,\n      {ImageOrGradient{\"https://yyy/i/bg_flower.gif\"},\n       ImageOrGradient{\"https://xxxx/ee/lynx-home/static/img/\"\n                       \"zh-logo-color.7c750dd6.png\"}},\n      // position\n      {{MakeLengthT(\n            starlight::BackgroundPositionType::kLeft,\n            -1.f * static_cast<int>(starlight::BackgroundPositionType::kLeft)),\n        MakeLengthT(tasm::CSSValuePattern::PERCENT, 5.f)},\n       {MakeLengthT(tasm::CSSValuePattern::PERCENT, 0.f),\n        MakeLengthT(tasm::CSSValuePattern::PERCENT, 0.f)}},\n      // size\n      {{MakeLengthT(tasm::CSSValuePattern::PERCENT, 15.f),\n        MakeLengthT(tasm::CSSValuePattern::PERCENT, 60.f)},\n       {MakeLengthT(\n            tasm::CSSValuePattern::NUMBER,\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto)),\n        MakeLengthT(\n            tasm::CSSValuePattern::NUMBER,\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto))}},\n      // repeat\n      {MakeRepeat(starlight::BackgroundRepeatType::kRepeat,\n                  starlight::BackgroundRepeatType::kNoRepeat),\n       MakeRepeat(starlight::BackgroundRepeatType::kRepeat,\n                  starlight::BackgroundRepeatType::kRepeat)},\n      MakeBoxList(starlight::BackgroundOriginType::kPaddingBox,\n                  starlight::BackgroundOriginType::kPaddingBox),\n      MakeBoxList(starlight::BackgroundClipType::kPaddingBox,\n                  starlight::BackgroundClipType::kPaddingBox));\n\n  std::string color_any_where =\n      \"content-box center / contain no-repeat \"\n      \"url(\\\"../../media/examples/logo.svg\\\")\"\n      \", content-box #eee border-box 35% url(\\\"../../media/examples/1.png\\\")\";\n\n  CSSStringParser parser3{color_any_where.c_str(),\n                          static_cast<uint32_t>(color_any_where.size()),\n                          configs};\n\n  parser3.SetIsLegacyParser(false);\n  CSSValue result3 = parser3.ParseBackgroundOrMask(false);\n\n  EXPECT_EQ(result3.GetPattern(), CSSValuePattern::ARRAY);\n  check_bg_array(\n      result3.GetValue(), 0xffeeeeee,\n      {ImageOrGradient{\"../../media/examples/logo.svg\"},\n       ImageOrGradient{\"../../media/examples/1.png\"}},\n      // position\n      {{MakeLengthT(starlight::BackgroundPositionType::kCenter,\n                    -1.f * static_cast<int>(\n                               starlight::BackgroundPositionType::kCenter)),\n        MakeLengthT(starlight::BackgroundPositionType::kCenter,\n                    -1.f * static_cast<int>(\n                               starlight::BackgroundPositionType::kCenter))},\n       {MakeLengthT(tasm::CSSValuePattern::PERCENT, 35.f),\n        MakeLengthT(starlight::BackgroundPositionType::kCenter,\n                    -1.f * static_cast<int>(\n                               starlight::BackgroundPositionType::kCenter))}},\n      // size\n      {{MakeLengthT(\n            tasm::CSSValuePattern::NUMBER,\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kContain)),\n        MakeLengthT(\n            tasm::CSSValuePattern::NUMBER,\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kContain))},\n       {MakeLengthT(\n            tasm::CSSValuePattern::NUMBER,\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto)),\n        MakeLengthT(\n            tasm::CSSValuePattern::NUMBER,\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto))}},\n      // repeat\n      {MakeRepeat(starlight::BackgroundRepeatType::kNoRepeat,\n                  starlight::BackgroundRepeatType::kNoRepeat),\n       MakeRepeat(starlight::BackgroundRepeatType::kRepeat,\n                  starlight::BackgroundRepeatType::kRepeat)},\n      MakeBoxList(starlight::BackgroundOriginType::kContentBox,\n                  starlight::BackgroundOriginType::kContentBox),\n      MakeBoxList(starlight::BackgroundClipType::kContentBox,\n                  starlight::BackgroundClipType::kBorderBox));\n}\n\nTEST(BackgroundHandler, Invalid) {\n  std::vector<std::string> cases = {\n      \"url(https://zzz/obj/eden-cn/ulojhpenuln/\"\n      \"brand-shop-bg.png) 100% 100% / cover top no-repeat';\",\n      \"hello\",\n      \"hello red\",\n      \"red red\",\n      \"url(https://lf3-static) left hello\",\n      \"red url('https://xxxx/ee/lynx-home/static/img/\"\n      \"zh-logo-color.7c750dd6.png'), radial-gradient(#FF0000, #00FF00)\",\n      \"url(https://lf3-static) url(https://lf3-static)\",\n      \"url(https://lf3-static) 100% 100% 100%\",\n      \"url(https://lf3-static) 100% 100% top\",\n      \"url(https://lf3-static) repeat-x repeat-y\",\n      \"url(https://lf3-static) repeat-x repeat\",\n  };\n  CSSParserConfigs configs;\n  CSSPropertyID id = CSSPropertyID::kPropertyIDBackground;\n  for (const auto& s : cases) {\n    StyleMap output;\n    auto impl = lepus::Value(s);\n    EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n    EXPECT_TRUE(output.empty());\n  }\n}\n\nTEST(BackgroundHandler, None) {\n  std::string none = \"NONE\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{none.c_str(), static_cast<uint32_t>(none.size()),\n                         configs};\n\n  CSSValue result = parser.ParseBackgroundOrMask(false);\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  EXPECT_EQ(result.GetArray()->size(), 2);\n  EXPECT_EQ(result.GetArray()->get(0).Number(), 0);  // background-color\n  const auto& images = result.GetArray()->get(1);\n  EXPECT_TRUE(images.IsArray());  // background-image is an array\n  EXPECT_EQ(images.Array()->get(0).Number(),\n            static_cast<int>(starlight::BackgroundImageType::kNone));  // none\n}\n\nTEST(BackgroundHandler, Color) {\n  std::string red = \"red\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{red.c_str(), static_cast<uint32_t>(red.size()),\n                         configs};\n\n  CSSValue result = parser.ParseBackgroundOrMask(false);\n  EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n  EXPECT_EQ(result.GetArray()->size(), 2);\n  EXPECT_EQ(result.GetArray()->get(0).Number(),\n            0xffff0000);  // background-color\n  const auto& images = result.GetArray()->get(1);\n  EXPECT_TRUE(images.IsArray());\n  EXPECT_EQ(images.Array()->size(), 0);  // background-image is empty\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_size_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/background_size_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/tasm/config.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundSizeHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  parser.SetIsLegacyParser(configs.enable_legacy_parser);\n  auto size = parser.ParseBackgroundSize();\n  if (size.IsEmpty()) {\n    return false;\n  }\n  output.insert_or_assign(key, std::move(size));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDBackgroundSize] = &Handle;\n  array[kPropertyIDMaskSize] = &Handle;\n}\n\n}  // namespace BackgroundSizeHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/background_size_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_BACKGROUND_SIZE_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_BACKGROUND_SIZE_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BackgroundSizeHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace BackgroundSizeHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_BACKGROUND_SIZE_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/background_size_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/background_size_handler.h\"\n\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(BackgroundSizeHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDBackgroundSize;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  // input invalid.\n  auto impl = lepus::Value(111);\n  bool ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  std::vector<std::string> cases = {\"auto\", \"cover\", \"contain\"};\n\n  for (int i = 0; i < cases.size(); ++i) {\n    output.clear();\n    impl = lepus::Value(cases[i]);\n    EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output.find(id) != output.end());\n    EXPECT_EQ(output.size(), static_cast<size_t>(1));\n    auto background_size = output[id];\n    EXPECT_TRUE(background_size.IsArray());\n    auto size = background_size.GetArray();\n    EXPECT_EQ(size->size(), static_cast<size_t>(1));\n    auto arr = size->get(0).Array();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n    for (size_t j = 0; j < arr->size(); j += 2) {\n      EXPECT_EQ((uint32_t)arr->get(j).Number(),\n                static_cast<uint32_t>(CSSValuePattern::NUMBER));\n      EXPECT_EQ(\n          (float)arr->get(j + 1).Number(),\n          -1.f * (static_cast<int>(starlight::BackgroundSizeType::kAuto) + i));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"50px\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  auto background_size = output[id];\n  EXPECT_TRUE(background_size.IsArray());\n  auto size = background_size.GetArray().strongify();\n  EXPECT_EQ(size->size(), static_cast<size_t>(1));\n  auto arr = size->get(0).Array().strongify();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::PX);\n  EXPECT_EQ((float)arr->get(1).Number(), 50);\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::NUMBER);\n  EXPECT_EQ((float)arr->get(3).Number(),\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto));\n\n  output.clear();\n  impl = lepus::Value(\"50px auto\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_size = output[id];\n  EXPECT_TRUE(background_size.IsArray());\n  size = background_size.GetArray();\n  EXPECT_EQ(size->size(), static_cast<size_t>(1));\n  arr = size->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::PX);\n  EXPECT_EQ((float)arr->get(1).Number(), 50);\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::NUMBER);\n  EXPECT_EQ((float)arr->get(3).Number(),\n            -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto));\n\n  output.clear();\n  impl = lepus::Value(\"50px 40px\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_size = output[id];\n  EXPECT_TRUE(background_size.IsArray());\n  size = background_size.GetArray();\n  EXPECT_EQ(size->size(), static_cast<size_t>(1));\n  arr = size->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::PX);\n  EXPECT_EQ((float)arr->get(1).Number(), 50);\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::PX);\n  EXPECT_EQ((float)arr->get(3).Number(), 40);\n\n  output.clear();\n  impl = lepus::Value(\"50px 40%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_size = output[id];\n  EXPECT_TRUE(background_size.IsArray());\n  size = background_size.GetArray();\n  EXPECT_EQ(size->size(), static_cast<size_t>(1));\n  arr = size->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::PX);\n  EXPECT_EQ((float)arr->get(1).Number(), 50);\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::PERCENT);\n  EXPECT_EQ((float)arr->get(3).Number(), 40);\n\n  output.clear();\n  impl = lepus::Value(\"50px, 30px 40%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  background_size = output[id];\n  EXPECT_TRUE(background_size.IsArray());\n  size = background_size.GetArray();\n  EXPECT_EQ(size->size(), static_cast<size_t>(2));\n  {\n    arr = size->get(0).Array();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n    EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::PX);\n    EXPECT_EQ((float)arr->get(1).Number(), 50);\n    EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::NUMBER);\n    EXPECT_EQ((float)arr->get(3).Number(),\n              -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto));\n  }\n  {\n    arr = size->get(1).Array();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n    EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::PX);\n    EXPECT_EQ((float)arr->get(1).Number(), 30);\n    EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::PERCENT);\n    EXPECT_EQ((float)arr->get(3).Number(), 40);\n  }\n}\n\nTEST(BackgroundSizeHandler, Legacy) {\n  auto id = CSSPropertyID::kPropertyIDBackgroundSize;\n  StyleMap output;\n  CSSParserConfigs configs;\n  configs.enable_legacy_parser = true;\n\n  output.clear();\n  auto impl = lepus::Value(\"auto\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  auto background_size = output[id];\n  EXPECT_TRUE(background_size.IsArray());\n  auto size = background_size.GetArray();\n  EXPECT_EQ(size->size(), static_cast<size_t>(1));\n  auto arr = size->get(0).Array();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((CSSValuePattern)arr->get(0).Number(), CSSValuePattern::PERCENT);\n  EXPECT_EQ((float)arr->get(1).Number(), 100);\n  EXPECT_EQ((CSSValuePattern)arr->get(2).Number(), CSSValuePattern::PERCENT);\n  EXPECT_EQ((float)arr->get(3).Number(), 100);\n}\n\nTEST(BackgroundSizeHandler, Invalid) {\n  {\n    CSSParserConfigs configs;\n    // This is an invalid value for background-size_array according to CSS\n    // syntax. But in Lynx the value can be parsed, and the first pair of values\n    // take effect.\n    const char* val = \"1px 2px 3px\";\n    CSSStringParser parser{val, static_cast<uint32_t>(strlen(val)), configs};\n    parser.SetIsLegacyParser(false);\n    CSSValue size_array = parser.ParseBackgroundSize();\n    EXPECT_TRUE(size_array.IsEmpty());\n  }\n\n  {\n    // Unknown keyword, no parse result.\n    CSSParserConfigs configs;\n    const char* val = \"wrap\";\n    CSSStringParser parser{val, static_cast<uint32_t>(strlen(val)), configs};\n    parser.SetIsLegacyParser(false);\n    CSSValue size_array = parser.ParseBackgroundSize();\n    EXPECT_TRUE(size_array.IsEmpty());\n  }\n}\n\nstatic void CheckLepusArrayNumberValue(fml::RefPtr<lepus::CArray> left,\n                                       fml::RefPtr<lepus::CArray> right) {\n  EXPECT_EQ(left->size(), right->size());\n  for (int i = 0; i < left->size(); i++) {\n    if (left->get(i).IsArray()) {\n      EXPECT_TRUE(right->get(i).IsArray());\n      CheckLepusArrayNumberValue(left->get(i).Array(), right->get(i).Array());\n    } else {\n      EXPECT_EQ(left->get(i).Number(), right->get(i).Number());\n    }\n  }\n}\n\nTEST(BackgroundSizeHandler, Valid) {\n  CSSParserConfigs configs;\n  const char* values[] = {\"1px\",     \"1px auto\",  \"2% 3%\",\n                          \"auto\",    \"auto auto\", \"auto 4%\",\n                          \"contain\", \"cover\",     \"1px 2px, 3px 4px\",\n                          nullptr};\n\n  constexpr int num_val = sizeof(values) / sizeof(char*) - 1;\n\n  // check value equals value in target value array\n  auto test_background_size_valid =\n      [configs](const char* values[num_val],\n                fml::RefPtr<lepus::CArray> expected_size[num_val]) {\n        // For each testing values\n        for (int i = 0; i < num_val; i++) {\n          // Get parsed size value\n          CSSStringParser parser{\n              values[i], static_cast<uint32_t>(strlen(values[i])), configs};\n          parser.SetIsLegacyParser(false);\n          CSSValue size = parser.ParseBackgroundSize();\n          // parsed result should be an array of <size-array>. And <size-array>\n          // contains 4 value,( unit x, value x, unit y, value y)\n          EXPECT_TRUE(size.IsArray());\n          EXPECT_NE(size.GetArray()->size(), 0);\n          fml::RefPtr<lepus::CArray> size_array = size.GetArray();\n          EXPECT_EQ(size_array->size(), expected_size[i]->size());\n          CheckLepusArrayNumberValue(size_array, expected_size[i]);\n        }\n      };\n\n#pragma region BUILD_EXPECTED_SIZE\n  fml::RefPtr<lepus::CArray> expected_size[9];\n\n  typedef struct Size {\n    uint32_t uw;\n    double w;\n    uint32_t uh;\n    double h;\n  } Size;\n\n#define make_a_size(uw, w, uh, h)                     \\\n  (Size) {                                            \\\n    static_cast<uint32_t>(CSSValuePattern::uw), w,    \\\n        static_cast<uint32_t>(CSSValuePattern::uh), h \\\n  }\n  // help function to create lepus array from value def.\n  auto create_size_array = [](int len,\n                              Size* sizes) -> fml::RefPtr<lepus::CArray> {\n    auto array = lepus::CArray::Create();\n\n    for (int i = 0; i < len; i++) {\n      const auto [ux, vx, uy, vy] = *(sizes + i);\n      auto value = lepus::CArray::Create();\n      value->push_back(lepus::Value(ux));\n      value->push_back(lepus::Value(vx));\n      value->push_back(lepus::Value(uy));\n      value->push_back(lepus::Value(vy));\n      array->push_back(lepus::Value(value));\n    }\n    return array;\n  };\n\n  // 1px\n  Size size = make_a_size(\n      PX, 1.0, NUMBER,\n      -1.0 * static_cast<int>(starlight::BackgroundSizeType::kAuto));\n\n  expected_size[0] = create_size_array(1, &size);\n\n  // 1px auto\n  size = make_a_size(\n      PX, 1, NUMBER,\n      -1.0 * static_cast<int>(starlight::BackgroundSizeType::kAuto));\n  expected_size[1] = create_size_array(1, &size);\n\n  // 2% 3%\n  size = make_a_size(PERCENT, 2, PERCENT, 3);\n  expected_size[2] = create_size_array(1, &size);\n\n  // auto\n  size = make_a_size(\n      NUMBER, -1.0 * static_cast<int>(starlight::BackgroundSizeType::kAuto),\n      NUMBER, -1.0 * static_cast<int>(starlight::BackgroundSizeType::kAuto));\n  expected_size[3] = create_size_array(1, &size);\n\n  // auto auto\n  size = make_a_size(\n      NUMBER, -1.0 * static_cast<int>(starlight::BackgroundSizeType::kAuto),\n      NUMBER, -1.0 * static_cast<int>(starlight::BackgroundSizeType::kAuto));\n  expected_size[4] = create_size_array(1, &size);\n\n  // auto 4%\n  size = make_a_size(\n      NUMBER, -1.0 * static_cast<int>(starlight::BackgroundSizeType::kAuto),\n      PERCENT, 4);\n  expected_size[5] = create_size_array(1, &size);\n\n  // contain\n  size = make_a_size(\n      NUMBER, -1.0 * static_cast<int>(starlight::BackgroundSizeType::kContain),\n      NUMBER, -1.0 * static_cast<int>(starlight::BackgroundSizeType::kContain));\n  expected_size[6] = create_size_array(1, &size);\n\n  // cover\n  size = make_a_size(\n      NUMBER, -1.0 * static_cast<int>(starlight::BackgroundSizeType::kCover),\n      NUMBER, -1.0 * static_cast<int>(starlight::BackgroundSizeType::kCover));\n  expected_size[7] = create_size_array(1, &size);\n\n  // 1px 2px, 3px 4px\n  Size sizes[] = {make_a_size(PX, 1, PX, 2), make_a_size(PX, 3, PX, 4)};\n  expected_size[8] = create_size_array(2, sizes);\n#pragma endregion BUILD_EXPECTED_SIZE\n  test_background_size_valid(values, expected_size);\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/bool_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/bool_handler.h\"\n\n#include <utility>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BoolHandler {\n\nHANDLER_IMPL() {\n  CSSValue css_value;\n  if (input.IsBool()) {\n    css_value.SetBoolean(input.Bool());\n  } else if (input.IsString()) {\n    CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n    css_value = parser.ParseBool();\n  } else {\n    return false;\n  }\n  if (!css_value.IsEmpty()) {\n    output.insert_or_assign(key, std::move(css_value));\n    return true;\n  }\n  return false;\n}\n\n}  // namespace BoolHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/bool_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/bool_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(BoolHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDImplicitAnimation;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value(10);\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"true\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsBoolean());\n  EXPECT_TRUE(output[id].GetBool());\n\n  output.clear();\n  impl = lepus::Value(\"True\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsBoolean());\n  EXPECT_TRUE(output[id].GetBool());\n\n  output.clear();\n  impl = lepus::Value(\"false\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsBoolean());\n  EXPECT_FALSE(output[id].GetBool());\n\n  output.clear();\n  impl = lepus::Value(\"False\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsBoolean());\n  EXPECT_FALSE(output[id].GetBool());\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/border_handler.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/border_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/parser/four_sides_shorthand_handler.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BorderHandler {\n\nstatic inline void AddProperty(CSSPropertyID property, CSSValue&& value,\n                               StyleMap& output) {\n  if (value.IsEmpty()) {\n    return;\n  }\n  output.insert_or_assign(property, std::move(value));\n}\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue result_width;\n  CSSValue result_style;\n  CSSValue result_color;\n  bool ret = parser.ParseBorder(result_width, result_style, result_color);\n  if (!ret) {\n    return false;\n  }\n  if (key == kPropertyIDBorder) {\n    FourSidesShorthandHandler::AddProperty(kPropertyIDBorderWidth,\n                                           std::move(result_width), output);\n    FourSidesShorthandHandler::AddProperty(kPropertyIDBorderColor,\n                                           std::move(result_color), output);\n    FourSidesShorthandHandler::AddProperty(kPropertyIDBorderStyle,\n                                           std::move(result_style), output);\n  } else {\n    CSSPropertyID width_id;\n    CSSPropertyID style_id;\n    CSSPropertyID color_id;\n    switch (key) {\n      case kPropertyIDBorderTop:\n        width_id = kPropertyIDBorderTopWidth;\n        style_id = kPropertyIDBorderTopStyle;\n        color_id = kPropertyIDBorderTopColor;\n        break;\n      case kPropertyIDBorderRight:\n        width_id = kPropertyIDBorderRightWidth;\n        style_id = kPropertyIDBorderRightStyle;\n        color_id = kPropertyIDBorderRightColor;\n        break;\n      case kPropertyIDBorderBottom:\n        width_id = kPropertyIDBorderBottomWidth;\n        style_id = kPropertyIDBorderBottomStyle;\n        color_id = kPropertyIDBorderBottomColor;\n        break;\n      case kPropertyIDBorderLeft:\n        width_id = kPropertyIDBorderLeftWidth;\n        style_id = kPropertyIDBorderLeftStyle;\n        color_id = kPropertyIDBorderLeftColor;\n        break;\n      case kPropertyIDOutline:\n        width_id = kPropertyIDOutlineWidth;\n        style_id = kPropertyIDOutlineStyle;\n        color_id = kPropertyIDOutlineColor;\n        break;\n      default:\n        UnitHandler::CSSUnreachable(configs.enable_css_strict_mode,\n                                    \"BorderCombineInterceptor id unreachable!\");\n        return false;\n    }\n\n    AddProperty(width_id, std::move(result_width), output);\n    AddProperty(color_id, std::move(result_color), output);\n    AddProperty(style_id, std::move(result_style), output);\n  }\n  return ret;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDBorder] = &Handle;\n  array[kPropertyIDBorderTop] = &Handle;\n  array[kPropertyIDBorderRight] = &Handle;\n  array[kPropertyIDBorderBottom] = &Handle;\n  array[kPropertyIDBorderLeft] = &Handle;\n  array[kPropertyIDOutline] = &Handle;\n}\n\n}  // namespace BorderHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/border_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_BORDER_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_BORDER_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BorderHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace BorderHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_BORDER_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/border_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/border_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nusing namespace starlight;\nnamespace tasm {\nnamespace test {\n\nTEST(BorderHandler, Border) {\n  auto id = CSSPropertyID::kPropertyIDBorder;\n  auto style_id = CSSPropertyID::kPropertyIDBorderTopStyle;\n  auto color_id = CSSPropertyID::kPropertyIDBorderTopColor;\n  auto width_id = CSSPropertyID::kPropertyIDBorderTopWidth;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  // input invalid.\n  auto impl = lepus::Value(111);\n  bool ret;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"10px double red\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(12));\n  EXPECT_EQ(output[width_id].GetNumber(), 10);\n  EXPECT_EQ(output[width_id].GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(output[style_id].GetNumber(),\n            static_cast<double>(BorderStyleType::kDouble));\n  EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n  EXPECT_EQ(output[color_id].GetNumber(), 0xffff0000);\n  EXPECT_EQ(output[color_id].GetPattern(), CSSValuePattern::NUMBER);\n\n  output.clear();\n  impl = lepus::Value(\"10px double\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(8));\n  EXPECT_EQ(output[width_id].GetNumber(), 10);\n  EXPECT_EQ(output[width_id].GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(output[style_id].GetNumber(),\n            static_cast<double>(BorderStyleType::kDouble));\n  EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n\n  output.clear();\n  impl = lepus::Value(\"double\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_EQ(output[style_id].GetNumber(),\n            static_cast<double>(BorderStyleType::kDouble));\n  EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n  configs.enable_new_border_handler = true;\n  // The new handler will fill the default value\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(12));\n  EXPECT_EQ(output[width_id].GetNumber(), 0);\n  EXPECT_EQ(output[width_id].GetPattern(), CSSValuePattern::NUMBER);\n  EXPECT_EQ(output[style_id].GetNumber(),\n            static_cast<double>(BorderStyleType::kDouble));\n  EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n  EXPECT_EQ(output[color_id].GetNumber(), 0xff000000);\n  EXPECT_EQ(output[color_id].GetPattern(), CSSValuePattern::NUMBER);\n\n  output.clear();\n  impl = lepus::Value(\"double double\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n}\n\nTEST(BorderHandler, BorderLine) {\n  StyleMap output;\n  CSSParserConfigs configs;\n  CSSPropertyID lines[] = {\n      CSSPropertyID::kPropertyIDBorderRight,\n      CSSPropertyID::kPropertyIDBorderLeft,\n      CSSPropertyID::kPropertyIDBorderTop,\n      CSSPropertyID::kPropertyIDBorderBottom,\n      CSSPropertyID::kPropertyIDOutline,\n  };\n  CSSPropertyID width_id;\n  CSSPropertyID style_id;\n  CSSPropertyID color_id;\n  for (const auto& id : lines) {\n    switch (id) {\n      case kPropertyIDBorderTop:\n        width_id = kPropertyIDBorderTopWidth;\n        style_id = kPropertyIDBorderTopStyle;\n        color_id = kPropertyIDBorderTopColor;\n        break;\n      case kPropertyIDBorderRight:\n        width_id = kPropertyIDBorderRightWidth;\n        style_id = kPropertyIDBorderRightStyle;\n        color_id = kPropertyIDBorderRightColor;\n        break;\n      case kPropertyIDBorderBottom:\n        width_id = kPropertyIDBorderBottomWidth;\n        style_id = kPropertyIDBorderBottomStyle;\n        color_id = kPropertyIDBorderBottomColor;\n        break;\n      case kPropertyIDBorderLeft:\n        width_id = kPropertyIDBorderLeftWidth;\n        style_id = kPropertyIDBorderLeftStyle;\n        color_id = kPropertyIDBorderLeftColor;\n        break;\n      case kPropertyIDOutline:\n        width_id = kPropertyIDOutlineWidth;\n        style_id = kPropertyIDOutlineStyle;\n        color_id = kPropertyIDOutlineColor;\n        break;\n      default:\n        break;\n    }\n    output.clear();\n    auto impl = lepus::Value(\"10px ridge black\");\n    EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n    EXPECT_TRUE(output.find(id) == output.end());\n    EXPECT_EQ(output.size(), static_cast<size_t>(3));\n    EXPECT_EQ(output[width_id].GetNumber(), 10);\n    EXPECT_EQ(output[width_id].GetPattern(), CSSValuePattern::PX);\n    EXPECT_EQ(output[style_id].GetNumber(),\n              static_cast<double>(BorderStyleType::kRidge));\n    EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n    EXPECT_EQ(output[color_id].GetNumber(), 0xff000000);\n    EXPECT_EQ(output[color_id].GetPattern(), CSSValuePattern::NUMBER);\n  }\n}\n\nTEST(BorderHandler, Outline) {\n  auto id = CSSPropertyID::kPropertyIDOutline;\n  auto style_id = CSSPropertyID::kPropertyIDOutlineStyle;\n  auto color_id = CSSPropertyID::kPropertyIDOutlineColor;\n  auto width_id = CSSPropertyID::kPropertyIDOutlineWidth;\n  StyleMap output;\n  CSSParserConfigs configs;\n  configs.enable_length_unit_check = false;\n\n  // input invalid.\n  auto impl = lepus::Value(111);\n  bool ret;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"10px double red\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(3));\n  EXPECT_EQ(output[width_id].GetNumber(), 10);\n  EXPECT_EQ(output[width_id].GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(output[style_id].GetNumber(),\n            static_cast<double>(BorderStyleType::kDouble));\n  EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n  EXPECT_EQ(output[color_id].GetNumber(), 0xffff0000);\n  EXPECT_EQ(output[color_id].GetPattern(), CSSValuePattern::NUMBER);\n\n  output.clear();\n  impl = lepus::Value(\"10px double\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(2));\n  EXPECT_EQ(output[width_id].GetNumber(), 10);\n  EXPECT_EQ(output[width_id].GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(output[style_id].GetNumber(),\n            static_cast<double>(BorderStyleType::kDouble));\n  EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n\n  output.clear();\n  impl = lepus::Value(\"thick double red\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(3));\n\n  EXPECT_EQ(output[width_id].GetNumber(), 5);\n  EXPECT_EQ(output[width_id].GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(output[style_id].GetNumber(),\n            static_cast<double>(BorderStyleType::kDouble));\n  EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n  EXPECT_EQ(output[color_id].GetNumber(), 0xffff0000);\n  EXPECT_EQ(output[color_id].GetPattern(), CSSValuePattern::NUMBER);\n\n  output.clear();\n  impl = lepus::Value(\"medium dashed #0000ff\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(3));\n\n  EXPECT_EQ(output[width_id].GetNumber(), 3);\n  EXPECT_EQ(output[width_id].GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(output[style_id].GetNumber(),\n            static_cast<double>(BorderStyleType::kDashed));\n  EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n  EXPECT_EQ(output[color_id].GetNumber(), 0xff0000ff);\n  EXPECT_EQ(output[color_id].GetPattern(), CSSValuePattern::NUMBER);\n\n  output.clear();\n  impl = lepus::Value(\"thin outset hsla(89,43%,51%,0.3)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(3));\n\n  EXPECT_EQ(output[width_id].GetNumber(), 1);\n  EXPECT_EQ(output[width_id].GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(output[style_id].GetNumber(),\n            static_cast<double>(BorderStyleType::kOutset));\n  EXPECT_EQ(output[style_id].GetPattern(), CSSValuePattern::ENUM);\n  EXPECT_EQ(output[color_id].GetNumber(), 0x4c84b84c);\n  EXPECT_EQ(output[color_id].GetPattern(), CSSValuePattern::NUMBER);\n}\n\nstatic bool ToBorderStyleType(const std::string& str,\n                              starlight::BorderStyleType& result) {\n  if (str == \"solid\") {\n    result = BorderStyleType::kSolid;\n  } else if (str == \"dashed\") {\n    result = BorderStyleType::kDashed;\n  } else if (str == \"dotted\") {\n    result = BorderStyleType::kDotted;\n  } else if (str == \"double\") {\n    result = BorderStyleType::kDouble;\n  } else if (str == \"groove\") {\n    result = BorderStyleType::kGroove;\n  } else if (str == \"ridge\") {\n    result = BorderStyleType::kRidge;\n  } else if (str == \"inset\") {\n    result = BorderStyleType::kInset;\n  } else if (str == \"outset\") {\n    result = BorderStyleType::kOutset;\n  } else if (str == \"hidden\") {\n    result = BorderStyleType::kHide;\n  } else if (str == \"none\") {\n    result = BorderStyleType::kNone;\n  } else {\n    return false;\n  }\n  return true;\n}\n\nTEST(BorderStyleHandler, BorderStyle) {\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto id = CSSPropertyID::kPropertyIDBorderTopStyle;\n  std::string styles[] = {\"none\",   \"hidden\", \"dotted\", \"dashed\", \"solid\",\n                          \"double\", \"groove\", \"ridge\",  \"inset\",  \"outset\"};\n\n  for (const auto& style : styles) {\n    output.clear();\n    auto impl = lepus::Value(style);\n    EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n    EXPECT_TRUE(output.find(id) != output.end());\n    BorderStyleType type;\n    ToBorderStyleType(style, type);\n    EXPECT_EQ(output[id].GetValue().Int32(), static_cast<int>(type));\n    EXPECT_EQ(output[id].GetPattern(), CSSValuePattern::ENUM);\n  }\n  output.clear();\n  auto impl = lepus::Value(\"invalid\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n}\n\nTEST(BorderWidthHandler, BorderWidth) {\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto id = CSSPropertyID::kPropertyIDBorderTopWidth;\n  output.clear();\n  auto impl = lepus::Value(\"thin\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output[id].GetNumber(), 1);\n  EXPECT_EQ(output[id].GetPattern(), CSSValuePattern::PX);\n\n  output.clear();\n  impl = lepus::Value(\"10px\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_EQ(output[id].GetNumber(), 10);\n  EXPECT_EQ(output[id].GetPattern(), CSSValuePattern::PX);\n\n  output.clear();\n  impl = lepus::Value(\"invalid\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/border_radius_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/border_radius_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BorderRadiusHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  switch (key) {\n    case kPropertyIDBorderRadius: {\n      CSSValue x_radii[4] = {CSSValue(), CSSValue(), CSSValue(), CSSValue()};\n      CSSValue y_radii[4] = {CSSValue(), CSSValue(), CSSValue(), CSSValue()};\n      if (!parser.ParseBorderRadius(x_radii, y_radii)) {\n        return false;\n      }\n      static const CSSPropertyID radius_key_array[] = {\n          kPropertyIDBorderTopLeftRadius, kPropertyIDBorderTopRightRadius,\n          kPropertyIDBorderBottomRightRadius,\n          kPropertyIDBorderBottomLeftRadius};\n      for (int i = 0; i < 4; i++) {\n        auto container = lepus::CArray::Create();\n        container->emplace_back(x_radii[i].GetValue());\n        container->emplace_back(static_cast<int>(x_radii[i].GetPattern()));\n        container->emplace_back(y_radii[i].GetValue());\n        container->emplace_back(static_cast<int>(y_radii[i].GetPattern()));\n        output.emplace_or_assign(radius_key_array[i], std::move(container));\n      }\n      output.erase(key);\n    } break;\n    case kPropertyIDBorderTopLeftRadius:\n    case kPropertyIDBorderTopRightRadius:\n    case kPropertyIDBorderBottomRightRadius:\n    case kPropertyIDBorderBottomLeftRadius:\n    case kPropertyIDBorderStartStartRadius:\n    case kPropertyIDBorderStartEndRadius:\n    case kPropertyIDBorderEndStartRadius:\n    case kPropertyIDBorderEndEndRadius: {\n      CSSValue value = parser.ParseSingleBorderRadius();\n      if (value.IsArray()) {\n        output.insert_or_assign(key, std::move(value));\n      }\n    } break;\n    default:\n      break;\n  }\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDBorderRadius] = &Handle;\n  array[kPropertyIDBorderTopLeftRadius] = &Handle;\n  array[kPropertyIDBorderTopRightRadius] = &Handle;\n  array[kPropertyIDBorderBottomLeftRadius] = &Handle;\n  array[kPropertyIDBorderBottomRightRadius] = &Handle;\n  array[kPropertyIDBorderStartStartRadius] = &Handle;\n  array[kPropertyIDBorderStartEndRadius] = &Handle;\n  array[kPropertyIDBorderEndStartRadius] = &Handle;\n  array[kPropertyIDBorderEndEndRadius] = &Handle;\n}\n\n}  // namespace BorderRadiusHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/border_radius_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_BORDER_RADIUS_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_BORDER_RADIUS_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BorderRadiusHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace BorderRadiusHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_BORDER_RADIUS_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/border_radius_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/border_radius_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nusing namespace starlight;\nnamespace tasm {\nnamespace test {\n\nstd::vector<CSSPropertyID> radius_array = {\n    CSSPropertyID::kPropertyIDBorderTopLeftRadius,\n    CSSPropertyID::kPropertyIDBorderTopRightRadius,\n    CSSPropertyID::kPropertyIDBorderBottomRightRadius,\n    CSSPropertyID::kPropertyIDBorderBottomLeftRadius,\n};\n\nTEST(BorderRadiusHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDBorderRadius;\n  StyleMap output;\n  CSSParserConfigs configs;\n  configs.enable_length_unit_check = false;\n\n  // input invalid.\n  auto impl = lepus::Value(111);\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"50%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n    for (int i = 0; i < 4;) {\n      EXPECT_EQ((int)arr->get(i).Number(), 50);\n      EXPECT_EQ((CSSValuePattern)arr->get(i + 1).Number(),\n                CSSValuePattern::PERCENT);\n      i += 2;\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"50px 10%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n\n    // TOP\n    if (id == 0) {\n      EXPECT_EQ((int)arr->get(0).Number(), 50);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 50);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::PX);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(),\n                CSSValuePattern::PERCENT);\n      EXPECT_EQ((int)arr->get(2).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(),\n                CSSValuePattern::PERCENT);\n    } else if (id == 2) {\n      // BOTTOM == TOP\n      EXPECT_EQ(arr->get(0), output[radius_array[0]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[0]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[0]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[0]].GetArray()->get(3));\n    } else {\n      // RIGHT == LEFT\n      EXPECT_EQ(arr->get(0), output[radius_array[1]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[1]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[1]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[1]].GetArray()->get(3));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"50px           10%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    // TOP\n    if (id == 0) {\n      EXPECT_EQ((int)arr->get(0).Number(), 50);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 50);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::PX);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(),\n                CSSValuePattern::PERCENT);\n      EXPECT_EQ((int)arr->get(2).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(),\n                CSSValuePattern::PERCENT);\n    } else if (id == 2) {\n      // BOTTOM == TOP\n      EXPECT_EQ(arr->get(0), output[radius_array[0]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[0]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[0]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[0]].GetArray()->get(3));\n    } else {\n      // RIGHT == LEFT\n      EXPECT_EQ(arr->get(0), output[radius_array[1]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[1]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[1]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[1]].GetArray()->get(3));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"50px/10%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    // TOP\n    if (id == 0) {\n      EXPECT_EQ((int)arr->get(0).Number(), 50);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(),\n                CSSValuePattern::PERCENT);\n    } else {\n      EXPECT_EQ(arr->get(0), output[radius_array[0]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[0]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[0]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[0]].GetArray()->get(3));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\" 50px   /  10%    \");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    // TOP\n    if (id == 0) {\n      EXPECT_EQ((int)arr->get(0).Number(), 50);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(),\n                CSSValuePattern::PERCENT);\n    } else {\n      EXPECT_EQ(arr->get(0), output[radius_array[1]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[1]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[1]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[1]].GetArray()->get(3));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"50px 10px/ 10%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    // TOP\n    if (id == 0) {\n      EXPECT_EQ((int)arr->get(0).Number(), 50);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(),\n                CSSValuePattern::PERCENT);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(),\n                CSSValuePattern::PERCENT);\n    } else if (id == 2) {\n      // BOTTOM == TOP\n      EXPECT_EQ(arr->get(0), output[radius_array[0]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[0]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[0]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[0]].GetArray()->get(3));\n    } else {\n      // RIGHT == LEFT\n      EXPECT_EQ(arr->get(0), output[radius_array[1]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[1]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[1]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[1]].GetArray()->get(3));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"50px 10px/ 10% 20%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    if (id == 0) {\n      // TOP\n      EXPECT_EQ((int)arr->get(0).Number(), 50);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(),\n                CSSValuePattern::PERCENT);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 20);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(),\n                CSSValuePattern::PERCENT);\n    } else if (id == 2) {\n      // BOTTOM == TOP\n      EXPECT_EQ(arr->get(0), output[radius_array[0]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[0]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[0]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[0]].GetArray()->get(3));\n    } else {\n      // RIGHT == LEFT\n      EXPECT_EQ(arr->get(0), output[radius_array[1]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[1]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[1]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[1]].GetArray()->get(3));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"12px 0 /12px 0\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    if (id == 0) {\n      // TOP\n      EXPECT_EQ((int)arr->get(0).Number(), 12);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 12);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::PX);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::NUMBER);\n      EXPECT_EQ((int)arr->get(2).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::NUMBER);\n    } else if (id == 2) {\n      // BOTTOM == TOP\n      EXPECT_EQ(arr->get(0), output[radius_array[0]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[0]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[0]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[0]].GetArray()->get(3));\n    } else {\n      // RIGHT == LEFT\n      EXPECT_EQ(arr->get(0), output[radius_array[1]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[1]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[1]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[1]].GetArray()->get(3));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"10px 0 /12px\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    if (id == 0) {\n      // TOP\n      EXPECT_EQ((int)arr->get(0).Number(), 10);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 12);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::PX);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::NUMBER);\n      EXPECT_EQ((int)arr->get(2).Number(), 12);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::PX);\n    } else if (id == 2) {\n      // BOTTOM == TOP\n      EXPECT_EQ(arr->get(0), output[radius_array[0]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[0]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[0]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[0]].GetArray()->get(3));\n    } else {\n      // RIGHT == LEFT\n      EXPECT_EQ(arr->get(0), output[radius_array[1]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[1]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[1]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[1]].GetArray()->get(3));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"0/0\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    if (id == 0) {\n      // TOP\n      EXPECT_EQ((int)arr->get(0).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::NUMBER);\n      EXPECT_EQ((int)arr->get(2).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::NUMBER);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::NUMBER);\n      EXPECT_EQ((int)arr->get(2).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::NUMBER);\n    } else if (id == 2) {\n      // BOTTOM == TOP\n      EXPECT_EQ(arr->get(0), output[radius_array[0]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[0]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[0]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[0]].GetArray()->get(3));\n    } else {\n      // RIGHT == LEFT\n      EXPECT_EQ(arr->get(0), output[radius_array[1]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[1]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[1]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[1]].GetArray()->get(3));\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"0 0/0 0\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    if (id == 0) {\n      // TOP\n      EXPECT_EQ((int)arr->get(0).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::NUMBER);\n      EXPECT_EQ((int)arr->get(2).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::NUMBER);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::NUMBER);\n      EXPECT_EQ((int)arr->get(2).Number(), 0);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::NUMBER);\n    } else if (id == 2) {\n      // BOTTOM == TOP\n      EXPECT_EQ(arr->get(0), output[radius_array[0]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[0]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[0]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[0]].GetArray()->get(3));\n    } else {\n      // RIGHT == LEFT\n      EXPECT_EQ(arr->get(0), output[radius_array[1]].GetArray()->get(0));\n      EXPECT_EQ(arr->get(1), output[radius_array[1]].GetArray()->get(1));\n      EXPECT_EQ(arr->get(2), output[radius_array[1]].GetArray()->get(2));\n      EXPECT_EQ(arr->get(3), output[radius_array[1]].GetArray()->get(3));\n    }\n  }\n}\n\nTEST(BorderRadiusHandler, Calc) {\n  auto id = CSSPropertyID::kPropertyIDBorderRadius;\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto impl = lepus::Value(\"calc(12px*3)  3px 6px / 5px calc(20px*2)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    if (id == 0) {\n      // TOP\n      EXPECT_EQ(arr->get(0).StringView(), \"calc(12px*3)\");\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::CALC);\n      EXPECT_EQ((int)arr->get(2).Number(), 5);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::PX);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 3);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ(arr->get(2).StringView(), \"calc(20px*2)\");\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::CALC);\n    } else if (id == 2) {\n      // BOTTOM\n      EXPECT_EQ((int)arr->get(0).Number(), 6);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ((int)arr->get(2).Number(), 5);\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::PX);\n    } else {\n      // LEFT\n      EXPECT_EQ((int)arr->get(0).Number(), 3);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ(arr->get(2).StringView(), \"calc(20px*2)\");\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::CALC);\n    }\n  }\n\n  output.clear();\n  impl = lepus::Value(\"calc(20px/5) 3px 6px / calc(2px + 3px) calc(2px +2px) \");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  for (size_t id = 0; id < radius_array.size(); id++) {\n    EXPECT_FALSE(output.find(radius_array[id]) == output.end());\n    EXPECT_TRUE(output[radius_array[id]].IsArray());\n    auto arr = output[radius_array[id]].GetArray();\n    if (id == 0) {\n      // TOP\n      EXPECT_EQ(arr->get(0).StringView(), \"calc(20px/5)\");\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::CALC);\n      EXPECT_EQ(arr->get(2).StringView(), \"calc(2px + 3px)\");\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::CALC);\n    } else if (id == 1) {\n      // RIGHT\n      EXPECT_EQ((int)arr->get(0).Number(), 3);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ(arr->get(2).StringView(), \"calc(2px +2px)\");\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::CALC);\n    } else if (id == 2) {\n      // BOTTOM\n      EXPECT_EQ((int)arr->get(0).Number(), 6);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ(arr->get(2).StringView(), \"calc(2px + 3px)\");\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::CALC);\n    } else {\n      // LEFT\n      EXPECT_EQ((int)arr->get(0).Number(), 3);\n      EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PX);\n      EXPECT_EQ(arr->get(2).StringView(), \"calc(2px +2px)\");\n      EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::CALC);\n    }\n  }\n}\n\nTEST(BorderRadiusHandler, Longhand) {\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto id = kPropertyIDBorderTopLeftRadius;\n  // With one value\n  auto impl = lepus::Value(\"50%\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray().strongify();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  for (int i = 0; i < 4;) {\n    EXPECT_EQ((int)arr->get(i).Number(), 50);\n    EXPECT_EQ((CSSValuePattern)arr->get(i + 1).Number(),\n              CSSValuePattern::PERCENT);\n    i += 2;\n  }\n  // With two values\n  output.clear();\n  id = kPropertyIDBorderTopLeftRadius;\n  impl = lepus::Value(\"50% 10px\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  // x\n  EXPECT_EQ((int)arr->get(0).Number(), 50);\n  EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PERCENT);\n  // y\n  EXPECT_EQ((int)arr->get(2).Number(), 10);\n  EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::PX);\n\n  output.clear();\n  id = kPropertyIDBorderTopRightRadius;\n  // In fact, this value is invalid, for compatibility\n  impl = lepus::Value(\"50%/10px\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((int)arr->get(0).Number(), 50);\n  EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PERCENT);\n  EXPECT_EQ((int)arr->get(2).Number(), 10);\n  EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::PX);\n\n  output.clear();\n  id = kPropertyIDBorderTopRightRadius;\n  impl = lepus::Value(\"50%/calc(10px+10px)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(4));\n  EXPECT_EQ((int)arr->get(0).Number(), 50);\n  EXPECT_EQ((CSSValuePattern)arr->get(1).Number(), CSSValuePattern::PERCENT);\n  EXPECT_EQ(arr->get(2).StringView(), \"calc(10px+10px)\");\n  EXPECT_EQ((CSSValuePattern)arr->get(3).Number(), CSSValuePattern::CALC);\n}\n\nTEST(BorderRadiusHandler, Invalid) {\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto id = kPropertyIDBorderRadius;\n  // this will be ignored.\n  auto impl = lepus::Value(\"hello\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n\n  output.clear();\n  impl = lepus::Value(\"100test/0\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n\n  output.clear();\n  impl = lepus::Value(\"100test 100/0\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n}\n\nTEST(BorderRadiusHandler, LengthUnitCheck) {\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto id = kPropertyIDBorderRadius;\n  output.clear();\n  auto impl = lepus::Value(\"0/0\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  impl = lepus::Value(\"0 /     0\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  impl = lepus::Value(\"100/0\");\n  configs.enable_length_unit_check = true;\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n  configs.enable_length_unit_check = false;\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/border_style_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/border_style_handler.h\"\n\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BorderStyleHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue ret;\n  parser.ParseBorderStyle(ret);\n  if (ret.IsEmpty()) {\n    CSS_HANDLER_FAIL_IF_NOT(\n        false, configs.enable_css_strict_mode, TYPE_UNSUPPORTED,\n        CSSProperty::GetPropertyNameCStr(key), parser.content())\n  }\n  output.insert_or_assign(key, std::move(ret));\n  return true;\n}\n\n}  // namespace BorderStyleHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/border_width_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/border_width_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace BorderWidthHandler {\n\nHANDLER_IMPL() {\n  if (input.IsString()) {\n    CSSValue result;\n    CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n    auto ret = parser.ParseBorderLineWidth(result);\n    if (ret && !result.IsEmpty()) {\n      output.insert_or_assign(key, std::move(result));\n    }\n    return ret;\n  } else if (input.IsNumber()) {\n    return LengthHandler::Handle(key, input, output, configs);\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(false, configs.enable_css_strict_mode, TYPE_MUST_BE,\n                            CSSProperty::GetPropertyNameCStr(key),\n                            STRING_OR_NUMBER_TYPE)\n  }\n}\n\n}  // namespace BorderWidthHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/clip_path_handler.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/clip_path_handler.h\"\n\n#include <string>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace ClipPathHandler {\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  lepus::Value result = parser.ParseShapePath();\n  CSS_HANDLER_FAIL_IF_NOT(result.IsArray(), configs.enable_css_strict_mode,\n                          \"clip path format error.\")\n  output.emplace_or_assign(key, result.Array());\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDClipPath] = &Handle;\n  array[kPropertyIDOffsetPath] = &Handle;\n}\n}  // namespace ClipPathHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/clip_path_handler.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_CLIP_PATH_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_CLIP_PATH_HANDLER_H_\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace ClipPathHandler {\nHANDLER_REGISTER_DECLARE();\n}  // namespace ClipPathHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_CLIP_PATH_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/clip_path_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/clip_path_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nTEST(ClipPathHandler, HandleCircle) {\n  CSSParserConfigs configs;\n  StyleMap out;\n  // valid circle\n  lepus::Value input = lepus::Value(R\"(circle(40px at 30px bottom))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  CSSValue circle = out[kPropertyIDClipPath];\n  auto arr = circle.GetArray().strongify();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kCircle));\n  EXPECT_EQ(arr->get(1).Number(), 40);\n  EXPECT_EQ(arr->get(2).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n  EXPECT_EQ(arr->get(3).Number(), 30);\n  EXPECT_EQ(arr->get(4).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n  EXPECT_EQ(arr->get(5).Number(), 100);\n  EXPECT_EQ(arr->get(6).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n\n  // shorthand circle\n  input = lepus::Value(R\"(circle(30%))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  circle = out[kPropertyIDClipPath];\n  arr = circle.GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kCircle));\n  EXPECT_EQ(arr->get(1).Number(), 30);\n  EXPECT_EQ(arr->get(2).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  EXPECT_EQ(arr->get(3).Number(), 50);\n  EXPECT_EQ(arr->get(4).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  EXPECT_EQ(arr->get(5).Number(), 50);\n  EXPECT_EQ(arr->get(6).Number(),\n            static_cast<uint32_t>(CSSValuePattern(CSSValuePattern::PERCENT)));\n\n  // invalid circle\n  input = lepus::Value(R\"(circle(ppp))\");\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n}\n\nTEST(ClipPathHandler, Handle) {\n  CSSParserConfigs configs;\n  StyleMap out;\n  // Input is not string\n  lepus::Value input = lepus::Value(30);\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n\n  // Input is string\n  // Test parse path\n  CSSValue value;\n  input = lepus::Value(R\"(path(\"M 0 0 L 100 100 L 30 30 Z\"))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  CSSValue path = out[kPropertyIDClipPath];\n  auto arr = path.GetArray().strongify();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kPath));\n  EXPECT_TRUE(strcmp(arr->get(1).CString(), \"M 0 0 L 100 100 L 30 30 Z\") == 0);\n\n  // invalid path\n  input = lepus::Value(R\"(path(100)\");\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n\n  // valid ellipse\n  input = lepus::Value(R\"(ellipse(20px 50% at bottom right))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  CSSValue ellipse = out[kPropertyIDClipPath];\n  arr = ellipse.GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kEllipse));\n  EXPECT_EQ(arr->get(1).Number(), 20);\n  EXPECT_EQ(arr->get(2).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n  EXPECT_EQ(arr->get(3).Number(), 50);\n  EXPECT_EQ(arr->get(4).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  EXPECT_EQ(arr->get(5).Number(), 100);\n  EXPECT_EQ(arr->get(6).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  EXPECT_EQ(arr->get(7).Number(), 100);\n  EXPECT_EQ(arr->get(8).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n\n  input = lepus::Value(R\"(ellipse(20px 50ppx at left top))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  ellipse = out[kPropertyIDClipPath];\n  arr = ellipse.GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kEllipse));\n  EXPECT_EQ(arr->get(1).Number(), 20);\n  EXPECT_EQ(arr->get(2).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n  EXPECT_EQ(arr->get(3).Number(), 50);\n  EXPECT_EQ(arr->get(4).Number(), static_cast<uint32_t>(CSSValuePattern::PPX));\n  EXPECT_EQ(arr->get(5).Number(), 0);\n  EXPECT_EQ(arr->get(6).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  EXPECT_EQ(arr->get(7).Number(), 0);\n  EXPECT_EQ(arr->get(8).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n\n  // invalid ellipse\n  input = lepus::Value(R\"(ellipse(20px at left center))\");\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n\n  // <percentage> position\n  input = lepus::Value(R\"(ellipse(20px 50ppx at 35% 20%))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  ellipse = out[kPropertyIDClipPath];\n  arr = ellipse.GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kEllipse));\n  EXPECT_EQ(arr->get(1).Number(), 20);\n  EXPECT_EQ(arr->get(2).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n  EXPECT_EQ(arr->get(3).Number(), 50);\n  EXPECT_EQ(arr->get(4).Number(), static_cast<uint32_t>(CSSValuePattern::PPX));\n  EXPECT_EQ(arr->get(5).Number(), 35);\n  EXPECT_EQ(arr->get(6).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  EXPECT_EQ(arr->get(7).Number(), 20);\n  EXPECT_EQ(arr->get(8).Number(),\n            static_cast<uint32_t>(CSSValuePattern::PERCENT));\n}\n\nTEST(ClipPathHandler, HandleSuperEllipse) {\n  CSSParserConfigs configs;\n  StyleMap out;\n  // valid super-ellipse\n\n  lepus::Value input =\n      lepus::Value(R\"(super-ellipse(40px 30px 2 2 at 30px bottom))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  CSSValue super_ellipse = out[kPropertyIDClipPath];\n  auto arr = super_ellipse.GetArray().strongify();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kSuperEllipse));\n#define UNIT_PX static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PX)\n#define UNIT_PERCENT static_cast<uint32_t>(lynx::tasm::CSSValuePattern::PERCENT)\n#define CHECK_SUPER_ELLIPSE(arr, rx, urx, ry, ury, ex, ey, px, upx, py, upy) \\\n  EXPECT_EQ(arr->get(1).Number(), rx);                                       \\\n  EXPECT_EQ(arr->get(2).Number(), urx);                                      \\\n  EXPECT_EQ(arr->get(3).Number(), ry);                                       \\\n  EXPECT_EQ(arr->get(4).Number(), ury);                                      \\\n  EXPECT_EQ(arr->get(5).Number(), ex);                                       \\\n  EXPECT_EQ(arr->get(6).Number(), ey);                                       \\\n  EXPECT_EQ(arr->get(7).Number(), px);                                       \\\n  EXPECT_EQ(arr->get(8).Number(), upx);                                      \\\n  EXPECT_EQ(arr->get(9).Number(), py);                                       \\\n  EXPECT_EQ(arr->get(10).Number(), upy);\n\n  CHECK_SUPER_ELLIPSE(arr, 40, UNIT_PX, 30, UNIT_PX, 2, 2, 30, UNIT_PX, 100,\n                      UNIT_PERCENT)\n\n  // test shorthand\n  // <shape-radius>{2} at <position>\n  input = lepus::Value(R\"(super-ellipse(40% 30px at top right))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  super_ellipse = out[kPropertyIDClipPath];\n  arr = super_ellipse.GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kSuperEllipse));\n  CHECK_SUPER_ELLIPSE(arr, 40, UNIT_PERCENT, 30, UNIT_PX, 2, 2, 100,\n                      UNIT_PERCENT, 0, UNIT_PERCENT)\n\n  // <shape-radius>{2}\n  input = lepus::Value(R\"(super-ellipse(100px 20%))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  super_ellipse = out[kPropertyIDClipPath];\n  arr = super_ellipse.GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kSuperEllipse));\n  CHECK_SUPER_ELLIPSE(arr, 100, UNIT_PX, 20, UNIT_PERCENT, 2, 2, 50,\n                      UNIT_PERCENT, 50, UNIT_PERCENT)\n\n  // <shape-radius>{2} <number>{2}\n  input = lepus::Value(R\"(super-ellipse(100px 20% 9 13))\");\n  EXPECT_TRUE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  super_ellipse = out[kPropertyIDClipPath];\n  arr = super_ellipse.GetArray();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<uint32_t>(starlight::BasicShapeType::kSuperEllipse));\n  CHECK_SUPER_ELLIPSE(arr, 100, UNIT_PX, 20, UNIT_PERCENT, 9, 13, 50,\n                      UNIT_PERCENT, 50, UNIT_PERCENT)\n\n  // test invalid\n  // <shape-radius>{2} failed\n  input = lepus::Value(R\"(super-ellipse(100px))\");\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n\n  // [<number>{2}] failed\n  input = lepus::Value(R\"(super-ellipse(100px 100px 2 at bottom right))\");\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  input = lepus::Value(R\"(super-ellipse(100px 100px 2))\");\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n\n  // [at position failed]\n  input = lepus::Value(R\"(super-ellipse(100px 100px 2 3 at))\");\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n  input = lepus::Value(R\"(super-ellipse(100px 100px 2 3 at 100))\");\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDClipPath, input, out, configs));\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/color_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/color_handler.h\"\n\n#include <string>\n\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace ColorHandler {\n\nnamespace {\n// Previously Process method will not change input css_value if parsing\n// failed. Extract method to keep this behavior and optimize handler at\n// the same time.\n// This method will parse directly to input css_value.\nbool _Process(const lepus::Value& input, CSSValue& css_value,\n              const CSSParserConfigs& configs, bool is_text_color) {\n  if (!input.IsString()) {\n    return false;\n  }\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  if (is_text_color) {\n    parser.ParseTextColorTo(css_value);\n  } else {\n    parser.ParseCSSColorTo(css_value);\n  }\n  return !css_value.IsEmpty();\n}\n}  // namespace\n\nbool Process(const lepus::Value& input, CSSValue& css_value,\n             const CSSParserConfigs& configs, bool is_text_color) {\n  CSSValue color;\n  bool result = _Process(input, color, configs, is_text_color);\n  if (result) {\n    css_value = color;\n  }\n  return result;\n}\n\nHANDLER_IMPL() {\n  const bool is_text_color = key == kPropertyIDColor;\n  auto existing = output.insert_default_if_absent(key);\n  if (existing.second) {\n    // Insertion happened, key is not found in output but a default CSSValue is\n    // constructed in output map and pointed to by existing.first iterator.\n    // We parse directly to the value instance in map.\n    if (UNLIKELY(!_Process(input, *existing.first, configs, is_text_color))) {\n      // Erase the inserted one to restore output map to original state.\n      output.erase(key);\n      goto fail;\n    }\n  } else {\n    // Found existing key, but the parsing may fail and we should not changed\n    // the existing value. So use Process it will not change\n    // `existing.first->second` if parsing failed.\n    if (UNLIKELY(!Process(input, *existing.first, configs, is_text_color))) {\n      goto fail;\n    }\n  }\n  return true;\n\nfail:\n  if (configs.enable_css_strict_mode) {\n    UnitHandler::CSSWarningUnconditional(\n        FORMAT_ERROR, CSSProperty::GetPropertyNameCStr(key), input.CString());\n  }\n  return false;\n}\n\n}  // namespace ColorHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/color_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/color_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(ColorHandler, Process) {\n  auto impl = lepus::Value(\"not color\");\n  CSSPropertyID id = CSSPropertyID::kPropertyIDBackgroundColor;\n  StyleMap output;\n  CSSParserConfigs configs;\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n\n  // string color\n  impl = lepus::Value(\"red\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output[id].IsNumber());\n  EXPECT_TRUE(output[id].GetValue().IsUInt32());\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xffff0000);\n\n  output.clear();\n  impl = lepus::Value(\"#00ff00\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xff00ff00);\n\n  output.clear();\n  impl = lepus::Value(\"#056b\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xbb005566);\n\n  output.clear();\n  impl = lepus::Value(\"#090a\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xaa009900);\n\n  output.clear();\n  impl = lepus::Value(\"#ghff00\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n\n  output.clear();\n  impl = lepus::Value(\"#00ff00ee\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xee00ff00);\n\n  output.clear();\n  impl = lepus::Value(\"rgb(0,0,255)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xff0000ff);\n\n  output.clear();\n  impl = lepus::Value(\"rgb(2000,-1,255)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xffff00ff);\n\n  output.clear();\n  impl = lepus::Value(\"rgb(0, 0, 255)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xff0000ff);\n\n  output.clear();\n  impl = lepus::Value(\"rgba(0, 0, 255, 100)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xff0000ff);\n\n  output.clear();\n  impl = lepus::Value(\"hsl(240, 100%, 50%)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xff0000ff);\n\n  output.clear();\n  impl = lepus::Value(\"hsla(240, 100%, 50%, 0.3)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0x4c0000ff);\n\n  output.clear();\n  impl = lepus::Value(\"#unknow\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"Red\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xffff0000);\n\n  output.clear();\n  impl = lepus::Value(\"rgb(50%, 0%, 100%)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xff8000ff);\n\n  output.clear();\n  impl = lepus::Value(\"rgb(100%, 0%, 105%)\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_EQ(output[id].GetValue().UInt32(), 0xffff00ff);\n}\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/css_parser_configs.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_CSS_PARSER_CONFIGS_H_\n#define CORE_RENDERER_CSS_PARSER_CSS_PARSER_CONFIGS_H_\n\n#include <string>\n\n#include \"core/renderer/tasm/config.h\"\n#include \"core/template_bundle/template_codec/compile_options.h\"\n#include \"core/template_bundle/template_codec/version.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nstruct CSSParserConfigs {\n  static CSSParserConfigs GetCSSParserConfigsByComplierOptions(\n      const CompileOptions& compile_options) {\n    CSSParserConfigs config;\n    if (!compile_options.target_sdk_version_.empty() &&\n        std::isdigit(compile_options.target_sdk_version_[0])) {\n      base::Version version(compile_options.target_sdk_version_);\n      if (version >= V_2_6) {\n        config.enable_length_unit_check = true;\n      }\n      if (version < V_1_6) {\n        config.enable_legacy_parser = true;\n      }\n      if (version >= V_2_11) {\n        config.enable_new_border_handler = true;\n      }\n      if (version >= V_2_12) {\n        config.enable_new_transform_handler = true;\n        config.enable_new_flex_handler = true;\n        config.enable_new_time_handler = true;\n      }\n    }\n    config.enable_css_strict_mode = compile_options.enable_css_strict_mode_;\n    return config;\n  }\n  // default is disable.\n  bool enable_css_strict_mode = false;\n  bool enable_legacy_parser = false;\n  bool enable_length_unit_check = false;\n  bool enable_new_border_handler = false;\n  bool enable_new_transform_handler = false;\n  bool enable_new_flex_handler = false;\n  bool enable_new_time_handler = false;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_CSS_PARSER_CONFIGS_H_\n"
  },
  {
    "path": "core/renderer/css/parser/css_string_parser.cc",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifdef OS_WIN\n#define _USE_MATH_DEFINES\n#endif\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n\n#include <algorithm>\n#include <array>\n#include <cmath>\n#include <cstdint>\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/float_comparison.h\"\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/css_color.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/css/parser/css_string_scanner.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nnamespace {\nvoid ConvertPositionEnumToValue(unsigned int type, float &value,\n                                uint32_t &pattern) {\n  switch (type) {\n    case POS_CENTER:\n      value = 50.f;\n      pattern = PATTERN_PERCENT;\n      return;\n    case POS_LEFT:\n    case POS_TOP:\n      value = 0;\n      pattern = PATTERN_PERCENT;\n      return;\n    case POS_RIGHT:\n    case POS_BOTTOM:\n      value = 100.f;\n      pattern = PATTERN_PERCENT;\n      return;\n    default:\n      pattern = type;\n      return;\n  }\n}\n\nbool PositionAddValue(fml::RefPtr<lepus::CArray> &arr, const CSSValue &value) {\n  if (value.IsEmpty()) {\n    return false;\n  }\n\n  uint32_t pattern = 0;\n  float f = 0;\n  if (value.IsEnum()) {\n    ConvertPositionEnumToValue(static_cast<uint32_t>(value.AsNumber()), f,\n                               pattern);\n    arr->emplace_back(f);\n    arr->emplace_back(pattern);\n  } else {  // Length value\n    arr->emplace_back(value.GetValue());\n    arr->emplace_back(static_cast<uint32_t>(value.GetPattern()));\n  }\n  return true;\n}\n\nvoid PositionAddLegacyValue(fml::RefPtr<lepus::CArray> &arr,\n                            const CSSValue &pos) {\n  // [patten|enum, value]\n  if (pos.IsEnum()) {\n    arr->emplace_back(pos.GetValue());\n    arr->emplace_back(-pos.GetNumber());\n  } else {  // Length\n    arr->emplace_back(static_cast<uint32_t>(pos.GetPattern()));\n    arr->emplace_back(pos.GetValue());\n  }\n}\n\nvoid SizeAddLegacyValue(fml::RefPtr<lepus::CArray> &arr, const CSSValue &size) {\n  // [patten|enum, value]\n  arr->emplace_back(static_cast<uint32_t>(size.GetPattern()));\n  arr->emplace_back(size.GetValue());\n}\n}  // namespace\n\nbool CSSStringParser::AtEnd() {\n  // Before eof, we allow a semicolon for compatibility.\n  Consume(TokenType::SEMICOLON);\n  return Check(TokenType::TOKEN_EOF);\n}\n\nCSSValue CSSStringParser::ParseBackgroundOrMask(bool mask) {\n  Advance();\n  auto image_array = lepus::CArray::Create();\n  auto position_array = lepus::CArray::Create();\n  auto size_array = lepus::CArray::Create();\n  auto origin_array = lepus::CArray::Create();\n  auto repeat_array = lepus::CArray::Create();\n  auto clip_array = lepus::CArray::Create();\n\n  std::optional<uint32_t> color;\n  do {\n    CSSBackgroundLayer layer;\n    bool valid = BackgroundLayer(layer, mask);\n    // Must be a valid layer and a color is only allowed in the final layer\n    if (!valid || (layer.color.has_value() && !AtEnd())) {\n      return CSSValue();\n    }\n    color = layer.color;\n    // FIXME: If the background layer does not have an image, we should update\n    // the current layer as well\n    // But for performance we skip the layer, which is different from the web\n    if (!layer.image) {\n      continue;\n    }\n    BackgroundLayerToArray(layer, image_array.get(), position_array.get(),\n                           size_array.get(), origin_array.get(),\n                           repeat_array.get(), clip_array.get());\n  } while (!AtEnd() && Consume(TokenType::COMMA));\n\n  auto bg_array = lepus::CArray::Create();\n  if (!legacy_parser_) {\n    bg_array->reserve(7);\n  }\n  bg_array->emplace_back(color.has_value() ? *color : 0);\n  bg_array->emplace_back(std::move(image_array));\n  // Old version parser not handle <position> <size> <repeat> <origin> in\n  // shorthand parser\n  if (!legacy_parser_) {\n    bg_array->emplace_back(std::move(position_array));\n    bg_array->emplace_back(std::move(size_array));\n    bg_array->emplace_back(std::move(repeat_array));\n    bg_array->emplace_back(std::move(origin_array));\n    bg_array->emplace_back(std::move(clip_array));\n  }\n\n  return CSSValue(std::move(bg_array));\n}\n\nCSSValue CSSStringParser::ParseBackgroundImage() {\n  Advance();\n  auto result = lepus::CArray::Create();\n  while (!Check(TokenType::TOKEN_EOF) && !Check(TokenType::SEMICOLON) &&\n         !Check(TokenType::ERROR)) {\n    if (BackgroundImage()) {\n      auto value = PopValue();\n      result->emplace_back(TokenTypeToENUM(value.value_type));\n      if (value.value) {\n        result->emplace_back(std::move(*value.value));\n      }\n    } else {\n      // parse failed\n      return CSSValue();\n    }\n    if (Consume(TokenType::COMMA)) {\n      // ','\n      continue;\n    }\n  }\n\n  if (!AtEnd()) {\n    return CSSValue();\n  }\n\n  return CSSValue(std::move(result));\n}\n\nstd::string CSSStringParser::ParseUrl() {\n  Advance();\n  std::string result;\n  if (!Check(TokenType::TOKEN_EOF) && !Check(TokenType::SEMICOLON)) {\n    if (Check(TokenType::URL) && Url()) {\n      auto value = PopValue();\n      if (value.value) {\n        result = value.value->ToString();\n      }\n    } else {\n      // parse failed\n      result.clear();\n    }\n  }\n  return result;\n}\n\nCSSValue CSSStringParser::ParseLength() {\n  CSSValue result;\n  ParseLengthTo(result);\n  return result;\n}\n\nvoid CSSStringParser::ParseLengthTo(CSSValue &target) {\n  Advance();\n  LengthTo(target);\n  if (!AtEnd()) {\n    target = CSSValue();\n  }\n}\n\nCSSValue CSSStringParser::ParseSingleBorderRadius() {\n  Advance();\n  CSSValue radii[2] = {CSSValue(), CSSValue()};\n  radii[0] = Length();\n  if (radii[0].IsEmpty()) {\n    return CSSValue();\n  }\n  // Single value should not have slash, for compatibility\n  Consume(TokenType::SLASH);\n  radii[1] = Length();\n  if (radii[1].IsEmpty()) {\n    radii[1] = radii[0];\n  }\n  auto array = lepus::CArray::Create();\n  array->emplace_back(radii[0].GetValue());\n  array->emplace_back(static_cast<int>(radii[0].GetPattern()));\n  array->emplace_back(radii[1].GetValue());\n  array->emplace_back(static_cast<int>(radii[1].GetPattern()));\n  return CSSValue(std::move(array));\n}\n\nstatic void Complete4Sides(CSSValue side[4]) {\n  if (!side[3].IsEmpty()) return;\n  if (side[2].IsEmpty()) {\n    if (side[1].IsEmpty()) side[1] = side[0];\n    side[2] = side[0];\n  }\n  side[3] = side[1];\n}\n\nbool CSSStringParser::ParseBorderRadius(CSSValue horizontal_radii[4],\n                                        CSSValue vertical_radii[4]) {\n  Advance();\n  if (Check(TokenType::ERROR)) {\n    return false;\n  }\n  if (!BorderRadius(horizontal_radii, vertical_radii)) {\n    return false;\n  }\n  if (!AtEnd()) {\n    return false;\n  }\n  Complete4Sides(horizontal_radii);\n  Complete4Sides(vertical_radii);\n  return true;\n}\n\nbool CSSStringParser::BorderRadius(CSSValue horizontal_radii[4],\n                                   CSSValue vertical_radii[4]) {\n  unsigned horizontal_value_count = 0;\n  for (; horizontal_value_count < 4 && !Check(TokenType::SLASH);\n       ++horizontal_value_count) {\n    CSSValue length_value = Length();\n    if (length_value.IsEmpty()) {\n      break;\n    }\n    horizontal_radii[horizontal_value_count] = length_value;\n  }\n  if (horizontal_radii[0].IsEmpty()) return false;\n  if (!CheckAndAdvance(TokenType::SLASH)) {\n    Complete4Sides(horizontal_radii);\n    for (unsigned i = 0; i < 4; ++i) {\n      vertical_radii[i] = horizontal_radii[i];\n    }\n    return true;\n  } else {\n    for (unsigned i = 0; i < 4; ++i) {\n      CSSValue length_value = Length();\n      if (length_value.IsEmpty()) {\n        break;\n      }\n      vertical_radii[i] = length_value;\n    }\n    if (vertical_radii[0].IsEmpty()) {\n      return false;\n    }\n  }\n  return true;\n}\n\nCSSValue CSSStringParser::ParseBackgroundPosition() {\n  Advance();\n  auto result = lepus::CArray::Create();\n  do {\n    CSSValue pos_x;\n    CSSValue pos_y;\n    if (!BackgroundPosition(pos_x, pos_y)) {\n      return CSSValue();\n    }\n\n    auto array = lepus::CArray::Create();\n    PositionAddLegacyValue(array, pos_x);\n    PositionAddLegacyValue(array, pos_y);\n    result->emplace_back(std::move(array));\n  } while (Consume(TokenType::COMMA));\n\n  if (!AtEnd()) {\n    return CSSValue();\n  }\n  return CSSValue(std::move(result));\n}\n\nCSSValue CSSStringParser::ParseBackgroundSize() {\n  Advance();\n  auto result = lepus::CArray::Create();\n  do {\n    CSSValue size_x;\n    CSSValue size_y;\n    if (!BackgroundSize(size_x, size_y)) {\n      return CSSValue();\n    }\n\n    if (legacy_parser_ && size_x.GetNumber() == SIZE_AUTO &&\n        size_y.GetNumber() == SIZE_AUTO) {\n      // For compatibility, <auto> <contain> and <cover> is all 100%\n      // tailed\n      size_x = CSSValue(100.0, CSSValuePattern::PERCENT);\n      size_y = CSSValue(100.0, CSSValuePattern::PERCENT);\n    }\n\n    auto array = lepus::CArray::Create();\n    SizeAddLegacyValue(array, size_x);\n    SizeAddLegacyValue(array, size_y);\n    result->emplace_back(std::move(array));\n  } while (Consume(TokenType::COMMA));\n\n  if (!AtEnd()) {\n    return CSSValue();\n  }\n  return CSSValue(std::move(result));\n}\n\nCSSValue CSSStringParser::ParseBackgroundBox() {\n  Advance();\n  return ConsumeCommaSeparatedList(&CSSStringParser::BackgroundBox);\n}\n\nCSSValue CSSStringParser::ParseBackgroundClip() {\n  Advance();\n  return ConsumeCommaSeparatedList(&CSSStringParser::BackgroundClip);\n}\n\nCSSValue CSSStringParser::ParseBackgroundRepeat() {\n  Advance();\n\n  auto arr = lepus::CArray::Create();\n  do {\n    uint32_t repeat_x;\n    uint32_t repeat_y;\n    if (!BackgroundRepeatStyle(repeat_x, repeat_y)) {\n      return CSSValue();\n    }\n    auto repeat = lepus::CArray::Create();\n    repeat->emplace_back(repeat_x);\n    repeat->emplace_back(repeat_y);\n    arr->emplace_back(std::move(repeat));\n  } while (Consume(TokenType::COMMA));\n\n  if (!AtEnd()) {\n    return CSSValue();\n  }\n  return CSSValue(std::move(arr));\n}\n\nCSSValue CSSStringParser::ParseTextColor() {\n  CSSValue result;\n  ParseTextColorTo(result);\n  return result;\n}\n\nCSSValue CSSStringParser::ParseCSSColor() {\n  CSSValue result;\n  ParseCSSColorTo(result);\n  return result;\n}\n\nvoid CSSStringParser::ParseTextColorTo(CSSValue &target) {\n  Advance();\n\n  if (Color() || LinearGradient() || RadialGradient()) {\n    StackValue stack_value = PopValue();\n    if (stack_value.value_type == TokenType::NUMBER) {\n      target.SetValueAndPattern(*stack_value.value, CSSValuePattern::NUMBER);\n      return;\n    }\n    auto arr = lepus::CArray::Create();\n    arr->emplace_back(TokenTypeToENUM(stack_value.value_type));\n    arr->emplace_back(std::move(*stack_value.value));\n\n    // For compatibility, don't check if it's finished\n    target.SetArray(std::move(arr));\n  } else {\n    target = CSSValue();\n  }\n}\n\nvoid CSSStringParser::ParseCSSColorTo(CSSValue &target) {\n  Advance();\n  ConsumeColor(target);\n}\n\nCSSValue CSSStringParser::ParseTextDecoration() {\n  Advance();\n\n  auto result = lepus::CArray::Create();\n  int flag = 0;\n  while (!Check(TokenType::TOKEN_EOF) && !Check(TokenType::SEMICOLON) &&\n         !Check(TokenType::ERROR)) {\n    int temp_flag = 0;\n    if (TextDecorationLine()) {  // text-decoration-line\n      auto value = PopValue();\n      if (value.value_type == TokenType::NONE) {\n        result = lepus::CArray::Create();\n        result->emplace_back(TokenTypeToTextENUM(TokenType::NONE));\n        return CSSValue(std::move(result));\n      }\n      result->emplace_back(TokenTypeToTextENUM(value.value_type));\n    } else if (TextDecorationStyle()) {  // text-decoration-style\n      auto value = PopValue();\n      result->emplace_back(TokenTypeToTextENUM(value.value_type));\n      temp_flag |= 1 << 1;\n    } else if (Color()) {  // text-decoration-color\n      StackValue value = PopValue();\n      result->emplace_back(\n          static_cast<uint32_t>(starlight::TextDecorationType::kColor));\n      if (value.value) {\n        result->emplace_back(std::move(*value.value));\n      }\n      temp_flag |= 1 << 2;\n    } else {\n      return CSSValue();\n    }\n    if ((temp_flag & flag) != 0) {\n      return CSSValue();\n    }\n    flag |= temp_flag;\n  }\n  return CSSValue(std::move(result));\n}\n\nCSSValue CSSStringParser::ParseFontSrc() {\n  Advance();\n\n  auto result = lepus::CArray::Create();\n\n  while (!Check(TokenType::TOKEN_EOF) && !Check(TokenType::SEMICOLON) &&\n         !Check(TokenType::ERROR)) {\n    bool check_url = false;\n    bool check_local = false;\n    bool check_format = false;\n\n    if (Url()) {\n      auto value = PopValue();\n      result->emplace_back(\n          static_cast<uint32_t>(starlight::FontFaceSrcType::kUrl));\n      result->emplace_back(std::move(*value.value));\n      check_url = true;\n    }\n\n    if (!check_url && Local()) {\n      auto value = PopValue();\n      result->emplace_back(\n          static_cast<uint32_t>(starlight::FontFaceSrcType::kLocal));\n      result->emplace_back(std::move(*value.value));\n      check_local = true;\n    }\n\n    if (Format()) {\n      // Ignore format for now\n      PopValue();\n      check_format = true;\n    }\n\n    if (Consume(TokenType::COMMA)) {\n      if (!check_local && !check_url && !check_format) {\n        result = lepus::CArray::Create();\n        break;\n      }\n\n      continue;\n    } else if (Consume(TokenType::SEMICOLON)) {\n      // we have done\n      break;\n    } else {\n      // any other unexpected token mark failed\n      result = lepus::CArray::Create();\n      break;\n    }\n  }\n\n  return CSSValue(std::move(result));\n}\n\nCSSValue CSSStringParser::ParseFontWeight() {\n  Advance();\n  Token token;\n  auto result = lepus::CArray::Create();\n\n  while (!Check(TokenType::TOKEN_EOF) && !Check(TokenType::SEMICOLON) &&\n         !Check(TokenType::ERROR)) {\n    if (Consume(TokenType::NORMAL)) {\n      // Normal is just like font-weight: 400\n      result->emplace_back(static_cast<int32_t>(400));\n    } else if (Consume(TokenType::BOLD)) {\n      // Bold is just like font-weight: 700\n      result->emplace_back(static_cast<int32_t>(700));\n    } else if (ConsumeAndSave(TokenType::NUMBER, token)) {\n      auto number = TokenToInt(token);\n      // align number with 100\n      number += 99;\n      number /= 100;\n      number *= 100;\n      result->emplace_back(static_cast<int32_t>(number));\n    } else {\n      // unexpected error reset result and return\n      result = lepus::CArray::Create();\n      break;\n    }\n  }\n\n  return CSSValue(std::move(result));\n}\n\nCSSValue CSSStringParser::ParseFontLength() {\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return CSSValue();\n  }\n  auto res = CSSValue();\n  LengthTo(res);\n  if (!Check(TokenType::TOKEN_EOF)) {\n    return CSSValue();\n  }\n  return res;\n}\n\nCSSValue CSSStringParser::ParseListGap() {\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return CSSValue();\n  }\n  auto res = CSSValue();\n  LengthTo(res);\n  auto css_value_pattern = res.GetPattern();\n  if (!(css_value_pattern == CSSValuePattern::PX ||\n        css_value_pattern == CSSValuePattern::RPX ||\n        css_value_pattern == CSSValuePattern::PPX ||\n        css_value_pattern == CSSValuePattern::REM ||\n        css_value_pattern == CSSValuePattern::EM)) {\n    return CSSValue();\n  }\n  if (!Check(TokenType::TOKEN_EOF)) {\n    return CSSValue();\n  }\n  return res;\n}\n\nCSSValue CSSStringParser::ParseCursor() {\n  Advance();\n\n  Token t1, t2;\n  auto result = lepus::CArray::Create();\n  while (!Check(TokenType::TOKEN_EOF) && !Check(TokenType::SEMICOLON) &&\n         !Check(TokenType::ERROR)) {\n    if (Url()) {\n      auto value = PopValue();\n      result->emplace_back(static_cast<uint32_t>(starlight::CursorType::kUrl));\n      auto url = lepus::CArray::Create();\n      url->emplace_back(std::move(*value.value));\n\n      if (ConsumeAndSave(TokenType::NUMBER, t1) &&\n          ConsumeAndSave(TokenType::NUMBER, t2)) {\n        auto x = TokenToDouble(t1);\n        auto y = TokenToDouble(t2);\n        url->emplace_back(x);\n        url->emplace_back(y);\n      } else {\n        url->emplace_back(0.f);\n        url->emplace_back(0.f);\n      }\n      result->emplace_back(std::move(url));\n    } else if (ConsumeAndSave(TokenType::IDENTIFIER, t1) ||\n               ConsumeAndSave(TokenType::TEXT, t1)) {\n      // TODO(renzhongyue): make all cursor keywords builtin keywords.\n      result->emplace_back(\n          static_cast<uint32_t>(starlight::CursorType::kKeyword));\n      result->emplace_back(std::string(t1.start, t1.length));\n    } else {\n      result = lepus::CArray::Create();\n      break;\n    }\n    if (Consume(TokenType::COMMA)) {\n      // ','\n      continue;\n    }\n  }\n  return CSSValue(std::move(result));\n}\n\nlepus::Value CSSStringParser::ParseShapePath() {\n  Advance();\n  if (BasicShape()) {\n    auto shape = PopValue().value;\n    return shape.value_or(lepus::Value());\n  }\n  return lepus::Value();\n}\n\nlepus::Value CSSStringParser::ParseOffsetRotate() {\n  Advance();\n  Token angle_token;\n  if (ConsumeAndSave(TokenType::AUTO, angle_token)) {\n    return lepus::Value(ANGLE_AUTO);\n  } else if (AngleValue(angle_token) && angle_token.type == TokenType::DEG) {\n    return lepus::Value(TokenToAngleValue(angle_token));\n  } else {\n    return lepus::Value();\n  }\n}\n\nbool CSSStringParser::BasicShape() {\n  switch (current_token_.type) {\n    case TokenType::CIRCLE:\n      return BasicShapeCircle();\n    case TokenType::ELLIPSE:\n      return BasicShapeEllipse();\n    case TokenType::PATH:\n      return BasicShapePath();\n    case TokenType::SUPER_ELLIPSE:\n      return SuperEllipse();\n    case TokenType::INSET:\n      return BasicShapeInset();\n    default:\n      return false;\n  }\n}\n\nCSSValue CSSStringParser::Length() {\n  CSSValue result;\n  LengthTo(result);\n  return result;\n}\n\nvoid CSSStringParser::LengthTo(CSSValue &target) {\n  Token token;\n  if (!LengthOrPercentageValue(token)) {\n    target = CSSValue();\n  } else {\n    TokenToLengthTarget(token, target);\n  }\n}\n\nCSSValue CSSStringParser::TokenToLength(const Token &token) {\n  CSSValue result;\n  TokenToLengthTarget(token, result);\n  return result;\n}\n\nvoid CSSStringParser::TokenToLengthTarget(const Token &token,\n                                          CSSValue &css_value) {\n  auto pattern = TokenTypeToENUM(token.type);\n  if (pattern == static_cast<uint32_t>(CSSValuePattern::CALC) ||\n      pattern == static_cast<uint32_t>(CSSValuePattern::ENV) ||\n      pattern == static_cast<uint32_t>(CSSValuePattern::INTRINSIC)) {\n    css_value.SetString(base::String(token.start, token.length),\n                        static_cast<CSSValuePattern>(pattern));\n  } else if (pattern == static_cast<uint32_t>(CSSValuePattern::ENUM)) {\n    // We know the enum pattern is auto\n    css_value.SetEnum(starlight::LengthValueType::kAuto);\n  } else if (pattern < static_cast<uint32_t>(CSSValuePattern::COUNT)) {\n    auto dest = TokenToDouble(token);\n    css_value.SetNumber(dest, static_cast<CSSValuePattern>(pattern));\n\n    // As the FE developer's wish, red screen won't show if no value exists\n    // before unit. Only show a red screen when the value is Inf or NaN.\n    bool is_normal_number = !(std::isnan(dest) || std::isinf(dest));\n    UnitHandler::CSSWarning(is_normal_number,\n                            parser_configs_.enable_css_strict_mode,\n                            \"invalid length: %s\", scanner_.content());\n  } else {\n    css_value = CSSValue();\n  }\n}\n\nlepus::Value CSSStringParser::NumberOrPercentage() {\n  Token token;\n  if (NumberOrPercentValue(token)) {\n    float value = TokenToDouble(token);\n    if (token.type == TokenType::PERCENTAGE) {\n      value /= 100.f;\n    }\n    return lepus::Value(value);\n  }\n  return lepus::Value();\n}\n\nlepus::Value CSSStringParser::NumberOnly(bool nonnegative) {\n  Token token;\n  if (NumberValue(token)) {\n    double res_value = TokenToDouble(token);\n    if (nonnegative && res_value < 0) {\n      return lepus::Value();\n    }\n    return lepus::Value(res_value);\n  }\n  return lepus::Value();\n}\n\nbool CSSStringParser::BackgroundLayer(CSSBackgroundLayer &layer, bool mask) {\n  uint8_t full_byte = BG_ORIGIN | BG_CLIP_BOX | BG_IMAGE |\n                      BG_POSITION_AND_SIZE | BG_REPEAT | BG_COLOR;\n\n  uint8_t byte = full_byte;\n\n  while (!Check(TokenType::TOKEN_EOF) && !Check(TokenType::SEMICOLON) &&\n         !Check(TokenType::COMMA) && !Check(TokenType::ERROR)) {\n    uint8_t curr_byte = byte;\n\n    // check origin box first\n    if (curr_byte & BG_ORIGIN) {\n      auto origin = BackgroundBox();\n      if (!origin.IsEmpty()) {\n        curr_byte &= ~BG_ORIGIN;\n        byte = curr_byte;\n        layer.origin = origin.UInt32();\n        layer.clip = layer.origin;\n        continue;\n      }\n    } else {\n      auto clip = BackgroundBox();\n      if (!clip.IsEmpty()) {\n        if ((curr_byte & BG_CLIP_BOX) == 0) {\n          return false;\n        }\n        curr_byte &= ~BG_CLIP_BOX;\n        byte = curr_byte;\n        layer.clip = clip.UInt32();\n        continue;\n      }\n    }\n\n    if (BackgroundImage()) {\n      if ((curr_byte & BG_IMAGE) == 0) {\n        return false;\n      }\n      curr_byte &= ~BG_IMAGE;\n      byte = curr_byte;\n      layer.image = PopValue();\n      continue;\n    }\n\n    if (!mask && Color()) {\n      if ((curr_byte & BG_COLOR) == 0) {\n        return false;\n      }\n      curr_byte &= ~BG_COLOR;\n      byte = curr_byte;\n      layer.color = PopValue().value->UInt32();\n      continue;\n    }\n\n    // Add position and size to current background layer\n    if (BackgroundPositionAndSize(layer)) {\n      if ((curr_byte & BG_POSITION_AND_SIZE) == 0) {\n        return false;\n      }\n      curr_byte &= ~BG_POSITION_AND_SIZE;\n      byte = curr_byte;\n      continue;\n    }\n\n    if (BackgroundRepeatStyle(layer.repeat_x, layer.repeat_y)) {\n      if ((curr_byte & BG_REPEAT) == 0) {\n        return false;\n      }\n      curr_byte &= ~BG_REPEAT;\n      byte = curr_byte;\n      continue;\n    }\n\n    if (curr_byte == byte) {\n      return false;\n    }\n  }\n\n  // Found property will return true\n  return byte != full_byte;\n}\n\nbool CSSStringParser::BasicShapeInset() {\n  // Begin with 'inset('\n  if (!Consume(TokenType::INSET) || !Consume(TokenType::LEFT_PAREN)) {\n    return false;\n  }\n  auto arr = lepus::CArray::Create();\n  arr->emplace_back(static_cast<uint32_t>(starlight::BasicShapeType::kInset));\n  CSSValue insets[4] = {CSSValue(), CSSValue(), CSSValue(), CSSValue()};\n  unsigned int length_value_num = 0;\n  while (length_value_num < 4 && !Check(TokenType::TOKEN_EOF) &&\n         !Check(TokenType::RIGHT_PAREN) && !Check(TokenType::ROUND) &&\n         !Check(TokenType::SUPER_ELLIPSE)) {\n    insets[length_value_num] = Length();\n    if (insets[length_value_num].IsEmpty()) {\n      return false;\n    }\n    length_value_num++;\n  }\n  // insets should be followed by 'round', 'super-ellipse' (lynx support) or\n  // ')'.\n  if (!Check(TokenType::RIGHT_PAREN) && !Check(TokenType::ROUND) &&\n      !Check(TokenType::SUPER_ELLIPSE)) {\n    return false;\n  }\n  Complete4Sides(insets);\n  for (auto &inset : insets) {\n    arr->emplace_back(inset.GetValue());\n    arr->emplace_back(static_cast<uint32_t>(inset.GetPattern()));\n  }\n\n  switch (current_token_.type) {\n    case TokenType::RIGHT_PAREN: {\n      break;\n    }\n    case TokenType::SUPER_ELLIPSE: {\n      Consume(TokenType::SUPER_ELLIPSE);\n      Token token;\n      if (!ConsumeAndSave(TokenType::NUMBER, token) ||\n          !Consume(TokenType::NUMBER)) {\n        return false;\n      }\n      arr->emplace_back(TokenToDouble(token));\n      arr->emplace_back(TokenToDouble(previous_token_));\n    }\n    case TokenType::ROUND: {\n      Consume(TokenType::ROUND);\n      CSSValue x_radii[4] = {CSSValue(), CSSValue(), CSSValue(), CSSValue()};\n      CSSValue y_radii[4] = {CSSValue(), CSSValue(), CSSValue(), CSSValue()};\n      if (!BorderRadius(x_radii, y_radii)) {\n        return false;\n      }\n      Complete4Sides(x_radii);\n      Complete4Sides(y_radii);\n      for (int i = 0; i < 4; i++) {\n        arr->push_back(x_radii[i].GetValue());\n        arr->emplace_back(static_cast<int>(x_radii[i].GetPattern()));\n        arr->push_back(y_radii[i].GetValue());\n        arr->emplace_back(static_cast<int>(y_radii[i].GetPattern()));\n      }\n    } break;\n    default:\n      // error\n      return false;\n  }\n  // not closed with right parenthesis or has other token after ')'.\n  if (!Consume(TokenType::RIGHT_PAREN) || !Consume(TokenType::TOKEN_EOF)) {\n    return false;\n  }\n  PushValue(StackValue(TokenType::INSET, std::move(arr)));\n  return true;\n}\n\nbool CSSStringParser::BackgroundImage() {\n  Token token;\n  if (ConsumeAndSave(TokenType::NONE, token)) {\n    PushValue(StackValue(token.type));\n    return true;\n  } else if (Check(TokenType::URL)) {\n    return Url();\n  } else {\n    return Gradient();\n  }\n}\n\nlepus::Value CSSStringParser::BackgroundBox() {\n  Token token;\n  if (Box(token)) {\n    return lepus::Value(TokenTypeToENUM(token.type));\n  }\n  return lepus::Value();\n}\n\nlepus::Value CSSStringParser::BackgroundClip() {\n  Token token;\n  if (Box(token) || ConsumeAndSave(TokenType::TEXT, token) ||\n      ConsumeAndSave(TokenType::BORDER_AREA, token)) {\n    return lepus::Value(TokenTypeToENUM(token.type));\n  }\n  return lepus::Value();\n}\n\nbool CSSStringParser::Box(Token &token) {\n  return ConsumeAndSave(TokenType::PADDING_BOX, token) ||\n         ConsumeAndSave(TokenType::BORDER_BOX, token) ||\n         ConsumeAndSave(TokenType::CONTENT_BOX, token);\n}\n\nbool CSSStringParser::BackgroundPositionAndSize(CSSBackgroundLayer &layer) {\n  if (BackgroundPosition(layer.position_x, layer.position_y) &&\n      !Check(TokenType::COMMA) && !Check(TokenType::SEMICOLON)) {\n    // if pass <bg-position> parse and not reach ',' or end of string, try parse\n    // <bg-size>\n    if (Check(TokenType::SLASH)) {\n      return Consume(TokenType::SLASH) &&\n             BackgroundSize(layer.size_x, layer.size_y);\n    }\n  } else {\n    return false;\n  }\n  return true;\n}\n\nbool CSSStringParser::ConsumePosition(bool &horizontal_edge,\n                                      bool &vertical_edge, CSSValue &ret) {\n  Token token;\n  if (ConsumeAndSave(TokenType::LEFT, token) ||\n      ConsumeAndSave(TokenType::RIGHT, token)) {\n    if (horizontal_edge) {\n      return false;\n    }\n    horizontal_edge = true;\n    ret = CSSValue(TokenTypeToENUM(token.type), CSSValuePattern::ENUM);\n    return true;\n  } else if (ConsumeAndSave(TokenType::TOP, token) ||\n             ConsumeAndSave(TokenType::BOTTOM, token)) {\n    if (vertical_edge) {\n      return false;\n    }\n    vertical_edge = true;\n    ret = CSSValue(TokenTypeToENUM(token.type), CSSValuePattern::ENUM);\n    return true;\n  } else if (ConsumeAndSave(TokenType::CENTER, token)) {\n    ret = CSSValue(TokenTypeToENUM(token.type), CSSValuePattern::ENUM);\n    return true;\n  }\n  // Maybe length value, should check if at the end\n  ret = Length();\n  return true;\n}\n\nstatic bool IsHorizontalPositionKeywordOnly(const CSSValue &value) {\n  if (!value.IsEnum()) {\n    return false;\n  }\n  return value.AsNumber() == POS_LEFT || value.AsNumber() == POS_RIGHT;\n}\n\nstatic bool IsVerticalPositionKeywordOnly(const CSSValue &value) {\n  if (!value.IsEnum()) {\n    return false;\n  }\n  return value.AsNumber() == POS_TOP || value.AsNumber() == POS_BOTTOM;\n}\n\nstatic void PositionFromOneValue(const CSSValue &value, CSSValue &result_x,\n                                 CSSValue &result_y) {\n  bool swap_x_y = IsVerticalPositionKeywordOnly(value);\n  result_x = value;\n  result_y = CSSValue(POS_CENTER, CSSValuePattern::ENUM);\n  if (swap_x_y) {\n    std::swap(result_x, result_y);\n  }\n}\n\nstatic void PositionFromTwoValues(const CSSValue &value1,\n                                  const CSSValue &value2, CSSValue &result_x,\n                                  CSSValue &result_y) {\n  bool must_order_as_yx = IsVerticalPositionKeywordOnly(value1) ||\n                          IsHorizontalPositionKeywordOnly(value2);\n  result_x = value1;\n  result_y = value2;\n  if (must_order_as_yx) {\n    std::swap(result_x, result_y);\n  }\n}\n\nbool CSSStringParser::BackgroundPosition(CSSValue &x, CSSValue &y) {\n  bool horizontal_edge = false;\n  bool vertical_edge = false;\n  CSSValue value1;\n  if (!ConsumePosition(horizontal_edge, vertical_edge, value1) ||\n      value1.IsEmpty()) {\n    return false;\n  }\n  // Length value\n  if (!value1.IsEnum()) {\n    horizontal_edge = true;\n  }\n\n  if (vertical_edge && !Length().IsEmpty()) {\n    // <length-percentage> is not permitted after top | bottom.\n    return false;\n  }\n\n  // For compatibility, we support comma in transform-origin\n  if (enable_transform_legacy_) {\n    Consume(TokenType::COMMA);\n  }\n\n  CSSValue value2;\n  if (!ConsumePosition(horizontal_edge, vertical_edge, value2)) {\n    return false;\n  }\n  if (value2.IsEmpty()) {\n    PositionFromOneValue(value1, x, y);\n  } else {\n    PositionFromTwoValues(value1, value2, x, y);\n  }\n  return true;\n}\n\nbool CSSStringParser::BackgroundSize(CSSValue &x, CSSValue &y) {\n  Token token;\n  if (ConsumeAndSave(TokenType::COVER, token) ||\n      ConsumeAndSave(TokenType::CONTAIN, token)) {\n    x = CSSValue(-1.f * TokenTypeToENUM(token.type), CSSValuePattern::NUMBER);\n    y = CSSValue(-1.f * TokenTypeToENUM(token.type), CSSValuePattern::NUMBER);\n    return true;\n  }\n\n  // check first value\n  if (ConsumeAndSave(TokenType::AUTO, token)) {\n    x = CSSValue(SIZE_AUTO, CSSValuePattern::NUMBER);\n  } else {\n    x = Length();\n  }\n\n  if (x.IsEmpty()) {\n    return false;\n  }\n\n  if (ConsumeAndSave(TokenType::AUTO, token)) {\n    y = CSSValue(SIZE_AUTO, CSSValuePattern::NUMBER);\n  } else {\n    y = Length();\n  }\n  if (y.IsEmpty()) {\n    y = CSSValue(SIZE_AUTO, CSSValuePattern::NUMBER);\n  }\n  return true;\n}\n\nbool CSSStringParser::BackgroundRepeatStyle(uint32_t &x, uint32_t &y) {\n  Token token;\n  if (ConsumeAndSave(TokenType::REPEAT_X, token)     // repeat-x\n      || ConsumeAndSave(TokenType::REPEAT_Y, token)  // repeat-y\n  ) {\n    // make sure no other repeat style follow this two token\n    if (Check(TokenType::REPEAT) || Check(TokenType::REPEAT_X) ||\n        Check(TokenType::REPEAT_Y) || Check(TokenType::NO_REPEAT) ||\n        Check(TokenType::SPACE) || Check(TokenType::ROUND)) {\n      return false;\n    }\n    // repeat-x | repeat-y can only appear once\n    x = TokenTypeToENUM(TokenType::REPEAT);\n    y = TokenTypeToENUM(TokenType::NO_REPEAT);\n    // repeat-y should swap\n    if (token.type == TokenType::REPEAT_Y) {\n      std::swap(x, y);\n    }\n    return true;\n  }\n\n  if (!ConsumeAndSave(TokenType::REPEAT, token)        // repeat\n      && !ConsumeAndSave(TokenType::NO_REPEAT, token)  // no-repeat\n      && !ConsumeAndSave(TokenType::SPACE, token)      // space\n      && !ConsumeAndSave(TokenType::ROUND, token)      // round\n  ) {\n    return false;\n  }\n  x = TokenTypeToENUM(token.type);\n  Token second_token;\n  // try to check if there is second value\n  if (ConsumeAndSave(TokenType::REPEAT, second_token) ||\n      ConsumeAndSave(TokenType::NO_REPEAT, second_token) ||\n      ConsumeAndSave(TokenType::SPACE, second_token) ||\n      ConsumeAndSave(TokenType::ROUND, second_token)) {\n    y = TokenTypeToENUM(second_token.type);\n  } else {\n    y = x;\n  }\n  return true;\n}\n\nbool CSSStringParser::TextDecorationLine() {\n  Token token;\n  if (ConsumeAndSave(TokenType::NONE, token) ||\n      ConsumeAndSave(TokenType::UNDERLINE, token) ||\n      ConsumeAndSave(TokenType::LINE_THROUGH, token)) {\n    PushValue(StackValue{token.type});\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::TextDecorationStyle() {\n  Token token;\n  if (ConsumeAndSave(TokenType::SOLID, token) ||\n      ConsumeAndSave(TokenType::DOUBLE, token) ||\n      ConsumeAndSave(TokenType::DOTTED, token) ||\n      ConsumeAndSave(TokenType::DASHED, token) ||\n      ConsumeAndSave(TokenType::WAVY, token)) {\n    PushValue(StackValue(token.type));\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::Format() {\n  Token format, string;\n  if (!ConsumeAndSave(TokenType::FORMAT, format)) {\n    return false;\n  }\n\n  if (!Consume(TokenType::LEFT_PAREN)) {\n    return false;\n  }\n\n  if (!ConsumeAndSave(TokenType::STRING, string)) {\n    return false;\n  }\n\n  if (!Consume(TokenType::RIGHT_PAREN)) {\n    return false;\n  }\n  PushValue(\n      StackValue(TokenType::FORMAT, base::String(string.start, string.length)));\n\n  return true;\n}\n\nbool CSSStringParser::Local() {\n  Token local;\n  ConsumeAndSave(TokenType::LOCAL, local);\n  if (!Consume(TokenType::LEFT_PAREN)) {  // (\n    return false;\n  }\n  Token string;\n  if (ConsumeAndSave(TokenType::STRING, string) &&\n      Consume(TokenType::RIGHT_PAREN)) {\n    PushValue(StackValue{TokenType::LOCAL,\n                         base::String(string.start, string.length)});\n    return true;\n  }\n\n  if (!Check(TokenType::RIGHT_PAREN)) {\n    // may be this <local>(...) with no quotes\n    Token virtual_token;\n    virtual_token.start = previous_token_.start + previous_token_.length;\n    while (!Check(TokenType::RIGHT_PAREN)) {\n      if (Check(TokenType::TOKEN_EOF) || Check(TokenType::ERROR)) {\n        return false;\n      }\n      Advance();\n    }\n\n    virtual_token.length =\n        static_cast<uint32_t>(current_token_.start - virtual_token.start);\n    if (!Consume(TokenType::RIGHT_PAREN)) {\n      return false;\n    }\n    PushValue(StackValue{TokenType::LOCAL, base::String(virtual_token.start,\n                                                        virtual_token.length)});\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::Url() {\n  Token url;\n  ConsumeAndSave(TokenType::URL, url);\n  if (!Consume(TokenType::LEFT_PAREN)) {  // (\n    return false;\n  }\n  Token data;\n  if (ConsumeAndSave(TokenType::STRING, data) &&  // <string>\n      Consume(TokenType::RIGHT_PAREN)) {          // )\n    PushValue(\n        StackValue{TokenType::URL, base::String(data.start, data.length)});\n    return true;\n  }\n\n  if (ConsumeAndSave(TokenType::DATA, data)) {\n    while (!Check(TokenType::RIGHT_PAREN)) {\n      if (Check(TokenType::TOKEN_EOF) || Check(TokenType::ERROR)) {\n        return false;\n      }\n      Advance();\n    }\n    data.length = static_cast<uint32_t>(current_token_.start - data.start);\n    if (!Consume(TokenType::RIGHT_PAREN)) {\n      return false;\n    }\n    PushValue(\n        StackValue{TokenType::URL, base::String(data.start, data.length)});\n    return true;\n  }\n\n  if (!Check(TokenType::RIGHT_PAREN)) {\n    // may be this is <url>(...) with no quotes\n    Token virtual_token;\n    virtual_token.start = previous_token_.start + previous_token_.length;\n    while (!Check(TokenType::RIGHT_PAREN)) {\n      if (Check(TokenType::TOKEN_EOF) || Check(TokenType::ERROR)) {\n        return false;\n      }\n      Advance();\n    }\n\n    virtual_token.length =\n        static_cast<uint32_t>(current_token_.start - virtual_token.start);\n    if (!Consume(TokenType::RIGHT_PAREN)) {\n      return false;\n    }\n    PushValue(StackValue{TokenType::URL, base::String(virtual_token.start,\n                                                      virtual_token.length)});\n    return true;\n  }\n\n  return false;\n}\n\nbool CSSStringParser::Gradient() {\n  if (Check(TokenType::LINEAR_GRADIENT)) {\n    return LinearGradient();\n  } else if (Check(TokenType::RADIAL_GRADIENT)) {\n    return RadialGradient();\n  } else if (Check(TokenType::CONIC_GRADIENT)) {\n    return ConicGradient();\n  } else {\n    return false;\n  }\n}\n\nbool CSSStringParser::LinearGradient() {\n  if (!Consume(TokenType::LINEAR_GRADIENT)) {\n    return false;\n  }\n\n  if (!Consume(TokenType::LEFT_PAREN)) {  // (\n    return false;\n  }\n\n  auto side_or_corner = starlight::LinearGradientDirection::kBottom;\n\n  float angle = 180.f;\n  if (Check(TokenType::NUMBER) || Check(TokenType::DIMENSION)) {\n    Token angle_token;\n    if (!AngleValue(angle_token)) {\n      return false;\n    }\n    side_or_corner = starlight::LinearGradientDirection::kAngle;\n    angle = TokenToAngleValue(angle_token);\n    // ','\n    if (!Consume(TokenType::COMMA)) {\n      return false;\n    }\n  } else if (Check(TokenType::TO)) {\n    Consume(TokenType::TO);\n    if (Consume(TokenType::LEFT)) {\n      if (Consume(TokenType::TOP)) {\n        angle = 315.f;\n        side_or_corner = starlight::LinearGradientDirection::kTopLeft;\n      } else if (Consume(TokenType::BOTTOM)) {\n        angle = 225.f;\n        side_or_corner = starlight::LinearGradientDirection::kBottomLeft;\n      } else {\n        angle = 270.f;\n        side_or_corner = starlight::LinearGradientDirection::kLeft;\n      }\n    } else if (Consume(TokenType::BOTTOM)) {\n      if (Consume(TokenType::LEFT)) {\n        angle = 225.f;\n        side_or_corner = starlight::LinearGradientDirection::kBottomLeft;\n      } else if (Consume(TokenType::RIGHT)) {\n        angle = 135.f;\n        side_or_corner = starlight::LinearGradientDirection::kBottomRight;\n      } else {\n        angle = 180.f;\n        side_or_corner = starlight::LinearGradientDirection::kBottom;\n      }\n    } else if (Consume(TokenType::TOP)) {\n      if (Consume(TokenType::LEFT)) {\n        angle = 315.f;\n        side_or_corner = starlight::LinearGradientDirection::kTopLeft;\n      } else if (Consume(TokenType::RIGHT)) {\n        angle = 45.f;\n        side_or_corner = starlight::LinearGradientDirection::kTopRight;\n      } else {\n        angle = 0.f;\n        side_or_corner = starlight::LinearGradientDirection::kTop;\n      }\n    } else if (Consume(TokenType::RIGHT)) {\n      if (Consume(TokenType::TOP)) {\n        angle = 45.f;\n        side_or_corner = starlight::LinearGradientDirection::kTopRight;\n      } else if (Consume(TokenType::BOTTOM)) {\n        angle = 135.f;\n        side_or_corner = starlight::LinearGradientDirection::kBottomRight;\n      } else {\n        angle = 90.f;\n        side_or_corner = starlight::LinearGradientDirection::kRight;\n      }\n    } else {\n      return false;\n    }\n    // ','\n    if (!Consume(TokenType::COMMA)) {\n      return false;\n    }\n  } else if (Consume(TokenType::TOLEFT)) {\n    angle = 270.f;\n    side_or_corner = starlight::LinearGradientDirection::kLeft;\n    // ','\n    if (!Consume(TokenType::COMMA)) {\n      return false;\n    }\n  } else if (Consume(TokenType::TOBOTTOM)) {\n    angle = 180.f;\n    side_or_corner = starlight::LinearGradientDirection::kBottom;\n    // ','\n    if (!Consume(TokenType::COMMA)) {\n      return false;\n    }\n  } else if (Consume(TokenType::TOTOP)) {\n    side_or_corner = starlight::LinearGradientDirection::kTop;\n    angle = 0.f;\n    // ','\n    if (!Consume(TokenType::COMMA)) {\n      return false;\n    }\n  } else if (Consume(TokenType::TORIGHT)) {\n    side_or_corner = starlight::LinearGradientDirection::kRight;\n    angle = 90.f;\n    // ','\n    if (!Consume(TokenType::COMMA)) {\n      return false;\n    }\n  }\n\n  auto color_array = lepus::CArray::Create();\n  auto position_array = lepus::CArray::Create();\n\n  if (!ColorStopList(color_array, position_array)) {\n    return false;\n  }\n\n  if (color_array->size() == 0) {\n    return false;\n  }\n\n  auto linear_gradient_obj = lepus::CArray::Create();\n  linear_gradient_obj->emplace_back(angle);\n  linear_gradient_obj->emplace_back(std::move(color_array));\n  linear_gradient_obj->emplace_back(std::move(position_array));\n  linear_gradient_obj->emplace_back(static_cast<int32_t>(side_or_corner));\n\n  PushValue(\n      StackValue(TokenType::LINEAR_GRADIENT, std::move(linear_gradient_obj)));\n  return true;\n}\n\nbool CSSStringParser::RadialGradient() {\n  Consume(TokenType::RADIAL_GRADIENT);\n  if (!Consume(TokenType::LEFT_PAREN)) {  // '('\n    return false;\n  }\n\n  auto color_array = lepus::CArray::Create();\n  auto position_array = lepus::CArray::Create();\n\n  uint32_t shape =\n      static_cast<uint32_t>(starlight::RadialGradientShapeType::kEllipse);\n  uint32_t shape_size =\n      static_cast<uint32_t>(starlight::RadialGradientSizeType::kFarthestCorner);\n\n  CSSValue pos_x = CSSValue(50.f, CSSValuePattern::PERCENT);\n  CSSValue pos_y = CSSValue(50.f, CSSValuePattern::PERCENT);\n  CSSValue size_x;\n  CSSValue size_y;\n\n  bool shape_valid = false;\n  bool has_shape = false;\n  if (EndingShape()) {\n    StackValue value = PopValue();\n    shape = TokenTypeToENUM(value.value_type);\n    shape_valid = true;\n    has_shape = true;\n  }\n  bool size_keyword = EndingShapeSizeIdent();\n  if (size_keyword) {\n    StackValue value = PopValue();\n    shape_size = TokenTypeToENUM(value.value_type);\n    shape_valid = true;\n  }\n\n  // Optional size\n  size_x = Length();\n  if (!size_x.IsEmpty()) {\n    shape_valid = true;\n    size_y = Length();\n  }\n\n  // Invalid value\n  if (size_keyword && !size_x.IsEmpty()) {\n    return false;\n  }\n\n  if (!size_x.IsEmpty()) {\n    shape_size =\n        static_cast<uint32_t>(starlight::RadialGradientSizeType::kLength);\n  }\n\n  // Circles must have 0 or 1 lengths.\n  if (has_shape &&\n      shape ==\n          static_cast<uint32_t>(starlight::RadialGradientShapeType::kCircle) &&\n      !size_y.IsEmpty()) {\n    return false;\n  }\n\n  // Ellipses must have 0 or 2 length/percentages.\n  if (has_shape &&\n      shape ==\n          static_cast<uint32_t>(starlight::RadialGradientShapeType::kEllipse) &&\n      !size_x.IsEmpty() && size_y.IsEmpty()) {\n    return false;\n  }\n\n  if (!size_x.IsEmpty() && size_y.IsEmpty()) {\n    shape = static_cast<uint32_t>(starlight::RadialGradientShapeType::kCircle);\n    size_y = size_x;\n  }\n\n  if (Consume(TokenType::AT)) {\n    if (!BackgroundPosition(pos_x, pos_y)) {\n      return false;\n    }\n    shape_valid = true;\n  }\n\n  if (shape_valid && !Consume(TokenType::COMMA)) {\n    return false;\n  }\n\n  if (!ColorStopList(color_array, position_array)) {\n    return false;\n  }\n\n  auto radial_gradient_obj = lepus::CArray::Create();\n  // ending-shape size position\n  {\n    auto shape_arr = lepus::CArray::Create();\n    shape_arr->emplace_back(shape);\n    shape_arr->emplace_back(shape_size);\n    PositionAddLegacyValue(shape_arr, pos_x);\n    PositionAddLegacyValue(shape_arr, pos_y);\n    // Has length value: [x_pattern, x_value, y_pattern, y_value]\n    if (shape_size ==\n        static_cast<uint32_t>(starlight::RadialGradientSizeType::kLength)) {\n      shape_arr->emplace_back(static_cast<uint32_t>(size_x.GetPattern()));\n      shape_arr->emplace_back(size_x.GetValue());\n      shape_arr->emplace_back(static_cast<uint32_t>(size_y.GetPattern()));\n      shape_arr->emplace_back(size_y.GetValue());\n    }\n    radial_gradient_obj->emplace_back(std::move(shape_arr));\n  }\n  radial_gradient_obj->emplace_back(std::move(color_array));\n  radial_gradient_obj->emplace_back(std::move(position_array));\n\n  PushValue(\n      StackValue(TokenType::RADIAL_GRADIENT, std::move(radial_gradient_obj)));\n\n  return true;\n}\n\nbool CSSStringParser::ConicGradient() {\n  if (!Consume(TokenType::CONIC_GRADIENT)) {\n    return false;\n  }\n  if (!Consume(TokenType::LEFT_PAREN)) {  // '('\n    return false;\n  }\n\n  float angle = 0.f;\n  bool need_comma = false;\n  if (Consume(TokenType::FROM)) {\n    need_comma = true;\n    if (Check(TokenType::NUMBER) || Check(TokenType::DIMENSION)) {\n      Token angle_token;\n      if (!AngleValue(angle_token)) {\n        return false;\n      }\n      angle = TokenToAngleValue(angle_token);\n    } else {\n      return false;\n    }\n  }\n\n  CSSValue pos_x = CSSValue(50.f, CSSValuePattern::PERCENT);\n  CSSValue pos_y = CSSValue(50.f, CSSValuePattern::PERCENT);\n  if (Consume(TokenType::AT)) {\n    need_comma = true;\n    if (!BackgroundPosition(pos_x, pos_y)) {\n      return false;\n    }\n  }\n  if (need_comma && !Consume(TokenType::COMMA)) {\n    return false;\n  }\n\n  auto color_array = lepus::CArray::Create();\n  auto position_array = lepus::CArray::Create();\n\n  if (!ColorStopList(color_array, position_array)) {\n    return false;\n  }\n\n  if (color_array->size() == 0) {\n    return false;\n  }\n\n  auto conic_gradient_obj = lepus::CArray::Create();\n  conic_gradient_obj->emplace_back(angle);\n  auto array = lepus::CArray::Create();\n  PositionAddValue(array, pos_x);\n  PositionAddValue(array, pos_y);\n  conic_gradient_obj->emplace_back(std::move(array));\n  conic_gradient_obj->emplace_back(std::move(color_array));\n  conic_gradient_obj->emplace_back(std::move(position_array));\n\n  PushValue(\n      StackValue(TokenType::CONIC_GRADIENT, std::move(conic_gradient_obj)));\n  return true;\n}\n\nbool CSSStringParser::EndingShape() {\n  if (Consume(TokenType::ELLIPSE)) {\n    PushValue(StackValue(TokenType::ELLIPSE));\n    return true;\n  } else if (Consume(TokenType::CIRCLE)) {\n    PushValue(StackValue(TokenType::CIRCLE));\n    return true;\n  } else {\n    return false;\n  }\n}\n\nbool CSSStringParser::EndingShapeSizeIdent() {\n  if (Consume(TokenType::FARTHEST_CORNER)) {\n    PushValue(StackValue(TokenType::FARTHEST_CORNER));\n    return true;\n  } else if (Consume(TokenType::FARTHEST_SIDE)) {\n    PushValue(StackValue(TokenType::FARTHEST_SIDE));\n    return true;\n  } else if (Consume(TokenType::CLOSEST_CORNER)) {\n    PushValue(StackValue(TokenType::CLOSEST_CORNER));\n    return true;\n  } else if (Consume(TokenType::CLOSEST_SIDE)) {\n    PushValue(StackValue(TokenType::CLOSEST_SIDE));\n    return true;\n  } else {\n    return false;\n  }\n}\n\nbool CSSStringParser::ColorStopList(\n    const fml::RefPtr<lepus::CArray> &color_array,\n    const fml::RefPtr<lepus::CArray> &stop_array) {\n  size_t position_begin_index = -1;\n  float position_begin_value = 0.f;\n  static constexpr size_t kColorStopInlineSize = 16;\n  base::InlineVector<uint32_t, kColorStopInlineSize> temp_color_list;\n  base::InlineVector<float, kColorStopInlineSize> temp_stop_list;\n  while (Color() && !(Check(TokenType::TOKEN_EOF))) {\n    auto color_value = PopValue();\n    temp_color_list.emplace_back(color_value.value->UInt32());\n    if (Check(TokenType::COMMA)) {\n      // ',' after color, no position\n      if (position_begin_index == static_cast<size_t>(-1)) {\n        position_begin_index = temp_color_list.size() - 1;\n      }\n      Consume(TokenType::COMMA);\n      continue;\n    }\n    if (Check(TokenType::RIGHT_PAREN)) {\n      break;\n    }\n    Token position;\n    if (!NumberOrPercentValue(position)) {\n      return false;\n    }\n    float current_stop_position = TokenToDouble(position);\n    if (position.type == TokenType::NUMBER) {\n      current_stop_position *= 100.f;\n    }\n\n    if (position_begin_index != static_cast<size_t>(-1)) {\n      // fill empty position with previous stop position and current stop\n      // position\n\n      size_t current_index = temp_color_list.size() - 1;\n      if (position_begin_index > 0) {\n        position_begin_value = temp_stop_list[position_begin_index - 1];\n      } else if (position_begin_index == 0) {\n        position_begin_index++;\n        temp_stop_list.emplace_back(0.f);\n      }\n\n      float step = (current_stop_position - position_begin_value) /\n                   (current_index - position_begin_index + 1);\n\n      for (size_t j = position_begin_index; j < current_index; j++) {\n        temp_stop_list.emplace_back(position_begin_value +\n                                    (j - position_begin_index + 1) * step);\n      }\n    }\n    temp_stop_list.emplace_back(current_stop_position);\n    // clear position begin index\n    position_begin_index = -1;\n\n    if (Check(TokenType::COMMA)) {\n      Consume(TokenType::COMMA);\n    }\n  }\n\n  if (!Consume(TokenType::RIGHT_PAREN)) {\n    return false;\n  }\n  // fill empty position to the end\n  int32_t fill_step =\n      static_cast<int32_t>(temp_color_list.size() - temp_stop_list.size());\n  if (!temp_stop_list.empty() && fill_step > 0) {\n    float step_value = (100.f - temp_stop_list.back()) / fill_step;\n    float begin_value = temp_stop_list.back();\n    for (int32_t i = 1; i < fill_step; i++) {\n      temp_stop_list.emplace_back(begin_value + step_value * i);\n    }\n    temp_stop_list.emplace_back(100.f);\n  }\n  // clamp color and stop\n  ClampColorAndStopList(temp_color_list, temp_stop_list);\n\n  if (temp_color_list.size() < 2 ||\n      (!temp_stop_list.empty() &&\n       temp_stop_list.size() != temp_color_list.size())) {\n    // gradient need at least two colors\n    return false;\n  }\n\n  for (auto color_value : temp_color_list) {\n    color_array->emplace_back(color_value);\n  }\n\n  for (auto stop_value : temp_stop_list) {\n    stop_array->emplace_back(stop_value);\n  }\n\n  return true;\n}\n\n// bool CSSStringParser::\n\nbool CSSStringParser::AngleValue(Token &token) {\n  if (NumberValue(token)) {\n    // For compatibility, we support number without unit in angle value\n    return token.IsZero() || enable_transform_legacy_;\n  }\n  if (DimensionValue(token) &&\n      (token.unit == TokenType::DEG || token.unit == TokenType::TURN ||\n       token.unit == TokenType::RAD || token.unit == TokenType::GRAD)) {\n    token.type = token.unit;\n    return true;\n  }\n\n  return false;\n}\n\nbool CSSStringParser::TimeValue(Token &token) {\n  // Time need unit include 0,\n  // for compatibility, we support number without unit in time value\n  if (enable_time_legacy_ && NumberValue(token)) {\n    return true;\n  }\n  if (DimensionValue(token) && (token.unit == TokenType::SECOND ||\n                                token.unit == TokenType::MILLISECOND)) {\n    token.type = token.unit;\n    return true;\n  }\n\n  return false;\n}\n\nbool CSSStringParser::TransitionProperty(Token &token) {\n  SkipWhitespaceToken();\n  // keyword and ident\n  if (current_token_.IsIdent()) {\n    token = current_token_;\n    Advance();\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::TimingFunctionValue(Token &token) {\n  SkipWhitespaceToken();\n  if ((current_token_.type >= TokenType::LINEAR &&\n       current_token_.type <= TokenType::STEP_END) ||\n      current_token_.type == TokenType::SQUARE_BEZIER ||\n      current_token_.type == TokenType::CUBIC_BEZIER ||\n      current_token_.type == TokenType::STEPS) {\n    token = current_token_;\n    Advance();\n    return true;\n  }\n\n  return false;\n}\n\nCSSValue CSSStringParser::ConsumeTimingFunction(\n    const Token &token, const CSSParserConfigs &configs) {\n  CSSValue css_value;\n  auto type = TokenToTimingFunctionType(token);\n  if (token.type >= TokenType::LINEAR && token.type <= TokenType::EASE_IN_OUT) {\n    css_value.SetEnum(type);\n  } else if (token.type == TokenType::STEP_START ||\n             token.type == TokenType::STEP_END) {\n    auto arr = lepus::CArray::Create();\n    auto step_type = token.type == TokenType::STEP_START\n                         ? starlight::StepsType::kStart\n                         : starlight::StepsType::kEnd;\n    arr->emplace_back(static_cast<int>(type));\n    arr->emplace_back(1);\n    arr->emplace_back(static_cast<int>(step_type));\n    css_value.SetArray(std::move(arr));\n  } else if (token.type == TokenType::SQUARE_BEZIER ||\n             token.type == TokenType::CUBIC_BEZIER ||\n             token.type == TokenType::STEPS) {\n    auto arr = lepus::CArray::Create();\n    CSSStringParser params_parser{token.start, token.length, configs};\n    if (!params_parser.ParseTimingFunctionParams(token, arr)) {\n      return CSSValue();\n    }\n    css_value.SetArray(std::move(arr));\n  }\n  return css_value;\n}\n\nbool CSSStringParser::ParseTimingFunctionParams(\n    const Token &function_token, fml::RefPtr<lepus::CArray> &arr) {\n  Advance();\n  if (function_token.type == TokenType::SQUARE_BEZIER) {\n    arr->emplace_back(\n        static_cast<int>(starlight::TimingFunctionType::kSquareBezier));\n    Token x, y;\n    if (NumberValue(x) && Consume(TokenType::COMMA) && NumberValue(y) &&\n        Check(TokenType::TOKEN_EOF)) {\n      arr->emplace_back(TokenToDouble(x));\n      arr->emplace_back(TokenToDouble(y));\n      return true;\n    } else {\n      return false;\n    }\n  } else if (function_token.type == TokenType::CUBIC_BEZIER) {\n    arr->emplace_back(\n        static_cast<int>(starlight::TimingFunctionType::kCubicBezier));\n    Token x1, y1, x2, y2;\n    if (NumberValue(x1) && Consume(TokenType::COMMA) && NumberValue(y1) &&\n        Consume(TokenType::COMMA) && NumberValue(x2) &&\n        Consume(TokenType::COMMA) && NumberValue(y2) &&\n        Check(TokenType::TOKEN_EOF)) {\n      // x1 >= 0 && x1 <= 1\n      // x2 >= 0 && x2 <= 1\n      arr->emplace_back(TokenToDouble(x1));\n      arr->emplace_back(TokenToDouble(y1));\n      arr->emplace_back(TokenToDouble(x2));\n      arr->emplace_back(TokenToDouble(y2));\n      return true;\n    } else {\n      return false;\n    }\n  } else if (function_token.type == TokenType::STEPS) {\n    arr->emplace_back(static_cast<int>(starlight::TimingFunctionType::kSteps));\n    Token t;\n    if (!NumberValue(t)) {\n      return false;\n    }\n    arr->emplace_back(static_cast<int>(TokenToInt(t)));\n\n    if (!Consume(TokenType::COMMA)) {\n      return false;\n    }\n    SkipWhitespaceToken();\n    std::string s_type_str =\n        std::string(current_token_.start, current_token_.length);\n    auto s_type = starlight::StepsType::kInvalid;\n    if (s_type_str == \"start\" || s_type_str == \"jump-start\") {\n      s_type = starlight::StepsType::kStart;\n    } else if (s_type_str == \"end\" || s_type_str == \"jump-end\") {\n      s_type = starlight::StepsType::kEnd;\n    } else if (s_type_str == \"jump-both\") {\n      s_type = starlight::StepsType::kJumpBoth;\n    } else if (s_type_str == \"jump-none\") {\n      s_type = starlight::StepsType::kJumpNone;\n    } else {\n      return false;\n    }\n    arr->emplace_back(static_cast<int>(s_type));\n    Advance();\n    return Check(TokenType::TOKEN_EOF);\n  }\n  return false;\n}\n\nvoid CSSStringParser::ConsumeBorderLineWidth(Token &token, CSSValue &result) {\n  if (BorderWidthIdent(token)) {\n    result.SetNumber(TokenTypeToBorderWidth(token.type), CSSValuePattern::PX);\n  } else {\n    // The next token may be length\n    LengthTo(result);\n  }\n}\n\nbool CSSStringParser::BorderWidthIdent(Token &token) {\n  if (ConsumeAndSave(TokenType::THIN, token) ||\n      ConsumeAndSave(TokenType::MEDIUM, token) ||\n      ConsumeAndSave(TokenType::THICK, token)) {\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::BorderStyleIdent(Token &token) {\n  SkipWhitespaceToken();\n  // hidden, dotted, dashed, solid, double, groove, ridge, inset, outset, none\n  if ((current_token_.type >= TokenType::HIDDEN &&\n       current_token_.type <= TokenType::OUTSET) ||\n      current_token_.type == TokenType::NONE) {\n    token = current_token_;\n    Advance();\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::TransformFunctionIdent(Token &token) {\n  SkipWhitespaceToken();\n  if ((current_token_.type >= TokenType::ROTATE &&\n       current_token_.type <= TokenType::MATRIX_3D)) {\n    token = current_token_;\n    Advance();\n    return true;\n  }\n  return false;\n}\n\nvoid CSSStringParser::ConsumeColor(CSSValue &result) {\n  if (Color()) {\n    StackValue stack_value = PopValue();\n    if (stack_value.value_type == TokenType::NUMBER) {\n      result.SetValueAndPattern(*stack_value.value, CSSValuePattern::NUMBER);\n    }\n  } else {\n    result = CSSValue();\n  }\n}\n\nbool CSSStringParser::ShadowOptionIdent(Token &token) {\n  if (ConsumeAndSave(TokenType::INSET, token)) {\n    return true;\n  }\n  return false;\n}\n\ntemplate <typename Func, typename... Args>\nCSSValue CSSStringParser::ConsumeCommaSeparatedList(Func callback,\n                                                    Args &&...args) {\n  auto list = lepus::CArray::Create();\n  do {\n    lepus::Value value = (this->*callback)(std::forward<Args>(args)...);\n    if (value.IsEmpty()) {\n      return CSSValue();\n    }\n    list->emplace_back(std::move(value));\n  } while (Consume(TokenType::COMMA));\n  if (!AtEnd()) {\n    return CSSValue();\n  }\n  return CSSValue(std::move(list));\n}\n\nbool CSSStringParser::Color() {\n  if (CheckAndAdvance(TokenType::RGBA)) {\n    return RGBAColor();\n  } else if (CheckAndAdvance(TokenType::RGB)) {\n    return RGBColor();\n  } else if (CheckAndAdvance(TokenType::HSLA)) {\n    return HSLAColor();\n  } else if (CheckAndAdvance(TokenType::HSL)) {\n    return HSLColor();\n  } else if (Check(TokenType::HEX)) {\n    return HexColor();\n  } else if (CSSColor::IsColorIdentifier(current_token_.type)) {\n    auto color = CSSColor::CreateFromKeyword(current_token_.type);\n    PushValue(StackValue(TokenType::NUMBER, color.Cast()));\n    Advance();\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::RGBAColor() {\n  // save RGBA prefix\n  Token rgba[5] = {[0] = previous_token_};\n  if (Consume(TokenType::LEFT_PAREN)      // (\n      && NumberOrPercentValue(rgba[1])    // Number 1\n      && Consume(TokenType::COMMA)        // ,\n      && NumberOrPercentValue(rgba[2])    // Number 2\n      && Consume(TokenType::COMMA)        // ,\n      && NumberOrPercentValue(rgba[3])    // Number 3\n      && Consume(TokenType::COMMA)        // ,\n      && NumberOrPercentValue(rgba[4])    // Alpha\n      && Consume(TokenType::RIGHT_PAREN)  // )\n  ) {\n    PushValue(MakeColorValue(rgba));\n    return true;\n  } else {\n    return false;\n  }\n}\n\nbool CSSStringParser::RGBColor() {\n  // 'rgb(' has been consumed, and previous_token_ is 'rgb'.\n  if (!Consume(TokenType::LEFT_PAREN)) {\n    return false;\n  }\n\n  Token r_token, g_token, b_token, a_token;\n  bool r_is_none = false, g_is_none = false, b_is_none = false,\n       a_is_none = false;\n  bool has_alpha = false;\n\n  // We need to decide if it's legacy or modern syntax.\n  // A simple way is to parse the first value, and then check for a comma.\n  if (Consume(TokenType::NONE)) {\n    r_is_none = true;\n  } else if (!NumberOrPercentValue(r_token)) {\n    return false;\n  }\n\n  bool is_legacy = Check(TokenType::COMMA);\n\n  if (is_legacy) {\n    if (r_is_none) return false;  // 'none' not allowed in legacy rgb\n    Consume(TokenType::COMMA);\n    if (!NumberOrPercentValue(g_token)) return false;\n    Consume(TokenType::COMMA);\n    if (!NumberOrPercentValue(b_token)) return false;\n\n    if (Check(TokenType::COMMA)) {\n      Consume(TokenType::COMMA);\n      has_alpha = true;\n      if (!NumberOrPercentValue(a_token)) return false;\n    }\n  } else {  // Modern syntax\n    if (Consume(TokenType::NONE)) {\n      g_is_none = true;\n    } else if (!NumberOrPercentValue(g_token)) {\n      return false;\n    }\n\n    if (Consume(TokenType::NONE)) {\n      b_is_none = true;\n    } else if (!NumberOrPercentValue(b_token)) {\n      return false;\n    }\n\n    if (Consume(TokenType::SLASH)) {\n      has_alpha = true;\n      if (Consume(TokenType::NONE)) {\n        a_is_none = true;\n      } else if (!NumberOrPercentValue(a_token)) {\n        return false;\n      }\n    }\n  }\n\n  if (!Consume(TokenType::RIGHT_PAREN)) {\n    return false;\n  }\n\n  auto get_color_value = [](const Token &token, bool is_none) {\n    if (is_none) return 0.0;\n    if (token.type == TokenType::PERCENTAGE) {\n      return TokenToDouble(token) / 100.0 * 255.0;\n    }\n    return TokenToDouble(token);\n  };\n\n  auto get_alpha_value = [](const Token &token, bool is_none) {\n    if (is_none) return 1.0;\n    if (token.type == TokenType::PERCENTAGE) {\n      return TokenToDouble(token) / 100.0;\n    }\n    return TokenToDouble(token);\n  };\n\n  double r_val = get_color_value(r_token, r_is_none);\n  double g_val = get_color_value(g_token, g_is_none);\n  double b_val = get_color_value(b_token, b_is_none);\n  double a_val = has_alpha ? get_alpha_value(a_token, a_is_none) : 1.0;\n\n  CSSColor color = CSSColor::CreateFromRGBA(r_val, g_val, b_val, a_val);\n  PushValue(StackValue(TokenType::NUMBER, color.Cast()));\n  return true;\n}\n\nbool CSSStringParser::HSLAColor() {\n  // save hsla prefix\n  Token hsla[5] = {[0] = previous_token_};\n  if (Consume(TokenType::LEFT_PAREN)      // (\n      && NumberValue(hsla[1])             // hue\n      && Consume(TokenType::COMMA)        // ,\n      && PercentageValue(hsla[2])         // percentage\n      && Consume(TokenType::COMMA)        // ,\n      && PercentageValue(hsla[3])         // percentage\n      && Consume(TokenType::COMMA)        // ,\n      && NumberOrPercentValue(hsla[4])    // alpha\n      && Consume(TokenType::RIGHT_PAREN)  // )\n  ) {\n    PushValue(MakeColorValue(hsla));\n    return true;\n  } else {\n    return false;\n  }\n}\n\nbool CSSStringParser::HSLColor() {\n  Token hsl[4] = {[0] = previous_token_};\n\n  if (Consume(TokenType::LEFT_PAREN)      // (\n      && NumberValue(hsl[1])              // hue\n      && Consume(TokenType::COMMA)        // ,\n      && PercentageValue(hsl[2])          // percentage\n      && Consume(TokenType::COMMA)        // ,\n      && PercentageValue(hsl[3])          // percentage\n      && Consume(TokenType::RIGHT_PAREN)  // )\n  ) {\n    PushValue(MakeColorValue(hsl));\n    return true;\n  } else {\n    return false;\n  }\n}\n\nbool CSSStringParser::HexColor() {\n  Token hex_token[1];\n  // number\n  if (HexValue(hex_token[0])) {\n    auto color = MakeColorValue(hex_token);\n    if (color.value_type == TokenType::ERROR) {\n      return false;\n    }\n    PushValue(color);\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::NumberOrPercentValue(Token &token) {\n  if (NumberValue(token)) {\n    return true;\n  }\n  if (DimensionValue(token) && token.unit == TokenType::PERCENTAGE) {\n    token.type = TokenType::PERCENTAGE;\n    return true;\n  }\n\n  return false;\n}\n\nbool CSSStringParser::HexValue(Token &token) {\n  return ConsumeAndSave(TokenType::HEX, token);\n}\n\nbool CSSStringParser::LengthOrPercentageValue(Token &token) {\n  if (ConsumeAndSave(TokenType::CALC, token) ||\n      ConsumeAndSave(TokenType::ENV, token) ||\n      ConsumeAndSave(TokenType::FIT_CONTENT, token) ||\n      ConsumeAndSave(TokenType::MAX_CONTENT, token) ||\n      ConsumeAndSave(TokenType::AUTO, token)) {\n    return true;\n  }\n  if (ConsumeAndSave(TokenType::NUMBER, token)) {\n    if (token.IsZero()) {\n      return true;\n    }\n    // For compatibility, we use numbers as valid length\n    if (Check(TokenType::TOKEN_EOF)) {\n      return true;\n    }\n    // engine version >= 2.6\n    if (parser_configs_.enable_length_unit_check) {\n      return false;\n    }\n    // If the next char is white space, comma or slash, can be\n    // resolved to a valid length value, for compatibility\n    const char *next = token.start + token.length;\n    if (next[0] == ' ' || next[0] == '/' || next[0] == ',') {\n      token.type = TokenType::NUMBER;\n      return true;\n    }\n    return false;\n  }\n\n  if (ConsumeAndSave(TokenType::DIMENSION, token) &&\n      ((token.unit >= TokenType::PX && token.unit <= TokenType::SP) ||\n       token.unit == TokenType::PERCENTAGE)) {\n    token.type = token.unit;\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::PercentageValue(Token &token) {\n  if (DimensionValue(token) && token.unit == TokenType::PERCENTAGE) {\n    token.type = TokenType::PERCENTAGE;\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::DimensionValue(Token &token) {\n  return ConsumeAndSave(TokenType::DIMENSION, token);\n}\n\nbool CSSStringParser::NumberValue(Token &token) {\n  return ConsumeAndSave(TokenType::NUMBER, token);\n}\n\nvoid CSSStringParser::PushValue(const StackValue &value) {\n  if (stack_value_.has_value) {\n    UnitHandler::CSSUnreachable(true, \"PushValue has value\");\n  }\n  stack_value_ = value;\n  stack_value_.has_value = true;\n}\n\nvoid CSSStringParser::PushValue(StackValue &&value) {\n  if (stack_value_.has_value) {\n    UnitHandler::CSSUnreachable(true, \"PushValue has value\");\n  }\n  stack_value_ = std::move(value);\n  stack_value_.has_value = true;\n}\n\nconst CSSStringParser::StackValue &CSSStringParser::PopValue() {\n  if (!stack_value_.has_value) {\n    UnitHandler::CSSUnreachable(true, \"PopValue has no value\");\n  }\n  stack_value_.has_value = false;\n  return stack_value_;\n}\n\nbool CSSStringParser::CheckAndAdvance(TokenType type) {\n  if (!Check(type)) {\n    return false;\n  }\n  Advance();\n  return current_token_.type != TokenType::ERROR;\n}\n\nvoid CSSStringParser::SkipWhitespaceToken() {\n  if (current_token_.type == TokenType::WHITESPACE) {\n    Advance();\n  }\n}\n\nbool CSSStringParser::Consume(TokenType type) {\n  SkipWhitespaceToken();\n  if (current_token_.type == type) {\n    Advance();\n    return current_token_.type != TokenType::ERROR;\n  }\n  return false;\n}\n\nbool CSSStringParser::ConsumeAndSave(TokenType type, Token &token) {\n  if (Consume(type)) {\n    token = previous_token_;\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::Check(TokenType type) {\n  SkipWhitespaceToken();\n  return current_token_.type == type;\n}\n\nvoid CSSStringParser::Advance() {\n  previous_token_ = current_token_;\n  current_token_ = scanner_.ScanToken();\n}\n\nuint32_t CSSStringParser::TokenTypeToTextENUM(TokenType token_type) {\n  switch (token_type) {\n    case TokenType::NONE:\n      return static_cast<uint32_t>(starlight::TextDecorationType::kNone);\n    case TokenType::UNDERLINE:\n      return static_cast<uint32_t>(starlight::TextDecorationType::kUnderLine);\n    case TokenType::LINE_THROUGH:\n      return static_cast<uint32_t>(starlight::TextDecorationType::kLineThrough);\n    case TokenType::SOLID:\n      return static_cast<uint32_t>(starlight::TextDecorationType::kSolid);\n    case TokenType::DOUBLE:\n      return static_cast<uint32_t>(starlight::TextDecorationType::kDouble);\n    case TokenType::DOTTED:\n      return static_cast<uint32_t>(starlight::TextDecorationType::kDotted);\n    case TokenType::DASHED:\n      return static_cast<uint32_t>(starlight::TextDecorationType::kDashed);\n    case TokenType::WAVY:\n      return static_cast<uint32_t>(starlight::TextDecorationType::kWavy);\n    default:\n      return -1;\n  }\n}\n\nuint32_t CSSStringParser::TokenTypeToENUM(TokenType token_type) {\n  switch (token_type) {\n    case TokenType::NUMBER:\n      return static_cast<uint32_t>(CSSValuePattern::NUMBER);\n    case TokenType::URL:\n      return static_cast<uint32_t>(starlight::BackgroundImageType::kUrl);\n    case TokenType::LINEAR_GRADIENT:\n      return static_cast<uint32_t>(\n          starlight::BackgroundImageType::kLinearGradient);\n    case TokenType::RADIAL_GRADIENT:\n      return static_cast<uint32_t>(\n          starlight::BackgroundImageType::kRadialGradient);\n    case TokenType::CONIC_GRADIENT:\n      return static_cast<uint32_t>(\n          starlight::BackgroundImageType::kConicGradient);\n    case TokenType::ELLIPSE:\n      return static_cast<uint32_t>(\n          starlight::RadialGradientShapeType::kEllipse);\n    case TokenType::CIRCLE:\n      return static_cast<uint32_t>(starlight::RadialGradientShapeType::kCircle);\n    case TokenType::FARTHEST_CORNER:\n      return static_cast<uint32_t>(\n          starlight::RadialGradientSizeType::kFarthestCorner);\n    case TokenType::FARTHEST_SIDE:\n      return static_cast<uint32_t>(\n          starlight::RadialGradientSizeType::kFarthestSide);\n    case TokenType::CLOSEST_CORNER:\n      return static_cast<uint32_t>(\n          starlight::RadialGradientSizeType::kClosestCorner);\n    case TokenType::CLOSEST_SIDE:\n      return static_cast<uint32_t>(\n          starlight::RadialGradientSizeType::kClosestSide);\n    case TokenType::BORDER_BOX:\n      return static_cast<uint32_t>(starlight::BackgroundOriginType::kBorderBox);\n    case TokenType::PADDING_BOX:\n      return static_cast<uint32_t>(\n          starlight::BackgroundOriginType::kPaddingBox);\n    case TokenType::CONTENT_BOX:\n      return static_cast<uint32_t>(\n          starlight::BackgroundOriginType::kContentBox);\n    case TokenType::TEXT:\n      return static_cast<uint32_t>(starlight::BackgroundClipType::kText);\n    case TokenType::BORDER_AREA:\n      return static_cast<uint32_t>(starlight::BackgroundClipType::kBorderArea);\n    case TokenType::LEFT:\n      return POS_LEFT;\n    case TokenType::RIGHT:\n      return POS_RIGHT;\n    case TokenType::TOP:\n      return POS_TOP;\n    case TokenType::BOTTOM:\n      return POS_BOTTOM;\n    case TokenType::CENTER:\n      return POS_CENTER;\n    case TokenType::PERCENTAGE:\n      return static_cast<uint32_t>(CSSValuePattern::PERCENT);\n    case TokenType::RPX:\n      return static_cast<uint32_t>(CSSValuePattern::RPX);\n    case TokenType::PX:\n      return static_cast<uint32_t>(CSSValuePattern::PX);\n    case TokenType::REM:\n      return static_cast<uint32_t>(CSSValuePattern::REM);\n    case TokenType::EM:\n      return static_cast<uint32_t>(CSSValuePattern::EM);\n    case TokenType::VW:\n      return static_cast<uint32_t>(CSSValuePattern::VW);\n    case TokenType::VH:\n      return static_cast<uint32_t>(CSSValuePattern::VH);\n    case TokenType::PPX:\n      return static_cast<uint32_t>(CSSValuePattern::PPX);\n    case TokenType::FR:\n      return static_cast<uint32_t>(CSSValuePattern::FR);\n    case TokenType::SP:\n      return static_cast<uint32_t>(CSSValuePattern::SP);\n    case TokenType::CALC:\n      return static_cast<uint32_t>(CSSValuePattern::CALC);\n    case TokenType::ENV:\n      return static_cast<uint32_t>(CSSValuePattern::ENV);\n    case TokenType::MAX_CONTENT:\n    case TokenType::FIT_CONTENT:\n      return static_cast<uint32_t>(CSSValuePattern::INTRINSIC);\n    case TokenType::AUTO:\n      return static_cast<uint32_t>(CSSValuePattern::ENUM);\n    case TokenType::REPEAT:\n    case TokenType::REPEAT_X:\n    case TokenType::REPEAT_Y:\n      return static_cast<uint32_t>(starlight::BackgroundRepeatType::kRepeat);\n    case TokenType::NO_REPEAT:\n      return static_cast<uint32_t>(starlight::BackgroundRepeatType::kNoRepeat);\n    case TokenType::SPACE:\n      return static_cast<uint32_t>(starlight::BackgroundRepeatType::kSpace);\n    case TokenType::ROUND:\n      return static_cast<uint32_t>(starlight::BackgroundRepeatType::kRound);\n    case TokenType::COVER:\n      return static_cast<uint32_t>(starlight::BackgroundSizeType::kCover);\n    case TokenType::CONTAIN:\n      return static_cast<uint32_t>(starlight::BackgroundSizeType::kContain);\n    case TokenType::NONE:\n      return static_cast<uint32_t>(starlight::BackgroundImageType::kNone);\n    default:\n      return -1;\n  }\n}\n\n// For compatibility with old type\nstarlight::BorderStyleType CSSStringParser::TokenTypeToBorderStyle(\n    TokenType token_type) {\n  switch (token_type) {\n    case TokenType::HIDDEN:\n      return starlight::BorderStyleType::kHide;\n    case TokenType::DOTTED:\n      return starlight::BorderStyleType::kDotted;\n    case TokenType::DASHED:\n      return starlight::BorderStyleType::kDashed;\n    case TokenType::SOLID:\n      return starlight::BorderStyleType::kSolid;\n    case TokenType::DOUBLE:\n      return starlight::BorderStyleType::kDouble;\n    case TokenType::GROOVE:\n      return starlight::BorderStyleType::kGroove;\n    case TokenType::RIDGE:\n      return starlight::BorderStyleType::kRidge;\n    case TokenType::INSET:\n      return starlight::BorderStyleType::kInset;\n    case TokenType::OUTSET:\n      return starlight::BorderStyleType::kOutset;\n    case TokenType::NONE:\n    default:\n      return starlight::BorderStyleType::kNone;\n  }\n}\n\nuint32_t CSSStringParser::TokenTypeToBorderWidth(TokenType token_type) {\n  switch (token_type) {\n    case TokenType::THIN:\n      return 1;\n    case TokenType::MEDIUM:\n      return 3;\n    case TokenType::THICK:\n      return 5;\n    default:\n      return 0;\n  }\n}\n\nint CSSStringParser::TokenTypeToShadowOption(TokenType token_type) {\n  if (token_type == TokenType::INSET) {\n    return static_cast<int>(starlight::ShadowOption::kInset);\n  }\n  return static_cast<int>(starlight::ShadowOption::kNone);\n}\n\ndouble CSSStringParser::GetColorValue(const Token &token, double max_value) {\n  if (token.type == TokenType::PERCENTAGE) {\n    return TokenToDouble(token) / 100.0 * max_value;\n  }\n  return CSSStringParser::TokenToDouble(token);\n}\n\nCSSStringParser::StackValue CSSStringParser::MakeColorValue(\n    const Token token_list[]) {\n  CSSColor color;\n  switch (token_list[0].type) {\n    case TokenType::RGBA:\n      color = CSSColor::CreateFromRGBA(\n          GetColorValue(token_list[1]), GetColorValue(token_list[2]),\n          GetColorValue(token_list[3]), GetColorValue(token_list[4], 1));\n      break;\n    case TokenType::RGB:\n      color = CSSColor::CreateFromRGBA(GetColorValue(token_list[1]),\n                                       GetColorValue(token_list[2]),\n                                       GetColorValue(token_list[3]), 1.f);\n      break;\n    case TokenType::HSLA:\n      color = CSSColor::CreateFromHSLA(\n          static_cast<float>(TokenToDouble(token_list[1])),\n          static_cast<float>(TokenToDouble(token_list[2])),\n          static_cast<float>(TokenToDouble(token_list[3])),\n          static_cast<float>(TokenToDouble(token_list[4])));\n      break;\n    case TokenType::HSL:\n      color = CSSColor::CreateFromHSLA(\n          static_cast<float>(TokenToDouble(token_list[1])),\n          static_cast<float>(TokenToDouble(token_list[2])),\n          static_cast<float>(TokenToDouble(token_list[3])), 1.f);\n      break;\n    case TokenType::HEX: {\n      std::string str = \"#\";\n      str.append(token_list[0].start, token_list[0].length);\n      if (!CSSColor::Parse(str, color)) {\n        return StackValue(TokenType::ERROR);\n      }\n    } break;\n    default:\n      break;\n  }\n\n  return StackValue(TokenType::NUMBER, color.Cast());\n}\n\nint64_t CSSStringParser::TokenToInt(const Token &token) {\n  int64_t ret = 0;\n  base::StringToInt(std::string(token.start, token.length), ret, 10);\n  return ret;\n}\n\ndouble CSSStringParser::TokenToDouble(const Token &token) {\n  double ret = 0;\n  base::StringToDouble(std::string(token.start, token.length), ret);\n  return ret;\n}\n\nfloat CSSStringParser::TokenToAngleValue(const Token &token) {\n  switch (token.type) {\n    case TokenType::DEG:\n    case TokenType::NUMBER:\n      return TokenToDouble(token);\n    case TokenType::RAD:\n      return TokenToDouble(token) * 180.f / M_PI;\n    case TokenType::TURN:\n      return TokenToDouble(token) * 360.f;\n    case TokenType::GRAD:\n      return TokenToDouble(token) * 360.f / 400.f;\n    default:\n      return 0.f;\n  }\n}\n\ndouble CSSStringParser::TimeToNumber(const Token &token) {\n  return token.type == TokenType::SECOND ? TokenToDouble(token) * 1000\n                                         : TokenToDouble(token);\n}\n\nstarlight::TransformType CSSStringParser::TokenToTransformFunction(\n    const Token &token) {\n  switch (token.type) {\n    case TokenType::ROTATE:\n      return starlight::TransformType::kRotate;\n    case TokenType::ROTATE_X:\n      return starlight::TransformType::kRotateX;\n    case TokenType::ROTATE_Y:\n      return starlight::TransformType::kRotateY;\n    case TokenType::ROTATE_Z:\n      return starlight::TransformType::kRotateZ;\n    case TokenType::TRANSLATE:\n      return starlight::TransformType::kTranslate;\n    case TokenType::TRANSLATE_3D:\n      return starlight::TransformType::kTranslate3d;\n    case TokenType::TRANSLATE_X:\n      return starlight::TransformType::kTranslateX;\n    case TokenType::TRANSLATE_Y:\n      return starlight::TransformType::kTranslateY;\n    case TokenType::TRANSLATE_Z:\n      return starlight::TransformType::kTranslateZ;\n    case TokenType::SCALE:\n      return starlight::TransformType::kScale;\n    case TokenType::SCALE_X:\n      return starlight::TransformType::kScaleX;\n    case TokenType::SCALE_Y:\n      return starlight::TransformType::kScaleY;\n    case TokenType::SKEW:\n      return starlight::TransformType::kSkew;\n    case TokenType::SKEW_X:\n      return starlight::TransformType::kSkewX;\n    case TokenType::SKEW_Y:\n      return starlight::TransformType::kSkewY;\n    case TokenType::MATRIX:\n      return starlight::TransformType::kMatrix;\n    case TokenType::MATRIX_3D:\n      return starlight::TransformType::kMatrix3d;\n    default:\n      return starlight::TransformType::kNone;\n  }\n}\n\nstarlight::AnimationPropertyType CSSStringParser::TokenToTransitionType(\n    const Token &token, const CSSParserConfigs &configs) {\n  switch (token.type) {\n    case TokenType::NONE:\n      return starlight::AnimationPropertyType::kNone;\n    case TokenType::OPACITY:\n      return starlight::AnimationPropertyType::kOpacity;\n    case TokenType::SCALE_X:\n      return starlight::AnimationPropertyType::kScaleX;\n    case TokenType::SCALE_Y:\n      return starlight::AnimationPropertyType::kScaleY;\n    case TokenType::SCALE_XY:\n      return starlight::AnimationPropertyType::kScaleXY;\n    case TokenType::WIDTH:\n      return starlight::AnimationPropertyType::kWidth;\n    case TokenType::HEIGHT:\n      return starlight::AnimationPropertyType::kHeight;\n    case TokenType::BACKGROUND_COLOR:\n      return starlight::AnimationPropertyType::kBackgroundColor;\n    case TokenType::COLOR:\n      return starlight::AnimationPropertyType::kColor;\n    case TokenType::VISIBILITY:\n      return starlight::AnimationPropertyType::kVisibility;\n    case TokenType::LEFT:\n      return starlight::AnimationPropertyType::kLeft;\n    case TokenType::TOP:\n      return starlight::AnimationPropertyType::kTop;\n    case TokenType::RIGHT:\n      return starlight::AnimationPropertyType::kRight;\n    case TokenType::BOTTOM:\n      return starlight::AnimationPropertyType::kBottom;\n    case TokenType::TRANSFORM:\n      return starlight::AnimationPropertyType::kTransform;\n    case TokenType::ALL:\n      return starlight::AnimationPropertyType::kAll;\n    case TokenType::MAX_WIDTH:\n      return starlight::AnimationPropertyType::kMaxWidth;\n    case TokenType::MAX_HEIGHT:\n      return starlight::AnimationPropertyType::kMaxHeight;\n    case TokenType::MIN_WIDTH:\n      return starlight::AnimationPropertyType::kMinWidth;\n    case TokenType::MIN_HEIGHT:\n      return starlight::AnimationPropertyType::kMinHeight;\n    case TokenType::PADDING_LEFT:\n      return starlight::AnimationPropertyType::kPaddingLeft;\n    case TokenType::PADDING_RIGHT:\n      return starlight::AnimationPropertyType::kPaddingRight;\n    case TokenType::PADDING_TOP:\n      return starlight::AnimationPropertyType::kPaddingTop;\n    case TokenType::PADDING_BOTTOM:\n      return starlight::AnimationPropertyType::kPaddingBottom;\n    case TokenType::MARGIN_LEFT:\n      return starlight::AnimationPropertyType::kMarginLeft;\n    case TokenType::MARGIN_RIGHT:\n      return starlight::AnimationPropertyType::kMarginRight;\n    case TokenType::MARGIN_TOP:\n      return starlight::AnimationPropertyType::kMarginTop;\n    case TokenType::MARGIN_BOTTOM:\n      return starlight::AnimationPropertyType::kMarginBottom;\n    case TokenType::BORDER_LEFT_COLOR:\n      return starlight::AnimationPropertyType::kBorderLeftColor;\n    case TokenType::BORDER_RIGHT_COLOR:\n      return starlight::AnimationPropertyType::kBorderRightColor;\n    case TokenType::BORDER_TOP_COLOR:\n      return starlight::AnimationPropertyType::kBorderTopColor;\n    case TokenType::BORDER_BOTTOM_COLOR:\n      return starlight::AnimationPropertyType::kBorderBottomColor;\n    case TokenType::BORDER_LEFT_WIDTH:\n      return starlight::AnimationPropertyType::kBorderLeftWidth;\n    case TokenType::BORDER_RIGHT_WIDTH:\n      return starlight::AnimationPropertyType::kBorderRightWidth;\n    case TokenType::BORDER_TOP_WIDTH:\n      return starlight::AnimationPropertyType::kBorderTopWidth;\n    case TokenType::BORDER_BOTTOM_WIDTH:\n      return starlight::AnimationPropertyType::kBorderBottomWidth;\n    case TokenType::FLEX_BASIS:\n      return starlight::AnimationPropertyType::kFlexBasis;\n    case TokenType::FLEX_GROW:\n      return starlight::AnimationPropertyType::kFlexGrow;\n    case TokenType::BORDER_WIDTH:\n      return starlight::AnimationPropertyType::kBorderWidth;\n    case TokenType::BORDER_COLOR:\n      return starlight::AnimationPropertyType::kBorderColor;\n    case TokenType::MARGIN:\n      return starlight::AnimationPropertyType::kMargin;\n    case TokenType::PADDING:\n      return starlight::AnimationPropertyType::kPadding;\n    case TokenType::FILTER:\n      return starlight::AnimationPropertyType::kFilter;\n    case TokenType::OFFSET_DISTANCE:\n      return starlight::AnimationPropertyType::kOffsetDistance;\n    case TokenType::BACKGROUND_POSITION:\n      return starlight::AnimationPropertyType::kBackgroundPosition;\n    case TokenType::TRANSFORM_ORIGIN:\n      return starlight::AnimationPropertyType::kTransformOrigin;\n    default:\n      UnitHandler::CSSWarning(false, configs.enable_css_strict_mode,\n                              \"Unsupported value: %s in transition-property \"\n                              \"will be set to none!\",\n                              std::string(token.start, token.length).c_str());\n      return starlight::AnimationPropertyType::kNone;\n  }\n}\n\nstarlight::TimingFunctionType CSSStringParser::TokenToTimingFunctionType(\n    const Token &token) {\n  switch (token.type) {\n    case TokenType::LINEAR:\n      return starlight::TimingFunctionType::kLinear;\n    case TokenType::EASE_IN:\n      return starlight::TimingFunctionType::kEaseIn;\n    case TokenType::EASE_OUT:\n      return starlight::TimingFunctionType::kEaseOut;\n    case TokenType::EASE:\n    case TokenType::EASE_IN_EASE_OUT:\n    case TokenType::EASE_IN_OUT:\n      return starlight::TimingFunctionType::kEaseInEaseOut;\n    case TokenType::SQUARE_BEZIER:\n      return starlight::TimingFunctionType::kSquareBezier;\n    case TokenType::CUBIC_BEZIER:\n      return starlight::TimingFunctionType::kCubicBezier;\n    case TokenType::STEP_START:\n    case TokenType::STEP_END:\n    case TokenType::STEPS:\n      return starlight::TimingFunctionType::kSteps;\n    default:\n      return starlight::TimingFunctionType::kLinear;\n  }\n}\n\nvoid CSSStringParser::BackgroundLayerToArray(const CSSBackgroundLayer &layer,\n                                             lepus::CArray *image_array,\n                                             lepus::CArray *position_array,\n                                             lepus::CArray *size_array,\n                                             lepus::CArray *origin_array,\n                                             lepus::CArray *repeat_array,\n                                             lepus::CArray *clip_array) {\n  image_array->emplace_back(TokenTypeToENUM(layer.image->value_type));\n  if (layer.image->value) {\n    image_array->emplace_back(*layer.image->value);\n  }\n\n  // position\n  {\n    auto array = lepus::CArray::Create();\n    PositionAddLegacyValue(array, layer.position_x);\n    PositionAddLegacyValue(array, layer.position_y);\n    position_array->emplace_back(std::move(array));\n  }\n  // size\n  {\n    auto array = lepus::CArray::Create();\n    SizeAddLegacyValue(array, layer.size_x);\n    SizeAddLegacyValue(array, layer.size_y);\n    size_array->emplace_back(std::move(array));\n  }\n\n  // repeat\n  {\n    auto array = lepus::CArray::Create();\n    const auto vx = layer.repeat_x;\n    const auto vy = layer.repeat_y;\n\n    array->emplace_back(vx);\n    array->emplace_back(vy);\n\n    repeat_array->emplace_back(std::move(array));\n  }\n\n  // origin\n  origin_array->emplace_back(layer.origin);\n\n  // clip\n  clip_array->emplace_back(layer.clip);\n}\n\nvoid CSSStringParser::ClampColorAndStopList(base::Vector<uint32_t> &colors,\n                                            base::Vector<float> &stops) {\n  if (stops.size() < 2) {\n    return;\n  }\n  bool clamp_front = stops.front() < 0.f;\n  bool clamp_back = stops.back() > 100.f;\n\n  if (!clamp_front && !clamp_back) {\n    return;\n  }\n\n  if (clamp_front) {\n    // find first positive position\n    uint32_t first_positive_index = 0;\n    auto result = std::find_if(stops.begin(), stops.end(),\n                               [](float v) { return v >= 0.f; });\n    if (result != stops.end()) {\n      first_positive_index = static_cast<uint32_t>(result - stops.begin());\n    }\n\n    if (first_positive_index != 0) {\n      ClampColorAndStopListAtFront(colors, stops, first_positive_index);\n    }\n  }\n\n  if (clamp_back) {\n    // find fist greater than 100.f position\n    uint32_t tail_position = 0;\n    auto result = std::find_if(stops.begin(), stops.end(),\n                               [](float v) { return v >= 100.f; });\n    if (result != stops.end()) {\n      tail_position = static_cast<uint32_t>(result - stops.begin());\n    }\n    if (tail_position != 0) {\n      ClampColorAndStopListAtBack(colors, stops, tail_position);\n    }\n  }\n}\n\nvoid CSSStringParser::ClampColorAndStopListAtFront(\n    base::Vector<uint32_t> &colors, base::Vector<float> &stops,\n    uint32_t first_positive_index) {\n  float prev_stop = stops[first_positive_index - 1];\n  uint32_t prev_color = colors[first_positive_index - 1];\n\n  float current_stop = stops[first_positive_index];\n  uint32_t current_color = colors[first_positive_index];\n\n  uint32_t result_color =\n      LerpColor(prev_color, current_color, prev_stop, current_stop, 0.f);\n  // update prev content\n  stops[first_positive_index - 1] = 0.f;\n  colors[first_positive_index - 1] = result_color;\n\n  // remove all other negative stops and colors\n  if (first_positive_index - 1 > 0) {\n    stops.erase(stops.begin(), stops.begin() + first_positive_index - 1);\n    colors.erase(colors.begin(), colors.begin() + first_positive_index - 1);\n  }\n}\n\nvoid CSSStringParser::ClampColorAndStopListAtBack(\n    base::Vector<uint32_t> &colors, base::Vector<float> &stops,\n    uint32_t tail_position) {\n  float prev_stop = stops[tail_position - 1];\n  uint32_t prev_color = colors[tail_position - 1];\n\n  float current_stop = stops[tail_position];\n  uint32_t current_color = colors[tail_position];\n\n  uint32_t result_color =\n      LerpColor(prev_color, current_color, prev_stop, current_stop, 100.f);\n  // update tail content\n  stops[tail_position] = 100.f;\n  colors[tail_position] = result_color;\n\n  // remote all other greater than 100% stops and colors\n  if (tail_position + 1 < stops.size()) {\n    stops.erase(stops.begin() + tail_position + 1, stops.end());\n    colors.erase(colors.begin() + tail_position + 1, colors.end());\n  }\n}\n\nstatic uint8_t ClampColorValue(float x) {\n  return static_cast<uint8_t>(round(x < 0 ? 0 : (x > 255 ? 255 : x)));\n}\n\nuint32_t CSSStringParser::LerpColor(uint32_t start_color, uint32_t end_color,\n                                    float start_pos, float end_pos,\n                                    float current_pos) {\n  float weight = (current_pos - start_pos) / (end_pos - start_pos);\n\n  int a1 = (start_color >> 24) & 0xFF;\n  int b1 = (start_color >> 16) & 0xFF;\n  int c1 = (start_color >> 8) & 0xFF;\n  int d1 = (start_color) & 0xFF;\n\n  int a2 = (end_color >> 24) & 0xFF;\n  int b2 = (end_color >> 16) & 0xFF;\n  int c2 = (end_color >> 8) & 0xFF;\n  int d2 = (end_color) & 0xFF;\n\n  uint8_t a = ClampColorValue(a1 + (a2 - a1) * weight);\n  uint8_t b = ClampColorValue(b1 + (b2 - b1) * weight);\n  uint8_t c = ClampColorValue(c1 + (c2 - c1) * weight);\n  uint8_t d = ClampColorValue(d1 + (d2 - d1) * weight);\n\n  return ((a << 24) & 0xffffffff) | ((b << 16) & 0xffffffff) |\n         ((c << 8) & 0xffffffff) | (d & 0xffffffff);\n}\n\nbool CSSStringParser::BasicShapeCircle() {\n  if (!Consume(TokenType::CIRCLE) || !Consume(TokenType::LEFT_PAREN)) {\n    return false;\n  }\n  auto arr = lepus::CArray::Create();\n\n  constexpr uint32_t BASIC_SHAPE_CIRCLE_TYPE =\n      static_cast<uint32_t>(starlight::BasicShapeType::kCircle);\n  arr->emplace_back(BASIC_SHAPE_CIRCLE_TYPE);\n\n  // Radius is required\n  if (!ConsumeLengthAndSetValue(arr)) {\n    return false;\n  }\n\n  // position is optional\n  if (Check(TokenType::RIGHT_PAREN)) {\n    // default center x\n    arr->emplace_back(50);\n    arr->emplace_back(PATTERN_PERCENT);\n\n    // default center y\n    arr->emplace_back(50);\n    arr->emplace_back(PATTERN_PERCENT);\n  } else if (!AtPositionAndSetValue(arr)) {\n    // parse [<position>]? failed\n    return false;\n  }\n\n  PushValue(StackValue(TokenType::CIRCLE, std::move(arr)));\n  return true;\n}\n\nbool CSSStringParser::AtPositionAndSetValue(fml::RefPtr<lepus::CArray> &arr) {\n  if (!Consume(TokenType::AT)) {\n    return false;\n  }\n  return ConsumePositionAndSetValue(arr);\n}\n\nbool CSSStringParser::ConsumePositionAndSetValue(\n    fml::RefPtr<lepus::CArray> &arr) {\n  CSSValue pos_x;\n  CSSValue pos_y;\n  if (!BackgroundPosition(pos_x, pos_y)) {\n    return false;\n  }\n  return PositionAddValue(arr, pos_x) && PositionAddValue(arr, pos_y);\n}\n\nbool CSSStringParser::BasicShapeEllipse() {\n  if (!Consume(TokenType::ELLIPSE) || !Consume(TokenType::LEFT_PAREN)) {\n    return false;\n  }\n  auto arr = lepus::CArray::Create();\n\n  constexpr uint32_t BASIC_SHAPE_ELLIPSE_TYPE =\n      static_cast<uint32_t>(starlight::BasicShapeType::kEllipse);\n  arr->emplace_back(BASIC_SHAPE_ELLIPSE_TYPE);\n\n  // radius is required.\n  if (!ConsumeLengthAndSetValue(arr)) {\n    return false;\n  }\n\n  if (!ConsumeLengthAndSetValue(arr)) {\n    return false;\n  }\n\n  if (Check(TokenType::RIGHT_PAREN)) {\n    // [at <position>] is optional, use default value.\n    // default center x\n    arr->emplace_back(50);\n    arr->emplace_back(PATTERN_PERCENT);\n\n    // default center y\n    arr->emplace_back(50);\n    arr->emplace_back(PATTERN_PERCENT);\n  } else if (!AtPositionAndSetValue(arr)) {\n    // function not end, but parse position failed, return false.\n    return false;\n  }\n\n  PushValue(StackValue{TokenType::ELLIPSE, std::move(arr)});\n  return true;\n}\n\nbool CSSStringParser::ConsumeLengthAndSetValue(\n    fml::RefPtr<lepus::CArray> &arr) {\n  CSSValue value = Length();\n  if (value.IsEmpty()) {\n    return false;\n  }\n  arr->emplace_back(value.GetValue());\n  arr->emplace_back(static_cast<int>(value.GetPattern()));\n  return true;\n}\n\nbool CSSStringParser::BasicShapePath() {\n  // path()\n  if (!Consume(TokenType::PATH) || !Consume(TokenType::LEFT_PAREN)) {\n    return false;\n  }\n  // svg path data string\n  if (!Consume(TokenType::STRING)) {\n    return false;\n  }\n  std::string path_data{previous_token_.start, previous_token_.length};\n  auto arr = lepus::CArray::Create();\n\n  constexpr uint32_t BASIC_SHAPE_PATH_TYPE =\n      static_cast<uint32_t>(starlight::BasicShapeType::kPath);\n  arr->emplace_back(BASIC_SHAPE_PATH_TYPE);\n\n  arr->emplace_back(path_data);\n  PushValue(StackValue(TokenType::PATH, std::move(arr)));\n  return true;\n}\n\nbool CSSStringParser::SuperEllipse() {\n  // Begin with 'super-ellipse('\n  if (!Consume(TokenType::SUPER_ELLIPSE) || !Consume(TokenType::LEFT_PAREN)) {\n    return false;\n  }\n  auto arr = lepus::CArray::Create();\n\n  // append type enum\n  constexpr uint32_t SUPER_ELLIPSE_TYPE =\n      static_cast<uint32_t>(starlight::BasicShapeType::kSuperEllipse);\n  arr->emplace_back(SUPER_ELLIPSE_TYPE);\n\n  // [<shape-radius>{2}] are required\n  // parse radius x\n  if (!ConsumeLengthAndSetValue(arr)) {\n    return false;\n  }\n\n  // parse radius y\n  if (!ConsumeLengthAndSetValue(arr)) {\n    return false;\n  }\n\n  if (Check(TokenType::AT) || Check(TokenType::RIGHT_PAREN)) {\n    // [<number>{2}]?  is optional, [at] means use default exponent\n    // default exponent is 2\n    arr->emplace_back(2);\n    arr->emplace_back(2);\n\n    // [at <position>]? is optional, append default position\n    if (Check(TokenType::RIGHT_PAREN)) {\n      // default center x\n      arr->emplace_back(50);\n      arr->emplace_back(static_cast<uint32_t>(CSSValuePattern::PERCENT));\n\n      // default center y\n      arr->emplace_back(50);\n      arr->emplace_back(static_cast<uint32_t>(CSSValuePattern::PERCENT));\n      // parse finished\n    } else if (!AtPositionAndSetValue(arr)) {\n      // params not end but parse [at <position>] failed\n      return false;\n    }\n  } else if (Check(TokenType::NUMBER)) {\n    Token token;\n    if (!ConsumeAndSave(TokenType::NUMBER, token) ||\n        !Consume(TokenType::NUMBER)) {\n      // [<number>{2}] parse failed\n      return false;\n    }\n\n    // append exponent x and y\n    arr->emplace_back(TokenToDouble(token));\n    arr->emplace_back(TokenToDouble(previous_token_));\n\n    if (Check(TokenType::RIGHT_PAREN)) {\n      // default center x, center\n      arr->emplace_back(50);\n      arr->emplace_back(PATTERN_PERCENT);\n\n      // default center y, center\n      arr->emplace_back(50);\n      arr->emplace_back(PATTERN_PERCENT);\n    } else if (!AtPositionAndSetValue(arr)) {\n      return false;\n    }\n  }\n\n  // Parse finished\n  PushValue(StackValue(TokenType::ELLIPSE, std::move(arr)));\n  return true;\n}\n\nbool CSSStringParser::ParseVarReference(VarReference &ref) {\n  // Parse variable name - should be the first argument\n  Advance();\n  SkipWhitespaceToken();\n  if (current_token_.IsIdent()) {\n    // Record the position of the variable name in the original string\n    ref.name_start = current_token_.start - scanner_.content();\n    ref.name_end = ref.name_start + current_token_.length;\n    Advance();\n  } else {\n    return false;  // Invalid variable name\n  }\n  // Check for fallback value after comma\n  if (Consume(TokenType::COMMA)) {\n    // Collect everything until the closing parenthesis as fallback\n    // The previous_token_ is the comma, so we start after it.\n    const char *fallback_start = previous_token_.start + previous_token_.length;\n\n    while (!Check(TokenType::TOKEN_EOF)) {\n      Advance();\n    }\n    ref.fallback = base::String(\n        fallback_start,\n        current_token_.start + current_token_.length - fallback_start);\n  }\n  return Check(TokenType::TOKEN_EOF);\n}\n\nCSSValue CSSStringParser::ParseVariable() {\n  Advance();\n\n  if (scanner_.Length() == 0) {\n    return CSSValue();\n  }\n\n  // Create a CSSValue to store the result\n  CSSValue result(base::String(scanner_.content(), scanner_.Length()),\n                  CSSValuePattern::STRING, CSSValueType::DEFAULT);\n\n  base::Vector<VarReference> references;\n  Token token;\n\n  while (!Check(TokenType::TOKEN_EOF)) {\n    // Look for var() function calls\n    if (ConsumeAndSave(TokenType::VAR, token)) {\n      VarReference ref;\n      // Record the position of 'var(' in the original string\n      ref.start = token.start - scanner_.content() - /*var(*/ 4;\n      ref.end = ref.start + token.length + /*var()*/ 5;\n      CSSStringParser args_parser_{token.start, token.length,\n                                   this->parser_configs_};\n\n      if (args_parser_.ParseVarReference(ref)) {\n        ref.parser_configs = parser_configs_;\n        // Add reference to the vector\n        references.push_back(std::move(ref));\n      }\n    }\n    Advance();\n  }\n\n  if (HasMetVarToken()) {\n    result.SetVarReferences(std::move(references));\n  }\n  return result;\n}\n\nCSSValue CSSStringParser::ParseFilter() {\n  Advance();\n  Token function_token;\n  if (Consume(TokenType::NONE) && Consume(TokenType::TOKEN_EOF)) {\n    // None\n    auto result = lepus::CArray::Create();\n    result->emplace_back(static_cast<uint32_t>(starlight::FilterType::kNone));\n    return CSSValue{std::move(result)};\n  } else if (ConsumeAndSave(TokenType::GRAYSCALE, function_token)) {\n    return FilterValue(function_token, starlight::FilterType::kGrayscale);\n  } else if (ConsumeAndSave(TokenType::BLUR, function_token)) {\n    return FilterValue(function_token, starlight::FilterType::kBlur);\n  } else if (ConsumeAndSave(TokenType::BRIGHTNESS, function_token)) {\n    return FilterValue(function_token, starlight::FilterType::kBrightness);\n  } else if (ConsumeAndSave(TokenType::CONTRAST, function_token)) {\n    return FilterValue(function_token, starlight::FilterType::kContrast);\n  } else if (ConsumeAndSave(TokenType::SATURATE, function_token)) {\n    return FilterValue(function_token, starlight::FilterType::kSaturate);\n  }\n  return CSSValue();\n}\n\nCSSValue CSSStringParser::FilterValue(const Token &function_token,\n                                      starlight::FilterType type) {\n  auto result = lepus::CArray::Create();\n  result->emplace_back(static_cast<uint32_t>(type));\n  CSSStringParser filter_parser = CSSStringParser{\n      function_token.start, function_token.length, this->parser_configs_};\n  CSSValue filter = filter_parser.ParseFilterValue(type);\n  if (!filter.IsEmpty() && Check(TokenType::TOKEN_EOF)) {\n    result->emplace_back(filter.GetValue());\n    result->emplace_back(static_cast<uint32_t>(filter.GetPattern()));\n    return CSSValue(std::move(result));\n  }\n  return CSSValue();\n}\n\nCSSValue CSSStringParser::ParseFilterValue(starlight::FilterType filter_type) {\n  Token token;\n  Advance();\n  if (!ConsumeFilter(token, filter_type) || !Check(TokenType::TOKEN_EOF)) {\n    return CSSValue();\n  }\n  double value = TokenToDouble(token);\n  CSSValuePattern pattern_type = CSSValuePattern::NUMBER;\n  if (filter_type == starlight::FilterType::kGrayscale) {\n    if (token.type == TokenType::NUMBER) {\n      value *= 100;\n    }\n    pattern_type = CSSValuePattern::PERCENT;\n  } else if (filter_type == starlight::FilterType::kBrightness ||\n             filter_type == starlight::FilterType::kContrast ||\n             filter_type == starlight::FilterType::kSaturate) {\n    if (token.type == TokenType::PERCENTAGE) {\n      value /= 100.0;\n    }\n    pattern_type = CSSValuePattern::NUMBER;\n  } else if (filter_type == starlight::FilterType::kBlur) {\n    pattern_type = static_cast<CSSValuePattern>(TokenTypeToENUM(token.type));\n  }\n\n  return CSSValue(value, pattern_type);\n}\n\nbool CSSStringParser::ConsumeFilter(Token &token, starlight::FilterType type) {\n  switch (type) {\n    case starlight::FilterType::kBlur:\n      return LengthOrPercentageValue(token) &&\n             token.type != TokenType::PERCENTAGE;\n    case starlight::FilterType::kGrayscale:\n    case starlight::FilterType::kBrightness:\n    case starlight::FilterType::kContrast:\n    case starlight::FilterType::kSaturate:\n      return NumberOrPercentValue(token);\n    default:\n      return false;\n  }\n}\n\nbool CSSStringParser::ParseBorderLineWidth(CSSValue &result_width) {\n  Advance();\n  Token token;\n  ConsumeBorderLineWidth(token, result_width);\n  return Check(TokenType::TOKEN_EOF);\n}\n\nbool CSSStringParser::ParseBorderStyle(CSSValue &result_style) {\n  Advance();\n  Token token;\n  if (BorderStyleIdent(token)) {\n    result_style.SetEnum(TokenTypeToBorderStyle(token.type));\n    return Check(TokenType::TOKEN_EOF);\n  }\n  return false;\n}\n\nbool CSSStringParser::ParseBorder(CSSValue &result_width,\n                                  CSSValue &result_style,\n                                  CSSValue &result_color) {\n  Advance();\n  Token token;\n  while (result_width.IsEmpty() || result_style.IsEmpty() ||\n         result_color.IsEmpty()) {\n    if (result_width.IsEmpty()) {\n      ConsumeBorderLineWidth(token, result_width);\n      if (!result_width.IsEmpty()) {\n        continue;\n      }\n    }\n    if (result_style.IsEmpty() && BorderStyleIdent(token)) {\n      result_style.SetEnum(TokenTypeToBorderStyle(token.type));\n      if (!result_style.IsEmpty()) {\n        continue;\n      }\n    }\n    if (result_color.IsEmpty() && Color()) {\n      const auto &stack_value = PopValue();\n      if (stack_value.value_type == TokenType::NUMBER) {\n        result_color = CSSValue(*stack_value.value, CSSValuePattern::NUMBER);\n      }\n      if (!result_color.IsEmpty()) {\n        continue;\n      }\n    }\n    break;\n  }\n\n  if (!AtEnd()) {\n    return false;\n  }\n\n  if (result_width.IsEmpty() && result_style.IsEmpty() &&\n      result_color.IsEmpty()) {\n    return false;\n  }\n\n  // Fill default values\n  if (parser_configs_.enable_new_border_handler) {\n    if (result_width.IsEmpty()) {\n      result_width = CSSValue(0, CSSValuePattern::NUMBER);\n    }\n    if (result_style.IsEmpty()) {\n      result_style = CSSValue(TokenTypeToBorderStyle(TokenType::SOLID));\n    }\n    if (result_color.IsEmpty()) {\n      result_color = CSSValue(CSSColor::Black, CSSValuePattern::NUMBER);\n    }\n  }\n  return true;\n}\n\nCSSValue CSSStringParser::ParseShadow(bool inset_and_spread) {\n  Advance();\n  if (Consume(TokenType::NONE) && AtEnd()) {\n    return CSSValue(lepus::CArray::Create());\n  }\n  return ConsumeCommaSeparatedList(&CSSStringParser::ParseSingleShadow,\n                                   inset_and_spread);\n}\n\nlepus::Value CSSStringParser::ParseSingleShadow(bool inset_and_spread) {\n  // [1px 2px 3px red, ] is invalid\n  if (Check(TokenType::TOKEN_EOF) || Check(TokenType::SEMICOLON) ||\n      Check(TokenType::ERROR)) {\n    return lepus::Value();\n  }\n\n  // Shadow item\n  auto dict = lepus::Dictionary::Create();\n\n  CSSValue color;\n  std::optional<int> option;\n  CSSValue lengths[4] = {\n      CSSValue(),  // horizontal_offset\n      CSSValue(),  // vertical_offset\n      CSSValue(),  // blur_radius\n      CSSValue(),  // spread_distance\n  };\n  ConsumeColor(color);\n\n  Token token;\n  if (Check(TokenType::INSET)) {\n    // text-shadow doesn't support inset and spread\n    if (!inset_and_spread) {\n      return lepus::Value();\n    }\n    if (ShadowOptionIdent(token)) {\n      option = TokenTypeToShadowOption(token.type);\n    }\n    if (color.IsEmpty()) {\n      ConsumeColor(color);\n    }\n  }\n\n  // horizontal_offset\n  lengths[0] = Length();\n  if (lengths[0].IsEmpty()) {\n    return lepus::Value();\n  }\n\n  // vertical_offset\n  lengths[1] = Length();\n  if (lengths[1].IsEmpty()) {\n    return lepus::Value();\n  }\n\n  // blur_radius\n  lengths[2] = Length();\n  if (!lengths[2].IsEmpty()) {\n    if (inset_and_spread) {\n      // spread_distance\n      lengths[3] = Length();\n    }\n  }\n\n  // Still has token for current shadow\n  if (!Check(TokenType::COMMA) || !Check(TokenType::TOKEN_EOF)) {\n    if (color.IsEmpty()) {\n      ConsumeColor(color);\n    }\n\n    if (Check(TokenType::INSET)) {\n      if (!inset_and_spread || option.has_value()) {\n        return lepus::Value();\n      }\n      if (ShadowOptionIdent(token)) {\n        option = TokenTypeToShadowOption(token.type);\n      }\n      if (color.IsEmpty()) {\n        ConsumeColor(color);\n      }\n    }\n  }\n\n  BASE_STATIC_STRING_DECL(kEnable, \"enable\");\n  dict->SetValue(kEnable, true);\n  if (option.has_value()) {\n    BASE_STATIC_STRING_DECL(kOption, \"option\");\n    dict->SetValue(kOption, *option);\n  }\n\n  BASE_STATIC_STRING_DECL(kColor, \"color\");\n  dict->SetValue(kColor, color.GetValue());\n\n  static constexpr const char kHOffset[] = \"h_offset\";\n  static constexpr const char kVOffset[] = \"v_offset\";\n  static constexpr const char kBlur[] = \"blur\";\n  static constexpr const char kSpread[] = \"spread\";\n\n  const std::array<const base::String, 4> props{\n      BASE_STATIC_STRING(kHOffset), BASE_STATIC_STRING(kVOffset),\n      BASE_STATIC_STRING(kBlur), BASE_STATIC_STRING(kSpread)};\n  for (int i = 0; i < 4; ++i) {\n    // horizontal_offset and vertical_offset can not be empty, early return\n    if (lengths[i].IsEmpty()) {\n      continue;\n    }\n    auto arr = lepus::CArray::Create();\n    arr->emplace_back(lengths[i].GetValue());\n    arr->emplace_back(static_cast<int>(lengths[i].GetPattern()));\n    dict->SetValue(props[i], std::move(arr));\n  }\n\n  return lepus::Value(std::move(dict));\n}\n\nCSSValue CSSStringParser::ParseTransformOrigin() {\n  // For compatibility, we support comma in transform-origin\n  enable_transform_legacy_ = !parser_configs_.enable_new_transform_handler;\n  Advance();\n  auto result = lepus::CArray::Create();\n  if (ConsumePositionAndSetValue(result) && Check(TokenType::TOKEN_EOF)) {\n    return CSSValue(std::move(result));\n  }\n  return CSSValue();\n}\n\nCSSValue CSSStringParser::ParseAspectRatio() {\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return CSSValue();\n  }\n  if (Consume(TokenType::NONE) && Check(TokenType::TOKEN_EOF)) {\n    return CSSValue();\n  }\n  auto param1 = NumberOnly(false);\n  if (param1.IsEmpty()) {\n    return CSSValue();\n  }\n  if (Consume(TokenType::SLASH)) {\n    auto param2 = NumberOnly(false);\n    if (param2.IsEmpty() || base::IsZero(param2.Number())) {\n      return CSSValue();\n    }\n    return CSSValue(param1.Number() / param2.Number(), CSSValuePattern::NUMBER);\n  } else if (Check(TokenType::TOKEN_EOF)) {\n    auto result = CSSValue(std::move(param1), CSSValuePattern::NUMBER);\n    return result;\n  }\n  return CSSValue();\n}\n\nstd::pair<CSSValue, CSSValue> CSSStringParser::ParseGap() {\n  CSSValue default_gap1 = CSSValue(0.0, CSSValuePattern::PX);\n  CSSValue default_gap2 = CSSValue(0.0, CSSValuePattern::PX);\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return std::make_pair(std::move(default_gap1), std::move(default_gap2));\n  }\n  if (Consume(TokenType::NONE) && Check(TokenType::TOKEN_EOF)) {\n    return std::make_pair(std::move(default_gap1), std::move(default_gap2));\n  }\n  auto param1 = Length();\n  if (param1.IsEmpty()) {\n    param1 = std::move(default_gap1);\n  }\n  Advance();\n  if (!Check(TokenType::TOKEN_EOF)) {\n    auto param2 = Length();\n    if (param2.IsEmpty()) {\n      param2 = std::move(default_gap2);\n    }\n    return std::make_pair(param1, param2);\n  }\n  return std::make_pair(param1, param1);\n}\n\nbool CSSStringParser::ParseTextStroke(CSSValue &result_width,\n                                      CSSValue &result_color) {\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return false;\n  }\n  if (Consume(TokenType::NONE)) {\n    return true;\n  }\n  result_width = Length();\n  if (result_width.IsEmpty()) {\n    ConsumeColor(result_color);\n    if (result_color.IsEmpty()) {\n      return false;\n    }\n    Advance();\n    if (!Check(TokenType::TOKEN_EOF)) {\n      result_width = Length();\n      if (result_width.IsEmpty()) {\n        return false;\n      }\n      return true;\n    }\n    return false;\n  } else {\n    Advance();\n    if (!Check(TokenType::TOKEN_EOF)) {\n      ConsumeColor(result_color);\n      if (result_color.IsEmpty()) {\n        return false;\n      }\n      return true;\n    }\n    return false;\n  }\n}\n\nCSSValue CSSStringParser::ParseBool() {\n  Advance();\n  if (Consume(TokenType::TOKEN_TRUE) || Consume(TokenType::TOKEN_FALSE)) {\n    return CSSValue(previous_token_.type == TokenType::TOKEN_TRUE);\n  }\n  return CSSValue();\n}\n\nbool CSSStringParser::ParseAutoFontSize(\n    CSSValue &is_auto_font_size, CSSValue &auto_font_size_min_size,\n    CSSValue &auto_font_size_max_size,\n    CSSValue &auto_font_size_step_granularity) {\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return true;\n  }\n\n  if (!Consume(TokenType::TOKEN_TRUE) && !Consume(TokenType::TOKEN_FALSE)) {\n    return false;\n  }\n\n  is_auto_font_size = CSSValue(previous_token_.type == TokenType::TOKEN_TRUE);\n  if (Check(TokenType::TOKEN_EOF)) {\n    return true;\n  }\n\n  auto temp_auto_font_size_min_size = Length();\n  if (temp_auto_font_size_min_size.IsEmpty()) {\n    return false;\n  }\n  auto_font_size_min_size = temp_auto_font_size_min_size;\n  if (Check(TokenType::TOKEN_EOF)) {\n    return true;\n  }\n\n  auto temp_auto_font_size_max_size = Length();\n  if (temp_auto_font_size_max_size.IsEmpty()) {\n    return false;\n  }\n  auto_font_size_max_size = temp_auto_font_size_max_size;\n  if (Check(TokenType::TOKEN_EOF)) {\n    return true;\n  }\n\n  auto temp_auto_font_size_step_granularity = Length();\n  if (temp_auto_font_size_step_granularity.IsEmpty()) {\n    return false;\n  }\n  auto_font_size_step_granularity = temp_auto_font_size_step_granularity;\n\n  if (!Check(TokenType::TOKEN_EOF)) {\n    is_auto_font_size = CSSValue(false);\n    auto_font_size_min_size = CSSValue(0, CSSValuePattern::PX);\n    auto_font_size_max_size = CSSValue(0, CSSValuePattern::PX);\n    auto_font_size_step_granularity = CSSValue(1, CSSValuePattern::PX);\n    return false;\n  }\n  return true;\n}\n\nbool CSSStringParser::ParseAutoFontSizePresetSize(\n    fml::RefPtr<lepus::CArray> &arr) {\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return true;\n  }\n  while (!Check(TokenType::TOKEN_EOF)) {\n    if (!ConsumeLengthAndSetValue(arr)) {\n      return false;\n    }\n  }\n  return true;\n}\n\nCSSValue CSSStringParser::ParseTransform() {\n  enable_transform_legacy_ = !parser_configs_.enable_new_transform_handler;\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return CSSValue();\n  }\n  if (Consume(TokenType::NONE) && Check(TokenType::TOKEN_EOF)) {\n    return CSSValue(lepus::CArray::Create());\n  }\n  auto result = lepus::CArray::Create();\n  while (!Check(TokenType::TOKEN_EOF) && !Check(TokenType::SEMICOLON) &&\n         !Check(TokenType::ERROR)) {\n    auto arr = lepus::CArray::Create();\n    Token function_token;\n    if (!TransformFunctionIdent(function_token)) {\n      return CSSValue();\n    }\n    arr->emplace_back(\n        static_cast<int>(TokenToTransformFunction(function_token)));\n\n    CSSStringParser params_parser{function_token.start, function_token.length,\n                                  parser_configs_};\n    if (!params_parser.ParseTransformParams(function_token, arr)) {\n      return CSSValue();\n    }\n\n    result->emplace_back(std::move(arr));\n  }\n\n  if (!AtEnd()) {\n    return CSSValue();\n  }\n  return CSSValue(std::move(result));\n}\n\nbool CSSStringParser::ParseTransformParams(const Token &function_token,\n                                           fml::RefPtr<lepus::CArray> &arr) {\n  // For compatibility, we support number in angle value\n  enable_transform_legacy_ = !parser_configs_.enable_new_transform_handler;\n  Advance();\n  switch (function_token.type) {\n    case TokenType::ROTATE:\n    case TokenType::ROTATE_X:\n    case TokenType::ROTATE_Y:\n    case TokenType::ROTATE_Z:\n    case TokenType::SKEW_X:\n    case TokenType::SKEW_Y:\n    case TokenType::SKEW: {\n      Token angle_token;\n      if (!AngleValue(angle_token)) {\n        return false;\n      }\n      arr->emplace_back(TokenToAngleValue(angle_token));\n      // skew(angle, angle)\n      if (function_token.type == TokenType::SKEW && Consume(TokenType::COMMA)) {\n        if (!AngleValue(angle_token)) {\n          return false;\n        }\n        arr->emplace_back(TokenToAngleValue(angle_token));\n      }\n    } break;\n    case TokenType::SCALE_X:\n    case TokenType::SCALE_Y:\n    case TokenType::SCALE: {\n      auto param = NumberOrPercentage();\n      if (param.IsEmpty()) {\n        return false;\n      }\n      arr->emplace_back(std::move(param));\n      if (function_token.type == TokenType::SCALE &&\n          Consume(TokenType::COMMA)) {\n        auto param = NumberOrPercentage();\n        if (param.IsEmpty()) {\n          return false;\n        }\n        arr->emplace_back(std::move(param));\n      }\n    } break;\n    case TokenType::TRANSLATE_X:\n    case TokenType::TRANSLATE_Y:\n    case TokenType::TRANSLATE_Z:\n    case TokenType::TRANSLATE: {\n      if (!ConsumeLengthAndSetValue(arr)) {\n        return false;\n      }\n      // transform: translate(12px, 50%);\n      if (function_token.type == TokenType::TRANSLATE) {\n        if (Consume(TokenType::COMMA) && !ConsumeLengthAndSetValue(arr)) {\n          return false;\n        }\n        // For compatibility, we support translate(12px, 50%, 3), it's equal\n        // to translate(12px, 50%)\n        if (enable_transform_legacy_ && Consume(TokenType::COMMA) &&\n            !Length().IsEmpty()) {\n          break;\n        }\n      }\n    } break;\n    case TokenType::TRANSLATE_3D: {\n      // transform: translate3d(12px, 50%, 5px);\n      if (!ConsumeLengthAndSetValue(arr)) {\n        return false;\n      }\n      if (!Consume(TokenType::COMMA)) {\n        return false;\n      }\n      if (!ConsumeLengthAndSetValue(arr)) {\n        return false;\n      }\n      if (!Consume(TokenType::COMMA)) {\n        return false;\n      }\n      if (!ConsumeLengthAndSetValue(arr)) {\n        return false;\n      }\n    } break;\n    case TokenType::MATRIX:\n    case TokenType::MATRIX_3D:\n      if (!ConsumeMatrixNumbers(\n              arr, function_token.type == TokenType::MATRIX_3D ? 16 : 6)) {\n        return false;\n      }\n      break;\n    default:\n      return false;\n  }\n  // Semicolon not allowed\n  return Check(TokenType::TOKEN_EOF);\n}\n\nbool CSSStringParser::ConsumeMatrixNumbers(fml::RefPtr<lepus::CArray> &arr,\n                                           int count) {\n  do {\n    auto param = NumberOrPercentage();\n    if (param.IsEmpty()) {\n      return false;\n    }\n    arr->emplace_back(std::move(param));\n    if (--count && !Consume(TokenType::COMMA)) {\n      return false;\n    }\n  } while (count);\n  return true;\n}\n\nbool CSSStringParser::ParseFlex(double &flex_grow, double &flex_shrink,\n                                CSSValue &flex_basis) {\n  Advance();\n  static const double unset_value = -1;\n\n  if (Consume(TokenType::NONE) && Check(TokenType::TOKEN_EOF)) {\n    flex_grow = 0;\n    // For compatibility, none is equivalent to setting '0 1 auto'.\n    // In fact, this should be '0 0 auto'\n    flex_shrink = parser_configs_.enable_new_flex_handler ? 0 : 1;\n    flex_basis.SetEnum(starlight::LengthValueType::kAuto);\n    return true;\n  }\n\n  Token t;\n  unsigned index = 0;\n  while (!Check(TokenType::TOKEN_EOF) && index++ < 3) {\n    double num = 0;\n    if (LengthOrPercentageValue(t) ||\n        // If enable length unit check number is not a valid length\n        (parser_configs_.enable_length_unit_check &&\n         t.type == TokenType::NUMBER)) {\n      // number only\n      if (t.type == TokenType::NUMBER) {\n        num = TokenToDouble(t);\n        if (num < 0) {\n          return false;\n        }\n        if (flex_grow == unset_value) {\n          flex_grow = num;\n        } else if (flex_shrink == unset_value) {\n          flex_shrink = num;\n        } else if (!num || !parser_configs_.enable_length_unit_check) {\n          // flex only allows a basis of 0\n          // If disable unit check the last number can be basis\n          flex_basis.SetNumber(num, CSSValuePattern::NUMBER);\n        } else {\n          return false;\n        }\n      } else if (flex_basis.IsEmpty()) {  // length value\n        TokenToLengthTarget(t, flex_basis);\n        if (index == 2 && !Check(TokenType::TOKEN_EOF)) {\n          return false;\n        }\n      }\n    } else {\n      return false;\n    }\n  }\n  if (index == 0) {\n    return false;\n  }\n\n  if (flex_grow == unset_value) {\n    // FIXME: The legacy code. If flex only has a flex basis value, let's make\n    // flex shrink 0\n    if (flex_shrink == unset_value && !flex_basis.IsEmpty() &&\n        !parser_configs_.enable_new_flex_handler) {\n      flex_grow = 0;\n    } else {\n      flex_grow = 1;\n    }\n  }\n  if (flex_shrink == unset_value) {\n    flex_shrink = 1;\n  }\n  if (flex_basis.IsEmpty()) {\n    flex_basis.SetNumber(0.f, CSSValuePattern::NUMBER);\n  }\n  return AtEnd();\n}\n\ntemplate <typename TokenFunc, typename ConsumeTokenFunc>\nbool CSSStringParser::ParseNumberOrArray(bool single, TokenFunc is_token,\n                                         ConsumeTokenFunc consume,\n                                         CSSValue &ret) {\n  Advance();\n  Token t;\n  if (single) {\n    if ((this->*is_token)(t)) {\n      CSSValue value = consume(t);\n      if (value.IsEmpty()) {\n        return false;\n      }\n      ret = std::move(value);\n      return AtEnd();\n    } else {\n      return false;\n    }\n  } else {\n    auto arr = lepus::CArray::Create();\n    do {\n      if ((this->*is_token)(t)) {\n        CSSValue value = consume(t);\n        if (value.IsEmpty()) {\n          return false;\n        }\n        arr->emplace_back(value.GetValue());\n      } else {\n        return false;\n      }\n    } while (Consume(TokenType::COMMA));\n    ret.SetArray(std::move(arr));\n    return AtEnd();\n  }\n}\n\nbool CSSStringParser::ParseTime(bool single, bool no_negative, CSSValue &ret) {\n  enable_time_legacy_ = !parser_configs_.enable_new_time_handler;\n  return ParseNumberOrArray(\n      single, &CSSStringParser::TimeValue,\n      [no_negative](const Token &t) {\n        double number = TimeToNumber(t);\n        if (no_negative && number < 0) {\n          return CSSValue();\n        }\n        return CSSValue(number, CSSValuePattern::NUMBER);\n      },\n      ret);\n}\n\nbool CSSStringParser::ParseTimingFunction(bool single, CSSValue &ret) {\n  return ParseNumberOrArray(\n      single, &CSSStringParser::TimingFunctionValue,\n      [&configs = parser_configs_](const Token &t) {\n        return ConsumeTimingFunction(t, configs);\n      },\n      ret);\n}\n\nbool CSSStringParser::AnimationDirectionValue(Token &token) {\n  if (ConsumeAndSave(TokenType::NORMAL, token) ||\n      ConsumeAndSave(TokenType::REVERSE, token) ||\n      ConsumeAndSave(TokenType::ALTERNATE, token) ||\n      ConsumeAndSave(TokenType::ALTERNATE_REVERSE, token)) {\n    return true;\n  }\n  return false;\n}\n\nstarlight::AnimationDirectionType\nCSSStringParser::TokenToAnimationDirectionType(const Token &token) {\n  switch (token.type) {\n    case TokenType::ALTERNATE_REVERSE:\n      return starlight::AnimationDirectionType::kAlternateReverse;\n    case TokenType::ALTERNATE:\n      return starlight::AnimationDirectionType::kAlternate;\n    case TokenType::REVERSE:\n      return starlight::AnimationDirectionType::kReverse;\n    case TokenType::NORMAL:\n    default:\n      return starlight::AnimationDirectionType::kNormal;\n  }\n}\n\nbool CSSStringParser::ParseAnimationDirection(bool single, CSSValue &ret) {\n  return ParseNumberOrArray(\n      single, &CSSStringParser::AnimationDirectionValue,\n      [](const Token &t) { return CSSValue(TokenToAnimationDirectionType(t)); },\n      ret);\n}\n\nbool CSSStringParser::AnimationFillModeValue(Token &token) {\n  if (ConsumeAndSave(TokenType::NONE, token) ||\n      ConsumeAndSave(TokenType::FORWARDS, token) ||\n      ConsumeAndSave(TokenType::BACKWARDS, token) ||\n      ConsumeAndSave(TokenType::BOTH, token)) {\n    return true;\n  }\n  return false;\n}\n\nstarlight::AnimationFillModeType CSSStringParser::TokenToAnimationFillModeType(\n    const Token &token) {\n  switch (token.type) {\n    case TokenType::FORWARDS:\n      return starlight::AnimationFillModeType::kForwards;\n    case TokenType::BACKWARDS:\n      return starlight::AnimationFillModeType::kBackwards;\n    case TokenType::BOTH:\n      return starlight::AnimationFillModeType::kBoth;\n    case TokenType::NONE:\n    default:\n      return starlight::AnimationFillModeType::kNone;\n  }\n}\n\nbool CSSStringParser::ParseAnimationFillMode(bool single, CSSValue &ret) {\n  return ParseNumberOrArray(\n      single, &CSSStringParser::AnimationFillModeValue,\n      [](const Token &t) { return CSSValue(TokenToAnimationFillModeType(t)); },\n      ret);\n}\n\nbool CSSStringParser::AnimationIterCountValue(Token &token) {\n  if (ConsumeAndSave(TokenType::INFINITE, token)) {\n    return true;\n  }\n  return NumberValue(token);\n}\n\ndouble CSSStringParser::TokenToAnimationIterCount(const Token &token) {\n  static constexpr double infinite = 10E8;\n  if (token.type == TokenType::INFINITE) {\n    return infinite;\n  } else {\n    return TokenToDouble(token);\n  }\n}\n\nbool CSSStringParser::ParseAnimationIterCount(bool single, CSSValue &ret) {\n  return ParseNumberOrArray(\n      single, &CSSStringParser::AnimationIterCountValue,\n      [](const Token &t) {\n        double num = TokenToAnimationIterCount(t);\n        if (num < 0) {\n          return CSSValue();\n        }\n        return CSSValue(num, CSSValuePattern::NUMBER);\n      },\n      ret);\n}\n\nbool CSSStringParser::AnimationPlayStateValue(Token &token) {\n  if (ConsumeAndSave(TokenType::PAUSED, token) ||\n      ConsumeAndSave(TokenType::RUNNING, token)) {\n    return true;\n  }\n  return false;\n}\n\nbool CSSStringParser::AnimationNameValue(Token &token) {\n  SkipWhitespaceToken();\n  // keyword and ident\n  if (current_token_.IsIdent()) {\n    token = current_token_;\n    Advance();\n    return true;\n  }\n  return false;\n}\n\nstarlight::AnimationPlayStateType CSSStringParser::TokenToAnimationPlayState(\n    const Token &token) {\n  if (token.type == TokenType::PAUSED) {\n    return starlight::AnimationPlayStateType::kPaused;\n  }\n  return starlight::AnimationPlayStateType::kRunning;\n}\n\nbool CSSStringParser::ParseAnimationPlayState(bool single, CSSValue &ret) {\n  return ParseNumberOrArray(\n      single, &CSSStringParser::AnimationPlayStateValue,\n      [](const Token &t) { return CSSValue(TokenToAnimationPlayState(t)); },\n      ret);\n}\n\nbool CSSStringParser::ParseAnimationName(bool single, CSSValue &ret) {\n  return ParseNumberOrArray(\n      single, &CSSStringParser::AnimationNameValue,\n      [](const Token &t) {\n        return CSSValue(std::string(t.start, t.length), CSSValuePattern::STRING,\n                        CSSValueType::DEFAULT);\n      },\n      ret);\n}\n\nbool CSSStringParser::ParseTransitionProperty(bool single, CSSValue &ret) {\n  return ParseNumberOrArray(\n      single, &CSSStringParser::TransitionProperty,\n      [&config = parser_configs_](const Token &t) {\n        return CSSValue(TokenToTransitionType(t, config));\n      },\n      ret);\n}\n\nbool CSSStringParser::Transition(CSSTransitionLayer &layer) {\n  SkipWhitespaceToken();\n  if (AtEnd()) {\n    return false;\n  }\n  Token t;\n  bool longhands[4] = {};\n  while (!Check(TokenType::COMMA) && !Check(TokenType::TOKEN_EOF)) {\n    if (TimeValue(t)) {\n      double time = TimeToNumber(t);\n      if (!longhands[1] && time >= 0) {\n        longhands[1] = true;\n        layer.duration = time;\n      } else if (!longhands[2]) {\n        longhands[2] = true;\n        layer.delay = time;\n      } else {\n        return false;\n      }\n    } else if (TimingFunctionValue(t)) {\n      if (longhands[3]) {\n        return false;\n      }\n      longhands[3] = true;\n      layer.timing_function = ConsumeTimingFunction(t, parser_configs_);\n    } else if (TransitionProperty(t)) {\n      if (!longhands[0]) {\n        longhands[0] = true;\n        layer.property = TokenToTransitionType(t, parser_configs_);\n      } else {\n        return false;\n      }\n    } else {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool CSSStringParser::ParseTransition(bool single, lepus::Value arr[4]) {\n  Advance();\n  if (single) {\n    CSSTransitionLayer layer;\n    if (!Transition(layer)) {\n      return false;\n    }\n    arr[0].SetNumber(static_cast<int>(layer.property));\n    arr[1].SetNumber(layer.duration);\n    arr[2].SetNumber(layer.delay);\n    auto timing_array = lepus::CArray::Create();\n    timing_array->emplace_back(layer.timing_function.GetValue());\n    arr[3].SetArray(std::move(timing_array));\n    return AtEnd();\n  } else {\n    auto property_array = lepus::CArray::Create();\n    auto duration_array = lepus::CArray::Create();\n    auto delay_array = lepus::CArray::Create();\n    auto timing_array = lepus::CArray::Create();\n    bool has_property_none = false;\n\n    do {\n      CSSTransitionLayer layer;\n      if (!Transition(layer)) {\n        return false;\n      }\n      if (layer.property == starlight::AnimationPropertyType::kNone) {\n        if (has_property_none) {  // Only once otherwise failed\n          return false;\n        }\n        has_property_none = true;\n      }\n      property_array->emplace_back(static_cast<int>(layer.property));\n      duration_array->emplace_back(layer.duration);\n      delay_array->emplace_back(layer.delay);\n      timing_array->emplace_back(layer.timing_function.GetValue());\n    } while (Consume(TokenType::COMMA));\n\n    arr[0].SetArray(std::move(property_array));\n    arr[1].SetArray(std::move(duration_array));\n    arr[2].SetArray(std::move(delay_array));\n    arr[3].SetArray(std::move(timing_array));\n    return AtEnd();\n  }\n}\n\nbool CSSStringParser::Animation(CSSAnimationLayer &layer) {\n  SkipWhitespaceToken();\n  if (AtEnd()) {\n    return false;\n  }\n  Token t;\n  // [duration, delay, timing, count, direction, fill_mode, play_state, name]\n  bool longhands[8] = {};\n  while (!Check(TokenType::COMMA) && !Check(TokenType::TOKEN_EOF)) {\n    if (TimeValue(t)) {\n      double time = TimeToNumber(t);\n      if (!longhands[0] && time >= 0) {\n        longhands[0] = true;\n        layer.duration = time;\n      } else if (!longhands[1]) {\n        longhands[1] = true;\n        layer.delay = time;\n      } else {\n        return false;\n      }\n    } else if (TimingFunctionValue(t)) {\n      if (longhands[2]) {\n        return false;\n      }\n      longhands[2] = true;\n      layer.timing_function = ConsumeTimingFunction(t, parser_configs_);\n    } else if (AnimationIterCountValue(t)) {\n      if (!longhands[3]) {\n        longhands[3] = true;\n        layer.count = TokenToAnimationIterCount(t);\n        if (layer.count < 0) {\n          return false;\n        }\n      } else {\n        return false;\n      }\n    } else if (AnimationDirectionValue(t)) {\n      if (!longhands[4]) {\n        longhands[4] = true;\n        layer.direction = TokenToAnimationDirectionType(t);\n      } else {\n        return false;\n      }\n    } else if (AnimationFillModeValue(t)) {\n      if (!longhands[5]) {\n        longhands[5] = true;\n        layer.fill_mode = TokenToAnimationFillModeType(t);\n      } else {\n        return false;\n      }\n    } else if (AnimationPlayStateValue(t)) {\n      if (!longhands[6]) {\n        longhands[6] = true;\n        layer.play_state = TokenToAnimationPlayState(t);\n      } else {\n        return false;\n      }\n    } else if (AnimationNameValue(t)) {\n      if (!longhands[7]) {\n        longhands[7] = true;\n        layer.name = std::string(t.start, t.length);\n      } else {\n        return false;\n      }\n    } else {\n      return false;\n    }\n  }\n  return true;\n}\n\nbool CSSStringParser::ParseAnimation(bool single, lepus::Value arr[8]) {\n  Advance();\n  if (single) {\n    CSSAnimationLayer layer;\n    if (!Animation(layer)) {\n      return false;\n    }\n    // [name, duration, delay, timing, count, direction, fill_mode, play_state]\n    arr[0].SetString(layer.name);\n    arr[1].SetNumber(layer.duration);\n    arr[2].SetNumber(layer.delay);\n    auto timing_array = lepus::CArray::Create();\n    timing_array->emplace_back(layer.timing_function.GetValue());\n    arr[3].SetArray(std::move(timing_array));\n    arr[4].SetNumber(layer.count);\n    arr[5].SetNumber(static_cast<int>(layer.direction));\n    arr[6].SetNumber(static_cast<int>(layer.fill_mode));\n    arr[7].SetNumber(static_cast<int>(layer.play_state));\n    return AtEnd();\n  } else {\n    auto name_array = lepus::CArray::Create();\n    auto duration_array = lepus::CArray::Create();\n    auto delay_array = lepus::CArray::Create();\n    auto timing_array = lepus::CArray::Create();\n    auto count_array = lepus::CArray::Create();\n    auto direction_array = lepus::CArray::Create();\n    auto fill_mode_array = lepus::CArray::Create();\n    auto play_state_array = lepus::CArray::Create();\n\n    do {\n      CSSAnimationLayer layer;\n      if (!Animation(layer)) {\n        return false;\n      }\n      name_array->emplace_back(layer.name);\n      duration_array->emplace_back(layer.duration);\n      delay_array->emplace_back(layer.delay);\n      timing_array->emplace_back(layer.timing_function.GetValue());\n      count_array->emplace_back(layer.count);\n      direction_array->emplace_back(static_cast<int>(layer.direction));\n      fill_mode_array->emplace_back(static_cast<int>(layer.fill_mode));\n      play_state_array->emplace_back(static_cast<int>(layer.play_state));\n    } while (Consume(TokenType::COMMA));\n\n    // [name, duration, delay, timing, count, direction, fill_mode, play_state]\n    arr[0].SetArray(std::move(name_array));\n    arr[1].SetArray(std::move(duration_array));\n    arr[2].SetArray(std::move(delay_array));\n    arr[3].SetArray(std::move(timing_array));\n    arr[4].SetArray(std::move(count_array));\n    arr[5].SetArray(std::move(direction_array));\n    arr[6].SetArray(std::move(fill_mode_array));\n    arr[7].SetArray(std::move(play_state_array));\n    return AtEnd();\n  }\n}\n\nbool CSSStringParser::ConsumeOpenTypeTag(fml::RefPtr<lepus::CArray> &arr) {\n  SkipWhitespaceToken();\n  std::string open_type_tag_str =\n      std::string(current_token_.start, current_token_.length);\n\n  if (open_type_tag_str.size() >= 2) {\n    char first = open_type_tag_str.front();\n    char last = open_type_tag_str.back();\n    if ((first == '\"' && last == '\"') || (first == '\\'' && last == '\\'')) {\n      open_type_tag_str =\n          open_type_tag_str.substr(1, open_type_tag_str.size() - 2);\n    }\n  } else {\n    return false;\n  }\n\n  if (!IsValidOpenTypeTag(open_type_tag_str)) {\n    return false;\n  }\n  arr->emplace_back(open_type_tag_str);\n  Advance();\n  return true;\n}\n\nbool CSSStringParser::IsValidOpenTypeTag(const std::string &tag) {\n  if (tag.length() != 4) {\n    return false;\n  }\n\n  for (char c : tag) {\n    if (c < 0x20 || c > 0x7E) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\nbool CSSStringParser::ParseFontVariationSettings(\n    fml::RefPtr<lepus::CArray> &arr) {\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return true;\n  }\n\n  if (Consume(TokenType::NORMAL)) {\n    return true;\n  }\n  do {\n    if (!ConsumeOpenTypeTag(arr)) {\n      return false;\n    }\n\n    Token number_token;\n    if (!ConsumeAndSave(TokenType::NUMBER, number_token)) {\n      return false;\n    }\n    arr->emplace_back(TokenToDouble(number_token));\n  } while (Consume(TokenType::COMMA));\n\n  return true;\n}\n\nbool CSSStringParser::ParseFontFeatureSettings(\n    fml::RefPtr<lepus::CArray> &arr) {\n  Advance();\n  if (Check(TokenType::TOKEN_EOF)) {\n    return true;\n  }\n\n  if (Consume(TokenType::NORMAL)) {\n    return true;\n  }\n  do {\n    if (!ConsumeOpenTypeTag(arr)) {\n      return false;\n    }\n\n    if (Consume(TokenType::ON)) {\n      arr->emplace_back(1);\n    } else if (Consume(TokenType::OFF)) {\n      arr->emplace_back(0);\n    } else {\n      Token number_token;\n      if (ConsumeAndSave(TokenType::NUMBER, number_token)) {\n        arr->emplace_back(static_cast<int32_t>(TokenToInt(number_token)));\n      } else {\n        arr->emplace_back(1);\n      }\n    }\n  } while (Consume(TokenType::COMMA));\n\n  return true;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/css_string_parser.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_CSS_STRING_PARSER_H_\n#define CORE_RENDERER_CSS_PARSER_CSS_STRING_PARSER_H_\n\n#include <optional>\n#include <string>\n#include <tuple>\n#include <utility>\n\n#include \"base/include/vector.h\"\n#include \"core/renderer/css/css_keywords.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/css/parser/css_parser_configs.h\"\n#include \"core/renderer/css/parser/css_string_scanner.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace tasm {\n\n/// A Recursive descent parser to parse CSS background and border string\n/// more info see https://en.wikipedia.org/wiki/Recursive_descent_parser\n/// Syntax is follow https://developer.mozilla.org/en-US/docs/Web/CSS/\n\nstatic constexpr unsigned int POS_LEFT =\n    static_cast<uint32_t>(starlight::BackgroundPositionType::kLeft);\nstatic constexpr unsigned int POS_TOP =\n    static_cast<uint32_t>(starlight::BackgroundPositionType::kTop);\nstatic constexpr unsigned int POS_RIGHT =\n    static_cast<uint32_t>(starlight::BackgroundPositionType::kRight);\nstatic constexpr unsigned int POS_BOTTOM =\n    static_cast<uint32_t>(starlight::BackgroundPositionType::kBottom);\nstatic constexpr unsigned int POS_CENTER =\n    static_cast<uint32_t>(starlight::BackgroundPositionType::kCenter);\nstatic constexpr uint32_t PATTERN_PERCENT =\n    static_cast<uint32_t>(CSSValuePattern::PERCENT);\nstatic constexpr int SIZE_AUTO =\n    -1.f * static_cast<int>(starlight::BackgroundSizeType::kAuto);\nstatic constexpr int ANGLE_AUTO =\n    -1.f * static_cast<int>(starlight::OffsetRotateType::kAuto);\n\nclass CSSStringParser final {\n  struct StackValue {\n    explicit StackValue(TokenType type) : value_type(type) {}\n    StackValue(const lepus::Value& value, TokenType type)\n        : value(value), value_type(type) {}\n    StackValue() = default;\n\n    // Inplace construct value\n    template <typename... Args>\n    explicit StackValue(TokenType type, Args&&... args)\n        : value(std::forward<Args>(args)...), value_type(type) {}\n\n    std::optional<lepus::Value> value;\n    TokenType value_type;\n    bool has_value{false};\n  };\n\n  struct CSSBackgroundLayer {\n    CSSValue position_x{0, CSSValuePattern::PERCENT};\n    CSSValue position_y{0, CSSValuePattern::PERCENT};\n    // [-enum, number] | length\n    CSSValue size_x{SIZE_AUTO, CSSValuePattern::NUMBER};\n    CSSValue size_y{SIZE_AUTO, CSSValuePattern::NUMBER};\n    uint32_t repeat_x =\n        static_cast<uint32_t>(starlight::BackgroundRepeatType::kRepeat);\n    uint32_t repeat_y =\n        static_cast<uint32_t>(starlight::BackgroundRepeatType::kRepeat);\n    uint32_t origin =\n        static_cast<uint32_t>(starlight::BackgroundOriginType::kPaddingBox);\n    uint32_t clip =\n        static_cast<uint32_t>(starlight::BackgroundClipType::kPaddingBox);\n\n    std::optional<StackValue> image;\n    std::optional<uint32_t> color;\n  };\n\n  struct CSSTransitionLayer {\n    double delay = 0;\n    double duration = 0;\n    starlight::AnimationPropertyType property =\n        starlight::AnimationPropertyType::kAll;\n    CSSValue timing_function{starlight::TimingFunctionType::kLinear};\n  };\n\n  struct CSSAnimationLayer {\n    std::string name = \"none\";\n    double delay = 0;\n    double duration = 0;\n    double count = 1;\n    starlight::AnimationDirectionType direction =\n        starlight::AnimationDirectionType::kNormal;\n    starlight::AnimationFillModeType fill_mode =\n        starlight::AnimationFillModeType::kNone;\n    starlight::AnimationPlayStateType play_state =\n        starlight::AnimationPlayStateType::kRunning;\n    CSSValue timing_function{starlight::TimingFunctionType::kLinear};\n  };\n\n  enum {\n    BG_REPEAT = 1 << 0,\n    BG_POSITION_AND_SIZE = 1 << 1,\n    BG_IMAGE = 1 << 2,\n    BG_CLIP_BOX = 1 << 3,\n    BG_ORIGIN = 1 << 4,\n    BG_COLOR = 1 << 5,\n  };\n\n public:\n  CSSStringParser(const char* str, uint32_t len,\n                  const CSSParserConfigs& configs)\n      : scanner_(str, len), parser_configs_(configs) {}\n  ~CSSStringParser() = default;\n\n  static CSSStringParser FromLepusString(const lepus::Value& value_str,\n                                         const CSSParserConfigs& configs) {\n    auto str = value_str.StringView();\n    return CSSStringParser(str.data(), static_cast<uint32_t>(str.size()),\n                           configs);\n  }\n\n  /// <background> = [ <bg-layer>, ]* <final-bg-layer>\n  CSSValue ParseBackgroundOrMask(bool mask);\n  /// <bg-image> [, <bg-image> ]*\n  CSSValue ParseBackgroundImage();\n\n  /// <bg-position> [, <bg-position>]*\n  /// <bg-position> =\n  ///     [\n  ///      left | center | right | top | bottom | <length-percentage> ]\n  ///       | [ left | center | right | <length-percentage>\n  ///     ]\n  ///   [top | center | bottom | <length-percentage> ]\n  CSSValue ParseBackgroundPosition();\n  /// <bg-size> [, <bg-size> ]*\n  /// <bg-size> = [ <length-percentage> | auto ]{1,2} | cover | contain\n  CSSValue ParseBackgroundSize();\n  /// <bg-origin> = <box> [, <box> ]*\n  CSSValue ParseBackgroundBox();\n  /// <bg-clip> = <box> [, <box> ]*\n  CSSValue ParseBackgroundClip();\n  /// <bg-repeat> [, <bg-repeat>]*\n  /// <bg-repeat> = <repeat-style> = repeat-x | repeat-y | [ repeat | space |\n  ///               round | no-repeat ]{1,2}\n  CSSValue ParseBackgroundRepeat();\n  /// <text-color> = <color> | <linear-gradient> | <radial-gradient>\n  CSSValue ParseBool();\n\n  CSSValue ParseTextColor();\n  /// <color>\n  CSSValue ParseCSSColor();\n\n  void ParseTextColorTo(CSSValue& target);\n  void ParseCSSColorTo(CSSValue& target);\n\n  /// <text-decoration> = <text-decoration-line> || <text-decoration-style> ||\n  /// <text-decoration-color>\n  CSSValue ParseTextDecoration();\n\n  /// <src> = [ <url> [ format( <string> ) ]? | local( <family-name> )\n  CSSValue ParseFontSrc();\n\n  /// font-weight [ normal | bold | <number [1, 1000]>{1, 2}\n  CSSValue ParseFontWeight();\n\n  /// <length>\n  CSSValue ParseFontLength();\n\n  /// [ [ <url> [ <x> <y> ]? , ]* [ auto | default | none | ... ] ]\n  CSSValue ParseCursor();\n\n  CSSValue ParseVariable();\n\n  static bool IsVariable(const char* name, uint32_t len) {\n    return len > 3 && name[0] == 'v' && name[1] == 'a' && name[2] == 'r';\n  }\n\n  inline void SetIsLegacyParser(bool is_legacy) { legacy_parser_ = is_legacy; }\n\n  /// for image related only composed with url\n  std::string ParseUrl();\n\n  // <basic-shape>\n  lepus::Value ParseShapePath();\n\n  lepus::Value ParseOffsetRotate();\n\n  CSSValue ParseLength();\n  void ParseLengthTo(CSSValue& target);\n\n  CSSValue ParseListGap();\n\n  CSSValue ParseSingleBorderRadius();\n  CSSValue ParseAspectRatio();\n  bool ParseTextStroke(CSSValue& result_width, CSSValue& result_color);\n  std::pair<CSSValue, CSSValue> ParseGap();\n  bool ParseBorderRadius(CSSValue horizontal_radii[4],\n                         CSSValue vertical_radii[4]);\n\n  CSSValue ParseFilter();\n  bool ParseBorderLineWidth(CSSValue& result_width);\n  bool ParseBorderStyle(CSSValue& result_style);\n  bool ParseBorder(CSSValue& result_width, CSSValue& result_style,\n                   CSSValue& result_color);\n\n  CSSValue ParseShadow(bool inset_and_spread);\n  lepus::Value ParseSingleShadow(bool inset_and_spread);\n\n  CSSValue ParseTransform();\n  bool ParseAutoFontSize(CSSValue& is_auto_font_size,\n                         CSSValue& auto_font_size_min_size,\n                         CSSValue& auto_font_size_max_size,\n                         CSSValue& auto_font_size_step_granularity);\n  bool ParseAutoFontSizePresetSize(fml::RefPtr<lepus::CArray>& arr);\n  bool ParseTransformParams(const Token& function_token,\n                            fml::RefPtr<lepus::CArray>& arr);\n  bool ConsumeMatrixNumbers(fml::RefPtr<lepus::CArray>& arr, int count);\n  CSSValue ParseTransformOrigin();\n\n  bool ParseFlex(double& flex_grow, double& flex_shrink, CSSValue& flex_basis);\n\n  static starlight::BorderStyleType TokenTypeToBorderStyle(\n      TokenType token_type);\n\n  static uint32_t LerpColor(uint32_t start_color, uint32_t end_color,\n                            float start_pos, float end_pos, float current_pos);\n\n  bool ParseTransitionProperty(bool single, CSSValue& ret);\n  bool ParseTime(bool single, bool no_negative, CSSValue& ret);\n  bool ParseTimingFunction(bool single, CSSValue& ret);\n  bool ParseTransition(bool single, lepus::Value arr[4]);\n  bool ParseAnimationDirection(bool single, CSSValue& ret);\n  bool ParseAnimationFillMode(bool single, CSSValue& ret);\n  bool ParseAnimationIterCount(bool single, CSSValue& ret);\n  bool ParseAnimationPlayState(bool single, CSSValue& ret);\n  bool ParseAnimationName(bool single, CSSValue& ret);\n  bool ParseAnimation(bool single, lepus::Value arr[8]);\n  bool ParseFontVariationSettings(fml::RefPtr<lepus::CArray>& arr);\n  bool ParseFontFeatureSettings(fml::RefPtr<lepus::CArray>& arr);\n  bool ConsumeOpenTypeTag(fml::RefPtr<lepus::CArray>& arr);\n  bool HasMetVarToken() const { return scanner_.HasMetVarToken(); }\n\n  const char* content() const { return scanner_.content(); }\n\n  template <auto F, typename... Args>\n  bool ParseSingleOrMultipleValuePreview(Args&&... args) {\n    bool single = std::strchr(content(), ',') == nullptr;\n    return (this->*F)(single, std::forward<Args>(args)...);\n  }\n\n private:\n  template <typename TokenFunc, typename ConsumeTokenFunc>\n  bool ParseNumberOrArray(bool single, TokenFunc is_token,\n                          ConsumeTokenFunc consume, CSSValue& ret);\n  bool Transition(CSSTransitionLayer& layer);\n  bool Animation(CSSAnimationLayer& layer);\n\n  bool AnimationDirectionValue(Token& token);\n  bool AnimationFillModeValue(Token& token);\n  bool AnimationIterCountValue(Token& token);\n  bool AnimationPlayStateValue(Token& token);\n  bool AnimationNameValue(Token& token);\n\n  CSSValue TokenToLength(const Token& token);\n  void TokenToLengthTarget(const Token& token, CSSValue& target);\n\n  bool AtEnd();\n  bool ConsumePosition(bool& horizontal_edge, bool& vertical_edge,\n                       CSSValue& ret);\n  /// LengthOrPercentage\n  CSSValue Length();\n  void LengthTo(CSSValue& target);\n\n  /// NumberOrPercentage\n  lepus::Value NumberOrPercentage();\n  /// NumberOnly\n  lepus::Value NumberOnly(bool nonnegative);\n  /// <bg-layer> =\n  ///  <bg-image> || <bg-position> [ / <bg-size> ]? || <repeat-style> || <box>\n  ///  || <box>\n  bool BackgroundLayer(CSSBackgroundLayer& layer, bool mask);\n  /// <bg-image> =\n  ///   none |\n  ///   <url> |\n  ///   <gradient>\n  bool BackgroundImage();\n  /// <bg-origin-box> = <box>\n  lepus::Value BackgroundBox();\n  /// <bg-clip-box> = <box>\n  lepus::Value BackgroundClip();\n  /// <box> = [ border-box | padding-box | content-box ]\n  bool Box(Token& token);\n  /// <bg-position-and-size> = <bg-position> [ / <bg-size>] ?\n  bool BackgroundPositionAndSize(CSSBackgroundLayer& layer);\n  /// <bg-position> = [\n  ///               [ left | center | right | top | bottom | <length-percentage>\n  ///               ]\n  ///               |\n  ///               [ left | center | right | <length-percentage> ]  [ top |\n  ///               center | bottom | <length-percentage> ]\n  ///               |\n  ///               [ center | [ left | right ] <length-percentage> ? ] && [\n  ///               center | [ top | bottom ] < length-percentage> ? ]\n  ///             ]\n  bool BackgroundPosition(CSSValue& x, CSSValue& y);\n  /// <bg-size> = [ <length-percentage> | auto ] {1, 2} | cover | contain\n  bool BackgroundSize(CSSValue& x, CSSValue& y);\n  /// <repeat-style> = repeat-x | repeat-y | [ repeat | no-repeat] {1, 2}\n  bool BackgroundRepeatStyle(uint32_t& x, uint32_t& y);\n  /// <text-decoration-line> = none | [underline || line-through]\n  bool TextDecorationLine();\n  /// <text-decoration-style> = solid | double | dotted | dashed | wavy\n  bool TextDecorationStyle();\n  /// <format> = format('<string>')\n  bool Format();\n  /// <local> = local('<string>')\n  bool Local();\n  /// <url> = url('<string>')\n  bool Url();\n  /// <gradient> =\n  ///   <linear-gradient> |\n  ///   <radial-gradient>\n  bool Gradient();\n  /// <linear-gradient> =linear-gradient ( [ <angle> | to <side-or-corner> ] ? ,\n  /// <color-stop-list>)\n  bool LinearGradient();\n  /// <radial-gradient> = radial-gradient( [ <ending-shape> || <size> ] ? [ at\n  /// <position> ] ?, <color-stop-list>)\n  bool RadialGradient();\n  bool ConicGradient();\n  /// <ending-shape> = ellipse | circle\n  bool EndingShape();\n  /// <size> = closest-side | closest-corner | farthest-side | farthest-corner\n  bool EndingShapeSizeIdent();\n  /// <color-stop-list> = [<color> [, <percentage>] ?]*\n  bool ColorStopList(const fml::RefPtr<lepus::CArray>& colors,\n                     const fml::RefPtr<lepus::CArray>& stops);\n\n  /// <angle> = <number> [ deg | grad | rad | turn]\n  bool AngleValue(Token& token);\n  // <time> = <number> [ ms | s]\n  bool TimeValue(Token& token);\n  bool TransitionProperty(Token& token);\n  bool TimingFunctionValue(Token& token);\n  bool ParseTimingFunctionParams(const Token& function_token,\n                                 fml::RefPtr<lepus::CArray>& arr);\n  void ConsumeBorderLineWidth(Token& token, CSSValue& result);\n  bool BorderWidthIdent(Token& token);\n  bool BorderStyleIdent(Token& token);\n  bool TransformFunctionIdent(Token& token);\n\n  void ConsumeColor(CSSValue& result);\n  bool ShadowOptionIdent(Token& token);\n  template <typename Func, typename... Args>\n  CSSValue ConsumeCommaSeparatedList(Func callback, Args&&... args);\n  /// <color> =\n  ///   <rgba()> |\n  ///   <rgb()> |\n  ///   <hsla()> |\n  ///   <hsl()> |\n  ///   <hex-color> |\n  ///   <named-color>\n  bool Color();\n  /// <rgba()> = rgba( <number> , <number>, <number> , <alpha-value>)\n  bool RGBAColor();\n  /// <rgb()> = rgb(<number> , <number> , <number>)\n  bool RGBColor();\n  /// <hsla()> = hsla( <number> | <angle>, <percentage>, <percentage>\n  bool HSLAColor();\n  bool HSLColor();\n  /// <hex-color> = #<number>\n  bool HexColor();\n  /// <number/percentage-value> = ( <percentage-value> | <number>)\n  bool NumberOrPercentValue(Token& token);\n  /// <percentage-value> = <number> %\n  bool PercentageValue(Token& token);\n  bool DimensionValue(Token& token);\n  bool NumberValue(Token& token);\n  bool HexValue(Token& token);\n  /// <basic-shape> =\n  ///   inset( <shape-arg>{1,4} [round <border-radius>]? ) |\n  ///   circle( [<shape-radius>] [at <position>]? ) |\n  ///   ellipse( [<shape-radius>{2}]  [at <position>]? ) |\n  ///   polygon( [<fill-rule>,]  [<shape-arg> <shape-arg>]# ) |\n  ///   path( [<fill-rule>,]? <string>)\n  bool BasicShape();\n  /// circle( [shape-radius]? [at <position>]?)\n  bool BasicShapeCircle();\n  /// ellipse([<shape-radius>{2}]? [at <position>]?)\n  bool BasicShapeEllipse();\n  bool ConsumeLengthAndSetValue(fml::RefPtr<lepus::CArray>& arr);\n  bool AtPositionAndSetValue(fml::RefPtr<lepus::CArray>&);\n  bool ConsumePositionAndSetValue(fml::RefPtr<lepus::CArray>&);\n  /// path(<string>)\n  /// a string of SVG path data follow EBNF grammar\n  bool BasicShapePath();\n  /// super-ellipse([<shape-radius>{2}] [<number>{2}] [at <position>] ?)\n  bool SuperEllipse();\n\n  /// inset([<length-percentage>{1,4} [(round | super-ellipse ex ey)\n  /// <border-radius>]?])\n  bool BasicShapeInset();\n\n  /// <length-percentage>{1,4} [/ <length-percentage>{1,4}]?\n  bool BorderRadius(CSSValue horizontal_radii[4], CSSValue vertical_radii[4]);\n\n  void PushValue(const StackValue& value);\n  void PushValue(StackValue&& value);\n  const StackValue& PopValue();\n\n  // Scanner function\n  bool CheckAndAdvance(TokenType type);\n  bool Consume(TokenType type);\n  bool Check(TokenType type);\n  void Advance();\n\n  // utils function\n  static uint32_t TokenTypeToTextENUM(TokenType token_type);\n  static uint32_t TokenTypeToENUM(TokenType token_type);\n  static uint32_t TokenTypeToBorderWidth(TokenType token_type);\n  static int TokenTypeToShadowOption(TokenType token_type);\n  static double GetColorValue(const Token& token, double max_value = 255);\n  static StackValue MakeColorValue(const Token token_list[]);\n  static int64_t TokenToInt(const Token& token);\n  static double TokenToDouble(const Token& token);\n  static float TokenToAngleValue(const Token& token);\n  static double TimeToNumber(const Token& token);\n  static starlight::TransformType TokenToTransformFunction(const Token& token);\n  static starlight::AnimationPropertyType TokenToTransitionType(\n      const Token& token, const CSSParserConfigs& configs);\n  static starlight::TimingFunctionType TokenToTimingFunctionType(\n      const Token& token);\n  static starlight::AnimationDirectionType TokenToAnimationDirectionType(\n      const Token& token);\n  static starlight::AnimationFillModeType TokenToAnimationFillModeType(\n      const Token& token);\n  static double TokenToAnimationIterCount(const Token& token);\n  static starlight::AnimationPlayStateType TokenToAnimationPlayState(\n      const Token& token);\n  static CSSValue ConsumeTimingFunction(const Token& token,\n                                        const CSSParserConfigs& configs);\n  static void BackgroundLayerToArray(const CSSBackgroundLayer& layer,\n                                     lepus::CArray* image_array,\n                                     lepus::CArray* position_array,\n                                     lepus::CArray* size_array,\n                                     lepus::CArray* origin_array,\n                                     lepus::CArray* repeat_array,\n                                     lepus::CArray* clip_array);\n  static void ClampColorAndStopList(base::Vector<uint32_t>& colors,\n                                    base::Vector<float>& stops);\n  static void ClampColorAndStopListAtFront(base::Vector<uint32_t>& colors,\n                                           base::Vector<float>& stops,\n                                           uint32_t first_positive_index);\n  static void ClampColorAndStopListAtBack(base::Vector<uint32_t>& colors,\n                                          base::Vector<float>& stops,\n                                          uint32_t tail_position);\n  static bool IsValidOpenTypeTag(const std::string& tag);\n  void SkipWhitespaceToken();\n  bool ConsumeAndSave(TokenType tokenType, Token& token);\n  bool LengthOrPercentageValue(Token& token);\n  bool ParseVarReference(VarReference& ref);\n  CSSValue FilterValue(const Token& function_token, starlight::FilterType type);\n  CSSValue ParseFilterValue(starlight::FilterType filter_type);\n  bool ConsumeFilter(Token& token, starlight::FilterType type);\n  StackValue stack_value_;\n  Token current_token_;\n  Token previous_token_;\n  Scanner scanner_;\n  bool legacy_parser_ = true;\n  bool enable_transform_legacy_ = false;\n  bool enable_time_legacy_ = false;\n  CSSParserConfigs parser_configs_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_CSS_STRING_PARSER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/css_string_parser_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#define private public\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#undef private\n\n#include <array>\n#include <cmath>\n#include <cstring>\n#include <limits>\n#include <map>\n#include <string>\n#include <tuple>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/css_color.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/css/parser/css_parser_configs.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(CSSStringParser, offset_rotate_value) {\n  CSSParserConfigs configs;\n  {\n    std::string raw = \"auto\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseOffsetRotate();\n    EXPECT_TRUE(result.IsNumber());\n\n    auto auto_value = result.Number();\n    EXPECT_FLOAT_EQ(auto_value, -1024.0);\n\n    raw = \"45deg\";\n    parser = CSSStringParser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                             configs};\n    result = parser.ParseOffsetRotate();\n    EXPECT_TRUE(result.IsNumber());\n    auto deg_value = result.Number();\n    EXPECT_FLOAT_EQ(deg_value, 45.0);\n\n    raw = \"100%\";\n    parser = CSSStringParser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                             configs};\n    result = parser.ParseOffsetRotate();\n    EXPECT_TRUE(result.IsEmpty());\n\n    raw = \"wrongvalue\";\n    parser = CSSStringParser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                             configs};\n    result = parser.ParseOffsetRotate();\n    EXPECT_TRUE(result.IsEmpty());\n  }\n}\n\nTEST(CSSStringParser, parse_cursor) {\n  CSSParserConfigs configs;\n  {\n    std::string cursor = \"help\";\n    CSSStringParser parser{cursor.c_str(), (uint32_t)cursor.size(), configs};\n    CSSValue result = parser.ParseCursor();\n    EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n    auto cursor_array = result.GetArray();\n    EXPECT_EQ(cursor_array->size(), static_cast<size_t>(2));\n    EXPECT_EQ(cursor_array->get(0).UInt32(),\n              static_cast<uint32_t>(starlight::CursorType::kKeyword));\n    EXPECT_EQ(cursor_array->get(1).StdString(), cursor);\n  }\n  std::string cursor_url = \"hand.cur\";\n  std::string cursor_key = \"pointer\";\n  {\n    std::string cursor = \"url(\" + cursor_url + \"), \" + cursor_key;\n    CSSStringParser parser{cursor.c_str(), (uint32_t)cursor.size(), configs};\n    CSSValue result = parser.ParseCursor();\n    EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n    auto cursor_array = result.GetArray();\n    EXPECT_EQ(cursor_array->size(), static_cast<size_t>(4));\n    EXPECT_EQ(cursor_array->get(0).UInt32(),\n              static_cast<uint32_t>(starlight::CursorType::kUrl));\n    auto url = cursor_array->get(1).Array();\n    EXPECT_EQ(url->get(0).StdString(), cursor_url);\n    EXPECT_NEAR(url->get(1).Number(), 0, 0.0001);\n    EXPECT_NEAR(url->get(2).Number(), 0, 0.0001);\n    EXPECT_EQ(cursor_array->get(2).UInt32(),\n              static_cast<uint32_t>(starlight::CursorType::kKeyword));\n    EXPECT_EQ(cursor_array->get(3).StdString(), cursor_key);\n  }\n  {\n    std::string cursor = \"url(\" + cursor_url + \") 10 20, \" + cursor_key;\n    CSSStringParser parser{cursor.c_str(), (uint32_t)cursor.size(), configs};\n    CSSValue result = parser.ParseCursor();\n    EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n    auto cursor_array = result.GetArray();\n    EXPECT_EQ(cursor_array->size(), static_cast<size_t>(4));\n    EXPECT_EQ(cursor_array->get(0).UInt32(),\n              static_cast<uint32_t>(starlight::CursorType::kUrl));\n    auto url = cursor_array->get(1).Array();\n    EXPECT_EQ(url->get(0).StdString(), cursor_url);\n    EXPECT_NEAR(url->get(1).Number(), 10, 0.0001);\n    EXPECT_NEAR(url->get(2).Number(), 20, 0.0001);\n    EXPECT_EQ(cursor_array->get(2).UInt32(),\n              static_cast<uint32_t>(starlight::CursorType::kKeyword));\n    EXPECT_EQ(cursor_array->get(3).StdString(), cursor_key);\n  }\n  {\n    std::string cursor = \"url(\" + cursor_url + \") 10, \" + cursor_key;\n    CSSStringParser parser{cursor.c_str(), (uint32_t)cursor.size(), configs};\n    CSSValue result = parser.ParseCursor();\n    EXPECT_EQ(result.GetPattern(), CSSValuePattern::ARRAY);\n    auto cursor_array = result.GetArray();\n    EXPECT_EQ(cursor_array->size(), static_cast<size_t>(4));\n    EXPECT_EQ(cursor_array->get(0).UInt32(),\n              static_cast<uint32_t>(starlight::CursorType::kUrl));\n    auto url = cursor_array->get(1).Array();\n    EXPECT_EQ(url->get(0).StdString(), cursor_url);\n    EXPECT_NEAR(url->get(1).Number(), 0, 0.0001);\n    EXPECT_NEAR(url->get(2).Number(), 0, 0.0001);\n    EXPECT_EQ(cursor_array->get(2).UInt32(),\n              static_cast<uint32_t>(starlight::CursorType::kKeyword));\n    EXPECT_EQ(cursor_array->get(3).StdString(), cursor_key);\n  }\n}\n\nTEST(CSSStringParser, test_lerp_color) {\n  uint32_t start = CSSColor(150, 150, 150, 1).Cast();\n  uint32_t end = CSSColor(0, 0, 0, 0).Cast();\n  uint32_t mix = CSSStringParser::LerpColor(start, end, 0.f, 1.5f, 1.f);\n  EXPECT_EQ(mix >> 16 & 0xFF, 50);\n  EXPECT_EQ(mix >> 8 & 0xFF, 50);\n  EXPECT_EQ(mix & 0xFF, 50);\n  EXPECT_EQ(mix >> 24 & 0xFF, 85);\n\n  mix = CSSStringParser::LerpColor(start, end, 0.8f, 1.2f, 1.f);\n  EXPECT_EQ(mix >> 16 & 0xFF, 75);\n  EXPECT_EQ(mix >> 8 & 0xFF, 75);\n  EXPECT_EQ(mix & 0xFF, 75);\n  EXPECT_EQ(mix >> 24 & 0xFF, 128);\n}\n\nTEST(CSSStringParse, font_face_src) {\n  std::string url = R\"(\n    url(\"/fonts/OpenSans-Regular-webfont.woff2\") format(\"woff2\"), url(\"/fonts/OpenSans-Regular-webfont.woff\")\n      format(\"woff\"), local(\"PingFang SC\"), local(song);\n  )\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{url.c_str(), static_cast<uint32_t>(url.size()),\n                         configs};\n\n  auto result = parser.ParseFontSrc();\n\n  EXPECT_TRUE(result.IsArray());\n\n  auto arr = result.GetArray();\n\n  EXPECT_EQ(arr->size(), static_cast<size_t>(8));\n\n  // type 1\n  EXPECT_TRUE(arr->get(0).IsUInt32());\n  EXPECT_EQ(arr->get(0).UInt32(),\n            static_cast<uint32_t>(starlight::FontFaceSrcType::kUrl));\n  // url 1\n  EXPECT_TRUE(arr->get(1).IsString());\n  EXPECT_EQ(arr->get(1).StdString(),\n            std::string(\"/fonts/OpenSans-Regular-webfont.woff2\"));\n\n  // type 2\n  EXPECT_TRUE(arr->get(2).IsUInt32());\n  EXPECT_EQ(arr->get(2).UInt32(),\n            static_cast<uint32_t>(starlight::FontFaceSrcType::kUrl));\n\n  // url 2\n  EXPECT_TRUE(arr->get(3).IsString());\n  EXPECT_EQ(arr->get(3).StdString(),\n            std::string(\"/fonts/OpenSans-Regular-webfont.woff\"));\n\n  // type 3\n  EXPECT_TRUE(arr->get(4).IsUInt32());\n  EXPECT_EQ(arr->get(4).UInt32(),\n            static_cast<uint32_t>(starlight::FontFaceSrcType::kLocal));\n\n  // local name 3\n  EXPECT_TRUE(arr->get(5).IsString());\n  EXPECT_EQ(arr->get(5).StdString(), std::string(\"PingFang SC\"));\n\n  // type 4\n  EXPECT_TRUE(arr->get(6).IsUInt32());\n  EXPECT_EQ(arr->get(6).UInt32(),\n            static_cast<uint32_t>(starlight::FontFaceSrcType::kLocal));\n\n  // local name 4\n  EXPECT_TRUE(arr->get(7).IsString());\n  EXPECT_EQ(arr->get(7).StdString(), std::string(\"song\"));\n}\n\nTEST(CSSStringParse, font_face_src_failed) {\n  std::string url1 = R\"(\n    url(\"/fonts/OpenSans-Regular-webfont.woff2\") url(\"/fonts/OpenSans-Regular-webfont.woff\")\n      format(\"woff\"), local(\"PingFang SC\");\n  )\";\n\n  std::string url2 = R\"(\n    url(\"/fonts/OpenSans-Regular-webfont.woff2\") local(\"PingFang SC\");\n  )\";\n\n  std::string url3 = R\"(\n    local(\"PingFang SC\") url(\"/fonts/OpenSans-Regular-webfont.woff2\") ;\n  )\";\n\n  std::string url4 = R\"(\n    local(\"PingFang SC\"), , url(\"/fonts/OpenSans-Regular-webfont.woff2\") ;\n  )\";\n\n  CSSParserConfigs configs;\n  {\n    CSSStringParser parser{url1.c_str(), static_cast<uint32_t>(url1.size()),\n                           configs};\n\n    auto result = parser.ParseFontSrc();\n\n    EXPECT_TRUE(result.IsArray());\n    auto arr = result.GetArray();\n    EXPECT_TRUE(arr->size() == 0);\n  }\n\n  {\n    CSSStringParser parser{url2.c_str(), static_cast<uint32_t>(url2.size()),\n                           configs};\n\n    auto result = parser.ParseFontSrc();\n\n    EXPECT_TRUE(result.IsArray());\n    auto arr = result.GetArray();\n    EXPECT_TRUE(arr->size() == 0);\n  }\n\n  {\n    CSSStringParser parser{url3.c_str(), static_cast<uint32_t>(url3.size()),\n                           configs};\n\n    auto result = parser.ParseFontSrc();\n\n    EXPECT_TRUE(result.IsArray());\n    auto arr = result.GetArray();\n    EXPECT_TRUE(arr->size() == 0);\n  }\n\n  {\n    CSSStringParser parser{url4.c_str(), static_cast<uint32_t>(url4.size()),\n                           configs};\n\n    auto result = parser.ParseFontSrc();\n\n    EXPECT_TRUE(result.IsArray());\n    auto arr = result.GetArray();\n    EXPECT_TRUE(arr->size() == 0);\n  }\n}\n\nTEST(CSSStringParse, font_weight_parser) {\n  std::string weight = R\"(\n    normal;\n  )\";\n\n  std::string weight2 = R\"(\n    400 600;\n  )\";\n\n  std::string weight3 = R\"(\n    477;\n  )\";\n  CSSParserConfigs configs;\n  {\n    CSSStringParser parser{weight.c_str(), static_cast<uint32_t>(weight.size()),\n                           configs};\n\n    auto result = parser.ParseFontWeight();\n\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.GetArray();\n\n    EXPECT_TRUE(arr->size() == 1);\n    EXPECT_TRUE(arr->get(0).IsInt32());\n    EXPECT_EQ(arr->get(0).Int32(), static_cast<int32_t>(400));\n  }\n\n  {\n    CSSStringParser parser{weight2.c_str(),\n                           static_cast<uint32_t>(weight2.size()), configs};\n\n    auto result = parser.ParseFontWeight();\n\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.GetArray();\n\n    EXPECT_TRUE(arr->size() == 2);\n    EXPECT_TRUE(arr->get(0).IsInt32());\n    EXPECT_EQ(arr->get(0).Int32(), static_cast<int32_t>(400));\n\n    EXPECT_TRUE(arr->get(1).IsInt32());\n    EXPECT_EQ(arr->get(1).Int32(), static_cast<int32_t>(600));\n  }\n\n  {\n    CSSStringParser parser{weight3.c_str(),\n                           static_cast<uint32_t>(weight3.size()), configs};\n\n    auto result = parser.ParseFontWeight();\n\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.GetArray();\n\n    EXPECT_TRUE(arr->size() == 1);\n    EXPECT_TRUE(arr->get(0).IsInt32());\n    EXPECT_EQ(arr->get(0).Int32(), static_cast<int32_t>(500));\n  }\n}\n\nTEST(CSSStringParser, length_valid_and_value) {\n  const char* valid_value[] = {\"auto\",\n                               \"max-content\",\n                               \"0\",\n                               \"0px\",\n                               \"10%\",\n                               \"0.5em\",\n                               \"calc(10% - 0.5em)\",\n                               \"fit-content(10%)\",\n                               \"fit-content(calc(10% - 0.5em))\"};\n  int len = sizeof(valid_value) / sizeof(char*);\n#define make_a_length(value, pattern) \\\n  CSSValue(lepus::Value(value), lynx::tasm::CSSValuePattern::pattern)\n  CSSValue lengths[] = {\n      make_a_length(static_cast<int>(starlight::LengthValueType::kAuto), ENUM),\n      make_a_length(\"max-content\", INTRINSIC),\n      make_a_length(0, NUMBER),\n      make_a_length(0, PX),\n      make_a_length(10, PERCENT),\n      make_a_length(0.5, EM),\n      make_a_length(\"calc(10% - 0.5em)\", CALC),\n      make_a_length(\"fit-content(10%)\", INTRINSIC),\n      make_a_length(\"fit-content(calc(10% - 0.5em))\", INTRINSIC)};\n#undef make_a_length\n\n  CSSParserConfigs configs;\n  for (int i = 0; i < len; i++) {\n    CSSStringParser parser{\n        valid_value[i], static_cast<uint32_t>(strlen(valid_value[i])), configs};\n    CSSValue length = parser.ParseLength();\n    EXPECT_TRUE(length.GetValue().IsEqual(lengths[i].GetValue()));\n    EXPECT_EQ(length.GetPattern(), lengths[i].GetPattern());\n  }\n}\n\nTEST(CSSStringParse, length_invalid) {\n  std::string len = R\"(\n    100 px\n  )\";\n  CSSParserConfigs configs;\n  CSSStringParser parser{len.c_str(), static_cast<uint32_t>(len.size()),\n                         configs};\n\n  auto res = parser.ParseLength();\n  EXPECT_TRUE(res.IsEmpty());\n}\n\nTEST(CSSStringScanner, DecimalPointsInNumbers) {\n  const char* valid[] = {\"0.1px\", \".1px\", nullptr};\n  CSSParserConfigs configs;\n  for (const char** it = valid; *it; it++) {\n    CSSStringParser parser{*it, static_cast<uint32_t>(strlen(*it)), configs};\n    CSSValue length = parser.ParseLength();\n    EXPECT_EQ(length.GetNumber(), 0.1);\n  }\n\n  // decimal point after digits is invalid.\n  const char* invalid = \"1.px\";\n  {\n    CSSStringParser parser{invalid, static_cast<uint32_t>(strlen(invalid)),\n                           configs};\n    CSSValue length = parser.ParseLength();\n    EXPECT_TRUE(length.IsEmpty());\n  }\n}\n\nTEST(CSSStringParser, url) {\n  // url without quote and with keywords, such as 'calc' but without\n  // parenthesis. Should be parsed correctly.\n  {\n    std::string raw =\n        R\"(url(https://ellipse.circle.com/obj/linear-gradient-babel/calcfakq640khkohk/env?x-/intrinsic1679421600&))\";\n    std::string golden =\n        R\"(https://ellipse.circle.com/obj/linear-gradient-babel/calcfakq640khkohk/env?x-/intrinsic1679421600&)\";\n    CSSParserConfigs configs;\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    std::string parsed_url = parser.ParseUrl();\n    EXPECT_EQ(parsed_url, golden);\n  }\n  {\n    // Function keywords with parenthesis but not quoted is an undefined\n    // behavior. Only ensure not crash.\n    std::string raw =\n        R\"(url(https://ellipse.circle.com/obj/linear-gradient-babel/calc()fakq640khkohk/env?x-/intrinsic1679421600&))\";\n    CSSParserConfigs configs;\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    std::string parsed_url = parser.ParseUrl();\n  }\n}\n\nTEST(CSSStringParser, basic_shape_inset_invalid) {\n  CSSParserConfigs configs;\n  {\n    // should close with right parenthesis\n    std::string raw = R\"(inset(10px)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n\n  {\n    // <length-percentage>{1,4}. length should have unit or be a percentage\n    // value.\n    std::string raw = R\"(inset(10 10 10 10))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n\n  {\n    // nothing should appear after right parenthesis\n    std::string raw = R\"(inset(10px 10px 10px 10px) asd)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n\n  {\n    // only accept 'round' and 'super-ellipse'\n    std::string raw = R\"(inset(10px 10px circle))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n\n  {\n    // invalid number of inset params\n    std::string raw = R\"(inset(10px 10px 10px 10px 10px))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n\n  {\n    // border-radius\n    std::string raw = R\"(inset(10px round 10px // 10px))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n\n  {\n    // invalid number of vertical radius params\n    std::string raw = R\"(inset(10px round 10px 10px 10px 10px 10px / 10px))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n\n  {\n    // invalid number of vertical radius params\n    std::string raw =\n        R\"(inset(10px round 10px 10px / 10px 10px 10px 10px 10px))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n\n  {\n    // invalid number of exponents\n    std::string raw =\n        R\"(inset(10px super-ellipse 10 10px 10px / 10px 10px 10px 10px))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n\n  {\n    // invalid number of exponents\n    std::string raw =\n        R\"(inset(10px super-ellipse 10px 10px / 10px 10px 10px 10px))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_FALSE(result.IsArray());\n  }\n}\n\nTEST(CSSStringParser, basic_shape_inset_rect) {\n  // valid inset without rounded corner\n  CSSParserConfigs configs;\n  {\n    std::string raw = R\"(inset(5%))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.Array();\n    EXPECT_EQ(arr->size(), 9);\n\n    EXPECT_EQ(arr->get(1).Number(), 5);\n    EXPECT_EQ(arr->get(2).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n    EXPECT_EQ(arr->get(3).Number(), 5);\n    EXPECT_EQ(arr->get(4).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n    EXPECT_EQ(arr->get(5).Number(), 5);\n    EXPECT_EQ(arr->get(6).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n    EXPECT_EQ(arr->get(7).Number(), 5);\n    EXPECT_EQ(arr->get(8).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  }\n  {\n    std::string raw = R\"(inset(10px 20px))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.Array();\n    EXPECT_EQ(arr->size(), 9);\n\n    EXPECT_EQ(arr->get(1).Number(), 10);\n    EXPECT_EQ(arr->get(2).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(3).Number(), 20);\n    EXPECT_EQ(arr->get(4).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(5).Number(), 10);\n    EXPECT_EQ(arr->get(6).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(7).Number(), 20);\n    EXPECT_EQ(arr->get(8).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n  }\n  {\n    std::string raw = R\"(inset(10px 20% 30ppx))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.Array();\n    EXPECT_EQ(arr->size(), 9);\n\n    EXPECT_EQ(arr->get(1).Number(), 10);\n    EXPECT_EQ(arr->get(2).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(3).Number(), 20);\n    EXPECT_EQ(arr->get(4).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n    EXPECT_EQ(arr->get(5).Number(), 30);\n    EXPECT_EQ(arr->get(6).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PPX));\n    EXPECT_EQ(arr->get(7).Number(), 20);\n    EXPECT_EQ(arr->get(8).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  }\n  {\n    std::string raw = R\"(inset(10px 20% 30ppx 40rpx))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.Array();\n    EXPECT_EQ(arr->size(), 9);\n\n    EXPECT_EQ(arr->get(1).Number(), 10);\n    EXPECT_EQ(arr->get(2).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(3).Number(), 20);\n    EXPECT_EQ(arr->get(4).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n    EXPECT_EQ(arr->get(5).Number(), 30);\n    EXPECT_EQ(arr->get(6).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PPX));\n    EXPECT_EQ(arr->get(7).Number(), 40);\n    EXPECT_EQ(arr->get(8).Number(),\n              static_cast<uint32_t>(CSSValuePattern::RPX));\n  }\n}\n\nTEST(CSSStringParser, basic_shape_inset_rounded) {\n  CSSParserConfigs configs;\n  {\n    std::string raw = R\"(inset(10px round 10px / 20px))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.Array();\n    EXPECT_EQ(arr->size(), 25);\n\n    // sides\n    EXPECT_EQ(arr->get(1).Number(), 10);\n    EXPECT_EQ(arr->get(2).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(3).Number(), 10);\n    EXPECT_EQ(arr->get(4).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(5).Number(), 10);\n    EXPECT_EQ(arr->get(6).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(7).Number(), 10);\n    EXPECT_EQ(arr->get(8).Number(), static_cast<uint32_t>(CSSValuePattern::PX));\n\n    // top-left\n    EXPECT_EQ(arr->get(9).Number(), 10);\n    EXPECT_EQ(arr->get(10).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(11).Number(), 20);\n    EXPECT_EQ(arr->get(12).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n\n    // top-right\n    EXPECT_EQ(arr->get(13).Number(), 10);\n    EXPECT_EQ(arr->get(14).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(15).Number(), 20);\n    EXPECT_EQ(arr->get(16).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n\n    // bottom-right\n    EXPECT_EQ(arr->get(17).Number(), 10);\n    EXPECT_EQ(arr->get(18).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(19).Number(), 20);\n    EXPECT_EQ(arr->get(20).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n\n    // bottom-left\n    EXPECT_EQ(arr->get(21).Number(), 10);\n    EXPECT_EQ(arr->get(22).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(23).Number(), 20);\n    EXPECT_EQ(arr->get(24).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n  }\n  {\n    std::string raw = R\"(inset(10px round 10px 20ppx / 20px 30% 40%))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.Array();\n    EXPECT_EQ(arr->size(), 25);\n\n    // top-left\n    EXPECT_EQ(arr->get(9).Number(), 10);\n    EXPECT_EQ(arr->get(10).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(11).Number(), 20);\n    EXPECT_EQ(arr->get(12).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n\n    // top-right\n    EXPECT_EQ(arr->get(13).Number(), 20);\n    EXPECT_EQ(arr->get(14).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PPX));\n    EXPECT_EQ(arr->get(15).Number(), 30);\n    EXPECT_EQ(arr->get(16).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n\n    // bottom-right\n    EXPECT_EQ(arr->get(17).Number(), 10);\n    EXPECT_EQ(arr->get(18).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(19).Number(), 40);\n    EXPECT_EQ(arr->get(20).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n\n    // bottom-left\n    EXPECT_EQ(arr->get(21).Number(), 20);\n    EXPECT_EQ(arr->get(22).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PPX));\n    EXPECT_EQ(arr->get(23).Number(), 30);\n    EXPECT_EQ(arr->get(24).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  }\n}\n\nTEST(CSSStringParser, basic_shape_inset_ellipse_corner) {\n  CSSParserConfigs configs;\n  {\n    std::string raw =\n        R\"(inset(10px super-ellipse 5 6 10px 20ppx / 20px 30% 40%))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto result = parser.ParseShapePath();\n    EXPECT_TRUE(result.IsArray());\n\n    auto arr = result.Array();\n    EXPECT_EQ(arr->size(), 27);\n\n    EXPECT_EQ(arr->get(9).Number(), 5);\n    EXPECT_EQ(arr->get(10).Number(), 6);\n\n    // top-left\n    EXPECT_EQ(arr->get(11).Number(), 10);\n    EXPECT_EQ(arr->get(12).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(13).Number(), 20);\n    EXPECT_EQ(arr->get(14).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n\n    // top-right\n    EXPECT_EQ(arr->get(15).Number(), 20);\n    EXPECT_EQ(arr->get(16).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PPX));\n    EXPECT_EQ(arr->get(17).Number(), 30);\n    EXPECT_EQ(arr->get(18).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n\n    // bottom-right\n    EXPECT_EQ(arr->get(19).Number(), 10);\n    EXPECT_EQ(arr->get(20).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PX));\n    EXPECT_EQ(arr->get(21).Number(), 40);\n    EXPECT_EQ(arr->get(22).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n\n    // bottom-left\n    EXPECT_EQ(arr->get(23).Number(), 20);\n    EXPECT_EQ(arr->get(24).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PPX));\n    EXPECT_EQ(arr->get(25).Number(), 30);\n    EXPECT_EQ(arr->get(26).Number(),\n              static_cast<uint32_t>(CSSValuePattern::PERCENT));\n  }\n}\n\nTEST(CSSStringParser, valid_blur_value) {\n  constexpr const char* valid_blur[] = {\n      \"10px\",\n      \"1.5rpx\",\n  };\n  constexpr int value_len = sizeof(valid_blur) / sizeof(char*);\n  const CSSValue valid_values[] = {CSSValue(10, CSSValuePattern::PX),\n                                   CSSValue(1.5, CSSValuePattern::RPX)};\n  for (int i = 0; i < value_len; i++) {\n    CSSStringParser parser{valid_blur[i],\n                           static_cast<uint32_t>(strlen(valid_blur[i])),\n                           CSSParserConfigs()};\n    CSSValue blur = parser.ParseFilterValue(starlight::FilterType::kBlur);\n    EXPECT_EQ(valid_values[i].GetNumber(), blur.GetNumber());\n    EXPECT_EQ(static_cast<uint32_t>(valid_values[i].GetPattern()),\n              static_cast<uint32_t>(blur.GetPattern()));\n  }\n}\n\nTEST(CSSStringParser, invalid_blur_value) {\n  const char* invalid_blur_str[] = {\" \",   \"px\",   \"not_blur_value\",\n                                    \"10%\", \"2px9\", nullptr};\n  for (const char** it = invalid_blur_str; *it; it++) {\n    CSSStringParser parser{*it, static_cast<uint32_t>(strlen(*it)),\n                           CSSParserConfigs()};\n    EXPECT_TRUE(\n        parser.ParseFilterValue(starlight::FilterType::kBlur).IsEmpty());\n  }\n}\n\nTEST(CSSStringParser, valid_grayscale_value) {\n  constexpr const char* valid_grayscale_str[] = {\"0\" /* 0 doesn't need unit*/,\n                                                 \"50%\", \"100%\", \"0.5\", \".5\"};\n  CSSValue grayscale_values[] = {CSSValue(0, CSSValuePattern::PERCENT),\n                                 CSSValue(50, CSSValuePattern::PERCENT),\n                                 CSSValue(100, CSSValuePattern::PERCENT),\n                                 CSSValue(50, CSSValuePattern::PERCENT),\n                                 CSSValue(50, CSSValuePattern::PERCENT)};\n  constexpr int num_str = sizeof(valid_grayscale_str) / sizeof(char*);\n  for (int i = 0; i < num_str; i++) {\n    CSSStringParser parser{\n        valid_grayscale_str[i],\n        static_cast<uint32_t>(strlen(valid_grayscale_str[i])),\n        CSSParserConfigs()};\n    CSSValue grayscale =\n        parser.ParseFilterValue(starlight::FilterType::kGrayscale);\n    EXPECT_EQ(grayscale_values[i].GetNumber(), grayscale.GetNumber());\n    EXPECT_EQ(static_cast<uint32_t>(grayscale_values[i].GetPattern()),\n              static_cast<uint32_t>(grayscale.GetPattern()));\n  }\n}\n\nTEST(CSSStringParser, valid_brightness_value) {\n  constexpr const char* valid_brightness_str[] = {\"0\" /* 0 doesn't need unit*/,\n                                                  \"50%\", \"100%\", \"0.5\", \".5\"};\n  CSSValue brightness_values[] = {CSSValue(0.0, CSSValuePattern::NUMBER),\n                                  CSSValue(0.5, CSSValuePattern::NUMBER),\n                                  CSSValue(1.0, CSSValuePattern::NUMBER),\n                                  CSSValue(0.5, CSSValuePattern::NUMBER),\n                                  CSSValue(0.5, CSSValuePattern::NUMBER)};\n  constexpr int num_str = sizeof(valid_brightness_str) / sizeof(char*);\n  for (int i = 0; i < num_str; i++) {\n    CSSStringParser parser{\n        valid_brightness_str[i],\n        static_cast<uint32_t>(strlen(valid_brightness_str[i])),\n        CSSParserConfigs()};\n    CSSValue brightness =\n        parser.ParseFilterValue(starlight::FilterType::kBrightness);\n    EXPECT_EQ(brightness_values[i].GetNumber(), brightness.GetNumber());\n    EXPECT_EQ(static_cast<uint32_t>(brightness_values[i].GetPattern()),\n              static_cast<uint32_t>(brightness.GetPattern()));\n  }\n}\n\nTEST(CSSStringParser, valid_contrast_value) {\n  constexpr const char* valid_contrast_str[] = {\"0\" /* 0 doesn't need unit*/,\n                                                \"50%\", \"100%\", \"0.5\", \".5\"};\n  CSSValue contrast_values[] = {CSSValue(0.0, CSSValuePattern::NUMBER),\n                                CSSValue(0.5, CSSValuePattern::NUMBER),\n                                CSSValue(1.0, CSSValuePattern::NUMBER),\n                                CSSValue(0.5, CSSValuePattern::NUMBER),\n                                CSSValue(0.5, CSSValuePattern::NUMBER)};\n  constexpr int num_str = sizeof(valid_contrast_str) / sizeof(char*);\n  for (int i = 0; i < num_str; i++) {\n    CSSStringParser parser{valid_contrast_str[i],\n                           static_cast<uint32_t>(strlen(valid_contrast_str[i])),\n                           CSSParserConfigs()};\n    CSSValue contrast =\n        parser.ParseFilterValue(starlight::FilterType::kContrast);\n    EXPECT_EQ(contrast_values[i].GetNumber(), contrast.GetNumber());\n    EXPECT_EQ(static_cast<uint32_t>(contrast_values[i].GetPattern()),\n              static_cast<uint32_t>(contrast.GetPattern()));\n  }\n}\n\nTEST(CSSStringParser, valid_saturate_value) {\n  constexpr const char* valid_saturate_str[] = {\"0\" /* 0 doesn't need unit*/,\n                                                \"50%\", \"100%\", \"0.5\", \".5\"};\n  CSSValue saturate_values[] = {\n      CSSValue(0.0, CSSValuePattern::NUMBER),\n      CSSValue(0.5, CSSValuePattern::NUMBER),\n      CSSValue(1.0, CSSValuePattern::NUMBER),\n      CSSValue(0.5, CSSValuePattern::NUMBER),\n      CSSValue(0.5, CSSValuePattern::NUMBER),\n  };\n  constexpr int num_str = sizeof(valid_saturate_str) / sizeof(char*);\n  for (int i = 0; i < num_str; i++) {\n    CSSStringParser parser{valid_saturate_str[i],\n                           static_cast<uint32_t>(strlen(valid_saturate_str[i])),\n                           CSSParserConfigs()};\n    CSSValue saturate =\n        parser.ParseFilterValue(starlight::FilterType::kSaturate);\n    EXPECT_EQ(saturate_values[i].GetNumber(), saturate.GetNumber());\n    EXPECT_EQ(static_cast<uint32_t>(saturate_values[i].GetPattern()),\n              static_cast<uint32_t>(saturate.GetPattern()));\n  }\n}\n\nTEST(CSSStringParser, parse_variable_positions) {\n  CSSParserConfigs configs;\n  // single var without fallback\n  {\n    std::string raw = \"var(--bg-color)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    CSSValue result = parser.ParseVariable();\n    EXPECT_TRUE(result.IsVariable());\n\n    // optionals_ is private; test file exposes private members via\n    // #define private public trick at top of this test file.\n    auto& refs = result.optionals_.Get<CSSValue::VarReferenceField>();\n    ASSERT_EQ(refs.size(), 1u);\n    auto& ref = refs[0];\n\n    // start should point to the 'v' of \"var(\" which is index 0\n    EXPECT_EQ(ref.start, static_cast<size_t>(0));\n    // end should point to the index after the closing ')'\n    EXPECT_EQ(ref.end, static_cast<size_t>(raw.size()));\n    EXPECT_EQ(ref.Name(raw), \"--bg-color\");\n    EXPECT_TRUE(ref.fallback.empty());\n  }\n\n  // var with fallback\n  {\n    std::string raw = \"prefix var(--a, fallback) suffix\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    CSSValue result = parser.ParseVariable();\n    EXPECT_TRUE(result.IsVariable());\n\n    auto& refs = result.optionals_.Get<CSSValue::VarReferenceField>();\n    ASSERT_EQ(refs.size(), 1u);\n    auto& ref = refs[0];\n\n    // locate the var occurrence in the raw string to compute expected start\n    size_t idx = raw.find(\"var(\");\n    EXPECT_NE(idx, std::string::npos);\n    EXPECT_EQ(ref.start, idx);\n    EXPECT_EQ(ref.end, idx + std::string(\"var(--a, fallback)\").size());\n    EXPECT_EQ(ref.Name(raw), \"--a\");\n    ASSERT_TRUE(!ref.fallback.empty());\n    EXPECT_EQ(ref.fallback.str(), \" fallback\");\n  }\n}\n\nTEST(CSSStringParser, parse_variable_multiple_and_malformed) {\n  CSSParserConfigs configs;\n\n  // multiple var occurrences\n  {\n    std::string raw = \"foo var(--a) middle var(--b, fallback) end\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    CSSValue result = parser.ParseVariable();\n    EXPECT_TRUE(result.IsVariable());\n    ASSERT_TRUE(result.optionals_.HasValue<CSSValue::VarReferenceField>());\n\n    auto& refs = result.optionals_.Get<CSSValue::VarReferenceField>();\n    ASSERT_EQ(refs.size(), 2u);\n\n    size_t first_idx = raw.find(\"var(\");\n    size_t second_idx = raw.find(\"var(\", first_idx + 1);\n\n    EXPECT_EQ(refs[0].start, first_idx);\n    EXPECT_EQ(refs[0].end, first_idx + std::string(\"var(--a)\").size());\n    EXPECT_EQ(refs[0].Name(result.AsStdString()), \"--a\");\n    EXPECT_TRUE(refs[0].fallback.empty());\n\n    EXPECT_EQ(refs[1].start, second_idx);\n    EXPECT_EQ(refs[1].end,\n              second_idx + std::string(\"var(--b, fallback)\").size());\n    EXPECT_EQ(refs[1].Name(result.AsStdString()), \"--b\");\n    ASSERT_FALSE(refs[1].fallback.empty());\n    EXPECT_EQ(refs[1].fallback.str(), \" fallback\");\n  }\n\n  // malformed var (missing closing parenthesis) should not produce references\n  {\n    std::string raw = \"start var(--bad end\";  // missing ')'\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    CSSValue result = parser.ParseVariable();\n    EXPECT_FALSE(result.IsVariable());\n    // No valid references should be attached\n    EXPECT_TRUE(!result.optionals_.HasValue<CSSValue::VarReferenceField>() ||\n                result.optionals_.Get<CSSValue::VarReferenceField>().empty());\n  }\n}\n\nTEST(CSSStringParser, parse_variable_nested_fallback) {\n  CSSParserConfigs configs;\n  std::string raw = \"var(--a, var(--b))\";\n  CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                         configs};\n  CSSValue result = parser.ParseVariable();\n  EXPECT_TRUE(result.IsVariable());\n  ASSERT_TRUE(result.optionals_.HasValue<CSSValue::VarReferenceField>());\n\n  auto& refs = result.optionals_.Get<CSSValue::VarReferenceField>();\n  ASSERT_EQ(refs.size(), 1u);\n  EXPECT_EQ(refs[0].Name(result.AsStdString()), \"--a\");\n  ASSERT_FALSE(refs[0].fallback.empty());\n  // fallback should include the nested var text\n  EXPECT_NE(refs[0].fallback.str().find(\"var(--b)\"), std::string::npos);\n}\n\nTEST(CSSStringParser, parse_variable_with_leading_whitespaces) {\n  CSSParserConfigs configs;\n\n  // Test variable with leading whitespaces in name\n  {\n    std::string raw = \"var(  --bg-color  )\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    CSSValue result = parser.ParseVariable();\n    EXPECT_TRUE(result.IsVariable());\n\n    auto& refs = result.optionals_.Get<CSSValue::VarReferenceField>();\n    ASSERT_EQ(refs.size(), 1u);\n    auto& ref = refs[0];\n\n    // start should point to the 'v' of \"var(\" which is index 0\n    EXPECT_EQ(ref.start, static_cast<size_t>(0));\n    // end should point to the index after the closing ')'\n    EXPECT_EQ(ref.end, static_cast<size_t>(raw.size()));\n    // Name should return the variable name without leading/tailing whitespaces\n    EXPECT_EQ(ref.Name(raw), \"--bg-color\");\n    EXPECT_TRUE(ref.fallback.empty());\n  }\n\n  // Test variable with leading whitespaces and fallback\n  {\n    std::string raw = \"var(  --spacing  ,  10px  )\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    CSSValue result = parser.ParseVariable();\n    EXPECT_TRUE(result.IsVariable());\n\n    auto& refs = result.optionals_.Get<CSSValue::VarReferenceField>();\n    ASSERT_EQ(refs.size(), 1u);\n    auto& ref = refs[0];\n\n    // Name should return the variable name without leading/tailing whitespaces\n    EXPECT_EQ(ref.Name(raw), \"--spacing\");\n    ASSERT_FALSE(ref.fallback.empty());\n    // Fallback should preserve its whitespaces\n    EXPECT_EQ(ref.fallback.str(), \"  10px  \");\n  }\n\n  // Test variable with tabs and mixed whitespaces\n  {\n    std::string raw = \"var(\\t  --main-color\\t  )\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    CSSValue result = parser.ParseVariable();\n    EXPECT_TRUE(result.IsVariable());\n\n    auto& refs = result.optionals_.Get<CSSValue::VarReferenceField>();\n    ASSERT_EQ(refs.size(), 1u);\n    auto& ref = refs[0];\n\n    // Name should return the variable name without leading/tailing whitespaces\n    EXPECT_EQ(ref.Name(raw), \"--main-color\");\n    EXPECT_TRUE(ref.fallback.empty());\n  }\n}\n\nTEST(CSSStringParser, invalid_grayscale) {\n  const char* invalid_grayscale_str[] = {\n      \"ab%\", \"50,5%\" /* should have only one value*/, \"50.5 percent\", nullptr};\n  for (const char** it = invalid_grayscale_str; *it; it++) {\n    CSSStringParser parser{*it, static_cast<uint32_t>(strlen(*it)),\n                           CSSParserConfigs()};\n    CSSValue grayscale =\n        parser.ParseFilterValue(starlight::FilterType::kGrayscale);\n    EXPECT_TRUE(grayscale.IsEmpty());\n  }\n}\n\nTEST(CSSStringParser, valid_values) {\n  const char* valid_blur_values_str[] = {\n      \"blur(20px)\",\n      \"grayscale(0.5)\",\n      \"none\",\n      \"BlUr(20px)\" /* case-insensitive */,\n  };\n  constexpr int len = sizeof(valid_blur_values_str) / sizeof(char*);\n  const int valid_blur_values[][3] = {\n      {static_cast<int>(starlight::FilterType::kBlur), 20,\n       static_cast<int>(CSSValuePattern::PX)},\n      {static_cast<const int>(starlight::FilterType::kGrayscale), 50,\n       static_cast<const int>(CSSValuePattern::PERCENT)},\n      {static_cast<const int>(starlight::FilterType::kNone), 0, 0},\n      {static_cast<int>(starlight::FilterType::kBlur), 20,\n       static_cast<int>(CSSValuePattern::PX)}};\n  for (int i = 0; i < len; i++) {\n    CSSStringParser parser{\n        valid_blur_values_str[i],\n        static_cast<uint32_t>(strlen(valid_blur_values_str[i])),\n        CSSParserConfigs()};\n    CSSValue filter = parser.ParseFilter();\n    EXPECT_EQ(filter.GetPattern(), CSSValuePattern::ARRAY);\n    EXPECT_EQ(filter.GetArray()->get(0).Number(), valid_blur_values[i][0]);\n    EXPECT_EQ(filter.GetArray()->get(1).Number(), valid_blur_values[i][1]);\n    EXPECT_EQ(filter.GetArray()->get(2).Number(), valid_blur_values[i][2]);\n  }\n}\n\nTEST(CSSStringParser, invalid_filter_values) {\n  const char* invalid_filter_str[] = {\n      \"abd(2px)\",\n      \"blur(2px), grayscale(2px)\" /* multiple filters are not supported */,\n      \"12px\",\n      nullptr,\n  };\n  for (const char** it = invalid_filter_str; *it; it++) {\n    CSSStringParser parser{*it, static_cast<uint32_t>(strlen(*it)),\n                           CSSParserConfigs()};\n    CSSValue filter = parser.ParseFilter();\n    EXPECT_TRUE(filter.IsEmpty());\n  }\n}\n\nTEST(CSSStringParser, background_image_none) {\n  const char* background_image_none = \"none\";\n  CSSStringParser parser{background_image_none,\n                         static_cast<uint32_t>(strlen(background_image_none)),\n                         CSSParserConfigs()};\n  CSSValue css_value_none = parser.ParseBackgroundImage();\n  EXPECT_FALSE(css_value_none.IsEmpty());\n  EXPECT_TRUE(css_value_none.IsArray());\n  EXPECT_EQ(css_value_none.GetArray()->size(), 1);\n  EXPECT_EQ(css_value_none.GetArray()->get(0).Number(), 0);\n}\n\nTEST(CSSStringParser, aspect_ratio_value) {\n  const char* aspect_ratio_none = \" \";\n  CSSStringParser parser_1{aspect_ratio_none,\n                           static_cast<uint32_t>(strlen(aspect_ratio_none)),\n                           CSSParserConfigs()};\n  CSSValue aspect_ratio_none_css_value = parser_1.ParseAspectRatio();\n  EXPECT_TRUE(aspect_ratio_none_css_value.IsEmpty());\n\n  const char* aspect_ratio_single = \"6\";\n  CSSStringParser parser_2{aspect_ratio_single,\n                           static_cast<uint32_t>(strlen(aspect_ratio_single)),\n                           CSSParserConfigs()};\n  CSSValue aspect_ratio_single_css_value = parser_2.ParseAspectRatio();\n  EXPECT_EQ(aspect_ratio_single_css_value.GetNumber(), 6.0);\n  EXPECT_EQ(aspect_ratio_single_css_value.GetPattern(),\n            CSSValuePattern::NUMBER);\n\n  const char* aspect_ratio_divide = \"1/2\";\n  CSSStringParser parser_3{aspect_ratio_divide,\n                           static_cast<uint32_t>(strlen(aspect_ratio_divide)),\n                           CSSParserConfigs()};\n  CSSValue aspect_ratio_divide_css_value = parser_3.ParseAspectRatio();\n  EXPECT_EQ(aspect_ratio_divide_css_value.GetNumber(), 0.5);\n  EXPECT_EQ(aspect_ratio_divide_css_value.GetPattern(),\n            CSSValuePattern::NUMBER);\n\n  const char* aspect_ratio_divide_zero = \"2/0\";\n  CSSStringParser parser_4{\n      aspect_ratio_divide_zero,\n      static_cast<uint32_t>(strlen(aspect_ratio_divide_zero)),\n      CSSParserConfigs()};\n  CSSValue aspect_ratio_divide_zero_css_value = parser_4.ParseAspectRatio();\n  EXPECT_TRUE(aspect_ratio_divide_zero_css_value.IsEmpty());\n\n  const char* aspect_ratio_chaos = \"2 chaos 1\";\n  CSSStringParser parser_5{aspect_ratio_chaos,\n                           static_cast<uint32_t>(strlen(aspect_ratio_chaos)),\n                           CSSParserConfigs()};\n  CSSValue aspect_ratio_chaos_css_value = parser_5.ParseAspectRatio();\n  EXPECT_TRUE(aspect_ratio_chaos_css_value.IsEmpty());\n\n  const char* aspect_ratio_percentage = \"50%\";\n  CSSStringParser parser_6{\n      aspect_ratio_percentage,\n      static_cast<uint32_t>(strlen(aspect_ratio_percentage)),\n      CSSParserConfigs()};\n  CSSValue aspect_ratio_percentage_css_value = parser_6.ParseAspectRatio();\n  EXPECT_TRUE(aspect_ratio_percentage_css_value.IsEmpty());\n\n  const char* aspect_ratio_equal = \"2/2\";\n  CSSStringParser parser_7{aspect_ratio_equal,\n                           static_cast<uint32_t>(strlen(aspect_ratio_equal)),\n                           CSSParserConfigs()};\n  CSSValue aspect_ratio_equal_css_value = parser_7.ParseAspectRatio();\n  EXPECT_EQ(aspect_ratio_equal_css_value.GetNumber(), 1.0);\n  EXPECT_EQ(aspect_ratio_equal_css_value.GetPattern(), CSSValuePattern::NUMBER);\n\n  const char* aspect_ratio_negative = \"-7\";\n  CSSStringParser parser_8{aspect_ratio_negative,\n                           static_cast<uint32_t>(strlen(aspect_ratio_negative)),\n                           CSSParserConfigs()};\n  CSSValue aspect_ratio_negative_css_value = parser_8.ParseAspectRatio();\n  EXPECT_EQ(aspect_ratio_negative_css_value.GetNumber(), -7.0);\n  EXPECT_EQ(aspect_ratio_negative_css_value.GetPattern(),\n            CSSValuePattern::NUMBER);\n}\n\nTEST(CSSStringParser, gap_value) {\n  const char* gap_none = \" \";\n  CSSStringParser parser_1{gap_none, static_cast<uint32_t>(strlen(gap_none)),\n                           CSSParserConfigs()};\n  auto gap_none_value = parser_1.ParseGap();\n  EXPECT_EQ(gap_none_value.first.GetNumber(), 0);\n  EXPECT_EQ(gap_none_value.first.GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(gap_none_value.second.GetNumber(), 0);\n  EXPECT_EQ(gap_none_value.second.GetPattern(), CSSValuePattern::PX);\n\n  const char* gap_single = \"10px\";\n  CSSStringParser parser_2{gap_single,\n                           static_cast<uint32_t>(strlen(gap_single)),\n                           CSSParserConfigs()};\n  auto gap_single_value = parser_2.ParseGap();\n  EXPECT_EQ(gap_single_value.first.GetNumber(), 10);\n  EXPECT_EQ(gap_single_value.first.GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(gap_single_value.second.GetNumber(), 10);\n  EXPECT_EQ(gap_single_value.second.GetPattern(), CSSValuePattern::PX);\n\n  const char* gap_double = \"10px 20px\";\n  CSSStringParser parser_3{gap_double,\n                           static_cast<uint32_t>(strlen(gap_double)),\n                           CSSParserConfigs()};\n  auto gap_double_value = parser_3.ParseGap();\n  EXPECT_EQ(gap_double_value.first.GetNumber(), 10);\n  EXPECT_EQ(gap_double_value.first.GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(gap_double_value.second.GetNumber(), 20);\n  EXPECT_EQ(gap_double_value.second.GetPattern(), CSSValuePattern::PX);\n\n  const char* gap_single_prev_wrong = \"abc 20px\";\n  CSSStringParser parser_4{gap_single_prev_wrong,\n                           static_cast<uint32_t>(strlen(gap_single_prev_wrong)),\n                           CSSParserConfigs()};\n  auto gap_single_prev_wrong_value = parser_4.ParseGap();\n  EXPECT_EQ(gap_single_prev_wrong_value.first.GetNumber(), 0);\n  EXPECT_EQ(gap_single_prev_wrong_value.first.GetPattern(),\n            CSSValuePattern::PX);\n  EXPECT_EQ(gap_single_prev_wrong_value.second.GetNumber(), 20);\n  EXPECT_EQ(gap_single_prev_wrong_value.second.GetPattern(),\n            CSSValuePattern::PX);\n\n  const char* gap_single_next_wrong = \"30px cde\";\n  CSSStringParser parser_5{gap_single_next_wrong,\n                           static_cast<uint32_t>(strlen(gap_single_next_wrong)),\n                           CSSParserConfigs()};\n  auto gap_single_next_wrong_value = parser_5.ParseGap();\n  EXPECT_EQ(gap_single_next_wrong_value.first.GetNumber(), 30);\n  EXPECT_EQ(gap_single_next_wrong_value.first.GetPattern(),\n            CSSValuePattern::PX);\n  EXPECT_EQ(gap_single_next_wrong_value.second.GetNumber(), 0);\n  EXPECT_EQ(gap_single_next_wrong_value.second.GetPattern(),\n            CSSValuePattern::PX);\n\n  const char* gap_all_wrong = \"fghijk\";\n  CSSStringParser parser_6{gap_all_wrong,\n                           static_cast<uint32_t>(strlen(gap_all_wrong)),\n                           CSSParserConfigs()};\n  auto gap_all_wrong_value = parser_6.ParseGap();\n  EXPECT_EQ(gap_all_wrong_value.first.GetNumber(), 0);\n  EXPECT_EQ(gap_all_wrong_value.first.GetPattern(), CSSValuePattern::PX);\n  EXPECT_EQ(gap_all_wrong_value.second.GetNumber(), 0);\n  EXPECT_EQ(gap_all_wrong_value.second.GetPattern(), CSSValuePattern::PX);\n\n  const char* gap_single_percent = \"40%\";\n  CSSStringParser parser_7{gap_single_percent,\n                           static_cast<uint32_t>(strlen(gap_single_percent)),\n                           CSSParserConfigs()};\n  auto gap_single_percent_value = parser_7.ParseGap();\n  EXPECT_EQ(gap_single_percent_value.first.GetNumber(), 40);\n  EXPECT_EQ(gap_single_percent_value.first.GetPattern(),\n            CSSValuePattern::PERCENT);\n  EXPECT_EQ(gap_single_percent_value.second.GetNumber(), 40);\n  EXPECT_EQ(gap_single_percent_value.second.GetPattern(),\n            CSSValuePattern::PERCENT);\n}\n\nTEST(CSSStringParser, RGBColor) {\n  CSSParserConfigs configs;\n\n  // Test legacy syntax: rgb(R, G, B)\n  {\n    std::string rgb = \"rgb(255, 128, 0)\";\n    CSSStringParser parser{rgb.c_str(), static_cast<uint32_t>(rgb.size()),\n                           configs};\n    CSSValue result;\n    parser.ParseCSSColorTo(result);\n    EXPECT_FALSE(result.IsEmpty());\n    EXPECT_EQ(result.GetPattern(), CSSValuePattern::NUMBER);\n\n    uint32_t color = result.GetValue().UInt32();\n    EXPECT_EQ((color >> 16) & 0xFF, 255);  // R\n    EXPECT_EQ((color >> 8) & 0xFF, 128);   // G\n    EXPECT_EQ(color & 0xFF, 0);            // B\n    EXPECT_EQ((color >> 24) & 0xFF, 255);  // A (default 1.0)\n  }\n\n  // Test legacy syntax with alpha: rgb(R, G, B, A)\n  {\n    std::string rgba = \"rgb(255, 128, 0, 0.5)\";\n    CSSStringParser parser{rgba.c_str(), static_cast<uint32_t>(rgba.size()),\n                           configs};\n    CSSValue result;\n    parser.ParseCSSColorTo(result);\n\n    uint32_t color = result.GetValue().UInt32();\n    EXPECT_EQ((color >> 24) & 0xFF, 127);  // A (0.5 * 255)\n  }\n\n  // Test modern syntax: rgb(R G B)\n  {\n    std::string rgb = \"rgb(255 128 0)\";\n    CSSStringParser parser{rgb.c_str(), static_cast<uint32_t>(rgb.size()),\n                           configs};\n    CSSValue result;\n    parser.ParseCSSColorTo(result);\n\n    uint32_t color = result.GetValue().UInt32();\n    EXPECT_EQ((color >> 16) & 0xFF, 255);\n    EXPECT_EQ((color >> 8) & 0xFF, 128);\n    EXPECT_EQ(color & 0xFF, 0);\n  }\n\n  // Test modern syntax with alpha: rgb(R G B / A)\n  {\n    std::string rgba = \"rgb(255 128 0 / 0.5)\";\n    CSSStringParser parser{rgba.c_str(), static_cast<uint32_t>(rgba.size()),\n                           configs};\n    CSSValue result;\n    parser.ParseCSSColorTo(result);\n\n    uint32_t color = result.GetValue().UInt32();\n    EXPECT_EQ((color >> 24) & 0xFF, 127);\n  }\n\n  // Test percentage values\n  {\n    std::string rgb = \"rgb(100% 50% 0%)\";\n    CSSStringParser parser{rgb.c_str(), static_cast<uint32_t>(rgb.size()),\n                           configs};\n    CSSValue result;\n    parser.ParseCSSColorTo(result);\n\n    uint32_t color = result.GetValue().UInt32();\n    EXPECT_EQ((color >> 16) & 0xFF, 255);  // 100% of 255\n    EXPECT_EQ((color >> 8) & 0xFF, 128);   // 50% of 255\n    EXPECT_EQ(color & 0xFF, 0);            // 0% of 255\n  }\n\n  // Test 'none' values (modern syntax only)\n  {\n    std::string rgb = \"rgb(none 128 none / 0.5)\";\n    CSSStringParser parser{rgb.c_str(), static_cast<uint32_t>(rgb.size()),\n                           configs};\n    CSSValue result;\n    parser.ParseCSSColorTo(result);\n\n    uint32_t color = result.GetValue().UInt32();\n    EXPECT_EQ((color >> 16) & 0xFF, 0);  // none -> 0\n    EXPECT_EQ((color >> 8) & 0xFF, 128);\n    EXPECT_EQ(color & 0xFF, 0);            // none -> 0\n    EXPECT_EQ((color >> 24) & 0xFF, 127);  // alpha 0.5\n  }\n\n  // Test invalid syntax\n  {\n    // Missing closing parenthesis\n    std::string invalid = \"rgb(255, 128, 0\";\n    CSSStringParser parser{invalid.c_str(),\n                           static_cast<uint32_t>(invalid.size()), configs};\n    CSSValue result;\n    parser.ParseCSSColorTo(result);\n    EXPECT_TRUE(result.IsEmpty());\n  }\n\n  // Test invalid legacy syntax with 'none'\n  {\n    // 'none' not allowed in legacy syntax\n    std::string invalid = \"rgb(none, 128, 0)\";\n    CSSStringParser parser{invalid.c_str(),\n                           static_cast<uint32_t>(invalid.size()), configs};\n    CSSValue result;\n    parser.ParseCSSColorTo(result);\n    EXPECT_TRUE(result.IsEmpty());\n  }\n}\nTEST(CSSStringParser, open_type_tag) {\n  EXPECT_TRUE(CSSStringParser::IsValidOpenTypeTag(\"aBcD\"));\n  EXPECT_TRUE(CSSStringParser::IsValidOpenTypeTag(\"1234\"));\n  EXPECT_TRUE(CSSStringParser::IsValidOpenTypeTag(\"ab12\"));\n  EXPECT_FALSE(CSSStringParser::IsValidOpenTypeTag(\"\"));\n  EXPECT_FALSE(CSSStringParser::IsValidOpenTypeTag(\"abc\"));\n  EXPECT_FALSE(CSSStringParser::IsValidOpenTypeTag(\"abcde\"));\n  EXPECT_FALSE(CSSStringParser::IsValidOpenTypeTag(\"ab,.c\"));\n}\n\nTEST(CSSStringParser, font_variation_settings) {\n  CSSParserConfigs configs;\n\n  // Normal cases\n  {\n    std::string raw = R\"('wght' 800)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_TRUE(parser.ParseFontVariationSettings(arr));\n    EXPECT_EQ(arr->size(), 2);\n    EXPECT_EQ(arr->get(0).ToString(), \"wght\");\n    EXPECT_FLOAT_EQ(arr->get(1).Number(), 800.0);\n  }\n  {\n    std::string raw = R\"(\"wdth\" 125.0, 'wght' 750)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_TRUE(parser.ParseFontVariationSettings(arr));\n    EXPECT_EQ(arr->size(), 4);\n    EXPECT_EQ(arr->get(0).ToString(), \"wdth\");\n    EXPECT_FLOAT_EQ(arr->get(1).Number(), 125.0);\n    EXPECT_EQ(arr->get(2).ToString(), \"wght\");\n    EXPECT_FLOAT_EQ(arr->get(3).Number(), 750.0);\n  }\n  {\n    std::string raw = R\"(normal)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_TRUE(parser.ParseFontVariationSettings(arr));\n    EXPECT_EQ(arr->size(), 0);\n  }\n  // Error cases\n  {\n    std::string raw = R\"('wght')\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_FALSE(parser.ParseFontVariationSettings(arr));\n  }\n  {\n    std::string raw = R\"('wght', 100)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_FALSE(parser.ParseFontVariationSettings(arr));\n  }\n  {\n    std::string raw = R\"('badtagname' 100)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_FALSE(parser.ParseFontVariationSettings(arr));\n  }\n}\n\nTEST(CSSStringParser, font_feature_settings) {\n  CSSParserConfigs configs;\n\n  // Normal cases\n  {\n    std::string raw = R\"('dlig')\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_TRUE(parser.ParseFontFeatureSettings(arr));\n    EXPECT_EQ(arr->size(), 2);\n    EXPECT_EQ(arr->get(0).ToString(), \"dlig\");\n    EXPECT_EQ(arr->get(1).Int32(), 1);\n  }\n  {\n    std::string raw = R\"(dlig on, smcp off, c2sc 2,aalt 1)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_TRUE(parser.ParseFontFeatureSettings(arr));\n    EXPECT_EQ(arr->size(), 8);\n    EXPECT_EQ(arr->get(0).ToString(), \"dlig\");\n    EXPECT_EQ(arr->get(1).Int32(), 1);\n    EXPECT_EQ(arr->get(2).ToString(), \"smcp\");\n    EXPECT_EQ(arr->get(3).Int32(), 0);\n    EXPECT_EQ(arr->get(4).ToString(), \"c2sc\");\n    EXPECT_EQ(arr->get(5).Int32(), 2);\n    EXPECT_EQ(arr->get(6).ToString(), \"aalt\");\n    EXPECT_EQ(arr->get(7).Int32(), 1);\n  }\n  {\n    std::string raw = R\"('dlig' on, \"smcp\" off, 'c2sc' 2)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_TRUE(parser.ParseFontFeatureSettings(arr));\n    EXPECT_EQ(arr->size(), 6);\n    EXPECT_EQ(arr->get(0).ToString(), \"dlig\");\n    EXPECT_EQ(arr->get(1).Int32(), 1);\n    EXPECT_EQ(arr->get(2).ToString(), \"smcp\");\n    EXPECT_EQ(arr->get(3).Int32(), 0);\n    EXPECT_EQ(arr->get(4).ToString(), \"c2sc\");\n    EXPECT_EQ(arr->get(5).Int32(), 2);\n  }\n  {\n    std::string raw = R\"(normal)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_TRUE(parser.ParseFontFeatureSettings(arr));\n    EXPECT_EQ(arr->size(), 0);\n  }\n  // Error cases\n  {\n    std::string raw = R\"('dlig' , on off)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_FALSE(parser.ParseFontFeatureSettings(arr));\n  }\n  {\n    std::string raw = R\"('badtagname')\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_FALSE(parser.ParseFontFeatureSettings(arr));\n  }\n  {\n    std::string raw = R\"('dlig',, 'smcp')\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    auto arr = lepus::CArray::Create();\n    EXPECT_FALSE(parser.ParseFontFeatureSettings(arr));\n  }\n}\n\nTEST(CSSStringParser, parse_variable_compact) {\n  CSSParserConfigs configs;\n  // Compact var with fallback, no whitespace\n  {\n    std::string raw = \"var(--a,10px)\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    CSSValue result = parser.ParseVariable();\n    EXPECT_TRUE(result.IsVariable());\n    ASSERT_TRUE(result.optionals_.HasValue<CSSValue::VarReferenceField>());\n\n    auto& refs = result.optionals_.Get<CSSValue::VarReferenceField>();\n    ASSERT_EQ(refs.size(), 1u);\n    auto& ref = refs[0];\n\n    EXPECT_EQ(ref.Name(raw), \"--a\");\n    ASSERT_FALSE(ref.fallback.empty());\n    EXPECT_EQ(ref.fallback.str(), \"10px\");\n  }\n\n  // Compact nested var, no whitespace\n  {\n    std::string raw = \"var(--a,var(--b))\";\n    CSSStringParser parser{raw.c_str(), static_cast<uint32_t>(raw.size()),\n                           configs};\n    CSSValue result = parser.ParseVariable();\n    EXPECT_TRUE(result.IsVariable());\n    ASSERT_TRUE(result.optionals_.HasValue<CSSValue::VarReferenceField>());\n\n    auto& refs = result.optionals_.Get<CSSValue::VarReferenceField>();\n    ASSERT_EQ(refs.size(), 1u);\n    auto& ref = refs[0];\n\n    EXPECT_EQ(ref.Name(raw), \"--a\");\n    ASSERT_FALSE(ref.fallback.empty());\n    EXPECT_EQ(ref.fallback.str(), \"var(--b)\");\n  }\n}\n\n// Split HSL edge case tests to identify which one hangs\nTEST(CSSStringParser, ParseHSLColor_Basic) {\n  CSSParserConfigs configs;\n  std::string hsl = \"hsl(240, 100%, 50%)\";\n  CSSStringParser parser{hsl.c_str(), static_cast<uint32_t>(hsl.size()),\n                         configs};\n  CSSValue result;\n  parser.ParseCSSColorTo(result);\n  EXPECT_FALSE(result.IsEmpty());\n}\n\nTEST(CSSStringParser, ParseHSLColor_Hue480) {\n  CSSParserConfigs configs;\n  std::string hsl = \"hsl(480, 100%, 50%)\";\n  CSSStringParser parser{hsl.c_str(), static_cast<uint32_t>(hsl.size()),\n                         configs};\n  CSSValue result;\n  parser.ParseCSSColorTo(result);\n  EXPECT_FALSE(result.IsEmpty());\n}\n\nTEST(CSSStringParser, ParseHSLColor_NegativeHue) {\n  CSSParserConfigs configs;\n  std::string hsl = \"hsl(-120, 100%, 50%)\";\n  CSSStringParser parser{hsl.c_str(), static_cast<uint32_t>(hsl.size()),\n                         configs};\n  CSSValue result;\n  parser.ParseCSSColorTo(result);\n  EXPECT_FALSE(result.IsEmpty());\n}\n\nTEST(CSSStringParser, ParseHSLColor_LargeHue1000000) {\n  CSSParserConfigs configs;\n  std::string hsl = \"hsl(1000000, 1000%, 1000%)\";\n  CSSStringParser parser{hsl.c_str(), static_cast<uint32_t>(hsl.size()),\n                         configs};\n  CSSValue result;\n  parser.ParseCSSColorTo(result);\n  EXPECT_FALSE(result.IsEmpty());\n}\n\nTEST(CSSStringParser, ParseHSLColor_SmallValues) {\n  CSSParserConfigs configs;\n  std::string hsl = \"hsl(0.0001, 0.0001%, 0.0001%)\";\n  CSSStringParser parser{hsl.c_str(), static_cast<uint32_t>(hsl.size()),\n                         configs};\n  CSSValue result;\n  parser.ParseCSSColorTo(result);\n  EXPECT_FALSE(result.IsEmpty());\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/css_string_scanner.cc",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/css_string_scanner.h\"\n\n#include <cstring>\n\n#include \"base/include/vector.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nToken Scanner::ScanToken() {\n  start_ = current_;\n\n  if (IsAtEnd()) {\n    return MakeToken(TokenType::TOKEN_EOF);\n  }\n\n  char c = Advance();\n\n  if (IsWhitespace(c)) {\n    return Whitespace();\n  }\n\n  if (IsIdentStart(c) || (c == '\\\\' && TwoCharsAreValidEscape(c, Peek()))) {\n    return IdentLikeToken();\n  }\n  // [<Number>] <dot> <Number>\n  if (IsDigit(c) || (c == '.' && IsDigit(Peek()))) {\n    return Numeric(c == '.');\n  }\n\n  // negative number\n  if (c == '-' || c == '+') {\n    if (IsDigit(Peek()) || (Peek() == '.' && IsDigit(PeekNext()))) {\n      return Numeric();\n    }\n  }\n\n  // hex number\n  if (c == '#') {\n    return Hex();\n  }\n\n  switch (c) {\n    case '(':\n      return MakeToken(TokenType::LEFT_PAREN);\n    case ')':\n      return MakeToken(TokenType::RIGHT_PAREN);\n    case ',':\n      return MakeToken(TokenType::COMMA);\n    case ':':\n      return MakeToken(TokenType::COLON);\n    case '.':\n      return MakeToken(TokenType::DOT);\n    case '-':\n      return MakeToken(TokenType::MINUS);\n    case '+':\n      return MakeToken(TokenType::PLUS);\n    case ';':\n      return MakeToken(TokenType::SEMICOLON);\n    case '/':\n      return MakeToken(TokenType::SLASH);\n      // fixme(renzhongyue): all '#' are parsed to hex number now. This path is\n      // unreachable.\n    case '#':\n      return MakeToken(TokenType::SHARP);\n    case '%':\n      return MakeToken(TokenType::PERCENTAGE);\n    case '\\'':\n      return String('\\'');\n    case '\\\"':\n      return String('\\\"');\n    default:\n      return MakeToken(TokenType::UNKNOWN);\n  }\n}\n\nchar Scanner::Advance() {\n  current_++;\n  return content_[current_ - 1];\n}\n\nbool Scanner::Match(char expected) {\n  if (IsAtEnd()) {\n    return false;\n  }\n\n  if (content_[current_] != expected) {\n    return false;\n  }\n\n  current_++;\n  return true;\n}\n\nbool Scanner::IsIdentStart(char c) {\n  if (IsAlpha(c)) {\n    return true;\n  } else if (c == '-') {\n    if (IsAlpha(Peek()) || Peek() == '-' || Peek() == '_' || Peek() == '\\0') {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool Scanner::IsWhitespace(char c) {\n  return c == ' ' || c == '\\n' || c == '\\t' || c == '\\r' || c == '\\f';\n}\n\nbool Scanner::IsCSSNewLine(char c) {\n  return c == '\\n' || c == '\\t' || c == '\\r' || c == '\\f';\n}\n\nbool Scanner::TwoCharsAreValidEscape(char first, char second) {\n  return first == '\\\\' && !IsCSSNewLine(second);\n}\n\nbool Scanner::IsDigit(char c) { return c >= '0' && c <= '9'; }\n\nbool Scanner::IsAlpha(char c) {\n  return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';\n}\n\nbool Scanner::IsNamed(char c) { return IsAlpha(c) || IsDigit(c) || c == '-'; }\n\nToken Scanner::String(char boundary) {\n  // skip first `'`\n  Advance();\n  while (Peek() != boundary && !IsAtEnd()) {\n    Advance();\n  }\n\n  if (IsAtEnd()) {\n    // Unterminated string without `'` at end , like 'xxxxx\n    return ErrorToken();\n  }\n  // skip last `'`\n  Advance();\n\n  return MakeToken(TokenType::STRING);\n}\n\nToken Scanner::FunctionExpression(TokenType type) {\n  char left_paren = '(';\n  char right_paren = ')';\n\n  // Save the `current_` value. If the token is not a function name who followed\n  // by parenthesis, restore the `current_` to this value.\n  uint32_t previous_current = current_;\n  SkipWhiteSpace();\n  if (Peek() != left_paren /* begin with ( */ || IsAtEnd()) {\n    // The token is keyword but not a function. Should not skip the whitespaces.\n    current_ = previous_current;\n    return MakeToken(type);\n  }\n  base::InlineStack<TokenType, 16> brackets;\n  brackets.push(TokenType::LEFT_PAREN);\n  uint32_t args_start = current_;\n  while (!IsAtEnd() && !brackets.empty()) {\n    Advance();\n    if (Peek() == left_paren) {\n      brackets.push(TokenType::LEFT_PAREN);\n    }\n    if (Peek() == right_paren) {\n      brackets.pop();\n    }\n  }\n  if (!brackets.empty() || IsAtEnd()) {\n    return Token{TokenType::ERROR, content_ + start_, current_ - start_};\n  }\n  Advance();\n  // TODO(renzhongyue): all function type token should begin with the char next\n  // to '('\n  if (TokenType::BLUR <= type && type <= TokenType::VAR) {\n    if (TokenType::VAR == type) {\n      met_var_ = true;\n    }\n    return Token{type, content_ + args_start + 1, current_ - args_start - 2};\n  }\n  return MakeToken(type);\n}\n\nToken Scanner::Numeric(bool begin_with_dot) {\n  // in case is negative number\n  if ((Peek() == '-' || Peek() == '+') &&\n      (IsDigit(PeekNext() || PeekNext() == '.'))) {\n    Advance();\n  }\n  while (IsDigit(Peek())) {\n    Advance();\n  }\n  if (begin_with_dot && Peek() == '.') {\n    return MakeToken(TokenType::NUMBER);\n  }\n  // support float number\n  if (Peek() == '.' && IsDigit(PeekNext())) {\n    // consume the dot\n    Advance();\n  }\n\n  while (IsDigit(Peek())) {\n    Advance();\n  }\n\n  // <number> ,e , -, <number> like '3e-5'\n  if (Peek() == 'e' && PeekNext() == '-' && IsDigit(PeekNextNext())) {\n    Advance();  // e\n    Advance();  // -\n  }\n  while (IsDigit(Peek())) {\n    Advance();\n  }\n\n  Token number = MakeToken(TokenType::NUMBER);\n  char p = Peek();\n  // <percentage-token>, or <dimension-token>\n  if (IsAlpha(p) || p == '%') {\n    Token unit_token = ScanToken();\n    return Token(TokenType::DIMENSION, unit_token.type, number.start,\n                 number.length);\n  }\n  // <number-token>\n  return number;\n}\n\nToken Scanner::Hex() {\n  // skip #\n  start_ = current_;\n  while (IsAlpha(Peek()) || IsDigit(Peek())) {\n    Advance();\n  }\n\n  return MakeToken(TokenType::HEX);\n}\n\nToken Scanner::Whitespace() {\n  while (IsWhitespace(Peek())) {\n    Advance();\n  }\n\n  return MakeToken(TokenType::WHITESPACE);\n}\n\nToken Scanner::IdentLikeToken() {\n  while (IsNamed(Peek())) {\n    Advance();\n  }\n  if (start_ > content_length_ || current_ > content_length_) {\n    return MakeToken(TokenType::ERROR);\n  }\n  unsigned len = static_cast<unsigned>(current_ - start_);\n  char s[len + 1];\n  ToLower(content_ + start_, len, s);\n  auto* it = GetTokenValue(s, len);\n  if (it != nullptr /* got token keywords */) {\n    if (TokenType::CALC <= it->type && it->type <= TokenType::VAR) {\n      // maybe function\n      return FunctionExpression(it->type);\n    }\n    return MakeToken(it->type);\n  }\n  // not a keyword\n  return MakeToken(TokenType::IDENTIFIER);\n}\n\nbool Scanner::IsAtEnd() const {\n  return content_[current_] == '\\0' || current_ >= content_length_;\n}\n\nToken Scanner::MakeToken(TokenType type) const {\n  return Token{type, content_ + start_ + (type == TokenType::STRING ? 1 : 0),\n               current_ - start_ - (type == TokenType::STRING ? 2 : 0)};\n}\n\nToken Scanner::ErrorToken() const {\n  return Token(TokenType::ERROR, nullptr, 0);\n}\n\nchar Scanner::Peek() const {\n  if (current_ > content_length_) {\n    return '\\0';\n  }\n  return content_[current_];\n}\n\nchar Scanner::PeekNext() const {\n  if (IsAtEnd()) {\n    return '\\0';\n  }\n\n  return content_[current_ + 1];\n}\n\nchar Scanner::PeekNextNext() const {\n  if (PeekNext() == '\\0' || current_ + 2 > content_length_) {\n    return '\\0';\n  }\n\n  return content_[current_ + 2];\n}\n\nvoid Scanner::SkipWhiteSpace() {\n  for (;;) {\n    char c = Peek();\n    if (IsWhitespace(c)) {\n      Advance();\n    } else {\n      return;\n    }\n  }\n}\n// Use uint32_t to improve efficiency, we can convert 4 chars together. Notice:\n// '@' , '[' , '\\' , ']' , '^' , '_' will lead to fault with this method.\nbool Scanner::ToLower(const char* src, unsigned length, char* dst) {\n  unsigned i;\n  for (i = 0; i < (length & ~3); i += 4) {\n    uint32_t x;\n    memcpy(&x, src + i, sizeof(x));\n    x |= (x & 0x40404040) >> 1;\n    memcpy(dst + i, &x, sizeof(x));\n  }\n  for (; i < length; ++i) {\n    char c = src[i];\n    dst[i] = c | ((c & 0x40) >> 1);\n  }\n  dst[length] = '\\0';\n  return true;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/css_string_scanner.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_CSS_STRING_SCANNER_H_\n#define CORE_RENDERER_CSS_PARSER_CSS_STRING_SCANNER_H_\n\n#include <map>\n#include <string>\n\n#include \"core/renderer/css/css_keywords.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nstruct Token final {\n  TokenType type = TokenType::TOKEN_EOF;\n  TokenType unit = TokenType::UNKNOWN;\n  const char* start = nullptr;\n  uint32_t length = 0;\n\n  bool IsZero() {\n    return type == TokenType::NUMBER && length == 1 && start[0] == '0';\n  }\n\n  bool IsIdent() {\n    return type == TokenType::IDENTIFIER || type >= TokenType::UNKNOWN;\n  }\n\n  Token() = default;\n  Token(TokenType type, const char* start, uint32_t length)\n      : Token(type, TokenType::UNKNOWN, start, length) {}\n\n  Token(TokenType type, TokenType unit, const char* start, uint32_t length)\n      : type(type), unit(unit), start(start), length(length) {}\n\n  Token& operator=(const Token&) = default;\n  ~Token() = default;\n};\n\nclass Scanner final {\n public:\n  Scanner(const char* content, uint32_t content_length)\n      : content_(content), content_length_(content_length) {}\n\n  ~Scanner() = default;\n\n  Token ScanToken();\n\n  char Advance();\n  bool IsAtEnd() const;\n  Token MakeToken(TokenType type) const;\n  Token ErrorToken() const;\n  Token String(char boundary);\n\n  Token Numeric(bool begin_with_dot = false);\n  Token Hex();\n  Token Whitespace();\n  bool Match(char expected);\n  char Peek() const;\n  char PeekNext() const;\n  char PeekNextNext() const;\n  void SkipWhiteSpace();\n  const char* content() const { return content_; }\n  size_t Length() const { return content_length_; }\n  bool HasMetVarToken() const { return met_var_; }\n\n private:\n  static bool IsWhitespace(char c);\n  static bool IsCSSNewLine(char c);\n  static bool IsDigit(char c);\n  static bool IsAlpha(char c);\n  static bool IsNamed(char c);\n  static bool ToLower(const char* src, unsigned length, char* dst);\n  static bool TwoCharsAreValidEscape(char first, char second);\n  bool IsIdentStart(char c);\n  Token IdentLikeToken();\n  Token FunctionExpression(TokenType type);\n\n  const char* content_;\n  uint32_t content_length_;\n  uint32_t start_{0};\n  uint32_t current_{0};\n  bool met_var_{false};\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_CSS_STRING_SCANNER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/css_string_scanner_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/css_string_scanner.h\"\n\n#include <array>\n#include <map>\n#include <string>\n#include <tuple>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/css_color.h\"\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nTEST(CSSStringScanner, EmptyString) {\n  std::string input = \"\";\n  Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::TOKEN_EOF);\n  EXPECT_TRUE(scanner.IsAtEnd());\n}\n\nTEST(CSSStringScanner, Consume) {\n  std::string input = \"A\";\n  Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::IDENTIFIER);\n  EXPECT_TRUE(scanner.IsAtEnd());\n}\n\nTEST(CSSStringScanner, ConsumeMultipleTokens) {\n  std::string input = \"A 1\";\n  Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::IDENTIFIER);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::WHITESPACE);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::NUMBER);\n  EXPECT_TRUE(scanner.IsAtEnd());\n}\n\nTEST(CSSStringScanner, ConsumeComponentValue) {\n  std::string input = \"A(1)(2(3))B\";\n  Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::IDENTIFIER);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::LEFT_PAREN);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::NUMBER);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::RIGHT_PAREN);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::LEFT_PAREN);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::NUMBER);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::LEFT_PAREN);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::NUMBER);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::RIGHT_PAREN);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::RIGHT_PAREN);\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::IDENTIFIER);\n  EXPECT_TRUE(scanner.IsAtEnd());\n}\n\nTEST(CSSStringScanner, ConsumeWhitespace) {\n  std::string input = \" \\t\\n\";\n  Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n\n  EXPECT_EQ(scanner.ScanToken().type, TokenType::WHITESPACE);\n  EXPECT_TRUE(scanner.IsAtEnd());\n}\n\nTEST(CSSStringScanner, ConsumeString) {\n  {\n    std::string input = \"\\\"A\\\"\";\n    Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n\n    auto token = scanner.ScanToken();\n\n    EXPECT_EQ(token.type, TokenType::STRING);\n    EXPECT_EQ(std::string(token.start, token.length), \"A\");\n    EXPECT_TRUE(scanner.IsAtEnd());\n  }\n  {\n    std::string input = \"'a'\";\n    Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n\n    auto token = scanner.ScanToken();\n\n    EXPECT_EQ(token.type, TokenType::STRING);\n    EXPECT_EQ(std::string(token.start, token.length), \"a\");\n    EXPECT_TRUE(scanner.IsAtEnd());\n  }\n  {\n    std::string input = \"'as\\\"\";\n    Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n\n    auto token = scanner.ScanToken();\n\n    EXPECT_EQ(token.type, TokenType::ERROR);\n    EXPECT_TRUE(scanner.IsAtEnd());\n  }\n}\n\nTEST(CSSStringScanner, ConsumeHex) {\n  std::string input = \"#00214\";\n  Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n\n  auto token = scanner.ScanToken();\n\n  EXPECT_EQ(token.type, TokenType::HEX);\n  EXPECT_EQ(std::string(token.start, token.length), \"00214\");\n  EXPECT_TRUE(scanner.IsAtEnd());\n}\n\nTEST(CSSStringScanner, ConsumeFunction) {\n  std::string input = \"calc()\";\n  Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n\n  auto token = scanner.ScanToken();\n\n  EXPECT_EQ(token.type, TokenType::CALC);\n  EXPECT_EQ(std::string(token.start, token.length), input);\n  EXPECT_TRUE(scanner.IsAtEnd());\n}\n\nTEST(CSSStringScanner, ConsumeFunctionWithArguments) {\n  std::string input = \"calc(2px)\";\n  Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n\n  auto token = scanner.ScanToken();\n\n  EXPECT_EQ(token.type, TokenType::CALC);\n  EXPECT_EQ(std::string(token.start, token.length), input);\n  EXPECT_TRUE(scanner.IsAtEnd());\n}\n\nTEST(CSSStringScanner, ConsumeFunctionError) {\n  std::string input = \"calc((2px)))\";\n  Scanner scanner(input.c_str(), static_cast<uint32_t>(input.size()));\n\n  auto token = scanner.ScanToken();\n\n  EXPECT_EQ(token.type, TokenType::CALC);\n  EXPECT_FALSE(scanner.IsAtEnd());\n}\n\nTEST(CSSStringScanner, Dimension) {\n  std::string input = \"20px\";\n  Scanner scanner1(input.c_str(), static_cast<uint32_t>(input.size()));\n  auto token = scanner1.ScanToken();\n  EXPECT_EQ(token.type, TokenType::DIMENSION);\n  EXPECT_EQ(token.unit, TokenType::PX);\n  EXPECT_TRUE(scanner1.IsAtEnd());\n\n  input = \"20 px\";\n  Scanner scanner2(input.c_str(), static_cast<uint32_t>(input.size()));\n  auto number = scanner2.ScanToken();\n  EXPECT_EQ(number.type, TokenType::NUMBER);\n  EXPECT_FALSE(scanner2.IsAtEnd());\n}\n\nTEST(CSSStringScanner, Ident) {\n  std::string input = \"item1-ani-frames\";\n  Scanner scanner1(input.c_str(), static_cast<uint32_t>(input.size()));\n  auto token = scanner1.ScanToken();\n  EXPECT_EQ(token.type, TokenType::IDENTIFIER);\n  EXPECT_EQ(std::string(token.start, token.length), input);\n  EXPECT_TRUE(scanner1.IsAtEnd());\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/cursor_handler.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/cursor_handler.h\"\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace CursorHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  output.insert_or_assign(key, parser.ParseCursor());\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDCursor] = &Handle; }\n\n}  // namespace CursorHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/cursor_handler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_CURSOR_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_CURSOR_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace CursorHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace CursorHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_CURSOR_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/enum_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/enum_handler.h\"\n\n#include <string>\n#include <string_view>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nnamespace {\n// AUTO INSERT, DON'T CHANGE IT!\nusing starlight::PositionType;\nstatic bool ToPositionType(std::string_view str, int& result) {\n  PositionType type = PositionType::kRelative;\n  if (str == \"absolute\") {\n    type = PositionType::kAbsolute;\n  } else if (str == \"relative\") {\n    type = PositionType::kRelative;\n  } else if (str == \"fixed\") {\n    type = PositionType::kFixed;\n  } else if (str == \"sticky\") {\n    type = PositionType::kSticky;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::BoxSizingType;\nstatic bool ToBoxSizingType(std::string_view str, int& result) {\n  BoxSizingType type = BoxSizingType::kAuto;\n  if (str == \"border-box\") {\n    type = BoxSizingType::kBorderBox;\n  } else if (str == \"content-box\") {\n    type = BoxSizingType::kContentBox;\n  } else if (str == \"auto\") {\n    type = BoxSizingType::kAuto;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::DisplayType;\nstatic bool ToDisplayType(std::string_view str, int& result) {\n  DisplayType type = DisplayType::kAuto;\n  if (str == \"none\") {\n    type = DisplayType::kNone;\n  } else if (str == \"flex\") {\n    type = DisplayType::kFlex;\n  } else if (str == \"grid\") {\n    type = DisplayType::kGrid;\n  } else if (str == \"linear\") {\n    type = DisplayType::kLinear;\n  } else if (str == \"relative\") {\n    type = DisplayType::kRelative;\n  } else if (str == \"block\") {\n    type = DisplayType::kBlock;\n  } else if (str == \"auto\") {\n    type = DisplayType::kAuto;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::WhiteSpaceType;\nstatic bool ToWhiteSpaceType(std::string_view str, int& result) {\n  WhiteSpaceType type = WhiteSpaceType::kNormal;\n  if (str == \"normal\") {\n    type = WhiteSpaceType::kNormal;\n  } else if (str == \"nowrap\") {\n    type = WhiteSpaceType::kNowrap;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::TextAlignType;\nstatic bool ToTextAlignType(std::string_view str, int& result) {\n  TextAlignType type = TextAlignType::kStart;\n  if (str == \"left\") {\n    type = TextAlignType::kLeft;\n  } else if (str == \"center\") {\n    type = TextAlignType::kCenter;\n  } else if (str == \"right\") {\n    type = TextAlignType::kRight;\n  } else if (str == \"start\") {\n    type = TextAlignType::kStart;\n  } else if (str == \"end\") {\n    type = TextAlignType::kEnd;\n  } else if (str == \"justify\") {\n    type = TextAlignType::kJustify;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::TextOverflowType;\nstatic bool ToTextOverflowType(std::string_view str, int& result) {\n  TextOverflowType type = TextOverflowType::kClip;\n  if (str == \"clip\") {\n    type = TextOverflowType::kClip;\n  } else if (str == \"ellipsis\") {\n    type = TextOverflowType::kEllipsis;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::FontWeightType;\nstatic bool ToFontWeightType(std::string_view str, int& result) {\n  FontWeightType type = FontWeightType::kNormal;\n  if (str == \"normal\") {\n    type = FontWeightType::kNormal;\n  } else if (str == \"bold\") {\n    type = FontWeightType::kBold;\n  } else if (str == \"100\") {\n    type = FontWeightType::k100;\n  } else if (str == \"200\") {\n    type = FontWeightType::k200;\n  } else if (str == \"300\") {\n    type = FontWeightType::k300;\n  } else if (str == \"400\") {\n    type = FontWeightType::k400;\n  } else if (str == \"500\") {\n    type = FontWeightType::k500;\n  } else if (str == \"600\") {\n    type = FontWeightType::k600;\n  } else if (str == \"700\") {\n    type = FontWeightType::k700;\n  } else if (str == \"800\") {\n    type = FontWeightType::k800;\n  } else if (str == \"900\") {\n    type = FontWeightType::k900;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::FlexDirectionType;\nstatic bool ToFlexDirectionType(std::string_view str, int& result) {\n  FlexDirectionType type = FlexDirectionType::kRow;\n  if (str == \"column\") {\n    type = FlexDirectionType::kColumn;\n  } else if (str == \"row\") {\n    type = FlexDirectionType::kRow;\n  } else if (str == \"row-reverse\") {\n    type = FlexDirectionType::kRowReverse;\n  } else if (str == \"column-reverse\") {\n    type = FlexDirectionType::kColumnReverse;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::FlexWrapType;\nstatic bool ToFlexWrapType(std::string_view str, int& result) {\n  FlexWrapType type = FlexWrapType::kNowrap;\n  if (str == \"wrap\") {\n    type = FlexWrapType::kWrap;\n  } else if (str == \"nowrap\") {\n    type = FlexWrapType::kNowrap;\n  } else if (str == \"wrap-reverse\") {\n    type = FlexWrapType::kWrapReverse;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::AlignContentType;\nstatic bool ToAlignContentType(std::string_view str, int& result) {\n  AlignContentType type = AlignContentType::kStretch;\n  if (str == \"flex-start\") {\n    type = AlignContentType::kFlexStart;\n  } else if (str == \"flex-end\") {\n    type = AlignContentType::kFlexEnd;\n  } else if (str == \"center\") {\n    type = AlignContentType::kCenter;\n  } else if (str == \"stretch\") {\n    type = AlignContentType::kStretch;\n  } else if (str == \"space-between\") {\n    type = AlignContentType::kSpaceBetween;\n  } else if (str == \"space-around\") {\n    type = AlignContentType::kSpaceAround;\n  } else if (str == \"start\") {\n    type = AlignContentType::kFlexStart;\n  } else if (str == \"end\") {\n    type = AlignContentType::kFlexEnd;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::JustifyContentType;\nstatic bool ToJustifyContentType(std::string_view str, int& result) {\n  JustifyContentType type = JustifyContentType::kStretch;\n  if (str == \"flex-start\") {\n    type = JustifyContentType::kFlexStart;\n  } else if (str == \"center\") {\n    type = JustifyContentType::kCenter;\n  } else if (str == \"flex-end\") {\n    type = JustifyContentType::kFlexEnd;\n  } else if (str == \"space-between\") {\n    type = JustifyContentType::kSpaceBetween;\n  } else if (str == \"space-around\") {\n    type = JustifyContentType::kSpaceAround;\n  } else if (str == \"space-evenly\") {\n    type = JustifyContentType::kSpaceEvenly;\n  } else if (str == \"stretch\") {\n    type = JustifyContentType::kStretch;\n  } else if (str == \"start\") {\n    type = JustifyContentType::kFlexStart;\n  } else if (str == \"end\") {\n    type = JustifyContentType::kFlexEnd;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::FontStyleType;\nstatic bool ToFontStyleType(std::string_view str, int& result) {\n  FontStyleType type = FontStyleType::kNormal;\n  if (str == \"normal\") {\n    type = FontStyleType::kNormal;\n  } else if (str == \"italic\") {\n    type = FontStyleType::kItalic;\n  } else if (str == \"oblique\") {\n    type = FontStyleType::kOblique;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::LinearOrientationType;\nstatic bool ToLinearOrientationType(std::string_view str, int& result) {\n  LinearOrientationType type = LinearOrientationType::kVertical;\n  if (str == \"horizontal\") {\n    type = LinearOrientationType::kHorizontal;\n  } else if (str == \"vertical\") {\n    type = LinearOrientationType::kVertical;\n  } else if (str == \"horizontal-reverse\") {\n    type = LinearOrientationType::kHorizontalReverse;\n  } else if (str == \"vertical-reverse\") {\n    type = LinearOrientationType::kVerticalReverse;\n  } else if (str == \"row\") {\n    type = LinearOrientationType::kRow;\n  } else if (str == \"column\") {\n    type = LinearOrientationType::kColumn;\n  } else if (str == \"row-reverse\") {\n    type = LinearOrientationType::kRowReverse;\n  } else if (str == \"column-reverse\") {\n    type = LinearOrientationType::kColumnReverse;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::LinearGravityType;\nstatic bool ToLinearGravityType(std::string_view str, int& result) {\n  LinearGravityType type = LinearGravityType::kNone;\n  if (str == \"none\") {\n    type = LinearGravityType::kNone;\n  } else if (str == \"top\") {\n    type = LinearGravityType::kTop;\n  } else if (str == \"bottom\") {\n    type = LinearGravityType::kBottom;\n  } else if (str == \"left\") {\n    type = LinearGravityType::kLeft;\n  } else if (str == \"right\") {\n    type = LinearGravityType::kRight;\n  } else if (str == \"center-vertical\") {\n    type = LinearGravityType::kCenterVertical;\n  } else if (str == \"center-horizontal\") {\n    type = LinearGravityType::kCenterHorizontal;\n  } else if (str == \"space-between\") {\n    type = LinearGravityType::kSpaceBetween;\n  } else if (str == \"start\") {\n    type = LinearGravityType::kStart;\n  } else if (str == \"end\") {\n    type = LinearGravityType::kEnd;\n  } else if (str == \"center\") {\n    type = LinearGravityType::kCenter;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::LinearLayoutGravityType;\nstatic bool ToLinearLayoutGravityType(std::string_view str, int& result) {\n  LinearLayoutGravityType type = LinearLayoutGravityType::kNone;\n  if (str == \"none\") {\n    type = LinearLayoutGravityType::kNone;\n  } else if (str == \"top\") {\n    type = LinearLayoutGravityType::kTop;\n  } else if (str == \"bottom\") {\n    type = LinearLayoutGravityType::kBottom;\n  } else if (str == \"left\") {\n    type = LinearLayoutGravityType::kLeft;\n  } else if (str == \"right\") {\n    type = LinearLayoutGravityType::kRight;\n  } else if (str == \"center-vertical\") {\n    type = LinearLayoutGravityType::kCenterVertical;\n  } else if (str == \"center-horizontal\") {\n    type = LinearLayoutGravityType::kCenterHorizontal;\n  } else if (str == \"fill-vertical\") {\n    type = LinearLayoutGravityType::kFillVertical;\n  } else if (str == \"fill-horizontal\") {\n    type = LinearLayoutGravityType::kFillHorizontal;\n  } else if (str == \"center\") {\n    type = LinearLayoutGravityType::kCenter;\n  } else if (str == \"stretch\") {\n    type = LinearLayoutGravityType::kStretch;\n  } else if (str == \"start\") {\n    type = LinearLayoutGravityType::kStart;\n  } else if (str == \"end\") {\n    type = LinearLayoutGravityType::kEnd;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::VisibilityType;\nstatic bool ToVisibilityType(std::string_view str, int& result) {\n  VisibilityType type = VisibilityType::kVisible;\n  if (str == \"hidden\") {\n    type = VisibilityType::kHidden;\n  } else if (str == \"visible\") {\n    type = VisibilityType::kVisible;\n  } else if (str == \"none\") {\n    type = VisibilityType::kNone;\n  } else if (str == \"collapse\") {\n    type = VisibilityType::kCollapse;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::WordBreakType;\nstatic bool ToWordBreakType(std::string_view str, int& result) {\n  WordBreakType type = WordBreakType::kNormal;\n  if (str == \"normal\") {\n    type = WordBreakType::kNormal;\n  } else if (str == \"break-all\") {\n    type = WordBreakType::kBreakAll;\n  } else if (str == \"keep-all\") {\n    type = WordBreakType::kKeepAll;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::DirectionType;\nstatic bool ToDirectionType(std::string_view str, int& result) {\n  DirectionType type = DirectionType::kNormal;\n  if (str == \"normal\") {\n    type = DirectionType::kNormal;\n  } else if (str == \"lynx-rtl\") {\n    type = DirectionType::kLynxRtl;\n  } else if (str == \"rtl\") {\n    type = DirectionType::kRtl;\n  } else if (str == \"ltr\") {\n    type = DirectionType::kLtr;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::RelativeCenterType;\nstatic bool ToRelativeCenterType(std::string_view str, int& result) {\n  RelativeCenterType type = RelativeCenterType::kNone;\n  if (str == \"none\") {\n    type = RelativeCenterType::kNone;\n  } else if (str == \"vertical\") {\n    type = RelativeCenterType::kVertical;\n  } else if (str == \"horizontal\") {\n    type = RelativeCenterType::kHorizontal;\n  } else if (str == \"both\") {\n    type = RelativeCenterType::kBoth;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::LinearCrossGravityType;\nstatic bool ToLinearCrossGravityType(std::string_view str, int& result) {\n  LinearCrossGravityType type = LinearCrossGravityType::kNone;\n  if (str == \"none\") {\n    type = LinearCrossGravityType::kNone;\n  } else if (str == \"start\") {\n    type = LinearCrossGravityType::kStart;\n  } else if (str == \"end\") {\n    type = LinearCrossGravityType::kEnd;\n  } else if (str == \"center\") {\n    type = LinearCrossGravityType::kCenter;\n  } else if (str == \"stretch\") {\n    type = LinearCrossGravityType::kStretch;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::ImageRenderingType;\nstatic bool ToImageRenderingType(std::string_view str, int& result) {\n  ImageRenderingType type = ImageRenderingType::kAuto;\n  if (str == \"auto\") {\n    type = ImageRenderingType::kAuto;\n  } else if (str == \"crisp-edges\") {\n    type = ImageRenderingType::kCrispEdges;\n  } else if (str == \"pixelated\") {\n    type = ImageRenderingType::kPixelated;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::HyphensType;\nstatic bool ToHyphensType(std::string_view str, int& result) {\n  HyphensType type = HyphensType::kManual;\n  if (str == \"none\") {\n    type = HyphensType::kNone;\n  } else if (str == \"manual\") {\n    type = HyphensType::kManual;\n  } else if (str == \"auto\") {\n    type = HyphensType::kAuto;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::XAppRegionType;\nstatic bool ToXAppRegionType(std::string_view str, int& result) {\n  XAppRegionType type = XAppRegionType::kNone;\n  if (str == \"none\") {\n    type = XAppRegionType::kNone;\n  } else if (str == \"drag\") {\n    type = XAppRegionType::kDrag;\n  } else if (str == \"no-drag\") {\n    type = XAppRegionType::kNoDrag;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::XAnimationColorInterpolationType;\nstatic bool ToXAnimationColorInterpolationType(std::string_view str,\n                                               int& result) {\n  XAnimationColorInterpolationType type =\n      XAnimationColorInterpolationType::kAuto;\n  if (str == \"auto\") {\n    type = XAnimationColorInterpolationType::kAuto;\n  } else if (str == \"sRGB\") {\n    type = XAnimationColorInterpolationType::kSRGB;\n  } else if (str == \"linearRGB\") {\n    type = XAnimationColorInterpolationType::kLinearRGB;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::FontOpticalSizingType;\nstatic bool ToFontOpticalSizingType(std::string_view str, int& result) {\n  FontOpticalSizingType type = FontOpticalSizingType::kNone;\n  if (str == \"none\") {\n    type = FontOpticalSizingType::kNone;\n  } else if (str == \"auto\") {\n    type = FontOpticalSizingType::kAuto;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::XPlaceholderFontWeightType;\nstatic bool ToXPlaceholderFontWeightType(std::string_view str, int& result) {\n  XPlaceholderFontWeightType type = XPlaceholderFontWeightType::kNormal;\n  if (str == \"normal\") {\n    type = XPlaceholderFontWeightType::kNormal;\n  } else if (str == \"bold\") {\n    type = XPlaceholderFontWeightType::kBold;\n  } else if (str == \"100\") {\n    type = XPlaceholderFontWeightType::k100;\n  } else if (str == \"200\") {\n    type = XPlaceholderFontWeightType::k200;\n  } else if (str == \"300\") {\n    type = XPlaceholderFontWeightType::k300;\n  } else if (str == \"400\") {\n    type = XPlaceholderFontWeightType::k400;\n  } else if (str == \"500\") {\n    type = XPlaceholderFontWeightType::k500;\n  } else if (str == \"600\") {\n    type = XPlaceholderFontWeightType::k600;\n  } else if (str == \"700\") {\n    type = XPlaceholderFontWeightType::k700;\n  } else if (str == \"800\") {\n    type = XPlaceholderFontWeightType::k800;\n  } else if (str == \"900\") {\n    type = XPlaceholderFontWeightType::k900;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::XPlaceholderFontStyleType;\nstatic bool ToXPlaceholderFontStyleType(std::string_view str, int& result) {\n  XPlaceholderFontStyleType type = XPlaceholderFontStyleType::kNormal;\n  if (str == \"normal\") {\n    type = XPlaceholderFontStyleType::kNormal;\n  } else if (str == \"italic\") {\n    type = XPlaceholderFontStyleType::kItalic;\n  } else if (str == \"oblique\") {\n    type = XPlaceholderFontStyleType::kOblique;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::PointerEventsType;\nstatic bool ToPointerEventsType(std::string_view str, int& result) {\n  PointerEventsType type = PointerEventsType::kAuto;\n  if (str == \"auto\") {\n    type = PointerEventsType::kAuto;\n  } else if (str == \"none\") {\n    type = PointerEventsType::kNone;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\n// AUTO INSERT END, DON'T CHANGE IT!\n\nusing starlight::FlexAlignType;\nstatic bool ToFlexAlignType(CSSPropertyID key, std::string_view str,\n                            int& result) {\n  FlexAlignType type = FlexAlignType::kAuto;\n  if (str == \"flex-start\") {\n    type = FlexAlignType::kFlexStart;\n  } else if (str == \"flex-end\") {\n    type = FlexAlignType::kFlexEnd;\n  } else if (str == \"center\") {\n    type = FlexAlignType::kCenter;\n  } else if (str == \"stretch\") {\n    type = FlexAlignType::kStretch;\n  } else if (str == \"baseline\") {\n    type = FlexAlignType::kBaseline;\n  } else if (str == \"auto\") {\n    type = FlexAlignType::kAuto;\n    // Compatible with the old version\n    if (key == kPropertyIDAlignItems) {\n      type = FlexAlignType::kStretch;\n    }\n  } else if (str == \"start\") {\n    type = FlexAlignType::kStart;\n  } else if (str == \"end\") {\n    type = FlexAlignType::kEnd;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::OverflowType;\nstatic bool ToOverflowType(std::string_view str, int& result) {\n  OverflowType type = OverflowType::kHidden;\n  if (str == \"visible\") {\n    type = OverflowType::kVisible;\n  } else if (str == \"scroll\") {\n    type = OverflowType::kScroll;\n  } else if (str == \"hidden\") {\n    type = OverflowType::kHidden;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::JustifyType;\nstatic bool ToJustifyType(CSSPropertyID key, std::string_view str,\n                          int& result) {\n  JustifyType type = JustifyType::kAuto;\n  if (str == \"start\") {\n    type = JustifyType::kStart;\n  } else if (str == \"end\") {\n    type = JustifyType::kEnd;\n  } else if (str == \"center\") {\n    type = JustifyType::kCenter;\n  } else if (str == \"stretch\") {\n    type = JustifyType::kStretch;\n  } else if (str == \"auto\") {\n    type = JustifyType::kAuto;\n  } else {\n    return false;\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\nusing starlight::GridAutoFlowType;\nstatic bool ToGridAutoFlowType(std::string_view str, int& result) {\n  GridAutoFlowType type = GridAutoFlowType::kRow;\n\n  bool has_row = str.find(\"row\") != std::string::npos;\n  bool has_dense = str.find(\"dense\") != std::string::npos;\n  bool has_column = str.find(\"column\") != std::string::npos;\n  if (has_row && has_column) {\n    return false;\n  }\n  if (!has_row && !has_column && !has_dense) {\n    return false;\n  }\n\n  if (has_dense) {\n    if (has_row) {\n      type = GridAutoFlowType::kRowDense;\n    } else if (has_column) {\n      type = GridAutoFlowType::kColumnDense;\n    } else {\n      type = GridAutoFlowType::kDense;\n    }\n  } else {\n    if (has_row) {\n      type = GridAutoFlowType::kRow;\n    } else if (has_column) {\n      type = GridAutoFlowType::kColumn;\n    }\n  }\n  result = static_cast<int>(type);\n  return true;\n}\n\n}  // namespace\nnamespace EnumHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  auto str = input.StringView();\n  int result(0);\n  bool success = false;\n  switch (key) {\n    // AUTO INSERT, DON'T CHANGE IT!\n    case kPropertyIDPosition:\n      success = ToPositionType(str, result);\n      break;\n    case kPropertyIDBoxSizing:\n      success = ToBoxSizingType(str, result);\n      break;\n    case kPropertyIDDisplay:\n      success = ToDisplayType(str, result);\n      break;\n    case kPropertyIDWhiteSpace:\n      success = ToWhiteSpaceType(str, result);\n      break;\n    case kPropertyIDTextAlign:\n      success = ToTextAlignType(str, result);\n      break;\n    case kPropertyIDTextOverflow:\n      success = ToTextOverflowType(str, result);\n      break;\n    case kPropertyIDFontWeight:\n      success = ToFontWeightType(str, result);\n      break;\n    case kPropertyIDFlexDirection:\n      success = ToFlexDirectionType(str, result);\n      break;\n    case kPropertyIDFlexWrap:\n      success = ToFlexWrapType(str, result);\n      break;\n    case kPropertyIDAlignContent:\n      success = ToAlignContentType(str, result);\n      break;\n    case kPropertyIDJustifyContent:\n      success = ToJustifyContentType(str, result);\n      break;\n    case kPropertyIDFontStyle:\n      success = ToFontStyleType(str, result);\n      break;\n    case kPropertyIDLinearOrientation:\n      success = ToLinearOrientationType(str, result);\n      break;\n    case kPropertyIDLinearGravity:\n      success = ToLinearGravityType(str, result);\n      break;\n    case kPropertyIDLinearLayoutGravity:\n      success = ToLinearLayoutGravityType(str, result);\n      break;\n    case kPropertyIDVisibility:\n      success = ToVisibilityType(str, result);\n      break;\n    case kPropertyIDWordBreak:\n      success = ToWordBreakType(str, result);\n      break;\n    case kPropertyIDDirection:\n      success = ToDirectionType(str, result);\n      break;\n    case kPropertyIDRelativeCenter:\n      success = ToRelativeCenterType(str, result);\n      break;\n    case kPropertyIDLinearCrossGravity:\n      success = ToLinearCrossGravityType(str, result);\n      break;\n    case kPropertyIDImageRendering:\n      success = ToImageRenderingType(str, result);\n      break;\n    case kPropertyIDHyphens:\n      success = ToHyphensType(str, result);\n      break;\n    case kPropertyIDXAppRegion:\n      success = ToXAppRegionType(str, result);\n      break;\n    case kPropertyIDXAnimationColorInterpolation:\n      success = ToXAnimationColorInterpolationType(str, result);\n      break;\n    case kPropertyIDFontOpticalSizing:\n      success = ToFontOpticalSizingType(str, result);\n      break;\n    case kPropertyIDXPlaceholderFontWeight:\n      success = ToXPlaceholderFontWeightType(str, result);\n      break;\n    case kPropertyIDXPlaceholderFontStyle:\n      success = ToXPlaceholderFontStyleType(str, result);\n      break;\n    case kPropertyIDPointerEvents:\n      success = ToPointerEventsType(str, result);\n      break;\n    // AUTO INSERT END, DON'T CHANGE IT!\n    case kPropertyIDLinearDirection:\n      success = ToLinearOrientationType(str, result);\n      break;\n    case kPropertyIDAlignItems:\n    case kPropertyIDAlignSelf:\n      success = ToFlexAlignType(key, str, result);\n      break;\n    case kPropertyIDOverflow:\n    case kPropertyIDOverflowX:\n    case kPropertyIDOverflowY:\n      success = ToOverflowType(str, result);\n      break;\n    case kPropertyIDJustifyItems:\n    case kPropertyIDJustifySelf:\n      success = ToJustifyType(key, str, result);\n      break;\n    case kPropertyIDGridAutoFlow:\n      success = ToGridAutoFlowType(str, result);\n      break;\n    default:\n      break;\n  }\n\n  CSS_HANDLER_FAIL_IF_NOT(success, configs.enable_css_strict_mode,\n                          TYPE_UNSUPPORTED,\n                          CSSProperty::GetPropertyNameCStr(key), str.data())\n  output.emplace_or_assign(key, result, CSSValuePattern::ENUM);\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  // AUTO INSERT, DON'T CHANGE IT!\n  array[kPropertyIDPosition] = &Handle;\n  array[kPropertyIDBoxSizing] = &Handle;\n  array[kPropertyIDDisplay] = &Handle;\n  array[kPropertyIDWhiteSpace] = &Handle;\n  array[kPropertyIDTextAlign] = &Handle;\n  array[kPropertyIDTextOverflow] = &Handle;\n  array[kPropertyIDFontWeight] = &Handle;\n  array[kPropertyIDFlexDirection] = &Handle;\n  array[kPropertyIDFlexWrap] = &Handle;\n  array[kPropertyIDAlignContent] = &Handle;\n  array[kPropertyIDJustifyContent] = &Handle;\n  array[kPropertyIDFontStyle] = &Handle;\n  array[kPropertyIDLinearOrientation] = &Handle;\n  array[kPropertyIDLinearGravity] = &Handle;\n  array[kPropertyIDLinearLayoutGravity] = &Handle;\n  array[kPropertyIDVisibility] = &Handle;\n  array[kPropertyIDWordBreak] = &Handle;\n  array[kPropertyIDDirection] = &Handle;\n  array[kPropertyIDRelativeCenter] = &Handle;\n  array[kPropertyIDLinearCrossGravity] = &Handle;\n  array[kPropertyIDImageRendering] = &Handle;\n  array[kPropertyIDHyphens] = &Handle;\n  array[kPropertyIDXAppRegion] = &Handle;\n  array[kPropertyIDXAnimationColorInterpolation] = &Handle;\n  array[kPropertyIDFontOpticalSizing] = &Handle;\n  array[kPropertyIDXPlaceholderFontWeight] = &Handle;\n  array[kPropertyIDXPlaceholderFontStyle] = &Handle;\n  array[kPropertyIDPointerEvents] = &Handle;\n  // AUTO INSERT END, DON'T CHANGE IT!\n  array[kPropertyIDLinearDirection] = &Handle;\n  array[kPropertyIDAlignItems] = &Handle;\n  array[kPropertyIDAlignSelf] = &Handle;\n  array[kPropertyIDOverflow] = &Handle;\n  array[kPropertyIDOverflowX] = &Handle;\n  array[kPropertyIDOverflowY] = &Handle;\n  array[kPropertyIDJustifyItems] = &Handle;\n  array[kPropertyIDJustifySelf] = &Handle;\n  array[kPropertyIDGridAutoFlow] = &Handle;\n}\n\n}  // namespace EnumHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/enum_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_ENUM_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_ENUM_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace EnumHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace EnumHandler\n\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_ENUM_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/enum_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/enum_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n// enum handler test case.\n// enum handler is auto generated by tools/css_generator/css_parser_generator.py\n// so just test only one can cover all enum type handler.\nTEST(EnumHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDAlignContent;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value(\"align\");\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  impl = lepus::Value(\"flex-start\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsInt32());\n  EXPECT_EQ((starlight::AlignContentType)output[id].GetNumber(),\n            starlight::AlignContentType::kFlexStart);\n\n  impl = lepus::Value(\"flex-end\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ((starlight::AlignContentType)output[id].GetNumber(),\n            starlight::AlignContentType::kFlexEnd);\n\n  impl = lepus::Value(\"center\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ((starlight::AlignContentType)output[id].GetNumber(),\n            starlight::AlignContentType::kCenter);\n\n  impl = lepus::Value(\"stretch\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ((starlight::AlignContentType)output[id].GetNumber(),\n            starlight::AlignContentType::kStretch);\n\n  impl = lepus::Value(\"space-between\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ((starlight::AlignContentType)output[id].GetNumber(),\n            starlight::AlignContentType::kSpaceBetween);\n\n  impl = lepus::Value(\"space-around\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ((starlight::AlignContentType)output[id].GetNumber(),\n            starlight::AlignContentType::kSpaceAround);\n\n  // other cases see before @link{process}.\n}\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/filter_handler.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/filter_handler.h\"\n\n#include <utility>\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/base_string.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FilterHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue filter_value = parser.ParseFilter();\n  if (filter_value.IsEmpty()) {\n    return false;\n  }\n  output.insert_or_assign(key, std::move(filter_value));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDFilter] = &Handle; }\n\n}  // namespace FilterHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/filter_handler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_FILTER_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_FILTER_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FilterHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace FilterHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_FILTER_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/filter_handler_unittest.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/filter_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(FilterHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDFilter;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  impl = lepus::Value(\"grays(1\");\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  impl = lepus::Value(\"grayscale(80%)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray().strongify();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(3));\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<int>(starlight::FilterType::kGrayscale));\n  EXPECT_EQ(arr->get(1).Number(), 80);\n  EXPECT_EQ(arr->get(2).Number(), static_cast<int>(CSSValuePattern::PERCENT));\n\n  impl = lepus::Value(\"blur(20px)\");\n  output.clear();\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), 3);\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<int>(starlight::FilterType::kBlur));\n  EXPECT_EQ(arr->get(1).Number(), 20);\n  EXPECT_EQ(arr->get(2).Number(), static_cast<int>(CSSValuePattern::PX));\n}\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/flex_flow_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/flex_flow_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/vector.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FlexFlowHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  auto str = input.StringView();\n  base::InlineVector<std::string, 2> styles;\n  base::SplitString(str, ' ', false, [&](const char* s, size_t length, int) {\n    styles.emplace_back(s, length);\n    return true;\n  });\n  if (styles.size() > 2) {\n    return false;\n  }\n  StyleMap tmp;\n  static CSSPropertyID property_ids[2] = {kPropertyIDFlexDirection,\n                                          kPropertyIDFlexWrap};\n  bool longhands[2] = {false, false};\n  for (const auto& style : styles) {\n    bool found_longhand = false;\n    for (size_t i = 0; !found_longhand && i < std::size(property_ids); ++i) {\n      if (longhands[i]) {\n        continue;\n      }\n      longhands[i] = UnitHandler::Process(property_ids[i], lepus::Value(style),\n                                          tmp, configs);\n\n      if (longhands[i]) {\n        found_longhand = true;\n      }\n    }\n    if (!found_longhand) {\n      return false;\n    }\n  }\n  for (size_t i = 0; i < std::size(property_ids); ++i) {\n    if (longhands[i]) {\n      output.insert_or_assign(property_ids[i], std::move(tmp[property_ids[i]]));\n    }\n  }\n\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDFlexFlow] = &Handle; }\n\n}  // namespace FlexFlowHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/flex_flow_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_FLEX_FLOW_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_FLEX_FLOW_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FlexFlowHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace FlexFlowHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_FLEX_FLOW_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/flex_flow_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/flex_flow_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nusing namespace starlight;\nnamespace tasm {\nnamespace test {\n\nTEST(FlexFlowHandler, OneDirection) {\n  auto id = CSSPropertyID::kPropertyIDFlexFlow;\n  StyleMap output;\n  CSSParserConfigs configs;\n  lepus::Value impl;\n\n  output.clear();\n  impl = lepus::Value(\"row\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[kPropertyIDFlexDirection].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexDirection].AsNumber(),\n            static_cast<double>(FlexDirectionType::kRow));\n\n  output.clear();\n  impl = lepus::Value(\"row-reverse\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[kPropertyIDFlexDirection].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexDirection].AsNumber(),\n            static_cast<double>(FlexDirectionType::kRowReverse));\n\n  output.clear();\n  impl = lepus::Value(\"column\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[kPropertyIDFlexDirection].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexDirection].AsNumber(),\n            static_cast<double>(FlexDirectionType::kColumn));\n\n  output.clear();\n  impl = lepus::Value(\"column-reverse\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[kPropertyIDFlexDirection].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexDirection].AsNumber(),\n            static_cast<double>(FlexDirectionType::kColumnReverse));\n}\n\nTEST(FlexFlowHandler, OneWrap) {\n  auto id = CSSPropertyID::kPropertyIDFlexFlow;\n  StyleMap output;\n  CSSParserConfigs configs;\n  lepus::Value impl;\n\n  output.clear();\n  impl = lepus::Value(\"nowrap\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[kPropertyIDFlexWrap].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexWrap].AsNumber(),\n            static_cast<double>(FlexWrapType::kNowrap));\n\n  output.clear();\n  impl = lepus::Value(\"wrap\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[kPropertyIDFlexWrap].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexWrap].AsNumber(),\n            static_cast<double>(FlexWrapType::kWrap));\n\n  output.clear();\n  impl = lepus::Value(\"wrap-reverse\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(1));\n  EXPECT_TRUE(output[kPropertyIDFlexWrap].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexWrap].AsNumber(),\n            static_cast<double>(FlexWrapType::kWrapReverse));\n}\n\nTEST(FlexFlowHandler, TwoValues) {\n  auto id = CSSPropertyID::kPropertyIDFlexFlow;\n  StyleMap output;\n  CSSParserConfigs configs;\n  lepus::Value impl;\n\n  output.clear();\n  impl = lepus::Value(\"row nowrap\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(2));\n  EXPECT_TRUE(output[kPropertyIDFlexDirection].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexDirection].AsNumber(),\n            static_cast<double>(FlexDirectionType::kRow));\n  EXPECT_TRUE(output[kPropertyIDFlexWrap].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexWrap].AsNumber(),\n            static_cast<double>(FlexWrapType::kNowrap));\n\n  output.clear();\n  impl = lepus::Value(\"column-reverse wrap-reverse\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(2));\n  EXPECT_TRUE(output[kPropertyIDFlexDirection].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexDirection].AsNumber(),\n            static_cast<double>(FlexDirectionType::kColumnReverse));\n  EXPECT_TRUE(output[kPropertyIDFlexWrap].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexWrap].AsNumber(),\n            static_cast<double>(FlexWrapType::kWrapReverse));\n\n  // wrap direction\n  output.clear();\n  impl = lepus::Value(\"wrap-reverse column-reverse \");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(2));\n  EXPECT_TRUE(output[kPropertyIDFlexDirection].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexDirection].AsNumber(),\n            static_cast<double>(FlexDirectionType::kColumnReverse));\n  EXPECT_TRUE(output[kPropertyIDFlexWrap].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexWrap].AsNumber(),\n            static_cast<double>(FlexWrapType::kWrapReverse));\n}\n\nTEST(FlexFlowHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDFlexFlow;\n  StyleMap output;\n  CSSParserConfigs configs;\n  lepus::Value impl;\n  bool ret;\n\n  output.clear();\n  impl = lepus::Value(\"invalid\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"row row\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"wrap wrap\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"column row\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/flex_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/flex_handler.h\"\n\n#include <string>\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FlexHandler {\n\nHANDLER_IMPL() {\n  if (input.IsNumber()) {\n    StyleMap num_map;\n    if (UnitHandler::Process(kPropertyIDFlexGrow, lepus::Value(input.Double()),\n                             num_map, configs) &&\n        UnitHandler::Process(kPropertyIDFlexShrink, lepus::Value(1), num_map,\n                             configs) &&\n        UnitHandler::Process(kPropertyIDFlexBasis, lepus::Value(0), num_map,\n                             configs)) {\n      for (auto& v : num_map) {\n        output.insert_or_assign(v.first, std::move(v.second));\n      }\n      return true;\n    } else {\n      CSS_HANDLER_FAIL_IF_NOT(\n          false, configs.enable_css_strict_mode, FORMAT_ERROR,\n          CSSProperty::GetPropertyNameCStr(key), input.Number())\n    }\n  }\n\n  if (!input.IsString()) {\n    CSS_HANDLER_FAIL_IF_NOT(false, configs.enable_css_strict_mode, TYPE_MUST_BE,\n                            CSSProperty::GetPropertyNameCStr(key),\n                            STRING_OR_NUMBER_TYPE)\n  }\n\n  double flex_grow = -1;\n  double flex_shrink = -1;\n  CSSValue flex_basis;\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto ret = parser.ParseFlex(flex_grow, flex_shrink, flex_basis);\n  if (ret) {\n    output.emplace_or_assign(kPropertyIDFlexGrow, flex_grow,\n                             CSSValuePattern::NUMBER);\n    output.emplace_or_assign(kPropertyIDFlexShrink, flex_shrink,\n                             CSSValuePattern::NUMBER);\n    output.insert_or_assign(kPropertyIDFlexBasis, std::move(flex_basis));\n  }\n  return ret;\n}\n\nHANDLER_REGISTER_IMPL() {\n  // AUTO INSERT, DON'T CHANGE IT!\n  array[kPropertyIDFlex] = &Handle;\n  // AUTO INSERT END, DON'T CHANGE IT!\n}\n\n}  // namespace FlexHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/flex_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_FLEX_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_FLEX_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FlexHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace FlexHandler\n\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_FLEX_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/flex_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/flex_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nusing namespace starlight;\nnamespace tasm {\nnamespace test {\n\nTEST(FlexHandler, One) {\n  auto id = CSSPropertyID::kPropertyIDFlex;\n  StyleMap output;\n  CSSParserConfigs configs;\n  configs.enable_new_flex_handler = true;\n\n  output.clear();\n  auto impl = lepus::Value(\"2\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 2);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 1);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 0);\n\n  output.clear();\n  impl = lepus::Value(\"20px\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 1);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 1);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsPx());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 20);\n}\n\nTEST(FlexHandler, Two) {\n  auto id = CSSPropertyID::kPropertyIDFlex;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  output.clear();\n  // flex-grow | flex-basis\n  auto impl = lepus::Value(\"3 100px\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 1);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsPx());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 100);\n\n  output.clear();\n  // flex-grow | flex-shrink\n  impl = lepus::Value(\"2 3\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 2);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 0);\n}\n\nTEST(FlexHandler, Three) {\n  auto id = CSSPropertyID::kPropertyIDFlex;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  output.clear();\n  // flex-grow | flex-shrink | flex-basis\n  auto impl = lepus::Value(\"2 3 10%\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 2);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsPercent());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 10);\n\n  output.clear();\n  // flex-basis | flex-grow | flex-shrink\n  impl = lepus::Value(\"10% 2 3\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 2);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsPercent());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 10);\n\n  output.clear();\n  // flex-grow | flex-shrink | flex-basis\n  impl = lepus::Value(\"2 3 0\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 2);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 0);\n\n  output.clear();\n  // flex-grow | flex-shrink | flex-basis(compat)\n  impl = lepus::Value(\"2 3 5\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 2);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 5);\n\n  output.clear();\n  // flex-grow | flex-shrink | flex-basis\n  impl = lepus::Value(\"1 0 100px\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 1);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 0);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsPx());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 100);\n}\n\nTEST(FlexHandler, EnableLengthUnitCheck) {\n  auto id = CSSPropertyID::kPropertyIDFlex;\n  StyleMap output;\n  CSSParserConfigs configs;\n  configs.enable_length_unit_check = true;\n\n  output.clear();\n  // flex-grow | flex-shrink | flex-basis\n  auto impl = lepus::Value(\"2 3 10%\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 2);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsPercent());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 10);\n\n  // invalid\n  impl = lepus::Value(\"10 2 3\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n}\n\nTEST(FlexHandler, Legacy) {\n  auto id = CSSPropertyID::kPropertyIDFlex;\n  StyleMap output;\n  CSSParserConfigs configs;\n  configs.enable_new_flex_handler = false;\n\n  auto impl = lepus::Value(\"20px\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 0);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 1);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsPx());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(), 20);\n\n  impl = lepus::Value(\"none\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_EQ(output.size(), 3);\n  EXPECT_TRUE(output[kPropertyIDFlexGrow].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexGrow].AsNumber(), 0);\n  EXPECT_TRUE(output[kPropertyIDFlexShrink].IsNumber());\n  EXPECT_EQ(output[kPropertyIDFlexShrink].AsNumber(), 1);\n  EXPECT_TRUE(output[kPropertyIDFlexBasis].IsEnum());\n  EXPECT_EQ(output[kPropertyIDFlexBasis].AsNumber(),\n            static_cast<int>(starlight::LengthValueType::kAuto));\n}\n\nTEST(FlexHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDFlex;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  output.clear();\n  auto impl = lepus::Value(\"hello\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_EQ(output.size(), 0);\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/font_feature_settings_handler.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/font_feature_settings_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FontFeatureSettingsHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto arr = lepus::CArray::Create();\n  if (!parser.ParseFontFeatureSettings(arr)) {\n    return false;\n  }\n  output.emplace_or_assign(key, std::move(arr));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDFontFeatureSettings] = &Handle; }\n\n}  // namespace FontFeatureSettingsHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/font_feature_settings_handler.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_FONT_FEATURE_SETTINGS_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_FONT_FEATURE_SETTINGS_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FontFeatureSettingsHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace FontFeatureSettingsHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_FONT_FEATURE_SETTINGS_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/font_length_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/font_length_handler.h\"\n\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FontLengthHandler {\nstatic constexpr float UNDEFINED = 10E20;\n\nHANDLER_IMPL() {\n  if (input.IsNumber()) {\n    output.emplace_or_assign(key, input, CSSValuePattern::NUMBER);\n    return true;\n  }\n  if (input.IsString()) {\n    if (input.StdString() == \"normal\") {\n      output.emplace_or_assign(key, UNDEFINED, CSSValuePattern::NUMBER);\n      return true;\n    }\n    CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n    auto res = parser.ParseFontLength();\n    if (!res.IsEmpty()) {\n      output.insert_or_assign(key, std::move(res));\n      return true;\n    }\n    return false;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() {\n  // AUTO INSERT, DON'T CHANGE IT!\n  array[kPropertyIDLineHeight] = &Handle;\n  array[kPropertyIDLetterSpacing] = &Handle;\n  array[kPropertyIDLineSpacing] = &Handle;\n  // AUTO INSERT END, DON'T CHANGE IT!\n}\n}  // namespace FontLengthHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/font_length_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_FONT_LENGTH_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_FONT_LENGTH_HANDLER_H_\n\n#include \"core/renderer/css/parser/length_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FontLengthHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace FontLengthHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_FONT_LENGTH_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/font_length_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/aspect_ratio_handler.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(FontLengthHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDLineHeight;\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(5);\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetPattern() == CSSValuePattern::NUMBER);\n  EXPECT_EQ(output[id].GetNumber(), 5);\n\n  output.clear();\n  impl = lepus::Value(\"10\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetPattern() == CSSValuePattern::NUMBER);\n  EXPECT_EQ(output[id].GetNumber(), 10);\n\n  output.clear();\n  impl = lepus::Value(\"20px\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetPattern() == CSSValuePattern::PX);\n  EXPECT_EQ(output[id].GetNumber(), 20);\n\n  output.clear();\n  impl = lepus::Value(\"30rpx\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 30);\n\n  output.clear();\n  impl = lepus::Value(\"40px 123456\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"normal\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  float UNDEFINED = 10E20;\n  EXPECT_EQ(output[id].GetNumber(), UNDEFINED);\n}\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/font_variation_settings_handler.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/font_variation_settings_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FontVariationSettingsHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto arr = lepus::CArray::Create();\n  if (!parser.ParseFontVariationSettings(arr)) {\n    return false;\n  }\n  output.emplace_or_assign(key, std::move(arr));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDFontVariationSettings] = &Handle; }\n\n}  // namespace FontVariationSettingsHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/font_variation_settings_handler.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_FONT_VARIATION_SETTINGS_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_FONT_VARIATION_SETTINGS_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FontVariationSettingsHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace FontVariationSettingsHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_FONT_VARIATION_SETTINGS_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/four_sides_shorthand_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/four_sides_shorthand_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nnamespace {\nconstexpr CSSPropertyID kMarginIDs[] = {\n    kPropertyIDMarginTop, kPropertyIDMarginRight, kPropertyIDMarginBottom,\n    kPropertyIDMarginLeft};\nconstexpr CSSPropertyID kBorderWidthIDs[] = {\n    kPropertyIDBorderTopWidth, kPropertyIDBorderRightWidth,\n    kPropertyIDBorderBottomWidth, kPropertyIDBorderLeftWidth};\nconstexpr CSSPropertyID kPaddingsIDs[] = {\n    kPropertyIDPaddingTop, kPropertyIDPaddingRight, kPropertyIDPaddingBottom,\n    kPropertyIDPaddingLeft};\nconstexpr CSSPropertyID kBorderColorIDs[] = {\n    kPropertyIDBorderTopColor, kPropertyIDBorderRightColor,\n    kPropertyIDBorderBottomColor, kPropertyIDBorderLeftColor};\nconstexpr CSSPropertyID kBorderStyleIDs[] = {\n    kPropertyIDBorderTopStyle, kPropertyIDBorderRightStyle,\n    kPropertyIDBorderBottomStyle, kPropertyIDBorderLeftStyle};\n}  // namespace\n\nnamespace FourSidesShorthandHandler {\n\nconst CSSPropertyID* GetLonghandProperties(CSSPropertyID property) {\n  const CSSPropertyID* properties = nullptr;\n  switch (property) {\n    case kPropertyIDMargin:\n      properties = kMarginIDs;\n      break;\n    case kPropertyIDBorderWidth:\n      properties = kBorderWidthIDs;\n      break;\n    case kPropertyIDPadding:\n      properties = kPaddingsIDs;\n      break;\n    case kPropertyIDBorderColor:\n      properties = kBorderColorIDs;\n      break;\n    case kPropertyIDBorderStyle:\n      properties = kBorderStyleIDs;\n      break;\n    default:\n      break;\n  }\n  return properties;\n}\n\nHANDLER_IMPL() {\n  const CSSPropertyID* properties = GetLonghandProperties(key);\n  if (!properties) {\n    return false;\n  }\n\n  output.reserve(output.size() + 4);\n  if (input.IsString()) {\n    auto str = input.StringView();\n    base::InlineVector<std::string, 4> combines;\n    base::SplitStringBySpaceOutOfBrackets(str, combines);\n    switch (combines.size()) {\n      case 1: {\n        UnitHandler::Process(properties[0], lepus::Value(combines[0]), output,\n                             configs);\n        auto it = output.find(properties[0]);\n        if (it != output.end()) {\n          output.insert_or_assign(properties[1], it->second);\n          output.insert_or_assign(properties[2], it->second);\n          output.insert_or_assign(properties[3], it->second);\n        } else {\n          return false;\n        }\n      } break;\n      case 2: {\n        UnitHandler::Process(properties[0], lepus::Value(combines[0]), output,\n                             configs);\n        UnitHandler::Process(properties[1], lepus::Value(combines[1]), output,\n                             configs);\n        auto it0 = output.find(properties[0]);\n        auto it1 = output.find(properties[1]);\n        if (it0 != output.end() && it1 != output.end()) {\n          output.insert_or_assign(properties[2], it0->second);\n          output.insert_or_assign(properties[3], it1->second);\n        } else {\n          return false;\n        }\n      } break;\n      case 3: {\n        UnitHandler::Process(properties[0], lepus::Value(combines[0]), output,\n                             configs);\n        UnitHandler::Process(properties[1], lepus::Value(combines[1]), output,\n                             configs);\n        UnitHandler::Process(properties[2], lepus::Value(combines[2]), output,\n                             configs);\n        auto it = output.find(properties[1]);\n        if (it != output.end()) {\n          output.insert_or_assign(properties[3], it->second);\n        } else {\n          return false;\n        }\n      } break;\n      case 4: {\n        UnitHandler::Process(properties[0], lepus::Value(combines[0]), output,\n                             configs);\n        UnitHandler::Process(properties[1], lepus::Value(combines[1]), output,\n                             configs);\n        UnitHandler::Process(properties[2], lepus::Value(combines[2]), output,\n                             configs);\n        UnitHandler::Process(properties[3], lepus::Value(combines[3]), output,\n                             configs);\n      } break;\n      default:\n        return false;\n    }\n  } else if (input.IsNumber()) {\n    CSS_HANDLER_FAIL_IF(\n        key == kPropertyIDBorderColor || key == kPropertyIDBorderStyle,\n        TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key), STRING_TYPE)\n    UnitHandler::Process(properties[0], input, output, configs);\n    if (auto find0 = output.find(properties[0]); find0 != output.end()) {\n      output.insert_or_assign(properties[1], find0->second);\n      output.insert_or_assign(properties[2], find0->second);\n      output.insert_or_assign(properties[3], find0->second);\n    } else {\n      return false;\n    }\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(false, configs.enable_css_strict_mode, TYPE_MUST_BE,\n                            CSSProperty::GetPropertyNameCStr(key),\n                            STRING_OR_NUMBER_TYPE)\n  }\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDMargin] = &Handle;\n  array[kPropertyIDPadding] = &Handle;\n  array[kPropertyIDBorderWidth] = &Handle;\n  array[kPropertyIDBorderColor] = &Handle;\n  array[kPropertyIDBorderStyle] = &Handle;\n}\n\nvoid AddProperty(CSSPropertyID property, CSSValue&& value, StyleMap& output) {\n  auto properties = GetLonghandProperties(property);\n  if (!properties || value.IsEmpty()) {\n    return;\n  }\n  output.insert_or_assign(properties[0], value);\n  output.insert_or_assign(properties[1], value);\n  output.insert_or_assign(properties[2], value);\n  output.insert_or_assign(properties[3], std::move(value));\n}\n\n}  // namespace FourSidesShorthandHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/four_sides_shorthand_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_FOUR_SIDES_SHORTHAND_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_FOUR_SIDES_SHORTHAND_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace FourSidesShorthandHandler {\n\nHANDLER_REGISTER_DECLARE();\n\nvoid AddProperty(CSSPropertyID property, CSSValue&& value, StyleMap& output);\n\n}  // namespace FourSidesShorthandHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_FOUR_SIDES_SHORTHAND_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/four_sides_shorthand_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/four_sides_shorthand_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nusing namespace starlight;\nnamespace tasm {\nnamespace test {\n\nTEST(FourSidesShorthandHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDMargin;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  // input invalid.\n  auto impl = lepus::Value(true);\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  // valid cases.\n  output.clear();\n  impl = lepus::Value(\"2px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_FALSE(output.find(kPropertyIDMarginTop) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginRight) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginBottom) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginLeft) == output.end());\n  EXPECT_EQ(output[kPropertyIDMarginLeft], output[kPropertyIDMarginRight]);\n  EXPECT_EQ(output[kPropertyIDMarginLeft], output[kPropertyIDMarginTop]);\n  EXPECT_EQ(output[kPropertyIDMarginLeft], output[kPropertyIDMarginRight]);\n  EXPECT_TRUE(output[kPropertyIDMarginTop].IsPx());\n  EXPECT_EQ((int)output[kPropertyIDMarginTop].GetNumber(), 2);\n\n  output.clear();\n  impl = lepus::Value(\"2px 3px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_FALSE(output.find(kPropertyIDMarginTop) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginRight) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginBottom) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginLeft) == output.end());\n  EXPECT_EQ(output[kPropertyIDMarginTop], output[kPropertyIDMarginBottom]);\n  EXPECT_EQ(output[kPropertyIDMarginRight], output[kPropertyIDMarginLeft]);\n  EXPECT_TRUE(output[kPropertyIDMarginTop].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginRight].IsPx());\n  EXPECT_EQ((int)output[kPropertyIDMarginTop].GetNumber(), 2);\n  EXPECT_EQ((int)output[kPropertyIDMarginRight].GetNumber(), 3);\n\n  output.clear();\n  impl = lepus::Value(\"2px 3px 4px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_FALSE(output.find(kPropertyIDMarginTop) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginRight) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginBottom) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginLeft) == output.end());\n  EXPECT_NE(output[kPropertyIDMarginTop], output[kPropertyIDMarginBottom]);\n  EXPECT_EQ(output[kPropertyIDMarginRight], output[kPropertyIDMarginLeft]);\n  EXPECT_TRUE(output[kPropertyIDMarginTop].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginRight].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginBottom].IsPx());\n  EXPECT_EQ((int)output[kPropertyIDMarginTop].GetNumber(), 2);\n  EXPECT_EQ((int)output[kPropertyIDMarginRight].GetNumber(), 3);\n  EXPECT_EQ((int)output[kPropertyIDMarginBottom].GetNumber(), 4);\n  EXPECT_EQ((int)output[kPropertyIDMarginLeft].GetNumber(), 3);\n\n  output.clear();\n  impl = lepus::Value(\"2px 3px 4px 5px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_FALSE(output.find(kPropertyIDMarginTop) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginRight) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginBottom) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginLeft) == output.end());\n  EXPECT_NE(output[kPropertyIDMarginTop], output[kPropertyIDMarginBottom]);\n  EXPECT_NE(output[kPropertyIDMarginRight], output[kPropertyIDMarginLeft]);\n  EXPECT_NE(output[kPropertyIDMarginTop], output[kPropertyIDMarginRight]);\n  EXPECT_TRUE(output[kPropertyIDMarginTop].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginRight].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginBottom].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginLeft].IsPx());\n  EXPECT_EQ((int)output[kPropertyIDMarginTop].GetNumber(), 2);\n  EXPECT_EQ((int)output[kPropertyIDMarginRight].GetNumber(), 3);\n  EXPECT_EQ((int)output[kPropertyIDMarginBottom].GetNumber(), 4);\n  EXPECT_EQ((int)output[kPropertyIDMarginLeft].GetNumber(), 5);\n\n  output.clear();\n  impl = lepus::Value(\" 2px  3px    4px     5px  \");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_FALSE(output.find(kPropertyIDMarginTop) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginRight) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginBottom) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDMarginLeft) == output.end());\n  EXPECT_NE(output[kPropertyIDMarginTop], output[kPropertyIDMarginBottom]);\n  EXPECT_NE(output[kPropertyIDMarginRight], output[kPropertyIDMarginLeft]);\n  EXPECT_NE(output[kPropertyIDMarginTop], output[kPropertyIDMarginRight]);\n  EXPECT_TRUE(output[kPropertyIDMarginTop].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginRight].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginBottom].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginLeft].IsPx());\n  EXPECT_EQ((int)output[kPropertyIDMarginTop].GetNumber(), 2);\n  EXPECT_EQ((int)output[kPropertyIDMarginRight].GetNumber(), 3);\n  EXPECT_EQ((int)output[kPropertyIDMarginBottom].GetNumber(), 4);\n  EXPECT_EQ((int)output[kPropertyIDMarginLeft].GetNumber(), 5);\n\n  output.clear();\n  impl = lepus::Value(\"2px 3em 4rem 5rpx\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[kPropertyIDMarginTop].IsPx());\n  EXPECT_TRUE(output[kPropertyIDMarginRight].IsEm());\n  EXPECT_TRUE(output[kPropertyIDMarginBottom].IsRem());\n  EXPECT_TRUE(output[kPropertyIDMarginLeft].IsRpx());\n\n  output.clear();\n  impl = lepus::Value(\"2% 3\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[kPropertyIDMarginTop].IsPercent());\n  EXPECT_TRUE(output[kPropertyIDMarginRight].IsNumber());\n\n  // invalid cases.\n  output.clear();\n  impl = lepus::Value(\"2test\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output.find(kPropertyIDMarginTop) == output.end());\n\n  output.clear();\n  impl = lepus::Value(\"test\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output.find(kPropertyIDMarginTop) == output.end());\n\n  // padding\n  id = kPropertyIDPadding;\n  output.clear();\n  impl = lepus::Value(\"2px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_FALSE(output.find(kPropertyIDPaddingTop) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDPaddingRight) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDPaddingBottom) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDPaddingLeft) == output.end());\n  EXPECT_EQ(output[kPropertyIDPaddingLeft], output[kPropertyIDPaddingRight]);\n  EXPECT_EQ(output[kPropertyIDPaddingLeft], output[kPropertyIDPaddingTop]);\n  EXPECT_EQ(output[kPropertyIDPaddingLeft], output[kPropertyIDPaddingRight]);\n  EXPECT_TRUE(output[kPropertyIDPaddingTop].IsPx());\n  EXPECT_EQ((int)output[kPropertyIDPaddingTop].GetNumber(), 2);\n\n  // border-width\n  id = kPropertyIDBorderWidth;\n  output.clear();\n  impl = lepus::Value(\"2px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_FALSE(output.find(kPropertyIDBorderTopWidth) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDBorderRightWidth) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDBorderBottomWidth) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDBorderLeftWidth) == output.end());\n  EXPECT_EQ(output[kPropertyIDBorderLeftWidth],\n            output[kPropertyIDBorderRightWidth]);\n  EXPECT_EQ(output[kPropertyIDBorderLeftWidth],\n            output[kPropertyIDBorderTopWidth]);\n  EXPECT_EQ(output[kPropertyIDBorderLeftWidth],\n            output[kPropertyIDBorderRightWidth]);\n  EXPECT_TRUE(output[kPropertyIDBorderTopWidth].IsPx());\n  EXPECT_EQ((int)output[kPropertyIDBorderTopWidth].GetNumber(), 2);\n\n  // border-style\n  id = kPropertyIDBorderStyle;\n  output.clear();\n  impl = lepus::Value(\"solid dashed dotted double\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_FALSE(output.find(kPropertyIDBorderTopStyle) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDBorderRightStyle) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDBorderBottomStyle) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDBorderLeftStyle) == output.end());\n  EXPECT_TRUE(output[kPropertyIDBorderTopStyle].IsEnum());\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderTopStyle].GetNumber(),\n            BorderStyleType::kSolid);\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderRightStyle].GetNumber(),\n            BorderStyleType::kDashed);\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderBottomStyle].GetNumber(),\n            BorderStyleType::kDotted);\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderLeftStyle].GetNumber(),\n            BorderStyleType::kDouble);\n\n  output.clear();\n  impl = lepus::Value(\"groove ridge inset outset\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderTopStyle].GetNumber(),\n            BorderStyleType::kGroove);\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderRightStyle].GetNumber(),\n            BorderStyleType::kRidge);\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderBottomStyle].GetNumber(),\n            BorderStyleType::kInset);\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderLeftStyle].GetNumber(),\n            BorderStyleType::kOutset);\n\n  output.clear();\n  impl = lepus::Value(\"hidden none\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderTopStyle].GetNumber(),\n            BorderStyleType::kHide);\n  EXPECT_EQ((BorderStyleType)output[kPropertyIDBorderRightStyle].GetNumber(),\n            BorderStyleType::kNone);\n\n  output.clear();\n  impl = lepus::Value(\"notstyle\");\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  // border-color\n  id = kPropertyIDBorderColor;\n  output.clear();\n  impl = lepus::Value(\"red #00ff00 #00ff00ee rgb(0,0,255)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.size(), static_cast<size_t>(4));\n  EXPECT_FALSE(output.find(kPropertyIDBorderTopColor) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDBorderRightColor) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDBorderBottomColor) == output.end());\n  EXPECT_FALSE(output.find(kPropertyIDBorderLeftColor) == output.end());\n  EXPECT_TRUE(output[kPropertyIDBorderTopColor].IsNumber());\n  EXPECT_EQ(output[kPropertyIDBorderTopColor].GetValue().UInt32(), 4294901760);\n  EXPECT_EQ(output[kPropertyIDBorderRightColor].GetValue().UInt32(),\n            4278255360);\n  EXPECT_EQ(output[kPropertyIDBorderBottomColor].GetValue().UInt32(),\n            3993042688);\n  EXPECT_EQ(output[kPropertyIDBorderLeftColor].GetValue().UInt32(), 4278190335);\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/gap_handler.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/gap_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nnamespace GapHandler {\n\nHANDLER_IMPL() {\n  const CSSPropertyID properties[2] = {kPropertyIDRowGap, kPropertyIDColumnGap};\n  if (input.IsString()) {\n    CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n    auto ret = parser.ParseGap();\n    bool parse_success = false;\n    if (!ret.first.IsEmpty()) {\n      output.insert_or_assign(kPropertyIDRowGap, std::move(ret.first));\n      parse_success = true;\n    }\n    if (!ret.second.IsEmpty()) {\n      output.insert_or_assign(kPropertyIDColumnGap, std::move(ret.second));\n    }\n    return parse_success;\n  } else if (input.IsNumber()) {\n    output.reserve(output.size() + 2);\n    UnitHandler::Process(properties[0], input, output, configs);\n    if (auto it = output.find(properties[0]); it != output.end()) {\n      output.insert_or_assign(properties[1], it->second);\n    } else {\n      return false;\n    }\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(false, configs.enable_css_strict_mode, TYPE_MUST_BE,\n                            CSSProperty::GetPropertyNameCStr(key),\n                            STRING_OR_NUMBER_TYPE)\n  }\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDGap] = &Handle; }\n\n}  // namespace GapHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/gap_handler.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_GAP_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_GAP_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace GapHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace GapHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_GAP_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/grid_position_handler.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/grid_position_handler.h\"\n\n#include <string>\n\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nnamespace GridPositionHandler {\nconstexpr int32_t kAutoValue = 0;\nconstexpr const char* kAuto = \"auto\";\n// \"span\"\nconstexpr int32_t kSpanStrSize = 4;\nconstexpr const char* kSpan = \"span\";\n\nHANDLER_IMPL() {\n  if (!input.IsString()) {\n    return false;\n  }\n\n  const auto& str = input.StdString();\n  if (str.find(kAuto) != std::string::npos) {\n    output.emplace_or_assign(key, kAutoValue, CSSValuePattern::NUMBER);\n    return true;\n  }\n\n  std::string::size_type span_pos = str.find(kSpan);\n  if (span_pos != std::string::npos) {\n    lepus::Value value =\n        lepus::Value(atoi(str.substr(span_pos + kSpanStrSize).c_str()));\n    if (key == kPropertyIDGridColumnStart || key == kPropertyIDGridColumnEnd) {\n      UnitHandler::Process(kPropertyIDGridColumnSpan, value, output, configs);\n    } else {\n      UnitHandler::Process(kPropertyIDGridRowSpan, value, output, configs);\n    }\n  } else {\n    output.emplace_or_assign(key, atoi(str.c_str()), CSSValuePattern::NUMBER);\n  }\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDGridColumnStart] = &Handle;\n  array[kPropertyIDGridColumnEnd] = &Handle;\n  array[kPropertyIDGridRowStart] = &Handle;\n  array[kPropertyIDGridRowEnd] = &Handle;\n}\n\n}  // namespace GridPositionHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/grid_position_handler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_GRID_POSITION_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_GRID_POSITION_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace GridPositionHandler {\nHANDLER_REGISTER_DECLARE();\n}\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_GRID_POSITION_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/grid_template_handler.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/grid_template_handler.h\"\n\n#include <algorithm>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/length_handler.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace GridTemplateHandler {\n\n// \"repeat()\" string size.\nnamespace {\nconstexpr size_t kRepeatFunMinSize = 8;\nconstexpr size_t kMinmaxFunMinSize = 8;\nconstexpr const char* kValueRepeat = \"repeat\";\nconstexpr const char* kValueMinmax = \"minmax\";\nconstexpr const char* kValueErrorMessage =\n    \"value must be a string or percentage array:%d\";\n\nbool ParserTrackListValue(const std::string& len_arr_str,\n                          fml::RefPtr<lepus::CArray> array,\n                          const CSSParserConfigs& configs) {\n  std::vector<std::string> arr_values;\n  base::SplitStringBySpaceOutOfBrackets(len_arr_str, arr_values);\n\n  const auto& ParserLengthValue =\n      [array, configs](const std::string& value_str) -> bool {\n    tasm::CSSValue css_value;\n    lepus::Value lepus_value(value_str);\n    if (!LengthHandler::Process(lepus_value, css_value, configs)) {\n      return false;\n    }\n\n    array->emplace_back(css_value.GetValue());\n    array->emplace_back(static_cast<int32_t>(css_value.GetPattern()));\n    return true;\n  };\n\n  for (const std::string& value_str : arr_values) {\n    if (value_str.empty()) {\n      continue;\n    }\n\n    const std::string::size_type minmax_pos = value_str.find(kValueMinmax);\n    // resolve minmax function.\n    if (minmax_pos != std::string::npos) {\n      const std::string& content_str = value_str.substr(\n          kMinmaxFunMinSize - 1, value_str.size() - kMinmaxFunMinSize);\n      std::vector<std::string> content_arr;\n      base::SplitString(content_str, ',', content_arr);\n      // Insert two additional lepus::Value, representing that the next two\n      // Length denotes the two parameters in minmax() function.\n      array->emplace_back(static_cast<int32_t>(CSSFunctionType::MINMAX));\n      array->emplace_back(static_cast<int32_t>(CSSValuePattern::ENUM));\n\n      if (content_arr.size() != 2 || !ParserLengthValue(content_arr[0]) ||\n          !ParserLengthValue(content_arr[1])) {\n        return false;\n      }\n    } else {\n      if (!ParserLengthValue(value_str)) {\n        return false;\n      }\n    }\n  }\n  return true;\n}\n\n// parm format:\"repeat(size,content);\"\nbool ResolveRepeatFunc(const std::string& repeat_func,\n                       fml::RefPtr<lepus::CArray> array,\n                       const CSSParserConfigs& configs) {\n  if (!base::BeginsWith(repeat_func, kValueRepeat)) {\n    return false;\n  }\n\n  const std::string& content_str = repeat_func.substr(\n      kRepeatFunMinSize - 1, repeat_func.size() - kRepeatFunMinSize);\n  const std::string::size_type comma_pos = content_str.find(',');\n  if (comma_pos == std::string::npos) {\n    return false;\n  }\n  const std::string& repeat_size_string = content_str.substr(0, comma_pos);\n  const std::string& track_list_string = content_str.substr(comma_pos + 1);\n  const int32_t repeat_size = std::max(atoi(repeat_size_string.c_str()), 0);\n  for (int32_t idx = 0; idx < repeat_size; ++idx) {\n    if (!ParserTrackListValue(track_list_string, array, configs)) {\n      return false;\n    }\n  }\n\n  return true;\n}\n}  // namespace\n\nHANDLER_IMPL() {\n  if (!(input.IsString())) {\n    return false;\n  }\n\n  auto array = lepus::CArray::Create();\n  const auto& value_str = input.StdString();\n  std::string::size_type value_str_idx = 0;\n  std::string::size_type value_str_size = value_str.size();\n  while (value_str_idx < value_str_size) {\n    std::string::size_type repeat_pos =\n        value_str.find(kValueRepeat, value_str_idx);\n    std::string::size_type length_end =\n        repeat_pos == std::string::npos ? value_str_size : repeat_pos;\n\n    CSS_HANDLER_FAIL_IF_NOT(\n        ParserTrackListValue(\n            value_str.substr(value_str_idx, length_end - value_str_idx), array,\n            configs),\n        configs.enable_css_strict_mode, kValueErrorMessage, key)\n\n    if (repeat_pos != std::string::npos) {\n      std::string::size_type repeat_end_pos =\n          repeat_pos + kRepeatFunMinSize - 1;\n      if (repeat_end_pos >= value_str_size) {\n        return false;\n      }\n      int32_t parenthesis_balance = 1;\n      for (; repeat_end_pos < value_str_size; ++repeat_end_pos) {\n        char current_char = value_str[repeat_end_pos];\n        if (current_char == '(') {\n          parenthesis_balance++;\n        } else if (current_char == ')') {\n          parenthesis_balance--;\n          if (parenthesis_balance == 0) {\n            break;\n          }\n        }\n      }\n\n      // The number of \")\" should be matched with the number of \"(\".\n      if (parenthesis_balance != 0) {\n        return false;\n      }\n\n      CSS_HANDLER_FAIL_IF_NOT(\n          ResolveRepeatFunc(\n              value_str.substr(repeat_pos, repeat_end_pos - length_end + 1),\n              array, configs),\n          configs.enable_css_strict_mode, kValueErrorMessage, key)\n      value_str_idx = repeat_end_pos + 1;\n    } else {\n      value_str_idx = length_end;\n    }\n  }\n\n  output.emplace_or_assign(key, std::move(array));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDGridTemplateColumns] = &Handle;\n  array[kPropertyIDGridTemplateRows] = &Handle;\n  array[kPropertyIDGridAutoColumns] = &Handle;\n  array[kPropertyIDGridAutoRows] = &Handle;\n}\n\n}  // namespace GridTemplateHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/grid_template_handler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_GRID_TEMPLATE_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_GRID_TEMPLATE_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace GridTemplateHandler {\nHANDLER_REGISTER_DECLARE();\n}\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_GRID_TEMPLATE_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/grid_template_handler_unittest.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/four_sides_shorthand_handler.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nusing namespace starlight;\nnamespace tasm {\nnamespace test {\n\nTEST(CSSProperty, GridTemplateHandler_OneLength) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"2px\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(2));\n  EXPECT_EQ(length_array->get(0).Number(), 2);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::PX);\n}\n\nTEST(CSSProperty, GridTemplateHandler_TwoLength) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"2px auto\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(4));\n  EXPECT_EQ(length_array->get(0).Number(), 2);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(2).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::ENUM);\n}\n\nTEST(CSSProperty, GridTemplateHandler_ThreeLength) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"2rpx auto auto\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(6));\n  EXPECT_EQ(length_array->get(0).Number(), 2);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::RPX);\n  EXPECT_EQ(length_array->get(2).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(4).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::ENUM);\n}\n\nTEST(CSSProperty, GridTemplateHandler_OneLengthAndRepeatOne) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"2rpx repeat(2, auto)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(6));\n  EXPECT_EQ(length_array->get(0).Number(), 2);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::RPX);\n  EXPECT_EQ(length_array->get(2).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(4).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::ENUM);\n}\n\nTEST(CSSProperty, GridTemplateHandler_OneLengthAndRepeatTwo) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"2rpx repeat(1, auto 100px)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(6));\n  EXPECT_EQ(length_array->get(0).Number(), 2);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::RPX);\n  EXPECT_EQ(length_array->get(2).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(4).Number(), 100);\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::PX);\n}\n\nTEST(CSSProperty, GridTemplateHandler_OneLengthAndRepeatFour) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"2rpx repeat(2, auto 100px)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(10));\n  EXPECT_EQ(length_array->get(0).Number(), 2);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::RPX);\n  EXPECT_EQ(length_array->get(2).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(4).Number(), 100);\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(6).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(7).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(8).Number(), 100);\n  EXPECT_EQ(length_array->get(9).Number(), (int)tasm::CSSValuePattern::PX);\n}\n\nTEST(CSSProperty, GridTemplateHandler_RepeatFourAndTwoLength) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"repeat(2, auto 100px) 2rpx auto\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(12));\n  EXPECT_EQ(length_array->get(0).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(2).Number(), 100);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(4).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(6).Number(), 100);\n  EXPECT_EQ(length_array->get(7).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(8).Number(), 2);\n  EXPECT_EQ(length_array->get(9).Number(), (int)tasm::CSSValuePattern::RPX);\n  EXPECT_EQ(length_array->get(0).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::ENUM);\n}\n\nTEST(CSSProperty,\n     GridTemplateHandler_OneLengthRepeatTwoAndOneLengthAndRepeatTwo) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"auto repeat(2, auto) 120rpx repeat(2, 100vh)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(12));\n  EXPECT_EQ(length_array->get(0).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(2).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(4).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(6).Number(), 120);\n  EXPECT_EQ(length_array->get(7).Number(), (int)tasm::CSSValuePattern::RPX);\n  EXPECT_EQ(length_array->get(8).Number(), 100);\n  EXPECT_EQ(length_array->get(9).Number(), (int)tasm::CSSValuePattern::VH);\n  EXPECT_EQ(length_array->get(10).Number(), 100);\n  EXPECT_EQ(length_array->get(11).Number(), (int)tasm::CSSValuePattern::VH);\n}\n\nTEST(CSSProperty, GridTemplateHandler_RepeatOne) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"repeat(1, 100px)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(2));\n  EXPECT_EQ(length_array->get(0).Number(), 100);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::PX);\n}\nTEST(CSSProperty, GridTemplateHandler_RepeatOneAndRepeatOne) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"repeat(1, 100px)repeat(1, 100px)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(4));\n  EXPECT_EQ(length_array->get(0).Number(), 100);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(2).Number(), 100);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::PX);\n}\n\nTEST(CSSProperty, GridTemplateHandler_RepeatOneAndOneLengthAndRepeatOne) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"repeat(1,100px)100pxrepeat(1,100px)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(6));\n  EXPECT_EQ(length_array->get(0).Number(), 100);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(2).Number(), 100);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(4).Number(), 100);\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::PX);\n}\n\nTEST(CSSProperty, GridTemplateHandler_None) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(0));\n}\n\nTEST(CSSProperty, GridTemplateHandler_OneCalc) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"calc(2px + 3rpx)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(2));\n  EXPECT_EQ(length_array->get(0).StringView(), \"calc(2px + 3rpx)\");\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::CALC);\n}\n\nTEST(CSSProperty, GridTemplateHandler_TwoCalc) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"calc(2px + 3rpx) calc(100px + (2vh - 100px))\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(4));\n  EXPECT_EQ(length_array->get(0).StringView(), \"calc(2px + 3rpx)\");\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(2).StringView(), \"calc(100px + (2vh - 100px))\");\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::CALC);\n}\n\nTEST(CSSProperty, GridTemplateHandler_CalcAndOtherLength) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"calc(2px + (1px - 3rpx)) 100rpx 20vw\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(6));\n  EXPECT_EQ(length_array->get(0).StringView(), \"calc(2px + (1px - 3rpx))\");\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(2).Number(), 100);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::RPX);\n  EXPECT_EQ(length_array->get(4).Number(), 20);\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::VW);\n}\n\nTEST(CSSProperty, GridTemplateHandler_RepeatCalcAndOtherLength) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\n      \"repeat(2, 100px calc(2px + 3rpx) calc(100px + (2vh - 100px))) 100px \"\n      \"2fr repeat(3, calc(2px + 3vw))\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(22));\n  EXPECT_EQ(length_array->get(0).Number(), 100);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(2).StringView(), \"calc(2px + 3rpx)\");\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(4).StringView(), \"calc(100px + (2vh - 100px))\");\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(6).Number(), 100);\n  EXPECT_EQ(length_array->get(7).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(8).StringView(), \"calc(2px + 3rpx)\");\n  EXPECT_EQ(length_array->get(9).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(10).StringView(), \"calc(100px + (2vh - 100px))\");\n  EXPECT_EQ(length_array->get(11).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(12).Number(), 100);\n  EXPECT_EQ(length_array->get(13).Number(), (int)tasm::CSSValuePattern::PX);\n  EXPECT_EQ(length_array->get(14).Number(), 2);\n  EXPECT_EQ(length_array->get(15).Number(), (int)tasm::CSSValuePattern::FR);\n  EXPECT_EQ(length_array->get(16).StringView(), \"calc(2px + 3vw)\");\n  EXPECT_EQ(length_array->get(17).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(18).StringView(), \"calc(2px + 3vw)\");\n  EXPECT_EQ(length_array->get(19).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(20).StringView(), \"calc(2px + 3vw)\");\n  EXPECT_EQ(length_array->get(21).Number(), (int)tasm::CSSValuePattern::CALC);\n}\n\nTEST(CSSProperty, GridTemplateHandler_Fr) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\"1fr 0.2fr auto 2fr\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(8));\n  EXPECT_EQ(length_array->get(0).Number(), 1);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::FR);\n  EXPECT_EQ(length_array->get(2).Number(), 0.2);\n  EXPECT_EQ(length_array->get(3).Number(), (int)tasm::CSSValuePattern::FR);\n  EXPECT_EQ(length_array->get(4).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(6).Number(), 2);\n  EXPECT_EQ(length_array->get(7).Number(), (int)tasm::CSSValuePattern::FR);\n}\n\nTEST(CSSProperty, GridTemplateHandler_MinMax1) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\n      \"minmax(max-content, calc(10px + 0.5em)) minmax(auto, 4%) \"\n      \"fit-content(calc(10px + 0.5em))\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(14));\n  EXPECT_EQ(length_array->get(0).Number(), (int)tasm::CSSFunctionType::MINMAX);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(2).StringView(), \"max-content\");\n  EXPECT_EQ(length_array->get(3).Number(),\n            (int)tasm::CSSValuePattern::INTRINSIC);\n  EXPECT_EQ(length_array->get(4).StringView(), \"calc(10px + 0.5em)\");\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(6).Number(), (int)tasm::CSSFunctionType::MINMAX);\n  EXPECT_EQ(length_array->get(7).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(8).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(9).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(10).Number(), 4);\n  EXPECT_EQ(length_array->get(11).Number(),\n            (int)tasm::CSSValuePattern::PERCENT);\n  EXPECT_EQ(length_array->get(12).StringView(),\n            \"fit-content(calc(10px + 0.5em))\");\n  EXPECT_EQ(length_array->get(13).Number(),\n            (int)tasm::CSSValuePattern::INTRINSIC);\n}\n\nTEST(CSSProperty, GridTemplateHandler_MinMax) {\n  auto id = CSSPropertyID::kPropertyIDGridTemplateRows;\n  StyleMap output;\n  CSSParserConfigs configs;\n  output.clear();\n  auto impl = lepus::Value(\n      \"repeat(2, minmax(max-content, calc(10px + 0.5em))) 1fr auto repeat(1, \"\n      \"minmax(calc(100px + 10vw), auto)) repeat(1, minmax(fit-content(100px), \"\n      \"2fr))\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto length_array = output[id].GetArray();\n  EXPECT_EQ(length_array->size(), static_cast<size_t>(28));\n  EXPECT_EQ(length_array->get(0).Number(), (int)tasm::CSSFunctionType::MINMAX);\n  EXPECT_EQ(length_array->get(1).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(2).StringView(), \"max-content\");\n  EXPECT_EQ(length_array->get(3).Number(),\n            (int)tasm::CSSValuePattern::INTRINSIC);\n  EXPECT_EQ(length_array->get(4).StringView(), \"calc(10px + 0.5em)\");\n  EXPECT_EQ(length_array->get(5).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(6).Number(), (int)tasm::CSSFunctionType::MINMAX);\n  EXPECT_EQ(length_array->get(7).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(8).StringView(), \"max-content\");\n  EXPECT_EQ(length_array->get(9).Number(),\n            (int)tasm::CSSValuePattern::INTRINSIC);\n  EXPECT_EQ(length_array->get(10).StringView(), \"calc(10px + 0.5em)\");\n  EXPECT_EQ(length_array->get(11).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(12).Number(), 1);\n  EXPECT_EQ(length_array->get(13).Number(), (int)tasm::CSSValuePattern::FR);\n  EXPECT_EQ(length_array->get(14).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(15).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(16).Number(), (int)tasm::CSSFunctionType::MINMAX);\n  EXPECT_EQ(length_array->get(17).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(18).StringView(), \"calc(100px + 10vw)\");\n  EXPECT_EQ(length_array->get(19).Number(), (int)tasm::CSSValuePattern::CALC);\n  EXPECT_EQ(length_array->get(20).Number(),\n            (int)starlight::LengthValueType::kAuto);\n  EXPECT_EQ(length_array->get(21).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(22).Number(), (int)tasm::CSSFunctionType::MINMAX);\n  EXPECT_EQ(length_array->get(23).Number(), (int)tasm::CSSValuePattern::ENUM);\n  EXPECT_EQ(length_array->get(24).StringView(), \"fit-content(100px)\");\n  EXPECT_EQ(length_array->get(25).Number(),\n            (int)tasm::CSSValuePattern::INTRINSIC);\n  EXPECT_EQ(length_array->get(26).Number(), 2);\n  EXPECT_EQ(length_array->get(27).Number(), (int)tasm::CSSValuePattern::FR);\n}\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/handler_defines.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_HANDLER_DEFINES_H_\n#define CORE_RENDERER_CSS_PARSER_HANDLER_DEFINES_H_\n\n#include <array>\n\n#include \"base/include/value/base_value.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/parser/css_parser_configs.h\"\n\n#define HANDLER_REGISTER_DECLARE() \\\n  void Register(std::array<pHandlerFunc, kCSSPropertyCount> &array)\n\n#define HANDLER_REGISTER_IMPL() \\\n  void Register(std::array<pHandlerFunc, kCSSPropertyCount> &array)\n\n#define HANDLER_REGISTER_IMPL_INL() \\\n  inline void Register(std::array<pHandlerFunc, kCSSPropertyCount> &array)\n\n#define HANDLER_IMPL()                                                        \\\n  bool Handle(CSSPropertyID key, const lepus::Value &input, StyleMap &output, \\\n              const CSSParserConfigs &configs)\n\n#define HANDLER_DECLARE()                                                     \\\n  bool Handle(CSSPropertyID key, const lepus::Value &input, StyleMap &output, \\\n              const CSSParserConfigs &configs)\n\nnamespace lynx {\nnamespace tasm {\n\nusing pHandlerFunc = bool (*)(CSSPropertyID key, const lepus::Value &input,\n                              StyleMap &output,\n                              const CSSParserConfigs &configs);\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_HANDLER_DEFINES_H_\n"
  },
  {
    "path": "core/renderer/css/parser/length_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/length_handler.h\"\n\n#include <string>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace LengthHandler {\n\nvoid CheckLengthUnitValid(CSSPropertyID key, const lepus::Value& input,\n                          const CSSValue& css_value,\n                          const CSSParserConfigs& configs) {\n  // TODO, currently the testcases online still doesnt carry units, so that\n  // this red box warning message would block CQ test, add the sdk version\n  // to turn off the warning until the testcases are fixed.\n  if (!configs.enable_length_unit_check) {\n    return;\n  }\n  // line-height: 3 is a valid css value(means the 3 times of font size)\n  if (key == CSSPropertyID::kPropertyIDLineHeight) {\n    return;\n  }\n  // number 0 doesn't need to carry any units\n  if (css_value.IsNumber() && css_value.GetNumber() != 0) {\n    UnitHandler::ReportError(\"CSS length need units (except 0)\",\n                             \"Add unit for length value\", key,\n                             input.StdString());\n  }\n}\n\nbool Process(const lepus::Value& input, CSSValue& css_value,\n             const CSSParserConfigs& configs) {\n  if (input.IsString()) {\n    CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n    parser.ParseLengthTo(css_value);\n    return !css_value.IsEmpty();\n  }\n\n  if (input.IsNumber()) {\n    css_value.SetValueAndPattern(input, CSSValuePattern::NUMBER);\n    return true;\n  }\n\n  return false;\n}\n\nHANDLER_IMPL() {\n  auto existing = output.insert_default_if_absent(key);\n  if (existing.second) {\n    // Insertion happened, key is not found in output but a default CSSValue is\n    // constructed in output map and pointed to by existing.first iterator.\n    // We parse directly to the value instance in map.\n    if (UNLIKELY(!Process(input, *existing.first, configs))) {\n      // Erase the inserted one to restore output map to original state.\n      output.erase(key);\n      goto fail;\n    }\n  } else {\n    // Found existing key, but the parsing may fail and we should not changed\n    // the existing value. So parse to temporary css_value and if all are\n    // successful assign to existing value.\n    CSSValue css_value;\n    if (UNLIKELY(!Process(input, css_value, configs))) {\n      goto fail;\n    }\n    *existing.first = css_value;\n  }\n  CheckLengthUnitValid(key, input, *existing.first, configs);\n  return true;\n\nfail:\n  if (configs.enable_css_strict_mode) {\n    UnitHandler::CSSWarningUnconditional(TYPE_UNSUPPORTED,\n                                         CSSProperty::GetPropertyNameCStr(key),\n                                         input.CString());\n  }\n  return false;\n}\n\n}  // namespace LengthHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/length_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/length_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(LengthHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDWidth;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value(true);\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  impl = lepus::Value(\"10px\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 10);\n  EXPECT_TRUE(output[id].IsPx());\n\n  impl = lepus::Value(\"1em\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 1);\n  EXPECT_TRUE(output[id].IsEm());\n\n  impl = lepus::Value(\"2.1rem\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 2.1);\n  EXPECT_TRUE(output[id].IsRem());\n\n  impl = lepus::Value(\"0.7rpx\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 0.7);\n  EXPECT_TRUE(output[id].IsRpx());\n\n  impl = lepus::Value(\"0.7vw\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 0.7);\n  EXPECT_TRUE(output[id].IsVw());\n\n  impl = lepus::Value(\"0.7vh\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 0.7);\n  EXPECT_TRUE(output[id].IsVh());\n\n  impl = lepus::Value(\"10%\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 10);\n  EXPECT_TRUE(output[id].IsPercent());\n\n  impl = lepus::Value(\"calc(2px + 3rpx)\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].GetValue().IsString());\n  EXPECT_EQ(output[id].GetValue().StringView(), \"calc(2px + 3rpx)\");\n  EXPECT_TRUE(output[id].IsCalc());\n\n  output.clear();\n  impl = lepus::Value(\"abcd\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_EQ(output.find(id), output.end());\n}\n\nTEST(LengthHandler, Process) {\n  auto impl = lepus::Value(true);\n  CSSValue css_value;\n  CSSParserConfigs configs;\n  EXPECT_FALSE(LengthHandler::Process(impl, css_value, configs));\n\n  impl = lepus::Value(\"10px\");\n  EXPECT_TRUE(LengthHandler::Process(impl, css_value, configs));\n  EXPECT_TRUE(css_value.IsPx());\n  EXPECT_EQ(css_value.GetNumber(), 10);\n}\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/list_gap_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/list_gap_handler.h\"\n\n#include <utility>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace ListGapHandler {\n\nHANDLER_IMPL() {\n  if (input.IsString()) {\n    CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n    auto res = parser.ParseListGap();\n    if (!res.IsEmpty()) {\n      output.insert_or_assign(key, std::move(res));\n      return true;\n    }\n    return false;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDListCrossAxisGap] = &Handle;\n  array[kPropertyIDListMainAxisGap] = &Handle;\n}\n\n}  // namespace ListGapHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/list_gap_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_LIST_GAP_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_LIST_GAP_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace ListGapHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace ListGapHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_LIST_GAP_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/list_gap_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/aspect_ratio_handler.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(ListGapHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDListMainAxisGap;\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(5);\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"10\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"20px\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetPattern() == CSSValuePattern::PX);\n  EXPECT_EQ(output[id].GetNumber(), 20);\n\n  output.clear();\n  impl = lepus::Value(\"30vh\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"40px 123456\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n}\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/mask_shorthand_handler.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/mask_shorthand_handler.h\"\n\n#include <string>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace MaskShorthandHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  parser.SetIsLegacyParser(false);\n  auto ret = parser.ParseBackgroundOrMask(true);\n  if (ret.IsEmpty()) {\n    return false;\n  }\n  const auto& mask = ret.GetArray();\n  output.emplace_or_assign(kPropertyIDMaskImage, mask->get(1).Array());\n  output.emplace_or_assign(kPropertyIDMaskPosition, mask->get(2).Array());\n  output.emplace_or_assign(kPropertyIDMaskSize, mask->get(3).Array());\n  output.emplace_or_assign(kPropertyIDMaskRepeat, mask->get(4).Array());\n  output.emplace_or_assign(kPropertyIDMaskOrigin, mask->get(5).Array());\n  output.emplace_or_assign(kPropertyIDMaskClip, mask->get(6).Array());\n  return true;\n}\nHANDLER_REGISTER_IMPL() { array[kPropertyIDMask] = &Handle; }\n}  // namespace MaskShorthandHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/mask_shorthand_handler.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_RENDERER_CSS_PARSER_MASK_SHORTHAND_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_MASK_SHORTHAND_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace MaskShorthandHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace MaskShorthandHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_MASK_SHORTHAND_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/number_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/number_handler.h\"\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_number_convert.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace NumberHandler {\n\nHANDLER_IMPL() {\n  double num = 0;\n  if (input.IsNumber()) {\n    num = input.Number();\n  } else if (input.IsString()) {\n    const auto& str = input.StdString();\n    if (str == \"infinite\") {\n      num = 10E8;\n    } else {\n      CSS_HANDLER_FAIL_IF_NOT(\n          base::StringToDouble(str, num, true), configs.enable_css_strict_mode,\n          TYPE_UNSUPPORTED, CSSProperty::GetPropertyNameCStr(key), str.c_str())\n    }\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(false, configs.enable_css_strict_mode, TYPE_MUST_BE,\n                            FLOAT_TYPE, STRING_OR_NUMBER_TYPE)\n  }\n  output.emplace_or_assign(key, num, CSSValuePattern::NUMBER);\n  return true;\n}\n\n}  // namespace NumberHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/number_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/number_handler.h\"\n\n#include \"core/renderer/css/unit_handler.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(NumberHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDOpacity;\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto impl = lepus::Value(true);\n\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"test\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"0.85\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_TRUE(output[id].IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 0.85);\n\n  output.clear();\n  impl = lepus::Value(0.99);\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_TRUE(output[id].IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 0.99);\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/offset_rotate_handler.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/offset_rotate_handler.h\"\n\n#include <string>\n\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace OffsetRotateHandler {\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  lepus::Value result = parser.ParseOffsetRotate();\n  CSS_HANDLER_FAIL_IF_NOT(result.IsNumber(), configs.enable_css_strict_mode,\n                          \"offset-rotate format error.\")\n  output.emplace_or_assign(key, result.Number(), CSSValuePattern::NUMBER);\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDOffsetRotate] = &Handle; }\n}  // namespace OffsetRotateHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/offset_rotate_handler.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_OFFSET_ROTATE_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_OFFSET_ROTATE_HANDLER_H_\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace OffsetRotateHandler {\nHANDLER_REGISTER_DECLARE();\n}  // namespace OffsetRotateHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_OFFSET_ROTATE_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/relative_align_handler.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/relative_align_handler.h\"\n\n#include <string>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_number_convert.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nnamespace {\nstatic constexpr char kRelativeAlignErrorMessage[] =\n    \"Value of %s must be \\\"parent\\\" or a positive number\";\n}\n\nnamespace RelativeAlignHandler {\n\nHANDLER_IMPL() {\n  bool valid = false;\n  int result = starlight::RelativeAlignType::kNone;\n\n  if (input.IsString()) {\n    const auto& str = input.StdString();\n    if (str == \"parent\") {\n      valid = true;\n      result = starlight::RelativeAlignType::kParent;\n    } else {\n      // TODO, parse from std::string_view with std::from_chars. Current\n      // toolchain does not support that.\n      valid = base::StringToInt(str, &result, 10);\n    }\n  } else if (input.IsNumber()) {\n    int number = input.Number();\n    valid = number > 0;\n    result = number;\n  }\n  CSS_HANDLER_FAIL_IF_NOT(valid, configs.enable_css_strict_mode,\n                          kRelativeAlignErrorMessage,\n                          CSSProperty::GetPropertyNameCStr(key))\n  output.emplace_or_assign(key, result, CSSValuePattern::NUMBER);\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDRelativeAlignTop] = &Handle;\n  array[kPropertyIDRelativeAlignBottom] = &Handle;\n  array[kPropertyIDRelativeAlignLeft] = &Handle;\n  array[kPropertyIDRelativeAlignRight] = &Handle;\n  array[kPropertyIDRelativeAlignInlineStart] = &Handle;\n  array[kPropertyIDRelativeAlignInlineEnd] = &Handle;\n}\n\n}  // namespace RelativeAlignHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/relative_align_handler.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_RELATIVE_ALIGN_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_RELATIVE_ALIGN_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace RelativeAlignHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace RelativeAlignHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_RELATIVE_ALIGN_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/shadow_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/shadow_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace ShadowHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto ret = parser.ParseShadow(key == kPropertyIDBoxShadow);\n  if (!ret.IsEmpty()) {\n    output.insert_or_assign(key, std::move(ret));\n    return true;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() {\n  array[kPropertyIDTextShadow] = &Handle;\n  array[kPropertyIDBoxShadow] = &Handle;\n}\n\n}  // namespace ShadowHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/shadow_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_SHADOW_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_SHADOW_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace ShadowHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace ShadowHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_SHADOW_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/shadow_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/shadow_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(ShadowHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDBoxShadow;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  impl = lepus::Value(\"1px 2px 3px 4px red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray().strongify();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(1));\n  EXPECT_TRUE(arr->get(0).IsTable());\n  auto table = arr->get(0).Table().strongify();\n  EXPECT_TRUE(table->GetValue(\"enable\").Bool());\n  EXPECT_EQ(table->GetValue(\"color\").Number(), 4294901760);\n  auto item_arr = table->GetValue(\"h_offset\").Array().strongify();\n  EXPECT_EQ(item_arr->get(0).Number(), 1);\n  EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n  item_arr = table->GetValue(\"v_offset\").Array();\n  EXPECT_EQ(item_arr->get(0).Number(), 2);\n  EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n  item_arr = table->GetValue(\"blur\").Array();\n  EXPECT_EQ(item_arr->get(0).Number(), 3);\n  EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n  item_arr = table->GetValue(\"spread\").Array();\n  EXPECT_EQ(item_arr->get(0).Number(), 4);\n  EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n\n  output.clear();\n  impl = lepus::Value(\"1px 2px 3px inset rgb(255, 0, 0)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(1));\n  EXPECT_TRUE(arr->get(0).IsTable());\n  table = arr->get(0).Table();\n  EXPECT_TRUE(table->GetValue(\"enable\").Bool());\n  EXPECT_EQ(table->GetValue(\"color\").Number(), 4294901760);\n  item_arr = table->GetValue(\"h_offset\").Array();\n  EXPECT_EQ(item_arr->get(0).Number(), 1);\n  EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n  item_arr = table->GetValue(\"v_offset\").Array();\n  EXPECT_EQ(item_arr->get(0).Number(), 2);\n  EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n  item_arr = table->GetValue(\"blur\").Array();\n  EXPECT_EQ(item_arr->get(0).Number(), 3);\n  EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n  EXPECT_EQ(table->GetValue(\"option\").Number(),\n            (int)starlight::ShadowOption::kInset);\n\n  output.clear();\n  impl =\n      lepus::Value(\"1px 2px 3px inset rgb(255, 0, 0), 1px 2px 3px inset red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n  for (size_t i = 0; i < arr->size(); i++) {\n    EXPECT_TRUE(arr->get(i).IsTable());\n    table = arr->get(i).Table();\n    EXPECT_TRUE(table->GetValue(\"enable\").Bool());\n    EXPECT_EQ(table->GetValue(\"color\").Number(), 0xffff0000);\n    item_arr = table->GetValue(\"h_offset\").Array();\n    EXPECT_EQ(item_arr->get(0).Number(), 1);\n    EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n    item_arr = table->GetValue(\"v_offset\").Array();\n    EXPECT_EQ(item_arr->get(0).Number(), 2);\n    EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n    item_arr = table->GetValue(\"blur\").Array();\n    EXPECT_EQ(item_arr->get(0).Number(), 3);\n    EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n    EXPECT_EQ(table->GetValue(\"option\").Number(),\n              (int)starlight::ShadowOption::kInset);\n  }\n\n  output.clear();\n  impl = lepus::Value(\"inset 1px 2px 3px red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(1));\n  for (size_t i = 0; i < arr->size(); i++) {\n    EXPECT_TRUE(arr->get(i).IsTable());\n    table = arr->get(i).Table();\n    EXPECT_TRUE(table->GetValue(\"enable\").Bool());\n    EXPECT_EQ(table->GetValue(\"color\").Number(), 0xffff0000);\n    item_arr = table->GetValue(\"h_offset\").Array();\n    EXPECT_EQ(item_arr->get(0).Number(), 1);\n    EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n    item_arr = table->GetValue(\"v_offset\").Array();\n    EXPECT_EQ(item_arr->get(0).Number(), 2);\n    EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n    item_arr = table->GetValue(\"blur\").Array();\n    EXPECT_EQ(item_arr->get(0).Number(), 3);\n    EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n    EXPECT_EQ(table->GetValue(\"option\").Number(),\n              (int)starlight::ShadowOption::kInset);\n  }\n}\n\nTEST(ShadowHandler, None) {\n  auto id = CSSPropertyID::kPropertyIDBoxShadow;\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto impl = lepus::Value(\"none\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray().strongify();\n  EXPECT_EQ(arr->size(), 0);\n\n  impl = lepus::Value(\"NONE\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), 0);\n\n  impl = lepus::Value(\"none \");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), 0);\n\n  impl = lepus::Value(\"none,\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n}\n\nTEST(ShadowHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDBoxShadow;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  bool ret;\n  output.clear();\n  auto impl = lepus::Value(\"1px 2px 3px inset red,\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"inset 1px 2px 3px inset red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"1px red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n}\n\nTEST(ShadowHandler, TextShadow) {\n  auto id = CSSPropertyID::kPropertyIDTextShadow;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  bool ret;\n  output.clear();\n  auto impl = lepus::Value(\"1px 2px 3px inset red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"inset 1px 2px 3px red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  // spread for text-shadow\n  impl = lepus::Value(\"1px 2px 3px 4px red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  output.clear();\n  impl = lepus::Value(\"1px 2px 3px red\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  auto arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(1));\n  for (size_t i = 0; i < arr->size(); i++) {\n    EXPECT_TRUE(arr->get(i).IsTable());\n    auto table = arr->get(i).Table();\n    EXPECT_TRUE(table->GetValue(\"enable\").Bool());\n    EXPECT_EQ(table->GetValue(\"color\").Number(), 0xFFFF0000);\n    auto item_arr = table->GetValue(\"h_offset\").Array().strongify();\n    EXPECT_EQ(item_arr->get(0).Number(), 1);\n    EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n    item_arr = table->GetValue(\"v_offset\").Array();\n    EXPECT_EQ(item_arr->get(0).Number(), 2);\n    EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n    item_arr = table->GetValue(\"blur\").Array();\n    EXPECT_EQ(item_arr->get(0).Number(), 3);\n    EXPECT_EQ((CSSValuePattern)item_arr->get(1).Number(), CSSValuePattern::PX);\n  }\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/string_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/string_handler.h\"\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace StringHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          \"id:%d value must be string.\",\n                          CSSProperty::GetPropertyNameCStr(key))\n  output.emplace_or_assign(key, input, CSSValuePattern::STRING);\n  return true;\n}\n\n}  // namespace StringHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/string_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/string_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(StringHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDFontFamily;\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto impl = lepus::Value(true);\n\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"test\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_TRUE(output[id].IsString());\n  EXPECT_EQ(output[id].GetValue().StringView(), \"test\");\n\n  output.clear();\n  impl = lepus::Value(\"+(*^%$.\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_TRUE(output[id].IsString());\n  EXPECT_EQ(output[id].GetValue().StringView(), \"+(*^%$.\");\n\n  output.clear();\n  impl = lepus::Value();\n\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(20);\n\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output.empty());\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/text_decoration_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/text_decoration_handler.h\"\n\n#include <string>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TextDecorationHandler {\n\nusing starlight::TextDecorationType;\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  return !(output.insert_or_assign(key, parser.ParseTextDecoration())\n               .first->IsEmpty());\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDTextDecoration] = &Handle; }\n\n}  // namespace TextDecorationHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/text_decoration_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_TEXT_DECORATION_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_TEXT_DECORATION_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TextDecorationHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace TextDecorationHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_TEXT_DECORATION_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/text_decoration_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/text_decoration_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(TextDecorationHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDTextDecoration;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"none\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr1 = output[id].GetArray();\n  EXPECT_EQ((starlight::TextDecorationType)arr1->get(0).Number(),\n            starlight::TextDecorationType::kNone);\n\n  output.clear();\n  impl = lepus::Value(\"underline line-through\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr2 = output[id].GetArray();\n  EXPECT_EQ((starlight::TextDecorationType)arr2->get(0).Number(),\n            starlight::TextDecorationType::kUnderLine);\n  EXPECT_EQ((starlight::TextDecorationType)arr2->get(1).Number(),\n            starlight::TextDecorationType::kLineThrough);\n\n  output.clear();\n  impl = lepus::Value(\"yellow dashed underline\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr3 = output[id].GetArray();\n  EXPECT_EQ((starlight::TextDecorationType)arr3->get(0).Number(),\n            starlight::TextDecorationType::kColor);\n  EXPECT_EQ(arr3->get(1).Number(), 0xffffff00);  // yellow rgb(255,255,0)\n  EXPECT_EQ((starlight::TextDecorationType)arr3->get(2).Number(),\n            starlight::TextDecorationType::kDashed);\n  EXPECT_EQ((starlight::TextDecorationType)arr3->get(3).Number(),\n            starlight::TextDecorationType::kUnderLine);\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/text_stroke_handler.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/text_stroke_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/string/string_utils.h\"\n#include \"core/renderer/css/css_color.h\"\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/css/parser/color_handler.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TextStrokeHandler {\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  CSSValue result_width;\n  CSSValue result_color;\n  bool ret = parser.ParseTextStroke(result_width, result_color);\n  output.insert_or_assign(kPropertyIDTextStrokeWidth,\n                          ret ? std::move(result_width) : CSSValue());\n  output.insert_or_assign(kPropertyIDTextStrokeColor,\n                          ret ? std::move(result_color) : CSSValue());\n  return ret;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDTextStroke] = &Handle; }\n}  // namespace TextStrokeHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/text_stroke_handler.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_TEXT_STROKE_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_TEXT_STROKE_HANDLER_H_\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TextStrokeHandler {\nHANDLER_REGISTER_DECLARE();\n}  // namespace TextStrokeHandler\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_TEXT_STROKE_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/text_stroke_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/text_stroke_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nTEST(TextStrokeHandler0, Handle) {\n  auto id = CSSPropertyID::kPropertyIDTextStroke;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\" 1px\");\n  id = CSSPropertyID::kPropertyIDTextStroke;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_FALSE(output.empty());\n  id = CSSPropertyID::kPropertyIDTextStrokeColor;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsEmpty());\n  id = CSSPropertyID::kPropertyIDTextStrokeWidth;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsEmpty());\n\n  output.clear();\n  impl = lepus::Value(\" yellow\");\n  id = CSSPropertyID::kPropertyIDTextStroke;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_FALSE(output.empty());\n  id = CSSPropertyID::kPropertyIDTextStrokeColor;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsEmpty());\n  id = CSSPropertyID::kPropertyIDTextStrokeWidth;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsEmpty());\n}\n\nTEST(TextStrokeHandler1, Handle) {\n  auto id = CSSPropertyID::kPropertyIDTextStroke;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value();\n  bool ret;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"1px yellow\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  id = CSSPropertyID::kPropertyIDTextStrokeColor;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_EQ(output[id].GetNumber(),\n            0xffffff00);  // yellow rgb(255,255,0)\n  id = CSSPropertyID::kPropertyIDTextStrokeWidth;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 1);\n  EXPECT_TRUE(output[id].IsPx());\n\n  output.clear();\n  impl = lepus::Value(\"            2px       yellow      \");\n  id = CSSPropertyID::kPropertyIDTextStroke;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  id = CSSPropertyID::kPropertyIDTextStrokeColor;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_EQ(output[id].GetNumber(),\n            0xffffff00);  // yellow rgb(255,255,0)\n  id = CSSPropertyID::kPropertyIDTextStrokeWidth;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 2.0);\n  EXPECT_TRUE(output[id].IsPx());\n\n  output.clear();\n  impl = lepus::Value(\"         yellow         1px       \");\n  id = CSSPropertyID::kPropertyIDTextStroke;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  id = CSSPropertyID::kPropertyIDTextStrokeColor;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_EQ(output[id].GetNumber(),\n            0xffffff00);  // yellow rgb(255,255,0)\n  id = CSSPropertyID::kPropertyIDTextStrokeWidth;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].GetValue().IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 1.0);\n  EXPECT_TRUE(output[id].IsPx());\n\n  output.clear();\n  impl = lepus::Value(\"NoNe\");\n  id = CSSPropertyID::kPropertyIDTextStroke;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  id = CSSPropertyID::kPropertyIDTextStrokeColor;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsEmpty());\n  id = CSSPropertyID::kPropertyIDTextStrokeWidth;\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsEmpty());\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/time_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/time_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TimeHandler {\n\nHANDLER_IMPL() {\n  if (input.IsNumber()) {\n    output.emplace_or_assign(key, input.Number(), CSSValuePattern::NUMBER);\n    return true;\n  } else if (input.IsString()) {\n    CSSValue css_value;\n    bool duration = key == kPropertyIDAnimationDuration ||\n                    key == kPropertyIDTransitionDuration;\n    CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n    if (parser.ParseSingleOrMultipleValuePreview<&CSSStringParser::ParseTime>(\n            duration, css_value)) {\n      output.insert_or_assign(key, std::move(css_value));\n      return true;\n    }\n  } else {\n    CSS_HANDLER_FAIL_IF_NOT(false, configs.enable_css_strict_mode, TYPE_MUST_BE,\n                            TIME_VALUE, STRING_OR_NUMBER_TYPE)\n  }\n  return false;\n}\n\n}  // namespace TimeHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/time_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/time_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(TimeHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDAnimationDuration;\n  StyleMap output;\n  CSSParserConfigs configs;\n  configs.enable_new_time_handler = true;\n  auto impl = lepus::Value(true);\n  bool ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"2s\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_TRUE(output[id].IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 2000);\n\n  output.clear();\n  impl = lepus::Value(\"2ms\");\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_TRUE(output[id].IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 2);\n\n  output.clear();\n  impl = lepus::Value(\"2000ms, 1s, 10s\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray().strongify();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(3));\n  EXPECT_EQ(arr->get(0).Number(), 2000);\n  EXPECT_EQ(arr->get(1).Number(), 1000);\n  EXPECT_EQ(arr->get(2).Number(), 10000);\n\n  output.clear();\n  impl = lepus::Value(\"2000ms,1s,   010ms \");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output.find(id) != output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(3));\n  EXPECT_EQ(arr->get(0).Number(), 2000);\n  EXPECT_EQ(arr->get(1).Number(), 1000);\n  EXPECT_EQ(arr->get(2).Number(), 10);\n}\n\nTEST(TimeHandler, Invalid) {\n  CSSPropertyID id = CSSPropertyID::kPropertyIDAnimationDuration;\n  CSSParserConfigs configs;\n  configs.enable_new_time_handler = true;\n\n  StyleMap output;\n  auto impl = lepus::Value(\"200\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n\n  impl = lepus::Value(\"0\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n\n  impl = lepus::Value(\"abc\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n\n  impl = lepus::Value(\"7 ms\");\n  EXPECT_FALSE(UnitHandler::Process(id, impl, output, configs));\n}\n\nTEST(TimeHandler, Compatibility) {\n  CSSPropertyID id = CSSPropertyID::kPropertyIDAnimationDuration;\n  CSSParserConfigs configs;\n  auto impl = lepus::Value(\"200\");\n  StyleMap output;\n  EXPECT_TRUE(UnitHandler::Process(id, impl, output, configs));\n  EXPECT_TRUE(output[id].IsNumber());\n  EXPECT_EQ(output[id].GetNumber(), 200);\n}\n\nTEST(TimeHandler, Negative) {\n  CSSParserConfigs configs;\n  configs.enable_new_time_handler = true;\n  auto impl = lepus::Value(\"-2ms\");\n  StyleMap output;\n  EXPECT_FALSE(UnitHandler::Process(kPropertyIDAnimationDuration, impl, output,\n                                    configs));\n  EXPECT_TRUE(\n      UnitHandler::Process(kPropertyIDAnimationDelay, impl, output, configs));\n  EXPECT_EQ(output[kPropertyIDAnimationDelay].GetNumber(), -2);\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/timing_function_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/timing_function_handler.h\"\n\n#include <utility>\n#include <vector>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/string/string_utils.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TimingFunctionHandler {\n\nHANDLER_IMPL() {\n  CSSValue css_value;\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  // For compatibility, the output must be an array\n  if (parser.ParseTimingFunction(false, css_value)) {\n    output.insert_or_assign(key, std::move(css_value));\n    return true;\n  }\n  return false;\n}\n\n}  // namespace TimingFunctionHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/timing_function_handler_unittest.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/timing_function_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(TimingFunctionHandler, Keywords) {\n  CSSPropertyID id = CSSPropertyID::kPropertyIDAnimationTimingFunction;\n  std::vector<std::pair<std::string, starlight::TimingFunctionType>> cases = {\n      {\"linear\", starlight::TimingFunctionType::kLinear},\n      {\"ease-in\", starlight::TimingFunctionType::kEaseIn},\n      {\"ease-out\", starlight::TimingFunctionType::kEaseOut},\n      {\"ease-in-ease-out\", starlight::TimingFunctionType::kEaseInEaseOut},\n      {\"ease\", starlight::TimingFunctionType::kEaseInEaseOut},\n      {\"ease-in-out\", starlight::TimingFunctionType::kEaseInEaseOut}};\n  CSSParserConfigs configs;\n  StyleMap output;\n  for (const auto& s : cases) {\n    output.clear();\n    EXPECT_TRUE(\n        UnitHandler::Process(id, lepus::Value(s.first), output, configs));\n    EXPECT_FALSE(output.empty());\n\n    EXPECT_TRUE(output[id].IsArray());\n    const auto& arr = output[id].GetArray();\n    EXPECT_TRUE(arr->get(0).IsNumber());\n    EXPECT_EQ(arr->get(0).Number(), static_cast<int>(s.second));\n  }\n}\n\nTEST(TimingFunctionHandler, Square) {\n  CSSPropertyID id = CSSPropertyID::kPropertyIDTransitionTimingFunction;\n  tasm::CSSValue out;\n  CSSParserConfigs configs;\n  StyleMap output;\n  std::string s = \"square-bezier(1, 0.5)\";\n  EXPECT_TRUE(UnitHandler::Process(id, lepus::Value(s), output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].GetValue().IsArray());\n  EXPECT_TRUE(output[id].GetArray()->get(0).IsArray());\n  const auto& arr = output[id].GetArray()->get(0).Array();\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kSquareBezier));\n  EXPECT_EQ(arr->get(1).Number(), 1);\n  EXPECT_EQ(arr->get(2).Number(), 0.5);\n}\n\nTEST(TimingFunctionHandler, Cubic) {\n  CSSPropertyID id = CSSPropertyID::kPropertyIDTransitionTimingFunction;\n  tasm::CSSValue out;\n  CSSParserConfigs configs;\n  StyleMap output;\n  std::string s = \"cubic-bezier(1, 0.5, 0.5, 1)\";\n  EXPECT_TRUE(UnitHandler::Process(id, lepus::Value(s), output, configs));\n  EXPECT_FALSE(output.empty());\n  EXPECT_TRUE(output[id].GetValue().IsArray());\n  EXPECT_TRUE(output[id].GetArray()->get(0).IsArray());\n  const auto& arr = output[id].GetArray()->get(0).Array();\n\n  EXPECT_EQ(arr->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kCubicBezier));\n  EXPECT_EQ(arr->get(1).Number(), 1);\n  EXPECT_EQ(arr->get(2).Number(), 0.5);\n  EXPECT_EQ(arr->get(3).Number(), 0.5);\n  EXPECT_EQ(arr->get(4).Number(), 1);\n}\n\nTEST(TimingFunctionHandler, StepsKeywords) {\n  CSSPropertyID id = CSSPropertyID::kPropertyIDTransitionTimingFunction;\n  tasm::CSSValue out;\n  CSSParserConfigs configs;\n  StyleMap output;\n  std::vector<std::pair<std::string, starlight::StepsType>> cases = {\n      {\"step-start\", starlight::StepsType::kStart},\n      {\"step-end\", starlight::StepsType::kEnd}};\n\n  for (const auto& s : cases) {\n    output.clear();\n    SCOPED_TRACE(s.first);\n    EXPECT_TRUE(\n        UnitHandler::Process(id, lepus::Value(s.first), output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output[id].GetValue().IsArray());\n    const auto& arr = output[id].GetArray();\n    EXPECT_TRUE(arr->get(0).IsArray());\n    const auto& item = arr->get(0).Array();\n    EXPECT_EQ(item->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kSteps));\n    EXPECT_EQ(item->get(1).Number(), 1);\n    EXPECT_EQ(item->get(2).Number(), static_cast<int>(s.second));\n  }\n}\n\nTEST(TimingFunctionHandler, StepsFunction) {\n  CSSPropertyID id = CSSPropertyID::kPropertyIDTransitionTimingFunction;\n  tasm::CSSValue out;\n  CSSParserConfigs configs;\n  StyleMap output;\n  std::vector<std::pair<std::string, starlight::StepsType>> cases = {\n      {\"steps(1, jump-start)\", starlight::StepsType::kStart},\n      {\"steps(1, jump-end)\", starlight::StepsType::kEnd},\n      {\"steps(1, jump-none)\", starlight::StepsType::kJumpNone},\n      {\"steps(1, jump-both)\", starlight::StepsType::kJumpBoth},\n      {\"steps(1, jump-start)\", starlight::StepsType::kStart},\n      {\"steps(1, jump-end)\", starlight::StepsType::kEnd},\n  };\n\n  for (const auto& s : cases) {\n    output.clear();\n    SCOPED_TRACE(s.first);\n    EXPECT_TRUE(\n        UnitHandler::Process(id, lepus::Value(s.first), output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output[id].GetValue().IsArray());\n    EXPECT_TRUE(output[id].GetArray()->get(0).IsArray());\n    const auto& arr = output[id].GetArray()->get(0).Array();\n\n    EXPECT_EQ(arr->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kSteps));\n    EXPECT_EQ(arr->get(1).Number(), 1);\n    EXPECT_EQ(arr->get(2).Number(), static_cast<int>(s.second));\n  }\n}\n\nTEST(TimingFunctionHandler, Multi) {\n  CSSPropertyID id = CSSPropertyID::kPropertyIDTransitionTimingFunction;\n  tasm::CSSValue out;\n  CSSParserConfigs configs;\n  StyleMap output;\n  std::vector<std::pair<std::string,\n                        std::pair<starlight::StepsType, starlight::StepsType>>>\n      cases = {{\"steps(1, jump-start), step-end\",\n                {starlight::StepsType::kStart, starlight::StepsType::kEnd}},\n               {\"step-start, steps(1, jump-end)\",\n                {starlight::StepsType::kStart, starlight::StepsType::kEnd}}};\n\n  for (const auto& s : cases) {\n    output.clear();\n    EXPECT_TRUE(\n        UnitHandler::Process(id, lepus::Value(s.first), output, configs));\n    EXPECT_FALSE(output.empty());\n    EXPECT_TRUE(output[id].IsArray());\n    EXPECT_TRUE(output[id].GetArray()->get(0).IsArray());\n    {\n      const auto& arr = output[id].GetArray()->get(0).Array();\n\n      EXPECT_EQ(arr->get(0).Number(),\n                static_cast<int>(starlight::TimingFunctionType::kSteps));\n      EXPECT_EQ(arr->get(1).Number(), 1);\n      EXPECT_EQ(arr->get(2).Number(), static_cast<int>(s.second.first));\n    }\n    {\n      const auto& arr = output[id].GetArray()->get(1).Array();\n\n      EXPECT_EQ(arr->get(0).Number(),\n                static_cast<int>(starlight::TimingFunctionType::kSteps));\n      EXPECT_EQ(arr->get(1).Number(), 1);\n      EXPECT_EQ(arr->get(2).Number(), static_cast<int>(s.second.second));\n    }\n  }\n}\n\nTEST(TimingFunctionHandler, Invalid) {\n  CSSPropertyID id = CSSPropertyID::kPropertyIDTransitionTimingFunction;\n  tasm::CSSValue out;\n  CSSParserConfigs configs;\n  StyleMap output;\n  std::vector<std::string> cases = {\n      \"\",         \"hello\",     \"ease, \",         \"cubic-bezier(1, 0.5)\",\n      \"steps(1)\", \"steps(1,)\", \"steps(1, hello)\"};\n\n  for (const auto& s : cases) {\n    output.clear();\n    EXPECT_FALSE(UnitHandler::Process(id, lepus::Value(s), output, configs));\n    EXPECT_TRUE(output.empty());\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/transform_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/transform_handler.h\"\n\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\n#ifdef OS_WIN\n#define _USE_MATH_DEFINES\n#include <math.h>\n#endif\n\nnamespace lynx {\nnamespace tasm {\nnamespace TransformHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto ret = parser.ParseTransform();\n  if (!ret.IsEmpty()) {\n    output.insert_or_assign(key, std::move(ret));\n    return true;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDTransform] = &Handle; }\n}  // namespace TransformHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/transform_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_TRANSFORM_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_TRANSFORM_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TransformHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace TransformHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_TRANSFORM_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/transform_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/transform_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(TransformHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDTransform;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value();\n  bool ret;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  impl = lepus::Value(\"translate(1px,\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  impl = lepus::Value(\"translate(1px, 2px) scale(0.1) rotate(10deg)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray().strongify();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(3));\n  auto item = arr->get(0).Array().strongify();\n  EXPECT_EQ(item->size(), static_cast<size_t>(5));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kTranslate);\n  EXPECT_EQ(item->get(1).Number(), 1);\n  EXPECT_EQ(item->get(2).Number(), (int)CSSValuePattern::PX);\n  EXPECT_EQ(item->get(3).Number(), 2);\n  EXPECT_EQ(item->get(4).Number(), (int)CSSValuePattern::PX);\n  item = arr->get(1).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kScale);\n  EXPECT_FLOAT_EQ(item->get(1).Number(), 0.1);\n  item = arr->get(2).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotate);\n  EXPECT_EQ(item->get(1).Number(), 10);\n\n  impl = lepus::Value(\"translate(1px, 2px) scale(1.1,1.5) rotateX(15deg)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(3));\n  item = arr->get(0).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(5));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kTranslate);\n  EXPECT_EQ(item->get(1).Number(), 1);\n  EXPECT_EQ(item->get(2).Number(), (int)CSSValuePattern::PX);\n  EXPECT_EQ(item->get(3).Number(), 2);\n  EXPECT_EQ(item->get(4).Number(), (int)CSSValuePattern::PX);\n  item = arr->get(1).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(3));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kScale);\n  EXPECT_FLOAT_EQ(item->get(1).Number(), 1.1);\n  EXPECT_FLOAT_EQ(item->get(2).Number(), 1.5);\n  item = arr->get(2).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateX);\n  EXPECT_EQ(item->get(1).Number(), 15);\n\n  impl = lepus::Value(\"translate3d(1px, 2px, 3px) rotateY(30deg)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n  item = arr->get(0).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(7));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kTranslate3d);\n  EXPECT_EQ(item->get(1).Number(), 1);\n  EXPECT_EQ(item->get(2).Number(), (int)CSSValuePattern::PX);\n  EXPECT_EQ(item->get(3).Number(), 2);\n  EXPECT_EQ(item->get(4).Number(), (int)CSSValuePattern::PX);\n  EXPECT_EQ(item->get(5).Number(), 3);\n  EXPECT_EQ(item->get(4).Number(), (int)CSSValuePattern::PX);\n  item = arr->get(1).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateY);\n  EXPECT_FLOAT_EQ(item->get(1).Number(), 30);\n\n  impl = lepus::Value(\"rotateX(-30deg) rotateY(-10deg)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n  item = arr->get(0).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateX);\n  EXPECT_EQ(item->get(1).Number(), -30);\n  item = arr->get(1).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateY);\n  EXPECT_FLOAT_EQ(item->get(1).Number(), -10);\n\n  impl = lepus::Value(\"rotateX(-30deg) rotateY(-10deg)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n  item = arr->get(0).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateX);\n  EXPECT_EQ(item->get(1).Number(), -30);\n  item = arr->get(1).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateY);\n  EXPECT_FLOAT_EQ(item->get(1).Number(), -10);\n\n  impl = lepus::Value(\"rotateX(-30deg) rotateY(-10deg)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n  item = arr->get(0).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateX);\n  EXPECT_EQ(item->get(1).Number(), -30);\n  item = arr->get(1).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateY);\n  EXPECT_FLOAT_EQ(item->get(1).Number(), -10);\n\n  impl = lepus::Value(\"scale(-10, 20) translate3d(2px, -4px, 5px)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n  item = arr->get(0).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(3));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kScale);\n  EXPECT_EQ(item->get(1).Number(), -10);\n  EXPECT_EQ(item->get(2).Number(), 20);\n  item = arr->get(1).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(7));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kTranslate3d);\n  EXPECT_FLOAT_EQ(item->get(1).Number(), 2);\n  EXPECT_EQ(item->get(2).Number(), (int)CSSValuePattern::PX);\n  EXPECT_FLOAT_EQ(item->get(3).Number(), -4);\n  EXPECT_EQ(item->get(4).Number(), (int)CSSValuePattern::PX);\n  EXPECT_FLOAT_EQ(item->get(5).Number(), 5);\n  EXPECT_EQ(item->get(6).Number(), (int)CSSValuePattern::PX);\n\n  impl = lepus::Value(\n      \"translateX(2px) translateY(3px) translateZ(-4px) rotateX(10deg) \"\n      \"rotateY(20deg) rotateZ(-10deg)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), static_cast<size_t>(6));\n  item = arr->get(0).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(3));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kTranslateX);\n  EXPECT_EQ(item->get(1).Number(), 2);\n  EXPECT_EQ(item->get(2).Number(), (int)CSSValuePattern::PX);\n  item = arr->get(1).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(3));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kTranslateY);\n  EXPECT_FLOAT_EQ(item->get(1).Number(), 3);\n  EXPECT_EQ(item->get(2).Number(), (int)CSSValuePattern::PX);\n  item = arr->get(2).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(3));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kTranslateZ);\n  EXPECT_FLOAT_EQ(item->get(1).Number(), -4);\n  EXPECT_EQ(item->get(2).Number(), (int)CSSValuePattern::PX);\n  item = arr->get(3).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateX);\n  EXPECT_EQ(item->get(1).Number(), 10);\n  item = arr->get(4).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateY);\n  EXPECT_EQ(item->get(1).Number(), 20);\n  item = arr->get(5).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(2));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateZ);\n  EXPECT_EQ(item->get(1).Number(), -10);\n}\n\nTEST(TransformHandler, One) {\n  auto id = CSSPropertyID::kPropertyIDTransform;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value(\"translate(1px)\");\n  bool ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), 1);\n  auto item = arr->get(0).Array();\n  EXPECT_EQ(item->size(), static_cast<size_t>(3));\n  EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kTranslate);\n  EXPECT_EQ(item->get(1).Number(), 1);\n  EXPECT_EQ(item->get(2).Number(), (int)CSSValuePattern::PX);\n}\n\nTEST(TransformHandler, None) {\n  auto id = CSSPropertyID::kPropertyIDTransform;\n  StyleMap output;\n  CSSParserConfigs configs;\n  auto impl = lepus::Value(\"none\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray();\n  EXPECT_EQ(arr->size(), 0);\n}\n\nTEST(TransformHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDTransform;\n  StyleMap output;\n  CSSParserConfigs configs;\n  configs.enable_new_transform_handler = true;\n\n  bool ret;\n  lepus::Value impl;\n\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  impl = lepus::Value(\"\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  impl = lepus::Value(\"rotate(20)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  impl = lepus::Value(\"skew(20deg, 20)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  impl = lepus::Value(\"scale(20px)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  impl = lepus::Value(\"scale(2, 20px)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  impl = lepus::Value(\"scale(2, 20px)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  impl = lepus::Value(\"translate(1px, 10\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  impl = lepus::Value(\"translate(1px, 10px, 10px)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n\n  // Need three values\n  impl = lepus::Value(\"translate3d(2px, -4px)\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n}\n\nTEST(TransformHandler, Compatibility) {\n  auto id = CSSPropertyID::kPropertyIDTransform;\n  CSSParserConfigs configs;\n  std::vector<std::pair<std::string, std::string>> cases = {\n      {\"rotate(20)\", \"rotate(20deg)\"},\n      {\"translate(1px, 10px, 10px)\", \"translate(1px, 10px)\"},\n  };\n\n  for (const auto& c : cases) {\n    StyleMap output;\n    auto ret = UnitHandler::Process(id, lepus::Value(c.first), output, configs);\n    EXPECT_TRUE(ret);\n    StyleMap right;\n    UnitHandler::Process(id, lepus::Value(c.second), right, configs);\n    EXPECT_EQ(output[id], right[id]);\n  }\n}\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/transform_origin_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/transform_origin_handler.h\"\n\n#include <cmath>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TransformOriginHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  auto ret = parser.ParseTransformOrigin();\n  if (!ret.IsEmpty()) {\n    output.insert_or_assign(key, std::move(ret));\n    return true;\n  }\n  return false;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDTransformOrigin] = &Handle; }\n\n}  // namespace TransformOriginHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/transform_origin_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_TRANSFORM_ORIGIN_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_TRANSFORM_ORIGIN_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TransformOriginHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace TransformOriginHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_TRANSFORM_ORIGIN_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/transform_origin_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/transform_origin_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\nTEST(TransformOriginHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDTransformOrigin;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value();\n  bool ret;\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n\n  output.clear();\n  impl = lepus::Value(\"10px\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray().strongify();\n  EXPECT_EQ(arr->get(0).Number(), 10);\n  EXPECT_EQ(arr->get(1).Number(), (int)CSSValuePattern::PX);\n  EXPECT_EQ(arr->get(2).Number(), 50);\n  EXPECT_EQ(arr->get(3).Number(), (int)CSSValuePattern::PERCENT);\n\n  output.clear();\n  impl = lepus::Value(\"10px 10%\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(), 10);\n  EXPECT_EQ(arr->get(1).Number(), (int)CSSValuePattern::PX);\n  EXPECT_EQ(arr->get(2).Number(), 10);\n  EXPECT_EQ(arr->get(3).Number(), (int)CSSValuePattern::PERCENT);\n\n  output.clear();\n  impl = lepus::Value(\"left top\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(), 0);\n  EXPECT_EQ(arr->get(1).Number(), (int)CSSValuePattern::PERCENT);\n  EXPECT_EQ(arr->get(2).Number(), 0);\n  EXPECT_EQ(arr->get(3).Number(), (int)CSSValuePattern::PERCENT);\n\n  output.clear();\n  impl = lepus::Value(\"bottom right\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(), 100);\n  EXPECT_EQ(arr->get(1).Number(), (int)CSSValuePattern::PERCENT);\n  EXPECT_EQ(arr->get(2).Number(), 100);\n  EXPECT_EQ(arr->get(3).Number(), (int)CSSValuePattern::PERCENT);\n\n  output.clear();\n  impl = lepus::Value(\"right bottom\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(), 100);\n  EXPECT_EQ(arr->get(1).Number(), (int)CSSValuePattern::PERCENT);\n  EXPECT_EQ(arr->get(2).Number(), 100);\n  EXPECT_EQ(arr->get(3).Number(), (int)CSSValuePattern::PERCENT);\n\n  output.clear();\n  impl = lepus::Value(\"center  center \");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(), 50);\n  EXPECT_EQ(arr->get(1).Number(), (int)CSSValuePattern::PERCENT);\n  EXPECT_EQ(arr->get(2).Number(), 50);\n  EXPECT_EQ(arr->get(3).Number(), (int)CSSValuePattern::PERCENT);\n}\n\nTEST(TransformOriginHandler, Compatibility) {\n  auto id = CSSPropertyID::kPropertyIDTransformOrigin;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  output.clear();\n  auto impl = lepus::Value(\"center, center\");\n  UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(output[id].IsArray());\n  auto arr = output[id].GetArray();\n  EXPECT_EQ(arr->get(0).Number(), 50);\n  EXPECT_EQ(arr->get(1).Number(), (int)CSSValuePattern::PERCENT);\n  EXPECT_EQ(arr->get(2).Number(), 50);\n  EXPECT_EQ(arr->get(3).Number(), (int)CSSValuePattern::PERCENT);\n\n  output.clear();\n  configs.enable_new_transform_handler = true;\n  impl = lepus::Value(\"center, center\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_FALSE(ret);\n  EXPECT_FALSE(output[id].IsArray());\n}\n\n}  // namespace test\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/transition_shorthand_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/transition_shorthand_handler.h\"\n\n#include <string>\n#include <vector>\n\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/css_string_parser.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TransitionShorthandHandler {\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  CSSStringParser parser = CSSStringParser::FromLepusString(input, configs);\n  bool single = std::strchr(parser.content(), ',') == nullptr;\n  lepus::Value arr[4];\n  if (!parser.ParseTransition(single, arr)) {\n    return false;\n  }\n  // [property, duration, delay, timing-function]\n  output.emplace_or_assign(\n      CSSPropertyID::kPropertyIDTransitionProperty, arr[0],\n      single ? CSSValuePattern::ENUM : CSSValuePattern::ARRAY);\n  output.emplace_or_assign(\n      CSSPropertyID::kPropertyIDTransitionDuration, arr[1],\n      single ? CSSValuePattern::NUMBER : CSSValuePattern::ARRAY);\n  output.emplace_or_assign(\n      CSSPropertyID::kPropertyIDTransitionDelay, arr[2],\n      single ? CSSValuePattern::NUMBER : CSSValuePattern::ARRAY);\n  output.emplace_or_assign(CSSPropertyID::kPropertyIDTransitionTimingFunction,\n                           arr[3].Array());\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDTransition] = &Handle; }\n\n}  // namespace TransitionShorthandHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/transition_shorthand_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_TRANSITION_SHORTHAND_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_TRANSITION_SHORTHAND_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace TransitionShorthandHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace TransitionShorthandHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_TRANSITION_SHORTHAND_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/parser/transition_shorthand_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/parser/transition_shorthand_handler.h\"\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace test {\n\nTEST(TransitionShorthandHandler, ToLonghand) {\n  auto id = CSSPropertyID::kPropertyIDTransition;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  output.clear();\n  auto impl = lepus::Value(\"width 2s ease-in 1ms\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  std::vector<std::pair<std::string, CSSPropertyID>> longhands = {\n      {\"width\", CSSPropertyID::kPropertyIDTransitionProperty},\n      {\"2s\", CSSPropertyID::kPropertyIDTransitionDuration},\n      {\"1ms\", CSSPropertyID::kPropertyIDTransitionDelay},\n      {\"ease-in\", CSSPropertyID::kPropertyIDTransitionTimingFunction},\n  };\n\n  for (const auto& longhand : longhands) {\n    StyleMap longhand_output;\n    EXPECT_TRUE(UnitHandler::Process(longhand.second,\n                                     lepus::Value(longhand.first),\n                                     longhand_output, configs));\n    EXPECT_EQ(output[longhand.second], longhand_output[longhand.second]);\n  }\n}\n\nTEST(TransitionShorthandHandler, Handler) {\n  auto id = CSSPropertyID::kPropertyIDTransition;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  output.clear();\n  auto impl = lepus::Value(\"width 2s ease-in 1ms\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  {\n    auto property = output[CSSPropertyID::kPropertyIDTransitionProperty];\n    EXPECT_TRUE(property.GetValue().IsNumber());\n    EXPECT_EQ(property.GetNumber(),\n              static_cast<int>(starlight::AnimationPropertyType::kWidth));\n    auto duration = output[CSSPropertyID::kPropertyIDTransitionDuration];\n    EXPECT_TRUE(duration.GetValue().IsNumber());\n    EXPECT_EQ(duration.GetNumber(), 2000);\n    auto delay = output[CSSPropertyID::kPropertyIDTransitionDelay];\n    EXPECT_TRUE(delay.GetValue().IsNumber());\n    EXPECT_EQ(delay.GetNumber(), 1);\n    auto timing_function =\n        output[CSSPropertyID::kPropertyIDTransitionTimingFunction];\n    EXPECT_TRUE(timing_function.GetValue().IsArray());\n    EXPECT_EQ(timing_function.GetArray()->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kEaseIn));\n  }\n\n  output.clear();\n  impl = lepus::Value(\"width 2s ease\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  {\n    auto property = output[CSSPropertyID::kPropertyIDTransitionProperty];\n    EXPECT_TRUE(property.GetValue().IsNumber());\n    EXPECT_EQ(property.GetNumber(),\n              static_cast<int>(starlight::AnimationPropertyType::kWidth));\n    auto duration = output[CSSPropertyID::kPropertyIDTransitionDuration];\n    EXPECT_TRUE(duration.GetValue().IsNumber());\n    EXPECT_EQ(duration.GetNumber(), 2000);\n    auto delay = output[CSSPropertyID::kPropertyIDTransitionDelay];\n    EXPECT_TRUE(delay.GetValue().IsNumber());\n    EXPECT_EQ(delay.GetNumber(), 0);\n    auto timing_function =\n        output[CSSPropertyID::kPropertyIDTransitionTimingFunction];\n    EXPECT_TRUE(timing_function.GetValue().IsArray());\n    EXPECT_EQ(timing_function.GetArray()->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kEaseInEaseOut));\n  }\n\n  output.clear();\n  impl = lepus::Value(\"width 2s\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  {\n    auto property = output[CSSPropertyID::kPropertyIDTransitionProperty];\n    EXPECT_TRUE(property.GetValue().IsNumber());\n    EXPECT_EQ(property.GetNumber(),\n              static_cast<int>(starlight::AnimationPropertyType::kWidth));\n    auto duration = output[CSSPropertyID::kPropertyIDTransitionDuration];\n    EXPECT_TRUE(duration.GetValue().IsNumber());\n    EXPECT_EQ(duration.GetNumber(), 2000);\n    auto delay = output[CSSPropertyID::kPropertyIDTransitionDelay];\n    EXPECT_TRUE(delay.GetValue().IsNumber());\n    EXPECT_EQ(delay.GetNumber(), 0);\n    auto timing_function =\n        output[CSSPropertyID::kPropertyIDTransitionTimingFunction];\n    EXPECT_TRUE(timing_function.GetValue().IsArray());\n    EXPECT_EQ(timing_function.GetArray()->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kLinear));\n  }\n\n  output.clear();\n  impl = lepus::Value(\"width ease-out\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  {\n    auto property = output[CSSPropertyID::kPropertyIDTransitionProperty];\n    EXPECT_TRUE(property.GetValue().IsNumber());\n    EXPECT_EQ(property.GetNumber(),\n              static_cast<int>(starlight::AnimationPropertyType::kWidth));\n    auto duration = output[CSSPropertyID::kPropertyIDTransitionDuration];\n    EXPECT_TRUE(duration.GetValue().IsNumber());\n    EXPECT_EQ(duration.GetNumber(), 0);\n    auto delay = output[CSSPropertyID::kPropertyIDTransitionDelay];\n    EXPECT_TRUE(delay.GetValue().IsNumber());\n    EXPECT_EQ(delay.GetNumber(), 0);\n    auto timing_function =\n        output[CSSPropertyID::kPropertyIDTransitionTimingFunction];\n    EXPECT_TRUE(timing_function.GetValue().IsArray());\n    EXPECT_EQ(timing_function.GetArray()->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kEaseOut));\n  }\n\n  impl = lepus::Value(\"width\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  {\n    auto property = output[CSSPropertyID::kPropertyIDTransitionProperty];\n    EXPECT_TRUE(property.GetValue().IsNumber());\n    EXPECT_EQ(property.GetNumber(),\n              static_cast<int>(starlight::AnimationPropertyType::kWidth));\n    auto duration = output[CSSPropertyID::kPropertyIDTransitionDuration];\n    EXPECT_TRUE(duration.GetValue().IsNumber());\n    EXPECT_EQ(duration.GetNumber(), 0);\n    auto delay = output[CSSPropertyID::kPropertyIDTransitionDelay];\n    EXPECT_TRUE(delay.GetValue().IsNumber());\n    EXPECT_EQ(delay.GetNumber(), 0);\n    auto timing_function =\n        output[CSSPropertyID::kPropertyIDTransitionTimingFunction];\n    EXPECT_TRUE(timing_function.GetValue().IsArray());\n    EXPECT_EQ(timing_function.GetArray()->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kLinear));\n  }\n\n  impl = lepus::Value(\"hello\");\n  ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  {\n    auto property = output[CSSPropertyID::kPropertyIDTransitionProperty];\n    EXPECT_TRUE(property.GetValue().IsNumber());\n    EXPECT_EQ(property.GetNumber(),\n              static_cast<int>(starlight::AnimationPropertyType::kNone));\n    auto duration = output[CSSPropertyID::kPropertyIDTransitionDuration];\n    EXPECT_TRUE(duration.GetValue().IsNumber());\n    EXPECT_EQ(duration.GetNumber(), 0);\n    auto delay = output[CSSPropertyID::kPropertyIDTransitionDelay];\n    EXPECT_TRUE(delay.GetValue().IsNumber());\n    EXPECT_EQ(delay.GetNumber(), 0);\n    auto timing_function =\n        output[CSSPropertyID::kPropertyIDTransitionTimingFunction];\n    EXPECT_TRUE(timing_function.GetValue().IsArray());\n    EXPECT_EQ(timing_function.GetArray()->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kLinear));\n  }\n}\n\nTEST(TransitionShorthandHandler, Negative) {\n  auto id = CSSPropertyID::kPropertyIDTransition;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  output.clear();\n  auto impl = lepus::Value(\"none -2s ease-in 1ms\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  {\n    auto property = output[CSSPropertyID::kPropertyIDTransitionProperty];\n    EXPECT_TRUE(property.GetValue().IsNumber());\n    EXPECT_EQ(property.GetNumber(),\n              static_cast<int>(starlight::AnimationPropertyType::kNone));\n    auto duration = output[CSSPropertyID::kPropertyIDTransitionDuration];\n    EXPECT_TRUE(duration.GetValue().IsNumber());\n    EXPECT_EQ(duration.GetNumber(), 1);\n    auto delay = output[CSSPropertyID::kPropertyIDTransitionDelay];\n    EXPECT_TRUE(delay.GetValue().IsNumber());\n    EXPECT_EQ(delay.GetNumber(), -2000);\n    auto timing_function =\n        output[CSSPropertyID::kPropertyIDTransitionTimingFunction];\n    EXPECT_TRUE(timing_function.GetValue().IsArray());\n    EXPECT_EQ(timing_function.GetArray()->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kEaseIn));\n  }\n}\n\nTEST(TransitionShorthandHandler, None) {\n  auto id = CSSPropertyID::kPropertyIDTransition;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  output.clear();\n  auto impl = lepus::Value(\"none 2s ease-in 1ms\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  {\n    auto property = output[CSSPropertyID::kPropertyIDTransitionProperty];\n    EXPECT_TRUE(property.GetValue().IsNumber());\n    EXPECT_EQ(property.GetNumber(),\n              static_cast<int>(starlight::AnimationPropertyType::kNone));\n    auto duration = output[CSSPropertyID::kPropertyIDTransitionDuration];\n    EXPECT_TRUE(duration.GetValue().IsNumber());\n    EXPECT_EQ(duration.GetNumber(), 2000);\n    auto delay = output[CSSPropertyID::kPropertyIDTransitionDelay];\n    EXPECT_TRUE(delay.GetValue().IsNumber());\n    EXPECT_EQ(delay.GetNumber(), 1);\n    auto timing_function =\n        output[CSSPropertyID::kPropertyIDTransitionTimingFunction];\n    EXPECT_TRUE(timing_function.GetValue().IsArray());\n    EXPECT_EQ(timing_function.GetArray()->get(0).Number(),\n              static_cast<int>(starlight::TimingFunctionType::kEaseIn));\n  }\n}\n\nTEST(TransitionShorthandHandler, Multi) {\n  auto id = CSSPropertyID::kPropertyIDTransition;\n  StyleMap output;\n  CSSParserConfigs configs;\n\n  auto impl = lepus::Value(\"width 2s ease-in 1ms, height 10s\");\n  auto ret = UnitHandler::Process(id, impl, output, configs);\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  // shorthand -> longhand\n  EXPECT_TRUE(output.find(id) == output.end());\n  auto property = output[CSSPropertyID::kPropertyIDTransitionProperty];\n  EXPECT_TRUE(property.GetValue().IsArray());\n  EXPECT_EQ(property.GetArray()->get(0).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kWidth));\n  EXPECT_EQ(property.GetArray()->get(1).Number(),\n            static_cast<int>(starlight::AnimationPropertyType::kHeight));\n  auto duration = output[CSSPropertyID::kPropertyIDTransitionDuration];\n  EXPECT_TRUE(duration.GetValue().IsArray());\n  EXPECT_EQ(duration.GetArray()->get(0).Number(), 2000);\n  EXPECT_EQ(duration.GetArray()->get(1).Number(), 10000);\n\n  auto delay = output[CSSPropertyID::kPropertyIDTransitionDelay];\n  EXPECT_TRUE(delay.GetValue().IsArray());\n  EXPECT_EQ(delay.GetArray()->get(0).Number(), 1);\n  EXPECT_EQ(delay.GetArray()->get(1).Number(), 0);\n\n  auto timing_function =\n      output[CSSPropertyID::kPropertyIDTransitionTimingFunction];\n  EXPECT_TRUE(timing_function.GetValue().IsArray());\n\n  EXPECT_EQ(timing_function.GetArray()->get(0).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kEaseIn));\n  EXPECT_EQ(timing_function.GetArray()->get(1).Number(),\n            static_cast<int>(starlight::TimingFunctionType::kLinear));\n}\n\nTEST(TransitionShorthandHandler, Invalid) {\n  auto id = CSSPropertyID::kPropertyIDTransition;\n  StyleMap output;\n  CSSParserConfigs configs;\n  configs.enable_css_strict_mode = true;\n\n  std::vector<std::string> cases = {\"width 2s ease-in 1ms, \",\n                                    \"width 2s ease-in 1ms 1ms\", \"none 1s, none\",\n                                    \"none, hello\", \"hello, world\"};\n  for (const auto& s : cases) {\n    EXPECT_FALSE(UnitHandler::Process(id, lepus::Value(s), output, configs));\n    EXPECT_TRUE(output.empty());\n  }\n}\n\n}  // namespace test\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/vertical_align_handler.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/parser/vertical_align_handler.h\"\n\n#include <utility>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/value/array.h\"\n#include \"core/renderer/css/parser/length_handler.h\"\n#include \"core/renderer/css/unit_handler.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace VerticalAlignHandler {\n\nusing starlight::VerticalAlignType;\n\nHANDLER_IMPL() {\n  CSS_HANDLER_FAIL_IF_NOT(input.IsString(), configs.enable_css_strict_mode,\n                          TYPE_MUST_BE, CSSProperty::GetPropertyNameCStr(key),\n                          STRING_TYPE)\n\n  const auto& str = input.StdString();\n  auto array = lepus::CArray::Create();\n  VerticalAlignType vertical_align_type = VerticalAlignType::kDefault;\n  tasm::CSSValue css_value;\n  if (str == \"baseline\") {\n    vertical_align_type = VerticalAlignType::kBaseline;\n  } else if (str == \"sub\") {\n    vertical_align_type = VerticalAlignType::kSub;\n  } else if (str == \"super\") {\n    vertical_align_type = VerticalAlignType::kSuper;\n  } else if (str == \"top\") {\n    vertical_align_type = VerticalAlignType::kTop;\n  } else if (str == \"text-top\") {\n    vertical_align_type = VerticalAlignType::kTextTop;\n  } else if (str == \"middle\") {\n    vertical_align_type = VerticalAlignType::kMiddle;\n  } else if (str == \"bottom\") {\n    vertical_align_type = VerticalAlignType::kBottom;\n  } else if (str == \"text-bottom\") {\n    vertical_align_type = VerticalAlignType::kTextBottom;\n  } else if (str == \"center\") {\n    vertical_align_type = VerticalAlignType::kCenter;\n  } else {\n    lepus::Value lepus_value(str);\n    if (!lynx::tasm::LengthHandler::Process(lepus_value, css_value, configs)) {\n      return false;\n    }\n    if (str[str.length() - 1] == '%') {\n      vertical_align_type = VerticalAlignType::kPercent;\n    } else {\n      vertical_align_type = VerticalAlignType::kLength;\n    }\n  }\n\n  array->emplace_back(static_cast<int>(vertical_align_type));\n  array->emplace_back(static_cast<int>(CSSValuePattern::ENUM));\n  array->emplace_back(css_value.GetValue());\n  array->emplace_back(static_cast<int32_t>(css_value.GetPattern()));\n  output.emplace_or_assign(key, std::move(array));\n  return true;\n}\n\nHANDLER_REGISTER_IMPL() { array[kPropertyIDVerticalAlign] = &Handle; }\n\n}  // namespace VerticalAlignHandler\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/parser/vertical_align_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_PARSER_VERTICAL_ALIGN_HANDLER_H_\n#define CORE_RENDERER_CSS_PARSER_VERTICAL_ALIGN_HANDLER_H_\n\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\nnamespace VerticalAlignHandler {\n\nHANDLER_REGISTER_DECLARE();\n\n}  // namespace VerticalAlignHandler\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_PARSER_VERTICAL_ALIGN_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/select_element_token.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/select_element_token.h\"\n\n#include <algorithm>\n#include <iterator>\nnamespace lynx {\nnamespace tasm {\n\nnamespace {\ninline bool isblank(char c) { return c == ' ' || c == '\\t'; }\n}  // namespace\n\n/**\n * @returns a std::pair.\n * pair.first is a vector of selectors parsed.\n * pair.second indicates if the selector is legal.\n */\nstd::pair<std::vector<SelectElementToken>, bool>\nSelectElementToken::ParseCssSelector(const std::string& selector_string) {\n  std::vector<SelectElementToken> res;\n  for (auto begin = selector_string.cbegin();\n       begin != selector_string.cend();) {\n    auto pair = ParseSingleCssSelector(begin, selector_string.cend());\n    if (!pair.second) {\n      return {std::vector<SelectElementToken>(), false};\n    }\n    res.push_back(pair.first);\n    // skip spaces\n    begin = std::find_if_not(begin, selector_string.cend(), &isblank);\n  }\n  if (!res.empty() && res.back().combinator_to_next != Combinator::LAST) {\n    return {std::vector<SelectElementToken>(), false};\n  }\n  return {res, true};\n}\n\n// parse a single selector. move begin iterator to the beginning of next single\n// selector.\n//\n// current supported css selectors:\n// id: \"#id\"\n// class: \".class\"\n// tag: \"tag\"\n// attribute: \"[attribute=value]\"\n// child: \"#a>#b\"\n// descendant: \"#a #b\"\n// descendant_cross_components: \"#a>>>#b\"\nstd::pair<SelectElementToken, bool> SelectElementToken::ParseSingleCssSelector(\n    std::string::const_iterator& begin,\n    const std::string::const_iterator& end) {\n  // skip spaces\n  begin = std::find_if_not(begin, end, &isblank);\n  if (begin == end) {\n    return {SelectElementToken(\"\", Type::CSS_SELECTOR, Combinator::LAST),\n            false};\n  }\n\n  // check first character to filter not support selectors.\n  if (*begin != '#'     // \"#id\"\n      && *begin != '.'  // \".class\"\n      && *begin != '['  // \"[attribute=value]\"\n      && !std::isalpha(static_cast<unsigned int>(*begin))  // \"tag\"\n  ) {\n    return {SelectElementToken(\"\", Type::CSS_SELECTOR, Combinator::LAST),\n            false};\n  }\n\n  // read selector string until space or '>' is met.\n  std::string selector_string;\n  auto combinator_begin =\n      std::find_if(begin, end, [](auto c) { return isblank(c) || c == '>'; });\n  std::copy(begin, combinator_begin, std::back_inserter(selector_string));\n\n  // check selector string\n  bool empty = selector_string.empty();\n  bool character_only =\n      selector_string.size() == 1 && !std::isalpha(selector_string.front());\n  if (empty || character_only) {\n    return {SelectElementToken(\"\", Type::CSS_SELECTOR, Combinator::LAST),\n            false};\n  }\n\n  // read combinator. only supports selectors like \"a b\" , \"a>b\" and \"a>>>b\".\n  begin = std::find_if_not(combinator_begin, end, &isblank);\n  if (begin == end) {\n    return {SelectElementToken(selector_string, Type::CSS_SELECTOR,\n                               Combinator::LAST),\n            true};\n  }\n  if (*begin == '>') {\n    std::advance(begin, 1);\n    if (std::distance(begin, end) >= 2 && *begin == '>' &&\n        *std::next(begin) == '>') {\n      std::advance(begin, 2);\n      return {SelectElementToken(selector_string, Type::CSS_SELECTOR,\n                                 Combinator::DESCENDANT_ACROSS_COMPONENTS),\n              true};\n    } else {\n      return {SelectElementToken(selector_string, Type::CSS_SELECTOR,\n                                 Combinator::CHILD),\n              true};\n    }\n  } else {\n    // is descendant combinator\n    return {SelectElementToken(selector_string, Type::CSS_SELECTOR,\n                               Combinator::DESCENDANT),\n            true};\n  }\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/select_element_token.h",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_SELECT_ELEMENT_TOKEN_H_\n#define CORE_RENDERER_CSS_SELECT_ELEMENT_TOKEN_H_\n\n#include <string>\n#include <utility>\n#include <vector>\n\nnamespace lynx {\nnamespace tasm {\nclass SelectElementToken {\n public:\n  enum class Combinator {\n    LAST = 0,                          // no next selector\n    CHILD = 1,                         // \"a > b\"\n    DESCENDANT = 2,                    // \"a b\"\n    DESCENDANT_ACROSS_COMPONENTS = 3,  // \"a >>> b\"\n  };\n\n  enum class Type {\n    CSS_SELECTOR = 0,\n    REF_ID = 1,\n    ELEMENT_ID = 2,\n  };\n\n  // does not support ','\n  static std::pair<std::vector<SelectElementToken>, bool> ParseCssSelector(\n      const std::string &selector_string);\n\n  SelectElementToken(const std::string &selector_string, Type type,\n                     const Combinator &combinator_to_next)\n      : selector_string(selector_string),\n        type(type),\n        combinator_to_next(combinator_to_next) {}\n\n  SelectElementToken(const SelectElementToken &other) = default;\n  SelectElementToken &operator=(const SelectElementToken &other) = default;\n\n  SelectElementToken(SelectElementToken &&other) = default;\n  SelectElementToken &operator=(SelectElementToken &&other) = default;\n\n  bool OnlyCurrentComponent() const {\n    return combinator_to_next != Combinator::DESCENDANT_ACROSS_COMPONENTS;\n  }\n\n  bool NoDescendant() const {\n    return combinator_to_next == Combinator::LAST ||\n           combinator_to_next == Combinator::CHILD;\n  }\n\n  std::string selector_string;\n  Type type;\n  Combinator combinator_to_next;\n\n private:\n  static std::pair<SelectElementToken, bool> ParseSingleCssSelector(\n      std::string::const_iterator &begin,\n      const std::string::const_iterator &end);\n};\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_SELECT_ELEMENT_TOKEN_H_\n"
  },
  {
    "path": "core/renderer/css/select_element_token_unittest.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/select_element_token.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nbool operator==(const SelectElementToken &l, const SelectElementToken &r) {\n  return l.selector_string == r.selector_string &&\n         l.combinator_to_next == r.combinator_to_next && l.type == r.type;\n}\nnamespace testing {\n\nclass SelectElementTokenTest : public ::testing::Test {\n public:\n  SelectElementTokenTest() {}\n  ~SelectElementTokenTest() override {}\n\n  void SetUp() override {}\n};\n\nTEST_F(SelectElementTokenTest, ParseEmpty) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\"\");\n  auto expected = std::vector<SelectElementToken>{};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseId) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\" #id  \");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\"#id\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST)};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseClass) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\" .class1.class2  \");\n  auto expected = std::vector<SelectElementToken>{SelectElementToken(\n      \".class1.class2\", SelectElementToken::Type::CSS_SELECTOR,\n      SelectElementToken::Combinator::LAST)};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseChild) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\" #id>.class1 \");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\"#id\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::CHILD),\n      SelectElementToken(\".class1\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST)};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseDescendant) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\" #id .class1 \");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\"#id\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::DESCENDANT),\n      SelectElementToken(\".class1\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST)};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseTag) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\"tag .class1 \");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\"tag\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::DESCENDANT),\n      SelectElementToken(\".class1\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST)};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseTag2) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\" .class1 tag\");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\".class1\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::DESCENDANT),\n      SelectElementToken(\"tag\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST)};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseTagAndClass) {\n  auto actual_pair =\n      SelectElementToken::ParseCssSelector(\"tag.class3 .class1 \");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\"tag.class3\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::DESCENDANT),\n      SelectElementToken(\".class1\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST)};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseDescendantAC) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\" #id >>>.class1 \");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\n          \"#id\", SelectElementToken::Type::CSS_SELECTOR,\n          SelectElementToken::Combinator::DESCENDANT_ACROSS_COMPONENTS),\n      SelectElementToken(\".class1\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST)};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseAttr1) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\" [attr]  \");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\"[attr]\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST),\n  };\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseAttr2) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\" [attr=value]  \");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\"[attr=value]\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST),\n  };\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, ParseMixed) {\n  auto actual_pair =\n      SelectElementToken::ParseCssSelector(\"#id >>>.class1  #id2  >.class2  \");\n  auto expected = std::vector<SelectElementToken>{\n      SelectElementToken(\n          \"#id\", SelectElementToken::Type::CSS_SELECTOR,\n          SelectElementToken::Combinator::DESCENDANT_ACROSS_COMPONENTS),\n      SelectElementToken(\".class1\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::DESCENDANT),\n      SelectElementToken(\"#id2\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::CHILD),\n      SelectElementToken(\".class2\", SelectElementToken::Type::CSS_SELECTOR,\n                         SelectElementToken::Combinator::LAST)};\n  EXPECT_EQ(actual_pair.first, expected);\n  EXPECT_EQ(actual_pair.second, true);\n}\n\nTEST_F(SelectElementTokenTest, Error) {\n  auto actual_pair = SelectElementToken::ParseCssSelector(\"#\");\n  EXPECT_EQ(actual_pair.second, false);\n\n  actual_pair = SelectElementToken::ParseCssSelector(\"# id\");\n  EXPECT_EQ(actual_pair.second, false);\n\n  actual_pair = SelectElementToken::ParseCssSelector(\" #id >> .class \");\n  EXPECT_EQ(actual_pair.second, false);\n\n  actual_pair = SelectElementToken::ParseCssSelector(\" #id >>>> .class \");\n  EXPECT_EQ(actual_pair.second, false);\n\n  actual_pair = SelectElementToken::ParseCssSelector(\" #id >>>\");\n  EXPECT_EQ(actual_pair.second, false);\n\n  actual_pair = SelectElementToken::ParseCssSelector(\" #id >\");\n  EXPECT_EQ(actual_pair.second, false);\n\n  actual_pair = SelectElementToken::ParseCssSelector(\" > #id\");\n  EXPECT_EQ(actual_pair.second, false);\n\n  actual_pair = SelectElementToken::ParseCssSelector(\" $id\");\n  EXPECT_EQ(actual_pair.second, false);\n}\n\n}  // namespace testing\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/shared_css_fragment.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#include \"core/renderer/css/shared_css_fragment.h\"\n\n#include \"base/trace/native/trace_event.h\"\n#include \"core/renderer/css/css_style_sheet_manager.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nSharedCSSFragment::~SharedCSSFragment() = default;\n\nSharedCSSFragment::SharedCSSFragment(int32_t id,\n                                     const std::vector<int32_t>& dependent_ids,\n                                     CSSParserTokenMap css,\n                                     CSSKeyframesTokenMap keyframes,\n                                     CSSFontFaceRuleMap fontfaces,\n                                     CSSStyleSheetManager* manager)\n    : CSSFragment(std::move(keyframes), std::move(fontfaces)),\n      id_(id),\n      is_baked_(false),\n      dependent_ids_(dependent_ids),\n      css_(std::move(css)),\n      manager_(manager) {\n  if (manager_) {\n    enable_css_lazy_import_ = manager_->GetEnableCSSLazyImport();\n  }\n}\n\nbool SharedCSSFragment::HasCSSStyle() {\n  if (has_css_style_.has_value()) {\n    return has_css_style_.value_or(false);\n  }\n  if (!css_.empty()) {\n    has_css_style_ = true;\n    return true;\n  }\n  if (enable_css_lazy_import_) {\n    auto dependent_fragment_ids = dependent_ids();\n    for (auto id = dependent_fragment_ids.rbegin();\n         id != dependent_fragment_ids.rend(); ++id) {\n      auto dependent_fragment = manager_->GetCSSStyleSheet(*id);\n      if (dependent_fragment->HasCSSStyle()) {\n        has_css_style_ = true;\n        return true;\n      }\n    }\n    has_css_style_ = false;\n    return false;\n  } else {\n    has_css_style_ = !css_.empty();\n    return !css_.empty();\n  }\n}\n\nCSSParseToken* SharedCSSFragment::GetCSSStyle(const std::string& key) {\n  auto it = css_.find(key);\n  if (it != css_.end()) {\n    return it->second.get();\n  }\n  if (enable_css_lazy_import_) {\n    auto dependent_fragment_ids = dependent_ids();\n    for (auto id = dependent_fragment_ids.rbegin();\n         id != dependent_fragment_ids.rend(); ++id) {\n      auto dependent_fragment = manager_->GetCSSStyleSheet(*id);\n      auto result = dependent_fragment->GetCSSStyle(key);\n      if (result != nullptr) {\n        return result;\n      }\n    }\n  }\n  return nullptr;\n}\n\nfml::RefPtr<CSSParseToken> SharedCSSFragment::GetSharedCSSStyle(\n    const std::string& key) {\n  auto it = css_.find(key);\n  if (it != css_.end()) {\n    return it->second;\n  }\n  if (enable_css_lazy_import_) {\n    auto dependent_fragment_ids = dependent_ids();\n    for (auto id = dependent_fragment_ids.rbegin();\n         id != dependent_fragment_ids.rend(); ++id) {\n      auto dependent_fragment = manager_->GetCSSStyleSheet(*id);\n      auto result = dependent_fragment->GetSharedCSSStyle(key);\n      if (result != nullptr) {\n        return result;\n      }\n    }\n  }\n  return nullptr;\n}\n\n#define SHARED_CSS_FRAGMENT_GET_STYLE(field, name)                             \\\n  CSSParseToken* SharedCSSFragment::Get##name##Style(const std::string& key) { \\\n    auto it = field.find(key);                                                 \\\n    if (it != field.end()) {                                                   \\\n      return it->second.get();                                                 \\\n    }                                                                          \\\n    return nullptr;                                                            \\\n  }\n\nSHARED_CSS_FRAGMENT_GET_STYLE(pseudo_map_, Pseudo)\nSHARED_CSS_FRAGMENT_GET_STYLE(cascade_map_, Cascade)\nSHARED_CSS_FRAGMENT_GET_STYLE(id_map_, Id)\nSHARED_CSS_FRAGMENT_GET_STYLE(tag_map_, Tag)\nSHARED_CSS_FRAGMENT_GET_STYLE(universal_map_, Universal)\n#undef SHARED_CSS_FRAGMENT_GET_STYLE\n\nvoid SharedCSSFragment::ImportOtherFragment(const SharedCSSFragment* fragment) {\n  if (fragment == nullptr) return;\n  if (fragment->HasTouchPseudoToken()) {\n    // When ImportOtherFragment, if the previous fragment contains a touch\n    // pseudo, mark the current fragment also has a touch pseudo. So that the\n    // platform layer can judge whether to execute the pseudo related functions\n    // according to whether it has a touch state pseudo-class\n    MarkHasTouchPseudoToken();\n  }\n  css_.reserve(fragment->css_.size());\n  for (auto& css : fragment->css_) {\n    if (!enable_class_merge_) {\n      if (!enable_css_lazy_import_) {\n        css_[css.first] = css.second;\n      }\n      continue;\n    }\n    const auto& selector = css.first;\n    if (css_.find(selector) != css_.end()) {\n      auto& depend_attribute = css.second->GetAttributes();\n      StyleMap cur_attribute = css_[selector]->GetAttributes();\n      for (auto& it : depend_attribute) {\n        if (cur_attribute.find(it.first) == cur_attribute.end()) {\n          cur_attribute[it.first] = std::move(it.second);\n        }\n      }\n      css_[selector]->SetAttributes(std::move(cur_attribute));\n    } else {\n      css_[css.first] = css.second;\n    }\n  }\n  for (auto& pseudo : fragment->pseudo_map_) {\n    pseudo_map_[pseudo.first] = pseudo.second;\n  }\n  for (auto& child_pseudo : fragment->child_pseudo_map_) {\n    child_pseudo_map_[child_pseudo.first] = child_pseudo.second;\n  }\n  for (auto& cascade : fragment->cascade_map_) {\n    cascade_map_[cascade.first] = cascade.second;\n  }\n  for (auto& id : fragment->id_map_) {\n    id_map_[id.first] = id.second;\n  }\n  for (auto& tag : fragment->tag_map_) {\n    tag_map_[tag.first] = tag.second;\n  }\n  for (auto& universal : fragment->universal_map_) {\n    universal_map_[universal.first] = universal.second;\n  }\n  keyframes_.reserve(fragment->keyframes_.size());\n  for (auto& frame : fragment->keyframes_) {\n    keyframes_[frame.first] = frame.second;\n  }\n  fontfaces_.reserve(fragment->fontfaces_.size());\n  for (auto& face : fragment->fontfaces_) {\n    fontfaces_[face.first] = face.second;\n  }\n\n  if (rule_set_ && fragment->rule_set_) {\n    rule_set_->Merge(*fragment->rule_set_);\n    if (rule_invalidation_set_ && fragment->rule_invalidation_set_)\n      rule_invalidation_set_->Merge(*fragment->rule_invalidation_set_);\n  }\n}\n\nvoid SharedCSSFragment::InitPseudoNotStyle() {\n  if (pseudo_map_.empty()) {\n    return;\n  }\n  if (pseudo_not_style_) {\n    return;\n  }\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, SHARED_FRAGMENT_INIT_PSEUDO_NOT_STYLE);\n  pseudo_not_style_ = PseudoNotStyle();\n  PseudoClassStyleMap global_pseudo_not_tag, global_pseudo_not_class,\n      global_pseudo_not_id;\n\n  for (auto& it : pseudo_map_) {\n    const std::string& key_name = it.first;\n\n    // mark if has pseudo style\n    if (!it.second || !it.second->IsPseudoStyleToken()) {\n      // if :not is not used, do not run the code bellow\n      continue;\n    }\n\n    size_t pseudo_not_loc = key_name.find(\":not(\");\n    if (pseudo_not_loc != std::string::npos) {\n      has_pseudo_not_style_ = true;\n      size_t loc = key_name.find_first_of(\"(\");\n      std::string scope_for_pseudo_not =\n          key_name.substr(loc + 1, key_name.size() - loc - 2);\n      std::string selector_key = key_name.substr(0, pseudo_not_loc);\n\n      PseudoNotContent content;\n      content.selector_key = selector_key;\n      content.scope = scope_for_pseudo_not;\n      std::string selector_key_type = selector_key.substr(0, 1);\n      std::string scope_value_type = scope_for_pseudo_not.substr(0, 1);\n      bool is_global_pseudo_not_css = selector_key.compare(\"\") == 0;\n      if (selector_key.compare(scope_for_pseudo_not) == 0) {\n        continue;\n      }\n\n      if (scope_value_type.compare(\".\") == 0) {\n        content.scope_type = CSSSheet::CLASS_SELECT;\n        if (is_global_pseudo_not_css) {\n          global_pseudo_not_class.insert({key_name, content});\n        }\n      } else if (scope_value_type.compare(\"#\") == 0) {\n        content.scope_type = CSSSheet::ID_SELECT;\n        if (is_global_pseudo_not_css) {\n          global_pseudo_not_id.insert({key_name, content});\n        }\n      } else {\n        content.scope_type = CSSSheet::NAME_SELECT;\n        if (is_global_pseudo_not_css) {\n          global_pseudo_not_tag.insert({key_name, content});\n        }\n      }\n\n      if (is_global_pseudo_not_css) {\n        pseudo_not_style_->pseudo_not_global_map.insert(\n            {CSSSheet::NAME_SELECT, global_pseudo_not_tag});\n        pseudo_not_style_->pseudo_not_global_map.insert(\n            {CSSSheet::CLASS_SELECT, global_pseudo_not_class});\n        pseudo_not_style_->pseudo_not_global_map.insert(\n            {CSSSheet::ID_SELECT, global_pseudo_not_id});\n      } else if (selector_key_type.compare(\".\") == 0) {\n        pseudo_not_style_->pseudo_not_for_class.insert({key_name, content});\n      } else if (selector_key_type.compare(\"#\") == 0) {\n        pseudo_not_style_->pseudo_not_for_id.insert({key_name, content});\n      } else {\n        pseudo_not_style_->pseudo_not_for_tag.insert({key_name, content});\n      }\n    }\n  }\n}\n\nvoid SharedCSSFragment::FindSpecificMapAndAdd(\n    const std::string& key, const fml::RefPtr<CSSParseToken>& parse_token) {\n  if (parse_token->IsCascadeSelectorStyleToken()) {\n    cascade_map_.emplace(key, parse_token);\n  }\n  int type = parse_token->GetStyleTokenType();\n  if (type > CSSSheet::NAME_SELECT && type != CSSSheet::ALL_SELECT) {\n    pseudo_map_.emplace(key, parse_token);\n    if ((type & CSSSheet::FIRST_CHILD_SELECT) ||\n        (type & CSSSheet::LAST_CHILD_SELECT)) {\n      child_pseudo_map_.emplace(key, parse_token);\n    }\n  } else if (type == CSSSheet::ID_SELECT) {\n    id_map_.emplace(key, parse_token);\n  } else if (type == CSSSheet::NAME_SELECT) {\n    tag_map_.emplace(key, parse_token);\n  } else if (type == CSSSheet::ALL_SELECT) {\n    universal_map_.emplace(key, parse_token);\n  }\n}\n\nvoid SharedCSSFragment::AddStyleRule(\n    std::unique_ptr<css::LynxCSSSelector[]> selector_arr,\n    fml::RefPtr<CSSParseToken> parse_token) {\n  // We know the pointer is not empty\n  rule_set_->AddStyleRule(fml::MakeRefCounted<css::StyleRule>(\n      std::move(selector_arr), std::move(parse_token)));\n}\n\nvoid SharedCSSFragment::CollectInvalidationSetsForId(\n    css::InvalidationLists& lists, const std::string& id) {\n  if (rule_invalidation_set_) {\n    rule_invalidation_set_->CollectId(lists, id);\n  }\n}\n\nvoid SharedCSSFragment::CollectInvalidationSetsForClass(\n    css::InvalidationLists& lists, const std::string& class_name) {\n  if (rule_invalidation_set_) {\n    rule_invalidation_set_->CollectClass(lists, class_name);\n  }\n}\n\nvoid SharedCSSFragment::CollectInvalidationSetsForPseudoClass(\n    css::InvalidationLists& lists, css::LynxCSSSelector::PseudoType pseudo) {\n  if (rule_invalidation_set_) {\n    rule_invalidation_set_->CollectPseudoClass(lists, pseudo);\n  }\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/shared_css_fragment.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_SHARED_CSS_FRAGMENT_H_\n#define CORE_RENDERER_CSS_SHARED_CSS_FRAGMENT_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n#include <vector>\n\n#include \"core/renderer/css/css_fragment.h\"\n#include \"core/renderer/css/ng/invalidation/rule_invalidation_set.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass CSSStyleSheetManager;\n// A global CSSFragment implementation that gets registered in\n// CSSStyleSheetManager and shallow-copied into components referencing it.\nclass SharedCSSFragment : public CSSFragment {\n public:\n  SharedCSSFragment(int32_t id, const std::vector<int32_t>& dependent_ids,\n                    CSSParserTokenMap css, CSSKeyframesTokenMap keyframes,\n                    CSSFontFaceRuleMap fontfaces,\n                    CSSStyleSheetManager* manager = nullptr);\n\n  explicit SharedCSSFragment(int32_t id, CSSStyleSheetManager* manager)\n      : SharedCSSFragment(id, {}, {}, {}, {}, manager) {}\n\n  explicit SharedCSSFragment(CSSStyleSheetManager* manager)\n      : SharedCSSFragment(-1, manager) {}\n\n  explicit SharedCSSFragment(int32_t id) : SharedCSSFragment(id, nullptr) {}\n\n  SharedCSSFragment() : SharedCSSFragment(-1, nullptr) {}\n\n  ~SharedCSSFragment() override;\n\n  inline int32_t id() const { return id_; }\n  inline bool is_baked() { return is_baked_; }\n  inline bool enable_class_merge() { return enable_class_merge_; }\n  inline bool enable_css_selector() override { return enable_css_selector_; }\n  inline bool enable_css_invalidation() override {\n    return enable_css_invalidation_;\n  }\n  inline const std::vector<int32_t>& dependent_ids() { return dependent_ids_; }\n  const CSSParserTokenMap& css() override { return css_; }\n  css::RuleSet* rule_set() override { return rule_set_.get(); }\n\n  const CSSParserTokenMap& pseudo_map() override { return pseudo_map_; }\n  const CSSParserTokenMap& child_pseudo_map() override {\n    return child_pseudo_map_;\n  }\n  const CSSParserTokenMap& cascade_map() override { return cascade_map_; }\n  const PseudoNotStyle& pseudo_not_style() override {\n    return *pseudo_not_style_;\n  }\n\n  void MarkBaked() { is_baked_ = true; }\n  void ImportOtherFragment(const SharedCSSFragment* fragment);\n  void SetEnableClassMerge(bool class_merge) {\n    enable_class_merge_ = class_merge;\n  }\n\n  // Enabled in CSS selector only\n  void SetEnableCSSInvalidation() { enable_css_invalidation_ = true; }\n\n  // Invoked after SetEnableCSSInvalidation\n  void SetEnableCSSSelector() {\n    enable_css_selector_ = true;\n    if (rule_set_) {\n      return;\n    }\n    if (enable_css_invalidation_) {\n      rule_invalidation_set_ = std::make_unique<css::RuleInvalidationSet>();\n    }\n    rule_set_ = std::make_unique<css::RuleSet>(this);\n  }\n\n  css::RuleInvalidationSet* GetRuleInvalidationSet() {\n    return rule_invalidation_set_.get();\n  }\n\n  void CollectInvalidationSetsForId(css::InvalidationLists& lists,\n                                    const std::string& id) override;\n\n  void CollectInvalidationSetsForClass(css::InvalidationLists& lists,\n                                       const std::string& class_name) override;\n\n  void CollectInvalidationSetsForPseudoClass(\n      css::InvalidationLists& lists,\n      css::LynxCSSSelector::PseudoType pseudo) override;\n  fml::RefPtr<CSSParseToken> GetSharedCSSStyle(const std::string& key) override;\n  bool HasCSSStyle() override;\n  CSSParseToken* GetCSSStyle(const std::string& key) override;\n  CSSParseToken* GetPseudoStyle(const std::string& key) override;\n  CSSParseToken* GetCascadeStyle(const std::string& key) override;\n  CSSParseToken* GetIdStyle(const std::string& key) override;\n  CSSParseToken* GetTagStyle(const std::string& key) override;\n  CSSParseToken* GetUniversalStyle(const std::string& key) override;\n\n  bool HasPseudoNotStyle() override { return has_pseudo_not_style_; }\n  void InitPseudoNotStyle() override;\n  void FindSpecificMapAndAdd(const std::string& key,\n                             const fml::RefPtr<CSSParseToken>& parse_token);\n  void AddStyleRule(std::unique_ptr<css::LynxCSSSelector[]> selector_arr,\n                    fml::RefPtr<CSSParseToken> parse_token);\n  bool HasIdSelector() override { return !id_map_.empty(); }\n\n protected:\n  friend class TemplateBinaryReader;\n  friend class TemplateBinaryReaderSSR;\n  friend class LynxBinaryBaseCSSReader;\n\n  int32_t id_;\n  bool is_baked_;\n  bool enable_class_merge_ = false;\n  bool enable_css_selector_ = false;\n  bool enable_css_invalidation_ = false;\n  bool has_pseudo_not_style_ = false;\n\n  std::vector<int32_t> dependent_ids_;\n  CSSParserTokenMap css_;\n\n  std::optional<PseudoNotStyle> pseudo_not_style_;\n  // Save owner.\n  CSSStyleSheetManager* manager_ = nullptr;\n  // Structures for quickly rejecting the selector\n  CSSParserTokenMap pseudo_map_;\n  CSSParserTokenMap child_pseudo_map_;\n  CSSParserTokenMap cascade_map_;\n  CSSParserTokenMap id_map_;\n  CSSParserTokenMap tag_map_;\n  CSSParserTokenMap universal_map_;\n  std::unique_ptr<css::RuleSet> rule_set_;\n  // Initialize the RuleInvalidationSet only when the CSS invalidation is\n  // enabled\n  std::unique_ptr<css::RuleInvalidationSet> rule_invalidation_set_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_SHARED_CSS_FRAGMENT_H_\n"
  },
  {
    "path": "core/renderer/css/shared_css_fragment_unittest.cc",
    "content": "// Copyright 2021 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/shared_css_fragment.h\"\n\n#include \"base/include/value/table.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nusing namespace tasm;\nnamespace css {\nnamespace testing {\nTEST(SharedCSSFragment, Handler) {\n  // create the index fragment with four css classes\n  StyleMap indexAttributes;\n  CSSParserConfigs configs;\n  auto indexTokens = fml::MakeRefCounted<CSSParseToken>(configs);\n  CSSParserTokenMap indexTokensMap;\n  // create class text-1\n  auto id = CSSPropertyID::kPropertyIDFontSize;\n  auto impl = lepus::Value(\"18px\");\n  bool ret = false;\n  ret = UnitHandler::Process(id, impl, indexAttributes, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDTextAlign;\n  impl = lepus::Value(\"center\");\n  ret = UnitHandler::Process(id, impl, indexAttributes, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDColor;\n  impl = lepus::Value(\"red\");\n  ret = UnitHandler::Process(id, impl, indexAttributes, configs);\n  EXPECT_TRUE(ret);\n  indexTokens.get()->SetAttributes(std::move(indexAttributes));\n  indexTokensMap.insert(\n      std::make_pair(std::string(\".text-1\"), std::move(indexTokens)));\n\n  // create class text-3\n  StyleMap indexAttributes1;\n  auto indexTokens1 = fml::MakeRefCounted<CSSParseToken>(configs);\n  id = CSSPropertyID::kPropertyIDFontSize;\n  impl = lepus::Value(\"18px\");\n  ret = UnitHandler::Process(id, impl, indexAttributes1, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDTextAlign;\n  impl = lepus::Value(\"center\");\n  ret = UnitHandler::Process(id, impl, indexAttributes1, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDColor;\n  impl = lepus::Value(\"blueviolet\");\n  ret = UnitHandler::Process(id, impl, indexAttributes1, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDFontWeight;\n  impl = lepus::Value(\"bold\");\n  ret = UnitHandler::Process(id, impl, indexAttributes1, configs);\n  EXPECT_TRUE(ret);\n  indexTokens1.get()->SetAttributes(std::move(indexAttributes1));\n  indexTokensMap.insert(\n      std::make_pair(std::string(\".text-3\"), std::move(indexTokens1)));\n  // create class text-4\n  StyleMap indexAttributes2;\n  auto indexTokens2 = fml::MakeRefCounted<CSSParseToken>(configs);\n  id = CSSPropertyID::kPropertyIDBackgroundColor;\n  impl = lepus::Value(\"blue\");\n  ret = UnitHandler::Process(id, impl, indexAttributes2, configs);\n  EXPECT_TRUE(ret);\n  indexTokens2.get()->SetAttributes(std::move(indexAttributes2));\n  indexTokensMap.insert(\n      std::make_pair(std::string(\".text-4\"), std::move(indexTokens2)));\n  // create class text-5\n  StyleMap indexAttributes3;\n  auto indexTokens3 = fml::MakeRefCounted<CSSParseToken>(configs);\n  id = CSSPropertyID::kPropertyIDBackgroundColor;\n  impl = lepus::Value(\"lightblue\");\n  ret = UnitHandler::Process(id, impl, indexAttributes3, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDFontStyle;\n  impl = lepus::Value(\"italic\");\n  ret = UnitHandler::Process(id, impl, indexAttributes3, configs);\n  EXPECT_TRUE(ret);\n  indexTokens3.get()->SetAttributes(std::move(indexAttributes3));\n  indexTokensMap.insert(\n      std::make_pair(std::string(\".text-5\"), std::move(indexTokens3)));\n\n  const std::vector<int32_t> dependent_ids;\n  CSSKeyframesTokenMap keyframes;\n  CSSFontFaceRuleMap fontfaces;\n  SharedCSSFragment indexFragment(1, dependent_ids, indexTokensMap, keyframes,\n                                  fontfaces);\n  indexFragment.SetEnableClassMerge(true);\n\n  // create import fragment\n\n  CSSParserTokenMap importTokensMap;\n  // create class text-2\n  StyleMap importAttributes1;\n  auto importTokens1 = fml::MakeRefCounted<CSSParseToken>(configs);\n  id = CSSPropertyID::kPropertyIDFontSize;\n  impl = lepus::Value(\"20px\");\n  ret = UnitHandler::Process(id, impl, importAttributes1, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDTextAlign;\n  impl = lepus::Value(\"center\");\n  ret = UnitHandler::Process(id, impl, importAttributes1, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDColor;\n  impl = lepus::Value(\"green\");\n  ret = UnitHandler::Process(id, impl, importAttributes1, configs);\n  EXPECT_TRUE(ret);\n  importTokens1.get()->SetAttributes(std::move(importAttributes1));\n  importTokensMap.insert(\n      std::make_pair(std::string(\".text-2\"), std::move(importTokens1)));\n  // create class text-3\n  StyleMap importAttributes2;\n  auto importTokens2 = fml::MakeRefCounted<CSSParseToken>(configs);\n  id = CSSPropertyID::kPropertyIDFontWeight;\n  impl = lepus::Value(\"100\");\n  ret = UnitHandler::Process(id, impl, importAttributes2, configs);\n  EXPECT_TRUE(ret);\n  importTokens2.get()->SetAttributes(std::move(importAttributes2));\n  importTokensMap.insert(\n      std::make_pair(std::string(\".text-3\"), std::move(importTokens2)));\n  // create class text-4\n  StyleMap importAttributes3;\n  auto importTokens3 = fml::MakeRefCounted<CSSParseToken>(configs);\n  id = CSSPropertyID::kPropertyIDFontSize;\n  impl = lepus::Value(\"18px\");\n  ret = UnitHandler::Process(id, impl, importAttributes3, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDTextAlign;\n  impl = lepus::Value(\"center\");\n  ret = UnitHandler::Process(id, impl, importAttributes3, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDBackgroundColor;\n  impl = lepus::Value(\"red\");\n  ret = UnitHandler::Process(id, impl, importAttributes3, configs);\n  EXPECT_TRUE(ret);\n  importTokens3.get()->SetAttributes(std::move(importAttributes3));\n  importTokensMap.insert(\n      std::make_pair(std::string(\".text-4\"), std::move(importTokens3)));\n  // create class text-5\n  StyleMap importAttributes4;\n  auto importTokens4 = fml::MakeRefCounted<CSSParseToken>(configs);\n  id = CSSPropertyID::kPropertyIDFontSize;\n  impl = lepus::Value(\"20px\");\n  ret = UnitHandler::Process(id, impl, importAttributes4, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDFontWeight;\n  impl = lepus::Value(\"bold\");\n  ret = UnitHandler::Process(id, impl, importAttributes4, configs);\n  EXPECT_TRUE(ret);\n  id = CSSPropertyID::kPropertyIDColor;\n  impl = lepus::Value(\"orange\");\n  ret = UnitHandler::Process(id, impl, importAttributes4, configs);\n  EXPECT_TRUE(ret);\n  importTokens4.get()->SetAttributes(std::move(importAttributes4));\n  importTokensMap.insert(\n      std::make_pair(std::string(\".text-5\"), std::move(importTokens4)));\n\n  const std::vector<int32_t> import_dependent_ids;\n  CSSKeyframesTokenMap import_keyframes;\n  CSSFontFaceRuleMap import_fontfaces;\n  SharedCSSFragment importFragment(1, import_dependent_ids, importTokensMap,\n                                   import_keyframes, import_fontfaces);\n  importFragment.SetEnableClassMerge(true);\n\n  EXPECT_EQ(indexFragment.css().size(), static_cast<size_t>(4));\n  EXPECT_EQ(importFragment.css().size(), static_cast<size_t>(4));\n  // start to merge two fragments\n  indexFragment.ImportOtherFragment(&importFragment);\n  // check the fragment size\n  EXPECT_EQ(indexFragment.css().size(), static_cast<size_t>(5));\n  auto css = indexFragment.css();\n  EXPECT_TRUE(css.find(\".text-1\") != css.end());\n  EXPECT_TRUE(css.find(\".text-2\") != css.end());\n  EXPECT_TRUE(css.find(\".text-3\") != css.end());\n  EXPECT_TRUE(css.find(\".text-4\") != css.end());\n  EXPECT_TRUE(css.find(\".text-5\") != css.end());\n  EXPECT_TRUE(indexFragment.GetCSSStyle(\".text-1\"));\n  // check text-1 style\n  auto style = css[\".text-1\"]->GetAttributes();\n  EXPECT_EQ(style.size(), static_cast<size_t>(3));\n  id = kPropertyIDFontSize;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsNumber());\n  EXPECT_EQ(style[id].GetNumber(), 18);\n  EXPECT_TRUE(style[id].IsPx());\n  id = kPropertyIDTextAlign;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsInt32());\n  EXPECT_EQ((starlight::TextAlignType)style[id].GetNumber(),\n            starlight::TextAlignType::kCenter);\n  id = kPropertyIDColor;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_EQ(style[id].GetValue().UInt32(), 4294901760);\n  // check text-2 style\n  style = css[\".text-2\"]->GetAttributes();\n  EXPECT_EQ(style.size(), static_cast<size_t>(3));\n  id = kPropertyIDFontSize;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsNumber());\n  EXPECT_EQ(style[id].GetNumber(), 20);\n  EXPECT_TRUE(style[id].IsPx());\n  id = kPropertyIDTextAlign;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsInt32());\n  EXPECT_EQ((starlight::TextAlignType)style[id].GetNumber(),\n            starlight::TextAlignType::kCenter);\n  id = kPropertyIDColor;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_EQ(style[id].GetValue().UInt32(), 4278222848);\n  // check text-3 style\n  style = css[\".text-3\"]->GetAttributes();\n  EXPECT_EQ(style.size(), static_cast<size_t>(4));\n  id = kPropertyIDFontSize;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsNumber());\n  EXPECT_EQ(style[id].GetNumber(), 18);\n  EXPECT_TRUE(style[id].IsPx());\n  id = kPropertyIDTextAlign;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsInt32());\n  EXPECT_EQ((starlight::TextAlignType)style[id].GetNumber(),\n            starlight::TextAlignType::kCenter);\n  id = kPropertyIDColor;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_EQ(style[id].GetValue().UInt32(), 4287245282);\n  id = kPropertyIDFontWeight;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsInt32());\n  EXPECT_EQ((starlight::FontWeightType)style[id].GetNumber(),\n            starlight::FontWeightType::kBold);\n  // check text-4 style\n  style = css[\".text-4\"]->GetAttributes();\n  EXPECT_EQ(style.size(), static_cast<size_t>(3));\n  id = kPropertyIDFontSize;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsNumber());\n  EXPECT_EQ(style[id].GetNumber(), 18);\n  EXPECT_TRUE(style[id].IsPx());\n  id = kPropertyIDTextAlign;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsInt32());\n  EXPECT_EQ((starlight::TextAlignType)style[id].GetNumber(),\n            starlight::TextAlignType::kCenter);\n  id = kPropertyIDBackgroundColor;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_EQ(style[id].GetValue().UInt32(), 4278190335);\n  // check text-5 style\n  style = css[\".text-5\"]->GetAttributes();\n  EXPECT_EQ(style.size(), static_cast<size_t>(5));\n  id = kPropertyIDFontSize;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsNumber());\n  EXPECT_EQ(style[id].GetNumber(), 20);\n  EXPECT_TRUE(style[id].IsPx());\n  id = kPropertyIDFontWeight;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsInt32());\n  EXPECT_EQ((starlight::FontWeightType)style[id].GetNumber(),\n            starlight::FontWeightType::kBold);\n  id = kPropertyIDFontStyle;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_TRUE(style[id].GetValue().IsInt32());\n  EXPECT_EQ((starlight::FontStyleType)style[id].GetNumber(),\n            starlight::FontStyleType::kItalic);\n  id = kPropertyIDBackgroundColor;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_EQ(style[id].GetValue().UInt32(), 4289583334);\n  id = kPropertyIDColor;\n  EXPECT_FALSE(style.find(id) == style.end());\n  EXPECT_EQ(style[id].GetValue().UInt32(), 4294944000);\n}\n\nTEST(SharedCSSFragment, CheckHasID) {\n  bool hasIdSelector = false;\n  {\n    StyleMap indexAttributes;\n    CSSParserConfigs configs;\n    auto tokens = fml::MakeRefCounted<CSSParseToken>(configs);\n\n    CSSParserTokenMap indexTokensMap;\n    auto id = CSSPropertyID::kPropertyIDFontSize;\n    auto impl = lepus::Value(\"18px\");\n    UnitHandler::Process(id, impl, indexAttributes, configs);\n    tokens.get()->SetAttributes(std::move(indexAttributes));\n\n    std::string key = \".text-1\";\n    auto& sheets = tokens->sheets();\n    auto shared_css_sheet = std::make_shared<CSSSheet>(key);\n    sheets.emplace_back(shared_css_sheet);\n\n    indexTokensMap.insert(std::make_pair(key, tokens));\n\n    const std::vector<int32_t> dependent_ids;\n    CSSKeyframesTokenMap keyframes;\n    CSSFontFaceRuleMap fontfaces;\n    SharedCSSFragment indexFragment(1, dependent_ids, indexTokensMap, keyframes,\n                                    fontfaces);\n    indexFragment.FindSpecificMapAndAdd(key, tokens);\n    hasIdSelector = indexFragment.HasIdSelector();\n  }\n  EXPECT_FALSE(hasIdSelector);\n\n  {\n    StyleMap indexAttributes1;\n    CSSParserConfigs configs;\n    auto tokens1 = fml::MakeRefCounted<CSSParseToken>(configs);\n\n    CSSParserTokenMap indexTokensMap1;\n    auto id1 = CSSPropertyID::kPropertyIDFontSize;\n    auto impl1 = lepus::Value(\"28px\");\n    UnitHandler::Process(id1, impl1, indexAttributes1, configs);\n    tokens1.get()->SetAttributes(std::move(indexAttributes1));\n\n    std::string key1 = \"#TestID\";\n    auto& sheets1 = tokens1->sheets();\n    auto shared_css_sheet1 = std::make_shared<CSSSheet>(key1);\n    sheets1.emplace_back(shared_css_sheet1);\n\n    indexTokensMap1.insert(std::make_pair(key1, tokens1));\n\n    const std::vector<int32_t> dependent_ids1;\n    CSSKeyframesTokenMap keyframes1;\n    CSSFontFaceRuleMap fontfaces1;\n    SharedCSSFragment indexFragment1(1, dependent_ids1, indexTokensMap1,\n                                     keyframes1, fontfaces1);\n    indexFragment1.FindSpecificMapAndAdd(key1, tokens1);\n    hasIdSelector = indexFragment1.HasIdSelector();\n  }\n  EXPECT_TRUE(hasIdSelector);\n}\n\n}  // namespace testing\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/style_node.h",
    "content": "// Copyright 2024 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_STYLE_NODE_H_\n#define CORE_RENDERER_CSS_STYLE_NODE_H_\n\n#include <string>\n\n#include \"base/include/value/base_string.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/utils/base/base_def.h\"\n\nnamespace lynx {\nnamespace css {\n\nclass StyleNode {\n public:\n  StyleNode() = default;\n  virtual ~StyleNode() = default;\n\n  virtual void OnStyleChange() = 0;\n\n  virtual const base::String& tag() const = 0;\n\n  virtual const base::String& idSelector() const = 0;\n\n  virtual tasm::PseudoState GetPseudoState() const = 0;\n\n  virtual bool HasPseudoState(tasm::PseudoState type) const = 0;\n\n  virtual const tasm::ClassList& classes() const = 0;\n\n  virtual css::StyleNode* SelectorMatchingParent() const = 0;\n\n  virtual css::StyleNode* HolderParent() const = 0;\n\n  virtual css::StyleNode* NextSibling() const = 0;\n\n  virtual css::StyleNode* PreviousSibling() const = 0;\n\n  virtual css::StyleNode* PseudoElementOwner() const = 0;\n\n  virtual bool ContainsIdSelector(const std::string& selector) const = 0;\n\n  virtual bool ContainsClassSelector(const std::string& selector) const = 0;\n\n  virtual bool ContainsTagSelector(const std::string& selector) const = 0;\n};\n\n}  // namespace css\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_STYLE_NODE_H_\n"
  },
  {
    "path": "core/renderer/css/text_attributes.cc",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/text_attributes.h\"\n\n#include \"core/renderer/css/css_style_utils.h\"\n#include \"core/renderer/css/measure_context.h\"\n#include \"core/renderer/css/parser/css_parser_configs.h\"\n#include \"core/renderer/tasm/config.h\"\n#include \"core/style/color.h\"\n\nnamespace lynx {\nnamespace starlight {\n\nvoid TextAttributes::Apply(const TextAttributes& rhs) {\n  font_size = rhs.font_size;\n  color = rhs.color;\n  decoration_color = rhs.decoration_color;\n  text_gradient = rhs.text_gradient;\n  white_space = rhs.white_space;\n  text_overflow = rhs.text_overflow;\n  font_weight = rhs.font_weight;\n  font_style = rhs.font_style;\n  font_family = rhs.font_family;\n  computed_line_height = rhs.computed_line_height;\n  line_height_factor = rhs.line_height_factor;\n  enable_font_scaling = rhs.enable_font_scaling;\n  letter_spacing = rhs.letter_spacing;\n  line_spacing = rhs.line_spacing;\n  text_align = rhs.text_align;\n  word_break = rhs.word_break;\n  underline_decoration = rhs.underline_decoration;\n  line_through_decoration = rhs.line_through_decoration;\n  text_shadow = rhs.text_shadow ? rhs.text_shadow : text_shadow;\n  vertical_align = rhs.vertical_align;\n  vertical_align_length = rhs.vertical_align_length;\n  text_indent = rhs.text_indent;\n  is_auto_font_size = rhs.is_auto_font_size;\n  auto_font_size_min_size = rhs.auto_font_size_min_size;\n  auto_font_size_max_size = rhs.auto_font_size_max_size;\n  auto_font_size_step_granularity = rhs.auto_font_size_step_granularity;\n  auto_font_size_preset_sizes = rhs.auto_font_size_preset_sizes\n                                    ? *rhs.auto_font_size_preset_sizes\n                                    : auto_font_size_preset_sizes;\n  hyphens = rhs.hyphens;\n  font_variation_settings = rhs.font_variation_settings;\n  font_feature_settings = rhs.font_feature_settings;\n  font_optical_sizing = rhs.font_optical_sizing;\n}\n\nvoid TextAttributes::ProcessRadialGradientIfNeeded(\n    const tasm::CssMeasureContext& length_context,\n    const tasm::CSSParserConfigs& parser_configs) {\n  if (text_gradient.has_value() && text_gradient->IsArray()) {\n    // For radial gradients, compute the gradient data before setting the\n    // property.\n    if (text_gradient->Array()->size() > 1 &&\n        text_gradient->Array()->get(0).Number() ==\n            static_cast<uint32_t>(\n                starlight::BackgroundImageType::kRadialGradient)) {\n      // Clone the gradient data to avoid modifying the original data during\n      // computation.\n      if (!clone_text_gradient) {\n        text_gradient =\n            base::make_flex_optional(lepus::Value::Clone(*(text_gradient)));\n        clone_text_gradient = true;\n      }\n      auto& gradient_data = text_gradient->Array()->get(1);\n      starlight::CSSStyleUtils::ComputeRadialGradient(\n          gradient_data, length_context, parser_configs);\n    }\n  }\n}\n\n}  // namespace starlight\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/text_attributes.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n#ifndef CORE_RENDERER_CSS_TEXT_ATTRIBUTES_H_\n#define CORE_RENDERER_CSS_TEXT_ATTRIBUTES_H_\n\n#include <optional>\n#include <tuple>\n#include <vector>\n\n#include \"base/include/flex_optional.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/base_value.h\"\n#include \"base/include/vector.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/renderer/starlight/style/default_layout_style.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/style/color.h\"\n#include \"core/style/default_computed_style.h\"\n#include \"core/style/shadow_data.h\"\n\nnamespace lynx {\nnamespace tasm {\nclass CssMeasureContext;\nstruct CSSParserConfigs;\n}  // namespace tasm\n\nnamespace starlight {\n\nenum class TextPropertyID : uint8_t {\n  kTextProperIDFontSize = 1,\n  kTextProperIDColor = 2,\n  kTextProperIDWhiteSpace = 3,\n  kTextProperIDTextOverflow = 4,\n  kTextProperIDFontWeight = 5,\n  kTextProperIDFontStyle = 6,\n  kTextProperIDLineHeight = 7,\n  kTextProperIDEnableFontScaling = 8,\n  kTextProperIDLetterSpacing = 9,\n  kTextProperIDLineSpacing = 10,\n  kTextProperIDTextAlign = 11,\n  kTextProperIDWordBreak = 12,\n  kTextProperIDUnderline = 13,\n  kTextProperIDLineThrough = 14,\n  kTextProperIDHasTextShadow = 15,\n  kTextProperIDShadowHOffset = 16,\n  kTextProperIDShadowVOffset = 17,\n  kTextProperIDShadowBlur = 18,\n  kTextProperIDShadowColor = 19,\n  kTextProperIDVerticalAlign = 20,\n  kTextProperIDVerticalAlignLength = 21,\n  kTextProperIDTextIndent = 22,\n\n  kTextProperIDEnd = 0xFF,\n};\n\nclass TextAttributes {\n public:\n  TextAttributes(float default_font_size) : font_size{default_font_size} {}\n\n  base::flex_optional<base::InlineVector<ShadowData, 1>> text_shadow;\n  base::flex_optional<base::InlineVector<float, 6>> auto_font_size_preset_sizes;\n  NLength text_indent{DefaultLayoutStyle::SL_DEFAULT_ZEROLENGTH()};\n  base::String font_family;\n\n  bool clone_text_gradient = false;\n  base::flex_optional<lepus::Value> text_gradient;\n\n  float vertical_align_length{DefaultComputedStyle::DEFAULT_FLOAT};\n  float font_size;\n  float computed_line_height{DefaultComputedStyle::DEFAULT_LINE_HEIGHT};\n  float line_height_factor{DefaultComputedStyle::DEFAULT_LINE_HEIGHT_FACTOR};\n  float letter_spacing{DefaultComputedStyle::DEFAULT_LETTER_SPACING};\n  float line_spacing{DefaultComputedStyle::DEFAULT_LINE_SPACING};\n  float text_stroke_width{DefaultComputedStyle::DEFAULT_FLOAT};\n  float auto_font_size_min_size{DefaultComputedStyle::DEFAULT_FLOAT};\n  float auto_font_size_max_size{DefaultComputedStyle::DEFAULT_FLOAT};\n  float auto_font_size_step_granularity{\n      DefaultComputedStyle::DEFAULT_AUTO_FONT_SIZE_STEP_GRANULARITY};\n  base::flex_optional<uint32_t> text_stroke_color;\n  base::flex_optional<uint32_t> color;\n  base::flex_optional<uint32_t> decoration_color;\n  base::flex_optional<uint32_t> text_decoration_color;\n  uint8_t text_decoration_style{\n      DefaultComputedStyle::DEFAULT_TEXT_DECORATION_STYLE};\n  // TODO(linxs) this type has changed.\n  starlight::WhiteSpaceType white_space{\n      DefaultComputedStyle::DEFAULT_WHITE_SPACE};\n  starlight::TextOverflowType text_overflow{\n      DefaultComputedStyle::DEFAULT_TEXT_OVERFLOW};\n  starlight::FontWeightType font_weight{\n      DefaultComputedStyle::DEFAULT_FONT_WEIGHT};\n  starlight::FontStyleType font_style{DefaultComputedStyle::DEFAULT_FONT_STYLE};\n  starlight::VerticalAlignType vertical_align{\n      DefaultComputedStyle::DEFAULT_VERTICAL_ALIGN};\n  starlight::TextAlignType text_align{DefaultComputedStyle::DEFAULT_TEXT_ALIGN};\n  starlight::WordBreakType word_break{DefaultComputedStyle::DEFAULT_WORD_BREAK};\n  starlight::HyphensType hyphens{DefaultComputedStyle::DEFAULT_HYPHENS};\n  bool enable_font_scaling{DefaultComputedStyle::DEFAULT_BOOLEAN};\n  bool underline_decoration{DefaultComputedStyle::DEFAULT_BOOLEAN};\n  bool line_through_decoration{DefaultComputedStyle::DEFAULT_BOOLEAN};\n  bool is_auto_font_size{DefaultComputedStyle::DEFAULT_AUTO_FONT_SIZE};\n  fml::RefPtr<lepus::CArray> font_variation_settings{nullptr};\n  fml::RefPtr<lepus::CArray> font_feature_settings{nullptr};\n  starlight::FontOpticalSizingType font_optical_sizing{\n      DefaultComputedStyle::DEFAULT_FONT_OPTICAL_SIZING};\n\n  void Reset() {}\n\n  bool operator==(const TextAttributes& rhs) const {\n    bool base_equal =\n        std::tie(font_size, color, decoration_color, white_space, text_overflow,\n                 font_weight, font_style, font_family, computed_line_height,\n                 line_height_factor, enable_font_scaling, letter_spacing,\n                 line_spacing, text_shadow, text_align, word_break,\n                 underline_decoration, line_through_decoration,\n                 text_decoration_color, text_decoration_style, text_indent,\n                 is_auto_font_size, auto_font_size_min_size,\n                 auto_font_size_max_size, auto_font_size_step_granularity,\n                 auto_font_size_preset_sizes, hyphens, font_optical_sizing) ==\n        std::tie(\n            rhs.font_size, rhs.color, rhs.decoration_color, rhs.white_space,\n            rhs.text_overflow, rhs.font_weight, rhs.font_style, rhs.font_family,\n            rhs.computed_line_height, rhs.line_height_factor,\n            rhs.enable_font_scaling, rhs.letter_spacing, rhs.line_spacing,\n            rhs.text_shadow, rhs.text_align, rhs.word_break,\n            rhs.underline_decoration, rhs.line_through_decoration,\n            rhs.text_decoration_color, rhs.text_decoration_style,\n            rhs.text_indent, rhs.is_auto_font_size, rhs.auto_font_size_min_size,\n            rhs.auto_font_size_max_size, rhs.auto_font_size_step_granularity,\n            rhs.auto_font_size_preset_sizes, rhs.hyphens,\n            rhs.font_optical_sizing);\n    if (!base_equal) {\n      return false;\n    }\n\n    if (font_variation_settings == rhs.font_variation_settings) {\n    } else if (font_variation_settings && rhs.font_variation_settings) {\n      if (*font_variation_settings != *rhs.font_variation_settings) {\n        return false;\n      }\n    } else {\n      return false;\n    }\n\n    if (font_feature_settings == rhs.font_feature_settings) {\n    } else if (font_feature_settings && rhs.font_feature_settings) {\n      if (*font_feature_settings != *rhs.font_feature_settings) {\n        return false;\n      }\n    } else {\n      return false;\n    }\n\n    return true;\n  }\n\n  bool operator!=(const TextAttributes& rhs) const { return !(*this == rhs); }\n\n  void Apply(const TextAttributes& rhs);\n\n  void ProcessRadialGradientIfNeeded(\n      const tasm::CssMeasureContext& length_context,\n      const tasm::CSSParserConfigs& parser_configs);\n};\n\n}  // namespace starlight\n}  // namespace lynx\n#endif  // CORE_RENDERER_CSS_TEXT_ATTRIBUTES_H_\n"
  },
  {
    "path": "core/renderer/css/transforms/transform_operation.cc",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/transforms/transform_operation.h\"\n\n#include <memory>\n\n#include \"base/include/log/logging.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/style/transform/decomposed_transform.h\"\n#include \"core/style/transform/matrix44.h\"\n\nnamespace lynx {\nnamespace transforms {\n\nnamespace {\nbool IsOperationIdentity(const TransformOperation* operation) {\n  return !operation || operation->IsIdentity();\n}\n\nfloat GetDefaultValue(TransformOperation::Type type) {\n  switch (type) {\n    case TransformOperation::Type::kScale: {\n      return 1.0;\n    }\n    default: {\n      return 0.0;\n    }\n  }\n}\n\nbool IsIdentityMatrix(const std::array<float, 16>& matrix) {\n  static constexpr std::array<float, 16> identity_matrix = {\n      1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};\n  return matrix == identity_matrix;\n}\n\n// get the final length type of translateX and translateY\nstd::array<TransformOperation::LengthType, 2> GetTranslateLengthType(\n    const TransformOperation* from, const TransformOperation* to) {\n  DCHECK(from || to);\n  TransformOperation::LengthType final_type_x =\n      TransformOperation::LengthType::kLengthUnit;\n  TransformOperation::LengthType final_type_y =\n      TransformOperation::LengthType::kLengthUnit;\n\n  if (IsOperationIdentity(from) && !IsOperationIdentity(to)) {\n    final_type_x = to->translate.type.x == TransformOperation::kLengthPercentage\n                       ? TransformOperation::kLengthPercentage\n                       : TransformOperation::kLengthUnit;\n    final_type_y = to->translate.type.y == TransformOperation::kLengthPercentage\n                       ? TransformOperation::kLengthPercentage\n                       : TransformOperation::kLengthUnit;\n    ;\n  } else if (!IsOperationIdentity(from) && IsOperationIdentity(to)) {\n    final_type_x =\n        from->translate.type.x == TransformOperation::kLengthPercentage\n            ? TransformOperation::kLengthPercentage\n            : TransformOperation::kLengthUnit;\n    ;\n    final_type_y =\n        from->translate.type.y == TransformOperation::kLengthPercentage\n            ? TransformOperation::kLengthPercentage\n            : TransformOperation::kLengthUnit;\n    ;\n  } else if (!IsOperationIdentity(from) && !IsOperationIdentity(to)) {\n    if (from->translate.type.x == to->translate.type.x &&\n        from->translate.type.x == TransformOperation::kLengthPercentage) {\n      final_type_x = TransformOperation::kLengthPercentage;\n    } else {\n      final_type_x = TransformOperation::kLengthUnit;\n    }\n    if (from->translate.type.y == to->translate.type.y &&\n        from->translate.type.y == TransformOperation::kLengthPercentage) {\n      final_type_y = TransformOperation::kLengthPercentage;\n    } else {\n      final_type_y = TransformOperation::kLengthUnit;\n    }\n  }\n  return std::array<TransformOperation::LengthType, 2>{final_type_x,\n                                                       final_type_y};\n}\n\n// Convert possible percentage or calc values to unit values\nstd::array<float, 3> GetPercentOrCalcTranslateValue(\n    const TransformOperation* translate,\n    starlight::LayoutResultForRendering element_layout_result) {\n  if (IsOperationIdentity(translate)) {\n    return std::array<float, 3>{0.0f, 0.0f, 0.0f};\n  }\n  float x = starlight::NLengthToLayoutUnit(\n                translate->translate.value.x,\n                starlight::LayoutUnit(element_layout_result.size_.width_))\n                .ToFloat();\n  float y = starlight::NLengthToLayoutUnit(\n                translate->translate.value.y,\n                starlight::LayoutUnit(element_layout_result.size_.height_))\n                .ToFloat();\n  float z = starlight::NLengthToLayoutUnit(translate->translate.value.z,\n                                           starlight::LayoutUnit(0.0f))\n                .ToFloat();\n  return std::array<float, 3>{x, y, z};\n};\n\n// Convert possible percentage or calc values to unit values\nstd::array<float, 3> GetPercentOrCalcTranslateValue(\n    const TransformOperation* translate, tasm::Element* element) {\n  DCHECK(element);\n  return GetPercentOrCalcTranslateValue(translate, element->layout_result());\n};\n\nstatic float BlendValue(float from, float to, float progress) {\n  return from * (1 - progress) + to * progress;\n}\n\n}  // namespace\nbool TransformOperation::IsIdentity() const {\n  switch (type) {\n    case TransformOperation::Type::kTranslate: {\n      float default_value = GetDefaultValue(type);\n      return translate.value.x.NumericLength().GetFixedPart() ==\n                 default_value &&\n             !translate.value.x.NumericLength().ContainsPercentage() &&\n             translate.value.y.NumericLength().GetFixedPart() ==\n                 default_value &&\n             !translate.value.y.NumericLength().ContainsPercentage() &&\n             translate.value.z.NumericLength().GetFixedPart() ==\n                 default_value &&\n             !translate.value.z.NumericLength().ContainsPercentage();\n    }\n    case TransformOperation::Type::kRotateX:\n    case TransformOperation::Type::kRotateY:\n    case TransformOperation::Type::kRotateZ: {\n      return rotate.degree == GetDefaultValue(type);\n    }\n    case TransformOperation::Type::kScale: {\n      return scale.x == GetDefaultValue(type) &&\n             scale.y == GetDefaultValue(type);\n    }\n    case TransformOperation::Type::kSkew: {\n      return skew.x == GetDefaultValue(type) && skew.y == GetDefaultValue(type);\n    }\n    case TransformOperation::Type::kMatrix:\n    case TransformOperation::Type::kMatrix3d: {\n      return IsIdentityMatrix(matrix.matrix_data);\n    }\n    default: {\n      return true;\n    }\n  }\n}\n\n// Lazy bake matrix till we need matrix, to avoid getting invalid size of\n// element if layout is not ready.\nconst Matrix44& TransformOperation::GetMatrix(tasm::Element* element) {\n  if (matrix44) {\n    return *matrix44;\n  }\n  Bake(element);\n  return *matrix44;\n}\n\nconst Matrix44& TransformOperation::GetMatrix(\n    starlight::LayoutResultForRendering element_layout_result) {\n  if (matrix44) {\n    return *matrix44;\n  }\n  Bake(element_layout_result);\n  return *matrix44;\n}\n\nvoid TransformOperation::DoBakeNonTranslateOperation() {\n  switch (type) {\n    case TransformOperation::Type::kRotateX: {\n      matrix44->setRotateAboutXAxis(rotate.degree);\n      break;\n    }\n    case TransformOperation::Type::kRotateY: {\n      matrix44->setRotateAboutYAxis(rotate.degree);\n      break;\n    }\n    case TransformOperation::Type::kRotateZ: {\n      matrix44->setRotateAboutZAxis(rotate.degree);\n      break;\n    }\n    case TransformOperation::Type::kScale: {\n      matrix44->preScale(scale.x, scale.y, 1);\n      break;\n    }\n    case TransformOperation::Type::kSkew: {\n      matrix44->Skew(skew.x, skew.y);\n      break;\n    }\n    case TransformOperation::Type::kMatrix:\n    case TransformOperation::Type::kMatrix3d: {\n      matrix44->Matrix(matrix.matrix_data);\n    }\n    default: {\n      break;\n    }\n  }\n}\n\nvoid TransformOperation::Bake(tasm::Element* element) {\n  matrix44 = std::make_optional<Matrix44>();\n  if (type == TransformOperation::Type::kTranslate) {\n    std::array<float, 3> arr = GetPercentOrCalcTranslateValue(this, element);\n    matrix44->preTranslate(arr[0], arr[1], arr[2]);\n  } else {\n    DoBakeNonTranslateOperation();\n  }\n}\n\nvoid TransformOperation::Bake(\n    starlight::LayoutResultForRendering element_layout_result) {\n  matrix44 = std::make_optional<Matrix44>();\n  if (type == TransformOperation::Type::kTranslate) {\n    std::array<float, 3> arr =\n        GetPercentOrCalcTranslateValue(this, element_layout_result);\n    matrix44->preTranslate(arr[0], arr[1], arr[2]);\n  } else {\n    DoBakeNonTranslateOperation();\n  }\n}\n\nTransformOperation ComposeTransform(\n    const DecomposedTransform& decomposed_transform) {\n  TransformOperation result;\n  result.type = TransformOperation::kMatrix3d;\n  Matrix44 temp_matrix44;\n\n  // perspective\n  for (int i = 0; i < 3; ++i) {\n    if (decomposed_transform.perspective[i] != 0) {\n      temp_matrix44.setRC(3, i, decomposed_transform.perspective[i]);\n    }\n  }\n  if (decomposed_transform.perspective[3] != 1) {\n    temp_matrix44.setRC(3, 3, decomposed_transform.perspective[3]);\n  }\n\n  // translate\n  temp_matrix44.preTranslate(decomposed_transform.translate[0],\n                             decomposed_transform.translate[1],\n                             decomposed_transform.translate[2]);\n\n  // rotate\n  temp_matrix44.preConcat(Matrix44(decomposed_transform.quaternion));\n\n  // skew\n  if (decomposed_transform.skew[0] || decomposed_transform.skew[1] ||\n      decomposed_transform.skew[2])\n    temp_matrix44.Skew(decomposed_transform.skew[0], 2);\n\n  // scale\n  temp_matrix44.preScale(decomposed_transform.scale[0],\n                         decomposed_transform.scale[1],\n                         decomposed_transform.scale[2]);\n\n  for (int row = 0; row < 4; ++row) {\n    for (int col = 0; col < 4; ++col) {\n      result.matrix.matrix_data.at(4 * row + col) = temp_matrix44.rc(row, col);\n    }\n  }\n  return result;\n}\n\nTransformOperation TransformOperation::BlendTransformOperations(\n    const TransformOperation* from, const TransformOperation* to,\n    float progress, tasm::Element* element) {\n  if (!from && !to) {\n    return TransformOperation();\n  }\n  DCHECK(from != nullptr || to != nullptr);\n  DCHECK(element);\n  if (IsOperationIdentity(from) && IsOperationIdentity(to)) {\n    return TransformOperation();\n  }\n  TransformOperation operation;\n  TransformOperation::Type transform_type =\n      IsOperationIdentity(from) ? to->type : from->type;\n  operation.type = transform_type;\n  switch (transform_type) {\n    case TransformOperation::Type::kTranslate: {\n      float from_x = IsOperationIdentity(from)\n                         ? 0.0f\n                         : from->translate.value.x.GetRawValue();\n      float from_y = IsOperationIdentity(from)\n                         ? 0.0f\n                         : from->translate.value.y.GetRawValue();\n      float from_z = IsOperationIdentity(from)\n                         ? 0.0f\n                         : from->translate.value.z.GetRawValue();\n      float to_x =\n          IsOperationIdentity(to) ? 0.0f : to->translate.value.x.GetRawValue();\n      float to_y =\n          IsOperationIdentity(to) ? 0.0f : to->translate.value.y.GetRawValue();\n      float to_z =\n          IsOperationIdentity(to) ? 0.0f : to->translate.value.z.GetRawValue();\n      std::array<TransformOperation::LengthType, 2> result_type_arr =\n          GetTranslateLengthType(from, to);\n      std::array<float, 3> from_value_arr =\n          GetPercentOrCalcTranslateValue(from, element);\n      std::array<float, 3> to_value_arr =\n          GetPercentOrCalcTranslateValue(to, element);\n      if (result_type_arr[0] !=\n          TransformOperation::LengthType::kLengthPercentage) {\n        from_x = from_value_arr[0];\n        to_x = to_value_arr[0];\n      }\n      if (result_type_arr[1] !=\n          TransformOperation::LengthType::kLengthPercentage) {\n        from_y = from_value_arr[1];\n        to_y = to_value_arr[1];\n      }\n\n      operation.translate.type.x = result_type_arr[0];\n      operation.translate.value.x =\n          result_type_arr[0] == kLengthUnit\n              ? starlight::NLength::MakeUnitNLength(\n                    BlendValue(from_x, to_x, progress))\n              : starlight::NLength::MakePercentageNLength(\n                    BlendValue(from_x, to_x, progress));\n\n      operation.translate.type.y = result_type_arr[1];\n      operation.translate.value.y =\n          result_type_arr[1] == kLengthUnit\n              ? starlight::NLength::MakeUnitNLength(\n                    BlendValue(from_y, to_y, progress))\n              : starlight::NLength::MakePercentageNLength(\n                    BlendValue(from_y, to_y, progress));\n\n      operation.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n      operation.translate.value.z = starlight::NLength::MakeUnitNLength(\n          BlendValue(from_z, to_z, progress));\n      return operation;\n    }\n    case TransformOperation::Type::kRotateX:\n    case TransformOperation::Type::kRotateY:\n    case TransformOperation::Type::kRotateZ: {\n      float from_angle = IsOperationIdentity(from) ? 0.0f : from->rotate.degree;\n      float to_angle = IsOperationIdentity(to) ? 0.0f : to->rotate.degree;\n      operation.rotate.degree = BlendValue(from_angle, to_angle, progress);\n      return operation;\n    }\n    case TransformOperation::Type::kScale: {\n      float from_x = IsOperationIdentity(from) ? 1.0f : from->scale.x;\n      float from_y = IsOperationIdentity(from) ? 1.0f : from->scale.y;\n      float to_x = IsOperationIdentity(to) ? 1.0f : to->scale.x;\n      float to_y = IsOperationIdentity(to) ? 1.0f : to->scale.y;\n      operation.scale.x = BlendValue(from_x, to_x, progress);\n      operation.scale.y = BlendValue(from_y, to_y, progress);\n      return operation;\n    }\n    case TransformOperation::Type::kSkew: {\n      float from_x = IsOperationIdentity(from) ? 0.0f : from->skew.x;\n      float from_y = IsOperationIdentity(from) ? 0.0f : from->skew.y;\n      float to_x = IsOperationIdentity(to) ? 0.0f : to->skew.x;\n      float to_y = IsOperationIdentity(to) ? 0.0f : to->skew.y;\n      operation.skew.x = BlendValue(from_x, to_x, progress);\n      operation.skew.y = BlendValue(from_y, to_y, progress);\n      return operation;\n    }\n    case TransformOperation::Type::kMatrix:\n    case TransformOperation::Type::kMatrix3d: {\n      std::unique_ptr<DecomposedTransform> decomposed_transform_from =\n          std::make_unique<DecomposedTransform>();\n      Matrix44 from_matrix = Matrix44();\n      if (!IsOperationIdentity(from)) {\n        from_matrix.Matrix(from->matrix.matrix_data);\n      }\n      DecomposeTransform(decomposed_transform_from.get(), from_matrix);\n\n      std::unique_ptr<DecomposedTransform> decomposed_transform_to =\n          std::make_unique<DecomposedTransform>();\n      Matrix44 to_matrix = Matrix44();\n      if (!IsOperationIdentity(to)) {\n        to_matrix.Matrix(to->matrix.matrix_data);\n      }\n      DecomposeTransform(decomposed_transform_to.get(), to_matrix);\n      auto decomposed_transform_blended = BlendDecomposedTransforms(\n          *decomposed_transform_from, *decomposed_transform_to, progress);\n      operation = ComposeTransform(decomposed_transform_blended);\n    }\n    default: {\n      return operation;\n    }\n  }\n}\n\nbool TransformOperation::NotifyElementSizeUpdated() {\n  if (type == TransformOperation::Type::kTranslate &&\n      (translate.type.x == TransformOperation::LengthType::kLengthPercentage ||\n       translate.type.y == TransformOperation::LengthType::kLengthPercentage ||\n       translate.type.z == TransformOperation::LengthType::kLengthPercentage)) {\n    matrix44 = std::optional<Matrix44>();\n    return true;\n  }\n  return false;\n}\n\nbool TransformOperation::NotifyUnitValuesUpdatedToAnimation(\n    tasm::CSSValuePattern pattern_type) {\n  if (type == TransformOperation::Type::kTranslate) {\n    if (unit_type_0_ == pattern_type || unit_type_1_ == pattern_type ||\n        unit_type_2_ == pattern_type) {\n      matrix44 = std::optional<Matrix44>();\n      return true;\n    }\n  }\n  return false;\n}\n\n}  // namespace transforms\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/transforms/transform_operation.h",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_TRANSFORMS_TRANSFORM_OPERATION_H_\n#define CORE_RENDERER_CSS_TRANSFORMS_TRANSFORM_OPERATION_H_\n\n#include <array>\n#include <optional>\n\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/renderer/starlight/types/layout_result.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/style/transform/matrix44.h\"\n\nnamespace lynx {\nnamespace tasm {\nclass Element;\n}\nnamespace transforms {\n\nstruct TransformOperation {\n  TransformOperation() {\n    Skew();\n    Scale();\n    Translate();\n    Rotate();\n  }\n  enum LengthType {\n    kLengthUnit,\n    kLengthPercentage,\n    kLengthCalc,\n  };\n  enum Type {\n    kIdentity = 0,\n    kTranslate = 1,\n    kRotateX = 1 << 2,\n    kRotateY = 1 << 3,\n    kRotateZ = 1 << 4,\n    kScale = 1 << 5,\n    kSkew = 1 << 6,\n    kMatrix = 1 << 7,\n    kMatrix3d = 1 << 8,\n  };\n  const Matrix44& GetMatrix(tasm::Element* element);\n\n  const Matrix44& GetMatrix(\n      starlight::LayoutResultForRendering element_layout_result);\n\n  bool NotifyElementSizeUpdated();\n\n  bool NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern);\n\n  // If you change the union value of TransformOperation, you should call Bake()\n  // directly to make a new matrix!\n  void Bake(tasm::Element* element);\n\n  void Bake(starlight::LayoutResultForRendering element_layout_result);\n\n  bool IsIdentity() const;\n  static TransformOperation BlendTransformOperations(\n      const TransformOperation* from, const TransformOperation* to,\n      float progress, tasm::Element* element);\n\n  struct Skew {\n    Skew() {\n      x = 0.0f;\n      y = 0.0f;\n    }\n    float x, y;  // degree\n  } skew;\n\n  struct Scale {\n    Scale() {\n      x = 0.0f;\n      y = 0.0f;\n    }\n    float x, y;\n  } scale;\n\n  struct Translate {\n    struct Type {\n      Type() {\n        x = kLengthUnit;\n        y = kLengthUnit;\n        z = kLengthUnit;\n      }\n      LengthType x, y, z;\n    } type;\n\n    struct Value {\n      starlight::NLength x = starlight::NLength::MakeUnitNLength(0.0f);\n      starlight::NLength y = starlight::NLength::MakeUnitNLength(0.0f);\n      starlight::NLength z = starlight::NLength::MakeUnitNLength(0.0f);\n    } value;\n    Translate() {\n      type = Type();\n      //      value = Value();\n    }\n  } translate;\n\n  struct Rotate {\n    Rotate() { degree = 0.0f; }\n    float degree;\n  } rotate;\n\n  struct Matrix {\n    std::array<float, 16> matrix_data = {1, 0, 0, 0,   // {{1, 0, 0, 0}\n                                         0, 1, 0, 0,   //  {0, 1, 0, 0}\n                                         0, 0, 1, 0,   //  {0, 0, 1, 0}\n                                         0, 0, 0, 1};  //  {0, 0, 0, 1}}\n  } matrix;\n\n  Type type = kIdentity;\n\n  tasm::CSSValuePattern unit_type_0_ = tasm::CSSValuePattern::EMPTY;\n  tasm::CSSValuePattern unit_type_1_ = tasm::CSSValuePattern::EMPTY;\n  tasm::CSSValuePattern unit_type_2_ = tasm::CSSValuePattern::EMPTY;\n\n private:\n  // matrix44 is just a cache of intermediate results that are computed.\n  std::optional<Matrix44> matrix44;\n\n  void DoBakeNonTranslateOperation();\n};\n}  // namespace transforms\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_TRANSFORMS_TRANSFORM_OPERATION_H_\n"
  },
  {
    "path": "core/renderer/css/transforms/transform_operation_unittest.cc",
    "content": "// Copyright 2017 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/transforms/transform_operation.h\"\n\n#include <cmath>\n\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/css/transforms/transform_operations.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/transform/matrix44.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace transforms {\nnamespace testing {\n\nnamespace {\nvoid CompareMatrix44(const Matrix44& a, const Matrix44& b) {\n  for (int row = 0; row < 4; ++row) {\n    for (int col = 0; col < 4; ++col) {\n      EXPECT_FLOAT_EQ(a.rc(row, col), b.rc(row, col));\n    }\n  }\n}\n\nvoid CompareTransformOperation(const TransformOperation& a,\n                               const TransformOperation& b) {\n  EXPECT_EQ(a.type, b.type);\n  switch (a.type) {\n    case TransformOperation::Type::kTranslate: {\n      EXPECT_EQ(a.translate.type.x, b.translate.type.x);\n      EXPECT_EQ(a.translate.type.y, b.translate.type.y);\n      EXPECT_EQ(a.translate.type.z, b.translate.type.z);\n      EXPECT_FLOAT_EQ(a.translate.value.x.GetRawValue(),\n                      b.translate.value.x.GetRawValue());\n      EXPECT_FLOAT_EQ(a.translate.value.y.GetRawValue(),\n                      b.translate.value.y.GetRawValue());\n      EXPECT_FLOAT_EQ(a.translate.value.z.GetRawValue(),\n                      b.translate.value.z.GetRawValue());\n      break;\n    }\n    case TransformOperation::Type::kRotateX:\n    case TransformOperation::Type::kRotateY:\n    case TransformOperation::Type::kRotateZ: {\n      EXPECT_FLOAT_EQ(a.rotate.degree, b.rotate.degree);\n      break;\n    }\n    case TransformOperation::Type::kScale: {\n      EXPECT_FLOAT_EQ(a.scale.x, b.scale.x);\n      EXPECT_FLOAT_EQ(a.scale.y, b.scale.y);\n      break;\n    }\n    case TransformOperation::Type::kSkew: {\n      EXPECT_FLOAT_EQ(a.skew.x, b.skew.x);\n      EXPECT_FLOAT_EQ(a.skew.y, b.skew.y);\n      break;\n    }\n    default: {\n      break;\n    }\n  }\n}\n\n}  // namespace\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\n\nclass TransformOperationTest : public ::testing::Test {\n public:\n  TransformOperationTest() {}\n  ~TransformOperationTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>\n      tasm_mediator;\n\n  void SetUp() override {\n    tasm::LynxEnvConfig lynx_env_config(kWidth, kHeight,\n                                        kDefaultLayoutsUnitPerPx,\n                                        kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<tasm::MockPaintingContext>(), tasm_mediator.get(),\n        lynx_env_config);\n    auto config = std::make_shared<tasm::PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n  }\n};\n\nTEST_F(TransformOperationTest, GetMatrix) {\n  auto element = manager->CreateFiberElement(\"view\");\n  std::array<float, 4> arr = {0, 0, 0, 0};\n  element->UpdateLayout(0.0, 0.0, 100.0, 200.0, arr, arr, arr, &arr, 0);\n\n  TransformOperation translate_op;\n  translate_op.type = TransformOperation::Type::kTranslate;\n  translate_op.translate.type.x =\n      TransformOperation::LengthType::kLengthPercentage;\n  translate_op.translate.type.y =\n      TransformOperation::LengthType::kLengthPercentage;\n  translate_op.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n  translate_op.translate.value.x =\n      starlight::NLength::MakePercentageNLength(20.0f);\n  translate_op.translate.value.y =\n      starlight::NLength::MakePercentageNLength(50.0f);\n  translate_op.translate.value.z = starlight::NLength::MakeUnitNLength(100.0f);\n\n  Matrix44 m0 = translate_op.GetMatrix(element.get());\n  Matrix44 expected0;\n  expected0.setRC(0, 3, 20);\n  expected0.setRC(1, 3, 100);\n  expected0.setRC(2, 3, 100);\n  CompareMatrix44(expected0, m0);\n}\n\nTEST_F(TransformOperationTest, BlendTransformOperations) {\n  auto element = manager->CreateFiberElement(\"view\");\n  std::array<float, 4> arr = {0, 0, 0, 0};\n  element->UpdateLayout(0.0, 0.0, 100.0, 200.0, arr, arr, arr, &arr, 0);\n\n  {\n    TransformOperation transform0;\n    transform0.type = TransformOperation::Type::kTranslate;\n    transform0.translate.type.x =\n        TransformOperation::LengthType::kLengthPercentage;\n    transform0.translate.type.y =\n        TransformOperation::LengthType::kLengthPercentage;\n    transform0.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n    transform0.translate.value.x =\n        starlight::NLength::MakePercentageNLength(20.0f);\n    transform0.translate.value.y =\n        starlight::NLength::MakePercentageNLength(50.0f);\n    transform0.translate.value.z = starlight::NLength::MakeUnitNLength(100.0f);\n\n    TransformOperation transform1;\n    transform1.type = TransformOperation::Type::kTranslate;\n    transform1.translate.type.x =\n        TransformOperation::LengthType::kLengthPercentage;\n    transform1.translate.type.y =\n        TransformOperation::LengthType::kLengthPercentage;\n    transform1.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n    transform1.translate.value.x =\n        starlight::NLength::MakePercentageNLength(30.0f);\n    transform1.translate.value.y =\n        starlight::NLength::MakePercentageNLength(70.0f);\n    transform1.translate.value.z = starlight::NLength::MakeUnitNLength(20.0f);\n\n    TransformOperation expected;\n    expected.type = TransformOperation::Type::kTranslate;\n    expected.translate.type.x =\n        TransformOperation::LengthType::kLengthPercentage;\n    expected.translate.type.y =\n        TransformOperation::LengthType::kLengthPercentage;\n    expected.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n    expected.translate.value.x =\n        starlight::NLength::MakePercentageNLength(22.0f);\n    expected.translate.value.y =\n        starlight::NLength::MakePercentageNLength(54.0f);\n    expected.translate.value.z = starlight::NLength::MakeUnitNLength(84.0f);\n\n    TransformOperation result = TransformOperation::BlendTransformOperations(\n        &transform0, &transform1, 0.2, element.get());\n    CompareTransformOperation(expected, result);\n  }\n\n  {\n    TransformOperation transform0;\n    transform0.type = TransformOperation::Type::kTranslate;\n    transform0.translate.type.x = TransformOperation::LengthType::kLengthUnit;\n    transform0.translate.type.y =\n        TransformOperation::LengthType::kLengthPercentage;\n    transform0.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n    transform0.translate.value.x = starlight::NLength::MakeUnitNLength(20.0f);\n    transform0.translate.value.y =\n        starlight::NLength::MakePercentageNLength(50.0f);\n    transform0.translate.value.z = starlight::NLength::MakeUnitNLength(100.0f);\n\n    TransformOperation transform1;\n    transform1.type = TransformOperation::Type::kTranslate;\n    transform1.translate.type.x = TransformOperation::LengthType::kLengthUnit;\n    transform1.translate.type.y = TransformOperation::LengthType::kLengthUnit;\n    transform1.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n    transform1.translate.value.x = starlight::NLength::MakeUnitNLength(30.0f);\n    transform1.translate.value.y = starlight::NLength::MakeUnitNLength(70.0f);\n    transform1.translate.value.z = starlight::NLength::MakeUnitNLength(20.0f);\n\n    TransformOperation expected;\n    expected.type = TransformOperation::Type::kTranslate;\n    expected.translate.type.x = TransformOperation::LengthType::kLengthUnit;\n    expected.translate.type.y = TransformOperation::LengthType::kLengthUnit;\n    expected.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n    expected.translate.value.x = starlight::NLength::MakeUnitNLength(24.0f);\n    expected.translate.value.y = starlight::NLength::MakeUnitNLength(88.0f);\n    expected.translate.value.z = starlight::NLength::MakeUnitNLength(68.0f);\n\n    TransformOperation result = TransformOperation::BlendTransformOperations(\n        &transform0, &transform1, 0.4, element.get());\n    CompareTransformOperation(expected, result);\n  }\n\n  {\n    TransformOperation transform0;\n    transform0.type = TransformOperation::Type::kTranslate;\n    transform0.translate.type.x = TransformOperation::LengthType::kLengthUnit;\n    transform0.translate.type.y =\n        TransformOperation::LengthType::kLengthPercentage;\n    transform0.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n    transform0.translate.value.x = starlight::NLength::MakeUnitNLength(20.0f);\n    transform0.translate.value.y =\n        starlight::NLength::MakePercentageNLength(50.0f);\n    transform0.translate.value.z = starlight::NLength::MakeUnitNLength(100.0f);\n\n    TransformOperation transform1;\n\n    TransformOperation expected;\n    expected.type = TransformOperation::Type::kTranslate;\n    expected.translate.type.x = TransformOperation::LengthType::kLengthUnit;\n    expected.translate.type.y =\n        TransformOperation::LengthType::kLengthPercentage;\n    expected.translate.type.z = TransformOperation::LengthType::kLengthUnit;\n    expected.translate.value.x = starlight::NLength::MakeUnitNLength(8.0f);\n    expected.translate.value.y =\n        starlight::NLength::MakePercentageNLength(20.0f);\n    expected.translate.value.z = starlight::NLength::MakeUnitNLength(40.0f);\n\n    TransformOperation result = TransformOperation::BlendTransformOperations(\n        &transform0, &transform1, 0.6, element.get());\n    CompareTransformOperation(expected, result);\n  }\n  {\n    TransformOperation transform0;\n    transform0.type = TransformOperation::Type::kScale;\n    transform0.scale.x = 5;\n    transform0.scale.y = 0.5;\n\n    TransformOperation transform1;\n\n    TransformOperation expected;\n    expected.type = TransformOperation::Type::kScale;\n    expected.scale.x = 1.8;\n    expected.scale.y = 0.9;\n\n    TransformOperation result = TransformOperation::BlendTransformOperations(\n        &transform0, &transform1, 0.8, element.get());\n    CompareTransformOperation(expected, result);\n  }\n}\n\nTEST_F(TransformOperationTest, BlendIdentityAndNonIdentityLists) {\n  auto element = manager->CreateFiberElement(\"view\");\n  std::array<float, 4> arr = {0, 0, 0, 0};\n  element->UpdateLayout(0.0, 0.0, 100.0, 200.0, arr, arr, arr, &arr, 0);\n\n  // Reproduce crash: Check failed: from != nullptr || to != nullptr.\n  // Scenario:\n  // @keyframes crash {\n  //   from {\n  //     transform: translateX(0px) translateX(0px); /* 2 Identity ops */\n  //   }\n  //   to {\n  //     transform: translateX(100px); /* 1 Non-Identity op */\n  //   }\n  // }\n  //\n  // from: [Identity, Identity] -> IsIdentity() = true, size treated as 0\n  // to:   [Non-Identity]       -> IsIdentity() = false, size = 1\n  // MatchingPrefixLength returns max(2, 1) = 2.\n  // Loop runs for i=0, i=1.\n  // i=1: from_size=0, to_size=1. Both indices out of bounds -> nullptr,\n  // nullptr.\n\n  TransformOperation identity_op1;\n  identity_op1.type = TransformOperation::Type::kTranslate;\n  identity_op1.translate.value.x = starlight::NLength::MakeUnitNLength(0.0f);\n\n  TransformOperation identity_op2;\n  identity_op2.type = TransformOperation::Type::kTranslate;\n  identity_op2.translate.value.x = starlight::NLength::MakeUnitNLength(0.0f);\n\n  lynx::transforms::TransformOperations from_ops(element.get());\n  from_ops.Append(identity_op1);\n  from_ops.Append(identity_op2);\n\n  TransformOperation non_identity_op;\n  non_identity_op.type = TransformOperation::Type::kTranslate;\n  non_identity_op.translate.value.x =\n      starlight::NLength::MakeUnitNLength(100.0f);\n\n  lynx::transforms::TransformOperations to_ops(element.get());\n  to_ops.Append(non_identity_op);\n\n  // This should crash if the bug exists\n  lynx::transforms::TransformOperations result = to_ops.Blend(from_ops, 0.5f);\n}\n\n}  // namespace testing\n}  // namespace transforms\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/transforms/transform_operations.cc",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifdef OS_WIN\n#define _USE_MATH_DEFINES\n#endif\n\n#include <algorithm>\n#include <cmath>\n#include <string>\n#include <utility>\n\n// TODO(wujintian): Remove this include because the actual implementation has no\n// relation with CSSKeyframeManager\n#include \"base/include/vector.h\"\n#include \"core/animation/css_keyframe_manager.h\"  // nogncheck\n#include \"core/renderer/css/css_decoder.h\"\n#include \"core/renderer/css/transforms/transform_operation.h\"\n#include \"core/renderer/css/transforms/transform_operations.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/starlight/types/layout_result.h\"\n#include \"core/style/transform/decomposed_transform.h\"\n#include \"core/style/transform/matrix44.h\"\n\nnamespace lynx {\nnamespace transforms {\nstatic inline constexpr float RadToDeg(float rad) {\n  return rad * 180.0f / M_PI;\n}\n\n// A standalone function using for initialize a transform operations with\n// transform raw data. It will traverse each item in the raw data and append\n// different transform operation to transform operations according to the item\n// type.\nvoid TransformOperations::InitializeTransformOperations(\n    TransformOperations& transform_operations,\n    base::Vector<lynx::starlight::TransformRawData>& transform_raw_data) {\n  for (auto& item : transform_raw_data) {\n    auto zero_unit_nlength = starlight::NLength::MakeUnitNLength(0.0f);\n    switch (item.type) {\n      case starlight::TransformType::kTranslate: {\n        transform_operations.AppendTranslate(\n            item.p0,\n            item.p0.IsPercent()\n                ? TransformOperation::LengthType::kLengthPercentage\n                : TransformOperation::LengthType::kLengthUnit,\n            item.p1,\n            item.p1.IsPercent()\n                ? TransformOperation::LengthType::kLengthPercentage\n                : TransformOperation::LengthType::kLengthUnit,\n            zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n        transform_operations.AppendTranslateUnitType(item);\n        break;\n      }\n      case starlight::TransformType::kTranslateX: {\n        transform_operations.AppendTranslate(\n            item.p0,\n            item.p0.IsPercent()\n                ? TransformOperation::LengthType::kLengthPercentage\n                : TransformOperation::LengthType::kLengthUnit,\n            zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n            zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n        transform_operations.AppendTranslateUnitType(item);\n        break;\n      }\n      case starlight::TransformType::kTranslateY: {\n        transform_operations.AppendTranslate(\n            zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n            item.p0,\n            item.p0.IsPercent()\n                ? TransformOperation::LengthType::kLengthPercentage\n                : TransformOperation::LengthType::kLengthUnit,\n            zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n        transform_operations.AppendTranslateUnitType(item);\n        break;\n      }\n      case starlight::TransformType::kTranslateZ: {\n        transform_operations.AppendTranslate(\n            zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n            zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n            item.p0,\n            item.p0.IsPercent()\n                ? TransformOperation::LengthType::kLengthPercentage\n                : TransformOperation::LengthType::kLengthUnit);\n        transform_operations.AppendTranslateUnitType(item);\n        break;\n      }\n      case starlight::TransformType::kTranslate3d: {\n        transform_operations.AppendTranslate(\n            item.p0,\n            item.p0.IsPercent()\n                ? TransformOperation::LengthType::kLengthPercentage\n                : TransformOperation::LengthType::kLengthUnit,\n            item.p1,\n            item.p1.IsPercent()\n                ? TransformOperation::LengthType::kLengthPercentage\n                : TransformOperation::LengthType::kLengthUnit,\n            item.p2,\n            item.p2.IsPercent()\n                ? TransformOperation::LengthType::kLengthPercentage\n                : TransformOperation::LengthType::kLengthUnit);\n        transform_operations.AppendTranslateUnitType(item);\n        break;\n      }\n      case starlight::TransformType::kRotateX: {\n        transform_operations.AppendRotate(TransformOperation::Type::kRotateX,\n                                          item.p0.GetRawValue());\n        break;\n      }\n      case starlight::TransformType::kRotateY: {\n        transform_operations.AppendRotate(TransformOperation::Type::kRotateY,\n                                          item.p0.GetRawValue());\n        break;\n      }\n      case starlight::TransformType::kRotate:\n      case starlight::TransformType::kRotateZ: {\n        transform_operations.AppendRotate(TransformOperation::Type::kRotateZ,\n                                          item.p0.GetRawValue());\n        break;\n      }\n      case starlight::TransformType::kScale: {\n        transform_operations.AppendScale(item.p0.GetRawValue(),\n                                         item.p1.GetRawValue());\n        break;\n      }\n      case starlight::TransformType::kScaleX: {\n        transform_operations.AppendScale(item.p0.GetRawValue(), 1.0f);\n        break;\n      }\n      case starlight::TransformType::kScaleY: {\n        transform_operations.AppendScale(1.0f, item.p0.GetRawValue());\n        break;\n      }\n      case starlight::TransformType::kSkew: {\n        transform_operations.AppendSkew(item.p0.GetRawValue(),\n                                        item.p1.GetRawValue());\n        break;\n      }\n      case starlight::TransformType::kSkewX: {\n        transform_operations.AppendSkew(item.p0.GetRawValue(), 0.0f);\n        break;\n      }\n      case starlight::TransformType::kSkewY: {\n        transform_operations.AppendSkew(0.0f, item.p0.GetRawValue());\n        break;\n      }\n      case starlight::TransformType::kMatrix:\n      case starlight::TransformType::kMatrix3d: {\n        transform_operations.AppendMatrix(item.type, item.matrix);\n        break;\n      }\n      default: {\n        break;\n      }\n    }\n  }\n}\n\nTransformOperations::TransformOperations(tasm::Element* element)\n    : element_(element) {}\n\n// Construct a transform operations with transform data whose type is\n// tasm::CSSValue. The transform data should be parsed by\n// starlight::CSSStyleUtils::ComputeTransform before using it to initialize a\n// transform operations.\nTransformOperations::TransformOperations(tasm::Element* element,\n                                         const tasm::CSSValue& raw_data)\n    : element_(element) {\n  auto transform_data = base::make_flex_optional(\n      base::InlineVector<starlight::TransformRawData, 1>());\n  if (!starlight::CSSStyleUtils::ComputeTransform(\n          raw_data, false, transform_data,\n          animation::CSSKeyframeManager::GetLengthContext(element),\n          element->element_manager()->GetCSSParserConfigs())) {\n    return;\n  }\n  InitializeTransformOperations(*this, *transform_data);\n}\n\nTransformOperations::TransformOperations(\n    starlight::LayoutResultForRendering layout_result,\n    base::InlineVector<starlight::TransformRawData, 1> transform_raw_data)\n    : element_(nullptr), element_layout_result_(layout_result) {\n  InitializeTransformOperations(*this, transform_raw_data);\n}\n\nTransformOperations::TransformOperations(const TransformOperations& other) {\n  operations_ = other.operations_;\n  element_ = other.element_;\n}\n\nTransformOperations::~TransformOperations() = default;\n\nTransformOperations& TransformOperations::operator=(\n    const TransformOperations& other) {\n  operations_ = other.operations_;\n  element_ = other.element_;\n  return *this;\n}\n\nMatrix44 TransformOperations::ApplyRemaining(size_t start) {\n  Matrix44 to_return;\n  for (size_t i = start; i < operations_.size(); i++) {\n    to_return.preConcat(operations_[i].GetMatrix(element_));\n  }\n  return to_return;\n}\n\nMatrix44 TransformOperations::ApplyRemaining(\n    size_t start, starlight::LayoutResultForRendering layout_result) {\n  Matrix44 to_return;\n  for (size_t i = start; i < operations_.size(); i++) {\n    to_return.preConcat(operations_[i].GetMatrix(layout_result));\n  }\n  return to_return;\n}\n\nTransformOperations TransformOperations::Blend(TransformOperations& from,\n                                               float progress) {\n  TransformOperations to_return(this->element_);\n  if (!BlendInternal(from, progress, &to_return)) {\n    // If the matrices cannot be blended, fallback to discrete animation logic.\n    // See https://drafts.csswg.org/css-transforms/#matrix-interpolation\n    to_return = progress < 0.5 ? from : *this;\n  }\n  return to_return;\n}\n\nsize_t TransformOperations::MatchingPrefixLength(\n    const TransformOperations& other) const {\n  size_t num_operations =\n      std::min(operations_.size(), other.operations_.size());\n  for (size_t i = 0; i < num_operations; ++i) {\n    if (operations_[i].type != other.operations_[i].type) {\n      // Remaining operations in each operations list require matrix/matrix3d\n      // interpolation.\n      return i;\n    }\n    if (operations_[i].type == TransformOperation::kMatrix ||\n        operations_[i].type == TransformOperation::kMatrix3d) {\n      // The matched prefixes will interpolate with simple blend, but the matrix\n      // transform_operation cannot be simply blended, so matrix that are equal\n      // will not be treated as matched prefixes.\n      return i;\n    }\n  }\n  // If the operations match to the length of the shorter list, then pad its\n  // length with the matching identity operations.\n  // https://drafts.csswg.org/css-transforms/#transform-function-lists\n  return std::max(operations_.size(), other.operations_.size());\n}\n\nbool TransformOperations::IsIdentity() const {\n  for (auto& operation : operations_) {\n    if (!operation.IsIdentity()) return false;\n  }\n  return true;\n}\n\nvoid TransformOperations::Append(const TransformOperation& operation) {\n  operations_.push_back(operation);\n  decomposed_transforms_.clear();\n}\n\nvoid TransformOperations::AppendTranslate(\n    starlight::NLength x_value, TransformOperation::LengthType x_type,\n    starlight::NLength y_value, TransformOperation::LengthType y_type,\n    starlight::NLength z_value, TransformOperation::LengthType z_type) {\n  TransformOperation op;\n  op.type = TransformOperation::Type::kTranslate;\n  op.translate.type.x = x_type;\n  op.translate.type.y = y_type;\n  op.translate.type.z = z_type;\n  op.translate.value.x = x_value;\n  op.translate.value.y = y_value;\n  op.translate.value.z = z_value;\n  Append(op);\n}\n\nvoid TransformOperations::AppendTranslateUnitType(\n    lynx::starlight::TransformRawData& raw_data) {\n  operations_.front().unit_type_0_ = raw_data.unit_type0;\n  operations_.front().unit_type_1_ = raw_data.unit_type1;\n  operations_.front().unit_type_2_ = raw_data.unit_type2;\n}\n\nvoid TransformOperations::AppendRotate(TransformOperation::Type type,\n                                       float degree) {\n  TransformOperation op;\n  op.type = type;\n  op.rotate.degree = degree;\n  Append(op);\n}\nvoid TransformOperations::AppendScale(float x, float y) {\n  TransformOperation op;\n  op.type = TransformOperation::Type::kScale;\n  op.scale.x = x;\n  op.scale.y = y;\n  Append(op);\n}\nvoid TransformOperations::AppendSkew(float x, float y) {\n  TransformOperation op;\n  op.type = TransformOperation::Type::kSkew;\n  op.skew.x = x;\n  op.skew.y = y;\n  Append(op);\n}\nvoid TransformOperations::AppendMatrix(\n    starlight::TransformType type,\n    const std::array<double, 16>& raw_matrix_data) {\n  TransformOperation op;\n  op.type = type == starlight::TransformType::kMatrix\n                ? TransformOperation::kMatrix\n                : TransformOperation::kMatrix3d;\n\n  std::array<float, 16> temp_matrix;\n  std::transform(raw_matrix_data.begin(), raw_matrix_data.end(),\n                 temp_matrix.begin(),\n                 [](double d) { return static_cast<float>(d); });\n  op.matrix.matrix_data = temp_matrix;\n  Append(op);\n}\n\nvoid TransformOperations::AppendDecomposedTransform(\n    const DecomposedTransform& decomposed) {\n  AppendTranslate(starlight::NLength::MakeUnitNLength(decomposed.translate[0]),\n                  TransformOperation::LengthType::kLengthUnit,\n                  starlight::NLength::MakeUnitNLength(decomposed.translate[1]),\n                  TransformOperation::LengthType::kLengthUnit,\n                  starlight::NLength::MakeUnitNLength(decomposed.translate[2]),\n                  TransformOperation::LengthType::kLengthUnit);\n\n  Euler euler = decomposed.quaternion.ConvertToEuler();\n  AppendRotate(TransformOperation::Type::kRotateX, RadToDeg(euler.x));\n  AppendRotate(TransformOperation::Type::kRotateY, RadToDeg(euler.y));\n  AppendRotate(TransformOperation::Type::kRotateZ, RadToDeg(euler.z));\n\n  AppendSkew(RadToDeg(atan(decomposed.skew[0])), 0);\n\n  AppendScale(decomposed.scale[0], decomposed.scale[1]);\n}\n\nbool TransformOperations::BlendInternal(TransformOperations& from,\n                                        float progress,\n                                        TransformOperations* result) {\n  bool from_identity = from.IsIdentity();\n  bool to_identity = IsIdentity();\n  if (from_identity && to_identity) return true;\n\n  size_t matching_prefix_length = MatchingPrefixLength(from);\n  size_t from_size = from_identity ? 0 : from.operations_.size();\n  size_t to_size = to_identity ? 0 : operations_.size();\n  size_t num_operations = std::max(from_size, to_size);\n\n  for (size_t i = 0; i < matching_prefix_length; ++i) {\n    TransformOperation blended = TransformOperation::BlendTransformOperations(\n        i >= from_size ? nullptr : &from.operations_[i],\n        i >= to_size ? nullptr : &operations_[i], progress, element_);\n    result->Append(blended);\n  }\n\n  if (matching_prefix_length < num_operations) {\n    if (!ComputeDecomposedTransform(matching_prefix_length) ||\n        !from.ComputeDecomposedTransform(matching_prefix_length)) {\n      return false;\n    }\n    DecomposedTransform matrix_transform = BlendDecomposedTransforms(\n        *decomposed_transforms_[matching_prefix_length],\n        *from.decomposed_transforms_[matching_prefix_length], progress);\n    result->AppendDecomposedTransform(matrix_transform);\n  }\n  return true;\n}\n\nbool TransformOperations::ComputeDecomposedTransform(size_t start_offset) {\n  auto it = decomposed_transforms_.find(start_offset);\n  if (it == decomposed_transforms_.end()) {\n    std::unique_ptr<DecomposedTransform> decomposed_transform =\n        std::make_unique<DecomposedTransform>();\n    Matrix44 transform = ApplyRemaining(start_offset);\n    if (!DecomposeTransform(decomposed_transform.get(), transform)) {\n      return false;\n    }\n    decomposed_transforms_[start_offset] = std::move(decomposed_transform);\n  }\n  return true;\n}\n\nvoid TransformOperations::NotifyElementSizeUpdated() {\n  bool need_update = false;\n  for (auto& op : operations_) {\n    need_update = need_update || op.NotifyElementSizeUpdated();\n  }\n  if (need_update) {\n    decomposed_transforms_.clear();\n  }\n}\n\nvoid TransformOperations::NotifyUnitValuesUpdatedToAnimation(\n    tasm::CSSValuePattern type) {\n  bool need_update = false;\n  for (auto& op : operations_) {\n    need_update = need_update || op.NotifyUnitValuesUpdatedToAnimation(type);\n  }\n  if (need_update) {\n    decomposed_transforms_.clear();\n    operations_.clear();\n  }\n}\n\n// A method using for converting transform operations to transform raw data.\n// Transform operations will be used for animation calculations. After the\n// calculation is over, use this method to convert operations to raw data and\n// update it on element.\ntasm::CSSValue TransformOperations::ToTransformRawValue() {\n  auto items = lepus::CArray::Create();\n  for (auto& op : operations_) {\n    switch (op.type) {\n      case TransformOperation::Type::kTranslate: {\n        auto item = lepus::CArray::Create();\n        item->emplace_back(\n            static_cast<int>(starlight::TransformType::kTranslate3d));\n        item->emplace_back(op.translate.value.x.GetRawValue());\n        item->emplace_back(static_cast<int>(\n            op.translate.type.x ==\n                    TransformOperation::LengthType::kLengthPercentage\n                ? tasm::CSSValuePattern::PERCENT\n                : tasm::CSSValuePattern::NUMBER));\n        item->emplace_back(op.translate.value.y.GetRawValue());\n        item->emplace_back(static_cast<int>(\n            op.translate.type.y ==\n                    TransformOperation::LengthType::kLengthPercentage\n                ? tasm::CSSValuePattern::PERCENT\n                : tasm::CSSValuePattern::NUMBER));\n        item->emplace_back(op.translate.value.z.GetRawValue());\n        item->emplace_back(static_cast<int>(\n            op.translate.type.z ==\n                    TransformOperation::LengthType::kLengthPercentage\n                ? tasm::CSSValuePattern::PERCENT\n                : tasm::CSSValuePattern::NUMBER));\n        items->emplace_back(std::move(item));\n        break;\n      }\n      case TransformOperation::Type::kRotateX: {\n        auto item = lepus::CArray::Create();\n        item->emplace_back(\n            static_cast<int>(starlight::TransformType::kRotateX));\n        item->emplace_back(op.rotate.degree);\n        items->emplace_back(std::move(item));\n        break;\n      }\n      case TransformOperation::Type::kRotateY: {\n        auto item = lepus::CArray::Create();\n        item->emplace_back(\n            static_cast<int>(starlight::TransformType::kRotateY));\n        item->emplace_back(op.rotate.degree);\n        items->emplace_back(std::move(item));\n        break;\n      }\n      case TransformOperation::Type::kRotateZ: {\n        auto item = lepus::CArray::Create();\n        item->emplace_back(\n            static_cast<int>(starlight::TransformType::kRotateZ));\n        item->emplace_back(op.rotate.degree);\n        items->emplace_back(std::move(item));\n        break;\n      }\n      case TransformOperation::Type::kScale: {\n        auto item = lepus::CArray::Create();\n        item->emplace_back(static_cast<int>(starlight::TransformType::kScale));\n        item->emplace_back(op.scale.x);\n        item->emplace_back(op.scale.y);\n        items->emplace_back(std::move(item));\n        break;\n      }\n      case TransformOperation::Type::kSkew: {\n        auto item = lepus::CArray::Create();\n        item->emplace_back(static_cast<int>(starlight::TransformType::kSkew));\n        item->emplace_back(op.skew.x);\n        item->emplace_back(op.skew.y);\n        items->emplace_back(std::move(item));\n        break;\n      }\n      case TransformOperation::Type::kMatrix:\n      case TransformOperation::Type::kMatrix3d: {\n        auto item = lepus::CArray::Create();\n        item->emplace_back(\n            static_cast<int>(starlight::TransformType::kMatrix3d));\n\n        items->emplace_back(std::move(item));\n      }\n      default: {\n        break;\n      }\n    }\n  }\n  return tasm::CSSValue(std::move(items));\n}\n\nnamespace {\nstd::string Get2DRepresentation(const Matrix44& matrix) {\n  return \"matrix(\" + tasm::CSSDecoder::NumberToString(matrix.rc(0, 0)) + \", \" +\n         tasm::CSSDecoder::NumberToString(matrix.rc(1, 0)) + \", \" +\n         tasm::CSSDecoder::NumberToString(matrix.rc(0, 1)) + \", \" +\n         tasm::CSSDecoder::NumberToString(matrix.rc(1, 1)) + \", \" +\n         tasm::CSSDecoder::NumberToString(matrix.rc(0, 3)) + \", \" +\n         tasm::CSSDecoder::NumberToString(matrix.rc(1, 3)) + \")\";\n}\n\nstd::string Get3DRepresentation(const Matrix44& matrix) {\n  std::string res = \"matrix3d(\";\n  for (int col = 0; col < 4; ++col) {\n    for (int row = 0; row < 4; ++row) {\n      res = res + tasm::CSSDecoder::NumberToString(matrix.rc(row, col));\n      if (col != 3 || row != 3) {\n        res = res + \", \";\n      }\n    }\n  }\n  res = res + \")\";\n  return res;\n}\n}  // namespace\n\nbase::String TransformOperations::CssText() {\n  Matrix44 transform = ApplyRemaining(0, element_layout_result_);\n  if (transform.Is2dTransform()) {\n    return Get2DRepresentation(transform);\n  } else {\n    return Get3DRepresentation(transform);\n  }\n}\n\n}  // namespace transforms\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/transforms/transform_operations.h",
    "content": "// Copyright 2013 The Chromium Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_TRANSFORMS_TRANSFORM_OPERATIONS_H_\n#define CORE_RENDERER_CSS_TRANSFORMS_TRANSFORM_OPERATIONS_H_\n\n#include <memory>\n#include <unordered_map>\n#include <vector>\n\n#include \"core/renderer/css/css_value.h\"\n#include \"core/renderer/css/transforms/transform_operation.h\"\n#include \"core/renderer/starlight/types/layout_result.h\"\n#include \"core/style/transform_raw_data.h\"\n\nnamespace lynx {\nnamespace tasm {\nclass Element;\n}\nnamespace transforms {\n\nstruct DecomposedTransform;\nclass Matrix44;\n\n// Transform operations are a decomposed transformation matrix. It can be\n// applied to obtain a Transform at any time, and can be blended\n// intelligently with other transform operations, so long as they represent the\n// same decomposition. For example, if we have a transform that is made up of\n// a rotation followed by skew, it can be blended intelligently with another\n// transform made up of a rotation followed by a skew. Blending is possible if\n// we have two dissimilar sets of transform operations, but the effect may not\n// be what was intended. For more information, see the comments for the blend\n// function below.\nclass TransformOperations {\n public:\n  TransformOperations(tasm::Element* element);\n  // construct TransformOperations with transform raw data\n  TransformOperations(tasm::Element* element, const tasm::CSSValue& raw_data);\n  TransformOperations(\n      starlight::LayoutResultForRendering element_layout_result,\n      base::InlineVector<starlight::TransformRawData, 1> transform_raw_data);\n  TransformOperations(const TransformOperations& other);\n  ~TransformOperations();\n\n  TransformOperations& operator=(const TransformOperations& other);\n\n  static void InitializeTransformOperations(\n      TransformOperations& transform_operations,\n      base::Vector<lynx::starlight::TransformRawData>& transform_raw_data);\n\n  // Returns a transformation matrix representing the set of transform\n  // operations from index |start| to the end of the list.\n  Matrix44 ApplyRemaining(size_t start);\n\n  // Returns a transformation matrix representing the set of transform\n  // operations from index |start| to the end of the list.\n  Matrix44 ApplyRemaining(\n      size_t start, starlight::LayoutResultForRendering element_layout_result);\n\n  // Given another set of transform operations and a progress in the range\n  // [0, 1], returns a transformation matrix representing the intermediate\n  // value. If this->MatchesTypes(from), then each of the operations are\n  // blended separately and then combined. Otherwise, the two sets of\n  // transforms are baked to matrices (using apply), and the matrices are\n  // then decomposed and interpolated. For more information, see\n  // http://www.w3.org/TR/2011/WD-css3-2d-transforms-20111215/#matrix-decomposition.\n  //\n  // If either of the matrices are non-decomposable for the blend, Blend applies\n  // discrete interpolation between them based on the progress value.\n  TransformOperations Blend(TransformOperations& from, float progress);\n\n  // Returns the number of matching transform operations at the start of the\n  // transform lists. If one list is shorter but pairwise compatible, it will be\n  // extended with matching identity operators per spec\n  // (https://drafts.csswg.org/css-transforms/#interpolation-of-transforms).\n  size_t MatchingPrefixLength(const TransformOperations& other) const;\n\n  bool IsIdentity() const;\n  const std::vector<TransformOperation>& GetOperations() { return operations_; }\n  size_t size() const { return operations_.size(); }\n\n  void NotifyElementSizeUpdated();\n\n  void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern);\n\n  void Append(const TransformOperation& operation);\n  void AppendDecomposedTransform(const DecomposedTransform& operation);\n  void AppendTranslate(starlight::NLength x_value,\n                       TransformOperation::LengthType x_type,\n                       starlight::NLength y_value,\n                       TransformOperation::LengthType y_type,\n                       starlight::NLength z_value,\n                       TransformOperation::LengthType z_type);\n  void AppendTranslateUnitType(lynx::starlight::TransformRawData&);\n  void AppendRotate(TransformOperation::Type type, float degree);\n  void AppendScale(float x, float y);\n  void AppendSkew(float x, float y);\n  void AppendMatrix(starlight::TransformType type,\n                    const std::array<double, 16>& raw_matrix_data);\n\n  tasm::CSSValue ToTransformRawValue();\n\n  base::String CssText();\n\n private:\n  bool BlendInternal(TransformOperations& from, float progress,\n                     TransformOperations* result);\n\n  std::vector<TransformOperation> operations_;\n\n  bool ComputeDecomposedTransform(size_t start_offset);\n\n  // For efficiency, we cache the decomposed transforms.\n  mutable std::unordered_map<size_t, std::unique_ptr<DecomposedTransform>>\n      decomposed_transforms_;\n  tasm::Element* element_{nullptr};\n  starlight::LayoutResultForRendering element_layout_result_{};\n};\n\n}  // namespace transforms\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_TRANSFORMS_TRANSFORM_OPERATIONS_H_\n"
  },
  {
    "path": "core/renderer/css/transforms/transform_operations_unittest.cc",
    "content": "// Copyright 2013 The Chromium Authors\n// Use of this source code is governed by a BSD-style license that can be\n// found in the LICENSE file.\n\n// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/transforms/transform_operations.h\"\n\n#include <cmath>\n\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/css/parser/transform_handler.h\"\n#include \"core/renderer/css/transforms/transform_operation.h\"\n#include \"core/renderer/css/unit_handler.h\"\n#include \"core/renderer/dom/element.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/dom/vdom/radon/radon_component.h\"\n#include \"core/renderer/starlight/style/css_type.h\"\n#include \"core/renderer/starlight/types/nlength.h\"\n#include \"core/renderer/tasm/react/testing/mock_painting_context.h\"\n#include \"core/shell/tasm_operation_queue.h\"\n#include \"core/shell/testing/mock_tasm_delegate.h\"\n#include \"core/style/transform/decomposed_transform.h\"\n#include \"core/style/transform/matrix44.h\"\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\nnamespace transforms {\nnamespace testing {\nnamespace {\n\nvoid CompareMatrix44(const Matrix44& a, const Matrix44& b) {\n  for (int row = 0; row < 4; ++row) {\n    for (int col = 0; col < 4; ++col) {\n      EXPECT_FLOAT_EQ(a.rc(row, col), b.rc(row, col));\n    }\n  }\n}\n\nMatrix44 BuildPerspectiveMatrix(const DecomposedTransform& decomposed) {\n  Matrix44 matrix;\n  for (int i = 0; i < 4; i++) matrix.setRC(3, i, decomposed.perspective[i]);\n  return matrix;\n}\n\nMatrix44 BuildTranslationMatrix(const DecomposedTransform& decomposed) {\n  Matrix44 matrix;\n  matrix.preTranslate(decomposed.translate[0], decomposed.translate[1],\n                      decomposed.translate[2]);\n  return matrix;\n}\n\nMatrix44 BuildRotationMatrix(const DecomposedTransform& decomposed) {\n  Quaternion q = decomposed.quaternion;\n  Matrix44 matrix(\n      // Row 1.\n      (1.0 - 2.0 * (q.y() * q.y() + q.z() * q.z())),\n      (2.0 * (q.x() * q.y() - q.z() * q.w())),\n      (2.0 * (q.x() * q.z() + q.y() * q.w())), 0,\n      // Row 2.\n      (2.0 * (q.x() * q.y() + q.z() * q.w())),\n      (1.0 - 2.0 * (q.x() * q.x() + q.z() * q.z())),\n      (2.0 * (q.y() * q.z() - q.x() * q.w())), 0,\n      // Row 3.\n      (2.0 * (q.x() * q.z() - q.y() * q.w())),\n      (2.0 * (q.y() * q.z() + q.x() * q.w())),\n      (1.0 - 2.0 * (q.x() * q.x() + q.y() * q.y())), 0,\n      // row 4.\n      0, 0, 0, 1);\n  return matrix;\n}\n\nMatrix44 BuildSkewMatrix(const DecomposedTransform& decomposed) {\n  Matrix44 matrix;\n\n  Matrix44 temp;\n  if (decomposed.skew[2]) {\n    temp.setRC(1, 2, decomposed.skew[2]);\n    matrix.preConcat(temp);\n  }\n\n  if (decomposed.skew[1]) {\n    temp.setRC(1, 2, 0);\n    temp.setRC(0, 2, decomposed.skew[1]);\n    matrix.preConcat(temp);\n  }\n\n  if (decomposed.skew[0]) {\n    temp.setRC(0, 2, 0);\n    temp.setRC(0, 1, decomposed.skew[0]);\n    matrix.preConcat(temp);\n  }\n  return matrix;\n}\n\nMatrix44 BuildScaleMatrix(const DecomposedTransform& decomposed) {\n  Matrix44 matrix;\n  matrix.preScale((decomposed.scale[0]), (decomposed.scale[1]),\n                  (decomposed.scale[2]));\n  return matrix;\n}\n\nMatrix44 ComposeTransform(DecomposedTransform& decomposed) {\n  Matrix44 perspective = BuildPerspectiveMatrix(decomposed);\n  Matrix44 translation = BuildTranslationMatrix(decomposed);\n  Matrix44 rotation = BuildRotationMatrix(decomposed);\n  Matrix44 skew = BuildSkewMatrix(decomposed);\n  Matrix44 scale = BuildScaleMatrix(decomposed);\n\n  Matrix44 matrix;\n  matrix.preConcat(perspective);\n  matrix.preConcat(translation);\n  matrix.preConcat(rotation);\n  matrix.preConcat(skew);\n  matrix.preConcat(scale);\n  return matrix;\n}\n\nstatic void CheckProgress(float progress, const Matrix44& from_matrix,\n                          const Matrix44& to_matrix,\n                          TransformOperations& from_transform,\n                          TransformOperations& to_transform) {\n  DecomposedTransform decomposed_from = DecomposedTransform();\n  DecomposedTransform decomposed_to = DecomposedTransform();\n  DecomposeTransform(&decomposed_from, from_matrix);\n  DecomposeTransform(&decomposed_to, to_matrix);\n  DecomposedTransform blended =\n      BlendDecomposedTransforms(decomposed_to, decomposed_from, progress);\n  Matrix44 matrix0 = ComposeTransform(blended);\n  Matrix44 matrix1 =\n      to_transform.Blend(from_transform, progress).ApplyRemaining(0);\n  CompareMatrix44(matrix0, matrix1);\n}\n\nstd::vector<std::unique_ptr<TransformOperations>> GetIdentityOperations(\n    lynx::tasm::ElementManager* manager) {\n  auto element = manager->CreateFiberElement(\"view\");\n  std::vector<std::unique_ptr<TransformOperations>> operations;\n  std::unique_ptr<TransformOperations> to_add(\n      std::make_unique<TransformOperations>(element.get()));\n  operations.push_back(std::move(to_add));\n\n  to_add = std::make_unique<TransformOperations>(element.get());\n  auto zero_unit_nlength = starlight::NLength::MakeUnitNLength(0.0f);\n  to_add->AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  operations.push_back(std::move(to_add));\n\n  to_add = std::make_unique<TransformOperations>(element.get());\n  to_add->AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  to_add->AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  operations.push_back(std::move(to_add));\n\n  to_add = std::make_unique<TransformOperations>(element.get());\n  to_add->AppendScale(1, 1);\n  operations.push_back(std::move(to_add));\n\n  to_add = std::make_unique<TransformOperations>(element.get());\n  to_add->AppendScale(1, 1);\n  to_add->AppendScale(1, 1);\n  operations.push_back(std::move(to_add));\n\n  to_add = std::make_unique<TransformOperations>(element.get());\n  to_add->AppendSkew(0, 0);\n  operations.push_back(std::move(to_add));\n\n  to_add = std::make_unique<TransformOperations>(element.get());\n  to_add->AppendSkew(0, 0);\n  to_add->AppendSkew(0, 0);\n  operations.push_back(std::move(to_add));\n\n  to_add = std::make_unique<TransformOperations>(element.get());\n  to_add->AppendRotate(TransformOperation::Type::kRotateX, 0);\n  operations.push_back(std::move(to_add));\n\n  to_add = std::make_unique<TransformOperations>(element.get());\n  to_add->AppendRotate(TransformOperation::Type::kRotateY, 0);\n  operations.push_back(std::move(to_add));\n\n  to_add = std::make_unique<TransformOperations>(element.get());\n  to_add->AppendRotate(TransformOperation::Type::kRotateZ, 0);\n  operations.push_back(std::move(to_add));\n\n  return operations;\n}\n\n}  // namespace\n\nstatic constexpr int32_t kWidth = 1080;\nstatic constexpr int32_t kHeight = 1920;\nstatic constexpr float kDefaultLayoutsUnitPerPx = 1.f;\nstatic constexpr double kDefaultPhysicalPixelsPerLayoutUnit = 1.f;\nclass TransformOperationsTest : public ::testing::Test {\n public:\n  TransformOperationsTest() {}\n  ~TransformOperationsTest() override {}\n  std::unique_ptr<lynx::tasm::ElementManager> manager;\n  std::shared_ptr<::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>\n      tasm_mediator;\n  fml::RefPtr<lynx::tasm::Element> element;\n\n  void SetUp() override {\n    tasm::LynxEnvConfig lynx_env_config(kWidth, kHeight,\n                                        kDefaultLayoutsUnitPerPx,\n                                        kDefaultPhysicalPixelsPerLayoutUnit);\n    tasm_mediator = std::make_shared<\n        ::testing::NiceMock<lynx::tasm::test::MockTasmDelegate>>();\n    manager = std::make_unique<lynx::tasm::ElementManager>(\n        std::make_unique<tasm::MockPaintingContext>(), tasm_mediator.get(),\n        lynx_env_config);\n    auto config = std::make_shared<tasm::PageConfig>();\n    config->SetEnableZIndex(true);\n    manager->SetConfig(config);\n    element = manager->CreateFiberElement(\"view\");\n  }\n};\n\nTEST_F(TransformOperationsTest, MatchingPrefixSameLength) {\n  TransformOperations translates(element.get());\n  auto zero_unit_nlength = starlight::NLength::MakeUnitNLength(0.0f);\n  auto one_unit_nlength = starlight::NLength::MakeUnitNLength(1.0f);\n  auto two_unit_nlength = starlight::NLength::MakeUnitNLength(2.0f);\n  translates.AppendTranslate(\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  translates.AppendTranslate(\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  translates.AppendTranslate(\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n\n  TransformOperations skews(element.get());\n  skews.AppendSkew(0, 2);\n  skews.AppendSkew(0, 2);\n  skews.AppendSkew(0, 2);\n\n  TransformOperations translates2(element.get());\n  translates2.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      two_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  translates2.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      two_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  translates2.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      two_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n\n  TransformOperations mixed(element.get());\n  mixed.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      two_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  mixed.AppendScale(2, 1);\n  mixed.AppendSkew(0, 2);\n\n  TransformOperations translates3 = translates2;\n\n  EXPECT_EQ(0UL, translates.MatchingPrefixLength(skews));\n  EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates2));\n  EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates3));\n  EXPECT_EQ(1UL, translates.MatchingPrefixLength(mixed));\n}\n\nTEST_F(TransformOperationsTest, MatchingPrefixDifferentLength) {\n  auto zero_unit_nlength = starlight::NLength::MakeUnitNLength(0.0f);\n  auto one_unit_nlength = starlight::NLength::MakeUnitNLength(1.0f);\n  auto two_unit_nlength = starlight::NLength::MakeUnitNLength(2.0f);\n  TransformOperations translates(element.get());\n  translates.AppendTranslate(\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  translates.AppendTranslate(\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  translates.AppendTranslate(\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n\n  TransformOperations skews(element.get());\n  skews.AppendSkew(2, 0);\n  skews.AppendSkew(2, 0);\n\n  TransformOperations translates2(element.get());\n  translates2.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      two_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  translates2.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      two_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n\n  TransformOperations none(element.get());\n\n  EXPECT_EQ(0UL, translates.MatchingPrefixLength(skews));\n  // Pad the length of the shorter list provided all previous operation-\n  // pairs match per spec\n  // (https://drafts.csswg.org/css-transforms/#interpolation-of-transforms).\n  EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates2));\n  EXPECT_EQ(3UL, translates.MatchingPrefixLength(none));\n}\n\nTEST_F(TransformOperationsTest, MatchingPrefixLengthOrder) {\n  auto zero_unit_nlength = starlight::NLength::MakeUnitNLength(0.0f);\n  auto one_unit_nlength = starlight::NLength::MakeUnitNLength(1.0f);\n  TransformOperations mix_order_identity(element.get());\n  mix_order_identity.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  mix_order_identity.AppendScale(1, 1);\n  mix_order_identity.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n\n  TransformOperations mix_order_one(element.get());\n  mix_order_one.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  mix_order_one.AppendScale(2, 1);\n  mix_order_one.AppendTranslate(\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n\n  TransformOperations mix_order_two(element.get());\n  mix_order_two.AppendTranslate(\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  mix_order_two.AppendTranslate(\n      one_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit,\n      zero_unit_nlength, TransformOperation::LengthType::kLengthUnit);\n  mix_order_two.AppendScale(2, 1);\n\n  EXPECT_EQ(3UL, mix_order_identity.MatchingPrefixLength(mix_order_one));\n  EXPECT_EQ(1UL, mix_order_identity.MatchingPrefixLength(mix_order_two));\n  EXPECT_EQ(1UL, mix_order_one.MatchingPrefixLength(mix_order_two));\n}\n\nTEST_F(TransformOperationsTest, NoneAlwaysMatches) {\n  std::vector<std::unique_ptr<TransformOperations>> operations =\n      GetIdentityOperations(manager.get());\n\n  TransformOperations none_operation(element.get());\n  for (const auto& operation : operations) {\n    EXPECT_EQ(operation->size(),\n              operation->MatchingPrefixLength(none_operation));\n  }\n}\n\nTEST_F(TransformOperationsTest, ApplyOrder) {\n  float sx = 2;\n  float sy = 4;\n  float sz = 1;\n\n  float dx = 1;\n  float dy = 2;\n  float dz = 3;\n\n  TransformOperations operations(element.get());\n  operations.AppendScale(sx, sy);\n  operations.AppendTranslate(starlight::NLength::MakeUnitNLength(dx),\n                             TransformOperation::LengthType::kLengthUnit,\n                             starlight::NLength::MakeUnitNLength(dy),\n                             TransformOperation::LengthType::kLengthUnit,\n                             starlight::NLength::MakeUnitNLength(dz),\n                             TransformOperation::LengthType::kLengthUnit);\n\n  Matrix44 expected_scale_matrix;\n  expected_scale_matrix.preScale(sx, sy, sz);\n\n  Matrix44 expected_translate_matrix;\n  expected_translate_matrix.preTranslate(dx, dy, dz);\n\n  Matrix44 expected_combined_matrix = expected_scale_matrix;\n  expected_combined_matrix.preConcat(expected_translate_matrix);\n\n  CompareMatrix44(expected_combined_matrix, operations.ApplyRemaining(0));\n}\n\nTEST_F(TransformOperationsTest, BlendProgress) {\n  float sx = 2;\n  float sy = 4;\n  float sz = 1;\n  TransformOperations operations_from(element.get());\n  operations_from.AppendScale(sx, sy);\n\n  Matrix44 matrix_from;\n  matrix_from.preScale(sx, sy, sz);\n\n  sx = 4;\n  sy = 8;\n  sz = 1;\n  TransformOperations operations_to(element.get());\n  operations_to.AppendScale(sx, sy);\n\n  Matrix44 matrix_to;\n  matrix_to.preScale(sx, sy, sz);\n\n  CheckProgress(-1, matrix_from, matrix_to, operations_from, operations_to);\n  CheckProgress(0, matrix_from, matrix_to, operations_from, operations_to);\n  CheckProgress(0.25f, matrix_from, matrix_to, operations_from, operations_to);\n  CheckProgress(0.5f, matrix_from, matrix_to, operations_from, operations_to);\n  CheckProgress(1, matrix_from, matrix_to, operations_from, operations_to);\n  CheckProgress(2, matrix_from, matrix_to, operations_from, operations_to);\n}\n\nTEST_F(TransformOperationsTest, BlendWhenTypesDoNotMatch) {\n  float sx1 = 2;\n  float sy1 = 4;\n  float sz1 = 1;\n\n  starlight::NLength dx1 = starlight::NLength::MakeUnitNLength(1.0f);\n  starlight::NLength dy1 = starlight::NLength::MakeUnitNLength(2.0f);\n  starlight::NLength dz1 = starlight::NLength::MakeUnitNLength(3.0f);\n\n  float sx2 = 4;\n  float sy2 = 8;\n  float sz2 = 1;\n\n  starlight::NLength dx2 = starlight::NLength::MakeUnitNLength(10.0f);\n  starlight::NLength dy2 = starlight::NLength::MakeUnitNLength(20.0f);\n  starlight::NLength dz2 = starlight::NLength::MakeUnitNLength(30.0f);\n\n  TransformOperations operations_from(element.get());\n  operations_from.AppendScale(sx1, sy1);\n  operations_from.AppendTranslate(\n      dx1, TransformOperation::LengthType::kLengthUnit, dy1,\n      TransformOperation::LengthType::kLengthUnit, dz1,\n      TransformOperation::LengthType::kLengthUnit);\n\n  TransformOperations operations_to(element.get());\n  operations_to.AppendTranslate(\n      dx2, TransformOperation::LengthType::kLengthUnit, dy2,\n      TransformOperation::LengthType::kLengthUnit, dz2,\n      TransformOperation::LengthType::kLengthUnit);\n  operations_to.AppendScale(sx2, sy2);\n\n  Matrix44 from;\n  from.preScale(sx1, sy1, sz1);\n  from.preTranslate(dx1.GetRawValue(), dy1.GetRawValue(), dz1.GetRawValue());\n\n  Matrix44 to;\n  to.preTranslate(dx2.GetRawValue(), dy2.GetRawValue(), dz2.GetRawValue());\n  to.preScale(sx2, sy2, sz2);\n\n  float progress = 0.25f;\n  CheckProgress(progress, from, to, operations_from, operations_to);\n}\n\nTEST_F(TransformOperationsTest,\n       ConstructWithCSSValueRawDataAndToTransformRawValue) {\n  auto id = tasm::CSSPropertyID::kPropertyIDTransform;\n  tasm::StyleMap output;\n  tasm::CSSParserConfigs configs;\n  auto impl =\n      lepus::Value(\"translate3D(1rem, 2rem, 3rem) scale(0.1) rotate(10deg)\");\n  bool ret = tasm::UnitHandler::Process(id, impl, output, configs);\n\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  auto raw_value = output[id];\n\n  {\n    TransformOperations operations(element.get(), raw_value);\n    EXPECT_EQ(operations.size(), static_cast<size_t>(3));\n    auto result_value = operations.ToTransformRawValue();\n    EXPECT_TRUE(result_value.IsArray());\n    auto arr = result_value.GetArray();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(3));\n    auto item = arr->get(0).Array().strongify();\n    EXPECT_EQ(item->size(), static_cast<size_t>(7));\n    EXPECT_EQ(item->get(0).Number(),\n              (int)starlight::TransformType::kTranslate3d);\n    EXPECT_EQ(item->get(1).Number(), 14);\n    EXPECT_EQ(item->get(2).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(3).Number(), 28);\n    EXPECT_EQ(item->get(4).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(5).Number(), 42);\n    EXPECT_EQ(item->get(6).Number(), (int)tasm::CSSValuePattern::NUMBER);\n\n    item = arr->get(1).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(3));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kScale);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 0.1);\n    EXPECT_FLOAT_EQ(item->get(2).Number(), 0.1);\n\n    item = arr->get(2).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(2));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateZ);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 10);\n  }\n\n  impl = lepus::Value(\"scaleY(0.1) translate(100%, 1rem)\");\n  ret = tasm::UnitHandler::Process(id, impl, output, configs);\n\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  raw_value = output[id];\n  {\n    TransformOperations operations(element.get(), raw_value);\n    EXPECT_EQ(operations.size(), static_cast<size_t>(2));\n    auto result_value = operations.ToTransformRawValue();\n    EXPECT_TRUE(result_value.IsArray());\n    auto arr = result_value.GetArray();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n    auto item = arr->get(0).Array().strongify();\n    EXPECT_EQ(item->size(), static_cast<size_t>(3));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kScale);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 1);\n    EXPECT_FLOAT_EQ(item->get(2).Number(), 0.1);\n\n    item = arr->get(1).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(7));\n    EXPECT_EQ(item->get(0).Number(),\n              (int)starlight::TransformType::kTranslate3d);\n    EXPECT_EQ(item->get(1).Number(), 100);\n    EXPECT_EQ(item->get(2).Number(), (int)tasm::CSSValuePattern::PERCENT);\n    EXPECT_EQ(item->get(3).Number(), 14);\n    EXPECT_EQ(item->get(4).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(5).Number(), 0);\n    EXPECT_EQ(item->get(6).Number(), (int)tasm::CSSValuePattern::NUMBER);\n  }\n\n  impl = lepus::Value(\"rotateY(36deg) translateY(50%)\");\n  ret = tasm::UnitHandler::Process(id, impl, output, configs);\n\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  raw_value = output[id];\n  {\n    TransformOperations operations(element.get(), raw_value);\n    EXPECT_EQ(operations.size(), static_cast<size_t>(2));\n    auto result_value = operations.ToTransformRawValue();\n    EXPECT_TRUE(result_value.IsArray());\n    auto arr = result_value.GetArray();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(2));\n    auto item = arr->get(0).Array().strongify();\n    EXPECT_EQ(item->size(), static_cast<size_t>(2));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateY);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 36);\n\n    item = arr->get(1).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(7));\n    EXPECT_EQ(item->get(0).Number(),\n              (int)starlight::TransformType::kTranslate3d);\n    EXPECT_EQ(item->get(1).Number(), 0);\n    EXPECT_EQ(item->get(2).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(3).Number(), 50);\n    EXPECT_EQ(item->get(4).Number(), (int)tasm::CSSValuePattern::PERCENT);\n    EXPECT_EQ(item->get(5).Number(), 0);\n    EXPECT_EQ(item->get(6).Number(), (int)tasm::CSSValuePattern::NUMBER);\n  }\n\n  impl = lepus::Value(\"aaa hahaha\");\n  output.clear();\n  ret = tasm::UnitHandler::Process(id, impl, output, configs);\n\n  EXPECT_FALSE(ret);\n  EXPECT_TRUE(output.empty());\n  raw_value = tasm::CSSValue();\n  {\n    TransformOperations operations(element.get(), raw_value);\n    EXPECT_EQ(operations.size(), static_cast<size_t>(0));\n    auto result_value = operations.ToTransformRawValue();\n    EXPECT_TRUE(result_value.IsArray());\n    auto arr = result_value.GetArray();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(0));\n  }\n\n  impl = lepus::Value(\n      \"translateX(50%) translateY(1rem) translateZ(2rem) rotateX(36deg) \"\n      \"rotateY(36deg) rotateZ(36deg) scaleX(0.5) scaleY(2) skewX(36deg) \"\n      \"skewY(36deg) translate(3rem, 25%) skew(18deg, 72deg)\");\n  ret = tasm::UnitHandler::Process(id, impl, output, configs);\n\n  EXPECT_TRUE(ret);\n  EXPECT_FALSE(output.empty());\n  EXPECT_FALSE(output.find(id) == output.end());\n  EXPECT_TRUE(output[id].IsArray());\n  raw_value = output[id];\n  {\n    TransformOperations operations(element.get(), raw_value);\n    EXPECT_EQ(operations.size(), static_cast<size_t>(12));\n    auto result_value = operations.ToTransformRawValue();\n    EXPECT_TRUE(result_value.IsArray());\n    auto arr = result_value.GetArray();\n    EXPECT_EQ(arr->size(), static_cast<size_t>(12));\n\n    auto item = arr->get(0).Array().strongify();\n    EXPECT_EQ(item->size(), static_cast<size_t>(7));\n    EXPECT_EQ(item->get(0).Number(),\n              (int)starlight::TransformType::kTranslate3d);\n    EXPECT_EQ(item->get(1).Number(), 50);\n    EXPECT_EQ(item->get(2).Number(), (int)tasm::CSSValuePattern::PERCENT);\n    EXPECT_EQ(item->get(3).Number(), 0);\n    EXPECT_EQ(item->get(4).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(5).Number(), 0);\n    EXPECT_EQ(item->get(6).Number(), (int)tasm::CSSValuePattern::NUMBER);\n\n    item = arr->get(1).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(7));\n    EXPECT_EQ(item->get(0).Number(),\n              (int)starlight::TransformType::kTranslate3d);\n    EXPECT_EQ(item->get(1).Number(), 0);\n    EXPECT_EQ(item->get(2).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(3).Number(), 14);\n    EXPECT_EQ(item->get(4).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(5).Number(), 0);\n    EXPECT_EQ(item->get(6).Number(), (int)tasm::CSSValuePattern::NUMBER);\n\n    item = arr->get(2).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(7));\n    EXPECT_EQ(item->get(0).Number(),\n              (int)starlight::TransformType::kTranslate3d);\n    EXPECT_EQ(item->get(1).Number(), 0);\n    EXPECT_EQ(item->get(2).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(3).Number(), 0);\n    EXPECT_EQ(item->get(4).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(5).Number(), 28);\n    EXPECT_EQ(item->get(6).Number(), (int)tasm::CSSValuePattern::NUMBER);\n\n    item = arr->get(3).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(2));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateX);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 36);\n\n    item = arr->get(4).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(2));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateY);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 36);\n\n    item = arr->get(5).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(2));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kRotateZ);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 36);\n\n    item = arr->get(6).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(3));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kScale);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 0.5);\n    EXPECT_FLOAT_EQ(item->get(2).Number(), 1);\n\n    item = arr->get(7).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(3));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kScale);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 1);\n    EXPECT_FLOAT_EQ(item->get(2).Number(), 2);\n\n    item = arr->get(8).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(3));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kSkew);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 36);\n    EXPECT_FLOAT_EQ(item->get(2).Number(), 0);\n\n    item = arr->get(9).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(3));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kSkew);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 0);\n    EXPECT_FLOAT_EQ(item->get(2).Number(), 36);\n\n    item = arr->get(10).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(7));\n    EXPECT_EQ(item->get(0).Number(),\n              (int)starlight::TransformType::kTranslate3d);\n    EXPECT_EQ(item->get(1).Number(), 42);\n    EXPECT_EQ(item->get(2).Number(), (int)tasm::CSSValuePattern::NUMBER);\n    EXPECT_EQ(item->get(3).Number(), 25);\n    EXPECT_EQ(item->get(4).Number(), (int)tasm::CSSValuePattern::PERCENT);\n    EXPECT_EQ(item->get(5).Number(), 0);\n    EXPECT_EQ(item->get(6).Number(), (int)tasm::CSSValuePattern::NUMBER);\n\n    item = arr->get(11).Array();\n    EXPECT_EQ(item->size(), static_cast<size_t>(3));\n    EXPECT_EQ(item->get(0).Number(), (int)starlight::TransformType::kSkew);\n    EXPECT_FLOAT_EQ(item->get(1).Number(), 18);\n    EXPECT_FLOAT_EQ(item->get(2).Number(), 72);\n  }\n}\n\n}  // namespace testing\n}  // namespace transforms\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/unit_handler.cc",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/unit_handler.h\"\n\n#include <cstdarg>\n#include <utility>\n\n#include \"base/include/log/logging.h\"\n#include \"base/include/no_destructor.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/build/gen/lynx_sub_error_code.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/parser/animation_direction_handler.h\"\n#include \"core/renderer/css/parser/animation_fill_mode_handler.h\"\n#include \"core/renderer/css/parser/animation_iteration_count_handler.h\"\n#include \"core/renderer/css/parser/animation_name_handler.h\"\n#include \"core/renderer/css/parser/animation_play_state_handler.h\"\n#include \"core/renderer/css/parser/animation_property_handler.h\"\n#include \"core/renderer/css/parser/animation_shorthand_handler.h\"\n#include \"core/renderer/css/parser/aspect_ratio_handler.h\"\n#include \"core/renderer/css/parser/auto_font_size_handler.h\"\n#include \"core/renderer/css/parser/auto_font_size_preset_sizes_handler.h\"\n#include \"core/renderer/css/parser/background_box_handler.h\"\n#include \"core/renderer/css/parser/background_clip_handler.h\"\n#include \"core/renderer/css/parser/background_image_handler.h\"\n#include \"core/renderer/css/parser/background_position_handler.h\"\n#include \"core/renderer/css/parser/background_repeat_handler.h\"\n#include \"core/renderer/css/parser/background_shorthand_handler.h\"\n#include \"core/renderer/css/parser/background_size_handler.h\"\n#include \"core/renderer/css/parser/bool_handler.h\"\n#include \"core/renderer/css/parser/border_handler.h\"\n#include \"core/renderer/css/parser/border_radius_handler.h\"\n#include \"core/renderer/css/parser/border_style_handler.h\"\n#include \"core/renderer/css/parser/border_width_handler.h\"\n#include \"core/renderer/css/parser/clip_path_handler.h\"\n#include \"core/renderer/css/parser/color_handler.h\"\n#include \"core/renderer/css/parser/cursor_handler.h\"\n#include \"core/renderer/css/parser/enum_handler.h\"\n#include \"core/renderer/css/parser/filter_handler.h\"\n#include \"core/renderer/css/parser/flex_flow_handler.h\"\n#include \"core/renderer/css/parser/flex_handler.h\"\n#include \"core/renderer/css/parser/font_feature_settings_handler.h\"\n#include \"core/renderer/css/parser/font_length_handler.h\"\n#include \"core/renderer/css/parser/font_variation_settings_handler.h\"\n#include \"core/renderer/css/parser/four_sides_shorthand_handler.h\"\n#include \"core/renderer/css/parser/gap_handler.h\"\n#include \"core/renderer/css/parser/grid_position_handler.h\"\n#include \"core/renderer/css/parser/grid_template_handler.h\"\n#include \"core/renderer/css/parser/handler_defines.h\"\n#include \"core/renderer/css/parser/length_handler.h\"\n#include \"core/renderer/css/parser/list_gap_handler.h\"\n#include \"core/renderer/css/parser/mask_shorthand_handler.h\"\n#include \"core/renderer/css/parser/number_handler.h\"\n#include \"core/renderer/css/parser/offset_rotate_handler.h\"\n#include \"core/renderer/css/parser/relative_align_handler.h\"\n#include \"core/renderer/css/parser/shadow_handler.h\"\n#include \"core/renderer/css/parser/string_handler.h\"\n#include \"core/renderer/css/parser/text_decoration_handler.h\"\n#include \"core/renderer/css/parser/text_stroke_handler.h\"\n#include \"core/renderer/css/parser/time_handler.h\"\n#include \"core/renderer/css/parser/timing_function_handler.h\"\n#include \"core/renderer/css/parser/transform_handler.h\"\n#include \"core/renderer/css/parser/transform_origin_handler.h\"\n#include \"core/renderer/css/parser/transition_shorthand_handler.h\"\n#include \"core/renderer/css/parser/vertical_align_handler.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nUnitHandler& UnitHandler::Instance() {\n  static base::NoDestructor<UnitHandler> instance;\n  return *instance;\n}\n\nvoid UnitHandler::ReportError(std::string error_msg, std::string fix_suggestion,\n                              CSSPropertyID key, const std::string& input) {\n  base::LynxError error(error::E_CSS_PARSER, std::move(error_msg),\n                        std::move(fix_suggestion), base::LynxErrorLevel::Error,\n                        true);\n  static const base::NoDestructor<std::string> first(\"css_property\");\n  static const base::NoDestructor<std::string> second(\"css_value\");\n  error.AddContextInfo(*first, CSSProperty::GetPropertyNameCStr(key));\n  error.AddContextInfo(*second, input);\n  base::ErrorStorage::GetInstance().SetError(std::move(error));\n}\n\nnamespace {\nvoid _CSSWarning(const char* fmt, va_list args) {\n  auto error_msg = base::FormatStringWithVaList(fmt, args);\n  LynxWarning(false, error::E_CSS_PARSER, error_msg);\n}\n}  // namespace\n\nbool UnitHandler::CSSWarningUnconditional(const char* fmt...) {\n  va_list args;\n  va_start(args, fmt);\n  _CSSWarning(fmt, args);\n  va_end(args);\n  return false;\n}\n\n/*\n * if a function calls CSSWarning() but is supposed to return immediately, do\n * things below like:\n * if(!CSSWarning(...)) {return false;}\n *  // then do something\n */\nbool UnitHandler::CSSWarning(bool expression, bool enable_css_strict_mode,\n                             const char* fmt, ...) {\n  if (UNLIKELY(!(expression))) {\n    if (enable_css_strict_mode) {\n      va_list args;\n      va_start(args, fmt);\n      _CSSWarning(fmt, args);\n      va_end(args);\n    }\n    return false;\n  }\n  return true;\n}\n\nbool UnitHandler::CSSUnreachable(bool enable_css_strict_mode, const char* fmt,\n                                 ...) {\n  if (UNLIKELY(enable_css_strict_mode)) {\n    va_list args;\n    va_start(args, fmt);\n    _CSSWarning(fmt, args);\n    va_end(args);\n  }\n  return false;\n}\n\nbool UnitHandler::CSSMethodUnreachable(bool enable_css_strict_mode) {\n  CSSUnreachable(enable_css_strict_mode, CANNOT_REACH_METHOD);\n  return false;\n}\n\nbool UnitHandler::ProcessCSSValue(const CSSPropertyID key,\n                                  const tasm::CSSValue& input, StyleMap& output,\n                                  const CSSParserConfigs& configs) {\n  if (input.IsVariable()) {\n    output[key] = input;\n    return true;\n  }\n  return UnitHandler::Process(key, input.GetValue(), output, configs);\n}\n\nbool UnitHandler::Process(const CSSPropertyID key, const lepus::Value& input,\n                          StyleMap& output, const CSSParserConfigs& configs) {\n  if (key <= CSSPropertyID::kPropertyStart ||\n      key >= CSSPropertyID::kPropertyEnd) {\n    LOGE(\"[UnitHandler] illegal css key:\" << key);\n    CSSUnreachable(configs.enable_css_strict_mode,\n                   \"[UnitHandler] illegal css key:%d\", key);\n    return false;\n  }\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, UNIT_HANDLER_PROCESS,\n              [&](lynx::perfetto::EventContext ctx) {\n                ctx.event()->add_debug_annotations(\n                    \"property_name\", CSSProperty::GetPropertyName(key).str());\n              });\n  auto maybe_handler = Instance().interceptors_[key];\n  if (!maybe_handler) {\n    output[key] = CSSValue(input, CSSValuePattern::STRING);\n    return true;\n  }\n\n  if (output.capacity() == 0) {\n    // If target map buffer has not been allocated, we have the opportunity\n    // to reserve memory for it. This will optimize the case that a inline\n    // style is set by render functions.\n    if (auto expand = CSSProperty::GetShorthandExpand(key); true) {\n      output.reserve(expand + kCSSStyleMapFuzzyAllocationSize);\n    }\n  }\n\n  if (!maybe_handler(key, input, output, configs)) {\n    return false;\n  }\n  return true;\n}\n\nStyleMap UnitHandler::Process(const CSSPropertyID key,\n                              const lepus::Value& input,\n                              const CSSParserConfigs& configs) {\n  StyleMap ret;\n  ret.reserve(CSSProperty::GetShorthandExpand(key));\n  Process(key, input, ret, configs);\n  return ret;\n}\n\nUnitHandler::UnitHandler() {\n  // TODO(liyanbo): must at first position. other will replace pre define.\n  StringHandler::Register(interceptors_);\n  AnimationDirectionHandler::Register(interceptors_);\n  AnimationFillModeHandler::Register(interceptors_);\n  AnimationPlayStateHandler::Register(interceptors_);\n  AnimationPropertyHandler::Register(interceptors_);\n  AnimationNameHandler::Register(interceptors_);\n  AnimationShorthandHandler::Register(interceptors_);\n  AspectRatioHandler::Register(interceptors_);\n  BoolHandler::Register(interceptors_);\n  ColorHandler::Register(interceptors_);\n  BorderHandler::Register(interceptors_);\n  TextStrokeHandler::Register(interceptors_);\n  BorderStyleHandler::Register(interceptors_);\n  BorderWidthHandler::Register(interceptors_);\n  EnumHandler::Register(interceptors_);\n  FlexFlowHandler::Register(interceptors_);\n  FlexHandler::Register(interceptors_);\n  FontLengthHandler::Register(interceptors_);\n  FourSidesShorthandHandler::Register(interceptors_);\n  GridPositionHandler::Register(interceptors_);\n  GridTemplateHandler::Register(interceptors_);\n  LengthHandler::Register(interceptors_);\n  NumberHandler::Register(interceptors_);\n  AnimIterCountHandler::Register(interceptors_);\n  ShadowHandler::Register(interceptors_);\n  TimeHandler::Register(interceptors_);\n  TimingFunctionHandler::Register(interceptors_);\n  TransformHandler::Register(interceptors_);\n  TransformOriginHandler::Register(interceptors_);\n  TransitionShorthandHandler::Register(interceptors_);\n  TextDecorationHandler::Register(interceptors_);\n  BorderRadiusHandler::Register(interceptors_);\n  BackgroundShorthandHandler::Register(interceptors_);\n  BackgroundBoxHandler::Register(interceptors_);\n  BackgroundClipHandler::Register(interceptors_);\n  BackgroundImageHandler::Register(interceptors_);\n  BackgroundPositionHandler::Register(interceptors_);\n  BackgroundRepeatHandler::Register(interceptors_);\n  BackgroundSizeHandler::Register(interceptors_);\n  MaskShorthandHandler::Register(interceptors_);\n  FilterHandler::Register(interceptors_);\n  VerticalAlignHandler::Register(interceptors_);\n  RelativeAlignHandler::Register(interceptors_);\n  ListGapHandler::Register(interceptors_);\n  CursorHandler::Register(interceptors_);\n  ClipPathHandler::Register(interceptors_);\n  AutoFontSizeHandler::Register(interceptors_);\n  AutoFontSizePresetSizesHandler::Register(interceptors_);\n  GapHandler::Register(interceptors_);\n  OffsetRotateHandler::Register(interceptors_);\n  FontVariationSettingsHandler::Register(interceptors_);\n  FontFeatureSettingsHandler::Register(interceptors_);\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/css/unit_handler.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_CSS_UNIT_HANDLER_H_\n#define CORE_RENDERER_CSS_UNIT_HANDLER_H_\n\n#include <array>\n#include <cctype>\n#include <cmath>\n#include <string>\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"core/base/lynx_export.h\"\n#include \"core/renderer/css/css_debug_msg.h\"\n#include \"core/renderer/css/css_property.h\"\n#include \"core/renderer/css/parser/css_parser_configs.h\"\n#include \"core/renderer/css/parser/handler_defines.h\"\n\nnamespace lynx {\nnamespace tasm {\n\n#define CSS_HANDLER_FAIL_IF(COND, STRICT, ...)                       \\\n  if (UNLIKELY(COND)) {                                              \\\n    if (UNLIKELY(STRICT)) {                                          \\\n      lynx::tasm::UnitHandler::CSSWarningUnconditional(__VA_ARGS__); \\\n    }                                                                \\\n    return false;                                                    \\\n  }\n\n#define CSS_HANDLER_FAIL_IF_NOT(COND, STRICT, ...)                   \\\n  if (UNLIKELY(!(COND))) {                                           \\\n    if (UNLIKELY(STRICT)) {                                          \\\n      lynx::tasm::UnitHandler::CSSWarningUnconditional(__VA_ARGS__); \\\n    }                                                                \\\n    return false;                                                    \\\n  }\n\nclass UnitHandler {\n public:\n  // only for NoDestructor.\n  UnitHandler();\n\n  static void ReportError(std::string error_msg, std::string fix_suggestion,\n                          CSSPropertyID key, const std::string& input);\n\n  // Log formatted warning message unconditionally and always returns false.\n  static bool CSSWarningUnconditional(const char* fmt...);\n\n  // If !expression returns false and log formatted message if in strict mode.\n  static bool CSSWarning(bool expression, bool enableCSSStrictMode,\n                         const char* fmt...);\n\n  // Log custom unreachable message in strict mode and always returns false.\n  static bool CSSUnreachable(bool enableCSSStrictMode, const char* fmt...);\n\n  // Log unreachable message in strict mode and always returns false\n  static bool CSSMethodUnreachable(bool enableCSSStrictMode);\n\n  LYNX_EXPORT_FOR_DEVTOOL static bool Process(const CSSPropertyID key,\n                                              const lepus::Value& input,\n                                              StyleMap& output,\n                                              const CSSParserConfigs& configs);\n  LYNX_EXPORT static StyleMap Process(const CSSPropertyID key,\n                                      const lepus::Value& input,\n                                      const CSSParserConfigs& configs);\n\n  LYNX_EXPORT_FOR_DEVTOOL static bool ProcessCSSValue(\n      const CSSPropertyID key, const tasm::CSSValue& input, StyleMap& output,\n      const CSSParserConfigs& configs);\n\n private:\n  static UnitHandler& Instance();\n\n  std::array<pHandlerFunc, kCSSPropertyCount> interceptors_;\n};\n}  // namespace tasm\n\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_CSS_UNIT_HANDLER_H_\n"
  },
  {
    "path": "core/renderer/css/unit_handler_unittest.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/css/unit_handler.h\"\n\n#include \"third_party/googletest/googletest/include/gtest/gtest.h\"\n\nnamespace lynx {\n\nusing tasm::UnitHandler;\n\nnamespace css {\nnamespace testing {\n\nTEST(UnitHandler, Process2) {\n  tasm::StyleMap output;\n  tasm::CSSParserConfigs configs;\n  lepus::Value v(\"10px\");\n  EXPECT_FALSE(UnitHandler::Process(tasm::kPropertyStart, v, output, configs));\n  EXPECT_FALSE(UnitHandler::Process(tasm::kPropertyEnd, v, output, configs));\n  EXPECT_FALSE(\n      UnitHandler::Process((tasm::CSSPropertyID)-100, v, output, configs));\n  EXPECT_FALSE(\n      UnitHandler::Process((tasm::CSSPropertyID)99999999, v, output, configs));\n  EXPECT_TRUE(UnitHandler::Process(tasm::kPropertyIDWidth, v, output, configs));\n}\n\nTEST(UnitHandler, Process3) {\n  lepus::Value v(\"10px\");\n  tasm::CSSParserConfigs configs;\n  EXPECT_TRUE(UnitHandler::Process(tasm::kPropertyStart, v, configs).empty());\n  EXPECT_TRUE(UnitHandler::Process(tasm::kPropertyEnd, v, configs).empty());\n  EXPECT_TRUE(\n      UnitHandler::Process((tasm::CSSPropertyID)-100, v, configs).empty());\n  EXPECT_TRUE(\n      UnitHandler::Process((tasm::CSSPropertyID)99999999, v, configs).empty());\n  EXPECT_FALSE(\n      UnitHandler::Process(tasm::kPropertyIDWidth, v, configs).empty());\n}\n\n}  // namespace testing\n\n}  // namespace css\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/data/BUILD.gn",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../testing/test.gni\")\nimport(\"../../Lynx.gni\")\nimport(\"build.gni\")\n\nlynx_core_source_set(\"data\") {\n  sources = lynx_data_shared_sources_path\n  if (!is_oliver_ssr) {\n    sources += lynx_data_platform_sources_path\n  }\n  deps = [\n    \"../../../base/src:base_log_headers\",\n    \"../../../third_party/rapidjson:rapidjson\",\n  ]\n}\n"
  },
  {
    "path": "core/renderer/data/android/platform_data_android.cc",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/data/android/platform_data_android.h\"\n\n#include \"base/include/platform/android/jni_utils.h\"\n#include \"core/renderer/dom/android/lynx_view_data_manager_android.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nvoid PlatformDataAndroid::EnsureConvertData() {\n  if (value_converted_from_platform_data_.IsEmpty() &&\n      template_data_.Get() != nullptr) {\n    JNIEnv* env = base::android::AttachCurrentThread();\n    value_converted_from_platform_data_ =\n        LynxViewDataManagerAndroid::GetJsThreadDataFromTemplateData(\n            env, template_data_.Get());\n  }\n}\n\nvoid PlatformDataAndroid::ShallowCopy() {\n  PlatformData::ShallowCopy();\n  JNIEnv* env = base::android::AttachCurrentThread();\n  template_data_.Reset(env,\n                       LynxViewDataManagerAndroid::GetTemplateDataForJSThread(\n                           env, template_data_.Get()));\n}\n\nPlatformDataAndroid::~PlatformDataAndroid() {\n  JNIEnv* env = base::android::AttachCurrentThread();\n  LynxViewDataManagerAndroid::ConsumeTemplateDataActions(env,\n                                                         template_data_.Get());\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/data/android/platform_data_android.h",
    "content": "// Copyright 2023 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_DATA_ANDROID_PLATFORM_DATA_ANDROID_H_\n#define CORE_RENDERER_DATA_ANDROID_PLATFORM_DATA_ANDROID_H_\n\n#include <string>\n\n#include \"base/include/platform/android/scoped_java_ref.h\"\n#include \"base/include/value/base_value.h\"\n#include \"core/renderer/data/platform_data.h\"\n\nnamespace lynx {\nnamespace tasm {\nclass PlatformDataAndroid : public PlatformData {\n public:\n  PlatformDataAndroid(\n      const base::android::ScopedGlobalJavaRef<jobject>& template_data)\n      : template_data_(template_data) {}\n\n  virtual void ShallowCopy() override;\n\n  virtual ~PlatformDataAndroid() override;\n\n private:\n  void EnsureConvertData() override;\n\n  base::android::ScopedGlobalJavaRef<jobject> template_data_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_DATA_ANDROID_PLATFORM_DATA_ANDROID_H_\n"
  },
  {
    "path": "core/renderer/data/build.gni",
    "content": "# Copyright 2024 The Lynx Authors. All rights reserved.\n# Licensed under the Apache License Version 2.0 that can be found in the\n# LICENSE file in the root directory of this source tree.\n\nimport(\"../../../config.gni\")\n\nlynx_data_shared_sources = [\n  \"lynx_view_data_manager.cc\",\n  \"lynx_view_data_manager.h\",\n  \"template_data.cc\",\n  \"template_data.h\",\n]\n\nlynx_data_shared_sources_path =\n    get_path_info(lynx_data_shared_sources, \"abspath\")\n\nlynx_data_platform_sources = []\nlynx_data_platform_sources += [ \"platform_data.h\" ]\nif (is_android) {\n  lynx_data_platform_sources += [\n    \"android/platform_data_android.cc\",\n    \"android/platform_data_android.h\",\n  ]\n} else if (is_harmony) {\n  lynx_data_platform_sources += [\n    \"harmony/platform_data_harmony.cc\",\n    \"harmony/platform_data_harmony.h\",\n    \"harmony/template_data_harmony.cc\",\n    \"harmony/template_data_harmony.h\",\n  ]\n} else if (is_ios) {\n  lynx_data_platform_sources += [\n    \"ios/platform_data_darwin.h\",\n    \"ios/platform_data_darwin.mm\",\n  ]\n}\n\nlynx_data_platform_sources_path =\n    get_path_info(lynx_data_platform_sources, \"abspath\")\n"
  },
  {
    "path": "core/renderer/data/harmony/platform_data_harmony.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/data/harmony/platform_data_harmony.h\"\n\n#include \"core/renderer/data/harmony/template_data_harmony.h\"\n#include \"core/runtime/lepus/json_parser.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nvoid PlatformDataHarmony::EnsureConvertData() {\n  if (is_json_string_) {\n    value_converted_from_platform_data_ =\n        lepus::jsonValueTolepusValue(json_string_.c_str());\n  }\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/data/harmony/platform_data_harmony.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_DATA_HARMONY_PLATFORM_DATA_HARMONY_H_\n#define CORE_RENDERER_DATA_HARMONY_PLATFORM_DATA_HARMONY_H_\n\n#include <string>\n#include <utility>\n\n#include \"base/include/value/base_value.h\"\n#include \"core/renderer/data/platform_data.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass PlatformDataHarmony : public PlatformData {\n public:\n  PlatformDataHarmony() = default;\n  ~PlatformDataHarmony() override = default;\n\n  explicit PlatformDataHarmony(const std::string& json_string)\n      : is_json_string_(true), json_string_(json_string) {}\n  explicit PlatformDataHarmony(lepus::Value&& value) {\n    value_converted_from_platform_data_ = std::move(value);\n  }\n\n private:\n  void EnsureConvertData() override;\n\n  bool is_json_string_{false};\n  std::string json_string_{};\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_DATA_HARMONY_PLATFORM_DATA_HARMONY_H_\n"
  },
  {
    "path": "core/renderer/data/harmony/template_data_harmony.cc",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/data/harmony/template_data_harmony.h\"\n\n#include <utility>\n\n#include \"base/include/platform/harmony/napi_util.h\"\n#include \"core/base/harmony/napi_convert_helper.h\"\n#include \"core/base/threading/task_runner_manufactor.h\"\n#include \"core/renderer/data/harmony/platform_data_harmony.h\"\n#include \"core/runtime/lepus/json_parser.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nstd::shared_ptr<tasm::TemplateData> TemplateDataHarmony::GenerateTemplateData(\n    napi_env env, napi_value raw_value, napi_value raw_read_only,\n    napi_value raw_processor_name) {\n  napi_valuetype type;\n  napi_typeof(env, raw_value, &type);\n\n  bool from_json = false;\n  std::string json_str;\n  lepus::Value result;\n\n  if (type == napi_valuetype::napi_string) {\n    from_json = true;\n    json_str = base::NapiUtil::ConvertToString(env, raw_value);\n    result = lepus::jsonValueTolepusValue(json_str.c_str());\n  } else if (type == napi_valuetype::napi_object) {\n    result = base::NapiConvertHelper::ConvertToLepusValue(env, raw_value);\n  } else {\n    LOGE(\n        \"Error in TemplateDataHarmony::GenerateTemplateData, type should be \"\n        \"string or object\");\n  }\n\n  if (result.IsNil()) {\n    return nullptr;\n  }\n\n  bool read_only = false;\n  napi_typeof(env, raw_read_only, &type);\n  if (type == napi_boolean) {\n    read_only = base::NapiUtil::ConvertToBoolean(env, raw_read_only);\n  }\n\n  std::string processor_name;\n  napi_typeof(env, raw_processor_name, &type);\n\n  if (type == napi_string) {\n    processor_name =\n        base::NapiUtil::ConvertToShortString(env, raw_processor_name);\n  }\n\n  auto data =\n      std::make_shared<TemplateDataHarmony>(result, read_only, processor_name);\n  if (from_json) {\n    data->SetPlatformData(std::make_unique<PlatformDataHarmony>(json_str));\n  } else {\n    std::promise<lepus::Value> promise;\n    std::future<lepus::Value> future = promise.get_future();\n    auto async_task = fml::MakeRefCounted<base::OnceTask<lepus::Value>>(\n        [result, promise = std::move(promise)]() mutable {\n          promise.set_value(lepus::Value::Clone(result));\n        },\n        std::move(future));\n\n    base::TaskRunnerManufactor::PostTaskToConcurrentLoop(\n        [async_task]() { async_task->Run(); },\n        base::ConcurrentTaskType::HIGH_PRIORITY);\n\n    data->SetAsyncTask(async_task);\n  }\n\n  return data;\n}\n\nconst lepus::Value& TemplateDataHarmony::GetValue() const {\n  const_cast<TemplateDataHarmony*>(this)->EnsurePlatformData();\n  return value_;\n}\n\nstd::unique_ptr<PlatformData> TemplateDataHarmony::ObtainPlatformData() {\n  EnsurePlatformData();\n  return std::move(platform_data_);\n}\n\nvoid TemplateDataHarmony::EnsurePlatformData() {\n  if (platform_data_ == nullptr && async_task_.get() != nullptr) {\n    async_task_->Run();\n    SetPlatformData(\n        std::make_unique<PlatformDataHarmony>(async_task_->GetFuture().get()));\n    async_task_ = nullptr;\n  }\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/data/harmony/template_data_harmony.h",
    "content": "// Copyright 2025 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_DATA_HARMONY_TEMPLATE_DATA_HARMONY_H_\n#define CORE_RENDERER_DATA_HARMONY_TEMPLATE_DATA_HARMONY_H_\n\n#include <node_api.h>\n\n#include <memory>\n#include <string>\n\n#include \"base/include/fml/memory/ref_ptr.h\"\n#include \"base/include/value/base_value.h\"\n#include \"core/base/thread/once_task.h\"\n#include \"core/renderer/data/template_data.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nclass TemplateData;\n\nclass TemplateDataHarmony : public TemplateData {\n public:\n  static std::shared_ptr<tasm::TemplateData> GenerateTemplateData(\n      napi_env env, napi_value obj, napi_value raw_read_only,\n      napi_value raw_processor_name);\n\n  TemplateDataHarmony() = default;\n  ~TemplateDataHarmony() override = default;\n\n  TemplateDataHarmony(const lepus::Value& value, bool read_only,\n                      std::string preprocessorName)\n      : TemplateData(value, read_only, preprocessorName) {}\n\n  TemplateDataHarmony(const lepus::Value& value, bool read_only)\n      : TemplateData(value, read_only) {}\n\n  virtual const lepus::Value& GetValue() const override;\n\n  // Will be called when execute CopyPlatformData\n  virtual std::unique_ptr<PlatformData> ObtainPlatformData() override;\n\n  void SetAsyncTask(\n      const fml::RefPtr<base::OnceTask<lepus::Value>>& async_task) {\n    async_task_ = async_task;\n  }\n\n private:\n  void EnsurePlatformData();\n\n  fml::RefPtr<base::OnceTask<lepus::Value>> async_task_{nullptr};\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_DATA_HARMONY_TEMPLATE_DATA_HARMONY_H_\n"
  },
  {
    "path": "core/renderer/data/lynx_view_data_manager.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/data/lynx_view_data_manager.h\"\n\n#include \"base/include/debug/lynx_assert.h\"\n#include \"base/include/log/logging.h\"\n#include \"core/build/gen/lynx_sub_error_code.h\"\n#include \"core/runtime/lepus/json_parser.h\"\n#include \"core/runtime/lepus/lepus_global.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nlepus::Value* LynxViewDataManager::ParseData(const char* data) {\n  lepus_value* value = new lepus_value;\n  *value = lepus::jsonValueTolepusValue(data);\n  if (!value->IsTable()) {\n    LynxInfo(error::E_DATA_FLOW_UPDATE_INVALID_TYPE,\n             \"ParseData error, data is:%s\", data);\n    value->SetTable(lepus::Dictionary::Create());\n  }\n  return value;\n}\n\nvoid LynxViewDataManager::UpdateData(lepus::Value& dest,\n                                     const lepus::Value& src) {\n  if (src.IsTable()) {\n    auto src_dict = src.Table();\n    auto dest_dict = dest.Table();\n    dest_dict->reserve(src_dict->size());\n    for (const auto& [src_key, src_value] : *src_dict) {\n      if (src_value.IsTable()) {\n        lynx::lepus::Value old_value = dest_dict->GetValue(src_key);\n        if (old_value.IsTable()) {\n          if (old_value.Table()->IsConst()) {\n            old_value = lynx::lepus::Value::Clone(old_value);\n            dest_dict->SetValue(src_key, old_value);\n          }\n          // Merge table.\n          auto target_table = old_value.Table();\n          auto table = src_value.Table();\n          if (target_table.get() != table.get()) {\n            for (const auto& [key, value] : *table) {\n              target_table->SetValue(key, value);\n            }\n          }\n          continue;\n        }\n      }\n      dest_dict->SetValue(src_key, src_value);\n    }\n  }\n}\n\nvoid LynxViewDataManager::ReleaseData(lepus::Value* obj) {\n  if (obj != nullptr) {\n    delete obj;\n  }\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/data/lynx_view_data_manager.h",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_DATA_LYNX_VIEW_DATA_MANAGER_H_\n#define CORE_RENDERER_DATA_LYNX_VIEW_DATA_MANAGER_H_\n\nnamespace lynx {\nnamespace lepus {\nclass Value;\n}\n\nnamespace tasm {\nclass LynxViewDataManager {\n public:\n  static lepus::Value* ParseData(const char* data);\n  static void ReleaseData(lepus::Value* obj);\n  static void UpdateData(lepus::Value& dest, const lepus::Value& src);\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_DATA_LYNX_VIEW_DATA_MANAGER_H_\n"
  },
  {
    "path": "core/renderer/data/template_data.h",
    "content": "// Copyright 2020 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#ifndef CORE_RENDERER_DATA_TEMPLATE_DATA_H_\n#define CORE_RENDERER_DATA_TEMPLATE_DATA_H_\n\n#include <memory>\n#include <string>\n#include <utility>\n\n#include \"base/include/value/base_value.h\"\n#include \"base/include/value/table.h\"\n#include \"core/renderer/data/platform_data.h\"\n#include \"core/renderer/utils/lynx_env.h\"\n\nnamespace lynx {\n\nnamespace lepus {\nclass Value;\n}\n\nnamespace tasm {\n\nclass TemplateData {\n public:\n  // Return a new Template Data. If other has platform data, obtain the platform\n  // data from other and set value_ to empty. If other does not have platform\n  // data, deep copy other's value_ and assign it to its own value_.\n  static TemplateData CopyPlatformData(\n      const std::shared_ptr<TemplateData>& other);\n  static TemplateData CopyPlatformData(const TemplateData& other);\n  static TemplateData ShallowCopy(const TemplateData& other);\n  static TemplateData DeepClone(const TemplateData& other);\n\n  TemplateData() = default;\n  virtual ~TemplateData() = default;\n\n  // make TemplateData move only\n  TemplateData(const TemplateData&) = delete;\n  TemplateData& operator=(const TemplateData&) = delete;\n  TemplateData(TemplateData&&) = default;\n  TemplateData& operator=(TemplateData&&) = default;\n\n  TemplateData(const lepus::Value& value, bool read_only,\n               std::string preprocessorName);\n  TemplateData(const lepus::Value& value, bool read_only);\n\n  void SetValue(const lepus::Value& value);\n\n  // value() only used in ProcessInitData & ProcessTemplateDataForRadon now, it\n  // does not invole platform_data_.\n  lepus::Value& value() { return value_; }\n\n  // When calling GetValue, if value_ is empty, it attempts to obtain the value\n  // from platform data.\n  virtual const lepus::Value& GetValue() const;\n\n  void SetPreprocessorName(const std::string& name);\n  const std::string& PreprocessorName() const;\n\n  void SetReadOnly(bool flag) { read_only_ = flag; }\n  bool IsReadOnly() const;\n\n  void CloneValue();\n\n  void SetPlatformData(std::unique_ptr<PlatformData> data) {\n    platform_data_ = std::move(data);\n  }\n\n  // Will be called when execute CopyPlatformData\n  virtual std::unique_ptr<PlatformData> ObtainPlatformData() {\n    if (LynxEnv::GetInstance().EnablePlatformDataFix()) {\n      if (platform_data_) {\n        platform_data_->ShallowCopy();\n      }\n    }\n\n    return std::move(platform_data_);\n  }\n\n protected:\n  lepus::Value value_;\n  std::string processor_name_;\n  bool read_only_{false};\n\n  std::unique_ptr<PlatformData> platform_data_;\n};\n\n}  // namespace tasm\n}  // namespace lynx\n\n#endif  // CORE_RENDERER_DATA_TEMPLATE_DATA_H_\n"
  },
  {
    "path": "core/renderer/dom/air/air_element/air_block_element.cc",
    "content": "// Copyright 2022 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/dom/air/air_element/air_block_element.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nAirBlockElement::AirBlockElement(ElementManager* manager, uint32_t lepus_id,\n                                 int32_t id)\n    : AirElement(kAirBlock, manager, BASE_STATIC_STRING(kAirBlockTag), lepus_id,\n                 id) {}\n\nAirBlockElement::AirBlockElement(ElementManager* manager, AirElementType type,\n                                 const base::String& tag, uint32_t lepus_id,\n                                 int32_t id)\n    : AirElement(type, manager, tag, lepus_id, id) {}\n\nvoid AirBlockElement::InsertNode(AirElement* child, bool from_virtual_child) {\n  if (!from_virtual_child) {\n    InsertAirNode(child);\n  }\n  if (parent_) {\n    parent_->InsertNode(child, true);\n  }\n}\n\nvoid AirBlockElement::RemoveNode(AirElement* child, bool destroy) {\n  if (destroy) {\n    RemoveAirNode(child);\n  }\n  if (parent_) {\n    parent_->RemoveNode(child, destroy);\n  }\n}\n\nvoid AirBlockElement::RemoveAllNodes(bool destroy) {\n  if (parent_) {\n    for (auto const& child : SharedAirElementVector(air_children_)) {\n      parent_->RemoveNode(child.get(), destroy);\n    }\n  }\n\n  if (destroy) {\n    air_children_.erase(air_children_.begin(), air_children_.end());\n  }\n}\n\nuint32_t AirBlockElement::NonVirtualNodeCountInParent() {\n  uint32_t sum = 0;\n  for (auto child : air_children_) {\n    sum += child->NonVirtualNodeCountInParent();\n  }\n  return sum;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/renderer/dom/air/air_element/air_element.cc",
    "content": "// Copyright 2019 The Lynx Authors. All rights reserved.\n// Licensed under the Apache License Version 2.0 that can be found in the\n// LICENSE file in the root directory of this source tree.\n\n#include \"core/renderer/dom/air/air_element/air_element.h\"\n\n#include <utility>\n\n#include \"base/include/string/string_number_convert.h\"\n#include \"base/include/value/array.h\"\n#include \"base/include/value/table.h\"\n#include \"base/trace/native/trace_event.h\"\n#include \"core/renderer/css/css_keyframes_token.h\"\n#include \"core/renderer/css/layout_property.h\"\n#include \"core/renderer/dom/element_manager.h\"\n#include \"core/renderer/page_proxy.h\"\n#include \"core/renderer/starlight/layout/layout_object.h\"\n#include \"core/renderer/trace/renderer_trace_event_def.h\"\n#include \"core/renderer/utils/base/tasm_constants.h\"\n#include \"core/renderer/utils/lynx_env.h\"\n#include \"core/renderer/utils/value_utils.h\"\n#include \"core/value_wrapper/value_impl_lepus.h\"\n\nnamespace lynx {\nnamespace tasm {\n\nnamespace {\n// compute array:[value , pattern , value , pattern], need to compute px.\n// eg:border-radius , padding , margin\n//      \"property_id\": {\n//        \"value\": [\n//          width_value,\n//          width_value_pattern,\n//          height_value,\n//          height_value_pattern\n//        ],\n//        \"pattern\": 14\n//      }\nconstexpr static int32_t kInvalidIndex = -1;\n// constexpr static float kRpxRatio = 750.0f;\n}  // namespace\n\nAirElement::AirElement(AirElementType type, ElementManager *manager,\n                       const base::String &tag, uint32_t lepus_id, int32_t id)\n    : element_type_(type),\n      tag_(tag),\n      catalyzer_(manager->catalyzer()),\n      dirty_(kDirtyCreated),\n      lepus_id_(lepus_id),\n      font_size_(manager->GetLynxEnvConfig().DefaultFontSize()),\n      root_font_size_(manager->GetLynxEnvConfig().DefaultFontSize()),\n      air_element_manager_(manager) {\n  id_ = id > 0 ? id : manager->GenerateElementID();\n  config_flatten_ = manager->GetPageFlatten();\n  element_manager()->CreateLayoutNode(id_, tag_);\n}\n\nvoid AirElement::MergeHigherPriorityCSSStyle(StyleMap &primary,\n                                             const StyleMap &higher) {\n  for (const auto &it : higher) {\n    primary[it.first] = it.second;\n  }\n}\n\nbool AirElement::ResolveKeyframesMap(CSSPropertyID id,\n                                     const std::string &keyframes_name) {\n  StyleMap keyframes;\n  GetKeyframesMap(keyframes_name, keyframes);\n  if (!keyframes.empty()) {\n    return ResolveKeyframesMap(kPropertyIDAnimationName,\n                               keyframes[kPropertyIDAnimationName].GetValue());\n  }\n  return false;\n}\n\nbool AirElement::ResolveKeyframesMap(CSSPropertyID id,\n                                     const lepus::Value &keyframes_map) {\n  if (CSSProperty::IsKeyframeProps(id)) {\n    if (id == kPropertyIDAnimation) {\n      // If animation is declared in inline style ,we should resolve it by using\n      // keyframes saved by parse_style_\n      auto animation_name =\n          keyframes_map.GetProperty(std::to_string(kPropertyIDAnimationName));\n      if (animation_name.IsString()) {\n        return ResolveKeyframesMap(kPropertyIDAnimationName,\n                                   animation_name.StdString());\n      }\n    } else if (id == kPropertyIDAnimationName) {\n      if (keyframes_map.IsString()) {\n        return ResolveKeyframesMap(id, keyframes_map.StdString());\n      } else if (keyframes_map.IsTable()) {\n        // decode keyframe map, for example\n        // {\"translateX-ani\":{\n        //    \"0\"(string to float) : {\"transform\"(string) :\n        //    \"translateX(0)\"(string)}, \"1\" : {\"transform\"(string) :\n        //    \"translateX(50px)\"(string)},\n        // }}\n        ForEachLepusValue(\n            keyframes_map, [this](const lepus::Value &keyframe_name,\n                                  const lepus::Value &keyframe_dic) {\n              starlight::CSSStyleUtils::UpdateCSSKeyframes(\n                  keyframes_map_, keyframe_name.String(), keyframe_dic,\n                  air_element_manager_->GetCSSParserConfigs());\n            });\n      }\n    }\n    return true;\n  }\n  return false;\n}\n\nvoid AirElement::PushKeyframesToPlatform() {\n  if (!keyframes_map_.empty()) {\n    auto lepus_keyframes = starlight::CSSStyleUtils::ResolveCSSKeyframes(\n        keyframes_map_, computed_css_style()->GetMeasureContext(),\n        air_element_manager_->GetCSSParserConfigs());\n    if (!lepus_keyframes.IsTable()) {\n      return;\n    }\n    auto bundle = element_manager()->GetPropBundleCreator()->CreatePropBundle();\n    bundle->SetProps(\"keyframes\", pub::ValueImplLepus(lepus_keyframes));\n    PushToPropsBundle(BASE_STATIC_STRING(kPropertyNameAnimation),\n                      computed_css_style()->GetValue(kPropertyIDAnimation));\n    painting_context()->SetKeyframes(std::move(bundle));\n    keyframes_map_.clear();\n  }\n}\n\nAirElement *AirElement::GetParentComponent() const {\n  AirElement *parent_node = air_parent_;\n  while (parent_node != nullptr) {\n    if (parent_node->is_component() || parent_node->is_page()) {\n      return parent_node;\n    }\n    parent_node = parent_node->air_parent_;\n  }\n  return nullptr;\n}\n\n/// used by touch_event_handle mainly\nbool AirElement::InComponent() const {\n  auto parent = GetParentComponent();\n  if (parent) {\n    return !(parent->is_page());\n  }\n  return false;\n}\n\nvoid AirElement::InsertNode(AirElement *child, bool from_virtual_child) {\n  if (child != this) {\n    if (!from_virtual_child) {\n      InsertAirNode(child);\n    }\n    if (child->is_virtual_node()) {\n      child->set_parent(this);\n      return;\n    }\n    size_t index = FindInsertIndex(children_, child);\n    AddChildAt(child, index);\n    dirty_ |= kDirtyTree;\n    child->dirty_ |= kDirtyTree;\n  }\n}\n\nvoid AirElement::InsertNodeIndex(AirElement *child, size_t index) {\n  element_manager()->InsertLayoutNode(impl_id(), child->impl_id(),\n                                      static_cast<int>(index));\n  child->layout_node_inserted_ = true;\n  element_container()->AttachChildToTargetContainer(GetChildAt(index));\n}\n\nvoid AirElement::InsertNodeBefore(AirElement *child,\n                                  const AirElement *reference_child) {\n  if (child->is_virtual_node()) {\n    // for virtual_node, only save the parent's ptr\n    child->set_parent(this);\n    return;\n  }\n  auto index = IndexOf(reference_child);\n  if (index >= static_cast<int>(children_.size())) {\n    return;\n  }\n  AddChildAt(child, index);\n  dirty_ |= kDirtyTree;\n  child->dirty_ |= kDirtyTree;\n}\n\nvoid AirElement::InsertNodeAfterIndex(AirElement *child, int &index) {\n  dirty_ |= kDirtyTree;\n  child->dirty_ |= kDirtyTree;\n  if (!child->is_virtual_node()) {\n    ++index;\n    AddChildAt(child, index);\n  } else {\n    child->set_parent(this);\n    for (auto air_child : child->air_children_) {\n      InsertNodeAfterIndex(air_child.get(), index);\n    }\n  }\n}\n\nvoid AirElement::InsertNodeAtBottom(AirElement *child) {\n  int index = kInvalidIndex;\n  if (!children_.empty()) {\n    index = static_cast<int>(children_.size() - 1);\n  }\n  InsertNodeAfterIndex(child, index);\n}\n\nvoid AirElement::InsertAirNode(AirElement *child) {\n  if (child != this) {\n    size_t index = FindInsertIndex(air_children_, child);\n    AddAirChildAt(child, index);\n  }\n}\n\nAirElement *AirElement::LastNonVirtualNode() {\n  AirElement *result = nullptr;\n  if (!is_virtual_node()) {\n    return this;\n  }\n  for (auto it = air_children_.rbegin(); it != air_children_.rend(); ++it) {\n    if (!(*it)->is_virtual_node()) {\n      result = (*it).get();\n    } else {\n      result = (*it)->LastNonVirtualNode();\n    }\n    if (result != nullptr) {\n      return result;\n    }\n  }\n  return nullptr;\n}\n\nvoid AirElement::RemoveNode(AirElement *child, bool destroy) {\n  child->has_been_removed_ = true;\n  if (child->is_virtual_node()) {\n    child->RemoveAllNodes(destroy);\n    if (destroy) {\n      element_manager()->DestroyLayoutNode(child->impl_id());\n      element_manager()->air_node_manager()->Erase(child->impl_id());\n      element_manager()->air_node_manager()->EraseLepusId(child->GetLepusId(),\n                                                          child);\n    }\n    RemoveAirNode(child);\n    dirty_ |= kDirtyTree;\n    return;\n  }\n  if (child->is_component()) {\n    child->OnElementRemoved();\n  }\n  auto index = IndexOf(child);\n  if (index != kInvalidIndex) {\n    child->RemoveAllNodes();\n    RemoveNode(child, index, destroy);\n    RemoveAirNode(child);\n    dirty_ |= kDirtyTree;\n  }\n}\n\nvoid AirElement::RemoveAllNodes(bool destroy) {\n  for (auto const &child : SharedAirElementVector(air_children_)) {\n    RemoveNode(child.get(), destroy);\n  }\n\n  if (destroy) {\n    air_children_.erase(air_children_.begin(), air_children_.end());\n  }\n}\n\nvoid AirElement::RemoveAirNode(AirElement *child) {\n  auto index = IndexOfAirChild(child);\n  RemoveAirNode(child, index);\n}\n\nvoid AirElement::SetAttribute(const base::String &key,\n                              const lepus::Value &value, bool resolve) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_SET_ATTRIBUTE);\n  // check flatten prop and update config_flatten_\n  if (resolve) {\n    CheckFlattenProp(key, value);\n    PushToPropsBundle(key, value);\n#if OS_ANDROID\n    CheckHasNonFlattenAttr(key, value);\n#endif\n  } else {\n    raw_attributes_[key] = value.ToLepusValue();\n  }\n}\n\nvoid AirElement::ComputeCSSStyle(CSSPropertyID id, tasm::CSSValue &css_value) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_COMPUTE_CSS_STYLE);\n  computed_css_style()->SetValue(id, css_value);\n  css_value.SetValue(computed_css_style()->GetValue(id));\n}\n\nvoid AirElement::Destroy() {\n  // Layout only destroy recursively\n  if (!IsLayoutOnly()) {\n    painting_context()->DestroyPaintingNode(\n        parent() ? parent()->impl_id() : kInvalidIndex, impl_id(), 0);\n  } else {\n    for (int i = static_cast<int>(GetChildCount()) - 1; i >= 0; --i) {\n      GetChildAt(i)->Destroy();\n    }\n  }\n  if (parent()) {\n    parent()->RemoveNode(this);\n  }\n}\n\nvoid AirElement::MarkPlatformNodeDestroyedRecursively() {\n  has_painting_node_ = false;\n  // All descent UI will be deleted recursively in platform side, should mark it\n  // recursively\n  for (size_t i = 0; i < GetChildCount(); ++i) {\n    auto child = GetChildAt(i);\n    child->MarkPlatformNodeDestroyedRecursively();\n    child->Destroy();\n    if (child->parent() == this) {\n      child->parent_ = nullptr;\n    }\n  }\n  children_.clear();\n}\n\nvoid AirElement::RemoveNode(AirElement *child, unsigned int index,\n                            bool destroy) {\n  if (index >= children_.size()) {\n    return;\n  }\n  bool destroy_platform_node = destroy && child->HasPaintingNode();\n  element_manager()->RemoveLayoutNode(impl_id(), child->impl_id());\n  if (child->HasPaintingNode()) {\n    child->element_container()->RemoveSelf(destroy_platform_node);\n  }\n  if (destroy_platform_node) {\n    child->MarkPlatformNodeDestroyedRecursively();\n  }\n  RemoveChildAt(index);\n  if (destroy) {\n    element_manager()->DestroyLayoutNode(child->impl_id());\n    element_manager()->air_node_manager()->Erase(child->impl_id());\n    element_manager()->air_node_manager()->EraseLepusId(child->GetLepusId(),\n                                                        child);\n  }\n}\n\nvoid AirElement::RemoveAirNode(AirElement *child, unsigned int index,\n                               bool destroy) {\n  if (index >= air_children_.size()) {\n    return;\n  }\n  RemoveAirChildAt(index);\n}\n\nvoid AirElement::UpdateLayout(float left, float top, float width, float height,\n                              const std::array<float, 4> &paddings,\n                              const std::array<float, 4> &margins,\n                              const std::array<float, 4> &borders,\n                              const std::array<float, 4> *sticky_positions,\n                              float max_height) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_UPDATE_LAYOUT);\n  frame_changed_ = true;\n  top_ = top;\n  left_ = left;\n  width_ = width;\n  height_ = height;\n  paddings_ = paddings;\n  margins_ = margins;\n  borders_ = borders;\n}\n\n/// for radon mode\nvoid AirElement::PushDynamicNode(AirElement *node) {\n  dynamic_nodes_.push_back(node);\n}\n\nAirElement *AirElement::GetDynamicNode(uint32_t index,\n                                       uint32_t lepus_id) const {\n  if (index >= dynamic_nodes_.size()) {\n    LOGF(\"GetDynamicNode overflow. node_index \"\n         << lepus_id << \" index: \" << index\n         << \" dynamic_nodes_.size(): \" << dynamic_nodes_.size());\n  }\n  auto *node = dynamic_nodes_[index];\n  if (node->GetLepusId() != lepus_id) {\n    LOGF(\"GetDynamicNode indices not equal. target node index \"\n         << lepus_id << \" but got: \" << node->GetLepusId());\n  }\n  return node;\n}\n\nvoid AirElement::UpdateUIChildrenCountInParent(int delta) {\n  if (IsLayoutOnly()) {\n    AirElement *parent = parent_;\n    while (parent) {\n      parent->ui_children_count += delta;\n      if (!parent->IsLayoutOnly()) {\n        break;\n      }\n      parent = static_cast<AirElement *>(parent->parent());\n    }\n  }\n}\n\n// element tree\nvoid AirElement::AddChildAt(AirElement *child, size_t index) {\n  children_.insert(\n      children_.begin() + index,\n      air_element_manager_->air_node_manager()->Get(child->impl_id()));\n  child->parent_ = this;\n}\n\nAirElement *AirElement::RemoveChildAt(size_t index) {\n  if (index < children_.size()) {\n    AirElement *removed = children_[index].get();\n    children_.erase(children_.begin() + index);\n    removed->parent_ = nullptr;\n    return removed;\n  }\n  return nullptr;\n}\n\nint AirElement::IndexOf(const AirElement *child) {\n  for (size_t index = 0; index != children_.size(); ++index) {\n    if (children_[index].get() == child) {\n      return static_cast<int>(index);\n    }\n  }\n  return kInvalidIndex;\n}\n\nvoid AirElement::AddAirChildAt(AirElement *child, size_t index) {\n  air_children_.insert(\n      air_children_.begin() + index,\n      air_element_manager_->air_node_manager()->Get(child->impl_id()));\n  child->air_parent_ = this;\n}\n\nAirElement *AirElement::RemoveAirChildAt(size_t index) {\n  if (index < air_children_.size()) {\n    auto removed = air_children_[index];\n    air_children_.erase(air_children_.begin() + index);\n    removed->air_parent_ = nullptr;\n    return removed.get();\n  }\n  return nullptr;\n}\n\nint AirElement::IndexOfAirChild(AirElement *child) {\n  if (child == nullptr) {\n    return kInvalidIndex;\n  }\n  for (size_t index = 0; index < air_children_.size(); ++index) {\n    if (air_children_[index].get() == child) {\n      return static_cast<int>(index);\n    }\n  }\n  return kInvalidIndex;\n}\n\nAirElement *AirElement::GetChildAt(size_t index) const {\n  if (index >= children_.size()) {\n    return nullptr;\n  }\n  return children_[index].get();\n}\n// TODO(liukeang): after transformation of air_element extending element,\n// relevant codes from element.cc can be reused.\nvoid AirElement::CheckHasNonFlattenCSSProps(CSSPropertyID id) {\n  if (has_non_flatten_attrs_) {\n    // never change has_non_flatten_attrs_ to false again\n    return;\n  }\n  // check is transition animation\n  if (CSSProperty::IsTransitionProps(id)) {\n    has_non_flatten_attrs_ = true;\n    return;\n  }\n\n  // check opacity props\n  if (UNLIKELY(id == CSSPropertyID::kPropertyIDOpacity) &&\n      !tag_.IsEquals(\"text\") && !tag_.IsEquals(\"image\")) {\n    has_non_flatten_attrs_ = true;\n    return;\n  }\n\n  // check specific CSS Property\n  if (id == CSSPropertyID::kPropertyIDFilter || id == kPropertyIDVisibility ||\n      id == kPropertyIDClipPath || id == CSSPropertyID::kPropertyIDBoxShadow ||\n      id == CSSPropertyID::kPropertyIDTransform ||\n      id == CSSPropertyID::kPropertyIDTransformOrigin ||\n      (id >= CSSPropertyID::kPropertyIDOutline &&\n       id <= CSSPropertyID::kPropertyIDOutlineWidth) ||\n      (id >= CSSPropertyID::kPropertyIDLayoutAnimationCreateDuration &&\n       id <= CSSPropertyID::kPropertyIDLayoutAnimationUpdateDelay)) {\n    has_non_flatten_attrs_ = true;\n  }\n}\n\nvoid AirElement::CheckHasNonFlattenAttr(const base::String &key,\n                                        const lepus::Value &value) {\n  if (has_non_flatten_attrs_) {\n    return;\n  }\n  // TODO(songshourui.null): When set exposure prop, the ui must not be\n  // flatten, since the flattenUI exposure rect may be not correct. Will\n  // remove the exposure prop when fix flattenUI exposure rect's bug.\n  static const base::NoDestructor<std::unordered_set<std::string>>\n      non_flatten_attr_set{{\"name\", \"clip-radius\", \"overlap\",\n                            \"enter-transition-name\", \"exit-transition-name\",\n                            \"pause-transition-name\", \"resume-transition-name\",\n                            \"exposure-scene\", \"exposure-id\"}};\n\n  if (non_flatten_attr_set->count(key.str())) {\n    has_non_flatten_attrs_ = true;\n  }\n}\n\nvoid AirElement::FlushFontSize() {\n  computed_css_style()->SetFontSize(font_size_, root_font_size_);\n  if (!EnableAsyncCalc()) {\n    element_manager()->UpdateLayoutNodeFontSize(id_, font_size_,\n                                                root_font_size_);\n  }\n  prop_bundle_->SetProps(\n      CSSProperty::GetPropertyName(CSSPropertyID::kPropertyIDFontSize).c_str(),\n      font_size_);\n}\n\nvoid AirElement::SetStyle(CSSPropertyID id, tasm::CSSValue &value) {\n  // After CSS Diff&Merge is completed, the entry method for processing css\n  // properties. Including four types:\n  // 1. animation\n  // 2. layoutOnly: set to starlight\n  // 3. layoutWanted: set to starlight & set to platform\n  // 4. other:set to platform\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_SET_STYLE);\n  // Handle animation properties\n  if (UNLIKELY(ResolveKeyframesMap(id, value.GetValue()))) {\n    has_animate_props_ = true;\n    ComputeCSSStyle(id, value);\n    return;\n  }\n  if (UNLIKELY(id == kPropertyIDFontSize)) {\n    auto result = starlight::CSSStyleUtils::ResolveFontSize(\n        value, element_manager()->GetLynxEnvConfig(),\n        element_manager()->GetLynxEnvConfig().ViewportWidth(),\n        element_manager()->GetLynxEnvConfig().ViewportHeight(), font_size_,\n        root_font_size_, element_manager()->GetCSSParserConfigs());\n    if (result.has_value() && *result != font_size_) {\n      font_size_ = *result;\n      FlushFontSize();\n      has_font_size_ = true;\n    }\n    return;\n  }\n  // The LayoutOnly css property only needs to be set to starlight\n  if (LayoutProperty::IsLayoutOnly(id)) {\n    ConsumeStyle(id, value);\n  } else {\n    // check if css props passing to platform are flatten-related\n    if (!has_animate_props_) {\n#if OS_ANDROID\n      // check flatten flag for Android platform if needed\n      // Normally, it's better to move below checks to Android platform side,\n      // but checking in C++ size has a better performance\n      CheckHasNonFlattenCSSProps(id);\n#endif\n    }\n    if (!has_transition_attrs_ && CSSProperty::IsTransitionProps(id)) {\n      has_transition_attrs_ = true;\n    }\n\n    // The LayoutWanted css property needs to be set to starlight, and also\n    // needs to be set to the platform.\n    if (LayoutProperty::IsLayoutWanted(id)) {\n      ConsumeStyle(id, value);\n      ComputeCSSStyle(id, value);\n      // set to the platform\n      PushToPropsBundle(CSSProperty::GetPropertyName(id), value.GetValue());\n    } else {\n      // Other css properties only need to be set to the platform layer\n      ComputeCSSStyle(id, value);\n      if (!CSSProperty::IsTransitionProps(id)) {\n        PushToPropsBundle(CSSProperty::GetPropertyName(id), value.GetValue());\n      }\n    }\n  }\n}\n\nvoid AirElement::ResetStyle(CSSPropertyID id) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_RESET_STYLE);\n  bool is_layout_only = LayoutProperty::IsLayoutOnly(id);\n  if (is_layout_only || LayoutProperty::IsLayoutWanted(id)) {\n    if (EnableAsyncCalc()) {\n      async_reset_styles_.insert(id);\n    } else {\n      air_element_manager_->ResetLayoutNodeStyle(impl_id(), id);\n    }\n    dirty_ |= kDirtyStyle;\n  }\n  if (is_layout_only) {\n    return;\n  }\n  has_layout_only_props_ = false;\n  prop_bundle_->SetNullProps(CSSProperty::GetPropertyName(id).c_str());\n  dirty_ |= kDirtyAttr;\n}\n\nstarlight::ComputedCSSStyle *AirElement::computed_css_style() {\n  if (!platform_css_style_ && air_element_manager_) {\n    platform_css_style_ = std::make_unique<starlight::ComputedCSSStyle>(\n        *air_element_manager_->platform_computed_css());\n    const auto &env_config = air_element_manager_->GetLynxEnvConfig();\n    platform_css_style_->SetScreenWidth(env_config.ScreenWidth());\n    platform_css_style_->SetViewportHeight(env_config.ViewportHeight());\n    platform_css_style_->SetViewportWidth(env_config.ViewportWidth());\n    platform_css_style_->SetCssAlignLegacyWithW3c(\n        air_element_manager_->GetLayoutConfigs().css_align_with_legacy_w3c_);\n    platform_css_style_->SetFontScaleOnlyEffectiveOnSp(\n        env_config.FontScaleSpOnly());\n    platform_css_style_->SetFontScale(env_config.FontScale());\n    platform_css_style_->SetLayoutUnit(env_config.PhysicalPixelsPerLayoutUnit(),\n                                       env_config.LayoutsUnitPerPx());\n  }\n  return platform_css_style_.get();\n}\n\nvoid AirElement::CheckHasAnimateProps(CSSPropertyID id) {\n  if (!has_animate_props_) {\n    has_animate_props_ =\n        (id >= CSSPropertyID::kPropertyIDTransform &&\n         id <= CSSPropertyID::kPropertyIDAnimationPlayState) ||\n        (id >= CSSPropertyID::kPropertyIDLayoutAnimationCreateDuration &&\n         id <= CSSPropertyID::kPropertyIDLayoutAnimationUpdateDelay) ||\n        (id >= CSSPropertyID::kPropertyIDTransition &&\n         id <= CSSPropertyID::kPropertyIDTransitionDuration) ||\n        (id == CSSPropertyID::kPropertyIDTransformOrigin);\n  }\n}\n\n// event handler\nvoid AirElement::SetEventHandler(const base::String &name,\n                                 EventHandler *handler) {\n  PreparePropBundleIfNeed();\n  prop_bundle_->SetEventHandler(handler->ToPubLepusValue());\n  has_layout_only_props_ = false;\n}\n\nvoid AirElement::ResetEventHandlers() {\n  if (prop_bundle_ != nullptr) {\n    prop_bundle_->ResetEventHandler();\n  }\n}\n\nsize_t AirElement::GetUIChildrenCount() const {\n  size_t ret = 0;\n  for (auto current : children_) {\n    if (current->IsLayoutOnly()) {\n      ret += current->GetUIChildrenCount();\n    } else {\n      ret++;\n    }\n  }\n  return ret;\n}\n\nsize_t AirElement::GetUIIndexForChild(AirElement *child) const {\n  int index = 0;\n  bool found = false;\n  for (auto it : children_) {\n    auto current = it;\n    if (child == current.get()) {\n      found = true;\n      break;\n    }\n    index += (current->IsLayoutOnly() ? current->GetUIChildrenCount() : 1);\n  }\n  if (!found) {\n    LOGE(\"air_element can not found:\" + tag_.str());\n    // Child id was not a child of parent id\n    // TODO(renpengcheng):logbox error\n    DCHECK(false);\n  }\n  return index;\n}\n\nvoid AirElement::CreateElementContainer(bool platform_is_flatten) {\n  element_container_ = std::make_unique<AirElementContainer>(this);\n  if (IsLayoutOnly()) {\n    return;\n  }\n  painting_context()->CreatePaintingNode(id_, tag_.str(), prop_bundle_,\n                                         platform_is_flatten, false, lepus_id_);\n}\n\nvoid AirElement::ConsumeStyle(CSSPropertyID id, const tasm::CSSValue &value) {\n  if (EnableAsyncCalc()) {\n    async_resolved_styles_.emplace_back(std::make_pair(id, value));\n  } else {\n    element_manager()->UpdateLayoutNodeStyle(impl_id(), id, value);\n  }\n  dirty_ |= kDirtyStyle;\n}\n\nvoid AirElement::FlushProps() { FlushPropsResolveStyles(true); }\n\nvoid AirElement::FlushPropsResolveStyles(bool resolve_styles) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_FLUSH_PROPS_RESOLVE_STYLES);\n  if (!resolve_styles && state_ & ElementState::kPropsUpdated) {\n    return;\n  };\n  if (resolve_styles && !is_page() && !is_virtual_node()) {\n    RefreshStyles();\n  }\n  PushKeyframesToPlatform();\n  // Only view and component can be optimized as layout only node\n  if (has_layout_only_props_ &&\n      !(tag_.IsEquals(\"view\") || tag_.IsEquals(\"component\"))) {\n    has_layout_only_props_ = false;\n  }\n\n  if (has_transition_attrs_) {\n    PushToPropsBundle(CSSProperty::GetPropertyName(kPropertyIDTransition),\n                      computed_css_style()->GetValue(kPropertyIDTransition));\n    has_transition_attrs_ = false;\n  }\n\n  // Update The root if needed\n  if (!has_painting_node_) {\n    TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_NO_PAINTING_NODE);\n    const auto &env_config = element_manager()->GetLynxEnvConfig();\n    if (EnableAsyncCalc()) {\n      // send to layoutcontext in main thread\n      if (has_font_size_) {\n        element_manager()->UpdateLayoutNodeFontSize(id_, font_size_,\n                                                    root_font_size_);\n      }\n      for (const auto &[css_id, css_value] : async_resolved_styles_) {\n        element_manager()->UpdateLayoutNodeStyle(impl_id(), css_id, css_value);\n      }\n      for (const auto &css_id : async_reset_styles_) {\n        element_manager()->ResetLayoutNodeStyle(impl_id(), css_id);\n      }\n      async_resolved_styles_.clear();\n      async_reset_styles_.clear();\n    }\n\n    if (Config::DefaultFontScale() != env_config.FontScale() &&\n        !has_font_size_ && (tag_.IsEquals(\"text\") || tag_.IsEquals(\"x-text\"))) {\n      font_size_ = root_font_size_ = env_config.PageDefaultFontSize();\n      FlushFontSize();\n    }\n    PreparePropBundleIfNeed();\n    element_manager()->AttachLayoutNodeType(impl_id(), tag_, false,\n                                            prop_bundle_);\n    is_virtual_ = air_element_manager_->IsShadowNodeVirtual(tag_);\n    set_is_layout_only(CanBeLayoutOnly() || is_virtual_);\n    CreateElementContainer(TendToFlatten());\n    has_painting_node_ = true;\n    enable_async_calc_ = false;\n  } else {\n    if (!prop_bundle_) {\n      prop_bundle_ =\n          element_manager()->GetPropBundleCreator()->CreatePropBundle();\n    }\n    element_manager()->UpdateLayoutNodeProps(impl_id(), prop_bundle_);\n    if (!is_virtual_) {\n      if (!IsLayoutOnly()) {\n        painting_context()->UpdatePaintingNode(impl_id(), TendToFlatten(),\n                                               prop_bundle_);\n      }\n      element_container_->SetPropsChanged(true);\n    }\n  }\n  prop_bundle_ = nullptr;\n  dirty_ = 0;\n  state_ |= ElementState::kPropsUpdated;\n}\n\nbool AirElement::CalcStyle(bool waiting) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_CALC_STYLE,\n              [&](lynx::perfetto::EventContext ctx) {\n                auto *debug = ctx.event()->add_debug_annotations();\n                debug->set_name(\"lepus_id\");\n                debug->set_string_value(std::to_string(lepus_id_));\n                auto *debug1 = ctx.event()->add_debug_annotations();\n                debug1->set_name(\"impl_id\");\n                debug1->set_string_value(std::to_string(id_));\n              });\n  if (!waiting && state_ & kStyleCalculating) {\n    return false;\n  }\n  std::lock_guard<std::mutex> guard(cal_mutex_);\n  if (state_ & kPropsUpdated || state_ & kStyleCalculated) {\n    return true;\n  }\n  state_ |= kStyleCalculating;\n  PreparePropBundleIfNeed();\n  if (!dynamic_inline_style_.empty()) {\n    SetInlineStyle(dynamic_inline_style_);\n    dynamic_inline_style_.clear();\n  }\n  // resolve static style\n  if (!static_inline_style_.empty()) {\n    for (const auto &[css_id, lepus_value] : static_inline_style_) {\n      SetInlineStyle(css_id, lepus_value);\n    }\n    static_inline_style_.clear();\n  }\n  if (!is_page() && !is_virtual_node()) {\n    RefreshStyles();\n  }\n  // resolve attributes\n  if (!raw_attributes_.empty()) {\n    for (const auto &[key, value] : raw_attributes_) {\n#if OS_ANDROID\n      CheckHasNonFlattenAttr(key, value);\n#endif\n      prop_bundle_->SetProps(key.c_str(), pub::ValueImplLepus(value));\n    }\n    raw_attributes_.clear();\n  }\n  state_ |= ElementState::kStyleCalculated;\n  return true;\n}\n\nvoid AirElement::FlushRecursively() {\n  if (is_virtual_node() && parent_) {\n    parent_->FlushRecursively();\n    return;\n  }\n  if (dirty_ & ~kDirtyTree || style_dirty_ > 0 || !has_painting_node_) {\n    FlushProps();\n  }\n  for (size_t i = 0; i < GetChildCount(); ++i) {\n    auto *child = GetChildAt(i);\n    bool has_flush_recursive = false;\n    bool child_has_painting_node = child->has_painting_node_;\n    if (child->dirty_ & ~kDirtyTree || child->style_dirty_ > 0 ||\n        !child_has_painting_node) {\n      child->FlushProps();\n    }\n    if (child->IsLayoutOnly()) {\n      child->FlushRecursively();\n      has_flush_recursive = true;\n    }\n    if (!child_has_painting_node || child->has_been_removed_ ||\n        !child->layout_node_inserted_) {\n      InsertNodeIndex(child, i);\n      child->has_been_removed_ = false;\n    }\n    //    if (!(child->layout_node_->parent())) {\n    //      // In case the order of multiple flushes is not as expected, insert\n    //      again\n    //      // to ensure that the nodes are correct.\n    //      InsertNodeIndex(child, i);\n    //    }\n    if (!has_flush_recursive) {\n      child->FlushRecursively();\n    }\n  }\n}\n\nvoid AirElement::PreparePropBundleIfNeed() {\n  if (!prop_bundle_) {\n    prop_bundle_ =\n        element_manager()->GetPropBundleCreator()->CreatePropBundle();\n  }\n}\n\nbool AirElement::TendToFlatten() const {\n  return config_flatten_ && !has_animate_props_ && !has_non_flatten_attrs_;\n}\n\nPaintingContext *AirElement::painting_context() {\n  return catalyzer_->painting_context();\n}\n\nlepus::Value AirElement::GetData() {\n  AirElement *component = GetParentComponent();\n  if (component) {\n    return component->GetData();\n  }\n  return lepus::Value();\n}\n\nlepus::Value AirElement::GetProperties() {\n  AirElement *component = GetParentComponent();\n  if (component->is_component()) {\n    return component->GetProperties();\n  }\n  return lepus::Value();\n}\n\nbool AirElement::CheckFlattenProp(const base::String &key,\n                                  const lepus::Value &value) {\n  if (key.IsEquals(\"flatten\")) {\n    if ((value.IsString() && value.StdString() == \"false\") ||\n        (value.IsBool() && !value.Bool())) {\n      config_flatten_ = false;\n      return true;\n    }\n    config_flatten_ = true;\n    return true;\n  }\n  return false;\n}\n\nvoid AirElement::SetClasses(const lepus::Value &class_names) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_SET_CLASSES);\n  if (class_names.IsEmpty()) {\n    classes_.clear();\n    style_dirty_ |= Selector::kCLASS;\n    return;\n  }\n\n  std::vector<std::string> class_name_vec;\n  base::SplitString(class_names.StringView(), ' ', class_name_vec);\n\n  if (classes_.size() != class_name_vec.size()) {\n    style_dirty_ |= Selector::kCLASS;\n  } else {\n    for (auto &class_name : class_name_vec) {\n      bool need_mark_style_dirty =\n          !(style_dirty_ & Selector::kCLASS) && !class_name.empty() &&\n          std::find(classes_.begin(), classes_.end(), class_name) ==\n              classes_.end();\n      if (need_mark_style_dirty) {\n        style_dirty_ |= Selector::kCLASS;\n        break;\n      }\n    }\n  }\n  if (style_dirty_ & Selector::kCLASS) {\n    classes_.swap(class_name_vec);\n  }\n}\n\nvoid AirElement::SetIdSelector(const lepus::Value &id_selector) {\n  const auto &input_str = id_selector.StdString();\n  if (input_str != id_selector_) {\n    id_selector_ = input_str;\n    PushToPropsBundle(BASE_STATIC_STRING(kElementIdSelector), id_selector);\n    style_dirty_ |= Selector::kID;\n  }\n}\n\nsize_t AirElement::FindInsertIndex(const SharedAirElementVector &target,\n                                   AirElement *child) {\n  // The lepus id of elements in vector are arranged from small to large. Find\n  // the first element whose lepus id is greater than the element to be inserted\n  // and insert the element in front of it. (In most cases, elements are\n  // inserted in order from small to large, so search from the end.)\n  size_t index = target.size();\n  for (auto iter = target.rbegin(); iter != target.rend(); ++iter) {\n    if (child->lepus_id_ < (*iter)->lepus_id_) {\n      index--;\n    } else {\n      break;\n    }\n  }\n  return index;\n}\n\nvoid AirElement::SetInlineStyle(CSSPropertyID id, const CSSValue &value) {\n  inline_style_map_[id] = value;\n  style_dirty_ |= Selector::kINLINE;\n}\n\nvoid AirElement::SetInlineStyle(CSSPropertyID id, tasm::CSSValue &&value) {\n  inline_style_map_[id] = std::move(value);\n  style_dirty_ |= Selector::kINLINE;\n}\n\nvoid AirElement::SetInlineStyle(CSSPropertyID id, const lepus::Value &value,\n                                bool resolve) {\n  if (resolve) {\n    tasm::StyleMap ret;\n    tasm::UnitHandler::Process(id, value, ret,\n                               air_element_manager_->GetCSSParserConfigs());\n    // key for aggregation css may change after UnitHandler::Process,\n    // e.g.: kPropertyIDBorderBottom-> kPropertyIDBorderBottomStyle\n    for (auto &[css_id, css_value] : ret) {\n      SetInlineStyle(css_id, std::move(css_value));\n    }\n  } else {\n    static_inline_style_[id] = value.ToLepusValue();\n  }\n}\n\nvoid AirElement::SetInlineStyle(const std::string &inline_style, bool resolve) {\n  if (resolve) {\n    auto splits = base::SplitStringByCharsOrderly<':', ';'>(inline_style);\n    // inline_style is empty means reset all inline styles\n    if (splits.size() < 2) {\n      style_dirty_ |= Selector::kINLINE;\n      inline_style_map_.clear();\n      return;\n    }\n    for (size_t i = 0; i + 1 < splits.size(); i += 2) {\n      std::string key = base::TrimString(splits[i]);\n      CSSPropertyID id = CSSProperty::GetPropertyID(key);\n      if (CSSProperty::IsPropertyValid(id)) {\n        std::string value = base::TrimString(splits[i + 1]);\n        auto css_values =\n            UnitHandler::Process(id, lepus::Value(std::move(value)),\n                                 air_element_manager_->GetCSSParserConfigs());\n        for (auto &[css_id, css_value] : css_values) {\n          SetInlineStyle(css_id, std::move(css_value));\n        }\n      }\n    }\n  } else {\n    dynamic_inline_style_ = inline_style;\n  }\n}\n\nvoid AirElement::DiffStyles(StyleMap &old_map, const StyleMap &new_map,\n                            StylePatch &style_patch, bool is_final,\n                            bool is_dirty) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_DIFF_STYLES);\n  // Selector has not changed\n  if (new_map.empty()) {\n    for (auto &old_map_item : old_map) {\n      // Delete those css ids that need to be updated in the low priority but\n      // need to be reserved in the high priority from the update_map of the\n      // patch\n      auto exist = style_patch.update_styles_map_.find(old_map_item.first);\n      if (exist != style_patch.update_styles_map_.end()) {\n        style_patch.update_styles_map_.erase(exist);\n      }\n\n      if (is_dirty) {\n        // When new_map is empty but style is dirty, all ids in old_map need to\n        // be reset\n        style_patch.reset_id_set_.insert(old_map_item.first);\n      } else {\n        // When new_map is empty and style is not dirty, all ids in old_map need\n        // to be reserved\n        style_patch.reserve_styles_map_.insert_or_assign(old_map_item.first,\n                                                         old_map_item.second);\n      }\n    }\n  } else {\n    for (auto &new_map_item : new_map) {\n      style_patch.reset_id_set_.erase(new_map_item.first);\n      auto old_map_iterator = old_map.find(new_map_item.first);\n      // Exists in new_map but not in old_map\n      if (old_map_iterator == old_map.end() ||\n          new_map_item.second != old_map_iterator->second) {\n        // Delete those css ids that need to be reserved in low priority but\n        // need to be update in high priority from the reserve_set of patch\n        auto exist = style_patch.reserve_styles_map_.find(new_map_item.first);\n        if (exist != style_patch.reserve_styles_map_.end()) {\n          style_patch.reserve_styles_map_.erase(exist);\n        }\n        style_patch.update_styles_map_.insert_or_assign(new_map_item.first,\n                                                        new_map_item.second);\n        // Delete the css ids that have been processed, and determine which ones\n        // need to be reset from the remaining ids\n        old_map.erase(old_map_iterator);\n      } else {\n        // Delete those css ids that need to be updated in the low priority but\n        // need to be reserved in the high priority from the update_map of the\n        // patch\n        auto exist =\n            style_patch.update_styles_map_.find(old_map_iterator->first);\n        if (exist != style_patch.update_styles_map_.end()) {\n          style_patch.update_styles_map_.erase(exist);\n        }\n        // When new_map is empty, all ids in old_map need to be kept\n        style_patch.reserve_styles_map_.insert_or_assign(\n            old_map_iterator->first, old_map_iterator->second);\n        // Delete the css ids that have been processed, and determine which ones\n        // need to be reset from the remaining ids\n        old_map.erase(old_map_iterator);\n      }\n    }\n    // Determine the id that needs to be reset in this diff\n    for (auto &old_map_item : old_map) {\n      style_patch.reset_id_set_.insert(old_map_item.first);\n    }\n  }\n\n  if (is_final) {\n    // Handle the id appearing in reserve_map or update_map in reset_set.'reset'\n    // has the lowest priority and needs to be removed from reset_set\n    std::unordered_set<CSSPropertyID>::iterator iterator =\n        style_patch.reset_id_set_.begin();\n    while (iterator != style_patch.reset_id_set_.end()) {\n      if (style_patch.update_styles_map_.find(*iterator) !=\n          style_patch.update_styles_map_.end()) {\n        style_patch.reset_id_set_.erase(iterator++);\n      } else {\n        auto reserve_iterator = style_patch.reserve_styles_map_.find(*iterator);\n        if (reserve_iterator != style_patch.reserve_styles_map_.end()) {\n          style_patch.update_styles_map_.insert_or_assign(\n              *iterator, reserve_iterator->second);\n          style_patch.reset_id_set_.erase(iterator++);\n          style_patch.reserve_styles_map_.erase(reserve_iterator);\n        } else {\n          ++iterator;\n        }\n      }\n    }\n  }\n}\n\nvoid AirElement::RefreshStyles() {\n  // Diff&merge the css StyleMap corresponding to global, tag, class, id and\n  // inline in order to get stylePatch\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_REFRESH_STYLES);\n  StylePatch style_patch;\n  UpdateStylePatch(Selector::kSTABLE, style_patch);\n  UpdateStylePatch(Selector::kCLASS, style_patch);\n  UpdateStylePatch(Selector::kID, style_patch);\n  UpdateStylePatch(Selector::kINLINE, style_patch);\n\n  // Reset the css props in reset_id_set_\n  PreparePropBundleIfNeed();\n  if (!style_patch.reset_id_set_.empty()) {\n    for (auto css_id : style_patch.reset_id_set_) {\n      ResetStyle(css_id);\n    }\n  }\n  // Update the css props in update_styles_map_\n  if (!style_patch.update_styles_map_.empty()) {\n    for (auto &style : style_patch.update_styles_map_) {\n      SetStyle(style.first, style.second);\n    }\n  }\n\n  inline_style_map_.clear();\n  style_dirty_ = 0;\n}\n\nvoid AirElement::UpdateStylePatch(Selector selector,\n                                  AirElement::StylePatch &style_patch) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_UPDATE_STYLE_PATCH);\n  // first screen & not style_dirty_\n  if (!has_painting_node_ && !(style_dirty_ & selector)) {\n    return;\n  }\n  StyleMap selector_styles;\n  auto old_selector_styles_iterator = cur_css_styles_.find(selector);\n  if (old_selector_styles_iterator == cur_css_styles_.end()) {\n    // first screen\n    GetStyleMap(selector, selector_styles);\n    style_patch.update_styles_map_.insert(selector_styles.begin(),\n                                          selector_styles.end());\n  } else {\n    // Decide whether to get a new styleMap through style_dirty_.\n    //'SetClass', 'SetId' and 'SetInlineStyle' will change style_dirty_value\n    if (style_dirty_ & selector) {\n      GetStyleMap(selector, selector_styles);\n    }\n    // Diff old styleMap and new styleMap(may be empty), and update stylePatch\n    DiffStyles(old_selector_styles_iterator->second, selector_styles,\n               style_patch, selector == Selector::kINLINE,\n               style_dirty_ & selector);\n  }\n\n  if (style_dirty_ & selector) {\n    if (selector_styles.empty()) {\n      cur_css_styles_.erase(selector);\n    } else {\n      cur_css_styles_.insert_or_assign(selector, selector_styles);\n    }\n  }\n}\nvoid AirElement::GetStyleMap(Selector selector, StyleMap &result) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_GET_STYLE_MAP);\n  switch (selector) {\n    case Selector::kSTABLE:\n      GetStableStyleMap(tag_.str(), result);\n      break;\n    case Selector::kCLASS:\n      GetClassStyleMap(classes_, result);\n      break;\n    case Selector::kID:\n      GetIdStyleMap(id_selector_, result);\n      break;\n    case Selector::kINLINE:\n      result = inline_style_map_;\n      break;\n    default:\n      LOGE(\"invalid css selector\");\n  }\n}\n\nvoid AirElement::GetStableStyleMap(const std::string &tag_name,\n                                   StyleMap &result) {\n  if (is_page() || is_component()) {\n    static constexpr const char *kGlobal = \"*\";\n    auto iterator = parsed_styles_.find(kGlobal);\n    if (iterator != parsed_styles_.end()) {\n      result = *(iterator->second);\n    }\n    if (!tag_name.empty()) {\n      StyleMap tag_style_map;\n      iterator = parsed_styles_.find(tag_name);\n      if (iterator != parsed_styles_.end()) {\n        tag_style_map = *(iterator->second);\n      }\n      MergeHigherPriorityCSSStyle(result, tag_style_map);\n    }\n  } else {\n    AirElement *parent_component = GetParentComponent();\n    if (parent_component) {\n      parent_component->GetStableStyleMap(tag_name, result);\n    }\n  }\n}\n\nvoid AirElement::GetClassStyleMap(const ClassVector &class_list,\n                                  StyleMap &result) {\n  if (is_page() || is_component()) {\n    for (const auto &class_name : class_list) {\n      auto iterator = parsed_styles_.find(\".\" + class_name);\n      if (iterator != parsed_styles_.end()) {\n        const auto &class_css_styles = *(iterator->second);\n        MergeHigherPriorityCSSStyle(result, class_css_styles);\n      }\n    }\n  } else {\n    if (class_list.empty()) {\n      return;\n    }\n    AirElement *parent_component = GetParentComponent();\n    if (parent_component) {\n      parent_component->GetClassStyleMap(class_list, result);\n    }\n  }\n}\n\nvoid AirElement::GetIdStyleMap(const std::string &id_name, StyleMap &result) {\n  if (is_page() || is_component()) {\n    auto iterator = parsed_styles_.find(\"#\" + id_name);\n    if (iterator != parsed_styles_.end()) {\n      result = *(iterator->second);\n    }\n  } else {\n    if (id_name.empty()) {\n      return;\n    }\n    AirElement *parent_component = GetParentComponent();\n    if (parent_component) {\n      parent_component->GetIdStyleMap(id_name, result);\n    }\n  }\n}\n\nvoid AirElement::GetKeyframesMap(const std::string &keyframes_name,\n                                 StyleMap &result) {\n  if (keyframes_name.empty()) {\n    return;\n  }\n  if (is_page() || is_component()) {\n    auto iterator = parsed_styles_.find(\"$keyframes\" + keyframes_name);\n    if (iterator != parsed_styles_.end()) {\n      result = *(iterator->second);\n    }\n  } else {\n    AirElement *parent_component = GetParentComponent();\n    if (parent_component) {\n      parent_component->GetKeyframesMap(keyframes_name, result);\n    }\n  }\n}\n\nvoid AirElement::PushToPropsBundle(const base::String &key,\n                                   const lepus::Value &value) {\n  TRACE_EVENT(LYNX_TRACE_CATEGORY, AIR_ELEMENT_PUSH_TO_PROPS_BUNDLE);\n  PreparePropBundleIfNeed();\n  has_layout_only_props_ = false;\n  prop_bundle_->SetProps(key.c_str(), pub::ValueImplLepus(value));\n  dirty_ |= kDirtyAttr;\n}\n\nbool AirElement::AirComputedCSSStyle::Process(CSSPropertyID css_property_id,\n                                              CSSValuePattern pattern,\n                                              lepus::Value &value) {\n  return ProcessWithPattern(pattern, value);\n}\n\n// this function is not used currently.\nbool AirElement::AirComputedCSSStyle::ProcessWithPattern(\n    CSSValuePattern attr_pattern, lepus::Value &attr_value) {\n  // compute with density when pattern is px. eg:fontSize\n  if (attr_pattern == CSSValuePattern::PX) {\n    //    attr_value.SetNumber(attr_value.Number() * Config::Density());\n    return true;\n  } else if (attr_pattern == CSSValuePattern::RPX) {\n    //    attr_value.SetNumber(attr_value.Number() * Config::Width() /\n    //    kRpxRatio);\n    return true;\n  }\n  return false;\n}\n\n/**\n * + config_enable_layout_only_: 1. LynxViewBuilder.setEnableLayoutOnly, 2.\n * PageConfig decode 'enableNewLayoutOnly'\n *\n * + has_layout_only_props_:\n * SetAttribute、SetEventHandler、view+component\n */\nbool AirElement::CanBeLayoutOnly() const {\n  return element_manager()->GetEnableLayoutOnly() && has_layout_only_props_;\n}\n\n}  // namespace tasm\n}  // namespace lynx\n"
  },
  {
    "path": "core/runtime/lepus/compiler/unit_test/number.prototype.tofixed.js",
    "content": ""
  },
  {
    "path": "devtool/base_devtool/android/base_devtool/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "platform/android/lynx_devtool/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "platform/android/lynx_xelement/lynx_xelement/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "platform/android/lynx_xelement/lynx_xelement_input/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "platform/android/lynx_xelement/lynx_xelement_overlay/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "platform/android/lynx_xelement/lynx_xelement_refresh/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "platform/android/lynx_xelement/lynx_xelement_svg/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "platform/android/testing_base/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "platform/harmony/lynx_devtool/consumer-rules.txt",
    "content": ""
  },
  {
    "path": "platform/harmony/lynx_services/lynx_devtool_service/consumer-rules.txt",
    "content": ""
  },
  {
    "path": "platform/harmony/lynx_services/lynx_http_service/consumer-rules.txt",
    "content": ""
  },
  {
    "path": "platform/harmony/lynx_services/lynx_image_service/consumer-rules.txt",
    "content": ""
  },
  {
    "path": "platform/harmony/lynx_services/lynx_log_service/consumer-rules.txt",
    "content": ""
  },
  {
    "path": "platform/harmony/lynx_xelement/svg/consumer-rules.txt",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/scripts/idl_parser/__init__.py",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/templates/napi_command_buffer.h.tmpl",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/third_party/examples/bench/django/templatetags/__init__.py",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/third_party/mako/ext/__init__.py",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/third_party/pyjson5/src/json5/fakes/__init__.py",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/third_party/pyjson5/src/json5/tests/__init__.py",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/third_party/pyjson5/src/tests/__init__.py",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/third_party/typ/tools/__init__.py",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/third_party/typ/typ/fakes/__init__.py",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/third_party/typ/typ/fakes/tests/__init__.py",
    "content": ""
  },
  {
    "path": "third_party/binding/idl-codegen/third_party/typ/typ/tests/__init__.py",
    "content": ""
  }
]